diff --git a/.env.example b/.env.example index 2b67d8bf67cd..bf7f8042c9c2 100644 --- a/.env.example +++ b/.env.example @@ -22,9 +22,12 @@ JWT_SECRET= API_KEY_SECRET= # Initial admin login password — CHANGE THIS before first use! -# Used by: bootstrap only — sets the initial dashboard password on first boot. +# Used by: src/lib/auth/managementPassword.ts — sets the dashboard password at startup +# while none is saved yet; ignored once a password is saved. # After first login you can change it from Dashboard → Settings → Security. -# Default: CHANGEME (insecure, for local dev only) +# CHANGEME (this template's value) is publicly known. npm and source installs copy this +# file to .env, so replace CHANGEME there before first boot, or set INITIAL_PASSWORD= +# (empty) there to create the password in the dashboard's onboarding wizard instead. INITIAL_PASSWORD=CHANGEME # ═══════════════════════════════════════════════════════════════════════════════ @@ -317,6 +320,21 @@ OMNIROUTE_USE_TURBOPACK=1 # hints in production logs. # OMNIROUTE_PROXY_FETCH_DEBUG=true +# Absolute path of the local proxy core binary (a file named `sing-box`) used to check a +# generated core configuration before it replaces the adopted file. Unset = write +# `.generated` beside the adopted file only. Host environment only: never stored in the +# database nor accepted through the management API, because the binary is executed. +# Used by: src/lib/proxySubscription/coreConfig/sync.ts. +# OMNIROUTE_PROXY_CORE_BINARY_PATH=/usr/bin/sing-box + +# How the local proxy core reloads after a verified config replacement. +# Either a JSON argument array run without a shell, e.g. +# ["systemctl-reload-sing-box"] is NOT valid (must be an absolute path array), +# or exactly `external` when an outside observer applies the file. +# Never stored in the database nor exposed via the API. +# Used by: src/lib/proxySubscription/coreConfig/reload.ts. +# OMNIROUTE_PROXY_CORE_RELOAD_COMMAND=["/usr/bin/systemctl","--user","reload","sing-box"] + # Set to "true" or "1" to include client/egress IPs and the account prefix in # the verbose `[ProxyEgress]` process-log line (src/lib/proxyLogger.ts). Kept # OFF by default so the process log does not leak IPs or the account prefix. @@ -552,20 +570,36 @@ ALLOW_API_KEY_REVEAL=false # CORS_ORIGIN=https://your-frontend.example.com # legacy single-origin alias # CORS_ALLOW_ALL=false -# Allow provider URLs pointing to private/local networks (localhost, 192.168.x.x, etc.). -# REQUIRED for self-hosted providers: LM Studio, Ollama, vLLM, Llamafile, Triton, etc. -# Used by: src/shared/network/outboundUrlGuard.ts — disables SSRF guard for provider calls. -# Default: false (blocked) | Set true to enable local providers. +# Turns off the outbound URL guard's host checks, cloud-metadata block included, on the +# provider paths that follow the guard mode: URL validation, model discovery, provider-node +# base URLs (including remote rerank nodes), and the proxy-fallback test, among other +# provider-adjacent outbound paths such as remote image/media fetch. It also allows +# private webhook targets, except cloud-metadata/link-local endpoints, which stay +# blocked even with this flag on. Local providers work with the defaults: +# OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS below (default true) already allows local and LAN +# URLs. A value saved from the dashboard toggle takes precedence over this variable. +# Details: docs/reference/ENVIRONMENT.md. +# Used by: src/shared/network/outboundUrlGuardPolicy.ts +# Default: false # OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true -# Allow adding/validating providers on local/private addresses (127.0.0.1, localhost, LAN). -# Used by: src/shared/network/outboundUrlGuard.ts — scopes to the provider validation path and -# still blocks cloud-metadata (169.254.169.254, metadata.google.internal). Default: true -# (OmniRoute is local-first). Set false to enforce strict public-only blocking. +# Allow provider URLs on local and private addresses (127.0.0.1, localhost, LAN). +# Default: true (OmniRoute is local-first); the guard then blocks cloud-metadata +# endpoints — all of 169.254.0.0/16 plus the known metadata hostnames. Set false to +# block private and loopback hosts too (public-only mode). The guard checks the +# hostname or IP literal as written first; in public-only mode the safe-fetch +# wrapper (validation, model discovery) also resolves the name and refuses +# private answers. +# Built-in local providers skip these checks for key validation and chat. +# A value saved from the dashboard toggle takes precedence over this variable. +# Used by: src/shared/network/outboundUrlGuardPolicy.ts # OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false -# Legacy alias toggling the SSRF guard. Used by: src/shared/network/outboundUrlGuard.ts -# When unset, OmniRoute uses the per-feature defaults. Set to "false"/"0" to disable. +# Legacy alias; its dashboard toggle (a DB override) is read before this environment value. +# "false", "0", "no", or "off" in either turns the host checks off the way +# OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true does, even when that flag's dashboard toggle +# is saved off. Other values leave the guard to the two flags above. +# Used by: src/shared/network/outboundUrlGuardPolicy.ts # OUTBOUND_SSRF_GUARD_ENABLED=true # ── Self-hosted unified OpenAI-compatible entry (RIC-738, D4) ──────────────────── @@ -888,6 +922,12 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true # Set to 1 only for legacy diagnostics. Values above 256 are capped. # OMNIROUTE_PROXY_DISPATCHER_CONNECTIONS=32 +# HTTP/2 negotiation for Undici upstream dispatchers (direct, HTTP/SOCKS proxies, +# relays and their retries). Explicit false/0/no/off forces HTTP/1.1. +# Does not control wreq TLS fingerprinting or provider-specific transports. +# Restart OmniRoute after changing this value; dispatchers are cached. +# OMNIROUTE_UPSTREAM_HTTP2_ENABLED=true + # SOCKS5 handshake (connect) timeout in ms (default 10000, capped at 120000). # Raise it when a single residential gateway host is hit by high concurrency # (e.g. 100 simultaneous requests): the real SOCKS5 handshake can exceed 10s @@ -1686,6 +1726,8 @@ CURSOR_USER_AGENT="Cursor/3.4" # FETCH_TIMEOUT_MS=600000 # Total request timeout (default: 600000 = 10 min) # # Also drives anthropic-compatible-cc-* X-Stainless-Timeout. # FETCH_HEADERS_TIMEOUT_MS=600000 # Time to receive response headers +# Maximum response-header wait (ms) for streaming requests. Default: 110000; 0 disables. +# OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS=110000 # FETCH_BODY_TIMEOUT_MS=600000 # Time to receive full response body # FETCH_CONNECT_TIMEOUT_MS=30000 # TCP connection establishment (default: 30s) # FETCH_KEEPALIVE_TIMEOUT_MS=4000 # Keep-alive socket idle timeout (default: 4s) @@ -2234,6 +2276,13 @@ APP_LOG_TO_FILE=true # Default: 2592000000 (30 days) # OMNIROUTE_SYNCED_CATALOG_STALE_AFTER_MS=2592000000 +# Response cache for GET /api/usage/analytics (#15629). Only responses that took at +# least MIN_COMPUTE_MS to build are kept, per query string, for TTL_MS. +# Used by: src/lib/usage/analyticsResponseCache.ts +# Default: 60000 (60 seconds; 0 disables) and 1000 (1 second) +# OMNIROUTE_ANALYTICS_CACHE_TTL_MS=60000 +# OMNIROUTE_ANALYTICS_CACHE_MIN_COMPUTE_MS=1000 + # ── NanoBanana (Image Generation) ── # Polling config for async image generation jobs. # Used by: open-sse/handlers/imageGeneration.ts @@ -2488,6 +2537,23 @@ APP_LOG_TO_FILE=true # never used for routing). "true" (or 1, yes) enables it. # PROXY_POOL_EGRESS_OBSERVATION=false +# Opt-in: skip the scheduler probe for proxies already proven by recent +# production traffic (single provider, no recent proxy failure). Default: off. +# PROXY_HEALTH_PASSIVE_SKIP=false +# Window in ms of production traffic the passive skip looks at (60000..599999). +# Default: 300000 (5min). +# PROXY_PASSIVE_WINDOW_MS=300000 +# Opt-in feature flag (default off; a dashboard DB override wins over this value): accept +# operator-pushed dated observed addresses per pool member and merge them with the journal +# read for display and pool order. "true" (or 1, yes) enables the push route and the merge. +# PROXY_OPERATOR_EGRESS_ENABLED=false + +# Observation window for operator-pushed egress rows (read on every read; no restart +# needed). Bounded to 1..168; out-of-range, non-numeric, or empty falls back to 24 with a +# single process-wide warning, never a throw. +# Used by: src/lib/db/proxyOperatorEgress.ts. +# PROXY_OPERATOR_EGRESS_WINDOW_HOURS=24 + # Allow OAuth and provider validation flows to bypass a pinned proxy and connect # directly when proxy reachability pre-checks fail. Default: false. # Also configurable from Dashboard > Settings > Feature Flags. @@ -2874,9 +2940,8 @@ APP_LOG_TO_FILE=true # hosted outside localhost, e.g. a LAN box or Tailscale peer running TEI/Infinity/vLLM. # OFF by default: routing to a remote host changes egress identity, so it must be an # explicit operator decision. Loopback/private nodes (localhost, 127.0.0.1, -# 172.16-31.x) are always allowed and unaffected by this flag. Remote nodes must also -# pass the provider outbound URL policy (see OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS); -# cloud-metadata hosts are never routed to. +# 172.16-31.x) are always allowed and unaffected by this flag. A remote node's base URL +# must also pass the provider outbound URL policy (see OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS). # RERANK_REMOTE_PROVIDER_NODES=false # ── Free Proxy Pool (auto-sync scheduler) ── @@ -3017,6 +3082,8 @@ APP_LOG_TO_FILE=true # OMNIROUTE_TRANSLATION_MODEL=gpt-4o-mini # Per-request timeout in milliseconds (default 60000). # OMNIROUTE_TRANSLATION_TIMEOUT_MS=60000 +# Optional reasoning control, sent as-is to the translation backend when set (e.g. none). +# OMNIROUTE_TRANSLATION_REASONING_EFFORT= # Number of parallel translation requests (default 4). # OMNIROUTE_TRANSLATION_CONCURRENCY=4 diff --git a/.env.selfhost.example b/.env.selfhost.example index d30e58a470b0..27d6b77fb043 100644 --- a/.env.selfhost.example +++ b/.env.selfhost.example @@ -23,11 +23,15 @@ APP_BIND_HOST=127.0.0.1 # ── Auth ────────────────────────────────────────────────────────────── # false = the dashboard and /v1 proxy are open to APP_BIND_HOST's network. -# true = every request needs an API key / dashboard login. The dashboard -# auto-creates INITIAL_PASSWORD on first boot (read it from the logs: -# `docker logs omniroute | grep -i password`). # EDIT ME — set true. +# true = /v1 requests need an API key (a logged-in dashboard session also works); +# the dashboard needs login once a password +# exists. Create the login password in the onboarding wizard on first visit +# (skipping that step turns dashboard login off, even with +# REQUIRE_API_KEY=true); after you submit it, the wizard asks for +# a one-time token from `docker logs omniroute | grep BOOTSTRAP`. +# # EDIT ME — set true. REQUIRE_API_KEY=false -# INITIAL_PASSWORD= # uncomment to pre-seed the dashboard password +# INITIAL_PASSWORD=strong-password-here # fill in to pre-seed the dashboard password (an empty value behaves like unset) # ── Memory ceiling (V8 old-space) ────────────────────────────────────── # 1024 = dashboard + light chat. Coding agents (long POST /v1/responses diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffdb51596dda..b22666cdf7b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -741,6 +741,10 @@ jobs: - run: npm run build env: OMNIROUTE_USE_TURBOPACK: "1" + - name: Verify colocated compression worker + run: node --import tsx/esm --test tests/integration/compression-worker-standalone.int.test.ts + env: + RUN_STANDALONE_INT: "1" - name: Archive Next.js build for downstream jobs # Use tar so the archive preserves paths relative to CWD (.build/next/...). # upload-artifact path-stripping is ambiguous when exclude patterns are used; @@ -1066,7 +1070,7 @@ jobs: - name: Upload test results to Trunk (advisory) if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} continue-on-error: true - uses: trunk-io/analytics-uploader@817e1a2ec58d888825699ee15ea172809bb3f226 # v2.1.3 + uses: trunk-io/analytics-uploader@14c0f18990953eecd01c9c5afe3cc63a73b6c371 # v2.1.5 with: junit-paths: trunk-junit/**/*.xml org-slug: omniroute @@ -1399,7 +1403,7 @@ jobs: - name: Upload test results to Trunk (advisory) if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} continue-on-error: true - uses: trunk-io/analytics-uploader@817e1a2ec58d888825699ee15ea172809bb3f226 # v2.1.3 + uses: trunk-io/analytics-uploader@14c0f18990953eecd01c9c5afe3cc63a73b6c371 # v2.1.5 with: junit-paths: junit-e2e-results.xml org-slug: omniroute diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7d802828f912..1ee115d45a3b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,10 +22,10 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: javascript-typescript queries: security-extended - - uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1 + - uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: category: "/language:javascript-typescript" diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 2d00eea44c35..b027708e1286 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -584,7 +584,7 @@ jobs: - name: Upload Trivy SARIF to Security tab if: needs.prepare.outputs.version != 'main' continue-on-error: true - uses: github/codeql-action/upload-sarif@v4.38.1 + uses: github/codeql-action/upload-sarif@v4.38.2 with: sarif_file: trivy-results.sarif category: trivy-image diff --git a/.github/workflows/nightly-compat.yml b/.github/workflows/nightly-compat.yml index 9515d6b21892..0b940477eebc 100644 --- a/.github/workflows/nightly-compat.yml +++ b/.github/workflows/nightly-compat.yml @@ -27,6 +27,7 @@ concurrency: jobs: resolve-branch: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: Resolve active release branch runs-on: ubuntu-latest outputs: @@ -57,6 +58,7 @@ jobs: echo "target=$TARGET" >> "$GITHUB_OUTPUT" compat-build-26: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: Node 26 Compatibility Build runs-on: ubuntu-latest timeout-minutes: 25 @@ -87,6 +89,7 @@ jobs: OMNIROUTE_USE_TURBOPACK: "0" compat-tests: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: Node ${{ matrix.node }} Compat Tests (${{ matrix.shard }}/4) runs-on: ubuntu-latest timeout-minutes: 25 @@ -117,7 +120,7 @@ jobs: report: name: Open / update tracking issue on failure runs-on: ubuntu-latest - if: ${{ !cancelled() && (needs.compat-tests.result == 'failure' || needs.compat-build-26.result == 'failure') }} + if: ${{ (!cancelled() && (needs.compat-tests.result == 'failure' || needs.compat-build-26.result == 'failure')) && (github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute') }} needs: [resolve-branch, compat-build-26, compat-tests] permissions: issues: write diff --git a/.github/workflows/nightly-llm-security.yml b/.github/workflows/nightly-llm-security.yml index e0631f4f0477..1f74d209f685 100644 --- a/.github/workflows/nightly-llm-security.yml +++ b/.github/workflows/nightly-llm-security.yml @@ -9,6 +9,7 @@ permissions: jobs: promptfoo-guard: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: promptfoo — injection guard (block mode, no secret) # #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build # release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`: @@ -56,6 +57,7 @@ jobs: run: kill "$(cat server.pid)" || true garak: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: garak probes (skip without provider secret) # #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build # release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`: diff --git a/.github/workflows/nightly-mutation.yml b/.github/workflows/nightly-mutation.yml index 9ffb5c22b5b4..3c1cf824528f 100644 --- a/.github/workflows/nightly-mutation.yml +++ b/.github/workflows/nightly-mutation.yml @@ -9,6 +9,7 @@ permissions: jobs: stryker: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: Stryker mutation (batch ${{ matrix.batch.name }} — advisory) runs-on: ubuntu-latest # Mutation testing is expensive. History of the budget: @@ -144,7 +145,7 @@ jobs: mutation-ratchet: name: Mutation score ratchet (blocking) needs: stryker - if: always() + if: ${{ always() && (github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute') }} runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/.github/workflows/nightly-property.yml b/.github/workflows/nightly-property.yml index 2ef7a543aff5..ad5c1a6468fc 100644 --- a/.github/workflows/nightly-property.yml +++ b/.github/workflows/nightly-property.yml @@ -8,6 +8,7 @@ permissions: issues: write jobs: property-random-seed: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/nightly-release-green.yml b/.github/workflows/nightly-release-green.yml index 53f8d15e82d7..99e25fc30e8d 100644 --- a/.github/workflows/nightly-release-green.yml +++ b/.github/workflows/nightly-release-green.yml @@ -65,7 +65,7 @@ jobs: name: Validate active release branch # On a push, only run for release/* pushes — a push to main is handled by the # main-green job below. Schedule/dispatch always run (they validate the highest release). - if: ${{ github.event_name != 'push' || startsWith(github.ref_name, 'release/') }} + if: ${{ (github.event_name != 'push' || startsWith(github.ref_name, 'release/')) && (github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute') }} # Dynamic runner: with USE_VPS_RUNNER=true (release window / on-demand pre-flight) # this runs on the dedicated VPS runner — clean env (no operator OMNIROUTE_API_KEY, # no local noauth CLIs => zero machine-specific false positives) and no contention. @@ -275,7 +275,7 @@ jobs: name: Validate main branch # On a push, only run for a push to main — a push to release/* is handled by # release-green above. Schedule/dispatch always run (they also sweep main). - if: ${{ github.event_name != 'push' || github.ref_name == 'main' }} + if: ${{ (github.event_name != 'push' || github.ref_name == 'main') && (github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute') }} runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-build"]')) || 'ubuntu-latest' }} env: JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation @@ -436,7 +436,7 @@ jobs: # walk each time. Detection stays on push (release-green above); banking is batched. bank-ratchet-shrinks: name: Bank ratchet shrinks - if: ${{ github.event_name != 'push' }} + if: ${{ github.event_name != 'push' && (github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute') }} runs-on: ubuntu-latest permissions: contents: write @@ -567,7 +567,7 @@ jobs: # first red PR. Advisory: never fails the workflow. baseline-headroom: name: Baseline headroom - if: ${{ github.event_name != 'push' }} + if: ${{ github.event_name != 'push' && (github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute') }} timeout-minutes: 60 runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-light"]')) || 'ubuntu-latest' }} permissions: diff --git a/.github/workflows/nightly-resilience.yml b/.github/workflows/nightly-resilience.yml index 0961791828fd..516bd8aa06b3 100644 --- a/.github/workflows/nightly-resilience.yml +++ b/.github/workflows/nightly-resilience.yml @@ -9,6 +9,7 @@ permissions: jobs: heap: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: Heap-growth gate runs-on: ubuntu-latest steps: @@ -23,6 +24,7 @@ jobs: - run: npm run test:heap chaos: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: Resilience chaos (fault injection) runs-on: ubuntu-latest steps: @@ -37,6 +39,7 @@ jobs: - run: npm run test:chaos k6-soak: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: k6 load/soak runs-on: ubuntu-latest steps: @@ -77,6 +80,7 @@ jobs: run: kill "$(cat server.pid)" || true a11y: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: A11y axe (nightly, freeze-and-alert) # #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build # release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`: diff --git a/.github/workflows/nightly-schemathesis.yml b/.github/workflows/nightly-schemathesis.yml index 33ec974698b0..4a7b02adcdc5 100644 --- a/.github/workflows/nightly-schemathesis.yml +++ b/.github/workflows/nightly-schemathesis.yml @@ -9,6 +9,7 @@ permissions: jobs: schemathesis: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} name: Schemathesis — OpenAPI contract fuzz (advisory) # #11965: this job runs a backend-only `next build`; the hosted 7 GB runner cannot build # release/v3.8.51 (VM shutdown ~7 min in), so it targets the box's light pool (`omni-light`: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 27f77cd88b92..bca2fc3cf236 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -230,7 +230,7 @@ jobs: route-guard-membership test-discovery test-runner-api mutation-test-coverage any-budget:t11 build-scope pack-policy complexity-ratchets model-lifecycle - cycles lockfile duplication dead-code type-coverage compression-budget + lockfile duplication dead-code type-coverage compression-budget # #8781: open-sse workspace typecheck gate — the workspace imports @/ which # escapes to src/ via undeclared path aliases. See check-open-sse-typecheck.mjs. open-sse-typecheck @@ -238,8 +238,12 @@ jobs: # from GITHUB_EVENT_PATH; no-op on non-PR events. ci.yml only runs on PRs to main. ai-attribution ) + # Ratchet-mode gates append --ratchet. `cycles` belongs here since #15281 + # (69cb18dca8): check-cycles.mjs exits 1 on ANY cycle without --ratchet, and + # the frozen ceiling lives in quality-baseline.json → metrics.cycles — same + # `check:cycles:ratchet` semantics ci.yml runs (base-red #15306). ratchet_gates=( - secrets vuln-ratchet workflows openapi-breaking + secrets vuln-ratchet workflows openapi-breaking cycles ) failed=() for g in "${gates[@]}"; do @@ -405,7 +409,7 @@ jobs: - name: Upload test results to Trunk (advisory) if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} continue-on-error: true - uses: trunk-io/analytics-uploader@817e1a2ec58d888825699ee15ea172809bb3f226 # v2.1.3 + uses: trunk-io/analytics-uploader@14c0f18990953eecd01c9c5afe3cc63a73b6c371 # v2.1.5 with: junit-paths: trunk-junit/**/*.xml org-slug: omniroute diff --git a/.github/workflows/radar-export.yml b/.github/workflows/radar-export.yml index 18f29e4d60c1..d93113104835 100644 --- a/.github/workflows/radar-export.yml +++ b/.github/workflows/radar-export.yml @@ -39,6 +39,7 @@ env: jobs: publish-export: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} runs-on: ubuntu-latest permissions: contents: write # gh release upload — clobra o asset estável do export diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0f46beefbeb1..202a5f277ed3 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -12,7 +12,7 @@ permissions: read-all jobs: analysis: - if: ${{ github.event_name != 'push' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + if: ${{ (github.event_name != 'push' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) && (github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute') }} name: Scorecard analysis runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/test-quarantine.yml b/.github/workflows/test-quarantine.yml index 0e89add9d5c5..05b7003aa9d0 100644 --- a/.github/workflows/test-quarantine.yml +++ b/.github/workflows/test-quarantine.yml @@ -15,6 +15,7 @@ concurrency: jobs: quarantine: + if: ${{ github.event_name != 'schedule' || github.repository == 'diegosouzapw/OmniRoute' }} runs-on: ubuntu-latest timeout-minutes: 20 steps: diff --git a/.i18n-state.json b/.i18n-state.json index 288fb23b96b8..e42ff42ee9b9 100644 --- a/.i18n-state.json +++ b/.i18n-state.json @@ -1,12 +1,12 @@ { "sources": { "AGENTS.md": { - "source_hash": "16ce2ed061bfbe18538229a60583087fd3c68d5e794d9538a29840b7cf44927f", + "source_hash": "ea703be88fa1cb8d8c7bbe1def8681d6181bd8cdc464492e4ab61bda016a8556", "locales": { "bs": { - "source_hash": "5e4e8946cf0946c08f2dc7d3a728913fc77d956c4d21f186528be4a6c18b774f", + "source_hash": "ea703be88fa1cb8d8c7bbe1def8681d6181bd8cdc464492e4ab61bda016a8556", "target_hash": "fbb2df318b8aa03ad14888149e4e16de2e909ca078208f699b18ced770250c15", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -16,540 +16,332 @@ "ar": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "4f0f31b7b5b4c0212ab608ef6868c6a4c95d3d1e7bbc520dc15f50903a9f4e64", - "updated_at": "2026-10-02T09:42:37.015Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "8de5f9d4a4bfc157b554d2f90f995df7dc45a31a84d1343b9919f8589eb7ab0f", - "updated_at": "2026-10-02T09:42:37.343Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "0228c3dd63aa11e99f47c02189af16bc4adcabe1a9c9088a3773f2c0ba50b9ef", - "updated_at": "2026-10-02T09:42:37.748Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "becccce910a805bbf0d108aa2a6845499810b147abb3f317af9e0fc416fce791", - "updated_at": "2026-10-02T09:42:38.064Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "cd2325debc5e66dc9392e57de1d15ace3a3ffa9d1602c70619eb83979893d090", - "updated_at": "2026-10-02T09:42:38.704Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "7035b148c44a5f89cd5a12055782ff70d6ad50c6a4a738bd06a7c2db3506ebed", - "updated_at": "2026-10-02T09:42:39.089Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "d40a5356643085426d2069ef63d7c14113132060d651ddc91c749f30cb52f95c", - "updated_at": "2026-10-02T09:42:39.377Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "ce174acbfd6e0106e489dc99a14904589f498d0487288f84d7bdb20d0c6a55ba", - "updated_at": "2026-10-02T09:42:40.039Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "b848ec940568283cf3384f2a3b0c18361a31ddaac7c35efb64bb0d1fc7b7b7dc", - "updated_at": "2026-10-02T09:42:40.638Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "0b6071b714ed1991cdf932b02563b955f97b11f533c66679c683bf4523738e16", - "updated_at": "2026-10-02T09:42:40.959Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "c7355de71246a8adaf8561b316bcc6fcfa0bfaa5d477d5755d7d9b9be4b7cfb5", - "updated_at": "2026-10-02T09:42:41.315Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "105f929d1bc0320c5f7fb5eee393c663773b1865fb0118375bd5fb9e5283ef9e", - "updated_at": "2026-10-02T09:42:41.907Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "11acffd9e3bc123d70b2b80e7f7dc524013d96bccb57517647a257a5b17cd08a", - "updated_at": "2026-10-02T09:42:42.523Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "50e0fa5f89ef52cc8032251a1e24c27d56d7b6f544f6a914453fbe4b93ff25d3", - "updated_at": "2026-10-02T09:42:42.841Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "4c387c109a43869bcfaa6f21df7cec89ccc87ca30b18161bb246db28db4c27ba", - "updated_at": "2026-10-02T09:42:43.460Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "cf27fb82cb1d2df99e8ec4e335e6af193ab790bed8333ba709f8882d725ec721", - "updated_at": "2026-10-02T09:42:44.089Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "c57c8b14bbe139fa39a5276bec8253829144a7c7eba8167d309de432a0310a10", - "updated_at": "2026-10-02T09:42:44.703Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "b8b6e080989c98ad488943c4751836a943785f2f2da9053d07371e69aa5bd66a", - "updated_at": "2026-10-02T09:42:45.057Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "70ffdf7413083bfdd2fe07297c7f76f96f8ef167625eeafd2d404e75dd3f0aba", - "updated_at": "2026-10-02T09:42:46.537Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "5b2a7673a4ea0eb98e5b3616cc9d1dff92297079a21f355afca509c8b693dd73", - "updated_at": "2026-10-02T09:42:47.795Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "66e377001c22f7497669f39c3c582e95e8b2d3099b06bc06627533349a948a72", - "updated_at": "2026-10-02T09:42:48.246Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "c68f18c747506bd8ed3311714b353215daafdc98c927872db8a5dafd90c121b0", - "updated_at": "2026-10-02T09:42:49.503Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "120b7b8937593132d68ff3a7ed2902051c04f222a60e66c63c5b299553ed8266", - "updated_at": "2026-10-02T09:42:49.796Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "1b8ad76b8bc7407bb8173ed4f638777b1eba37110a434211714bda9f4b3dd76a", - "updated_at": "2026-10-02T09:42:50.776Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "3f83a42132e4e007b9d3f821084d50a0e2a6db555ef30c1f4fea8d6eeffc2468", - "updated_at": "2026-10-02T09:42:51.053Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "bf48cdb294cd0792fc3840ce171ae9edea2e62c1f006f49fcad06e084aed1a34", - "updated_at": "2026-10-02T09:42:51.348Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "4aeb7132923818642a239157d0c9f467afbc4be6b4f5153ca0fe98df235adf0a", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "c505c451fd56facc051da2be60167441cbb6bfc44a2fcd68c7b344fd8bdabd72", - "updated_at": "2026-10-02T09:42:52.041Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "722e2e48288a019329d1a4a717a930d732823509eb103255169551c84a11e828", - "updated_at": "2026-10-02T09:42:52.337Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "690c167caf9ef1de98c21aa9ac9fba808c017d608974418819d7e917ecec8a51", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "804d61035d21c723ee1bf484c2a627192c48c1d36204c54dd07c6f6c99b4efed", - "updated_at": "2026-10-02T09:42:53.891Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "e49f8ed10352ecce6bd9e062dd82309b41b447ad7fbf83022b11ebbe17fcc28a", - "updated_at": "2026-10-02T09:42:54.259Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "fce647aadc913533b71dd8156e6ee30d80a6491d8cf64e6353352a35a7104d93", - "updated_at": "2026-10-02T09:42:54.713Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "0195593f2edc5b1dcf50aa9c4544ab64c42857637554fefe26f97336002af6ce", - "updated_at": "2026-10-02T09:42:55.233Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "af28e78d0b9bfe476f40e4c24ada41ea3228c54309ca6f871ea5c1991edb338f", - "updated_at": "2026-10-02T09:42:55.634Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "08015e57fe957499c6beed1bf95f9f4fed8b6c3a2c82bd146d9588db5e912ec0", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "2848fab49070328c40c9878f838fb0178283161cba5543b15f4e7e279b1e9907", - "updated_at": "2026-10-02T09:42:56.597Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "81359bf6c7dda25b8adac5c7c90135004fd4d551351cee77495958cdf4434ac7", - "updated_at": "2026-10-02T09:42:57.041Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "2efbb793e2c0002d7ba67239c31eec9d31e540dc429ddbff87b8d161ae6079d8", - "updated_at": "2026-10-02T09:42:57.633Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "cff5af47ef70ec1a44c1b36872569999ea99a20bbf80859c7a4990129fc396f8", - "updated_at": "2026-10-02T09:42:58.640Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "a45b13520001af3e719d40a550836bead7c459b8321ad31f38cb12c0f0345a40", - "updated_at": "2026-10-02T09:42:59.011Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "e912569e16d4c2e743ab2f709afe008d730f1186ce6b2d3326500d8b0a7fce61", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "305e0018cab56756ddfab5648c082a7b82eb4a3efd2745f282fa5420e90039e4", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "9deea69e014afb6e29033f53e2b916aa0ffa3275a05f33fe056a58e002993117", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "8ccb887bba1a303d6c3dd0836781b27c5329f92de0e66e3cfd4eb254d6901752", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "4f856e4c1776511ee2d272603e542ad809b780c76faa1e41eac1ef77527f1cce", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "f577d40c32bb9a124a2eab3e4d3bc71ba39a7d5ad9e194f379028c739bd021c2", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "0f7bcd701c13c4c38775bba3c1fd862a2407b13ded1c944021032ca305063d45", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "eec2f45284fa347d48f40bb9557012221f303a9c25c096792a3b5daaa645606b", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "e19e648bb2a2648dfdfc7995393aeea058a9f2cb1741154ff01d0b3595960c2d", - "updated_at": "2026-10-02T09:42:41.595Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "2c94e7218bde38d1369a6bd98f9c0518142f541ae15a25ed9ca9eba839e5489d", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "34fa0c25323728250c8440b4fabc2a385287fed2c8e4f8cdd6457220365e2a65", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "65c96025321593607004799dd342ab1743ef417c51609d27ec86be1057101994", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "aab3b0abd273a79e3e163086e686276d67ed8ce81feaca0ef128e75626238b47", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "d7e16cb2d4b634425f6f46fbd3f8ce299a839d6a7baf4214277bae87b437d54f", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "00644521321e9340b54c6c51ae6413398d9354cc82130970775a22b91eb8ab0b", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "2623660edc3dd104dda90352453524ad093311efb5074b161f22d0beb7e9390e", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "619ec3558d498c140631307a3b9ed2a07deef6f564addd3e3c3b4266bc340b37", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "badfb63e8c71fa438d9c9a2654fd9ba08d751684c7026d7edf923b1afe88f09f", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "c69c50310edd227ed8e80b8d9b54f216b4c98ea6ba3e6c45235383da2ab6f3d3", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "55399570893971f2daf10ce645408d7e0fd4b8b2451e650b4a1a7fca554f58e2", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "774990f518dba13e5170298a0dabd5148ca63396730140d6acb9085a073add80", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "126e133d2c3baeda57e176799c2987b26a2fe3070024c30ba9fa52965c6e54eb", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "180e22667dc5d94ae9e88dcca7562072ef7d138774f91874db4a68aa737f19bb", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "149a0074744758289e6d576913cc486678c16a114351b4a7c27597607a1c0a6d", - "section_hashes": [ - "b49338d6c937", - "631c6efdc743", - "b819984e763f", - "a9b2e82b5fd2", - "0230edb63f1f", - "067f0a1d366e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "b90b22873977a250f16576a05ee3eae11b4a6be0882674d3d368985032201a88", "target_hash": "1acf3f8f2b5ef67f4c4f612d1ef19d096ac126cd8c7aaaf464c381be54f3980d", - "updated_at": "2026-10-02T09:42:38.370Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -559,1321 +351,667 @@ "ar": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "93db7cffd2a21e2f8d690fb5fbe5d2590cf249fd0b2faf99a4a6c6c3383a5965", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "c110e75d2a7c5d4ec20b61c88eae069dddba030030e1e4373c19b9b2003b50f9", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "93df1876336d95da26b604d648733e58b3a7f3ee19f84c2d422defb9fbbdd14e", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "aeb2faefbb4574d367f0fd66ee6741db818c7ee92c40e85284b284fffdbec691", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "15d6175fda1835e7a17754ae1d5985c40bc57ec14f120267cd9c86a1c6d03c2a", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "743e60c49746e7f3c619a0f051e126727cddaf3b3b8c1bc7092d952df9956c03", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "3514030efe098edec5dff3ee318bf9cf68fb2f451bdefdc7e1f4822117a28fe7", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "6b0789ddb00e0d2d4d6871704c0fbf6e7d9713656667b6aea63812d38ee91bac", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "6624c0955ee9061c38cad576503ef58db56c36698c238acd3a576e0863ce96b2", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "d2df3b2c67d6494bce682f57ecdbbd281eab7c8864c42ff0541151c57488de23", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "2a78d8447ee3401244da7aeca7a09f240766b9cbe5307f2939d0a9cf6c46cff9", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "3d529289714e38f28160c642d26130366e639626185ddd7a2b5c70388b394d87", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "1535b1dbeb099538096ca6987b5cf983436782c873be83fc8b775a7ad9d8e5db", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "19c4ca13db55282ee0eb0085044e5f34e6596fbf8651519ecc6f59c4695ce251", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "32a79443133e819b1244ace614b8c67dbbcc165d561800ac6b1574e1d92a2944", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "034290239ad32989ac576d381b10a10f993ebc9b151cd9d767f0f9bfb9c65211", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "a416fff0cbd37bbc1b56584e88fc55d1057ca8ad089cbb6330e99986bd4dcd56", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "053b6083254633565f9d15c9adafa5cc2921b0b65846eeec2ca9d38da895f0e3", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "10ec323ba8bb6956154f473e4632f356c095569c839898dbbf5fb83920c1f01f", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "0cb5195e96e0ad2d92c34e2df5da40fc2b3e6c25aa30fac142d3e5b683a3769f", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "b5f2d47efb466fd5ee3fb77fb17c4b31177942e9d48e495cf2ea4a37ead6b8b5", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "6d70a117eed5818417a2f0dd0ecffb7d139b6773c344f11078dfe32468e08f2b", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "79d57719c9fb6b85b219fceb2f9c0fbe6586f16aaea27cb6964be029f86f0f22", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "59256013bf523419e33c5af8ab38581a6ee1f8e57a9b060b48c9a8dbb08d2978", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "3c0558bdc8966e5f311b7e428e8e9aaccde8e7b17982aa789b6e86acb575a77a", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "24d3423c7a8df1fe78d5a2c48d6384f0e7fbefb8c2a0f4b8c6e3b3e77f119a58", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "cd7bef53c6f2fc4660e0721962efaa859beee7917eaac21ae5a7554b28791935", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "341756d97497a0df7e20bdb2b4219b92bb12cd21a0bbd081931b4514c82fe2a3", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "6084db1a237268574f984b18c3fe983829198eeb37048bccad54c7b59bebebb5", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "c064091abd19ad213d7bfbea47a177de2d08ce05357b9b005cd37ac4d7b5a8d5", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "6a2d8b87b584d96f0a500949c8a9a36c9d33f14cfb859fcda978cba030428883", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "b760c0d630d24567abb923031d60bf1869217343381da4790a165131c41bf2bd", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "905bbd816f482f8f1cd75787cea62a8b1d08281fc53bbdfd1a0fb8c79b15b63a", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "971a0ebf35d87a7489c0184244c15ed243aa2951f1139163a5f0f395e2b47593", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "233c9d9004b94679a39f0b41d10351523794a209b1642314b1637da3e674182e", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "9f9f03ea0386e03de8b4bbe7c59fd9fb6ecc2119c065dc9d9513d790a3db6532", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "fa18335dd8b287be727bc2ad73e048ab6ac6e12b647ff859ae664a8b43c5ffc5", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "c77e7e3d59122382c1cea83a952db18721144638fb44726e5560287014a442b9", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "67bcde9df173b83a6808cc7a6ab7b95dec55824981b09cc9624a6235a3f8f201", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "3c9211f7de2b5657f47a598f4cf1bb7453933959b68c5ebbcdef3923ce752f8f", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "9177cc5617550e944a88065e683c63c47f3403f1bd724c7d91408d0fc772f70c", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "e6b34430124983752714bd8d22dbed3ff7b0fe708e49a8ee04378f5246965116", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "2122f745ad13a8924903481ea6dd60a51f19ea224cbff4c796707c55cd5fc7e3", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "303039e2f53c4fa21934733fca0adb5c7a617c25aa00db1b2329cc234c530660", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "ab18441a855c7ed4d6deff93b0d49b097d72f12862d9eb5db171038a589efbfe", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "e08becdf72d282349e70f6277af411b086d0ee7034e87e01d0aba77e904762b0", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "1ce0d8d31a7b09984792abeac32abf6d39583ce735f82e9ae718676d63c7d173", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "78adfd539aae8527d6a1d6834526357710fb9cc0237ea68fbeb13948f6355ec9", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "0bdfebaf69ae91e10f93be3a4cf31db0ec1630da3f2b9f737a1e7a91c8adbb6a", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "c4de2aa397acacc28096b279e65cb547f4b049a2cd664edda670723f0fe35b99", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "eb40c694de03cf458087c100ef714515ea6ca01ee3fc8a2264f12825d827c56f", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "21bd017915498e0365dd3fa5f3f809ef21c154723b9d75f43c842f27bd89012f", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "fc145518011e507a55eada30b99bf2cbf2fc54c4e023b84fce513733550c8635", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "d37df3ca411ba16d4b3ef65cc3debae0741ac25b34da4d621ae036b0810c7b99", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "37d80835fce459dde0b40b6e2bb6981753c75341df9db1a355bcb8fdf35d1134", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "b8086e083e4826657cd12c261a864623786532602cd48911c261279511992285", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "f8b383c14b9bc2c239ddfcea48582ba90bf68ee514b6395a4824019a77047550", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "3c8f8868f4b63ef8e7fb245acc1617ed164f1c7a44051bcd41e4451a79ff0552", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "d19014b30d4e79f8cfafae4dc9c61f14c97435f361aa98cba8cefb8926dbf21a", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "44207c80c672c3d407498d4916971e9844ab535d5d10ec528dd56e25eb127959", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "b7465058e4b72ef3e18680b7615c66db973d68e4f0f26977ffca7c5e92edfc4e", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "309b8df2efd56a23b29d4d005851f195d1de9bdc060ef4179b72468550089fad", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "0f0677e22f7cd0f5eb310be0c2ab044d1cd327baebc22ac54c959c8bd20f46bc", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "2527e1fc1f7fd5dcc4a578118ca41d8d537e8f0481531f6a5efc8eaa5ffdfe0c", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "f83ab72c4fc9cb508b3f3077ef4843d333c736796d61a3d9a15aa855f7428204", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "151cd0ae57fb50b8a7363c084ecc4593f59c6bf73f7ef632c077e7b8c41923ea", "target_hash": "fde3e5ca12e1f8565bffd57e7eec9237b977f1f2d0de808475e43a7bd0e093f4", - "section_hashes": [ - "a39582d6575c", - "bad0535d04ff", - "e85aa655d1aa", - "333a73063476", - "14b6a9c72a82", - "d792555d879c", - "ac0e8fd4868e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "CONTRIBUTING.md": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", "locales": { "ar": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "ce174bb3eb60960ece0b2589d9b80ac891dac79d9201be2023ab7ee290c76070", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "ed6ae10fd58301c4ea60514aa3c56e77c7ca896014220a30690cc0229f96467f", + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "14b3eb09cb7d248cd1cb2aa95e7a3223f527ae3bdad30443c591865da70d5ab2", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "40cf6880fcf0cc2f4112892d267bb73737a4b27652dec088d7b819ecf7d040af", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "23d4837e002f673d0a6b0f40ccb2d19cc7d15143cd8be4aca95f8463e0bccf1d", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "1067c34529521ef5a552cdccc8b387fb491f2b152e7a28050be39b1fe542b83f", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "afa7c485773ed42eed512cd332d1c93a0920ee10f690d3002d77922ce0c6605c", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "6b5dc712820137a7258e832fd2cf46d03e871d04e91885bc3c102d150bf1ecac", + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "cca0c06836f11d634514b4ff6dc21a8ef41c5c04c5b05cdc5a665a322141119f", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "d8eb0d6511bcc18e26808f769793e757d32a80842be6f262ab15f6728e80daa0", + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "d37720e7aa9eeb475dee6efe8bab36f93865e6a45af91cd5884a9467a05a5437", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "7999f0491361342d13d88381b7ef3e5144b9e6dfbe092eb90ceb874b471802b7", + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "f679222b7fde53c0a212e9fa2363029ffb21580a16cb68315ca64a0eb3d76cb7", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "625cc613ddeb3251f3a31088117ea564c455f9278f3c26301cd0949743dd6778", + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "da051268cb42dacae70dfc1173eaa23244c8ffc7cc1a808fdb30582ca047cb6d", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "19909eaf36ebcd2737cd08c4ac6934d18ba88b733c1ce6b9d19fdfe81e83b02b", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "7a817b44949f1b5bdd090f868538b520bd9454294af6b0e294240a6e7da08bd7", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "1fb994f52d8d7d5ad2f3915b9f1897bd3e9671cce41dd7e218fd929b553e17cc", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "733ed1453eb66c989f4cac1e429fe626696ddd3c6963c9b0ee302767a681f140", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "ab5439ff2ddf2455bd6c71455d1cf374d8f455605abc2a0deb37fc6b059c0357", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "906083f14c19acea7f66db64fb79e8586917416092d3a1e7c9cd203d4be3a3c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "f5d5758dc8e5abe2da32028ee4210849770c4ce636ca7987cd0a7266461b8a57", + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "2ee50b221295a7db477efb76457925980bb293f19a802518144474e5b10e541a", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "975c5431c17c21ef8a2219c8fd8fd4e671dfb122b46fd9bba8680b467d15cb9c", + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "2fc437644f4dc8f0c1b1f2f631de74ea81daecaea9d72238d82a59d45afb4c1d", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "a8bacc17a9f0448d3a680976321b0a35b0a9bbbac7e22916002d3e5ae9d0e68a", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "4dc6f88bbc238dd9f0ef5f1364b4d4fa6b32250f947d64da8129e301f938efd9", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "cc27850aae7adba5d1ddffff1c50ae6872a9a69358db09bd4fd0df18d39ec6da", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "4dcd3b0f8f886b8d243d29f16f993b1d8e61ca7ccc9ecee8b6f39299ad5f3c95", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "8fb3d369f7da1500aff16c39de126ef71703f4cef321617a83ec0be2ab4e945e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "504bb048b7ec87bd8369d5aa51ec4568785cf8e680c6ec22805950070accdf69", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "2e2533e3451e5f53a0e0938eae9063b971895ce31bd2b176bbfc249525ee0353", + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "d564d3a880e04edeb79b2e11679ca178f6cfd32d7cb098a10f5cc63243b16d02", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "a2a618da88417896bf8b437a462e2cdb42f27f923e4eabad176189f597eb7aa6", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "663a22061c48a46380f89175a99f815d6f7ed4f7c1b489d33fd88c595772a8f3", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "e6aa5e821498e21c91d3ccfcdfd668c22513997f7bee3a1218a0b799d30a82f0", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "c2e7cdeceed51ab7f42531686fe412e59e29b4ca664703c1c41f83aa154dbedf", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "a4ad117a86df80e7467bc9a2e7bbb43a1ba26fb7a14bf02d4aaebfb5c2f233f3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "23e0320854ead5f46c2538a83ceedb0793d1de85a4a855fa352e74b6ba09ca97", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "779f4808adbc7d563483ed05bcf23fc1c44e528835348e1cf87a827f6f8614e5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "2de4e0c8cc37a0bd26c328cf6063cb25d44cd6b7f296ee34bd831b1e8432a197", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "a31c707b0809c957feda31a052b2401bc7fb2d916e921a02c0e686ab188cae58", + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "6e39f0b8767ab226ac20a2138d5c7f671de87a67aae75f056cc4d87624cce322", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "0306117c853a189ca47022b29c7793eda37319d1dfcad6907e375467c4daff53", + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "138c3d12dc067d18dd860a6d252604c84883ee43013cfef6ba69bc2ffe04844a", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "de2b4cc81ad78aa9b0e2f7471b83997e19582ba00948855332e0bad952bac453", + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "e45af8a391429d12526075ac725e71df704b2e0fe86c7df8052caa7dceaed0d1", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "f04801ee034aa201f2b7322ec69ce2cf736d45672b8dbacec2730c27a976aebc", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "04c84625322e2f8a37a78a72cd968a58851912abd5aeb827dfc264719b1fb364", - "section_hashes": [ - "b02037e2c3b4", - "bd62ad738622", - "0aaf8cbdbf9f", - "0273a7b0a44e", - "0f100572a3e2", - "1325f364c550", - "9d26c940c8a4", - "1e3d267afedf", - "ecfaa4a13490", - "9c5e3db8efe2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "d2bc953b9bc3155ad82e0996a355dcd47ba742feeb433102b609854ce027cb93", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "2cbff6d67f5c8a4b955038fabba41a931342df178ed1f42beae37e73c74f7625", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "5823dbac7d347adee78b00410222d2a56f081f62edfdecea13b565ae92f03a10", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "7d78b838eebdf089ad2d3dbba6efc07fd1069cc36d76328a1aa586d7179afcf3", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "312f71dd5c35619853c89ef9ab53ac7bf11adae233c10eef6954a31e26c65509", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "3b6668afa54a987c456c5905277994dba89669754d3b203ca7e030cf22c2f26e", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "fbeaa9df80958a5929093eae7f4b4297a7762e89dd1d52542b06b4b3d38bc5db", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "21c950a74c3ea2e381dc4135366e004cdd0a0a490ec14cf9cb11876a98629b38", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "ea5d69e9d6eb02a243399c4405180401a7e47a7041ee0008a2d9eb247d94990a", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "5b9c41c7bc9250bef56fe56b17802c04db3868ebeadd6325ae053b1a211df500", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "a07e8cf4c63450e51605e1803a202de2eacf12bcb75c59c9379dc29baee947ad", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "ae5834fba39f63a24a30189408de1d3a07695c8cf7ccfd3cef6943e712d5e2c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "64fa832dc8faf569b5b9acf4a022d8c4441b8da4a162286e895e2921ecf3b610", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "c3190238c6873673344d81718a7ca3c60d768aeaf87e799a6e8ba00da413ff10", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "bdb5d1b9804ccd1e1c729ff8d8172572263d386f57450cfd87c48fc29e71078f", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "54c4e3b86e8ee39ced8380751025b6b8317b52b41c0633d9fa9434a0764dde42", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "52f06bd140e475cde0cab817e9da341164223dc68e076392ddcf65b57ebe5878", + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "34cf9e920237c7584c975e32c7ead609f92d427f84311dc6c86433538e4501cb", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "a9b6816840db0860486b51ee3ec877772c77ea8fc2890efe60650fd92395b9ae", + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "e399bf9e47c3951b8e6d27a9754c83611ff28b311b7c5a4fbccd3c769d26b2d5", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "e3b2fce77fe2d4319638960b9794e5b8f97952f4578849e727ebb06be88acc32", + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "2d93638fbe8b3d8a7de4d73bd1302cd0c6edb26db8a6f445b389e7d20de4eaea", - "section_hashes": [ - "b02037e2c3b4", - "bd62ad738622", - "0aaf8cbdbf9f", - "0273a7b0a44e", - "0f100572a3e2", - "1325f364c550", - "9d26c940c8a4", - "1e3d267afedf", - "ecfaa4a13490", - "9c5e3db8efe2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "c81984e8895bd5cc58f1256c73dbab020afccc20ef428dabcdd5e4071acac27b", + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "8cb92d89003fc7524211c239ef9403a9d56ab34a3b797c5545386543005bc260", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "3394a3bc83234c943dbea2c62fd847a78ebf1e14efc50065c62f5e8e713d8ed6", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "5b4aa19706b3f58a9d49f23696bdf31703d7561e77d282a5b8d0dbd4b8cdea5d", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "6458b64238e8cddfd36830501cef8fe240f1ffc9958bdac3709b226239d2654d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "7668b254b67cc5b793477dfaa82029c8dc5dfc757306f9d4ddaa58647cd4841c", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "2fe5da84e3acec289ac1b74bd4b57f4dba61360fa8d697337f3542ea0debb433", + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "7beae5f17d69a8dbb0fdcd656a63feae5048170baa265de51094f4a4c415749d", - "section_hashes": [ - "b02037e2c3b4", - "bd62ad738622", - "0aaf8cbdbf9f", - "0273a7b0a44e", - "0f100572a3e2", - "1325f364c550", - "9d26c940c8a4", - "1e3d267afedf", - "ecfaa4a13490", - "9c5e3db8efe2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "f0c3f9ae736b6aaa1e3cd28fb9c19834912f8c992cceec8db0daa101790a0f7d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "c349c421a665ea22b3b8067e36a2d300babd5b451edf259b4621b571162f0e73", - "section_hashes": [ - "b02037e2c3b4", - "bd62ad738622", - "0aaf8cbdbf9f", - "0273a7b0a44e", - "0f100572a3e2", - "1325f364c550", - "9d26c940c8a4", - "1e3d267afedf", - "ecfaa4a13490", - "9c5e3db8efe2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "2eeac88ae0054aaef6f7d53ccdab8003c05dd534427247df3fd93e9ca2ddc32c", + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "08d928654d50fb20606ca1283f303be1edad4a578d41007096f79a457a98f609", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "4a1e8c15cf67a994b6a1ddf702958208efb597ac2d59015e7665010d2e531e5e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "e0461fc84a567a7e330b882dd382f8df7ca6c74b889452d240acf085b50cfabf", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "fc739273adae641159ce47d982c826aa0528f9e5a6f3b8eb4972f860ae7d9635", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "334ab4b0be862588dffc2073dccbcdc5c9d3fc50d5c61cc46a3854c88a4b6e65", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "fcdcc324b0ad1c98e131228231e7cb5212efd750e294ed4c8ffef586126475a1", + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "324699402260a74b322f654f58ccf04e15f260ce5e9b2cdf266489d2e77487fa", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "4f5a93297ded66aa7d33e9fe71c150beb0861ccb2221b4857e0b0d6bc3bacc4a", + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "c75857a116bc2e5e2d47fe61284aea36172c2acb650b79293c11abd514162572", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "b1269b1471cf5795b5166767b8246af4baced362e104036e4e2cf1d4d25c3ba3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "eda56dc2c6c9f3713e94d9d3fe63d18f9dad964fdafacdba3be0110edaa94978", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "14ce11f1289201600ead11275dae000f55c302d7a9ca6ac22e7ad4193e92b64a", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "85fe2cbb59de96b8c8583cf800df9cbef3abdf2cdc5a4e78981f3efd5c66aa10", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "bbb991f30ad9960da4b5653dc678fb5895cdb2a116efc04440973834140682cf", + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "8f451ed4f4a652a0af4ad47e5d0f60e7cd9fbc7ea3204ada11e979f1f3c6c7d7", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "cf30574c6226f7566230663b20646ca6caa806aa448036f95df721c48415a8cb", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "dc3f0ef5a6e42072f0b73e54e11ed08538934ff92f5aa8b886e566cb51c8584f", - "section_hashes": [ - "b02037e2c3b4", - "bd62ad738622", - "0aaf8cbdbf9f", - "0273a7b0a44e", - "0f100572a3e2", - "1325f364c550", - "9d26c940c8a4", - "1e3d267afedf", - "ecfaa4a13490", - "9c5e3db8efe2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "a6e33dea4f793d07a43a5ba75b3c73b86530c55ed78dc0ce6b57d6a61018dcdf", + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "bdccd6eb2c7e840f53b78e19937cc823959085533743a0f2c2f1de2b628d9901", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "2b605c8884592e1e5aa1f28b404b43cb11a8706b3858bde85846ac8aa0dd2051", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "a981f438b79550a2094523c078a1e093b84b73ce9584755b3d791a513c56dd02", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "30b9b133f5e99c341ce187d9547c6c1743945e307224c0eec0a9a82be924f42d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "8daa8020d21accb65535f9ff19ea753ed3a72b07b01b0cd02ce4c20bbfc1585e", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "042d144312fdeae0dd68db7c72afce7b9df9f09b9fbd7826f8a14d8566151688", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "5ae57afe7aa6efe1809b5a7c8c0946f11f57d8f01bf1d3391e10667f380a1ad4", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "5cfad4e47e4d9eee92bd1c5748b808c0991fec7be9ac83e7268a2a0a6a9c493e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "aee927731d833651c2dc882cedba9ccd56323bda6c0179617e482cf323b48bdb", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "6b730bca2f1819dec82a4b8f836b16216f5d0ba5645fffdacec5a2f92913d937", + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "1d451dd7be81133662e432c3de793aaa719cf58ec8a40f927a7b9e21f98e42de", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "c87f9e2b660b2d3616f367d4c0f13a2b25350e62fc6c6aae45a65a83dfbc2257", + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "15b0d515f5b170faf17582c537a1c756f133bbc0ecbba6857d9821350be3b438", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "66f4644d080504bd7ba8ffabfffd572e87d448ce2cc8c9184605df4f20fd7761", + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "5b4d2109bc9d40ef0109a5f4c081f63fc2e5e3f2482367ef99680623c8c39913", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "7559d01ece5d07b6cba81de96c027dbadee9fa87f11afdda3e6a1015c00b07c5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "38a249bca954a7f503a7b14e0c0bb06f15104eff19010eed89aa1b24638135eb", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "0ffcc3fee8ef30416b289e29c09e52433bfa072803251b080535742129d4f637", + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "29dd140f3c62ca8339c113a3a588bee9241599416bb0a7a3946cb02a57a087d1", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "9d9af89798f16d9282e398fb4711394d236a3072367a8cf4622862808fd6061d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "d8128e982bef46b44a3a13d8fef2d8046e2716708af0173d0b3ce5c0daf0731f", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "623052e9bfce2ef5d98302437f4c327a36240856754a5fb460a40d080615411d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "2da8b37d6f513457ba606d7170577acdaeff349c8723afdba37528a97278e2ed", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "1dc2f2ade0593b9623a7f05a6d5d7239e2decbb1d8bb665b4c0aadacee91b9ca", + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "0b09496656e4826b22d829331ee4ca64b2cfe339d8e49bd6d5d6a06081cb6608", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "b1b099be4748c02750207354e1d7ab0e991584586ea6d759071687f7ef6ebdcb", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "13887af54863f3f5f9f3033e5c7e212c162950e93236c23615a26ba17d3b1d38", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "50cdbc998df48ffe256be2b69341e729525b43437a7e1413308f9325d7a5f08d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "b42256d732bd9f9e85b8d3340ea4ac126c3cd591995a9fc241a4990440f203b0", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "028c8431bee80cdc897a508940be795bafa741f4dc36cc3d23b2da8a15997185", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "491d19cc41c8a7036a4d44f98fc6f487a12f0b9dc212e17e881f948b2c3c0078", - "target_hash": "05e92e85b705c89f70340cb50af050e25b345504c075d28cca10eb79a4432ac2", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "d102a87d79ae29150c8884d9e5df5aa054d0fe53f7c22ddb5eb930edbccac80d", + "target_hash": "056f56983f138438ac58e3541fb289e322288476a040b044b561c0717c431f84", + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -1883,730 +1021,667 @@ "ar": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "8393bd10d30059c4cdff7ff6360abfdaf1f82d9ed9f00f574b33e5d0e135b5dc", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "bdccc5f6905d67a6fad2c082f3fa2cc26c4eb9786e5ef9e8259a8dd6c9170fd0", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "29f55cb97fc23f3f5b92bd08f4b623d8c97e96385f987578f6c0b7d7cc12a9e0", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "e743b06ae20916bebc8ac1b271759db8b49b7f0ba5178fb4382fafd997d930ca", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "e6d3394e25af79cc1052d3434eaaed6573a915b7224b5ffb599dfad5b371e0ef", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "9eed6bd975c951c595102347a6c848edb2565b143da75872809008715a4c2f11", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "fc586eb01c421831cb1023105f284895f5857e35c87065410b466598595f43d5", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "3c89b53619cd22f0301b76de5b177a2786de056aea87a4be411c4f09c5193f12", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "c13502d9200f2bd49395e833bac9b6d746e9d635a817f5cf0d0c41f8bb48e63b", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "a1fc39423ba1b9c58a811634936de163f52e9febd246983550b6aaaed82b1c19", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "75409f66edd7e3e65953af59aa4cc3223013ff5430f84995124ce87be1918788", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "622bcdc41f56aa7a0eefd4a0d2a71409cb1913d4e1f7ea01aeda48f275d9db2e", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "86b6d6df9446675390d2cd328e61815d7d2178e2885edca8803c79a7e5e09ff5", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "838acb03935e24352239f37986c203e16bea751d617d6d11b0bb9199c1a58d6b", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "6d4d82817dd8a1a039ed99da3f373b12457ad73c0bcde71bb1d6feab957afdbd", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "26a7f8e4e8495fca6428a9a7b5b6c5be87eed3ffdc9f03c08b8d637d7c06e2c8", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "16ff8b0578d04b83d7ba9853b905f2c0b6fab2c8d731c7ef0243c415427abed1", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "f9399236b3666c87ccfc4a13dc16cb0570fdde7eccbbe4b9e33c8b5d92eb41f2", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "fad9d4a5da76d2fbf0e928e56ced1ac32f13537a274e88d66660c7a5db8bbfba", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "1d53c5cf5006fd37d88990ae69c7d2a6e4a7738940719ced3583408810900858", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "a0d30915d2f828ed7b107ea896a1c883adcbf83cdafaedb8d7c41292ed44e4b1", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "c5e7e02ccf2d256c849f83d646e714782673d856e4db4a76129933c03195e14a", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "51622d32ad04268713cd290dd7e28bee9657863294c89527f9e523f567f02a03", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "8324bc06577ee1df84e10943219f3903381424cc0f28b3bd0ebb3c6346562504", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "73670c81e75c834fef18f3cd38e6297db5cab9a93a683aa48868af70dbcb75fc", - "updated_at": "2026-10-02T09:42:51.053Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "89313e08e45f81bd7c2521c904904d7b7f665158854dc74afb9929f306f0c1e4", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "c953a2298561c0aac8c3d9ce79f9baef87578093f1d375721513e9ec95f6e598", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "9e8bf3106270a095e47470704f0eb7c7d2bd359e81c14e51bde720e8809ae3b2", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "ca3e945fecd3c957f3470ca112068a30cbefcd4f60be7a6fd27ec85cc8691238", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "e6721b2f0a9bbe9f663567020bbbb5ff289f7de1983e693fa786300371730eff", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "b3173977d298e7dc519576ec2dfd1e1fd6c3dd44672f278d0bacb53768ea1544", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "6b5514325b0a0d47227a18defb160e39a29671a665545e5555e7123597668b61", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "101a010cca9851e2149c38a515fb30e721e6baff9412a9396c215ecbe4b32460", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "bed1ef3e436487853aff00f931491fd0551aba1b70a8b71a9cfaf68c19448377", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "115c60c7c310a897fc05832a5b3921cc0113c22321fa96a1df4a72294d47fc6a", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "ece05f792486fddb17967819bac3b4397b4a62adad2c70525ba8799c6af3e0dd", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "cd22e56a7157acac1ca243d8b29a87dab1f57cdaafa8e7d04a61db7b344e124c", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "1197f2fc4abb1772b7a16f745ef3f6e05489eee371c9223396924248088eb342", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "9bca33b3d93f8445c77dbc2c8b8e1639fd7e83eaff984996b0692b3702c45c03", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "2c460af294fbd5f9b691797c97d26fa0d4244e6ed3ffefa9e8c7127acda4676c", - "updated_at": "2026-10-02T09:42:58.640Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "db99eee937ebc21867e94db1189403df8b8c4545bddd9b5799aa3b61e556e3c3", - "updated_at": "2026-10-02T09:42:59.011Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "a10232fe78df4dfab3689ef36d089078df5dc46f443ea97f174e069179e3765c", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "f48c99968502c3eee2959f2522d81fb30ae550b66b4c869b2b2340dbe6780a5f", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "76230aa5374fc1510777e2456203fe70b6b03d257fae1656978b0559170a2012", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "6bd25b48ca1beb81702802c707dea909aa3ebf63d583c34e6e324bcb4a05ca33", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "3b4a820dd3606eca6781ee8a0b400d9050401e6219f22cd83e13ed6e167f931f", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "7a3f9c31d86b36fbd1df733995165476e02173ed11123f7b9b99e4c9e9556f89", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "6b9f773e30563fc35e8ffa20233e090e1b62c779343d1ef6ca975b51f580d05a", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "c3ba30662c2812c1912946bca9b17f7c86c5ee07b6b44082b297d24c25170b87", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "a631a06dd18f2b44092b3eb3338e5a5e1765a0eaea6ad40beadeabc7dc13a7c9", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "b66adeeb3171fc9f0561c81e563f4ca10c4360790d2206773520eaf8f9a36e2f", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "c0dd8d0c07cbb5c674a67e6579609228c440535db9c0fc3d5b707c008d621588", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "760a2b2822762f089824ace7f46e7c052e42aa161446ce09cd92f4bfb8b7a93f", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "dbeaa0bce924ee5b2e5ad2f008975cc02389d33f2063cf4cebddcac9a523deaf", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "6dfdc4669324e0eae97a486b18bd0df8b091d08b72a01363c5ba766d147063a6", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "272208b51eac814bd99e8d732ba1c8136c30bc16086cb8f1379a2c59cbc9e9d2", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "5027ca9917df9d11597d17414c526c0f857e693feff3ea5ed9c7b1b7493a2387", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "fffc59f0bcf8b8572ae480820f7fdeb2ecfafefae8e043f954fa80b7633ce4f9", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "44fc0269e2aba43d8e4a745363d85f35b5abacf873c36ad61dcde1f31d11cd83", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "4ae72338a63b9afd5dad5ce6126fb77cd3ebdff1601a189aa8e9f34ded2f388e", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "b8dd20c7405bafa97a74dd926f045dcaff6591343be51daab328ab762050fb5b", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "e9f71767182fece8ddc093f06e02f102dcfd3e950377e2a20c7ae3dc8addca21", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "428096f6a3178b94b9780de7a63ebfa7b6d143221ff8c4791ed2d4fc0d7d5d83", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "6ac7a0a2f9aeb1275515de76cc9958925a9e23b8155900f6fdec89ee44240651", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "07dfe09a4a0c9361135b74dd936e87fd309ac483c5e9c62e3b0e4f635941bf05", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "8ce18538a6fafc686015b9d5c69e0f9f0f9ec4465173b96d09f5502067abaf1e", "target_hash": "3c0e55090ae04d6f60a9ac135a7e57580762a816ef4af319bb07e80f71ace7c7", - "section_hashes": ["5f35908464cf"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "README.md": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "locales": { "ar": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "cab6ee32d19716326a98b56c84c3cf5c5e6e80db70794d716d906894579434ed", - "updated_at": "2026-10-02T09:42:37.015Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "az": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "c4686e545fb70b9f3f1fbe4c5c1201ccc135b2123e599db942be6dc56ee8c0ee", - "updated_at": "2026-10-02T09:42:37.343Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "bg": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "b682ba94ec6d6418f1da5e2d89b696426b6ed5960627eb76d165e6c98e98e1cf", - "updated_at": "2026-10-02T09:42:37.748Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "bn": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "c01eb33babbd3e0762392ccc53fbdc1a8ff7af6aecc105ec5c26bd369446d427", - "updated_at": "2026-10-02T09:42:38.064Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "cs": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "1c23c35ccb3560d5e9757d1cbdfe5f680bf9ea1255939005bb1df7e46f9f3d24", - "updated_at": "2026-10-02T09:42:38.704Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "da": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "eaed8e04f7fa2638e83167ec6222be160393647bac756d84606bdd3165f46b98", - "updated_at": "2026-10-02T09:42:39.089Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "de": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "a7d3601396506bdc08fab3355860159a2a7e5ed67323cfcb8516307417e5e971", - "updated_at": "2026-10-02T09:42:39.377Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "es": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "b884647ffda8cacaa4a755d7d86c9f68c383119398b62e5b34b3eb87f6d35ad1", - "updated_at": "2026-10-02T09:42:40.039Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "fa": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "244d1da17c1b7126a2c0e22b873b29679390cbbae23e94fe00fbb14586228159", - "updated_at": "2026-10-02T09:42:40.638Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "fi": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "356c1705ce89927865434c23ebb6d57ec4cb581579484a9412d6b41a5335b7ff", - "updated_at": "2026-10-02T09:42:40.959Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "fr": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "abd4e122ae22d6eb67a501b1bdf05974363ed44aa335a9c684c9d427a81d02b3", - "updated_at": "2026-10-02T09:42:41.315Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "gu": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "3ff7fb79dee0b3496ac101e42a84431820dd9c161925e78ad164d905ef0db67a", - "updated_at": "2026-10-02T09:42:41.907Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "he": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "93bf0921abbb4da442f25fb7c09f0a39c39d52863eb5a141bfd90dc279aeecc4", - "updated_at": "2026-10-02T09:42:42.523Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "hi": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "d6d32fa4276705f515fe3f9bd909d7ec4c7543905d2327f0fd4f5a7e5971f39c", - "updated_at": "2026-10-02T09:42:42.841Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "hu": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "ee4c876a980cddfa85d7e4c7cd0999fc139aee93d4f89315a435cbf9d7128f32", - "updated_at": "2026-10-02T09:42:43.460Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "id": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "56ddda1186a93da960fe049c211c8899f994167ea2939658e509bc2d13798363", - "updated_at": "2026-10-02T09:42:44.089Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "it": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "df9efe96eebc44a2e3127aaf138d158b90882300adb48034d18e1f180ac38b87", - "updated_at": "2026-10-02T09:42:44.703Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ja": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "10741e59a44fc8860e7ae26803bb93ae919cc81b0f3f298cc5009a47a84b6eeb", - "updated_at": "2026-10-02T09:42:45.057Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ko": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "06631ee10a9f8f936c74f029eca6f93bab212606ba34f076e770dabb315aa889", - "updated_at": "2026-10-02T09:42:46.537Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "mr": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "b682180a2dc8375f82cdf75b377d170836ecc45a9f7fa654f104a8d0d560d7c9", - "updated_at": "2026-10-02T09:42:47.795Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ms": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "e4253c91d95b31736b6d091acda8bbafe156ea1ae923033bb8b0fe60b4898799", - "updated_at": "2026-10-02T09:42:48.246Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "nl": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "21d138030b0c8994d4eb5b5bd074caa0a0734c7a87f5e223b3096c08ad999807", - "updated_at": "2026-10-02T09:42:49.503Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "no": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "a0513cc76a7a92d90ef6f865d9d1f2b8e06449c846e547a3fc23ec1993d55bc7", - "updated_at": "2026-10-02T09:42:49.796Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "phi": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "9bbe1dca5e47f5f74831157916f861d247f0cdb04845b368efd8f9eed4d49bb7", - "updated_at": "2026-10-02T09:42:50.776Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "pl": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "ef058c7437dac8b520b55441e0f1fec217b38ba682826860ce7bde862527aceb", - "updated_at": "2026-10-02T09:42:51.053Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "pt": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "33d691043c025628fa5f0db352ece6fdd93131293a1084549e70f6f0489471e2", - "updated_at": "2026-10-02T09:42:51.348Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "pt-BR": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "4ea34d95fffec4304e2a63fab05b887051aed0b1f23ed370871a01147c45cf85", - "updated_at": "2026-10-02T09:42:51.687Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ro": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "0f2529addd4a159c87ca6bd5704bffc46667d31a0306ac384faadc6c6916f4ce", - "updated_at": "2026-10-02T09:42:52.041Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ru": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "a4531b52684018c94d2b71982f788064a0269c3235feeb0ecc624549a007638f", - "updated_at": "2026-10-02T09:42:52.337Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "sk": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "018ecb61cdbb73e79fd22087c03445f303f76e5c4c11e8710a413362b95b4941", - "updated_at": "2026-10-02T09:42:53.027Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "sv": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "3f3d77ec27a18f162a76ff9a7d02c83055eb59afeb0e69672f8c836903daf7b6", - "updated_at": "2026-10-02T09:42:53.891Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "sw": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "6f531e07d11dea4017af33021047b6945188174aeb33273eca0734eba8e00d85", - "updated_at": "2026-10-02T09:42:54.259Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ta": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "0746ec10085448dec8f965912ebf4b0c3e7a23c6e2fd4c97d5466b225cfbee3c", - "updated_at": "2026-10-02T09:42:54.713Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "te": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "952cf00e23229b6df08dc03f63a6ee0a86bbbe959ae83dd7d87af113cb326c92", - "updated_at": "2026-10-02T09:42:55.233Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "th": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "bd9b7c782b8bd348c0194f2ef1c625aa9716ba0c95fd741885a79b1f7d92f265", - "updated_at": "2026-10-02T09:42:55.634Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "tr": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "dab3ff7b6264feeaef0d21a096866da63bd978b1945b71a6c10f49a7ecfff7f2", - "updated_at": "2026-10-02T09:42:55.980Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "uk-UA": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "acd71206000f003b7ffdbbaefccc6df9cb7bc16f80b256eb8ea8c692c3cb97db", - "updated_at": "2026-10-02T09:42:56.597Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ur": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "d1bcf55b8b497e79a383c4fb0acb28323feae5e79489803a7383c2aa8cb11dbc", - "updated_at": "2026-10-02T09:42:57.041Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "vi": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "229471054251672cb042ae16f61cae525e4c3a4076d8d7d454bce1aa511ef793", - "updated_at": "2026-10-02T09:42:57.633Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "zh-CN": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "660dfd19ed9bf1e300232b0cff26d909e42139ca1bdd0e53383ca5c34676bffc", - "updated_at": "2026-10-02T09:42:58.640Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "zh-TW": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "37314f1f235c941b2c963a62adc5aeb0dba38fb36892851797ee9d4efbd43d52", - "updated_at": "2026-10-02T09:42:59.011Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "el": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "acf0e131ab1cfe343639de6648e082540185dd6e7b2e80e49f40de6a9d210f76", - "updated_at": "2026-10-02T09:42:39.752Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "hr": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "1a87dda408797347cca368f2f531b36995d68e9f3c36db7d9f81057fcf077a0c", - "updated_at": "2026-10-02T09:42:43.142Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "sr": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "aa0c78371e05f140c22819ed471a6475083b2e72532af6aa5ba867e621b6d37a", - "updated_at": "2026-10-02T09:42:53.601Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "lt": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "9a98bef99a65fdacba05e2908b122091ebf53e05cb1fbefa22e4262f7ce4410a", - "updated_at": "2026-10-02T09:42:46.846Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "et": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "c4f2473991aada0688cef2d9279a89c11cbe10aece2d5092768e180b211f15bb", - "updated_at": "2026-10-02T09:42:40.339Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "lv": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "5a4820d06e79c3f8b644caa23c14f8460e720fd5cb06f271e5615dbd624c867a", - "updated_at": "2026-10-02T09:42:47.172Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "sl": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "db8453b814c264352031e1376965ae2412ed856854db2ee4d0a1583541cddd6a", - "updated_at": "2026-10-02T09:42:53.304Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "mt": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "92f592af9133f15cac03426196a0e2888e80678e2cb37c8f27daf554c3fcea3f", - "updated_at": "2026-10-02T09:42:48.595Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ga": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "1d3d2009face9c354c2e736227641511cf404432f18f73324a366fbb1a0d2864", - "updated_at": "2026-10-02T09:42:41.595Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "kn": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "563f5e62ebdb11bc55222256781c47ab42813ae03e64540372cfbbd97cb55bfd", - "updated_at": "2026-10-02T09:42:46.142Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ml": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "783994532a029004381301da48048e7a886b4407ac88b9074e4a2c3f044a9df6", - "updated_at": "2026-10-02T09:42:47.491Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "or": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "7c7e1db8c9b20f0b76698a68bb0c5f1754ede657ac0520695354834b56c190e7", - "updated_at": "2026-10-02T09:42:50.108Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "pa": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "f3cda59d0b39b6524a579cc21421af3fbb416b7c3f8b352dbc3f2275457374e9", - "updated_at": "2026-10-02T09:42:50.446Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ne": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "7fe3447784aadfc0e1853982344eafaedadc262f98c41f24ad119f71513b3338", - "updated_at": "2026-10-02T09:42:49.201Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "si": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "cfc71dd5eddad9674c815b2abeaae0172a83ac91ba605645447d2d83e24cdbe9", - "updated_at": "2026-10-02T09:42:52.747Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "my": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "69169c793f22a09a723ac974a344c518d32196a2148e6b3eb58d0434b90fc7a7", - "updated_at": "2026-10-02T09:42:48.873Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "km": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "18b9dae4c3b6b86c0c7413b32ce4a175b38aa90bd172d5049cd3374185b729c0", - "updated_at": "2026-10-02T09:42:45.808Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ha": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "86ef9a149758112b9de44db2c0c64e92bcd33d4c0db592735efee2ed147e21b1", - "updated_at": "2026-10-02T09:42:42.215Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "yo": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "6a2fe0b2cbbb7501ef32ed3c0900fbf674f173a84d1349a7094ccc78491138bd", - "updated_at": "2026-10-02T09:42:58.138Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ig": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "ced92ddf4068d9a6fc6cfcade06b8728fa56c543651c4c80e64ecab1c250687a", - "updated_at": "2026-10-02T09:42:44.362Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "am": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "ee0e9cc5199ef9f8c0e5d5ed40a7a9d7e869ed69afee51f6e71f4b4f43396ddb", - "updated_at": "2026-10-02T09:42:36.672Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "uz": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "8e9e57285845544d348ccb31b48877e63c1eb54f7d56f3a5a20edabc47b08ef5", - "updated_at": "2026-10-02T09:42:57.330Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "ka": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "a65653aad737eb0807ee26907b8f292ff867e51c7e8e7964a5302943d64051e1", - "updated_at": "2026-10-02T09:42:45.416Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "hy": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "2f6bcfcaf3a0b938a5a0a0d31e6ac88ce1324176a0ce0f3b7a5bc3ae9d06594b", - "updated_at": "2026-10-02T09:42:43.784Z" + "updated_at": "2026-10-06T22:45:36.370Z" }, "bs": { - "source_hash": "64768029327b60fb281f2bbbd8325bc267aad08b6bbd8b7458118fe6070f6ed9", + "source_hash": "2605075029ea8964550ac65df6b62740efab0f9a1ab67c2738a38b311f5cc848", "target_hash": "56af81870eead22ca2f5ffe207f7e19a389f287b74d3abb3303809c5262b0b03", - "updated_at": "2026-10-02T09:42:38.370Z" + "updated_at": "2026-10-06T22:45:36.370Z" } } }, @@ -2616,1112 +1691,332 @@ "ar": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "a13f0fa73e8360c24d4bce5dc0982b0a6daf0dff44f39f2b6ad68a9eacda4ea2", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "6f13c0691d1f9f838291782375fe6359ab86dc0239e0dd24063537c1211b3092", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "62599e2a9dff8401c1bea8cd5f9f5d4903b5ac1d9840e55196e94457d33db476", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "d26b51d9facf57c8f6f29ba9370df24ba88e78371780adc653da357a04c65490", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "380635e9a0c57fc37926938614d79d98f17eb5780f1d9305ed20c8eac01b8390", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "8d5201b2ae0f35fb94994b0c0aaa3fbac185b3313d200ab009b35932a781be27", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "b4ad29e1d636cfe12701c7900f722958a9ec5f87300db5a5065ab0d37215e176", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "4de0e23b84793bc38bca077bd423102f7d9c4e8a35d9b21b4d55fada4a0e20a7", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "f7ab4048f70c91b45f7f9a987b400675a4a7bd3ff0fe5e4866698ebc47294cc1", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "d98c5ae931dd19873b5c24000e13ed934916e0b8141842ba5f12b6e06ccfd502", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "b5c9cbee4db4c7201db3ba42f9e5433bc7fccaa8c4d81b9f86979b321dcc2281", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "c8fdb67cc83f7e6e19510e08506f2b02542ee391c23b6d5e2af981440a5dc94b", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "b7b40e6876956fa736898b251720839f3cd0a243a78411ad8d65c6a5936be3ce", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "1642f9a1a2769b2a736599dedb687184f5b31e4e13bfbb4d876991355becb54e", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "b2db377b950038da652ff8473da47324ef0a93cc0ebad612a23739ce665f3810", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "db19dd8612f12a017e6379f618d3f35aad20d5774107562de03b49e0613b4342", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "6bb47cf493eae59bbea5a36ee46ad44e526454214429cee1baa14e293d7c8254", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "804807a47da55ab4b13f58c1126f1b6bced27806e9c3fb6f98525d4942c47a5a", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "6c57e96b585ebd871711a8f02644c660900669d6d179a796c4681b2fa1693027", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "f672d2bd0d5d8f4d0481da1942136094264fdc8be3548b951abfaead3cf30a42", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "332f42e23b5070861a765b5d342f9474630ed9f7104217dc5995e097f78026cd", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "a773b2ec1efedde670b1ba20ba302d015637a7d4ed739d97ebbf2578216ab5ba", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "456b60ffa5bb2fafde9c348b877a8ca97764486e7a6a055ff70bb3619cc8093d", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "99af9e7f21e21b9ed04fb062763f56dc4239ccfffdd41cd817bf0fd41c4745bf", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "17b89ec729f913b23ba80cb328384310a534289bf680b27e7641a98dc8b64c61", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "fa83f741d8b22f7dce4d71525910f8d1f8fd46af402061a0fa605fda7b62df82", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "b5c199bdd4fdc4dfa5418e0085d293a5c308c5279bb431447a7e7fc20a1c15ee", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "9b8c8188ae060d8b545bd83731d7f1dba468eeb7fa1a3c8e6ed3240c1ccd29b5", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "15428ff5fb57afada95b813d9587e9edd67ac615177227ef5429690097cf5508", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "50ee4a8a6461739e145f05daa803c6b1ac6b374ca508018f575c54f412646886", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "6e84cbc1f2a1dcea55859050c6fc149add018e6282a812c25949c6025bd271fc", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "f2944dd435f175ae23eb53b0a884caaa5321e78e113165fd3ecb661b5c204683", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "08689aee0f7d736257063df8a408ff29fb758ad3ba9f02d8281b0a32f4c9fdf8", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "ea2c57f911d6a45e960e6f3f4bb6303ad05f5a7fecfb9e2f25bc41d5bfa99949", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "c09c9997a7f74019d0409068999947d9afc0bf103238d05314cd85b4d69b11d4", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "0ea2a72ba015a38091ecd950894e8de53a01e2a59c7997735194e9c4c66453ba", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "88e1115dccf9b53d2ae5a362d5ded9da584c1012e71fecd4de9ef7f5cf923a38", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "d08ac1781e74ee0027bf9d504fa4848af2c3e1c84140b4104605a44caa8b079d", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "9590452971b42e95353b0d008a1a74adafbb280d93050e6caaa9defa947da6d0", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "2a2520da2716d429e69c2c7a2cd1a4828d0abf93e144e55c2baa83dea8cf02fb", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "cf02285936f622dd07f33f881fabbe009470ee10ca6040e472630fefb8be59d6", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "148a3547656e4675b3a367308401e195ef4849ebb74dc5dc5a68c09e3e53dca8", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "3747d6fb4634fd8a7e754cb9f9e536da36959ac79e99236274630a1ad811a0f3", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "fe9c2f97d2a9ede00b17ef691925f04d7b322f31a10130b449b66fd695654874", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "4c3ae8ebd1dd407b2ec2080dcf344e655b067bc729ac04a3264137bd5d930537", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "33ebd48f52f60162f2baf049316645f8488d4c48f43234d9debfad54670b0882", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "dac6fec9a071f2c68705a1972f5bd31890bcdaf3533c75f8e450d1a470f418e7", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "8425fb98f17f1eb90ba178e7965f074753607e7412bc04fef1b90b201a555f68", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "6623d044252832a6d1e5b5c088672b6e2f064a08817ce775cb2b0c8f0fc5b49d", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "f66e65e059ec011aea49950140276b89d9b7e2c737a6a3c833791862d69eaf0e", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "4f1c2c79c70270a36d65c23894df72548b750bdef23522be2af520126771a777", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "5028a2f595f73092dde07e967d2b97a926fbecd14b5990809dc7370dbcdeda05", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "4865c35fa2735db6cb96696eaaf99f16beb7708123e4fee2d0f81694b3d53a3f", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "e9cdb69ab17e5e3f121aeca4d64d19114c43cb8edb30d0ed6d8510887cf8fb01", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "0f353914c52da128e50f2d36e828926832532de77399191c8d2b6bfb7d56adfb", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "c273549a4a375e2206cd417d8ec94dd51b4dc6d4682b31a8fedb935b3a406166", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "86ed471d2e9d9d79a011bb7a54465587d195a5da0d14fa19dc95f6623163afc1", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "1665c3b49aee6abd8e306871c3bcc5c9931a5f7aadfc0e626868bbaa5dbef6b3", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "15b6c16b050ee04d5c0aab828436cff195a057a58f9cb361f514ee622b307c49", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "3241f88b70f83bdd9092c7dedc0593c2fb8b21d31348085f7bb74d1a96eaa184", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "d1fd3962196d6190ad0ae53bd96253c7b8a4e2b55a4122cd81bf3ecbcfc85b22", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "0fc8dc37281dcd91fc8359fdac90e949efd066e2c9f31c3e5f3a57c074065047", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "a707d9f9cba45e63c588463f6be48af17485928b8aa361c52aecf37241cd457c", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "05701998a4f3af43da029f189010296941d7c71ee00bd6abfe4644c97e01d50e", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "0ccbb60b2761b0b955978582cb68c5ed44adbbacbbedd0bf62b0bc3daa869ba1", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "66fac7b0f9f34ac357594dd3c394616281d5c0cb31ffaf96d2722ea277a3971d", "target_hash": "8bbebf381e421c582665258b0be61da0a5e7e80156267dfe457a2fa267b9456c", - "section_hashes": [ - "8b7d0d2d9f1b", - "53a1fdad5161", - "3ed1d6688e49", - "f4f12d9fcc4c", - "ba3eef1639f1", - "08fab9471813", - "a425bf3cea6e", - "07ec37c8737a", - "62b1ad888029", - "9ddd813b41e7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -3731,794 +2026,332 @@ "ar": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "54183ae2a3b0fbaf105514c5b0b077ee8d1c6aeb6974dc3916bae40a3e394ee9", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "500f9a7f3f80c0fa82145e9cc4d8a0ec62461bae3e19855e8d29a0ccf9c44b66", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "c010ea15c7e420d0f519e3f8800dacc30dcb972b9d597eefe2eb9147c43af39c", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "815779e780003f1b1e0ea09904fc475d62c5b51cf44982952803b10c560ea507", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "c520d161cf64a2a3c00e29ae2e24cd5b318125ad969b84768d1938e4017ed535", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "5e49a7bac7692321d560c6ba1429aec38324a151ea6a835b981ac7f45d0fca45", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "502c1b651bd70f732972a39a67d3608a8f0dbb1b74afc660020914bc11abfb85", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "f2dcd1e37495b7cc52c1e89544f7956cac15876dc8926ebdc444526a312abe57", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "5e3b761464ebb6a5fdb5b6f5637e5baa621fa1b324336050db5462f4024141f1", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "d01c3e98353893317d473f6cddc83614d8066a1ac5b2bd07c521a8190d463701", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "32d95dc3651a204092aee4ad2981f8082d3821988aa5791a0685e64f57944fd0", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "d0dea83a94a3e9273a664a73b4e710e9f59ca19900fdfda75d6acffe28bda82b", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "a8d8a7375e0ef2e97363ec0cf5de00a6101257aa501ea4853e0a3594d8745544", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "a632dbe3f4d4239c6cbac9aa2c468357a0c1deabf1396044aae1c2c8373fa1ca", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "3c45842f992b20d54468e9b1f01a4fb1696fc41c5258ed3074ec8598971c1e56", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "4cff280789ae7db295be4c63f4056169a4d40d4f92ebeafcc14713f2948d72a7", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "d177721f0c507e5156bcf89ae27d179b2c369aa10247d8abd1a0b035f0e551a6", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "9d15cb27ac9fab80766d2dca77a42f7cdabe405e3617ec2c91abe3ccb4d15d8a", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "b436759e6df7fc16c4640788ad51adc8eabd6917ed289b2ce155d2765fb91569", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "f75d38f84eef2c6779bc92afcae33e18308b43f5c950f5d87581224e785fd6a5", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "6df96e557ca8dffeef00c444ac365e16c5ec452e23fe90285b2c7403019f2912", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "18a7a526f23b2785c7e2e8942c4e4281981b370dc5804f3bed869eab50d6fdc3", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "f5aac5aeae8ccda1650b88246b34443091db27a5f17bd1d72fbcb54e49ac4c1f", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "597086f85e4ba4093609eaaa2e636568505d2fceedc5e5fc6b29fe045af68c4f", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "21360b179e3bb842a4a15210abceec0f738302d1c7c9a7c251c6d72a0fc45fb5", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "1880fa16211ee10da76d3eb5c65e39a19033ebab118cf3946794278c35030560", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "ae4efa917b1990c8d1feb08641eefecac051a73cea24ec7f20fae18a649fca9a", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "bf015d4426e8d9deefde3ec0ee45d13cce82a04b81aaa2bf7f1fd31085f9259a", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "f817594ee7233cc97560cf2ab757aaad944b5a65585839239c89754a892772ad", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "1ffb5e9a861427cf126417c521fb46884939d3c405a34c293308bd69936500ac", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "e09d48b20a8e4875b1151a33f25aa626c0e27614fd9222db72f71bb1ba794d32", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "eba6f44a7dcdbcf7606e75e627d18b67b9368fbc076b745e31dc55902beb9063", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "1fb2c25e927a44ce1508b4f310167d93b36912615a2808d0ce6963f21b1c3298", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "1607117bea1ff4d077b832fe6a6a478e471e8fac5c2f039687e30623a3da9146", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "e582dfd0d5fef0156d176633d845fe3d577b80b33d7bec11e253324f87523601", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "b76c940bb0314e1f65cc52043fc4b3b77d13591559cb85d3929de08ccaff9528", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "baf39be747d51d72cc64ad31462a505ba69d3d5439e9ba0e5477fc511989727a", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "8c1d97c20c73729b4e8885da93c1cf2d21940ab0c948330aba16f391eba8bab5", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "762472f591b4bfc6b3e6ff555037befe68302ce708484be21646a1c3a9eed9f5", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "5625937eb15989ab9dadaa33a2e2af6abfdf4d4a4e0a249e4a1c17214e28b5cb", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "bc125068109aaeef5dc98ef97421a5e8bd491d1e01db50823e1df6b3f8f1020e", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "e02e6047f5fce237b8b6d0b64c461a261cef323a6223e9b67b37069c426e740c", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "ad8827db06142a1e7d605e90886e2730a3a180a3b3b1d1240e5f5db0689912d6", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "92d1a0459e58827a2c57caa32943b6d748c50cbf5ef63e12c40f75786a1860d6", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "6d06742e9f84f9e69bcb0935bc118b0395c171b055a694c84161ddaeb5bb7e4b", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "b54359c58188ed75d32c10b4178c915f037e949c69c0ddd6cd2c1434c072edfb", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "3eca82ef76bfe79eca5fa015d47482d44f1613a6be0dbe56d1bdb5f5a7ad986f", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "21a537a289bcf7b0cbbc85383c79530e59166635e11bb0ca6b7351099c6788d8", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "f7b90d9b421eb3d327c8d6dc9625e9f06d9ef663b636b1b2620832a3b17d9cd3", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "d1338818a0d95f9e874abb6e48a45a39814e58c16c5b88ab845919e161a3b1e9", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "1bbbfba361148ee7d23ebfbdb70a5f88f2b426f3eb9da88ddd362367c2323f86", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "bbb428dac80595e922314ebd0131551a25ce9c94f8e54df6b6c356ef56d3c4ee", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "1617e792cee6b8f531c65a4c6bb733627b403e3d4edba8b5ced15f8e2be68427", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "18a4f9e2e1f805a60b24bd328f012c368329ba7284f09374f327ae4cf4647536", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "6fd479d201fab14858a52eab180111bd581a17fe3336fcced0e702e08fdd28a2", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "c9e3e4b90504dead75010e530e77e1b77c4faa025733849a55d89c01d7e187ef", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "1fb22bd939d28085d5df246cd978e2577d2aa9763a79941f79a5c1ef06e956b5", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "9ddbe96a67b3dfd5235d5f4f35597a2b8ab4d36f44441d9674032102c4c0b8e5", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "c17e7f6b21cc7d9f130b77c99fdae91209a595dc9ab4fd30f5e2c0a74958dcf2", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "79f23852dbc2aacf41b324951aa26c7fe4c4992c46fd75300e9d2ab42ad9a8ce", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "27895be9e66d0b82e79b7260d6611971a08c6669af58d6c28056acbf33a4e54d", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "c4ce6dcd0dc3473c999aa529472a7cd4c62ab66db14f00299c8d9756858ea631", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "13eb390e5138f1ddd5de63685a6c221ec7803e815aef4a8b698950e3fca1675e", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "84b1c7fc8ac038699f8c8ed3a66a4f9b0d33ffead43b814595e5013fd4700818", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "f4a870d92b78be78ac4c742310f01e351da5b2d001324fbba20a08d344b93fba", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "6fe1440747e67daef474f4fd1c869e1a8623cd178641a83a4099085829dbf084", "target_hash": "8f043b81c9a8494a08ebc4baeb1623b5b18113d6f6f1bb06b6e4efa7555eeca6", - "section_hashes": [ - "881b0750c6d4", - "eab90d9affff", - "2df9cba24845", - "0ba973060355", - "98731b3c0167" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -4528,992 +2361,332 @@ "ar": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "f20f222a61c8c44929471085c1cfdba56698b552d9b3f62e7f76dc59d5ee4d06", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "298e765508561f71110d076e9594b600752b7ddfaa553dd9792a2b166d2aed36", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "1f4f9d609d84b80218d31c606795a54a4739c3fae97dfdb4681de8cd6d254efb", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "1417e089111e95726f3494ee0156f7a8cafda20c75ff97134c0612ce02f2c7b7", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "d2be862f70f21a4413c5f4a9e88b538c1ccbe5f838c21a6b11e90be053f7baf5", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "d37d933e416f40d3732fd391b09960dc4a49b68c01bc1175a17aee9b7df4efd2", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "371554b1e93c9424a727c017f14ebc9511cf27927c6a621f6f2c7c86c9208d77", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "354a03f8676592c8cd1e955540fed3cba7059f51733d0f48bc9d1a79ddb3930a", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "aee49614692dd8481a9dc6b3c69b4c02c14d71f1f1e69bc988ca58fd9db54e31", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "33c930e61f8ff617122520ef967342f8763f8a3c78d04adb7b007592faeb24b6", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "e41625dd832c09ace486b33ec404b35f88de035724e8f6cd6b5104442efba2c3", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "898f6e58f6d49770a820d51038a8621cf7c9a40bbb3485ad81d7b656d10d3ff2", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "67c45e0bfe32cb978695bcc315a1c6ef637b22ac75c00457c51513f169ff9f8a", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "79cbcc4d73fd4a0797ce75732014e111e9567185c19683ac0cd8bedfdb83a82d", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "f27bb623c54d27127c484df1a691eb26f96b90e56e4230bf055d18f24b1a494b", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "44f969b381c6ac25f641a3c4e1b67e9829223529b8274eb9143cf5b855113935", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "74b91a1ac070deddfbe9c29203ac3b340ec68065a865a8e6d6c68b9910d1223d", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "a0292f3f414ede8d50b16d9981bb48074d53d677fab1154f27b7d93a233d5648", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "a435b419f725fe4d90e904bc56071e31adbc9931b88c4640cb52b5dcaf6ffd4d", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "7cc8a67187029a585b59908ddb25033a91467fe3a10da15fa28ab8ed8b193815", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "432e2d333266d9ac7495c0b6851fa4f7f79331a75c5ec3423aceb1d3961490cc", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "00955d75de4030d7f478f21b837ac6db8593d0c0b2fd294d5ac81d9db47e4657", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "430b4e6641f37f6e2e6a77f17707e4ed1be49efacba61064c168525f466d05cd", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "4833b371572fd27d77bf66b15a8bc82fd1e51ac4d388573b48d049c02ca77828", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "d9458800c81220e992df6af26afd0470f7e92f3a33278d7aa41f03bf1dc2727f", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "ecd045d7091d6ba0c48ac8d4d217b81eab006f243da272ed410ef305c5d0fd48", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "5d477a83e505ee3f8bd924ede586f6c6f1603d73ebda960462d86f6ed0ea23f5", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "c2c1918f13ad8e3864681989ee42215a890ce7096f3f7008f0e52cdb16ebe101", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "209cd6f6042c8637294704d28e00955b7b4e6d659b00c98b811ff6c1dd359f5c", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "b79326ead62a311cadf62267f394be4dff7f26737b7156a6af5b0157a75dffd3", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "89d980122bf1d000efdb210838ca6f6657239577942b050bc48b357cb1ee2419", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "a38076a3ba57e53bf59992403790af80c55999510c98acfb13e64c9a21d5530b", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "a09085665bf47d96a8ad685fe1e1d0e36e20401a5d55a6e95fe7703aa832e6e4", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "e7852cd96cdc1777be89e3c916e72f3445537e880bae33ec212a509666788a97", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "cd29cf3ad36f570f29036fe5632cfe3b5cf51525fab929cd107b99279211b1c0", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "797c59cf64d834b104190dd831d6b6070b63ae1c7072f5fb9495b69dfb36b069", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "07f62c929edc73d9af7eefda2522a06c71aa3dc7e63d9935898ff6e17a7ed829", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "7eb0ed423b880149bbd74cfa58403152105526a9c529dac080c5646752588fb0", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "9a1786b8a54a64e6f10b575691253e84bc610da18bec5122d3fda57f3fcdd98e", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "19c8a8c1a31e6770deb059bd19874f879e931c23ff61ca959a78be17e25ab4d9", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "9fe6a2a6af0eceaf621fab87e3f654a07c0fca3ada61b72165b0e6ccd5ee04e3", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "dee748775c15e483990a069913086bc911e607f6096a9127684d636b3b04d281", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "eac3be3112e93bb71cc71ed35caf823b82c54eac45fa84769ddf75a249e590c9", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "eb81f4ddf4dfcb05b009119e64d3567a943f054ace48a6a77c8a8ae61c756d8c", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "574de20010a45a79949926afbf8b1c368289edfdbf56dd8a3608afed1d9d6949", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "173d543cbadca1be226ef4c5fd38fb31ea0b3a4381f36b93aa2b00276d3cd57b", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "6d08d4518ff09b9cebef4e322130775c1e6b2c4d2efa04eb13d525128c90ce5c", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "7b0f36e6cd9f6ba66c56d0ea0f387dabf4ee9fb28676e6ce52692d63d5635e1a", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "8bc0823827fd5764e6abc7798ee3b518bd3835162ee8afb91df52b7bf2e2ee6a", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "07d800e79ee6533e77b392a04208099272e18b9e87cdaead3c618b85aa403381", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "288520f279edfc371381d08778047115503c550c993686dca4f6502277aa321f", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "5f655f7dfaf4184e33e30891d0567fcb2fc0fb37c939d302ce5750fd504625ba", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "b5142e5fc70092b2fbeff50b77b320befb5a5cccc0b8c2921a7c64f989ccf858", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "d925519eaacbddaf5a4053bdd1df63fe36001c7f1ae463117c2162b8392a2fc8", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "7f2402fd810a959db4a327f08d8383feac96ea739628fc08137ffde46107db36", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "fbc97aebbe00a757e6dea6292a6faafbafbb0b621d04f43fa97e5cd5655f40b1", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "de32852cc6c244a6ba9e61f6e99fa2c1278d67ae9e50cd9b6558f805ca7a2fd4", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "fee97167961fed570f0be2bc8f31f2b85e8976260d64119d808abe6b4ec7e4f6", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "ff17293de8f3a70c39e4da76e941dfc75d0fdd7c80d1b6e73b91a82c7aa180f9", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "e324fd20bb79d7a567e8d8eff5f73a0bc02fb0d8d4ef0abccb1f5c3e5b48ab9a", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "daeadcde042943ed9db3d2dc8851c6e57bc23508d6f667237abd6beb06c23ff6", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "8e1d0be5e933817b50188b62f911b41ab6fca4bb13208afb16eeef0cce7a1bcf", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "174e205c42d5e39ebfccc672a52e0ff0c89509c0690a8964e077c00a3a0cc56c", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "d89f12b1ca69526aa3936f3f8599b41c094f9295abf75fbd4921e489ef6b4966", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "69b83d88e7cd43b602d6db5802b5f8d24b4aeadc8e69304843de18b179a1033b", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "440546e20dc2217adcab9d68378cc95488e40669400fde7e5d1f13dfafaab1bc", "target_hash": "303135cb3a01b123f7ff6664704a10f876630888edaf1aa8b64ebc563f79a47d", - "section_hashes": [ - "b3db29eaadf5", - "17fa8e60fbe7", - "bc207474dc4c", - "8bfd2dfcb863", - "95dbf9b673de", - "93c2814e9e8d", - "2f04adfe418d", - "7a512a6354a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -5523,398 +2696,332 @@ "ar": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "72d77194af16afaf3cdcc33f0670cafec245f5e23a3ad84dc2ab05f6d0acfb68", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "0b592581d70c62467f7dbd5b32ee65ab3599006d89a00b53ff45fc5dcdcdd76c", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "5ee1ca838ea6deba52280824f108b33172b267f3f6fcb70eaa97775338e97f49", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "80076b7c2b501a47f0aeb5b2f9df86768b8c90b0f35c54ebd045702450f1bc25", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "8fdc0d8fbc386d61795502fe9b3387eb457a5c815b103a6a4fefc51fcfe5adb7", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "3331b0ed700590ab6798971f4f59641c1c24303319215374a243696788f31fdb", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "a72e5ee15fd7b2bbd9553175d78c2bd68513745faa3b35202624b76da6d2b155", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "ed90c941fd126908f53cc402afc903d3c5a14f65c431a3e36b630a63ed35c6d5", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "53c936217c3d50248036b7150195ec29a1c6599588f1fb8081273dbe19a29300", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "d1b6ed59d5cf7cd562d0e8e90e5a48fdc6be0641a92d319b16e53bac4036eea8", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "58890b545bdfe849da1890fbe35bee4f769d869095d0cadfefe9089031d2e8aa", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "5c379a7ffc5ae8cce9864f0d1dff599332c7a216d64c99c6fefd63d0ab2ef227", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "1bf9c2668b2a8ed5ca9f6bb2fc0b0b0420d7a0c4d4e056c5d3e312794676b302", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "04960348089428ab312ecbfdb6ed2220649688f5377e6b96d502d406a87ca0b4", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "074925f69d9611488cb063b7da7423d7815ae7f4f8f45eb17645247078828942", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "1c045e88c0bba9a7c5420404d5bc8f05165cc917235f77ad3e7e8c44ce2ce9ac", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "357cb060eea1542e1fd9fef63a9536f2a6bbab5ae03a523b5c24f3f26c1c73ae", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "0fda10d0e6278a38ba3a6ccc50999ad44a7100a9cdba78ff0d76e22d31000f29", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "3b5b88e47664800e3cc6517513bf7c7a8d6305129580ecaad69caf8c410ae038", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "22894ee5d2b35e4905bef1c08a926c551b85b54ebe673f75cae73e035cdac296", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "e5693eb3ecb74b714c6fe4161e8cce85d91636412c178dbe3fc71278ed4bb496", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "c84fd0f897b353cf669e8641f62147632182701e1a1094fd32f95edbdc24a2f1", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "0209588690e45dca916c4deef97f21f06a48f6e7df69c074d39ab8b33936ed1d", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "55b5019965fce99a5d23ae0fe522497eef5d599f36c806bd1d7dc862778863e5", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "daf81e495a5495a97f48a7aa59ce6665f5aa3f05581a94f83fe9353a12432d6c", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "db71545058cd116ee932f35359a5215cd6032b25a8ff6fb87dc8259b55c8856c", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "8992cdfcf34cdd3a1e78a14efa433c60e6b86aba4480e975f15e6c58e5798441", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "b1a08795db57e05e789d6518770394d45a149fcf3d0b5df93d6b7ef034d63b19", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "36311aaf521116e656430d70f8e9d4895d5aeeeeab5a939b93ad2680aa90f12f", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "cf308a4bb32863c533d0d4d3ebb7750469ed28ea49d0b780b8d3b0f0c1af8f38", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "047fcfd64a25e7fd8a1d396fd6cb97fd7a2efa5e7c0a373f1916d49c93a0aae1", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "834f1b29653486f9f0ff714620b334b39a632f05c5d7615fa5c376f83e4cd88d", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "ea8a1a4783996206a972bf851707dc73441504c6d63f8e37d3ccdf0e845e29bb", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "e498aef195dfda31ef105abbef98cb3848f821eeda3137366392ebdd489e34fd", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "ce5b662c5bed233fb7540215b3fff5a27e5c596d57ffe396fd0a43504aab3fe0", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "80005fa486466da29f121653e7afe72d4d976555e72ae272164818afdf877aa1", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "325a28172f03a19c78ffd0c0e4bb5dd4c2fd479365918ad14d57811e14687486", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "dad5d4e5b20c6128d81c4253296a9e4d5d2b92942caaee65e76818812dad84b8", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "d76205798bcd70e1477d461782ba22cdf765f41571b80dd9b7445f1b79aa6113", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "c6450b79a97c8fad55bd909dbe165982a983d97329e915023ca4f14b8337b4bf", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "8293fae572fc577e18b6fb720d7a758c3dd1e7601cc5c8b4e6615d1a81fa82ff", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "f111136b7ba8118b7bd583f32b070959da36743dadc8ffbf4e036eb5af8e55e4", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "9af7a8f916737c94568c04ae1952c44cb8701ae15e1eca43ed435d5b59bdff77", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "d64560be11121f3bcbf1202e85a534883785bf885e9f94c7f66643a40f8cf5ac", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "3173479820eccfedf19c37c1410f07f747b651058eff331f70fdfffdc2b75c6c", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "6a74aeb65bad4282f052e9167d66adfa9088402f1825fcaa521608c7f482d09d", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "41775ecb7653d0a3667078e84bc8e76fc5aefa836b0dc9dc198c187e53b6d6d1", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "e3d04a6eecdbb5a05a95413bda60d940c37c825eca8351c989f3a31947a5aadd", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "9be4887056bf1d15d33cf923d5b97f33006dd0be9b64da534a3560ea5ca7adb1", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "2c6aa1e6ef3222d0ea8512a59d83eccb7ab37e7d40b3cbe404edcf262f9eaf09", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "d9ed138e79658bde71bac4f3486ec51512d358fa7d9a3c8e60e9384777ed5957", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "9b93a569e7aa6e8897e96c92fc86aba6c6ff16aebc031f3e0bdbfcdb5fb508fa", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "434a9f02b47c95fa2b7b6d1762b445dde7e5f58ee6a9cd2f3ba8b4e487c0e59c", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "fe818b2f7fada1399c09489b7e623049c08633a8176368939448da6efac16bde", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "3f6771524600d145c6fb9d776661a1ee5d70d1db3785bcbf649d5cbaa775606d", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "dd5242b1da7c8d2fbe5ca5e5ce50e969298181248bd76a2e96da6242dbac516a", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "8146a6036d70ac00d6f9ceb51ec9c442f9eccca070a38fbacf2f97a89b2fcdde", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "663e0174fd641df78dd76e9a0153fad77004d83e658d9c6b9b6bc6019971b381", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "9bdd25921f7a33cc99e36797c0d11ede2e657c8cccb71893e0c5d45d937a31f7", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "c24668db2c83039d6d9ac497c72b1a4f7a021961bb0fcb792eb7c2a96a68335e", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "2a16db8a1362a7c31f263551c6c7f1b23f2437e5adc4bc9a7397c0743af7f5f6", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "360c2e7a3e4623167b56e664ec5d94b5edb36f9461e6f7e12b0ef2fc6d939822", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "2aaf0de14b8987db4c35f72a15205c792e27d0710b57bd2b26eba4d69ef4b430", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "beea90c2e50ea97200100332118e0735c55db5f90e4e96011061f7ff5807b04a", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "e0cb8124c0a22d0456e8c5b2b92a421e8abc4ac01c3361fa4485272bbf44951b", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7", "target_hash": "942725904181fb9fb45dbec8fe63f8e74f3ad250ab40263a02c33f26d0a4fddf", - "section_hashes": ["11f1da7d820b"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -5924,398 +3031,332 @@ "ar": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "0170f40aa1081f4a01abe9d38469a8a0ace716d5973fec1686c7ecfafa061208", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "b70a0e953cf0cffd56a42803e792ccba02fad1bfe688c4990265b2b5aa10eb94", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "63f2845895ff3360ae2e0d75e7fe278416b441020287f8c179e937c87a8cb817", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "17c096470c655b922f8e1aadb0409c83080c5912b94063a954b3b3a3f0c5c351", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "fcbdeb6ff3a30c012f30fb73daa4c6eb58ca3019fb6309229b93ae319039b431", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "deaa1feb26438424e08bda52e89147f752467653268c7684d09be156af72b52f", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "c952c94184ac7caf5c8fb594a9eb34e5da9991f1ef8e0925d3679d0d7eb065de", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "bf0f39c473f842ac928ed323a617f0b84226633e7d4b1a302acf22e5588692fd", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "5cc40886c34c26c8001abe04d2dba008b531443f33e9955bea7601b47138558c", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "bca0821a75133a50d39f5fa8339d7eac7e11112fe4ca3dc01b809b7f8581dc6b", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "db1192ad6b2a4df15fe24de91f02060c37dec2829574dc0ab40611ca867c4107", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "e17aebcfacb7cf10d09d516305345dafc0f537716e02848ba6b380d1ce8b0446", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "e89e28b1b4b6cb6bf24cc8116ae0bfbd5f7f9405482e4468ccd1c3ab0b920c2f", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "8797533b11f4e6020c8459fecef1a1bbd83684ea84aa363f61d01f335f497091", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "705a95b623dc51cad228cf9b9d842f4a7825b276611fb8fb8af23d87a43a1926", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "81c256a1dbe1186288d6fbb75f14117975d83a8eced9960fb9bb5123f273c487", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "1d5ab447b4ee70f0b0871eb1327b7b905ab9f4b6f05c8206ecb6d1999d46c42f", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "eae294416a84e820536c48e7e48642ecfbe1841b6cf05537af765ec04a45e2bd", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "22d2417d4913f1deb195bb49fff3cf86a0eaa2a7aca5167ed5ee69d441aa2e91", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "53a555c2865035fa79b4b7a96f4dbeca1bf456f0be772f550cf664c58a90679f", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "a97c3ce9543da309cf792d7cbad26a1e63e4c356ce29af274a9960d570306afa", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "107787fa9e0fb3d96d1077540930268dcf229b25c824a3d779908bc30bc8bb48", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "8e14424647dcdfff4741eece1a317244fa636f71e31f9baf90993430cfa2cfc6", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "e7d52e7ae6ebee1b95bd979c45ff6fda7c4647841b747ea696c0daa68d68533b", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "05053a1222c617467b502deaf913e91e5b2cb74c092595e8c4e610edc8881ae6", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "07d1ed30c5858db30a868e9fbda3dc23cd9035cede42d1ac449c9ca342f7f388", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "7aef2c0840df5773cca82abff8f8e90b9701eefc0749a7536dec29b5ee781127", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "fb782027b4fa9ccf197d49500b69842ffab767d525cbeb1353e6e5b644baba83", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "027b800d27d3feebcf56d29e921fc16ca12ef2f1ec8f0ec20c2eaa2593362e2a", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "bffa5a7bde9e8fa1736ae72943b83ee859f05eff815c3bca53ab194aca38a266", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "22b24843702dc54a6cbd88bffcae881c6bb51ad31b521708c7df2f1b4b6f9977", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "a688947ca3a817891b616fb7d65bcacc112ca5a099b962c06be3d0ae80530a9c", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "d5f8dbe14d32a4fe7ba19bb5fa4264ebae4b8a19f9cc037cab5380fea1ad4bd7", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "39fb0e2e6ddc812b0aed2832c4baaf67181403f4323f3600386874007540f484", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "93035cd5144d85bcac263aba84ad4e26965bcd0a995f4a02c6bf8e8a4151922d", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "0ba09a4ce2b14553c00a643caaae6e06093dfacf46bcb9a2a5356324196b0bee", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "a747ddc401fe27e053d9106da9d93132d06079cd7eeca5e93d5766af90650169", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "569516195e7a1319d0b00aeffd87648cbbe00a4ddad050ab23069eb0f14e55db", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "8d4876609b6b4039343e97aca5bd9aebdbe756e4e142ed33809b8e42c0e6e9f3", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "48c47fd8a587ae752ed16c811f860331cc008d6871717d847c8d23eaee6715f0", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "7c881ee70d87c9ec91c74adb81976f7f182bcc3c47ee73c48207899caee67901", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "4329313f4fdd9d95930a4e783db7ce2a754095f7de3c75e6b39fd6594f113562", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "b0ec351a23e03dd86ad926f83503b4a69318f8217280c8ab674e3c1808770940", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "f38d558e15c19a4fe1f1a1cd6203b009e9e9236fffe0d60b3e5cc3ffa3bf0966", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "7f0a5680556b04b42f0a696e1f6029be2eb5d611cfb7ffb744dac23088ae3ee0", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "427774bad31a442b716eb823cab5bc46e20b642ea0eab38373f436bf2aa90cd8", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "b3a156676f6fc649b5fa2aecf1e1d8b75758c8f2ccd2f9b0110aadb3f96781d1", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "04c827a0586815e0aa7be1f1d09783f85df18502da21f43210916f075a330ed0", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "c3fa09415a579f67758c4c8464d0614cc28ce1b0c0c69cccc6a312636396665a", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "4d190d77aa6aa2dd53644189810d9b5ee97171cdfb5e76172b39b49da17df2c6", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "a893a84e4420b2d54d91a205e70bd5756f17897ca0ec2724d2e4be4375a87a8c", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "f3ce80e122e33690ecdf7e4fcac3be47840e0c4a8e067b78f918acd8e2c243f2", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "3681c1ae3ba5f750ca21774eab3974e46eac492f21bbe8334ac3bd21f672d16f", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "afd10e247aa12bd2e122ef9f2fbf8430a2c5aaaa8dd2fe9fc3d340841ca15c28", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "debd4582decc85e7499c06e6c4b49f7a0b52f3bd76e80ef2dfdd30eaaae98e27", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "54564f29a1d8cde0a839b2958af248dcbccf0a9c96357dc72160fb158ccf01bf", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "165ce6c303977cfb3291d744d0bd462c9ea9f11e7c7bc9f4535b7d07466b11e6", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "3e84ff15851c6730fe3dc5aa787a2bed68344f4657b213fe3c4119c56e121156", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "9296d414f6ea0772c9bebb0604d8c9d27743dfdf3634f7491b31b2a95c78965a", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "430e362767fe2eaee5085cfb318115c9c4b57e68851120ff3ee5d5c946693f3c", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "ecb016c3d93972fd56f3d59a6c5dbf101c5b5b7c0ee6b3285495f797a26111dc", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "61972bdc613c8c4fbba34b89f8c659467e5835df383a03bb7dc05ac65705cb3f", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "769f0d6cfd2d9121a43261fb9409beb6e6ec789c030aeefc1ccf6b0886c4313a", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "2889a55cc455601777a0a5ad160e1767e9f440b35bfa2a1ac294461a8b19f07e", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "649e4fab0b29b4f35158dcd8522d0c63791b9b4b3e7390d49c92224c0aa8debf", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43", "target_hash": "6b4840da91e64df0da142c0c15fc804442967a183c59f97745db74fbe3b127b0", - "section_hashes": ["0eae750fc7c6"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -6325,398 +3366,332 @@ "ar": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "301a346a1e670a052fb34cda32c6ecbddf2e90f54abf23e3f867d234ce29783c", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "114974515a145aad220d53a6df21f59548d3319d7f0a06d31d5c66d9fead83a5", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "9798c80754df695d5afe8214ce6e742b1e010acab8eb407578e1085bb8758f6e", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "b66bce3a9e0614a3c401bf6e990b4563c69b695dfd3d280397a0fce57317f2ab", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "447361d617b0696a966db093e0455b27d3ba09a3e4608302fc52dcb58455b0d6", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "1d1b8733e03bdeaa7207a1712a3ce60e3267d34fb33df9c34d7aed0a78a4489b", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "769be177dbec8dca855aac6c536a7fa3ab4a49f320cf1cf9e0d63a8efab72fcb", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "f5a1449634a46693752f18833204bce371a763fa9881fd44f134cf811ace2ca8", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "5cd06fff94266be602954931ee9395640f8bd1f8db3e5d2862ed6cf1bae99b80", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "271bc8239b9214d9d14b7ab728917262c3ef6f367b6cbc58a6128c2755784ba9", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "7a699d2d4acd68f8e5939837d4ce66385d71b6dc9fcadcaca7e5ba4b5b1c7840", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "7a03ee76cdd2ee7c555859ea5381dc3a697322c3f2a5dabeb8433de5d729092b", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "1744b856ea149fb130161326be58c8d419522577c8f4fd85510c378241f6cab7", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "7fc990b4fc33465264b18b40e0d6f59d8e1230fa4ef2a4fa0f581724e010783d", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "4980c32500518352978738044138e2fc76d056a84ae39479b5b18125d4c124c6", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "52dea6ad296a4ae99e4fe0d63c850744152672361da28ec190dfe6c6be5c6151", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "da10a632f1347e30206d925ba429815263fe5632c16d2bc6c32c7f0b75b7bfdc", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "932dc684bf7af579dcf95da90fbe13e08d701ef602983304c699e8cfaf6850dc", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "9f9da95fa5d83cf9f845d42f79fd50228e5d32acab974be625daddc6c7c04252", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "8f37c3ed7b089c66681cd1d032ecda20ee45b6b231e92eb2bcbdee4502379f82", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "b721d6684b0faa7b98aae2e7f9781b89994eaa003f4af2ad6e4047499dfe3521", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "5d2708d84145e241da99a6ec285347483459877d41e8bf6cddd71ab779f4c31c", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "23292a7a32eac0c778d27f2c40a6de3cefaa4cde3a2e34cc204ce2b0e626c83f", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "26c66e383ba7137d83601bc150e5d1dacf69aa1f1772df7a56f2819759504eaf", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "5937d5ca46f6b845fa11a736d1127908f3e3b948449f86b1c3bdfa92f5a5fc27", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "9a329ae3f556d76da74818a9b996a6916e5fb605a0a541ae396b76fc5f4118a3", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "4fc25463894b483253b4a6e67bfe73138f59526a8706ab930423bbda9d1f73bd", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "22d93f56ab96628f2d432ade0c33514158aee56aa61a2031bb401dd834193fab", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "19ab3a3f849fa60f3b5dd3f57a3947074aa6832d081961c807d88e1aab8aaa6c", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "81e1830f12b2a46324fd9e197137bfff96cce27c6aa05e1d40edec5f665a0a54", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "1569651b7d0b97611ac9a2c705ea11878b76ff065e72f41779ce5d2c00cf8830", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "3283be7bf74aa1ed9505819e1e6f1d0732127a23abca48f6cc04761e98408904", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "4fc6d9476c0fb65734eb223086a4b75622d3047a03564998f88b824daa950c79", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "dc0acf03b8c9af0038670b7f5ad4f836f3b18025d641d2c167cc98930e025475", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "62875823e684781a756a79d52d8361ba89e1b32f79c2ddf22bdac0aa9401b505", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "ffbd273fb65eee34880ef771b325dbc234730cd752ca75b072abfdd45fc9cf41", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "faaafceab992b6728951415f13216b4a9e91d6cf3a0fcac5d7b24b932b83d042", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "3f53494ba4789170cec07558f32320e66a3cc32b6dbb7383f6cbc0c9484161c5", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "828e8d57c0f60185e082c7d16b5fb02637fe12334db3ac66fb3e943a5386c2ea", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "dd7cd72f5fb9cb3f2407f658555bc5623298249b59fa8049b9105d3e50b55c1b", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "d8bd5fdadbdd4a3057c0bf2f67997722611785ff4cc05eee50133726ca082fe2", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "5001ed2f98d05635ce4ceef5fa0c86f1b12bf9c0b45793b909aaa3249b8f93f0", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "151b3214e16291cec9d542c986f6b2cc78462219e734f12120eb3e551ef16477", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "39ca233ae0430705e1977eede907a75999d8cbbbd11d7eeeec7eef6c1b73a9d6", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "53ec95c3c0aa02767083853de97e7a2943f702a2c3fa95e2e3b4c08d743114ab", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "9b636d1dd28f2a889214b68e09fdeb9be03d7c0b33e7e4e76c5e4cb03dc6099b", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "f45bf126c0d43af885602823eb944d5a4f985676a007254f2cd0578738611ca3", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "811c447efd67f6c6bffc45190524a40a0b339eead109999e3b12e02dabc13107", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "4cef88d531d6c481b7efe55517aeb10f40ce5267bcbe92ad40948a4a944e22f5", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "d81c7e96053bd8d477a3fb570f849776d1e563f152ce779c771e8491bacc641f", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "4579aea6f1b15f5691d98aac0b813ec97d3c40dfb16ad0897631575d116b7304", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "5a3262115f1f4fd977a965bd6da5b02770db24aa6483a409b20caacd6491b0b5", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "cef3769fe509dd166f7897552c6b06cd087d520e074e97e2079970f5131eafa5", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "30cfcbfeeb434135ef7071618317aa3ba78fc367fb74055bbe50b51fa0db7b71", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "e892534352a7f0a0df4b0c464993514cc623fc41a492e391a3404753aa1cd770", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "901e97c7dd7d1de7d79293c865e61f8f7854aed3f4c9b286052d6e514a96984f", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "694db7abb1f9ddb91283d5a5e0389b8997c76a8b42ce593b45141073e1870dd7", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "6af8d1588272c3f3c12d59bdbe05440a1727b6a4da0ce5b4117c627e0e625d20", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "a92651a620b01fa40fa45cc842d37ab641603585c5042a4a2899ee0b73ced57b", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "437bc5650a1d3b028d60bc0f9427334f9b2a3129953e9dc0e7fa97eb79fa77f0", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "c803a26fac56a33619e45c08a848838fbd171a6e570824325d7fe5d31ae6a731", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "c40491290243ea12a1d452cb37a07608115b2143ec8f54e538542449a24d67e0", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "55598d9621fe53548b5094270701ba338db31f443f77eb78e952c8828200effa", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "d60f94529e9a1bd1cd46e199de66ad8788f12e7fb9d29e381a115929703fbd04", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "0a1d9bc90539aef5f3b88f54561c043a4846a2f2ffca0f994487d5adb1769354", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4", "target_hash": "4a52193641b223663d2eebf8cc757ec59c787d1aea7336958590056aa2ceb1b0", - "section_hashes": ["010ee2af3ddc", "a553945446f7"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -6726,398 +3701,332 @@ "ar": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "dfff0502ce815157c60cf3340c65799e7990d829809b7d6f4c96cfe5c6d307c1", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "eb7cccbbb6319c04abd934459ea558dde4d2e154ee8331de311c68031543f958", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "abe7465c2a2013eda1bb73ed4dad59c8230a10e6aadc7dcc2eeca7527bcb2cc0", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "88d486cd9e6509a0c1063ed099e3cae306d61ba66029cc87e061887005552569", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "36dc0bda117d40dd90b60d5f5682e69821afeddc02f4f408c6628b7d491ea72b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "c53ead374378e7cffcb11c480fd9b478d000795585e7b20b4b75e0711ec6519b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "5f516d9551cd053d036dc16773c3d65f5b8027971141fc281964586cd0f75926", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "abbecf6184f3dac2a0b426026950a44ed6c4dfa5df567a449f7cc37b298382c3", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "94ea9a9e6eb7cf07beea2aef8d856211d6b6124d9d809b99d8aa1a2cbad6797b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "debc50cafc5667ca8572534d8c342be8d8bef4a2103a7187f1a25590f77feddd", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "5066608f356dbc4af05a7b51021ad449d87ecb880a3d72d2cbf568eba0e2bc18", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "7fa20fe980f0d7df36f5e7e3491df7e67128e1de5f72878883a3e1bb84304e34", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "4048ea381daea496d92a249cef46280c35d72a6bc99402e9c4e1bb2d40fa74f5", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "113e7fc397af1802e561a9250258ec72fe32f2c5d9bc978c06edb6cbf3c20534", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "0f10ae4497d44fd5bf587764c518fa251b3b3c75129804c1fdcb298ff904c9db", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "3be9b1a02d05a158c609b206cc8d5e1d6b3229312044b2a97e766f66f757e6f6", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "d40f67b70a8756d1e6b1f9930ccfff3465ffa047c46b2eef23c5d7d51f7505ff", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "383c4b30b4f8f7866c8adb4635904278e44dee7e627dc863a859dfeaf20f920d", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "a8312d98bc21debddba55d136e035d427128256a7a88a0043dba309dcdb41c07", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "50e578cbc0c0d97faf25bdc209f51b631aec68142c8abbc1a87b9779d3a7f8d9", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "5dc7eb621dbc6fae4024f53b8d32a247acd98169819c5ea0a2cdb2da5b81cddd", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "78f3a97f1149e93c22a85454166f001e69eae946aaa8a3107b6948f53575f80b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "53fe0606c20ab7e377f29d740b3fff458fae9a997b45248e9e01f3547d63d0f9", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "1160d08228907784700efa1ad07516fa0004fa7e96e197aa563bdf1ab5061455", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "0e6cdbd14391bb9195da7874a913e7f77904a5ed4c3ef7e3882c1fece4a0a912", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "63b94ae40d909edb61e41c5496b59c806c5ac979144a56762c9ea4098216f81a", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "2a3fd7a0f8f84837eefecdff273c0d7dc8c2698c28902e52d34cc023a65b4c74", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "6ce2c6227848aff4071982145963283dfdf48483050cdeee50ecbe56e30ca044", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "a73de84736d797cd557cb819cd6d600c221fe79ba37ab2f0b44025fcc7550748", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "51fbc7deca895d39979585e17204c814d9d1c3453b8d461395d4cd11d3974efc", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "3fd8cba02fc01ae180f0e384daba2342bcc99f1b127254d9eb4d8d8fd5768bff", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "73c206817550cdcdc5939e0bd8eb693de9e4732e38fae6025538d1550e998f38", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "812f864defa34e1e875d01034bd5c9184554704c8866c2ba78dd5180617e4550", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "d89282be92ec150795f7b82a0fd573ffa1f28dffb3f5955860e4ddc5561b5787", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "2561370a482e112eeefca2c38529700d664f2b55ef6baeeb838a1e8c032b9584", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "432e91f8b5a5f8261e59effbf1902914b8fc392409f030f367dc30d506402e28", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "215944c37a03b89256b14b32638be8557dc5207ba62f2aeb80cdab315f7638e6", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "6cad8c8033d9adfdedc49d556f174c3cb98e2f9154e308bc796f751a53e26a2f", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "4ba487b4304c8f05f18dd2e34bf0ae6c5bf4a021175acd80359b070cbb05bb22", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "47e8e7d17ae4619fae9e68086d05b452267f56a0755b2208ebab88b29c6c969e", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "52293c883f3d455ccb29a618010f5dc04c87c9dd68eaf6eada40fdc7f60b5e90", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "ab7b09af6e138e0e46c1006449382955ec87eb7ac7166fa54ed59a926d22f1d8", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "003daa5e00becb450ccd48342faae76d8d86881c78e2c8b7e14f2a9156923e82", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "37e25abcd2cffea490bca64597ec008c0bb215ad2a5da256c7e1df86265f3c46", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "2ec8ebb262849658faa80c1d35386430685acb372a27bf55d27bbea7ae152305", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "1e09c055368f7ceed038de0f1734b8d83761164536941e77b59bfca9e9f01718", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "b4c902c68c3e980737080de32b79c42be28cdd0aa748aed3f30b2f2dae371fea", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "2c3e333788e87e1310f55daa7c99aefb6ff6781d4a3f4ba60548191725d1ed2c", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "42236be0676866259bf6aecfa2843735967164fc1f124b49d2c9b2c16d534a2b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "a91ebd57fad92d065609ce94d8f4b73c585e11e2039985b6b786eed1b5b5410f", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "7488820251fa7dfa494b952b3800a7f1819cf1c96e30b2f263ad56ff6c6efdeb", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "2d1e1f9af504a2e6787dc8d9f7b038671b8cf2c5091803dc8513a12d3daaf384", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "c1221aed31afa20a9ec2f705d31963f0345b33b4678db468d1f4b0837c9f267c", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "87c3d270d2ce54ed8b5a29b5eae22a91a598f21d804f77eb1e43bfe653c2edb7", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "5b12a19b34ef9231c9441233f5ce39fa1159ed8af0c8d298ae7afb7119528c3b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "bcdfbbde90a8b0fdb6125b7294144293fc04b88b70da86e9f89d4eaa40a090cd", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "e67ced1f1888e26fee7fa59582698b68fcba706b276ec612ed2dd2d8e800d161", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "ff1dee82271093572f8d0e83676d3424ad22c66569812511a0234b4f0f8e25f1", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "85def269d7ae4d3e551fe7b5b97dc5acfa0625f7aa3c3cbd6ade6853986b837b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "de9550c69bda30443052fe9ab32a677c30bb1a80d7faac98776ce6418a11ab31", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "618f1ec4702293ec2a48c48ca238f813389785d386d7d7549ddb56f46c011335", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "dfa6ea166a0b518644e47c5bc80aadcd769c82dd0296492d3ec3107512e2fdbe", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "ddf4fb0629d6c3002a3130665e1d293694ac76e094050205769d953dfef7f24b", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "22c9bd1dff8abbbfc4c6f150d579b42e1e97787990c1dea3cfac9ecd182fe495", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "9a303ceacf6f1c9d0600e04a10192b5ccbc61c4010b1026320c18f7d19b398f3", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb", "target_hash": "550a048d7702aedc6f8eeefb803a54bcf9ffdf70355b546194ac49e44a7b56be", - "section_hashes": ["84275b597d7e"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -7127,1967 +4036,1002 @@ "ar": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "c9d7db1391ae2d084849d714e43187a45d71529ec88e598bd5465d5a73634b68", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "8140b08d3bd0231d28abd83da21582e2a9e553af3008acbacf70c2e307a778ba", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "3135066ce2eec2ce9a20346ab0958c52ed8d1cfc92ff428edb1801a85535f19e", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "dcd1c2c9606ca212c81589f0d3429448937f81c7d3d471d9245b642b90ac562a", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "f778fbd9af46124279dd40fc5ff90f635e3bcfa81d6cb1515c9b693398f607ab", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "746e022938e5ba4386c67daca9272e62c4c5133d2b34c5968d6057d643ffc7dc", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "1142e5f7bc425a9bffcde52961ba02bd63cf3b17b76b94843d37d91a0d904231", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "5cabe925e7e2e5d6e3a5d8eb14542c4c4410122de028ed6e13b268d8fb1b1362", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "3d9343e09de59699949006aac73ade78885d52a0d0e7920ffbe83b7383833ed9", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "50ed212b02cc2b04fd6ab1d5a8ab42d968a47dbb100dcf3f41df50819808a9e8", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "a82689c5fa8fc6f6c43f87f6ee368cf639aa7b585d9dd4e391cb1a8242039c7e", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "8922aafdaf3e25d961c721232e137d33c88b25ecccf5a7037712486fd15f3d98", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "34c3940da61f174b7227658cfae456655e2f6f5f47843fe15d8a2b45bc626046", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "cb4d2f3bdffdb7baee8073d32aa10ca8e4d1c69119315e2917e636cbc56efb9d", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "fe8e8eeee8b44e266fef07b11ce4ffa4699969d8856b6c5b109d2fa7832be1ed", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "d577faafb57bb04e3e6fb32e54c5cee65db5db72e5569f9bfcc36f76f20cdb3b", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "49e7c0150da130593b1eb497fda53536c43bb494e682e250b318cf5120a14dae", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "765422830f244f158dc2841de83c11852879dbbc654af97d530365b6c8e6cfb5", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "ce658366660a5c8afb7227dade260b721c3beac2fe84e06cc31b27677d321c5a", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "0fe9e17916fb0dd3a8018406182c222ccc9f9b1c310ecf729e43c75e9d603b83", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "c133f78c19d97f154d4659a2654bcd0a276e4b3ae3f80b481e48d437f112f003", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "979061d767d5afa2db666b8b18247a94b7b354c78d622a9475ce2155ecf1c6da", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "79b584afd370d0afe5ce311d867ac1974c3beb76bc7ca76bf72f11e2b3861ee2", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "96d856533ffb3147bb8ad2aa7769924b276a18f4e177c27ffb3aefac2f990872", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "398930ab3af95e909ece3815e3c793681a3fd9818799f15b21af77989149ea1b", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "1dde109dae0adb5c429911dc54056ae00b116328331bd29e28394c86b1e22def", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "d8d83c370c99e4113c884297803accbc60122b67bb21cff89bcc1660173b6fb3", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "bdd47a22b78169eb7610b37f5bf38d770d1514b455c1ea5009e7fb9b669879e2", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "9e91bbb357803d5e7c4cba819b1403433f9210b83517bc85085f2a4afc05942b", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "0967c480091c828686267654c3d954d2f714d499f7e01b8f49b6d1eedcd44c9e", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "f5d5f4c0717d2dbd00866c6282a36669780ce100cc71077deaeb4f9f12735d62", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "b47423ea72fe923d4d31ae5e0a9db74340697d96a708bda8acb547a36ff58544", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "2cdb18ea3b8523a525e2bd408ceec9c30610d7a72168469da00009c6051b7a65", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "12ca852d72a9965fe7f6302c3a07d85e6bfdf6bf3729a8e0372da49abccf9025", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "1b9a153c2b734cbd2fa2c4b1999d4bb5d70c7924117e8a0a0178d2b36ea16511", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "70f599562435a8a921707883f784ad8183450b4eb05c2e1f61e7d0317a1da135", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "dd3506babfb05cfca645e7593c4975660bd815765a3c64fba03b6812ed9e5066", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "7f4d43afec467149eb06150a63240c00bc1fca72efde5bbc54bc1463abef3d9d", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "bfa1bfb3622a4749cc6f911b95f6058f4d0ec5627ee98094de1060b3dbc00bee", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "99f7a00398c3dfed081bd45e3af781e1ff75e2f1f7a8bf1f7125315abb1d1336", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "2f8e18ee96ff342d37fb3041f38e04769126119bde5d8e3fed5192302ebd0560", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "1d9b28f2af17300f1a3e066165c1cb67b16902749b9ad034263dc2e6954c2694", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "a4eafef6215186fd6781b68cef16aa34097e7a794988aebba5f3adab126c1a03", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "be69300ff83b5d12eaba4fb48fe62b65c9928d67ea664388f72baaee734aded6", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "98e09e196e413d96bd4887623e23e8dd25bc8beb57ba85dfe0f664a7ec609005", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "f595819d3ee1053cf1fbc1227c2419217e2ee97c26e727759fe84548629f53fe", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "8fe0792fe7beb4f4cff27b49e1281d28ae0c15e327bc08c2a4f3ccfd3bcd1a57", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "0bacdc61aec0056f855ce68675618fff211abeb98108beee24737871a4a3cb9d", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "cbeda6a247a7874f6853913f8a56fcca08559644a572695d694c21e609a0fc1c", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "ff21f6282c7ab8e6ee7368d51880a8d09ca22c2d97b6e2b6b668c844815c54b9", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "fe6e9d228084e1d2b44b331ddd84fbd7a6e3ad112b46426bfc79d31325fbaf83", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "c0de9423ea612b6ff2d4ba669fb6f44417e1e3bc33884dfce0b454b2c70ba6d6", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "50fcc7037eeb5762e489095092e7710b7ac18de7968a33d3fb52470664e40350", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "7b2d2e0c3028c4188382dfabcbf272d6cb9782f827509d618b94857d6a887b49", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "12fb17a8be83a03788fe78cc8eff72c9e99140d319bef0727cfdb3f4f32dfc42", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "91d575f5ac73e55205d416e8fc09661c840038cfe51df360f4a4b0d4b650b51e", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "ddf75964cb9e37782492c8b40a3f25b07a646db277f42906955992ea7162b131", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "78334cf80c0718256aa15378b0b354dbc882a561324eb1e5e04dcd5df7d27b10", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "cbcb443cc86d3d6e6eeaa460b366275d75a0749bf41c4cdeaf9307147fb441a7", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "431cde7dcb42dc8ebb006689bec5acf383bc1c11b9beaa2fd4e44b67e6b5eea5", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "f8598ecb621760ac5c226d525ddcb114dcf8b885ff56b6d406bc703a9c2fd7a1", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "6100993fcaa6b454955b4c7fb53c877c07655763f3563adb4005fe8bde28dd73", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "e86f57310f0bdb4867233dd39961c3307337457f50e8cfde62e990503a780457", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "14155affc597fa29082eaaada8cce6acbda70596b1ab91dc6a64069f2a8bf32e", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "a3e1982cba4324c7397b6efa0a2dea7c16461765c94f339734782c40dd45786b", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "4e2bb669321a2c6dfba9dc165c00ca185be7e051d56d3119d9c65a869538ae3d", "target_hash": "2ae83f023e9c559761e0f7a4d8d2ef7a941cdea53ead269c604ecfe719ae16a0", - "section_hashes": [ - "0278c9c4729e", - "d43399469ef8", - "e168d0b246e9", - "49639d1a14f0", - "841643436d79", - "8829f8f7a790", - "09be3040ba70", - "b47dcd78c657", - "5aed93edc799", - "0321c6f612e2", - "15e9a542ec1e", - "56d0c07efd61" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/architecture/ARCHITECTURE.md": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "locales": { "ar": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "28a25267ee50c850c1fe9cd4171c83628a5be964fb639045760137690e3d0917", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "a3d11fa560af7af96a8a7961d06e559e88729b7c3aa6421e792537550927c1c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "0c5e0e4e1a32f250b7742a36bc14f9da2ee8c3a043871caa199fee19d1c5a274", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "fef8cf8fb2d311ad2892f8f418d05cca82d9460943bb78eb16eb9e08bbf83bc9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "019e6fe80ca6617a4b3c46047487a6206811b78b349b70dc371eb6fde03e2158", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "e336370bd5a69d88f401e2198a0fa41706e1338d2218c922e8a03f9fdb5e625a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "a21da4ed26ae403fb1db318bd4e2f35b4eded7c3e8c2677df02687e4b427809f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "e341b95f7df054b06c5163f3537f1d749d2773d19a3dac0e7aaa8d0f8e55f238", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "03e004560fc7b28663535784fd9640bb095bfe7b51dafb877481f05c0a283adb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "1c57ac3a3a757028ad723769e259712475d4313d3c9201021e4ddb97d4347db1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "eed98d719b634dce248289e18625298ce6b8119a9b0acfe12085b1c425dc0b2b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "7ef1377edb90142509482650053216f692ef2486181efa6250035a02fdea9860", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "198ca71989665b8fd7671fd3aadcfcfce1dbc7ce6bff51a49decef3ee196d401", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "94c9b2397a599426a01ca9092a60b1251fb29813ae69ac0909e3293100051634", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "6019eb161852e7a2d39208a3797c48e9f72b1ed891f34323b477265656446284", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "8a91e194529c78a8aa83d59203dbd7749f81d40049060e9dd86e4a3462ea11ab", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "c724765e374483fcd4b8a0ea6841000759ca2cecbb1c12e438247139417e1808", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "49be489bbbc7857f78bc802137863050332d930a797fa326526b5541cdc17e5e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "c54f8a8523c5d8ab761ea69b2985e16fd035fad15528f59c0cf5693f0ffaecde", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "522ed9628fa881c5416ad313b50304233b2a8b6922e09fa59f5d356cf7c6ecfa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "f06c735ea863616d0cb0ac782a5d8fa8fdbda94cd3abbef7c43644559677eb87", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "c09eaf8d337b542c6ab71e764badbbd25695a0c903d6bc99398ef399c367d5a3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "1eb303f31eb517193ca61cdc3e11049d164e9676193d6a6cae0cf157c23206f4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "90909f4b74e47c9359079df5d382acc467a8b37158e42f37a99c23e4b7ff596c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "ac40192346248c48b82c9b67b8e4a4cfbadb06df7d0aa8846bbeae8049e1eca4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "681dd81aca7b3fd9dd0f4e735cd79080f519fe182072be2308faca7ce7c6d392", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "94b72e74856dd1dde435144fd18ec5b9ba04445ca6f2e3de7c822c19a04c99af", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "cc01979c9629bd2ddc4f14e3400414f488180318a61269d2745721cfccc15d9d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "b5956a873313a61717b3147aa9c5ef7f862283734803f5a7ba29ca7e86c236ce", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "41d0c73837353b049bf43a8eb2149aa1a5b5aebfe0444132bb95d187808c86d4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "10c009f7f645ae696bc5eacc6ae28641ebc50f33b29f7809c15748b604e9daa7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "38d004f4193aad3ccaef582cf072a6b95a1d0207f6a808acaa22373005c2f5e4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "ad458f4c3fce109b968548229c96a4d06e18b6412713c1aad738601a16ea609f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "d8a831d70274e00063982dbdec55d4527d847add0726bce6daaa2ec5332ef0ed", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "ede50d8fa60ac425f05aa4f871852a10beeda6d321dda8788e0dc9db24769415", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "19f64cb15ae7271a39a28e83cfe4d0c4326d4f87c875a4d68ddb98b3965d93ce", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "29ec8f5a8d76db4cb539f660d0654c6c467e1de59236358e090135222e9b8144", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "fa9e67d02b9f1597197afc69d135e66af15e05db3cc5706d924f1cd35ff53123", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "f04ed116eda0bb7aa5996c13912183d00ee98709c590fb6885f611933abdf222", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "6adca4f386093e2de5866b33cba220120c91029d96160c7966eb1b0734fc4ba9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "0d53cf0ad50d27541aa6c70f716c9bf5332020f28ee982251655da2897d6eca8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "cb7d1c2cce57b70d2db874d6d55148176094974db800f4900dc13b304cc5246e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "575477ea9f6b9140b313b12f303685579ffe9e43f187cde063549b1aa7958103", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "59eab7f5fd72bea77091de6e21bb6c92a195447a562e64f818830d11ca6f1c42", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "68552abfe7f2306cfd07c561d26b27cb9eaa4fcdcc0d7f0447c7ef033e6bec73", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "1348ff87d3c60546469bc613dd28e2193c846f8bec59379bdeba0c0df1308d3c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "360686f6a8d4291b6d49f3b5476480ff3c4fd53137afbbcc0d841abcf72679e8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "7a2590eae0b133356e5d73aec453aed38067bd6c5391b4526dfdda28940ab162", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "42a8d506e749fa7346746bdc796dbfed770fc226b8d1a5e6ad930b0bed2ffc63", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "07ac2557fffd9d0c4f08a23528f84e45b43c5a6888cdd3153b0b6965f8de9dfb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "6315348a57caaf6e730a0385cae4d44df197a1513f0d5aab2d37229644650a87", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "2ac74a97c0a83f46ee00bf203d5ebea1cc0ebc6b8fd21b09e41b4f16a8908c1c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "be1ff5a2c5b5b069ae8dbc9bb92f42fc1ce9b35eac845da1d5f893dc029630af", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "5af53ebfd13b9bf8c5b1298b43fcd3ec3da3077818d8acc9e10a271c55de0d5a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "0a205fd31d9c0127db6be355f7c3bd2da10b7d3040b2eb29eecd138a1a817241", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "3e2a8e01703eabdd4fda1cc8fe823a41eacadfc367dfc9d674c038ae3c49cef0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "69186eae8c5f6f81957071634a5c619fdd2d377977f62805f6c21250f5a4c767", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "fdd8e30dd92a96f4cdec88f1f4c3f73e5e9bf2b44ab25d1c84ee997eedf5e77f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "7b9defd2fc421f82a41c552e3e374602bf20263f14892f6451b583952d1d4ed1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "bca2412c29cc165f9d6ebd0a2118971f8ee545105d2acdfbf1679abb1bf6a40d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "04f80ad61c7bf0f047bbc5ad571734f0b870f3301e2ce26de342da74f09ab091", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "7b3f686c0b8a5ace062b1ccc2de1398a6a2dd462bd6e4707025109f1bd1d2fb2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "cab7ee53becc7d4ee9ecf7f07a7ba5836a6b86cbbc6ff74aa5dfebf796ec5373", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "e59c38e4bd8381e28c336d50801982fcf70c7895805c7739e19e62c154f79e37", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "60d34b1ecf17e89dbdff2e8d5921256aeed9e4306991ad3ad33ca3a44aaa2cbd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "f74f1f2897a2573c7f28d5db437eef478a5504c88b2bfdd660e304b78c593603", + "source_hash": "a366aeb553b433e80e264ef564ccb8578447661b90a582853bc546126c01b595", "target_hash": "78c5f919682e5aaedbbbc9e8463a32c7adcc96f8db41f8f44a01884c95d1313e", - "section_hashes": [ - "0d13968e0788", - "debbc9b592be", - "864a5ef2d51a", - "012373a7ffc8", - "5f1a2a600387", - "7f37a5827ab7", - "d164ae35dfd9", - "183c10de0de6", - "b2088767138b", - "55505c423296", - "401a67d9b674", - "7c7d9302a097", - "129cc43d88bc", - "5f11adf08c54", - "dee1b0747af9", - "6138d6feec6d", - "ecab0118c8ed", - "64572fe494b5", - "b53156a27589", - "a44a4f150a92", - "0eff7af7f870", - "bf0bc6ab760c", - "07e56c20a8d9", - "63f6ef8fa6e7", - "d8ca1c6102ba", - "5d63da78192a", - "842305909546", - "b70e58f3ac90", - "31ee5cd9cda0", - "1b47cc61c251", - "593523acdfa0", - "066067c7fed2", - "f2808ceab5d1", - "4b3c89a8d34d", - "322b8210992c", - "8cd504899526", - "0c17ad29a3e2", - "e9a2c22593a2", - "f5e0bf887df9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/architecture/AUTHZ_GUIDE.md": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "locales": { "pl": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "4df3289c20c64467e1ba93a588adbb856f93beb399270eab23fa2a1007878c21", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "dc76f91a7539dcd1c9ade932f2dca8dc3e1c971999107c15c033f0e898b283f6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "b4c9b3bfcd6590f5b17e43abf9bbaef310eaba3c0526dd1779724c2d7092796a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "aff5be513d7c21faa8c5cbde7a32211aa59b6902eab25ed219c234ee3f8e3b1d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "7c77b34f327ce254b2d317be331c43b281354fb3305388ed0c1ef211c9e96384", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "7b5eecc60b8714366f9e380c01517af581a1a5ff00ce79b21bc8c7d66b327b35", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "34cef20c58fcc49f12defa6a109227b4c845ded159339efa7e33ed0dfb6f9859", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "251dd878bd31610900b2e21680422d5eaf61d7f0021dac54681a88d73f2b0bfb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "2cd0e0b00ebb3353db32afc48615f1a2e7a6848d4fcde71344ecf3816f51b25d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "a23430536bef6ad00811f36ca6bd860a7d4c4c3579cbc74b41f849dbf8e7380a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "1836e699aefa5c54ca24bc12404fdb04305c975ad811a36db38e497c509c09b4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "2a007949c73a2f013987ab1cc00fa5fd7bb6dd81ab14dab47d249db0e812b607", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "7787747e7d17854ed14aa0728168eaf7411eaed38908d691b4685be4da25882a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "b56085494eb857c32e3a92c1bb5f7cf42d44ac72c705d1d5b4a6e98a2d79a32f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "4f01e546d27c6aaa93d59a9d8231529e79f56b31fd7f7ea250ca9ecc0b6ef611", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "1a0992a13f6156affe7493a539dfd99224bf5e5b2e9b995a6881edda6a917c18", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "104fe7e8655f0100def3dc159bf5360c58e6b164c314e957055ae95db99b9014", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "dd70deb3bf4c01657d7547daa681bc5a6e8007a45d99771abd8b07f690702e8c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "f2cff698dc73d3ba7d3cb43df8d1fc0c9eed70d1a738d41f94367ba57d5968f5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "89705e2a509d386cefa6c0b22869f215c2a883ffbeb386fb428d31065061fc32", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "714d46fc5c5341797c8864719d194d65d51cc1f2598277479a9ce1b1545a7aaa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "95eb0981d922cc1d62cefa950dea6ff47438fe8340f6352f28b1d56422b0f760", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "43dbc415dbfc2ad295e68379385dfa0cf8394a5b176cdff6ceb90db8509f3df3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "3c0ba303b53f7b69d85e705bec0567e56e8fd1ccb922f1bf2454f86e36af971b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "30fa193f553d509f63d70cc2c0757418ecd0522e54094c28ce239119e2812832", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "1d971eac67b46d5e85ab99401700fef48ec04ba057bfb3efd4461545b4a9976a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "5b654c2dd6e906fd2f56e0165e49e251597ea944fb759dab4b85ec8e02d37e33", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "2d2123483e32f03bea59dbeffb9b60438c74fd5c0a0570119d1c17e6d807bf96", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "64db4f40d13ef1fd9f9641de362b1aa4998da76fd0fbd2cb205c5795e66e00f6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "9090dc6377e35a52b39e28728a7e5244751da2a59f70ea9028b7557331719478", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "96ccb7f294032a83a6b630ad1af4e7887e011f4fe43c8787d8f6c52ae6c857fb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "7d4bef85a3ba36142389d56b270929306d14361176f3513484414b74d978c5db", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "448e4d48a011eadd9bb92518be51f9f7c7a109e0029736aa66f7c44030b8f130", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "c9e7cc42ccce02d8a4cb4b4a66fd7cdacd143ceda55438121d4fcf6b2c2eba8e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "7c76434ac6430a65bd25896025afb35cd2139d2d3c400edafac3074f043da941", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "1ad7f2435eb8c940b2c0e5204ba6e50551b213f9cd3f7f93da8a571be6e6d495", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "c96fea490112efe68010e98d309cee79ee941f6071976d0ff31865af5ee746f1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "36840f51774d5cd4b2929617ce214142001c472883e0cfb45eaade954951b71f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "05cc9e29136999898d9dca0302b9239a8f9444c7253e1b35fd5322f9fd0e63b1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "e2f9165f97c116a0a2da5cd87b7ef57c4ff80576fd2fc046f1e416280fadac2b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "8bf3f857fe2abf78a46bd38e21eeffb18edb3d123219ec57a0e3e830a6c5890a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "6d4a3a01a10bb5000511655333e1d8d101ddf48801cc60505c260c1e8797b6d8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "7099047de43fc330d5bf163ff45155437f75202e6ef2a433922592412810fdec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "89c0a4de053f9961561f456f4c146b7e985a154d98de070976a910c620d9e49f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "6beb72b94f420a8cb67d68245707f0ed67d391e44bde524df9861463eed53fa3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "1d453617e1a506d9ad09764b6942700e0e5e4302a3dfed5aa08ba5715a296267", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "4f31ff147682c1ab7dc615930253b6c8b6a974b387840fd7e95ce19d9bf9884d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "37328322640f45e856174526db72aa4bb9a1f17d392f910f04c1069d3cb20443", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "5f60be52c06e6258a79dd3dfa8d95e913939f90799d323e0718056a1e7dda677", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "7ee1a04a8d0703f3078f299197805cd88c0c9c009946848ebe26c99cd62b3eb0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "8c80a39320c63e0ca63d85e1239626b47394fb9c1f363e02b14b5de141826aa3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "0f0e9e1feca80b1f45f334cdef97c637c713b8f6172cd14c3a70f17aa526142d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "9f5aeda4bb3a522030aedbdfbeaa3c7066a9a1c98c7c29db9e9ba73ab1797f18", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "e6c32f88d25a9ff7fcaa3c3646b763fcdd5147cdeed243db9cec62bcd094471b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "0def0cc02e432502efda52762482dd48e8f228d6c648ba9b05df85e44872f091", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "5ee0e49e7f9562b397d2150b3731f006fd2c132ca4713833c1ce1cae198219d3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "d70ee52cf8f6acac3a32c30e82b2881ec046a4ee7991b65f2ef0de3d2f0f958b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "dadc878fa1784e5a0f5f36a49681d285f5677e03a13131b867420df105daead1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "30eb917652f2449ff09e7a3f953e7f97b27a1a8653d055e5a9a067d8de186caf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "571c35f752d4d506e7197cf21b2705b77caa24562023551d0e4f576a9452fd65", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "d164f4e56aedd3ae30228f908c08a402b0f47f6945bb2b52ab885acc62d33d50", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "6d04495a97c0804f87c33b9c5e4c21b761b98b9445ae811c17acd3bb1f679ce5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "e9417964670d2f2445749bd564efddbd869f610fe6f43b3aa95da45d79a48ace", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "89633ae302569fc5f959285a5bdd7346b7528cf2aa413fcd0ae5702e2040230a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "f7c36b640b26880e83314921f91f95dee10433406f0e0cf286d75da7e4b440f8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "4cae38d64a892c3e918c1885d587efc65b1c9ffbc4a67c46dc6da63c68c29b01", + "source_hash": "8150de96407c7a9f665aa75c3309e0c8df3301bcf67c0406acd36b6d27f82112", "target_hash": "1ed304523631c8d8bd83b633e1c3b13d0bbf56d589a2cc1561c6ad27a4a47585", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -9097,1372 +5041,332 @@ "ar": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "aae7959acfbdc0ac058ee21ddeeb74648c1ecc89d37790d0f392293efb8bb73d", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "28de6a75651b152571309aa96a704553c594c9d1e372ea23bcad832003023195", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "7fdbfc17456b73de2411f07ae8b69bd1728744e3eef73ad896baa4940271c4bd", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "26efb2aeed16de0a905200c680c16f0c2c319c608e9866a1f80a726dd2b779a3", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "222a4ab256c93ea1cf7b1edff97d60fead1b5aba858f47b3551bd0d025fe3542", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "b2b7b337393a179193de5d1aaa99cf78dbfc989b6d288de227cb90f843ea59da", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "2b42872aa39ecb724054d27dbd6b630fafbf523253bb99d68dec7ccdab9dc967", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "8e0fcdb2631879c68fb03fe27f97e7b0288e6e67383f0c9d0aa469efe6f61ba4", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "99a26a1751e41ebcf946235727ad3f3bfac09319478b309731457ea3a0ca3547", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "35c2ac3932906adcd44ccca39fd13a49f919957c7292089d5e7a3711e64406cc", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "37199652c6bc0b2c0572bc3ef14ffcf2f474bd237d5dc0ebb61cfd056a8568b2", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "d5de3cfd91ddea392f31aa7d3d7f54a7c9ebba6bd6bc6b74c20f2f96c8d107d7", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "36e6663106d2e1e0891cae5fddb684bdf058284f9076bfb668bcfba62fc5bec3", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "3f2da880de8dfc74dd2193ed2b1ba06dff2dcf6ec137011b2a2c7b9099891672", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "a6307ac2da29b73bb4d0e27baf2878aee9b386239edab98d984dd2799f4d0105", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "143aa99090b2f409fd0477c58604f174983b00ed6c6fda88525c31c8a44b3d89", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "e94bb5e629adb6864222336e2effebf686903aa90276f16c927b45109877b0cf", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "c9e2537b237a6b440b2898daf05e9871d68fb617a5f01430b99aac9271c9f159", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "7f12e91476a5dc565e4b861944c3268dd56fa8952c7dc309e6f824a35ca02242", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "cb148fa84bfb53e7634328c4addd3a9bebc418d26fa18322404eb3de858dc3c0", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "8bd14e4ec1e14366d51e92c3375808487f488ad26d73b5073e6ab85a0cdeddd9", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "2412347fda10f98ce8a6755b043c6d8576e26421eed614cd0baf354c08067e58", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "7aac2cbd2f8d2f26ebbef009458ac0ee297a5d2962f72ebb982bb6490eac2f05", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "777909b987e44deb6fc00395360b368841c5e7e0eb73dddb1b1ec9346789d881", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "f88709818177a81f1e0cee3e7dba8f6c3419a45e54cff6a1a2675a1a3a799e46", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "9f61b3c2e999c2150fbe6d405060a488ad5d9aa632b8787f9ffd7c050ee4aa5a", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "1380db9d9f690102a7c6b4ebfadfb00393273b3d9c9ab12bdaac85c1713973e0", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "a6c97d98daefcc134e9961be38ae9eb9a98ee222e528129a474f80ee2218aff7", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "3678e5ef66487d94f50155ff911f7cba4b3b1c4227bc157ef38ab71546fad01f", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "90b610a5b1a7ae2139a7c695560aee253dbff27004c6c60258996a53fe1bcedc", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "ff65e868103e44b38e47f9cc9a9cde8b09fe4dd22ad42ed86a31a76939a9df8e", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "b7258eba91cbfe73204182d725b237c11fe2990d36fa37326745eebcb0fccd51", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "2537c879bc0873f7a38be44f886944b44c47b5fd184d9411bcbdcf22c60fddd6", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "5f5ae11b2238917912766d9018a200638d0eba6e9dce79bc6a5b0e2ba3e447e0", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "a5f275506f0fcacf3e0653b2b644e9ff633615e7551e8b4e07e8a00412fa1bfa", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "2a67f32d95d67749f9e93bb2b99ad1305e0ff9c8f2588a5a34d56283618e0c01", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "5d4784b9e216e7323063a7e40fc336105b65161417caf7c7606f803f57c5e525", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "08d4bdec8dd2e6858cd02859641807c8c95b247e119c4219bad1c29885ae528a", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "4b2932509ff1410b336e7deb7d3c591356cf2c75019ecbeaa44b9a02f0b7e18f", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "fd89c4469451b4c9e12711bc17586eb8d8cbd9a00a66f9333acd62358443cb01", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "d0c2b43275aa1737fb1018091d22480fc670a836d721ccb8a3a21f7e876df496", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "d0b12bf5493cee87c01e1507a2994c7fc258fc04131f23cad2c764fafa9f20f7", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "e477997b5cc66f49d649457af6e2fe2c2e2f48e20ca5d601a4f4a94b6ed11d74", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "00627592095550ac452495d1a1e458bbb8074fb6cce97feae4d00db58a775058", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "305e3987d482c6c94a2753615fe777de0106c61985c9262a33d2021023d5db5e", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "869fab9afa9ba0a2ddf5d4939737381a44046bd32b16b780289be9e47e5e7371", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "338d7e1f9297b1d4f3dd4b495ea6eead5071c7a312667ac69364866d231ac3ef", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "79fbf4f13d614318ffcd5234fc3878c5bd0b39d9c77a4cdf3cb9d8c3eee821ae", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "789e52383c8a9cc104e955330029165bb41db5ecc7c9b179f5b6136e69ab3c6e", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "60ca490a3d619ad415c33dd66738e4c0ce3803eb74a276553ac0d30deadfc3fc", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "3499bbcee818c07236162b38cefe69ebb8161938eb5e456e9f7ff6bf571a477f", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "d54e47c0e440d247752857de2c91ca4b8fb17b572c4625bd9fac22939834698e", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "b3e70e946f4ec797d8889bb593327613de6ee6b8a45195a1ad21d3df262d1dc3", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "d4016b24c692beec66e48e4f791a9d1b7fbe9a6cdf9f6c121d6f2fc884670dd9", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "be0c6d8da257f1d4c645e49701c6be508871098d2b32cf40889673a974927b2e", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "9991bdf242fb00b0bce6d0f454046f02eddab0ae1a5cde6578140ef46d52f8bb", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "24e2b72fd06ec74314cc6851d47947ec6cbd8626bc162946a23912e87f399309", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "c16da94c5c78a7481d023ad7a06100dcc7be8c721c8b8ace4d6e9571e6ce4f78", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "c58cd7cc4dc95ec1076939bce04eee10f8ae66ec0821939bfd0878eaf86b6d3d", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "a7eba8fe22843f4285fdecc6e8d38c896db0732d73e74241e7d547e5c4f15218", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "d7f98dcb44981bad6984a6846b522499a66143281b17b32bd3b764c9be7e2303", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "91db511165f8395440f638db103939908e75fa602247fe5daf07fe131160df9d", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "0d0e336d1bedc32d05ebb956a100a7fef0b01cdda57c79b7d97dcf89b0b5bc4b", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "62d4adbf09d45172229f42d5f71bd2712a88653a6c53f0880cb6192f67399cbc", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "24653c47f64beb91e5110dbe35a00cfa51b3543cccbc12cd05ca6d22d57bf88d", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "974aac2e0936294e56987cb3145752e1caa19d10c743dfb99d4253ce48a5c2ef", "target_hash": "c98bcfba2845f2039272cf968c737a603e107368b288aa24e78db01ee1389d15", - "section_hashes": [ - "26c153cd9cc0", - "aa3929d4fa86", - "aa62c8ec1b70", - "64ca5a75ab96", - "2b304873282b", - "a1e2d1692282", - "b7b95d891013", - "152950966765", - "6d3ad7b1e74e", - "b7b58e0dc709", - "aaf912c51c6f", - "403c9343852d", - "bdadf4401c7c", - "f231837e6c99" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -10472,1177 +5376,332 @@ "pl": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "9dc80c4a3bbd7eda751dea3ebbff069ba57de54decbd8600146eed9989e61464", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "93a348e06697a4a3691bcf7636dcbeed964b2146825fd1203e7fc9de4e5fed73", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "67d5c680e8345fa99d92c93af24115bfd6c6cf30dfc249d764711353efd44bb0", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "2d81731e25c20a5f562e702b43ebae29d6106ad0928549ff9406776dd7378079", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "3e21ecd47a99b055b16f65382138b418df6c92fc6676f49cca4abbbb31b9dc9d", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "ad428cdaf607924d7c63c479226d7ee2e777fa9f1ff283ef5a66abc1c3e26d51", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "c45e32fb4270c753d016bb9170469ff91f098a0469223c1ef5c33b857773d83a", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "231bfea771af2b218405564bb8df8cda5baddbc5ae0c2ab8411a1be9ccb417d0", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "fc263c58523ecfe3265890627a9fe0099d583ce000fdd7cf015e6437f6f953c7", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "737d037cfb1f07ab243544e95c78de0e360646546dffb181f4bad36c16e7dd97", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "1260332f8b111657a41c7a66ad09a500211b2559d4c794ffadae7748999be393", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "5ac27261d6af0d541c2940b9fbf0b5fe5486e01e050530f5fd57d23e540ee966", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "291556f7c96c488a5757e8593665b69ccfc861a9584a47a9f7545ce00a98ffcc", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "d1abe1cf5cbabff067395562dd2298b13e48452789aec4f4c06d8720230dc978", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "6f925e61541ef2226aeadf61fd879323876ac2c0b118aecb6c2947c14d32c6b0", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "33c57a9d731571640b0acd9a8881ff87d93cd1b2a4966548884e399f313801b2", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "758c1a01aead92b3375bf0391e70f90a4fa999c2c9c1a66630e6bb15fb0965d1", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "438254906b181cfb513274fc0c8c1bf1b282d71bca9d85ce04ab93b3e57ab155", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "b4ced4d69748a388079159149742a2ace6f4ad61069a4684bc6472a7222d61f8", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "e972c4c140fea63cb3efc00642863095e70e180f0a0557d0245a83bda497a776", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "dd9c2d7f2f2d5b588bc9e3cbfd149f6aa515dfbb5964de5886b376911aff0e2e", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "490c11872b35c3b125b5dfc496e9df1b83bd712e3a1309912142035015583e2d", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "e95ae38824e48714b15feb004c13a9ee78280c463b3b8afab90fd746e70ea572", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "11fcdec99a1a0e0f7debf205cf0399c2fb983a928bde8066f7817c0f71dfb214", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "d2293ce7240e8c58f50d9a7f1b3bb15b159b3261689196438e936635ca162c4e", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "d78080d35f493c0960a30c959ad07205cdfe2519b3911f0eb629a69ee1e2e284", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "668eaeddf54d99fe3b2fea3a02c4a2153a42503291f7b0f9abfcaede3c99ba27", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "e0f1d6d801a6f1af85da773b6236f9a78006e07a9c7050924d0f118012a412f6", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "d73b94a766213614cb217e35d7b59aeb5a71ca9b3d05f41edfba326287cbea5d", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "7d43d523562bc679633b0218b0eb17ee23cc59893814568dfd9b22e461f66151", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "3d0fabcc8c84a88f6ae18356eaa3082b432956ec9acedc157e80f206501634c2", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "98d097ba5141ae29cc7df56a404ed9fa305330683efcd7f6a317cef62a7901d9", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "9c5aad0a2dd4f945a1e36e36201dab02d7860628dadb843d027deb65c4fd14ff", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "b90811234889c8ada445d68178b7da9d5f254c47ecc596cb71fdde84813a7fcc", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "b2cb115554d700a639f02aad06c9ddb8590947fc4711884dfdaff9f14b3d8c1c", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "6658e4e18425eb94bc167505a33f5acbe457d2a2b127133bdb6425d704bcccb5", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "68beab459feae10b73de1b4d732a2e43b9438ed7b794c6fd7125772ef7f055c4", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "113e2f22fba8b2bddfae98f183133fd28f6fc81d5c4f97526f2f8ec47acf3dbb", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "0a375b01cae3a113bfa31853b45eb0f319986a7db529e7f11af6ec10a09fd52a", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "d4194e6f800d989db9275d36e924865cb1521de1818eda4e0cbdc8be677c5b9a", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "30bd13bdc568f8ec446d08f18b26400de9da5019b960f732840510db9e0b03b3", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "c1449ae212c709286f7d760bc3bc084fbaf98bc23433b77fd5c49fd8bf357cbb", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "e4a7fad57c67ab7d47b0c0fcc2e9e57e51cdf4642bd12f974ed8c52c196c43d9", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "2029ff0b972a3a574c47b8ed37e13423910d7c2939baa3c4409128f54b871c9b", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "26e0c8203066b522dfee50072836e0958c15e400d086ec0efd56111bfa6d4c80", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "c6dc2a6cab3beb3ac5d409d506a79c68cc64a669604df94e10dbefda508b733f", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "8dd58ae67b446aa6337eef9e8f8c4e0ee379d2a90dc30e594adb861e1b8f9e02", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "8e4b831af997185dfc1993425707df543da3b917a0b475ebbeae484561a3aef3", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "dda6cd33769203da245b3fcd2b561bc219950262949c7c9be13e9db3f476b2eb", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "111dd5f83921c0e5a7764ebbc1145dd32f42e38e9954cc24dc02a2bc8ab6bcc7", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "2862eed9fd30c978da94cd741f4a25eee99686686d58feef3c8727779c5cfadc", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "1932a99bd0f8bd6724a4cd98767b1059a3b3d01ea5ec410691a3544c5a2154ee", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "19bc37909a52f451ecbca9c7200ee255b86810416b34853a8dd0b5feb9e09b29", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "a132b8752f117f39d2cb60497421009f65d8f4fee0a53205ddc314a93d6fb74c", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "2f3874a6704058601e01597110a9d12d0deae437181f305fc4b811e122955084", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "95fac549fe92c8f871491ed6f877bee258b75ea74e3574e6a6f201a049555ce3", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "0c22f1a5b630996efbf59a8faa03180b953da5cc87e2b3473c2a581e7d454a4c", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "c7dc864a6d7a80cc15dc6e724679d254e95c94efd421ad0cf73fef048369d30f", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "6d6778c7924c89c7fa3cfefa6f6d78a44ea32b5c80cbde3e8965b5d698ab764a", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "2ed9d6182bbadf8183b1d099e7373774215953570b5aeb6ce9825f21c56f815b", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "000616ba72e2a6725eab7d7802a012d4417770716d1a3d6610b5651f773fb141", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "9c40efd6f7a25465f2e4fa3f5416d68aa31ba070556a2f4ecf7e552bdf5b5ff2", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "690bdb6a51e9bd5bd21e02df068b1f99797ee2aa439edaea00b1af30503efac7", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "eebc322205c3e6a7191885f4d1047b51d998a974d8acfcf0c918dcf0e231a0c2", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "173fa034cec1771eeb0b9820e6a7675b114750a0f7b28b5730625142b3f5e686", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "911b4488c619a45208d8d8882243837de750b5f2da1eaf4580411f66bad2c90a", "target_hash": "024c28f1331bf66fdb447e438c0a47609d715868c178390aa03af7f096fd7372", - "section_hashes": [ - "ddd46ad0457f", - "c5470acfdcd8", - "71216b89f048", - "f0c7ef465dc2", - "cb647bc53ad8", - "e65647f0bd84", - "a86417e0b609", - "d6ac50febf52", - "a9797fb22833", - "cc0674b08670", - "d956dad6ebe1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -11652,2185 +5711,667 @@ "pl": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "0840a7762646cea61200cd25532889e48b8d987da874dd37f23f5f9effa8bfc6", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "b076d0d811493d139486aa3a847aa94e2fc40fc27f3d42209c4b6dc91e13ab23", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "fb75aceae8997f09bc6797eb1a398f99c27dcc7a05f4fa1401e92df14e5c5788", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "57da9e8b5329b23a8a47235317fa7a725a92733ac66e9713ec2dd41f5c172ef4", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "e275c745eb88e749185830011aac8979757ef0df29ecfc4c2475cc36e8fc40d1", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "473183ed1e192d145f48015fd3bd9c4ee9ba8e69024da5cf994a7e6e32278815", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "9e1f3d02a0e0c0753753837b8f8c75ed5b6becf6fdbf277365d5667dac408979", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "ee4edcf424c5d22d898cff146643b44467aa3e836a6db11f09e6474ea7d099d9", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "24c2c4b0e425d68b826ad6c9a07a108080d5e1f36f82a79ff5cb4d604407ff63", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "f151e95f11b506cb71e78790ec63359f7131539710dc682093e323fa8ce5dac0", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "c00b30a13a6e23179864f82108baaf3bb155e38191e1965264c8dd8bd3a6902c", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "e781c14c509a45e70d39ef787595e3f0a8215a3a59077de40dfa8573fc2cfccc", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "18a03b2153a3ad05bb05cd5005a7f28395a8a4c77da23fc2ff44b936d7e4dbaf", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "82918b67f3a0a88d7ac534491f0c158d469e39ec89b6032eabeaa761265b9897", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "089898b5a1b019c98bbf6cc484badb31ac304c79b7d966c6fb3e8e6a8b6caff6", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "781194e18e9b48a39d91e9c6c1a7ad5e1b0ef5c3741be4f9d55429bdb763437d", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "447802e8732c5465421f1a80358fd892c97b55c447c2e7c07ce55aee38ac5dde", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "90aa468cc53857c52d7bf5163a7352c62bde28052369d9cbdbe0012dd9b4cc41", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "854fc3737fe8a9494f8578b86d37b9defe57e6c674244ff4a74714a99a8672ac", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "54f10a9bd51afee2cecaba3dbe5808f4d715365984b26697414ba34e3c566214", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "8ece32b6ed7ee3a2c48cb8288121afd6cad8ff9f8b499a5519006aa01f673d26", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "fbbb5d2e337e4844ce2aa8c89587742c61214fd35c8f8b1ba1a4ec442c4bd5f8", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "fcb87d31fef60633490369d87da1c5feab9fd0b8057a3283625b5ac3c61a5278", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "a186631d7fb7df69eb85f29b7b363eccf9277bba5fd244cd2310f153f50a0273", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "1683b40f6d86d89ea52ace8d1e35283091cab824207990621b742d145b6ab65d", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "3579b62de502c96bb61423755ef3f017e93a85a934f36c840a87f133a43af867", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "862c4e1297e8029634d18fd4ebad5d5205c35b6d4f8369b1a987e3e4d0827977", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "a6f18482862bbd0633f181235a554e96e4e9693a58e73205c8d22d311d904aea", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "0a15b1b4201ccb2aa023ef19e6d1bda200029f5089886f3150a300c744752757", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "d00ac93328c2c76c4db3c8dbf608630f5d07fc18a633dea7c32b3468c737b166", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "887206f967aa64e00eefe32d74ad9cd8e652fe0355d0f108456624eaa068f6b0", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "bf78a4f321a279aad32fbffe15c7beff259e21f3e608f67ec9cff3a3c72100af", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "292c19f0511e0d5c1b23cdbb365f37ffe527d38804d56ee525b1f78fb3872b2a", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "f9ac155b7d14edc02af936f4d81f9de6c98eab0be0d11b08b286acc5d38c517a", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "f2827698b92fb0022ba2fcfdacb3c7d276376659e2704825fa0225fb45b8b6aa", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "1c2206ee9b023f0103c08a58ead6c5bae48d82c596c403c6afe25eb0ca6d1561", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "b1e5217b727a3404fc3bd79b498cd288336fc072d3c6ae10db860275476e2d6a", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "e19135b7eb946746a90c036a5ecbb74adcb19b55ad803000f50bb4b977a7e0b0", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "cb6399729058b95a5e187247e8e92102d81aacf0081222eb314560f1c688e813", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "11bd4590835b1ca1caa9a1a9a0fa46e3d278b73ff634ccae2809ecd90ce47f40", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "e563f756e0f3164730e07c6bccb8e4b7b7eabae8288f53b6017c2e3254de3770", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "f509d333c4c1198165b68049136c75de292b8e797057f3c0fcd636bb3271dfa9", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "7937f60496515216af89236528f4210c7d85cbd0fefe9c3c529a69edd214721d", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "cf9acb9216087c14c29b2867a3ebe2297707cf038e9eae880586e15464ef1012", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "35b8596bd8d36a1d375f8d775c7024975b1c4dd5a91eed302b0f64bb09d71a47", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "fbd3bc449b8d9b0c9c746d944966e8423ad37ee93695bff12e819f25c8cefcc6", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "08166e1a644e38aacdf2f79a7fc1a494c6553ba1687ac6b7a011bcfd16fca45c", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "cf0da1359206de1d61c3bcd65852af9a947f92311ecc0ae6e2dc4184c780b11b", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "e6e4bd64a45e5855fd1a2007a5ad95f7b52256e3b942ed9a4ded95837f941af3", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "6544d08fc9c364621c604bd7c2b9288c8dcf0f4ad67f49ac93fc4a4ac631ea9f", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "de82be85797b0eab3b7925b139f9b4bccd9468a559bd42df037ca7326a04c911", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "6cc5a3fc72913372fa3bddb1ada906bc0b004ae30138984b3297196daadec9db", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "ed529f51011bd68903ea01ca9a3ce850c0ebc868eb76a533df8847ae5e6bffe2", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "236997a9e8a5f3cd23a15797f79921a60d95d89f46cbfef619949c6f22b32eea", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "add1503ffd37e17f7385a723cedb4d78c5f17444b5e4eb504a2f00eaf6693ac1", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "d8a895da9c18f297501fa3d8541bb5c67802aaeecb43b0e145cfc7fb30e75aeb", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "277631b4e02d58900723595382dadaaabd93caa722f99645878d4fd07014ab7b", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "ba582bf58e2aa2a99fe7827898fff756fafcc5351226de71e848ca30e4456d76", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "5bf4603e3811209158557bdfeae63f9c6d43c7ae6bbd0a18af40aaeb2d748122", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "c05c4fb05f21ca4e97753a7b50e0ddae398d7feaf3a442c4e04a233cfad7795e", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "30b778568d89ff31f251a0ed57d2328d3c16579a853a83db8f6a27db330ff5fd", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "61d32929cbc1b263b1a7d5eff1102b742e8711f5280ccfbc1e379abe3819c4cf", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "ba6c2600d8a7f54dde4e909c7a573345fdd4291becacca6a2e85bd88cead23d0", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "619a951692dbe4e47f15aaf3ff0d7cd3b31fcca66808f358a243773fb0fe349f", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "97bd839006408efb295f311a88b0a05256fa90f8ee4c62fe7dfbca95d1d54a28", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "6fd77dec23610f04069de935de6b5e19dcfe68fc334fdf06f414f93debc92741", "target_hash": "0c5f042666cb7328df2bad5c9b953320fd1f83bfee491c8aa203a99e4c1ff20a", - "section_hashes": [ - "b50e6c28c34a", - "37bcadac931a", - "9d5190205b20", - "32e08b8d5a12", - "6b1c4ea26b7c", - "84489153dc0a", - "a031a223bfae" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/architecture/QUALITY_GATES.md": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", "locales": { "pl": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "4f56ec8af3a50a70ecc3f5bc0ade08f9dcbd1b6678bc321745a1795652d82e2e", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "7bd245b93a4a9c45f10535a899e05fb747af4730cd0f9b05584547c1014712e5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "a3ca72a6bf8388d3d0ef45d26e37e0fcf088a2af4943269a01b0547ececf2a65", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "28140157ce6e24334ab470b55ca8e0c5c898091b6dd09b63fe9d2c30ba702ee5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "7cfc96629aed632608359238f21c129112343c69a723b61d5a2ac9a22a7c9923", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "aaa7a1f41b888b46f09de36943857570688c7b4014211031b16fb030bdeddd49", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "69c168438f2b70ba158cd063f37bad5a5ee6fff11311c19effb43265a4d35a88", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "40fd0a74ff93c240584dbf51a1e62e47fdd060c2460af8356e6c2023beb3ee17", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "08b9b82bbf6377b112bffec745ade641c70abb5b052331410cc17219a602303f", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "3f1d976030789c4f4729c0ebce62ea9e26e4d9e78f21d90ac92c60cd5b70b4d4", + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "1be6b0f0a742214ba640285a92b159b776d96392272c4e16427c6adf1374e671", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "e7d08c0f54704c09657926c02585544435e0fa7d719269523dced00ade44ece3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "b7e60162cd27bf0597ac4a8293ac7e4acba641af7eed354d7c2937f6bfbbe360", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "7d1db53a5907a2d64ed02bc24571ef36c5e357b8ffb696950025d24ac3f1b8a5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "1e9c960c710e257117ed915c98fb0ab07b56e8e279bfa984b309b8bf23b8a9f7", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "60db403a046234b32f3153db2f933e1b3f78da01234cc6ef5f2ece49a863dff3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "53d0fb17cbd98cb8fb68f93a03c3a93579eaa6988a71a65471533a1d9d729e71", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "168636f31ac0368c597ebff3d55d91a520269bde4f02f3d89101efe380292aa0", + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "f9e49767833a222a7e0c0ceddd6a993b5391ea0d8893f32b14a0b194d8963c8a", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "b4370021d43a5b4f5cad67ab0b13d310528bb11f393b30a05dac84439fff9713", + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "16372a5475a089419b498a08be9f085b2f5f8336c6be4023ef1c1849441cb086", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "cd4854048f2a13e557808126c3338cdfc2be4fd6ca6a27b1d8fa302f9b9ed45e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "99b876649a3653501dcca7486290ab3ba487b9eb8a96fa4d6250e40d5854379a", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "e0b09de33e5d6edfdc41b4f3bb520489a3f7f9845a3f7fc8fa2a3a1c76112e6e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "095d64c78af69118722c2b64a933ec94464ea908da9b406ecc3ebdc51a5b2167", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "7f2789ef2a2985b46aedb954442bf9726189d146f884471967b6088f7ac45e2b", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "eddf9de15e93aa0ab42262c3267d1d04f9dae4a6949b81f0d65230a37cb21230", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "db9b3239ea7b532342aa46a90f520c91674d55e41faad31104e270917b2bd7ba", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "cc69ceef35a2a4f3b9ce803e490198b5f89d110cd03dadfd7bd1dfcf52181bc3", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "06669c1fb1ce5d846ba867a2aa2d18662c79a9ee573e99add267a7059db6f038", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "d7130b6ef433b2217f5c1325c31b77cf27d3d9cce37dc13bfb7efe28267b64ef", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "79b010198295dedfb9f05be2713403913c0b554b69b3560c033dd16d8ca7f538", + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "c78ff37b4c266c004170a0243a798216ec86ea7329237f8925dd9cb11843024b", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "d9ed9c21d20b9a7042891e4d88627f262dbb902702d0f02abb8bd6521de8f477", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "a088c0443b439474b401180ed4894deebae63d7d808c132eb58839debc08d64e", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "eb41e422845da5a30a2fbf68c317b1760b2b7eeb26893240b373d3dadf6dddc8", + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "78c358d8dbc6f94d8b0110807d8a79146e009e06a969ae8554a3c077e915635c", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "349ad6267732e21d44594b9b4396f47803cce57a53fea67922a280cffbcc4e26", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "65343b482c7d88ef6ee2084a70f0648dbb3dd133ece9b0612003ce813617b8ac", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "65a9acb75cd7c3f9268cb092df14424add9b3e6c1ac441cf9686ef76d9f7f053", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "eb0046a00b12985871bf6491a9f61bb2c9fc427b9a41041f9ed79fcff4c25f09", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "cfc5a2c6abe1a202eeeb1467aa4070b6eb61af1a6ddd57dd798bbee17f3d1783", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "dd4b046ec1fb1dbeb63d5e5a304820457dc2ffda12ae33a7c4f2fef17ef2e3a1", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "c1fbb06c1ec09a728b5e321844f6eaf4fe23ae993c0422aa70567b9e18a01e4d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "584f95511ffe84c848c89115f7c57d699e4f19322822b273244777bcca2861be", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "a02b72ea7fafbd5069d228cfb1b278d10ea64f82a80a86fcaff454c3b604b887", + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "2f940087c60d7a0fef27911c62e52b48cbd54c35218f570f91340eea6a973e06", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "9d1b07a3691305e3f9c957996fb1cce8d7d43f328c120908e90fe1153b23c799", + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "3e4a13354a4b24c064ce22df34c03339973534e329ff4ec27c9fb5b9f1b3e888", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "c8b5fc24682c514834849f01d5ca3e3509960d37da0bbda3e2b854184e5f6852", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "0be301652bc0ba0009c42d3c6f800fac497abb066c7c4a823fcd5ac6e4f6e54d", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "4b869d7d0d77a65760c20f189eae30636fd9daf050c84f9546511a4da57686c8", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "a8de56f6f25f85f94597e06d4a12691467aa7760f5e002a27ef134af360e52fa", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "3ad3c8fb3d9394ae5fd3382465cea2ae29da9136a80c4937d04c051abe180285", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "7a31dbdaf13e5e592037335ba73c291735ee7d6fa3a5c2e2c9d85d2e8ac465be", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "c3e6d4f38853b6680f71b9969f5b80b4ac5cd68fa390bc8031c31ae2f2b8ac58", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "c93724590cb451bb8faacd737ff860d8fd7d40e40aa8108af6189e93030fca4a", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "705077408fd0164d3bbd9cdbfe127c08d0726f28dcc548adaeca58e06d655b1e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "ad9b1206f2a785db0bb9650e5e40f4dbd5ecf1965c41011ca7a425b093dcbb6c", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "fa53dae4eeb157f0a298e0dfe4e30c8281061f00df4e39a95d128baf90614187", + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "bfad2dd50e6b0d654eedb1023b7d245d1f8c851cf96a5fef866478c7a10ab0c0", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "6df4371b831ac7a9261165a0ae914810fe2ee657e38352ec3d2a67d76a9a419a", + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "0d1e59f7b1a0a9e61da66aa7c2a29eb685a04bb54f6d8d7037a20bf2f90fdd8a", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "ec117d0aed84a85b20a7ce603587426f34aec26645264e858d6bd66f038112e8", + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "93bd6f2410cf2d0022612c2aa8b85a99a073ef2aca09aba1869b07bcf57917bf", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "3dfd03aed16d4219cf2eefd49047330a3850ab050a2317662ed17c59a37c5951", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "78d3c541415459e9737c8433a289646519629e2faec7dca638a4d908f6f74c46", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "7b6b6c2615bb2864d73d9b15673c7ad7f316c12c066ac78e8955b41697f089bb", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "449f4beb748551a533e87059d271ec6903c8dd1aa793194f17bc95b35856e083", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "36408d012ec0ec66204875184b5f1efc725d9beb0b69633b97d4a60b89a183d1", + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "2c0d65664133fe17110a7030ba9cee1f061d2d8601f331ee4a81d029ee6c843b", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "9e56b5e4260ad7b708e5e3169ae92236e49a9b3b3be10e1a497e2686fbb89159", + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "743f056595747b17d5cb37e279adaefc862fd92e41782dd493f5c33057b0222f", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "8c8a8e135930dca1c2388cdd331b0c4d2560ccf7d3d6b3e45589bdd59c960ff3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "be2d01e5710e4fe1cf17ee40e9d67a509fac0794dbd0933c61973c3b603d6ae6", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "2e953d0c940430dc8895b1ab8cbd04721b26d1b7f34504172e140951317c48ff", + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "b8c81981bc2c9267475de53b2003abfc33acb7e015cc2d40f9e41f753df33957", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "68d872d2607d229987c035778fc512cd180dda689a92e8961ec77808711c66f6", + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "dede7f62eb31e8a3f61fcf7520cf03e8b4541aa749d57f7baba621b5e3f5d07e", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "90bd4f4351beeec23aa85cbca1a79877c163b2b9da8994e434f67ca92c452a0e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "7b7d247984990bae61f836c7f385a8e8ab324efc3a011141314299c4d81d4cd6", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "88aa5cafae59ce0fae10e25a20eecbfecdfbbed398c6fb5e829290016eb25ed8", + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "370e22e3cb9b1e66b8885a1eee664d5cfc94cd6bf28498a514bdd383683ac369", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "7d1c43cc3a00228ecb6b0640f28ed9983eaa1373be0c4422d3fd3bc843508359", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "6c47d661e0a771e20333bd105737f9c51fc4fdeb7660cf4932634ef39aeaa5e7", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "e246ba7ad9c5ea354df3f45b10873ce7f659ffd1974f015c92945c54bdabe4b1", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "ef10520e96d522e42452dc72b7ec85fb4d77695ef549bf18083fe921fb53a9c1", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "39d73602e871791a38aeebd90806747ea90e9fca452d6ee627aedc1d2ea6a086", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "96c7ebbeb936732191017f1acb1fc30b53e2a246b8e1cbffce4708989b2b09f9", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "6ce06636e6321cfc0ba6275c9d4314e90e7aaaa2714ec126b6109a04c1ba346d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "49eba49a09e64d4e7dab2b31f99f04861ec4332aead1bba08baaac418d729f0d", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "6304442b949751fbfbb322ea42cc2e79bee5d50dea0867446abdcf4164d773b1", + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "456079497870a2fe372bf3b48fd1bc206c2c2d20336fd28cd757988c144f0edc", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "6099798576c3ce5e6ed3f2dfd54b364764ded4a79a2775a37a7462cf987a9288", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "d37d7d0a9b8a614fbf4101af441f78ac296fb443d773cacf1caad4057945eb06", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "61def0e8d22896df3aa17b4bae86cc17f2a2bf5b0804f340d08256b9a072ecf4", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "87fc0c6ea86bd0e4f24ba6210e1e6d154b6449977040f272869ed451fa662847", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "70a28045df1edd11217cedbc46e4f86c075c873b69c4e018018c8211163bf1a8", + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "3b49dae463429466053d7b940cef27509d65289df129f3f88406ce87a1e3a70a", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "004c6b35e212e9a576bc497dd754e02ab47c6dd40f45b63d878ac638e5041d98", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "c5203a2a672f3e751002ce1d7ea39fcc44cd4c43ff29ab0009437ca8b8d6af7d", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "8f21695b4148161723d4c1b09443d65bae4b8f5ad945c02935643c38e59b4eab", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "8bc4fa1bee511c6c8e083f163a845b79224c68fe38bc220288b139b10f50e8d3", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "a898ee9b56d084ef40fc7d9276ab440e810f74848ee9c0b7adaafa3bd17679f2", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "236770d16f3ea896ff8384f11652a29895abc2377a34dd8d3face0a11aa38ddc", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "458f1bdc61828c78d50dfccf8ed3c39ecd5819ed84c3828c54998f8add4680f7", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "70b3b8bc2fd9547e94aa3086dda990c828ac48aaf1492059f346f5c38e6c93e8", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "0d651d3a7a6a8ec0a350696fd1dd6d49b3b5dd9c45e031c9779537966d09ccd3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "52d6c211e805f914849949f6e70fc98daa0e018c9f26532d94c4b0cd50ef14dd", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "efdb55dba7f69de079b666c33708cbca0a45711d0256bc2c22eb48e1d2afb315", + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "ab27b8ea348f266c4eede8a09d37349b5ba6605a46d0b507381cc7dd310f74a2", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "ce5e35b0660917e99dbfa0b2050eca8d548e108eafc301d8c5fc20191e4db8e7", + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "231d2300995b72902411d5183a5343c6af1c9332c031dc64074559003fb566d4", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "45467b5838da74271a6ef9d1f31329db12b5ca8145ea86dbd59bce2e061f9398", + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "13b2231cbb4b719da8f2fe87b652a9382bd1e49667e475647f662405b0362d09", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "a7c0f7624cae4f89b0d08759fc04038eb4a8bd4dcd4bedb4b54685aaa75172bc", + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "442fc4c4c4f923e7f3e98457b2fea41f19fe3b81efb4c76b78d78ba4ce704802", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "5397e3be2e3fa97018f6d15f2e57edea139598f9be332ed1d66c14e72edc1cc3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "5ab287a7f218999c00f501a909a01ea347822d9369c98e0379ca535c82381705", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "9aaba070694fdf048f6e7ee99c7b9b75ab5244fd6097d403141d7ec0b7bda0d0", + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "fc837d6bf5ad4c2f1acd87c95e977738146d01031c16f6730b3821928120dabf", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "693ceead1eb1346822a62287492957d02f877e9e876797d44dd44bfd6e0b7dd3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "500aff2942d17fc25785e91e6bb81ba9d70353b870e68a2308644af341046537", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "4adb31c55addf81c4319c2632c5c8ea9844cb275312b95915cdae0e4f65ebbe6", + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "b0b0dcb42ea8752c42c66c9021b089654f32a27118f106a2f8f85038463973d3", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "e9db27a4c42c2a5e2c98f318763965eb6b4afbcae73a0650fc7857a3a10e016e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "b90712981315f27ec60a7660f1db14f59fb044942c13b3f764af45cafd56c79d", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "0dcc4a10d81d28042c33ab830361eb3c55bb2dda7211db326a6739fb6947c293", + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "a86dee5ad73fdc3c96fec071f6e666ab812e4280afd9643be154de43d1daab44", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "10e8d1f043b04077e2aae885d93c3fc4d432fca581ffd8ab1b1b7fe345ca6f73", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "c3c1be1cce796f05d916c2f3f698b21bfa5d957df9a1f51fcf14da23bd39a6b0", - "target_hash": "03d8a0f8b4dd7b2ba06e482be24bfaff835e5a039a4777184199a02a9ccc2156", - "section_hashes": [ - "67dc4021ab63", - "90957c0a6d55", - "d3f1d46376fb", - "7f330c18c808", - "786d5af09ecf", - "5be4512b8a39", - "12d9aeba14fa", - "c0afde5aa4fc", - "8617961bec5d", - "718f11b3dbb4", - "f78b02ca82d4", - "00edca0fcda6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "ccb89ff25686692bfdf3cb2f6ec9e6a4db003183efdf7c944fa6882233ea961f", + "target_hash": "2fa3922aeae49e2ce595977d9bf1ea1d6960ddffc478ff482b3e73751ea51a47", + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -13840,1642 +6381,667 @@ "pl": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "9043e565e04c280442685e854099e38e1a00a393861293f09dddf19df9bb4102", - "updated_at": "2026-10-02T09:42:51.053Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "403a5a9c21d47f077e0e3c66c6ab6ba4b471b725ca8263efbfde323df475a51d", - "updated_at": "2026-10-02T09:42:37.015Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "f724ee4bb54d87b107371cf2db9573e6bf1d8a408adff7e24241d9075f5d5330", - "updated_at": "2026-10-02T09:42:38.704Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "88624c46f8cd10516be2a04044699c859289868ca7545b8524cb8cbf9d3d8113", - "updated_at": "2026-10-02T09:42:37.748Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "2405288b2d981b13ab40a71cf04352e7b22ba062d31de9a8d7eedaf3baaad06b", - "updated_at": "2026-10-02T09:42:38.064Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "127957b6e1d8002fd01a220a319703ec5b5cea8d495dfa232d742873e9e31ee8", - "updated_at": "2026-10-02T09:42:37.343Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "a63c76904d163a5828218ccc74a25775cda5a9716974b2a77e531cee11637072", - "updated_at": "2026-10-02T09:42:39.089Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "dbf99453c0e6dce62858ee8b5f1c66b0b50385e8a6ea27717f26ec531c61607d", - "updated_at": "2026-10-02T09:42:39.377Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "9d6337c1236bd6859d7c25023aeffbef3e4576b8edc9591c108abee3d29b766a", - "updated_at": "2026-10-02T09:42:40.039Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "ddec572ecf2658f78a333700418393d3abb3f34f1d8f1cffe903516ef71faba4", - "updated_at": "2026-10-02T09:42:40.339Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "e38b9da1f5b7771998eba7e79a656ea6581f94cc75e4a883b0aea0f00184e153", - "updated_at": "2026-10-02T09:42:39.752Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "5d75a35a0c1115ab393bfa55d6709f47bd7e2e6d736497f36b77d22406f20d78", - "updated_at": "2026-10-02T09:42:36.672Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "0ea5f768a82516833898add5dd060c7b13846f51fb899d8b411f322ab8d01121", - "updated_at": "2026-10-02T09:42:41.315Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "d96fa79999e461bef6dbb1bf6787262906502c60c1989316904bbb2df1779762", - "updated_at": "2026-10-02T09:42:40.638Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "7c4e9a82944d083ea5daf35faa28ebb7809abdd2b82db815d05978386f1e5f64", - "updated_at": "2026-10-02T09:42:40.959Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "73230e5c53fbd80af27163cf4ae563efbbae53fd0c0045190dfc164b13ea66d3", - "updated_at": "2026-10-02T09:42:41.595Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "608c656b6f52df51a4ce078e69916ffd6654a459a7eea7fadc54c5589537e531", - "updated_at": "2026-10-02T09:42:41.907Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "dcbd486279cb31261199f8b95e461583a5cb0c2e6f47778dfb30fce251a9e424", - "updated_at": "2026-10-02T09:42:42.215Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "3178692fcb8ae4fd4cc9ab6fab8ed14c61e9c2c2f384e03bf99ac9684b5d1cf0", - "updated_at": "2026-10-02T09:42:42.523Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "b0e028e9193f499e3b515f1b5804aad5f891cbeb8eb6ceb493d881a7a56b2994", - "updated_at": "2026-10-02T09:42:42.841Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "d98051b36705076a4037a3a5a9c0d247c940e684be0b47015b8027fef7cc5108", - "updated_at": "2026-10-02T09:42:43.142Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "8294a3310e658e8f72a08f2388d699fffa4a186995e7d46ed8a0dc2b7ceb2bd4", - "updated_at": "2026-10-02T09:42:43.460Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "8f74fc7807b8ff18b103d6da08022cd7b9c197215bda32326771aa94011d0989", - "updated_at": "2026-10-02T09:42:43.784Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "da8d7f307406171552992f262893fb2f26780a77cf6c7395a6aade5a5d27c673", - "updated_at": "2026-10-02T09:42:44.089Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "24616abe25ee6b435848a2486bde70edb3053c70a9cddd91b3a3bf562d8e9038", - "updated_at": "2026-10-02T09:42:44.703Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "54d88d84726339404ae4a07e0fcafbc5fe18a734d456748fef09ed3ec7e5b648", - "updated_at": "2026-10-02T09:42:44.362Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "0bd0bbb308d524c158ec236b38832e0dfc340a0a4782177b0def3528db4532f3", - "updated_at": "2026-10-02T09:42:45.057Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "f8e0647b386d144a4472bcbf4f1a5ae68e733d12ef1ddf4bdb1b9dad12f6ddc3", - "updated_at": "2026-10-02T09:42:45.416Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "5222596042c0417df036c3e08d05fa1133245789909858a390948001056e7593", - "updated_at": "2026-10-02T09:42:46.537Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "d5ac84a9ee498604cee3b88352178d4dfb613bbd0a837bb3fa36f92b11f4f01c", - "updated_at": "2026-10-02T09:42:46.142Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "fd84d18bd3bfbf55bfec24eef483c65c4780ca66203177b851a886b776092236", - "updated_at": "2026-10-02T09:42:45.808Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "b01eb7315b69ae7fb3aee38d4cc2efa7e57adfd444dbfc73387f251cd50f0a79", - "updated_at": "2026-10-02T09:42:46.846Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "c3d1990bfef3dfbb460bf37bead4201d37b47bf0f7935e905dc7d343dd0cb777", - "updated_at": "2026-10-02T09:42:47.172Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "a6e48da7f145bdc913b7b31cdcef5d9ae14a6c0090b14f063266d33696101aca", - "updated_at": "2026-10-02T09:42:47.491Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "0a8a4f4d623a4603562f2e899041d1e598e5a50be43f62f31d05f549c6cc3a4f", - "updated_at": "2026-10-02T09:42:48.246Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "f5d9454f28e6b5787a611064259d45c0021f833adbd268c8e538184cc2bb8d05", - "updated_at": "2026-10-02T09:42:47.795Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "389d5c4c0541236c9bba775e404006ea8e1cb5ed8c123e6c0bd76cfd4fd959a8", - "updated_at": "2026-10-02T09:42:48.595Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "759e7a84a7de9ea902e8365e3f6c1d54b1f6659e0c8fe6403070b64db2f072c7", - "updated_at": "2026-10-02T09:42:49.201Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "50e5665610326cdddddf6b066783946078854260beb28698f4130c20b493eda1", - "updated_at": "2026-10-02T09:42:48.873Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "dcc22b6eaeaa3d587496d1562b716a697ed693fcfb3d841bef43e6eb791853ae", - "updated_at": "2026-10-02T09:42:49.503Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "23d00ca3408c57c2e265fc85fa7eaa2f24c3775e24599b6eca3007ab5a8cc868", - "updated_at": "2026-10-02T09:42:49.796Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "b0c0ddfed227e0cc83fd2004a6c0b125a503411feda1188147fb0fbc666e0593", - "updated_at": "2026-10-02T09:42:50.776Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "cfd1f3d3b038472c54147c95bda57f5006dbf553a26ee1b2932534e467ebf10e", - "updated_at": "2026-10-02T09:42:51.687Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "71a06475283972542308b29d1f034f43092dfd67294f0331aeadeee6d3701fc1", - "updated_at": "2026-10-02T09:42:51.348Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "80f42269e2c45cd43b403df2babcc5822e74be57f336c5b43d4ee33a43cdc15c", - "updated_at": "2026-10-02T09:42:50.446Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "32e99a93f163a88e42e0be8d6c7e6fddf0d371f000dd6d56f4160f7b96ca1272", - "updated_at": "2026-10-02T09:42:52.041Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "e74c0e0785f8ebd1ddc8e592b7eb72e5729ad0b1ff360f65b42364780f538e20", - "updated_at": "2026-10-02T09:42:50.108Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "3d990af680f4acbf2c4c1ded3a74c5b2a7ebfe1280478f8d61bb67124d966b55", - "updated_at": "2026-10-02T09:42:52.337Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "44d6d694d8874555722ad1a36d7bc7d0c0521a1e51113014846b94f64d981760", - "updated_at": "2026-10-02T09:42:53.027Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "1ab59d2c9d92e4be9c695d4081f9b4a58dae366083cf147c3e0219e205dca9b3", - "updated_at": "2026-10-02T09:42:52.747Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "c4e981c86a1cde369cee5cda61182b13de42a51054eccf7ed2bc1eabe89861e8", - "updated_at": "2026-10-02T09:42:53.304Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "4f45a316c409c721afb9df3b964f5fb8d7e1b4665d509687c93e047eee5e8217", - "updated_at": "2026-10-02T09:42:53.891Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "9f0fd5bffb2b100a41aa57d87b8c0d8d9bd62a1600fc79b810040806aa58058d", - "updated_at": "2026-10-02T09:42:53.601Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "e546da373f6d7200320445af3184142d6375ed88095b383d8c9157a8c5b90ad9", - "updated_at": "2026-10-02T09:42:54.259Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "d0abaff236db7d23cd52d63f54537b6839b7b92c18a36d16880af07c768d12a0", - "updated_at": "2026-10-02T09:42:54.713Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "715ef9ccec511f169332c4c46a2b701af82a7030bf27365e93eb2fa92608939a", - "updated_at": "2026-10-02T09:42:55.233Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "bc0360ed283a53b354d659121ed3b785dd8069d4253beb93fe3510a0c7ab3919", - "updated_at": "2026-10-02T09:42:55.634Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "0ecd271667f2ee956c4d7cc794f28bae5b1bec325688c9eefc75c38dc8709f05", - "updated_at": "2026-10-02T09:42:55.980Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "2d53fc04eea8cfd6c3ba349cee6d3d82eff5439e4d13724431a55ea9634b52ae", - "updated_at": "2026-10-02T09:42:56.597Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "9d61f110af893b6d47530cd0177d16d54654167490d7d8c59221296c7f25b8eb", - "updated_at": "2026-10-02T09:42:57.041Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "5b454c380dbfae64cba89eff7a9f6164f18fb122b9af03eab599c874a49cc647", - "updated_at": "2026-10-02T09:42:57.633Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "e837b718c36578a2a55364d4bbb06db233783537d29fa14d0a6cd58002b6dcd0", - "updated_at": "2026-10-02T09:42:58.640Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "2d50c9137c3f132ffccc3b449394730d9f5e67d378d925f7590c8dbd22d12a8d", - "updated_at": "2026-10-02T09:42:57.330Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "75d0b96c4ba8fa9d82bcecb62c2a17e140e152ccc611a144fd9345ea88372d1d", - "updated_at": "2026-10-02T09:42:59.011Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "ebeaa6ece3718f1bbf2ba2e35ba93efb662289680d3ab127bf77c63e65f40339", - "updated_at": "2026-10-02T09:42:58.138Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "64c35b9c2ed70f5a5c47d65e5110aafb5fb5a6893c625e692f1b343ff8ada75f", "target_hash": "c617c11f2fa377a33ffeac71ae83d4fdabd854d3bee7bc5ceedb93865008208e", - "updated_at": "2026-10-02T09:42:38.370Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/architecture/RESILIENCE_GUIDE.md": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "locales": { "pl": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "35b147f9e83a53385d60de4561f422477704c5ed3f533af9f16c5410de40953e", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ar": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "d4e8529e416c22f8aed79a11c42f03181869131e33b6a858f2b6a029199770c4", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "cs": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "6d953a149904a64f89f4f3dd5c595e81527644dbbf273019978303c26b7893ab", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "bg": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "753919fd7560236f852174924db016eaa47a812e1dd8c2ae179aea25be654110", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "bn": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "ee04e8f1708dff235af9cd2c7265c1a170a6b54deb16f2a60bf693d1f8fd304c", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "az": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "7a61b5af37ed534bfb07488b57e5eb5933c2e29d57f7df95d07a936dc9221d92", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "da": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "951b9b0ac193d1805bd7534e937fcb9b1472eb878bf00da771883626dab5ff5a", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "de": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "64f9948503991166703adf9ebe134def34f140f56be189243df1676db13d1e81", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "es": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "2464055faaef1a504778faf1f45f223a87d82d52f3c902b9decb15a8fb58ced6", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "et": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "e67c421588190b5bf12fe102ae8280ac2e986e0285a0e374679f710531bc977a", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "el": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "a77c3e9ef33b7c1ba155711730f187098a85d5852da35929a13ec184735da540", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "am": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "0c099fbc09cbad69d405ca406f0be50c8ed2232e43670d9de02fc67170b4b72d", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "fr": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "412c9e9492e62464b408a07726ff2ae5d163b84ad1f113f6c23b58616fa9f6db", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "fa": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "c92da5784b7306ffbccae1bad2f6f689a6c39c6da2ff906ffb71221adeceffc3", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "fi": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "1cdb1886d8f3e0c83d2b53df647d18cf374686b644f4c0f97a8ad46f37dbc5fb", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ga": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "fb0f2222e21d871894bad12f04090ed55f3c4164edac605ff695d93adbc72ac0", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "gu": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "f8fa5ca3c26cc58e19046737db115a70788f68f0294c09c9e686eea5ce69b798", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ha": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "460366f4d67db2c92f70a2064a3362167dbf532af8672ed79bc15b76b754455e", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "he": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "9fdbd912efa4be034868cb9c9da1d2d8b9c2d2d9fca81862c3dab46a0fa75b56", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hi": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "dfa9e95b05adb2d960b2205371b56e9f44444cce412d0c031a467dc489332468", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hr": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "18dab2f587e42c97d1cf1c2429d76d48f2093f04ce3608521e75a97cc058ce38", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hu": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "ca11da4fba4a909c615d012d0fc6ab0edd08ebb8673185c02c64783add8b4eaf", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hy": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "6deeb56205bd505ed2f279fba8339aa7eac09b0c9c74f6239325f5690cebae48", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "id": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "a919545a14c0943f92414dbd55335888840920873ebe567b56b11ba55b014c99", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "it": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "d3758cb8ec00f198db9c4d7d61ff39f78518f219e0337f2106d11f79859247e2", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ig": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "87aa496168c355214be8ea9f64e47b7af2487a5b6fb479df33b6874dd8e18df0", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ja": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "0976f214a97d7bca175f5d5b5dbc12a9ff6fb3abc8dd070697ee34118b765d22", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ka": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "769c5e2f0214c44a37d5ff2b8e572cf88474ae1f01bc8ec6b40b11e19a2964b1", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ko": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "1a853bee4c9f9f44d080ef93633536fcec143271ec1176c5e55b45ddd321cf1a", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "kn": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "5e888eb747bab9a4ee77080fedeab9a1250a4e9f8e3e3df6d35c6b3a37a5896f", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "km": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "8df15688bca5bd69eb6895652653697cb471d5a4a574dba6da41d64dae40c321", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "lt": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "e79c516ad0fc48c91e8a34443e1c7e097be75ff0561370e1c93bec2b9efeb17b", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "lv": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "19b687a408878afc5c3df9246415e9a549765fce8b315f97895d0b935c4bb412", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ml": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "0f6efae4cc1332194ad6278f692450fa74cb3b22f9afa4de27f14fa9f2a91bf8", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ms": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "c547d990331a5e80a84caf575915d0d96d163d115a1b74faad24d2564ad321cc", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "mr": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "ff32e3cb511da43416a9be2943c84ab18333cb6bd6abe8dfb38cc0951e946347", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "mt": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "5c81c5d36fd336b92dc9dadaa6f45a10a93296ab159d5b914736b750ca62a586", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ne": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "599f6e960fa87ba8dd3c07b01b4b973da7cb4b984114cf096beae9ee88b1ea42", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "my": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "5e418a12d76fdbe91bbbe983d8cb703d492d0d3f3c314526fb97125be7dab9b7", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "nl": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "aa0858319c20877ea94d1ad5290e305f8e1ecba621a506a01a18579be769426f", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "no": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "c4ef58c83e54867d970de852ccc5ceebd814d7859527e107ef3b44d4a66de22b", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "phi": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "7b45f231c2b7bf76e4bda9201c46ddf6bef9606943e4e4c3b334214b7f31c480", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "pt-BR": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "25a0fed21a9cda2114ef33efb66edfbcf4214fedd21bb6b3d81057be8c52b2b3", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "pt": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "a4cc1af4602c3fb4dae497e0e363ccdec6422f1b856798e0ec393111eee92f9a", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "pa": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "a08c42997585c768bb63f2310549936aacc7685488a044ffd5c7f52e2950c043", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ro": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "11e0ca93753eb4aecd6585c75a6f7a09d37c19c44a3e8ed4a5379bfae39f69e2", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "or": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "540dbc86b13825f49116e23fa9a7deb2bd6189cb0b08b59edf39d7bb0cd13781", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ru": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "c0ea7f133fd216a4a520d3d662a52addcf7a67b8ccccc190ff62576d54a9216a", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sk": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "862605336ea6eec0fb3ce884a97a82d67d78970afc7b8804c30b45498a4a7015", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "si": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "9338da6fe845ffc4f2495680c2faf827c4c4d6b4d1de43d2b614fb594a0726d9", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sl": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "783422c3028a6489370ad495ab3260d5051205c1d28146e11925a136b1804c30", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sv": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "3afe6b3f0b0b8a410a13c97a3e508ab0f6430c86e7ac1505071d33ce5d2e1ed4", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sr": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "3324f3c737f35750668fcc92331b623a1209808691f9da88eafa93fe9a6ae97a", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sw": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "b7cab5b9e8d504d2bf37dc969d50b225ea701a488f627e4e7180351c7dec503b", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ta": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "e2107026b10695d1b98f1be67bec70f2d475ec0ab992e2785c057ebe59af246b", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "te": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "e6d0b59d1514bc3011cd95f13affa9b1a9366103a9319357f37d05b1f764bd51", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "th": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "d42070f4abe1d97a600543462b166e1303aebca7c6d82d7421417dd000b210bc", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "tr": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "d9549521b5e2c1d9b223b5203861413b97073e3d461a4fc370b7c424e5e05e81", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "uk-UA": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "58f7cb4bd38ae51290a88c55942e0463898d006e04165c4b413d34c0a61ffe15", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ur": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "44724a6f1d5b97e92a346eaa0b80e29e6b5731f1a294feae632889b82f21efa4", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "vi": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "4faf757e87b48c772bf3d73e62c916336d9ce7b31aad185d147e7cdf5eddefec", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "zh-CN": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "4192f3abf8198153c942e7fdf2de9cadbc03b906d92b193d35cef4eb69dd3bda", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "uz": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "60b12635a1db764be89a6f916c72a3098f2062a598c1b521fd14f34a28a207f9", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "zh-TW": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "e976c495b9419cc1227c1325244daef3547b1ca7370694878638204f0eb64d96", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "yo": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "4b8e455430888905054d6db368311d2f6bd441db4d9e20e6bb4b5ae378c90b2c", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "bs": { - "source_hash": "1e0a398f8473ed435b20a9f17cc553587125fcf9c9a9252e44b3e28931eb6bb1", + "source_hash": "09300027c8aa2d543439f56b4188bf082bfbe52e99e3c6979e8fba89ffd0b3e7", "target_hash": "ba2a54c98000316a2a9b1213b6e52a9c0fc6a7a1ec9efda0b193d0653a282573", - "section_hashes": [ - "15ba7314426c", - "ce3fb2647dda", - "95aa5d11702e", - "45d160ecfbd9", - "5a8870d81eef", - "3eb96bd72521", - "410c3e43f410", - "6875871eef95", - "939c16e4ae8b", - "449015e3d7dc", - "fe596e9c2d72", - "07e2d767adb6", - "c710e1078ad5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" } } }, @@ -15485,917 +7051,332 @@ "pl": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "8bf837b12b508520ce83ceec3bba236f1b6c65ecf1bb64b2d62cc8a57fb2d6bf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "604916866f275cbb365e4b3dfd300d749e83788a1b049a91707bbe7a135312de", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "1ecbd899f9a21591c3457e54132dd93772c3e25935b52c43814f29ab02d4b4af", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "69f344ad8449e329ac04e4b3eb4ced475ca0f6469195eef76ea68c3c3ef1d0d3", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "25b824d7d465ec5f1454208fe95f8aef3ee96804218e6d99b21bc480673a5fdf", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "803937ffc844651d4ce110bd96115bacb5a2bf61892a4e9de3d867ea22e345c2", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "52534d7aae81a6884b856de4dad7f6f45202533360db71566fdf2b27ff7c6674", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "3c3868e81ec0f122210565c1a38acdc88f92ca0e3b1767062e39cd461a3fe575", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "243cdc8ce0ad0007a64103676d2ca7b5ef1ff63da4e3dbea323502f6f762013f", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "e8cc3f9edc62ecaee23dab8f649b1446de34a6801b8717c63f531b80d7f62efa", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "be987d5eeb2ecb7bf059db440bf968539c42183d37173867ee56808946311cd0", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "6c3c6b13a3505efcd46a31f76464c178ad8550c843b2d16c65b51ca9a6d8e094", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "28553057c2536aacc95ab422543bc6ac1d403ce10f170972255cc269f0134ebd", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "dcf083bd49a568b0e1f0d9f3cf9d370611abd84ce5a57fb5e9361c22d06b1fcd", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "151aecbe3eeb50647ec1706f05ba97ac8b9e45fa338f9ad8a168ef643b48a5fc", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "317baefe7e9ad11240cb0cee7bc32643f9f478163191b4fae9241c6eaff9f193", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "06e1e6e952566021a2b040a479f12a4b1e8062c51e1cdc172291d3d7068bee54", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "deed5b51911c4c94fbdd0ce7a55a4ddb6fae71cc51d68d621161fb544b1c888f", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "0868a214190ada31dfb8bf862ad2824ab5f3f0351ff5f2490321eb8df93ed9ca", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "70ea927982845bbbf1e95d8c1ddfa6fbaabea75b18840a59b99c23f56e8acd64", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "a9a616864149644542ab3d6bf2b56189c93774452797da01f56588eccca8fd7a", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "12c7fccbe1020d017d1c739b62a86bbed71fff16089a08c9016a6f0aef31340c", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "5ac33f0dc6bdf59d3123496adad52d7ffb4cf90af456a715e94c7a7e4481a4bb", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "b761351b4cb63275134c922b3e4fe1f7fd290fe0b7407f35b0286c71219bab21", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "10f6be6d153eef32a47304c46249fe04961a3e796399c29ef78a07407cc5144f", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "8aa82e5e39b4af55dc2fc99c50875d15c6dacf7749216d065d37d3870cbd5d8c", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "623d8a1e65f439611a22ce5bcdd9db73000f24ceb172edd4f321f23deb8cfa86", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "ce80be0ed5c72843cec970366af372ae1428075d90d59d4f151328e732ddc486", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "2b3b52ddf6029579e0e635cb3d7c1e1dfbb6d5dceb63e94d1c100c2d4d5d7e55", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "e136f0ce17ca30810346423063329fbd00cc4a58ad0a5e137cea87d357c8ac5a", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "68a98716fc60984f89ec934ff2a8e50c91cf1952316ef94033466e2a41d96ac4", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "a96d5e117c73d1665a4c09df3ad0d4753e83d8e649e2553c54a9364263d50129", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "1bc18c4d30fab26187c0fd4fc5735c58f8e9544c94c84ca00bcb2fc6229d6266", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "0474cb9d174569c51a4e160d392dedb20000d6447b12055630b6c42cdaebf554", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "56da30150b22200d4e0f5c1584c966852de5aa2b3eddfab49f59df237b23e4eb", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "915646a06a6aa983c1cca900fcbd417783a5a0f1d891862ee20459cf739dd6a7", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "578547c99f2d30fb1a762e82552269fb1bd83603a796da695ebed249af945c2f", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "696e67fac3aa0ec77a105ea4483ec5da59a346e6d84450b9e04660cbffc29317", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "f18137eea189b8dcbdb9eced6414d1f7a914a9e4b3591d61e2a11dc6250d4a37", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "12220a8c3bd93398b4e2881ac0597a2d2e788d83fc98c6fb5050415d2b7baca7", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "e5ace26013345a93362927eb0264ffa923811429d97acea93cb2d14ac3065ca1", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "4a50dbc239221ee9cabf0ac00d822efe7442051698123d588221c1e78ebaaed9", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "c7dcecaa11aa734b77543162a7d5f90cbfa2e0c0c6ed91681541f5cdce24bd00", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "401c76ae251f1d190d39cc3cf72d52268fd5f52f8a7bbbe4676af0c901d7f5b1", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "e5d2fe47bf0bcdeba9011d8ee142750a30fcb74d69bd02d64a97c56275bfdcca", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "fb00774cf0ba98f645d9301bf4e3338d721b1ea679517f9a2a8912fe28e8bf61", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "0925d3ba312ed451f689f57be4b3fbded901c8b39499182d3d0ea6858f8c7250", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "f8afa85339673bbbbd3a1ab9374386a970034e99b3c28004a6704567eefb5300", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "c95c7e554c398ecdedf6c74488b85c3bd1876c92f2fd21956a015493f2298cf0", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "61e92071a5ce0db4978595df653c258c9a3e1c53b3d8c3a4a070e33854837f8a", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "c49f19ec999f04807088e5a81ece8ec95e18898358eb0664d00053672d4eeb2f", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "74668cbc2d7c322dfa083717faa1096a0d60c7627d7ae58f9bd384eb96e9fd53", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "0f31048b8eb9584e9c729dc6a31c79ea6b67f6ba9d064305b7ddf71e76c2bebc", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "48ac36116fd004fbeabb38868367680770420829f2618bf651201863f0019c2b", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "f9c5dae03bcda48b35aa269dbd71ec832836d87c529de5734515365608fcaa11", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "2f1336744fc9401e78c578f01736afbcccd4a5c838f55b3f93e87260a9a72852", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "d2e0b98a52bfc20fa86b107d65fe36436e924ce2114bb7c9ca75a63af2db4266", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "400ebd23f1b54583955f156cc6abab6419c79e176a933e74a83ec952e4974114", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "4467aff10e23797e416fd4c8b617f24adf308155b0ccb3104329b112988c83c0", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "b466f79da10baa0a3d05f8ccd97e1c8fe2289169dedffae336b0812feb1a6de0", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "752443688f539847043773167300790b5a73e867a8467e2ad1502ca1a42d4923", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "ee54256d47b3e4489e75e93068c9e894068e93cb82b4a42624647f44b73d0aa7", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "b12d9f03e966b8f15a3fa098041805910158ccf466c65c88a69edb849805fd8b", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "f878dfa8472a896968be4e33893a83a094ccc79af597893f5d6267967010aad5", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "dec7ab461e21d6ee3b9992330bd5618972cab9430302725f4f717952acc57a10", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "62f7caad8677dc65e86eb57e61db8dcbf0055b45299c318b27967581a28c2106", "target_hash": "19b72afc183fca40e5e6b5f541a2e224c6cfbd633191139eea1066bd7ce70814", - "section_hashes": [ - "a81d6f2d1f2c", - "1dcc47c19524", - "0c029ffc0a87", - "489cdbb29baa", - "017aac33f0f3", - "11ad4b7fb589", - "d66fdc49bc54" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -16405,332 +7386,332 @@ "ar": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "86263c3204f1e9a56dd7b8981b2e2474baded2145e038459f06702618755fe0e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "1392b32d8644a64f4fe06426f2cb5c891241f1a4f8cfd9511d7b46d37036b368", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "ea2d2b673bfae9719f2c223c8327689d159803a9d7760dd8fee68693cce750d3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "9cd9fbb3b890b5d9ce30aa3f0119625083a694e51c153c38c3492f9eb708052d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "4f878045aad3559155c8faf808127506b07c0480ae3b55001884eef03473bdc3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "9165423a4359e8d3036e99f1f62e9b832f938cb68339abcd366dbe25bfcf251d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "64a377a18d8394ee3b1f59541465b5580af354992b026592b77c852db924729d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "905ff259866d1946dde81b6bd90d1bafb21c98c218366235c6da7b2a9bef9aca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "566c657bd9e0d61eff6595b33d408e4de3404cc3087aef5a274f0ed58d629cd8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "ee35d4e81a2d8e7814ebb5a55882448e4651e4dfb95ec577a8b902492663017a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "2d92db27a237e36a052f8e6972d5cdbb4ed636962e656a5931b0d4aef4d203fc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "46eb5d2842b7e4907c04f759549497a7206cf8cfbd0de981993c85ab191a7d8a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "408608ba198c4fbb5fe19c0911f8fe37c02ec4fbd545d6224678f33ab237bed3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "e8626b9e05ffe19858c3ef4f3580f9362e03f0e36ef7528c7206b8b4794fcb2d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "bf111135b8296c982adae9bf5ed7023535b39f5c2fb978746488d1d6fe3e1f95", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "b26a7ca397792543affce479b90e8a62e20c8148be2054d400f5140a0c8a5e6c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "538f6821f26b3112ee17f425f0df11f57374346b52dd87fc22f2ad77b55dc147", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "628da35399a6ebe72e4da5ccea7e450ac7a7a15501f59116981f22f13c1ad6f7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "dc2ac07ee05a057ba7a6eb1cc124475c4f9eb4cc67ab131d080772bd92106c73", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "0a4d7136a39e4aa577b02fc78be601c22f9c74e40d7fffe9780f423c4c7af4a2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "b3e57a2c3117e80faa52b6d220eeabd7d34db4bf763b70a0b88ba3e50012442b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "098e921fac0088ed7cf47780591846cf983991a25de87faf53e896cc18a30804", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "43a2ae2de788a3403a1558c2311b4ac547263a96a359a4caebae2e249330cf8e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "16fa77c2e4c16fa03229aec328224867eaf9459ba9b0b4f935149ffa6b22f40d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "c941a65969dbbc4d2af704ae44c87f31a9d487058fd181a646631ebb4490596b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "fa1e9a4ac89ebaa6f6c863519bb9d72ac683285fc9b88306409d155c8b9a54c2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "6fe982ef2767282892adadc4980fda79ed002a2bc5cebfdc303271e9fa8626e4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "ff1610ede8687ca1e6ac3b1d676edf8d5f5e3973b8b100290e4f9b2daa7adf04", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "f015e7b9ef03620c326d99f387f06ca5b4923b28479ea25684644b40848c130e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "10b18045cc0ffbb173a55d1e2e5b289535c47c68b17fb5c84fe0ce152db96afa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "7bc027b63529101e74b8ba294367a60ded7b48589d217ccd4ee132c1e3142d1e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "3bd2dfe382a116399945717abd51b52160e2ec97654c1c19f976311e63056647", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "0c5d4c89e91f390dc7588f10b9ab31a8ad010adcbd0842ca2e70fe86e8aca71f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "af81f9bf35f6ae7eeac28d02cf74797f1d18f9d793dea2693ab5de8b1d5adbd3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "828d6183439bf2e4defe2c21906c26a8104af46639779fb5e1260808d724792a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "316738d01a6d24ee5d071a15fd9759d601a3a98328aa8a53e80dd31d1cf9320c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "91093d5bc3b07a37118ef0eb140d6a2b02b3b03a0057a9e0eeeec8e089f7dae8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "2ec44827257e05f5ce8de8a433c97d7860a746561b34319027b1d1ce86f553f9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "59fb6c37ce2be21cdb208155d6a16be975dc5549f212ef10d5447e2cc9f37834", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "c07b2a09f2a5a0ccf5beff3e1ec11093a9aef27aa16abac97b663b7d7896c54b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "a904ab5a4db9de6c642a746eb8ae95b03262f06443711285ebce4dbc72df8d86", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "b5ab9168a08c161c6414865f3c4a940c75af13efbdabc2103c3e7adb23757175", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "e7fb428f99fcfdfc700c825f10cfcc8a671059e575d31a89b8fccd3addcab21e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "8d72930c01bb027a8d67464206118b1cb64118a2dcde645eaed4f90e00b68d8a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "11e63f96f966f44f32c2e10d9b0f8be72b14dfc8b5c0cc7851c300e793c0c98c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "20cabaa5910c9df0e7f1fccef9f223cb7eb75f11ad2781b71272d469843bde5a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "eca2d2a2d99b7da560227657a55791f1d6bfd9f9867ce990bae147760399b6fa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "4502ef85a2ba2958a6627d3f1939b5964394ef3b2c6ab866af43bd90e2ce9cc9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "0faadff53717df6f9b670ae516a47d0576f75b4cf3aee846951c774e46526f83", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "182bd6d302bf30fd3e76c402cc7a14bfa74194d9e83001db4fa2d9a512c7a28c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "252d1d193a77b4add734698d0a0633c013f8fbed4105d838fb8e307858daf062", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "841467b849d8e6d30c56df63a4966271b52157a676243f10f69b11920741456e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "95ccce48cb6abb490a6c9fadeb95d77e9defee2e54d2a21675006066ddafcb66", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "15f25eebbb3002c95fcff00dd44690689e5da601b145200098a9c6f1aa70cab8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "15451058c43a1691c09ec2b75e511c10d3fba67dc4c2c51ee6475bd6dfba7412", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "492673c1056148ffe42f37242982f7ed8e5b09717b638d3550c70cbeb82882f5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "10000439343040cc22b4f09e126c452d166915895ecb699b8f577bb42c5d54cf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "4792f46b4195aad6663a714de81926499234f02b787368099ab043362f7bada5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "82b33d3d686f7e9e7ccbeaf6b3e14fc6050c3687f3366c2e42b3045cffee92ec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "ddb7a3685717d60b135fbe1b8734451e845f2e5896e9cb7bab9d0612166adc5e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "923344d47277def93d45c550997270093f2c769323a4b24a50e15ae125928232", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "df60c4d20bb02bf85ac2c18722ffaa2ac83cfe41bb3a409731c5ba61485302ac", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "84440a872da24eb0c992af2051e819f4ce43218270179be284a8022a404354b1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "d8c017dc8c2ce3fe407f0c60078a5a188facc09715231fdcde85bb65f3099a4f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "17ae063bf81189a0e0e6100f4871d9fa7cfe7dc4d956f4d3e42096e14bc2125b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "234462bd2df075fd28c4338fa4e8c318a90428199ad8f3747b041bd6a57dd497", "target_hash": "aa72998a538a1bf477d6910e9132e325dc3f187a7056b7855080870372e5a9c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -16740,917 +7721,332 @@ "pl": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "302929df77d66358c89b5ad3910a77a70b346a18b937975c09baddacfbd25a05", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "4076476b769e6a9d8263837d9dd437ad36f82826417ba373e932675eabe9029b", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "334878cbe93ebfb8e93aaf3bc785143c298a233f9a99288e8832b394e6c71203", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "623905423e1844e9d77fdda2289411eba5aed28e8c51b51810b8247ba707fc5b", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "7edf15ebb274ef0d014a90b5688eb06954655b0a37e19a5034857f50446b6782", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "af33c092a0ccf3915958dcea4e70c8a294f08ad5ec96b56aa52166a1de5a9f35", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "0d9b533824ce28b302882cefad494d0c4b2311644150931f60d9e2294766bdd4", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "73e8b6de32dcbe6be8688a31f80bec06159bf8a51167fd2de57bfe146a54a29c", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "36b49ed1fb419d2e2adbcf391670183c1644c5678c2c0a0f39b1ac0af7242dc4", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "698a95ea86c80ad28d67cdd41f6ee937316de3895c50f4d5ca10c92ec74fe277", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "3d2c0ee48e8539278e5c4d9c871ba63ba1f950e563f5b6fa0ab3391077415fc6", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "624f7ae3caadbbd56153fcf91ceadf92e784b19f2bff2925c034893528fed852", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "18f69af651bcfe5bede9a91dc919b7b3d2aa496e2ce5b756a33efb2b4d0b15c4", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "ddf7f935883eea8a874a840417979776182ea022e95d6ad84c407b366aceae02", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "c1746de8fa0a3c028554a50bb638e4bef04eaf4857be51e1ed7f9235cac0b222", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "adc89897eabfd9a35d7b658d1478e02db5df4a89b0a2a36aa428e346eabf8352", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "548e1294ec37fc53ccbc07654d9ce939a7d954f1e9464f129606a46dcef20837", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "cf3379abb4faf758373ae5e490e89a2817353dda8fa1887f3edad83182499498", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "78ffb94f625e2facb87eccf7025390f22136631ae59c88de36223a21e9e25b97", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "aca15b38d5faf34770571dca892bc0f696163e7564c78a8d06a35864518d8606", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "afa05f66fac4fb30bf6cd2c7a16268a47e431bc85b3c29c114de8759864cb4fa", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "f9ee534bddcdf512c0d7ed6122cb9c486c43442c1d789bf5949e0ff6d7fc5e09", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "c5f1f3979b577cacfb42cb981d4a0fe3bf255a37fe6b09e5c215e2379f3eb9e9", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "c3dcb3f6ef2de3c4b9068d89491e6b5fc0c73403ee97626a069f52f1153421be", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "430886a855d676008be28f10fef7f57359ef6a87cc4a7f9b3664b99ab5a8de20", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "4ea7ef910ccfd7ce3dbe118188acd2071b3b63fcba85074ed1d2f4368899edaa", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "9ab40235e636c19d45c24b857235e8989e89d494b6eb48479cbfe0f555e6c545", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "1b4197dbe7b0b9f81ff490fe32e604ca6989ca8d7d6142745c232c49da07a9b2", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "b0c23f7d687a400cb4fbd8e003e7af136ef3c28f56f779eb46bf98c102399eaa", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "b85757fccfcb7c3bb4626965bbe7dd588f489deb4b9cad47b7032196f5f15347", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "68fa637d5948dfa04e441cda2e62d492f02c4c4c3add42cf6ccfe756e72993aa", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "e2f2984a5e8d8d31455a171b09d7ab1409cda0280fe4847e5d6f4208297fc58c", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "cb540449474ce1162e9dc08ad94d91b5d2ddb78b1697085e21afcc0bf9c1a2bd", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "834047b368eaf827faa049b4e7756d1da1a625fac5954c5e92c78a5374e765d9", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "3248e63b80d32aa0e460152233b0f2581f6e0433592362b3533a1456e5fc377e", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "737553588e0b3d63a103b40ac191155f1dd2d89f7383960bd1b5c11521426cb2", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "d7a3722e69c045c86709fa06a6f6067455a127b1b0ce1a6590881b2f7a6e1df0", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "828eafb90bd0b1e8ec883b70f838ca971349c4306b83e373560b6a24bb6d20e6", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "d5078d42d7cb898c4e9562a0795143cd913bd71beb2e9b247cf21e1d050e3363", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "9ceddf3318442121c2ea383adfbec361f4769290f4a3ee50417c91a7b75e7509", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "fb4747aa52c5527687f3079998558e5f3539ad08b6d43e53f8db1c7a23b8c937", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "90e45e82418b029acb116f17dce9333863dc8acd61102db6d6003d8bb640a880", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "d02b17c5dc90d2fa26068a2203e6daaa19922f8bb6d4ef1b5117010496c7ce8f", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "9b6283ac53d160027615ba1e7b35fcfcccd65bc256730bce72ed7baf65aa995e", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "657de4a0ab5bb6fb62e94a77f99d6437e9d798a87feffbe9ee31e99c75363fb5", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "8ff8f08c111b14a9469dc71b26331959f4e6782daa2d15e87d7ea5040be84a16", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "cc18241c67522d002655d5ead0f1e92a98759164371cc2b6ef6e3b53e65a0068", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "88ec5b536c1850aad5fe36f24af889ac13427d9c646620c8e53d2fd98cd08853", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "b3a862f98a720d275903a7caff5c4ed1ccf6dfed479d7dcb07f83d455c39a231", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "3095047b163cec795a18a5b8affadd18dea95a02eee0b04b5d1ac4866605c468", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "6e066a74e2d334a40009aa91ab616949fe3746455bca8901a8ed50cbaaa780b7", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "bfcb74df89468a14b0c4e0c7af7e97a90fe2887fcfad14af4aee9c7fd96b9e81", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "a2b8161797ef87c1c92348740e783ebe918b3e95a5227babffb375b7f77f8fb8", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "c55fc975ddd4f9ee453891fea93d42b87255e970f837a479d8dfdc8f444baff5", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "ab2106a19538cc538e7f3e30a5ebf8c474aae6c9811ddf3e4197454ea905ddc5", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "8159e4696803112972fc838b41cd863b1fc2729e6394a381f7790e4350d77070", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "d1af786c89a683a71fa2425f08287fd820e2f0ff376061f2c34b2052b1d664bc", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "709f2095f4531f9e52c3dadd15077481f36636c0b94f3396f99e979d2a595599", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "88025e06273a6d7d226b7728768b5f2aad59d699060c02363b28f26231386806", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "422b3bdd0f0cfeeb11d727a9159261b5cccb2ceb493f1390f0ac9be4700934d2", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "d76f971d8b35b4a6df02a2abacbfd4d2db6865bd2ba9c7617c25258a5ee3b242", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "ac2fdbd846edcdaec7fbbf7a924dbeed7b08e3faaf714896008417219cd1af7b", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "dc67951690dd09c38e08396e94db27f8d43224135aac4df6c1e2857478840226", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "f20b1a6bca52e952ebc25b32814eb1b71ec5717daa8a56553429888202306bf3", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "2bc3838215cc008bb32c3fb9c43316a800eaf00c4151b930daffcf55624e74eb", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "b2e7d6083e4ffc08b95a94df5bf1ed87b819b94fc8a70cc7808a87a82589d27c", "target_hash": "c8e065208bb5922d6cb22f950329ba061da00e661ec982893f78d867173b1aee", - "section_hashes": [ - "a9838c515e9a", - "8de424f867c3", - "eac91e229c1d", - "d45f93a7608e", - "efdeb73fc91e", - "68f4c0dffe08", - "cb9f2ed94589" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -17660,1307 +8056,332 @@ "pl": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "0171e8b37aa6c56fabf90eb1863359be3e1add09a309d628b65ae483fe3995df", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "7e19936a9bd0fbaa0b8421721a43735d86925a8c72addf0b52849ad4216a8bdd", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "0e9671ef83de4a00dbd4638899ab79444d267e23a477c187878a079ffa3f66b6", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "04cf9ba04de46748a45c392248028869c5ecf8cd0026a1b04cd32ddb077b16dd", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "0a96e79b2dd33e7ab19335b486ea279a0886624a2ae327a2ec27be96d46014a5", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "cc1aac3fd73c5c79f75c5596d2cd1608357abb304d91289cf2868de76c9a9098", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "0fd3294649c0751bb077ae69f62e7e908da45690a7bca01a64ae580b9255e683", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "bbb13daffcedc46763eb131aa2721460ba20b3dcdc7641fd16535978abf3ce73", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "1e8df0dc873148c15e682d4b5f2bb91f09eec4e8dbf22799db581965f6f2fa07", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "79ba098de98d47339ad6b925197d9bf5762603714cd3797d6d15e015c5245c7c", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "5d73126de38acba48e7627c4c7c3c8ca7f9cead22b29ac4e6503376f67db24dd", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "52ebd12f6537aa04bd6245d867c6ed22e1768c28f820d95433134bf6ff6b9aa6", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "c93da154a586578fe26799fbc7f2d6b25ac6fd72ef11922fb1013c95797b5f89", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "d7383b239087dfe79fcc1170c2e44422c014c28d168e58e58ec1aa9aeff6eb19", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "4579be898e7a03ab3d9a90a3ce2853a26ca0f11f5c400dd6a4bb7510c7d6735c", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "4d0815d669fb25b90a4701a2eb629f84f05c3ebb6fd0332d1a3b1a7547e1d868", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "604a21e815e08b4cc419d7b895baf93e84832e983d3a361ff90c2675de577e98", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "af3ad40d43c4b0a0d38b2f961636ff232bb64ede589483177158aa6ea2c5c4e1", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "2f3562ead6115d9c2ba3f459d8792de5e16975196e37e7b2ccee7990cb214fef", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "1246c2ca70d8055904ab6a6e848bad34875dac75d5d178d521df482cb506e3db", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "53cfa411c9630cca3a7ddd84243b74cd52e873631c439045417cca1d8ba29eb9", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "a3e40a77e36f1f00474c9e6b98b492a26288257e7ca6dd581a4d939df91eccb3", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "aa4274f63224a0b112f7b64e63c82cbe35b8e09efb094c28938750875d22b86e", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "cf46a44d03be33ec10c66479bc2f56dba9546c1739ca22a0d020bb9e2bc5b6aa", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "a72bf3d10f20f5c4d57e229d8877f29580a3e56dddbf0f75922d6b5e2b95cff0", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "4d2a3a229d20451bc1ed27e65da667ab50d3dc622a991f2d00583a62c30e15a2", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "a587ce21297432469d6fc3893f46162ea4f3efe066ad84b83390477c83cd7065", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "a5035ca8462fe7fd4d4c18406d95bec0f810e476ff0ea16ff79b3710bb17f13c", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "c60482d2c6757adf1567beefa753126e977bd372d21fdc2ff5b097db492b6cc6", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "d0e8c3eae9112a1bdb64b45e101874b050a690b23cc526f9385f896df91cab67", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "e74d350ac09989476063c74aa0587acfa9e22be4c0cbfba735f6f2187d8b39d4", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "4a7b14e6de703c80d9587ebce834c128abb85374546064f923598e8627d23f2c", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "816ad4f255d05d3b85510fc5a9327eb5364a732ce7a38ee8524d29991cfebe73", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "7487c199818d2d1195314cbbd56259198ea00ba14178c4dab7af800a75cad703", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "46b7e54ef426b95bc5ad32a0db6bfa3a4f35676bb6abceb4e93673bdc5e5b753", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "e0950629feefe24a2ef0e352f8fc1f3a23aa695a18c1c4bedce177ab76379ee4", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "14b3479c2bf2678ebdfb72c1b87988613926b74b379bbb46ff5857099585a9dd", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "8da91dd037831c7ba31ae2866a05550188b7116e2f20d966ddea6144abece2fb", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "25052dfd175cbf5362b7a5b4a5550810e351e9976c07c7d46280648851268ae3", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "946cd63be7629b2c1eaad6558b5260958c1e9e879fadf6b0cf3a954f16d26c81", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "2601f657f791feddd69d960711817134095e904cd68fe608e27160a742295868", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "785fadbfc4a87b4071d6da5679a6b8cdeeca197f2de68bf1dff4a83959d5a6bc", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "d2b539239b66836bdf9dd98eeca2b6f5835a6e4e52004c26b15f2d07742b0c54", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "1462f70b5cdc578c04a47f113b959ca979795b093348e0355992e5442c1f4f84", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "f2d8d71e9fc8104d860fff3043ef3454cfda1cbea8537a686c0d6c34b226f97a", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "3e4435ef9e1e9b7effe7281d1b618ef97c857d6249e707502987eaddfe1e7928", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "3d5138139b0033e5bd44d1bb8a5dc41298cedc9321d9249b7d37245aeec9ab91", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "08cdb3eeba3ebd01881694badbc5bdeb1ed757700f7cf0a553c54092152b1477", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "3892fcf0f12fc4686b26ccea15fa076e120483d75c797c265f4d994f2121955d", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "73105f1598aa91649afebe5ca1229b7601205e7b71a03afcbfe5b8ea7de06002", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "29fc788a3e24f553d8c0ba3f86f4f3c34061c0950b912e6ea07707d273ce0eea", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "8b5c65682de0f2394f4fb78ca9c66c3ad78bbeb3d7b873a6495d7e4d7201d91b", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "51f99ccc71f3fcb6be61bc8e139b85d6549d497b5c0960dee5007c0c41847771", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "700092103d52766059cb1e976772ce5dec67ba48ddc7ae520976acacb5217350", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "aa89abfb9eab9ef5adea36d632f792bb016ab1bd0a7b650fbc423b0db471028b", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "cc1366d9d6beaedef1d42472e82e684b055232cc07b5c1b45a8dae74115670e7", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "85dd6a8bbe8ba0275f0de521c8e58a3a9b1841a63ba903ad594fcb4a7f54a301", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "a29cfaac9ad5b5b19f2596174a3dec37cbd3b02cac49593f0e7b8fc3ffa5b35f", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "dc4e80315bdc2471ff7c547704e0c6cb3dd7219d2207e654d0f36b458e5c7679", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "86764ba003226f079cb6a4cab4c6e0f85a5b997b7a7b1a6e2c96db8c2999eb84", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "f0dbb2ab8adc45fbdbee946976147587f664abe2d877d6fbcf525e3279ccc3b1", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "9ebd89ab7aa754975fbb9d69de3e3fe279511c379033c66ca34a4da2762e3aee", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "1430db7e17d3e8313252e4f4097323ee926f9b9fe1c04f18f15b59bfaec77e9d", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "270534cb65133e211aa4396f11ce0e384284bc503d287ca6ac64e0816b4d07c9", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "704ef6d8f984f14e9e0f4c1685ec1ef103d1fcfb59504c97c0ddd1ae1be65d73", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "82f765990f98f3bca598d75452ff7a3a02a5ddfd1489a74ab9ecd475c9dbc772", "target_hash": "470a14bf0dd313382e889a96a8a2e51d570a66fcfaef0f5a36e7ed4677f3354d", - "section_hashes": [ - "a4244e3cec8d", - "e700298ad701", - "666502f6ded1", - "4dbb8852581b", - "a4d18c8a1a90", - "530eef245260", - "a5ed3787bf55", - "182ba9ce210a", - "034af5892acd", - "28376c762f53", - "906f14119b75", - "7e65b884f0c8", - "dca529342543" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -18970,794 +8391,332 @@ "pl": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "528274ed08f8d921a1b14b219f43904a467eac784fa73dff3ade617eb967b7d3", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "d256e4c47c3f1e41262076697ba62133e48448fc1f9050aa7f137bd8bbfa6b10", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "edffca9f294b3792b9a0054b6138a30f3f4e36bdf850fd7bf15939fe59a834c9", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "893584e6eb22f5b301ddfe7b0117266e5b8ec9c072efa47b29acccb047bcf654", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "e8e746ee85670858e8e16f844d165b0e0bbd1461cf0ec2546e4cf406cdb6f739", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "3dd818888c37b9db9639eb76721b96e9df6d610c019758adc9fc09e9e9596414", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "321f010c694f459f883183121525592d9bff8d09e227138ef665b81dc74770c7", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "83eabbb9286c8d8e45ebd5a89703b08837c118a200fb1a6aac257065ef69c79d", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "75431342f8272480777330fb44342ae044cc0fa94cab6164d7322a2f34e4fdc3", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "2af916c222906d6f2573fcda8c68975bcac503de6e6330f1e579163e88084c54", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "1f3ef68785fbd8a22e9474b0d7cb8b3eb1979b01d60cef883f913c4391cc0b5d", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "b53b2678f206f9b629b367e76ff9c0100f255a929264bdcacc0472a4b52e7a65", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "bdefaecc226fdd9c80b414226bda184bfbd3833ae679e7417ba7f4cd5f71cbe3", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "fb5999293a10975445dd7da89516d8f0651eb2eca0b7c21314e4b9b730218591", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "fcd83299b9318af8ee4460d69b10de0e621d42d638548490a495e1e85657956c", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "a0c3e1c3ec582db4933dea61d6d476351b5bf3dd47887a7698fd87879a0dec49", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "2f188d0c1a5af985881bd7bde06219a9fcb5ce70fb2e94c3aecad0ea3a89ec0e", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "8bcc7a82a0cd8dadbddef1565979f2420769a4f32031ebcc0a1b980e31c86a5e", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "85515686597c939719a12c6d3365925d5420b7d9f126ef903ccda21ff565e6c3", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "e5e748c77ee8ac0bc22f0483ceffb6c9e03a14c712e4e067c438bbdbf268a7ea", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "97c17bb0ad08e592c2afd672a1fa3d54ef04b646d290a967e2090e9b66829b89", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "0705a70e3b096c1aa583e3a4d5c8def146c7de12eb0c6535e587c714f33593c7", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "8da2b5cddb95fc4df430836722e1c382f71a373ccdcfbe00864acec9038aac1b", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "1328586221bc7736ed817efc91ee4bc809a623440f6299676c9a64192265cc0b", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "137f5e399b6d7d2a0ed442cd5782ae6a5485f2830434c8f10e2ed3a31f6eb378", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "407ae05274eefbc013f8fc837123ff5b3e73350a765099237304662c28881723", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "81828d34e0dda66e55eb8f2da50f0d9587e4aa93b5b143498ca4121a92198cd5", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "ea1fb6f6c9a204d76d5d068f6e1315bf6573c6ae60ef216a29132e732929d696", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "1eb7a06b2541dc7d8a07f22d485c6e63123f6c39b9839efeace4ab8de9921725", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "13cc4f12467b9ac922dc78b2a181dd6483b48505a47bc30da05ab0117f8bb1ab", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "3df6457224f32ee6058617cd4a235f7fdc3bcf2ff3392ad395bf3afe21dc5992", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "04c787245b90ee4d48704d13c52016a6f0f97c3b67e7ccc85919d2558a4f1f85", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "f2f651333fa135e8884da5d4db7e9dfce4aa27041340d89396ae453d41b3f479", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "9ae72db92c5eaa0973891f961efc7a66faa92981264221f91ed5d3736927ad33", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "b838a73977a5354af76b41cf4b55ffd1838dcb8ac170ed6e6eb13caa456b3547", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "bdd37538046b2134bbaebcc893131c442ce0b99998c7c6902a65e2fcdb9a1a05", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "2b430a69e32dc8216b8ebaed98af9eb66113300bd8791eae75c11540cf08c91f", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "5abdd06db3526286b72c8d95f770d0567841d4acc7996c99df54b9d72ff5353d", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "683323ac2e253e4aa02d2a5bd625fef04395cf7d04ff648d384e25207380dc64", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "a4ccb6e46396e610ca49dd4d805165f8f7e131f25b6f539330347119647b3197", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "604b8abb6883d6afadccdeba2adba509986401c5b04d42d13c3a22d5b08d91cf", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "85ee5b162a0878bca6866b0a8b373e9b40e0694c59aeb29267f5d1c1857fed33", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "151c0ed54944b8438f2aaa6bd0c99f113030cc412125de661becde770796b622", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "ae0f37ca0ad42166c6301acd8dcf2a65e24bff8a350610f72086de5937f4cac3", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "138c6de591486430074ea33ae111b8ef9619d398a5d6167a74cc86bb18dab656", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "62800b832c7634651c268f937e306aac9da41f2b562526a622c05c9f71076a9b", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "77dc5946f66296b5505732eca7d9582ea370a4e550db3852a79372aad9a9f6c9", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "3f43b9dc95ddc6622864933f73631b08ae67b3eec188f69a5a2a12263bee61e1", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "3fd5b5494bc442559a2c7161ca7369331fd221fbf6ee2b4daae3c2524dcca016", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "99a26642bf7264f7fe43b8c28715a1f3a4210d80ed209f0e374df101856965de", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "1f4488ff81749b98477bba0d264e8cdcdc53b43f5fdbb306b8291ea3ec6fad21", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "164fd27c77d91bd7eca845fb574125e86721e75da54132ce5ac8911ee5f302b3", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "549d7cd78570f7d5779b64d1e312d6c8bd2edaba91f781750846ff63a5a26095", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "6773de3a7b8d4343bed311cf1619d847220f6cb850de5738a9709f11313321a6", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "0085edcfb761b396af326bf9b2032e46d88621f2bd8071787b06aba132f84ac2", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "2265e0eff5a1852c7ddad220660ddde7a396633c55db8a3cc2888e5f5d8d999a", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "ed78cb72b3b841d473ef7e7ccd14298ec3f425be10b0ae137b390d93a9b7a846", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "be1567c71b608c27905e9ffbe8b98642008dffd3848d46cbd5c0b0e8d4bf0a04", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "82508a6e6f4b03d465b321314e476bd0b28cde1d305c3b6561025c5e8ec942ca", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "e5c12cd758472c684bb60c0f16b28304538673de0066eec387d7f1061d4f0ccc", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "2be203f8b2ae734618149634fda6687d1c2b46a1fb95b057411b7429f88857ba", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "1eae01665bfea2272eb41e967a4224a20d3bacd8fe4d996f26811404afbfac6f", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "2344f04fc96c429632234ce23add15f0351fcb7b3761a077282b831fe40f852b", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "4bf3043b0889168b19acc9ae29449db7596aadf71355373d6e550fa3261d6fc5", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "33d5e7e669137aee72ae6d79aed6ccc5a3cf88acaaf1944e9cdb2ee9008f54d4", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "aae288a51e3df2b8a5386bb53711148e0e8783dfdf93ce4e105c87b282e10bc8", "target_hash": "d6e500b02776e5c1027cc381c544e48e155a22b8bceaaf635f2bea6dd9f4ea50", - "section_hashes": [ - "6db75df28d5e", - "a1029754a9ec", - "045a33232625", - "75f2e38ee4db", - "8e693a8a3c94" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -19767,2278 +8726,667 @@ "pl": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "e94a33ae2a9cbd9bba0e7e19964d719f4e1c420ad5c7c2f78c6e432975f754d6", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "1facc6b15fedf1ec91b1287bd6e434891b15b87bedb38f6770d824791df26395", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "06971eb7c477a7a0247c5509e34b50f6a20dd11dea972efc4f45efe1cc71777e", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "8d5109c495d9ec8ffd820ab97070c9609dc3bbe71ac96e6f7694f2cdc5ecf560", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "f046a486d2aec7ce3920322fbc54a65ef467b708c4ab749728017f9ecc7eebab", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "7b0345c35757fbba3eba553df7d413761fa8b0c9daeb2ae99e8b2611d1224a78", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "0ed10f99c0a169dd4d9857d7c91baf96bf678994a6b32538d0aec3975b7935b6", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "0f2025d27c0579509b17cc32c60c224af59837082ab7d1c3eef83d44a6e666e0", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "b16e957853b9acacf847832606a81c01086f3b0eb79788442384e97e67a3f629", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "1c6cfcd2b432441a40d0ab687e1ac66a8e784d88213452641fea9716ee1bc11c", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "63fbadb697ddeb5e15c91c7f7f6f2f58a95374a1d820be7cf0fb9a395afeaf3b", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "73756f5d9427f8d79b7a5caec03e83eef413c0a6b43003d696865ef01c056633", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "7af523881933767208575c8cd763319c4740a5cef9e1a1eac3b7c4f60f6e4d9e", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "ab8ef01f53f29b96756d333ba6580356f31a083b2fa2e354c7a7e2ae1ac04de6", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "719a38f4b19b39621a76aaaa2e5323a5bb4c8c62a9038e1ca1bad750e132b3e9", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "896df5a03ed67f32daf1eb3638153fec78ee4e8888e9074cdf245d706e498fee", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "3ec044e383d524943c964e0ae1719824670d826b7112fe9e8fc417b44fe1118d", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "ae7704938557941fab439aadd58744c63c74fb646a05e02a79a783e8a1a86c1f", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "b2108bc2ee35398ac571cbaad6f39428ac2a6e8f7fb73a043f89b7ce859870c6", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "1a0d3f43cd9572ff80b50c3d997d1d168adc20a7a9d2915345be01776805ce41", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "9d1857261f319fd686f74af93bb81a94ea865bfcce90f0c7cf035a6345e493b4", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "a2d271db87e7e5cbe6f2992df0f7639f92e0ba31902816a38325cc292e84a1c5", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "2dedf85fda2116205099650bb6970aaa576555e904bc376029a7e970d4454888", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "f1c5bb6fca8025734828c2f6771832dc1aaf18115897ba0eddfc1cbba1b3668d", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "65713de074f8772172b49c7d04164037ae8271736f3fae080df4dee28ffd335c", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "81d4847fa00608fee62e1091e265396c80ce450e4d3380cfc0e1a471c359b5f0", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "a4965e0f82e06105bd7a0e800d73014d3dc1f0e249299321d3f471e8a7ca6139", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "7201708e45dc94ffc318aa6e3ec97b142ec08a3f3a97d883372cabeca7658627", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "b83cf9ec684c31fe27e6ea85ee43dcf6588f3df81c6b1f579831b22d9c4f1898", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "413788b92e2491e2c18bf61d557a82cdb3b1a7e7501e6f861e5fbe44006c04e1", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "77ea64e3414420742890d8dcc91d8729b428eea376f467eeda8f299590d8ae09", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "29d7c5939bd66b278f3878d8580c39140721083fd2910c9046cb98c79b1a3fea", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "db8c4233b6858c9aedf233733312416d5bf9f8c84516409a505a73e77d891790", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "6231299e58a2a5eab0e2eb20232f19fd62c7faa02b3014c73f119ed0324d95cb", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "f7eeec30040c2bb3b03161e4a66737c0ee36d349e62a3987114af6d22543bb68", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "2af01a3d0eee22de6e6f4e02d64a68250cadd937b01ec08bf3a93a7b3132f1bd", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "491952c7f2ac0283d141888c41d0b15db66453707f0a695c0892abea7d0ca75c", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "5676dbebf0d2c297a207bcef9e7a94dede383d311db14a2927dd10bf2d498326", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "5daab4c5ef5e819d6e14345311064754ce4b27ae6e16337a948735b831ddb9c2", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "7c8f573f73dee1ebc1547b68efad378e83c9dc0fe89012edc911a875f25eb351", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "5e40ca72dc578aa87e402993d8d9cc93cd67c13c7a157ab2d0ef39733b9d1b32", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "94348a31c468aa40f4717f633fa37dedbf0d6d5cd8c68066261ca917717d8aa5", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "2f9221bb729bd3fc5e0d8ee348daf3e3cd3b34046239f3c7e2bb1e2159c64e8f", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "8b36869002f91de5c8b613b6167ae08c1270e3a73a5bad8540e5960ef6a645cb", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "98675b6fca8050f3aeafa9555ac73c1d3683968e6a4e18ca0f85b77c2993fde7", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "0f3b8635e829622f845ee014417d4371f10d81a8c65380fc399c3d6da5fa710e", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "5f9914696e24b6f43331b39c9d29b5ba7fa3e31c9b411ca331bf09a7f9340d7e", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "bac9efdbeadf32dbf1f43fd4fc7bcc6495b40fb881334379abb50af3ff507c99", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "924bad8c7fce68817a03a8bcdaec525193cc30a7b0cecbaae3b0a4ed30a03845", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "701f34df975421aa2120f417854f2fbd63a277035e3c1986e19662b04963f52d", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "2f27022350ac052d75b886e84dd18cbcaf4f7f54e764a030d95a72a2b46516b3", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "b6c8e84dafac674466dc91826f42dab3e427595bd74e9065cec800b2c6d37f00", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "07f92674a315be569d69b11e22711b3cf13a876e52b9b90a0fb11405c7c3cc0f", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "1f7c6eb19a174442a603cef609eb556ba9b565ce7719deaa3b396b0d616ba6d4", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "dde16e33a3bcaa335abd6b74de63e140132e8ca9c8c98a4ec0ed3f84d1b236aa", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "450df3657d7f0a18c1fb5e82dae9fed7ee19622c53510b541b30324c35ecebb1", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "723226231ff42584b72972c874e9e71f646d0d32b7c5d309f45ec52a6db04c3a", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "8a9918de1892cd975e63d61f10c79b78ce7d519ce5e068bfad23a0666ffe0880", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "ce268065eefd76ed06716c228b1649d82b6d0b7cbe268daaad70112cde5841bc", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "e3abc5dd037b1eaebb388a03ce6648370cead149da2dfdf26f6aae5830c399ab", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "b90be4b19328d68ba560fa017a4d7c5374f0bbfb79e5007810d7fce44a96ea26", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "ac08d4ddbde1fb0d16030c9296ed0814f506ff2a8066b7983c3a3bb5b2d1bfd9", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "0ce217271087a2b4da1ad66be5e0ba36aa35facf81c3522ea758b7ae6d509dfc", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "ed3b4d1b728cdbf48628be88948e0ce366b0f7f579f03241f4b61f4c13dae8d7", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "d69bf6bbc827c216ea911b87a275c48b828eac1d8e837133fd8eb7ca6f2938dc", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "2ec77520a2d622acea9be604f49442b4d9c4e92f9b8bc53f96c7c6e0c0f255e5", "target_hash": "199371e90f57aea9ba8d8d8e1e808e1995887460d7701e5edafcc25e81093c25", - "section_hashes": [ - "573a00efb6a5", - "dcec95866244", - "4e2915420b33", - "7da86c3386f4", - "7460dbbc9931", - "3e11c982a2a1", - "c91b518872a9", - "55a3395a1a29", - "0c3e7aff1fe7", - "f5ef824d49d8", - "47905066845f", - "15df429fe922", - "5b792221aa25", - "c320591509c9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/compression/COMPRESSION_GUIDE.md": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", "locales": { "pl": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "2da69a52a9fd95003370c1c91fb87a35963b76325ccd7246b5e3f5a17ce9736f", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "38232421bba900b5a3b7d25af687aa16f480b6bb8173223da5a2605125868dc5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "98d496235dbace9a5d623bedeb34c943c523291d3c12e0c0d605f6d959fc3d84", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "8ccffbe79875546372bdb5e1a8075e961cb8edf127ee5645025bcf5c55b56e34", + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "966e367f5cc6762a39859cff8da26ea4ac5308d0bc0889e06be4302786caf065", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "9015bde5d9f3cd968bd3963b7a11cb3776b9e60d040ea941d5ac1526b173bc45", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "eabf5f9c7ff04da8abfa1534d24742e534bfd1b3075352b05d6c1b459ab914f4", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "ef6cb47e1cec9253f6e7eb6eccaac85682ee23f0128aa8184476937822f23472", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "1ae0ec89342f0e110d7c5568b39b53548cfb6966d6895550a1937de0e101bcd3", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "95876ba4934a05b01a29d5f955bfbac99081b3ff62ba5ad5bd4477b27233baca", + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "3b0f8ce326afef0560a4e1b1e6c79ff06b4f9425ba159810025b73a453668068", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "d23b30ac58c62a0292dacdef87ec0a11322c734a233696372bac453d49e30e57", + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "89dd25e9cf90aef233d6bb67850bc29ba31477949e68d0d4086175aede9ec22f", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "96a4df0b3ad75026f1fb7596c0dc5d7423a825f34c3cd6509d80cdb3b00e2f9f", + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "2a266db13ba53851893bd83a556335fc93161f38e3b4a3257bd84aeb4499bb64", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "3a52bf62ad9fdf65d240100feb7f3bbd4f09c3f7b9106fa339f5d225adca172c", + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "4e323128cf2ea2f61096d4781a88a91296cf34bfe84c32611bede3da41ad5ffd", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "e9707e374eadf2c04f6a6dbf905ad2dd0ceff1ca716bba58c25dac55cb7334ac", + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "6a82b6b82eac5e6e56a5bd66bede35371369b86795d4beb35df61faebeb07615", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "a67626e8c53676cf3aa23457d8340278cfce6123855eb35dafbfb92716d8392f", + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "595527c9ea087598f7ba3c94e55ed571886b59a332f661bf57a46e9b2a0cda73", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "9581c9d9e3d12b197f068e93bed404dd6ea14ab81de1a8f33fee8b0a9cdb48a1", + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "d1267a87248771d082ee66126c2fe814f9200f8d1784e3e7412cc052f82a6335", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "6a4aeef1dc2369e2a924e67e6a993df220e966d4c3c593a8c43ab5721b1b5e3c", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "0e9e4cd1905355aedca5fc9ce41171a1d71c6d2a1c69338618af725afb5e6063", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "45db1cd2da2b7442d833466f76e56baba3675fdfe0fd3a55920af8461cec7c3f", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "2a38fdbf51210a80e3437ab1be12488e0c2bc9d32a8893b63cd6bbf0944c646c", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "39d5de5135844604910703495e02ecdd080c6509dd153db202ad3de738097668", + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "90878c3bb7068929027cdfaf456196a44d8e537a3cec1d3044bcb81b9e8fe128", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "06053d2972259e207cf16b3535318d837978f0ac221c74b3bc074fd7a5465088", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "7d86f26aabf329dcee33cff8aed016838a877d5c8213dc7b428e9f260e4c84b2", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "9c1b60df909ca0e3d1e7c5fc631444b718b9054b02bf5e198122934a6ecd6f1e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "f61ae66c57356a6cffc7527a5ab2b0c7f2e661c7fb3d2cf33b9f23bc6d7a9165", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "a7edc419ce13b24a22bef50c31b666407fe7ab607479a14ffc48702eeebd2c30", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "73908a2ad282464be6770a6479182d3bdcc9f304c83a984fa145598904805123", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "b2276ca6eb0cf1eab0c6f263efead6817d1c803a37c393eb59190eb3b0693551", + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "afc122441fca7e08128702cc4a4356fe7120207da28d2dfcc1ea2e99d1c85eaa", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "2f4f9ec3be89c240a6026b595d53a3d04f70d6a694f7e2e54e285a32e454d7e3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "d55137fa819d5ef169c75db4f7b571c33a7221eaf65ad4c2d2e26f1180c34c45", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "eb5fecda1132fe3ccbdf737bcbdcb9f513e95fe2e594ef6bc8b1fbdbcd3dc6fd", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "13a05852018f0f06d9cea6bab10b8dc6a97a191fdcf8869a8d412dd98b41434b", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "a408c67b8cb202e62003f6ef63831202bf26df622de4f2b63da08d152e8fc213", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "c9eaab73a7ac7bc33ebfe52376e298789da3076f26d531cf9acf401d32b04466", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "f45cd1b07e44e0403ac5dbbbfde65c5b0b2c24b370085f9ff18b7669cb6295f6", + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "856b50530a9eff3402051063f38e602d0f716afc797c68f58157f0f007af1c07", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "6c08f9de035152e2923048685492d045d41b124b54800a52b2cbde7782a1a7f6", + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "0099897eb4ebbab1ab868b3b4d907a50e1367f108bfa53088bccaea1f78bec81", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "fa419520784286ac3b940393363917d62478df940906d96cc1ef91c674c8fb6e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "7825a6f74fa40448b1f9b134e989c636294afc85227fb0af8c37dad341538651", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "bd2a554066492249d2e4baa91a3c7ee5c6c000c136e18264316536f54dba9878", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "4d907e30f1d35b16befc322b0f0875b1882b7b68a83cf2e55d29abab62a8bf1c", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "033ba699f43c2d8399de7ff7c5740ec06ce64f9f2e47644a90888f91b4501c4b", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "7c054ab0ad72c8a253c117617fd65cc328b68a59faa7a3121ed28cbe71abe46e", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "032d919cbb17669190f1cb59815b904a9c5b399d9fd04789c7f60f3397b26ab8", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "e3163d07249e0971d34350bf249082b36d8d58b7c32e619fd7bb8feeefb4db63", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "1ea78311c09442f15bf5be380f153d6bf30cb9ac48664a881f8e7c8a0301e59e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "d06425fec9f5f9178548239d6b90099a7ae995426e30604befa0bf3adfb3abff", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "ff79392eec331ee8df4c4cdbde736480a3a412aa017234b5cbc609c62db109a9", + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "810d440be3b5b245ae402fca72735d0f684556767c6a505e1e1af2bbbbc27ea4", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "6210e841d96c1a0b51c08262b9d6354264ed4b9e43323b5d89454003b8fc4028", + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "9cd090a76eb34acb7808bec20d8b4c0c55e3f45156d2cebf7ff9d7b40b9005ee", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "f74feaa834fea25042b654a15dbdb7771fc5555e5399b15c7ed8df7afeaef9c7", + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "ec260a2aaa87aba13a856ef73f33be10fb92f4146f1aaf220c5cae98fe353607", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "c32cb136e6bb58096082ad1b0d3d7dfa275c5ed7d5c7bb8668f1bd0e56add19e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "504094f702db584042a4ddfef5c056b36aba70c01288fbc7aac4a660707e076e", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "34185989407a090c13172c74e75f592a26f9ffba5689c457a6ddda38320cf2a1", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "45ec1884b59380b9de23a2ce2a68d78ad69fe681164efc2df757cd70b351184b", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "e4448893833bab9f6f34f8614ecbf97c038073a5f05399a944bd3577417b2c7d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "6ad455593c78dce56f302e35b175dcb59a9e812f6a3e51e01c4212b6543c728d", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "728d2f5b9e07d622d1d4f50b416d5d0d7a2a1f985231e697080214d7b6546a69", + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "1a79ad5e98088d8962d51c22640758af0cc28b2c269bbef587d8fb182d623d1f", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "123d4f7d30e334b7341e51e7b4ea74ab463e47d1d17363fbf47f8df58c21123c", + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "f5471ce13309f6305ce828592fa38bfbb94d9c886418b42c86e40ab3f88bef46", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "d1bb954e316f5784332c0fc4c64df7416db88793d08c3fc07a027b85c6abc5c3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "acb0b747f209ec2d40854ebe46a51ccd5c581b0b32e7d5cfc0dc7128e24da172", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "acb91d62b5629fe782eba1b1cfdaedd1d01f0307c24473ec537a56ddfd6b9d4b", + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "7b744840174302c17dd6930bd9a546a3fa3ead5744e763141cced420b96d4d48", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "4df49edb6382d4df78a1b81e9e3488d3fed08f4f721056156f0c19c3e9283546", + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "841465ff63c3bf08cc21cbbc7391b09e9924e18d1ee79279d9d46b8d78642b58", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "88c54a466f1620b47440ebeb6df25ff9606069394de5bdf268c2850bfe46d553", + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "0993706f4905fa2fcf77ca4ce9358c6910a0a3abb6d69537ac7de37fb448fbf0", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "9e8dd1b72b328e6e4a521a9c86875692a641779c4f8397943a832c05f53db46d", + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "bbc30925713f65c715b591d788c18c2fbc07633b9ee9e117604b135f9c02df0b", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "6d02ae794678808b94e54e59e521255ccc17abc4420d993bf072af60e652f176", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "6bf0cf7d62b602a8e1b4a3b3966d888e1837e33b46c9bdf9d24f6191911d3eb2", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "d20d8209b3db47bdf0f37463c0d1929e09b9f68357b796915d498589204f1116", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "720e1143d5df98b6591efb115ffef09e1f041ac8385a60fa589d18e2042473c6", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "fcb7012dab1467d24bda248527afe884ab623e081c01d2e1470f50150cc76d9e", + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "c627b03e3182e20678553b2dc43e47da53f1674f6ff155fbdb92733c28d965a0", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "40f9e54f1d2dced7ecd88269bc27aa3cc535f3818f3f400ac967987e2078aed2", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "7aea21a6edafe7060ae87dc0e8c0be962d34274a30eb6bb3958efc97a0cc3f59", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "28aec8156975e24b42ce7c0b351f4a3d5f6ee79b1e3fd31bcdeacd3ca12e2506", + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "c6548edefe8e208f7c15637b43e70fa4b62f55e9910e6a5770a5baa16bb69ccb", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "eaa1a140fef56cb91d07ce5fcc7c69d14fdc580fc6c5c069f41e2a1e1b58e2f3", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "0a8080d27a12bc46f21966c7024bf2c315b037e9adf6e1d6b0dec461a309c710", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "aa04159dac9fec12f2768abae98d024dcb9afd65bdf3e08e7dfc7e20193f6c7f", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "815a7583f4779332be225739ba066d5fd2e2bfe4a031efc0d5c8fa6aa71bc6be", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "397c60e797d96467c0eb3263193da07cfe8b5026e00fd7d0553cd773d6098fa5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "b916eaa11bebfcf31e8cd1059c52054bf4231469cc4231d0aa9deae501b390e2", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "d793a0aeb51a8d1b49ef85a62c66592ee87e66d1dd41127bcde9a6561dbdb407", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "8b35c094d1a06093a67b5318ba5c219d1cad3f9a3f3895046d204bcd2a71514e", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "47f979135a44c90a0f35a53ace8b3277dbb604e2b7e0d2ef30d7df5c795d9590", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "ce6810118fc7fd9abac501fc562b83bff621798119f0e72c4ace15915c2a1887", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "248130e58bb3ce0a1803a63bfd075e708e1d16c52be1262dfa1eea80e1021a31", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "1e60c9a1443d0a8040fcb2d31ece0deaf5dda6e2bdaa9552897121766d32fe0c", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "48d8a197777101fdb087bf89cfc18e2ce6cfdb7c299737149e2e92605341fc3a", + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "fdd146d622c5e05c08d5ab245d59cf30df45b55ee9bf07983d0d53299a52b5ba", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "0373b57d1304fcbc9cc358b5d16c9b4eea6fbe425a2f4eeb525b7ba3668e2b89", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "0b4ce0769c59adfcd6be3be4fbbd6dc640ce785a7a32f6a3c0f7e4ba100d20d6", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "081164660497b481b64cc85f87f7b1f9ada7952796a99fe202e3d6063ce4fef9", + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "e258a016ecd35447eef4fbffd179e91fba4c7764c93d863ff180137fbe5a4938", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "6ea224be5d2d6b78a9a13b1e349d15a84e87f5bf5ac40685a1f1ab40dcd76da0", + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "3a1c400a61ce30adeaea4f5f95515bee0868362f9567e3ec9328c67b871ae55b", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "7afd343e62280db0ed42a73d673912bb09c2cc36271f6444a2c99fa05ff3a94b", + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "53d915497f6a30310165f223a71a0e4f6dfe413293e5a8ce71d02369ee89ebbb", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "dec8235b1d081d4353a91366e491a185df339ef5ff6bca4f1d390aea7c76cd40", + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "2ee5adce4fb531eeaabaeb6c88326181d554f1a42656980ca1fade09d3dd6dbe", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "4c2be4db152f5e0ca54349bcb555ea5ba38bd39e46d987953cd5eb0a9624d471", + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "1bb9a0965d3e4ba816c576e8d347ac0c3cb224ea372b47732836b90a3a2b26de", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "90b1329bad30c031298711cc8834c954636214758b824f1ec856a956cb246d4a", + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "e259c6d9c872a1e4ed929ff5767b4eda2137fcf744a52ee41f64ccd2b4282b75", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "522854f164ca9d62b34f545753354742414d3f9d030bd1b227ddaff72c842c64", + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "b4d058667db9d553ee9b7c1fa95cfb69b57190743f336847d042d76e37129303", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "33565b5e37f66f3c8b7f8587e5cf610481de0e6c43e21dddd41b73aeed15a0d6", + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "fc24570a10992b52dab9d3d907a4593f2349b0dc1a4841ac18c687bedbebf5c9", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "843eb55289e751ad28bc8e386ad3fd18d607ebf9546faae41d3107b55c3e70c5", + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "faebb6a75e375b6f5bc96a5abcd384287cffd73e9fddce223bd24c6aff788929", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "8e6a9871565df15889a9541a639bf49abbe813cc28ffd9ce09e81a5f8c6c4134", + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "f9bdb2ff3d2207195878db3959665cbbf8a0bb729ac5420aa11e0a0e0171b974", - "section_hashes": [ - "68f6f35598a6", - "31c9b00969eb", - "6eab12a7dc38", - "1facc6645bd4", - "4bca064a69ee", - "1d2d4f29a188", - "c5f7d2640ed6", - "808837d85452", - "af1f5183cbc7", - "3e035c670f59", - "bbef3f889da1", - "f8488ea7e911", - "1f90083224fc" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "4e75915e1f5aa8282d8a3fe6871eaa218ba37322e3ac2ba0897fad1074b41186", + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "b95ed090c47a7f8ac93432ed9fcb6f9eb55a6850fa2d05379ecf16c137aca17f", - "target_hash": "9171936199eeb9e4b533748f18658267ec17483b5ed537740177101d119e681e", - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "fbc343d6b8eb2afc5a71db203bad9c5caebfb7431ac0ac2284852fafb88b18e6", + "target_hash": "583d0cfcf6f098ebc4d7032bedaf85a64db15f76ce719016550296d9b06cf406", + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -22048,982 +9396,332 @@ "pl": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "6890e165fef9642d07a0539290c37c367e8791754fef478abaa991d5e8413d46", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "d5db3b94332bd72803b0ac1eec157fa08f7b099401351acc2e3e7ee5e314f240", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "17d19ddb71678d51c512c50f3955033173bf7fb993f3bac1e01923cde185cb65", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "b41b46991cf5cf477ef9c38b55548b3b23356691468c50d72a5022025ee45a21", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "cba314aaa03909e299cd78759805769c460f243c7f8e679510743df88eaaaf1d", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "ade19e20928f41b648ea34e321bc7b56e4fec4ed8c87686e45b6786f593c2adc", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "97181212b9d5a622ecc7a6b3baad98f95f3d1225e861908da8be0a2522a1249b", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "7038486598031ab5c5f48958d5d03fcb931d74e490bcb37a255c045798237f76", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "74c9d9d36fde1083d32ccfed43844e4913b8d0e3b99eeb5c4719d66be39cc5d9", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "ea74e6de675f4ae59a5f3cc9d4614aec789b91cd598959b1b0bb35bc3aea132f", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "68b3609ff9c57c0b3bd0d48986fe6c35f092af14390290f1c297d927e3408ac4", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "4da8b96f8d51346287036190ba5a1a117d6be823bda97d34acbc28ad9d5a6ffb", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "fd2b44d2224ec44824fa3fa93d5c8ec305ff16ef8aca26f971473bbfbbb41df7", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "8758468412bd547fe779d5f38302a454d95e4e6b80e759079ef0e2392df1e455", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "e77e67caab16d89159b0436af07d06ffe5ade38d6fcde4ae69cd1b76d47092a7", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "8781222cb9a719ff3b3ff6851a6b26a0052d4e88ca35fafbb62fc424ab899cff", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "ebfca478607dd3bc947d6b2a0d1b93e81bb07a97dcae875760884d95006fa5f1", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "0f8de286716ce39c676c7fdb3eba7e67340666de77ec6121b2d05fdeaadf2345", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "c2a49e5319607c5f9fc5ad5e0ed5e314a0a01ce1741dd0a4a7adbd2171c02bfb", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "7dce4e68a45b441882dec0c98e9249e30ac45e5a8b4e7c2220831cd7a6403aa8", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "7f73c4e630452ee5feb15e3d53d8e5a891f46770ba109dae18393874f0e9fe5b", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "9db33da5c65b560fec1644bdcae50d8a7ba71165561922381ec2739b77c61449", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "722df1f41a3eb2248935cdc56b4345509088bdc3dcccd9d4725486ff1eca3c3c", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "f0ed129c8f2a2413d515427b34f6c0e04cc2127308266802f5925b84941c122d", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "60b7e7f42a3ce5f14905893b32ce58136f141932991d30a36e0aa5d3aa51ab95", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "448df4053c0828601391d229468f3f484073758cffa5628b3daedf7621e7b2e2", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "f8ff67e06d71044f7d18631934ad24f103fa61ea9e7b055e392c1146d1cb04d9", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "33fb4bb903c38255556ff5bc3b9dc037f446babb86d2c8dc80dd757d21cf6011", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "2bd43e27c435c4a0fb329a73e8967a7f3e2745ed71c8777594671c0e2f4c71f5", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "33963d8affc8a1afc224c16bab81ed152cabb96538c2ab045abced66fd335782", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "c8ce1f8cb08a112ec6ad224fb8e0885e88cfe4f6a24b3626455fe92406b37729", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "2f4beb6c5bd6d4f60cfcbffb23a4fb4ae4cd6d1d8bd7d90b9dadd00687f83c9d", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "80c2d02f1cd72b2f53015acacdd69409ecf2f9c1a0f66db8dd8f80b5b75c5c5f", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "3b01702fc929ebfd9f979739d3240219459a3e1e9ad599c56169ef0de2a7ff55", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "cc207399528b5cb9829126f556d5e2fd73802ea9f26744a5cfd3b44ead60156f", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "bb3e4017538d81d4ee4affde8ba6892c3b3f8501964690c4a740720ce7214a93", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "47e1ad6d468091315c826b6dc79edd042c9b51f408ec8a6ea2f896e05edebc34", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "d6cc90b17c9f10b8b8bf170e31d6ada41c828da01257592781de74722a1fa2e9", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "36bb17f2c440ddf74eb6245d511fd1b52f51be38f148df9214fa1e26510129e1", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "aaed58d7ec73034d703e00335a54aac7f91ecba58d08f726850905d44c5eef0c", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "d30b160d50ceb34419aee919093147cf61c40d14c92d8b0516eddaad6495acbd", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "a0c1200516ad594c48a6008d140f9a71611805cb1d77f356f8f7f35e0e813a3c", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "5a05085e27fd97c334b05595d5044500399810bba84bfa8e0bb6b51a572f7ebe", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "6a3bb13aa8045e47e2fe858e8d40c82cff2768e208027b9cb471e00f6071ff0b", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "03468a04f5a10e0a7466578ec03053a28a9b62dd7677d9d1693521832e292502", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "63fd390e4719be7349baeadae94af741e9548d40f5dbd8924c7880b106a045e7", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "7355d8dd2d8e57fc3d8ef7ed7bd040ed54b68c994c4001e12a8e921644c38915", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "7edd80f0035f6f6a911c6347a1d89b2179c286a678ec34500e8c8e957256f7ef", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "5cdf1fbf9ac7658303b1e3b971f2afadf55885c86d9c4542a7a6037e23cd7168", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "cab56311055347ba1003129b39456ce2b1921d5d09e9bc6140a0e909d5d96444", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "07ea67e8e3bfc5b1ab9b3171bd344315334cb351bbfb6e4e1bd150a252ac62a5", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "f68d980caeb0f1c7439a308a748eeca755e6fcf02cbf096ed8734cf6a60ec61b", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "3da39bc704dcf770a9006ab75543e1f4d8b4a72608ead7ada9413f6623d10712", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "6166416d2256e31c97504827aedb0768c714fdae9887c41d184aefd698191482", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "32f24f5247138ec47ca6453389eb7b2d8f5f5bc0eb804119eace71cbb30ecd38", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "8537dbfbf0da1099ca9c1b9dd5a84ea5910d8218743bf1384900b57af543095b", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "f9e0c665e34759a555e20698dcdf7b3e1637f95cca54cc1fade7f2c5fa2cf622", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "8cf871aa274407c1703d4ab233a13dae6ad9f5d3431c3d7f5798e6371a8533ab", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "f435a05aeec573aa93c647b4b7541a3de88239d772bbdbbc78564ed02defffc3", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "f3b1329b303675b618d559610b36c725811824e840a4a27d518cce9e4e45222c", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "4ee43fbe34e66eafa49e961ed9b4c59b2b47d22f924202e908d28586aa900035", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "a124e8ac4045bf1253a0f8251ab72680d8961d32075ff7f2138c2c8a435ca1f7", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "cbf7cd1a0df3750110a4425639c5c87f200ba1f29e17d2266cfa19cbb0bbf42b", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "0c8d9b3fb0d737fed4dd11192fc9b80bdc0e56cc067a0ab43c227668290aa009", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "da1b2ffc3febc5d4dd34de9dc40edf3c8db6d68ee3480cdac0973a82ef7315cb", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "5a8920f8cffdd9778556e6d34a3b3158737ad4de3756b049ad58c44c671b54fe", "target_hash": "9ba426e3f7a13ce9f5bd158333ea4cbd368bad42d33edb86ac4361269d019775", - "section_hashes": [ - "a4923a55f5d6", - "4290d2b2170f", - "818962df8da6", - "c41e30225676", - "dbe7e6086e46", - "d0b9a1efb01e", - "bacefc4db261", - "38c7b6aa54aa" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -23033,787 +9731,332 @@ "pl": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "948f668f138f45a66b0afa9c28406b466eae72a72f573ebadb5b2a373fb3e86e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "134fc13f1557a2ea8eddfea2d988e20ef0b39d4437eadb8e0e8783678b828078", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "c57befa680304bf60f5b0ea7e747fa6711d142e6454737f5e4c3a978d7ff1cb9", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "a66af7f969b181871ded28a884cc4b54a6e66c33439d958043b7cdf3994889be", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "8fb9832bbb2af4b4380524127f642a820ac16628d407f4a2d92971a65e463f74", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "c36121b048f463326566e22dc817b528ad2f4d82f0fbfa7d15d8357d1305ab7a", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "88b5f4db32516088d4ea422b8c244ea9478cc8f17221237290527c9ccb3e8b20", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "f013b16f1ce5f25e83568fe55e04e544c03720f32877f0295b77f8f133b5867a", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "f6e9c51c01ee0233c5b1011c633e13575366be94ec108daa35b48feaaef752e9", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "6c4f7311ca47b1964b99370e4090ffb0ab7a46c859b64340f7fe9de6e3bf45f0", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "2fa8ad6fafdd7f3aefd05de01fe1d3e845f8b79680e255a903765dc13e1a8f13", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "cc72b5ff0b286ab70f193c85fa83a195ba668b0f32271ac95f7ddd681a594af9", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "dbaf7096aab3908396a7ded8d48c03c0b13e6b9b57e96f18ff2230919ca11971", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "405c1de48bab7c008aa6319846340e8b3d48a57cd5ebfb7f9a002e72bac8514b", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "9b50b0c83f89d85efe1ce225e80cc338e2c1e5a52210830e6f35418250a15268", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "620c1c22801d0548f8d49f9b7e89ac0ac7cdcda3be710821cf653bee49f6b7ec", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "a5572bcee680e8f78fabf4a1a457f86d2fe1c32962a6ee3987867d6845848436", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "fac0b2cec0e7423faecbe1dd7e5e549700a7de48e80d5eb076696d6ddc24c2f3", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "b8e28fe25a82b04e08c0eab4e2383d007957d2610e77e4a2a1fd9194654bbf68", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "7f16e25814dd51a390b0124b4e3588fe0d752e800634b6cb8811dd5c5b505987", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "d06de5ae6afb959878908e15f858f51d490946c503c2cdf804edbb2f6a006177", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "36f1904b149ac5bb4163ae5cc9d56575f45ece18109fe08075d1523764cb7349", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "0c98fa36d48ec4c559c0801664fe4ef279561716f6eaa8dbfb5946e1a36e4806", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "a697d3957ae4ed803e77ca3053711eddffa7d218b030b5111d4e803512da8b8b", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "3e63c6d720dcd90881391dd259904e94d9c49f5cab662379538d7ec4d872ffde", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "4de3d2ae3e42faa3c5d98b3529b1682488dfc61574cdcad88faf6f45cc8fbc23", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "50f15406458e7cfdc6a32bf17a861e63a016821daa4fd943c4487c0cf0a86a5e", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "11fb5ea2a74bb143c3469c8760b80fc05a57809f5e8141dd87a31e13fa76beae", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "edf08da1abfb347e381a47f314327bfe59fb4051f27c1f3237bc7556adb254ab", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "fd4964694a8d07b9b8f1ebd81ba9345ff049756076aae355771cbba480fb86a4", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "df85c5d64d23fca1b71fca960ad7be924063986ee552693496b7db5f01faf188", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "c1430aa42b3133dc703ef762cbbbafdf87259694c976d8bc590617af7ace120f", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "1dd44d00df11814c16ec38428a5db81a7b5ee00fcb72dd0d139fab214673a84a", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "5bc0ace74732f2e8d5bf89261f2a11a2c7e6a67fdf6e5f35bb3aed680180ba24", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "e9420c0bf3eb6770f1fb7715ea7bf1bad8acb7c3d14ae5925eb92321a0558af1", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "25c7ddb82b3baca3ca91cec6cb9018d70a30f12a95d5fc066b02c8cf3682a236", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "591f43c16224a94199a9cb707f751dbbb2691eef2b6480cf93d54f3c8fae68c0", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "c45cebf447d5c497de09bcf2eb78014eb6c8951fb20f29a5370eb54333aec7ca", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "18873f153395d266cce9ac8fedae402df9ba7acb7e7f60758d0dbf8dbc5782c0", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "ec704013f58ef5148f91945b8e5ca859561d79e57a7d515fd64dcdc828e552bf", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "c74848e82913242a89f89f68dcaebda4fb0011f1290f99ed50740c8947efdc57", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "e316fa6a747dd01de7f50e6016a4ec1931f88b75465fc5617d0356fd9705405d", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "d9f1ba4bb99065a80cbc22a7a54dfd50f8847c3c2761ca473eb349c727cb16f4", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "12636a0afa5b959403a53319a1d409a508d5b3fd8c87a4a2a2543ed261cc30b7", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "502fba296d7f020c6a677cbbcc9091118643d1355ad3683fba1de0c55b8a204c", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "469715d1c62f2d18ca51594461991e0400d2395f91bf9603d4d96942bfc61a4e", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "ef500db8844fd53ea003e15876674f34778b3b46c996e1c6db3df052309d7bd5", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "992632b5df2c5c086f68e07d31a24a20a49f37e39d7e74eade428ea54694e205", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "0e5c0a095e790047e75dc98bff4790fc40ff1aeb74f87d93080ecf42b6ff2a3d", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "f2671f0df4be2cafa3ef7739715fe5dd46a1f9fb8deb98e9379d6e2917f4e5cf", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "234e1bd325dbf0c57619588d8bae491b973261b91ad54d280f3091fcee4ced0b", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "2d7b2a612913eeca333f5487857a47db6037d605f0266dda9bcbac36ba838c23", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "384b3c3c4318949e28016aeefc095fa19870b385184998fe9083a989c7bfc537", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "62ce4df060f5815c58bdfc3b637cbb6b51ab67f3c6184c7d9c9f00f5d3d14d97", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "069a3b304233b1885aceb2a91f8ceebb5296d0b9d3d16e4485634e8b1032574a", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "7c368a51a00bf0b0e28dc7d263e234f8bdb97f20b75030f5dde4651e39c321f8", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "a83cc6cb9156ca5c527b52462ef4db9c4267283516ef0e32918c431d311bd7d9", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "f193b2285727503c7d3df0095dab7453e9a5dbdb1fccc2e8764f318fc8654019", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "1dc989aec2825721551ce749ebc94afd3ddc1be8b9f2eff44d64ae5450d962a1", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "fe89b823edd0916cb6f1b541175871226ef30a84e2d1edf632964a1f7867b8d5", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "62e47c11468d9d77ee03e240cbc18b3c152e66266cf34480d13dd4a4b2d52845", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "1820adaae653c0eb4d46c20882f976e2871885fcfd2af38fce7f9fa3c7753b60", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "ff6738d52d02c0cb085f0a455016a524a190a5e7244344a450f5249131bdc66b", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "ad27b93b271978a42f312e1f45343fc431f3fc18a86c50ecf47a5b94baab382c", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "12b72de4f89c6d547d2c31f9c7a49ed9f30392a654db8633a409812c0fa84b3b", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "fcd863fd55fae0163c17289ba1216739f2311a19dd21bf83b9e512ad1179c872", "target_hash": "1bd6e7fbbb135880b14cff6353565672ed6126b074f0b91f5d18cf96693b1511", - "section_hashes": [ - "96ef7d492d32", - "1ea35e5a253e", - "d7449d0b332a", - "76ab2a7cda32", - "ab6ae78eb1d3" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -23823,982 +10066,332 @@ "pl": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "8d1905e504c106c3701de2c8f1575b035742d827bdca64ef80f606039b820e5f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "c134424aa690c4bced9f4e9d47edaf20078f1eb8852758933f2cda08e8879449", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b45faa7ac6be0634140edcedfe36913746d279173a6b0d416425c6b57f3ffbe8", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "9bb1af81280ed0aab9a07bdf5c70e8c02490c35d7334aafe1d8c2c04ed50088c", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "443eb61f297afda92bd5a6c7285b4d5895b26b15286707c466fbbc8a6f95904c", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b7da8852b56f629af54e21bc3b30a369f2f1dc55aa6004ea4670e1e5f0bd3940", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "93fe5ea5ddf91c1bd8d2d2a28507257ab6a6ca165dab6d7a2ceae49dcaabb843", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b0e0199aa6b43dc9c5cc4f44acc9c4c4ded48b37f9d9eac3237cda2d764abc40", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "3a6d172e0321243108a7f3e372a39f219aa66f23a847f66a7fc75bb6cdfacb2f", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "8db4783698eafb40c06e6074622ac1a5851f5f98cf224b6fd67e3f562c698d30", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "42f5801e74742cd13e774309b4c92e13867af5871aacdb50cc5a7687c2ace46b", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "09c8392e65a0ee714b4539eca104d0e1c48860d0d6038aa445bcb752c285e46d", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "4595c7af595e334ce1d67a599da5ae4f438833de4c414db4f21bbc39efd01ea7", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "90a375a1aa50c4b3b10827690c27aaef77976443ace14ed54a282a8a174b96d2", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "765bae138ab721ec68744621393f0b282e50044221850661c98880ed8cd97496", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "2e5a8ce7f1cf8922ca2cc4f15e383e5cbe429a9b7d287928f22592e90677a7e6", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "276715c81c04bb6ce733e6a1da22853371f281ff6fc236f6f61162709c9629d7", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "674fa99d77b424845765e67f9a7a3b0722d71b73e34002ff99163a88fed827de", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "c396362127ce0c940e1a570884f69d11a233ee3f86973487f1c16a63e9b85932", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "691bcf6840e178e341e584493c5d9294ce5e7aec8ddaf9a15ed7ab09eec249c6", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "e3eb0dc8696b6709940bfa881941407e91062cbba23b9ff962a67797d48263a3", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "2c6ea17695aaf395c6af4ad662026e026c4d296afe896765011255d7f035c404", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "45c3e480f494fe194f89b08b1c9a9e8ba1aa7c54e0c0fb45a5f5c9a6a9f7bc51", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b04f4043197cbb03ad2ec1b42c4a0d3d99d458fc04ad42a7a2869256d551bd1a", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "4e94a94072f7a51cbe41c4fc0023cbe78db2802586787da65b58919cce5a1a64", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "a7db1d7006bca9661aefa845abbbcf19a45c2ce23bc45995d4875fa8ac0afccc", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "e4abb74656e890cfdc63711467427eb537e1ca62f4d6b7993f133325ae6c2b54", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "3518bfc98c14e24c98055219d0bd0c60db3d18b4a8395cd0725350cfe67084d5", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "82fdf2d05fb37a4795c1c7472dcfbaaececd3ba520faaafe31da35c4cc8700a8", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "eeba8462003548eb79f8986f4abf99bbb4eb7eb075b4f3ea20f5cf0db19d55bf", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "080b537c6029ebaa634d5d671ff19d680fde5431e6da53f249cfc66397dd04fa", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "e3f6096d5d282ec90687eb81b4642561154fdc2e22f4a7210427750254b44fd8", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "ac8be79b445ba6dac0d4096ae40b592e3f0b11dda908135a8303a83539900650", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "8c54143f8cf299bf3e06369f70844a06f237179eab6975e25d5cc79fe105ba38", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "3a947aac37fa7bfd6c4f33929425be424f831b515166a12bb6eab206cd3b9cb0", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b5b5995fa1c02f8e85da63ffc5a3b4098955c1ed86515fcf93ac88d9ae89f6af", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "04f6b9a41767e890ac11d14f2090cf0e7a352e6b6d5ef3191c60007f978bae2f", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "6fe061845af8d212c696c3a691aee8eae0ad38396c80ea847be4fb40de509b9a", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "c619daa5a5f714ce58497a598ce1c5b6046e28a80984f4038818f7e3db898b29", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "0e5acea1b1aea6781b6470ee38ac58c780d077b99b8759f8f3795012e8ab6bfd", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b2cd275520bffdd025eb50fc1abf4396b3f2cc445f15e16dfb233c65fe217611", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "ccf8e9dd294c5cdcffa2f733dae687c4ca88695d3ca81d68f812d2090a91d94d", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "20e0834ca174f4a32d1e92d710315f9672aad287d59cb9686f25d57c853875e7", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "96be463176fd449f5e3f8d9339ee36bf7b97d5054586217927d05aae827a82e7", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "89d179c581e92347ede4097571dfff8274582527095a8419af0a7d7c05e6f8a3", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b9fee8c108eaf7acea8322c3d9e80e2cdc1e5540a79a07ca9739ca3273c0dc69", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "fff9075e0d32b9f742bbd591b0db7a1920222b3133c0137f6c30501f27557b60", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "05788babb5bc61c16e5b0cd6d279c793f7e0c4109183291069365f2deb480350", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "461618edc462166337510c13bc6e2791985cce914b3cd7f04ae1355ff6d8023b", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "5738c5f4c3513ea1dd4b2c66fdc9a9d46be115d6a03db2f188c4a69fae24d173", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "513447af2ab9532d31cd1b281912b911ceafc3c7c1a7d0072390c7c2d1e27543", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "358fd5b0921da970bfe3b946169e9994e7dc4f99ccb0c2582e18c290575e8b8b", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "30e03522b4a05eb40b69b478bdc82ba624c30526ddbbf5cefda49224c25c0815", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "37509758f1e44ff540582e5b3579e738849e77a6705eeba2be61df105b0ff6f3", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "b17b1c5dcf6f4779b09d18adbce285fac0561b2889a626333c42d28ac10626e5", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "19441c6763977f82e5b248d0dbc75693fbc57ae0122517ce2ce00d1ec1878b0c", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "8ce801023462dec489eb7e97c3afc64f4f04d5339fa3e68b1b1aab2b7758ce59", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "9d588667d38b7eea26c6f49a57d86b84f822b37b0661b9b0e88a67eeec5dc0f3", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "87a30b51ae56c2b76c6b9d85ee0a71a6983104c6b35f101e56992239f21ffead", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "119e10745fbdbcdbdf3268b216a42c37d6d08c4403423951f22c2f378def6bba", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "2b774415315423bd6b426ab9681490e5bc6a3ed0e9fa6310c1aa52df586193e9", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "cbde7b0bbb0055cb42e8fa9cbab710cb2aa15941c7af621b94d57405f188bfd3", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "2ed6ab5557307f379f9bb520cf65ee73129a88d88cc78a53776961e031084383", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "e730a09f6e41ff8a04293bcb2f3e6da74e0f65c69b9dde8e4dabfd2ea6087ac3", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "a25894bec6c10fa8652943192785b2b7f1367572121ed61a0b90758de0306987", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "9d2d68507d41f8eac88b7ad2b56949571bbf62e38f5c6ce064069e04b06f08e9", "target_hash": "deb7e139db7afe983a0684da3014e70f3cd0bb0ae1c1add396faff523bbe76b1", - "section_hashes": [ - "03e637203f9e", - "a1b83c7941b1", - "f9d428ad17cb", - "87abab84a14c", - "de68bcf3dffa", - "c81af4fd934e", - "975e5da49a31", - "4a448398f4b0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -24808,1058 +10401,332 @@ "pl": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "0f736d9bed125a7c1dc732372dc44f44f201a7e13bc7190fc392591081936c26", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "c64f6ecfff24095d8b95accff6f4f734388319fb26c7bbbe806375317c041800", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "9998c452ffb5347d50ebdff34ddcff4c7e2b02ff96eb1469f9b4e68be2cdcbb3", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "da913e3beca698ea246af578dceeeb41de91a63a77081dc88c269aa51e657c5b", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "c5bce4b1b9a00c4e791dc10cd454af1c157e42b0ec5e898501a38ba891fb1e93", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "a47f6a301587d62981402172ce4a7bae50d8711ac8b1e5e9cafca3d8fa1ee99b", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "3bfd02eb4cb66dac4c320514385d3a6ad103c38eafb9033b541525fbb3822783", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "93fb6dc11f2657781e77f6fb42dabe1a780da8473d48ee59b4a9ae64cc162dda", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "149299d9a7af771e0531e2449a5eb9af72ba19c90e14b9b4f2f3936f5dd31eba", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "bb6103d2fa8a1183bbcdcfa6d16edc619cf19f685f22e7c53672d192a98d9410", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "0d2f58df986d712e3cbc79a791b6d376a4bd5aef767fbeabdc78d972814cf7dd", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "35a04be050cf1ab2b510e3da741942a57d830e3c339a4ad55459e4150e6e1d29", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "8a7ce6887a859440a4cdf7836fc9dd288878481d02026b582fe94130f08967e7", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "a1def9b19b33cddd1cdd5e22a88592ea7dc93bb297473796da3811b64713bde2", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "a2f94b8d7c0845d228fedef80a2c4159812aefacec8f953357fc8965371e5f43", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "4cf23098fbdbd89cc64c5a76364d93f64faa4403c94beb3eda7caa9b213e2ce3", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "314fdea823fcf1eea9083b61abaddcca2828e529c58f7279fd442ebbaec5fef3", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "7a28d29d364383387a65ca0eeefc565d70e483d864399a51d10bc84b21264bc2", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "491c416b0a2f6b5901195aa9e3b949da5ffa898ad589ba44d3c2a362a834b5b7", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "b6849407f458f9d0c2a812d8afb928258b013b1d2623f98e7dacc7b8c98f3f80", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "761144de1de8e7223e25ccc75328a21fd91e31c1793262573ae5c5f90a81fd32", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "81247f65c681ed2d0cc9a9a97ced08867a1c5c253718970345a8694537881aeb", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "03a38741fcbcd6d940be726727b2d1c20506de981044a3450af1c93cdc5f0250", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "c38b7655958a3fb53667da572af54ee97661ca4279823628510afe050ba7423d", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "b9832df24ecbd1aadddd9274bdd07e22ba891843089d801abca3102f7b8f7f70", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "752338a59107fea7cf5977ed0008ea93456fc590a24a2116cc39e88cd1b2be85", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "8dbb8e197f626ecbb58c792d4d02fe461cfbe28d60ceb0a8ae32d43666739f68", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "3531a2370645878c37be4dc344980dee1b17c27e98b4ac8765235ee7430d1e9c", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "383881312eaf2edc2721a2d4c9831eaff744456bbf52806df17df7843c3cc50f", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "0b8da68cf6192a756916aa0d284feed8e77ad8fcfdedb1149728218920fc014f", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "4e23c41b6c8dac5f62fd2dc2ae0355ce625c01e7b0f04e5f3e8a924bfd36e8b7", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "d5f066ccced6c7f5e4f5b5dffe14fc55529be542154b361b7ad113385d1946f1", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "120efee338b454029a9cc615f241665dae3c7ae6364864d2eaf7572009ddbde0", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "1ab4153c91800f260e9f8c1ae3885416a34fb6e609122aed5805f509a9114725", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "0d6612e8079f265346ae2e35b064b09e1a7751f54ddc938883088c52f7500b8b", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "f78d68f8fbd061aab1fc09c24c22bc86bd47f51ed85ef54bc3235f82e2e047f9", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "0548ee2e21d4008de5ee0e6e9ad04bd566ce9ce41e08d7c0d02ce53448acfc05", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "b1525fc3d55d876694d7c44b7e6d4f5cec06eddd16638cc98d6807419e7f47b2", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "5f23eb59fba3cf02337971d24e3072264048cc5975a2987cd03b7f8401f4ab89", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "7f70a33ca73069480b5c024bcf36d6f3d89e21276bcd1567e373805622740ce1", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "79368a7334f668c1657393cf6e7782db0e0a185ceb848b675e3380a3db5394ff", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "17bda50786fd173b41be9010c4bc7baae7c920199f16238d08375a19cce9ab6c", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "81f0e454dfaa8109fc4fcda7c50a93e63722331141cba21df52d665d188fb360", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "c4c3a285c6192fd9e9fe602cc7826581aeae3eac2f9a64e3d5fa59849a3453df", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "1ba92af8ceadfe76c8b02b74f0fe9738c3f1b5bc6a955be59273d71d11c37f49", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "9749c581f33c3cf04f16d6ce9257518a9b18124857d05ef4462378bb66d40344", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "89ce0bd10f989723f200323aaf9523522cd14f1a8cef68d6f5ea658f0aec4d9b", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "a04c7b72885205608e3fed14971f773db3f677e5abb41b3d023dbf2c223aafb5", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "e9f4053751dc861055a49c6d0ae2bbcd7f61155ccde5577aa66aba7373ab9018", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "fe257059c5cd667b9720020716c27ab2d14876daacf5bcb7c05f701d1698ad6e", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "2f37666edc7d9772d2bcb0da58708086fa81b692d54fe7569f688b92327f212c", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "88e82f9842f10474523ab2fde6f72dede1cb670f4471ceb49484fc08e87da56f", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "64171e842313e90ed9470799f95261d9ca0001f3dd0a40c9f6e48019b79b17f0", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "bf438e2d412b1acad957e6497707a8e3f305262f8ad370df29b380c0263e6459", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "92ce8195f8d38914c2aacebc6a97c11ddfc3e366cbff02431d3f03f95af6153d", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "bf81f50ff13b4dff3de589916c57e6d730afab501b163b36190b31e5adea230d", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "f6521f5bf5a138e873250a328cbf2b5c6396666d747223539d467983dd162cfe", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "9a6f6ba9bdf196ff20a0b17703279fa009e59406be1a90f1e250726d9269f29d", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "7212072971a23ff10559f5b39600dd847bb8e00ebaa6e5ca8c7e098b474f420f", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "94ed7cb0dcd42a12e74e2b50dd411907369e840a58810f12fb4902baa612ef16", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "6c5c43f7506a1eb7d2a6999356649d70ba0f1ba7425bee77ec7a5494472f386f", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "f4638cabc0cd53e20f003de21d0964d0cdda69a31e5b33d6f20e6f40395d3726", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "902fd4b734a6de984ffb462f7311d0f50c70b2e3b2e5848bcfbfb10d6b9c1f38", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "76f74557568eb2aed13283ba60e6af3acd040771544f68edadc4f5ebe1b8b399", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "1b8ca4c8f2e86dc0ce662f4a1a1936c55a1e6ae6e87d2a3146714fbebd72a026", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "a86e0a8b88711283623c64d053fadf8959feb198db4504f402d87bf6eb9dbc67", "target_hash": "3bda10949cf2f23426deebbd77999a36981441fef1d01d21e0d40815281a8f0b", - "section_hashes": [ - "dce12939b182", - "9d9fbf269685", - "7c3d7b27e8c5", - "78500f44709b", - "9994a3224801", - "3f6a293a84df", - "621f99258f99", - "9c18a09154ae", - "d1071fec39fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -25869,1567 +10736,332 @@ "pl": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "b5ac5489f327fdc39433bd7c58a44738445b0be508370c01f07e1fe5da8489fd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "2589511c7050de1b4ab195132feedd5ebca7167c28748ac3da5fcf42f367dde5", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "e6f451dfa645f60c3ba22f900d09fabf5dad6ecb8f82ee1fb3e39a7bd1df0ab6", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "d2a813083dee7af3cc25573fe38f4da4df97a12e6b39286896e37789ff2fe66d", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "d56494d9cea5c1b162544d71d350f2461ac65cbf880324c4e7dd1e2c48e008f3", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "865a945335b31d754dfe7178e7cad8f8876cfadae8e66db5e9f3207b251455ed", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "28fc368652a7c0f0328fb48cba5585f443e8b0ea8126ba59f4e0b5d8121927e1", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "bbdc061926aee2f6538003cde7f9f4268493cd2e5f4e79ab588cbd52a8181931", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "b1cd46695895e3bd55eea22886552e58f3fb75de28320dd6bc98aaefc60cff2c", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "9d20eac308ae19881103313af35d377ffa976520b3ccaf612f68a26b140de905", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "4b0a397087968cf3ae334fb235e6763f9e4075310fcbb540d45694075fe00af6", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "cc7d718932883cadb7d31c0d86c91ba7b85ff20c7227c3014c662f37c7a3658e", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "a0380cd9cfb4a0b5696af8daad8b7e4eefdfb921f317732002e0ed590399a4b5", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "80ce24e9eb84255365662b3e093a53bb293e8fdd25e1c6ad2d7dbcc68b425dc2", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "a9b6e29ca0ee78ae94bf8cf15fa6d52e0acbd6adf5e52f1a3f31539901af17ba", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "0717b5033849e5478ece3d37da9ce115bbcf18dca09cefee1ada8323cb71b48e", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "5520b759ba7ffc7215da3e52a92810482c9809e4f570775bc82fddb223fd376a", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "b492d9000f972437089bc184e1585cd096d78e244b99e7224d2612695624332a", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "19638f888e950868efa698db85f24da8772c33e69386f18d6fe3e7bc7e9b8893", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "85ab300a8de46958ee3072e79651abf2efa68df34d91e47f04c50421270d9421", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "d596fea080833061b8b984a5359883581b2236ec4fe48b317dc08099f3ee45eb", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "81c3e95a43217d9359ef7ef8e9586497b13c0da50b17325f9548875875ae9b82", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "4197374d77fbf9d037b9f7b7f980387ecb0e675c59bcb99d75c4fff0867f6d17", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "330b35ceb7cb8642a5277154cc73bc83b6150521a762fe9ab18c457ba1a148a1", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "ba5520758b5e2222b6cf0369138ee2549b7286c4cccc1d8c53802fd1a1c019e3", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "2574b284cf915d53bd08af2c25f51ed70d96c44c73f786a8f60624a9d9f41f93", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "646c893ff92b1f1bc37e00d5889829afef3b4c74b3edf249298e17a390dae196", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "fa084e4118019fd8bf9bd8cfd43c4ec8c669084733cd953671c7e00a6d78f8cd", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "cf7c1ac389a1d26d45a9d89879ca9eda5f966f380e4e9f5f5094541bcd96da3b", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "7605902b7ae33d23c8a1ed40f505527bab6d4348267f0319dc77d25141235a89", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "fbfa5a2fda4ca38b4d5be018631f7ff1f750eab45d91f415122316a1e238f09f", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "af81ddfcb1cd2c51d4e7f1ed6c7844fc9999e2136c614c65f200841bd9b8668d", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "6c0fa3b67656983fbd0c741139bd2b25ecc5f03eb7226c1738425822ad3f68cf", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "24934bf136c439c4587d73f034500e32ea28747ef73c86555d5ecd1536060b87", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "8a95d7835432a38af30da5240ce055dc68588545e000372f85903b05f706f9ec", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "c4975b2ba9a6992996a396a7ec73dae6a6cebee8f4cc6de87fa73dfe0ecc4282", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "a36f23dcf06a394668fa399b5241dfc4f8f9ba341959392ce3d1e27c681271f0", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "cdad9d8dc38b549427d6faf6cc167adf1457629c7c0b6539818f8ce5d2138200", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "4d190cc657d671d3e616be1556e8458dc718e2b856cef84e59ca67288e3a3d68", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "1795b838c55c48c50986c58cb89e34510a232f5c7fd0fbcaf0c5559a04a40685", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "bb01f823784f420982e70c46e64e47049b1958562fffe43d9916bb837d4c7bd2", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "10c6b8e0346bf9270fa8e7a35f051ff3bbef4013aab0d6bad8a519678f53f5ab", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "b31d0511f70ac75a1f8494369f0e7fe9fcd73b0f7a9316c99e156c8fbe309b71", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "f8730c3dd63bab3b7ea4dd0bac43786f084504f2e7ee900a5ead0257a29c6423", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "4fe0b126430ac943df4c4a8b96e16df915871a231e0a680bc6159c58d8932e5c", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "62858be54584d9e61b4fc6717eee8b42d479b3bdf26b7ad1a3162bfe9f79d2b5", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "13a39a7cd83b5613a84c0822266eafad022756ab34c183080cc1755e267a1dd0", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "82837677eaf75a989d8643cadd6a6bf73f786b40638ab2314703c985d2953bf5", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "62d0357f358de2f5f09397c9728fa3cb98a9344cb3b8b1ed0357a3881f8d1a64", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "e6f5916356a32c29660fe22b0aa124cfb691bd2a2c1d6bdace731aa8bebaf872", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "b77915d22406119bc183ef400d42afc70998b7aaba92a1d71f024b60970ad24b", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "adc95869f127dcf555b614e7c9a878e33e6fc30f19e38175b0d35749fd4c5548", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "4ab6c44fccb5ada8f986a2bb6f4a88218216e239fd274aad84110b9bb5e6b09b", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "893af7ac8738a8790d4421bdc15981e1f150a5b6d02f2097f3cdc547d04c2c56", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "39d4a2788294ad6f8adb5a20eeb2b4805eacc7de3c755f1681e96511f6d44214", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "1f13d409e6280420bfcddcd97699d5e0fe48911af03297e3b82a37fcb4912a2e", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "635520a35386caea74e2475f27ee948c1038eef3bb95ea2f763607c6291b65ba", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "c0249794d935ec417be3ffbcbcaddd1ba695155e41ccb182b616b5fdf805361b", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "9117cd2280cef9577adfa219fee6adf3bfc7e51c4ca34987463cf536eda0edba", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "aaad325f2c2b7f680c5932f056e97a8a2cc8b4a61bd02f331c36c53170023ee4", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "6d5c426195f243a99faf61eb63dcf108284b1f0b54b32ecebd6e6f1859a414bc", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "cd6fcc9de2f53dde7bca5789e47e2cb6c9d5665e18e92dfe3c4dfcb438879edd", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "39bdb019b3e0ee8ce9a7ec01f5002021784d3d2935ec1200df25d2cc7057373f", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "fe92e43fd9aaa9ded5ef115c0596dc75db7bac66fbc07b69958b4cb570b65677", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "1c981fc5a1ceffcb9b821916266faf867815489a6593b78133fb2bc764fe639b", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "ab00069d9b51d071b2ce0b9ec82c70d94f681333af0add67f3bcf4aed0ca86dd", "target_hash": "91a9907133182f5e8bddd26c2cccfe424ee2046697db319983e9090ab265faf3", - "section_hashes": [ - "e2e09996f22a", - "33fa23ae693a", - "b503dc393cab", - "cdc2df0f6be6", - "f88a17e9a2de", - "32d5859d1b86", - "d19790b951da", - "c3ff99748651", - "600774e773da", - "a21860648544", - "ddc9e77aafff", - "e9e9378511e9", - "1e6b4eed500f", - "f305379306d1", - "21f58096edc7", - "0e168864fe85", - "8c654c6bc81d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -27439,1256 +11071,332 @@ "ar": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "be5c7388d563ce8cade6228a6e1659d8dc7650d02118e4eaa5fec91ff686fc8e", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "9dc0932327c0c7dd518cd383cd34502fc81db11dca038a4e5272cfec645aef69", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "d118d4b95c3d8abc4f908d944bbeb7937b6950d4ae3b987f51db4609a705fde2", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "39ef7711bd7461b4f417d82a9f6145a6c6e94f9bfd9b66f4552101e537150953", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "0237b95dca4e7f19dfb1e4c495d0136e27d890d0f0c705abc4ef4b4a83558939", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "185454652c49f36d79345e266ef3f75e17ea010b3026005256efe83376a3f3aa", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "7bedc6caa3a4573cf597779e8c703b795c3d6eb51cf067d05292f1bedbadc7d1", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "428bc38931bf48fa65295f61332d676f45919b74f20bd2c33d70b8ecce4bef26", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "279b8f37ce3e80861647fa195e3b95e4f92297b83dd7b4e0f2036310faf412d8", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "e7fd6bf1e9533778ec35b918119cab5f7c362b07180d80be1c0a0667236b9952", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "ad65d9f2014000c37520b7408ef8b5578491d72b747e61c70ff22506f34e600e", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "1c9110851f54a1156e38f5e104d2dd74ebe4d4768b6ab1c5d3f47b42786a7b91", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "1b7d9708f89d43696c72022daa9af2a314dc7ca9c2fc032f3b7704931e003159", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "a0617c26d752218ca6c55d09a35dfc6d522b7215bf8912c6bf682bd116de7111", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "b3bc6071f6847d182d12a777651b855c1df83c71be17bcf317001226cc4271b4", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "12f56922cd30f566e51bbfb4b2a11d5ecabf63f35d8b8465513fa00b956083cd", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "3ec79046d98f34cf7d16e9675acf0a3037cf04ae0d5ba7639b6a729998e4f9ee", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "59052441a91ccc233a3b3c7121314843cfec62b1818247fc778724109229af1f", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "6b7af74f655ba39d3025a17700d1c9f23342a855f06713cf70ec1dc26ac3050c", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "90c8e9debbebc2af47c593b97cde977df0963b5f0de052eefdb4fc2b9bd5fe61", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "10221e24c4b7746877c0b469fc3aebece74560f89fd76864cb9bc7f2307d82c8", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "3239f8acba4fc2e6e989d93a80ab6db94f265c88adba7bfd696df602653cb741", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "d0522730a067884025f25e83ecde3e3560d79b624b93056d12890e68de573f74", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "a61e910e16797c313ea118d545c07c61bba606d38547331fdf13f9e5ca7071bd", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "2a97de4e164d7c063dc3d6c7f98eeff7dbf4df4c5ea9eaee3d4e660138dc2e5c", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "feece4548bc6e2695887d539698e434cde10b8aeea75ef1c467a7b7d9f950d6c", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "79af4cef782c9365de3b481c24d4436d1cfe14dc1a9f84c97b490d41eefb9346", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "544b06f1d5500f942fa3fae1ab30b5ec63a43eab53424e2a4872d8d990752ede", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "e072a14457d2411f60f9279dfc5f3371271b058057e8b3c19814c8b4dd7d365e", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "341b51488f4d64f3a5e4d87cab7c463ffb6f176fabfc1849cfba42fdd6f3e60f", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "cd55bd8d5ee5ded84d6cb21ca26e9d7c9ab68df1e38fe89d714a693294a871c0", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "24a1c6ac5f5ec059b3d7d7368982eac73b2c90b3eb3838ca9848202756fccbdf", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "517fcdee44b56c9b4f28e875972c856602c23796b1c27c5ab2c20a06b1cc9a66", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "dca2f5d17c32093c93d19d4fb259873f34bc5a0fbda7a18f2a921054fc839402", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "6cb1f0314249e7d178eded4859f23ddb6c32fa3c0045e984177b286d1c4a9a17", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "0242f69964fbdb329b603b79e6e8e0b59521f1b7f579948f99222cecd284ca52", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "8ece0720db96b780078c7f6e7a9dba4df3ca6430a41c4de0ce7ec4d417d0b02b", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "0ca65c5f76f66fb4ee148a62a2e4b9645b198ad576c66e0a11cb437b8b3b835e", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "0cab6e20fd1ea62a4f2d5f1e12d6664dc184aeb58269413384667c9333b36150", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "0b915a94e16a6229faba248885fc399e957bc29a75056cf95daeb54e329f6cb6", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "75068783071f45e6608b63239573bda94c47462743c7304c15648eeb18c009aa", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "e24c854811fcc0d66869ccef7482726cfd8b0660937884ea6a5fa14606c3941e", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "2dfef4feb43dec826c84a76ce30b5406c98929f990d8effa3e8083d04da69c3e", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "f83726901fe1dae9d478dd8c95b9054ff7d8d85d0687f6708324538694d6b458", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "650e68de56fbc68f3ca9e3492bd67e0e731247985bec5608123341da6d77c91c", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "fa7741f83c3e74111cd3c2458aac2c6503dfe014fddffbd4ef6712edec376be5", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "a478cbfe3b8cce2df8fd9868396da2054f64bf2b225e60603d43dcd3dfac8f27", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "0bf2c6c2285974ba98f3410ef37efbe1a50a2adae9ccf4f06309106ebda42edf", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "a0858e82e9d0c3670454b6ab3c12b036b72a87e92ce82aeeaa2cf2c8022ae4cc", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "e70ab96b18f18c058114303e4d997d99a49ce68e58651f8aa84792256c990a21", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "dbae5e585fe4b44e16b84bfa1d303016b58514b4ddb31ba84313a00624138e4b", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "a48aff153f03eee275a606d22f8f0d788b4c59bcb33edc0b97481f138aa1450a", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "773b1cae0f65689f6b73f20da29fdd1011ebf83295c138e459486dc087f7d707", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "91bed4b8bcd987c33b6fd39ac21faecc168ad8eebc2ad34316afed4e7d01ba0b", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "4cdebcedc9c246ae0e3af09f5a47d8936af18d863205963f09c7eaf3703a2570", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "e73038d81ae0f6e4df9236e0cf1bec8d8cfa81f119c6dad7b481fd2baf8cdd95", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "71e9ab05bf13dbc10570538ff9de3f160d19ee38d4f31a5a478a321b1540fef4", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "9453a17f149907a57e098e6e9129ad4e9a49232d2b882074907845b8b242aeb3", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "388020bed3e1836cdc1e81c4de95fb41829141d318f4000354aa33fd5ad59545", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "cb46af7d842b493a1dbe21e202c44fa06caf9a1aa4326564f3fe1b5cf7907cf1", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "9c65dd39e819700937ac6908aeb68fe7fd64cd8f8b08e30b77285776613451bf", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "a010272bde0b38a2ad905d6f7ee16f582e2839f9c34d9b7233c4d1c9f94921b4", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "7a5bc0f9fdcbb6f8706d86a98ba0ddf9e9b8df66d16d33b3ea2d23f37576c112", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "04f6b8535c1f6a257ed0dfce79b34462c41066b494a83fc99e4f906d7c51fccb", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "c0074b25a965e210e22537de8a86923b845de3fea7383b111e38c2e13184d8d1", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "9c132a2f85f7d809acfc51d38efc7f5c077ad83f3b162f68e0d470c2fcff74c1", "target_hash": "1fc1bce095c825e37bc5ca08a6dc41d1aebd8165927e5851e34de29f57b74712", - "section_hashes": [ - "5e246e4efd2a", - "5d521111c22d", - "7615ae52a6d2", - "d74bedb4a7a6", - "a38d17fb4a19", - "f38291a09a54", - "c321b86b98f4", - "d3b5701feedf", - "1cb372475972", - "5f9433c7b75c", - "b8f202acbf51", - "62984457af2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -28698,926 +11406,332 @@ "pl": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "f02c189b7320ba5bd8e3c9a0bc4291a9877a6aa6192f6b880934b1ca6f459a7c", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "051dc91ac0bd6d149269c7d2990cfe53f7054617ae3c8dcfbf741bdb61a26787", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "e8566a1acf713336f5e35ae4216821bb98ec62e6e28befa6baebe23093bcf72d", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "45f23d16e5c3a1f137524f7510e69ac6e9da3adfcdd481806eedd7e34087dc1b", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "57917f437a8c8fbe0d319ea80d29bf4774998130668a843c9202e16d811ecc6f", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "fc9ef5e96260aca395f5f464f09490ce07c80272cdf0839af56d14aa77ecbacf", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "c8fb45ae74bb0f6c6371992a791a3d3f15e403810c978a7204219fcb87bb48c6", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "9468de1ec96e539b3797800134dfe522a8ee194b5d0577e8220236228348d936", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "b4c5ee8e99565d10c60077b2e0dfd15db2ab25964db2dcc8f92aab547bb1b4a6", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "b772118938d4efa112c0358addf7d87a0119198eb9198c1c1010b3ff86e421af", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "6008ff9527cf657c2a3704a13fc955682a7cc61663cc5ef0faccce092c499161", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "9d4dca8183cccec75d3aac49fcd2eda511207a50f37884a12d2e0268c77684a4", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "972b0cd4df34c023433d5f15e5afeacaf82e1c6cb26ab89678aea9c7a5133ab0", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "d2f30402840485fecc44ba2755de6850eb202b3a247377392b46ea3d783c9e6a", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "76b8ee4b0489feed1911d0376164bf99acceb6a21bc1ab95daa47b26da8a1d36", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "ebcc568a403857a2b27b460e972a594bfc494e8af8d383184de723b782a347a4", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "852762e0ea6ad175c5be5b45a842e5f3fea1fa44ba30643c4e35c33526e4746e", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "716c1c6a6b0c678d4735395b338d4204444dec50196e96b490c66363377b96d8", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "3365c373d952d87044935f6740379480455bb2c67bd973f806017cf83eb640c8", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "1b353ac1b62f8ca31f301215c8759b9006ebe0ff75182ddbbac5b0f3433011c8", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "cee0e95eb666a0b420d0617aa37286b06f327ccb7aac0b55e32dac0ba278d101", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "ae83eec496f5271db2717891b54974e0dc7bc6e5b0e68d09a81a0ab3aaea3cae", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "a9004942a3cafe864dc08861667f1a0944165d9ea47ccd5074ef2105a63270a9", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "f099d8f13f086ad5206c556a70e465472a2384a4558caf133aa3d1a86124c491", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "7093fe4051fb45523fb361aff4d24f67d8d3fd9b23d0c9d427ce02ec6d21b1ba", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "07512584ac36ef8b7e37184842aacb2faef34bd56c69f31d18ddaef1d473d30b", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "b7512d8fcfda6b50d7654cd2f1298a2bfedf5b7c55008ee58b0f721ba2dd3f0b", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "a15bdceab4021833c1515f6d1b9d6954ed923c8bcf617d1f0e9174a548a1e06a", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "c6d75ba625fa266751b5890c738b449e35e6b1670723a36644ce125dfd6aaca5", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "efeb289660803d328d4ab158b63764769d7fe20517fdd1125f5808ef5633f9ba", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "368288f9dcdd23a7f9b6156e20ff50d64c78c8e5b8d1e0decbe6a886ce66be3c", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "cc43b520f13d0bee5fe984ad7606eda32c4346fd048433ddbe0ca73cc560830a", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "f8dc8824d3788f2cf859f19d9041bcc59ba8e1fda290c0f2e666609c20d3d72b", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "ccb469267f06ad246ead607fb12100162e15f333f8a688146596d20c06dcccad", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "f31347d9c3889dbc73e76ba6c107e366a249e2543d8b5f2b189fdf422dfe9182", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "79013ad201b698fe2523b8bde1224ad3aafd30b77d212b2366ff7b9e1353edcf", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "ab2c172e1edb08f8ce73a671f35bcd64b8bb44a055216acd228a2be5077613e7", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "f330aaa1b98f0afd22a0ee77eaf20140b79076b078800d83dc4128b554777620", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "7feabbd0d4171efa74a8922851e4bf04ae4ad2d5cda776fb8eab2cca34b5969f", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "3290a4f61b8cbc49353283e22c43d0fe8f2a5da6e1a4da2f35d41c4d34f3fbe8", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "833b56206ba6570ef2359434b533d300c36efaac65984a792b57f6296d0ca2b8", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "34207e2d328817c811a7863996b41af38c9902fe6947daae9f2a6af1b62ef226", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "08a3058610aa1ccee3e780fad08bd2f2871727af31915a42947247cc2ded99b9", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "987030023c6aa41ae89670940ab20d99990ad7b8279d00066f5279bd554c3de6", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "a5ca878775983c33318584761acc9d22c464cdf3c72f6fc291156281f6072e3e", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "847610fa6d5857658b8ba618882b8542f9ea6244eedfa6d249cc584e797f5ef5", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "3d74f7327b28ee37fb6be6361df6c986e54ab57f91d1f25cf69f5ec0d2bfac38", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "a0606c50ee80d31d57b7c6e7c12c98f397acccc6af006500ad37d8d1cf98460d", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "53efdd9fb486f6078cff4cfb31c8627ca5f30eb2d8680b7dede7a75b0a933029", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "05b3c2fa14bef4c859687cbc1bc74d33770d07d91a44ac753d680d5fa94f9576", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "cda920a0bafb78a6089b320a7d6742c23e77b3ed261d8fe96ecb1f8d921c8f7b", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "d9f56d5688d3372f03f688b9156383981255317aa00b742c1ec011a2054f4222", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "bc7faa16deabd628f952a07f1ae3b3a64ead99699144b4768ff1717fb8d76a5d", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "2cf7d814d8f81ed3d2e6618a4d636796a7bd44a0ffc0b5d945b5fe74fc862584", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "c5125ffb11280a2f00aba6e4aca7b9e9d6b518418f17a79f0d18d70652013013", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "a7c78b9684a03d84aa7635d19c4e2c2f86a24de2dfc7776a31cfad60d60c30aa", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "be4f65b7e5e3d47fa8e4690c6086130cd4012d4f86cf62bd8abb9638c83665b3", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "1c0d6fbbd6c4713a52ad98f9341565236f7d3edaa70a8c0750a4eaefe363c6f9", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "c5edec5ff3eb247e5ef31adc0fdbe64021ec5273c4e6ec7d1a01ff5f0bf579e1", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "bcfd39819816237b896bff743dc5b4c7626f04cfeccea70d012bc0201f7f6d5b", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "1c4ff437eecb8ecba66cc7437c4e2790eedfab21457af4a1e1bbf9fcb6782edc", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "c720add0359f8d8a362f27079a732a4a95614d262518eca165cf3057bef87a25", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "4d6c3450c8e35d2c25a70c0cb56eb84303daf51cf142b824bb3fd3953683604a", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "9606b6edf961d4a7d293e1799589df9729d63638aecaa47ac05b8c082359831f", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "7f11ce58cbe5613717b5f9d17b252dd13f26bea206eb5ae058c6054fb6438cb1", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "db286775d5d8a9ab9f815f640337c9a3667d8f8d8c2e064865295aa61843cc95", "target_hash": "6dac2cb79841e8954fa2c5fa51fd2b3d22fcfc2d06eaf516c7c7ff99860fbb9a", - "section_hashes": [ - "1d63d5f1ebe3", - "627d98d8c555", - "4a3cbfdcc596", - "0e1d975d2058", - "a78b67c6c906", - "a7a0d3495c80", - "cbb9bcb4728e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -29627,1256 +11741,332 @@ "pl": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "69d75f08cffcca6277cee7a52c9c420f16b5e0a8b7ab15f3b8383f1bbd3bd8b2", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "dc669ace914ed232f840fe8c1e9d2a41a0d6709d2b6fe95f15b32ed2651a1278", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "275eacabac08bf5e8968be3f6ad4dad1f30a4ecff87ddc26586d0eb5b5b3ae69", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "0e981b6783020291a14bbf5de67251dbb2a3ce9920b356dea223947fc1950522", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "ed2b4b055398060be76976d3cad3432e96bb2d8f6a2f60f2f3c45f7c2a307965", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "5745e5fcad29e0d5683abb4780a91ab19bb2cd13581798ac6f0cad18def9ffaf", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "e5b7dd2e1018358a06687c9818681d383660c248bd5707c79d7a332e5b097559", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "2d2ca73f581c6abb9ece66027adc384af7ee09c900d19c3ac171b2e96ea95e2d", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "89019ce47b725568eefa1ff36e516ae709dfdd97b143ec23af556a473d04ce04", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "4c1eb06b578ade12828b5039d9adeacb85418ad8db166f690219fc8cf160b3ab", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "f8badabdfdfbe58ae955eda98accbfae240f0ff0c1f03845c37ee2c2226cf3a4", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "709b66192f928cdca5a1b16268ff6c2d3554c15d2471251188592c4c1bfb7a2c", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "1dfcb39b1bbee0edd63268628bcfa71da20d320676f00ceefe568c3d7a2819aa", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "94d6e701f98b10b070baf95d7ea014ce1fd33cd2138fe66e64415ce29b71e5e4", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "c3a16f424716479980bbfb17b14b33d5b32ec471061a38359084e3768fde454a", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "309a0999e4e8bfe37480317511b148215be8fd94a956c3d8b59a6f4334a26b79", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "b49f53e4ddb2c14743f13273bc33608b5cb353f8ca268c09525e4329efe526fc", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "f0afc834e8d07ab90815df089c8b7fabe3607afa5dc89af36600d3f7b81c24ea", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "4139fdc26c222c06eff23525c78bf75309cfa0cf3e36c9235e94c5c70b136e60", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "eb0aa4ed1bf9c596d17c64db393a723fcbacf4429b8f158da62bd248db697503", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "19a7cfe5767ac72aa2c03e48cecb37457b9ead9f1630129e27b203e81ac28254", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "bcd3db65e22103cb6162ba3b494d0db3a74d550567217365d61d5181c04f74da", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "e52d452fa3af64881489ae5f2dd01b5fb74de6cf626fec849104268e64847d18", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "091befce495fc412e960b33c2e6127af4433387f66fca2c4b0786dc097d37dc3", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "c078e96fbc7d2acbbf6170f6d8b25f3ef145d70a2ce1f81905201df1ea0f0399", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "ffaf4ea9e5d72d90af965e41711cb1f5e603abeceefdf5da685e141f985b6149", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "1e298eb1fcd2a8f5354aa2ce507103327d322bf0dd68e4031c6188267b08e9cb", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "a4c53baf1a6ffc5d6cc5b381c756ada249220a5e85f027c12e1aaf44c205f07c", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "389f0794209ec312d657186d705b8cb7ce00faa9efb2f54f811c5d366277a3df", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "b50fce025454d340d108923746f16a93b951ad97c346cd8bd4bafb8421ea87f6", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "91ed84934de71dd51f3a55b543ea0b860ca64230a02b39551df2552acece20a3", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "d705333bb3ef9c10bc1fa50431fd5ab653fce1f2fd58240b2b62be162f6651e4", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "f6f3cebb45e88e48b645713f5d28a5886b1dea0ff880e4851d55ed1a93d5d569", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "bbafc5a9fc0b034247f07c59c3f730388e6d54af9aefcf9fcc1d71289b1d5a84", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "f678f1ef5efc0352ea62d02c16b3e357fcfac7d008c4fed5c3df29380cbaeba5", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "363cd0419d175285dbc1d701e5146f7c6c17354fdc501c4c284b05afd456a760", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "d24f872b3fd0ece380fb260623b90623ac723ea67c0dda72d2a79b9201ba9471", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "36265c370ae3d59b8f5d3d72fb0aab874313a25a7c5184bcb4684ff190abd0d0", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "efa403ac8b64b9f2402ce264a2a34ed369636d5452c7a5ac74c60649380fbadd", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "c31ce96bb741dd3367f503ffda0a14c3e5b84191a7db25d2f2a2ab44103de795", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "8e0a151474b0101daf44529a885dc4913d6aa383fa24cac657160008d24251e7", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "c5187597e89121347b2fe212213b6aea7320580af22cb229cdc9b6b9ce5eb637", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "866c06dfe6dd92f6e0110453a37db8d6126fbae279b5ccc74187772c31be2c45", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "b57ac26e0932a5b57c31d76c6c04bbe620811301581ad4980c9e4fccce1ef0ba", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "09ee3d4318a1e4485652ebdc6019c262e035d6c267811d870c3060e20a73ec00", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "39662f3ad646aa080884b202523794bdeaf4c59e3012789b3d63468be5fff11e", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "57d0eb674a675873b2fc609148b2648ddb5bef0d9d877e990373e36ada1c27d6", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "2d00c1282b3dfe722a6de4cdb24ceea29f6561d138852c750e79be0245aee68b", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "af59d2b5344a8492562f439a3fb582e5a33b111b98c3dbf2deb119c84c7967a3", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "d4ae749d299d2563310f2d408062efcfcb20393181c26b55b92c62c3cd4d1856", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "872ccd573911f13cfde1521a5e36f82510378ff2310ea9a58e134db920deaef0", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "ffe5f164cf224873f10cfdf4584cda8bc6416da34c7c21166ad26a4b583a7ff1", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "b9fcc83684431668cbf09d09037ba4e6ffee617d5847f48260c6377d2d7f9351", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "ade2322e1db4f8219737643e696d9bfef995b6e458696b6dd17d846c93fa920c", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "6d80f9d5b0fdc9f8c54aba50dbaf9827521c476c4dc0582e9357708542b32dac", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "f44f2e3cebbfc66d7f77247c09eee59c58fa298d6a35219547fc93a430b30e72", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "d0660c30bf3f6562f27bd0d9b7f5ec0d46e25a0fdce57e483b8e071ed3cb5390", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "6badc78fa762ce69ab84defbd7c6c05bb7d544540c052a12d85acf363df8cb51", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "dfd0381cfbe7ad95d2acb6b077ea6a84d15c65e65029febf70b5e173f1855cc3", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "bf923d14635333af3d0d73db261de4d6d2fb44780d1ea1176637f39c798bbea8", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "5cea81c07272bccef90e3e90684c8649f8b14b89ffe9e9b4d84a1acd66930ceb", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "f0fe4d3f422eba20db249d871055ecd53611dff5205c03db8122ed8e2e6387e9", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "0cb710bf46550011c77ab4736c7d8857126229bda8cb9645e47283eed63e9151", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "90b525f0e5bdef8ed8b7c01948c056e3bfd77a0c31a2b369e8ffa0ee03e50e8d", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "7eff9ece3567a11ba3435616fa476f6799316fee9a83301c806ce535d45a4dde", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e79de0d778a47847cb9d91a1b9834f467e82f87ac09625500abb4ae24e97dc76", "target_hash": "882b5b35a08d0edef22ec1aaf3404997134311b74fc78a311d9558b257d5cf40", - "section_hashes": [ - "5b9079258110", - "558c9e1b1f0e", - "29e42a801ac7", - "3bd2473f73bb", - "3052e3b0c1c4", - "94e788bb58ec", - "a0392cfd06ea", - "6e2c1482b7ee", - "30929822a493", - "33650d8c836e", - "13174f7647ce", - "a485057194db" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -30886,1058 +12076,332 @@ "pl": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "31312631609b9e072ca70675a7806fe9d190de88c564206e71dda34921be5308", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "1c6e9b6c99055d21716348e96601ba272bf3c8e9b0bbdf041c7759d8765a57f8", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "bdc3f1bcdbdf7a92892fb964d83636be38808440461554756e03b55b929ee117", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "a86cadfe46575f4494a062f2be0f99283ce1a8761b9614fada4cf2796f508b01", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c4cdd8b01ce4b4f1f7d56beee55c6c69395621f490897d69cf09de340b14216d", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c38eebb6c5dc2ab6d5c6b23103274108d16da6c03c1d327099bb00c9ea6769f9", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "84de65782ff7ad59c3ddec785b165a38f54ff19ae697b53455387d989c1cc75b", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "b1b06d9cce28c6bfc30aa307007483c98ee6a7f5d7221149c86185c5a7f8a95d", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "0a86799c5b156027c26b78730a2d81b34f83936604f368e6cbfe4fb501d8858c", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "e659b000db8f0eb26bc5e0d70ea746c1501354bd79742ca99ff7efb48c08e2b9", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "1e378e575b202833f726342c8d885b07886ecc97831549b35d5d6e8cafd3d394", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "1b26f6db30803b259c557f7d443b99aaf09b51e41bff8479f477e78bba60f5a1", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "ce81cc5dfcc0bdf57480fff3bb6e2f1b7f8307eda80fa9fa3f8f8cc3ce518a87", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "0a4d9e42cf66bbb5ad6efcc440420d78c2a85ecd3e89cba9b6681ef366ebba76", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "aacd1ab794a5917050c4d95ad268cb0c1515bac96316e50b00c2accdf5520e7f", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "91410f50498cfe17e8a88edb3d076ca538424c99c1ac9f9a87b4d92760c74896", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "7b1831f93a0e83d36856d7b6816df76455a7efe5af64c31322bc86dcbec01f1b", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c31d8904b6ebf282e40f26535f64fe38a138c735a6f654e742db7a8d6d0a6c51", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "eaa7765de77799f8f2f61ebe37e40e4809b23d7acee13a6aa4177a95e0fe596c", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "0096795f790a3ae5b483a91e407c7912f39a9956783601daeb7c4e7eb08c5f6a", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "d3604223e0fbb9a47f44f43c9e31d38a1105ca1c959eb64d69d27138344a03b6", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "8267b0ac878db8f2fc68cecd9d827903af70999046c05373de113a73a75ed63c", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c8a8c4d6183b8a7215b2c856baad5b4bd5bc4ba8704ac0709318da0179494cf8", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "4a51ff0cfd12a6bf4f5590dca1926d91b6f12c34a222b7ca9f9acb86b024671e", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "5cb5b87adbe7ce4925886b45f16fc788eb1275e2ae713e22295e19a97e2e067b", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "6d6164f1f3e1ae5e618e21df27d5ae3f1bc501d03950cac6e3e366ecd87b6dbf", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "680609c46755017a8b9707d3eed5db263d22e5c222118fdd337fb801f3455aa4", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c4722b1130c8b3bd5a0e59f2ca75f051767cfac910030cd0012279d55ff7147a", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "8a4565221a90247ba032ae7c8ad210402138c1a0519d3e2a27e400b8e3b234a2", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "d758b15ffdff623597bfadafadc9346e12b6fee461caa8c151b2507b006cf9e5", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "eb8491fd3d17ae3f862417d5fd91f1f407ff22f044ffeb727abb803c1e120aae", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "54d08d185164e150072860d2c27317a33470d37d5ded366230648795e950cdeb", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "698a5567ba2d0f06fed32c86ffda16ae3ec8693bb7debbb435f0df40a5eeced5", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "5239a47e4f9b25a64dd84c0aaa9c01b738a3f2b830f0d6995efefc4aca37bab1", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "cc779dfe2e1f5a4855dc1b7b14a667a7d54bb6261c36ca85ef35145d792de4a6", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "4fb51424010e7d0af7c4fa9ab4a394818fd0bd45ab9563b40f877f526c413966", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "75235f0b1d26bacc23d9ef5ab1a96367468df35df02c0f5b50e68d3f1fba24d7", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "a44d9caec35555886530dea4b0c9cdf3680ee0a9376af540de8ef833606d341d", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "aa35a6ce4a818e170b73811a44546028c3e9a11ddc02a3cabcb7def315cb94c3", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "b18d52d570adf6c064d1255017878673bdb61095445c1d895fd5e73465d6b6db", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c112a73b2fef014a72e22cebbd5d0afef6ecff793a13576406185e3154087ae2", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "26ec618b527cfab949b0351d0f3051ea96bd4fbffedf5e80f42a83fbbe8e55ef", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "10292f93626150bb9d577a722f0285ad6f035b6a3ddbd4a0ac74e752aeea3420", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "72cfd142d64ee4dd931439f228f4e41cbb5e5392ed4062282905dceb214b081b", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "095d834bff980fd53048eca2b1cb9b33fc982f444e024f87dab86123a9e448ff", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c3bf36c791cd89400215227269b15af8fe745bac7288bddcb55f6f40e6d55336", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "6a998c32720206579cede7bfe87fc3b9dbd234fc4f1c5ad913ee8497ad49c03c", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "68423c98e45017f3825842a5a52b1a25276c6866ee42c38b519bda7c330c2cef", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "2bf12a91598cf796bff0a4904d0248f58969c0763dfc9469e18955a11cb216e6", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "371d7e90143358561af69f7331451cd6c2627d0beeb9311b42d020e7f738c4ff", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "4869b681704b4aa1ff6196f4751c9552d244936b487e7fd0b4c0d3b13b306fd9", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "868fa3e4dcbd1929415e2fe5b34860389b7b7e4338610fe64ca1a66e4bbad003", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "2a9c732f9bd35992689d12e9b77afad0613aff8cf14cf94f1685448a33fa0e40", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "aeee848137f710edbf84bea5adcd7b3827e988e9182370d1a3f5128855106ac2", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "eb181260fe1ec4b3bbb731dd2743f8fc3e92262478e14133fba5e81d162d8524", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "98bc4dd67507b01a4b643f3c29765516ad3a8153ba6228bda53d9cec81a807c9", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "ddde084297a4e15dce5b84d48eb4dd4857b85200227a73bd8512930999922032", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "b298b43aa3fcf2990faf5dfd40aa79e6d7a84a6339c052b36cee2f6d02b7b4c0", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "6d705ae1d2dff663c8abe3601253dbed99625e672386fa8438437dea3ae9e15a", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "c21aba21a3e4407cb0a55acf80acecec433c4eb28148743fd2173cf22b92d870", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "aca46f79c66253225751e6faa5736bcf749ba4b3db0998b67f9ca16cf1ac0afd", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "8227b67ec12bb2844c2c5110e588df51d1d41f79e97dc203baad412f9f41db49", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "e87c7abb7b86ad3216781f5177a3b679ad6f885cf4e4958c74aa7267455fc907", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "b54064fa1e749f3a020d0424070722836e1f89297b658eca52896cc775ed2466", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "fae04971758c5258be8f6612b0739b11e1fbaba9138b519612aaa8b73762ce46", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "15a5069bf3d7f2d3fc9a91e0b36497102d52c2dccf683dbed5cf74a381b6ac16", "target_hash": "7b10c8e2248ea1416d5dc044cff9134ceae0d4c9949fe00456267cd37a37b144", - "section_hashes": [ - "46236e44f648", - "1791873058f1", - "e9b66dc37894", - "e647193d72ec", - "df5d39f028f2", - "70ab4194808c", - "f291e85f40b6", - "9295329348f0", - "c72fde4b840b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -31947,1190 +12411,332 @@ "pl": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "b951455d662dfc733253d11285b5e8f41b9dc28c74a32c44f6675ddc2045dd9d", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "81cd44179249094ef371bf8626503fb544d6889a074a2739b5ea84f5e5dc2d73", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "1a7bdcec0de67f2bd0e7441f2c70330968ca21a8c00d4200ee5737f7527cf008", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "cd37b7fe1c01f40b4b129aceeaff787d75cfdf79ab02e3f08d04da5ddccb8043", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "98cf8dc6f7e4cb377df9f80fccab4c94ca2892fbe608a3d8cb1e7c013005e364", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "f952adcce736c6089a26db381a48cc13f942799b99f76100c454acfecb403524", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "e11eaca4982b2226c98d4b67bdd579c60973eeca0cfcf147a522fe756ccca581", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "6dea437b94acf4e6247776b16e849a54bc6dc7d5e26901630285efeed7d995ed", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "582a0be3ca7d1859e94eef693e4656c4d3d55c9dcf64232a1831f9c1b9567a81", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "ee6e18a7c8f9c1b1ffb37ac4cd16f02b252b4703b9c455ccf838359f471918bc", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "7cd7aedbf692ba8cbae3098b1ef9f72a96b4628752c33f07c63dc2edfdcf7cb1", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "8b17b0687935900e52320fad6bcdd3e1af99b11beded5060baddccabf96628e8", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "a88d601fcb7a10001538bc1ccd98d1c0a080bfd873240a4c40560dd6d8a2cc11", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "275e280388fe0e73e182e79dbbd9935b659f1b3c06e0bc63559ab8bc8d879387", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "e3e40adae2d7897414de3f4da9e01edb5faf00714282d6d41d90daf017e3e527", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "9e9d332bd5a59fc049e45a032ef7240372af037ac8d4c00036a78ad85540bc5b", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "6abd27901feb7f174f51899efb3634719b08b75a10c4d0769a8fddb4119f6c6a", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "79bbb2d34ea652c8315652c3ba1f500fb6eb8231f6c0d785b0d6552ecc08d346", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "2f1d552356c7006b2e66c546a8b76a59110ecaca5238076d4eb51e6854a0fde7", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "e8e66ce44f51dba3ccba9233b73f48729ff39e880ab49783504dd514a565e852", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "b3ac584c44057f477ddc1bd4afbb3bf2f354cc42635e10582e3e0a6915b88965", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "f3ca402c735038902c4a95b50fdf5359e48b39c14aa23393d232a1075e7c27ea", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "2ade8c1f4ebb06c2b3896b0a72d8246aa920c896aaa2790499345511bd161e9b", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "3fa4410fca24ba8bd5b2c3bdfb399a5f74f1d380a06f28e744f266d768942a9d", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "f84d6ff7572747b12663f3f35c42ee1a0613bd3918933bbb06a7ed925b848872", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "d4de1772cfc0fc2de134283cafc11b55fac39343ebc623233c8fe92ef892ed96", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "9a2d466784de8d96a560cee3386455c182785c7c85a25d076ba4eb490c93a219", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "95832eebab3755be20bbaba675c37df08e4c958857794d761b4b664421b4ea71", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "a073bdf09c975fb52f784e84304ab1535325d4503fd5437ad2eddf8558770279", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "8cb1fb86ee9ea77488beada24e3f852a9aba3a9e45e38cdbae6da0bd4156fff0", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "2fef4412fb4e58e415907e01914a55e444de4c4f088b6818d6007c61f36dfc3d", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "19f4c24ddf4b5b6bf9fd296cc84d750c073cd9f446829e80c9861cfc4a6d1221", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "3210b783d346d6ca6be67b85c0d2eb9372046591e37c1a4b3b83776224667116", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "3c18b6a1bf825baffad66530b7048bfc2350bf40a1c8c0d4002ddf7ada7fd7ac", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "3fc6c0d4d27c07f94b70ac32171dd81c71fbcc6b9b855ed95e2a55a8ef8dc1bb", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "7179ccc5e7b745c56490cc793ab89028c12c626e9730d8b2aac1923c278b7375", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "db4b16211cdf6603d10ba6f3029a5e3fbd265f5b8e2be1b5562db09b02c575b3", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "fa712f1b72a99f58daacd4ac230d810abcff31aa088caa0b2406806864bfee45", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "d5bd620af3738bd59fc2f50d092a28f14ae7c2ea3e7dc19e578c00d3769a9a0f", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "344cefa5474e1954698145d46b61477f00d2c8f8933d4e95da98a99be3e3827e", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "e35f3f7632c8a640773e7ff29071f2aac7adcc465090c1181f84b9f26f822bcd", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "f9533c57adae06c7bcc4bbda53867ebecd5ac764d16cca00ae28156d20a1ed3e", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "eced7e3f4d6e50807e1fd02e23999bd13e8edbc6b4bd6fd733c2c3b6851f35f1", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "ff402cf517fac44d796fe57745b981b174bb69d6425367b8c36868b7844fd002", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "dc8c6912a3c1ff40944dff99ddc3790a4f8351cf673f8d42f5825e18cc3939a1", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "e04833b1acfd818acabd66b45b01e470eb7476fb0934a021a7eb6af27ebcff46", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "9811c67b419c02b27be3fa4ae86af149c78b806895f02247589652c2b70f6963", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "f8d5ab9fe31b67dd64cc93625ee5778b10f776c1c4d02c1bab75029c8f0761ed", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "5d253862401d373d9a4c6e220c7b56dc75e3bfcf8233fca7b8cc456b2a5e67f2", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "198f0d3cacc00dd02cecbc0a94cdaf8c4018638171436312855c06eafe03fb6b", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "ff3bb71f61aeaa6bce39c78840a0fd702b76e423d0018d4c5ed4ecf8bdb2cdba", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "8311498a6ef18f0a5295e5397be6c8fe04e6fa0993b8b5d394194742a88ebd5e", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "0c5c15daa1ef4323443d43db08a8f19bc3483edede28e062d53c862a184d15e4", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "cbd9fabade690bfd37bb12b863e5822d1230933392e55c741f47ebd8684e9cb8", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "b4df7b78f78c670f72ad90c8503364522fc4233dcbed8408aa50e7d2fd103879", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "38ba4c9c21d492ba5e451e29cb4fd73d13b56a79432773aeb56be33918f5dff3", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "8054a10efd33d7e9d17e08366eceaddf30ec9fe3b1a60b4776cc85cf4fb9cc9f", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "7fbbccbfb7f36c287ecc48cbb924636b81531244a8ebb31a233e7c788b712fd2", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "8a2acd2293f81d7294dc6b78434dca3921d9234ef20d892f964eccd323ff1d30", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "8d4bc13a1bcd50ecca02ef0a9f8e04bea7e24b7687f895e099579c2d9dcbf967", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "576665ead64b1b844f31d7ba0d84b2b97284a45afae1700a16fdef9b3b0cdc19", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "6f81e74ef7520de2002303bf471f7d120b35f327a1542a5674ac466fc6a42be5", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "74b597d88da8c5b1392042df69812355c6df18f6fe7c92253617ca8dd142b122", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "cc3e3bfb7a8846c866ef4fd9d2222a29a6afc61ad6c58cb67216fbbdef026778", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "bc7432db555da24d8a36d5d7aeec7c9ccaf5af7373974ee40b9bfccd8ee575c5", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "b6cdf3f3b3ef2a5775c6c7a60e1b464650b69bc004438c4c9b3aceaaa130504f", "target_hash": "076ce27a20c12890498c2d261d83b9c8c23d573e497723f26f8c230fbac84066", - "section_hashes": [ - "f68eb923d817", - "2b2b4ca971a1", - "f0422bc4ce9b", - "f0e39a0dae61", - "e42ba05f102c", - "50a0bddc3094", - "d66407df4848", - "010861266a3f", - "3a3117775f60", - "df09c6a633a1", - "69f5b6175dd2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -33140,332 +12746,332 @@ "pl": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "b87d0844cfe94a36f6e321ecfdb51e5fba7e65f0cf663ceaa966659a0b6a825e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "d980d3a16d52c9de1ce24a5c4ba7b7cbc6f1615161949b2ae550fb6b143445ff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "0baba33605a207d734f10f8a6e45da75bb2d045883e135fd26dcfeb6a580611a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "07d3341f49d9532a424fafeeebfc0c0e32c4945d6a7f288b2bd5d7fd9dfdca0d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "8acbad6f254f09d36ff07d5ab5e3449c863b4fb53e99138267ab2aaec954fd99", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "1772c21d25f2df6261e29223130dab5d62e2932b5d141fb166820c781974f540", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "a880a72e1a45f3003d6e85f70c75fe3e782f751b7b3bae07d2a9348a3b9e79b0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "65bfda84561b7d6bba7d195135a004d41de2097974c7066d7fc3c3aafb005ade", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "73d698529fae08c105b3c0a3df7150df5dc6a270a63c3c5030549dae85faf7d9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "da33ec7afc5222a947543c905ab0e6d0b43836e653da4ba46ad19d33bb2c1896", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "6cf3745daadd62f80289deb916b93ef8d53996885cd2b7c8b2a3138e7da9d2ff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "e01c6037527044687e07fa6bea55914ba9a3ed60aa79e133965c967d027ef92a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "59957ee84cf0f751581353763bb05b679afc63b7c493795d734cc4b5ce6e674c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "999b4af7297809b778d5b56d9e057f1338378b0cb35ce74c9db988e9c808d5a2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "c17cfbbc29679c37c1ba10e291d8ec8da54802d8c195483cae283c3b2121b9f6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "1f999b3a934a0c6fa4a50f0aec41ef2bc62d85a3dcb71e7715b8a1c25660bdab", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "5476788d3e62913715ca0e1dcc8a4b89b8b085d89a05e76fe84c0061afddb072", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "1c16ddcc5ed3404b4bcbae215c744c18214082ea2ca754f601b451a393539a09", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "9f50e7d9061344bdb38c3476bf462abbbbd037b16a90b552e268c793f8ab5f8f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "5bf879328c8eef5eec5952f5b0f779766cef4d98eab3b8abf90b174600c49962", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "82dec385b58a5c3fc71cfd182975bbefee2771a740a97a52ddbcbe8645538af6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "2fb31d228ac2bb963f9b74539df6fc9fc05e8cbf7e7152b09f51f400267769ca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "5210073ec8ce2b8a0ab34f25bd538cb26dac5f7f7915a06d68d777685fa2ebcf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "f69b0588aa215a5dbae0cc2514e58af1b9194913a9d6408d7a928f89c4632c3e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "84b6b3f6f31e11ae5bdc094ad9f08701fd2a0ffd3517541da14854729ce236f7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "6e85af5e20bb5e24d531e7c449374f759819fe34cd44c5fd6dec99e427f912f6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "8d731aba56467e8b47f18c63584219366282185c4222efeba2bc4474d552cf4a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "6eaac3d4788e3a73ec93a80142a99b59a4bb3c63e11e624c205ec89b747df3bd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "9aa63af90267a0a3d2acf9eee770f7d97138eb5dc6c26e4c5515af2149f75fce", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "81edc86fe3ec7a96f0d6f7bc712697880809a70c2c49c88ead23af3a94872c07", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "dcbb95b0d5f42a3f16a8704207e3cb213bef8ace5ceaaf22e690a72a08da4cbc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "95a222351b400d5dbb291ca9debaaa49535edda4f1b17a6d7720989e67c8a846", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "418737bf6838b574c94846c0d36edcfcabc5e371cd6c96d36b1cf94b94c809d5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "7aa0765b1b8a2dc922e8a2d4f1b5ba6563e01db2a15264aeb7b8770fbec48bde", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "e9eff8659ada52bc467b1f1b184b296ec30ac9c37ec0c14e39eaaffcef2c9393", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "33026cdcc53c5652ab8dca7a4d9ba7ffb3ba92bc6c1ba494c0fb82f57182191e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "d4ffae986699999fb322ac5e615b2b79f3aff5851924716d000917ca14f85306", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "2a45e9eaba65ef2db9d220531fe46b9aa92cf4135c6b42135db4c4a73af4f32c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "7ae969ba4ed111b58187c0c1f0b8d2b8717af15f6d541f5375f4cf7a2a11519c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "2faa3d38c38bbd157ed7e90c2c47ca082274028abca0f2103a6a990442c1164b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "cc47c55e217006065b0f12af15c0b47a857c187beaabb88cb57124069533ea7d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "24ec09088d0e8eafa371cefeaae9b20a3ac8db4e8a7b9aa649bd392e5c6d0fbc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "8d81fb2b198f4d01f200d647652f27585c51ceb1a7f982af12739c7fcd65b50f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "3de090ae0ed912c4e312ab911cf44a48a11613ace0cee2c51ece82113119754b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "6205d7fc18aff2893f4e794178d95dfca7fa66aa728661eda7c38a4232006dda", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "d890c6b5f2102045d2bc8b207eea01c6e3de11567fd5e2b92859409218eb9fa5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "95ad1b4b056a5937dab412fb72d7268257b49497b4c4bdd9f79bbadab7fcafa3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "7e1876a17545ca44175b6e1de446d4db6a659f7f5dc0c228aaba55931576042f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "0b74854733222d07c31ff326e6944f84a372c211e5332976aab9a4c4b5e407ed", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "5eb236258390c13772f52ed5993931392d22ae3567ddf4c8cad723438d072cff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "e92afaf7e8bc44a007f5f496e0a11c42e0d9f1205e7a0f47f1179fe5253ad3b7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "02682a49d3a1e9c848bee5520bf6989ed3ec2257f3eb2185c9fbaf084bc60bdf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "2051aa07e83546f659c9df11cb7e6dc3d37d911364019aedd72d787436afddf9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "aaf1367daf220413817c637f558f225fb05543ca789ce23683feefe51f426967", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "c6edb30eb8ffd1b9e1df90c7e9216ce19d018df41006f170578119736560cee5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "3630da14c5677f44845b3ae526fd48414d0e44b9c597d88e7c07d524c78c67fa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "cd399f88e2a3670d81574df4c7714e525660c4628f752a7fd9af8c2fa40c6aad", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "d36a507da7d8730ee4c4e63c4f0cffaa1105778fa7973de1023f2c1a05a50266", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "3f9c9081b8861d6e3d1c673da7335a3cca0274dc70aa024c0f5795b6b1803bfa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "bd7550316fc6f1026b9cd454aef6137d740456e0023b6fa6b9168a93b477364c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "70bd7fe7dd5722ececcb90973dd0568deefb73bd2d9425d0e447099ed532b993", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "8139c2a72b36f6b1f5987029613985b7c65205a4a945e68b8feffc2a33af8feb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "e204c01be2e52abf26613a805e8e2ed6f2d6576f35c77a5d47094621eaed2e7b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "060c78cddc5eccad31efa0c15954e5754b292e98c41714c0ab1187b2a0b84690", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "d2619ad8ab092d959d12191f399188b295b8c618f9e2f0ad2f8582c75fc1c3e7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "77e3f17de9a41057cf05da24abb68bd2f1c8e8831b1b0b455d28e91815bfaf74", "target_hash": "45d49bfa75193bfe3906b4761ec0f48cdd9917287dbc36c84cbb7aa57f5a17b8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -33475,1177 +13081,332 @@ "pl": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "e9fe87f5a07457eca8780cc4e25647c70e8717a9f6a6ffcf9437331907943fc8", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "8a7128502b1249bcc2b777de2e39301496da3a944d8501121b95952e09cc7259", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "be5c48cf3c519f0329f1810df9b870c82a04f96678ecf31cf2940b6341a72de4", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "34d0ecde508b6b535a3daa57e74dde10bff2488b7e6413b29a511357235f3fc1", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "b8291eb540c028907fe46baa365be4142bd086977978c7d13203f2e0a907e2ce", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "0d4ff49af6d9c1953bf00e352041c2ca7c8989b6182f993ed435394c27bc5de0", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "87034f86b26ddc3e1258277692e316580ed38a1fabab9c9da443aa2faaef3184", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "5147252343408ebf1c1de0ba1c724aa34dab09c1e3b8fc736a881d47c312a58c", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "9038028882788062b98daabd38deae8b9b62c9dda8775c5ef0347b2d30d8c734", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "13ded0de831363c9ce39abc4f1570d47123d368ec7d31b9da79f259987735e8f", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "d2f8019f943c735710384145125ddf935316036131f3f3c8daf40b1581240f13", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "1cf5024e837123cc7114ac2b0a82e28cdb381bdd880473c79d5749f040ab9212", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "54a28e6c45c79d5a964dd53bac048ae8c7431ba84108e0d0223f8e2b510a4b97", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "e270342b6a671a626eb8696a5dd05b1e3f4d1bdf44265b86410828c4abdb9e36", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "61b1cd1f60380c4c998f6c69342e08ef97ca4e7cdd08f927c66943cfa948d11d", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "419d7cfbdaa0a48ba384ff5bad0c7666326711861129b9015b597b025e41b1e2", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "aa2975c899b8e363e75d152b3f95d4b7c1e41dca775b539821c98ea4433988a3", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "d104806e650881145be311e1697efe2acd82ac2311ecc1c2ed6ab7242002ffb5", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "94fe108d57b165e4a4c2f3b1ce284edd355cc69725f6c26b66003243b84f20dd", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "cfb5350ae0ba212e6f0d974b0c9267a5adbe1b01ff756cc8b134ee9809fcf15d", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "b24c39b759a178cd329cdd41032dbae5597e1589d158fd0516b350bfbfd0c086", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "7c27517fbe2ddb305d3a39ae90fef668ac890fa29d75109dd8f58c26cd2518ce", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "5f03050e7f5a546d57d8b8d543e79d6a06c25bec705a9a3750a7ae72300ea1e4", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "8c281d1061ac86870e2bc22aee2a7b08d0abc5d77f6fc2e80017442ced536e51", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "c93e16eaaa1edc1919a254a9b43679451ac544125c7240cbb8e94619a51e570a", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "9a8d2fefc111055314b78acae0f12ac3f9b1e995595017dee4e41a9f14a8a9be", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "78fe518b905170be1226776fbb7e2558b1ad908a2e0ab09b3738f05af7fec694", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "4a710df706a6fa8272284c70e09c90f8db18b0f0981272be4f2bc38e86ae85ee", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "3613d203e4300e1519ea2d00a3bd42600b76a7977360dfcdf4b9a11fd31c8335", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "52ab9f2a621c7431f564b57ab13883b601df9c2f0f580eade78deb4f1664598d", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "4c92808f97fe2378b4502afbb39dcd24b1f59b511a476311e739517cc7f75af5", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "99279f34e8a2f65fceb12efb0a52ba3c4558f9b31e71f73914fc6c74d0309321", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "8fad4f8177cac23ed1060ab976fe34dbea9e2f35451638f234536e97c86a580b", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "1c6f021ab5378270cf5945b4507306a174623b9be1d0fd4271b0048d81352bb8", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "32553a9aa2308115f3de39da19c3fac735ca66019d71b93d677f4d8c3d22a850", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "95d7a821a054d053ae82608704a13265f8a47aa1b37e5396874ca1df577e7c2a", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "4592391e7df75a99f1ba5711dafd0c15ccd134e6762c7e19cf5ebebdd36ce372", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "8a41034b1bf8456f09cb8edfdbb7368ea7ae7ef52c03f0277aa33b4cd4cd6be4", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "39ea2f94824c7411128104c3ec0e30de7a682aa0a0a1c5fdda93fc0f57e47ff7", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "9837a8fa02fcdf6ed1a3019385a0b58906148dff477057638ef1994ff83dfc8e", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "350862ec812cd0ffca1dd476aeb6b8b61ee1657452ddd2a4aa513f0a559377b2", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "8519d802fc9a231ad60c60c27783399944706498dcd78a6aa815e60def3b84aa", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "7ba5a979695fbe749e13ed9a74c15e37577db27348c71e26c028d6b2c26e29a4", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "f76bb84decde588f02146a51c9b71e2401f6dda7f921d03cdff22cccb945edba", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "cb7b3645712f1c8f7bc05a95b75819c8a21b140490e7c53982788144018c627f", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "552d647def2c21654e8f03b657956b0124593683a7849168af2c8ade46b2aecb", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "b84d2b3eeb4e7733c235df2665e9dc9eb7fa543da4710b0e74a979cf217c04e2", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "a012072e913c23d3c865edbabc69a7ea8f6fae9c03a249c711514080c95ea35c", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "2d6d997e7768d0be11d148f557d1435f6f565c9d3b61666bd4168d068e8eb62d", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "07ee4618e0c52b6b8f6ab830d4bede15664c6e719f5ca5e2c668cff97e6ade15", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "5f894073915747af4f2d69b6761471d04abaf0201690d24dcf3716c8716c73e7", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "1eaf57c8c32d45237ff436e348490c22802b9b3cafa9878ae32e24ea8368bff5", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "779a830e8f2fcdf3eb18cef007b3789030a754072721d58c14a4dd644f21e5e9", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "fecae8dd4f6c92bc70a2529cd53f7fb2bc77c252ae419dd585d3bcd670ead61f", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "2494929a4000ad01d6f12491830de7b966344dc5c96d6bec151e41c50c92734b", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "73af16d425d6c0bdf7ced43cfcbb081181eea5cc3192d1aeea9c05b4feb51133", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "05d6c4cb8c2c6e798d128cc184ae468576a6c5a637f1fae2e8eab5575d649517", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "24c3d7bcabb5b9f028ec47f8ef7544d8d7cf75ef8177298cd7fbd36413082b69", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "b1b1f60656bfc108f8226bd854978d3ced0140468ebf9b03113eb9194d366dfa", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "054427cfd09d760438566687bb55011e55ccb6201c0e19090f7244289f2fd679", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "dbf11a21f4144183d234a06848749de10724b6cd93d11f7f5cffcfef63e73dd8", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "261f56119e5a5615d2081e36ca229035a0e260d53366191dc4efc57d12939916", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "b55cd73716d602787bea823e5d4f5504e4d81d542c472a50180a414601822293", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "5e097542d71375f2ccb4a5d56c80e6b29f61077cbe9c5ded82a08cc4134b1ebd", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "7d36aff0e2d8c4b1eb4dda84cda8ca7d925eb9e58eca47a449349b0978937c97", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "19a2881aa00f2636c5df51fa1f26d87a84adad1ea95fb47d89965549625240a0", "target_hash": "623b8329e9bf4d12e54c2458cce74a0c1e9031a7ab5ca45abced28cc5bac9285", - "section_hashes": [ - "01dfe33f13bf", - "99e9925aa7fa", - "24cca85fea9a", - "4c810134d368", - "b3186dce71c2", - "b6f5bff0491a", - "bef9b42ca9b3", - "34440383caf8", - "6d6742b4d721", - "ffa17c5ac309", - "b68bf7eedc90" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -34655,1177 +13416,332 @@ "pl": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a6e1aa14e6f570900e9d1916ca8a1c54d8885bf2986df7325791ae5442fae34e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "b915d5a65a52639884293928c5d96e01e9df4fda526d8ff557e587a39b46c131", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "4c1512b3eb9d5caf00a58d58b6b36e0da8c601d43bc7365c33ed683431f499a2", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a51808b127e46bc712528528d3bfa0c47dae40d1e52435dff3a4cb56dad3b185", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "35f556232e693fcbd46c30e7373919b7956569418bee6a9e0274dfe3d382dcd5", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a9b6fdf45546bd18ab88e2c40627f7865b1634b1eb20d997f15e281bd177a8e5", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "3a6497478e7fa885f6b6657188652d8434f12f004b39b6b4a3f6302d937fe865", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "23df84ff88bd402c88cca99cc3ff3337f315019341be7af3c5d3043cac2a3411", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "92a84f6b7a60ddc70af9734b6837cd061abe608c518b0687044ca8da2c7adc6b", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "87aec39854bcfa55abe560437df24f9c84d185b27586e2a2e6c9ee1bfeb98eb6", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a64dfd61e11fbee7252c11098181177f08f2001d17e831de01d51e4be3476307", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "e155f22f8624831e66b85193e4761ee725daab5c0c568f5cb1f196b549588d47", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "aa8ad270af6bd0a917c3fcb771d6d22b277aad0ff3f0af2535a8fe52023fdefa", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "dc137f36712800e5eed51eccc74bdfc4321365a1b5c84b09b2f2170400470038", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "4eb64f87c517608650fde61ba72ede7f1191d1ba090ae5b2c3dcacea154cc1fd", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a44b342e990fabbf8a4ebb7d41a8d6eec6c3cc783f8e981093d0bad495cccc3d", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "cb58b96e348a57f4e5eb21c99f8640901ea0629ab4a9712bdaebaff8e3d4144a", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "d5dcb086c280de908bf3b4396901d9cbf9e248d068a546a2477742c34e78e772", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "e7d30083664096f64f911c41e941a4fcb575a93878a60117072b33a3f457c0c7", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "c3d31584347a753fd77d601ed295832f0e7c53c75635164bf66c1dfcfd37de04", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a65b637d4d5f5e01d894609a48fc6e10df1875734416d08623cc5ce387be924b", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "e3c3bc46dc691143159e825e9ea374c8d36f04b5513d186817f4068e65c35aef", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "992511f585917cbea002b109f0a0638addbb4c2bab1a3f17c2fdb5629ef3152b", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "8181614f0ae819146ce1e5831bf2ae014e45d65fdaa230e98e8a186fd6ceb6db", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "c94edfd6601f584acbd9c112d685419c771a679f4cf50c79b724cdaa38f77765", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "64451860e1aa466dfdd8b0f7325615bb2396e14d7285b4fc34bece36b8727955", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a9520fcae6b0922d2538f24ed5779f9a164b5f2abc15160eae1361354ff2bfaa", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "78463810d9f4f7ac2ca2ff28bc0f2d0b3fd9a919452064ac7468f52fd8e2f303", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "b1773440fee826dad369840544a9903db170da1dd62c7df2da3c22c7f1f4a97e", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "720d4e7675e0ddb6e0eed79086df619665b69a5bafe5831a43dcadb4fa2eb02c", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "b5d2f9025768448572c5346ed3c8d5dd283829d9cc1802e45275f2e0ff554ad6", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "09be644799898aa579767789f8d4ad4cf941a773b104b56f804d369fa35cc550", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "24816d6a79081bd3ee0a0fbb8dcb732193e8170273c477ba07b6d62b74af6ffe", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "151a904f336e8bc6f475a7f4ffc06fdb5c279c0883aeead72714ca330ce9dcd1", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "317d61acd317ce2fffee2cb1db97a891268d9e50b8a71da9e9dbee9a4d040f16", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "b936983399a8a81851584eb76af5a22ddfcc7a882ac8b0079b5ff316f0ffb352", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "47a9d2585c1c109648ad717104ac798b066f8a9421b718d47bea08cc9f269b05", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "ec9f7ed1f1fc8e3a058aa2a1f650ac2d04899572426a5b8f1b7f7b23e73367f3", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "978c26bd9f0a4b43dd62590438c91e0c84dda5474cbf029cdeab019d0e2d6624", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "20cb0ba2d253ee0b23a4c81fece8c9ce3c254fbfabf2a710b067dc26da1a2c29", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "e115ca17bcd4506293ecd5b8be929ae83fce9e8371c548834e28b142aba6f7e6", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "cb7ddaf6c7040dbbdbcfd40167584a6a8194db71edfaa5f23e858dfe2e5c5b73", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "1d4a706cb0d29891c99fb963d6ddf19c6127c0b26336d952aef32d426c8b530e", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "0374797b04fb81147b5e8a235c97bb3ad08d48d7bd786ff14a3e7bcbf9202ab1", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "e1b64d8b77a07f9354d3b3de0a297a0ad3ef3a5cb0fd7887587767ca56e3bdd9", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "f99b7b381e38bc882d0a71f7278b548f2de730b5b7a8a646b1b8f1f5684399be", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "97ef0eb0a6edf7daf0d024bb4c37d257f73985222924fe51629f925a12ccae1c", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "a7417726537dc6afe23062cdd8deedd8734ceae95bc37a8f2f24e5e722cf82d6", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "d6f37b699348cb4cf71cde7ad1ca3fa0899d86e2499c7b60bdf9d6682d2ba8a5", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "25868034bee8bba17611b133aa1f38211457580b54efdcedfef3130e810db4b4", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "0a79309ed44d71cfc412c3bc0747dcaf6093ae057fa654281c52a885e5997814", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "3ce995c245c04fd27b73902aef5d945a87fd17fb8ac4df2f9f29f1a51b017ddb", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "3a8a22b4d81be8f386205646368b3d10a75e036d14616ecd640b6752083d3861", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "7c459ed65f13aa64378ccf5fe2cfe94b0a6504ee8b3d88ac27bd0171da9c67a1", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "71e7db3455e3518db182dcc2d93116f66d5fe2587f87a9e736f8ac2dfb7fddcf", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "fc6eaade417c741a06b71e0485ddf6cbc4678b30e0d0851e673b0a8616671ae5", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "802c8b731e4bd041ad064e076025965b2712ea5903638e44287016e5b7c0346a", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "e24593ca78d83fd1ef5595ba65e1d8e4b9f2345f53c02b26f84ec696648c9746", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "54528a690ee325d15bbce3a65458aeec5debea2cd4963c41031a9fed8b988fdc", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "027bf1c0348877eceb4b95a3a044531c299c87814ddd23b883457d1c3e445d5b", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "8ac5c71d58d1e8e670871c8dc320ba410b5c862df1d3ef869ce2e18279909df3", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "f4b4670c398a0bfa52217a7b09965e43763b76451a83def8d9f77e5e63cb0de2", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "c1e9268833340d3bcbbd3f41f185779bebb78ee3690b0004d0a955e0d2a87585", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "9b924c4c478404daff066f84524f1d59eb53943ab283623f68308ba216182d59", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "f957abc6081dd94e0956ee9a96d8e3f18abf983a68afa9fc6dc2ff347bc7fdef", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "5f289d55d0335b6751e54c52f185649193a2e1d923d19b0b22a69c2a6742e064", "target_hash": "0c5aa6f3826c2fa5695bb6944030d459736836d0f2cca0f2f1133d7c147389a7", - "section_hashes": [ - "726a423566e3", - "aeafa2136818", - "ee17702b5f02", - "bc4c0b27b689", - "48bec6dcb675", - "85079d715969", - "e924e07f1211", - "197621a0ac73", - "e601f161c16b", - "43ecc9840055", - "392de54fa699" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -35835,1850 +13751,332 @@ "pl": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "3f534f83661d5aa2af05ecc389c60ecac933db72790d54f368127d140376a11a", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "a727cef7c145b1d06ca195ec15e2ece609a171a0536f79826d646e8f6b06a6c8", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "542babc64190a0cef2d2d1a80eb0b4c37223aa2c5a8df90bc0caf8dfd0dcddf4", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "966987acda58a3328aff713f22b570125e8f148372abfb641e19fe216ba636d0", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "03ef12e055b3afbb1771286d3d9f3098e49a8a5368d1ecf5c9a05cafc73c0c32", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "efc4270b43ee66ccf8fc8c995b43112746fbceee4962f0f892507a94e5a0b8bb", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "1c5dd403303b9ad06e5af2e7ce0edb357240a47c95975db4994a0eef61d9c23d", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "ba83288ae6012ea6fcb8f0136b32878a1ab3ecbefbbe6e9a735f6886904b6d72", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "453dfb451a959e12d7f6e6bae48479eb2ea358785f2afae39f94ea443f99a120", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "73e2e9f9dc33936e4fea02ba6210512022cf59e1d61990a8fda4a97c5d907549", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "f5bd51cc3100c204ba12be89ad444eed2cd36934d07ece308b581a9a0e3ab3e9", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "da410f01e7871ba9723bdec80408494cb11d20230b398841b0e6cbd10ed3549c", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "d3c134c6e484b4085a5cd19c2c1db82015b6fb420b387cc408fb57ca507b4f86", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "f4b904295ae6f381b51bafe285b317cc18f2935753e3c7412ff6f59cf59933c3", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "0d91e2e310a5a02be8e75a2aa774f74650e40b40412ad2e34b7c613a166117b4", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "13029bd334a6882d786575e3ca53e81180c584b8e63720aac8daa32ffa8c7df5", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "147006dc34c72bd56fbd37ef012f4d518b869b06dca6ced83b45de4bb5a052e8", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "baed99baa0eaff13a6e1a8aaf618c3b5e5fb5b39ddfecb6f5a61e2c6cfba314f", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "b898bf88a22bfc632451fea6a6d3b44e706753173bd64d39a1338fe6fcd6c191", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "a073678ba9c8dcd453e032124a2cdb61f38451645686a261bdd26ac7a1dbe30f", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "2c7431d2dda484697e78dafa857fdab0973fc16ed7e97f1d0317abdd9518d28d", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "570092a34a93e480811a07fae533f2d5df189dba5c633581bfbe6c0f5f9718b4", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "8fad09f66b3c43a207447e1e9533079373fa31ee19d512a8b7a99c02fe6d4344", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "b8aff19ea71158f509f7bea334cb01d15b9d2b5af30d3af9e1efffa71fd208c2", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "32b6e164564a2cc5d77973e2b12ccd6e46b3d8899386656d2976da333033d100", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "f148d33acabb3c1c2d10ffde0c5bf547c261f76efdc23311be5df51fd62cc8fe", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "1035fa88160ecb5a92ffa1d97d184df741bfeebe985596c4f38f3ec7fc78264f", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "53b170946ea1d1372aed8c194f700efd4ea6cd436f12f58772a272b9d8753a12", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "35042fa77593c4fdfb35034d1b66967b6382443b1e471486686b5943a0ffd1fd", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "89a241381099217398f731bd0db469702c2c42212d3a8c85ff6c9f6eed5882b1", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "790447f931108c5a8c1dd27a79f8fdeb2e6be10d3ff32a6393c46fb887976e8f", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "fc5dfa016207da25082bd1ec79dc5cde36b1e108fcbc3589bbaa06349afe0559", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "b4d829bcb8d6f6c5a97db55ca0c6b11db4b2a3c643d960787e9ec108195d14f5", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "3201c151b812a381b2d839da55ad7810472fa87da4f1239bdb483ab214434ba2", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "a68be2b466c4b0e62218f634c767fd0e0ddd7e7ec1332b08d5cf22c975eb04ba", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "638ddc0b6420c840da7d3ee581e9630db60fc5f6a26e7532bcc12d5123545627", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "6dfc5d6e386536425795371e8c554a67a8e0680289517cbda941302e65538a6d", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "e4975488681db9405a141abb6f8c88857b9a85505a6ad4d67540abe9a89cbdbb", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "fb9eab34500e2bbbd675f686effab00a6e6d2f67a7625e79bfcf05a148670796", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "2bebedd4de6c3c0f429f8073adaedfe05bbef53e8c8e1d5ebbb534a32c55445b", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "d7d39c2799212882716a59ef209d6ace18a9e181394f85a1d127efa456b8f7dd", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "f1de6f0bb0598a8e6ce54bb933b03229258b0bc5e87a5711ed640ad1d3787b62", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "a1109c0e76a8313618b10a76b5dffe3f8b4cb704c33371c1830a779d73f0f7af", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "3d47d5b1f36fb4b17f8661d2e28571bc972714cfa4a21d66ee239d891983b9d6", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "2da3675d483fdc98ed89cc729eefb5251aea7756550b6f751f1fde0f720713ef", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "62a2a517fedb97b8f4fef356f8faa9bb7243051f890d82649191dbc3190d05e3", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "ab3231ac4f0d39accfb82b6c6af054997811f6bcbd79d4ac7201a539202e78e5", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "ee68ff2122e88620b76414ebb2b2675ab8723033f0f5ecb4082e26d07d8b162d", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "2ced1f0ce0f28c3618c0668cd397aed5884497081dd9a46e43312f77631ef268", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "791696b2b3eeb6eacdc8e84c212507c7ca3741e506920852561889deba2b7450", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "09b668b95844394037cafbf907ecdc8e7bff9c801ad8036af527d5f5ab72f8f2", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "36d2774858173002f11baec92bf224a611f11e599f5287277ff5a0c48190c618", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "3beab0b9a01ccf73d8f4dd3624656d7ed04122c6f732b2a0d59e26c12bd08509", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "021e698e1e27e2428f4bf3effd49e7a870a2d659590f2f5541d697ff0cecffaa", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "a070de7f643131b9de7c472bf90796065f22acb0c6eeb9aae3ec961dd7ae5f48", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "bb06fe248e89fd1771cc103da6051646ac2155aa9c9cd0e928e14d28c4079612", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "2f2f5c0c93ab53a0f49f7f43bc9c84befd87242533534b9811d8a8da0b34cbf8", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "b59e73a5a90a806acae4f60321ee6d1682fc31d30c10605e26c5c1b712b897d1", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "477dc6145ed08f99ee462af6e25877e5816537d2424c14878356c9cea49db466", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "14d951a97232dace2e1e38789f06e55b4282e471bb27924b269391e7d3060570", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "9af329880bfa5106895b38a6a55ab77f36832ca2772a4c630de5eb4e21df7073", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "70683167f15fc56d364629d29c7cbf863cb4129db7e6472de4b5c6c596fded60", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "623846b5cab461b066c69cf30924dcf0f6eb7910081a028a76bd061e81e46675", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "0c0f414540e75f63eae055a9f166ccc88af44414c1658a9f70af66259d0dc5a0", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "36771f936148b5d055acef968db27f5d4431475842298ac3125bea7c5b1077a3", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e86df45bc47aaecf1cf4d9cd539dbf04a2643e754b3c217097ac287c4da97684", "target_hash": "105da0016119917bf1691e89d2bac079e940613f594b0f26b543624f1f112285", - "section_hashes": [ - "328774135833", - "426358d60fea", - "8f07c6d3407b", - "87ec4923671a", - "97d4c558fec5", - "a3e8ed33ebd8", - "44c113f72fc4", - "d3db890c6564", - "c3963de4f013", - "2da696eb6f8a", - "053b0869dc39", - "5c6f99ae3958", - "a39b2d18c84f", - "48bcfecb9cc3", - "be51cad454cc", - "a13d5c94f50a", - "f05fe23e3046", - "7703857a6265", - "979d54e89211", - "2fcf669a6c24", - "4e1a9c6a0860" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -37688,852 +14086,332 @@ "pl": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "ff498afa7762ec16b404c99c13986824344cbae6ec248c731e3de0a66487b5da", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "75aa326f6a5df49dd8bc8e2f03bb77f906a161ceb0d8f4b5d73dd45594ba362e", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "7a82d0db550cc50bf5f1710914728718ec5b21cab93a678848a3334f7d3b5cc7", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "5f934e681f68dcf6e978a5538b6798d051a2074129df615997b674e78da35f75", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "aca1f342dedc6d67d786374ac06dcf00a29a417c1490534845d72d1d063d71d4", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "208f2bfdcccea89990f0fa23bc85d2fa161de396eef19017f9bedf6d8957f08c", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "80db0d98f9b92bc18e9eb7710b25811a5dfbf3bc3ab385356ee7e498eb6d33fd", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "7a289bfff39984e09994a5a15e854e120cebff6400d28553a899a8258e12ee1d", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "b831f3a1689d98d0937b67f3d727db5031cee92459ee7a7a178b1bcd6d8798fc", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "7819fd5b6d624aed78884e934964200e26e743c482d521ad0a22df161d711c86", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "62efa0961979fc60757552b72d7b1833248ac0163cb1f930c4f73b3f93784df6", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "354efc80c00465c69f8c28655d45521f5bb94a2ef85007a738c12a7be1eb994b", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "fcae389c0e7fec083a68056941af5c44a92321cda0316cd210239b1f85a3eff0", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "c2ddd154b100b69fa5e6e9b962064b70f823e9f5912a08ed92f0d4735e774f38", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "e349551e2836e974ab077c06bb6d7600538f300bf1ba75d8ff765acb1120b3c2", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "891ce1d63e5c47da4df91456a4f9f387a72ee62eb3eee5cdfc11460a5e1b9576", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "fd5487436ce6ba65c673e0aef3d7358deaf39e86962a5d156f34c0903f9ce75e", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "ef426fd67cfcfee899f4184a821a381c61aae33abfd2d2b09fe98f3d6ae0faaf", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "e23b9617b6a266686f77733cc24ff4fba9124987a9edb3a5d242a3832830c4d3", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "18d186e7143548c0e0f448059a4173acf1111ccf1af792c7913a62597f822206", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "9a67ffb0dcc4e73ff47d768de1fbfba0703cccf4846af11fe60aba09d2745edd", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "cc05e4e04b4e56ff736dbd15bd2d296652b08b6b05e3ca15d2e69188119e1e76", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "dd1580f282ce9fad1c398f23fbf43d6c6555dec7eb0340df73081ff2dab48ca4", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "218807398d633ab851e0f8a772e546d5c32c3d833d5fbe8d956c7fc950e9c16e", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "cafa32a2f9e265fc2dc4e028f227adcbadc46656b07bd1cb4a76e79b48837de5", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "4625b3dabb4894c2363367fedd467a69b4b75cb106bd2cfb0224cd07dadd1e62", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "1f6bf5d321053fa9e6bd965e72980ea8ebfeb2c04fc721a6e1ddb1043986b20d", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "4746d80ff345678d6eec3aa791a319bb04c8d37ab517b69b04c8238389f27c76", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "8e77aa0cdbb9e7ef0dafcbccc988c008ffa2f651c11fda961d92917e24f47089", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "c1d202dc4586806ed8ab3b3f579d699455ce4fcf134dc691aad35afcb19ec606", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "73985c9fdc09bf2ddff9cb9f375ba0b8c76092023cc442d201fa7160a7afea38", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "345dc1fb429c93c211468d07eac905666d42c66cfbdfd45ff5373c9a40844715", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "eef4579b656864f7f41bf6469471ad55238b302b8be22af68cbf5c9cf8c01840", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "99be254fbd1761415639e4be3d716edbacf7b0ca307544746beb61f28d17d874", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "35cd7cb014d0c1effb4728f88c579fae194d221d1c09e2c8b2dac568641f3d94", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "891cdb22724bd6eef6c7b0af3d930f057cc1cf0dfaa6661b5c8f5b2e8ebd39df", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "ba1d8c6c6ec85d0466da10e9775cc6bec03dc8e27cb4bf2b9e22ae4a610acc3c", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "3f412638cb219722cb944a92853473ad8b632238e47d7d96c0f61797c900993c", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "ed770ff1c7ee1670252d334a088811563db3444687751442eca833944b84c7cc", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "c34747139fb9b8ab22680c668af00f1da0e228d26d93cf9271fb03b1d171f310", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "b553343e2c9e44c2747f13a6271a728c00a9689811e4a9231370b54a86542fe9", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "0aeb5f6fd4bfbf7fe2c6367fac66c32f71bdf1b97b071d2c0b511fd4c4805f95", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "926a393c661208451dd4d526cdf88a9585e251475b3f2f8da61649478a439564", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "f4945dbff9ec5c1e8e0a4adad210a3de9f3110677692c94eb7e0f11d3f3b700c", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "e43efbb9e1eb825d6e951e09b20f41440abf31bf7f7c50b06517e05f63f15363", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "91bc4fec7d192db10b1a127ffa181112833c9e412faa200ad930a03f5e0bb135", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "008f20b287c4c2655412a614fe62818520102439ca5e17febdcf25df10798726", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "4af930a6250cc7e2b12c3807722c4e033d7c560f01ad14b6c6790ab00e16ce69", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "e14e61090c622e435a4193786f04d98934aa3307ff443e9ef96fc635c288c654", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "40eb07fa7c78618d28f8bc90947bba9836b162a85fef187e163be6cd1a1620ee", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "f91588a711d5d0b0d5705de6f646706cc2e4785fd850c4272c63449ca9f6763f", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "ea462551e535e53504d144d8c9a2417ae28d9a24c5532f74d2db012b93c160ab", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "3256950960f80b068181c49ae4ad1e4b738c3c1cbdd7c352cbe4d5ed33bc6800", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "8a0b2d8d6823d257fa2ee5da5e913a3151cae812245d16ed861a100375ab5bb1", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "67f5f98ab7f2f8c59b74b9b9c40c816fd0cb3d496f749314782b031e7f8933b6", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "390415dc7a0ab7e7d715026cbc59c193f9fad09fc069728e662b8df493cee9f7", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "37e876f0e12134f3e70d32be1be5d8b791a84e950ba2541665e31925c4726151", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "481a4f3bbd65c55028f1fdc20b374bec20e9f0d7bf1bd10bf35da3cb0ac079ec", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "5d1e3d57db82a46758fd94d6849d7b6a018683cf44d8958bed634033dd298eaa", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "311ed18900bc3791c9d89b92e0d69cdcaeaad1e3caaac37cd93b01d458e5c96d", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "16d38661b1e508d265929692803038793424a00d9d9ac714c52644f06c3c081e", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "59275d17c5a6b17401d5484344408af34c71d9d999f3ca896138ef5754bcf116", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "e06266cb1b2c8766fb5345d1e805b412ff74bd53dd4855e8e6ab48e21f3f82d8", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "00e409dd737c2bda67480a78affde643c8ec57962c27e6a6552ea21b58dbd23c", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "3ab5a9829ec2a755ab65f4d483fd42cf80f86158145aba1f6f6c24dd94ef015d", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "a878e8f648fae9e28b18340c1812cfa587c5d15656ffbcb9dc8c528c694712ca", "target_hash": "3137e97d5447ee35c29884c290dee9c32e081a1ae3598505869c9fb7abe070bb", - "section_hashes": [ - "ca52198f1502", - "f3855e5b4a07", - "2c38dcf59783", - "0096685f6bfa", - "e1b48218032e", - "117617930abd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -38543,926 +14421,332 @@ "ar": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "1b7f842721cb13ee8569f73386244dea669ef93fab52c6d0ef92dcb2ada37f82", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "c66acefc9f1269ad56b418318aec9ae030574e33235fb82494a39f1880e6ac93", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "4cd799bf86514ae827f150dee4017fa6bc9f0f009e731cdc45ffd5615230d2bb", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "f485f49cdf23c7328193814a8d27a93020ae251ab0f8854b27343b9feb5e1adf", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "20d9da1185ebd99f0d34cb28f0c46a868dd2f4cbbbd54e51c523f66e3ac43af1", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "68b1ff8e31862b513c73d24ddb8da3f369725fb64b209f141e286de9edde84fc", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "8961d18642842d4cc07950d243228088babe145bd4261da44f3d52648f294756", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "d774d48181ff589fd6265e890c2ed1c82f40c756de03d94627e21265bd7e681a", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "448097b746a586e2b5e183717ea35b351b37732d0322b910adbbe9db697d5795", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "ea9ed08f6338f358533c5527638f9a549522d08c7759471b8e06b4ee4a645c7c", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "180ff78ee69b71777fa9886ceaadb922718f5a62dddb3eb682a708625ceaa928", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "d3f6b266614a3622c8d38de90b54edab7c3d987b924dad516030bef313aa86dd", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "dc63d7aadd5c5f79019066b3b1fb72788918c5698e7524687ac8d4635e93e4d2", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "29624253cfcfe2339511b093e4352b30c790fe84957fbd0bd6c04a05b8c6187a", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "84362de6fad46023a1a8d58050d5a7b93e28a7a8a5ed86e2f2e6c41afe53c1e4", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "afbf37add8b9a81fb9c3cb7975e51e8b92c1c8626b30396ea2a82b52613a36b4", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "50246a7b26258df754c10243abdaa4bcffdb3ba0eb4c00e89086db9f82486fb8", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "1564e2cb4dc70b40e6620e3d81e1045baf7d69cf0732d813cca8085e4e9f9292", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "b64d061232480ea5e7f3d04daa170ca742eba344a32f1099316a295638a4ec74", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "4f1fca7a46cec4fff0a6dde88db71ec7615c6f2ed16ec4a301d011f269bb8802", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "d0c55a24474ba0c823739875d1d864d0c3923d69c26ae6fb45ff99ad34f44bfd", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "f34e9b84b92e32ef6835761eca3e688aa46b425d722542198fafb9c842a183f8", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "709991f55c82eb599de45f4430140c6b3c134b1a4ad469dec65ae63d7060935d", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "d223a646eaede929a6590f892fd78ff5aa96c9e27456cacb7f4633d7a6d238a4", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "1d2fa09b6eea37889f11ed0aea23340f613f4ea15d3e38d0c51f8236de917055", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "dd7e558557161f8204c749e0a194d455fab96705b469d2fe0512d0c4e77f6ff6", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "b72729a38a1846562fca7afa51ccc353ff3fe66d921c95422eb49869d9c89f8b", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "11e687fb9fe4510b1c86973807e410f12182662d81de932b9ef5128d3e509bca", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "98f3c957de47e31607c311353743b874ec3731ab4ae9ba83d4c677f66ffd5fc2", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "b91dcde8469e0dd28365ff9fcbefec96ae478aca2b32736ceca5b7c5d5d51eee", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "6cd042547a9bc5b12b06279c0495dd332845667846a025e7fb4d84606fa5855e", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "c19c602457c79627c7a764213c7e37e086d83d94f06bdea807cbc3a7eb3b37fb", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "bc39a798146026888233f2d4cd37def4e72ae72ef56cc3df86d298deace10c7d", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "f321a6713f96e20fbd870042fa47e7efa22688e9f33193c9370ab992145a2719", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "f01da0d19dc441b842008886d552f765e0aef90a1e34350ff681c394f6f2a6e7", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "88e3d817cc36da361317d75dcf5dd02f1944553432ca0186bf0de765e9da33fa", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "542da71f888617679199a2bff412fb8e715cd8d4c67b074faf5f98bfe6a3ccc7", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "8317d798304c91871e71f72423a2da6ac9314e356dc3ff6c2fa674061e8a3cb4", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "ced1536b380d3a7c1134648c57106188f130e8ec899f933c7a4cf22d98906e6b", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "b3b898323f6bb15dd9375ddff64892a75e92445f9b182a9103c075854486d000", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "c25f8c39c94c8e2eac5569bff8efc4b94e8d68ecf62736a4697b8057b61a5ed4", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "6da09bc93fde436b60035060750070ae712e3721a8bdc85bbf263d0e75108266", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "1b20ac4b096387389ad1858997bc7ca3efd17d02fbf2472a13bbbe3296f9a41f", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "3929d931395363d101ecca92b136397fdbb28674b248cec5c4014a392892cdec", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "011b0de255281da20aec97f8d8b6c7079db1cddad45d538b842c23e35f87a2dd", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "dc4e50786a422bf1b4292553176719ec140e11e1482060ac9fa1bd0472fd3237", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "e8bb6fdc7fa3ff06385d6f833e3fc7a53095bb63f14613c72aebce3f53dc463a", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "0428a0fbe5624a77d86afaefe8dccfcfcadfa58132076a995779ff268bbdcb20", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "06f2b94712bcd14efd536588e85f58f6029e3fad87278c465fe1857579bfaa63", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "90942e3121b51b16ac44c411bc0faa3b84d87c95af9ebb8db3e8ac29c1549beb", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "dd87a5c4c3263be4a0723f8d7b88ac510f5cd8017e97eebf261fc85442407a65", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "a9f62cef09405aaa0749d9641ab0f3db86cccb142060436e96d215e0510a67cb", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "7719405eec0a7d7bec0c11a818b3d9578c989215610b89963d90327ba519b91a", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "3d07ffe7aa2448f1fae1dc9592c78ce96adf12318cc6e34b95df4613e09d102b", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "381222cb1938060b34221fb766916f2492b1b756661efb26a0177b91b79d969b", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "a0aa2af1e2b17f3ac702996da4f3e9fded8d991be783c5ebe2d177e3bbff0315", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "4c96cf8169077ca59a3ebb9a7c093b44b9dc2e1fb87994d8b58b02d1b6ab3f13", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "51f1f993953ee8fec0c3f34daeef9e89c0bfa0485e9d36e93c6cefbbc053fc7e", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "32fd8a8b1e1dfc22a458268c663bfa42b3cb18fb092b3171043b9508263fa6a1", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "e3c2d338a05be10a7919e2272dedb776944a36624a213d397cb50359055aff93", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "e8f06444e4b08b62f2c3f5b9574dda1d3b6ef28c68e9697a9f113b2b2b31e425", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "e62b3ce5ba50260219ee3f3f873b87f269ab8109449e644125db64bced5b6971", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "2df86a36752d38d9c63be176ddbd6155157b878b074eb2a6c808b4cd022f6dec", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "c3980533d99fa650fd596d6fc3bc180a25c798a0253963e9493719103643504d", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "0c0839457b37f54818da3ce9fa45adc76530f91e4f7d6de28c8129afa2907f69", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "fab37611a2115ee5edbc00da9bc68356b58b0320d27c3cb70e3e68923845144f", "target_hash": "39f3cb124b71972b188965cb5db5866dd53bffbdbf11fc05e676e48c964b8f1c", - "section_hashes": [ - "6d974b4ad584", - "6eb14917e11d", - "ff678aac574b", - "cad540083edf", - "d841c8e8d0b5", - "3f1c98e24bcb", - "dca1281a49a7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -39472,1916 +14756,332 @@ "ar": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "0e082a9d41aa03b329d5397a5059687d8d9f79d6b8833f7ef342aa8c3d11c305", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "47cad0da932422deb76fcbc9addd53716e2956ef22721d2549b09f88fdb92617", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "a783aa02da325555b65ad90449b8e2f45f54bdbffaf6af8df4e2805af168ad52", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "eec9649718193ac944c64daf863bebcb548067544ff8234b3da3c57bae34a941", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "d8d701889352cbf3aaf3f29af869241973211cdf92d58926eb13c92bc0416633", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "df05777428da0f0fd2dd5f180a514e1e0e69da83a3daf5dd99b5aecbc33edc66", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "2314f39820da7df058effc1761e79d7fa2a96102f17e1383560b59b46cd66b1e", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "1867b55c554a2e9fa7deb6d8e510229ae3e29c63bc9921196d2638ab97cb965e", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "aa1d452b25daa62d0f3275c78967cb03823d6945d27c6e8fca9767fc84250d4d", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "5c3a0182bf32389c73667f01fe510b1746f757948a66615d422e956160f9a178", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "0aea83048a7c88562207c4bdd66f8d6d2e204d764c17aa53f3676d65948c945e", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "545cea3c846de29eb69b311d76a15d240ab957c140a5da4726cd0e5e16e13c4b", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "47125e61f8b79a3fd1495cd707fe0318991ba5f89e169ab951b1efe0d70e7367", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "12220f64029354f67cea9e1edfdd6c02999a25b1773f38c975ab83ff7327d743", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "e01b36aa60e27ca575ad245f025f4878a9ce52866ab6cc14a47761068af83bfc", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "4397393169b68d53f70c8ee263bf0277828df1cbd0a0529f2a7aa7b0b4c9ffbd", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "97cdc9a3ebee3d0a6230306bbcc376c83aa93e2802ce70aba8e08b57c2fb7fff", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "56c52bee283210d1190963299bfce16ec4db8865ee91dfc83a4a2cf0691b017b", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "dd3efd3b55bdaaf864b034d29627eceba8acc563f3adae84ece7cc83277a5216", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "9322a68772e84e7ac27393bdd5e0022039f44aebb24359bf9155b3362e01699e", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "7dc97a1a562ce1f0b26c8862ad21b23d25580ad3539084c658ec14ea977ee31b", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "dd35a76620f8c16786f58b2251ed49372bc0e22cdf815e580e2b9a685539e84c", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "580364b5538f64ae1d5585a56ee6e47c1590464adf431a551dcb968e316017f7", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "1612840b275a4effcca6e567a3cf67d3d7948cce5e919831a2d272ee6090ce7a", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "7f556f25926576c479fa92fe392be86924d134b9acfddf22906ec79b4f05ce40", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "a07add7aab73799e1b1745dc543dac08c24edd32c30ebaa527412e88aa746801", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "25a1f03f9683df5d7005aacf6f076eae544497f8ed406deff1c9b0a44dd19387", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "645a25d22e56884cd2fad1e635ce52ae6b8ae3fe4e710a9f970b6f0359f88663", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "fc1898f11b77f7f44a29887f61b283adcea2b0a4a4adbb723bafe1223cd6fd55", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "76ef8323dd1e68943ff1a2b0b1755fc4bf4297df5c86bbb7f6594b21e61a028f", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "217219228465afae84a9d356fe626c98650738bbf89d24555057fce54f7dbf2d", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "bf6807966ece20d82e66c23f6cdb1c763dffa02786277dc3d1d32e967f57852c", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "208856780474f69cd6b21ef00f7bf57f29898529d23853daa438d66a68523f1e", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "cc6830269542cc1165c72527a700a9ab69f76c89ef1668399c1aebd6163bb79d", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "ae53418809029e67cd47f0944b7c1d060f368ab7d036977f69db60a4c8b6f4e3", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "d9ae7d913e5e8bff56854f8be0cefa9b32da1931f45a9d987a80bda4350b7fb3", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "fb6d2f3edb5b014374b577b5416eaa2eef7083a34cb53354523f12d335a5c42e", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "953cd07d7456b24f8ba7cf60197e71eae24efa8ccc1e985b40102d4b8e50f849", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "a3c610140c2bd6a21a906086e48bea8ab4fd3378eb99b4042d5019e1890da17a", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "cab1b6c278dc2a2224a936c1402493014180c796b5aac9ccfbcc82498112b1a5", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "3deaca539090d90f9f4e7ee05fc9a8014b28b4d3467c0c81925edd3abeb78513", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "5a192702a0b969f198c81cc458884f11412c21af022a9e5b4ed82385290bbcb3", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "a1a45b30b398a4908f3bb5a7c775f7c808bd96b2017dd717fa5570f4d31bbc69", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "934b3cef76705cc7f5124275ae628597c2dfc650eaf54584f56b5b2872ff51ab", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "085582071d2ac34f9067f93a37f43977b48e10d51d627435e677ee17336539ba", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "a495610316d1b454bbadbde1abfc5e7b69be1bcb974ba72555f861c23e08507d", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "d23934f10c58d9ebc9cbe5c4352610e1d2ed92eefeb1affb58ecc535c9f064da", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "7ae239a37b3a25f60c15b36d7a09e6e54a546e461a1941211b25ee1d58176293", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "658007fa8c6750ed4d06d5f3847c76b42992a59d2fb254acf4f19007f8cc1a39", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "7dc43bf054742d080b52179735e07771f957584f95ef31078bf1a0198a31ac79", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "81e4d0059f4e6b80be40c94555e0b4e40e7823c1923fc9faa7ba838ced485134", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "aff9b3e56f3a9bb11a721cd5723f628e84524e30733f7640e78bb5e92aaa1e1f", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "d8df24010cd9d96d097be96ea575ea770cc4bd23ff521dc5057b84e64e7b46e9", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "dde11dbf30373d129314e8b258f84b3c26afcc766d92535b8860808e7c349c35", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "c54608f14923adeec6e56316aac90ee96243f488eb4b564524e364bfacb15c62", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "07628e8ce961613a52bbc71933c4c98cbf9bd7f8d5afb9a5bfe5d1a80bf62f61", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "77ee05b28585cc692ea1ea3d36cf338616ff4644a2abe353a5a79cd7e6f212f8", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "4a25d3cb65b1d3cc46b4d46c69fec21a157a431b64c51c3298ce9df4da22ed80", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "3fb6f76e8e6fba55f3f497b976c39a7d0d4430ce8a4d133e75b573937745b1ab", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "b6b108559bb8ed004dac681479841a89f24b1c7a2ca89b17c34db7d7725bfba8", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "aa8a7625cde583865580d9e8e4d6fcd9d07826010ab36058a4e367b72516ed4c", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "001cb0037272da9e432cea2693f7eb37a9c0238df31ca67949bda58720affcd8", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "1b7204d29000eb6d0c95cf30b455f2be59c2318060da83bd8af4ae68204ddb84", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "249bac61ceb0d823d07aba7f91edbdf857a0a1508e425b13155e4a0670ceb00c", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "b952e684493ea9eeabc7b9dc4fefa7e61c46b37713ca0b4ab42b5c58e8ef6aae", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e6dad5fa6e9eb6c0c7484edebc4d008255a04bd6058e0cf36464a26dbe114130", "target_hash": "d8ff2e218203df8d089b792d321ecbb8b3e86dcfa12f0a3e84df2282c072e3d6", - "section_hashes": [ - "b53a6c6125ca", - "13ca2c156dda", - "33ac4b83df5c", - "b8ebfd1055bd", - "c186bce93ab1", - "1825ff65195c", - "e4e572aaba66", - "fd34db2fe1c3", - "43ef7084f887", - "9d2316ce9ac9", - "f3d6adca425f", - "4e13e3ae38fd", - "a95896114b56", - "aa3890484cb6", - "a62e9be1cc22", - "05eba1138173", - "89863292f58a", - "3f238d89e23b", - "03a531281c81", - "b59945598f5b", - "a130004a15b9", - "106955b9a8bd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -41391,2087 +15091,332 @@ "pl": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "570cbe530aacdcefb23ab969dee56f38670c022cf08da050023ac17ac2bfc99f", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "b2a1d6573f6faad6a4284e3f5a6aca4b15ffbbbe0ce4509af56d7cd31ba8c365", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "f95785449025abc8282c8012a63b58a1bd258d2cf36c8a0a3ce03852abe856c8", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "824e9b0341f9a92ed573db0d7fb7555a00ced2c7e1ca86ff2cc83b4877f90e06", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "4b032db9a7f25f9478170deb707d88aa878a85929e0493a54d7d90a2a2200722", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "d95cc510e49efda9b82b9624b6e06b253279ff40e674c9a8ea6f11339a2e12d2", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "96a43bb2ccb0971a579dbdc5ac21129d3a71f70a9ec7e23e1d22767bc2eaf0a2", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "f54ba02e08f0e827ccb7d2ef118615050d0ac668f3986a69c19238720314676e", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "7838e5ff7180e7a45863dbc4dc90d0e8734b6bab792d8bf1dd3ce70d5371ccf6", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "3bf6f38fe95b08eb0e3a89ea5ce10b17f3c9daccbc2f896fc612fd7f0ed8d534", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "ecd4c75a6884adfc71923ea257d7392d72170bc0444e2949275f9f8c1b2029ea", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "a5fc5571b61c60f164f3724509799da7577a29d654e5b527ca649f760bfaba75", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "e1f2404185c67cfa74c2a0d3cb3fad29c37d627db72de825c724646753f6cb4e", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "081b0fedec68c99c0a7c176c99423754dd3698455d29e10414817a86dae73d52", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "f6d8668a7bf74b6d3a778e5796770ea1d2014ddbc18d25001ae3becb0fede347", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "050b5efcb9838a7ac94fcb8e21656d61e9d2e886638e0d460e511125c365750d", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "2d68b14649151b83b5b449f342de2e1a06bf3876bcf9bcc4ecc3fcb745279617", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "b3bf9a19f0497035bbaa535f731e84069e7b95a88b78e85c1ac3054f13aed32f", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "df28f1a19226bfd9893dbc1319c7b1e6abca0d522ca83e70c48cd9194b4b54f9", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "94fc064be3b367581e3c3ceb6da54d78a5aa0be18da76fd8501408b736f352f8", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "dd96c50c11486b4514fe571e37ace1700fc5c5863bfa880b97d38bf226d2fe25", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "085db747d913dce326c223342d5d3ae1df2dbf711b001dda599fcd92b71aea88", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "a82b2429d4ccbb85eddc9c84bc4cb9fcaeedbadbb0f4a9866b8ffa7a2039cc54", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "6a1b01f5b49a6fe66b49a1383f44131163f04266483949423c9753104da3df60", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "57b7e8f62f67519bb420dd00d22db4e09f0a6438b3bf569cf9141f46c2882e0b", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "9aca568655db52ba16b0c58234ca5ad2062c353db7108daeffc9c7b1f219d2b6", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "d931ca79118613428adeeb4194efbc29c3523492aa080365d843d0f204fefe5d", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "d6105361d0b8c94d9e1ba1fc8b26c4588bba2a48b68141dd7482ebc6a3aeaeef", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "8c7f98f6a5b7cc978554cef763456608b1f9284145c84d486df68f319c22aed0", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "abb789124ce7413289284a02fb2e0aebfc788864cf47a204be124665e96fa60d", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "2e844e489b951adf1a1b791d34fdf285c80e44ca50576b12315250b0fbcf2fd7", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "6bc3c65117868aea84908bddf8c647d127dc2fd803587ca3fb7984076974c2a2", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "a44d23347d5c0da7ab7b97f6b3834125d565f358076f6ad87152284e172db408", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "47c901cab56bef394e2412df630783045896b90df531e5dc438341325fd63da1", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "0158d7c359531e85ba6a2082aed36290e5d46e9ad1487de7b0a4f41d38513e89", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "bdf902bc28e8c63a6877c0c01519068d23457e405e4b644108a01fdef70dc3bc", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "18c778fbb03259d5f442577c9956ad38009ba06e322ce5bf63d1621e93f54c52", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "a5f42e71b364afda4d8100d6a5d0fc2a2da7e9ea1cdc18645e688e91351c3a3b", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "a1bb81ed0561853fd073da9e0dc796498efa0044ed7a769d24f8876afa40303a", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "bb3c134865fbbec5f9e69978db2c9bca3c5214a0f8d7028c994d4c5bed82db19", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "a5c7f0163a71024e2da70798a24ccba89a1626838ba8bd706701dda3b81a4604", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "f56aa764769a3632ea57bcc809d4eb02e10325bfbedfbf7fa10122faef1dd747", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "08d492ce4da00187a7ff81be9850ba8a4147bdf9454f879c2a7f499f84678956", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "7ace09274a06cff6656a39430683a95d612cf346031fac0b5a863f985737f660", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "3f37cee191cf2f2fe23a5cadc9bba527bff97cbe51b2fee75665411f0de28ee5", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "0d0a1533ba9f0bb9ef0db3302a04ebccb9a2eb7de05240e22a4ed987ef983fac", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "1e5bf7ad746fb9f288d981cd06b7536f7a7fc16f078ebf31e1abb5bb654d3950", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "d32292e1f1832ac249e176104190f5f974237133a8270008941e9e594601cc93", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "d8ee5ade86c61c69931091104cb81ae94fcc0aa2750d2ba1a8a683a3eef51637", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "1a3b4ca53b36c5b06dc942fa2abd8cc106d0c8bbb4e5624cc1eb887f6b4c2855", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "277644c09803c8c291c30c1d7b82bacb514c8dd2e1616ad5ca341c55d3266607", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "c0073d28c911d8ffec10a8e0e0aa86139be65b34110a4fd4a8bd8d76a7ccffea", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "8727c7719e44ca17fd165b2704b715afc175d5ee8c0f8bfccf0a70cd3f905140", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "7d496e4e45cc285ea367a5b0b299b921ae7172ab5accd13b66034c43d34b025a", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "fc1df6703f7d08e9cd626aab93eda04723af293d18f5aa9a1fdfe2c5979bc523", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "559d74bee29f37e21c641f9622fd1f53b51d22c0b242a0554b41a5cec6751cd2", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "f652d2b63f8d803ff2cb4eb720128f6ece2e33859e0fc8a0add3dfc9c6364c77", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "1655ff07a2b3850f65b7054639375d30495e1c2cd76635413e2935f94b4cd6c6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "6d9e1889336b5d0d151f14b5530fd0d2af143379fc6925f8356ca445d75daa52", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "126d4b61cd5ba2f5bb503ce5aff2c8a3cee0ee6d7ac1d9729d4403de4af28f80", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "e974a0bc0c7eb9504e72bada4f6480f2a4102f09dae33aa53ab5383764ae7687", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "27777a851af5e60552ce1e3e1d07cd2543ab93e0853dea6d7c37c91a939d6d51", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "1a6676f3109713428d8c4b3d33218190d994b68024ec390a2bdf54c497ff4577", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "2aab4905181221bfacfc60f47039fced4835465b910cc96c1b3f55d8cae5fdc3", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "2fe5416e576064515b893c1eb9e69b9b8fcd7ee1cefe17218ecf83fd563bc7be", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "2817788aa9f196d3b87963859d07c92b4bee4c97c2e9f97af18432ac371c2b4d", "target_hash": "d0d6ad1a130bcf928859446897e6dd90788aa0a252694fb9a4f18aa44aa192c1", - "section_hashes": [ - "da5ca1ba5e13", - "ffeae614628d", - "b8edc88f66eb", - "bb82d2f0ccce", - "d8fbdf93a6b4", - "d50d2fcc4b72", - "69283a7950dd", - "1b0279365af9", - "ece83d119d2b", - "97c66908fbc5", - "efeb24b22336", - "21247abf6a46", - "a712fefc6570", - "93320ea4bc0c", - "889152aa1f4a", - "892f1b256409", - "7b35e32153b6", - "7a91c50e108c", - "b5783f374ef6", - "e536074034d7", - "43500b3811b5", - "eaa38675e13d", - "a363f5f5b840", - "edfd46050340", - "dee403844efb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -43481,917 +15426,332 @@ "pl": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "6e9eef7faf4c53380d560a957f0be6b66107235f2c0f22ddd843d3ac6f150931", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "1e0997ba9d17e21f7691fe4444ce27b003b83314f06f5982b84c4ea0a127f1b8", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "6755fe8b5b3bfa9d9a05d057e00c15b7265fc6cf077f6f3edf1a9c9dbb63a4a6", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "126949c0ebe94a166c8a781c2c763c868f9c5a27b77e51eccd01c40c151f11b9", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "769a91a8f61d83f9bc1955443e686b4eca9038fd3672b6a85c7dfd993864d0d6", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "a7513ed225d469de86d921cea8e29c6e0d59f6e4ac6eb5c98ba4951b5832d5b4", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "2063963803bf9e1a931b26eefd61593a7706d9dfe4e259a812bd22261c3fe7a8", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "9ed675e6689484ac04a0f6c439ad272dcf5718a2cd03c5c158752666f59a27d0", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "1de3f681e8b77baf14b9fc8482cc21f4c0ece96a2b3ddfede4da7c1e6fef5127", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "f8f24ff64de0ebb672ce42f1ec06698f0b1b063d4a7449c866887ccc25e65895", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "bb01c908bfe45e9ae71a0c457f9c2ad9c71ee0fac77cb081a20290346f0436c7", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "11eb19af62043d232628bba3881466b9bfa0e45846ff66690f1689acb87937d3", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "e45c3b2e78d06ae1deb640c09db34dcc14fde5346c9d8017be290990718c4234", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "239e26f8cb741754ab462dd27a607e8b70100d97a3a8c173f42da6d308a9ba5d", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "c9898a7fda58eb2c28a14c476466caa699a1384dd92015f3e6d21b32011e1993", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "b3c635a304baa13e57b8aef1583a0028a2f0a934dc34747df68a0ab64aae94b7", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "8e75524e6fbad23a97eb332385780a6187172b51c1d6b72243015748c05ca044", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "26a3a7d0027fdc028fbcbf0b9d81f1c97090b6e27b7199354fd26118cc73b136", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "0bd9109c867d98be4d7de759733a3eee3e8ae661df8cd2bcda5aa1b563aed564", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "90de5028750c0adaae10508edc5cdeaddc259c53bb63c67b76a95a0c7b19ec46", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "9502d19c164897394fdd90c8cb98aecc6fa022b593bcedfcff109ffbc20ef274", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "c8c87195b3084b025ab8e6617fae130cbaaf0ef0b4c3490921e8bd742978c92f", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "1f1836b66a61b2fa671cb415988a68bdd052ea82834f3562b5219d5b513f570c", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "505f6029a843d3c2fbcbf262e2b4863e093dc11785739c75a550e3b0f5846f16", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "b2f6847f56c5bc8f8561022fae18fabac5c8b4227c8e3ecbd83fbef2f6845eeb", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "aab4464d21e8ce739fee1c4bc7eca4eecb8671366adc2735bfd53d155082b93e", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "b13adf66763198e2d85c4ccc17d3bc75fb661cb45989ffa020752f251d4b25d6", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "c7d228c0f7e8ba945cdd170e368c210158b56fca385d680d1e993f6356222918", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "8a40841af3a0f74a1edecf12f748e6987b8bf533c5850e476797edf000f585e6", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "de7a4291736849da20f4e3f78a3f8bfd706e818d310c36844fcaf26638eba83f", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "f38b1c70e14585505dd21ea53b5462e1daa7cfadfa358540ae061fdb0012db25", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "539c363435bb684178df7eca9c373cc3a22e3c243a8447ae50162ed35b8808f0", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "9b4972bd6cab17140839ffade07ab34175541bb263e54eab7bffee8f5acfd94e", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "46bbddfa4239c1f5240289f310592bd0a6cbc79e6881305d7b113ca6ab443550", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "893fc1434ce01c3023738f59e5a2b66d4a1a739fe856a5542714ac93d7377b68", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "da666a685b50ac0bdb42f5daf3a338cf56ed8079d7527a93127df8f8c2f6e8bc", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "09e4b7b929112adda835bc9258132d4a84c512ec05087a533a0ed766e3dfc81c", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "b3621b0144f0c77839ce478cd1082644db1d4cbec6dd02aafec54ef025bafa83", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "989285a2e26f88936e2f5e08788be3fdffc08600c387cb7af2c4e0f477738ad9", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "c2042517d71f3572bf66f6e5c13273c440015e5a9cf4f4555deca4b289586daf", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "361f7caca4107193fdaccb128bffa2f011a24dad9a97269e776cec2def1072cc", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "18514d951a687a536c6037fd840e8761e464b73fa572b1a9978ba1712444658e", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "6083b5e8ebee03cfd8e55c3aea9af1df87122d5518bddd647f236271bddd0a7d", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "362093e976332ecc59890c928dd69dca154bbcd8ba1d7132b594b910b556148f", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "0f260acf83039f969e69961f3a3e309c647683e7ec22617a79819851ed9fbc34", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "c0dc4bb478cd53d794575d4e0a273dd864c979ca60551e2d65e4dc75e0f38f28", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "aa77e5677e1d23171e68d9270e605dd88ab49dd296c86f981a7b77ae64506976", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "a3b78bea58046a54a3cc5f0cf761dc7fb5be8d5b0d4480c6ddf5f5df6b48d2dc", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "af944589d15a9d283cb973d27be0383524b9be8efad35ca3dade54e6b914c5f9", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "73965ccd3486fde3f4fe9d96581e34e1b6311d125b5afd7df458605e49bf83a7", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "cf6801b62de976ce3b85adb2b74e6b2a0b692b645e2064d0be59f9e0feffd1be", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "b49ac36daaba00acb4ff6cd64a9b83fafc2315245ed9f987246fb303dba5a940", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "065acd2088b9772a566136b4caa0c80f5a7b7932c715df6bfba02383b86b7911", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "b49aa64d936b6df00d2ba673ea668cd5a2b80f92543ee6dd178b61b81916095e", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "1255f4aa6b108f7326bb842b628c57d0ac48e8b63c57ea5c53c5fb3a1b91b65f", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "3edfa3f1f93ca78df24205366d0269e214438d820a66d536a34c060685760a24", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "fd8a3e9ec3d59f85fd225b35361d4ef12c2f969d4517b058012e0cdc9c395757", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "9654df5e1c50c5cccc4224921c63be60b61674525423b18692b6eccc7f280bc4", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "36a7edee8a1a6e0f5200f4d5b73499bfcba0aa667160989052223acf5206c22c", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "e3a03c1ce486ac29c2a368cac616c53726e66f54d62bf58862dac43407f21ff4", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "2287940398003b0bc9633de302ca4bc4cdc124e146677d380bfa3677899ce51e", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "53765d525faa2fef6a149ab8bae91b71acaf1ac1ea940768a0455f2bc2949f31", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "aa3d5b66d4d1445503b97bf0b219df9cc05946976142fe204ad867175683546f", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "bf7e4b05fb71b692eae7e5a34d41b12f6824f6b029722146b46af42425a5dae4", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "7b0a12928f4096834ec26ac0d4c3fd062134631e3139b70fe150407c8304cbac", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "d8f943bc9b0125c9d959ddb577002891d6a7a119819019990e08981dce06899b", "target_hash": "c3144552647d30e0c9602a3f2f9fd90cf346bcd2f459094204524600ba0ff859", - "section_hashes": [ - "9cbeb5ab3f14", - "abefaaf5b205", - "c2e9e265e475", - "b7beda73850b", - "8cb9ab0e3fd4", - "7713c07e26db", - "d5bfa2107cbf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -44401,917 +15761,332 @@ "pl": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "fbcf22755d31250d43e09b6a566c850a009cb2a5d4179a953bd0279b1f46ff4e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "64403ee04acec91f5856a53f636959d07cefd3d7d386fac57175ec2d04a919e5", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "7d64eef64e4afda100e4c071035587a8e40d2e27b477e8431584986c5e76be90", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "84d8e23674ce32efb7379e3dfc29387c4d97bafa37a9e22e6d66f6aa615cf39a", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "b20f6de2e026e9810efe99b3813047f6552576702157de68c0feb7455b3c1dcc", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "786791f971315f7a9713a777feaab0b4007547febf17018b318b04fa30ba8d7c", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "ac6c07dd31ce2fca03a1c80f55dc74a2ea086633e299d78863945866cfe706fa", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "6613a63f7e5555e0e7959728ef22b35e8d433995b2702b66509938f2cf2b5d19", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "ccf47b2399ef2ac5edfc72d4e705f089439bde266a41559352860d2ce0a9e646", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "17334d2c497a274c877a73b331e8166281cce599f57c9dc9aed682aff9c01124", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "8290e1f2ff098d6840ad74dcaf7d1ae7c8974d03a4afbf5a07ee1730717ac2a1", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "ae5a105c9b2f55d011221320709a94d3c35c3e8aeece138904775c67d1f6262f", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "803650eb00256682eb2477bb858fb6d3b9459e8e96daeec49972ed2ca9b3138e", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "08cf153e0152c506355d58533cd72a652e2f55f34f47953fed0e45d3b274b97d", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "edbeb5e33ba9756b9442b8b74ff96fcc3db192326941b6b17ba332cb665708a9", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "2317d4cdc94cf4138c75b5f4fc1688e03bae53402abba53f288224b215bbf909", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "5fa7f558f4fbf746259d34d60eff056974df2748dd42081ca81da771e8a33c77", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "40b1c20a78d73f87cfaddbadb83088bf962da7659eaa2280686fdc183ea3f7d1", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "c934647e09ddf73a92bdd4ef79c01fbddc279f44d9a5a7628cdbd1a570e41c82", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "f689d01b57f66ea5c92a937e19f9554e44900bf58e84cfbd6e335da92f2568e9", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "656141966c5af406f9328a0c26415cf4dd97cc0a170d714d213dcd46c8d6ffb0", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "3ad04e19dad8d5a5fe201ea38f74e19ed1d61c0addbce1d4f0f59fed5796fc0a", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "1a10149c3b64970543f162976d3d64ae33a49818a99c377df8888b0bae20b0d9", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "2e2aed0827989ef06b0b15432dc169ef631cc1c30bd69b3aaa53fc7086aaaf71", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "2ed4635213f19abca040bfc9dc04f6b109d21010340637905b9c4ce67bc355ec", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "666bcdd1533d74632200b465a343e8c185c54260bd17e3ce3e12770a63ef3ed7", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "aeede3b433875351a13dee31b62ad1a6b71f21d3682c01ac6e6efcdc9f5a81c2", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "4b2dc9f50c7271f7b7d3ab8677d8099060a83e030fb2530ba464d5e2246db04a", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "cedbaef9f142d5efc434d6b26b1a9d3a982f4fbd0dfb991004ef643985fc9a1e", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "aa3b0232b4ad98e9b95c5418c4d7d397b8df7cbee41e0c4b13622de99898301b", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "77fa27d0dce29ac1e6f780ca822843e6d2ac6bbd2a4151ef8137bec48f72dca3", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "aa91cdb4728d327bbfa5167a98b564ab8e75764782ef4e59ac8a0da6179d99db", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "9c591cfddf734679a00c069803fa40aabbd9701484add50ee04164aed4aceffc", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "813ddbdb3f19bb179c536797211b69d095770328ecae282b882cabfcb296bd9b", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "0b1ea6fb7c219eb3398da284f96cfe575454732f070452a91f1fffdbb25a1287", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "8045dcd2f6e357af05699f5792a375f271ddf33a671dab02e1018b0644881982", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "26ae0eae3f7e45b99f7bff06aac00fe7634c4bb38cc7dd7a68aeccb9374b4a0a", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "84d82ff05da886a1203ee9ec3049b5b1418503613e6d71035bb7ad2e8d543bbf", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "3ece7b31ad248f7c87e784f9f48857c1b6bd097bfc93990d11a91c515ff80c3d", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "fe1fb4506d5d2cd76ac74d97c7dae62dc4403e463460d5b8808cff999f0116fc", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "ef45e0f041627883ceab7cdbee88797661b933370a3a9e9f404339ebccd6b053", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "8936a733b43575a8d2eb474f75f2ff80a2c740a649fe5e72bfed8ffa6c9a529c", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "e01c1a8cfdc42d73e1131957fdce0a7cbb74c9e182ee68493a0482710437f202", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "d0bd1414ae64f62c6d9525f3c33705faff635fc92c807231f10bb9a0c787f614", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "51b88e7032124d8ddfc669cbd016a41ba62eb804250fcd838022dcf0f6489a41", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "af842d7162fb74cf840b68c2d2799f5e8344efe6af050bd869a5b4dfe39dd528", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "60426bac95c245c1dd77155d45eb948f17d0ec775e75ee3464def4342f49e305", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "d6c96554e2b3edd095664471a4817032a7a8c69a8af080ff570c129c35ace251", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "120d1d185744da5afa9c8f7bc0fbe990602c2eb8000e816e28f3e4befcc7db9c", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "ee05c17f1cdcb3e179569b65232a323d7db076dde80db22a40caccd5d9f9f7b3", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "eee97681ada2c49bf31ea6ca5c8bb6ed687d44761e51db0f096eb0c61b480fb9", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "c6fb5d7a632d53a8916fa733aa5c8f77ed05b037bf0cdb19f5ca7f0b4034e6c1", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "2687d654d1e51ab4883e11717d81c6568890d6fb966c0f7c503c1b07756e78d9", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "f450f9a2675c11c6aef77544f4478a1a095850a49eae489bb54e8624b4f6be84", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "7fbe36ad1e4a2ed744e7ea1172c85255ae2873643c52a2e2663e25fe24a8b82d", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "74abeb32c0bd3460c9b317fad992e83a4e9a8220dc08476db7565a21bf80b073", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "7ece593dc75fa8c55d7220163d61bda425844e23f7645d4e7323c70d7404b7d8", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "57a6b1cca0fdc0eb8835193b3fceff6bdab270bcde1e7475033d88e6116dcc24", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "a4449d5f8d4daba3d89309605c2d403540425b686f1f533695a794ce41559d2a", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "c0b97607b631b275c0436cd01246eebad45796b33cad5a59983e081b77577ccd", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "8b2fb32789f5f7931734ca582728a13a578afcb1b740061b5699ac7636ecf3cf", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "92f403d2734b33d1c3aeb0404a5eb4538b00cbebc1fa1f9ae8d43cdf8c8b2a08", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "a4c516799c78e874c47abc841ad6c350230ce3a09c30c2bef1f13c5bde9d20bb", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "04e8e1ba4d4a2b27f747b965ecf38c5431aa063eac10ac327cdf109b1e869c39", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "86d3bc6a58f06ecb73bd96b2aac24ffbbf6d7853e63e871b5dc30f75a15807d4", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "80a20416af055577b2fca2469678346c4eb478e747bcafe4b90d4d526dbe0f6b", "target_hash": "eef116e46f06ef1609e586f2d811e9a087c19ee9a3fa1abe133070580c99fd05", - "section_hashes": [ - "06975988561e", - "86c5d629b916", - "88f3416bcb14", - "179309e12a6b", - "66c260fca297", - "bd3284ce64bf", - "e11d01773c74" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -45321,1047 +16096,332 @@ "pl": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "98ab603e1b66b166ff2f2cfae90cce573c2b85880132f1c773f01d9efec54ca1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "144d25cb29af29288825bbcb5c9200377e9d01854c81b5a05f1fd3f530d0b3a4", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "2a7193262ff7b2aeb196fa6e876c518da76d7f63718487e0e466d1c09d7abd25", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "17162e271b516240cc4cbfcdb078825539d41c5af912d29de0c735bee142e038", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "3c58078c7f83f7619722888537c70ef7c129a6197c00d33777c9bc25164fd0ec", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "09f559a9fa126249485a7dfa32c4b99e3cba0e5c9f7705386cf673cf44347321", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "0971aefd6c9129e198a3355c896e37752e17b4cfa974ad95eef8eabb3a4c1426", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "6d5a111ac7918eadf01b241a50a08b4ec70f1b2083367237fa0a7813a1063be3", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "487ee4bac09515a47f05f3ec3e709735fd10c1cb1b3cdc30528605e332a21039", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "54ee5cc747b019213adaf11f8fbd5ba8d5298b4688becb55c27305f116c0eda6", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "b6a8b68466ac2404b5f52fa8116f25baca73af51e517e994e3666b697acab10b", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "b9e6ce90f77ae7883f096dc64afdac9770592742c9dbbb470eaea0b7e37012b1", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "fb1adf0faa80b5eef5cd855e08c587104e4238640af986d28bbba7dbcc0dd812", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "dcc4aa3d0ed35ed31eb5f397f8931d129302c6779ba7f58e62c1a9ce6029f1fe", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "56d2a95f9da0848a9bc582965f080d4d2fdb6fee9de7f008871a00a99764db54", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "0bafcdaf2193af48bb189e7d0f1983e4fc0bb6e8cf2eae428094d75fced2e0ac", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "30352fe783e42bf5636acc9a62061f7bea0a9a243b5394631edb7ddab62085c5", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "be9119e81f92ff4ec898e1150bd2a154c24b7a34fe323ca7956841f99c289287", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "b1eb7a5570b89119fd6d0459b484dfa5b4aaef0d733fec7ca275423d1b7e58f8", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "d16d388b928b6e7214fe56eab424099ae417df4a4ffde766e71f171339ad8358", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "f07285866d0f94be2a501a693fb25f5f86d763e520b7cf561814d0584796924f", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "42efb62dfddd91e4b2ab30d6cad353cb1b859f0de7c88fa0a747bd5a56cb7a10", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "9f3b2cf99f90bef9894ac0964e4216b6cf3003dd804aaa7a5974f5fef75439db", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "100f4bad0ec84a35b0dc8a27c4b2320c1cb33854290dc0b9c96c294eb2a2e9b5", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "1188822df6525c84b6ca5a786dda61c3a93357fae4988e7a32743ff0bf018481", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "9ec6ff7c326c5269713b8e62556d1d361899bd345da8b375c21b86d3be9b9179", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "b2e73fd881091ff6bfc9fd3b0d3b52b8a63cdbb8dccf649b02570068677fa2e1", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "b91a9fa8a662b4d5f1979a7fdae6a4da00e64471bbbbe6a2c004e3e2422f4fda", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "8b628266d0a8879257f2debb12c44e3fe4ba72a0bb75944a2b27b1bd33710820", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "f4b3f87871a38f77989f31adc2c259b54e3524cbe109d3e9bb465c2b9994ddbd", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "c03cf3d27caa523026befef384f54889e7c5be882d318c9efbe35d204d85c1b1", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "3eac482e05a6b54de788d017ec44cf6f2f3488d17f4290cd966b7a1a4ddc36df", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "91c27362c6de81f442de27290295e5c51ae092f615d6cc95fe9be0b704a02f2a", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "381da18d88730cd01be3a2a98360ffebcecb98200029b9b7d69c22d4ae70bf87", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "6579d1706f1b8f7912358542f00adbfa48109164e5f752aa81c8b93d6021c3c1", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "eccf1e3648fe0b58a5e108f06210d42145184452eab0f94c62241b973d6198ad", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "327efd49143673cc9a6c3026d1428899131761179d1370de79df99f4d6576956", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "6f4bc5364c1d06c6c542fa3dbac4c64a8e6f5b922919e53ab0c5bfb23f2dab1d", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "50ed46656017d09d34d2787393cba54cf0c66ff9cdf5cd1c9df443e65065936c", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "b9b3acbb015d84fc8cb8cd50e5aa750d28eede8bb22ae2915f928e3888a71954", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "655f2f6f9f47a168772dc6f354231d72290e3bf0c06e752c1c2fbb4692f1f79e", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "8d99c578c8d82ee2349f3e3d134604a89e3e270df8f415980d4ced3589082185", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "4e7534b0f81c50ab41b687cbf60b2b21113b7430c3e54fe66f8300e7cd09d678", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "96b59c66c6c1cc505807752b94a213d9551da749a8fd5e4cce7fad0ce0a6d771", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "40e0a88d10fd3f93b9fbf9c043cc77ee1b28ee13330d986ac78ebae346681ac4", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "5c60602a0cbcee15d7e596c88ae3dd7c3dd35632ccb3ff7c7123c5705d438f27", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "41d638ab6c1f970c2e57cb3dbf4c6a67e67033d28c62743d1f355c95dccaadb8", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "333e42f0fc5f18373dcb440cf018a4fdde3b8158fa19567500cbb92214ebeae8", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "99d1f5b045abb18be49e6c210996720576141326562a499023e6b5bcaa05b5c2", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "ed393a5dbee925eab482be4b36a1ae37c1906cefb8b3ff0d9177788da784fe8f", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "6b34f7246abf2ce54260fa3f46845ee1fe382f44bf4ff0fbcc8a658095313092", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "b56459db396a65c84c9dbb27da70a795663a51137890926c3e57cc74ad732de9", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "a0542d00f2916292760572173a8dbaf07e3c9cdc241efbf504493c6cfa751515", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "66cdae8b85e3ce878b05e15a174f81b6f403574fa958730127b8915741753948", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "c9064720ac570471f8d2e4248673e2115826fe384d5020c528c4adcdf88c1b60", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "47162c5f578d0d35e2558209f074bad11bd2272b3a50fe140d5ccdffb678cdea", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "499db4beca912efdb71cd2f200204733d017d9c0a9258cd400d98561ebdacec6", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "819c9bb2ffd7f053af3caa466bf6adea206d188e2dadf301d9fe06c125cde382", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "007b5a47d2a55e9728080b1b267533c5f4cbbc9920c94d9f950ef998e7cab072", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "808ac8dc68d7c3d67aa759ee3ae50f4a507b2d6600ac0e4dfed1ca4ecf3bef7c", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "5ca1437769b8ef96b1754fab645ead870184ea824f86213267a78298216ee0ed", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "e29571dcd3ee96f0915a57ab72ee03717dc7e4c8f1ea2ad4c166d89dccec5cf9", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "464e1769315069e5efb862c647550b5edab8c40e27e2e1d0371732a9196ae018", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "4ca82a3396bc4538e4a4577297bac0224d692057e1e50e42b486a4e5f22ac25b", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "2bfb48702d6e91307cf0a43b847d8cf889e7565a36604959da338fd4488cad09", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "bde6f6774b0df7509efd52d127ccfb4e521c952aa07aeecb929e025954c1ca65", "target_hash": "bb4e7c48a9601bfc88bde539a4a01137b0f35d3ca6140aaa1fa64c02898af56c", - "section_hashes": [ - "eb5f8bb6777f", - "21fc52226b7f", - "a3854737e2eb", - "51e63bef86ff", - "6bb442cd19ba", - "7acf7aece6ea", - "f00eb123830d", - "c9825236dfe8", - "731ca3efba46" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -46371,1322 +16431,332 @@ "pl": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "c69200e039352f31f1a302d4b05fae4cb224d5192a72ecf16ffd96efff2bf73c", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "0faba696aaaeabe5ae05ae966802a01c52998ad8dedf68097ce355d5dff4a04b", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "81cb86547bec99d7a94e5a5717ad5cc09f45b84524f5378efae67c7b819031d7", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "9dc314e6788e3a11bb05b52ce4ae629a95c6184667b0450305a14cf7186d1e8c", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "98fe3c66caba2dddafe22cea738526e28f149dde195597276e06c484598bb481", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "1ceda0e7d48f13ebb7f44f6588d7a86425519dd31ad9e50d1302919f182490fc", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "37332985768ad91dfbd367c284be8bb65c8e519e95dea279e7b9bd062829a9b2", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "42b0603e1ec95ac1350582402eb1a2f5862ca638069cac30ab9bdc5ec0de49e0", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "4cda0a086d818b496fe35e1aad77e7c8b13ffc34a298379185f3962809926717", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "ed388827a95506c5704a06c472cc9f56179fa6a9b08a33eaa6dbf7064cb3b983", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "63a92eb4d0f95f0af1cb5f5bfa9c02f9de5fa69baa443d5b130b90325cb7f6e0", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "8148c5a49e10c70bffdf435db501a7be543509a4cec8e270f8596e9b0a978b32", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "0d81531b448afb1c216afef2dd7fde03eebeeb1934d17940a2d756bc4320fd6f", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "d6af6826f075996d5e854575df4672f46a95f843ab0efe168b28be0b9d727b2d", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "578c93e2015ed42d7c0d5a5c32b20aa587e7f12ee0001375a1b5727a6f9120ad", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "db3b705793e8839437f6c8ceeb6acadc9cbe6c8c3c39bbf6e3229f9dac373c8e", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "7cacb5b31f9ddc229d16b3d4c8e3112968cca526a2f32a90d9b1e6b27c158fed", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "7446f873856e2ecf80f2e189010e720f54b36994246ea20bf38aa242e5689c12", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "26994358ff1f06031b3fa8567f255a1f3d82a934b7e84b72829a9207d1f48302", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "64defd2a8458c6e536ca35c4e2214e59a781ad83c058472024e05641bd0c0d0f", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "ae16e5dad15f748e018e2c72c8cbbc05e7c6b203d580b1a599195d1fa6400185", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "3f04aa6b2268975581475cee49353bb2240ef5a0631fafae45d891c6fed7efc5", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "01cc05f9a4e91cc9f7535c106b8cceb85fbf505d980a0f973ca5b067229b2850", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "9c0c5b135903b42dfce42219f18fea38854b646d780ace62e42bc47c8c308659", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "589e5013cbd5e4991a9d30b38e5d298cb743426a68b83b01f69c07205953d533", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "8d370dacce42c0f4c6edbc6fe02571ecc86ee312a724e81f964c4c9fdb6b2259", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "7f73ae84fae63ef30b565d1fb985458b24a7801cce9ac075b3ab160124604540", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "303f3f7777185c3b417a3bbc2e7922ab2be51135ae2c22f5e69b4f45b3e43c43", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "6ca491c162028371fde5c38656a71399d060faaeeb4b9baf013b3e2271f65efe", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "f3299e8528a10500c0ed2e3e2d82078ff6a2671fca9550856c1b114dbac5370b", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "35494e4c0562bf70fc48a1fc72eb8967607d5f3400c0fadac3571ce92d8a884c", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "d61f01d2d600befb9cbd1a05de4a99a5302add5a0f70c11bb1e4cc2b2df5bd1c", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "7e9c7e6b1ae1aa498131fa8bd33986729805ff65d9c73f7ebd91f54234de0048", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "a08bb637b02b711ff6690102f1552b00df686f7a8402d6655de806bb6dd37787", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "8c7d2229a7c23573bb0ba2715523905a0c60d933fe4f9ef60a445e109b668606", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "a8be7ff75efefd761fa6b45c819aa080791c529edc3debd8a090b3f28d94fb46", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "dbf075a274cad662a54dcc39e44b1c8b1ec6a8ca208fe5bafff5cd0d0b1792a0", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "8f3f7ce11750a1462d25016605182484cc38601d251969be540839994ff77d2c", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "e8e5c0021727b55c2ffa5a256c7eb9a6bef7ef429a2b079ff684dcabd3f4c37a", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "52e0c58e94a40f9bd09e7df8491e7b36e3d65af60a50547f0d4dc860920e410e", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "058adb0d6a2d5fc1b0b51c7aff4d7c9313a7bf0b5d985db5fd74b258356ae344", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "d6a2c37b6c4955cd695a7ed88425ed581a89401bb1c47c2bbdff1dbf838d9da2", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "83c0e8c77a6206ecd85591638c34bb800b34f2d47d38cc15788dec2dd33d9207", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "f976c013f00eba9e236de49a1c999426c15d1f74e0ca8a4a39743397bb691ddd", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "c61ab4fe7fadfc24776b2ba9725cec89535c3b58d8788cbde128207f1ea7e3bc", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "36c545c2a4bb7209c3b492c0ecb0516fcbe05f502dc76a90e821a9e4bb751cfa", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "68db319135d36f31ca8944a2670c0310901049676f9b68245a173d61c1490f6f", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "76f8532fe6f55fe73c9c845aeaef821e142a386a5686e48affe47e29033e6f97", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "6034fcdd6ef1fcd4419aba49e8825c3ecd7fa630dd32ba699e52d926fa1816cc", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "c184f1a392674ddecad1e7d2d17e95ba227b7c1da9cb4444e9c4f8764240fe1f", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "9bd8829a5e72b05076c1ce043e128708bc134cf18236de9f036142e41776f1f7", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "0308a90e34d656f94803f0b4bd120a8fce222576c6637b39ef2f72bf473be3d8", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "6ba72cca1b12dd4a9f5c1f5fc26dc2c6549685ec31496476d30c87cf6fb3f356", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "d2b6bdc756961fb0775ee8e4f94080c36428b8675b6e9d15c10abe2cafa78b10", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "9c67007c5c087667325018b1105cf10b94e18a3ee6b98fca6ee9ebb9cbba461a", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "18cb118f8c85e1d1d00d1917634254322b15ab708b055a0b8dab7d1e57e31a21", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "4ba0e230b322fa5113fbef808dbfc2afcd63defc522b32b78b2d6b407c2d8976", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "5135ad77863772b4f012ffa34511032c155514aa82c93dc20d8cf59e8cff72b6", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "437ac4c664912079f1a5c62dd7cde46e99edd18347f842dfd6fe052642cc0915", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "2b612d9cb2e9ec7283e029bd3a59202ec59c919befa35968127498c9ddfcb6a8", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "34cfc768fe5cb542862446ae644ca9b83d6b12dbda104ec9d60c5dbdce2217fe", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "2977f299b3870af1740c6978f0bc76940fe41ab1731ed89d4c61c3f8459f3987", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "c6ca7cd9723f5bf96aadd399084ff5dcf75877c550cc3be69a2b6c34798dcc1a", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "93dae7af3eee31b62714ff1b8bf291eb775abf1e1d989ad223e6c6eeed0a7745", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "2f46a935c592ef91cda1aef1f022668b47297e7074f3b9ac074e0e1a6ba3a32f", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "0eb0405cc3dac44f4cdab64763f46818f2ca159d6f9da1eb4e9f1a077a660cf1", "target_hash": "062fedcce9c5d03002bab49ebf1ccbd8c8f7578feacb3a78c5b1a6710f2ff795", - "section_hashes": [ - "f7585f486f9b", - "2f36cbf31ab7", - "e1ea70507026", - "826751afc9a4", - "0be3ffe4c09a", - "ab8c37c8bc89", - "439f8d96d899", - "76eb6c227517", - "07da8bb78933", - "2a9c630d5386", - "a9e8b8c74dba", - "e5e60acd9d0c", - "1372aec9223f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -47696,332 +16766,332 @@ "pl": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "7c65557ccb822dff2e9c7dd5d2896f310f4cb88007046a19f1ef7f224788e0f6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "b0f5fc07ac746e139f1780691528b2d52017cdbc2331324218799f29057c150f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "78794c0bb690a90bf32423f3d444a70d32aac6e2b90136a590bd56accc73b595", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "2b718ed08da05888dae4f269c709f4c7cbbdb8e5f21c9db464babcb8877561ec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "a01932b994ed8f7db7fa9bd442250f7c324079409907b5078d315e261249e4ef", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "c94db52c5365333c631560718d9a91f98882c31c0ae43e3a177a15745665f165", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "a1d274df2b274b757a595fb68122ff16f02207ecad5a23e44f9b6fd119c43d8a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "5d14826cc213c7cc72416a958729b508ce7cac87aa94a64702b6cd23e7f610e3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "9c5c1076545d4fc4aaa8b5037130a594f6cc7233916a6cf28b5d17cf1382fa8a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "b6b9bedd2cc74c560261acb525dd5100079d262f2b5d465fa3fa038b2d5a4603", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "90991aa44f9d19b36eac81504caec9e409277058aca69a736a8dd342d447072b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "9f94bd93914ec793fb00aff19866d32981a3c52d61991576749c164b8f7be238", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "bc784736cd3788f7ac69d223dc074d1bab02794d70ae570c6bf621e450d80fbd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "22ce03efe86bbba19f26de5a824d03349ad7001c77f1c6dcb0481c7e68e6fa47", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "89117b9246ab121333760cf4ec77e343c053d02b54cdad7ad4fe8949123f3bd3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "a812c9e7150a546aaa9c95056a4134483ebbe7ca7bcc29851050e3423495569b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "e7766f19ae6869f167a7836d44e55f65f372e0d80be69ce15f151c5f9889a8ba", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "6fd23da0cff4099cdb86a2b5fe23dd6b3b24b13d47d9f6245b8e24c6e462976b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "6b0eed0fe99e0d7aa02f187206b0f4ac7503d167d5f50e828db56179f4b72f93", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "36ecb6a65372a06860b11aa5be4c43e55a210a518a625d5182b0cb4805121c96", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "7b0dd3e2fb113ffb4705e568375ce14497ebb14c867ad491b7a6468bc4e8f216", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "66812ae106b0defe4f4c4bab0d8595f3657ef14922a651848d94966807b0ba65", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "569faec69890e8c19270eb8047c1440a674380024f5b15487f1343d16cc304c6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "960dad1b18eabd13e66b7fc5e0d86bcbe7358dd2ff0b9f216b3322b27dff0eb6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "6219ad560eead4b3a798e12eb42006fef4be9b46a343c397901ff921ab55c9a8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "7ac8751a18a6f8153dec596bf50b97b4e6a2f5652f66cf2ac153c6bcc27ace49", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "141a8f2f28494f8c692a9143f420c78878590c88d5ffbc6bafa95e3085ed7935", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "a6ce8988a5d01d5309a81a9006258322082ceedef64ece71d1698aa73ebf2e21", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "c5deeca5f991c3f9ad648b2399f27d4eb238e9ab83c49adf8dd2222a00ff20ca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "a30b910b7a3d34fb524d6743f9503b719f1891851fcf63667e42afd45c737116", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "808aa1c3681cca6db73224ecbcf9e50fb2bdd23bdc88d673845fedb8c7a9c615", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "50093aed3aa96c46469a6f2eb4fc17d763650b60ad4f8aa075ab374211138c39", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "219936f9b15d241dc4f704bba5819f32aa5bd655398653f4a4b050df07665bea", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "423047a36250edf7b11b2e27e482d5e96ce8aef098f9b1bbdbb31e2b42b81469", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "0448fb18214a83349f59c68ea2d16de292a6d6b1ecc439c0d837b136994a6afe", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "217088e01a1afa15183be4292f4e935f5d0049c5815b6e3dd616b08527789dc5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "0848bdd8fbba8cac64551a3a2acc5b266280cbcb6fe873ec6f5ab7f39658eaf7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "79b024b748e51a1f3db0af7e97688b14be700a2557e9a7f4687fbd93fa3669f4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "afa0368bb7b8ab1c31cf10ea8cc7d8e28909e08c22e4983f5d5587e0f84cb0a4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "f01c7e931d11fb5ef1589a981f73a096dda3eda8818ed3d6edec340cb0a86ede", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "0d176a2ffa7bf8c933c08155d5cb3a9a2c76d9279dd4fc7499ebb05a21e56ca3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "27f927fa2541a07fdcd23088998d464f7c00ce03c43b8cbefe42b118e49247cc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "67b4ceade77d43992ec7238c926b7deff51c7a493d7d321c2ff517865c5daf0b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "7ed3d355c079809763bcb18bb6d4c52c41d87b080ee8d695fd4e59eef45dcde5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "79d16c61ae8a2898685e252fca33364343fbf22d4ad64cfb771c50ef4a22cc74", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "45868c0eef6e3fbc93fe2a42d8c52a440c162265b427f34b411a15dc327a39e8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "551dadd707948376d7c59e162b408724b5c21a931992bf109574aa6e54bc904f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "e89880ff8be812a55267ab9fdb2700803ab892cd0b1d628deeac162e1fe9f9d0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "a834c64318120ce6ac304f221dca2742adcb88b5298a751f36948ab56304b640", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "c9c102924d9a0034bbca9c5025bb63504123b05e97bc11c9d99283feca6122ff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "88d8325bb9d0f9e459fd6c4f1ebbc5a637584059ba14fa7cbb5d302fde70bd2b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "17df65ea67d01c580296003d7b31db1deeed0d187304f086ce354f2921f3192b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "94d4badca141e94102b726d6cbb2941e4ec86a28fc30825885229e3d2bfcd009", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "1efb959c34a9f410a0365f777cad3b62b749b5171b6d3a3b96432d42626821f3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "49cf221bcff8834c2789ce32acb4d8380bdbb4eb31d87dd72e7bb89552e81f81", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "114557a33d86a59bd4c52b580f308e554e8569e7ef747a9a1931e51ec61243a2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "77dff1bc875bda37d5d293adb6a3b36cdc27f0b1820879ab6d420fc04fb1c55c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "374d4c66cd3c5a7c3de651b1560411d947b2658d29070891818389ba6d250968", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "b19bd77254ea31f733e40bd9b983a78e53d9321f9536fd7bb92e1acf101958c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "12d32682779a825d200f80b77e8c0ac0f89db9d336a0c4d7fd3d808053b9ad6c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "ad11bccaa7deed258ce97de09b2c256889767758f62a5e139361e146300b4ebd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "f8d119f3339a733eb0f71f5aec1f9c91d1eddca8ac22114849db3b3185818d2d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "9255c7cf6571dc1e971f474a35c2610d0bcb2fc87805385cd5f4e1ac7eea2f69", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "48fc9f610235890606afdf6ca0e0c7f6d39058b534f636dcd7cd11bd01baf8c4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "12ab0a2f28d4d897ec9aff6b80dbf73c0254449b0aac1a949caa7bf6fd131dd8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "fc8637dabff473b3c31ad2a15c4d0048efeaa74b39d0f8c719d2b0adc9060459", "target_hash": "5f218a68a57ce28451818572cf256ca602b5f2903883376467a25dc720e086af", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -48031,982 +17101,332 @@ "pl": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "2c46c285df877d403b39853dcadf0251d587bc9350f746bcad28a84861eaefca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "26d7b15b63287546176939a718b6529a884fef2a73ce82a4f3192b05723ce492", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "220bd5579ae76c7cd2d074643d76b54e3eae58375308cbd59aa1dd07516ec34d", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "454394768db8548b070736b7965ecd89c1f7f63e6267f0870833866ce64c86c8", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "a5af60deb22c824126bba0fa84a1c6047b366c7ac9b9e933d68ce310a49fdf34", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "9b05dcca580e1acac1908b80a416a50784d7f10fd5049eb31aec8004ef0ad8cc", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "35ba1ff9e60d666d4b78e062c8f9e96dfcc1a3c2c29b5a00868699253e65a6f7", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "65618c1c9ed4811be7052e02c40a24ecbfa915e4859dde808c58f4c1cef5462a", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "95b48df856d415aed29e1c97999dccd668be78868a35a9a28657e958c916078d", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "c9770bfd626852fef6ee25bd5a2694932c86b6aeff7955f22935acbf4a1c47d2", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "49d3b2226da514eb7639bf491f338a0d7d9161865e2bb5ea63344d1cdbd6b429", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "0f5d120dc6ab5a2803e0d76c19436d4232192840ebed32c80e166ec650fc3fda", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "b9a94254041f7626fbd5c03ba7352d091688006c8938d139c312140cff2c38aa", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "bceb19ecff65e789e309bc1ea9ced15d7bce8dfb9b7e84206fa4a0cd7b31a0d0", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "6734b61f918f2cf19470448630fabe97ebf21c41f81df70a589651bfd0cd66d8", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "5dea74bc3f932a05df91a5d5f436741c6d6dc352a94ba7e7d04138dbaf648533", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "e45d14d994c99e9efee1c31c87a8e752b29a6847971a395ad987082d1a514a5b", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "d571ccd643e98c7edcf998acd625e31e0419ce03449cc2f0dced363b78c10621", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "c6f224f23987fd99a053da435ec81e097493d678f10c67bf06e634cad9effeed", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "1b44359387d1a1d1ebb437b94c3985fe37d7de57a50efcb2174f9a7020f296a1", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "1e27f02240838e1995cae8a497532349486e8f940f4435e25b8b61e3aa5e2904", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "54ada518e59707d58df026f6b17cad33a1abb97ea60d49cf5a373cfa27cef506", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "22aea6a6170b4453c7b583d8f43cbc7f43b67547895690dc230053f58966bb13", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "ea4e5f5e45d020bb0f47d74d3fbd450288ef03c23f5865dd13598825ac3bca1d", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "7faf4714212e99d93e56050ae7340f7af2ff145c13d1ae03cc8ec8130c820888", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "21ed9b6eb96ea4316a2ce18ff3bd9979435fb4d9278195a489bc138beee1d7f8", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "d244940b6976762ed5a122494f55d5601f0615915845d5f967e4c8dbb0ac967f", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "83bf3522a3cda1376ed960faf95022ff48bb38113002779f8f5284924aea5966", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "45e4620dc16167240ad4c84cfc076b13963a07a799c8a633e3a083e09990e0e2", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "de87e05f9a8b4d8c61cec8a71580bbe30edc66957e50ecba25a2758825197424", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "ee7ccd06f280a568279dad6b0dd165aaaaa723d42289cccbd3f491e7ac7e0e19", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "dc345e0bbfc8550b5c906333fea4de005137601e2ea85136bd21de150e2e1dc2", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "da7fee4107349a593542af1ddaaacdd8d89de9a3a4fc8a1effb2c52400410577", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "eedde6c7cf36a98dc67d63835620631a00a8b309de8ac603bbe882fc7e2564f2", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "1a22f1fbfde1683b4bd7f6ae7df713c01ac941579be499254d03ddee01b5b8c8", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "e56269c6637e1439c9f1bf2e30bda5a365c5a1b2e25b2870525071862dda0b18", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "fc719d42ce1a7abaa1142f86ebc0b304b78f2655c854dc52ad8fa09a6b88f069", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "959fd7ee55bc4db1f7d382253f3ccad769fc9b8c08f33965db69d689e31027c4", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "484de210d6c26d74907554b0b9f6d9132ebf4638a5d87b4ba6b53b783ccf4c10", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "62990d31d31e1e199b115ca0e57925a961e9678218f864dc34a048a48f605699", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "704ed3172a50bdea8af84feba1189e5a6e56766f97b25754d4e6627dd712730e", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "1326a92f513a26a98c3746b94bafaa4ae23bac54ce340bdfd37eb2c749be261f", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "50c2ee0ff2e9054a07ba47c05c9759629a3f4b84b100edf25e0274cb68957ee7", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "0f27402e1417ea2dba5523f2bf79e93704d181fe4c3e75b31d47a94fbdb87739", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "6c58133a08151b280d385f7fef0e676c6e7bc1e161858930f9fa6129ddcc0c8a", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "cc48636863bf53039520404276e3a4fa0d7e0a24c68b54541fe93ba4bc5e0aed", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "37b5e80e6405569f53f0fb630a2fb1b8b7fa0479be9ad18dc9f8c8bc1f952dfd", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "8e593515c8083d39014814db8cc5ffefa5ad621e3bbb052ba639ca4846a3c6ca", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "df75373b348b0c40627a061034e68e4c4a2d49c19079b5df01ba5b71dba8a7f8", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "f66779afbc30d0a779abbbe8bdf94103dec230c0371525be180a018a68c9045e", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "7b3536c82405f178e180de9f3e1bd664fd35b9b70978bdb1b82c4c2e5dbd7505", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "f7aeb904ebeadcb6c2f8e0d51757bc590a25847d67eb2a025bb8962b4b841591", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "b382b167e3c001a0f98fce32774eef4343c2f73c783b47ac4aeecb1f2697036e", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "4591d9e29f9ab0915bef1bd341044356cfd3f5a7c7f7e14fe5b9bab2a0810a38", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "31190b34834e2cac0167f7e3223e83a31b40369c76fdebcec6f269ac5967e39c", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "230570e9e4122891626c76158708f5acf67d20112963294f11008f465e7ad454", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "0fffd61690d7692483de5a65c7d36ed2626d1268810e80ef571b8b3a4489d405", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "1ffc27a8635c2a8c9c757e8cf0f6958b86ab5454182c3c318f5e46a732c8bda8", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "b5e6b960bdfd5e09e680f905d36812d3664929de7bac145451e6135ec9852473", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "ffd0aa2b14ab865c20afbb1bf0e257e8ed2200b1e1e1e45a50851868b830956f", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "b9615bd0de9a9f7619a48b38339b68f0e580cc5835d38b84ba0688d07cbf9e85", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "057bf466e06ca6abcf55f3ce844045ff0c84d0523857b308876291fb3a02cf1f", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "08e5fa2fe01c846e2b4d42a958f85bc1ad2aa40f0e2849ac381a71355bd1cd9e", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "becff3aa456a38d032566fd3c1072d02aaca372da265d8a2544c1b6fe8ed6386", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "85dee58cb64ba3db4538d47f9c09904d8ce790dfb2d73efe2bc724c284245e5a", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "ab8c80915c28d29dc3984d7d8d4ff383074cef646562f0f83b03ea46f7d5dc87", "target_hash": "1358cae14aaf9d502d90da4c548e143dbc01ce5fec729ed19afe0f2eb2f1bb94", - "section_hashes": [ - "23e24ab2e0f3", - "67c371438b02", - "1871d529af11", - "d35453984f32", - "9493d6435bb2", - "f217c4d73597", - "c1bb92f207fb", - "0b75b76b8da0" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -49016,1177 +17436,332 @@ "pl": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "8049e42b2bf98e6234b6f554bace424bc563c0bee8ed274df05ca67931388ec4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "035fa6492638f0ef06a48b2e5a1633c5ac8f4b6321a7300b951a41be616c75d1", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "40c3430ac1994a3eb3191b90e3f3682020ee4511a52c02bc204590a476e7bdd4", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "b89804199e8082823dd1167dc35e3aeefaa9da6d503dc9353b9e8976f9cf4629", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "a987ca2288bcf4422a665ea4178ad49eac90cbddcce824b980c371203b1c570c", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "39284c51165db168500bf9866b3579f92fad22f50b32deb86c50ba2d096575bf", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "62f211aa3c99af80c70025ed41ed834c99ca6eeb20292d2627d90ec090ee2d5a", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "802b4b007f17f5e8f23681901612b4d84ec1dcc081b758f6469f7987224693dc", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "5e2c845c7376dc5fd5b3d2ce10a3ccb4fe583fb1005373bc31afdcaae7c12c70", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "ad9fff2766249e18504524a07fff8310ab1468eb6991bef337b291dd1da4e920", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "d51b22c8f64cc23f29ddbbb06444ce03e7f78693df8d3b105668cba084e47aba", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "fbf0131d0ea7899bc073bcc6c75cd96deefce9f9945b0a94f443339958c7c16e", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "400ab670515706f8d4749fc024874c27284c3367c37473ad092e3b5dcae84f7c", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "d1dc04768fcf90764aaa1f3f075e0aa0ffad0d167bbdc6b1ff17da3993593d31", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "274be3eb00fc4a7fa8e3cb64bf1ebfd66dc69177cf2681d95cca1e4623eac231", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "786566dc026939a4b7d5182fd49f9d1d68680c3af5d2c4be946ea18bb3325bab", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "0984b209bccb6990e4c934179b8243f25ed8a50c25144fd90db36cd13dbd58cb", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "0b3985385b11840061fbecc4d3cdf87013c73da1a741e1049879ddb824205351", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "1e23c4a8498c47b439bb8054ed47ce5d2271792801994b13dea5e2f7e3e93380", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "76e4c6ecb3c8fc472767b323d008c3c16a3313f4e4b8fdf1b6ddba693dbf2be5", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "f07e59f4fdf82b3df0c6f086ad919aa076caa77470bba9f054b66a08a1490c8d", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "40550b74ef97bc5101d6f180547dd4601df67b3e1ed8f250b718381fb2f8b291", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "63124fb1d3617d4855f6121929b9c9ef1540706fd084a5e1531b5e67fe257b16", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "013e55f99a1dbb4e2819cea9b7eecf98f966346e224be9add610141bb06a2061", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "2ad2e3cbe2c019f6688825f2c2ea40aaf7d0bcf31ac01078ae13ec5b4ffefedf", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "f160942de962c13fe2e63c85ebd662c4ba7edc340dc24c07b87af683f4a390c1", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "c6113de19e64159f21593673617bc4975feb51388a15999e98253c1e171acbf0", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "59bd15ddf84f872fb2ae3a28c72287ff1730e1846e49962f30917ee5e9f14439", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "86a812624c6bb0d3d927649ba1b3bee847a8c36a5a1da9cc6f2eae814b02b83d", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "1124fb0251f84e7263ac1d504faefb6786c7a0155c89fe0aca8ddea57253d006", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "6db9c7f5ea10c39d058310a4d6f5923e8410edfbec8e3586c83fb30cbf1a4e85", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "8983b0b92712026e82aeb48c0ee4a25f6413a30e5a1a0edca281936b15f9749a", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "17c5eed7dea3bbec5b8459b4ce7cd89dc4b5f208b967405cf50329e3457a0463", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "f4eaa59dd9a5462fbf9e5afa2d9422474162723caef80178a32f63a22ab7c92d", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "2e4bc1d721fd740625bfbcc326b04a47f7c1c25d328b7d7b087d01db5420c5da", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "1f303c73316968a184925070f3b6b4f6af0072023cc6a3bc649c4e65f1e3d4ef", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "347276e8952bd972b74a62ca84936afbe8e64da9e14bbba68cde8349b3eb6673", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "7aa631415dc31966443e9e169e70916b95e53800a3a552cc4d1ed77cab27cf20", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "d7cb4837e98712139a79f6d430ff640eb2ac1f467f6d0729260e51c289459cdc", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "748207fc86adbf4db243e087d66a3451ec97dee26fe307fc3035857e4c5c0b6d", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "af2a86aa050f105b30108d506614d5425b74c0de0bd514a03165e7a51dc5e922", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "e26ca072a02673fca6dd11923f3aeb9451c49623e903ebb2fe5230d2c226f6b8", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "7495b7df367df881b1612c68f8f77c01d1ae14765d3d19c4cb2632c86eea7406", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "f8229183a240d5533fefd779be2fcf8a4e8fb302859d2188e3c2c5617e8f3cae", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "2f9edc52f0c8dab5b2b05a7ca25eea9814d7eca9d30860ac1e4e381bb46a09d5", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "e5e7e5294bc83c0d1888fc15c947026e30e9fe6a276a482418a27e11fb91d134", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "c55d3d24611504031b261eef066b21757f89809d3041d7421361b47c26d6e2f5", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "f966377c54f952468e7b7376dac4926ad1b1c44c0538247208901590fd62eb7a", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "395a0250dc539942682782639fd1b61b52a7f490cf8002db2fea7c57e3486cac", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "6a35a60e52724740902043a141c22b4ffa3bc0f6e817206568beed6d2d2e88a7", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "5b44583cdebc00e4e6ae17bd0b1254f7634dacd54b2a8c62f11ad1c1c17a8585", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "1d2a2682937db58c890869e514d4276f593ee59c6fe3e696765a53fbd9e1d560", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "462f0879bc2451e60023df774c8a841c749371b501ffc79855bfce0d5dbbc66b", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "02939929cf576119893f3f34b6591aa349dbd072293a685cf8e79645e4ab2ac5", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "3b5178c56ec4aa77af330cbb9db519a05820bd7a58c29f9cf9817e6146498347", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "57c0cf83fb9bf9b8bf4e843314d04a97ab7a61e53819801f9063579663fb40b2", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "e33ed3206eea2c4e5da188233928e55edcf11ea245dd62eb4723c9e088d94f4a", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "52a6ec1f0fcd10085e4f1b4f36384ef13338af65c241818470f0b2cf5a2589ed", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "6a1f97789685c537c9d288f9f290d545d2b2911f020f042ab83d5433905ffb12", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "6a89deee64285c75c5518b8672894172e89035c30459311b72f3d9191608f2b2", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "b7befbc9710fd53636751d167514aafc1845c0209172241c0c7de13b14f9c593", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "d60af899d29d274b48bc847b8f0e4491b539b32b2846db083293dd3b6c0688c7", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "47df77e00d5f883b7104f3a38941ec459cb2856bb038a00a1fb71908c3ddfafe", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "4efe1f7f6c1dffdcb934b5758311f8d7abb1852163fac4eb6fd9f253e1fbd5aa", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "6225f09fcb88da4beb8bb166d18e465daaf67d9910dc2d23c7e156254a395688", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "54d8f4d4829910ffbffe2a3606ca290e0009aa7fbd2df3ebc40dacbf7ad66c2f", "target_hash": "788c55d907c47f2606bf7bef164bbad15773671a6161bdbf2510ee915cb1e165", - "section_hashes": [ - "f2b92246f7ec", - "e53053db3ef6", - "477522e5391b", - "88cd19671094", - "c08f5e19fea1", - "a41c2306a076", - "0328d6147d99", - "ec6c7329044b", - "80e2d1cd07a2", - "f6f09ffc8ecd", - "dab7444d3863" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -50196,982 +17771,332 @@ "pl": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "c7f32ca9455aaed5972948a18892e466c111b20a910b1601c680abd6f7d0d003", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "1aa15bb2eb2447a0803f4bf78f615a72e5d6aee2b5086e7e857596d14f143afc", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "5bc157d95ff1a1342e5683b9ea9efb14e98650bb1473e3e640ac3a72c750b027", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "a259efdfd2ef876228b62442a12450ae1910fff3d8bcdfcc52ec36440f134956", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "c481182294014dab956bef3ebba82087590bad2f20851276802156312e2baa4b", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "f03dc3027edf7aadc2f0242dc7bd2061d43f4f5a5b6071e11ca000474940d52f", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "66f3fe2a28b399e33468373656c10fb84c96fa59d49790818f8f94d4ff5c71cd", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "5c7f50f14e9f8ed17f20bcfd6fd2bf3ba0fa91d4d096a36899cf9ebf92abe5b3", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "5d4f28d432464647ef3f881cd1b01643b9a2d6e7ddc2344a1b1deb47dbd53a08", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "6a0e13ad3d7f5f8b6cc25f10bbd821d369dd29202e34bdd3ee5aebbf34a38b6d", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "0b490b5df769b4b7fc4b200c3598c9ac8219cce48194d3639fb06285a2cdd073", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "8515bfe25d059af8425d0c13fe30d8be2bb3f806cea1216e601b2ac829249071", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "1620509ca94c7474f5cdb33ba7cf023c021aa0f4390e73fd7a7fe6ea35eaf3b1", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "9255317fa9a905589a1f259cf9846f717c3142ee8821658c488ef3875f4afb13", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "2b32f44a1e1f445d47e2cfcb5f0c8d81def099fd67a57015a12c5e65b6dd2171", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "7a3f5fbc130033e5184844b46a1036d22c01336593315e9f034fa13c894b7565", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "f482522accad75978b193059d1173b226edaeaf26013dcd7cbbcbd8b1873e170", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "382dc903d4ee311f83288b47255627dcd0e9035c8ec9e29e5f19a083397b309e", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "8b42b5d2e472bcadfedc1e69c3fb0e2036097a1656c0996d9ece9dee653f579f", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "a8593a11098ad51d38015c6f8a2cc6313291df73ae81b87c667f866ec7d7bf36", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "37a8d368400de99a364a3e6d03d323adcb678c95376bd0debc5ae1fbe04e19f4", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "2c6be48829286db53e8c61e3c70a046cf4e2648de2a609015981a645f289810f", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "fc305798400aba35bc972c729201207043d2dbc87c18e0d7489cb33247189544", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "7120f8a96c2719a5721a763b749239055ff0f7d4d31093597f9c0296c531da25", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "3ed1f098c63a33e94dfb0ca75720c628896343a27f5aac1ff2ec731d46360709", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "e2ebd902d2f3427da70785d9a97786ca72a1f20f05afa429385269137a599c29", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "96ac474b81fba0d732db7b83e445ff65b9ecd8fa50d96af831ab4998e04fefa1", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "fc6b29ce818887c5cb195ea30cf8555779c1e0cf7b07a42cf05725affd6d537a", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "8567a54a3ea7be0916f3ba65910408437d38120de1e908b5899856bbb34648b2", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "8e475117dd88450ffbc05e8fbb6f3d964b7501f39be24cf2902a77da0939bdd6", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "1ea67063866cbaced8e57afdea6efe7bd1c7bf812ba8726722c5ff892d5e578f", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "27d0670a09c94fb5e81d792d8ffbd02ee416cbba047e1a63618fa4d616d9c634", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "054dad6524b21fe8dd52808430bfdd41f73cfc21f28e6879f86bb4a6b18a4c3d", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "d7d01204e13c0f6c6101b5fd85729b3e2d8599f02255c4d7ce1a34ada71328d7", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "47beafa48d7792cf8a9784fc0da3577413943bde192c81f6cf34ca10e423d1d0", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "10ca99f7263f1b7f9223caee18bbf3e7c68764bcf65c78f0daf8af7181a9c6c1", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "4d041892f99c3d8000c2f6d3fb07c73aa6375883721cfcda4b8158866f36e54c", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "666b049dddfedc1347945a76d1bbaa6d248c293ab45bdcbd75f6e5ecda72fbeb", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "b50fba3d5fc8faa92f874281cb3ba64b9e85382828180b478766eeef50cf9330", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "38ed29c462dae493914ec95d134f2f9fd651d1a76c1daa9ddf676bf4709bc384", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "6ec9ae085ba394bb6a4984131097ffccf60a7413af8459b8cbe7ceb93a143c86", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "d944bfbfd98d5ff461156972e6d5f3157370b97b024a4af9477eb50ecf99d8dc", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "5299446506caa9776ae6bbfbdd4c2cf11af0f1fb1e498eb3c0dcd17d09d5b70d", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "90f61374348fe8184353d3f1bb9d0e35983c58cee7781127b077e9534d3de855", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "4fa4b3ddebe8d48c4e459c87086e732af4b292db86c8d5dce383bf8f45ce2f1d", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "bdf3602064fc581f04652d1f6bdb7a7e505258cd33f0f7dc528eb2183f03e4a5", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "fbc08cd56dd645e6afcaa07e2c99fe40be0a6723b67bfcceb6d1b01d91b74579", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "7b1483b3414dcb30f87fa44f7c985ff190f0e41811abb845d432b8f118099f3a", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "ccc0af1db649ec021701c8d6eabf525202816423cab0b6ad98f75eed016c7ef9", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "0f94bc9a517a6793260903a6060d7a949ba879b260381a2b687fc5986322e782", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "155fd6eda12f3e9e4daaf6f4a165ce2bbe3b5719da85d76f142b41a7435f9af6", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "0765ccbbce9fe89683619cdcdba64b858679e5965c3fca1980aa5d87d5c1a1a8", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "0339949ef30b035150e63189467550ed290f1a8bead965081da93d96a2dcd559", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "97e72db5f15dc3c3298de91ff6707246309fe51d02e55f03eded9126d9610246", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "01a6cd63d84078096973793eb9ac6a1388823824cf87cbbf8dd1c81d583da4d6", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "167e7bed9a413e9cc9b0a929de2247fa2d24ce3da9574b8eef1fdf699434ba95", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "9e7ba7991ba97d330bc10aab3d91a4c7f3064da505875a6fd161c510eab30490", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "7e0320fed4d2142fc49fe6cacabaa14b70a4b1a195514635bab5ba29469e4089", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "a19d3457cf087924befec7ca347fb43af22e3f84c5679e75ef90cd6a25920f4b", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "cce445f8a3da64ad9ef599211883889efbca7bb98231fa7a6ef0469102a77eda", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "8e081c1174e9fe57b1a1e1e566520e7b118be3f3060dce53663c9e0b71d6108e", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "cc8ee15f0c407fa76793a9a968278cbf44b91e0b01aed5a82f8e317a3789da9d", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "c8950e8dcdcf8484f45bb49b2494dbbf937398a261bf8b7546128a4d243231f1", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "846740ce7e48ce753027a2d2d72249ddd3dd75972598442dbb2b24ec75a49d71", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "3cfed54f11898cc807f12da7acc100654587b677054cdca9f277d0b73b161614", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "30acec62f3d5c0a44fed23e0d011ee8953182ac8f28811a0aae4eccd993efe3f", "target_hash": "729d95fc9edaa6dec481db37842fe0cf2fc0f94fb1068b30c438578138fe20a5", - "section_hashes": [ - "dbbb352d2812", - "469c3c8b5797", - "8d21784660a2", - "1344a26e7f28", - "14a37042279c", - "9dc0360d2e16", - "a97a5de28b90", - "2202cae42ea4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -51181,1520 +18106,332 @@ "ar": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "a7968786eab1b8f5806e5b2cf6c361780ae4ae22eb43a648f4bf97dcc91d655a", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "e9ccfc2d601861e1bbc92c5e6fc696d2250dc3e8370b6be31c88ae3808b8e3ec", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "bc845497e0f52352c60bd88f25f678dce2b6cfb0cc84e282f2eecf765df26c87", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "2520586960006d8919e535b488f54af33aa733fc3c64b3517ad1775be1b821a5", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "eb7e17b9196068cee227d86b2713112deee7a5cb6667c094dac37a5b67e7d234", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "58326e3d896440053696734a538243072b660d9ea9c46c15f1d3b7ee4e04aa9d", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "03a2ed0c693e97ac1f3815cad3114d9f90e7b4b982d2a22712f64debfa88317f", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "8f727fba3797755cda67818c09fc9fb33c6fe8111942395e29bd07e3dff8726a", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "b7656dd98f8234e6e3609633e23cd85c0162ad2c228164a70d291e4b2c568a84", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "43508bb2488f7b652af05bf77c046b050525ef5176f19654f3a67659869af2e7", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "0c37d0f14e5d3cb936d27bbe9bfc8f76bda8fb439fa519f66496d71a26fae378", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "98fe78e3f10bd92a895eb408345161d4c6ff7dc99d8ee1453d6d8ef03a0824af", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "ffb2b7071e40594038274fa6a196c7b4d9f31be9bd7be11a12fe012f639b1dcf", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "363a731c5ae90baacf490be0042da788617699598fe95f930641a98fb8f7194a", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "c211dd093e3238d8e96af66e32d1d028a01567bde8ce46044daf6201da6b6723", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "dabb481dbbb07b86b158a56e3a328ffd9e3d8241a3a9a614e1da6900ff739038", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "f0cddb65743fb4a5983447972521fa0a2fb5f37f33a8d6fc7d3e06d027688ed7", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "889a2f6b21dd6a89d7550117388f7e599a63196ea127ef3e9e30dbf4d1b74bd6", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "55539ed8921cb12966fa8aef28308d6f077f39e39a6c7f730a5dfdca4821dac7", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "4a2bbfe2ee3656fb31854ac5bbc0946b0695f90061d8c1551a9dcc86ba47b561", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "d12bec8d858d121be8be2d137591a943cbdca5f350e0e233c0a19f7f723c7767", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "3e244e7ab4b729ac16ab00027fb5e743f4c6fc1baf0b668dd379b2d7de601ca3", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "77df3ddd853530df38def439f1f7e86839601e07fce3b484f65556d256bb0510", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "62bec21a729294c17caa4aee78442725fc00a71fc2e7e12a8454becb8548c407", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "cf6344b4cf0a285fc5f1f87f5ac8f98522a7b999319987825c5375370360899a", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "20833178eec2c8c898a9f1118981faccf7f18d1c4003015b1054164e80114502", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "d58b31515c8d186a0c370772e32222e6a20ae1e011d3e993f6a9ff4b01778ccc", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "e33aa679f5abd9eb941f2766fb04ae289e14cb35c7ba12686bf06beab1a97e74", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "1494ffe6ef05676985ae83691b14d8bc7b3900951a0f07126ec04068db12a6c0", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "cfc99e6af71e97e6aa6eab049b7d070bbfe674506fe66b5ba59690398e180f34", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "43b736846e289e2ce46a95eba467c01a2eed558e1c0baa5bda6997b98f1b5918", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "6dfe0c8dcc201ba784ec43b4178fbac1388ea63ece1f005638263069770dfad7", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "a3796abbf29b9e5c57b2ffcc183bbc47054f918cdb623bc60abd450ddeb40f93", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "577f6ecb9d7bdd3ed52f6d009748bde0c18f0373661f02c7d572e306f48c3238", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "1199e08af53dde4ed6f3fdd45ebc7c9a87e07a4a99c1efc91d605ea51e410fee", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "5df81520b1e9d3930b49fe2736c02984086137d964db6103141697125e9b3272", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "12667eff9ee035b40f1d885a0edc45ddf31df0e204214894a0c3eaedf9a23c3f", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "cf67c590eecd419119f4d4630acfc3740582aac03a1f833981341ac8fdabcfdf", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "67e9460c1132b770be495485143ec52f0b47c5814d4d7fb373a7fad066f448e6", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "295ff71474741fd97b64c90684019a340a5c77d195fcb07299b67408b2ae3ee9", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "e64b675b37412de1e9cdd11ba103fa2d826fa06fc1ec663c85f44c3d30d46122", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "2af3514d845c7c04d6299fbf5bd40cfa4c4081ee2a966e165203792dce7502b6", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "82558e31a3235ec998f027c11ecee36d71b1228d2ca836e4c3af016d5db6f527", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "592248d29b43712ca70295c747495d75163d4908d4a359d2df23b506aaa121db", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "9cb8b5dd4e8b2a6834b4fbb496a578d3b39e6af63d68252a75cb9bf131c29911", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "b2533a4911e671f482b8625465fc098fda0511cdaaf94300d41beafc635d1df4", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "24276799307b06db0c6a18ef2d838fdb219d00b8f051fb14cacb497493dc23a2", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "991af7e9bb818f2ce1c6a18c8ce95553ca762eed8e1aea8f3d926972b7f17d49", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "97c3d7dfdef1c7f1ad0e03e7620db7f426fd77ccf52b1291f07eef4f7f774491", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "810a39c736dc9900ccfbd02f074464407bd7f04951c7ab1cc28ac7eb3f4eb0ad", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "0fc34a6791fb226f4d854e3dee8d46475ddb1946fc35e4164fbd41a6317d54c0", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "0a902957327b94ccfa3ea1b99d8a64847f0521e2e999d9148442e1f04f811c4f", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "ab317c509a7b364c62a994b5f8520a4d9f95d34c4570f3488e7921623593c2d3", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "59ee7c4517dc0d6c2d60c1b9c71a6afd3322163ac8295bad99276e1678047230", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "5ccbeefdd92ef1100e6845ee48698d68656fc42fe9b59a05ebeabf6943738bfa", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "341da39637248a9460ae0ea7ac9756cb611a6ccdf54af7b86a1d16f7466c5ece", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "00884a0117f91b1e8bb30489236bb7c03d6f7d6115e9252bb95016f3d63262b1", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "f94212569e958370f83a0154cd8cfe0779360d6d39ba0427038940063f6a8d5d", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "d6d8c8d6eb6ce5a7a070dbe1dcaae05f01fc9a4c966e0775751e50b8200de822", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "6e194fba0b476cc133cb1c2be52a42a7abbffd47721271a5f5c545323f01e5c0", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "d9f1ca5d0054dc13251ffebd36f797ba50371114c57325b4d3eb8bb37a09701e", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "fe711bd9c3720d948fcbe2b0942b3e8a3c02415e8933ac03bd26fd6375c6e8ca", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "6dc3ba2a0c2508cc8b235d11bd4cad02bbf131c4be6ebe15900fcfad7d986f1c", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "7ce038865d6a72e7039aa42533f70e0f0f2866872867d9a8aa5fb6f7ba0f375d", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "58c1bbc8b9cdc78f10f287b6c8cd5de6c7bbc1f70151715ed06099fc7c3ca220", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "c2b293bf2def1b9e429e78d4aa908f1bd76b35e1985eb117ab930f4e0cbd1067", "target_hash": "9d3f6344ef9ee4afdda760a0ff9e7f0a3145b7ae9564bd7d53d6f99ba3240305", - "section_hashes": [ - "7b8751f4e119", - "dac0788c236e", - "dbf93e8e8a6f", - "740ed3a8f3c4", - "cd976471b45f", - "1d0de77e6fe0", - "5d24a9935f29", - "49f5c5f584e6", - "b8171a31cb02", - "171018793a7e", - "e536f6272743", - "140d778ca589", - "b5b12b2b4832", - "d1b97406c0f3", - "3a657a6baaff", - "3e44493d657d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -52704,1190 +18441,332 @@ "pl": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "db35454eb5a15b5c219924039b9130347c36535868fadbc7f62e37c6680e87be", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "22c6d9ca752db08553530c8bd21d7d43b3b3872e34d0b6bfdcfa99f896294407", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "913006cd122eb96c053f0ff7c1b13fd437bc2193555b38caf0aa69e2ecab224c", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "1a63513e9a67a6a37807f7d88232268b46f7dbcf8b05546556c9ca6946a25c5f", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "fc0b0a213dc1f861080e4444b268b0365d3d480e4e2a048f004b5fd451a14290", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "e1aebda69668338e98e304ae0863271c40ac8ede5a871d874822bd56a4e47fdd", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "227bc035c782644d2e5bb4213693564a8bd3b8209b519584c6461049dcfc2b6d", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "88180ff0e63a598e884061ae59e56de58440830a4e0a7e9b12cccf581dea459d", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "3e7b6d2a44472f3126afa4d0ca511c74c374e918014a9a028d60382a71eea9d8", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "8d0ae734658090eacd96880f54717517f8a0eed40b6dde3442279e5ddbf21231", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "4fe4ce4743ac5671abb68373c98554dfdbc5f2f2de9b79df842c6beff32b1ac1", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "ef8986c01e928592dc2f4a5a5c6aabdcafd6c91650ff622abbbb4ca7f0e8ef1b", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "6a00525adc2875e8ec7e2ae73a4543cd60551ca213729b378af107a1b1817c6a", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "9a7ee658c2273daa97701a91fe5c12584bf2e69e57cacb45d0b4f6c328765449", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "1e4835432f6fb8eb12fab3189f4d7ed290ff4649458ab8921b7f8597def7cc13", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "85f60e6f753adf8a1cce8888abe9406f74d4e3383409994519d05143af585fe8", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "77681dd91123ebc0f603654ca4d617014838f4c630d90b18a23ff1dcfc96e412", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "6ea69a33deecb2720c0e7aa3cf2a5cce501c1aac5693b07bab7ed69515c7204d", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "cd30d808bf12d1c8cba5437bf602b2430293b09357073e934a1de03dc5d5ce15", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "94ae6b9647f6d84d036ef49cb20ac024695c5c9bb6b9d215606e0b2b8b9e700c", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "07b7f2a6be0c009dc5d3ab8f772b8cc481e3b309fd43db0032157c0b7b88c5fa", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "515581a78a4e68fbc56d24d4cf642f522cfb3a2ebb3c0ef13d8e61e231634007", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "056891f219790ca2c5737ca88e721ec3637f200c7d6d95b32c63a560f0ef52fc", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "0bf670ad64466b8d2e9fbfdddc337440501dd0cd6ae25dac7dad17a9c6c3a711", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "c52ea78cfd1ac52efef8c48e364ddc21a30baf0d8a6ae68ec2c72ab68ade2cf0", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "1fd6b700349accb1f0a5cfc0d98c99f1174bc29b910b8d877c250e27aedb5653", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "f781989412849a48bc7e9803468e0a2b1d1b6b88dec9482c25575ac73ea57f31", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "db60886b15401ce683f091ee3019ff61ea73b44ecb593e47d1a05a7e348d9cd4", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "ecda4bed903b4fedd6a50ba2d729794d3e6500116190e16be4aaef576df7de56", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "d246668ab9292b4b2bdddf98f3df1a069cd0de831482931f426082182de0b946", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "9866d552ede9b65528d3d6c3f351cc00d7e40e84019648aa9d745417d6170ef0", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "aa62ccaa5d6ed4f2bfa671a3f7cee8bf38a034bf7fa1e51ead6bcdd585de0cec", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "2c6dc37ac332bd1d0fab797d29cf487c2c72af109f65b83f243ec9c7e7d95569", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "d4bc734ebf8b34596fb0320701b0cdbacf4ca9da00ab8c808aaa7fe2c3626de3", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "ee4ad87cae8ef68193a0c427ee1ad07dbf2d52c0c0bdb2abcabaccd4a8dd82a9", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "b699505002c8d63e556207e654001aeb3618a16cee342874f377b88f340d04c3", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "464274d8b6b5cc3288c58ecccdcac796accaeca82281938bcc87d72c777febc4", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "49f04b9c026a48a981bee4088c273d7873a65e2a46af7a02000e5cd3954c3635", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "7c5a24220c4cf4344097671829fc8d52b4b1885bf4f656bca368b53d93438a15", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "5f448e6e9db3653874c80e7170663048c291b517a1985c8e78a0b8ee1ea1bad4", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "7fdb2060f7e39e092d6091b098c4ca1d541e7182f4f5b94e005ff7807011861a", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "63b9b525bb748352c5410a2a2f890e482429a9f9f6c9e854db3b11b4301694ae", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "f4057c9d0598d8023ac4d0e44aef8ee50d47cb75622c9ed3bae35d335012fbf3", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "6d088176318b96a9614b0619e1cb770869662afc5187d9499b332d0a7a48c917", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "01530c6c2014c8b2b2e4dad09fe137df6193cbfc5a2fb5f4f73c3de550611ec1", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "b70e334699abafac3b510f321995448a9d64bd9d29a856e6fb0919fb5de4bf3e", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "78a040d830b46a7ef732355b3cd7234859205fd18de6a317f1c3fce1a8172842", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "2e48a5cc9b63490da77b5167e6c9dc6aff56ed36f8e6f83ceac67f557527fc9b", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "02fc158784abca9446263173e70ad26377f8711682cf334c56b4019559216c1c", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "25015b283cf6c5ab0ccba3150181304c64dcec92c9a4b528be76c401f4bfb628", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "5d0f6a4d8b46548fd64b6dea3b44627f08ef745d71819f89e9a08e558dc1ba39", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "aba544c96b3e14886ac07d89417ef928782bac6900af07bc4238896b60e1e056", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "eaee17515334d495e95173538720f12c9ab45c65a7b9b2fac9aa83e6d02f6e97", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "f2b27a112d8700e3da078b1e25d17a6a4d52de642ce5077763fc8cc22bfe4e10", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "6fb0e49c6ae71cee2d086a79379e6cafad288c965e137cdc4a2e7c20d437e696", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "1d1ec94a5fad3f6192725fb520e7d8dc6862f611e75c3d3418079f2f3c253f8c", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "9fb43826f051ae12377d8d856e9a8faff3e6d1fa0d83bc8a60aaafb5ec0f7644", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "fa942bb264f42e37601b02506b1711ae63352c3472b24d69fab747de4ed8a3a3", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "560b58d1ebd07debc95a81ac692551e85393831e36e6f3ef8a7f6aaa8ba06e78", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "e6d0b0264b7901389b7867c213b299a12a34112e16217a81390d2df0b21349e9", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "1e56442970414d8589705647ae9d7e9fa90ba8127f146888d51d72cc0bad44dc", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "2f3f612aaf66eec4b3a3d91c28802afcac2b770a6a9b5d541b2e5e8fd0f40a3f", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "563c1a9960f05ac1e1b87ecef6d7890b3976117d97bc6cfa9cf120f38f9bf07e", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "44472c642e24c903b53b6e71b1bdaee61896af1947e0a07c48b614970100e640", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "2545e4d32a4f15d599e1e6bb382f691cdeac8c3ab1f7010b1f33f970853f2ce9", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "4d7d18abb0225f17e6a844dbac1b7759b56d714210f79b7ce28da3f544adbd19", "target_hash": "55a882a2e4f89c5eb9b5807ae3d87bee14163ca49d7645cbb395f569b32d276d", - "section_hashes": [ - "382755c06b74", - "528f19015a47", - "16b9184f26f3", - "97ea48ed1049", - "4ce104223496", - "06b41fb1da6a", - "c9da6cd47f23", - "f4e65ea07dcc", - "1e227a7d2905", - "909faa98ca90", - "1a5b8b2bb7a8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -53897,1502 +18776,332 @@ "pl": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e75fbd54811dba15872799e8e916e16928902da70bc5574ee733b8c874c3fb9c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "b072b9c0c8ae284daf03e19faf7d7f5a4ceadb2b98fe941dc2a448f48f52fcbe", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "9ed0d8c517f5038e65f8610c0ffd68d95d9c9669aec967d09e11035725dd9a8d", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "2df8d7e0351dd2865aebf7ade1a7b9972b5a9f292339f9ba78c0959545a2d997", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e9125c74a44c7c42604450ea431a970a971d1966e537238d21d6e9b74876ab84", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "73fcd818ed822343e9fb342e680ce947569d07326f4f730920d4f0768769a71f", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "4f55858ce373ea763325f0980fac2fc938d26ff0e7e5b9901c13d524b181fbf8", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "ec37749c61151d7c389599688f02b337fe772df979fc61a2f298392364531ff9", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "6cb8bb632c2fd8ae115f76a1ba9ffb98ae503b36db14a25c1c19aec96b8462ea", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "79cad1eff8f81a4037004d6d4c71890f5348a9667aff89faf2686f906b60dff2", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "7ad18d6b28e1748cbb7e6d6687d81829d3d363d9ebdcf93ea5f827a994d830c5", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "2c77b2bfc57d5a2463068a19378885291d7c33bc89aacb997398c8e20ed431ca", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "efda2cbd3bc1dd3151a407dcd8adf999648f1c1f720a95583ad0776f5cf42b8b", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "92699d3b1c940e4f9c94c2f005a72e7e8ddb8ed4bea8180ae0862baac0d9b263", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "d0b1b8f8a948343a4b3d941e91b6e51d2dc896be404bae46f5f56be3e38e8df1", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "d2b9ce1ea16f8a9ab8d0c391c23a87b30bd33ba4b0eca8583cce18b6fe3bee68", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "9ac4e24419e37204f60528f1f17edc42a979233a6386818005d83e127e5ffbfa", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "f3647e9ac11ad5551d4c3de74e08b97a95c8f35fae63d05f3313b54353a22a69", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "d5adc899e6fcb4862dcdf3c4c7c21a81649ae7a3ca51aab1e55986c287c2d5ae", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "da60e7f6604b50f6e16e93b3cecbcde063b7fdec2a6b3053247b3f1692960067", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "d5f85b17ffe919968d36907e0352d1e98dba4951ce4b64d69f917a6f1e271bc0", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "f8c0187c61e56a28e553c398d266a55761eb5749009582ad030b881ad9055ec3", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "6520c6618ebc13c1b4fb5f53b3c3842877b9aa3bdcc3445fa3756804d79de7c7", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "9ee7741bd91fd2aad4247a9af7ee2f127969f379fe0ef0b9862e5c3cc9a67c92", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "3cbca2cdc6f9024006a8ed555c9823e4a92e870277032e3d9f5fa84c0644d5e7", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "8174a66d751772edaeedda4664ba5c5eb1af91751df02d10ad25c66816e073f3", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "dd68d6e7b0abcf11f303dc0d7b75743851025bea24a5bbba952d90c97c95224b", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "cc74257390ccabb6245c2150ab92baf61b32cdcb737e0c89ec6493fa2658132d", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "6dd88556f16c02ec9c664bb01ca865765fd6733f1b6c848e160551f84acfcc22", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e94dfea5bd956579f734ed9c8248f9ba8235ee20368cb9911d7c25e4fcb5b146", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "73ecc46a01798760c0b0efb8481cceb003782244e3bcf3a16f352ed999ff615e", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "0fa1c737a0f7cd125cfa0049af2abb0818a51ea95b757f224d941c983120b11c", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "8f40b885045adfe826ddedb69ea3dd68c8718f7e2edd8609115c6b44ad53ba60", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "d28a1649ffdf21e854c45c6d5c8ea9ac5cce8efc2d54a21d7abfbee87446c9dc", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e694e59dbae7912cceba40c6896098297aed30af3aa489820c5b7efc143649b2", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e58815c87ac04c70e3b59cdf32dbc6fdedcaff7e150fbdcdf263dae892b40dd3", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "3ff409f12a87cf1a20c8e796945db99f1953d137580503ae8536d0ac790919c4", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "51ae99d9b0c50e44738abe1d36302e0c5e2bbba61393d95e77deeb1d3953f9cf", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "ff2861ec03bfaea59ebe6f4d782b8f71223e76dd034fbb252ff240422ef98e8a", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "3d7ab00a494ac38d9403dcd710fc3f8dca5d884df43d459fd56d0a2e6d00bfa0", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "7685a9b08f4fbdb0bd5e12d03c39acd74402fa210a2b763f1e4b49f2df06a3a6", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "6e5b2cf6065352a289d8443cdef364199db089b0bef50dc3acb2dd32c56bea66", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "b6d87e3914ddd6cb58c47770edb5cd73b60402fe7666f2928d0be39906130b7a", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "473b57676d8825daed5632a30f575fec6cd28defb13444ef936b1320c5fcb30c", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "f6f31a7ca24cf6046e0533c120ab766406044fed345747bde421f80c39be8d4b", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "624829c8950e865d09be94e3e099923c20198bbf19259dd6114c89016fa70e43", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "92d3f1ec3d943af63dabe663d4578c02afaf370928a2d41b8ed83395dba389d0", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "8b6214c4e368e2b72a835a64b17b89fa7bfe9408c7fa3620b76b1a037307985b", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "183788f18921231f3a278f3ce60198a0cdf3bc490aed920feeeeb3741632dbdd", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "9a7c40bf0b2d56647714fa39d3f08d7d2ff531131383855cbb3e7628a2856f0f", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "3dcf9e423bf04fa69a27c05c7deaebcfcce2a3f994a77c8862b31e34eeca206b", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "da0eb43034fcd4ec7d25459eb8caf8b53a1c207ece84d8dcc6f7d1eec94be899", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "b7ea569714e7be5a38309a740f18d67ae458947bb3bd0c038991c1e86ba3f7dc", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "5fe424167fb1991286e31212030dd2a7338e3f3db52173dbb87d5c1b4f102ca1", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "ceff625dfca14c7b135a96b557f7f2b1801d3690e039110ca85785def2dff0ab", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "649f2bf82322a076a7d3cc1090f482f0d1ca68626312286f8861980facf4aea4", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "24ce58a901b7ca11aebf25fd2b842bdf2b844350a2a34a17bfef1075c798a186", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "8e173fcc8445b50fac5309918b6b553b9533705e4b2abba46014540333d9c34a", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "003aa6825508e62caaf514e5e630820c7f3db3692a8d3221ade42655340d1c68", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "118e13c27ea796ad41b343b0ace0089f2efb27b5f2e1ca526037e861af820c35", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "7b6c09ff562b6c8e1451e564f3b8992b648ad00cd532e46c52cbd4ef502ebdab", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "f4e653a69c7bf77d6b83b6ee44a2352f4f96548e3eb4cf785c96454e8ea4828e", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e8ea3303ee2919e7cdbc483884d1f074162783a7eda7cf185002fc580138f836", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e5823cd3aaa4e1ce50b01dc626aca919d1e9d5a23305d51bd978a3a1ff703394", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "4a6195513aae7d980c5765b58c1b0d2e10811b0d402f577a4d04402d1dfd2277", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "28a01227b43d4bd03a30354fced80cb04806e95fda984df28afbdd98d841f018", "target_hash": "e57d03136239c655e234176d94abad65df29e97946a59b3a56a8347554349896", - "section_hashes": [ - "fc5a8bb70d43", - "746255e0324c", - "e855a67c58ed", - "b7c6f437e44e", - "1cffb8d4d7bd", - "8e16293e9ed2", - "ecc6f9fe2948", - "57ac1ddd8937", - "86e006310fc7", - "136a5ccc39b0", - "acd9e7563ee4", - "518646bfadc5", - "a00012de0aa0", - "a80cfa8731b9", - "7a1a198d2f76", - "f547e8224895" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -55402,1058 +19111,332 @@ "pl": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "223fc63280e9fd12eb945f6ca6e0fbf7bce3fc79ff64758ee3e2f141e47990e2", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "dfba75adef7fbfefff2f7298970e44d410e53ac90495c0cf7d0e5bdfa9e35403", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "ed11976571f4af7d3aac506df86642a3d3b99c937b6b665467acef6a3dd481d6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "e302c532e3ab5c8d75784ebf29e80c2e2eca3975baf62d72eddc0f6ac80360f0", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "b1c7650378a11c0ff24c13695a4da67ed8e8b3b10b01a4f2730ec2981da7c20a", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "5ed7acd6d25baec46c27631bf5f3a1846b53a96fb55d3971f2c5c71e5d2967e3", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "35003804f8705a35fa529ca3aee43f90c8391adc78a500d2a804a630e7c1b92d", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "092152d0337467c34e53ec376fe9e1e030dca09641ba3a97ef938f1fb13b4f15", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "1838c60a00043f8957ce4351a00cf096b6af6aec2322bf69acb61b480db6e127", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "9c88f713678e38ae0824209b40fcce6badc9b0b8b5d335471c13b28654b8f454", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "7a8f2100ee64575fcf373e9d00540e51562a12eb62b234bcbeb405b5100332c6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "378c3c8a8fa781dcb3eee4e8423399574ed3d228edbc81edce3306f1a6e53c77", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "84dc4d69a98e48eb5bd7135f3143c04107653d09f909152a2c0cf605464b9b2d", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "8d49e252537b7cb0231f3f53585bf3ccf7935b7a87e513731789e124fdd30ab1", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "9194e9de543b551e7ae69238bcade9a7f8b906b81b9137d77f05aa622a016f29", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "34db1f2b4518c15b6598b5834a9245731f70c8eb72d97ebdc62e717a1bf62b27", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "a2a3c240a58eaed2bf035e5034cd800a8d7af79463398162e2b8c95ebce74535", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "d1aa741c1f5dab820ea66d36e48ec25dd87d99956e4eb7b0d033c52c465b91da", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "1b28e6f45e9d6368cd59a9c1da10ecc69f65c187dcb95f8151d71858d1054778", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "c5fcd66bc386a9d13ee4dab236d4961624835575c2cfd21da7e16d610d3c360f", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "c8de25a861f30788b33f9586e3a451b696c3e7ce64028b1506cb453154db0215", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "5b9939f6502d2bee2fec34dff9c66321db26d71fecf945906b60684d0a8a1692", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "f36a95395550358a00c8ff085485ae88664c24341d0f24420c1843deb66c8b30", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "77925e6914e89d4720fada0ef79b14ecd62cf0614eee68c2eeabdabe1ce3859f", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "001ad702f516ec2a92e3bfe26975550cd72acc4bc8fc2b39c9ea8a0134e89c21", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "e8169783c86927bfe3c4a06ec4ea4e8a9b1cfd621e98f38649e40da97a301470", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "128f828a68e0fe06539f2116483e16d5d0ad8a5b58d950d1c127c7e2d50eede0", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "a0863ed53468f43ba709addc60fd5168afc9a4dfe7fe831dfdb70df009bb004f", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "08ec6e368c05ae616c88ce25df7b03b7d2b998dc1af0d7557e2db50eb096af22", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "e1bc13890fd74cc0e6f53b88955fc2f5468fe869d2e31ca085e26706180abfc9", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "2b4878479a0d62b5b10ce684f5b69ebd851aaef513ed3e57fac9b615ddb17258", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "77d8b51f52f5e9759e732e1aeebccf9cb17c280d0e9e69725b22b44f855635d6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "dd5991542f03da222866ea11bd7dd6df5d224eacf6a7bc5fd907c43a836c74de", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "b1718ac3c4792f84a2c56057d18075f6968db3ed147f806b9f631b8d8e1ba333", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "78f62498652d32f1328a836a432ad15d5bb99d408093ce3d54f055a60f2656da", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "2ee7e2212e8f689b36615fcb47a404b71d5b86a9fc88743c313e22727629b949", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "8883e23cce5d789b994a4ce7162490aa780577f890bffdcb0c45df72f5b9bc9f", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "30584ccdcbf4aae72179029ec5e7935d890111fe1ad98a637d2b0b0acc0ae2d2", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "4905b9af081d57c8a6c306b7a12d9e8a8da86fade9ff9f4ee824171ab17f65e6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "0c9e129e4b536cf4f7dbb913099ab3f8ffbccec8554a064007c32e3b31f61e2d", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "c60cbb74cd48853f72e53bdb335abf61ad1d5e433fa97eafcb8570eb79defeea", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "efbd275c0af6890867f9ad6d377e62100f1ddea955ff1677f03ae02ed656e3c6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "bc5c967972a44346baa68f0295c6b06fc3e1cfa8b2f2dcd0d97e18e9fe54353a", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "4c052669d21b819e077af7953944af7b1e01c5dd469313d4d51f96cc4a8d7f60", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "b600bbf138f5c90b1c31189852af27b99de15b2e2239ccda028c70791e713d41", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "3f60cec46f39f85accb54301ee2bdab7a1d47122720eac64584a5a2b6df13cb6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "adba1d366baaed8727dde0de253697cea9b9f1431a1dacc18b8c29a8544a4415", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "1d048475c97ce6bec1b8989fa1791737aefeccd13c9fa723d2addb4177395189", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "2342178677cb5c90879ca2a755ad28cdc80210ca6b09a91c17881716fd82dbcf", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "3bd4d62b48089dce404bad1bd3bad7f4c7a5f1bf8cc48e28fd32721f0c070b14", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "5a165e238a612fb35f0e5b8d677f082be0de26081367f63032da7d4898e66cad", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "75d23b6eb3ae2bd96ed336ffb40b9abc51ebf628a8f7f3928cba4bb41c7184f6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "352b363d293754c11deeb90943fcdb0c113c12a8f2c9c4a56fc6ffe1cf070e40", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "9b571891be676aca6b69e5bd4a5f064d02864a50f644282a562f4e6b229b81c8", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "25864e5f3d048ceb1b45eb9291c53b41610f23f01c6d3873473d0842d6483481", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "fbdec2a8af9505e638322464c518784ea6454a79572bd42e2b9f1297fb9143d6", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "0a5fdad4d8995305b49b3197b90a76a451ebb6670e0e2142aa320f022660e03e", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "f9ad4a37553fa20555da3f54dc5b550668efb80566d14684f260e371fa6c11ff", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "313aa87785b4458a51a124b1624419536894cf6f03fa4573892304069b1b6be2", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "f26daf59953763421c946be83f397035703aafb8e74779f371582af9444af95c", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "af377a15a1b2655209d0c9fd0d1ee05ff47e44eb35f7965d3e56926156ba4b97", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "f41bc14fc252c1b554255145b26d01a2afeb137554047e2e79ad7c03fc7dac92", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "b966576c6633abba7e656e863dde037b5414770b4a63b95cf103da926b49ad68", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "a5490e30aeebb62c752118f7e750b58deefbb219f4506b4978cfff21aea4f227", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "38ffbed4d0d9eafa2e4eeadc2d14ac18c07717240010a6b40484d45feeab2b4c", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "62779038dd1b2c49fbf21e7ac0a3aaf7f730bec129d271d56fb9c5b31f6df4a0", "target_hash": "9e383b8685a3a809c63dfb343df1b2adb014c8b31d84bdf8c1c8cdb3bc3b38b3", - "section_hashes": [ - "b61e37dd17cb", - "f4906b7affb7", - "a49739c25d6e", - "3a5ea74ef2ff", - "1187490ba9a4", - "f894b317bfd5", - "4ca5d2c69b8e", - "ad2eadc559af", - "a84c9ef97872" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -56463,1190 +19446,332 @@ "pl": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "ecafb9259fb7947d6a21a12eaa7f9e4bb637678e88b54b72462a227747ba2cf1", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "206c0f8662b2b2503395075353c886ee9e9717b6f0500587e3e38c458040916e", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "7de987d18d0af9260a02ef8a04f1a8ff9599933720122d5a7aa23a8e2dbfa2ef", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "8c2199aab98c17213fe52579b80bbd1cea6263496d1d785d5f466eaf9f708a7b", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "91c50bc6272ed2d3f4d7d14a1f4fb0b458f3e4a15dbb3b9a7da2efc860116713", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "652ddbdde44326dd5364e941c29e681a159a511cbd9a095740ba6c8aa5d4a545", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "c2f08edd0210e4a0764fbeb26afa87dee7d8e0fe8fbb7830ad32bb61507bfe9d", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "32ea3b9199b3716b0918d242c20c9b0ab8f0f32ee4d2732241d52f6763c048a0", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "5ed7055758a3bd4a557a6c59cd54247e5fba7b1b7a0322c840979e0d8fefb63d", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "81db4ea6d95f8a26567cca4a6d14e1338dd409c95ab9797399204c19eb39d2c5", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "02f9af37fdaed533c835f675c05d602e72d0d9e40ec14dd6a0f25eddf68ef5ff", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "59b02f5710d5af8c4a998c51f139db1f452fef251638a3057aade534d6db0745", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "359f1b514d0fb51bc121af601e79ce36a4ec44a9aa9dc089b6f04f4f2f6e51e8", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "3fa2512aae65c41b53d4012114dbfb058b23c50eaae55137e47d1db4e1feb5d6", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "e2275a8a960232cb76904cd1268ce5f9380895e4a40e9c044cb793c767d5d735", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "fbca1d2dca7923fb24674ae8fb82fefa8795f3b444481fd43abddf1f5417c75a", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "dd8dc8467fafd0d6958c1d93e1b2b6d88d3a05a9a63e435e197db6c694a30bbf", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "3c7de61159e3c217ba2257d04ab9f5d9b127ba90e8cdada65a03ac94f168c59a", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "37875231f6a428125ebd82cb297bc6cd624197f721f056247942db7b39cd4207", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "a6f3c472335d4eb34133428336490d9b7ac7136d4ed035d45a5581445441d464", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "c4560fbd23829bb4a850300014981582e0f377cf60d7e5f95fd79888d49b523c", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "f12f5a0f9bb4cd183baa03549bfd4a814ce1804d7f85d5c3b886ca97d76c1c84", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "d24691c6ab71a8820b047ef464e1d6de00127ff5ae53ee64a1958ece6d006661", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "296ac7481037c43c2ca85dc61528cb4f91f01fec01e5c7e30f4c75de92f769d3", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "139d9e96290aac4edee03378189afbd9b625c031302792dc7cba536ecb02daea", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "77ae5357fb65ab28ae0a9b38b133d7c439e86d128b188301fc8d04323d2efc89", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "4d0c5525b55d4b636d991ca0ab224628385ea5e58d3523454dd3cbfced97cd0c", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "570447621f148553007bd964d92160daa2d617778e7ffeb533a92cf51556a6d4", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "b1a72f738aebeb84ded88f0d5945f2213f1ac68d923a219e2d93ae6a67e132ab", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "99d94ab6f611330da56e449751b556efbaf5a92db76f39cc521789fd24bf378d", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "b714e1180e0694fea7ca9e91b5f3db7f7fe923163fbfbd09456d60f81ce00a2a", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "77b68950f25296d4d2a9d5f04a316e530cebe4af4205757b65a83caba514f2e1", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "5b4bc8912cb7b4371eac1b890ef5c8cbe595fac3584c882bf37301aadb127d3a", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "d6b9d13d08318157098cdb49a37dace9edbbf5a83f6156a58dd6ea2f50d37b80", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "aec9d8b9a7967cc15ea08f8cbb946fd6a2ced2c9bff7a894afce4dab0c420c9d", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "eeab7fb129fd966cad6e0ac4ec37c9ee7d72c0516aa29f3dfd04ee2da71f59e0", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "ceb9984040ec652beb662efa6e153e1087c11db7369b38cbff4f23d2d0def75d", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "6d33cf0c41bcbe5c13413e680c8e7c3d8eb8ce82460a170b0d7087f2def88ba6", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "d4e6955dab79cf101cb79cedad24107ee57699754f2116de3380f4b7f237ce56", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "f394165eb4ee9a56c5ebd994439dde59690defb2ff29b90265d6d36d0618bc6d", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "2fce99fb6ddfc7511bafa4daccbfcef89030ca0842ef8697896b88daab80d6e5", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "5fcc31b4524b9342660062ce754cf213429cd4b8d97d33ec7de97a68ec67f2d5", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "ad12ad95817997ab9116b0df54465458e5e4a424f9c739788fe1efc1384544a5", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "7e1a0e678f9e6286db92017e20464544b55fd9d4410daf763c434d1c2c7be2f7", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "faa7b1211811696b0d93c34fed72abffda88c371bfc3ddcefe688976dc37564f", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "19c49ee59b0e387e5d5122e7fba72aed1791d6f48ac4367e1c7d6dbd3221f0b7", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "5bec1cb31002d0ede092521efe1cb199b77298e44df5cc0d11375603698a6715", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "2b255d5ac718293a5f18ef88f14e6015adbd2237de5e82cda0e3e300f2ab0492", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "8e1c88dfc34d8754735541afff9c30c03cd56f217750fba8a29120b6f2d56c04", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "cda38f48d3cc9afc77ae707dd07af35f5c40e4f7faa09711aab9bbcec9db8d50", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "26af7275d4c5cb63cf9c033d3946b632754b364d7bd723178dc885c82b3d2892", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "86fd34bb1805cd48447c26b75169214dd2103ff89145fcd6775ffc139ffa6ca8", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "ba1f7863841de7f1be23d7b776db618d461601c0d44a84a0306b20269700acf1", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "af930566a676af99f170d0ec98c9f142bfa11b8c19303b91f5c36bde89a49ed5", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "01e89f66f1e9263f7bb0de037a2bddd53d57d94930180934d72373402f31d268", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "aa2c7b24e23c85931cd8d14a714da684ee150372748bc4936b63c736d544419d", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "83a60603152a09be6e7f2183343e09e64df8ff9c62674c9c65f273ea1a776e94", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "32a5ace1731c2eef8e1bbfa52628ba35166637a7742c4f35d794f9e4270104ec", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "75f405e428bcbef73ed3554dc7f1e76b782fb3e176cd1be3e6963d8a0c2df8af", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "989188296dbf193f18462ef9c95eebb98d77b2447bfe9ff579af7e56f11ba9c9", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "5831d11fbca7400378b3c0780b8b198be5c9c58eed02da6ce84e60ae861776d9", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "6c39e8dd78827219349d84d3e47f7581c15d7a7f93ac7dcedaa01b2e13749812", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "86f03b3312ec54fd366c369272cacdc7504bda74db917f712cb84f466125aced", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "3113a4e961d5bbc309a2fc24f9327385329ca19149b400e128b74a49ef7390a4", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "60905a3c034f8d24e5b0429f5a4acba01e75826728634d77c7d93524f09e9214", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "20ebdc7341ead4a9a33efc0e2a00210cdd3bc617382595c703292425ef1ce3fc", "target_hash": "4d23ee11aed36ebc853be8d80add838e69553bb6e6e648443e51f185e80408eb", - "section_hashes": [ - "ac2c71a9252f", - "fb07159a08ea", - "f192abc568f1", - "9884d9bf4c92", - "c12fa3b94801", - "94127a320742", - "12302c319a9e", - "9f275c463e50", - "3f2dd32f3494", - "2850c6c2b018", - "7466a8efcac6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -57656,1190 +19781,332 @@ "pl": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "3ce9640e226c1c61d445eae7c3148533d50c26298a0195b96ab88a0ad203cc11", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "a65fb536fbdca32f037348fc966b2f9363f5cffdb53fdd67f34c8fae6118f4a4", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "3c3dce3e0748b0130695bf5940ab384077e3b67aedea11e2d61b987d9cfd7de6", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "300809b5e3dfba4146bc8f67f36f849e6c454100680192aa3d501a55f2604e3d", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "a290d2d9fee27cf87a95b4e0157103f19fee491475e34372a4a7960c4dfdac98", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "7ff662f8710b9a62e1deedee8063c965a83b1958a4b927b4540e3a9435ae1462", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "d74ddb6ece456a33a1b44a81ed1aff0110ba150503327d3d881e7964533484fe", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "664c4aa17b2a62b81e705e87f5fd064113cf50f9844ea827c20635cd1a1e8060", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "4cb1591bab8f09f80b8066b60177795495bdea0dbc1c373e0a7ed58f190dc9d8", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "1a62768eeaa05cca57bf0f47ee965fe1805884c9258f3d5897618ab2287028ab", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "6cfae4e2ab26e258fe273bd101d278a4cad58072594fb8bcc344de3ec04da51f", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "5d67ce9453a6a1a2a4c406fc0ff3be921e16ef2c7dfed94c26b66b336ca04ae1", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "801e322b3bd16ff9c4449976a92d683dea7ad3e47df9b9c7ce6ae27a967ba8c5", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "7de50222af074508521e6dc6ffce812af94dc615648b28c04ed370ca3004b0a9", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "efbd45bb09665529008b6a0b688950154326b0c3bd7e01d3dd0ce5b7487630b7", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "d6d84d2f0387e1f3f0912e20153b4db72503e8cbd1d68d3bce5efd42b975b9ca", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "a7e5084724e32a8eaaf00dcd4bcc35b19d26926f14ea5ee712f99750f07dbf76", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "63e332cc555564387b1e6c5651829179a00eac6cee60bbca172d94148aa11d2d", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "7448e5022bcb7a2df3f328e5f4361ad23592a3dfef182239f65b1ac9d4ed53b5", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "1cd890b76a22795cdbf7a6c2d829e1e2fd5d488b2c3be93b856e78baef769188", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "88a3a1b6e2f863ea3e0c4fc2420ec07f0fa163b53dab0c3ff02c9296b2e0c744", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "40286f4c03132143606f031db818c99d82b3b5632ce4786561d15c406f6bdad2", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "11b22fcbbc682d30d8ba4e68a4cb0921241284f0583f47b0b0f304cebf50ec06", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "e6ec8d8a0f69d75e1f83390e9c78f71e11321a85d34ced671ed7912d8014bfb8", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "ed5a698d63d55f9cb808e72c140433a6d79fb10c1e229030d5baa3c498ab91de", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "bf70dd69074699dff0e4e5879727957bae154f15a7ff459e51d0d8e5f81b7659", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "b88de419d6063e5847a9a6fda0830541a71341cdb5166c2a4dc30bb06fe193ee", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "fb71a80795aaebca9f74a626adebba0209daaed7d9e4346357ae55e8f2f6c27c", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "e06f9a9f5d0db94641bde3dba4d55b93f7d4cb021590152f76d4fb49ca0afe36", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "169e89c24d2dc6d087a362f3cb6ea8530bbacb5d6691be484107ecefc37267f7", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "c9099f4db07ee9aabb93edd9268272ce44fa5125de1cb56b50ea69596c3d8b9c", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "93dd0bae83d7eec1fa1bd3886dca48057c508f90b0ecd83a9e0ec58f77c56216", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "4b7c8df6e18bfcf465caea6bd919718c2dd93d7db6c604212e09d9000770f424", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "b9144144e23fb50a08bd1324266e631d1a81142edae90d676fbf46fe080024c1", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "a84951a0dfea84e7e91d6b462ff3312ccebb406bf4f6f636056b3bdf3a947881", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "b28957b05152bd9761a11826d1ef912947e6eb1ebd486b5dbd9f325f6351316c", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "fd0423eb0852cb6c399e5793fbb8ab1171579b85f40e22ade4645204156cd083", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "eba06ec49451d60e55c4ec7fffa14ccc2c15b0f2c8d7c4d18ff349a9bac0e407", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "0a83d6901c62fd4b2d8c74b0ac4db1342333264571688804ca3298a68e2ef7a5", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "8c56c39ec2fe2d2c87746ae02c2bb80b6b63439367dee055003f3dbfa8026b1c", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "a38fea8e82c6a0b0ae2bed54938345c05097bcd1a9da21c2d29333037411b40c", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "58e3524d992bb84d7bd20cd9ef1511a8f1909541fd5362e78596baa8075d31e9", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "67858f92e42a1c50a78d7bb3c363090bc041e32261105dd05dd4ead88941715c", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "972dd4c90c19853c8e5f383b78bf4337d44bc46a43adc0812fdd2b7e8d03bb88", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "005411b9c3430679292ff6b908c8e6fe27f43cea7bd51cf3c80317afe90904aa", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "3b315fab6a8f8d1cc916438703ac7435fe7b7f3742d8ba73204d9fde8a809267", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "78226b427599d5b8f8091bc02b025c89f90a8e327a1d3fbb90274474e02c5cad", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "7f4ae7c36003f0156ba622a5eb4108facbf87025ce45cb0c74f9a801b172c4eb", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "9959d84471ff517100157a0ebb0bf2f385d03777383d5aa2bb9fdc0979bfe9a6", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "14718f12bd4436abd958872b51e6c0d22ddaa3dc16ec659cd3e3b9110bd22485", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "e63ca90c12d8693f22db2d60d039d0c1b8b30a0079ca5c0c13917f3e8d9a079a", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "f62c57be224186f88ccf1138c89ef9c1d88171f937b317f8636bbf508eabc8b3", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "67c5d67dab9b7e814af04b9682be568e5e636eb15cfe41939b6fab84cffcb1ca", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "39ab29d19b1e331d78c3f23972caf90f4092b3983dd625d110abc529f4f0c615", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "01132fe0ec7935632c17b633fb7b252bde559653c43b864e49b717fbb0bafd86", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "7313c2f863b1236c96dfa1bf934142a9a4c048ef532235d822f57e022cc9c88e", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "cbce57bbfef6f12bbb33d451a7592664cd9a8c5e40922ce7aa5ade19885062ec", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "9a21c141292b94023db5a12f276b2f404ecacae12958ca155db9ffc9106e2bd5", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "ccfbf16fd455d565c44a8414e5dd9fdef4f85b851ddc982c5860b4ef58f22a86", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "961ab62b6102d19722c8e0a8b2231f2a530c1bae014c7b2e6bce015a004b25db", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "11b2c4bba8ab4ef9892cd76788973aa590e7243bde2d8726e8f8c24c70f90c11", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "6ccebbb63c78c195909bef3960801d66d04931aacf4c61c769a5ea35f264bdac", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "a0d5e5d04d655b5c8c69fa9f698f1c2988b26abee434aca09d9cc4f25a68ba1c", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "2a47ea15e4d2915ee1c4c629db47ac8109ad024c9049e0fc028052d6bdd9d31d", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "67a471099b9bd402b3fdb258799a69dd6a157daf833dfef1908e53b00d3ee0b0", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "113d282921cf4bb189d7d444727e0a752aedd39c9574cd7cd48cffe4b1563296", "target_hash": "ec75647c10befc46806648afcbbd9c9a9ca08cb917478b674ef313240707d893", - "section_hashes": [ - "c9fa0b526940", - "78aefa8e52d8", - "b59f18457b19", - "9d870974dbac", - "b8b7a5c5b541", - "5c0871b7742a", - "1160ba87a069", - "a9f979f22a00", - "f4e7da1b98e4", - "a3d8d77bacf5", - "6b98b1d33d63" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -58849,1124 +20116,332 @@ "pl": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "ea10e5b12b5e0d1d8b703e5b9813b9ef95604f554d5edfe4aaa34504f60df97d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "3021221fff44a1a5685f03599538c1bd4381245ca74bdffc19f0f11c7e54a6a8", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "6ec2ff4e4f878cba613b77f13b4615cfabc0643a7a6f017a4004aeb64fa8d0e0", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "995464d60183080a69b1fe87f974b1cf99a678ed8c8a28d2c61538b1eba6bb88", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "08150fa43de85d528978c6665906153991eb7b68450d5013cbaa109702e7b63f", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "fc4e18a42054ffed94f7b16d12de672dff5f96f6e37f3c20b843d9917bc28ab7", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "8dfe7ba88926ef7a7cd3ecc9432f755dcf4422eec1bfdb78d768ba9af0b4d799", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "bae431b6ff18faa49ca4d5c58f1e081299ab46fa13841c641f32b4fdbc98eebc", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "6d3f06dbd986410705e86568cfdb673dcf1c530cf7916082b41980b951cc701f", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "d25c7e07bcf221eccd0e2ffe24719d5c42b18a7716d0812b0783d8203ceecfc3", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "833ea87301e6973e1e741d0105c1e08268c15e4ca6a2d3f73ba583dd2e007a0d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "9f801e7d587283aa425a3ed4c2f06d86c4c51d8ee1c55b64eaac08a36f2975b8", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "f65450e64bfd2795aedbd77e240a728a5328889f731b6a522a8ec78ffe2a72ae", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "6543d561ffe494425b210c285332828fdbc70db2e5a6ed527c909a28a0946c05", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "6bda22eb39702f72fdf883c99668cfa10aca51b29c23c904dc8416901b59b528", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "c328f9a82a74c90b7bddc0be4dbb636f1d19e10e9c52daefcb72d5c7bbf1d67f", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "b3182cd0c482f0f86f04b09948fd8b68950cf7b1cc2ffb108d4870bd14cd091e", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "54e650e06d4763ecec0a2a1ba26eba0e51b66e502bfcef6b450436c81f7fee0d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "a0db5a543be5f3e5030b5ff317aa16b803074d02ba7a081029c1c2fadcb4d7ad", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "e98db88245e90fa4e70106c91364995f51bae7e85a471c74e03fe358122b00f0", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "c0067017f61e35e44448478e89abb7c349e18234d3999ecaeec5fe14dff5b950", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "e6b5d1602abe114794cd52d1765fa24c8072c6c0a0123ab10fde1d34cf4793bd", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "13800b3a33932450cd1165db25d69e20009c12fd6f767017b3d435bb0e289b1b", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "c25bc1a0267738a82d9fa266b8bce78f4c76cbe180d17bf4358c7478a850e1ea", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "9262ac1854e6fd53c249174785d68637df4ac52b5816129c64f1090364c6c4d7", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "f488e861f01e2e2681e1cf566c04d1dbb432d8b5d22817970251db2cd2ca9d5f", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "d821e4ce21e8191a726c77b0c51ea9080930ae81c5dfa17cfb71df96c252080d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "70abe0f5be51235f08f0ac5d9c4c22acef2a2d0b5b72f3a05b398ff73bbe1918", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "f491425f88308820dd0e1e877afab22e2f83a98382fa9d20ceb73b7407f68849", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "974dd805fc8be914041cdbd8ef7d1a483e7c4010ee1887dace75229177bccb64", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "93125eb0c6a80ae5660c9bc3a73d992635307e7cf2e80abdf7749b09fe08ce45", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "ed116bc55dc8c1279d170269d949ff3390f6152aecf9517bf48a8b8fe91e1817", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "96f6b36df16684b8f4a1fd6ebeb827e56bc073019b0e24afdbbc80937fd49c4f", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "2bc6084689cbb62087001878563eb675893b76f90c79668b6a493109fc5386ff", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "f338849a64137b1ace124e235d21c61d5f10a5a5cf90e0b14cf4ad16929b024e", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "3895bca260285223414b5bb0a98f3995b19a7a742d6b025ebe871de3faacd316", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "bb2a5f469be777373b5e61cdd048f729bc9e9a77ac6ef6b432960b47459e39ad", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "4cf329b2b3cbeb1a20b223d8cd439926deb9efe8841ae6daa83784c24f10952a", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "303e563f2dda66c80279a0874c66af58710d502b2d591d57e0a3b0c5ddab72f9", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "1b055e20bea596170d7ca349b5909edb823f8159fbcdc6d3d7c9401870f81f7d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "15840fe6a5754b13af092a12cbd6a93ed8b2763d3a6ad6fb3fc269a011ae7301", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "67c591fb916967f4d27e7145fdff1109983db5a503d6d92696784db0d67cbd23", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "679822f9e107da7e262052e13acd89669ebbfc84e31c499e531e29be19e9bdb1", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "182b4f722d05c38fd19e64214a00b5dd5fa487451122be556520dbfcfd1e4442", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "f5e7184e7f2e2726ddec615c6e19b89c2553a78be38b6b5db3931f821f1815ca", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "feab4451ca71e99adba12b9152dc2318cf1a2c2a26275f01b4b99a15b1302f36", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "7b8168620f735c08e3ec12b7e8660b9606d729022cfc57dd826edad307c1c491", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "0c338603bbddf22ca13c3a6747d55302b6ecf8c6efd67550e58df677fae0a37a", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "9609dfb260ee3914944259957785a17d8df8980aff7818ed0739e3fb06164df9", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "891900d0eb68882f504f9c8e728072f917240fe3b7791ed7b0c5f2c48715e864", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "06821dd683d1b0902d0b45c2fb8de1fe51ee0e816041d8ad992a6445259be0c7", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "b44321ca14ef8bc50a3afb6913e311868f366d37d553058d6d0ac367caf21590", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "5214bc680e8d6c7ebc6b67e6965fc67459299f56027110264c55e209d9df03f6", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "3d41da92d7024acf0a66dd33232870fc7e6b0475e960954a2ab1cd84681d6f75", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "fe2279496ed445e83353c1f2183a936e19744268dafeea77c17aa715e368e6d2", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "d269a440788c0c2c26364d09aabba8305d10d3818cf07445f74e62a150299b35", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "0ce42c95dc8447e3aeac8bf0251912d0e3abe51e2516ebdf05afbb7083021acd", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "2ac0e57b56aacc4c6662ac2023032c1a95b2c8ff28ec13cfced46e830c2e86c4", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "f8bdc471c96d24db184747173bcd57e0835d3ebc33791105dc4836783bffa3d2", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "01ac96cc55d5d4f5f4f0f11b6a551980d340e15403ee5f78d90692cd9805531d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "69e0ec325643f80403466c441659e01e876f2c36de8b931b7f555d27560f30c6", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "a0d7ca1d9bc7eb712fd8b76ea78d9ce0162def37f4183a4ecb055f7d941e064d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "d6ecb1c4f10d505dfc8a35b87a32ff9f30c6f6eac4d211ff4a00313e45e94a54", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "cbfe840f5553b73da791b12a9f02e7c273a971f549724b85018ac619b8eaa1a6", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "2d2d636775f969a2641e5f8593d90ba238ec2519875519dd87ac114e13652b2b", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "f60be16d7b8a96d812a8286b0547323d57585c1dd8666ffd922dc3da967e7ca0", "target_hash": "612a057600d084207ed093e576c482e2281c99cf9016e6973597c8798f36868d", - "section_hashes": [ - "6731c82dc06c", - "dc8ef357bfa4", - "ec9b1cb45eea", - "f5bdf33a0046", - "5dc7c80068dd", - "0498a470afc2", - "2a47c5718ca5", - "cfcc80247efb", - "485d9591da7f", - "e1a7bb007ec2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -59976,1190 +20451,332 @@ "pl": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "e87069e96c2db59c4c82d26310b1954c41d2bfda1027b445a77cc42062f0af7a", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "b5aa68d912e4679910914f9beee6a6343fe55391cdb68644a25ff05f9bc5186d", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "60e5e6fdb1aa77373775dfad67130615ff19f54e7da45e4e60c4c8383ccca557", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "64da2e55d3eedf17878657307dd4be96818d8fb89a04ee8979411fa93645aec1", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "525da11d5e817255bfae70b39981917a1a8d85123f090a9372c14c5c47fc63d4", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "6c9badad402419a0c0c47b5b4ef03995066039910d0ca4e552ace3a06fd20f26", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "dd8768b58a3e09f2b1caea5932a99285bdb5f03fb0fe0da5d1ece3f8c4a2b81c", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "07bda54343f5f7b2727d52538ef0528bee29fdb513c0878f702a448e99d3ed91", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "7b77d03cacafaf20cd56f985d3f97daa6b9b9135b75d37eeaefd27da270b777e", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "f66e76c29f68e5b80880f5be21c41176fa42db5016df3b514891fa7ced4084a3", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "9855f1bfdad82c6282ff499633f351489d4351f92363ce79052685691af2a1e6", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "b8414307ca2319db962fe174fdc1ec495ecf05249f196619c54b68fd14a5048b", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "6a6d9b37506db39e908c81db133fcdd9da73501f1c6fbc8f69155bb47cfec766", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "a63d509d31d6b65d9482056a8f3604231e54ffe4cf69efd1e7e38d0e20fdb037", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "d7e9dde37eb5ecd7ef33bdab46ce3d8ada2bd27f9133cab71d7ec031517f2516", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "18dde6f6f542c2d67574116bb8fdb1723da5f43546e0f7e646dab426594f4b89", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "6d935d46849a52b301bebf59aab28659fa68e5e03e12506b95c2ef208598d101", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "2f9a6f06c75d44f7b6e40ba255dae3ddaa00953d8318d200c181b8ed5230a7a1", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "401515f85205efd898bed0fa366942188d70573ef098d43f82c15e4f0d8a81e0", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "5d79b2f995184ca7b2bb3e60ae792538e5ce18fdc20d8fca16d0b364bec712c5", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "c9f3120ee4a3454f2f3c4d2cd2ad6786a54e890d99e65f76a5f8d683b2414877", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "7810b65f47f6a436a1a606885ec1621096dc622ffa2ffc0c056baab897b40a2b", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "7f92633caebbeb5503b00d54acc0ec53d6e0cd24826ad8ce5af96903099c0e3b", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "1d575547ebfea425e38e76d84eeb804b81887bf6104823c2706caee98d7a373f", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "5c220ba9338906f2f11b8570c93fcb265682ad45ead21d48716cf0330520733b", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "5c938d50a36e64753ae297f5a5889782b24e8554a8e748d77a5c920997bbe1b2", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "3e15242b4dabc04a7deb587017d3384d818a9db9e015b83772a3f2ab362584fa", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "00387d71921300fb0417ef220e7a5c3b301058a9ddb3d9c1115d9074d990fd8a", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "7e48eafb0d5c2db29877f88b26905575142ff8bdadba2f2bf2aecc0ba1ce7a4a", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "7c609972e17adb4b7234216b378250151df941e9a50d2cd4d36dba3b53d92549", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "15e77b65688822f39bd676ff6785d96ce8126c996f5bee5e45bc2dd3c8331a1d", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "98a20b2cac37750f1cb4aaf5c0c2b13186544621fdb00e892a86c0609d42174d", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "5e51cc7cd1c3ad9672bd23c6184e45fd4c60000da6f430d1ea51ea172e66e603", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "7de622e14cf2487d7d96f7da176a5e0b0727508cd4d4db5b022ab2e907329ad7", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "75bed9f27f2366c4412f0c334c2d162140a10ddee966e6e9adc532aebd811511", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "3e93f4f82f9d34772cd4801135aa78cc1fa3f36794022a7823fcb2210082c12c", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "2fb35fa11cdd7430b881da3bc300559bcd12807a397fb9db56fa12f351a5f7fd", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "6cf1ae6283f25f92c77792b0f6c43350d1370e504738e3edd1e029077c6ee94a", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "74659e41e15b4e041a495921a047fd89d4315457f8ad757501cbf71f7219ee68", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "ba46a6e317142f88f796ae675119640bb3778034fbfc4ceabd1dfe90a1701e38", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "126b9ef40fd16c4fe0d60e8d0b0b4176fd6d7dc1003dca060490fc42dce4cbaf", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "0817606e352b2cb097ffd38ab6e26c9b97527e9aa798a26d9ed22af53d1a2576", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "b78204e32621ba17456cfb2c6e1459469fde5c387c0864b6c1f4c80b188f35ec", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "2a62b130ffaff0950b0fb209272b1c7a185ff3f895069ad290988b500ffee36f", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "d0a04ac9e671795b104693b876c56cd2e6ae645a1d2ad844b560a5253b2be589", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "fcba3d0762a77ae11b1e34113c85c12d4f17f00974df6e13c493580209c71ee6", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "b85bcad92f771dfc570e3ebc33bb6e00027d7aa96c986d3f1d2df86c41bb0c55", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "c9d3d7e6973da43969c9a025173294f0ca2f5b08ea028c36a08c327f0e0ada40", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "1609d1798bf88065ab92b79b1d7a31b5acd2ad2d643319b0e39f3c63fe71171a", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "921dfd3551f5b3125504f3737876dca8d871422757b70f7e1c217ebeb2eb0e32", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "f46a4fe37aef41a5ef309a0fec2fbced64a8f4eb3dad5393e2e3eb53de6ee94d", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "e8cfaf25779aa9f93bcaabae3b46d05ec20748b93e7734d8320c627bdc30ed55", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "1458342b941a78180def72edde80c41d72430ffd7ebfd5c494e7dea1a2deea81", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "642f3c1ae78179217ad089d92033472e707e81d2a3eb758986bd84157df0ef35", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "b1948aede99217b43bd618740fd505aaa5c683da27d97a12015b7e23e1219688", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "ba9396d6d4573498fe0d7409af40ce48b8989b034ad64fb6ecadaf6b9eb4407c", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "f8824ddcb2449067cc4719b74694d78dae52226742d01f6357e0c345d484c935", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "c3ea3c4de985d9385d51d5422a19fc2a447da3af9c1c16238457a1fce4c68cec", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "20d72d072b08335c52903305a14076ee3f4d60115883f4d10aa6e7a01f657f6d", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "7f52c2fcb832736dbfb1385cc0cefe1c828e3d7c27005f35758d353e8f1d45ab", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "4c0d5123dc8b7c61611fea6593e79664f578e112591b1ab1c469e648ca6dbbe1", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "f00ff37fd40ae03aa10b0ebebd3b2ae586a36cc5fb9220693b1cd5e20e821806", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "f47d02b4a3af8e49b01077082bb5c0d0010d45b143984480f27cb8844464592c", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "bd1df1c23c1dd90353ffe7205b9b17f9747c2d4a2568da8ab2a3013a0b5e7e12", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "13d99749d9b8d1bdc8088663e83ab863ab07398c3aaad5706e314b80e433ddc0", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "df59bd25a1c05b789bb269b9fc273ea3a183431eadd72b13f051df617fdd7a02", "target_hash": "769d590f0b4bbac6c1d77ad4ff4da723871d010fe08fd8d952c4c852ad4f3af8", - "section_hashes": [ - "a12b33bd234c", - "0bfeb308e98c", - "07679e0da61c", - "6d93f3b3ee9d", - "d7088cdc6b5e", - "1dc6b6f5f11a", - "9b95d86a9b7c", - "30dbc06d9b3c", - "d8365cbce502", - "caac010b6088", - "1247a72b9a47" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -61169,1058 +20786,332 @@ "pl": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "90841cfcda8ba835e7cf049bb0096a5949d4d6360b9003d9b08d1be84a7658ed", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "8b8c4d1e807d2536e1a6af93620fefabd90bb3d022d822b4382b8e4e9365a89b", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "8c8ca837c7ba5a745cba99e6a3738b8702fb4e3268477f8f11476037a0ad8eca", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "6f1cdee77eeea8d106b0b551349ba0fa17ae73d1881ab0d352f0d8706ebc7eea", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "17a84989d3fc7a3e6a8b0cc3fc2a5fb22bf446f0837fd48ef53eb9c24379a835", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "4c091ef6c3c3e5fcd898e229cf03a07ed7012f350ab5aaf5463da7485071bae1", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "a1a6a3390412288cb965fd06c0bed9393cc090b412d04c6af346356eb896dc72", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "89e3118a54abfcf6235809c350a7e4960ebb1ba832d24aaaee280ab8989cd2ae", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "f7e5819ab8b973100640cc50a309a4c6dbc639f8759d96e5382af3977049cc1a", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "25cd11610aff020db67edc22292ecedd5afef8afa9ba56d1f1f491783aa177a9", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b7ab66f7eeb789d332cbc2c45049dd69305c07b48cbf1b9aa869db1cff7e2340", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "c280ab5bd3ccfeeb1f39522979a999ed1e22dff00949689766b02cc103de1269", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "1a57fc7a316c309f21fdf0a17aaaea8f747c715ab03768c10e3ae12e37e5f540", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "d4695619e1f49744a83a03bd9014a3e36108aa58c18cbdc815d5e605483a3b8a", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "38c8a938669039a54dbfc3bf9c68c23b4169a1232d03c3dddce24a73220cec94", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "88a9b8e14fbb5d198c02bcfe40da5964106442cceb7da0151dcac0e54f3a4996", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "6fb2724315c51f2111235e6be9df0b0d1864d2419e0c67d453d8497ff17ab884", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "f3cae4d18d5af895bbd90bed0056b798ca422c4e0af1b5f47b37527404cdc1e0", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "15726558c85659df43d63f527c2fbaf64311b800d4c810f8f5b763133fe07357", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "666c4166338ad299c5b1001b4af3a60c939fe1f95dd389fff32c32e8ce900a13", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "eb20a43e915a35ea5e71f0a95f10d8fa945097393de2336db92d83008d66ff89", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "32356d1ac07db2ba5bdfa0c809215374d9c68d93e4422f71cfec0a26506b2312", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b472447d4a09d0d82ba4cda22e512eabee0fa6bb1d6345d641d0045ded507ce0", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "f074f4749116869831c230a67172c0655208a3263beb9300384c1fc43e0b758a", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "08c2fa63d93bb0c96a6cfd431f4062d996a25528f46115daf4150a5b2cde471d", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "78edd41542b0854effdd50db7cbda155e741b202ab9ea94e72655a30f77e3037", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "387f0ebdc9aabc85da99011da95482cd6f96c66a710e08332b0fa0aa5ae15c5a", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "59c9ef5eb58909c3ebe6387c024517a7dbb893b9a999c69d143e33155bb7707e", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "2dd7658264470bfd624e14145a0d6914d14b4f77071756f40d8f2eae0765663d", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "e774b2b585de939e9ef7b5b7391fcc95d49e8c9260ceb92cf3c49bf661c5ffbd", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "14447e2a81ba897dff04e4d9ec89ed527a221ab1ec8b56644852e1448aa10db7", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "7c909f0d5165359bf39a28acee929d21ebaaf1ee8df3659303e4c6ace2ba3009", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "45d10eb7fb291715bc4fa98729c73cda29bce62ab50faad3edf4172036d945b5", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "80b1944009d9d178fab7835a02b8bc70cab48e29dacd2ddf7253128312c675a8", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "ba6c78884ccc1a77933c11c2c21ff0735072333955a5920283d671be3f384cf3", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "91e253073c458e385181fd7ac196c6b33fa4610a5a9343429d16941e85364037", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "933c14e922257b737844765622a15bb90c4dab7fc409c3f43041bdd242a308b1", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "502f05223e8cb6877ee45399b728d94ba54189d59c49de1984b32a166fcdd527", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b7ab29450e44d1dd1ae238f52c11bd11f0b61a874d0d33d28f32fe7e9cb258a4", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "de36e7f0dc88b7d50562f4c52b8b729adbc8e334df6243c3dc1781ccd9164cf8", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "601298aae6bf060a70bda3cead86377c2e63e3082e9c4467ce43a5b3ea6c048d", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "9f3fc329a491f775c50bcfbdde68b4bd82e82f97b371ba2de7129b12dc5f7128", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "c13a9ac6d0ea469076fceaeec6a5ee4aba42677c04d4fb811c79bab5d2ea0d71", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "ef784dd795d1a2ba7f0063015bbdd4baf3ec770e98bfb1111f68680ab626464e", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "5361b3de11042456875f1c4ec4234fec3d643aaaa382bd7408a7cd33810635ef", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "80be9ba62f9447077979791e27273bceb3664bc7b9b3ae613095b264368763bd", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "e79cd05cc14b05b38c14e0a82078c9ef0d0a148f1d360562f85e905fba636c89", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b601c907db4030a27b67a126dc03ef3c31a228ac5e41b02b50c1c69fefa9acd6", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "506c1aaea78d87e7ef4c5d3a6de044abbf3a24b01659a84ba6f93647926fd6bd", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "7e8b8bb40ae07712b5788a317d587884ee2c2ad4c0d37561a2e473066e7e66ca", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "4807c4002d0f104364c44d34edc3710bc0847b2e8e37c986bf88995da1bdb878", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "f4545fe26c9d3e2eab32a3c8ff7b26dc3abe9aeaebe0fcdd74af2a8676d51861", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "07cfb910efe36e88cbeaccb969c76444ea9eddc4dbbc28832242e4188070281f", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "d6b17e056acc57e2da231705b39488ecb48a90b3634dad998abfd7e5600ec139", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "3eb3587ebc0faffae0202a6cd28b3fe7f04d6b14bbf61d8ebc3cdff074b1f315", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "2b43b628078464ad558058695763ea44ca5e01297f23c8c4578eeef4e0a85819", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b0038d94f8231f09475cb7d062ad2f7f0757e0f74585519c215cf5e87bb7a92a", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "3e9b085c873cb0a0261f4e338de751f86fdfbd3dc915a2cd1c2f8d0cf33ff74c", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b15687a8c3689ee011b94da3107d2df2d8889df7a11875e7923ca125caec0377", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "ec2d2f3ecfe56313b5bdc265ca83cbce7e2975f5fa34b3085157ca6ee186093f", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b68445044e3f940f9e032a23150ff5a92059078ec835ef56dd6ffde86d2411c7", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "9c9bfac12cca0297af2dce9cceab2f4e0352f9ba3ee6932e17ff339b21b051a4", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "8c967f701cc91f5f5b434c9b77116d96ceb0fd1b4ee84d41d9a5469fc304cd10", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "a1e80ad8b2bf4c19afa0f5934c4e11e1a00d29ef63990ffeb3fe38d0e2ec4354", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "b7bcb61c959cb06f3714902d69fa1e1a56762f7b4e9c75e8d498ed1857896603", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "ee65b3f0ec8185f56eb4a73c32d4fd54330ae340430fdb7783790c73b8937492", "target_hash": "69d87683838663ea109de9fb911d36850e5d780860f6401f451f8b8223b8af18", - "section_hashes": [ - "b5c5133871df", - "0cf13963d8c6", - "d8e764ac6492", - "a873df269934", - "dea0690f5551", - "c4090193a0c8", - "6e74bb58bf03", - "477a489925d4", - "21bab737a900" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -62230,1058 +21121,332 @@ "pl": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "3f2af1d2d8967a9a0981613408cc300c77cd84240c2f59d6f64d5431ee785ea2", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "c9fe87d562851f1380394773072a12e0993358707ececfd6644e5256c0cd1a70", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "5db4f54cb4ab946da261a03c39e4ce26033486d7111775950fcfefa7e643b0bf", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "7feda8fa8066522705ed3f1613c5efa8705b22c5cc0201afab97942bf84b19c1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "40b3a0f7f0d5f85e9f03e018f33a573064f3748cc20b440471198bcea7117ec7", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "0a9705c839c83698e4157a8767c13a3b67ea0a17ded8a2e207e588c40f51b764", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "c1e59cca6a778219a8459798e4dfe085e61dc00b67c2e1c93c087474ea32fd3d", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "894993be713e86d80695e2d0c71847291189164d4370843215f3155a30e4b614", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "146d315ce45361917f93ed6ba1e2ca28603b9c5ff1d66e71d6ea830d0c97b1e1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "9d6b4aa60b5a4f5e3203359045e02dd8c49b9f1ffdc08fed54078da7d55eeda8", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "b04478f59ded5f6466d0c5b24126122b1dbad44695983ef4f2feff3439f1e6a1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "bbaf76ab54d841699eef95b7b6611392b80e0d4cc98fa10967321941c2e4bac9", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "cf48f5d31a71b202bcecdc44f962451836a2dbb837196993e68edcdfdcec11db", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "4fb7d5d97a95bd301ba4472a358bf8e4a920869f04f2a931210faba0cae3a52b", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "6f01ff24a3103e33353b6382e831858342ab6cd0eb9c6494e8653f4eae0d2071", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "3e7aca57647d610081b46b3efbd6702c3d590b0fc6e0cfb0d44a90fdc9561fc5", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "57e4412d171a3731c461b226e9e85130080f30729cf895bd6e0aeb21abac8a0c", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "13c2151726e6bc46663e5c397dc886af04f55d39053ea4fe5cea79d17fb06f76", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "a2a5fa5ada6243ea8887a199b5bd9d679654466eb37b2978aada4a0bc136452a", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "9cca35aac67d0367aa29df9c56887220c5245ddc1f0e6b6ab3a61d6c83282bc4", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "653bb62da9a33db8925e140060f14ec91e1d50818368a5e2d2e604c3c8e13658", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "701e726dfb01e82640ef16f6e2f5c5f29576f864eaa05267f8a9d2043d05d5f1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "9e6e15dcab520d338f429afb3afc92582de6dae401bed24c62b3a100c0afeed1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "673af5aa7f5e30af060021e8406bf429134f3535e2c55923840b9981811610c0", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "d78065e475f26118b5ce4a3c6a58df3f774df95d0e22d854b551ffcdbb203b89", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "dc2901146927cb6322c712586fdfa0c891211d1989441f5ae12da74338c8a5a1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "2bdf0ba34699543787d7ffb85c4ce13293b76bb815f13d23e1cccdee6ccee331", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "274a189e848e41e551dedf3fe6298cb9f16d6492b9534c0663471e6dcf166430", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "123646e7bed3d1a0148b3fc0202b0b22cc55adf7b9af1cf2df27c2e98577dc73", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "0a58305eb4c5852eba0483d7932d428defd8297d8f3b9bb7e55a17aef5bf58ac", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "785338174d9093beb97d6fbbca91d6a5957500753689be8b3e8520540a880776", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "648bf9b4d97ca3ad9492318799a594bb618b5565e6625849d13ca4c3ae00e1c4", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "d9ed5e16c0dc46f7530a2674b9e0669807d850c5c8385898ab91d3202dda6363", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "2a3885822f4cd427e08bb8027fe8000e7d6ef579d314a5da248b1716354f2c6b", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "1299c7e6a45c76e5565956f3d62424fc1df1bdf502063fcbcfce63de976a5c5a", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "a764e18d2a8ce64b60c55e97ad1b4dd272989d7e6ac56db8ce7de1b769150c5f", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "b0ac5bd97f5cd3990e7552a625d1a0cc7f53fad7809d63a0a01bbb87cbaff17d", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "15aa80ad4a4708fff567330b585c3339c23682787166895ff14039f74abe3f45", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "00378de03582386e1c020024ef1e899132ca71177822b6f89a896ee64dde54e0", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "4c218274b544faa947511fc2e168c2a25664643bf3f35bf8b0d4dc4e37f3b7c0", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "695bd03733532083137cd36efa22f7a03144b40405aa14e0b5aa57e5ee550b18", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "9c509bcba70435e901e84d8af5ce9e3f56567b5b6e5b93e608475fe85f67598c", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "15ed51110487625fe6878c52f066a16fdebcfcda2d8364b8416874f15abcad3e", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "3ae00cf33196608d8ed048a249eb932e9aa3820b447a1573f40ac5d1b1e642d6", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "874540fe8539c5db9a5c7bb63de5dc7571f71c88c88841456e965a4f7c8abfc3", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "fe7403d65e871b7627ed23f1dc1edecb27a35ab7a4c91499e8097c5733a8bbee", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "d2f67d766443936ab57e4359ba85b8e1424930cee1a543c8adf92e35d90d79d1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "6306605980cc5c0668dc707761b79e36916ff3ed2a0226ed4d7714fdd23f3ab0", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "0fdac6be1b87c8a91ae719aada80522c94280ebfbfb07559f914de68b4c62035", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "18a3c6b5c774413dafca85b1d8878968cd6d5a5bdf127c0e7053b225a0bbab46", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "78678633950789d984aebabf7a5923683e3ed22e5cbd03682f7916dc67682561", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "9da788f0928fb19f63d03615fc4aff0cbed8b58c3dfc66f71ccf498cfcd6e779", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "e05b84e13ddff66518de1cb2ab50073c2ac0f6a90a0c569c2337662328ebad32", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "d03894f4d8049d031208d7165ecc25beea02d3327270605936bb001d65facfb2", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "4bf8ca8957799870c8d7b0316524337888d36d02224328d9e773a245cf41b192", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "c4797ae36f2d825606e24e6b2093ad372d6968303ec74d3e65f1a5e1089ee608", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "896d6de4afdc9c1d349761d6d35efa890798c3abaedc32ca937372a5fc3b18f4", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "e77b5cea3995e4c39a0d4bff25c5ddb5f03788933947012884b1b7cf7982ba5a", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "bddbac77864efb585a8f793f026b991561d05e139cb9b3105d076bc8ac278d4a", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "f5e4d62434fa27e0683ff980050482ef785e95b9f1f8547125eec30ed8ad8be4", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "bb65ca0cc7dd8ab2ae98f87061a422437cebf88f72125240c67bf933b59b23f1", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "4cb28383b6aa72f776e82d3aa783c8ad625f27ccb635a121e881bd0e2eaa3366", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "27f0acfcd77d26d6995c02724ada26dbf00b1a82183c22affb9e7b6c44000255", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "a8e402c1c37776910714f263de5c81ddcaf0106d0848019db0d4081619e1ffe6", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "1fb76d528a9501e4341577a34815f8da1c7c66922a9c39c9b697208797a7d830", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "a8345a135d8724a4f938bb19cb2bd4385a5f78744bda09b2819f0b4b640621b9", "target_hash": "5070fe5357d6b79400a07b9bb1c5d751a20a66312096e7849ea2c8872e4cd4c5", - "section_hashes": [ - "43598972c571", - "9dad51c31fa3", - "8e33892d2ed8", - "afe04c0f26ad", - "4fd336c44a1c", - "985038a87789", - "352bca450f4c", - "e19283a03c34", - "b25789910d2d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -63291,992 +21456,332 @@ "ar": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "1a50c40f2442e50ba11f2c956ae03a581528a6c90bcffca61b3c2233ab494bf4", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "d8e0ad8923854286b22d182042e4cbc36eec5439a5d6a7147e9ad24748ea9af7", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "c8e60d707ffe106048dc04acbc6363249817325e302d8a53b8a2760cf7f39d5d", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "0dfa09d3bbfbc2b7dbc6ee1dc067da837b93b39bcf2626948cc581b7d48f87ca", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "fc56c43d25520037789795fc3729b6fca87edad1264fc1a3a101928f6c6f753a", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "d8dad9c3862364327c2ccd707cde41469464fdd4b8a083a4795985aa0b8104c4", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "32efc6755324948a046e40267c02f496e51321175b0c07c9468adbf5b0a62f0a", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "48640b2c416aac3211db92807ad67b8f011f3f18fe9fea26da75b8638f18ba52", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "ea101988989d07a162f7139d79624e21a2363107c750d846abd58f3df70e1e56", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "5f8888b204591bf361555e85e9d30b71340ffa81a334d8a427d13998f217baf9", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "c5f3edfbc5c18fd6f2adee69bd553aeb064b296a253c5c1634d22bf1b8de8867", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "a163a3f53ee5230e23eaf9ba8b416586665f7aeae00b103cc1a47068bbd084b3", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "7785c59b5f88b46dc3ca0c8d45e03eee63cd03f57761cffaa57b29ca49efd6fc", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "84674450e68c1847e0cb422908d1443bfb481c9e26033e8b5a92071cc78fb0d2", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "94fb92c32870c8447d9815d521726bd64bc67e718c557f35d9afd6a60458ea4a", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "7b1f17eccc7a5b47f2cbdd4d8a43c19d69dde7ac56146f027e51d5e4544814ac", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "4520c5b93d35bdd6bee1e8b21e11997c418633da6ccc95ef0bd6d2c756b97107", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "a0c2143d4862730da32aafefbd905d90c4a4938a12fe17895d35589e1b70e150", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "4e4966d4e493cf66aedec5c9c5e2868dab04b14b384798dec79bae28b5f7e777", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "1953724cb7d9f57b2e4ec4aeef614aec45c3440d1cd661fc063b399b0bbc38af", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "f67ef3a89ea6879f960d4f5a202fc5701eb301d52a6d476931cff7b2af9a425d", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "9306c6a2601097431c68b104ab4929fd55cd40afa5d4dc21a3cc54fbbe7c20ba", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "6d880730e0ecfc8b8bd9e116ffebb786f60627fb8055bb744a1d5ca40db235d4", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "2f20090a1474450bbbc8a65872493acfd275fb9dd31764b742a75bf6c8674e34", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "b9cbd23634479b627a53600878734f00b2f67077c55504184e4803cec0b2e05a", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "faaa744ad1fc779fb024d49f49a05bdd839d992db8e92e6c8ea9eb7895b48832", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "35415b67bf28760a7a2d38b0feca8232a7106a71f99bccbb921f8b4d20e988bb", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "0e496be9825c9d53cd8e07ed4a36e7507de6da4a1b1107b603e5d170d278f6b6", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "65bf8a30c0c5cdf4d602aeca72a723b42d71536578d4f217adec14f142504d05", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "fe89f9943a5cf0325b9dfcbdceb414728ff6f28bce402b9f13f22fe4b60d23fc", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "38ef0d167410a9a4c20318bb54b80bc0954b4bb5b0140e9f2cccd714bbc7ff06", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "f6d8c8a83132547ac3fd99a55ed0c024853675bc2c6048a5fa31fb12325d2e61", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "965ef0d049f914dd2788def460dee9721d23c95ab7549c77f4fa368b3f76a483", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "6c6fb510ed985f154e6c0f0ff037c2a5e93e616e92053825b6964cf3e9fa199d", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "a533da19a25ad8a6a05527ffd69d3de04d8380408c92c953ad9403de9fe22096", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "92bf5ba7e02bc72d9ec5b2ee3d23f175e6e946b0d383910cbf57cfbc3bd837a2", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "56f210bca65b096fee2e33b6b88e53e395acdd300546aa5697ea2c4e935a8979", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "374d94a4fe3fa43cdf37ea5b1045e65ca99b2fcde1eea7b829b58aa16be8f904", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "05fefe972c22010deed9048c59850d0949490c1f7e04f8b8fb19f0b45e50c3a8", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "7477ccc30bb70747d4ebd43e372494aba7ccc337a453e17f4389ce9a3f3b5f50", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "c3444c950381e352528b016ae75c488afd2943d41af23c922d0ca678fcc3d1ec", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "6898e90ad1957513f41beab6e11c9e9c49a841d0df3467d9a084a6b6216c9f4d", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "6b2f8b7d27c06a374fe2eede2418e2036817942acd992fe846f8dd7881b41198", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "372f01584e80f7c3d2faef94e4bba7486840370b77d8eff929f7268b15320dea", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "71dfb3bd3ad94b4ce20c93037a439caf963c51c76ddaf990dd026175c10d6ace", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "cb478533dcc2b2bf9d4442bc2e8e5cc73e6cfac49381c984d60f591257a6461a", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "6e2e01950b947c54ec283b7fd17756cefb4cb5d751f5622bed0a9c9e1c5c9e91", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "8523be8d9d25148ac75d560cb1c456a2c07c6b6b44b7707051f4d98885a1fba9", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "53e5e95901b264f49ae370c0787e5c40d4207116ec192b6cd1a0bf2f41ebf089", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "dd658266dec2f111be118a2db3fe289d10819a20bc3c33f97b12869ae185c99b", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "c1e0b7330f0ad22ccbc0551c1a51dc316d1dc198b97a061e9658d0f7f0c10142", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "f65e1d8ddb327eeb1086562b7c13f63e1647c63561a951f1a11c3d0abb25c857", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "9ba69fe49627df45a6f40d51622c00d9cc704e7caa35c4028db46929529c970a", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "ad37d50b3e8ae6873e3dc0b74f1c7a5d62674ded925d6cc1b72bc928f661946b", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "3f88a7599a07727446ee15fceecea8415f8778aa662fe2468508f14f9d440374", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "30afed9eacf27e43895ca0f8f3c1a3e9947d0cc4f5d11f42828351db0081fbe0", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "8964006bf2e26e41916995bce4fe7d1cc5e33f9e9a8e36467371ad3f53628ece", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "30eb9e79192d6287ab182a37bcba3ba001c2dd92d16c768e66e0e2a72735445e", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "89bda2c4cec64a193dd3e7d0cd35eb258a79ef5dbbd402d1a08662d1e14e2c5a", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "7a0f5dc0997fb3b21738f3bb379987b49691c204db90cbf265ffb7abe6c39e2e", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "f49c8f598556560bb239e78efada65bc37afc8c7579fa666b703340f7335437e", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "a33860858230d2785d7faed3a7b1b9bb07235009e07a08d874de0dee5b998c75", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "844a8322b42bda7d5477c064b523bc2f5ce153ba67b109f59a63bdf4e704741e", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "a35dbfe4f009e469de9967c8148d22b924d6dc9aeadc5656c6942179e6b4fb72", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "f3d003bacf93fdcee890aff0f51b86d042bd25fd9b7cc62d3e939b07c66c006f", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "4fa3fcefc35c56293d0170cb92fea1bb55fda1cf41eab1b0ee7a26ced6bd96a4", "target_hash": "afae177eb46bd45bc0045bfbff580520b3001905a887248a553745e814d5315d", - "section_hashes": [ - "58807b9a33e2", - "f12f35725176", - "3f3989c5a8e5", - "a595e5bb0d20", - "82ee17ea48ad", - "0a63187449ea", - "c63e35271c39", - "e7a8683b963f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -64286,332 +21791,332 @@ "ar": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "32f8fc922814e38457ea11a10b9ece1040ac8c81cba2f67ca313eb4e46fdbbad", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "1cb97f9f40dd82a6953139f5b8224749899150c0796457c45832fd4d048a7254", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "532d31d27cae9d37c245b2eb945f9191217ede499303d641a9b39bfee83dea30", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "dd08d9695223dde4e6cc1ce13b087f95cb49b3edf4ba6873dfe80689351ef25e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "b5852898384c19298d4653346fe7df7ab4f471540210e5aa96751db01b37a6de", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "d7d9534e94cd9dbd9df2e03e2cd86cea962da6a72cec1c5825ee7bde2a3c2915", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "5183be4c9581b87e0096f31a929b001768d315a2499e3acd6ec934a4d95e4b42", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "f8f61c38797381ff66e0b0b347500dd8844db4d3b3422fd15d85c359f7f63b6f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "7ed1e3a35887edf4301b1746cb505930a2e1311de3e01c0ae042b551e54a8cd6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "3cdea451eaecd26840cb41b4ad5775ea5f09100212f424f32943e5aed7b27ea0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "f4eb9ae1558673254a8f0095bc2e39f338017fb158a05cca9140a591b57620f1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "313ab7e9192b9d253bfb8fd3a6cb33c393879f094aec69ba70fb0b26ac56823f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "e8d25bc9c002cc049a2bbb31607827f7c780ce21d306eeb9278f8d231b34d700", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ba072bad82d6fab9792ec75b4c9ac151964c16c72dd98db356a716e95496856b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "07b3c697283553665e04db124602285ff824ca78aa82f0f12901d3764a677d24", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "0779fd004ab02e40afaf96e24de490b46af1318e65b9a5b8faab84ae679894db", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "15c4ce20ad99ce197587c97846045ce263b693db99df4ee3b72913f6dd3135b0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "21b0cd0936dc50bf5627d14cc31e8c2131916365551c699deb2547982369c74f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ae908e5e7b8f6671621a0229626c30b95cf97d9b3657a3b9503553051a365d1e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "fef6f2cfa20abca815f1d36ef7917f7db6b3e9616060cd17bece5bc84e82e768", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "b73c7c69e51d3ac92b24a25cb895b94479bd5eb1a4c6e16bcebda5b866ec2317", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "0d3ff9c1e627a58e537dd435d7e9f6b2452eae1f59b01491726e22eb4846c15c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "bd9fa93288665ceca026f246b4c133ae46a87d8773aa4994cad255428b939e4f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "2de45b75312c07fad8a5f8c6f9e8468fd70d6d6dc5672a9563a2e44a237ebf5b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "d1ab0124d6e8c242521850a353fb5eee8aed20af592cde0f9ca2593ac73dfe84", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "456513b94ff9864b1aa94911e92ead3dfc736ecde1b59c2fbd66e84fefd77edf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "255fb83edf4de6a5dd32880946a54eff4f453cee372549550d40fb5371b5b7a3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "b988eae414240fc0e47f83e9a6833985a0434a90edbd5967ac4b27d1384e93aa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "dc0cef1120df2871ca93b6f8431c0dbc30339e1e6d380710edff4cd9040e06cb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "f486349147ae2f5e572482ccfc9e2413bc52763a7b99ddb665639bf31ffff61b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ceabd13b8b097d6091baaf239c0073c58e06c06de39879cc200c43958f94ed33", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "4707e74b1f55da4c54be1004bc6981e3695fcc30b518e46756ef320cc242d93f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "5a8b5d9944f966a06662af81d32cd323e83ccec3ba25a0431347bcceeffda145", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "0e40f34d65ad225a5316925c711eb8250f5c7e4dc5cad03c89c00125d3cbb5fa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ff4b3a54f5b345d8c3927b50441ba20166c304681a1b1644d7a9c7339f8e1d39", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "21fbbbe73ff0a7a9d414daf9e49e2e8a8b5113d33db203c608bf783108df891f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "b2473d7e18260cd9086fc8ef4b102ddf5d8e9e0171f4a62340a37800b2661355", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "0d34d4b637bb7eaae29e803acd1658beb408ce83dc85c41e0d290891b17cf4c4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "71b8076e9697c0ad4b68c235bf7fc1d44ba1160b99111d63c809d738dbff8bfd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "8ccd7413b26f8e61b508f107625208abdf84803676c204e891f184709addcc06", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ce28fd94564d6f86270d849342f343a0692966718b3083e00b86c5e480c8ced0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "5c5ab0d3655c365fe44b2f2b3dfca1a20bb1382ac22c84d261f23bd48df3c270", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "97ee5d75496eb3e0952c28f6db57c0b6f0ef80afeb1e8a082cdca9f68bed2b27", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ccc9fd07141b98b47637305bf925bf4694a0174999d7466142fad8855faac316", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "6fa413ed4ca63e1ed5b5aa7b0cbcdf4493eda17e818d8e8065bf591cc26263df", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "03cdff953a64b67faf849392548a30bfa63e1f2a2f8194166e096e2c2d816bb5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "30ee2def9a8738f4bed08eea71291272cdd5b3898c86c75b0bea43575d7e8735", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "9e0ed06a7a44f5ac14c4861fda1babc05ce65bff17ec598393ac189ddfaa80ab", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "210bda2d62703a02872544b8772822df882d950873e8be79d2f97b08d0e19285", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "b6545cee6a45b5f6ced5da88429ba54fa60260c4a9b3e44f04b24d526aa49130", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "e063751829a3a3f254acab25fe2ba2f4ea1b22c93611c260c9f258913cf52695", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "eeb973dc4a51fa0de0f152d2bf1214f6c68984d2cdbc6f9002019270319df22c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "49f0decb71eef83321d931966e6fcab7c3d07c2d00ae2a5ce5142bded698e3f3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "c60239550a9b1702d1169cea12deb30ec20c500c7a89078eef408220b6e4ffab", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ded833cccfdb64ca2fa31899785ff93028e7302a45155f17ae351d7894089199", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "d9d682260c068af7164e7f11a4b2ff33fddcab73255a72166d62245a184a31e7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "49a24f0bb68fb1680074781d6ed087196cc653dd481aa2b1521abbd47551eb25", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "d2339b55630ba16f0941d8a462a4fb009bd7246fe69ebead9a78131f9fb6c678", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "3c790dfc9e202447c448f14ebd98463627d59eed41f7d72dc812a7c50faf8106", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "295a8a25fe7bdb39b5d3a7396327bf2fcba2830b8bbdc18557e1ee8afcd71bc8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "bad633aff7efe8193339cd07e543f2b902b46360d6a9359c3a0b964b0f4d9dce", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "ec4cb6e05e93cc4259aa9d576a5d9945799fdd12648b696278f16379918739d0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "7217c5a547be7ffe7ecebb13c11328c8348567004c3e4892007cdbee486f8e32", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "778172bdab38264210bc60ed39cb93ed2558c4d6983e8c7d34f70d2c4dd9639c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "273f40ecc53b1a78fae0537205421303d2e7c8d390a50b769fede38d148a5c5e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "8be81dfbcc2ff275a31647510f9ff12f1bd9601e3f4cb5bd28dc774f0c4af7d6", "target_hash": "d465bf484b6c39f791a1707a26a9b2c682db732019257904714a555f88bebfab", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -64621,1047 +22126,332 @@ "pl": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "f74941eb955fc05408956bd848a56064e86bf562f1942eac58aa8a9e03819f42", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "d35856d592286e3b59ba4eb2ef43337a6d16e205e9fa6a24c5dc70a32105759e", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "6e1b6cb7bba228af3442a84e790a4974d250c0cd41de6517c342469c0827d553", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "f8fa421c9415863e9dce12a9ecb68f1ec1e6180441822252391e64b964790b15", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "7713ebcdf61d39ff881872c456ef910ab5137f4ba2008f23ccb1dfb0a1880028", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "8413e6d64dd1383e848652fd7445b28e865842fe08cb97f0423bfc9b2879a5de", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "5e6bbed4a66d9a5c3e26bd354a7e2a75134eee28bca6d4cc4fafd1704d79346a", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "5dbf14f34451a90528180c70fd5b61cceb5926ab03b65dbbc7663bfb47cf4744", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "eb0fd82d3260af629c9b3c7209e3a54879a7f39a3ceb00bc6792cb01e2c0fc8a", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "c5634e58278b600c27cbba427eec1b935db48a67cbb103dc04600fd556c135a3", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "9a9bdf8d02946f13353099f41461115c518af12be2acfb7ce4ddfb3c73e88808", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "489bce4e0888585551bcb2be8eaf839e7b1b5016434fe67a3da21d5083470101", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "9609e759404c2d2335590fe0dff5ed429900e794cd230516f5dfa92d99a55050", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "1288ce0b7153af51aa8552a02f89b1c7c124ed99408b68c0a92373bff513fe60", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "9b271bc731cf8b390a733cb720edf4f3596d56b63871f6de801b49db4a8c38ba", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "c38481a5819b87d425e952130d2c7a4078f92bfe92711061bd8769e85000bfab", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "b2bbf1ba08a5628595561b06bd85d8f848ee320e2d0768199169ef67f518b64f", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "a2efd62f2542b83938bafbff93b0775be8a770be5408db091cd8ed60d681f3fc", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "606dc06597e956505bf143af1eee2262e68b46af7fd43d4cfe6c8c8ca1022aa5", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "db8a0039379d7d2e27ce71b3fdca80fe3c1c1aa73de7836899090000b4f7d829", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "0c5785d13c370689cc93c5383d024f00e337dce346fa263b5ae2c37f2ca1cc56", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "b54e14055c41a6e272da5e64cda8f0e668800c24f4a724a7cb9f5b65266c46d5", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "50b3f0bc8ef3ec0e178a1b1e25513c7ca77d21c57f54860f752085192127b1d1", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "6b07cbb04a804f368b196f7df0f447c2bb322d8848073f20a25923687095e7aa", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "26b427ec55471f5dbf378fc9115e95676b619887b772a67fccb5abaa34550453", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "1eb11f41bf4199560f79851e3e34faebf0444ece6f9afcabf191bbfecaa47092", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "df388bbb495c6db9bbb5731d2a7fa99f18eb371bf930c15d28881159f070f824", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "d373ac1b74b01f885aa595aeb9e83513afb5cecaa3f43ba15ff2e02dd5dd98bf", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "a8fd48dd520856c452386b75b785ea7face52d02aee67798fdb1298be5ab33f7", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "bb6e7e6a1141fde68e60af1ddb8e16c4e03ac637630f07a80a19f4a990987a9a", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "6d9beec7ebc8e0258b8495c84d0c789bd4d21eeb11621b16bd2f2c80be03c599", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "e07d6afcc069a099b74e7ad8211a3edbfe7abb3f8f166b6fdb11192305fe10ce", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "fe8b9c67a5c16c9027a2361e444a20ca4898dc53940ea2912ddaab6718422dc8", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "65fe022c58576fa6a4a35fc0f44cb8ce0f5a17765d664a1656afaa85c7be3240", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "f6e8edd4f29a31dfafdaba92885399ebeff7b642ec9d7be3fc2a5841adc449b6", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "54bc40179dcd811a66e150297814c0cbbb08836920659581a4b98777fe8aae58", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "65d2cb848e7fcaa95b9eb820739b0f4285bd5e5e206fdb223f5b765149d5b245", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "9c25f505c1fe4eadaaea678a706450c7c1e047bcca46fa578cafb151d5d340dc", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "08543f7e77333c38bd58aedfb2f6939fccc9ca43e1abd5ed147b6f6cd0646c65", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "c5f3b008e3cf4d2df56ce73c8a4add411b21921b0280ed63b7456a455108ceca", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "fe4bab4cb4229bec6fc81452a29c507c9f73e8e8d6870c92ed220600cb8d081b", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "570db6dcc210f545b7eafeb5d5955b1b2df9e358805190d57e47b9f7dbd5cfe5", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "9b042c297b727ed1e4dd4d5ffb97b541e21c91e2896850613ffe52052b8601ac", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "3f0296513625fe22159b6488fa141428a69428643596cd2f931242f619be8b60", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "83e01c003932e569151d1209cc8591706443381ee826d53bd4d90a9659a8f4dc", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "e236f82b069a7e69ef7b54766fbf1482de0bc86ac5299c6c0f5ba121cd1d8f34", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "688d00de5ff0fc96c8b34c94fe0ec7fa79321a6ab01353f50d6ffd841d5af888", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "ac8ab98761a69dec5c61be6f585e710b56e390fd7edb464aa16ab9e8f92e45b8", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "e003833e9ba947ee4e5c5f847d958ff432aa942075e8c3fd54a9ff72e6be1236", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "948f4ce7a09b30a2e921cdf91f13c0e15c03e6a4b455fd66348a34cc4c5d1a6b", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "822f45d59c46a3b50149dfc727417adbd7a8dcbd4187cc897b00e79e6f4f0a5f", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "002a974f6f38962ed9ec8a660f2c8710beba1878f3f0b3f77505619e57f00a14", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "51fc4cd6f0345f965b9a62cb9ed4bbcbf82035a9d3dd5b368c03acc1fb7c8e19", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "1f60367d2d30cab2c0bb7737d6d75d7fd2c33f6eca3cedd49440826d5198b4db", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "a50b3b3f5e85c7a32fe024cce6221f45049ce95417579d858b6d164ff849a21b", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "7461a2676b46ee65897946605397adc602bd2078c2782bacdb823fa3a443cec7", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "57b205560c5f7cfa1a93130f8824b2b68684c7a36b7887b6b48b5b9f8b9a53e4", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "687e3e59370c27716742c25f4611a346a9bd3bafdf1ccb31b00e9de401224510", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "90cc3f8a199c65bd8cba049a10244cbe460328d48160ba3ec0a9740d7e9bf7f0", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "a37e8093008867b7a33c14d895a05cb645ad9202d4657cf302fb856b379d0548", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "5c5a7da709539cffd8f2b426d04887af631119af122dc85d4d21baf2e027f27a", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "1d2b8296c00bc54061bebf80b25fdb18be297b27526e2bc01c0b3e3a5953a816", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "b17b5781602990badadb54513228161e53d6f0b98bb4f6d2b935c12bc57577b9", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "236ea512a93d6ee8f1209a3e6711801b568a4c5872806c044c91abda2bc7dac7", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "b0d2af4cb8b77f8252484e3313c2647913ee8e72e876dace4474f36149857dfc", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "690bbb6faba1429b3df3066f0e42414a9d17ceb4024a2d41d2dd07795b162fd7", "target_hash": "444cf110e358ed5f590a85e6de541d303aaa6c0e452e076e200049cc0cfbc731", - "section_hashes": [ - "660dba325601", - "c767210c1597", - "ceba2bd5e516", - "50543af4a40f", - "9d93c304a349", - "05817284361a", - "5324ac06b670", - "37ffd5b69c91", - "81a75836d3b1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -65671,1124 +22461,332 @@ "ar": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "c05c5837056bcc4397e8952f93b7d83ba4aa1158aaa80f402c3868dbe719de3d", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "3198eeffa9c7d4a032d3dda16e013065585b03379efa6d4ebac51c0168e0ef68", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "e5bfe457e1bd11cfcea898d1104c76af3afee9175070928337657cabe9c22466", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "c8b422aa03e9793ee1748342844ac521b13ea6fffd6d3611f6346f3a9fa88da4", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "d43c6df3a6ad8bd8199caa3e057cd4869207e46efa71512c034ff9531505f1e4", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "673ab68103a0b9121637980c92244aa1e5938542e698981313af17dd7f9721d9", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "f941c2c7107228dce343874c71081b4a21c92956ad516194bbef06716ebba999", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "d2aebdb7a99cfe2d04d542f39bf6ca5051fee32216c3b335ee6fad3f1e27605a", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "d3510eb7097af104df5fa829d8433d71fcb4bb91e1dfd8238f8339eeb81199ce", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "2f1a1bdbae8b6ccdb1764e564c9702120aad71f8b22fa28daa76e909d70a5087", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "80769d2c58dc2dd13d26b92fd798d8d3e516f5e676c46e99117522ce2b38855c", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "65ffa0dbf5eca8c066b0c475e4d4b0bd3a30241a475d7ae00b42c7836bd10cb2", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "397a548139203b7fe9ab1c494dddf1da726f731001a8e55e54d04fc864a59b4a", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "423eca5bc71ffe1cab9e9777c4c1ac775814b9348138f9dc0fe58f72f0bb444c", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "db0943dcb36d492333cf15bf781a92b1ce0edf7b5c5d5b1cc136dc880e811fd8", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "3e62b16e96cd061559bb6b7c778842893a41422ea9fb4fbc8c794af715d2d6e6", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "20c85e4a23feadf154f5ff938befe1cea26fb94b083a7cadcc0e56ed1940f8e1", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "bcc3dd66839053b51ad3994cc9dc23feae6a76025145c2bfc847c754b836b258", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "be4e42d775d4a857924c7160c0a0aa6d475741cda7d9a743bebff0411202e8ec", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "beec893a035bb211066567ef36c3a32481561c0f275d76548530484896c95817", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "41f347932a1db8b20f26def1ac57ef95ecef7457fe6c4dd61e464015846021ff", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "fed4a261d8a05db1aa529c570536c124c50f3d140cc7d4c445f5d7b3d63d4839", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "1704e902e0bb99725f3fa0c1dc5202922e43638a566a5deb6e35af290e841032", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "1857a2e20131ee6dc3858dc0e638228fe0a8d4a03e7ee2d4ffb47e44a2b94d9d", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "9bf24a88785f82d3f0ccb2e33421e6f4894cfee1dd21131be1f161364a8ea8b4", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "fec411ae6895e33d564a7f573e365eb48a78e8177cb1c8cb48fe17625c114bba", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "19ab5565321ccf3fc29ce36fb746c774aec419278ee97f1a2b1808d499ea47d9", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "5e5725e878cd1019125bbc3e77d45c22934f970b4a196880ec9bcdabaf42f48f", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "4a23250e4d2f96deadfcfed1c4938d8e050263b0917e786f02e6ddbf27db2cae", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "c300453dcd23dfa938f7b2ef1df8e568b9365cf5569d175e622e4803fa656395", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "ff7c5de0c43dd9e4184a74238a5174f748d7f89f80a319ed22ed75a8dcaf8c2e", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "766ee269b04c4fe322512c9c40d3d435ec12d54bf7414ef0883f4290cfd19104", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "e4936034e1fd1e935a3631fe679c5af2a170d6c0a45a56deae98465914b25a06", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "2f2563426b75ec1b6e90d571c95edb6b77dfad52c924ae32ed349cd6dac16c47", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "26267606d15e0d0cb8a83706407c823e9a79e37b896acb6ce3ca0d5da768bf37", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "7a1c1ae566a929e8e3c52e756773c45019be83de41edfdce97438624133a660f", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "45d5a4db8c1599f60f97c49b5fe52a83f8ace94f1b1cb4383c954dc20cbb18c5", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "52863fac81d9218a7cc863da808f47c3e18947945e566f7a3ed26c6da45df694", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "db8c049ecf6e8634b67007facc9509334b103bb0762f12c87511cbc2fdcb66b5", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "1ca807ff2284b0275984dc96b593747b53649bb04148259fff298118ca1ca4d4", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "c4baee57f0f75ed9bae697a8a3d94fc7f27f61baa2a18ebd0b8ebf04acbd2df6", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "dc19a53d6fc4525ce84d3c535c37a2f16c1a2fab2d68befcce208672d38860c3", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "1fda0641c9ee26746a7eb60c9719744cdb72fc48f56387b95789e350d40b7bb8", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "99d3b7ff7e12c88950025bf2e4b9390571e46ba815ac9f0cf68b9e3b4b23d7b0", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "ef63aacde4bf1cf52395189e9740be14bc23b51f7d719ce3e35d7a6666208b11", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "5023e7e11a9fb40dac8a25a1611baf89767e24661169d4b3aa46444cc127d398", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "4e0d5ee0be8bf07967e5867ecac254e1889ebf4fc07d3d79df82d055216c6c94", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "1551a912a35c57c7910185edbd6a45978feddc5001a99e89e7aef41e63fad59e", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "0e447e36cefc7aa166a3b463a751cefa39405acf863d68494313874e96a4576e", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "1907aa39d8f8930412e7d5051e95302bfc8eea91c05b37d8d661b81612924ec1", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "3c97c55e16c194f1a5eafb2de283e1bf862901defc5691189fd8ea2ba81aaf2a", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "4aba6b28434e63b25825d1ea13c6300226a03b9a1a012e6b909021129b75c610", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "0397454f541bae51643056e3e89f79625bf4608c2cc262c02eba04cc9c6c5da2", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "456c37437e582dbff192a329869170b4d3967a79abab7bef752c049f803dc424", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "abe93de29a3b66ae1a529b0c111c42322525b5b8b2042f695e0a8077092cd54c", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "d51917bb970743077281ad1d24df8f86fb33e3de69b1de97871ed4c13cf30db1", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "b41f68775e687d2d72a005821ea8f4e7be8984a6198ee0f984bca07ab91635cf", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "84525b9303ce0214f187105a50d461f5d55ff5a6f3967f0772806d9f344f8afc", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "84c41d881d067acebb4929ff4ba503cdd54faca3725dfe637d5a9a39074503a4", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "761dee912fa47631394357b577e4084b939291e5ea924251a3c751d91a9cb93c", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "cdf1d09ca7c10529d1940ad4c6fe93694066e2db9dd05848d0383bee3bdaafa5", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "b9d84d5f9d6de333f4f10d58e1d2aa3f158fe842dcd55b16945ad812233635bb", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "b43f81d62e233390850fe11ed8fa2bb9d61bfb5b5988ce7441d199bf963676bf", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "eeff8b7e46a6ce15065b403918afe9c87d1800cf2fdb44ff5f108823fc782afb", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "f03a6a8a7fabcaa2b869513fab536f2449a3a5ef3a75884d67285332721c5aa9", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "91d71d2f0af3a29637d28ed12b7d3e56d77d3a9603fd4aa1b050a66a7f4c1b87", "target_hash": "3080716906ba27467b7c1bf8a67fa92ec244ad8877a373001cdab8bfcf858896", - "section_hashes": [ - "a34e78531014", - "90a166452b97", - "62f10fffa941", - "8867d49319e3", - "3705e2bf8196", - "14aaf284bbc1", - "c530d09f78e1", - "84c3a0559bb9", - "cea4e3637cba", - "cef80c483b88" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -66798,860 +22796,332 @@ "ar": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "3a6422934eacd7d71f3cccaf2f4b3c2999ec2f8b30deaea57d72db12893893bc", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "98cef13f22ffeaed5ff17a1d58ceff43bc3b6471d25fb7dd3f5f1d6f73df2810", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "cb7c775adb80e0b0d510312669d94041ceb4d64b1e9d08b59a2878ca23d8de82", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "4008ac1eea594058fddc8a87dedcdcf31ff8875dedd6deb8bd8decf931e69fad", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "815b5daa87b1a563e81cfab3766d4ec4739a195d6a596448ac81a6e5bc57ca8b", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "5d1d788e8a89e08b47fa3e17342a40d97b0325f927531917e5bc4abd13a6e215", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "2a4fe7607d969e51cdcf8692d72e19676172cfdba12448de5fd5d772c76420df", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "cc485312268034620869c7e2c9ee4c3c3130929b0a54226c7a4073afe529166a", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "a045a0bc05c28d5c84a86fd352dfc9c25b2f48fc4387ee75f6c131035b7e0027", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "c09454f49c172d507d85679445d86900f62a5b2743dbaac6ba442315771a4b43", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "24a0134648d989cf9f52594e1315fd3c3260f10dfd74247a73bebef266a87e7b", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "7bbf8c93de7c30ce8926f6b512e9f04be7cead09a7fbd9eea0eb20ad353e2f0c", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "19324a8114fecbb70aa119ddfec8baaa766a00fa4e4582dce21fb8600af325d6", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "aa0224fc921d6cfd74a169c853061e89784ab57b14cf6dc64b9c7d888b65ee74", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "2e4f96c11c5cd7bf894f835398a7ee537ec9b7ec53919dcf29f948b25d0469a5", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "497b7af2bda248aff25881eca91410d6e11c7b52cf8ea344979a2fa28c3c7228", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "b41b419266d481114a2fe875f2013632e808c3a7b958d4326c2a7732afcf06cb", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "eec5a51db44dd220f8e63ca23cbeb91a3e95d7f1700899ff414c0129b5d524bd", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "9951405212db400d63d97d04048371aae0bd7d47ea850c7ee3161b3864f0eea6", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "bffc936d3cc99c21bbe3b6e3e3ed04a76fa2cb99600fcb821e08194cc5b06fed", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "a8eb3d685c2ac9991150a30c1b461333880b4b37682b5f070081e481e7e1e907", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "ae8990cbdfc1d657f919a9eb51507881ae3965e05e5756e1ca9328786ceb99f7", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "119d7f1d4188de48ba08d9240f73f53e24ec2799ab45b886f5ebb1d90164f2f2", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "25d4cbe2d9c23c2ecf8232071483a2b18b618fceb5349eab62fdeb3cc9a1ef3c", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "dac25cc09ad94a2b3b8dfeab68541bf4ea5e527e43b5e0cedaf4584c461459e7", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "94642e2126ab3d15cca98200696178c36eb177cd4a635866e0d142c0a1129a09", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "f6f4220d9086a542741f9e48ccb8fe75e0bc0d4fccfbabd6200acfca82584151", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "b8712ca547e87d09f201e90e909b3a8d83ddc7797c2f9e5149592839be45afd6", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "b60539bd38c4e44526bfecc1a1917255411bf3c679910eb26cebab8fee271d89", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "95bcc20956e36d15bea70a961f794b48a572ade302592d0a42fabe9dfc5eee9f", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "116ed63582944610342b887f27c8fff2e7df3a3b345620032f1c2d4513b7dacd", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "31d020c80224bc727dccd50e9c338396df18bc7289e550f55abb6c398ef64b37", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "37f7f70b42dd64ff02681b850ef5bed04cd1e129b80441f95ddbbb82f74002a5", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "ded59ed647939c109df56af16b240d39c28a51887dc2701837f087903bc699c6", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "c4acfed9601cf480ac83d8f5e2942383c0582697d90c61349ed20c2a1f6ae1eb", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "c8342329bb5101ea74b6a728840a833f34ec3ce95e53f0397d6d261e557196a8", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "b0db86717f68387e1bd7c826149e6c90e2b6f824c078e68acfa2baaac8e8c3b5", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "e8da9840792ada2e54d166fbe82ae63cf461962bb537c7570490a63857cfaf3e", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "318e9b648ff4f5d9afceda4c386e2b01fb864bc137b20d856eddea1c70599960", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "9d94782344398e7224d1c148273e19fc9613710a73801f7ee57fdc12c41fd0e7", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "50d582217cd5ffab8f7d183796939c2abbcc089c6f33fe6c81783c74097794b3", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "f8cd569218ce4626388bbfb18d861dec17b2624aa33b958c65fbe8e3e7902b2d", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "775e822891c7c062302564d0cbfb47f48d983555f6de7fe5673b0ac9e39fb7d6", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "0af70607230fe6dee582037f83edd1650b884566ebe405a604385f2b6b18f262", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "ff90065086d78071737094c4adc59948ccd7291107d43560d90f0b293d27bcf8", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "95e098482e1bbe618511fd82b80ba7376a7c892edc89273326b6e9e118a9762c", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "5e2e57388642f5ec961ba5b04c5a39220fab4514e5702891c178ebedd1fb5fda", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "8b0213d782c6027e965afd21a128389afc553e9124a075cdc88fe148c0ee2b8e", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "e970c456713a6490c5b9da0ffdfb320a8ade914c4a289534075b816294e3fba2", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "6c361968de100e8623f4fa2586f8ab537837f3c3232b5241bf6fbb59c4b4402c", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "3cd14f3ad578eb8272f830c8370a787a1371d6d0d3b4c97ac69ffdb78376cf6c", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "c95887407a7fd43ad19288b77051b7965f2791910c484aea8aa61ef23050d8c5", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "0a4d364f0ba2ce7f71c1bf5ba1e461791843ea1d71277bca20ca676a484e1b2d", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "dc7523388f9c74ffaf26799966a26916dc5160d933791ef0f0b7576371583ced", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "e1bd8724a2e926911d420ddd33bc5ec8c8f05526ae3d0152f646e33a82189916", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "c7528630071c2a54d787889dee72c650a8794feeda6e250b1d20b8e3927ce555", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "81bc5798a1fcd0736285b5892122269b065a5e5741d09a17109a2154707c9e40", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "5b964bad7f194c5065b41381a2f9ba3c53cc3ecdd85eb6679c23910289720c2b", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "d864d522a709db129f38186daff335d9eff47c1e2cfcc54aa864aa0531006c48", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "36828e7658867127ff7640643102cd3d33a10a5a8b0996a64e6e0e90748e9f39", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "206e1bb2ce09e2c56be213710a71e4410893b0faca615928d235ab1456da54a0", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "d06a1ac5f1db8e0dc2b6466d526f0c28b9f98e637fd076e215e5bfe314b9e722", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "9e2203b4691ec7dfa2f056a1e2a5152ad2f90b6f6346bebc3746d1d73ee655a1", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "9cc16b8092c3c4b11b26308aa5135290c98d76943dff11c25eb5f9ef7a4eed6c", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "627bbf8855920437deb40d691a659fa3b09c6d31153467ab88d1828fd1cd4395", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "61b7030eadc90ca97ef26373f991ed720c3fe7d4b6a6b53884859e8b0c493de3", "target_hash": "f54f7bfe722086d8831071087a6300452590718acbae495790ab075fe47c3a66", - "section_hashes": [ - "b12a5112d247", - "da10b2c8bdb0", - "565055e10219", - "92e81dc5e7ca", - "45da7a1bfe0e", - "607c54652fb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -67661,1454 +23131,332 @@ "pl": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "085f3ce672c0dcb13172864bd7e0aceea59cb402f64a3ff940ab082b866d3bb2", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "e5a6d37a956e920f917b73193a56b8e4070a18e73fa3e74f27d9c1c3f204ceb6", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "d507e55168237b2a504e21be7c404e583c7ac24e83627fdfee11c23ca74c5b69", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "d236007895f17bc7bf702858ea5081327e314dbb90f68ffc42c4308e35f06362", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "b2b11439781610fb7312079801f11509908fe087e7d76323c55ec262e1504d31", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "ae8e71ebac88f606a8893a142bbf576ab015ea2688e5cd8c8c7c6ef01ba3f25c", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "9683eaedd0d2d4236c6994afcf673114d7c785e7f09d2ac25bc7653de26f32a8", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "d3a16dbc668cf04dcd79c6529906598484ff2a0733fda9b2a7af583e46180c95", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "6993a5fd01cf59cb06f1c2b68ec77201019a20e8e26eed26bd0c4494274c8a39", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "15e765515530f9f22e308bbf66d4e447a7e6cacca6d1549028591338debabaa6", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "efcbd23ce55772fadeffcfda5b7427a00279fc7e5d806f9aa0bb86e9c6f399ba", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "27149cbbb7ea28e1a2b51e414c687abb1dbb224a134348b7081b42048c74a06b", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "4eb7d7b9fd5eab76ef0dc78543d7d0a078cbee770b191f65a3a840b61eb7c1bb", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "a159504eb9e0fd3a7945d942b1a4143bbbe7719c6ef77cfb50d718a95fa51a8f", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "a80390d7b55b6c1fd51cf95c662a9ddf41a4357325298978892275fd61bf2d24", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "392b980fac0c2aff8261ced7ba8e8c374647d88252cf6d1d9653254315bb4818", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "3462639c9e02c272c77b2ced4ff5cd17fcaa93ae9faebf7ebf3053b1c09f0843", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "041219ba67da0b8d8a0fbb1a8fd245f3acf64ded82f6da50dc6ad82929f404d1", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "7b656b96e1cad6f29d81fe07375970c4c4916498f753417c9d3561965e47b6ad", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "4ee9794b0ddbe4add3705bb50e4ae5f03c42d21277611c45184f5cd650684971", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "ba1e0ff73e6351942bb69e3e94116775e6afb4fbeefdb677f26c10ad97b938c5", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "4893b4c52c6fe06c16666050c0765079073d2d4ee9d56b39084c3f48f72552fb", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "503506bdfc73490be39c64c7a48ff42583647ff6d719f92e4be27c33305d7c10", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "e99d03aa948265e6ec8321cad73e237fb5085f9526d18bf374d370f5927e83b1", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "c2092820c7f7011fd88f9c3659c3bb479264b2515271332faf14d6d313c13831", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "7f3363d559fd685696aa3d0bb8cf5d8a115e48880725663551c16775cdf30539", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "ba54efffff447cb9066058405ac3db74173da704bdaed2adfc5efaf1f14c8e49", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "b4d50b913a244d952272d1b154206bbbebf4febd049966fb1c927caf63c9c440", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "2de2567a8cd8bb4f53d2edfe01bda306059b28190e9833229a843899d74523a4", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "88154583a3b04ed23019b9e17090399f861a57972fdbd6fcc059db3dac026e84", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "b0fa4dce71d8d28d86febdfb24da7ed55046c46ddcc0eabcb53e0255718b8c2b", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "522b2831820d7e0a223c3af40d8e95b5004f06dd08852d05ab9589409b882aa2", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "1b5b074e4089bf6d41f61ed6528fee1c9384ce6e60d88a224bd5878b37364d28", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "ce9077687c79dc62989e9cbfc093659c32b49ef70d91b2a2ff28a9d43c690808", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "93e1f85c86076ae57a47fdd8fcf4ed8bc7e911aef2a0212856e2ea0aec32b4f4", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "9fdcba4e26e35399eb55fe79fb12687dfac7a7e08793a7c09fb23248fa926910", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "965840f334e699ef80c7f017a519bab9aaa40fc50dfb91d20699b91cdd78fc8d", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "c6575e3ef6870f39500dd246c69fc20bc302e591b9f0afc0d22ef255855767b5", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "41908ba2874589961d82342d7d85dd76b68e09cc666e006339a932a202ac906b", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "328e07dfa87c1304ad39b6504d1dd6b798547c4d172b3a3661d961c8dc520d34", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "6896e9147258bc6702edb2beec24d721eb54f4acdc1ed8013357c7b927d5c826", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "e51959054f186c7a2fd73f20df2fcab5f130d827f4296784b520e96618230e10", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "d6d8b84c83c3d4f55d7f91def9ee8e4e3e83f0eee7b322dbf3359f23de8f6325", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "47806642262bbece24e8f37b5f1b273483310010ad9965e21297bb46ae54ea53", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "36ce5eaa185bc7e73838bd4e06808237f2e8033abd6294de2bd09db2508e0170", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "bc01332a4513ee0dde14d560e9ecc95822b6fffc4bc65abbdb2dc69cea46f136", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "997ba6fabb7c2d84b4e3c768a56c364a8391cb238afe627989598a14745c3918", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "4fe9e34bc31180d0c7af6981fd9f7e5d965a7a47539ac0c7d7ff66470f8645c4", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "4b35a6c9969763751a35be674a10acd581caab952f13db0a5522a1df799078de", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "60682e8dda3d371445704fe3e6fef730714eb2e2e927a11aaf269fc235ae0526", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "b14670b8989bedd9762751033f553335e5c5fb66056e1814e3e2b68984873c31", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "873be47ed8c56695f09d21ae7d1ab1892749c61af5025feb09c481fa29a00742", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "efad643777408e244cd469e783c9a3ec698624903bbda9fafaac1becf178ce4a", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "fe5842629e9d156dcadcf9ab221524c378bc23337ca4b51f510eab1e8c8310f9", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "08a95a78027f973130f5077efcfc373a826cd8ad4a2d29b46b1fb72be1f6e386", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "fbea910e6ba1edda3d1f46b3d39780d8d166fa0dd05572bb9c3a12f629816856", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "53f17a83141ca80d6888425a7e2f919f25f7bd8f0abf2b4b7b59aca096e0839b", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "3ee30622b39c4f26f6c2e62e86bbec78a35cd69af08f7658d6cf3888a7af9be6", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "8c1f49b65d13f0079c2a6710bf969118a204cdc6cc9335aa6ba0fec0a14cd3f0", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "a4d13a8d47c843f1a10ce3fe2f3ad75a038b2983e2893076a261c72f13450a41", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "6bc95aefb29c8414e504f9eda1017cf9cadd529ab6614aaef31914675d3974d0", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "794b0c11145f86dee19113ab07c9e08035f52357ea30c0395d48e78a8b4c7a0e", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "b384fc6596780d3a3e029143dbe1081a841d05bb10e21db04f21f8b910d978f3", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "9e0be7129218aaad7c6fc57a388bc7f0baedd8b728a767776186b9621e15c993", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "e6593d22c232cdb017167ccfe65b6b80f72be93bbd7036c2f8bae402febe4882", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "9023181188088b29242b3486270499fef51dc3e29e75150677e5a59536e71298", "target_hash": "07ec7c8e8cdce963f8835f1f0e9a8c0c57d5cf7d14304b6f5000c66cedc66e3d", - "section_hashes": [ - "d40059b05d72", - "c79916e0b101", - "87e9b1c7c0d1", - "f745e4cf3456", - "892dfaefde0d", - "5bf1c767c27a", - "996ad153114f", - "87ea1fdfd62f", - "1e28368781d6", - "40536b7ee29d", - "cf4c87f96dd7", - "bc4a539f22b9", - "1810b0caec73", - "cf1c62419cf7", - "76103b462408" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -69118,917 +23466,332 @@ "pl": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "b30bbc34a3b766bd75a9dcf601fa8d8be7f8ebcf16bf901933f23d6e48aee97c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "0af0ba0dd018a4bffd82c1c651d7bbe96c99f864a2142abd98c4a93919dba94e", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "743a6f6998d0101aa103112a1b9a5e13792ade887d9df8154b0e96e08c2875d0", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "3b2baa8cb7cea9235af90a02243c4d2d883b0d1ba4d1ce9ef0c0cba066069903", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "6f05b9a0247faad394d8b6bf4c9c0a4c756084fd6bfba02a751bf91997e172ae", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "21c1e6812bcb96402d35ebe4b3191564379fe48141d0460efc16fd83fc2d89d7", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "4063ca2fc74ac0e959aefb7a71ed2384ee92fe135f45cb6914bc001935f4c2fc", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "be6878152a83adb84366f3671a01d43643647ffbfbdbfb5bf4574b481888ab23", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "b70da24105af1849f895567bd0b6b0514d090db36c3d6f3d5a8d7b37a4d0bbe0", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "297b4723a13e74b303bf6149a22baf3dad69240c81af69f3158208ccd99321f0", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "aafa7dc499bebe32439bdf09462657ddc0076238d5f3be97c60b115b947d36f4", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "be8efff085c9abb0b48bd7b5b76101bd6a97b8a9d8b92fefe0817fc3f6836f70", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "76dbcef50d9734b0bc2809518b79f86d554a528061d95cfd73e299dcf62152f1", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "834fe922b20733fab632122fba9b3b518e2739f988289590a0277d01e2827421", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "41c70f8801953703cf0e0bb9b0da800fc212ef6a0e2c72401d34c7d74f6223fe", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "f90f0334c58b31e839421c01b6c35c0cfdf6500116f76420f0005eba33af4e89", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "3e96dca3208cc84975ef7f0dc6abed13792ae7d70b8470c008578972ea267f75", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "ed7568065be53ede0178a0c8a84e3eac7f857f3813cd174863c34afb44fbbf32", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "f7ff56272609cd7ba5af599c9e7c0cabc6b428529ef0458dc72d7bdb6377a87c", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "a51def535c44d8df69379cc5fa814479e636cfe6ecf3f43444c6d94336458763", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "43f4ac9c2441b3ddb25e91ba66f4ed8f8fd33a8c1d5511e92f10d1dedc861ecf", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "7aaf35223382998720d121455878d053674c3524628f3923f8db46d1f62aba30", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "7752d69bd0c74320ada7bb6f9a8d801c69bff751e0f1bfd9335b4fd07c50d262", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "13bbd9800f8134b8a65f55aada83593523425ee45369e9a3ef8e952607724433", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "cb171eeab96c2a537a26744eb106721ba92318698e261d73b43b750036040038", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "6516eda5d8313c381f0c494eb2498145eb651d1a58f04186ae2d482f083177b6", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "1738a6257417763b662468c50d62e947917a065366318d30f36f87caec9c303f", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "4b07c16202eb1b4cd5ec033eea25dee59451a023e1a5ffc5d14ff7e40417feba", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "7ee5f5207798b075908d95f8d5dc291359b1d3f6918011054ee07127c9314791", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "52d18396dc346da5cdc2541e76d133a76a0a520c2e6101c19ce81f1c0e36d53d", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "e708a7d3d363d51ef0f7ad93735f91172d36c61f9e7066c49c4feb826256ec7e", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "8fb488983cafaaf6b232ee7181aaeaa3a1d71413b1f4366608ef58e79dbd20a8", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "3c3a6d70af7c8c0b1d5158d10e09a1fe85508847b57bfa703bb667180003f38e", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "49683e5c63ce8302ad701327b2cac04928e4ac75b76a6b0c51a1353494da4367", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "a9127790ea0e5975065d6856743002213c3f0d5a0650a16d71023d6ccfe5c062", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "2157fec8ce6dced57818216ea499a35493889679b4fcdc121ae772557f42ae49", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "5022a0c27cb6b12f62bed999d708b87b2782431e6fa802533c5feb477afccc6c", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "065ca0598cd4f39c11b29053e305a183d793ec93eddba29140da26aba6ed7786", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "4e5a9e1fd0d2332cee58ed1676ffa69c4bbccf5b299cb92bfa0e140dc999e1e9", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "40428c543f727d924df73a2f63edb3de0d295ed21f12ee19ff629660d91bc7a9", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "0a5ea3a4bbec7ccd0a867a5f7755845d99e54188b3f219b6263c873ed061aa42", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "21f8ff2fa74377c570ac3e1f909c0ea664d9a64da1496618169b76d684446e51", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "58f7a2e7505cdb2eea825affbec65eb69a3dd1986a3628824a1b3c881a0cc10b", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "0d4e395fac5f71704e7e9fda7f5e2618d0a74b60bf3087475b83caf8e7e6d16b", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "d10a3e40074f920a6f05490ee8d1653f1987a5a3877c9518af12471e23b478f9", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "718468a26de51b542811dbc88a07d8f9de305d46aaa106f6aea1855cea4c3a35", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "3dd80e2983182d0dc4f19edcdac70f286fba8d22766b0ddb3c39fdd9f92a08c6", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "4250a22d880a5cd81a5bef75491c32aba5935cb624b9cd5b8ef2f43802ce3ce2", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "d8006c937ce825dedede8eb11c4f874db57718bf03d429e48f4114ab565358d0", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "f07d578628f01d2497ebce771a26f99761e986f2874bc8e990a95e2b01ded16f", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "8a90d189b0b0794720411c5843823b3d6fc374ec652c49c80d7fa02a3e292126", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "5f5f69cebdd79fd507a798bdacace8a07eef4fc5e6f3a9421d29fd2789095cd1", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "30f631ad5dd656499c36ac5c22e4a4de0401166a20ae5aec5c15867170d4686f", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "b9f2ac49e3d380e87debcdfafd10f90c91f16f898a0133d9f8ddae72a66b2750", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "b9f9d7122180ed7a6662ab577c26babd96846f38b0e9587ea1d16a52f84bf688", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "1348e9546b17f140b3f595162cd6d7244a1974fb02198d924730857873e38cf0", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "5642f9585eff48c866952bae6e7ce512757c1eb5b202b9a3479168a47a0093c9", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "78c9dad1b4be4ed2598806290f646ba348aac16b63737aee3c3f51c0adbc5651", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "450a5d4617594476604e4ac68baa1454a80c0650825ebbdd81296c99f4b6667e", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "823c40425829ec4a89e7dd7270903c32bbe4dabdfd6b49e58e176ecbd3f36bf4", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "16771b9fbb7527b5b5381768d0ccf5e8833d5bb93f09ea9d50dd1fdcc54ad3bb", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "df4c1c60e593ff229f8a4118fda3a649ae3d9585f3af0f027bf8b17796fbefe4", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "a7bff55bb9bfdc24017720243fdf81a0b1e1f50613f620a42310bdf0db9e325f", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "2f9b63cd1e16234abc809bdc357703a1ed12753cb9058cb3feac393dd9477690", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "fdb43c6103e9a8203d458b12222e8ea0f62a73118d19bc512ee3801ab98b7f3d", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "d30f989533ba92322ee859bc5dfa4eaaa05972b24297cf7936da493f3f2f60bd", "target_hash": "4e263cdf7c39660a0ab268e28b0321a11032d5afea995e8685692e1e825986f4", - "section_hashes": [ - "03f3d1038cb6", - "4845e6674be0", - "46a186f0dd7a", - "02f67492f0f1", - "5a875022d8e9", - "c30face701df", - "b619d9ca2672" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -70038,1718 +23801,332 @@ "pl": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "c09ed0fff718c764f52bf4ae4da7455da61720809f1f597e4d14e1a81648aae4", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "8829296790412af85acdb04362caad948f1df761cc8bba26e7ffefb06fb9577b", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "b24cc533d7057fd06f2d07319525a91310b9cd1b22e37b4f45bf5536684acbe5", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "fbf353492d2eafbd3d3731fa66cc6aa2764338d8b984bb2a033151793bf39f34", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "00d6065ead0c90128cd73f414edce07c0ff26efa7dfc0f7d475d586797782434", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "abffa5dba1cb81b42b764508d742d2ac347a4555f0671307a43a399a962f1379", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "8e50fbca86021896e83249c98098c92232e3cfbb86478ad09760efe66e24047e", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "1541c6ac916ea8d8b63cd9dde49aa402da4d6c91e8037b0b2bd9450a4ba80c16", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "3da9c3ed9d51382f91c427d5c37eced7fffac1d6970f6273cf897832e7c2bf08", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "156707eef55cb9b55ab60a14313e932df62cde62fa2b76c36f5e92d8e3f8eb9f", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "bd15b080408955573223b134b096d67518f25e8d127aa5057c82d6ed2ae028fe", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "ab1705ba4b4ec817c42fabda693933818822f81b178699ce763f769283724c7c", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "9dd098242646856580994a35187ef8acc301604985960b7a00316096f7cc9937", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "29bc96eefe9f0483e9f9022a97deedf3c3c3d866239180a9e8ff6dc982832f2d", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "99c104103686bafe10654071873c111e446c29e10a4c2a69b66b9979daef474e", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "0570283946393f57fc677c3749c87503bc46fc907a9801d816e47aa9fb4583b7", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "e037e8487843a4344fb2a4308cb5430a1a0d2f0473418dea1cec0ab19eb2d8c4", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "f63d831fefeca7a9a2a9604d963466c4bcd8bf15be117fcbbb9e558eafbe31cc", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "aa3939397b19575547db1754ee83b3a5aef6b8b21bf38f726167d4cb1cd282a3", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "58173f1d89261ec03686733652376f6dc9baa7c6959a64ab0dfc41724bb06ae6", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "4ca884fe70b1f610cd11869d68680f7e66c2d220f369a1e72daf30f224ea03ec", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "4f13075c9cb250d90801894d3582d9276eb3c52533dc8dfcc9f1bdf11213984b", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "0353c653b1d4b881126ecc833b3ed561878df99b0f6880e86d394914977a632e", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "25ec02a30ae32181844efc352f19bbd7e90871ca620d39d5b756dca1e15b4ab3", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "dd1f26611daed8d346ef5d059797d1a3b5282b196fa16fae13dcd320012927a2", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "d79c796372fa6d060bd17405d0f1cd95aed6976806425797552afcd38f1634b8", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "26c368c9a15dfa1eede2eb6afbeebc117c958fe543ab320fbf4e624a766c43a8", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "f034e0b44be16c61b2471b1b3532044f85642f4e6965335994be61ef4fc142d8", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "52ab943b58e730fda37f4bd3b0388382f464cc355e8a7e0b19c6bb7aaa0f2d60", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "2baf8194d1b97e58b895047fbdd481383a879a49b9f55a7044d9e1b026d6d00e", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "da1f3e8c92ab2118f9d21a6d9968e259d11209026d63794ce72f00113bf94d80", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "b2c8cee0a051216b7509d7da97d0fb2a407f55bc7182e011cb5acceed68edd62", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "b8d5ec7d1d3809a5401458e99e72fd319169fe923411f9adf6a02e544c273947", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "3d9abcc4b936d47d1a97ccbe980804eee356083d2ed346b6783ee3dcc2fb1cdc", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "786831eb24784257058884f5df9b9e63897ea38d80c25217a73da1aa736f3b5d", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "b66e28fec2633ef01ad4639ee14d88d16c4ed9f6ef1614abca17f6a766d863fb", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "908e3945fac9b7369ac13d8059721e9e6267b892e36f3bb8a6c2deb69a429806", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "f51003a14ca420a091abe43f444ced52e7fe84ebb3dbe90840be3663b84e26f2", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "6604926e0131c2408a96c2e8e21d20441bda2e933a1925d1f5db99c013902cf0", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "3871e7e63382c38def0409d0e9aba8c30d13c7195ad77ee2c12428be54e7e2bf", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "2e8aab657bb94df94bb6d3fac7bf597852a897f785eee642990b7aebe3088aca", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "b83496ba5a2565a5143e27750181db9e1ef752127d2539d3dc377a187927b47b", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "0e520575ee67edf786d74bdc586b5fb186b5ad910c538290d4e2d0236505d077", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "f387beb3d58f59c35be5d37a714385d3d7e0010e680c691edcd6b7756c118d5c", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "f9189b4d5ec71c131a014aebbb7936606ccf29f23bac57f7943527b0fa848125", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "9702cb61df03dbd39072f8a8fdda6e6adaae4c42c832c0ad106e4f70ef97f2a8", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "67c24da780177cd96d493af790cbae226f4a7507713ab54b46aae967377a90c0", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "909c028aec8621cbfca85d574cef118d5bd77406fa8dd7c1ab42a8895cbef925", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "0cee2032e88718e4c948f54ffb1fd8370b7cc6a787c08899de1fd5d3c3796a29", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "1f4f3a30ac33ee463448e64ef2a688de8f4f6cb78b92c9dd56db007705c6beee", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "1e8fb00cafe11b3886ccc3039e0073e5a1ec1bf7fab9aecf23865f4a7cc3b38f", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "58bc3fa30f67aba50d0ee966443525b563fb7b5b10750d012eaa01de507cc7c7", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "2b852932108e92f5fa7608639f704e58316f985052992a972b82a85f5acad786", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "32f706e60ae2b028711ebd12e987b230338f71314471c65f71a865beb914f396", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "50a9b7ad65c3f6cce70bdba6f98344f778e58b15d51a9b5a2b287542cd080ef8", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "78982036cb89575c76ea76abce7617cc543e711dd36d4b67d4e4a3f6be417259", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "372f44543660a080d59376448cf00f2b35f99a83efcd714f70c3d03e5bd785aa", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "dbd2b67abda6bdc2eede702bc9f8c408a0f6dff647fbf2b70717ae3ebc28bc0d", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "5986bc51aeb88f4725c3104b7febfa5a2b2631aab8ba8627866cd2287a11f789", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "d645f7a3180eda6dd46706feb576ee585e9448109cf51649b5ec25437a8e9577", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "934c04207b01c11cbd63c568dec6526ccc3eba4cc07d30df8c7aea9eedfed16e", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "ca67430623f71c270b4235bf3f4ac43771a76feef0bbcb6c845dd09f0e111871", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "115b98bc190bfafd6cc9c06a6032db8c309961be4f47a1137160cc0e80463a87", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "bdff136481ecdac2ae963ecb0042fb20f0ff0ceb31102140cd3cbe8a65200785", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "1934f8234a8f7d077b52a461d9f0d5905ac4286d5c877acaacd77b149631075b", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "0c6aa7f2b0d941820975b8c831c5608799124a2ef44cf4435fa9b28a95cdf3d0", "target_hash": "b1dab171086fab600bd3fa2721f53eba88c6af3d5e5c398bf8a0cd1efc899e57", - "section_hashes": [ - "69d02ce6a243", - "2ff327384a95", - "dd9bb6cf4163", - "8e9abd6c096a", - "e0d2c99b931f", - "8e21177fb73b", - "04b55ec8aa2b", - "047143888462", - "bae3aecfda35", - "0d8f3eb59642", - "24a273f81929", - "eb1fcd2c081a", - "e9d7d4ae9742", - "de274057507f", - "d50852fa8989", - "0ec33a56d169", - "c6bcd91a9c79", - "7317b76faaaa", - "c68737563e9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -71759,766 +24136,667 @@ "pl": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "407d106698a6ea864d487e9f72ba2347c7d6e1dba523897e3f49ee65966543d4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "b10ffc48d3ba53b0bb980fcb042497d7012b9cdacfd40a553fa6077ec5d90bf3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "f808178c78a8146e6acf300027ab29da42251d647a89d6516f12e5fc25e9912b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "62a46ae73d6ca97ea6d04df16124f7d7c1e06a9297b1a582326e7e27bd4ec7dd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "1aef7701f78eb1edda54823d04ad3dde0285cbb569e24cc6d228824bb78c70d8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "8442aee740ea0d92f7e9d3039d003884cdddae47588070dafa5d2d433615e861", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "042a4697e3f8e763bc6449175c34f1fada2b366bbdb2dae98b8f5c3913c84ac6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "2854d5c040724df882992adb9625e152db8ee8ac9c16134db4e9bad387a8857d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "12f7ab272a89f5df98d4dac668c8cfb84aca319fc7a14a9bb68496e3921dcf7d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "3317a66b3014a6edefaeeeff6d9b2d0fa822c14aa0430db8cb73a484d45061ef", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "bc238673f359b02a21bf26366b0a0790ec5f414aa3bc2c078a5ebd5c44c94171", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "8561ad60c5806db370cad222926ac87636b864d88dd22834cd01929ab27c7892", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "b2a6b6de7bb2fc4c1d3984ed3b4e32b10debf91050e1734e6bb44d5395922258", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "9743cdb9ae91fa9aa0648c2aa0f68e91d24d77f83c2b64f7fefef8ef2891209d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "6e6cd265a472b69a0ce71301b6bbe912f6a1e07032fdfa7eab23119f45a00ebd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "a8695e2f6ac447140bb1396f6b87db897948e9e6f1a11aea15e8c67e9a637715", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "03bfc21fac4d8fe7a9a568b70ceab86862017de63685651c6a56ac9474cca920", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "5912c3fc7c2e3e08d6ea971b255473c8cf58dc054a0d810791fbfd5d6de44ac2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "553497954d8f84bae62d9ac67c8213cf18b5752921221f0f6bd61859a95fb6bf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "e3b74ef02fad9bb66694fe28722df6bbf57e204328635bd8777bf1eb91cea6bf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "a11deb4691658a98df39c98604f0118e8096d0e45573c55a78d2646d513d31ca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "f30b5a17eda7046ad1be8669235d83921f5576cde8b57028497b3c7ed4d9a7ff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "42d466934dd694db6d9dfe2215fdc53e7b2cc7d9b5869715dd859e86928c1e9a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "08bf8aa145dee37d15e1b9ba5b20e7972fd7ceb5bda6d97d3fadc185ec9db380", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "355e5a9a57331243959c2cbe74bb00593169511e23256f62ae3bc886923f4268", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "44a361c33b95bdedf91d2434c5a10970ede01c3b35912f95a77b7b95ab5ab162", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "7e2f724e72ef829909bfcc6dd018f0182a2e1e7c819cd7f9fc1557984e23d738", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "7b2534af2c10d029a39ae9628051d85ef5ec229dcdc04c4eb9f2ba8a0f7b7c85", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "7fd01b44bf388e26dd01f2020a6987aaf9055d7ca228fbd463c352a250e6acab", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "81c07e440495ed390cd0be7d486a7a74e652dfffc47916655c0417affd896001", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "d717af12ba38ecaecfb334345b3720cf4699e76a1d0320457e5e066295450929", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "449e037a4124268b3bd0f50f6ac51c7dea34974453ec329a4a2df4f4fd9a67d4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "45bcbfda77d1245fce529edb6d0e43702bd0bdcec2846f10fa908b7e71928874", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "9fd82644ccf5275f9cb2265861aeabc8cb8fe1bee759a0d60640445939c4c50c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "d8c48b3e2c03e78849cb8dc9f6e2c74b7bae88418e07dbdeb61a9533dfdc91bb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "40d9e3b70a5434243d287b809bcf64c3d3df0fad78b6f76492ba3d13d93e8bb2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "021b87f89aee6f56deecde02c61401f755655b74d2bce8370ca06c85201e1cdb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "17964398d73693a15a9be0167f0fef7ff8f768fe822a6e51aa2e280ded28c293", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "c21f02ba79ee14fc4674f8969eb4f1b207e4f9c992f14de190404a7444fdae27", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "9ebc663f226f1916ebc149afb939d181099a5908565837320ce43680ffbc6700", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "7cfd2954f948992c98b369462a6bce4d5c58836fc32e4be19b4759ecef1504ac", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "a67219a572d0308ee21f3ad76383f909a29f245912ef03d4de1aedc3599a2cc0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "42bc68d191490cb25cc61a0747dad60796c84b5008fdfa77f83ab23dc2a4939b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "3af0332bb9bd52244fcaa565846f5634bdc06ef72e845aa77053ba8a21f48905", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "0c20691d317c9539b6a7bd622424f1f63ff9c31314414ad92bc314a131f74f63", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "44bc00b32f6240a29709b8f769a7d6f09947eb452802ad1b876c83c266e75505", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "d1564d1e40677eb2b3c6263b00e71d54a7c0b3d04e78ab22d633ee493246b900", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "ae7ad0f6bdc11a600d0e37fe534340b39ae27144004f984ff51c86d04c82a90d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "42ca425fe5f12b82da1eff0966842f3ef0cfdcb60a51f159cc47daa5d1a608d0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "e88bc2ac534090ed6760ea1e0a7d12a995829598442c9706281a3c8c7b8d81c7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "bd9f4481ee7c4c63ff5190fdd9285d7ac47698e920350f47b0df97487537a805", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "9ef74586c5ff7cdd5829f486d3459576b46a9a84fcccf8e472d8ee5cd10d996d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "24c29e327a60fab0e5f2ddd6572ef0df2d34791bc821a66b8f9517780caf25f2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "267c3e16408245ce5a6adbb85a9514fcce6477f311b32bdbc85ad196960b6d1b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "c73fa81c96d5ee68d87463e41799d9846e603698a42291981a35e75461ace17f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "befc44825d99b252acefc6e241615d6b57f2b42aa2d9450ade82c11f041efa75", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "f4915fe7f4f92663a81392f7d438c6a65ac759b81fd392b997463a12840be62d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "074af12d14e8adf80ea1b53d1a2b0dd66e7e788251388df0402f2f3d9489e96d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "3fa82577501cc73c2f41bb19a52b524d1c8b9c0800d8f4d806cb782d654ef5e2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "132ed36b8eeca87c876f6e6e7c6037932af723ff34c32574bdb9826fbc8ab8e6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "269a2cf4c8301c3a626a2a88d7209c76f4880a329d0e1f8aa72ac0a275b6e882", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "3eb73a3730b56260921a2b5f7bee21034d87aa195321376ec322bdbc8049e93f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "4199486726c21583817bf223d0fd75ee3acdf9bf7256fa345db978174948d15d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "5e10bbc108cc44c56d7055f57c6e2f117018fe031ac96ab3177f18bc4b435c8e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "b2a9f33c70f3ccb70cb118aa807025bda5b86d8cd8fa01a00aad56930e897eb3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "4ab8b4fba5351fbcbd7bfecd99e463ccef56ca041477c402439d069acfac09a5", "target_hash": "e68d2cb171f9dea8eeb6ea8cdc80453716ec5541fe238607561305fd7fb60f09", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/guides/FEATURES.md": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "locales": { "ar": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "df7c445b071929d402ca50ee8840204faa38e2586c40533f1629db257dd73e24", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "c57f9af5bd3d65aac3ce56b8b4b4669e8f88e8fe023ddab26c83946e07ff3311", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "fdbb0c049de4ee566ba27a9d28fc3ba6bd84144955924703e62cb37e1b589943", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "77c02c960000b7bffa0b6cffbb96bca51b4bcdca25e992f10b1095767242bcbd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "caf13952c60a566cbb46e51558d373502545d0e870d59a51d954e7d83e3405ec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "02605265853a25416278fcdd8356dd6037f44208b0846eb7b2c4ebe74e42e031", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "5b67b9b485497524428823809ff1bfc24b08f262b14170e7464fba685efad68a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "b54e5350460c9ba428ba0c2f77a8c7d9971394e97ce1834d11ac85fc7b0a3f61", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "8f4c4f33ac13cbe7152db7638e4beebde89e5efcce9f5915384ab6e39b2e70a4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "b395cf40145d1c578b2278556d427e15fd33d64d0d0b0af3a2279f85a5b3c932", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "5d82284b37d570581e6ae2bd18b77924790ba4c962854b77a4fe4e472410ab36", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "52c90e7730b04f846987afbc49f67690bf479fd1adfab65cd18755b0283b8dcc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "6e52865184b6396fed2f6a4946ab309409b6c5655b2da5339b61ea8d234f2366", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "1faf6e8c465caffab7eefa9ff71c4cebe18d9865ce9491a227a50ec999406ad8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "f53c2409c367f102f54972ee46970882f139fb0cc771b06d1906fca2a18bc6b3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "2820abb1111a4483fc87a2079583200ad1a43e029bf58f2bdef8f6c71c478cdd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "128f4982a13ce66095960a62230a972ba9d2e9a4a80f636b554921c6d045bb41", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "6aa472e14a5f851595563a1fd0ee49a53a06a0974e168237e5985f83c5aa0485", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "881ce796a3368b810c3ba1921d270aca1bc3b2832280c6754fdf2d8a3c975157", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "f02cf59806f0f3805835872aede7002945cad19315c5cf8fdb257f099632d010", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "51e5627fef91103908dc831aed671d835d55f287fb9718c7bcc01e83256e5709", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "3037c00c6e1fd9605b5d155b779d9dad3942d451875308a727fcabbf8dba649d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "c16c3539221b581fd59472be4c3f8aed726202a57408247c0dc036464f262355", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "d4d764f7100ca40d6a3e6db46467fd7252bc517edde8578df171fdb5675a400b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "f4f535b1708e17f2e49ca5e751da8d1da892e78ee8e3b2a0870d2289348f5c35", - "section_hashes": [ - "eb0d40bb2bef", - "e7784077deb3", - "06b02b952231", - "5ad7ea1478f9", - "6b3b3c1d977b", - "f39b4f54fa7d", - "44f74765272f", - "e9d9cf097e40", - "dfb349934596", - "d8ed06777d93", - "ced6de562388", - "fb50066fb496", - "6b1763c243d7", - "5f87a404b8ff", - "79c7b66d0e24", - "18c6a36ca42a", - "69145927ac92", - "839498523106", - "b1ecb73073ed", - "7deb70c36535", - "8ad4ac5c8fc0", - "b38df4b1d91d", - "c7d9453455cf", - "29ba7c1846c9", - "b0ebe9ab7492", - "89dc9bd4ba92", - "3f1eff7e3072", - "0785d9127e39", - "0c6b42505577", - "7c105ea32885", - "3a1ce419154d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "bf8f18c912fdb9879fc711eb7637e974116c7acea478d99f239bbe22a834a376", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "be7c3ca8e5352eaccfee30835f184db793a5eab3798e3c3afca5ed4aac6dfd00", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "5120fd5ce26f78a2e42d0dc88801bfb01ed29d8ea8d8dbceb6666ec07fc90f7a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "bf3f867ee26b64a10c773e1ef016db97d332a083962f5ae55fc077e842033adf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "b1caa73b7339f1b97a0d19d0701c2fa6295f42c1b1384bf03bac263f49c7ddd8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "60d8852d1801e9f069307549a1cfca2bf426b4b99157678365b397f169c18a8d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "d6fef7b4101415a96ad1e4d73744f2dee8587c08d0d4a8696ae6e6b179638757", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "106de75bf06cbe4b92ad71d312206e02a4c28a0f3d2a7b65bb47e1fe3ece6da7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "a583130d953e0e455afc238aa7bc82813cb31775d112df563c023e9440d8be1e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "36bfb156063b103ce7f9430ecf44e1961864dd212bcad8f2db13342eea9a7580", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "8051d96ed294e5abd20eac79d6c7259636ea9bf6b3606585a6b35123138ce1cc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "2c936b9d007974cf903d3f027d34e77f0c6bd01f101bb95b791b7ff34963705a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "6759caf659f7534bb936a73afc7895e2d06fb1994f38d3358ad6d5e27c9de414", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "2491c3b562f8dd286a773393927bc2c0aac94c02dbbf09d8c81c767c70f7a378", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "0c04d30c34b919504628af6a91595763374acb63347e18c5b19e78732f04e6e1", - "section_hashes": [ - "eb0d40bb2bef", - "e7784077deb3", - "06b02b952231", - "5ad7ea1478f9", - "6b3b3c1d977b", - "f39b4f54fa7d", - "44f74765272f", - "e9d9cf097e40", - "dfb349934596", - "d8ed06777d93", - "ced6de562388", - "fb50066fb496", - "6b1763c243d7", - "5f87a404b8ff", - "79c7b66d0e24", - "18c6a36ca42a", - "69145927ac92", - "839498523106", - "b1ecb73073ed", - "7deb70c36535", - "8ad4ac5c8fc0", - "b38df4b1d91d", - "c7d9453455cf", - "29ba7c1846c9", - "b0ebe9ab7492", - "89dc9bd4ba92", - "3f1eff7e3072", - "0785d9127e39", - "0c6b42505577", - "7c105ea32885", - "3a1ce419154d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "90670d20502d42d47e9090022ab6d5061feb5c0aadc069eaec4035c62ec8d0a5", - "section_hashes": [ - "eb0d40bb2bef", - "e7784077deb3", - "06b02b952231", - "5ad7ea1478f9", - "6b3b3c1d977b", - "f39b4f54fa7d", - "44f74765272f", - "e9d9cf097e40", - "dfb349934596", - "d8ed06777d93", - "ced6de562388", - "fb50066fb496", - "6b1763c243d7", - "5f87a404b8ff", - "79c7b66d0e24", - "18c6a36ca42a", - "69145927ac92", - "839498523106", - "b1ecb73073ed", - "7deb70c36535", - "8ad4ac5c8fc0", - "b38df4b1d91d", - "c7d9453455cf", - "29ba7c1846c9", - "b0ebe9ab7492", - "89dc9bd4ba92", - "3f1eff7e3072", - "0785d9127e39", - "0c6b42505577", - "7c105ea32885", - "3a1ce419154d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "083a375ad06d74e689ef92508c3f0286cbd3a766a7e982c2e9313c3f58264fbf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "0545e6f90bef06bd716487c5d0308eece6e438f80e3516be262ba8b4376b886e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "961341b36ba1f5610e723b52966db9e3d552a89981db39c93dbf93f88c4fbfe4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "f5bd7d07ef93d4e783e10bda5679407b89ea1f2088ab8a6aacdd8f31ad76fb68", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "4693dbd9a64c749b215e2eef510e29ff919df923f591237ea29a0671e42f459d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "910b8f6e0e1188eb86dea7c736401cb01e0d41f43568fc0f64426405e0a1aa77", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "efb22ff90c1d8d01ea2d1b92b7a2765562f7b1a795007da79ee87bcd3970df9f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "f1a3335db6be5c070b2cb1ced2d148763e3a8bec1b6a00ebe1c9864e13391f91", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "c510b118c0270219e668f2c161c9db573514f2e2ed3ad5dab77d28c9ad7e3a81", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "833300f0fbdeb0e323d9b73ed0419576200e8bd6f8afd343d633c94ff98b8dfc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "6ed5439f1cb984b211e001212125f8f257b7a9a1f344c036d6fd9b5f5292df2c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "0d555e4f02260506dfaa9e0104e485dc73c7fba2a571025a416a31e1699b6c61", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "f5b20de4bd24cb5247fc7fe25fa01be9cf51e56735d9d270c67d2d846fda3e45", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "7e88bf3b9e1875b783aa43aa404908a20171149ea68f51a9701882a6497d0795", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "df9afc1d45f1f4b4fa741689111bf5735ddb0d39929be3490cb5a7e964d85f46", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "11e47bdd4c456ddfcfc87a57d49dbe43d6b58f51963adc22444ab4860ecfd5d4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "8b949f0c3e63fe79991258e7f6e07b9a4ae133dc436796f5449db06f623c4747", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "ac7c26090fbd8d774d3c1fbf92c82dfa50dff472289d3ca38633fc4ac7dad906", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "b69005f7401c40503e43b03caa951d52e3673d0e9e8f0d23f1ae0b6865077b17", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "8be1bc39cd51d7ff0cbd0aa98940a3faba4ce1bb777b88703c1a4714d72f0081", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "ee1e35a23817b10cbc93da8bd14311a59e7b032c4aac73dc8620fd71633d24d8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "844bb0e5bb76f48834d37a1961067260ddab8b3765578620ac4a5d753463959e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "611dd241810083ff4b72be49df8b59a4f5e2c3d9d99bc62b9161fb8c9284dec5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "c4abb7bbe54cf06621e486e4f361a96b78b18d24d4c2770172cd9eaa831df56e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "0021dbb4a682e199e0b3eb29844a78656cde465fb085c6f9c906b7c38f987e27", + "source_hash": "960ca430db3b7366fdd9843040ce1b10c2412b56e085e3529556bec4f147a831", "target_hash": "5633a60d423f0ea529d270601bb68429042dfd7141b412a3103ab0f47dcb0c3e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -72528,852 +24806,332 @@ "pl": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "f98c7c431d25642ca8a14854b6430b8019f6a93fe1b197d6132c74340e72e693", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "ed42e190d61baa55186ae30f69862a6950766403c4940897a5e85ce9467e836f", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "d00c75d178a4e3f48dab229d38c6ffe0bd3d2e4f3a23ed28c6becd92f3726084", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "e0a32e332f48c60438ed7d94f5e9c3237963eb3bec6411005328547ba5a4f394", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "74018189102a56c591d8016f5c811be99c8d8173d0b3b05f5b1904c91f9c0b06", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "d87e4058732fa8ea91414ae81f14f2083334399a43486fd164d084e2e3cc1116", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "6fb9e9eee749c683c7f08ca63096ca0764fc5bca6520a70891ed3e568cdd0044", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "7a5d2fd41e33505fdf97509356ec8ab3e4914bc6269870b2aeab26d34a201427", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "16165a2209d33f506ac2f2b81b28de8a668b2f65a3acc1abe3121745f8934301", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "319892d163abfa9dd5fdc2500789aa924bd622f9f9aa6b9671a02af5fa49eab5", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "298585a1898beb57def3bf8efb436df4d3fb6987e195cce8c8ac42d5084f7adc", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "f243617405d97e2f2b8417cf75380fccac2c860fa9b49ce2ab4f1779119c6109", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "15a5bb6fbfbcc4185940df90e5044556cba4948b868dc9f85b976797cbbb5930", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "c23a6613483169aad1fd91fab69c9b397c747929313cf03a9eaa794548bf48c7", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "93ca08ef8ae0e5740920fa0c897c8bad4e51da4d7982d44963eafab5921fcb6e", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "c0fef7e81364700ed49b850ce6774d5e7e088594ecf8379ca9e83e71f9d0343d", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "524aefde9dd530effccfefb125fd0eb2e8fa08f819ee9bec0176fc2be333a2ac", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "8d5842b55f61556c7cf43bed3bdee5aac33d0e1b64146de470185eb5a463f0ce", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "360b7afc8868480fae5e55667775f0e8bc177fe3c6256aeb49c2e0dc112bad31", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "3d887eed1e16c5055bbc3062e0be127ded20923210bccc9f85840b3cf5c84c75", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "7e65185719806f33cebc5f2fb4b3267a4e0b694f14c40d76e714abe8f5be1d54", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "83334d23ec691ab66dec78fd9c1eab2375e1efbdb74d6b1932a114b50d9213bf", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "d92fe9db37205eac46625d215f33c4918c561985affab26a5eb0a0a7bc4789e5", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "9b6f80730e9fa0af50815ab1e93ae3b5b1b72b6ae0e4a515524a0da63acc1c70", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "3f5ef652996e20bae3e03f07fa3626a949263389d614455afa1f70f43a0766be", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "3fb768e374641b11c61c66d535b06c7f7cdccefd0b1f23e6d68733e6df712408", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "381282ec6c72e6927587bcbde1d7b250cf0f7b98f15de073ce5efdc49586b544", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "c37a2d1c179198c2eed94be1b99b5d01ec93173ed7b0682751697c0c5f278ecd", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "2f3079a80b67af971b79905d9faf2bd69e0cf8fb4b8907190bd9c01baaadaf7f", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "0d9c3f987cf2ba41f9a4327bffb923ebb1dfd1b983f028fa1cc10cc3d5e477d4", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "424aeffa0311dfc09e9215a67f251ebd0f021f3d0b8fef64fb2cb1be2589a47e", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "19c0fe90580dd7e85ae59ca10aacb69f3308f0060fe560fa315c88ed85b85da5", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "cab2a9a7d7b5500d8016f0199fe51a8cb7a4f119a57816d9d6aa2f9f61c398ee", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "05848246365470ec30e93246aa48f6f41cb81c62827349f7caf76b7527223449", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "c8577ec1174dea8839fd1c912e9a6bba56dfc94ea41fd75d67fefb55e6bd8fdc", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "4dabf294d781ffb2d1cc92901c263987dc03fa86b5bb3669904f369d0c6cc3a2", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "b683f1af53702a812bfad039049071143388bf95568564171d76e7cc6a2eb264", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "b7d27c918ca9fc53ab06a5b72c676c5bf30664627a0b1b441d2302a7bebe28bf", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "c0a2b653dfab9b1e81d35e803aeb6f6ba0c269bb49c14309755a274c7c8f64c7", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "a0d499c0b05ff5db1811737a6fd1785a2df68e94bb0a710fde62bde90ae68c73", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "9b725228961634de3acf6f94609cc0bb3045c2af6187bcfa0c7ffa2b3e72b8a0", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "e3f89807e1cc9dfb586f893e3aa4f42750512b80e14554dc0e9ea19ecb5a2e13", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "4bec7c1280a03ed8e8b3c19e7c961224b05109fa9b1b50bec2b15650b0f9aa68", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "a461bcc8f97cce8cf45b17caea689feb2af9cebced9005025831cba310bcf619", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "e4254a8d42769a4b573be95b9a018721afb8534b686cc5dfc4fc15fd03e90505", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "9dd415916b1639e2346b220ba796d875490bde9634abdc885cdd10c6b3835b28", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "43a29f138bdbf03a7d67cffc7c54ee02ba694c0ffa683d3318c675b0a8f4fb35", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "7891b4c48ea0250ed67dc621c5d38297b72dcd1ea94c940ddfcd17578570be7a", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "e5685b1a432f83add29d3b4722fd2bb20f28548c49ce08e96fb8755b3035d5b0", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "4339beaf3f14a2ab2dc8b323b532f61bd261435096e2ebd51a8fc21bbc575ab0", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "4231307c3c13efc9eb9f5aec049fe25a75f25f02640342cebcdf4a551e542fe0", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "fb21ac041975b7eb8cc14774aeddd36a187c6271344ffc3b94e174358adcd0c4", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "4a6ba0504342deaae9fcacc7f4d7b6c747900ee3dad3d752b89a2937b37ce7df", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "a7bc457b0fc8e2a0f275c2571e56383abbd7b40dae406e0f015d110d656e0388", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "77de0a274d8039d17459fd02941b8d6281dd969c2c38fef7eec2378088b46675", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "b3d098f227d505448b4162efc2dbc4e8221a4f9ab3513977b97c2b7a696193a7", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "e4f9d12c2c30280a6ffbda24909b8d32bf61992f86aba5348d0c4c3dd4a9e8e9", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "3a5e9a59146d8364ac1e528a0d47fe7d90102425ee598b1f72065db2b05c4899", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "7714f8e75372732dfc132cb2d3b4529e1ba07f3fd8ea3fa48d7b2edba0faf4d8", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "98435b36b228ba5e5eed6c7c365c7b3caadcc7e5ef62096601ddaa22b101c019", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "2c36dc82f7d3d9258ba43ce65d17c50e5caaf2b4ef20df2e3e8993dedde7b2ef", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "f8fbdabb4b64533df45f9375eebe2359a065d15b36b5234395d514a74f68f7ed", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "48a26357d84a5f4b6d2cebab2cbd3ede2f59c17007a113cd1615f16a9dd77152", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "c14b44720bcccec4b7bc4573c0941d33386cd6d5bfe281d53dfeb4c8e2b59557", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "aba3b3f05a46095b15a776a97d40657e032493aeacd3cf5aeacfae85e9621c2e", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "b159542019e7188f4ef67bac0e1e16b00ea1842d9090fac72f41ba9f7163826c", "target_hash": "4a644f3972af3856e0ed4e390a0b3eef1e0fa176bf7984124b732f8e87068e80", - "section_hashes": [ - "0fc1199660e3", - "c86ea448d6b7", - "753b81d8fc2c", - "fdc69a2fde81", - "57a3af4b3dd4", - "abe7d026d8d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -73383,1047 +25141,332 @@ "pl": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "668a046b825a253a5e08e71e0ddb42d8c51ab0b7a3f9470b09a6d52d6df26540", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "7429d3c64de4b3aa1deca3fd48b96e9d973b5ea3bebe63c4885c6bb2f6c8912d", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "c131d81595b531ac94724b08a087dbf81a6f92fe02c57a5a3a30988717c928b5", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "69d2b49e7fbfffdb7e699f99b7515a7ef07f3b311adb9117fcb9bfd110f15a77", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "381713d20c2b4e96802087320bffff48345a89ea8b9ed5a20f859bf6c7c7b3b8", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "bc79b32fb1b3e4ce679492feeb9f70f487be82170a236be8345e7011f10f9797", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "e5cebc1235e6a26a1143ac5af76e7e0eb17606a93d1ea1aa9f056a827686a749", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "78c69ed5e164cb8f4ecea1ddcf1b274c52144e2e9395f3eb58284cf3444a9bfc", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "9563ce53f30c26cfb0cdc99368f6e32f05d663394c3a9a8861d7a7447d10f019", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "7663c83e1b7a3065b0f9472aef9ba15b30ff604bb6e5b44f2edd5fdd71ba3f72", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "e23f491885b6a1581718ddf6de1879805ca3d8d7add6003ae62b23cd68ef5831", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "f80a14596132c1700bbe4d8f65edd0e12f6e435f0d9babb28de4d41b0e7edd56", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "94c69a9e0a2d4010ab7af1cbeee7ddcc9c577f630b0a5f95560f3b54245d56b7", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "7c06add5524b0f2d32a1362cce3fa8f2de43ddd8634f614a7b76236cebd06bb4", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "bc0c5aae36b064f46040a61f0ceb25309239afe5e0c49370cb893eb4a734b7ad", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "e453758793c97704c40d4b319dd1c4776a318ce76e6fbe8cf1d0c760c2eceb62", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "0aabb28ac3fe39fa8c41111a6d3751404617d14464b8c6bed378dbcedf9ac7d5", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "97a9ca167b8063a8c7fd3b6c7cbca987be1f268a406902332b7e6b8f1ff05572", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "9fa701623a3cb6891029883d5783fdc0072a74e75253a359c8b34763771bee41", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "c3b20791310d453ad8b977fb3cb468c6417ac1161aabb66f6415d4114ee4aab9", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "9e747c34cf99752697023e3e7762d19f0644d347ecda65d4cdbfeae1476a715f", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "2437437d73079377b70684a8c4cbad2957437ff9bf0afd2828b1ba472dffa391", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "59db2a4a2fb1d041da50eed976f4530968253cd5d809e17694ef4c30045db72a", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "939a3e2541b2775b001b5659093873bb6344826038ddf9f466a40fe74faef52c", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "312c0107f841c02d48fc747c17d6a46a3800ae64b013e1640f5b719426f2a10c", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "67103abc6fa9c31f776ac7b1a322a96ba77fdcf3c6c696bdba1530d896766454", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "e63bf33925a7480c912c0d7ceddfacb051c432b71ab13705733c3e11aecf4666", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "ba100a05c3ed490d30cc42b9fd0d817791e9299213504afd07da7bef81d34bc4", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "9f08dd5dd878802b540e09459d79067a9a120d30bd3a81d86709efa5f3bac0bd", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "5e31b8fe1e37ff879103cf8bae3940a948b9cf1e4b3851230806bdc249fee5de", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "58baf9a4a2f7f3e112d622fed275579c5f2aa5e0311dff5f5e144b3537455140", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "bbd08b26a81d24883aafe17aaf0728856ab4d2de684290bb18cca9b845477109", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "d9a2d6b999bbbb3d17af512a52cad2729d8156e2028a9843d568fdff00d3b9b3", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "6736a1c33ef7a60b87d05d219a81414df18a68730a6321f370860776948dd778", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "b525a85a30eed55ab638d020f1984a305b3d496360a703adf55d9555b2dfb839", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "0f4a87d6da8a95f91e79aa3cf1d1822953eb0bece0a6e2481ba2a66ed292c148", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "95005adbdbb94cadeed0497f58bcdd884290a376e318442132ef2b9239ce7eed", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "576ce89b926b287b58dcf236c35f118826994d8149ffc4863858f4f08b4949e6", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "d0e5d876179d4bc54bf930d47186a6cab8c9de309215daf46351efd79c6c6e11", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "737c48d8aee4a97d2bf6ed9a4fba95321f3ac6b6e369275d3fb5fb671bb9682c", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "cedda691eb24e356aa43c9a6ba62ccd48495c71d62569123cca9dfcdf03c347f", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "540fb0f09cff8c56035b4a0082bfd15b84080f539caf6ccb47ddc0c686b70449", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "e36e33a6485ce2daa19dd4d7588517084521087bbe0a4fc5c3f41b7e55dfb4e1", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "35074cbe5ba66b4e66d07d9cb0cb09e2f380833bc515b73d55b6ac6f274312e5", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "85816ba77f44c35f740e6895269dfb67351e50a83ee6b1dbc4baec5475043433", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "5df77acac4365d81fa3c942b020173afbc50e026a0be111fcba0ddfa6368beac", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "f2c3b0fa6f23247a0b3a1c395b274c018c1c4510c58bff88a70ea54aa1d38a27", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "77e29ed335ac9f74fb148fc31d3b853b980783cbc4b5d157325f672f1c9ca8fc", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "2df6b68ef6370073a3baae502dc468b165b704b807f9474940c72f932d153660", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "f2b84e04c573201f9847a1e0af6d44f46e72999c23a5bb989f09896c0a089fdf", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "0a3c098bf82c258457f3b5acbbd8de77748b0fee0843d9427f1453ff89c4857b", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "fe11b02835611868a304f78e6453b230b4ca0369577347a65edf43922774589b", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "cec0f37a3e450124c9bbcc7769e331098f83bb1e687bfb5782f75ef21290100b", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "60abee042277ad4092881cd3351bcf2b2bea8d6369addcb9b0f3180fa19450dd", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "92319b2981801536ff8afcf890475a7c6fbf463a15ad8f945b704bb79cc9cd74", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "93f47975b57951cbc0738e24a1c0c8b128a281dd48aad33dc5b24cf9bbb38950", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "8867f346776aff60eacaea6f9e19e571a39069dc8126836ff932cd553f7fbd98", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "005fc3db4ef4fee3bba27d1ec23cc7e3e41c4385a04b29aa320e0752fd8be3a3", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "a194ae74a9a69751bf9ff03eaca02d0df7aa8b8fe5fc4396c30ae252adbdcb9b", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "a28c383bfe66739d55046d9706388ef87f26405dbbc81d19c9a45e789a8d6531", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "bb7511a9c342772e396498f51b5df65172993167f48ab219f693fe63c5b0d863", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "5432702ccd0415a840a248559fba779f322fad3a4e9e17a59a17610197747259", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "c3d18869782355f368ec608370384534c9def37e79157acff5ade44d7c9078ee", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "4bf835c3bb4f1bdd4f457377d69b1357fd5b0f2c89a0903570edab7fd83fa280", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "f90dc744e2cdc3a49cfb7a6e71a177df14e689f3022c9efa44eb3ea7f63b602f", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "65285718640286f05af499143cea817a9ba75c0f436d54de030f7b2c2f9c3cf5", "target_hash": "34326981c90a61f89a137bd626c6aa5c6b0904c2d22f683ca93e74c07bbc891c", - "section_hashes": [ - "432363e9b040", - "7f92760a68c2", - "a5b7d5f99cc6", - "12efd5909a82", - "f830d28fa182", - "6982dd5f8026", - "d19d2303fe8f", - "2c5b8f9dea93", - "6d6f825feca2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -74433,926 +25476,332 @@ "ar": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "f89cd67e2fe9f3ff2220c57c67758a253165f01e5d9980cd9b4c06dd05bef78e", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "da47be7701ab3d197e674a3d8f7b6ae7a281fcdbfcc184681569d70730936625", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "42d7fcb98412750d2af12394db31e3b1905998c270f5b2c56b684a0c5aec3d12", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "336107655afbd24c0ded16802a6f12f6110b95f3d82006de49c0f218fad19bda", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "704c1d203c3474a3cef2c9a3339b16343d36d2aaf29f8fc67631bbd749120372", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "5d17fee25b4e4ff2d178f85a218da956694b030c0a66f192f3e8e087c8d4e160", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "ac242de147d255cd12c524a8c039c22dc50891e6993068bc2f8d2f6acd5bad39", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "e90c4cd12e102e62255091f69f796a8ac78b63c251f3eba558f3184856a72c2e", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "6787269fd6c193bd697009201556bcdfe3a5023e8337a76ea736b5e35b769afb", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "298ebc5932b45d28a7f954cf5034493cf148bcaa7f8921a475d3de66592f0069", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "353a2686decad2cb468db8524fd242423c6b76fd20c16de259e01ba9cdc5b593", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "a23e92e62da5921db997b552eead2128cc710a8770ee7867870ff0809c142a39", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "d28c117729e2a302fa0182052947689b5639bd4917a10c1863e3b84067e3b71d", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "b400a767c908bf4c460561cd9373de936a20213e8d762e4a472d41392ca818ff", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "b3907b3233d49acbdba1f1ebc861f8e5fc022f199a7c2229311f9c9fd3408c68", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "236e5d2bbbad930d24b3df03c50ce0295942a456c3c5d461c09c5eff22a9892a", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "d1536048fec7a63aade3a2c61bae8ba789ca387acaba0fec2b3d5c4dab4137df", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "cd704eff2d99cece93d08ccab349bab87d9206942c61de88e5feee66d39c23c4", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "0ef3beb354e432df785dc2b173d02304b36e7e09059726ab356a3e8f67b43abd", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "7da6c13c7ec0067e42c5cd1c9015392f05282fb784e1c597fc0470bc566ef337", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "0ed60fb730f10273425313dfe19a084fe810ed173e489cb3ddf90de97f5ee193", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "0909de755e96656b536e4f012e257ecfbc62fe2392e091611e9aa57974e9315b", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "6a231000fa989c97c4cc29ec28cccab053c94f604fd4413ccaa20d51101b0abd", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "a9fb38c22a759e6ea5484b266eddf52359ab04673ee879fce0f3567af97a528d", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "7e3b7ca93438164c992ba04bc2189fa76be187287f310b65d206c345b49cfe34", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "f785f82dc36932558b9748535f9f6ece33f51afea4eff159ae6b516342633898", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "3bca92cb1ae6f6f87bbcd89293205138ce2257e235b32e3f28e6c6463d2b92cc", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "06a3585619510466de2fa0de9e0458e828ec0002f42bb63237007ee5b5ebbf88", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "84c0be66d1c6d537bb7a4e1d9aa5bb1cd9b1fc31f41f1a81916d3d7d5205bebc", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "af7e17799a870b6276d9cf35625215654ff5546b7c4147cb66e3e71592c81797", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "250c587b73a68bbd2ffb31e8dde1b48de206e385f818921d54259b2d9a60cb5d", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "d48eee37cdc7ffdc3838ba21d13aa9e654f31dc8200138ba26b6c191f3b19283", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "7a70744ab63bdab6ee946261c9c2e2caee3f1b80d00bea9874f48991089d68d7", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "38dcfa75f5768b6b11a894f124a9f6705f8b31c9f3b469fb2d866c562a88ff1d", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "0aa310e83b9dd1b37eb38e3af8b5b2a793a1ca11180ed3d7cd187d61f10f7bea", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "cc33ff0774d64a9fad69f3469219501e92b7a328bae3f72a6a086e2d474eac78", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "f72de06b83cb522e099044b56687822870509d2318536a8bad523c0146926db7", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "d8307a13a5860c7039db54ad7f93c779f00851d8ea76bfba52727ecaf98b3d6d", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "c1fa08f94ce6d50bc8443179b43dc2990764d537797c5032d488374170334156", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "70a92a819b928cb3434dd2c2b8d870a602ed96d7277f0878b7d0a58cc212b8bf", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "c6072a1512423fb3d8c29a206082d52c7fbc9533faca78a6a414e3c1d4f17f2e", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "c7cf2257eb78f47a5ba789e0d5e10ee8785461f361cf133f55124ee651edd44c", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "677d7c88532b54da2b5b353eade69c34a07aa8a82242f2b12de92b5f8d22e17e", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "bcb61334e5edac1fcc980f1e76995edee58230bd79fb7ad6c3d508550c16e021", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "a13c08fe15d4de8d8f4d0a68a712bd45a2209192e30b7376849a74d86254e7b0", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "6abb7cd7d9e46d2ab8dc86da3b0db713f568706c208c8626f6c9b98842a39671", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "f7ef9fc68c22a5bdddc2e07700c9d7bfa72875b60762983833ddb44ad4b3aa4b", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "d431c40034374116fe38dab35e9bf30ee0c74b50f3a2295d45e61c854583962d", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "5786304e2832e464e37a8c97999e5df0b54b02c1fe3b028e1f2fa69a01f02eb7", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "94e0ed0b3cd6d885471b1bd31859f3634a63c064f734368fa08679097b144813", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "a5e55f479f780aba4526b4f72944f3a2fe3fddd47a92a0d3ab471812c4796e34", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "b33909e5167e763cdf40b8baa41105aa4e7f3c3005316fd4a6ea513cf7ff2379", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "388bdee708591b4dfd48022343a234309e2f5340560de1a0736d13b0c2a55c85", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "50470de41cd9282a7ac336dea8ec3cc1519e68ab7c1318928dae338a728ccd51", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "f3f1300076f3cd57c3a500d0878722c4f76fe50f748b60d5fac7e9429c7ec483", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "4faed2bc15091e90f584ef397afe979d5728ef9737388c815686dde5cc46899c", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "50842ba84e517857505c9768cdd2a18f84d4c9ef2f8385dd93730452d8c2d69a", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "d3fd9b3a7917329d60c4e5da02f654a9535edf656eab51de7662ce9e7218aa80", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "4ab5b1b0153b3f423bd5d679e262b016a1cf48ecd655d3c10bcdc8fee9a994f3", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "ac4e43ee264f0bb09735ef15ab2cc9bd1f092f20908bdc408b10a6b34ad6c9fe", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "8b35d86f9541d353a28eef80bd17676225ef76c19f9bd87716405853f11f0001", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "bc47b85de75e4894f2390f202418d6a45fdb0e1b5b20d70a04f9203decac8081", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "6fc1fb9ba78390e16932ca7f14db24a207bf2c10208f39f1babfcc0b06237e88", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "d10bd925a9dd05a856a5d4157542c906571eab52d2e879b4b679c9789055955b", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "96bc7a4ae89f9fe5950e633770384563e18effddcb1fa46e55588641eab1c7e9", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "ce38882aad0d55fe18aeaa25226cf44b18b91f9ebb1c495e0e8b091041568798", "target_hash": "314ff5f12a1f8d4bcc70066c8078fe936a08dab3d5a150b9eda9437ac6977498", - "section_hashes": [ - "6608cc1dbf5d", - "a73762759f59", - "6e8297582471", - "da903ac26392", - "2acf770bc9d9", - "53c2d848a2b9", - "534644c91fa2" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -75362,1124 +25811,332 @@ "pl": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "4df358e470b4c899be076ad01b3cd3887ac77ad92e7f18d2d94a6540fcdf4cb6", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "39992abe23e994840ecf8cb3db02f13b2ae4aedf2d8d3e0540fa712affbb0d40", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "794a49adee71dc2ddfbb61481230a457f80272971c18ae6f22f45a9ba3b3bb0e", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "839a68b14c6ab70a3567741f4e7e0183732060cd62a4569e46d3f24d2de0ef6e", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "77721cddca8152919ff7b314d20a8d8b50deff8bcb67e2093a72e290ffb17819", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "170e78b5f4cf032b2d0606b93c3918835f7add5413405dfa8d83fd1273b54dd3", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "14cb341620df2980df1a35aecf18c561de57c53c939f1839864b31f716c37149", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "90be21dde64b5428d996944d8499a449ff6d37d61522b3b75486fe2439d712d4", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "b7318670beef7dd19b9928695e11c8e8aad070fb8946851e7616c793588546bb", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "5050ffff106fbf5062f7420064a6b3c56f622fda5c1062715b2c1386f28f0acd", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "355b8af91cce130755f1e6f34c0d5655cdce9b0ab81dc92edfe3bc49a3b4cfd9", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "f37189d8569b72a135c4917b446860e40e6e54363d812cade193b13bfe3252b1", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "73cbe00bbdf80bb22e70d6f50c5cf67afbc2fd0ecab9243627f3108d9e475764", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "87e296b0c6ccd27dbd1d0ae18c6716d3674af6a276f393bcd7b408d7f8c0573b", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "3623ce54bc24b24d5558970f06deb496567d87e20203829d3d6058cf8f9635d0", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "39c642f8e11536f766f0e7a86ed158f69b3d3bdc96f025c27759dffa80fce555", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "12165a14b0008493ec8b385b49073182fd2653650f0b814e930020adae7705d1", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "9c1666ca635eefd8bed4d21b5a74d07ebdd8b51dcf8eb2cd8a20f635d870d381", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "1f46966bae4d644373e6854005797bd29a3b9d77c054c0e92c4788e729f3af99", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "5bad19554baad3cecf692b372c5806736356c89549f782dc3f5620255bc176d9", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "2c2928e2f490918182c8aedba8de9cd1df9e1bb41737e04671f80538f2cb5b93", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "cd7008754776822037c8bd0a86f22d95e16202e13c2841969d8a84d41eaa4036", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "5c8cf02703f8bd9c71492609e06ea777a8e58143389a95155a017f8723c5ff3a", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "5f24f4e2d285c7d900012ab7d94532c45319a0aea2e6960080c5e71d0983aac0", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "c58fa12815b53e802d156494a7850aa6a65b1b790dc27825112bc1a9f65e7ada", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "640fb76e0edd7133c30dd025dcc4af5857b144d010f0cf444c39dbac7f6b0508", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "e8806eb329fd700a34b6f463d9bd9adc4bf951b3d60cffc7d36288a6f57ab546", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "b460e6d04b12cf236bb4f1bc6040b584031b197fd32b21ab0fb5accdb3d63dbe", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "5d9e0a3f6bb4a2a120d9506bf3940943588a786cc05d0171dff429cd5da99aa4", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "539ce78159fa93c8a6b7448f7529057a6637adc9c9daf813dea109d696e80429", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "6b70eff3cb895aa8691be0ec2027c7390e369f7ed7c776c8e3341ae37a00cfda", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "54863f1609936e39f6943bf7a88b7476cd2bc8a2b7e7b92f2ef0ef3ad6a9ea68", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "28d32006ad95b4654c69206d7422738308766d0d00c07ca29a171370a00b057d", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "6c7a9295e8427af4504dffcb561ac5cb9d5e90279740dc408b0dcae388b758a5", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "10bcce27b9d8408b3dfe674fd7d67466cb57638d28c13e5510774c9481f1ea75", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "fa6dea87817b374b1504e1e55de8e23178e228a56eab2636d1c58cd12cb9be94", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "88eb8490b8f53deda13b5226313a49a8528ca1da73586c363d278ab60f0f4901", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "1bc09378bf020786bfa6b5b242bfda539923043d48c3fedcbc1abd4a2d300769", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "c52d0f870ccffb09739f217837ee19ed2e5fbe2bbe772448739bf96ee54c7fb8", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "11d7f5834165270dfeeec08488ebaf33a8ad1f6789ca79247f35b21b5685b796", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "4db0cdbd0b566efa0a2e2438b4770859432e6bf2243e124df7b6a76a2eb1143a", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "ce4ea81bf8337b7fd98764365850b1c0b22c556b11cbbe174d0d2a454e689dbb", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "f611c34ef497c47dabfc3085e44c2141e6a2843fedadcdffe8cc1e54d1c16b4c", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "d3d546fe09e4296b926087de7c392a13082e0534014fe4a219226d6c85a2b5c4", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "d3129a0ec641ffdba1b6d0469489f62c23b117cf62b13ec55d6da6cef9356e14", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "a57900d6b8fd6a22ff5571ee3ee18f017bba3757ac1692e136d3ad40a0eae29a", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "4dba98766820c7eda89b7438861ed530e54ec4f56829f707d30b79a238abd218", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "16d46ac496d664513c5097a61164cc7d6d4cf3398abf3d4cb5857a145c103638", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "dcebe344ac928d314f5c4929df6243cd36aba88bb4450d38eea9fe9ae7a544cd", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "12336e7db4c1dcbff5f216b9ccc70b964dc9ab90857e9379de053e548447e926", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "efd1539eb17f336f834ac89c281912c86edfc9da8c3651783407e8068d2f618f", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "832c7924989aa972d5a6bb6be33c685f1627822bb6c71a08477e5468ead1a10e", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "7772cb75f10b8199d4b383e2a0605672ee056e9d857741577d120612883b1179", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "12a764a35daf0c5e5f13ad5f488153bc158da77acaaecc99265aa222731bfd50", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "de79ac2146db66d3a375cde9a6a79667c106e6b685ffdfe7cc9dbc92d303b2c7", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "1ae75a20c0aac916e87db9a214a75b7e8375872ace33327e19c236e674269895", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "88bdcdb243c62e06139f273f5371823e4aa28c58433602dfc88d69b03fc6428b", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "57a1b15b94572633beae52c6db5bb21e79be37460c1a24d0ec48b007898afbbf", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "ed65693c8ef0549e6347b54e66dd12e83af33800ea8db367caefd15ded68b962", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "f45b8faf3b589dcda5ca4c2994355b909fcf9fc012f61b2404b0fcaa248a654f", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "703d7e18ebd2c97b59916dbea0e66cc52f3b014ba3ad0c406270ae6c7d871f0f", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "9f6e972523886f5c046654afb44c5dce3b4a79c120b83727b78d67e8dfeadc39", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "175710add03c30ce2bc18dbbd831d31fced940a2d1995ce3aa63d73afcf2d718", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "af4d1c1023cca982d726f59726dcc38563015ed012eff25249dfa2458e00886e", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "659b1c602666894ef1dc83463922db5fb7af6ef37796d4437cab00d00ee3c168", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "94813fd24e7e84d0e685b4dbdc2c4efce6ea3202d6e5dfa6fc529ff5da665d28", "target_hash": "0a210e9d2cc462d263f10a6486ee76eb2074bfb34180c2c44e05fcec9d4fa5c3", - "section_hashes": [ - "1701f969456b", - "fa02915fa827", - "cd58009ed0a4", - "ae858e8f46eb", - "ae74a7324650", - "0594b622821a", - "f7404a2c60e3", - "bd54d587a94f", - "673f0d2dcc2a", - "13df09b1e734" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -76489,1256 +26146,332 @@ "pl": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "2aac8aa7f266f1f493672f6115c5b53876b7847c91c06790b528f6ac554189a7", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "4e771dcd29fec07c59c349bf8820b667175a7e320b1f1349f8fec4b8260ba1f9", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "9035b957eb3c73921f55a1ef38133d03d1ee4d8826d980857b229f1294b4f3a6", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "dea6b53735e3452b33f40fc0827de12369bf33cfce4ff3c09d17fe3cc1cadd77", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "3541ac0a5392cde02d5a5e22cf07f2b0f3049bec782efed6ddf3f7a75dbe0589", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "e021ca31ae537004c1bba8ba32d842f856b91b7f54d0dcccf99df3c47a1cb11f", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "47fffcf2137944160f90229cf0927bdc7d2fd8700f32ef58bed3d7980225098f", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "a682e9ded85f3aace4e2d6bd5f95150cd88c553742bf9c0fdc31a303c353e1dd", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "20eabf8564c87bdc46ca2bcaa9142e11a1d23edfd1a140904d78682758e8c938", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "38bb60c8fa40012ff06b236d9a6cd55b1ff102c36ff56a79ed3299347088e61b", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "496db68f36817a26a13dd9d859737476cee9a1d1e74d05b16dea4df1f60f8bc5", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "c4f169278c71543228947b246950da6692518ec9d54db95f32c20c0aee98a2e8", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "78aae316fabde7ccd791a447e59d5868c16b65ac58c574c253bb6afe1511671f", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "3189282d393e7f9318456aa0f840f2c4b944e0dbcf6e9f9fbda43ba65fab2f23", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "00a57359e71f283fc371cba16fea1c362dfb6ae9860593d68f5e617492ad72fd", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "8a27b30adbb293a5220498fc877e22ce571c09521a3b571d7b6f81a282b8ae26", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "aa256b4bb29a32f974fc2dd9a5b6d7e6bf8238b251c6f84538831ecab7e59e69", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "92c9fafcdc57be3b82e3b9f7c7a94af5d7e737a4f12af8481d28006869815464", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "8a62354a76f34df78384831f70e00c34b1fa36f60451e0abbf3025523100e514", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "2a7711d69489e0b733b730dc820b83ef5031d9bf492b0ee0045c7106681629bd", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "891854ffcf8ebc909a9bb76a47caaf6ce2c61eaaf87bd2075c5f41c21edd4f54", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "9e16655d1565456c9e605a8c8bdddcef5a25806f93882abba36e23a6d7c0eaa9", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "b4997dd0212a580109000447bfd48fc5683b1c4d14270b9b1989c6f2fb638a32", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "fe5bc4d5eaafef85c1e993cc7bcd7bc925962149c2ce3eb789bd40b9a1900566", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "6418f8abb9fb2fffea2692affa1e17e7173bab9bb3fefd76c9c7acb3d8552d75", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "15c9ece3565cd6fa7e694b15c37db274dc6b64891537fe20c04bf1fa150c0238", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "0918fe7ddf0c6a586264513e087e1f973f0fb2d5f234298bb806bba34bcfea7c", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "cb93f446d8117adc3e94327df52b9bf25075879e01f77881511ca8efb7955148", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "482d716fe86d857b3dd318ccab15250570ecfd772b7d268da46c8208eaf05c97", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "2fe47fc300af55dfa6e076f70fd4b6c445c075c5852b089d15832d6e490a94fa", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "47ab8d246d59c1425cbecb2160ee4b256a74c24f33a7598a5b419b3af9f864fb", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "673f9728b6c66749f2021e2efe5feee4c41d5930bc1df5caf78b737612894abd", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "5e5cbde132e47cb36687a8451d5940d7b7cd1232ddb9fc41b6f1ef117b93c729", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "8b0b5b869573f14299cbe992232e6bf978bd56ceb6207c41ed89028fcd1bd43a", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "fae973dcfc24db89547cec09aba632ffffc837797a65977c8898e330f2d6615a", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "6fabdc410aa65c90e7a0217a4366a38f4ae077cd620ff934a202000c6ed584c9", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "94017d24afac33b8856d4fca4495f740d9bae0e27b4da14359b197d2347c6fd3", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "84d4ea13b31dfbfa2f9fdec643285b711ed8b5b5c63393f6f984708b12d0d6cc", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "3a253d0e303dfbb265219cca6f0c7911663ebf2cad1f13fc63af6ae931210ce2", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "84143a82a6247d16679bfe126ad28f317fb3eff0166dc08680916921ee9dec47", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "8dd979a6461fc5aa9080ec6c8b3c1263f7538ed947d00224993537f697bc5a97", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "bc9cd8d327a6ca53b1f1f9d807474c98b180a0742aa73a84a652297c453bd106", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "407ff5e1deac999ed035150a2f35657b16e563aa024b2abcc734a0e99d7ade3b", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "9586249c3049ef8dc956161d0ef4dae6d1947fffbc67ba4a94216a2bd6c94579", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "d241961a03f8f8fec7fbb0dbe3774bdd62dcd2c2add0c42206a627c3ff396756", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "b1336c436595f3121bdfc621298799541217aabeb2f93937ae82be0b7c5ac058", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "748b80841f5d188684eea7075422d807e504f5642631418f536d8fae054d991f", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "25fff07f4f0141a8922466c6b15373734728b1682a4de21aafc95ed361f0f539", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "441795912bda262f06481b8e815729a648b592d5748a33603445747031af426f", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "78102e504b07351d9ae48250b8c2bdbdbf361a796bc4452ef366377fdbad79d2", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "f9adcce498a2ce43c971b4fa93d4a7a1991deecd4c42756f0f5a50092de04f88", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "2fbcd8b74dcfa63a12445047d6d5ec51b09062e0e1c19f85effb35b4d88e4955", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "8a76d08d2fd36c3803e5ecc12ff0205bdeeb2465d5ed93383bfaf800aa24cafc", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "7fc2642204f095c14233b0828cc8659acff2c7b500fee921812737acb4a1dd04", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "f528f94e82c8a2129db844feab2b914a5474f1cc3d8ba7d0559bf9329d3f4a33", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "15fec4a0ad99d6561581c7865393506fb19d296efc83751b910b750813416c38", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "150b33ec20e95bee7eca24ea2b98543cb4519c1cec6a23dc9c26e8d64221926e", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "856722a7dd32f92c8e4146e8ff0990ce4ff89ea41fb48963586190e2a295c92e", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "5111a7159359b3be7afd9c09c9689a9acffc511157fba2f3d70b90c23089cf98", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "3924c60108edc1aad65268cc394be61e98b7ad58b0201a94e1a2f264e15f4762", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "eb0cd802d0d1bf15dd05c29a3542e8409a79e3c6a13c8b4c1719674458ffae33", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "eacf3b8dc280da678f733e87940dfee289f50dc58460868c176fb6c2c43c25ad", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "434c50d3e70dbc0d44a351e527c1c244a94e815c307f8d1b4395d1422a0bd722", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "84c6e7171db172a5bd1a99b1f893f94e54cedea0afedcfe8c3df205f8dbec830", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "2c99eb8bcba9ff2ee8dd3c7132eb0b3fba41c2dfc7111295d4a62ecc1b07b54f", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "f2bca7d3a9cfc1233e7345a8b7f5cb757abc86a3eb33bea2e618fb5a12432fc1", "target_hash": "a372ce83568125a262bda06ea326a67561c18b46d811e442698b89f87b30c5ff", - "section_hashes": [ - "1b6428e7d292", - "a23f911371da", - "33916837e705", - "26958a3200b8", - "02ca26cd7235", - "89b93f2f82d8", - "61913281a1e5", - "af2e52ff4045", - "ede4b8b36b12", - "cc23ecaaec37", - "d97d7be0b4da", - "6f7a09c85a11" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -77748,1047 +26481,332 @@ "pl": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "08bb73bc03ff3caab4013c63d113cdd175b2ed60e1573eccd262ac5f5d08a650", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "11e16b7ead0c30bc19a894d3d7ffca73d7ff01e5ddc156a8ecc92c84f05be615", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "2cba9e496534363902050add807248d4714b739291a9265165c388bbc5454432", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "30028a3ef3fdb690b33a7e98f26b236b5d2fa546d8a9d84240f84c60e658231d", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "15d1a0683d2f894f11a6c9dbed8668375a6f865d909c05aa647683b5f6c76142", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "5a32aefd7c65556f6ad221d11f04e9b32493dbb09108cbf8c306f5dbc84c014e", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "6fccc2bd35687e0fa51531c32d3d62a2082836ca5258995f07003ba338d92d6c", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "3f3053aff2e124aad8e46690bd136a2d64508ea8a8a393e0ddc02ca0c6367360", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "846800b3c8e6d89955dc1c9c3b70e8d9ca9ff3305bf9c8197b59cf87964aabc1", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "58397da08ba19a51f59de19f8a7c5cead3488287332fcf080ad930737d967bc6", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "a74ef8bd28009f16c6e57f9096a37872a28084c35ed1d7eeeb61b1ead712229d", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "c13f1c2f4cfe84ba83f197d54e53b9c4543d04cf975c1cd47620bf8f2e297f40", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "c9e6a110f0e07fd28cd25fe0608cd66be9d3cb5dd92750bf2aa598db7919b8a9", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "5a188cbdeea4d520e176a2b505c07c2f8f55c3b96487d29b8992f2b4ec57b175", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "a65b8928ab7375259d7c509e3afdb041194cb2ae4ba80629df2bd20486a3af1f", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "c3dff2942379a46d28ad4dd2e2f1c8019ef524fe376222ce1c5015ff01897bc5", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "9ac8b487ed19a78404934812635801d1ab65e36242237e4748fac4e66ad6ce48", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "ef18295da3aaa2d1c673ec7658d4ad212b2cbe7a5b45ce0b5d58f9bfe36e8b13", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "4cc42406ea684e40259f2e6aca062e58464342257b93e262c88114e573a3d774", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "22d8b770172cdd1dd6e27fd967d5092aee040085bc5b5672d6c3fca2aeebfe93", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "83d2470302a18d8e3f74cd154f99a62b6e540fed5bfab5fc99c70a7f18716cac", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "d5da9b0c55b5fd99dc89b259589d865f5448da989482f892c7240188ae67d29c", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "aa442dc47288de78b526910ed0023fb17ca87757ef7b10d31e3dc256f46c14f7", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "320197b5887ec828e660ea0f2903a5705f22912c0b16e667e5137434d25afa5f", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "fa027c9f660bcb0a01c04dbddc98a9f3c8a5dd2e86ff906a21e0eaf297aec116", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "b7fa43db836259f44f79aaee4bef1b645c0a28f66bdc6855685d9630d6e85a3c", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "d9af324d79410026fa8a1adfb7517fa07a7710feff426b26aa59d8d2ba86a2fa", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "0dd2578e484bb861122055ba27a12e2bbba63171efa340d1ff7793b0afbaff17", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "dac93ab03e0adfbbe6c974523d28aa47b9e43ea9ccb1fb1bc0e097a07a6257aa", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "a333262be5ffe0ce301d13dc8f177a0eb9582cddd58d95648bcfab3fc9d89480", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "cfb28e18d42cc901003f431d0333fd389170eae047930f5ba8a646608a7c2abd", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "94ecd972625196a2ae5b7d0af9698e886b8755263fed668f96e95af39abe0274", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "1a96ae8a47f65110b8ad885d947fcf415c5e8960dcc131b2afea47d753c6d44f", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "8e81dc8830e96637a8387b75452683912c5bf6e96454b4b176db40808227c59d", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "551ae1bdae7d299b056beb2b5b0be694a33be8126e3ac14da79ea68cca39c6b0", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "edd821b4904cd645510518fab36a6a9e6a31acaeeb11775e30a86be386858c0f", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "ea5291f170355de3a12660b7061993d0efcadbcfa130226ec2cc91714be89ce1", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "72ec7cf1ad1eccf0fdcbc3bfd6793cc8b42d36bb0c405a4419aa5244cc421c71", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "85a67b07a950ad13fe6ebe7c2046859810935a3fa27e0e05bb42457095fcf040", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "7b68bd7dfd7657c62ec46d3227106b40f31228d9aa10403caf4d721ba05bbb34", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "24941e249b9c11c6748ef977d7e485a4017952215a3e6bcaed3fccd8b5a2a49c", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "7336b19528aaa32b08b3b3b409e2abe8725a718a35dd867d017ffe35716be3ad", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "5a258df8d52dc705c1547bc9a581b0e2fa4b46b1207a39ec7840d39841a1d9e4", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "5a29f98ad2f292244e36613363cd2c40377b0638232cc1202a3a5c73eca9dee8", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "9c7688bb2fe5664d63ba27f95df2b44b0133925f8f7079efb2c85da17a166880", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "875c1301210afdca04d2a06ba8ccde552fdc252e384f6f59b992b0cab82eac8a", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "580506e24d10b174e68e06f192420208eebc6972ca0572e38ce56aa252907b1e", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "b91361453957d02efae4a9c0fbacc4bfa188d061e387bb807993d1f2bc6e8fde", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "91c308c23edc7ac21b88e71d47c07660d00104fdbd32bb50d252f21b2d60b848", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "fa1392e107492a2289d9acc2cc73d85f0eb32a183c0e44d9728789aca3d5daa4", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "2a7e1fc0e9eb5bbbc3303b3405a6e20b607a03da517f8c470d00e9a2066b8c6b", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "ca437194af60c477d106b8d760d4dac296f0d96a1b266b0d91ff70b097d447e4", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "e1736d6e5af93b550dda8f2c21d6192c988fa323a6dd9c1d5f500e83a9c236de", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "6bbea04ac1d58eccac28e131267d9c7a5c8b2fa3fb84e9ca85a43a148feea8d4", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "13fd239b7c4b7049fee912176a89f9593c02bd3cc8e9cc18c7a3c4495f2cfa65", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "452dba34de3e1efe504443d4f2cdf974e0025df542f2295080aa3c0ef6eec994", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "f78f237c0c7aeacc30df4961106fa2bb66451432d4f06ffc85156bd8957fec7b", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "5b78d29288d42279a064d76e82e18a842550190c0e0205362a3d69e1b7664211", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "7e47885d0c5d4faa14d6fa7a78cd0444126358fa5ba49976cade927c73152993", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "f5d74b940ddbefe1e8b5754375e24b41be5bdc75f89e6887d3bf283cdc25986a", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "f6754f6f964d3ae16a10249e41e4f73e0f5f8f9089ef02f1b625c6a7365ba857", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "2ec13d7b507a3a82ce2557f5de13f6b73fcaa7fbf4d9b21e90717557cf601ce5", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "9af0b1e5f2eb62cc651356eff1ffe8c968cac56e0558a5895922e7263e036fa0", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "0547a209d4ccf370ce193f7585fb8ef3ed2240a2434a8e6605b261a25c2a3720", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "1cecc46be308fbbc942b0dcfce3d3fa3b6697b77f792e47d806c3dae1bf0cd61", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "0cfe583891c0196105a321f8763a1df90e8243a6a3b0567c37ef162aca16a129", "target_hash": "8ea322539c29e58d71d774c9a2a7f1877711746098925a7f39afbb5289161f53", - "section_hashes": [ - "b20276c0e9f5", - "5912b80681d8", - "b8eab27e70f7", - "b31f8fa1792e", - "019350e2aaae", - "b9cdf1ed0ca2", - "ea4bb04cbf6c", - "8dae03f7d8eb", - "fcd3b8c89194" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -78798,1047 +26816,332 @@ "pl": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "a54965e9e0b6080d3213effb08b27cf16c62dd2e101e687f36b36777e2ea7dec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "496426661b8fd146fc04fa019c25b144b50ff397c02000fe2cc263208dffcc08", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "896e2a683ef8601d6a5aa411964914fd4a2422062ad0bbc8beba1520a9af5b2d", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "f70970792daf700273dd2a2c191c95b869ac5fdb96ceb3027bd12bc0fd6bd664", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "68188e9a2e1edfddf7b230c297ecd6196cdf295ff9d2cfe917b45c6e28b9a1ac", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "9f54e762e2cf6a39225837565b00451b007bff88bc34398cd41c8f69e0403ef8", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "fc876a911f30005334b37fd7799228c8a22b635f89c1103d6d26de9cd47eb39f", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "bf7c1dc92df48be8fb421cc400b00f6a9d8d6dd6253672bb64bdfdbedd87da2c", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "b1c02d0680a05e675b7fb9e9d31b78b99ec419116f220678a70f7af69a951fc7", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "0e79d920dcfbb02c6743bdeb603fe71c98e2e8cc6869b7650415179aadf0cb4e", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "928fe4584f3c1a4b2a902168a2d950abb76e31aaa814e52cb8e767d7d9576cc9", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "2a8a585cb0c51709a2c9e3204e9a53cc70b6a5162eb6ec354e1c7c328ef974d6", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "0fa13a43d92715e38294dee15374d9d7d01a7c030cbac42a3b19f79cfe5af78d", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "1a479b146d44349956a6a79a16fb5e3917f47b0af0bf510683acef69b230fca4", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "0186e1445b43beaf88383eebf5663dee2a2b430514b0124544400a3174dc3c54", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "7d888e6a92a8f8a1603c28f3eb18614242fb02cc046513ba2b8da29edfe97789", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "a5c169d96326e20f31b9b5e14abae3579277d2e3b522cb5581952332276f7a99", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "0d78d8693a9ee839355f416de6ba0a513cf150d5d722291cbb30252efc6998aa", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "ea15bed45bac6d3722f31d03e3c5feae75683e7d14f3287bca1fb4781a2853d7", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "f366d6c3b4e9be9ebce47e68d004985cf3b75c785b3fdbb91eac4ccc58b469a9", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "40acef65e54aa1ca0ab3f62154924ccac8472f04eed4d02411d141a2d7cc7242", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "2c98e9e2d22b0c5cfeb55ceb59a319d8cc923fac56a72ff42a0d0b1fda6b3844", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "64883aa226a557100e0ec18fb9a00ef8b320a074a40e4fb8cb9e6c0af94e8b55", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "cf6d5d05f8d2e99d2e413f2d3c8a8ba1be8c5305a41b0283ab007d2ff210ca7b", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "4dd559835135d068306d33b1f7d520263e1fb90adff5ebc11445aa3e316e98a4", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "fb0ea87e9d31376f862484c1a5e593779b0eea96897e8c3fe5569cae20ff9320", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "6acf2d5cb67346f92599135086296f7e2b40594ca752293a3b441a929f03e66d", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "1ff735b1621fdc08e40d1a7a02171b4759e1cf513c3fbe9c5eeb2828b921b045", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "225434382a3c41d8d24f0c6ef0c8a0e56247ac1132f86e3107424ccfce725645", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "efaf31c2a84c7330834b71e16d31471c9f90cf1967acb053b87bbee432260c4f", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "80cbcb197c8af117c9e97f001323064fa50b9e2ad57d2f34c8ca5147aef7b117", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "24b2cdd8dd582f817d7495b88921edf571f3438d62846d3803fb45c6d92207cb", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "a44f20d6fc5cae8f9c4d826543067eeea8fbeb28ebbcb6ea1675589c27b73612", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "8a4a4428de9d947a05b288046dfa740e00bd001c2a542161050c627b0717a405", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "ff0785e18e07ff466684f29e616c917e9d2dd187f7b790e3f4377cc203063bcc", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "99664d8106838ba269177ee3380c86973b23f0c4de70c866557884063fa2570a", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "63f9c683fbb8d7d5c44f61bd0a5135be5214525149eb0d7aaf7e5c221ee896cf", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "cf6f09443ef41ab2d49af8a998d7a214f3f9223b9cda1d6a5b9127880608731b", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "46f87afe6643b91c91a72421b43d97eb4d4ece708a46fce16d52155b6076de28", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "c79c805403eaa007851242907d5d6e632cdbbf3e210db29ff57673ac1d302a57", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "8258f4f24bfeb192ea9f0c0ebdb9846fdb68c54d2d4112b1a96e65ef42e11ef5", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "b6706389cf878f134649d92474e492eedfd126120cc1f106e725e0411e0aadf4", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "e56dfc50a499fdfeb37bc9daa738622dbb8e5288ffa4412a70608d0b5d1d59e6", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "4071ce5a6fedd0f7c703bb88dd1e305fe3b4ffa37db66a6aabfe98fb67bccaa5", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "4dd4a3424e9be9f10ab6e4aab32234b33e6ed20e4400d825eafd39223529bdb4", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "3859e2347208495098e4ef0422da7c3078d6d939d1ae98ced66c619dc737c0ef", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "1c96d9402fd5d5e96cecaa09bd33f4eae2348026532c323826cb5cbf605074b9", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "7946c8182cbc95d96075a33b88d6b0aa2b7abe32939030102d6b7e79d153fe6e", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "de090aa1689edcb6998bf69a785e66ff9ec571f657d7eaf61d94d2d9f85f597a", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "c26cb9c59fbf1fc5641acd136a4fc12c4313f0b8b07bb1fb1062db0ad39f1a43", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "d7b8c7083a85c18f9174b7841b65ab2a540d27df1861dcc5bdaa9dc3ae4b09e0", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "ba68401aad93bedd9133c59eb3e17acbecfe377f5e945d70c15940529c17a5e9", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "c9a5715b41f64a15cd5cac4cb3409f58311ba2a91bd9286f33ccd5ba3cd441cd", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "1ee5abb90238eb4ca5e0c96fc81bac059ee6bb732e5ce1e5f7a4af3a456e6442", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "0a64200429882236261c088c09a47ced65c0b34430358eb32056b99505b0e01d", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "5cd82ea1d3df20d8e235f46cfe265c57be24de24273c12e71bc5fdb4514d3b6c", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "80e04b3f6571c39def47c577fa647b0a8d5e9b7859b4b09de0e85f19a07ccd5e", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "02e4822967064ddc4feabf2cc966d51390bfbe3c85990143240c708c2dcfd5a6", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "fa4ac45b95ae21046d1d1f811169028b771815d7650402b8edbb261e82fb7259", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "51599b22f1d1a95f971f0f15a48adb9067d4a15e5d5c96b6a9a5ad7a93f32787", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "c09c3a0a79e109ec731771a67a218d3d4a8813f8a5dc4fce43740181430434bf", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "ae2354ca9a18fe3b7d41ff2ff27466bea54e34a5fdbba5de3dea370bd91f23a8", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "c143f7fe376508c120c6c159410a42725a7e0a5569d5580c39523036f339c6b3", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "654bb40e5a958440094344faac04fc9714ff863bb958fb4528b9fea34029382c", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "db0894e5ab233a3bf6767fa4a1aaa9985f88b3d9aa248c4de7fe9a6d905f5ebc", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "ef75ca1653a5c04d7e611da6ec9ca8c418dee73477bc1bd0d24bffcfbfd51295", "target_hash": "d6ce1fe22fb47e2c8bdd5eafb7da8b0054508dc022b196d550b0ff022d2e448b", - "section_hashes": [ - "3c5a916a63fc", - "0ed2d1cff837", - "65ee99b49676", - "ae52ff4cc02f", - "b706c3db2a53", - "0485c6964cb3", - "b44d3a6c048b", - "08f5de954bfe", - "fdab43c9da71" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -79848,926 +27151,332 @@ "ar": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "7c4a8651490c094fd82ae2a7c53f46fbb40bddd19840dd40a0eeac7a2fd7302b", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "3e5c8c76e174ce3ae93dca58570d92c07fd6b03dac7015a24e5cbfe4c3e1ccd8", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "2c842a1d39b6a57806c57984d3a0e66aad0699894ef3443a84eca01fc27f3d92", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "bbc4520ac97e1062e882219b9edb721894b359d1d0c71cc930b459f5b352ded4", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "4620af509590682de403533f4c041ec4f4acc148b6935400bf2a36bf7855e4a4", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "06868f2f1c5bf803110ed0287509b1e359fe70f98e8e6db111990917ac69b78e", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "cb4c92fd53b8f51320558ed6b3f40e1b496151c37ea6db28ef47cf4428e9c177", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "3d94531fd5c80b3c0ce3e73acb0138d27647a7ec62ea534a2eb4ded37c6e5614", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "12ec0ff2588da83b1860e7c3e76f57e09c64e35311e996f59e01c093ac7a2642", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "011ca542b03737ec3f0886eb3a053d722412be1bb36de06a8b7e18883c8f77e1", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "47b03f15858bce336ca5f67bd70e5950cb5c32438f40133faca6d89f393238a9", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "e3cbde7bc6fd4c081c79e6b97e104a0cbff3ca9754b28fbef15bad18acbe4455", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "43043a3b726f23eceb547649552b54dfb25d525d9039e6250be95e6371e34277", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "488a6a700227b40edd778ddecf4f5c245bec332065ea6f66df6797c21a80287a", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "32d67a4c797b5b38dc8e0d46c4f3ad38559a34ba85c8074172f07881aae2b29b", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "5f7f356a4128c5e403b9dd11836379afabd6fd21109d30339be2bda6b7bce991", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "dbf89bfe649bbc0381d2183eaf160e0fc377a65062548069aaca7885921c3204", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "578b933c665e004a2aa7fb4ec87d76e29cfac688f76c1e349b3b7d42c229e75f", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "87d9e1119704e6ed71cf3e54c44ae5e8fa9b740e8b7a248584929804f6d43771", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "315deb5017e1b055e51c56a971c5747f7ef1c5ede2825088895d1e6a05f1d5ca", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "d911ab7af4f3e8a30ee0dd35acf08fa47cd0d71cf19f660de25567382c346bf8", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "faca6b129f616e5502ece19b79391e77c7e5e7fd35ebc67dd7c1b33f8254dcd8", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "a1afcd914b02f31872e09b4f31d6e8c24bf952d34f4e72b9f7c1f1ed52a0058f", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "ae580e9197bdb0cbaa742016988ebd84e53a8821159d4ce22e1cdae6ee42cb67", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "845ed1f2dffdf39b565d9fdde9ace2d41acc28a022950f4cf8e0463a74878894", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "2d07130b5feb8850c823e497701104d3e7e507780fd59723933b9c30a9200b75", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "cfd152ecae93d3d886a71a154a346d200b07c043eb5a22649dd14dbac3b8e03a", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "d5f6d9673f3f35fad957ca6baf0eb90f4ed6ab460ba9426d769652df70801fc3", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "cde4466f57717971ead700b58578fd420d3afd42a28867616b5f463e7cab14e0", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "6cb7181626296d8aad99226bcd561a617234c8b38c6619712e5b1af1d7f3838f", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "fdc7af63fe2bbd9e7a6b03fbee86bb7f3578a04fa76684a42a96372a3cd9872f", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "63d220be5a8fa2cee48effe49db241abd0f7f6f7d949b75ec0778f0f213b06f9", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "8497ffc63797cb41d49cac2289d34016e50710fe043bcfbdc73e6eb20ca09986", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "332801e75ef76a51effeaad870fe780c168dababaaed3c4e6d845afc033e54a9", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "e0f875bf7c04c9bc6230c820b41f9f8560c24f4a45e57c30d1549512001c828a", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "412307a556d58aec7875c798ea7215b6fed9a969f30b18e12ed3c72430987e86", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "8534dee79203192ae30366fc67035cd2ae315a5a35154713a2dbe1c9fdcadeac", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "8d99529c57f509ec5ba9231589a0a9010cbb1bd24cb6a6bd73589a8d620bfc0a", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "e72e949448033b8594196edcc44b98a6cd0f912b5043bb465e63c6b3f13b7806", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "195b145a59adf9d530453e4ebc7dab937ab1c5dcc46590d50e4c7069cb7914fb", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "67daf638f18473a8571dd3d62e2760aa92ec5236324886ee75cb99a3732facae", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "d5baea56eda595203ed0304ef8f51eb2653b79be9d47398e12963ede1a66ecb7", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "9bbc522aab72b3150f191cc9f8aa8c858935d216ad43fba3e4b80f61487eafa8", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "6f9b05a57cd70a843b057602731ba05aa8c758220f0967c597451ad01ddb7824", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "2302cfa01b1d156b581e3f2cfffd57fb3365dd4e653be6f43365ea59382b96fc", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "0d97b663cb400738a1529ae6fd387979b8fab51d1197571e1d45cd541a735680", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "c23c3da99b2de6fffcd0511f4b2f9297c5acd6c4d9db4bcb117bde2a4e3552fe", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "7bfc9040d24591645b76ea92e48d11421233817ad8ee9643a96781c119abc6f7", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "05274990c7af2e94b10b54b6e2082ac17201260591fc08d9c406f9f0ed9e638d", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "d252c8c3b33e6d7da97d2074f8337be8779baa253dcef95f0bfd60ee2e4c4282", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "8885c9405fc9bbc7808bab14aa62338ec6c019df0fd035f845e7c044405b029a", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "0f15b23665abf9cb84865fa91969aa1c4fd0c522ae0d68cfb7333441f585e900", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "8afc8fed67e7937b831d71a6991c6cf5c41313b012ddedc23cbd2eb6bccd59ea", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "7ed7b934d06718ae8d1c8446f1db684dc912350dbc76f8a1069345b4deaf7e34", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "6730f2cd3084726c69f81b2be774c8ee4650a73475b7078e2f15d31a84552469", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "c2fddf0680c76add7646143dfb2fe35f6db5fc764c7260eafd74fa6e507320a6", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "935c8dc0979a1c5e7ba8fd21e4576221e0f0f066d1d71ec8424d950dbf8596e0", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "94eede45646201dd5496fd2fdda007235a5bc6e49d73c66c204da8bc74b0b96f", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "260cadfaaff62e1e013b9010434655b81e19213f3ad7e049cdc9d1ef560a63ce", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "2560dbf1d88a724bdb073d2bdbd8a8f3cb456c26473a6c2d207bbb56a5f4110f", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "32b068c04013f7f5f2c312e2f91dbcb0ec6af791ad8d17900260363b829f7a1a", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "bd7af72539d059a0aa268683a120f64e464a3cd5f9d6189f512021c255a41d3f", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "327090f35be9501637b11b29a8664bf3ccb249536f509a9eebdc8926b8de0f4b", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "aa583bb57782fdf90d4d38f07d7a394d64582996815c03b1e671e8382c4ec397", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "3a873ea2e05223d4012141ac588344f579d2aa70b636240e6f722e77cacd8917", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "7c88ff18be2e76c9216e00e32ffc25e6c335dabce8e13d1501e80a059e7a5d87", "target_hash": "c0ef037594ab7f5da20c589913683475b1e17375fcf4db27ca02ec6254a1aaa8", - "section_hashes": [ - "ed74fa6d9588", - "5e2ed8debc8d", - "35c3edcf248b", - "ade46e35f80c", - "33cd2b2168bb", - "431cd7b7d011", - "53ac591b667c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -80777,926 +27486,332 @@ "pl": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "b2aa2929047c653bc7cfd1d0aeb0df217217e144c5eb6d96c9241a7b68467410", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "9f2c209e428ca21561e584fe343cc0249efec05bbfc915ef92d5b9360c0afaf3", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "881adae7d353c851024c23b0afbcd81eba42c0e536a8b62141f298ebb598222c", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "e31afd32eb207a65c18b50c4107ad34159e591d842ea97af8a9757de7ab05277", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "831e5a5c24e2541b3cbd315691b369626a102671820963a6d5e3857de930877d", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "ace0b114215999018d31c7542ca30f8bce9a8a2a9239452fff1835f7030309e7", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "de26e0cf653bf526be2bcf5df227eb1c43cceaab6fd4ea3271b35a3a110a1830", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "2d5d9a9587573e2f154fc55e239ffc2fc31454593fa4455fa9f48c65b3a3165a", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "e49c47e2663be60e10769cca24c83a2a5785a4fbd330753f25c5315e8c8552dd", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "2fb9b79b75cf6a9c6514fb9b048b5baf97cb261d019d288585d1913d4b7ef2c1", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "16f023247ac48a06769c58f5a0e6603d7656dac6035f3cdacefa2fb4cae0e58e", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "b120b27c44105c8f5f5251c6f09b056a6d8b6f4680078d688660d56c65d14070", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "d585a4189941bcaf4945f46a5b1286b00018889f41b3683370c507f4e6c9546a", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "536a16d2420b0f810e73114ac9045fd62af7f52d9965f21a1078e30afdfd27de", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "a613f2a3d51543e64b401bd2c4d8e52c9483b0586286eee52ab6a49616bc9242", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "2e9f0ab3b3c813e9dd92c43d88ffe3fb9dd8048e4e730aa67aac7b89b8370568", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "f443d90c9446ff3321488a2252e06e34da3bf47b5be0396cd6ca9dae03f51ab8", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "bee5ebeaa9a618088c544793fbe2183bf90f4a3f499dc417def9bbe1ea7bbbaf", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "ce83fd751372b9d275593e72f333a4149c12dadac3413f7090b11412aabb9ea1", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "fcaaa33f2f05c72bcaf25cf041c4613c582aad6277b717438a480316d085d7f3", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "a81f5af0a18be2d34b159f12b0b507c62b8eb90c414d66198ec90275c0a44823", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "be3e1b0735cb9ba0b873559b1103dae884222d0e16cae7a7ec4b8907e4a5be8c", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "c32ccf6dd465ef68e67570f558da1a701b8515dc1fa73ce008be7420f912e780", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "5520b8f3b69f9ea039a2436b0901ffeba519ef6137d5d048aed76148b2378641", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "1a3f43890067511849c51ca47fd333b5126a6f90baf044fa586fd670001bbe7d", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "a298872299c2e4076b3aad25b524804796c54c8223cdaad3defd72cd691dbb68", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "96af73b30091a8cf56cad36ae2bca2232562361a2596fd0a587ef10f2faf068d", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "364c574d5f6c32846022d215cc20495da7632e81c1261e7cdd8286a924adb6c9", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "df797c4158a358488d7d562c8981cf2872ae128d32e3c0a931c46c9517e92a3d", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "ba8fba6ddcdf3d05c0152edea3fc01f9e2f8112b8303f3b5eea60f5c12147624", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "8758719fc0cbdf8f37b1df8904a7421c8f79d7c4f1c2bf8b9098b9c7c7f4f17a", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "03d769e67aaa674c14683d3010593c59283886530f9820a7c209cf8e7fd8c285", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "cb3cbb73692d2fe378f94bcf5885a1c4a2c5cf7141e1a961f55061ce86b16d67", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "f9ed4f0d1c5ede3a9ae30ee02b22db41418f3b802c4fad3d9195911d75097723", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "1ea0643fe357276c15d7f648b98376f6d4fa07a6e48733861f4cb7599ed73a8f", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "00486240280435da345d1713e2f09b85620ee1da8fb32fb9f97824bda30d55e9", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "13644af409b2dfceb3c6765590683a1716897f1f303738daa5853f571a645371", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "0383194caad5e4fd47043f0a15615abf28fffd1c80f6ce42eaac0b7b9df663ec", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "19cd9c44cbb6eb1c9dbabda564a8cc2d9adffa2d1040be291cd7cf2dd2ded827", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "c20d78afc582f849b324aa85a0ad196c3c23e866d29947d2b0a33c271f54a082", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "9372b9d3fa22b812503afd568f3704a7da7704375bb7b2dc214e3bd8d5b34b9f", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "c03b3e072948cef2a4c9ca3268f9c81207556699a64a9dc5df71d8a642a04ecb", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "79b67d787e9424dd285fb9d34ac744feffdb2717b7b9204048e4b463c2cd09eb", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "03092d2cd5b6c14ac1ed2a4fe30492efa141955da40f9dbafc0d7af5abcb4867", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "2f9554f884e4d1483f5d93b4233bbae526f07315f6ad554d7b8be16c9da7483c", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "959593e828da65f04be34bcf9e4dd08530203544c466371052b29c3452ace05e", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "2db14479b4494a0d36ad21f0be9dba2458c8cdabe9e337341e34c8e4c960b91c", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "e39acc76cd2694fa571f7482a1a74de29daff6ae6ce4e001f5f4dc6e4c5c935b", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "97e52f0adfe2efb940e417105e5f7559b22f7e9b15502645031286e863bbbb4a", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "c76ed81ddb2102e39236463c5516da03c8333b54f2152ba6a86a746329651d56", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "d6b080185fe677453ddebd146e2bc68d8ec4a3d6f2681997e20211981d537a1c", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "fb7b5738099964a2aa72ea225e63b073543ef851fe1569de672dffd7ea39b877", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "f7066e55d5c758f33380b4cc05f87b26cf6e415c61950847e6e788dcc45c29a9", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "97fcea22efa2cafcbc7188d3bb7dae0c2acf2e6662869b47480d616cded2d05b", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "f894d297b8900f6ad8f275f009dd89a0f7d82e704c43b7cfbd18082b277c6704", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "606280a22fb019d8065ac47831527fac2f3ec89dc0b272ca1c2cf10e00f1723d", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "2d243deed3f34ba60b57e785f529074d02b02951f63ccb7c4e12265ded01a58c", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "7d96015e83ded0e914370bec3af248e08cd51a0a2ddde49909ec5bbabfa3925a", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "1c93c0f9daf18351c28a9c3fcaba6419263a374d589bf62cc93abb224b64b6d1", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "d9682aec5ccf8ee96b7e62500f45e3e3fac63945bc7e9c082b3990d1ed23ea75", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "92941e9cd667246d9f8d37b150825cc9e41aa5b9472afea878cf5db9d75cad49", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "97827f355f1f38afe3ecb28a0a704aab73952c7f1e866753e6615cf55d2970c9", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "f6bf03e28c2f390147e906dc8674ce2358d5ac2b64a3609b74a2f901b4ba4ca6", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "56fc32e085e73a2ab74211c02789b94006184ebe4f5dfaa889ecd9e713a1ad32", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "967ee9f4bb6e6d254bfc012cbd5e4756b2f0b34207397d3994b3c1af19032ffa", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "7d08a55656e078501044a3e018a6c91adaad177a5729042637979918de70e2d8", "target_hash": "67c4b5a3eea85c0762c137e43ac4ca6548a4d52366c87fc2b9d1405dd9cea03a", - "section_hashes": [ - "2ebe4cf48392", - "1ee258e36a02", - "9eb77a71aa8c", - "ab9372b344c6", - "c516b09496ee", - "8fed20fa7764", - "4759275562a5" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -81706,332 +27821,332 @@ "ar": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "d7f155ee9a9ea883df16d3f73ae3d744eab431f05d461a31ea431ddd38337d44", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "11e25286d09048f98a8ffdd027acf62cf65bba1743116c16d607279f35d0f58e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "e4eadae21f4a8b1d276f0a792c5ed9d0321ff509f0ba22c625cf38cfa7b7e5bb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "661ab323dc42944c16fe0bd0bb5d846727e3ce9265db491994b2d7f6e77ea3a0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "217017f622b1c91a08b987dafe7b0faad9c74f3e21144cbf09324f6d7191e88b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "26d5c52da00e20e18807bb9bed7b0abbd060b212ab1ef942a8d6cbf2012fcc14", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "f3aa7d909f887ea70a5743304394f1291d86a5feaa23e2436fa999a21063f9aa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "279aeaeb3e8a47456b4096b4acf60ec306b97dcade678afa238bd7484fa38029", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "f156260b377c2f016cfb31157e600421f506a362d84d05026718a4ac71a75d11", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "3703be66a9434cc846f5abc773fc4f8ac821651dcdcb0508b9abb20c11084b4f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "7dcccaf49cfa5f13a9c34e7f8e2b3e258daab666b2c97a8f1a53f34a6144db73", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "f78c9c196c16e8fbe2984831abb033e58ed8efe787e0de62ca5c2d443d2b2d35", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "dbd99e2bbc79097d576179ac8827205ed5ce0daa049962674d13928fe256c505", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "d0c72aa8f322f059e6bc865f96050b1f7056ea998cb57924a29308465620726e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "ec9bdbf89829fc182e30ba82069a018c6973842be38127af873431fdc0e35de6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "7b1d309929ed7ab86baf847807a5f225e126aeb1ba340de6b8a1439e3d07b54d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "b3a208405262cbd3d8f6d10351f5ab2d238a731bed89c1b2d3788ca4302a0c2d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "6783e5bb2d4bf2aa3b7041ecf81d3d94863d89267a5090873639161efeafd4c5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "6377a20ca188be09804f9e60f81440539611cd886f99c9851d77f8aa32ebfa27", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "17a1846be003fcf209ae9bc30e6f353e7f4084ed0ef6d25a8509cec50af43181", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "16ea9c1d49182d92e9880d2768530780ff82851d0b74fc15f46b304bfc1976dc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "2480b31f8cebc8dbe616cebe15acf79fa613751d31c85320f8ffdb9db3c5306f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "aa7a4c3784b1a61eebd946e61bd046b77b634378e8046650f5dc8dfb23df9833", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "9f3435aa76ccb349ade863217518a0b3e741ae2f947109bd88a68a58d7c06f58", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "7c2bfc4a79ad69e5981204f3022c1afc673ac6a66a98c077a805154719462523", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "7fe759d211c9bbebfa5e14c88c2d10d9f3cf83b71a91b8567aea3bbd9e55445b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "2c682a0463b7237bf6a9ddc43eb81de2dc4610f88ea88e5c6f0679a141ae5487", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "0fdac5383320a6704b7af0629781bfda6f46c45c9e5b865ceab0fe6d8b95afae", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "8e7a513763ce68c9e74a3fb9dc4ab238ba8eecd7e1573688f69f614920c14717", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "e5d4be8528df3689dc661e281a652eae729541c95a76836cb33e6ca9ce4eeb7c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "c7569abd45d482c2777fd5c0e5ac1ee8e21ff93f7bd9e35c19e0835aea8e087c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "5e88dd7670ac56d963fa94185c12300c0b4a6441cdfbc67ee258698ad69a4397", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "7fb418a76354ef5d3303610488ac40f491b7d43cf4e9470204fdd27751e372bd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "3ac7c305223f4c2990b19d5b2754f43d952d75afcd4559c363543b53f73a65f3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "5801fac1010240570edff040283eca29a4a1ed853afa195b034fec1111b6fddb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "b6a316a3031e567cffc7554a64a50bc17f1d6d3ba15294421648ee9d4dfc664b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "44ab759f245f81f32368349b49bef918683415bf8f6c0aec953957470342d2bd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "7307216b4e06c56381722693ddf02e4ced85048734efbb9f3fd98917142a4c54", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "2dd606a1c3624df1e8ee8a17430b4f3c5121fd621770defd5498baaf876d7abd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "f2f6985fad3f4b00552fecf117f7c6c6da5f815d883574b9fa8722506f3bf816", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "7cc249732cddbf9d7c517bb1f8fae05fc758ee9a3ce455f79e533e660c7d7f3d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "cf841390c4778ab30fae817617223a7bd48ef5efee62eada50d82144e0e6d5f6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "c95707c7a98cc8e22e74f479464811b240608123b0f334ae56c221d074a7051d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "3adb5ef1efa13576ad2b2a68ffe60e5fa50830533676d21bcf4822bf94b6ac45", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "5f22872c7982b6a25e9638b5ac9c1571c17ac3756bef988c01917b2b13d481be", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "b1f0a4b7abe909680b8ac5981b6df510b9b30ad20c39120616fe4f41f6390e12", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "b1a5eaa00646c96446459ec5e9f81389eab7be1e2224d13aafbe544960539245", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "28b969ad26c569ca202bb8507f5b16d1214dd6e84648dabf07afee30f7a311be", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "3395ab9fd7dc04989e937429a37c487fd418fd921935a742a0aec8cabd723926", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "81afd6dc79f64c3e67344af6de97b4a037584c3379bf573ad222b7a5fe8e9e09", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "057a001c3a7c5e89e07a92184c90c848ef3f41de284153afa13430e3a1242610", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "77a232e646efeba78be583426fcdda0c4e92daa83ab6fbf33fe5e1e0d316b6cd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "ca0b4668fdfe609fb25589e3986837aedaa35091c3dc84cc64a2d58c869def93", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "ebf2acfdbab499c195092e181e71ffec69bcaf747a7acc3e81ad8221e1e847af", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "627981514176ff55b14a06a9fc6283e32a76462ce3876465a082ffb15a15e6f3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "6e5e43eac75a9229a98552f84a6e5d29b0e5c3782bf3adb07131564f5cbdcb9d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "0d1d56121dc3a6376b0888fb9bbe597e1c37089525838a36838f7c3d3235996d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "8bbc62d770fd32e89e7c674df3320b949653f8a4f97773fc59cdc2719be1e8e1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "6289bdbd65d5246c8c9cb5c3ddb67d315f2b8e5963595fad18a6236285576fca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "94a5462707a7e376ce65beaacc85f9394d8fcca1b2855110b83c2e01f9107d44", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "1c533921a2f1be6e47f5e506dfb92b050f8c64f28ad0f21e911fa67fa7cb9bd0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "a44c8a9883bc0e2c40bed77831570badd87a9dd1af12e80026eebda93d241d95", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "3bcb10b7f16f3436cf8bc2166d849558833d5fa685898c8cf004b4e43cf485e5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "9b5ac1dc6c8b851773f90c11dc99c27328b4d10eeff1d2a823fbac7a8f73f488", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "82366b897e72051d85f106b4c1e3bdc9a40a47ab9442c8d1edd6917b0be46df5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "acf8f574a369e81466c72a5aedc118e37c6f84797f45a1169ff8102b91579d56", "target_hash": "f5974baa7fdfed2370e5335f7c8e08833ab203e289a0d04b57fb2ee17794af94", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -82041,339 +28156,332 @@ "ar": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "4ad64727e119b7c8bb6df2491f382696e5e12b88bd0b3fa08a347c92d0ba0d92", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "ca7ea9e6ceba58dde8c340734f9e64863b0db91d7f21b7bdfbe84d0b6b318f8b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "d8f33e5088b2bd210454548cc20c09205305cda7872eeef332ca0c9fbfdcc6d5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "77f9ac7202209136587f6b0f2e86c0b360b86228276f133acc1f877fcfad9c3d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "0e44e8e196dadc2a25e2042f61494df84db11a6ff043dfa3590ad7412ffc1ddd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "b37db69bdb052b2cfa4b52c038ca139c6ac1ea5157b6496104414b9c1504fa2d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "459729ace3c3cce569084f5a987b611098f1d139a4e7b20f80aedc126a05add3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "30542809a5a53c21819bc91cf7ced0c17a90bcc8231bceb6b331a4e2cff0a8ca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "ea0245e600fec77b214e486bab96e2439ca9f572fa640ffe8ddff6fd9198195f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "79590e38d96fd7b5c38f68df05bb1354fae1cf7c95e3bc425357549f1b6972a9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "52f4c920e0295553d605e4035e09e87f2819588ae88b7bc2a7635ca6ee41412b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "c025701b72866bab16e8608f4c4e11defe7760555241bead5b693c2291fc65ec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "e96db65be89d2be6ad62a0a201c36213d79c1ac66268b18b3cf5eda9335a80db", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "823ee834567d9f0cb7efc0a75f91333c7a3abebc6af4e685c9c9b0925ae66f11", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "151560ae5486dc7eba699c7432fa224afa72f890f5344fb2b314af829873db7e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "d5775939e4e09c91e967803c948216e92a182de6776979050319f86e3d45ab7b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "5140a265e43eaeb5cc0cab951dcc5112feadcc1c9b378ba4d2b216c95c6c6456", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "56b7b61356e17fa9801e09a79f6d877c45a1ea0a2fe57c552d604846eebc75cd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "9c94f931685f1a09dd8b8300da0b711a74f7f097af484c6aad14e56cc8fe9228", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "a1626398cdd87c007bcc0b96ee0d8588297f32a97ba7838b8cb4c07436cdc06b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "ac2e6d16493017323234275ccad7ae4ac33518ee06bc41c73e8df88e6a4a9ee0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "842d3afeeab826a8d1dba58ea999e5ff470fb88b8be478e5618f6816d8fb7882", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "6d665912cbec0df8c6942ab17140d2403a26368f1931eca297c7371f77233962", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "e99af1c3b1bcf543e894d17e80495535bb8790f42adb746647bf0c62bc806333", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "c406265d4567177a623e654d08a5f03f95bf31f791a0a85a1a58fcb78e893e7d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "79d005d134eef5edc07650719c4b75f1e338942691bbd4c58a8a4378f848e036", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "164975332c16f556bd6b329148b131c36df7bdbfeb7c44457c32cdc4e4727f08", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "b47c32e8efc47b9c983e17944b8e8ed471d85d28bf5071f45085715ee58bcd05", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "002b858fa182cf70019b08051aa8a3fa4d6ddffca66acdfb4781ecf0bb75f840", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "b8ee76f0d7400580b21e8a34be1a641ac870ecf53c0c743669074c1637a9bbc7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "8e8e95991b813537f1403c374805040f6296edd8650ad47cca9149f647783ca6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "25f943ffef6419dc2ff975d66396481d23f21362d76a58b8f82a927c6fb5e4ae", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "4b9aba0635270c886b86314793ca99267817a236ce2d28d9d9695ea2bc8b8a9b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "187f385f09027cc814a299d8122cd1c26d2f3a0916dd35387587c5940371b30e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "fafd0212c8ddf3deb97121e32767a111701250ded83fcf62d08f0a3465315d55", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "013467434dc238c8f5a511bf7feacd86f21a8bdb7fe3b66ede2ef7f2627c5d3e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "9302c18d115390e46c0f65e2cc08c75309d0d44c0bfd3878ae6dbedf3bc1e423", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "4872fa3874b5f74d59ccc72241adc2ebc32847e40ba2402ed511ff3a68edb71a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "e27a206ebb9e0534bf8188da3c3a7389d95e267bd2a6f92acb57b2a77b676fca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "193a3d9682fa293e74fb2c5ddf1d52ce613673b0a6f43d2b88ed864ca8055f83", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "b54cb9ffe724fc0e4f9983b09cc57fffa91f05efdb58ba1bb0d3c0a2e292fd24", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "9c97e129b3326912c601e5bc40f3b705367fb23c5df74ac98b438c954c58ad2b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "e00d484cf67dd90bad114bbba1e84982d4702dc2f7e943bc8bd65ce80029dfb8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "00e00e3f21e181e8a34971bf1a77d618f07c32bec5ca383e69ee35f1e86d490a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "88eda773a522e0937fde4f531fc819e3ebce35fc3fa416c30bd6f7dd8e18deb1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "bbcd24fd8c353849d14179e74e45b5d4daf1228d13c474e0f50300a718f6bce3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "6974bac16ef62bb1bda67ea044340a88ff7c11d0249c174becc1993a9a293ef3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "aae992c716b87b0c7fbb3becd5ed1da0560ec65493425b42bf70a34dbb37aae1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "b7ff76c6c115be041d704f541a1623743ffe0e838697bd6882c7373b4015e740", - "section_hashes": [ - "3f4b2db42802", - "c98a5cc1ad5c", - "bb88e6e6895d", - "61d53d9c4c97", - "85fb5e897f2f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "e92bafca970e00f40eccdbeb26410a3e8b394def3ca7a9f02ca6e9a7991b1af1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "d22eb925918b251b02e4d24b4f2966b6a0bd25a5c9f7272888975bef4e1235f2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "57aae3f6ef2af8d24d533b8bc00ce647c57e15b50d6c1ade86b2d59f782f95e4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "607b5a5c61711cda6804770557cd49fe7f3595345c2228ff437a574b6707d770", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "daba432c21a67902990939a9ba6fa026451a41b5171c5e3d9e062a136349f080", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "6bd553de7fdc7a75b172c62191529644a4bacbd5051b7f4d95c517c18dfeb870", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "08a4292a2e5f96b73e5a1b0a252e999e22e4b4075dc7fbd43323a00931d2661a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "de309280ab4b87b8cb485fb7af8dc5e876cfdec464280262931a2c3010be5f7b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "292941b851204b525646bd6a1c147df53cb8db705ce952d5fb114b1118abf36f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "87da7a0d211a50782b007ffb1167ba7b03076c9980cef8b495d7251fc71964af", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "57fc82977c0428a760deca9521d03437445ed419e7a849e3807f550a30795ab4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "7b8f6a9e63a920b5cf7dbcb2403f27ecc0e2e020ece1d104874a08c5dad6979a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "37789389ffb90de3047202b8e44304c23e7f5f0a92996ee5a665d1bb67e4393a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "285c881320129a443cd1851cb40c54df7addc8d73b58849b4d6bdc0bc7d82f21", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "a730b25457af5196cb4ec4dc11cee331b87bf40726e965ea399c00d152972252", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "d4ee9c8475a9170b8c22219168da6de98dc8326e4119d2efcd65915334a653ec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "82afaf3a1ea599529fe30f558ea2f1477677a8c269adfe7d95054b1c95de57c4", "target_hash": "e8d9346a0636a3313a454c7f8e35589e5f2d13333766f10edd267f0e5b350553", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -82383,1185 +28491,667 @@ "pl": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "4dc20653aa4492340ce36b8f511af8680f87e11a5181d801a606d0c8c872492a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "82345be9a61be1de30c29aa35e2ddd1bab287267d6779d90701e3b46e7077241", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "ea2538a67fd3c4d768a9c3999a923444d2e0989bdc6ad8f02f28cdef47107dad", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "d2f28604c0d0e1f4e98e162d4999d59ec4fd06018e3b2f857189e55f50380360", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "11663efcb32f59056d9886b936e4f60943e05d5a02ff672d1012a6d27986acc6", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "fd88bf1cc058ed0c9e0ac130b9333fcf63b1173076d3f4a9abc036baa6252048", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "303af12f489cde89ef1595056bbda14668ae8d4a094c78ca4184ae545a742b6b", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "eefeb87076403ed9422f497ba3b766fe401df6c56c23e33a9e1028db6d65e7e4", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "aa983b182cd10d384346bc42e0a39d5aa6ca55e95d2a1ab58af7a39efe2ca4ba", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "d38a2286e1be567af88370e64e10456076649939d2ad537da3c17dcfd0e4c6fc", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "b3f591c8a8dfcd32ed8969e5b948796a67e3987cecb7d1e27586c3f4768c7c13", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "a15f0a7217378edae354deea637866f91892bda5db5eadabfe1fea1fba6e96ef", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "f9413fced9a00578c451254f6c49151c40f5fec11e0b3d840b32cbad04cd6159", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "4c525f3c123361e465fd185d69506fb0c33590d0d07535683b76246eb517feb0", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "00807702076642c3bf9a9a4f5244ee033f3a8625c4a8a4c9280d13309114c54a", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "81e2f325f2f7b2228205c28c7853eefe1b8299e1777cd58d28972f48fed54c8d", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "3f45b521bcbf5e050c451793365d0dab46bb6c4d5c4a5e4641081ab0eaf1cff7", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "6a237f3d42e6c8ec21c042638d99b6a6cc1955595d058c4e71c31e3970e367c9", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "9dfb0ea96d128e4784b690a5daeb64794bf20b02429a8915bbd5fb8b02e29db6", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "ce4882233f21033530a44a35027aea3a98c9dee9506764ce9633b6d89a34f8ee", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "ec02acfce2b687125a1130102cddea03163db003a56d5bc7c60eeed0b7d85fdb", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "d2d552bdceb86cc9eb73f21b2ac58223502b047818d837433dd9161c1021e33b", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "3c58277bda846fc2706a1dbf3bdac15f9272476384b5976dc8b80219111044aa", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "b89a32152d78fa04d23f9144211d0e422ffa401a3d79ee46e6bcea49216b97b5", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "8af1e34db4ba4a5d101b51b26f9f21a03365ec8e3efda02ee374c1e5f071a2fb", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "c8f5889125ad43245da39a24121619a7a66a577667a41f878eede96edced16b2", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "78fc8a3660c2c567832bcf6f56eaaabeed44f04b8148db4ffaa724b148ce3542", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "9ed88d13e55e944d688a1dd091523fc86803a97b58cab0c249c4be3be94b7ab8", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "808a01db94b1bacfad6117e672f0852166259e9c9c3ec88b42b46c2b1059ef2b", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "69c1816d46f10193d803f2a9f7af39ea74befb9e8e9ae29c02ffa3acc12dc25f", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "937c371be5db46f6865d8a55e3484aef669123c5750f0acc245f91ff3a1988c8", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "ac040cf0c7353d6d22667a8d341bdd53be7d4651de306bf84224dcfa4161bd18", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "2d33c223f001446c0cb73211a8d40ef0520a0734ee62365785b2c42bd7730331", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "9d73c0a81bf604343d4264c81903c7d50f94e19aa16780442e70f0d091659a3e", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "37bbe97c4454f027d135e1bbeb9767aa32e4e24cce9ec0bab04f94d6cf96cd01", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "2023ef1662f7824a247785a226deb62a76c7f2f1672207c03a175cd533317a87", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "114caee35ce405705e79cc44e404196187579c3ec87e0f515ec825b7eded4329", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "b6b5fa45c488587a9fad6203fb7779c69ae3566eaaa73f1ca3c95c38f798bff6", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "b33fb43e00a59f2d92564d4598d20b6bde12f6a5c20ac67e5fb6f8e0588e80eb", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "3e72caf198e57b70e646e89ce14be5b9b2ef8842d62c665d738cc25c66aef404", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "03d76026017810385fc53348b662a59e0a3153291d86980f25ac0630b461c69f", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "eadd68d3a8851d62ac746a8935bc830980acaca3414f84e46d352098ed021891", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "e7375f17d968f45896376cc49ebc41e95a62d1ee00735945ac77029831dadac1", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "43b8d42c1dee6a20ffc9ee1935ec9d8b9905c5de1e41b969336b463fc50ee30c", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "e4118917e9071b1f5156dea306ef0785d5d904c5add0a95461be7d1a933d25f6", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "fd19f427ef6f8d4863af43202e631b61ee81703e5ecb40531a4f5377aeaac6f6", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "be39c1c6d26a754faae4b6bf2d8c37adb36ac86f5d5b015b91f12a0f1727fdf2", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "9197e292f417011f52d022e97e4969c09b2a57b4bbf646051c871b757970d6de", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "bdca181d7e588a5359b30fb0838109978d8c3b5c72aceb14c7fff67bd5375512", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "8032699b8c07d01a00bb94788cc24d7c4d4a0047be98acc3697fc3c1e76df6f9", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "fdc6233174f281cccbba2c31c978ed241a45821b6f92dd4f6aca443d2bf9a470", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "853268fe3e170a1b1847b747842ee01f324a6c1b0966642450bbdf204a757303", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "62dce862fb6eed157c18f022cd5a0f406f7d8330ddea6911003d0193d9123c5a", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "a0fbe664a86b182c636b24ed2396979e8967c547a08fdab07d6df41922d569ef", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "53726ae34f871b15e68ad0d3d06f7354460e2723af669322acdad21f835f84b9", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "e83a0b7e24134a6f715ca7bd6d8fc0f225e86bdc0b014099fa19888f27a8c22b", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "9f09c89528187e787379f13bc71219b8c3ceec54f58d03e10376dd2236688f64", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "025a823e188769a3c164731ab5f51030b4d4d4713970ce5fbac9319635ce77a0", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "2eca14b2666ddbbb27a38a451d6837d365aad9d7695fbaaaae97f2a09ad13a57", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "7183df45b814f59a03d2714039e8f5a87c58ba7b0f5c712e235969b573fca5c5", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "a8fadbdc206b44e30d5ce6d322101173c2e8f23649495ca981d76015ab4a9b12", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "a76d246fd5878ac00ad051665762fe32e37bbb12856a67f425178b2e475c661e", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "107c5eeb40649091ad3c2766048ef05e204c9daf11dbd02d919e4441316bd3ff", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "7cabc8cdfcd6e6d311c305461248f1cc955507998bfa9565c92870b3318d69af", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "b4b75454a56f63e8ba576cd1dca500ce75bb34752f7e12e8a3a753601d2877e3", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "d286daf2055a2ad6e0979ebbf3296968ef02734e8fa9a6b2dbbaae729cf2923e", "target_hash": "d129a157155928224c3c818566e618c0657e0a810b2dba0d2697842be8b777af", - "section_hashes": [ - "adae313434de", - "4255fd2684ee", - "f4696c4c1256", - "4f09146dc8b8", - "833ac169895b" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/guides/USER_GUIDE.md": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "locales": { "ar": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "651dba6d31c0f3bc9d61919e00e9dcfdea0c3886320873eab6bd43f5d530eba4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "98913657004f3e5d2808e76b1c1e53e2a7ffa1c9e03041441da8991468ca13ce", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "24fa4000c59634a1c2159dc5d3fa94f4afe3d3143dd50179e698c6764dff3862", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "c3f4aa5cb20df00ed7b07d6655c6cb8fe95e50405eaac5cf41081f5b4a2c6c2e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "67b83585f74bab27cc150158a3ca2d74a7889e0c341364906761c86891436fb6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "b3ba7ac50b486b6c39da91120c2cfc66aa8bf4158cc5385192dd3c82735e9eba", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "ae4f17d41123dafed5ec6f15999c8f7cc5c477879d3c47d94906a680546d495e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "dd557dc1d01c19928d2fed81916b1de9458efd1a389b66c71b594acca9c74938", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "e2625619e80f30b9c4cbe479161d397b9771119e4ce7a4e6669dd38ed32fc9b8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "213fa5b1c1911a1e6a0a453e933de50d4a00819e8410e20ead597f1772211114", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "25ff8e71dd15daaa6f85baea81d85c89cc28521261a972681a806c54fd198d81", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "3dd48bd0140cd08a6d7a3b94d220240c086c87f64d9e8c6537ad15916c220c36", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "c9854ec305ea3b90f592160486c4d4f4395589f28d49929ef3b5873020181276", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "9ec3b42ff861c3eaaeba956ccfe67fce5b4e3d74483de724a6cc54d70dd8f0b2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "15257f00671130a74004f61ecbfa1c5daed13e8d2926208bd33fca8fa1ad31a0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "870f3f60d330157c93d12cb4e4bdefb90e1dda644f1a0e88cfebc11a9b839b4e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "e5bcad7d05bd1acfb4af5f6234ec45e84fad2c189d483058aa81e093f8275d19", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "5cf7723c2b72c87daeb7bd846e31ff0a8d11c9c8eb86a0ba24e290b1145bf9ca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "00a9db9df3ac36d0e8f1d723d29f6375c0d70336171f548ce672fc6f1af253ae", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "5c087d6214da111b11db7e78e61df1c14c956870fd84b61a4c7e932e1caa55c6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "20bdae18db0451238a1e91b0f17b2ec5488222b25360b45377b58708fd8a9def", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "8f7542383ea5ee3af0aee3fb6ee96daa7fc6c65a6d7afc42f2a6b578f6c8880a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "c4e8a52bfa4849cbabbb35becdc20ddb363529f8f7b5e97d2e7dc45a5d539318", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "c66c9ca2a1b6c008d60150c039e738778a0d03f62e4b9b1d065a3a1731ba35cd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "5373dd61f01be5016d0087092f00904904ea0945f4047b43d4f81072901d1620", - "section_hashes": [ - "982aace73f0a", - "431f9efe3d07", - "205355552b67", - "385f212f1cf4", - "9f4009429b13", - "de182ba3d06c", - "8e57e7c4f63e", - "5096167bb4dc", - "5f5fe1e9cd78", - "b325e10ca454", - "2b4f8c92b3fc", - "1cb9e0977f46", - "531d479f81f7", - "95ff096f3d63", - "93c6e4c8572c", - "2d400889230a", - "4269d2966451", - "0fe355b3aaaa", - "8af1252b116a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "5468c8972d6b2b86ffe29fc70ffac08e705a4d50e8d6f3e97a6a9308560b8066", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "27bf5b8f1c4ce0b375fa149cdd9a864944688db7658119f1279e613466aad647", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "0fa0a2a03d2c577fd99d785f2ef6c73ce67620a7bf96052a2bab5c3bd420085b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "b947676ae14e33d66fbd010f8b052a65ba58212f530d47462b59c3f9df9d5d7a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "1a283e3fec407df2d5e69a622c842f1f4f5079c5d59dc2e6598d69b56398c6c9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "365186cd110cc55014aae191e73eaf58fd03e6502b12afe5fc52c74a48fa18c3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "efb1c83c832487215450bb87d5aa34f1f119252bd1219483f69e013facb28138", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "5f4f117aaa5dfd8cff611690e73de2f9a9e19e9c12f071cf5f232bf36040fc72", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "ffdf4c55384a0b993f9d11df0e11d7f4b9db8503d20972cedd635a9d36380aa6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "b2f8bc456c354f599cb53fb489937b9d2035d360ce0c0a0c0b7025e6811522c8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "b51beb258dbad9d99648e0a0c0c7ed9d1376bb4c897dba051e2153c8ffa15ae7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "2b3a4e4c86e3eee2aa10ddf5bc745a988d66bc7761da4705cae81cb114f6e376", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "fddeb616602d3fd334be57e6c29eeeb77c1c30cbf9b3e48523c13f675bcf2026", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "824f98a7219e01f8433c753d76d3574e09220d341badeecadaa97c6313034870", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "870064ebaffce962ef213bb3385e10ced7191e6714cf6e1a046279229789b7df", - "section_hashes": [ - "982aace73f0a", - "431f9efe3d07", - "205355552b67", - "385f212f1cf4", - "9f4009429b13", - "de182ba3d06c", - "8e57e7c4f63e", - "5096167bb4dc", - "5f5fe1e9cd78", - "b325e10ca454", - "2b4f8c92b3fc", - "1cb9e0977f46", - "531d479f81f7", - "95ff096f3d63", - "93c6e4c8572c", - "2d400889230a", - "4269d2966451", - "0fe355b3aaaa", - "8af1252b116a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "fd27d99424a5f60b1394ad9523ddebbdf269fe7c4e04e1881c116ebaf0b42b3d", - "section_hashes": [ - "982aace73f0a", - "431f9efe3d07", - "205355552b67", - "385f212f1cf4", - "9f4009429b13", - "de182ba3d06c", - "8e57e7c4f63e", - "5096167bb4dc", - "5f5fe1e9cd78", - "b325e10ca454", - "2b4f8c92b3fc", - "1cb9e0977f46", - "531d479f81f7", - "95ff096f3d63", - "93c6e4c8572c", - "2d400889230a", - "4269d2966451", - "0fe355b3aaaa", - "8af1252b116a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "9f00d829b4929c7ae97fea6083135d71fb3ebe034631233fe8bff98554675c3b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "82d53bb66abba820bd9b464942c166eb934370a387098dbffaa76a17267e4fc4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "828948570046aa917d89a552929f5a2b8044cb39aa3f39cb32e67dce51a5e91d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "24698e323c6718a5ffccdfa6ac8c496d45cca44bd20b3521a311e33968aea130", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "eddd2466c09818629afe1f15c407f7e1ffd13f2eb82613ba6751cdf448729390", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "9c030a9bb3abb846e7848a8a01428482dc46646e97dfafe9e7cedb9a4cf853c8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "76f949ff24d84a3d0d884fe3e6fac87e59ace516ae66507a096ab7cf3b2c38b1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "447fc3e1614043f47a725000a971db53407b8f453ef4135b30846e99e8aced8e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "3a81f4f0c8ea89ba530c5493a63800b5cf9d7a281f023d430501e188ce3b5c20", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "fb8a45c52f0f46e7d55b02ff87bda06e84d91a3d17ce1ab46200d1e6ddd039f1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "ac5d49ee1277ff3bd0c1dfa54a2abcab0f87235f31db2e3eea875eb4f02405dd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "905eba818985b7170e698b87537a2f72d99ca9948b18380bb427f36c8505b3fd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "dbe3e71ee842950349415f75490f47319555919eeda712ade7193edec99adcbe", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "42340cc54bc483b50102657e649f37848947a518eff1ec86521c9b6287d2caa3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "2f3debc732723489dce70e5ba2d1c559964db7be513da6e5a33e12e3f095b264", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "7e22dbec2ae73bef9959fa93933e83359b0d72c98554cd0a18e89800714d5284", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "f40b77f1df360a40aa531395b2b5358e3a684f1369f4daf067d76dececd071f0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "481b7e97e5f5f99a21e1244db74dde75582262f4373d23ff9ebddae75ba30840", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "12344b904c3b8ca51b18218f67dd421fea960e10d2fee5d155812572fd461e2f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "6687ea328ef16c4cbefff23ee9248467da524de97501743531aee3b4aa25cac4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "58ac664954f48ab55a1fa1dfb814f169ef3b486f3a2a3ce9f762f0b305e3b198", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "856f61df0a5cfa560b571d2a7faf41060b2a8a5509f2c52eaf57981d14551ed5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "91c938ed586a349c28e3243744017d0ecbfc37238e15dcfb7b6ed336eaee0e90", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "1adc9e762e1df9a475587133bd57c0cd6fab19920094aef7647ea41fce0e2836", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "6245286ac637c7e3a0ceae43a9e35b3a06dd45d1833da9b67459d53dc46e3c95", + "source_hash": "214fb2bd342c43b9e0bac8525207724a5af1ae9fe273c8a1600d68402cb92132", "target_hash": "5bb23d1b0c47b8696b83ddc56a744ecf4720add309dd3520a8a5d2d507080a4a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -83571,926 +29161,332 @@ "ar": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "7d3a2a95750252164839a29cd75783392bee36410996d1a4f8798aefd2469408", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "3c1e8a6268f5b7842c1ca732bb0b7dc23430aa077b21b46b743ab289f36b7346", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "ccf14b5b5b4f7c229763f1dc0e37a7d6cbb3dd1d5642ed815a2ee0f00c89cdc5", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "2958d8338d35e97cdeea9fba6ffca3ae4415726b31c3425d3df98328f2f36fcb", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "2299f6117e1da77e5d2a08a8979ac18d1daf5b04268ee7852c1d08c263bd8d40", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "056e9be1f26bb3a803d5f942adb890a5ae2ed8044eb86ca3091baa98319dff25", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "f73c87ea53d45c31c6195fd9886115e2edbc24aa7e5c2de531af49b76ae43320", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "25023248a72f29e07d9e15502035446c8a1ce45c6cd844a3c365a1732d4d0e0d", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "3121b3428637050803e84d54a3bfdaae4a634f62f422b63a354d8e7d686c9432", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "0c18e53b2c27736923db1fc0fbedbbd5b10edb37c90f2772fae21e1a165eb597", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "1c1faf268b288928993c83f326a90ea925de61a0999d4758e320b39ce7c0f261", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "9179afd19e9f86ff3dbd192cc8ebd874a074f27146ad4352e0f9adc99209b4b9", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "092095a9b87edfca948aaf363812f023f720b00c8a447c8f3d72931e4fe35021", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "8d4a51b5d7ba678cb42732c8e9e80811ddfa9e3883b6e441c01ee3f8465e8d7e", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "d1f6f0b0a72d241bc59a72129e1dcf6bb245c379e695f402b3030cf363180ec4", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "bd1ee51652b2854f2d9e9b30bb4b9e13f7fb46786d674649990838bb6baeb0e2", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "e334795a61bb3b876b3fa64f38e0888190bb00ba0c99ed3682e272aefd8c1bbb", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "419b63efb79274426c328f8551c6b35731340208013d3ddc0161fb931dce1c0b", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "24cd930ba3b667ddd1c6b224d2205b0a264a66e3db61dbdde95b398664cd8c6a", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "9b82a181ed79364714d51afe736b7821def4921ea11e09caf8b7bb2b7a4ab811", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "f964219c0dbf1d4b4c602ba01f50d93ad4efa8da9c47d07a5c74a6e9962d30b0", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "a38071b2fc1d426c24c93771d4bca0fe439f42e40d27933d4c00cdd701bca3f2", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "77d29a17b610ed630cbb3a3283f721408635c8078dc5cffb77560c08fb0078b1", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "6612db14cfd205d0f9c767685ba3608abbf80e74e11393761b1f2d04304c4da3", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "d576e0a80b85c3a96df610d335ae17197a36c0ec3b2bdb97359f27fb6397ddba", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "94ccb27c4235d441ede34b682165520733e63bea20257f599da74efb58c53563", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "825edc1ace62bf0297c0be93fd35048fa19a07f966e38f88c02aca5490858cfa", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "0005453a120d768c5adace8e10021f5f9204ae85f070fced4b67b2987ccebba3", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "6dfb41c482ac4c434f9b5c01a31b22aa48fc21e244ccd62f01453e7cb4afc82a", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "b9edb2594accdf719b9754fdf918532d7e4d5088be05aada84ff378d5bc889a9", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "93fa86c7ef045b88fffacc0328a6af9cd062604e5cc6a82c5f323e1e01545208", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "624f5aed3a81b7deb8b6e6dc53cd5251f718aa64217b69d920b30a9fc6381f1b", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "b9405ca1fe3574ef10a7e18fd5c3f322fcac49313603121e7b9528dafd2a2994", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "8425a9a64ac78f538e1af7e4eb768c687184279e9ccf345e9a54cf56e249ec1e", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "03182f074d6fcf79adc013303e2b47f898690980fda7750052cd227bd4b917a6", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "0aca3c2fa00d48dc72eff40679174f53189eee66b9dda29894790d6b4aa445a2", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "cf98e298f473da20a2375efd5dee313516efbf8773c262fc5ed62ba33d5c1e9c", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "859a7e26ccb2b2266dcd67f15fb2970959a76a41404d1766bab965515de26853", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "3d83b855bcebaaaf8c20406b2e914c0b5bed5d55b91bb1b9fad0290f1563f7d6", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "8dfefa1c902c6e580babd02b28df45778a930b3f46c11b98f84bd56347248acb", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "31c6d4de7e72d84cc1b09cd328400e5e419a5f0f69bd1294e87825fc47c2f389", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "801d29645d0062ecb931a7104d565a514b249edfc336053d2c6500b5fbde79ff", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "2056cf816221cd06942c06ec738faf4543aaf40e47b3d3a6811c55b7e7a3025b", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "38b370ec490799731fede805a39fcb5fe6eee0046bde81925317f011ce9fb439", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "a252be7ad3fce24b82bf033e3960c796be7216fe7d5884c62f1bfa2325d1e64a", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "09c81cedf56e28bb08b50d12002976561b1af5457b8e52932de25fece4ef13ab", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "83ce5e364f253112608c85f4e674e87a4c77e9be83a96956fb3a8f0317a4a590", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "2fe260832ba5365170a8f0a146f56a5756fc1decba03400b7022097511445a10", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "1e4c5828e1e0892ac77820eeef22613dc01d93f39508262387467aa43bc201cc", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "9b230e6ad2190754d32857d5e8fa8e7059b62246d91400e4b4efb732ef5f12b6", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "9583441f8ada9850e8433dbc40562b3257fecd9b6dc028bd4ab9e3b3e18037a7", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "71baba477b972ccc2894f91b8ca52420f1b96fe8c469971b2969cf042d47e8dd", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "b37f61dfea512789c14dda7480ca08d08935b00e37a4d8e1fd81896b7562f405", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "83a0db4ac565d977fe5dd7c208c0b5b647235919be64ac592b69ec3fd1e06ed0", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "e14ca72172587767d187ad323037e1341df93ed4d461bd7e56f4b6b424ea2f2d", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "e646a2172a65f2347526e62f9b60a75d690370266c668c8ea9c8c225022d6874", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "667243cfe43f8519e15b09979d6d1a00c84fde08bf24c5ea63f54a2668be072d", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "64a012cc29f3adebed2b254770c7c837b1ab855fddef8e08edcdc1eff85e1df1", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "fa1aa3258073c9084799d9d4bf452a663d4afe1d7f551bb9bf379198e7925eb2", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "108c42311366bedabe228122351d7a31c4b31c9a71cdf1168261ec4c17912927", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "ac984ecbba921f33f257d2e9d9da66ca05e742749edee32464eacbd047f92dd4", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "c2a06add5fa58ff304bed842282113ff2d2c51b9ddeca5e5f31907b58eec0e43", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "a64f803fc3b80cffa0f1b9ebbc3de71c988861d89d400fd1db16b5509cb8505f", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "de1fe2e93b2facb4607b8b4cf6e038d0f31fe8f65d74c519127006d280bc72b3", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "17c6b9ab8f5afb971170d4b7bda5c7f79dc27bd85d17fa923f66ddce62c91bf5", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "bcf155d5508003d6e7495bf3370c5b844b6d1ed5a4efb547fbf5835de63d5816", "target_hash": "12a1514cb51b22b1eb8dc4ceecbb8db9c02c8edae196953b4f5d7ab83f22bcc9", - "section_hashes": [ - "e9b82d0dd188", - "069d38237347", - "d29bae4fdbf5", - "3d9560c2571a", - "bca12ef8f4d1", - "e6b8adddb94e", - "76c9ca7dbe9d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -84500,852 +29496,332 @@ "pl": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "b241220481a7462bfa14828420e552ca1307ba063d0e650987dce889e6e06a74", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "82d2d1012eb4dbdbd8257266208cd2ac95f92ef94df54678be7de922298f34ad", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "cf4bdc85389a929b6630724572663876a6100effd447aa26a7a3ffc305723da8", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "be0b1080b229766863ce44d414e7389ae5cef127a515c0eb0463add181d08a88", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "d9ea8bd19dd7f81565f7ba832d4b45af68876772fb7f89f8e2a15d1b86dc7b82", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "e341e01be7763d518667f5d7bbd9b89e21e240c83fee7d0dcdf6c7f440e931c2", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "f063d49c6bfedc63abeb72a4c83f30cb8745552ac085feb917183962d08768a7", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "23e391e6fa92e7288e561104e220d09c2df7e78dee0833d4a886f9e205264419", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "8912bc45cadd07f1fbfc5fd7e5666cceb8db3ed9e4003abd07aec8259b7aef65", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "b55873b5285aa4cfd2c4fe81dd818f9e41bdee207d80abb8f8b929886561fe45", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "f0653fc955de62f0ac98101a9579d3d6c3e3c480ad369692ac0ab13a0db57164", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "d46cd39c8c038f973967e5c41ffbcef9b0d5f613f0c5eb127bd15a66c40a3612", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "f8cf0768b0fcc9f0ff0e2833f29d36d833f2128720fec6cbc1a96d6f408fbd45", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "f42965a300ea9f9955bd294d66718d006e825acc2ac68259038b1debfe462835", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "dafb17b99246d1331e089511d6c9d281746ecbb6e7edf262c404ed01ece5211c", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "432368090170bd8ea8cce9be1048e261b8e47e3b19219581883c734955b1b0fd", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "4eaae8fdb1e307d74442a48d5f9204fe383db85a94c69ffb19251020aa7b1e0d", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "29bdbf9e220241bad745f69754ab9d197d23e2a61c3bd5b384002ff41374d718", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "297d11773a18d6fac5da427aaeaadb942abd7e19963e0cb9bb271ae5fbd0997b", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "d9c194bcab386b63dd05cdb36119cebe442b810b849a1bfb79a6bdfd7f67ec0c", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "d79a657d523259bf1f6a0e0cd9364dbb47f5611db7b51bb5c62cb8f7f1df44c2", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "357d88c425d9e128b23cc6512e5e8990e10159d4dd062c229aaf801b0af4a601", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "d0d9e6b8c4133cda372a71dcd968b4cec54c6477665d99f541c1dae9256f92fd", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "3775966a880cc96e91b1d1686b153d2cfd34bf4b025e8d9080dd3849eb832fc5", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "0bb8f1bb52a885bb70dc3278532d9ccf17a5cf02505187f78ed420cc9040ffe4", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "52925d941ef74f59da8ba24598b87b6d9c2a728c69b8926f162a0b3f977711d5", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "aede2ec94b735508ab8c6434712bdee2f6f850e008d146f8958b4442db1bcb8a", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "17d9af554be32716c909647f9f9a66d4f25bd6e0d05892fce6f9084e69dff6fd", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "e91c24ec146d718e43ebe3fba10a6677a332a0315fbf62d965428ac9e8ad3b62", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "a3f4f46b252a701615f8dd900add46e9053c10ee1ff4c3ee245022af115c90e7", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "317bd6aa53b1f1e329baf1f8e41fa6e043091187d3662fe0873eb0b99e3d4b7b", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "ca159755dd7ae4b7fc881b8038a3f78e45732be8d3cc1cbb74c24f901d606eac", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "2741b7887a258dd31ee505d634ad94f538a83ff3805293be5e4ce977bc211b78", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "51a0e6bf7feb03d966a8f488ade5c196fadd7ae4f792e7db02d5f8b0861c5797", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "9fc41ab88359fc09af4ba7312a2fca1a4e7a1c4ca13deb35790a87ddb8e7a5b0", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "75835b187c27098841872afffba777f191867fe37d8698fe4328c4db97914741", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "b11165f254cbd4d61b1bd9ca771fdb7ca1a4ff6322cf843ade8c8b25749564d5", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "801d5ff899a7d99ca9cf00d365c3df9de2d8e2b31e329e0cbf99218f3be3f4aa", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "4900f8ce9c6a6397ed4859283860160e26786afc64deddf2bfc81f9ba6a465ce", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "6702388a629312670a18e559165dbeaef101c56d80b3c6df4f4386474df1b863", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "ee9e4789c28b58e098471460f8f369bd1b65974f81742f86934954adf247f377", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "dc338c9bde3f61740d921ed594f5fcaa528714e56e5214cda8da69de878f23be", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "281cec9b9aafeca9e76092ebc2b38e59d0826b663586481383ce4aa1eadfe27f", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "bb987d672be7ccf760e9a2bbe0da25d6fb4210b3cae1945e99a5f37721432670", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "9666349c00ca0970aca0e4675da24b4a8da22ce658b9a52309a61c3cca1636cc", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "92838f319da76a129435ceb8f1a28a8602e251357c2ad49463e323881b4a8006", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "91809b348b666dd7d0760b13fb9a3820d26b640da4a6d82bfed8d328bf5c5c62", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "507fa10141c58b66dcf3f2f85d3c61eb04c84bce75ce8ad56d2a1a5b3215b9e9", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "5c9956ecd57d860d37cb48e6e0f25fbb6949fd9d96097500e66b8beac53cfbfd", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "6d0b4143f008bd755e210c6b3f092c9a55e0bdf829633cc745bfa4799607ac28", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "a908b7bdbc83110c86a98b4f445ac82d01ffd09a16b7c575723f5d4f1df9571b", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "1207c10cd5eefe78ae51c40519fc62e410b5c40e562232b99ed60e8669d9f374", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "78d04a6b1546a406cfe9dc263c5255907588afe776719293c3904c2e0fca8403", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "8900d9ae2a56ee8a5fa30a40d75b7c5be5e7103d8f733c5c13a07bc7b27b9f3d", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "cd35ee3148bd41db026edef600641edd3bd0d9721e740d35b11aa9b706303c6f", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "cd46e30b31c3b9850809639aebbc61c46323e488aa38db76564f17e35b17a796", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "f7bd3b057481dec60e5473168298cd4083cb89104910366008ba1b61cf87a842", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "648e6bfe7c1a8aff5861a228b80a6609947aa8b4b3c0cd0623c69d3cea25179d", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "71df271cce5db58a9f1404711eb0f4f5a202afc1efa79a63623c9e91fc557f79", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "36d5bf417e9c521fc7315ebee057fc5d092111a696dabfb20af5f1fffedc4105", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "d080a16201e3fe8e921c20fddf4f9b9ede2f829f716f79882035b89f60f3d535", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "d8437295e2e74e730fa8b274085873a88e1281cf90e6c05b7cb0dccbd76e987a", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "b8a1f18e4788a346a565c18e5bedb9996c9050c34a9530baccf4ce39388c9984", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "6e6d909ca6933627c411a3fabd10e20061eb8260b0ed5e2509b3299c6aee3301", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "02ef2cf2c771dcc2c4f84804a4d67e0bf681093058e1f2216fe735ad82f02317", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "aeb3c53a79cb1c6ca5b95cf409936b69d407dfef3d5d8d17e91194fb732fbb78", "target_hash": "2cfc58e6ee8159c2cb3a2b34cb7c9605fe1c35971aaf6c6d179103959ec08dfc", - "section_hashes": [ - "89605593fc09", - "812c11762e3c", - "26485b6d56b2", - "dc86fdadbd02", - "93434f5a50f8", - "2250a5c7f534" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -85355,397 +29831,332 @@ "pl": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "fd3960a9fd856c353b32469937b14e63debc07f764791144f887b5636262e880", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "eb5c1a56ca5722b9c63717925d45dffeb49f105a76d9ce31cf7a13d32f1406f2", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "1a87081ffa1f3fc513ebeff0d0dd7ffc9e5126aee6dfbf17e576e37d5f0aeadc", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "e89f1693728882014adf0038e1a60a937e9446e92f6c9d6d47e3b5faab4adc65", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "0af6aa5bac901828abf54ed57369d17b823eb47a81c138a1696e062c3cd2b978", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "7e3d9e7cfa68cb6be036968530482ff85b12e0fa7b8bcdac003474c33be6dfdb", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "dbe2f1783376b6c16f4cfc7530018d7493a8c2c4530073a42616a999655dec56", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "30c692904f3938b7400eeb2d5ec8515027fe40a06e76904d1259bbed81c9961a", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "1e074cb0243594c4b58b3f4ba3dc01ca6fe421c485ab25b7e2f201549b79bede", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "19180ca11b703a6fe4151bbc52f371c79a882350addf4f5b865c96b18e058ad5", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "d72793b2d86e66863a36e5dc5b28a037d3215c1783f3df6199d56cd8ee69b4a4", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "2da81ffb7fd5438e12590fc3f4acfba7cf8a7f74244cc2a4797cd44b06c376ce", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "96ab7c2d2ff8943a71f2acf9db0a3d2a5fb4ce957879ade478d1ceee04813708", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "e5df20420c7f532b2e2b2117f41062d0ff6c086f5be6054632c3ee3ef3ee365f", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "f9372977d6ff8db74113ab1fb026a75616aa871c0d157ea2dc10adc8f9b453d9", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "2904094a7a65efdacee310f43b246f594f4f0cc46dd1a3e1923aef0ce584eb7f", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "6718e67933862475c0a65518286a5dd313644b9c66bf07a77f870224f8e42027", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "b31e3fe65f064dc75c3c9216f5b09acb0d7956eb307dfd22b90cc6c2d2dc7911", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "59149f625bc4951f62443216f2d78786214b7fa15519cabb8d8e87e871ec9509", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "bcbc2dceeabb3e153c02a147e717dee97af74d786261e415b46537674dce708a", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "5c68f0d1d555327b5bdec818f0f47148768adf966fc81e7ffa20d23aa9376697", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "698c5657c77a15bfacc9f7b5952ae967827714d6075a2174117cc61cdae3c0c6", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "9809e444718b0eb1259443c65ad050b334401f3d5ae9e1f7330a973ee269c52b", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "51c7f89cbc6b794b6592631cc35c2deea31e6e5315e640ff2ba046794b40731e", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "33e96c28308238dc72d8ce57ab7a0aced2f98961c8c8c7c46b52989a690076ae", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "b3237bc305ca903893f3477320d615b11849d69a263f757aafa384ddc8870b77", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "3016bcd23da1150560bc413ab2d57eac16525e366684fe299583214f18d77da7", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "5b1f723950b5290b6bc42de07abbb0c366b91b371ddbfbcdbbb04b962c65daa5", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "db0b63bd2190c8077702bf6351b53ee6caef987c42245eedfd50fc89a6e7cd64", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "901d89760bcb3ce33d1369a83cc0c62aa8def221d341b421bde1b93e3e94468d", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "be3aeb2a54c965dbb3b748f27ba01045e07a01d0f850faae9eecaa1aa733528f", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "cf4fec36447b7e46add808557253a9b5061b52ccd8ecfe5fb74d801712b12534", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "9c8ad89320151f4ac7832d64ed3f6cb5f253a5067582f1f03f10c32ab1b9c4be", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "e7023ec7a98afca4ef4032c85b80ef8d6ae9720b879c737b63aa43fc38f34f27", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "ba1fbf5e661102a6f90fcf153241bc896c508dc983d6caefb24911c1bccc6d21", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "1388b857b7226c75023aadfdc242f3a8d6535908ad06aeae411d91ad461944cd", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "d751897fae6a415b35e043c99ab2125e28631147f78b8c72247cbe6372af6d8d", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "1c95817599b8bb413100ea5a1e095aeb6b5eca992041e38d5ec952ff6ff56ed6", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "8627a3ada12b0f78290c7a9e5a9f7540e3f7709f75a2ab151ff66f4d3c001aa0", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "21925b2b6ef9e1224386789301bcc699db827cf4603b83e38d0e97a7ff2fcb4e", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "93089544889a75163ee0e079e86b633d61e5adf0da84024550eeb7f76c094c36", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "34a055f80dd6bc52ae3e46c3460b114154e9b481c55e5c45af7b3f8c2211c182", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "e73ac374ebc3c2a2dab92de88960e686dae013e887e20f5bc9068d3fdc0a4d0d", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "4dd4dea2b4e062383463e0039e5e01e1a61ea7656c680a583506b2c7ba908cec", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "042572df5468ca98f0fb507658748342f46f5de4d570b064c0aa051bcdc41e6a", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "1483a0fb3bd6dde5e663faf29dd08a931c726039296f13c0c7ec71bc144ded2e", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "28589bfea9becdd613d97d5a6e9222f2a364822c6cfe45768bd608691b319fda", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "6874f1d4258b499e0e449e1166de33d5cc5d1e39ed986e9648e35bf6bb1cc7ea", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "5d06d73a44b21aa7020400b43d2227514d35ccb25aed5b18c1fd44c6dc08535c", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "268a32dd3b45b8acc5b84dfb94add101cd124b4f5403fb46e1992c190baff85d", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "e5aca9cb12478ec48a84a85b1bdccdcadeab300a9d559545aa3a24114f312b57", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "47c8f668a6c7241416f6d6141d0b0529bd63dd1e08a9c1f4dc9ee0d5f13e4703", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "0c0e0e7d1ef996205053c9c6ad1dfd9e2aa5f31fd6d4ec49e85b75ae11c6774d", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "f7490a446eb68edbf0c68f8c4614393a1312ddd39346f47f52f2bf2fcfd879ee", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "dd05ce19c2de43995699d2ac21af3a3462f30effcb433a34a3959bf51bf4d0b5", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "f35b3a5cd9cd94839cffba73feeb71e9cb597ba503b0949bf56ae02e1f9d59e3", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "9c0876586959ecb56baed51556e3de57398e4e28c03bccc9c976d3ae5ec627ae", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "d21a3c016dbd1685d90c6162ad6b7e8e99af08c15d25a0b8ddfb221fa06ec573", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "f937ea714a6ac2408269ca7427e2dd9661afddbb69033738b4248f4a3b81dcbd", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "81a494fca4969db43908617486d3560a54700e2cbc3d127c2ab660e7f649ac9d", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "64d470058150efde0ecebf2a67988600eb6cdb09e0c7c655001aa61a350e9697", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "79c182c14994d29f5aa5502171fb0c9da3f45cd39e014137c51dbbe2e75ec841", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "b18e0f1c1e7999d28c6e7e32aa7ac5f82141cb0f77c8d31ad873e057c9ec0788", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "8e3120a696515cea598e8e50300298978b98960febcc4af62e5d05ba66a286cf", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "a01f18f572f5ade11d3da309c6ca1f7786750753329c354eafd3f3531fbf5d71", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e1ce17414e65ba12c6a49d18149cf61d2b84d89a50e32df41a67a8a409f1816a", "target_hash": "7bd0fbfab234978272234861633bc6e883effc279b291558147e79faa7478e95", - "section_hashes": ["8092d72df715"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -85755,794 +30166,332 @@ "ar": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "c8ccea8af8d206cef9a5a3096ca60add3eded9cec3260c161933e66762e4fcf1", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "a9ab75665eec6fb7efb2e4b45ec8e17e1052e1711a14944959ea09b660cf8f21", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "2f501baafdc3e2d23fafbac214e25421456f988dc0cea16434c5ac4d5bcfdbc6", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "379c81f1c01f2579e87457c841374c831701f769b86b4126f8e489626b7a522c", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "39bdebd4023be454d447a3db5a3fc1222bce96798a4c024024fc48ec6cdf809b", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "0c4ffcd37b0de31d066aa3bef4eded4a049cb3b7a3d15a562cdf3905c9a2c05a", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "3c66b200d85d01ea23edb4c949d19c25bf0072da6c4c8456450c0f9648d3b26d", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "56c52c793833ce9cd3eaf85059458f66bc68ecf96c2f118b3db6a27c0dc095a3", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "7cc99875a03577648173de2891c4d6cee6d9865ab5be62edd33d4c4a8b69eaaf", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "480bf36da20e99eda6f9a46484fe8985b227223e394bd83e281912fb3b88d482", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "400d1b66eb5333b3a5c92319124bdf11655d841c3290d109908074b998216056", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "bdb8195fc241a2d4159d823c6bed110ceab3594a6d46cf0c5ff74a671aec7774", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "e3ec64c047b941b544a0b54c7155a17c134e86da756c39e16af7dc100827756e", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "b48fd08fd2c9eb6359d9a66142f66816e578505c33b5a092d2858eb2491b1bc0", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "bfbf52bca9f192120218d0e5b755b7fb9d41cb4fea769e5f5912d3be8e45ca64", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "66e888ca3c02a13644c5c7f7cebd980aabb99f83809c38b295873fb6abe5594d", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "8ef6fc857430c15a8b1fb3f6c2acc8f24c8d04f4b8a1003bd415fdbbe102ec0e", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "55045410ed0d03c5ea9587753780ea026d8904e676e8ccfce48fcd8022fc208e", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "0294378aec2c06409315eb050a52b78b5a99624d1548094ab061475ea7452c9b", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "501b9502e2963e30911f585a6b658b87819643c1b6a40873c6d9102c9ade4fb0", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "79399637d1dd643de8ce1641060e8a1176b1ea96bac52db35ad75624fc8d6c16", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "db83efcdcb56b3c34d705b2f3cbc1c7fdd4ecacb007af5d7aa81d0ef1a08351b", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "89073868ebd3ac56116c539fc30a4983d0b6af1c5b4fcf6b9edbe2454e7eee4c", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "d0f59695b6d92d0b4d69afec7ce242460c8a30fc6630eb71ab59c4bd14456407", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "9e0e7d8f2559ee8f1bd0d1f67aca17161019e7c13bc2cb32d543f9cae9094426", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "fd0655af6a6ce765d29c43cbea2a9f20515af4e19c01a7d3a9ac96dcaa97f86e", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "f547e065a7ccaff2cc8477cd47895abcaa8a9120c9a016914e3e1eea9e0cff7c", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "3e6a547914086cdab347cc002da3fb1be75d99fad390985c7e4415e37b73c956", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "90829c1ba7e7b03a7a09cbbd722eccd32967296e9186a6e51427eb911134359e", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "e3b86f7f72c9a52e800e85c2a57ee5fcf425d9d2b3d148a95a83f10bb46308da", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "488f57823bc29259203fad042d46df5db2c50ae2a42180c6bd482a28c4a9d542", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "746cd95e43af1e1677b72ab206c24e40f62cfab9a6f8f77fc369b568ae7617ec", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "bb4ba7c0d906c76972b330b827324518dad3f2d590d6269ba6fb2addd1d9cb87", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "e163be2711976dfe3663fba4059769bb1e3e4ef7f339e2a90af7d87b9da0cd1a", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "d7624c4def567df7e3757d4e66a3a8817c2aeaa6eedb54de7b492510d417d12f", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "b6027c1bc1db62d7b394bc46c1b631ee6497f60064cc70ea8ebc5f54c75fd70d", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "286c55b2677d6e2b002d63e47639d4d10d44a183c73ba94a7a7b7363cc154b4c", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "6c06463648653a3a3315181cda49ac0febb7eedfda83103554e266a05a8de2e4", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "4eedd4471e787b5c3ca9ca2a2fad55a08ed578f6aa967d60922ddbb42bccb2e1", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "59e96d26ebef3e8b9388ca3331791a7e48d31a973bfa9317d0819f5d95d4a2ba", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "c6aa3f408db7c786c00d398dbd9fe8705c80b3146b5e807c9bb35dafdc7b57f4", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "1fd3e3e189d50c127701985c151146157ebdd09e56dcb57a0c6022a60fb40123", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "6c12933da4a85bdedf341ba9321643fe0340d75ca0d981ab9cc1ada2fa248d9f", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "eb0799ad859904f2928ecc5508336323bba5c938379aaef0230983e88728f153", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "64f94f47fd066bc8538f3c93afe11b0a2e4070cea7f9155c27cd5f927ce0ddf7", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "acd39fdd7abe45dd39017ae70cae39ce2c289f30faaaea8f221d160825ff4aa5", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "1d9caf24b1297e7c8839f7323bb3e6aa0284dd9087c442847bb47ef39cbe2ae4", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "f28e52dac11ebe917eb512270854152fff0ab15680b0927f04a25a33eda7e623", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "9d0bfa325d4bf91b5d09183fc06a96e6254df15f2f9f2748c04a6c39ac76bcfe", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "9d80cdcd7107144e20e44da3096e6fe752d244d89817c2f6d673b76d996fa3d1", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "6c3e3c72bd86cb30ade03146b8e20bc688a8ef1b1d2bb41debbce4c60032c7a2", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "de0870b9c5b25b8fdc45226e09c3c58de21123fb0dd7d2f99c76862cd9924f67", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "576d56362e835049f6f051d313d4fb6ccd1435969dfea4eb31db4aceba52234d", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "71f3fe3693399bf3b95aeecb869ca19bfb9cf6d516417f742e2a22416663e11c", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "0aa947cce2196bed7f036187894a42c19bca4abab9faae194511ef298ff2e5e9", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "aea17625278263a57bdc396cf6365953cf31914ec5f3ffa31ae21607e58c7112", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "191a7fb0728a466527ae29dbe3e4e65370377b700a1fa9b685dcb89c1cb62041", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "c48685cae572ecb95c364f2e64b043f41358ff4b781db8ea04363b6359b0915e", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "6a55c4ace19ac60461341527829ecab7432126c023b3b037681602819a4bd658", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "9e231cc9c4cda35e101d8911d0f2015ad9581139584398cea3d81702e112dc42", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "8437c6f5d7def42c989b74d417edde2b6707f0f1495cfc3e35e74ec368466f3f", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "fdc19b38955324b477e540c887847b1f860fa88e302ed4b1818f1230a7b4ade3", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "23def51d4a7c34adea8d5fd445ff7cef22488b7ee04f865535f226ebca3193c5", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "406dd1f678fa9d0e97583d2a7637743b499f6b62f1ec7c6f2d134ab609262de2", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "050df55aff6d826ad7d09493f040fbc1b94366e222c9bb8d914df4e591e7df1b", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "c2e19e8fc37c85ff85c24c4d638066e86bcf1eb784b06a1cc52c4ad2b51c063c", "target_hash": "a1db8f8f42fb3d467341df99f5edcc687dae9d39d1dacd7c644b25c768d7ce67", - "section_hashes": [ - "41e6275b825c", - "cddf3b39ce03", - "46c074972100", - "0e1c707b80db", - "c0414f8ead81" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -86552,1058 +30501,332 @@ "ar": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "da892021b3045521c2beda2b01041cfdb1885fef5289591a8ee7835441d6e187", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "31ebb46b2c6b0a72dca6b60580d5de669b8a0df23286ab110bcd35ff203792af", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "e4a13bdde129beb068e59a765b1dcdf467e674c0a268c0bc88dca9b742355e65", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "8474a3f18bb16520581231eb25a8151fdce0af6a09e836db624ca505e652960e", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "5a81e4bc086eb7d96892ffaa98ec2ccc93dd4f93e614936bfa09c729d6ba92bc", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "b2d7fd34574694a9a09668af94358534761723d638759cc96c5db9fc0d36332f", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "f702c1b17a334c86974242588f7cf7a4b78dd40513518ab5d5ac697868d08910", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "d9a4776420f5552ece7ae63018381d0a8389fb94914a057074d6b184ebfa7745", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "eb2a83a2a6b7147192fc794180ec338801aae7bf66e6db445ef5ab538ef795c3", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "451089e3a03f89d5c0c858e5d595a3d61d954ccf675434f3e873de15e9e0d130", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "c43a30a37bd61ab46db2994f07d0dba67a07af6b929eef614b9d2312879e8184", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "62812e75d450d25730660a51cfcfc5004c7aa9cfdeb3fac1ecddeb1ec1fd47b3", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "778634837235debf2f9968784107602949ebcab80a5d8aaab0f4af19542c5882", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "7bc5cc47bf666af42cba6e3e2cdb1cf79f4d092481899838f434651ed154fc96", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "e24efcdba8902e27678bc8146b6d1086ccbc0364a5672a12d4e08b72cfaa9276", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "5f4908bb06ffb30576646a5c0de07f09ecd8ed9e394bfd593ff959f5125edf47", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "1a79812f2b7306f24e7a282be258a085be55d3840e59db5a74f3a49d0ccdab85", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "ec33de4b4581dd2702f9fe78d66e6b6da2942fa78c99f8e6d6a7f6d4cb525d8b", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "c3dd80ece0477abf5a6ca3584fd92eeda98c55518650e81e34595dff86b69453", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "b275c253006cafe72c0a319ad05da775e849770340d7a39e965da0bf139d4a99", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "7a54d7041b4b6b28fc945697201c24e550b3c7d32723cd7ae1c20a018c2454d3", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "a0aa2057c8bc4e6a078be94f9eb988cfde3e6849d7c00ae260796f5c6eaea24d", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "7caa811ba2db4281d4f6e13c729ba4e66bb84e32fc15c2c7fd45b112522aa514", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "d69c7987a749c4152f2a5264eb8db49f7d64be73da2e291b3803f188dd0ed9ae", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "fb779e9596577f564bfc6d901cb7a668be56fe3e4701e8a59edeba59802bcac7", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "02dff06496c35e81a5125c767a7c95eb518f46bbb77596b71a787a525b33262d", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "8738ded8d0cdc242e956d9a3d9fbcf1f67d855c9f61acb3402ab1052bef4cc3b", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "f794e61c35e0233080f6666aabdc0cb20dc50855fb5b6698c9beffb1af9e0944", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "e683d62916f3c41e04a00c03af29ce6c5098f7cfe14859ef0a4281135f3f290c", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "130191f29ef473eaaee6b8383b2e1900ce6406a10bf12906cd29a31bb333256e", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "0b87d57453a0cf96723f847851be90cd2aae7711bde596381e7b3a0d91fb765a", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "61e240ded75f2b949eeee2f480642d6e08fc850b00432478112ca144e4abd3ed", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "3a70d21e3c280ee576f6b37660e720a41f496dd34b80bd98c7a22530f2adcc43", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "2090d5d5165f260661bca0bb766dd727fa73a89e681802c3a157506948f7ffda", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "f9a20efecc50efe96458c6954c3d7df2d73fba321842e1768210f9d25e2302af", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "94c1860e46e8c6ea1b0be90d1ed2938d30d0f423542fc505a5199afafdbf12ca", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "fd5865f3322363414300a5acba7f57df3d4177705afaff9c7b475e151d9b5c18", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "0783a531e682679df64e0832c2ed4e71866048d48c56f6cdce20076e6a1b6187", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "571055eb5aa0e37db9169f8444a601dbe950258b0a7df4b4f8992a51fb551163", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "49e55968f634414edcf07f0e25793eb56ffdd1ddacea84a51f5b6684b6cd85f5", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "8460b1c6584332a82fa7b9a2e7366c93541e50f825df1db3283652ed57084b57", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "98dfdf82d7ab75e40a08dacd1ce4595d9a81aef8a896f4b1248be0e89eee9670", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "40314e75138b6de96592bfd028f2d9a933da8f90f29c04084e852cc7dd3fa793", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "08da409e265818620a02ae2b39d9c7bbbf3a998cbcdd134ac800186c4da5fc83", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "eb0888194d7815dc8db4deba9a14fdea5d480dd5462fdd83c5ad0a685d03eb8c", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "07fe0e18aa2e6829383bc8d948c04652e746cd330f935037466a45c6111aedf3", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "7cc7b70703bdee3048f85b655ac1c1094cd66b22105d5a4ef5f2ab0da84f3e64", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "1aa529c9de8e0a5dc46ddac77a86a68d01f8486be924198593c3377a7d6ba5e4", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "44efaac479e46ae2b22aa3c56b4078195e8b1affbffcd42b96e9e9888752c7cc", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "60db554e223f0070728e053fac97d55a8597c74cb2ed9f9833a9ecaf4485a0fe", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "d1c47d1313fdf19fae7c487e85816a5c49d3d3bbb9003c1cdc0584d4dbb92baa", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "9da28c4a2d940d9293b047f4ae93510f6aea27df41a3d8dcfc1f2b936aa4ae68", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "bcbfaab8f68fdb10ad6906e3f760bae6991b2597ca9f55bf30108184fc5d235d", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "6f8d7ab471215396aec65e0f501658c2175df5916d2321e4cc7cf9ed3def269c", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "e0a237fbe6050dc4f5f6c2d52cadf2230d4a36b31de4ea04134107b8f38d5019", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "fca41566f1e730b66a577737a82707e244812b760a110e6afa79bc39bf3b08f2", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "f0f230ea4437bf05b3a0a60cf8ce89c5e0e2fc7c9486b91a319daa288a1d3fee", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "0b3276a56630f9299e54bb0f101a0f73a3593053438e09e7049a65c92cb77cae", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "89640f7d2cff31e2244855668800eefdfb40f633bcec8ccf8df148e0e041c818", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "d6c071cf7933c4a9c398246082a9bce27fed49ca40dfbc2f5a58b2b740174b4d", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "c55fa93d8ac0a06bad405db1a60e46fef9e73d201375c13ae65779bffdbb2aee", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "1700d80156ea23af1292534b04e94ab7ded1c7e27fc322664c16297c1a2fe084", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "f36f8d36abe1ec38dbdb15ceb5b1754009fdf9f6dea0c34b0f1dcc31d46e9c07", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "c6aa39dc3b972151bc86f434c2abc291950291fda15e59e8066d4ae3a431584f", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "d27dbf60a81068aa5f63d2331c0c3fc0432aa5a3050e5c60de51a4ff10b7cb9d", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "c4baff7b3acbf3851df536092938b2d75b15c1ea374a7a8d55b8a7d08f72f775", "target_hash": "87a65ced5cf534b547788eecbba12c6b6c2c52d987bc7357ced99df37c0cbf7d", - "section_hashes": [ - "20733e87e9b0", - "369d4c764f5b", - "a9852b865b72", - "3c78ed4ee913", - "cee483b4012a", - "f2e9950e0df1", - "3732f8e6ad10", - "0368fb6ed20e", - "6d1c856e73a1" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -87613,2977 +30836,667 @@ "pl": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "858360904a522c5d8afcb3432da00126967ca593612c86b90754a1efd547abdb", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "7fefbedb761005ac13d5f85fd5a7032a6face544211dd102839560c62865f407", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "ce2c7569d2cb2dc1283673f1faf98bf27fdd20fb01b5dccbc714c2b19701a840", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "6949f814e776c014de568cfa785765b1db10907b4840db300b05ef95018e5015", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "1fd6ac2477f099397c0888adc2add377c93f334da4c18ecf639206f56c17c54d", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "b8bb3742d05129f841a3b74f06b8b2b85ed81a63fb31e135c276ad3688322562", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "260fcc4da06ef05e266aea326047c5f7464b334e3e8896ee02bfa3494f9f996c", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "108c7c67f8d4fc999b3659ae361cd57112208fe7a6f80f948293a6a77351f4b2", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "3eee06fadbc26c86a5fbd30a9c2707a7c3d0f0f33c00085548a7d0532c22189e", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "76a1befdf49a5f8a0285a450499543109644ba8c2a3c1066dd58e0f8d85c84ea", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "79aba1c1099418a2c99d5efa0c043223af80353d4af41b1cfce8f95d8de05d24", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "b717b8b2390479201498f6ad2027dd7c8a5b7c995d6664b5617c9ff37dd290eb", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "844b905674bcd0cc1d0989db08b4bbeaf64083fa848a3ee71e9c1c135b2e1fd6", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "16be8b4e9e7e506ec957b9e18e3cc9ccf7b56cdb3c8675bc08310b8a0cf5fe55", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "3ea20ea81c76cba21441eca37183dbd7babb4178dcc9d77c521789b9dabd6f07", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "29775a3f5a8b160df1a130b80c349298a7c54bdc0b45c1d9f3fc47437850306f", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "b16ed8dc2fa6767058b775156cabed1fd96e630e7832e41a596e20037099604d", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "c269e8352e52ebd2542716f15546fd38c7c909a0d6b9d132e13ad9f41ee11240", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "f689ac7d7c0ef2ab515f3345caa6592f58298ef61e7e5566ac0542378ec099e3", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "2b340388c87c4839e215328308943a63f8f3e6a1ad39daa4964803cce3f7b76a", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "ce10f0a164701aae72170d375dd850390b26e1398a1dba51c20463cd47577edf", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "25f4d47b03e0d6c76710e05d120d515472068ed7fd7b279597151ccb9b1ae656", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "fe6f35c269918f7cab5ad1e606865defb92edf7fa84be95ae1306fba446a8319", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "3f232751e08f812972838e8f2bce28f5672896eaedcbdbff35d23371f4856eb5", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "e1ecc877c5b66a1df659fee0788e72c9da136233f30c6e0f5d3ab13d2abdf151", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "bc60db2d6a40f4d65d13381c99827b3a2167c6d1dfb4c2836dcdef7fa1c529b5", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "e5b7d1929fd4ad21503c0cb109d080f8745d75544d989185b5c2792185fc401b", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "7fb233f821b987d7e56aa85b9bd956a513b2836a687d51c7ddd35ad254bf6eb1", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "26c029d26e2092e429136c36a798cb2f22ebe06acccb51772748ff7aea6d5c43", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "a624bee26b599dfe8c7cadefa097dbb1472f2e2f6a6cea16c0413ce00d2e7b3e", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "aa5907ec7431fa8482a996023f7186bdf08ebdf1455e9229b3245c26fc2770b8", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "708715e2cabf89c0cef524f09bac38afbea43d852d880bcd0b3b5bea05f01243", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "9f64cca9eeb2203f69d7e0c289199a4723609676d382f62dc67ab793f56a98d5", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "28f8122e1d0af33eda1f8f2c742acf36ee87e14082024519cdb888ec9ebe5b99", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "d7e922b72f6282d55816185d27e8cbfa7aeaf81e7807f9ff126c59e40a0852dd", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "fa833243b7b4f4c77d9b1c7e555c059d86737dbed5fd9f0612e8721320fd7bbb", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "48b4b82f3997719f39b1c7bbbece4154b6b184b152e2e957a5a928ca94991f05", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "9912f9044bf5b6a698ea46705b2c354dcf823512602eb54c25cd5819a2dfbf3a", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "12bb8c7c1072e999ee7c23553a627c27988ba4c7b4c6a3450098df404fdca74b", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "e3b643dc510535c56ce6424ede4ec9bcd3a2bdccaa0d24dbfcd90135d8e8bcc2", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "f249f6fa0bf177b1131b87a8659480aaff6af6586236c8ac93e3b7a17ee18740", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "e13459e40216bff3b7ae31fd386dd11cedccd0268ef7cf59de6504b42b7e2461", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "f60cff64cbbafb6e2770a8608534784e8321eb52677d8d9a55b09b3b89b0da24", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "79be6d182bed87d751fcf14363dc732182829507c686cad6f22c4297606daeaa", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "aa64d09c818b2811d2e17e9c9ebd3a847b090b7d0fff59398bcf7205c1ad9ace", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "4125f2374cb2fd6d8c3603e9ebd1607fcd7164b922a20a9e90702c735480ca19", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "852e734d0dad8b4f40f296110f4d49954f24f1160290b67a5f2c3622273b8764", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "0a142fcec259768758656b9f37cca2eae67dcb75b273f97d58758ed58f0ecce3", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "a4fd115ac78e9459d2d4e1e4d438e9b20b56f862a922bb21236812e4d5b12b21", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "835b7f92b5c04ac3de2707e1db9f3a55ede8fd3d77a99326946d67270c6f37c6", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "47201721989577b30abb489441df4acc6cb85bba4849e10c32b2532d4945fc23", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "4dc7bc88974480de642a9bfe37be11c5139041630acfcd0be37ded3112c23c03", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "f298f197e41f05b3cf7f64d689a71d9cd7f85fe642afe8d94e323eadf309da26", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "aed9e64750dd01f32a5674dfb17396a401199b013250014383a91b7b42fda121", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "76f131542597c6a861488d9b37f5527bd9eaf029b2e32074ff3e4e50aa2afc5e", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "f535e566292e179e7a89e87a78b888e6a949a0d8898292a58acfceb91609b344", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "e3579f768b00139832aaa03da0ae5c7dd5ea7c8b262f482e48d3d2119a39913b", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "45765b3c11516450264e5841fdb7c618011e90c6d44689d47b98665b099a45b6", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "0493e9f11b3c0e4a6802b49bafd7c9d81f81bcc900c2e86ca7eb634904cb12b7", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "807e6f51c75b43bc13498dc055434f85d01ad6f7635ce655f3645bba3e69b887", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "a9b8eaaf994fcecb207d35f4ca818d3beabbc18efea9e555e3ce579833d0d95b", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "e6152c7b9114913c4345f1bfbbed422d20f2336598af4427e65da106ef64c4ac", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "9fa9a74ce23ce38dbcf6c36e2a1c07fab85ae3517b433620ce64ac46febb5a33", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "35240b32cd2408dcdb2a259cd580e8cf9a948f2ba157a7d28ca855bc4a0dd47f", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "bf96115ba25cb9e8ad6dee1b948475de608f92e65c0f72e4ad336d3b17a766f6", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "19a460c34885bacb6c2dc3494aba5da8f252b3c4bec0e51f773f8ea15e8fccc6", "target_hash": "5a64190bde57ace0757299d5f49b32df365cd37d2198e1662336c4269f8a58c9", - "section_hashes": [ - "e80713bde916", - "ae980e243b5f", - "abab941579bc", - "5bc697d82e62", - "082d514c92cd", - "cc863cad413b", - "eb04f963fea1", - "7258fe4e3026", - "6897abc4a510", - "fea702f0ecdc", - "1439a678a1b1", - "f2932f9aa6d3", - "746d5c367887", - "42ef31925f26", - "1089ca6126f2", - "86d5fe5203ea" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "locales": { "ar": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "89587789efedbee6a1a2c107f9fec21e9e2079aadff01d5a24e836ce76548361", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "d9a84849ed1a1d4f573d611f7772606b4a9c8a92c1411051008d43b4bd1a2b40", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "31d0734a0acd24a6e24546f1be5e721946b019404714eb9cf5835f6b1046f3ac", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "3ea21f23ba9a160a1f261f1a47cc56203ee51d9b81273a141bb6ef17d075296b", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "10460cfe552b1f0ad2e717a39d4bf3c83acbddbacd488ad67a2ec9f5afed9a4e", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "3a67252db73299a97bfbf183a14a8f033977d36b9a88ea32f9caa48f3071d1aa", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "8bb6a8016f9afc696f8f3aa5e423d0cd81cf1e070027ad667f4125cc2dd11d4f", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "0b28337833a478290279763ed155344133b0c8cad215c45aaf8c976dbdd8a2b0", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "53d8e43fa7b022e49ff6dfeb49de5a8bbb6034b25b11dbdd37d64402e9b2c997", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "0a914bd1d8def64a72d1ff7a6858263404900f996f8059e06a90b99a8395ba99", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "ef6476e65500dfc2433716806a4c4a816cb56d6739268b5d67549a7a09361b3f", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "09eae11b125b4175bdb117fe68d19e99a79dcfedaf54e028bfd577a3bd498818", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "9f03cdb24442dbf6460dba95cf27450fa17aaa7f5ec8f3b96403e1674349bfd5", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "04bc78e20c9a1c9e43bfd42f3cb132dcfb2b841546f05e235b81db51a24e8d43", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "db1d63e009eab1d6917893f958d66c7540c5eb99cc4cb06fa21931656040e777", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "2bfbda0b285a6a694c32579167829b330c0f86a480d8c479aba33a59ba2836b0", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "bec1fb60f300ab2e64260402c77a2c5df0f3129e5822daf6b86eeb577e72115d", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "234ceff1b50e40864631f97a93ea9e833a12e8a232f80d4c467d8f1500630c37", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "415cada1d5490a2a84e0b8fd841719c2801c8112a28948853cb2161eec873559", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "4978e2a0318d30657868739ee5484db745ab8aeb460a3d00c53f1f585e001c1b", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "f42eaeb8d63c761327b33986e1b547024f5531904620e39d3fffe4b59571be13", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "2504e2103f3c326300e647e84a36157a16716754666a10319597b873ed419717", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "fbcacfb26f77128b6aad7d1529a241228251cc4cb027684185bb5a9bcb74695c", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "1e78aabc8c8c9c12020c9496493bfd0426f0e993b7e6bc369c9a61e7490bbc76", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "7cb9fe2891ef431333d4a1676ffdc6a477a7a421cf984c5a83639b4caaf79ce8", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "ccde8e0eb9dfcdc47e732ae7e6b267317a917a97499068d0f0d9224cd8b0a7ea", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "2afe7597662947ab82404dd70ba133ad776fb11d98d6050cce17f5e1c7c5d4c8", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "52a6a337e08f758f06d9acdd117062d455d55317d03a04002cab99b604be9d3f", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "eb8cb610cae7f9567c4df9eb46cf32d1ef1e97936b0824c1c5c48b719e26e35d", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "579c09ab6391794fbb54f5345f367c31a0513c1880066e8ff80f4574193d9871", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "1496bf64a1dbe8371d99ad29f7cf86c49267356fff11e5c1588d4c4e8883f6f1", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "45acd07fc28b278bb8a6aec41bb7c57ebd6dc0333c3fd575938d0e4962db5491", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "9cd4a55f285e0a16c9537167d025e33a30a28166b0b7f143adc7608add3fe8d5", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "45e24ac4c2e1edae6a85ab891d59afc5e58e48c9bd53404e0cfd1aa606573ae3", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "c0b9c7b47fd3dcbc3344a61f70aa9a810e3a6f692ddcefd7e2a689f830fa20ce", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "4b03a730004e5645614b0e16fca3cb594a2d665a9986d25a9bc76b4042a8e2c0", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "53192c3f80f1cb7c615e82b87d3e46155be9c78d0b57749435831bd6baa66f2d", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "00aad117b56063bb176cc3461153db5e7cbd11680eec059e166548677b1d855a", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "2f0db2037ec7950801eeefa9fefd44446a01bca098bf5e715e3b479251695b57", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "4034b355acd0b1fe1e20c3ef760d677f4c8c445e8aea91c5b8257e55d3328850", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "b6463a19a0fadb5ca2dfabfe7c196a2518858fc0d817247404cb4a523bcda742", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "1a5ce7f900d5fd67195266771d892029fe429f3dd39d495d9a3aede5754c2b1e", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "29d8142a1ed5af425a3480f5a0bac9ec04cdd517900bfcb4884ffc68def25c27", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "3ab9417337b0fcaff9f1aa4e0242c409a2e7ee477ad72d57be30475ad9ac5710", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "21cdc7f21715a64c1fb591c9bee124c327c05b8886657ab86318cb997c6b403f", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "a774763dd3df8e9d66a711d7226766521325dfe6ae1d7340a0e48007e2a04063", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "c494cab14e43480ab8ae8c3cfd751264ebadf5cb776c76841d351538b03f6376", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "7f05fe4a9c767a991c3c0c6ac85bdafd3555cea698f0aba549f1506a6d1b1280", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "85ce0e7e67847f164c8f962c09df86a4322ac535b34df7783e6b6ba159f5c93a", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "4357a088e0b9eeebda5eaac9682314ebf01320a67529bb4cdbe44c001a36d310", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "fdf4f511cceb2958cefa579f74a35b9834123323a330824421aa6835f5f5bae6", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "d910995fa1606daa41758126a82fd4fff636c124368e45ed344c5f5897440646", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "b5dd74bad919b0ff3820e14d762d632ad838a5721334b621e19087f43de30a07", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "8a59958a056aac3fd572d7e6e48856810a90096de48a5283b3151e027edc345f", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "4cd179ab0198220ec4737441a56fd568b438e6367e54df8c63bd8d57a71681c8", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "bd70fd462d25d92cdd373f1906135924d38c8303156e2261d4ceb4ee206998a6", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "eeec744c91677f42430f54babbd4cac09af5226075bf1f9f5c7a048b8dcc728e", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "930ca1a9aecb6fa1f841bb9580735b2f5ab6953353daea1f1a8b5cfc327efb74", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "7d6308f6e909310862fe3c88e07943f7b0bec0ad1a8c7cd3b78a48b8a0248070", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "7a5b2e8674309541f5d053e08ca58165c79573168dd0d8b00042220f79a5aaa8", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "254a341401bcdf139f34c12c64166da7cfae998f2e3c6c0ebe3f2307b73b4bc1", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "bacb92dd3ba1e0e2b5c74f94cea0e7fbc608e0b70fb7572f726fb8183f701986", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "c6bf3cb8dc5683bb2fb5a5ca9ea58c6539156e7febd5c85c0e3f8e4822b4c44a", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "7d1e99963197f8316ed6463a104cf0ba3ed36e240d9ff9a7822d8b69113f6ed0", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "126257644ef763a6df654fca0e438e257f8bc4ccdc720c26f6bed86f1251f751", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "bea4a7166194e8ed25e75d91deb2cff7f6e370abad1265258b60ffdcb193980a", + "source_hash": "df4bd69e545c6b6e0670d53512c53e1ed418611ea5cf04ef5e50311b41113b4c", "target_hash": "4502bd8efd34e5dca4114fe305de09477ebae901a2ea11e01f7c9a68d188df87", - "section_hashes": [ - "82d26183b47c", - "4a8fbd3ba2e0", - "a83be677778b", - "b51cde099451", - "5b8c0bc448ce", - "be7a152d0f49", - "697fc762d052", - "ceaef3204be2", - "67b77551f9a7", - "6baed08a50f2", - "36bd2774165d", - "1cfe6bbfd241", - "9db4839982d1", - "c7bc1f48d98a", - "9acad70ca7b7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -90593,917 +31506,332 @@ "pl": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "d060915ce0cbb2842411aae6556f4c8e9fde60e8cec8600d003422bd7d1f93e8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "56a7bf01759ffd7303220a07f1d4fd1befdf8570bcc46494b427afb1428821f2", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "fd6fcca84a9b248a7a4bd06afecc4f3ced2b689b45ccabaeab475cdcac552dd8", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "27275a6daa9a0a30e2797a4bc1d233fb1ea357f4dd6f0c8f0ee438848ebcf0a9", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "cf5820ebb943dd2bef7154e4f141abc85ca357afd5edb9ace126fba321a352aa", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "c7a1f1a3c0f6ed199edb8d1064964830ebe17a0c3f40c61eb1b2db43f708d4ed", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "3fa9d89b94c7727c34c2796e7e30d4fc32344ac72f7339bfb96776f43ce679f6", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "09c399f3bbde6edd418a9cc12367d64c72f9cfcbd42ff6f43426b04c6d237e9e", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "b9cb0b67582c5e230bedda87a3fcbef813775a52ad0d01c4cd0742e839ae31c3", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "2d5fb4071b07c30783bbacc5a6e3b5613afb326a0cbf39d020585e67621455a5", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "b6af1b86a2a5353b07beeb9fa4d5e78f70112b230b600bc950c9ac72c47f2773", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "7c50133c75e13899faca752ca05f98174527fa6927bf1c935ff2c94adada994f", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "216efd364296f7c6090e246a51744873e78a5e175d016e0f90226ed49d425a31", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "2813a3238aa6ca7de0beb2d45aab19979d4bc5299caed17f0eede1f488acba8c", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "7e20af80a44a4372452c511975731b0fa854abdd1344a3df281612abfa45981a", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "1c9d9831f4c1cfc10f2f0250eb1170274700c222d60d9a6249e70656f651150c", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "b2c17a160b17f11a13f466a92970bd7f3bd754e4a81965962271391bc98cb3c1", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "4b645e5e91a021cda8c06b45810d0d7c5c9ce89868ef942194345c768f3c653e", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "ee90b7604c2d361f846eaa61552f8eecf9efb1dd64d0db75557e6477bf3b16e2", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "0adda76690194bea04e6a3cc02177619e0042ef8017d8c9f01d7796dad457ac5", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "a015e5a9b475dce09f144a3cabcd444efe31403dbe11a18cf6050f0e9af0a26d", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "bf8d2a8ca98bf48ce262017887ff1ddfe3fedc3cdf408b4d2b8e4a91e52a8e57", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "7483608e0d59f69eade675f304d807d257b2033358ed2c36f85c36112bd133f0", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "0af9ddad9876ccc56f555bab99ce9c5ad40617058eb12fd123395a5328b30114", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "d2c6d7307e8362d40ca4265860ab381d4893c32e7a9c3e439a83dd25bfa73661", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "59073b5438718337e3391e153dbb7c879fde6dd8cbad9e29d2229eb821a26047", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "50439a340d3b2e23d76fb959f077a672eebd7f0254f9d45a3169b6c1ecea26f5", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "5fcd3b8cd44fb86e433700ce5f0fda9685bf8aeaf47c7cfdaaac96cf90ea9cf8", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "f4e630430ea1b1b0e8a335dfb498a71ac2ccc47341456117951837f2cf82a4e8", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "ce0a74339a27bd47eafebdfd735dfcb876f18e21f046185d5a96058b069e6806", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "383d59d26bc614101669ad9ca55f3afe47f75377e5abe6e15edae46aea9f84c9", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "df26ec0deaf8cd616f2383f138f1e5a7aa21f75e07e6292c6c6e3ee0df06193d", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "9221abcf31f3da6bd7ca256e83de87b575f8758aea688b7b24b9d1669f26db71", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "e36421b4d73a01f926f21d08c207b99e891147bd8cf62c56c5a8be376867e879", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "b374fea47f0f4fcceeb176bfceb2d49672d8e86fdb580c5a3b0ce1ac58fc9e92", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "c0a90dd8c2947e8698b64fca25f7f579ff135cd6a7d27a4fda088e83b2d2467e", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "be070443cbb65b34d120efa9a416941c0f283314e497737e3aefe458e6d47bcc", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "f1d67223cc582cd7ba111bfd5401003570b8665f734479a8e3cc3505faaf6213", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "0e72d785823beb3f60ea19fc707fb6e791f97e00f95bdef5b59f8c43acf95b69", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "01a1bb71c63894790d7ce92430a1bd6958ab847476328e90441adf4e8412cfed", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "6f306587f425170c7cf75beb868401c43a383e1b6a5e89ba4a44a04a4a6b2081", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "eae4c1291927fb9e95bd70d73697c503e2b92f0ce8249c24776576bfe712bea2", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "edcdcd057b0789aaafe8ac8186609da6cef812482039fea5fdcb1b23236d055b", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "ce9881170fc4248c1fdf16f2d4806eab7335daddc2b046bb86b54f774d4fe66d", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "d4bf32fce6ef694456071b94f3e5b0428469e42c20113497dba57b52a05a2cfa", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "666b9dd2e617720c122d5c64d03e85564ffadd26a96e482296549bcfd74003d1", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "f05da35431f67007c37df143cb447fb8a52a3f81f44f79571f666700ec29785d", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "e8f5bab0201dbb6a95214b9c88dfd153c825432f21096227a6e4dee0091185bb", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "fd7a02a7e033fda9d88f258d9b8d9b1988f71119fbf24933c4cc94088ebc1303", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "e8a6c6223c47b238133d319941d70435e327e691f0fe075d1c12a0e7fd568135", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "0ca721a8f8b4177e8688d55b618a4f4f4cbefc38c90a4de101605f43b75b2681", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "310a5ffe3a7f3d7a3b4a375e955f8b80177badff7b5edca3261cc267a8b05778", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "57e64ef1b52b2ef3164561c36fdfba28169602fc219e7ae567963cdca7fd0903", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "c68f830cbdc82658cfe4c6b896eeee0baf507cd6d07dd40e71cfe62f76419ff5", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "16b102fbbe4ef4f4a1e34146a1f2808666ef4c1fe27dbe531d5857c6aeaf8a6a", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "a8d67eaa66c485b34fd3d7c0957aef85a3adad3ec3675ddf856ae2f3a1853b4b", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "f946217e85d31edd5da7eec6ca247a4d52f21fd6c900ad98cbc21cdbf7016d8a", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "6fbb620b28eb7c57e4eb5a6816310a76e5c52e11d5ea761a0e6896abedef4f80", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "1da1f2f97125ba946d4ed089319f8a53c5eada359a0c2b0317ca9c7e841cff2c", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "d0543c42fa20ca1072523cbfa613add2829708a8bff239aadc7e6ef7d6cb9e4a", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "9d980c060321e8185d3938feaf103621ebbdc07e711f4e9265d0e5b39636950e", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "3c0dbcf1de66c52b63f5fdfeff14cdd34788cd2cb4faae83b2614d1a267e671d", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "736328bb47a39235a4465386256028b7f67431df512c033d79f09cfb3caef35a", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "d3ae0e110a686f2d1eea660b0c352d0a420c70f3a9d6f3b7abaa7a6e6805d8ac", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "a3c096395d289354b961e460816c9c1e5d4528ce1a2cb4472361cebad13ad7b6", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "0a1423feb85f8acd9debac07f716b65e06eaef8b222038d90d12b9823fe19c94", "target_hash": "bfa49a5a85e25b86f0c606511025b3f674991c5d70a8a445e03c1f17471fcdbf", - "section_hashes": [ - "141b0e200577", - "79a927ba26ce", - "3974e858534e", - "cf292404f8ca", - "2305a56c2343", - "010e6473d4ca", - "2d8c1c27b184" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -91513,398 +31841,332 @@ "pl": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "0ed07ba959dfe141e175af7c343ab2cc3927247383629aff0a1910fe7e04b558", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "74e59bf6db2cd4f33f743b260ba8b969ebd2e1b1de843ace7945dd264a3bd6c4", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "443725cf254c1e08e94e719c663119c742422809c6e433fdd8c808681e4b8b8c", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "5bdef00cec995a9619a7e57c7526e03926470b6b53c91196a9325352b69679c6", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "dd9d2f265f3cad7dc56f35e8c4eb1d226eb58ae8e19ad9de3c63727f333e6a29", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "2f84ab8814d1d3b9b4da36120c3af237a5cc62b90b1574eeca6426345963fb12", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "28cbe22340187c42e5f2d222d8eff27538a5de261bfcecc12454c5654358d55e", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "85dacdc0216b4311806792644f12d1c312bc76cb2cacdf4989264c4cffb73969", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "f5d1358f14a7672110e166b474382d84f68c784dc773f4498a8c217eaf75beae", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "fe8d43ab70dfd35ff8cafb385cc1265ed8eb0984b4645f85a13918359c642e18", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "2084fc814f3429d75993d9e1ee897a759ae222872aef38b22e5bbe359f7f8609", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "33862314683d0d4fa392ff8e0b8c76eacb1a3ef08e575daae030a76938aa3ca2", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "07997b94a09edd07ab69a25597e4f818f03ba8c3a0f54135ed4dd956e2397722", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "991a4ae2818a5c51a0bea5d9101a4658f40dacec57c421ef5fea0c80d1f89a06", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "7941b72842e0d2768bb758fa9e8a3f2bd7bff67d80f016b288dbd51fb5d5b57e", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "546c4881a87775fac26126e7aef42b435a7481b760d247be2f8ad7a73dbc1ce0", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "749d2224c816697ca85ef99a42dbd8a0cde8b48caf31d7551310013b56d40b7f", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "eab5868362c13541c257c0dd688e12f684ec40fcdb891d7128b56f1c3ad579ea", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "6f9602fe64c427ef4fc1aee6555d2190b94d3110a79d4900a8ae06d6d206adc5", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "b7106a0192095c2fcad19869af9ebc069db8efab7a02f941d1b72a2b3bad6ffb", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "101aa3f5adaed321c5e4cf5fffb8f1d2c773e35cb17e54c5c606a80d7638a482", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "01f080efd05e01cad1b818bc118352a3ab00f0fada169bb25da567dc9e031b73", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "22b2aa565ecda6d83dbd25a72a0ff7d3426593266359eba18afd568252db2426", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "07e59e0c2342cfad8f8a197555d5cdbb372d2f0a692d512864ea56f6d05f8405", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "b15d710d87cd1a88ef4b507b38ea55d677158c661e44451cc0a7d3aab6a42fc3", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "9bdf30fafaa85d1cc0255873480cbc739e9fb127c240ca1da3841052f54989f5", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "a573d99c1c6594683e000d5a7201125774d998ba38b51a6b3220d724fb8f198a", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "eadb6d2e8875e08721d4e18608a0f10946d614af09e6f7f562a981caf85a247f", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "654b6b6f3743f7adc78074dd6f7cfe72d6ea1849bfbab76afd25a1a1c015ec53", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "f75bcb0a7b75d26eab5282494c48ddd9162aa8169783afbfa7ecc44fc3a48699", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "16711329d51a823c289f8747ccaf0194c5f6f9460c28d2dc4818c0b985cc374c", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "4f3307ca0f4f9d6424946cce9e4b423907f1f0bf5f93e57d83e17f24ae3d56df", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "223561743d521d728cbf2ef645a93c7acf03e174382166b91c1128ab0ae61390", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "74b245f545c9bee2f26af3c8031afb09d44f6060463e462a3863357f16617a6a", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "670c8c08559d9ef82d7748c66dd33d4a0601c5550b45932aa5b09791ed5a4fb9", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "473ff28be674333fd7c922bbf915562ccb9b2c33d439062159856e678b290f65", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "b812690d930df775b90945927e970c196212c963fcdd578e55064d5ff44c6d4f", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "602a1f929ff3d50640cb9d4f4bcdb8b4078c7d8fb4245423f16d4bb7f7681ce8", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "86418a2e9a600058e33b2ce43d55caa57b55a9d4ea807cc15c99bf9387dafe0b", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "171ed3ed81707a1eeb02b1ca293ec5409b00a7a7193f8c16037d0ae41b0dc4a8", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "648f47eb5df98b7b0f91b0ce2d1f37599fc5310aeacea1274f30b50284dbe9af", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "503db5b38ad64f2302eb610e63ef018e6bcc45504668097321ac686c88b2e049", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "10f1476a3d574700aef28acb9348dcac21a4a2c07e1964e8668039a4489073e6", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "30c166bcca6dba641e86ff331088615612353ac634eaae4bc82ff2a7616383d7", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "26006b29d51a335125006729b7fbee66f2ed684f1e1a0571536946640374a6f2", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "f44d5ef99ceb13a89da9581a71455f93904c1cba1deaeab4b35593cf16f2850b", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "120e9e2912019f0eb0717638b26ab56c4b135f28691c92f60e743e4a6e4cbce4", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "e0dd8c9dd373a74b0d8fa831d5890bc709a3b1bc23f8408d99dc9d2e66500332", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "f31d1298efddeb4ec9ba3c5742b7e4e7515deedc72d62e8dc53bd5ebe09b019a", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "472cb6008d09d737424b0a0c5389cb0b066479dcbec131664140cbe681a9f4d8", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "c45b427231a6b5c8ad611168c96a566890f6c17bf6dfbc85fa67b4c5ada17d29", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "e2c1bb3a378dc3810862c891ebe508278ff99495c521f66463049077fa1912cb", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "5b39d251441512b3349293a2c1bb6334708d9a4b2443b76e20a7a1f15e979804", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "fa2079aa5495570f229137b6da9ed17c94c04db0a5a2e127b9d5680b0b21a9df", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "28c2136e8c869c4e36761bbe7571f7b6e3a29dac431897e636fed28305477617", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "03626fbda51b797b32099690bf9da9bb55f466852e70ec2e4624227861ead616", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "0c3df306fbce27dc5719bfb1f631e17e26a38c1e049eeabb293978ac30e8d5e8", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "f665696dffa1b8b9c63865cf511163d3c29d48a740c9c2d1ef2e50356c91bae4", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "aa3cb546ccadfa004e19ee808040f0ee10341e5b3d19377d63fb30675c00be1e", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "f9858b7bf4f73b18a94a57bad3a3c4260c18d79ac102793b9a23546300f6161a", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "5e206d679119568cbe744565725ea6807f1d4ea3779c2702742468ef7710ed00", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "8ce8db52b8f34ff2c3bc495463ab08551ff90d6b88865fb70a14c041ba8a6ea0", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "c99823371547e3f0721f756dcc084fa90afb78c8b47f0d88068c899993c201d4", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "863f274685307dc246209bdfc6882887aa3f821f7c4ada1ce41e75e686fdff69", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "02ff9231b1e464246407d97d1d5e174de8ca66f01ec5d32b1c347370d1038e8f", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "c29927e069170dbbce4193657763478acc9df1a6cca967a10dfc92a0f9cde721", "target_hash": "5f7c3c0e8942ac6a9ae24827d068d31100fdbb3af54209bfd71702fda10f0bf4", - "section_hashes": ["0ad9c748c800", "3fd8f9692360", "0a14fa51418e", "8c083bf10fb1"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -91914,1454 +32176,332 @@ "pl": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "50d1e35144b9e209b377aaff7cc0f5ca51e0bcec195e9c2a82596b623a3179ac", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "1116d0bc427185816c01bbc6f9f0cbc6f9663b2a3ecc78f383a58d611213f3cc", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "2162bf82a1a3ab9cc3949b3f9943e49e19c746b7a39d95631d98fa018b04cd5d", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "5a572a048a204521eba7769dbdcc0b4407ae688478d940d7afdd814238b6f4a8", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "252085eee8d4fa064a4052f96e6706f4d788b24d5543df7fc197216a423777ea", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "cbf6308446cf3634515175456215ebec66b5cdd8edbd7f40ccd60b6fb5afb879", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "c7eebe12ce9457fb885d78938eb06f2bda66ba6e15323928b228aee472f97446", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "1ff1995063ec9b31a60a93b9a1b02704631f0aff27c6dcf67a490f8f1ac34e9a", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "8d9a58e7ee2292b80c38c72fb5506a474f889da7adefa8aa4844a38ce9459bf7", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "397dc7e95ebf3a9677312332c1f5a26b52661da58c6b4ea6d9ffa444f2ef32c4", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "d3ee17d38915442d79057e8951fb8c387acd1caae3aa7844141ffe8d69b1bf09", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "a14e704fbead73f95dd659d00691ece37f49971af727b4c5c70a93dae221e918", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "a1390ad9d2606afe1d763e54dbbbe5c3dd2b20e6c755278d0367e25e8e5af24c", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "9c41e7f4e55b4fef257433135ca33f53ae4b9f6c9610307e15f823313ea42741", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "260f828f2f0e6e854a8e1d68b47910692b6251e4957f0d1af1bd5bec2b52efd3", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "f5e89bc4538b8e695053f498dd466d30d3dfe58396e43d956560b33671ff09f8", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "4b577304321f01806a5df9b65eab4f75a2a1c9c20391fc80b7e164c8a6b6ce58", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b767f800f29ab714acc77700efd96a9a442f9844ea2b517dc667a433aa3bc6b2", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "f32fc5de661408f9826a5961b474baa2afcf6d2a36026fd942337d26b0388cf5", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "1e5ce73d8d65dc8c64daa0263084e5b2af54831ef796d2406ee7c08a3302e497", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "a777e2906bec04820833ba78284a7dd4a86c54657dd1ac64c3ce67a5bad8a609", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "68fc9681bf5126d9080213f4b69f1d69de438695737e00a360b2d7ad31851c0b", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "056a9142ed977d61b244a340e3388462fe5c2cb9920f4b707dcfffc1d5fdc2d1", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "aa3b6486ed67ecb877d0f11fce381f619fd9ce9e3dfd4d3323e05d4a83b02447", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "8b729deddab8aebeb9abe35a2ed769661b38846f3053d91607c1168906a143c8", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "4e7cce5b139dd3a2c6246d81f40bd72f518a3b9f1d3708456872f652bf7c8264", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b4e5f20b03ef5cd0f0f0f88571f9cf423c2cd69e70e2ae7f4d9073ee9399664f", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "3a6fc0cf4ac4416c9373f009f9db72f9e9231569e9e9d944908d8ecf36fe6a43", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "db6adc612f90076d6767d48198e6f53950827ba1f20096aede76fe0f3113a8ad", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "1618a4637f6871479051480dff72c669dd52e69c75ec966702f9ec4fa8d4a2d3", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "199f308131987141215eb7b5290b468fdf090b3a1980734de4cf6306103b6a0e", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "11afc2625aaeb43fd9eaa2ff4cf8d38add3c26adfa17da981309b24c54abd552", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "5690c5ae11b81d2ba1485ac3b2c010c0cf47c66c3d172218f158691d876000f9", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "49de8b9322ecc0b1c7ae18d1d1221707d4c81dc2eb0b38fe64e6f43163e40158", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "5e09be5de44dec25b86310293d6a2546ae4a8145bb77511ffba6e2e00d4dc2a9", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "10f3949d0b38cd97cf292e64e044a41cb60d49f4388bba913daa8d044df25160", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b167838c82c588faeaf6e32ae6dc803445708cbe13f5c59638144e6e0f9ef41a", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b81a511df7ebb3641c01858757d263e3b8069b0406fde221fc089ac490a156dd", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "34adb2f8af088c0955bbc4d3e25207624d739a92c3edcd5877cfe4dd2f9d93d8", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "8c4baa15413e7d18bbf051a5b0e12507e0963384bb5f7a1b8ce55cac24e6b82e", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "89c03c22ff01b56d225f559a3ad4f33209bbb50e851c11ec9243e5d9e4d13830", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "4a56435265df8f4d36fe4a48cb3c32c61772ee8262e10969adca083e4ecd9924", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b673809670fa1c588329f657291febb7641a40ef234882064b5b9d3caa3da5e6", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "9045b4347f94fb88c552c263646027eae703f1d0f7e87a29116768f429706de9", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "25d019db909d2e09836e6a4c28bfd75bbde21d934876816c7c45624c069385a0", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "9435ea268a59bbef9124a3e612eaef2c8e5ea94300fbf2d1b95c1c2502500206", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "f0c909ee974a0635b0147db5389ca7670c4323dd8ecfe7515c79c4e2e6e4ec08", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "9a891572b082cccedf8d85b6d6aa93f973cc54efb4d1e42456bb14ae0aeabd7b", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "9d0e56b8515450d5246db6b88a940f68986ac86803b522e894b613bd88c3e4da", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b1a28526c006104f7b770edba1792d5ac541b204795fa1966b032390a0033416", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "6eb2b1dbdb1ad7a0f7bc451e117ba62515a447f9c09c52cac268c194b20245c3", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "a1997fdcf46f82510ce4435ef5269928c46363f3450327ccb1bb690b1115cc25", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "426020b982d8398575baa1f9e79f965f96e2be9828e7e3bca6207440613c948e", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b147ef971f8b32bcdd4f0bad8151b3e97564fbaecd096dbc80bbfa02d5874fd7", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "21147787cffd212d0723a219aab455b2b4de056404ed26878998ec8af05a42c3", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "260485935421a4835943d3e7d2e3a2d476acdcdff15327feed8f8edfc111ba3f", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "b3409fdf9db5cf22d9c71bcb8cb732e152aad98bfdb99e5a9f2c7fddfaacad78", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "828018df5fe04da574a9fa7d3df224091e921630d41813017bb8c69de03d8e11", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "e3b3d89499bfa9f9850cfd19839fc13569fe5b46527ad202d62de8a6a8d6e68c", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "592d63a3270275f15adc8beb3c6a22dc54d8cc55bb2ca3e0fb482011c3a41007", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "79df74dc0c5d10d60c4379ad97e016ddfcd4b2542ae3dc4981c9839404f18e0d", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "f29abd3f4a9bc9ebf44e715780498a1cf7b7741a86986099c7576020f0a93fc5", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "2bab3621a4b30550dda4ced6c68159e685ce5a34592b085abf6c777c54ba051b", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "15c7092560f0432c3464b428d078d697e9ff7a944a09b45d74dec339ed14f72e", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "798b77598c5351e63fd5723b18dd725170b578db04358d9ebabaa12f8e465a94", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "a79837793ac88b1cc25125995f9d33e98a69355b42f704e4dd95ed06efda1dee", "target_hash": "3b816d02bcbcb9027ef5865385c1f146e6fc1ee57e87e4ab29f094c4495b5566", - "section_hashes": [ - "ab8849df9634", - "7e02b8d0c1e0", - "5a08e524869f", - "92108b0f2ef2", - "abc7eff34da5", - "859be42db1c0", - "32b7b3cfd451", - "55c3d07b1014", - "8a42110fe9a7", - "a2c9a08d2b69", - "d4a8f9966b19", - "db594cccedfd", - "23021df38c14", - "77e16a35c8f1", - "a36d04952ace" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -93371,1652 +32511,332 @@ "pl": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "94cc234e5792e478a18b1b7186a6f29e33c449ec38f4a970685bf8649b588cfc", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "0e7ee537838c8c1c4e4207247fcd08dd7aaef43078381e7f7df66177c36b56ae", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "05b77acb97ef006ce2769a22a1bbabf5ad8db35bf96f3260fc2bff71e045564b", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "d8fd2001a6cd75501f94210f74c0964c3f43aa780c2532ff347f433cf039d057", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "c21db995d4e67877333451fd9fc03e6a5b485dbff00ae62acfb92ef24613e66b", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "926c68c634786a732585a1282501f51742195d135effe5df1da8d5f57a492bd8", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "5dd30ce626ff49bf3dabd946a1715baffd379542f03431e5bfdbec30b2c479ca", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "9f3976078d174e9b0eff2d9021b65ba4973c445d5de1da9a96003edbd482edfc", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "416af647fd295b6582aab51cf029e7ca6f04ae42d5bec9f0fe7879501fa89f7a", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "bca08629c6f9c6b6dd45188428a6a77bb71bc57961088e4c21b31f65a2d9cb16", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "da696b223f30415175e17d437e50e924781b65c00ebd3d7464efac435c34ca37", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "079d36a466718d172fd6de387b181d42a917818674e7a5b665e3f8478c4af0e9", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "68a9520892155028efce3fc13c04d5e25150b55fb7cee7070782d8cae07b5c6c", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "62df65dbefadd6db4bb037b3821923173e10fb3959106788cd8b959a89d1bb51", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "0e6e41395d96ae6b80f6f9aeb6cef16d8c31082fbc4130bca62d8d7d2eb57395", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "0e5057532ecbe3fc8d900d1a348792e6f2d08e128f81376f429d75c703a510d9", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "97262cc1ac3f3addf6db2810b7b275022a0b03ed7fe4ab846cd12c33b688c3cf", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "bcc553a686f1c4b50777ca3dcf2d91ed72fa6c687fbccdaa9324fccc9607b338", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "7f045d4afd0b71235248acc76b9f4cae11ad7b8e21ede861d32f8f596ecc5a95", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "cc5a526c650f9eae9b1145283eb865571204c92eedab1771b431f328b2873253", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "e70acfeaaba49ede8477595c29ad459ba46aae1451295b0cafb30f7d81027ded", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "9bc26f15d46519bbb094e6433a0606e878d67fa0eb5e03eb346d9982c21f1290", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "9d2f20334ce5fcfa84b7e05dd7e8adaf75d4638a9ea7768f5b9c851037acdd27", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "a7de91fc922b2110930c76670ea846a983196670b8fbe41cb6692b14f8c21d73", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "56a26efd04c42acf418db9aa497947c6a5c99cf278e773de2dd1134ac30cf73d", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "85937f49fec929eb280634aee3e0295234518ac551755ae18110d7711513a92d", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "b34c87abc56825654b02ac889c5e44dfc5663ed51e2023c9852b707c9bcb814f", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "e5f22f3529d27ef06bd6d20df99e32183efcf2bbe1464c3b405ff11de546e12b", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "bd2151b8731ba3ba014c477c645cf92fe7ca1f304ba2e4bdc0e88f55fc3bc1b4", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "40571d8f5a012c3725837a15f1fa8795e3fcaa1f59cd846e08b64a5a4a102be2", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "e4ee1d7468d0b92da18de258eabc8b1cf9c2468793cf20ffe80bde72f9603184", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "b5ec59fe8efb0ce1fca7b542fcc6ecbbb263c1e1524d3d059c179d22976d93d7", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "15d3b58bf9a7094d50430c1e4f9c16033b58cf3af6cc1ceb6e06de7d5fa87d16", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "05f5e4c993e547d0650605e132c8becb3a32cb43064eff797a4915db97df6d6b", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "1367db0088563f84eb57a88cddd349e9ecb1d1972d8108eb9de43b198e1a1e8b", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "2b0100b17c461bbdaca3af06b7fabe7f54d8559c147038110014f4a9b0eeae24", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "3ebcb9be5b3dcbd350b407d23240ae651f15c34608b5ff5ab6e2fc0e1ba6ee86", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "f4e9d39d9ad4482f2345781e1ef99748268a201dda7fd578465b314a53269719", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "bf02298bee6856c0c016dc0cb69f1f75ed0be971e06b789b5d44d0cf1ea4a823", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "20879ba6e967482e61617376cae9ae049f0e3a8f68b56950f0d59d2a2cbd54e9", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "50b5552be9f878f28ae7b287d0a8665efd7daed5a893e5a5c7f08b0a72b4b223", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "8fe8ecf6c214fecf24399029dd9fe5f5b26958d3ce75087f21bbb00e70fb7838", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "3d67db16a6b0c34fb7c8a7ae59a4e69accd4d97a5ae84126b56a13a057d44fc4", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "2dec2d3a75f8b00ff3007b5d77e795e6b80b2ac571b248b254942ccff141d9e5", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "f821a52992dcfa93ddb2725736c3a356f870db9682f1751c208bcd900d3c4e70", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "7c43a5a98f319e81efc84f5c03d1c6119281e87d577cd508fdf6952582a21982", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "406cd9a06712c761078aec59a2cef8263aaf6caf280812f68d9a1fa14b48188e", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "cb40f97ef61f4f789d56419668b0366c6858595e480851c604b6884ee257d439", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "758e7ab2f07f0f943c69f128abc2d1466cf49c2d3c59b751d8ec328e6900a251", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "25a2047e2624c6605504303e20aa0a2a8821bb8bdafc974bb304ceb8587069dd", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "94e76ed69947ac41d3d0ce2c4789833e0293696c4cc07a20743115266116f680", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "b26789d01d363299357c46c672cde81bace2b6809ec8302dceb84a5b92bb9e75", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "1a9d3a59d34e802596d64c9ed0f0763e0884b33ed047b4173b166878503061c9", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "7c1f7f67a6caea5020cc09058bd95551db4f7605be4c7ddb78b29b66674986ef", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "f1c47299bf5a5ba99b1fa0b1809209bf72554da045c88a15f90f424cb04270ac", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "591671383ff1ab73830852ab3b5193d3f5019b68d13f576a098aca0a151b7f88", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "ccc6542fce96f4a835f3e11b32785e0473ec7ef91a2c473e82eb4fee9b2d7fcf", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "da392fe338e36339c189e47be829e2199dc719ac598963083e1f4caacb022749", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "abd9b115b3c3e34c5702750d51ea0366405f573a4174d50619bc105b3b0573ec", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "a2c194cb9ee6bc536ad78bd3132b4bbbb0b6e0c4618bf76efca2d3e8c149d8bb", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "a549b8059e8c942c7485b0ee626e005b71f79165fc608f6d168ee1791d620078", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "194a21845fdb07a45e27568cc4ed03cb180e2e9c6b6da95bfc8d90b75bee0f84", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "b7fddf873f40ef834f8c7697ceae8b67edada109b98bab7f7eda7c201aef6b72", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "e6b61bbf68cce8c25afccb09bc6e19438d88b9a51bcffdeb0d0a0ea7436c7432", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "8bf2a39ab3a0371a169ba9f9bc7c55dd4c0936e712bea403f148c2f6c3e569d8", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "1fa535ab3c9dbd5324ad8de7f4f59e91c56712b94817eccea5025efd7a587e9d", "target_hash": "dd2ea52072201d878be899ac1637bee1c18c5d5ced4855368bd050fd3ebde014", - "section_hashes": [ - "5f4447ea6c15", - "ea68898f1cba", - "a723b7da90b4", - "4601ddf6545f", - "95ef8c2f09fa", - "c596b011e55e", - "eee03be37764", - "55b356335658", - "3e8dc5a90529", - "4a79c84ad422", - "4b9b2534d060", - "9b09cf438063", - "6bbedae03c98", - "a90f68be699a", - "bb6531fa12d4", - "b5165d11b181", - "2cb520d2706e", - "494f80016552" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -95026,982 +32846,332 @@ "pl": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "f95ae3cb1c27edeb3d232919433b4f0e44c863f46e3ced1e52a9f3da52f30bde", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "7a23ca1f54eaef641a3b10c9347e6560f7646e3255bcc21110fdab3c7312afe4", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ae8125313ab0b88df35c21b249c02c49667f39091ab5ce0058fe7d89d7879638", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "7c17cd47222e03f139307d3c59fd95cbff0cd5bb53f6997bb73a717c02c15a5e", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "b0268b4ec65053d8efe1339af2221ea809f6f1bb36b80aceebc258b3aee0fe6f", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ac12a1ecef379bf6811318caf5042e003fa7cc23eabfcb0abe9e88ddfde9e766", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ee1133ac97353f303052915ba19f9364d0d95af87480ccf51fdca5d98f9bc2b8", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "aa39ab9bec46d94a74dc9e0623f2105b0f78b5d8fa8c08fdec83e06e32186cd4", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "6da2fabc26964858c0d22f8e0f835c3c61662d9ae6bdbb2fdce12ec97e22ef08", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "c3e85a692412d15366613c8d0fcd7cfaa8b7dadc4ca07d01138be28819a6e18c", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "7a3ec7c3586e579fa6cd2736cb27c208458ca82f39ac26bbd1767ba3450a928a", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "d02155d216ab92bd11b21ca878228d714cdfb8222d4ce8b06908f9481275d5ed", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "f4fe7600ce4579e1386d004fb5e0d555bb1e5589c71cccf965225fd478710e56", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ee8e52eeb9e55f0d869fe2e32add5e598a39ffdf07105b1376731a76c1a3f258", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "19fd6436aebeaa4677856b7e5a3f47161ec87178c7572ee1cb807d0fa148ee75", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "f859914bd4c4abb01f0968378eb524a03882d6c5945be55cd3287a95f5b3cf00", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "9b8fbdf08d3c7eed82fc3256e08a075359e2e54a9442376b11526c78dddc2696", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "c05efb11db9aead5cb54d136a8008997b973cc6c7d69c8e5e6da8b5e54fae586", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "6ffd75d9de10bbbc28e4dd65049f4b07c274c2e1b9b2c3cfed999db68623ee8d", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "245b6b301ce914daa52fcce826b39dd9ea62bd306d5ae6486aacdb8478cf62e9", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ab03e5083cc7daa63a199a4c85c0db312b446e8fa7b652d3133b810ef74169dd", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "0a7966db49ecd6b9c847aed636c65531fe760c5d12262eae9a3b4611945c814c", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "d1bee65aff6a074f019674507eea7bc4c7d831c0bb167174674789db815ffd95", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "414fb3de5ae261a1a89a4ade9d3356a3eaaba2336e4649729c5d0e5a01712cda", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "a22fe80b5b3e89f06841784404dfc1b018d5917bd61fc13aca50aa127326a40b", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "33a61a7afcc6004c8d5bbe51071e83e64aa84817dac32592cd7ff30089b794e1", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "26f297ebf241d528116fd6046c56e16316e87b7e723bb212f24e8d5b929cea40", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "f0fcb1f5d82d219f6a8c3e3c22ddb1d1a85c5de5d1435479d6645aa2f2ab86f8", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "3851a13365b0d9ecb8306ab618559edb9f39bf7f8f250566932db6e0a4bb7d8d", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "4172bc329b0a8a49fc670f59fea9ec82c0020d704a9650a0fda9a885f7b8903a", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "f07874afc3417ee6e0effdd844c4c4ab68c919c60a0d9ca0adc0e35ca56ee833", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "46821eeb7a3b88b0f148ba745887dad98fdf0c18873c261d044a89cf9a233bee", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "26cc6740467f477116b4b81e4f1463f3122e1e52625f03dc1b383d2602f4e3f6", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "7bfabd51c36bde3dae8e9a1b7a27c658c192b8b76c2aa9d289e2efabbfff5cfa", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "020c06f7180215e80d98e6b70d138946ba7501a7cd106204e7db2b32c06b7ab4", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "a04986878638c655e0e2ee771e79f84b6f049a0076e5ade286e01b32e9edab4c", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "88283b5fb0f64ca3d4b5a11d46666ea8a9632659406d3ce585ce5597b77bd742", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "05a6076613c628931751d57e0aed124603f75bfb06bda6df8b1f8c95542b5a44", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "bf0d0c68e5652b0c0dae2a488910e9e8b93ab7a7cf9676f9d0b2d90e371fbd6a", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "004b8ae7c6f870916d010e8f89e0436da57d88a9b39105d009e5290168fd5661", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "d5d725b3bb7cd158740873c532775cfc09c7ab47aa36f3017944765dc205e54f", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "5286609919762cc3062887d05a28d89dc11f50c900ae435423464d32a7cf9625", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "0c9674916622a3d62dc486c382b513d0939afdaa049e60aa995d0d787490513e", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "b4c70e3842deddea98948736b8eda20540ef8653209d8c460bbc974827f02ebf", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ca71cd11c1182fb65db122e64dcde61143bf1e4adc49e8eb63fb49326f133966", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "452065055c421c5af1ff42073098d2323bf0663e88a0076cd7631dfb67ed0f83", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "82d3981b64e076c9dd7f24d62154711755228369678d923cb1ee06b0e73ee6ff", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "db3e93ebb7fa9072c76bbf6b5c5d1b46776bbbc803acce0a86f636b6758a35d8", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "99d9279f732593ab585ae4b06b0ce5427719829e7f5cdb8166f84cbd426c2b3f", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "42a0f589c60fc90ea397cafc7fe7adb76dacae608f3870ad7e394569ce698241", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "de1998830f5ab1097fee728392253f7cfc7a7f65d62c2cc20fd84a76e9884fe3", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "fc2317313900372c6dc2cf72e86357a7d649e07eff2c3a400eb963c46a4338b2", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "4b3eef3b20d1a72febeb8f2bee2ec09614b8b7f9f8db5c1c7defb0212a030d63", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "af54af867d7cfb5c42d2db95b25ad3676906814fd1d6df0d9d5f9990fdb6c1ba", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "d3e27e45f791dc747a5f8ba8e5233d04ffec0ec5e55a611e6962d4ded16b49a2", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "9507e3b9bd6813b72984f33154f104888ea72f3f240a72b344432d29eb96b087", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "c956520308b8d8c19de5d448af945125c8b0b828181a1866272bf414b0822ad6", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "1d3dc13a80fb27ff56a0a0678c01c12a6fb28411fb76e2657f1dfdc9a9b68ad9", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "b664a0ebdd15e167bb2446df2f87a56aed3415f88c1022df1fe29ec4892a53da", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "698092479dfb6175eaada6bd85a93c526393659c2c815e3eee9d4f45cb73926a", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ef7761bc5ca43dc179c31cb305ccb0093d19af6029f5e0315292128f6b8358ce", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "c48b54247044481dfc0ac08ee67ad3633b92a7056c018859a52a93edea860e5d", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "4f23b7ff9bd815b0e99ffd07aa596cd52d2236e7370e87d5ec14b0a17784c36e", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "ce6b7f132b505c946863f76441594ccc33e2bbbb9c038343688d54b23d779ff4", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "d0b0e865def8a840a8ed237372fbb0288ecc79db78911578551919aa05773b33", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e7d30fb4d962f260cb06e265b9db7a8526042a582cd8887d20eca7aa4e0a3045", "target_hash": "4246b12facba753c74212e29b0058efc8c28d2f7e9b96edd9c0d8f83ce0442f9", - "section_hashes": [ - "a6fd6ed81144", - "c7c855e949e0", - "1d7581b3fcd1", - "52cc53f67945", - "f9bc5c575f9f", - "4205968223f5", - "4b81ad79a528", - "240026694b06" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -96011,860 +33181,332 @@ "ar": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "615982e70a26c519f3a32874c5fe0fc16470ef6de76c6019015181dc5fc7d369", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "b93e146707e530c41c9ddf10bdc23e5ec6cc9e420599df242512dcf1533e3b7d", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "33c5785950e256e51a71399f5101664ec2f49e7a2ea49248963087db0f10b21a", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "a3be3022b84bc570a5b90ca7686104a2b39dc441b7757f1c1ca2a316ab5cb5cf", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "4f8dd522f82b31c47657da4cf75174cb4e825bebed8b9ab28d68e826b38992e1", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "0dd1c47c9c032cf2576be8e134181c5ba2f28b6772775de79450a763629cd735", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "c316c65174cc7fc8d0d589bc054810c6f04099db9011e47a1dd3d20277dd55b0", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "18b4a3e9743296f0171283798704ecd47cd2c3c0448ac0e9db0d7438e4c2c5bb", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "da15a750007240a34d47b84b742c78ab00fc4b927b1d57292f8b95f3ab5575ad", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "f01fcb5b3f7ff55c3f8e4a6c6753f5692f3139935972a144efc8a3f9c95fe45c", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "f048348dfa42ff97dbc69af10fbfa405724d9bb5f965a7e6c4f822fc5f5b5082", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "c3e5adabe2f3ca5a580095e23fdbc381bf1f5cf2a05abcd130395dfd0d7626c1", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "86aa5a9c590479d707c1da05ab5ce06be7e9825f8f275d6ca689bd8ab8167316", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "d78aa4142f4f8b78efa34af1ccd3d9329c4fd9cea066b0f33a0ea011c7110d57", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "e8690c434f8038383e18ece156a7e76ec729142d938c4ed43341a2211315c1c8", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "9c4c8f7f5bc0919926a938ef140fb75b61d2d2652444aa02962a73a2c41babc6", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "5ffb4a80b2cff5e6522ff411fdf6a213e0dc3758bcd112f88975b23ba7a484ce", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "33cc1e0374412eab2bf597efe39173c6ea49b30351056884c48f37e1ffea05ea", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "bc342a8d255acff44fafe9c6ec17b898bc68ff375868c0fb0fd3de32fe7980ba", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "4420b6520c7205e7b8b7bfe760295b9fae4befabedb8481fad4fb36cc3821eb3", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "e34e9dd82315528528b20b35e6ef9146900e76cf6c748fa14bddbd756a4ba2d8", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "d15c45ff43ee5120bc56cfc871200a6a161bf537376f1891ebbfcde5fd987761", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "ac2a7424f9c68c4a12cdbe50557d5c69c0c0f9e1d8833cfc3fc45ccf3506937e", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "9eee373e56e0cec92c1fc6d0140c8cb14100045e061fa0170786310ef60c9ff4", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "f5008b492cbce7a6fad02cae2adf5c5f95dfe661d715d80b3aaad1315df7563b", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "0d2d8e2e83deab9c0e202ec2ae4fa16f81df561d5ad3c2f549adc6a139f4fe2e", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "1ba17317fb70115e8bb0ba213b03439da4d113694f6e07095559d5ff2fd8ab47", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "903eb2a7365e85ff6f9f2055b50852e7dcf265bd6f86b15abf36456e36664f29", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "f0661769fc050144185563985f5ca2f3332338961dee19e081316c45c5edd1ad", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "94ecc27cc01283f246488d8c2a196b13975a9e0ad61b760642ac202fa1ae94b7", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "e884303db94032e5f7a17c3a68e926f5b8228a7ad4dde7e747ec9ac9ce63cc86", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "95b538e41ea1a961812f61624be3b421af675adca3426a6a5049e6c2173d7489", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "de3ac4b9b80f4719edc196f67c1c37d91946e9bce9ff64bce56ffb56d538d341", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "0af0c92d8c5d50cd90548ac94ddf8986b442e761bb945c12f06a41afd26cf666", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "9123203222156a1cc574b94af0c1a2a6c1af86c7f079fe66a7a67551ddc5ee76", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "c2c06545084e41fe814ffec8fbfbd38f5f33dff1670ac3af4b3707f5ff4eb228", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "c88d8f40be6e846b1d78facbb7f5683228dbfe95a240817bdc18f2ce5c52daf6", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "df119101fdb656a162d7f2f2b4f73ae6bc2f4b902f05a6f1efadf532b881b2ce", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "a457130137f569eb909106d3664d0300f375644fee4997c133310706acfeb2f7", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "62abc52c05a5f55c43d8629ee8c171bfb967e466d114e615ae0435a433b6af2f", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "cd04ce0301cdb38031350d863a5f846fdb5b102d58df81c4c9c51d2f886e2fa4", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "83e4aa2f814489743745694c3422faa78bdd0d295fd1f93d89db37d968726f74", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "2ad17f283fa362fe990cee1ae33fc2822af2d33a5b07def600309c089f092e4b", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "5be47d7762812c0863f1e7485d191f11dd382ae3bd914e99bdccd038a36ccbd9", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "37631c7f09c36016187470819bdee3e9db7f826d5262c78983d8ce440b6415a6", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "44bf5d5ceb370b8ae4d34f66125baf9c687d4e597b97587dfcfcb9769faff450", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "9c73baad01b08ace45b573e4bb6f51216275c83fc391d5386f364dae7c666d44", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "ce0ed8d98c33e9d9c01693ae2c2bb5f762445c4da0b2438ca1ce6e7ec0e302c8", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "5633e6391c0f44b168e815b18658accbddb8ed537a7645926772acf28b19612d", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "403dda9e618fd337be4d13812761373d384174448d4e8b1fc351d20122322046", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "b760a4df92e9643a243a97797b18447e4c58f5348a19bdd929e28ccd341dd5c1", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "b8c43a85d746c29a9e1b73ba0f5f3e870f12a54593bb5288e54e25c8b6d5e6bf", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "d27f5922856db3f864e2078209a19e4313c415a57dd8d8d2235330346ac2970b", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "3a81c001851309356c2ad6751b50fb7a058c412044094ef0cefb3a0c317bbf16", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "607b9a8f0ff4e5eeb668cf5c40fc1ab74bedcc36d65a620928fef72000da58e6", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "33f5655796e0a5b4f5139662bb2c35118c91c2f2f5846f96a37c5e8bbcb07b10", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "f4d88325c067a0a34f06eb6afbd251e30ed78d858c25458ecdecbb83c047385f", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "e1926335bd3fe21d9de6700525be12ead7cba2836417a8e643a44f5bbfea65c4", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "646e743377102de7e2b0e228c9ccd0a93bdf8c0e36d6d7d74c88a2d9396567c6", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "9833ce81283b963cd975dba8ef09cd391e95fd1615289a2eb38dfd3b7ee51d7c", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "3bc6e013fd625347af6f6aa2c50e4e46df016a113d3356c41c67960478ae0f92", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "331e190bc0080acf485badb9ecd17059f35c0b9135ebd73679b66421d5fbc345", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "8e9e040d0b7a6ab10bafd97b09b9d916234ff03cedb04621b6515712687d84ff", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "acc128ede8dddc3d5ce7e4b38e764ddf2e69f9c0d0d7d0de6528b39dde46ef42", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "44bb9c05e2d3d937a597fa84d720670c2c12fb2cd94b52f1968daf77fa28e133", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "f1713be4173f2fd7dd7c19f0e67b57f1018018580fb48a77b53ed01ca630eb7c", "target_hash": "42fcef671b3b7ac2709ebd22396465af25f8a9b1d7ddb2bad5e0e154c55857b6", - "section_hashes": [ - "f30c97883433", - "924e3cfe813c", - "a47d9f56c971", - "0f1cca9007f7", - "70ff5c36b3c8", - "74291d61a2d9" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -96874,1124 +33516,332 @@ "ar": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "a7d168e6608c55b1f8442a1db31d16da62ff9ca351f63337b71fcd9944729f39", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "65663d59bc725dda6db0e80ffcb2c9f794a9e6856075d11895913a633d592e9e", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "c05ce45d82326ce7ac589dcceec679d4e9b94587019700225a5d87e158d0c29a", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "59dc53b3ae8bd973aa72671097e2bde780f9412fcb1d23ef0745e83e96e280ad", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "3c5a7c653f28c95bbe65b3c2fdca2e1359bddb650e2fd27d433c06b160d67c21", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "d87f539d86d4e1b0699c5972a557394644fee75f5a02ccbd75a9e68872873698", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "f40d61191d4f2f65eb926c6e65bc59dea892e19f0804626890c1a39a671b891b", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "bf0b70a8e4b03fb5eb55789435e673b8178153558d414d5889bf77b24feeb04a", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "1b7eb4af08a4865c392d422eda836fa1cb9ab42002176b6b4d6b4bd32c5ffaf3", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "34dff0cef8a7e12823b4ed0474f029367b2965e37c07aeec45736a43d2a692d8", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "33d8c686f8d9067f0fa16d5428b7df6d05090169c9c539ea7d1ea59ba66cc0f2", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "087b653581095a257f1f4cd04295843f57061f48ebcab20a03580893635bbaf0", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "9288c7f61fb31dfbf31bba73c39d37b2fe0e9978712c642416e4dfbc85efc717", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "ea91787fe4cfb0b7ab3c0071859fdab062023bae1bf1017730ed32aa492a9315", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "4aba3f4c772ef32fa27f1f0ecf8b650ccdff7f7f6f3062ef8d6c4eec134a8cf2", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "c5a934f4db748dbb8fa6fa05b6a3a9166d51ec032921a8fc22d6fb0a50e6b769", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "895e703a33064363cca370abc639135d1747799cd1227f6d439d13c3446a2e65", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "f5d4b6ccf610a95b1269908c8ab2b88c3539626f3dca04bedf11b251dc47dd63", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "1618f7b47a23bb98ac7f3cc879f9d693dec969e89782a492650936d6f104f4a2", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "7ee037b5623295f138028ac0598a872091583cf062c75b3cb9ade3f9dfa0537e", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "0fb4d291de93eba08217ca453ce550c23a7049f40b670d3861531362ce9eb12d", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "74ce826f7f8453d889f3cdfbcaf7079c6a76b37eb7c12d821d33baa9a4b716d9", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "b3c33a5210966798e64829ae5057faec09df31968dff5dad485d94ce6a1dcca0", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "006a30be91c6511237dd12692c0926cbe10dfa59b93760e909c9feac3d40d349", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "756c4507aeb74fbcfb2892105c7a4cfaeb20645aa970e44dd6dee10ca44a9286", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "e63dee9ee012bb6a8b624140dd21f102eccd21d9c20a0d393061cf4e24e94af5", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "419e90368d4880356157c01264de290a269c6ef6d53a87054e7e0e9fdd5f8197", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "6d4a65e5d9a74b0e79137aaafb4592dffb9389faebfb0f3cb1e5e51974d53c37", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "ef64ff378447d231d1914ed43d97eeaac4c3320691d1adffc679405045ae8417", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "d820a72d537bbccc15bdc92d8d27edfd739b308cff56ba6d058868883ac5cf0f", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "1d4cc33d368464555932d5685fe0b26ae25e23573fb5700cc456746876492ca9", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "6ea72c86cb95154af8565a3381627e6e51c01cad5c5572e58781fbd0726cbe62", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "583abbaf5c44d0047c38e43c26747073c368178b76a1ed08a5abe244717f2d30", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "05b69eeee7304238d93db3819a45471b0783b6388961112a8e0b1d6878f697c2", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "942513433d59e95c3ea0ec0f8e618931e77c95ae1c7a525de57968c4735095f1", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "ff3db981aed7d6f1199bb22e2099d77f1325729dcfce5c5159d240899078c61b", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "7297268d92b655a2b6129052a574b80012c5158ab02bead6f760f92166b316da", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "2eebfc05469df42bfec582a62ccf0323e0fa4ea2da6d018343e6dd8f18d1b25b", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "8d40e0fb66dab2c74f79e78091cc6b6d39f953df28a26f55a4c6cd90ddd6607a", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "2b34859429ab41c9470847b3f8e03c6801e92e3c21d974faf5e69c71a96855c8", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "3578a0ae22853270183abc85113df697382af5ae8f17d5e7a33b43711ffa5101", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "f137b82b8c9a74fa6b0b8508fda5ba6425fe64384b8ade154a1eb9f1a16bf2ca", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "be6cb806b77766c1cd85c6d87b6ab809bf47777f7465007ff8c0bfebb0c2b412", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "dd3c1664a2a64ab4b86210fd152a0b01490c54bf7871cbbf17097e208c50b7f4", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "bf83b60d91e97928995a823ed8c0f6496f1692c0ae1f9e8a8cf3ba7d3e4efd56", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "e48d76bcf1f7cd9c7de456c76a99196b0a53b88a9f8f9ba3e04c3e914e6feb3e", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "2b01010d490bea1f9ae37ef611b765ae60b4dddf1000bf0970084d45408a4050", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "cef38bafe4762151efcc242ba53853a2647e0ffc5bc2e9c5f51b1836ff1c711d", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "a02cb46f33d4dee6b2245f11d5127e87e4abf8beb50e341ec6b8d0e297fbd70a", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "bba3409983ea73e84201e8e5241c2cf616f9d60fabc7765419de6f51f986db85", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "b33142c4e5edd8c270962312b8c3b7adf70b1f656edc2ab1648233b0529a1a7b", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "ce9ab924f5bf2923338c587230e6dd34cf263a1a6e34176a23a915faedd92572", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "8eea02fa05002c080c8df5a7aff5961d423e21aa6ba574c5cacab5fd27ae858c", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "218536459b7601abaa3b55e96e26b06193075de65a0d9051ba5a4e7b331474ab", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "197f845d1afe0aa4d840a1389598be92298346a813faab960e9a7b9dff132a55", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "a71949a87b57895a800720e168f547b0adfdda45ca105d150c4d500650044378", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "03d49e48355ef79cb64f9a00207046b3f15f74f83e0f2ae404b75434f92155b0", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "86375da2a439009029d8643e0ffff5ea7c17ba4a8fc0bd0e73f2bc20626f81a3", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "a16658ffb723cd0fc3bbf697272f915628c96dcb534975a8e454d0d4b390f453", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "f19c6031d2a855c5ea1aea851ad8f0926d7b3e22fd3e9ea981cd66d6d6e1c37a", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "9e427fcd95fbf4d282ba75020ae1e256f9c3f7820f7c6bacd733d9917f8f2618", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "18e20fe865ecb52c6c8d1119e10811beb3a064368182fbd89a7af6b322753ae5", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "12fb8d948674ab5c280a058e10aac71510be6cb5e74aeb2cc66f44c11e1813fd", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "f885bca88f18e815afbd9218dc8814eb01dc5bff1b586285c6c741798233c1c9", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "b3116dfd3ad6317d6cdf9af4a8d3a7ad65f8167754e590589c088dee40f23991", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "52ade6827c7ee5ba9b62cf287cea4e6718ef714bea1cfd1a40b8820ae29e1e60", "target_hash": "e50b1d2a25cb0b06bb2b00396649106282e6836738474beac5029777e15f6010", - "section_hashes": [ - "ede9b52891e1", - "09e1afcf5abf", - "68ad44f14d65", - "16916bbdb5a3", - "2b3ded8659d1", - "f7140ac3ffbb", - "18dbb058c12d", - "34a22931e3d6", - "e6a2ec2f3b4c", - "426c570b8bb4" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -98001,917 +33851,332 @@ "pl": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "96421fad9e1945697b1a26f35952e17aa56db6cc3932bbf9d86006c4b734642a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "c5a1488e6967ee000345703cfa749c982ec2cbaeb95689f93c7605f5785d08a2", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "9a1d76ecc0652288a9286aede154202a483e0a18d1b7396c8b10b9c555ebdbdd", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "1031465fdc1699e5710ac200feba7d124e301df81b2cdcb6fbd3d3e8595ae10f", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "6e7422c6513bfe441516e57443cbc2796a2e4fcd592e2c269c50a5ecf6139003", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "64207e784d14e646ee5216f99627929338a49ce53dc4e8d99338191106065bfa", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "af6b3b5bbac9a77e74c2a2b96c0bdb01820ddeabd8d89eaa7214925f9b3aee4c", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "2a1da1741f20ed926e48c369ed2559067a0453e3c0014f6ea26d06c7c65fa6c4", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "01e93332748b1867088424d37c8b18dff4565f17186ce5e7d4699c6ff9f0d1f8", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "fbcd28fcf4d65083773f85df1a518ee79eb8fa80fae66fa4e2da39df1afed627", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "57a6a6f2809fb400267453983f05168633fc113a2ecedb117ca4649956f5ef1f", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "bf6dcd11855c96c9b9467ec582a206799baa243890732ac7ec3ec12d02ce1600", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "047fa56d2b3b7073655fe4fd56cd988089cdd89819d336bcd906736ec8844f4e", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "2c93ddf4455f57ee782cf5d42a10ec11a6760e594bf50595dd054c1686e9a08f", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "81d6cafc3cae0af961f5eb9b44a56e5e6a8bb61acb1dd9af9991bd20c847c389", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "38e5ae3f28c9b2d1090857f38e9b41a7b6a08d7db18820e8424079232fa147e7", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "7fdfa7942fbf0defb8dac68fdfbe71a97ed510909ff4f6e68891540122a13c0f", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "67ca4394d0085f1b5fb6b57371f2bc8d76ead5823335ded27bb5338b2503d8bf", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "877817e7fe86a6c234fa6ab6aaeb2f57e16285d4cd72d36367e712aa78960a1e", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "d2dda9d7d8525bbe742afdf0a26781ed76b57eeabcd0230e41567f21ffac86a1", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "e7ff3eb379a5cf205b818169cf855b32d672c9b8f14bb48863bd4e2fec656072", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "40953d26950995f2e3bc4378ff5bfcdcb23acbc48b625de8e404c1c15afec776", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "b4f9907c8ff519dea59aa7789fef835974fa260018f93b6ed99dfc42d01442b5", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "d1ff54bf59f87d4917ed3516f453da65ff863426630883322d71fc57beeb41a8", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "977c5fdba4bb15870fdc5248a4a36042005110a9a0dc814f949c830ac41bd371", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "f8c10ab4cfc2001fd85907202e67383c3f41c09f16e6fac14234b62ffd2b06ae", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "bdd167f5027116f1382b4240f1262f1caed3f4ed1634e5603e128d6e942b473a", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "9368433cca4db462fe6459d953d9e7969a39753e7a3d4d0f4627f2c2d0576155", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "50ffe9b51a21ab9e8a18c8ee386662e1cb57298703d85c211888cd15118bd26d", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "2c4d6737b60c9813fd7e754912551013492fab8442278e3c26d43cf690cb8e96", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "3c383a841e59da659c04cb061e86b3200724ee83273359e6008ab80f57a93296", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "26ca895d4e45c52202861571ce4b3380251ace154b7a3939459e09e9b57c8ee8", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "e6c8a82206b7faaef82841462cf1e7ef665fe9be9656380fb9f42a4a207a0742", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "a7fe3350334cd801d6baa8c53c15dcf0a8dd63ae77182bcc0d5699c4eff72615", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "f0ae395f1be78db243d870f5955c190f08ee478e3a89c503486f4d15924c7b10", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "e5273a1fa52694a2731864a72b7a3f3ad2964234e1c61929f21c701181a17997", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "1869d233d3ad4f59becc175f181d494c2ce01d8477acdb214bfa17e12865b888", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "5ae614d3aa014d9b3082a7cd873af41f1a86625a07262c365414143af4bc724c", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "7751da5406a897700eca26f97a6e12291cfd6cafef51e15ee754da39d073eb22", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "8c43ab370e6b2af677325590a0615a1ec7a9b3f1d59540cb27b7480d3035795f", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "abc9ff0d9091eadb1bd9f902da80a6a10ff3d46fd3b5f5cb69c299f4fb454858", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "09a7b2b91d898b2b27df102961923f70a6adede8ed42d6e72fe3d89ebfd45ee4", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "2f5ec7aec647ebed8b1ceff47636b87e4362b0df8620de4ba03bc6c4d45d314c", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "697de4ba560fbf2e441b33403ce1dd1d6e534ba36389de2fef9adf26d3f69a6b", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "8c58194d14aa9f4da09b65058d807619d918c7ab03b437e2edbe233122b4096b", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "2738c92644ca088724fcd19030d4b5549ec3098f7d6abac1b2d6d0b4e29e649c", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "d07be02cc1a6d0007968f1224a177cc7d8f333771944a26429eab66fc5c517de", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "471228989e5d573e2806a8d072db6d98e753759c07cc5468b0922565091d7e29", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "24069d6483d0862cd71cdc954b36bb6148d7bd33c225956b2fc3888987aa20d5", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "78322c5d2caa09ce304fce68b3a9429d74d7d70bb1eb1e9119d8493ae991feca", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "e7e352539d4843a22137fff4722b96267dd5b2a418b72a1bf9fbefe9a8604642", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "0baaf42d8ed98261bafdfab5e68bc6aebee78e664cbeefd6fa4c523770b16424", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "c997930d1c97c415e60ed045a3aeefc155cc1b7071c225767c9f89aa77fc5404", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "730e35fe9d3593f5b32c4ba22ea2ffb65db5b469d9d94ca503404b76e08da7fa", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "df9e4b97736bd3f53141e20b61b0defe19614beb358c79553d20a41878585291", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "b208d79b5342f1a8497519bb7467cc8192f76edfbe9dfe442b2b9592e144dd00", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "731e37537201abe566dc60f55ad569c1b96ab9c7acffda6dfa037365ebc73461", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "b9ab7ac190d92279e872747cd88acbb39b36d39e2611765b2fac59c72b1a2b88", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "7bb44dc936bf2dbfe261ecbd13c457d5186c441e3d34a0eb42843c11d895f0a1", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "30499be77ac07f06ec7b78d5830936412fbb0c358e089aa11f34445ece973499", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "dda8e5b6cc499cc9946d9041eccd622505c49e58b29fbdb7c05e9ab0a2bfea5c", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "b32770b071e960994485d1c6409b47c78be0974c2796cae9faee579ec827a0ad", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "98b6c34cc1db3a5e3e39548f86056f972c3b3635b258239404e037a6228e7728", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "a273295cea3fb39b33909d1a3e44861ba080aefad3d910618b36f1dc8e0e9517", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "c5682766b625dcd9535b793a0bd480968fd2532ce835d641a4ae153d89123f67", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "2e9eb65469c90ff48e0d27c46c3346216372c63b130266a529a5ab0c11b409cd", "target_hash": "700b44868cb433304d5dec2e643258514567bdbba0c865121adfe65a24f17274", - "section_hashes": [ - "47af2b040165", - "13e6837ed88a", - "f043db4f9935", - "f90302bebddc", - "64f9ee1ce496", - "eafa6057fce9", - "c48c806ad856" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -98921,398 +34186,332 @@ "pl": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "25cf06ba31a5130725bcf222b63d5df201e2086300923a12967f5a416dcf04d7", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "1897c8a85f7564a12ae692426ee35bd6ef07b00191b29f515570015bf4ef36df", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "d729f35069e0d33f5d0a235ef6fc7c748c44c7cc07f018e7afcf4e51e2b884d0", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "09cc46f8d5a164fc9b03905bb7b97e8504221a9d65a405336e0c1e7c653efa30", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "50268a1ccac628ee235a77ff025fa736bd34beef4670702885d686e69d39ab88", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "9be9ef48dd954f5365ac247d609f81fca2b5d27f0140277de4aaa0a3f4074618", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "6898536b1ed40d80b5631554b9601c7de05eb0dbbdad4a2559171b7f98cf4b7e", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "e7492839477a63418538be93b33182f6104172b9bce61b1baec838549d0c939a", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "32641ce05dd14615eb5c592075ca287cf4204276aaa31a32ed863edb4030e6fa", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "b257a6ad89dbe60d114020f0840dedb20ffbdc1854590e8e9c58edeff036b807", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "ab79d4f65d3acfdde2fe4b7866db8c2d3ce0edfa6a7a11da91e04fa4bbcf13c6", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "1cffc2678cf27e14f2d76b004a7855047132cab60686e0cf13e9e12c3c59fe54", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "a157e6b7bfe66760162cf9dfcdddb0822363023abffbe3ca76eff003f468532b", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "20a5f7d4545268b2674bd41824d73b056e69a9c18aaf98c9b8009440e489eece", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "f6e19f6cb9d453519dd2be11aa0a133fbd5c8014f9b2b3a0a814dc1170e632ed", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "9110daf9f49944fd25931f14dc77da00355e2410e3847b78762ec40c927bf8fc", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "265e87ab90a4ba72a094a899896d0b25a48d29c7452d00fa345c155680921e61", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "d22e594286dfafa589c42318b2c795cef9ad3e1ef0f219a1740f5320c092b528", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "e6a706f68d87366c1c464d8e992c0545ed1e3f341aa631c25c4f3ae4ce8aa7df", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "ffa309a44a2ff7b017e2250acb63d358b17e65bd5b62d49513f6d89d37a2ef8a", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "7f65458e5ed738e4ac3f76d35a1dd23eb72486057aeef4d13ec29480056bdba2", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "de1e52ad55c206538b5318adda1be2d443d837b8c2f066d3bcdae36c39e1256d", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "7c244bd3b7d3942049b0594b7f2bdfbf24183d3ce89d68d6036030d6c85ef529", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "8b194eb95d1e8ad58ee407ab8ef74c3692f690855a18fc64cbc0818e528106d4", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "7609e80947cc9024d80e96d26d1285c0615db781d21e475e487d0992ac8f7e89", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "bb0162863d42462b7f549d6e91c6afad8c1586fa90c63cfa5ed4d7349e7b627c", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "ea3ab285768fc56cdd206e7e443506b1f5db3a53fa07129c2df82b5644999307", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "094d3122f7cae99664fc9a0704e2b27d0506205ebee5a023f2fe588dde089f2f", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "99c226b577aee7c6e342af9065cf27d58bed3612a1d73e5f0c3fa79673febdfa", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "a4f7871605a32ba02adc70b2e49a316aa031a871d0f7f237855b5962a761b5d8", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "fb2e7fdaa1820f96da44318782d49ee1f5d27e30e542862d455e3507beb03092", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "fe8ec553bab8925664c084d3a4c325b4a4e931d5c842ab39d14da460c46ca35f", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "a67151aea331a2277f573b73dbe5d7bc316e99845e31e04c00695bf04a4a89d5", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "cc9ee7f4498b0032e17e11f9ce343fd887ca24e10995e7369aa6f42d137260ab", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "03670af74fb62be462d489976c7650f8ce1894cf3d8fe3cc99bd5ace9fce192a", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "0fff70e1e0a7605c4cefb3f67af2b8a950671b9334dfc3066efb244acdda14c7", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "7df8db6f96e63cb0b6f404596e1019246ac8ab3b5dfd3d044c3aab292e818a71", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "1dbadb8c25eb40ac15c48a83de03b92b6ab2f099eb92edf92abe591d31f072a8", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "5a54c463b1719c0c3c3f731960003f6972c2b77ea457b86737f38cda7afe04b2", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "edb3bc81085338bec4942e5e7cccc3794ca168443dfcc99907060da855b31346", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "02cb86d73a481b3bb212a25b81094d33fdbcefc2f07a9d31332f9351c072b554", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "993e8d88f12767823356ea2b9a0c4dfdf0160d4de9c283e76cb08fa4c4ceac68", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "0919fc368e1091781e69f53cd1f21005790778f43d31ca4a1e5a2588015cf7ee", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "cc4e0a842de5e71cd07b5a04b5ff37a1580407dd427bc1728d0a26a0d4438143", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "9cbce8ceebc8084481fdb7fbfc823347a6d3fcf05a06f85fe7a8711c01e6a5a7", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "450958351e525034bac85d23544515ba68bb103b3da3f510fbb83832db487f50", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "64460da858ca7e5aed44339b422db5392819d6861b2437abb7485659965dd88d", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "e18a837a23e9637d6861917bd053908d40e691ee103c35733f0f98f0a1748be5", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "550c6f7cf0cff1e117becfd8f7952db34cdda4fe38a995d66c5de7d73f6aad76", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "2c9185a2579c02e3e51d699edc756992a9a08f13080e8bcb63a4af2d7e90c954", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "9474c5a04a793af325050665341b2ddbd22877602a5a58873b8226b9ff3940b7", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "ae05b0c90da3c5caa8adb3c85cece8012f583103807479a36ca4f78d18ded9af", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "b128980039665d5845af8909ca83ca4c5367da516fcc4e4629f811eed778281e", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "a00be16bc79b6fe8e2113c550d64b791db1f636ea320f8015ce446eb782a6571", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "a42ee8e86c50afc79f5cb5690255c7ce6c7e062580ebaf345f3ec55e85a4d0d2", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "1ccff307b4aad636dd76a627d31494a0b81e71b82c444cda716ff3414110317d", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "be173ce253128e7395b847819aa8f1a1ddf62e0b74b16d71dbd72da0db84b594", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "5fd85607af4d2d1a9491ea07bb6652f94247b25ebc5aec18370e6c042de601cb", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "89f138a40a62362f44d3950b2064bf3c8079074afeebafaed2861060012a8fb8", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "6fec7eaa7812a05b19d2effd4a6139cb1d034f0662b41393bbb2a0dd7bf14302", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "f178c9f635ae909ad626b855dd3dd0099db71035649edb27934649c08bf64ac9", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "148210f2eee6f7431b6b9fe407bd005284e218c89717c8534482b94343cc0586", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "ab77254374e36b043356e40e02cb7d4922d56fb107c566b11965e7b88867f86e", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "d479e5215d98bd56b0313467eb2d0d0e54928462d02fa2e1d8d121729d689514", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "8f1b0c10bbcfe4bbbf305f1ff9b4f96557a891158bef81d3e7d5bc2a72ed514e", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "ffdb7f8695298a78f16bfc22cdb81311329d987d3f6ed657e1992a7ccb280b18", "target_hash": "58d84fb102753fdf7016a80a9d7c14a89face2a1b013331e6ecacde31763b0d9", - "section_hashes": ["36cf94a9caea", "bdf253043a44", "13f84529f9da", "72df22656954"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -99322,917 +34521,332 @@ "pl": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "532b407095cc885286c4eef6ed7edc175602f523409f4bce067c4cc1537cf8cf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "8a2b901146397c46c36e50c8e71b11f95299e5f6a6fabdcf01edbbb2b7b83ae9", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "efb62ea22454ac2cf00d5381d1687294e0bc7c13a182e98c6a1ef660408c40de", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "fb7ce84e99f9fd97bb22f26ce4607e88c9b835f1bbb302777313dff9876f10c6", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "b59a0ac5b5d3bc36d59d79552fafabbe21a0825f83af685367a14a61713edfed", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "135aca007ee5b5898e95fd541ee44d2a537430dedff28c7d63562ce137440fcf", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "e3444b76c84af06ac0cfec65282a1a95386be69afce773fb08eb518f12bbbfbb", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "c2a54c1a471519c9c73f3eec87b19ab3e83f2e8794380e3f3ef4ee246423301f", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "5676867fc1164a60d81d687f4b32f08c058bb7f8bbcbaea037e4dc8525f4498b", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "b308a1cf5216e8de43bad2689a4003a6e6ceac54f6a76e45dae43b06bffe4272", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "9415063be8839079d42882359429a1c81336e87e5f59715ecbefd8ca0b706559", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "fcb238e34d319937ae6de4c16d5a8f4a2f09df92c737ce6994e5b08e109ea040", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "5c51ddb6207c0b5abd414265288806788a40ab54be11194506a6fa281e39d32a", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "9f9f99bebb4cdea812a88463330e5d50ada65bdc91a5c8bd89fbd6523bb1a8b2", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "3152f2ccd60b68a1448a28f40ccb1a86d77593ab9b84bae2bed1dea8118059a1", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "f6d7d3a9478a807faee68b951d3b290117b66411085dd80d5a5e16863f56e4d8", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "3d5af979090436c338364f92ab1a4ca3cd930633d4421acab5c5a1ce7a9b451c", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "43693a5bcbb2e9228301149d839b9e2af1380183d20e1c04715f2b510009dc11", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "c31cf88ae09dd1794d4c0ec48c89be5f356b21e8bf85f5973898f53612815e14", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "f54a21679c35170b467f0520edada0799db7266529ecef6784e9d29e879de1e4", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "eccac560cad1636451d21653c5aeea6c5fac98c2f6bfa50d7a8566a8625e7c0a", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "1bad34c862d8093bf1eccef0908129046557837d676a74154b2037dff81f017f", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "f7c04a2959d77b5f38f44c978149272203e95e38d51c206250e4bcfef81ed3f5", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "47fa4b322d650bcb8b1d6ac5209ced28b6c2696e0300a0de0431ff12b780a948", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "0d6189fe8a2193cc5189e5f4a00a283d2972a0c9348b00ffa15cf0b0dae20e10", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "54e709e50c97a45faa8e15a348ea1fe5710083e36a088284260f74c866d1eaaa", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "10a89c07937ef3a08106322194de9239e6c0832a96e4785a66206c170784c740", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "55e51924ea62e5656126ed37b9aff1d74860c310c637281b8e9e5928942ea24f", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "6a179c58492f8ebe0262e388b641af9640fb46f0a064a02feae98f5755c3df3e", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "699a1a91865abf1abfabe04003c695814b1c77376229898f88f595f84fe5ab97", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "d42c0b8a36221ab490170b7cfeb2789daedff9c21b5c0d6587c17761485e7d62", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "84b8fbcab65cf93f010d5ccbea74317adcf5f57e9b0015d2b632eabdaab2b6a3", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "e31cbfadfe32e9d959de6bbbd616b77deffada014881f59a3ef1e6dd66ab7374", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "31fcb3fe7ceb950f2ca79efa8e9e0a23a6f1db689c77b05072e8ce97a444c955", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "b8e2e5b226f5a9054934e3f6ff889606aafc6d81a941b90b183491eba3d8747f", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "0a6ff3b8f0cae60a0bcce5ba4102f09128d5630cea007907ab868a0fc2f56f5e", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "d226614b21f43bd7bd15225465b30f64a7363f833b6271a15b694b0d38bdbdf5", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "1e3a1b309771e64109ad68a765ff4d93473fe8768b55a1d9c0800cf9302aab20", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "77a799e3d8b2f35604e037513612a52b67ae1eb5d51f065ab0807c8531cb7603", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "4125cc91f89ebb3cdafddfa1a22d4b979529ccd65a4ac11c9d63b3891d655a60", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "3f3a9bc1194503cbf7965d5a0c9d6ccfdad8922229eb84480d7473d151d0e7c6", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "8fcc83525a9465bb318c5b3b0224be56d457f93d29ec2f87706ee15d0f1478bc", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "1acb91501619d338c212bebfbb94a1ee2cc41f687b200bb4d4a00a324c36f1b0", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "7dfd9a55e18e81591dd6da2de355ca85e45b9c0d591242e72ea8250b41195f4d", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "a4512e8b561a20bb9affe3816a58a6917c29dccef703f6f70ee7ae514e60fe70", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "310dfe7b8e30c38ed0f72634e97e7c97879cb6f67ab441e3cc9e9ce368ceaacb", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "ce1a6fa483d7a6f5affcce88dda8cafe930b821082b8b24ad720c9407782ad19", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "9396ed7fcb075e2181be4157a7699a3ba9f6ddb564857b1040caf4f5e2f735ea", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "d8f639eaf319e794b4f0bd8c57cad4177dd3ab165fc9028c0b46e195cc9bf9a7", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "072559d3c77ab28887b78ca14734b2f3f84f5dda1ece9a7ccb5f45de4213181a", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "9af836cd3ea38e4b6837b59cad75e3bf31c9394243d36ae6e63d9881b177a5dd", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "ca7b9df0e6a5e5b5a1e02a76b2601603d2fdf487363a2b2df4c503cb9ab59b97", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "cf1fc1985f6bc216e53270f984eb8cef704b83e40f500342a5e01f4528fcab2a", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "272f47a926a7f75bc0eaf4323c8e1ba9eccb28259bf96b639c3e7524e575e0d9", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "5e6a311b42b36987784614020b5b8b765894546e6368ebc5f97df8802acf540d", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "fd988edf9bccfcf1cd537d5d4caab00b40952b96fa818cf1d7b23b88b2e22f6a", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "b6e8f646e59eeef180711a259e523a7644f1763fa4eb496e9df465776415789c", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "71fc85dfbde124499df944911ca2fa06c57d32cb8c15a837ddd6e6ad61d9f058", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "40347a9659aa7ee2860d6db6a6cd7bd044954e0d60453c536735f6dbdf2bf1cb", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "6c943a1d3e74e6c790e4d286c30577a7e86670e3a5ad711df7c7021a11118c42", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "14919d69a632467da800bf52d55a8f8ff620275c4ee58016ece48b9a1c6e7e2b", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "f710da63e451d4ee44b2d50bdea76c3864e29e4098e00cc8bc0192eeb2067065", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "ca48e93c783c92dc4add56f539223a3db438b7f26a88f8acd7441b46726f200e", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "8e64ebd34cdeec0b26c05a025dc788a3534f66ecfe5ac21057603f59801054cc", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "c2e1c15ce440a1a174af47640b11db6f30552b6fe5c834cd8285b0c1f305b467", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "059bcbb3e8bda92378de7cf56ad50f469e9ef6ddf78e014658298a844cf4be78", "target_hash": "b8a073b6086f510f3ea12655063cbcc6b0e55c4d1d48002790418e1054e17918", - "section_hashes": [ - "78fa1f892554", - "5e2bd90b3e1c", - "c58ab202d7f1", - "380f2a7730a3", - "f1fe34658407", - "ec8c41d7eaef", - "6e56c55f0eaf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -100242,1124 +34856,332 @@ "pl": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "6332fa834d86aaeeb1b2ce8586b4594dad7e7ddef482bfc907c2074bff428b69", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "5eac0a1924657e3be4dedcd6a9828b1c42fd0c3a6852334f615c137de94fece4", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "24bc21f78dbea21f49c07eafc8b7dbeb60e501d7e52c0b7d640d01f24c451b09", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "82436530491e46c120a5ae81ca87f7c6040f696c7716a5911fb7756763e36831", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "0b813684e257cf4c80c400654676234e8836ff1eb102b7f0262b19135ace8dc0", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "cf0518ef13fb8cb7930eb5a181ded28d85c288b08137f8e8b0de2ced6b87b60e", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "6f78a215dfc3c38a8020d73cc44cdeadb7b1efb04f32c3af5730b2505ec3cbfb", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "d82d54fbd27b47dec3de9e7bf9e8e872ade936d1b44c3441fd1351ffa22b0268", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "8b5ebb00949e1d93678a8587396318605e8832f374d12b8b4a03bf8213b2683f", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "b8d618525a0566e9f3372dd7212d58537b0e9b6e3f7ade7344e9b2a668c7c3ce", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "43907b594c61946e8329ee6c5d1f037aaea60d8476b04fa5cd9d6e6c21a0ea79", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "eea4f89b1a2f787f76fd80c4cc67a205d92ee548070d83b2682a5ed160d545dd", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "3d73d629d1ff5724e29584e4d6932a152d2ba735a995bb08cd46139c4e0d8dbb", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "498088b9cb7a095d937d2b13ad629eedd23d2e14bf9627760bb2a1239fa0f07f", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "4e36a61a324ee84ade0dee99d7fc29f236c831fc4e35e95e74ea5f5845f7dffe", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "9410add93dbf8526c7919fc3993b5d437493ebb9c38146f261a2887590d3d2a4", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "d153c01d25cd06f301a12a4586e9ec4fc1540c1ad61fa4a8ad3f905353c6e582", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "1d940ff3273d5667009311b4249792cd4445283cd09b54aef16d83a3492afc37", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "1e8a81419aaa343f3f189629ca457b3ea41362b352b6f73172f6bd3c2a3e35f2", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "18d1055aa65f2d9aefdfad1cf9c49a3791b8c8d006101e01dd5680fe84edb0df", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "76c1158142c017fedb67bb52e127d0bfee6c5bd60e5e3b6ecb49b445ba0275df", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "f550060a11ad88f35973ff89dd9ccd7d849cb915f4f9eb8db3dc754c9824857d", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "8b2775d94ac083d1cd6d12e35996837e88638e9b9aa02747358811572b58087a", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "68ecbc535d44cc026d9502718aa1bf6db58186c2c93858be1a3e68c328129585", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "4df45e567140caaf2865690c08ab3b0c30a58c53cddf3885c6ab4c0d34ad8ce4", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "f393b8d1e4924c340c8ecd2cc887a0c817d02e02ad27773f05ed1dec3d23fa63", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "a3580a09e20ddd69c4ed20e44c7dfc40ee20bb156c4927083226688deff2c982", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "94726c72fedf25f1d4a5e9948d16fe0dad5f22d6b0a8f613642ca51af80158d2", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "d8fdbd21ca709449b77cfd654feed700816279fecfefd16f66e5049901268e35", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "8aef50f37aaa717be77932a840b121759bdf739df7553076dffbcae319cef255", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "746f30d07cf8af7c9480af29124d74bb4080901f7497d84121e0157a03626335", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "9354e8d69921d253dcd427af2051d5782d42799b14f19d53bd4e78d7c000decf", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "6911abba509f6a65f9e8db87e16c00e4df1902bf02a2b0f8b0da2d42d922a704", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "e64b785c5c3e73e59b25f85f1ec6b8195f37357faa7b83f3500c5de3057eaca2", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "e9b2c482cc81ab8e6f0a15d0d92be9f4be6ec3687be6e1883482ecbe95963745", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "f0c242ed19f2394be64340b3caf9e19bb7a4e22cb0bcdf0bdc6ee279f98586b1", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "6c7e93be83246fe68e7e34c753454acd34b1795a2d8aa71e9fb60bdc86c8501c", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "99419bd14811da28a3f3a44dd31812966eec009381d7eae856c4cb9b2213dedf", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "e2afbd643b5bb73a415cd265103ddd31aa305bb2da4e65e94825c8ea5e3adacf", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "86b06bdd836daf392ee795cb85e1ce077fc7240b3e81188724bc9ddefe2a925b", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "7d43f14c68b8fef3e12471cb5c7c16017e451b73368f81b18c1bafcd5e656149", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "9ac9d77cacb63cf174fece92ae8a4a359cddcc602e41a463e23c0f53ec71d9e6", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "c1bad41ad27c2570a1197373edf389164d483ae13a270e2610ea8cbbc8e44e75", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "72f26ec8ca6edb3bb94b11c53cbc5397d0b650a07ed3853e694124a8120afc2f", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "3861ac611029fdc021ce4d51b2d74c483e172cdb79d4b911d54fbe970c1bf927", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "56448c6db41a6633e4c06e9fc1e676a554a69294ad975db8d657ae79e8cff845", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "09840270b44986c674f3867ff6e7de2598b78f56bd8193194f0b13d09a6f8136", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "aebbdccdda8a53a12ece689acfe3610181304b31f55ba3a4607b383fe120ff69", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "e0cf6731e7f5b13fa8b207b31e531f884ebb6f4e7371452d476d8b04bb0024a7", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "e64c4588e98433b7f62002ea559a309d34536d26d0e454eb0e91e21ef2ab8127", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "4cb2d4dc714fded025c13ffaeedfabc159852e9ac7943f26d021105c83439e61", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "5bc11cf05660a873f65909014c806ab27e51f22f0e49ec344d05f8a649a2e41d", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "d3626d8458543beade1275e9b34fb0012abab2c694fc1769118d81b8369ad3d3", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "e94649f0de50a5d9ea24edf3a8979ea6dffef064ffb0d4005d24121083c9db03", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "b7538bbc6b670d655dd77a92527e65de3e2747a86eac1e44fefdea5abb5863ad", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "6025d305ec4830ed9566ec954f9f385140bf819e41a89ba1fa723b7be992a8cf", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "a983c4b359f6fc6b46e00697d778524b2b34bbc21423ce495555cb30be8480dc", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "3d994547e61f3533531b23b87e25d8e5ff89a369462f337f6d04eb905fd4d96f", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "aa340aad1a941bfe40421d72e597dd74d0c492a6cefeaffe328f30b489224c0e", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "2b567f30d6da942ec1cddf72185148d848030d7e3ed413dc55a6484420e24960", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "6bc58719842d1a977c9d5bc2f0b64eb27fb7e6b03090ac25d5e75399ad9f713a", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "c79fe3fa91590853ccf4d4b6637a50ce35eb7c30aadd0ae645f911c3e3161254", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "8945233c699f42060b842dfebe44ea25c77cab5bea6d8cfcc52fa5053d386b31", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "338329e1560873987c621c5327a214416eae1a3125143bd9647fef6c1df83092", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "f146cd6e6213f6e9224f8d57aaa662742aa8d37f891f6ba054fe78c83f983599", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "74663d79665a1ad8ac2d8163f5ffcc6fb0eb932f659c0b3e370afa2a2841005b", "target_hash": "733d71671f726f55e603c3ba80ba31fb8f7c7f24e2c60e862cd57c2ac8bee1d9", - "section_hashes": [ - "56ac8e09edc4", - "0b97fd275103", - "f2e676923f1e", - "e905531d571b", - "f4bcebec7a92", - "4149b05c62ee", - "b04b9e451307", - "b2ac36c194f3", - "5d44786488ac", - "d2d983469714" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -101369,345 +35191,332 @@ "ar": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "205db8b772ad2dcd546b4d87ae5531287437a984b0186f0ad3972677d9d011b3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "2ea9d1a669f1fe04eef13bf64b683f065876eca694264530b757327b273e4de0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "1f5864192650604bca3fe9f6009763cc22ee2d00c987a5c08a9462d1e5aea354", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "42018fbfcbe47bda974cb2a9c91cf94658d36c0b01c48d16200868229d79a3cf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "ff884013ee9885b4814f746727cd1721566e7d77247f6da6afc7baea03ba3984", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "cf010ea7402071ff88cacf07058f298d76b3927d724deb3593f883080a637b7e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "45078ba840c2e59c08055bd7eefed78ab5dd42c4fc769890ab446f6162f3d521", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "75015b795575249c592f3f9dd3c990e30527733788fa8bd7fdf072873c6c4656", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "6a194964330ded008707aa46d8d4b555c7c08c65739ac4c83642e643f89cc213", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "681aafb9d8bde78f7107a48f38b41dcd7a15182bd23aac92308d43d8be493246", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "2d7915cf49a0020a21d3b903e83f6834b80b880664739e2d9fa39c07b9f8fa32", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "128bbc3befbede81d06f5dda27f6f1f019c14ede7a38b093c3a84f09d6a92d09", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "707ea8a1a560a7c80aba503f17ed156736727d50a2bc9b7efe9649e9f46a3f80", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "23b7e440180fb6cd4c33422a378aa0c6afc29f832b19b22db3f57f04e9a00956", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "272283db92109b03f9ed53ac5700d0f3295c069a2af14d41c10c0d1b4fb36de0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "97f9946fac032c29546b6e4ef1c81f31758681d1a8439974a4d354494c96538c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "c2fdccd46416437d7d2066cd87cf621b4da38d6831430a5a4ca30336b7535481", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "519ce31c7e82f1b05ee505ee327f24de67a16d6e7f0dc44d07d7a085e9075744", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "8aba93c064a4c8f998f311d1b1e19adfd416033bef7b3907197a229de303cf62", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "1f55705c2daa172b7bb7e03d33fc716230bfb7a49501128674f5707dab49fc66", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "2e73338382e5add2aa407a05f55300a8b37ca1300ec0fbda718cc43f2d8a9deb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "20aac5973b7cc92d0721b3d2517a0200c8b7b7c7eef23501523d2bab1e2028df", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "ec1c57278a80095e3e927890cbad0993f7163783e2036061e7d3602b442971a0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "48fa526562c3853ea2c9d0cf53490987d2d5f72ec5574a4b87bc6bee572bd0fc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "4ff336eb00802e45035265bbbe19370ff828c9b7e41028d7d48f2ce540baa772", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "250aa5f075c9d9ebee5c9ce16d243730140d1035f73220d9373f9af2589be2e9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "f696874f0ab650ca0652ef1a5a927dfe189a5b55b06551c2952b8f129cc39c3c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "69567146f0ef127291f58e2b7f2c4844ccba7dd5c353235ac9f0c6d45d92388a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "8892caefafaa0c9a835ed527861f378f70d1c521e5a3567cfc3edb0e51fc4bd6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "2995ad54bd1a7babfd5acf281bbe6a8c7f69157bc7be04c3aba6c66ee6b0aa8d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "b19c59024d011b8ee970c636432839c4b6db0863d34a30842d2d32029ecbd154", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "30a2e68f341d3b69f758e028e4d43750a64a69a3b6a3e1bec4da40c5c375bed3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "a456daa6034e038f9c507b83c2c916b6b5324340c1a5f0acfa2d556ab85ef293", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "ce83dea85271d2c00103e4802057b90633154b82ee7838807d7565e44b187940", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "407286adc04d0bb53868745fc1a5dc2b188cc04283e8abe4929ff90f8d7984ed", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "ab8b433ec396ff2415a4949e48235ee7767fb1943fae67a9b02899666ff54c61", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "1dca7aca3cd7515220c4103249d2bb58a16e6c225bbd6e3a06376e5755a027e5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "68f9a6e53ba75fe542d96bcaf9fd13d51f77aa661f74009dd1c55e021cff73d8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "6659313f5738c3c2eaa9f2f48d62b8f185299bdbc00c92988bd96e105bea9e6e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "7a6475e4f99a442751d5c90cdd686bef67228e93202fea51c1dc78206b53079a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "6736efcbc87a4b69c0d804e20adf533ea23272f9380d31532e073687cd484be0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "359ed49efbaa29a9e4952ea4c4880c4fc5bcb98e6b60ec62c8b76694e66fadde", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "46afea60c71929c80a82f6377023501d12ddff74b61c777af6c488814aa80a07", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "5482c56ec49d94e80a3ee803b5e1bb06ca09090fdfeb7234d6f3f04bd660feae", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "a2032c8d5e0d98ddf145fe9dc3fb72f93517cd6391c04bf5ec2fe6c6470072b3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "9c3d51c6f5a974ac5853e28ebb7d9f1542b7903a68c97f0c75ce65ed12e9ef60", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "6f1567a5cd170fcc9f9cbfac60eb2dac8b6c24acbc5a667d41e6d89dd8972cc8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "73308b5cb579ff5d8029b7188790784ff616aaffb8980011ae51b1abd93b909b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "c733512d0721cb6102a2afb7921acea762011baca7b5220e701f6bb1a6f89f8a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "bcc9f4b6050c47ba0c5bed7e2a21aed8bde97e6e075c1da38f86ecce089ef1a4", - "section_hashes": [ - "604551543ad0", - "30888350b3b9", - "a9649e540c22", - "8e32334aa704", - "4f9cb6fc725f", - "ca01ea64ce02", - "cf378e83491e", - "6955f0b56570", - "6c7706403068", - "9aa588261cf5", - "f1150fc2035c" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "67942f6e73d529a6ca5abea5c17423dd6b09bc16a7c46511fdbf85f341c561e4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "31bdaa4e9fae8ac2b5b69d85b705adb72b44e71566b22d7d0a6f4434c3de2cd8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "3f6ddbe2a4de495e27bf64f2182559b32638d665271848a4260f67376c20c9f9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "e456b6ef447840ba10dce227972dff3ea569793b1537e32b8ba8dbaf6b2f1ac2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "54b232ac379e0251c73d247ad259c1cf2fe18c235efde3131455b36b30b3d281", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "09ce1ebc54ac2ac6d497587946d325ad2f77a37743d8df0523e11499c5b15e79", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "05d0e72d39196167e3c24ca1b1919c41a7376887c3dcb5904a9c5f5e5c50b1c0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "49905e7de44a33c5475b416cbf2e1d9f33a8ac8c13a850f33c3a9931189126ae", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "492876b2c3c8bf039a99e4ea8d4edddafd139e0988605f2aee379bf81a266979", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "3c186548685caa0cdfe5145ab6e5b544c3831a8f7bc0919f3d1d78f46774f588", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "35c328f58f7b7a13c4e6ab191cbd6c4b85d8a33ba250c06cef3143ddb38a9731", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "be0e31d43579f304c96a520e541bcd13cc95e0694f44f16bd504116331ccc0f9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "6a66446d17a174486920671d4332dac3d17ae3adf58a0367faf571594dcdd33f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "3bb4cf8a20316f9b0b4c91363dfa8043debf3e12b8f20661b6981aacda73c38b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "3bbe97c39332b56e90be7048349cdfefd3fb3ea9910183b7a287599eda561f66", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "c440b0ecb9c2dc60cb2bd321516a2218adad402708d26e6357e9c0d33567fe2b", "target_hash": "5556e54b011e4ba3928bf77300f8cab558b28aa613ca969c33c5abf090ba57aa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -101717,794 +35526,332 @@ "ar": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "30a4010ab829a0acf9682a6cf426f39c4b36b64fee0e1a84c389f7672a9f7ced", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "49334c11cfd3fd61a8ffb3008a6749a242f6c8b31467e98b4171499d3eed6489", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "8070fdf1da86687c0849d094a9a808ea80745f005c632289906776b231a94e63", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "2e2528011b51fc3f906309f0f00b02dd9ba092c71d018805917c61b109490f44", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "2f613ced91dcb643ea9b140c94f860ac69d4c62457bad112792becff231bcf15", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "87f7b1e02d1b60935d49c00a8455ea4951b32cf3f916597e48f7f9c183c1cb1d", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "f24e17d231fde6f0046e57b3bf3f700326d4c74d9938805ed3e95e61daef20c9", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "5da119b75fedb565f5ffbaebab5450b6e18319684a7c33dea3dc3b3594e7ff2d", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "2b4a418dbd5dd968f654921ef1d64f16c5f454adaeecf4b4c07b1520f09bc14b", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "8085be9cff0b134853023c7c58a43f443fe17ec2d6e2ac475819d84fbe1fae99", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "577ab30fe08f0903cb1b04eb41335683a020ee236f4bb5ba82fca11e2612b575", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "dbf29e2f3b251b4e24915cb481ab2bf6d19c2e60e5db1d358f27aaf1e238d585", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "9ce0efc6d26f813bb3f819ed90bd6742139ee7f854f6ee897061b2a10ede78ab", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "77242a18ccfeec20ffa8a68ce85b3c62862aa80f8bf1b4c651d6ed1e883edb0f", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "c9628a49a3d0558a817bc266406891781624cb73ccb518dd43a4a5895c9af4ec", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "aa4923035d3ff9d9455ab7cc8e7e48c5842ad559b30dd1c7b89ef4e68085dc8a", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "8936ca8832207e8f49cb9ece77b9f0406073d9fbfa05f3a66261ebb0eaeaf0fb", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "398ed576cead53967f1e51437cf73f2e4ea61d51ace7bb6bb393da6bd37f04ea", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "072674cfcd9cf34e53cc7b0815d1750121539fc50c03391a9a88d499e0c1321e", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "fcb1751fafb7b5cc67e0361079c29f29ab078f631a0ab8af3ad987ceec0fab90", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "831797799698a155ec459f0b9e75c8607f738523e3c38ecbf7f80280ae12189a", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "fc2ce47ab655d26ba455d6e95b5ebe38be16ea8954e2ab97bb2f5a2fd5dc465b", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "0c72802d93ccd654a1d91c2a9b95bf04785d86e0a304a54a7e0c4ab057f87c75", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "f2faf13946dcab9208f1a1f79da639951305d0fc759c64aee5cafa3d33aa0732", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "9349aeb23858618035f3202906da8b31db387e6fa21020abce30df5a0aab01f9", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "067f984576cecc0ae431729887358d7ffd1685932f6cc44241e7b1cfb91679ba", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "a5d022505b87e9b6de0aa366fcfb9b054a371752dea6766f5f51b1750297583c", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "e0f14d84296cf99125fc2994917efccaa0d91d8fc303830b35b8a841841b5e17", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "7714ad62b099baf62b1e682c8b2fe5f5a6ea809a579d525a23b3b3c1a847a672", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "6bf26afb5823ad02c76aff22b96a228214a62cac20a5dd8c2cb10247760cc5e5", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "c3abd3a1c396f36dd044416066fe608797f3487dfd2320a8c17a423931090666", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "619ac5b279263844c9c50fab9ca0aaaa3668850d53e73c495711a886b6289c62", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "9fe7355e422c359d1db4348f2a99054ef910a0e4f0c9bd4f65eb1bb8bf24cca1", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "9b401a0c542020081c6fdc8cc45a9ec0a89455e62add551948362189ec1cee43", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "68009f49ddfb761f30833abd4f485d4417f0ee4f5d22ab898632dc6481706db8", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "818cfff4cf2c485d1267170044afa4e5f621f4087ef8ccfd65317441860f85b8", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "b5e5d710d31bac39150512ed4810a0d563f347174b3517354c8ea26be8abc392", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "729e47b4d8b2b4d831e150c9428e8193e7d06c6185bfef26436fa878da30578e", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "39c045b7d3e84107a9499481c9a78cb38287af45342b0962e8982ced0d9b4546", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "66fb9a7bc560f91ab20d457886da0047a36e757c3722f759eaa6b275dc058db3", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "60346e5dcd88cc0ab4d774e7c4ccc90c3996018823fcd3cdafb1059ed98b2f84", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "076f4b572ca18c488915dc942e7640bb53e26a96a2c4840de059d611ba59fa8b", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "3e447ba6c22954a97d75c7ddbe5bde3549e3d39b678f97eafaec4878ab3a0b3c", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "e05597eee1c8a7a3ccd4e4d46becea9e8d70114b0f466c8480e6039f56ab5caa", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "55153e8d124a7019cc90bef1a6cbe33408f28df78d001f642acb98e7873b2955", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "ea47a5bf30a6da226da6ad58ce34d334bb7a2ca8e637354c5dc673c3b1fbfb3e", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "f84b877e0590d008d78b5d2a7cb004cd8d3fae760798f3f16ade26f59a21a55c", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "f588fd7c0535bc46418988fab1ac2ee124420453e1896f698c20d7ffed9aed49", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "8933d695c179e9877b166db9bda5cb0672d4fae5f9ab1541710eb887c96f342c", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "ba23e199f45788c687703bdefaf043c46268f5d9f19b1a77f341fd12bfc3d80f", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "5a15b6164fb7abf35708e0e0641bb078e3e62aa8b902a608d209d657611a1511", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "52f7c32d4dc88901e69fc525803acc884766e01efda1741531fe4d6089c6c51a", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "572ca03d8dd8deed9e60ed2812167fdf1d7e0dd2e2e41ebf1af14883e677b175", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "7302f304aa78939dd389a36eacbd99cdc20a9fd40f1a5ba27e4402230eea8750", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "559ec8666a37e5195cced3f7dec485c186fd7e17b6d64e51661febe89f6b4073", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "da8b3886e27c0274624fc156b1de49b632b37302aedd19c1aec8245636d5a8da", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "9c762e5b6035f94c0b790e4c9ff3246870d1407808ab778844a01735260fe196", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "c7e69ca162b141e2b402f70b4ccae23ed659aa64b6198d0418af6cc13dcfeded", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "c0e7628d0c4644137ade55f254e47ad8d5817fcaed8d6a7592bfb7aac139d389", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "63eacefd21778b908805becc5f47d7fb96b3a482462e0d2b204e19d138f6a879", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "b054058ee49ea66bdbe5f44254862f9121af7a779025d700f4f3d11800e9e148", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "e843340b7866609abdbf01c5e7e1c76fff22bc90f0ad56db90897b4cbb86a4f8", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "58ab346ca0f0e72fa8388f1a9fbafa72de7564696c0911bedbf644dcb6e06de1", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "f2142c7bc2f9bc3cb41e77ce5d38a0be8a09ebabcc562b028b94f5de988025cb", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "c2710a3f790bd04ad9f9e8776ed343616dc1d1b1e09c710655e751a98dcf03cc", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "ea672d4b77b14e22e62b5485723062af6e1dbadb58e5172045df582a9ee1e0fd", "target_hash": "92624be6bf7bef36cc442149f00e2889c6804d61584ecbf076b0be037c0dca75", - "section_hashes": [ - "d6344c1d23b6", - "b8335bc5ee27", - "5750322abb91", - "ba2ccf1b3b57", - "777bc66520cd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -102514,398 +35861,332 @@ "ar": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "a1c91c88c6c44d24d81ca94018534eae97a71b03e37d57af1fb9c119b2f219ab", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "d1e378203234c03c996fb0d98a3c2b9c5dedcc3c4b10481936b1621f5aba5744", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "a4ff761242e68cd7be4f212bfacc84f840dc90977d6437d98c1d46cacc10afed", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "0838a105c2df4cf40961f9f83243f852b46814f3d3f5a5864952804914dab975", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "69707dbc01505da1d6f440f1bd1323a45d6a91e3355757ae7832adee63a59dde", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "21cc075efd448b88d2a5ca8427cfd59a8eed0c86f32ff48b20c4e78161b44fd1", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "d3250b2e1bebf1935a47eaca7a55c50c1435ca40700c67e750b819762efca74b", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "e15827bbd1dd7572e8480a80f8c51157e8bd9b54e6400ea2045d1dfa4c13c1e5", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "d41fbb8e218e4524b030f989310cbae4424792fee675f25c09f63579fb4ad15d", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "66abf052549e85114a0da5308fef565b587510da03339c69ef4c77742e17af8b", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "a25f07d981869bcacbcda6084cce0810fe7a1889553f8bd5a49c441a06541853", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "7a11c354d3d799cf233d4a388a63cb683ba6a0ba5a04a7b88d7206c423f07bf5", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "83a6adbdf1dcbb225c61ee439258189b1bf27d422cf6aafd3c50e420589c7abb", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "a62cae8f98487d698d7dfb7960720318e199d4f815942200984cf707100112de", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "0dcedd140b3c02ae78bbeda28b6d41256472d29ad2a806c86f5d76c4de2d0d82", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "ddbd3cce30a5ab9a33b30c20a62035302fb82be90c3b50149874d77eb2239811", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "548af64bcdc92b7e8319c35501f6dd1b0eea6fa1883759e5d8dea5badbddca20", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "014eb06745ee8ddac095e2f7179c94bc31f1896c614d5e132bba84bb3158bb92", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "dbce9bd718da7e43e849d8257cdc77a06cdc1d1e29b992f2c21903badd20ad7d", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "e09ef4ce02399f283968ec8574f7179e43d3c12c63bb19394a5cc4f14e895bdc", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "ba3021bfddcf5ecb28656698cc4866bdead78fad6d4aa9c81ea16e2b768cabaf", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "8cd299c208027952aa2d5ddea8eee33c825f16ed87e4bfe4744d7df9f5971d6b", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "e005925255f3417757ade206023c3ecbfb7ed04370eab3f0c8128c130871e8b5", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "7c489a32f864ee8309e78c9e21402e72da47881e25cc8fb9fdeba3068e749e77", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "6534cf70a94c6355e20f4c9b00f5c10fa7239a418d24d6879a4580ba6bfbc9d2", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "2810a01c5dadad811ba889630ab585866c2907f6e6c4b11c0f290f782f439e34", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "7b49c89945fcafd00d58d8bb309fd49cb041d912172d62f94a627713e5ed5766", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "23fc7c7d9a152a8e3de33d17a0ed1ea04a8b37ed69ba06b075720f4e09632748", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "fcfd3acd40110500bf3a13bee9a7324c163360b82112300c5c56298b7834523a", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "2f8d9e5b18c240ef5ec517a0df8b95e3bcaf2f6f84d47bc7ac4688f2d037e77c", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "7a12f98e4466a2b1be07d496a8dc0ee3f3be53c911524ededcfe4ace43ff2723", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "e200248633d90ddfb74a3dec493b26bed6d78ed3fafb5fe854dfed20404e8751", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "90c45404c400377f775f84236038806e377efb2e562ff71b5ddb18ba150bd884", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "189d27ae78cd2cbfbfdb8b82a68ed6268d8265281327062be79de426cba21cd5", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "da81a766598967e9ff5324a5f31ffa1055d7b8f53d465b2d1226e23f903f00ca", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "8bf1665327b898b0f7fea51c248998d7202f3f6eb414dd022b495bfc06c28ba1", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "7a38cf4c22d50ce1295c70c03e78716acebd362dcabeee3e357470f6a8ae8fef", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "3bfc26f098c71c966feeca4a7e0a0ca49e6bc101ff2bcc1fdbb48d272c403d1d", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "9b00f01a4d1eddab83a117b1173470e10a337a876f54b2e562fbf218cc1c1c68", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "33cc810208744d67942910664aee4d8a00ce5f7e653cefd95a482eb7b9cc6747", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "e2188636321fb6e59ba0498cdfb0c579534f14bcb2a25f430de71104bc230aab", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "7c9f248398a58b2887425be48843b76a9f9473adbce906435560cf8d0cbb0a96", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "199f87f83df72068026872e67966ef3fcd6f749358c6d9ba22abda0ab401e515", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "da952f01704596680cf0586dba3d197411d9b908f13ced41bad74852a9936b2d", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "7ce654b1590ec608d84658880f962f720cdf3184debd721df2ca529b2a85903b", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "4162ec24fc07c432a886715be3febdf61317919a018612bec4b99372bb4b7323", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "8ed0ca75cfe3281a2f87e35964651507b377ea0603490d5f68f57151830258d2", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "b44f16ad251cbbf4eb9242c49dc02b7bb51c1d12ac4b2f5425ecff802443f0ce", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "c3a0d82799ae9e83d36a07b21ddc1434d02fbf3dd930bad5e7f2333789aa989e", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "05378fbab55f211c4bc5585d1cc694efd2a74732754bc763c784467b48606ad9", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "868af32dcd78fcf460f706420d7a6ad6eeb6a471c5c9fbb13ba44d4c98c0c9eb", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "0373d2dc5ca16dd33bbf52ef94ba9873a2f0b0e3b174dc56edf9a71f1a438da4", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "2c4eff2d4d32d2ac9106155f45e2bda7a9f1c3da93ff636b85dbf8868dfc0114", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "4247f79c7e0c65737b33c6e5eda15acfde00e59455d5593e924537875521d1e0", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "a6d70f71fb3e92a8e29f9302db01e6cdd48b50c2d97f0d2d75d967eb32ad7af3", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "1628a9128c807128c2370fe75aecb3adc7bc4fd066bb4ddf1c981502837c86a5", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "0b348f2735122ea96370f1642adf9c271031825a2b6d1ff1a0cf54ca1e836270", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "149577252eaca7c16f37fdf67b6eade45a911950d49e9b15c4ef4786261a2328", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "91db6f243ccac6387537a08239ab86709d71752b3fc7e9e6fa3d1e93c004d626", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "8ecfed2a37e47193db86150b65a2225f8debcd1155b55e0022ad3abedc493772", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "ce3f22fdd93afde61b0170225a2c791e16ce76623069135a1cdc66aa0d607e67", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "ec05a7c872a91d123181665c1a8ab81ec966a1be72310f111961c7363a6af504", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "b6f6f047da1efbb4cf0b117d7e47edb795e750719ef29e438386b6c46f46c143", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "da5a7b296e06c0cf892682c320bddaeb26529c8928f0f553b721c735e31b5be9", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "03a61d0c7c587a41071574cd54164d65f6b5bdae5cd6b4b651a813a4e3ec4991", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e495b8347ee0e1ca0674e22fbe937285581e0a5cdc9ba513481aa1e591479deb", "target_hash": "bfe10cc42cc7f59a8f5e4fcc67b8de2c468b6516ad28c8f72f612454b38528d3", - "section_hashes": ["e577b41a38b8"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -102915,1124 +36196,332 @@ "pl": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "0019acf4d7607b0d69613d656fca2c2f9f3154d626413b2fc815874e7999eb0b", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "f0f80e39549b166b6cac560f68bb0bdfae510b22d1642f4517a0ebaa3716edb6", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "6ee2b33b0699ef1f5f8a1992db39a85b4f8b54351aa2a1e796c543eb79897167", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "fa71158f9276201c8529a632894f73c0fe41a57b279c8040e849ba8d18c1e18a", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "fdf0ae5d704da3d02dd4baaa5e100e9d89960539f527b0235694b7cb583a937e", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "401ace2fc2ee431cb440f576a4dd1daf58b83b23324541a08f2a75e81cab0b03", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "3f1789d32f421ef4d222aafa32f786ad8b47f7f6f07122c7881fda0593bd6b15", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "bd738c349479edb0ee07843e35fc248bb30f598c7e077bf4de0630ad571ff2d9", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "822e247bceba177e7d22f397d01a8d0737f7100f80b793fad5f89ddfef660f09", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "784beb86243a42e5f549c2f112cd283bff85ac3234bb50a6990f974a82685290", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "9c56a4a1645e8883d107f5c4d9b4623c2186da407e5820de9e5278dfba56d33b", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "fc54ee09de0c131c159af3dd9042e8a2db1d908941047c2053bbe0e865935bdf", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "d118ccafd0d77a5f3b015ba67ed032c0b4664e06521e7bc4b3ee886668a1607f", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "3241552110c8102813dd4dd473be9714dbc7a04a1fc3eb4ef6251a00a412c39b", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "5f25f855e67e531a4d088ad3bc4864d287b28597ecfea5cc406a562165245392", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "2da526b4d346e1403be2cc0879d223c1c46b18ddee3529a3372fa35546d949f3", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "2d54c9d6b9c38b15211ec512e78f5683371e0024fe30a600b92c31c3da2ccb1a", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "d5305cc2e1ab28da88675cf24a41f33ae7ef5a8a05725f7b5998dd80d2b4beaa", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "b7b4184e149b8fe33b5fe7e1c6aa33578558b9070cff88fb1303060c7a1795e1", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "93675bf2c1d097f653d921cf453cb89f8d2cb9d0d815149e244b976890d62fbd", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "fa9b7b29173fd763c458c3c928b349f2c5fe1be4d1d25dc097e2e61b40b09aa0", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "c3c5024fa6dd0e9d69bb3a3a35c56d8010bcd08a9bf07ba3cd555845120881c4", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "000c6cf94726b8b002ed4322df9565f47f39d9fb430d4f57ae10e739814c9906", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "f94a1ebbc1b2b6271cb411fa06008e00a383cc07c3472a040de21f0fa7005389", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "462940f9c118767fb9687c348a10221054097cb4fc77304c5be112b18f34936a", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "b4364624b5827fbd9df12dd03cf3364c35ade8cf19f01d896bf8c4b21743fccf", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "0e659151940f4acd30001bd5d1006c04b124c9fe145735617a77943a6651f2d7", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "9e1839538a542b35f646f96573cfd197bc787b178ca6bc9d71fcb282f754a294", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "761e6853a7ee5ad20ce4ab48e9c59f2490cf027e99b01a33b4a633e75dc1767d", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "8eefc42eacf9c4240752cca2edd826e2d6da360f90c2419c8d5079c7b0d3bbfc", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "1700398e104365af2ace8fb963d1af4e77ee0b886f2de4522da23a3768a50f9f", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "7d5070b8a089f8922f383f0b92be972d5b0e791e0dd0f35ed88e5ddbc3b13fd6", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "7c9f1f4e1eed631cfb0d13c012e201c19466cd0981395502a3afb3119457ad81", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "5eeaccd823f89f0fdaed5c78a0b7923ee70de85a730103b4d875e9f3426a453e", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "30243b6fa68beebb99639c10d2022a18262c812f8d7092361b59d15651d4fbe8", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "c65d93ae660ed8b2674d812a47bbafc9b89eed0122ff79fdd2d31c5a69e90a98", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "0cf3e9d209099342873581505f4c3c0b8bc408e3d81dbb782cad1dd91ebc1d0f", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "d6a4eda5b2794dc82dcb052d67e65d69b1817c7cbfd9af37f3318448b377e4cf", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "14dfc7c29afaaedba894b1ddb0d56e1ed0942eb2350b2c18c5bace94857ef42b", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "f4726a6eb6ee70c4e721cf681ecda28fc2cd60ad164442e1890f3fbc81fc115d", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "72e961f9fb1aae936c49162465583ee5c9e1b7294ae7e9d2cc9f019ba326528a", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "3de6caeaaa7003b2f5f8aae891472b4a881cb9f345d62effb9af2077513f28c7", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "ca857a5f997a9d2bc5d3e692e682cd7bbd343d508f4f5e6f46cdad3da8a098b0", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "ede28e7c94d13150ef20ae93297a666b176a4dd6d3264af18632010bd9eab91d", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "a032e657c617f3b3f8ec5b68ececeb11b538699e49c3d0de95b79e04d2c06056", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "9bbd00de1ae567d0f9d15e55abb4b8ad2650a7f9637448dab491d5c02b9b093c", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "a295f9ff7439df67e19a847e6ae41f68069ef0ebc3cf2aa3fcb3c7ef1b204618", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "0fee1244fb3d864079378a77f672953d7565742e5c4d25251dcae27cb83af641", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "23c29e9577a64ccd2a5ab574aa2f114aa96a423d72da6bec59f34caf884b1fe5", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "da18638caecef49b04de9ac9adfe5e31c9ed629560a856a01a00b1981f368078", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "95671d27a45e000bb33a9bb3fbcf3c9fbc28b26c90da865e20704c21308506e0", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "d8640ab875f0c30e32d8c4a2cf25f189fdfe7ca6f686ade973f2726ed8c2b47d", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "c5a9211b698a9016e791d57e0ae3abf9088866d4ce374a17930f9b17c052b8fc", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "6005e7db177b460c3ad2c2618f64d70d14135da187680d1deaafe0bf1601a8b3", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "8729ca54a5094854195bd0498592df90ecda51600508ac7717dd15d4301e1123", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "f52ad3f6783921b0e0a32c2dbe4bcdd61f594fa898cf515b19f4a13956f780fd", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "f1df4f5083e8d773294ac902e2244478622607225ddf19262cdff69ee3e1de66", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "1368ecac5a374cbacacbd64321743f7cd976952de4a715fb390468bcc8636d69", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "da99fe62d7954b4d19a0cb62c9b976b57263fd238dac851d9882e1a5640374eb", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "f83874ae667337dd047ac058f8108931a0c86b49536a1efbdf67b12556692c04", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "f49a76eb11084382fadbbbfe6aa09872affd0df6f730481cdfe6d4325de65c39", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "ff2d6972829c2b8b834c77dd72a099830b5679ecb5c9f9556d01feb7c052e0e5", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "327d5abbd8501cd7a29b539ff511c138724a8d90135f0afe10628fb853e091ed", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "868b8b9901516a958ac666f5276775717ee4b920030131f20264bc245aeb1e94", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "0a8991f5f1672c6c5f090813859f110b2a6ab75079c64fd8c7285313ca5775c9", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "7c689b038f26dc4da2729873b2927df62a1f5b696a1d4426faef11ec0ac4dcc6", "target_hash": "a3aa155e95eabed6a0602d354fa924087f99dd5b6fd90067272548d92e7bd03f", - "section_hashes": [ - "461b41f65cfd", - "ba54af648928", - "91a6d793859f", - "6b4b9d74de6d", - "2a8586ab3f77", - "a95b5e6f064e", - "b19f3cfc801b", - "76da990139c0", - "88b3aa0b0274", - "c1c62ed7814f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -104042,852 +36531,332 @@ "pl": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "7fdae062fe9a00e533d26183f86b2c95abe6e1d5058bb2adc15bf477783c3113", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "d7b5aaca9e4f62a7d6be9484f1ab4002161cd0d8fbd0d543aac9a0ae9ebf4ca1", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "0201713ce4cf468da38792f2d8db6105b5304bbca3bc111762c36a8d232616a1", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "f06b9fe0b2cbe8ad9dabef3cd8be4f354c8ced122699b67c1a357f9b1239ba13", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "786dd8f896953ca8bb79cc401cffa8c433f1f818254e645a33c01ac427edb34e", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "ee01336cf31d42c09f6334c875ec31b73c73eed25ea5f1e7c5db9505900d8701", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "339c3619a0f3fc5c483b6a425e27998fea6d51df0a2b13d28ed266152776f6eb", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "2dbe2fd0a40041341239c8ffccd0b812601625fe21b99b793db35b32446715fa", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "5ddf3135b6e0b177ff51aef6a08760f6c2a772482054bf618e92549cc907b5ee", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "027a018925780fd081af85943df8f2fc2a38ccd32576de6ad1fdc30796765310", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "ed46c634614316e6ed580fd246d90fc1be832b33f83cde99fad534e3325f058a", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "cc9321670553e44f36c57702ff6cdf56e18a72add5f371c84b6d8078e4c22297", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "074fa78a3abd2db43c9dc809c17f3a1aaeed74ce8115f05b583cfa58729eef31", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "443e469e4a83e473da0491d4019d2bcb587443f5e38a3a15d9a275b9f9e07cb1", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "545d37952c690d9a3406eb3f0700f187947132b2fde377fa873a94d8e9738b18", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "a38afe4fc1d36aaf191037d21385c5efed37b4679b901629842407c557015841", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "922f46fd8a901c96609e85766232b7466223c898b4c2f754e7d6dca4a646ded5", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "45fab4acec49dbc552d8aa4d4727c9f433109913283c0c343c5480e1ceb13afd", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "5e01c66574eee429a7d9b2989ab6fbbdc1dd41de656aa1c69020a9f4c548920b", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "581f35ec31d585b44ca15f785dcc7000c61db0f171c70ae2d56abedc285fde0d", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "d9d3d824f5919856b9170f66b0e99903327d00516dfe32e9830accde4255357c", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "ae3cd493b6f73705c8311b83ca9670432e9186a3d4ced5c34bdcd4d044cd93ad", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "7a7b6fb13dbf4e8a80f28153cab8e8c7ad2c7cf01ac0d32d62d882d473b8f283", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "72f6378ce260bfa3d7a922e9ac7e253dd0d38ffe7f13d4ef818a6dd25535a802", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "c04c8ca186ed3f07ee9588749d73abddddbe1e7e835a3d09461b8f1a0ae887c9", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "96d5c376665ea001f65abe5acd833474cbdf724a3861ff130b2a84b2d8dababd", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "56b3f0d29076b479c2e74fe195f426d35851233f968a2e28d44e26c3b0ac2599", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "295ca53eb0b82181f75ac0f4b80ded97d265db0fd7ba03f0cc8612d7bc63dc9a", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "8184fa6fb1cab90047111ddcb9c78477a5fa301fba6f47b3384c2f2ce40ca614", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "ef386cb8317732149edaaaa9c15507fc9f1c5e1b77ae4fdb71dc241320deefdc", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "cf03ee899f519193e4b53d78e8cb927fab44a1a1d8d077bb964ec9826cc31503", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "a27552e12667322c06f3159421c64af8b9c51feb334e257a403c37f84a6b9656", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "f5f7764581954a94fffcfd55ea8106fa795ffd0b00759c9d93b021694fe17356", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "e64fe9a627aafab4b4e689ea350143192ecadc3f1e5f70bcae75a32a9d4cf05a", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "6839046cea2e14e600f757db230a0b4912ce6f3ef8d73fcc387513989ed835e3", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "2972196ed5458520c5466989f7048e24d59e1b6713084b6e763f4641c1358dac", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "2a8274420d7174351fc06490d7e8a4798b1e1167ac10e7d2f193f641abeac322", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "fbe88c4f008727b9a0688449b890ee4321d774f6099a9c0fa650d8b1a39bfff4", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "03db15d611de68d757e2d983a518ae83282e9b9fb965a782d8413ebd21c479dc", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "06b40ea37838be299bee86c0a2756827463b1d488cee99110115c5925e9782f1", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "2043155416c8c3d13efb0967cfb4a5d2da607f4c38867d772f6ffd400721f127", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "b15c66a395840d27ba6958f61571423a76123daeb636f49f717756b27fd040e2", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "3cca2e806878af05295ae2c56fc72843f1c236a079e097bc1f1da9c87742deef", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "4ad9dc4d718b2125821e1931978da470d7dbaa310abbb6b1970aa707235291de", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "e2705c469b146709fd40554244f91741f712735d138b15effea51dbf316cc479", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "b6d8b268e85ff82d0518e811e5db59f093d8db477bcd4682efb64571a11d6fdd", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "555566438d8a7af5b02f78de44b2ba7357220fd7adf9db2c3bc20560ab8a18e8", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "dc2f5155eb487b6f3ebbcfbefa51e82bf1cd55cb590b95804be5b68af2290e03", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "5438410b8d4c29129063fe0fed042d3b042e4709f6cb8a77b0098f5473496949", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "0132de550051527a581e8f2d0d462b2cb27abe8bf36ee15ff5c5230bc654799b", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "fa9eb6003fe197ea595cca83d6c28561908051618708cc602b3cbde2daf04899", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "5c48dcbe4889d193e855defd81a31d4f5cb26fa4c31306bb25b027787580a2e8", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "699317bc25fa48a8b4c89d725bbc8f4b63f5103ab0e1dd5876d128bdeb548ca9", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "26310627b65497a6282c5330ebbc1c55f3dc4b8b0931522de69cbea8ccfd825f", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "bfb29c12b306d6f821dd4ab95dea678884051885e7d89f75b135889cef678cb0", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "424296fd6bfd5933d15ce33dab4d8dc684a3ed5f477782edde7ca43ed721aa4e", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "6f333059f06ce3445bd320c5b602e7543ad752aa30acb8ee15cb56c6d2cbcc03", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "66afdac450030480dbdb4145fbfc7e64bdc92e8c3d77bf99df450fa3ee87ee92", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "dc357a61011e3c63d71f18c5c7a5f7d0519740aa60670fa6b21721e2dfd50dda", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "2397122dbafec36b38b7fdc14e246d41ef8f7322b828f7f5fe390669b137b109", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "bc3fbfb5dc8c4e1780f48255b587bf370c6417f6fe9d5478d7f5f8c74d49df4e", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "f8f37844b8be37ad5f282106dbdd58e9175831acee1d5b8187c98059949d7e7d", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "19907a114a72582c919357cd7054ead1e37811bc657da89b6061423cc08c73bb", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "f62c6e98ed75d9ece660df75bb8791c287693c48e27c89501bf7cde35d03a210", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "fbae131263c1fd3d6e5aef454ce0a74f401c36eefa77f2643695073e7446998b", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "f1c997fbde59a8c170cb2232719c769ce8d1a156138bd8ef177f7fb905b92c8b", "target_hash": "b9d01727da7d3dc971d581929d16e2e4e1ebe586622ec8cac7ed89fe763fa9fb", - "section_hashes": [ - "dcc7a34aead2", - "ce0179110407", - "355f7156ccf1", - "9cf129a5d03c", - "a4f02c056ff8", - "c646956f7571" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -104897,1124 +36866,332 @@ "ar": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "9c2d5aea507bd62afa4a08392a8a999a63e5697ecd63199beeec2cfdfa492308", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "6b8e0731166984f5f8f503cdffc7ed991379e510d94477acce3c977628b59e43", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "29c3b444bd8c10a6b50b74cae651cd9af80a99e39ed55f3b7c2347299638a0aa", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "b7798571f4186d4c7f6689c000d80ae65ba04990bb7093eabdf59d8d9917bf65", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "fe68defe54b06d1e182787bde80f089b7942a513df958f8ba5b0fa15608f2488", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "e1eaf983c57d90e3690a0508305be7a5e7272b2b3f938dc061dec4be1e14bbe8", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "feffc5aaeac79308b4b0d76159845c9d174c8e1eea4fce4effb2576d4e888b2e", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "2d12febfb35caeb3fc74dabc05e29cf87602ce3638f9e432a7523a4c55e0f251", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "9e4ffec93a73b0eb13e96f4942af87adae2db78ec9716870f0b7d4b6b235dc67", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "876d2bcd870ec8b2f3e264d4269fb342a24ac919bd1aa531d679782b2c1f3b36", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "4870c0b23dc4638bc1ec106e13bb42894035e56902192108d2b2daab79c7d3da", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "b5736e4597d29f0e550e56e1911d02c177faed1468659d76dae67a8bbe88de58", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "330668b3c037c8f5518ff957cc8c4296efe819aeb400de116d5a830500565067", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "91619108f4d2e5f1633d90ddd32857b44c5c01f3a691decc49f864212bd582c2", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "80c737493aa83118350f56370ab8702fd7cb362327c226ba07d0159447af1082", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "27a3aedfb239cb031e9657ff3f2a278d1e23ae0b45736aa0b9088c90b0f6c73d", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "86ff9be62b41fb6020ec78f504a35bf82c715460e78bcc6e57c6cc7f51dc5a91", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "2122c778f230423e11f94ee9cd477244ad6e8254581cab4421731e3ec561e219", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "a8d0a3129728419a278a988a22c64963b37d86c3b9aaad1263096f8d928db170", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "453314f958e37ef62a941e3e2f68a65610a6adeb3365473c4b1548140ee32703", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "4f2ac5156be65757dc27e014bbbdca175d97d4550901649ac79f71b2d81467bf", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "a7d37f0770ce63ce8f95835a27368ba7b43ea5a5ba4d8472e087bd4d61543306", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "97f97fb5a2a748b887d6b401b5bdcd6f5a78d006d4a68f8fe1359f375419d28f", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "88cf5fa91b36f5056ea058f69ae5ee4d3b4780760dd1851638907a43107fb87a", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "34ce665a5af6886cc0085badc6086e5be8fd6ed4d215fa9dce432edf5cb15568", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "f6ffa47fd0855d21ebad3da3ca828f9f4cfb2767d12267ffc1e3c5c4f8112066", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "433d01c3f696e42184fde4c8a08a7488d0ca922451ed1b55b3971ea64bd8561b", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "4a26ae491c0f84e520ed625a3ab2837ac3c7b4530375e50b2227da36f0e3b904", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "51da95644d227472ead084a9943e79fa068cecb3882903854fa7fed575a2ab34", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "74ccfb4b93a0bceb1024f1f6df67f798a6efcd75573792e30b69717e4589f9ec", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "852e84b7ea9e96109d9597228295e0b9010faa2b272591448aaa04c7c9c9e074", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "f5415abfaade93a56781dfba3cc5f1590dfa2a90cf93c63d598ab157570bab97", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "109d5e1dda369b810cae83688ffd57a8217728ffe8f0536905ac19cf4c7c4452", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "713fe21aadabbfd0184814e29849b5b6ab06d1eef28e24efde4f6a2b52511228", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "ed43b2883d51ead9eca54af2be19823af11ee1e4a2752a3ab02844eaf7b5bc87", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "e0f7464f51cc0710417a83891b90995e08323c83d84580c42bc9188af06fb11c", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "cf64302d45331113e7d6f79d48b6539c9d18cfa3c93e65504d7220ddf830d7fa", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "6a9ed4c7268bc09356d5029b4d95073daee2ae1061edb72bc78d248d056ea714", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "b7d5b3c0ffe0cb0406ff4aecc325336cee042383c5e0f30b476462342e417d6c", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "7aaf9c7604386aceec9bcfefaf8ed6c908ce3dcbc81e7b64a840326674218ad9", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "eae6d1295bea96e8330480ae11cd5cf3d16778fdf785a2992184b3d281d8a92f", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "9d352d7a461569f681f621ac5c2f48dc7eda3ff732589859f4eb154c78724b93", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "4a527be2b761ca768238d590a72b9713927d5b5627309a8c8db806997527b6a7", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "436e0e41f59abba809a788234e811f6a9a8080f7e8e140856e1920610db48e10", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "82e92baf739cbcfab5f53a490335d3c58106c6eba576e7251ab299d6461d162b", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "c62101703b599bb7691ecd6e2938e01f61aced5fbb615161c930c67c7e3481d1", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "4ca729c07f268883fab4a6dc96dff08897e77f1280acb98a953e5dd2477912e4", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "f4206c61dc4ff9a8901efc58731d1e478b5ab54dfdd787af3fc4b8f2419561a7", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "361c05f94138b5da6ecd0559f422ad569dec8d63ac66051d684b7dcf03e79271", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "2a394638a7316815b941450553dca8345a01d6a044fd1ae686b2d0287efccfeb", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "afdf20be2c848f2b76b2fad705e2f5aeb933a7d5f41493c1f48bf32d33e097c7", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "7ddf792405f3b30bd3946b6d01fdb9dbe2e0089fefe43c70cc71083a426cfeaa", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "8fe430d0cbd49f39b18359c96388dbc97c8a1a59318b74c12f4e66b9bc1cc59b", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "1ade5d2ddb1d9a3bda4fc9ebf7b8c1a8aade36af0c775a7a7293f3baf909ac92", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "c877d9d2dab3aa4c4fe7cd0a16a7bb47dba1ca2dce7ff406e4d25047a63efe30", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "04bef2fec9f972571add7dea4974dcb520865aee0c02e9d69507487c8aabf927", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "c368e683b0443f62cced1c244c44aae723ba97519b44d6ba0e7fa3b94a11628b", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "26c4663e487b23d821699b981bc59d91b477b0aaa9f142535d0c8630d7e22315", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "c1a8097af1492f4eeaace025a119e1b9fdd8372eaa9d039dd0b217d70d3d068a", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "1652d039dcdb39096d80b654e9c69694f2d3fd73b5892336ba6cf2be71839760", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "ca15ceba8035ac1a00b95df46c64239d49f93fcd59e75b13205ce9f235150231", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "45e318a9ba1a4fe65ab8c2e870ce40937484a3573b0941dd5a80a214d83032bc", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "ca60ddbf16fb173e605e58b492fdfad667a25a0255246f9fcd761a63937ed0ae", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "a7b20d273bc98d212ff7e0cde6c1af5e794aa6a81e7dcbc6d07f7309395cce7e", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "55076aafcce76fcff96eb466da229be61b29e7c374f4ea4d20ff3e9f8e74348d", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "f9a2ba781de0fb548e8bc6b8b01146406bdfca2b1788d88dcba963ae38ece09e", "target_hash": "8981ee17d2b26b128e09c5dc2c1f5c2095a14c9a6ef7c0c98d8b23d6445e56d8", - "section_hashes": [ - "2ed68d4e3b45", - "8c90c6bbfa3c", - "98c8fc0e2f43", - "39b074e0a923", - "e00466795396", - "20d644d6424c", - "14e15442bd4e", - "f52299427f26", - "0d42014c4e5e", - "b3d7a83a33f7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -106024,397 +37201,332 @@ "pl": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "6e72f8f98ab55bfceef8856d14c83d808f4807c4d37fcdefcdaa42cd3543dc58", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "45d2cf1c4b81aec8070cffc105cbf0696a5cda9970f736b832431a07e6f58580", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "210caadfd4b8670fcec69738d8b65b26e3474d6b3d8395088c117f943fd1d028", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "1e75cdf89fba392b483d3d05656fde38b8195cdefb9b36ee5dcfa493477ae0b6", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "9ddb86b5a859e597dd755411ca5ecdf163e98e9d5241a5e99dddf297872ab3c1", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "a23d1109409e71c7764c088b646e460ee65a2de61d3564ca37f285f7ca60f566", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "3fbf030378577065b0806dfb947c7ad96815e96fae1eaab17944e19ad15263a7", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "1f515c97122f73fce81e1b0db5bc144fe9b54a0a3d24c5be7c4ab25f4fbcfef8", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "0e3cbd1b12b2746bb8e41495a52f133633d69c9235975e4e56c6fef871a27271", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "c1e4368a9df5a682982bafac207a13a3a4e1d29a081f2d3f68feafdb25052083", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "301e046e55121cffa04b16971c6f77ffe6b22d5c821a69bfa3dacbb57efcb600", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "de57055cdaec01c6f59613b391f9c480d29388347258b5cc88fab5c6d34e9e56", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "8e0f0e98ff3c8e021548428031085cbeda7dd176032d0d92227a622b45d65e71", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "e80354e49bf2a140533c2689d481163576e9f9e3023bfba8dc5ea070cd8a2be7", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "ad189010b3b2e868d84f598a8f2ef7780c28da9988228867fc5ac4dab57bcabb", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "cd885d69a264682cd6d3a0153594a740e8c0338bc3a70acf7869e8e3aefa4b42", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "d3b7aa0ca52e3da172dae7391b2e93b276599aa38c45061669e9407c218268fb", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "75893c8552021d3d34146dc5ef036b7d46ac498bed740f13f5e541d26b4fb8cf", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "bfe24794d4bb33653cdad184d58347fcee783f27b7abf66602abe6b7abbcd199", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "d2254e645ffcef5ff5b31425a75d6e7c64b67be004131f798355c5d757fd8d1c", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "7c78c46f181b08dbc2435fee97c345b33fa1e2375dfe2c35392f395b94ddea62", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "891680ad4daa710b0f254f112b52c0f8bb58ecfc9f7ab2ba0b143cdae59cded6", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "6dbe07bfe6e2c6588c0f29aede43056a401d14649fdcc22c5618fbb62d4be471", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "d56eb7b2bb0918f143ab07555975f365003b4cdd26c7e2fb5dfcac1696b1121d", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "87412c615a9c828b687aac3d4502166b548963cd1cf0a41fd8c37b30ab8a19c0", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "0410da6c68fd61435cfc0eded633dee92276fd8cde4ac7ca9bf74616239fff53", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "f8284273c2a041e9362852f490a713653931863d14acdbb87ef8c29cab5d4b37", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "1c1b961a93e23db33ac7275d6d9051d0a0baeb988dd88a2183afa9a2303ab3d6", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "d9fd76f5115433568bda2d7d9e23cffbfcfe37e0d5684c914f10e04fb7afcd86", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "2f663923bcbe7343828f013f3f8a85cd9bbc7c708e93338983028e7b6d445e44", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "631af6c4ad30f5a20f2641e250f68ae0f782683bf553375439dc5ce598d55811", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "bb57809a62ffe4866ae71bf0926cb7f61996911c3935200350a75f7eb5448fbf", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "166fec4bc4bd24849a754ef8023ab14480281c42765bd40da6911d80fc848be0", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "a93ab83a042bcb6198887eeca0d7ddc97ab1205c1a55c15b0857ce6105eaf76d", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "38a43da2b36ee2181f26da9a6e0a31a029f171f77f2a60c3b5087bd55cdf8dc0", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "ba641f284728e0d68b55d2a522a873fa45c74c54b80938315720889e2ee3dd66", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "3a64cf1ad16877d6da05cd4cb6e1d0fe8009a0149ce3c3fd159c7345dabcb983", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "75e3414024f61915bd6ee7cf18a6197dbe691f3c0738756e6ce0fb96f184a522", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "5f8b9cc0b2a0292a72b1e871c78cfeefdffcd51dd32ee48842073fc2c47593b7", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "762a8a0d7e73b49ebaecd8caf02d8ef81f20ec49e592742072dbf67ad5a568c1", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "df1b6f27034bd104d056b697cc8211f8d6555345dba3cb0c0d0227b9c84b4ca7", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "169664d690c775c629fc8689dc6b2a5c422b1c11da7d59eebff72150ab5195ac", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "43c51d98c192385ab0e29f4c6201d79318213babe6b6dd310ed89a20d8831cc8", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "5b1b1de25cf42e78335802aab828325533841ed4a1f339f8cbd6402368707312", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "c5e8c83f4893f32cf0ab2c7a929b8a62de3fba824365eee2671753963ec35f93", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "7eb12f5251a84547296a0c09a4b623689609ed568742dbc082eb448c6c17c0cd", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "4a0d730bc586671299003c36585a71b110be9632e6ead5b746abb505b88a2c51", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "3b23c715603b7542bbce5e32faeeaa8359f0001f847a1b91aa9223f204c77432", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "9935d0dfb3479a7c7edf8167fd0661ab654218d3564afdf7fbda1dd587cb4c11", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "f442c127bc208d3a1cef379e763f1ff006043a3cc0159c28d476c97719d62e2f", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "08ed9be1e807ce09d86299f0320be554c29f5a4ad88fb755b24b43fbe6a07e11", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "bcf3acfc6c32c7deb7e7fd1c6efdc2045db3203b058cb55f7541004c9299e62c", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "48650b3a801146ad69268d492d1373680d8a7d6991a350960a671d1ce4e430e7", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "8b62923e501d50febd107904b8e0f1e7f30a27fc6cbd8a20157989a4df05940e", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "32fab7c1b7723573e6162a01e6c7f74338a80b6e2a2e6249b16f4696d0545724", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "8f91a2a90df7534895ea12c1a3444f1112b8f1b94dbc152a5c7d5dda776f8b31", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "9db0726ae232ebd5eb2d8bf3a6c171b85f10247a7ad0ac6dc8e6f62ef3a8a66a", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "fdfa483ba15a4085eb9dddfbacca365f38488692e94312860bb561d581c90594", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "755a26b4fa80beb9e3b3b7949a9260196ebdf9d67468d0e57c66374d81c8612e", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "52f3f1793d3a83770a860b03295b5bff926dd8d4c92decf8bacfcf00a0e7423a", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "8dfa5fba986226f10a49bbf10984ea6a9428e3c815efabaf2cb1d1550d8c0cd8", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "3d14a73c735d48ab6e489c680b075ef265d6c3335d348fc39bbae80de0d7ce91", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "dbb63aa527ca3a2751379d4f8fa63316158a8e8770186515a1965c9063cda589", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "2b1f88c9953529cf82a2468854a552b9e2ddc2128edf935fa367ee37fc0fa2b8", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "1831bce0b716218be21415a5ae3077179f71c53d81dad2cae6aad9d05ccc1c0f", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "afe4fcfdc2128ddd524148c5816437b2a4be1e34a295d16b2bcb70aec45722a1", "target_hash": "124f890a560c9cd7f79235598347e25340f241b707d53955319df8cf76fde556", - "section_hashes": ["f6eb57a75cec"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -106424,787 +37536,332 @@ "ar": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "6cd24b483c087485f2d874a07164b5e06c68c6d7908bbec18aeed5ab3e009fe0", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "8e9b82939f3d2d54cf6b341bfda30268443a16ff64cf8a15595f96b06292c2a9", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "1820c7635a558698c3bd10684e3b36c6cb46abe7b98e1ac46d861d4729544d6e", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "468806bc1fc40fd97e4b4574cb35a2b8825389d13fdc7cf27a1c253e97411ac5", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "68850679b8d4f1ad973807f9a9a712d604e1f82bb299d61c234d95fb1df01171", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "5e0e5a2cc6f1df06a3f9a0bec9411c50683ce193dc7e79f77b1261389eceeb09", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "fc4e555af5f7342002fa513fcfbec29a54afbb32e37e6c6979490292ac6e53ae", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "17b30fe70c2ce451986c21db550597f15b93b47db87a6e922e05bcee7f206a8e", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "7c2c35d7935d5234d78adecb681d3bf0855cabb384c7c38ab88064442a46a274", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "434e34e7507ec77271bf374e51a216e94eb123ed51ff0261825689014ba7b3e5", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "00450102e146da4ab98d1023eff0f22f3906c095ca9208e3234409c8c5e15aa4", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "5158ce1dcd6e0a9f2ef9e92ec39a2fc71d2f2915888b94b4d44dea9bf40c10f7", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "a5defeceb32f318a891a9bc311cdde787780c2b1998aa67fea1e5a086ad395ba", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "98e8c2ca3d553bfb3e67113e6eef2ea8f56f033f9170ef5bb7fa54d1f42a0be3", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "0aae43328931e3e763b50a72381f7fb3550b098f826273cdc9f46bc979ece496", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "430278708a877694ae70428630fdb57332906bc22b34e795d1f1b75206ff09c0", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "5ee44bd4320709220972505a9b2523a2d62469dacb63b3a7c95c36a1746b8588", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "b90aa0b20aa071513111697b59f4987e3af4b48a0b89d3c4a63365301d28d5af", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "875841ae455680b98ba13a580e54f18715d92e6673c77d377765bced755b5c86", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "977f9238ff5b40e75ab7768424d7b9e454c4b07adde7e87d26301603cf494d93", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "03585575a47b7ea8c65b5b5b43f57f8d5698888d24a757284c0d5cd0d0d25bd6", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "0adb4f7811a30c5964d411d868604277bdc0b8e3793afee97fba35af94ed21e1", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "4776046e0c34c619bf9ee9b1ece415617f0830776bf1eb742de167473d2db9bf", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "35876c9a59ebe96e94b0d937eebd21525101cfc69471153d87524e1b59af6188", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "d438e22c3fa2f1552702f876e522b8365cff35d944cf8c63cf42b10e1227c48b", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "b6c50ab13f52f6e2ad30d91290dc63e7562c53f408aaa1a5e91f31e06d9a8452", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "28d65141ac03bed2387d8287350921ca854a987f3ae5f5741c39f1e670cf3549", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "94e84fe4b632bae5f19afc8379265ad6b466a7b263a27310c6d86367acb17491", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "6f44a748d876f3c727eacbb381feb24a72b9111960d1b41849c0d78114718650", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "783d909aba1882c835257942bbd3e07163c2528de7d20cc118ff59ee873326af", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "60fa8394d557cc37f8abdc22c01ffd0078e64a075083503c1da672e171c5809c", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "177cbb9c810be995adda1b8e5434e2b9a4b8af4fefc94e296c90386a4042d49a", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "3642f103aef2f2daa8cbed79ef671e7ca191551087d18f5f3c7cc9c45d8a617e", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "8b2d6484c644f367f1af72791cd081427dc5881477cc221bf22f8b72c1995ae8", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "6d2831e39637bdb92ff5250d5d62cfefc592909ed828c460a458cb8fd3135a03", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "dfbb6d1d59d1362d42d49ae69c763ebf1d4a7f61d57fcf846add6943d8766846", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "c1e835536712bd214d1e46b608c9cdca220bcd17b762aca5c1256e60b3d4b448", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "9b61f66bdc4f87ac67a582ae224c8222f17fe8f2a5c8268ce1b883367ddf2b51", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "6067a754eed6a5a22ddd30094763bf0371556bef0f4aa882c1939dcac7500dab", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "f0335d784e25fa1f04ba7ee861f6bdb7902b663b5bc78c2ffb615187d27b6c1d", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "2c487efcb10a3c934de4e4207bf8e1be2749a53b4ada4b0fa09c82bcdc739e2b", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "bb4fa729c63a36ad53c05b76195ef90f3ae1db55cb30267b37a4b69fbfda1f20", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "c2b0865edfeea4f2985a38a1d049471ff3f4275007f729e212b88d03a4522a61", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "d9572924d7ddcb1b420e4decacb1b0bb95ecea4678cc0898997a90ac5306b27b", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "30cb3f5f18c948bbd7be25ab9bd9b639c6214e65131780ecc96adbaca4d6cc3b", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "d98dd25394fc867c7f73f0295ac870baf622087c2ea068f912075e3a77625e8e", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "29b712c94b5a009869e9c52f0090d74986ad019574e0c98ec3d628750926d03e", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "cb1f140892c3601415213932d2f0bf7b31360f5a09176871a72a80517ada12bf", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "81223ccfd73a0d15a14bcc96e48d0f81529cb65318bea858d1dd8a1c69692f9d", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "056fc3382a5ffa247311066a8e5298f69bd2cad364f069546ce59c1cd416947f", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "0998b47f9d3ac9dbc481057acbcb4041933acc5eea08cf542b6838d8d17fe102", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "f2411d77c86a0b6711213a7bf2ddd835b2870d71468e525c9d7498de29cbbe9e", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "6649214621998e77e8136638d346f474d50d7ded95c08a83f19b716edfad2854", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "6f1316120230850e84b919d577fb8e95e05295ecab01ce96f354a5e2a090f7ec", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "b72224b3a696629ca853c3382bdff8f45c403ef3148b3a6d5e4c5df5e7ed1190", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "c9982d98ff9963573248aa789250f95fccd40592b2f44a98aaaae493e582ff93", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "de1e64a965407c5992f785eb3dcfcfe69b2320b1f2010bf73e7d7137afe82ce6", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "21cae954466b9bf3995d0e619b6c84d1b27590a4c3c0d7327f8ca6df8b149ab1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "88ff19e64a778090bd9758f05685ed9367aa1374c4a0d3ebed355d3d4936c98e", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "79ec10caea2ccef0099f8a1c305ffa9f15355bb84c105d49e7aae9ed2d47c6b0", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "f1870d58ca8a7fcd1e560658548a4dce681a8df8d7af5dfed3099556bda882f9", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "e6dd303e88614c31c987151eb7e72c07feb8872f0ac2996fad7fe110c1925cb8", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "044f4acc37230f2b407bca9ed8c9e074eb5d0b20997ac4422a4e96570edb5728", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "0e550b762ba230b5f71da4368389c04104c7e190e0a241b51b6f50fdc5cc6378", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "b83bec56ccc18efa204748dfca11d4fdd726d591cc43154c7739772928c65915", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "9e8cdc3508e243fe2ddcbd3e556a4324f7c045041c36132ff86d58c627943804", "target_hash": "4076005985a7692655f2cfd85224a56ec86931a4203ddba8d4899914ca80a43d", - "section_hashes": [ - "449fa7012bcc", - "5726d3b1cb81", - "3f8aa8c3a154", - "49b087f71cd8", - "aaf3899beb9e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -107214,1058 +37871,332 @@ "ar": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "7d53f04131bf2eede1b12e1647d390a49474af21c66cfc67992c9e9ede15447d", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "e119e1485fb2a7624b9cf58bd75251cadb6f91f20a64d86ac920ff2051e238a7", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "e2c006885de1098097a5886bfa106f86bbb9e99b743d5596b9e521ef85bad9a4", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "cc6f6b8d032b782ba179c0b327a50c5ba526479fd5330cb2dce5531d2c629edd", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "6a31416ff0ac7596fef60779b9fe073d14551b39e4ca348caf12ac915794fba1", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "16ca5437bb38abade38787b20b376beaac2f52a8d6c94f5db09c7e045bdb68c5", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "4791190bf89982f95f726953fba4176a174b9709f35ecc80e12b864dd826b0bd", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "336f1a114b1366da1a230b2d0ec8e2abfc7c75a43b54bc9783474163ef242c35", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "4e6f298feab15bcc2370801ce47ec523688ba4ee81bff027dca47e4c19f7ce41", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "5164e1d89fabfd91c02744c3359d856b65138f833e60fda28e4590584c03ebfb", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "e860b4c1216521adc0536bfc56a4c2d6e7d64667fe827f383e647ed1fc231fd5", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "7ab1c8c0def5625781bf511f5a0834c0a5903f76f3ea167d30973f6ebf43796d", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "38adace701df738a1b3a5394a868aaaaa111aa05a314ca5a16db2c8437928f32", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "ddce64c1e9ad055e156bc9b3870194f9f9a60415ce663c2123bd0abeb8acaffc", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "3001f7abaa8351fac50174c7afc267513e3d9031405f2d719dd43aacd2b70527", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "8c725553a53303d2d954adf128cedb4cf3dbdc87e48db329a42c7dc3a3052c1e", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "d2531f409b697fdf7070a887d25349dcce519bf9feca2b9e516ff438352465a7", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "03c649a2bd85ebd27c50eaf99e531bf0cc0c16c9eaa35f85b85e9e1a2c2a771f", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "c1248496630093395c673379a1c2c5fa73a8dd050570483f372ac80b125b7173", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "46073b1c85fd55e422c68ab0161cfb87bb86b95fcdac822a4183b7219be6ac72", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "eb4b1003803e654588f32e41ae12fe1e96f44f431a03fa814ae898474be30934", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "571cdb54e311efd90fa8d6de79a500533bcbcb2148579ab5c6c4cd174f6d4500", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "e860ff5e3e54cec3f8cfcd2f9b4a8aeffdea39acb2577127daf79761c7097571", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "454e18ec5760e0bc1d9d84feebe65bdc6a990ff942e1f0a6757eb7c74a25f288", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "8fa205e584b7e494fc787b4ff1832d0ba68d77288ef081560531322800aa6239", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "44c79568d375b71563058568b1248bafcd0b2599fb381a571c9db7419f2abeb6", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "3704e06819acbb0b54355a14701a510c3e9ee4c8fb51d8c94ad8e9a59b19be38", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "1898ce9ffba6696fa617661af0e84dbea0490a576288f60d8eb68b0369cf0767", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "882f54a83c581adfef134ce19090d01789ff5f62d5e65a51664b734b193f27bb", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "cb59b99ebcacdb84ca66a6c7fec90a4567eaf9e6368997a108afa6a5945a3f21", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "c11303cd0b55e649bb55ddec04fae1c54db5e510c634982d0afb5b5779d26a65", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "1f368c5d4dd94fc7e2dc68767d0261f1f85429ad06dd484ee2aa063ebd5782b5", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "6f4e46e1b666311010c81b0364d89dcdb3372639dd9fcf05e436cb2f24c8bf7f", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "9898625016f3c1285bd8340e8310136d484c80111bebc021ac0a7774439fda27", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "7a7f3339f814fbef613b85ed54aa6ca604d624eacf6d8cabeedff807ca19ffce", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "258b65b33b9aa711eb081a128324af3535d038ba389ccc0cf31222bb9fa905ba", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "981b91ff3456e9714427cb1ab720df2ef0d9874eca4650b2790cc839d7347d57", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "d2ed901d1acbcf629e3a51ab50dab9d635ae6da8928c6becb4d8a00c9733b1fc", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "3693de51169fbc6973c9a04dc8d7ee0638e8173af8d265cc2c9b13fa9d5bccee", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "df052db5ee43770e873a68646f916ea4434b0b07fb4c28d7a068255401e3b7c2", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "c6eb9b9c3e79b54c60c2f904a70fa789c83023f8ac9df4f9d0a6da8459d725cb", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "99e37f2a0b8679dc8c0c8ef27cfa566e32788d33e0d2f2309486dda48914c82f", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "40f707f73163e5f767b66d64f3d39b6598a9199e5d2e476eb79d98928f0b354b", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "14d86fe10848889001a9b36acc4c2a7d62788e901b53c65ab422e26a1f1cebf0", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "9c97cfa39d841e031f5b61fc9ab2eb39d64a2364766751211fdc1a1880c2133c", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "d39781ea10f260364bda5e9d5c1459e63e307ed2c3a82d3c880705e03df4aa2a", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "848a30419d7389ea1d44798d17e15577c2acafb3c0e5a12dbbba3d48f07fe505", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "7ef0f49d74716778f6eb06f01cb525f1daf2bfce47ae14e05832bb0688ce01ff", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "0dbf2c45d0eea77e8dc62f6a1a7963d6f439c334a20b97717ec15444d63f564d", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "537dc6f64fb5564932cd054c910556884721b4c971a288e72269983bb0828c1a", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "d5e8a3be79ad721015e1c198e58761ad29ec7b77b534aa3756c814310514e025", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "b81a37d253e85629a9ebb38769f129eafcbb0bc50ef597a504c02f6a93530701", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "afd8a859e469f3afbba8265aa1111af1ef52706cb0b94deff153356f6a25da2b", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "8586f399ca487c222395f79a0ecfbfba99fd0820e12418fd7a56c1f81f7b7924", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "f57b1438a841a51ac8636ad82a4d696aa0cc8c9ae33df596817f4dc5fb52c6c2", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "434952400e5011f0b50436fd910590c8ce0b909010b7937caf9a15c33ebb5d54", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "7afa8aa5714c272643e01a37cf18039f720a2e6a5f27c484cd0d26ad897b2f9d", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "61d5a8d46c710e172966950a23b7607961eaf13316ff3c7f85816fe37779c9c7", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "235aa79212b431ae66e4759c1819df6b21b9c9e41167725e92ae261d322da740", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "4021670dbb6c3a1cd964844475a1840562443aa973360661467e0807054562ac", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "fecddae9418ea0498976308567afff9ea60eeaaf1964f84f31b2deb6f89c4329", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "b8ba85bdff81e6394cdc21885327afb5c22048bcd6c2293aea64497ac2283148", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "3c8ef3f7d6fc6ad641cc2230f54ce6c7b3d7927e51215479caf05a38c55ca2a4", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "69e861b35cf12c225d630d2a698358b3d5b69e1d3e779ef0bd456fba1eab693b", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "c57f895992c8fc9e3af24c31f34a0946f451433e26d927af9b0706f58f9d12ec", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "6141459487b9d9d3c17c9ea659a2e31235d5c8fc85504a9dda09618db5bc9f0f", "target_hash": "503e976046ef33c641dc465eda196d7a9b5fa4505a90bdcdd23bf157170df3b5", - "section_hashes": [ - "cab2ed0f6654", - "6b14fee907d4", - "8b681e931873", - "b2f2fd71248b", - "49f1532e4e45", - "c5dbfa878c44", - "f64bea4ef729", - "fab14aa80ee9", - "241b5d10a60f" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -108275,926 +38206,332 @@ "ar": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "671a61df58ad3062b84de37c5d46ef90b52a12823980d10886db904f398c3ce8", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "18b2b8c28877e3fb52574847bc563d0c17af6e6c15c6de2943ac4232c93401be", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "4dd094372a22b90e765d4c730d21e0a8bcdf278d7b046c3cc0467dcc137df253", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "5feacbec961f58972b451f347cf66fe6d5254350a448d9b18ba294322f77bcf8", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "4f47d17d90a48cf511f9f89087317700f42e37aee9c554fb3a452c53643ab8ae", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "358453fe254c301b0315ec20d4b0e75d766aa5fac970f099db8ff4774e344d3d", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "4bd6e2ecfb709a3962f01eac7212258547b1b4a9925a5741638a2140c73d3bb5", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "fcf420568dd7d5d14bd7b89bea049725d85b5382404a52cf441f71e8b25053eb", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "678cbf42a3795f5f398134c9b998a4a3d461426db57fd9a109881094aa2c2727", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "b02c77e30d41ee04a34d45b75685ea8dabf429bf8d8bb38c5e1b7d203a412909", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "c23ab13c6af91a40c102b72fcf36a32b583c7d81f02e1f26df4076cf02eeb41e", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "4dae3dbf994ec9192e6dd4fe4c17b7f2b5dd3f3de2aaace0ff5514811a3b5ddd", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "ed2debef2eae650e846e0818eafb2bcf2685b69f93c49f843e22e5ffb3af318e", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "8ea5f6a13f2f5f839a4a9c52c297787e8067f7ab287a03bf4cd7681a8cf271c2", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "8aa0940bc8e0097a326164615333a1a837be55952738044f2fca0f6ee34414e3", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "fefe3bb28daa6574358b9c18332bacfbc9e6b493d459948db162cd6fc9f1b46e", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "b31d7e89ba8a5993dd74bd1210a20877e8799667e8adfa89178c90776df1b9c1", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "539bffb44c3fbf815d09e2208c2040c953466a4a2e9533788550f515bde3973e", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "f3aa859f64c9851c3778247e910e233d6e2cc12d879669447dc37cb89387f6fa", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "3ae785849932a95a6672f907113208a677ab648db2eef3a4689ded12479be9c6", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "0a8ee68af2c0c507d6b995e9bfe15aab5be027b945ffe01ad6151b8f8df090c2", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "7efe144efca6cccbf15b3093bbb6e275e7743ae43d2b958b5dbe878ebd823c9b", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "8683020b63fd67279b3f73aed0036d7a2f9c9b629d3946e80e0250ff3916106b", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "df1261617aae438965379528b79a4f5133bff4964b81f14c90fe9e0fd7d90c90", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "1571f129903a09b5424465f456be819f3c60d548a46aa9da4cd263086e95b5ec", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "a7e0bc06f4ff877998d10862fbe6c41d3568ac604c642fd8875c907eca12955b", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "d374a522c83981874d60d129320cfff463a0b0dcc28e83af16e8c70ec62e6cf0", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "ee53c18a146a44189e1f122fe2f68acd7373399cca9b1ea4a43bbf5cec2f989e", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "a353179f5d66ac2c86c3331976e6b556ed1c35b48f40af30c1a821f9d691bf15", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "0dfee2089592361fa98bac331583193858e6a6b1dc6b7b7ffcd0cdb77b6353cc", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "269f7ff8830c79098c53486674e8c24b0848fbb49b18193f536126e963bc768b", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "7eacb2b9aed2f8b1e0108271360eaa46c6e9efeb2b85e473c496e6eb1299dd7a", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "172373e79af88ba3771468d70bc73125712a38857f8730e4ca2f6d133ebab8cb", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "eb74e06529be8cf4207970608fac9cc2f6a46bdc63612ad198de11755a394b2f", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "3e2e8b153a29bbae4c203d4352f24981449e830e6c4bb735d1cc9d0e1a3cd3ed", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "125d41dabbb5fb3857de3968c16d2c011251b83b575545c074a5bbc5daa9af95", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "b4c14a5378ec0e859f99b97388f5871578ced71a2cac9e627efdc94e0581a7db", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "8f415e40ee88cfd51060bedf875ac54c1fc975ceacc15ef40695e56c48d7592f", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "b8925d5626aed9740bf4187018e530a622989351271e852762e438f936e751b8", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "5600881adf58e7d3b109bd63367fdada766ec0a768578642c91b1da3710c7052", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "af95afd41801f7a1c114aa6690da84622e17a714b660b85c2966bd6022a58d3b", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "495d6894c7a06be9388d632990d0da9b222d7f43d53d9a620f2e96cc7b223485", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "a23a6de132d791940344df1c6fe8512f588aa69f21f1dbb61950237c8765bbe9", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "a3553ef7a80f7a040e67fef822ff06b22e59cd8384ef9379a07cfa210c43fb31", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "1cb62adf164884a1ac8fd11dc6c6e015062dec4c70be23766fe5bf5aba07ab77", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "44a5b8c28943acb3e12843be7f27e932cb8f5a9d6ee02f7802eb5531cb61533f", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "3375bdcca8fc3adf65729ac9827f6ee2db45a93c1db89bd2a77155bf0cc1078d", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "d0840695046c739c30802a7e013bbe5bb7866de2f704a3dcd8c787843fe68347", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "d8581aec1a4de660852472ce8d4f72acbea1f4202ac50c57f244255b897ecd49", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "2dd8f1081e64c776acfb84e1844d8bb4faaa93b4b618ff10df6320f86d72d9a5", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "a688240b07da911045e706c937a78a511d06be810a280f01ae3733cb5105e682", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "ea08e468e389328048c81494b9532aafedd14b35b64878cd03c573e6257006ed", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "bbb69a3ed2f469741c0ed9f931016d3a42c5affa963912a79916b06abfc26a5a", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "6e964bf065267f7324eb026dd73bd866afc60f31df9e1d84770ccfe0dfde890d", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "7de7618168a4db66446526609c2026228ed5d49506c0d42e3c8f6cf171795aee", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "caf3ed7d6327b548bdcdcd0a6dc33902c74189523a8dfa73b51d6b38a7a82632", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "6c5e33bf33687580a37a2e5c499ea10ce219019ced8b32e5bc04d6f6429b35ca", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "81ccc7753ed9541c9b92ce4cab1bbdfb10b83800e0a7c40f871a3ca51198b21c", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "73999a9a4fce55a84fe9ff12c4649facbe779e935f8e7c9678e4f22c2c366ddd", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "8410b5c0baa3b8c866bc244e01e6a00c82eba9aa2a94800f48c19e3b734cf374", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "b6f126bc04277e3f5c62125eb05e8cd709ad71bc0cdf43e9bf914c343d0603b0", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "4f7d1832ecc4d8f19d0184c5a6967f2c45ffc0565c23fb2b8742d715f0e46e90", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "ce7e1c1f584c80230049d76ffae658fb112f05bc57efb51204c999b3d70fd302", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "241256fbb8ed3bb20df0b94c4a8c6044481877b434535d01d501f12eb6c4ca9d", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "ec2b383d0b960cfe0b7cefcef10ab061f39556383a82a36c2305f32575817a00", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "67be529170f43f063f35359d76704637f6505e4fe2074b0656f8d6f74dbb3a7c", "target_hash": "c77dc0ece40542171ab21e78cad8e1dc5b9c817ba668b02ed4e1c2b58883c847", - "section_hashes": [ - "e6966016de79", - "7b650529ae98", - "38d43e4428bd", - "5b6e2d9f5c58", - "2aa25f99965f", - "983f2d00dc52", - "f206a6d8e1de" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -109204,4684 +38541,667 @@ "pl": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "e9b4983080533175eaf987e640215c6d0e4847aa6bde2f60631198fb39673e4a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "3fa3d784db59356d9810c9b3d9b7023802470d4454237314ae118990e8763dcd", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "d8a5937e737157e7af47cee0ed86705a1f30fc968fa688b16dfb88bfe402bb33", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "9591124bf886b9f7e6814d9085513fa126580766b0ae03a989a1f3164d51bad4", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "8a81087ab54c0e0258a672fb48931220d32606ecef763bde078bd4d65570d264", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "168dc43b7005f5e7af002331e6bc67d4b20bdb79e064cea26b21a1de7ce1bbcd", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "521f7f090cc605e11a7405a1137a86db711d94009daea4009b844a3c45da2b44", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "4fa6dd858de418b7b154cd43c0bf36613937512e3d373ea76a20a221fc519f70", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "f130735b38ac164021688b7417cc5c3ea1606f869b7604e248d436ea267b4fee", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "b0434501ef43097862eba29bec1c5b56ce0b129d3158112a356e28e323fda530", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "53f2fbe834b19f01affe1df42a33c7136b24e0ca5e2bff926a6c5b631d9d8f54", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "259c2cb6e47cbee38767f707412d47890204d893899366e9650c4269e48e906e", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "1ec517897bbd7cb02d98797960487e0e49fa686951ef4b70ab8e87f7c4463720", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "7e602762e5c1f2d59990bc5f2ca8e550cf07551f4c9e7c651bffb2dff1baefc6", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "a4c4e675a326552424cf3c33b598faf1b5f8abab8af86c2c82d8366e00489951", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "296ecc9548795482a9017a21fb592142c842fdaf4edc7c82b9c2cbb005845251", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "e4d3463b17ba8960a8960c3e7b24cc5c7aff4d841c2cddea4e82c3aa89e58513", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "5f6d31a56eb0246559c8ff66ed7a499f5209e358a9a39a3e73aa35ec8e615a25", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "588c9f2115c046cbabc53291bbf05a290df60601c183ed2ffde4120c448ed4ca", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "490d4bf0cee8299ab12401b3925954faf5ecb498ac718590450dcddbd9ecfbf6", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "2acc5487dc7984229be0b4e23dcbdc1f19581e9b0edeb2815c2621537908415d", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "274a7fc59e26ed4d8ee3b1499906f275b5152532dce04ad2eb0f41e423e19fd3", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "3c70de8cd01bab10d712186f9efbb2892cc9c795fd078ce7af8aefd94edc372c", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "2f1f0fce8e7f84387c110ba58517a62b5918c3f91d1c94030b3c09a4aa766ae7", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "d75a0f8e681613eabcc719af49ca00e921b1214a7aa9255f0043cb6ad84f6e44", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "da9f62143db38d26c21508af314017d987b078ef42063631f3c9625bcb00b2e8", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "cfabbe7155545337a56916ffd8a9300fd4a4f07720a4cb1b1ba2af9390a6bea1", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "6c6df4b746e7fca696195f748534e6aee30ddf3c221b5fe94a87610573d2ff68", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "ee3de06503cfc1d27577944dd4ba5695f50e4bef0a1d02f0060e944ce69151fb", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "40b84a7eb93b7b4967a1aade1684a8fca11e59181ff6c34ee7ab1408774e48b3", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "f3234897c39ceacd49c15b3292231258f3e318f10cc9694b32ebcf9554707a76", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "8bad28c43e04ff41a5590dd6f40a16171402f0e29ca363d060d44c5e7472f745", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "178677d092c0e76826368f2875547462f785c250f1ab40f8da186e93a6802e54", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "ea206e1117edffbdfff95794d7c0de92c601ce3f563ab11ab85cba6c4046c28f", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "dbec3e69748e2e7c52d9a138138af0f3dafa0e06e287efa6aa1ee27495a2b4c1", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "70091aa683ffa242b34fc1ee082a3d4562ea1fa29066586d79f2dd277bcec901", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "80a58d8bec2d6d4a3030b9900a5894b0b0b4b4855eb51114ea9954f908200505", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "7ef4a31bee1c93b4978ad974917ea7fd135db4dd84f207981804a1fe765960ff", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "047c981a5e8e2bc774b13081a504ecb754c54b968d8305aa0844066156516905", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "b71bc2cbe1711b268285f712a6234e40f1c2ad0c2a6560b6b874f8833b151047", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "7588986caf04b2ebcac4d617704d35e42bc22988bc4c5f2a0e67add0d088c99f", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "1e8be9adda62d13da82a8eeec66530f5f5f5342f988ab17c5f117bb1b9493a39", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "bac9f06e622546f54519bb4ea3a26b92f465fb5e19ff8aeca5f8978977902d79", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "1e321792c2db78837fc38dcfa35929c800c1aae6e87bc82029b95f401242e2c8", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "9a0071065b2272ffa556f54f68b003df8c683235a8b3c5e09bf04a0afaef6305", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "c9357cee6610c8bf5d7148ba401e0d81db64fd5799155684831f7ae11979f04f", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "95cbd317400afba07e0e8e0f8e22afce719de32353e45da214798797371ac9d5", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "e172e8efa80c949811aadf50acf90122ed98d2b020e24b52d46a39a09af05390", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "0ee1d682e9096bd6b984de699a3f9ba145246c4a7a4a10d4ba58bbccd9256586", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "bc7e279a00e3fccbc005095e1ad85d27439e58c6eaf8566c45276cb960ada8ca", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "843173315039c4aef66fb5e8a34e4e65bc0539740061c18192262cf54254f259", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "a00182f7f98adf13d5edf0c27812a20d566fd770eb3c230c1fd0db8d8292c8e0", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "cfc4b64cdef866347a673979c628271b281e10966a80243c09682aa8c8bfa536", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "f97527613a97be889b294fa5a42823399fd51c9d20081daca40a040705d16608", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "1fa71927948fe5b3ddb2a7a929aa255e3b79ccfbb23c194685ffb996f019f948", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "3f17698ac401b781405215c5ad7b6459eb65d6567949630a384ad015b9fb7660", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "3d02e683b870084107e1150077d015204d1bbe0b57bba863a122cb422d0101c7", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "5ba4ffaaff52468e3dd94c29cf319a701bc3efb2b8a295cf0a237f3a9a92c804", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "601da8daf0b91e99f86cbd4edb1295d94efb88bb95a5aa21f5ff91f4ed359268", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "da58854e501f37f3405f15ec127812245b38d6fde7e5861f0c9750a0789d3576", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "9cc62075f0f0e270474b91f72133590337e44790e6853a3e5269f70af2c80919", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "8b9fd42e209e28dde6b44edba3c99b781175ac6217bd9fdeaf2906d9e068d6db", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "21981b909a7ad78e55b7ae9ae2a508785ecbbd545c0d218ef18ea09768e3c7d8", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "29aee38d089aa982a35598fad3e8a28b88239872e66509b2b9bea3405147a604", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "053da0fa2afff661e580dc5bd78eebf5a171b7a7572c73b4a24530e846e446f0", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "60c679beb7e4b5e31757af38044b33ddeb718951f81c422772693d37effbf2b7", "target_hash": "c4b65a9837fa6dc8f44e9155a5efb7e1e2d4aa7fa8b4287857ca3369dcd0b0ad", - "section_hashes": [ - "5cfb138f9a20", - "242aed10c6f5", - "1f2a4482088d", - "7bd0feb759a6", - "7891eaa056bb", - "989b4d77f418", - "80a37ccd59f6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/reference/API_REFERENCE.md": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "locales": { "ar": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "3f6cd8cca84b92b25e496bf2cbdff2328e9e7a642936359ede298e780e802b8f", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "5cd564a53c65ba56e67801ab5d7f09365f622b483a92d92559a1b99be80c070a", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "63c7ef77a7212103b190b5cb0f2f5e069e9ff99887116b14e6cd61d68670e28c", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "ccbff9b88d50ec71299d0aaec4874b3f81f12d8463cf5c7626b6dae1f84d6ed3", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "1824d4f4b38e4303958804f30aec431979c3f63f23c4d0fc32341b33b444fa3d", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "0ba86336ffefdbf45d81233480865dfe1319e1c0e06f561d6eaedbd845727f16", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "6afb47241154e2022ebfcf94811e817ed559ef7aee76acedb1857b7bcf8a0f2d", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "b1b6b7700c50f9aa061c001189d5b82a8994b44a93ea6e2d23dfd03f904b2230", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "2ea217fbb9d96632dc638176346f84abb185116546c81bb41bdedf1b0d15ea7e", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "8ffc3b4ee9a7451370163e34ef4e6097d6de6a0e04ed94d0cd33176ebc300411", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "cafcd74b808f18445436296c08e54c5649a2f1c84378a877e134dd165f10540f", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "06a73e82e2be80fdd7790eb0dea6e6f1e3d6e6754027e5a9b1d1c3320ead445b", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "2e73971f0898468071f0db591180cbf9713aa6405d692d1f8ee481ad22691da7", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "f514c468e7a9e80bd0aa200b34472d9444206b2ce5bfc388bda7ea560de39d79", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "bd2d7f92537ff06f7c448fb452b9ec7927b80bba96b8b86962d8573f37a397d8", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "963e962e5243745c268dca6233d61be0bd6441cf8d57344c08a11a73a0f52311", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "6adfc25acffcfc344edbcb9dccbaba0141a04fbf4fec5e924ee525a97891a00d", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "045c9b3fef50de1598dedfe6e877e2ca098fc20a47548257928c54f17fd886c4", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "699793cbdd1c625ca2ab710d48145be2664242c7cc856a9a46272c2e27f2ff8d", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "a880a4739ed0ea7f10768eb77948f4dde4c527cda608da341eabc35462bbbd26", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "c99ca8f30763ecdd3af89565ee0d5cf78353f0929d0b3fd0bc4dd43ac029b010", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "29f872c00a8c5ae1487c5ecdc3a958c38c61d8d1adf9822db126553413606009", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "52ff28397bb1e0799e398f13eeda3d9241ccea2549228ff73ab7742add424de1", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "a8f8958916649cb5e651a998c59ad9e1cee9470f95136b6d7f9639502df588fa", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "9b8b50a1aeea9590e33d15b40d6207f6011303fabc5bd52449115e8a4569d1a6", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "ad18c310fbee36db71b57b3e38d2cf5cb8412c522b91dab04c293598e782a348", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "a690e3999aeeab238e0546578e25f82400ebcfef2fd4990dcf66e1fabc5a4419", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "2cfdbbaee3ca9b02a17c9b5bc33c24a51c617ee4b479d6e6e134cb55ddf6de61", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "18dbb3431bcf2520e874719b261df1992015bb9401ee11182c98c41c644f0a6f", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "96eb6b1122567a351f71e147c100215326e96cc69dfabdc8fb8e5a1ea6f29dc6", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "597cb01f30570369313a5f91caa293a1795e1a2c37365c53e598d02f4668895f", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "a06996e957452754255425e40950f9c24264c48e7190e24967558119fbbee9fb", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "9a34ad85e276a8e39eb903eef01c773859a2d24c3f1d40c6a0a5326e48661627", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "34a6a783714b3db2110ee6f82e50e491fd57cf8b749b4f62135e36822a235994", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "dc07039842185bd1d7df2a542f7b0c725aa84b8bb68140352e66f8196616e000", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "c7205c9e562c5f64618c72b2a67185102ce26e5b92673a98912fbbf01e24d7ff", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "8955a146c81092536327f7279fe563a4993f7a93a9fce8d81089308b1df6d2fc", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "2e5fbd6bb3a9455c396c3eb320bfc27dea37e92af22bfb685504a472633c46d4", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "68e1586f40ed696c1aaedb5b88c08d323817d821b45a168548f9489435469bdf", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "98fb533411e9724aa1c768401a41b0f8348f487b4ef10cafc4047c1ac2276b9f", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "c4b7cdcc30d5aadcbb00dc7c3411cd44a889c1a184cd564e16c5a9e7b52b704a", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "6ea17984771dc485d188d20b30a156da71213ce7704ce40f21d543bc4dd9361c", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "a3a9a00fabeca53739971de61a6679eb6c60bcde9f73177c2f99d10268ebb205", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "2b0c66005fb718233f7afdf613a7112e20acfa6685f4d63519060c8f069d65f3", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "cebe86b8ac01468a511426375a2f893a62fd9af8130d96e2e928731bb59aba08", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "48de82ae5ac651a60cc1da92069655ed02a881fab81589f752303401477b8227", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "1edf34121f3af4b8a9b727714d4d36455186060c842e3243a2e2303a98bd841e", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "427a66ebd0272bf2e1af88d944c5af97a692da5df4b52b1dfe63452e8a6beb25", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "546aaa83fd59820f9ac1ce981b241041a3f37b2b6c876a5d93e0df8b47c0cf47", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "8a9ba35737c0c3355b031251718b84b503e051018064cc84a70d2b6d85d6e399", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "efe443cb85ff40a5e4ac52aa9be8ab8bb0743923e42226ece8ea71cd269415a0", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "46d6d6693725afa7e11038996cd36717fbdd81d75fef0e356b433ff1dd9960f7", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "7b4be5e9c06710835dff8a3a2850f5db29d75e9d14f72dafbec3373548e18ed8", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "e2003717ba3d844fed56a61e6cc97ea476e7837eead3eaab215b43203d0d46d4", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "968e4e9dd83aa59cc990d411e0b2a1f072ce43613e5cf98f68bb6dfbd097343a", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "a98798463b214bcd6b2a379aaf93040460581424c554e0a553d8826aaca65e36", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "51d0c81d115f41aa99b574f8132e8f8ba23959f793016d7bb1bfe19895ea56a7", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "c50652cf51ea54dd4348decd501dcf57e57821ffffe8e754a5147726f44a20c4", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "d6fae90e6c868b7140e660601f4f21953112c324de8573c0ccf3039a8ceb11f1", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "ab09b27ba40b232916debac81f5e080ba07554bf568b967886e6c8d44caa56a5", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "4ac36e01ba32dae4c7c62632e1fd182bb5cabb7ab186960d07e2f53ea997a9c6", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "5484115e798a30ca830410f24682dd4e7b92eb9e4f7a22575c9b0f529ec16f9d", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "4c297035c476fae550321d6ac0a90c0992629f70487c55ce14c71c4c28128aee", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "1b4f5d509b73fd74d5cdedc2e498e0314260a2d76e4da0a998b509778264f286", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "164550b13f47fbcb205a0c150dc9402f2b1177baee14e2a731188a4c3a4ea8d6", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "2e76a7659d35e8920ff0afe7a06f9f0ba832c2eaf6faca64c3e2446f2f71f65c", + "source_hash": "d620a15d2310e84f2816d5cb851da3d19ab17bf669e7f724e769ca6057328b6a", "target_hash": "2d6960b3fb5ea59e6ed543b24779309291718f80c9b79ad5534549db226136c1", - "section_hashes": [ - "306b9cace67f", - "347341ba6233", - "e204d960719f", - "22c38d9263f4", - "8d86f35fecee", - "56f7905203aa", - "f5031749e5e2", - "fcc4e86034f8", - "9c2d17f8f88c", - "32743dfba167", - "b21010bacda0", - "f857debd468b", - "7f149429c43c", - "e2cc0079fbae", - "ecf685aef1a2", - "c7cf690e4c7f", - "dc3fca0a3678", - "e0441852a761", - "b03aba2cf4b4", - "aecbbfa6b82f", - "378015fd3c6f", - "029bcea52971", - "a135676d8baa", - "2ecada80a578", - "ac277a45ae62", - "c4223f1d10d6", - "5d5664b36ad8", - "093b661837be", - "60a22a43b565", - "6895e7dd5976", - "1a70e472dcca", - "061eb1b0bcf6", - "10d608ee5493", - "4ca9f30f5264", - "4b3b1a11d822", - "33f13333233e", - "b99f41594737", - "8d1ee9920950", - "1fe40a34a7c5", - "4218d28f1068", - "e778f95a895c", - "50cfcd5f8f42", - "34687d446919", - "20a384fc4d78", - "56d68f12f9bd", - "867dfa2ae429", - "585f36374b92", - "acc0afc65a03", - "09c726593fd5", - "488a77759b48" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -113891,1520 +39211,332 @@ "ar": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "bdeca16404f5d8db0bd3bba1d5dc5b0d3105e750fd665605c4bcc7cd7aa2b126", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "2f616707cdb8bef3a44aa66a5e2cac5fb6d6794f284c401d4bb79aa14927c24d", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "70951cfcd08827afa832e65144461580b51f13bc5688e44415f4e475e83ead18", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "b08157ff51cee89e238958e71f6219a6c8e89e542713f4f90296563c39f052fb", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "cc55a522aaed5567e3c56ae5949d52ed4fb5bd2d200edfac2583fab75f15d0ac", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "c1aadd152e073269b89ee3e127f371561511fb8b3f2bb99157073de046b3ae57", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "8bf86b7d75108716da2a86231cb5e77b8fcb1a94b6e33e1c73f682065bb107cb", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "0ab55d89e24502c74e2a9d773529778527c230f1a2bf5bac0a41d217f47b4961", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "12577c77d5662cd312829017f8f42e322a383941cdbe0a6e4928de5a8883e3fc", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "a6f491a473605cd5990173a118e625bedfaeed90a71dceee2818da536220ba28", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "4964fbe758de2f8d36f60cd8640e8b33c257219404b0db44917cc5475d41af9a", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "c2ab58db7a73b6e06029b641e813232d28d4f1345205ecc4488b1afe9588b32d", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "397618ad2805074d2b645d54b98b0fec6def575bf77f4ecf424e996e5a6a15a4", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "2a2f33a01fb21e4bb9d0ff1d5b7c05fa63c7f8a0012c88f83e727ce5535d7c2e", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "c8b05448cde27f7df6d1ac4c3f902aa46c5a4bfb230055119413282ce6ce217b", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "96b24afef5ab7f564cba3b2e5226ad608631774552c7af89917737678c50d4c8", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "1a8dba4854c0083902c71f6169a52bfc3f9e319d8c8727dbb78592169bc4fbdf", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "fed339083998e5ab9721cf6155c0cb7be1d4a2c7fd797b220feff1a42be8b3f9", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "3e355a070bb9bbc8769ef7ef583558f2aa3ae968c16afc362afde4e272cef7fb", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "921667bd9b8af988343965e57042be403e1617a9634789a37df9beac5669ae90", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "1d229e28143cd0c37c495dc6612fe2b7a926a1376bc3574b66afe1144a27fe29", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "b9298c582dd7f054f4594ed2f47683611970171f778e15721855248fabf64c79", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "d45d1e37767ae570be5b9f6c20e63336947485c3b38e30de039f648368a55bb8", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "c159c527c531f202c3a3f78d789d4c8ca0b25dfeaa088473c17bf5a298009f39", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "b2fd1894126e2efa6b4fabd318e1a81e882226acddc987d635fc227d629bd68e", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "b230b3988cdabacc61a228ca186d9572603b71109b79f98cb18aa1078c6f2b1a", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "325651c33aa93e7c3c717a6bcaab0c516149d6096c0be2ba14d0cca681ef1b9c", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "b0777ed1df42279905bfcc411b954a5aa2580270c911114a2ebf1c44a221b698", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "fb2d276c82265e3f38d70b28e8d0d258a842fca5bcff87f122a102c9e6ac11ed", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "641c88be0a194db07f459ed7ec01e94539725619f1725431dd06e16e8b40f76c", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "3a8198621c5611c6a6fde7d6a0cf2386c3a800975454d568b1b8ebde4b95c347", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "4122fad8185442353ad1c424c87a625c8a36fec44286528d154613d642d3efc1", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "5ebb77dd01b33ff034d6c68eccf73977a78ffa24b1b51674695315d2c07725e9", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "4170f5746cd56799f871de767494767100a8a44950cbdee85eac6003db41b2b4", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "3256c80df871529c87a38e39278deb65fa105adf7f40d79fcac1c3313fcab1e4", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "3606fa9ff8ae5e4f18e1985cf16236d901210e52758b04eb55a4e96f343d36aa", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "23cd11151bcccb5cc04f612fb70e68ded3c2f67f8ec32824cdc768e6e8603e76", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "fe56d11f1436d813cb558560662ffefd86ae166fbcb3113514159c58cb46e514", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "8075fbcb5261b54b60ba0581612ea345fca2d97bcce352a8af1c53b76ff5d781", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "9c42111b56c1ed9d3c593b551116c7bbfcd56839ff969354adba444fe571885e", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "4052557049a34769a93b7148cfcb0b1ee309aed4dacc518eb05c5b0b27551fcb", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "0f963d6c563043a77ecb7990f594b589f4f7320e4018bcd3fba45e634046eb2e", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "3d01f219dda46f39cf73092313f823f1c00336164156450e068aa295ed482335", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "8149c2b13990417e1ee2728ce01e5ced70de489685f77679fb4520214d652dd3", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "e8aa0156c2214afdbc26ee1797aca11a6d1f4a13b36c252d3a24a3d32b014746", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "ce7b180b131a386ba9dd7e16957b5fb59de76f99f6c1a96bc84f9d1d4fa56266", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "5f128c9de2625689686757ae2677b50368f94fc4564e8feb8e3a42d7cd179fc5", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "44c88cf4f5ae4d298f4e5b442d8d47d47a2b0286b2636430858abe7ab5a4c286", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "7260f19be73a8ed5635d4ebb2a6a286218b2e9d3ccaa31ea3cdc48ed46ee5b0e", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "beed573f9733074ec52267d2baddce54e0695d809acfed3d5a8d836ec14d195c", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "442c6d3773a4bf20ca1945e2f39f1811cbfc7cb55a4435e40924d47270e5d7be", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "e3daaa5a14749a1f2e0e9df7d920f30a037f2d5ce92e04e94fd0dad4e2776bda", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "17c03039bf88cdb31789ebc901707e8c9eb1186cdd75aa787d67de0bf478f8c2", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "5bf16251c843413ef06643e12e07384c9433cf2afb38d5007b253edb9840eb17", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "fe192ec82b8144ccd54602c079a8b1426123adf3ee6701dbedfad9f9746322c4", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "1fdda3ad8f50ea7cb551f3cf519b7ce63f8fc8d2a9730860e7e5d48541466b34", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "8a5c6f2902c959782fdf608f69712b427c8451092bb291dc24c076759a40e84a", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "ed1a12b3bf0c8e62db12987a2eae20516170a507e8aeceaa5a25d202f7909550", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "27ff46fdac240dbae713045e1c7eb28c1c3c357d444e84f7cfb185c412bd6a15", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "cc73e6bc08fb867cc75dbf0abe7dd680411bcdc75f24039228be2b1f86dec2b1", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "905c791f5c760eea28c78731754e9c8fc7becb2c88ec500863c4b1d8961bc58d", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "6d734ac80956f307155f541cbfdf0555b75d2c747029f647e86802ad693bd2ac", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "d35ae2eaad84d070890696ab2d505fc3ad608649ada06a15d248aa46b7e32d97", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "09a76cece0e0b8fbb1374f14e7c3cb714aa595132734b5409c9c8da60fc1ab0c", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "5a64e7e4c8e0786aa5ac9d6271f7cecec96b469d934dcb43b99113071e1f2d83", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "90bf37703c4aa521bfc2ca1db423144333454294ae6fe86e8fab69dea013480e", "target_hash": "0338062c0a7bc3b24ca33ed6f493042d88719182e4e58787883ef0616eeee6c9", - "section_hashes": [ - "336a7795b403", - "8c51d9b0bf1d", - "9cfd9497bf6d", - "7e8a691191fb", - "5a4cf772d930", - "0fa1b07d0a1d", - "4df8d0938e50", - "508c47b75d54", - "99dc922b2171", - "7dba7cb5defa", - "470d05ab081e", - "1f4ff9ea2892", - "653fa0585c99", - "b6328da39c1a", - "d83ab231873b", - "a40207921896" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -115414,3708 +39546,1002 @@ "ar": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "e7e608f046b8ec9414d496ba4075d5910e1cdbcf928856abf8223666286b99cc", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "85f8c7c7831958444fd5634a62e1d9cd005aaeb296043dac717e18f9c99969ee", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "a95a52d68f235e2dee99a072ba709861eeff77b853f679a9867b2d9f22681cde", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "281fb1435ba414d3134669de79917b6a67f0077192656b4174f148df446ba032", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "b39650e2e0c121950729bd129e21d5801dead13516e4a813c24bd8ce66f12e5b", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "58af09d0b62b1f0f4fe53cd00de09168162b4f79f7da673ebd9ad89d82c3e7f8", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "5eb2f4ae3a769a3c86b2aee7214380857b27333fc60306eec57941001936e07b", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "cb74ace588fb2b8ead3528eeee76879a5a3acfa45a00ef6aedea374aa083c7b8", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "efd806998f7036a25e640b008e2037c88661d4a24427860839c4e25d9c9b7eb5", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "7aae7138bc8c232d8a63e7a09756df593efa13c2ace159bde919f3cd83813151", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "f24e48811e29fa964686c36824750f424b5a794820eb581b21ec854ec9249075", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "dc7dc7c81621643c27f583e7b15e33ddedbcafc0013d8ac4720535da262a2009", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "b2848e2204faade50063070798195c69bf3f0f0b487bca186006b11a58a3dbb3", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "0c088bacdcf45c85d8881004e986b2d229f8063aa8600ea76acb738db7ae7d44", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "4cf76497ebe60ada58254e7bf2b4fecca5d1e7c166d76321fe2697e74f48072b", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "c285ef5a96036f629e69db6a9a4fc58f42cdf93fb129fb8e44fd6355f8220b70", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "b6c279a770363cb811e51386dbba46b4dcef859b8ea8c4801211893add98dc7f", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "039696844231584058a38e7c89a43c313a04c2fb9afbb9a6fbc53da5e3ff46f7", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "01bc7d4123b1e4a2353537f58a207f622cd682668c091015215073d5f58d60d1", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "6df0172b33462c45f111837dc7b438d534e622e9672369b83e2f2d9757f6fd44", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "396f11cabb1afebd93f7bf547b2bf3488267463f35999c8fd5cee0167817e601", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "0f54a49fe9bd9fd1d740d8055804efeb4c3eddccc839cc65c3d06dfacd417f98", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "1f78b7d4685871999917ec9a5a09758be4614c7df860b53a3577d94d9f3f9b33", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "3e11c91922857ac896fa7ba6043fc39b2eb5438fb8ec3b81d4f3c37297ed785e", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "52d850c5980e0e6f8737b57543fe99a1bd34ae88b56b21627aeee0fd04d86cea", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "2dd55de2c9b456382be5817bcea38a4b49e9ada9fcd3c20ae3dd5fe5553de464", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "00043e94f2841ef20a425e65d5af0e97e93663a5a5d46b78127bf455f1650bbe", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "f22134eed791490b1dee9aef2aa1cd4c23a999f628c4ffc0f7a91673f7063c46", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "ec7e53dc84b9b3e1b08f76de83c7cb1aaa0e98d507b86ea3fe1ef277cb4239b9", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "8046bc045010299a04d97e0e2df8c0f89a576c7c4eee763cbbb999b2911d5d15", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "596543ae0cf6e2041c38c6490e0bdb2439806a90598ff798888e805c48aedb9b", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "21bfe2780dca08b78c75d978fc77f1b092cffec6a1b3355bc690ca61163b4ee9", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "7cce302e5c947198bc0e8aabaab206656c0865a48e4d1c015cbe3abf1a8afed6", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "3712072110e83dc7131a1b62f73048d30f9387494df52c4153cb7fcf8c0ff08f", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "df29953d5165a9a75f6f326747fb179adfb0bf5cda0f7089d6a4429c117d2413", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "7390175f5af4695d760fb1e7a70e471aad2c32c8b935d3d8e12ee3c3e12b8637", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "56d7413592e0e726353d3a021171b7c73db0f79cddb8a974f8458645469180e6", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "d553ec79cae313562bf909e39d4c6632b0c5b8f48e03efea0d215eef77b3d140", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "49a446d3910e05fa0b2b1e64c3c3b97c3f83bd5e1dc57ceca147c49e6f86bebe", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "01716a2fcdc49edddd608f7465f49dfe394a864798e4b3d8b97647b97765c3b2", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "456d99cb2cda03dceb7983ca852e538fc817c1ce0260cccf6e318bc15326d29d", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "ae3688aa4c25fc6b77135237006822b672abd06979f0cc6cdcde8c49a71d55be", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "63101687225c5f31486a5e290260263b05b7dce646ca48a46479cb5ece79b204", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "d000222fa6b1665e661d36d854e414efb005304f4786c0a0b7b102a2a26cb7be", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "1d324d5e5f983cf2b79fefda101402c1d2273feb4636e3250300237999c60247", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "393c77b4cb7ab7bc2faf199f027fed2aea76a95d759defde92ae3161a19d6588", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "be3d523553687744c8035785cbf5d5f45a3d87c2fe890328b1aa338982bd0d13", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "0783ecd9d28070866cac3635747654de0ddd064ee8f17b369d9b3d9aa6903099", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "6d149b8a5eddf941f20ce0ce03382723bdf427415c378541124fa361377e542f", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "272e2cd5d2999c74d4ad0572d0ca77092c3d022e3bbc581da16afb046f4aa480", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "decdc96a444881bd49c0f8f4f57db54a13c5a1de149109f1ee1ee01a996007f5", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "a2f7894de4bcaf20a0119939ab8d26797b638a6d0a0772e686bf704af5807aba", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "e18f8227b55c4dcb8a36469decafc325f8d88677f1c1cadd12d0a3dc55bc81f1", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "f48a7041876a57d2665ae9466203afdcabc9668de51b8f595e262c889435d457", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "7afe94323085471fe5c755533aa38fc6a71862648cd9bd5ded3285070d848c36", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "ebb2cd01f47de23d7cd07ecf831696b68e2dff4c50feebbf452846c4748ba3eb", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "3191511db354cf645506f435b28bcb47b6f268edc59175ff6a94aa7ff0886870", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "87f8cdc61a319e7773a6d932fc1148bb6afb40d18550a3e879a856cb087586c3", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "cdcdbf09f8a67e4aa16d580e7abd24f2491c4b6dbd8a0fc3d014ac0563358e67", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "62fcd036da35488d045513b3792877ed28f126bf0684aa739728b4cafe1d607c", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "a7cc128a50c499da266178490c76d449b5624f2a4ea51f69eda3c975da44ddb7", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "e7e36de9f45e77472591b1399bc3a6d062307b01f59a76c832783f4c23e8e36d", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "14e19d46250daec00a096bdfe99bd4240faf60ad018710869e3e3dd3131f825f", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "f3c8fb7b6d9d090927855d373051280de32994a25ad9f416c76159c8a1b05dc5", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "6c34b20e17357578c966775d08eabed1e781af6f28a0b8597de3f377543217ad", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "4c0e0e95f7bd40fdbf9732091454ce4199d76e50244e9992b349eeab11d6a7d9", "target_hash": "15b58b248dea11b0c3ffb6da3de9e0a095b8ab26cb1c6f3181040b3dd2c62e10", - "section_hashes": ["0b289c723752", "31dd7985b49f", "a04f16655688", "60c73d142489"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/reference/ENVIRONMENT.md": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", "locales": { "ar": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "3ed8f7a72fdeb680c34afdf920c223c20823c77b99f5cb56570d477901ec59fa", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "980a5369deb281c3acf9c5ffb2001214589543819b44607638825b22852b61e5", + "updated_at": "2026-10-06T22:45:36.370Z" }, "az": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "76fe6f6f6b75faefe0e511800a1ef8842708615faf4d003ccce7c5fd94bd9412", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "41708f2f1c0f62f82374dfe75c3170a552e4070bcbdb513d4c699cd46225165e", + "updated_at": "2026-10-06T22:45:36.370Z" }, "bg": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "10a66b9f5cdcc1ef82f6b92e2150703a8604888bea795d92c8c3947062ead4fa", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "af6421ed5eb1365612e4b2d62e5763c9478bb28a86aa92df78fa38faf547a3c1", + "updated_at": "2026-10-06T22:45:36.370Z" }, "bn": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "55555989ae0a42089ead6db5fa2f605b6536947fb924efef31966af76286d3f8", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "c44ee7ab0ee7a695f13911cab45784862598cc29e8f5834bec0a802548086acd", + "updated_at": "2026-10-06T22:45:36.370Z" }, "cs": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "8bfc26dd4cb226945f103957124337b06e0e70279e7d1cc395453b2c082fe17b", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "506253e4a7c4bfd68bafbc7868ee5b69d0e63ba4a7963d8b101321c00b65224d", + "updated_at": "2026-10-06T22:45:36.370Z" }, "da": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "8fa4f13d0ccde97bf891b2470243b5e840d3a4b08a658e1eb5909502e5b5c180", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "ffebe72beea6bff57db6af13e51ac00f85f4472ac8a65b0f16631a2fa0036433", + "updated_at": "2026-10-06T22:45:36.370Z" }, "de": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "1aa819f885dff90b0314aa2bb1e8f9c00d45301d43fab935e2978ce26401c32d", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "ad77350ca9efc673a2d5d9144c51e9856fd5cdf96c0ad90ae2a7be4c1c50f7e9", + "updated_at": "2026-10-06T22:45:36.370Z" }, "es": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "bd86f32cdc968f373448c751661b07352d7db606b3d36d73b25ecaa8c961a286", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "f74b88cc1b2768cd1923c06a65c175ea2d3db8df9d083f1514c5d908f7732151", + "updated_at": "2026-10-06T22:45:36.370Z" }, "fa": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "e496b154865f387ec63c2b7544e5f8a7f67f2a201688e608426a5fd9288e1254", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "8bfbe5ac36b868cd7f8d2853c6050a001ac37e70d78a8bc21bf308b81cec03b2", + "updated_at": "2026-10-06T22:45:36.370Z" }, "fi": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "1ecaf7257d97695260f165caf7bb495a3589258609a321e5325642754d8a81d5", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "f952571b8e1c76487d12d494260cc49310544f047602aed496de6864a754706f", + "updated_at": "2026-10-06T22:45:36.370Z" }, "fr": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "c71469fdfbdb8aca4f42348510c51e28daaec1c7a381167a578de3659f589129", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "09d55a40320bd5c7051c5264c55897da3a4def86d6b0150e557130b36eae3abb", + "updated_at": "2026-10-06T22:45:36.370Z" }, "gu": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "334cac396415107f4047226c95ec7009a36c48e7ab675904aa04810185790b2b", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "61c6d89e827a21986c59454d816b093f21f662c426fab57fd0b0945c65199300", + "updated_at": "2026-10-06T22:45:36.370Z" }, "he": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "714c7eb84f60e8e3cb907045628ae1fb07381c6302a951cdccfdcbbe62855fc4", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "8d7550162255f09d281175cc2e2394cf607c7e5aa02f578eca07bee741cf76a1", + "updated_at": "2026-10-06T22:45:36.370Z" }, "hi": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "78e2005b32fed522260697d32c11912a7cc3d49385a644847d7060c8e4b818d1", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "c6126926a0aafbf8e6a9fddd0117791bd9f295a8a44502c484ed3695871c5415", + "updated_at": "2026-10-06T22:45:36.370Z" }, "hu": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "9a9f910e2461c554173cd361e15bdf57f9921b10289525e0f6a029428a536d0c", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "6be209d33b2d8b21d50ac653f9160684e4a865fd954cde9934e150b161ee6783", + "updated_at": "2026-10-06T22:45:36.370Z" }, "id": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "a7ab866142b369893be9c9c1a60f4f468c50f1c44f519ea1c24d92ba53fbd303", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "1a898179a73e3d3b175d088c08399be747486cebc24b86cfa6d14c1db840c7de", + "updated_at": "2026-10-06T22:45:36.370Z" }, "it": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "f5268f37d6506e877206deb1373d0947bfaeaa95b8a332e75daf3e7e009a06b4", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "60ec795f22d6102d9aaf2f63c583d2c8ae51e839d98ab09fe57b87ac5a09e955", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ja": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "da252c19412c9b34091fa3d8c20aa636c3e43275d626bdc40b24befd61cb2820", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "1f78be725da05cead44e8bbfc75659f3330c9f5f75a08a2c01ae0e9456fae884", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ko": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "3bae873977a46f5fc0bbd68da95fab54ac54499d31299732b41ca184bcb9e4e9", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "747a2121493070aeca49cd4a89ffca4897c5407a4b6c972adc49410a832cc650", + "updated_at": "2026-10-06T22:45:36.370Z" }, "mr": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "e717fd8dfb02ff5eb06845b8274490f9340b4c32b7c123150fc62bb6bb6193e8", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "3812e6d096e357d8d8c07948f77991a3ae5844d227240d08ced3db56e5354cfa", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ms": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "7815a825b18628101ae87431e041227d0d0b3bda8b8858f5224192850bcdd6ea", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "09a614043d364efeea5ea22f1c9bf5a3671956337980fb15ef81a1c9dc732d14", + "updated_at": "2026-10-06T22:45:36.370Z" }, "nl": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "505d7dc4f989dd4ae47f7f1b86d8b4b4a995bec1aba05948338a584a24e4bb1f", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "4049b1c88f764590c50a1fe3c831ae6938b312e7b121d6618eb273c2ab72e5c1", + "updated_at": "2026-10-06T22:45:36.370Z" }, "no": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "703a9987d38f71e3909d6c3bc99ed440dc613cee0f3ced85ef48eb2d116f872e", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "c7b737c6f4eb281afbc2895c9d3365f76655b9444dccffc4b7adf07c76c94964", + "updated_at": "2026-10-06T22:45:36.370Z" }, "phi": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "ff4de3cf655fc2a86c76f0328228d8000e80f3f8163813fbd5c9a7f66a58d590", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "8975c0c46c77746ca4640bcea7aa9535dd53c3aa850da28c8946fae4b9d9cef1", + "updated_at": "2026-10-06T22:45:36.370Z" }, "pt": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "ef9ab5511370cc6fc17de51343a1b9149a59f974d84bc74243a7fbc91491cbab", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "36e2e1cba5baddcc33b83b159193ee2d5b91145f78aeadb0f2e9ef71c8937f33", + "updated_at": "2026-10-06T22:45:36.370Z" }, "pt-BR": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "fa80a2d14c4b978488e8cfbbf59230b06ce3ddd0b28530ee339ee6f75778e1cb", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "66b7141295ebe922f2d84cfa2791661e102a58728146bd8e1fbbb66ea18e73a6", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ro": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "aafc693d029aad4112c7123cbe5b7847e47cf95c527429e5066d2cc7f8036004", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "b1bedbdfa47c2d2fff061d1d7bfb4e39a905261f0494f3f0c8ce685d87726bec", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ru": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "d492a2c8f7cd7e6b512aacd293a2248d8024f80b341a560da18d01054b5b89a6", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "2ded21064d3abbb2abc5170443e51b120d88c33d683ddb6fa820a495084efe58", + "updated_at": "2026-10-06T22:45:36.370Z" }, "sk": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "7a60054cbfc31df785fcee5095fbb13a3de7ac53f9e2002dd2bd803962663947", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "17ed428468d6426475baebd0b3262851d21f85b566d333bda949a07d284e0741", + "updated_at": "2026-10-06T22:45:36.370Z" }, "sv": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "13309cc4c7ba14fa8ce252f4993628f9bfdb1d5d77c90ceff954cd7ce8cc8c49", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "1e6bd45532c3167b25580f5d359be4b3fe77136437155daef2739d13c9fbff75", + "updated_at": "2026-10-06T22:45:36.370Z" }, "sw": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "c34d743b43e8a766c6fb5212d92bbf0090710a0a430d06813ebe7f0104b5d97a", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "a1a382f26cab6c7c61fe8332f94f9a4fd4dfd88226149afd66f995268d41bd08", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ta": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "f94a8272d5039e3a1474a64935fccc081784b566bf5e4b98066261d49f508668", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "de3f80abc1afe1601846317d1cff0c5f7a82cdc1924e7248b50f52f4c85496b3", + "updated_at": "2026-10-06T22:45:36.370Z" }, "te": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "49f8f3989d00f1bbf6f57599b015eeabae44c0dfc2c7bdfdd0a2e8e3e9513414", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "f67d9926b00ff72e2d7defeb7321df7d43aa55014c3a22faf312543181e4d469", + "updated_at": "2026-10-06T22:45:36.370Z" }, "th": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "62190db41188b2583f52f6d0c88f45ccd3fb313c48d8007790a03bee83126381", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "a2a55717520925e1ab65f9806331a0b5bfef0535cba0cfb021b1010373cfc128", + "updated_at": "2026-10-06T22:45:36.370Z" }, "tr": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "c3d662a82ea0c4b6edc28c287896ef4af0eff1456a6dc6ca97f53866dcdc7b44", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "af38edabdf42d86b8c562aa922231eb771741f6b1a0aa4c692f455587f911d9d", + "updated_at": "2026-10-06T22:45:36.370Z" }, "uk-UA": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "af6954da275342eda4271741de49bbeea398704314c1fb014d2c6dd24a71a2bf", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "150681d57d30faf6ce24d5d6e420f7b788a86c4ce3b01868fd869c38fce074d0", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ur": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "0d80e5ca707941bebc8bf794c00e6d96daadd636f2bb14ce0bb1359c780a1fd5", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "2a107c920d7314de9efb02be70d82af3d38075d344ed2034ad6a21aff2f82d1b", + "updated_at": "2026-10-06T22:45:36.370Z" }, "vi": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "48bff5a7ab40db35fa6f24477940ff7564c8248d5b5c93fc4e9330073b7f22c7", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "838151fc8440f8021f490aa1fd815f6936b987c2550b3a887bc2c17c6913649d", + "updated_at": "2026-10-06T22:45:36.370Z" }, "zh-CN": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "538c6c6e6dc45395c35b3df81b19b311b49b1ac6a55cd03f279ec7bea0569503", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "f7eb5ab9326da2f87046abf32ebf9d599654a803fa6a52e166e61aebe6bdd41e", + "updated_at": "2026-10-06T22:45:36.370Z" }, "zh-TW": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "855855385ddfb652990509556dd9186a0db1f5907bccd89de3d33dda1fe1fe63", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "d33aba1fd6798b8aa54d755f355c1d42223073ccb94051ee13d6a3a5d014292c", + "updated_at": "2026-10-06T22:45:36.370Z" }, "et": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "48760ba81caf32f1cb0b78ebd98e6007e21ec05cbf8c75712c4c0cd5527788b6", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "f1dc05f93a8a4f9882e6db8a3a9f588165948a95222b3498fae428a840ff77ed", + "updated_at": "2026-10-06T22:45:36.370Z" }, "el": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "c682667e1a1bb3daa4805d93ed81bac4071d73c6cde9f8c81eb1adafeaa0e9e5", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "f7b546645ae96b5710e362bb1ec3f128ec7faa21b9fe0bf82a71fe96aa880c82", + "updated_at": "2026-10-06T22:45:36.370Z" }, "am": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "0c5dfed3bcdb13abf96c217b660cc88c408974ef1352eb3ac69058752e8b59b6", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "263675b5a1a969aa14469fb94e106976387ee8a10c9f7ee83ede5698a6bf44cd", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ga": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "a6e254b9d12b7fa84fbf42b126b85d214f3bb20f59206d9614e51190c5f3a8cb", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "91c5653871e30cd78e97c4684fcef658d071df83eae880a45b5f7ce6bdcc47c2", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ha": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "6bf162946e3f18e3fee1512d99a9c524dd5c3670b51bb39235ed74a5551bebe7", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "af01ba424f64b0e438572502f31c5d5fa8a2a429d10a13be1256ede0453bbd9f", + "updated_at": "2026-10-06T22:45:36.370Z" }, "hr": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "efc247d2afb4cbc6ac5bfc2d48cc426991722904aecd2ca96f5ed5c628b0cf20", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "cdfb62078be29bc4f9e923d220d4870f3ea9d09b6805e9e52db1e64f9d8a2a87", + "updated_at": "2026-10-06T22:45:36.370Z" }, "hy": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "845ea724fa9a74097e140a1fd89068bbe8615dd8c33fdfaac38c620cfe00a4d2", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "282d862851057c7709b2423931d8d84dbeb3ab9f14e557caa8ab97ba8cef7d0d", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ig": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "0d955cf99f1b7b85528e0c65495f8eca7299ab612653762e2045e3ea83a0a4bd", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "874a0c9587092548292f231e08f54abfc3b74745651d94027e84e071c3a41a17", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ka": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "029a0404dbc9ad7bdf8564212abe6a1cd45ed8d253f699a799cebdc69f310111", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "cc62cdc39a395b9fe43f72d9af2405002f559bc003cf66bf50403db54c6ede76", + "updated_at": "2026-10-06T22:45:36.370Z" }, "kn": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "87ca98019d7b2ff245d5ee565a344a57727ec1dce6fc0f8e84e3a0fee0bee8b0", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "769805c8a8d2a0895e6ea461cab9c981db08359d7b6be2494882e5dec72a117a", + "updated_at": "2026-10-06T22:45:36.370Z" }, "km": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "bcfc91e03a715c69bebdf500e3db8b9b40fb71d3088b607d679db69385e6e842", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "9213ae2c7a206f19b59d713f06cd9ee8516446dc03fae2ca677cc5798ccd424b", + "updated_at": "2026-10-06T22:45:36.370Z" }, "lt": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "e4b098587abeaf4c906470c0e5ffc3c0c8d79cc18c8fa7d7c01ccb4e0b15b3bf", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "16a5332778cb1f458f032eed5540c5e4862b931cf2fb84031966117185b44db2", + "updated_at": "2026-10-06T22:45:36.370Z" }, "lv": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "5f3e67790aa690c042ba93cebdeab16d53c56ea25db485bb12dbbb6b9263e72e", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "f1bac9092ce43dc1de33bb8ebbc0069ad37891bc846d52113605a7f370d3272c", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ml": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "002ec810797d520f1307983225e24133e9ee3eb05951d630def6f4b35d81c648", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "7dffca03dfcd7b804ab2592db420a456aa653910e2c2f3726f1a28d09097e252", + "updated_at": "2026-10-06T22:45:36.370Z" }, "mt": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "58bf610db24724d740af6311777fe0e818fe6988f2bb5e0501c0dcd412fb02fb", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "90e867ef7e6a81f4f52e5dea5d2b18e5fa0f1be4d69cf6f00708937fd6302437", + "updated_at": "2026-10-06T22:45:36.370Z" }, "ne": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "77954c29e078c6085599ee4320169cbe64f7777398221d793dd97ba2acbe9c9a", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "4a35f41d2314db4a21957b311cec702938d8466bc02759edfb40c3aca33c1089", + "updated_at": "2026-10-06T22:45:36.370Z" }, "my": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "bf1835b184d67deff3bf5d225f959c4702132acad79d1b853d0c36092232f52f", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "b4eefccacf373a620dc969bbad35186ecefa8508f9d1ef04a535f6031a773f74", + "updated_at": "2026-10-06T22:45:36.370Z" }, "pl": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "e3bfebd28c3206b3259ed2b2891a2e81b4049958f22be36ab38a82446abab8d4", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "5519a625566f923efc290e6a8f5041d3c5d83fc4fd2eb0b4a2c0fbebc9af6705", + "updated_at": "2026-10-06T22:45:36.370Z" }, "pa": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "7e3ad35b835e5d84136a35dfa4a36d0ef6c32542d9d81e7e345d8a9c52004375", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "dc3d402ce5fd69cea53c9e80e1283cf6c574b373dbcd00cb3a45e0e724987ff8", + "updated_at": "2026-10-06T22:45:36.370Z" }, "or": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "2e85c91f5dab021d28fcc8c1b29b6b6aa63fefdb8b939b0082ce80dd7bdc15bf", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "55986919dc92948b4181c55e40cee597e6f20224ae90f5e74a6de4b6321c2c36", + "updated_at": "2026-10-06T22:45:36.370Z" }, "si": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "5d54d06a87a3ee89901182be0386dcf2327f6ab6810dce27f506e7ca6ad1162c", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "c3e9259c73a80c553e2b96b18d4d71b772d998c090dcf9deefb2ff282cdd06f2", + "updated_at": "2026-10-06T22:45:36.370Z" }, "sl": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "76bf265f537d0466b23387f4e128c12083f2fdfdbd3f223216a211ffae3439bd", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "77b9a508bc35bd4fb45fd19cbcfe445fe3302826b142b9537c5d99c8d506ee5a", + "updated_at": "2026-10-06T22:45:36.370Z" }, "sr": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "9bb8bca9d321e20a1f654691f3df124d5f5ebd1c4d304e84c43a3d47934dbfd6", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "ec064150da9b035e2993c01492ba999e1a2eae4a9508901e97ca4b5006f33699", + "updated_at": "2026-10-06T22:45:36.370Z" }, "uz": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "cf3cc3a2e531fae6c2edfd2ebaba0bc482c5acbc9123e959aa4ea73aac62cfd9", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "e7ad898a0ac3130e844ec7c205a8643b42d7d6cef8b79c169dc06577ed7b4cf8", + "updated_at": "2026-10-06T22:45:36.370Z" }, "yo": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "cb43bfca9be8f621f25b40dfbb4f76d65cd9e75a5eea39b7141610b56551d9eb", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "20d302f7b5c02bf173bb1070291797800f7d68397bfd82d344684299ef246bfa", + "updated_at": "2026-10-06T22:45:36.370Z" }, "bs": { - "source_hash": "ac8ebeae73bb4e231d2f07fa7743f1f1da6fd0b089d4f2817521d4f22b064425", - "target_hash": "487c50c4f7ee8b98705bbdc1df88a63c5446d4c949712e175369b8f12da03e83", - "section_hashes": [ - "c7914887e077", - "93c63cd7c00b", - "96f6ccdaa496", - "5143103fb14a", - "bbfcc456f639", - "63edc67061bf", - "4b3527de00e4", - "02ab6ff5cc52", - "121c03b11416", - "adec70037bf2", - "eaace122d544", - "c5d615231a7e", - "abe8d7fd1503", - "daf0ea89c8a0", - "272d62ffedf5", - "5a81b930d1fe", - "7321637534bd", - "8bad48ea1d8c", - "37a3557cfb09", - "243eadf2310d", - "92b3a9f53c85", - "e4eef6ee1e99", - "e2dcc8c11f36", - "d14f258d42ff", - "1e6cc45bb598", - "ed1a9b885aed", - "e19f626607b0", - "106f26ed9f35", - "54fbe037f418", - "e0c9b8623eb6", - "67417e743e79", - "7d12e45e096d", - "c6f876449496", - "fe84af0fa454", - "74babf3f302f", - "3dba13203257", - "f48ac62f9f6a", - "e7327d596060" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "source_hash": "9d5d672488a654e4b9885b98d783d6308b306afcf93963c126c6fe7e0ae4fb52", + "target_hash": "e025033d85c3ddb83e1648c978c7464a46ebb817bd4be4337418d31658165639", + "updated_at": "2026-10-06T22:45:36.370Z" } } }, "docs/reference/FEATURE_FLAGS.md": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "locales": { "ar": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "20c9e9a62cd5bf7e34a657f78840c8cb6655977790eafc44dab4e0e5921c676c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "6cd20dcc2779c10a64bc199b21569338a4076ea265cf6791e8c755ba7ab0789b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "1f2dc93c926151c59e9c5c01f0b6d5a41c7fa13978918a32d6a23f481032f62a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "819d719ba7a1a148952134321e1f8d28ecf22e4e2d86c1f0a65d9c9145600fce", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "73a46c697bb8c1f94ef73cf51cbda79d7a7c49c6a6df726d2d35702528237b8d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "884849422983e6b4b79b8ac8af7a2a01a1ca5d2533be947b3738fdbc72cc3037", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "1af89f35730d203ece8be47d88ea5ac28517079599d41f25122d7de4db09de78", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "3279ea2a7ee528bf66c3fa1e8083989435bf5141849fbb80f44dd65b317e7856", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "2adb5d5d444788187557ea64d273acd8cc7e26a796731591a899d063aea3f2d2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "2b91cfcd19440b62ed44e8c6e4f332068f4e081bddbf93a84b85a9f4b64995f7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "b85b2e33617d03196dbc254aa5377a0a70111f9537186b2dd17cf26a01f7ab9b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "739db605aac968aac54a724d01436ca423c832d84bf0f1a66cb4417f477ac9df", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "fd8d437222a05ac465b199a06aa9b1271334d0e7d075caa6ad2e483e24720cc0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "51603c459bb51c0d55e9a36f40df099f4258f51fcbc13301111454d7f22a37fd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "77e0c5f0fee9df6e68d3546c9f61647c6c674fb7085bfc4550e775bde1055d85", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "229160b2e0deeb33f08a0472cb985d45e27fcaf8b23e2222583bf262c82f6b68", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "86ca6ab6ca855e5d6e243e6f2d5ae896d51173b014d85257b0f2d70d0ce58e1a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "ff92a0d51956178ab90ea5c53070f09ec8ee1898c0c180a4687f68d0f1d945c5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "e2a0a1d4a5e2e5585c5cafdae3b5c20c6f0ba1d13b8d62fe5fbac025c7ae53bf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "b66947c8763d178c0d863a4881a23e32ab4fa4f98b5989d2571192073baec856", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "97de3e88a55ac9de236b2bfe8f5809f27be1d4d038a568ae9edd47b50b48ee53", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "b334f6830fe58a4cc570872651b436ce292fb7b4ff89f1e768232d4b784e263d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "524bc951e24c225e403224d136751960886c3462e2c83be5ed18722cab18f279", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "19a1cae906929df016ddc89d48f25d1a79f61d376c6dcd2a6fdf748eaa3044e4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "4a5ebbe1732c5d12ae709d65b2ecdfbf2e20d7c4f7092356fbd84780f023f4be", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "926a52ca5b90eec3c48e4b4d4652a74fc629f64c88c0ac8a565e5203c5a57e41", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "768ce7e0d79c36c12fd9d96d80aa2ab7aba3087998dd8b089eadb1fd09410898", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "7c9c8b3cba3f5e1badf6e443075acca9479f23d27ef91cb4f388fa885032b116", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "10319a79aec8ea19594f1e3dad8e87c59bfc5a5333bd02b1ed98ddd6e3717a1b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "3990c6a07b88855ed778edd21978c2791f57ce2c9bc4f2306afdceb57b788d72", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "cfc4394e3be19a51c9cc5468a3f2fb93ddf4c8f8cbf06ba48fdbe0611b1ecf91", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "4544efe3291de10f01d94183c5019839daa94f5dd669a64f04756e39458afda3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "0148aa8f44a39365874bd541f0788ed6dfd63e8fd7fa0f983febc514f246ce92", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "318f3b877d2566a5173996a68e3b12fec3b47e3f29ed69c4e6cfd7ff054f4307", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "4f5eb9427ec7d7fc06c987f4570495fe8c60aa9a9cde3070e579744adbf03e64", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "0dbd3749eeec6caf4f0c5dbbcd40a04060bca5357b8e3b9e8a97b2b6b4e9aeec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "f32d0872f3b99ea7c205af95d71ca8da87c95a0f5fd3cec23e34c7d26d26c4dc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "5b3463fc4a88a3d8739210fe95578a126528f308cb2ec08c1af2af020835c7d5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "7a19e7b22bfeeaa0dd37c5fc6953af052fca6aa5956f6cb73866f8c4de225170", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "2de2be6dd7323df564b615ce95e1e8ed0d326334381f8df3b6690fe20254cc05", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "f6d0b3b2ec6c399e3f830143ff429e1ceb0e85bcadf03a41df21ab236fe57300", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "d0e47092d54c9574c07b2fd8fb781898f6c5d4f42df125d9d3d7001efc22fb83", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "40dc5d49de8eda8ae8e09afbc8d098e0c99a73d50572da0edfb3d85628a96366", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "0a10f6b1f5ccb78f4583b04c7e2e9f5f69213b8a695afdcb3133d5bd19532c3b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "d007053d6e4b6fb2e780452102875097c727e412556d31a39d61a665902f3f21", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "939745bc44ccb47574b488e3a2f60d9dc1ad879c44e988943c950ed6226f7bcf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "1d7eeea2af8b20d438d00622cc0315ae88f317388937b1e6266ed368254cadab", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "f47f6554985eb6e93da2464dde2618e7454600d3f461330c12a8a937d79e2390", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "7d308aa79d2589e2a6c54499ddf7dee6df7b38d260397e1640ff86048ecd337e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "8cae5c9b925507804cd8ea3c054c68880f5c272a0aeb5cc7d0dd82c76bf46bd7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "6a29aa7eadfa65d1f6d096dbc857f8ff04e84c7076767d31f660715103b12cbc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "321def21457ffe1971552d3dbde7b3b57de5586ba8c7be4eed5e46272285d67e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "a8c1982aecfdb26a02ca2ebdb3130f69cdcc1e33bb8d2f841974c5cd17efa515", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "4b71ce5023635d9247ea42d2b323d3a703665e0d8d5abcb7ba42117027208766", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "1834b3fdebe906343f347a680ff11e2ea38214f3941ec2dc70e8489d9d7da610", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "db06f3cbacbc40e71340521cb3431c42406010d927001dd0585c00143fb75f86", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "53035e4a7fa13c77d8dca95bd4139241030166f91cf1a0c81d332e43bbc7a693", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "6dfd03572ca3e1af16dc4709807199c52ac1ace1f95b7351986925499938f3d8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "bb74908175526959c32f4c5ce945cb37908f4c5def12cce35b5621fcdfed2eba", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "4419cadecf68380eeeb7b6ce3be16536f6dbada916ee660024c1cfb063eeef76", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "5b6fd3b1d836fcad98c1b7617c1507270e65cbf78edd75569bffdc3564a790b8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "aca8a9de40f1f89d7209ae02ab5c6fe78107d16a69f53b06153e669d59785199", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "58bd8d7f13b4eff991844f4d36555889e0863a706e04891de28a2ab5d8e788b4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "ff710bc701c2ace7a4a16b748ad722aa99131214caee6c4cc88a5b055c8e4ac1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "1eb747d4aa76012bd712fe57261fa2d103cee5ae5f1369368cce84aa31aa3352", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "4ef1133a2a4d4e7e8288b3a56bbe1310225f56ed5eb39396f31052963e21fe09", + "source_hash": "d3a234b1fe16d29b706b52bd283c0f5c35b87841520a67fb162512dd90a62ec0", "target_hash": "5f5171209d50a8bb33df7172c728160e6455fa441743f327e7d2b0393b3ef4d8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -119125,794 +40551,332 @@ "ar": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "adf5a4e0e3ad3defd86d8ba549217979bf889c91fcc2ab1de256097732da7949", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "9040b42a306a205bb57209ab8aa49f4e0f394a26a542cd611f80b0f4ed359b39", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "5106eb65d447e2ccc7a7fe64c13c3b8b83d5b378cd3a2b9ad4ed157f9edc753c", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "98783729c66b4c053a3ae83707cfaa03acb77f4bc1502d2e6313a43ac2ea75e2", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "cc6c8a1038ab967120e3601c683a214ec6754e94a1fee29ea41278edf90341ad", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "b100d96500a386fcfee70db22c9f4b89e8406c6745062b2fbde93379265d17c3", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "01e81489d51ad0282b3050ea0731a0711f2a1c055887b261c067a6aaac58a774", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "4547b0d9e01d49faefd7abf95db0cef13910d2fdb9ce0554c59ed081673295a5", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "a8d9b8ddd613c0089b27151b95865f57ec0719179ced43cb7f05b53b0906dfaf", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "d1c34f5d81936c9e5cfdaada78b96a66dd141bdc74058b1d646667cb54cce8d0", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "be2c88873b30464f616d9321d2dbe054e38491207b8714472281de22e7a60106", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "0d3b3df58eb31e347260883d6da8342b1646714aae8fe1fcd8f91c31ea5d6647", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "795a354ada689541ed2f857f8ed8075c4e39196890a801fa5df483a7b0cbe126", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "bcb7f8d379bf39ba37e86b0b36e658ebbd78ee56be9b835f54ee0ea178648bd4", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "d65acb82c9cd49bf61c85ddfbf238ed7101ef32271e3b256b8b63fdeececcde5", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "517c80ba234b640a8f77e0610ab891650eaaf5c20e1d73596480b51555ebda3e", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "7eebe014047dd95d0bc88a1ac95838c9967b6816dad764dbcbdf23f7b6981185", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "708765a9bff39192963ff45735bcfa996864d6819b59e24c7b108a422a372908", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "16ab6bffe41dc80981919eea6f0432d13b62121f225e7bdad13e607dd361e9c0", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "f5b9eaa0e83097cc4df6ed4af3cd75b0406877dc867c9f2461f709f5a5135d09", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "09fb59b7a4f57279f35b5569df753a9ce51799f0ec468f2f88d8b1da82ffa18f", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "8e0c25586a1275837a01f9cb2360fa6742b0f9f903b633ca508e9a3e7e3d405d", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "589a63876e1405923f292ee1f385677a627dec2d9b9a648ff4691d1bcfea08ee", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "0218b9140deacd8c2708df8f5d72804bad9059fdd0edb9c55e3418fb63cd4eac", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "08879ff53766712fd96ae690cedc4d0d29d1630dd74ddcfaa8c79eb2af709f60", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "b72a1a2db06482b97bc7de22b498d4094a3d1e7ae663d4e3df79602a8fa22817", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "69f333d9fffaa205375952e0b1c32b0f137740b1d3d51af15f8540e5607bed54", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "8bcbcbc5b86e5c5b18863b9f26173f6df00d3477cca0c31f0a53d2f499dbd053", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "4e74509170559de0e9f872d3217b2b594f67c6aee01918e85088807faad6e04c", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "51a7e8446871765bf457be21274a1f78fd2ce6d2fe3e426b405c2a624dbd1fb6", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "662ca86f3c4b746dd15a37f79b157dbd44c8dbf23329e6fca3d05a01d8a3b702", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "426641b3c67f56c72cee260bb86bf9459f1bf7809cb23e85c3db0618964ffcca", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "588d07cb18019a4c3545f8f3f870afa7ba52ccb44f1771b94b11b92295447895", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "50abe73c3a00bfb72fb20546f903cc340e36c546022740e631234f71af494337", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "368d1cdb33287f4c791a3c57e9d0090590bd32f06c578393baf66ee662c916c9", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "ca188d0639585b4363eda2fd5780a874b80a82d88d80e6f3a973297b0d56a240", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "267cc1e2a5999e9bdfc77d9fb10a411b8291e0bb48e704b59430a0c5a4f603d3", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "3d13d44e19116b260e7052c8b5e35565c55970fd496914e25f2193348ebad978", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "31af963e0e426d5b5f1e317b9c267d51230acfb6fd69aa89db4c42474b3b487a", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "78de9a0f9aef42089720de16363bde57708478170f7e664bfd47944fb647c143", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "8653d96ad9f69dd290a3c80e67a8628014646897a09e62ab6aa2f7d6b3e9ddc0", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "fa7f1a37809afbd4456d41e03088549cf51b080760042fbe5d83ce25a1bac096", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "05c2572b80ace7c4212c0c6b58490302551b61011cc1c0e6f7c91bc66236e9d4", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "596539c787e1c07ed85437b09159d991bf24c4179055ed3d1c1d21d0ba949457", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "0c4d9c52c2410136e41759e9173d9ee9d34cf7428fd67266c1b4f425a518ccad", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "a560e39875c323dc61053618a6188e0ed3cdb0ab504e1de839940a17d52efde9", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "181f62c724e5b76e3cee4f6a2b6e3e499fb2536e8c2eb05fe6a2705e09ffb7b8", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "a6a055f06b11b79f7998fde62c9815c601bafaffda0b8af260d14f901937ea7e", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "c40ff7b4b72806e46224d1e9ba3e211c8ecd20d5f5b2155c21941b7d0c94dfc1", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "30ad231a829fe3b7d4fa3ec269f1a76a7cfab72b1764d82a60215122268957cf", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "36c3426ebd8698d38a6da4d2390166b88eb02aed1f41d7348a7878884776f095", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "108af24f986f0801d4cbd1770ffe842ac0033b16a878493605d823e6ebf4999a", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "40010b55287951fad0eab34e997d2705fd8974049bf93b6b46aab15e056a07b1", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "6aed725e34c02a5f42c6a2c294535ebd834ce42792d8a6565437675442a417ea", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "caa64848c5e8782faab8f4bb447137a6a38dd758963c7c209e047456c33ecf44", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "75820432275698a28bb3926990bd3009cedd0e7c5fe1d767ca953eb73c4b7c7b", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "a2e17427bb8d7d18570981bf2918e6dd04d869845ccbf079da5467a51b0587a5", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "c540c7f32374708369c8f0b58e90e9b4d2fb84dce22472c891eec1c729a23de2", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "fd274a465b9c486e699b6104a71b70353adf133bdcff87ad0abd1295e6088655", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "266b246679f3560d5dc2abdc9e3d25bb3f94e1e38cad36d9b4dd2eaee61138d8", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "7f271f907572ea8a810d4141a34f690aa2aaddd12c8cd6ffa30a681954a39432", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "e302486fe643380700440758996134295d4e0e1a6b8125aa53d363e648a453cf", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "213f3ff4dbcb686b2e73396660bf0a87a5644f653f4d5b06f2d1227a18801a30", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "f7bd346fe5d0ac4fad6cf528829cb27e856dfbb6c9c302b88c2f88ae894f2918", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "5fed0e5b75397d13da223b75315c08ebe636bd8d26c1ad85d4a5ca0eca72f73d", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "1de3f3be467f6004323c0547074622d8d8c0eaa3b5b1a50e4b86b8a338b5d66d", "target_hash": "ec470c172fdbaba92ffc255d689cec6bdb7d6c64ef76bf88176698a12332690f", - "section_hashes": [ - "af41ee79ccb9", - "cce6230c3598", - "7886fc37b0e7", - "71f0669b524e", - "bc4c47c6afdd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -119922,1190 +40886,332 @@ "ar": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "250c38cf31129bf2efb2b334c4b9d66876cbbeb3196aaeca19b04f7081573b9e", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "a65dc3d91a71d6204e2694253566ded2fe3e2ca40bb08ee7cfff391f99b08f43", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "e9fbc665493f948e7ddfc7ac0980709a6686bf8d3381faeef6380d0ed003924d", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "ebef558b3ed739a907fc15beb473248d711a50e85a328492d519eae47fb7f55b", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "9f4d6b5a7dc2c65eafacc0f9086efd556d687bc90a5b6bd329b4bd76f4d8f5b6", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "99613fc40a1b5e0fb7da473a22ab528341e0430a4b9598876a8e7b4f10eb0544", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "88abc7db3c1d04cb5e7431979d270a72c53e4602c698d3dd4c07b5913529d229", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "5c04decba13a62d003b2aa9d31c4e406b64d9b4a02835bb2b651dfdbe77f0741", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "d825193546579e5524f606c2c5104559210d0ad410e052478735b26aa571918a", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "a286134bcf56c3dffe454bb54669a1421254e7dd1ea9caa5a9655e5226941457", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "7ab49612189b5fd1c69edcb93c4a61016783931fc57053a97d6cfc9b4c9c0f9b", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "61820c92d9a22d8b46f621528e367c3e1237314a0a005f728ef6c051eb4f3dd2", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "3ba65f8f09e87ada47aa1cc20d5601307f00902af50fb5a335fabb525c734a34", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "f6b8116289a06e8fa894ca3946c748454f16ea2afc0747c47729f4c5f5df2044", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "91574f647b89fb5ce624e9ea1606bb81e366d146f6f030b0ee3ce9c6ef50f866", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "2dd1b89a5cf2c31c26b0eb8f4f3f8e687d89d9da4cca67f418f8a0d88aedf462", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "83834f756f510b21216abdeeefcf5f9bac44cff37ae6247048f5e9d8d52946a4", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "0aee04ffbbd6cea27d6f456320adb0a7e2e5beb9a71183f98ec54dbf4e4f083e", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "d4a6835be0e0f871e9dcf1e71cfd24a74aaa224b18a6cc5837f147f680d17168", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "b79ac9999e516effca729608a853c4747743f3939118b323b60a8116be53a8a4", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "09e8380a852257d1307e922149fdc10b3930ebd7b10485ab39cb08960faf42ac", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "f6772a7fab5b56529dff3891d82830b03d24533dadf85da216224fa7d698a3b0", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "a79df99c96de2ecfb21cdb39c1f8d6cd08a576467d64f11706f62dac23d56cee", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "82a1e30b11b5c33917923068af617809086679ae8e9412ec780262d6acdf50df", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "54cf7d950bd0450f0f5438a668a824e5d55bfd18d730b3af8ea33a98236f4df8", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "153488dc11b17e3e0fc8a733e1127d8f78e6ec4288a3a8196a51e6fe4180ed97", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "52789b1bf24a0206548d29acaaf15042c0dc5e12bfff29b84a7925a03554d47f", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "98b45b474dff925e07a8349c66038838f0e604a871687a72e6b5871bbc6988b0", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "1a1ddc5ae8b2d208e2cc6cbf21d0a6fbe9fab3b1d03c009653be6f50b7a89e0e", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "c292caf5bc69b2ee8ec3ef412b7815ab634810063cfef02b135d0f0e6b948985", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "46bf44c36ff4bcaa58e7ec1ffdf4ef2441a86cea0af06f680e5aca62a338de2a", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "42d67c0a178e4f8d741c590202da8821a7101151032537a2b2348a619dd47391", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "7b173ea418a5836967e1757ce7311f0370f90f1df262d0f15aacfbe70ae9945c", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "2833cf0b948e886f44a345bb236d679f690abff81c86e339e3ada355a5a4b4bf", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "a026c153f0af9aabe78ee1ae4d25bdddb59d4d6303c41637c616cafa23e8b60b", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "767639ca0a7647178d5cee2e8bd67ff08eee318d80c5a779a22a95ab9c36d6c2", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "dc67b12f494fbb84d8164437609b3454033e56b538b4e86ef1d74fa6f20a08ad", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "3548dfab27bf5cbb04554169edcb59156e60d16af13814996dea27cdb3630f4b", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "e3c9c87e0f635efe32ef950b408ad67a44299351f7ae60a86dd0367634c0011d", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "859697ddab152cb85fc5bfa1c2267780fed7f23cdff51270d1d008cd7dce3ebc", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "b97d97a3908b5ae08b09cc4fe57c5657fc7d71fc4e4351e31546103e91c21363", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "f29c35f8cda5f49412ce7e049526b405accb82c23391bd9a3f5c3b8ba2781a2a", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "426d047b0dbc894052b381fa514b500034da2a97b33dbb25ba952225aea8fba0", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "641bd07641c42740d61fde18a7f74db4cd0ddd6d3480b45391e67e958dd19b19", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "2d387276755bcd4558a95608803909a9bbac6af8a6c4d7af89787ab51efccfa7", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "0c0d93c1aa3e29075333602cfacc57345d1917da9bc3d830b03d359438029539", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "8787cf681a8d2ec143d59a44ef533972f76892cf1ff45dfee9cd3d4791b27543", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "3efc79685100db241cabc6124074c792aa49cf0cb2229b2e248a879d25acf9be", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "4dd5e60e3710acadfed11dde185797991f153b047c6aca7ff258ea2b7f9756de", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "6bd12318e5da9aa31ae1bbc988d3a6a88dc4a9104695bcfabcb20164377f19ba", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "f61a66f3d54109d1bbead09866a9fab19e5cbde18f3d2425f7bf1ac04ec702dc", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "9fea1d5600559c7d44dfcfe37d87a6f366bb8a23aa775e828dd246f77697c040", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "9f1e0b4afa738cb53b6b3b4303cda3f860aa5a5a4069d0eaa3d8108f6413e997", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "cdb596ff83fbd8b9cf15b5a750ad7a8f0fdce5fdedfe0cd4a7293f5f797a9c3a", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "456f5c456640eac946e58fe0be711b796d9acf516b2e79e1be0cb64c514a91f2", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "83879820cc6f5eb89ad783849ba484547200d37ab18a97139ae9fd2fd8104a11", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "5d921c576f2a8f4be766072fe77ce873adbb20583f893f057a5ea71c44c9a86e", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "28b38528be7a648e69b4c9dc198db17a90cdcfd814f10a201230cdbeca0bf4e1", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "026b777e90dd50ad1131bc094c990daa8d382d8f04517b22e8648da23df11c90", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "32df832a4bea66cd5f98df79ed8bf4f83abba41e8ceefe43739473c48c21f158", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "687e6ada30671ccffa281cc1fb31cf4bcf4deff8dac2f013375f8a978fa5bf42", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "e2e3f33bdf000cfb41cefb35eb9f81081767cdf1860b37d7ce99feb8b26a13ae", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "0ff96f0e7aaf9da9e3857154392834ce440e8a475e4d173060c76a70c2d3463a", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "626a83f9716618fa89b50de4340d782d7d838c1a65362935520bdd55c5e1bccc", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "8337f1687f2402b80de4b093c0c84aee4154f3cd897223eda5c5e36cb33e5b31", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "3c3c0a6d74c8e1e491748b9b32fd88b776c92d4683e437f727e6241162847665", "target_hash": "e189ee0bcb32a008757279adfb346857bc4dcc7ff3ffd5fcf59558bbe69af166", - "section_hashes": [ - "8d0ce34b5080", - "a6d86bbaa71f", - "162b24053fd7", - "37e5d29b1fda", - "09a7578b029f", - "852f8e9cda2d", - "c4d355f1404e", - "3591a5a709d4", - "122b0745a4dd", - "e41ea6d30c9f", - "02571c4c19e6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -121115,1309 +41221,667 @@ "ar": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "49a94c07f6db63bfea4267c30aad1c1f22d8cda9dbcf2bb5bb6dae3b4ed612d4", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "3815bbbb7251a3fbe461d7138a4610f7532e195182bfe2605b06a061e654eba3", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "11e2f0dcf3c77d619d25cc41f46962e614daad46bdcec636f49cf3185fea46b1", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "7329b11852cd05576b56c5b3a18ef8905a408295ed1799880cc00e0fbea5c156", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "a162007ed0b9369b98ca6e2d8fb39db24c7c9d8038d99e879907bf428f1b2ed8", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "14b6a7f4a09f6c143190aaca5c5d08de59680d8bcf9b381af5d9ac15dce5a045", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "29193d1e1505f786cdfbb0afe67c28a2028089ad60d6a1ba614d8621d554409a", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "b8a53834033cc82dcd4ec9efa9d03db9c0f61961c65f8d4db9c33ed673d8570d", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "c084e380abb44f186d6cef1a15524494cdf5b324e8c70cf8d2fdfd9cb2a77031", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "b96f924667e741d80dcd574b3a26c2f3b0ba8c218ecaae6a8eb09192f90298d9", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "5b329eeb9071958a1d490850703a4429490d7511e16455cbc693b26a13de13f2", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "671db37ba48ffa716f95cc99fc6e2f29c2074d9743fc27e6432c67b2960e337c", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "5e8ee286d02654bc81a2b9db529821cf55c961c799f63c894044dd9ce33d5b7e", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "ff16069405a3682bf67b5a25b775199b5a3936a80ed1b0511c5f562dedd78a7a", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "a7da909e1db70487b592c4450ee561a05d7ea3661bf54e707b20b8d5d3424c75", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "d2ef09b017f01a33b0a07393115e6afe5cbe2d1a02aa51bb28d511310493bc55", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "dac0707a532f8ca27523bb5c9f8a0d58e529449605d4dd645e7fcaed70421bfd", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "124475ff15cd7e66f38af19b2157e58120328cec8b129b34772cad71f5e568d5", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "f67e885ce66cdf3f6e4d090fcdde76333d85637af0b64691ddb8917f9f6fed8f", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "c7e96aacfd6ae4b89349ed508198efc2c332e6a39705b02ef73c9317e51d4211", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "8961c676aad958533b6a18cd36a23873d8c8cbf13bdf11e2282795303d3d1e4d", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "0e681aa0d1e4b3fdce7870df93dc747d7525a7c2a6e5bd02df0a2e82203363e5", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "2e752f335a4b3bcee8dae4159a1bc41a1befb7677fe281556f982436dcbee0d6", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "4f5bc18a9c4b4fae17c0bbdce66f7b3a3b29d63cc10bd37f2aae9a847a232071", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "6da00dffe88edebd5d94593a4f381b29b0604e829df46cbac2c495fdcacb0464", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "95d2f8bb740ccc8241213a3ff49e56fed522d505536d2a530376306935cc5068", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "caa8940211e8007c47378042fa914b0cf925771407c0d118647a2e99490035ed", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "5ad9db16b2187fdcad10ab8b15ab336cfac4754e85c8859021b07037f2e943df", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "53cf938095b3751a6c38a97e13d8002d17d9f5740953ea361b583a92db4441df", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "ef9a2f78c293d93d4a17115bd4e0e68e5ba715166c3626936b5f64617954e1b4", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "2556380af5ffdf5377fb6e387b7ebf03ae594af9c88f2680b7d7dc1fb56012f9", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "bcaaf5c779b5c410b5ad6856465b844c9d841a526e272ddb44e9be2694a3eef5", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "b46df04794b5c5ec1ef4846b5ac59599f6409ea7ac7e61e2f3d7b3a305fae364", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "496347d7d137a99fd8c4412bde523dde1b2154f6e5bec975939b993019e13a0e", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "e417f0984dee15fbb0739dbc9a1f847d286f15524d680bb76dfed2fbbbb61470", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "4affa97855af3ba0eb7c300995ee5b37e5c8f89058aa2e51546dbb495915fbcf", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "371712c7dea6735f30dd0f3ee1312d05f5c7750e0adb5f9dd832f8c514e597f1", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "a1c71b98e0397cf4d36613be3f5d2da3ddaeb319753875b6ac7ee38a2d57f498", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "3ce7fbbf69d7509718f0bbd0f15e52be623e150122768133a8a76dd203aeefc0", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "d473b0d8515a7cacba47b6601e740956717923e4421824b80e7a10c4c3a8fcb0", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "cd67bda084b91c3662c8ba5751b59caea24f0ae8686eb483651a2f5e5e32b27a", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "51d7a8d4d3a6f98f619d0d01398ba0e40d02cfc8ba7ed6dd099ff161d00df14f", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "191cbd38e22db38bec4c8354c21d4286a5de35ca0e26eeff834b396f54d784ce", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "364af21607a407b9585bd13f32d05b58e475dafb5bdcaccc6c85648076aa24a1", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "8de95f64b8e3fc9228f586bc6068ba4c8ca965a2d821eb4045bb92c86f04d9a2", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "2646bf31492318180e1b48d8d0c5d75f3a630db2dc51e46e507196778f3ac5e0", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "cd7f6d58a1f3f05bf1ce474aefa17e42363069a14f9c21fc8d6c5ce8fb6e6b7a", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "b1d44b0f526efcfce1e8dd7521b6dd6d0b18bee63adc693ffc6851c2948b76a2", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "1eae1b5222855f021df96c335554b906962509fe385cf75fc66903b5f3bc3512", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "f34f7474c7af5f95b53896d172f1ed6e7856a719651a0d8921c3caeaafcfa413", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "58b956c7bc06fcbc16710f98085930f577d3bfec2da7efeadf321c6889b33127", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "7721b9df92b02a282d94b207863f9a1261a5a5316a67acd59aff65e17bbe0277", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "fd365ed95f668122a77c6acc0543fe15cee862f4260df60ee39e174f6272d28b", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "ef002b5fcfe94068797a4fa8e7896383ccc0f95bc0012c9510f8d51bc3733db4", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "02637288947a74f21fdf6efb7d6da16d8a9033fed17335a6b369e50676048fe0", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "7bd09144ed2c606decbc8e005e72f0544954e99203eab199b8be3e81e803876e", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "c9e4dd281ff94d204a3091a670b29c89a36d4332319bb107a261fcf2d748b90f", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "b2a9ede284e0046258b9c1a31d0bec0a378f88421c82c2801fcefd629f1401d8", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "0c2b5b7991c4aa3c33badbfbab424a80a8f0da2ef0e4ce53af4eb3933f15730a", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "ad7d822d430f36383c40333b35aaeaaac23800dae284322f4ccf315d52a8ff76", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "7f049f9f4df7dbcb609b4a86269c2bd7deee91cfd4217d630bf6ce597595d0bb", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "09318099d916431c9979dee75ab6b8938cdd9df7e2c6f473b80af1f8ae73b8c0", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "434079e0f758487dcb340d332a39ce61a28f5f50bc89a8ba98fa0599b9928872", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "3e130a8b3f22a4e58b2729251e3c31faa0ffa6f1eb5896143192624d8998e5c1", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "551c00fac2336332f210c6feae65c8b33de717b8f5f938144b190c2729370f18", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "52f40a5bb68ad220d445ceefde16fa6ba95a7f1bc5605b9b31975f2e56aad908", "target_hash": "cdabe1ad122307e154ca479e71ec83066e6b66d848676eeb07ed070acc6edcfd", - "section_hashes": [ - "a47b42b40026", - "01f9f54e4552", - "fd8829af6749", - "b8509f344452", - "3a25ad2d7f14", - "1fb91cbfec0e", - "c16912ad6c89" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/reference/PROVIDER_REFERENCE.md": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "locales": { "cs": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "50d3060c148903a3fce8e70b21948c02172cf4ed29845168114b2bb648f078d5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "d18cbe6c4eebac072eee0a438ea918fd2e5e1aee0febe236ec0d183daf087f0f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "937fe8dc290b37d05decfde3b2db1f072b871e83ba8e7b54e7627b8602f293f8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "d519eedc1e1161a4d6df9ee3d61d8cda516821fdbd6b8418ec2d3d425d5aaa27", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "f10a9753e73257a72d6e9211db09058300408bbeb309abb5a588298219a33f41", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "e675a587592d57fd418f0a1494257fe0b5c3830353df0057269dd6a2574f791d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "29a2ef83b01b01ecb9deaafd8361ae3ba475372e5c097fba803c4866aa575a32", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "ee4e1d9c1767ba4302133909d1d9e186d55097155634eb6cc0cae237975a7350", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "d0d653a2074e21f58a75a12bd842ae3a2949332b6ac4acabf4776156955df2a0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "9ce7ac397e0613a4cec98fbf7bd85e3ce03f499f42be24601910ec4f0c431661", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "9d3da1d2299793ccff2de371b6623bc22a76c7bc52540b8ae293ccd4fdc73fbc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "b5187ba3113d15a62e402600640d0d0ad7922da322838c93477ab74e4bff4f1c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "323f1db143d3f53312f75602b16bb39ada98c959540df94569001d541de1a310", - "section_hashes": [ - "39fe8798e65d", - "8ab33a31d2cb", - "247045a88cfc", - "1d9fdc4d6404", - "e4f4bde7cc3d", - "0e60ea14e093", - "5a0dd732ec60", - "abb3e114751a", - "5b129d0710da", - "e109021091b6", - "aa9a5592bf18", - "a87282570abe", - "7d6dfe6f4cd6", - "c071fec914c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "4cfe9d599f776e4a90426a0d2b75ebebbf3a504c980b5ad532ebf3f1ae4ed817", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "a8340c5e47b0f14254d205a9c75e5c83464114760c17b38437c7af89131d1ed9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "d8510c52209b0bda51068a1d0bc09eda9f10559f30c3a388102c2abbf7e91499", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "3d5501509da209718ec9124aacf8aeb204513fcd24e3caf3d688bf61a49c5f09", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "5966bedec5bb521b712a38a8550dc63a7fdbc535b1e1f8eed6f318445e3a06eb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "d9fe948f50d9d15b2cadbc0e1faba4626e4b724687e2253c5b0f56f105136b35", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "a8616ffc46af8c242145c94e7175dd214a429262e609f546f516ed33f7c89a39", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "be95bcf33f45079d282bdc33332f5af4d5311fc9c9a9b3ad0e0f36c59bdf1282", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "5a6ec19b605296421aa97af41238b886db6256ce9137de179c876db94760ccfa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "60899c39d25862c693a660d2ad45ccdfe524473dafb350e3e3b9f3943323e39e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "04d4d9ecc93b40a46029e5c78607a4f425c7ebc60e51b8f155e3fc12e1180857", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "6fc0bfdcbed385d33cac777d387c0dc1895b55c241af2cd46c7290f143e54815", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "4673b28572cdf8cdea1235761f98b061b66c012f07c92a2710e6b52d9fd72c01", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "cffe8cdd7b7b01f5529034911b0e282f131928ba1e90ef902274977ccfbd34c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "c0105fed8e6f2b4f82fe270641af61e947be01677c06005041e0a5eb5691cbd6", - "section_hashes": [ - "39fe8798e65d", - "8ab33a31d2cb", - "247045a88cfc", - "1d9fdc4d6404", - "e4f4bde7cc3d", - "0e60ea14e093", - "5a0dd732ec60", - "abb3e114751a", - "5b129d0710da", - "e109021091b6", - "aa9a5592bf18", - "a87282570abe", - "7d6dfe6f4cd6", - "c071fec914c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "1ded5fc52b12e4b4da901557bc9e028a806222b684a27f6fcb4e2a6db9268a7a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "52b12d64c32481985d60ca002465a70c067d97e5bd60c1a5ac3fef0642a33941", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "de1d4557b678e72e7225db3565e27c10c90fae5f51521ba8fd8b9c17617ee593", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "9ebada5edd38161542a337b18ea6e0dbfd6963181505195696e61a48fb1e67ef", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "2734854f6e238122bd26f6366551af5207b42bedd4a3cb6b18a6a8054ea6b0a8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "8a2d386dc1f1baa0813e2289fdd79bc19f312b9a0e7401c58fabeafeacb89ce2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "8ccc8a82ed1c599987bb3f56160fa4605d4834dd960033c2e554fc0ff4782713", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "d2076e79222507790331c2d43dbcf15fc67626506ffa29d6fa3527dd453735bf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "b1deaa8c89ae172ed7ca8c824fe55f0c5725c2ee2a899a26682a5b3d571f9507", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "332006f2533861ee3f52958474460900f96eac47284efbe3cbcd6e698ebe02d9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "e87e8cf7916f59b0993d9d5f00d93fb71f0d15e63beb9e0f1179198fca4d299a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "8c207f6b969d11d90eb2d52238315f384d6f55432d63499f69201ee56ac2f264", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "c087ca23b786343e8e6abe43d89ab2bdb0c7a867a98d55281aba6d5d8ef71555", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "3c021b222cf7dddae08441ed8a8a99b6b306338bda132805ffc087a1d2651df0", - "section_hashes": [ - "39fe8798e65d", - "8ab33a31d2cb", - "247045a88cfc", - "1d9fdc4d6404", - "e4f4bde7cc3d", - "0e60ea14e093", - "5a0dd732ec60", - "abb3e114751a", - "5b129d0710da", - "e109021091b6", - "aa9a5592bf18", - "a87282570abe", - "7d6dfe6f4cd6", - "c071fec914c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "15bdfe37c158ce671c5f1cbb19a4798bafcd44a5e7a3a52db359b9aadc3d6d37", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "1733738e8e218aecb52c482dcdff018728adbff2c2d5ffc7713205dd4f32da71", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "7bd842e7908963d2260866b8d092eac64d8e2e32e4e568bef9d191bf1cec9489", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "c8903cb2159bfd580956408f8f9ce08edfdbd4ec3cac1b967db5da2a7c00708f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "02d2fba256957461b2d10246afbe1f15962e6aa079b123d92a9557096fb1f2fa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "dedcd6fcbc2355f73e8b131fd561df553356e10d7ef348a1deb13aa4af583d45", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "5eb13e8d961eed9541955b5ba1272543726c65239ebfbee0a9d801c422b85bff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "ada67ef416aa81c9d7d7daee6f964678ece36f911f8c05e2f1427ca97cf14ddd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "39c363385abf9fba2ea2bbd1350656733592aa9ee1a86147f6ffa4b2a3bcf521", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "15657efd2eae5710a22a1a99e4253a1e78ab6e4f69e356417fb61ea32c70e6b8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "7c04843a228f2fd6477c55e94eda89df4276332c4a0b5b448371a4cb4e4a5a09", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "78273809e7d4c7aa3fd9c60a7a551addaca29b3773b111b0d983f824adb96c4b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "4929c42622a4b09db077529ab118901ad6f55300cb58aaf43476c7cf9ea5fd06", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "003b014e0e30b21731fc6447a5ae277ec26a3dbf24007a0a61d53b3e9e4af2d0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "f420976ebb37b11c8ac15a72515bafdcbff8491eab142e5829a96de635f6e05c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "7af506247214d0fa2ec259cfbc0d43c5e8f497d6829463e66d5a3bf31407cad6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "d4d3c626562661313f6ee46ff5b0fec4ac180734ca510dad16d974a429214314", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "bcf6ce4df5174dfb68e395f18fb23cdd54996d3b10592d2e3f7b49f3a9399978", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "cf94903c0c5c3365d393453eeaf44f3a2fdc0932be44392326508886776336c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "7e0ea57975bd4f8864d16571d5aefa4ca24597294ffeac4e9a1f7e538771c7e4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "0fbeec95ba53fbfb88d1fa5bb4ecf01e4452c12b6f9a65c5f28849b41e15a117", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "ade0e66fb64d6c3c3511e82c85f3a26040dab3edfc67c66c793abf6dcb1f9326", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "1c416040783001c2cc5cdd393857023de8204f9821758d36dcd6cd9d487d84f3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "9fe45f59da45f7d383b9f7eaee692609066b17cd343e7c9bbed4d111c3e003d0", + "source_hash": "95de5d2f1cabf161af7511c6750be8004e14bb42c75d5454c61fe59a763c1286", "target_hash": "db4ebb87b71b9af69994015779addc68fdb845215acdcfc7de8cb1779623758e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -122427,926 +41891,332 @@ "ar": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "0118268900f40682e372cff02d99a9ee0c0240f8b7a41a9aa5e75ba99d2ab520", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "847e97b5d25248362cf4ff15227fb201dc6f84f7079b06dd85f3ee9877aad0a0", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "b570ac23a1ab2e3f8f2fc873a60d26c15830030e973d405562af1c11e34a340a", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "005aa131cacca8044651c69f271630b6250b26f50b477dbab1f59361f78c2868", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "039c6ae50da7d4eba702e65775d2636c32fc2ecbd1583b84faae6ec463036797", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "9a6cfa61d9583ad179df0555a0dc636b12b55a30a1987696376f18976559cacc", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "f88d20df5c1162c1b25319dc476e6fea100fdac08c81b251fbecbcd0555a2352", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "81061b0535a39b6ec8f468753ee31672709c7b4bcc28b544f289f5f7e8493cec", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "56221a76ddde6b9107362d1c1b89e9a6ea11596dd10fddc011e9fcb1e003addf", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "c372ec127d991e2ab481150c07a9aaf447c0778d4679394b76facb9b9dd3bcd6", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "993dda462338ddc8e946812a4c8833a16438f5e0b15d3fff0edece9b24271bdc", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "a87ce1c9a19e987c5ef2be12217a2f6a020d26daa516faf6eebdfa1b6d4f6ae7", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "0abb7cb27b207d3aea18c59905b7f66e2d62342b67165db27aaaa90316abbacf", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "357c7bb984675ecd38ace24f96cc0600b34498d51303fc1cea65b27af15ab0bc", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "a823fce194c0427f717d1182053bfdad9b16fea450f1f2bc1e6a5fe650b6901d", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "e2a30aadc848686b85cc3a8366ca8b2274875bcaa18be1195556ff860293d7e1", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "64a90c64c8cf4f09b52fe7438233e2edf134a947217e9ca880baab2412ba8519", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "8defe5f5ac6dcedf68c1db1ddfe8f35e1dbe5498b57649603a31afe83f845d6b", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "f82026d93f3750eccb593b44e0011e9c48e6c8a464f79907ab7b9b96cb80ef3f", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "682578fc25bb149d964a26dce333d56d5581411c1c3f732e22f3061d7d4f0675", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "bf45382e74263e76aaeb5a477210db804a5dddd00967ad69f010f6adc72be94e", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "4b9cb73fd04d5dbad878fbc47c08b1952ec06671c825db3f65628732a961693f", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "e544f775cd21e81c493348e58e378f6a942737da842fc2304add7fa1b5e778df", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "108bf66fc5f19cf9ccdb68ab1f16f58d517a456e6aedad2d7ee175dcfdbb7c25", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "30066cc63ade45d93c2a497dfb3eb81f8d24f4dbdb001718c5fa20fdcf146ce7", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "fcce3890e40181b8be7b5b3b7f802ce0bb058be0b6dfed83bbfb661f2e13765e", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "3c1373f2928fe753c12da9c142bf7a1d55002972b534a4ac994d3397c9f670fc", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "066b3aa10e925ebed22c6148c926a923210d0c50cfb286c57a39e345558ff19e", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "cf45643bc81e42b530fc5b03847d772999ed6362301d06f556631184c1638b2e", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "688c12db4102662f9685345f8c6db1eb59cfe05f52b6f5d416f771a460fef210", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "b99bde78436d44ee657ced60057d0defe95d480bb3a0f7ace6717ccd7df35e21", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "a8a1703fa647a4f74096c672fb06560a47edd21fe75432a57692749329687e64", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "1d85500370b6bdb3e7b179f33c5d965d240b98e8be7945784d230271ef4f60b1", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "681a18c73aa1635437e5c0a6e528fac661afcf9c9660a0a1e14981b60ffe9f08", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "2fa874135f373d623c1bb88cd8ac66b0934f133bdba6851b855c6b9c7613833c", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "d6cc3c088a2e7b1bf80f208acda0b2ed0a4dc89ba5b83f102ad9f4c3f565d1a5", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "17d2252022035297bd1620ffcd81fb69290d22daf274302c85b5b064080c4eac", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "26e12ae6bafe08a733ea93192ebc270de621e7b6743522f4b43c75a237483f59", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "4d544ed515b7025a2a8d3c9853a8926e93a22992e3903944011ca50fb7b639a4", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "dc9792f6fc97003b589536da75a96e2e9308988f9265ed453a7979c9def830af", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "24a8d2485eabdc1c760253aa56ef1ff5bdb1eccf79b08814f6e72c5f04272c20", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "11e3d638c1e759b964244fabd4e870e817e643a7e3c516366b37417c9faa4c6e", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "6893406ce24ad21bda0ce00afc5e62322c90ea6c587935ed0c9c4d016763a38d", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "11ecaf87c7d1c4ff7a12c32b9833695a4bb5a430df4881d8f2060f4eb46091b8", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "dc4e6461017c921837dcdbd003a7bd602fa30eeca89a3fceb3bf80e60bd93fcd", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "325c129c801c944eec5a8c09b7a440fb72b57eefc95a6e0eff498db1ff83f837", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "143aea21b8c8b61cc3bffbd31c7fa8caa750efb4086aafc9a21676281387220b", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "f1d2359d3c6d8adadf7d179b6ae07b5a7608c7a5472e491a4e24994ee1a28bb6", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "b6cc6417be3ccf97012ab8b1dae85a5172b5093b55e668e6f07f8d1327e41692", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "c9543bde68cbcc139140c2d711c19d632dca97f320f2e5544ca5e36a2bc1794e", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "d94532109baf35809ce7dd68c61b02b1e7047bb8254c8bccc638ce93c36d9fc6", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "594828b0e1a81d1bc677eb52d0edaa748934b113ff6ae53f511aa9917719e082", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "2951a5148cc0f469113811c0d8b169487be4cfe69f14c2a3422ffe0d44c3c3d8", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "57fa690b8694621ca6993100ed1718aec03a732a5caa9639b452f225e25fe32a", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "21a1c4f5fc3387dec551a994d0bb20c6cd3721f077a0a25be88aa2fe153c3898", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "a3c5cc64d128d4e368d5c275a69e6ca3d3276629b60097f6088c1323fb6c954e", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "4181f9a0a49ef7abba1d708128262aab5a3686e6ba12f8db40aab9603dbfccde", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "f00683508364d41b041711418958851528747eaa243dabd8f3585d893991b564", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "54314d42f76555216a685a6325dd4368e8e737ac2d87c402b8aaab5407087441", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "1e8ec8011b03891912518c63cabafbf3bd751ca2c563e5a18b83cd5234829930", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "53e401d827e0500606eb1328c314ecc5d7cf1715cd3e8492344e5bbdcaab4163", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "64ef0237eb7f2173ce481975e14c93f5ff57d4c065b79bf7c44aa2dc5f6e68aa", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "a2a475e7d92d469a5aa192e0222f21257ce76862c169849c9fc37e7e7c15cd38", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "642f99016e04d462206f0bd7d6d523142f05c79b62d5edb59bcd69193a12ff77", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "aa4608735c49bb922b615add8318d65e220e0e53574f10074a1c3570222c0a8b", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "5ff245112a1246894c29de5c9f049cc92f6a74b20b55bbd322d3c10f0a15c483", "target_hash": "c2edcb8a87f21c3e35de4c32f64a4587949be922e1ef2cfd1add34fbf45a8b27", - "section_hashes": [ - "b0f0dc7db793", - "9dc65788cc70", - "26441496c042", - "2ba3612202fa", - "cef42592c9ff", - "91b85980361d", - "ea551accf008" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -123356,1195 +42226,667 @@ "ar": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "71856ef2079643886a53edf40f0a5314bddc356180756d1a308a061bfabdab32", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "991a01725f39e8439ece0f8dc8c9b7aae389bff8c6bf635f6f6d2ac01baaf868", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "c5b5254eb3c8479a9b6736e24eb3ed3c2a2a90f453642a1b3b90460c92af3c48", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "92e46789185bbbfa715d99399361ada0f0604ea4f075409aeed0e5a833e6aacb", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "d00c1fabd4778876ad0c98cd8eb56b70d538fade5ac0fb11b1584b6b1c73f750", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "fb7d1f2bb23c9a2490cce76c15157185383450ed4698e46ee50f0a5f68c130c5", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "06e9585e0afb638c7a43788a1c17c2d4e8c6cb601828aa073b4563f21b1a18be", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "c278c5ea00edf58cf1cdde8470203b0497eb4d607c1ddbb01cae12d2dbcf251b", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "418943f3637f6ef5845f002393c2d4bb57ee50d3d4ecb5dac962b73fcbdb73be", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "36f2d662d85abbb8109b0e795cd260fda76101f3c06bd762eb235f40d1edbb51", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "25137add88ec2535653533334ece1f700f96ad0b228d0f635394a61a8bcc18de", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "fd8d5017ae23958f42912974e48fef9172087bc85cafb5083b0a4e3cf9411d2e", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "7a670055a621a3df673c1b8aa6b51a7c997ba5e3e3a6904d45d50a66e75cd923", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "b78d740c6e3e680091000fe6689ea0498b959a1a43b02eb952270099c37c6e6c", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "0c2ad8dfa27fa71fab53911af5d7b5c6a941f113520251c99d9da03f91de9564", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "7e211a83fa9a4039b68a50e0fc8e63b03a7225715a1c4fd7725d2da878e9bd2b", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "3dec6e123bf74b80b60e26a68360508cf1de69a71d77b8cfd412382389a6c0a6", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "fa1b0cb69f31ea313cf86db6286610ab904c01578e8ffe6e951917148e161087", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "2825b5dfc0890fe6dc98279b11fed2aa4c34d0e273aab2b4d892769e620a06eb", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "21c872aaa4f7f22af9edf88aaa2b912a9919f97f3d5c2ff75b989d9c9633d38e", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "3a9d1624a2c6441e9691e4de0b4f40b47461c0e35c7a38bc6919dff96245bf53", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "49d03edabe81b2d9f5e63bc94c2c17ba59e8036bf7077f554b3720b63cf082e9", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "2827666ff44e12ed8cdadda24d220ffb4f30cb850b94b9e91652816ca933afd0", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "f242f8620df123a5539ac583939deaa5aa6b058df1f8d8f567cbb3bc6e178169", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "4857496951a2a2ce47ca0f96c446b71da7136d749f6f43a52f6b0d64357513e7", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "0061033dd55d2e1a1610eac61a6c86b86fe3204df490db3b316bf1957797a207", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "cfdf990c8defc43acb4d28b9b528da1f78284f49b868f7f0d83c91f9f390bacc", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "fc1b0c84c741c6dab240c90e0903f94b774ca936639469230be1ece214324d78", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "4cf26e417f939a03ae4c7a6dcbbbb003d396dec13be9dbe612ad95a4316864e5", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "25b5f90c93f035e30208d303aa1de0e46d139fe751a4c122c8c7773980742184", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "33ebac8bc4c593dfff6b25648fdf1887e51ffd8476d91acd12fcbbaf87b88b92", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "5237ab707c5af28855ef2b101509c6fb9b8c63c2df53cd7f18b7b7934f331297", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "cb15f78a99a7d3d5941c4ed5896d000c08510319806ba5dcf87351a105b4cb26", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "ac1dbea5e8a349dd71e254d95da736dd3d30e61bf34360069797623237eb1945", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "13155c70bd29893c680eaa4bfa1a1dcf82eb424dfe3ae00c5a0f4a02ff8999d2", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "5383ccc83a3ff6c3793b4df8f52753f040b15c6390ec602797fe5885bc9a4ee4", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "fc585d854cd3a07e067f530618b8fb749ce752c715b676c6bfe6365b71654946", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "65b90e3de62782f282d731679140622c9e4f80bd8b9d25bdf4dd70b44379de9a", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "2f8cc61a9ba7b0a6bfd654ae6c20a1dc32b9c2ffaa7848499fbc71abfb25b9f6", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "d2e9b21cb4fb4336bf0b01a51bc27d6cd2c1bb10e29badec3df394116ece20a7", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "e496ba110f61c69053f127794eebf2d03a0a51c182adc0aac9bd20132a0eca49", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "ad3075fe0ebf597877b6ee6ba63dc22cbc2d417479fc7e0f7a1fbfd5b9337652", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "9b7c20835f4d00d8610be93a8a71aba83040e2d6651811713ef893819cf5277f", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "a2e7d8c9025d33f5e373a063f31b71f0915fcaef5f935747e2a8b7cd90cf7b3e", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "13aacb9cec13e6c965ed0df3aa9d2e28e240173d639dd304ca0acd11aa8fc549", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "d1d92335a8bf993651696bdcb491bf1cf294370274844a1244ff7904edbca9ae", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "94b8b058a2409fcf062e0e6fe41d8509421dee6edbcf1fb5b6db1a146811d7ca", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "7d40dce0bece54f4c976abf764380782e89830229951789ce4a321917fd98256", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "306c8858373a874177e7d169110f34877cba9e2eb5d6940c45af299a403aca8e", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "bbc11128d18fe2366d08deaf5efee597d4265f7dc2f95cbd9117c422c89d9729", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "7749267dac4aa8ac9f9c49c131028f176f0fb929de3a830c8c71d5814d61fbe7", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "f1bfe5f273501dc264266424e08f5c61dbefa1da9e84a775ae610b9a4ef6e6a3", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "4c81f85498d24cfc20622e4226931b3416818f6f64b56315ba1daae406239a8b", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "45a97f6f28a0d2f62b8e5c69d202cb9ffed6fc2e18d672faca99126cdb8886ac", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "4b54d8635fadeda07bf873493fa8e91fc306f093fe2d50edc6cda4e26272be7a", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "3f4d36bf70f1417156b78a3322170e4be3cc7d3e96b8ce1af246eaf98ab85dd9", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "f51e2f6bba92d610c739b653fb552323f2145539123587cc565e903e127100ef", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "9b5609b019a1b1be19daaa1228efeaa2cc61c3f58007572ed70a35a4d5ac6660", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "3822430c6e194b2f0db943bbc9e55a291673a51cde3bafb5b3052088babf6c98", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "e26663651923166868209768a6fd76fca328784c8ef54e789ee2e8aba5a43e91", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "d409a6e9f426590cd6097dc39cd8df503ef8627b1107fbf02da755d3dac98949", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "698d20c56410f59f0b95df0c3e5deea3706dcb982e860caf28eb67c0386c1f2f", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "2ce2afe38fd6cd3c5358b0b05d151d50a9f62db898f31e67c17192c99111e8a4", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "ae69736940248af619aa33d432af092c177adb8317583ab0557b7a6e4a744166", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "59010d57ab7972e532a99a4e88c5443fdb60dfe26385678f8d30f8dc36e59f9b", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "aced413cdbc2974712238968976237c6c988d4b0e930d4f01d989479bc55d8ac", "target_hash": "5131bb4a2a6257110966c7cd21c2f5a95c51f8b9eeda07df8b119d83995528ec", - "section_hashes": [ - "10595b8d5808", - "662c5292d1c8", - "cf9e7548372e", - "d9e978330ed3", - "05e650d39b28", - "b4a6a5a2ece7" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/routing/AUTO-COMBO.md": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "locales": { "ar": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "0431e027e06c208a1e36c096bc9272e4b972a360b69c0adda176683fccf5d961", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "az": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "92b4826c99a2ad9cfa8b8ddb004d1dcb5107b34470853a4aae1cf29fd9dbe088", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "bg": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "b87338c8e5fcfea04d2748c81206bf86e68f7a40b3a48dd8ec7563ca8b7d39a0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "bn": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "c1a1fa7b52c35a29a0c7d46b784b2294328891f642c8c63ce9d77f544614bb83", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "cs": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "b4123a8f3d5c937c33de2156bef458072319c6af914ad2f9314625c9b651ac0b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "da": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "b3b209159ff35b12883a9f148b464c7fe50b67db694dee14413ba203236ba6b3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "de": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "1e34fccc582a42f79f1b1b63c84bb1ed7da81d1d27b2769f74ec775022bd1fc7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "es": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "e2cd3144995278c071980f4618fbafad4ae0552496c95f6c11ccded99cba5914", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "fa": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "536a8718fd4820a9f9f790851f3b70cba923f66c8d74e3869149f9661258ec32", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "fi": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "47c286baebea7c38803ac00e0388d4b5267825657fd7548a760afd3ebd71b565", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "fr": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "9f6d776b2e563cee4f0e0ae0cde69dd5d4cd03266b1ce7a522941917e09080c9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "gu": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "2372a686115261a2f17841f272085017ad015660a790ff2082ecb3b76ea4ed88", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "he": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "0317c679496a3cd242cda347211c5fd8e4136a590361cf16a78d275834705695", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hi": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "55da3129a67f2d8cd335635064744cbb9a80f293cbe228dbdd6fa69ecdb17b3a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hu": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "22099b03c220beb4abbff9d4d56bb3026f63193cc23dcf8f401a40b77cd84038", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "id": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "87ddc0e16f0ef833ab34a45960c785ae0be6d6c496981796719801ee4529691a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "it": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "4c37306e741152160603eefe6aa3a5494cb25affa6152964d3c17f5e3eb3cced", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ja": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "d2290dee4181706cec6b92b874f8de1708bafb22b34c52f1fd275c2fbc82b23d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ko": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "164d8c0bac6e0cf637b550123f9030c6e946fec5c931aafdb068975dd525f331", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "mr": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "c6d101ee5fffa3fbcfc37ee7493e77469fc6efbd8e0cd696c66097c69e1f1373", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ms": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "5275ac9a87f3c2c59c66cd0f12ddba4628e742ee1b68e750252a2c8cf5ced3da", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "nl": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "76e66e638f0c90712fc4d9a095d47005757cdf5846e44ac482cf2c9181c97052", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "no": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "a41d17cc31619fe13cb6db341398ac447e066b5df42523786a2e0aefbcb1f6e0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "phi": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "ecb082a5b31ffa3902216a1b8950415190ec9814303057e02b9ee17c271bcda8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "pl": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "1d502d5d3b1469e60018202eb8b8409a2332322f943c1b1fedbdca5241a7b394", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "pt": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "b5d8d0fc0724ec6899bc7cc97e38b840dcfd0b7199d0892e1a34444a33a9e59a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "pt-BR": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "62ee1710baac442025636a594b983768e1bc909563e1d076b622b25ae9df9586", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ro": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "675dcb62fb6112043903a3e2de02a19d022f41b3904cc5328cf6e243fe17aa54", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ru": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "86b7e5b9cf738d437d7647cc23a4b5248a08fc8daa0061f98ef0f0570c79e585", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sk": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "2b97155d1c32d329a0d269c6b38044a3ce07c43e760528796ae31454f6bd25c7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sv": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "469c29db8becb0f87351b60e5c557e02ffa71eb163e2768eb297d4b449d1c491", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sw": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "a94f233b27cb30da04fb5271f102a9ee6bb9d843a97de6e39c8d31632740f4ff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ta": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "29cff49b2498c28ad711f1158501be3b9e828f3105cd165f24580db173ca7632", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "te": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "e960541e17c8e60ce119d50302075f67b3ed8eb49b78a12b1423a558ff5467a4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "th": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "2cb78e1d4f39c494912e539a618320c38e82ba0962586d9de45c64b671597991", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "tr": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "048533e9dc57ca6fbcb51ed7e9e518e0fdf150fc47b1646ab88a0c4b0aea0e51", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "uk-UA": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "97a1621b6d6646d51b98fea201bddee46d1e94c5e61bdace8c4bd8a8325ba835", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ur": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "31112a71e2d8f76c905866b8eebfcce9c85e2119108ec19eafc085351488fe77", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "vi": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "83d8f7e6744c31b519aeb414c806ac505dc503cfd70cfe344f64ca204ef6040a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "zh-CN": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "91279529482fded42bb69215222d3c5d6cd0b4dfa454c7378ad590a9b3e74461", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "zh-TW": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "d8c46eb49899d7d401433565ee55120e8d4f6508b6fb64ce778aa6ef1b43e27b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "el": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "b4f9c3fff2c53e2eeb580f15b1643868987a660ab15efecd67587e92ddeac88a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hr": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "36e304845fbad2b1b9903d5e7a115fc145788c533bc54683d045998cc2716e52", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sr": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "150db3a20cce6d6d06aad9f04702a8eba0b6031f293fece4a725bcbfd098646d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "lt": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "ad51945110aab9965dedb63a0abc4ecf4bcbcf0115ed8e4da7dda5b770e53b6a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "et": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "693ed52deb2c3e4b32092f26ba268ca07197f02ccc3bf306c533232c16b7f187", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "lv": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "d5a0114630b2bebf149846a8879ea377c9cfcc5c17ac96a1d84e3bfa6ff853ad", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "sl": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "f7ab950959fdfa36b5e40921398c93ff22652f4e02f50e8da3d07b241875dd73", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "mt": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "de345f9e0b7fd1f585fc02cb6f1e1ddc594ab0bee5d5e6529a36765349b912a8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ga": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "2b7546cbe0da5b6fe5873ab1561ee34b20e85b81fd124335b801d169455d0895", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "kn": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "06267c68fbb2a17670dde4eca9ecbd0595294c766bfbac338cd944e28ed6d273", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ml": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "554f469adbfa504a1044349ff5b810330d65a042f737e0d6b5743c0991627a35", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "or": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "c7a347cfe787311bc60ff50cd0dd71c6e0892381b57ff832085747e4c126ed88", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "pa": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "3dd6a2c08158740401906d6366d8ff6f504111e3fd5ee1c09023ac67c740852a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ne": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "7afd216aaf4066a05bf8474a10e789a10ab6a2041d299ceff371966572f903f9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "si": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "8f2c5a309f97104b064752ecbff115e3e5e79227a00c61fd5ecabab761d86fa1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "my": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "cca9ce2984c181a95682d7e542d18ad72dbd1f33ca0cc5141c01e359f82fc70f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "km": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "3f04960ecc8bfb72b885ae69408a401cbc9cb8d319eeb161a925211f38990b46", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ha": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "70b1099cb075c748d1b6f31dc53d8359fbc25b6552c501ebf74ece4a9700bcfd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "yo": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "cce6162a14c13c961176372fb7198efaed3834f17428c87c0c45fed6900879d0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ig": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "92ee1fc623ce6f8cb0ba4ae4a4ba2f6cddaa2aec4c0ef8fd5bcbc7931ee8a7da", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "am": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "63d84bb8f993a34db35f09ba70cea0dfb8553952d8d00d61e5e6c24baa60560c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "uz": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "b77fc8790b3c29db2565ce533b292a74c4118346c1e5f2f43c6d9776336367a3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "ka": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "35e60f8378a5f50f6f4b48613efa49087ad733373c0f1da9e541e5c59b6ebc9c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "hy": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "88b8bb00e740983a605f18c96fbfa08f77c95ec5b781a164bbba6686a7dfc015", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" }, "bs": { - "source_hash": "04125c0f794dcbf82dc751be2ba01799feaf3eafcfbc1145113226059176e27b", + "source_hash": "72de08511f6fe3b81aef47001cf4a9504b7faf9e21824845125a6fabe700bbec", "target_hash": "8dbd8cb7c7cea31c0ee13306d65104bc16c55f6aa846e864d31259828957ca28", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:52:58.161Z" } } }, @@ -124554,794 +42896,332 @@ "ar": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "70792e661bcdc9e399a63516cafbc391068957f1aa3c75e9813dc41ff8db00ae", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "9d75de6409a8d91269023e24f5eba848f1fd52a1a9a0d968aa5aa10b07e3255f", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "c3dee833b1778ff0ab56340d40ecf210ea6b385f45f56b205f202db2054dc2f9", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "5a2cf158753dec831e863137ae213a79e1be13909674b1c7d4ad6463ea9e8b48", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "3664018ef9ef5851b7d32f067157a19340c7e7df4fd2c2271320c61f11405285", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "25db3053c23d79ec3ed60e29505354c5fa95c5322768ab911aabc0ee6bb14ac7", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "fdd1ead7882c32545c3a3203e823a9fa6d37ee6730b08c597bfa4696c50a5f7a", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "ca9719fb7e572252cdd88dbdc3020b044fc106bef53e958e7e6a02a0f0114ae3", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "83eeee337c473c09e04c6ca4dbe4c42fb60e1dd3e025cf00c0267822eae24217", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "58b62f4e3d0aa1e0bd1a2852cfe11da7dc69e35f3986474342a190c3d2c0d15f", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "2ef8ebf7edc1bdf4362f1d1fec5055937e3dac5542f01d9cc057d787e49a53a1", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "c4a9c12e3f2f90c950cab88635eb25622c2437cf206e520fba3e7b03770b8e4c", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "4c40e0745397e0debc2e5b4a08ce4bf56a4367fbeb600878b477bb8c9e53f8ab", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "754b5017c0b5380dd54c4556b89da351b67fe1dc66931309cdb2ee3779650f0a", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "0d122d6eafd76bcd3eb9bca631a40627d1636671b63a57dede35855a27428a10", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "f69e00d5b92d21b977ef5e776e9b38543766378a0a7fdea394293177bc775a75", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "4147f4540402af087faf0bf9670802f493ff98daea9b39d349f575c3f64a8986", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "b79d60fad4026a97a3821b956a87f246c77696187ef530af33be3ef65c37f0bc", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "2184eab8346b92ff8b74bc0ce91fcb5e871aa4b9b08ca7fee1aec76a62becac1", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "7b292395b531e3c047c986f61117ed667325fa30e0d58059c2e83e4ef90608a7", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "2fe38e95640f79b5267feafa58c73e533bfe5cf0ec6cbf0dddc3995e307e17de", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "e2e63cf1efcd6a3145e6cf05a66d0d17e179c8fd68a339cfe1c62421c7992d41", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "debbabd9b7b1682c37a64575de4956cd04d4fd56a146b43c940a58cd0588d2e1", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "241463ad16f781d27a0a3bb5236d51990e1e687c2b8e7b2472f2b0c1438d3a4e", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "81f55d2745234544098b62c602bbb9b0554e95bd4ad7e91b69ff06cf68d43ec4", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "f36fcf28c012a4846f513dd7f10980beca23406f1b001e0d4d2dab96140f1471", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "d0994c20ef8157da5b75575173529ee712ae28fbdee3a2377bfba6f974e98b3d", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "b6f928a6b904e2d066c24e481efd29d28ab020871fa0edc9244c3e26b3a42d99", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "c98aeffabe457f08a07af13bf9dc6db53fbb13b9bfa262803cfffea7d9fd8c36", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "b5377fa0d7b4d133a7b931288145174391d17fd514296fd7214bfa9145a612ac", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "74395cd9320ca174415558f3672e2d2d1d0eeadc564c21bd0df3478da55ad238", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "5ec2d5b3481cc8897bae38807d55e8f0d1856cc7b4d0607ea2d247b097d7ca1e", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "dad4c2f87f47d44e976d5c238d72a7288c7e8b9e75e7b9414339ea06e732aa1d", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "034530ee01611953c76f115be68e8388251980e2ae58f3e3131ec4dba63da9e0", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "26b31ba07241dcf10163e54679d2a748a86477ccd39d9a5d05cda4f4e80ca800", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "b6df83549ad6542bd73a0672a8cfb1fd73041417316c43191cb1848e44f05829", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "e91b45940f81850c73143a4a64c68da02a45c5c2d19441139adf06b835a0b10e", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "5b4625aad8d6e59fa1dcc8e5feb1f12bc6c253c9cf96ea78ee51faf574cffa50", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "9168c8e5cc415f2156bf8d68f89f7f32eaa70efaf41d6780196eccd038b90ac6", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "14d4ed2b62315a1f4d6887536c141b2642a8a4cef2940542a63e595305480c05", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "c0b802d2f50dfd41945deb8761dde51118b0db585e818f623202feb214dafb37", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "b5c7672f54c2d330cabe93b8d9f61d3a715cad6cbe1702103a097500e364750c", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "333ef5ebd46b0992206a0d24c1804f677c440b064e8fb5825f8c1b63000c7cd3", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "790962e8e526d53740315814f2bc7b42d1ef7aaa29c4ef1437eb0ea3e1e6dfaa", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "304f51958dedf57bea6b5ea1cc9699aec083c6ff3c7a0deeaf5a64e23be04ff9", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "7c0a3fba2323b3846ea3a38eb0210c75a905f1f6ff86b6162e467bb9e7533d86", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "de0332841126594f099d8bce015dde6970191edfea02438e105fb4dc96d66f1c", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "be353ffbb0582b53d17b276d3b58b6c4eef3336eebf28162297fc2b4f2ec1df9", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "cf0cbaf2b797b656ce370291bd5c58a481817b6c0b3d735faef7040b82f0a9c3", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "4cb23a31fadb29e3dec73a21ee3ac20c2c1f6f5625a455109d774acf619f7861", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "4f19362db4376cc50898a553369cea41e3b6804fd59b57442e0ff1afefd9db09", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "5a1a85bceab09a55f7b123bfeece08bdeefa2473d50e4b99c66b09a7d048b6eb", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "d6d37a6670a6bee5498f01d5f2bb3577245e0b47bb32dfcae3f49152b202a9b6", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "155cebc5c5190262398804193f679042fe8abe4b7a7d536471cbf4919fce9c11", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "69a10e5013f04da2ad1e6eb7fbe74f4f5b85fe410e4264b5dbc0f1cc747b5dcc", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "916137b22fab45bf44666711066f8fa2cbaf1a3f1ffc41241c11afc79aa15adf", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "4e04a41722bf10ae643581f0522e54af0cea5730e3495e8ff03f98e4758af0d9", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "4912cdec38e7c35d945d2139123d34958cadd245c644aaed3791b1d7dae2d4ed", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "66d1a6602e2223229267d47ef1f5357d6fedb38df72f8c4596c68b0152cfe0f3", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "5a4ff10040e5f2f5ad418374f1c960132438fafac45ef90f4b32683e6f9fb32f", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "fc0b82f5113244a5bd376253343488acb305680b00d77bf7c6108da7c538de5e", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "da962e145bdd2648941ddff8adafe882b869a69ed1ec41589ad0176fd1557fe6", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "c1898c08030e616504275e8dd6e7c4aafab8283f9b411a5d8c5bc4e019c8dc42", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "ca431809eaa64b4ed6fd54c461794af5cdb88a30bd39ae1ee117b6d5440574d2", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "03d30f10f029fab6c95ede78f5f061a45fdc9a8a58f5197f97c9b22992c82450", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "53fae81597c09fb2d6b607736dac18a62886c13c2afa96d711d329e89a27ceb5", "target_hash": "66da9b35b97944dc4b66b984a05295099dd2767fbfbdc17e5f20b96b3c7a92b6", - "section_hashes": [ - "9fed48e35e32", - "6b1a0664f0bc", - "158a0a7cf0b5", - "e5bb27e3a220", - "47577ebe8144" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -125351,1437 +43231,332 @@ "pl": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "7ab1867332b6aac8f35f764a5c94810b5d3fdc549ef08c4c258038218941ab50", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "d17f510b835a94c460952f5ada1cf084a0ca2349b80f734c21851a4edd541c09", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "16934f6a8554fce2750f52a53647fdaba413fd71c69488632e4f5cd151290716", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "abe890dddb5ac955e635af837677f00bbbcb83e4610e9a780400e9161484116d", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "45a50aff91760bc0cab12163525704e18ca229f3ae4e8e267e8d5d8b833e2eeb", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "e82843147919b1b8a860cf49dcb70510a7719d7e7ada3ee00d2c5da194985f27", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "3214d8b10c2c6066a4b74fc0ac8e3d272c382a84ecfe7b8db5752b98a8b87a61", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "9d7de9e648704c1915d3f037cd7d66876798d886707695a2031477ab8d0ef25b", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "a6394b67f6fc475491c875aa2bae9494cd3ea31e4414df3eea364543d17194ba", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "2431f1c3bda3e220bec0fd99dcf8b0a0ee627b78f7b108ebd7cd1b1bece5595a", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "dd4ffb9c0066fab58984c43aeb333595f3f8d0a8b01d88582c60bfc787ea9eef", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "76b0817e616fa198dcfba88def75d6c3c1f53c4a085a3a6baec803d7edef8ebe", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "20ca065702902cffcfc97c0fe9849b479641085dbfc9cbe6a9fcb217491a9ba4", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "81ddf18ea0548a49f7ebe8e6d4685284256c4a5dfb37e9c6a1c571746360668a", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "5655e06e1adc342bd4894df7c1b7a79ce88e649bb4f3470c90ec32e7a683240c", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "2c8fd1bf792631206a35027ee2c341c521d86caf42974cc1610c9616f37a6bc1", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "7257fd307f241e1ac7dbc6f83c8cfa89d9c0b5137b2ddf88bc5d0ed3de639335", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "3032a654619e9c76830d4dfb69aad71fdcb683a6d4712ccbb5ec1e7bc5fcc750", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "dedf69a7a311c1ecbfe2aabdeac1ba868abe3602c157bbf009b41b513af975b3", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "43d35050ea76ba77a82b5cd7e795292079f2249498568e197dc434a171ab272b", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "a346c98fae7a607400164696ec4e4ea581d9a2a4acf3672189e4fd5a905769f4", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "470b0aac8cb0f68697d7bb268db5e4985b2636a349b15e55c8caf6b53f7b77ff", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "3e6b86ad97631d081c014eae57aaf885b163338a444906745839b337ea1e4dd5", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "d4f6adf3523cb182ebf4b3cc500aa3cfa11bbbc69c92d5566f8384b3f85cd06c", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "dab544d579c53587432e604125d97513d255abe9cb18bb975478915b92ec6348", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "91fe3d343d8f2b6fc1179b1f93bfb607309be0bedcee0e0869d66f90dc8e6344", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "7c598771384e66136be7aa1c421c14ecbe782e973cf8b35700a633497b76a6b9", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "e5402ed9234bdea08d77269df55b7ed03db384920c57d4a6df5468d3ec8ba149", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "61fa1bc89998a4c84a3835ad90c51c87b481bded348a0ac027aa2d6d566e74b9", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "d8026be4bc48abd3affe64f5a954f61e8a6b557c98e160575c64a474789baa7b", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "6d3d7c7e063a42e671b5acdc65167a9c7b084276155fc84d5a8dcf05a6b533f1", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "128537f7ffa246a3767684155679cf2f6defcc9cbfd0db0b05fdd18651350022", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "f41c993935a7f7ec47cf23ff4d92f631f3fb2337673748fa682b0940070b87c2", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "c15331475de80e477246424b6e70026cfd622cc435752633ec9e1205a860fd49", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "ee6c746ca06fec7205b515b84ba9e4adc007ce7f3843e3b0f3babac50cfb2d29", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "71ba8aaff8e64ee9dba0949426dc539f73389c30574897a4448a94496e580421", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "4d3f895f12446be9bee0a2871c75c73294dbc9ef153a39bf9f636d065a877687", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "23ab0b0e1369882668feea02eb0cc3f4545e2325dce0e7d155ae7e24b5d7743b", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "112cd9c58218ec9b5e35d6950ef848b6c95221f9bbf125cb605e2b3b0b6825cc", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "a418e7d31a1f40f613d5a06fb7abe0edd94b5dfaf7f06ed2b5bf845e8f06bf12", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "27b93c17cf9fa4147325a6f0947cc959df011d1396aaa3036b30b030d314b9ac", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "2cf6180e2cf6a290bd05e26ed6d99f17ca99d5967d85c71d9501b1f95ab0bf01", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "494a98e643dd76f72aa6768142bd7666124761b5e6709b38e618df5fe3e33599", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "8b0b1a18c1972f482d1627369b0baf56c00a878cbb6e8427569548b480e24b98", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "6f97dcf1089c0a5cd01fdcb8fcede03311e8412340db2c75c02fa305106085a4", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "414d99f9525eaa5c37c96e72913f74287d5ea435faeee4663a3552127cecab52", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "f73974cdf3f999994dde595ef4464e3fd1635716ea4949e2d77434be49c55f7a", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "4e8e15b12fced289e15d55bbc60ba538e9feeddb730e7ccf433e09ca8d070168", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "6943442fb7c4fb4ded2aa1ca680f3dba7991ec97fa21086a974c3c50a0d47ef4", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "6ff5fec24c2445b87940b66794038cd67c073b0b0439daf06b9048fdf5204361", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "f365ed4395002eebad4b4c3bbd09432f9e04a1365387fcf5b576277585040a0c", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "4aebd485241dc6501b5c120a743033486f8ca1dd65b4b3a74f1b12185e43eff3", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "03ff1c3e7c170744b979ca7d938b161640db7763f9bc150fdb42f954da3111dd", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "e09d367075208151b109b4119f4bbf79843e465e03beaca23df00878e52e3945", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "640f3a1bd3c817d36b9216c0c8b6f8ee343dc4ba80425a0b9e0350a340977f4e", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "6dd81db0994cefca2445f08c9600baa3e03d65487a702b33570a50325c1f8969", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "7dd56960d41d6c8ec81e1e5d0e438fda2ec81ff5ac085d43fb8a560784557354", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "0edbc9a45f1cf5285a75dc7a9d3954564ad7f01877eccfec1eee40648fe5a235", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "dc5d4daf914bdd069a8e2735bd8a0fb836eed91ae92ac68add9679ec62ecba64", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "0146c0fcb5fc2d61a96f03167428052b4cbd6f80cc5c1e2fbbb78a9e2716aaaa", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "6a322905a8e7a4081157e8d720a9b0a004a80bcd3f362afde7a18a005ebffbba", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "bc74538480c6c7adebf4214531ba8e6bea12e183e208ef2663322f5de2544dc1", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "c90b91725d95765864ca11333e2b5b07a63258a8a632d00b7e580dbeda600a3f", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "a80a7e44b8abb8e827cc678549ea7e239087ce7eb15712f8a685a12571f316af", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "d00436725691b2ad97ff466005f43017f67ee341515bc7b04b5f39bc26863a11", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "3cb723ac2fb0c041c4008e91c7f5125d18541bfe494c3464018d240d559caae6", "target_hash": "bd3e7ba5aeb6221db04c06632b58a2600bf8f7d4ae7b6bf0f5d601b1051b704f", - "section_hashes": [ - "880317fb6aa8", - "45214c3653bd", - "19f49c02468f", - "1572797c7f85", - "638ee8b5a839", - "675d38bf0133", - "07dfb8042ac7", - "733a6d34af19", - "6fee320f8c2f", - "3c3d1da1ca6d", - "485afa906402", - "3e098664605e", - "b79e442ce365", - "7b06e46a3a12", - "2eddb7dc88cb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -126791,1047 +43566,332 @@ "pl": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "6631a237ed9fbc2714f22e1bd00fe08987fe183445e338be73750b3de9756726", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "fefe0929b9278f453b6c5b56ab5c97e24fa90ce0cad45085d518eb437ba99d66", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "7fa5a552c55421ae1b3d7fcef20c01cb9045970e77de7793e6705fc5ce74d317", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "03094917e9076df88d00e50b773241da6b08b3be3d7ff9b92af815fe07d437d6", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "7c141f1e53e1c6e9bf18a7bfc13563d917e0d3ea9386d6230b2126a8fb9310db", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "6d9207efd5720d7c877093bda05ed1b87791b50231a70172be4c15264932fc5c", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "19582714dda9f22a2f533ed505a63d5eb9c6b53904fa06b742398fad3d8e016d", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "17ca2171b2521f3aee271e1a5db0568a071e8f8f4eb76d4750fe1822f691afa9", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "8ffc0c4785bbc36116ac9cb0f9661624b2b41f629b371560b4d5995fac28674c", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "65189c63e4768623c482265247cc02a5d12f494a21be103449e514d4439a6e48", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "7975efc69afb6a42b0d72af169a20b36962bb3f57bf0807f1e0f02fc2edf2f5c", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "e22d609736059046372bac52b88a47e88da43ae7e0199a2a70823321b3b1d1d6", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "811be09cc2358ee94b8269ae98ad428d7ed0fa0369452cdc9c17bab327f7eb0e", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "e57ed01ca6af6029f8503228361f3f546ef4f52f4a94f8901876cbad5f8a962b", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "a3305bb3e8cf420da97917e264d00cf4b9df6b6f36fed438bc579d68330cce8c", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "e28add2b9c7074c50ff9dcd37a7d3c883060a5534757fcdc69b3571aa8d24775", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "a251afe16758e7947f2fac7782872ef48a0cd36199e4a50f7bf1126e6a348089", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "5b1ef6f3f0e917af0f0bf28f1c7c95a2addfd6cfdb00d06de73ed283b232c0ca", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "fcb46766246b827651d90298bb92d34a90aa3b34c0033135031063f45103d4c5", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "1df2a83fa49303ea26eb5c6abb972257426dcc01807c5a10a964d569b8deb0bf", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "98f85d5ddf576c99f3ad6f1639fb9c61bac8b9a0b82314f865d248d5eef53579", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "94614f4ddc2976b212fdbe9c7dcb5f660f755b1ee099c92cc53b3c9d955d9c12", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "85e719590e81f79a1a18e886bc4ef7f732dfec5a995e99b3083df196b3430907", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "c1aad38d89accacccf5bdf2903d3d80663084d1703ae97b216c0224ed0a564db", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "98351b9c323fe41cc0ba42ae764ac37b5841c3ac2639ef673c10de07e43e3153", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "21f76af547ab1740cb36a29db946bbb03e52ca1a5fb8ffa84f4f4ee66f280862", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "dbfa5452066fa09209b48a08e5600bb35c76019bacc6cf779f0c4da31cd8f44f", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "fc965d074f5952fb33a14483f98b2b0f9b02cb1a1e2f90f23839fa62741aa81e", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "639f9a283818bf90c184346981d29cd011239f9c8049192209da3e058dfbd822", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "371af31a8afc47ebd1c03fffc4e346e53723ce4765fef9d12cefbd697edcc576", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "51ae2264d7610d1b99617b91b9904b27762270c2af23f230cd9279f4dedad4ad", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "b49adba3a289b879892f98ef1d1e59edcfd04092b10e96c08bac49fa164cb60c", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "d3c4bf1618db40ef782a399d537fcd8e4a4904382eb01e9bc904c9d85ecd2b48", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "fa6ae67360bbdd847ba84ab832c8ff13921331fc5be58b5f4149006d91470fd2", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "04b6dfd4e1d23cdda3b9a4e228910d44ec21598e401004fc2fea87522341f40e", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "b213631ce98e20f569475ab3a8dcce64693dbd5d122c45750fcaa7bb65eb6912", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "ac286efdf43c13eb41fd7c1257ad4df64e14af8b5bf2ca2b1bb3dd0348deafd8", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "2f0251474f8c47a3601f7a5f21fc69e37f9b2bb8a670d2dde893498828d80aac", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "ba995da3f95f47979106e55b47f92944639c350c8ab61ca14cf387b63d4985f9", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "ec39482bd701af6a6d9d4ee4158fc03da9109433b8f650346bbe9329769a5c0a", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "6eeb85d7d6d8a883b0eaa126078d469285e8e6a7726c1b6f056acaab26d0c006", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "8ed0afdfb3008b04ede476ff26737bdb942148ee4de176385750adddabec9953", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "9ec740284c96f96e3edc727fc0f883bedec14e1501cdabefa20c6cdb3da997de", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "e118811e7869160fd76f2ee64560b6b7109f18f7d2660d1c529424c2186637af", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "df0da8251d05d8f0879dc25c03ac911ce72e3418f0c61f8b53d2235d087db89f", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "2c20b81351198b2611aa3a24e420458ca6bd15709f40b62e96814f33446dc962", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "15dc00fbfdd88e1872992e71382f0df612dda1b9c3d0c02a25fe4002c695a3d2", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "888c35f965e0c19c5b929a2284b0ec8e2f03b74e8ceadb754855b5fe020e67d9", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "dcd7b3295375b390aee236fecc150c4935c7aff8b4b25406362b95f546a8be32", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "1a8ffd1121d67243290a7eaf3685bba264fab3017ebd8645e5f2b60637b48b89", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "8e55080bf69ab1c202af6d6221a8c4dd32329c84ed3b91e8f3045afb2cdf83fb", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "09fcccae16d8c340dc389f25e800497c3542d5cb8deb782e45024abfece891f2", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "5836c633ec29003fcd143e599aba89ba7ccbc37063833829f70ae15ddfd8c33b", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "7684e346b2254ddc42ef7ecb0e82a6858a0f5562e2050f879a12aabb9881fdf5", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "f2176599d47106ea88e2498262f4bb52ec3cf514b6ccbf7e22f3e238287feb21", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "2ffbbe9bacba241c2eb7de43eb648752ef3575937d1c6a06b25f107d26bf8f69", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "78635673571787133acf5887477a0f2faa63534b5d4d3535a8a1a8f24b94ce4e", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "e961aa4d46b4a4825a1e7a0f4988ee6318fd8cfe7c194ce2b1d916c6d759ebc8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "c59cd8a77b0418a6f849aa1378b217d1169b92e3700fae48f837abce2cc3af6b", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "38a120d6e88f7e23f63a71ed516983ca4ad1230e79aeb8d8015e08a6375afc96", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "5a04c1b2fe0ab585497424c9d4c1724bd0a05625f04f4693572a66452df162f4", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "e8a97c508833c891d953773468a87c6066bcc47a572bb3420bd899bbf30fa447", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "6f3c661806ce2215f07fd92ccaa07d7d59aa08c27dea52a70780d1d8985060c9", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "6b80d1a7fb3d18934ef9fb941fdb7c5d92573d4af9e7c491de7d1591298ed004", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "a56461e22eab4cf2a32eb36f47f035ad6fba43ab97c5f3005a23f9b745073ed5", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "a3806ced11d379777af89907f1aac3f3d3b6a1b2efb34b846fc74e8c78185dd4", "target_hash": "cc7c88c27fc8753923f74077f54a3d18d832d0e261ecc59427468b8f90af6ecf", - "section_hashes": [ - "b6f55d28f096", - "84bdfaef9ad8", - "30f6d644a64b", - "342dfd21a627", - "f085aa104fc7", - "2c085e9ca501", - "153a783e8d64", - "06646cbc800f", - "7acad1ac329e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -127841,852 +43901,332 @@ "pl": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "2348e1024854cc83f1bb077a0ca23bb5b2ccb75b035568fe38fd5b3cf154159f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "62df6f548e75dbbe846fed93ebbcf41be97e31833963a1d428f7233781e4bd2c", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "c264d398bbab6e694d43e40a8272400641fdc6e97fa0cbc5a94e75139cfefca6", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "b6a9b7759b9c4d4d7137d46f7fa2276c2a20e987474bec511d80d79e9fc228ff", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "0b60f3d2f7db6fb6f6174415f8ce16f0cb1f35f4ab377b163febcb4fa5f1312b", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "5cdd6e67c1fcf7e8e09b011ca4ea033cd34c8375b645a9fa800bfe5aa2b7babb", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "145ca1fe43de712eea3630d23e7e54953f56af36327db43108a82ba987f40243", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "16a895acb79e645e84497298f984498a89ede2da1c39abe43ea9afcfc6c19d36", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "48a226732c1f72511cde1001175e11728729c0aee60bb422b52e4c864e7a6e59", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "e4c322d13467cbd558c7fa8ce663a60c6fa47560ec0bc34453988b8b0ab59b17", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "7dd04268a591bfdf3fcf337145ab7a055f9de8ab33b8a2905397a4d20cae65e1", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "1c6165813ca6d1cb2ca3b1e291be8d982efe11096acff45f8ce61649963b4749", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "e372dcdf27d7a4270cb279d887a94d9773cff8cbc96d7c52990fdcc65a8c8fbe", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "7558547bdcbd36e37736b12b54312bb65153e35442d04645078396b6c0166e6d", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "4adbd012a33f85851357e7b9195f68e8d485061d9cf93d029cfc308a2f9d6bd6", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "cb107b32b14af4aa321966228723dfdefd1a9f9b7cfdb8ff9416adb54d90ffa3", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "aad4945a4158a1eb704c80f325b9f540b912486768156125dbd485164358fea6", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "0c00aafe1075c4a551c1eec6d5b57f4c5ed2afe5d74fa7ecd9f3863569229f33", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "ed3ebaa366f31e6627762ed6ad443cd72b261a32475bc74f145926d057b0a528", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "6854c61c3e9a7bc9ba1df92c3c5f991be9c1a36a46db3b4341057b64cfa5d154", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "8b32aca870a2b10ed88d45e5851a7e624c33fdd6b4fda169f8fd3cca371a9ead", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "078fe0fdcd18bfb7acf51c6c3e92bda07cae78059a57116f61d8c5323a2f87f3", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "5e5ef72d3a9c317f95bcd3020de27bd48d8529b8d5f6cd9402d6a6cea9cc2dfa", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "49c3c598b6ca64276eba8442e20d8b8947926e1d46dba25086fabf6d0af8360b", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "39e4b251cdcbe1dec60289b61d32e42eebe4f8c7d2abd902809dd8b6dbb9e0e5", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "ce7cbfdf8d2e5f31709f2f878e427982c8a889498d9ae2a9c9c5fa844ad3dfaa", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "3a1afa64b58809025f3bd6dc6013245bb1abed84b20bac4aa33139392b8949e0", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "8825c02242bf6553d961bbe618f18470edc18d10e3448ecced1adefe42b3736e", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "e7e080245cd4e3f532bf4fa029d7c7c74ec32d456f2ceb77f9508e06c899c1c5", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "7cb7572f3c737668ae34c8cc1bc6f749fb5fd3940e590f63ede589e671d4067a", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "4f483e2f4f90fa48f06f1b63171c24d7b4f4feb5146315be765dca3b5b68bb9c", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "0efde9073f11c35db85d2065ec8ac2540a3c183babf170331ab5f91f45f90e02", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "8ed743f1ac16568eef120a705269618f6ffcf43de71fb8624ecbd037cca00c0b", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "b159612e569866cc68e997f9c7417a381a9f9b2ad72de892cd6ea5cd5c9ccd86", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "c5917f893e5e5b4d56c37cdd421b961291f8858ee91c5ca2996dc5f0fdba92be", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "a76043eaa45e405557ba8f0b826a541f22d730126d1b385af1a8d93eb20caada", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "7fd7ff6e1464250c6d0e37e43a4ea8ee7998de488b2dcea5f39b602408b31fe3", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "bd3eb6f44e0e9e995f3abb372f36dbd1d5c9fbce2381669f1ced7457fda36a9b", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "4ebc2713e2c1b36912d3dc0e5e9f36710538ff4651f42a36e50fa01767045f87", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "e511a2bfd56d6ee039489568223726d11af29eff1f7f00653ff8428132087a61", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "9c53c8a4ebf914104c9b56e545267ec02ffbeb745b05be78cbffeb4146173d07", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "8f9eee211bf7dad0fed7e33b81c560cc27282e287033334f33bcee5dc42ada4e", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "895b42a021ae6d57e12349c2c741042e9a25da2dfa93b0ea70efe09cc198a4e3", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "7d60e36975b0167a25273c3fa96847da3c0a6076927df1b7e2de13a208ca2c6c", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "d1831207041623126e3d2dc0da431cc402efe6feaf1563b1bea4a07cfff90f8d", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "83c7a31873a2d18645680f032e4f53ca7284630fe53ac739ec0bd9dc387a62d8", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "e587aabbe56fb99cb952b4c1540de177bd13e785684b073d6b5ea58db5d41dd2", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "3c27436285adaa704318f42e7cdbe98b18eeeb37f4e11eb1f42f9cec2542a22d", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "341f2a17c08c40f93be0a20fd139d8c902d4ab1fc81b58e5123c4c48d22a4441", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "2ec6567791d5074545fa994f3a27e02193420be55db810eb570e1158d0a38596", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "3e6e32df1d0c70e3598ddea51c8738f20f285a65ae88941b39a4f3be12c3680d", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "bc042ad4609cc7e72f3263e86177d44e8573b7c65d59f59cb67543082ca42883", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "ac2e04a1408f7f1c07316e44307da74e3cf516a0844cce320614e5a2ddd4fe61", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "2ce13581245a6ae705f4fb9fbc1f35863bfe146ce64f70aebd1b18f7ff971131", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "c13e87a95995edff84c021df5adc126d1b6619e3b17f97b5f568c0c5bcba0b8c", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "b4bf2310cd7587109b95c5f495926c2fe5094de43429c0a7a429fd74362ffbb3", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "f4f4bf0d0c850597896731dd194bb83e430820a6ba40336bf9789fb970ab9449", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "fabcdd18f766309167000edfbf0e81d19b0a2d9b578c076158241a5393d16d22", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "1a19542554b281dbb0867b34d763436bef6d2b176e79a2179a3233b4e74c1c6a", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "01b0ebe15cc446470dba99bae49c94a9321836578bb8e6508f83bba39c15b240", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "de8f29a6bf412f8e0ae4ea4feeb2ea2eb98f3ff8517992bdab4ede470e7a80b8", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "0c1da592af47659cf36ea02c19cdd72aa088d801468c5980370f15c41cca286f", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "9b66b699a6c4229024b7e7c677d4910e039b846d1186c439712f39914d90df30", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "d405a4c13b3e2f8d20620c0c7f039f79260dbd78beccd63cc6ebf5b754f22eab", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "2cedab8900a1904838ce05f9f91bb6c1339d35c9afe24576473b232fe43eefd9", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "6ecb87e3fba360aa0ab8d663d61d45f4f87c2a43a9dd198ce4a13d80c0f82357", "target_hash": "aa4a938d5e0eac50d58545c0868623e4dd7be055defd8c6c1476df0ec95de0a9", - "section_hashes": [ - "a1694ade14b8", - "e950acce4106", - "ae9892c74f5e", - "e2ebeb058b29", - "073aba1471fb", - "5bc103a19c8a" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -128696,992 +44236,332 @@ "ar": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "38c0b63902cb3432dd1673d104756aa6ed658d9a128772fac1d9f54d6c918a37", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "b5214132fd1cdce7a5dd2c59fc889754e4e993959c24450f5ea5555374b1c4d4", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "be836935205679413b397f276bfae3eb0094356a6f6dca7f389ef7307586b166", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "d261f533235a671253c05c2400aadeb2abf713f73d2ee85b4e5026a97b19ecae", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "46b287f2d2c93237633cf7404679adcf507e0186617f95b558011e3174fff32c", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "a2f87fdee56ce26ce37fd8bb15d0f51ce2e0e882e42be4d508f1730df0277cf6", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "6db453f741fe70484d503f45405bc1742ea8165cc062b130a097247f25edad86", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "4d3e7777d373ab61ee06ae485b481e1fe4b7c9cbd733bd41c665147b69b365c3", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "48ae37ad165f030b7fb8323b5c43ea0ab68c6c820ae95ea11b5d6ccbce64ed2a", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "e9503ab5287b24165ab7b2ddc04fb7d9cdfc7bc6d68ae937fc99ece2e0297615", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "80b855504e18ac17d68574d2756bd8b097d835845febd354d7a143bfa90fccb5", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "556eab79c61457ad2bb13c2eab88a42734c36ca26395e68976dd5641399c82e3", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "9297bc79a6652e63cd30e28c51e60593719fc1cc064e928f04645e2750dfde91", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "d6c90352611dda22b4a66d63c7ea2b8350cbb1bf36559263154bbb11004335ff", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "86d8ae6f387f9d1976d31e554b0d5326e7b2b8bedb0c04843c2451ae0ccf5de2", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "99545c391d6de448a15af03d88e9476b3a7673f95acb19834f9b8d322ded91e0", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "67c95779c10c136f7d86ef0abc3004c6979aacfbcbab289cc5d5b7820b6650fa", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "049f10334f0789933bf244444eea2b40af2314d3fff9cf8cf92190bc61788cc1", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "1ab90ec114e98226ae2f6213ceb5e1f377d82cf6a1e286d27a0432a318cd65df", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "4423632f8b40216460f133b3866201399f60d759f10265c9e28c3936a8bab484", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "40ac97077a80558089f5a6f4a659a5d31a55717568e85cee7661b5880a9054ba", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "5c6c8ac639b007fb9967f702ba82f91c353159d44f9e9c387edafe8aff40fad5", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "4cc29151f9b49e49e191b1f650ec4f21d10d2a3241b47a1124a4999cd8aa3bda", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "0ea42c3a49a61579419c98e31fb8ae13fc89bbf8cf934299a337a6aabe0661db", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "5e9171693340da6d5e3fa2e6b699c8b65e0cf09a78605bf24e5abefdf2a726d3", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "81e36356d4f6f6551984e908d9fb6af6e6cbbfce56bf7680cd662610e29c6af6", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "b24aa259d85d1c3cfe6f2e2487353df5b3b74203d2e45e191aad46243d087d6c", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "e38247744496caf97e10ef8f77970e5e458c8fba3d1c8cb328c18224dc423613", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "7ce3c30cd2218fc3a6a8898342bf1d42e15f051d8e11571222708f4b8445906b", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "8e3a1d29ff11af480bb9995eb4fd2a5aa265521fe721b4e875f77fb29b97a39e", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "c105fdde1ea4401292ad6754e054b078b5eee4e52dbc1f2ea7e7a89804260576", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "f1abb637aa44f4fdfc207e42afa9395d96e06b799123023ce1996f8ba6b5950d", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "c0d26ed4c228377700f5812b5bd6df176734285e5d0ba8eb3ca8bd38cad1a5c0", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "997be5ebd8f54c7f4bc4eeb8567df87d139c0f76bcfa2e0d49dcbae3e3bcb752", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "3bb257c5de028b712035d4bc8cb4a34104954a21724c107b234c7f48da2e129f", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "5fcb548230f2456cb05ea80f8769bbdfd42e170ce6dc05a79959e834d1b0a88d", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "819935af29c865a8539a813c27da7bc7ee428117e3270060054c4e78da5647e0", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "ee9f81dcd0ba43cb05e4d8b379f9cb9cd4781a74ec8ee0e240c035a49156c245", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "d3b366be76e8620295792bc52b6d6cbc20a11060314f936a4458fba959ec0830", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "f5c9df03a1f75bc7c8a6463e635a0f991eac22888064cf265bfedc28a00d1d71", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "eff716be94ef2103ec992c325752064ec0efe007399684465aa7135377714d93", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "63abf98b02c50aace9c7b9ead2aa30be01c45dab71482f2a3767721f0af3852c", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "6443e21fe7aea645057f3c3b539446c7a0b8ae0e9d1d05a2bb8ee7f98dddb8dc", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "1058895ddae68cda0625e7a50bc897d1452890827d83a94acf9b703c31838444", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "f49d65827a1c2689b75379191b84950e0cbba2b06113af114855d0c855dd6886", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "7018fb3e48ab47b4bb05e7a240738f1fc2550e1dfbeb795cb7467c36c94fe0aa", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "464d35207621a5341002e04505488392d40b555c8d8531846d4d20543508156e", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "b175bbcb29eec5c263dcb0c208d1ae62ec7c0d5043786c3770d9af3d4a127f3a", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "a62e6168e08ee29fe6635e0c38dab5efb8b1cacc860783e03dd4e2cbe57698ad", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "984e4d4cddfaedcc2f419e89eaedf38df89e1af7d0906e94879b21c493c2fa16", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "6538d7f7afc735cbe4eb1b00083c2d3a226889f899a660e563e5801074a84eb4", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "f1fa9330bf513d19a19f12a20494975e3c33aea999ed3013af141c7a66015e6b", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "33fd2d5b28237d07eeff2ec7c0193c8ab0b78ef020b003e4b1fb491d67a9a739", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "415f82609d1e810ef86a772722b300f11cb98b64423893202f99c16287fd1a14", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "07657556322d3d5a23179ca71e28f72089f1074781bfb01bc2ab4dc7bb1ec88a", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "717b3b40863671570dfde7afee45cab26c94e76da0f9fb69d72fa562f8a3f41c", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "19904972041578bcf75d2df578e1f5faa674016065356252758b040f8affc70c", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "c2ff993339d4e18f6665bb7dc007ac6468eb63913693f329b141407b1c0a5599", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "c7c78d84b9a2952c75f2466915f35cecb20e2b2dab6b0331249a2b51b6c72d8a", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "273de4cf2cda4f6a0c3c313f6ed78c83b69769390f1709ac3eafd6d2d1bd7bf1", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "d80da46ca6af190869c81ff902941cecb980341683995fd4c18a16c3fa88ebd6", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "73439abb3259dc2b828a8fbb7f7dfd90e2470a84027f9cd0292294c8f22a79cd", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "c3301e47d2712bc0357ddf4c48bca4d237f1ee126b18af7e8f1d27a3fe091a60", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "4cba541e48932968d4060a10fd5e8ff6f99e0ae250dc7e1b6c4e3a2eea2f6a02", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "7ecdd47581403e99aae6811ad32be8c9b7e6c9c218c3890a3fadb60845a2a4a6", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "0dfc46b285a6839d1603fe85a3179b085cf0cf510d67d2af1921f12cd31bbaff", "target_hash": "5cc7fd664ff43c397682494a6e8694c80261861b7c557d0b3733f7b177447b9f", - "section_hashes": [ - "408752ed376a", - "9217559d3db6", - "4d337fd79ddd", - "1fdfa659abf6", - "dd23bd4a7f89", - "337a2a283235", - "e0901cc271d6", - "da32b48282a6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -129691,1124 +44571,332 @@ "ar": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "d3d2ddb256b40af3b9dcd89a308dc88c6e4d46155875e24e15cb104a772f92cf", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "fa49142d5d70a83f46011e361ad80777539da84fb1c37d983c6ff41a963649a8", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "df751a477d1d1df1c95e3e2793901feed588f579c2aa564349fb5ecbefc3563f", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "4e944f1c051db4d7ddf774b5a36b2ae1415e00c2d23a125a9760dd359c81fcb0", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "8faf778587ec48c59c1a219c4286076d5d9314bcf2b3d83cd0c89fc406edd48e", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "ea1493623d88bd7e5cb82c2d44083413f7246a8e808a872d719d189f684f34f4", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "462420fe4c5aa4b0f4692b59c80b64a902818e70e3f9c025e0c6868c1f911e92", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "c2e4b6ce346e544bba849aa2acf474fca41ae77f8006be01c02db3dc2890dd5a", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "92a21ca01fdaadeb65cd1145d5a658e1986adaef652626545aa9d819c938be3c", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "42e5df2e99b4e84341c72381e4a0f8d05941e6a3ebf860b40f850dd9f1646de2", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "71eb8b93cb9dba4c2234aef777e4bfb480d03ce77d16ffbbaf06f47b72c96c4b", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "2606872e9d9807abf1339b721351ee60599eac33257c7b00b347b669a479e3af", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "3cbd1b3d4fae961a73a495b11378b5a83d6964767e057d8208ad9240d978ae0e", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "54ab2f95658f90498ed9ca1e86e9e73ceb06831b175269913be6168582a6b2c9", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "bd99b96dff8983a0cadb83a7886edc770ce3fcd4adc26a2a8e556ece930fe6f8", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "3ef9355d32a6771c93b29eb4e9649dd78218f239b2e1dd18606504e405b81342", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "3e2ce9df778f4c20c1d4173af6ec1450a5127ac8032bd923497a2cc71959bec6", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "b383b6d498d9f31e20ad3196d7e89bf219a00af0ebc375c0e0c5b29c7a6a914d", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "0306bfe9aabb323e9247b0177c92dadee3e46bea63ff698890535fc7d4e257ea", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "67d9eb3701adae523145006c5ed173c54be6f4db222f7b95a43bd5046d1bd719", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "5ebaed5d48a53c10310a26fa00231239d8e2202b44ea5ae27c032c1896cc9b3e", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "f18e1e79bb3f3401429d9372300c7166889e93053b4c53691358587a63ea7c17", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "518b96c7f75a8c0db85112c3a8c88d45032b7c323e5feed6cc371f5e3f0b64b8", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "21fbbdb54e03b8f16515ed772ea942e4dd712a52cbb6f4a2a4709a62c0d78709", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "1424ae03a47aab5c3ad82f863092117dbf6ab04cc1fbb5aa5c8fa87b6e0908f2", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "7606a35ce9aa4a104e0083100d144421bb5d26b5fcc932123983c2d665d4acdf", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "0b5da21d6ebbac88e3d7600ca9c69e5e190ddcaef9b98e5bd9b96e59a0145771", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "aa87b7e10e0900322314b5ac7e5836a5b24cd07eb3999c0de4604702466476e8", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "cb76b0b77677bffd886f0bf512e20491791b95c4a21d889e8f8f951a5e923227", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "6b99447a65eb728d912a21ab372d11e623bb9f5336f070bc7254d34601cd1017", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "e62563b4bc27a533abc1205abd1130f491f1c6f9fcfa701b5a7377b7e87db337", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "ea3844da1d40fe06b0caa012e5c08f62f6ce45d2da6737f7f7498ab67b07ddd6", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "96f5155eb4cc0a840ae604e942b11b9a19596d2737c3306386c818a573cba68f", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "1e0dc0112ca51e596ebef4827e152fa23f65e903716c8b2063fc0ec6e1189b7c", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "1da7c839fa10ee841f10e3070f07e536199d3591526fa7cba8029abeb5fa4f18", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "dfbd214f75a599e02c08d2671f7d435bcd69254f25a06a2ce7b1544c4baf8486", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "dc93cde01b3ac4340b3aaeecff4bbd3dcf7d442470f8c3996950f78ab16dc585", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "1f0bec712b3e3a27ede719b238a9cac209ceeeed67499b6d8cf60dc72028c3ba", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "8e6786d123d2cc26f7a146c54e393f0c1b3f8215e1625b90fb71568d5ff39669", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "f69f83fbc12657cbe5bc6d03af5a91d1e8bb9c68e58dc8a67cdd886b7c763d13", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "9084a59f16dd8080cf530b4957778581df64985c14a2d12af5516f8a13204ca5", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "551d23f6f4df092565715a1e602487710fb7f8a4dfc92ee831724c74ab56eab4", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "9090e77362d8d30df3c0b2b7ff24865ba6db74b3a0260ae3c223f7ddf18c7651", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "db7dcebb635bd1e6fde13c672206c833a95fbd37a8f4238cc125652c02077108", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "9f7fa2bab42ffd21f12deaf72c5c2e5a52284952425dfb7e4ff4a4b02a815a13", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "ef157a5d2afd15a293fdcddf79290b55e978a60fe8eb53eea20f3d11731cd3e1", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "b94566b6c5898435c22d63267401073cc2fe69d185259e4cd299ecafcf256f5f", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "f394911338eab7ddd005410449a47461aec8245a41b84e13fa5d6aa9af9c52bc", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "d9df305b0cb888e049a11c174c6b148da2d8f0c0075484f2170692359fa80581", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "a97a47e8c75c24bd729de881bed39f0b5e72561508443b6140eafc5d7a508884", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "2af75133a3b53419c3b729038897c3a2a04401bb9e91ec69b438b44290453e2a", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "ce119edbc247f8ba7a5e11f5763e059fae6e28f5180c86a112bd022a8b6c43b1", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "b667d86e54771191e02ffe6824cb520501d38e1c04e96a9c5cee4440fcf6b752", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "bb9ac0e77bb73c3801b67fc4c95b721fd02e6843d4696600f20851827cba2d6f", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "e8efc6a332c64067a35235f30658afed2cbbd0768ffba94ad959b9550bc63685", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "a9f9e73c4a2be2ff6821d9ea7d7a66d5eaa4b61d737ff2a50c3dac2cc9644470", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "5fbca3c7f883c2862d3f0c0a0df86c4ec509a680dbcb01d4332b7eb4437def57", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "6ff758e0b1a11923e03f4f9bec2812ba42f88091e721316e9844c8a13a312627", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "ae74d3383403d1c139a2122ed0dd4acfafb2e14b367b789b6c5ffca839f6f7c2", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "f294f2bd63e5828980ab0428345a62be30ae8eb3488cee09b2a1fa70869d95a3", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "f3f6f6cc45159db3f70c7745066c8b375d75e2b18ce2908d0da63f2eb52c394c", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "8ee14da7b1c7f0b32f594ee9d280c89b62baa55d2e9f652d182f4f219fe1082f", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "85ceeec1175f2efeb8bc82865ba282ac8d66f1aa2e7439f29fbf8de8d6ef550d", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "9d80df8844f0263e2ecfbcf8e680931c3f0137bf2df83a134c704d681dd23ac2", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "546617c21765756971ecf2fbe73dbaed3e494693953cb3e7c82159dcb5051654", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e0608dc6d37542667f459307864303dc6bf16161823e4dfd9d13d9bf0d7f0d5a", "target_hash": "e470f788381226a7c94c67d6f287545091548b15fdba3272751494f9f6ba930f", - "section_hashes": [ - "a12fb75ea214", - "edee4fd6fcc1", - "800ff390d4e1", - "75314485de4a", - "f028423cca6a", - "99b3d4bcea2d", - "4cd1441926de", - "03232bd38b78", - "efe09e2cc603", - "3c930b32496d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -130818,992 +44906,332 @@ "ar": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "aca09cdb7ddc42ffb35ad14145a16918de071d620c623433627c594659cc0b3a", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "e707301f46abc1894c5465fce759790709b25c9d71908e4d576fe52cba3b108d", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "23549941b64bab31938f18c7f231b34827546b25973318aa437f6557059aad6d", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "f3637fd03618e1a553c2cd22ca7caf1d479f5acb0af4d410bd1d8bc12d3b30b5", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "0e7226a55c22e0178e4a7b6c01df90371e8191e0a193d1ec6016ca288df219e7", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "4cb119f435541a99eda46f7284730190ce302480a99a046ff0076db02fd7b173", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "08677f35daab4f2e87b31e492eb0946449c6dd8bb7c6aea3f0048521926f6772", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "97bb13d6711098743587b0c3ddb5d2457ac5716c46d4a05c1d3ac113afe19f25", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "f1df1c781c3465dec713efd615971da4269c42f095022162f2134d2973ef4475", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "07c9c8983c010ff097931ee5f9dd29ea124febfed078d70763dd3f9afef44f97", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "a4dc9e4b4105331243a08b235c3dbef093397412dcfc3994817b61b4cdd413b7", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "1e24848cec9514c1dc2d42f7bea6be578089f0bf7de928d281b62c174aed6875", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "591027441badb255de2317e8ebcaedc8c3bfe12b4e5fea3cd7e114f0fbd9f12b", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "ea8798c1d5911ea1916e6d996e48410ee883256799cd330c6e6fa1dfc4bd30d5", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "938dbc91b9f0554faa7f1ddd0775dc4f8e3617aad3ed080b5aa6c9389c0fe823", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "e883557bf61c2f0508a90ebfa59394fbae975f2ca2cccd24c8c503780bacea8e", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "f8054d07e4af5129b2982dd34ae1c57c74522e6db75f6a021597330303683ebc", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "462c9b311f6dcdd70cc6eec2d16c4e565596055f97bb964cf5928b34a899424b", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "702153547f27d0f79bd54af877f6246df17d04fdb2c629fedbcbafc99295a5c7", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "088e408156274f0d6694483095fca21672a47324ce09c942cf59d89874f0fe88", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "32be8172fbe691f2b07b42129b7a42b5444d0a5cbea04976117a41589dfcbeef", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "5aef6b71e908cf0411c8af33ba459a0b2a4bf47f993a1f1c46a6abef15ba96cb", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "4e32949e1eeb04f508f6cfe34285f7f5d22408caebfb29b09ad35e2060eb6b4f", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "394e95ce1bd6b3235bcf99fb01190021c41a5ce1e7fbb7ac5566982e4a396c14", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "6818c83cd1e07b96604c5e834bac2aa1bb5513413ac6768cf2b5e435cbb0a5a0", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "80e7c5f3a19ca7c7aa9f9cd7181d991e9bcd3cdc247ce694097f78b10150ac31", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "bd4b1d42cc82d1c1410c8083b92f700d6f9d0b8c6c040a94ad8854c9c16b4be9", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "25acad2e7dcde84d6532a5cdb35ff9a654125bd92386f4d08c58d14a0f6d50e4", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "5a90dc56a2b3f573d0bf8c6df6d825f8f31f0b996476347a7026f29e1cbd25c1", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "b99201e7cb5bb2eaa0ce345f0cdfb1dc180b270c6c48e0057ad77b3dfa754dde", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "e753a0cce9034a4b4c553e64dcd27a6530b2b409fe5fe420ca3ee15234c596b6", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "f8f2254b55848cce8525789bdfc81cc918ebf5613078408c4068188f7e0844e8", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "38769da8ffc2cd3ba4637163b4de12fdeee2feda86d42ba32c356fcbd07f2391", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "687c527296caf717df0a85f38f0444ebee62ab1af8d3d414f1599a325b7121a3", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "0a263e399c442e71acde44ffe7ac3583fb6aa712daf3a9b5f2d50865a1495440", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "81a9999cc86c430fe548aa97ff392255d0bb73aa5bb9e40489d5c0c13084f21b", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "0a77de23bd2cb2ac01be1d9e188944d965773182408a36e299dfa08bac950f82", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "ea201f4409cb3414bb4b067a59a0556dac36a13abf0806515168db65aa956d4b", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "37843ac775f18b507cd8906858ca601f29e04f8b40ed8cccfa86c4b295704df9", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "53d775f68ff4027daa72dc3c32a6606f4f767491579b649df7267dff6082c901", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "eaca1c57fc78809e12c8d34b0ff928e54b75fd90f428bd7752356cd312ba4b0b", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "05da8340c30766e4672b9156ba5e41c309143b160a966a141b858e023537c226", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "87c72ab31ce7abaa15083cb00dd0561d6b10117b46f0113ee3d7451b7b5407bd", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "2e4c61a1a83898d470cb9caae29544699ff86f8e3698cafc016e31262ca94bd2", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "583d2804c72680d11c4826eb1847c8acef5163dca99d4cbd20befe983faa2e02", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "3d40815bd408e17b5ed5ff47c5a33aaeddc4d8a241382bc5e4cbb9c5800122d0", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "3d315bda1d95e136885288bfa0e53f3505f60a3b38b4a62fb71153d184f6b05a", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "45178855b5130c6491973268abf9358e457b3bf70a885e72522571e3c90aca0b", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "2c5030570ed529048570094a89c56acf5d6e78abd56be2b7e1d7012843131404", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "ee0f654a5b1ea6ca5b4950f43fd8ec1a782996aa4cd6ad00101f6c1c007d6a84", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "16e11a605289e74f6fed9d26b444141caad938d5a6a7582bd2da28f7a77e8c4a", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "8676853633a7495d989cabbc8bdb69d293e7d76d6cfa58d95ecc344421a30b71", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "ceb1b18c7c4bfadbae447772d084041fac91c643fd5aded0cbc665c41cbec4f9", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "32dc19f99348462c1dced4a0669d6a5da2ca262eb4192302b90bc781c7dd5f36", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "eb8f04f8ccc3df300279367cb2fce0f8ffadb0cd0c76b0f59d11e3e457526a19", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "01368e0b2f1fc8356d51851d3467002c8e70d5dda7e57a0a5d7ddbaaaf535086", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "45899601b42750f407521388ea96d47d420a7ae7efb6fb243bac4f8a0e0b309f", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "dbec03d5523bb30c29b426376b0e84c2fed5ecfdce059556a37164a07e5c5f0b", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "5770c06a50d08d164c9248da52e2051dd11a40d2551a667f974a89a677702ab1", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "139b89503f9a830b340fe66f03abe37cd5c9656e97e9bd01dbfde2be1c7d7d32", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "affe2fdc1591ea571e8160f39b8784ff22e7eb18b52587d9eafc209762490309", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "f9f6b44db460a850d029430db314bb0fbf8cc85ad4d348b0859cfa438ced48ee", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "858c7d0b478edd6bebb3852c960275179265e2c778a9c0ba2a0b4f00857cf3eb", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "70a5148e375fdcec818c9c4b519ec2b6dfc9c21e4b6e20436a84ffa1d7d4c053", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "5e3b9b519369f40a5eae7b731a54e435ec990a52cf886f44f86441563257fd1a", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "fa3cf700c3d0baee8b83daf06562791eb1a24c14b63e217da4304a5a5d1eddc0", "target_hash": "90596a827399d32abe756e9e7dc8af7b7218553553d7dd5780b304afb0a1e009", - "section_hashes": [ - "b101663242b6", - "5aa8dcca38cb", - "527ee15029bf", - "c35d323b066d", - "9126c3d5362c", - "ec7236353da2", - "dc0116f4fd71", - "83d7ebc3ab73" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -131813,332 +45241,332 @@ "pl": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "31f0dae7408a80838cf97d4f27f3e24fdc08592745d2a20d8cccd51be02f8097", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "7c35d1f5d459af8f598e744025765e560cf9443cf4384ec407cdc00f0176d411", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "32b879bd8b340e4c9f679c66e40f7a79bc48f0609cc8488b31305ba113bddc40", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "bad1ad6a498ada13983274c2997853b5c413630474089d2fd315e4e5ec44516d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "a63b1718e80abe06e1b6e2f819356206a936fe4b068118633993fa4f4c857446", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "6f3ee4e69a6bbc47d2b0bdfc5ad0b46628006c1886ff19ce4e499e48a7172553", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "d39065ceaab6a0ad405324f3d8023b811fb0129d5443fb06b80c305992c406db", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "2bbfb9023e73adf119ad4c8608b22662673442747e1fc71032575bf1594f1045", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "eea4049ca78edccd3b948f0e2b7c8cbf871f89ae1394f94b50f65fc09796ad99", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "710db57603c5934e438fa80df365bee889e1e3f71f6a7045d72eb17c552b1641", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "348e2209ed36367dc4b136adb9b23c21245964a4ad5b38d4b8d4b31cd49e7506", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "f79b14fbbe0966de0ac161ed1761d5909ae59778a2b92fabaf616227c5dce3f1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "540c105368481a74a43e8cabaf7cb6be7d3706a5e4f112eaa8bdb8d5124a4bca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "4a4544f941512ed51392cca5675c5a347cbf3bab6926c5d91f3d43b3fd7e4d9f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "7e14a717be60b101493bc91acf433c5fedb4b083929265e77aaef8dd39d022b7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "ba6a6fa2d1e614838e3c42d3631c7141ac243fd5ec0043aefaae2b02d6bf7e82", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "4ad6ddab54371cd861831d5bc9c983585b3ee8256348ae8a146ab7be28f4b14e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "b9fdfa54681322245b5abfbb9083b317bb1186ac57513c58f7f48fa659f1880c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "cad82e0d5500d9f3a66399831167fc8dd74c4f3e09c7b11a7bd4e8f1ef317a50", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "87fb86b144498bf5e9806a9202f667396497f1240cb9fe627de93264811fe9f0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "29280eda5a952b8e587f7879bb3ebb9df022fbd7e7558491a3d29f46b9db7ea7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "8b44dc752f3b4cf745b12fcdd45c2ddb5814731940c1cabd7ff127fa2000689f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "243e003567530a4e18da416a90a1cf5b0bdbf5d2c65a37aa4e21b6cd73dc1ff8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "fb29e43a9d268c18045292358d51617d6e38df4e3e07fc8d84245a5500dc2b35", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "ac1a91f6aad79e26ffcd6865c01856c22329a9778c135f0e948345d57e91bc36", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "97c7119740b2ef14d94ca57e13fdc938a99f0c964a73255694252b80ab4ef28b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "06518fdb00e0192ad5d602785f61177ccb26aba57f39879e5e8e209cb44fc1a0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "88298d7d2445cf224da1ae90eccf794d528b399d8a2c46b8bd9b740b8557f283", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "bb2f5f37ae21096cdb47652d47aa4cc939b3dd9cc6e6ca0fdbe9490b1f16de9b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "943d560c53be779bc5b3ca5ee916ca2e5ece20c0789b790ef2282e49b05a4c86", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "25a8bd5f49bb918248ea60cd107282cf1dbd34398651b699565985b8175e1cb3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "399acddaa120a3a7d74bb8c66c9d0fd93ceda7441af8fd24f18ce0b1a29f24a5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "d88d41e47aa4b5b933b48c3aff894d34bccc7374ca231253ecc077d47188dbfb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "d5393c7362e4407553883f372b914ca3bdabf68abc87724246186a394b8e65f7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "58d9fa127ed430b1c7feb88e41ffcc905fa4b0f3831f7a4b65e1759a0d2fdd27", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "04e8a9fd2ff77d3e9979394fcde54620ae6cef55a719dd3199108aae6d16b3fe", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "1694fb768e08b7c71a6cb0272c2f37f47f10443dfed59b3f44caf9b6620a319a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "17df0592c06c1282d527ef16752c579eab1b53e0f1d11948ccb088e34985e0c5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "4cb806a8892e40f4dbb12b41b4a8151b7d3792a2dd253d8bf2e2a7fa638c39d6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "45f1be1531f9e0c337b713b1956236bdc9944c8a6d48f61f66f3fdd632af3297", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "c895670ce3dc53fb06b2d5cf0720b02a4ecbd1bbc514db54f91a0ae3c0fd7a57", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "806279169e0e2155c6610a696fdbfeff1e54971278a8a5f2d772d85e5779ea7c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "e90dfed54ca11970095458b0f9529ac9e4f72e5ef20b5e383caec3763cd356ed", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "7b2d35bc1ea646aecd509c84960b740d26658f266bc68c5c9840ea88b3c1d481", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "d046a8800d43af84669f6b37dcdefd0f0b41fbe02b4f35f72e379388e96f8996", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "fd9b3f07b10bf0703758a91ad8ab482ea191fe5b67ea0b94dddd1262b0c6ec2d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "1a30ae8c5931ce3968bdc12cb502da0ce52ba62b348da600dd5944ec0a918a60", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "7ff887a7027b8b1b37de8b0b5c4a41c960b0201b9e0fbd41c21f892c5368fcc5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "4937451c16272274f459c048d2182ab336844c44725db860304aeaa33ad2683e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "dd908c404674e0fc9b96ba52e63535b3e2d9c7dd810a708eb19f80a67a4d4779", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "f1700c18c0b81ebad5116433a2b4ea37d72fee0b694a165ac751d7bbbb284bff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "ed4e8443a6c0b3063552c9a3bb77d0516b260570dd57fa3ceaa63ca4979ffba5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "f1832ac4afbba7ca2be1b86ae01fa8911a14b112228822d02d0e35ddef1a14f4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "35a2838fbc462e1d6285d4c02da7fa7ae54228a1b6255af1c62f4863ab3928fd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "33355e3d0511bdda80a9f2886ec8284c7de568969b6a28160d2cf6a5e5fd98b9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "956c63554f4d6f11991a9a54ebcc5c4be73fdc84278fa8afe1156bb64ab004d2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "ae451e2e6e6d58b4cf39382bde915cdce3988eabd20fcc22db8a4326d010b25e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "87e73288074e6845c7f7ae7112af21940c923d2d6b41282843d89d3b361cda35", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "1e6f35541b130b97514f3f85fc7cb171995a1fecf81677c2a5bef0061a0d57a2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "b485b7d68f44da6d684c2e0eafdb5f62a9cf9ae1d8f8140268d6c84ee3cc6f79", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "d659de61385fc000a6060a67e940f341e06b273b63264c69d7f6b52b5cbd015a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "448939e8d77cca6b4a5e5f42cf6ead7bde184a59aebf2e5c08492b5e3ae20676", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "5a67e1c72f9659fa8e41ed6642aa6bc4d421956a17f3fe5004480a4900d3df61", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "59dcb530ca0998bbb3bf532ecbe346544d3f2e41b7173a51709592d677b49c69", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "c6c0f84348a7b55497883fc414b8797ba5fab970d7451caa8486c49642d865b5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e093fec3c52df6f36ceee3deb008d60ac62a195436a3df9920cd0181607cdac5", "target_hash": "48085ad7667bdba26cddaef21ce4b24f4bd0420cd833bfde40cf922e3d213f40", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -132148,992 +45576,332 @@ "pl": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "f85d281d8518cadcfcccbd9612a2eeb4ba14511da7aa103ad4dacfdfb5bb9aaa", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "0a5e3f74bd384749892092efc1f58af8410820a85d8672155454b6287a798ad9", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "5a2bcc7a57761448611ec03a78db41dd55bcb9d6397ed539b76e566756871d0c", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "03a400ef26eef2a0874ed9367ac0d969025c734f2d7ea19f800b3c24e876304b", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "c4de0f3a3c2acce47b19ef9a067e837abbc341bff7baee41044b9c1e397a2b07", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "b9424f4738232ba2414a59643691f5fca91e9133ebe1641f318868146871916c", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "d4896851395566dfd6e42b5151c738c4d14900ab5df554182c35f9cef8050e60", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "185af65bcf43004c2aa047185c9b7643132b9d58c80daca6ddef1bc06511317a", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "99d58b0392c19245c4e8217648578837f9b8bd58addf691989d98ae79da061d2", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "16a30190036ab20a42eb3c119fa29384e91e13ce33ecef1350d574cd238ce433", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "e2815810201ff9cedc55deee2bd807c0ec1a9670959d66c4777e5d06a9344f81", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "4386027782bd8e8a5495686e1f0fc9c4d0450b063bfd91916a66b158e55144b7", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "1c5a6873508dc76bbb43bbb3ae9cd342cbe02dc3dca534165efd7bd69c4368c9", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "be7cbb54c508c5341405da47a3859f40e3c6723edfd0bed168a5f1cdb65e75bb", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "7cbee3f385ae48c6578f2b036e8e91e80e2088522b8aee4100381b70ea3cdf99", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "b397359027373d76f034d3eb58240396fe502b3bad6936a04dfc0fa046979724", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "0d9e14b1a8db6dfe05fa2bc615024be8477945fbd2f23f8394f697c6b1b94bfa", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "2575de4dd00a8312227151381aef8b3a91de2be454dc984e1e1060cf415d9d38", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "66bdcd91dba731c6c88014076713d55493f36493e4fc98c2a635d42ffd79bd45", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "9e48754165609aa5ab65ecd0e7b15bccc42094c26a4dcd0bd87e262623f1d55a", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "8a7f2bfe037def03f59af6581ce240b256dd0937946a44ccb531c602060c8a6b", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "0496f3044d7fee54e7c16bd52880cca6c4e259ac88051e0323c636fd259db916", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "38c4cb32017e1091eb9093e3214b9e347f016e6e209bf5be38e8f52daa2781c1", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "a2b92fc8d8dd5bccb106a0dbaf18f8f62a9c50334ceb39df427dff82855f4a73", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "12c99c9f588eee1ad2ddfb3dff1a2b88874c6c812d1c5ddae3549cbd4a8a1a15", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "c43a4b7f481ffb59de9466c7dd7e959ff471b9e8a3e70f93f75457e797f9edfb", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "7526b14172ef44efed56645d7e2c2dde2309018e9f0749a813763e40dcb71ac0", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "8ee02d2750fb3e7cf7e880af0fb1be11284a9b309b842be1b6b1d1c25e241a87", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "f249a4471d34767995580761dde05c5ee330ae07f2d476cd8fa3a3488f32662c", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "a8979c86b0090f6ca2c2c2938eae7a91947f031fbf702f32d15246f040e34cd1", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "b929f656dcfd75917f80e377dca8e0a30e003cd9a98a2202dbbcf5633285206f", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "77d814611d6f43215da5a2adc175ba25dcfc1380df89f664410dbdc079e6ec01", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "e70e9458826845631bcab8d0d7d68930a5682515e44344298a1a6e5746b19998", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "3b6dd2676c0ecc1be47d953f451f1c3e1012a4a4976fcc880eb33bd10816e141", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "fbe09d375bc8d881002091dd844cde23af31194d853955d2b7b7424c5917340f", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "c17108c8c92c0b23d77fd30f0d28831592c7ad896052548b1d27f12e691ed24b", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "ea0e84264220150dfdfefb3fdf76f2868d0a9fd7c02b71bbbaf25df5446f6a72", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "cbbfc13b28ef91248a5d8f8960689c833525afbb7ee3436694bbaf2ad8d3de29", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "127b082e648707cca3ce0ed7ff5bac2d7157f1766d241569815c0b64ca400065", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "5d56d8f2e865ba68491fe4c4ebd5aabbf79dc889483282f0c32be509c6cbced3", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "f1bbca0175361a6ed2c7b4127dd10e1aecd9adef83915b4175abba87303644fc", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "3fc7be8ca32dcb138fbebeb23bef994fb5b437e5bba54f2b5192ef3538f53cb0", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "98ccd3712a43f189d98805bf42dbcb9e5a4efa621908b90dc710968fe3cd8ae7", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "706d16c3915bd97984aee09d43eb90e4f7311ba1a84757f39600208c7afb637e", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "44d1523a2bd90892cac7bb60038c2a6de146f9ad500af829b1edccd1631d2950", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "8101ba1acc3b816fd48354ca0a31b8e749acb52c6d0431671dc47e1f9019336a", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "57c878192a764834ed7bd024940b30e6d352c14f715e28d7428ba8d012764841", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "906641d79b1d45c126d071ee2b0bd7fb1f5fc548c73a40c07c5d7147f7405c3f", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "bcb5ac42e984417d5539d9870f09633bbb051dce85a0ab02a6ceaf77a7684e8f", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "b6cc83c405c1895ef62e3cfde796a69c53a62c1db407b0b27e216e1e6453db02", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "31642e036c307748136176ae67ac533c86b6be0affc999eecdcd15efa1e598a4", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "c884ec0e87fd30e5a9bb74361b05294a48379a257a4f1216bda784cbea852f98", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "2353e7a1be4af9218b27b19493ee54fdd8fccb9a9e70107327fb921892084851", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "2c8738655accde9203ef2bea522b72ffe5c975ca8292e040333f856a39ae6734", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "7ae3379cf363ef552f3497b94f27587f4e1decff50b8997f5f4a6e0bc32eda71", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "ce212b1087e160e98555aacab4a3a93829198553ad7284ec274d409b9c2b575c", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "9a1f77c121799b14e91368ee7a6d4ec64683518783f8be810d6ff563789c6358", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "0647724f5a34694e9e4fdf12a03f31c252620d07f4d5adea5af44da8d8ee1e01", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "68068d3f25a464051f0f0478d2a178842c4a8be520e08b96c002f31ba3cf0934", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "08af49ecf4f096a23e0491ac57e7fab7aab755f872c0557cb89a51b67e8f11bf", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "bfd1988d034000d7b732144827697c8e8560887831710a0c41f01824ac87ad4a", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "f2dd3eed146d66b0fcb192dd96ad36129069c6500830d237867eda10988071a5", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "71d225a432823bc0b62c39bdb5bbfd9710199a388267ff0dd51c4770e3383c51", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "62ea8d8eaa7bd801766e5653e6e00cf450552d2b9ec9026e85499293d3552412", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "6fc23b12af8c7989f6dab892ff9eb4c5e7c343d6945aa08ac176529679d77533", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "fb659fe65202ac95adba54d3a9588453914f7abf76f686706dbb616140304994", "target_hash": "27356e8321f12b1137519d06c27ce7fdf1e1785a519f2dd5ec9867a88aed1652", - "section_hashes": [ - "c678e588c735", - "d21e4a1439cd", - "37828c1e2003", - "347e9963d8f8", - "6019ebf6dbf7", - "8139a55fee93", - "77fa66951d15", - "2781dd0616bf" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -133143,1112 +45911,332 @@ "pl": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "0e801aaa401a0a66fa9dad8b1340f98cf4d012b03469eaba6ac029598279c09e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "93bd0752522e2ac34f17ddba7210ee845eba688cce56703c326384578136207f", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "00cdbe039824366f004b462808a19a5dc5e4f3475f905b54813e99bcb6a8d8a6", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "64c22ceb4364032ee7374efda4435a19800af00559b0398d31c1a32e96cf9d40", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "6c9e474c387cfc86a67572bc074e1293c9986c816c63cb87a43f3bc3c1240657", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "d6813e453850065c9b7995012a52c17e03a6d43b388defff1c2872681a3384ab", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "256e7673ecb25e421b322dc13c5a5d8cde61520b6f0d7d75deb3387ff9d197c9", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "94e163265ea686c1eb8259c4a46b2d7a538c6f2c11b34616ac028bcc561bef18", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "fc046b60384213acd8821ab9597187c545418cf564ddd615b3e94d1259c533ce", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "c79bd27017c2585f012b8a5af329d5284b5ff492955672ed98241fbfa1a679e7", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "1874e0a97ec8986e772fa059a027b8f4d33d271f5bd359034dc285dd845b14cc", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "e86fe0f525f378a7dadc2e963f8ccd7ce462f94c6aee0e47ec33dc06422311b0", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "b816dd9a71542c0e87a13225279801212fc4dbb7f0894f6432c71c27abd92549", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "2cae9f02edcc5db0e19812cc92fb30139415333fbe89666f0b32bf6306835b81", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "6a73f084d2f100a6e71bc0447c7a6fe8a48352937667052753cfc8889ba25ca1", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "8e10b0823dbdd2097dd54270dd72f0fa742128125d266cc5b56b55f0f3879c89", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "718ed31c954325e598919fa1ff419708e6b226e5cf2c4f7a8350c2469d654ec6", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "c5667b0c1ee7543d82041ceb0dd6e9f2cf4ca10dd91f12b972081b8034566a96", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "3db9da9c652264496a25af077a1161e8a4085db20562fa0c7218124524bb7825", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "aa32bc5eb3009b6bcf7b28366cb461949b00fe689afbaf43630ba44805184f3e", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "245d9e73195f838a1d596366ee7f52191ef7412b09684d80e5be648c47543a56", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "ea30625479d8a490195499b28ed69981f05a3d59dbbbe876087eee53f2435a77", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "a782e963ae9c60742cebde299e9a9812ecfaadf9bc1b1439b1a2293a5f024ca4", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "73e814fd414e397ca48b49bb83077220525a402ed1c84be0b58701334d09503e", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "191f661f7f3a01ef2b6161c7055c85270f5159e3cfb4902f0888718fd0f28cf0", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "af1942465d5a68496e6a60b6824e60071403f8a92e4dada4043bf8f49e33ace7", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "a6a9bc62dbc14d9f1fff3aabf6cf3f6fd5ea9f7a4ad75ff373cacf294fc182df", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "f6900f0098a0a236145ba633597b0f9eba55be04d25582f5571b1b7475bc4fc3", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "055fcf61573bba24a777b9454cc472dc87870edbfae3d5b4b23e283828eff4fa", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "7146ffac75c4d60ef4a0c110bb21540593cfc8cc387c8c21c2acdea991965e77", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "ee29734fe071eda9f88c61e0b4900d0892e20cacbfccade28562bb895da89029", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "2f5fbaf0186ef7908f7f121bf6f87dd7634265155f5e0b9b8619813fac298130", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "b31edcfa08fa388e05b9832658348067dfee5b2dc6b0eca8e808583ead2d58a0", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "cb9f88678d6d709ce6134ed95cc3425630279afe9509c279219b9a9c82a7e8fa", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "76fbb2c71a30d350dbf2907bd982cd61acdb8f1b28390e48897c8628a335f982", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "cea42fd8f593793b3ff8b09c530fd250ba5e15f2398721c81c85089ad6a0dfc7", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "d609478063c022ccb234135f01757b595dcf10a57ec78e65bbccb6173cffd6b7", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "b440918c45f3c340c71fefde7b3a55cdda66d0bff6e7a07cdd0a72954779be24", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "34c037fa46df19af42143f71dc48651dd72139858641c95efac39e5b099e0ef4", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "347966e29a3aa316fb88505dec555799b7fda8aa50bdc35f8995109d8e364018", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "da262877eab44d7ceeb234b31129db869473ae080d924fcf3ad4ef68cb052a18", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "0c3683133fcaff8765ee7529580d754426fbfd2c0d549772f4aa94432da30e2b", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "7b9847eb50b9b8586bc7bfe8a115ebc48d70d109cb3ed4b4835fbd14eac81a30", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "3638de9c53c2b55df6c93ae0fcfb6b890625c74a899342e4f4eb3075c67cc2b5", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "61b308e0cf05ec9e0e635c2b031e86769c3ff95608cd2df42a5a30d96c0f62df", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "17918321595a5ab041b1435f1c56e970a6534bcd26a64cc8fbb8b9b8d49932f0", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "9c66ceb174b22dacbdf31bab68ca965161d8d703fb48feb226c21df23b40c249", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "2118c2e2964ba066f61273d64d4e0cc891ea063d2b8a57c0d62182c6199911b1", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "91916df0d0e4e78eb9abfbc55822fd6e8db221dc2b102e7e70b08328b289238f", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "2cda303dcae5864f05cd8b438af757e49b8b54c4cffdc1c7937dcd9df56dd8d5", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "6310e32567624092507d966936a20707e222c479a527ce231bbcb86d2da283a1", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "f2c647ba18d5e77519b46e0e6810d13ec746bdd0b6bb19f883516dfd86f37867", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "fce412c28212e981b9d39dfd73b566f7214067776b6913ad59a9fbddaa4bdbb3", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "227f2c4511b24e0acb6ab5e40af48784706890c80f4275158f68a1948ecc86e9", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "994656cf2f4bc279904937c9b813d285d0ab637cf6f5621ff301d1f534e6e70a", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "5aeecd8f1fd792817f4849272a3f918b3ae2d8cc8ae9d7ed92bc30898323b774", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "f0fed5e99991e06347e0e9e7893a7a1ddc81a6124ce85b955ae66af712f800fb", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "058464a20313c488285b0af31c03643d6285b1a87d170957cc10e787bdfef4a6", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "7aaa2c39e5665a4010debbf6301565fdd402d1cd765440d22528c4b7203be58c", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "d2e6c00c3b2d7f307170b9126ddebaa0386492bbef0060c0ebd65901ba5c040a", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "e3d5ffa188e11f0a2cd6081fa087ea40db4d01e3154d45cbd409eaccb64fb2e7", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "07786de86c2c2ea893039e4b8e7c561045d9bd6edff78f221e0486830b2809c1", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "94a9460ecfd1959b6dd8e545cf092f53e3ac02554a48a4dfdf3f8570104b6a81", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "f1bdc86a9c888ca6a22f45513ea9fdc30c20d48fa99030b6b982a94120b0ad08", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "0f4b722f479889e469cecdfedd96fd74a83e29a3621d1c12d33161974b5b0eb2", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "dddea3aa9ec515ef8beafd4408953b32dde8ffa9d427c0e7fd564091c7b19660", "target_hash": "30dc3789fdc38f88d7719f58b59aeec28f27c6b0629a8a84f80ad33945e94fdf", - "section_hashes": [ - "4b3eb247b4ae", - "f334055f2569", - "41c72124c65c", - "1f37e83dd4c5", - "4877301bdfc5", - "d5a66ba0c192", - "b92109224fdd", - "e8edb2a63f66", - "dc43cac52c54", - "534424b3f74d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -134258,332 +46246,332 @@ "pl": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "944f69236e633b8a9feebc3ab4ef7aa6258efa39c05dc9fd087ea16b09cf76ef", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "8fff42f6575d0f508bcb65b784782a7137f6a8e7c6d467bf9b41c998dbc81c91", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "1ab91e67880340a6239bc87ca6b9e7dbe7d011b94f60c03a927e839aa2a47ff6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "a5efcb5f9b573fa3e0efdc74d00b0e8bf29b9c12f8d2bfb51fc30765c38af1f9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "9ce83de74756097f764a55b997b537cf15de32b70a8dea311596673f66d22eaf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "0ab206e9a53b67bd973d71391812c229f46412d000066ffb3d584dcd53349f76", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "4a8bd35005d985a86a041c4bb1f36233e06db3687e81ff26880b08028128496f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "fdaec2e0c80990c26e39092b35b15b31be5594add3144effce57298496411c04", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "c67f301cae91432870e1f0257b3810020bd884499bc9ad532ae9c5fb7fd8464f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "aded39480e47c9c07ac8e9d680800da6e692b64a43071098b9f0dd711f49a8f1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "dd55af94de4d7aae142b3abfb74e683c32c7258e9df7d015b6f352fb6e482643", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "224db9dafa2e55879d3c02c151267e52d981950a3436681076e58c44c5d24af5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "8c35a60ebb2f4394e6af264fbca83a077c9ce787dce56d531019f98b00f863e9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "b2956ca945951a61aab1245ed7614623cf7db7e9ccf19a52d8623d13b35e1c6e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "df591062bcc93f881d679d113798be9cd0272d5c8bc1e328e08bcb497908bbda", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "0d7b1fa81027d0d3f58c889445b75c6c9232787a1cc498cd29631b2e21261ab8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "7bf24213e006d924638f0e84d3121f1d40ff8f7dfa4d111c8bd9bb3306982786", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "fe66b6006f8c094f307c7fb2f580ae0989a740baf9bb0c94e0a39bb9413b53c9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "2f0ae88d4bac8d0fb2353c529038a202a881c76f35c364c41e6bc176fb3436ec", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "4b43e5b6590fd82b0c5a4a40843ea24fea5b602ac51e8502eef4f4c022f2cf95", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "1753b1a54f9061905ed2df9462b39bb564ce4057069993ef12a58833a7366f54", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "e2808e4fbbd418d770da7a064e34c9961df2e095fafb1961ddf5ccc263eddfcb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "fee1b333e838ca7377eb4681e26eaf10f23467578765b6f6d7c9c1f2ca649a4e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "f00f02d32a9e153bba4338bb85276aa0712c26057dd5dd5fab7cd8ee97dd8e03", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "668ff52ccbf725cb020e49b00ed0ddb042754c25886e9934bfa448e6eea11fca", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "0f4060a9c321a2389ea806516484dfc3b399e7577d511728c9763bc8ab421760", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "5db3200d758c11283361cb82c267fd2283e042eceeb3df7449b172dea61c020d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "d2562c13148c7b8f93397c13029351c10ecbec4c84593d5a21e1c4ec651daba9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "1790d6632c0ce1c09bb7bc83efbb4855acb79f0efe76c414f38a8b27fa478966", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "ec1cae7eb127e0ce00cd9d9d1d8b9bb34bd639d01320a18712617927b3cc094c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "11b2ab34a72ef82d1278838cbb50e52646a20f69b96a922c47ef85c697702994", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "a2627aa64abf5fe23ee871290f063a17a3e3c02dce6f66ac196ca191341e9cd8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "b02921c131483eb3b624677c86b20baf204bf8600f2e1feb1027a72923d06fe6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "5fa5d329b29ed020b8aa87226fbe7370bd1d83adc2de16ed7c9b7e9ad29d1f0c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "c0ad6c7c7ea21deea10d27bc7c655faaeb2826dc969cb9750733bf015fd94442", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "c5a6ac13b718d7f09a26ead85b647ac7a1cac98905c4856ab29de278936b672f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "05edacc35daad51ebb28a5c9223104175035655b7a2cf7362cf3a917429fe4c9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "637d12a7f50785443d7703fc70d9edf9e4ba4f79a7c59fefdf3ced55adf472b4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "bc82cee9379d8c08d1dcfe5a565647e84cd39cf0bd2bc8821718c4f98cf4c2ae", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "9a10db023499c47d0df2ec3f90fa13a49b11dc5fb6e443aca58b634994652f68", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "97785eae15a4ed0d053368a31e401db0e09513323fd333b268aa863c8849499d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "846480ae20469f7a8382e3f7de2110ff1e25916c139c6ada460f33e110cd89dd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "1817e84520e2cf374582ad16198c1df4dc23419daf7fc89dcd72b85a3b075f08", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "21e8a64fa8b82b17aef2f75bdd796990347e88781089b6aaff447b32355851b6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "61dcf14d438a30dad327bc5924bf3c6c22728e094193b4ebee6516cfc61cb03e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "4fdc69420373534af3a6e5c4853d6647b3dacdbd2c1b23199b3a7acf929dbf1f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "ddc35243cbe89ece6e08679ffdd8017351f505c7fd03bb98bc1f6938e5eb0754", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "e753e68fabf0b404bf4a789167a4dc8165942af1ce105ea9ffd9dd1c2aaa43c0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "7e654208cb8320f9aacdfc310aac51ba02866eb369a9de2da45e90b90674df7f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "4be17ee4ef1e1f3d2adf49092fb8ca80b76e386893ca20a069b955725a193409", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "87d7f39d69bddf71f3c7ee9f6fb8e9b33b60ef42a4b68cfa20b51d0d9afaafd6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "fd4a92899222bef8473cd496cf033fa30d4fffafa881c0d28fb80dd96829f029", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "e75284f474902d1e591d1782e90283e6bdd9599088dd12645c9e8c46a9cd5158", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "520e971112a061f8f1e0a1a967407480f3d7caa7d91c92f242136af0386f8d83", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "3df82c82193e0751dac480fada5e37de31df078b20d375336317415e501a65a7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "4de8975d5750ce83bd399ac282c12f3b7eb7e80e084103d8fd4c930ec9c10d69", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "b1745fb857bc7191ccecb46b3133904a8d37be77ac35745baeff862dd7d0bff4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "dba1905027f595809079ec109d02d611c446ab6cba37a087d102e6c02307d1d3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "0f2eed94b401ee4fd22643bd22cf7303a2a522059d7cc2494c9ee6d9c04afbb0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "a9aac4350f2481710d91f6d65b9b5e7828bff1be4d43eb7bd95150c1c6c709db", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "ecda1b2127fe1bd8727f331836b7304a9423c5d91a0221798002aa45b09975fd", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "0e21494a1d0ba6abc26bf35e09137da119b14513e5748985ec6eb6292bbdd0de", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "18289a177da2d67331f2ed11768916cfbd011d87e6c7c76cda5d382f5269d761", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "3f9c5d8d5b01d7b9a74a084e1185749b72261fa67bd7f36dc7bedc8e9669d034", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "01d6ab5894013dad92142b9e2da60117c916aedc339157876ae456870c89f369", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "197c1803b069a9372b800e9d8e8508379217b4b37cb1cb0c74c050c461372f17", "target_hash": "bb5732f54168ec33dea4769e2a96711de8f0d45a1bc61e798e1581af3244ee77", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -134593,2251 +46581,667 @@ "pl": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "c4486d30b3eaf1bc366709de54cdb41eaf1ca759df2e185e6d254f8eb9826db6", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "a29d3766e76dbbc6467898dc4cde4e75874e1b5010f7984c070f72527a6603ba", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "a2a65d3b83d0e5c7288234acc6126cd647e415debfa06c26b740c76321c9ffa0", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "6e9350a5f0551b4364c412c792e08e9bfcd4289598b161624d96830c87a71e44", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "78c71aa9cd928dde96106ba6c4d0667e24e03e7218d6a6d76791949affdf13c5", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "5d452edff391ec06b52a96705f6a3e7ab7cb787cb177a6cf8266981535cc1fc6", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "a25e29bcce13b262a48d58e1b1992c6a2a17da3a197c34edc26f33b75459fe11", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "6e1901488fc5dc8ec921b34d6be7d7d2b15f8ed9f4fbc4a7ba2da77bccfa222a", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "6c66fe36bd63c21c0af6ce2b5c7c4c188bc785e20dfd5c5701db9aabf2fb1587", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "3fd4f76861494473140668813b8cf4c7045fccab079852de899db9dcac133d65", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "ba38052a74ac6150ff4ac31b8565f096c9e161229a7b370dccd975b006a5ed24", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "0af20ae7a7fc0735981e2392e6b8d9e230eec77e7df14ed9ca2c260a0c130cd2", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "28f08ba06adfa3ce518b4367bf7cc87704ef03ca079adc5888d499b15a9e20d4", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "274b3a8923c5abd56cb7f9f3598ebcf4b094decf2948f867aecc68efea4a83cf", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "b12f0b9290280a601d9aedd66dd0075507a5bd3ef6b91939ae85261ee048ea75", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "4f377fb9c349d24b1a55267b15edef4aadf646dcef467c1f2c293444d339c5f5", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "2b7130fe6924d82c7cdcb78b3ebeca0477a02d9be14a40592a5589674fdde354", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "5e7ff475b2ee9740d8141c65953e0791c57a43d4cdc5839b6e74da095c10cc52", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "0c5c1d945c96a1a9a202fd796402769c87e9f1667e1462c6888396bfbc8e2907", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "b25e029787ae394ee9fa2c4cf195c1f016175a32d52dd70683b7b1a86478f32d", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "57b9549783aa16550b74d7776c62738eef7c30a080cdbdb7aa4ada8700d25b87", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "dd7b6e2514e3bc8288ad1ce012c69c8cf87d9749fb32a01f267669bb59ce1043", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "c53941f646bc0e8223be26ea8bef05a2e4b1e7a43c295a7b2a3a2c8f93ec8ff0", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "ef5def55b841f728b635c858983744ce04f62f1237ce47ab51365ca49762f823", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "1dd1a4376c9b2e33c58a16970f9fcafee970c9af45bfc138bf16a6fd97ccba53", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "59541778eae513724afefede43d3671114c1eeb7ae8c3ee61df9104ef8ed8b8b", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "7dd10579de1ca67942de3aab98597aa99a3c99c377f291da64d6fb661bd7850a", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "58660e01aa5c2be4e2bad179e4b1bbc774d0520c6c87fa9a1df503cedbbe632b", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "e8281a57bb96e5efd5759a18c7286cd2ffb41097c89c98d3deee7563e3e36f94", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "040e6dc670efdf67d8a208de7a4478742c88129da02100624035bda9dbb7c84c", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "1f34014107ee609b472e47196c19b5c684fd8e12f46467babbdbf50021b60eab", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "d3eedb6c42772bb3024be9565f9d2ee431371f188b7d2160ecfbeb869f5ccc79", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "3748091f97eaa72a26e9a9b135625402a535b15ecd897b94bb26cb10d002a25a", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "4873ed6dad48d686898a2d3316e226544ef290a27daf32702bc3afbc7c804c0b", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "7a3162773c921746c0e5cedf2abaddea2e265fdaad084e4ba447fb336ad8ab30", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "40f17bbfb0f5cdf2150ed3ac7bf8cb570e01755cec5017ad0421cc24b034a73e", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "00ac941772a6b8d02b2070ee55f254fc3a465fabade4fe594a410d6b494452f2", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "4de5958cbbd4f4162a84ae71d05b777ef3917787daabd93e439e5a30c653f474", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "e0a123686e2201d4b2271e7780d84065281e641594a389dd45606a20b599171c", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "7a132aa8f3a12c0d5b199dd6eef62dc608473b90401a31fdca91dd3c8ea57f28", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "8574bbd68148a82075ba112dc064cfd855c1e460875271079de8b68df57c7ea5", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "87e83a40217ff1b87476b230090a6793a96a10206c87fa742b169b3133985d23", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "0fe0361be9a42ba33b1bb8fe0f3a5968985228b5bd37c1f9e30835618c8d02fd", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "031559a4b6871ad5a95ea5e5260b90da988613e734e9a545cffe5498b26bb4db", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "09637b9a1997125d5042a691c7e2908999d2ffae54cdc22c0f13b1256c1863ba", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "83c35bf2495ec9d665cbaef9ed8df96377c996b19614c4c614337453546469a3", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "074f49a0ff4b29332d7fd6967a905270e9b7a680c7aa80db05a0eec0103263ae", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "2a34fc150165e9d1383d12dbea06b01970b47124f50bd14adaaa210b8e7ab172", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "36bf87c4e73eb9534188ccaf4a256b6c1f16032884691d65463428bdf8aae155", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "60b1af9a53234d55c1ca6bad71c376b1450f1fa73073d27d344a699f4c81e0b8", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "f0ed9b1538508c599427caa5a5f498768f6fba2373bd1d298739f86140f58cbe", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "1659b157f55f52e58f02f0fd917995d4eec1246f60fad5b422205f5a89b2bcd0", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "0f665fab93d9d0e36aabbf4ec70faa5b229d8800c865901482739a057150d069", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "ecfb65720f68b8a18e3f5b30b7fe2b31c1e2ef71e0c1e5823303dd8db2b833ab", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "766d056952d6e5f02a625c5398aa51001cb492c92bc37b2ec8427047529f95ed", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "7ce83968e100f8f9b5e31af3d90b6f051df71d3e44886912fc7a6c2a41083b25", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "2a1ccda8054d2519bfa5d2aa02202e03992df4c824aac63e610b9371d86bdd19", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "35d8a817b8f2213c6b5063ea62473f097811d13a4937d66098a51243c9c9f461", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "66584b05fdd44f25fbb1f449a9618a447349d1497cd5a11396c7abd3184b4338", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "b8c841f6f99176f18fa27c89aa95874a1fdb54e9f61842615317a2353a712696", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "3c9af5087c1c64cc3ef467fd765ff7eae6cd10a53f5783153e16f0a5074a7748", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "39ba33ba28199a8a03fbbfb2858e2d39a8635dc34f59751827f144e6028f5c48", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "3f2d9bc9382c810d50011230285724f313825bd07e99e45fd236a0be2db2d1c6", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "63f50788eeb2343e89a0d189de7be5c7f29b77d0b5d3aba288feeb3638810f08", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "c2dfb5102e5faf2a71471c31b631ac2b81a111e3b9a39589aa5cfaa14fa1d1ff", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "e479a28f054074737f805d60e3a71d63221cd1ce9f2e0953aa4d18e73bd3ee40", "target_hash": "d03747a6964bf7fd4d43642ab05a49a0d17d0cfc25433ec779589cfe19712966", - "section_hashes": [ - "dd2a1afb2218", - "c98260ded6c2", - "6876e6296d28", - "ae3e760be69f", - "46cbaf8facea", - "5450d3d4be52", - "63427e6e6633", - "a5e365e3bf6e", - "bd20936cd5d7", - "ff65e4618223", - "207dcdd907de", - "66ce74b2d562" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/security/ERROR_SANITIZATION.md": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "locales": { "pl": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "065d6aaa0352491d044448c260a56e078fedcea0adf1017b6e4a0fd0053d9386", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "7bb9a812ceb665564e8a2206460fb2707424bcd417964eb153966d401e47088b", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "bbcee86b8475b9e376937769ea55d5b1716d3e08315579ab17de36d6fd6e6c6a", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "a368986b70f3743059cc39d612e9b9672562adc76271537da9f39e4ae2f16904", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "2c18bd1a614bbd78a22b488385ba554cf4067678198ad533882067c5d978bc79", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "e4e602463b300c8bda973c450529cc40a4194629abe2c2d59fab023f491bbda4", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "68ae4782c29891f6942af59ea22002681ce85969158a52904ad45caf3bc90556", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "bbb88d6c18e8f88675222882894de7688b9e59adc2e4d341789190667defbef8", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "43082152b26f0725736aec1c5b11bbad6d781ecc85f7a48cbec288cd22281369", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "6df587e26d05ec3515c6a4346581d6082d80fba5b0920fe64fc713fda6f5751c", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "97e0285b718323f900052b4442b1d284688574b4bb4e80be3a5ed239032677d6", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "7bbea754aedcdcffdb00181a879103ab4a989cdfa6944e4cf7fdca3f5f8e8976", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "39a4c6400155848446f64889e1125ba167b45f557d89c3d3b3f1f140bb935d45", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "f70b0eed154cb563127cc11bc946e5cfceb545d83b6b087c11d371b3227993ac", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "06d0b33df7373bc47d55c244f422e284c514af1151e7141c63ce2856d2a3d4a5", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "37b9b637a352a93e75797a082ff3698c997a4d787f500be885c11813da8d5649", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "f0a4cba041a98ddd81c865ee1c83edd59f07dd53d3a1b79b88568be01a81b6e3", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "01c669ddb7b24cec2e4d0564f3885bf694676a5c759fcb8479f0bd7a2664b1eb", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "1aa509c7dbb5acd3307537f5e2437582c2cc54d5a022c158eeecd16f20c416b5", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "d9446b1febd3cff7793a306a50100a87ee872169d5487d30b6e4d49e7c2e0a5c", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "9a1668349c301b99395cb7d24c1656aaa55a89037804bf9be5951b79e78e07b2", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "70078c3fce44be9856960ef9dc256d040da559da4fa82e87d5fcb7b116ddaf06", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "bf84aa63b9c035e1f59ac44874c5e3f93763f64fbda5e82470b6c02eb7d4cf14", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "cb8380c83b8809b53cc0dbccfd39bdce52e78ce012b0d64d701e974e29f073dc", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "fe82b0ebabeba70cdce9f0efc54706c370396b462a1f61a56e08a2dec35c3fa8", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "3c3ef3d9dfad18b2cfc067e011810e16cbed32cd88ca5a23fdecf620ab3c7d0a", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "85b0afca1e690b66d7fafec4492b2b98b6fec9fe667dcdd2c51215b5cba2b14e", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "253568ff771799aaf14ff0f1e385fafd93299497a015b37e9f6ac9157acf2f2f", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "108b07e92f1c6034f110c8f5ae3ac04572e7c7855dd79d7e17e38466b3a94be7", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "67642c136fb85f45f37805429884c6aaf380bbd4d3fc0e63fadffe1f9797e861", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "2585b1ca9b71dcdc9a7f65f52f054cf7bad95ccc5dad8c0e962e4720a23cf60e", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "bec222683ea0d1b9fb1fde93c3aea6a5cab9f4827610a1eb58c1446c95692365", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "eb828393f02437d729502f684c97f8c0ff7091a8d19fcda6a3bb0c558e7966a9", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "8dea7f03f38c1dfc66396a3dc9a8d5010eab1c33fef3875a056913bf31b6a9e0", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "6e1627c20f2d240eb52893347321a69ff6208ff7ccb4cd3b2e267cc660c38bb5", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "95f39011464f6d0aa0c25c380b6bdc3c7b2e6a13613668faa4914046185193d9", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "a7766d5b95e5129f0e4e4aee7d8ac84e78b953c5d22da0d07f6bd6935a3e6e63", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "0d4e1de3f09223dc65adcc4f508c5656eaab6dd35410e3d490c697337dd738c2", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "b34143897f770d393497da02e502d7f4f897ef9744f9f29aeb874c72e1446886", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "661ff2134eadf00efa822334934b7d8ab95fbb7b32ed94ab8eac288e8750ff81", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "374966c86ada965823d8a0407eb31fee7a24dc4b1c32d1ac2d08b4ebfcefa08f", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "2d8a183d2b44820e44875b9c2601e69f47e3f4728f73a6136e3d845596b7ab82", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "10826942c3bc4c8e4e6a4d4a54270af6f20d695d97e01f01d392ebf13d41eb80", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "808f9fdb65e6e861b2da01ab9b4698b575fa27bc3088f6e0779ade6c267f5ddd", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "026d39c86c164ac7a787baa704caa0fe7e528ce5fbab01de6c14d28de621d0b8", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "44e78d015258411031638c5f797e054e9d72b05ea4735502f5630187ca9e1253", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "b814582b1b7d24f264a3112ec8e39dd920d437fe3951bdfe723a68a9206206da", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "be81f240faa6d85936731cf8a5fb0cbca6c58bb76abb54ebd776afafe846f4cf", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "78130fabf98014eddf9a1981898dd48d8848402e57e78592b0d4416d4783a79a", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "f761323b4d46199eff85dd2fa30b801d19aef6f2e8189ad5ad3d711d7702b4db", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "ac9eff5c6d5f92db76bee62665192433cb8e7bbcccd64b40b1fe9e4d9008a6a4", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "9d4df68ef21c0f84b4a9264310cd2891bfe868642e5d2be1aef05f268f45cdd6", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "6fb9a540962a205c6641eee7194e08d818c172dc608790cb8dc0105311e9330d", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "d458f9e6f79209e88ebbcfcf0d82395a51b6464e38405237367ac44924d8b8fa", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "9eaa9741a13136032222ff4857f72cea77d4e34beaaf7bfc9bf4c7e88cf33d81", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "62c5d557dba735da247a27c3210a0bbae1eb91660a8f68895d8839cc08dc1248", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "beac6e8757f3c772be3e281f9b6d39388c41f1c0346aa741907834f69cfae0c8", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "bdd6d488d29d23756627b206b650f55676ed332d2cd4504b867a5186bd7395b3", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "d777ca73c221c3d76466f7ecec00cadc29cf620fa6097762f74c5626b328933c", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "9c34ec902f8045115ded1865911067b41595e967b590c4ba574f966033aff5ab", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "242874ef2a53c35d8fdcde3367c04301545176fdc89cba54a3634cec99d5dbe8", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "831f44caf878466ce1d8698e03324396ea7220365e56f349de28a374616077a2", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "9cff83f2f9d2f7d9e7c13a63fe220e5973ffce71f02f37b7311e68264c106311", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "a97f7f2604b19fe837410cbbde1cc69c6a22d16b0c9a2697020368ca7e2a3138", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "55c48c88bce4d5795e80cf10321a41140e28add558261515605bbe122855c909", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "812bac536ad139e75539de11f3d1e1df495977f9c63d15dfedf6a40eea26d15c", + "source_hash": "5c8600a8861c745b6dcc421663218d00f00c40f11d78e4ab21f22743e434c713", "target_hash": "625b4c795f6a854f33c9cc66347cc4b5c6986a5354443b302d1c64469829bbb8", - "section_hashes": [ - "1013e15d9d52", - "ee993d86e938", - "0790ae0e801f", - "b0d81a53ae56", - "3842fb607f38", - "6dbe49639770", - "c0a1129fd179", - "c5645e4bf6b8" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -136847,1124 +47251,332 @@ "pl": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "061c8fac0b9e3487198ab383e1acfc604657354e7d9e271a2d247459ad2608b4", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "1b4015136434de088e76cba73aa4f52caa767c67f866916b8e8a7a7814397ba5", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "54337d73d5566629a41ede855fd98634ca218b7a32ac61e57606ff228ba0db6a", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "d882044da9f4c3b6203ddffc4ed0c45f0dc30b6dc06becb11d2680be045925d8", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "644f64a228676789af6d598dc43cb1eb94b89b6befaf3e7057e76feae5982915", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "f91243751a72238c2cdc572db064bcd44c1e48fa6333b30c3eceffa58efc1442", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "3acb50248d5f91f29fd574377b4f94e29f88b214ab162c566a8e3d61eae3d2d1", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "57e0aaade5c7ab978402f2ccdbbf282ccad2cc3a95fe7674fa617dd4f584733e", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "fa29fcd5a2c33e5333bae50a798fdc6be5f06d302b03556fb1dd405616775c13", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "f3f73b5ed65145d12d039e33d746194cf474db59363258aeac833f697541036c", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "6467908a25675a36ea134cb8b4323f393e8132854ceea9b5b44a9a211c30f1a9", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "444e6e03e58116e581b2cc8000a20aa569912a53cb9402588246bbcf88f59238", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "0c7176e301f4079a2823fe5b939bd8b5134334ded681810c5c63784831746af3", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "75834b740046ffe75ac2af103e780b8c82ed51eb3afb3e350a64204a1e0adabf", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "1d47a633844fbadb8056f170ab9089eb796d790eed7232af0b86f0aff7e5b045", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "0bcc2b4cc6b31787d45fc1d17ad204f6a87a10b2515e15c555234273329069a3", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "ae1056fbb76dc7517cf0cf08053c69207aef142fcf78043e2f81aff155a34925", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "001f138bf04ca6e1ae7fc8c87e7c8c0a97644b95eceb1ab17616c9e0b34dba02", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "396a7cfa7a7555099f1c3c1b103cab69ba0cca479943afb860a23dc31be150e4", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "d9b33702079dbf756c03f82db54c0603e6c3613cb713f265676ac8441ade1171", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "a267d6a33b9ddea97a431d9857609c8d062855b4a0c9b55986cdd7f49c7c2861", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "8b6d555cf0959f108636827b416704c135029f531b8c4eccc7eb54507a800d3c", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "f08172b3a101770e13b28d4f70398139839717c98f40a4355c687c31cb705587", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "c6f7cb527c54a2df0712f9c0347f6f3fff5fd06c800351a4a7f0523c133470e1", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "897d1d67f2a89f59fae5811f5f29779d1237c5a5b7ade2b1bff3028910622f22", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "64470a0643815f1c666202dd3b6f480f07e5b6cbad5074aa22d839b9596ecbf5", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "7b253a2ac0da8c94e8819a235d113dfa1ed0f2019cc8561234127d67ffbaba42", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "c6abf85d269ffbce9b7d6dadd7747ef75429fece0e671df2c545789700bb2076", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "db36484b3d413c455fe001abea408325d8c99cf9dc7618e0dde84c4374164bbc", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "5dfeff85590b8835dd9bb278beaec74d14f7f3c4b59894aadd934b32cd9062c6", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "0fd6ac2f8264e8cd2f5e12ec05e313221445c7cb252d02e55979d7feb9358c22", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "edcab92a5365c44425d8284b238339c81b1ebaa3ef9cca71d3914530b95d94e2", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "83e863e3d04ec52fbf00845a452fe89be513b9bd7ba012177b2a9d5bce1ce75f", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "bcf8e03ead70e9211691ff81d55aec26649b14d16c7a4eae1078ce6b8c44c72a", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "29419a9aa85d21a33ba8187ae0319b27a4ff3d453f18a2f90bc31909b0c2f6d7", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "8916330d9f190c1272400a94a95da2b2af7e34579c54685ca9c70682fbeb6dab", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "38cfee9eaca3e4786ef098f9278c01c9147db769ed5ec107edbe24615931cf6f", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "148092c9ed3b9bb4fb1df30c81fae20a5030cf11bc5c48f22a20d396e0f25617", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "ec8ccf43c4c6911e512543c016ed291cd636f401595ef7427a7f7e2cd901bb57", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "c66ae41d4d4dabc5ba6fc0bee82ee5f634b695613edd21c9e58edc71a248d7c2", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "137cc86f7e870013339f9880d5b108922e61631f2048d17546650f203d6fc336", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "49e8d3eba07d1b864838338e97dfe5b0770326d1ecc1eba79d1f7d3cb444bd37", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "102fea6a4a881ea3da930daa4fec3d9f9d914e6b0e495333f115706a8717623d", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "947452425a8546a7c11a1bc4ce1cc4ecc3884dad44f15b3fe75ccacdee32298f", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "b438c87294478ca6e9e17b7f89ae437d8adb94918b9d72fd8d8126c22db20b82", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "5fef702860626b975b9510b782b422e959bb87401b661b8e07866c1a931a011d", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "9aeae433ddfc18627aec31e90b5763d37702544c13680c2bf90180c92f25ad58", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "418995a3d71a9dc891071f7b45f4311c891d00b272cd80413629d20fc478f868", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "c1e879cbd9b13fa7aa1d30da04988aaaa240ffd7b4ae82f03bdca3915106a7be", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "438a12376080731e612e51faceb12249be8d0cc818e07ac8f474933e3704aa04", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "ac652de402b2255a60cd48d0cb181f69e2fa5d7a414aeedc6cfc11d7b31a0477", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "5ce514c32a0b23868c5d7e34b1a05ac504674dc9ceea265420953bc587544a38", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "1367e72dc1cf124ed9a768e3bedc0e8dadad13b7bf0ae34fed1481176eba6dfa", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "10a67df26a1e6c60a94c7e87fc9336a3492ef9dc17efd7e7705e962984633390", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "250509772555f8c9e0753c1bef26156f2de2570f226679e4d8022ede8a7020ad", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "c8f9504d064aac4037840dc6c430b42cb4bd2c05af5defd2a8d26eca863d6d42", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "8b3b48e5a86a7cb4c413d5922a355dae2095bfe69cd4f74f70e61376e05982c5", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "c64fd14c124d861946a68dd080488e464ee6749ca6200c14a016f0ac35dfe5ae", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "da45ad036e49bc0438938b213c523e6d7969d36086b10d0d667503092f2f260b", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "897a6d828991f2a2e915ee106f837062b810cec107bef33c55669ed0049b39ee", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "02a5d063e990cdf667920bbf1e38d3744047c25844e546f67f6f92a0af3c1ac8", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "93850a5f5802b5ecff5d9654d3bfd430bb2e0c07e8cf38463903b22c39f24e68", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "848712d6427b0a70f1154313a277fb7c71fe833189932c960b946a3afd155911", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "e7178a3b68cbe1be9899710a616ab4c117ec8ac6b54278f6e1e9a33ed5240aa9", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "ecb720fac8f4b6e40456b723a9f1cfec542ba6031e7cd4e25f397c71704603e8", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "787f26ee7c908a968e820adbf444b5e87e08eadf228e646f18945c24f4c7be26", "target_hash": "31344565b0986a338e6b28c06dbb16ecbdf3e776c785e75edccac2186591ed60", - "section_hashes": [ - "4d23ad7eb5c6", - "384c49966a3d", - "cf78d8416f0e", - "49b62c8eebdf", - "11cac6cc4eaa", - "7b98d7390efd", - "4422e425d260", - "1a5190face23", - "41864b605a74", - "8c17200a15fd" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -137974,1242 +47586,332 @@ "pl": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "5e0701b9d0a306e5a6bea8f049d29913a72a37ea8592bc574a1ca96079d02168", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "b106da4a873f6803683f9950569b6eee20f286c0d3c6174b63422e2d491ff89b", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "d734a9263ca9ef9ca450686f104f45c31e7f0544015545c66736d3fb0c19d17b", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "6b9dc57f142f22bf3e06261c218e419d9cf68ae19ef4192ce029776c9d8e4756", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "c20775f88ec6c27740d1f9e55df2540aa7fe26a3acba34667e4836b1acd7ef65", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "ad1ccd5b7fcdaaadd3eaea7ff98a4411352c59998856a09b28b2f8a166d99bcc", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "6c2586a5668a5db89c02394f8c2d444f7f24773f7f988801cd2b8f6dd43658e7", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "a7ddd3e0061b2fced4308ba3538b5960b73d693b5956eaa8521d73c53aac9a20", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "f246a3a9a2131ee0e489cabde8c1d4953ee87a966ed82985c8621f2a61c38c57", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "b7e512ced23748a8e8cdb220b781214ca4a2a68267c164a402850be6c96e6ca5", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "86fb80579cff31ab5e208c9a1cf9c67f2cc81bcbfc1ef2ac00ed0ae62890a6f5", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "e1a296563eb4f997bc29fb28e9b5847fc9037651bc68f88b61f92160e7ddd18b", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "526e5db930d05b416a9c06c125b648713c6e9871b71efb5eb83190a04044ab56", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "f28eb4f416dbb46450a722a22396b75e4fa3ec69d44cc71a9d69e28b23eb396a", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "05aa49514fe2b5ccd88c628d892a286d787ef624619636545de54dcfd60b2376", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "e5277466dffe19ac37b7cdc1b04d85cd6b2d5e5ff3479b532b4f95d664690a4a", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "a31b6cafe352d34da6f4649b325695001dae097da012d976beec72d3c0abe780", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "17d1222dfff434766b2df664b31a28bd8d7a5c25f137f304bce13aadb2de0424", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "8c4d77a33c4293af0dfd404902431770d71625d90cb0513aae382e4ea6f28812", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "36273916895c406ae387769e409e1bc154c3346eaabcedf4ea702cdea470ab05", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "30b08231a54b16719831a6b2e4b6a3c74ef083f86f0fb7df80cabde2c71faeda", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "6a50b9236d4e0f4953e2b1884d9dfb33c08eb63346cccbc89d2e56972867b559", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "7b9a0080f5854e9b679f55825dde8131fbc0eebbeb620bfc64c658742618486c", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "551f70511b97f736c97d73bc2f78e417dec7c01ac52da336a4aa6ef678f476fe", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "00045937318ccc29e1d46b982e4e63e6c16f7df24f471197e33a6734492003d7", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "b2a7f606dc00d6b0517c27cd306743ced4dce50d9aa2eed42ec5c614bef9a237", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "83785f6a1eb52ebbcc7acbeceb4c4e780de15fa3ea7b59e35563373efabbf800", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "edc578b6c5e92a3c9efa291d41f5fd68afac3201d027640a6a909b915a694b76", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "d0093dd87755e2629a8ad39e65b8e81c024206e844a1ec5a46f4c16ab3b642c4", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "b0f2c2bb6b662792a59d217cc1478d127e708e25e0fd548002cc7f71b4cbf1d1", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "a0d252d39f88571b7051699faf005db7531af0c8420a324f18f0ddc1811bcc2c", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "d20a942f51e10552f4ba88f9ac1ffacef29b790a150f7cd03d2a7327bd9ad9f4", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "825ccf8180f019e7aba03b2083163dcd606bf5451ee041b40eaca04be0ca38ba", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "bde08332d6dd6d4939373c36f373eacd40130fa451c4d00c82cb67cda3081879", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "1c54610cf3f4267df2e03d210bcc24040e2adb8485dad68b7916c79024d25c52", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "e9a433687c59775da7f2f3cd48fb247b09c1bcd4485f0295e35d550acab3381a", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "c82ec57be4d7b974bcf14b29a2cab8756f210c304cf1a4172ab7debfae5f25b6", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "6f1f0a73e61f0be58c6513c84583f3d57af5c31b5061d22b83c13f4cc009ab89", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "b69d65bdaeefa153eb8b6b1d57f3afc0d3e1629abcbedf34d2674c7c2a0849fc", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "740abc043a62910ba723f27a1ea55d9bb418ea64c905b419c95617496e20e1c5", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "802760b4bff69178ab568ac237faa4fa7da2d1d753ce93906a2238fa03e3fd4e", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "55e7f19b4a099c576984e4fa8294530f4339da45048fe5b628a9076ca6a58bb6", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "933d04c4181ea2dada3f980fbc718b687406c3576254000340ef3b297fbc0e81", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "a9c034c545c11fcef170656398263981b287c0d3b30cbc85912a86ea4e527b39", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "7b5d414028dad2d747cc75ca0e3cf76a0703905aacd9e163c27d4e1a21b09fb3", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "97be3686398fd5f88a95c9b8d774de21ae7df46198faa55832fb0c71382448ba", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "d352536f07158faf75014f91f25056e1d0f8b5c2b0363947e10b803cd41c0054", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "0085575b8f59783869c148722b2952563476abfb1fe5898a06198e06d947c0f2", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "c6f19c33f0f3505b3b91b9cdb11ca8694d47caff589c77c30d939fc385729821", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "36dd213ccc36b08b0b04c56fc5c181494506d47ffac4724ef092962ad0fa5443", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "9d37f392111b5e16c24e329142d78419791db322bb2310ebcc16dcd83e6c4b6e", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "cbe8a4d6bfe0cfd8e9f17bcfe273ce91fc1d5d337db42d3ca54939dfdfbd436b", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "e7bc0af2dc33529c3dbf9ed6f389daf3ab51ed61b91a3effc7f83988b9ed2130", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "5fbc73c62414549819e45e6e75ebd3ebf3c842f9c3028ccc2497de9490e6981f", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "473155ea59ed8cf67d8e4846fdd03faae1807e158d7ed8c88a907cda3309c51f", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "32638977c099d623e418f5edf510880fa304880ec1d0e0b91de5553a5338b425", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "39cf5418f68393cfe34fc16afd89257b3df46190156409e6947f779763c43ae5", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "bf511523ea6111577b60977713b7abbc1fe9a69d87952fd7e8e980d4c4a45b1d", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "34abdc2df76e3cf8a145c9d2082dbb56f974dfcb9d6ab715c2f00f627c64f410", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "9eb29544554cf5093fd841a4444a99273eeac1ba3d66b7228c95b65ad06d9d1a", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "2941c5212d695c92e552481c170a6accc06bcbdb65718fa3ba1c4bc909e828e3", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "5acdfe84cb3b0d18355768de589d8948f0e562f8167b6eb1580b96baae0e4301", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "788b8868281578c8ebf4922897e9d0ce5a232595df2dd62d0ce79c1c8930bfc4", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "b7f2775326145b023689250900f11dcef0396b0da160ddff43ff7ec9cf958b65", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "c6dae2e061e37743aaf7af9685de6f41a4f9fcc2d44edbfd6b6077b37eb1fb16", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "56c3e1f4910e8e9484216e3aba9cafae5570e7fbcb45c8f4d20cf502e8456742", "target_hash": "74a018ea32e704868a7aa75fe8648e57d1f4ec8c65a92cbc423301fbf4a993f3", - "section_hashes": [ - "b8dc4d80a957", - "62599c211819", - "7a24394d68c2", - "d3952c30b758", - "e5685f0c88d0", - "c9cf701f42da", - "18f5a25d0439", - "0541f5bd7590", - "20087187605a", - "bf305578b8c4", - "62aef5b51f60", - "2c33511fc4c6" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -139219,852 +47921,332 @@ "pl": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "4788378490b19044a7ce7569360a1af42b6ef35754d637bae67342779f283ce7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "e14b1217776df18e37e059b944e3d3fe4948931f8ff665ff67b56ecaabc16ecf", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "65cc375904af97b0606116e93dcecf159f34917f77a44b991800e16d72379b20", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "356dbd626e66c875db2c523f5cdf344ef6f2267d5d6afe06008a47e5fc7ef7bf", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "e6d9019dda302a5f4bee60ba7b348f959b8c1a36b8e889fcd791c8a32a813045", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "a19ee9e8707e0b899d0897568f92fd91365245c00547de7068401781a6d2ba9a", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "a5a11c32b203ad1e37af36cc0a65d46ce2410a2d43d8d2640ef852c915c394ba", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "cbc97d8c926c048db1f62de8ceb88af43e3853e95ae10c8f3651d1c7afda826c", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "33449740b37f0ed4252f1518b5067133675ab526b92c8fed95cb04381dd86d8d", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "177a6692f22b3a71ac6dd4ab03ec5d549c1e740a39ea8288d8773c3e7133fe88", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "020169512082698d703c4f122a6dac4031fc4ba1c8c1f069228fb7dd7a9ea508", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "8559fbb3100d96afc4eb530fc0848a303253ad0a633a3bf1ec8a17b7c353e36f", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "d7886fd4f94f58d9f301c1bfb11df078b950d694ff5d07f2158874ae99ccad67", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "ab85b5e8e172879d2b8cd4c4cbbe0929207bb2ccb9447cba0378e52354ba13da", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "e4a3fe24e3ca237da071a95b537ad6469e642890cb76e3c0c3b7a187fc06c083", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "542023928e0fbc09ce9aa08535e4a5d431929297126309cd2592da27525a88a8", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "dcefeaee933217dd99595514e231bb8ac9e2ec9500dbb3fb9927af45f60ee6dd", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "b483932e82fdd2f342f9ad6ea95012cb2b20737d2833c04c9db7a2df5bd31530", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "df9e7055a40d3a04f265d8ef56871ad5d7bbe8b9aa8d2661f03b998b3d9118c0", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "b4fa7aa1b9be71a6da72583ca890782f4dbe37093d65e49de5afbf2685d0df12", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "d065dd43156201a58ef295508f84d6ffc1375f8b780e95597f8a67fe4573d719", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "a7330555adf86d92080e23ce06dcc887673b57151bc424adee2baf876502e9fa", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "dee8cf0a5b40feba2813b8ae1b1d23afe74499f70f7f651be25f66304266cd03", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "3e49c620a7c1a34039a8869b51098eea8889c3c87f82865a52f11a04886f28be", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "063d932e389892532b48a150e1e607e4aa6d3cf9836e863cf48a10c3988587d9", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "61e51828b56da26f0012ec125ab9d026f0a688f3036b79f6974640cb4b56c0e5", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "9a9842659e42e0466bd8400dc191d728686df27d759e2ee27037fa6a6f4f5430", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "692b51e78c378068f1eaf897ec30fcc4937924d0f737cf4c3f26e265a5d57cec", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "909e4c7d9ee12ea055633a94a9c135c4c5033704e51f4470b8254cf0016202ef", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "f8994f63c0947ebca0a311b6db0fb2da28be70a1fb74930dd2f072be7f15fd40", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "2d5b2d06b3e8fe81eaabd6c1fd77aa4c233bf82524798e6d8996259f0a40625d", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "7e40aaa31fa7738bac3a1ce1060214b4d42492243603b07e3556da6371c79493", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "ffe1cb4fce1645407a874c1365882f264d832bbb8806d856fa1cd0650d31c93f", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "fa6ef843894c7516231c7c55d69cbc6d3f8ccb19acc3bc68075cd348de1be3c3", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "80fcd9f5ab62d243e33ddc0f8224e9619aa67fb1f6dee7720a14a8dd3125731b", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "42acf8fa9bcc2d66d58efcec094e891dbd5df2dda09f860bbb8d99da10d1ace0", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "95e247cf093b7eea2dfc33a429db2776789420684b7e297c8afd1ab47c6b8635", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "c80c6ddd6b3e45d114214bd3fa7d2931f248f76c519ba65ae0a57d9028062aac", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "121fd4d3c81f5f9edce5c4694affa49e4eb9aabce8e6b8c21f96a250dc853d5e", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "85f53478bc8aa42078ce8eddf517258651931c83c2483e30e9881fc8dd0b02a4", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "a3dbb49adb1c4a474c10d9e8586ab513e33e49e0aac6c3cd48ac66eba76baaae", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "b013ad41ca07c6aa3b6080f5799bf87a2293d3ef0f005bea30dcbf45cdb76dc5", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "e382169c32deb7256642c40035a49a68bc5617db0735239bd44ccd73efdb117e", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "db343cee44e31fd2b4967aafaf894327a430096b24c0c304e0c432efed8157fe", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "c6eaea7a8fedbac678272971031a553066ed92e127624b025a623185dba647ee", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "0bd55372bde7b0c1c7205b1b1a19244ef2ccb6faf6256af43f1a7f51cee1165f", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "162d03fa18ab38fed0d74e38745ef6a1f6cee951ad1f50b9586f802cd29ca028", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "e680e06fba8c05b8a3d655b3139c4024f6a07de0cb362027394badc312fee91a", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "144f84678975112996cef93aa78cad1e031460a44e08ad39a5b15f7ca4850eb6", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "013728cce8b24b60a3c4150a5b5ba993a4161ae6923c9e9aa65f92fcf55d50d5", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "0f850bcbdcd36837dc7b82f6d21983ee12f075da31b2d0782f116fdb4b57e601", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "51a7f3bd353df9369a9d7963864540048f5005ecdfd167fd549ed0da1e0d55c0", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "800caa6c9d43f331ae28ed3b8b1a7894222d2be07eb2411ae0a20f88df998641", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "ed2404bb3e7c2c20872737a9143edee685099c6d44cf282c38354150c2767d29", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "c9cc596a6fc91d7a76f43e17ed58d3baebebaaec75a6e93cad444f3324882a75", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "7b61dd44e360dffe22dc7445e9fa77ce56e272562048b0125d2814a4b1f52aa1", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "9cca119d1f817b1cf55b51417a391b84f8d8436d7fedbb829e6af65322fd35d7", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "963e13a5c94c74f4a16d891ef25e7027ba22f81acb8ad84ff74fe1b7663f5014", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "67d72ff81c89f3910d341cce0ca706e8d5740c65db3db874a3910bf09bde24ff", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "67f2f25e460c0dcd01a1a688b544ecf8cade518df0486a9533684cb10aa7197c", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "bdd463d9c54be41eba4c59f7cf9de91e75205307b4e1bf0be7dbcc42dd1f9f04", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "a706aa358159eb9106578ec2e13028427a27f09f7dd8c6768f66b697e427c122", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "2af46518971a8009cf57cf2f56a6b68a11c0960657f4129c8f5e7ea7a248b7e3", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "06f88d319be05cec2ac164d6a5720f4fe5638ad99bf99b3bd8cf9f485e0a096d", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "36806ddedd7409d357e9fc03034326a503259d77f498bf20d1a4abc2e9ee7749", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "3d0d4039ad665156ba9127ed93976b8544f9815f9739181bb4bab15a99fce6f7", "target_hash": "3da239cc923eab4dd76490c8a2f27c7bfae6dfc936718b2e6ed514e266499dcc", - "section_hashes": [ - "f6f6780b9435", - "0de308fa9b55", - "281312c0f4f6", - "22924ca81d23", - "a7e3b91e64d4", - "f1c81129d235" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -140074,772 +48256,332 @@ "pl": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "036981a1df6ce81e520af0ba2d8a7a69b6602b641409f16515661a712d7d4846", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "8aaf5bbe7c4f4bee93eca8e1a5709eba72d2e2a1935eed56f63ed4cc16243f44", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "1980a703828c03c1c8cb92967a4d355af2d868002b241541de086e670eae68fb", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "89d5608c1cfa587a09b34ec019f4e08950a789f7e9ade49a33316f1f29c68ce8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "e77b48119fa059c4859b9af52c5aa09ff0c63ccbf0292f78d18c4bea84d22786", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "8be5d3360fe2c370c2168d7260edfd6326497feb3662b5f2138a8f82a38c65ea", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "c63c747abac8e047c9dbefdbe488150a2138672c731d0da8ba071aa08da433d1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "af8c7f8e1182a9d0a17b1e2b68e597cc51221075e2bb1148a172429e19000432", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "98a41988f318fea45d792574396885b28d3020c3c7496a65e261410e1093aef5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "bcc365deba749625e04b43b589c9cdf55f45e00237e73faefb88371447171921", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "55e28f5b77a66bff51344180641c8f5a4083a172bbd9994ec8a419a9c230b753", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "a5b73222f6981e674081171bae3053a80741af3a718168242f080ca8d751759f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "ad847d03da6b174eb5a9f6571f7840ae6ef2b1b8e4a2e4c2c35754e663b071b2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "c3b7211cb737e50e18cb9081fb8dc14c4cc6b84247a65499441b56f0c0719153", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "78dfd9bb658c2b89118f11a886788182cce4e326711c4b9a6d077f7ceb55211b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "407971c43916f768b0e5a0a46f75fc8ab414ee2d7facd9968f3181de75892d00", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "cafa3d9b71375d28c5319889bc1e8617d0565ec79d46091b1a6900ec3d2fdfac", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "0e1dc345c16125e8b7cd7a795870e32af7338f0b54ec131d6894520e52457496", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "9c1698d0fd17d9f9ab7376b7cf30ff1637a6d12074ba71329fb58b763bc0a0bf", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "23ce6cca984453d887db9a5cb07f01791d53b44ea1aea8025fc01596263f298c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "d979ccb64e0ddd210b535c10f627e34d8b0f0f760edb065f3bf9e93075ca04f0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "ae0ed626b438472cfd3244c610a593a8cf02edca8fad67f69003fdcfae928639", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "cb1c70e73cedd6cf9db2f9cc7de10fe2c8aa1c27e9c22f0f738a775a143b9c2f", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "98754c18ef0c1715d34fda3e3613ae7315634b6f0f06910c0d32a4637b3b50c6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "4d4bb7ec8044930594010652ba14ebcb3f76d3ab8fecf7e219bfb3c835bbc88f", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "6566c850f07f8dd374d8974f7ecae674dc93804965c9257714222b69317c00ad", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "877e987847e8ec2e0b340828c26491731eadd7bb5d347b453901dd7a44b39a4b", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "612b5032872df98a11fde4b2e88258210d72f14286341d6100b4c1ddc85a088f", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "bad24e8834beb87b84ab07d87744a4b096227d449cf83f085581a3498cf7eb4b", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "d2302f5fce589f89883c91d3240469ef41ec7403a50a2600d99525f84599d2e9", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "301639dd8a1a0e8307acb958066ada895605c25ba1f5258dd2f78b0697ba0b22", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "5c9c319df34d0840419a80587ed6a4bb6d6754187dc3322d3e2a344510bd5414", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "fadf13f217b0ea6effe2d7b7ff255778a349737ac49a00ae4edfcf361409acb3", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "62de6be156c0bdcb14eaea97f091e04612b939bccf21de9da9965a5362787285", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "dea8dbe480a60c572a4a51243c6f28db6c048e0a67f27b04293504a457fac599", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "565f43f53f9cc2263b201c8155fb09f3b40836152594f8b638f3ccc0d5c409ad", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "95c01abd3ffc87f57c7762e4d2342240d0ac1451971fdbe612ae9b420448fc40", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "770492a6b6c6e944639dd10a041ece1e20175e568eff9b533f73638b920e6789", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "217ec5df3968335013aaa50e87f21624096fab57ee2dccac0aec7eeb9066cfcb", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "6f36c1a09b2797db79bf6725a676d3213e25570c66fc0ddf11c7bd36ca42dea7", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "1e6c2a2dad5d713d48142148ba8b7500bdc67aa8cd63d84095a509f82016afaf", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "2b8e16b6d1d63699555238aaef8be90395bb124b8bd5881b0859386be9980f5c", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "0f8f686911ad5fcd62fbe1393a0ff4f7e623b25962106c23b7869256bf4a29f9", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "d3774a90ade5a2bb63ad60aad4ba2c8b28714d82e5397f8b67ba5578c287a3f4", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "770c07c78e164df9659e2adb8b50065fd61066f74b3d26d6b07036bf89740a7a", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "37cad1a4199f7acffeb92ff09fc4b39ef6f09119caddeeaed8b562619ee78d2f", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "f7c8a781e2101f4f415d4a2d80be82f74a10fd0aca8c0148f2ff0b66b72892bc", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "92cb6257d026234191c2483c6658f05a9a57d31f4957e4bea6ac9dedb8c87bbe", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "6c47b68baff8286f28dd1dcf8f4d03c2474b2dc3257bbde4c7e22f1d9aceb9e9", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "5e78f59e757b12e47b5e918d0d3d701f48ddb357d44f1891ddc0a182b1545175", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "6d4f8b34c92f355ee3dbbb473dc8ab4bcfb58d454eb57cea003ea5d553f63f85", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "db6383699a186a0c3a50424077597a2b5c3272add41927885d5fde31eb57ce06", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "30d57107c9744dd1662b0f1f85c9510fc24afdffa91956c579428ae3465a1e15", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "df319af89a7cab13dde9393852025a9cd23a1b9f7de504d069074e5196c53f0c", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "830f7e4ffd2dc63180a1c27f74c5b7e5340b0e12839c2fa9689c41700404accb", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "f00adb73ab387ac102af0a0f68a237bed1e1786a03c914a16f3bf1d1c34f7415", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "dd6a3f97ace67fd79c2c8a1b519a1e063edd840051d40d129ebff6f2891c80db", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "50635082fb994897bbee2afb9cba6b80dcea44c899bd0bb072ad8aabf06c0022", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "111b38d3cdab183ed60257fccb247f8ea932d24aa94a4903695ab1248fafa3a8", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "b5cbc932b40b72cab2964c979948490f23ec26498e84584c86962df9610b2c7c", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "8f08f97ea0b73750816f6ad1a5583e204acdbda4caa81f40bf6dffc5bc0690cb", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "c43aa25846ce7a1773398158496cf94cd517d69ac9a19efa0d1f0043a376273c", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "73104a4fb8132f4522d94e635b622d28d3330549f1675ae5bc16d9aded9bf34a", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "b6ba0536efb711da1e9174402bae665b60392873545fb6810cab93191ef3a8e5", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "fdd5d25b354778f4b46bd50a53be6dc9d918b76505b406c84762a9be969d8d12", - "section_hashes": [ - "a8a79ea6b80e", - "3269d13f7dca", - "0ef532d66d80", - "417eb876c982", - "c96e850edead", - "bab3aaaa857d", - "ba950787cd3a", - "b737fdacfb1d" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "0ddd67335f83435009773dfe38eb9f75dd82840714fae2627fc2016a5649f085", "target_hash": "194f80efdbbe8fcb1db92cbbce24b316a2ba5a5d0f6f65ae1f695b0b4836a0c0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -140849,332 +48591,332 @@ "pl": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "77bcd738cc8564025e6af17464a5fe899db74a67064ff6ebf9fca8b7763232a8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "4f8082c6fa04176aaf47a889223fffeafa4b6b4af8c3cab398acb59cb090e226", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "bd0b486c42f2c269093e15885a7c857c9b7abd09cc94976dade00a55b81ec553", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "c00bf9903d7f5d8f339353e3cd13ef9f0477e49fbfe54207995990d373e2e03a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "89660baa9978e8cbbdb58bd9a5d0aefef5ba61bbdef9627cd72e2eed564eca2b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "e2d3b6a1a221199c1ea8c94039b18050294185abe3e508329307630e2147ef32", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "83d8a7140b7116d2eb7cf5db28cd89a8527c327363e02126558e43b07a3f08b2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "0b4a86f6cc21328000b52486230a668ac75fef61ffe710f50c1361c0ee49c18c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "ebeaf11417fdecad5f71f6dbac0984a9120b824d165a1dc226cf2d9cbda57904", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "4f938f687a613fdc559dc902c2bcd8344bbad579189bfd9ce8b78ab67aeb241d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "fe4248469ea223ef30a0faec9d95378a1704d970192b678e0d21765a1c68e930", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "b6da0f6ca01dc074a025b3e64daa60ca3dcf955292f5386856f142a344617cf2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "ceee0cae1b894fe2b5f2bb51f8c4c9ac69f3c65ffc6ba8789e39784ce5b08b7c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "afaed746c3ced3f75b5fe0b998433db0f37ed342dbd3f8020c53b8e9d151f9ff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "d93c5f374b75ac7567f7260fd6d7e2dc3c3a38ac7f342bcf24241079454c71ff", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "e3e5598292548302f785b399e3ea1c28c06894d8bf4cd78ec4bb9c47b1a7b7f4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "d5e67e59d5783cd6d1e69665c3f89ac84cdb2fab5782839a9be56cd5e61cfd59", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "f671c7247d26d2fc5861f613d228a252d143221fb9b9578bec684b51e0d6cd94", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "e3f03e7d2dfbbe68569ec43144948916e9d6cbfd650e57337229fcacb30e871c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "0f085a1a695696882738dc8b44d338c197d14633f517877dd7821acf2a6880e9", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "fbe07e738df21d4008a7d7085c271721228067b71a2dd9bb2815b5ca9abfaa9b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "1483968dc66973922522f826c5cb2f0e84a21769d22c123ab99e8abc329047c2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "cf0920f2ace5ac20c561efacb2a4109453bf2781514c48efa789c05da9995be5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "a2c46e46e57879488bf77164acc9a0be357d241f47fd9cb5c11adc60a2cb961b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "da12e19ed4fed9ef7910bc352d2f7880a955b79a2d8ce114145b5053ca822b11", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "3fe0a0cbfba390dbe40e68b78630621593f4b01e70624475c1fd4b7959875256", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "69c7e0e786fccfea38c22f62591c957d290100b6db847c3d4eefc93fa549bfb5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "dc1d990060d831867c6d0d219fd9daec3902967155dc4457316b5986eee7ac75", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "a54c820dfa72674ea3c9f5d74619ce1e633c75ae5a41ac55de4258738203747e", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "56ecded4e8e2fc15c397c43e27daad9a0e461aa1b82e891b178c106af3826ef2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "f8a2aa61db2a3fcd97838881f36718ec6d220e994afe1c83512c94da92e4e9f4", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "818428c6bd731d43b8f3bc3d97168d3ec2de3e777204cafff99c67bd98b57757", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "a5d11fffc39f5852d9a800559f77817393a7874c8e16aa7722d8cdcc8b5bdd86", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "10c51f1aa1842682e487c5b7cce3dfa648d30f8e9a3a473ef7f24128b0449b43", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "078caa83f1908d0b24f5cc688ba6977bd85fd62f87d8f8122105cca8ff4b46aa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "98b760827749b4f7293d7b67fbc54a2d9862eba418f484e7a8c93405d48de222", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "c4e0bd8ec9c865ae5ee8726c0bdbc62c96e5708fb9f63ab265a998da994484f2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "81dcdd818de12258eeb5a81935f6279bb7e9bc8add39d0fe4246d9e936ec80c1", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "427e6338755a04f3290eb3815deac2d7bcae85405d4f76b88591581ded352d99", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "1eaf9b34a74e9b275d61cc044bc0e5063dbcaaf1981c4dda5bf2fc4a0d43f7b0", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "2b0a7b852540dc2ca8a24e3fed9c4fe1175d579a6447e3a46d34ecab961972bc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "95c0c9018b1a90609310900386ec20e80f69acad7722a32a85986d61c255011b", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "02ff489693f26589fae68849d2aa1dcba05fa37782f0bfa557b50e20de66f65a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "19cc6f1cc17ac43543c84ec42a316152453cd2d176fcfcf978b06946e0760cde", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "f1645db4f8ef795101d023453c9b803898fe27805230f388d9ec7ceadeb68a22", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "bf1689e1ce7d38b95e847b69b02a777896512f3b06074604d4b76f5c9ce2ed03", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "976487708937a29fd44496476661bbd8f3a41a6bb3ecb49fc228f381cc09c3a2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "82bd70b6b1d257c84667b4a54e90781ed81d4ca74bff1ab5ccbf8fadfaed68de", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "1424baaee072e0e1b9b56aca7b3ff95b155fe17fbf9502a2a86d08f4c2e74d42", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "c44233d2a6be52bb5004560ea3012aa891e1a34c715e48cc92360316fa05c490", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "a26b50ee038cc80cd881bdea856ca923b82eef1ab27b9867787d40a66250d23d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "d80a4b9e23dd7de40308452e241e128820eca1d2094669da0a95ad5371995bb3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "f5b879eb556d3114ce33203ca812bd66065934f05f5c8ae73697f7ef0af9dcc7", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "25ebd088cb8163ea0d9acab5d50cdcc77e34650fb49141e9c67275d2a5e910b8", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "53978fde98b0cb85fe2746baec62550e92445c5a91534059f15f54178f3560fa", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "0dba0ddba3ead63ce8e3427882b2f12db0bc907f2eb7ccdf93375a6583878e2f", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "303b06d889e2cee15e4ae81b43fd038451842bbad5f8299b9c11c13166124776", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "7885d45c745e950893b4de22f1599e33a858192e4599d546e71c789db999b5b6", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "3b39fa96e1d90b8c0ff71b0c8b20c8eb397c2bd1ea4a4a0e409624c19b4bf9fe", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "d5482d648bd71dca83c3bd4daec44aef2f567f3016548bbdbfad9014eeec3907", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "7efaccf9d339bffff2843a5da4781e3d4538107e97f7feb99cfef0b1d03fb3b3", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "2ee792e0b4acda30ab881b86585f7e1585bc8a927937f7ff101f0c2c2e34a1e5", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "7918215df2e40d71f82359294d0cd2bdf0e26b15781e6cd45df256d20cd0c815", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "b7e5527dd0ca5102d80cf839d401c645893c5a3893b3f636284ee6f6bea3c5bc", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "ede743005f3b01bb5506862d18fecdba1a5957bf40b28d4777a1b94c431a9c8c", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "b7d3f25cc5e1c336a9a96e0f288b53144e05c5e75f3df9efb2a88ba595c4670e", "target_hash": "c21567577199d176fd5020635bd0755656fdc3d8b430dbc96dc1e8cc00719f7a", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -141184,1292 +48926,332 @@ "pl": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "f0b77f4263889b2274b61d03810bdd653aa23ea3fd5aa69f0eecbc3f83ed56e3", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "2e9f78450539a783f90f3ccd9fd7ba68a07c72c1f71b9c8e3c7b9a206d41377c", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "0c843dc8e2ce7ccb2da6be6047aa5080f7df4a3356f60c9146814f17ef7a05bf", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "86d5517ba42f2bda65cf8fc4cd45243403f15be8f5980559938ff0998c1c7535", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "0737ecb4734abbbfae580849aaf3a5313490e6e53c7cfd583b1191206a13fd88", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "1497a6dcdab8574b6b6759973c143caf8e94fea7a5cce63d89f867661f28ee3c", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "af81eb333d85c9c8afdf326ffba4ab036665f9206ac0d899142f105b1dcce305", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6d4cad99219b8a0203e272c933ab2ff9218a4afef6895ae7345da73cff9b767d", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "029d1fe1a090bbae753f1e6a5c252eb724fa31d466defef5d6e380aff1fc0ee0", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "c3fbbb8b5551e500b3fc6540b82145c853a7a5fb93376c2addda6249225e142d", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "231cf77482aa23b15ca52c4b3597e206aa0d1fbb601c0c72e3c65f047438209e", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "212489461e557fffb0157bebd7f798a0af2ba07a25352a5e72bc91f0289b0aae", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "d12cefb46aaca1071a238ab7c2e6cfe1d930d3649782bf43c3e25636cf34062a", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "1fd3e4d23406f0b2779370f1b2532bb3ed557144b0dd7a409d6ec6bbf07a61de", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "f50d6922acd4d4715e3b272d89a69aef39d7478b9200955ea6a182f2dd9269f9", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6fd1aac3b0479f5e1ead53546b6417f04508368e511c50597019927de2cf146a", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6fe8412a9786d6bd1acf95cb3d11270316137400f80f957f0304ee5238cac7d9", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "4153f836574e300123627d72acdc3e9409b2ee9a6a1b82faebe02b6c3bf628b2", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6577911564664e44761b2e80c762648168adeda4b488244469a7d09448eb4957", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "b56a1be7acbccab2bc0eed1558c621a6cab9f953a4d70f98376d5d4fb8cffb95", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "d0a98b35e212c87bd57f3f0b36a78a176387db7d8f05bce85a25faca682665c1", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "b9dd1c447e02df3d1e095c1d6d7df8f9908c82d06c5e88a0818966bc42d02080", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "115380188d3f004be39567907c99afda1b55715415cc0c2759cb01cdb69f9f87", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "f1b4e00a9f083b4a481b2cd7d1b24afc038d79abdc6768c2b2f6b1071a84904f", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6232cbeffd311dcffff37aad1fa4719e426659a6a8154a0dcb8aa502730fd2db", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "f886a366e65074cd2b62dccd900b9fed526e22424cfcd78ffb0e6af08343d7df", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "e87a0417fc908cee8630cefd891fc0c1a2defd487b96f55bfa73705b3165d77d", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "17249f44cf6b4be8142b5715309eb9ce3c517a961e02bae12c77746da67dcb36", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "a4afd8167ef1ba35b49c4404f9447e347727161be86062ce816e892547ec5462", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "bd30c2097339ed9d65ff549fe92b82ed3829e3252da910399962cbf5fd112d63", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "51c160b9055fd445b386a64b390d0f5f2546f4c6e5b3c92f138fbbe3c69140c2", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "54c7f9e51046508add90b4a25da0e5f256ac9b1eb6e856955f4a63d5005dd227", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "cc8cdcb0c4d0ca5904930131bc35a7057ff732a7ec0f32844aa4ffab4b947a65", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "f662afad6523dab2e5374e10383fa2884bd049609a4eae9c15b0e237f70e7333", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "408c614765edd27241ff08e5ecd52c24fc51badcff70917717faf42c6a8ffab6", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "636b840a27b17bc6acb6d932c368327f36cad748adcf7371d1023b63183e39d4", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6f3616dbdd8d02b42313e449b78d40462d47458b21dd1a0262d56521a93f1dea", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "e1aec11030b8c884c17ea8d754da1c4484de2ac9893b63a72340f9d6f8b10220", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "d41845e3bdfa47420727c7e80aedc4447536257b4aa2fa9f1cc9be134a2b2f8e", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "a43a338d011628b30b12facec516f55b1403ea42afe7311239d82bc9969d858a", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "23665d26bc8645a96579f61dd2eb4f2b0b95db73d4a0bb8bcdecb4da10531ec1", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "77611f893c6871db9d3f3cbe99717f01f906cc25d8087e600982428359ac6612", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "bb0504297dde6b03dd59962f9a09825ab760a835dc268435d082db7584a928f9", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6c16160979ed6782446b82ec841aa751998652b56aa25d2a0dc3b29ff062f4f1", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "f0f4abb3699d0ff65837b4afaf2af8a9ab7bae3ad8327ecdbe4142d0bc6d22d6", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "93dbaa8b108efc20f581889b12943f3f43d171cedb43b00fe48246aa0b29c30e", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "016358883fe09682eb6eda1dcc66d5bb80b93a5ee3fdc276660fdc36b5d899e2", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "aeced59d9b51e0ba1a9473a59248d0537067a5767721a0d0b7625c9a7889ff1a", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "f7a0efe24c64efe5b9431a50e8d3343e6cc11feb724435ffbde8103b904f2d87", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "18cbf3740209a2557d5f9b724fa8b3b2c3efd43f13cc13fa6b81a2590b3f69a6", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "6bdf7565d1a72565ad96ed695432de97416891c7c4d7d508c5bc04e1c7ffbf81", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "d2729129e3e79c3be020c0cbb23268de6d49fe54a27bcc3ea79e87d140cfc6e3", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "910b0f971350ce2b8327f73326fc69f181e68d0916c1ce7814ea131ede22341d", - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "4522a4ee4c447c546644c0398b949083c0bb2f193b48f874aec800f228fe483b", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "44826272fbcbe53f1edbd4b7decb74eacdfe6c4dd112cab2edacbe5d3d9fd995", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "8bbdab174a7cc5f9f54c44a111aa54a2815d86f05c9e707b80c303d83dde2d60", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "b6622eb5469f1f8c3df7ae12c8f6b97f59e85e22810b0ebf31d858acacd26527", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "8f46252ba5f46b5bee3f239f7724be531c43ca99d6b95d131c930f85808c1c8c", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "7c275c69980ae3b3ec2a25d07729328f9bb19cb452a4ef5057afe95b6de3b4ed", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "bcafb0fa1213bb641a0af2245959ec0e3fc2cbcdb3630559a2df8f97e836d6a7", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "7bf8adf4c4fa5ac0cf90d40f99a6e89ea1175f53dbe5022066482f79b9c7fcd9", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "217f1056c671166cff26275a289c5bec4c0a184af4e19207119dbd699a815d09", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "dcd9d214d085413165156fff9281643f4b4a80fb19f4d7fa034dd404cb992eee", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "5291015edaa001ddaf1dc41bd02e4a74b7928299c06a62826791016b718e8a0d", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "3a697f079f3998775398b4dc785636b289af350e7079ece9daa561b2c26af54e", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "403438cd5bb999f088c0c0d797f097b325525301fd382d0526fdfe571ee69d99", "target_hash": "ecdb7add07a4bf84d533bd81aab3440f3a3afb923d6e99584d61ecae641bdd48", - "section_hashes": [ - "bf798d7e0046", - "7342ad7301d1", - "eed3684f5d75", - "42e931d674d8", - "81ee8dad439d", - "2039de1b4e0e", - "11b2a417bc8f", - "963723489736", - "38bc7a7e6bc5", - "dd2830b6a4e7", - "456c802427f5", - "43e82f857be4", - "87f4eee642eb" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -142479,1459 +49261,667 @@ "pl": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "1fe90e241c9ec2d704ade0e2a1b2e3a60dcec5b9ab02102140bdbb883b474c89", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ar": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "f753c4676bee6f4183f25ea97c4d3fca458a4a1881605039b0926549569db403", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "9039e6535e7b2160a7e1031615feea3f6fe36a4c7aa56c51b45d60db94f804f1", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "eea3b105bbff56aed73a18d2cc8e848b6ac71b21cf59ed25ed7b42a27ce5eb3a", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "2caa34a25cbf35df78f0e7f55ae99424148c7c583743200ff550d2e344169953", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "35f09aeb0dbefb7c732a1e5c9c772758e9448419cf347888b87054dd89978b88", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "6d323fc7748bb975ec235b6edc78d717c08974533fdb9b49c954b1f89574da39", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "94f1f6dd80948cf46a03d3f65fae141a7cb541961dbefcfff0f5a374a6cfc87f", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "5a0fc9e7e44aff91d2a3275d23fb74b2979fa3317a67c244e4a2cca41a1632cc", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "cfa5c84689ae970585e6cded388aadd07a3badbc1b857c01fe98ca13dc7628e4", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "795d6c8c19e554e6cfb200aca3706e14c912df93e0e7b076cae711f4b8ebc007", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "213b4fb46df62e2a10ea788baa63180b085ee9f485c45b6ae529838ac5e0682d", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "9da4ff5605ebd81f814882958ef13894a1cf07ae3c5ca7c2d2afc9cd5afc3379", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "8936ddd81cbe3f21c8c1f3de82441aa82cea7a03b4e5894749f2191036bb9e9b", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "8d39f80fd78d3eeb2e57f96611f3114974b1442e4cb6913bf273891f4d9c0ad5", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "e6a675948d4dc0d6fefe8f7dc81fa818a06c42013e440e45e0ecf4d6e92ac789", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "bc625ee4c99269d90f4033b59fe411b8846fa9c627736a5f20a09d249e9e99d8", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "bcc14bfc8d94caed7e949a8c46b0ffc7f69ed81b1162d6d92c49067e1f99ce33", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "99e99e618df3cb040d4e0d19584726cb1f44dc3f923531ab75a96b7f153a91e7", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "94441933458ef7c5a717978067a8c427ff36b977e05a686122ed6f3d3113c617", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "b35b430c39629ef49bc2ad3cfe3c23a78d7cb1b46cebba151b416f08ed86cd6d", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "7391b3c24ce902f0447a34579eecbc654df9ad6e756d281d94b35224080a3b27", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "58186659d5335db8cc9d61e214a12317aa527a7a75f67b4d61da281b7afe053d", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "00b233f53789a57a961825b44f9992e1f1c1a8dc5215cf930518bbb3fc4aa17d", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "ead4f9e57b4ee0db495218c7f2c7d895e37818544a24384b32c19d4bcfd0fccb", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "015f49a43040593b10921c86506b0823e0676491ee4ae8ada4cc6094045f6838", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "846bdac80e39320b6c21bc56dece5c491a59ea1eba22ac325f27455cd456654f", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "818c97e77fec0543ec008d7e52337483768902ad0cacfd66e4e04a87415d8b7c", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "a65632a45e481e567dccee8c40479d60775e97128bb55202e2c18c18e638bc89", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "c9495b894ddbeeed5801e6a75479c42dcb49abf1e256946717e64357459a9d09", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "29595d0ebb0726baaa5901f43d94764b4e3ac7da3fafe5eb9acc5dd4a37d1e4b", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "50faf7ccf63f5b5e5706fb88de4e57b86d24553302a5aa32a77faa6debe84d4e", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "b8faf81cca97e9d3e68f530c52b94dc407d369fa9454a1a839107acce8d332bc", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "c7b1d4f80e67d5ff798f177c160dde5dfef3c6516ffb974d49af9f7900cf0e45", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "442d1b31f9b5569728e3e0802edbfb1186280e0c582d37b221a0c6b617b284b5", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "7f02c861a56b8decd97100dfe57274b5959b2555e80f6433f450a7cad00f3d2f", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "5749388c31b673e057017e171002e098e9608be8c06638dc8d0a719bbebb6fa9", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "528420f30d06b807336987f9245226cd32cc856b8fc648d915c00ac9e3664571", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "d4a50d3a7b5cbc092e543ea67e7fd80a2d9dadd7e702313081fa06aaf39fbaca", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "8b78081cc80dc1f0297b3033a904f9c91b8ab539693e33af39a4d98c6b1a54a6", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "d029b653a4689a930f7fa398edf06bde3e9462781ae24c09ed8a166fac81c0a9", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "b112052cb8789380ce42070abe388ab311c7968541aeb751c8dfd8b5a1f2f5da", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "4010ccb69e21ead7b22127c25b847c682bc56106b2c40336c454129f176a7904", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "80aad1d3de3c3584acdb4879d19e0963f3354a262d553839f8544498b31833db", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "3b1b2f730af12ff601a0805da295b87519e6015e67cba1d84814032b9a1fcc96", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "d3eeb83f62f0a3bae48ab58636a7f6dc1e38862e44c1b2e2ea57a833009f11b2", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "28c3a7e844e2126471a225512eacfb72567af79ca36ce5de9dfccd5f86b54629", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "89d1ac8a30497a7fcc084efcc6237394cfe2e83a9e60cd4f5224f217bc39d4c8", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "25e5803a4bffd4b9bad19ec09c009e8d4567abdda11f6c03e1bb98098450e056", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "f0c1ae41938c0ee638dbe4787ba8b4af05d5e9abc8b4551f59c4831f4c52270a", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "b71d07466b2f1c078a8ddfecfb5a0e2000d6911b7ccdaafc06ca4924b67d46ba", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "8b536b8c67d2897cfc5d8b9cde6d1827822dc596cadf06c4338c876a5f2ada91", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "3b3b04d0f36f4c833aea66dfb2ce7906917c61e3c43cc5393df322e414f19d72", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "19d559b1d9f61246af2da4e3893fa7abe99f434237c705081d141bae2fa924df", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "758379b969199ce862ad30b88423bd8ba04ccaae1e2990cb7095231054ca255e", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "dfe41c12e95f017f58b44bca6c04406813f0224891385506d52d1c7ce56bfb2b", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "7cabc2f8d2622ad75655a2d16d97535920f7618eb5a02fec21b927b3bba22b60", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "bedc47cc1c2d81490f7f833eacfe65c52d4664b8de69dc95b12c6eadc34e9980", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "af8256d6285e8c0e6e8b2b496f0af92b1f20666419ff73b3d683db23fe3929c2", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "a8c20775c917665dc0fa30ad9d3330286d46f29003c4596a60ab92fe7098bf26", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "98942174a60f64f70c89b3cd9e31d1d04427f9e67e109e2ae20eb617a3a398dd", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "62a9bb7e9d03b2ffc9a7e2e5e405324dc26a32495b05b8edf690ac70502fd58f", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "cb69f5738632688477a8f3dc7920be7fe778606239db71fc46c7e2a7110f2fd7", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "e70c29617431d740d8cd9c97289cf3beb1b16586cf59ff50bab82dd85b4a10b8", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "ea0a3131a7c4275e26355d8afd9cec3eeacfa6da9800a2baaf64ff2c97ddb55a", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "136d6d9a6d9ec91f12afc2bc7a78962deaa999b5014bb0c18343d825988cce14", "target_hash": "d592593fed8a0273d6947739c9400af8117e64e02d30e1d3dbf4da691e1369c1", - "section_hashes": ["49d2e9fff519", "375bc8492229", "6f84eb9669ab", "9199a77a4bb5"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, "docs/guides/OPENCODE-V2-PLUGIN.md": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "locales": { "ar": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "3f78903e780b8e1194fbb8f4b7b452fa522be4edc6633fe2e5acec540d5d2441", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "28888a4f2640d55262f565a99776934bb076879fc10c0281a01accc5296e2dd3", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "fa097cbfe566d86c9b459b8ff9fa17e14b8373c598b0dbad8942cb64678c69f4", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "214762ff8bf81077024709a6451117b7c525d1abebcb6183b5b15fda1ee983aa", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "7bb4c9619258ab8b4f3d4b821c88890cd004935c0797a3ee532bef30a40dce64", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "af2deafb6df6251491a4c20a429ceb0208fb213c0d1bb78c5e45a54e5ccb21a6", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "961aaf661575b29c5c7d94ab79efb67b92436b34c14eaf632daeecedaf951306", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "f1bb2b72551b68000b84695f66ca16af18e325aacc0520c9b6ba9ff45911244d", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "3683b34f9f4274f610ad7040749470b872e8227c3333f44792a65494a05d03be", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "20003a678823566512ffeed955be3c12ae32865fd397b80636899105d5eecf17", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "d6e4010d0d9f83e00c5ac270b0284d1e4198747c99d604efb8e0634a56644deb", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "2583af75fb3248c486d03b332ac1e33249bc8ba859a29fbb41ee820f2dacfc3d", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "ade0ca60f0cacb73f71802c1851cb028e506811f333560fe6009feb93c4b450a", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "8fdecba23f99a42c4e5ee8799575ee825b2a0f6ced0f42908043eaa2569d27f0", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "d6936e00b8191c997019d49cd4fc661870c9103ee49193627fa13222e9507741", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "106e2fa350da669a9f8e3e983ff70c4e880cea800674ca4a7e775c3ef113a2f0", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "8a9ce9d0e296fd6c5a8bce61abdf9c9195203226508fbda48ecd6f1d5bb25cd5", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "d05f12aa92a1e9150c2e0f90254d23366ffb36c02e90e479346e94d8cc11a8cf", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "1192ea4fc5c19ea9b8609fe61cda381d7352ddf75a53d13a18f478199cbb8793", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "0625c94e0fd8d6580b0369e5a58fca2145ead26b74482d20b7de137c646caa72", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "35fc0e571f79ad24ec970cfafbafe46205b2586e06382016e8d3904515fe7af6", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "eea0e040c4335eda404decd2e065739084110737cdc47ce2116a5c588a3cba3e", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "1ca65b071dd9baebd7cef03f81d988104e30907ef395fd84851e3c98335a1dcd", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "befc58efe4fe3310079c1b7755acbbcbc5903a6b12ab883e685ef44b4c2a133b", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "bbcc9b9c142bbcd37c996b1ea423f1cba056b56154cad22d455ffe9340fdffce", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "deeae1ba14c63b539615caf7d1edfe575d54fc6cc3aa63d07ba90cb9dc6891b1", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "8765b45a1858d29bb002dd42bf7e418078507331f1de1f4a54bea721e56779a1", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "c98ae3c2980cf4d16acadcbf660740709ea3516e8e1800649271cca17b3c5191", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "e4b7e3d83b2fcea1924a1db4b879a3c670e156a19496e92c7c0756052f55a5c8", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "fea3eb4039eaf8924903abc00b776612bfdb0244311767f186e6b227226937a5", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "274ecc9d2de5f9ef92d92bb1f7ea0f4de3a7397d97f8cd2975d4edc6e8d47c6b", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "f1f8e8cbe1ca2737ffd1475d56e1cdc8ea903d5fa0199f25b1bbf9e7a6432b00", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "48815d6951cc7c215392fc5289d15b789897fda512494c2fbe8eda16321b7a70", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "ec4185a4d741354ac4383b284afa46b1ae264913c6bc48d9e26a224b7e5ac21b", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "5c8269b64915463a232d53ba13e12ba36505a9824672a377f7063036fb7cf055", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "5cb1d33df643319ed86789776fced8f98f7b59a2ab7bcb1f59781a0fa3900ed0", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "d777b4eae3a0e42e5d00f966e1be3759330a381842c43b966d7efa2f3bad35e1", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "36fcd11ca2bb033b063c6885fa233f18ea1acf3c4b3664901be08fbf84c9e04a", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "cdd7b24181c14f220779a0cbba081514344b37aa0566e4cbba2e9051f400f1d2", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "ef9babe2aac52b60ee1f472c7cf1ff4b877088a3ebee5fd966188b4473834931", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "09377cc9e22409a108b22c44cb6dda2db0255ab21a0dc50ea128d9159171d5a4", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "f4724d1b43a65707ec914958205cd3cff384142d31cf6a2bab4d1ca4ed3afd01", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "d37392f0765f3beeb60e73dfc2f0f0b2ed1a0ac13052482020d0b314be638047", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "0d6d9906f27e69eba1a612c5e36c3ac963c7790552c3ea782c952987584c3733", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "56dade1c6996cfa511337cac94c58f2cb3807f3435593be9c2177edb83d31329", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "240cb4567b9219fc1bfcaf32f1ca97795696b185aae8ef7a26af738fb8eb2b15", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "4655ec0990dff268b77dad389403cba87907ec1023d569d5f286b4a8fe1ce189", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "9bcf018ce30d05d7754c21acd13306889537b9f01d771d28a4c48ea64c2ee107", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "5b450036ee7b6119b374c504162dcdc48b7606a53f67ae5099b5b45c01fa3ea9", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "13deaa98d8645f197cc8a239ed3c92d2e25504cd316f229a44c3198ec3fa903e", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "681de2c9c646cfcf3da460476c944b1f28d46e490a184ecc38fccb878c724f92", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "50082fda86aba2442eb15c47f2b8c532639c32181324618190a96247a6636d5a", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "ba51eca44e81ff9662d27611292ebf540349f6f076b0598eacfc33ef11236e7c", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "e40058f67530e6e7b240c31169a00ba39e5fbba90bed0295d0606f527265c329", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "f7f9e946dbd01d1c4a077c17daae22d984036ffa1cac0cc5d710bc9c8d1918b7", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "e9e4de6d4b95f795603b69bc34c9861a817517ebefe33fcc44e617f5fbb3a9ac", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "7010079f207cb61b592b32a4808f2203f11c7df4a42561168494e1e73b33a6d2", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "018d45dac865167102ca4a6a049763e99928b9dab5ae56fc2f0ef021f94cf850", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "42f3af9123046a190195c3acd298ddcce4e249b3f967871aedebc731c5a623b0", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "5295e5670314f8026fc06127926655c3d8527dfdbdb2e2420aee4b912eef7c7a", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "18e02b7a9819bf00265063fe69d346b75afa32402153dbce6f473198eff13901", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "2ccf79af14b7dc6cf5c4586a0c918a57677f02c6cae83d178f7d3ac36794f7e5", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "c4af953f598772091c1534a35dbb9cfec34a7f9d4fea7fcf13a760f8507fa464", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "c336fd3f4d52a42738e0967f6f1049702ab08b739d53b8086ad703066b9b79e3", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "2640622772e3159115ab9cecd72150ddcd3ca897f53cb7a85207f8f0f722b51c", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { - "source_hash": "d666da44da62ea086cfe547e44c09b8980041360e331f74a64a2bab2ecd0f44b", + "source_hash": "fd7bfbc60b018afb9b1906d940327f537073a836bd8957365cba2f60a9f6cbf2", "target_hash": "9a4c24cbe55fd0fb80358a275106f3c054190e7f6e4906031a34563753dfd5d5", - "section_hashes": [ - "ad273b98245c", - "4530ffa604f4", - "c40debad9375", - "46d53f71bf95", - "60a30c4e0e31", - "375bd17a389a", - "8b0413099d57", - "5ac52da3109f", - "b1dd5fb2d738" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -143941,398 +49931,332 @@ "ar": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "b4fff8f171691982fc4a18d1cb19ed171c38f5c59c057bc38fbeab9950755375", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "26eb0108ff2047d80163783dc2c85b74a77f51f7aa9dbf575b6692cdffcf2220", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a95d8a18c2bb3c5f9ad6e917c4acc92d2edc825f7d451c1e638fe2c25bb1c5d1", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "c0a4696ea23bd27247e507c4010d3e869f7b02214c408b0f6c6644c76e527b82", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "62d06f8ba3841edea9de8645b6fa9cb7f0866bbc4c67856a4718160b71b23306", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a8e3caa596ba069bd07bd7270548ba83ef4546b9206cd4e75b8910a6a6410d34", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a24ffe6e7fc65efcebed4bc4f858b7ebc8c1cadfa1823c14c2d179509e589920", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "ba2d8ce2e6b75f1db5c5326e7cff36ad5665e18ad2429849a0a057b0632aff80", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "34903791abeee1c7ea092408f2670fd184e3a48149226c7a22d51798413579a3", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "211c6f93b5016a2e31681a1db09bb2f7f818297512e73bbc39da11b5b21bc72c", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "10967889ef5d782b4fb9cb77415e497b302ea5042ce79fe18b67a06c2a65c815", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "efd6f3c731ba6ec7f739c3fd1acfae47c7c9b2fd10428c66acfecc87afb3bab7", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "9b63d0e8bfa230cd2c329890594382ace1c027e089f2591658b933e815a22f1a", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "90bc6bcf90bdd6436bd3810b1af07f415d6ee99a88f6b78ddb3d39dd8e829716", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "e8385425ce6db6c0fdc3c53bd691339d9e53ae3abc12ff76103ed970471e45a8", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "e444123d17a97bdcde5ddec5c2078d27dbaa1ac0479206dbe3378bb00cbf413a", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "5f38a39bcf8f4ed4a3cd6efac62d3a49c785d9756f631425748e815821b2c8c4", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "2b2e3d8046a360e621e9e9770d7162172f5c15732430f46bee4cba5493e1739b", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "96aaa2e8d0747d186a8a74c5fa45420e733bd09e311b40349cb75d2fe55e4327", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "c9eac5e613401b009d1c3912caf5c2768506690e1d81d152617897ee6996abb5", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "b205bc8e2ca65b211d7afd6fd9ed8c5517a2ea1066a0ba9e646c062fdb232446", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a19c2976e6338e637bb89d1e89ac4385666f1911694d3f115586bfa6c513fa43", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "34997375863c863b88718cc5da3dcd92d2e397b88cbaed2d49264e8627ad51ac", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "9d6e755e94ae80e9f225503558949fe5211cc72a6a5f240f98ad7c57cf01d1d8", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "f55794f3fee691349f790778a6572f6f8cb3d9f9b32ba0fb3b992d2d615f466c", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a952ace23c69064e60de3ad7a449e1c5063b99f685748c36f4363232cc458f39", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "f4689b31d6a2707e6907b611abca3a04f6f8af530fcd7a0c0a10598c1b7e497b", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "128054118dd12649c7f1a67af0b07f1fadf39ea1ca0b8d93f60f1de11615a9bf", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "40edfc7d967cc47fc1e4a7e080beb6da83429fe33dc508f5ee4bcaf70d261485", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "633971232d39ad573995a2b2ee24221218d5288512c6bf14187d2c4577088765", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "f8fa5969d1ada820ce08fb25251e7fd3e8693b7d7f3374aec092f59ebdb00cb6", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "f95214f53cede289f8c5dd6dd85a6ac026ccf40a2028d549f13e92324e61814e", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "e214222e2c881670f16e84b2683c9482854fe943b3ad48afa6b4ba6b23364856", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "4641627ba69c18b08558119dad6f1a2b1bef2fe94cffdfb82605e1bbcc0eaf59", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "8f6b18a4a2b6a8d8709aee961614ccf2ee0470c8487b49972a86a6af39b2fe8c", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "4e37a863ac584523e7fef41ef2cb6ee037a7d675ba2e4822ca196d5af23ff84d", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "33905c2567e615588b942d48a6dcb4f5769e318a74a009afb4f5519c0f5e125a", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "f4ba952931ad879aff7bc53552d8762ced57c13d732bd39182ed986302b138e8", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "26a1a30dd8f81f59ea42cf4f8a55644d7774d7c6ba4511a14f0b604f9b59c256", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "5cfd8b7d400444b17f39db893588a293f0e91feeadbff010a86b17a141684707", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "05bb80a2c48a1eca814c5845602cad34b252d26a4b16a57765c04f3786baa0ea", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "0544c3cdddb3cc49fc6907dd917a6305be05a4465b4553aee5e311b773910880", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "76b52ed384f029aa3793bfdae6b41fbaa1f27502a3b34dd1ac82285f5fb111d0", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "119b6dbb9374c02ae131e3436893b01c7509b89c14e9f743ad65b1bc21dcf391", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "c712b369458da07e30367e3654f51bfe4dd5dcbae933d2c839f001cd5752708d", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "c69f6a32e092ff601616cf7503e625e11c09a8c6dd441306019c916ed815ef4e", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "679af7d8cd003698a0f7a2e6f71f64b0cc0710b28afc873f3f53271203287711", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "488597982b09f54e886e4dff503288e94f55a8911ce312ffa49d7cb1fc32c024", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "0abd0046fadd0c64712063353ff9a77aa5ac1a04ff9f695aed2bd0e006f9c6a4", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "5b79b7ef0916181426e2b322ade97c03b960647146cbf87acbbbbfeb6b6caf7f", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "cbf0877589d7f7c5e887e37d1d5f64c77d165b04b78693831abd65b30666ad38", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "794bd0267d3479ec97288319df8fb65586d44cd9b68589c63d29eda9e68b7be1", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "47c93410c7f8c898c303426197c60ca22f0ebcfc58773c2ebb429ab25d167c8d", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "b1f6e187f963b4eaa8a8ffd484e04131a49d61ae0b4bfafa4982598073d40c73", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "89fecfa1d5ef01295efbe0f77c12aae651233c03bc1a971304ba4f8c529c2c0e", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "1c37e80a5ef8c8ab270b6dec7e851ea51e4637ddcff947d4889bc6a7a944026f", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "efd6021a34e00942afe01026234b99996eec41c4da8e7119efced33a21b1d878", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "d45d4dfba15d486d236f2564431f48858a36eb9f4af91e3066786aa15a92a7a6", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "3a1fed92891bcc04d70e87e902f101c0c32837dc6fb8e8489453cc45c62a2c66", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a34b745d9560ec8a8581a0cbea4d57b23bd9f23207291acc42840ace53e5ebc9", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a73edf57f5db139a504cc5942741c4810ddd111ebb423d2d391c783a44c1e56e", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "c2f3746c44455459401cb00a42f27f5e1493f5cdcb6c66314941c8eb387a0daf", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "052cb530fd3905fdf8558a3e30cefa5266fe26c03bb9782fc8894924704eea7f", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "43fd87eb4f93674ab84a79d1879d9865f5ccc750fa466b32f6d9b449ea5f8118", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "e194eacb5fb5290edb7cd8c9344afd35421c8c1af256a70612b4609083da0efc", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "eb7a507afa3e74b92180a732e77f11e368dd543fb556e21c0d988aba3e20bb5d", "target_hash": "a61cff1e60e78c9303ff820d859e7867495b8171994151148bb0fd5d4f4e8682", - "section_hashes": ["4d36438e84b1", "50770e75c649", "4fa9d1e70b35"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -144342,992 +50266,332 @@ "ar": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "f799dc5a4444df28ee4e492d5c8e2cd907e627b38ed6037f078402432cecc042", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "51c3b96bd3901f8073bc711734159d636f572901bbafe73118505b7136dc11ad", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "ba94a36b8c07ea73fbbc4f7fa1602a1840459c9a5cbad54ed4ceb8a517dddc42", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "787d8594ad8bbfd80afed0d9f325a33d026af5f852a943c35724aaaa792cb0db", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "ad7c520ffd2b5eac33e8026b035f144c4471294b44fb63120ed507156c0757ba", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "0038384ae96b93578071efbc7e8205b39b2c4e461fe1c5d293b97a24ffc55eb0", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "7c060968a21071ceb3d113d4d592dbf89f71e397bba6ce1f825061755824dc5c", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "caf31c2763d377c6dd5acc90f2811e345fd00c85789ce65751945cca7187d3d1", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "9f31da5a622c8069e68064a3bc85a59aba6b98d9a55ae186d7e4ba5f96925d57", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "2dc98269d7163c07bf6a1482cceedb549624fe603ceece7cf1868771a8f8202a", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "38013230822d8a95438382e449bbc075f29361c30902d4b19d9af8b52d61d32a", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "3db9346aa5c48a14207c949bec4fb2e2fb8fcc51306c0a6eda76017782481571", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "15522be1aace001d206f39766c702a36281e9bcb14b7b1dabd105d533f1ddffa", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "285f33fbf93d21b0fe937ab15105fa315a0a633011032e734321b1c94df58f34", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "8081075d180cac09719799386ef053f85dda0f0ff3d372c5091464feddca5bd4", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "640330c94d5954e6a945fd5693de82745a1481e9bcccc212716f76208b193b37", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "d7373d1fa10f813aae38dbe78b56b30dab7f7a27c5cdf55dcbc82c472d4643a4", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "a3f2e26f1bc58a643d0d4bc28e8b360382566ac6e5395b1708b22768991adbb0", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "502d6a14fa1edd1129d4c6c912f43252e875ddf1b226dac9d935cc0a9ff97ccf", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "65abf5d7b6fc2c777f5a18e370c5fbf6e4c622b9a725377b1df4765cbd7853bc", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "0ff1e0eee584e933a41330d8f673c6a3900bfcd046b47c63ab66752c3e0278cd", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "9f7e522e6b85c2218eb3e958e239e4e80164c6a0e33b71cca6052eb79a0c5946", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "77a5020a32cb3d10a19c4add796f28f3f388df15157254b78332e370d8d39d04", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "7d90a84f7629a50d460e316f589de9d2b64c04c288683b0c17157f00f254b714", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "208facb4b2871286e4934e978f22c6fe98c290111910c66f68799956cb6d8ded", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "1cf4dd5d3091fbf4c15c1bbf047bf2552ec36db8e00e3aa39554bb018c2b6536", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "8e9ed8bd41ff25e1b86b1c795da2bf97c506b947f3b755159018b457fe58df0c", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "c3b4bc4eed365d0536a1aa9deee7449ffc37ec822ed760335a5904697da7c41a", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "b3954a5ea5629b22c74fa9b5da48d64fdd0d06bd72cf25cd9637e789f32649ec", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "b90de28533ce784ca26b1b9b4f27e22f611278dc6daea48c5824b1b70343fd2d", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "aa10e2814b210f04978a23476d775e6bbd1590bcde81faa8e1083b8b5ab84519", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "627599dd47c1f8a3d2e79de63578725013c058867ca1cf30cd5ee633c11005f9", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "407acf980324d875656549057354fce21ac2c9fa60b945a3dd02bc8e85b6f370", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "fab10565ad144f9358bc0f3044e8fc1ecd8b5232b14e9df2dbabea4297462f7b", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "763c1c699de63081846e87a6ddc01a661c72ccb2a7b568f478a0559a7e38322a", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "cafaae1951f8b844cb670e6359f0e15c41728bed44b619b5b49c94a2dae9e28e", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "503dce3006a10d631984d8a45056e90497c01733f6c9d506ca27985b3f8f4c35", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "f8122e07b29221bbdd25c6cff3d5c9b21234e04f807deb023edd1b39c91414ed", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "6a4e07d3cf46134f5301d008ac4872c780f4c7007172640783ac07a95f6020b4", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "14b4405ca62b8814120f4893920697af07658432527648433224de2d2b7ff04a", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "299f901b6da50495f0bd32b43de02e0aee389bbe0315fa293850ccceca41a87e", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "1c922808e4200171ad6e8fd41fef1c785b444c795ea266e481bb19083ee51642", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "9ea0d6195b6a6ef4d33f32b556505a24f062941987c530c0fcbf43a51d6d827a", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "39033a971fb0d52118a5adee8ca36bbe2465f440cabd89f868c2d4544adbc141", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "556e769c7fccd07a2e58cafcad70128624514f0f21a22ce42de88fe1fae08e7e", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "f79d77a27c37a9b560f56d1b046bce7bf38cac6ba4d6ec19f8c9432fec8d3ef3", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "56cdaab999c40aff0b9e581474f2c6ebbb10ae0b070b769943728519b9805c20", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "1ca08c2f4c62ad5e4fc661a5850410fdb51bb2fda705738e8ef1b85b8517a1e2", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "984f2749b34d2035bc51bed87b026a442f92c8d93ca9cc5629f5a705d72ccb79", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "71d8857d65b18ffdff66a3e8abeb165e2ae46dad6f8559f3690dede5ad3cf615", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "9ee41f6b113b2e795c11c3d41dff95ea0d1039d96e7677e256bd375295b9f585", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "e9e26102bb9f40b4bbe76dfc3921a81184ae942f3ae1e3eda5dae24651cc1e16", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "61ce60e784f12b0d3785318ab85867402da5f0b2a1610e3220e7f18171e1dbc2", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "0c1bd669aea439bba54fa06e0c5faa19ef8f501302a5fdd6787f9565f3e17e7e", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "7180030276ceedfd65edf7e75de045419d8aa806ac859966ca69d0289e61f6a7", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "acd01ff1d6504e0470558779bb43ed823d5380bd2a73cbae0af2df1b3e29dc7c", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "fc819005e73ec14f1e03fce48830fa9738bc900524b02b934b4d35f2751189f7", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "a130c4e7a8395d32c1a72de376a561217192067fd2f6ffe5fa2606757c240d28", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "75378ad1c1e2848547a0bc4c6e1d199165079432e61b682b60bf3f5fab886a3f", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "49c58015fc53dff6fff3860d9d2efaf1ea72eecbe93d96763e3e1d3f2d19d772", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "17ff29884a1772d3ba11b35fcf8530f450c00217db5175309730bdf144b0d4bc", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "96a7c4bc2f0faacc5dc81cff74d33194ccd162be521873bb27b67cc96397e85b", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "ea00c7d1a687ec18d36f7082fd51051f0da570b815eff80881d45e85eb69ff9a", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "50f3a26d419e47aefb015b79935560f97a9ad28979b0cedc63b71bf2598d8f16", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "4b0ec2476f8128cc6dca354cd88fb5e3e735a19f71520c5d9767c421dc6c77ce", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "bb5087a17bcb6411b9e566f9d4e52548241d4b39b786f32acbbb62253ceebbb2", "target_hash": "2364964c2e0eb9656d02f02454dc5232b7d7b4e08355f6d56d8612897083466b", - "section_hashes": [ - "1d6f9df2028c", - "374e9274f5c8", - "bef15684147f", - "7e7b6ac38178", - "b0f1bb7ede74", - "683232c431ce", - "7a420ed52517", - "04b36ae05b6e" - ], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } }, @@ -145337,400 +50601,386 @@ "ar": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "32b7da2e53abe31c1e2a63fa7a76691a10be3f6e04377a11da90efb13fd72df3", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "cs": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "8fd2f21d68fc2e88a384ed6b65589e7ee1bfb8221862c52c98a1a1df96439812", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bg": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "d9d6ce821aa3c8bbb661a6aa9a356505d751715680d23d89809a18e3e790b6ba", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bn": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "be4414bac9db7b5c9084001dfec08ec34c69ee9471c8f84e7366599cc2120857", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "az": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "50f3db0c7899f02681472a8cfd14c022e4a934b2cdcad2c0a13ccbe280a7d9b0", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "da": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "99d4b54fbabe16f596461b7f9a52ec1ca7cf4b4be9fe877786d545763392d081", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "de": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "5d35f5d6d94e88570eb1ea4c04ca46c1b744ce1f16684f5aa85b57a9559cf3d7", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "es": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "2b9405b2b8cabac2766f4b16b21beaa6badd5d96c03ee2c1664832998d463863", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "et": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "8cb0e11da3a786e171634e816709ac3d5a255101a2b313c0a8592068e033099b", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "el": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "f5c1e76219bee2fdac0d6e4e14c14244c1e3b834ed06281e12de402d56926851", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "am": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "a25cfb4c9af5e8fd410774732367c2da4ffcb5b8a7cf122fe7df4b2836912e66", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fr": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "082d42d66fd7f9aed74de63c212707d3370d24bc377f1e6ac8c4c7f551ecdc49", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fa": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "5dc6262f7995c643fc190949c7b8d297abc7d2c074524943770de25d99c763aa", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "fi": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "45c126546bc536efe051af10b57acbeb26971e39391da9265667a33b8ec6bfb6", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ga": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "6c49c96537055ece24baf22f684fe764b0b0be2fb2ad704154be0290b88aca2a", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "gu": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "cb2e0e2c6ab3a1f66fec0b1c574b41b3b16df02bffc39d13e96231e7c8b8d118", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ha": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "e33193472fa67fa48d539ea8a9464602020dd8bf7d4292f4ca5e4a40ec11cd67", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "he": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "5a68d972a9f72f27cb2a6d176860453534122ff8fbe1748532e7af78d03011d0", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hi": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "4460af862051671edab77efddac65aa16964b5098146c84ab8fb24d27b7133cf", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hr": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "3e4301d0d977a01669805d563df9e6c313d0feec1ac2ced86224574e9bb39b9f", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hu": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "f53375ae3d7f039b3c194187fec59210595268f441f4ade8f7dc3b00ae07cb68", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "hy": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "714ca807ca2436a56fef89ac78ca96cf94fa52194482480949ce915d35024b4d", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "id": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "0545b8830853938208bb3cd3e30ded2a2370cf3ee6bab9173214dc3a1e138b0b", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "it": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "6b341ff42d2701f51a4a831d5af35dfff2d5ebf2883b4ca71021ecbdb111a59c", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ig": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "1e90202d8d1543a68b3d206703986133d01a4bf8410f721498df73a0ea8f9fcc", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ja": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "bbe32c79ddebada1fab562678016b6005926ac56330ff95abcc1b9209d0c6a2d", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ka": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "870dc1d8fdf7253646ff840a9474e76e1db7d44b0a1e55d28092106d3219ccbf", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ko": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "65b71466976975cdd120cb259427d7671ee95b69b288817de362de49ae99d8dd", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "kn": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "60fdc1dd7f8451519056d96fd245ce950ce083436693aac680af316e57328f41", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "km": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "fb0d8d4a1471e61f67a8aef8c24dff37a2bb24bba6aba64d78d127e3e061b191", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lt": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "43176a785ab0c7e61fa114b337ab8c97c29cd423cbd911ee251746e47fd507fc", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "lv": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "79dd10cf6b0c04aa9a2d2af9902b68714863d220d291d014284be2f4779e8c57", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ml": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "b380e7b88a5d8df149b150f53c52bf0bf06def0248b5fbaba9887cb38d42fc00", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ms": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "6ed893b89ab338320c0a33ee5adde74ffa5b716940d3117620c581c5cad3e757", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mr": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "90a2a14daec55124aaa7eb011dbf84e29823b69cd636038a20f770e0b4640854", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "mt": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "d3cb63e5f14ccb9ea074b346e9ecdb0beae151cd0caea3d91ebe88050636fe88", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ne": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "22d3b988e582a715b36d89e13e4efefd33375097f400e3eab475c475f224c142", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "my": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "5aa3107db66bf3b27031da12fe2a93eba547fce257c1667d83b7b71b5c926538", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "nl": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "15c4b8735aa687eb9b8a7dea2ae9a353ce177b3bc86368d419f252c2d4eabc07", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "no": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "d9461e9da5c2d3eeb32b181ad38038a61ebe0937fa8d7cf86b6af211279df9c4", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pl": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "5a5e232471890776fc161e2512275f23caa95c519c7e533d6cc9f58f4029d85e", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "phi": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "fe4b6573a1544d9b0ff09b72c7ef9c1a9d9865f5fe608d598799b4d55ab27233", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt-BR": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "e108000f3fa7d1536ceff292245b5be696619683c85926a518ca771f99aae4e5", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pt": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "eb967d4e0a62c46fdcd8c2042de22762cc9046b1bbdf99c8a3e1d09652b6c91a", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "pa": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "cb159d7746161e3073344773ab7c24651f8c347661bdb10f1e4ffe11662041e2", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ro": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "709e69dc9bdd158113387de104fa49c4462a28995ef1632e8cc120b85a4e66bd", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "or": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "db6528c543d7b72c3a7fed2a1d03da8ac95d3a5f727d77e0e908d5e84261f6b1", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ru": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "6ffafe99a8f8bf8a7bc0b7165ec3078279e88b53f7a2010cc5f45015c5519e03", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sk": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "1dc0d303620aab31ea3b27b607de0a24eee7b2763317d3e8787ddf0624207ea8", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "si": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "ed2c5300605e7e3426069e7382366ce7ad2d8e399ba1ea2d898a62b5159b5e92", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sl": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "05e79387d68b7dd74188e2b694787811d41c988ca57aa77fb96757895d3d9705", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sv": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "1d38c3f3f7381f3f2af4d5b1a62e6080922df7ff3df2f2407095babaf28bf859", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sr": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "f36484f5b2612871e7727a52bccbdef46b2fe39c0a12f7db425a4b13966a2483", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "sw": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "56b8d82be224a553dfc714a985e38a9fa3adc4a47c7d342063e85b6ebe3725e6", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ta": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "b75d5cac919d289bb1993708705d0e81fdce6bed40f6026cfe64fddedfe73db5", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "te": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "0eeeaceb1801aaf8cabd6e9965b12611a18d159bb58858c73ac0ccb5455916e8", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "th": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "9b90b628300e9b24b476ae71d499d37ebae7145106144e6aa0cca03036a947ec", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "tr": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "43bc370b89da8948d493883769f7e656486b1411235e93aaef13c1680c491170", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uk-UA": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "5f8d93f91010ca06ea8d447d930f5fa2ab8c6cdcfd8b43852d2fb9da97316d35", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "ur": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "6ef235579f9732a3efe6054abc5e30d913d84fc9e13fd0153bfdfc8181ad610c", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "vi": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "9537336a934b64455d1132adf10b337f6669475688f8fa816c31e4175bc46bfc", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-CN": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "e2ba1255a03ec884e77ed3be270b564eaaad3fc7e8af370d518c7cbdb1991fc1", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "uz": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "423669a59b43bf0204d5dcddbec9d7028010c65c709f5d29f81176e9bfb0e767", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "zh-TW": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "cb444163c3494eae4e7e7192b4cddb2a9399e387fb3bc7ca9b373f18511fda35", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "yo": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "dcc02c1cdec935fddda5b6813a72c134520d861ad3df2d9528bf3ff816061797", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" }, "bs": { "source_hash": "fd7c59d293a5f1c8c7ee4aed51834f3e672715ccc50437202b1608433a26b47c", "target_hash": "5cb56e498976db79e2fe23b9e3f48b5a15c006031dc6dae0e8df8815bf8c780f", - "section_hashes": ["492c0eaf722e", "e3f403671566", "7231623ee278", "5f56c9ad1c18"], - "updated_at": "2026-10-02T07:07:52.756Z" + "updated_at": "2026-10-06T21:36:54.572Z" } } + }, + "docs/architecture/QUALITY_EVIDENCE_CONTRACT.md": { + "source_hash": "80e49dcdd48fac53a20fa6418387c6486984eca0128f3479dbbfc9a6dac2bbe9", + "locales": {} + }, + "docs/getting-started/SELF_HOST_GUIDE.md": { + "source_hash": "28aff6607573128598b854acb0add3380e0484ac746cd80e71ae4727d59a2e5e", + "locales": {} + }, + "docs/guides/CLI-COMPANION.md": { + "source_hash": "a1b998eceb890aa3a178281844462773ed9f51ad5edcc33d6c5e2c77a0101f6f", + "locales": {} + }, + "docs/guides/CLI-LAUNCH-CONTRACTS.md": { + "source_hash": "c1d95cf070093c6938f0cd3c131fc7f86395d56470beb291799d308450d71f42", + "locales": {} + }, + "docs/guides/CLI-MANUAL-MODELS.md": { + "source_hash": "43df8a749887c5f60054be622b8d79001fe89ecf7fcc151e241a7cc2ca53f6a4", + "locales": {} + }, + "docs/guides/CLI-SMOKE-TESTS.md": { + "source_hash": "e6c362a9ad1177040449b89fa1b86620dcdec37910b2c2dfc3d85e1283386e24", + "locales": {} + }, + "docs/guides/LOCAL_OLLAMA_DOCKER.md": { + "source_hash": "5d1033b68baf03889ca6e06720f60d074f962cffcacb98297605b1acdfb2f026", + "locales": {} + }, + "docs/guides/MODEL-VALIDATION.md": { + "source_hash": "d70cfc7735aa4e12fc194dfd6528e7617084e5ca0290bbe0ca800cff442f5184", + "locales": {} + }, + "docs/reference/OPENCODE-SESSION-IDENTITY.md": { + "source_hash": "1bd42a4bf85da55527f3efbf0f04f993791ef572cec0a0ea02b0f9e55a5667c0", + "locales": {} + }, + "docs/routing/DETERMINISTIC_ROUTING.md": { + "source_hash": "48032b2d079e62818c1c4d58764632a135db0d985744d92b6db34a824368d09d", + "locales": {} + }, + "docs/routing/SELF_HOSTED_OPENAI_ENTRY.md": { + "source_hash": "66275478bd92918f79d658254cec9ed782b9dec91ac8967245815af9d2b13270", + "locales": {} + }, + "docs/security/CLI-CONFIGURATION.md": { + "source_hash": "1c0098ef49753fb3a8e561428a040bd5643f5608b193b6101e26230207a88325", + "locales": {} + }, + "docs/security/INFERENCE_AUTH_POSTURE.md": { + "source_hash": "52cf72e6ab3d362d2cc8099b517845745e92faf785bac7f38611e49c4225b80b", + "locales": {} } } } diff --git a/@omniroute/opencode-plugin-v2/README.md b/@omniroute/opencode-plugin-v2/README.md index 5b3f3e143172..02ecad81e828 100644 --- a/@omniroute/opencode-plugin-v2/README.md +++ b/@omniroute/opencode-plugin-v2/README.md @@ -1,6 +1,6 @@ # @omniroute/opencode-plugin-v2 -OpenCode v2 plugin (`define({ id, setup })`, Promise API) that publishes the live OmniRoute catalog — models from `/v1/models`, combos from `/api/combos` (least-common-denominator join), auto-combos from `/api/combos/auto`, enrichment (names + pricing), and usable-provider filtering — into the v2 `catalog.transform`, with `key` + `env` auth via `integration.transform`. +OpenCode v2 plugin (`define({ id, setup })`, Promise API) that publishes the live OmniRoute catalog — models from `/v1/models`, combos from `/api/combos` (least-common-denominator join), enrichment (names + pricing), and usable-provider filtering — into the v2 `catalog.transform`, with `key` + `env` auth via `integration.transform`. Companion to `@omniroute/opencode-plugin` (OpenCode v1, same repo). The two packages are independent: this one carries its own catalog-mapping logic and the v1 plugin is left untouched. @@ -99,25 +99,41 @@ catalog is never a mystery. ## Options -| Key | Default | Notes | -| -------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| `providerId` | `"omniroute"` | Provider id and integration id; models publish under `/…` | -| `baseURL` | required | OmniRoute gateway root (no `/v1` suffix needed) | -| `apiKey` | connected credential, then `OMNIROUTE_API_KEY` | Chat key for `/v1/*` — see [Credentials](#credentials) | -| `managementReadToken` | option, then `OMNIROUTE_MANAGEMENT_API_KEY`, then `apiKey` | Management key for `/api/*` (combos, providers, enrichment) — usually **not** the same key | -| `displayName` | `"OmniRoute"` | Provider display name | -| `timeoutMs` | `10000` | Per-endpoint fetch timeout (auto-combos use 5s) | -| `modelCacheTtlMs` | `300000` | Catalog cache TTL; disk snapshot warms cold starts | -| `timeouts` | per-endpoint override | `{ models, combos, autoCombos, enrichment }` in ms; falls back to `timeoutMs` | -| `enrichment` | `true` | Fetch names + pricing (`/api/pricing*`, `/api/free-tier/summary`) | -| `providerTag` | `true` | Prefix a display name with the upstream provider it routes to | -| `geminiSanitization` | `true` | Strip `$schema`/`additionalProperties` from tool schemas sent to Gemini models (`$ref` tools are forwarded untouched) | -| `usableOnly` | `false` | Filter to healthy provisioned providers (`/api/providers`) | -| `visibleModels` / `hiddenModels` | `[]` | Exact-or-suffix allowlists, deny wins | -| `apiFormat.allowAnthropic` | `false` | Route allowlisted ids to the Anthropic API block | -| `apiFormat.anthropicModels` | `[]` | Full model ids routed to Anthropic | -| `apiFormat.anthropicPrefixes` | v1 defaults | Deprecated, warns once — prefer `anthropicModels` | -| `logLevel` / `startupDebug` | `warn` / `false` | Logger verbosity | +| Key | Default | Notes | +| --------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `providerId` | `"omniroute"` | Provider id and integration id; models publish under `/…` | +| `baseURL` | required | OmniRoute gateway root (no `/v1` suffix needed) | +| `apiKey` | connected credential, then `OMNIROUTE_API_KEY` | Chat key for `/v1/*` — see [Credentials](#credentials) | +| `managementReadToken` | option, then `OMNIROUTE_MANAGEMENT_API_KEY`, then `apiKey` | Management key for `/api/*` (combos, providers, enrichment) — usually **not** the same key | +| `displayName` | `"OmniRoute"` | Provider display name | +| `timeoutMs` | `10000` | Per-endpoint fetch timeout | +| `modelCacheTtlMs` | `300000` | Catalog cache TTL; disk snapshot warms cold starts | +| `timeouts` | per-endpoint override | `{ models, combos, enrichment }` in ms; falls back to `timeoutMs` (`autoCombos` still parses but is ignored — the virtual-entries route is no longer requested) | +| `enrichment` | `true` | Fetch names + pricing (`/api/pricing*`, `/api/free-tier/summary`) | +| `providerTag` | `true` | Prefix a display name with the upstream provider it routes to | +| `geminiSanitization` | `true` | Strip `$schema`/`additionalProperties` from tool schemas sent to Gemini models (`$ref` tools are forwarded untouched) | +| `usableOnly` | `false` | Filter to healthy provisioned providers (`/api/providers`) | +| `freeOnly` / `toolsOnly` / `visionOnly` | `false` / `true` / `false` | Filter to free-tier / tool-calling / image-input models; combos with a filtered member are dropped, never partial | +| `showcasePerOwner` | `10` | Default-view entries kept per provider | +| `freshPerOwner` | `10` | Default-view fresh entries kept per provider | +| `freshWindowDays` | `90` | Freshness window in days for the fresh branch | +| `usageMemory` | `true` | Restore statically dropped entries named by 30-day usage analytics (needs a management token; inert without one) | +| `visibleModels` / `hiddenModels` | `[]` | Exact-or-suffix allowlists, deny wins | +| `providersAllow` | `[]` | Provider allowlist by id prefix (empty = full catalog); unknown names warn once, empty result publishes empty + warns. Unknown catalog prefixes always pass, and an allowlist or vocabulary that names nothing known keeps the full catalog + warns | +| `apiFormat.allowAnthropic` | `false` | Route allowlisted ids to the Anthropic API block | +| `apiFormat.anthropicModels` | `[]` | Full model ids routed to Anthropic | +| `apiFormat.anthropicPrefixes` | v1 defaults | Deprecated, warns once — prefer `anthropicModels` | +| `logLevel` / `startupDebug` | `warn` / `false` | Logger verbosity | + +`toolsOnly` is on by default: agent sessions call tools on almost every turn, so +models without tool calling are hidden from the host picker. This removes models +an agent cannot use; it does not cap the catalog size, and a very large catalog +can still slow the host picker (see +[opencode#47615](https://github.com/anomalyco/opencode/issues/47615)). Set +`toolsOnly: false` to publish the full catalog. + +Usage memory is on by default. Without a management token it stays inert +(a startup notice is logged) and nothing is restored — the picker stays filtered. ## Tool calling on Gemini models diff --git a/@omniroute/opencode-plugin-v2/package-lock.json b/@omniroute/opencode-plugin-v2/package-lock.json index 0bc1f919b3a8..7a531eb54d93 100644 --- a/@omniroute/opencode-plugin-v2/package-lock.json +++ b/@omniroute/opencode-plugin-v2/package-lock.json @@ -2019,88 +2019,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@redis/bloom": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-6.2.1.tgz", - "integrity": "sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 20.0.0" - }, - "peerDependencies": { - "@redis/client": "^6.2.1" - } - }, - "node_modules/@redis/client": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-6.2.1.tgz", - "integrity": "sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "cluster-key-slot": "1.1.2" - }, - "engines": { - "node": ">= 20.0.0" - }, - "peerDependencies": { - "@node-rs/xxhash": "^1.1.0", - "@opentelemetry/api": ">=1 <2" - }, - "peerDependenciesMeta": { - "@node-rs/xxhash": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@redis/json": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-6.2.1.tgz", - "integrity": "sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 20.0.0" - }, - "peerDependencies": { - "@redis/client": "^6.2.1" - } - }, - "node_modules/@redis/search": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-6.2.1.tgz", - "integrity": "sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 20.0.0" - }, - "peerDependencies": { - "@redis/client": "^6.2.1" - } - }, - "node_modules/@redis/time-series": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-6.2.1.tgz", - "integrity": "sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 20.0.0" - }, - "peerDependencies": { - "@redis/client": "^6.2.1" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -3016,17 +2934,6 @@ "node": ">=8" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cmd-shim": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", @@ -3526,9 +3433,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", "dev": true, "license": "BSD-2-Clause" }, @@ -4591,24 +4498,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/redis": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/redis/-/redis-6.2.1.tgz", - "integrity": "sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@redis/bloom": "6.2.1", - "@redis/client": "6.2.1", - "@redis/json": "6.2.1", - "@redis/search": "6.2.1", - "@redis/time-series": "6.2.1" - }, - "engines": { - "node": ">= 20.0.0" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "dev": true, diff --git a/@omniroute/opencode-plugin-v2/src/cache.ts b/@omniroute/opencode-plugin-v2/src/cache.ts index b6a950449c0e..3b56396f8f38 100644 --- a/@omniroute/opencode-plugin-v2/src/cache.ts +++ b/@omniroute/opencode-plugin-v2/src/cache.ts @@ -6,7 +6,6 @@ import type { OmniRouteEnrichmentEntry, OmniRouteEnrichmentMap, OmniRouteProviderConnection, - OmniRouteRawAutoCombo, OmniRouteRawCombo, OmniRouteRawModelEntry, } from "./shared/index.js"; @@ -25,7 +24,6 @@ export const UNREACHABLE_COOLDOWN_MS = 15_000 as const; export interface CatalogSnapshot { models: OmniRouteRawModelEntry[]; combos: OmniRouteRawCombo[]; - autoCombos: OmniRouteRawAutoCombo[]; providers?: OmniRouteProviderConnection[]; enrichment?: OmniRouteEnrichmentMap; fetchedAt: number; @@ -61,7 +59,6 @@ interface DiskSnapshotV2 { identityFingerprint: string; models: OmniRouteRawModelEntry[]; combos: OmniRouteRawCombo[]; - autoCombos?: OmniRouteRawAutoCombo[]; providers?: OmniRouteProviderConnection[]; /** * Display names, provider labels, pricing and free-tier budgets, as @@ -135,7 +132,12 @@ export async function readDiskSnapshot( ): Promise { try { const body = await readFile(diskSnapshotPath(providerId), "utf8"); - const parsed = JSON.parse(body) as Partial; + // A retired snapshot field survives JSON.parse as an unknown key and is + // never read back, so legacy snapshots load with it ignored. + const parsed = JSON.parse(body) as Partial & { + autoCombos?: unknown; + }; + void (parsed as { autoCombos?: unknown }).autoCombos; if ( !parsed || typeof parsed.v !== "number" || @@ -157,15 +159,14 @@ export async function readDiskSnapshot( (entry) => !isStaleSnapshotModel(entry) ); if (stale > 0) { - logger?.warn(`[omniroute-v2] dropping ${stale} stale snapshot entries with an unusable api block`); + logger?.warn( + `[omniroute-v2] dropping ${stale} stale snapshot entries with an unusable api block` + ); } if (models.length === 0) return undefined; return { models, combos: parsed.combos as OmniRouteRawCombo[], - autoCombos: Array.isArray(parsed.autoCombos) - ? (parsed.autoCombos as OmniRouteRawAutoCombo[]) - : [], providers: Array.isArray(parsed.providers) ? (parsed.providers as OmniRouteProviderConnection[]) : [], @@ -202,7 +203,6 @@ export async function writeDiskSnapshot( identityFingerprint, models: snapshot.models, combos: snapshot.combos, - autoCombos: snapshot.autoCombos, providers: snapshot.providers ?? [], enrichment: snapshot.enrichment ? [...snapshot.enrichment.entries()] : undefined, writtenAt: Date.now(), diff --git a/@omniroute/opencode-plugin-v2/src/capability-presets.ts b/@omniroute/opencode-plugin-v2/src/capability-presets.ts new file mode 100644 index 000000000000..af627bee7abd --- /dev/null +++ b/@omniroute/opencode-plugin-v2/src/capability-presets.ts @@ -0,0 +1,31 @@ +import type { LegacyModel } from "./legacy-model.js"; +import type { OmniRouteEnrichmentEntry } from "./shared/enrich.js"; + +export interface CapabilityPresetFlags { + freeOnly?: boolean; + toolsOnly?: boolean; + visionOnly?: boolean; +} + +/** + * Single subtractive predicate for the capability presets. Reads the + * mapped+enriched entry: `freeOnly` needs the enrichment overlay entry + * (absent entry = not free = dropped), `toolsOnly`/`visionOnly` read the + * mapped capabilities (proven 1:1 with the raw signals). Applied after the + * allowlist at the model and combo collection points. Auto combos are not + * published (#15392), so there is no third point to filter. + */ +export function passesCapabilityPresets( + mapped: LegacyModel, + enrichment: OmniRouteEnrichmentEntry | undefined, + flags: CapabilityPresetFlags +): boolean { + if (flags.freeOnly === true && enrichment?.freeType === undefined) return false; + if (flags.toolsOnly === true && mapped.capabilities.toolcall !== true) return false; + if ( + flags.visionOnly === true && + !(mapped.capabilities.attachment === true || mapped.capabilities.input.image === true) + ) + return false; + return true; +} diff --git a/@omniroute/opencode-plugin-v2/src/catalog-freshness.ts b/@omniroute/opencode-plugin-v2/src/catalog-freshness.ts new file mode 100644 index 000000000000..9e1f1f97d6f9 --- /dev/null +++ b/@omniroute/opencode-plugin-v2/src/catalog-freshness.ts @@ -0,0 +1,246 @@ +import type { OmniRouteRawModelEntry } from "./shared/index.js"; + +/** + * Freshness window in days. The millisecond value below derives from it, + * so tuning the default means touching this line only. + */ +export const FRESH_WINDOW_DAYS = 90; + +/** + * Default freshness window in milliseconds. Models curated with a + * `release_date` or `last_updated` inside this window publish without any + * other signal. + */ +export const FRESHNESS_WINDOW_MS = FRESH_WINDOW_DAYS * 24 * 3600 * 1000; + +/** How many models each provider keeps in the default showcase. */ +export const SHOWCASE_PER_OWNER = 10; + +/** How many recent models each provider keeps in the default view. */ +export const FRESH_PER_OWNER = 10; + +/** + * Convert a freshness window in days to milliseconds. A non-finite or + * non-positive value cannot come through parsed options (the schema rejects + * it), only through direct calls: fall back to the default window instead + * of an unbounded (Infinity) or empty (0) one. + */ +export function freshWindowDaysToMs(days: number): number { + if (!Number.isFinite(days) || days <= 0) return FRESHNESS_WINDOW_MS; + return days * 24 * 3600 * 1000; +} + +/** + * Fresh when the newest valid curatorial date (`release_date`, then + * `last_updated`) is inside the window. No fallback to `created`: + * gateway-assembled rows carry the build timestamp there (same value for + * the whole catalog), except openrouter rows which mix an upstream value + * — neither works as a uniform freshness date. + */ +export function isFreshModel( + entry: OmniRouteRawModelEntry, + nowMs: number, + windowMs: number = FRESHNESS_WINDOW_MS +): boolean { + if (!Number.isFinite(windowMs) || windowMs <= 0) windowMs = FRESHNESS_WINDOW_MS; + const candidates = [entry.release_date, entry.last_updated]; + let newest = Number.NaN; + for (const raw of candidates) { + if (typeof raw !== "string" || raw.length === 0) continue; + const parsed = Date.parse(raw); + if (Number.isNaN(parsed)) continue; + if (Number.isNaN(newest) || parsed > newest) newest = parsed; + } + if (Number.isNaN(newest)) return false; + if (newest > nowMs) return false; + return nowMs - newest <= windowMs; +} + +/** + * Owner group key: explicit `owned_by` first, then the id prefix before + * `/`, else `"unknown"`. Shared by the showcase ranking and the fresh cap + * so both count the same groups. + */ +export function ownerKeyOf(entry: OmniRouteRawModelEntry): string { + if (typeof entry.owned_by === "string" && entry.owned_by.length > 0) return entry.owned_by; + if (typeof entry.id === "string" && entry.id.includes("/")) + return entry.id.slice(0, entry.id.indexOf("/")); + return "unknown"; +} + +/** + * Recency order shared by the showcase ranking and the fresh cap: newest + * `release_date` first (unknown = oldest), then largest `context_length`. + */ +export function compareByRecency(a: OmniRouteRawModelEntry, b: OmniRouteRawModelEntry): number { + const aDate = typeof a.release_date === "string" ? Date.parse(a.release_date) : NaN; + const bDate = typeof b.release_date === "string" ? Date.parse(b.release_date) : NaN; + const aMs = Number.isNaN(aDate) ? 0 : aDate; + const bMs = Number.isNaN(bDate) ? 0 : bDate; + if (aMs !== bMs) return bMs - aMs; + const aCtx = typeof a.context_length === "number" ? a.context_length : 0; + const bCtx = typeof b.context_length === "number" ? b.context_length : 0; + return bCtx - aCtx; +} + +/** + * Read the lifecycle hint the raw gateway row carries, if any. The typed + * entry has no `status` field, so this stays a defensive local read: only + * the exact `"deprecated"` string excludes, anything else (absent, + * mistyped, other value) keeps the entry. + */ +export function readEntryStatus(entry: OmniRouteRawModelEntry): unknown { + return (entry as { status?: unknown }).status; +} + +/** + * Flat effort-variant ids the gateway publishes next to their base model + * (e.g. `owner/model-high` beside `owner/model`). Only these seven + * suffixes exclude; any other ending (provider literals) leaves the id + * alone. Returns the base id, or undefined when there is no such suffix. + */ +const FLAT_TIER_SUFFIXES = new Set(["high", "low", "max", "medium", "xhigh", "minimal", "none"]); + +export function baseIdOf(id: string): string | undefined { + const slash = id.lastIndexOf("/"); + const short = slash >= 0 ? id.slice(slash + 1) : id; + const dash = short.lastIndexOf("-"); + if (dash <= 0) return undefined; + const suffix = short.slice(dash + 1).toLowerCase(); + if (!FLAT_TIER_SUFFIXES.has(suffix)) return undefined; + const base = short.slice(0, dash); + if (base.length === 0) return undefined; + return slash >= 0 ? id.slice(0, slash + 1) + base : base; +} + +/** True when the id is a flat effort-variant id (see `baseIdOf`). */ +export function isFlatTierId(id: string): boolean { + return baseIdOf(id) !== undefined; +} + +/** + * Pick the showcase ids: per `owned_by` group (prefix before `/`, else + * `"unknown"`), newest `release_date` first (unknown = oldest), then + * largest `context_length`. Providers surface at runtime; nothing is + * hardcoded here. + */ +export function selectShowcaseIds( + entries: readonly OmniRouteRawModelEntry[], + perOwner: number = SHOWCASE_PER_OWNER +): Set { + const groups = new Map(); + for (const entry of entries) { + if (!entry || typeof entry.id !== "string" || entry.id.length === 0) continue; + const owner = ownerKeyOf(entry); + const list = groups.get(owner); + if (list) list.push(entry); + else groups.set(owner, [entry]); + } + const out = new Set(); + // A non-finite or non-positive size cannot come through parsed options + // (the schema rejects it), only through direct calls: fall back instead + // of publishing an unbounded group (Infinity) or an empty one (0). + const perOwnerCount = Number.isFinite(perOwner) && perOwner > 0 ? perOwner : SHOWCASE_PER_OWNER; + for (const list of groups.values()) { + const ranked = [...list].sort(compareByRecency); + for (const entry of ranked.slice(0, perOwnerCount)) out.add(entry.id); + } + return out; +} + +/** + * Static pass: allowlisted (exact id or short suffix) first, then the + * default view — retired entries and flat effort-variant ids stay out, + * recent entries only up to the per-owner fresh cap, showcased ids pass. + * A pin always wins over every exclusion below. + */ +export function passesWhatServes( + entry: OmniRouteRawModelEntry, + showcased: ReadonlySet, + visibleFilter: ModelListFilter | undefined, + nowMs: number = Date.now(), + freshKept?: ReadonlySet, + windowMs: number = FRESHNESS_WINDOW_MS +): boolean { + if (visibleFilter !== undefined) { + if (visibleFilter.exact.has(entry.id)) return true; + if (matchesSuffix(entry.id, visibleFilter.suffixes)) return true; + } + // The raw gateway row may carry a lifecycle hint the typed entry does + // not declare (it already flows through the fetcher untouched). + if (readEntryStatus(entry) === "deprecated") return false; + if (isFlatTierId(entry.id)) return false; + if (isFreshModel(entry, nowMs, windowMs)) { + if (freshKept !== undefined) return freshKept.has(entry.id); + return true; + } + if (showcased.has(entry.id)) return true; + return false; +} + +/** + * Rank recent entries per owner (same group key and order as the + * showcase) and keep up to `perOwner` ids per group. Only + * `isFreshModel`-true entries compete; pinned, retired and flat + * effort-variant ids never take a fresh slot. + */ +export function selectFreshKeptIds( + entries: readonly OmniRouteRawModelEntry[], + perOwner: number = FRESH_PER_OWNER, + nowMs: number = Date.now(), + visibleFilter?: ModelListFilter, + windowMs: number = FRESHNESS_WINDOW_MS +): Set { + const perOwnerCount = Number.isFinite(perOwner) && perOwner > 0 ? perOwner : FRESH_PER_OWNER; + const groups = new Map(); + for (const entry of entries) { + if (!entry || typeof entry.id !== "string" || entry.id.length === 0) continue; + if (readEntryStatus(entry) === "deprecated") continue; + if (isFlatTierId(entry.id)) continue; + if (visibleFilter !== undefined) { + if (visibleFilter.exact.has(entry.id)) continue; + if (matchesSuffix(entry.id, visibleFilter.suffixes)) continue; + } + if (!isFreshModel(entry, nowMs, windowMs)) continue; + const owner = ownerKeyOf(entry); + const list = groups.get(owner); + if (list) list.push(entry); + else groups.set(owner, [entry]); + } + const out = new Set(); + for (const list of groups.values()) { + const ranked = [...list].sort(compareByRecency); + for (const entry of ranked.slice(0, perOwnerCount)) out.add(entry.id); + } + return out; +} + +/** + * Whether a statically dropped entry stays dropped under the usage + * memory: retired entries, flat effort-variant ids and entries past the + * per-owner fresh cap are never restored. Pinned ids never reach this + * filter (they pass the static gate already). + */ +export function staysDropped( + entry: OmniRouteRawModelEntry, + freshKept: ReadonlySet, + nowMs: number = Date.now(), + windowMs: number = FRESHNESS_WINDOW_MS +): boolean { + if (readEntryStatus(entry) === "deprecated") return true; + if (isFlatTierId(entry.id)) return true; + if (isFreshModel(entry, nowMs, windowMs) && !freshKept.has(entry.id)) return true; + return false; +} + +export interface ModelListFilter { + exact: Set; + suffixes: Set; +} + +export function matchesSuffix(id: string, suffixes: Set): boolean { + if (suffixes.size === 0) return false; + const slash = id.indexOf("/"); + const suffix = slash > 0 ? id.slice(slash + 1) : id; + return suffixes.has(suffix); +} diff --git a/@omniroute/opencode-plugin-v2/src/catalog.ts b/@omniroute/opencode-plugin-v2/src/catalog.ts index 5c3089b3ef9c..8c37b5fb4eb8 100644 --- a/@omniroute/opencode-plugin-v2/src/catalog.ts +++ b/@omniroute/opencode-plugin-v2/src/catalog.ts @@ -1,18 +1,28 @@ import type { Model, Provider } from "@opencode/plugin"; +import { + buildProviderResolve, + compileProviderFilter, + filterAllUnknown, + passesProviderCombo, + passesProviderFilter, + warnNoVocabulary, + warnUnknownProviders, + type ProviderFilter, + type ProviderResolve, +} from "./provider-filter.js"; import type { LegacyModel } from "./legacy-model.js"; +import { type CapabilityPresetFlags, passesCapabilityPresets } from "./capability-presets.js"; import { isHttpUrl, type ApiFormatV2, type LogLevel, type Logger, - type OmniRouteAutoCombosFetcher, type OmniRouteCombosFetcher, type OmniRouteEnrichmentFetcher, type OmniRouteEnrichmentMap, type OmniRouteModelsFetcher, type OmniRouteProviderConnection, type OmniRouteProvidersFetcher, - type OmniRouteRawAutoCombo, type OmniRouteRawCombo, type OmniRouteRawModelEntry, applyEnrichment, @@ -25,22 +35,51 @@ import { isUsableCombo, isUsableRawModelId, lookupEnrichment, - mapAutoComboToModelV2, mapComboToModelV2, mapRawModelToModelV2, usableProviderAliasSet, } from "./shared/index.js"; +import { + FRESH_PER_OWNER, + FRESH_WINDOW_DAYS, + SHOWCASE_PER_OWNER, + baseIdOf, + freshWindowDaysToMs, + isFlatTierId, + matchesSuffix, + passesWhatServes, + readEntryStatus, + selectFreshKeptIds, + selectShowcaseIds, + staysDropped, +} from "./catalog-freshness.js"; +export { + FRESH_PER_OWNER, + FRESH_WINDOW_DAYS, + FRESHNESS_WINDOW_MS, + SHOWCASE_PER_OWNER, + baseIdOf, + compareByRecency, + freshWindowDaysToMs, + isFlatTierId, + isFreshModel, + ownerKeyOf, + passesWhatServes, + selectFreshKeptIds, + selectShowcaseIds, + staysDropped, +} from "./catalog-freshness.js"; +export type { ModelListFilter } from "./catalog-freshness.js"; +import type { ModelListFilter } from "./catalog-freshness.js"; export type ModelsFetcher = OmniRouteModelsFetcher; export type CombosFetcher = OmniRouteCombosFetcher; -export type AutoCombosFetcher = OmniRouteAutoCombosFetcher; export type ProvidersFetcher = OmniRouteProvidersFetcher; export type EnrichmentFetcher = OmniRouteEnrichmentFetcher; export interface EndpointTimeouts { models?: number; combos?: number; - autoCombos?: number; enrichment?: number; } @@ -61,8 +100,33 @@ export interface ResolvedOptions { apiFormat?: ApiFormatV2; visibleModels?: string[]; hiddenModels?: string[]; + providersAllow?: string[]; usableOnly: boolean; + freeOnly?: boolean; + toolsOnly?: boolean; + visionOnly?: boolean; enrichment?: OmniRouteEnrichmentMap | boolean; + /** + * Per-provider showcase size. Absent means the catalog default + * (`SHOWCASE_PER_OWNER`); direct callers keep working without it. + */ + showcasePerOwner?: number; + /** + * Per-provider fresh cap. Absent means the catalog default + * (`FRESH_PER_OWNER`); direct callers keep working without it. + */ + freshPerOwner?: number; + /** + * Freshness window in days. Absent means the catalog default (FRESH_WINDOW_DAYS); + * direct callers keep working without it. + */ + freshWindowDays?: number; + /** + * Usage memory over the statically dropped entries. Absent means + * enabled (default on); false disables it. Needs a management token — + * without one the memory stays inert. + */ + usageMemory?: boolean; /** * Shared collision-warning dedupe set keyed `cacheKey::comboKey`. When * omitted a fresh per-publish set is used. index.ts passes one setup-wide @@ -74,14 +138,20 @@ export interface ResolvedOptions { export interface CatalogFetchers { fetcher?: ModelsFetcher; combosFetcher?: CombosFetcher; - autoCombosFetcher?: AutoCombosFetcher; providersFetcher?: ProvidersFetcher; enrichmentFetcher?: EnrichmentFetcher; models?: ModelsFetcher; combos?: CombosFetcher; - autoCombos?: AutoCombosFetcher; providers?: ProvidersFetcher; enrichment?: EnrichmentFetcher; + /** + * Resolves the model ids used in the last 30 days from the gateway + * usage analytics. Injected so unit tests can stub it; defaults to + * a single `GET /api/usage/analytics?range=30d` call. + */ + usageFetcher?: OmniRouteUsageFetcher; + /** Alias of `usageFetcher`, same shape as the other fetcher pairs. */ + usage?: OmniRouteUsageFetcher; /** * Called when a gateway source cannot be read. Without it this function * degrades silently — the catalog publishes with raw ids and no combos and @@ -90,9 +160,158 @@ export interface CatalogFetchers { onSourceError?: (endpoint: string, reason: string) => void; } +/** Contract for the 30-day usage memory: model ids only, never payloads. */ +export type OmniRouteUsageFetcher = ( + rootBaseURL: string, + managementToken: string, + timeoutMs?: number, + onSourceError?: (endpoint: string, reason: string) => void +) => Promise; + +function trimUsageSlashes(value: string): string { + let i = value.length; + while (i > 0 && value.charCodeAt(i - 1) === 0x2f /* "/" */) i--; + return i === value.length ? value : value.slice(0, i); +} + +/** + * Default usage fetcher: one `GET /api/usage/analytics?range=30d` + * call with the management token, reading `byModel[].model` (plus + * `rawModel` when present) as plain identifiers. Refusals, network + * errors and a 2xx without a `byModel` table THROW so the caller keeps + * the statically dropped entries; only a 2xx array (even empty) + * narrows the catalog. + */ +export const defaultOmniRouteUsageFetcher: OmniRouteUsageFetcher = async ( + rootBaseURL, + managementToken, + timeoutMs = 10_000, + onSourceError +) => { + if (!rootBaseURL) throw new Error("[omniroute-v2] baseURL required to fetch usage analytics"); + if (!managementToken) throw new Error("[omniroute-v2] management token required"); + const trimmed = trimUsageSlashes(rootBaseURL); + const root = trimmed.replace(/\/v\d+$/, ""); + const url = `${root}/api/usage/analytics?range=30d`; + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + const res = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${managementToken}`, + Accept: "application/json", + }, + signal: controller.signal, + }); + if (!res.ok) { + onSourceError?.("/api/usage/analytics", `HTTP ${res.status}`); + throw new Error(`[omniroute-v2] GET ${url} failed: ${res.status} ${res.statusText}`); + } + const body = (await res.json()) as unknown; + const table = (body as { byModel?: unknown } | null | undefined)?.byModel; + if (!Array.isArray(table)) { + onSourceError?.("/api/usage/analytics", "missing byModel table"); + throw new Error("[omniroute-v2] usage analytics response has no byModel table"); + } + const rows = table as unknown[]; + const out: string[] = []; + for (const row of rows) { + if (!row || typeof row !== "object") continue; + const model = (row as { model?: unknown }).model; + const rawModel = (row as { rawModel?: unknown }).rawModel; + if (typeof model === "string" && model.length > 0) out.push(model); + if (typeof rawModel === "string" && rawModel.length > 0 && rawModel !== model) + out.push(rawModel); + } + return out; + } catch (err) { + throw err instanceof Error ? err : new Error(String(err)); + } finally { + clearTimeout(timer); + } +}; + +/** + * Resolve the usage memory over the statically dropped entries only. + * No dropped entries (or no management token) means no call at all. + * Success returns the lowercased id set (empty set = the gateway + * answered "no usage", a narrowed fallback); failure warns once and + * returns an empty set, so a down gateway never reopens the picker. + */ +async function resolveUsageIds( + dropped: readonly OmniRouteRawModelEntry[], + opts: ResolvedOptions, + fetchers: CatalogFetchers | undefined, + log: Logger +): Promise> { + if (dropped.length === 0) return new Set(); + if (!opts.managementReadToken) return new Set(); + const fetchUsage = fetchers?.usageFetcher ?? fetchers?.usage ?? defaultOmniRouteUsageFetcher; + let used: string[]; + try { + used = await fetchUsage( + opts.baseURL, + opts.managementReadToken, + opts.timeoutMs, + fetchers?.onSourceError + ); + } catch (err) { + log.warn( + `[omniroute-v2] usage fetch failed, no dropped models restored: ${err instanceof Error ? err.message : String(err)}` + ); + return new Set(); + } + const out = new Set(); + const capped = used.slice(0, 50); + for (const id of capped) { + if (typeof id !== "string" || id.length === 0) continue; + out.add(id.toLowerCase()); + } + return out; +} + +/** + * True on an exact usage hit, or when usage names a flat variant of this + * base entry: the base restores in place of its flat, never the flat + * itself (flats stay excluded by the `restorable` filter above). + */ +function matchesUsage(id: string, used: ReadonlySet): boolean { + const lower = id.toLowerCase(); + if (used.has(lower)) return true; + if (isFlatTierId(id)) return false; + for (const name of used) { + if (baseIdOf(name) === lower) return true; + } + return false; +} + export type StableModelInfo = Model.Info; export type StableProviderInfo = Provider.Info; +/** + * Every path the refresh flow may request. The removed virtual-entries route + * is absent by construction: no fetcher may build it, and the route-guard + * test below asserts no stubbed call ever matches it. + */ +export const DECLARED_REFRESH_ROUTES = [ + "/v1/models", + "/api/combos", + "/api/providers", + "/api/pricing/models", + "/api/pricing", + "/api/free-tier/summary", +] as const; + +/** + * Whether a request pathname belongs to the declared refresh set. Exact + * equality on purpose: a prefix check would re-admit the removed route via + * its `/api/combos/` prefix. + */ +export function isDeclaredRefreshPath(pathname: string): boolean { + return (DECLARED_REFRESH_ROUTES as readonly string[]).includes(pathname); +} + /** * Structural mirror of the stable `ctx.provider.transform` editor, used as * the parameter type where the payload is handed to the host (and in tests @@ -169,9 +388,7 @@ export function legacyToStable( capabilities: { tools: m.capabilities.toolcall, input, output }, variants, time: { released: Number.isNaN(parsed) ? 0 : parsed }, - cost: [ - { input: m.cost.input, output: m.cost.output, cache: { ...m.cost.cache } }, - ], + cost: [{ input: m.cost.input, output: m.cost.output, cache: { ...m.cost.cache } }], status: m.status, enabled: true, limit: { ...m.limit }, @@ -262,12 +479,6 @@ function legacyToInfo(providerID: string, modelID: string, m: LegacyModel): Stab export interface PublishCounts { models: number; combos: number; - autoCombos: number; -} - -export interface ModelListFilter { - exact: Set; - suffixes: Set; } export function compileModelListFilter(list?: string[]): ModelListFilter | undefined { @@ -285,13 +496,6 @@ export function compileModelListFilter(list?: string[]): ModelListFilter | undef return { exact, suffixes }; } -function matchesSuffix(id: string, suffixes: Set): boolean { - if (suffixes.size === 0) return false; - const slash = id.indexOf("/"); - const suffix = slash > 0 ? id.slice(slash + 1) : id; - return suffixes.has(suffix); -} - export function passesModelAllowlist( id: string, visible?: ModelListFilter, @@ -450,9 +654,13 @@ interface PublishContext { hiddenFilter: ReturnType; usable: ReturnType | undefined; canonicalToAlias: ReturnType; + providerFilter: ProviderFilter | undefined; + providerResolve: ProviderResolve | undefined; + /** Precomputed once per publish: every allow entry sits outside the vocabulary. */ + providerAllUnknown: boolean; combosFetcher: CatalogFetchers["combos"] | undefined; combosTimeout: number; - /** Shared with the auto-combos pass: one collision warning per key, per run. */ + /** Shared with the combos pass: one collision warning per key, per run. */ warnedCombos: Set; cacheKey: string; } @@ -464,10 +672,13 @@ interface PublishContext { * are dropped rather than published with a fabricated capability set, and * reported once. * - * Returns the number published, or `undefined` when the combos fetch failed — - * the caller then publishes a models-only catalog instead of an empty one. + * Returns the published and provider-dropped counts, or `undefined` when the + * combos fetch failed — the caller then publishes a models-only catalog + * instead of an empty one. */ -async function publishCombos(ctx: PublishContext): Promise { +async function publishCombos( + ctx: PublishContext +): Promise<{ published: number; providerDropped: number } | undefined> { const { opts, log, @@ -481,6 +692,9 @@ async function publishCombos(ctx: PublishContext): Promise { hiddenFilter, usable, canonicalToAlias, + providerFilter, + providerResolve, + providerAllUnknown, combosFetcher, combosTimeout, warnedCombos, @@ -499,6 +713,7 @@ async function publishCombos(ctx: PublishContext): Promise { } let comboCount = 0; + let providerDropped = 0; // Ported from v1 (fixpoint 8 passes + warn once per (cacheKey, comboKey) // + intentional-dedup exception). Nested combo-refs resolve against the // friendly combo name; unresolvable combos are dropped (never published @@ -512,6 +727,10 @@ async function publishCombos(ctx: PublishContext): Promise { // Deny wins for combos too: a user who hides an id expects it gone from // the picker whether it is a model or a combo built on it. if (hiddenFilter && passesComboAllowlist(combo, hiddenFilter)) return false; + if (!passesProviderCombo(combo, providerFilter, providerResolve, providerAllUnknown)) { + if (providerFilter) providerDropped += 1; + return false; + } return true; }); const resolvedByName = new Map(); @@ -546,9 +765,18 @@ async function publishCombos(ctx: PublishContext): Promise { continue; } const mapped = mapComboToModelV2(combo, memberEntries, X, opts.baseURL, opts.apiFormat); - applyEnrichment(mapped, lookupEnrichment(combo.id, enrichment, canonicalToAlias), { + const comboEnrichment = lookupEnrichment(combo.id, enrichment, canonicalToAlias); + applyEnrichment(mapped, comboEnrichment, { isCombo: true, }); + if ( + !passesCapabilityPresets(mapped, comboEnrichment, { + freeOnly: opts.freeOnly, + toolsOnly: opts.toolsOnly, + visionOnly: opts.visionOnly, + } satisfies CapabilityPresetFlags) + ) + continue; const mid = mapped.id.startsWith(X + "/") ? mapped.id.slice(X.length + 1) : mapped.id; const key = X + "/" + mid; if (publishedKeys.has(key)) { @@ -598,7 +826,7 @@ async function publishCombos(ctx: PublishContext): Promise { `[omniroute-v2] ${unresolved.length} combo(s) could not resolve all nested combo-refs after ${MAX_COMBO_PASSES} passes; dropped to avoid over-claiming.` ); } - return comboCount; + return { published: comboCount, providerDropped }; } /** @@ -643,7 +871,7 @@ function synthesizeNestedMember(name: string, nested: LegacyModel): OmniRouteRaw } /** - * Collect the full catalog (models + combos + auto-combos) as legacy entries + * Collect the full catalog (models + combos) as legacy entries * keyed `providerId/bareId`, then project them onto the stable contract in * `buildProviderPayload`. Collect-then-project keeps every fetch/filter/LCD * behavior identical to the beta path while the only host touchpoint is the @@ -662,19 +890,14 @@ export async function collectCatalog( const log = opts.logger ?? createLogger(opts.startupDebug ? "debug" : (opts.logLevel ?? "warn")); const modelsTimeout = opts.timeouts?.models ?? opts.timeoutMs; const combosTimeout = opts.timeouts?.combos ?? opts.timeoutMs; - // v1 parity keeps the 5s auto-combos budget when no per-endpoint value is - // set (P2 resolves it in index.ts; direct publishCatalog callers may only - // pass timeoutMs). - const autoCombosTimeout = opts.timeouts?.autoCombos ?? 5_000; const modelsFetcher = fetchers?.fetcher ?? fetchers?.models; const combosFetcher = fetchers?.combosFetcher ?? fetchers?.combos; - const autoCombosFetcher = fetchers?.autoCombosFetcher ?? fetchers?.autoCombos; const providersFetcher = fetchers?.providersFetcher ?? fetchers?.providers; const empty: CollectedCatalog = { entries: new Map(), - counts: { models: 0, combos: 0, autoCombos: 0 }, + counts: { models: 0, combos: 0 }, }; let rawModels: OmniRouteRawModelEntry[]; try { @@ -686,12 +909,30 @@ export async function collectCatalog( return empty; } - const visibleFilter = compileModelListFilter(opts.visibleModels); + const catalogAll = opts.visibleModels?.includes("*") === true; + const visibleFilter = catalogAll ? undefined : compileModelListFilter(opts.visibleModels); const hiddenFilter = compileModelListFilter(opts.hiddenModels); const enrichment = await resolveEnrichmentOverlay(opts, fetchers, log); const canonicalToAlias = buildCanonicalToAliasMap(enrichment); const canonicalDedup = canonicalDedupSet(rawModels, canonicalToAlias); + // `freeOnly` reads the overlay: an empty overlay (no management token, + // `enrichment: false`, or fetch failure) would otherwise empty the catalog + // silently. Warn once per refresh and keep filtering (fail-closed). + if (opts.freeOnly === true) { + let hasFreeEntry = false; + for (const entry of enrichment.values()) { + if (entry.freeType !== undefined) { + hasFreeEntry = true; + break; + } + } + if (!hasFreeEntry) { + log.warn( + `[omniroute-v2] freeOnly is on but the enrichment overlay has no free-tier entries (no management token, enrichment disabled, or free-tier fetch failed); publishing an empty catalog. Disable freeOnly or configure the management token.` + ); + } + } const usable = await resolveUsableAliases( opts, @@ -702,6 +943,29 @@ export async function collectCatalog( log ); + // Provider allowlist (same seam as the allowlists above, applied last): + // compile once, resolve alias<->canonical once via the usable pass, then + // one predicate per collection point. `buildCanonicalToAliasMap` returns + // canonical->alias; the resolve table needs the inverse alias->canonical. + const warnedCombos = opts.collisionWarned ?? new Set(); + const providerFilter = compileProviderFilter(opts.providersAllow); + let providerResolve: ProviderResolve | undefined; + let providerAllUnknown = false; + if (providerFilter) { + const pairs: Array<{ alias?: string; canonical?: string }> = []; + for (const entry of enrichment.values()) { + pairs.push({ alias: entry.providerAlias, canonical: entry.providerCanonical }); + } + for (const [canonical, alias] of canonicalToAlias) { + pairs.push({ alias, canonical }); + } + providerResolve = buildProviderResolve(pairs, usable?.canonicals); + providerAllUnknown = filterAllUnknown(providerFilter, providerResolve); + if (providerResolve.known.size === 0) + warnNoVocabulary(providerFilter, providerResolve.known, warnedCombos, log); + else warnUnknownProviders(providerFilter, providerResolve.known, warnedCombos, log); + } + const rawModelById = new Map(); for (const entry of rawModels) { if (entry.id) rawModelById.set(entry.id, entry); @@ -714,19 +978,69 @@ export async function collectCatalog( const publishedModelIds = new Map(); const collected = new Map(); let modelCount = 0; + let providerDroppedCount = 0; + // Showcase ranks the allowlisted pool so the default picker stays usable + // without pinning plus the curated dates and pinned ids below. + const showcasePool = catalogAll + ? [] + : rawModels.filter( + (entry) => + entry.id && + !canonicalDedup.has(entry.id) && + (!usable || isUsableRawModelId(entry.id, usable)) && + passesModelAllowlist(entry.id, visibleFilter, hiddenFilter) && + passesProviderFilter(entry.id, providerFilter, providerResolve, providerAllUnknown) && + readEntryStatus(entry) !== "deprecated" && + !isFlatTierId(entry.id) + ); + const showcased = catalogAll + ? new Set() + : selectShowcaseIds(showcasePool, opts.showcasePerOwner ?? SHOWCASE_PER_OWNER); + const nowMs = Date.now(); + const windowMs = freshWindowDaysToMs(opts.freshWindowDays ?? FRESH_WINDOW_DAYS); + const freshKept = catalogAll + ? new Set() + : selectFreshKeptIds( + rawModels, + opts.freshPerOwner ?? FRESH_PER_OWNER, + nowMs, + visibleFilter, + windowMs + ); + const staticallyDropped: OmniRouteRawModelEntry[] = []; for (const entry of rawModels) { if (!entry.id) continue; if (canonicalDedup.has(entry.id)) continue; if (usable && !isUsableRawModelId(entry.id, usable)) continue; if (!passesModelAllowlist(entry.id, visibleFilter, hiddenFilter)) continue; + if (!passesProviderFilter(entry.id, providerFilter, providerResolve, providerAllUnknown)) { + providerDroppedCount += 1; + continue; + } + if ( + !catalogAll && + !passesWhatServes(entry, showcased, visibleFilter, nowMs, freshKept, windowMs) + ) { + staticallyDropped.push(entry); + continue; + } const mapped = mapRawModelToModelV2(entry, { providerId: X, baseURL: opts.baseURL, apiFormat: opts.apiFormat, }); - applyEnrichment(mapped, lookupEnrichment(entry.id, enrichment, canonicalToAlias), { + const enrichmentEntry = lookupEnrichment(entry.id, enrichment, canonicalToAlias); + applyEnrichment(mapped, enrichmentEntry, { providerTag: opts.providerTag !== false, }); + if ( + !passesCapabilityPresets(mapped, enrichmentEntry, { + freeOnly: opts.freeOnly, + toolsOnly: opts.toolsOnly, + visionOnly: opts.visionOnly, + } satisfies CapabilityPresetFlags) + ) + continue; const mid = mapped.id.startsWith(X + "/") ? mapped.id.slice(X.length + 1) : mapped.id; const key = X + "/" + mid; collected.set(key, mapped); @@ -734,10 +1048,38 @@ export async function collectCatalog( publishedModelIds.set(key, mapped.id); modelCount += 1; } + // Memory branch: restore only what the static pass dropped and the last + // 30 days of usage still names. Empty usage, a failed fetch, no token or + // an explicit opt-out restores nothing; the narrowed fallback stands. + if (!catalogAll && staticallyDropped.length > 0 && opts.usageMemory !== false) { + // The exclusions above hold under the usage memory too: a retired + // entry, a flat effort-variant id or an id past the fresh cap is + // never restored — filter before resolving. + const restorable = staticallyDropped.filter( + (entry) => !staysDropped(entry, freshKept, nowMs, windowMs) + ); + const used = await resolveUsageIds(restorable, opts, fetchers, log); + const toRestore = restorable.filter((entry) => matchesUsage(entry.id, used)); + for (const entry of toRestore) { + const mapped = mapRawModelToModelV2(entry, { + providerId: X, + baseURL: opts.baseURL, + apiFormat: opts.apiFormat, + }); + applyEnrichment(mapped, lookupEnrichment(entry.id, enrichment, canonicalToAlias), { + providerTag: opts.providerTag !== false, + }); + const mid = mapped.id.startsWith(X + "/") ? mapped.id.slice(X.length + 1) : mapped.id; + const key = X + "/" + mid; + collected.set(key, mapped); + publishedKeys.add(key); + publishedModelIds.set(key, mapped.id); + modelCount += 1; + } + } - const warnedCombos = opts.collisionWarned ?? new Set(); const cacheKey = `${opts.baseURL}::${opts.providerId}`; - const comboCount = await publishCombos({ + const comboResult = await publishCombos({ opts, log, providerId: X, @@ -750,70 +1092,46 @@ export async function collectCatalog( hiddenFilter, usable, canonicalToAlias, + providerFilter, + providerResolve, + providerAllUnknown, combosFetcher, combosTimeout, warnedCombos, cacheKey, }); - if (comboCount === undefined) - return { entries: collected, counts: { models: modelCount, combos: 0, autoCombos: 0 } }; + if (comboResult === undefined) + return { entries: collected, counts: { models: modelCount, combos: 0 } }; + const comboCount = comboResult.published; + providerDroppedCount += comboResult.providerDropped; // Migration: v1 published opencode-X; v2 publishes X bare. Sessions pinned // opencode-X resolve ModelUnavailableError -- see RELEASE.md migration note. // Re-publishing under "opencode-"+X here is FORBIDDEN: a double // publish would double chat entries in the picker. - // Auto combos: virtual server-side entries from /api/combos/auto, keyed - // "auto" / "auto/" (v1 parity). Fail-open: a fetcher throw keeps - // models + combos and only warns - old gateways may not serve the - // endpoint at all (the default fetcher maps 404 to [] itself). - let rawAutoCombos: OmniRouteRawAutoCombo[]; - try { - rawAutoCombos = autoCombosFetcher - ? await autoCombosFetcher( - opts.baseURL, - opts.managementReadToken ?? opts.apiKey, - autoCombosTimeout - ) - : []; - } catch (err) { - log.warn( - `[omniroute-v2] auto combos fetch failed, falling back to models+combos catalog: ${err instanceof Error ? err.message : String(err)}` - ); - return { entries: collected, counts: { models: modelCount, combos: comboCount, autoCombos: 0 } }; + if (providerFilter && collected.size === 0 && providerDroppedCount > 0) { + warnProviderMatchedNothing(providerFilter, warnedCombos, log); } - let autoComboCount = 0; - for (const autoCombo of rawAutoCombos) { - if (!autoCombo || !autoCombo.id) continue; - if (autoCombo.isHidden === true) continue; - // Auto combos are catalog entries like any other: an id a user asked to - // hide must stay hidden, and an allowlist that excludes it must exclude - // it. They used to skip both filters entirely. - if (!passesModelAllowlist(autoCombo.id, visibleFilter, hiddenFilter)) continue; - if (usable && !isUsableRawModelId(autoCombo.id, usable)) continue; - const mapped = mapAutoComboToModelV2(autoCombo, X, opts.baseURL, opts.apiFormat); - applyEnrichment(mapped, lookupEnrichment(autoCombo.id, enrichment, canonicalToAlias), { - isCombo: true, - isAutoCombo: true, - }); - const key = X + "/" + mapped.id; - if (publishedKeys.has(key)) { - const dedupeKey = `${cacheKey}::${key}`; - if (!warnedCombos.has(dedupeKey)) { - warnedCombos.add(dedupeKey); - log.warn( - `[omniroute-v2] auto combo key "${key}" collides with a model id; auto combo wins.` - ); - } - } - collected.set(key, mapped); - publishedKeys.add(key); - publishedModelIds.set(key, mapped.id); - autoComboCount += 1; - } + return { + entries: collected, + counts: { models: modelCount, combos: comboCount }, + }; +} - return { entries: collected, counts: { models: modelCount, combos: comboCount, autoCombos: autoComboCount } }; +function warnProviderMatchedNothing( + providerFilter: ProviderFilter | undefined, + warnedCombos: Set, + log: Logger +): void { + if (!providerFilter) return; + const key = `provider-matched-nothing::${[...providerFilter.allow].sort().join(",")}`; + if (warnedCombos.has(key)) return; + warnedCombos.add(key); + log.warn( + `[omniroute-v2] providersAllow [${providerFilter.originals.join(", ")}] matched nothing, publishing empty catalog.` + ); } /** @@ -876,7 +1194,10 @@ export async function publishCatalog( const settings = (info.settings ?? {}) as Record; const npm = String(info.package ?? "").replace("@opencode/ai/providers/", "@ai-sdk/"); p["api"] = { type: "aisdk", package: npm, url: settings["baseURL"] }; - p["request"] = { headers: (info.headers ?? {}) as Record, body: (info.body ?? {}) as Record }; + p["request"] = { + headers: (info.headers ?? {}) as Record, + body: (info.body ?? {}) as Record, + }; }); for (const m of collected.entries.keys()) { const slash = m.indexOf("/"); diff --git a/@omniroute/opencode-plugin-v2/src/index.ts b/@omniroute/opencode-plugin-v2/src/index.ts index c05749c42276..f05ed193f4f5 100644 --- a/@omniroute/opencode-plugin-v2/src/index.ts +++ b/@omniroute/opencode-plugin-v2/src/index.ts @@ -3,7 +3,6 @@ import { optionalTierFingerprint, catalogContentFingerprint, createLogger, - defaultOmniRouteAutoCombosFetcher, defaultOmniRouteCombosFetcher, defaultOmniRouteEnrichmentFetcher, defaultOmniRouteModelsFetcher, @@ -11,15 +10,9 @@ import { type OmniRouteEnrichmentMap, type OmniRouteProviderConnection, } from "./shared/index.js"; -import type { - OmniRouteRawAutoCombo, - OmniRouteRawCombo, - OmniRouteRawModelEntry, -} from "./shared/index.js"; -import type { ResolvedOptions } from "./catalog.js"; +import type { OmniRouteRawCombo, OmniRouteRawModelEntry } from "./shared/index.js"; import { buildProviderPayload, collectCatalog } from "./catalog.js"; import { - DEFAULT_MODEL_CACHE_TTL_MS, UNREACHABLE_COOLDOWN_MS, memoryCacheKey, readDiskSnapshot, @@ -35,9 +28,8 @@ import { MANAGEMENT_TOKEN_ENV_VAR, PLUGIN_ID, parsePluginOptions, - resolveManagementReadToken, resolveTimeouts, - type PluginOptions, + toResolvedOptions, } from "./options.js"; /** @@ -48,6 +40,9 @@ import { */ type SourceResult = { ok: true; value: T } | { ok: false }; +/** Warn-once guard for the usage-memory startup notice (one warn per process). */ +let warnedMemoryNoToken = false; + interface RefreshState { entries: Map; inFlight: Map>; @@ -63,29 +58,7 @@ interface RefreshState { unreachableUntil: number; } -function toResolvedOptions(parsed: PluginOptions): ResolvedOptions { - return { - providerId: parsed.providerId, - baseURL: parsed.baseURL, - apiKey: parsed.apiKey ?? process.env.OMNIROUTE_API_KEY ?? "", - managementReadToken: resolveManagementReadToken(parsed.managementReadToken), - timeoutMs: parsed.timeoutMs, - timeouts: parsed.timeouts, - logLevel: parsed.logLevel, - startupDebug: parsed.startupDebug, - providerTag: parsed.providerTag, - modelCacheTtlMs: - typeof parsed.modelCacheTtlMs === "number" && parsed.modelCacheTtlMs > 0 - ? parsed.modelCacheTtlMs - : DEFAULT_MODEL_CACHE_TTL_MS, - displayName: parsed.displayName, - apiFormat: parsed.apiFormat, - visibleModels: parsed.visibleModels, - hiddenModels: parsed.hiddenModels, - usableOnly: parsed.usableOnly, - enrichment: parsed.enrichment, - }; -} +export { toResolvedOptions } from "./options.js"; export default Plugin.define({ id: PLUGIN_ID, @@ -110,6 +83,16 @@ export default Plugin.define({ `or export ${MANAGEMENT_TOKEN_ENV_VAR}.` ); } + if ( + resolved.usageMemory !== false && + resolved.managementReadToken === undefined && + !warnedMemoryNoToken + ) { + warnedMemoryNoToken = true; + log.warn( + "[omniroute-v2] usage history inactive without management token: statically dropped models stay unpublished." + ); + } // v1 parity port: in-memory TTL + disk snapshot. The memory key // `baseURL::sha256(creds)` isolates credential tuples (prod vs @@ -243,31 +226,10 @@ export default Plugin.define({ return { ok: false }; } }; - const fetchAutoCombosSafe = async (): Promise> => { - try { - return { - ok: true, - value: await defaultOmniRouteAutoCombosFetcher( - resolved.baseURL, - resolved.managementReadToken ?? resolved.apiKey, - timeouts.autoCombos, - log, - reportSourceError - ), - }; - } catch (err) { - // The default fetcher reports the refusal itself (with the - // management-token hint); this warn is the fallback for injected - // stubs that throw without reporting. - const reason = err instanceof Error ? err.message : String(err); - log.warn(`[omniroute-v2] auto combos fetch failed, keeping the last known ones: ${reason}`); - return { ok: false }; - } - }; /** * Fetch in two tiers. Models are what a catalog *is*: without them there - * is nothing to publish. Everything else — combos, auto-combos, the + * is nothing to publish. Everything else — combos, the * provider list, the enrichment overlay — improves an already usable * catalog, so awaiting any of them before publishing makes the catalog * hostage to the slowest source: a gateway that accepts the connection @@ -286,7 +248,6 @@ export default Plugin.define({ const essential = fetchModelsSafe(); const optional = Promise.all([ fetchCombosSafe(), - fetchAutoCombosSafe(), fetchProvidersSafe(), fetchEnrichmentSafe(), ]); @@ -307,7 +268,6 @@ export default Plugin.define({ const snapshot: CatalogSnapshot = { models, combos: previous?.combos ?? [], - autoCombos: previous?.autoCombos ?? [], providers: previous?.providers ?? [], enrichment: previous?.enrichment ?? new Map(), fetchedAt: Date.now(), @@ -336,9 +296,8 @@ export default Plugin.define({ */ async function upgradeWithOptional( base: CatalogSnapshot, - [combos, autoCombos, providers, enrichment]: [ + [combos, providers, enrichment]: [ SourceResult, - SourceResult, SourceResult, SourceResult, ] @@ -349,13 +308,11 @@ export default Plugin.define({ const upgraded: CatalogSnapshot = { ...base, combos: combos.ok ? combos.value : base.combos, - autoCombos: autoCombos.ok ? autoCombos.value : base.autoCombos, providers: providers.ok ? providers.value : base.providers, enrichment: enrichment.ok ? enrichment.value : base.enrichment, }; const unchanged = upgraded.combos === base.combos && - upgraded.autoCombos === base.autoCombos && upgraded.providers === base.providers && upgraded.enrichment === base.enrichment; if (unchanged) return; @@ -367,7 +324,6 @@ export default Plugin.define({ // fingerprint covers ids alone, so without this the host would rebuild // its catalog once per TTL window for an identical result. const optionalFingerprint = optionalTierFingerprint( - upgraded.autoCombos ?? [], upgraded.providers ?? [], upgraded.enrichment, upgraded.combos @@ -456,7 +412,6 @@ export default Plugin.define({ const counts = await (async (): Promise<{ models: number; combos: number; - autoCombos: number; }> => { // fetcher-level fail-open covers fetches; this guard covers mapper throws. try { @@ -464,7 +419,6 @@ export default Plugin.define({ onSourceError: reportSourceError, models: async () => effective.models, combos: async () => effective.combos, - autoCombos: async () => effective.autoCombos, providers: async () => effective.providers ?? [], enrichment: async () => effective.enrichment ?? new Map(), }); @@ -475,15 +429,11 @@ export default Plugin.define({ log.warn( `[omniroute-v2] catalog publish failed, keeping current catalog: ${err instanceof Error ? err.message : String(err)}` ); - return { models: 0, combos: 0, autoCombos: 0 }; + return { models: 0, combos: 0 }; } })(); void counts; - const fingerprint = catalogContentFingerprint( - effective.models, - effective.combos, - effective.autoCombos - ); + const fingerprint = catalogContentFingerprint(effective.models, effective.combos); const changed = state.fingerprint !== undefined && state.fingerprint !== fingerprint; state.fingerprint = fingerprint; if (changed) { diff --git a/@omniroute/opencode-plugin-v2/src/legacy-model.ts b/@omniroute/opencode-plugin-v2/src/legacy-model.ts index ef32cadb1fbd..1312e0397629 100644 --- a/@omniroute/opencode-plugin-v2/src/legacy-model.ts +++ b/@omniroute/opencode-plugin-v2/src/legacy-model.ts @@ -1,8 +1,8 @@ /** * Vendored legacy catalog shape (beta `Model`), kept dependency-free. * - * The shared mappers (`models-map`, `combos-map`, `auto-combos`, `enrich`) - * speak the rich legacy catalog shape: nested boolean capabilities, a + * The shared mappers (`models-map`, `combos-map`, `enrich`) + * speak the rich legacy catalog shape (`auto-combos` retired): nested boolean capabilities, a * single-object cost block, `options`/`headers` escape hatches, a string * `release_date`, and variants as a record. It was previously imported from * `@opencode-ai/sdk/v2`; vendoring it removes the beta SDK dependency while diff --git a/@omniroute/opencode-plugin-v2/src/options.ts b/@omniroute/opencode-plugin-v2/src/options.ts index 17fb658802ea..2311b356b02e 100644 --- a/@omniroute/opencode-plugin-v2/src/options.ts +++ b/@omniroute/opencode-plugin-v2/src/options.ts @@ -1,5 +1,7 @@ import { z } from "zod"; +import { DEFAULT_MODEL_CACHE_TTL_MS } from "./cache.js"; +import type { ResolvedOptions } from "./catalog.js"; import { isHttpUrl } from "./shared/models-map.js"; const apiFormatSchema = z @@ -44,7 +46,21 @@ const pluginOptionsSchema = z modelCacheTtlMs: z.number().positive().optional(), visibleModels: z.array(z.string()).optional(), hiddenModels: z.array(z.string()).optional(), + providersAllow: z.array(z.string()).optional(), usableOnly: z.boolean().default(false), + freeOnly: z.boolean().default(false), + toolsOnly: z.boolean().default(true), + visionOnly: z.boolean().default(false), + // Per-provider showcase size: how many models each provider keeps in + // the default view. Absent means the catalog default below. + showcasePerOwner: z.number().positive().optional(), + freshPerOwner: z.number().positive().optional(), + // Freshness window in days: entries dated within it publish via the + // fresh branch. Absent means the catalog default below. + freshWindowDays: z.number().positive().optional(), + // Restore statically dropped entries named by 30-day usage analytics. + // On by default; needs a management token. + usageMemory: z.boolean().optional(), // v1 parity: enrichment overlay on by default (names + pricing). enrichment: z.boolean().default(true), // v1 parity: strip the JSON-Schema keywords Gemini rejects from tool @@ -81,13 +97,10 @@ export function resolveManagementReadToken(optionValue: string | undefined): str /** Per-endpoint timeout defaults (v1 parity). `timeoutMs` is the global fallback. */ export const DEFAULT_TIMEOUT_MS = 10_000 as const; -/** Auto-combos keep the v1 5s budget; the field is resolved now for the P3 port. */ -export const DEFAULT_AUTO_COMBOS_TIMEOUT_MS = 5_000 as const; export interface EndpointTimeouts { models: number; combos: number; - autoCombos: number; enrichment: number; } @@ -99,7 +112,6 @@ export function resolveTimeouts( return { models: opts.timeouts?.models ?? fallback, combos: opts.timeouts?.combos ?? fallback, - autoCombos: opts.timeouts?.autoCombos ?? DEFAULT_AUTO_COMBOS_TIMEOUT_MS, enrichment: opts.timeouts?.enrichment ?? fallback, }; } @@ -124,6 +136,44 @@ export function parsePluginOptions(raw: unknown): PluginOptions { throw new Error(`[omniroute-v2] invalid plugin options — ${problems.join("; ")}`); } +/** + * Map parsed options to the catalog's runtime shape. Lives here (not + * index.ts) so tests reach it without importing the plugin entrypoint, whose + * `@opencode/plugin` runtime import is a devDependency absent from the + * caller's tree — importing index.ts fails at module load there. + */ +export function toResolvedOptions(parsed: PluginOptions): ResolvedOptions { + return { + providerId: parsed.providerId, + baseURL: parsed.baseURL, + apiKey: parsed.apiKey ?? process.env.OMNIROUTE_API_KEY ?? "", + managementReadToken: resolveManagementReadToken(parsed.managementReadToken), + timeoutMs: parsed.timeoutMs, + timeouts: parsed.timeouts, + logLevel: parsed.logLevel, + startupDebug: parsed.startupDebug, + providerTag: parsed.providerTag, + modelCacheTtlMs: + typeof parsed.modelCacheTtlMs === "number" && parsed.modelCacheTtlMs > 0 + ? parsed.modelCacheTtlMs + : DEFAULT_MODEL_CACHE_TTL_MS, + displayName: parsed.displayName, + apiFormat: parsed.apiFormat, + visibleModels: parsed.visibleModels, + hiddenModels: parsed.hiddenModels, + providersAllow: parsed.providersAllow, + usableOnly: parsed.usableOnly, + freeOnly: parsed.freeOnly, + toolsOnly: parsed.toolsOnly, + visionOnly: parsed.visionOnly, + showcasePerOwner: parsed.showcasePerOwner, + freshPerOwner: parsed.freshPerOwner, + freshWindowDays: parsed.freshWindowDays, + usageMemory: parsed.usageMemory, + enrichment: parsed.enrichment, + }; +} + /** * The host reads the plugin id from the module, before any option is known, so * it cannot carry the configured provider id. Publishing two gateways from one diff --git a/@omniroute/opencode-plugin-v2/src/provider-filter.ts b/@omniroute/opencode-plugin-v2/src/provider-filter.ts new file mode 100644 index 000000000000..5999ac4a53bc --- /dev/null +++ b/@omniroute/opencode-plugin-v2/src/provider-filter.ts @@ -0,0 +1,272 @@ +/** + * Provider allowlist filter for the published catalog. + * + * A provider is the raw id prefix before the first `/` (for example `cc` + * in `cc/claude-sonnet-4-6`), read BEFORE the enrichment overlay renames + * anything. Ids without a `/` carry no provider claim and always pass. + * + * Unknown names in the allowlist warn once and filter nothing for that + * name (fail-open, the inverse of the capacity-subset options which are + * fail-closed). Only a filter that matches nothing publishes an empty + * catalog, with an explicit warning (fail-explicit, never a silent + * fallback to the full catalog). + * + * Alias design: the same alias pass as the usable-prefix filter resolves + * short aliases (`cc`) to canonical provider ids (`claude`), so `["claude"]` + * matches `cc/...` rows. + */ + +import type { OmniRouteRawCombo } from "./shared/combos-map.js"; +import type { Logger } from "./shared/logger.js"; + +/** Compiled allowlist. `undefined` means inactive (empty/absent = full catalog). */ +export interface ProviderFilter { + /** Normalized (trimmed, lowercased, deduplicated) allowlist entries. */ + allow: Set; + /** Operator-facing spellings, same order, for diagnostics. */ + originals: string[]; +} + +/** Alias/canonical resolution tables, normalized lowercase at build time. */ +export interface ProviderResolve { + /** Every known name: enrichment aliases, provider canonicals, canonicals of aliases. */ + known: Set; + /** alias -> canonical (for example `cc` -> `claude`). */ + aliasToCanonical: Map; + /** canonical -> aliases (for example `claude` -> {`cc`}). */ + canonicalToAliases: Map>; +} + +/** Normalize one operator entry; empty after trim means "no opinion". */ +function normalizeName(value: string): string | undefined { + if (typeof value !== "string") return undefined; + const trimmed = value.trim().toLowerCase(); + return trimmed.length > 0 ? trimmed : undefined; +} + +/** + * Compile the operator allowlist. Empty/absent/blank-only input returns + * `undefined` (filter inactive, full catalog, no warning). + */ +export function compileProviderFilter(allow?: string[]): ProviderFilter | undefined { + if (!allow || allow.length === 0) return undefined; + const seen = new Set(); + const originals: string[] = []; + for (const raw of allow) { + const normalized = normalizeName(raw); + if (normalized === undefined || seen.has(normalized)) continue; + seen.add(normalized); + originals.push(typeof raw === "string" ? raw.trim() : String(raw)); + } + if (seen.size === 0) return undefined; + return { allow: seen, originals }; +} + +/** + * Provider prefix of a raw catalog id (lowercased). `undefined` for bare + * ids without a `/` — those carry no provider claim and always pass. + */ +export function providerOf(id: string): string | undefined { + if (typeof id !== "string") return undefined; + const slash = id.indexOf("/"); + if (slash <= 0) return undefined; + const prefix = id.slice(0, slash).trim().toLowerCase(); + return prefix.length > 0 ? prefix : undefined; +} + +/** + * Build the resolution tables from raw enrichment alias/canonical pairs + * plus usable canonicals. All keys normalized lowercase once here, never + * per comparison. + */ +export function buildProviderResolve( + pairs: Array<{ alias?: string; canonical?: string }>, + usableCanonicals?: Iterable +): ProviderResolve { + const known = new Set(); + const aliasToCanonical = new Map(); + const canonicalToAliases = new Map>(); + const addAlias = (alias: string, canonical: string): void => { + aliasToCanonical.set(alias, canonical); + let bucket = canonicalToAliases.get(canonical); + if (!bucket) { + bucket = new Set(); + canonicalToAliases.set(canonical, bucket); + } + bucket.add(alias); + }; + for (const pair of pairs) { + const alias = normalizeName(pair.alias ?? ""); + const canonical = normalizeName(pair.canonical ?? ""); + if (alias !== undefined) known.add(alias); + if (canonical !== undefined) known.add(canonical); + if (alias !== undefined && canonical !== undefined && alias !== canonical) { + if (!aliasToCanonical.has(alias)) addAlias(alias, canonical); + else { + const existing = aliasToCanonical.get(alias); + if (existing !== undefined && existing !== canonical) { + let bucket = canonicalToAliases.get(canonical); + if (!bucket) { + bucket = new Set(); + canonicalToAliases.set(canonical, bucket); + } + bucket.add(alias); + } + } + known.add(alias); + known.add(canonical); + } + } + if (usableCanonicals) { + for (const raw of usableCanonicals) { + const canonical = normalizeName(raw); + if (canonical === undefined) continue; + known.add(canonical); + } + } + return { known, aliasToCanonical, canonicalToAliases }; +} + +/** + * Whether one provider prefix matches the allowlist. Without a resolve + * table this is a direct prefix comparison; with one, aliases resolve both + * ways: prefix in allow, canonical(prefix) in allow, or prefix is an alias + * of an allowed canonical. + */ +export function matchesAllow( + prefix: string | undefined, + filter: ProviderFilter | undefined, + resolve?: ProviderResolve +): boolean { + if (!filter) return true; + if (prefix === undefined) return true; + if (filter.allow.has(prefix)) return true; + if (!resolve) return false; + const canonPrefix = resolve.aliasToCanonical.get(prefix) ?? prefix; + for (const allowed of filter.allow) { + if (canonPrefix === allowed) return true; + const canonAllowed = resolve.aliasToCanonical.get(allowed) ?? allowed; + if (prefix === canonAllowed || canonPrefix === canonAllowed) return true; + const aliases = resolve.canonicalToAliases.get(allowed); + if (aliases !== undefined && aliases.has(prefix)) return true; + const aliasesOfPrefix = resolve.canonicalToAliases.get(canonPrefix); + if (aliasesOfPrefix !== undefined && aliasesOfPrefix.has(allowed)) return true; + } + return false; +} + +/** Whether a raw model/combo id passes the provider filter. */ +export function passesProviderFilter( + id: string, + filter: ProviderFilter | undefined, + resolve?: ProviderResolve, + allUnknown?: boolean +): boolean { + if (!filter) return true; + const prefix = providerOf(id); + if (prefix === undefined) return true; + if (matchesAllow(prefix, filter, resolve)) return true; + // Fail-open on prefixes unknown to the vocabulary tables: an unknown + // prefix proves nothing about the provider, so it cannot exclude. + if (resolve && !resolve.known.has(prefix)) return true; + // Fail-open on allow entries unknown to the vocabulary: when EVERY allow + // name is outside the vocabulary, the whole filter stays inert (warned + // once at publish time). A mix of known + unknown still lets the known + // names restrict. The flag is precomputed once per publish; without a + // resolve table it is false and only the direct comparison above decides. + if (resolve && (allUnknown ?? filterAllUnknown(filter, resolve))) return true; + return false; +} + +/** Whether every allow entry sits outside the vocabulary (filter inert). */ +export function filterAllUnknown(filter: ProviderFilter, resolve: ProviderResolve): boolean { + for (const allowed of filter.allow) { + if (resolve.known.has(allowed)) return false; + } + return true; +} + +/** Read one combo member model id, skipping `combo-ref` steps. */ +function comboMemberIds(combo: OmniRouteRawCombo): string[] { + const steps = Array.isArray(combo.models) ? combo.models : []; + const out: string[] = []; + for (const step of steps) { + if (step?.kind === "combo-ref") continue; + const modelId = typeof step?.model === "string" ? step.model : ""; + if (modelId.length === 0) continue; + out.push(modelId); + } + return out; +} + +/** + * Whether a combo passes: at least one member passes, `combo-ref` steps + * skipped, zero resolvable members keep (mirrors the usableOnly combo rule). + */ +export function passesProviderCombo( + combo: OmniRouteRawCombo, + filter: ProviderFilter | undefined, + resolve?: ProviderResolve, + allUnknown?: boolean +): boolean { + if (!filter) return true; + const members = comboMemberIds(combo); + if (members.length === 0) return true; + for (const member of members) { + if (passesProviderFilter(member, filter, resolve, allUnknown)) return true; + } + return false; +} + +/** Allow entries unknown to the vocabulary tables (operator spellings back). */ +export function unknownProviders(filter: ProviderFilter | undefined, known: Set): string[] { + if (!filter) return []; + const out: string[] = []; + for (const original of filter.originals) { + const normalized = normalizeName(original); + if (normalized === undefined) continue; + if (!known.has(normalized)) out.push(original); + } + return out; +} + +/** + * Warn once about unknown allow entries. Callers pass the publish-wide + * warning set (`opts.collisionWarned`, shared across publishes) so a repeat + * publish warns once per key. + */ +export function warnUnknownProviders( + filter: ProviderFilter | undefined, + known: Set, + warned: Set, + log: Pick +): void { + if (!filter) return; + const unknown = unknownProviders(filter, known); + if (unknown.length === 0) return; + const key = `provider-unknown::${[...filter.allow].sort().join(",")}`; + if (warned.has(key)) return; + warned.add(key); + log.warn( + `[omniroute-v2] unknown provider(s) in providersAllow: ${unknown.join(",")} — ` + + `no catalog entry matches these names, they filter nothing.` + ); +} + +/** Warn once when the filter is active but no vocabulary exists. */ +export function warnNoVocabulary( + filter: ProviderFilter | undefined, + known: Set, + warned: Set, + log: Pick +): void { + if (!filter) return; + if (known.size > 0) return; + const key = "provider-no-vocabulary"; + if (warned.has(key)) return; + warned.add(key); + log.warn( + `[omniroute-v2] provider filter active but no provider vocabulary available, ` + + `keeping the full catalog.` + ); +} diff --git a/@omniroute/opencode-plugin-v2/src/shared/auto-combos.ts b/@omniroute/opencode-plugin-v2/src/shared/auto-combos.ts deleted file mode 100644 index 654b81333019..000000000000 --- a/@omniroute/opencode-plugin-v2/src/shared/auto-combos.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { LegacyModel as ModelV2 } from "../legacy-model.js"; -import type { ApiFormatV2 } from "./models-map.js"; -import { resolveApiBlockV2 } from "./models-map.js"; -import { autoComboModelId, formatAutoComboName, type AutoVariant } from "./naming.js"; - -export type { AutoVariant }; - -/** - * Raw shape of an auto combo entry as returned by OmniRoute's - * `/api/combos/auto` endpoint. Auto combos are virtual -- they self-manage - * provider selection via scoring/bandit exploration at runtime. - * - * Ported from the v1 plugin (`index.ts:1672-1698`); the shape is unchanged - * so old and new gateways stay wire-compatible. - */ -export interface OmniRouteRawAutoCombo { - /** Stable id (e.g. "auto", "auto/coding"). */ - id: string; - /** Human-readable name (e.g. "Auto", "Auto Coding"). */ - name?: string; - /** Variant key or undefined for the default auto. */ - variant?: AutoVariant; - /** Provider names eligible for this auto combo. */ - candidatePool?: string[]; - /** Number of candidates resolved at fetch time. */ - candidateCount?: number; - /** MAX of candidates' context windows, served by newer gateway builds. - * Absent on older servers -- the mapper falls back to a safe default. */ - context_length?: number; - /** MAX of candidates' max output tokens (same provenance as context_length). */ - max_output_tokens?: number; - /** Whether this auto combo should be hidden from the picker. */ - isHidden?: boolean; - /** Auto-combo configuration. */ - config?: { - auto?: { - candidatePool?: string[]; - explorationRate?: number; - routerStrategy?: string; - }; - }; -} - -/** Minimal warn sink so the fetcher never depends on the plugin logger. */ -export interface AutoCombosWarnSink { - warn: (message: string, ...args: unknown[]) => void; -} - -/** - * Fetcher contract for `/api/combos/auto`. Returns the list of virtual - * auto combos the server can create. Same DI shape as the other fetchers - * so unit tests can inject a stub instead of monkey-patching `fetch`. - * - * HTTP refusals (non-2xx other than 404) and network errors THROW: the caller - * distinguishes "the gateway failed" (keep last-known) from "the gateway - * answered empty" (publish empty). Only 404 stays soft — the endpoint does - * not exist yet on older gateways, and that is an answer, not a failure. - */ -export type OmniRouteAutoCombosFetcher = ( - baseURL: string, - apiKey: string, - timeoutMs?: number, - logger?: AutoCombosWarnSink, - onSourceError?: (endpoint: string, reason: string) => void -) => Promise; - -function trimTrailingSlashes(value: string): string { - let i = value.length; - while (i > 0 && value.charCodeAt(i - 1) === 0x2f /* "/" */) i--; - return i === value.length ? value : value.slice(0, i); -} - -function fallbackWarn(message: string, ...args: unknown[]): void { - console.warn(`[omniroute-plugin] [WARN] ${message}`, ...args); -} - -/** - * Default auto combos fetcher: `GET /api/combos/auto`. - * - * 404 stays soft (endpoint not deployed yet on older gateways — an answer, - * not a failure). Any other non-2xx or network error THROWS so the caller - * keeps last-known instead of publishing an empty tier: a 403 behind a - * management-token gate must not wipe the auto combos the picker had. - * v1 parity keeps the 5s timeout budget. - */ -export const defaultOmniRouteAutoCombosFetcher: OmniRouteAutoCombosFetcher = async ( - baseURL, - apiKey, - timeoutMs = 5_000, - logger?: AutoCombosWarnSink, - onSourceError?: (endpoint: string, reason: string) => void -) => { - if (!apiKey || !baseURL) return []; - const warn = logger?.warn ?? fallbackWarn; - const report = (reason: string): void => { - warn(reason); - onSourceError?.("/api/combos/auto", reason); - }; - - const trimmed = trimTrailingSlashes(baseURL); - const root = trimmed.replace(/\/v\d+$/, ""); - const url = `${root}/api/combos/auto`; - - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - try { - const res = await fetch(url, { - method: "GET", - headers: { - Authorization: `Bearer ${apiKey}`, - Accept: "application/json", - }, - signal: controller.signal, - }); - // 404 = endpoint not deployed yet -- expected during rollout - if (res.status === 404) { - warn(`/api/combos/auto not available (404) -- auto combos disabled`); - return []; - } - if (!res.ok) { - const reason = `HTTP ${res.status} ${res.statusText}`; - report(`/api/combos/auto refused (${reason}) -- keeping last-known auto combos`); - throw new Error(reason); - } - const body = (await res.json()) as unknown; - const rawList: unknown[] = Array.isArray(body) - ? body - : body && typeof body === "object" && Array.isArray((body as { combos?: unknown }).combos) - ? ((body as { combos: unknown[] }).combos as unknown[]) - : []; - const out: OmniRouteRawAutoCombo[] = []; - for (const r of rawList) { - if (r && typeof r === "object" && typeof (r as { id?: unknown }).id === "string") { - out.push(r as OmniRouteRawAutoCombo); - } - } - return out; - } catch (err) { - // Network error, timeout, abort -- keep last-known, never publish empty. - // (The 404-soft path above returns directly and never reaches this throw.) - const reason = `/api/combos/auto fetch failed: ${err instanceof Error ? err.message : String(err)} -- keeping last-known auto combos`; - report(reason); - throw err instanceof Error ? err : new Error(String(err)); - } finally { - clearTimeout(timer); - } -}; - -/** Fallbacks when the server does not advertise auto-combo limits (older - * gateway builds). MUST be positive: OpenCode's overflow guard treats - * `limit.context === 0` as "never overflow" and silently DISABLES smart - * auto-compaction, letting the session grow until the gateway's destructive - * history purge kicks in. */ -export const AUTO_COMBO_FALLBACK_CONTEXT = 128_000; -export const AUTO_COMBO_FALLBACK_OUTPUT = 8_192; - -/** - * Convert a raw auto combo into a `ModelV2` entry for the picker. - * Auto combos route to capable models, so tool_call and reasoning default - * to true. Context/output limits come from the server (MAX of the - * candidate pool's windows); a safe positive fallback applies when the - * server omits them. Never 0. - */ -export function mapAutoComboToModelV2( - autoCombo: OmniRouteRawAutoCombo, - providerId: string, - baseURL: string, - apiFormat?: ApiFormatV2 -): ModelV2 { - const name = formatAutoComboName(autoCombo.variant, autoCombo.candidateCount); - const context = - typeof autoCombo.context_length === "number" && autoCombo.context_length > 0 - ? autoCombo.context_length - : AUTO_COMBO_FALLBACK_CONTEXT; - const output = - typeof autoCombo.max_output_tokens === "number" && autoCombo.max_output_tokens > 0 - ? autoCombo.max_output_tokens - : AUTO_COMBO_FALLBACK_OUTPUT; - return { - id: autoComboModelId(autoCombo.variant), - providerID: providerId, - api: resolveApiBlockV2(autoComboModelId(autoCombo.variant), baseURL, apiFormat), - name, - capabilities: { - temperature: true, - reasoning: true, - attachment: false, - toolcall: true, - input: { - text: true, - audio: false, - image: false, - video: false, - pdf: false, - }, - output: { - text: true, - audio: false, - image: false, - video: false, - pdf: false, - }, - interleaved: false, - }, - cost: { - input: 0, - output: 0, - cache: { read: 0, write: 0 }, - }, - limit: { - context, - output, - }, - status: "active", - options: {}, - headers: {}, - release_date: "", - }; -} diff --git a/@omniroute/opencode-plugin-v2/src/shared/fingerprint.ts b/@omniroute/opencode-plugin-v2/src/shared/fingerprint.ts index 58835956bb62..5b3029768f8d 100644 --- a/@omniroute/opencode-plugin-v2/src/shared/fingerprint.ts +++ b/@omniroute/opencode-plugin-v2/src/shared/fingerprint.ts @@ -11,8 +11,7 @@ import { createHash } from "node:crypto"; */ export function catalogContentFingerprint( models: { id: string; release_date?: string }[], - combos: { id: string }[], - autoCombos: { id: string }[] = [] + combos: { id: string }[] ): string { const modelLines = models .map((m) => `${m.id}|${m.release_date ?? ""}`) @@ -22,22 +21,17 @@ export function catalogContentFingerprint( .map((c) => c.id) .sort() .join("\n"); - const autoLines = autoCombos - .map((c) => c.id) - .sort() - .join("\n"); - return createHash("sha256").update(`${modelLines}\n${comboLines}\n${autoLines}`).digest("hex"); + return createHash("sha256").update(`${modelLines}\n${comboLines}`).digest("hex"); } /** - * Digest of the optional tier (auto-combos, provider connections, enrichment). - * The catalog fingerprint covers model and combo ids only, so an overlay that - * moves — a renamed model, a provider going unusable — leaves it unchanged. - * Reloading on every refresh instead would ask the host to rebuild its catalog - * once per TTL window for nothing. + * Digest of the optional tier (combos membership, provider connections, + * enrichment). The catalog fingerprint covers model and combo ids only, so an + * overlay that moves — a renamed model, a provider going unusable — leaves it + * unchanged. Reloading on every refresh instead would ask the host to rebuild + * its catalog once per TTL window for nothing. */ export function optionalTierFingerprint( - autoCombos: { id: string }[], providers: { id?: string; name?: string; @@ -69,12 +63,6 @@ export function optionalTierFingerprint( .sort() .join(",") ); - parts.push( - autoCombos - .map((c) => c.id) - .sort() - .join(",") - ); // A provider going quiet or getting renamed is as visible to the user as a // price move: its activity flag and display name belong in the digest. parts.push( diff --git a/@omniroute/opencode-plugin-v2/src/shared/index.ts b/@omniroute/opencode-plugin-v2/src/shared/index.ts index d65d093abb1f..c209ff971baf 100644 --- a/@omniroute/opencode-plugin-v2/src/shared/index.ts +++ b/@omniroute/opencode-plugin-v2/src/shared/index.ts @@ -1,6 +1,5 @@ export * from "./models-map.js"; export * from "./combos-map.js"; -export * from "./auto-combos.js"; export * from "./naming.js"; export * from "./enrich.js"; export * from "./fingerprint.js"; diff --git a/@omniroute/opencode-plugin-v2/tests/api-package.test.ts b/@omniroute/opencode-plugin-v2/tests/api-package.test.ts index dc88677730d9..1458283f9d36 100644 --- a/@omniroute/opencode-plugin-v2/tests/api-package.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/api-package.test.ts @@ -63,7 +63,7 @@ function apiPackageOf(m: Record | undefined): string { return m.api.package; } -describe("catalog api package (models + combos + auto-combos)", () => { +describe("catalog api package (models + combos)", () => { it("every published entry carries a non-empty supported api.package", async () => { const { models, draft } = fakeDraft(); const res = await publishCatalog(draft, baseOpts, { @@ -71,35 +71,30 @@ describe("catalog api package (models + combos + auto-combos)", () => { combosFetcher: async () => [ { id: "combo-a", name: "Combo A", models: [{ kind: "model", model: "gpt-x" }] }, ], - autoCombosFetcher: async () => [{ id: "auto", candidateCount: 6 }], }); - assert.deepEqual(res, { models: 1, combos: 1, autoCombos: 1 }); - for (const key of ["omniroute/gpt-x", "omniroute/combo-a", "omniroute/auto"]) { + assert.deepEqual(res, { models: 1, combos: 1 }); + for (const key of ["omniroute/gpt-x", "omniroute/combo-a"]) { const pkg = apiPackageOf(models.get(key)); assert.ok(pkg.length > 0, `${key} api.package must be non-empty`); assert.ok(SUPPORTED_PACKAGES.has(pkg), `${key} api.package must be supported, got ${pkg}`); } }); - it("auto-combos follow the same anthropic apiFormat rule as models", async () => { + it("models keep their gateway ids and api blocks after the virtual-entries removal", async () => { const { models, draft } = fakeDraft(); - await publishCatalog( + const res = await publishCatalog( draft, { ...baseOpts, - apiFormat: { allowAnthropic: true, anthropicModels: ["anthropic/claude-x", "auto/coding"] }, + apiFormat: { allowAnthropic: true, anthropicModels: ["anthropic/claude-x"] }, }, { - fetcher: async () => [{ id: "anthropic/claude-x" }], + fetcher: async () => [{ id: "anthropic/claude-x" }, { id: "auto/coding" }], combosFetcher: async () => [], - autoCombosFetcher: async () => [ - { id: "auto/coding", variant: "coding", candidateCount: 4 }, - { id: "auto/fast", variant: "fast", candidateCount: 2 }, - ], } ); + assert.deepEqual(res, { models: 2, combos: 0 }); assert.equal(apiPackageOf(models.get("omniroute/anthropic/claude-x")), "@ai-sdk/anthropic"); - assert.equal(apiPackageOf(models.get("omniroute/auto/coding")), "@ai-sdk/anthropic"); - assert.equal(apiPackageOf(models.get("omniroute/auto/fast")), "@ai-sdk/openai-compatible"); + assert.ok(models.has("omniroute/auto/coding"), "gateway entries publish under their own id"); }); }); diff --git a/@omniroute/opencode-plugin-v2/tests/auto-combos.test.ts b/@omniroute/opencode-plugin-v2/tests/auto-combos.test.ts deleted file mode 100644 index c27b553c1ef6..000000000000 --- a/@omniroute/opencode-plugin-v2/tests/auto-combos.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { describe, it } from "node:test"; -import assert from "node:assert/strict"; -import { publishCatalog } from "../src/catalog.js"; -type BetaDraft = { - provider: { - list?: () => unknown[]; - get?: (id: string) => unknown; - update: (id: string, fn: (p: Record) => void) => void; - remove?: () => void; - }; - model: { - get?: (...a: string[]) => unknown; - update: (pid: string, mid: string, fn: (m: Record) => void) => void; - remove?: () => void; - default?: { get: () => undefined; set: () => void }; - }; -}; - -function fakeDraft(): { - models: Map>; - draft: BetaDraft; - warns: string[]; - restore: () => void; -} { - const providers = new Map>(); - const models = new Map>(); - const warns: string[] = []; - const origWarn = console.warn; - console.warn = (...args: unknown[]) => { - warns.push(String(args[0])); - }; - const draft = { - provider: { - list: () => [], - get: (id: string) => providers.get(id) as never, - update: (id: string, fn: (p: Record) => void) => { - const p = (providers.get(id) ?? { id }) as Record; - fn(p); - providers.set(id, p); - }, - remove: () => {}, - }, - model: { - get: () => undefined, - update: (pid: string, mid: string, fn: (m: Record) => void) => { - const k = pid + "/" + mid; - const m = (models.get(k) ?? { id: mid, providerID: pid }) as Record; - fn(m); - models.set(k, m); - }, - remove: () => {}, - default: { get: () => undefined, set: () => {} }, - }, - }; - return { - models, - draft, - warns, - restore: () => { - console.warn = origWarn; - }, - }; -} - -const baseOpts = { - providerId: "omniroute", - baseURL: "https://gw.example.com", - apiKey: "k", - timeoutMs: 1000, - timeouts: { autoCombos: 5000 }, - modelCacheTtlMs: 300000, - usableOnly: false, -}; - -describe("catalog auto combos (v1 parity)", () => { - it("publishes auto/* ids from the auto-combos fetcher", async () => { - const { models, draft, warns, restore } = fakeDraft(); - try { - const res = await publishCatalog(draft, baseOpts, { - fetcher: async () => [], - combosFetcher: async () => [], - autoCombosFetcher: async () => [ - { id: "auto", variant: undefined, candidateCount: 6 }, - { id: "auto/coding", variant: "coding", candidateCount: 4 }, - ], - }); - assert.deepEqual(res, { models: 0, combos: 0, autoCombos: 2 }); - assert.ok(models.has("omniroute/auto"), "auto entry must be published"); - assert.ok(models.has("omniroute/auto/coding"), "auto/coding entry must be published"); - const coding = models.get("omniroute/auto/coding"); - assert.equal(coding?.limit.context, 128_000); - assert.equal(coding?.limit.output, 8_192); - } finally { - restore(); - } - assert.ok(!warns.some((w) => w.includes("auto combo") && w.includes("failed"))); - }); - - it("auto-combos fetch throw stays fail-open (warn, models kept)", async () => { - const { models, draft, warns, restore } = fakeDraft(); - try { - const res = await publishCatalog(draft, baseOpts, { - fetcher: async () => [{ id: "m1" }], - combosFetcher: async () => [], - autoCombosFetcher: async () => { - throw new Error("GET https://gw.example.com/api/combos/auto failed: 500 boom"); - }, - }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); - assert.ok(models.has("omniroute/m1")); - } finally { - restore(); - } - assert.ok(warns.some((w) => w.includes("auto combos") || w.includes("auto-combos"))); - }); - - it("forwards the 5s auto-combos timeout to the fetcher", async () => { - const seen: number[] = []; - const { draft, restore } = fakeDraft(); - try { - await publishCatalog( - draft, - { ...baseOpts, timeoutMs: 9999, timeouts: { autoCombos: 5000 } }, - { - fetcher: async () => [], - combosFetcher: async () => [], - autoCombosFetcher: async (_base, _key, timeoutMs) => { - seen.push(timeoutMs ?? -1); - return []; - }, - } - ); - } finally { - restore(); - } - assert.deepEqual(seen, [5000]); - }); - - it("hidden auto combos are skipped", async () => { - const { models, draft, restore } = fakeDraft(); - try { - const res = await publishCatalog(draft, baseOpts, { - fetcher: async () => [], - combosFetcher: async () => [], - autoCombosFetcher: async () => [ - { id: "auto/offline", variant: "offline", isHidden: true }, - { id: "auto/fast", variant: "fast" }, - ], - }); - assert.deepEqual(res, { models: 0, combos: 0, autoCombos: 1 }); - assert.ok(!models.has("omniroute/auto/offline")); - assert.ok(models.has("omniroute/auto/fast")); - } finally { - restore(); - } - }); -}); - -describe("auto combos obey the same allowlists as everything else", () => { - it("hides an auto combo the user hid, and keeps the rest", async () => { - const { publishCatalog } = await import("../src/catalog.js"); - const published = new Map>(); - const draft = { - provider: { update: (_i: string, fn: (p: Record) => void) => fn({}) }, - model: { - update: (pid: string, mid: string, fn: (m: Record) => void) => { - const e: Record = { id: mid, providerID: pid }; - fn(e); - published.set(`${pid}/${mid}`, e); - }, - }, - }; - await publishCatalog( - draft as never, - { - providerId: "omni", - baseURL: "https://gw.example.com", - apiKey: "k", - timeoutMs: 1000, - modelCacheTtlMs: 1000, - usableOnly: false, - enrichment: new Map(), - hiddenModels: ["auto/best-chaos"], - } as never, - { - models: async () => [{ id: "m1" }], - combos: async () => [], - autoCombos: async () => - [ - { id: "auto/best-coding", variant: "best-coding", candidateCount: 3 }, - { id: "auto/best-chaos", variant: "best-chaos", candidateCount: 2 }, - ] as never, - providers: async () => [], - enrichment: async () => new Map(), - } - ); - const keys = [...published.keys()]; - assert.ok( - keys.some((k) => k.includes("best-coding")), - `the other auto combo stays, got ${JSON.stringify(keys)}` - ); - assert.equal( - keys.some((k) => k.includes("best-chaos")), - false, - `a hidden auto combo must not reach the picker, got ${JSON.stringify(keys)}` - ); - }); -}); diff --git a/@omniroute/opencode-plugin-v2/tests/cache-ttl-snapshot.test.ts b/@omniroute/opencode-plugin-v2/tests/cache-ttl-snapshot.test.ts index 6850a41db03b..e9e25aab87af 100644 --- a/@omniroute/opencode-plugin-v2/tests/cache-ttl-snapshot.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/cache-ttl-snapshot.test.ts @@ -13,8 +13,10 @@ interface CtxOpts { modelCacheTtlMs?: number; } +const requestedPaths: string[] = []; + function stubFetch( - counter: { models: number; combos: number; autoCombos?: number; enrichment?: number }, + counter: { models: number; combos: number; enrichment?: number }, modelIds: string[] ) { return (async (url: unknown) => { @@ -23,10 +25,7 @@ function stubFetch( if (counter.enrichment !== undefined) counter.enrichment += 1; return { ok: true, status: 200, statusText: "OK", json: async () => ({}) }; } - if (href.includes("/api/combos/auto")) { - if (counter.autoCombos !== undefined) counter.autoCombos += 1; - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } + requestedPaths.push(new URL(href).pathname); if (href.includes("/api/combos")) { counter.combos += 1; return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; @@ -36,7 +35,9 @@ function stubFetch( ok: true, status: 200, statusText: "OK", - json: async () => ({ data: modelIds.map((id) => ({ id })) }), + json: async () => ({ + data: modelIds.map((id) => ({ id, capabilities: { tool_calling: true } })), + }), }; }) as typeof fetch; } @@ -89,7 +90,10 @@ async function setupPlugin(opts: CtxOpts): Promise<{ function publishedOf(added: unknown[]): Map> { const published = new Map>(); - for (const entry of added as Array<{ info: { id: string }; models: Array> }>) { + for (const entry of added as Array<{ + info: { id: string }; + models: Array>; + }>) { for (const m of entry.models) published.set(entry.info.id + "/" + String(m.id), m); } return published; @@ -132,7 +136,8 @@ describe("plugin-v2 P1 parity: TTL 300s + disk snapshot", () => { it("2nd transform within TTL -> 0 network fetches (stub counter)", async () => { const disk = isolateDisk(); - const counter = { models: 0, combos: 0, autoCombos: 0 }; + const counter = { models: 0, combos: 0 }; + requestedPaths.length = 0; const origFetch = globalThis.fetch; globalThis.fetch = stubFetch(counter, ["m1"]); try { @@ -144,7 +149,10 @@ describe("plugin-v2 P1 parity: TTL 300s + disk snapshot", () => { const published = publishedOf(added); assert.equal(counter.models, 1); assert.equal(counter.combos, 1); - assert.equal(counter.autoCombos, 1); + assert.ok( + !requestedPaths.some((p) => p.includes("/api/combos/auto")), + `retired route must never be requested, got ${JSON.stringify(requestedPaths)}` + ); assert.ok(published.has("ttl-hit/m1")); } finally { globalThis.fetch = origFetch; @@ -204,9 +212,6 @@ describe("plugin-v2 P1 parity: TTL 300s + disk snapshot", () => { json: async () => ({ data: [{ id: "m1" }] }), }; } - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/providers")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({}) }; } @@ -270,9 +275,6 @@ describe("plugin-v2 P1 parity: TTL 300s + disk snapshot", () => { if (href.includes("/api/pricing") || href.includes("/api/free-tier")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({}) }; } - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -362,7 +364,6 @@ describe("the snapshot carries the display overlay across a restart", () => { { models: [{ id: "cc/sonnet" }], combos: [], - autoCombos: [], providers: [], enrichment, fetchedAt: Date.now(), diff --git a/@omniroute/opencode-plugin-v2/tests/catalog-provider.test.ts b/@omniroute/opencode-plugin-v2/tests/catalog-provider.test.ts new file mode 100644 index 000000000000..efdfe0a1fcf1 --- /dev/null +++ b/@omniroute/opencode-plugin-v2/tests/catalog-provider.test.ts @@ -0,0 +1,289 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { collectCatalog, type ResolvedOptions } from "../src/catalog.js"; +import { parsePluginOptions, toResolvedOptions } from "../src/options.js"; +import type { Logger } from "../src/shared/logger.js"; + +function silentLogger(warns: string[]): Logger { + return { + error: () => {}, + warn: (msg: string) => { + warns.push(String(msg)); + }, + info: () => {}, + debug: () => {}, + always: () => {}, + child: () => silentLogger(warns), + } as unknown as Logger; +} + +function resolvedWith( + raw: Record, + warns: string[], + extra?: Partial +): ResolvedOptions { + // Real option resolution path: parse, then the catalog-shaping copy in + // `../src/options.js` (re-exported by the entrypoint). Run with + // `node --import tsx/esm --test` from the package directory. + // #14554: toolsOnly now defaults true and drops models without tool_calling. + // These cases assert the provider filter, so they opt out of that preset. + // A caller can still override by passing toolsOnly in `raw`. + const parsed = parsePluginOptions({ + baseURL: "https://gw.example.com", + toolsOnly: false, + ...raw, + }); + const resolved = toResolvedOptions(parsed); + resolved.logger = silentLogger(warns); + resolved.modelCacheTtlMs = 300000; + return { ...resolved, ...extra }; +} + +function enrichmentOf(...pairs: Array<[string, string]>) { + return new Map( + pairs.map(([alias, canonical]) => [ + `${alias}/model-x`, + { providerAlias: alias, providerCanonical: canonical }, + ]) + ); +} + +const stubModels = async () => [{ id: "cc/a" }, { id: "alpha/b" }]; +const stubCombos = async () => [ + { + id: "mix", + name: "Mix", + models: [ + { kind: "model", model: "cc/a" }, + { kind: "model", model: "alpha/b" }, + ], + }, +]; + +function baseFetchers() { + return { + models: stubModels, + combos: stubCombos, + providers: async () => [], + enrichment: async () => enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }; +} + +describe("provider filter integration (real option resolution)", () => { + it("empty/absent allowlist publishes the full catalog with no warning", async () => { + for (const raw of [{}, { providersAllow: [] }]) { + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith(raw, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }), + baseFetchers() + ); + assert.equal(collected.counts.models, 2); + assert.equal(collected.counts.combos, 1); + assert.deepEqual(collected.counts, { models: 2, combos: 1 }); + assert.equal( + warns.filter((w) => w.includes("providersAllow") || w.includes("provider filter")).length, + 0 + ); + } + }); + + it("single provider keeps only its models and combos via alias", async () => { + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["claude"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }), + baseFetchers() + ); + assert.equal(collected.counts.models, 1); + assert.ok([...collected.entries.keys()].some((k) => k.endsWith("/cc/a"))); + // Mixed combo survives: one member resolves through the alias. + assert.equal(collected.counts.combos, 1); + assert.deepEqual(collected.counts, { models: 1, combos: 1 }); + }); + + it("keeps the full catalog when the allowlist names nothing known", async () => { + const warns: string[] = []; + // An allow name outside the vocabulary filters nothing: it warns once + // and every entry keeps (fail-open on the unknown NAME, same bias as + // unknown catalog prefixes). + const sharedWarned = new Set(); + const opts = resolvedWith({ providersAllow: ["nope"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + collisionWarned: sharedWarned, + }); + const first = await collectCatalog(opts, baseFetchers()); + assert.equal(first.counts.models, 2); + assert.equal(first.counts.combos, 1); + assert.deepEqual(first.counts, { models: 2, combos: 1 }); + assert.equal(warns.filter((w) => w.includes("unknown provider")).length, 1); + const second = await collectCatalog(opts, baseFetchers()); + assert.equal(second.counts.models, 2); + assert.equal(warns.filter((w) => w.includes("unknown provider")).length, 1); + }); + + it("unknown provider warns once while a known sibling still filters", async () => { + const warns: string[] = []; + // An unknown allow entry warns and filters nothing for THAT name; the + // known sibling still restricts. Fail-open is per-name, not whole-filter. + const sharedWarned = new Set(); + const opts = resolvedWith({ providersAllow: ["mystery", "claude"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + collisionWarned: sharedWarned, + }); + const first = await collectCatalog(opts, baseFetchers()); + assert.equal(first.counts.models, 1); + assert.equal(first.counts.combos, 1); + assert.deepEqual(first.counts, { models: 1, combos: 1 }); + assert.equal(warns.filter((w) => w.includes("unknown provider")).length, 1); + const second = await collectCatalog(opts, baseFetchers()); + assert.equal(second.counts.models, 1); + assert.equal(warns.filter((w) => w.includes("unknown provider")).length, 1); + }); + + it("filter matching nothing publishes empty with an explicit warning", async () => { + const warns: string[] = []; + // "alpha" is KNOWN to the vocabulary (enrichment pair alpha/alpha) but + // absent from the catalog rows, so the filter is active yet drops the + // only model: empty catalog + explicit warning, no silent fallback. + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["alpha"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }), + { + models: async () => [{ id: "cc/a" }], + combos: async () => [], + providers: async () => [], + enrichment: async () => enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + } + ); + assert.equal(collected.entries.size, 0); + assert.ok(warns.some((w) => w.includes("matched nothing"))); + }); + + it("drops a combo whose only member is excluded", async () => { + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["alpha"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }), + { + models: async () => [{ id: "cc/a" }, { id: "alpha/b" }], + combos: async () => [ + { id: "cc-only", name: "CcOnly", models: [{ kind: "model", model: "cc/a" }] }, + ], + providers: async () => [], + enrichment: async () => enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + } + ); + assert.equal(collected.counts.combos, 0); + assert.ok([...collected.entries.keys()].some((k) => k.endsWith("/alpha/b"))); + }); + + it("combo-ref-only combos survive the filter", async () => { + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["claude"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"]), + }), + { + models: async () => [{ id: "cc/a" }], + combos: async () => [ + { id: "child", name: "Child", models: [{ kind: "model", model: "cc/a" }] }, + { id: "parent", name: "Parent", models: [{ kind: "combo-ref", comboName: "Child" }] }, + ], + providers: async () => [], + enrichment: async () => enrichmentOf(["cc", "claude"]), + } + ); + assert.equal(collected.counts.combos, 2); + }); + + it("composes with visibleModels by AND (provider applies last)", async () => { + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith({ visibleModels: ["cc/*"], providersAllow: ["alpha"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }), + baseFetchers() + ); + assert.equal(collected.counts.models, 0); + assert.equal(warns.filter((w) => w.includes("unknown provider")).length, 0); + }); + + it("empty upstream plus active filter emits no matched-nothing warning", async () => { + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["claude"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"]), + }), + { + models: async () => [], + combos: async () => [], + providers: async () => [], + enrichment: async () => enrichmentOf(["cc", "claude"]), + } + ); + assert.equal(collected.entries.size, 0); + assert.equal(warns.filter((w) => w.includes("matched nothing")).length, 0); + }); + + it("vocabulary-free run warns and keeps the full catalog", async () => { + const warns: string[] = []; + // No vocabulary means the filter stays inert: nothing is compared, every + // entry keeps, and the run warns once that the full catalog is kept. + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["alpha"] }, warns, { enrichment: new Map() }), + { + models: async () => [{ id: "cc/a" }, { id: "alpha/b" }], + combos: async () => [], + providers: async () => [], + enrichment: async () => new Map(), + } + ); + assert.equal(collected.counts.models, 2); + assert.equal(warns.filter((w) => w.includes("no provider vocabulary")).length, 1); + assert.equal(warns.filter((w) => w.includes("unknown provider")).length, 0); + }); + + it("ignores a retired fetcher entry the refresh no longer reads", async () => { + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["claude"] }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }), + { + models: async () => [{ id: "cc/a" }], + combos: async () => [], + autoCombos: async () => [{ id: "auto", candidatePool: ["alpha"] }], + providers: async () => [], + enrichment: async () => enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + } as never + ); + assert.equal(collected.counts.models, 1); + assert.deepEqual(collected.counts, { models: 1, combos: 0 }); + }); + + it("scales N+N models through the real pipeline", async () => { + const N = 500; + // The default view now caps entries per provider (#15484); the scale case wants all N. + const models: Array<{ id: string }> = []; + for (let i = 0; i < N; i++) models.push({ id: `cc/m-${i}` }); + for (let i = 0; i < N; i++) models.push({ id: `alpha/m-${i}` }); + const warns: string[] = []; + const collected = await collectCatalog( + resolvedWith({ providersAllow: ["claude"], showcasePerOwner: N, freshPerOwner: N }, warns, { + enrichment: enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + }), + { + models: async () => models, + combos: async () => [], + providers: async () => [], + enrichment: async () => enrichmentOf(["cc", "claude"], ["alpha", "alpha"]), + } + ); + assert.equal(collected.counts.models, N); + }); +}); diff --git a/@omniroute/opencode-plugin-v2/tests/catalog-what-serves.test.ts b/@omniroute/opencode-plugin-v2/tests/catalog-what-serves.test.ts new file mode 100644 index 000000000000..838702492933 --- /dev/null +++ b/@omniroute/opencode-plugin-v2/tests/catalog-what-serves.test.ts @@ -0,0 +1,945 @@ +import { describe, it, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import type { AddressInfo } from "node:net"; +import { collectCatalog } from "../src/catalog.js"; +import { defaultOmniRouteModelsFetcher } from "../src/shared/models-map.js"; +import type { OmniRouteRawModelEntry } from "../src/shared/models-map.js"; + +const baseOpts = { + providerId: "omniroute", + baseURL: "https://gw.example.com", + apiKey: "k", + timeoutMs: 1000, + modelCacheTtlMs: 300000, + usableOnly: false, + enrichment: false as const, +}; + +const servers: http.Server[] = []; +afterEach(async () => { + while (servers.length > 0) { + const server = servers.pop(); + if (server) await new Promise((resolve) => server.close(() => resolve())); + } +}); + +async function entriesThroughReader( + data: Array>, + envelope: "list" | "bare" = "list" +): Promise { + const body = envelope === "list" ? { object: "list", data } : data; + const server = http.createServer((_req, res) => { + res.writeHead(200, { "content-type": "application/json" }); + res.end(JSON.stringify(body)); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve())); + servers.push(server); + const port = (server.address() as AddressInfo).port; + return defaultOmniRouteModelsFetcher(`http://127.0.0.1:${port}`, "k", 2000); +} + +function dated(daysAgo: number, nowMs: number): string { + return new Date(nowMs - daysAgo * 24 * 3600 * 1000).toISOString(); +} + +const FIXED_NOW = Date.parse("2026-10-03T12:00:00.000Z"); + +async function withFixedNow(fn: () => Promise): Promise { + const realNow = Date.now; + Date.now = () => FIXED_NOW; + try { + return await fn(); + } finally { + Date.now = realNow; + } +} + +function staleEntry(id: string, ownedBy = "solo"): Record { + return { id, owned_by: ownedBy, context_length: 1000, capabilities: {} }; +} + +/** + * Eleven entries under one owner: ten high-context fillers take the + * showcase slots, the low-context target falls out of every static + * branch (no dates, no capability shortcut, no pinning) — the only way to be + * statically dropped when each owner keeps its top ten. + */ +function crowd(owner: string, targetId: string): Array> { + const fillers = Array.from({ length: 10 }, (_v, i) => ({ + id: `${owner}/filler-${i}`, + owned_by: owner, + context_length: 100000, + })); + return [...fillers, { id: targetId, owned_by: owner, context_length: 1000 }]; +} + +/** + * Same crowd, but the ten fillers also carry a newer curatorial date + * than the low-context fresh target: the target falls out of the + * showcase (11th by date) while staying inside the 90-day window, so + * only the freshness branch can publish it. + */ +function freshCrowd( + owner: string, + targetId: string, + targetDate: string, + fillerDate: string +): Array> { + const fillers = Array.from({ length: 10 }, (_v, i) => ({ + id: `${owner}/filler-${i}`, + owned_by: owner, + context_length: 100000, + release_date: fillerDate, + })); + return [ + ...fillers, + { id: targetId, owned_by: owner, context_length: 1000, release_date: targetDate }, + ]; +} + +describe("publish what serves by default", () => { + it("recent model without usage is published", async () => { + await withFixedNow(async () => { + // The target is the newest entry, so it sits inside the per-owner + // fresh cap: a recent model with no usage signal still publishes. + const raw = await entriesThroughReader( + freshCrowd("fresh", "fresh/new-model", dated(1, FIXED_NOW), dated(2, FIXED_NOW)) + ); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/fresh/new-model")); + }); + }); + + it("entry with only tool calling stays unpublished", async () => { + await withFixedNow(async () => { + const entries = crowd("tooled", "tooled/tool-only").map((row) => + row.id === "tooled/tool-only" ? { ...row, capabilities: { tool_calling: true } } : row + ); + const raw = await entriesThroughReader(entries); + const opts = { ...baseOpts, managementReadToken: "m" }; + const fetchers = { + fetcher: async () => raw, + combosFetcher: async () => [], + }; + const unpublished = await collectCatalog(opts, { + ...fetchers, + usageFetcher: async () => [], + }); + assert.ok(!unpublished.entries.has("omniroute/tooled/tool-only")); + const unrelated = await collectCatalog(opts, { + ...fetchers, + usageFetcher: async () => ["unrelated-id"], + }); + assert.ok(!unrelated.entries.has("omniroute/tooled/tool-only")); + }); + }); + + it("model without dates falls back to other branches", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader(crowd("plain", "plain/undated")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(!collected.entries.has("omniroute/plain/undated")); + }); + }); + + it("history restores used models on top of static pass", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { id: "fresh/new-model", owned_by: "fresh", release_date: dated(10, FIXED_NOW) }, + ...crowd("zzz", "zzz/old-one"), + ...crowd("never", "never/never-used"), + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => ["zzz/old-one"], + } + ); + assert.ok(collected.entries.has("omniroute/fresh/new-model")); + assert.ok(collected.entries.has("omniroute/zzz/old-one")); + assert.ok(!collected.entries.has("omniroute/never/never-used")); + }); + }); + + it("sentinel restores the full catalog", async () => { + const collected = await collectCatalog( + { ...baseOpts, visibleModels: ["*"], hiddenModels: ["gone/dead"] }, + { + fetcher: async () => + [staleEntry("gone/dead"), staleEntry("zzz/kept")] as unknown as OmniRouteRawModelEntry[], + combosFetcher: async () => [ + { id: "all-combo", models: [{ kind: "model", model: "zzz/kept" }] }, + ], + } + ); + assert.ok(collected.entries.has("omniroute/zzz/kept")); + assert.ok(!collected.entries.has("omniroute/gone/dead")); + assert.ok(collected.entries.has("omniroute/all-combo")); + }); + + it("short pinned id keeps the prefixed model", async () => { + const collected = await collectCatalog( + { ...baseOpts, visibleModels: ["keep-me"] }, + { + fetcher: async () => + [ + staleEntry("cc/keep-me"), + staleEntry("cc/drop-me"), + ] as unknown as OmniRouteRawModelEntry[], + combosFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/cc/keep-me")); + assert.ok(!collected.entries.has("omniroute/cc/drop-me")); + }); + + it("empty usage keeps the restricted fallback", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { id: "fresh/new-model", owned_by: "fresh", release_date: dated(10, FIXED_NOW) }, + ...crowd("zzz", "zzz/old-one"), + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/fresh/new-model")); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + }); + }); + + it("failed usage keeps the remaining models", async () => { + const warns: string[] = []; + const origWarn = console.warn; + console.warn = (...args: unknown[]) => { + warns.push(String(args[0])); + }; + try { + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => crowd("zzz", "zzz/old-one") as unknown as OmniRouteRawModelEntry[], + combosFetcher: async () => [], + usageFetcher: async () => { + throw new Error("boom 500"); + }, + } + ); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + } finally { + console.warn = origWarn; + } + assert.equal(warns.filter((w) => /usage fetch failed/.test(w)).length, 1); + }); + + it("missing token never calls the usage endpoint", async () => { + let calls = 0; + const collected = await collectCatalog(baseOpts, { + fetcher: async () => crowd("zzz", "zzz/old-one") as unknown as OmniRouteRawModelEntry[], + combosFetcher: async () => [], + usageFetcher: async () => { + calls += 1; + return ["zzz/old-one"]; + }, + }); + assert.equal(calls, 0); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + }); + + it("repeated refresh keeps a stable fingerprint", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader( + freshCrowd("fresh", "fresh/new-model", dated(10, FIXED_NOW), dated(2, FIXED_NOW)) + ); + const fetchers = { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [] as string[], + }; + const opts = { ...baseOpts, managementReadToken: "m" }; + const first = await collectCatalog(opts, fetchers); + const second = await collectCatalog(opts, fetchers); + assert.deepEqual([...first.entries.keys()].sort(), [...second.entries.keys()].sort()); + }); + }); + + it("usage beyond the top fifty is not restored", async () => { + const used = Array.from({ length: 60 }, (_v, i) => `used-${i}`); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => + [ + ...crowd("zzz", "zzz/used-59"), + ...crowd("never", "never/never"), + ] as unknown as OmniRouteRawModelEntry[], + combosFetcher: async () => [], + usageFetcher: async () => used, + } + ); + assert.ok(!collected.entries.has("omniroute/zzz/used-59")); + assert.ok(!collected.entries.has("omniroute/never/never")); + }); +}); + +describe("usage memory option", () => { + it("restores a dropped entry named by recent usage by default", async () => { + await withFixedNow(async () => { + let calls = 0; + const raw = await entriesThroughReader(crowd("zzz", "zzz/old-one")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => { + calls += 1; + return ["zzz/old-one"]; + }, + } + ); + assert.equal(calls, 1); + assert.ok(collected.entries.has("omniroute/zzz/old-one")); + }); + }); + + it("restores a dropped entry named by recent usage when enabled", async () => { + await withFixedNow(async () => { + let calls = 0; + const raw = await entriesThroughReader(crowd("zzz", "zzz/old-one")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", usageMemory: true }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => { + calls += 1; + return ["zzz/old-one"]; + }, + } + ); + assert.equal(calls, 1); + assert.ok(collected.entries.has("omniroute/zzz/old-one")); + }); + }); + + it("leaves usage memory off when disabled", async () => { + await withFixedNow(async () => { + let calls = 0; + const raw = await entriesThroughReader(crowd("zzz", "zzz/old-one")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", usageMemory: false }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => { + calls += 1; + return ["zzz/old-one"]; + }, + } + ); + assert.equal(calls, 0); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + }); + }); + + it("makes no usage call without a token", async () => { + await withFixedNow(async () => { + let calls = 0; + const raw = await entriesThroughReader(crowd("zzz", "zzz/old-one")); + const collected = await collectCatalog(baseOpts, { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => { + calls += 1; + return ["zzz/old-one"]; + }, + }); + assert.equal(calls, 0); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + }); + }); + + it("makes no usage call without a token when enabled", async () => { + await withFixedNow(async () => { + let calls = 0; + const raw = await entriesThroughReader(crowd("zzz", "zzz/old-one")); + const collected = await collectCatalog( + { ...baseOpts, usageMemory: true }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => { + calls += 1; + return ["zzz/old-one"]; + }, + } + ); + assert.equal(calls, 0); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + }); + }); + + it("restores nothing when the usage fetch fails", async () => { + const warns: string[] = []; + const origWarn = console.warn; + console.warn = (...args: unknown[]) => { + warns.push(String(args[0])); + }; + try { + await withFixedNow(async () => { + const raw = await entriesThroughReader(crowd("zzz", "zzz/old-one")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => { + throw new Error("boom 500"); + }, + } + ); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + }); + } finally { + console.warn = origWarn; + } + assert.equal(warns.filter((w) => /usage fetch failed/.test(w)).length, 1); + }); + + it("leaves a short id unmatched without its owner", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader(crowd("zzz", "zzz/old-one")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => ["old-one"], + } + ); + assert.ok(!collected.entries.has("omniroute/zzz/old-one")); + }); + }); + + it("restores the base entry when usage names its flat variant", async () => { + await withFixedNow(async () => { + const fillers = Array.from({ length: 10 }, (_v, i) => ({ + id: `base/filler-${i}`, + owned_by: "base", + context_length: 100000, + })); + const raw = await entriesThroughReader([ + ...fillers, + { id: "base/vivid", owned_by: "base", context_length: 1000 }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => ["base/vivid-high"], + } + ); + assert.ok(collected.entries.has("omniroute/base/vivid")); + assert.ok(!collected.entries.has("omniroute/base/vivid-high")); + }); + }); +}); + +describe("retired entries stay out of the default view", () => { + it("drops a retired model even when usage still names it", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { + id: "old/gone", + owned_by: "old", + context_length: 1000, + release_date: dated(5, FIXED_NOW), + status: "deprecated", + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => ["old/gone"], + } + ); + assert.ok(!collected.entries.has("omniroute/old/gone")); + }); + }); + + it("drops a retired model on the fail-open path too", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { + id: "old/gone", + owned_by: "old", + context_length: 1000, + release_date: dated(5, FIXED_NOW), + status: "deprecated", + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => { + throw new Error("boom 500"); + }, + } + ); + assert.ok(!collected.entries.has("omniroute/old/gone")); + }); + }); + + it("keeps a retired model when pinned by exact id", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { + id: "old/gone", + owned_by: "old", + context_length: 1000, + release_date: dated(5, FIXED_NOW), + status: "deprecated", + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, visibleModels: ["old/gone"] }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/old/gone")); + }); + }); +}); + +describe("flat effort-variant ids stay out of the default view", () => { + it("drops the flat id and keeps the dated base id", async () => { + await withFixedNow(async () => { + for (const tier of ["medium", "high"]) { + const raw = await entriesThroughReader([ + { + id: "o/m", + owned_by: "o", + context_length: 1000, + release_date: dated(5, FIXED_NOW), + }, + { + id: `o/m-${tier}`, + owned_by: "o", + context_length: 1000, + release_date: dated(4, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/o/m"), `base kept for -${tier}`); + assert.ok(!collected.entries.has(`omniroute/o/m-${tier}`), `flat dropped for -${tier}`); + } + }); + }); + + it("never restores a flat id through usage", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { + id: "o/m", + owned_by: "o", + context_length: 1000, + release_date: dated(5, FIXED_NOW), + }, + { + id: "o/m-high", + owned_by: "o", + context_length: 1000, + release_date: dated(4, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => ["o/m-high"], + } + ); + assert.ok(!collected.entries.has("omniroute/o/m-high")); + }); + }); + + it("keeps a flat id when pinned by exact id", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { + id: "o/m-medium", + owned_by: "o", + context_length: 1000, + release_date: dated(4, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, visibleModels: ["o/m-medium"] }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/o/m-medium")); + }); + }); + + it("leaves ids without an effort suffix alone", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { + id: "o/model-turbo", + owned_by: "o", + context_length: 1000, + release_date: dated(5, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/o/model-turbo")); + }); + }); +}); + +describe("fresh cap per owner", () => { + function rankedFresh(owner: string, count: number): Array> { + return Array.from({ length: count }, (_v, i) => ({ + id: `${owner}/m-${i}`, + owned_by: owner, + context_length: 1000 + (count - i), + release_date: dated(i + 1, FIXED_NOW), + })); + } + + it("keeps ten recent entries per owner by default", async () => { + await withFixedNow(async () => { + // A narrow showcase (3) isolates the fresh cap: with a wide + // showcase all eleven would publish through the showcase branch. + const raw = await entriesThroughReader(rankedFresh("cap", 11)); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", showcasePerOwner: 3 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + for (let i = 0; i < 10; i++) assert.ok(collected.entries.has(`omniroute/cap/m-${i}`)); + assert.ok(!collected.entries.has("omniroute/cap/m-10")); + }); + }); + + it("keeps three recent entries per owner when set to three", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader(rankedFresh("cap", 11)); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", freshPerOwner: 3 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + for (let i = 0; i < 3; i++) assert.ok(collected.entries.has(`omniroute/cap/m-${i}`)); + assert.ok(!collected.entries.has("omniroute/cap/m-3")); + }); + }); + + it("leaves owners without dates on the showcase branch", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader(crowd("plain", "plain/undated")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + for (let i = 0; i < 10; i++) assert.ok(collected.entries.has(`omniroute/plain/filler-${i}`)); + assert.ok(!collected.entries.has("omniroute/plain/undated")); + }); + }); + + it("sentinel publishes everything regardless of the fresh cap", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader(rankedFresh("cap", 11)); + const collected = await collectCatalog( + { ...baseOpts, visibleModels: ["*"], freshPerOwner: 3 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + } + ); + for (let i = 0; i < 11; i++) assert.ok(collected.entries.has(`omniroute/cap/m-${i}`)); + }); + }); +}); + +describe("showcase size option", () => { + it("keeps three entries per owner when set to three", async () => { + const raw = await entriesThroughReader([ + ...crowd("aaa", "aaa/target"), + ...crowd("bbb", "bbb/target"), + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", showcasePerOwner: 3 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + for (const owner of ["aaa", "bbb"]) { + for (let i = 0; i < 3; i++) + assert.ok(collected.entries.has(`omniroute/${owner}/filler-${i}`)); + assert.ok(!collected.entries.has(`omniroute/${owner}/filler-3`)); + assert.ok(!collected.entries.has(`omniroute/${owner}/target`)); + } + }); + + it("keeps ten entries per owner when unset", async () => { + const raw = await entriesThroughReader(crowd("aaa", "aaa/target")); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m" }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + for (let i = 0; i < 10; i++) assert.ok(collected.entries.has(`omniroute/aaa/filler-${i}`)); + assert.ok(!collected.entries.has("omniroute/aaa/target")); + }); + + it("zero on a direct call publishes the same set as unset", async () => { + const raw = await entriesThroughReader(crowd("aaa", "aaa/target")); + const fetchers = { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [] as string[], + }; + const tokenOpts = { ...baseOpts, managementReadToken: "m" }; + const fallback = await collectCatalog(tokenOpts, fetchers); + const zeroed = await collectCatalog({ ...tokenOpts, showcasePerOwner: 0 }, fetchers); + assert.ok(fallback.entries.size > 0); + assert.deepEqual([...zeroed.entries.keys()].sort(), [...fallback.entries.keys()].sort()); + }); + + it("sentinel publishes everything regardless of the showcase size", async () => { + const collected = await collectCatalog( + { ...baseOpts, visibleModels: ["*"], showcasePerOwner: 3 }, + { + fetcher: async () => crowd("aaa", "aaa/target") as unknown as OmniRouteRawModelEntry[], + combosFetcher: async () => [], + } + ); + for (let i = 0; i < 10; i++) assert.ok(collected.entries.has(`omniroute/aaa/filler-${i}`)); + assert.ok(collected.entries.has("omniroute/aaa/target")); + }); +}); + +describe("freshness window in days", () => { + it("excludes a 30-day-old entry under a seven-day window", async () => { + await withFixedNow(async () => { + // Five newer fillers push the target out of a small showcase (3) while + // staying inside the fresh cap (10): only the freshness window decides. + const fillers = Array.from({ length: 5 }, (_v, i) => ({ + id: `win/filler-${i}`, + owned_by: "win", + context_length: 100000, + release_date: dated(1, FIXED_NOW), + })); + const raw = await entriesThroughReader([ + ...fillers, + { + id: "win/thirty-day", + owned_by: "win", + context_length: 1000, + release_date: dated(30, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", showcasePerOwner: 3, freshWindowDays: 7 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/win/filler-0")); + assert.ok(!collected.entries.has("omniroute/win/thirty-day")); + }); + }); + + it("keeps a 30-day-old entry under the default window", async () => { + await withFixedNow(async () => { + // Five newer fillers push the target out of a small showcase (3) while + // staying inside the fresh cap (10): only the freshness window decides. + const fillers = Array.from({ length: 5 }, (_v, i) => ({ + id: `win/filler-${i}`, + owned_by: "win", + context_length: 100000, + release_date: dated(1, FIXED_NOW), + })); + const raw = await entriesThroughReader([ + ...fillers, + { + id: "win/thirty-day", + owned_by: "win", + context_length: 1000, + release_date: dated(30, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", showcasePerOwner: 3 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/win/thirty-day")); + }); + }); + + it("keeps a 30-day-old entry under a wide window", async () => { + await withFixedNow(async () => { + // Five newer fillers push the target out of a small showcase (3) while + // staying inside the fresh cap (10): only the freshness window decides. + const fillers = Array.from({ length: 5 }, (_v, i) => ({ + id: `win/filler-${i}`, + owned_by: "win", + context_length: 100000, + release_date: dated(1, FIXED_NOW), + })); + const raw = await entriesThroughReader([ + ...fillers, + { + id: "win/thirty-day", + owned_by: "win", + context_length: 1000, + release_date: dated(30, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, managementReadToken: "m", showcasePerOwner: 3, freshWindowDays: 180 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/win/thirty-day")); + }); + }); + + it("falls back to the default window on a non-finite direct value", async () => { + await withFixedNow(async () => { + // Five newer fillers push the target out of a small showcase (3) while + // staying inside the fresh cap (10): only the freshness window decides. + const fillers = Array.from({ length: 5 }, (_v, i) => ({ + id: `win/filler-${i}`, + owned_by: "win", + context_length: 100000, + release_date: dated(1, FIXED_NOW), + })); + const raw = await entriesThroughReader([ + ...fillers, + { + id: "win/thirty-day", + owned_by: "win", + context_length: 1000, + release_date: dated(30, FIXED_NOW), + }, + ]); + // The schema rejects NaN; a direct caller outside it still gets the + // default window instead of an empty fresh branch. + const collected = await collectCatalog( + { + ...baseOpts, + managementReadToken: "m", + showcasePerOwner: 3, + freshWindowDays: Number.NaN, + }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + usageFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/win/thirty-day")); + }); + }); + + it("sentinel publishes everything under a narrow window", async () => { + await withFixedNow(async () => { + const raw = await entriesThroughReader([ + { + id: "win/old", + owned_by: "win", + context_length: 1000, + release_date: dated(30, FIXED_NOW), + }, + { + id: "win/older", + owned_by: "win", + context_length: 1000, + release_date: dated(60, FIXED_NOW), + }, + ]); + const collected = await collectCatalog( + { ...baseOpts, visibleModels: ["*"], freshWindowDays: 7 }, + { + fetcher: async () => raw, + combosFetcher: async () => [], + } + ); + assert.ok(collected.entries.has("omniroute/win/old")); + assert.ok(collected.entries.has("omniroute/win/older")); + }); + }); +}); diff --git a/@omniroute/opencode-plugin-v2/tests/catalog.test.ts b/@omniroute/opencode-plugin-v2/tests/catalog.test.ts index 1318f721a8c8..1d917969b71c 100644 --- a/@omniroute/opencode-plugin-v2/tests/catalog.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/catalog.test.ts @@ -97,7 +97,7 @@ describe("catalog provider template", () => { ], combosFetcher: async () => [], }); - assert.deepEqual(res, { models: 2, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 2, combos: 0 }); const m = draft.models.get("omniroute/gpt-x"); assert.ok(m); assert.equal(m?.providerID, "omniroute"); @@ -160,7 +160,7 @@ describe("catalog fail-open", () => { }, combosFetcher: async () => [], }); - assert.deepEqual(res, { models: 0, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 0, combos: 0 }); } finally { console.warn = origWarn; } @@ -184,7 +184,7 @@ describe("catalog fail-open", () => { throw Object.assign(new Error("Not Found"), { status: 404 }); }, }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); } finally { console.warn = origWarn; } @@ -210,7 +210,7 @@ describe("catalog fail-open", () => { ); }, }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); } finally { console.warn = origWarn; } @@ -237,7 +237,7 @@ describe("catalog fail-open", () => { ); }, }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); } finally { console.warn = origWarn; } @@ -264,7 +264,7 @@ describe("catalog fail-open", () => { throw err; }, }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); } finally { console.warn = origWarn; } @@ -299,7 +299,7 @@ describe("catalog combo vs combo", () => { }, ], }); - assert.deepEqual(res, { models: 1, combos: 2, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 2 }); } finally { console.warn = origWarn; } @@ -331,7 +331,7 @@ describe("catalog model bare vs combo", () => { }, ], }); - assert.deepEqual(res, { models: 1, combos: 1, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 1 }); } finally { console.warn = origWarn; } @@ -340,3 +340,300 @@ describe("catalog model bare vs combo", () => { assert.match(warns[0], /"omniroute\/dupe"/); }); }); + +describe("catalog capability presets", () => { + function captureWarns() { + const warns: string[] = []; + const origWarn = console.warn; + console.warn = (...args: unknown[]) => { + warns.push(String(args[0])); + }; + return { + warns, + restore() { + console.warn = origWarn; + }, + }; + } + + it("freeOnly keeps only models with a free-tier enrichment entry", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, freeOnly: true }, + { + fetcher: async () => [{ id: "free-m" }, { id: "paid-m" }], + enrichmentFetcher: async () => + new Map([["free-m", { freeType: "recurring-monthly" as const }]]), + combosFetcher: async () => [], + } + ); + assert.equal(res.models, 1); + assert.ok(draft.models.has("omniroute/free-m")); + assert.ok(!draft.models.has("omniroute/paid-m")); + }); + + it("freeOnly without enrichment publishes nothing and warns once", async () => { + const draft = fakeDraft(); + const { warns, restore } = captureWarns(); + try { + const res = await publishCatalog( + draft, + { ...baseOpts, freeOnly: true }, + { + fetcher: async () => [{ id: "m1" }, { id: "m2" }], + enrichmentFetcher: async () => new Map(), + combosFetcher: async () => [], + } + ); + // #15392 stopped publishing auto combos, so counts no longer include autoCombos. + assert.deepEqual(res, { models: 0, combos: 0 }); + } finally { + restore(); + } + assert.equal(warns.length, 1); + assert.match(warns[0], /freeOnly.*enrichment|enrichment.*free/i); + }); + + it("freeOnly with enrichment disabled publishes nothing", async () => { + const draft = fakeDraft(); + const { warns, restore } = captureWarns(); + try { + const res = await publishCatalog( + draft, + { ...baseOpts, freeOnly: true, enrichment: false }, + { + fetcher: async () => [{ id: "m1" }], + combosFetcher: async () => [], + } + ); + assert.equal(res.models, 0); + } finally { + restore(); + } + assert.equal(warns.length, 1); + assert.match(warns[0], /freeOnly.*enrichment|enrichment.*free/i); + }); + + it("freeOnly drops combos without an enrichment entry", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, freeOnly: true }, + { + fetcher: async () => [{ id: "m1" }], + enrichmentFetcher: async () => new Map([["m1", { freeType: "keyless" as const }]]), + combosFetcher: async () => [ + { id: "combo-a", name: "Combo A", models: [{ kind: "model", model: "m1" }] }, + ], + } + ); + assert.equal(res.models, 1); + assert.equal(res.combos, 0); + assert.ok(!draft.models.has("omniroute/combo-a")); + }); + + it("toolsOnly keeps only tool-calling models", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, toolsOnly: true }, + { + fetcher: async () => [ + { id: "with-tools", capabilities: { tool_calling: true } }, + { id: "no-tools" }, + ], + combosFetcher: async () => [], + } + ); + assert.equal(res.models, 1); + assert.ok(draft.models.has("omniroute/with-tools")); + assert.ok(!draft.models.has("omniroute/no-tools")); + }); + + it("toolsOnly drops a combo with a member lacking tool calls", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, toolsOnly: true }, + { + fetcher: async () => [{ id: "a", capabilities: { tool_calling: true } }, { id: "b" }], + combosFetcher: async () => [ + { + id: "mixed", + name: "Mixed", + models: [ + { kind: "model", model: "a" }, + { kind: "model", model: "b" }, + ], + }, + { id: "pure", name: "Pure", models: [{ kind: "model", model: "a" }] }, + ], + } + ); + assert.equal(res.combos, 1); + assert.ok(draft.models.has("omniroute/pure")); + assert.ok(!draft.models.has("omniroute/mixed")); + }); + + it("visionOnly keeps image-input models from either server convention", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, visionOnly: true }, + { + fetcher: async () => [ + { id: "img", input_modalities: ["text", "image"] }, + { id: "txt", input_modalities: ["text"] }, + { id: "vis", capabilities: { vision: true } }, + ], + combosFetcher: async () => [], + } + ); + assert.equal(res.models, 2); + assert.ok(draft.models.has("omniroute/img")); + assert.ok(draft.models.has("omniroute/vis")); + assert.ok(!draft.models.has("omniroute/txt")); + }); + + it("visionOnly drops auto combos (no image input)", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, visionOnly: true }, + { + fetcher: async () => [], + combosFetcher: async () => [], + autoCombosFetcher: async () => [{ id: "auto", variant: undefined, candidateCount: 3 }], + } + ); + // #15392 no longer requests the virtual auto-combo route, so the entry is absent. + assert.equal((res as { autoCombos?: number }).autoCombos, undefined); + assert.ok(!draft.models.has("omniroute/auto")); + }); + + it("visibleModels allowlist composes with presets (both subtract)", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, visibleModels: ["a", "b"], toolsOnly: true }, + { + fetcher: async () => [ + { id: "a", capabilities: { tool_calling: true } }, + { id: "b" }, + { id: "c", capabilities: { tool_calling: true } }, + ], + combosFetcher: async () => [], + } + ); + assert.equal(res.models, 1); + assert.ok(draft.models.has("omniroute/a")); + }); + + it("hiddenModels deny wins over presets", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, hiddenModels: ["a"], toolsOnly: true }, + { + fetcher: async () => [ + { id: "a", capabilities: { tool_calling: true } }, + { id: "b", capabilities: { tool_calling: true } }, + ], + combosFetcher: async () => [], + } + ); + assert.equal(res.models, 1); + assert.ok(draft.models.has("omniroute/b")); + }); + + it("usableOnly and presets compose (both subtractive)", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { + ...baseOpts, + usableOnly: true, + toolsOnly: true, + enrichment: new Map([ + ["cc/good", { providerAlias: "cc", providerCanonical: "claude" }], + ["dead/bad", { providerAlias: "dead", providerCanonical: "legacy" }], + ["cc/plain", { providerAlias: "cc", providerCanonical: "claude" }], + ]), + }, + { + fetcher: async () => [ + { id: "cc/good", capabilities: { tool_calling: true } }, + { id: "dead/bad", capabilities: { tool_calling: true } }, + { id: "cc/plain" }, + ], + combosFetcher: async () => [], + providersFetcher: async () => [ + { id: "c1", provider: "claude", isActive: true, testStatus: "active" }, + ], + } + ); + assert.equal(res.models, 1); + assert.ok(draft.models.has("omniroute/cc/good")); + }); + + it("toolsOnly: false opts out and publishes the full catalog", async () => { + const draft = fakeDraft(); + const res = await publishCatalog( + draft, + { ...baseOpts, toolsOnly: false }, + { + fetcher: async () => [ + { id: "paid", capabilities: { tool_calling: true }, input_modalities: ["text", "image"] }, + { id: "plain" }, + ], + combosFetcher: async () => [], + } + ); + // #15392 stopped publishing auto combos, so counts no longer include autoCombos. + assert.deepEqual(res, { models: 2, combos: 0 }); + }); + + it("presets shrink a large stub catalog below threshold", async () => { + const stubModels = async () => + Array.from({ length: 4000 }, (_, i) => ({ + id: `m${i}`, + ...(i % 40 === 0 ? { capabilities: { tool_calling: true } } : {}), + ...(i % 25 === 0 ? { input_modalities: ["text", "image"] } : {}), + })); + const freeEnrichment = async () => + new Map( + Array.from({ length: 4000 }, (_, i) => + i % 80 === 0 ? [`m${i}`, { freeType: "recurring-monthly" as const }] : undefined + ).filter((x): x is [`m${number}`, { freeType: "recurring-monthly" }] => x !== undefined) + ); + // #15392 caps the default view before capability presets run. The `*` + // sentinel publishes every stub id so these counts measure the preset, + // not the per-owner showcase slice. + const allVisible = { visibleModels: ["*"] }; + const draftTools = fakeDraft(); + const resTools = await publishCatalog( + draftTools, + { ...baseOpts, toolsOnly: true, ...allVisible }, + { fetcher: stubModels, combosFetcher: async () => [] } + ); + assert.ok(resTools.models < 200, `toolsOnly kept ${resTools.models}`); + assert.equal(resTools.models, 100); + const draftVision = fakeDraft(); + const resVision = await publishCatalog( + draftVision, + { ...baseOpts, visionOnly: true, ...allVisible }, + { fetcher: stubModels, combosFetcher: async () => [] } + ); + assert.ok(resVision.models < 200, `visionOnly kept ${resVision.models}`); + assert.equal(resVision.models, 160); + const draftFree = fakeDraft(); + const resFree = await publishCatalog( + draftFree, + { ...baseOpts, freeOnly: true, ...allVisible }, + { fetcher: stubModels, enrichmentFetcher: freeEnrichment, combosFetcher: async () => [] } + ); + assert.ok(resFree.models < 200, `freeOnly kept ${resFree.models}`); + assert.equal(resFree.models, 50); + }); +}); diff --git a/@omniroute/opencode-plugin-v2/tests/disk-snapshot-atomicity.test.ts b/@omniroute/opencode-plugin-v2/tests/disk-snapshot-atomicity.test.ts index 042cbda3b5a2..4124bff050b6 100644 --- a/@omniroute/opencode-plugin-v2/tests/disk-snapshot-atomicity.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/disk-snapshot-atomicity.test.ts @@ -36,7 +36,6 @@ function makeSnapshot(models: string[] = ["m-a"]): CatalogSnapshot { return { models: models.map((id) => ({ id })), combos: [], - autoCombos: [], providers: [], fetchedAt: Date.now(), } as unknown as CatalogSnapshot; @@ -134,6 +133,13 @@ describe("disk snapshot atomic write, strict version, traced give-ups", () => { (back?.models ?? []).map((entry) => entry.id), ["m-before"] ); + assert.deepEqual(Object.keys((back ?? {}) as object).sort(), [ + "combos", + "enrichment", + "fetchedAt", + "models", + "providers", + ]); assert.match(messages.join("\n"), /failed|EISDIR|error/i); } finally { rmSync(blocker, { recursive: true, force: true }); diff --git a/@omniroute/opencode-plugin-v2/tests/enrichment.test.ts b/@omniroute/opencode-plugin-v2/tests/enrichment.test.ts index 033c3e9d9edd..b217ad72d2ad 100644 --- a/@omniroute/opencode-plugin-v2/tests/enrichment.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/enrichment.test.ts @@ -69,7 +69,7 @@ describe("catalog enrichment source", () => { combosFetcher: async () => [], enrichmentFetcher: async () => enrichment, }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); const m = models.get("omniroute/cc/m1"); assert.ok(m); assert.equal(m?.name, "Model One"); @@ -92,7 +92,7 @@ describe("catalog enrichment source", () => { throw new Error("pricing down"); }, }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); assert.ok(models.get("omniroute/cc/m1")); } finally { console.warn = origWarn; diff --git a/@omniroute/opencode-plugin-v2/tests/management-token-env.test.ts b/@omniroute/opencode-plugin-v2/tests/management-token-env.test.ts index 2f18811efe09..7be52546e25c 100644 --- a/@omniroute/opencode-plugin-v2/tests/management-token-env.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/management-token-env.test.ts @@ -54,18 +54,6 @@ function installHarness(combos: unknown[]): Harness { globalThis.fetch = (async (url: unknown, init?: { headers?: Record }) => { const href = String(url); seen.set(href, String(init?.headers?.Authorization ?? "")); - if (href.includes("/api/combos/auto")) return okJson({ combos: [] }); - if (href.includes("/api/pricing/models")) { - return okJson({ - providers: { - demo: { - id: "demo", - name: "Demo", - models: [{ id: "team-combo", name: "Team Combo" }], - }, - }, - }); - } if (href.includes("/api/pricing")) return okJson({}); if (href.includes("/api/free-tier/summary")) return okJson({ perModel: [] }); if (href.includes("/api/combos")) return okJson({ combos }); @@ -122,10 +110,10 @@ function setupHarness(options: Record) { }); return Promise.resolve({ dispose: async () => {} }); }, - }, - model: { - transform: () => Promise.resolve({ dispose: async () => {} }), - }, + }, + model: { + transform: () => Promise.resolve({ dispose: async () => {} }), + }, integration: { transform: () => Promise.resolve({ dispose: async () => {} }), }, @@ -378,7 +366,7 @@ describe("plugin-v2 management token environment source", () => { } ) ); - assert.deepEqual(res, { models: 1, combos: 1, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 1 }); assert.equal(seenCombos, "mgmt-env-token"); assert.equal(seenPricing, "mgmt-env-token"); const entry = models.get("omniroute/team-combo"); diff --git a/@omniroute/opencode-plugin-v2/tests/management-token.test.ts b/@omniroute/opencode-plugin-v2/tests/management-token.test.ts index db1ce0e81f7a..4efa38c152d7 100644 --- a/@omniroute/opencode-plugin-v2/tests/management-token.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/management-token.test.ts @@ -66,9 +66,6 @@ describe("plugin-v2 managementReadToken wiring (F1)", () => { globalThis.fetch = (async (url: unknown, init?: { headers?: Record }) => { const href = String(url); seen.set(href, String(init?.headers?.Authorization ?? "")); - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -76,7 +73,7 @@ describe("plugin-v2 managementReadToken wiring (F1)", () => { ok: true, status: 200, statusText: "OK", - json: async () => ({ data: [{ id: "m1" }] }), + json: async () => ({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }), }; }) as typeof fetch; const guard = silence(); @@ -88,7 +85,9 @@ describe("plugin-v2 managementReadToken wiring (F1)", () => { managementReadToken: "mgmt-key", }); await (plugin as unknown as { setup: (ctx: unknown) => Promise }).setup(ctx); - const ids = (added as Array<{ models: Array<{ id: string }> }>).flatMap((a) => a.models.map((m) => m.id)); + const ids = (added as Array<{ models: Array<{ id: string }> }>).flatMap((a) => + a.models.map((m) => m.id) + ); assert.ok(ids.includes("m1")); assert.equal(seen.get(COMBOS_URL), "Bearer mgmt-key"); assert.equal(seen.get(MODELS_URL), "Bearer chat-key"); @@ -104,9 +103,6 @@ describe("plugin-v2 managementReadToken wiring (F1)", () => { globalThis.fetch = (async (url: unknown, init?: { headers?: Record }) => { const href = String(url); seen.set(href, String(init?.headers?.Authorization ?? "")); - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -114,7 +110,7 @@ describe("plugin-v2 managementReadToken wiring (F1)", () => { ok: true, status: 200, statusText: "OK", - json: async () => ({ data: [{ id: "m1" }] }), + json: async () => ({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }), }; }) as typeof fetch; const guard = silence(); @@ -147,7 +143,7 @@ describe("plugin-v2 managementReadToken wiring (F1)", () => { { fetcher: async (_baseURL, token) => { calls.push(["models", token]); - return [{ id: "m1" }]; + return [{ id: "m1", capabilities: { tool_calling: true } }]; }, combosFetcher: async (_baseURL, token) => { calls.push(["combos", token]); @@ -156,7 +152,7 @@ describe("plugin-v2 managementReadToken wiring (F1)", () => { } ); const res = collected.counts; - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); assert.deepEqual(calls, [ ["models", "chat-key"], ["combos", "mgmt-key"], @@ -176,9 +172,6 @@ describe("plugin-v2 fail-closed models (F2)", () => { const origFetch = globalThis.fetch; globalThis.fetch = (async (url: unknown) => { const href = String(url); - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -188,7 +181,7 @@ describe("plugin-v2 fail-closed models (F2)", () => { ok: true, status: 200, statusText: "OK", - json: async () => ({ data: [{ id: "m1" }] }), + json: async () => ({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }), }; } return { ok: false, status: 500, statusText: "Error", json: async () => ({}) }; @@ -214,7 +207,9 @@ describe("plugin-v2 fail-closed models (F2)", () => { integration: { transform: () => Promise.resolve({ dispose: async () => {} }) }, }; await (plugin as unknown as { setup: (ctx: unknown) => Promise }).setup(firstCtx); - const firstIds = (firstAdded as Array<{ models: Array<{ id: string }> }>).flatMap((a) => a.models.map((m) => m.id)); + const firstIds = (firstAdded as Array<{ models: Array<{ id: string }> }>).flatMap((a) => + a.models.map((m) => m.id) + ); assert.ok(firstIds.includes("m1"), "first refresh must publish m1"); const { setTimeout: sleep } = await import("node:timers/promises"); await sleep(5); @@ -239,7 +234,9 @@ describe("plugin-v2 fail-closed models (F2)", () => { await (plugin as unknown as { setup: (ctx: unknown) => Promise }).setup(secondCtx); if (prevDataDir === undefined) delete process.env.OPENCODE_DATA_DIR; else process.env.OPENCODE_DATA_DIR = prevDataDir; - const secondIds = (secondAdded as Array<{ models: Array<{ id: string }> }>).flatMap((a) => a.models.map((m) => m.id)); + const secondIds = (secondAdded as Array<{ models: Array<{ id: string }> }>).flatMap((a) => + a.models.map((m) => m.id) + ); assert.ok(secondIds.includes("m1"), "empty models fetch must reuse last-known catalog"); assert.ok( guard.warns.some((w) => w.includes("keeping last-known catalog")), diff --git a/@omniroute/opencode-plugin-v2/tests/nested-combos.test.ts b/@omniroute/opencode-plugin-v2/tests/nested-combos.test.ts index 6626cde3c5db..9b673fa53214 100644 --- a/@omniroute/opencode-plugin-v2/tests/nested-combos.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/nested-combos.test.ts @@ -108,7 +108,7 @@ describe("catalog nested combo refs", () => { }, ], }); - assert.deepEqual(res, { models: 2, combos: 2, autoCombos: 0 }); + assert.deepEqual(res, { models: 2, combos: 2 }); const parent = c.models.get("omniroute/parent"); assert.ok(parent); assert.equal(parent?.limit.context, 50000); @@ -132,7 +132,7 @@ describe("catalog nested combo refs", () => { }, ], }); - assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 0 }); assert.ok(!c.models.has("omniroute/orphan")); assert.ok(c.warns.some((w) => w.includes("could not resolve"))); } finally { @@ -215,7 +215,7 @@ describe("catalog collision dedupe", () => { }, ], }); - assert.deepEqual(res, { models: 1, combos: 1, autoCombos: 0 }); + assert.deepEqual(res, { models: 1, combos: 1 }); assert.ok(!c.warns.some((w) => w.includes("collides"))); assert.equal(c.models.get("omniroute/Mirror Combo")?.name, "Mirror Combo"); } finally { diff --git a/@omniroute/opencode-plugin-v2/tests/options.test.ts b/@omniroute/opencode-plugin-v2/tests/options.test.ts index cf36b1331ded..b2a3f18af2ba 100644 --- a/@omniroute/opencode-plugin-v2/tests/options.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/options.test.ts @@ -9,18 +9,123 @@ import { } from "../src/options.js"; describe("parsePluginOptions", () => { - it("applies defaults for providerId, timeoutMs, usableOnly, enrichment", () => { + it("applies the real default path via parsePluginOptions: toolsOnly on, freeOnly/visionOnly off", () => { const opts = parsePluginOptions({ baseURL: "https://gw.example.com" }); assert.equal(opts.providerId, "omniroute"); assert.equal(opts.timeoutMs, 10000); assert.equal(opts.usableOnly, false); assert.equal(opts.enrichment, true); + assert.equal(opts.freeOnly, false); + assert.equal(opts.toolsOnly, true); + assert.equal(opts.visionOnly, false); assert.equal(opts.modelCacheTtlMs, undefined); }); + it("toolsOnly: false is an explicit full-catalog opt-out", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com", toolsOnly: false }); + assert.equal(opts.toolsOnly, false); + }); it("accepts a positive modelCacheTtlMs (in-memory TTL cache, default 300s)", () => { const opts = parsePluginOptions({ baseURL: "https://gw.example.com", modelCacheTtlMs: 60000 }); assert.equal(opts.modelCacheTtlMs, 60000); }); + it("accepts a showcase size of seven", () => { + const opts = parsePluginOptions({ + baseURL: "https://gw.example.com", + showcasePerOwner: 7, + }); + assert.equal(opts.showcasePerOwner, 7); + }); + it("leaves the showcase size unset when absent", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com" }); + assert.equal(opts.showcasePerOwner, undefined); + }); + it("accepts a custom showcase size", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com", showcasePerOwner: 3 }); + assert.equal(opts.showcasePerOwner, 3); + }); + it("rejects a non-positive showcase size", () => { + for (const showcasePerOwner of [0, -1, Number.NaN]) { + assert.throws(() => + parsePluginOptions({ baseURL: "https://gw.example.com", showcasePerOwner }) + ); + } + }); + it("rejects a non-numeric showcase size", () => { + assert.throws(() => + parsePluginOptions({ baseURL: "https://gw.example.com", showcasePerOwner: "10" }) + ); + }); + it("accepts a fresh cap of seven", () => { + const opts = parsePluginOptions({ + baseURL: "https://gw.example.com", + freshPerOwner: 7, + }); + assert.equal(opts.freshPerOwner, 7); + }); + it("leaves the fresh cap unset when absent", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com" }); + assert.equal(opts.freshPerOwner, undefined); + }); + it("accepts a custom fresh cap", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com", freshPerOwner: 3 }); + assert.equal(opts.freshPerOwner, 3); + }); + it("accepts a freshness window of seven days", () => { + const opts = parsePluginOptions({ + baseURL: "https://gw.example.com", + freshWindowDays: 7, + }); + assert.equal(opts.freshWindowDays, 7); + }); + it("leaves the usage memory unset when absent", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com" }); + assert.equal(opts.usageMemory, undefined); + }); + it("accepts usage memory true or false", () => { + assert.equal( + parsePluginOptions({ baseURL: "https://gw.example.com", usageMemory: true }).usageMemory, + true + ); + assert.equal( + parsePluginOptions({ baseURL: "https://gw.example.com", usageMemory: false }).usageMemory, + false + ); + }); + it("rejects a non-boolean usage memory", () => { + for (const usageMemory of ["yes", 1]) { + assert.throws(() => parsePluginOptions({ baseURL: "https://gw.example.com", usageMemory })); + } + }); + it("leaves the freshness window unset when absent", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com" }); + assert.equal(opts.freshWindowDays, undefined); + }); + it("accepts a wide freshness window", () => { + const opts = parsePluginOptions({ baseURL: "https://gw.example.com", freshWindowDays: 180 }); + assert.equal(opts.freshWindowDays, 180); + }); + it("rejects a non-positive freshness window", () => { + for (const freshWindowDays of [0, -1, Number.NaN]) { + assert.throws(() => + parsePluginOptions({ baseURL: "https://gw.example.com", freshWindowDays }) + ); + } + }); + it("rejects a non-numeric freshness window", () => { + assert.throws(() => + parsePluginOptions({ baseURL: "https://gw.example.com", freshWindowDays: "90" }) + ); + }); + it("rejects a non-positive fresh cap", () => { + for (const freshPerOwner of [0, -1, Number.NaN]) { + assert.throws(() => parsePluginOptions({ baseURL: "https://gw.example.com", freshPerOwner })); + } + }); + it("rejects a non-numeric fresh cap", () => { + assert.throws(() => + parsePluginOptions({ baseURL: "https://gw.example.com", freshPerOwner: "10" }) + ); + }); it("rejects a non-positive modelCacheTtlMs", () => { assert.throws(() => parsePluginOptions({ baseURL: "https://gw.example.com", modelCacheTtlMs: 0 }) @@ -137,6 +242,27 @@ describe("invalid options say what to fix", () => { assert.equal(parsed.modelCacheTtlMs, 300000); assert.equal(resolveTimeouts(parsed).models, 15000); }); + + it("leaves providersAllow absent by default", () => { + assert.equal( + parsePluginOptions({ baseURL: "http://gw.example.com" }).providersAllow, + undefined + ); + }); + + it("accepts a providersAllow list", () => { + assert.deepEqual( + parsePluginOptions({ baseURL: "http://gw.example.com", providersAllow: ["claude"] }) + .providersAllow, + ["claude"] + ); + }); + + it("rejects a non-array providersAllow", () => { + assert.throws(() => + parsePluginOptions({ baseURL: "http://gw.example.com", providersAllow: "cc" }) + ); + }); }); describe("providerId is bounded because it reaches a filesystem path", () => { diff --git a/@omniroute/opencode-plugin-v2/tests/parity.test.ts b/@omniroute/opencode-plugin-v2/tests/parity.test.ts index 0d86390665cc..1ce3c60712aa 100644 --- a/@omniroute/opencode-plugin-v2/tests/parity.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/parity.test.ts @@ -126,7 +126,7 @@ describe("v1-vs-v2 catalog parity", () => { const counts = await publishCatalog(draft, TEST_OPTS, { fetcher, combosFetcher }); assert.equal(counts.models, 5); assert.equal(counts.combos, 2); - assert.equal(counts.autoCombos, 0); + assert.deepEqual(counts, { models: 5, combos: 2 }); // Final converted Record shape (legacy→info boundary in // src/catalog.ts assignModelFields): api resolves to the diff --git a/@omniroute/opencode-plugin-v2/tests/provider-filter.test.ts b/@omniroute/opencode-plugin-v2/tests/provider-filter.test.ts new file mode 100644 index 000000000000..63f1ba16c0a3 --- /dev/null +++ b/@omniroute/opencode-plugin-v2/tests/provider-filter.test.ts @@ -0,0 +1,178 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { + buildProviderResolve, + compileProviderFilter, + matchesAllow, + passesProviderCombo, + passesProviderFilter, + providerOf, + unknownProviders, +} from "../src/provider-filter.js"; + +function resolveCcClaude() { + return buildProviderResolve([{ alias: "cc", canonical: "claude" }], ["claude"]); +} + +describe("providerOf", () => { + it("returns the prefix before the first slash, lowercased", () => { + assert.equal(providerOf("cc/claude-sonnet-4-6"), "cc"); + assert.equal(providerOf("CC/model"), "cc"); + }); + + it("returns undefined for bare ids (no provider claim, always keep)", () => { + assert.equal(providerOf("plain"), undefined); + assert.equal(providerOf("auto"), undefined); + }); +}); + +describe("compileProviderFilter", () => { + it("absent/empty/blank-only input means inactive (full catalog)", () => { + assert.equal(compileProviderFilter(undefined), undefined); + assert.equal(compileProviderFilter([]), undefined); + assert.equal(compileProviderFilter([" "]), undefined); + }); + + it("normalizes case, trims, dedupes", () => { + const filter = compileProviderFilter([" Claude ", "claude", "KIRO"]); + assert.ok(filter); + assert.deepEqual([...filter.allow].sort(), ["claude", "kiro"]); + }); +}); + +describe("matchesAllow alias resolution", () => { + it("allow canonical matches alias prefix (cc resolves to claude)", () => { + const filter = compileProviderFilter(["claude"]); + assert.ok(filter); + assert.equal(matchesAllow("cc", filter, resolveCcClaude()), true); + assert.equal(matchesAllow("claude", filter, resolveCcClaude()), true); + assert.equal(matchesAllow("kiro", filter, resolveCcClaude()), false); + }); + + it("allow alias matches canonical prefix", () => { + const filter = compileProviderFilter(["cc"]); + assert.ok(filter); + const resolve = buildProviderResolve([{ alias: "cc", canonical: "claude" }], ["claude"]); + assert.equal(matchesAllow("claude", filter, resolve), true); + }); + + it("without a resolve table only direct prefixes match", () => { + const filter = compileProviderFilter(["claude"]); + assert.ok(filter); + assert.equal(matchesAllow("cc", filter, undefined), false); + assert.equal(matchesAllow("claude", filter, undefined), true); + }); +}); + +describe("passesProviderFilter", () => { + it("no filter keeps everything", () => { + assert.equal(passesProviderFilter("cc/x", undefined, resolveCcClaude()), true); + }); + + it("bare ids always pass", () => { + const filter = compileProviderFilter(["kiro"]); + assert.ok(filter); + assert.equal(passesProviderFilter("plain", filter, resolveCcClaude()), true); + }); + + it("unknown prefixes keep (fail-open)", () => { + const filter = compileProviderFilter(["claude"]); + assert.ok(filter); + assert.equal(passesProviderFilter("mystery/x", filter, resolveCcClaude()), true); + }); + + it("lone unknown allow name keeps known prefixes (fail-open on the NAME)", () => { + const filter = compileProviderFilter(["nope"]); + assert.ok(filter); + assert.equal(passesProviderFilter("cc/x", filter, resolveCcClaude()), true); + assert.equal(passesProviderFilter("mystery/x", filter, resolveCcClaude()), true); + }); + + it("known-but-excluded prefixes drop", () => { + const resolve = buildProviderResolve( + [ + { alias: "cc", canonical: "claude" }, + { alias: "kiro", canonical: "kiro" }, + ], + ["claude", "kiro"] + ); + const filter = compileProviderFilter(["claude"]); + assert.ok(filter); + assert.equal(passesProviderFilter("kiro/y", filter, resolve), false); + }); +}); + +describe("passesProviderCombo", () => { + it("multi-provider combo survives when one member is allowed", () => { + const filter = compileProviderFilter(["claude"]); + assert.ok(filter); + assert.equal( + passesProviderCombo( + { + id: "mix", + models: [ + { kind: "model", model: "cc/a" }, + { kind: "model", model: "kiro/b" }, + ], + }, + filter, + buildProviderResolve( + [ + { alias: "cc", canonical: "claude" }, + { alias: "kiro", canonical: "kiro" }, + ], + ["claude", "kiro"] + ) + ), + true + ); + }); + + it("combo drops when no member is allowed", () => { + const filter = compileProviderFilter(["claude"]); + assert.ok(filter); + const resolveOnlyClaude = buildProviderResolve( + [{ alias: "cc", canonical: "claude" }], + ["claude"] + ); + // "kiro/b" is unknown to this vocabulary so it keeps fail-open; add a + // companion member known-but-excluded to prove the drop path instead. + assert.equal( + passesProviderCombo( + { id: "other", models: [{ kind: "model", model: "cc/a" }] }, + compileProviderFilter(["zzz-absent"])!, + buildProviderResolve( + [ + { alias: "cc", canonical: "claude" }, + { alias: "zzz-absent", canonical: "zzz-absent" }, + ], + ["claude"] + ) + ), + false + ); + void filter; + void resolveOnlyClaude; + }); + + it("combo-ref-only combos keep (nothing resolvable to exclude)", () => { + const filter = compileProviderFilter(["claude"]); + assert.ok(filter); + assert.equal( + passesProviderCombo( + { id: "nested", models: [{ kind: "combo-ref", comboName: "child" }] }, + filter, + resolveCcClaude() + ), + true + ); + }); +}); + +describe("unknownProviders", () => { + it("reports allow entries missing from the vocabulary", () => { + const filter = compileProviderFilter(["claude", "nope"]); + assert.ok(filter); + assert.deepEqual(unknownProviders(filter, resolveCcClaude().known), ["nope"]); + }); +}); diff --git a/@omniroute/opencode-plugin-v2/tests/publish-guard.test.ts b/@omniroute/opencode-plugin-v2/tests/publish-guard.test.ts index b9a8949fa4a2..a363f288d2d4 100644 --- a/@omniroute/opencode-plugin-v2/tests/publish-guard.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/publish-guard.test.ts @@ -43,9 +43,6 @@ describe("plugin-v2 publish guard (mapper/host throws)", () => { function stubFetch(): typeof fetch { return (async (url: unknown) => { const href = String(url); - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -96,4 +93,44 @@ describe("plugin-v2 publish guard (mapper/host throws)", () => { restoreDisk(); } }); + + it("requests exactly the declared refresh routes, never the retired one", async () => { + const { isDeclaredRefreshPath } = await import("../src/catalog.js"); + const restoreDisk = isolateDisk(); + const { ctx } = setupCtx(() => {}); + const requested: string[] = []; + const origFetch = globalThis.fetch; + globalThis.fetch = (async (url: unknown) => { + const href = String(url); + requested.push(new URL(href).pathname); + if (href.includes("/api/combos")) { + return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; + } + if (href.includes("/api/pricing") || href.includes("/api/free-tier")) { + return { ok: true, status: 200, statusText: "OK", json: async () => ({}) }; + } + return { + ok: true, + status: 200, + statusText: "OK", + json: async () => ({ data: [{ id: "m1" }] }), + }; + }) as typeof fetch; + try { + await silenceConsole(async () => { + await (plugin as unknown as { setup: (ctx: unknown) => Promise }).setup(ctx); + }); + assert.ok(requested.length > 0, "setup must issue refresh requests"); + for (const pathname of requested) { + assert.equal( + isDeclaredRefreshPath(pathname), + true, + `unexpected refresh path ${pathname} in ${JSON.stringify(requested)}` + ); + } + } finally { + globalThis.fetch = origFetch; + restoreDisk(); + } + }); }); diff --git a/@omniroute/opencode-plugin-v2/tests/refresh-failopen.test.ts b/@omniroute/opencode-plugin-v2/tests/refresh-failopen.test.ts index 0080e13970f6..cce04cb9279b 100644 --- a/@omniroute/opencode-plugin-v2/tests/refresh-failopen.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/refresh-failopen.test.ts @@ -56,9 +56,6 @@ describe("plugin-v2 fail-open refresh (PROD 403 combos)", () => { const modelsStatus = opts.modelsStatus ?? 200; return (async (url: unknown) => { const href = String(url); - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: opts.combosStatus === 200, @@ -71,7 +68,7 @@ describe("plugin-v2 fail-open refresh (PROD 403 combos)", () => { ok: modelsStatus === 200, status: modelsStatus, statusText: "OK", - json: async () => ({ data: [{ id: "m1" }] }), + json: async () => ({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }), }; }) as typeof fetch; } @@ -159,11 +156,10 @@ describe("plugin-v2 fail-open refresh (PROD 403 combos)", () => { const added: unknown[] = []; const { ctx } = setupCtx({ combosStatus: 200, reloads, added }); const origFetch = globalThis.fetch; + const requested: string[] = []; globalThis.fetch = (async (url: unknown) => { const href = String(url); - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } + requested.push(new URL(href).pathname); if (href.includes("/api/combos")) { const err = new Error("This operation was aborted"); err.name = "AbortError"; @@ -173,7 +169,7 @@ describe("plugin-v2 fail-open refresh (PROD 403 combos)", () => { ok: true, status: 200, statusText: "OK", - json: async () => ({ data: [{ id: "m1" }] }), + json: async () => ({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }), }; }) as typeof fetch; try { @@ -188,6 +184,10 @@ describe("plugin-v2 fail-open refresh (PROD 403 combos)", () => { warns.some((w) => w.includes("combos")), `expected a combos warn, got: ${JSON.stringify(warns)}` ); + assert.ok( + !requested.some((p) => p === "/api/combos/auto"), + `retired route must never be requested, got ${JSON.stringify(requested)}` + ); } finally { globalThis.fetch = origFetch; restoreDisk(); diff --git a/@omniroute/opencode-plugin-v2/tests/refresh-routes.test.ts b/@omniroute/opencode-plugin-v2/tests/refresh-routes.test.ts new file mode 100644 index 000000000000..ab952e65506a --- /dev/null +++ b/@omniroute/opencode-plugin-v2/tests/refresh-routes.test.ts @@ -0,0 +1,140 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { collectCatalog, DECLARED_REFRESH_ROUTES, isDeclaredRefreshPath } from "../src/catalog.js"; +import { autoComboModelId, AUTO_VARIANTS } from "../src/shared/naming.js"; + +const baseOpts = { + providerId: "omniroute", + baseURL: "https://gw.example.com", + apiKey: "k", + timeoutMs: 1000, + modelCacheTtlMs: 300000, + usableOnly: false, +}; + +function virtualIds(): string[] { + const ids = ["auto"]; + for (const variant of AUTO_VARIANTS) ids.push(autoComboModelId(variant)); + return ids; +} + +describe("refresh route table (closed set)", () => { + it("declares exactly the six refresh routes", () => { + assert.deepEqual( + [...DECLARED_REFRESH_ROUTES], + [ + "/v1/models", + "/api/combos", + "/api/providers", + "/api/pricing/models", + "/api/pricing", + "/api/free-tier/summary", + ] + ); + }); + + it("matches declared paths by exact equality, never the removed route", () => { + for (const route of DECLARED_REFRESH_ROUTES) assert.equal(isDeclaredRefreshPath(route), true); + assert.equal(isDeclaredRefreshPath("/api/combos/auto"), false); + assert.equal(isDeclaredRefreshPath("/api/combos/auto/extra"), false); + assert.equal(isDeclaredRefreshPath("/v1/models/extra"), false); + }); + + it("collects the catalog without publishing retired virtual entries", async () => { + const collected = await collectCatalog(baseOpts, { + models: async () => [{ id: "m1" }], + combos: async () => [], + providers: async () => [], + enrichment: async () => new Map(), + } as never); + assert.deepEqual(collected.counts, { models: 1, combos: 0 }); + const keys = [...collected.entries.keys()]; + for (const id of virtualIds()) { + assert.equal( + keys.some((k) => k === `omniroute/${id}`), + false, + `retired entry ${id} must stay unpublished, got ${JSON.stringify(keys)}` + ); + } + }); + + it("ignores a legacy fetcher entry the refresh no longer reads", async () => { + const collected = await collectCatalog(baseOpts, { + models: async () => [{ id: "m1" }], + combos: async () => [], + autoCombos: async () => [{ id: "auto" }, { id: "auto/coding" }], + providers: async () => [], + enrichment: async () => new Map(), + } as never); + assert.deepEqual(collected.counts, { models: 1, combos: 0 }); + const keys = [...collected.entries.keys()]; + for (const id of ["auto", "auto/coding"]) { + assert.equal( + keys.some((k) => k === `omniroute/${id}`), + false, + `retired entry ${id} must stay unpublished, got ${JSON.stringify(keys)}` + ); + } + }); + + it("keeps server-provided auto entries published with their own limits", async () => { + const collected = await collectCatalog(baseOpts, { + models: async () => [{ id: "auto/coding", context_length: 64000, max_output_tokens: 4000 }], + combos: async () => [], + providers: async () => [], + enrichment: async () => new Map(), + } as never); + const entry = collected.entries.get("omniroute/auto/coding"); + assert.ok(entry, "server-provided entry must stay published"); + assert.equal(entry?.limit.context, 64000); + assert.equal(entry?.limit.output, 4000); + }); + + it("round-trips a snapshot without the retired field", async () => { + const { mkdtempSync } = await import("node:fs"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const { readFileSync, writeFileSync, rmSync } = await import("node:fs"); + const { writeDiskSnapshot, readDiskSnapshot, diskSnapshotPath } = + await import("../src/cache.js"); + const dir = mkdtempSync(join(tmpdir(), "omniroute-retired-field-")); + const prev = process.env.OPENCODE_DATA_DIR; + process.env.OPENCODE_DATA_DIR = dir; + try { + await writeDiskSnapshot( + "legacy-tolerant", + { models: [{ id: "m-a" }], combos: [], providers: [], fetchedAt: Date.now() } as never, + "fp-legacy" + ); + const file = diskSnapshotPath("legacy-tolerant"); + const raw = JSON.parse(readFileSync(file, "utf8")) as Record; + assert.equal("autoCombos" in raw, false); + writeFileSync(file, JSON.stringify({ ...raw, autoCombos: [{ id: "auto" }] })); + const stored = readFileSync(file, "utf8"); + assert.ok( + (JSON.parse(stored) as Record).autoCombos !== undefined, + "the stored snapshot must expose the retired key before it is ignored" + ); + const back = await readDiskSnapshot("legacy-tolerant", "fp-legacy"); + assert.ok(back, "snapshot with a retired field still loads"); + assert.equal("autoCombos" in (back as object), false); + assert.deepEqual( + (back?.models ?? []).map((entry) => entry.id), + ["m-a"] + ); + } finally { + if (prev === undefined) delete process.env.OPENCODE_DATA_DIR; + else process.env.OPENCODE_DATA_DIR = prev; + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("parses a retired per-endpoint timeout key without requesting anything", async () => { + const { parsePluginOptions, resolveTimeouts } = await import("../src/options.js"); + const parsed = parsePluginOptions({ + baseURL: "https://gw.example.com", + timeouts: { models: 1111, autoCombos: 3333 }, + }); + assert.deepEqual(resolveTimeouts(parsed), { models: 1111, combos: 10000, enrichment: 10000 }); + }); +}); diff --git a/@omniroute/opencode-plugin-v2/tests/shared-anthropic-prefixes.test.ts b/@omniroute/opencode-plugin-v2/tests/shared-anthropic-prefixes.test.ts index 8e1e93b2752a..7248baafc30e 100644 --- a/@omniroute/opencode-plugin-v2/tests/shared-anthropic-prefixes.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/shared-anthropic-prefixes.test.ts @@ -164,7 +164,7 @@ describe("deprecated anthropicPrefixes", () => { enrichmentFetcher: async () => new Map(), } ); - assert.deepEqual(collected.counts, { models: 1, combos: 0, autoCombos: 0 }); + assert.deepEqual(collected.counts, { models: 1, combos: 0 }); const payload = buildProviderPayload(collected, { providerId: "omniroute", baseURL: GW, @@ -173,7 +173,9 @@ describe("deprecated anthropicPrefixes", () => { modelCacheTtlMs: 300000, usableOnly: false, }); - const m = payload.models.find((x) => String((x as unknown as { id: string }).id) === "cc/claude-x") as unknown as Record; + const m = payload.models.find( + (x) => String((x as unknown as { id: string }).id) === "cc/claude-x" + ) as unknown as Record; assert.ok(m); assert.equal(m?.package, "@opencode/ai/providers/anthropic"); } finally { diff --git a/@omniroute/opencode-plugin-v2/tests/shared-auto-combos.test.ts b/@omniroute/opencode-plugin-v2/tests/shared-auto-combos.test.ts deleted file mode 100644 index 9d5adf867d39..000000000000 --- a/@omniroute/opencode-plugin-v2/tests/shared-auto-combos.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { describe, it } from "node:test"; -import assert from "node:assert/strict"; -import { - defaultOmniRouteAutoCombosFetcher, - mapAutoComboToModelV2, -} from "../src/shared/auto-combos.js"; - -// A fetch stub that hangs until the caller aborts: proves the AbortController -// wiring fires. Rejects with an AbortError like undici does on abort. -function hangingFetch(): typeof fetch { - return ((url: unknown, init?: { signal?: AbortSignal }) => { - return new Promise((_resolve, reject) => { - const signal = init?.signal; - if (signal?.aborted) { - reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })); - return; - } - signal?.addEventListener("abort", () => { - reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })); - }); - }) as Promise; - }) as unknown as typeof fetch; -} - -function stubFetch(res: { status: number; statusText?: string; body?: unknown }): typeof fetch { - return (async () => ({ - ok: res.status >= 200 && res.status < 300, - status: res.status, - statusText: res.statusText ?? (res.status === 404 ? "Not Found" : "Error"), - json: async () => res.body, - })) as unknown as typeof fetch; -} - -function silenceWarn(): { warns: string[]; restore: () => void } { - const warns: string[] = []; - const origWarn = console.warn; - console.warn = (...args: unknown[]) => { - warns.push(String(args[0])); - }; - return { - warns, - restore() { - console.warn = origWarn; - }, - }; -} - -describe("defaultOmniRouteAutoCombosFetcher", () => { - it("404 returns [] with a warn (old gateway without the endpoint stays fail-open)", async () => { - const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ status: 404 }); - const guard = silenceWarn(); - try { - const res = await defaultOmniRouteAutoCombosFetcher("https://gw.example.com", "k", 5000); - assert.deepEqual(res, []); - } finally { - globalThis.fetch = origFetch; - guard.restore(); - } - assert.ok( - guard.warns.some((w) => w.includes("/api/combos/auto") && w.includes("404")), - `expected a 404 warn, got: ${JSON.stringify(guard.warns)}` - ); - }); - - it("500 throws (with warn + token-hint report) so the caller keeps last-known", async () => { - const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ status: 500, statusText: "Internal Server Error" }); - const guard = silenceWarn(); - const reported: string[] = []; - try { - await assert.rejects( - defaultOmniRouteAutoCombosFetcher("https://gw.example.com", "k", 5000, undefined, (e, r) => - reported.push(`${e} ${r}`) - ), - /HTTP 500/ - ); - } finally { - globalThis.fetch = origFetch; - guard.restore(); - } - assert.ok( - guard.warns.some((w) => w.includes("/api/combos/auto") && w.includes("500")), - `expected a 500 warn, got: ${JSON.stringify(guard.warns)}` - ); - assert.ok( - reported.some((r) => r.includes("/api/combos/auto")), - `expected a per-endpoint report, got: ${JSON.stringify(reported)}` - ); - }); - - it("a network throw rejects instead of resolving empty", async () => { - const origFetch = globalThis.fetch; - globalThis.fetch = hangingFetch(); - const guard = silenceWarn(); - try { - await assert.rejects( - defaultOmniRouteAutoCombosFetcher("https://gw.example.com", "k", 20), - /abort|aborted/i - ); - } finally { - globalThis.fetch = origFetch; - guard.restore(); - } - assert.ok( - guard.warns.some((w) => w.includes("/api/combos/auto")), - `expected an abort warn, got: ${JSON.stringify(guard.warns)}` - ); - }); - - it("accepts the {combos:[...]} envelope and filters entries without a string id", async () => { - const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ - status: 200, - body: { combos: [{ id: "auto/coding", name: "Auto Coding" }, { name: "no-id" }] }, - }); - const guard = silenceWarn(); - try { - const res = await defaultOmniRouteAutoCombosFetcher("https://gw.example.com", "k", 5000); - assert.deepEqual(res, [{ id: "auto/coding", name: "Auto Coding" }]); - } finally { - globalThis.fetch = origFetch; - guard.restore(); - } - }); -}); - -describe("mapAutoComboToModelV2", () => { - it("falls back to 128k context / 8k output when the server omits limits (never 0)", () => { - const m = mapAutoComboToModelV2( - { id: "auto/coding", name: "Auto Coding", variant: "coding" }, - "omniroute", - "https://gw.example.com" - ); - assert.equal(m.id, "auto/coding"); - assert.equal(m.limit.context, 128_000); - assert.equal(m.limit.output, 8_192); - assert.equal(m.capabilities.toolcall, true); - assert.equal(m.capabilities.reasoning, true); - }); - - it("uses server limits when positive, falls back when zero or negative", () => { - const served = mapAutoComboToModelV2( - { id: "auto", name: "Auto", context_length: 200000, max_output_tokens: 16000 }, - "omniroute", - "https://gw.example.com" - ); - assert.equal(served.limit.context, 200000); - assert.equal(served.limit.output, 16000); - const zeroed = mapAutoComboToModelV2( - { id: "auto", name: "Auto", context_length: 0, max_output_tokens: -1 }, - "omniroute", - "https://gw.example.com" - ); - assert.equal(zeroed.limit.context, 128_000); - assert.equal(zeroed.limit.output, 8_192); - }); - - it("defaults the model id to the auto variant key (auto, auto/coding)", () => { - const def = mapAutoComboToModelV2({ id: "whatever" }, "omniroute", "https://gw.example.com"); - assert.equal(def.id, "auto"); - const coding = mapAutoComboToModelV2( - { id: "auto/coding", variant: "coding" }, - "omniroute", - "https://gw.example.com" - ); - assert.equal(coding.id, "auto/coding"); - }); - - it("stamps the openai-compatible api block by default", () => { - const m = mapAutoComboToModelV2( - { id: "auto", candidateCount: 3 }, - "omniroute", - "https://gw.example.com" - ); - assert.deepEqual(m.api, { - id: "openai-compatible", - url: "https://gw.example.com/v1", - npm: "@ai-sdk/openai-compatible", - }); - }); - - it("routes an allowlisted auto id to anthropic (same rule as models)", () => { - const m = mapAutoComboToModelV2( - { id: "auto/coding", variant: "coding" }, - "omniroute", - "https://gw.example.com", - { allowAnthropic: true, anthropicModels: ["auto/coding"] } - ); - assert.deepEqual(m.api, { - id: "anthropic", - url: "https://gw.example.com", - npm: "@ai-sdk/anthropic", - }); - }); -}); diff --git a/@omniroute/opencode-plugin-v2/tests/shared-fingerprint.test.ts b/@omniroute/opencode-plugin-v2/tests/shared-fingerprint.test.ts index 0a5cc337c0e2..11d12b3545ce 100644 --- a/@omniroute/opencode-plugin-v2/tests/shared-fingerprint.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/shared-fingerprint.test.ts @@ -20,25 +20,34 @@ describe("catalogContentFingerprint", () => { const after = catalogContentFingerprint([{ id: "a2" }], combos); assert.notEqual(before, after); }); - it("returns a different hash when the auto-combos set changes", () => { + it("takes exactly the models and combos entries", () => { + assert.equal(catalogContentFingerprint.length, 2); const models = [{ id: "a" }]; const combos = [{ id: "combo-a" }]; - const before = catalogContentFingerprint(models, combos, [{ id: "auto" }]); - const after = catalogContentFingerprint(models, combos, [ - { id: "auto" }, - { id: "auto/coding" }, - ]); - assert.notEqual(before, after); + assert.equal( + catalogContentFingerprint(models, combos), + catalogContentFingerprint([...models], [...combos]) + ); }); }); describe("optionalTierFingerprint", () => { + it("takes providers, enrichment, then combos, with no legacy first entry", () => { + assert.equal(optionalTierFingerprint.length, 2); + const providers = [{ id: "c1", testStatus: "active", isActive: true }]; + const priced = (input: number) => + new Map([["example/model", { name: "Model One", pricing: { input, output: 1 } }]]); + const first = optionalTierFingerprint(providers, priced(3), [{ id: "combo-a" }]); + const moved = optionalTierFingerprint(providers, priced(99), [{ id: "combo-a" }]); + assert.notEqual(first, moved); + }); + it("moves on a pricing-only change, so stale prices reach the picker", () => { const priced = (input: number) => - new Map([["cc/m1", { name: "M1", pricing: { input, output: 1 } }]]); + new Map([["example/model", { name: "Model One", pricing: { input, output: 1 } }]]); assert.notEqual( - optionalTierFingerprint([], [], priced(3)), - optionalTierFingerprint([], [], priced(99)) + optionalTierFingerprint([], priced(3)), + optionalTierFingerprint([], priced(99)) ); }); @@ -46,8 +55,17 @@ describe("optionalTierFingerprint", () => { const one = [{ id: "combo-a", name: "A", models: [{ model: "m1" }] }]; const two = [{ id: "combo-a", name: "A", models: [{ model: "m1" }, { model: "m2" }] }]; assert.notEqual( - optionalTierFingerprint([], [], undefined, one), - optionalTierFingerprint([], [], undefined, two) + optionalTierFingerprint([], undefined, one), + optionalTierFingerprint([], undefined, two) + ); + }); + + it("tells providers apart from enrichment by position, not just arity", () => { + const providers = [{ id: "c1", testStatus: "active", isActive: true }]; + const enrichment = new Map([["example/model", { name: "Model One" }]]); + assert.notEqual( + optionalTierFingerprint(providers, undefined), + optionalTierFingerprint([], enrichment) ); }); @@ -55,8 +73,8 @@ describe("optionalTierFingerprint", () => { const active = [{ id: "c1", testStatus: "active", isActive: true }]; const quiet = [{ id: "c1", testStatus: "active", isActive: false }]; assert.notEqual( - optionalTierFingerprint([], active, undefined), - optionalTierFingerprint([], quiet, undefined) + optionalTierFingerprint(active, undefined), + optionalTierFingerprint(quiet, undefined) ); }); }); diff --git a/@omniroute/opencode-plugin-v2/tests/snapshot-stale-entries.test.ts b/@omniroute/opencode-plugin-v2/tests/snapshot-stale-entries.test.ts index eae7a4e80989..734091fde720 100644 --- a/@omniroute/opencode-plugin-v2/tests/snapshot-stale-entries.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/snapshot-stale-entries.test.ts @@ -53,7 +53,10 @@ function setupCtx(providerId: string): { function publishedOf(added: unknown[]): Map> { const published = new Map>(); - for (const entry of added as Array<{ info: { id: string }; models: Array> }>) { + for (const entry of added as Array<{ + info: { id: string }; + models: Array>; + }>) { for (const m of entry.models) published.set(entry.info.id + "/" + String(m.id), m); } return published; @@ -99,9 +102,6 @@ function downFetch(): typeof fetch { if (href.includes("/api/pricing") || href.includes("/api/free-tier")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({}) }; } - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -130,10 +130,9 @@ describe("plugin-v2 snapshot stale-entry filter", () => { { id: "stale-a", api: {} }, { id: "stale-b", api: { npm: "" } }, { id: "stale-c", api: { id: "openai-compatible", npm: "@ai-sdk/openai-compatible" } }, - { id: "good-1", context_length: 128000 }, + { id: "good-1", context_length: 128000, capabilities: { tool_calling: true } }, ], combos: [], - autoCombos: [], providers: [], writtenAt: Date.now(), }) @@ -155,7 +154,9 @@ describe("plugin-v2 snapshot stale-entry filter", () => { ); }); assert.ok( - warns.some((w) => w.includes("dropping 3 stale snapshot entries with an unusable api block")), + warns.some((w) => + w.includes("dropping 3 stale snapshot entries with an unusable api block") + ), `expected stale-drop warn, got: ${JSON.stringify(warns)}` ); } finally { @@ -178,14 +179,17 @@ describe("plugin-v2 snapshot stale-entry filter", () => { }) ); const origFetch = globalThis.fetch; + const { collectCatalog: collect } = await import("../src/catalog.js"); globalThis.fetch = (async (url: unknown) => { const href = String(url); + assert.equal( + new URL(href).pathname === "/api/combos/auto", + false, + "retired route must never be requested" + ); if (href.includes("/api/pricing") || href.includes("/api/free-tier")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({}) }; } - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -193,7 +197,7 @@ describe("plugin-v2 snapshot stale-entry filter", () => { ok: true, status: 200, statusText: "OK", - json: async () => ({ data: [{ id: "fresh-1" }] }), + json: async () => ({ data: [{ id: "fresh-1", capabilities: { tool_calling: true } }] }), }; }) as typeof fetch; try { @@ -210,6 +214,23 @@ describe("plugin-v2 snapshot stale-entry filter", () => { `unversioned snapshot must be ignored, got: ${JSON.stringify([...published.keys()])}` ); }); + const collected = await collect( + { + providerId, + baseURL: "https://gw.example.com", + apiKey: "k-snapfix", + timeoutMs: 1000, + modelCacheTtlMs: 300000, + usableOnly: false, + }, + { + models: async () => [{ id: "fresh-1" }], + combos: async () => [], + providers: async () => [], + enrichment: async () => new Map(), + } + ); + assert.deepEqual(collected.counts, { models: 1, combos: 0 }); } finally { globalThis.fetch = origFetch; disk.restore(); @@ -272,7 +293,10 @@ describe("plugin-v2 snapshot stale-entry filter", () => { // Present-but-unusable url: stale, for the same reason a missing npm is. for (const url of [undefined, "", " ", "/v1", "gw.example.com/v1", "ftp://gw/v1"]) { assert.equal( - isStaleSnapshotModel({ id: "a/b", api: { id: "x", npm, ...(url === undefined ? {} : { url }) } }), + isStaleSnapshotModel({ + id: "a/b", + api: { id: "x", npm, ...(url === undefined ? {} : { url }) }, + }), true, `expected ${JSON.stringify(url)} to be treated as stale` ); @@ -285,4 +309,39 @@ describe("plugin-v2 snapshot stale-entry filter", () => { // No api block at all stays publishable: it is synthesized at publish time. assert.equal(isStaleSnapshotModel({ id: "a/b" }), false); }); + + it("snapshot carrying a retired field still loads the valid entry", async () => { + const disk = isolateDisk(); + const providerId = "snapfix-retired-field"; + mkdirSync(join(disk.dir, "plugins"), { recursive: true }); + writeFileSync( + diskSnapshotPath(providerId), + JSON.stringify({ + v: 2, + identityFingerprint: fingerprint, + models: [{ id: "good-1" }], + combos: [], + autoCombos: [{ id: "auto" }], + providers: [], + writtenAt: Date.now(), + }) + ); + const origFetch = globalThis.fetch; + globalThis.fetch = downFetch(); + try { + const { added, ctx } = setupCtx(providerId); + const { warns } = await silenceConsole(async () => { + await (plugin as unknown as { setup: (ctx: unknown) => Promise }).setup(ctx); + const published = publishedOf(added); + assert.ok( + published.has(`${providerId}/good-1`), + `valid entry must load past the retired field, got: ${JSON.stringify([...published.keys()])}` + ); + }); + void warns; + } finally { + globalThis.fetch = origFetch; + disk.restore(); + } + }); }); diff --git a/@omniroute/opencode-plugin-v2/tests/staged-refresh.test.ts b/@omniroute/opencode-plugin-v2/tests/staged-refresh.test.ts index 63f1733bd171..e880b68f9b57 100644 --- a/@omniroute/opencode-plugin-v2/tests/staged-refresh.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/staged-refresh.test.ts @@ -3,11 +3,10 @@ import assert from "node:assert/strict"; import plugin from "../src/index.js"; // A catalog that only appears once the slowest optional source has answered is -// a catalog that never appears at all on a host that exits first: an -// unreachable `/api/combos/auto` kept models, combos and everything else -// unpublished until its own timeout fired. Models and combos must reach the -// draft as soon as they are known; the optional sources upgrade the snapshot -// when they land. +// a catalog that never appears at all on a host that exits first: a hanging +// optional endpoint kept models, combos and everything else unpublished until +// its own timeout fired. Models and combos must reach the draft as soon as +// they are known; the optional sources upgrade the snapshot when they land. describe("plugin-v2 staged refresh: optional sources never gate the publish", () => { let seq = 0; @@ -54,21 +53,21 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () function publishedOf(added: unknown[]): Map> { const published = new Map>(); - for (const entry of added as Array<{ info: { id: string }; models: Array> }>) { + for (const entry of added as Array<{ + info: { id: string }; + models: Array>; + }>) { for (const m of entry.models) published.set(entry.info.id + "/" + String(m.id), m); } return published; } /** - * `/api/combos/auto` never answers and never honours the abort signal — the - * shape of a gateway that accepts the connection and then goes quiet. + * A hanging optional endpoint never answers and never honours the abort + * signal — the shape of a gateway that accepts the connection and then + * goes quiet. */ - function stubFetch(opts: { - autoCombosHangs: boolean; - combosHangs?: boolean; - enrichmentDelayMs?: number; - }): typeof fetch { + function stubFetch(opts: { combosHangs?: boolean; enrichmentDelayMs?: number }): typeof fetch { return (async (url: unknown) => { const href = String(url); const ok = (body: unknown) => ({ @@ -77,10 +76,6 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () statusText: "OK", json: async () => body, }); - if (href.includes("/api/combos/auto")) { - if (opts.autoCombosHangs) return await new Promise(() => {}); - return ok({ combos: [] }); - } if (href.includes("/api/combos")) { // A gateway that accepts the connection and then goes quiet on the // combos endpoint: models must still publish without waiting for it. @@ -102,7 +97,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () } if (href.includes("/api/pricing")) return ok({}); if (href.includes("/api/free-tier/summary")) return ok({}); - return ok({ data: [{ id: "m1" }] }); + return ok({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }); }) as typeof fetch; } @@ -119,10 +114,10 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () } } - it("publishes models while an optional source is still hanging", async () => { + it("publishes models while the combos source is still hanging", async () => { const restoreDisk = await isolateDisk(); const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ autoCombosHangs: true }); + globalThis.fetch = stubFetch({ combosHangs: true }); const reloads = { count: 0 }; const { added, ctx } = setupCtx("staged-hang", reloads); try { @@ -148,7 +143,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () it("applies an optional source that lands after the publish, on the next transform", async () => { const restoreDisk = await isolateDisk(); const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ autoCombosHangs: false, enrichmentDelayMs: 120 }); + globalThis.fetch = stubFetch({ enrichmentDelayMs: 120 }); const reloads = { count: 0 }; const { added, ctx } = setupCtx("staged-late", reloads); try { @@ -179,7 +174,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () it("does not ask the host to reload when the overlay came back identical", async () => { const restoreDisk = await isolateDisk(); const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ autoCombosHangs: false }); + globalThis.fetch = stubFetch({}); const reloads = { count: 0 }; const { ctx } = setupCtx("staged-stable", reloads); try { @@ -214,7 +209,6 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () statusText: "OK", json: async () => body, }); - if (href.includes("/api/combos/auto")) return ok({ combos: [] }); if (href.includes("/api/combos")) return ok({ combos: [] }); if (href.includes("/api/pricing/models")) { enrichCalls += 1; @@ -232,7 +226,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () } if (href.includes("/api/pricing")) return ok({}); if (href.includes("/api/free-tier/summary")) return ok({}); - return ok({ data: [{ id: "m1" }] }); + return ok({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }); }) as unknown as typeof fetch; const reloads = { count: 0 }; const { added, ctx } = setupCtx("staged-ttl", reloads); @@ -262,7 +256,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () // models publish even when combos never answers. const restoreDisk = await isolateDisk(); const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ autoCombosHangs: false, combosHangs: true }); + globalThis.fetch = stubFetch({ combosHangs: true }); const reloads = { count: 0 }; const { added, ctx } = setupCtx("staged-combos-hang", reloads); try { @@ -294,6 +288,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () }); it("keeps names/pricing on the third transform when enrichment stays down", async () => { + const requested: string[] = []; // The old all-empty guard is gone by design (it also blocked genuine // removals); the per-source failure signal replaces it. A persistent 503 // on the overlay must keep last-known names on EVERY later transform, @@ -310,7 +305,6 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () statusText: "OK", json: async () => body, }); - if (href.includes("/api/combos/auto")) return ok({ combos: [] }); if (href.includes("/api/combos")) return ok({ combos: [] }); if (href.includes("/api/pricing/models")) { enrichCalls += 1; @@ -328,7 +322,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () } if (href.includes("/api/pricing")) return ok({}); if (href.includes("/api/free-tier/summary")) return ok({}); - return ok({ data: [{ id: "m1" }] }); + return ok({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }); }) as unknown as typeof fetch; const reloads = { count: 0 }; const { added, ctx } = setupCtx("staged-enrich-down", reloads); @@ -347,6 +341,10 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () "Omni - Model One", `a persistently failing overlay must not wipe names, got ${JSON.stringify(m1?.["name"])}` ); + assert.ok( + !requested.some((p) => p === "/api/combos/auto"), + `retired route must never be requested, got ${JSON.stringify(requested)}` + ); }); } finally { globalThis.fetch = origFetch; @@ -363,22 +361,23 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () const origFetch = globalThis.fetch; let modelCalls = 0; let down = false; + const requested: string[] = []; globalThis.fetch = (async (url: unknown) => { const href = String(url); + requested.push(new URL(href).pathname); const ok = (body: unknown) => ({ ok: true, status: 200, statusText: "OK", json: async () => body, }); - if (href.includes("/api/combos/auto")) return ok({ combos: [] }); if (href.includes("/api/combos")) return ok({ combos: [] }); if (href.includes("/api/pricing") || href.includes("/api/free-tier")) return ok({}); modelCalls += 1; if (down) { return { ok: false, status: 500, statusText: "Down", json: async () => ({}) }; } - return ok({ data: [{ id: "m1" }] }); + return ok({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }); }) as unknown as typeof fetch; const reloads = { count: 0 }; const { added: _addedU, ctx } = setupCtx("staged-unreachable", reloads); @@ -395,6 +394,10 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () await (plugin as unknown as { setup: (c: unknown) => Promise }).setup(ctx); const afterArming = modelCalls; assert.ok(afterArming >= 2, "the failing transform tries the network once"); + assert.ok( + !requested.some((p) => p === "/api/combos/auto"), + `retired route must never be requested, got ${JSON.stringify(requested)}` + ); }); } finally { globalThis.fetch = origFetch; @@ -408,7 +411,7 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () // callback is registered, and the throw is warned, not propagated. const restoreDisk = await isolateDisk(); const origFetch = globalThis.fetch; - globalThis.fetch = stubFetch({ autoCombosHangs: false }); + globalThis.fetch = stubFetch({}); const catalogCallbacks: Array<(draft: unknown) => Promise> = []; const ctx = { options: { baseURL: "https://gw.example.com", providerId: "staged-integ", apiKey: "k" }, @@ -419,10 +422,10 @@ describe("plugin-v2 staged refresh: optional sources never gate the publish", () }); return Promise.resolve({ dispose: async () => {} }); }, - }, - model: { - transform: () => Promise.resolve({ dispose: async () => {} }), - }, + }, + model: { + transform: () => Promise.resolve({ dispose: async () => {} }), + }, integration: { transform: () => { throw new Error("host says no"); diff --git a/@omniroute/opencode-plugin-v2/tests/timeouts-logger.test.ts b/@omniroute/opencode-plugin-v2/tests/timeouts-logger.test.ts index 45af21a67cc2..78a818e6ce59 100644 --- a/@omniroute/opencode-plugin-v2/tests/timeouts-logger.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/timeouts-logger.test.ts @@ -58,22 +58,20 @@ describe("plugin-v2 P2 parity: per-endpoint timeouts", () => { assert.deepEqual(resolveTimeouts(opts), { models: 10000, combos: 10000, - autoCombos: 5000, enrichment: 10000, }); }); - it("endpoint overrides win over the global timeout", () => { + it("a retired per-endpoint timeout key still parses (ignored)", () => { const opts = parsePluginOptions({ baseURL: BER, timeoutMs: 3000, - timeouts: { models: 1111, combos: 2222, autoCombos: 3333, enrichment: 4444 }, + timeouts: { models: 1111, autoCombos: 3333 }, }); assert.deepEqual(resolveTimeouts(opts), { models: 1111, - combos: 2222, - autoCombos: 3333, - enrichment: 4444, + combos: 3000, + enrichment: 3000, }); }); @@ -108,7 +106,7 @@ describe("plugin-v2 P2 parity: per-endpoint timeouts", () => { }, { fetcher: slowModels, combosFetcher: async () => [] } ); - assert.deepEqual(res, { models: 0, combos: 0, autoCombos: 0 }); + assert.deepEqual(res, { models: 0, combos: 0 }); } finally { console.warn = origWarn; } diff --git a/@omniroute/opencode-plugin-v2/tests/usable-only.test.ts b/@omniroute/opencode-plugin-v2/tests/usable-only.test.ts index 6fe93747ac57..abbabe3d94f7 100644 --- a/@omniroute/opencode-plugin-v2/tests/usable-only.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/usable-only.test.ts @@ -106,6 +106,7 @@ describe("catalog usableOnly gating", () => { ); assert.equal(providersCalls, 0); assert.equal(res.models, 3); + assert.deepEqual(res, { models: 3, combos: 0 }); assert.ok(models.has("omniroute/cc/keep-me")); assert.ok(models.has("omniroute/dead/drop-me")); }); @@ -171,6 +172,7 @@ describe("catalog usableOnly gating", () => { } ); assert.equal(res.combos, 1); + assert.deepEqual(res, { models: 1, combos: 1 }); assert.ok(models.has("omniroute/good")); assert.ok(!models.has("omniroute/bad")); }); @@ -181,9 +183,6 @@ describe("catalog usableOnly gating", () => { globalThis.fetch = (async (url: unknown) => { const href = String(url); seen.push(href); - if (href.includes("/api/combos/auto")) { - return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; - } if (href.includes("/api/combos")) { return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; } @@ -191,7 +190,7 @@ describe("catalog usableOnly gating", () => { ok: true, status: 200, statusText: "OK", - json: async () => ({ data: [{ id: "m1" }] }), + json: async () => ({ data: [{ id: "m1", capabilities: { tool_calling: true } }] }), }; }) as typeof fetch; const guard = silence(); @@ -209,10 +208,10 @@ describe("catalog usableOnly gating", () => { }); return Promise.resolve({ dispose: async () => {} }); }, - }, - model: { - transform: () => Promise.resolve({ dispose: async () => {} }), - }, + }, + model: { + transform: () => Promise.resolve({ dispose: async () => {} }), + }, integration: { transform: () => Promise.resolve({ dispose: async () => {} }) }, }); const { mkdtempSync } = await import("node:fs"); @@ -231,7 +230,75 @@ describe("catalog usableOnly gating", () => { !seen.some((href) => href.includes("/api/providers")), `no providers fetch expected, got: ${JSON.stringify(seen)}` ); + assert.ok( + !seen.some((href) => new URL(href).pathname === "/api/combos/auto"), + `retired route must never be requested, got: ${JSON.stringify(seen)}` + ); + } finally { + globalThis.fetch = origFetch; + guard.restore(); + } + }); + + it("toolsOnly default drops models without tool calling through setup", async () => { + const origFetch = globalThis.fetch; + globalThis.fetch = (async (url: unknown) => { + const href = String(url); + if (href.includes("/api/combos/auto")) { + return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; + } + if (href.includes("/api/combos")) { + return { ok: true, status: 200, statusText: "OK", json: async () => ({ combos: [] }) }; + } + return { + ok: true, + status: 200, + statusText: "OK", + json: async () => ({ + data: [{ id: "m-tools", capabilities: { tool_calling: true } }, { id: "m-plain" }], + }), + }; + }) as typeof fetch; + const guard = silence(); + const { mkdtempSync } = await import("node:fs"); + const { tmpdir } = await import("node:os"); + const { join } = await import("node:path"); + const prevDataDir = process.env.OPENCODE_DATA_DIR; + process.env.OPENCODE_DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-toolsdrop-")); + try { + const plugin = (await import("../src/index.js")).default as unknown as { + setup: (ctx: unknown) => Promise; + }; + const added: unknown[] = []; + const catalogCallbacks: Array<(draft: unknown) => Promise> = []; + await plugin.setup({ + options: { baseURL: "https://gw.example.com", providerId: "tools-drop", apiKey: "k" }, + provider: { + transform: (cb: (editor: { add: (input: unknown) => void }) => void) => { + catalogCallbacks.push(async () => { + cb({ + add: (input: unknown) => { + added.push(input); + }, + }); + }); + return Promise.resolve({ dispose: async () => {} }); + }, + }, + model: { + transform: () => Promise.resolve({ dispose: async () => {} }), + }, + integration: { transform: () => Promise.resolve({ dispose: async () => {} }) }, + }); + const { draft } = fakeDraft(); + await catalogCallbacks[0](draft as never); + const dumped = JSON.stringify(added); + assert.ok(added.length > 0, `expected a published catalog, got: ${dumped}`); + assert.ok(dumped.includes("m-tools"), `expected the tool-capable model, got: ${dumped}`); + assert.ok(!dumped.includes("m-plain"), `expected the plain model dropped, got: ${dumped}`); } finally { + if (prevDataDir === undefined) delete process.env.OPENCODE_DATA_DIR; + else process.env.OPENCODE_DATA_DIR = prevDataDir; globalThis.fetch = origFetch; guard.restore(); } diff --git a/@omniroute/opencode-plugin-v2/tests/warm-snapshot-identity.test.ts b/@omniroute/opencode-plugin-v2/tests/warm-snapshot-identity.test.ts index d50974baea66..65ea0e5502f6 100644 --- a/@omniroute/opencode-plugin-v2/tests/warm-snapshot-identity.test.ts +++ b/@omniroute/opencode-plugin-v2/tests/warm-snapshot-identity.test.ts @@ -1,6 +1,7 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; import plugin from "../src/index.js"; +import { collectCatalog } from "../src/catalog.js"; import { snapshotIdentityFingerprint, writeDiskSnapshot } from "../src/cache.js"; /** @@ -32,9 +33,8 @@ describe("warm snapshot is read under the credential actually in use", () => { await writeDiskSnapshot( "warmid", { - models: [{ id: "m-snap" }], + models: [{ id: "m-snap", capabilities: { tool_calling: true } }], combos: [], - autoCombos: [], providers: [], fetchedAt: Date.now(), } as never, @@ -65,13 +65,38 @@ describe("warm snapshot is read under the credential actually in use", () => { }; await (plugin as unknown as { setup: (c: unknown) => Promise }).setup(ctx); const published = new Map>(); - for (const entry of added as Array<{ info: { id: string }; models: Array> }>) { + for (const entry of added as Array<{ + info: { id: string }; + models: Array>; + }>) { for (const m of entry.models) published.set(`${entry.info.id}/${String(m.id)}`, m); } assert.ok( [...published.keys()].some((k) => k.endsWith("/m-snap")), `the snapshot must survive the credential switch, published: ${JSON.stringify([...published.keys()])}` ); + assert.equal( + [...published.keys()].some((k) => k.endsWith("/auto")), + false, + `no retired entry may be served from disk, got: ${JSON.stringify([...published.keys()])}` + ); + const collected = await collectCatalog( + { + providerId: "warmid", + baseURL, + apiKey: hostKey, + timeoutMs: 1000, + modelCacheTtlMs: 300000, + usableOnly: false, + }, + { + models: async () => [{ id: "m-snap" }], + combos: async () => [], + providers: async () => [], + enrichment: async () => new Map(), + } + ); + assert.deepEqual(collected.counts, { models: 1, combos: 0 }); } finally { globalThis.fetch = origFetch; console.warn = warn; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9bb775dc794..9e3eeb1661e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -410,7 +410,7 @@ Write unit tests in `tests/unit/` covering at minimum: - [ ] Documentation updated (if applicable) - [ ] No new CodeQL / Secret-Scanning alerts opened, or each one dismissed with technical justification referencing the relevant `docs/security/` doc - [ ] Routes that spawn child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) classified as `isLocalOnlyPath()` in `src/server/authz/routeGuard.ts` — see [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] No `Co-Authored-By` trailers in commit messages — commits must appear solely under the repository owner's Git identity (Hard Rule #16) +- [ ] No AI/bot `Co-authored-by` trailers in commit messages (Hard Rule #16) — human collaborators whose work is reused are credited with standard `Co-authored-by: Name ` trailers --- diff --git a/Dockerfile b/Dockerfile index 897c8dbe516e..0d906bb11747 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,7 +45,7 @@ RUN set -eux; \ npm install -g npm@latest; \ npm install --prefix /tmp/npm-cve-patch --no-audit --no-fund --ignore-scripts \ --install-strategy=nested \ - brace-expansion@5.0.9 ip-address@10.7.2 tar@7.5.22 undici@6.28.0; \ + brace-expansion@5.0.12 ip-address@10.7.3 tar@7.5.22 undici@6.29.0; \ for pkg in brace-expansion ip-address tar undici; do \ test -d "/usr/local/lib/node_modules/npm/node_modules/$pkg"; \ rm -rf "/usr/local/lib/node_modules/npm/node_modules/$pkg"; \ diff --git a/README.md b/README.md index 6a5728d8c593..6dde49b891b8 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,8 @@ No combo to create. Set your model to `auto` (or a variant) and OmniRoute builds auto/coding🧑‍💻 Quality-first weights for code generation auto/fast⚡ Lowest latency first auto/cheap💰 Cheapest per token first + auto/subscription🎟️ Only the subscription quota you already pay for — fails closed, never a billable fallback (guide) + auto/thrifty🪜 Plan quota first, then one rung at a time to the cheapest paid options — back on the plan after it resets (guide) auto/offline🔋 Most quota / rate-limit headroom first auto/smart🔭 Quality-first + 10% exploration to discover better models auto/lkgp📌 Explicit last-known-good-provider stickiness @@ -1007,6 +1009,20 @@ omniroute ``` > 💡 See `npm warn ERESOLVE` or peer-dep warnings? [They're harmless](docs/guides/TROUBLESHOOTING.md#npm-install-warnings-eresolve--peer--deprecated). +> **Using npm 11 or later?** npm may block package lifecycle scripts unless they are allowed. OmniRoute's `postinstall` (`node scripts/build/postinstall.mjs`) is required to prepare its native runtime files. Allow the packages named in npm's warning when installing globally. For the package set reported by OmniRoute 3.8.51: +> +> ```bash +> npm install -g --allow-scripts=omniroute,keytar,onnxruntime-node,@parcel/watcher,@swc/core,protobufjs,esbuild omniroute +> ``` +> +> To reuse this allowlist for future global installs, configure it once, then install normally: +> +> ```bash +> npm config set allow-scripts=omniroute,keytar,onnxruntime-node,@parcel/watcher,@swc/core,protobufjs,esbuild --location=user +> npm install -g omniroute +> ``` +> +> The dependency list can change between releases; if npm reports a different list, use the package names from that warning. Allowing a package permits its install scripts to run. > **Using Gemini Web or another web-cookie provider?** The npm package includes > Playwright but not its Chromium binary. See the > [Playwright Chromium setup](docs/guides/TROUBLESHOOTING.md#gemini-web-and-playwright-chromium) diff --git a/bin/cli/commands/provider-crud.mjs b/bin/cli/commands/provider-crud.mjs index f1de0b6c31a0..7d0adcede21b 100644 --- a/bin/cli/commands/provider-crud.mjs +++ b/bin/cli/commands/provider-crud.mjs @@ -102,6 +102,7 @@ export function buildProviderPayload(provider, opts = {}, credential) { }; if (!body.name) throw new Error("Provider name is required."); if (!isBlank(credential)) body.apiKey = String(credential); + if (opts.allowNoCredential === true) body.allowNoCredential = true; if (!isBlank(opts.defaultModel)) body.defaultModel = String(opts.defaultModel).trim(); if (!isBlank(opts.priority)) { const priority = Number(opts.priority); diff --git a/bin/cli/commands/serve.mjs b/bin/cli/commands/serve.mjs index 8be09c1a808f..e6704cf66ab7 100644 --- a/bin/cli/commands/serve.mjs +++ b/bin/cli/commands/serve.mjs @@ -246,7 +246,8 @@ export async function runServe(opts = {}) { // BEFORE any pid file is written or any child is spawned. Otherwise the // doomed child's EADDRINUSE arrives only after this process has rewritten // the pid files of the healthy instance that actually owns the port. - const busyPids = await resolveServeBusyPids(dashboardPort); + const serverHost = resolveServerHost(); + const busyPids = await resolveServeBusyPids(dashboardPort, { host: serverHost }); if (busyPids.length > 0) { reportPortInUse(dashboardPort, busyPids); process.exit(1); @@ -276,7 +277,7 @@ export async function runServe(opts = {}) { // #10492: HOSTNAME is standard shell state on Unix-like systems, not an // OmniRoute bind setting. The resolver only keeps its legacy meaning on // Windows; OMNIROUTE_SERVER_HOST is the cross-platform explicit setting. - HOSTNAME: resolveServerHost(), + HOSTNAME: serverHost, NODE_ENV: "production", // #5238: preserve a user-set NODE_OPTIONS (incl. their own // `--max-old-space-size=…`) instead of clobbering it with the calibrated @@ -347,15 +348,15 @@ export async function resolveServeBusyPids(port, deps = {}) { // findListeningPids() returning null means the discovery tool itself is // missing or unusable (Termux, slim containers, #14518) — fall back to a // bind probe so the guard still answers before spawning the doomed child. - let busyPids = await discover(port); + let busyPids = await discover(port, { host: deps.host }); if (busyPids === null) { // Discovery tool missing/unusable (#14518): the bind probe is the guard. - if (!(await probe(port))) busyPids = [null]; + if (!(await probe(port, { host: deps.host }))) busyPids = [null]; else busyPids = []; } else if (busyPids.length === 0) { // Discovery ran and saw nothing, but that window can race a starting // instance; a bind probe costs nothing and doubles as confirmation. - if (!(await probe(port))) busyPids = [null]; + if (!(await probe(port, { host: deps.host }))) busyPids = [null]; } return busyPids; } diff --git a/bin/cli/commands/update.mjs b/bin/cli/commands/update.mjs index 6c14db2bfc1f..d812cec3535e 100644 --- a/bin/cli/commands/update.mjs +++ b/bin/cli/commands/update.mjs @@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { t } from "../i18n.mjs"; -import { npmBin, npmExecOptions } from "../npm-exec.mjs"; +import { npmBin, npmExecInvocation } from "../npm-exec.mjs"; import { readPidFile, isPidRunning } from "../utils/pid.mjs"; const execFileAsync = promisify(execFile); @@ -30,15 +30,21 @@ export async function getCurrentVersion() { // `--prefer-online` forces npm to revalidate its HTTP cache against the registry. // Without it `npm view` can return a stale cached version (e.g. report 3.8.30 as // "latest" after 3.8.31 was published), so the updater told users on an old build -// they were already on the latest version (#4376). `execFn` is injectable for tests. -export async function getLatestVersion(execFn = execFileAsync) { +// they were already on the latest version (#4376). `execFn` and `platform` are +// injectable for tests. +export async function getLatestVersion(execFn = execFileAsync, platform = process.platform) { try { - // argv is all literals, so enabling the shell on win32 cannot splice a - // runtime value into the command line (Hard Rule #13). + // Every token is a literal, so joining them into a command line on win32 cannot + // splice a runtime value into it (Hard Rule #13) — npmExecInvocation enforces + // that. It also hands execFile an EMPTY args array there, so the shell is + // enabled for npm.cmd without tripping DEP0190 (#15327). const { stdout } = await execFn( - npmBin(), - ["view", "omniroute", "version", "--prefer-online"], - npmExecOptions(process.platform, { timeoutMs: 15000 }) + ...npmExecInvocation( + platform, + npmBin(platform), + ["view", "omniroute", "version", "--prefer-online"], + { timeoutMs: 15000 } + ) ); return stdout.trim(); } catch { @@ -154,9 +160,9 @@ export async function runUpdateCommand(opts = {}) { if (showChangelog) { try { const { stdout } = await execFileAsync( - npmBin(), - ["view", "omniroute", "changelog"], - npmExecOptions(process.platform, { timeoutMs: 15000 }) + ...npmExecInvocation(process.platform, npmBin(), ["view", "omniroute", "changelog"], { + timeoutMs: 15000, + }) ); if (stdout.trim()) { console.log(stdout.trim()); diff --git a/bin/cli/npm-exec.mjs b/bin/cli/npm-exec.mjs index b54dc3d5dab2..e75c57b97a00 100644 --- a/bin/cli/npm-exec.mjs +++ b/bin/cli/npm-exec.mjs @@ -11,6 +11,14 @@ // line, not `execFile`. Every argv element passed alongside these options must be // a literal — never a runtime value — or it must be validated first. Callers that // need to pass a user-supplied name have to guard it themselves. +// +// DEP0190 (Node ≥ 24): `normalizeSpawnArguments` also emits a deprecation warning +// whenever `shell` is set alongside a non-empty args array, because the shell only +// concatenates that argv onto the command instead of escaping it. So the argv-array +// form that #11335 needed is the one thing that cannot be used with the shell on +// win32 — the two fixes collided. `npmExecInvocation` resolves it by handing +// `execFile` the already-joined command line and an EMPTY args array, which is the +// same string Node would have built, minus the warning. /** The npm binary to spawn on this platform. */ export function npmBin(platform = process.platform) { @@ -32,3 +40,65 @@ export function npmExecOptions(platform = process.platform, options = {}) { if (platform !== "win32") return { ...base, shell: false }; return { ...base, shell: true, windowsHide: true }; } + +/** + * A token that cannot change how a shell splits a command line: no whitespace, no + * quote, no cmd.exe or POSIX metacharacter, no backslash. Covers every shape the + * CLI actually spawns npm with — subcommands (`view`, `changelog`), flags + * (`--prefer-online`), package names, scoped names (`@scope/pkg`), dist-tags and + * semver ranges. + */ +const LITERAL_NPM_TOKEN = /^[A-Za-z0-9@._/:=+-]+$/; + +/** + * The `execFile` arguments for an npm call on `platform`, ready to spread: + * `execFile(...npmExecInvocation(platform, npmBin(platform), args, options))`. + * + * Off Windows there is no shell, so the argv array is returned verbatim and the + * tokens are not inspected at all — the platform that does not need a shell gets no + * new restriction. On win32 the tokens are joined into a single command string and + * the args array comes back EMPTY, which is the only shape that both spawns + * `npm.cmd` (it needs the shell, #11335) and avoids DEP0190 (#15327): Node's + * `normalizeSpawnArguments` warns on `shell` + a non-empty args array and then + * builds `${file} ${args.join(" ")}` itself, so an empty array with a pre-joined + * file produces byte-identical input to the shell. + * + * Hard Rule #13 is enforced here rather than assumed. Joining a command line is + * only safe while every token is a literal, so a token outside + * {@link LITERAL_NPM_TOKEN} throws instead of being concatenated — the call fails + * loudly rather than letting the shell reinterpret a runtime value. A caller that + * genuinely has one (a path, a user-supplied package name) must validate it first + * and escape it with `escapeWindowsShellArg` from `./utils/winShellArgs.mjs`, or + * pass it through the environment. + * + * @param {NodeJS.Platform} platform + * @param {string} bin + * @param {string[]} [args] + * @param {{ timeoutMs?: number, stdio?: string }} [options] + * @returns {[string, string[], Record]} + */ +export function npmExecInvocation( + platform = process.platform, + bin = npmBin(platform), + args = [], + options = {} +) { + const execOptions = npmExecOptions(platform, options); + const tokens = [bin, ...(args ?? [])]; + + if (execOptions.shell !== true) { + return [tokens[0], tokens.slice(1), execOptions]; + } + + for (const token of tokens) { + if (!LITERAL_NPM_TOKEN.test(token)) { + throw new Error( + `npmExecInvocation: refusing to join the non-literal token ${JSON.stringify( + token + )} into a win32 shell command line (Hard Rule #13) — validate it first, then escape it with escapeWindowsShellArg() from bin/cli/utils/winShellArgs.mjs, or pass it through the environment.` + ); + } + } + + return [tokens.join(" "), [], execOptions]; +} diff --git a/bin/cli/utils/pid.mjs b/bin/cli/utils/pid.mjs index 36dc8019ff61..3c6792460b21 100644 --- a/bin/cli/utils/pid.mjs +++ b/bin/cli/utils/pid.mjs @@ -86,20 +86,12 @@ export async function findListeningPids(port, deps = {}) { const { stdout } = await exec("netstat", ["-ano"]); return parseNetstatListeningPids(stdout, port); } - const { stdout } = await exec("lsof", ["-nP", "-t", `-iTCP:${port}`, "-sTCP:LISTEN"]); - return stdout - .trim() - .split("\n") - .map((entry) => parseInt(entry, 10)) - .filter((entry) => Number.isFinite(entry) && entry > 0); + const { stdout } = await exec("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"]); + return parseLsofListeningPids(stdout, port, deps.host); } catch (err) { // POSIX lsof exits 1 with empty output when there are simply no matches. // That is the normal "port is free" result, not a discovery failure. - if ( - platform !== "win32" && - err?.code === 1 && - !String(err?.stdout ?? "").trim() - ) { + if (platform !== "win32" && err?.code === 1 && !String(err?.stdout ?? "").trim()) { return []; } // Tool missing (ENOENT) or genuinely unusable: "no listener" cannot be @@ -132,12 +124,62 @@ export async function probePortFree(port, deps = {}) { // port, so a server on 0.0.0.0 (the default), 127.0.0.1 or ::1 (localhost) is // only visible to a probe on that same address. A host without one of these // addresses gets EADDRNOTAVAIL, which reads as free. - for (const host of [undefined, "0.0.0.0", "127.0.0.1", "::1"]) { + const bindHost = deps.host; + const hosts = + bindHost && bindHost !== "0.0.0.0" && bindHost !== "::" && bindHost !== "*" + ? [bindHost] + : [undefined, "0.0.0.0", "127.0.0.1", "::1"]; + for (const host of hosts) { if (!(await bindable(host))) return false; } return true; } +function parseLsofListeningPids(stdout, port, host) { + const pids = []; + for (const line of String(stdout || "").split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("COMMAND")) continue; + const numeric = parseInt(trimmed, 10); + if (String(numeric) === trimmed && numeric > 0) { + pids.push(numeric); + continue; + } + const cols = trimmed.split(/\s+/); + const pid = parseInt(cols[1], 10); + if (!Number.isFinite(pid) || pid <= 0) continue; + const name = + cols.find((col) => { + const at = col.lastIndexOf(":"); + return at > 0 && col.slice(at + 1) === String(port); + }) || ""; + if (!name) continue; + const at = name.lastIndexOf(":"); + const listenHost = name.slice(0, at); + if (!listenerBlocksBind(listenHost, host)) continue; + pids.push(pid); + } + return pids; +} + +function listenerBlocksBind(listenHost, bindHost) { + if (!bindHost || bindHost === "0.0.0.0" || bindHost === "::" || bindHost === "*") return true; + const listen = normalizeListenHost(listenHost); + const bind = normalizeListenHost(bindHost); + if (!listen) return true; + if (listen === "*" || listen === "0.0.0.0" || listen === "::") return true; + if (bind === "127.0.0.1" || bind === "::1" || bind === "localhost") { + return listen === "127.0.0.1" || listen === "::1" || listen === "localhost"; + } + return listen === bind; +} + +function normalizeListenHost(host) { + const value = String(host || "").trim(); + if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1); + return value; +} + function parseNetstatListeningPids(stdout, port) { const portCol = `:${port}`; const pids = []; diff --git a/changelog.d/features/14554-plugin-capability-presets.md b/changelog.d/features/14554-plugin-capability-presets.md new file mode 100644 index 000000000000..5b468782109d --- /dev/null +++ b/changelog.d/features/14554-plugin-capability-presets.md @@ -0,0 +1 @@ +- **feat(plugin):** filter the published catalog by capability — `freeOnly`, `toolsOnly` (on by default, hides models that cannot call tools; set `toolsOnly: false` for the full catalog), `visionOnly` ([#14554](https://github.com/diegosouzapw/OmniRoute/pull/14554)) — thanks @maxmad64bis diff --git a/changelog.d/features/15141-core-config-beside.md b/changelog.d/features/15141-core-config-beside.md new file mode 100644 index 000000000000..f124a7c24b9c --- /dev/null +++ b/changelog.d/features/15141-core-config-beside.md @@ -0,0 +1 @@ +- **feat(proxy-subscription):** generate the local proxy-core config beside the adopted file, opt-in per subscription ([#15141](https://github.com/diegosouzapw/OmniRoute/pull/15141)) — thanks @maxmad64bis diff --git a/changelog.d/features/15142-core-config-verify.md b/changelog.d/features/15142-core-config-verify.md new file mode 100644 index 000000000000..7cc9e5650974 --- /dev/null +++ b/changelog.d/features/15142-core-config-verify.md @@ -0,0 +1 @@ +- **feat(proxy-subscription):** verify the generated core configuration with the native binary before replacing the adopted file ([#15142](https://github.com/diegosouzapw/OmniRoute/pull/15142)) — thanks @maxmad64bis diff --git a/changelog.d/features/15238-proxy-core-reload.md b/changelog.d/features/15238-proxy-core-reload.md new file mode 100644 index 000000000000..5edb669d4ca8 --- /dev/null +++ b/changelog.d/features/15238-proxy-core-reload.md @@ -0,0 +1 @@ +- **feat(proxy-subscription):** reload the local proxy core after a verified config replacement, paced at most once per 60 s per subscription ([#15238](https://github.com/diegosouzapw/OmniRoute/pull/15238)) — thanks @maxmad64bis diff --git a/changelog.d/features/15239-plugin-provider-filter.md b/changelog.d/features/15239-plugin-provider-filter.md new file mode 100644 index 000000000000..c6a55afe9c7d --- /dev/null +++ b/changelog.d/features/15239-plugin-provider-filter.md @@ -0,0 +1 @@ +- **feat(plugin):** filter the published catalog by provider (`providersAllow`, empty means full catalog) with an explicit empty-result guard ([#15239](https://github.com/diegosouzapw/OmniRoute/pull/15239)) — thanks @maxmad64bis diff --git a/changelog.d/features/15295-core-prune-rejected.md b/changelog.d/features/15295-core-prune-rejected.md new file mode 100644 index 000000000000..94b05ec0e09d --- /dev/null +++ b/changelog.d/features/15295-core-prune-rejected.md @@ -0,0 +1 @@ +- **feat(proxy-subscription):** prune nodes the core check rejects, then retry verification before keeping the previous file ([#15295](https://github.com/diegosouzapw/OmniRoute/pull/15295)) — thanks @maxmad64bis diff --git a/changelog.d/features/15314-operator-egress-observations.md b/changelog.d/features/15314-operator-egress-observations.md new file mode 100644 index 000000000000..0da5e164a99c --- /dev/null +++ b/changelog.d/features/15314-operator-egress-observations.md @@ -0,0 +1 @@ +- **feat(proxies):** accept operator-provided dated egress observations for pool members — an authenticated push route stores dated observed addresses per member behind a new opt-in flag, merged with the journal read for display and pool order with the freshest observation winning ([#15314](https://github.com/diegosouzapw/OmniRoute/pull/15314)) — thanks @maxmad64bis diff --git a/changelog.d/features/15318-upstream-http2-optout.md b/changelog.d/features/15318-upstream-http2-optout.md new file mode 100644 index 000000000000..11e6d51030cc --- /dev/null +++ b/changelog.d/features/15318-upstream-http2-optout.md @@ -0,0 +1 @@ +- **feat(proxy):** Add `OMNIROUTE_UPSTREAM_HTTP2_ENABLED=false` to force HTTP/1.1 on Undici direct, HTTP(S)/SOCKS proxy, relay, and retry dispatchers without disabling certificate verification. HTTP/2 remains enabled by default (#15318; closes #15313). diff --git a/changelog.d/features/15374-refusal-store-observability.md b/changelog.d/features/15374-refusal-store-observability.md new file mode 100644 index 000000000000..81031fdddd1b --- /dev/null +++ b/changelog.d/features/15374-refusal-store-observability.md @@ -0,0 +1 @@ +- **feat(proxies):** Log refusal-store writes and show transport proof in pool visibility ([#15374](https://github.com/diegosouzapw/OmniRoute/pull/15374)) — thanks @maxmad64bis diff --git a/changelog.d/features/15378-synced-vision-verdict-memo.md b/changelog.d/features/15378-synced-vision-verdict-memo.md new file mode 100644 index 000000000000..ee7d7143ba14 --- /dev/null +++ b/changelog.d/features/15378-synced-vision-verdict-memo.md @@ -0,0 +1 @@ +- **perf(db):** Serve repeated synced vision verdicts from a version-gated memo instead of re-reading the catalog ([#15378](https://github.com/diegosouzapw/OmniRoute/pull/15378)) — thanks @maxmad64bis diff --git a/changelog.d/features/15386-uniform-provider-regime.md b/changelog.d/features/15386-uniform-provider-regime.md new file mode 100644 index 000000000000..935631293119 --- /dev/null +++ b/changelog.d/features/15386-uniform-provider-regime.md @@ -0,0 +1 @@ +- **feat(dashboard):** flag a provider whose exits fail upstream together so the pool screen reads it as an upstream problem, and show upstream 4xx and 5xx as separate counts ([#15386](https://github.com/diegosouzapw/OmniRoute/pull/15386)) — thanks @maxmad64bis diff --git a/changelog.d/features/15462-translation-reasoning-effort.md b/changelog.d/features/15462-translation-reasoning-effort.md new file mode 100644 index 000000000000..94348be074db --- /dev/null +++ b/changelog.d/features/15462-translation-reasoning-effort.md @@ -0,0 +1 @@ +- **feat(i18n):** the translation backend forwards the optional reasoning control when set ([#15462](https://github.com/diegosouzapw/OmniRoute/pull/15462)) — thanks @maxmad64bis diff --git a/changelog.d/features/15469-token-refresh-breaker-settings.md b/changelog.d/features/15469-token-refresh-breaker-settings.md new file mode 100644 index 000000000000..56d4f4017fe3 --- /dev/null +++ b/changelog.d/features/15469-token-refresh-breaker-settings.md @@ -0,0 +1 @@ +- **feat(dashboard):** token refresh failures can pause per account instead of blocking the whole provider ([#15469](https://github.com/diegosouzapw/OmniRoute/pull/15469)) — thanks @maxmad64bis (with thanks to @xiaoyaner0201 for the scope proposal in #15444) diff --git a/changelog.d/features/15484-plugin-catalog-what-serves.md b/changelog.d/features/15484-plugin-catalog-what-serves.md new file mode 100644 index 000000000000..0f9e8d7e6301 --- /dev/null +++ b/changelog.d/features/15484-plugin-catalog-what-serves.md @@ -0,0 +1 @@ +- **feat(plugin):** trim the default view with per-provider fresh and showcase caps, tunable freshness window and new options ([#15484](https://github.com/diegosouzapw/OmniRoute/pull/15484)) — thanks @maxmad64bis diff --git a/changelog.d/features/15564-core-config-entries-skipped.md b/changelog.d/features/15564-core-config-entries-skipped.md new file mode 100644 index 000000000000..7da545efa756 --- /dev/null +++ b/changelog.d/features/15564-core-config-entries-skipped.md @@ -0,0 +1 @@ +- **feat(proxy-subscription):** warn with a translated count when generation skips entries instead of reporting not generated or staying silent ([#15564](https://github.com/diegosouzapw/OmniRoute/pull/15564)) — thanks @maxmad64bis diff --git a/changelog.d/features/15573-perplexity-integration-attribution.md b/changelog.d/features/15573-perplexity-integration-attribution.md new file mode 100644 index 000000000000..6676205be200 --- /dev/null +++ b/changelog.d/features/15573-perplexity-integration-attribution.md @@ -0,0 +1 @@ +- **feat(providers):** Perplexity API (Sonar, Agent) and Perplexity Search requests now send an `X-Pplx-Integration: omniroute` attribution header diff --git a/changelog.d/features/PRNUM-sync-ui-keys-locales-per-request.md b/changelog.d/features/PRNUM-sync-ui-keys-locales-per-request.md new file mode 100644 index 000000000000..7695cfc8757a --- /dev/null +++ b/changelog.d/features/PRNUM-sync-ui-keys-locales-per-request.md @@ -0,0 +1 @@ +- **feat(i18n):** translate up to N locales per request with `--locales-per-request` in `sync-ui-keys` ([#PRNUM](https://github.com/diegosouzapw/OmniRoute/pull/PRNUM)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/0000-audio-upstream-error-sanitization.md b/changelog.d/fixes/0000-audio-upstream-error-sanitization.md new file mode 100644 index 000000000000..9212af9794fb --- /dev/null +++ b/changelog.d/fixes/0000-audio-upstream-error-sanitization.md @@ -0,0 +1 @@ +- **fix(speech):** audio and transcription upstream failures no longer echo the provider's raw response body (#15159). Two byte-identical-in-spirit `upstreamErrorResponse` helpers — `open-sse/utils/audioResponse.ts` (18 call sites: every TTS provider in `audioSpeech.ts`, `awsPollyTts`, `fishAudioTts`, `geminiTts`) and a duplicate definition inside `open-sse/handlers/audioTranscription.ts` (17 call sites, re-exported to `openrouterTranscription.ts`) — interpolated `await res.text()` straight into `error.message`, so any stack frame, absolute path or credential a provider chose to return reached the client. That is what the repo's own rule already forbids: `open-sse/utils/upstreamErrorResponse.ts` states that an opaque body must not be echoed "even sanitized fragments", and `moderations.ts` / `ocr.ts` already comply. Both helpers now build through the canonical `errorResponse`, reusing a JSON upstream's own message only after `sanitizeErrorMessage` and falling back to a fixed `Upstream error ()` for opaque bodies. The duplicate is re-exported rather than redefined, so the next fix is a one-place change, and CORS headers are re-applied because the canonical builders do not emit them and these routes set CORS only on the `OPTIONS` preflight. The catalog did not report this; it is the largest remaining Hard Rule #12 surface. diff --git a/changelog.d/fixes/0000-command-code-claude-messages-endpoint.md b/changelog.d/fixes/0000-command-code-claude-messages-endpoint.md new file mode 100644 index 000000000000..b4a4e18d2c86 --- /dev/null +++ b/changelog.d/fixes/0000-command-code-claude-messages-endpoint.md @@ -0,0 +1 @@ +- **fix(providers):** Route Command Code Claude models through the Anthropic Messages endpoint. diff --git a/changelog.d/fixes/0000-compression-panel-partial-save.md b/changelog.d/fixes/0000-compression-panel-partial-save.md new file mode 100644 index 000000000000..de22f0a9a78b --- /dev/null +++ b/changelog.d/fixes/0000-compression-panel-partial-save.md @@ -0,0 +1 @@ +- **fix(dashboard):** the compression settings panel sends each save at once and rolls back only the fields of a failed save; the auto-trigger box commits once on Enter or blur and keeps rejected values as a draft, and the panel re-reads the settings after a save that threw so a value the server stored anyway is not lost diff --git a/changelog.d/fixes/0000-cursor-native-tool-bridge-shape-matching.md b/changelog.d/fixes/0000-cursor-native-tool-bridge-shape-matching.md new file mode 100644 index 000000000000..9893f3f56e17 --- /dev/null +++ b/changelog.d/fixes/0000-cursor-native-tool-bridge-shape-matching.md @@ -0,0 +1 @@ +- **fix(cursor):** Bridge native Cursor tool execution requests to client-declared tools by JSON-schema shape when tool names diverge from the static allowlists, and expand the name allowlists for common harness names (Claude Code, OpenCode, Cline, Kilo, Codex, Cursor). The bridge remains strictly fail-closed: schemas that cannot express the event or carry unpopulated required fields retain typed rejections, and ambiguous multiple-candidate matches fail closed to null ([#8168](https://github.com/diegosouzapw/OmniRoute/issues/8168)). diff --git a/changelog.d/fixes/0000-g03-error-helper-call-scoped-trust.md b/changelog.d/fixes/0000-g03-error-helper-call-scoped-trust.md new file mode 100644 index 000000000000..f0d3b1aebe25 --- /dev/null +++ b/changelog.d/fixes/0000-g03-error-helper-call-scoped-trust.md @@ -0,0 +1 @@ +- **fix(ci):** `check:error-helper` now trusts the _call_ instead of the _file_ (#15159 G-03). The gate used to skip an entire file as soon as it saw any import from a `utils/error` path — a file-scoped exemption applied to a call-scoped hazard — so one correct `import { sanitizeErrorMessage }` permanently excused every other sink in the file. That is how a live Hard Rule #12 leak shipped green: `deepseek-web.ts` imports the sanitizer for one call site while a second, file-local `errorResponse` builder forwarded raw upstream `errBody.msg` to the client. Trust is now resolved per line and per symbol: a line routing through a sanctioned builder/sanitizer is clean, a canonical builder counts only when the file actually imports it, and a file-local builder whose own body sanitizes is recognised too. Sanctioned builders are also recognised when called **multi-line**, and `src/lib/api/errorResponse.ts` (`createErrorResponse`, which sanitizes both exports and covers ~54 route handlers) now counts as a sanctioned module alongside `open-sse/utils/error.ts`. Two compliant shapes that the new rules over-reported are fixed as well: a raw error inside a **message-first** logger call (`log.error("BATCHES", "sweep failed", { error: err.message })`) is an audit row, not a leak. Removing the skip surfaced **21 pre-existing violations** (8 provider-auth routes leaking `error.message`, the unsanitized `toJsonErrorPayload` passthrough the earlier E-15 PR missed, and executors forwarding a raw caught error); they are frozen in `KNOWN_MISSING_ERROR_HELPER` with per-class justifications and self-clean via `assertNoStale`, so the gate now blocks new leaks while the backlog is worked down. diff --git a/changelog.d/fixes/0000-kie-transcription-error-sanitization.md b/changelog.d/fixes/0000-kie-transcription-error-sanitization.md new file mode 100644 index 000000000000..e966772f099d --- /dev/null +++ b/changelog.d/fixes/0000-kie-transcription-error-sanitization.md @@ -0,0 +1 @@ +- **fix(transcription):** the Kie.ai async transcription failure path no longer returns the raw upstream response body to the client (#15159 E-02). `handleKieAudioTranscription` built its `createTask` error body with a hand-rolled `Response.json({ error: { message: err.message } })` and no sanitizer — and the caught error _is_ the upstream body, because `kieExecutor.createTask` throws `new Error(await res.text())`. An upstream that returned a stack frame or a credential-shaped string therefore reached the browser verbatim. The branch now uses the canonical `errorResponse` builder, which the same file already imported and which the Kie poll path 40 lines below already used. The upstream status is still preserved, and the CORS headers are merged back on because that route only sets them on the `OPTIONS` preflight, never on the `POST` response. diff --git a/changelog.d/fixes/0000-m06-shared-mcp-internal-hop.md b/changelog.d/fixes/0000-m06-shared-mcp-internal-hop.md new file mode 100644 index 000000000000..65ca4632ae7f --- /dev/null +++ b/changelog.d/fixes/0000-m06-shared-mcp-internal-hop.md @@ -0,0 +1,9 @@ +- **fix(mcp):** one shared internal hop for the MCP server, read lazily (#15159 M-06). The server→server call from an MCP tool to OmniRoute's own API existed as **three** copies — `server.ts:199` (`omniRouteFetch`, lazy env read), `tools/advancedTools.ts:37` and `tools/pickFastestModel.ts:17` (both private `apiFetch`, env read frozen at **module load**) — and had already drifted three ways. All five consumers now import one hop from the new `open-sse/mcp-server/internalFetch.ts`, the module M-01's prescribed split already names. + + Three concrete defects are fixed rather than tidied: + + 1. **The two tool-side copies snapshotted `OMNIROUTE_API_KEY` and the base URL at import time**, so a key configured after the module was loaded — the normal shape for a test, and for any process that sets its environment late — was silently dropped and the hop went out unauthenticated instead of failing loudly. Every read is now per-call. + 2. **`catalog.ts` and `radarCatalog.ts` reached the hop through `import("./server.ts")`**, a workaround for the hop living inside the file that registers the tools. That dynamic import is what closed the `server.ts -> catalog.ts` and `server.ts -> radarCatalog.ts` edges: removing it takes the blocking `check-cycles` ratchet from **14 to 13** and the advisory `check-circular-deps` count from **195 to 193**. + 3. **The two copies hardcoded `AbortSignal.timeout(30000)`**, so the documented `OMNIROUTE_MCP_FETCH_TIMEOUT_MS` override silently did not apply to the advanced-tools and pick-fastest paths. + + One behavioural judgement, made explicitly: `omniroute_test_combo` probes every provider in a combo through `/v1/chat/completions`, so it waits on upstream providers, not local reads. Left on the shared default it would have dropped from 30s to the 10s management budget and aborted live probes — exactly the failure #9717 was filed for on `route_request`. It now requests `mcpFetchTimeoutSignal("upstream")` (60s), placing it alongside the three other upstream-budget call sites, and the test asserts the signal is scoped to that hop alone rather than applied blanket-wide. diff --git a/changelog.d/fixes/0000-model-import-degraded-catalog.md b/changelog.d/fixes/0000-model-import-degraded-catalog.md new file mode 100644 index 000000000000..bc07ef0038b0 --- /dev/null +++ b/changelog.d/fixes/0000-model-import-degraded-catalog.md @@ -0,0 +1 @@ +- **fix(dashboard):** the provider Import button no longer reports "All models already imported" when model discovery actually failed (#15159 B-03). `/api/providers//models?refresh=true` flags a degraded response with `source` + `warning` — `local_catalog` when remote discovery failed, `cache` or `github_catalog` with a warning — and the server already defines the condition as `isDegradedDiscovery`. The dashboard read `data.warning` but pushed it into the import modal's log lines while the headline `status` stayed `allModelsAlreadyImported` (or `noModelsFound`), so an operator whose provider had silently fallen back to a local catalog concluded there was nothing to import and the missing models stayed missing. The warning is now promoted to the headline status, with the success-flavoured line demoted to a log entry. The decision lives in a pure `classifyModelImport()` helper that reuses the **server's own** `isDegradedDiscovery` rather than a second local copy, so the two cannot drift — and `intentional: true` (reka / voyage-ai / t3-web, whose local catalog is the intended and only source, #5460 / #5465) is still correctly treated as healthy. diff --git a/changelog.d/fixes/0000-registry-union-dispatch-tagged-models.md b/changelog.d/fixes/0000-registry-union-dispatch-tagged-models.md new file mode 100644 index 000000000000..73a933edb871 --- /dev/null +++ b/changelog.d/fixes/0000-registry-union-dispatch-tagged-models.md @@ -0,0 +1 @@ +- **fix(db):** authoritative live catalogs no longer veto registry models that a provider explicitly marks as dispatch-discoverable (`registryDispatchUnion` + per-model `targetFormat`); z.ai opts in first, because its Anthropic-compat `/models` surface omits the coding-plan glm-5.3-flash family and a fresh authoritative snapshot then rejected dispatchable models with `model_not_in_catalog`. Unioned rows are listed alongside discovered ones and stamped `catalogOrigin: "registry"`. On an account not entitled to the model, the first dispatch fails upstream and model lockout re-gates that connection. diff --git a/changelog.d/fixes/0000-windows-compression-tests.md b/changelog.d/fixes/0000-windows-compression-tests.md new file mode 100644 index 000000000000..6281f3783db5 --- /dev/null +++ b/changelog.d/fixes/0000-windows-compression-tests.md @@ -0,0 +1 @@ +- **fix(tests):** Compression worker-pool and RTK TOML test assertions now cross-platform — worker specifier compares URL pathnames via `pathToFileURL`, and the 0600 permission check is POSIX-only. Both failures were Windows-only test bugs, not runtime regressions. diff --git a/changelog.d/fixes/10139-thinking-output-cap-provider-scope.md b/changelog.d/fixes/10139-thinking-output-cap-provider-scope.md new file mode 100644 index 000000000000..6fc97e467a1b --- /dev/null +++ b/changelog.d/fixes/10139-thinking-output-cap-provider-scope.md @@ -0,0 +1 @@ +- **fix(translator):** resolve the Claude thinking output cap with the routed provider so a provider-scoped-only `max_output_tokens` override is no longer invisible to `fitThinkingToMaxTokens()`, which previously let the synthesized `max_tokens` (caller room + thinking budget) go out unbounded and 400 upstream ([#10139](https://github.com/diegosouzapw/OmniRoute/issues/10139)) diff --git a/changelog.d/fixes/14805-passive-proxy-health.md b/changelog.d/fixes/14805-passive-proxy-health.md new file mode 100644 index 000000000000..0d877373162a --- /dev/null +++ b/changelog.d/fixes/14805-passive-proxy-health.md @@ -0,0 +1 @@ +- **fix(proxy-health):** skip sweep probes already proven by recent single-provider production successes (a recent failure still gets its live probe), opt-in via `PROXY_HEALTH_PASSIVE_SKIP` (default off: sweep probes every proxy as before) ([#14805](https://github.com/diegosouzapw/OmniRoute/pull/14805)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15003-pricing-freshness-ci.md b/changelog.d/fixes/15003-pricing-freshness-ci.md new file mode 100644 index 000000000000..5cd49b66bf8b --- /dev/null +++ b/changelog.d/fixes/15003-pricing-freshness-ci.md @@ -0,0 +1 @@ +- **ci(quality):** the release-green sweep now reports pricing data untouched for 90 days, so stale tier pricing shows up in the report instead of drifting silently ([#15003](https://github.com/diegosouzapw/OmniRoute/pull/15003)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15024-cursor-api-live-catalog-model-not-found.md b/changelog.d/fixes/15024-cursor-api-live-catalog-model-not-found.md new file mode 100644 index 000000000000..f2d7a51ea06a --- /dev/null +++ b/changelog.d/fixes/15024-cursor-api-live-catalog-model-not-found.md @@ -0,0 +1 @@ +- **fix(cursor):** `cursor-api` connections now discover Cursor's live `AvailableModels` catalog (using the exchanged session token) instead of persisting the static registry as their authoritative catalog, so newly released models such as `claude-opus-5-5-*` are routable; a Cursor `AI Model Not Found` reply without a usage-reset hint is reported as a 404 model-not-found error instead of a 429 rate limit ([#15024](https://github.com/diegosouzapw/OmniRoute/pull/15024)). diff --git a/changelog.d/fixes/15054-estimator-usage-calibration.md b/changelog.d/fixes/15054-estimator-usage-calibration.md new file mode 100644 index 000000000000..0eb2ef8b547c --- /dev/null +++ b/changelog.d/fixes/15054-estimator-usage-calibration.md @@ -0,0 +1 @@ +- **fix(context):** calibrate the chars/4 guard estimate with provider-reported prompt_tokens ([#15054](https://github.com/diegosouzapw/OmniRoute/pull/15054)) — thanks @skygunner diff --git a/changelog.d/fixes/15224-fallback-provenance-flag.md b/changelog.d/fixes/15224-fallback-provenance-flag.md new file mode 100644 index 000000000000..78fd27529bd4 --- /dev/null +++ b/changelog.d/fixes/15224-fallback-provenance-flag.md @@ -0,0 +1 @@ +- **fix(routing):** unknown models now carry a provenance flag on pricing and latency bootstrap fallbacks, so guesses are traceable instead of silent ([#15224](https://github.com/diegosouzapw/OmniRoute/pull/15224)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15236-migration-duplicate-column.md b/changelog.d/fixes/15236-migration-duplicate-column.md new file mode 100644 index 000000000000..92d60797a35a --- /dev/null +++ b/changelog.d/fixes/15236-migration-duplicate-column.md @@ -0,0 +1 @@ +- **fix(db):** a migration file that hits "duplicate column name" now gets its missing columns instead of being marked applied with none of them added, which left routes failing with "no such column" ([#15236](https://github.com/diegosouzapw/OmniRoute/pull/15236)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15248-proxy-health-slow-abandoned.md b/changelog.d/fixes/15248-proxy-health-slow-abandoned.md new file mode 100644 index 000000000000..8c35890e0d15 --- /dev/null +++ b/changelog.d/fixes/15248-proxy-health-slow-abandoned.md @@ -0,0 +1 @@ +- **fix(dashboard):** proxy health now reports headers-wait abandonments and client aborts as separate counters instead of transport failures ([#15248](https://github.com/diegosouzapw/OmniRoute/pull/15248)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15298-file-size-test-base-loc.md b/changelog.d/fixes/15298-file-size-test-base-loc.md new file mode 100644 index 000000000000..27c41f9e2d48 --- /dev/null +++ b/changelog.d/fixes/15298-file-size-test-base-loc.md @@ -0,0 +1 @@ +- **fix(ci):** `check:file-size` PR mode judges test files against their base line counts too, so an innocent PR passes when a test file already drifted past its ceiling on the base ([#15298](https://github.com/diegosouzapw/OmniRoute/pull/15298)) — thanks @woodsonl diff --git a/changelog.d/fixes/15299-opencode-go-mimo-xhigh-effort-clamp.md b/changelog.d/fixes/15299-opencode-go-mimo-xhigh-effort-clamp.md new file mode 100644 index 000000000000..2d97882dcd65 --- /dev/null +++ b/changelog.d/fixes/15299-opencode-go-mimo-xhigh-effort-clamp.md @@ -0,0 +1 @@ +- **fix(sse):** clamp Xiaomi MiMo V2.5/V2.6 `reasoning_effort` on OpenCode gateways so `xhigh`/`max`/`ultra` become `high` and `minimal` becomes `low`, instead of `opencode-go/mimo-v2.6-pro` and `mimo-v2.6-flash` answering HTTP 400 "Invalid request parameters" diff --git a/changelog.d/fixes/15316-refusal-store-shared-state.md b/changelog.d/fixes/15316-refusal-store-shared-state.md new file mode 100644 index 000000000000..fe53f00c1771 --- /dev/null +++ b/changelog.d/fixes/15316-refusal-store-shared-state.md @@ -0,0 +1 @@ +- **fix(proxies):** Share the set-aside store across duplicated server module copies, so a `connection reset` or `429` recorded through one copy keeps that egress set aside for every reader ([#15316](https://github.com/diegosouzapw/OmniRoute/pull/15316)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15319-chronological-tool-ids.md b/changelog.d/fixes/15319-chronological-tool-ids.md new file mode 100644 index 000000000000..6dfaa1821b25 --- /dev/null +++ b/changelog.d/fixes/15319-chronological-tool-ids.md @@ -0,0 +1 @@ +- **fix(translator):** Give chronologically reused tool calls unique paired Gemini wire IDs after resolving thought signatures with the original IDs. Reject overlapping duplicate calls locally instead of guessing result pairing; native Responses passthrough and client history stay unchanged (#15319; closes #15312). diff --git a/changelog.d/fixes/15331-standalone-sigterm-cleanup.md b/changelog.d/fixes/15331-standalone-sigterm-cleanup.md new file mode 100644 index 000000000000..f71fbc224d0e --- /dev/null +++ b/changelog.d/fixes/15331-standalone-sigterm-cleanup.md @@ -0,0 +1 @@ +- **fix(api):** the standalone server (Docker `dev/run-standalone.mjs`) now owns SIGTERM/SIGINT, so graceful-shutdown cleanup (spend flush, call-log flush, DB checkpoint) runs before exit instead of being preempted by Next's own handler calling `process.exit(143)` ([#15331](https://github.com/diegosouzapw/OmniRoute/pull/15331)) diff --git a/changelog.d/fixes/15332-next-upgrade-relay.md b/changelog.d/fixes/15332-next-upgrade-relay.md new file mode 100644 index 000000000000..987a0a9103d3 --- /dev/null +++ b/changelog.d/fixes/15332-next-upgrade-relay.md @@ -0,0 +1 @@ +- **fix(api):** `npm run dev` / `npm start` no longer cut claimed WebSocket upgrades (Responses WS, `/v1/ws` bridge): Next's lazily self-attached upgrade listener now sits on a relay and only receives upgrades nobody else claimed (HMR) ([#15332](https://github.com/diegosouzapw/OmniRoute/pull/15332)) diff --git a/changelog.d/fixes/15339-relay-timeout-crash-guard.md b/changelog.d/fixes/15339-relay-timeout-crash-guard.md new file mode 100644 index 000000000000..ed2fb647e325 --- /dev/null +++ b/changelog.d/fixes/15339-relay-timeout-crash-guard.md @@ -0,0 +1 @@ +- **fix(resilience):** a pooled relay attempt that exceeds `RELAY_FETCH_TIMEOUT_MS` no longer escapes as an `unhandledRejection` and kills the server (exit code 7) — the process crash guard now classifies `RELAY_TIMEOUT`/`relay_timeout` as a recoverable upstream timeout, like the direct-fetch `DIRECT_RESPONSE_START_TIMEOUT` case ([#PR](https://github.com/diegosouzapw/OmniRoute/pull/PR)) diff --git a/changelog.d/fixes/15346-compression-settings-load-failure.md b/changelog.d/fixes/15346-compression-settings-load-failure.md new file mode 100644 index 000000000000..0cacb00b20b1 --- /dev/null +++ b/changelog.d/fixes/15346-compression-settings-load-failure.md @@ -0,0 +1 @@ +- **fix(compression):** the compression panel and the compression settings tab show a Retry when `GET /api/settings/compression` fails, and keep their controls hidden until a load succeeds, so a failed load can no longer save defaults over the stored output styles, context budget or engines ([#15346](https://github.com/diegosouzapw/OmniRoute/pull/15346)) — thanks @woodsonl diff --git a/changelog.d/fixes/15353-combos-auto-resolved-capabilities.md b/changelog.d/fixes/15353-combos-auto-resolved-capabilities.md new file mode 100644 index 000000000000..81ef0ca83ad8 --- /dev/null +++ b/changelog.d/fixes/15353-combos-auto-resolved-capabilities.md @@ -0,0 +1 @@ +- **fix(api):** `GET /api/combos/auto` now resolves candidate capabilities once per request instead of once per candidate for every listed variant, so listing auto combos no longer holds the event loop on large pools ([#15353](https://github.com/diegosouzapw/OmniRoute/pull/15353)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15354-drop-unused-turn-node-indexes.md b/changelog.d/fixes/15354-drop-unused-turn-node-indexes.md new file mode 100644 index 000000000000..921b349a3dbe --- /dev/null +++ b/changelog.d/fixes/15354-drop-unused-turn-node-indexes.md @@ -0,0 +1 @@ +- **perf(db):** drop two conversation turn node indexes no query reads ([#15354](https://github.com/diegosouzapw/OmniRoute/pull/15354)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15358-unreadable-quota-not-full.md b/changelog.d/fixes/15358-unreadable-quota-not-full.md new file mode 100644 index 000000000000..f1bb8574391b --- /dev/null +++ b/changelog.d/fixes/15358-unreadable-quota-not-full.md @@ -0,0 +1 @@ +- **fix(auto-combo):** a provider whose quota fetcher returns nothing readable (failed fetch, missing credentials, message-only or malformed snapshot) no longer scores as a full quota in auto-combo; it ranks strictly below any real reading without being blocked or evicted, and unlimited plans now report `unlimited: true` so they still score as full ([#15358](https://github.com/diegosouzapw/OmniRoute/pull/15358)) — thanks @shannonlowder diff --git a/changelog.d/fixes/15362-combos-auto-log-skipped-variants.md b/changelog.d/fixes/15362-combos-auto-log-skipped-variants.md new file mode 100644 index 000000000000..2b65cd510355 --- /dev/null +++ b/changelog.d/fixes/15362-combos-auto-log-skipped-variants.md @@ -0,0 +1 @@ +- **fix(api):** log the auto-combo variants GET /api/combos/auto skips instead of failing silently ([#15362](https://github.com/diegosouzapw/OmniRoute/pull/15362)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15375-opencode-rotation-logs-name-egress.md b/changelog.d/fixes/15375-opencode-rotation-logs-name-egress.md new file mode 100644 index 000000000000..b0a6a677a6e4 --- /dev/null +++ b/changelog.d/fixes/15375-opencode-rotation-logs-name-egress.md @@ -0,0 +1 @@ +- **fix(sse):** name the egress proxy on every opencode rotation log line ([#15375](https://github.com/diegosouzapw/OmniRoute/pull/15375)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15383-region-refusal-set-aside.md b/changelog.d/fixes/15383-region-refusal-set-aside.md new file mode 100644 index 000000000000..9cfb56c444e0 --- /dev/null +++ b/changelog.d/fixes/15383-region-refusal-set-aside.md @@ -0,0 +1 @@ +- **fix(proxies):** a member refused for the request region (403/451 with a region signal) is set aside briefly instead of retried first by every following request ([#15383](https://github.com/diegosouzapw/OmniRoute/pull/15383)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15385-freetheai-domain-move.md b/changelog.d/fixes/15385-freetheai-domain-move.md new file mode 100644 index 000000000000..1fd0df6d7f37 --- /dev/null +++ b/changelog.d/fixes/15385-freetheai-domain-move.md @@ -0,0 +1 @@ +- **fix(providers):** the `freetheai` gateway now points at `freetheai.org` — `api.freetheai.xyz` is retired (answers HTTP 530), signup moved off Discord, and model ids are namespaced `fta//` and must be sent in full ([#15385](https://github.com/diegosouzapw/OmniRoute/issues/15385)) — thanks Vibhek Soni (FreeTheAI) diff --git a/changelog.d/fixes/15390-admission-rejection-call-log.md b/changelog.d/fixes/15390-admission-rejection-call-log.md new file mode 100644 index 000000000000..f4cab1778c91 --- /dev/null +++ b/changelog.d/fixes/15390-admission-rejection-call-log.md @@ -0,0 +1 @@ +- **fix(api):** log admission rejections on the chat and responses routes to the request journal with their status and reason ([#15390](https://github.com/diegosouzapw/OmniRoute/pull/15390)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15392-plugin-drop-retired-route.md b/changelog.d/fixes/15392-plugin-drop-retired-route.md new file mode 100644 index 000000000000..b96077f4582e --- /dev/null +++ b/changelog.d/fixes/15392-plugin-drop-retired-route.md @@ -0,0 +1 @@ +- **fix(plugin):** stop requesting the virtual-entries route and publish the gateway `/v1/models` entries as served ([#15392](https://github.com/diegosouzapw/OmniRoute/pull/15392)) — thanks @maxmad64bis, with thanks to @bilalsafdardev for the diagnosis in #14889 diff --git a/changelog.d/fixes/15395-share-request-context-symbol-for.md b/changelog.d/fixes/15395-share-request-context-symbol-for.md new file mode 100644 index 000000000000..7e4f52963b27 --- /dev/null +++ b/changelog.d/fixes/15395-share-request-context-symbol-for.md @@ -0,0 +1 @@ +- **fix(shared):** share request context stores across duplicated modules ([#15395](https://github.com/diegosouzapw/OmniRoute/pull/15395)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15396-unify-free-provider-lists.md b/changelog.d/fixes/15396-unify-free-provider-lists.md new file mode 100644 index 000000000000..18ae8d01b062 --- /dev/null +++ b/changelog.d/fixes/15396-unify-free-provider-lists.md @@ -0,0 +1 @@ +- **fix(rankings):** free provider ranking now follows the documented free-model catalog ([#15396](https://github.com/diegosouzapw/OmniRoute/pull/15396)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15399-apostrophe-placeholder-parity.md b/changelog.d/fixes/15399-apostrophe-placeholder-parity.md new file mode 100644 index 000000000000..f8f9a66f40be --- /dev/null +++ b/changelog.d/fixes/15399-apostrophe-placeholder-parity.md @@ -0,0 +1 @@ +- **fix(i18n):** keep quoted placeholders working in Maltese and Korean messages ([#15399](https://github.com/diegosouzapw/OmniRoute/pull/15399)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15402-noauth-refusal-pause-retryable.md b/changelog.d/fixes/15402-noauth-refusal-pause-retryable.md new file mode 100644 index 000000000000..6e24cd85246e --- /dev/null +++ b/changelog.d/fixes/15402-noauth-refusal-pause-retryable.md @@ -0,0 +1 @@ +- **fix(auth):** answer a paused no-auth provider with a retryable cooldown ([#15402](https://github.com/diegosouzapw/OmniRoute/pull/15402)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15413-kiro-discovery-context-window.md b/changelog.d/fixes/15413-kiro-discovery-context-window.md new file mode 100644 index 000000000000..2b94f91963aa --- /dev/null +++ b/changelog.d/fixes/15413-kiro-discovery-context-window.md @@ -0,0 +1 @@ +- **fix(kiro):** Kiro model discovery no longer stamps a 200k context window when `tokenLimits.maxInputTokens` is missing, so the reconciler cannot pin a fake `auto:discovery` override over the registry / models.dev window ([#15413](https://github.com/diegosouzapw/OmniRoute/pull/15413)) diff --git a/changelog.d/fixes/15432-lkgp-intra-request-ttl.md b/changelog.d/fixes/15432-lkgp-intra-request-ttl.md new file mode 100644 index 000000000000..19ca1e92362d --- /dev/null +++ b/changelog.d/fixes/15432-lkgp-intra-request-ttl.md @@ -0,0 +1 @@ +- **fix(combo):** skip the redundant second LKGP write when a combo success already pins the combo-level key ([#15432](https://github.com/diegosouzapw/OmniRoute/pull/15432)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15433-content-stall-thinking-liveness.md b/changelog.d/fixes/15433-content-stall-thinking-liveness.md new file mode 100644 index 000000000000..62234b9d03c1 --- /dev/null +++ b/changelog.d/fixes/15433-content-stall-thinking-liveness.md @@ -0,0 +1 @@ +- **fix(sse):** keep thinking-only streams alive past the content-stall budget ([#15433](https://github.com/diegosouzapw/OmniRoute/pull/15433)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15434-trae-upstream-status.md b/changelog.d/fixes/15434-trae-upstream-status.md new file mode 100644 index 000000000000..1ba53633180e --- /dev/null +++ b/changelog.d/fixes/15434-trae-upstream-status.md @@ -0,0 +1 @@ +- **fix(sse):** record the received Trae upstream status on proxy failures so health stats count them as upstream instead of transport failures ([#15434](https://github.com/diegosouzapw/OmniRoute/pull/15434)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15436-openrouter-credits-exhausted-free-access.md b/changelog.d/fixes/15436-openrouter-credits-exhausted-free-access.md new file mode 100644 index 000000000000..2d630cd0d798 --- /dev/null +++ b/changelog.d/fixes/15436-openrouter-credits-exhausted-free-access.md @@ -0,0 +1 @@ +- **fix(auth):** serve catalogued free-access models only on credits-exhausted OpenRouter connections ([#15436](https://github.com/diegosouzapw/OmniRoute/pull/15436)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15443-opencode-pause-per-model.md b/changelog.d/fixes/15443-opencode-pause-per-model.md new file mode 100644 index 000000000000..358d3e665488 --- /dev/null +++ b/changelog.d/fixes/15443-opencode-pause-per-model.md @@ -0,0 +1 @@ +- **fix(auth):** limit the no-auth free-tier pause to the refused model so sibling models stay eligible ([#15443](https://github.com/diegosouzapw/OmniRoute/pull/15443)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15449-codex-breaker-scope.md b/changelog.d/fixes/15449-codex-breaker-scope.md new file mode 100644 index 000000000000..604c84b45123 --- /dev/null +++ b/changelog.d/fixes/15449-codex-breaker-scope.md @@ -0,0 +1 @@ +- **fix(sse):** a dead Codex refresh token no longer trips the provider circuit breaker, so healthy accounts keep serving while the dead one asks for re-authentication ([#15449](https://github.com/diegosouzapw/OmniRoute/pull/15449)) — thanks @maxmad64bis (with thanks to @xiaoyaner0201 for the diagnosis and repro in #15444) diff --git a/changelog.d/fixes/15450-datadir-legacy-before-xdg.md b/changelog.d/fixes/15450-datadir-legacy-before-xdg.md new file mode 100644 index 000000000000..483c201b67db --- /dev/null +++ b/changelog.d/fixes/15450-datadir-legacy-before-xdg.md @@ -0,0 +1 @@ +- **fix(cli):** the server bootstrap (`scripts/build/bootstrap-env.mjs`), `npm run env:sync` and the desktop app now resolve the default `DATA_DIR` exactly like the runtime and the CLI: an existing `~/.omniroute` wins over `$XDG_CONFIG_HOME/omniroute`. With `XDG_CONFIG_HOME` exported, the first start persisted `JWT_SECRET` / `STORAGE_ENCRYPTION_KEY` / `API_KEY_SECRET` to `~/.config/omniroute/server.env` while the database and the CLI used `~/.omniroute`, so a provider credential saved on one side could not be decrypted on the other. ([#15450](https://github.com/diegosouzapw/OmniRoute/pull/15450)) diff --git a/changelog.d/fixes/15451-insufficient-quota-replay.md b/changelog.d/fixes/15451-insufficient-quota-replay.md new file mode 100644 index 000000000000..a8fbb82be9d8 --- /dev/null +++ b/changelog.d/fixes/15451-insufficient-quota-replay.md @@ -0,0 +1 @@ +- **fix(open-sse):** replay a `403 or 451` title refusal carrying `insufficient_quota` once in the other tool shape, so a wrong shape no longer surfaces as exhausted credits ([#15451](https://github.com/diegosouzapw/OmniRoute/pull/15451)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15455-responses-stall-measure.md b/changelog.d/fixes/15455-responses-stall-measure.md new file mode 100644 index 000000000000..5027524e1263 --- /dev/null +++ b/changelog.d/fixes/15455-responses-stall-measure.md @@ -0,0 +1 @@ +- **fix(opencode):** count silent streamed replies that would have tripped the 15 s first-byte window while the guard stays off ([#15455](https://github.com/diegosouzapw/OmniRoute/pull/15455)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15458-priority-order-vs-stickiness.md b/changelog.d/fixes/15458-priority-order-vs-stickiness.md new file mode 100644 index 000000000000..ab7ddf37b084 --- /dev/null +++ b/changelog.d/fixes/15458-priority-order-vs-stickiness.md @@ -0,0 +1 @@ +- **fix(combo):** keep declared priority order ahead of session stickiness ([#15458](https://github.com/diegosouzapw/OmniRoute/pull/15458)) — thanks @skygunner diff --git a/changelog.d/fixes/15461-auto-pool-custom-model-type.md b/changelog.d/fixes/15461-auto-pool-custom-model-type.md new file mode 100644 index 000000000000..4297944be727 --- /dev/null +++ b/changelog.d/fixes/15461-auto-pool-custom-model-type.md @@ -0,0 +1 @@ +- **fix(combo):** the auto combo pool check types the custom-model row so `typecheck:noimplicit:core` passes ([#15461](https://github.com/diegosouzapw/OmniRoute/pull/15461) — thanks @HouMinXi) diff --git a/changelog.d/fixes/15463-executor-upstream-status-sink.md b/changelog.d/fixes/15463-executor-upstream-status-sink.md new file mode 100644 index 000000000000..bb2152be751a --- /dev/null +++ b/changelog.d/fixes/15463-executor-upstream-status-sink.md @@ -0,0 +1 @@ +- **fix(sse):** record the received Cursor upstream status on proxy failures so health stats count them as upstream instead of transport failures ([#15463](https://github.com/diegosouzapw/OmniRoute/pull/15463)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15472-serve-port-guard-bind-address.md b/changelog.d/fixes/15472-serve-port-guard-bind-address.md new file mode 100644 index 000000000000..39f9d5a5a810 --- /dev/null +++ b/changelog.d/fixes/15472-serve-port-guard-bind-address.md @@ -0,0 +1 @@ +- **fix(cli):** the serve port guard ignores a listener on another address when the server binds a specific host ([#15472](https://github.com/diegosouzapw/OmniRoute/pull/15472) — thanks @HouMinXi) diff --git a/changelog.d/fixes/15474-opencode-endpoint-unavailable-cooldown.md b/changelog.d/fixes/15474-opencode-endpoint-unavailable-cooldown.md new file mode 100644 index 000000000000..49f0feb72573 --- /dev/null +++ b/changelog.d/fixes/15474-opencode-endpoint-unavailable-cooldown.md @@ -0,0 +1 @@ +- **fix(providers):** a 400 or 429 reading "endpoint is unavailable" now pauses only that model for 5 minutes ([#15474](https://github.com/diegosouzapw/OmniRoute/pull/15474)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15475-free-tier-429-not-tool-refusal.md b/changelog.d/fixes/15475-free-tier-429-not-tool-refusal.md new file mode 100644 index 000000000000..f6789f8a8f65 --- /dev/null +++ b/changelog.d/fixes/15475-free-tier-429-not-tool-refusal.md @@ -0,0 +1 @@ +- **fix(opencode):** only a 403 or 451 naming the refused model clears borrowed tools; a 429 leaves them alone ([#15475](https://github.com/diegosouzapw/OmniRoute/pull/15475)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15482-admission-rejection-withchat.md b/changelog.d/fixes/15482-admission-rejection-withchat.md new file mode 100644 index 000000000000..2f60cfb648ac --- /dev/null +++ b/changelog.d/fixes/15482-admission-rejection-withchat.md @@ -0,0 +1 @@ +- **fix(api):** log wrapper admission rejections to the request journal with their status and reason ([#15482](https://github.com/diegosouzapw/OmniRoute/pull/15482)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15483-free-tier-pauses-visibles.md b/changelog.d/fixes/15483-free-tier-pauses-visibles.md new file mode 100644 index 000000000000..284bea06e195 --- /dev/null +++ b/changelog.d/fixes/15483-free-tier-pauses-visibles.md @@ -0,0 +1 @@ +- **fix(noauth):** paused free-tier models now show a pause note on the account card instead of unexplained failures ([#15483](https://github.com/diegosouzapw/OmniRoute/pull/15483)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/15485-preserve-provider-priority.md b/changelog.d/fixes/15485-preserve-provider-priority.md new file mode 100644 index 000000000000..0b2578692665 --- /dev/null +++ b/changelog.d/fixes/15485-preserve-provider-priority.md @@ -0,0 +1 @@ +- **fix(db):** Preserve explicitly saved provider-connection priorities instead of silently renumbering them on edit; keep the internal move-to-top operation and return its persisted rank ([#15485](https://github.com/diegosouzapw/OmniRoute/pull/15485)) — thanks @piyush97 diff --git a/changelog.d/fixes/15494-combo-target-timeout-lockout.md b/changelog.d/fixes/15494-combo-target-timeout-lockout.md new file mode 100644 index 000000000000..92cbead5cc9f --- /dev/null +++ b/changelog.d/fixes/15494-combo-target-timeout-lockout.md @@ -0,0 +1 @@ +- **fix(combo):** a local per-target timeout records a model lockout without tripping the provider breaker ([#15494](https://github.com/diegosouzapw/OmniRoute/pull/15494) — thanks @HouMinXi) diff --git a/changelog.d/fixes/15504-configurable-fetch-start-cap.md b/changelog.d/fixes/15504-configurable-fetch-start-cap.md new file mode 100644 index 000000000000..6134e5f34cda --- /dev/null +++ b/changelog.d/fixes/15504-configurable-fetch-start-cap.md @@ -0,0 +1 @@ +- **fix(timeout):** configure the streaming fetch-start response-header cap while preserving its 110-second default. diff --git a/changelog.d/fixes/15505-claude-empty-end-turn.md b/changelog.d/fixes/15505-claude-empty-end-turn.md new file mode 100644 index 000000000000..6b98fd6fe004 --- /dev/null +++ b/changelog.d/fixes/15505-claude-empty-end-turn.md @@ -0,0 +1 @@ +- **fix(sse):** a finished Claude stream with `end_turn` or `stop_sequence` and no content block stays a 200 ([#15505](https://github.com/diegosouzapw/OmniRoute/pull/15505) — thanks @HouMinXi) diff --git a/changelog.d/fixes/15510-string-sequence-replay.md b/changelog.d/fixes/15510-string-sequence-replay.md new file mode 100644 index 000000000000..d267ac601e74 --- /dev/null +++ b/changelog.d/fixes/15510-string-sequence-replay.md @@ -0,0 +1 @@ +- **fix(sse):** a replayed Responses event is dropped when `sequence_number` is a whole-number string ([#15510](https://github.com/diegosouzapw/OmniRoute/pull/15510) — thanks @HouMinXi) diff --git a/changelog.d/fixes/15548-chat-quota-batch-init.md b/changelog.d/fixes/15548-chat-quota-batch-init.md new file mode 100644 index 000000000000..260cd6b9eccc --- /dev/null +++ b/changelog.d/fixes/15548-chat-quota-batch-init.md @@ -0,0 +1 @@ +- **fix(sse):** Register batch quota trackers after imports resolve, so a cold chat route no longer dies with an empty HTTP 500 (`(0 , e.Zd) is not a function`) ([#15548](https://github.com/diegosouzapw/OmniRoute/pull/15548) — thanks @wedreamer). diff --git a/changelog.d/fixes/15559-qoder-pat-validation-and-transient-cli-failures.md b/changelog.d/fixes/15559-qoder-pat-validation-and-transient-cli-failures.md new file mode 100644 index 000000000000..108b064c27f0 --- /dev/null +++ b/changelog.d/fixes/15559-qoder-pat-validation-and-transient-cli-failures.md @@ -0,0 +1 @@ +- **fix(qoder):** Qoder PAT validation accepts any exit-0 model catalog instead of requiring the literal `MODEL` header, and queued/busy (`10605`) qodercli responses are classified as a retryable `503` instead of a long-cooldown `502` ([#15559](https://github.com/diegosouzapw/OmniRoute/pull/15559)) — thanks @christianmahardhika diff --git a/changelog.d/fixes/15566-antigravity-claude-5-tier-ids.md b/changelog.d/fixes/15566-antigravity-claude-5-tier-ids.md new file mode 100644 index 000000000000..85147a1ae1de --- /dev/null +++ b/changelog.d/fixes/15566-antigravity-claude-5-tier-ids.md @@ -0,0 +1 @@ +- **fix(providers):** Antigravity Claude Opus 5.5 / Sonnet 5.5 work again. Antigravity serves each reasoning tier as its own upstream id (`claude-opus-5-5-high`, …) with no bare `claude-opus-5-5`, but the Claude effort-variant normalizer stripped the tier and dispatched the bare id, so every request failed with `429 Resource has been exhausted` on all accounts. Tiered Claude 5.x ids on `antigravity` / `ag` / `agy` are now dispatched verbatim, and the catalog no longer synthesizes effort variants on top of an Antigravity Claude 5.x base id. (#15566 — thanks @edosulai) diff --git a/changelog.d/fixes/15571-caveman-page-single-auto-clarity.md b/changelog.d/fixes/15571-caveman-page-single-auto-clarity.md new file mode 100644 index 000000000000..4c1b77a438e4 --- /dev/null +++ b/changelog.d/fixes/15571-caveman-page-single-auto-clarity.md @@ -0,0 +1 @@ +- **fix(compression):** the caveman context page shows one Auto-Clarity control, and its Auto-Clarity, language and pack saves keep changes saved elsewhere after the page loaded; turning a language pack off keeps English enabled, and the two translation keys only the removed control used are deleted from every UI catalog ([#15571](https://github.com/diegosouzapw/OmniRoute/pull/15571)) — thanks @woodsonl diff --git a/changelog.d/fixes/15583-surface-failed-settings-loads.md b/changelog.d/fixes/15583-surface-failed-settings-loads.md new file mode 100644 index 000000000000..3d060e32efe0 --- /dev/null +++ b/changelog.d/fixes/15583-surface-failed-settings-loads.md @@ -0,0 +1 @@ +- **fix(compression):** the Compression Hub, the combos manager and the RTK page show a Retry when `GET /api/settings/compression` fails, and keep the controls that depend on the loaded settings off screen until a GET succeeds, so a failed load can no longer render defaults as the stored settings ([#15583](https://github.com/diegosouzapw/OmniRoute/pull/15583)) — thanks @woodsonl diff --git a/changelog.d/fixes/15587-omniglyph-load-retry.md b/changelog.d/fixes/15587-omniglyph-load-retry.md new file mode 100644 index 000000000000..c2193bfca58f --- /dev/null +++ b/changelog.d/fixes/15587-omniglyph-load-retry.md @@ -0,0 +1 @@ +- **fix(compression):** the omniglyph context page shows a Retry when `GET /api/settings/compression` fails, and keeps its enable toggle and profile select hidden until a load succeeds, so a failed load can no longer save default engines over the stored row ([#15587](https://github.com/diegosouzapw/OmniRoute/pull/15587)) — thanks @woodsonl diff --git a/changelog.d/fixes/15589-caveman-preview-output-styles-marker.md b/changelog.d/fixes/15589-caveman-preview-output-styles-marker.md new file mode 100644 index 000000000000..1e21624d03fc --- /dev/null +++ b/changelog.d/fixes/15589-caveman-preview-output-styles-marker.md @@ -0,0 +1 @@ +- **fix(dashboard):** the caveman page's Output Mode card previews the exact block requests carry; the injector's composition moves into the exported `buildOutputStylesInstruction()` so preview and injection cannot drift, and the dead `contextCaveman.preview` keys are removed from all 67 UI catalogs ([#15589](https://github.com/diegosouzapw/OmniRoute/pull/15589)) — thanks @woodsonl diff --git a/changelog.d/fixes/15590-drain-v3852-base-reds.md b/changelog.d/fixes/15590-drain-v3852-base-reds.md new file mode 100644 index 000000000000..8e6395e194d1 --- /dev/null +++ b/changelog.d/fixes/15590-drain-v3852-base-reds.md @@ -0,0 +1,3 @@ +- **fix(ci):** the Fast Quality Gates loop runs the import-cycles gate in ratchet mode (`--ratchet`, ceiling `quality-baseline.json` → `metrics.cycles`) like ci.yml already does, so PRs against the release branch stop failing on the frozen 14-SCC baseline that #15281 turned into a hard exit (#15590) +- **fix(quality):** new-code ratchet gates (complexity, dead-code) diff against the GitHub merge ref's first parent — the live base tip — instead of the stale `pull_request.base.sha`, so base commits landing after a PR is opened are no longer counted as the PR's new code (#15590) +- **fix(dev):** the dev server runner (`run-next.mjs`) retries its listen with bounded backoff on `EADDRINUSE`, ending the integration-suite port race where a leftover socket claims the harness port during the ~15-18s `prepare()` window and kills the boot (#15590) diff --git a/changelog.d/fixes/15591-output-styles-hu-ja-zh-text.md b/changelog.d/fixes/15591-output-styles-hu-ja-zh-text.md new file mode 100644 index 000000000000..2bc41663cad5 --- /dev/null +++ b/changelog.d/fixes/15591-output-styles-hu-ja-zh-text.md @@ -0,0 +1 @@ +- **fix(compression):** legacy `cavemanOutputMode` injection now matches the old caveman text in Hungarian (it no longer falls back to English) and drops the extra space before the shared boundary sentence in Japanese and Chinese, including `terse-cjk` and multi-style selections. Prompt-cache prefixes for existing ja/zh selections change once on upgrade. diff --git a/changelog.d/fixes/15592-exclusions-rtk-save-failures.md b/changelog.d/fixes/15592-exclusions-rtk-save-failures.md new file mode 100644 index 000000000000..f86261c99261 --- /dev/null +++ b/changelog.d/fixes/15592-exclusions-rtk-save-failures.md @@ -0,0 +1 @@ +- **fix(dashboard):** surface failed saves and loads in the compression exclusions panel (save-failed alert, load-failed alert that locks the editor so Save cannot store an empty list over the stored exclusions, 15-second request timeouts, loading-aware count), and make the RTK context page queue its config saves one at a time with per-edit rollback, shape-checked replies with one read-back inside a shared 15-second budget, number fields that commit once and clamp to the schema bounds, an unload guard with commit-on-unmount, a failure toast after the page closed, and a load-failure alert that blocks saves ([#15592](https://github.com/diegosouzapw/OmniRoute/pull/15592)) diff --git a/changelog.d/fixes/15593-compression-hub-overlapping-saves.md b/changelog.d/fixes/15593-compression-hub-overlapping-saves.md new file mode 100644 index 000000000000..458cc48ac2e0 --- /dev/null +++ b/changelog.d/fixes/15593-compression-hub-overlapping-saves.md @@ -0,0 +1 @@ +- **fix(compression):** the Compression Hub's two settings controls stay enabled while a save is in flight, so saves overlapped and a failed one reverted the whole screen to its own starting snapshot, wiping a newer save the server had already stored; saves now go out one at a time through a queue shared by every mounted Hub, a failed save rolls back only its own fields, its error clears when a later save stores the field that failed or at the next edit, a queued save that a later one replaces is skipped, a stalled PUT fails after 15 seconds instead of holding the queue, the page asks before unloading while a save is pending, and a Hub mounted behind the queue shows what the server stored ([#15593](https://github.com/diegosouzapw/OmniRoute/pull/15593)) — thanks @woodsonl diff --git a/changelog.d/fixes/15595-engine-config-page-partial-save.md b/changelog.d/fixes/15595-engine-config-page-partial-save.md new file mode 100644 index 000000000000..e1f0ab4b750f --- /dev/null +++ b/changelog.d/fixes/15595-engine-config-page-partial-save.md @@ -0,0 +1 @@ +- **fix(dashboard):** Compression engine pages now save only the fields edited on that page. A save re-reads the stored settings and applies the edits onto the fresh stored copy, so saving an engine page no longer reverts thresholds and tool strategies the Compression Settings tab saved after the page loaded. An emptied text field clears its stored key, `ultra.enabled` survives engine-page saves, and the Ultra page saves on a default install where the model path was never set. (#15595) diff --git a/changelog.d/fixes/15603-ccr-ramp-factor-fraction.md b/changelog.d/fixes/15603-ccr-ramp-factor-fraction.md new file mode 100644 index 000000000000..1d2257a6d990 --- /dev/null +++ b/changelog.d/fixes/15603-ccr-ramp-factor-fraction.md @@ -0,0 +1 @@ +- **fix(compression):** CCR `retrievalRampFactor` keeps fractional values across save and reload — the read normalizer clamps to [1, 100] without flooring, so a saved 1.5 no longer disables the retrieval ramp ([#15603](https://github.com/diegosouzapw/OmniRoute/pull/15603)) — thanks @woodsonl diff --git a/changelog.d/fixes/15604-compression-preview-engine-config.md b/changelog.d/fixes/15604-compression-preview-engine-config.md new file mode 100644 index 000000000000..35fe49aaa3cb --- /dev/null +++ b/changelog.d/fixes/15604-compression-preview-engine-config.md @@ -0,0 +1 @@ +- **fix(compression):** the Aggressive and Ultra engine pages' Preview now runs with the form values the operator set: `/api/compression/preview` threads the sent `config.aggressive`/`config.ultra` into single-engine and pipeline previews instead of silently using built-in defaults, and the engine config page strips empty-string form values so Ultra's default `modelPath: ""` no longer fails preview and save validation ([#15604](https://github.com/diegosouzapw/OmniRoute/pull/15604)) diff --git a/changelog.d/fixes/15605-remove-engine-preserve-system-prompt.md b/changelog.d/fixes/15605-remove-engine-preserve-system-prompt.md new file mode 100644 index 000000000000..10fc1bb5077f --- /dev/null +++ b/changelog.d/fixes/15605-remove-engine-preserve-system-prompt.md @@ -0,0 +1 @@ +- **fix(compression):** the Lite, Aggressive and Ultra engine pages no longer show a "Preserve system prompt" checkbox. The per-engine field never took effect: every apply site reads the flag only from the global compression settings, the settings normalizers drop the per-engine copy, and the Lite page never sent it, so unchecking the box saved silently and snapped back on reload. The global "Preserve system prompt" control stays the real switch, stored combo step configs that still carry the key keep validating, and the orphaned locale label went away with it diff --git a/changelog.d/fixes/15607-compression-legacy-key-shadow.md b/changelog.d/fixes/15607-compression-legacy-key-shadow.md new file mode 100644 index 000000000000..8d48cba2dbc1 --- /dev/null +++ b/changelog.d/fixes/15607-compression-legacy-key-shadow.md @@ -0,0 +1 @@ +- **fix(compression):** current engine settings rows (`aggressive`, `ultra`, `headroom`) now win over legacy `aggressiveConfig`/`ultraConfig`/`headroomConfig` rows in `getCompressionSettings()`. The unordered namespace read let the longer legacy key, read second via the `(namespace, key)` index, shadow every save on the Aggressive, Ultra and Headroom engine pages: saves returned 200 while GET and live compression kept the legacy values. A legacy row with no current counterpart still applies, and a parsed non-object legacy value still resets the engine to defaults. diff --git a/changelog.d/fixes/15609-compression-default-combo-lossy.md b/changelog.d/fixes/15609-compression-default-combo-lossy.md new file mode 100644 index 000000000000..5d56cb73d95f --- /dev/null +++ b/changelog.d/fixes/15609-compression-default-combo-lossy.md @@ -0,0 +1 @@ +- **fix(compression):** the legacy default-combo fallback now respects the `x-omniroute-compression` header and the lossy request policy: a header-chosen plan (named combo, `engine:`, `default`, `off`) always wins, and without an opt-in the default combo runs only its safe steps, so an operator-edited default combo can no longer run rtk or other lossy engines on requests that never asked for them diff --git a/changelog.d/fixes/15612-engine-config-empty-number-unset.md b/changelog.d/fixes/15612-engine-config-empty-number-unset.md new file mode 100644 index 000000000000..cd826b728344 --- /dev/null +++ b/changelog.d/fixes/15612-engine-config-empty-number-unset.md @@ -0,0 +1 @@ +- **fix(dashboard):** compression engine config pages treat an emptied number field as "not set" instead of saving `0`, which silently changed compression for fields that accept zero (`aggressive.minSavingsThreshold`, `ultra.compressionRate`, `ultra.minScoreThreshold`) and 400'd saves for fields with a floor (`maxTokensPerMessage`, `minRows`, `minBlockChars`, `minChars`, `retrievalRampFactor`). Emptied fields now save as unset, show the schema default as a placeholder, keep an unparseable entry from unsetting the value, and reject overflow input visibly; lite's cap still clears when emptied. diff --git a/changelog.d/fixes/15613-compression-engines-partial-write.md b/changelog.d/fixes/15613-compression-engines-partial-write.md new file mode 100644 index 000000000000..efdc459aec0a --- /dev/null +++ b/changelog.d/fixes/15613-compression-engines-partial-write.md @@ -0,0 +1 @@ +- **fix(compression):** the compression engines map now merges by engine id on `PUT /api/settings/compression` instead of storing the map whole, and the Omniglyph page and context settings panel send only the engine they changed — so a toggle on one page no longer wipes what the other page (or a failed settings GET) never loaded. diff --git a/changelog.d/fixes/15616-stale-setup-notes.md b/changelog.d/fixes/15616-stale-setup-notes.md new file mode 100644 index 000000000000..1286364a008f --- /dev/null +++ b/changelog.d/fixes/15616-stale-setup-notes.md @@ -0,0 +1 @@ +- **fix(docs):** Correct two setup notes that contradicted the code. `.env.example` and the docs called `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` required for local providers, but `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` already allows local and LAN URLs by default, and the private flag turns off the cloud-metadata block on provider validation and model discovery. The bootstrap `INITIAL_PASSWORD` warning said an unset variable fell back to `CHANGEME`; it now reports the `CHANGEME` placeholder, an unset variable (the onboarding wizard asks you to create the password), and a whitespace-only value separately — thanks @woodsonl diff --git a/changelog.d/fixes/15628-stream-stall-account-cooldown.md b/changelog.d/fixes/15628-stream-stall-account-cooldown.md new file mode 100644 index 000000000000..a363e9ebc0e4 --- /dev/null +++ b/changelog.d/fixes/15628-stream-stall-account-cooldown.md @@ -0,0 +1 @@ +- **fix(sse):** a stream content stall no longer cools down the account that served it. The stall watchdog fails that one request (usually a long reasoning turn with no output yet), but the 60s cooldown also took the account out of routing, so on a pool with one usable Codex account every request in that minute failed over or returned 503. The error stays recorded on the connection, and operators can opt back in with `resilienceSettings.streamStallCooldown.enabled`. Codex GPT-6.x models and the `xhigh`/`max` effort tiers now also get the high-reasoning readiness budget. ([#15628](https://github.com/diegosouzapw/OmniRoute/pull/15628)) diff --git a/changelog.d/fixes/15629-usage-analytics-response-cache.md b/changelog.d/fixes/15629-usage-analytics-response-cache.md new file mode 100644 index 000000000000..198ed72537c6 --- /dev/null +++ b/changelog.d/fixes/15629-usage-analytics-response-cache.md @@ -0,0 +1 @@ +- **perf(usage):** cache slow `GET /api/usage/analytics` responses for 60 s per query and share in-flight computations, so repeated dashboard visits (API Manager, Costs, Budget, Analytics) no longer block the event loop — and every other request, including `/v1` traffic — for several seconds on large `usage_history` tables. Tunable with `OMNIROUTE_ANALYTICS_CACHE_TTL_MS` (0 disables) and `OMNIROUTE_ANALYTICS_CACHE_MIN_COMPUTE_MS` ([#15629](https://github.com/diegosouzapw/OmniRoute/pull/15629)) — thanks @HDBR diff --git a/changelog.d/fixes/15640-pending-map-byte-ceiling.md b/changelog.d/fixes/15640-pending-map-byte-ceiling.md new file mode 100644 index 000000000000..3c52eef04f1a --- /dev/null +++ b/changelog.d/fixes/15640-pending-map-byte-ceiling.md @@ -0,0 +1 @@ +- **fix(usage):** bound the in-flight pending-request map by retained bytes, not entry count alone — the 5000-entry cap bounded COUNT but not MEMORY (each entry retains up to four preview-truncated payloads, landing a worst-case burst near 250 MB). A 256 MB byte ceiling now evicts stale-marked-then-oldest entries after every insert, instead of waiting for the 5-minute age sweep; the UPDATE paths are measured too, so the ceiling cannot no-op. Pending ids also gained a monotonic suffix: the previous 24-bit random suffix collided ~52% of the time across a 5000-request burst. \ No newline at end of file diff --git a/changelog.d/fixes/PRNUM-evict-idle-limiter-aggregate-log.md b/changelog.d/fixes/PRNUM-evict-idle-limiter-aggregate-log.md new file mode 100644 index 000000000000..1bf1d71146a8 --- /dev/null +++ b/changelog.d/fixes/PRNUM-evict-idle-limiter-aggregate-log.md @@ -0,0 +1 @@ +- **fix(rate-limit):** idle limiter eviction now emits one aggregated line per watchdog pass instead of one line per limiter, with per-key detail on the debug channel ([#PRNUM](https://github.com/diegosouzapw/OmniRoute/pull/PRNUM)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/antigravity-family-weekly-quota-keys.md b/changelog.d/fixes/antigravity-family-weekly-quota-keys.md new file mode 100644 index 000000000000..14dac621ccbe --- /dev/null +++ b/changelog.d/fixes/antigravity-family-weekly-quota-keys.md @@ -0,0 +1 @@ +- **fix(usage):** Antigravity family weekly buckets `gemini_weekly` and `claude_gpt_weekly` are kept by the provider-limits sanitizer, so `/api/usage/provider-limits` and the quota card can show the weekly window next to the 5-hour window instead of dropping those keys as unknown model ids diff --git a/changelog.d/fixes/flag-descriptions-setup-claims.md b/changelog.d/fixes/flag-descriptions-setup-claims.md new file mode 100644 index 000000000000..77ba3e8e38e3 --- /dev/null +++ b/changelog.d/fixes/flag-descriptions-setup-claims.md @@ -0,0 +1 @@ +- **fix(dashboard):** The feature-flag cards for `OUTBOUND_SSRF_GUARD_ENABLED`, `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`, `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` and `RERANK_REMOTE_PROVIDER_NODES` still described the pre-correction guard semantics (local URLs blocked by default, metadata hosts never routed to). The dashboard renders the raw English `description` from `featureFlagDefinitions.ts`, so the four descriptions now carry the same corrected semantics as `docs/reference/FEATURE_FLAGS.md` (PR #15616), pinned by unit tests — thanks @woodsonl diff --git a/changelog.d/fixes/oidc-empty-allowlist-fail-closed.md b/changelog.d/fixes/oidc-empty-allowlist-fail-closed.md new file mode 100644 index 000000000000..205983b776ad --- /dev/null +++ b/changelog.d/fixes/oidc-empty-allowlist-fail-closed.md @@ -0,0 +1 @@ +- **fix(auth):** the OIDC callback no longer signs anyone in when `oidcAllowedSubjects` is empty, and the settings route rejects any update that would leave OIDC enabled with an empty allowlist, not only the one that enables it diff --git a/changelog.d/fixes/opencode-go-agent-user-agent.md b/changelog.d/fixes/opencode-go-agent-user-agent.md new file mode 100644 index 000000000000..39fc739adb5d --- /dev/null +++ b/changelog.d/fixes/opencode-go-agent-user-agent.md @@ -0,0 +1 @@ +- **fix(providers):** OpenCode Go keeps a third-party coding agent's own `User-Agent` (e.g. `my-coding-agent/1.0`) instead of rewriting it to `opencode/`, as Go's client requirements ask; generic SDK / HTTP-library UAs are still replaced and the Zen free-tier policy is unchanged ([#15311](https://github.com/diegosouzapw/OmniRoute/issues/15311)) — thanks @yourspraveen diff --git a/changelog.d/fixes/restricted-key-catalog-root-namespace.md b/changelog.d/fixes/restricted-key-catalog-root-namespace.md new file mode 100644 index 000000000000..825cc2d64fd4 --- /dev/null +++ b/changelog.d/fixes/restricted-key-catalog-root-namespace.md @@ -0,0 +1 @@ +- **fix(api):** `/v1/models` no longer lists another provider's models to a restricted API key — an aggregator row whose `root` carries a vendor namespace (cline's `deepseek/deepseek-v4-flash`, `openrouter/free`) no longer matches an allowlist entry meant for that vendor, and a blocked `id` is no longer re-admitted through its `root` ([#15409](https://github.com/diegosouzapw/OmniRoute/issues/15409)) — thanks @yourspraveen diff --git a/changelog.d/maintenance/0000-provider-auth-error-message-literals.md b/changelog.d/maintenance/0000-provider-auth-error-message-literals.md new file mode 100644 index 000000000000..1cdc00aac425 --- /dev/null +++ b/changelog.d/maintenance/0000-provider-auth-error-message-literals.md @@ -0,0 +1 @@ +- **test(auth):** pin the invariant that makes the 8 provider-auth routes safe (#15159). The `agy-auth` / `claude-auth` / `codex-auth` import, apply-local and export routes forward a _typed_ error straight to the client — `if (error instanceof AgyAuthFileError) return NextResponse.json({ error: error.message, code: error.code }, …)` — while their generic branch already routes through `sanitizeErrorMessage`. That is safe only because every message those classes are built with is a static app-authored literal: all **34** throw sites across `src/lib/oauth/utils/{agy,claude,codex}Auth*.ts` were verified to interpolate nothing, so no upstream body, token or filesystem path can reach the client through those routes. The new test asserts that, naming the offending file and line the moment someone adds a throw site like `` `refresh failed: ${upstream.body}` `` — which would otherwise silently start leaking through eight routes that read as safe on inspection, past a `check:error-helper` that trusts the whole file because it imports the sanitizer. The right fix for such a site is at the **throw site**, not eight rounds of route sanitization. diff --git a/changelog.d/maintenance/15225-scoring-weights-doc-parity.md b/changelog.d/maintenance/15225-scoring-weights-doc-parity.md new file mode 100644 index 000000000000..f709f39fcc06 --- /dev/null +++ b/changelog.d/maintenance/15225-scoring-weights-doc-parity.md @@ -0,0 +1 @@ +- **docs(routing):** pin the Auto-Combo scoring diagram weights to the code with a parity test that compares each diagram weight against the scoring defaults ([#15225](https://github.com/diegosouzapw/OmniRoute/pull/15225)) — thanks @maxmad64bis diff --git a/changelog.d/maintenance/15297-compression-guide-output-mode.md b/changelog.d/maintenance/15297-compression-guide-output-mode.md new file mode 100644 index 000000000000..e580958ed514 --- /dev/null +++ b/changelog.d/maintenance/15297-compression-guide-output-mode.md @@ -0,0 +1 @@ +- **docs(compression):** the Compression Guide names `cavemanOutputMode.enabled` as the switch for the legacy caveman output mode, states where its back-compat injection differs from the legacy one (marker line, ja/zh spacing, Hungarian fallback), lists the boundaries clauses as written, and documents what the Auto-Clarity Bypass toggle does and that it lives on the Caveman page ([#15297](https://github.com/diegosouzapw/OmniRoute/pull/15297)) — thanks @woodsonl diff --git a/changelog.d/maintenance/15345-quality-gates-file-size-doc.md b/changelog.d/maintenance/15345-quality-gates-file-size-doc.md new file mode 100644 index 000000000000..6b95387eea22 --- /dev/null +++ b/changelog.d/maintenance/15345-quality-gates-file-size-doc.md @@ -0,0 +1 @@ +- **docs:** `QUALITY_GATES.md` describes the `check:file-size` PR mode as the whole-tree, base-relative check it runs, and its gate-table row states the source and test-file caps ([#15345](https://github.com/diegosouzapw/OmniRoute/pull/15345)) — thanks @woodsonl diff --git a/changelog.d/maintenance/15389-contributing-co-authored-by-line.md b/changelog.d/maintenance/15389-contributing-co-authored-by-line.md new file mode 100644 index 000000000000..7e97bcb26ae8 --- /dev/null +++ b/changelog.d/maintenance/15389-contributing-co-authored-by-line.md @@ -0,0 +1 @@ +- **docs(contributing):** align the Co-Authored-By checklist line with Hard Rule #16 so reused human work keeps its standard credit ([#15389](https://github.com/diegosouzapw/OmniRoute/pull/15389)) — thanks @maxmad64bis diff --git a/changelog.d/maintenance/15398-guard-scheduled-workflows.md b/changelog.d/maintenance/15398-guard-scheduled-workflows.md new file mode 100644 index 000000000000..3d163041ae34 --- /dev/null +++ b/changelog.d/maintenance/15398-guard-scheduled-workflows.md @@ -0,0 +1 @@ +- **ci:** scheduled workflows now run only in the upstream repository, so forks with Actions enabled skip the inherited crons ([#15398](https://github.com/diegosouzapw/OmniRoute/pull/15398)) — thanks @maxmad64bis diff --git a/changelog.d/maintenance/15428-stream-readiness-async-leak.md b/changelog.d/maintenance/15428-stream-readiness-async-leak.md new file mode 100644 index 000000000000..ad61b369d8cb --- /dev/null +++ b/changelog.d/maintenance/15428-stream-readiness-async-leak.md @@ -0,0 +1 @@ +- **fix(test):** `stream-readiness-fallback` no longer fails the whole file on asynchronous activity after a test ends — two cases cancelled the served body instead of draining it, so the feeding pipeline settled post-test and tripped an unhandledRejection ([#15428](https://github.com/diegosouzapw/OmniRoute/pull/15428)). diff --git a/changelog.d/maintenance/15435-provider-health-matrix-covering-index.md b/changelog.d/maintenance/15435-provider-health-matrix-covering-index.md new file mode 100644 index 000000000000..443d72072b8d --- /dev/null +++ b/changelog.d/maintenance/15435-provider-health-matrix-covering-index.md @@ -0,0 +1 @@ +- **perf(db):** speed up the provider health matrix with a covering call-log index ([#15435](https://github.com/diegosouzapw/OmniRoute/pull/15435)) — thanks @maxmad64bis diff --git a/changelog.d/maintenance/15466-sync-ui-keys-linear-walk.md b/changelog.d/maintenance/15466-sync-ui-keys-linear-walk.md new file mode 100644 index 000000000000..b0043546f21c --- /dev/null +++ b/changelog.d/maintenance/15466-sync-ui-keys-linear-walk.md @@ -0,0 +1 @@ +- **perf(i18n):** syncing UI keys across locales no longer rebuilds each target node per key — missing keys merge in linear time with identical output ([#15466](https://github.com/diegosouzapw/OmniRoute/pull/15466)) — thanks @maxmad64bis diff --git a/changelog.d/maintenance/15596-compression-guide-source-check.md b/changelog.d/maintenance/15596-compression-guide-source-check.md new file mode 100644 index 000000000000..5f41c9cf798c --- /dev/null +++ b/changelog.d/maintenance/15596-compression-guide-source-check.md @@ -0,0 +1 @@ +- **docs(compression):** the Compression Guide's Caveman Output Mode, Output Styles, Tool Result Compression and language-selection sections now match the sources that implement them, and a verification sweep corrected the Ultra, Aggressive, Cache-Aware, Progressive Aging, Stacked Pipeline and Combo Overrides sections (independent ultra engine, truncation not summarization, provider-triggered cache downgrade, shipped aging thresholds, stacked math, string compressionOverride, 55 RTK filters) ([#15596](https://github.com/diegosouzapw/OmniRoute/pull/15596)) — thanks @woodsonl diff --git a/changelog.d/maintenance/15601-remove-aggressive-enabled-fallback.md b/changelog.d/maintenance/15601-remove-aggressive-enabled-fallback.md new file mode 100644 index 000000000000..40f48d50aed4 --- /dev/null +++ b/changelog.d/maintenance/15601-remove-aggressive-enabled-fallback.md @@ -0,0 +1 @@ +- **refactor(db):** the engines-map derivation no longer calls the unreachable `aggressiveEnabled` helper, which always returned false because the aggressive config normalizer never emits an `enabled` key; the derived default is now pinned by a unit test (aggressive off unless `defaultMode` is "aggressive") ([#15601](https://github.com/diegosouzapw/OmniRoute/pull/15601)) — thanks @woodsonl diff --git a/changelog.d/maintenance/15610-auto-clarity-pipeline-tests.md b/changelog.d/maintenance/15610-auto-clarity-pipeline-tests.md new file mode 100644 index 000000000000..3fd176707ddb --- /dev/null +++ b/changelog.d/maintenance/15610-auto-clarity-pipeline-tests.md @@ -0,0 +1 @@ +- **test(compression):** the Auto-Clarity chat-pipeline tests from #14551 move to `tests/unit/compression` and cover the legacy caveman switch, the Output Styles panel, and compression-combo output mode with the toggle off, on, left unset, and a benign turn; the compression guide (and its 66 locale mirrors) now say the toggle gates the bypass ([#15610](https://github.com/diegosouzapw/OmniRoute/pull/15610)) diff --git a/changelog.d/maintenance/readme-auto-subscription-thrifty.md b/changelog.d/maintenance/readme-auto-subscription-thrifty.md new file mode 100644 index 000000000000..49cfa18f10b0 --- /dev/null +++ b/changelog.d/maintenance/readme-auto-subscription-thrifty.md @@ -0,0 +1 @@ +- **docs(readme):** list the `auto/subscription` and `auto/thrifty` subscription-first auto combos (#11146) in the README's zero-config `auto` table, linking the subscription ladder guide — thanks @yourspraveen diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 8339e9dbc881..4cb914bba558 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -446,11 +446,6 @@ "count": 5 } }, - "open-sse/services/compression/engines/cavemanAdapter.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "open-sse/services/compression/engines/ccr/index.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -900,11 +895,6 @@ "count": 1 } }, - "src/app/(dashboard)/dashboard/settings/components/CompressionSettingsTab.tsx": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "src/app/(dashboard)/dashboard/settings/components/MitmProxyTab.tsx": { "@next/next/no-html-link-for-pages": { "count": 1 @@ -2033,7 +2023,7 @@ }, "tests/integration/chatcore-compression-integration.test.ts": { "@typescript-eslint/no-explicit-any": { - "count": 9 + "count": 8 }, "@typescript-eslint/no-unused-vars": { "count": 4 diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index fbeab3f25fb7..cdadf94f2ac9 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,10 @@ { + "_rebaseline_2026_10_06_fixes_board": "Fixes board own growth, measured with split(\"\\n\").length on the post-squash tip. src/app/api/providers/[id]/models/route.ts 2432->2446 (#15024 live cursor-api catalog discovery wired at the existing models route). tests/unit/combo-routing-engine.test.ts 3625->3663 (#15535 hedge skip for large bodies; the new cases stay in the existing combo engine suite). Ceilings are the measured sizes, not slack.", + "_rebaseline_2026_10_06_wave6_board": "Wave 6 board own growth, measured with split(\"\\n\").length on the post-squash tip. open-sse/executors/opencode.ts 1518->1519 (#15453 one irreducible keepAgentUserAgent flag at the existing forwardOpencodeClientHeaders call). tests/unit/combo/quota-weighted-strategy.test.ts new testFrozen 1217 (#15480 monthly-quota weighted-draw suite, 17 lines over testCap 1200; splitting the cohesive suite would not shrink the behavior). Covered by tests/unit/opencode-go-client-user-agent-15311.test.ts and the quota-weighted suite itself.", + "_rebaseline_2026_10_06_wave5_board": "Wave 5 board own growth, measured with split(\"\\n\").length on the post-merge tip. src/lib/db/providers.ts 1237->1240 (#15485 explicit provider priority on edit); src/lib/usage/usageHistory.ts crossed the 1200 new-file cap to 1227 (#15640 pending-map byte ceiling; newly frozen); src/sse/services/auth.ts 3642->3666 (#15628 stream-stall must not cool the account); open-sse/translator/response/openai-responses.ts 1536->1547 and open-sse/utils/stream.ts 3313->3325 (#15310 premature upstream EOF); open-sse/utils/proxyFetch.ts 1405->1406 (#15318 upstream HTTP/2 opt-out). Ceilings are the measured sizes, not slack.", + "_rebaseline_2026_10_06_wave2_hou_fastpath": "Wave 2 (HouMinXi fast-path, 26 squash-merges onto release/v3.8.52) own growth, measured with split(\"\\n\").length on the post-merge tip. src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx 1618->1678; src/sse/services/auth.ts 3640->3642; open-sse/handlers/responseSanitizer.ts 1204->1210; open-sse/handlers/search.ts 1789->1805; open-sse/utils/proxyFetch.ts 1404->1405; open-sse/utils/stream.ts 3298->3313. gateways.ts stays at the #15679 ceiling when it no longer exceeds it. Covered by the wave-2 focused tests (OIDC, cache scope, allowlist, tokens, TLS proxy, stream replay, Claude empty end_turn, collaboration plaintext, quota, combo timeout, MCP analytics, content-length, cloudflared, no-auth banner, thinking floor, provider crud, serve port, validation, catalog/version pins).", + "_rebaseline_2026_10_06_15520_freetheai_domain": "PR #15520 (jonlwheat2-gif) own growth: src/shared/constants/providers/apikey/gateways.ts 1544->1547 (freetheai base URL and signup copy moved off the dead .xyz host onto freetheai.org; declarative catalog strings only, same god-file no-split rationale as prior gateways.ts rebaselines). Measured with split(\"\\n\").length on origin/release/v3.8.52 after the wave-1 squash. Covered by tests/unit/provider-registry-freetheai.test.ts.", + "_rebaseline_2026_10_03_15443_per_model_pause": "Per-model pause own growth: src/sse/services/auth.ts 3623->3640 (+17 = one optional model param + one relayed pause call + one model-aware skip read + four Prettier-split model-threading callsites, irreducible wiring; a shared-helper variant was tried and measured +14 net, reverted). Covered by tests/unit/opencode-free-tier-per-model-pause.test.ts (14). PR #15443.", "_rebaseline_2026_09_29_14973_cursor_conversation_cache": "PR #14973 own growth: open-sse/utils/cursorAgentProtobuf.ts 1619->1622 (+3 over the 1619-line tip after #15036 = the single-line import of the new open-sse/utils/cursorAgentProtobuf/ttft.ts, the TtftBreakdownDelta member of DecodedDelta, and the one-line pushTtftBreakdown call in decodeAgentServerMessage; the turn_ended entry is rewritten at the same length to keep its end signal when the usage body is malformed). The ttft_breakdown decoder, its type and the safely() telemetry helper live in ttft.ts, under cap. open-sse/executors/cursor.ts stays under its 1868 ceiling. Covered by tests/unit/cursor-conversation-cache.test.ts.", "_rebaseline_2026_09_29_15036_cursor_read_range": "PR #15036 (QuangBlue) own growth: open-sse/utils/cursorAgentProtobuf.ts 1588->1619 (+31 = ReadArgs offset/limit field constants, the two optional fields on the exec_read event type, and createReadExecEvent, which decodes them next to the shared path-event decoder it reuses and cannot move out without exporting the file's private varint helpers). The bridge-side range check lives in open-sse/executors/cursor/builtinToolBridge.ts (under cap). Covered by tests/unit/cursor-agent-exec-router.test.ts and tests/unit/cursor-builtin-tool-bridge.test.ts.", "_rebaseline_2026_09_29_15050_timing_safe_env_key": "PR #15050 (HouMinXi) own growth: src/sse/services/auth.ts 3622->3623 (+1 = the timingSafeCompare import; the env passthrough-key check in isValidApiKey swaps `===` for the constant-time helper on its existing line). Irreducible. Covered by tests/unit/secret-compare-constant-time.test.ts.", @@ -287,14 +293,14 @@ "_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').", "_rebaseline_2026_09_04_12737_codex_ws_premature_close_tests": "PR #12737 own test growth: executor-codex.test.ts 1465->1620 (+155, entirely this PR's diff — regression coverage for the premature WebSocket close fix: emits terminal response.failed with code upstream_websocket_closed when the socket closes before any terminal event, and proves no second terminal event fires after a normal post-response.completed close).", "_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.", + "tests/unit/combo/quota-weighted-strategy.test.ts": 1217, "tests/integration/chat-pipeline.test.ts": 1756, - "tests/integration/chatcore-compression-integration.test.ts": 1214, "tests/unit/account-fallback-service.test.ts": 2086, "tests/unit/batch_api.test.ts": 1353, "tests/unit/cc-compatible-provider.test.ts": 1225, "tests/unit/chatcore-translation-paths.test.ts": 3558, "tests/unit/chatgpt-web.test.ts": 4911, - "tests/unit/combo-routing-engine.test.ts": 3625, + "tests/unit/combo-routing-engine.test.ts": 3663, "tests/unit/db-migration-runner.test.ts": 1509, "tests/unit/executor-codex.test.ts": 1620, "tests/unit/executor-default-base.test.ts": 1632, @@ -396,8 +402,13 @@ "_rebaseline_2026_07_27_3850_relax_filesize_cap": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). cap 800->900 (+100), testCap 800->900 (+100). Targets: decompose-existing-frozen unchanged (frozen still only-shrink); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes. SUPERSEDED by _rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct (v1 +20% buffer) — retained for audit. Tracked via same roadmap issue.", "_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)", "_rebaseline_2026_10_02_15159_jonlwheat2_wave": "Wave of jonlwheat2-gif audit PRs (#15159) landed together. src/lib/db/providers.ts 1199->1237 (+38, #15290: findExistingCookieConnection now matches the credential before the editable name, plus the collision guard; own growth at the existing chokepoint) is frozen at its measured size; open-sse/executors/deepseek-web.ts 1224->1228 (+4: prettier line-wrapping of the #15217 sanitization). Re-measured on the release tip after the wave.", + "_rebaseline_2026_10_03_15455_responses_stall_measure": "PR 15455 own growth: open-sse/executors/opencode.ts 1457->1463 (+6 = one import swap plus one guarded-call factory line at the existing stall wiring chokepoint, logic in open-sse/executors/opencodeResponsesStall.ts under no frozen ceiling). Additive and flag-off inert. Covered by tests/unit/opencode-responses-stall-measure.test.ts.", + "_rebaseline_2026_10_06_15455_opencode_silent_replies": "Own growth of #15455 (silent streamed replies counted while the first-byte guard runs): open-sse/executors/opencode.ts 1463->1467 on top of the release tip, wiring at the existing chokepoints; logic stays in sibling modules. Measured on the PR head merged with the tip.", + "_rebaseline_2026_10_03_15474_opencode-endpoint-unavailable-cooldown": "PR own growth: open-sse/services/accountFallback.ts 2517->2522 (+5 = 429 pre-check calling getOpencodeModelUnavailableMatch with the full error text before the configured 429 path, irreducible call-site wiring at the existing chokepoint; the rule itself lives outside the frozen file in open-sse/config/providerErrorRules.ts). Covered by tests/unit/opencode-endpoint-unavailable.test.ts (5 cases: 400 lock, 429 lock, zen registration, header-conflict priority, header-without-marker guard).", + "_rebaseline_2026_10_03_15475_free_tier_429_not_tool_refusal": "PR own growth: open-sse/executors/opencode.ts 1457->1509 (+52 = outcome triplet note in finalizeForcedStream via extracted helper + noteFreeTierRefusal helper gated to 403/451 with try/catch body read + two explicit notation sites reusing the verdict already held, no new branches). Measured with split(\"\\n\").length. Covered by tests/unit/opencode-free-tier-request-contract.test.ts (6 new cases: 3x429 kept, 3x403 dropped, 200-borrowed confirm, non-tool refusals ignored, deferred no-verdict ignored, deferred 403 counted).", + "_rebaseline_2026_10_06_15475_opencode_borrowed_tools": "Own growth of #15475 (only a refusal naming the model clears borrowed tools): open-sse/executors/opencode.ts frozen at its measured size 1518 on top of the release tip (#15383/#15455/#15451 landed in between); the shape/verdict logic lives in opencodeRequestShape.ts, opencodeFreeTierContract.ts and opencodeToolObservation.ts, only the wiring stays at the existing chokepoints.", "frozen": { - "src/lib/db/providers.ts": 1237, + "src/lib/db/providers.ts": 1240, "_rebaseline_2026_09_29_release_drain_wave": "Release-drain merge wave 2026-09-28/29 (~150 PRs into release/v3.8.51) follow-up; each file grew through already-merged PRs, re-measured with split(\"\\n\").length on release/v3.8.51 @b636b6d9: src/app/(dashboard)/dashboard/combos/page.tsx 5099->5110 (#14150/#14280/#14278); src/sse/handlers/chat.ts 2583->2586 (#14209/#13888/#15010); src/sse/services/auth.ts 3617->3622 (#13888 native Claude quota scope); open-sse/executors/default.ts 1207->1209 (#14870 GPT-6 max_completion_tokens); open-sse/handlers/responseSanitizer.ts 1200->1204 (#14813/#14894 (first frozen entry, file crossed the 1200 cap)); open-sse/utils/proxyFetch.ts 1383->1404 (#14315 stale keep-alive dispatcher + #13988 local direct timeout). Covered by the merged PRs' own tests; shrink work tracked with the other frozen files.", "_rebaseline_2026_09_28_merge_batch_wave": "Merge-batch 2026-09-28 follow-up (HouMinXi/shipsfromrio/maxmad64bis wave): src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx 1598->1611 (#14951: surface the API error envelope in the storage tab instead of crashing, #14846; covered by tests/unit/ui/system-storage-error-envelope-14846.test.ts + system-storage-backup-auth-envelope-14846.test.tsx). Measured with split(\"\\n\").length on release/v3.8.51 @dc061efc.", "_rebaseline_2026_09_25_14659_reselected_proxy_decl_restore": "Hotfix after the #14659 squash (3d8a9a2f): the release-merge conflict resolution dropped the 5-line `let reselectedProxy` declaration (+ comment) while keeping its two uses, leaving open-sse/executors/opencode.ts with TS2304 and a runtime ReferenceError on proxy-less dispatch. Restoring it adds 5 lines over the tip ceiling (1419->1424 at the #14659 tip, 1452->1457 after #14756 grew the file). Covered by tests/unit/opencode-429-pool-reselect.test.ts (0/6 on the broken tip, 6/6 with the fix).", @@ -522,19 +533,21 @@ "open-sse/executors/codex.ts": 1584, "open-sse/executors/cursor.ts": 1868, "open-sse/executors/muse-spark-web.ts": 1405, - "open-sse/handlers/chatCore.ts": 6444, + "open-sse/handlers/chatCore.ts": 6426, + "open-sse/handlers/chatCore/nonStreamingResponse.ts": 1212, + "open-sse/handlers/chatCore/streamingResponse.ts": 1308, "open-sse/handlers/imageGeneration.ts": 3334, - "open-sse/handlers/search.ts": 1789, + "open-sse/handlers/search.ts": 1805, "open-sse/mcp-server/schemas/tools.ts": 1621, "open-sse/mcp-server/server.ts": 1572, - "open-sse/services/accountFallback.ts": 2517, + "open-sse/services/accountFallback.ts": 2522, "open-sse/services/adobeFireflyBrowserLogin.ts": 1401, "open-sse/services/combo.ts": 4080, "open-sse/services/combo/executeTargetAttempt.ts": 1273, - "open-sse/translator/response/openai-responses.ts": 1536, + "open-sse/translator/response/openai-responses.ts": 1547, "open-sse/utils/cursorAgentProtobuf.ts": 1622, - "open-sse/utils/proxyFetch.ts": 1404, - "open-sse/utils/stream.ts": 3298, + "open-sse/utils/proxyFetch.ts": 1406, + "open-sse/utils/stream.ts": 3325, "open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/browser-worker.ts": 4398, "open-sse/vendor/codex-chatgpt-web/bridge.ts": 1335, "src/app/(dashboard)/dashboard/HomePageClient.tsx": 1344, @@ -545,12 +558,12 @@ "src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631, "src/app/(dashboard)/dashboard/providers/page.tsx": 2025, "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222, - "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1479, + "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1483, "src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271, - "src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1618, + "src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1678, "src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1611, "src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2152, - "src/app/api/providers/[id]/models/route.ts": 2432, + "src/app/api/providers/[id]/models/route.ts": 2446, "src/app/api/providers/[id]/test/route.ts": 1252, "src/app/api/v1/models/catalog.ts": 2127, "src/app/docs/lib/openapi.generated.ts": 1347, @@ -560,10 +573,10 @@ "src/lib/tailscaleTunnel.ts": 1208, "src/lib/tokenHealthCheck.ts": 1254, "src/shared/components/RequestLoggerV2.tsx": 1762, - "src/shared/constants/providers/apikey/gateways.ts": 1544, + "src/shared/constants/providers/apikey/gateways.ts": 1547, "src/shared/services/cliRuntime.ts": 1296, - "src/sse/handlers/chat.ts": 2586, - "src/sse/services/auth.ts": 3623, + "src/sse/handlers/chat.ts": 2597, + "src/sse/services/auth.ts": 3666, "tests/unit/account-fallback-service.test.ts": 2453, "tests/unit/provider-validation-specialty.test.ts": 4656, "open-sse/services/autoCombo/virtualFactory.ts": 1258, @@ -573,8 +586,9 @@ "open-sse/executors/deepseek-web.ts": 1228, "open-sse/executors/default.ts": 1209, "open-sse/services/rateLimitManager.ts": 1329, - "open-sse/executors/opencode.ts": 1457, - "open-sse/handlers/responseSanitizer.ts": 1204 + "open-sse/executors/opencode.ts": 1519, + "open-sse/handlers/responseSanitizer.ts": 1210, + "src/lib/usage/usageHistory.ts": 1227 }, "_rebaseline_2026_09_15_roundrobin_dashboard_events": "Fix #13089 (Combo Studio Live dashboard shows an empty backlog for round-robin combos): open-sse/services/combo/roundRobinCombo.ts 1205->1213. Round-robin is the only combo strategy that bypasses handleComboChat/executeTargetAttempt.ts, the path that publishes the combo.target.attempt/succeeded/failed EventBus events the Live dashboard listens for — so round-robin completions never showed up. The new call-site wiring (createRRDashboardEvents(...) instantiated once per target, one-line .attempt()/.succeeded()/.failed() calls at the 6 existing dispatch/outcome points) is the emitter logic actually extracted into a new module, open-sse/services/combo/rrDashboardEvents.ts — this is the minimum irreducible footprint for wiring 6 required call sites into 6 fixed control-flow points of the frozen file. Covered by tests/unit/issue-13089-roundrobin-live-ws-events.test.ts (2 tests: success + failure paths).", "_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.", @@ -775,5 +789,9 @@ "_rebaseline_2026_09_19_14162_native_codex_auto_resume": "PR #14162 (re-land of #13180, @mdigitalbh81 via @diegosouzapw): native Codex turn auto-resume. open-sse/services/combo/executeTargetAttempt.ts 1258->1273 (+15). Growth is 100% the PR's own, measured against the clean tip (1258 there, gate green): the pin step now advances the logical turn generation and logs the resumed provider/model when the attempt is an auto-resume dispatch, and the generation is passed into pinNativeCodexTurn — the branch has to sit at the pin site because that is the only place the winning target and effective connection are known. Covered by tests/unit/native-codex-auto-resume.test.ts + native-codex-auto-resume-guards.test.ts (15/15) and #13564's native-codex-turn-pin-model-scoped-fallback.test.ts (7/7).", "_rebaseline_2026_09_22_14405_freetier_observed_tools_retry": "Issue #14405 own growth: open-sse/executors/opencode.ts 1251->1303 (measured after merging release/v3.8.51, whose own drift entry _rebaseline_2026_09_22_opencode_train10c_drift had already moved the ceiling 1247->1251; the extra 5 lines over the original 1247->1294 measurement are the #14148 reconciliation: freeTierRetryCtx now reads the contract attempt back from the request body via attemptFor() instead of the removed shared _contractAttempt field) (+47 irreducible call-site wiring: freeTierRetryCtx builder + direct fast-path retry call + rotation-loop refusal arm delegating to handleLoopFreeTierRefusal + api-typecheck fixes (unknown-cast on stall-guard dispatch, callback-shaped loop handler keeping private finalize methods); the retry logic itself lives in the new module open-sse/executors/opencodeFreeTierRetry.ts (131 lines, under cap) and the merge helper in opencodeFreeTierContract.ts, so no logic was added to the frozen file beyond wiring two existing dispatch arms. Compaction attempts measured and reverted: loop-call wrapper (+22 net), fast-path/loop fusion (fragile: retry-403 vs original-403 share status). Covered by tests/unit/opencode-free-tier-refusal-rotation.test.ts (3 retry tests: union success, original refusal + store untouched, no retry without names) + tests/unit/opencode-free-tier-request-contract.test.ts (2 merge tests).", "_rebaseline_2026_09_24_14659_pool_reselect_per_attempt_growth": "PR #14659 (opt-in OPENCODE_POOL_RESELECT, default off): own growth at existing chokepoints, additive and flag-off inert, re-measured on the release tip post-#14620 merge. open-sse/executors/opencode.ts 1301->1355 (+54: flag/resolver hoist, reselect cell, 429-arm hunk after stop/park exits); open-sse/utils/proxyFetch.ts 1294->1311 (+17: reselectPoolMember field on AppliedProxySink + read-only currentAppliedProxySink getter on the shared store); src/sse/handlers/chat.ts 2563->2564 (+1: reselectPoolMember field on the sink literal); src/sse/handlers/chatHelpers.ts 1258->1303 (+45: publishPoolReselectResolver helper + one call at the capture site, resolver re-invokes pool selection). Covered by tests/unit/opencode-429-pool-reselect.test.ts (6/6 GREEN) + flag guards 58/58.", - "_rebaseline_2026_09_30_15186_stream_output_model_lockout": "Model-only lockout skipped for an isolated 5xx after the stream already relayed output (locked again once 3 such failures arrive in a row with no completed stream in between): open-sse/utils/stream.ts 3297->3298 (return registerStreamTiming(sseStream, timing)), open-sse/handlers/chatCore.ts 6441->6444 (hasEmittedOutput passed to createStreamFailureFinalizers + streak reset on a completed stream + its import) and src/sse/handlers/chatHelpers.ts 1285->1286 (streamOutputEmitted read from the failure payload) -- irreducible call-site wiring. The logic lives in new modules: open-sse/utils/streamTiming.ts (output registry), open-sse/services/accountFallback/postOutputFailureStreak.ts (streak) and src/sse/services/syntheticEmptyStream.ts (isRequestScopedServerFailure); src/sse/services/auth.ts stays under its ceiling because buildExhaustionOptions now reuses the markAccountUnavailable option type. Covered by tests/unit/stream-failure-after-output-no-model-lockout.test.ts." + "_rebaseline_2026_09_23_13065_chatcore_reextract": "Re-extract of #13065 onto the release/v3.8.51 tip (Diego's ask: each leaf must be an empty-diff lift of its chatCore.ts region, imports/exports aside). The two new leaves inherit the exact size of the blocks they carry out of the barrel: nonStreamingResponse.ts holds the if(!stream) leg plus the runNonStreamingProviderLeg/finalizeToolLoopError wrappers; streamingResponse.ts holds the if(stream) provider pipeline. Shrinking below the cap would mean re-modularizing beyond the approved extraction boundaries, which defeats the empty-diff review criterion. Frozen at current size: any further growth of these two files fails the gate.", + "_rebaseline_2026_09_30_15186_stream_output_model_lockout": "Model-only lockout skipped for an isolated 5xx after the stream already relayed output (locked again once 3 such failures arrive in a row with no completed stream in between): open-sse/utils/stream.ts 3297->3298 (return registerStreamTiming(sseStream, timing)), open-sse/handlers/chatCore.ts 6441->6444 (hasEmittedOutput passed to createStreamFailureFinalizers + streak reset on a completed stream + its import) and src/sse/handlers/chatHelpers.ts 1285->1286 (streamOutputEmitted read from the failure payload) -- irreducible call-site wiring. The logic lives in new modules: open-sse/utils/streamTiming.ts (output registry), open-sse/services/accountFallback/postOutputFailureStreak.ts (streak) and src/sse/services/syntheticEmptyStream.ts (isRequestScopedServerFailure); src/sse/services/auth.ts stays under its ceiling because buildExhaustionOptions now reuses the markAccountUnavailable option type. Covered by tests/unit/stream-failure-after-output-no-model-lockout.test.ts.", + "_rebaseline_2026_10_03_uniform_provider_regime_mount": "Own growth (uniform provider regime mount): src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx 1479->1483 (+4 = the PoolUpstreamRegimeLine import and its three-line conditional mount under the pool members label, next to PoolEgressObservation/PoolMemberEgressLines: rendered only when poolScope is 'provider' with a non-blank poolScopeId, which becomes the line's provider; nothing renders for other scopes or a blank id, so no conclusion without a provider). The regime itself lives outside the frozen file, all under cap: PoolUpstreamRegimeLine.tsx, the dedicated GET /api/settings/proxies/pool/uniform-egress route, readUniformEgressRegime/isUpstreamRegime in src/lib/proxyPoolEgressObservation.ts and getProviderUpstreamSummary in src/lib/db/proxies.ts. Only the mount point is irreducible. Covered by tests/unit/ui/proxy-registry-manager-regime-mount.test.tsx and tests/unit/ui/pool-upstream-regime-line.test.tsx.", + "_rebaseline_2026_10_02_admission_rejection_call_log": "PR own growth (admission rejection call-log) after extraction of the rejection-logging logic into the new src/sse/handlers/admissionRejectionLog.ts (under cap): src/sse/handlers/chat.ts 2586->2597 (+11 = one import + 10 call-site lines at the single acquire() rejection site, irreducible — plancher +6 prouve, gate exige +0). Covered by tests/unit/admission-rejection-call-log.test.ts.", + "_rebaseline_2026_10_02_15375_rotation_egress_label": "Own growth: open-sse/executors/opencode.ts 1456->1460 gate count (+4 = one hoisted attempt-egress const after the paced repick + its served-account comment share, nine inline `${egress}` suffixes inside existing warn templates, one inline 429 label argument, one pool re-select member label; all rotation-log suffixes, no new line, level, or branch. Label extraction lives outside the frozen file in open-sse/executors/opencodeEgressThrottle.ts::egressLabel (under no frozen ceiling; +51 = hoisted port/host helpers + label + 6th log429Outcome param). Measured with split(\"\\n\").length on the PR head. Covered by tests/unit/opencode-egress-label.test.ts (6: dedicated/direct/explicit-default-port/member/no-leak/fail-open) + strengthened rotation warns (transient 500 names egress, network throw ECONNRESET names egress). Red-proof: opencode-egress-label 1 case red without / green with; proxy-rotation-4954 3 red/green; transient-rotation 2 red/green." } diff --git a/config/quality/gate-manifest.json b/config/quality/gate-manifest.json index 47f090976970..3610a6135bcb 100644 --- a/config/quality/gate-manifest.json +++ b/config/quality/gate-manifest.json @@ -480,7 +480,7 @@ }, { "name": "test", - "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "disposition": "separately-invoked" }, { @@ -535,7 +535,7 @@ }, { "name": "test:coverage:runner", - "command": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "command": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "disposition": "separately-invoked" }, { @@ -615,22 +615,22 @@ }, { "name": "test:unit", - "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "disposition": "separately-invoked" }, { "name": "test:unit:ci", - "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "disposition": "separately-invoked" }, { "name": "test:unit:ci:shard", - "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD \"tests/unit/serial/**/*.test.ts\"", + "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD \"tests/unit/serial/**/*.test.ts\"", "disposition": "separately-invoked" }, { "name": "test:unit:fast", - "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "disposition": "separately-invoked" }, { @@ -645,12 +645,12 @@ }, { "name": "test:unit:shard:1", - "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"", + "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"", "disposition": "separately-invoked" }, { "name": "test:unit:shard:2", - "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"", + "command": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"", "disposition": "separately-invoked" }, { diff --git a/config/quality/test-masking-allowlist.json b/config/quality/test-masking-allowlist.json index 1876b8c677ba..de0154921330 100644 --- a/config/quality/test-masking-allowlist.json +++ b/config/quality/test-masking-allowlist.json @@ -61,6 +61,14 @@ "sourceRemoved": ["src/lib/oauth/services/raycastLocal.ts"], "reason": "v3.8.50 provenance remediation: the retired Raycast integration no longer ships local credential extraction. The replacement checks exact source closure and the negative runtime contract." }, + "@omniroute/opencode-plugin-v2/tests/auto-combos.test.ts": { + "replacement": "@omniroute/opencode-plugin-v2/tests/refresh-routes.test.ts", + "reason": "v3.8.52 #15392: the retired virtual-entries route and its fetcher (src/shared/auto-combos.ts) no longer ship; the replacement covers the gateway /v1/models publish path." + }, + "@omniroute/opencode-plugin-v2/tests/shared-auto-combos.test.ts": { + "replacement": "@omniroute/opencode-plugin-v2/tests/catalog-provider.test.ts", + "reason": "v3.8.52 #15392: same retirement; the replacement covers the served catalog entries." + }, "tests/unit/web-cookie-hailuo-web-11000.test.ts": { "replacement": "tests/unit/gpl-derived-provider-removals.test.ts", "sourceRemoved": [ @@ -174,9 +182,7 @@ "reason": "v3.8.50 #9126 (commit 8fac6bcd48): pluginWorker.ts, sandbox.ts e signing.ts foram removidos por completo (\"zero importers confirmed\") — o subsistema de sandbox de plugins com worker-thread nunca foi ligado a nenhum consumidor. O teste era source-scan sobre pluginWorker.ts (ver docstring do arquivo deletado); sem o arquivo-fonte não há mais o que testar. OMNIROUTE_PLUGINS_ALLOW_EXEC também foi removido de .env.example e da doc na mesma release. Sem substituto porque a feature foi extinta, não migrada." }, "tests/unit/plugins-sandbox.test.ts": { - "sourceRemoved": [ - "src/lib/plugins/sandbox.ts" - ], + "sourceRemoved": ["src/lib/plugins/sandbox.ts"], "reason": "v3.8.50 #9126 (commit 8fac6bcd48): sandbox.ts foi removido por completo junto com pluginWorker.ts e signing.ts (\"zero importers confirmed\", subsistema de sandbox de plugins nunca ligado a nenhum consumidor). O teste cobria SandboxLevel/getSandboxLabel exportados por sandbox.ts; sem o arquivo-fonte não há mais símbolo a testar. Mesma causa-raiz de tests/unit/plugin-sandbox-permissions.test.ts nesta entrada." }, "tests/unit/gemini-3-5-flash-thinking.test.ts": { @@ -255,5 +261,6 @@ "tests/unit/deepseek-web-tools-execute-2820.test.ts": "v3.8.50 ed661f2126 (alinhamento ao #9343): o teste 'parses bare JSON reply into OpenAI tool_calls' foi reescrito para o contrato INVERTIDO do fix de seguranca #9343 — JSON puro sem envelope NAO deve mais ser promovido. Verificar a nao-promocao custa 3 asserts (finish_reason stop, sem tool_calls, content preservado verbatim) onde validar o objeto promovido custava 5 (23->21). Mesma classe da entrada web-tools-translation-2820 acima. Contrato mais restritivo, nao mais fraco.", "tests/unit/startup-stale-cooldown-recovery.test.ts": "v3.8.50 #11355: o contrato do startup foi INVERTIDO — clearStaleCrashCooldowns deixou de limpar todo cooldown transiente e passa a limpar apenas os EXPIRADOS/não-parseáveis, preservando um rateLimitedUntil futuro (quota semanal/mensal que era apagada a cada restart). Os asserts das pós-condições da limpeza deixaram de existir e foram substituídos pelos da preservação (cleared === 0 + campos intactos), net 24→21. Asserts migrados ao novo contrato, não enfraquecidos. Prune após v3.8.50 mergear para main.", "tests/unit/8134-github-t5-fallback-filter.test.ts": "v3.8.50 #11280: #10952 adicionou claude-opus-4.6 ao catálogo do github, então o papel de 'tier provadamente ausente' migrou para claude-opus-4-6-thinking. Os asserts desenrolados de 2 hops viraram um LOOP de 3 hops (cada hop assere pertencimento ao catálogo + ausência do tier morto) mais uma igualdade exata no hop final — cobertura ESTRITAMENTE MAIOR com contagem estática menor (10→9). Verificado legítimo. Prune após v3.8.50 mergear para main.", - "tests/unit/model-capabilities-registry.test.ts": "v3.8.50 2764812ee4 (eliminate Gemini 3.5 Flash): os IDs gemini-3.5-flash{,-extra-low,-low} e gemini-3-flash-agent foram RETIRADOS do MODEL_SPECS, então os 6 asserts de capabilities por ID (contextWindow/maxOutputTokens/supportsThinking/Tools/Vision) deixaram de ter objeto e foram substituídos pelo assert da ausência (MODEL_SPECS[id] === undefined), net 77→72. Superfície aposentada, não mascaramento. Prune após v3.8.50 mergear para main." + "tests/unit/model-capabilities-registry.test.ts": "v3.8.50 2764812ee4 (eliminate Gemini 3.5 Flash): os IDs gemini-3.5-flash{,-extra-low,-low} e gemini-3-flash-agent foram RETIRADOS do MODEL_SPECS, então os 6 asserts de capabilities por ID (contextWindow/maxOutputTokens/supportsThinking/Tools/Vision) deixaram de ter objeto e foram substituídos pelo assert da ausência (MODEL_SPECS[id] === undefined), net 77→72. Superfície aposentada, não mascaramento. Prune após v3.8.50 mergear para main.", + "tests/unit/openrouter-free-model-credits-exhausted.test.ts": "v3.8.52 #15436: serve only catalogued free-access models on credits-exhausted connections — the positive `:free` assert moved to tests/unit/openrouter-credits-exhausted-catalog-access.test.ts (a catalogued free-access model is still served); net 5→4 is a split, not masking." } diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index 5bdd889bcb07..1db009b19985 100644 --- a/docs/architecture/ARCHITECTURE.md +++ b/docs/architecture/ARCHITECTURE.md @@ -1,7 +1,7 @@ --- title: "OmniRoute Architecture" -version: 3.8.40 -lastUpdated: 2026-06-28 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # OmniRoute Architecture @@ -299,7 +299,7 @@ Services (business logic): - Codex quota fetcher: `open-sse/services/codexQuotaFetcher.ts` — fetches Codex quota for context-relay handoff decisions - Cooldown-aware retry: `src/sse/services/cooldownAwareRetry.ts` — per-model cooldown retries with configurable `requestRetry` / `maxRetryIntervalSec` - Safe outbound fetch: `src/shared/network/safeOutboundFetch.ts` — guarded provider/model fetch with SSRF guard, private-URL blocking, retry, and timeout -- Outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — validates provider URLs against private/localhost CIDR ranges +- Outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — host checks on provider URLs; `src/shared/network/outboundUrlGuardPolicy.ts` picks the mode from `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS`, `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`, and their dashboard toggles (see `docs/reference/ENVIRONMENT.md`) - Provider request defaults: `open-sse/services/providerRequestDefaults.ts` — provider-level `maxTokens`, `temperature`, `thinkingBudgetTokens` defaults - GLM provider constants: `open-sse/config/glmProvider.ts` — shared GLM models, quota URLs, GLMT timeout/defaults - Antigravity upstream: `open-sse/config/antigravityUpstream.ts` — base URL and discovery path constants @@ -541,7 +541,7 @@ Domain State DB (SQLite): - API key generation/verification: `src/shared/utils/apiKey.ts` - Provider secrets persisted in `providerConnections` entries - Outbound proxy support via `open-sse/utils/proxyFetch.ts` (env vars) and `open-sse/utils/networkProxy.ts` (configurable per-provider or global) -- SSRF / outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — blocks private/loopback/link-local ranges for all provider calls +- SSRF / outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — host checks on provider calls and webhook targets; the mode comes from `src/shared/network/outboundUrlGuardPolicy.ts` (flags in `docs/reference/ENVIRONMENT.md`) - Runtime env validation: `src/lib/env/runtimeEnv.ts` — Zod schema for all environment variables, surfaced as startup errors/warnings - Sync tokens: `src/lib/db/syncTokens.ts` — scoped tokens for config bundle download endpoints; backed by `sync_tokens` SQLite table (migration `024_create_sync_tokens.sql`) - WebSocket handshake auth: `src/lib/ws/handshake.ts` — validates WS upgrade requests via API key or session cookie @@ -1095,9 +1095,9 @@ legacy compatibility. The current runtime contract uses: ## 6) SSRF / Outbound URL Guard -- `src/shared/network/outboundUrlGuard.ts` blocks all private/loopback/link-local target URLs before they reach provider executors +- `BaseExecutor.assertOutboundUrlAllowed` (`open-sse/executors/base.ts`) applies `src/shared/network/outboundUrlGuard.ts` to chat requests dispatched through `BaseExecutor.execute()`. In public-only mode (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` with the private flag off) it blocks private and loopback hosts and 169.254.0.0/16; in every other mode it blocks cloud-metadata hosts. It checks the hostname or IP literal as written, before any DNS lookup. Built-in local providers skip it. - Provider model discovery and validation routes use `src/shared/network/safeOutboundFetch.ts` which applies the guard before every outbound request -- Guard errors surface as `URL_GUARD_BLOCKED` with HTTP 422 and are logged to the compliance audit trail via `providerAudit.ts` +- Guard errors surface as `URL_GUARD_BLOCKED` with HTTP 503 (`getSafeOutboundFetchErrorStatus`; the model-discovery route returns 400), and genuine SSRF blocks during validation are logged to the audit trail as `provider.validation.ssrf_blocked` events ## Observability and Operational Signals diff --git a/docs/architecture/AUTHZ_GUIDE.md b/docs/architecture/AUTHZ_GUIDE.md index 53d0fc5caabd..67f8040a9f7b 100644 --- a/docs/architecture/AUTHZ_GUIDE.md +++ b/docs/architecture/AUTHZ_GUIDE.md @@ -66,10 +66,11 @@ supplemented: - `GET /api/auth/oidc/callback` validates `state`, exchanges the authorization code, and verifies the ID token's signature via the issuer's JWKS (`jose`'s `createRemoteJWKSet`, cached per JWKS URI) with `issuer`/`audience` - checks. An optional `oidcAllowedSubjects` allowlist matches the token's - `sub` claim or its `email` claim — the email claim is only honored when - `email_verified === true`, so an unverified email at the IdP can never pass - the gate. + checks. The `oidcAllowedSubjects` allowlist is required: it matches the + token's `sub` claim or its `email` claim — the email claim is only honored + when `email_verified === true`, so an unverified email at the IdP can never + pass the gate — and with no entries the callback refuses every login + (`not_configured`). - On success it mints the **exact same** 30-day `auth_token` JWT the password login issues (`src/app/api/auth/login/route.ts`), so the rest of the dashboard session pipeline (auto-refresh, cookie flags) is unchanged — diff --git a/docs/architecture/QUALITY_GATES.md b/docs/architecture/QUALITY_GATES.md index 6e2936391335..a1fc07a6bce5 100644 --- a/docs/architecture/QUALITY_GATES.md +++ b/docs/architecture/QUALITY_GATES.md @@ -115,7 +115,7 @@ Runs on every PR to `main`. Blocks merge on failure. | `check:tracked-artifacts` | No build artifacts / committed `node_modules` symlinks (also runs in husky pre-commit; pre-push is intentionally light — #6716) | Yes | | `check:ai-attribution` | No AI/bot `Co-Authored-By` trailer or AI-generation footer in PR commits, title or body — Hard Rule #16 (in the `quality.yml` fast-gates loop for PR→`release/**` — reads the event payload, no-op off PRs — and a PR-only step in `ci.yml` lint for PR→`main`; also the husky `commit-msg` hook; human co-authors allowed; #14436) | | `check:vitest-exclusions` | Every Vitest exclusion names a tracking issue and appears in `config/quality/vitest-exclusions.json` (#13204) | Yes | -| `check:file-size` | No source file exceeds the per-extension cap (ratchet: frozen large files in `frozen` list) | Yes | +| `check:file-size` | Source files (`.ts`/`.tsx` in `src/`, `open-sse/`, `electron/`, `bin/`) stay within `cap` and test files (`*.test.ts(x)`) within `testCap`; files frozen in `file-size-baseline.json` (`frozen` / `testFrozen`) must not grow past their recorded size | Yes | | `check:error-helper` | Error responses in executors/handlers use `buildErrorBody()` / `sanitizeErrorMessage()` (Hard Rule #12) | Yes | | `check:migration-numbering` | Migration SQL files are sequentially numbered, no gaps or duplicates | Yes | | `check:public-creds` | No literal OAuth `client_id`/`client_secret` or Firebase Web keys outside `publicCreds.ts` (Hard Rule #11) | Yes | @@ -317,16 +317,22 @@ docs/env contract, i18n parity, unit tests) are unchanged — a red test is stil **New-code mode (Clean-as-You-Code) — since 2026-08-30, PR fast-path only** On `pull_request` events `quality.yml` passes `--base-ref ` to `check:file-size`, -`check:complexity-ratchets` and `check:dead-code`. In that mode the gate compares HEAD with the -merge-base **restricted to the files the PR touched** (`scripts/check/newCodeMode.mjs`: the -merge-base is materialized in a throwaway `git worktree`, ESLint/knip run there and on HEAD, the -per-file counts are diffed): +`check:complexity-ratchets` and `check:dead-code`. For `check:complexity-ratchets` and +`check:dead-code` the gate compares HEAD with the merge-base **restricted to the files the PR +touched** (`scripts/check/newCodeMode.mjs`: the merge-base is materialized in a throwaway +`git worktree`, ESLint/knip run there and on HEAD, the per-file counts are diffed): - **blocking** — the PR added cyclomatic/cognitive violations or dead exports in files it changed (`complexityNewCode=`, `cognitiveComplexityNewCode=`, `deadExportsNewCode=` in the log); - **advisory** — the global total vs. the frozen baseline. Inherited drift never reds an innocent PR; the drift is re-frozen at release reconciliation and watched by the headroom job. +`check:file-size` uses the base ref on its own terms (`scripts/check/check-file-size.mjs`, #8522): +it checks every source and test file in the tree, reads each one's line count at the PR base +with `git show`, and fails a file only when it grows past the larger of its baseline ceiling +(`frozen` / `testFrozen`, or `cap` / `testCap` for a file outside the baseline) and its base +size. A file that already drifted on the base therefore never reds an innocent PR. + `workflow_dispatch` runs, the release-green sweep and the nightly headroom job have no PR base and keep the absolute (global) comparison. Coverage, duplication and type-coverage stay global for now (their tools do not produce a per-file diff cheaply) — candidates for the same treatment. diff --git a/docs/architecture/RESILIENCE_GUIDE.md b/docs/architecture/RESILIENCE_GUIDE.md index eff68abb158c..2d3060791d6e 100644 --- a/docs/architecture/RESILIENCE_GUIDE.md +++ b/docs/architecture/RESILIENCE_GUIDE.md @@ -98,6 +98,13 @@ Regression guard: `tests/unit/provider-cooldown-window-gate.test.ts`. **Anti-thundering-herd guard:** prevents concurrent failures from over-extending cooldown or double-incrementing `backoffLevel`. +**Stream content stalls do not cool the account.** When the content-stall watchdog +(`open-sse/utils/streamHandler.ts`) gives up on a stream that sent no model output in +time, `markAccountUnavailable()` records the error on the connection but sets no +cooldown: the stall belongs to that request, most often a long reasoning turn with no +output yet. Operators can opt back in with `resilienceSettings.streamStallCooldown.enabled` +(default `false`). + **Terminal states (NOT cooldowns):** - `banned` — set by banned-keyword / account-ban detection (see [BAN_DETECTION](../security/BAN_DETECTION.md)), and by three consecutive upstream per-request refusals (`request_rejected`, e.g. Anthropic OAuth 403 "Request not allowed" — `open-sse/services/requestRejectedStreak.ts`); a single refusal only cools the connection down diff --git a/docs/compression/COMPRESSION_GUIDE.md b/docs/compression/COMPRESSION_GUIDE.md index 2f1c050d977c..506f47814a33 100644 --- a/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/compression/COMPRESSION_GUIDE.md @@ -68,7 +68,8 @@ Inspired by [Caveman](https://github.com/JuliusBrussee/caveman) — removes fill Smart history management for long sessions: - **Message Aging** — older messages get progressively compressed -- **Tool Result Summarization** — long tool outputs replaced with summaries +- **Tool Result Compression** — long tool outputs truncated or elided (first/last lines, + match-line filtering, JSON key compaction) - **Structural Integrity Guards** — ensures `tool_use` + `tool_result` pairs stay consistent - **Context Window Awareness** — respects per-model token limits @@ -78,10 +79,13 @@ Smart history management for long sessions: Maximum compression for token-critical scenarios: -- **Heuristic Pruning** — removes messages below relevance threshold -- **Code Block Thinning** — compresses repetitive code examples -- **Binary Search Truncation** — finds optimal cut point for context window -- All Aggressive mode features included +- **Heuristic Pruning** — score-based token pruning of prose +- **Structure Preservation** — fenced code blocks, inline code, URLs and identifiers are + tombstoned and re-stitched verbatim, never pruned +- **Optional SLM tier** — a small local model can refine the prune when configured +- Independent of Aggressive mode: it does not run message aging, tool-result compression + or the fallback summarizer (only an SLM-tier failure can route a fallback pass through + aggressive) **Best for:** When you're hitting context limits repeatedly. @@ -95,7 +99,7 @@ RTK mode is optimized for verbose tool outputs that appear in coding-agent sessi - Applies JSON filter packs from `open-sse/services/compression/engines/rtk/filters/` - Imports RTK TOML schema v1 filters from project or global `filters.toml` files, with inline-test validation and trust-gating for project files -- Ships 49 built-in filters with inline verify samples +- Ships 55 built-in filters with inline verify samples - Removes ANSI control sequences, progress bars, repeated lines, and non-actionable noise - Preserves failures, errors, warnings, changed files, summaries, and the tail of long output - Supports trust-gated project filters, global filters, and optional redacted raw-output recovery @@ -211,7 +215,8 @@ This lets you use stacked compression on free/coding providers while keeping lit subscriptions. This "Per-Combo Override" assignment is a different control from the **routing-combo compression -mode** override (Default/Off/Lite/Standard/Aggressive/Ultra) — that override does not pick a named +mode** override (Default/Off/Lite/Standard/Aggressive/Ultra/Codex Responses — the field's +schema also accepts `rtk`, `stacked` and `omniglyph`) — that override does not pick a named compression-combo pipeline; it just sets the `compressionMode` field consulted by `resolveCompressionPlan`. It can be set either on the combo card (`Dashboard → Combos`) or, since #6760, per routing combo in the "Assign to routing" list on @@ -328,8 +333,14 @@ RTK mode is inspired by **[RTK - Rust Token Killer](https://github.com/rtk-ai/rt ## Advanced Compression Systems -Beyond the 7 standard modes, OmniRoute includes several advanced compression -systems that work automatically based on context. +Beyond the 7 modes described above (the source also accepts `codex-responses` and +`omniglyph` modes, which this guide does not cover), the sections below cover features +that work inside or alongside those modes: Tool Result Compression and Progressive Aging +are steps 1 and 2 of the aggressive engine (Aggressive mode and an `aggressive` step of a +stacked pipeline), the Stacked Pipeline is how Stacked mode runs, Cache-Aware Compression +downgrades `aggressive` and `ultra` to `standard` for caching providers while compression +is on, and Caveman Output Mode and Output Styles are opt-in system-prompt instructions, +off by default, that shape the model's output instead of compressing the request. ### Cache-Aware Compression @@ -347,7 +358,9 @@ The `cachingAware.ts` module solves this by **detecting caching context** and 2. **Identify caching providers** — Checks if the target provider supports caching 3. **Adjust strategy** — Downgrades `aggressive`/`ultra` to `standard` for caching providers 4. **Skip system prompt** — System prompts are usually cached, so don't compress them -5. **Use deterministic transformations** — Only use transformations that produce consistent output + +The strategy helper also returns a `deterministicOnly` flag, but the plan builder consumes +only the strategy — nothing downstream reads the flag today. #### Code example @@ -364,7 +377,7 @@ const body = { }; const ctx = detectCachingContext(body, { provider: "anthropic" }); -// → { hasCacheControl: true, provider: "anthropic", isCachingProvider: true } +// → { hasCacheControl: true, provider: "anthropic", targetFormat: null, isCachingProvider: true } const strategy = getCacheAwareStrategy("aggressive", ctx); // → { strategy: "standard", skipSystemPrompt: true, deterministicOnly: true } @@ -372,21 +385,26 @@ const strategy = getCacheAwareStrategy("aggressive", ctx); #### When to use -Cache-aware compression is **always on** — no configuration needed. It only kicks in -when: - -- The request has `cache_control` markers -- The target provider supports prompt caching (Anthropic, OpenAI, etc.) +Cache-aware compression is **always on** — no configuration needed. It kicks in whenever +compression is on and the target provider supports prompt caching (Anthropic, OpenAI, +etc.); explicit `cache_control` markers are not required — a caching provider alone +triggers the downgrade, and markers alone never do (marker detection feeds cache +telemetry, not the strategy decision). ### Progressive Aging Long conversations accumulate many message turns, but older turns become less -relevant. The `progressiveAging.ts` module **degrades messages by turn distance**: - -- **Recent turns (0-3)**: Kept verbatim (full detail) -- **Medium turns (4-8)**: Lite compression (whitespace, formatting cleanup) -- **Old turns (9+)**: Caveman compression (filler removal, summarization) -- **Very old turns (20+)**: Heavily summarized or dropped +relevant. The `progressiveAging.ts` module **degrades messages by turn distance** +(distance measured from the end of the conversation). With the shipped defaults +(`verbatim: 2, light: 2, moderate: 3`): + +- **Last 2 turns (distance ≤ 2)**: Kept verbatim +- **Distance 3**: Caveman compression (filler removal) +- **Distance 4+**: Assistant messages summarized; user messages reduced to their first + line, capped at 120 characters; other roles untouched. System prompts, already-aged + messages and the latest user message are always kept verbatim regardless of distance. + Nothing is dropped outright, and the `light` + band is unreachable with the shipped defaults (`light` equals `verbatim`). #### Code example @@ -401,10 +419,11 @@ const messages = [ ]; const { messages: aged, saved } = applyAging(messages, { - verbatim: 3, // First 3 turns: verbatim - light: 8, // Turns 4-8: lite compression - moderate: 20, // Turns 9-20: caveman compression - // Turns 21+: heavy summarization + verbatim: 3, // last 3 turns: verbatim + light: 8, // distance <= 8: lite compression + moderate: 20, // distance <= 20: caveman compression + fullSummary: 5, // required by the type, not read by the banding code + // distance > 20: summarized (assistant) / first line kept (user) }); // saved = number of tokens saved @@ -412,7 +431,8 @@ const { messages: aged, saved } = applyAging(messages, { #### When to use -Progressive aging is **always on** for `aggressive` and `ultra` modes. It's +Progressive aging is **always on** for `aggressive` mode — it is step 2 of +`compressAggressive()`. Ultra mode does not run it. It's particularly effective for: - Long-running coding sessions @@ -421,14 +441,30 @@ particularly effective for: ### Caveman Output Mode -The `outputMode.ts` module injects **system prompt instructions** to make the -model itself produce compressed, terse output (a "caveman" style). +Caveman output mode adds **system prompt instructions** that ask the model itself for +terse output — the `lite` level asks for concise answers that keep full sentences, `full` +asks it to "respond terse like smart caveman", and `ultra` asks for telegraphic output; +instructions only ask, they cannot guarantee it. Requests receive them through +`applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`): +`open-sse/handlers/chatCore.ts` first resolves the selection with the back-compat shim +(`resolveOutputStyleSelection()` in +`open-sse/services/compression/outputStyles/backCompat.ts`), which, while `outputStyles` +is empty, maps an enabled `cavemanOutputMode` to the `terse-prose` output style at +`cavemanOutputMode.intensity` (see Back-compat below); a non-empty `outputStyles` +selection is used as is, and `cavemanOutputMode.enabled` and `intensity` then have no +effect, while its `autoClarity` toggle still applies. `outputMode.ts` holds the +instruction texts (`CAVEMAN_INSTRUCTION_BY_LANGUAGE`), the content bypass and the +placement helper that injection uses; its own `applyCavemanOutputMode()` injector has no +production caller. #### How it works -Instead of compressing the input, this mode adds a system prompt like: +This mode does not compress the input. It adds an instruction block to the system prompt +(see How injection works below), and any input compression mode selected for the request +still runs afterwards, on the body that now carries the block. Ahead of the shared +boundaries clause that every level ends with, the English `full` level reads: -> "Reply in minimal words. Skip pleasantries. Use short sentences." +> "Respond terse like smart caveman. Drop articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries, hedging. Fragments OK. Short synonyms (big not extensive, fix not implement). Keep all technical substance, code, errors, URLs, identifiers exact." This works particularly well for: @@ -438,54 +474,81 @@ This works particularly well for: #### When to use -Caveman output mode is **opt-in** — set it via the combo config: +Caveman output mode is **opt-in**. With compression on (`enabled: true`, the master toggle +on the Compression Settings page), turn it on with `cavemanOutputMode.enabled`; `intensity` +picks `lite`, `full` or `ultra`: ```json { - "strategy": "auto", - "config": { - "auto": { - "outputMode": "caveman" - } + "enabled": true, + "cavemanOutputMode": { + "enabled": true, + "intensity": "full" } } ``` +A compression combo's **Output Mode** toggle (`outputMode`, level in `outputModeIntensity`) +sets the same switch for the requests that combo applies to, and the +`omniroute_set_compression_engine` MCP tool writes it through its boolean `outputMode` +argument. A non-empty `outputStyles` selection takes precedence over this switch. In the +dashboard, enabling the **Terse prose** output style injects the same block (see Output +Styles below). + ### Output Styles (catalog) Caveman output mode above is the **legacy single-style path**. Phase 4 generalized it into a catalog of composable output styles: `OUTPUT_STYLE_CATALOG` in `open-sse/services/compression/outputStyles/catalog.ts`. Each style is a system-prompt -instruction that makes the model itself produce cheaper output; styles can be enabled +instruction that asks the model itself for cheaper output; styles can be enabled together and are injected in catalog order. -| Style | `id` | What it does | Instruction languages | -| -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | -| Terse prose | `terse-prose` | Drop filler/articles/hedging; keep technical substance exact. Same text as the legacy caveman output mode (referenced, not re-typed). | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | -| Less code | `less-code` | YAGNI ladder: smallest working change, no unrequested abstractions. | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | -| Ponytail (lazy senior dev) | `ponytail` | "The best code is the code never written": reuse > rewrite, root cause > symptom, shortest working diff. | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | -| I have ADHD (action-first) | `i-have-adhd` | Action first (command/path/snippet before prose), numbered bounded steps, ONE concrete next step, no preamble/recap/closers. Adapted from [ayghri/i-have-adhd](https://github.com/ayghri/i-have-adhd) (MIT). | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | -| Terse CJK (文言) | `terse-cjk` | Classical-Chinese ultra-terse style. | zh (locale-gated: only offered when the resolved language is `zh`) | +| Style | `id` | What it does | Instruction languages | +| -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | +| Terse prose | `terse-prose` | Drop filler/articles/hedging; keep technical substance exact. Same text as the legacy caveman output mode (referenced, not re-typed). | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | +| Less code | `less-code` | YAGNI ladder: smallest working change, no unrequested abstractions. | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | +| Ponytail (lazy senior dev) | `ponytail` | "The best code is the code never written": reuse > rewrite, root cause > symptom, shortest working diff. | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | +| I have ADHD (action-first) | `i-have-adhd` | Action first (command/path/snippet before prose), numbered bounded steps, ONE concrete next step, no preamble/recap/closers. Adapted from [ayghri/i-have-adhd](https://github.com/ayghri/i-have-adhd) (MIT). | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi | +| Terse CJK (文言) | `terse-cjk` | `full`/`ultra` answer in Classical Chinese (文言); `lite` only asks for brief answers without function words, pleasantries or embellishment. | zh (locale-gated, see below) | Every style ships three intensity levels — `lite`, `full`, `ultra` — and every level -ends with the shared boundaries clause, which keeps code blocks, file paths, commands, -error strings, URLs and identifiers verbatim. +ends with the shared boundaries clause (`SHARED_BOUNDARIES` in `outputMode.ts`), which +keeps code blocks, file paths, commands, errors and URLs exact. The `terse-prose` and +`terse-cjk` level texts add identifiers to that list. + +`terse-cjk` is locale-gated to `zh` in two places. The Compression Settings page lists +its row only when the dashboard UI language is Chinese (`zh-CN` or `zh-TW`), and +`applyOutputStyles()` injects it only when the request's resolved language (see Language +selection below) is `zh`. Hiding the row does not clear a saved `terse-cjk` selection: +the settings API accepts any style id, and saving other styles on the page keeps it. At +request time, the `applyOutputStyles()` language check is the only locale gate. #### How injection works `applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`) resolves the selection against the catalog (unknown ids and locale-mismatched styles are -dropped, never an error), concatenates the selected instructions in catalog order, -appends the boundaries clause **once**, and starts the block with a single idempotency -marker (`[OmniRoute Output Styles]`), so re-applying is a no-op. When the resolved -language (see Language selection below) has a translation, the localized instruction is -injected instead of English. - -On a body with `messages`, a content bypass (`shouldBypassCavemanOutputMode()` in -`open-sse/services/compression/outputMode.ts`) checks the last three messages and skips -the styles for the whole turn when they match its security, irreversible-action, -clarification, or order-sensitive keywords. The bypass runs whatever the dashboard's -**Auto-Clarity Bypass** toggle (`cavemanOutputMode.autoClarity`) is set to. +dropped, never an error; a selection that resolves to no style leaves the body +unchanged, skipped as `no_styles`), concatenates the selected instructions in catalog +order, +appends the boundaries clause **once** (plus the safety clause, `SAFETY_BOUNDARIES` or its +translation, when `less-code` or `ponytail` is selected), and starts the block with a +single idempotency marker (`[OmniRoute Output Styles]`), so re-applying is a no-op. When +the resolved language (see Language selection below) has a translation, the localized +instruction is injected instead of English. + +On a body with a non-empty `messages` array, the idempotency check runs before the +content bypass: when the `[OmniRoute Output Styles]` marker is already in the top-level +`system` field (a string or a content-block array) or in a system message with string +content, the body is left unchanged as `already_applied` and no keyword check runs. +Otherwise a content bypass (`shouldBypassCavemanOutputMode()` in +`open-sse/services/compression/outputMode.ts`) checks the text of the last three +messages, whatever their role, and skips the styles for the whole turn when that text +matches its security, irreversible-action or clarification keywords, or an +order-sensitive sequence: `first`, `then`, `after that`, `before`, `rollback` or +`backup` followed within 240 characters by `delete`, `drop`, `migrate`, `deploy` or +`release`. The bypass runs while the **Auto-Clarity Bypass** toggle +(`cavemanOutputMode.autoClarity`, on by default) is on; turning the toggle off skips the +keyword check. When the bypass lets the turn through, `placeSystemInstruction()` (same file), which never creates a new `messages[0]`, places the block in the first of these it finds: @@ -497,15 +560,23 @@ never creates a new `messages[0]`, places the block in the first of these it fin text. 4. None of the above: the block goes into a new system message at the end of `messages`. -On a body without `messages`, the block is appended to a string `instructions` field, -or becomes `instructions` when the body carries `input` (a string or an array). A body -with neither `instructions` nor `input` is skipped as `no_messages`. +On a body without a `messages` array (or with an empty one), no content bypass runs and +a top-level `system` field is not consulted. The block is appended after the text of a +string `instructions` field, unless that field already contains the +`[OmniRoute Output Styles]` marker, in which case the body is left unchanged as +`already_applied`. When the body has no string `instructions` field but carries `input` +(a string or an array), the block becomes `instructions`, replacing any non-string value +that field held. A body with neither a string `instructions` field nor a string or array +`input` is left unchanged and skipped as `no_messages`. #### How to enable -In the dashboard: **Context → Settings → Compression** — one row per style with an -on/off toggle and a level selector. Programmatically, the compression config persists -the selection as: +In the dashboard: **Compression Context → Compression Settings** +(`/dashboard/context/settings`), Output styles section: one row per style with an on/off +toggle and a level selector. Styles inject while compression itself is on (the page's +master toggle, `enabled`). The **Auto-Clarity Bypass** toggle is on the **Caveman** +page (`/dashboard/context/caveman`), in its **Output Mode** card. Programmatically, the +compression config persists the selection as: ```json { @@ -516,48 +587,118 @@ the selection as: } ``` -Back-compat: the legacy `outputMode: "caveman"` combo setting still works and maps to -`terse-prose`, byte-identical to the old injection in every legacy language. - -Language selection: with `languageConfig.enabled` on, `autoDetect` picks the -language of the latest user message (same detector as the input engines); -turning `autoDetect` off pins `defaultLanguage`. Off → English. +Back-compat: while `outputStyles` is empty, the legacy `cavemanOutputMode.enabled` +setting maps to `terse-prose` at `cavemanOutputMode.intensity`. The block then starts +with the `[OmniRoute Output Styles]` marker, where the legacy `applyCavemanOutputMode()` +injector wrote `[OmniRoute Caveman Output Mode]`. Below the marker, the text matches the +legacy injection in en, pt-BR, es, de, fr, it, ru, id and vi; in ja and zh it carries one +extra space before the boundaries clause. `terse-prose` translates into pt-BR, es, de, +fr, it, ru, zh, ja, id and vi, so a request whose resolved language is `hu` gets the +English text where the legacy injector used its Hungarian one. + +Output-style language selection (`resolveOutputStyleLanguage()` in +`outputStyles/apply.ts`): with `languageConfig.enabled` on, `autoDetect` samples the +latest user message in the request's `messages` array that has text (string content, or +the `text` of its content parts) and runs the Caveman engine's detector +(`detectCompressionLanguage()`) on it. The detector returns `zh` for text with Han +characters and no kana; otherwise it returns whichever of `it`, `pt-BR`, `es`, `de`, +`fr`, `ru`, `ja`, `hu` and `id` has the most hint matches, and `en` when none match — +text it cannot classify gets English, never `defaultLanguage`, and `vi` is never +detected although the styles ship `vi` text. A Responses API body keeps its turns in +`input`, which is not sampled, so it gets `defaultLanguage`, then English. When no user +message in `messages` has text, or with `autoDetect` off, `defaultLanguage` applies, +then English. With `languageConfig.enabled` off, the language is English — unless a +compression combo applies to the request (a combo assigned to the request's routing +combo, or the default compression combo chatCore falls back to for the built-in stacked +pipeline): applying a combo turns `languageConfig.enabled` on for that request and sets +`defaultLanguage` from the combo's language packs (the saved value if it is one of the +combo's packs, otherwise the combo's first pack, which defaults to `en`), while the +saved `autoDetect` (on by default) still applies. The Caveman input engine picks its +rule-pack language differently — per text part and, with auto-detect off, gated on +`enabledPacks`. The style × language matrix is pinned by -`tests/unit/compression/output-styles-i18n-matrix.test.ts`: a new style cannot ship -without at least a pt-BR translation (or an explicit tracked exception), and an -existing style cannot silently lose a locale. To add a style, see +`tests/unit/compression/output-styles-i18n-matrix.test.ts`: every catalog style needs an +entry in the test's `BASELINE_LANGUAGES`; a style that is not locale-gated must ship a +pt-BR translation (the locale-gated `terse-cjk` is exempt from this rule) unless it is +listed in `KNOWN_ENGLISH_ONLY`, which may hold only styles with no translations at all — +a listed style that has any translation fails the test; and a style fails the test when +it loses a language that its `BASELINE_LANGUAGES` entry lists. To add a style, see [EXTENDING_COMPRESSION.md](./EXTENDING_COMPRESSION.md#adding-an-output-style). ### Tool Result Compression -The `toolResultCompressor.ts` module provides **5 specialized compression strategies** -for tool results (function calls, agent outputs, search results, etc.): - -1. **Search result compression** — Removes redundant results, keeps top-N -2. **File read compression** — Truncates large files, preserves headers/imports -3. **Code execution compression** — Keeps only essential stdout/stderr -4. **Database query compression** — Limits rows, removes verbose metadata -5. **API response compression** — Strips null fields, condenses arrays +`compressToolResult()` in `open-sse/services/compression/toolResultCompressor.ts` +compresses tool-result text with **5 strategies**. It tries them in this order, and the +first enabled strategy whose check matches the content decides the result: + +1. **`fileContent`**: content of 3 or more lines in which at least one line, ignoring + leading indentation, starts with `import `, `export `, `function `, `class `, + `const `, `let `, `var ` or `return ` (the keyword plus a space), or with `if`, + `for` or `while` followed by `(` or ` (`, keeps its first 20 and last 5 lines, with + the elided middle marked. +2. **`grepSearch`**: content with at least one line of the form `::`, + where the text before the first colon has no whitespace, keeps only those lines, at + most 30, followed by a count of any further matches and the list of matched files; + every other line is dropped. One such line is enough to trigger the strategy, so a + log line starting with a timestamp such as `12:30:45` also counts. +3. **`shellOutput`**: output that contains an ANSI CSI sequence (`ESC[` then digits or + semicolons then a letter, as in color codes) or a `$` followed by whitespace + anywhere in the text loses those sequences (other escapes, such as `ESC[?25l` or an + OSC window-title sequence, are kept) and keeps its last 50 lines, with consecutive + repeated lines collapsed. Because this check runs before `json` and `errorMessage`, + JSON or error output that contains such a `$` never reaches them while + `shellOutput` is on. +4. **`json`**: a JSON payload over 2,000 characters that starts with `{` or `[` (after + optional whitespace) and parses is summarized: an array of more than 7 items keeps + its first 5 and last 2 items and its total count, and an object keeps its first 20 + keys, with each nested object or array value replaced by a `{…N keys}` placeholder + (for an array, N is its length) and a `_remaining__keys` marker counting the keys + dropped past the first 20. Scalar values are copied whole, so an object of 20 keys + or fewer with no nested values is only re-indented — a minified one gains characters + and stays unchanged. +5. **`errorMessage`**: output that contains, anywhere and in any letter case, `error:`, + `error ` (the word followed by a space, as in `no error found`), `[error]`, + `exception:`, `exception `, `[exception]` or `traceback` keeps its first line, the + next 10 lines and the last 3, with a `… [N frames elided] …` marker in place of the + lines between them. The marker appears only when more than 13 lines follow the first + line, so error output of 14 lines or fewer is not shortened (at 12 or 13 lines the + last 3 repeat lines already kept). + +After a strategy matches, even one that saves nothing, the later strategies are not +tried. When the matching strategy saves no estimated tokens (length ÷ 4, rounded up) — +for example a code-like file of 25 lines or fewer, or a JSON array over 2,000 +characters with 7 items or fewer — the aggressive engine keeps the original tool +result: both callers (`compressAggressive()` and `compressAnthropicToolResultBlock()`) +keep the original when `saved` is 0 or below, while `compressToolResult()` itself still +returns that strategy's output. The tool-result step is not the last word: the +engine's fallback summarizer can still shorten a `tool` or `function` message longer +than 8,192 characters (`maxTokensPerMessage`, 2,048, times 4). #### When to use -Tool result compression is **always on** when tool calls are present. No -configuration needed. +Tool result compression is step 1 of the aggressive engine (`compressAggressive()` in +`open-sse/services/compression/aggressive.ts`), so it runs in Aggressive mode and in an +`aggressive` step of a stacked pipeline. It compresses OpenAI-shape `tool` and `function` +messages and the text inside Anthropic `tool_result` blocks. Each strategy has its own +switch under `aggressive.toolStrategies`, all on by default. In the dashboard, the +switches are in the Caveman page's **Advanced** view while compression is on and the +default mode is Aggressive. ### Stacked Pipeline The stacked mode runs **multiple engines in sequence** — usually RTK first -(60-90% savings on tool output), then Caveman (30% additional savings on the -remaining text). This achieves **78-95% total savings**. +(60-90% savings on tool output), then Caveman on the remaining text (~46% input +savings). Composed, that is the **78-95% eligible range** (see Upstream Savings Math +above): `1 - (1 - 0.60..0.90) × (1 - 0.46)` averages ≈89%. #### How it works ``` Input (1000 tokens) → RTK (command-aware filter) → 200 tokens - → Caveman (filler removal) → 140 tokens - → Output (140 tokens, 86% savings) + → Caveman (filler removal) → 108 tokens + → Output (108 tokens, ~89% savings) ``` #### When to use @@ -568,18 +709,10 @@ Use stacked mode for: - Cost-sensitive batch processing - When you need maximum token savings -Configure via combo: - -```json -{ - "strategy": "auto", - "config": { - "auto": { - "modePack": "stacked" - } - } -} -``` +Stacked pipelines are configured through the global `stackedPipeline` compression +setting, or through a named compression combo assigned to a routing combo (see +Per-Combo Override above) — not through an auto-combo `modePack` (that field only +re-weights auto-combo model selection, and `stacked` is not a valid pack name). --- @@ -593,16 +726,10 @@ for different use cases: "id": "coding-combo", "strategy": "priority", "config": { - "auto": { - "weights": { "taskFit": 0.5 }, - "modePack": "quality-first" - } + "weights": { "taskFit": 0.5 }, + "modePack": "quality-first" }, - "compressionOverride": { - "mode": "aggressive", - "stackedPipelines": ["rtk", "caveman"], - "preserveToolDefinitions": true - } + "compressionOverride": "aggressive" } ``` @@ -611,7 +738,9 @@ This is useful for: - **Coding combos**: Use `aggressive` mode for long sessions - **Quick Q&A combos**: Use `lite` mode for fast responses - **Tool-heavy combos**: Use `stacked` mode for max savings -- **Production combos**: Use `cache-aware` mode for caching providers +- **Production combos**: leave the override off for caching providers — the always-on + cache-aware adjustment downgrades `aggressive`/`ultra` to `standard` automatically + (there is no selectable `cache-aware` mode) --- diff --git a/docs/getting-started/SELF_HOST_GUIDE.md b/docs/getting-started/SELF_HOST_GUIDE.md index 8e10a4ac9efe..515b576de38a 100644 --- a/docs/getting-started/SELF_HOST_GUIDE.md +++ b/docs/getting-started/SELF_HOST_GUIDE.md @@ -1,7 +1,7 @@ --- title: "🚀 Self-Host Guide — OmniRoute (零月费自托管 / zero-fee self-host)" -version: 3.8.51 -lastUpdated: 2026-09-27 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # 🚀 Self-Host Guide — OmniRoute @@ -36,7 +36,7 @@ form: a packaged container/binary you run on your own machine in 5 minutes. OmniRoute ships a sophisticated `docker-compose.yml` with profiles (`base`, `web`, `cli`, `host`, `cliproxyapi`, `memory`, `bifrost`). Each app service is profile-gated, so a bare `docker compose up -d` only starts Redis. -That is correct for power users who pick a profile — but it is *not* a +That is correct for power users who pick a profile — but it is _not_ a 5-minute self-host story. `docker-compose.selfhost.yml` is the KISS overlay: **one command, published @@ -44,10 +44,10 @@ image, loopback-only, Redis included, no profile choice, no build step.** When you outgrow it, graduate to the full [DOCKER_GUIDE](../guides/DOCKER_GUIDE.md) profiles. -| Audience | Start here | Graduate to | -| --- | --- | --- | -| Self-hoster, single user | this guide | — | -| Power user, CLI tools / web-cookie providers / sidecars | — | `docker-compose.yml` profiles | +| Audience | Start here | Graduate to | +| ------------------------------------------------------- | ---------- | ----------------------------- | +| Self-hoster, single user | this guide | — | +| Power user, CLI tools / web-cookie providers / sidecars | — | `docker-compose.yml` profiles | --- @@ -92,12 +92,17 @@ REQUIRE_API_KEY=true # was false — lock the endpoint down APP_BIND_HOST=127.0.0.1 # keep loopback; see "Exposing" only if needed ``` -`REQUIRE_API_KEY=true` makes every `/v1` request and the dashboard require an -API key / login. On first boot the dashboard auto-generates an -`INITIAL_PASSWORD` — read it from the logs: +`REQUIRE_API_KEY=true` makes `/v1` requests require an API key (a logged-in +dashboard session also works), and the +dashboard require login once a password exists. On first visit you create the +login password in the dashboard's onboarding wizard; skipping that step turns +dashboard login off, even with `REQUIRE_API_KEY=true`. Docker +port-forwarding makes your browser look non-local to the container, so after you +submit the password the wizard asks for a one-time bootstrap token, which the +container prints to its log at that moment: ```bash -docker logs omniroute | grep -i password +docker logs omniroute | grep BOOTSTRAP ``` The other variables (`DASHBOARD_PORT`, `API_PORT`, `LIVE_WS_PORT`, @@ -139,11 +144,11 @@ is `healthy` — the acceptance bar from the self-host issue. ## Ports -| Port | What | Default bind | -| --- | --- | --- | -| `20128` | Dashboard + `/v1` LLM proxy (unified entry) | `127.0.0.1` | -| `20129` | API port (server-to-server) | `127.0.0.1` | -| `20132` | Live WebSocket (realtime dashboard updates) | `127.0.0.1` | +| Port | What | Default bind | +| ------- | ------------------------------------------- | ------------ | +| `20128` | Dashboard + `/v1` LLM proxy (unified entry) | `127.0.0.1` | +| `20129` | API port (server-to-server) | `127.0.0.1` | +| `20132` | Live WebSocket (realtime dashboard updates) | `127.0.0.1` | All three bind to **loopback only** by default. Redis is **not** published to the host at all — the app reaches it over the compose network. This is @@ -226,8 +231,8 @@ publishes an open `/v1` proxy on every LAN/WAN interface — anyone on the network can burn your provider quotas. The order is fixed: 1. Set `REQUIRE_API_KEY=true` in `.env`. -2. Read `INITIAL_PASSWORD` from the logs and log in. -3. *Only then* set `APP_BIND_HOST=0.0.0.0` (or put an auth-enforcing reverse +2. Create the login password in the onboarding wizard (see above) and log in. +3. _Only then_ set `APP_BIND_HOST=0.0.0.0` (or put an auth-enforcing reverse proxy in front and keep loopback). For TLS / a domain, run Caddy or Traefik in front and leave `APP_BIND_HOST` @@ -301,6 +306,7 @@ Or, with the full compose, pick a profile: - The healthcheck probes `/healthz` and allows a 20 s start period. A slow first boot (cold migrations) can take longer — bump `start_period` in the compose file if your disk is slow. +
@@ -354,8 +360,8 @@ It is a **pure local self-host** carrier — the simplest thing that makes the Before you expose beyond loopback: - [ ] `REQUIRE_API_KEY=true` in `.env` -- [ ] `INITIAL_PASSWORD` rotated to a strong, unique value -- [ ] `APP_BIND_HOST` left at `127.0.0.1` *unless* behind an auth-enforcing proxy +- [ ] Dashboard login password set in the onboarding wizard to a strong, unique value +- [ ] `APP_BIND_HOST` left at `127.0.0.1` _unless_ behind an auth-enforcing proxy - [ ] TLS terminated by Caddy/Traefik/Cloudflare in front (never plain HTTP on WAN) - [ ] Redis not published to the host (the self-host compose already enforces this) - [ ] `./data` volume backed up regularly (`bin/snapshot-data.sh`) diff --git a/docs/guides/FEATURES.md b/docs/guides/FEATURES.md index 69fe6a0bf7ee..47711ecb365f 100644 --- a/docs/guides/FEATURES.md +++ b/docs/guides/FEATURES.md @@ -1,7 +1,7 @@ --- title: "OmniRoute — Dashboard Features Gallery" -version: 3.8.40 -lastUpdated: 2026-06-28 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # OmniRoute — Dashboard Features Gallery @@ -322,10 +322,10 @@ The config bundle is built by `src/lib/sync/bundle.ts`. Consumers compare the `E All provider validation and model discovery calls now go through a two-layer outbound guard: -1. **URL guard** (`src/shared/network/outboundUrlGuard.ts`) — Blocks private/loopback/link-local IP ranges before the socket is opened. +1. **URL guard** (`src/shared/network/outboundUrlGuard.ts`) — Checks the target's hostname or IP literal as written first; in public-only mode the safe-fetch wrapper also resolves the name and refuses private answers. By default it blocks cloud-metadata endpoints — all of 169.254.0.0/16 plus the known metadata hostnames; `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` blocks private and loopback hosts too; `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` (or the legacy `OUTBOUND_SSRF_GUARD_ENABLED=false`) turns the checks off. A value saved from a flag's dashboard toggle takes precedence over its variable, and built-in local providers skip the guard during key validation. See `docs/reference/ENVIRONMENT.md`. 2. **Safe fetch wrapper** (`src/shared/network/safeOutboundFetch.ts`) — Applies the URL guard, normalises timeouts, and retries transient errors with exponential backoff. -Guard violations surface as HTTP 422 (`URL_GUARD_BLOCKED`) and are written to the compliance audit log via `providerAudit.ts`. +Guard violations surface as `URL_GUARD_BLOCKED` — HTTP 503 via `getSafeOutboundFetchErrorStatus` (400 on the model-discovery route) — and genuine SSRF blocks during validation are written to the audit log as `provider.validation.ssrf_blocked` events. --- diff --git a/docs/guides/OPENCODE-V2-PLUGIN.md b/docs/guides/OPENCODE-V2-PLUGIN.md index cc27ab54f213..23a7f9b8f438 100644 --- a/docs/guides/OPENCODE-V2-PLUGIN.md +++ b/docs/guides/OPENCODE-V2-PLUGIN.md @@ -87,12 +87,19 @@ naming the endpoint and what was lost — so a degraded picker is never a myster | `enrichment` | `true` | Fetch names, pricing and free-tier budgets | | `providerTag` | `true` | Prefix a display name with the upstream provider it routes to | | `usableOnly` | `false` | Keep only providers the gateway reports as provisioned | +| `showcasePerOwner` | `10` | Default-view entries kept per provider | +| `freshPerOwner` | `10` | Default-view fresh entries kept per provider | +| `freshWindowDays` | `90` | Freshness window in days for the fresh branch | +| `usageMemory` | `true` | Restore statically dropped entries named by 30-day usage analytics (needs a management token) | | `visibleModels` / `hiddenModels` | `[]` | Exact-or-suffix allowlists; deny wins | | `geminiSanitization` | `true` | Strip the JSON-Schema keywords Gemini rejects from tool schemas (`$ref` tools are forwarded untouched) | | `apiFormat.allowAnthropic` | `false` | Route allowlisted ids through the Anthropic API block | | `apiFormat.anthropicModels` | `[]` | Full model ids routed to Anthropic | | `logLevel` / `startupDebug` | `warn` / `false` | Logger verbosity | +Usage memory is on by default. Without a management token it stays inert +(a startup notice is logged) and nothing is restored. + ## How the catalog stays fresh The catalog is fetched lazily and cached for `modelCacheTtlMs`, and a disk snapshot keeps the diff --git a/docs/guides/USER_GUIDE.md b/docs/guides/USER_GUIDE.md index ff8a0a56814d..584a881d5975 100644 --- a/docs/guides/USER_GUIDE.md +++ b/docs/guides/USER_GUIDE.md @@ -1,7 +1,7 @@ --- title: "User Guide" -version: 3.8.40 -lastUpdated: 2026-06-28 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # User Guide @@ -582,28 +582,28 @@ post_install() { ### Environment Variables -| Variable | Default | Description | -| --------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) | -| `INITIAL_PASSWORD` | `CHANGEME` | First login password | -| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) | -| `PORT` | framework default | Service port (`20128` in examples) | -| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) | -| `NODE_ENV` | runtime default | Set `production` for deploy | -| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | Public base URL surfaced to the dashboard and exposed to the server (replaces legacy `BASE_URL`) | -| `NEXT_PUBLIC_CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL (replaces legacy `CLOUD_URL`) | -| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys | -| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` | -| `ALLOW_API_KEY_REVEAL` | `false` | Allow authenticated dashboard users to reveal full stored API key values on demand | -| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | Server-side refresh cadence for cached Provider Limits data; UI refresh buttons still trigger manual sync | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | Disable automatic SQLite snapshots before writes/import/restore; manual backups still work | -| `APP_LOG_TO_FILE` | `true` | Enables application and audit log output to disk | -| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) | -| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download | -| `CLOUDFLARED_PROTOCOL` | `http2` | Transport for managed Quick Tunnels (`http2`, `quic`, or `auto`) | -| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB | -| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries | -| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries | +| Variable | Default | Description | +| --------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | _(auto-generated)_ | JWT signing secret — auto-generated on first run and persisted to `server.env`; set it explicitly when the data directory is ephemeral | +| `INITIAL_PASSWORD` | _(unset)_ | First login password. npm/source installs start with `CHANGEME`: replace it. Unset: onboarding wizard | +| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) | +| `PORT` | framework default | Service port (`20128` in examples) | +| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) | +| `NODE_ENV` | runtime default | Set `production` for deploy | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | Public base URL surfaced to the dashboard and exposed to the server (replaces legacy `BASE_URL`) | +| `NEXT_PUBLIC_CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL (replaces legacy `CLOUD_URL`) | +| `API_KEY_SECRET` | _(auto-generated)_ | HMAC secret for generated API keys — auto-generated on first run and persisted to `server.env` | +| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` | +| `ALLOW_API_KEY_REVEAL` | `false` | Allow authenticated dashboard users to reveal full stored API key values on demand | +| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | Server-side refresh cadence for cached Provider Limits data; UI refresh buttons still trigger manual sync | +| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | Disable automatic SQLite snapshots before writes/import/restore; manual backups still work | +| `APP_LOG_TO_FILE` | `true` | Enables application and audit log output to disk | +| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) | +| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download | +| `CLOUDFLARED_PROTOCOL` | `http2` | Transport for managed Quick Tunnels (`http2`, `quic`, or `auto`) | +| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB | +| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries | +| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries | For the full environment variable reference, see the [README](../README.md). diff --git a/docs/i18n/am/CONTRIBUTING.md b/docs/i18n/am/CONTRIBUTING.md index 5b79f3616713..0196ef6b87d6 100644 --- a/docs/i18n/am/CONTRIBUTING.md +++ b/docs/i18n/am/CONTRIBUTING.md @@ -398,20 +398,20 @@ docs/ ## የPull Request ማረጋገጫ ዝርዝር -- [ ] Tests በትክክል ያልፋሉ (`npm test`) -- [ ] Linting በትክክል ያልፋል (`npm run lint`) +- [ ] ሙከራዎች ያልፋሉ (`npm test`) +- [ ] Linting ያልፋል (`npm run lint`) - [ ] Build ይሳካል (`npm run build`) - [ ] ለአዲስ ይፋዊ functions እና interfaces የTypeScript types ታክለዋል -- [ ] በኮድ ውስጥ በቀጥታ የተጻፉ secrets ወይም fallback values የሉም -- [ ] ይፋዊ የupstream ማረጋገጫ መረጃዎች በ`resolvePublicCred()` ተካትተዋል ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)ን ይመልከቱ)፣ ፈጽሞ እንደ literals አልተጻፉም -- [ ] የስህተት ምላሾች በ`buildErrorBody()` / `sanitizeErrorMessage()` ያልፋሉ — በresponse bodies ውስጥ ጥሬ stack traces የሉም ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)ን ይመልከቱ) -- [ ] Shell commands (`exec` / `spawn`) የruntime valuesን በstring interpolation ሳይሆን በ`env` ያስተላልፋሉ +- [ ] Hardcoded secrets ወይም fallback values የሉም +- [ ] ይፋዊ upstream credentials በ`resolvePublicCred()` በኩል ተካትተዋል ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ይመልከቱ)፤ በፍጹም እንደ literals አልተጻፉም +- [ ] የስህተት ምላሾች በ`buildErrorBody()` / `sanitizeErrorMessage()` በኩል ያልፋሉ — በምላሽ bodies ውስጥ raw stack traces የሉም ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ይመልከቱ) +- [ ] Shell commands (`exec` / `spawn`) የruntime valuesን በstring interpolation ሳይሆን በ`env` በኩል ያስተላልፋሉ - [ ] ሁሉም inputs በZod schemas ተረጋግጠዋል -- [ ] ለተጠቃሚ የሚታዩ ለውጦች Changelog **fragment** በ`changelog.d/{features|fixes|maintenance}/-.md` ስር ታክሏል ([`changelog.d/README.md`](./changelog.d/README.md)ን ይመልከቱ) — `CHANGELOG.md`ን በቀጥታ **አያርትዑ**፤ fragments በrelease ጊዜ አንድ ላይ ይሰበሰባሉ እና በPRs መካከል ፈጽሞ አይጋጩም +- [ ] በተጠቃሚ ላይ ለሚታዩ ለውጦች የChangelog **fragment** በ`changelog.d/{features|fixes|maintenance}/-.md` ስር ታክሏል ([`changelog.d/README.md`](./changelog.d/README.md) ይመልከቱ) — `CHANGELOG.md`ን በቀጥታ **አርትዕ አያድርጉ**፤ fragments በrelease ጊዜ ይሰባሰባሉ እና በPRs መካከል በፍጹም አይጋጩም - [ ] Documentation ዘምኗል (አስፈላጊ ከሆነ) -- [ ] ምንም አዲስ CodeQL / Secret-Scanning alerts አልተከፈቱም፣ ወይም እያንዳንዳቸው ተዛማጁን `docs/security/` doc በሚጠቅስ ቴክኒካዊ ምክንያት ውድቅ ተደርገዋል -- [ ] child processes የሚጀምሩ Routes (`/api/mcp/`, `/api/cli-tools/runtime/`) በ`src/server/authz/routeGuard.ts` ውስጥ እንደ `isLocalOnlyPath()` ተመድበዋል — [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md)ን ይመልከቱ -- [ ] በcommit messages ውስጥ `Co-Authored-By` trailers የሉም — commits በrepository owner Git identity ስር ብቻ መታየት አለባቸው (Hard Rule #16) +- [ ] አዲስ CodeQL / Secret-Scanning alerts አልተከፈቱም፣ ወይም እያንዳንዳቸው ተዛማጅ `docs/security/` docን በሚጠቅስ ቴክኒካዊ ምክንያት ውድቅ ተደርገዋል +- [ ] Child processes የሚጀምሩ routes (`/api/mcp/`, `/api/cli-tools/runtime/`) በ`src/server/authz/routeGuard.ts` ውስጥ እንደ `isLocalOnlyPath()` ተመድበዋል — [ጥብቅ ደንብ #15](docs/security/ROUTE_GUARD_TIERS.md)ን ይመልከቱ +- [ ] በcommit messages ውስጥ የAI/bot `Co-authored-by` trailers የሉም (ጥብቅ ደንብ #16) — ሥራቸው እንደገና ጥቅም ላይ የዋለ ሰብዓዊ collaborators በመደበኛ `Co-authored-by: Name ` trailers እውቅና ያገኛሉ --- diff --git a/docs/i18n/am/docs/architecture/QUALITY_GATES.md b/docs/i18n/am/docs/architecture/QUALITY_GATES.md index 028fbaf7cc6b..4011984a257c 100644 --- a/docs/i18n/am/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/am/docs/architecture/QUALITY_GATES.md @@ -15,206 +15,212 @@ --- -## የጌቶች ዝርዝር እና የአፈጻጸም መገለጫዎች +## የጌት ዝርዝር እና የአፈጻጸም መገለጫዎች -### የዕጩ ቅበላ +### የእጩ ተቀባይነት -የCI እና Quality Gates የስራ ፍሰቶች እያንዳንዳቸው የማይለዋወጥ ውሳኔ ያወጣሉ፦ `Gate / CI` እና -`Gate / Quality`። ስሪት የተሰጠው የቅበላ ፖሊሲያቸው እያንዳንዱን ቀዳሚ ስራ +የCI እና Quality Gates የስራ ፍሰቶች እያንዳንዳቸው የማይለወጥ ውሳኔ ያወጣሉ፦ `Gate / CI` እና +`Gate / Quality`። በስሪት የሚተዳደረው የተቀባይነት ፖሊሲያቸው እያንዳንዱን የላይኛው ደረጃ ስራ እንደ አስፈላጊ ወይም አማካሪ ይዘረዝራል። ተፈጻሚ የሆነ አስፈላጊ ስራ መሳካት አለበት፦ የጎደሉ፣ የተሰረዙ፣ የታለፉ፣ በመጠባበቅ ላይ ያሉ እና ያልታወቁ ውጤቶች PASSን ማረጋገጥ አይችሉም። ትክክለኛ -የdocs-only ወይም catalog-only ምደባ የኮድ መስመርን ተፈጻሚ ያልሆነ ሊያደርገው ይችላል፤ -ረቂቅ PR ተቀባይነት ያለው ዕጩ አይደለም። የ`hotfix` መለያ የማስረጃ ግዴታን አያስቀርም። +docs-only ወይም catalog-only ምደባ የኮድ መስመርን ተፈጻሚ ያልሆነ ሊያደርገው ይችላል፤ +ረቂቅ PR ተቀባይነት ያገኘ እጩ አይደለም። የ`hotfix` መለያ ማስረጃን አያስቀርም። -ሁለቱም የስራ ፍሰቶች PRዎችን፣ ወደ main/release ቅርንጫፎች የሚደረጉ pushዎችን፣ manual dispatchን እና -merge-group ክስተቶችን ይሸፍናሉ። Push፣ dispatch እና merge-group ሙሉ ምርጫውን ያስኬዳሉ። Forkዎች -እና merge groupዎች በሌላ ሁኔታ self-hosted runnerዎችን ለሚመርጡ ስራዎች hosted runnerዎችን ይጠቀማሉ፤ -ከመዘርጋቱ በፊት በቂ hosted አቅም መኖሩ መረጋገጥ አለበት። +ሁለቱም የስራ ፍሰቶች PRዎችን፣ ወደ main/release ቅርንጫፎች የሚደረጉ pushesን፣ manual dispatchን እና +merge-group ክስተቶችን ይሸፍናሉ። Push፣ dispatch እና merge-group ሙሉውን ምርጫ ያስኬዳሉ። Forks +እና merge groups በሌላ ሁኔታ self-hosted runnersን ለሚመርጡ ስራዎች hosted runnersን ይጠቀማሉ፤ +ከማሰማራት በፊት በቂ hosted አቅም መኖሩ መረጋገጥ አለበት። -እያንዳንዱ JSON ደረሰኝ የተፈተሸውን SHA፣ የስራ ፍሰት አሂድ እና ሙከራ ይለያል። -CLIው በcheckout/event SHA መካከል ያለውን አለመጣጣም ውድቅ ያደርጋል። የስራ ፍሰት ሙከራዎች የፖሊሲ አባልነትን -ከውሳኔ ስራው `needs` ዝርዝር ጋር ያስተሳስራሉ፣ ስለዚህ አዲስ ወይም የተወገደ መስመር በጸጥታ ሊጠፋ አይችልም። -ደረሰኞቹ የሚሸፍኑት የራሳቸውን የስራ ፍሰት እንጂ ህትመትን፣ deploymentን ወይም የአንድ ነባር አማካሪ scanner ውስጣዊ አሰራርን -አይደለም። ሁለቱንም የፍተሻ ስሞች በቅርንጫፍ ደንቦች ውስጥ ማንቃት የተለየ አስተዳደራዊ ለውጥ ነው፤ እነዚህን ስራዎች ማከል በራሱ ቅርንጫፍን አይጠብቅም። +እያንዳንዱ JSON ደረሰኝ checked-out SHAን፣ workflow runን እና attemptን ይለያል። +CLIው የcheckout/event SHA አለመዛመድን ውድቅ ያደርጋል። የስራ ፍሰት ሙከራዎች የፖሊሲ አባልነትን +ከውሳኔ ስራው `needs` ዝርዝር ጋር ያስተሳስራሉ፣ ስለዚህ አዲስ ወይም የተወገደ መስመር በዝምታ ሊጠፋ አይችልም። +ደረሰኞቹ የሚሸፍኑት የራሳቸውን የስራ ፍሰት እንጂ ህትመትን፣ deploymentን ወይም የነባር አማካሪ scanner ውስጣዊ +አሰራሮችን አይደለም። ሁለቱንም የcheck ስሞች በbranch rules ውስጥ ማንቃት የተለየ አስተዳደራዊ ለውጥ ነው፤ +እነዚህን ስራዎች መጨመር በራሱ ቅርንጫፍን አይጠብቅም። ### የስታቲክ ቅኝት ዝርዝር -ስሪት የተሰጠው የnpm-alias ዝርዝር እና የስታቲክ ቅኝት አባልነት በ -`config/quality/gate-manifest.json` ውስጥ ይገኛሉ። የscript ስሞችን እና ትክክለኛ ትዕዛዞችን ከ`package.json` ጋር ለማረጋገጥ -`npm run check:gate-manifest`ን ያስኪዱ፤ መጨመር፣ ማስወገድ እና -የትዕዛዝ ለውጥ በCI ውስጥ ያሉትን የአካባቢ hook እና የለውጥ-ምደባ ስራዎች ሁለቱንም እንዲወድቁ ያደርጋሉ። -alias የስራ ፍሰት ስራ፣ matrix instance ወይም test case አይደለም፦ እነዚህ ቁጥሮች -እርስ በርስ እንደሚተካኩ መቅረብ የለባቸውም። - -የተመረጡትን aliasዎች ሳያስኬዷቸው ለመመርመር `npm run quality:scan -- --list` ወይም `npm run quality:scan:fast -- --list` -ይጠቀሙ። runnerው -የnpm entrypointን ይጠራል፣ ስለዚህ runtimeው (በተዋቀረበት ቦታ Bunን ጨምሮ) እንደተጠበቀ ይቆያል። -manifestው ከእነዚያ መገለጫዎች ውጭ ያሉ aliasዎችን በተለየ ሁኔታ የሚጠሩ ብሎ ይመዘግባል፣ እና +በስሪት የሚተዳደረው የnpm-alias ዝርዝር እና የstatic-scan አባልነት በ +`config/quality/gate-manifest.json` ውስጥ ይገኛሉ። የscript ስሞችን እና ትክክለኛ ትዕዛዞችን ከ`package.json` ጋር +ለማረጋገጥ `npm run check:gate-manifest`ን ያስኪዱ፤ መጨመሮች፣ ማስወገዶች እና +የትዕዛዝ ለውጦች ሁለቱንም local hook እና በCI ውስጥ ያሉትን የchange-classification ስራዎች ያሳክታሉ። +alias የworkflow ስራ፣ matrix instance ወይም test case አይደለም፦ እነዚህ ቁጥሮች +በአንድ ዓይነት ሊቀርቡ አይገባም። + +የተመረጡትን aliases ሳያስኬዷቸው ለመመርመር `npm run quality:scan -- --list` ወይም `npm run quality:scan:fast -- --list`ን +ይጠቀሙ። runnerው የnpm entrypointን ይጠራል፣ ስለዚህ runtimeው (በተዋቀረበት ቦታ Bunን ጨምሮ) እንዳለ ይጠበቃል። +manifestው ከእነዚያ መገለጫዎች ውጭ ያሉ aliasesን በተናጠል እንደሚጠሩ ይመዘግባል፣ እና የጥገና ትዕዛዞች በread-only የቅኝት መገለጫዎች ውስጥ የተከለከሉ ናቸው። -እነዚህ መገለጫዎች የሚሸፍኑት የስታቲክ ቅኝቱን ብቻ ነው። የምርት ሙከራዎችን፣ -coverageን፣ packagingን፣ ውጫዊ ፍተሻዎችን ወይም የዕጩን ሙሉ የrelease ቅበላ አያረጋግጡም። -የስራ ፍሰት ቅበላ የተገናኙትን `config/quality/admission-policy.json` እና +እነዚህ መገለጫዎች የሚሸፍኑት የስታቲክ ቅኝትን ብቻ ነው። የምርት ሙከራዎችን፣ +coverageን፣ packagingን፣ ውጫዊ checksን ወይም የእጩን ሙሉ የrelease ተቀባይነት አያረጋግጡም። +የስራ ፍሰት ተቀባይነት የተገናኘውን `config/quality/admission-policy.json` እና `scripts/quality/admission-verdict.mjs` ይጠቀማል። የRelease-observer መገለጫዎች ተለይተው ይቆያሉ፤ -ተፈጻሚ ፍተሻዎቻቸውን እና ደረሰኞቻቸውን በተናጠል ይመርምሩ። ከታች ያለው የጽሑፍ -ዝርዝር ማጣቀሻ እንጂ አንድ ጌት በእርግጥ እንደተከናወነ ማረጋገጫ አይደለም። +ተፈጻሚ checksና ደረሰኞቻቸውን በተናጠል ይመርምሩ። ከታች ያለው ገላጭ +ዝርዝር ማጣቀሻ እንጂ አንድ gate በትክክል እንደተሰራ ማረጋገጫ አይደለም። -Scriptዎች በ`scripts/check/` (የፖሊሲ ጌቶች) እና `scripts/quality/` (ratchet engine) ስር ይገኛሉ። +Scripts በ`scripts/check/` (የፖሊሲ gates) እና `scripts/quality/` (ratchet engine) ስር ይገኛሉ። የCI ዋና የእውነት ምንጭ `.github/workflows/ci.yml` ነው። ### የRelease PR ፈጣን መንገድ (`quality.yml`) -`.github/workflows/quality.yml` በmain/release PRዎች፣ protected-branch -pushዎች፣ dispatch እና merge groupዎች ላይ CIን ያሟላል። PRዎች በpath የተጣሩ ፈጣን ፍተሻዎችን ይጠቀማሉ። በቋሚነት -የተሰናከለው የተባዛ build ተወግዷል፤ እውነተኛዎቹ የbuild/package/boot ፍተሻዎች በCI ውስጥ ይቀራሉ። +`.github/workflows/quality.yml` በmain/release PRዎች፣ ወደ የተጠበቁ ቅርንጫፎች በሚደረጉ +pushes፣ dispatch እና merge groups ላይ CIን ያሟላል። PRዎች በpath የተጣሩ ፈጣን checksን ይጠቀማሉ። በቋሚነት +የተሰናከለው የተባዛ build ተወግዷል፤ እውነተኛዎቹ build/package/boot checks በCI ውስጥ ይቀራሉ። -| ስራ | ወሰን | አጋጅ | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | -| `Docs Gates (fast-path)` | Docs/code PRዎች፤ የAPI docs refs እና docs-all | አዎ | -| `Fast Quality Gates` | የCode PRዎች፤ static checks፣ typecheck፣ dashboard typecheck፣ ተጽዕኖ ያረፈባቸው unit tests | አዎ | -| `Forgotten sibling tests` | የCode PRዎች፤ የተቀየሩ modules ወደ static consumers እና candidate sibling tests ይከታተላሉ፤ barrel እና dynamic-import paths እንደ አማካሪ diagnostics ሪፖርት ይደረጋሉ፣ ከተጠቀሱ allowlist exceptions ጋር | **አማካሪ** | -| `Vitest (fast-path)` | የCode PRዎች፤ ፈጣን vitest suite | አዎ | -| `Unit Tests fast-path` | የCode PRዎች፤ 4-shard unit suite | አዎ | -| `No new ESLint warnings` | የCode PRዎች፤ suppressions-aware lint guard | አዎ፣ forkዎችን ጨምሮ | -| `Merge integrity (changelog + generated skills)` | ረቂቅ ያልሆኑ PRዎች፤ changelog እና generated skill sync | አዎ፣ forkዎችን ጨምሮ | +| ስራ | ወሰን | አጋጅ | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `Docs Gates (fast-path)` | Docs/code PRዎች፤ የAPI docs refs እና docs-all | አዎ | +| `Fast Quality Gates` | Code PRዎች፤ static checks፣ typecheck፣ dashboard typecheck፣ ተጽዕኖ የደረሰባቸው unit tests | አዎ | +| `Forgotten sibling tests` | Code PRዎች፤ የተለወጡ modules ወደ static consumers እና candidate sibling tests ይከታተላሉ፤ barrel እና dynamic-import paths ከተጠቀሱት allowlist exceptions ጋር እንደ አማካሪ diagnostics ይመዘገባሉ | **አማካሪ** | +| `Vitest (fast-path)` | Code PRዎች፤ ፈጣን vitest suite | አዎ | +| `Unit Tests fast-path` | Code PRዎች፤ ባለ4-shard unit suite | አዎ | +| `No new ESLint warnings` | Code PRዎች፤ suppressions-aware lint guard | አዎ፣ forksን ጨምሮ | +| `Merge integrity (changelog + generated skills)` | ረቂቅ ያልሆኑ PRዎች፤ changelog እና generated skill sync | አዎ፣ forksን ጨምሮ | #### የተረሱ sibling tests ሪፖርት -`npm run check:forgotten-sibling-tests` ከtest-impact map በስተጀርባ ያለውን import resolver እንደገና ይጠቀማል። -ለእያንዳንዱ የተቀየረ production module፣ candidate -test በpull-request diff ውስጥ በማይኖርበት ጊዜ ቁርጥ የሆኑ -`changed module/symbol -> static consumer -> candidate sibling test` ሰንሰለቶችን ሪፖርት ያደርጋል። የMarkdown ማጠቃለያው እና JSON ውጤቱ ከማንኛውም አጋጅ ማስጀመሪያ በፊት ለመለኪያ -እንደ `forgotten-sibling-tests` የስራ ፍሰት artifact ተይዘው ይቆያሉ። +`npm run check:forgotten-sibling-tests` ከtest-impact map ጀርባ ያለውን import resolver እንደገና ይጠቀማል። +ለእያንዳንዱ የተለወጠ production module፣ እጩው +test በpull-request diff ውስጥ በማይኖርበት ጊዜ ወጥነት ያላቸውን `changed module/symbol -> static consumer -> candidate sibling test` ሰንሰለቶች ይመዘግባል። +የMarkdown ማጠቃለያው እና JSON ውጤቱ ማንኛውም አጋጅ ማሰማራት ከመደረጉ በፊት ለcalibration እንደ +`forgotten-sibling-tests` የworkflow artifact ተይዘው ይቆያሉ። -Barrel ዳግም-ወደ-ውጭ-መላኮች እና dynamic imports የresolution ምርመራዎች ብቻ ናቸው፤ blocking finding ፈጽሞ አይፈጥሩም። የተገመገሙ ልዩ ሁኔታዎች በ +Barrel re-exports እና dynamic imports የresolution ምርመራዎች ብቻ ናቸው፤ መቼም +የሚያግድ ግኝት አይፈጥሩም። የተገመገሙ ልዩ ሁኔታዎች በ `config/quality/forgotten-sibling-allowlist.json` ውስጥ ይገኛሉ። እያንዳንዱ ግቤት consumer-ን እና candidate -test-ን መጥቀስ፣ ዝርዝር ምክንያት መስጠት እና ወደ GitHub issue ወይም pull request ማገናኘት አለበት። ቅርጸታቸው የተበላሸ ግቤቶች -በነባሪነት ውድቅ ይደረጋሉ። ልዩ ሁኔታዎች የተሰረዘ candidate test-ን ወይም `.skip`/`.todo` የሚጨምር diff-ን -ማፈን አይችሉም፤ assertion ማዳከም እና ሌሎች masking ድርጊቶች ራሱን ችሎ blocking በሆነው -`check:test-masking` gate ሥር ይቆያሉ። +test-ን መጥቀስ፣ የተወሰነ ምክንያት መስጠት እና ወደ GitHub issue ወይም pull request ማገናኘት አለበት። ቅርጸታቸው የተበላሸ ግቤቶች +በነባሪነት ይከለከላሉ። ልዩ ሁኔታዎች የተሰረዘ candidate test-ን ወይም `.skip`/`.todo` የሚጨምር diff-ን ማፈን አይችሉም፤ +assertion weakening እና ሌሎች masking ዘዴዎች አሁንም ራሱን ችሎ የሚያግደው +`check:test-masking` gate ኃላፊነት ናቸው። ### ሥራ፦ `lint` -ወደ `main` በሚደረግ እያንዳንዱ PR ላይ ይሠራል። ካልተሳካ merge እንዳይደረግ ያግዳል። - -| ስክሪፕት (`npm run ...`) | የሚያረጋግጠው | አጋጅ | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `check:node-runtime` | የNode.js ስሪት በሚደገፈው ክልል ውስጥ መሆኑን | አዎ | -| `check:cycles` | ዑደታዊ imports — ሁሉም የ`src/` + `open-sse/` modules | አዎ | -| `check:route-validation:t06` | በሁሉም routes ላይ የZod schemas መኖር (Tier 6 policy) | አዎ | -| `check:any-budget:t11` | የ`@ts-expect-error // any` ብዛት ከተመደበው ገደብ አለማለፉን (Tier 11 catraca) | አዎ | -| `check:provider-consistency` | `providers.ts` ውስጥ ያለው እያንዳንዱ provider በ`providerRegistry.ts` ውስጥ ተዛማጅ ግቤት አለው (እና በተቃራኒውም፣ በallowlist ወሰን ውስጥ) | አዎ | -| `check:model-lifecycle` | በእጅ የሚጠበቁት ሦስቱ routing tables ከተካተተው lifecycle snapshot (#11503) ጋር ወጥነታቸውን ይጠብቃሉ፦ `FITNESS_TABLE` (`taskFitness.ts`) `REGISTRY` ሊያዞረው የሚችል retired id ለአንዳቸውም score አይሰጥም፤ እያንዳንዱ የ`BUILT_IN_ALIASES` target በ`REGISTRY` ውስጥ ይገኛል እና በretired-id snapshot ውስጥ አይገኝም፤ በ`REGISTRY` ውስጥ አሁንም ያለ እያንዳንዱ retired id ወደ ሌላ ቦታ ይተላለፋል ወይም በ`allowedRetiredInCatalog` ውስጥ ተዘርዝሯል፤ እንዲሁም በዚያ snapshot ውስጥ አንድም የ`DEFAULT_DEGRADATION_MAP` source ወይም target እንደ retired አይታይም። ይህ አንድ model በአሁኑ ጊዜ በቀጥታ upstream እየቀረበ መሆኑን አያረጋግጥም። Offline — በእጅ በ`npm run quality:refresh-model-lifecycle` (network፤ ከCI ጋር አልተያያዘም) ከሚታደሰው `config/quality/model-lifecycle.json` ጋር ያነጻጽራል። `allowedRetiredInCatalog` ቀስ በቀስ የሚቀንስ ratchet ነው፦ ግቤት የሚጨመረው ከtracking issue ጋር ብቻ ነው። | አዎ | -| `check:fetch-targets` | በclient-side `src/` ውስጥ ያለ እያንዳንዱ `fetch("/api/...")` ወደ እውነተኛ `route.ts` ይፈታል | አዎ | -| `check:deps` | በrepo ውስጥ ባለ እያንዳንዱ `package.json` ውስጥ የሚገኙ ሁሉም በ`npm install` ሊጫኑ የሚችሉ deps በ`dependency-allowlist.json` ውስጥ አሉ፤ አዲስ unpinned ወይም slopsquatted packages ምልክት ይደረግባቸዋል | አዎ | -| `audit:deps` | `npm audit` (root + electron) — high/critical advisories የሉም (ከosv `check:vuln-ratchet` ጋር ይደራረባል፤ Rationalization Backlogን ይመልከቱ) | አዎ | -| `check:lockfile` | የ`package-lock.json` integrity — https registry፣ integrity hashes፣ ምንም host overrides የሉም | አዎ | -| `check:licenses` | ለምርት ጥገኞች የSPDX ፈቃድ የተፈቀዱ ዝርዝር | አዎ | -| `check:tracked-artifacts` | ምንም የግንባታ አርቲፋክቶች / ወደ commit የተደረጉ `node_modules` symlinks የሉም (በhusky pre-commit ውስጥም ይሰራል፤ pre-push ሆን ተብሎ ቀላል ተደርጓል — #6716) | አዎ | -| `check:ai-attribution` | በPR commits፣ title ወይም body ውስጥ የAI/bot `Co-Authored-By` trailer ወይም የAI ማመንጨት footer የለም — ጥብቅ ደንብ #16 (በ`quality.yml` ፈጣን መግቢያ-ፍተሻዎች loop ውስጥ ለPR→`release/**` — የevent payloadን ያነባል፣ ከPR ውጭ ምንም አያደርግም — እንዲሁም በ`ci.yml` lint ውስጥ ለPR→`main` የPR-ብቻ step፤ በተጨማሪም የhusky `commit-msg` hook፤ ሰው co-authors ይፈቀዳሉ፤ #14436) | -| `check:vitest-exclusions` | እያንዳንዱ የVitest ማግለያ tracking issueን ይጠቅሳል እና በ`config/quality/vitest-exclusions.json` ውስጥ ይገኛል (#13204) | አዎ | -| `check:file-size` | ምንም የምንጭ ፋይል በቅጥያ የተወሰነውን ከፍተኛ ገደብ አያልፍም (ratchet፦ የታሰሩ ትልልቅ ፋይሎች በ`frozen` ዝርዝር ውስጥ) | አዎ | -| `check:error-helper` | በexecutors/handlers ውስጥ ያሉ የስህተት ምላሾች `buildErrorBody()` / `sanitizeErrorMessage()`ን ይጠቀማሉ (ጥብቅ ደንብ #12) | አዎ | -| `check:migration-numbering` | የማይግሬሽን SQL ፋይሎች ያለ ክፍተት ወይም ድግግሞሽ በተከታታይ ተቆጥረዋል | አዎ | -| `check:public-creds` | ከ`publicCreds.ts` ውጭ ቀጥተኛ የOAuth `client_id`/`client_secret` ወይም የFirebase Web ቁልፎች የሉም (ጥብቅ ደንብ #11) | አዎ | -| `check:db-rules` | ከ`src/lib/db/` ሞጁሎች ውጭ ያልተጣራ SQL የለም፤ ከ`localDb.ts` የbarrel-import ማስመጣቶች የሉም (ጥብቅ ደንቦች #2/#5) | አዎ | -| `check:known-symbols` | በdispatch ሰንጠረዦቻቸው ውስጥ የተመዘገቡ የአቅራቢ አስፈጻሚዎች፣ የማዘዋወሪያ ስልቶች እና ተርጓሚዎች በዲስክ ላይ ካሉት ፋይሎች ጋር ይዛመዳሉ—ወላጅ አልባ ወይም ያልታወጁ symbols የሉም | አዎ | -| `check:route-guard-membership` | የልጅ process የሚፈጥር እያንዳንዱ route በ`isLocalOnlyPath()` ተመድቧል (ጥብቅ ደንቦች #15/#17) | አዎ | -| `check:test-discovery` | በrepo ውስጥ ያለ እያንዳንዱ `*.test.ts` / `*.spec.ts` ፋይል ቢያንስ በአንድ test runner ይሰበሰባል (ratchet፦ በ`test-discovery-baseline.json` ውስጥ ያለው የወላጅ አልባ ፋይሎች ዝርዝር ሊያጥር ብቻ ይችላል) | አዎ | -| `check:agent-skills-sync` | የተፈጠሩ የagent-skills አርቲፋክቶች ከምንጭ ካታሎጋቸው ጋር ይዛመዳሉ (ልዩነት የለም) | -| `check:provider-asset-provenance` | የአቅራቢ አርማዎች/ንብረቶች የተመዘገበ የምንጭ መረጃ አላቸው | -| `lint:json` | የJSON ውቅር ፋይሎች በትክክል ይተነተናሉ እና የrepo lint ደንቦችን ያሟላሉ | -| `typecheck:core` | የTypeScript ማጠናቀር ያለ ስህተት (የምክር ማስጠንቀቂያዎች ብቻ) | አዎ | -| `typecheck:noimplicit:core` | ጥብቅ `noImplicitAny` — ወደፊትን ያማከለ፤ አስቀድመው የነበሩ ብዙ የጥሪ ቦታዎች አሁንም ማብራሪያዎችን ይፈልጋሉ | **የምክር** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | ለ`src/app/(dashboard)/**` ብቻ የተገደበ `tsc` (#7033) — የ`typecheck:core` በጥንቃቄ የተመረጠ ባለ27-ፋይል የፈቃድ ዝርዝር ምንም የdashboard TSX አያካትትም፣ እንዲሁም `next build` እሱን በፍጹም የዓይነት ምርመራ አያደርግበትም (`next.config.mjs` `ignoreBuildErrors: true` ያዘጋጃል)፣ ስለዚህ በዚያ ያሉ ብቸኛ-የሆኑ የመለያ መመለስ ስህተቶች (#6625/#6909) ለCI የማይታዩ ነበሩ። ከቀዘቀዘ በየፋይሉ/በየTS-code የቁጥር መነሻ መለኪያ (`config/quality/dashboard-typecheck-baseline.json`፣ እንደ `check:known-symbols` ተመሳሳይ የጊዜ ያለፈበትን የማስፈጸም ንድፍ) ጋር ልዩነቶች ይነጻጸራሉ — ከመነሻ መለኪያው ቁጥር በላይ ያሉ አዲስ ስህተቶች ብቻ መግቢያውን ያሳንፋሉ፤ አስቀድሞ የነበረ ስህተት ሲስተካከል በ`--update` በመጠቀም መነሻ መለኪያውን ዝቅ ያድርጉ። | አዎ | +ወደ `main` በሚቀርብ እያንዳንዱ PR ላይ ይሰራል። ካልተሳካ merge እንዳይደረግ ያግዳል። + +| Script (`npm run ...`) | የሚያረጋግጠው | አጋጅ | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | የNode.js ስሪት በሚደገፈው ክልል ውስጥ መሆኑን | አዎ | +| `check:cycles` | በሁሉም `src/` + `open-sse/` ውስጥ ያሉ circular imports (በAST ላይ የተመሠረተ፣ የtsconfig `paths` የተፈቱ)። ብቻውን ሲሰራ = ምክር ሰጪ ሲሆን cycles-ን ይዘረዝራል። `check:cycles:ratchet` (CI የሚያስኬደው) ብዛቱ በ`quality-baseline.json` ውስጥ ያለውን የ`metrics.cycles` ጣሪያ ሲያልፍ ያግዳል — በአሁኑ ጊዜ 14፣ `direction: down` ስለሆነ መቀነስ ብቻ ይችላል (#15159 G-01/G-02) | አዎ (ratchet) | +| `check:route-validation:t06` | በሁሉም routes ላይ የZod schemas መኖራቸውን (Tier 6 policy) | አዎ | +| `check:any-budget:t11` | የ`@ts-expect-error // any` ብዛት ከተፈቀደው budget እንደማይበልጥ (Tier 11 catraca) | አዎ | +| `check:provider-consistency` | `providers.ts` ውስጥ ያለው እያንዳንዱ provider `providerRegistry.ts` ውስጥ ተዛማጅ ግቤት አለው (እንዲሁም በተቃራኒው፣ በallowlist ውስጥ) | አዎ | +| `check:model-lifecycle` | ሦስቱ በእጅ የሚጠበቁ routing table-ዎች ከተመዘገበው lifecycle snapshot (#11503) ጋር ወጥነታቸውን ይጠብቃሉ፦ `FITNESS_TABLE` (`taskFitness.ts`)፣ `REGISTRY` route ሊያደርገው ለሚችል ከአገልግሎት የወጣ id ነጥብ አይሰጥም፤ እያንዳንዱ `BUILT_IN_ALIASES` target በ`REGISTRY` ውስጥ ይገኛል እና ከretired-id snapshot ውስጥ አይገኝም፤ አሁንም `REGISTRY` ውስጥ ያለ እያንዳንዱ ከአገልግሎት የወጣ id ወደ ሌላ ይተላለፋል ወይም በ`allowedRetiredInCatalog` ውስጥ ይዘረዘራል፤ እንዲሁም ምንም የ`DEFAULT_DEGRADATION_MAP` source ወይም target በዚያ snapshot ውስጥ ከአገልግሎት እንደወጣ አይታይም። ይህ፣ አንድ model በአሁኑ ጊዜ በቀጥታ upstream እየቀረበ መሆኑን አያረጋግጥም። Offline — በ`config/quality/model-lifecycle.json` ላይ በማነጻጸር ይሰራል፤ ይህም በእጅ `npm run quality:refresh-model-lifecycle` በመጠቀም ይታደሳል (network፤ ከCI ጋር አልተገናኘም)። `allowedRetiredInCatalog` የburn-down ratchet ነው፦ ግቤት ያክሉ ከtracking issue ጋር ብቻ። | አዎ | +| `check:fetch-targets` | በclient-side `src/` ውስጥ ያለ እያንዳንዱ `fetch("/api/...")` ወደ እውነተኛ `route.ts` ይፈታል | አዎ | +| `check:deps` | በrepo ውስጥ ባለው እያንዳንዱ `package.json` ውስጥ `npm install` ሊደረጉ የሚችሉ ሁሉም deps በ`dependency-allowlist.json` ውስጥ አሉ፤ አዲስ unpinned ወይም slopsquatted package-ዎች ምልክት ይደረግባቸዋል | አዎ | +| `audit:deps` | `npm audit` (root + electron) — ከፍተኛ/ወሳኝ advisories የሉም (ከosv `check:vuln-ratchet` ጋር ይደራረባል፤ Rationalization Backlogን ይመልከቱ) | አዎ | +| `check:lockfile` | የ`package-lock.json` ትክክለኛነት — https registry፣ integrity hashes፣ ምንም host overrides የሉም | አዎ | +| `check:licenses` | ለምርት ጥገኞች የSPDX ፈቃድ የተፈቀዱ ዝርዝር | አዎ | +| `check:tracked-artifacts` | ምንም የግንባታ ቅሪቶች / የተመዘገቡ `node_modules` ምሳሌያዊ አገናኞች የሉም (በተጨማሪም በhusky pre-commit ውስጥ ይሰራል፤ pre-push ሆን ተብሎ ቀላል ተደርጓል — #6716) | አዎ | +| `check:ai-attribution` | በPR commits፣ title ወይም body ውስጥ የAI/bot `Co-Authored-By` trailer ወይም የAI ማመንጨት footer የለም — ጥብቅ ደንብ #16 (ለPR→`release/**` በ`quality.yml` fast-gates loop ውስጥ — event payloadን ያነባል፣ PR ካልሆነ no-op ይሆናል — እና ለPR→`main` በ`ci.yml` lint ውስጥ ያለ PR-ብቻ step፤ እንዲሁም የhusky `commit-msg` hook፤ ሰብዓዊ co-authors ይፈቀዳሉ፤ #14436) | +| `check:vitest-exclusions` | እያንዳንዱ የVitest exclusion የመከታተያ issueን ይጠቅሳል እና በ`config/quality/vitest-exclusions.json` ውስጥ ይገኛል (#13204) | አዎ | +| `check:file-size` | ምንም source file በextension የተወሰነውን ከፍተኛ ገደብ አያልፍም (ratchet፦ የተቆለፉ ትልልቅ files በ`frozen` list ውስጥ) | አዎ | +| `check:error-helper` | በexecutors/handlers ውስጥ ያሉ error responses `buildErrorBody()` / `sanitizeErrorMessage()`ን ይጠቀማሉ (ጥብቅ ደንብ #12) | አዎ | +| `check:migration-numbering` | የMigration SQL ፋይሎች ያለ ክፍተት ወይም ድግግሞሽ በቅደም ተከተል ቁጥር ተሰጥቷቸዋል | አዎ | +| `check:public-creds` | ከ`publicCreds.ts` ውጭ ቀጥተኛ የOAuth `client_id`/`client_secret` ወይም የFirebase Web ቁልፎች የሉም (ጥብቅ ደንብ #11) | አዎ | +| `check:db-rules` | ከ`src/lib/db/` ሞጁሎች ውጭ ቀጥተኛ SQL የለም፤ ከ`localDb.ts` የbarrel-imports የሉም (ጥብቅ ደንቦች #2/#5) | አዎ | +| `check:known-symbols` | በdispatch ሰንጠረዦቻቸው ውስጥ የተመዘገቡ የአቅራቢ አስፈጻሚዎች፣ የማዘዋወሪያ ስልቶች እና ተርጓሚዎች በዲስክ ላይ ካሉት ፋይሎች ጋር ይዛመዳሉ — ወላጅ አልባ ወይም ያልታወጁ ምልክቶች የሉም | አዎ | +| `check:route-guard-membership` | ንዑስ ሂደትን የሚጀምር እያንዳንዱ route በ`isLocalOnlyPath()` ተመድቧል (ጥብቅ ደንቦች #15/#17) | አዎ | +| `check:test-discovery` | በrepo ውስጥ ያለ እያንዳንዱ `*.test.ts` / `*.spec.ts` ፋይል ቢያንስ በአንድ የሙከራ አስኪያጅ ይሰበሰባል (ratchet፦ በ`test-discovery-baseline.json` ውስጥ ያለው የወላጅ አልባ ፋይሎች ዝርዝር መቀነስ ብቻ ይችላል) | አዎ | +| `check:agent-skills-sync` | የተፈጠሩ agent-skills አርቲፋክቶች ከምንጭ ካታሎጋቸው ጋር ይዛመዳሉ (ልዩነት የለም) | +| `check:provider-asset-provenance` | የአቅራቢ አርማዎች/ንብረቶች የተመዘገበ የምንጭ መረጃ ግቤት አላቸው | +| `lint:json` | የJSON ውቅር ፋይሎች ያለ ስህተት ይተነተናሉ፣ እንዲሁም የrepo lint ደንቦችን ያሟላሉ | +| `typecheck:core` | የTypeScript ማጠናቀር ያለ ስህተት (የምክር ማስጠንቀቂያዎች ብቻ) | አዎ | +| `typecheck:noimplicit:core` | ጥብቅ `noImplicitAny` — ወደፊትን ያማከለ፤ ከዚህ ቀደም የነበሩ ብዙ የጥሪ ቦታዎች አሁንም ማብራሪያ ያስፈልጋቸዋል | **የምክር ብቻ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | በ`src/app/(dashboard)/**` (#7033) ላይ የተወሰነ `tsc` — የ`typecheck:core` በጥንቃቄ የተመረጠው የ27 ፋይሎች allowlist ምንም dashboard TSX አያካትትም፣ እንዲሁም `next build` እሱን ፈጽሞ type-check አያደርገውም (`next.config.mjs` `ignoreBuildErrors: true` ያዘጋጃል)፤ ስለዚህ በዚያ ያሉ የተተዉ-መለያ መመለሻ ስህተቶች (#6625/#6909) ለCI የማይታዩ ነበሩ። ልዩነቶቹ ከቀዘቀዘው የእያንዳንዱ-ፋይል/የእያንዳንዱ-TS-ኮድ ብዛት መነሻ (`config/quality/dashboard-typecheck-baseline.json`፣ ከ`check:known-symbols` ጋር ተመሳሳይ የጊዜ-ያለፈበት ማስገደጃ ንድፍ) ጋር ይነጻጸራሉ — ከመነሻው ብዛት በላይ ያሉ አዲስ ስህተቶች ብቻ በሩን ያሳንፋሉ፤ ቀድሞ የነበረ ስህተት ሲስተካከል በ`--update` መነሻውን ወደ ታች ያስተካክሉ። | አዎ | ### ስራ፦ `quality-gate` -ከ`test-coverage` በኋላ ይሰራል። ከተሳካ ውህደትን ያግዳል። +ከ`test-coverage` በኋላ ይሰራል። ካልተሳካ ውህደትን ይከለክላል። -| ስክሪፕት | የሚያረጋግጠው | አጋጅ | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | `quality-metrics.json`ን ያመነጫል (የESLint ማስጠንቀቂያዎች ብዛት፣ ከተዋሃደው shard ሪፖርት የተገኘ ሽፋን) | አዎ (ከratchet በፊት የሚከናወን) | -| `quality:ratchet` | በ`quality-baseline.json` ውስጥ ያለው እያንዳንዱ መለኪያ ወደ ኋላ አለመመለሱን ያረጋግጣል (የESLint ማስጠንቀቂያዎች ≤ baseline፤ ሽፋን ≥ baseline) | አዎ | -| `check:duplication` | የኮድ መደጋገም (jscpd@4) በ`quality-baseline.json` ውስጥ ካለው baseline እንዳይበልጥ ያረጋግጣል | አዎ | -| `check:complexity` | የፋይል ደረጃ cyclomatic complexity ከተወሰነው ከፍተኛ ገደብ እንዳይበልጥ ያረጋግጣል (ዋናው ESLint `complexity` + `max-lines-per-function`) | አዎ | -| `check:cognitive-complexity` | የcognitive complexity ratchet (`eslint-plugin-sonarjs`) — የተለየ የESLint ሂደት፤ CI ሁለቱንም አዋህዶ እንደ አንድ `check:complexity-ratchets` ደረጃ ያስኬዳል | አዎ | -| `check:dead-code` | ጥቅም ላይ ያልዋሉ exports / ፋይሎች ratchet (knip) ከbaseline ጋር ሲነጻጸር ወደ ኋላ እንዳይመለስ ያረጋግጣል | አዎ | -| `check:compression-budget` | የመጭመቂያ benchmark በጀት — ለእያንዳንዱ engine የተወሰኑት ዝቅተኛ የtoken ቁጠባ ገደቦች ወደ ኋላ መመለስ የለባቸውም | አዎ | -| `check:type-coverage` | የpercent-typed ratchet (`type-coverage`) ወደ ኋላ እንዳይመለስ ያረጋግጣል፤ በአብዛኛው `typecheck:noimplicit:core`ን ያካትታል | አዎ | -| `check:codeql-ratchet` | የተከፈቱ CodeQL ማንቂያዎች ብዛት ወደ ኋላ እንዳይመለስ ያረጋግጣል (በ`gh api` ያነባል፤ token ከሌለ ያለ ስህተት ይዘላል) — የማደሻ ድግግሞሽና በእጅ ማስጀመር፦ ከታች ያለውን "CodeQL ratchet" ይመልከቱ | አዎ | +| ስክሪፕት | የሚያረጋግጠው | የሚያግድ | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | `quality-metrics.json` ያመነጫል (የESLint ማስጠንቀቂያዎች ብዛት፣ ከተዋሃደው shard ሪፖርት የተገኘ ሽፋን) | አዎ (ከratchet በፊት የሚከናወን) | +| `quality:ratchet` | በ`quality-baseline.json` ውስጥ ያለው እያንዳንዱ መለኪያ ወደ ኋላ አለመመለሱን ያረጋግጣል (የESLint ማስጠንቀቂያዎች ≤ መነሻ መስመር፤ ሽፋን ≥ መነሻ መስመር) | አዎ | +| `check:duplication` | የኮድ መደጋገም (jscpd@4) በ`quality-baseline.json` ውስጥ ካለው መነሻ መስመር አይበልጥም | አዎ | +| `check:complexity` | የፋይል ደረጃ cyclomatic complexity ከገደቡ አይበልጥም (ዋናው ESLint `complexity` + `max-lines-per-function`) | አዎ | +| `check:cognitive-complexity` | የግንዛቤ ውስብስብነት ratchet (`eslint-plugin-sonarjs`) — የተለየ የESLint ማለፊያ፤ CI ሁለቱንም በአንድ `check:complexity-ratchets` ደረጃ ውስጥ አዋህዶ ያስኬዳል | አዎ | +| `check:dead-code` | ጥቅም ላይ ያልዋሉ exports / ፋይሎች ratchet (knip) ከመነሻ መስመሩ አንጻር ወደ ኋላ አይመለስም | አዎ | +| `check:compression-budget` | የመጭመቂያ ቤንችማርክ በጀት — ለእያንዳንዱ engine የtoken ቁጠባ ዝቅተኛ ገደቦች ወደ ኋላ መመለስ የለባቸውም | አዎ | +| `check:type-coverage` | በዓይነት የተገለጸው መቶኛ ratchet (`type-coverage`) ወደ ኋላ አይመለስም፤ `typecheck:noimplicit:core`ን በአብዛኛው ያካትታል | አዎ | +| `check:codeql-ratchet` | የተከፈቱ CodeQL ማንቂያዎች ብዛት ወደ ኋላ አይመለስም (በ`gh api` ያነባል፤ token ከሌለ ችግር ሳይፈጥር ይዘላል) — የማደሻ ድግግሞሽ እና በእጅ ማስጀመሪያ፦ ከታች ያለውን "CodeQL ratchet" ይመልከቱ | አዎ | ### ሥራ፦ `quality-extended` -ጠቅላላው ሥራ የምክር ብቻ ነው (`continue-on-error: true`)። በnpm ላይ የተመሠረቱ ratchets በተጨባጭ -ይሰራሉ፤ ውጫዊ scanners በ`gh release download` በኩል ይጫናሉ፣ binary አሁንም ከሌለም -ራሳቸውን ይዘላሉ (exit 0)። +ሙሉው ሥራ አማካሪ ነው (`continue-on-error: true`)። በnpm ላይ የተመሠረቱ ratchets በተግባር +ይሠራሉ፤ ውጫዊ scanners በ`gh release download` በኩል ይጫናሉ፣ binary አሁንም ከሌለም +በራሳቸው ይዘላሉ (exit 0)። -| ስክሪፕት | የሚያረጋግጠው | አጋጅነት | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:circular-deps` | ምንም ዓይነት ዑደታዊ dependencies አለመኖራቸውን ያረጋግጣል (dpdm) | **የምክር ብቻ** | -| `check:bundle-size` | የbundle መጠን ከተወሰነው ከፍተኛ ገደብ እንዳይበልጥ ያረጋግጣል | **የምክር ብቻ** | -| `check:secrets` | የሚስጥር መረጃ ቅኝት (gitleaks) — binary ከሌለ ይዘላል | **የምክር ብቻ** | -| `check:vuln-ratchet` | የdependency ተጋላጭነቶች (osv-scanner) ወደ ኋላ እንዳይመለሱ ያረጋግጣል — binary ከሌለ ይዘላል | **የምክር ብቻ** | -| `check:workflows` | የworkflow lint (actionlint + zizmor)፤ የጠፉ/የተበላሹ scanners፣ ልክ ያልሆኑ ሪፖርቶች ወይም የጠፋ ratchet baseline እንደ INCOMPLETE ያስከሽፋሉ። ትክክለኛ ግኝቶች የተመረጠውን strict/advisory/ratchet ፖሊሲ ይከተላሉ | ማስኬድ ያስፈልጋል፤ zizmor ratchet በCI ውስጥ አጋጅ ነው | -| `check:openapi-breaking` | በbase branch ውስጥ ካለው ጋር ሲነጻጸሩ በይፋዊው API ውል (`openapi.yaml`) ላይ ያሉ breaking changes (oasdiff) — `openapiBreaking=N`ን ያመነጫል፤ oasdiff ከሌለ ወይም base spec ሊፈታ ካልቻለ ይዘላል | **የምክር ብቻ** | +| ስክሪፕት | የሚያረጋግጠው | የሚያግድ | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:circular-deps` | ምንም ዑደታዊ dependencies የሉም (dpdm) | **አማካሪ** | +| `check:bundle-size` | የbundle መጠን ከገደቡ አይበልጥም | **አማካሪ** | +| `check:secrets` | የምስጢሮች ቅኝት (gitleaks) — binary ከሌለ ይዘላል | **አማካሪ** | +| `check:vuln-ratchet` | የdependency ተጋላጭነቶች (osv-scanner) ወደ ኋላ አይመለሱም — binary ከሌለ ይዘላል | **አማካሪ** | +| `check:workflows` | የworkflow lint (actionlint + zizmor)፤ የጠፉ/የተበላሹ scanners፣ ልክ ያልሆኑ ሪፖርቶች ወይም የጎደለ ratchet መነሻ መስመር INCOMPLETE በሚል እንዲወድቅ ያደርጋሉ። ትክክለኛ ግኝቶች የተመረጠውን strict/advisory/ratchet policy ይከተላሉ | መፈጸም ያስፈልጋል፤ zizmor ratchet በCI ውስጥ የሚያግድ ነው | +| `check:openapi-breaking` | ከመሠረታዊው branch (oasdiff) ጋር ሲነጻጸር በይፋዊው API contract (`openapi.yaml`) ላይ ያሉ አፍራሽ ለውጦች — `openapiBreaking=N` ያመነጫል፤ oasdiff ከሌለ ወይም መሠረታዊው spec ሊፈታ ካልቻለ ይዘላል | **አማካሪ** | ### ሥራ፦ `docs-sync-strict` -ወደ `main` በሚቀርብ እያንዳንዱ PR ላይ ይሰራል። ካልተሳካ ውህደትን ያግዳል። - -| ስክሪፕት | የሚያረጋግጠው | አጋጅ | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `check:docs-all` | ከታች ያሉትን 6 ንዑስ-ጌቶች በቅደም ተከተል የሚያስኬድ ዋና ጌት | አዎ | -| ↳ `check:docs-sync` | የCHANGELOG / OpenAPI / llm.txt ስሪቶች ወጥነት | አዎ | -| ↳ `check:docs-counts` | በጽሑፍ ውስጥ ያሉ ቁጥሮች (የአቅራቢዎች ብዛት፣ የሽግግሮች ብዛት፣ ወዘተ) ከትክክለኛዎቹ ቁጥሮች የratchet ክልል ውስጥ መሆናቸውን | አዎ | -| ↳ `check:env-doc-sync` | በ`.env.example` ውስጥ ያለ እያንዳንዱ env var በሰነዶች ሰንጠረዥ ውስጥ መመዝገቡን፣ እንዲሁም በተቃራኒው | አዎ | -| ↳ `check:deprecated-versions` | በሰነዶች ውስጥ የተቋረጡ የስሪት ሕብረቁምፊዎች አለመኖራቸውን | አዎ | -| ↳ `check:doc-links` | በሰነዶች ውስጥ ያሉ ውስጣዊ የmarkdown አገናኞች ወደ እውነተኛ ፋይሎች መጠቆማቸውን (`[text]`/`(path)` ቅርጸት) | አዎ | -| ↳ `check:fabricated-docs` | በሰነዶች ውስጥ የተጠቀሱ መስመሮች፣ env vars፣ የCLI ትዕዛዞች፣ የhook ስሞች እና የፋይል ዱካዎች በኮድ ማከማቻው ውስጥ መኖራቸውን። በ`--strict` በኩል ጠንካራ ጌት፤ ያለዚህ ባንዲራ ለስላሳ ውድቀት። | አዎ (በCI ውስጥ በ`--strict` በኩል) | -| `check:cli-i18n` | የCLI ትዕዛዝ ሕብረቁምፊዎች በሁሉም የi18n አካባቢ ፋይሎች ውስጥ መኖራቸውን | አዎ | -| `check:openapi-coverage` | የOpenAPI ዝርዝር መግለጫ ቢያንስ በratchet የተወሰነውን የእውነተኛ መስመሮች ዝቅተኛ ገደብ መሸፈኑን | አዎ | -| `check:openapi-security-tiers` | በ`openapi.yaml` ውስጥ ያሉ የደህንነት ደረጃ ማብራሪያዎች ከ`routeGuard.ts` ምደባዎች ጋር ወጥ መሆናቸውን | **አማካሪ** | -| `check:openapi-routes` | በ`openapi.yaml` ውስጥ ያለ እያንዳንዱ ዱካ ወደ እውነተኛ `route.ts` መጠቆሙን (ምናባዊ ፈጠራን ለመከላከል) | አዎ | -| `check:docs-symbols` | በ`docs/**/*.md` ውስጥ ያለ እያንዳንዱ የ`/api/...` ማጣቀሻ ወደ እውነተኛ `route.ts` መጠቆሙን (ምናባዊ ፈጠራን ለመከላከል) | አዎ | -| `i18n translation drift` | በi18n አካባቢ ፋይሎች ውስጥ ያልተተረጎሙ ቁልፎች — ማስጠንቀቂያ ብቻ | **አማካሪ** | +ወደ `main` በሚቀርብ እያንዳንዱ PR ላይ ይሠራል። ካልተሳካ ውህደትን ያግዳል። + +| ስክሪፕት | የሚያረጋግጠው | አጋጅ | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `check:docs-all` | ከታች ያሉትን 6 ንዑስ-ጌቶች በቅደም ተከተል የሚያስኬድ ሜታ-ጌት | አዎ | +| ↳ `check:docs-sync` | የCHANGELOG / OpenAPI / llm.txt ስሪት ወጥነት | አዎ | +| ↳ `check:docs-counts` | በጽሑፍ ውስጥ ያሉ ቁጥሮች (የአቅራቢዎች ብዛት፣ የፍልሰት ብዛት፣ ወዘተ) ከእውነተኛዎቹ ቁጥሮች የራቼት ክልል ውስጥ መሆናቸውን | አዎ | +| ↳ `check:env-doc-sync` | በ`.env.example` ውስጥ ያለ እያንዳንዱ የአካባቢ ተለዋዋጭ በሰነዶች ሰንጠረዥ ውስጥ መመዝገቡን፣ እንዲሁም በተቃራኒው | አዎ | +| ↳ `check:deprecated-versions` | በሰነዶች ውስጥ የተቋረጡ የስሪት ሕብረቁምፊዎች አለመኖራቸውን | አዎ | +| ↳ `check:doc-links` | በሰነዶች ውስጥ ያሉ ውስጣዊ markdown አገናኞች ወደ እውነተኛ ፋይሎች መድረሳቸውን (`[text]`/`(path)` ቅርጸት) | አዎ | +| ↳ `check:fabricated-docs` | በሰነዶች ውስጥ የተጠቀሱ መስመሮች፣ የአካባቢ ተለዋዋጮች፣ የCLI ትዕዛዞች፣ የhook ስሞች እና የፋይል ዱካዎች በኮድ ማከማቻው ውስጥ መኖራቸውን። በ`--strict` በኩል ጠንካራ ጌት፤ ያለ ይህ ጥቆማ ለስላሳ-ውድቀት። | አዎ (በCI ውስጥ በ`--strict` በኩል) | +| `check:cli-i18n` | የCLI ትዕዛዝ ሕብረቁምፊዎች በሁሉም የi18n አካባቢ ፋይሎች ውስጥ መኖራቸውን | አዎ | +| `check:openapi-coverage` | የOpenAPI ዝርዝር መግለጫ ቢያንስ ራቼት የተደረገ ዝቅተኛ የእውነተኛ መስመሮች ብዛት መሸፈኑን | አዎ | +| `check:openapi-security-tiers` | በ`openapi.yaml` ውስጥ ያሉ የደህንነት ደረጃ ማብራሪያዎች ከ`routeGuard.ts` ምደባዎች ጋር ወጥነት እንዳላቸው | **ምክር ሰጪ** | +| `check:openapi-routes` | በ`openapi.yaml` ውስጥ ያለ እያንዳንዱ ዱካ ወደ እውነተኛ `route.ts` መድረሱን (ምናባዊ መረጃን ለመከላከል) | አዎ | +| `check:docs-symbols` | በ`docs/**/*.md` ውስጥ ያለ እያንዳንዱ የ`/api/...` ማጣቀሻ ወደ እውነተኛ `route.ts` መድረሱን (ምናባዊ መረጃን ለመከላከል) | አዎ | +| `i18n translation drift` | በi18n አካባቢ ፋይሎች ውስጥ ያልተተረጎሙ ቁልፎች — ማስጠንቀቂያ ብቻ | **ምክር ሰጪ** | ### ሥራ፦ `i18n-ui-coverage` -| ስክሪፕት | የሚያረጋግጠው | አጋጅ | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check-ui-keys-coverage` (ውስጠ-መስመር) | የUI i18n ቁልፍ ሽፋን ≥ 65% መሆኑን | አዎ | -| `check-ui-value-drift` (ውስጠ-መስመር) | እንደገና የተጻፈ የእንግሊዝኛ **እሴት** የቆየ ትርጉም እንዳያስቀር | አዎ | -| `check-new-key-coverage` (ውስጠ-መስመር) | **አዲስ** የእንግሊዝኛ ቁልፍ በእያንዳንዱ አካባቢ መተርጎሙን — የ`__MISSING__:` ምልክት ውድቅ ይደረጋል | አዎ | -| `check-translation-ratio` | በእያንዳንዱ አካባቢ ያለው የእውነተኛ ትርጉም ምጥጥን (ከእንግሊዝኛ ጋር ተመሳሳይ / ቦታ-ያዥ / ከፈቃድ ዝርዝሩ ውጭ የጎደሉ ቅጠሎች) ከ`config/quality/i18n-translation-baseline.json` + የመቻቻል መጠን መብለጥ የለበትም | **አማካሪ** | +| ስክሪፕት | የሚያረጋግጠው | አጋጅ | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check-ui-keys-coverage` (ውስጠ-መስመር) | የUI i18n ቁልፍ ሽፋን ≥ 65% መሆኑን | አዎ | +| `check-ui-value-drift` (ውስጠ-መስመር) | እንደገና የተጻፈ የእንግሊዝኛ **እሴት** ጊዜ ያለፈበት ትርጉም እንዳያስቀር | አዎ | +| `check-new-key-coverage` (ውስጠ-መስመር) | **አዲስ** የእንግሊዝኛ ቁልፍ በእያንዳንዱ አካባቢ መተርጎሙን — የ`__MISSING__:` ምልክት ውድቅ ይደረጋል | አዎ | +| `check-translation-ratio` | በእያንዳንዱ አካባቢ ያለው የእውነተኛ-ትርጉም ጥምርታ (ከእንግሊዝኛ ጋር ተመሳሳይ / ቦታ ያዥ / ከተፈቀዱት ዝርዝር ውጭ የጎደሉ ቅጠሎች) ከ`config/quality/i18n-translation-baseline.json` + ተጨማሪ ክፍተት መብለጥ የለበትም | **ምክር ሰጪ** | -`fetch-depth: 0` ያስፈልገዋል — የvalue-drift ጌቱ `en.json`ን ከmerge base ጋር ያነጻጽራል። +`fetch-depth: 0` ያስፈልገዋል — የእሴት-ለውጥ ጌቱ `en.json`ን ከውህደት መነሻው ጋር ያነጻጽራል። -#### `check-ui-value-drift` — የቆየ-ትርጉም ጌት +#### `check-ui-value-drift` — ጊዜ ያለፈበት-ትርጉም ጌት -ሌሎቹ ጌቶች በመዋቅራዊ ሁኔታ ሊያዩት የማይችሉትን አንድ የi18n ውድቀት ይይዛል፦ አንድ የእንግሊዝኛ እሴት -እንደገና ሲጻፍ፣ ከ_ቀድሞው_ እንግሊዝኛ የተገኙት ትርጉሞች እንደነበሩ ይቀራሉ፤ በዚህም +ሌሎቹ ጌቶች በመዋቅራዊ መንገድ ሊያዩት የማይችሉትን አንድ የi18n ድግግሞሽ ስህተት ይይዛል፦ አንድ የእንግሊዝኛ እሴት +እንደገና ሲጻፍ እና ከ_ቀድሞው_ እንግሊዝኛ የተወሰዱት ትርጉሞች ሳይቀየሩ ሲቀሩ፣ እንግሊዝኛ ያልሆነ ቋንቋ ተጠቃሚዎች በእርግጠኝነት የተጻፈ ነገር ግን አሁን የተሳሳተ ጽሑፍ ማንበባቸውን ይቀጥላሉ። -ይህ በእውነት ለተጠቃሚዎች ተለቋል። የAntigravity -መግቢያ አጋዥ ሲጨመር (#5203) `oauthModal.googleOAuthWarning` እንደገና ተጻፈ፤ **ከ43 አካባቢዎች 39ኙ** -ኦፕሬተሮችን "ሙሉውን URL ገልብጠው ከታች እንዲለጥፉት" የሚነግር ጽሑፍ እንደያዙ ቀሩ — ለዚያ አቅራቢ ሊጠናቀቅ የማይችል ፍሰት። በሚከተሉት ምክንያቶች -እስከ #8463 ድረስ ሳይስተዋል ቆይቷል፦ +ይህ በእውነት ለምርት ተለቋል። የAntigravity +የመግቢያ አጋዥ ሲጨመር (#5203) `oauthModal.googleOAuthWarning` እንደገና ተጻፈ፤ **ከ43 አካባቢዎች 39ኙ** ኦፕሬተሮችን «ሙሉውን URL ገልብጠው ከታች እንዲለጥፉት» የሚነግር ጽሑፍ አቆዩ — ለዚያ አቅራቢ ሊጠናቀቅ የማይችል ሂደት። ይህም +እስከ #8463 ድረስ ሳይስተዋል ቀረ፣ ምክንያቱም፦ -- `sync-ui-keys` የሚሞላው **የሌሉ** ቁልፎችን ብቻ ነው፤ **የቆዩትን** ፈጽሞ አያዘምንም፤ -- `check-ui-keys-coverage` የቁልፎችን _መኖር_ ይቆጥራል፣ ስለዚህ የቆየ ትርጉም እንደተሸፈነ ይቆጠራል፤ -- `check-translation-drift` የ`docs/i18n//**.md` ሰነድ ነጸብራቆችን ይከታተላል — - `src/i18n/messages/*.json`ን ፈጽሞ አያነብም። ከ2026-09 ዳግም-ማመሳሰል ጀምሮ በ`docs-sync-strict` ሥራ ውስጥ አጋጅ ነው፦ ዋና ሰነድ ያርትዑ → `npm run i18n:run -- --files=` (በክፍል ደረጃ፣ አነስተኛ ወጪ ያለው)። +- `sync-ui-keys` የሚሞላው **የጎደሉ** ቁልፎችን ብቻ ነው፣ **ጊዜ ያለፈባቸውን** በፍጹም አይሞላም፤ +- `check-ui-keys-coverage` የቁልፍ _መኖርን_ ስለሚቆጥር፣ ጊዜ ያለፈበት ትርጉም እንደተሸፈነ ይቆጠራል፤ +- `check-translation-drift` የ`docs/i18n//**.md` የሰነድ ነጸብራቆችን ይከታተላል — + `src/i18n/messages/*.json`ን በፍጹም አያነብም። ከ2026-09 ዳግም-ማመሳሰል ጀምሮ በ`docs-sync-strict` ሥራ ውስጥ አጋጅ ነው፦ ዋና ሰነድን አርትዕ → `npm run i18n:run -- --files=` (በክፍል ደረጃ፣ አነስተኛ ወጪ ያለው)። -**ልዩነትን የሚያገናዝብ እንጂ በመነሻ መስመር ላይ የተመሠረተ አይደለም።** በውህደት መነሻው ላይ ያለውን `en.json` ከሥራ ማውጫው ጋር ያወዳድራል፤ የእንግሊዝኛ እሴቱ ለተለወጠ እያንዳንዱ ቁልፍ፣ አሁንም ያልተነካ ትርጉም የያዘ ማንኛውም የቋንቋ ስሪት ጊዜ ያለፈበት ነው። ይህ ሆን ብሎ **ከዚህ ቀደም ያለውን ዕዳ ያቆማል** — ልዩነት ለረጅም ጊዜ የኖረ ትርጉም ከየትኛው የድሮ እንግሊዝኛ እንደመጣ ሊያሳይ ስለማይችል፣ መግቢያ ቁጥጥሩ የአሁኑ ለውጥ የነካውን ብቻ ይገመግማል። አማራጩ (ለእያንዳንዱ ቁልፍ የሃሽ መነሻ መስመር) በእያንዳንዱ i18n PR ላይ እየተለወጠ፣ ከነባሩ ትልቁ መነሻ መስመር 3 እጥፍ የሆነ ~600 KB የመነጨ ፋይል ይጠይቃል። +**ልዩነትን የሚያገናዝብ እንጂ በመነሻ መስመር የማይደገፍ።** በውህደት መነሻው ላይ ያለውን `en.json` ከስራ ዛፉ ጋር ያወዳድራል፤ +የእንግሊዝኛ እሴቱ ለተቀየረው እያንዳንዱ ቁልፍ፣ ያልተነካ ትርጉም የያዘ ማንኛውም አካባቢያዊ ቋንቋ +ጊዜ ያለፈበት ነው። ይህ ሆን ብሎ **ቀድሞ የነበረውን ዕዳ ያቆማል** — ልዩነት +ለረጅም ጊዜ የቆየ ትርጉም ከየትኛው የቀድሞ እንግሊዝኛ እንደመጣ ማሳየት ስለማይችል፣ መግቢያ መቆጣጠሪያው +የአሁኑ ለውጥ የሚነካውን ብቻ ይገመግማል። አማራጩ (ለእያንዳንዱ ቁልፍ የhash መነሻ መስመር) ወደ +~600 KB የሚጠጋ የሚመነጭ ፋይል ይጠይቃል፤ ይህም ካለው ትልቁ መነሻ መስመር 3× ሲሆን፣ በእያንዳንዱ i18n PR ላይ ይለዋወጣል። ይህን ለማሟላት ሁለት መንገዶች አሉ፦ 1. የተነኩትን ትርጉሞች ያዘምኑ፣ ወይም -2. ወደ `__MISSING__:` ያቀናብሯቸው — ከዚያም የአሂድ ጊዜው የታረመውን እንግሊዝኛ ያቀርባል - (`src/i18n/request.ts::deepMergeFallback`, #7258) እና ቁልፉ ለትርጉም ወረፋ ይይዛል። +2. ወደ `__MISSING__:` ያቀናብሯቸው — ከዚያ runtime የታረመውን እንግሊዝኛ + (`src/i18n/request.ts::deepMergeFallback`, #7258) ያቀርባል፣ እና ቁልፉ ለትርጉም ወረፋ ይይዛል። -የሕብረቁምፊው **ትርጉም** ከተለወጠ፣ **ቁልፉን እንደገና መሰየምን** ይምረጡ፦ አዲስ ቁልፍ ጊዜ ያለፈበትን ትርጉም ሊወርስ አይችልም። #8463 የተጠቀመው ንድፍ ይህ ነው። +የሕብረቁምፊው **ትርጉም** ከተቀየረ፣ **ቁልፉን እንደገና መሰየም** ይመረጣል፦ አዲስ ቁልፍ +ጊዜ ያለፈበትን ትርጉም ሊወርስ አይችልም። #8463 የተጠቀመው ስርዓተ ጥለት ይህ ነው። ```bash npm run i18n:check-value-drift # ጥብቅ (CI የሚያስኬደው) @@ -222,47 +228,48 @@ npm run i18n:check-value-drift:warn # ሪፖርት ብቻ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -መነሻ ካታሎጉ ሊነበብ በማይችልበት ጊዜ (መነሻ ማጣቀሻው የሌለበት ጥልቀት የሌለው clone) `check-openapi-breaking`ን በመከተል፣ `SKIP reason=base-unresolved` በማሳየት በ0 ይወጣል። +መነሻ ካታሎጉ ሊነበብ በማይችልበት ጊዜ (የመነሻ ref የሌለው shallow +clone) `check-openapi-breaking`ን በመከተል፣ `SKIP reason=base-unresolved` ከሚለው ጋር በ0 ይወጣል። -### ሥራ፦ `i18n` +### ስራ፦ `i18n` -ሙሉ የi18n ማረጋገጫ ማትሪክስ (ለእያንዳንዱ የቋንቋ ስሪት አንድ ሥራ)። መላው ሥራ አማካሪ ነው። +ሙሉ የi18n ማረጋገጫ ማትሪክስ (ለእያንዳንዱ አካባቢያዊ ቋንቋ አንድ ስራ)። ሙሉው ስራ አማካሪ ነው። -| ስክሪፕት | የሚያረጋግጠው | እገዳ | -| ------------------------------- | --------------------------- | ----------------------------------------------- | -| `validate_translation.py quick` | የእያንዳንዱ የቋንቋ ስሪት የትርጉም ሙሉነት | **አማካሪ** (በመላው ሥራ ላይ `continue-on-error: true`) | +| ስክሪፕት | የሚያረጋግጠው | አጋጅ | +| ------------------------------- | ----------------------------- | ----------------------------------------------- | +| `validate_translation.py quick` | የትርጉም ሙሉነት በእያንዳንዱ አካባቢያዊ ቋንቋ | **አማካሪ** (በሙሉው ስራ ላይ `continue-on-error: true`) | -### ሥራ፦ `pr-test-policy` +### ስራ፦ `pr-test-policy` -በpull requestዎች ላይ ብቻ ይሠራል። +በpull requestዎች ላይ ብቻ ይሰራል። -| ስክሪፕት | የሚያረጋግጠው | እገዳ | -| ---------------------- | --------------------------------------------------------------------------------------------------------------- | --- | -| `check:pr-test-policy` | በ`src/`፣ `open-sse/`፣ `electron/` ወይም `bin/` ውስጥ ያለን የምርት ኮድ የሚለውጡ PRዎች ፈተናዎችን ማካተት ወይም ማዘመን አለባቸው (ጥብቅ ደንብ #8) | አዎ | -| `check:test-masking` | የተለወጡ የፈተና ፋይሎች አጠቃላይ የassert ብዛትን አይቀንሱም ወይም እንደ `assert.ok(true)` ያሉ ሁልጊዜ እውነት የሆኑ አባባሎችን አይጨምሩም | አዎ | -| `check:pr-evidence` | የPR ይዘቱ ለለውጡ የፈተና/VPS ማስረጃን ይጠቅሳል (የPR ጽሑፍን grep በማድረግ ጥብቅ ደንብ #18ን በራስ-ሰር ያስፈጽማል — ተሰባሪ ነው፣ Backlogን ይመልከቱ) | አዎ | +| ስክሪፕት | የሚያረጋግጠው | አጋጅ | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------ | --- | +| `check:pr-test-policy` | በ`src/`፣ `open-sse/`፣ `electron/`፣ ወይም `bin/` ውስጥ ያለ የምርት ኮድን የሚቀይሩ PRዎች ሙከራዎችን ማካተት ወይም ማዘመን አለባቸው (ጥብቅ ደንብ #8) | አዎ | +| `check:test-masking` | የተቀየሩ የሙከራ ፋይሎች አጠቃላይ የassert ብዛትን እንዳይቀንሱ ወይም የ`assert.ok(true)` tautologyዎችን እንዳይጨምሩ | አዎ | +| `check:pr-evidence` | የPR ይዘት ለለውጡ የtest/VPS ማስረጃን እንዲጠቅስ (የPR ጽሑፍን በgrep በመፈለግ ጥብቅ ደንብ #18ን በራስ-ሰር ይተገብራል — ደካማ ነው፣ የኋላ ስራ ዝርዝርን ይመልከቱ) | አዎ | -### ሥራ፦ `test-vitest` +### ስራ፦ `test-vitest` -ከ`build` በኋላ ይሠራል። ካልተሳካ ውህደትን ያግዳል። +ከ`build` በኋላ ይሰራል። ሲከሽፍ ውህደትን ያግዳል። -| ስብስብ | የሚያረጋግጠው | እገዳ | -| ---------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP አገልጋይ (110 መሣሪያዎች)፣ autoCombo፣ cache — vitest runner | አዎ | -| `test:vitest:ui` | የUI አካል ፈተናዎች — vitest runner | **እገዳ ያደርጋል** — ቀድሞ የነበሩ አለመሳካቶች በ`vitest.config.ts` ውስጥ በግልጽ ተገልለዋል፤ አዲስ አለመሳካቶች ሥራውን ያሳኩታል | +| ስብስብ | የሚያረጋግጠው | አጋጅ | +| ---------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `test:vitest` | MCP አገልጋይ (110 መሳሪያዎች)፣ autoCombo፣ cache — vitest runner | አዎ | +| `test:vitest:ui` | የUI ክፍሎች ሙከራዎች — vitest runner | **አጋጅ** — ቀድሞ የነበሩ ውድቀቶች በ`vitest.config.ts` ውስጥ በግልጽ ተገልለዋል፤ አዳዲስ ውድቀቶች ስራውን ያወድቃሉ | -### የሌሊት የሥራ ፍሰቶች (መርሐግብር የተያዘላቸው፣ አማካሪ) +### የምሽት workflows (የጊዜ ሰሌዳ ያላቸው፣ አማካሪ) -እነዚህ በcron መርሐግብር (እና `workflow_dispatch`) ይሠራሉ፣ በPRዎች ላይ ግን ፈጽሞ አይሠሩም። ሁሉም አማካሪ ናቸው። +እነዚህ በcron የጊዜ ሰሌዳ (እና `workflow_dispatch`) ይሰራሉ፣ በPRዎች ላይ በፍጹም አይሰሩም። ሁሉም አማካሪ ናቸው። -| የሥራ ፍሰት | የሚያረጋግጠው | እገዳ | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `nightly-property` | በዘፈቀደ seed እና ከፍተኛ የማስኬጃ ብዛት የሚካሄዱ fast-check ባህሪያት ፈተናዎች | **አማካሪ** | -| `nightly-resilience` | የheap ዕድገት መግቢያ ቁጥጥር፣ chaos ጉድለት ማስገባት፣ k6 የጭነት/የረጅም ጊዜ ጭነት ፈተና | **አማካሪ** | -| `nightly-llm-security` | promptfoo የማስገባት ጥቃት መከላከያ (የእገዳ ሁነታ) + garak probes (የአቅራቢ ምስጢር ከሌለ ይዘለላል) | **አማካሪ** | -| `nightly-schemathesis` | `docs/openapi.yaml`ን በመጠቀም በቀጥታ በሚሠራ OmniRoute ላይ የOpenAPI ውል fuzzing (schemathesis) — የዝርዝር መግለጫ ጥሰቶችን/ያልተያዙ 500 ስህተቶችን ያጋልጣል (Fase 8 B.4) | **አማካሪ** | -| `nightly-mutation` | በፈጣኑ የunit መስመር ላይ የStryker mutation-testing ውጤት — የሚተርፉ mutants ደካማ assertዎችን ያጋልጣሉ | **አማካሪ** | -| `nightly-compat` | በሚደገፉት `engines.node` ክልሎች ውስጥ የNode engine ተኳሃኝነት ማትሪክስ | **አማካሪ** | +| Workflow | የሚያረጋግጠው | አጋጅ | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `nightly-property` | የfast-check property ሙከራዎች ከዘፈቀደ seed እና ከፍተኛ የማስኬጃ ብዛት ጋር | **አማካሪ** | +| `nightly-resilience` | የheap ዕድገት መግቢያ መቆጣጠሪያ፣ chaos fault-injection፣ k6 የload/soak ሙከራ | **አማካሪ** | +| `nightly-llm-security` | የpromptfoo injection መከላከያ (block mode) + garak probes (የprovider secret ከሌለ ይዘለላል) | **አማካሪ** | +| `nightly-schemathesis` | `docs/openapi.yaml`ን በመጠቀም ቀጥታ OmniRoute ላይ የOpenAPI contract fuzzing (schemathesis) — የspec ጥሰቶችን / ያልተያዙ 500ዎችን ያጋልጣል (ደረጃ 8 B.4) | **አማካሪ** | +| `nightly-mutation` | በፈጣኑ unit lane ላይ የStryker mutation-testing ውጤት — የተረፉ mutants ደካማ assertዎችን ያጋልጣሉ | **አማካሪ** | +| `nightly-compat` | በሚደገፉት `engines.node` ክልሎች ሁሉ የNode engine ተኳኋኝነት ማትሪክስ | **አማካሪ** | --- @@ -528,43 +535,43 @@ diagnosticsን እንዲፈቱ ያደርጋል — የ"የተፈጠረ symbol" --- -## የማቀላጠፍ የኋላ ቀር ሥራዎች (የROI ግምገማ — ደረጃ 9 ሞገድ 3) +## የምክንያታዊ ማድረግ የኋላ ዝርዝር (የROI ግምገማ — ደረጃ 9 ሞገድ 3) -ይህ ዝርዝር በ2026-06-17 ከ`ci.yml` ጋር ተመሳክሯል (ቀዳሚው ስሪት +ይህ ንብረት ዝርዝር በ2026-06-17 ከ`ci.yml` ጋር ተመሳክሯል (ቀዳሚው ስሪት `audit:deps`፣ `check:tracked-artifacts`፣ `check:lockfile`፣ `check:licenses`፣ `check:dead-code`፣ `check:cognitive-complexity`፣ `check:type-coverage`፣ -`check:codeql-ratchet`፣ `check:pr-evidence`ን አላካተተም ነበር)። በተመሳከረው ስብስብ ላይ የተደረገ የROI ግምገማ -የሚከተሉትን የማቀላጠፍ እጩዎች ለይቷል። **ውህደቶቹ ሜካኒካዊ የCI -ለውጦች ናቸው፤ ማስገደጃ ለውጦቹ/ማስወገዶቹ ለኦፕሬተሩ የተተዉ የፖሊሲ ውሳኔዎች ናቸው።** ከታች ያለው ምንም ነገር -እስካሁን አልተተገበረም። +`check:codeql-ratchet`፣ `check:pr-evidence`ን አላካተተም ነበር)። የተመሳከረው ስብስብ የROI ግምገማ +የሚከተሉትን የምክንያታዊ ማድረግ ዕጩዎች ለይቷል። **ውህደቶቹ መካኒካዊ የCI +ለውጦች ናቸው፤ መቀየር/ማስወገድ ውሳኔዎቹ ለኦፕሬተሩ የተተዉ የፖሊሲ ውሳኔዎች ናቸው።** ከታች ካሉት ውስጥ +እስካሁን የተተገበረ የለም። -**ከላይ ያልተመዘገቡ ተጨማሪ ነገሮች** (አማካሪ፣ ዝቅተኛ ምልክት ያላቸው)፦ የ`docs-lint` ሥራ -(markdownlint + Vale፣ ሙሉው ሥራ `continue-on-error`) እና ራሳቸውን የቻሉት የስካነር የሥራ ፍሰቶች +**ከላይ ያልተመዘገቡ ተጨማሪ ነገሮችም** (አማካሪ፣ ዝቅተኛ ምልክት)፦ የ`docs-lint` ሥራ +(markdownlint + Vale፣ ሙሉው ሥራ `continue-on-error`) እና ራሳቸውን የቻሉ የስካነር የሥራ ፍሰቶች `semgrep.yml` / `codeql.yml` / `scorecard.yml`። `semgrepFindings: 0` በ -`quality-baseline.json` ውስጥ አለ፣ ነገር ግን በ`ci.yml` ውስጥ ከአጋጅ ራቸት ጋር አልተገናኘም — መለኪያው +`quality-baseline.json` ውስጥ አለ፣ ነገር ግን በ`ci.yml` ውስጥ ከሚያግድ ራቼት ጋር አልተገናኘም — መለኪያው በአሁኑ ጊዜ ወላጅ አልባ ነው። -### ማዋሃድ / ድግግሞሽን ማስወገድ (ሜካኒካዊ፣ ዝቅተኛ ስጋት) +### ማዋሃድ / ብዜት ማስወገድ (መካኒካዊ፣ ዝቅተኛ አደጋ) -እያንዳንዱ እጩ በ2026-06-17 በነበረው ቀጥታ የጌት ሁኔታ ላይ ተረጋግጧል (እመን-ግን-አረጋግጥ)፤ -ብዙ “ግልጽ” የሚመስሉ ውህደቶች እዳን የሚደብቁ ሆነው ተገኝተዋል እና **ያለ ችግር በቀጥታ የሚተኩ አይደሉም**። +እያንዳንዱ ዕጩ በ2026-06-17 በነበረው የቀጥታ ጌት ሁኔታ ላይ ተረጋግጧል (እመን-ግን-አረጋግጥ)፤ +በርካታ "ግልጽ" የሚመስሉ ውህደቶች ዕዳን እንደሚደብቁ ታውቋል እና **ንጹሕ** ቀጥተኛ ምትኮች አይደሉም። -- **`check:docs-sync` ሁለት ጊዜ ይሠራል** — በ`lint` ሥራ ውስጥ ራሱን ችሎ እና እንደገና በ`check:docs-all` (`docs-sync-strict`) እና በhusky pre-commit hook ውስጥ። ✅ **ተጠናቋል** — ራሱን የቻለው የ`lint` ጥሪ ተወግዷል። -- **የCVE ቅኝት** — ❌ **ንጹሕ ውህደት አይደለም።** `audit:deps` በማንኛውም ከፍተኛ/ወሳኝ CVE ላይ በጥብቅ ይወድቃል፤ `check:vuln-ratchet` (osv) የሚወድቀው ከመነሻ መስመሩ ጋር ሲነጻጸር _ማሽቆልቆል_ ሲኖር ብቻ ነው (በአሁኑ ጊዜ 1 MODERATE)። የተለያየ ትርጉም አላቸው — `audit:deps`ን ማስወገድ ፍጹሙን የከፍተኛ/ወሳኝ ጌት ያስቀራል። ሁለቱንም ያቆዩ። -- **የዑደት ማወቂያ** — ❌ **ንጹሕ ውህደት አይደለም።** `check:circular-deps` (dpdm) **91 ዑደቶችን** ያሳውቃል (አማካሪ የሆነውም ለዚህ ነው)፤ መጀመሪያ እነሱን ሳይፈታ ወደ አጋጅነት ማሳደግ አይቻልም፣ እንዲሁም አረንጓዴ ከሆነውና በጥንቃቄ ከተመረጠው `check:cycles` የበለጠ ሰፊ ወሰን አለው። `check:cycles`ን አጋጅ አድርገው ያቆዩ፤ 91ዱን የdpdm ዑደቶች መፍታት የራሱ የኋላ ቀር ሥራ ነው። -- **ውስብስብነት** — ✅ **ተጠናቋል** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`)፦ አንድ የESLint ዳሰሳ፣ በruleId ይቆጥራል፣ ስለዚህ የcyclomatic+max-lines እና cognitive መነሻ መስመሮች ተለያይተው ይቆያሉ፤ ነጠላዎቹ `check:complexity` / `check:cognitive-complexity` ለአካባቢያዊ `--update` ይቆያሉ። -- **የ`/api` ፀረ-ቅዠት** — ✅ **ተጠናቋል** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`)፦ አንድ የ`src/app/api` FS ቆጠራ፣ openapi-routes + docs-symbols አሁንም ራሳቸውን ችለው ሪፖርት ያደርጋሉ፤ ነጠላዎቹ ለአካባቢያዊ ማስኬዶች ይቆያሉ። -- **`check:node-runtime` በ11 ሥራዎች ውስጥ ይሠራል** — ⚠️ **ዝቅተኛ ROI።** እያንዳንዱ የተለየ runner ነው፣ እና ምርመራው <1s ነው፤ አጠቃላይ ቁጠባው ~10s ሲሆን፣ በእያንዳንዱ ሥራ ያለውን ርካሽ መከላከያ ማጣትን አያካክስም። ለዚህ ለውጥ የሚደረገው ጥረት ዋጋ የለውም። -- **በCI lint ላይ `typecheck:noimplicit:core`** — ✅ **ከlint ሥራ ተወግዷል** (አማካሪ `continue-on-error` ነበር)፤ አጋጁ የዓይነት ወሰን `typecheck:core` + `check:type-coverage` ነው። አካባቢያዊው ስክሪፕት ተይዟል። +- **`check:docs-sync` ሁለት ጊዜ ይሰራል** — በ`lint` ሥራ ውስጥ ራሱን ችሎ እና እንደገና በ`check:docs-all` (`docs-sync-strict`) ውስጥ እንዲሁም በhusky pre-commit hook ውስጥ። ✅ **ተጠናቋል** — ራሱን የቻለው የ`lint` ጥሪ ተወግዷል። +- **የCVE ቅኝት** — ❌ **ንጹሕ ውህደት አይደለም።** `audit:deps` በማንኛውም high/critical CVE ላይ በጥብቅ ይወድቃል፤ `check:vuln-ratchet` (osv) የሚወድቀው ከመነሻ መስመሩ ጋር ሲነጻጸር _መመለስ_ ሲኖር ብቻ ነው (በአሁኑ ጊዜ 1 MODERATE)። የተለያየ ትርጉም አላቸው — `audit:deps`ን ማስወገድ ፍጹሙን high/critical ጌት ያሳጣል። ሁለቱንም አቆዩ። +- **የዑደት ማወቂያ** — ✅ **ተጠናቋል** (#15159 G-01/G-02)። እዚህ የነበረው የድሮ ጽሑፍ `check:cycles`ን "አረንጓዴው፣ በጥንቃቄ የተመረጠው" ጌት ብሎ ጠርቶታል እና `check:circular-deps` (dpdm) 91 ዑደቶችን ስለዘገበ እንደ አጋጅ መቆየቱን አጽድቋል። ያ አረንጓዴ ውጤት **ሐሰተኛ አረንጓዴ** ነበር፦ `check:cycles` 5 ንዑስ ማውጫዎችን (450 ፋይሎች) ቃኝቷል፣ static `import|export … from`ን ብቻ አዛምዷል፣ እና እያንዳንዱን `@/` እና `@omniroute/open-sse/` specifier አስወግዷል፤ ስለዚህ በrepoው ውስጥ የበዙትን dynamic-import + alias ዑደቶች ማየት አልቻለም። ተስተካክሏል፦ ጌቱ አሁን `src` + `open-sse`ን (5023 ፋይሎች) ያስሳል፣ specifierዎችን ከTypeScript AST ይሰበስባል (ስለዚህ `import("…")` ይቆጠራል እና በtype-position ያለ `typeof import("…")` አይቆጠርም)፣ እንዲሁም የtsconfig `paths`ን ይፈታል። 0 ሳይሆን **14** ዑደቶችን ያገኛል። ቀድሞ የነበሩት 14 ዑደቶች በጌት PR ውስጥ ሊስተካከሉ ስለማይችሉ፣ `check:cycles` አሁን **ራቼት** ነው (`--ratchet`፣ በ`quality-baseline.json` ውስጥ ጣሪያው `metrics.cycles.value = 14`፣ `direction: down`) — ማንኛውንም _መመለስ_ ያግዳል እና ቁጥሩ መቀነስ ብቻ ይችላል። CI `npm run check:cycles:ratchet`ን ያስኬዳል። ቀስ በቀስ የማጥፋት ሥራው ከ**A-01** ጋር ይከናወናል። `check:circular-deps` (dpdm) እንደ ሰፊ ሁለተኛ አስተያየት አማካሪ ሆኖ ይቆያል። +- **ውስብስብነት** — ✅ **ተጠናቋል** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`)፦ አንድ የESLint ቅኝት፣ cyclomatic+max-lines እና cognitive መነሻ መስመሮች እርስ በርሳቸው ነጻ ሆነው እንዲቆዩ በruleId ይቆጥራል፤ የተናጠል `check:complexity` / `check:cognitive-complexity` ለአካባቢያዊ `--update` ይቆያሉ። +- **የ`/api` ፀረ-ቅዠት** — ✅ **ተጠናቋል** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`)፦ አንድ የ`src/app/api` FS ንብረት ዝርዝር፣ openapi-routes + docs-symbols አሁንም በተናጠል ሪፖርት ያደርጋሉ፤ የተናጠል ስክሪፕቶቹ ለአካባቢያዊ ማስኬዶች ይቆያሉ። +- **`check:node-runtime` በ11 ሥራዎች ውስጥ ይሰራል** — ⚠️ **ዝቅተኛ ROI።** እያንዳንዱ የተለየ runner ነው እና ፍተሻው <1s ነው፤ ጠቅላላ ቁጠባው ~10s ነው፣ በምላሹ ግን ርካሽ የየሥራውን ጥበቃ ማጣት ይኖራል። የለውጥ ውጣ ውረዱን አያዋጣም። +- **በCI lint ላይ `typecheck:noimplicit:core`** — ✅ **ከlint ሥራ ተወግዷል** (አማካሪ `continue-on-error` ነበር)፤ አጋጁ የtype ወሰን `typecheck:core` + `check:type-coverage` ነው። አካባቢያዊ ስክሪፕቱ ተቀምጧል። -### ማስገደጃን መቀየር / መወሰን (የኦፕሬተር ፖሊሲ) +### መቀየር / መወሰን (የኦፕሬተር ፖሊሲ) -- `check:openapi-security-tiers` (አማካሪ) — ❌ **በንጽሕና ወደ አጋጅነት ሊቀየር አይችልም።** በ0 ይወጣል፣ ነገር ግን በ`LOCAL_ONLY_API_PREFIXES` ስር ያሉ በርካታ የ`traffic-inspector` መንገዶች የ`x-loopback-only: true` ማብራሪያ እንደሌላቸው ያስጠነቅቃል። ማስገደድ በመጀመሪያ እነዚያን ማብራሪያዎች ወደ`openapi.yaml` ማከልን ይጠይቃል። -- `typecheck:noimplicit:core` (አማካሪ) — በአብዛኛው በአጋጁ `check:type-coverage` ራቸት ተሸፍኗል። ወደ ራቸት ይቀይሩት ወይም ተደጋጋሚውን ሁለተኛ የ`tsc` ማስኬድ ያስወግዱ። -- `test:vitest:ui` (አሁን **አጋጅ**) — ቀድሞ የነበሩ ውድቀቶች በ`vitest.config.ts` ውስጥ በ`// #8618` የክትትል አስተያየቶች በግልጽ ተገልለዋል፤ አዳዲስ ውድቀቶች ሥራውን ያወድቃሉ። -- `check:secrets` (gitleaks፣ በ3 በሰነድ የተመዘገቡ የሐሰት-አዎንታዊ ውጤቶች ላይ የቀዘቀዘ አጋጅ ራቸት) — 0 ላይ ለመድረስ 3ቱን allowlist ያድርጉ፣ ወይም ወደ አማካሪነት ዝቅ ያድርጉት። ከGitHub ቤተኛ secret-scanning + `check:public-creds` ጋር ይደራረባል። -- `check:pr-evidence` (አጋጅ፣ የPR-body ጽሑፍን በgrep ይፈልጋል) — ከፍተኛ የሐሰት-አዎንታዊ ስጋት አለው፤ ከተወገደ የHard Rule #18 ማስፈጸሚያን ያዳክማል፣ ስለዚህ ይህ እውነተኛ የፖሊሲ ውሳኔ ነው። -- `semgrep` (ራሱን የቻለ አማካሪ) — ለOWASP ምድቦች ከCodeQL ጋር ይደራረባል፤ መነሻ መስመሩን ከራቸት ጋር ያገናኙ ወይም ያስወግዱት። +- `check:openapi-security-tiers` (አማካሪ) — ❌ **በንጽሕና ሊቀየር አይችልም።** በ0 ይወጣል፣ ነገር ግን በ`LOCAL_ONLY_API_PREFIXES` ስር ያሉ በርካታ `traffic-inspector` መስመሮች የ`x-loopback-only: true` ማብራሪያ እንደሌላቸው ያስጠነቅቃል። ማስገደድ በመጀመሪያ እነዚያን ማብራሪያዎች ወደ`openapi.yaml` መጨመርን ይጠይቃል። +- `typecheck:noimplicit:core` (አማካሪ) — በአብዛኛው በአጋጁ `check:type-coverage` ራቼት ውስጥ ተካቷል። ወደ ራቼት ይቀየር ወይም ተደጋጋሚው ሁለተኛ የ`tsc` ማለፊያ ይወገድ። +- `test:vitest:ui` (አሁን **አጋጅ**) — ቀድሞ የነበሩ ውድቀቶች በ`vitest.config.ts` ውስጥ በ`// #8618` የክትትል አስተያየቶች በግልጽ ተገልለዋል፤ አዲስ ውድቀቶች ሥራውን ያወድቃሉ። +- `check:secrets` (gitleaks፣ በ3 የተመዘገቡ ሐሰተኛ-አዎንታዊ ውጤቶች ላይ የቀዘቀዘ አጋጅ ራቼት) — 0 ላይ ለመድረስ 3ቱን allowlist ውስጥ ያስገቡ፣ ወይም ወደ አማካሪ ዝቅ ያድርጉት። ከGitHub ቤተኛ secret-scanning + `check:public-creds` ጋር ይደራረባል። +- `check:pr-evidence` (አጋጅ፣ የPR-body ስድ ንባብን በgrep ይፈልጋል) — ከፍተኛ የሐሰት-አዎንታዊ አደጋ አለው፤ ከተወገደ የHard Rule #18 ማስፈጸሚያን ያዳክማል፣ ስለዚህ ይህ እውነተኛ የፖሊሲ ውሳኔ ነው። +- `semgrep` (ራሱን የቻለ አማካሪ) — ለOWASP ቤተሰቦች ከCodeQL ጋር ይደራረባል፤ መነሻ መስመሩን ከራቼት ጋር ያገናኙ ወይም ያስወግዱት። --- diff --git a/docs/i18n/am/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/am/docs/compression/COMPRESSION_GUIDE.md index a98d54897132..f04786649eb8 100644 --- a/docs/i18n/am/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/am/docs/compression/COMPRESSION_GUIDE.md @@ -441,7 +441,7 @@ const { messages: aged, saved } = applyAging(messages, { `applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`) ምርጫውን ከካታሎግ ጋር ይፈታል (የማይታወቁ መለያዎች እና ከአካባቢ ጋር የማይጣጣሙ ቅጦች ይጣላሉ፣ ስህተት አይሆኑም)፣ የተመረጡትን መመሪያዎች በካታሎግ ቅደም ተከተል ያገናኛል፣ የወሰን አንቀጹን **አንድ ጊዜ** ያክላል፣ እና ብሎኩን በአንድ የአይደምፖቴንሲ ምልክት (`[OmniRoute Output Styles]`) ይጀምራል፣ ስለዚህ እንደገና መተግበር ምንም ውጤት የለውም። የተፈታው ቋንቋ (ከታች ያለውን የቋንቋ ምርጫ ይመልከቱ) ትርጉም ሲኖረው፣ የአካባቢው መመሪያ በእንግሊዝኛ ፋንታ ይገባል። -መልዕክቶች ባለው አካል ላይ፣ የይዘት ማለፊያ (`shouldBypassCavemanOutputMode()` በ`open-sse/services/compression/outputMode.ts` ውስጥ) የመጨረሻዎቹን ሶስት መልዕክቶች ይፈትሻል እና የደህንነት፣ የማይቀለበስ-ድርጊት፣ ማብራሪያ፣ ወይም ቅደም ተከተል-ስሜታዊ ቁልፍ ቃላትን ሲያሟሉ ቅጦቹን ለመላው ዙር ይዘላል። ማለፊያው የዳሽቦርዱ **ራስ-ሰር-ግልጽነት ማለፊያ** መቀየሪያ (`cavemanOutputMode.autoClarity`) ምንም ቢሆን ይሰራል። +መልዕክቶች ባለው አካል ላይ፣ የይዘት ማለፊያ (`shouldBypassCavemanOutputMode()` በ`open-sse/services/compression/outputMode.ts` ውስጥ) የመጨረሻዎቹን ሶስት መልዕክቶች ይፈትሻል እና የደህንነት፣ የማይቀለበስ-ድርጊት፣ ማብራሪያ፣ ወይም ቅደም ተከተል-ስሜታዊ ቁልፍ ቃላትን ሲያሟሉ ቅጦቹን ለመላው ዙር ይዘላል። ማለፊያው የዳሽቦርዱ **ራስ-ሰር-ግልጽነት ማለፊያ** መቀየሪያ (`cavemanOutputMode.autoClarity`) ሲበራ ነው የሚሰራው፣ ይህም ነባሪው ነው፤ መቀየሪያው ሲጠፋ ግን የተመረጡት ቅጦች በእነዚያ ዙሮችም ይተገበራሉ። ማለፊያው ዙሩን ሲያልፍ፣ `placeSystemInstruction()` (ተመሳሳይ ፋይል)፣ አዲስ `messages[0]` ፈጽሞ የማይፈጥር፣ ብሎኩን ከሚያገኛቸው የመጀመሪያዎቹ ውስጥ ያስቀምጣል፦ diff --git a/docs/i18n/am/docs/reference/ENVIRONMENT.md b/docs/i18n/am/docs/reference/ENVIRONMENT.md index 388988013855..6a4bbf8fc2be 100644 --- a/docs/i18n/am/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/am/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. አስፈላጊ ሚስጥሮች -እነዚህ ከመጀመሪያው ማስኬድ በፊት **መዋቀር አለባቸው**። ያለእነሱ መተግበሪያው ለመጀመር ፈቃደኛ አይሆንም ወይም ደህንነቱ ባልተጠበቀ ነባሪ ውቅረት ይሠራል። - -| ተለዋዋጭ | አስፈላጊነት | ነባሪ | የምንጭ ፋይል | መግለጫ | -| ---------------------------- | ------------------ | ----------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **አዎ** | _(የለም)_ | `src/lib/auth` | ሁሉንም የዳሽቦርድ ክፍለ-ጊዜ ኩኪዎችን (JWT) ይፈርማል/ያረጋግጣል። በ`openssl rand -base64 48` ያመንጩት። | -| `API_KEY_SECRET` | **አዎ** | _(የለም)_ | `src/lib/db/apiKeys.ts` | በSQLite ውስጥ የተከማቹ የAPI ቁልፍ እሴቶችን ለመመስጠር የሚያገለግል የAES ምስጠራ ቁልፍ። በ`openssl rand -hex 32` ያመንጩት። | -| `INITIAL_PASSWORD` | **አዎ** | `CHANGEME` | የመነሻ ስክሪፕት | የመጀመሪያውን የአስተዳዳሪ ዳሽቦርድ የይለፍ ቃል ያዘጋጃል (ከ`.env.example` ነባሪ ጋር ይዛመዳል — ለውጥን ለማስገደድ ሆን ተብሎ ደህንነቱ ያልተጠበቀ ሆኖ ተቀምጧል)። **ከመጀመሪያ አጠቃቀም በፊት ይቀይሩት።** ከገቡ በኋላ በዳሽቦርድ → ቅንብሮች → ደህንነት በኩል ይቀይሩት። | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **አዎ** (በምርት አካባቢ) | _(አልተዋቀረም)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ለውስጣዊው Codex Responses WebSocket ድልድይ የሚያገለግል የጋራ ሚስጥር። በElectron/አሳሽ WS ማስተላለፊያ እና OmniRoute መካከል ያሉ የድልድይ ጥያቄዎችን ያረጋግጣል። ⚠️ **በምርት አካባቢ አስፈላጊ ነው — ካልተዋቀረ ሁሉም የWS ድልድይ ጥያቄዎች ውድቅ ይደረጋሉ።** በ`openssl rand -base64 32` ያመንጩት። | -| `OMNIROUTE_SW_BUILD_ID` | አይ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ለPWA shell (#11779) ግልጽ የservice-worker cache-busting መለያ፤ በመፍትሔ ሰንሰለቱ ውስጥ የመጀመሪያው ነው። | -| `SOURCE_VERSION` | አይ | _(አልተዋቀረም)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | በሰንሰለቱ ውስጥ ሁለተኛው — በPaaS ግንባታ ስርዓቶች (ለምሳሌ Heroku-style) እንደተሰማራው commit ይዋቀራል። | -| `NEXT_PUBLIC_SW_BUILD_ID` | አይ | _(የሚመነጭ)_ | `src/shared/components/PwaRegister.tsx` | ደንበኛው `/sw.js?v=…`ን ለመመዝገብ የሚጠቀምበት በግንባታ ጊዜ የሚዘጋጅ ይፋዊ እሴት፤ ከላይ ካሉት ሁለት እና ከዚያም ከgit SHA ይመነጫል። | -| `OMNIROUTE_PEER_STAMP_TOKEN` | አይ (ራስ-ሰር) | _(በእያንዳንዱ ማስነሳት ራስ-ሰር)_ | `src/server/authz/policies/management.ts` | የታመነው የpeer-IP ማህተም ከOmniRoute የራሱ HTTP አገልጋይ (`scripts/dev/peer-stamp.mjs`) መምጣቱን የሚያረጋግጥ የእያንዳንዱ ሂደት ሚስጥር። የauthz middleware ማህተሙ ይህን ቶከን ሲይዝ ብቻ የጥያቄውን አካባቢያዊነት (የLOCAL_ONLY መስመሮች loopback/LAN መገደብ) ያምናል። በእያንዳንዱ ማስነሳት ራስ-ሰር ይመነጫል — አለመዋቀሩን ይተዉት፤ ማህተሙን መጋራት ለሚኖርባቸው ባለብዙ-ሂደት ውቅሮች ብቻ ይወስኑት። | -| `OMNIROUTE_TRUSTED_PROXIES` | አይ | _(አልተዋቀረም)_ | `scripts/dev/peer-stamp.mjs` | የIP ፍቀድ/ከልክል ዝርዝሩ ሊያምናቸው የሚችላቸው፣ በነጠላ ሰረዝ የተለያዩ የreverse proxy IPዎች ወይም CIDR ክልሎች። ይህም ሁልጊዜ ከሚታመኑት loopback፣ የግል-አውታረ መረብ አድራሻዎች እና Cloudflare edges በተጨማሪ `X-Forwarded-For` / `X-Real-IP`ን እንዲያምን ያደርገዋል። ይህ በሌላ ይፋዊ አድራሻ ላይ ላለ proxy ብቻ ያስፈልጋል፤ ያለበለዚያ ማጣሪያው የሚገመግመው የproxyውን የራሱን አድራሻ ነው። | +በ**አዎ** የተሰየሙ ያልተዋቀሩ ሚስጥሮች በመጀመሪያው ማስኬድ ላይ በራስ-ሰር ይመነጫሉ (`JWT_SECRET`፣ `API_KEY_SECRET`) እና በውሂብ ማውጫው ውስጥ ወደ `server.env` ተቀምጠው ይቀመጣሉ — ያ ማውጫ ጊዜያዊ (ephemeral) ሲሆን በግልጽ ያዘጋጁ። ልዩ ጉዳዩ `OMNIROUTE_WS_BRIDGE_SECRET` ነው፦ ወደ `server.env` አይቀመጥም — የserver መግቢያ ነጥቦች (entrypoints) በውስጥ ባለው ሂደት ድልድይ ብቻ ሊጠቀም የሚችል ጊዜያዊ የእያንዳንዱ-ማስነሳት ዋጋ ይመነጫሉ፤ ስለዚህ የውጭ የድልድይ ደንበኞች እሱን በግልጽ ማዋቀር አለባቸው። + +| ተለዋዋጭ | አስፈላጊነት | ነባሪ | የምንጭ ፋይል | መግለጫ | +| ---------------------------- | ------------------ | ----------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **አዎ** | _(የለም)_ | `src/lib/auth` | ሁሉንም የዳሽቦርድ ክፍለ-ጊዜ ኩኪዎችን (JWT) ይፈርማል/ያረጋግጣል። በ`openssl rand -base64 48` ያመንጩት። | +| `API_KEY_SECRET` | **አዎ** | _(የለም)_ | `src/lib/db/apiKeys.ts` | በSQLite ውስጥ የተከማቹ የAPI ቁልፍ እሴቶችን ለመመስጠር የሚያገለግል የAES ምስጠራ ቁልፍ። በ`openssl rand -hex 32` ያመንጩት። | +| `INITIAL_PASSWORD` | አይ | _(አልተዋቀረም)_ | `src/lib/auth/managementPassword.ts` | ገና የተቀመጠ የይለፍ ቃል ባይኖርም በማስነሳት ጊዜ የዳሽቦርድ የይለፍ ቃልን ያዘጋጃል፤ የይለፍ ቃል አንዴ ከተቀመጠ በኋላ ግን ይተወዋል። npm እና የምንጭ ተከላዎች `.env.example`ን ወደ `.env` ይቅዱል፣ ስለዚህ በግልጽ በሚታወቀው placeholder `CHANGEME` ይጀምራሉ፦ ከመጀመሪያው ማስነሳት በፊት ይተኩት፣ ወይም ባልተዋቀረ እሴት (የDocker image ነባሪ) እንዳደርገው በመጀመሪያው ጉብኝት ላይ የይለፍ ቃሉ በonboarding wizard ውስጥ እንዲፈጠር በዚያ `.env` ውስጥ `INITIAL_PASSWORD=` (ባዶ) ያዘጋጁ። የተቀመጠ የይለፍ ቃል የሚቀይሩት በዳሽቦርድ → ቅንብሮች → ደህንነት በኩል ነው፣ ወይም `DATA_DIR` ወደ አገልጋዩ የውሂብ ማውጫ እንደተዋቀረ `omniroute reset-password` ያሂዱ (በምንጭ checkout ውስጥ `node bin/reset-password.mjs`)። | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **አዎ** (በምርት አካባቢ) | _(አልተዋቀረም)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ለውስጣዊው Codex Responses WebSocket ድልድይ የሚያገለግል የጋራ ሚስጥር። በElectron/አሳሽ WS ማስተላለፊያ እና OmniRoute መካከል ያሉ የድልድይ ጥያቄዎችን ያረጋግጣል። ⚠️ **በምርት አካባቢ አስፈላጊ ነው — ካልተዋቀረ ሁሉም የWS ድልድይ ጥያቄዎች ውድቅ ይደረጋሉ።** በ`openssl rand -base64 32` ያመንጩት። | +| `OMNIROUTE_SW_BUILD_ID` | አይ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ለPWA shell (#11779) ግልጽ የservice-worker cache-busting መለያ፤ በመፍትሔ ሰንሰለቱ ውስጥ የመጀመሪያው ነው። | +| `SOURCE_VERSION` | አይ | _(አልተዋቀረም)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | በሰንሰለቱ ውስጥ ሁለተኛው — በPaaS ግንባታ ስርዓቶች (ለምሳሌ Heroku-style) እንደተሰማራው commit ይዋቀራል። | +| `NEXT_PUBLIC_SW_BUILD_ID` | አይ | _(የሚመነጭ)_ | `src/shared/components/PwaRegister.tsx` | ደንበኛው `/sw.js?v=…`ን ለመመዝገብ የሚጠቀምበት በግንባታ ጊዜ የሚዘጋጅ ይፋዊ እሴት፤ ከላይ ካሉት ሁለት እና ከዚያም ከgit SHA ይመነጫል። | +| `OMNIROUTE_PEER_STAMP_TOKEN` | አይ (ራስ-ሰር) | _(በእያንዳንዱ ማስነሳት ራስ-ሰር)_ | `src/server/authz/policies/management.ts` | የታመነው የpeer-IP ማህተም ከOmniRoute የራሱ HTTP አገልጋይ (`scripts/dev/peer-stamp.mjs`) መምጣቱን የሚያረጋግጥ የእያንዳንዱ ሂደት ሚስጥር። የauthz middleware ማህተሙ ይህን ቶከን ሲይዝ ብቻ የጥያቄውን አካባቢያዊነት (የLOCAL_ONLY መስመሮች loopback/LAN መገደብ) ያምናል። በእያንዳንዱ ማስነሳት ራስ-ሰር ይመነጫል — አለመዋቀሩን ይተዉት፤ ማህተሙን መጋራት ለሚኖርባቸው ባለብዙ-ሂደት ውቅሮች ብቻ ይወስኑት። | +| `OMNIROUTE_TRUSTED_PROXIES` | አይ | _(አልተዋቀረም)_ | `scripts/dev/peer-stamp.mjs` | የIP ፍቀድ/ከልክል ዝርዝሩ ሊያምናቸው የሚችላቸው፣ በነጠላ ሰረዝ የተለያዩ የreverse proxy IPዎች ወይም CIDR ክልሎች። ይህም ሁልጊዜ ከሚታመኑት loopback፣ የግል-አውታረ መረብ አድራሻዎች እና Cloudflare edges በተጨማሪ `X-Forwarded-For` / `X-Real-IP`ን እንዲያምን ያደርገዋል። ይህ በሌላ ይፋዊ አድራሻ ላይ ላለ proxy ብቻ ያስፈልጋል፤ ያለበለዚያ ማጣሪያው የሚገመግመው የproxyውን የራሱን አድራሻ ነው። | ### የማመንጫ ትዕዛዞች @@ -215,38 +215,38 @@ OmniRoute ለሁሉም ቋሚ ማከማቻ **SQLite**ን (በ`better-sqlite3` ## 4. ደህንነት እና ማረጋገጫ -| ተለዋዋጭ | ነባሪ | የምንጭ ፋይል | መግለጫ | -| ------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | የማሽን አሻራ ለመፍጠር ከሃርድዌር መለያዎች ጋር የሚጣመር salt። ማግለልን ለማረጋገጥ በእያንዳንዱ deployment ይቀይሩት። | -| `OMNIROUTE_CLI_SALT` | _(ካልተዋቀረ = በእያንዳንዱ install በዘፈቀደ የሚፈጠር እና በ`/cli-token-salt.json` የሚቀመጥ salt)_ | `src/lib/machineToken.ts` | የአካባቢያዊውን CLI ማረጋገጫ token ለማመንጨት የሚያገለግል HMAC salt። ይህን እሴት ማዋቀር በማሽኑ ላይ ያሉትን ሁሉንም CLI tokens ያድሳል፣ እንዲሁም ሁልጊዜ ከተቀመጠው salt ይልቅ ቅድሚያ ያገኛል። `docs/security/CLI_TOKEN.md`ን ይመልከቱ። | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | በsession cookies ላይ የ`Secure` flagን ያዘጋጃል። ከHTTPS በስተጀርባ ሲሰራ **`true` መሆን አለበት**። | -| `REQUIRE_API_KEY` | `false` | API middleware | `true` ሲሆን፣ ሁሉም የ`/v1/*` proxy requests ትክክለኛ API key ማካተት አለባቸው። **ይህ flag `GET /v1/models`ን አይገድብም**፤ በምትኩ የDashboard login postureን (`requireAuthForModels`) ይከተላል — ስለዚህ ከ`/v1/models` የሚመጣ `401` inference የተጠበቀ ነው ማለት **አይደለም**። `docs/security/INFERENCE_AUTH_POSTURE.md`ን (#13695) ይመልከቱ። | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | በDashboard UI ውስጥ ሙሉ የAPI key እሴቶችን ማሳየት ያስችላል። ከDashboard Feature Flags ሊዋቀር ይችላል፤ በጋራ ጥቅም ላይ በሚውሉ instances ላይ የደህንነት አደጋ አለው። | -| `NO_LOG_API_KEY_IDS` | _(ባዶ)_ | `src/lib/compliance/index.ts` | request loggingን የሚያልፉ በነጠላ ሰረዝ የተለያዩ API key IDs (ለGDPR compliance)። | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ካልተዋቀረ = ያልተገደበ)_ | `src/shared/utils/apiKeyPolicy.ts` | የ`rate_limits` column null ለሆነባቸው API keys የሚተገበር ተተኪ የዕለታዊ request budget። ካልተዋቀረ ወይም ባዶ ከሆነ፦ በስውር የሚተገበር ገደብ የለም (#2289, #11017)። `0`ም ተመሳሳይ ነው (ያልተገደበ)። አዎንታዊ ኢንቲጀር N፣ N/ቀን፣ 5N/ሳምንት፣ 20N/ወር ያነቃል። ቅርጸት የተበላሸ ባዶ ያልሆነ እሴት ወደ ቀድሞው 1000/ቀን፣ 5000/ሳምንት፣ 20000/ወር windows ይመለሳል። | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ከፍተኛው የተፈቀደ request body መጠን። ከዚህ ገደብ የሚበልጡ payloadsን ውድቅ ያደርጋል። | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ከዚህ threshold ጋር እኩል ወይም በላይ የሆኑ ትክክለኛ request bodies JSON ከመተንተናቸው በፊት አቶሚካዊውን፣ process-local heavyweight admission lease ይወስዳሉ (BYTE path፣ `POST /v1/responses`ን ጨምሮ)። ከstructure-heavy ጋር ተመሳሳይ የ[#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) healthy-headroom escape ነው፤ አሁንም በ`OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) የተገደበ ስለሆነ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) እንደገና አይከሰትም። | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | የ`Content-Length` እሴት የጎደለ፣ ልክ ያልሆነ ወይም ሐሰተኛ የሆነባቸውን ጥያቄዎች ጨምሮ፣ በተወሰነ የማስገባት ሂደት ወቅት በተነበቡ ባይቶች ላይ የሚተገበር የውይይት መስመር ጥብቅ ገደብ፤ ገደቡን ያለፈ ጥያቄ `413` ይቀበላል። | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(አልተዋቀረም — የጥያቄ ብዛት ገደብ የለም)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ይህ የቆየ የጥያቄ-ብዛት ገደብ አሁን የሚተገበረው በግልጽ ሲዋቀር ብቻ ነው። ሳይዋቀር ከተተወ (ነባሪው)፣ ከባድ የውይይት ጥያቄዎችን መቀበል በምትኩ በ`OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ይገደባል — በ**አንድ ሂደት** (አንድ V8 heap) ውስጥ ካለው እውነተኛ የሂደቱ የማህደረ ትውስታ ጣሪያ በራስ-ሰር የሚሰላ የባይት በጀት። ሁለት ተደራራቢ ~~750k-token `/v1/responses` በ~~12 Gi heaps ላይ መቋረጥ ያስከትላሉ (#7849) — ይህ **የማህደረ ትውስታ በጀት** ማስጠንቀቂያ እንጂ የምርቱ ጥብቅ ከፍተኛ ገደብ 2 ነው ማለት አይደለም። ጤናማ ሂደት (heap ከማስወገጃ ጥምርታው በታች ሲሆን) በ`OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` በኩል ተጨማሪ በአንድ ጊዜ የሚካሄዱ ረጅም `/v1/responses` ሊቀበል ይችላል። በአስሮች የሚቆጠሩ ረጅም SSE ደንበኞች (40–50) ጉዳይ heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ነው፣ “ከፍተኛው 2” አይደለም። ይህን “የአስተናጋጁን አቅም ለመጠቀም” በግዴለሽነት ከፍ ማድረግ #7849ን እንደገና ያስከትላል። **heaps**ን በ**N ነጻ `DATA_DIR`s** (#11024) ያባዙ፤ በአንድ SQLite ፋይል ላይ `replicas>1` ፈጽሞ አይጠቀሙ። | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(በራስ-ሰር የሚሰላ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** በራስ-ሰር ለሚሰላው የማስገቢያ ባይት በጀት የሚደረግ ማሻሻያ (ከV8/cgroup የማህደረ ትውስታ ጣሪያዎች ይበልጥ ጥብቅ ከሆነው 25%፣ በ8x ጊዜያዊ ማጉላት የተከፈለ)። የተሰሉ እና በግልጽ የተዋቀሩ እሴቶች በ8 MiB–2 GiB መካከል ይገደባሉ። ከተግባራዊው በጀት የሚበልጥ body ወዲያውኑ በ`413 body_exceeds_budget` ይሳካለታል፤ በተናጠል ሊስተናገዱ በሚችሉ bodies መካከል ያለ ፉክክር እንደገና ሊሞከር የሚችል `503` ሆኖ ይቆያል። 40–50 በአንድ ጊዜ የሚገናኙ ረጅም SSE ደንበኞች ጉዳይ ይህ በጀት + heap ነው፣ ጥብቅ “ከፍተኛው 2” አይደለም። ከማስተካከልዎ በፊት በ`/api/monitoring/health` ላይ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`ን ያንብቡ። | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | ለBYTE እና STRUCTURE ከባድ ጥያቄዎች መቀበል የheap-ጫና ማስወገጃ ጥምርታ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)። `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`ን ያለፈ በአንድ ጊዜ የሚካሄድ ከባድ ጥያቄ እንደገና ሊሞከር በሚችለው `503` የሚወገደው heap ደግሞ በዚህ ጥምርታ ወይም ከዚያ በላይ ሲሆን ብቻ ነው፤ ጤናማ heap ላይ በhealthy-headroom በኩል ይቀበላል። | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ነባሪ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | ለጤናማ-heap ፈጣን መንገድ (#10437) በ**ሁለቱም** STRUCTURE እና BYTE (`admitChatRequest`፣ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` የሆኑ bodiesን ጨምሮ) የተወሰነ ተጨማሪ አቅም። ያለዚህ ገደብ፣ heap ጤናማ ሆኖ ስራ የበዛበት እያንዳንዱ ጥያቄ ያለ ጣሪያ የመቀበያ ሂደቱን ያልፍ ነበር። በጤናማ-heap መንገድ በኩል ይህን ያህል በአንድ ጊዜ የሚካሄዱ leases ንቁ ከሆኑ በኋላ፣ ተጨማሪ የስራ ጫና ያለባቸው ጥያቄዎች በእውነተኛ heap ጫና ወቅት ወደሚጠቀመው ተመሳሳይ የተወሰነ-መጠበቂያ/ማስወገጃ መንገድ ይወርዳሉ። `0` ማለፊያውን ሙሉ በሙሉ ያሰናክላል። | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | bodyው ከባይት ወሰኑ በታች ቢሆንም የውይይት ጥያቄን ከባድ ብሎ የሚመድብ የመልዕክት ብዛት። | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | bodyው ከባይት ወሰኑ በታች ቢሆንም የውይይት ጥያቄን ከባድ ብሎ የሚመድብ የመሣሪያ ብዛት። | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ጥያቄን ከባድ ብሎ የሚመድብ ጥንቃቄ ያለው በሕብረቁምፊ መጠን ላይ የተመሠረተ የtoken ግምት፤ ይህ የመቀበያ ወጪ ግምታዊ መለኪያ እንጂ የአቅራቢ ክፍያ tokenization አይደለም። | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ተሰናክሏል) | `src/shared/middleware/chatBodyAdmission.ts` | አማራጭ በፈቃድ የሚነቃ የውይይት ታሪክ ገደብ። በነባሪ ተሰናክሏል፦ የመልዕክት ብዛት የስርዓት ማሰማራት ፖሊሲ እንጂ የጥያቄ ሁሉን አቀፍ ባህሪ አይደለም፣ እዚህ ላይ ገደብ ማድረግም የማመቅ ሂደቱ ሊስተናገዱ የሚችሉ ከማድረጉ በፊት ውይይቶችን በመጨረሻዊ `413` ውድቅ ያደርጋል። የheap እድገት በ`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` እና በheap-ጫና ማስወገጃው ይገደባል። ጥብቅ ጣሪያ በሚያስፈልጋቸው የማህደረ ትውስታ ገደብ ባለባቸው ማሰማራቶች ላይ አዎንታዊ እሴት ያዋቅሩ፤ ከገደቡ ያለፈ ጥያቄ ከዚያ የተዋቀረ ማመቅ-አስፈላጊ `413` ይቀበላል። | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ሙሉ በሙሉ ወደ ማህደረ ትውስታ ለሚከማች ዥረት-አልባ የላይኛው ምላሽ ጥብቅ ገደብ። ከዚህ ገደብ በላይ የላይኛው አንባቢ ይሰረዛል፣ እናም heap እስኪሟጠጥ ድረስ ገደብ የሌለው ሕብረቁምፊ እንዲያድግ ከማድረግ ይልቅ ጥያቄው በፍጥነት ይሳካለታል። | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ከወደላይ የሚመጡ የምላሽ ራስጌዎች የሚተላለፉበት ከፍተኛው የዋየር ባይት መጠን። በጀቱ ሲያልፍ፣ በተለመዱ የreverse-proxy ራስጌ ገደቦች ውስጥ ለመቆየት ዝቅተኛ ቅድሚያ ያላቸው ራስጌዎች (ለምሳሌ፣ ብጁ `x-codex-*`፣ `x-oai-request-id`) ይተዋሉ። በትልቅ የምላሽ ራስጌ መጠን ምትክ ተጨማሪ ከወደላይ የሚመጣ metadata ለማስተላለፍ ከፍ ያለ ዋጋ ያዘጋጁ። | -| `CORS_ORIGIN` | _(አልተዘጋጀም)_ | `src/server/cors/origins.ts` | የቆየ ባለአንድ-origin የCORS ፈቃድ ዝርዝር። ለአዳዲስ ስምሪቶች `CORS_ALLOWED_ORIGINS`ን ይምረጡ። CORS ለcross-origin የአሳሽ API ደንበኞች ብቻ ነው፤ ማንነታቸው የተረጋገጠ የዳሽቦርድ የመጻፍ ጥያቄዎች በምትኩ same-origin ጥያቄዎችን ከsession-bound CSRF ጥበቃ ጋር ይጠቀማሉ። | -| `CORS_ALLOWED_ORIGINS` | _(አልተዘጋጀም)_ | `src/server/cors/origins.ts` | በነጠላ ሰረዝ የተለየ የCORS ፈቃድ ዝርዝር። `CORS_ALLOW_ALL=true` በግልጽ ካልተዋቀረ በስተቀር ምንም wildcard አይላክም። | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ማንኛውንም የአሳሽ `Origin` መልሶ ለማስተጋባት ለልማት ብቻ የሚያገለግል የማለፊያ አማራጭ። በጋራ ወይም በproduction ስምሪቶች ላይ አያንቁት። | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | ወደ private/loopback/link-local IP ክልሎች የሚያመለክቱ የprovider ጥሪዎችን ያግዱ። በተነጠሉ የሙከራ አካባቢዎች ውስጥ ብቻ ያሰናክሉት። | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | ወደ private/local አውታረ መረቦች (localhost፣ 192.168.x.x፣ 10.x.x.x፣ ወዘተ) የሚያመለክቱ የprovider URLዎችን ፍቀድ። **በራስ ለሚስተናገዱ providerዎች ያስፈልጋል** (LM Studio፣ Ollama፣ vLLM፣ Llamafile፣ Triton፣ SearXNG)። `false` ሲሆን፣ ዳሽቦርዱ የlocal URLዎችን ማረጋገጥ ውድቅ ያደርጋል። | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | በlocal/private አድራሻዎች (127.0.0.1፣ localhost፣ LAN፣ private ክልሎች) ላይ providerዎችን ማከል/ማረጋገጥ ፍቀድ — ይህም በprovider ማረጋገጫ መንገድ ብቻ የተወሰነ ነው። **ነባሪው `true` ነው** (local-first)፤ public አድራሻዎችን ብቻ የሚፈቅድ ጥብቅ እገዳ ለማስፈጸም `false` ያዘጋጁ። የcloud-metadata endpoints (169.254.169.254፣ metadata.google.internal) ምንም ይሁን ምን እንደታገዱ ይቆያሉ። (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | የ`/v1/audio/*` መንገዶች (የጽሑፍ ቅጂዎች፣ ንግግር፣ ትርጉሞች) ከlocalhost ውጭ የተስተናገደ OpenAI-compatible provider node እንዲጠቀሙ ፍቀድ። በነባሪ የጠፋ ነው — audioን ወደ remote host መምራት የወጪ ትራፊክ ማንነትን ይቀይራል፣ ስለዚህ ግልጽ የኦፕሬተር ውሳኔ መሆን አለበት። Loopback/private nodes (localhost፣ 127.0.0.1፣ 172.16-31.x) ሁልጊዜ የተፈቀዱ ሲሆን ተጽዕኖም አያድርባቸውም። (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (እና የmemory engine loopback rerank ደረጃ) ከlocalhost ውጭ የተስተናገደ OpenAI-compatible provider node — TEI፣ Infinity፣ vLLM፣ ወዘተ የሚያስኬድ LAN ማሽን ወይም Tailscale peer — እንዲጠቀም ፍቀድ። በነባሪ የጠፋ ነው — ወደ remote host መምራት የወጪ ትራፊክ ማንነትን ይቀይራል፣ ስለዚህ ግልጽ የኦፕሬተር ውሳኔ መሆን አለበት። Loopback nodes (localhost፣ 127.0.0.1፣ 172.16-31.x) ሁልጊዜ የተፈቀዱ ሲሆን ተጽዕኖም አያድርባቸውም። Remote nodes በተጨማሪ የprovider outbound URL ፖሊሲን (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ማለፍ አለባቸው፤ የcloud-metadata hosts በፍጹም ወደእነሱ አይመሩም። | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ሲነቃ፣ ተጠቃሚዎች በOIDC Single Sign-On ብቻ ማንነታቸውን እንዲያረጋግጡ የይለፍ ቃል መግቢያን ያሰናክሉ። ቀጥተኛው alias `OIDC_DISABLE_PASSWORD_LOGIN` እንዲሁም ተቀባይነት አለው፤ ተመሳሳይ key ያለው የDashboard Feature Flag ቅድሚያ ይሰጠዋል። (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | የ`OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ቀጥተኛ alias (#10889)። | +| ተለዋዋጭ | ነባሪ | የምንጭ ፋይል | መግለጫ | +| ------------------------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | የማሽን አሻራ ለመፍጠር ከሃርድዌር መለያዎች ጋር የሚጣመር salt። ማግለልን ለማረጋገጥ በእያንዳንዱ deployment ይቀይሩት። | +| `OMNIROUTE_CLI_SALT` | _(ካልተዋቀረ = በእያንዳንዱ install በዘፈቀደ የሚፈጠር እና በ`/cli-token-salt.json` የሚቀመጥ salt)_ | `src/lib/machineToken.ts` | የአካባቢያዊውን CLI ማረጋገጫ token ለማመንጨት የሚያገለግል HMAC salt። ይህን እሴት ማዋቀር በማሽኑ ላይ ያሉትን ሁሉንም CLI tokens ያድሳል፣ እንዲሁም ሁልጊዜ ከተቀመጠው salt ይልቅ ቅድሚያ ያገኛል። `docs/security/CLI_TOKEN.md`ን ይመልከቱ። | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | በsession cookies ላይ የ`Secure` flagን ያዘጋጃል። ከHTTPS በስተጀርባ ሲሰራ **`true` መሆን አለበት**። | +| `REQUIRE_API_KEY` | `false` | API middleware | `true` ሲሆን፣ ሁሉም የ`/v1/*` proxy requests ትክክለኛ API key ማካተት አለባቸው። **ይህ flag `GET /v1/models`ን አይገድብም**፤ በምትኩ የDashboard login postureን (`requireAuthForModels`) ይከተላል — ስለዚህ ከ`/v1/models` የሚመጣ `401` inference የተጠበቀ ነው ማለት **አይደለም**። `docs/security/INFERENCE_AUTH_POSTURE.md`ን (#13695) ይመልከቱ። | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | በDashboard UI ውስጥ ሙሉ የAPI key እሴቶችን ማሳየት ያስችላል። ከDashboard Feature Flags ሊዋቀር ይችላል፤ በጋራ ጥቅም ላይ በሚውሉ instances ላይ የደህንነት አደጋ አለው። | +| `NO_LOG_API_KEY_IDS` | _(ባዶ)_ | `src/lib/compliance/index.ts` | request loggingን የሚያልፉ በነጠላ ሰረዝ የተለያዩ API key IDs (ለGDPR compliance)። | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ካልተዋቀረ = ያልተገደበ)_ | `src/shared/utils/apiKeyPolicy.ts` | የ`rate_limits` column null ለሆነባቸው API keys የሚተገበር ተተኪ የዕለታዊ request budget። ካልተዋቀረ ወይም ባዶ ከሆነ፦ በስውር የሚተገበር ገደብ የለም (#2289, #11017)። `0`ም ተመሳሳይ ነው (ያልተገደበ)። አዎንታዊ ኢንቲጀር N፣ N/ቀን፣ 5N/ሳምንት፣ 20N/ወር ያነቃል። ቅርጸት የተበላሸ ባዶ ያልሆነ እሴት ወደ ቀድሞው 1000/ቀን፣ 5000/ሳምንት፣ 20000/ወር windows ይመለሳል። | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ከፍተኛው የተፈቀደ request body መጠን። ከዚህ ገደብ የሚበልጡ payloadsን ውድቅ ያደርጋል። | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ከዚህ threshold ጋር እኩል ወይም በላይ የሆኑ ትክክለኛ request bodies JSON ከመተንተናቸው በፊት አቶሚካዊውን፣ process-local heavyweight admission lease ይወስዳሉ (BYTE path፣ `POST /v1/responses`ን ጨምሮ)። ከstructure-heavy ጋር ተመሳሳይ የ[#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) healthy-headroom escape ነው፤ አሁንም በ`OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) የተገደበ ስለሆነ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) እንደገና አይከሰትም። | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | የ`Content-Length` እሴት የጎደለ፣ ልክ ያልሆነ ወይም ሐሰተኛ የሆነባቸውን ጥያቄዎች ጨምሮ፣ በተወሰነ የማስገባት ሂደት ወቅት በተነበቡ ባይቶች ላይ የሚተገበር የውይይት መስመር ጥብቅ ገደብ፤ ገደቡን ያለፈ ጥያቄ `413` ይቀበላል። | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(አልተዋቀረም — የጥያቄ ብዛት ገደብ የለም)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ይህ የቆየ የጥያቄ-ብዛት ገደብ አሁን የሚተገበረው በግልጽ ሲዋቀር ብቻ ነው። ሳይዋቀር ከተተወ (ነባሪው)፣ ከባድ የውይይት ጥያቄዎችን መቀበል በምትኩ በ`OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ይገደባል — በ**አንድ ሂደት** (አንድ V8 heap) ውስጥ ካለው እውነተኛ የሂደቱ የማህደረ ትውስታ ጣሪያ በራስ-ሰር የሚሰላ የባይት በጀት። ሁለት ተደራራቢ ~~750k-token `/v1/responses` በ~~12 Gi heaps ላይ መቋረጥ ያስከትላሉ (#7849) — ይህ **የማህደረ ትውስታ በጀት** ማስጠንቀቂያ እንጂ የምርቱ ጥብቅ ከፍተኛ ገደብ 2 ነው ማለት አይደለም። ጤናማ ሂደት (heap ከማስወገጃ ጥምርታው በታች ሲሆን) በ`OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` በኩል ተጨማሪ በአንድ ጊዜ የሚካሄዱ ረጅም `/v1/responses` ሊቀበል ይችላል። በአስሮች የሚቆጠሩ ረጅም SSE ደንበኞች (40–50) ጉዳይ heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ነው፣ “ከፍተኛው 2” አይደለም። ይህን “የአስተናጋጁን አቅም ለመጠቀም” በግዴለሽነት ከፍ ማድረግ #7849ን እንደገና ያስከትላል። **heaps**ን በ**N ነጻ `DATA_DIR`s** (#11024) ያባዙ፤ በአንድ SQLite ፋይል ላይ `replicas>1` ፈጽሞ አይጠቀሙ። | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(በራስ-ሰር የሚሰላ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** በራስ-ሰር ለሚሰላው የማስገቢያ ባይት በጀት የሚደረግ ማሻሻያ (ከV8/cgroup የማህደረ ትውስታ ጣሪያዎች ይበልጥ ጥብቅ ከሆነው 25%፣ በ8x ጊዜያዊ ማጉላት የተከፈለ)። የተሰሉ እና በግልጽ የተዋቀሩ እሴቶች በ8 MiB–2 GiB መካከል ይገደባሉ። ከተግባራዊው በጀት የሚበልጥ body ወዲያውኑ በ`413 body_exceeds_budget` ይሳካለታል፤ በተናጠል ሊስተናገዱ በሚችሉ bodies መካከል ያለ ፉክክር እንደገና ሊሞከር የሚችል `503` ሆኖ ይቆያል። 40–50 በአንድ ጊዜ የሚገናኙ ረጅም SSE ደንበኞች ጉዳይ ይህ በጀት + heap ነው፣ ጥብቅ “ከፍተኛው 2” አይደለም። ከማስተካከልዎ በፊት በ`/api/monitoring/health` ላይ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`ን ያንብቡ። | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | ለBYTE እና STRUCTURE ከባድ ጥያቄዎች መቀበል የheap-ጫና ማስወገጃ ጥምርታ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)። `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`ን ያለፈ በአንድ ጊዜ የሚካሄድ ከባድ ጥያቄ እንደገና ሊሞከር በሚችለው `503` የሚወገደው heap ደግሞ በዚህ ጥምርታ ወይም ከዚያ በላይ ሲሆን ብቻ ነው፤ ጤናማ heap ላይ በhealthy-headroom በኩል ይቀበላል። | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ነባሪ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | ለጤናማ-heap ፈጣን መንገድ (#10437) በ**ሁለቱም** STRUCTURE እና BYTE (`admitChatRequest`፣ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` የሆኑ bodiesን ጨምሮ) የተወሰነ ተጨማሪ አቅም። ያለዚህ ገደብ፣ heap ጤናማ ሆኖ ስራ የበዛበት እያንዳንዱ ጥያቄ ያለ ጣሪያ የመቀበያ ሂደቱን ያልፍ ነበር። በጤናማ-heap መንገድ በኩል ይህን ያህል በአንድ ጊዜ የሚካሄዱ leases ንቁ ከሆኑ በኋላ፣ ተጨማሪ የስራ ጫና ያለባቸው ጥያቄዎች በእውነተኛ heap ጫና ወቅት ወደሚጠቀመው ተመሳሳይ የተወሰነ-መጠበቂያ/ማስወገጃ መንገድ ይወርዳሉ። `0` ማለፊያውን ሙሉ በሙሉ ያሰናክላል። | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | bodyው ከባይት ወሰኑ በታች ቢሆንም የውይይት ጥያቄን ከባድ ብሎ የሚመድብ የመልዕክት ብዛት። | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | bodyው ከባይት ወሰኑ በታች ቢሆንም የውይይት ጥያቄን ከባድ ብሎ የሚመድብ የመሣሪያ ብዛት። | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ጥያቄን ከባድ ብሎ የሚመድብ ጥንቃቄ ያለው በሕብረቁምፊ መጠን ላይ የተመሠረተ የtoken ግምት፤ ይህ የመቀበያ ወጪ ግምታዊ መለኪያ እንጂ የአቅራቢ ክፍያ tokenization አይደለም። | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ተሰናክሏል) | `src/shared/middleware/chatBodyAdmission.ts` | አማራጭ በፈቃድ የሚነቃ የውይይት ታሪክ ገደብ። በነባሪ ተሰናክሏል፦ የመልዕክት ብዛት የስርዓት ማሰማራት ፖሊሲ እንጂ የጥያቄ ሁሉን አቀፍ ባህሪ አይደለም፣ እዚህ ላይ ገደብ ማድረግም የማመቅ ሂደቱ ሊስተናገዱ የሚችሉ ከማድረጉ በፊት ውይይቶችን በመጨረሻዊ `413` ውድቅ ያደርጋል። የheap እድገት በ`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` እና በheap-ጫና ማስወገጃው ይገደባል። ጥብቅ ጣሪያ በሚያስፈልጋቸው የማህደረ ትውስታ ገደብ ባለባቸው ማሰማራቶች ላይ አዎንታዊ እሴት ያዋቅሩ፤ ከገደቡ ያለፈ ጥያቄ ከዚያ የተዋቀረ ማመቅ-አስፈላጊ `413` ይቀበላል። | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ሙሉ በሙሉ ወደ ማህደረ ትውስታ ለሚከማች ዥረት-አልባ የላይኛው ምላሽ ጥብቅ ገደብ። ከዚህ ገደብ በላይ የላይኛው አንባቢ ይሰረዛል፣ እናም heap እስኪሟጠጥ ድረስ ገደብ የሌለው ሕብረቁምፊ እንዲያድግ ከማድረግ ይልቅ ጥያቄው በፍጥነት ይሳካለታል። | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ከወደላይ የሚመጡ የምላሽ ራስጌዎች የሚተላለፉበት ከፍተኛው የዋየር ባይት መጠን። በጀቱ ሲያልፍ፣ በተለመዱ የreverse-proxy ራስጌ ገደቦች ውስጥ ለመቆየት ዝቅተኛ ቅድሚያ ያላቸው ራስጌዎች (ለምሳሌ፣ ብጁ `x-codex-*`፣ `x-oai-request-id`) ይተዋሉ። በትልቅ የምላሽ ራስጌ መጠን ምትክ ተጨማሪ ከወደላይ የሚመጣ metadata ለማስተላለፍ ከፍ ያለ ዋጋ ያዘጋጁ። | +| `CORS_ORIGIN` | _(አልተዘጋጀም)_ | `src/server/cors/origins.ts` | የቆየ ባለአንድ-origin የCORS ፈቃድ ዝርዝር። ለአዳዲስ ስምሪቶች `CORS_ALLOWED_ORIGINS`ን ይምረጡ። CORS ለcross-origin የአሳሽ API ደንበኞች ብቻ ነው፤ ማንነታቸው የተረጋገጠ የዳሽቦርድ የመጻፍ ጥያቄዎች በምትኩ same-origin ጥያቄዎችን ከsession-bound CSRF ጥበቃ ጋር ይጠቀማሉ። | +| `CORS_ALLOWED_ORIGINS` | _(አልተዘጋጀም)_ | `src/server/cors/origins.ts` | በነጠላ ሰረዝ የተለየ የCORS ፈቃድ ዝርዝር። `CORS_ALLOW_ALL=true` በግልጽ ካልተዋቀረ በስተቀር ምንም wildcard አይላክም። | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ማንኛውንም የአሳሽ `Origin` መልሶ ለማስተጋባት ለልማት ብቻ የሚያገለግል የማለፊያ አማራጭ። በጋራ ወይም በproduction ስምሪቶች ላይ አያንቁት። | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | የድሮ alias። የዚህ ተለዋዋጭ ዳሽቦርድ toggle (የDB override) ከዚህ የአካባቢ እሴት በፊት ይነበባል፤ በማንኛውም አንዱ ውስጥ `false`፣ `0`፣ `no` ወይም `off` ሲሆን፣ የዚያ የflag ዳሽቦርድ toggle እንደተቀመጠ ቢባልም እንኳ፣ የhost ማረጋገጫዎችን `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` እንደሚያጠፋው በተመሳሳይ መንገድ ያጠፋቸዋል፤ ሌሎች እሴቶች ግን ጠባቂውን (guard) ለከውስጠኞቹ ሁለት flags ይተወዋሉ። | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | የoutbound URL guard የhost ማረጋገጫዎችን፣ የcloud-metadata እገዳን ጨምሮ (ጠቅላላው 169.254.0.0/16፣ metadata.google.internal)፣ የguard mode ተከትለው በሚሄዱ የprovider መንገዶች ላይ ያጠፋል፦ URL validation፣ የmodel discovery፣ የprovider-node base URLዎች (remote rerank nodesን ጨምሮ) እና የproxy-fallback ሙከራ፣ እንዲሁም እንደ remote image/media fetch ያሉ ሌሎች provider-አጎራባች outbound መንገዶች። እንዲሁም የprivate webhook targets እንዲፈቀዱ ያደርጋል፣ ነገር ግን cloud-metadata/link-local endpoints በዚህ flag በሆነ ጊዜም እንኳ የታገዱ ይቆያሉ። በ`BaseExecutor.execute()` በኩል የሚላኑ የchat ጥያቄዎች ግን የcloud-metadata ማረጋገጣቸውን ይጠብቃሉ። Local providers በነባሪዎቹ ይሠራሉ፦ `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ነባሪ `true`) በvalidation፣ discovery እና provider-node መንገዶች ላይ local እና LAN URLዎችን ቀደም ሲል ይፈቅዳል፤ የproxy-fallback ሙከራ እና private webhook targets ግን ይህንን flag ብቻ ይመለከታሉ፣ እሱ እስኪያብራ ድረስም local/LAN hostsን ያገዳሉ። `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`ን ይሽራል። ከዳሽቦርድ toggle የተቀመጠ እሴት ከዚህ ተለዋዋጭ በላይ ተመራጭ ነው። | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ለprovider validation፣ የmodel discovery፣ chat እና guard mode በሚመራቸው ሌሎች provider-አጎራባች outbound መንገዶች በlocal/private አድራሻዎች (127.0.0.1፣ localhost፣ LAN፣ private ክልሎች) ላይ ያሉ የprovider URLዎችን ፍቀድ። **ነባሪ `true`** (local-first)፦ guard ውስጥ ያለው የcloud-metadata endpoints እገዳ በዚያን ጊዜ ያገዳል — ጠቅላላው 169.254.0.0/16 እና የታወቁ የmetadata hostnames (metadata.google.internal፣ metadata.goog፣ Alibaba/Azure/Oracle IMDS፣ fd00:ec2::254)። ለpublic-ብቻ mode `false` ያዘጋጁ — ይህም private እና loopback hostsን ጨምሮ ያገዳል። Guard የhostname ወይም የIP literal እንደተጻፈው ከማንኛውም DNS lookup በፊት ያረጋግጣል፤ በpublic-ብቻ mode ደግሞ safe-fetch wrapper hostnameን ተጨማሪ ፈትቶ ማንኛውንም private መልስ ውድቅ ያደርጋል። `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` እነዚህን ማረጋገጫዎች በራሱ ረድፍ በተዘረዘሩት መንገዶች ላይ ያጠፋል። እንደ LM Studio፣ Ollama፣ vLLM፣ Llamafile እና Triton ያሉ built-in local providers ለkey validation እና chat ሁሉንም የhost ማረጋገጫዎች ይዘላሉ። ከዳሽቦርድ toggle የተቀመጠ እሴት ከዚህ ተለዋዋጭ በላይ ተመራጭ ነው። (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | የ`/v1/audio/*` መንገዶች (የጽሑፍ ቅጂዎች፣ ንግግር፣ ትርጉሞች) ከlocalhost ውጭ የተስተናገደ OpenAI-compatible provider node እንዲጠቀሙ ፍቀድ። በነባሪ የጠፋ ነው — audioን ወደ remote host መምራት የወጪ ትራፊክ ማንነትን ይቀይራል፣ ስለዚህ ግልጽ የኦፕሬተር ውሳኔ መሆን አለበት። Loopback/private nodes (localhost፣ 127.0.0.1፣ 172.16-31.x) ሁልጊዜ የተፈቀዱ ሲሆን ተጽዕኖም አያድርባቸውም። (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (እና የmemory engine loopback rerank ደረጃ) ከlocalhost ውጭ የተስተናገደ OpenAI-compatible provider node — TEI፣ Infinity፣ vLLM፣ ወዘተ የሚያስኬድ LAN ማሽን ወይም Tailscale peer — እንዲጠቀም ፍቀድ። በነባሪ የጠፋ ነው — ወደ remote host መምራት የወጪ ትራፊክ ማንነትን ይቀይራል፣ ስለዚህ ግልጽ የኦፕሬተር ውሳኔ መሆን አለበት። Loopback nodes (localhost፣ 127.0.0.1፣ 172.16-31.x) ሁልጊዜ የተፈቀዱ ሲሆን ተጽዕኖም አያድርባቸውም። የremote node base URLም የprovider outbound URL ፖሊሲ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) መተላለፍ አለበት። | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ሲነቃ፣ ተጠቃሚዎች በOIDC Single Sign-On ብቻ ማንነታቸውን እንዲያረጋግጡ የይለፍ ቃል መግቢያን ያሰናክሉ። ቀጥተኛው alias `OIDC_DISABLE_PASSWORD_LOGIN` እንዲሁም ተቀባይነት አለው፤ ተመሳሳይ key ያለው የDashboard Feature Flag ቅድሚያ ይሰጠዋል። (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | የ`OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ቀጥተኛ alias (#10889)። | ### የደህንነት ማጠናከሪያ ማረጋገጫ ዝርዝር diff --git a/docs/i18n/ar/CONTRIBUTING.md b/docs/i18n/ar/CONTRIBUTING.md index 4ae77d2d20f3..5a51fb16d976 100644 --- a/docs/i18n/ar/CONTRIBUTING.md +++ b/docs/i18n/ar/CONTRIBUTING.md @@ -390,19 +390,19 @@ docs/ # Documentation ## قائمة التحقق لطلب السحب - [ ] نجاح الاختبارات (`npm test`) -- [ ] اجتياز فحص التنسيق (`npm run lint`) -- [ ] نجاح عملية البناء (`npm run build`) +- [ ] نجاح فحص التنسيق (`npm run lint`) +- [ ] نجاح البناء (`npm run build`) - [ ] إضافة أنواع TypeScript للدوال والواجهات العامة الجديدة - [ ] عدم وجود أسرار أو قيم احتياطية مضمنة مباشرةً -- [ ] تضمين بيانات اعتماد المصادر العامة عبر `resolvePublicCred()` (راجع [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md))، وليس كقيم حرفية مطلقًا -- [ ] تمرير استجابات الأخطاء عبر `buildErrorBody()` / `sanitizeErrorMessage()` — من دون آثار مكدس خام في نصوص الاستجابة (راجع [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] تضمين بيانات اعتماد المنبع العامة عبر `resolvePublicCred()` (راجع [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md))، وعدم تضمينها أبدًا كقيم حرفية +- [ ] تمرير استجابات الأخطاء عبر `buildErrorBody()` / `sanitizeErrorMessage()` — وعدم تضمين تتبعات المكدس الأولية في نصوص الاستجابات (راجع [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] تمرير أوامر الصدفة (`exec` / `spawn`) قيم وقت التشغيل عبر `env`، وليس عبر استيفاء السلاسل النصية - [ ] التحقق من صحة جميع المدخلات باستخدام مخططات Zod -- [ ] إضافة **جزء** من سجل التغييرات ضمن `changelog.d/{features|fixes|maintenance}/-.md` للتغييرات الظاهرة للمستخدم (راجع [`changelog.d/README.md`](./changelog.d/README.md)) — **لا** تعدّل `CHANGELOG.md` مباشرةً؛ إذ تُجمّع الأجزاء عند الإصدار ولا تتعارض مطلقًا بين طلبات السحب -- [ ] تحديث الوثائق (إن كان ذلك منطبقًا) -- [ ] عدم فتح أي تنبيهات جديدة من CodeQL / Secret-Scanning، أو رفض كل تنبيه مع تقديم مبرر تقني يشير إلى المستند ذي الصلة ضمن `docs/security/` -- [ ] تصنيف المسارات التي تُنشئ عمليات فرعية (`/api/mcp/`، و`/api/cli-tools/runtime/`) على أنها `isLocalOnlyPath()` في `src/server/authz/routeGuard.ts` — راجع [القاعدة الصارمة رقم 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] عدم وجود تذييلات `Co-Authored-By` في رسائل الالتزام — يجب أن تظهر الالتزامات حصريًا ضمن هوية Git الخاصة بمالك المستودع (القاعدة الصارمة رقم 16) +- [ ] إضافة **جزء** من سجل التغييرات ضمن `changelog.d/{features|fixes|maintenance}/-.md` للتغييرات التي تؤثر في المستخدم (راجع [`changelog.d/README.md`](./changelog.d/README.md)) — لا تعدّل `CHANGELOG.md` مباشرةً؛ إذ تُجمّع الأجزاء عند الإصدار ولا تتعارض أبدًا بين طلبات السحب +- [ ] تحديث الوثائق (إن أمكن) +- [ ] عدم فتح أي تنبيهات جديدة من CodeQL / Secret-Scanning، أو رفض كل تنبيه مع تقديم مبرر تقني يشير إلى مستند `docs/security/` ذي الصلة +- [ ] تصنيف المسارات التي تنشئ عمليات فرعية (`/api/mcp/`، و`/api/cli-tools/runtime/`) على أنها `isLocalOnlyPath()` في `src/server/authz/routeGuard.ts` — راجع [القاعدة الصارمة رقم 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] عدم تضمين تذييلات `Co-authored-by` خاصة بالذكاء الاصطناعي/الروبوتات في رسائل الالتزام (القاعدة الصارمة رقم 16) — يُنسب الفضل إلى المتعاونين البشريين الذين أُعيد استخدام عملهم باستخدام تذييلات `Co-authored-by: Name ` القياسية --- diff --git a/docs/i18n/ar/docs/architecture/QUALITY_GATES.md b/docs/i18n/ar/docs/architecture/QUALITY_GATES.md index 04972bf9248f..c7eb271ee825 100644 --- a/docs/i18n/ar/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ar/docs/architecture/QUALITY_GATES.md @@ -15,213 +15,212 @@ --- -## حصر البوابات وملفات تعريف التنفيذ +## مخزون البوابات وملفات تعريف التنفيذ ### قبول المرشحين -يُصدر كل من سيري عمل CI وبوابات الجودة حكمًا ثابتًا: `Gate / CI` و -`Gate / Quality`. وتسرد سياسة القبول ذات الإصدارات الخاصة بهما كل مهمة سابقة -بوصفها مطلوبة أو استشارية. يجب أن تنجح أي مهمة مطلوبة قابلة للتطبيق: فالنتائج -المفقودة أو الملغاة أو المتخطاة أو المعلقة أو غير المعروفة لا يمكنها إثبات PASS. يمكن -لتصنيف صالح خاص بالتوثيق فقط أو بالكتالوج فقط أن يجعل مسار الشيفرة غير قابل للتطبيق؛ -ولا يُعد طلب السحب المسودة مرشحًا مقبولًا. ولا تعفي التسمية `hotfix` من تقديم الأدلة. - -يغطي كلا سيري العمل طلبات السحب وعمليات الدفع إلى الفروع الرئيسية/فروع الإصدار، والتشغيل -اليدوي، وأحداث مجموعات الدمج. وتُشغّل عمليات الدفع والتشغيل اليدوي ومجموعات الدمج التحديد -الكامل. تستخدم التفرعات ومجموعات الدمج مشغّلات مستضافة للمهام التي تختار عادةً مشغّلات -ذاتية الاستضافة؛ ويجب التحقق من توفر سعة مستضافة كافية قبل الطرح. - -يحدد كل إيصال JSON قيمة SHA التي جرى جلبها، وتشغيل سير العمل، والمحاولة. -وترفض CLI عدم تطابق SHA بين النسخة المجلوبة والحدث. تربط اختبارات سير العمل عضوية السياسة -بقائمة `needs` الخاصة بمهمة الحكم، بحيث لا يمكن لمسار جديد أو محذوف أن يختفي بصمت. +يُصدر كل من سيري عمل CI وبوابات الجودة حكمًا مستقرًا: `Gate / CI` و +`Gate / Quality`. وتسرد سياسة القبول ذات الإصدارات الخاصة بهما كل مهمة مصدرية +على أنها مطلوبة أو استشارية. يجب أن تنجح أي مهمة مطلوبة منطبقة: فالنتائج +المفقودة أو الملغاة أو المتخطاة أو المعلقة أو غير المعروفة لا يمكنها إثبات PASS. ويمكن +لتصنيف صالح خاص بالوثائق فقط أو بالكتالوج فقط أن يجعل مسارًا برمجيًا غير منطبق؛ +ولا يُعد طلب السحب في وضع المسودة مرشحًا مقبولًا. كما أن تسمية `hotfix` لا تُعفي من تقديم الأدلة. + +يغطي كلا سيري العمل طلبات السحب وعمليات الدفع إلى فروع main/release، والتشغيل اليدوي، +وأحداث مجموعات الدمج. وتُشغّل عمليات الدفع والتشغيل اليدوي ومجموعات الدمج التحديد الكامل. تستخدم +التفرعات ومجموعات الدمج مشغّلات مستضافة للمهام التي تختار مشغّلات ذاتية الاستضافة في الحالات الأخرى؛ +ويجب التحقق من توفر سعة استضافة كافية قبل الطرح. + +يحدد كل إيصال JSON قيمة SHA التي تم سحبها، وتشغيل سير العمل، والمحاولة. +ترفض CLI عدم تطابق SHA بين النسخة المسحوبة والحدث. تربط اختبارات سير العمل عضوية السياسة +بقائمة `needs` الخاصة بمهمة الحكم، بحيث لا يمكن لمسار جديد أو مُزال أن يختفي بصمت. تغطي الإيصالات سير العمل الخاص بها، وليس النشر أو التوزيع أو التفاصيل الداخلية -لأداة فحص استشارية موجودة. ويُعد تفعيل اسمي الفحص في قواعد الفروع تغييرًا -إداريًا منفصلًا؛ فإضافة هذه المهام لا تحمي الفرع بحد ذاتها. +لأداة فحص استشارية موجودة. ويُعد تفعيل اسمي الفحص كليهما في قواعد الفروع +تغييرًا إداريًا منفصلًا؛ فإضافة هذه المهام لا تحمي فرعًا بحد ذاتها. -### حصر الفحص الثابت +### مخزون الفحص الساكن -يوجد حصر الأسماء المستعارة لـ npm ذات الإصدارات وعضوية الفحص الثابت في +يوجد مخزون الأسماء المستعارة لـ npm ذو الإصدارات وعضوية الفحص الساكن في `config/quality/gate-manifest.json`. شغّل `npm run check:gate-manifest` للتحقق -من أسماء البرامج النصية والأوامر الدقيقة بمقارنتها مع `package.json`؛ وتتسبب الإضافات -وعمليات الحذف وانحراف الأوامر في فشل كل من الخطاف المحلي ومهام تصنيف التغييرات في CI. -الاسم المستعار ليس مهمة في سير العمل، ولا مثيلًا في مصفوفة، ولا حالة اختبار: لذلك يجب -عدم عرض هذه الأعداد كما لو كانت قابلة للتبادل. +من أسماء البرامج النصية والأوامر الدقيقة بمقارنتها مع `package.json`؛ تؤدي الإضافات وعمليات الإزالة +وانحراف الأوامر إلى فشل كل من الخطاف المحلي ومهام تصنيف التغييرات في CI. +الاسم المستعار ليس مهمة سير عمل أو مثيل مصفوفة أو حالة اختبار: يجب +عدم عرض هذه الأعداد على أنها قابلة للتبادل. استخدم `npm run quality:scan -- --list` أو `npm run quality:scan:fast -- --list` -لفحص الأسماء المستعارة المحددة دون تنفيذها. يستدعي المشغّل نقطة دخول npm، ولذلك تُحفظ -بيئة تشغيلها (بما في ذلك Bun حيثما تكون مهيأة). يسجل البيان الأسماء المستعارة الواقعة خارج -ملفات التعريف هذه بوصفها مستدعاة بصورة منفصلة، وتُحظر أوامر الصيانة في ملفات تعريف الفحص -المخصصة للقراءة فقط. +لفحص الأسماء المستعارة المحددة دون تنفيذها. يستدعي المشغّل نقطة دخول +npm، ولذلك تُحفظ بيئة تشغيلها (بما في ذلك Bun حيث يكون مهيأ). +يسجل البيان الأسماء المستعارة الواقعة خارج ملفات التعريف هذه على أنها تُستدعى بشكل منفصل، كما +تُحظر أوامر الصيانة في ملفات تعريف الفحص للقراءة فقط. -لا تغطي ملفات التعريف هذه سوى الفحص الثابت. وهي لا تصادق على اختبارات المنتج +تغطي ملفات التعريف هذه الفحص الساكن فقط. وهي لا تعتمد اختبارات المنتج أو التغطية أو التحزيم أو الفحوص الخارجية أو القبول الكامل للمرشح للإصدار. يستخدم قبول سير العمل الملف المرتبط `config/quality/admission-policy.json` و -`scripts/quality/admission-verdict.mjs`. وتظل ملفات تعريف مراقبة الإصدار منفصلة؛ -افحص فحوصها وإيصالاتها القابلة للتطبيق بصورة مستقلة. ويُعد الحصر النصي -أدناه مرجعًا، وليس دليلًا على تشغيل إحدى البوابات فعليًا. - -توجد البرامج النصية ضمن `scripts/check/` (بوابات السياسة) و`scripts/quality/` (محرك التصعيد التدريجي). -ومصدر الحقيقة الخاص بـ CI هو `.github/workflows/ci.yml`. - -### المسار السريع لطلب سحب الإصدار (`quality.yml`) - -يُكمل `.github/workflows/quality.yml` ‏CI في طلبات السحب الخاصة بالفروع الرئيسية/فروع الإصدار، -وعمليات الدفع إلى الفروع المحمية، والتشغيل اليدوي، ومجموعات الدمج. تستخدم طلبات السحب فحوصًا -سريعة مرشحة حسب المسار. وقد أزيلت عملية البناء المكررة والمعطلة بصورة دائمة؛ بينما تظل فحوص -البناء/التحزيم/الإقلاع الفعلية ضمن CI. - -| المهمة | النطاق | الحجب | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `Docs Gates (fast-path)` | طلبات سحب التوثيق/الشيفرة؛ مراجع توثيق API وكل التوثيق | نعم | -| `Fast Quality Gates` | طلبات سحب الشيفرة؛ الفحوص الثابتة، وفحص الأنواع، وفحص أنواع لوحة المعلومات، واختبارات الوحدات المتأثرة | نعم | -| `Forgotten sibling tests` | طلبات سحب الشيفرة؛ تتبع الوحدات المتغيرة وصولًا إلى المستهلكين الثابتين واختبارات الأشقاء المرشحة؛ ويُبلّغ عن مسارات barrel والاستيراد الديناميكي بوصفها تشخيصات استشارية، مع استثناءات قائمة السماح المشار إليها | **استشاري** | -| `Vitest (fast-path)` | طلبات سحب الشيفرة؛ حزمة vitest السريعة | نعم | -| `Unit Tests fast-path` | طلبات سحب الشيفرة؛ حزمة اختبارات الوحدات ذات الأجزاء الأربعة | نعم | -| `No new ESLint warnings` | طلبات سحب الشيفرة؛ حارس lint المراعي لعمليات الكبت | نعم، بما في ذلك التفرعات | -| `Merge integrity (changelog + generated skills)` | طلبات السحب غير المسودة؛ مزامنة سجل التغييرات والمهارات المُنشأة | نعم، بما في ذلك التفرعات | - -#### تقرير اختبارات الأشقاء المنسية - -يعيد `npm run check:forgotten-sibling-tests` استخدام محلل الاستيراد الذي تستند إليه خريطة تأثير الاختبارات. -وبالنسبة إلى كل وحدة إنتاج متغيرة، فإنه يبلّغ عن سلاسل حتمية من النوع -`changed module/symbol -> static consumer -> candidate sibling test` عندما لا يكون الاختبار -المرشح موجودًا ضمن فروق طلب السحب. ويُحتفظ بملخص Markdown ونتيجة JSON بوصفهما -أثر سير العمل `forgotten-sibling-tests` لأغراض المعايرة قبل أي طرح حاجب. - -عمليات إعادة التصدير عبر ملفات التجميع والاستيرادات الديناميكية هي أدوات تشخيص للدقة فقط؛ ولا تؤدي مطلقًا إلى إنشاء -نتيجة مانعة. توجد الاستثناءات التي تمت مراجعتها في +`scripts/quality/admission-verdict.mjs`. وتظل ملفات تعريف مراقب الإصدار منفصلة؛ +افحص فحوصها المنطبقة وإيصالاتها بشكل مستقل. المخزون +النثري أدناه مرجع، وليس دليلًا على أن بوابة ما قد شُغّلت بالفعل. + +توجد البرامج النصية ضمن `scripts/check/` (بوابات السياسة) و`scripts/quality/` (محرك التصعيد). +مصدر الحقيقة الخاص بـ CI هو `.github/workflows/ci.yml`. + +### المسار السريع لطلبات سحب الإصدار (`quality.yml`) + +يُكمل `.github/workflows/quality.yml` ‏CI في طلبات السحب الخاصة بـ main/release، وعمليات الدفع +إلى الفروع المحمية، والتشغيل اليدوي، ومجموعات الدمج. تستخدم طلبات السحب فحوصًا سريعة مرشحة حسب المسار. وقد أزيل +بناء النسخة المكرر والمعطل بشكل دائم؛ بينما تظل فحوص البناء/التحزيم/الإقلاع الفعلية في CI. + +| المهمة | النطاق | الحظر | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `Docs Gates (fast-path)` | طلبات سحب الوثائق/الشيفرة؛ مراجع وثائق API وdocs-all | نعم | +| `Fast Quality Gates` | طلبات سحب الشيفرة؛ الفحوص الساكنة، وفحص الأنواع، وفحص أنواع لوحة المعلومات، واختبارات الوحدات المتأثرة | نعم | +| `Forgotten sibling tests` | طلبات سحب الشيفرة؛ تتبّع الوحدات المعدّلة إلى المستهلكين الساكنين واختبارات الوحدات الشقيقة المرشحة؛ تُبلّغ مسارات barrel والاستيراد الديناميكي بوصفها تشخيصات استشارية، مع استثناءات قائمة السماح المشار إليها | **استشاري** | +| `Vitest (fast-path)` | طلبات سحب الشيفرة؛ حزمة vitest السريعة | نعم | +| `Unit Tests fast-path` | طلبات سحب الشيفرة؛ حزمة اختبارات وحدات موزعة على 4 أجزاء | نعم | +| `No new ESLint warnings` | طلبات سحب الشيفرة؛ حارس lint مدرك لعمليات الكبت | نعم، بما في ذلك التفرعات | +| `Merge integrity (changelog + generated skills)` | طلبات السحب غير المسودة؛ مزامنة سجل التغييرات والمهارات المُنشأة | نعم، بما في ذلك التفرعات | + +#### تقرير اختبارات الوحدات الشقيقة المنسية + +يعيد `npm run check:forgotten-sibling-tests` استخدام محلّل الاستيراد الذي تستند إليه خريطة تأثير الاختبارات. +وبالنسبة إلى كل وحدة إنتاج معدّلة، فإنه يبلّغ عن سلاسل حتمية من نوع +`changed module/symbol -> static consumer -> candidate sibling test` عندما يكون الاختبار المرشح +غائبًا عن فرق طلب السحب. ويُحتفظ بملخص Markdown ونتيجة JSON بوصفهما +عنصر سير العمل `forgotten-sibling-tests` لأغراض المعايرة قبل أي طرح حاجب. + +إعادات التصدير المجمّعة والاستيرادات الديناميكية هي تشخيصات لحلّ الوحدات فقط؛ ولا تُنشئ مطلقًا +نتيجةً مانعة. توجد الاستثناءات المُراجَعة في `config/quality/forgotten-sibling-allowlist.json`. يجب أن يحدّد كل إدخال المستهلك والاختبار المرشّح، -وأن يقدّم مبررًا محددًا، وأن يتضمن رابطًا إلى مشكلة أو طلب سحب على GitHub. تؤدي الإدخالات المشوّهة إلى -الرفض افتراضيًا. لا يمكن للاستثناءات إخفاء اختبار مرشّح محذوف أو فرق يضيف `.skip`/`.todo`؛ -ويظل إضعاف التأكيدات وغيره من أساليب الإخفاء ضمن مسؤولية بوابة +وأن يقدّم مبررًا محددًا، وأن يتضمن رابطًا لمشكلة أو طلب سحب على GitHub. تؤدي الإدخالات غير الصحيحة +إلى الرفض افتراضيًا. لا يمكن للاستثناءات تجاوز اختبار مرشّح محذوف أو فرق يضيف `.skip`/`.todo`؛ +ويظل إضعاف التأكيدات وغيره من أساليب الإخفاء خاضعًا لبوابة `check:test-masking` المانعة بشكل مستقل. ### المهمة: `lint` -تعمل مع كل طلب سحب إلى `main`. تمنع الدمج عند الفشل. - -| البرنامج النصي (`npm run ...`) | ما يتحقق منه | مانع | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -| `check:node-runtime` | وجود إصدار Node.js ضمن النطاق المدعوم | نعم | -| `check:cycles` | الاستيرادات الدائرية — جميع وحدات `src/` + `open-sse/` | نعم | -| `check:route-validation:t06` | وجود مخططات Zod في جميع المسارات (سياسة المستوى 6) | نعم | -| `check:any-budget:t11` | ألّا يتجاوز عدد `@ts-expect-error // any` الميزانية المحددة (بوابة المستوى 11) | نعم | -| `check:provider-consistency` | لكل موفّر في `providers.ts` إدخال مطابق في `providerRegistry.ts` (والعكس صحيح، ضمن قائمة السماح) | نعم | -| `check:model-lifecycle` | تظل جداول التوجيه الثلاثة التي تُصان يدويًا متسقة مع لقطة دورة الحياة المحفوظة في المستودع (#11503): لا يمنح `FITNESS_TABLE` (`taskFitness.ts`) درجات لأي معرّف متقاعد يمكن لـ `REGISTRY` توجيهه؛ وكل هدف في `BUILT_IN_ALIASES` موجود في `REGISTRY` وغير موجود في لقطة المعرّفات المتقاعدة؛ وكل معرّف متقاعد لا يزال في `REGISTRY` تتم إعادة توجيهه أو إدراجه في `allowedRetiredInCatalog`؛ ولا يظهر أي مصدر أو هدف من `DEFAULT_DEGRADATION_MAP` كمتقاعد في تلك اللقطة. لا يثبت هذا أن نموذجًا ما تتم خدمته حاليًا بواسطة مصدر علوي فعّال. يعمل دون اتصال — إذ يقارن مع `config/quality/model-lifecycle.json`، الذي يُحدَّث يدويًا باستخدام `npm run quality:refresh-model-lifecycle` (يتطلب الشبكة؛ وغير مدمج في CI). تمثل `allowedRetiredInCatalog` آلية تناقص تدريجي: لا تُضف إدخالًا إلا مع مشكلة تتبع. | نعم | -| `check:fetch-targets` | كل استدعاء `fetch("/api/...")` في `src/` من جانب العميل يُحلّ إلى ملف `route.ts` فعلي | نعم | -| `check:deps` | جميع التبعيات القابلة للتثبيت باستخدام `npm install` عبر كل ملف `package.json` في المستودع موجودة في `dependency-allowlist.json`؛ ويتم تمييز الحزم الجديدة غير المثبّتة بإصدار محدد أو التي يُشتبه في انتحالها لأسماء حزم أخرى | نعم | -| `audit:deps` | `npm audit` (الجذر + Electron) — لا توجد تنبيهات عالية/حرجة (يتداخل مع `check:vuln-ratchet` الخاص بـ OSV؛ راجع قائمة الأعمال المتراكمة للترشيد) | نعم | -| `check:lockfile` | سلامة `package-lock.json` — سجل HTTPS، وبصمات تكامل، وعدم وجود تجاوزات للمضيف | نعم | -| `check:licenses` | قائمة السماح لتراخيص SPDX لتبعيات الإنتاج | نعم | -| `check:tracked-artifacts` | لا توجد نواتج بناء / روابط رمزية لـ `node_modules` مُضمَّنة في المستودع (يُشغَّل أيضًا في husky قبل الالتزام؛ أما ما قبل الدفع فخفيف عمدًا — #6716) | نعم | -| `check:ai-attribution` | لا يوجد تذييل `Co-Authored-By` منسوب إلى الذكاء الاصطناعي/بوت، ولا تذييل يشير إلى التوليد بالذكاء الاصطناعي في التزامات طلب السحب أو عنوانه أو نصه — القاعدة الصارمة رقم 16 (ضمن حلقة البوابات السريعة في `quality.yml` لطلبات السحب إلى `release/**` — يقرأ حمولة الحدث ولا ينفّذ شيئًا خارج طلبات السحب — وكذلك خطوة خاصة بطلبات السحب ضمن فحص التنسيق في `ci.yml` لطلبات السحب إلى `main`؛ بالإضافة إلى خطاف husky المسمى `commit-msg`؛ يُسمح بالمؤلفين المشاركين من البشر؛ #14436) | -| `check:vitest-exclusions` | يشير كل استثناء في Vitest إلى مشكلة تتبّع ويظهر في `config/quality/vitest-exclusions.json` (#13204) | نعم | -| `check:file-size` | لا يتجاوز أي ملف مصدر الحد الأقصى الخاص بامتداده (آلية تصعيد تدريجية: الملفات الكبيرة المجمّدة مدرجة في قائمة `frozen`) | نعم | -| `check:error-helper` | تستخدم استجابات الأخطاء في المنفّذات/المعالجات `buildErrorBody()` / `sanitizeErrorMessage()` (القاعدة الصارمة رقم 12) | نعم | -| `check:migration-numbering` | ملفات SQL الخاصة بالترحيل مرقّمة تسلسليًا، دون فجوات أو تكرارات | نعم | -| `check:public-creds` | لا توجد قيم حرفية لـ OAuth مثل `client_id`/`client_secret` أو مفاتيح Firebase Web خارج `publicCreds.ts` (القاعدة الصارمة رقم 11) | نعم | -| `check:db-rules` | لا توجد استعلامات SQL خام خارج وحدات `src/lib/db/`؛ ولا توجد استيرادات تجميعية من `localDb.ts` (القاعدتان الصارمتان رقم 2 ورقم 5) | نعم | -| `check:known-symbols` | تتطابق منفّذات المزوّد، واستراتيجيات التوجيه، والمترجمات المسجّلة في جداول التوزيع الخاصة بها مع الملفات الموجودة على القرص — دون رموز يتيمة أو غير مصرّح بها | نعم | -| `check:route-guard-membership` | كل مسار ينشئ عملية فرعية مُصنّف بواسطة `isLocalOnlyPath()` (القاعدتان الصارمتان رقم 15 ورقم 17) | نعم | -| `check:test-discovery` | يجمع مشغّل اختبار واحد على الأقل كل ملف `*.test.ts` / `*.spec.ts` في المستودع (آلية التدرّج: لا يمكن لقائمة الملفات اليتيمة في `test-discovery-baseline.json` إلا أن تتقلص) | نعم | -| `check:agent-skills-sync` | تطابق مخرجات مهارات الوكيل المُولَّدة كتالوج المصدر الخاص بها (دون انحراف) | -| `check:provider-asset-provenance` | تحمل شعارات/أصول المزوّد إدخالًا مسجلًا لمصدرها | -| `lint:json` | تُحلَّل ملفات إعداد JSON وتستوفي قواعد التدقيق الخاصة بالمستودع | -| `typecheck:core` | ترجمة TypeScript دون أخطاء (تحذيرات إرشادية فقط) | نعم | -| `typecheck:noimplicit:core` | تطبيق صارم لـ `noImplicitAny` — استشرافي؛ لا تزال العديد من مواضع الاستدعاء الموجودة مسبقًا بحاجة إلى تعليقات توضيحية للأنواع | **إرشادي** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | تشغيل `tsc` ضمن نطاق `src/app/(dashboard)/**` (#7033) — لا تتضمن قائمة السماح المنقّحة المكوّنة من 27 ملفًا في `typecheck:core` أي ملفات TSX للوحة المعلومات، كما أن `next build` لا يتحقق من أنواعها أيضًا (يضبط `next.config.mjs` الخيار `ignoreBuildErrors: true`)، لذا كانت تراجعات المعرّفات المعزولة هناك (#6625/#6909) غير مرئية لنظام CI. تُقارَن النتائج بخط أساس مجمّد لعدد الأخطاء لكل ملف ولكل رمز TS (`config/quality/dashboard-typecheck-baseline.json`، بنمط فرض التقادم نفسه المستخدم في `check:known-symbols`) — لا تفشل البوابة إلا عند ظهور أخطاء جديدة تتجاوز العدد المسجل في خط الأساس؛ ويمكن خفض الحد تدريجيًا باستخدام `--update` عند إصلاح خطأ موجود مسبقًا. | نعم | +تُشغَّل عند كل طلب سحب إلى `main`. تمنع الدمج عند الفشل. + +| البرنامج النصي (`npm run ...`) | ما يتحقق منه | مانع | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | +| `check:node-runtime` | أن يكون إصدار Node.js ضمن النطاق المدعوم | نعم | +| `check:cycles` | الاستيرادات الدائرية عبر كامل `src/` و`open-sse/` (استنادًا إلى AST، مع حلّ `paths` الخاصة بـ tsconfig). الوضع المجرّد = إرشادي، ويسرد الدورات. يحظر `check:cycles:ratchet` (الذي تشغّله CI) عندما يتجاوز العدد الحد الأقصى `metrics.cycles` في `quality-baseline.json` — وهو حاليًا 14، مع `direction: down`، لذا لا يمكنه إلا الانخفاض (#15159 G-01/G-02) | نعم (آلية تصعيدية) | +| `check:route-validation:t06` | وجود مخططات Zod في جميع المسارات (سياسة المستوى 6) | نعم | +| `check:any-budget:t11` | ألّا يتجاوز عدد `@ts-expect-error // any` الميزانية المحددة (آلية المستوى 11) | نعم | +| `check:provider-consistency` | لكل موفّر في `providers.ts` إدخال مطابق في `providerRegistry.ts` (والعكس صحيح، ضمن قائمة السماح) | نعم | +| `check:model-lifecycle` | تظل جداول التوجيه الثلاثة التي تتم صيانتها يدويًا متسقة مع لقطة دورة الحياة المُضمّنة في المستودع (#11503): لا يمنح `FITNESS_TABLE` (`taskFitness.ts`) أي معرّف متقاعد يمكن لـ `REGISTRY` توجيهه؛ وكل هدف في `BUILT_IN_ALIASES` موجود في `REGISTRY` وغير موجود في لقطة المعرّفات المتقاعدة؛ وكل معرّف متقاعد لا يزال موجودًا في `REGISTRY` تتم إعادة توجيهه أو إدراجه في `allowedRetiredInCatalog`؛ ولا يظهر أي مصدر أو هدف من `DEFAULT_DEGRADATION_MAP` كمتقاعد في تلك اللقطة. لا يثبت هذا أن النموذج تتم خدمته حاليًا بواسطة مزوّد فعّال. يعمل دون اتصال — إذ يقارن مع `config/quality/model-lifecycle.json`، الذي يُحدَّث يدويًا باستخدام `npm run quality:refresh-model-lifecycle` (يتطلب الشبكة؛ غير مدمج في CI). يُعد `allowedRetiredInCatalog` آلية تصعيدية لخفض العدد: لا تُضف إدخالًا إلا مع مشكلة تتبّع. | نعم | +| `check:fetch-targets` | كل استدعاء `fetch("/api/...")` في `src/` من جهة العميل يُحلّ إلى ملف `route.ts` فعلي | نعم | +| `check:deps` | جميع التبعيات القابلة للتثبيت عبر `npm install` في كل ملف `package.json` ضمن المستودع موجودة في `dependency-allowlist.json`؛ ويتم الإبلاغ عن الحزم الجديدة غير المثبّتة بإصدار محدد أو التي تنتحل أسماء حزم أخرى | نعم | +| `audit:deps` | تشغيل `npm audit` (الجذر + Electron) — لا توجد تحذيرات عالية/حرجة (يتداخل مع `check:vuln-ratchet` الخاص بـ OSV؛ راجع قائمة الأعمال المتراكمة للترشيد) | نعم | +| `check:lockfile` | سلامة `package-lock.json` — سجل HTTPS، وبصمات سلامة، ومن دون تجاوزات للمضيف | نعم | +| `check:licenses` | قائمة السماح لتراخيص SPDX لتبعيات الإنتاج | نعم | +| `check:tracked-artifacts` | عدم وجود نواتج بناء / روابط رمزية ملتزم بها إلى `node_modules` (يُشغَّل أيضًا ضمن husky قبل الالتزام؛ أما ما قبل الدفع فخفيف عمدًا — #6716) | نعم | +| `check:ai-attribution` | عدم وجود تذييل `Co-Authored-By` للذكاء الاصطناعي/الروبوتات أو تذييل يفيد بالتوليد بواسطة الذكاء الاصطناعي في التزامات طلب السحب أو عنوانه أو نصه — القاعدة الصارمة #16 (ضمن حلقة البوابات السريعة في `quality.yml` لطلبات السحب إلى `release/**` — يقرأ حمولة الحدث ولا ينفّذ شيئًا خارج طلبات السحب — وكذلك خطوة خاصة بطلبات السحب في فحص التنسيق بملف `ci.yml` لطلبات السحب إلى `main`؛ وأيضًا خطاف `commit-msg` في husky؛ يُسمح بالمؤلفين المشاركين من البشر؛ #14436) | +| `check:vitest-exclusions` | كل استثناء في Vitest يذكر مشكلة تتبّع ويظهر في `config/quality/vitest-exclusions.json` (#13204) | نعم | +| `check:file-size` | لا يتجاوز أي ملف مصدر الحد الأقصى المحدد لكل امتداد (آلية تصعيد تدريجية: الملفات الكبيرة المجمّدة في قائمة `frozen`) | نعم | +| `check:error-helper` | تستخدم استجابات الأخطاء في المنفّذات/المعالِجات `buildErrorBody()` / `sanitizeErrorMessage()` (القاعدة الصارمة #12) | نعم | +| `check:migration-numbering` | ملفات SQL الخاصة بالترحيل مرقّمة تسلسليًا، من دون فجوات أو تكرارات | نعم | +| `check:public-creds` | لا توجد قيم OAuth حرفية لـ `client_id`/`client_secret` أو مفاتيح Firebase Web خارج `publicCreds.ts` (القاعدة الصارمة رقم 11) | نعم | +| `check:db-rules` | لا توجد تعليمات SQL خام خارج وحدات `src/lib/db/`؛ ولا توجد عمليات استيراد تجميعي من `localDb.ts` (القاعدتان الصارمتان رقم 2 ورقم 5) | نعم | +| `check:known-symbols` | تتطابق منفّذات المزوّد واستراتيجيات التوجيه والمترجمات المسجّلة في جداول التوزيع الخاصة بها مع الملفات الموجودة على القرص — من دون رموز يتيمة أو غير مصرّح بها | نعم | +| `check:route-guard-membership` | يُصنَّف كل مسار ينشئ عملية فرعية بواسطة `isLocalOnlyPath()` (القاعدتان الصارمتان رقم 15 ورقم 17) | نعم | +| `check:test-discovery` | يلتقط مشغّل اختبار واحد على الأقل كل ملف `*.test.ts` / `*.spec.ts` في المستودع (آلية تصاعدية: لا يمكن لقائمة الملفات اليتيمة في `test-discovery-baseline.json` إلا أن تتقلّص) | نعم | +| `check:agent-skills-sync` | تطابق عناصر agent-skills المُنشأة كتالوج المصدر الخاص بها (من دون انحراف) | +| `check:provider-asset-provenance` | تحمل شعارات/أصول المزوّد إدخالًا مسجلًا لمصدرها | +| `lint:json` | يجري تحليل ملفات إعداد JSON وتستوفي قواعد التدقيق الخاصة بالمستودع | +| `typecheck:core` | تجميع TypeScript من دون أخطاء (تحذيرات إرشادية فقط) | نعم | +| `typecheck:noimplicit:core` | ‏`noImplicitAny` صارم — استشرافي؛ لا تزال العديد من مواضع الاستدعاء الموجودة مسبقًا بحاجة إلى تعليقات توضيحية للأنواع | **إرشادي** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | تشغيل `tsc` بنطاق يقتصر على `src/app/(dashboard)/**` ‏(#7033) — لا تتضمن قائمة السماح المنسّقة المكوّنة من 27 ملفًا والخاصة بـ `typecheck:core` أي ملف TSX للوحة المعلومات، كما أن `next build` لا يتحقق من أنواعها أيضًا (يضبط `next.config.mjs` الخيار `ignoreBuildErrors: true`)، ولذلك لم تكن تراجعات المعرّفات المعزولة هناك (#6625/#6909) مرئية لنظام CI. تُجرى المقارنة مع خط أساس مجمّد لأعداد الأخطاء لكل ملف ولكل رمز TS (`config/quality/dashboard-typecheck-baseline.json`، باتباع نمط فرض اكتشاف التقادم نفسه المستخدم في `check:known-symbols`) — لا تؤدي إلى فشل البوابة إلا الأخطاء الجديدة التي تتجاوز العدد المسجّل في خط الأساس؛ خفّض خط الأساس تدريجيًا باستخدام `--update` عند إصلاح خطأ موجود مسبقًا. | نعم | ### المهمة: `quality-gate` -تُشغَّل بعد `test-coverage`. تمنع الدمج عند الفشل. +تعمل بعد `test-coverage`. تمنع الدمج عند الفشل. -| البرنامج النصي | ما يتحقق منه | حاجب للدمج | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `quality:collect` | يُنشئ `quality-metrics.json` (عدد تحذيرات ESLint، والتغطية من تقرير الأجزاء المدمج) | نعم (يسبق آلية التثبيت) | -| `quality:ratchet` | عدم تراجع أي مقياس في `quality-baseline.json` (تحذيرات ESLint ≤ خط الأساس؛ التغطية ≥ خط الأساس) | نعم | -| `check:duplication` | عدم تجاوز تكرار الشيفرة (jscpd@4) لخط الأساس في `quality-baseline.json` | نعم | -| `check:complexity` | عدم تجاوز التعقيد الدوري على مستوى الملف للحد الأقصى (قاعدتا ESLint الأساسيتان `complexity` و`max-lines-per-function`) | نعم | -| `check:cognitive-complexity` | آلية تثبيت التعقيد الإدراكي (`eslint-plugin-sonarjs`) — تمريرة ESLint منفصلة؛ يشغّل CI كليهما مدمجين في خطوة `check:complexity-ratchets` واحدة | نعم | -| `check:dead-code` | عدم تراجع آلية تثبيت التصديرات / الملفات غير المستخدمة (knip) مقارنة بخط الأساس | نعم | -| `check:compression-budget` | ميزانية معيار الضغط — يجب ألا تتراجع الحدود الدنيا لتوفير الرموز لكل محرك | نعم | -| `check:type-coverage` | عدم تراجع آلية تثبيت نسبة التغطية بالأنواع (`type-coverage`)؛ وهي تستوعب إلى حد كبير `typecheck:noimplicit:core` | نعم | -| `check:codeql-ratchet` | عدم تراجع عدد تنبيهات CodeQL المفتوحة (تُقرأ عبر `gh api`؛ تخطٍ سلس عند عدم وجود رمز وصول) — لدورية التحديث والتشغيل اليدوي: راجع "آلية تثبيت CodeQL" أدناه | نعم | +| البرنامج النصي | ما يتحقق منه | مانع للدمج | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | +| `quality:collect` | يُصدر `quality-metrics.json` (عدد تحذيرات ESLint، والتغطية من تقرير الأجزاء المدمج) | نعم (يسبق آلية التصعيد) | +| `quality:ratchet` | عدم تراجع أي مقياس في `quality-baseline.json` (تحذيرات ESLint ≤ خط الأساس؛ التغطية ≥ خط الأساس) | نعم | +| `check:duplication` | عدم تجاوز تكرار الشيفرة (jscpd@4) لخط الأساس في `quality-baseline.json` | نعم | +| `check:complexity` | عدم تجاوز التعقيد الدوري على مستوى الملف للحد الأقصى (قاعدتا ESLint الأساسيتان `complexity` و`max-lines-per-function`) | نعم | +| `check:cognitive-complexity` | آلية تصعيد التعقيد المعرفي (`eslint-plugin-sonarjs`) — تمريرة ESLint منفصلة؛ يشغّل CI كلتيهما مدمجتين كخطوة `check:complexity-ratchets` واحدة | نعم | +| `check:dead-code` | عدم تراجع آلية تصعيد التصديرات / الملفات غير المستخدمة (knip) مقارنةً بخط الأساس | نعم | +| `check:compression-budget` | ميزانية معيار ضغط الأداء — يجب ألا تتراجع الحدود الدنيا لتوفير الرموز لكل محرك | نعم | +| `check:type-coverage` | عدم تراجع آلية تصعيد نسبة الأنواع المحددة (`type-coverage`)؛ وهي تغطي إلى حد كبير `typecheck:noimplicit:core` | نعم | +| `check:codeql-ratchet` | عدم تراجع عدد تنبيهات CodeQL المفتوحة (تُقرأ عبر `gh api`؛ تخطٍّ سلس عند عدم وجود رمز مميز) — لمعرفة وتيرة التحديث والتشغيل اليدوي: راجع "آلية تصعيد CodeQL" أدناه | نعم | ### المهمة: `quality-extended` -المهمة بأكملها استشارية (`continue-on-error: true`). تعمل آليات التثبيت المستندة إلى npm +المهمة بأكملها استشارية (`continue-on-error: true`). تعمل آليات التصعيد المستندة إلى npm فعليًا؛ أما أدوات الفحص الخارجية فتُثبَّت عبر `gh release download` وتتخطى نفسها (رمز الخروج 0) -إذا ظل الملف الثنائي غير موجود. +عندما تظل الأداة التنفيذية غير موجودة. -| البرنامج النصي | ما يتحقق منه | حاجب للدمج | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:circular-deps` | عدم وجود تبعيات دائرية (dpdm) | **استشاري** | -| `check:bundle-size` | عدم تجاوز حجم الحزمة للحد الأقصى | **استشاري** | -| `check:secrets` | فحص الأسرار (gitleaks) — يُتخطى إذا كان الملف الثنائي غير موجود | **استشاري** | -| `check:vuln-ratchet` | عدم تراجع ثغرات التبعيات (osv-scanner) — يُتخطى إذا كان الملف الثنائي غير موجود | **استشاري** | -| `check:workflows` | تدقيق سير العمل (actionlint + zizmor)؛ يؤدي غياب أدوات الفحص أو تعطلها، أو عدم صلاحية التقارير، أو غياب خط أساس آلية التثبيت إلى الفشل بحالة INCOMPLETE. تتبع النتائج الصالحة سياسة الوضع الصارم/الاستشاري/آلية التثبيت المحددة | التنفيذ مطلوب؛ آلية تثبيت zizmor حاجبة في CI | -| `check:openapi-breaking` | التغييرات الكاسرة لعقد API العام (`openapi.yaml`) مقارنة بالفرع الأساسي (oasdiff) — يُنشئ `openapiBreaking=N`؛ يُتخطى إذا كان oasdiff غير موجود أو تعذر تحليل المواصفات الأساسية | **استشاري** | +| البرنامج النصي | ما يتحقق منه | مانع للدمج | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | +| `check:circular-deps` | عدم وجود تبعيات دائرية (dpdm) | **استشاري** | +| `check:bundle-size` | عدم تجاوز حجم الحزمة للحد الأقصى | **استشاري** | +| `check:secrets` | فحص الأسرار (gitleaks) — يتم التخطي إذا كانت الأداة التنفيذية غير موجودة | **استشاري** | +| `check:vuln-ratchet` | عدم تراجع ثغرات التبعيات (osv-scanner) — يتم التخطي إذا كانت الأداة التنفيذية غير موجودة | **استشاري** | +| `check:workflows` | تدقيق سير العمل (actionlint + zizmor)؛ يؤدي غياب أدوات الفحص أو تعطلها، أو التقارير غير الصالحة، أو غياب خط أساس آلية التصعيد إلى الفشل بالحالة INCOMPLETE. تتبع النتائج الصالحة سياسة الصرامة/الاستشارة/التصعيد المحددة | التنفيذ مطلوب؛ آلية تصعيد zizmor مانعة للدمج في CI | +| `check:openapi-breaking` | التغييرات الكاسرة لعقد واجهة API العامة (`openapi.yaml`) مقارنةً بالفرع الأساسي (oasdiff) — يُصدر `openapiBreaking=N`؛ ويتم التخطي إذا كان oasdiff غائبًا أو تعذّر تحليل المواصفات الأساسية | **استشاري** | ### المهمة: `docs-sync-strict` -تعمل عند كل PR إلى `main`. وتحجب الدمج عند الفشل. - -| البرنامج النصي | ما يتحقق منه | مانع | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `check:docs-all` | بوابة شاملة تشغّل البوابات الفرعية الست أدناه بالتتابع | نعم | -| ↳ `check:docs-sync` | اتساق الإصدارات بين CHANGELOG وOpenAPI وllm.txt | نعم | -| ↳ `check:docs-counts` | أن تكون الأعداد المذكورة في النصوص (عدد المزوّدين، وعدد عمليات الترحيل، وما إلى ذلك) ضمن نافذة التصعيد التدريجي للأعداد الفعلية | نعم | -| ↳ `check:env-doc-sync` | أن يكون كل متغير بيئة في `.env.example` موثقًا في جدول ضمن الوثائق، والعكس صحيح | نعم | -| ↳ `check:deprecated-versions` | عدم وجود سلاسل إصدارات مهملة في الوثائق | نعم | -| ↳ `check:doc-links` | أن تشير روابط Markdown الداخلية في الوثائق إلى ملفات فعلية (بالصيغة `[النص]`/`(path)`) | نعم | -| ↳ `check:fabricated-docs` | أن تكون المسارات، ومتغيرات البيئة، وأوامر CLI، وأسماء الخطافات، ومسارات الملفات المذكورة في الوثائق موجودة في قاعدة الشيفرة. بوابة صارمة عبر `--strict`؛ وفشل غير مانع من دون هذا الخيار. | نعم (عبر `--strict` في CI) | -| `check:cli-i18n` | وجود سلاسل أوامر CLI في جميع ملفات الإعدادات المحلية لـ i18n | نعم | -| `check:openapi-coverage` | أن تغطي مواصفات OpenAPI حدًا أدنى متزايدًا تدريجيًا من المسارات الفعلية | نعم | -| `check:openapi-security-tiers` | اتساق تعليقات مستويات الأمان في `openapi.yaml` مع التصنيفات الموجودة في `routeGuard.ts` | **استشاري** | -| `check:openapi-routes` | أن يشير كل مسار في `openapi.yaml` إلى ملف `route.ts` فعلي (لمنع الهلوسة) | نعم | -| `check:docs-symbols` | أن يشير كل مرجع `/api/...` في `docs/**/*.md` إلى ملف `route.ts` فعلي (لمنع الهلوسة) | نعم | -| `i18n translation drift` | المفاتيح غير المترجمة في ملفات الإعدادات المحلية لـ i18n — تحذير فقط | **استشاري** | +تعمل مع كل طلب سحب إلى `main`. وتمنع الدمج عند الفشل. + +| السكربت | ما الذي يتحقق منه | حاجب | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | بوابة تجميعية تشغّل البوابات الفرعية الست أدناه بالتتابع | نعم | +| ↳ `check:docs-sync` | اتساق الإصدارات بين CHANGELOG وOpenAPI وllm.txt | نعم | +| ↳ `check:docs-counts` | الأعداد المذكورة في النصوص (عدد المزوّدين، وعدد عمليات الترحيل، وما إلى ذلك) تقع ضمن نافذة التصعيد التدريجي للأعداد الفعلية | نعم | +| ↳ `check:env-doc-sync` | كل متغير بيئة في `.env.example` موثّق في جدول ضمن الوثائق، والعكس صحيح | نعم | +| ↳ `check:deprecated-versions` | عدم وجود سلاسل إصدارات مهملة في الوثائق | نعم | +| ↳ `check:doc-links` | روابط markdown الداخلية في الوثائق تحيل إلى ملفات موجودة فعلًا (بصيغة `[text]`/`(path)`) | نعم | +| ↳ `check:fabricated-docs` | المسارات، ومتغيرات البيئة، وأوامر CLI، وأسماء الخطافات، ومسارات الملفات المذكورة في الوثائق موجودة في قاعدة الشفرة. بوابة صارمة عبر `--strict`؛ وفشل غير حاجب دون هذا الخيار. | نعم (عبر `--strict` في CI) | +| `check:cli-i18n` | سلاسل أوامر CLI موجودة في جميع ملفات اللغات في i18n | نعم | +| `check:openapi-coverage` | تغطي مواصفات OpenAPI حدًا أدنى متزايدًا تدريجيًا من المسارات الفعلية | نعم | +| `check:openapi-security-tiers` | تعليقات مستويات الأمان في `openapi.yaml` متسقة مع التصنيفات في `routeGuard.ts` | **استشاري** | +| `check:openapi-routes` | كل مسار في `openapi.yaml` يحيل إلى ملف `route.ts` موجود فعلًا (لمنع الهلوسة) | نعم | +| `check:docs-symbols` | كل مرجع إلى `/api/...` في `docs/**/*.md` يحيل إلى ملف `route.ts` موجود فعلًا (لمنع الهلوسة) | نعم | +| `i18n translation drift` | المفاتيح غير المترجمة في ملفات لغات i18n — تحذير فقط | **استشاري** | ### المهمة: `i18n-ui-coverage` -| البرنامج النصي | ما يتحقق منه | مانع | -| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check-ui-keys-coverage` (مضمّن) | أن تكون تغطية مفاتيح i18n لواجهة المستخدم ≥ 65% | نعم | -| `check-ui-value-drift` (مضمّن) | ألا تترك إعادة كتابة **قيمة** إنجليزية أي ترجمة قديمة | نعم | -| `check-new-key-coverage` (مضمّن) | أن يكون المفتاح الإنجليزي **الجديد** مترجمًا في كل إعداد محلي — وتُرفض علامة `__MISSING__:` | نعم | -| `check-translation-ratio` | يجب ألا تتجاوز نسبة الترجمات الفعلية لكل إعداد محلي (القيم المطابقة للإنجليزية / العناصر النائبة / العناصر المفقودة خارج قائمة السماح) القيمة المحددة في `config/quality/i18n-translation-baseline.json` مع هامش السماح | **استشاري** | +| السكربت | ما الذي يتحقق منه | حاجب | +| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check-ui-keys-coverage` (مضمّن) | تغطية مفاتيح i18n لواجهة المستخدم تبلغ ≥ 65% | نعم | +| `check-ui-value-drift` (مضمّن) | إعادة صياغة **قيمة** إنجليزية لا تترك وراءها ترجمة قديمة | نعم | +| `check-new-key-coverage` (مضمّن) | كل مفتاح إنجليزي **جديد** مترجم في كل لغة — تُرفض علامة `__MISSING__:` | نعم | +| `check-translation-ratio` | يجب ألا تتجاوز نسبة الترجمات الفعلية لكل لغة (القيم المطابقة للإنجليزية / العناصر النائبة / العناصر الناقصة خارج قائمة السماح) القيمة المحددة في `config/quality/i18n-translation-baseline.json` + الهامش | **استشاري** | -يتطلب `fetch-depth: 0` — إذ تقارن بوابة انحراف القيم ملف `en.json` مع قاعدة الدمج. +تحتاج إلى `fetch-depth: 0` — إذ تقارن بوابة انحراف القيم ملف `en.json` مع قاعدة الدمج. #### `check-ui-value-drift` — بوابة الترجمات القديمة -ترصد هذه البوابة حالة تراجع واحدة في i18n لا تستطيع البوابات الأخرى اكتشافها بنيويًا: إعادة -كتابة قيمة إنجليزية مع بقاء الترجمات المشتقة من النص الإنجليزي _السابق_، ما يؤدي إلى استمرار -المستخدمين غير الناطقين بالإنجليزية في قراءة نص مصاغ بثقة لكنه أصبح خاطئًا. +ترصد حالة التراجع الوحيدة في i18n التي لا تستطيع البوابات الأخرى اكتشافها بنيويًا: إعادة صياغة قيمة إنجليزية +مع بقاء الترجمات المشتقة من النص الإنجليزي _السابق_، ما يجعل +المستخدمين غير الناطقين بالإنجليزية يستمرون في قراءة نص يبدو موثوقًا لكنه أصبح خاطئًا. -وقد حدث هذا فعليًا في إصدار منشور. أُعيدت كتابة `oauthModal.googleOAuthWarning` عند إضافة -أداة تسجيل الدخول المساعدة Antigravity ‏(#5203)؛ لكن **39 من أصل 43 إعدادًا محليًا** احتفظت -بنص يطلب من المشغّلين «نسخ عنوان URL الكامل ولصقه أدناه» — وهي آلية لا يمكن إكمالها لهذا -المزوّد. ولم يُلاحظ ذلك حتى #8463 للأسباب التالية: +وقد حدث هذا فعليًا في إصدار منشور. أُعيدت صياغة `oauthModal.googleOAuthWarning` عند إضافة مساعد تسجيل الدخول +Antigravity (#5203)؛ وأبقت **39 من أصل 43 لغة** على نص يطلب من المشغّلين «نسخ عنوان URL +بالكامل ولصقه أدناه» — وهي آلية لا يمكن إكمالها لهذا المزوّد. ولم +يُلاحظ ذلك حتى #8463 للأسباب التالية: -- لا يملأ `sync-ui-keys` إلا المفاتيح **الغائبة**، وليس المفاتيح **القديمة**؛ -- يحسب `check-ui-keys-coverage` _وجود_ المفتاح، ولذلك تُحتسب الترجمة القديمة على أنها مغطاة؛ -- يتتبع `check-translation-drift` نُسخ الوثائق المطابقة في `docs/i18n//**.md` — - ولا يقرأ `src/i18n/messages/*.json` مطلقًا. وهو مانع في المهمة `docs-sync-strict` منذ - إعادة المزامنة في 2026-09: عند تعديل وثيقة أساسية ← `npm run i18n:run -- --files=` (على مستوى القسم، ومنخفض التكلفة). +- لا يملأ `sync-ui-keys` إلا المفاتيح **الغائبة**، ولا يحدّث المفاتيح **القديمة** مطلقًا؛ +- يحتسب `check-ui-keys-coverage` _وجود_ المفتاح، لذا تُحسب الترجمة القديمة على أنها مغطاة؛ +- يتتبع `check-translation-drift` نسخ الوثائق المتطابقة في `docs/i18n//**.md` — + ولا يقرأ `src/i18n/messages/*.json` أبدًا. وهو حاجب في المهمة `docs-sync-strict` منذ + إعادة المزامنة في 2026-09: عند تعديل وثيقة أساسية → `npm run i18n:run -- --files=` (على مستوى القسم، وقليل التكلفة). -**مراعي للفروق، وليس مدعومًا بخط أساس.** يقارن `en.json` عند قاعدة الدمج -بشجرة العمل؛ ولكل مفتاح تغيّرت قيمته الإنجليزية، تُعدّ أي لغة ما تزال تحتوي على -ترجمة لم تُمس قديمة. يؤدي هذا عمدًا إلى **تجميد الدين الموجود مسبقًا** — إذ لا يمكن للفرق -كشف النص الإنجليزي القديم الذي جاءت منه ترجمة قديمة، لذلك لا تتحقق البوابة إلا مما -يمسّه التغيير الحالي. أما البديل (خط أساس لتجزئة كل مفتاح) فسيتطلب ملفًا مولّدًا بحجم -نحو 600 KB، أي 3 أضعاف أكبر خط أساس موجود، وسيتغير مع كل PR متعلق بالتدويل. +**مدرك للاختلافات، وغير مستند إلى خط أساس.** يقارن `en.json` عند قاعدة الدمج +بشجرة العمل؛ ولكل مفتاح تغيّرت قيمته الإنجليزية، تُعد أي لغة ما تزال تحتفظ +بترجمة لم تُمس قديمة. يؤدي ذلك عمدًا إلى **تجميد الدَّين الموجود مسبقًا** — إذ لا يمكن +للاختلاف أن يكشف النص الإنجليزي القديم الذي جاءت منه ترجمة قديمة، لذا لا تفحص البوابة +إلا ما يمسّه التغيير الحالي. أما البديل (خط أساس لتجزئة كل مفتاح) فسيتطلب +ملفًا مولّدًا بحجم ~600 KB، أي 3× حجم أكبر خط أساس موجود، وسيتغير مع كل طلب سحب خاص بالتدويل. -هناك طريقتان لاستيفاء ذلك: +هناك طريقتان لاجتياز الفحص: 1. تحديث الترجمات المتأثرة، أو 2. تعيينها إلى `__MISSING__:` — وعندها يقدّم وقت التشغيل النص الإنجليزي المصحح - (`src/i18n/request.ts::deepMergeFallback`، #7258)، ويُضاف المفتاح إلى قائمة انتظار الترجمة. + (`src/i18n/request.ts::deepMergeFallback`، #7258) ويُدرج المفتاح في طابور الترجمة. إذا تغيّر **معنى** السلسلة، فمن الأفضل **إعادة تسمية المفتاح**: لا يمكن لمفتاح جديد أن يرث ترجمة قديمة. وهذا هو النمط الذي استخدمه #8463. @@ -233,47 +232,47 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` يخرج بالرمز 0 مع `SKIP reason=base-unresolved` عندما تتعذر قراءة الكتالوج الأساسي (نسخة -سطحية دون مرجع الأساس)، بما يماثل `check-openapi-breaking`. +سطحية من المستودع دون مرجع الأساس)، بما يماثل `check-openapi-breaking`. ### المهمة: `i18n` -مصفوفة تحقق كاملة للتدويل (مهمة واحدة لكل لغة). المهمة بأكملها استشارية. +مصفوفة التحقق الكاملة من التدويل (مهمة واحدة لكل لغة). المهمة بأكملها استشارية. -| البرنامج النصي | ما يتحقق منه | الحظر | -| ------------------------------- | ---------------------- | -------------------------------------------------------- | -| `validate_translation.py quick` | اكتمال الترجمة لكل لغة | **استشاري** (`continue-on-error: true` على المهمة كاملة) | +| البرنامج النصي | ما يتحقق منه | الحظر | +| ------------------------------- | ---------------------- | ------------------------------------------------------ | +| `validate_translation.py quick` | اكتمال الترجمة لكل لغة | **استشاري** (`continue-on-error: true` للمهمة بأكملها) | ### المهمة: `pr-test-policy` -تُشغّل لطلبات السحب فقط. +تعمل على طلبات السحب فقط. -| البرنامج النصي | ما يتحقق منه | الحظر | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| `check:pr-test-policy` | يجب أن تتضمن طلبات السحب التي تغيّر شيفرة الإنتاج في `src/` أو `open-sse/` أو `electron/` أو `bin/` اختبارات أو تحدّثها (القاعدة الصارمة #8) | نعم | -| `check:test-masking` | ملفات الاختبار المتغيرة لا تقلل العدد الصافي للتوكيدات ولا تضيف تحصيلات حاصل من نوع `assert.ok(true)` | نعم | -| `check:pr-evidence` | يشير نص طلب السحب إلى أدلة اختبار/VPS للتغيير (يؤتمت القاعدة الصارمة #18 بالبحث في نص طلب السحب — هش، راجع قائمة الأعمال المتراكمة) | نعم | +| البرنامج النصي | ما يتحقق منه | الحظر | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----- | +| `check:pr-test-policy` | يجب أن تتضمن طلبات السحب التي تغيّر شيفرة الإنتاج في `src/` أو `open-sse/` أو `electron/` أو `bin/` اختبارات أو أن تحدّثها (القاعدة الصارمة #8) | نعم | +| `check:test-masking` | لا تقلل ملفات الاختبار المتغيرة العدد الصافي للتأكيدات ولا تضيف تحصيلات حاصلة من نوع `assert.ok(true)` | نعم | +| `check:pr-evidence` | يشير نص طلب السحب إلى أدلة الاختبار/VPS الخاصة بالتغيير (يؤتمت القاعدة الصارمة #18 عبر البحث في نص طلب السحب — هش، راجع قائمة الأعمال المؤجلة) | نعم | ### المهمة: `test-vitest` -تُشغّل بعد `build`. تمنع الدمج عند الفشل. +تعمل بعد `build`. تمنع الدمج عند الفشل. | الحزمة | ما تتحقق منه | الحظر | | ---------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `test:vitest` | خادم MCP ‏(110 أداة)، وautoCombo، وذاكرة التخزين المؤقت — مشغّل vitest | نعم | -| `test:vitest:ui` | اختبارات مكوّنات واجهة المستخدم — مشغّل vitest | **مانع** — تُستبعد حالات الفشل الموجودة مسبقًا صراحةً في `vitest.config.ts`؛ وتتسبب حالات الفشل الجديدة في فشل المهمة | +| `test:vitest:ui` | اختبارات مكوّنات واجهة المستخدم — مشغّل vitest | **حاظر** — تُستبعد حالات الفشل الموجودة مسبقًا صراحةً في `vitest.config.ts`؛ وتؤدي حالات الفشل الجديدة إلى فشل المهمة | -### مهام سير العمل الليلية (مجدولة، استشارية) +### تدفقات العمل الليلية (مجدولة، استشارية) -تعمل وفق جدول cron (وكذلك عبر `workflow_dispatch`)، ولا تعمل مطلقًا على طلبات السحب. كلها استشارية. +تعمل هذه وفق جدول cron (وكذلك عبر `workflow_dispatch`)، ولا تعمل أبدًا على طلبات السحب. وجميعها استشارية. -| سير العمل | ما يتحقق منه | الحظر | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `nightly-property` | اختبارات الخصائص باستخدام fast-check مع بذرة عشوائية وعدد تشغيلات مرتفع | **استشاري** | -| `nightly-resilience` | بوابة نمو الكومة، وحقن الأعطال الفوضوي، واختبارات التحميل/التحمّل باستخدام k6 | **استشاري** | -| `nightly-llm-security` | حارس حقن promptfoo (وضع الحظر) + فحوصات garak (تُتخطى دون سر لمزوّد) | **استشاري** | -| `nightly-schemathesis` | اختبار عشوائي لعقد OpenAPI ‏(schemathesis) مقابل OmniRoute مباشر باستخدام `docs/openapi.yaml` — يكشف انتهاكات المواصفات / أخطاء 500 غير المعالجة (المرحلة 8 B.4) | **استشاري** | -| `nightly-mutation` | نتيجة اختبار الطفرات باستخدام Stryker على مسار اختبارات الوحدة السريع — تكشف الطفرات الباقية التوكيدات الضعيفة | **استشاري** | -| `nightly-compat` | مصفوفة توافق محرك Node عبر نطاقات `engines.node` المدعومة | **استشاري** | +| تدفق العمل | ما يتحقق منه | الحظر | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `nightly-property` | اختبارات الخصائص باستخدام fast-check مع بذرة عشوائية + عدد تشغيلات مرتفع | **استشاري** | +| `nightly-resilience` | بوابة نمو الكومة، وحقن أعطال الفوضى، واختبارات الحمل/الاستمرارية باستخدام k6 | **استشاري** | +| `nightly-llm-security` | حاجز حقن promptfoo (وضع الحظر) + فحوصات garak (تُتخطى دون سر لمزوّد الخدمة) | **استشاري** | +| `nightly-schemathesis` | اختبار عشوائي لعقد OpenAPI ‏(schemathesis) مقابل OmniRoute مباشر باستخدام `docs/openapi.yaml` — يكشف مخالفات المواصفات / أخطاء 500 غير المعالجة (المرحلة 8 B.4) | **استشاري** | +| `nightly-mutation` | درجة اختبار الطفرات باستخدام Stryker عبر مسار الوحدات السريع — تكشف الطفرات الناجية التأكيدات الضعيفة | **استشاري** | +| `nightly-compat` | مصفوفة توافق محرك Node عبر نطاقات `engines.node` المدعومة | **استشاري** | --- @@ -536,42 +535,42 @@ CodeQL: Default → Advanced. لا تضف مشغل `schedule:` من دون إج --- -## قائمة ترشيد الأعمال المتراكمة (مراجعة العائد على الاستثمار — المرحلة 9، الموجة 3) +## قائمة أعمال الترشيد المتراكمة (مراجعة العائد على الاستثمار — المرحلة 9، الموجة 3) -تمت مطابقة هذا الجرد مع `ci.yml` بتاريخ 2026-06-17 (أغفل الإصدار السابق +تمت مطابقة هذا الحصر مع `ci.yml` في 2026-06-17 (أغفل الإصدار السابق `audit:deps` و`check:tracked-artifacts` و`check:lockfile` و`check:licenses` و`check:dead-code` و`check:cognitive-complexity` و`check:type-coverage` و`check:codeql-ratchet` و`check:pr-evidence`). حدّدت مراجعة العائد على الاستثمار للمجموعة التي تمت مطابقتها -مرشحي الترشيد التاليين. **عمليات الدمج هي تغييرات ميكانيكية في CI؛ أما عمليات التفعيل/الحذف فهي قرارات تتعلق بالسياسة ومتروكة للمشغّل.** لم يُطبَّق أي مما يلي +مرشحي الترشيد الآتين. **عمليات الدمج هي تغييرات آلية في CI؛ أما تغييرات الحالة/عمليات الحذف فهي قرارات سياسات متروكة للمشغّل.** لم يُطبّق أي مما يرد أدناه حتى الآن. -**ومن الأمور غير الموثّقة أعلاه أيضًا** (استشارية، منخفضة الإشارة): مهمة `docs-lint` -(markdownlint + Vale، والمهمة بأكملها تستخدم `continue-on-error`) ومسارات عمل الفحص المستقلة +**ومن الأمور غير الموثقة أعلاه أيضًا** (استشارية، منخفضة الدلالة): مهمة `docs-lint` +(markdownlint + Vale، مع تعيين `continue-on-error` للمهمة بأكملها) ومسارات عمل الفحص المستقلة `semgrep.yml` / `codeql.yml` / `scorecard.yml`. القيمة `semgrepFindings: 0` موجودة في -`quality-baseline.json` لكنها غير موصولة بآلية تصعيد حاجبة في `ci.yml` — لذا فالمقياس -معزول حاليًا. - -### الدمج / إزالة التكرار (ميكانيكي، أقل خطورة) - -تم التحقق من كل مرشح مقابل الحالة الفعلية للبوابات بتاريخ 2026-06-17 (الثقة مع التحقق)؛ -وقد تبيّن أن عدة عمليات دمج بدت «بديهية» تخفي ديونًا، وبالتالي فهي **ليست** بدائل مباشرة سليمة. - -- **يعمل `check:docs-sync` مرتين** — مرة بصورة مستقلة في مهمة `lint` ومرة أخرى داخل `check:docs-all` (`docs-sync-strict`) وفي خطاف husky السابق للإيداع. ✅ **تم** — أزيل الاستدعاء المستقل من `lint`. -- **فحص CVE** — ❌ **ليس دمجًا مباشرًا سليمًا.** يفشل `audit:deps` فشلًا حاسمًا عند وجود أي CVE عالي/حرج؛ بينما لا يفشل `check:vuln-ratchet` (osv) إلا عند حدوث _تراجع_ مقارنة بخط الأساس (حاليًا توجد حالة واحدة MODERATE). الدلالات مختلفة — سيؤدي حذف `audit:deps` إلى فقدان البوابة المطلقة للثغرات العالية/الحرجة. أبقِ كليهما. -- **اكتشاف الدورات** — ❌ **ليس دمجًا مباشرًا سليمًا.** يُبلغ `check:circular-deps` (dpdm) عن **91 دورة** (ولهذا السبب هو استشاري)؛ ولا يمكن ترقيته إلى حاجب قبل حلها أولًا، كما أن نطاقه أوسع من `check:cycles` المنسّق والناجح. أبقِ `check:cycles` حاجبًا؛ أما حل دورات dpdm البالغ عددها 91 فهو بند مستقل في قائمة الأعمال المتراكمة. -- **التعقيد** — ✅ **تم** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): مرور واحد عبر ESLint، مع العد حسب ruleId بحيث تظل خطوط الأساس الخاصة بالتعقيد الدوري+الحد الأقصى للأسطر والتعقيد المعرفي مستقلة؛ ويظل كل من `check:complexity` و`check:cognitive-complexity` متاحًا على حدة لاستخدام `--update` محليًا. -- **مكافحة الهلوسة في `/api`** — ✅ **تم** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): جرد واحد لنظام الملفات في `src/app/api`، مع استمرار openapi-routes وdocs-symbols في إعداد تقارير مستقلة؛ وتظل الأوامر المنفردة متاحة للتشغيل المحلي. -- **يعمل `check:node-runtime` في 11 مهمة** — ⚠️ **عائد منخفض على الاستثمار.** تعمل كل مهمة على مشغّل منفصل ويستغرق الفحص أقل من ثانية واحدة؛ ويبلغ إجمالي التوفير نحو 10 ثوانٍ، مقابل فقدان أداة حماية زهيدة لكل مهمة. لا يستحق هذا الاضطراب. -- **`typecheck:noimplicit:core` في lint الخاص بـCI** — ✅ **أزيل من مهمة lint** (كان استشاريًا مع `continue-on-error`)؛ ويتكون سطح الأنواع الحاجب من `typecheck:core` + `check:type-coverage`. تم الإبقاء على السكربت المحلي. - -### التفعيل / اتخاذ القرار (سياسة المشغّل) - -- `check:openapi-security-tiers` (استشاري) — ❌ **لا يمكن تحويله إلى حاجب بصورة سليمة مباشرةً.** ينتهي برمز 0، لكنه يحذّر من أن عدة مسارات `traffic-inspector` ضمن `LOCAL_ONLY_API_PREFIXES` تفتقر إلى التعليق التوضيحي `x-loopback-only: true`. يتطلب فرضه إضافة تلك التعليقات التوضيحية إلى `openapi.yaml` أولًا. -- `typecheck:noimplicit:core` (استشاري) — تستوعبه إلى حد كبير آلية التصعيد الحاجبة `check:type-coverage`. حوّله إلى آلية تصعيد أو احذف مرور `tsc` الثاني المتكرر. -- `test:vitest:ui` (أصبح الآن **حاجبًا**) — تُستبعد حالات الفشل الموجودة مسبقًا صراحةً في `vitest.config.ts` باستخدام تعليقات التتبع `// #8618`؛ أما حالات الفشل الجديدة فتُفشل المهمة. -- `check:secrets` (gitleaks، آلية تصعيد حاجبة مجمّدة عند 3 نتائج إيجابية كاذبة موثّقة) — أضف الحالات الثلاث إلى قائمة السماح للوصول إلى 0، أو اخفضها إلى استشارية. تتداخل مع الفحص الأصلي للأسرار في GitHub ومع `check:public-creds`. -- `check:pr-evidence` (حاجب، يبحث في النص النثري لنص PR) — خطر مرتفع للنتائج الإيجابية الكاذبة؛ وسيؤدي حذفه إلى إضعاف تطبيق القاعدة الصارمة رقم 18، لذا فهذا قرار حقيقي يتعلق بالسياسة. -- `semgrep` (مسار مستقل استشاري) — يتداخل مع CodeQL بالنسبة إلى عائلات OWASP؛ اربط خط أساسه بآلية تصعيد أو احذفه. +`quality-baseline.json` لكنها غير موصولة بآلية سقاطة مانعة في `ci.yml` — وهذا المقياس +يتيم حاليًا. + +### الدمج / إزالة التكرار (آلي، أقل مخاطرة) + +تم التحقق من كل مرشح مقابل الحالة الفعلية للبوابات في 2026-06-17 (الثقة مع التحقق)؛ +وتبيّن أن عدة عمليات دمج «بديهية» تخفي ديونًا، ولذلك فهي **ليست** بدائل مباشرة سليمة. + +- **يُشغَّل `check:docs-sync` مرتين** — بصورة مستقلة في مهمة `lint`، ومرة أخرى داخل `check:docs-all` (`docs-sync-strict`) وفي خطاف husky السابق للالتزام. ✅ **تم** — أزيل الاستدعاء المستقل من `lint`. +- **فحص CVE** — ❌ **ليس دمجًا سليمًا مباشرًا.** يفشل `audit:deps` فشلًا قاطعًا عند وجود أي ثغرة CVE عالية/حرجة؛ بينما لا يفشل `check:vuln-ratchet` (osv) إلا عند حدوث _تراجع_ مقارنة بخط الأساس (حاليًا 1 MODERATE). الدلالات مختلفة — سيؤدي حذف `audit:deps` إلى فقدان البوابة المطلقة للثغرات العالية/الحرجة. أبقِ كليهما. +- **اكتشاف الدورات** — ✅ **تم** (#15159 G-01/G-02). وصف النص القديم هنا `check:cycles` بأنه البوابة «الخضراء والمنتقاة» وبرّر إبقاءها مانعة لأن `check:circular-deps` (dpdm) أبلغ عن 91 دورة. كان ذلك اللون الأخضر **نجاحًا زائفًا**: كان `check:cycles` يفحص 5 أدلة فرعية (450 ملفًا)، ولا يطابق سوى `import|export … from` الساكنة، ويتجاهل كل محددات `@/` و`@omniroute/open-sse/`، ولذلك لم يكن يستطيع رؤية دورات الاستيراد الديناميكي + الأسماء المستعارة التي كانت سائدة في المستودع. تم الإصلاح: تجتاز البوابة الآن `src` + `open-sse` (5023 ملفًا)، وتجمع المحددات من TypeScript AST (وبالتالي يُحتسب `import("…")`، بينما لا يُحتسب `typeof import("…")` في موضع النوع)، وتحل `paths` في tsconfig. وهي تعثر على **14** دورة، لا 0. ولأن إصلاح 14 دورة موجودة مسبقًا غير ممكن في طلب سحب خاص بالبوابة، أصبح `check:cycles` الآن **سقاطة** (`--ratchet`، بسقف `metrics.cycles.value = 14` في `quality-baseline.json`، و`direction: down`) — فهي تمنع أي _تراجع_، ولا يمكن للعدد إلا أن ينخفض. يشغّل CI الأمر `npm run check:cycles:ratchet`. تتم معالجة الخفض التدريجي ضمن **A-01**. يبقى `check:circular-deps` (dpdm) استشاريًا بوصفه رأيًا ثانيًا أوسع نطاقًا. +- **التعقيد** — ✅ **تم** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): اجتياز واحد بواسطة ESLint، مع العد حسب ruleId بحيث تظل خطوط الأساس للتعقيد الدوري+الحد الأقصى للأسطر وللتعقيد المعرفي مستقلة؛ ويظل كل من `check:complexity` / `check:cognitive-complexity` متاحًا للاستخدام المحلي مع `--update`. +- **مكافحة الهلوسة في `/api`** — ✅ **تم** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): حصر واحد لنظام الملفات في `src/app/api`، مع استمرار openapi-routes + docs-symbols في إعداد تقارير مستقلة؛ وتظل الفحوص الفردية متاحة للتشغيل المحلي. +- **يُشغَّل `check:node-runtime` في 11 مهمة** — ⚠️ **عائد منخفض على الاستثمار.** تستخدم كل مهمة مشغّلًا منفصلًا، ويستغرق الفحص أقل من ثانية واحدة؛ الوفر الإجمالي نحو 10 ثوانٍ، مقابل فقدان إجراء حماية زهيد لكل مهمة. لا يستحق عناء التغيير. +- **`typecheck:noimplicit:core` ضمن lint في CI** — ✅ **أزيل من مهمة lint** (كان استشاريًا مع `continue-on-error`)؛ سطح الأنواع المانع هو `typecheck:core` + `check:type-coverage`. تم الاحتفاظ بالبرنامج النصي المحلي. + +### تغيير الحالة / اتخاذ القرار (سياسة المشغّل) + +- `check:openapi-security-tiers` (استشاري) — ❌ **لا يمكن تحويله إلى مانع بصورة سليمة مباشرةً.** ينتهي بالرمز 0، لكنه يحذر من أن عدة مسارات `traffic-inspector` ضمن `LOCAL_ONLY_API_PREFIXES` تفتقر إلى التعليق التوضيحي `x-loopback-only: true`. يتطلب فرضه إضافة تلك التعليقات التوضيحية إلى `openapi.yaml` أولًا. +- `typecheck:noimplicit:core` (استشاري) — تغطيه إلى حد كبير سقاطة `check:type-coverage` المانعة. حوّله إلى سقاطة أو احذف مرور `tsc` الثاني المتكرر. +- `test:vitest:ui` (أصبح الآن **مانعًا**) — حالات الفشل الموجودة مسبقًا مستثناة صراحةً في `vitest.config.ts` مع تعليقات التتبع `// #8618`؛ وتؤدي حالات الفشل الجديدة إلى فشل المهمة. +- `check:secrets` (gitleaks، سقاطة مانعة مجمّدة عند 3 نتائج إيجابية زائفة موثقة) — أضف الحالات الثلاث إلى قائمة السماح للوصول إلى 0، أو اخفضه إلى استشاري. يتداخل مع فحص الأسرار الأصلي في GitHub ومع `check:public-creds`. +- `check:pr-evidence` (مانع، يبحث في نثر متن طلب السحب باستخدام grep) — ينطوي على خطر مرتفع للنتائج الإيجابية الزائفة؛ وسيؤدي حذفه إلى إضعاف فرض القاعدة الصارمة رقم 18، لذا فهذا قرار سياسات حقيقي. +- `semgrep` (مستقل واستشاري) — يتداخل مع CodeQL بالنسبة إلى عائلات OWASP؛ صِل خط أساسه بسقاطة أو احذفه. --- diff --git a/docs/i18n/ar/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ar/docs/compression/COMPRESSION_GUIDE.md index 3939f28e2e57..3631b5f822fa 100644 --- a/docs/i18n/ar/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ar/docs/compression/COMPRESSION_GUIDE.md @@ -456,8 +456,7 @@ const { messages: aged, saved } = applyAging(messages, { على نص يحتوي على `messages`، يتحقق تجاوز المحتوى (`shouldBypassCavemanOutputMode()` في `open-sse/services/compression/outputMode.ts`) من آخر ثلاث رسائل ويتخطى الأنماط للدورة بأكملها عندما تتطابق مع كلماتها الرئيسية المتعلقة بالأمان، والإجراءات غير القابلة للإلغاء، -والتوضيح، أو الحساسة للترتيب. يعمل التجاوز بغض النظر عن إعداد تبديل **تجاوز الوضوح التلقائي** -في لوحة التحكم (`cavemanOutputMode.autoClarity`). +والتوضيح، أو الحساسة للترتيب. يعمل التجاوز طالما مُفعَّل تبديل **تجاوز الوضوح التلقائي** في لوحة التحكم (`cavemanOutputMode.autoClarity`)، وهو الوضع الافتراضي؛ وعند إيقاف التبديل، تُطبَّق الأنماط المحددة على تلك الدورات أيضًا. عندما يسمح التجاوز بالدورة، `placeSystemInstruction()` (نفس الملف)، الذي لا ينشئ أبدًا `messages[0]` جديدًا، يضع الكتلة في أول هذه الرسائل التي يجدها: diff --git a/docs/i18n/ar/docs/reference/ENVIRONMENT.md b/docs/i18n/ar/docs/reference/ENVIRONMENT.md index 50b44a2f6cd6..a9d09b39cbf3 100644 --- a/docs/i18n/ar/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ar/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. الأسرار المطلوبة -**يجب** تعيين هذه القيم قبل التشغيل الأول. وبدونها، سيرفض التطبيق بدء التشغيل أو سيعمل باستخدام إعدادات افتراضية غير آمنة. - -| المتغير | مطلوب | القيمة الافتراضية | ملف المصدر | الوصف | -| ---------------------------- | ------------------------- | ----------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **نعم** | _(لا توجد)_ | `src/lib/auth` | يُوقّع جميع ملفات تعريف ارتباط جلسات لوحة المعلومات (JWT) ويتحقق منها. أنشئه باستخدام `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **نعم** | _(لا توجد)_ | `src/lib/db/apiKeys.ts` | مفتاح تشفير AES لقيم مفاتيح API المخزنة في SQLite. أنشئه باستخدام `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **نعم** | `CHANGEME` | برنامج التمهيد النصي | يعيّن كلمة مرور المسؤول الأولية للوحة المعلومات (تطابق القيمة الافتراضية في `.env.example` — وقد أُبقيت غير آمنة بشكل واضح لفرض تغييرها). **غيّرها قبل الاستخدام الأول.** بعد تسجيل الدخول، غيّرها عبر لوحة المعلومات ← الإعدادات ← الأمان. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **نعم** (في بيئة الإنتاج) | _(غير معيّن)_ | `src/app/api/internal/codex-responses-ws/route.ts` | السر المشترك لجسر WebSocket الداخلي الخاص بـ Codex Responses. يصادق على طلبات الجسر بين مُرحّل WS في Electron/المتصفح وOmniRoute. ⚠️ **مطلوب في بيئة الإنتاج — عند عدم تعيينه، تُرفض جميع طلبات جسر WS.** أنشئه باستخدام `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | لا | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | معرّف صريح لإبطال ذاكرة التخزين المؤقت لعامل الخدمة الخاص بواجهة PWA (#11779)؛ وهو الأول في سلسلة تحديد القيمة. | -| `SOURCE_VERSION` | لا | _(غير معيّن)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | الثاني في السلسلة — تعيّنه أدوات إنشاء PaaS (مثل الأدوات بأسلوب Heroku) إلى الالتزام المنشور. | -| `NEXT_PUBLIC_SW_BUILD_ID` | لا | _(مُشتق)_ | `src/shared/components/PwaRegister.tsx` | قيمة عامة في وقت الإنشاء يستخدمها العميل لتسجيل `/sw.js?v=…`؛ وتُشتق من القيمتين أعلاه، ثم من git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | لا (تلقائي) | _(تلقائي عند كل تشغيل)_ | `src/server/authz/policies/management.ts` | سر خاص بكل عملية يثبت أن ختم عنوان IP للنظير الموثوق صادر من خادم HTTP الخاص بـ OmniRoute نفسه (`scripts/dev/peer-stamp.mjs`). لا تثق برمجية authz الوسيطة في محلية الطلب (تقييد مسارات LOCAL_ONLY على الاسترجاع الحلقي/LAN) إلا عندما يحمل الختم هذا الرمز المميز. يُنشأ تلقائيًا عند كل تشغيل — اتركه دون تعيين؛ ولا تثبّته إلا في إعدادات العمليات المتعددة التي يجب أن تشترك في الختم. | -| `OMNIROUTE_TRUSTED_PROXIES` | لا | _(غير معيّن)_ | `scripts/dev/peer-stamp.mjs` | عناوين IP أو نطاقات CIDR مفصولة بفواصل للوكلاء العكسيين الذين يُسمح لقائمة السماح/الحظر بعناوين IP بالوثوق بقيم `X-Forwarded-For` / `X-Real-IP` الواردة منهم، بالإضافة إلى عناوين الاسترجاع الحلقي والشبكات الخاصة ونقاط Cloudflare الطرفية، التي تكون موثوقة دائمًا. لا يلزم ذلك إلا لوكيل موجود على عنوان عام آخر؛ وإلا فسيكون عنوان الوكيل نفسه هو ما يقيّمه عامل التصفية. | +الأسرار غير المعيّنة والمعلَّمة بـ**نعم** تُنشأ تلقائيًا عند التشغيل الأول (`JWT_SECRET` و`API_KEY_SECRET`) وتُحفَظ في `server.env` داخل دليل البيانات — عيّنها صراحةً عندما يكون ذلك الدليل مؤقتًا (ephemeral). الاستثناء هو `OMNIROUTE_WS_BRIDGE_SECRET`: فهو لا يُحفَظ في `server.env` — إذ تُنشئ نقاط دخول الخادم (server entrypoints) قيمة مؤقتة لكل تشغيل لا يستطيع استخدامها إلا الجسر داخل العملية نفسها، لذا يجب على عملاء الجسر الخارجيين تعيينه صراحةً. + +| المتغير | مطلوب | القيمة الافتراضية | ملف المصدر | الوصف | +| ---------------------------- | ------------------------- | ----------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **نعم** | _(لا توجد)_ | `src/lib/auth` | يُوقّع جميع ملفات تعريف ارتباط جلسات لوحة المعلومات (JWT) ويتحقق منها. أنشئه باستخدام `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **نعم** | _(لا توجد)_ | `src/lib/db/apiKeys.ts` | مفتاح تشفير AES لقيم مفاتيح API المخزنة في SQLite. أنشئه باستخدام `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | لا | _(غير معيّن)_ | `src/lib/auth/managementPassword.ts` | يعيّن كلمة مرور لوحة المعلومات عند بدء التشغيل طالما لم تُحفَظ كلمة مرور بعد؛ ويُتجاهَل بمجرد حفظ كلمة مرور. عمليات تثبيت npm ومن المصدر تنسخ `.env.example` إلى `.env`، فتبدأ إذن بقيمته البديلة المعروفة للجميع `CHANGEME`: استبدلها قبل التشغيل الأول، أو اضبط `INITIAL_PASSWORD=` (فارغة) في ملف `.env` ذاك لإنشاء كلمة المرور في معالج الإعداد الأولي (onboarding wizard) عند أول زيارة، تمامًا كما تفعل القيمة غير المعيّنة (الافتراضية في صورة Docker). غيّر كلمة المرور المحفوظة من لوحة المعلومات ← الإعدادات ← الأمان، أو شغّل `omniroute reset-password` (`node bin/reset-password.mjs` في نسخة من المصدر) مع تعيين `DATA_DIR` على دليل بيانات الخادم. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **نعم** (في بيئة الإنتاج) | _(غير معيّن)_ | `src/app/api/internal/codex-responses-ws/route.ts` | السر المشترك لجسر WebSocket الداخلي الخاص بـ Codex Responses. يصادق على طلبات الجسر بين مُرحّل WS في Electron/المتصفح وOmniRoute. ⚠️ **مطلوب في بيئة الإنتاج — عند عدم تعيينه، تُرفض جميع طلبات جسر WS.** أنشئه باستخدام `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | لا | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | معرّف صريح لإبطال ذاكرة التخزين المؤقت لعامل الخدمة الخاص بواجهة PWA (#11779)؛ وهو الأول في سلسلة تحديد القيمة. | +| `SOURCE_VERSION` | لا | _(غير معيّن)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | الثاني في السلسلة — تعيّنه أدوات إنشاء PaaS (مثل الأدوات بأسلوب Heroku) إلى الالتزام المنشور. | +| `NEXT_PUBLIC_SW_BUILD_ID` | لا | _(مُشتق)_ | `src/shared/components/PwaRegister.tsx` | قيمة عامة في وقت الإنشاء يستخدمها العميل لتسجيل `/sw.js?v=…`؛ وتُشتق من القيمتين أعلاه، ثم من git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | لا (تلقائي) | _(تلقائي عند كل تشغيل)_ | `src/server/authz/policies/management.ts` | سر خاص بكل عملية يثبت أن ختم عنوان IP للنظير الموثوق صادر من خادم HTTP الخاص بـ OmniRoute نفسه (`scripts/dev/peer-stamp.mjs`). لا تثق برمجية authz الوسيطة في محلية الطلب (تقييد مسارات LOCAL_ONLY على الاسترجاع الحلقي/LAN) إلا عندما يحمل الختم هذا الرمز المميز. يُنشأ تلقائيًا عند كل تشغيل — اتركه دون تعيين؛ ولا تثبّته إلا في إعدادات العمليات المتعددة التي يجب أن تشترك في الختم. | +| `OMNIROUTE_TRUSTED_PROXIES` | لا | _(غير معيّن)_ | `scripts/dev/peer-stamp.mjs` | عناوين IP أو نطاقات CIDR مفصولة بفواصل للوكلاء العكسيين الذين يُسمح لقائمة السماح/الحظر بعناوين IP بالوثوق بقيم `X-Forwarded-For` / `X-Real-IP` الواردة منهم، بالإضافة إلى عناوين الاسترجاع الحلقي والشبكات الخاصة ونقاط Cloudflare الطرفية، التي تكون موثوقة دائمًا. لا يلزم ذلك إلا لوكيل موجود على عنوان عام آخر؛ وإلا فسيكون عنوان الوكيل نفسه هو ما يقيّمه عامل التصفية. | ### أوامر الإنشاء @@ -215,38 +215,38 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ## 4. الأمان والمصادقة -| المتغير | القيمة الافتراضية | الملف المصدري | الوصف | -| ------------------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | قيمة salt تُدمج مع معرّفات الأجهزة لإنشاء بصمة الجهاز. غيّرها لكل عملية نشر لضمان العزل. | -| `OMNIROUTE_CLI_SALT` | _(غير معيّن = قيمة salt عشوائية لكل تثبيت، محفوظة في `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | قيمة salt لخوارزمية HMAC لاشتقاق رمز مصادقة CLI المحلي. يؤدي تعيين هذه القيمة إلى تدوير جميع رموز CLI على الجهاز، وتكون لها دائمًا الأولوية على قيمة salt المحفوظة. راجع `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | يعيّن العلامة `Secure` في ملفات تعريف ارتباط الجلسة. **يجب أن تكون `true`** عند التشغيل خلف HTTPS. | -| `REQUIRE_API_KEY` | `false` | البرمجيات الوسيطة لـ API | عندما تكون `true`، يجب أن تتضمن جميع طلبات الوكيل `/v1/*` مفتاح API صالحًا. **لا تتحكم هذه العلامة في `GET /v1/models`**، إذ يتبع بدلًا من ذلك وضع تسجيل الدخول إلى لوحة المعلومات (`requireAuthForModels`) — لذلك فإن الاستجابة `401` من `/v1/models` لا تعني أن الاستدلال محمي. راجع `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | يسمح بعرض قيم مفاتيح API كاملة في واجهة مستخدم لوحة المعلومات. يمكن تهيئته من علامات الميزات في لوحة المعلومات؛ ويمثل خطرًا أمنيًا على النسخ المشتركة. | -| `NO_LOG_API_KEY_IDS` | _(فارغ)_ | `src/lib/compliance/index.ts` | معرّفات مفاتيح API مفصولة بفواصل تتجاوز تسجيل الطلبات (للامتثال للائحة GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(غير معيّن = غير محدود)_ | `src/shared/utils/apiKeyPolicy.ts` | ميزانية طلبات احتياطية يومية تُطبّق على مفاتيح API التي يكون عمود `rate_limits` لها فارغًا. غير معيّن أو فارغ: لا يوجد حد ضمني (#2289، #11017). القيمة `0` لها المعنى نفسه (غير محدود). يفعّل العدد الصحيح الموجب N حدود N/يوم و5N/أسبوع و20N/شهر. تعود القيم غير الفارغة والمشوّهة إلى النوافذ القديمة: 1000/يوم و5000/أسبوع و20000/شهر. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | الحد الأقصى المسموح به لحجم نص الطلب. يرفض الحمولات التي تتجاوز هذا الحد. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | تحصل نصوص الطلبات الفعلية التي تساوي هذه العتبة أو تتجاوزها على حجز القبول الذري محلي العملية للأحمال الثقيلة قبل تحليل JSON (مسار BYTE، بما في ذلك `POST /v1/responses`). يستخدم مسار الإفلات نفسه في حال توافر هامش سعة سليم كما في [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) للطلبات الثقيلة من حيث البنية؛ ويظل مقيّدًا بـ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) كي لا تعود المشكلة [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 ميغابايت) | `src/shared/middleware/chatBodyAdmission.ts` | حدّ صارم لمسار الدردشة يُفرض على عدد البايتات المقروءة أثناء الاستيعاب المحدود، بما في ذلك الطلبات ذات `Content-Length` المفقود أو غير الصالح أو غير الصادق؛ ويؤدي التجاوز إلى تلقي `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(غير معيّن — لا يوجد حدّ لعدد الطلبات)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** لا يعود هذا الحدّ القديم لعدد الطلبات نافذًا إلا عند تعيينه صراحةً. وعند تركه غير معيّن (وهو الافتراضي)، يُضبط قبول طلبات الدردشة الثقيلة بدلًا من ذلك بواسطة `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — وهي ميزانية بايتات مشتقة تلقائيًا ومحددة وفق سقف الذاكرة الفعلي للعملية في **عملية واحدة** (كومة V8 واحدة). يؤدي تداخل طلبي `/v1/responses` بحجم يقارب 750 ألف رمز إلى انهيار أكوام بحجم يقارب 12 غيغابايت (#7849) — وهذا تحذير بشأن **ميزانية الذاكرة**، وليس حدًا أقصى ثابتًا للمنتج قدره 2. يجوز لعملية سليمة (تكون فيها الكومة دون نسبة التخلص من الحمل) قبول مزيد من طلبات `/v1/responses` الطويلة المتزامنة عبر `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. وجود عشرات عملاء SSE طويلي الاتصال (40–50) تحكمه الكومة + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110، وليس «حد أقصى 2». تؤدي زيادة هذه القيمة دون تمحيص بهدف «استخدام المضيف» إلى إعادة ظهور #7849. ضاعف **الأكوام** باستخدام **N من `DATA_DIR` المستقلة** (#11024)؛ ولا تستخدم مطلقًا `replicas>1` مع ملف SQLite واحد. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(مشتقة تلقائيًا)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** تجاوز لميزانية بايتات الاستيعاب المشتقة تلقائيًا (25% من الحد الأكثر تقييدًا لذاكرة V8/cgroup مقسومة على تضخيم مؤقت بمقدار 8x). تُقيّد القيم المشتقة والصريحة ضمن نطاق 8 ميبيبايت–2 غيبيبايت. يفشل الجسم الأكبر من الميزانية الفعلية فورًا مع `413 body_exceeds_budget`؛ أما التنافس بين الأجسام التي يمكن خدمتها كلٌّ على حدة فيظل قابلًا لإعادة المحاولة مع `503`. وجود 40–50 عميل SSE طويل الاتصال ومتزامن تحكمه هذه الميزانية + الكومة، وليس حدًا ثابتًا قدره «2 كحد أقصى». اقرأ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` في `/api/monitoring/health` قبل الضبط. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | نسبة التخلص من الحمل عند ضغط الكومة (`heapUsed / heap_size_limit`) لقبول الطلبات الثقيلة من نوع BYTE وSTRUCTURE ‏(#10183، #10268، #10437). لا يُتخلّص من طلب ثقيل متزامن يتجاوز `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` باستخدام `503` القابل لإعادة المحاولة إلا عندما تكون الكومة **أيضًا** عند هذه النسبة أو أعلى منها؛ أما مع كومة سليمة، فيُقبل عبر السعة الإضافية السليمة بدلًا من ذلك. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (الافتراضي `1`) | `src/shared/middleware/chatBodyAdmission.ts` | سعة إضافية محدودة للمسار السريع ذي الكومة السليمة (#10437) لكلٍ من STRUCTURE وBYTE (`admitChatRequest`، بما في ذلك الأجسام ذات الحجم ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). من دون هذا الحد، كان كل طلب مشغول لكن ذا كومة سليمة يتجاوز آلية القبول دون سقف. وبمجرد أن ينشط هذا العدد من عقود الإشغال المتزامنة عبر مسار الكومة السليمة، تنتقل الطلبات المشغولة الإضافية إلى مسار الانتظار المحدود/التخلص من الحمل **نفسه** المستخدم تحت ضغط الكومة الفعلي. تؤدي القيمة `0` إلى تعطيل التجاوز بالكامل. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | عدد الرسائل الذي يصنّف طلب دردشة باعتباره ثقيلًا حتى عندما يكون جسمه دون حد البايتات. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | عدد الأدوات الذي يصنّف طلب دردشة باعتباره ثقيلًا حتى عندما يكون جسمه دون حد البايتات. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | تقدير محافظ لعدد الرموز استنادًا إلى حجم السلسلة النصية، ويصنّف الطلب باعتباره ثقيلًا؛ وهو مؤشر تقريبي لتكلفة القبول، وليس ترميز الفوترة الخاص بمزوّد الخدمة. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (معطّل) | `src/shared/middleware/chatBodyAdmission.ts` | حد اختياري لسجل الدردشة يتطلب الاشتراك الصريح. وهو معطّل افتراضيًا: إذ إن عدد الرسائل سياسة نشر، وليس خاصية عامة للطلب، كما أن فرض حد هنا يرفض المحادثات نهائيًا باستخدام `413` قبل أن يتمكن مسار الضغط من جعلها قابلة للخدمة. يُحد نمو الكومة بواسطة `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` وآلية التخلص من الحمل عند ضغط الكومة. عيّن قيمة موجبة في عمليات النشر المقيّدة بالذاكرة التي تحتاج إلى سقف صارم؛ وعندها يؤدي التجاوز إلى تلقي استجابة `413` منظّمة تفيد بضرورة الضغط. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 ميغابايت) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | حد صارم لاستجابة منبع غير متدفقة تُخزّن مؤقتًا بالكامل في الذاكرة. عند تجاوزه، تُلغى قراءة المنبع ويفشل الطلب سريعًا بدلًا من تنمية سلسلة نصية غير محدودة حتى نفاد الكومة. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | الحد الأقصى لعدد البايتات المنقولة عبر الشبكة والمُمرَّرة من ترويسات استجابة المنبع. عند تجاوز الحد، تُحذف الترويسات ذات الأولوية الأقل (مثل `x-codex-*` و`x-oai-request-id`) للبقاء ضمن حدود أحجام الترويسات الشائعة في الوكلاء العكسيين. اضبطه على قيمة أعلى لتمرير المزيد من البيانات الوصفية للمنبع، على حساب زيادة حجم ترويسات الاستجابة. | -| `CORS_ORIGIN` | _(غير معيّن)_ | `src/server/cors/origins.ts` | قائمة سماح CORS قديمة لأصل واحد. يُفضّل استخدام `CORS_ALLOWED_ORIGINS` لعمليات النشر الجديدة. يُستخدم CORS فقط لعملاء واجهة API في المتصفح من أصول مختلفة؛ أما عمليات الكتابة المصادَق عليها في لوحة المعلومات فتستخدم طلبات من الأصل نفسه بالإضافة إلى حماية CSRF مرتبطة بالجلسة. | -| `CORS_ALLOWED_ORIGINS` | _(غير معيّن)_ | `src/server/cors/origins.ts` | قائمة سماح CORS مفصولة بفواصل. لا تُرسل علامة بدل ما لم يُضبط `CORS_ALLOW_ALL=true` صراحةً. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | آلية تجاوز مخصصة للتطوير تُعيد أي قيمة `Origin` من المتصفح. لا تفعّلها في عمليات النشر المشتركة أو الإنتاجية. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | حظر استدعاءات المزوّد التي تستهدف نطاقات عناوين IP الخاصة أو عناوين الاسترجاع أو الارتباط المحلي. عطّلها فقط في بيئات الاختبار المعزولة. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | السماح بعناوين URL للمزوّد التي تشير إلى شبكات خاصة/محلية (localhost و192.168.x.x و10.x.x.x وما إلى ذلك). **مطلوب للمزوّدين المستضافين ذاتيًا** (LM Studio وOllama وvLLM وLlamafile وTriton وSearXNG). عندما تكون القيمة `false`، ترفض لوحة المعلومات التحقق من صحة عناوين URL المحلية. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | السماح بإضافة المزوّدين والتحقق منهم على العناوين المحلية/الخاصة (127.0.0.1 وlocalhost وLAN والنطاقات الخاصة) — ويقتصر ذلك على مسار التحقق من المزوّد. **القيمة الافتراضية `true`** (الأولوية للمحلي)؛ اضبطها على `false` لفرض حظر صارم يقتصر على العناوين العامة. تظل نقاط نهاية البيانات الوصفية السحابية (169.254.169.254 وmetadata.google.internal) محظورة بصرف النظر عن ذلك. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | السماح لمسارات `/v1/audio/*` (النسخ والكلام والترجمة) باستخدام عقدة مزوّد متوافقة مع OpenAI ومستضافة خارج localhost. تكون معطّلة افتراضيًا — فتوجيه الصوت إلى مضيف بعيد يغيّر هوية الخروج ويجب أن يكون قرارًا صريحًا من المشغّل. يُسمح دائمًا بعُقد الاسترجاع/العُقد الخاصة (localhost و127.0.0.1 و172.16-31.x)، ولا تتأثر بهذا الإعداد. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | السماح لـ`POST /v1/rerank` (وكذلك خطوة إعادة الترتيب عبر الاسترجاع في محرك الذاكرة) باستخدام عقدة مزوّد متوافقة مع OpenAI ومستضافة خارج localhost — مثل جهاز على LAN أو نظير Tailscale يشغّل TEI أوInfinity أوvLLM وما إلى ذلك. تكون معطّلة افتراضيًا — فتوجيه الطلب إلى مضيف بعيد يغيّر هوية الخروج ويجب أن يكون قرارًا صريحًا من المشغّل. يُسمح دائمًا بعُقد الاسترجاع (localhost و127.0.0.1 و172.16-31.x)، ولا تتأثر بهذا الإعداد. يجب أيضًا أن تجتاز العُقد البعيدة سياسة عناوين URL الصادرة للمزوّد (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)؛ ولا تُوجَّه الطلبات مطلقًا إلى مضيفات البيانات الوصفية السحابية. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | عند تمكين OIDC، عطّل تسجيل الدخول بكلمة المرور بحيث لا يمكن للمستخدمين المصادقة إلا عبر تسجيل الدخول الأحادي باستخدام OIDC. يُقبل أيضًا الاسم البديل المجرّد `OIDC_DISABLE_PASSWORD_LOGIN`؛ وتكون لعلامة ميزة لوحة المعلومات التي تحمل المفتاح نفسه الأولوية. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | الاسم البديل المجرّد لـ`OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| المتغير | القيمة الافتراضية | الملف المصدري | الوصف | +| ------------------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | قيمة salt تُدمج مع معرّفات الأجهزة لإنشاء بصمة الجهاز. غيّرها لكل عملية نشر لضمان العزل. | +| `OMNIROUTE_CLI_SALT` | _(غير معيّن = قيمة salt عشوائية لكل تثبيت، محفوظة في `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | قيمة salt لخوارزمية HMAC لاشتقاق رمز مصادقة CLI المحلي. يؤدي تعيين هذه القيمة إلى تدوير جميع رموز CLI على الجهاز، وتكون لها دائمًا الأولوية على قيمة salt المحفوظة. راجع `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | يعيّن العلامة `Secure` في ملفات تعريف ارتباط الجلسة. **يجب أن تكون `true`** عند التشغيل خلف HTTPS. | +| `REQUIRE_API_KEY` | `false` | البرمجيات الوسيطة لـ API | عندما تكون `true`، يجب أن تتضمن جميع طلبات الوكيل `/v1/*` مفتاح API صالحًا. **لا تتحكم هذه العلامة في `GET /v1/models`**، إذ يتبع بدلًا من ذلك وضع تسجيل الدخول إلى لوحة المعلومات (`requireAuthForModels`) — لذلك فإن الاستجابة `401` من `/v1/models` لا تعني أن الاستدلال محمي. راجع `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | يسمح بعرض قيم مفاتيح API كاملة في واجهة مستخدم لوحة المعلومات. يمكن تهيئته من علامات الميزات في لوحة المعلومات؛ ويمثل خطرًا أمنيًا على النسخ المشتركة. | +| `NO_LOG_API_KEY_IDS` | _(فارغ)_ | `src/lib/compliance/index.ts` | معرّفات مفاتيح API مفصولة بفواصل تتجاوز تسجيل الطلبات (للامتثال للائحة GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(غير معيّن = غير محدود)_ | `src/shared/utils/apiKeyPolicy.ts` | ميزانية طلبات احتياطية يومية تُطبّق على مفاتيح API التي يكون عمود `rate_limits` لها فارغًا. غير معيّن أو فارغ: لا يوجد حد ضمني (#2289، #11017). القيمة `0` لها المعنى نفسه (غير محدود). يفعّل العدد الصحيح الموجب N حدود N/يوم و5N/أسبوع و20N/شهر. تعود القيم غير الفارغة والمشوّهة إلى النوافذ القديمة: 1000/يوم و5000/أسبوع و20000/شهر. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | الحد الأقصى المسموح به لحجم نص الطلب. يرفض الحمولات التي تتجاوز هذا الحد. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | تحصل نصوص الطلبات الفعلية التي تساوي هذه العتبة أو تتجاوزها على حجز القبول الذري محلي العملية للأحمال الثقيلة قبل تحليل JSON (مسار BYTE، بما في ذلك `POST /v1/responses`). يستخدم مسار الإفلات نفسه في حال توافر هامش سعة سليم كما في [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) للطلبات الثقيلة من حيث البنية؛ ويظل مقيّدًا بـ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) كي لا تعود المشكلة [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 ميغابايت) | `src/shared/middleware/chatBodyAdmission.ts` | حدّ صارم لمسار الدردشة يُفرض على عدد البايتات المقروءة أثناء الاستيعاب المحدود، بما في ذلك الطلبات ذات `Content-Length` المفقود أو غير الصالح أو غير الصادق؛ ويؤدي التجاوز إلى تلقي `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(غير معيّن — لا يوجد حدّ لعدد الطلبات)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** لا يعود هذا الحدّ القديم لعدد الطلبات نافذًا إلا عند تعيينه صراحةً. وعند تركه غير معيّن (وهو الافتراضي)، يُضبط قبول طلبات الدردشة الثقيلة بدلًا من ذلك بواسطة `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — وهي ميزانية بايتات مشتقة تلقائيًا ومحددة وفق سقف الذاكرة الفعلي للعملية في **عملية واحدة** (كومة V8 واحدة). يؤدي تداخل طلبي `/v1/responses` بحجم يقارب 750 ألف رمز إلى انهيار أكوام بحجم يقارب 12 غيغابايت (#7849) — وهذا تحذير بشأن **ميزانية الذاكرة**، وليس حدًا أقصى ثابتًا للمنتج قدره 2. يجوز لعملية سليمة (تكون فيها الكومة دون نسبة التخلص من الحمل) قبول مزيد من طلبات `/v1/responses` الطويلة المتزامنة عبر `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. وجود عشرات عملاء SSE طويلي الاتصال (40–50) تحكمه الكومة + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110، وليس «حد أقصى 2». تؤدي زيادة هذه القيمة دون تمحيص بهدف «استخدام المضيف» إلى إعادة ظهور #7849. ضاعف **الأكوام** باستخدام **N من `DATA_DIR` المستقلة** (#11024)؛ ولا تستخدم مطلقًا `replicas>1` مع ملف SQLite واحد. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(مشتقة تلقائيًا)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** تجاوز لميزانية بايتات الاستيعاب المشتقة تلقائيًا (25% من الحد الأكثر تقييدًا لذاكرة V8/cgroup مقسومة على تضخيم مؤقت بمقدار 8x). تُقيّد القيم المشتقة والصريحة ضمن نطاق 8 ميبيبايت–2 غيبيبايت. يفشل الجسم الأكبر من الميزانية الفعلية فورًا مع `413 body_exceeds_budget`؛ أما التنافس بين الأجسام التي يمكن خدمتها كلٌّ على حدة فيظل قابلًا لإعادة المحاولة مع `503`. وجود 40–50 عميل SSE طويل الاتصال ومتزامن تحكمه هذه الميزانية + الكومة، وليس حدًا ثابتًا قدره «2 كحد أقصى». اقرأ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` في `/api/monitoring/health` قبل الضبط. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | نسبة التخلص من الحمل عند ضغط الكومة (`heapUsed / heap_size_limit`) لقبول الطلبات الثقيلة من نوع BYTE وSTRUCTURE ‏(#10183، #10268، #10437). لا يُتخلّص من طلب ثقيل متزامن يتجاوز `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` باستخدام `503` القابل لإعادة المحاولة إلا عندما تكون الكومة **أيضًا** عند هذه النسبة أو أعلى منها؛ أما مع كومة سليمة، فيُقبل عبر السعة الإضافية السليمة بدلًا من ذلك. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (الافتراضي `1`) | `src/shared/middleware/chatBodyAdmission.ts` | سعة إضافية محدودة للمسار السريع ذي الكومة السليمة (#10437) لكلٍ من STRUCTURE وBYTE (`admitChatRequest`، بما في ذلك الأجسام ذات الحجم ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). من دون هذا الحد، كان كل طلب مشغول لكن ذا كومة سليمة يتجاوز آلية القبول دون سقف. وبمجرد أن ينشط هذا العدد من عقود الإشغال المتزامنة عبر مسار الكومة السليمة، تنتقل الطلبات المشغولة الإضافية إلى مسار الانتظار المحدود/التخلص من الحمل **نفسه** المستخدم تحت ضغط الكومة الفعلي. تؤدي القيمة `0` إلى تعطيل التجاوز بالكامل. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | عدد الرسائل الذي يصنّف طلب دردشة باعتباره ثقيلًا حتى عندما يكون جسمه دون حد البايتات. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | عدد الأدوات الذي يصنّف طلب دردشة باعتباره ثقيلًا حتى عندما يكون جسمه دون حد البايتات. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | تقدير محافظ لعدد الرموز استنادًا إلى حجم السلسلة النصية، ويصنّف الطلب باعتباره ثقيلًا؛ وهو مؤشر تقريبي لتكلفة القبول، وليس ترميز الفوترة الخاص بمزوّد الخدمة. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (معطّل) | `src/shared/middleware/chatBodyAdmission.ts` | حد اختياري لسجل الدردشة يتطلب الاشتراك الصريح. وهو معطّل افتراضيًا: إذ إن عدد الرسائل سياسة نشر، وليس خاصية عامة للطلب، كما أن فرض حد هنا يرفض المحادثات نهائيًا باستخدام `413` قبل أن يتمكن مسار الضغط من جعلها قابلة للخدمة. يُحد نمو الكومة بواسطة `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` وآلية التخلص من الحمل عند ضغط الكومة. عيّن قيمة موجبة في عمليات النشر المقيّدة بالذاكرة التي تحتاج إلى سقف صارم؛ وعندها يؤدي التجاوز إلى تلقي استجابة `413` منظّمة تفيد بضرورة الضغط. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 ميغابايت) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | حد صارم لاستجابة منبع غير متدفقة تُخزّن مؤقتًا بالكامل في الذاكرة. عند تجاوزه، تُلغى قراءة المنبع ويفشل الطلب سريعًا بدلًا من تنمية سلسلة نصية غير محدودة حتى نفاد الكومة. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | الحد الأقصى لعدد البايتات المنقولة عبر الشبكة والمُمرَّرة من ترويسات استجابة المنبع. عند تجاوز الحد، تُحذف الترويسات ذات الأولوية الأقل (مثل `x-codex-*` و`x-oai-request-id`) للبقاء ضمن حدود أحجام الترويسات الشائعة في الوكلاء العكسيين. اضبطه على قيمة أعلى لتمرير المزيد من البيانات الوصفية للمنبع، على حساب زيادة حجم ترويسات الاستجابة. | +| `CORS_ORIGIN` | _(غير معيّن)_ | `src/server/cors/origins.ts` | قائمة سماح CORS قديمة لأصل واحد. يُفضّل استخدام `CORS_ALLOWED_ORIGINS` لعمليات النشر الجديدة. يُستخدم CORS فقط لعملاء واجهة API في المتصفح من أصول مختلفة؛ أما عمليات الكتابة المصادَق عليها في لوحة المعلومات فتستخدم طلبات من الأصل نفسه بالإضافة إلى حماية CSRF مرتبطة بالجلسة. | +| `CORS_ALLOWED_ORIGINS` | _(غير معيّن)_ | `src/server/cors/origins.ts` | قائمة سماح CORS مفصولة بفواصل. لا تُرسل علامة بدل ما لم يُضبط `CORS_ALLOW_ALL=true` صراحةً. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | آلية تجاوز مخصصة للتطوير تُعيد أي قيمة `Origin` من المتصفح. لا تفعّلها في عمليات النشر المشتركة أو الإنتاجية. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | اسم بديل قديم (Legacy alias). يُقرأ مفتاح تبديله في لوحة المعلومات (وهو تجاوز محفوظ في قاعدة البيانات) قبل قيمة متغير البيئة هذه؛ فإذا كانت القيمة `false` أو `0` أو `no` أو `off` في أيٍّ منهما، تُطفأ فحوصات المضيف بالطريقة نفسها التي يفعلها `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`، حتى لو كان مفتاح تبديل ذلك الخيار في لوحة المعلومات محفوظًا في وضع الإيقاف؛ أما القيم الأخرى فتترك الحارس للخيارين أدناه. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | يُطفئ فحوصات المضيف لدى حارس عناوين URL الصادرة، بما في ذلك حظر البيانات الوصفية السحابية (كامل النطاق 169.254.0.0/16 وmetadata.google.internal)، على مسارات المزوّد التي تتبع وضع الحارس: التحقق من صحة عناوين URL، واكتشاف النماذج، وعناوين URL الأساسية لعُقد المزوّد (بما فيها عُقد إعادة الترتيب البعيدة)، واختبار الوكيل الاحتياطي، وغيرها من المسارات الصادرة المجاورة للمزوّد مثل جلب الصور/الوسائط البعيدة. كما يسمح بأهداف webhooks الخاصة، باستثناء نقاط نهاية البيانات الوصفية السحابية/الارتباط المحلي التي تظل محظورة حتى مع تفعيل هذا الخيار. أما طلبات المحادثة المُرسلة عبر `BaseExecutor.execute()` فتُبقي فحص البيانات الوصفية السحابية الخاص بها. المزوّدات المحلية تعمل بالإعدادات الافتراضية: فـ`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (الافتراضي `true`) يسمح مسبقًا بعناوين URL المحلية وعناوين LAN على مسارات التحقق والاكتشاف وعُقد المزوّد؛ غير أن اختبار الوكيل الاحتياطي وأهداف webhook الخاصة لا يرجعان إلا إلى هذا الخيار ويحظران المضيفين المحليين/مضيفات LAN ما دام معطّلًا. يتجاوز هذا الخيار `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. والقيمة المحفوظة من مفتاح التبديل في لوحة المعلومات تسبق هذا المتغير. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | السماح بعناوين URL للمزوّد على العناوين المحلية/الخاصة (127.0.0.1 وlocalhost وLAN والنطاقات الخاصة) لكل من التحقق من المزوّدين، واكتشاف النماذج، والمحادثة، وسائر المسارات الصادرة المجاورة للمزوّد التي يحكمها وضع الحارس. **القيمة الافتراضية `true`** (الأولوية للمحلي): عندها يحظر الحارس نقاط نهاية البيانات الوصفية السحابية — كامل النطاق 169.254.0.0/16 إضافة إلى أسماء مضيفي البيانات الوصفية المعروفة (metadata.google.internal وmetadata.goog وIMDS لدى Alibaba/Azure/Oracle وfd00:ec2::254). اضبطها على `false` لوضع «العام فقط» الذي يحظر المضيفين الخاصين ومضيفات الاسترجاع الحلقي أيضًا. يفحص الحارس اسم المضيف أو قيمة IP الحرفية كما كُتبت، قبل أي بحث DNS؛ وفي وضع «العام فقط» تقوم أداة الجلب الآمن (safe-fetch wrapper) إضافةً إلى ذلك بتحليل اسم المضيف ورفض أي إجابة خاصة. يُطفئ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` هذه الفحوصات على المسارات المذكورة في صفّه. المزوّدون المحليون المدمجون، مثل LM Studio وOllama وvLLM وLlamafile وTriton، يتخطون جميع فحوصات المضيف للتحقق من المفاتيح والمحادثة. والقيمة المحفوظة من مفتاح التبديل في لوحة المعلومات تسبق هذا المتغير. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | السماح لمسارات `/v1/audio/*` (النسخ والكلام والترجمة) باستخدام عقدة مزوّد متوافقة مع OpenAI ومستضافة خارج localhost. تكون معطّلة افتراضيًا — فتوجيه الصوت إلى مضيف بعيد يغيّر هوية الخروج ويجب أن يكون قرارًا صريحًا من المشغّل. يُسمح دائمًا بعُقد الاسترجاع/العُقد الخاصة (localhost و127.0.0.1 و172.16-31.x)، ولا تتأثر بهذا الإعداد. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | السماح لـ`POST /v1/rerank` (وكذلك خطوة إعادة الترتيب عبر الاسترجاع في محرك الذاكرة) باستخدام عقدة مزوّد متوافقة مع OpenAI ومستضافة خارج localhost — مثل جهاز على LAN أو نظير Tailscale يشغّل TEI أوInfinity أوvLLM وما إلى ذلك. تكون معطّلة افتراضيًا — فتوجيه الطلب إلى مضيف بعيد يغيّر هوية الخروج ويجب أن يكون قرارًا صريحًا من المشغّل. يُسمح دائمًا بعُقد الاسترجاع (localhost و127.0.0.1 و172.16-31.x)، ولا تتأثر بهذا الإعداد. يجب أيضًا أن يجتاز عنوان URL الأساسي للعُقدة البعيدة سياسة عناوين URL الصادرة للمزوّد (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | عند تمكين OIDC، عطّل تسجيل الدخول بكلمة المرور بحيث لا يمكن للمستخدمين المصادقة إلا عبر تسجيل الدخول الأحادي باستخدام OIDC. يُقبل أيضًا الاسم البديل المجرّد `OIDC_DISABLE_PASSWORD_LOGIN`؛ وتكون لعلامة ميزة لوحة المعلومات التي تحمل المفتاح نفسه الأولوية. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | الاسم البديل المجرّد لـ`OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### قائمة التحقق من التحصين diff --git a/docs/i18n/az/CONTRIBUTING.md b/docs/i18n/az/CONTRIBUTING.md index 5a9d415681b5..c6d99795c535 100644 --- a/docs/i18n/az/CONTRIBUTING.md +++ b/docs/i18n/az/CONTRIBUTING.md @@ -391,18 +391,18 @@ Model təriflərini `open-sse/config/providerRegistry.ts` faylına əlavə edin. - [ ] Testlər uğurla keçir (`npm test`) - [ ] Lint yoxlaması uğurla keçir (`npm run lint`) -- [ ] Yığma uğurla tamamlanır (`npm run build`) +- [ ] Build uğurla tamamlanır (`npm run build`) - [ ] Yeni ictimai funksiyalar və interfeyslər üçün TypeScript tipləri əlavə edilib - [ ] Sərt kodlaşdırılmış məxfi məlumatlar və ya ehtiyat dəyərlər yoxdur - [ ] İctimai upstream giriş məlumatları literal kimi deyil, `resolvePublicCred()` vasitəsilə daxil edilib (bax: [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) -- [ ] Xəta cavabları `buildErrorBody()` / `sanitizeErrorMessage()` vasitəsilə yönləndirilir — cavab gövdələrində emal edilməmiş stek izləri yoxdur (bax: [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell əmrləri (`exec` / `spawn`) icra vaxtı dəyərlərini sətir interpolyasiyası ilə deyil, `env` vasitəsilə ötürür -- [ ] Bütün giriş məlumatları Zod sxemləri ilə doğrulanıb -- [ ] İstifadəçiyə təsir edən dəyişikliklər üçün `changelog.d/{features|fixes|maintenance}/-.md` altında dəyişiklik jurnalı **fraqmenti** əlavə edilib (bax: [`changelog.d/README.md`](./changelog.d/README.md)) — `CHANGELOG.md` faylını birbaşa redaktə **etməyin**; fraqmentlər buraxılış zamanı birləşdirilir və PR-lar arasında heç vaxt ziddiyyət yaratmır +- [ ] Xəta cavabları `buildErrorBody()` / `sanitizeErrorMessage()` vasitəsilə yönləndirilir — cavab gövdələrində emal edilməmiş stack trace-lər yoxdur (bax: [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell komandaları (`exec` / `spawn`) icra vaxtı dəyərlərini sətir interpolasiyası ilə deyil, `env` vasitəsilə ötürür +- [ ] Bütün girişlər Zod sxemləri ilə yoxlanılır +- [ ] İstifadəçiyə təsir edən dəyişikliklər üçün `changelog.d/{features|fixes|maintenance}/-.md` altında dəyişiklik jurnalının **fraqmenti** əlavə edilib (bax: [`changelog.d/README.md`](./changelog.d/README.md)) — `CHANGELOG.md` faylını birbaşa redaktə **etməyin**; fraqmentlər buraxılış zamanı birləşdirilir və PR-lər arasında heç vaxt ziddiyyət yaratmır - [ ] Sənədləşmə yenilənib (tətbiq olunduğu halda) - [ ] Yeni CodeQL / Secret-Scanning xəbərdarlıqları açılmayıb və ya hər biri müvafiq `docs/security/` sənədinə istinad edən texniki əsaslandırma ilə rədd edilib -- [ ] Alt proseslər yaradan marşrutlar (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` daxilində `isLocalOnlyPath()` kimi təsnif edilib — bax: [Sərt Qayda #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Commit mesajlarında `Co-Authored-By` treylerləri yoxdur — commitlər yalnız repozitoriya sahibinin Git identifikasiyası altında görünməlidir (Sərt Qayda #16) +- [ ] Alt proseslər yaradan marşrutlar (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` faylında `isLocalOnlyPath()` kimi təsnif edilib — bax: [Sərt Qayda #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Commit mesajlarında AI/bot `Co-authored-by` treylerləri yoxdur (Sərt Qayda #16) — işindən yenidən istifadə edilən insan əməkdaşlara standart `Co-authored-by: Name ` treylerləri ilə istinad edilir --- diff --git a/docs/i18n/az/docs/architecture/QUALITY_GATES.md b/docs/i18n/az/docs/architecture/QUALITY_GATES.md index 09b9c08c1da2..3102953c0f2f 100644 --- a/docs/i18n/az/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/az/docs/architecture/QUALITY_GATES.md @@ -12,274 +12,271 @@ Qısa xülasə və icazə siyahısı siyasəti üçün `AGENTS.md` faylındakı --- -## Qeydlərin inventarı və icra profilləri +## Qeyt inventarı və icra profilləri -### Namizədlərin qəbulu +### Namizədin qəbulu -CI və Quality Gates iş axınlarının hər biri sabit hökm yaradır: `Gate / CI` və -`Gate / Quality`. Onların versiyalaşdırılmış qəbul siyasəti hər bir yuxarı axın tapşırığını -məcburi və ya tövsiyə xarakterli olaraq siyahıya alır. Tətbiq olunan məcburi tapşırıq uğurla -başa çatmalıdır: mövcud olmayan, ləğv edilmiş, ötürülmüş, gözləmədə olan və naməlum nəticələr +CI və Quality Gates iş axınlarının hər biri sabit nəticə yaradır: `Gate / CI` və +`Gate / Quality`. Onların versiyalaşdırılmış qəbul siyasəti bütün yuxarı axın tapşırıqlarını +məcburi və ya tövsiyə xarakterli kimi sadalayır. Tətbiq olunan məcburi tapşırıq uğurla +tamamlanmalıdır: mövcud olmayan, ləğv edilmiş, ötürülmüş, gözləmədə olan və naməlum nəticələr PASS statusunu təsdiqləyə bilməz. Yalnız sənədlərə və ya yalnız kataloqa aid etibarlı təsnifat -kod xəttini tətbiqedilməz edə bilər; qaralama PR qəbul edilmiş namizəd deyil. `hotfix` etiketi -sübut tələbini aradan qaldırmır. - -Hər iki iş axını PR-ləri və əsas/buraxılış budaqlarına göndərişləri, əl ilə işə salmanı və -birləşdirmə qrupu hadisələrini əhatə edir. Göndəriş, əl ilə işə salma və birləşdirmə qrupu tam -seçimi icra edir. Fork-lar və birləşdirmə qrupları, əks halda öz serverində yerləşdirilən -icraçıları seçəcək tapşırıqlar üçün platformada yerləşdirilən icraçılardan istifadə edir; -tətbiqdən əvvəl kifayət qədər platforma tutumu yoxlanmalıdır. - -Hər JSON qəbzi seçilmiş SHA-nı, iş axını icrasını və cəhdi müəyyənləşdirir. -CLI seçilmiş versiya ilə hadisənin SHA-sı arasındakı uyğunsuzluğu rədd edir. İş axını testləri -siyasət üzvlüyünü hökm tapşırığının `needs` siyahısına bağlayır ki, yeni və ya silinmiş xətt -səssizcə yoxa çıxmasın. Qəbzlər yalnız öz iş axınlarını əhatə edir; nəşri, yerləşdirməni və ya -mövcud tövsiyə xarakterli skanerin daxili mexanizmlərini əhatə etmir. Budaq qaydalarında hər -iki yoxlama adının aktivləşdirilməsi ayrıca inzibati dəyişiklikdir; bu tapşırıqların əlavə -edilməsi öz-özlüyündə budağı qorumur. +kod zolağını tətbiqedilməz edə bilər; qaralama PR qəbul edilmiş namizəd deyil. `hotfix` +etiketi sübut tələbini aradan qaldırmır. + +Hər iki iş axını PR-ləri və main/release budaqlarına göndərişləri, əl ilə başladılmanı və +birləşdirmə qrupu hadisələrini əhatə edir. Göndəriş, başladılma və birləşdirmə qrupu tam +seçimi işlədir. Fork-lar və birləşdirmə qrupları, əks halda öz serverində yerləşdirilən +icraçıları seçəcək tapşırıqlar üçün hostinq olunan icraçılardan istifadə edir; tətbiqdən +əvvəl kifayət qədər hostinq tutumu yoxlanılmalıdır. + +Hər bir JSON qəbzi checkout edilmiş SHA-nı, iş axını icrasını və cəhdi müəyyən edir. +CLI checkout/hadisə SHA uyğunsuzluğunu rədd edir. İş axını testləri siyasət üzvlüyünü +nəticə tapşırığının `needs` siyahısına bağlayır ki, yeni və ya silinmiş zolaq səssizcə +yoxa çıxmasın. Qəbzlər nəşri, yerləşdirməni və ya mövcud tövsiyə xarakterli skanerin +daxili işini deyil, öz iş axınlarını əhatə edir. Budaq qaydalarında hər iki yoxlama adının +aktivləşdirilməsi ayrıca inzibati dəyişiklikdir; bu tapşırıqların əlavə edilməsi öz-özlüyündə +budağı qorumur. ### Statik skan inventarı Versiyalaşdırılmış npm-alias inventarı və statik skan üzvlüyü `config/quality/gate-manifest.json` daxilində yerləşir. Skript adlarını və dəqiq əmrləri -`package.json` ilə müqayisə edərək yoxlamaq üçün `npm run check:gate-manifest` əmrini icra edin; -əlavələr, silinmələr və əmr dəyişiklikləri həm lokal hook-un, həm də CI-dakı dəyişiklik -təsnifatı tapşırıqlarının uğursuz olmasına səbəb olur. Alias iş axını tapşırığı, matris -instansiyası və ya test nümunəsi deyil: bu saylar bir-birini əvəz edə bilən göstəricilər kimi -təqdim edilməməlidir. - -Seçilmiş alias-ları icra etmədən nəzərdən keçirmək üçün -`npm run quality:scan -- --list` və ya `npm run quality:scan:fast -- --list` -əmrindən istifadə edin. İcraçı npm giriş nöqtəsini çağırır, buna görə də onun icra mühiti -(konfiqurasiya edildiyi hallarda Bun daxil olmaqla) qorunur. Manifest həmin profillərdən -kənardakı alias-ları ayrıca çağırılanlar kimi qeydə alır və yalnız oxuma üçün nəzərdə tutulan -skan profillərində texniki xidmət əmrləri qadağandır. +`package.json` ilə müqayisədə doğrulamaq üçün `npm run check:gate-manifest` əmrini icra edin; +əlavələr, silinmələr və əmr dəyişiklikləri həm lokal hook-u, həm də CI daxilindəki +dəyişiklik təsnifatı tapşırıqlarını uğursuz edir. Alias iş axını tapşırığı, matris instansiyası +və ya test nümunəsi deyil: bu saylar bir-birini əvəz edə bilən kimi təqdim edilməməlidir. + +Seçilmiş alias-ları icra etmədən yoxlamaq üçün `npm run quality:scan -- --list` və ya +`npm run quality:scan:fast -- --list` istifadə edin. İcraçı npm giriş nöqtəsini çağırır, +buna görə onun icra mühiti (konfiqurasiya edildiyi hallarda Bun daxil olmaqla) qorunur. +Manifest həmin profillərdən kənardakı alias-ları ayrıca çağırılan kimi qeyd edir və +yalnız oxuma üçün nəzərdə tutulmuş skan profillərində texniki xidmət əmrləri qadağandır. Bu profillər yalnız statik skanı əhatə edir. Onlar məhsul testlərini, əhatə dairəsini, -paketləməni, xarici yoxlamaları və ya namizədin buraxılış üçün tam qəbulunu təsdiqləmir. -İş axınına qəbul əlaqəli `config/quality/admission-policy.json` və +paketləməni, xarici yoxlamaları və ya namizədin tam buraxılış qəbulunu təsdiqləmir. +İş axını qəbulu əlaqələndirilmiş `config/quality/admission-policy.json` və `scripts/quality/admission-verdict.mjs` fayllarından istifadə edir. Buraxılış müşahidəçisi -profilləri ayrı qalır; onların tətbiq olunan yoxlamalarını və qəbzlərini müstəqil şəkildə -nəzərdən keçirin. Aşağıdakı mətn inventarı istinad üçündür və qeydin həqiqətən icra olunduğuna -dəlil deyil. +profilləri ayrıca qalır; onların tətbiq olunan yoxlamalarını və qəbzlərini müstəqil şəkildə +yoxlayın. Aşağıdakı mətn inventarı istinad üçündür, qeytin həqiqətən işə salındığının +sübutu deyil. -Skriptlər `scripts/check/` (siyasət qeydləri) və `scripts/quality/` (ratchet mühərriki) +Skriptlər `scripts/check/` (siyasət qeytləri) və `scripts/quality/` (ratchet mühərriki) altında yerləşir. CI üçün əsas həqiqət mənbəyi `.github/workflows/ci.yml` faylıdır. ### Buraxılış PR-i üçün sürətli yol (`quality.yml`) -`.github/workflows/quality.yml` əsas/buraxılış PR-lərində, qorunan budaqlara göndərişlərdə, -əl ilə işə salmada və birləşdirmə qruplarında CI-ı tamamlayır. PR-lər yol üzrə filtrlənmiş -sürətli yoxlamalardan istifadə edir. Daimi olaraq deaktiv edilmiş dublikat yığım silinib; -real yığım/paketləmə/işə salma yoxlamaları CI-da qalır. - -| Tapşırıq | Əhatə dairəsi | Bloklayıcı | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `Docs Gates (fast-path)` | Sənəd/kod PR-ləri; API sənəd istinadları və bütün sənədlər | Bəli | -| `Fast Quality Gates` | Kod PR-ləri; statik yoxlamalar, tip yoxlaması, idarə panelinin tip yoxlaması, təsirə məruz qalan modul testləri | Bəli | -| `Forgotten sibling tests` | Kod PR-ləri; dəyişdirilmiş modulların statik istehlakçılara və ehtimal olunan qonşu testlərə qədər izlənməsi; barrel və dinamik import yolları, istinad edilən icazə siyahısı istisnaları ilə birlikdə tövsiyə xarakterli diaqnostika kimi bildirilir | **Tövsiyə xarakterli** | -| `Vitest (fast-path)` | Kod PR-ləri; sürətli vitest dəsti | Bəli | -| `Unit Tests fast-path` | Kod PR-ləri; 4 hissəli modul test dəsti | Bəli | -| `No new ESLint warnings` | Kod PR-ləri; susdurmaları nəzərə alan lint qoruyucusu | Bəli, fork-lar daxil olmaqla | -| `Merge integrity (changelog + generated skills)` | Qaralama olmayan PR-lər; dəyişiklik jurnalı və yaradılmış bacarıqların sinxronlaşdırılması | Bəli, fork-lar daxil olmaqla | - -#### Unudulmuş qonşu testlər hesabatı - -`npm run check:forgotten-sibling-tests` test təsiri xəritəsinin əsasındakı import -həlledicisindən təkrar istifadə edir. Dəyişdirilmiş hər bir istehsal modulu üçün, ehtimal -olunan test pull-request fərqində olmadıqda, deterministik -`dəyişdirilmiş modul/simvol -> statik istehlakçı -> ehtimal olunan qonşu test` zəncirlərini -bildirir. Markdown xülasəsi və JSON nəticəsi hər hansı bloklayıcı tətbiqdən əvvəl -kalibrləmə üçün `forgotten-sibling-tests` iş axını artefaktı kimi saxlanılır. - -Barrel təkrar ixracları və dinamik importlar yalnız həll diaqnostikası üçündür; onlar heç vaxt -bloklayıcı tapıntı yaratmır. Nəzərdən keçirilmiş istisnalar -`config/quality/forgotten-sibling-allowlist.json` faylında saxlanılır. Hər bir qeyd istehlakçı və namizəd +`.github/workflows/quality.yml` main/release PR-lərində, qorunan budaqlara göndərişlərdə, +əl ilə başladılmada və birləşdirmə qruplarında CI-ni tamamlayır. PR-lər yol üzrə filtrlənmiş +sürətli yoxlamalardan istifadə edir. Daimi olaraq deaktiv edilmiş dublikat qurma prosesi +silinib; həqiqi qurma/paketləmə/işəsalma yoxlamaları CI daxilində qalır. + +| Tapşırıq | Əhatə dairəsi | Bloklayıcı | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `Docs Gates (fast-path)` | Sənəd/kod PR-ləri; API sənəd istinadları və bütün sənədlər | Bəli | +| `Fast Quality Gates` | Kod PR-ləri; statik yoxlamalar, tip yoxlaması, idarə panelinin tip yoxlaması, təsirlənmiş vahid testləri | Bəli | +| `Forgotten sibling tests` | Kod PR-ləri; dəyişdirilmiş modulların statik istehlakçılara və namizəd qardaş testlərə qədər izlənməsi; barrel və dinamik idxal yolları, istinad edilən icazə siyahısı istisnaları ilə birlikdə, tövsiyə xarakterli diaqnostika kimi bildirilir | **Tövsiyə xarakterli** | +| `Vitest (fast-path)` | Kod PR-ləri; sürətli vitest dəsti | Bəli | +| `Unit Tests fast-path` | Kod PR-ləri; 4 hissəli vahid test dəsti | Bəli | +| `No new ESLint warnings` | Kod PR-ləri; susdurmaları nəzərə alan lint qoruyucusu | Bəli, fork-lar daxil olmaqla | +| `Merge integrity (changelog + generated skills)` | Qaralama olmayan PR-lər; dəyişiklik jurnalı və yaradılmış bacarıqların sinxronlaşdırılması | Bəli, fork-lar daxil olmaqla | + +#### Unudulmuş qardaş testlər hesabatı + +`npm run check:forgotten-sibling-tests` test təsiri xəritəsinin arxasındakı idxal həlledicisindən +təkrar istifadə edir. Hər bir dəyişdirilmiş istehsal modulu üçün namizəd test pull request +fərqində olmadıqda deterministik +`dəyişdirilmiş modul/simvol -> statik istehlakçı -> namizəd qardaş test` zəncirlərini bildirir. +Markdown xülasəsi və JSON nəticəsi hər hansı bloklayıcı tətbiqdən əvvəl kalibrləmə üçün +`forgotten-sibling-tests` iş axını artefaktı kimi saxlanılır. + +Barrel təkrar ixracları və dinamik importlar yalnız həll etmə diaqnostikasıdır; onlar heç vaxt +bloklayıcı aşkarlama yaratmır. Nəzərdən keçirilmiş istisnalar +`config/quality/forgotten-sibling-allowlist.json` faylında saxlanılır. Hər qeyd istehlakçı və namizəd testi göstərməli, konkret əsaslandırma təqdim etməli və GitHub məsələsinə və ya pull request-ə keçid verməlidir. Səhv formatlanmış qeydlər -qapalı rejimdə uğursuz olur. İstisnalar silinmiş namizəd testi və ya `.skip`/`.todo` əlavə edən diff-i gizlədə bilməz; -assertion-ların zəiflədilməsi və digər maskalama halları ayrıca bloklayıcı olan -`check:test-masking` keçidinin məsuliyyətində qalır. +təhlükəsiz şəkildə rədd edilir. İstisnalar silinmiş namizəd testi və ya `.skip`/`.todo` əlavə edən diff-i gözardı edə bilməz; +assertion-ların zəiflədilməsi və digər maskalama halları müstəqil şəkildə bloklayan +`check:test-masking` keçidinin nəzarətində qalır. ### Tapşırıq: `lint` -`main` budağına göndərilən hər PR üçün icra olunur. Uğursuz olduqda birləşdirməni bloklayır. - -| Skript (`npm run ...`) | Yoxlayır | Bloklayıcı | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | -| `check:node-runtime` | Node.js versiyasının dəstəklənən diapazonda olmasını | Bəli | -| `check:cycles` | Dövri importları — bütün `src/` + `open-sse/` modullarında | Bəli | -| `check:route-validation:t06` | Zod sxemlərinin bütün marşrutlarda mövcud olmasını (Tier 6 siyasəti) | Bəli | -| `check:any-budget:t11` | `@ts-expect-error // any` sayının büdcəni aşmamasını (Tier 11 catraca) | Bəli | -| `check:provider-consistency` | `providers.ts` daxilindəki hər bir provayderin `providerRegistry.ts` daxilində uyğun qeydi var (və əksinə, icazə verilənlər siyahısı çərçivəsində) | Bəli | -| `check:model-lifecycle` | Əl ilə saxlanılan üç marşrutlaşdırma cədvəli repozitoriyada qeydə alınmış həyat dövrü ani görüntüsü (#11503) ilə uyğun qalır: `FITNESS_TABLE` (`taskFitness.ts`) `REGISTRY` tərəfindən marşrutlaşdırıla bilən istifadədən çıxarılmış heç bir identifikatoru qiymətləndirmir; hər bir `BUILT_IN_ALIASES` hədəfi `REGISTRY` daxilində mövcuddur və istifadədən çıxarılmış identifikatorların ani görüntüsündə yoxdur; `REGISTRY` daxilində hələ də mövcud olan hər bir istifadədən çıxarılmış identifikator yönləndirilir və ya `allowedRetiredInCatalog` daxilində siyahıya alınır; və heç bir `DEFAULT_DEGRADATION_MAP` mənbəyi və ya hədəfi həmin ani görüntüdə istifadədən çıxarılmış kimi görünmür. Bu, modelin hazırda aktiv yuxarı axın xidməti tərəfindən təqdim edildiyini sübut etmir. Oflayn — əl ilə `npm run quality:refresh-model-lifecycle` vasitəsilə yenilənən `config/quality/model-lifecycle.json` ilə müqayisə edir (şəbəkə tələb olunur; CI-a qoşulmayıb). `allowedRetiredInCatalog` tədricən azaldılmalı olan siyahıdır: yalnız izləmə məsələsi olduqda qeyd əlavə edin. | Bəli | -| `check:fetch-targets` | Müştəri tərəfindəki `src/` daxilində olan hər bir `fetch("/api/...")` real `route.ts` faylına yönəlir | Bəli | -| `check:deps` | Repozitoriyadakı hər bir `package.json` faylında olan və `npm install` ilə quraşdırıla bilən bütün asılılıqlar `dependency-allowlist.json` daxilindədir; yeni versiyası sabitlənməmiş və ya slopsquatting hücumuna məruz qala bilən paketlər işarələnir | Bəli | -| `audit:deps` | `npm audit` (kök + electron) — yüksək/kritik səviyyəli xəbərdarlıqlar yoxdur (osv `check:vuln-ratchet` ilə üst-üstə düşür; Rasionallaşdırma üzrə görüləcək işlər siyahısına baxın) | Bəli | -| `check:lockfile` | `package-lock.json` bütövlüyü — https reyestri, bütövlük heşləri, host əvəzləmələri yoxdur | Bəli | -| `check:licenses` | İstehsal asılılıqları üçün SPDX lisenziyalarının icazə siyahısı | Bəli | -| `check:tracked-artifacts` | Build artefaktları / repozitoriyaya daxil edilmiş `node_modules` simvolik keçidləri yoxdur (həmçinin husky pre-commit zamanı işə salınır; pre-push qəsdən yüngüldür — #6716) | Bəli | -| `check:ai-attribution` | PR commit-lərində, başlığında və ya mətnində AI/bot `Co-Authored-By` sonluğu və ya AI tərəfindən generasiya edildiyini bildirən altbilgi yoxdur — Sərt Qayda #16 (`quality.yml` faylındakı PR→`release/**` üçün fast-gates dövründə — hadisə yükünü oxuyur, PR olmadığı halda heç bir əməliyyat aparmır — və `ci.yml` lint yoxlamasında PR→`main` üçün yalnız PR-a aid addım; həmçinin husky `commit-msg` hook-u; insan həmmüəlliflərə icazə verilir; #14436) | -| `check:vitest-exclusions` | Hər bir Vitest istisnası izləmə məsələsini göstərir və `config/quality/vitest-exclusions.json` faylında yer alır (#13204) | Bəli | -| `check:file-size` | Heç bir mənbə faylı genişlənmə üzrə limiti aşmır (ratchet: böyük fayllar `frozen` siyahısında dondurulub) | Bəli | -| `check:error-helper` | İcraedicilərdə/işləyicilərdə xəta cavabları `buildErrorBody()` / `sanitizeErrorMessage()` funksiyalarından istifadə edir (Sərt Qayda #12) | Bəli | -| `check:migration-numbering` | Miqrasiya SQL faylları ardıcıl nömrələnib, boşluqlar və ya təkrarlar yoxdur | Bəli | -| `check:public-creds` | `publicCreds.ts` xaricində heç bir literal OAuth `client_id`/`client_secret` və ya Firebase Web açarı yoxdur (Sərt Qayda #11) | Bəli | -| `check:db-rules` | `src/lib/db/` modullarından kənarda xam SQL yoxdur; `localDb.ts` faylından barrel-importlar yoxdur (Sərt Qaydalar #2/#5) | Bəli | -| `check:known-symbols` | Dispetçer cədvəllərində qeydiyyata alınmış provayder icraçıları, marşrutlaşdırma strategiyaları və tərcüməçilər diskdəki fayllarla uyğun gəlir — sahibsiz və ya elan edilməmiş simvollar yoxdur | Bəli | -| `check:route-guard-membership` | Alt proses yaradan hər bir marşrut `isLocalOnlyPath()` tərəfindən təsnif edilir (Sərt Qaydalar #15/#17) | Bəli | -| `check:test-discovery` | Repozitoriyadakı hər bir `*.test.ts` / `*.spec.ts` faylı ən azı bir test icraçısı tərəfindən aşkarlanır (xırxıra mexanizmi: `test-discovery-baseline.json` faylındakı sahibsiz fayllar siyahısı yalnız kiçilə bilər) | Bəli | -| `check:agent-skills-sync` | Yaradılmış agent-skills artefaktları mənbə kataloqu ilə uyğun gəlir (fərqlənmə yoxdur) | -| `check:provider-asset-provenance` | Provayder loqoları/aktivləri qeydə alınmış mənşə qeydini ehtiva edir | -| `lint:json` | JSON konfiqurasiya faylları parsinq olunur və repozitoriyanın lint qaydalarına cavab verir | -| `typecheck:core` | TypeScript kompilyasiyası xətasız tamamlanır (yalnız tövsiyə xarakterli xəbərdarlıqlar) | Bəli | -| `typecheck:noimplicit:core` | Sərt `noImplicitAny` — gələcəyə yönəlikdir; əvvəlcədən mövcud olan bir çox çağırış nöqtəsi hələ də annotasiyalara ehtiyac duyur | **Tövsiyə xarakterli** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` ilə məhdudlaşdırılmış `tsc` (#7033) — `typecheck:core` üçün hazırlanmış 27 fayllıq icazə siyahısına heç bir dashboard TSX faylı daxil deyil və `next build` də onları heç vaxt tip yoxlamasından keçirmir (`next.config.mjs` faylında `ignoreBuildErrors: true` təyin olunub), buna görə də oradakı sahibsiz identifikator reqressiyaları (#6625/#6909) CI üçün görünməz idi. Dondurulmuş fayl üzrə/TS kodu üzrə say bazası (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ilə eyni köhnəlmə yoxlaması modeli) ilə fərqləri müqayisə edir — yalnız baza sayından artıq YENİ xətalar keçidin uğursuz olmasına səbəb olur; əvvəlcədən mövcud olan xəta düzəldildikdə `--update` ilə baza sayını azaldın. | Bəli | +`main` budağına edilən hər PR üçün işə salınır. Uğursuz olduqda birləşdirməni bloklayır. + +| Skript (`npm run ...`) | Yoxlayır | Bloklayıcı | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `check:node-runtime` | Node.js versiyasının dəstəklənən diapazonda olub-olmadığını | Bəli | +| `check:cycles` | Bütün `src/` + `open-sse/` üzrə dövri importları (AST əsaslıdır, tsconfig `paths` həll edilir). Sadə rejim = məsləhət xarakterlidir, dövrləri sadalayır. `check:cycles:ratchet` (CI-nin işə saldığı) say `quality-baseline.json` faylındakı `metrics.cycles` həddini aşdıqda bloklayır — hazırda 14, `direction: down`, buna görə də yalnız azala bilər (#15159 G-01/G-02) | Bəli (ratchet) | +| `check:route-validation:t06` | Zod sxemlərinin bütün marşrutlarda mövcudluğunu (Tier 6 siyasəti) | Bəli | +| `check:any-budget:t11` | `@ts-expect-error // any` sayının büdcəni aşmamasını (Tier 11 ratchet-i) | Bəli | +| `check:provider-consistency` | `providers.ts` faylındakı hər bir provayderin `providerRegistry.ts` faylında uyğun qeydi var (və əksinə, icazə siyahısı daxilində) | Bəli | +| `check:model-lifecycle` | Əl ilə idarə olunan üç marşrutlaşdırma cədvəli repozitoriyaya daxil edilmiş həyat dövrü anlıq görüntüsü (#11503) ilə uyğun qalır: `FITNESS_TABLE` (`taskFitness.ts`) `REGISTRY` tərəfindən marşrutlaşdırıla bilən heç bir istifadədən çıxarılmış id-ni qiymətləndirmir; hər `BUILT_IN_ALIASES` hədəfi `REGISTRY` daxilində mövcuddur və istifadədən çıxarılmış id-lərin anlıq görüntüsündə yoxdur; `REGISTRY` daxilində hələ də qalan hər istifadədən çıxarılmış id yönləndirilir və ya `allowedRetiredInCatalog` siyahısında göstərilir; həmçinin heç bir `DEFAULT_DEGRADATION_MAP` mənbəyi və ya hədəfi həmin anlıq görüntüdə istifadədən çıxarılmış kimi göstərilmir. Bu, modelin hazırda aktiv yuxarı axın xidməti tərəfindən təqdim olunduğunu sübut etmir. Oflayn — əl ilə `npm run quality:refresh-model-lifecycle` vasitəsilə yenilənən `config/quality/model-lifecycle.json` ilə müqayisə edir (şəbəkə tələb olunur; CI sisteminə qoşulmayıb). `allowedRetiredInCatalog` tədricən azaldılan məhdudlaşdırıcı siyahıdır: qeydi yalnız izləmə məsələsi ilə birlikdə əlavə edin. | Bəli | +| `check:fetch-targets` | Müştəri tərəfindəki `src/` daxilində hər `fetch("/api/...")` real `route.ts` faylına yönəlir | Bəli | +| `check:deps` | Repozitoriyadakı hər `package.json` faylında `npm install` vasitəsilə quraşdırıla bilən bütün asılılıqlar `dependency-allowlist.json` daxilindədir; versiyası sabitlənməmiş və ya slopsquatting riski daşıyan yeni paketlər işarələnir | Bəli | +| `audit:deps` | `npm audit` (kök + electron) — yüksək/kritik səviyyəli xəbərdarlıq yoxdur (osv `check:vuln-ratchet` ilə üst-üstə düşür; Əsaslandırma Gözləyən İşlər Siyahısına baxın) | Bəli | +| `check:lockfile` | `package-lock.json` bütövlüyü — https reyestri, bütövlük heşləri, host əvəzləmələri yoxdur | Bəli | +| `check:licenses` | İstehsal asılılıqları üçün SPDX lisenziyalarının icazə siyahısı | Bəli | +| `check:tracked-artifacts` | Build artefaktları / repozitoriyaya əlavə edilmiş `node_modules` simvolik keçidləri yoxdur (həmçinin husky pre-commit zamanı işləyir; pre-push qəsdən yüngüldür — #6716) | Bəli | +| `check:ai-attribution` | PR commit-lərində, başlığında və ya mətnində AI/bot `Co-Authored-By` treyleri və ya AI tərəfindən yaradılma altbilgisi yoxdur — Sərt Qayda #16 (`quality.yml` faylındakı PR→`release/**` üçün fast-gates dövrəsində — hadisə payload-unu oxuyur, PR olmadıqda heç bir əməliyyat etmir — və `ci.yml` lint prosesində PR→`main` üçün yalnız PR-a aid addım; həmçinin husky `commit-msg` hook-u; insan həmmüəlliflərə icazə verilir; #14436) | +| `check:vitest-exclusions` | Hər bir Vitest istisnası izləmə məsələsini göstərir və `config/quality/vitest-exclusions.json` faylında yer alır (#13204) | Bəli | +| `check:file-size` | Heç bir mənbə faylı genişlənməyə görə müəyyən edilmiş limiti aşmır (ratchet: böyük fayllar `frozen` siyahısında dondurulub) | Bəli | +| `check:error-helper` | İcraçılarda/işləyicilərdə xəta cavabları `buildErrorBody()` / `sanitizeErrorMessage()` funksiyalarından istifadə edir (Sərt Qayda #12) | Bəli | +| `check:migration-numbering` | Miqrasiya SQL faylları boşluqlar və ya dublikatlar olmadan ardıcıl nömrələnib | Bəli | +| `check:public-creds` | `publicCreds.ts` xaricində heç bir literal OAuth `client_id`/`client_secret` və ya Firebase Web açarı yoxdur (Sərt Qayda #11) | Bəli | +| `check:db-rules` | `src/lib/db/` modullarından kənarda xam SQL yoxdur; `localDb.ts` faylından barrel-importlar yoxdur (Sərt Qaydalar #2/#5) | Bəli | +| `check:known-symbols` | Dispetçer cədvəllərində qeydiyyatdan keçirilmiş provayder icraçıları, marşrutlaşdırma strategiyaları və tərcüməçilər diskdəki fayllarla uyğun gəlir — yetim və ya elan edilməmiş simvollar yoxdur | Bəli | +| `check:route-guard-membership` | Alt proses yaradan hər bir marşrut `isLocalOnlyPath()` tərəfindən təsnif edilir (Sərt Qaydalar #15/#17) | Bəli | +| `check:test-discovery` | Repodakı hər bir `*.test.ts` / `*.spec.ts` faylı ən azı bir test icraçısı tərəfindən toplanır (ratchet: `test-discovery-baseline.json` faylındakı yetimlər siyahısı yalnız kiçilə bilər) | Bəli | +| `check:agent-skills-sync` | Yaradılmış agent-skills artefaktları mənbə kataloqu ilə uyğun gəlir (fərqlənmə yoxdur) | +| `check:provider-asset-provenance` | Provayder loqoları/aktivləri qeydə alınmış mənşə qeydini ehtiva edir | +| `lint:json` | JSON konfiqurasiya faylları təhlil olunur və repozitoriyanın lint qaydalarına cavab verir | +| `typecheck:core` | TypeScript kompilyasiyası xətasız tamamlanır (yalnız tövsiyə xarakterli xəbərdarlıqlar) | Bəli | +| `typecheck:noimplicit:core` | Sərt `noImplicitAny` — gələcəyə yönəlib; əvvəlcədən mövcud olan bir çox çağırış nöqtəsi hələ də annotasiyalara ehtiyac duyur | **Tövsiyə xarakterli** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` ilə məhdudlaşdırılmış `tsc` (#7033) — `typecheck:core` modulunun xüsusi seçilmiş 27 fayllıq icazə siyahısına heç bir idarə paneli TSX faylı daxil deyil və `next build` də onları heç vaxt tip yoxlamasından keçirmir (`next.config.mjs` faylında `ignoreBuildErrors: true` təyin edilib), buna görə də oradakı sahibsiz identifikator reqressiyaları (#6625/#6909) CI üçün görünməz idi. Dondurulmuş hər-fayl/hər-TS-kod xəta sayı bazası (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ilə eyni köhnəlmə nəzarəti nümunəsi) ilə müqayisə aparır — yalnız baza sayını aşan YENİ xətalar yoxlamanın uğursuz olmasına səbəb olur; əvvəlcədən mövcud olan xəta düzəldildikdə `--update` ilə bazanı tədricən azaldın. | Bəli | ### Tapşırıq: `quality-gate` -`test-coverage` tamamlandıqdan sonra işə düşür. Uğursuzluq halında birləşdirməni bloklayır. +`test-coverage` tamamlandıqdan sonra işə düşür. Uğursuz olarsa, birləşdirməni bloklayır. -| Skript | Yoxlayır | Bloklayıcı | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | `quality-metrics.json` faylını yaradır (ESLint xəbərdarlıqlarının sayı, birləşdirilmiş shard hesabatından əhatə göstəricisi) | Bəli (ratchet-dən əvvəl) | -| `quality:ratchet` | `quality-baseline.json` faylındakı heç bir metrik geriləməyib (ESLint xəbərdarlıqları ≤ baza göstəricisi; əhatə ≥ baza göstəricisi) | Bəli | -| `check:duplication` | Kod təkrarlanması (jscpd@4) `quality-baseline.json` faylındakı baza göstəricisini aşmır | Bəli | -| `check:complexity` | Fayl səviyyəsində siklomatik mürəkkəblik həddi aşmır (əsas ESLint `complexity` + `max-lines-per-function`) | Bəli | -| `check:cognitive-complexity` | Koqnitiv mürəkkəblik ratchet-i (`eslint-plugin-sonarjs`) — ayrıca ESLint keçidi; CI hər ikisini vahid `check:complexity-ratchets` addımı kimi birlikdə işə salır | Bəli | -| `check:dead-code` | İstifadə olunmayan eksportlar / fayllar üçün ratchet (knip) baza göstəricisi ilə müqayisədə geriləmir | Bəli | -| `check:compression-budget` | Sıxılma bençmarkı büdcəsi — hər mühərrik üzrə token qənaətinin minimum hədləri geriləməməlidir | Bəli | -| `check:type-coverage` | Tiplənmə faizi ratchet-i (`type-coverage`) geriləmir; əsasən `typecheck:noimplicit:core` yoxlamasını əvəz edir | Bəli | -| `check:codeql-ratchet` | Açıq CodeQL xəbərdarlıqlarının sayı artmır (`gh api` vasitəsilə oxuyur; token olmadıqda təhlükəsiz şəkildə ötürür) — yeniləmə tezliyi və əl ilə işə salma: aşağıdakı "CodeQL ratchet" bölməsinə baxın | Bəli | +| Skript | Yoxlayır | Bloklayıcı | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | `quality-metrics.json` yaradır (ESLint xəbərdarlıqlarının sayı, birləşdirilmiş şard hesabatından əhatə göstəricisi) | Bəli (ratchet-dən əvvəl) | +| `quality:ratchet` | `quality-baseline.json` faylındakı hər bir metrik geriləməyib (ESLint xəbərdarlıqları ≤ baza göstəricisi; əhatə ≥ baza göstəricisi) | Bəli | +| `check:duplication` | Kod təkrarlanması (jscpd@4) `quality-baseline.json` faylındakı baza göstəricisini aşmır | Bəli | +| `check:complexity` | Fayl səviyyəsində siklomatik mürəkkəblik həddi aşmır (əsas ESLint `complexity` + `max-lines-per-function`) | Bəli | +| `check:cognitive-complexity` | Koqnitiv mürəkkəblik ratchet-i (`eslint-plugin-sonarjs`) — ayrıca ESLint keçidi; CI hər ikisini vahid `check:complexity-ratchets` addımı kimi birləşdirərək işə salır | Bəli | +| `check:dead-code` | İstifadə olunmayan eksportlar / fayllar ratchet-i (knip) baza göstəricisi ilə müqayisədə geriləmir | Bəli | +| `check:compression-budget` | Sıxılma etalon testinin büdcəsi — hər mühərrik üzrə token qənaətinin minimum hədləri geriləməməlidir | Bəli | +| `check:type-coverage` | Tipləşdirilmiş kod faizi ratchet-i (`type-coverage`) geriləmir; `typecheck:noimplicit:core` yoxlamasını böyük ölçüdə əvəz edir | Bəli | +| `check:codeql-ratchet` | Açıq CodeQL xəbərdarlıqlarının sayı artmır (`gh api` vasitəsilə oxuyur; token olmadıqda xətasız ötürür) — yenilənmə tezliyi və əl ilə işə salma: aşağıdakı "CodeQL ratchet" bölməsinə baxın | Bəli | ### Tapşırıq: `quality-extended` -Bütün tapşırıq tövsiyə xarakterlidir (`continue-on-error: true`). npm əsaslı ratchet-lər -faktiki olaraq işləyir; xarici skanerlər `gh release download` vasitəsilə quraşdırılır və -binar fayl hələ də mövcud olmadıqda özlərini ötürürlər (exit 0). +Bütün tapşırıq məsləhət xarakterlidir (`continue-on-error: true`). npm əsaslı ratchet-lər +real olaraq işə salınır; xarici skanerlər `gh release download` vasitəsilə quraşdırılır və +binar fayl hələ də mövcud olmadıqda özünü ötürür (exit 0). -| Skript | Yoxlayır | Bloklayıcı | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- | -| `check:circular-deps` | Dövri asılılıqların olmaması (dpdm) | **Tövsiyə xarakterli** | -| `check:bundle-size` | Paket ölçüsünün həddi aşmaması | **Tövsiyə xarakterli** | -| `check:secrets` | Məxfi məlumatların skan edilməsi (gitleaks) — binar fayl olmadıqda ötürür | **Tövsiyə xarakterli** | -| `check:vuln-ratchet` | Asılılıqlardakı zəifliklər (osv-scanner) geriləmir — binar fayl olmadıqda ötürür | **Tövsiyə xarakterli** | -| `check:workflows` | İş axını lint yoxlaması (actionlint + zizmor); çatışmayan/işləməyən skanerlər, etibarsız hesabatlar və ya çatışmayan ratchet baza göstəricisi INCOMPLETE kimi uğursuz olur. Etibarlı tapıntılar seçilmiş sərt/tövsiyə/ratchet siyasətinə tabedir | İcra tələb olunur; zizmor ratchet-i CI-də bloklayıcıdır | -| `check:openapi-breaking` | İctimai API müqaviləsində (`openapi.yaml`) baza budağı ilə müqayisədə geriyə uyğunluğu pozan dəyişikliklər (oasdiff) — `openapiBreaking=N` yaradır; oasdiff olmadıqda və ya baza spesifikasiyası həll edilə bilmədikdə ötürür | **Tövsiyə xarakterli** | +| Skript | Yoxlayır | Bloklayıcı | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| `check:circular-deps` | Dövri asılılıqların olmaması (dpdm) | **Məsləhət xarakterli** | +| `check:bundle-size` | Paket ölçüsü həddi aşmır | **Məsləhət xarakterli** | +| `check:secrets` | Məxfi məlumatların skan edilməsi (gitleaks) — binar fayl olmadıqda ötürülür | **Məsləhət xarakterli** | +| `check:vuln-ratchet` | Asılılıqlardakı zəifliklər (osv-scanner) geriləmir — binar fayl olmadıqda ötürülür | **Məsləhət xarakterli** | +| `check:workflows` | İş axını lint-i (actionlint + zizmor); çatışmayan/nasaz skanerlər, etibarsız hesabatlar və ya çatışmayan ratchet baza göstəricisi INCOMPLETE kimi uğursuz sayılır. Etibarlı tapıntılar seçilmiş sərt/məsləhət/ratchet siyasətinə tabedir | İcra tələb olunur; zizmor ratchet-i CI-də bloklayıcıdır | +| `check:openapi-breaking` | Açıq API müqaviləsində (`openapi.yaml`) baza budağı ilə müqayisədə geriyə uyğunluğu pozan dəyişikliklər (oasdiff) — `openapiBreaking=N` yaradır; oasdiff olmadıqda və ya baza spesifikasiyasını müəyyən etmək mümkün olmadıqda ötürülür | **Məsləhət xarakterli** | ### Tapşırıq: `docs-sync-strict` -`main` budağına açılan hər PR üçün işə salınır. Uğursuz olduqda birləşdirməni bloklayır. - -| Skript | Yoxlayır | Bloklayıcı | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | -| `check:docs-all` | Aşağıdakı 6 alt-keçidi ardıcıl işə salan meta-keçid | Bəli | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versiyalarının uyğunluğu | Bəli | -| ↳ `check:docs-counts` | Mətndəki sayların (provayder sayı, miqrasiya sayı və s.) real sayların ratchet intervalında olması | Bəli | -| ↳ `check:env-doc-sync` | `.env.example` daxilindəki hər bir mühit dəyişəninin sənədlərdəki cədvəldə təsvir edilməsi və əksinin də doğru olması | Bəli | -| ↳ `check:deprecated-versions` | Sənədlərdə köhnəlmiş versiya sətirlərinin olmaması | Bəli | -| ↳ `check:doc-links` | Sənədlərdəki daxili markdown keçidlərinin real fayllara yönəlməsi (`[mətn]`/`(path)` forması) | Bəli | -| ↳ `check:fabricated-docs` | Sənədlərdə qeyd olunan marşrutların, mühit dəyişənlərinin, CLI əmrlərinin, hook adlarının və fayl yollarının kod bazasında mövcud olması. `--strict` vasitəsilə sərt keçid; bayraq olmadan yumşaq xəta. | Bəli (CI-də `--strict` vasitəsilə) | -| `check:cli-i18n` | CLI əmr sətirlərinin bütün i18n lokal fayllarında mövcud olması | Bəli | -| `check:openapi-coverage` | OpenAPI spesifikasiyasının real marşrutların ən azı ratchet ilə müəyyən edilmiş minimum həddini əhatə etməsi | Bəli | -| `check:openapi-security-tiers` | `openapi.yaml` daxilindəki təhlükəsizlik səviyyəsi annotasiyalarının `routeGuard.ts` təsnifatları ilə uyğunluğu | **Tövsiyə xarakterli** | -| `check:openapi-routes` | `openapi.yaml` daxilindəki hər yolun real `route.ts` faylına uyğun gəlməsi (hallüsinasiyaya qarşı) | Bəli | -| `check:docs-symbols` | `docs/**/*.md` daxilindəki hər `/api/...` istinadının real `route.ts` faylına uyğun gəlməsi (hallüsinasiyaya qarşı) | Bəli | -| `i18n translation drift` | i18n lokal fayllarında tərcümə edilməmiş açarlar — yalnız xəbərdarlıq | **Tövsiyə xarakterli** | +`main` budağına hər PR zamanı işə salınır. Uğursuzluq halında birləşdirməni bloklayır. + +| Skript | Yoxlayır | Bloklayıcı | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | +| `check:docs-all` | Aşağıdakı 6 alt-keçidi ardıcıl işə salan meta-keçid | Bəli | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versiyalarının uyğunluğu | Bəli | +| ↳ `check:docs-counts` | Mətndəki sayların (provayder sayı, miqrasiya sayı və s.) real sayların ratchet pəncərəsi daxilində olması | Bəli | +| ↳ `check:env-doc-sync` | `.env.example` faylındakı hər bir mühit dəyişəninin sənədlərdəki cədvəldə təsvir edilməsi və əksinin də doğru olması | Bəli | +| ↳ `check:deprecated-versions` | Sənədlərdə köhnəlmiş versiya sətirlərinin olmaması | Bəli | +| ↳ `check:doc-links` | Sənədlərdəki daxili markdown keçidlərinin real fayllara yönəlməsi (`[mətn]`/`(path)` forması) | Bəli | +| ↳ `check:fabricated-docs` | Sənədlərdə qeyd edilən marşrutların, mühit dəyişənlərinin, CLI əmrlərinin, hook adlarının və fayl yollarının kod bazasında mövcud olması. `--strict` ilə sərt keçid; bayraq olmadan yumşaq xəta. | Bəli (CI-də `--strict` vasitəsilə) | +| `check:cli-i18n` | CLI əmr sətirlərinin bütün i18n lokalizasiya fayllarında mövcud olması | Bəli | +| `check:openapi-coverage` | OpenAPI spesifikasiyasının real marşrutların ən azı ratchet ilə müəyyən edilmiş minimumunu əhatə etməsi | Bəli | +| `check:openapi-security-tiers` | `openapi.yaml` faylındakı təhlükəsizlik səviyyəsi annotasiyalarının `routeGuard.ts` təsnifatları ilə uyğun olması | **Tövsiyə xarakterli** | +| `check:openapi-routes` | `openapi.yaml` faylındakı hər bir yolun real `route.ts` faylına yönəlməsi (hallüsinasiyaya qarşı) | Bəli | +| `check:docs-symbols` | `docs/**/*.md` daxilindəki hər bir `/api/...` istinadının real `route.ts` faylına yönəlməsi (hallüsinasiyaya qarşı) | Bəli | +| `i18n translation drift` | i18n lokalizasiya fayllarındakı tərcümə edilməmiş açarlar — yalnız xəbərdarlıq | **Tövsiyə xarakterli** | ### İş: `i18n-ui-coverage` -| Skript | Yoxlayır | Bloklayıcı | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `check-ui-keys-coverage` (inline) | UI i18n açarlarının əhatə səviyyəsi ≥ 65%-dir | Bəli | -| `check-ui-value-drift` (inline) | Yenidən yazılmış ingiliscə **dəyər** köhnəlmiş tərcümə saxlamır | Bəli | -| `check-new-key-coverage` (inline) | **Yeni** ingiliscə açar hər lokalda tərcümə olunur — `__MISSING__:` markeri rədd edilir | Bəli | -| `check-translation-ratio` | Hər lokal üzrə real tərcümə nisbəti (icazə siyahısından kənarda ingiliscə ilə eyni / yer tutucu / çatışmayan son düyünlər) `config/quality/i18n-translation-baseline.json` + ehtiyat həddini aşmamalıdır | **Tövsiyə xarakterli** | +| Skript | Yoxlayır | Bloklayıcı | +| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `check-ui-keys-coverage` (sətirdaxili) | UI i18n açarlarının əhatə dairəsi ≥ 65%-dir | Bəli | +| `check-ui-value-drift` (sətirdaxili) | Yenidən yazılmış ingiliscə **dəyər** geridə köhnəlmiş tərcümə saxlamır | Bəli | +| `check-new-key-coverage` (sətirdaxili) | **Yeni** ingiliscə açar hər bir lokalizasiya üçün tərcümə edilib — `__MISSING__:` markeri rədd edilir | Bəli | +| `check-translation-ratio` | Hər lokalizasiya üçün real tərcümə nisbəti (icazə siyahısından kənarda ingiliscə ilə eyni / yer tutucu / çatışmayan yarpaqlar) `config/quality/i18n-translation-baseline.json` + güzəşt həddini aşmamalıdır | **Tövsiyə xarakterli** | -`fetch-depth: 0` tələb olunur — dəyər yayınması keçidi `en.json` faylını birləşdirmə bazası ilə müqayisə edir. +`fetch-depth: 0` tələb edir — dəyər fərqi keçidi `en.json` faylını birləşdirmə bazası ilə müqayisə edir. #### `check-ui-value-drift` — köhnəlmiş tərcümə keçidi Digər keçidlərin struktur baxımından görə bilmədiyi yeganə i18n reqressiyasını aşkarlayır: ingiliscə dəyər -yenidən yazılır, lakin _əvvəlki_ ingiliscə mətndən yaradılmış tərcümələr saxlanılır və nəticədə -ingiliscə bilməyən istifadəçilər inamlı ifadə edilmiş, lakin artıq yanlış olan mətni oxumağa davam edirlər. +yenidən yazılır və _əvvəlki_ ingiliscə mətndən əldə edilmiş tərcümələr yerində qalır, buna görə də +ingilisdilli olmayan istifadəçilər əminliklə ifadə edilmiş, lakin artıq yanlış olan mətni oxumağa davam edirlər. -Bu, həqiqətən də buraxılışa düşüb. Antigravity giriş köməkçisi əlavə ediləndə (#5203) -`oauthModal.googleOAuthWarning` yenidən yazılmışdı; **43 lokaldan 39-u** operatorlara "tam URL-i -kopyalayıb aşağıya yapışdırmağı" bildirən mətni saxlamışdı — həmin provayder üçün tamamlanması +Bu, həqiqətən buraxılışa daxil olub. Antigravity giriş köməkçisi əlavə ediləndə (#5203) +`oauthModal.googleOAuthWarning` yenidən yazılmışdı; **43 lokalizasiyadan 39-u** operatorlara "tam +URL-i kopyalayıb aşağıya yapışdırmağı" bildirən mətni saxlamışdı — həmin provayder üçün tamamlanması mümkün olmayan bir axın. Bu, #8463-ə qədər nəzərdən qaçdı, çünki: -- `sync-ui-keys` yalnız **mövcud olmayan** açarları əlavə edir, **köhnəlmiş** açarları isə heç vaxt yeniləmir; -- `check-ui-keys-coverage` açarın _mövcudluğunu_ hesablayır, buna görə köhnəlmiş tərcümə əhatə olunmuş sayılır; +- `sync-ui-keys` yalnız **mövcud olmayan** açarları geriyə doldurur, **köhnəlmiş** olanları isə heç vaxt yeniləmir; +- `check-ui-keys-coverage` açarın _mövcudluğunu_ sayır, buna görə köhnəlmiş tərcümə əhatə olunmuş kimi hesablanır; - `check-translation-drift` `docs/i18n//**.md` sənədləşdirmə güzgülərini izləyir — - o, heç vaxt `src/i18n/messages/*.json` fayllarını oxumur. 2026-09 yenidən sinxronlaşdırılmasından etibarən - `docs-sync-strict` işində bloklayıcıdır: əsas sənədi redaktə edin → `npm run i18n:run -- --files=` - (bölmə səviyyəsində, az xərc tələb edir). - -**Fərqləri nəzərə alır, baza xəttinə əsaslanmır.** O, birləşdirmə bazasındakı `en.json` faylını -işçi ağacı ilə müqayisə edir; ingiliscə dəyəri dəyişmiş hər bir açar üçün hələ də -toxunulmamış tərcüməni saxlayan istənilən lokal köhnəlmiş sayılır. Bu, qəsdən **əvvəldən mövcud olan borcu dondurur** — -fərq uzun müddətdir mövcud olan tərcümənin hansı köhnə ingiliscə mətndən gəldiyini göstərə bilməz, buna görə də yoxlama -yalnız cari dəyişikliyin toxunduğu hissələri qiymətləndirir. Alternativ yanaşma (hər açar üzrə heş baza xətti) + o, heç vaxt `src/i18n/messages/*.json` fayllarını oxumur. 2026-09 yenidən sinxronizasiyasından bəri `docs-sync-strict` + işində bloklayıcıdır: əsas sənədi redaktə edin → `npm run i18n:run -- --files=` (bölmə səviyyəsində, az resurs tələb edir). + +**Fərqləri nəzərə alır, baza xəttinə əsaslanmır.** Birləşdirmə bazasındakı `en.json` faylını +işçi ağacı ilə müqayisə edir; ingiliscə dəyəri dəyişmiş hər açar üçün hələ də +toxunulmamış tərcüməni saxlayan istənilən lokal köhnəlmiş sayılır. Bu, qəsdən **əvvəldən mövcud olan borcu dondurur** — fərq +uzun müddətdir mövcud olan tərcümənin hansı köhnə ingiliscə mətndən gəldiyini göstərə bilməz, buna görə yoxlama +yalnız cari dəyişikliyin toxunduğu hissələri qiymətləndirir. Alternativ (hər açar üçün heş baza xətti) təxminən 600 KB həcmində yaradılan fayl tələb edərdi; bu, mövcud ən böyük baza xəttindən 3 dəfə böyükdür və hər i18n PR-da -dəyişiklik yaradardı. +dəyişiklik səs-küyü yaradardı. Tələbi ödəməyin iki yolu var: -1. təsirlənmiş tərcümələri yeniləyin və ya +1. təsirə məruz qalan tərcümələri yeniləyin və ya 2. onları `__MISSING__:` olaraq təyin edin — bundan sonra icra mühiti düzəldilmiş ingiliscə mətni təqdim edir (`src/i18n/request.ts::deepMergeFallback`, #7258) və açar tərcümə növbəsinə əlavə olunur. -Sətrin **mənası** dəyişibsə, **açarın adını dəyişməyə** üstünlük verin: yeni açar +Əgər sətrin **mənası** dəyişibsə, **açarın adını dəyişməyə** üstünlük verin: yeni açar köhnəlmiş tərcüməni miras ala bilməz. #8463-də istifadə olunan nümunə budur. ```bash -npm run i18n:check-value-drift # ciddi rejim (CI-da işlədilən) +npm run i18n:check-value-drift # sərt (CI-da işlədilən) npm run i18n:check-value-drift:warn # yalnız hesabat BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Baza kataloqu oxuna bilmədikdə (baza istinadı olmayan dayaz -klon) `SKIP reason=base-unresolved` ilə 0 çıxış kodu qaytarır və `check-openapi-breaking` davranışını təkrarlayır. +Baza kataloqunu oxumaq mümkün olmadıqda (baza ref-i olmayan dayaz +klon) `SKIP reason=base-unresolved` ilə 0 çıxış kodu qaytarır; bu, `check-openapi-breaking` davranışını təkrarlayır. ### Tapşırıq: `i18n` Tam i18n doğrulama matrisi (hər lokal üçün bir tapşırıq). Bütün tapşırıq tövsiyə xarakterlidir. -| Skript | Doğruladığı sahə | Bloklama | -| ------------------------------- | ------------------------------ | ------------------------------------------------------------------- | -| `validate_translation.py quick` | Hər lokal üzrə tərcümə tamlığı | **Tövsiyə xarakterli** (bütün tapşırıqda `continue-on-error: true`) | +| Skript | Doğruladığı | Bloklama | +| ------------------------------- | --------------------------------- | ------------------------------------------------------------------- | +| `validate_translation.py quick` | Hər lokal üzrə tərcümənin tamlığı | **Tövsiyə xarakterli** (bütün tapşırıqda `continue-on-error: true`) | ### Tapşırıq: `pr-test-policy` Yalnız pull request-lərdə işləyir. -| Skript | Doğruladığı sahə | Bloklama | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` və ya `bin/` daxilində istehsal kodunu dəyişən PR-lar testlər əlavə etməli və ya yeniləməlidir (Sərt Qayda #8) | Bəli | -| `check:test-masking` | Dəyişdirilmiş test faylları xalis təsdiq sayını azaltmır və ya `assert.ok(true)` tavtologiyaları əlavə etmir | Bəli | -| `check:pr-evidence` | PR mətni dəyişiklik üçün test/VPS sübutlarına istinad edir (PR mətnində axtarış apararaq Sərt Qayda #18-i avtomatlaşdırır — kövrəkdir, Backlog-a baxın) | Bəli | +| Skript | Doğruladığı | Bloklama | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` və ya `bin/` daxilində istehsal kodunu dəyişən PR-lar testləri daxil etməli və ya yeniləməlidir (Sərt Qayda #8) | Bəli | +| `check:test-masking` | Dəyişdirilmiş test faylları xalis təsdiq sayını azaltmır və `assert.ok(true)` tavtologiyaları əlavə etmir | Bəli | +| `check:pr-evidence` | PR mətni dəyişiklik üçün test/VPS sübutlarına istinad edir (PR mətnini grep etməklə Sərt Qayda #18-i avtomatlaşdırır — kövrəkdir, Backlog-a baxın) | Bəli | ### Tapşırıq: `test-vitest` -`build`-dən sonra işləyir. Uğursuzluq baş verdikdə birləşdirməni bloklayır. +`build` sonrasında işləyir. Uğursuz olduqda birləşdirməni bloklayır. -| Test dəsti | Doğruladığı sahə | Bloklama | -| ---------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP serveri (110 alət), autoCombo, keş — vitest icraçısı | Bəli | -| `test:vitest:ui` | UI komponent testləri — vitest icraçısı | **Bloklayır** — əvvəlcədən mövcud olan uğursuzluqlar `vitest.config.ts` daxilində açıq şəkildə istisna edilib; yeni uğursuzluqlar tapşırığı uğursuz edir | +| Test dəsti | Doğruladığı | Bloklama | +| ---------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP serveri (110 alət), autoCombo, keş — vitest icraedicisi | Bəli | +| `test:vitest:ui` | UI komponent testləri — vitest icraedicisi | **Bloklayır** — əvvəldən mövcud olan uğursuzluqlar `vitest.config.ts` daxilində açıq şəkildə istisna edilib; yeni uğursuzluqlar tapşırığın uğursuz olmasına səbəb olur | -### Gecəlik iş axınları (planlaşdırılmış, tövsiyə xarakterli) +### Gecə iş axınları (planlaşdırılmış, tövsiyə xarakterli) -Bunlar cron cədvəli üzrə (və `workflow_dispatch` ilə) işləyir, PR-larda isə heç vaxt işə düşmür. Hamısı tövsiyə xarakterlidir. +Bunlar cron cədvəli üzrə (və `workflow_dispatch` ilə) işləyir, PR-larda isə heç vaxt işləmir. Hamısı tövsiyə xarakterlidir. -| İş axını | Doğruladığı sahə | Bloklama | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `nightly-property` | təsadüfi toxum və yüksək icra sayı ilə fast-check xüsusiyyət testləri | **Tövsiyə xarakterli** | -| `nightly-resilience` | heap artımı yoxlaması, xaos nasazlıq inyeksiyası, k6 yük/uzunmüddətli yük testi | **Tövsiyə xarakterli** | -| `nightly-llm-security` | promptfoo inyeksiya qoruması (bloklama rejimi) + garak yoxlamaları (provayder sirri olmadıqda ötürülür) | **Tövsiyə xarakterli** | -| `nightly-schemathesis` | `docs/openapi.yaml` istifadə edərək canlı OmniRoute-a qarşı OpenAPI müqaviləsinin fuzzinqi (schemathesis) — spesifikasiya pozuntularını / idarə olunmayan 500 xətalarını üzə çıxarır (Mərhələ 8 B.4) | **Tövsiyə xarakterli** | -| `nightly-mutation` | sürətli modul testləri xətti üzrə Stryker mutasiya testi balı — sağ qalan mutantlar zəif təsdiqləri üzə çıxarır | **Tövsiyə xarakterli** | -| `nightly-compat` | dəstəklənən `engines.node` diapazonları üzrə Node mühərriki uyğunluq matrisi | **Tövsiyə xarakterli** | +| İş axını | Doğruladığı | Bloklama | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `nightly-property` | təsadüfi seed + yüksək icra sayı ilə fast-check xüsusiyyət testləri | **Tövsiyə xarakterli** | +| `nightly-resilience` | heap artımı yoxlaması, xaos nasazlıq inyeksiyası, k6 yük/davamlı yük testi | **Tövsiyə xarakterli** | +| `nightly-llm-security` | promptfoo inyeksiya qoruyucusu (bloklama rejimi) + garak yoxlamaları (provayder sirri olmadıqda ötürülür) | **Tövsiyə xarakterli** | +| `nightly-schemathesis` | `docs/openapi.yaml` istifadə etməklə canlı OmniRoute-a qarşı OpenAPI müqaviləsinin fuzzinqi (schemathesis) — spesifikasiya pozuntularını / idarə olunmamış 500 xətalarını üzə çıxarır (Mərhələ 8 B.4) | **Tövsiyə xarakterli** | +| `nightly-mutation` | sürətli vahid test xətti üzrə Stryker mutasiya testi balı — sağ qalan mutantlar zəif təsdiqləri üzə çıxarır | **Tövsiyə xarakterli** | +| `nightly-compat` | dəstəklənən `engines.node` diapazonları üzrə Node mühərriki uyğunluq matrisi | **Tövsiyə xarakterli** | --- @@ -549,43 +546,43 @@ bağlantı xətasını jurnala yazır və sessiyaları heç vaxt pozmur. --- -## Rasionallaşdırma üçün görüləcək işlər siyahısı (ROI icmalı — Mərhələ 9 Dalğa 3) +## Rasionallaşdırma ehtiyat siyahısı (ROI icmalı — Mərhələ 9 Dalğa 3) -Bu inventar 2026-06-17 tarixində `ci.yml` ilə tutuşdurulub (əvvəlki versiyada +Bu inventar 2026-06-17 tarixində `ci.yml` ilə tutuşduruldu (əvvəlki versiyada `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence` buraxılmışdı). Tutuşdurulmuş dəstin ROI icmalı aşağıdakı rasionallaşdırma namizədlərini müəyyən etdi. **Birləşdirmələr mexaniki CI -dəyişiklikləridir; aktivləşdirmə/statusun aşağı salınması qərarları operator üçün saxlanılan siyasət qərarlarıdır.** Aşağıdakılardan heç biri +dəyişiklikləridir; çevirmə/ləğvetmə qərarları operatorun səlahiyyətində olan siyasət qərarlarıdır.** Aşağıdakılardan heç biri hələ tətbiq edilməyib. **Yuxarıda sənədləşdirilməyənlər də var** (məsləhət xarakterli, zəif siqnal): `docs-lint` işi -(markdownlint + Vale, bütün iş üçün `continue-on-error`) və ayrıca skaner iş axınları +(markdownlint + Vale, bütün iş üçün `continue-on-error`) və müstəqil skaner iş axınları `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` -`quality-baseline.json` daxilindədir, lakin `ci.yml` daxilində bloklayıcı ratchet-ə qoşulmayıb — metrik -hazırda sahibsizdir. - -### Birləşdirmə / dublikatların aradan qaldırılması (mexaniki, daha az riskli) - -Hər bir namizəd 2026-06-17 tarixində aktiv keçid vəziyyəti ilə müqayisədə yoxlanılıb (etibar et, amma yoxla); -bir neçə «aşkar» birləşdirmənin əslində texniki borcu gizlətdiyi üzə çıxdı və onlar **təmiz şəkildə birbaşa əvəzlənə bilmir**. - -- **`check:docs-sync` iki dəfə icra olunur** — `lint` işində ayrıca və yenidən `check:docs-all` (`docs-sync-strict`) daxilində, həmçinin husky pre-commit hook-unda. ✅ **TAMAMLANDI** — ayrıca `lint` çağırışı silindi. -- **CVE skanı** — ❌ **Təmiz birləşdirmə DEYİL.** `audit:deps` istənilən yüksək/kritik CVE olduqda sərt şəkildə uğursuz olur; `check:vuln-ratchet` (osv) isə yalnız baza səviyyəsi ilə müqayisədə _reqressiya_ olduqda uğursuz olur (hazırda 1 MODERATE). Semantikaları fərqlidir — `audit:deps` silinsəydi, mütləq yüksək/kritik keçid itirilərdi. Hər ikisini saxlayın. -- **Dövr aşkarlanması** — ❌ **Təmiz birləşdirmə DEYİL.** `check:circular-deps` (dpdm) **91 dövr** bildirir (məhz buna görə məsləhət xarakterlidir); əvvəlcə onlar həll edilmədən bloklayıcı səviyyəyə qaldırıla bilməz və yaşıl statuslu, seçilmiş `check:cycles` ilə müqayisədə daha geniş əhatə dairəsinə malikdir. `check:cycles` bloklayıcı olaraq saxlanılsın; 91 dpdm dövrünün həlli ayrıca görüləcək işdir. -- **Mürəkkəblik** — ✅ **TAMAMLANDI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): bir ESLint keçidi, cyclomatic+max-lines və cognitive baza səviyyələrinin müstəqil qalması üçün ruleId üzrə hesablanır; ayrıca `check:complexity` / `check:cognitive-complexity` lokal `--update` üçün saxlanılır. -- **`/api` anti-hallüsinasiya** — ✅ **TAMAMLANDI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` üçün bir FS inventarı, openapi-routes + docs-symbols hələ də müstəqil hesabat verir; ayrı-ayrı yoxlamalar lokal icralar üçün saxlanılır. -- **`check:node-runtime` 11 işdə icra olunur** — ⚠️ **aşağı ROI.** Hər biri ayrıca runner-dır və yoxlama <1s çəkir; ucuz, hər iş üzrə qoruyucunun itirilməsi müqabilində ümumi qənaət təxminən 10s-dir. Yaradacağı qarışıqlığa dəyməz. -- **CI lint-də `typecheck:noimplicit:core`** — ✅ **lint işindən silindi** (məsləhət xarakterli `continue-on-error` idi); bloklayıcı tip səthi `typecheck:core` + `check:type-coverage` kombinasiyasıdır. Lokal skript saxlanılıb. - -### Aktivləşdirmə / qərar vermə (operator siyasəti) - -- `check:openapi-security-tiers` (məsləhət xarakterli) — ❌ **Təmiz şəkildə aktivləşdirilə BİLMƏZ.** O, 0 çıxış kodu ilə bitir, lakin `LOCAL_ONLY_API_PREFIXES` altındakı bir neçə `traffic-inspector` marşrutunda `x-loopback-only: true` annotasiyasının olmadığını bildirir. Onu məcburi etmək üçün əvvəlcə həmin annotasiyaları `openapi.yaml` faylına əlavə etmək lazımdır. -- `typecheck:noimplicit:core` (məsləhət xarakterli) — əsasən bloklayıcı `check:type-coverage` ratchet-i ilə əhatə olunur. Ratchet-ə çevirin və ya təkrarlanan ikinci `tsc` keçidini silin. -- `test:vitest:ui` (indi **bloklayıcıdır**) — əvvəlcədən mövcud olan xətalar `vitest.config.ts` daxilində `// #8618` izləmə şərhləri ilə açıq şəkildə istisna edilib; yeni xətalar işi uğursuz edir. -- `check:secrets` (gitleaks, 3 sənədləşdirilmiş yanlış müsbət nəticədə dondurulmuş bloklayıcı ratchet) — 0-a çatmaq üçün bu 3 nəticəni icazə siyahısına əlavə edin və ya məsləhət xarakterli səviyyəyə endirin. GitHub-un daxili secret-scanning funksiyası + `check:public-creds` ilə üst-üstə düşür. -- `check:pr-evidence` (bloklayıcıdır, PR mətnində grep axtarışı aparır) — yanlış müsbət nəticə riski yüksəkdir; silinərsə, Hard Rule #18 tələbinin tətbiqini zəiflədir, buna görə də bu, həqiqi siyasət qərarıdır. -- `semgrep` (ayrıca, məsləhət xarakterli) — OWASP ailələri üzrə CodeQL ilə üst-üstə düşür; onun baza səviyyəsini ratchet-ə qoşun və ya silin. +`quality-baseline.json` daxilindədir, lakin `ci.yml` daxilində bloklayıcı bir xırxır mexanizminə qoşulmayıb — metrik +hazırda təcrid olunub. + +### Birləşdirmə / təkrarların aradan qaldırılması (mexaniki, daha az riskli) + +Hər bir namizəd 2026-06-17 tarixində mövcud qapı vəziyyətinə əsasən yoxlanılıb (etibar et, amma yoxla); +bir neçə «aşkar» birləşdirmənin borcu gizlətdiyi üzə çıxdı və onlar **birbaşa əvəzləmə üçün uyğun deyil**. + +- **`check:docs-sync` iki dəfə işə düşür** — `lint` işində ayrıca və yenidən `check:docs-all` (`docs-sync-strict`) daxilində, həmçinin husky pre-commit qarmağında. ✅ **TAMAMLANDI** — ayrıca `lint` çağırışı silindi. +- **CVE skanlaması** — ❌ **Təmiz birləşdirmə DEYİL.** `audit:deps` istənilən yüksək/kritik CVE olduqda sərt şəkildə uğursuz olur; `check:vuln-ratchet` (osv) isə yalnız baza xətti ilə müqayisədə _geriləmə_ olduqda uğursuz olur (hazırda 1 MODERATE). Semantikaları fərqlidir — `audit:deps` ləğv edilsəydi, mütləq yüksək/kritik qapı itirilərdi. Hər ikisini saxlayın. +- **Dövr aşkarlanması** — ✅ **TAMAMLANDI** (#15159 G-01/G-02). Buradakı köhnə mətn `check:cycles` yoxlamasını «yaşıl, seçilmiş» qapı adlandırır və `check:circular-deps` (dpdm) 91 dövr bildirdiyi üçün onu bloklayıcı saxlamağı əsaslandırırdı. Həmin yaşıl status **yanlış yaşıl** idi: `check:cycles` 5 alt kataloqu (450 fayl) skan edir, yalnız statik `import|export … from` nümunələrini uyğunlaşdırır və hər bir `@/` və `@omniroute/open-sse/` spesifikatorunu kənarlaşdırırdı; buna görə repozitoriyada üstünlük təşkil edən dinamik import + alias dövrlərini görə bilmirdi. Düzəldildi: qapı indi `src` + `open-sse` daxilində gəzir (5023 fayl), spesifikatorları TypeScript AST-dən toplayır (beləliklə, `import("…")` nəzərə alınır, tip mövqeyindəki `typeof import("…")` isə alınmır) və tsconfig `paths` dəyərlərini həll edir. O, 0 deyil, **14** dövr tapır. Əvvəldən mövcud olan 14 dövr qapı PR-ında düzəldilə bilmədiyi üçün `check:cycles` indi **xırxır mexanizmidir** (`--ratchet`, `quality-baseline.json` daxilində yuxarı hədd `metrics.cycles.value = 14`, `direction: down`) — istənilən _geriləməni_ bloklayır və say yalnız azala bilər. CI `npm run check:cycles:ratchet` əmrini işə salır. Azaltma işi **A-01** ilə birlikdə aparılır. `check:circular-deps` (dpdm) daha geniş ikinci rəy kimi məsləhət xarakterli qalır. +- **Mürəkkəblik** — ✅ **TAMAMLANDI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): bir ESLint keçidi, ruleId üzrə sayır ki, siklomatik+max-lines və koqnitiv baza xətləri müstəqil qalsın; ayrı-ayrı `check:complexity` / `check:cognitive-complexity` yoxlamaları lokal `--update` üçün saxlanılır. +- **`/api` anti-hallüsinasiya yoxlaması** — ✅ **TAMAMLANDI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` üçün bir FS inventarı, openapi-routes + docs-symbols yenə də müstəqil hesabat verir; fərdi yoxlamalar lokal icralar üçün saxlanılır. +- **`check:node-runtime` 11 işdə icra olunur** — ⚠️ **aşağı ROI.** Hər biri ayrıca icra mühitidir və yoxlama <1s çəkir; ümumi qənaət ~10s təşkil edir, əvəzində isə hər iş üçün ucuz qoruyucu itirilir. Yaradacağı qarışıqlığa dəyməz. +- **CI lint-də `typecheck:noimplicit:core`** — ✅ **lint işindən silindi** (məsləhət xarakterli `continue-on-error` idi); bloklayıcı tip səthi `typecheck:core` + `check:type-coverage` ilə təmin edilir. Lokal skript saxlanıldı. + +### Çevirmə / qərarvermə (operator siyasəti) + +- `check:openapi-security-tiers` (məsləhət xarakterli) — ❌ **Təmiz şəkildə çevrilə BİLMƏZ.** 0 kodu ilə çıxır, lakin `LOCAL_ONLY_API_PREFIXES` altındakı bir neçə `traffic-inspector` marşrutunda `x-loopback-only: true` annotasiyasının olmadığını bildirir. Onu məcburi etmək üçün əvvəlcə həmin annotasiyalar `openapi.yaml` faylına əlavə edilməlidir. +- `typecheck:noimplicit:core` (məsləhət xarakterli) — bloklayıcı `check:type-coverage` xırxır mexanizmi tərəfindən əsasən əhatə olunur. Xırxır mexanizminə çevirin və ya lazımsız ikinci `tsc` keçidini ləğv edin. +- `test:vitest:ui` (indi **bloklayıcıdır**) — əvvəldən mövcud uğursuzluqlar `vitest.config.ts` daxilində `// #8618` izləmə şərhləri ilə açıq şəkildə istisna edilib; yeni uğursuzluqlar işin uğursuz olmasına səbəb olur. +- `check:secrets` (gitleaks, 3 sənədləşdirilmiş yanlış müsbət nəticədə dondurulmuş bloklayıcı xırxır mexanizmi) — 0-a çatmaq üçün bu 3 nəticəni icazə siyahısına əlavə edin və ya məsləhət xarakterli statusa endirin. GitHub-un daxili məxfi məlumat skanlaması + `check:public-creds` ilə üst-üstə düşür. +- `check:pr-evidence` (bloklayıcıdır, PR mətnini grep ilə axtarır) — yanlış müsbət nəticə riski yüksəkdir; ləğv edilərsə, Sərt Qayda #18-in icrasını zəiflədir, buna görə bu, həqiqi siyasət qərarıdır. +- `semgrep` (məsləhət xarakterli müstəqil yoxlama) — OWASP ailələri üzrə CodeQL ilə üst-üstə düşür; baza xəttini xırxır mexanizminə qoşun və ya ləğv edin. --- diff --git a/docs/i18n/az/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/az/docs/compression/COMPRESSION_GUIDE.md index 8589a56033ff..0096fed16fd9 100644 --- a/docs/i18n/az/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/az/docs/compression/COMPRESSION_GUIDE.md @@ -465,8 +465,7 @@ tərcüməsi olduqda, İngilis dili əvəzinə lokallaşdırılmış təlimat da `messages` olan bir gövdədə, məzmun bypassı (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts` faylında) son üç mesajı yoxlayır və təhlükəsizlik, geri dönməz hərəkət, aydınlaşdırma və ya sifarişə həssas açar sözlərinə uyğun gəldikdə -bütün növbə üçün stilləri atlayır. Bypass, idarə panelinin **Avtomatik Aydınlıq Bypassı** -keçidi (`cavemanOutputMode.autoClarity`) nəyə təyin olunmasından asılı olmayaraq işləyir. +bütün növbə üçün stilləri atlayır. Bypass, idarə panelinin **Avtomatik Aydınlıq Bypassı** keçidi (`cavemanOutputMode.autoClarity`) yandırıldıqda işləyir — bu standart haldır; keçid söndürüldükdə seçilmiş stillər həmin növbələrdə də tətbiq olunur. Bypass növbəni keçməyə icazə verdikdə, `placeSystemInstruction()` (eyni fayl), heç vaxt yeni `messages[0]` yaratmır, bloku tapdığı ilk yerə qoyur: diff --git a/docs/i18n/az/docs/reference/ENVIRONMENT.md b/docs/i18n/az/docs/reference/ENVIRONMENT.md index ce3b6bf6d4f9..c86797a5e3d1 100644 --- a/docs/i18n/az/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/az/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Tələb Olunan Məxfi Dəyərlər -Bunlar ilk işə salınmadan əvvəl **mütləq** təyin edilməlidir. Onlar olmadan tətbiq ya işə düşməkdən imtina edəcək, ya da təhlükəsiz olmayan standart dəyərlərlə işləyəcək. - -| Dəyişən | Tələb olunur | Standart | Mənbə faylı | Təsvir | -| ---------------------------- | -------------------------- | ------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Bəli** | _(yoxdur)_ | `src/lib/auth` | Bütün idarəetmə paneli sessiya kukilərini (JWT) imzalayır/yoxlayır. `openssl rand -base64 48` ilə yaradın. | -| `API_KEY_SECRET` | **Bəli** | _(yoxdur)_ | `src/lib/db/apiKeys.ts` | SQLite-də saxlanılan API açarı dəyərləri üçün AES şifrələmə açarıdır. `openssl rand -hex 32` ilə yaradın. | -| `INITIAL_PASSWORD` | **Bəli** | `CHANGEME` | İlkin quraşdırma skripti | İlkin administrator idarəetmə paneli parolunu təyin edir (`.env.example` faylındakı standart dəyərlə eynidir — dəyişdirilməsini məcbur etmək üçün qəsdən təhlükəsiz olmayan formada saxlanılıb). **İlk istifadədən əvvəl dəyişdirin.** Daxil olduqdan sonra Dashboard → Settings → Security vasitəsilə dəyişdirin. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Bəli** (istehsal mühiti) | _(təyin edilməyib)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Daxili Codex Responses WebSocket körpüsü üçün ortaq məxfi dəyərdir. Electron/brauzer WS retranslyatoru ilə OmniRoute arasındakı körpü sorğularını autentifikasiya edir. ⚠️ **İstehsal mühitində TƏLƏB OLUNUR — təyin edilmədikdə bütün WS körpü sorğuları rədd edilir.** `openssl rand -base64 32` ilə yaradın. | -| `OMNIROUTE_SW_BUILD_ID` | Xeyr | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA qabığı üçün xidmət işçisi keşinin yenilənməsini təmin edən açıq identifikator (#11779); müəyyənləşdirmə zəncirində birincidir. | -| `SOURCE_VERSION` | Xeyr | _(təyin edilməyib)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zəncirdə ikincidir — PaaS qurucuları (məsələn, Heroku tipli) tərəfindən yerləşdirilmiş commit kimi təyin edilir. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Xeyr | _(törədilmiş)_ | `src/shared/components/PwaRegister.tsx` | Müştərinin `/sw.js?v=…` qeydiyyatı üçün istifadə etdiyi qurma zamanı təyin olunan ictimai dəyərdir; əvvəlcə yuxarıdakı iki dəyərdən, sonra isə git SHA-dan törədilir. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Xeyr (avtomatik) | _(hər işə salınmada avtomatik)_ | `src/server/authz/policies/management.ts` | Etibarlı qarşı tərəf IP möhürünün OmniRoute-un öz HTTP serverindən (`scripts/dev/peer-stamp.mjs`) gəldiyini sübut edən hər prosesə aid məxfi dəyərdir. Authz ara proqramı sorğunun lokallığına (LOCAL_ONLY marşrutlarının geridöngə/LAN məhdudlaşdırılması) yalnız möhür bu tokeni daşıdıqda etibar edir. Hər işə salınmada avtomatik yaradılır — təyin edilməmiş saxlayın; yalnız möhürü paylaşmalı olan çoxprosesli quraşdırmalarda sabitləyin. | -| `OMNIROUTE_TRUSTED_PROXIES` | Xeyr | _(təyin edilməyib)_ | `scripts/dev/peer-stamp.mjs` | IP icazə/qadağa siyahısının `X-Forwarded-For` / `X-Real-IP` məlumatlarına etibar edə biləcəyi əks proksilərin vergüllə ayrılmış IP-ləri və ya CIDR diapazonlarıdır. Bunlar həmişə etibarlı sayılan geridöngə, şəxsi şəbəkə ünvanları və Cloudflare kənar serverlərinə əlavə olunur. Yalnız başqa ictimai ünvanda olan proksi üçün lazımdır; əks halda filtr həmin proksinin öz ünvanını qiymətləndirir. | +**Bəli** ilə işarələnmiş, təyin edilməmiş məxfi dəyərlər ilk işə salınmada avtomatik yaradılır (`JWT_SECRET`, `API_KEY_SECRET`) və data qovluğundakı `server.env` faylında saxlanılır — həmin qovluq qeyri-kalıcı (ephemeral) olduqda onları açıq şəkildə təyin edin. İstisna `OMNIROUTE_WS_BRIDGE_SECRET`-dir: o, `server.env` faylında saxlanılmır — server giriş nöqtələri (entrypoints) yalnız prosesdaxili körpünün istifadə edə biləcəyi hər işə salınmaya məxsus müvəqqəti dəyər yaradır, ona görə də xarici körpü kliyentləri bunu açıq şəkildə təyin etməlidir. + +| Dəyişən | Tələb olunur | Standart | Mənbə faylı | Təsvir | +| ---------------------------- | -------------------------- | ------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Bəli** | _(yoxdur)_ | `src/lib/auth` | Bütün idarəetmə paneli sessiya kukilərini (JWT) imzalayır/yoxlayır. `openssl rand -base64 48` ilə yaradın. | +| `API_KEY_SECRET` | **Bəli** | _(yoxdur)_ | `src/lib/db/apiKeys.ts` | SQLite-də saxlanılan API açarı dəyərləri üçün AES şifrələmə açarıdır. `openssl rand -hex 32` ilə yaradın. | +| `INITIAL_PASSWORD` | Xeyr | _(təyin edilməyib)_ | `src/lib/auth/managementPassword.ts` | Üzərinə parol hələ saxlanılmadığı halda idarəetmə paneli parolunu işə salınma zamanı təyin edir; parol bir dəfə saxlanıldıqdan sonra nəzərə alınmır. npm və mənbədən quraşdırmalar `.env.example` faylıını `.env` faylına köçürür, ona görə də hamıya məlum `CHANGEME` yer tutucusu ilə başlayırlar: ilk işə salınmadan əvvəl onu dəyişdirin, ya da təyin edilməmiş dəyərin (Docker təsvirinin defoltu) etdiyi kimi ilk girişdə parolun ilkin quraşdırma sehrbazında (onboarding wizard) yaradılması üçün həmin `.env` faylıında `INITIAL_PASSWORD=` (boş) təyin edin. Saxlanılmış parolu Dashboard → Settings → Security bölməsində dəyişdirin, ya da `DATA_DIR` serverin data qovluğuna təyin edilməklə `omniroute reset-password` əmrini icra edin (mənbə checkout-unda `node bin/reset-password.mjs`). | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Bəli** (istehsal mühiti) | _(təyin edilməyib)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Daxili Codex Responses WebSocket körpüsü üçün ortaq məxfi dəyərdir. Electron/brauzer WS retranslyatoru ilə OmniRoute arasındakı körpü sorğularını autentifikasiya edir. ⚠️ **İstehsal mühitində TƏLƏB OLUNUR — təyin edilmədikdə bütün WS körpü sorğuları rədd edilir.** `openssl rand -base64 32` ilə yaradın. | +| `OMNIROUTE_SW_BUILD_ID` | Xeyr | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA qabığı üçün xidmət işçisi keşinin yenilənməsini təmin edən açıq identifikator (#11779); müəyyənləşdirmə zəncirində birincidir. | +| `SOURCE_VERSION` | Xeyr | _(təyin edilməyib)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zəncirdə ikincidir — PaaS qurucuları (məsələn, Heroku tipli) tərəfindən yerləşdirilmiş commit kimi təyin edilir. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Xeyr | _(törədilmiş)_ | `src/shared/components/PwaRegister.tsx` | Müştərinin `/sw.js?v=…` qeydiyyatı üçün istifadə etdiyi qurma zamanı təyin olunan ictimai dəyərdir; əvvəlcə yuxarıdakı iki dəyərdən, sonra isə git SHA-dan törədilir. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Xeyr (avtomatik) | _(hər işə salınmada avtomatik)_ | `src/server/authz/policies/management.ts` | Etibarlı qarşı tərəf IP möhürünün OmniRoute-un öz HTTP serverindən (`scripts/dev/peer-stamp.mjs`) gəldiyini sübut edən hər prosesə aid məxfi dəyərdir. Authz ara proqramı sorğunun lokallığına (LOCAL_ONLY marşrutlarının geridöngə/LAN məhdudlaşdırılması) yalnız möhür bu tokeni daşıdıqda etibar edir. Hər işə salınmada avtomatik yaradılır — təyin edilməmiş saxlayın; yalnız möhürü paylaşmalı olan çoxprosesli quraşdırmalarda sabitləyin. | +| `OMNIROUTE_TRUSTED_PROXIES` | Xeyr | _(təyin edilməyib)_ | `scripts/dev/peer-stamp.mjs` | IP icazə/qadağa siyahısının `X-Forwarded-For` / `X-Real-IP` məlumatlarına etibar edə biləcəyi əks proksilərin vergüllə ayrılmış IP-ləri və ya CIDR diapazonlarıdır. Bunlar həmişə etibarlı sayılan geridöngə, şəxsi şəbəkə ünvanları və Cloudflare kənar serverlərinə əlavə olunur. Yalnız başqa ictimai ünvanda olan proksi üçün lazımdır; əks halda filtr həmin proksinin öz ünvanını qiymətləndirir. | ### Yaratma Əmrləri @@ -217,38 +217,38 @@ OmniRoute bütün davamlı saxlama əməliyyatları üçün **SQLite**-dan (`bet ## 4. Təhlükəsizlik və autentifikasiya -| Dəyişən | Standart dəyər | Mənbə faylı | Təsvir | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Maşın barmaq izinin yaradılması üçün aparat identifikatorları ilə birləşdirilən duz. İzolyasiyanı təmin etmək üçün hər yerləşdirmədə dəyişdirin. | -| `OMNIROUTE_CLI_SALT` | _(təyin edilməyib = hər quraşdırma üçün təsadüfi duz `/cli-token-salt.json` ünvanında saxlanılır)_ | `src/lib/machineToken.ts` | Lokal CLI autentifikasiya tokenini yaratmaq üçün HMAC duzu. Bu dəyərin təyin edilməsi maşındakı bütün CLI tokenlərini yeniləyir və həmişə saxlanılan duzdan üstün tutulur. `docs/security/CLI_TOKEN.md` sənədinə baxın. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sessiya kukilərində `Secure` bayrağını təyin edir. HTTPS arxasında işləyərkən **`true` olmalıdır**. | -| `REQUIRE_API_KEY` | `false` | API aralıq proqramı | `true` olduqda, bütün `/v1/*` proksi sorğuları etibarlı API açarı ehtiva etməlidir. **Bu bayraq `GET /v1/models` girişini məhdudlaşdırmır**; həmin sorğu əvəzinə idarəetmə panelinin giriş vəziyyətinə (`requireAuthForModels`) əsaslanır — buna görə də `/v1/models` sorğusundan alınan `401` cavabı inferensiyanın qorunduğu mənasına GƏLMİR. `docs/security/INFERENCE_AUTH_POSTURE.md` sənədinə baxın (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | İdarəetmə paneli interfeysində API açarlarının tam dəyərlərinin göstərilməsinə icazə verir. İdarəetmə panelinin funksiya bayraqlarından konfiqurasiya edilə bilər; ortaq instanslarda təhlükəsizlik riski yaradır. | -| `NO_LOG_API_KEY_IDS` | _(boş)_ | `src/lib/compliance/index.ts` | Sorğuların jurnallaşdırılmasından yan keçən, vergüllə ayrılmış API açarı ID-ləri (GDPR uyğunluğu). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(təyin edilməyib = limitsiz)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` sütunu null olan API açarlarına tətbiq edilən gündəlik ehtiyat sorğu büdcəsi. Təyin edilməyib və ya boşdur: dolayı limit yoxdur (#2289, #11017). `0` da eyni məna daşıyır (limitsiz). Müsbət tam N ədədi gündə N, həftədə 5N, ayda 20N sorğuya icazə verir. Səhv formatlı, boş olmayan dəyərlər köhnə gündə 1000, həftədə 5000, ayda 20000 limitlərinə qayıdır. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | İcazə verilən maksimum sorğu gövdəsi ölçüsü. Bu limiti aşan faydalı yükləri rədd edir. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Bu həddə çatan və ya onu aşan faktiki sorğu gövdələri JSON təhlilindən əvvəl atomik, proses-lokal ağırçəkili qəbul icarəsini əldə edir (BYTE yolu, o cümlədən `POST /v1/responses`). Struktur baxımından ağır sorğularda olduğu kimi eyni [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) sağlam ehtiyat resurs istisnası tətbiq olunur; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) probleminin təkrarlanmaması üçün hələ də `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ilə məhdudlaşdırılır. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Məhdud qəbul zamanı oxunan baytlara tətbiq edilən çat marşrutu üçün sərt hədd; buraya `Content-Length` dəyəri olmayan, etibarsız və ya həqiqəti əks etdirməyən sorğular da daxildir; həddi aşanlar `413` alır. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(təyin edilməyib — sorğu sayı həddi yoxdur)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** bu köhnə sorğu SAYI həddi indi yalnız açıq şəkildə təyin edildikdə tətbiq olunur. Təyin edilmədikdə (standart vəziyyət), ağır çəkili çat qəbulu əvəzinə `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ilə məhdudlaşdırılır — **bir prosesdə** (bir V8 yığını) prosesin real yaddaş həddinə əsasən avtomatik hesablanan BAYT büdcəsi. Üst-üstə düşən iki ~750k-token `/v1/responses` sorğusu ~12 GiB-lıq yığınları dayandırır (#7849) — bu, məhsul üçün sərt maksimum 2 həddi deyil, **yaddaş büdcəsi** xəbərdarlığıdır. Sağlam proses (yığın boşaltma nisbətindən aşağı olduqda) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` vasitəsilə daha çox paralel uzun `/v1/responses` sorğusunu qəbul EDƏ BİLƏR. Onlarla uzun SSE klienti (40–50) “maksimum 2” deyil, yığın + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 məsələsidir. “Hostdan istifadə etmək” üçün bunu kor-koranə artırmaq #7849 problemini yenidən yaradır. **N müstəqil `DATA_DIR`** ilə **yığınların** sayını artırın (#11024); bir SQLite faylında heç vaxt `replicas>1` istifadə etməyin. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(avtomatik hesablanır)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** avtomatik hesablanan qəbul bayt büdcəsi üçün əvəzləyici dəyər (daha məhdud V8/cgroup yaddaş həddinin 25%-i, 8 qat müvəqqəti artıma bölünür). Hesablanan və açıq şəkildə təyin edilən dəyərlər 8 MiB–2 GiB aralığı ilə məhdudlaşdırılır. Effektiv büdcədən böyük gövdə dərhal `413 body_exceeds_budget` xətası ilə rədd edilir; ayrı-ayrılıqda emal edilə bilən gövdələr arasındakı rəqabət üçün yenidən cəhd edilə bilən `503` qaytarılır. Eyni vaxtda 40–50 uzun SSE klienti sərt “maksimum 2” həddi deyil, bu büdcə + yığın məsələsidir. Tənzimləmədən əvvəl `/api/monitoring/health` ünvanında `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` dəyərlərini yoxlayın. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BAYT və STRUKTUR ağır çəkili qəbulu üçün yığın təzyiqinə əsaslanan yük azaltma nisbəti (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` həddini aşan paralel ağır çəkili sorğu yalnız yığın da bu nisbətə çatdıqda və ya onu aşdıqda yenidən cəhd edilə bilən `503` xətası ilə rədd edilir; sağlam yığında isə sağlam əlavə tutum vasitəsilə qəbul olunur. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standart `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Həm STRUKTUR, həm də BAYT (`admitChatRequest`, o cümlədən gövdəsi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` olanlar) üçün sağlam yığın sürətli yolunun (#10437) məhdud əlavə tutumu. Bu məhdudiyyət olmadan məşğul, lakin sağlam yığına malik hər sorğu heç bir yuxarı hədd olmadan qəbul nəzarətindən yan keçirdi. Sağlam yığın yolu vasitəsilə bu sayda paralel icarə aktiv olduqdan sonra növbəti məşğul sorğular real yığın təzyiqi zamanı istifadə edilən EYNİ məhdud gözləmə/yük azaltma yoluna keçir. `0` yan keçidi tamamilə söndürür. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdəsi bayt həddindən aşağı olsa belə, çat sorğusunu ağır çəkili kimi təsnif edən mesaj sayı. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdəsi bayt həddindən aşağı olsa belə, çat sorğusunu ağır çəkili kimi təsnif edən alət sayı. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Sorğunu ağır çəkili kimi təsnif edən konservativ sətir ölçüsünə əsaslanan token təxmini; bu, qəbul xərcinin təxmini göstəricisidir, provayderin ödəniş tokenləşdirilməsi deyil. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (söndürülüb) | `src/shared/middleware/chatBodyAdmission.ts` | Könüllü olaraq aktivləşdirilən çat tarixçəsi həddi. Standart olaraq söndürülüb: mesaj sayı sorğunun universal xüsusiyyəti deyil, yerləşdirmə siyasətidir və burada hədd qoyulması söhbətləri sıxılma konveyeri onları emal edilə bilən hala gətirməzdən əvvəl yekun `413` xətası ilə rədd edir. Yığın artımı `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` və yığın təzyiqinə əsaslanan yük azaltma ilə məhdudlaşdırılır. Sərt yuxarı həddə ehtiyacı olan yaddaşı məhdud yerləşdirmələrdə müsbət dəyər təyin edin; həddi aşanlar bu halda sıxlaşdırma tələbini bildirən strukturlaşdırılmış `413` xətası alır. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Tam şəkildə yaddaşda buferlənən axınsız yuxarı axın cavabı üçün sərt hədd. Bu hədd aşıldıqda yuxarı axın oxuyucusu ləğv edilir və yığın tükənənədək qeyri-məhdud sətir böyütmək əvəzinə sorğu dərhal xəta ilə dayandırılır. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Yuxarı axın cavab başlıqlarından ötürülən maksimum şəbəkə baytları. Büdcə aşıldıqda, geniş yayılmış əks-proksi başlıq limitləri daxilində qalmaq üçün daha aşağı prioritetli başlıqlar (məsələn, fərdi `x-codex-*`, `x-oai-request-id`) silinir. Cavab başlığının ölçüsünün artması hesabına daha çox yuxarı axın metadatasını ötürmək üçün daha yüksək dəyər təyin edin. | -| `CORS_ORIGIN` | _(təyin edilməyib)_ | `src/server/cors/origins.ts` | Köhnə, tək mənşəli CORS icazə siyahısı. Yeni yerləşdirmələr üçün `CORS_ALLOWED_ORIGINS` seçiminə üstünlük verin. CORS yalnız fərqli mənşəli brauzer API müştəriləri üçündür; autentifikasiya edilmiş idarə paneli yazma əməliyyatları bunun əvəzinə eyni mənşəli sorğulardan və sessiyaya bağlı CSRF qorumasından istifadə edir. | -| `CORS_ALLOWED_ORIGINS` | _(təyin edilməyib)_ | `src/server/cors/origins.ts` | Vergüllə ayrılmış CORS icazə siyahısı. `CORS_ALLOW_ALL=true` açıq şəkildə konfiqurasiya edilmədikcə heç bir universal şablon göndərilmir. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | İstənilən brauzer `Origin` dəyərini əks etdirmək üçün yalnız inkişaf mühitinə aid çıxış yolu. Paylaşılan və ya istehsal yerləşdirmələrində aktiv etməyin. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Şəxsi/geri döngə/yerli keçid IP diapazonlarını hədəfləyən provayder çağırışlarını bloklayın. Yalnız təcrid olunmuş sınaq mühitlərində deaktiv edin. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Şəxsi/lokal şəbəkələrə (localhost, 192.168.x.x, 10.x.x.x və s.) yönələn provayder URL-lərinə icazə verin. **Öz serverində yerləşdirilən provayderlər üçün TƏLƏB OLUNUR** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Dəyər `false` olduqda idarə paneli lokal URL-lərin doğrulanmasını rədd edir. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Lokal/şəxsi ünvanlardakı (127.0.0.1, localhost, LAN, şəxsi diapazonlar) provayderlərin əlavə edilməsinə/doğrulanmasına icazə verin — yalnız provayderin doğrulanması yolu ilə məhdudlaşır. **Defolt olaraq `true`** (lokal üstünlüklü); yalnız ictimai ünvanlara icazə verən sərt bloklamanı tətbiq etmək üçün `false` təyin edin. Bulud metadata son nöqtələri (169.254.169.254, metadata.google.internal) bundan asılı olmayaraq bloklanmış qalır. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` marşrutlarının (transkripsiyalar, nitq, tərcümələr) localhost xaricində yerləşdirilmiş OpenAI-uyğun provayder qovşağından istifadə etməsinə icazə verin. Defolt olaraq söndürülüb — audionun uzaq hosta yönləndirilməsi çıxış identikliyini dəyişir və operatorun açıq qərarı olmalıdır. Geri döngə/şəxsi qovşaqlara (localhost, 127.0.0.1, 172.16-31.x) həmişə icazə verilir və onlar bundan təsirlənmir. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` sorğusunun (və yaddaş mühərrikinin geri döngəli yenidən sıralama addımının) localhost xaricində yerləşdirilmiş OpenAI-uyğun provayder qovşağından — TEI, Infinity, vLLM və s. işlədən LAN cihazından və ya Tailscale bərabərtərəfindən — istifadə etməsinə icazə verin. Defolt olaraq söndürülüb — uzaq hosta yönləndirmə çıxış identikliyini dəyişir və operatorun açıq qərarı olmalıdır. Geri döngə qovşaqlarına (localhost, 127.0.0.1, 172.16-31.x) həmişə icazə verilir və onlar bundan təsirlənmir. Uzaq qovşaqlar həmçinin provayderin xaricə çıxan URL siyasətindən (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) keçməlidir; bulud metadata hostlarına heç vaxt yönləndirmə edilmir. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC aktiv olduqda parol ilə girişi deaktiv edin ki, istifadəçilər yalnız OIDC Vahid Girişi vasitəsilə autentifikasiya oluna bilsinlər. Sadə `OIDC_DISABLE_PASSWORD_LOGIN` aliası da qəbul edilir; eyni açara malik İdarə Paneli Funksiya Bayrağı üstünlüyə malikdir. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` üçün sadə alias (#10889). | +| Dəyişən | Standart dəyər | Mənbə faylı | Təsvir | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Maşın barmaq izinin yaradılması üçün aparat identifikatorları ilə birləşdirilən duz. İzolyasiyanı təmin etmək üçün hər yerləşdirmədə dəyişdirin. | +| `OMNIROUTE_CLI_SALT` | _(təyin edilməyib = hər quraşdırma üçün təsadüfi duz `/cli-token-salt.json` ünvanında saxlanılır)_ | `src/lib/machineToken.ts` | Lokal CLI autentifikasiya tokenini yaratmaq üçün HMAC duzu. Bu dəyərin təyin edilməsi maşındakı bütün CLI tokenlərini yeniləyir və həmişə saxlanılan duzdan üstün tutulur. `docs/security/CLI_TOKEN.md` sənədinə baxın. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sessiya kukilərində `Secure` bayrağını təyin edir. HTTPS arxasında işləyərkən **`true` olmalıdır**. | +| `REQUIRE_API_KEY` | `false` | API aralıq proqramı | `true` olduqda, bütün `/v1/*` proksi sorğuları etibarlı API açarı ehtiva etməlidir. **Bu bayraq `GET /v1/models` girişini məhdudlaşdırmır**; həmin sorğu əvəzinə idarəetmə panelinin giriş vəziyyətinə (`requireAuthForModels`) əsaslanır — buna görə də `/v1/models` sorğusundan alınan `401` cavabı inferensiyanın qorunduğu mənasına GƏLMİR. `docs/security/INFERENCE_AUTH_POSTURE.md` sənədinə baxın (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | İdarəetmə paneli interfeysində API açarlarının tam dəyərlərinin göstərilməsinə icazə verir. İdarəetmə panelinin funksiya bayraqlarından konfiqurasiya edilə bilər; ortaq instanslarda təhlükəsizlik riski yaradır. | +| `NO_LOG_API_KEY_IDS` | _(boş)_ | `src/lib/compliance/index.ts` | Sorğuların jurnallaşdırılmasından yan keçən, vergüllə ayrılmış API açarı ID-ləri (GDPR uyğunluğu). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(təyin edilməyib = limitsiz)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` sütunu null olan API açarlarına tətbiq edilən gündəlik ehtiyat sorğu büdcəsi. Təyin edilməyib və ya boşdur: dolayı limit yoxdur (#2289, #11017). `0` da eyni məna daşıyır (limitsiz). Müsbət tam N ədədi gündə N, həftədə 5N, ayda 20N sorğuya icazə verir. Səhv formatlı, boş olmayan dəyərlər köhnə gündə 1000, həftədə 5000, ayda 20000 limitlərinə qayıdır. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | İcazə verilən maksimum sorğu gövdəsi ölçüsü. Bu limiti aşan faydalı yükləri rədd edir. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Bu həddə çatan və ya onu aşan faktiki sorğu gövdələri JSON təhlilindən əvvəl atomik, proses-lokal ağırçəkili qəbul icarəsini əldə edir (BYTE yolu, o cümlədən `POST /v1/responses`). Struktur baxımından ağır sorğularda olduğu kimi eyni [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) sağlam ehtiyat resurs istisnası tətbiq olunur; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) probleminin təkrarlanmaması üçün hələ də `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ilə məhdudlaşdırılır. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Məhdud qəbul zamanı oxunan baytlara tətbiq edilən çat marşrutu üçün sərt hədd; buraya `Content-Length` dəyəri olmayan, etibarsız və ya həqiqəti əks etdirməyən sorğular da daxildir; həddi aşanlar `413` alır. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(təyin edilməyib — sorğu sayı həddi yoxdur)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** bu köhnə sorğu SAYI həddi indi yalnız açıq şəkildə təyin edildikdə tətbiq olunur. Təyin edilmədikdə (standart vəziyyət), ağır çəkili çat qəbulu əvəzinə `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ilə məhdudlaşdırılır — **bir prosesdə** (bir V8 yığını) prosesin real yaddaş həddinə əsasən avtomatik hesablanan BAYT büdcəsi. Üst-üstə düşən iki ~750k-token `/v1/responses` sorğusu ~12 GiB-lıq yığınları dayandırır (#7849) — bu, məhsul üçün sərt maksimum 2 həddi deyil, **yaddaş büdcəsi** xəbərdarlığıdır. Sağlam proses (yığın boşaltma nisbətindən aşağı olduqda) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` vasitəsilə daha çox paralel uzun `/v1/responses` sorğusunu qəbul EDƏ BİLƏR. Onlarla uzun SSE klienti (40–50) “maksimum 2” deyil, yığın + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 məsələsidir. “Hostdan istifadə etmək” üçün bunu kor-koranə artırmaq #7849 problemini yenidən yaradır. **N müstəqil `DATA_DIR`** ilə **yığınların** sayını artırın (#11024); bir SQLite faylında heç vaxt `replicas>1` istifadə etməyin. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(avtomatik hesablanır)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** avtomatik hesablanan qəbul bayt büdcəsi üçün əvəzləyici dəyər (daha məhdud V8/cgroup yaddaş həddinin 25%-i, 8 qat müvəqqəti artıma bölünür). Hesablanan və açıq şəkildə təyin edilən dəyərlər 8 MiB–2 GiB aralığı ilə məhdudlaşdırılır. Effektiv büdcədən böyük gövdə dərhal `413 body_exceeds_budget` xətası ilə rədd edilir; ayrı-ayrılıqda emal edilə bilən gövdələr arasındakı rəqabət üçün yenidən cəhd edilə bilən `503` qaytarılır. Eyni vaxtda 40–50 uzun SSE klienti sərt “maksimum 2” həddi deyil, bu büdcə + yığın məsələsidir. Tənzimləmədən əvvəl `/api/monitoring/health` ünvanında `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` dəyərlərini yoxlayın. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BAYT və STRUKTUR ağır çəkili qəbulu üçün yığın təzyiqinə əsaslanan yük azaltma nisbəti (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` həddini aşan paralel ağır çəkili sorğu yalnız yığın da bu nisbətə çatdıqda və ya onu aşdıqda yenidən cəhd edilə bilən `503` xətası ilə rədd edilir; sağlam yığında isə sağlam əlavə tutum vasitəsilə qəbul olunur. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standart `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Həm STRUKTUR, həm də BAYT (`admitChatRequest`, o cümlədən gövdəsi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` olanlar) üçün sağlam yığın sürətli yolunun (#10437) məhdud əlavə tutumu. Bu məhdudiyyət olmadan məşğul, lakin sağlam yığına malik hər sorğu heç bir yuxarı hədd olmadan qəbul nəzarətindən yan keçirdi. Sağlam yığın yolu vasitəsilə bu sayda paralel icarə aktiv olduqdan sonra növbəti məşğul sorğular real yığın təzyiqi zamanı istifadə edilən EYNİ məhdud gözləmə/yük azaltma yoluna keçir. `0` yan keçidi tamamilə söndürür. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdəsi bayt həddindən aşağı olsa belə, çat sorğusunu ağır çəkili kimi təsnif edən mesaj sayı. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdəsi bayt həddindən aşağı olsa belə, çat sorğusunu ağır çəkili kimi təsnif edən alət sayı. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Sorğunu ağır çəkili kimi təsnif edən konservativ sətir ölçüsünə əsaslanan token təxmini; bu, qəbul xərcinin təxmini göstəricisidir, provayderin ödəniş tokenləşdirilməsi deyil. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (söndürülüb) | `src/shared/middleware/chatBodyAdmission.ts` | Könüllü olaraq aktivləşdirilən çat tarixçəsi həddi. Standart olaraq söndürülüb: mesaj sayı sorğunun universal xüsusiyyəti deyil, yerləşdirmə siyasətidir və burada hədd qoyulması söhbətləri sıxılma konveyeri onları emal edilə bilən hala gətirməzdən əvvəl yekun `413` xətası ilə rədd edir. Yığın artımı `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` və yığın təzyiqinə əsaslanan yük azaltma ilə məhdudlaşdırılır. Sərt yuxarı həddə ehtiyacı olan yaddaşı məhdud yerləşdirmələrdə müsbət dəyər təyin edin; həddi aşanlar bu halda sıxlaşdırma tələbini bildirən strukturlaşdırılmış `413` xətası alır. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Tam şəkildə yaddaşda buferlənən axınsız yuxarı axın cavabı üçün sərt hədd. Bu hədd aşıldıqda yuxarı axın oxuyucusu ləğv edilir və yığın tükənənədək qeyri-məhdud sətir böyütmək əvəzinə sorğu dərhal xəta ilə dayandırılır. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Yuxarı axın cavab başlıqlarından ötürülən maksimum şəbəkə baytları. Büdcə aşıldıqda, geniş yayılmış əks-proksi başlıq limitləri daxilində qalmaq üçün daha aşağı prioritetli başlıqlar (məsələn, fərdi `x-codex-*`, `x-oai-request-id`) silinir. Cavab başlığının ölçüsünün artması hesabına daha çox yuxarı axın metadatasını ötürmək üçün daha yüksək dəyər təyin edin. | +| `CORS_ORIGIN` | _(təyin edilməyib)_ | `src/server/cors/origins.ts` | Köhnə, tək mənşəli CORS icazə siyahısı. Yeni yerləşdirmələr üçün `CORS_ALLOWED_ORIGINS` seçiminə üstünlük verin. CORS yalnız fərqli mənşəli brauzer API müştəriləri üçündür; autentifikasiya edilmiş idarə paneli yazma əməliyyatları bunun əvəzinə eyni mənşəli sorğulardan və sessiyaya bağlı CSRF qorumasından istifadə edir. | +| `CORS_ALLOWED_ORIGINS` | _(təyin edilməyib)_ | `src/server/cors/origins.ts` | Vergüllə ayrılmış CORS icazə siyahısı. `CORS_ALLOW_ALL=true` açıq şəkildə konfiqurasiya edilmədikcə heç bir universal şablon göndərilmir. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | İstənilən brauzer `Origin` dəyərini əks etdirmək üçün yalnız inkişaf mühitinə aid çıxış yolu. Paylaşılan və ya istehsal yerləşdirmələrində aktiv etməyin. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Köhnə ləqəbdir (legacy alias). Onun idarə panelindəki keçid düyməsi (verilənlər bazası override-i) bu mühit dəyərindən əvvəl oxunur; iklisindən birində `false`, `0`, `no` və ya `off` olduqda host yoxlamaları `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`-nun etdiyi şəkildə söndürülür — həmin bayrağın idarə paneli keçidi söndürülmüş vəziyyətdə saxlanıldıqda belə; digər dəyərlər isə qoruyucunu aşağıdakı iki bayrağa buraxır. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Xaricə çıxan URL qoruyucusunun host yoxlamalarını — bulud metadata blokunu da əhatə edərək (169.254.0.0/16-nın tamamı, metadata.google.internal) — qoruyucu rejiminin tətbiq olunduğu provayder yollarında söndürür: URL doğrulanması, model kəşfi, provayder-qovşağı base URL-ləri (uzaq rerank qovşaqları daxil), proksi-fallback testi, həmçinin uzaq şəkil/media çəkilməsi kimi digər provayderə yaxın xaricə çıxan yollar. Bundan əlavə şəxsi webhook hədəflərinə icazə verir; yalnız bulud-metadata/link-local son nöqtələri istisnadır — onlar bu bayraq açıq olsa da bloklanmış qalır. `BaseExecutor.execute()` vasitəsilə göndərilən söhbət sorğuları isə öz bulud-metadata yoxlamasını saxlayır. Lokal provayderlər defoltlarla işləyir: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (defolt `true`) doğrulanma, kəşf və provayder-qovşağı yollarında lokal və LAN URL-lərinə onsuz da icazə verir; proksi-fallback testi və şəxsi webhook hədəfləri isə yalnız bu bayrağa baxır və o söndürülmüş halda ikən lokal/LAN hostlarını bloklayır. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` dəyərini ləğv edir. İdarə paneli keçidindən saxlanılmış dəyər bu dəyişəndən üstün tutulur. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Provayder doğrulanması, model kəşfi, söhbət və qoruyucu rejiminin hökm sürdüyü digər provayderə yaxın xaricə çıxan yollar üçün lokal/şəxsi ünvanlardakı (127.0.0.1, localhost, LAN, şəxsi diapazonlar) provayder URL-lərinə icazə verin. **Defolt olaraq `true`** (lokal üstünlüklü): qoruyucu bu halda bulud-metadata son nöqtələrini bloklayır — 169.254.0.0/16-nın tamamı və məlum metadata host adları (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Yalnız-ictimai rejim üçün `false` təyin edin — bu rejim şəxsi və geri döngə hostlarını da bloklayır. Qoruyucu host adını və ya IP literalını yazıldığı şəkildə, hər hansı DNS sorğusundan əvvəl yoxlayır; yalnız-ictimai rejimdə təhlükəsiz-fetch sarmalayıcısı əlavə olaraq host adını həll edir və hər hansı şəxsi cavabı rədd edir. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` bu yoxlamaları onun sətirində sadalanan yollarda söndürür. LM Studio, Ollama, vLLM, Llamafile və Triton kimi daxili lokal provayderlər açar doğrulanması və söhbət üçün bütün host yoxlamalarını ötürür. İdarə paneli keçidindən saxlanılmış dəyər bu dəyişəndən üstün tutulur. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` marşrutlarının (transkripsiyalar, nitq, tərcümələr) localhost xaricində yerləşdirilmiş OpenAI-uyğun provayder qovşağından istifadə etməsinə icazə verin. Defolt olaraq söndürülüb — audionun uzaq hosta yönləndirilməsi çıxış identikliyini dəyişir və operatorun açıq qərarı olmalıdır. Geri döngə/şəxsi qovşaqlara (localhost, 127.0.0.1, 172.16-31.x) həmişə icazə verilir və onlar bundan təsirlənmir. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` sorğusunun (və yaddaş mühərrikinin geri döngəli yenidən sıralama addımının) localhost xaricində yerləşdirilmiş OpenAI-uyğun provayder qovşağından — TEI, Infinity, vLLM və s. işlədən LAN cihazından və ya Tailscale bərabərtərəfindən — istifadə etməsinə icazə verin. Defolt olaraq söndürülüb — uzaq hosta yönləndirmə çıxış identikliyini dəyişir və operatorun açıq qərarı olmalıdır. Geri döngə qovşaqlarına (localhost, 127.0.0.1, 172.16-31.x) həmişə icazə verilir və onlar bundan təsirlənmir. Uzaq qovşağın base URL-i də həmçinin provayderin xaricə çıxan URL siyasətindən (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) keçməlidir. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC aktiv olduqda parol ilə girişi deaktiv edin ki, istifadəçilər yalnız OIDC Vahid Girişi vasitəsilə autentifikasiya oluna bilsinlər. Sadə `OIDC_DISABLE_PASSWORD_LOGIN` aliası da qəbul edilir; eyni açara malik İdarə Paneli Funksiya Bayrağı üstünlüyə malikdir. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` üçün sadə alias (#10889). | ### Sərtləşdirmə Yoxlama Siyahısı diff --git a/docs/i18n/bg/CONTRIBUTING.md b/docs/i18n/bg/CONTRIBUTING.md index b81a73069a2a..112b954a62fe 100644 --- a/docs/i18n/bg/CONTRIBUTING.md +++ b/docs/i18n/bg/CONTRIBUTING.md @@ -391,18 +391,18 @@ docs/ # Documentation - [ ] Тестовете преминават успешно (`npm test`) - [ ] Проверката за стил преминава успешно (`npm run lint`) -- [ ] Компилацията завършва успешно (`npm run build`) +- [ ] Компилацията е успешна (`npm run build`) - [ ] Добавени са TypeScript типове за новите публични функции и интерфейси - [ ] Няма твърдо зададени тайни или резервни стойности -- [ ] Публичните идентификационни данни за upstream са вградени чрез `resolvePublicCred()` (вижте [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), никога като литерални стойности -- [ ] Отговорите при грешки преминават през `buildErrorBody()` / `sanitizeErrorMessage()` — без необработени stack trace-ове в телата на отговорите (вижте [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell командите (`exec` / `spawn`) подават стойности по време на изпълнение чрез `env`, а не чрез интерполация на низове +- [ ] Публичните идентификационни данни от upstream са вградени чрез `resolvePublicCred()` (вижте [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), никога като литерали +- [ ] Отговорите при грешки преминават през `buildErrorBody()` / `sanitizeErrorMessage()` — без необработени stack trace данни в телата на отговорите (вижте [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell командите (`exec` / `spawn`) предават стойностите по време на изпълнение чрез `env`, а не чрез интерполация на низове - [ ] Всички входни данни са валидирани със Zod схеми -- [ ] Добавен е **фрагмент** за списъка с промени в `changelog.d/{features|fixes|maintenance}/-.md` за промени, видими за потребителите (вижте [`changelog.d/README.md`](./changelog.d/README.md)) — **не** редактирайте директно `CHANGELOG.md`; фрагментите се обединяват при публикуване на версия и никога не предизвикват конфликти между Pull Request-и +- [ ] Добавен е **фрагмент** към регистъра на промените в `changelog.d/{features|fixes|maintenance}/-.md` за промени, видими за потребителите (вижте [`changelog.d/README.md`](./changelog.d/README.md)) — **не** редактирайте директно `CHANGELOG.md`; фрагментите се обединяват при издаване на версия и никога не създават конфликти между PR-и - [ ] Документацията е актуализирана (ако е приложимо) -- [ ] Няма нови отворени предупреждения от CodeQL / Secret-Scanning или всяко от тях е отхвърлено с техническа обосновка, която препраща към съответния документ в `docs/security/` +- [ ] Няма нови предупреждения от CodeQL / Secret-Scanning или всяко от тях е отхвърлено с техническа обосновка, която препраща към съответния документ в `docs/security/` - [ ] Маршрутите, които стартират дъщерни процеси (`/api/mcp/`, `/api/cli-tools/runtime/`), са класифицирани като `isLocalOnlyPath()` в `src/server/authz/routeGuard.ts` — вижте [Строго правило №15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Няма `Co-Authored-By` завършващи редове в съобщенията на commit-ите — commit-ите трябва да се показват единствено под Git самоличността на собственика на хранилището (Строго правило №16) +- [ ] Няма AI/bot трейлъри `Co-authored-by` в съобщенията за commit-и (Строго правило №16) — човешките сътрудници, чиято работа е използвана повторно, се посочват със стандартни трейлъри `Co-authored-by: Name ` --- diff --git a/docs/i18n/bg/docs/architecture/QUALITY_GATES.md b/docs/i18n/bg/docs/architecture/QUALITY_GATES.md index c648e0384ad1..e6b256278297 100644 --- a/docs/i18n/bg/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/bg/docs/architecture/QUALITY_GATES.md @@ -15,151 +15,151 @@ --- -## Инвентар на гейтовете и профили за изпълнение +## Инвентар на проверките и профили за изпълнение ### Допускане на кандидати -Работните процеси CI и Quality Gates публикуват по една стабилна присъда: `Gate / CI` и -`Gate / Quality`. Тяхната версионирана политика за допускане определя всяка предходна задача +Работните процеси CI и Quality Gates извеждат по една стабилна присъда: `Gate / CI` и +`Gate / Quality`. Тяхната версионирана политика за допускане определя всяка предшестваща задача като задължителна или препоръчителна. Всяка приложима задължителна задача трябва да завърши успешно: липсващи, отменени, пропуснати, изчакващи и неизвестни резултати не могат да установят PASS. Валидна -класификация „само документация“ или „само каталог“ може да направи дадена пътека за код неприложима; -чернова на PR не се приема като кандидат. Етикет `hotfix` не отменя изискването за доказателства. +класификация само за документация или само за каталог може да направи даден кодов поток неприложим; +чернова на PR не се приема за кандидат. Етикетът `hotfix` не отменя изискванията за доказателства. -И двата работни процеса обхващат PR-и и push операции към основни/release клонове, ръчно стартиране и -събития на merge group. Push операциите, ръчното стартиране и merge group изпълняват пълния набор. -Fork-овете и merge group-ите използват хоствани изпълнители за задачи, които иначе избират самостоятелно -хоствани изпълнители; преди внедряването трябва да се провери наличието на достатъчен хостван капацитет. +И двата работни процеса обхващат PR-и и push операции към основните клонове и клоновете за издания, ръчно стартиране и +събития от групи за сливане. Push операциите, ръчното стартиране и групите за сливане изпълняват пълния набор. Разклоненията +и групите за сливане използват хоствани изпълнители за задачи, които в противен случай избират самостоятелно хоствани +изпълнители; преди внедряването трябва да се потвърди наличието на достатъчен хостван капацитет. -Всяка JSON разписка идентифицира извлечения SHA, изпълнението на работния процес и опита. -CLI отхвърля несъответствие между SHA на извлеченото състояние и SHA на събитието. Тестовете на работните процеси свързват членството в политиката -със списъка `needs` на задачата за присъда, така че нова или премахната пътека да не може да изчезне незабелязано. +Всяка JSON разписка идентифицира SHA на извлечената версия, изпълнението на работния процес и опита. +CLI отхвърля несъответствие между SHA на извлечената версия и събитието. Тестовете на работния процес обвързват членството в политиката +със списъка `needs` на задачата за присъдата, така че нов или премахнат поток да не може да изчезне незабелязано. Разписките обхващат собствения си работен процес, а не публикуването, внедряването или вътрешната логика на съществуващ препоръчителен скенер. Активирането на двете имена на проверки в правилата за клонове е отделна административна промяна; добавянето на тези задачи само по себе си не защитава даден клон. -### Инвентар на статичните сканирания +### Инвентар на статичното сканиране -Версионираният инвентар от npm псевдоними и членството в статичните сканирания се намират в +Версионираният инвентар на npm псевдонимите и членството в статичното сканиране се намират в `config/quality/gate-manifest.json`. Изпълнете `npm run check:gate-manifest`, за да валидирате имената на скриптовете и точните команди спрямо `package.json`; добавянията, премахванията и -отклоненията в командите водят до неуспех както на локалната кука, така и на задачите за класификация на промените в CI. -Псевдонимът не е задача в работен процес, екземпляр от матрица или тестов случай: тези бройки не трябва +отклоненията в командите водят до неуспех както на локалната кука, така и на задачите за класифициране на промените в CI. +Псевдонимът не е задача от работен процес, екземпляр на матрица или тестов случай: тези бройки не трябва да се представят като взаимозаменяеми. Използвайте `npm run quality:scan -- --list` или `npm run quality:scan:fast -- --list`, за да прегледате избраните псевдоними, без да ги изпълнявате. Изпълнителят извиква -входната точка на npm, така че средата ѝ за изпълнение (включително Bun, където е конфигуриран) се запазва. +npm входната точка, така че нейната среда за изпълнение (включително Bun, където е конфигуриран) се запазва. Манифестът записва псевдонимите извън тези профили като извиквани отделно, а командите за поддръжка са забранени в профилите за сканиране само за четене. -Тези профили обхващат единствено статичното сканиране. Те не удостоверяват продуктовите тестове, +Тези профили обхващат единствено статичното сканиране. Те не сертифицират продуктовите тестове, покритието, пакетирането, външните проверки или пълното приемане на кандидат за издание. Допускането от работния процес използва свързаните `config/quality/admission-policy.json` и -`scripts/quality/admission-verdict.mjs`. Профилите за наблюдение на издания остават отделни; +`scripts/quality/admission-verdict.mjs`. Профилите за наблюдение на изданията остават отделни; проверявайте независимо приложимите им проверки и разписки. Текстовият -инвентар по-долу е справочен материал, а не доказателство, че даден гейт действително е бил изпълнен. +инвентар по-долу е справочен материал, а не доказателство, че дадена проверка действително е била изпълнена. -Скриптовете се намират в `scripts/check/` (гейтове за политики) и `scripts/quality/` (механизъм с храпов принцип). +Скриптовете се намират в `scripts/check/` (проверки на политики) и `scripts/quality/` (механизъм с тресчотка). Източникът на истина за CI е `.github/workflows/ci.yml`. -### Бърза пътека за PR за издание (`quality.yml`) +### Ускорен път за PR за издание (`quality.yml`) -`.github/workflows/quality.yml` допълва CI при PR-и към основни/release клонове, push операции към защитени -клонове, ръчно стартиране и merge group-и. PR-ите използват бързи проверки, филтрирани по пътища. Постоянно -деактивираното дублирано изграждане беше премахнато; реалните проверки за изграждане/пакетиране/стартиране остават в CI. +`.github/workflows/quality.yml` допълва CI за PR-и към основните клонове и клоновете за издания, push операции към защитени клонове, +ръчно стартиране и групи за сливане. PR-ите използват филтрирани по пътища бързи проверки. Постоянно +деактивираното дублиращо изграждане беше премахнато; реалните проверки за изграждане, пакетиране и стартиране остават в CI. -| Задача | Обхват | Блокираща | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `Docs Gates (fast-path)` | PR-и за документация/код; препратки към API документация и цялата документация | Да | -| `Fast Quality Gates` | PR-и за код; статични проверки, проверка на типове, проверка на типовете на таблото, засегнати модулни тестове | Да | -| `Forgotten sibling tests` | PR-и за код; променени модули, проследени до статични потребители и кандидат-тестове за сродни модули; пътищата през barrel файлове и динамични импорти се отчитат като препоръчителна диагностика, с посочени изключения от списъка с разрешения | **Препоръчителна** | -| `Vitest (fast-path)` | PR-и за код; бърз пакет от vitest тестове | Да | -| `Unit Tests fast-path` | PR-и за код; пакет от модулни тестове в 4 шарда | Да | -| `No new ESLint warnings` | PR-и за код; lint защита, отчитаща потисканията | Да, включително fork-ове | -| `Merge integrity (changelog + generated skills)` | PR-и, които не са чернови; синхронизиране на регистъра на промените и генерираните умения | Да, включително fork-ове | +| Задача | Обхват | Блокираща | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- | +| `Docs Gates (fast-path)` | PR-и за документация/код; препратки в API документацията и цялата документация | Да | +| `Fast Quality Gates` | PR-и за код; статични проверки, проверка на типовете, проверка на типовете на таблото, засегнати модулни тестове | Да | +| `Forgotten sibling tests` | PR-и за код; променените модули се проследяват до статичните потребители и кандидат-тестовете за сродни модули; пътищата през обобщаващи модули и динамичен импорт се отчитат като препоръчителна диагностика, с посочени изключения от списъка с разрешения | **Препоръчителна** | +| `Vitest (fast-path)` | PR-и за код; бърз набор от vitest тестове | Да | +| `Unit Tests fast-path` | PR-и за код; набор от модулни тестове в 4 сегмента | Да | +| `No new ESLint warnings` | PR-и за код; предпазна lint проверка, отчитаща потисканията | Да, включително разклонения | +| `Merge integrity (changelog + generated skills)` | PR-и, които не са чернови; синхронизиране на регистъра на промените и генерираните умения | Да, включително разклонения | -#### Отчет за забравени тестове за сродни модули +#### Отчет за забравени тестове на сродни модули -`npm run check:forgotten-sibling-tests` използва повторно механизма за разрешаване на импорти зад картата за въздействие върху тестовете. +`npm run check:forgotten-sibling-tests` използва повторно механизма за разрешаване на импорти зад картата на въздействието върху тестовете. За всеки променен производствен модул той отчита детерминистични вериги -`променен модул/символ -> статичен потребител -> кандидат-тест за сроден модул`, когато кандидат-тестът -отсъства от разликите в pull request-а. Markdown резюмето и JSON резултатът се запазват като -артефакта `forgotten-sibling-tests` на работния процес за калибриране преди евентуално внедряване като блокираща проверка. +`променен модул/символ -> статичен потребител -> кандидат-тест на сроден модул`, когато кандидат- +тестът липсва от разликите в pull request-а. Markdown обобщението и JSON резултатът се съхраняват като +артефакта `forgotten-sibling-tests` на работния процес за калибриране преди евентуално въвеждане като блокираща проверка. -Barrel реекспортите и динамичните импорти служат само за диагностика на резолюцията; те никога не създават +Barrel реекспортите и динамичните импорти служат само за диагностика на разрешаването; те никога не създават блокираща констатация. Прегледаните изключения се намират в -`config/quality/forgotten-sibling-allowlist.json`. Всеки запис трябва да посочва потребителя и кандидат-теста, -да предоставя конкретна обосновка и да съдържа връзка към GitHub issue или pull request. Неправилно форматираните записи водят -до отказ по подразбиране. Изключенията не могат да потиснат изтрит кандидат-тест или diff, който добавя `.skip`/`.todo`; -отслабването на твърденията и другите форми на прикриване остават отговорност на независимо блокиращата -проверка `check:test-masking`. +`config/quality/forgotten-sibling-allowlist.json`. Всеки запис трябва да посочва потребителския и кандидатстващия +тест, да съдържа конкретна обосновка и връзка към GitHub issue или pull request. Неправилно форматираните записи водят +до отказ по подразбиране. Изключенията не могат да потиснат изтрит кандидатстващ тест или diff, който добавя `.skip`/`.todo`; +отслабването на проверки и другите форми на прикриване остават отговорност на независимо блокиращия +контролен механизъм `check:test-masking`. ### Задача: `lint` Изпълнява се при всеки PR към `main`. Блокира сливането при неуспех. -| Скрипт (`npm run ...`) | Проверява | Блокираща | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| `check:node-runtime` | Версията на Node.js е в поддържания диапазон | Да | -| `check:cycles` | Циклични импорти — всички модули в `src/` + `open-sse/` | Да | -| `check:route-validation:t06` | Наличие на Zod схеми във всички маршрути (политика от ниво 6) | Да | -| `check:any-budget:t11` | Броят на `@ts-expect-error // any` не надвишава бюджета (catraca от ниво 11) | Да | -| `check:provider-consistency` | Всеки доставчик в `providers.ts` има съответстващ запис в `providerRegistry.ts` (и обратно, в рамките на списъка с разрешени стойности) | Да | -| `check:model-lifecycle` | Трите ръчно поддържани таблици за маршрутизиране остават съгласувани с включената в хранилището моментна снимка на жизнения цикъл (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не оценява нито един изведен от употреба идентификатор, който `REGISTRY` може да маршрутизира; всяка цел на `BUILT_IN_ALIASES` присъства в `REGISTRY` и отсъства от моментната снимка на изведените от употреба идентификатори; всеки изведен от употреба идентификатор, който все още е в `REGISTRY`, се препраща или е посочен в `allowedRetiredInCatalog`; и нито един източник или цел в `DEFAULT_DEGRADATION_MAP` не е отбелязан като изведен от употреба в тази моментна снимка. Това не доказва, че даден модел в момента се обслужва от активна външна услуга. Офлайн — сравнява с `config/quality/model-lifecycle.json`, обновяван ръчно чрез `npm run quality:refresh-model-lifecycle` (изисква мрежа; не е интегрирано в CI). `allowedRetiredInCatalog` е механизъм за постепенно намаляване: добавяйте запис само при наличие на задача за проследяване. | Да | -| `check:fetch-targets` | Всяко `fetch("/api/...")` в клиентската част на `src/` се съпоставя с реален `route.ts` | Да | -| `check:deps` | Всички зависимости, които могат да бъдат инсталирани чрез `npm install`, във всеки `package.json` в хранилището присъстват в `dependency-allowlist.json`; новите зависимости без фиксирана версия или пакетите с имена, имитиращи популярни пакети, се маркират | Да | -| `audit:deps` | `npm audit` (основна директория + electron) — няма предупреждения с висока/критична тежест (припокрива се с osv `check:vuln-ratchet`; вижте списъка със задачи за рационализиране) | Да | -| `check:lockfile` | Цялост на `package-lock.json` — регистър по https, хешове за цялост, без замествания на хоста | Да | -| `check:licenses` | Списък с разрешени SPDX лицензи за производствени зависимости | Да | -| `check:tracked-artifacts` | Без артефакти от компилация / включени в хранилището символни връзки към `node_modules` (изпълнява се и в husky pre-commit; pre-push умишлено е олекотен — #6716) | Да | -| `check:ai-attribution` | Без завършващ ред `Co-Authored-By` за AI/бот или бележка за генериране от AI в commit-ите, заглавието или описанието на PR — Строго правило #16 (в цикъла за бързи проверки на `quality.yml` за PR→`release/**` — прочита payload-а на събитието и не извършва действие извън PR — и стъпка само за PR в lint на `ci.yml` за PR→`main`; също и hook-ът `commit-msg` на husky; разрешени са човешки съавтори; #14436) | -| `check:vitest-exclusions` | Всяко изключение на Vitest посочва issue за проследяване и присъства в `config/quality/vitest-exclusions.json` (#13204) | Да | -| `check:file-size` | Никой файл с изходен код не надвишава ограничението за съответното разширение (ratchet: фиксирани големи файлове в списъка `frozen`) | Да | -| `check:error-helper` | Отговорите за грешки в изпълнителите/обработчиците използват `buildErrorBody()` / `sanitizeErrorMessage()` (Строго правило #12) | Да | -| `check:migration-numbering` | SQL файловете за миграции са номерирани последователно, без пропуски или дубликати | Да | -| `check:public-creds` | Няма литерални OAuth `client_id`/`client_secret` или Firebase Web ключове извън `publicCreds.ts` (Строго правило №11) | Да | -| `check:db-rules` | Няма необработен SQL извън модулите в `src/lib/db/`; няма обобщаващи импорти от `localDb.ts` (Строги правила №2/№5) | Да | -| `check:known-symbols` | Изпълнителите на доставчици, стратегиите за маршрутизиране и транслаторите, регистрирани в съответните им таблици за диспечиране, съответстват на файловете на диска — няма осиротели или недекларирани символи | Да | -| `check:route-guard-membership` | Всеки маршрут, който стартира дъщерен процес, е класифициран чрез `isLocalOnlyPath()` (Строги правила №15/№17) | Да | -| `check:test-discovery` | Всеки файл `*.test.ts` / `*.spec.ts` в хранилището се открива от поне един механизъм за изпълнение на тестове (ratchet: списъкът с осиротели файлове в `test-discovery-baseline.json` може само да намалява) | Да | -| `check:agent-skills-sync` | Генерираните артефакти за agent-skills съответстват на изходния им каталог (без разминаване) | -| `check:provider-asset-provenance` | Логата/ресурсите на доставчиците имат запис за произход | -| `lint:json` | JSON конфигурационните файлове се анализират успешно и отговарят на правилата за lint на хранилището | -| `typecheck:core` | Компилиране на TypeScript без грешки (само консултативни предупреждения) | Да | -| `typecheck:noimplicit:core` | Строг `noImplicitAny` — с поглед към бъдещето; много съществуващи места на извикване все още се нуждаят от анотации | **Консултативно** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, ограничен до `src/app/(dashboard)/**` (#7033) — подбраният списък с 27 файла за разрешаване на `typecheck:core` не включва нито един dashboard TSX файл, а `next build` също никога не извършва проверка на типовете му (`next.config.mjs` задава `ignoreBuildErrors: true`), така че регресиите с несвързани идентификатори там (#6625/#6909) оставаха невидими за CI. Сравнява със замразена базова линия на броя по файл/TS код (`config/quality/dashboard-typecheck-baseline.json`, същият модел за налагане при остаряване като при `check:known-symbols`) — само НОВИ грешки над броя в базовата линия водят до неуспешно преминаване на контролата; намалявайте постепенно с `--update`, когато съществуваща грешка бъде поправена. | Да | +| Скрипт (`npm run ...`) | Проверява | Блокиращ | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `check:node-runtime` | Версията на Node.js е в поддържания диапазон | Да | +| `check:cycles` | Циклични импорти в целите `src/` + `open-sse/` (базирано на AST, с разрешени `paths` от tsconfig). Самостоятелното изпълнение е с препоръчителен характер и извежда списък с циклите. `check:cycles:ratchet` (изпълняваното от CI) блокира, когато броят надвиши тавана `metrics.cycles` в `quality-baseline.json` — понастоящем 14, `direction: down`, така че може само да намалява (#15159 G-01/G-02) | Да (ratchet) | +| `check:route-validation:t06` | Наличие на Zod схеми във всички маршрути (политика от ниво 6) | Да | +| `check:any-budget:t11` | Броят на `@ts-expect-error // any` не надвишава бюджета (catraca от ниво 11) | Да | +| `check:provider-consistency` | Всеки доставчик в `providers.ts` има съответстващ запис в `providerRegistry.ts` (и обратно, в рамките на списъка с разрешени стойности) | Да | +| `check:model-lifecycle` | Трите ръчно поддържани таблици за маршрутизиране остават съгласувани със snapshot-а на жизнения цикъл, включен в хранилището (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не оценява нито един изведен от употреба id, който `REGISTRY` може да маршрутизира; всяка цел от `BUILT_IN_ALIASES` присъства в `REGISTRY` и отсъства от snapshot-а с изведени от употреба id; всеки изведен от употреба id, който все още е в `REGISTRY`, се пренасочва или е посочен в `allowedRetiredInCatalog`; и нито един източник или цел от `DEFAULT_DEGRADATION_MAP` не фигурира като изведен от употреба в този snapshot. Това не доказва, че даден модел в момента се обслужва от действаща външна услуга. Офлайн — сравнява с `config/quality/model-lifecycle.json`, обновяван ръчно чрез `npm run quality:refresh-model-lifecycle` (изисква мрежа; не е интегриран в CI). `allowedRetiredInCatalog` е поетапно затягащ се ограничител: добавяйте запис само при наличие на issue за проследяване. | Да | +| `check:fetch-targets` | Всеки `fetch("/api/...")` в клиентската част на `src/` се съпоставя с реален `route.ts` | Да | +| `check:deps` | Всички зависимости, които могат да бъдат инсталирани чрез `npm install`, във всеки `package.json` в хранилището присъстват в `dependency-allowlist.json`; новите пакети без фиксирана версия или със slopsquatted имена се маркират | Да | +| `audit:deps` | `npm audit` (основен проект + electron) — няма предупреждения с висока/критична тежест (припокрива се с osv `check:vuln-ratchet`; вижте списъка с натрупани задачи за рационализация) | Да | +| `check:lockfile` | Цялост на `package-lock.json` — https регистър, хешове за цялост, без замени на хостове | Да | +| `check:licenses` | Списък с разрешени SPDX лицензи за продукционните зависимости | Да | +| `check:tracked-artifacts` | Без артефакти от компилацията / включени в хранилището символни връзки към `node_modules` (изпълнява се и в husky pre-commit; pre-push умишлено е олекотен — #6716) | Да | +| `check:ai-attribution` | Без завършващ ред `Co-Authored-By` за ИИ/бот или бележка за генериране от ИИ в commit-ите, заглавието или описанието на PR — Строго правило №16 (в цикъла за бързи проверки на `quality.yml` за PR→`release/**` — прочита данните за събитието и не извършва операция извън PR — както и в стъпка само за PR в lint на `ci.yml` за PR→`main`; също и hook-ът `commit-msg` на husky; човешки съавтори са разрешени; #14436) | +| `check:vitest-exclusions` | Всяко изключение на Vitest посочва проследяващ issue и присъства в `config/quality/vitest-exclusions.json` (#13204) | Да | +| `check:file-size` | Никой файл с изходен код не надвишава ограничението за съответното разширение (ratchet: фиксирани големи файлове в списъка `frozen`) | Да | +| `check:error-helper` | Отговорите за грешки в изпълнители/обработчици използват `buildErrorBody()` / `sanitizeErrorMessage()` (Строго правило №12) | Да | +| `check:migration-numbering` | SQL файловете за миграции са номерирани последователно, без пропуски или дубликати | Да | +| `check:public-creds` | Няма буквално зададени OAuth `client_id`/`client_secret` или Firebase Web ключове извън `publicCreds.ts` (Строго правило №11) | Да | +| `check:db-rules` | Няма необработен SQL извън модулите в `src/lib/db/`; няма обобщаващи импорти от `localDb.ts` (Строги правила №2/№5) | Да | +| `check:known-symbols` | Изпълнителите на доставчици, стратегиите за маршрутизиране и преводачите, регистрирани в съответните им таблици за диспечиране, съвпадат с файловете на диска — няма осиротели или недекларирани символи | Да | +| `check:route-guard-membership` | Всеки маршрут, който стартира дъщерен процес, е класифициран чрез `isLocalOnlyPath()` (Строги правила №15/№17) | Да | +| `check:test-discovery` | Всеки файл `*.test.ts` / `*.spec.ts` в хранилището се открива от поне една програма за изпълнение на тестове (ограничителен механизъм: списъкът с осиротели файлове в `test-discovery-baseline.json` може само да намалява) | Да | +| `check:agent-skills-sync` | Генерираните артефакти за уменията на агентите съответстват на изходния им каталог (без разминавания) | +| `check:provider-asset-provenance` | Логата/ресурсите на доставчиците имат регистриран запис за произход | +| `lint:json` | JSON конфигурационните файлове се анализират успешно и отговарят на правилата за lint на хранилището | +| `typecheck:core` | Компилация на TypeScript без грешки (само консултативни предупреждения) | Да | +| `typecheck:noimplicit:core` | Строг `noImplicitAny` — с насоченост към бъдещето; много съществуващи места на извикване все още се нуждаят от анотации | **Консултативно** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, ограничен до `src/app/(dashboard)/**` (#7033) — подбраният списък с 27 файла на `typecheck:core` не включва нито един TSX файл на таблото, а `next build` също никога не извършва проверка на типовете му (`next.config.mjs` задава `ignoreBuildErrors: true`), така че регресиите с осиротели идентификатори там (#6625/#6909) оставаха невидими за CI. Сравнява със замразена базова линия за броя по файл/TS код (`config/quality/dashboard-typecheck-baseline.json`, същият модел за налагане при остаряване като при `check:known-symbols`) — само НОВИ грешки над броя в базовата линия водят до неуспех на проверката; намалявайте постепенно базовата линия с `--update`, когато съществуваща грешка бъде поправена. | Да | ### Задача: `quality-gate` Изпълнява се след `test-coverage`. Блокира сливането при неуспех. -| Скрипт | Проверява | Блокиращ | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | -| `quality:collect` | Генерира `quality-metrics.json` (брой предупреждения от ESLint, покритие от обединения отчет на отделните части) | Да (преди ratchet) | -| `quality:ratchet` | Нито един показател в `quality-baseline.json` не се е влошил (предупреждения от ESLint ≤ базовото ниво; покритие ≥ базовото ниво) | Да | -| `check:duplication` | Дублирането на код (jscpd@4) не надвишава базовото ниво в `quality-baseline.json` | Да | -| `check:complexity` | Цикломатичната сложност на ниво файл не надвишава лимита (основните правила на ESLint `complexity` + `max-lines-per-function`) | Да | -| `check:cognitive-complexity` | Ratchet за когнитивна сложност (`eslint-plugin-sonarjs`) — отделно изпълнение на ESLint; CI изпълнява и двете обединено като една стъпка `check:complexity-ratchets` | Да | -| `check:dead-code` | Ratchet за неизползвани експорти/файлове (knip) не се влошава спрямо базовото ниво | Да | -| `check:compression-budget` | Бюджет за сравнителния тест на компресията — минималните прагове за спестени токени за всяка машина не трябва да се влошават | Да | -| `check:type-coverage` | Ratchet за процента типизиран код (`type-coverage`) не се влошава; до голяма степен включва `typecheck:noimplicit:core` | Да | -| `check:codeql-ratchet` | Броят на отворените предупреждения от CodeQL не се влошава (прочита се чрез `gh api`; коректно пропускане без токен) — за честотата на обновяване и ръчното задействане вижте „CodeQL ratchet“ по-долу | Да | +| Скрипт | Проверява | Блокиращ | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `quality:collect` | Генерира `quality-metrics.json` (брой предупреждения от ESLint, покритие от обединения отчет на шардовете) | Да (предшества ratchet) | +| `quality:ratchet` | Нито една метрика в `quality-baseline.json` не се е влошила (предупреждения от ESLint ≤ базовата стойност; покритие ≥ базовата стойност) | Да | +| `check:duplication` | Дублирането на код (jscpd@4) не надвишава базовата стойност в `quality-baseline.json` | Да | +| `check:complexity` | Цикломатичната сложност на ниво файл не надвишава лимита (основните правила на ESLint `complexity` + `max-lines-per-function`) | Да | +| `check:cognitive-complexity` | Ratchet за когнитивна сложност (`eslint-plugin-sonarjs`) — отделно изпълнение на ESLint; CI изпълнява и двете обединени като една стъпка `check:complexity-ratchets` | Да | +| `check:dead-code` | Ratchet за неизползвани експорти / файлове (knip) не се влошава спрямо базовата стойност | Да | +| `check:compression-budget` | Бюджет за бенчмарк на компресията — минималните стойности за спестени токени за всяка машина не трябва да се влошават | Да | +| `check:type-coverage` | Ratchet за процента типизиран код (`type-coverage`) не се влошава; до голяма степен обхваща `typecheck:noimplicit:core` | Да | +| `check:codeql-ratchet` | Броят на отворените предупреждения от CodeQL не се влошава (чете чрез `gh api`; коректно пропуска без токен) — за честотата на опресняване и ръчното задействане вижте „CodeQL ratchet“ по-долу | Да | ### Задача: `quality-extended` Цялата задача е препоръчителна (`continue-on-error: true`). Базираните на npm ratchet проверки се изпълняват -реално; външните скенери се инсталират чрез `gh release download` и се пропускат автоматично (exit 0), -когато съответният изпълним файл все още липсва. - -| Скрипт | Проверява | Блокиращ | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -| `check:circular-deps` | Няма циклични зависимости (dpdm) | **Препоръчителен** | -| `check:bundle-size` | Размерът на пакета не надвишава лимита | **Препоръчителен** | -| `check:secrets` | Сканиране за тайни (gitleaks) — пропуска се, ако изпълнимият файл липсва | **Препоръчителен** | -| `check:vuln-ratchet` | Уязвимостите в зависимостите (osv-scanner) не се увеличават — пропуска се, ако изпълнимият файл липсва | **Препоръчителен** | -| `check:workflows` | Проверка на работните процеси (actionlint + zizmor); липсващи/повредени скенери, невалидни отчети или липсващо базово ниво за ratchet водят до неуспех със статус INCOMPLETE. Валидните констатации следват избраната строга/препоръчителна/ratchet политика | Изпълнението е задължително; zizmor ratchet блокира в CI | -| `check:openapi-breaking` | Нарушаващи съвместимостта промени в публичния API договор (`openapi.yaml`) спрямо базовия клон (oasdiff) — генерира `openapiBreaking=N`; пропуска се, ако oasdiff липсва или базовата спецификация не може да бъде намерена | **Препоръчителен** | +реално; външните скенери се инсталират чрез `gh release download` и се самопропускат (exit 0), +когато даден изпълним файл все още липсва. + +| Скрипт | Проверява | Блокиращ | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| `check:circular-deps` | Няма циклични зависимости (dpdm) | **Препоръчителен** | +| `check:bundle-size` | Размерът на пакета не надвишава лимита | **Препоръчителен** | +| `check:secrets` | Сканиране за тайни (gitleaks) — пропуска се, ако изпълнимият файл липсва | **Препоръчителен** | +| `check:vuln-ratchet` | Уязвимостите в зависимостите (osv-scanner) не се увеличават — пропуска се, ако изпълнимият файл липсва | **Препоръчителен** | +| `check:workflows` | Линтинг на работни процеси (actionlint + zizmor); липсващи/повредени скенери, невалидни отчети или липсваща базова стойност за ratchet водят до неуспех със статус INCOMPLETE. Валидните констатации следват избраната строга/препоръчителна/ratchet политика | Изпълнението е задължително; zizmor ratchet е блокиращ в CI | +| `check:openapi-breaking` | Несъвместими промени в публичния API договор (`openapi.yaml`) спрямо базовия клон (oasdiff) — генерира `openapiBreaking=N`; пропуска се, ако oasdiff липсва или базовата спецификация не може да бъде прочетена | **Препоръчителен** | ### Задача: `docs-sync-strict` @@ -169,49 +169,49 @@ Barrel реекспортите и динамичните импорти слу | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | `check:docs-all` | Мета-проверка, която изпълнява последователно 6-те подпроверки по-долу | Да | | ↳ `check:docs-sync` | Съгласуваност на версиите в CHANGELOG / OpenAPI / llm.txt | Да | -| ↳ `check:docs-counts` | Броевете в текста (брой доставчици, брой миграции и др.) са в рамките на прозореца за постепенно затягане спрямо реалните стойности | Да | +| ↳ `check:docs-counts` | Числата в текста (брой доставчици, брой миграции и т.н.) са в рамките на прозореца с еднопосочно затягане спрямо реалните стойности | Да | | ↳ `check:env-doc-sync` | Всяка променлива на средата в `.env.example` е документирана в таблица в документацията и обратно | Да | -| ↳ `check:deprecated-versions` | В документацията няма низове за оттеглени версии | Да | -| ↳ `check:doc-links` | Вътрешните markdown връзки в документацията сочат към реални файлове (формат `[text]`/`(path)`) | Да | +| ↳ `check:deprecated-versions` | В документацията няма низове с остарели версии | Да | +| ↳ `check:doc-links` | Вътрешните markdown връзки в документацията сочат към реални файлове (във формат `[text]`/`(path)`) | Да | | ↳ `check:fabricated-docs` | Маршрутите, променливите на средата, CLI командите, имената на hook-ове и файловите пътища, посочени в документацията, съществуват в кодовата база. Строга проверка чрез `--strict`; без флага неуспехът не е блокиращ. | Да (чрез `--strict` в CI) | -| `check:cli-i18n` | Низовете на CLI командите присъстват във всички i18n файлове за локали | Да | -| `check:openapi-coverage` | Спецификацията OpenAPI покрива поне постепенно повишаван минимален праг от реалните маршрути | Да | -| `check:openapi-security-tiers` | Анотациите за нивата на сигурност в `openapi.yaml` са съгласувани с класификациите в `routeGuard.ts` | **Препоръчителна** | +| `check:cli-i18n` | Низовете на CLI командите присъстват във всички файлове с i18n локали | Да | +| `check:openapi-coverage` | Спецификацията OpenAPI покрива поне постепенно повишавания минимален праг от реални маршрути | Да | +| `check:openapi-security-tiers` | Анотациите за нивата на сигурност в `openapi.yaml` са съгласувани с класификациите в `routeGuard.ts` | **Консултативно** | | `check:openapi-routes` | Всеки път в `openapi.yaml` съответства на реален `route.ts` (защита срещу халюцинации) | Да | -| `check:docs-symbols` | Всяка препратка към `/api/...` в `docs/**/*.md` съответства на реален `route.ts` (защита срещу халюцинации) | Да | -| `i18n translation drift` | Непреведени ключове във файловете за i18n локали — само предупреждение | **Препоръчителна** | +| `check:docs-symbols` | Всяко позоваване на `/api/...` в `docs/**/*.md` съответства на реален `route.ts` (защита срещу халюцинации) | Да | +| `i18n translation drift` | Непреведени ключове във файловете с i18n локали — само предупреждение | **Консултативно** | ### Задача: `i18n-ui-coverage` -| Скрипт | Проверява | Блокиращ | -| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `check-ui-keys-coverage` (вграден) | Покритието на UI i18n ключовете е ≥ 65% | Да | -| `check-ui-value-drift` (вграден) | Пренаписана английска **стойност** не оставя след себе си остарял превод | Да | -| `check-new-key-coverage` (вграден) | **Нов** английски ключ е преведен във всеки локал — маркер `__MISSING__:` се отхвърля | Да | -| `check-translation-ratio` | Делът на реалните преводи за всеки локал (еднакви с английския / заместващи / липсващи крайни ключове извън списъка с разрешени изключения) не трябва да надвишава `config/quality/i18n-translation-baseline.json` + допустимия толеранс | **Препоръчителна** | +| Скрипт | Проверява | Блокиращ | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `check-ui-keys-coverage` (вграден) | Покритието на UI i18n ключовете е ≥ 65% | Да | +| `check-ui-value-drift` (вграден) | Пренаписана английска **стойност** не оставя след себе си остарял превод | Да | +| `check-new-key-coverage` (вграден) | **Нов** английски ключ е преведен във всеки локал — маркерът `__MISSING__:` се отхвърля | Да | +| `check-translation-ratio` | Делът на реалните преводи за всеки локал (стойности, идентични с английските / заместители / липсващи стойности извън списъка с изключения) не трябва да надвишава `config/quality/i18n-translation-baseline.json` + допустимото отклонение | **Консултативно** | -Изисква `fetch-depth: 0` — проверката за разминаване на стойностите сравнява разликите в `en.json` спрямо базата на сливане. +Изисква `fetch-depth: 0` — проверката за отклонение на стойностите сравнява разликите в `en.json` спрямо общата база за сливане. #### `check-ui-value-drift` — проверка за остарели преводи -Улавя единствения тип i18n регресия, който останалите проверки структурно не могат да открият: английска стойност -е пренаписана, а преводите, създадени от _предишния_ английски текст, остават непроменени, така че -потребителите, които не използват английски, продължават да четат уверено формулиран, но вече неправилен текст. +Улавя единствената i18n регресия, която останалите проверки не могат да открият структурно: английска стойност +е пренаписана, а преводите, създадени от _предишния_ английски текст, остават, така че +потребителите, които не използват английски, продължават да четат уверено формулиран, но вече грешен текст. -Това действително достигна до продукционна версия. `oauthModal.googleOAuthWarning` беше пренаписан при добавянето на помощната -функция за вход в Antigravity (#5203); **39 от 43 локала** запазиха текст, който указваше на операторите да „копират -пълния URL адрес и да го поставят по-долу“ — процес, който не може да бъде завършен за този доставчик. Това остана -незабелязано до #8463, защото: +Това действително стигна до продукционна версия. `oauthModal.googleOAuthWarning` беше пренаписан при добавянето на помощната +функция за вход в Antigravity (#5203); **39 от 43 локала** запазиха текст, който казваше на операторите да „копират +целия URL адрес и да го поставят по-долу“ — процес, който не може да бъде завършен за този доставчик. Това +остана незабелязано до #8463, защото: -- `sync-ui-keys` попълва само ключове, които **липсват**, но никога такива, които са **остарели**; -- `check-ui-keys-coverage` отчита _наличието_ на ключа, така че остарелият превод се счита за покрит; +- `sync-ui-keys` добавя само ключове, които **липсват**, но никога такива, които са **остарели**; +- `check-ui-keys-coverage` отчита _наличието_ на ключа, затова остарелият превод се счита за покрит; - `check-translation-drift` следи огледалните копия на документацията в `docs/i18n//**.md` — - той никога не чете `src/i18n/messages/*.json`. Блокираща проверка в задачата `docs-sync-strict` след - повторното синхронизиране през 2026-09: редактирайте основен документ → `npm run i18n:run -- --files=` (на ниво раздел, бързо). + той никога не чете `src/i18n/messages/*.json`. Блокиращо в задачата `docs-sync-strict` след + повторната синхронизация през 2026-09: редактирайте основен документ → `npm run i18n:run -- --files=` (на ниво раздел, евтино). **Отчита разликите, без да разчита на базов файл.** Сравнява `en.json` в базата за сливане с работното дърво; за всеки ключ, чиято английска стойност е променена, всеки език, който все още съдържа -непроменен превод, се счита за остарял. Това умишлено **замразява вече съществуващия дълг** — разликата +непроменен превод, се счита за остарял. Това умишлено **замразява съществуващия дълг** — разликата не може да покаже от коя стара английска стойност произлиза отдавна съществуващ превод, затова проверката оценява само засегнатото от текущата промяна. Алтернативата (базов файл с хеш за всеки ключ) би изисквала генериран файл от ~600 KB, 3× по-голям от най-големия съществуващ базов файл, който би се променял при всяка i18n PR заявка. @@ -219,15 +219,15 @@ Barrel реекспортите и динамичните импорти слу Има два начина проверката да бъде удовлетворена: 1. актуализирайте засегнатите преводи или -2. задайте стойността им на `__MISSING__:` — тогава по време на изпълнение се предоставя коригираният английски текст - (`src/i18n/request.ts::deepMergeFallback`, #7258), а ключът се добавя към опашката за превод. +2. задайте ги като `__MISSING__:` — тогава по време на изпълнение се предоставя коригираният английски текст + (`src/i18n/request.ts::deepMergeFallback`, #7258), а ключът се поставя на опашка за превод. Ако **значението** на низа се е променило, предпочитайте **преименуване на ключа**: нов ключ не може да наследи остарял превод. Това е подходът, използван в #8463. ```bash npm run i18n:check-value-drift # строг режим (изпълняваният от CI) -npm run i18n:check-value-drift:warn # само отчет +npm run i18n:check-value-drift:warn # само доклад BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` @@ -236,43 +236,43 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ### Задача: `i18n` -Пълна матрица за валидиране на i18n (по една задача за всеки език). Цялата задача има препоръчителен характер. +Пълна матрица за валидиране на i18n (по една задача за всеки език). Цялата задача е препоръчителна. -| Скрипт | Валидира | Блокиране | -| ------------------------------- | ---------------------------------- | --------------------------------------------------------------- | -| `validate_translation.py quick` | Пълнотата на превода за всеки език | **Препоръчително** (`continue-on-error: true` за цялата задача) | +| Скрипт | Какво валидира | Блокираща | +| ------------------------------- | -------------------------------- | --------------------------------------------------------------- | +| `validate_translation.py quick` | Пълнота на превода за всеки език | **Препоръчителна** (`continue-on-error: true` за цялата задача) | ### Задача: `pr-test-policy` -Изпълнява се само при заявки за изтегляне. +Изпълнява се само за pull request заявки. -| Скрипт | Валидира | Блокиране | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | PR заявките, които променят продукционен код в `src/`, `open-sse/`, `electron/` или `bin/`, трябва да включват или актуализират тестове (Строго правило #8) | Да | -| `check:test-masking` | Променените тестови файлове не намаляват общия брой проверки и не добавят тавтологии `assert.ok(true)` | Да | -| `check:pr-evidence` | Описанието на PR заявката посочва доказателства от тестове/VPS за промяната (автоматизира Строго правило #18 чрез търсене в текста на PR заявката — ненадеждно, вижте списъка със задачи) | Да | +| Скрипт | Какво валидира | Блокираща | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | +| `check:pr-test-policy` | PR заявки, които променят производствен код в `src/`, `open-sse/`, `electron/` или `bin/`, трябва да включват или актуализират тестове (Твърдо правило №8) | Да | +| `check:test-masking` | Променените тестови файлове не намаляват общия брой проверки и не добавят тавтологии `assert.ok(true)` | Да | +| `check:pr-evidence` | Описанието на PR посочва доказателства от тестове/VPS за промяната (автоматизира Твърдо правило №18 чрез търсене в текста на PR — ненадеждно, вижте Backlog) | Да | ### Задача: `test-vitest` -Изпълнява се след `build`. При неуспех блокира сливането. +Изпълнява се след `build`. Блокира сливането при неуспех. -| Набор | Валидира | Блокиране | -| ---------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP сървър (110 инструмента), autoCombo, кеш — изпълнява се с vitest | Да | -| `test:vitest:ui` | Тестове на UI компоненти — изпълняват се с vitest | **Блокира** — вече съществуващите неуспешни тестове са изрично изключени във `vitest.config.ts`; новите неуспехи провалят задачата | +| Набор | Какво валидира | Блокираща | +| ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP сървър (110 инструмента), autoCombo, кеш — vitest runner | Да | +| `test:vitest:ui` | Тестове на UI компоненти — vitest runner | **Блокираща** — съществуващите неуспешни тестове са изрично изключени във `vitest.config.ts`; новите неуспехи провалят задачата | ### Нощни работни процеси (планирани, препоръчителни) -Те се изпълняват по cron график (и чрез `workflow_dispatch`), но никога при PR заявки. Всички имат препоръчителен характер. +Те се изпълняват по cron график (и чрез `workflow_dispatch`), никога при PR заявки. Всички са препоръчителни. -| Работен процес | Валидира | Блокиране | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | -| `nightly-property` | Тестове на свойства с fast-check, произволно начално число и голям брой изпълнения | **Препоръчително** | -| `nightly-resilience` | Проверка за нарастване на heap паметта, внедряване на хаотични откази, k6 тестове за натоварване/продължително натоварване | **Препоръчително** | -| `nightly-llm-security` | Защита срещу инжектиране с promptfoo (режим на блокиране) + garak сонди (пропускат се без тайна за доставчик) | **Препоръчително** | -| `nightly-schemathesis` | Размиващо тестване на OpenAPI договора (schemathesis) спрямо работещ OmniRoute чрез `docs/openapi.yaml` — открива нарушения на спецификацията / необработени грешки 500 (Фаза 8 B.4) | **Препоръчително** | -| `nightly-mutation` | Оценка от мутационно тестване със Stryker върху бързата линия за модулни тестове — оцелелите мутанти разкриват слаби проверки | **Препоръчително** | -| `nightly-compat` | Матрица за съвместимост с Node engine във всички поддържани диапазони на `engines.node` | **Препоръчително** | +| Работен процес | Какво валидира | Блокиращ | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `nightly-property` | fast-check тестове на свойства със случайно начално състояние + голям брой изпълнения | **Препоръчителен** | +| `nightly-resilience` | проверка за нарастване на heap паметта, инжектиране на хаотични повреди, k6 тестове за натоварване/продължително натоварване | **Препоръчителен** | +| `nightly-llm-security` | promptfoo защита срещу инжектиране (режим на блокиране) + garak сонди (пропускат се без тайна за доставчик) | **Препоръчителен** | +| `nightly-schemathesis` | fuzz тестване на OpenAPI договора (schemathesis) спрямо работещ OmniRoute чрез `docs/openapi.yaml` — открива нарушения на спецификацията / необработени грешки 500 (Фаза 8 B.4) | **Препоръчителен** | +| `nightly-mutation` | резултат от мутационно тестване със Stryker върху бързия набор от модулни тестове — оцелелите мутанти разкриват слаби проверки | **Препоръчителен** | +| `nightly-compat` | матрица за съвместимост на Node engine във всички поддържани диапазони на `engines.node` | **Препоръчителен** | --- @@ -536,43 +536,44 @@ Job-ът никога не прави push към `release/*` — човек mer --- -## Списък за рационализация (преглед на ROI — Фаза 9 Вълна 3) +## Натрупани задачи за рационализиране (преглед на ROI — Фаза 9, Вълна 3) -Този опис беше съгласуван с `ci.yml` на 2026-06-17 (предишната версия пропускаше +Този инвентар беше съпоставен с `ci.yml` на 2026-06-17 (в предходната версия бяха пропуснати `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Прегледът на ROI на съгласувания набор -идентифицира следните кандидати за рационализация. **Обединяванията са механични промени -по CI; превключванията/премахванията са решения по правилата, запазени за оператора.** Нищо -от посоченото по-долу все още не е приложено. - -**Също недокументирани по-горе** (препоръчителни, слаб сигнал): задачата `docs-lint` -(markdownlint + Vale, цялата задача е с `continue-on-error`) и самостоятелните работни процеси за сканиране -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` присъства в -`quality-baseline.json`, но не е свързано с блокиращ ограничител в `ci.yml` — метриката -в момента е изолирана. - -### Обединяване / премахване на дублирането (механично, с по-нисък риск) - -Всеки кандидат беше валидиран спрямо актуалното състояние на проверките на 2026-06-17 (доверявай се, но проверявай); -няколко „очевидни“ обединявания се оказаха прикриващи технически дълг и **не** могат да бъдат директно приложени. - -- **`check:docs-sync` се изпълнява два пъти** — самостоятелно в задачата `lint` и отново в `check:docs-all` (`docs-sync-strict`), както и в pre-commit куката на husky. ✅ **ГОТОВО** — самостоятелното извикване в `lint` е премахнато. -- **Сканиране за CVE** — ❌ **НЕ е подходящо за директно обединяване.** `audit:deps` приключва с грешка при всяка CVE с висока/критична тежест; `check:vuln-ratchet` (osv) се проваля само при _регресия_ спрямо базовото ниво (в момента 1 MODERATE). Семантиките са различни — премахването на `audit:deps` би премахнало абсолютната проверка за висока/критична тежест. Запазете и двете. -- **Откриване на цикли** — ❌ **НЕ е подходящо за директно обединяване.** `check:circular-deps` (dpdm) отчита **91 цикъла** (затова е препоръчителна проверка); тя не може да бъде направена блокираща, без те първо да бъдат отстранени, и има по-широк обхват от успешната, внимателно подбрана `check:cycles`. Запазете `check:cycles` като блокираща; отстраняването на 91-те цикъла на dpdm е самостоятелна задача. -- **Сложност** — ✅ **ГОТОВО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): едно обхождане с ESLint, броене по ruleId, така че базовите нива за цикломатична сложност + максимален брой редове и когнитивна сложност да останат независими; отделните `check:complexity` / `check:cognitive-complexity` се запазват за локално изпълнение с `--update`. -- **Защита срещу халюцинации за `/api`** — ✅ **ГОТОВО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): един опис на файловата система за `src/app/api`, като openapi-routes + docs-symbols продължават да докладват независимо; отделните проверки се запазват за локално изпълнение. -- **`check:node-runtime` се изпълнява в 11 задачи** — ⚠️ **ниска ROI.** Всяка е на отделен runner и проверката отнема <1s; общото спестяване е ~10s, срещу загубата на евтина защита за всяка задача. Не си струва сътресението. -- **`typecheck:noimplicit:core` в CI lint** — ✅ **премахнато от задачата lint** (беше препоръчителна проверка с `continue-on-error`); блокиращата повърхност за типовете е `typecheck:core` + `check:type-coverage`. Локалният скрипт е запазен. +`check:codeql-ratchet`, `check:pr-evidence`). Прегледът на ROI на съпоставения набор +идентифицира следните кандидати за рационализиране. **Обединяванията са механични промени +по CI; превключванията/премахванията са решения относно правилата, запазени за оператора.** +Нищо от посоченото по-долу все още не е приложено. + +**Също недокументирани по-горе** (консултативни, със слаб сигнал): задачата `docs-lint` +(markdownlint + Vale, цялата задача е с `continue-on-error`) и самостоятелните работни процеси +за сканиране `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` присъства в +`quality-baseline.json`, но не е свързана с блокиращ ограничител в `ci.yml` — понастоящем +метриката е осиротяла. + +### Обединяване / дедупликация (механично, по-нисък риск) + +Всеки кандидат беше валидиран спрямо актуалното състояние на проверките на 2026-06-17 +(доверявай се, но проверявай); оказа се, че няколко „очевидни“ обединявания прикриват +натрупани проблеми и **не** са директни заместители. + +- **`check:docs-sync` се изпълнява два пъти** — самостоятелно в задачата `lint` и отново в `check:docs-all` (`docs-sync-strict`), както и в husky pre-commit hook. ✅ **ГОТОВО** — самостоятелното извикване в `lint` е премахнато. +- **Сканиране за CVE** — ❌ **НЕ е чисто обединяване.** `audit:deps` прекратява с грешка при всяка CVE с висока/критична тежест; `check:vuln-ratchet` (osv) се проваля само при _регресия_ спрямо базовата стойност (в момента 1 MODERATE). Семантиката е различна — премахването на `audit:deps` би премахнало абсолютната проверка за висока/критична тежест. Запазете и двете. +- **Откриване на цикли** — ✅ **ГОТОВО** (#15159 G-01/G-02). Старият текст тук наричаше `check:cycles` „зелената, курирана“ проверка и обосноваваше запазването ѝ като блокираща с това, че `check:circular-deps` (dpdm) отчита 91 цикъла. Този зелен резултат беше **фалшиво зелен**: `check:cycles` сканираше 5 поддиректории (450 файла), съпоставяше само статични `import|export … from` и отхвърляше всеки спецификатор `@/` и `@omniroute/open-sse/`, така че не можеше да види циклите с динамичен импорт и псевдоними, които преобладаваха в хранилището. Поправено: проверката вече обхожда `src` + `open-sse` (5023 файла), събира спецификатори от TypeScript AST (така `import("…")` се отчита, а `typeof import("…")` в позиция на тип — не) и разрешава `paths` от tsconfig. Тя намира **14** цикъла, а не 0. Тъй като 14-те съществуващи цикъла не могат да бъдат поправени в PR за проверката, `check:cycles` вече е **ограничител** (`--ratchet`, таван `metrics.cycles.value = 14` в `quality-baseline.json`, `direction: down`) — блокира всяка _регресия_ и броят може само да намалява. CI изпълнява `npm run check:cycles:ratchet`. Постепенното намаляване върви с **A-01**. `check:circular-deps` (dpdm) остава консултативна като по-широко второ мнение. +- **Сложност** — ✅ **ГОТОВО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): едно обхождане с ESLint, броене по ruleId, така че базовите стойности за цикломатична сложност + максимален брой редове и когнитивна сложност да останат независими; отделните `check:complexity` / `check:cognitive-complexity` се запазват за локално `--update`. +- **Защита срещу халюцинации за `/api`** — ✅ **ГОТОВО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): един FS инвентар на `src/app/api`, openapi-routes + docs-symbols продължават да докладват независимо; отделните проверки се запазват за локални изпълнения. +- **`check:node-runtime` се изпълнява в 11 задачи** — ⚠️ **нисък ROI.** Всяка е на отделен runner и проверката отнема <1s; общото спестяване е ~10s, за сметка на загубата на евтина защита за всяка задача. Не си струва сътресението. +- **`typecheck:noimplicit:core` в CI lint** — ✅ **премахната от задачата lint** (беше консултативна с `continue-on-error`); блокиращата повърхност за типовете е `typecheck:core` + `check:type-coverage`. Локалният скрипт е запазен. ### Превключване / решение (правила на оператора) -- `check:openapi-security-tiers` (препоръчителна) — ❌ **НЕ може да бъде директно превключена.** Завършва с код 0, но предупреждава, че на няколко маршрута на `traffic-inspector` под `LOCAL_ONLY_API_PREFIXES` липсва анотацията `x-loopback-only: true`. Налагането ѝ изисква първо добавяне на тези анотации в `openapi.yaml`. -- `typecheck:noimplicit:core` (препоръчителна) — до голяма степен е покрита от блокиращия ограничител `check:type-coverage`. Превключете я към ограничител или премахнете излишното второ изпълнение на `tsc`. -- `test:vitest:ui` (вече **блокираща**) — съществуващите отпреди грешки са изрично изключени във `vitest.config.ts` с проследяващи коментари `// #8618`; новите грешки провалят задачата. -- `check:secrets` (gitleaks, блокиращ ограничител, замразен на 3 документирани фалшиво положителни резултата) — добавете трите в списъка с позволени, за да достигнете 0, или понижете проверката до препоръчителна. Припокрива се с вграденото сканиране за тайни на GitHub + `check:public-creds`. -- `check:pr-evidence` (блокираща, търси чрез grep в прозата на тялото на PR) — висок риск от фалшиво положителни резултати; премахването ѝ отслабва прилагането на строго правило №18, така че това е реално решение по правилата. -- `semgrep` (самостоятелна препоръчителна проверка) — припокрива се с CodeQL за категориите на OWASP; свържете базовото ѝ ниво с ограничител или я премахнете. +- `check:openapi-security-tiers` (консултативна) — ❌ **НЕ може да бъде чисто превключена.** Завършва с код 0, но предупреждава, че няколко маршрута на `traffic-inspector` под `LOCAL_ONLY_API_PREFIXES` нямат анотацията `x-loopback-only: true`. Налагането ѝ изисква първо тези анотации да бъдат добавени в `openapi.yaml`. +- `typecheck:noimplicit:core` (консултативна) — до голяма степен е покрита от блокиращия ограничител `check:type-coverage`. Превключете я към ограничител или премахнете излишното второ изпълнение на `tsc`. +- `test:vitest:ui` (вече **блокираща**) — съществуващите неуспешни тестове са изрично изключени във `vitest.config.ts` с проследяващи коментари `// #8618`; новите неуспешни тестове провалят задачата. +- `check:secrets` (gitleaks, блокиращ ограничител, фиксиран на 3 документирани фалшиво положителни резултата) — добавете трите към allowlist, за да достигне 0, или я понижете до консултативна. Припокрива се с вграденото сканиране на тайни в GitHub + `check:public-creds`. +- `check:pr-evidence` (блокираща, търси чрез grep в прозата на тялото на PR) — висок риск от фалшиво положителни резултати; премахването ѝ отслабва прилагането на Hard Rule #18, така че това е реално решение относно правилата. +- `semgrep` (самостоятелна консултативна) — припокрива се с CodeQL за категориите на OWASP; свържете базовата ѝ стойност с ограничител или я премахнете. --- diff --git a/docs/i18n/bg/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/bg/docs/compression/COMPRESSION_GUIDE.md index a4617b9af96d..32024e7cd2f8 100644 --- a/docs/i18n/bg/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/bg/docs/compression/COMPRESSION_GUIDE.md @@ -472,8 +472,7 @@ const { messages: aged, saved } = applyAging(messages, { В тяло с `messages`, заобикаляне на съдържанието (`shouldBypassCavemanOutputMode()` в `open-sse/services/compression/outputMode.ts`) проверява последните три съобщения и пропуска стиловете за целия ход, когато те съвпадат с неговите ключови думи за сигурност, необратимо действие, -изясняване или чувствителност към реда. Заобикалянето работи независимо от това какво е зададено -превключвателят **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) на таблото за управление. +изясняване или чувствителност към реда. Заобикалянето работи, докато превключвателят **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) на таблото за управление е включен, което е и стандартната настройка; при изключен превключвател избраните стилове се прилагат и на тези ходове. Когато заобикалянето позволи хода да премине, `placeSystemInstruction()` (същия файл), което никога не създава ново `messages[0]`, поставя блока в първото от следните, което намери: diff --git a/docs/i18n/bg/docs/reference/ENVIRONMENT.md b/docs/i18n/bg/docs/reference/ENVIRONMENT.md index fb43b7154e85..1b8b8776d87e 100644 --- a/docs/i18n/bg/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/bg/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Задължителни тайни -Те **трябва** да бъдат зададени преди първото стартиране. Без тях приложението или ще откаже да се стартира, или ще работи с несигурни стойности по подразбиране. - -| Променлива | Задължителна | По подразбиране | Изходен файл | Описание | -| ---------------------------- | ----------------------------- | ------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Да** | _(няма)_ | `src/lib/auth` | Подписва/проверява всички бисквитки за сесии на таблото за управление (JWT). Генерирайте с `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Да** | _(няма)_ | `src/lib/db/apiKeys.ts` | AES ключ за криптиране на стойностите на API ключовете при съхранение в SQLite. Генерирайте с `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Да** | `CHANGEME` | Скрипт за първоначална настройка | Задава първоначалната администраторска парола за таблото за управление (съвпада със стойността по подразбиране в `.env.example` — умишлено е очевидно несигурна, за да наложи промяна). **Променете я преди първата употреба.** След влизане я променете чрез Табло за управление → Настройки → Сигурност. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Да** (в продукционна среда) | _(не е зададена)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Споделена тайна за вътрешния WebSocket мост на Codex Responses. Удостоверява заявките към моста между WS препредаването на Electron/браузъра и OmniRoute. ⚠️ **ЗАДЪЛЖИТЕЛНА в продукционна среда — когато не е зададена, всички заявки към WS моста се отхвърлят.** Генерирайте с `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Не | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Изричен идентификатор за анулиране на кеша на service worker за PWA обвивката (#11779); първи във веригата за определяне на стойността. | -| `SOURCE_VERSION` | Не | _(не е зададена)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Втори във веригата — задава се от PaaS системи за изграждане (напр. от типа на Heroku) като внедрения commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Не | _(изведена)_ | `src/shared/components/PwaRegister.tsx` | Публична стойност от етапа на изграждане, която клиентът използва, за да регистрира `/sw.js?v=…`; извежда се от двете стойности по-горе, а след това от git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Не (автоматично) | _(автоматично при всяко стартиране)_ | `src/server/authz/policies/management.ts` | Тайна за всеки процес, доказваща, че довереният печат за IP адреса на отсрещната страна идва от собствения HTTP сървър на OmniRoute (`scripts/dev/peer-stamp.mjs`). Междинният софтуер за authz се доверява на локалността на заявката (ограничаване на маршрутите LOCAL_ONLY до loopback/LAN) само когато печатът съдържа този токен. Генерира се автоматично при всяко стартиране — оставете я незададена; задавайте фиксирана стойност само при конфигурации с множество процеси, които трябва да споделят печата. | -| `OMNIROUTE_TRUSTED_PROXIES` | Не | _(не е зададена)_ | `scripts/dev/peer-stamp.mjs` | Разделени със запетаи IP адреси или CIDR диапазони на обратни прокси сървъри, чиито `X-Forwarded-For` / `X-Real-IP` списъкът за разрешаване/забраняване на IP адреси може да приема за достоверни, в допълнение към loopback адресите, адресите от частни мрежи и периферните сървъри на Cloudflare, които винаги са доверени. Необходимо е само за прокси на друг публичен адрес; в противен случай филтърът оценява собствения адрес на това прокси. | +Незададените тайни, маркирани с **Да**, се генерират автоматично при първото стартиране (`JWT_SECRET`, `API_KEY_SECRET`) и се съхраняват в `server.env` в директорията за данни — задайте ги изрично, когато тази директория е ефимерна. Изключение е `OMNIROUTE_WS_BRIDGE_SECRET`: тя не се съхранява в `server.env` — входните точки на сървъра (entrypoints) генерират ефимерна стойност за всяко стартиране, която може да използва само мостът в самия процес, затова външните клиенти на моста трябва да я зададат изрично. + +| Променлива | Задължителна | По подразбиране | Изходен файл | Описание | +| ---------------------------- | ----------------------------- | ------------------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Да** | _(няма)_ | `src/lib/auth` | Подписва/проверява всички бисквитки за сесии на таблото за управление (JWT). Генерирайте с `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Да** | _(няма)_ | `src/lib/db/apiKeys.ts` | AES ключ за криптиране на стойностите на API ключовете при съхранение в SQLite. Генерирайте с `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Не | _(не е зададена)_ | `src/lib/auth/managementPassword.ts` | Задава паролата на таблото за управление при стартиране, докато все още няма запазена парола; игнорира се, след като парола е запазена. Инсталациите чрез npm и от изходен код копират `.env.example` в `.env`, така че стартират с публично известния му заместител `CHANGEME`: заменете го преди първото стартиране или задайте `INITIAL_PASSWORD=` (празна) в този `.env`, за да се създаде паролата в помощника за първоначална настройка при първо посещение — както прави и незададената стойност (по подразбиране в Docker образа). Запазена парола променяйте в Табло за управление → Настройки → Сигурност, или изпълнете `omniroute reset-password` (`node bin/reset-password.mjs` в копие на изходния код) с `DATA_DIR`, зададена към директорията за данни на сървъра. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Да** (в продукционна среда) | _(не е зададена)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Споделена тайна за вътрешния WebSocket мост на Codex Responses. Удостоверява заявките към моста между WS препредаването на Electron/браузъра и OmniRoute. ⚠️ **ЗАДЪЛЖИТЕЛНА в продукционна среда — когато не е зададена, всички заявки към WS моста се отхвърлят.** Генерирайте с `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Не | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Изричен идентификатор за анулиране на кеша на service worker за PWA обвивката (#11779); първи във веригата за определяне на стойността. | +| `SOURCE_VERSION` | Не | _(не е зададена)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Втори във веригата — задава се от PaaS системи за изграждане (напр. от типа на Heroku) като внедрения commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Не | _(изведена)_ | `src/shared/components/PwaRegister.tsx` | Публична стойност от етапа на изграждане, която клиентът използва, за да регистрира `/sw.js?v=…`; извежда се от двете стойности по-горе, а след това от git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Не (автоматично) | _(автоматично при всяко стартиране)_ | `src/server/authz/policies/management.ts` | Тайна за всеки процес, доказваща, че довереният печат за IP адреса на отсрещната страна идва от собствения HTTP сървър на OmniRoute (`scripts/dev/peer-stamp.mjs`). Междинният софтуер за authz се доверява на локалността на заявката (ограничаване на маршрутите LOCAL_ONLY до loopback/LAN) само когато печатът съдържа този токен. Генерира се автоматично при всяко стартиране — оставете я незададена; задавайте фиксирана стойност само при конфигурации с множество процеси, които трябва да споделят печата. | +| `OMNIROUTE_TRUSTED_PROXIES` | Не | _(не е зададена)_ | `scripts/dev/peer-stamp.mjs` | Разделени със запетаи IP адреси или CIDR диапазони на обратни прокси сървъри, чиито `X-Forwarded-For` / `X-Real-IP` списъкът за разрешаване/забраняване на IP адреси може да приема за достоверни, в допълнение към loopback адресите, адресите от частни мрежи и периферните сървъри на Cloudflare, които винаги са доверени. Необходимо е само за прокси на друг публичен адрес; в противен случай филтърът оценява собствения адрес на това прокси. | ### Команди за генериране @@ -217,38 +217,38 @@ OmniRoute използва **SQLite** (чрез `better-sqlite3`) за цяло ## 4. Сигурност и удостоверяване -| Променлива | Стойност по подразбиране | Файл източник | Описание | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Сол, комбинирана с хардуерни идентификатори за създаване на машинен отпечатък. Променяйте я за всяко внедряване с цел изолация. | -| `OMNIROUTE_CLI_SALT` | _(не е зададена = произволна сол за всяка инсталация, съхранена в `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC сол за извеждане на локалния токен за удостоверяване на CLI. Задаването на тази стойност подменя всички CLI токени на машината и винаги има предимство пред съхранената сол. Вижте `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Задава флага `Secure` на сесийните бисквитки. **Трябва да бъде `true`** при работа зад HTTPS. | -| `REQUIRE_API_KEY` | `false` | API междинен софтуер | Когато е `true`, всички прокси заявки към `/v1/*` трябва да включват валиден API ключ. **Този флаг не ограничава `GET /v1/models`**, който вместо това следва режима за вход в таблото за управление (`requireAuthForModels`) — следователно отговор `401` от `/v1/models` НЕ означава, че изводът е защитен. Вижте `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Позволява разкриването на пълните стойности на API ключовете в потребителския интерфейс на таблото за управление. Конфигурира се чрез флаговете за функционалност в таблото за управление; представлява риск за сигурността при споделени инстанции. | -| `NO_LOG_API_KEY_IDS` | _(празно)_ | `src/lib/compliance/index.ts` | Разделени със запетаи идентификатори на API ключове, за които не се записват журнали на заявките (съответствие с GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(не е зададена = неограничено)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервен дневен бюджет за заявки, прилаган към API ключове, чиято колона `rate_limits` е null. Незададена или празна стойност: без неявно ограничение (#2289, #11017). `0` означава същото (неограничено). Положително цяло число N активира N/ден, 5N/седмица, 20N/месец. Неправилно форматираните непразни стойности използват предишните интервали от 1000/ден, 5000/седмица, 20000/месец. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Максимално допустим размер на тялото на заявката. Отхвърля полезни товари, надвишаващи това ограничение. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Реалните тела на заявки с размер, равен на или по-голям от този праг, придобиват атомарния локален за процеса достъп за тежки операции преди анализирането на JSON (път BYTE, включително `POST /v1/responses`). Използва същия механизъм за продължаване при достатъчен свободен ресурс от [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), както при структурно тежките заявки; все пак е ограничено от `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), така че [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) да не се повтори. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Твърд лимит за маршрута за чат, прилаган спрямо байтовете, прочетени по време на ограниченото приемане, включително при заявки с липсващ, невалиден или неверен `Content-Length`; при превишаване се връща `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(не е зададено — няма лимит за броя заявки)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** този наследен лимит за БРОЯ заявки вече се прилага само когато е изрично зададен. Когато не е зададен (по подразбиране), приемането на тежки чат заявки вместо това се управлява от `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — автоматично изчислен бюджет в БАЙТОВЕ, оразмерен според действителния лимит на паметта на процеса в рамките на **един процес** (една V8 динамична памет). Две припокриващи се заявки към `/v1/responses` с ~750 хил. токена сриват динамични памети от ~12 GiB (#7849) — това е предупреждение за **бюджета на паметта**, а не твърд продуктов максимум от 2. Изправен процес (с динамична памет под коефициента за отхвърляне) МОЖЕ да допуска повече едновременни дълги заявки към `/v1/responses` чрез `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десетки дълготрайни SSE клиенти (40–50) зависят от динамичната памет + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не от „максимум 2“. Безразборното увеличаване на тази стойност с цел „използване на хоста“ отново води до #7849. Умножавайте **динамичните памети** с **N независими `DATA_DIR`** (#11024); никога не използвайте `replicas>1` с един SQLite файл. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(автоматично изчислено)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** замества автоматично изчисления бюджет в байтове за приемане (25% от по-строгия лимит на паметта между V8 и cgroup, разделени на 8-кратното временно увеличаване). Изчислените и изрично зададените стойности се ограничават до диапазона 8 MiB–2 GiB. Тяло, по-голямо от ефективния бюджет, се отхвърля незабавно с `413 body_exceeds_budget`; конкуренцията между тела, всяко от които може да бъде обслужено самостоятелно, продължава да връща позволяваща повторен опит грешка `503`. 40–50 едновременни дълготрайни SSE клиенти зависят от този бюджет + динамичната памет, а не от твърд „максимум 2“. Проверете `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` в `/api/monitoring/health`, преди да настройвате. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Коефициент за отхвърляне при натоварване на динамичната памет (`heapUsed / heap_size_limit`) за приемане на тежки заявки по БАЙТОВЕ и СТРУКТУРА (#10183, #10268, #10437). Едновременна тежка заявка над `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` се отхвърля с позволяваща повторен опит грешка `503` само когато динамичната памет СЪЩО е достигнала или надвишила този коефициент; при нормално състояние на динамичната памет тя се допуска чрез допълнителния капацитет за нормално състояние. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (по подразбиране `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ограничен допълнителен капацитет за бързия път при нормално състояние на динамичната памет (#10437) както за СТРУКТУРА, така и за БАЙТОВЕ (`admitChatRequest`, включително тела ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без това ограничение всяка заявка при натоварена, но нормално функционираща динамична памет заобикаляше механизма за приемане без никакъв горен лимит. След като толкова едновременни разрешения станат активни чрез пътя за нормално състояние на динамичната памет, следващите заявки при натоварване преминават към СЪЩИЯ път с ограничено изчакване/отхвърляне, използван при действително натоварване на динамичната памет. `0` изключва напълно заобикалянето. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Брой съобщения, при който заявка за чат се класифицира като тежка дори когато тялото ѝ е под прага в байтове. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Брой инструменти, при който заявка за чат се класифицира като тежка дори когато тялото ѝ е под прага в байтове. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Консервативна оценка на броя токени въз основа на размера на низа, при която заявката се класифицира като тежка; това е приблизителна оценка на разхода за приемане, а не токенизацията за таксуване на доставчика. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (изключено) | `src/shared/middleware/chatBodyAdmission.ts` | Незадължителен лимит за историята на чата, който се включва изрично. По подразбиране е изключен: броят съобщения е политика на внедряването, а не универсално свойство на заявката, и налагането на лимит тук отхвърля разговорите с окончателен отговор `413`, преди конвейерът за компресиране да може да ги направи обслужваеми. Нарастването на динамичната памет се ограничава от `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` и отхвърлянето при натоварване на динамичната памет. Задайте положителна стойност при внедрявания с ограничена памет, които се нуждаят от твърда горна граница; тогава при превишаване се връща структуриран отговор `413`, изискващ свиване. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Твърд лимит за непоточно предаван отговор от системата нагоре по веригата, който се буферира изцяло в паметта. При превишаването му четецът от системата нагоре по веригата се отменя и заявката се прекратява незабавно с грешка, вместо да се разраства неограничен низ до изчерпване на динамичната памет. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Максимален брой байтове по мрежата, препращани от заглавките на отговора от източника. Когато бюджетът бъде надвишен, заглавките с по-нисък приоритет (напр. персонализирани `x-codex-*`, `x-oai-request-id`) се премахват, за да се спазят обичайните ограничения за размера на заглавките на обратните проксита. Задайте по-висока стойност, за да препращате повече метаданни от източника за сметка на по-голям размер на заглавките на отговора. | -| `CORS_ORIGIN` | _(не е зададено)_ | `src/server/cors/origins.ts` | Остарял списък с разрешени източници за CORS, поддържащ само един източник. За нови внедрявания предпочитайте `CORS_ALLOWED_ORIGINS`. CORS е предназначен само за браузърни API клиенти от различен източник; удостоверените операции за запис в таблото за управление вместо това използват заявки от същия източник и обвързана със сесията CSRF защита. | -| `CORS_ALLOWED_ORIGINS` | _(не е зададено)_ | `src/server/cors/origins.ts` | Разделен със запетаи списък с разрешени източници за CORS. Не се изпраща заместващ символ, освен ако `CORS_ALLOW_ALL=true` не е изрично конфигурирано. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Авариен механизъм само за среди за разработка, който отразява всеки браузърен `Origin`. Не го активирайте в споделени или продукционни внедрявания. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Блокирайте извикванията към доставчици, насочени към частни/loopback/link-local диапазони от IP адреси. Деактивирайте само в изолирани тестови среди. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Разрешава URL адреси на доставчици, сочещи към частни/локални мрежи (localhost, 192.168.x.x, 10.x.x.x и т.н.). **ИЗИСКВА СЕ за самостоятелно хоствани доставчици** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Когато е `false`, таблото за управление отхвърля валидирането на локални URL адреси. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Разрешава добавянето/валидирането на доставчици на локални/частни адреси (127.0.0.1, localhost, LAN, частни диапазони) — ограничено до пътя за валидиране на доставчика. **По подразбиране е `true`** (с приоритет на локалната среда); задайте `false`, за да наложите строго блокиране, допускащо само публични адреси. Крайните точки за облачни метаданни (169.254.169.254, metadata.google.internal) остават блокирани независимо от настройката. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Позволява на маршрутите `/v1/audio/*` (транскрипции, реч, преводи) да използват съвместим с OpenAI възел на доставчик, хостван извън localhost. Изключено по подразбиране — насочването на аудио към отдалечен хост променя изходящата идентичност и трябва да бъде изрично решение на оператора. Loopback/частните възли (localhost, 127.0.0.1, 172.16-31.x) винаги са разрешени и не се засягат. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Позволява на `POST /v1/rerank` (и loopback стъпката за пренареждане на машината за памет) да използва съвместим с OpenAI възел на доставчик, хостван извън localhost — машина в LAN или Tailscale партньор, изпълняващ TEI, Infinity, vLLM и т.н. Изключено по подразбиране — насочването към отдалечен хост променя изходящата идентичност и трябва да бъде изрично решение на оператора. Loopback възлите (localhost, 127.0.0.1, 172.16-31.x) винаги са разрешени и не се засягат. Отдалечените възли трябва също да отговарят на правилата за изходящи URL адреси на доставчика (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); към хостове за облачни метаданни никога не се маршрутизира. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Когато OIDC е активиран, деактивира влизането с парола, така че потребителите да могат да се удостоверяват само чрез OIDC Single Sign-On. Приема се и краткият псевдоним `OIDC_DISABLE_PASSWORD_LOGIN`; функционалният флаг със същия ключ в таблото за управление е с предимство. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Кратък псевдоним на `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Променлива | Стойност по подразбиране | Файл източник | Описание | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Сол, комбинирана с хардуерни идентификатори за създаване на машинен отпечатък. Променяйте я за всяко внедряване с цел изолация. | +| `OMNIROUTE_CLI_SALT` | _(не е зададена = произволна сол за всяка инсталация, съхранена в `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC сол за извеждане на локалния токен за удостоверяване на CLI. Задаването на тази стойност подменя всички CLI токени на машината и винаги има предимство пред съхранената сол. Вижте `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Задава флага `Secure` на сесийните бисквитки. **Трябва да бъде `true`** при работа зад HTTPS. | +| `REQUIRE_API_KEY` | `false` | API междинен софтуер | Когато е `true`, всички прокси заявки към `/v1/*` трябва да включват валиден API ключ. **Този флаг не ограничава `GET /v1/models`**, който вместо това следва режима за вход в таблото за управление (`requireAuthForModels`) — следователно отговор `401` от `/v1/models` НЕ означава, че изводът е защитен. Вижте `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Позволява разкриването на пълните стойности на API ключовете в потребителския интерфейс на таблото за управление. Конфигурира се чрез флаговете за функционалност в таблото за управление; представлява риск за сигурността при споделени инстанции. | +| `NO_LOG_API_KEY_IDS` | _(празно)_ | `src/lib/compliance/index.ts` | Разделени със запетаи идентификатори на API ключове, за които не се записват журнали на заявките (съответствие с GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(не е зададена = неограничено)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервен дневен бюджет за заявки, прилаган към API ключове, чиято колона `rate_limits` е null. Незададена или празна стойност: без неявно ограничение (#2289, #11017). `0` означава същото (неограничено). Положително цяло число N активира N/ден, 5N/седмица, 20N/месец. Неправилно форматираните непразни стойности използват предишните интервали от 1000/ден, 5000/седмица, 20000/месец. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Максимално допустим размер на тялото на заявката. Отхвърля полезни товари, надвишаващи това ограничение. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Реалните тела на заявки с размер, равен на или по-голям от този праг, придобиват атомарния локален за процеса достъп за тежки операции преди анализирането на JSON (път BYTE, включително `POST /v1/responses`). Използва същия механизъм за продължаване при достатъчен свободен ресурс от [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), както при структурно тежките заявки; все пак е ограничено от `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), така че [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) да не се повтори. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Твърд лимит за маршрута за чат, прилаган спрямо байтовете, прочетени по време на ограниченото приемане, включително при заявки с липсващ, невалиден или неверен `Content-Length`; при превишаване се връща `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(не е зададено — няма лимит за броя заявки)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** този наследен лимит за БРОЯ заявки вече се прилага само когато е изрично зададен. Когато не е зададен (по подразбиране), приемането на тежки чат заявки вместо това се управлява от `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — автоматично изчислен бюджет в БАЙТОВЕ, оразмерен според действителния лимит на паметта на процеса в рамките на **един процес** (една V8 динамична памет). Две припокриващи се заявки към `/v1/responses` с ~750 хил. токена сриват динамични памети от ~12 GiB (#7849) — това е предупреждение за **бюджета на паметта**, а не твърд продуктов максимум от 2. Изправен процес (с динамична памет под коефициента за отхвърляне) МОЖЕ да допуска повече едновременни дълги заявки към `/v1/responses` чрез `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десетки дълготрайни SSE клиенти (40–50) зависят от динамичната памет + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не от „максимум 2“. Безразборното увеличаване на тази стойност с цел „използване на хоста“ отново води до #7849. Умножавайте **динамичните памети** с **N независими `DATA_DIR`** (#11024); никога не използвайте `replicas>1` с един SQLite файл. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(автоматично изчислено)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** замества автоматично изчисления бюджет в байтове за приемане (25% от по-строгия лимит на паметта между V8 и cgroup, разделени на 8-кратното временно увеличаване). Изчислените и изрично зададените стойности се ограничават до диапазона 8 MiB–2 GiB. Тяло, по-голямо от ефективния бюджет, се отхвърля незабавно с `413 body_exceeds_budget`; конкуренцията между тела, всяко от които може да бъде обслужено самостоятелно, продължава да връща позволяваща повторен опит грешка `503`. 40–50 едновременни дълготрайни SSE клиенти зависят от този бюджет + динамичната памет, а не от твърд „максимум 2“. Проверете `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` в `/api/monitoring/health`, преди да настройвате. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Коефициент за отхвърляне при натоварване на динамичната памет (`heapUsed / heap_size_limit`) за приемане на тежки заявки по БАЙТОВЕ и СТРУКТУРА (#10183, #10268, #10437). Едновременна тежка заявка над `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` се отхвърля с позволяваща повторен опит грешка `503` само когато динамичната памет СЪЩО е достигнала или надвишила този коефициент; при нормално състояние на динамичната памет тя се допуска чрез допълнителния капацитет за нормално състояние. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (по подразбиране `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ограничен допълнителен капацитет за бързия път при нормално състояние на динамичната памет (#10437) както за СТРУКТУРА, така и за БАЙТОВЕ (`admitChatRequest`, включително тела ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без това ограничение всяка заявка при натоварена, но нормално функционираща динамична памет заобикаляше механизма за приемане без никакъв горен лимит. След като толкова едновременни разрешения станат активни чрез пътя за нормално състояние на динамичната памет, следващите заявки при натоварване преминават към СЪЩИЯ път с ограничено изчакване/отхвърляне, използван при действително натоварване на динамичната памет. `0` изключва напълно заобикалянето. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Брой съобщения, при който заявка за чат се класифицира като тежка дори когато тялото ѝ е под прага в байтове. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Брой инструменти, при който заявка за чат се класифицира като тежка дори когато тялото ѝ е под прага в байтове. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Консервативна оценка на броя токени въз основа на размера на низа, при която заявката се класифицира като тежка; това е приблизителна оценка на разхода за приемане, а не токенизацията за таксуване на доставчика. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (изключено) | `src/shared/middleware/chatBodyAdmission.ts` | Незадължителен лимит за историята на чата, който се включва изрично. По подразбиране е изключен: броят съобщения е политика на внедряването, а не универсално свойство на заявката, и налагането на лимит тук отхвърля разговорите с окончателен отговор `413`, преди конвейерът за компресиране да може да ги направи обслужваеми. Нарастването на динамичната памет се ограничава от `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` и отхвърлянето при натоварване на динамичната памет. Задайте положителна стойност при внедрявания с ограничена памет, които се нуждаят от твърда горна граница; тогава при превишаване се връща структуриран отговор `413`, изискващ свиване. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Твърд лимит за непоточно предаван отговор от системата нагоре по веригата, който се буферира изцяло в паметта. При превишаването му четецът от системата нагоре по веригата се отменя и заявката се прекратява незабавно с грешка, вместо да се разраства неограничен низ до изчерпване на динамичната памет. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Максимален брой байтове по мрежата, препращани от заглавките на отговора от източника. Когато бюджетът бъде надвишен, заглавките с по-нисък приоритет (напр. персонализирани `x-codex-*`, `x-oai-request-id`) се премахват, за да се спазят обичайните ограничения за размера на заглавките на обратните проксита. Задайте по-висока стойност, за да препращате повече метаданни от източника за сметка на по-голям размер на заглавките на отговора. | +| `CORS_ORIGIN` | _(не е зададено)_ | `src/server/cors/origins.ts` | Остарял списък с разрешени източници за CORS, поддържащ само един източник. За нови внедрявания предпочитайте `CORS_ALLOWED_ORIGINS`. CORS е предназначен само за браузърни API клиенти от различен източник; удостоверените операции за запис в таблото за управление вместо това използват заявки от същия източник и обвързана със сесията CSRF защита. | +| `CORS_ALLOWED_ORIGINS` | _(не е зададено)_ | `src/server/cors/origins.ts` | Разделен със запетаи списък с разрешени източници за CORS. Не се изпраща заместващ символ, освен ако `CORS_ALLOW_ALL=true` не е изрично конфигурирано. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Авариен механизъм само за среди за разработка, който отразява всеки браузърен `Origin`. Не го активирайте в споделени или продукционни внедрявания. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Остарял псевдоним (legacy alias). Превключвателят му в таблото за управление (заместване през базата данни) се чете преди тази стойност на средата; `false`, `0`, `no` или `off` в която и да е от двете изключва проверките на хостовете по същия начин, по който го прави `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, дори когато превключвателят на този флаг в таблото е запазен изключен; другите стойности оставят защитата на двата флага по-долу. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Изключва проверките на хостовете на защитата за изходящи URL адреси, включително блокирането на облачни метаданни (целият 169.254.0.0/16, metadata.google.internal), по пътищата на доставчика, които следват режима на защитата: валидиране на URL адреси, откриване на модели, базови URL адреси на възлите на доставчика (включително отдалечени rerank възли) и тестът за резервен прокси, както и други изходящи пътища в близост до доставчика, като извличането на отдалечени изображения/медии. Позволява също частни webhook цели, с изключение на крайните точки за облачни метаданни/link-local, които остават блокирани даже при включен флаг. Заявките за чат, разпределени чрез `BaseExecutor.execute()`, запазват проверката си за облачни метаданни. Локалните доставчици работят със стойностите по подразбиране: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (по подразбиране `true`) вече разрешава локални и LAN URL адреси по пътищата за валидиране, откриване и възлите на доставчика; тестът за резервен прокси и частните webhook цели се съобразяват само с този флаг и блокират локални/LAN хостове, докато той е изключен. Отменя `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Стойност, запазена от превключвателя в таблото за управление, има предимство пред тази променлива. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Разрешава URL адреси на доставчици на локални/частни адреси (127.0.0.1, localhost, LAN, частни диапазони) за валидиране на доставчици, откриване на модели, чат и другите изходящи пътища в близост до доставчика, управлявани от режима на защитата. **По подразбиране е `true`** (с приоритет на локалната среда): тогава защитата блокира крайните точки за облачни метаданни — целият 169.254.0.0/16 плюс известните имена на хостове за метаданни (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Задайте `false` за режим само с публични адреси, който блокира и частните, и loopback хостовете. Защитата проверява името на хоста или IP литерала както е записано, преди всяко DNS търсене; в режим само с публични адреси обвивката за безопасно извличане допълнително разрешава името на хоста и отхвърля всеки частен отговор. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` изключва тези проверки по пътищата, изброени в неговия ред. Вградените локални доставчици, като LM Studio, Ollama, vLLM, Llamafile и Triton, прескачат всички проверки на хостове за валидиране на ключове и чат. Стойност, запазена от превключвателя в таблото за управление, има предимство пред тази променлива. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Позволява на маршрутите `/v1/audio/*` (транскрипции, реч, преводи) да използват съвместим с OpenAI възел на доставчик, хостван извън localhost. Изключено по подразбиране — насочването на аудио към отдалечен хост променя изходящата идентичност и трябва да бъде изрично решение на оператора. Loopback/частните възли (localhost, 127.0.0.1, 172.16-31.x) винаги са разрешени и не се засягат. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Позволява на `POST /v1/rerank` (и loopback стъпката за пренареждане на машината за памет) да използва съвместим с OpenAI възел на доставчик, хостван извън localhost — машина в LAN или Tailscale партньор, изпълняващ TEI, Infinity, vLLM и т.н. Изключено по подразбиране — насочването към отдалечен хост променя изходящата идентичност и трябва да бъде изрично решение на оператора. Loopback възлите (localhost, 127.0.0.1, 172.16-31.x) винаги са разрешени и не се засягат. Базовият URL адрес на отдалечен възел трябва също да отговаря на правилата за изходящи URL адреси на доставчика (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Когато OIDC е активиран, деактивира влизането с парола, така че потребителите да могат да се удостоверяват само чрез OIDC Single Sign-On. Приема се и краткият псевдоним `OIDC_DISABLE_PASSWORD_LOGIN`; функционалният флаг със същия ключ в таблото за управление е с предимство. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Кратък псевдоним на `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Контролен списък за подсилване на сигурността diff --git a/docs/i18n/bn/CONTRIBUTING.md b/docs/i18n/bn/CONTRIBUTING.md index 306b5911076e..16d682b42b59 100644 --- a/docs/i18n/bn/CONTRIBUTING.md +++ b/docs/i18n/bn/CONTRIBUTING.md @@ -387,20 +387,20 @@ docs/ # Documentation ## পুল রিকোয়েস্ট চেকলিস্ট -- [ ] টেস্টসমূহ পাস করে (`npm test`) +- [ ] টেস্ট পাস করে (`npm test`) - [ ] লিন্টিং পাস করে (`npm run lint`) - [ ] বিল্ড সফল হয় (`npm run build`) - [ ] নতুন পাবলিক ফাংশন ও ইন্টারফেসের জন্য TypeScript টাইপ যোগ করা হয়েছে - [ ] কোনো হার্ডকোড করা সিক্রেট বা ফলব্যাক মান নেই -- [ ] পাবলিক আপস্ট্রিম ক্রেডেনশিয়ালগুলো `resolvePublicCred()`-এর মাধ্যমে এম্বেড করা হয়েছে ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) দেখুন), কখনোই লিটারেল হিসেবে নয় -- [ ] ত্রুটির রেসপন্সগুলো `buildErrorBody()` / `sanitizeErrorMessage()`-এর মাধ্যমে রাউট করা হয় — রেসপন্স বডিতে কোনো অপরিশোধিত স্ট্যাক ট্রেস নেই ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) দেখুন) -- [ ] শেল কমান্ডগুলো (`exec` / `spawn`) রানটাইম মান স্ট্রিং ইন্টারপোলেশনের মাধ্যমে নয়, `env`-এর মাধ্যমে পাস করে +- [ ] পাবলিক আপস্ট্রিম ক্রেডেনশিয়াল `resolvePublicCred()`-এর মাধ্যমে এম্বেড করা হয়েছে ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) দেখুন), কখনোই লিটারেল হিসেবে নয় +- [ ] ত্রুটির রেসপন্স `buildErrorBody()` / `sanitizeErrorMessage()`-এর মাধ্যমে রাউট করা হয় — রেসপন্স বডিতে কোনো অপরিশোধিত স্ট্যাক ট্রেস নেই ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) দেখুন) +- [ ] শেল কমান্ড (`exec` / `spawn`) রানটাইম মান স্ট্রিং ইন্টারপোলেশনের মাধ্যমে নয়, `env`-এর মাধ্যমে পাস করে - [ ] সব ইনপুট Zod স্কিমা দিয়ে যাচাই করা হয়েছে -- [ ] ব্যবহারকারীর দৃশ্যমান পরিবর্তনের জন্য `changelog.d/{features|fixes|maintenance}/-.md`-এর অধীনে চেঞ্জলগ **ফ্র্যাগমেন্ট** যোগ করা হয়েছে ([`changelog.d/README.md`](./changelog.d/README.md) দেখুন) — সরাসরি `CHANGELOG.md` সম্পাদনা করবেন **না**; রিলিজের সময় ফ্র্যাগমেন্টগুলো একত্রিত করা হয় এবং PR-গুলোর মধ্যে কখনোই দ্বন্দ্ব হয় না +- [ ] ব্যবহারকারীর দৃশ্যমান পরিবর্তনের জন্য `changelog.d/{features|fixes|maintenance}/-.md`-এর অধীনে চেঞ্জলগ **ফ্র্যাগমেন্ট** যোগ করা হয়েছে ([`changelog.d/README.md`](./changelog.d/README.md) দেখুন) — `CHANGELOG.md` সরাসরি সম্পাদনা করবেন **না**; রিলিজের সময় ফ্র্যাগমেন্টগুলো একত্রিত করা হয় এবং PR-গুলোর মধ্যে কখনো দ্বন্দ্ব সৃষ্টি করে না - [ ] ডকুমেন্টেশন হালনাগাদ করা হয়েছে (প্রযোজ্য হলে) -- [ ] নতুন কোনো CodeQL / Secret-Scanning সতর্কতা খোলা হয়নি, অথবা প্রতিটি সতর্কতা প্রাসঙ্গিক `docs/security/` ডকুমেন্ট উল্লেখ করে কারিগরি যৌক্তিকতাসহ খারিজ করা হয়েছে -- [ ] যেসব রাউট চাইল্ড প্রসেস চালু করে (`/api/mcp/`, `/api/cli-tools/runtime/`), সেগুলো `src/server/authz/routeGuard.ts`-এ `isLocalOnlyPath()` হিসেবে শ্রেণিবদ্ধ করা হয়েছে — [কঠোর নিয়ম #15](docs/security/ROUTE_GUARD_TIERS.md) দেখুন -- [ ] কমিট মেসেজে কোনো `Co-Authored-By` ট্রেলার নেই — কমিটগুলো অবশ্যই কেবল রিপোজিটরি মালিকের Git পরিচয়ের অধীনে প্রদর্শিত হতে হবে (কঠোর নিয়ম #16) +- [ ] কোনো নতুন CodeQL / Secret-Scanning সতর্কতা খোলা হয়নি, অথবা প্রতিটি সতর্কতা প্রাসঙ্গিক `docs/security/` ডকুমেন্ট উল্লেখ করে কারিগরি যৌক্তিকতাসহ খারিজ করা হয়েছে +- [ ] চাইল্ড প্রসেস চালু করা রাউটগুলো (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts`-এ `isLocalOnlyPath()` হিসেবে শ্রেণিবদ্ধ করা হয়েছে — [কঠোর নিয়ম #15](docs/security/ROUTE_GUARD_TIERS.md) দেখুন +- [ ] কমিট মেসেজে AI/বটের `Co-authored-by` ট্রেলার নেই (কঠোর নিয়ম #16) — যেসব মানব সহযোগীর কাজ পুনর্ব্যবহার করা হয়েছে, তাঁদের স্ট্যান্ডার্ড `Co-authored-by: Name ` ট্রেলার দিয়ে কৃতিত্ব দেওয়া হয়েছে --- diff --git a/docs/i18n/bn/docs/architecture/QUALITY_GATES.md b/docs/i18n/bn/docs/architecture/QUALITY_GATES.md index 23a574b47790..b10c014124a9 100644 --- a/docs/i18n/bn/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/bn/docs/architecture/QUALITY_GATES.md @@ -19,261 +19,253 @@ ### প্রার্থী গ্রহণ -CI এবং Quality Gates ওয়ার্কফ্লো প্রতিটি একটি স্থিতিশীল সিদ্ধান্ত প্রকাশ করে: `Gate / CI` এবং -`Gate / Quality`। এগুলোর সংস্করণযুক্ত গ্রহণ নীতিতে প্রতিটি আপস্ট্রিম জবকে -আবশ্যিক বা পরামর্শমূলক হিসেবে তালিকাভুক্ত করা হয়। প্রযোজ্য কোনো আবশ্যিক জব অবশ্যই সফল হতে হবে: +CI এবং Quality Gates ওয়ার্কফ্লো—প্রতিটি একটি স্থিতিশীল রায় প্রকাশ করে: `Gate / CI` এবং +`Gate / Quality`। এগুলোর সংস্করণযুক্ত গ্রহণনীতি প্রতিটি আপস্ট্রিম জবকে +আবশ্যিক বা পরামর্শমূলক হিসেবে তালিকাভুক্ত করে। প্রযোজ্য কোনো আবশ্যিক জবকে অবশ্যই সফল হতে হবে: অনুপস্থিত, বাতিল, এড়িয়ে যাওয়া, অপেক্ষমাণ এবং অজানা ফলাফল PASS প্রতিষ্ঠা করতে পারে না। একটি বৈধ -শুধু-ডক্স বা শুধু-ক্যাটালগ শ্রেণিবিন্যাস কোনো কোড লেনকে অপ্রযোজ্য করতে পারে; +শুধু-ডকস বা শুধু-ক্যাটালগ শ্রেণিবিন্যাস কোনো কোড লেনকে অপ্রযোজ্য করতে পারে; একটি খসড়া PR গ্রহণযোগ্য প্রার্থী নয়। একটি `hotfix` লেবেল প্রমাণের আবশ্যকতা মওকুফ করে না। উভয় ওয়ার্কফ্লোই PR এবং main/release ব্রাঞ্চে push, manual dispatch এবং -merge-group ইভেন্ট অন্তর্ভুক্ত করে। Push, dispatch এবং merge-group সম্পূর্ণ নির্বাচন চালায়। Fork -এবং merge group সেইসব জবের জন্য hosted runner ব্যবহার করে, যেগুলো অন্যথায় self-hosted +merge-group ইভেন্ট কভার করে। Push, dispatch এবং merge-group সম্পূর্ণ নির্বাচন চালায়। Fork +এবং merge group এমন জবগুলোর জন্য hosted runner ব্যবহার করে, যেগুলো অন্যথায় self-hosted runner নির্বাচন করে; রোলআউটের আগে পর্যাপ্ত hosted capacity যাচাই করতে হবে। প্রতিটি JSON রসিদ checked-out SHA, workflow run এবং attempt শনাক্ত করে। -CLI একটি checkout/event SHA অমিল প্রত্যাখ্যান করে। Workflow test-গুলো policy membership-কে +CLI একটি checkout/event SHA অমিল প্রত্যাখ্যান করে। Workflow test নীতির সদস্যপদকে verdict job-এর `needs` তালিকার সঙ্গে আবদ্ধ করে, যাতে কোনো নতুন বা অপসারিত lane নীরবে অদৃশ্য হতে না পারে। -রসিদগুলো তাদের নিজস্ব workflow কভার করে; publication, deployment অথবা বিদ্যমান কোনো +রসিদগুলো নিজেদের workflow কভার করে, publication, deployment অথবা বিদ্যমান advisory scanner-এর অভ্যন্তরীণ কার্যপ্রণালী নয়। Branch rule-এ উভয় check name সক্রিয় করা একটি পৃথক প্রশাসনিক পরিবর্তন; এই job-গুলো যোগ করলেই কোনো branch সুরক্ষিত হয় না। ### স্ট্যাটিক স্ক্যান ইনভেন্টরি -সংস্করণযুক্ত npm-alias inventory এবং static-scan membership -`config/quality/gate-manifest.json`-এ রয়েছে। `package.json`-এর বিপরীতে -script name এবং সঠিক command যাচাই করতে `npm run check:gate-manifest` চালান; সংযোজন, অপসারণ এবং -command drift local hook এবং CI-এর change-classification job উভয়কেই ব্যর্থ করে। -কোনো alias একটি workflow job, matrix instance বা test case নয়: এই সংখ্যাগুলোকে -বিনিময়যোগ্য হিসেবে উপস্থাপন করা যাবে না। - -নির্বাচিত alias-গুলো এক্সিকিউট না করেই পরিদর্শন করতে `npm run quality:scan -- --list` অথবা -`npm run quality:scan:fast -- --list` ব্যবহার করুন। Runner npm entrypoint চালায়, -তাই এর runtime (যেখানে কনফিগার করা আছে সেখানে Bun-সহ) সংরক্ষিত থাকে। -Manifest-এ এই profile-গুলোর বাইরের alias-গুলোকে পৃথকভাবে আহ্বানকৃত হিসেবে রেকর্ড করা হয়, এবং +সংস্করণযুক্ত npm-alias ইনভেন্টরি এবং static-scan সদস্যপদ +`config/quality/gate-manifest.json`-এ রয়েছে। `package.json`-এর বিপরীতে script name এবং সঠিক +command যাচাই করতে `npm run check:gate-manifest` চালান; সংযোজন, অপসারণ এবং +command drift—দুটিই local hook এবং CI-এর change-classification job-গুলোকে ব্যর্থ করে। +একটি alias কোনো workflow job, matrix instance বা test case নয়: এই সংখ্যাগুলোকে +পরস্পর বিনিময়যোগ্য হিসেবে উপস্থাপন করা যাবে না। + +নির্বাচিত alias-গুলো নির্বাহ না করে পরিদর্শন করতে `npm run quality:scan -- --list` অথবা `npm run quality:scan:fast -- --list` +ব্যবহার করুন। Runner-টি +npm entrypoint আহ্বান করে, তাই এর runtime (যেখানে কনফিগার করা আছে সেখানে Bun-সহ) অক্ষুণ্ণ থাকে। +Manifest-টি ওই profile-গুলোর বাইরের alias-গুলোকে আলাদাভাবে আহ্বান করা হয়েছে বলে নথিভুক্ত করে এবং read-only scan profile-এ maintenance command নিষিদ্ধ। -এই profile-গুলো শুধু static scan কভার করে। এগুলো product test, +এই profile-গুলো কেবল static scan কভার করে। এগুলো product test, coverage, packaging, external check অথবা কোনো প্রার্থীর সম্পূর্ণ release acceptance প্রত্যয়িত করে না। Workflow admission সংযুক্ত `config/quality/admission-policy.json` এবং `scripts/quality/admission-verdict.mjs` ব্যবহার করে। Release-observer profile-গুলো পৃথক থাকে; -এগুলোর প্রযোজ্য check এবং receipt স্বতন্ত্রভাবে পরিদর্শন করুন। নিচের গদ্য -inventory একটি রেফারেন্স, কোনো gate বাস্তবে চলেছে—তার প্রমাণ নয়। +এগুলোর প্রযোজ্য check এবং receipt স্বাধীনভাবে পরিদর্শন করুন। নিচের গদ্যধর্মী +ইনভেন্টরিটি একটি রেফারেন্স, কোনো gate সত্যিই চলেছে তার প্রমাণ নয়। Script-গুলো `scripts/check/` (policy gate) এবং `scripts/quality/` (ratchet engine)-এর অধীনে থাকে। -CI-এর নির্ভরযোগ্য উৎস হলো `.github/workflows/ci.yml`। +CI-এর নির্ভরযোগ্য মূল উৎস হলো `.github/workflows/ci.yml`। -### Release PR ফাস্ট-পাথ (`quality.yml`) +### Release PR দ্রুত-পথ (`quality.yml`) `.github/workflows/quality.yml` main/release PR, protected-branch -push, dispatch এবং merge group-এ CI-এর পরিপূরক হিসেবে কাজ করে। PR-গুলো path-filtered fast check ব্যবহার করে। স্থায়ীভাবে -নিষ্ক্রিয় duplicate build সরিয়ে দেওয়া হয়েছে; প্রকৃত build/package/boot check-গুলো CI-তে রয়ে গেছে। - -| Job | পরিধি | ব্লকিং | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `Docs Gates (fast-path)` | Docs/code PR; API docs ref এবং docs-all | হ্যাঁ | -| `Fast Quality Gates` | Code PR; static check, typecheck, dashboard typecheck, প্রভাবিত unit test | হ্যাঁ | -| `Forgotten sibling tests` | Code PR; পরিবর্তিত module থেকে static consumer এবং সম্ভাব্য sibling test ট্রেস করা হয়; barrel এবং dynamic-import path-গুলো referenced allowlist exception-সহ পরামর্শমূলক diagnostic হিসেবে রিপোর্ট করা হয় | **পরামর্শমূলক** | -| `Vitest (fast-path)` | Code PR; fast vitest suite | হ্যাঁ | -| `Unit Tests fast-path` | Code PR; 4-shard unit suite | হ্যাঁ | -| `No new ESLint warnings` | Code PR; suppression-aware lint guard | হ্যাঁ, fork-সহ | -| `Merge integrity (changelog + generated skills)` | Non-draft PR; changelog এবং generated skill sync | হ্যাঁ, fork-সহ | +push, dispatch এবং merge group-এ CI-কে সম্পূরক করে। PR-গুলো path-filtered fast check ব্যবহার করে। স্থায়ীভাবে +নিষ্ক্রিয় duplicate build সরিয়ে ফেলা হয়েছে; প্রকৃত build/package/boot check-গুলো CI-তে রয়ে গেছে। + +| জব | পরিধি | ব্লকিং | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `Docs Gates (fast-path)` | Docs/code PR; API docs ref এবং docs-all | হ্যাঁ | +| `Fast Quality Gates` | Code PR; static check, typecheck, dashboard typecheck, প্রভাবিত unit test | হ্যাঁ | +| `Forgotten sibling tests` | Code PR; পরিবর্তিত module থেকে static consumer এবং সম্ভাব্য sibling test পর্যন্ত অনুসরণ; barrel এবং dynamic-import path-গুলো পরামর্শমূলক diagnostic হিসেবে রিপোর্ট করা হয়, সঙ্গে উল্লেখিত allowlist exception | **পরামর্শমূলক** | +| `Vitest (fast-path)` | Code PR; দ্রুত vitest suite | হ্যাঁ | +| `Unit Tests fast-path` | Code PR; 4-shard unit suite | হ্যাঁ | +| `No new ESLint warnings` | Code PR; suppression-aware lint guard | হ্যাঁ, fork-সহ | +| `Merge integrity (changelog + generated skills)` | Non-draft PR; changelog এবং generated skill sync | হ্যাঁ, fork-সহ | #### ভুলে যাওয়া sibling test-এর রিপোর্ট `npm run check:forgotten-sibling-tests` test-impact map-এর পেছনের import resolver পুনরায় ব্যবহার করে। -প্রতিটি পরিবর্তিত production module-এর জন্য, pull-request diff-এ সম্ভাব্য -test অনুপস্থিত থাকলে এটি নির্ধারিত ক্রমে -`changed module/symbol -> static consumer -> candidate sibling test` chain রিপোর্ট করে। যেকোনো blocking rollout-এর আগে -ক্যালিব্রেশনের জন্য Markdown summary এবং JSON result-কে `forgotten-sibling-tests` -workflow artifact হিসেবে সংরক্ষণ করা হয়। - -ব্যারেল রি-এক্সপোর্ট এবং ডায়নামিক ইমপোর্ট কেবল রেজোলিউশন ডায়াগনস্টিক; এগুলো কখনোই -ব্লকিং ফাইন্ডিং তৈরি করে না। পর্যালোচিত ব্যতিক্রমগুলো -`config/quality/forgotten-sibling-allowlist.json`-এ থাকে। প্রতিটি এন্ট্রিতে কনজিউমার এবং ক্যান্ডিডেট -টেস্টের নাম, একটি নির্দিষ্ট যৌক্তিকতা এবং একটি GitHub ইস্যু বা পুল রিকোয়েস্টের লিংক থাকতে হবে। ত্রুটিপূর্ণ এন্ট্রি -থাকলে যাচাই ব্যর্থ হয়। কোনো ব্যতিক্রম মুছে ফেলা ক্যান্ডিডেট টেস্ট অথবা `.skip`/`.todo` যোগ করা কোনো ডিফকে -উপেক্ষা করতে পারে না; অ্যাসারশন দুর্বল করা এবং অন্যান্য মাস্কিং স্বতন্ত্রভাবে ব্লকিং -`check:test-masking` গেটের আওতায় থাকে। - -### জব: `lint` - -`main`-এ প্রতিটি PR-এর ক্ষেত্রে চলে। ব্যর্থ হলে মার্জ ব্লক করে। - -| স্ক্রিপ্ট (`npm run ...`) | যা যাচাই করে | ব্লকিং | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | Node.js সংস্করণ সমর্থিত সীমার মধ্যে আছে | হ্যাঁ | -| `check:cycles` | সার্কুলার ইমপোর্ট — সব `src/` + `open-sse/` মডিউল | হ্যাঁ | -| `check:route-validation:t06` | সব রুটে Zod স্কিমা উপস্থিত আছে (Tier 6 নীতি) | হ্যাঁ | -| `check:any-budget:t11` | `@ts-expect-error // any`-এর সংখ্যা নির্ধারিত বাজেট অতিক্রম করে না (Tier 11 catraca) | হ্যাঁ | -| `check:provider-consistency` | `providers.ts`-এর প্রতিটি provider-এর জন্য `providerRegistry.ts`-এ একটি সামঞ্জস্যপূর্ণ এন্ট্রি রয়েছে (এবং বিপরীতক্রমেও, allowlist-এর মধ্যে) | হ্যাঁ | -| `check:model-lifecycle` | হাতে রক্ষণাবেক্ষণ করা তিনটি routing table checked-in lifecycle snapshot (#11503)-এর সঙ্গে সামঞ্জস্যপূর্ণ থাকে: `FITNESS_TABLE` (`taskFitness.ts`) এমন কোনো retired id-কে স্কোর করে না, যেটিতে `REGISTRY` route করতে পারে; প্রতিটি `BUILT_IN_ALIASES` target `REGISTRY`-তে উপস্থিত এবং retired-id snapshot-এ অনুপস্থিত; `REGISTRY`-তে এখনও থাকা প্রতিটি retired id forward করা হয় অথবা `allowedRetiredInCatalog`-এ তালিকাভুক্ত থাকে; এবং কোনো `DEFAULT_DEGRADATION_MAP` source বা target ওই snapshot-এ retired হিসেবে উপস্থিত নয়। এটি প্রমাণ করে না যে কোনো model বর্তমানে live upstream দ্বারা পরিবেশিত হচ্ছে। Offline — `config/quality/model-lifecycle.json`-এর সঙ্গে তুলনা করে, যা `npm run quality:refresh-model-lifecycle` ব্যবহার করে হাতে refresh করা হয় (network; CI-এর সঙ্গে সংযুক্ত নয়)। `allowedRetiredInCatalog` হলো একটি burn-down ratchet: শুধু tracking issue থাকলেই একটি এন্ট্রি যোগ করুন। | হ্যাঁ | -| `check:fetch-targets` | client-side `src/`-এর প্রতিটি `fetch("/api/...")` একটি বাস্তব `route.ts`-এ resolve করে | হ্যাঁ | -| `check:deps` | repo-র প্রতিটি `package.json` জুড়ে `npm install`-যোগ্য সব dependency `dependency-allowlist.json`-এ রয়েছে; নতুন unpinned বা slopsquatted package চিহ্নিত করা হয় | হ্যাঁ | -| `audit:deps` | `npm audit` (root + electron) — কোনো high/critical advisory নেই (osv `check:vuln-ratchet`-এর সঙ্গে overlap করে; Rationalization Backlog দেখুন) | হ্যাঁ | -| `check:lockfile` | `package-lock.json`-এর integrity — https registry, integrity hash, কোনো host override নেই | হ্যাঁ | -| `check:licenses` | প্রোডাকশন ডিপেন্ডেন্সিগুলোর জন্য SPDX লাইসেন্স allowlist | হ্যাঁ | -| `check:tracked-artifacts` | কোনো বিল্ড artifact / কমিট করা `node_modules` symlink নেই (husky pre-commit-এও চলে; pre-push ইচ্ছাকৃতভাবে হালকা রাখা হয়েছে — #6716) | হ্যাঁ | -| `check:ai-attribution` | PR commit, title বা body-তে কোনো AI/bot `Co-Authored-By` trailer বা AI-generation footer নেই — কঠোর নিয়ম #16 (`quality.yml`-এর PR→`release/**`-এর fast-gates loop-এ — event payload পড়ে, PR না হলে কোনো কাজ করে না — এবং `ci.yml` lint-এ PR→`main`-এর জন্য শুধু-PR ধাপ; এছাড়াও husky `commit-msg` hook; মানব co-author অনুমোদিত; #14436) | -| `check:vitest-exclusions` | প্রতিটি Vitest exclusion-এ একটি tracking issue উল্লেখ করা থাকে এবং সেটি `config/quality/vitest-exclusions.json`-এ উপস্থিত থাকে (#13204) | হ্যাঁ | -| `check:file-size` | কোনো source file সংশ্লিষ্ট extension-এর সর্বোচ্চ সীমা অতিক্রম করে না (ratchet: `frozen` তালিকায় স্থিরীকৃত বড় file) | হ্যাঁ | -| `check:error-helper` | executor/handler-এ error response-এর জন্য `buildErrorBody()` / `sanitizeErrorMessage()` ব্যবহার করা হয় (কঠোর নিয়ম #12) | হ্যাঁ | -| `check:migration-numbering` | Migration SQL ফাইলগুলো ধারাবাহিকভাবে নম্বরযুক্ত, কোনো ফাঁক বা সদৃশ নম্বর নেই | হ্যাঁ | -| `check:public-creds` | `publicCreds.ts`-এর বাইরে কোনো আক্ষরিক OAuth `client_id`/`client_secret` বা Firebase Web কী নেই (কঠোর নিয়ম #11) | হ্যাঁ | -| `check:db-rules` | `src/lib/db/` মডিউলগুলোর বাইরে কোনো কাঁচা SQL নেই; `localDb.ts` থেকে কোনো ব্যারেল-ইমপোর্ট নেই (কঠোর নিয়ম #2/#5) | হ্যাঁ | -| `check:known-symbols` | যেসব প্রদানকারী নির্বাহক, রাউটিং কৌশল এবং অনুবাদক তাদের ডিসপ্যাচ টেবিলে নিবন্ধিত, সেগুলো ডিস্কে থাকা ফাইলগুলোর সঙ্গে মেলে—কোনো অনাথ বা অঘোষিত সিম্বল নেই | হ্যাঁ | -| `check:route-guard-membership` | চাইল্ড প্রসেস তৈরি করে এমন প্রতিটি রুট `isLocalOnlyPath()` দ্বারা শ্রেণিবদ্ধ (কঠোর নিয়ম #15/#17) | হ্যাঁ | -| `check:test-discovery` | রিপোজিটরির প্রতিটি `*.test.ts` / `*.spec.ts` ফাইল অন্তত একটি টেস্ট রানার দ্বারা সংগ্রহ করা হয় (র্যাচেট: `test-discovery-baseline.json`-এর অনাথ তালিকা কেবল ছোট হতে পারে) | হ্যাঁ | -| `check:agent-skills-sync` | জেনারেট করা agent-skills আর্টিফ্যাক্টগুলো তাদের সোর্স ক্যাটালগের সঙ্গে মেলে (কোনো বিচ্যুতি নেই) | -| `check:provider-asset-provenance` | Provider লোগো/অ্যাসেটের জন্য একটি রেকর্ডকৃত উৎস-তথ্য এন্ট্রি রয়েছে | -| `lint:json` | JSON কনফিগ ফাইলগুলো পার্স হয় এবং repo-এর lint নিয়ম পূরণ করে | -| `typecheck:core` | কোনো ত্রুটি ছাড়াই TypeScript কম্পাইলেশন (শুধু পরামর্শমূলক সতর্কতা) | হ্যাঁ | -| `typecheck:noimplicit:core` | কঠোর `noImplicitAny` — ভবিষ্যৎমুখী; আগে থেকে থাকা অনেক call site-এ এখনও annotation প্রয়োজন | **পরামর্শমূলক** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**`-এ সীমাবদ্ধ `tsc` (#7033) — `typecheck:core`-এর বাছাইকৃত 27-ফাইলের allowlist-এ কোনো dashboard TSX অন্তর্ভুক্ত নেই এবং `next build`-ও কখনো এটি type-check করে না (`next.config.mjs`-এ `ignoreBuildErrors: true` সেট করা আছে), তাই সেখানকার orphaned-identifier regression-গুলো (#6625/#6909) CI-তে অদৃশ্য ছিল। একটি স্থির করা প্রতি-ফাইল/প্রতি-TS-কোড ত্রুটি-সংখ্যার baseline-এর (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols`-এর মতো একই stale-enforcement প্যাটার্ন) বিপরীতে diff করা হয় — baselined সংখ্যার অতিরিক্ত শুধু নতুন ত্রুটিই gate-কে ব্যর্থ করে; আগে থেকে থাকা কোনো ত্রুটি ঠিক করা হলে `--update` দিয়ে baseline কমিয়ে আনুন। | হ্যাঁ | - -### Job: `quality-gate` - -`test-coverage`-এর পরে চলে। ব্যর্থ হলে merge ব্লক করে। - -| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| `quality:collect` | `quality-metrics.json` তৈরি করে (ESLint সতর্কতার সংখ্যা, একীভূত শার্ড রিপোর্ট থেকে কভারেজ) | হ্যাঁ (ratchet-এর পূর্ববর্তী ধাপ) | -| `quality:ratchet` | `quality-baseline.json`-এর প্রতিটি মেট্রিকের অবনতি হয়নি (ESLint সতর্কতা ≤ বেসলাইন; কভারেজ ≥ বেসলাইন) | হ্যাঁ | -| `check:duplication` | কোডের অনুলিপি (jscpd@4) `quality-baseline.json`-এর বেসলাইন অতিক্রম করে না | হ্যাঁ | -| `check:complexity` | ফাইল-স্তরের সাইক্লোম্যাটিক জটিলতা সর্বোচ্চ সীমা অতিক্রম করে না (মূল ESLint `complexity` + `max-lines-per-function`) | হ্যাঁ | -| `check:cognitive-complexity` | কগনিটিভ জটিলতা ratchet (`eslint-plugin-sonarjs`) — পৃথক ESLint পাস; CI উভয়টিকে একীভূত করে একক `check:complexity-ratchets` ধাপ হিসেবে চালায় | হ্যাঁ | -| `check:dead-code` | অব্যবহৃত export / ফাইলের ratchet (knip) বেসলাইনের তুলনায় অবনতি ঘটায় না | হ্যাঁ | -| `check:compression-budget` | কম্প্রেশন বেঞ্চমার্ক বাজেট — প্রতিটি ইঞ্জিনের token-savings-এর ন্যূনতম সীমার অবনতি হওয়া যাবে না | হ্যাঁ | -| `check:type-coverage` | টাইপযুক্ত শতাংশের ratchet (`type-coverage`)-এর অবনতি হয় না; এটি মূলত `typecheck:noimplicit:core`-কে অন্তর্ভুক্ত করে | হ্যাঁ | -| `check:codeql-ratchet` | খোলা CodeQL সতর্কতার সংখ্যার অবনতি হয় না (`gh api`-এর মাধ্যমে পড়ে; token ছাড়া নির্বিঘ্নে এড়িয়ে যায়) — রিফ্রেশের সময়সূচি ও ম্যানুয়াল ট্রিগারের জন্য নিচের "CodeQL ratchet" দেখুন | হ্যাঁ | +প্রতিটি পরিবর্তিত production module-এর জন্য, সম্ভাব্য +test-টি pull-request diff-এ অনুপস্থিত থাকলে এটি নির্ধারিত ক্রমে +`changed module/symbol -> static consumer -> candidate sibling test` chain রিপোর্ট করে। যেকোনো blocking rollout-এর আগে calibration-এর জন্য +Markdown summary এবং JSON result `forgotten-sibling-tests` workflow artifact হিসেবে সংরক্ষিত থাকে। + +Barrel re-export এবং dynamic import কেবল resolution diagnostic; এগুলো কখনোই কোনো +blocking finding তৈরি করে না। পর্যালোচিত exception-গুলো +`config/quality/forgotten-sibling-allowlist.json`-এ থাকে। প্রতিটি entry-তে consumer এবং candidate +test-এর নাম, একটি সুনির্দিষ্ট rationale এবং একটি GitHub issue বা pull request-এর link থাকতে হবে। ত্রুটিপূর্ণ entry +fail closed করে। Exception কোনো মুছে ফেলা candidate test বা `.skip`/`.todo` যোগ করে এমন diff-কে suppress +করতে পারে না; assertion দুর্বল করা এবং অন্যান্য masking স্বাধীনভাবে blocking +`check:test-masking` gate-এর অধীনেই থাকে। + +### Job: `lint` + +`main`-এর প্রতিটি PR-এ চলে। ব্যর্থ হলে merge block করে। + +| Script (`npm run ...`) | যা যাচাই করে | Blocking | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | Node.js version সমর্থিত range-এর মধ্যে আছে | হ্যাঁ | +| `check:cycles` | `src/` + `open-sse/`-এর সব circular import (AST-ভিত্তিক, tsconfig `paths` resolve করা হয়)। Bare = advisory, cycle-গুলোর তালিকা দেয়। `check:cycles:ratchet` (যা CI চালায়) count `quality-baseline.json`-এর `metrics.cycles` ceiling অতিক্রম করলে block করে — বর্তমানে 14, `direction: down`, তাই এটি কেবল কমতে পারে (#15159 G-01/G-02) | হ্যাঁ (ratchet) | +| `check:route-validation:t06` | সব route-এ Zod schema উপস্থিত আছে (Tier 6 policy) | হ্যাঁ | +| `check:any-budget:t11` | `@ts-expect-error // any`-এর count budget অতিক্রম করে না (Tier 11 catraca) | হ্যাঁ | +| `check:provider-consistency` | `providers.ts`-এর প্রতিটি provider-এর জন্য `providerRegistry.ts`-এ একটি মিলে যাওয়া entry রয়েছে (এবং বিপরীতক্রমেও, allowlist-এর মধ্যে) | হ্যাঁ | +| `check:model-lifecycle` | হাতে রক্ষণাবেক্ষণ করা তিনটি routing table checked-in lifecycle snapshot-এর (#11503) সঙ্গে সামঞ্জস্যপূর্ণ থাকে: `FITNESS_TABLE` (`taskFitness.ts`) এমন কোনো retired id-কে score করে না, যেটিতে `REGISTRY` route করতে পারে; প্রতিটি `BUILT_IN_ALIASES` target `REGISTRY`-তে উপস্থিত এবং retired-id snapshot-এ অনুপস্থিত; `REGISTRY`-তে এখনও থাকা প্রতিটি retired id forwarded অথবা `allowedRetiredInCatalog`-এ তালিকাভুক্ত; এবং কোনো `DEFAULT_DEGRADATION_MAP` source বা target ওই snapshot-এ retired হিসেবে উপস্থিত নয়। এটি প্রমাণ করে না যে কোনো model বর্তমানে live upstream দ্বারা পরিবেশিত হচ্ছে। Offline — `config/quality/model-lifecycle.json`-এর সঙ্গে তুলনা করে, যা `npm run quality:refresh-model-lifecycle` দিয়ে হাতে refresh করা হয় (network; CI-এর সঙ্গে সংযুক্ত নয়)। `allowedRetiredInCatalog` একটি burn-down ratchet: কেবল tracking issue থাকলেই একটি entry যোগ করুন। | হ্যাঁ | +| `check:fetch-targets` | client-side `src/`-এর প্রতিটি `fetch("/api/...")` একটি বাস্তব `route.ts`-এ resolve হয় | হ্যাঁ | +| `check:deps` | repo-র প্রতিটি `package.json` জুড়ে `npm install`-যোগ্য সব deps `dependency-allowlist.json`-এ রয়েছে; নতুন unpinned বা slopsquatted package চিহ্নিত করা হয় | হ্যাঁ | +| `audit:deps` | `npm audit` (root + electron) — কোনো high/critical advisory নেই (osv `check:vuln-ratchet`-এর সঙ্গে overlap করে; Rationalization Backlog দেখুন) | হ্যাঁ | +| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hash, কোনো host override নেই | হ্যাঁ | +| `check:licenses` | প্রোডাকশন ডিপেন্ডেন্সিগুলোর জন্য SPDX লাইসেন্স অনুমোদন-তালিকা | হ্যাঁ | +| `check:tracked-artifacts` | কোনো বিল্ড আর্টিফ্যাক্ট / কমিট করা `node_modules` সিমলিংক নেই (husky pre-commit-এও চলে; pre-push ইচ্ছাকৃতভাবে হালকা — #6716) | হ্যাঁ | +| `check:ai-attribution` | PR কমিট, শিরোনাম বা বডিতে কোনো AI/bot `Co-Authored-By` ট্রেলার বা AI-জেনারেশন ফুটার নেই — কঠোর নিয়ম #16 (`quality.yml`-এর PR→`release/**`-এর fast-gates লুপে — event payload পড়ে, PR না হলে কোনো কাজ করে না — এবং `ci.yml` lint-এ PR→`main`-এর জন্য শুধু-PR ধাপ; এছাড়াও husky `commit-msg` hook; মানব সহ-লেখক অনুমোদিত; #14436) | +| `check:vitest-exclusions` | প্রতিটি Vitest exclusion-এ একটি tracking issue উল্লেখ থাকে এবং সেটি `config/quality/vitest-exclusions.json`-এ উপস্থিত থাকে (#13204) | হ্যাঁ | +| `check:file-size` | কোনো source file প্রতি-extension সীমা অতিক্রম করে না (ratchet: `frozen` তালিকায় স্থিরীকৃত বড় ফাইলসমূহ) | হ্যাঁ | +| `check:error-helper` | executors/handlers-এ error response-গুলো `buildErrorBody()` / `sanitizeErrorMessage()` ব্যবহার করে (কঠোর নিয়ম #12) | হ্যাঁ | +| `check:migration-numbering` | Migration SQL ফাইলগুলো কোনো ফাঁক বা সদৃশ নম্বর ছাড়াই ধারাবাহিকভাবে নম্বরযুক্ত | হ্যাঁ | +| `check:public-creds` | `publicCreds.ts`-এর বাইরে কোনো আক্ষরিক OAuth `client_id`/`client_secret` বা Firebase Web কী নেই (কঠোর নিয়ম #11) | হ্যাঁ | +| `check:db-rules` | `src/lib/db/` মডিউলের বাইরে কোনো সরাসরি SQL নেই; `localDb.ts` থেকে কোনো barrel-import নেই (কঠোর নিয়ম #2/#5) | হ্যাঁ | +| `check:known-symbols` | তাদের dispatch table-এ নিবন্ধিত provider executor, routing strategy এবং translator-গুলো ডিস্কের ফাইলগুলোর সঙ্গে মেলে—কোনো অনাথ বা অঘোষিত symbol নেই | হ্যাঁ | +| `check:route-guard-membership` | child process তৈরি করে এমন প্রতিটি route `isLocalOnlyPath()` দ্বারা শ্রেণিবদ্ধ (কঠোর নিয়ম #15/#17) | হ্যাঁ | +| `check:test-discovery` | repo-তে থাকা প্রতিটি `*.test.ts` / `*.spec.ts` ফাইল অন্তত একটি test runner দ্বারা সংগ্রহ করা হয় (ratchet: `test-discovery-baseline.json`-এর অনাথ তালিকা কেবল ছোট হতে পারে) | হ্যাঁ | +| `check:agent-skills-sync` | জেনারেট করা agent-skills আর্টিফ্যাক্টগুলো তাদের সোর্স ক্যাটালগের সঙ্গে মেলে (কোনো বিচ্যুতি নেই) | +| `check:provider-asset-provenance` | প্রোভাইডার লোগো/অ্যাসেটগুলোর জন্য নথিভুক্ত উৎস-তথ্যের এন্ট্রি রয়েছে | +| `lint:json` | JSON কনফিগ ফাইলগুলো সঠিকভাবে পার্স হয় এবং রিপোজিটরির লিন্ট নিয়ম পূরণ করে | +| `typecheck:core` | কোনো ত্রুটি ছাড়াই TypeScript কম্পাইলেশন (শুধু পরামর্শমূলক সতর্কতা) | হ্যাঁ | +| `typecheck:noimplicit:core` | কঠোর `noImplicitAny` — ভবিষ্যৎমুখী; আগে থেকে থাকা অনেক কল সাইটে এখনও অ্যানোটেশন প্রয়োজন | **পরামর্শমূলক** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**`-এ সীমাবদ্ধ `tsc` (#7033) — `typecheck:core`-এর বাছাইকৃত ২৭-ফাইলের অনুমোদিত তালিকায় কোনো ড্যাশবোর্ড TSX অন্তর্ভুক্ত নেই, এবং `next build`-ও কখনো এটির টাইপ পরীক্ষা করে না (`next.config.mjs`-এ `ignoreBuildErrors: true` সেট করা আছে), তাই সেখানে থাকা অনাথ-আইডেন্টিফায়ার রিগ্রেশনগুলো (#6625/#6909) CI-এর কাছে অদৃশ্য ছিল। একটি স্থিরীকৃত প্রতি-ফাইল/প্রতি-TS-কোড ত্রুটি-সংখ্যার বেসলাইনের (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols`-এর মতো একই অচলতা-প্রয়োগ প্যাটার্ন) সঙ্গে ডিফ করা হয় — বেসলাইনে থাকা সংখ্যার অতিরিক্ত শুধু নতুন ত্রুটিই গেটকে ব্যর্থ করে; আগে থেকে থাকা কোনো ত্রুটি সংশোধন হলে `--update` দিয়ে সীমা ধাপে ধাপে কমান। | হ্যাঁ | + +### জব: `quality-gate` + +`test-coverage`-এর পরে চলে। ব্যর্থ হলে মার্জ আটকে দেয়। + +| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| `quality:collect` | `quality-metrics.json` তৈরি করে (ESLint সতর্কতার সংখ্যা, মার্জ করা শার্ড রিপোর্ট থেকে কভারেজ) | হ্যাঁ (র্যাচেটের পূর্ববর্তী ধাপ) | +| `quality:ratchet` | `quality-baseline.json`-এর প্রতিটি মেট্রিকের অবনতি হয়নি (ESLint সতর্কতা ≤ বেসলাইন; কভারেজ ≥ বেসলাইন) | হ্যাঁ | +| `check:duplication` | কোডের পুনরাবৃত্তি (jscpd@4) `quality-baseline.json`-এর বেসলাইন অতিক্রম করে না | হ্যাঁ | +| `check:complexity` | ফাইল-স্তরের সাইক্লোম্যাটিক জটিলতা নির্ধারিত সীমা অতিক্রম করে না (মূল ESLint `complexity` + `max-lines-per-function`) | হ্যাঁ | +| `check:cognitive-complexity` | কগনিটিভ জটিলতা র্যাচেট (`eslint-plugin-sonarjs`) — পৃথক ESLint পাস; CI উভয়টিকে একক `check:complexity-ratchets` ধাপ হিসেবে চালায় | হ্যাঁ | +| `check:dead-code` | অব্যবহৃত এক্সপোর্ট/ফাইলের র্যাচেট (knip) বেসলাইনের তুলনায় অবনতি ঘটায় না | হ্যাঁ | +| `check:compression-budget` | কম্প্রেশন বেঞ্চমার্ক বাজেট — প্রতিটি ইঞ্জিনের টোকেন-সাশ্রয়ের ন্যূনতম সীমার অবনতি হওয়া চলবে না | হ্যাঁ | +| `check:type-coverage` | টাইপযুক্ত শতাংশের র্যাচেট (`type-coverage`) অবনতি ঘটায় না; এটি মূলত `typecheck:noimplicit:core`-কে অন্তর্ভুক্ত করে | হ্যাঁ | +| `check:codeql-ratchet` | উন্মুক্ত CodeQL সতর্কতার সংখ্যা বৃদ্ধি পায় না (`gh api`-এর মাধ্যমে পড়ে; টোকেন না থাকলে সুষ্ঠুভাবে এড়িয়ে যায়) — রিফ্রেশের সময়সূচি ও ম্যানুয়াল ট্রিগার: নিচের "CodeQL ratchet" দেখুন | হ্যাঁ | ### জব: `quality-extended` -সম্পূর্ণ জবটি পরামর্শমূলক (`continue-on-error: true`)। npm-ভিত্তিক ratchet-গুলো -বাস্তবভাবে চলে; বাহ্যিক স্ক্যানারগুলো `gh release download`-এর মাধ্যমে ইনস্টল হয় এবং কোনো +সম্পূর্ণ জবটি পরামর্শমূলক (`continue-on-error: true`)। npm-ভিত্তিক র্যাচেটগুলো +প্রকৃতভাবে চলে; বাহ্যিক স্ক্যানারগুলো `gh release download`-এর মাধ্যমে ইনস্টল হয় এবং কোনো বাইনারি তখনও অনুপস্থিত থাকলে স্বয়ংক্রিয়ভাবে এড়িয়ে যায় (exit 0)। -| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:circular-deps` | কোনো চক্রাকার dependency নেই (dpdm) | **পরামর্শমূলক** | -| `check:bundle-size` | bundle-এর আকার সর্বোচ্চ সীমা অতিক্রম করে না | **পরামর্শমূলক** | -| `check:secrets` | secret স্ক্যানিং (gitleaks) — বাইনারি অনুপস্থিত থাকলে এড়িয়ে যায় | **পরামর্শমূলক** | -| `check:vuln-ratchet` | dependency-এর দুর্বলতা (osv-scanner) বৃদ্ধি পায় না — বাইনারি অনুপস্থিত থাকলে এড়িয়ে যায় | **পরামর্শমূলক** | -| `check:workflows` | workflow lint (actionlint + zizmor); অনুপস্থিত/বিকল স্ক্যানার, অবৈধ রিপোর্ট বা অনুপস্থিত ratchet বেসলাইন INCOMPLETE হিসেবে ব্যর্থ হয়। বৈধ ফলাফল নির্বাচিত strict/advisory/ratchet নীতি অনুসরণ করে | নির্বাহ আবশ্যক; CI-তে zizmor ratchet ব্লকিং | -| `check:openapi-breaking` | base branch-এর তুলনায় পাবলিক API contract (`openapi.yaml`)-এ ভঙ্গকারী পরিবর্তন (oasdiff) — `openapiBreaking=N` তৈরি করে; oasdiff অনুপস্থিত হলে বা base spec সমাধানযোগ্য না হলে এড়িয়ে যায় | **পরামর্শমূলক** | +| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:circular-deps` | কোনো চক্রাকার ডিপেন্ডেন্সি নেই (dpdm) | **পরামর্শমূলক** | +| `check:bundle-size` | বান্ডলের আকার নির্ধারিত সীমা অতিক্রম করে না | **পরামর্শমূলক** | +| `check:secrets` | গোপন তথ্য স্ক্যানিং (gitleaks) — বাইনারি অনুপস্থিত থাকলে এড়িয়ে যায় | **পরামর্শমূলক** | +| `check:vuln-ratchet` | ডিপেন্ডেন্সির দুর্বলতা (osv-scanner) বৃদ্ধি পায় না — বাইনারি অনুপস্থিত থাকলে এড়িয়ে যায় | **পরামর্শমূলক** | +| `check:workflows` | ওয়ার্কফ্লো লিন্ট (actionlint + zizmor); স্ক্যানার অনুপস্থিত/ত্রুটিপূর্ণ হলে, রিপোর্ট অবৈধ হলে বা র্যাচেট বেসলাইন অনুপস্থিত থাকলে INCOMPLETE হিসেবে ব্যর্থ হয়। বৈধ ফলাফল নির্বাচিত কঠোর/পরামর্শমূলক/র্যাচেট নীতি অনুসরণ করে | এক্সিকিউশন আবশ্যক; CI-তে zizmor র্যাচেট ব্লকিং | +| `check:openapi-breaking` | বেস ব্রাঞ্চের তুলনায় পাবলিক API কনট্র্যাক্টে (`openapi.yaml`) ব্রেকিং পরিবর্তন (oasdiff) — `openapiBreaking=N` নির্গত করে; oasdiff অনুপস্থিত থাকলে বা বেস স্পেক সমাধান করা না গেলে এড়িয়ে যায় | **পরামর্শমূলক** | ### জব: `docs-sync-strict` -`main`-এর প্রতিটি PR-এ চলে। ব্যর্থ হলে merge ব্লক করে। - -| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -| `check:docs-all` | নিচের ৬টি সাব-গেট ধারাবাহিকভাবে চালানো মেটা-গেট | হ্যাঁ | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt সংস্করণের সামঞ্জস্য | হ্যাঁ | -| ↳ `check:docs-counts` | গদ্যে উল্লিখিত সংখ্যা (প্রোভাইডারের সংখ্যা, মাইগ্রেশনের সংখ্যা ইত্যাদি) প্রকৃত সংখ্যার র্যাচেট উইন্ডোর মধ্যে আছে | হ্যাঁ | -| ↳ `check:env-doc-sync` | `.env.example`-এর প্রতিটি env var একটি ডকুমেন্টেশন টেবিলে নথিভুক্ত আছে এবং বিপরীতটিও সত্য | হ্যাঁ | -| ↳ `check:deprecated-versions` | ডকুমেন্টেশনে কোনো অবচিত সংস্করণ স্ট্রিং নেই | হ্যাঁ | -| ↳ `check:doc-links` | ডকুমেন্টেশনের অভ্যন্তরীণ markdown লিংক বাস্তব ফাইলে রিজলভ করে (`[text]`/`(path)` ফর্ম) | হ্যাঁ | -| ↳ `check:fabricated-docs` | ডকুমেন্টেশনে উদ্ধৃত রুট, env var, CLI কমান্ড, হুকের নাম ও ফাইল পাথ কোডবেসে বিদ্যমান। `--strict`-এর মাধ্যমে হার্ড গেট; ফ্ল্যাগ ছাড়া সফট-ফেইল। | হ্যাঁ (CI-তে `--strict`-এর মাধ্যমে) | -| `check:cli-i18n` | CLI কমান্ডের স্ট্রিং সব i18n লোকেল ফাইলে উপস্থিত | হ্যাঁ | -| `check:openapi-coverage` | OpenAPI স্পেক প্রকৃত রুটগুলোর অন্তত র্যাচেট-করা ন্যূনতম সীমা পর্যন্ত কভার করে | হ্যাঁ | -| `check:openapi-security-tiers` | `openapi.yaml`-এর নিরাপত্তা স্তরের অ্যানোটেশনগুলো `routeGuard.ts`-এর শ্রেণিবিন্যাসের সঙ্গে সামঞ্জস্যপূর্ণ | **পরামর্শমূলক** | -| `check:openapi-routes` | `openapi.yaml`-এর প্রতিটি পাথ একটি বাস্তব `route.ts`-এ রিজলভ করে (হ্যালুসিনেশন-প্রতিরোধী) | হ্যাঁ | -| `check:docs-symbols` | `docs/**/*.md`-এর প্রতিটি `/api/...` রেফারেন্স একটি বাস্তব `route.ts`-এ রিজলভ করে (হ্যালুসিনেশন-প্রতিরোধী) | হ্যাঁ | -| `i18n translation drift` | i18n লোকেল ফাইলের অনূদিত না হওয়া কী — শুধু সতর্ক করে | **পরামর্শমূলক** | +`main`-এ প্রতিটি PR-এর সময় চলে। ব্যর্থ হলে মার্জ ব্লক করে। + +| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `check:docs-all` | নিচের 6টি সাব-গেট ধারাবাহিকভাবে চালানো মেটা-গেট | হ্যাঁ | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt সংস্করণের সামঞ্জস্য | হ্যাঁ | +| ↳ `check:docs-counts` | গদ্যে উল্লিখিত সংখ্যা (প্রোভাইডারের সংখ্যা, মাইগ্রেশনের সংখ্যা ইত্যাদি) প্রকৃত সংখ্যার র্যাচেট সীমার মধ্যে আছে | হ্যাঁ | +| ↳ `check:env-doc-sync` | `.env.example`-এর প্রতিটি env var ডকুমেন্টেশনের একটি টেবিলে নথিভুক্ত আছে এবং এর বিপরীতটিও সত্য | হ্যাঁ | +| ↳ `check:deprecated-versions` | ডকুমেন্টেশনে কোনো অবচিত সংস্করণ স্ট্রিং নেই | হ্যাঁ | +| ↳ `check:doc-links` | ডকুমেন্টেশনের অভ্যন্তরীণ markdown লিংক বাস্তব ফাইলে রিজলভ হয় (`[text]`/`(path)` রূপে) | হ্যাঁ | +| ↳ `check:fabricated-docs` | ডকুমেন্টেশনে উল্লিখিত রুট, env var, CLI কমান্ড, হুকের নাম এবং ফাইল পাথ codebase-এ বিদ্যমান। `--strict`-এর মাধ্যমে হার্ড গেট; ফ্ল্যাগ ছাড়া সফট-ফেইল। | হ্যাঁ (CI-তে `--strict` দিয়ে) | +| `check:cli-i18n` | CLI কমান্ডের স্ট্রিং সব i18n locale ফাইলে উপস্থিত | হ্যাঁ | +| `check:openapi-coverage` | OpenAPI spec বাস্তব রুটগুলোর অন্তত একটি র্যাচেটেড ন্যূনতম সীমা কভার করে | হ্যাঁ | +| `check:openapi-security-tiers` | `openapi.yaml`-এর নিরাপত্তা স্তরের annotation-গুলো `routeGuard.ts`-এর classification-এর সঙ্গে সামঞ্জস্যপূর্ণ | **পরামর্শমূলক** | +| `check:openapi-routes` | `openapi.yaml`-এর প্রতিটি পাথ একটি বাস্তব `route.ts`-এ রিজলভ হয় (অলীক তথ্য প্রতিরোধ) | হ্যাঁ | +| `check:docs-symbols` | `docs/**/*.md`-এর প্রতিটি `/api/...` রেফারেন্স একটি বাস্তব `route.ts`-এ রিজলভ হয় (অলীক তথ্য প্রতিরোধ) | হ্যাঁ | +| `i18n translation drift` | i18n locale ফাইলে অনূদিত না হওয়া key — শুধু সতর্ক করে | **পরামর্শমূলক** | ### জব: `i18n-ui-coverage` -| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (ইনলাইন) | UI i18n কী কভারেজ ≥ 65% | হ্যাঁ | -| `check-ui-value-drift` (ইনলাইন) | পুনর্লিখিত কোনো ইংরেজি **মান** পুরোনো অনুবাদ অবশিষ্ট রাখে না | হ্যাঁ | -| `check-new-key-coverage` (ইনলাইন) | একটি **নতুন** ইংরেজি কী প্রতিটি লোকেলে অনূদিত — `__MISSING__:` মার্কার প্রত্যাখ্যান করা হয় | হ্যাঁ | -| `check-translation-ratio` | প্রতি লোকেলের প্রকৃত-অনুবাদ অনুপাত (ইংরেজির সঙ্গে অভিন্ন / প্লেসহোল্ডার / অনুমোদিত তালিকার বাইরে অনুপস্থিত লিফ) অবশ্যই `config/quality/i18n-translation-baseline.json` + slack অতিক্রম করবে না | **পরামর্শমূলক** | +| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (inline) | UI i18n key coverage ≥ 65% | হ্যাঁ | +| `check-ui-value-drift` (inline) | পুনর্লিখিত ইংরেজি **value** কোনো পুরোনো translation রেখে যায় না | হ্যাঁ | +| `check-new-key-coverage` (inline) | একটি **নতুন** ইংরেজি key প্রতিটি locale-এ অনূদিত — একটি `__MISSING__:` marker প্রত্যাখ্যাত হয় | হ্যাঁ | +| `check-translation-ratio` | প্রতি locale-এ প্রকৃত translation-এর অনুপাত (allowlist-এর বাইরে ইংরেজির সঙ্গে অভিন্ন / placeholder / অনুপস্থিত leaf) অবশ্যই `config/quality/i18n-translation-baseline.json` + slack অতিক্রম করবে না | **পরামর্শমূলক** | -`fetch-depth: 0` প্রয়োজন — value-drift গেটটি মার্জ বেসের বিপরীতে `en.json`-এর পার্থক্য নির্ণয় করে। +`fetch-depth: 0` প্রয়োজন — value-drift gate merge base-এর সঙ্গে `en.json`-এর diff করে। -#### `check-ui-value-drift` — পুরোনো-অনুবাদ গেট +#### `check-ui-value-drift` — পুরোনো-translation গেট -এটি সেই একটি i18n রিগ্রেশন ধরে, যা অন্য গেটগুলো কাঠামোগতভাবে শনাক্ত করতে পারে না: কোনো ইংরেজি মান -পুনর্লিখিত হয়, কিন্তু _আগের_ ইংরেজি থেকে তৈরি অনুবাদগুলো রয়ে যায়; ফলে -অ-ইংরেজি ব্যবহারকারীরা আত্মবিশ্বাসী ভাষায় লেখা, কিন্তু এখন ভুল, কপি পড়তে থাকেন। +এটি এমন একটি i18n regression ধরে, যা অন্য গেটগুলো কাঠামোগতভাবে দেখতে পারে না: কোনো ইংরেজি value +পুনর্লিখিত হয়, কিন্তু _আগের_ ইংরেজি থেকে তৈরি translation-গুলো থেকে যায়, ফলে +অ-ইংরেজি ব্যবহারকারীরা আত্মবিশ্বাসী ভাষায় লেখা, কিন্তু এখন ভুল, এমন copy পড়তে থাকেন। -এটি বাস্তবেই রিলিজ হয়েছিল। Antigravity লগইন হেলপার যুক্ত হওয়ার সময় (#5203) -`oauthModal.googleOAuthWarning` পুনর্লিখিত হয়েছিল; **৪৩টি লোকেলের মধ্যে ৩৯টিতে** অপারেটরদের "সম্পূর্ণ -URL কপি করে নিচে পেস্ট করতে" বলা টেক্সট রয়ে গিয়েছিল — এমন একটি প্রবাহ, যা ওই প্রোভাইডারের জন্য সম্পন্ন করা যায় না। এটি -#8463 পর্যন্ত নজরে আসেনি, কারণ: +এটি বাস্তবেই রিলিজে চলে গিয়েছিল। Antigravity login helper যুক্ত হওয়ার সময় (#5203) +`oauthModal.googleOAuthWarning` পুনর্লিখিত হয়েছিল; **43টি locale-এর মধ্যে 39টিতে** operator-দের "সম্পূর্ণ +URL কপি করে নিচে পেস্ট করতে" বলা text থেকে গিয়েছিল — এমন একটি flow, যা ওই provider-এর জন্য সম্পন্ন করা +অসম্ভব। #8463 পর্যন্ত এটি নজরে আসেনি, কারণ: -- `sync-ui-keys` শুধু **অনুপস্থিত** কী ব্যাকফিল করে, **পুরোনো** কী কখনো নয়; -- `check-ui-keys-coverage` কী-এর _উপস্থিতি_ গণনা করে, তাই পুরোনো অনুবাদও কভারড হিসেবে স্কোর পায়; -- `check-translation-drift` `docs/i18n//**.md` ডকুমেন্টেশন মিররগুলো ট্র্যাক করে — - এটি কখনোই `src/i18n/messages/*.json` পড়ে না। 2026-09 পুনরায় সিঙ্ক করার পর থেকে `docs-sync-strict` জবে ব্লকিং: - একটি মূল ডকুমেন্ট সম্পাদনা করুন → `npm run i18n:run -- --files=` (সেকশন-স্তরের, সাশ্রয়ী)। +- `sync-ui-keys` শুধু **অনুপস্থিত** key backfill করে, **পুরোনো** key কখনো নয়; +- `check-ui-keys-coverage` key-এর _উপস্থিতি_ গণনা করে, তাই একটি পুরোনো translation-ও covered হিসেবে গণ্য হয়; +- `check-translation-drift` `docs/i18n//**.md` documentation mirror ট্র্যাক করে — + এটি কখনোই `src/i18n/messages/*.json` পড়ে না। 2026-09 re-sync থেকে job `docs-sync-strict`-এ ব্লকিং: + একটি core doc সম্পাদনা করুন → `npm run i18n:run -- --files=` (section-level, সাশ্রয়ী)। -**ডিফ-সচেতন, বেসলাইন-সমর্থিত নয়।** এটি মার্জ বেসের `en.json`-কে -ওয়ার্কিং ট্রির সঙ্গে তুলনা করে; যেসব কী-এর ইংরেজি মান পরিবর্তিত হয়েছে, সেগুলোর ক্ষেত্রে -যে লোকেলে এখনও অপরিবর্তিত অনুবাদ রয়েছে, সেটি অচল। এটি ইচ্ছাকৃতভাবে **পূর্ব-বিদ্যমান ঋণকে স্থির রাখে** — -দীর্ঘদিনের কোনো অনুবাদ কোন পুরোনো ইংরেজি থেকে এসেছে, তা একটি ডিফ প্রকাশ করতে পারে না, তাই গেটটি -শুধু বর্তমান পরিবর্তনে স্পর্শ করা বিষয়গুলোই যাচাই করে। বিকল্পটি (প্রতি-কী হ্যাশ বেসলাইন) তৈরি করতে -~600 KB-এর একটি জেনারেটেড ফাইল লাগত, যা বিদ্যমান বৃহত্তম বেসলাইনের 3× এবং প্রতিটি i18n PR-এ পরিবর্তিত হতো। +**Diff-সচেতন, baseline-সমর্থিত নয়।** এটি merge base-এর `en.json`-কে working tree-এর সঙ্গে তুলনা করে; যেসব key-এর ইংরেজি value পরিবর্তিত হয়েছে, সেগুলোর ক্ষেত্রে কোনো locale-এ এখনও অপরিবর্তিত translation থাকলে সেটি stale। এটি ইচ্ছাকৃতভাবে **আগে থেকে থাকা ঘাটতিকে স্থির রাখে** — দীর্ঘদিন ধরে থাকা কোনো translation কোন পুরোনো ইংরেজি থেকে এসেছে, তা একটি diff প্রকাশ করতে পারে না; তাই gate কেবল বর্তমান পরিবর্তনে স্পর্শ করা বিষয়গুলোই বিচার করে। বিকল্পটি (প্রতি-key hash baseline) তৈরি করতে ~600 KB-এর একটি generated file লাগবে, যা বিদ্যমান বৃহত্তম baseline-এর 3× এবং প্রতিটি i18n PR-এ পরিবর্তিত হবে। -এটি পূরণ করার দুটি উপায়: +এটি সন্তুষ্ট করার দুটি উপায়: -1. প্রভাবিত অনুবাদগুলো হালনাগাদ করুন, অথবা -2. সেগুলোকে `__MISSING__:` হিসেবে সেট করুন — এরপর রানটাইম সংশোধিত ইংরেজি পরিবেশন করে - (`src/i18n/request.ts::deepMergeFallback`, #7258) এবং কী-টি অনুবাদের সারিতে যুক্ত হয়। +1. প্রভাবিত translation-গুলো update করুন, অথবা +2. সেগুলোকে `__MISSING__:`-এ সেট করুন — তখন runtime সংশোধিত ইংরেজিটি পরিবেশন করে + (`src/i18n/request.ts::deepMergeFallback`, #7258) এবং key-টি translation-এর জন্য queue-তে যোগ হয়। -স্ট্রিংটির **অর্থ** পরিবর্তিত হলে, **কী-এর নাম পরিবর্তন** করাকে অগ্রাধিকার দিন: একটি নতুন কী -অচল অনুবাদ উত্তরাধিকারসূত্রে পেতে পারে না। #8463-এ এই প্যাটার্ন ব্যবহার করা হয়েছিল। +string-টির **অর্থ** পরিবর্তিত হলে, **key-টির নাম পরিবর্তন** করাকে অগ্রাধিকার দিন: একটি নতুন key stale translation উত্তরাধিকারসূত্রে পেতে পারে না। #8463-এ এই pattern-টিই ব্যবহার করা হয়েছিল। ```bash npm run i18n:check-value-drift # কঠোর (CI যা চালায়) -npm run i18n:check-value-drift:warn # শুধু প্রতিবেদন +npm run i18n:check-value-drift:warn # শুধু রিপোর্ট BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -বেস ক্যাটালগ পড়া না গেলে (বেস ref ছাড়া শ্যালো ক্লোন), `SKIP reason=base-unresolved` সহ -0 এক্সিট করে, যা `check-openapi-breaking`-এর আচরণের অনুরূপ। +base catalog পড়া না গেলে (base ref ছাড়া shallow clone), `check-openapi-breaking`-এর আচরণ অনুসরণ করে `SKIP reason=base-unresolved` সহ 0 exit করে। -### জব: `i18n` +### Job: `i18n` -সম্পূর্ণ i18n যাচাইকরণ ম্যাট্রিক্স (প্রতি লোকেলে একটি জব)। পুরো জবটি পরামর্শমূলক। +সম্পূর্ণ i18n validation matrix (প্রতি locale-এ একটি job)। পুরো job-টি advisory। -| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | -| ------------------------------- | -------------------------------- | ---------------------------------------------------- | -| `validate_translation.py quick` | প্রতি লোকেলে অনুবাদের সম্পূর্ণতা | **পরামর্শমূলক** (পুরো জবে `continue-on-error: true`) | +| Script | যা যাচাই করে | Blocking | +| ------------------------------- | ------------------------------------- | --------------------------------------------------- | +| `validate_translation.py quick` | প্রতি locale-এ translation-এর পূর্ণতা | **Advisory** (পুরো job-এ `continue-on-error: true`) | -### জব: `pr-test-policy` +### Job: `pr-test-policy` -শুধু পুল রিকোয়েস্টে চলে। +শুধু pull request-এ চলে। -| স্ক্রিপ্ট | যা যাচাই করে | ব্লকিং | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -| `check:pr-test-policy` | যেসব PR `src/`, `open-sse/`, `electron/`, বা `bin/`-এর প্রোডাকশন কোড পরিবর্তন করে, সেগুলোতে অবশ্যই টেস্ট যোগ বা হালনাগাদ করতে হবে (কঠোর নিয়ম #8) | হ্যাঁ | -| `check:test-masking` | পরিবর্তিত টেস্ট ফাইলগুলো মোট assert সংখ্যা কমায় না বা `assert.ok(true)`-এর মতো স্বতঃসিদ্ধ assertion যোগ করে না | হ্যাঁ | -| `check:pr-evidence` | PR-এর বিবরণে পরিবর্তনের জন্য টেস্ট/VPS প্রমাণ উল্লেখ করা হয়েছে (PR-এর গদ্য grep করে কঠোর নিয়ম #18 স্বয়ংক্রিয় করে — ভঙ্গুর, Backlog দেখুন) | হ্যাঁ | +| Script | যা যাচাই করে | Blocking | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | যেসব PR `src/`, `open-sse/`, `electron/`, বা `bin/`-এর production code পরিবর্তন করে, সেগুলোতে অবশ্যই test যোগ বা update করতে হবে (Hard Rule #8) | হ্যাঁ | +| `check:test-masking` | পরিবর্তিত test file-গুলো net assert count কমায় না বা `assert.ok(true)` tautology যোগ করে না | হ্যাঁ | +| `check:pr-evidence` | PR body-তে পরিবর্তনের জন্য test/VPS evidence উল্লেখ করা হয় (PR-এর prose grep করে Hard Rule #18 স্বয়ংক্রিয় করে — ভঙ্গুর, Backlog দেখুন) | হ্যাঁ | -### জব: `test-vitest` +### Job: `test-vitest` -`build`-এর পরে চলে। ব্যর্থ হলে মার্জ ব্লক করে। +`build`-এর পরে চলে। ব্যর্থ হলে merge block করে। -| স্যুট | যা যাচাই করে | ব্লকিং | -| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP সার্ভার (110টি টুল), autoCombo, cache — vitest রানার | হ্যাঁ | -| `test:vitest:ui` | UI কম্পোনেন্ট টেস্ট — vitest রানার | **ব্লকিং** — পূর্ব-বিদ্যমান ব্যর্থতাগুলো `vitest.config.ts`-এ স্পষ্টভাবে বাদ দেওয়া হয়েছে; নতুন ব্যর্থতায় জবটি ব্যর্থ হয় | +| Suite | যা যাচাই করে | Blocking | +| ---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | হ্যাঁ | +| `test:vitest:ui` | UI component test — vitest runner | **Blocking** — আগে থেকে থাকা failure-গুলো `vitest.config.ts`-এ স্পষ্টভাবে বাদ দেওয়া হয়েছে; নতুন failure হলে job ব্যর্থ হয় | -### রাত্রিকালীন ওয়ার্কফ্লো (নির্ধারিত, পরামর্শমূলক) +### Nightly workflow (নির্ধারিত, advisory) -এগুলো cron সময়সূচিতে (এবং `workflow_dispatch`-এ) চলে, PR-এ কখনোই নয়। সবগুলোই পরামর্শমূলক। +এগুলো cron schedule-এ (এবং `workflow_dispatch`-এ) চলে, PR-এ কখনোই চলে না। সবগুলোই advisory। -| ওয়ার্কফ্লো | যা যাচাই করে | ব্লকিং | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | র্যান্ডম seed + উচ্চ run count সহ fast-check প্রপার্টি টেস্ট | **পরামর্শমূলক** | -| `nightly-resilience` | heap-growth গেট, chaos fault-injection, k6 load/soak | **পরামর্শমূলক** | -| `nightly-llm-security` | promptfoo injection guard (block মোড) + garak probes (প্রোভাইডার secret না থাকলে বাদ দেওয়া হয়) | **পরামর্শমূলক** | -| `nightly-schemathesis` | `docs/openapi.yaml` ব্যবহার করে সচল OmniRoute-এর বিরুদ্ধে OpenAPI কন্ট্রাক্ট fuzzing (schemathesis) — স্পেক লঙ্ঘন / অনিয়ন্ত্রিত 500 প্রকাশ করে (পর্যায় 8 B.4) | **পরামর্শমূলক** | -| `nightly-mutation` | দ্রুত unit lane-এ Stryker mutation-testing স্কোর — টিকে যাওয়া mutants দুর্বল assertions প্রকাশ করে | **পরামর্শমূলক** | -| `nightly-compat` | সমর্থিত `engines.node` রেঞ্জজুড়ে Node engine compatibility matrix | **পরামর্শমূলক** | +| Workflow | যা যাচাই করে | Blocking | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `nightly-property` | random seed + উচ্চ run count সহ fast-check property test | **Advisory** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Advisory** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probe (provider secret ছাড়া skip করা হয়) | **Advisory** | +| `nightly-schemathesis` | `docs/openapi.yaml` ব্যবহার করে live OmniRoute-এর বিপরীতে OpenAPI contract fuzzing (schemathesis) — spec violation / unhandled 500 প্রকাশ করে (পর্যায় 8 B.4) | **Advisory** | +| `nightly-mutation` | fast unit lane-এর ওপর Stryker mutation-testing score — টিকে যাওয়া mutant দুর্বল assert প্রকাশ করে | **Advisory** | +| `nightly-compat` | সমর্থিত `engines.node` range জুড়ে Node engine compatibility matrix | **Advisory** | --- @@ -539,41 +531,41 @@ CI গেটগুলোর পাশাপাশি, OmniRoute একটি ** ## যৌক্তিকীকরণ ব্যাকলগ (ROI পর্যালোচনা — পর্যায় 9 তরঙ্গ 3) -এই তালিকাটি 2026-06-17 তারিখে `ci.yml`-এর সঙ্গে মিলিয়ে দেখা হয়েছে (আগের সংস্করণে +এই ইনভেন্টরিটি 2026-06-17 তারিখে `ci.yml`-এর সঙ্গে সমন্বয় করা হয়েছে (আগের সংস্করণে `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` বাদ পড়েছিল)। মিলিয়ে নেওয়া সেটটির একটি ROI পর্যালোচনায় -নিম্নলিখিত যৌক্তিকীকরণের প্রার্থীগুলো শনাক্ত হয়েছে। **মার্জগুলো যান্ত্রিক CI -পরিবর্তন; ফ্লিপ/বাদ দেওয়ার সিদ্ধান্তগুলো অপারেটরের জন্য সংরক্ষিত নীতিগত সিদ্ধান্ত।** নিচের কিছুই +`check:codeql-ratchet`, `check:pr-evidence` বাদ পড়েছিল)। সমন্বয়কৃত সেটটির একটি ROI পর্যালোচনায় +নিচের যৌক্তিকীকরণ প্রার্থীগুলো শনাক্ত হয়েছে। **মার্জগুলো যান্ত্রিক CI +পরিবর্তন; ফ্লিপ/বাদ দেওয়ার সিদ্ধান্তগুলো অপারেটরের জন্য সংরক্ষিত নীতিগত সিদ্ধান্ত।** নিচের কোনো কিছুই এখনও প্রয়োগ করা হয়নি। -**উপরের অংশে আরও যা নথিভুক্ত হয়নি** (পরামর্শমূলক, কম সংকেত): `docs-lint` জব -(markdownlint + Vale, পুরো জব `continue-on-error`) এবং স্বতন্ত্র স্ক্যানার ওয়ার্কফ্লো -`semgrep.yml` / `codeql.yml` / `scorecard.yml`। `semgrepFindings: 0` -`quality-baseline.json`-এ রয়েছে, কিন্তু `ci.yml`-এ কোনো ব্লকিং র্যাচেটের সঙ্গে সংযুক্ত নয় — মেট্রিকটি +**উপরেও নথিবদ্ধ হয়নি** (পরামর্শমূলক, দুর্বল সংকেত): `docs-lint` জব +(markdownlint + Vale, পুরো জবে `continue-on-error`) এবং স্বতন্ত্র স্ক্যানার ওয়ার্কফ্লো +`semgrep.yml` / `codeql.yml` / `scorecard.yml`। `quality-baseline.json`-এ +`semgrepFindings: 0` রয়েছে, কিন্তু `ci.yml`-এ এটি কোনো ব্লকিং র্যাচেটের সঙ্গে সংযুক্ত নয় — মেট্রিকটি বর্তমানে অনাথ। -### মার্জ / ডিডুপ (যান্ত্রিক, কম ঝুঁকি) +### মার্জ / ডিডুপ্লিকেশন (যান্ত্রিক, কম ঝুঁকি) -প্রতিটি প্রার্থীকে 2026-06-17 তারিখের সক্রিয় গেট অবস্থার বিপরীতে যাচাই করা হয়েছে (বিশ্বাস করুন, তবে যাচাইও করুন); -বেশ কয়েকটি "স্পষ্ট" মার্জের আড়ালে ঋণ লুকিয়ে থাকতে দেখা গেছে এবং সেগুলো **পরিষ্কার ড্রপ-ইন নয়**। +প্রতিটি প্রার্থীকে 2026-06-17 তারিখে সক্রিয় গেটের অবস্থার বিপরীতে যাচাই করা হয়েছে (বিশ্বাস করুন, তবে যাচাইও করুন); +বেশ কয়েকটি "স্পষ্ট" মার্জ আসলে লুকানো ঋণ আড়াল করছিল এবং সেগুলো **পরিষ্কার ড্রপ-ইন নয়**। -- **`check:docs-sync` দুবার চলে** — `lint` জবে স্বতন্ত্রভাবে এবং আবার `check:docs-all` (`docs-sync-strict`) ও husky pre-commit hook-এর মধ্যে। ✅ **সম্পন্ন** — স্বতন্ত্র `lint` ইনভোকেশন সরানো হয়েছে। -- **CVE স্ক্যানিং** — ❌ **পরিষ্কার মার্জ নয়।** `audit:deps` যেকোনো high/critical CVE-তে হার্ড-ফেল করে; `check:vuln-ratchet` (osv) কেবল বেসলাইনের তুলনায় কোনো _অবনতি_ হলে ব্যর্থ হয় (বর্তমানে 1 MODERATE)। অর্থগত আচরণ ভিন্ন — `audit:deps` বাদ দিলে পরম high/critical গেটটি হারিয়ে যাবে। দুটিই রাখুন। -- **সাইকেল শনাক্তকরণ** — ❌ **পরিষ্কার মার্জ নয়।** `check:circular-deps` (dpdm) **91টি সাইকেল** রিপোর্ট করে (এই কারণেই এটি পরামর্শমূলক); সেগুলো আগে সমাধান না করে এটিকে ব্লকিং হিসেবে উন্নীত করা যায় না, এবং সবুজ, নির্বাচিত `check:cycles`-এর তুলনায় এর পরিধি বিস্তৃত। `check:cycles`-কে ব্লকিং রাখুন; 91টি dpdm সাইকেল সমাধান করা একটি স্বতন্ত্র ব্যাকলগ। -- **জটিলতা** — ✅ **সম্পন্ন** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): একটি ESLint ওয়াক, ruleId অনুযায়ী গণনা করে যাতে cyclomatic+max-lines এবং cognitive বেসলাইনগুলো স্বাধীন থাকে; স্থানীয় `--update`-এর জন্য পৃথক `check:complexity` / `check:cognitive-complexity` বহাল রয়েছে। -- **`/api` অ্যান্টি-হ্যালুসিনেশন** — ✅ **সম্পন্ন** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-এর একটি FS ইনভেন্টরি, openapi-routes + docs-symbols এখনও স্বাধীনভাবে রিপোর্ট করে; স্থানীয় রানগুলোর জন্য পৃথকগুলো বহাল রয়েছে। -- **`check:node-runtime` 11টি জবে চলে** — ⚠️ **কম ROI।** প্রতিটি আলাদা রানার এবং যাচাইটি <1s; মোট সাশ্রয় ~10s, যার বিনিময়ে প্রতিটি জবের জন্য একটি সস্তা গার্ড হারাতে হবে। এই পরিবর্তনের ঝামেলার যোগ্য নয়। +- **`check:docs-sync` দুবার চলে** — `lint` জবে স্বতন্ত্রভাবে এবং আবার `check:docs-all` (`docs-sync-strict`) ও husky pre-commit hook-এর ভেতরে। ✅ **সম্পন্ন** — স্বতন্ত্র `lint` ইনভোকেশন সরানো হয়েছে। +- **CVE স্ক্যানিং** — ❌ **পরিষ্কার মার্জ নয়।** যেকোনো high/critical CVE-তে `audit:deps` হার্ড-ফেইল করে; `check:vuln-ratchet` (osv) কেবল baseline-এর তুলনায় কোনো _রিগ্রেশন_ হলে ব্যর্থ হয় (বর্তমানে 1 MODERATE)। সেমান্টিক্স ভিন্ন — `audit:deps` বাদ দিলে পরম high/critical গেটটি হারিয়ে যাবে। দুটিই রাখুন। +- **চক্র শনাক্তকরণ** — ✅ **সম্পন্ন** (#15159 G-01/G-02)। এখানে পুরোনো লেখায় `check:cycles`-কে "সবুজ, বাছাইকৃত" গেট বলা হয়েছিল এবং `check:circular-deps` (dpdm) 91টি চক্র রিপোর্ট করায় এটিকে ব্লকিং রাখার যৌক্তিকতা দেওয়া হয়েছিল। সেই সবুজ অবস্থা ছিল একটি **মিথ্যা সবুজ**: `check:cycles` 5টি সাবডিরেক্টরি (450টি ফাইল) স্ক্যান করত, শুধু স্থির `import|export … from` মেলাত এবং প্রতিটি `@/` ও `@omniroute/open-sse/` স্পেসিফায়ার বাদ দিত, ফলে রিপোজিটরিতে প্রাধান্য পাওয়া dynamic-import + alias চক্রগুলো এটি দেখতে পারত না। সংশোধন করা হয়েছে: গেটটি এখন `src` + `open-sse` (5023টি ফাইল) অতিক্রম করে, TypeScript AST থেকে স্পেসিফায়ার সংগ্রহ করে (তাই `import("…")` গণনা করা হয়, কিন্তু type-position `typeof import("…")` করা হয় না) এবং tsconfig `paths` রিজলভ করে। এটি 0 নয়, **14টি** চক্র খুঁজে পায়। যেহেতু গেট PR-এ আগে থেকে থাকা 14টি চক্র ঠিক করা সম্ভব নয়, `check:cycles` এখন একটি **র্যাচেট** (`--ratchet`, `quality-baseline.json`-এ সীমা `metrics.cycles.value = 14`, `direction: down`) — এটি যেকোনো _রিগ্রেশন_ ব্লক করে এবং সংখ্যা কেবল কমতে পারে। CI-তে `npm run check:cycles:ratchet` চলে। বার্ন-ডাউন **A-01**-এর সঙ্গে চলবে। বিস্তৃত দ্বিতীয় মতামত হিসেবে `check:circular-deps` (dpdm) পরামর্শমূলক থাকবে। +- **জটিলতা** — ✅ **সম্পন্ন** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): একটি ESLint ওয়াক, ruleId অনুযায়ী গণনা করে যাতে cyclomatic+max-lines এবং cognitive baseline স্বাধীন থাকে; স্থানীয় `--update`-এর জন্য পৃথক `check:complexity` / `check:cognitive-complexity` বজায় রাখা হয়েছে। +- **`/api` অ্যান্টি-হ্যালুসিনেশন** — ✅ **সম্পন্ন** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-এর একটি FS ইনভেন্টরি, openapi-routes + docs-symbols এখনও স্বাধীনভাবে রিপোর্ট করে; স্থানীয় রানের জন্য পৃথকগুলো বজায় রাখা হয়েছে। +- **`check:node-runtime` 11টি জবে চলে** — ⚠️ **কম ROI।** প্রতিটি আলাদা রানার এবং চেকটি <1s; সাশ্রয় মোটামুটি ~10s, যার বিপরীতে একটি সস্তা প্রতি-জব গার্ড হারাতে হবে। পরিবর্তনের ঝামেলা সার্থক নয়। - **CI lint-এ `typecheck:noimplicit:core`** — ✅ **lint জব থেকে সরানো হয়েছে** (এটি পরামর্শমূলক `continue-on-error` ছিল); ব্লকিং টাইপ সারফেস হলো `typecheck:core` + `check:type-coverage`। স্থানীয় স্ক্রিপ্ট রাখা হয়েছে। -### ফ্লিপ / সিদ্ধান্ত নিন (অপারেটর নীতি) +### ফ্লিপ / সিদ্ধান্ত (অপারেটর নীতি) -- `check:openapi-security-tiers` (পরামর্শমূলক) — ❌ **পরিষ্কারভাবে ফ্লিপযোগ্য নয়।** এটি 0 দিয়ে বের হয়, তবে সতর্ক করে যে `LOCAL_ONLY_API_PREFIXES`-এর অধীন একাধিক `traffic-inspector` রুটে `x-loopback-only: true` অ্যানোটেশন নেই। এটি কার্যকর করতে হলে প্রথমে `openapi.yaml`-এ ওই অ্যানোটেশনগুলো যোগ করতে হবে। -- `typecheck:noimplicit:core` (পরামর্শমূলক) — ব্লকিং `check:type-coverage` র্যাচেটের মাধ্যমে অনেকটাই অন্তর্ভুক্ত। এটিকে র্যাচেটে ফ্লিপ করুন অথবা অপ্রয়োজনীয় দ্বিতীয় `tsc` পাসটি বাদ দিন। -- `test:vitest:ui` (এখন **ব্লকিং**) — আগে থেকে বিদ্যমান ব্যর্থতাগুলো `vitest.config.ts`-এ `// #8618` ট্র্যাকিং মন্তব্যসহ স্পষ্টভাবে বাদ দেওয়া হয়েছে; নতুন ব্যর্থতা জবটিকে ব্যর্থ করে। -- `check:secrets` (gitleaks, 3টি নথিভুক্ত false-positive-এ স্থির করা ব্লকিং র্যাচেট) — 0-তে পৌঁছাতে 3টিকে allowlist করুন, অথবা পরামর্শমূলক স্তরে নামিয়ে দিন। GitHub-এর নেটিভ secret-scanning + `check:public-creds`-এর সঙ্গে ওভারল্যাপ করে। -- `check:pr-evidence` (ব্লকিং, PR-body-এর গদ্য grep করে) — false-positive-এর উচ্চ ঝুঁকি; বাদ দিলে Hard Rule #18-এর প্রয়োগ দুর্বল হয়, তাই এটি প্রকৃতপক্ষেই একটি নীতিগত সিদ্ধান্ত। -- `semgrep` (পরামর্শমূলক স্বতন্ত্র) — OWASP ফ্যামিলিগুলোর ক্ষেত্রে CodeQL-এর সঙ্গে ওভারল্যাপ করে; এর বেসলাইনকে একটি র্যাচেটের সঙ্গে সংযুক্ত করুন অথবা বাদ দিন। +- `check:openapi-security-tiers` (পরামর্শমূলক) — ❌ **পরিষ্কারভাবে ফ্লিপযোগ্য নয়।** এটি 0 দিয়ে এক্সিট করে, কিন্তু সতর্ক করে যে `LOCAL_ONLY_API_PREFIXES`-এর অধীন কয়েকটি `traffic-inspector` রুটে `x-loopback-only: true` অ্যানোটেশন নেই। এটি বলবৎ করার আগে `openapi.yaml`-এ ওই অ্যানোটেশনগুলো যোগ করতে হবে। +- `typecheck:noimplicit:core` (পরামর্শমূলক) — ব্লকিং `check:type-coverage` র্যাচেট এটিকে অনেকাংশে অন্তর্ভুক্ত করে। এটিকে র্যাচেটে ফ্লিপ করুন অথবা অপ্রয়োজনীয় দ্বিতীয় `tsc` পাসটি বাদ দিন। +- `test:vitest:ui` (এখন **ব্লকিং**) — আগে থেকে থাকা ব্যর্থতাগুলো `vitest.config.ts`-এ `// #8618` ট্র্যাকিং মন্তব্যসহ স্পষ্টভাবে বাদ দেওয়া হয়েছে; নতুন ব্যর্থতা জবটিকে ব্যর্থ করবে। +- `check:secrets` (gitleaks, 3টি নথিবদ্ধ false-positive-এ স্থির করা ব্লকিং র্যাচেট) — 0-তে পৌঁছাতে 3টিকে allowlist করুন, অথবা পরামর্শমূলক স্তরে নামিয়ে দিন। GitHub-এর নেটিভ secret-scanning + `check:public-creds`-এর সঙ্গে ওভারল্যাপ করে। +- `check:pr-evidence` (ব্লকিং, PR-body-এর গদ্য grep করে) — false-positive-এর উচ্চ ঝুঁকি; বাদ দিলে Hard Rule #18-এর প্রয়োগ দুর্বল হবে, তাই এটি সত্যিকারের নীতিগত সিদ্ধান্ত। +- `semgrep` (পরামর্শমূলক স্বতন্ত্র) — OWASP পরিবারগুলোর ক্ষেত্রে CodeQL-এর সঙ্গে ওভারল্যাপ করে; এর baseline-কে একটি র্যাচেটের সঙ্গে সংযুক্ত করুন অথবা বাদ দিন। --- diff --git a/docs/i18n/bn/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/bn/docs/compression/COMPRESSION_GUIDE.md index 0993cfbaf8de..c2cdeda8552c 100644 --- a/docs/i18n/bn/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/bn/docs/compression/COMPRESSION_GUIDE.md @@ -437,7 +437,7 @@ const { messages: aged, saved } = applyAging(messages, { `applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`) ক্যাটালগের বিরুদ্ধে নির্বাচনকে সমাধান করে (অজানা আইডি এবং লোকেল-বেমানান স্টাইলগুলি বাদ দেওয়া হয়, এটি কখনোই ত্রুটি নয়), ক্যাটালগ ক্রমে নির্বাচিত নির্দেশাবলীকে সংযুক্ত করে, বাউন্ডারি ক্লজটি **একবার** যুক্ত করে, এবং একটি একক আইডম্পোটেন্সি মার্কার (`[OmniRoute Output Styles]`) দিয়ে ব্লকটি শুরু করে, যাতে পুনরায় প্রয়োগ করা একটি নো-অপ হয়। যখন নির্ধারিত ভাষার (নিচে ভাষা নির্বাচন দেখুন) একটি অনুবাদ থাকে, তখন ইংরেজির পরিবর্তে স্থানীয় নির্দেশিকা ইনজেক্ট করা হয়। -যে বডিতে `messages` আছে, সেখানে একটি কন্টেন্ট বাইপাস (`open-sse/services/compression/outputMode.ts`-এ `shouldBypassCavemanOutputMode()`) শেষ তিনটি মেসেজ পরীক্ষা করে এবং যখন সেগুলি তার নিরাপত্তা, অপরিবর্তনীয়-অ্যাকশন, স্পষ্টীকরণ, বা অর্ডার-সংবেদনশীল কীওয়ার্ডগুলির সাথে মিলে যায় তখন পুরো টার্নের জন্য স্টাইলগুলি এড়িয়ে যায়। ড্যাশবোর্ডের **Auto-Clarity Bypass** টগল (`cavemanOutputMode.autoClarity`) যা সেট করা আছে তা নির্বিশেষে বাইপাসটি চলে। +যে বডিতে `messages` আছে, সেখানে একটি কন্টেন্ট বাইপাস (`open-sse/services/compression/outputMode.ts`-এ `shouldBypassCavemanOutputMode()`) শেষ তিনটি মেসেজ পরীক্ষা করে এবং যখন সেগুলি তার নিরাপত্তা, অপরিবর্তনীয়-অ্যাকশন, স্পষ্টীকরণ, বা অর্ডার-সংবেদনশীল কীওয়ার্ডগুলির সাথে মিলে যায় তখন পুরো টার্নের জন্য স্টাইলগুলি এড়িয়ে যায়। ড্যাশবোর্ডের **Auto-Clarity Bypass** টগল (`cavemanOutputMode.autoClarity`) চালু থাকলে বাইপাসটি চলে, যা ডিফল্ট; টগলটি বন্ধ থাকলে নির্বাচিত স্টাইলগুলি সেই টার্নগুলিতেও প্রযোজ্য হয়। যখন বাইপাস টার্নটিকে যেতে দেয়, তখন `placeSystemInstruction()` (একই ফাইল), যা কখনোই একটি নতুন `messages[0]` তৈরি করে না, ব্লকটিকে নিম্নলিখিতগুলির মধ্যে প্রথমটিতে রাখে যা এটি খুঁজে পায়: diff --git a/docs/i18n/bn/docs/reference/ENVIRONMENT.md b/docs/i18n/bn/docs/reference/ENVIRONMENT.md index 8054c9f4a981..7af8fa00dc60 100644 --- a/docs/i18n/bn/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/bn/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. প্রয়োজনীয় সিক্রেট -প্রথমবার চালানোর আগেই এগুলো **অবশ্যই** সেট করতে হবে। এগুলো ছাড়া অ্যাপ্লিকেশনটি হয় চালু হতে অস্বীকার করবে, নয়তো অনিরাপদ ডিফল্ট দিয়ে চলবে। - -| ভেরিয়েবল | প্রয়োজনীয় | ডিফল্ট | সোর্স ফাইল | বিবরণ | -| ---------------------------- | --------------------- | --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **হ্যাঁ** | _(কোনোটিই নয়)_ | `src/lib/auth` | সব ড্যাশবোর্ড সেশন কুকি (JWT) সাইন/যাচাই করে। `openssl rand -base64 48` দিয়ে তৈরি করুন। | -| `API_KEY_SECRET` | **হ্যাঁ** | _(কোনোটিই নয়)_ | `src/lib/db/apiKeys.ts` | SQLite-এ সংরক্ষিত API কী-এর মানগুলোর জন্য AES এনক্রিপশন কী। `openssl rand -hex 32` দিয়ে তৈরি করুন। | -| `INITIAL_PASSWORD` | **হ্যাঁ** | `CHANGEME` | বুটস্ট্র্যাপ স্ক্রিপ্ট | প্রাথমিক অ্যাডমিন ড্যাশবোর্ড পাসওয়ার্ড সেট করে (`.env.example`-এর ডিফল্টের সঙ্গে মেলে—পরিবর্তন করতে বাধ্য করার জন্য এটিকে স্পষ্টতই অনিরাপদ রাখা হয়েছে)। **প্রথমবার ব্যবহারের আগেই পরিবর্তন করুন।** লগইন করার পর Dashboard → Settings → Security-এর মাধ্যমে পরিবর্তন করুন। | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **হ্যাঁ** (প্রোডাকশন) | _(সেট করা নেই)_ | `src/app/api/internal/codex-responses-ws/route.ts` | অভ্যন্তরীণ Codex Responses WebSocket ব্রিজের শেয়ার্ড সিক্রেট। Electron/browser WS রিলে এবং OmniRoute-এর মধ্যকার ব্রিজ রিকোয়েস্ট প্রমাণীকরণ করে। ⚠️ **প্রোডাকশনে আবশ্যক—এটি সেট করা না থাকলে সব WS ব্রিজ রিকোয়েস্ট প্রত্যাখ্যাত হয়।** `openssl rand -base64 32` দিয়ে তৈরি করুন। | -| `OMNIROUTE_SW_BUILD_ID` | না | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA শেলের (#11779) জন্য সুস্পষ্ট সার্ভিস-ওয়ার্কার ক্যাশ-বাস্টিং আইডি; রেজোলিউশন চেইনে প্রথম। | -| `SOURCE_VERSION` | না | _(সেট করা নেই)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | চেইনের দ্বিতীয়টি—ডিপ্লয় করা কমিট হিসেবে PaaS বিল্ডারগুলো (যেমন Heroku-ধাঁচের) এটি সেট করে। | -| `NEXT_PUBLIC_SW_BUILD_ID` | না | _(উদ্ভূত)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` রেজিস্টার করতে ক্লায়েন্ট যে বিল্ড-টাইম পাবলিক মান ব্যবহার করে; ওপরের দুটি থেকে এবং তারপর git SHA থেকে উদ্ভূত। | -| `OMNIROUTE_PEER_STAMP_TOKEN` | না (স্বয়ংক্রিয়) | _(প্রতি বুটে স্বয়ংক্রিয়)_ | `src/server/authz/policies/management.ts` | বিশ্বস্ত পিয়ার-IP স্ট্যাম্পটি যে OmniRoute-এর নিজস্ব HTTP সার্ভার (`scripts/dev/peer-stamp.mjs`) থেকে এসেছে, তা প্রমাণকারী প্রতিটি প্রসেসের নিজস্ব সিক্রেট। স্ট্যাম্পে এই টোকেন থাকলেই কেবল authz মিডলওয়্যার রিকোয়েস্টের লোক্যালিটি (LOCAL_ONLY রুটগুলোর loopback/LAN গেটিং) বিশ্বাস করে। প্রতিটি বুটে স্বয়ংক্রিয়ভাবে তৈরি হয়—এটি সেট না করাই ভালো; শুধু যেসব মাল্টি-প্রসেস সেটআপে স্ট্যাম্পটি শেয়ার করা আবশ্যক, সেগুলোর জন্য এটিকে নির্দিষ্ট করে দিন। | -| `OMNIROUTE_TRUSTED_PROXIES` | না | _(সেট করা নেই)_ | `scripts/dev/peer-stamp.mjs` | যেসব রিভার্স প্রক্সির `X-Forwarded-For` / `X-Real-IP`-কে IP অনুমোদন/অস্বীকৃতি তালিকা বিশ্বাস করতে পারে, সেগুলোর কমা দিয়ে পৃথক করা IP বা CIDR রেঞ্জ। এটি loopback, প্রাইভেট-নেটওয়ার্ক অ্যাড্রেস এবং Cloudflare edge-এর অতিরিক্ত, যেগুলো সবসময় বিশ্বস্ত। অন্য কোনো পাবলিক অ্যাড্রেসে থাকা প্রক্সির ক্ষেত্রেই শুধু এটি প্রয়োজন; অন্যথায় ফিল্টারটি সেই প্রক্সির নিজস্ব অ্যাড্রেসকেই মূল্যায়ন করে। | +**হ্যাঁ** চিহ্নিত কিন্তু সেট করা নেই এমন সিক্রেটগুলো প্রথম রানেই স্বয়ংক্রিয়ভাবে তৈরি হয় (`JWT_SECRET`, `API_KEY_SECRET`) এবং ডেটা ডিরেক্টরির `server.env`-এ সংরক্ষিত থাকে — সেই ডিরেক্টরি ক্ষণস্থায়ী (ephemeral) হলে এগুলো স্পষ্টভাবে সেট করুন। ব্যতিক্রম হলো `OMNIROUTE_WS_BRIDGE_SECRET`: এটি `server.env`-এ সংরক্ষিত হয় না — সার্ভার entrypoint-গুলো প্রতি বুটে একটি ক্ষণস্থায়ী মান তৈরি করে যা কেবল প্রসেস-অভ্যন্তরীণ ব্রিজ ব্যবহার করতে পারে, তাই বাহ্যিক ব্রিজ ক্লায়েন্টদের এটি স্পষ্টভাবে সেট করতে হবে। + +| ভেরিয়েবল | প্রয়োজনীয় | ডিফল্ট | সোর্স ফাইল | বিবরণ | +| ---------------------------- | --------------------- | --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **হ্যাঁ** | _(কোনোটিই নয়)_ | `src/lib/auth` | সব ড্যাশবোর্ড সেশন কুকি (JWT) সাইন/যাচাই করে। `openssl rand -base64 48` দিয়ে তৈরি করুন। | +| `API_KEY_SECRET` | **হ্যাঁ** | _(কোনোটিই নয়)_ | `src/lib/db/apiKeys.ts` | SQLite-এ সংরক্ষিত API কী-এর মানগুলোর জন্য AES এনক্রিপশন কী। `openssl rand -hex 32` দিয়ে তৈরি করুন। | +| `INITIAL_PASSWORD` | না | _(সেট করা নেই)_ | `src/lib/auth/managementPassword.ts` | এখনো কোনো পাসওয়ার্ড সংরক্ষিত না থাকলে স্টার্টআপে ড্যাশবোর্ড পাসওয়ার্ড সেট করে; একবার পাসওয়ার্ড সংরক্ষিত হয়ে গেলে এটি উপেক্ষিত হয়। npm এবং সোর্স ইনস্টলগুলো `.env.example`-কে `.env`-এ কপি করে, তাই সেগুলো এর সর্বজনবিদিত প্লেসহোল্ডার `CHANGEME` দিয়ে শুরু হয়: প্রথম বুটের আগে এটি পরিবর্তন করুন, অথবা সেট করা নেই এমন মান (Docker ইমেজের ডিফল্ট) যেমন করে তেমনি প্রথম ভিজিটেই অনবোর্ডিং উইজার্ডে পাসওয়ার্ড তৈরি করতে সেই `.env`-এ `INITIAL_PASSWORD=` (খালি) সেট করুন। সংরক্ষিত পাসওয়ার্ড পরিবর্তন করুন Dashboard → Settings → Security-তে, অথবা সার্ভারের ডেটা ডিরেক্টরিতে `DATA_DIR` সেট করে `omniroute reset-password` চালান (সোর্স চেকআউটে `node bin/reset-password.mjs`)। | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **হ্যাঁ** (প্রোডাকশন) | _(সেট করা নেই)_ | `src/app/api/internal/codex-responses-ws/route.ts` | অভ্যন্তরীণ Codex Responses WebSocket ব্রিজের শেয়ার্ড সিক্রেট। Electron/browser WS রিলে এবং OmniRoute-এর মধ্যকার ব্রিজ রিকোয়েস্ট প্রমাণীকরণ করে। ⚠️ **প্রোডাকশনে আবশ্যক—এটি সেট করা না থাকলে সব WS ব্রিজ রিকোয়েস্ট প্রত্যাখ্যাত হয়।** `openssl rand -base64 32` দিয়ে তৈরি করুন। | +| `OMNIROUTE_SW_BUILD_ID` | না | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA শেলের (#11779) জন্য সুস্পষ্ট সার্ভিস-ওয়ার্কার ক্যাশ-বাস্টিং আইডি; রেজোলিউশন চেইনে প্রথম। | +| `SOURCE_VERSION` | না | _(সেট করা নেই)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | চেইনের দ্বিতীয়টি—ডিপ্লয় করা কমিট হিসেবে PaaS বিল্ডারগুলো (যেমন Heroku-ধাঁচের) এটি সেট করে। | +| `NEXT_PUBLIC_SW_BUILD_ID` | না | _(উদ্ভূত)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` রেজিস্টার করতে ক্লায়েন্ট যে বিল্ড-টাইম পাবলিক মান ব্যবহার করে; ওপরের দুটি থেকে এবং তারপর git SHA থেকে উদ্ভূত। | +| `OMNIROUTE_PEER_STAMP_TOKEN` | না (স্বয়ংক্রিয়) | _(প্রতি বুটে স্বয়ংক্রিয়)_ | `src/server/authz/policies/management.ts` | বিশ্বস্ত পিয়ার-IP স্ট্যাম্পটি যে OmniRoute-এর নিজস্ব HTTP সার্ভার (`scripts/dev/peer-stamp.mjs`) থেকে এসেছে, তা প্রমাণকারী প্রতিটি প্রসেসের নিজস্ব সিক্রেট। স্ট্যাম্পে এই টোকেন থাকলেই কেবল authz মিডলওয়্যার রিকোয়েস্টের লোক্যালিটি (LOCAL_ONLY রুটগুলোর loopback/LAN গেটিং) বিশ্বাস করে। প্রতিটি বুটে স্বয়ংক্রিয়ভাবে তৈরি হয়—এটি সেট না করাই ভালো; শুধু যেসব মাল্টি-প্রসেস সেটআপে স্ট্যাম্পটি শেয়ার করা আবশ্যক, সেগুলোর জন্য এটিকে নির্দিষ্ট করে দিন। | +| `OMNIROUTE_TRUSTED_PROXIES` | না | _(সেট করা নেই)_ | `scripts/dev/peer-stamp.mjs` | যেসব রিভার্স প্রক্সির `X-Forwarded-For` / `X-Real-IP`-কে IP অনুমোদন/অস্বীকৃতি তালিকা বিশ্বাস করতে পারে, সেগুলোর কমা দিয়ে পৃথক করা IP বা CIDR রেঞ্জ। এটি loopback, প্রাইভেট-নেটওয়ার্ক অ্যাড্রেস এবং Cloudflare edge-এর অতিরিক্ত, যেগুলো সবসময় বিশ্বস্ত। অন্য কোনো পাবলিক অ্যাড্রেসে থাকা প্রক্সির ক্ষেত্রেই শুধু এটি প্রয়োজন; অন্যথায় ফিল্টারটি সেই প্রক্সির নিজস্ব অ্যাড্রেসকেই মূল্যায়ন করে। | ### তৈরির কমান্ড @@ -215,38 +215,38 @@ OmniRoute সমস্ত স্থায়ী ডেটা সংরক্ষ ## 4. নিরাপত্তা ও প্রমাণীকরণ -| ভেরিয়েবল | ডিফল্ট | সোর্স ফাইল | বিবরণ | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | মেশিন ফিঙ্গারপ্রিন্টিংয়ের জন্য হার্ডওয়্যার শনাক্তকারীর সঙ্গে সমন্বিত সল্ট। বিচ্ছিন্নতা নিশ্চিত করতে প্রতিটি ডিপ্লয়মেন্টে এটি পরিবর্তন করুন। | -| `OMNIROUTE_CLI_SALT` | _(সেট করা নেই = প্রতিটি ইনস্টলে এলোমেলো সল্ট, যা `/cli-token-salt.json`-এ সংরক্ষিত থাকে)_ | `src/lib/machineToken.ts` | স্থানীয় CLI প্রমাণীকরণ টোকেন নির্ণয়ের জন্য HMAC সল্ট। এই মান সেট করলে মেশিনের সব CLI টোকেন রোটেট হয় এবং এটি সবসময় সংরক্ষিত সল্টের চেয়ে অগ্রাধিকার পায়। `docs/security/CLI_TOKEN.md` দেখুন। | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | সেশন কুকিতে `Secure` ফ্ল্যাগ সেট করে। HTTPS-এর পেছনে চালানোর সময় এটি **অবশ্যই `true` হতে হবে**। | -| `REQUIRE_API_KEY` | `false` | API মিডলওয়্যার | `true` হলে, সব `/v1/*` প্রক্সি অনুরোধে একটি বৈধ API কী অন্তর্ভুক্ত থাকতে হবে। **এই ফ্ল্যাগটি `GET /v1/models`-এর প্রবেশাধিকার নিয়ন্ত্রণ করে না**; বরং এটি ড্যাশবোর্ড লগইন নীতি (`requireAuthForModels`) অনুসরণ করে—তাই `/v1/models` থেকে একটি `401` পাওয়ার অর্থ এই নয় যে ইনফারেন্স সুরক্ষিত। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) দেখুন। | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ড্যাশবোর্ড UI-তে সম্পূর্ণ API কী-এর মান প্রকাশের অনুমতি দেয়। ড্যাশবোর্ডের ফিচার ফ্ল্যাগ থেকে কনফিগার করা যায়; শেয়ার্ড ইনস্ট্যান্সে এটি একটি নিরাপত্তা ঝুঁকি। | -| `NO_LOG_API_KEY_IDS` | _(খালি)_ | `src/lib/compliance/index.ts` | কমা দিয়ে পৃথক করা API কী ID, যেগুলোর অনুরোধ লগ করা হয় না (GDPR সম্মতি)। | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(সেট করা নেই = সীমাহীন)_ | `src/shared/utils/apiKeyPolicy.ts` | যেসব API কী-এর `rate_limits` কলাম null, সেগুলোর ক্ষেত্রে প্রয়োগ করা দৈনিক অনুরোধ বাজেটের ফলব্যাক। সেট করা না থাকলে বা খালি থাকলে: কোনো অন্তর্নিহিত সীমা নেই (#2289, #11017)। `0`-এর অর্থও একই (সীমাহীন)। ধনাত্মক পূর্ণসংখ্যা N দিলে N/দিন, 5N/সপ্তাহ এবং 20N/মাস সক্রিয় হয়। ত্রুটিপূর্ণ অ-খালি মানের ক্ষেত্রে পুরোনো 1000/দিন, 5000/সপ্তাহ এবং 20000/মাস সীমা ব্যবহৃত হয়। | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | অনুরোধের বডির সর্বাধিক অনুমোদিত আকার। এই সীমা অতিক্রমকারী পেলোড প্রত্যাখ্যান করে। | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | এই থ্রেশহোল্ডের সমান বা বেশি আকারের প্রকৃত অনুরোধ বডি JSON পার্সিংয়ের আগে অ্যাটমিক, প্রসেস-লোকাল হেভিওয়েট অ্যাডমিশন লিজ গ্রহণ করে (BYTE পাথ, `POST /v1/responses`-সহ)। স্ট্রাকচার-হেভি পাথের মতো একই [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) স্বাস্থ্যকর-হেডরুম এস্কেপ; তবুও এটি `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) দ্বারা সীমাবদ্ধ, যাতে [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) পুনরায় দেখা না দেয়। | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | সীমাবদ্ধ ইনজেশনের সময় পড়া বাইটের ওপর প্রয়োগ করা চ্যাট-রুটের হার্ড সীমা; এর মধ্যে অনুপস্থিত, অবৈধ বা অসত্য `Content-Length`-সহ অনুরোধও অন্তর্ভুক্ত; সীমা অতিক্রম করলে `413` পাওয়া যায়। | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(সেট করা নেই — অনুরোধ-সংখ্যার কোনো সীমা নেই)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** এই লিগ্যাসি অনুরোধ-সংখ্যা সীমা এখন কেবল স্পষ্টভাবে সেট করা হলেই কার্যকর হয়। এটি সেট না থাকলে (ডিফল্ট), হেভিওয়েট চ্যাট অ্যাডমিশন পরিবর্তে `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` দ্বারা নিয়ন্ত্রিত হয় — **একটি প্রসেসে** (একটি V8 হিপ) প্রসেসের প্রকৃত মেমরি সীমা থেকে স্বয়ংক্রিয়ভাবে নির্ধারিত একটি বাইট বাজেট। পরস্পর ওভারল্যাপ করা প্রায় 750k-টোকেনের দুটি `/v1/responses` প্রায় 12 GiB হিপকে অ্যাবোর্ট করায় (#7849) — এটি একটি **মেমরি-বাজেট** সতর্কতা, 2-এর কোনো কঠোর পণ্যসীমা নয়। একটি সুস্থ প্রসেস (শেড অনুপাতের নিচে হিপ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`-এর মাধ্যমে আরও বেশি সমবর্তী দীর্ঘ `/v1/responses` গ্রহণ করতে পারে। দশের ঘরে দীর্ঘ SSE ক্লায়েন্ট (40–50) হিপ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110-এর বিষয়, “সর্বোচ্চ 2” নয়। “হোস্ট ব্যবহার করার” জন্য না বুঝে এটি বাড়ালে #7849 আবার দেখা দেবে। **Nটি স্বাধীন `DATA_DIR`** দিয়ে **হিপের** সংখ্যা বাড়ান (#11024); একটি SQLite ফাইলে কখনোই `replicas>1` ব্যবহার করবেন না। | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(স্বয়ংক্রিয়ভাবে নির্ধারিত)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** স্বয়ংক্রিয়ভাবে নির্ধারিত ইনজেস্ট বাইট বাজেটের ওভাররাইড (আরও সীমিত V8/cgroup মেমরি সীমার 25%, 8x ক্ষণস্থায়ী অ্যামপ্লিফিকেশন দিয়ে ভাগ করা)। নির্ধারিত এবং স্পষ্ট উভয় মানই 8 MiB–2 GiB-এর মধ্যে সীমাবদ্ধ থাকে। কার্যকর বাজেটের চেয়ে বড় বডি অবিলম্বে `413 body_exceeds_budget` দিয়ে ব্যর্থ হয়; পৃথকভাবে পরিবেশনযোগ্য বডিগুলোর মধ্যকার কনটেনশনে পুনরায় চেষ্টা-যোগ্য `503` পাওয়া যায়। 40–50টি সমবর্তী দীর্ঘ SSE ক্লায়েন্ট এই বাজেট + হিপের বিষয়, কোনো কঠোর “সর্বোচ্চ 2” নয়। টিউন করার আগে `/api/monitoring/health`-এ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` দেখুন। | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | বাইট ও স্ট্রাকচার হেভিওয়েট অ্যাডমিশনের জন্য হিপ-প্রেশার শেড অনুপাত (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` অতিক্রম করা কোনো সমবর্তী হেভিওয়েট অনুরোধকে কেবল তখনই পুনরায় চেষ্টা-যোগ্য `503` দিয়ে শেড করা হয়, যখন হিপও এই অনুপাতের সমান বা বেশি থাকে; সুস্থ হিপে এটি পরিবর্তে হেলদি-হেডরুমের মাধ্যমে গ্রহণ করা হয়। | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ডিফল্ট `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **STRUCTURE** এবং **BYTE** উভয়ের (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES`-এর সমান বা বড় বডিসহ) সুস্থ-হিপ ফাস্ট পাথের জন্য সীমাবদ্ধ অতিরিক্ত সক্ষমতা (#10437)। এই সীমা ছাড়া, প্রতিটি ব্যস্ত-কিন্তু-সুস্থ-হিপ অনুরোধ কোনো ঊর্ধ্বসীমা ছাড়াই অ্যাডমিশন বাইপাস করত। সুস্থ-হিপ পাথ দিয়ে এতগুলো সমবর্তী লিজ সক্রিয় হয়ে গেলে, পরবর্তী ব্যস্ত অনুরোধগুলো প্রকৃত হিপ চাপের সময় ব্যবহৃত একই সীমাবদ্ধ-অপেক্ষা/শেড পাথে চলে যায়। `0` বাইপাসটি সম্পূর্ণ নিষ্ক্রিয় করে। | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | যে বার্তা সংখ্যা কোনো চ্যাট অনুরোধের বডি বাইট থ্রেশহোল্ডের নিচে থাকলেও সেটিকে হেভিওয়েট হিসেবে শ্রেণিবদ্ধ করে। | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | যে টুল সংখ্যা কোনো চ্যাট অনুরোধের বডি বাইট থ্রেশহোল্ডের নিচে থাকলেও সেটিকে হেভিওয়েট হিসেবে শ্রেণিবদ্ধ করে। | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | রক্ষণশীল স্ট্রিং-আকারভিত্তিক টোকেন অনুমান, যা কোনো অনুরোধকে হেভিওয়েট হিসেবে শ্রেণিবদ্ধ করে; এটি অ্যাডমিশন-খরচের একটি প্রক্সি, প্রোভাইডারের বিলিং টোকেনাইজেশন নয়। | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (নিষ্ক্রিয়) | `src/shared/middleware/chatBodyAdmission.ts` | ঐচ্ছিক অপ্ট-ইন চ্যাট ইতিহাস সীমা। ডিফল্টভাবে নিষ্ক্রিয়: বার্তার সংখ্যা একটি ডিপ্লয়মেন্ট নীতি, কোনো অনুরোধের সার্বজনীন বৈশিষ্ট্য নয়; এখানে সীমা আরোপ করলে কম্প্রেশন পাইপলাইন কথোপকথনগুলোকে পরিবেশনযোগ্য করে তোলার আগেই টার্মিনাল `413` দিয়ে সেগুলো প্রত্যাখ্যান করা হয়। হিপের বৃদ্ধি `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` এবং হিপ-প্রেশার শেড দ্বারা সীমাবদ্ধ থাকে। যেসব মেমরি-সীমাবদ্ধ ডিপ্লয়মেন্টে কঠোর ঊর্ধ্বসীমা দরকার, সেখানে একটি ধনাত্মক মান সেট করুন; সীমা অতিক্রম করলে তখন কাঠামোবদ্ধ কম্প্যাক্ট-প্রয়োজনীয় `413` পাওয়া যায়। | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | সম্পূর্ণভাবে মেমরিতে বাফার করা একটি নন-স্ট্রিমিং আপস্ট্রিম রেসপন্সের হার্ড সীমা। এটি অতিক্রম করলে আপস্ট্রিম রিডার বাতিল করা হয় এবং হিপ নিঃশেষ না হওয়া পর্যন্ত সীমাহীন স্ট্রিং বাড়তে দেওয়ার পরিবর্তে অনুরোধটি দ্রুত ব্যর্থ হয়। | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | আপস্ট্রিম রেসপন্স হেডার থেকে ফরওয়ার্ড করা সর্বোচ্চ ওয়্যার বাইট। বাজেট অতিক্রম করলে, সাধারণ রিভার্স-প্রক্সি হেডার সীমার মধ্যে থাকতে নিম্ন-অগ্রাধিকারের হেডারগুলো (যেমন, কাস্টম `x-codex-*`, `x-oai-request-id`) বাদ দেওয়া হয়। রেসপন্স হেডারের আকার বৃদ্ধির বিনিময়ে আরও বেশি আপস্ট্রিম মেটাডেটা ফরওয়ার্ড করতে এটিকে উচ্চতর মানে সেট করুন। | -| `CORS_ORIGIN` | _(সেট করা নেই)_ | `src/server/cors/origins.ts` | পুরোনো একক-অরিজিন CORS অনুমোদন-তালিকা। নতুন ডিপ্লয়মেন্টের জন্য `CORS_ALLOWED_ORIGINS` ব্যবহার করুন। CORS কেবল ক্রস-অরিজিন ব্রাউজার API ক্লায়েন্টের জন্য; এর পরিবর্তে প্রমাণীকৃত ড্যাশবোর্ড রাইট অপারেশনগুলো একই-অরিজিন রিকোয়েস্ট এবং সেশন-সংযুক্ত CSRF সুরক্ষা ব্যবহার করে। | -| `CORS_ALLOWED_ORIGINS` | _(সেট করা নেই)_ | `src/server/cors/origins.ts` | কমা দিয়ে পৃথক করা CORS অনুমোদন-তালিকা। `CORS_ALLOW_ALL=true` স্পষ্টভাবে কনফিগার করা না হলে কোনো ওয়াইল্ডকার্ড পাঠানো হয় না। | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | যেকোনো ব্রাউজার `Origin` প্রতিধ্বনিত করার জন্য কেবল ডেভেলপমেন্টে ব্যবহারের জরুরি বিকল্প। শেয়ার্ড বা প্রোডাকশন ডিপ্লয়মেন্টে এটি সক্রিয় করবেন না। | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | প্রাইভেট/লুপব্যাক/লিংক-লোকাল IP রেঞ্জ লক্ষ্য করে করা প্রোভাইডার কল ব্লক করুন। কেবল বিচ্ছিন্ন টেস্ট এনভায়রনমেন্টে এটি নিষ্ক্রিয় করুন। | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | প্রাইভেট/লোকাল নেটওয়ার্কের দিকে নির্দেশ করা প্রোভাইডার URL অনুমোদন করুন (localhost, 192.168.x.x, 10.x.x.x, ইত্যাদি)। **স্ব-হোস্টেড প্রোভাইডারের জন্য আবশ্যক** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)। `false` হলে, ড্যাশবোর্ড লোকাল URL-এর ভ্যালিডেশন প্রত্যাখ্যান করে। | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | লোকাল/প্রাইভেট ঠিকানায় (127.0.0.1, localhost, LAN, প্রাইভেট রেঞ্জ) প্রোভাইডার যোগ/ভ্যালিডেট করার অনুমতি দিন — এটি প্রোভাইডার ভ্যালিডেশন পাথের মধ্যে সীমাবদ্ধ। **ডিফল্ট `true`** (লোকাল-ফার্স্ট); কঠোরভাবে শুধু পাবলিক ঠিকানা অনুমোদন করতে `false` সেট করুন। ক্লাউড-মেটাডেটা এন্ডপয়েন্টগুলো (169.254.169.254, metadata.google.internal) যেকোনো অবস্থায় ব্লকই থাকে। (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` রুটগুলোকে (ট্রান্সক্রিপশন, স্পিচ, অনুবাদ) localhost-এর বাইরে হোস্ট করা OpenAI-সামঞ্জস্যপূর্ণ প্রোভাইডার নোড ব্যবহার করতে দিন। ডিফল্টভাবে বন্ধ — অডিওকে রিমোট হোস্টে রাউট করলে ইগ্রেস পরিচয় বদলে যায় এবং এটি অপারেটরের স্পষ্ট সিদ্ধান্ত হতে হবে। লুপব্যাক/প্রাইভেট নোডগুলো (localhost, 127.0.0.1, 172.16-31.x) সর্বদা অনুমোদিত এবং প্রভাবিত হয় না। (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`-কে (এবং মেমরি ইঞ্জিনের লুপব্যাক রির্যাঙ্ক ধাপকে) localhost-এর বাইরে হোস্ট করা OpenAI-সামঞ্জস্যপূর্ণ প্রোভাইডার নোড ব্যবহার করতে দিন — যেমন TEI, Infinity, vLLM ইত্যাদি চালানো কোনো LAN বক্স বা Tailscale পিয়ার। ডিফল্টভাবে বন্ধ — রিমোট হোস্টে রাউট করলে ইগ্রেস পরিচয় বদলে যায় এবং এটি অপারেটরের স্পষ্ট সিদ্ধান্ত হতে হবে। লুপব্যাক নোডগুলো (localhost, 127.0.0.1, 172.16-31.x) সর্বদা অনুমোদিত এবং প্রভাবিত হয় না। রিমোট নোডগুলোকে প্রোভাইডারের আউটবাউন্ড URL নীতিও (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) পাস করতে হবে; ক্লাউড-মেটাডেটা হোস্টে কখনোই রাউট করা হয় না। | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC সক্রিয় থাকলে পাসওয়ার্ড লগইন নিষ্ক্রিয় করুন, যাতে ব্যবহারকারীরা কেবল OIDC Single Sign-On-এর মাধ্যমে প্রমাণীকরণ করতে পারেন। সংক্ষিপ্ত অ্যালিয়াস `OIDC_DISABLE_PASSWORD_LOGIN`-ও গ্রহণযোগ্য; একই কী-এর Dashboard Feature Flag অগ্রাধিকার পায়। (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-এর সংক্ষিপ্ত অ্যালিয়াস (#10889)। | +| ভেরিয়েবল | ডিফল্ট | সোর্স ফাইল | বিবরণ | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | মেশিন ফিঙ্গারপ্রিন্টিংয়ের জন্য হার্ডওয়্যার শনাক্তকারীর সঙ্গে সমন্বিত সল্ট। বিচ্ছিন্নতা নিশ্চিত করতে প্রতিটি ডিপ্লয়মেন্টে এটি পরিবর্তন করুন। | +| `OMNIROUTE_CLI_SALT` | _(সেট করা নেই = প্রতিটি ইনস্টলে এলোমেলো সল্ট, যা `/cli-token-salt.json`-এ সংরক্ষিত থাকে)_ | `src/lib/machineToken.ts` | স্থানীয় CLI প্রমাণীকরণ টোকেন নির্ণয়ের জন্য HMAC সল্ট। এই মান সেট করলে মেশিনের সব CLI টোকেন রোটেট হয় এবং এটি সবসময় সংরক্ষিত সল্টের চেয়ে অগ্রাধিকার পায়। `docs/security/CLI_TOKEN.md` দেখুন। | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | সেশন কুকিতে `Secure` ফ্ল্যাগ সেট করে। HTTPS-এর পেছনে চালানোর সময় এটি **অবশ্যই `true` হতে হবে**। | +| `REQUIRE_API_KEY` | `false` | API মিডলওয়্যার | `true` হলে, সব `/v1/*` প্রক্সি অনুরোধে একটি বৈধ API কী অন্তর্ভুক্ত থাকতে হবে। **এই ফ্ল্যাগটি `GET /v1/models`-এর প্রবেশাধিকার নিয়ন্ত্রণ করে না**; বরং এটি ড্যাশবোর্ড লগইন নীতি (`requireAuthForModels`) অনুসরণ করে—তাই `/v1/models` থেকে একটি `401` পাওয়ার অর্থ এই নয় যে ইনফারেন্স সুরক্ষিত। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) দেখুন। | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ড্যাশবোর্ড UI-তে সম্পূর্ণ API কী-এর মান প্রকাশের অনুমতি দেয়। ড্যাশবোর্ডের ফিচার ফ্ল্যাগ থেকে কনফিগার করা যায়; শেয়ার্ড ইনস্ট্যান্সে এটি একটি নিরাপত্তা ঝুঁকি। | +| `NO_LOG_API_KEY_IDS` | _(খালি)_ | `src/lib/compliance/index.ts` | কমা দিয়ে পৃথক করা API কী ID, যেগুলোর অনুরোধ লগ করা হয় না (GDPR সম্মতি)। | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(সেট করা নেই = সীমাহীন)_ | `src/shared/utils/apiKeyPolicy.ts` | যেসব API কী-এর `rate_limits` কলাম null, সেগুলোর ক্ষেত্রে প্রয়োগ করা দৈনিক অনুরোধ বাজেটের ফলব্যাক। সেট করা না থাকলে বা খালি থাকলে: কোনো অন্তর্নিহিত সীমা নেই (#2289, #11017)। `0`-এর অর্থও একই (সীমাহীন)। ধনাত্মক পূর্ণসংখ্যা N দিলে N/দিন, 5N/সপ্তাহ এবং 20N/মাস সক্রিয় হয়। ত্রুটিপূর্ণ অ-খালি মানের ক্ষেত্রে পুরোনো 1000/দিন, 5000/সপ্তাহ এবং 20000/মাস সীমা ব্যবহৃত হয়। | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | অনুরোধের বডির সর্বাধিক অনুমোদিত আকার। এই সীমা অতিক্রমকারী পেলোড প্রত্যাখ্যান করে। | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | এই থ্রেশহোল্ডের সমান বা বেশি আকারের প্রকৃত অনুরোধ বডি JSON পার্সিংয়ের আগে অ্যাটমিক, প্রসেস-লোকাল হেভিওয়েট অ্যাডমিশন লিজ গ্রহণ করে (BYTE পাথ, `POST /v1/responses`-সহ)। স্ট্রাকচার-হেভি পাথের মতো একই [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) স্বাস্থ্যকর-হেডরুম এস্কেপ; তবুও এটি `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) দ্বারা সীমাবদ্ধ, যাতে [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) পুনরায় দেখা না দেয়। | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | সীমাবদ্ধ ইনজেশনের সময় পড়া বাইটের ওপর প্রয়োগ করা চ্যাট-রুটের হার্ড সীমা; এর মধ্যে অনুপস্থিত, অবৈধ বা অসত্য `Content-Length`-সহ অনুরোধও অন্তর্ভুক্ত; সীমা অতিক্রম করলে `413` পাওয়া যায়। | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(সেট করা নেই — অনুরোধ-সংখ্যার কোনো সীমা নেই)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** এই লিগ্যাসি অনুরোধ-সংখ্যা সীমা এখন কেবল স্পষ্টভাবে সেট করা হলেই কার্যকর হয়। এটি সেট না থাকলে (ডিফল্ট), হেভিওয়েট চ্যাট অ্যাডমিশন পরিবর্তে `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` দ্বারা নিয়ন্ত্রিত হয় — **একটি প্রসেসে** (একটি V8 হিপ) প্রসেসের প্রকৃত মেমরি সীমা থেকে স্বয়ংক্রিয়ভাবে নির্ধারিত একটি বাইট বাজেট। পরস্পর ওভারল্যাপ করা প্রায় 750k-টোকেনের দুটি `/v1/responses` প্রায় 12 GiB হিপকে অ্যাবোর্ট করায় (#7849) — এটি একটি **মেমরি-বাজেট** সতর্কতা, 2-এর কোনো কঠোর পণ্যসীমা নয়। একটি সুস্থ প্রসেস (শেড অনুপাতের নিচে হিপ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`-এর মাধ্যমে আরও বেশি সমবর্তী দীর্ঘ `/v1/responses` গ্রহণ করতে পারে। দশের ঘরে দীর্ঘ SSE ক্লায়েন্ট (40–50) হিপ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110-এর বিষয়, “সর্বোচ্চ 2” নয়। “হোস্ট ব্যবহার করার” জন্য না বুঝে এটি বাড়ালে #7849 আবার দেখা দেবে। **Nটি স্বাধীন `DATA_DIR`** দিয়ে **হিপের** সংখ্যা বাড়ান (#11024); একটি SQLite ফাইলে কখনোই `replicas>1` ব্যবহার করবেন না। | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(স্বয়ংক্রিয়ভাবে নির্ধারিত)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** স্বয়ংক্রিয়ভাবে নির্ধারিত ইনজেস্ট বাইট বাজেটের ওভাররাইড (আরও সীমিত V8/cgroup মেমরি সীমার 25%, 8x ক্ষণস্থায়ী অ্যামপ্লিফিকেশন দিয়ে ভাগ করা)। নির্ধারিত এবং স্পষ্ট উভয় মানই 8 MiB–2 GiB-এর মধ্যে সীমাবদ্ধ থাকে। কার্যকর বাজেটের চেয়ে বড় বডি অবিলম্বে `413 body_exceeds_budget` দিয়ে ব্যর্থ হয়; পৃথকভাবে পরিবেশনযোগ্য বডিগুলোর মধ্যকার কনটেনশনে পুনরায় চেষ্টা-যোগ্য `503` পাওয়া যায়। 40–50টি সমবর্তী দীর্ঘ SSE ক্লায়েন্ট এই বাজেট + হিপের বিষয়, কোনো কঠোর “সর্বোচ্চ 2” নয়। টিউন করার আগে `/api/monitoring/health`-এ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` দেখুন। | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | বাইট ও স্ট্রাকচার হেভিওয়েট অ্যাডমিশনের জন্য হিপ-প্রেশার শেড অনুপাত (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` অতিক্রম করা কোনো সমবর্তী হেভিওয়েট অনুরোধকে কেবল তখনই পুনরায় চেষ্টা-যোগ্য `503` দিয়ে শেড করা হয়, যখন হিপও এই অনুপাতের সমান বা বেশি থাকে; সুস্থ হিপে এটি পরিবর্তে হেলদি-হেডরুমের মাধ্যমে গ্রহণ করা হয়। | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ডিফল্ট `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **STRUCTURE** এবং **BYTE** উভয়ের (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES`-এর সমান বা বড় বডিসহ) সুস্থ-হিপ ফাস্ট পাথের জন্য সীমাবদ্ধ অতিরিক্ত সক্ষমতা (#10437)। এই সীমা ছাড়া, প্রতিটি ব্যস্ত-কিন্তু-সুস্থ-হিপ অনুরোধ কোনো ঊর্ধ্বসীমা ছাড়াই অ্যাডমিশন বাইপাস করত। সুস্থ-হিপ পাথ দিয়ে এতগুলো সমবর্তী লিজ সক্রিয় হয়ে গেলে, পরবর্তী ব্যস্ত অনুরোধগুলো প্রকৃত হিপ চাপের সময় ব্যবহৃত একই সীমাবদ্ধ-অপেক্ষা/শেড পাথে চলে যায়। `0` বাইপাসটি সম্পূর্ণ নিষ্ক্রিয় করে। | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | যে বার্তা সংখ্যা কোনো চ্যাট অনুরোধের বডি বাইট থ্রেশহোল্ডের নিচে থাকলেও সেটিকে হেভিওয়েট হিসেবে শ্রেণিবদ্ধ করে। | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | যে টুল সংখ্যা কোনো চ্যাট অনুরোধের বডি বাইট থ্রেশহোল্ডের নিচে থাকলেও সেটিকে হেভিওয়েট হিসেবে শ্রেণিবদ্ধ করে। | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | রক্ষণশীল স্ট্রিং-আকারভিত্তিক টোকেন অনুমান, যা কোনো অনুরোধকে হেভিওয়েট হিসেবে শ্রেণিবদ্ধ করে; এটি অ্যাডমিশন-খরচের একটি প্রক্সি, প্রোভাইডারের বিলিং টোকেনাইজেশন নয়। | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (নিষ্ক্রিয়) | `src/shared/middleware/chatBodyAdmission.ts` | ঐচ্ছিক অপ্ট-ইন চ্যাট ইতিহাস সীমা। ডিফল্টভাবে নিষ্ক্রিয়: বার্তার সংখ্যা একটি ডিপ্লয়মেন্ট নীতি, কোনো অনুরোধের সার্বজনীন বৈশিষ্ট্য নয়; এখানে সীমা আরোপ করলে কম্প্রেশন পাইপলাইন কথোপকথনগুলোকে পরিবেশনযোগ্য করে তোলার আগেই টার্মিনাল `413` দিয়ে সেগুলো প্রত্যাখ্যান করা হয়। হিপের বৃদ্ধি `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` এবং হিপ-প্রেশার শেড দ্বারা সীমাবদ্ধ থাকে। যেসব মেমরি-সীমাবদ্ধ ডিপ্লয়মেন্টে কঠোর ঊর্ধ্বসীমা দরকার, সেখানে একটি ধনাত্মক মান সেট করুন; সীমা অতিক্রম করলে তখন কাঠামোবদ্ধ কম্প্যাক্ট-প্রয়োজনীয় `413` পাওয়া যায়। | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | সম্পূর্ণভাবে মেমরিতে বাফার করা একটি নন-স্ট্রিমিং আপস্ট্রিম রেসপন্সের হার্ড সীমা। এটি অতিক্রম করলে আপস্ট্রিম রিডার বাতিল করা হয় এবং হিপ নিঃশেষ না হওয়া পর্যন্ত সীমাহীন স্ট্রিং বাড়তে দেওয়ার পরিবর্তে অনুরোধটি দ্রুত ব্যর্থ হয়। | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | আপস্ট্রিম রেসপন্স হেডার থেকে ফরওয়ার্ড করা সর্বোচ্চ ওয়্যার বাইট। বাজেট অতিক্রম করলে, সাধারণ রিভার্স-প্রক্সি হেডার সীমার মধ্যে থাকতে নিম্ন-অগ্রাধিকারের হেডারগুলো (যেমন, কাস্টম `x-codex-*`, `x-oai-request-id`) বাদ দেওয়া হয়। রেসপন্স হেডারের আকার বৃদ্ধির বিনিময়ে আরও বেশি আপস্ট্রিম মেটাডেটা ফরওয়ার্ড করতে এটিকে উচ্চতর মানে সেট করুন। | +| `CORS_ORIGIN` | _(সেট করা নেই)_ | `src/server/cors/origins.ts` | পুরোনো একক-অরিজিন CORS অনুমোদন-তালিকা। নতুন ডিপ্লয়মেন্টের জন্য `CORS_ALLOWED_ORIGINS` ব্যবহার করুন। CORS কেবল ক্রস-অরিজিন ব্রাউজার API ক্লায়েন্টের জন্য; এর পরিবর্তে প্রমাণীকৃত ড্যাশবোর্ড রাইট অপারেশনগুলো একই-অরিজিন রিকোয়েস্ট এবং সেশন-সংযুক্ত CSRF সুরক্ষা ব্যবহার করে। | +| `CORS_ALLOWED_ORIGINS` | _(সেট করা নেই)_ | `src/server/cors/origins.ts` | কমা দিয়ে পৃথক করা CORS অনুমোদন-তালিকা। `CORS_ALLOW_ALL=true` স্পষ্টভাবে কনফিগার করা না হলে কোনো ওয়াইল্ডকার্ড পাঠানো হয় না। | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | যেকোনো ব্রাউজার `Origin` প্রতিধ্বনিত করার জন্য কেবল ডেভেলপমেন্টে ব্যবহারের জরুরি বিকল্প। শেয়ার্ড বা প্রোডাকশন ডিপ্লয়মেন্টে এটি সক্রিয় করবেন না। | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | লেগেসি অ্যালিয়াস। এর ড্যাশবোর্ড টগল (একটি DB ওভাররাইড) এই এনভায়রনমেন্ট মানের আগেই পড়া হয়; যেকোনো একটিতে `false`, `0`, `no` বা `off` থাকলে হোস্ট চেকগুলো বন্ধ হয়ে যায় — ঠিক যেভাবে `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` করে, এমনকি সেই ফ্ল্যাগের ড্যাশবোর্ড টগল বন্ধ অবস্থায় সেভ করা থাকলেও; অন্য মানগুলো গার্ডকে নিচের দুটি ফ্ল্যাগের হাতে ছেড়ে দেয়। | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | আউটবাউন্ড URL গার্ডের হোস্ট চেকগুলো — ক্লাউড-মেটাডেটা ব্লকসহ (পুরো 169.254.0.0/16, metadata.google.internal) — গার্ড মোড অনুসরণকারী প্রোভাইডার পাথগুলোতে বন্ধ করে দেয়: URL ভ্যালিডেশন, মডেল ডিসকভারি, প্রোভাইডার-নোডের base URL (রিমোট রির্যাঙ্ক নোডসহ) এবং প্রক্সি-ফলব্যাক টেস্ট, পাশাপাশি রিমোট ইমেজ/মিডিয়া ফেচের মতো অন্যান্য প্রোভাইডার-সংলগ্ন আউটবাউন্ড পাথ। এটি প্রাইভেট webhook টার্গেটও অনুমোদন করে, তবে ক্লাউড-মেটাডেটা/লিংক-লোকাল এন্ডপয়েন্ট ব্যতিক্রম — এই ফ্ল্যাগ চালু থাকলেও সেগুলো ব্লকই থাকে। `BaseExecutor.execute()`-এর মাধ্যমে পাঠানো চ্যাট রিকোয়েস্টগুলো তাদের ক্লাউড-মেটাডেটা চেক বজায় রাখে। লোকাল প্রোভাইডারগুলো ডিফল্টেই কাজ করে: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ডিফল্ট `true`) ভ্যালিডেশন, ডিসকভারি ও প্রোভাইডার-নোড পাথে লোকাল ও LAN URL আগে থেকেই অনুমোদন করে; প্রক্সি-ফলব্যাক টেস্ট ও প্রাইভেট webhook টার্গেট কেবল এই ফ্ল্যাগই দেখে এবং এটি বন্ধ থাকা অবস্থায় লোকাল/LAN হোস্ট ব্লক করে। `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`-কে ওভাররাইড করে। ড্যাশবোর্ড টগল থেকে সেভ করা মান এই ভেরিয়েবলের চেয়ে অগ্রাধিকার পায়। | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | প্রোভাইডার ভ্যালিডেশন, মডেল ডিসকভারি, চ্যাট এবং গার্ড মোড যেসব প্রোভাইডার-সংলগ্ন আউটবাউন্ড পাথ নিয়ন্ত্রণ করে সেগুলোর জন্য লোকাল/প্রাইভেট ঠিকানায় (127.0.0.1, localhost, LAN, প্রাইভেট রেঞ্জ) প্রোভাইডার URL অনুমোদন করুন। **ডিফল্ট `true`** (লোকাল-ফার্স্ট): তখন গার্ড ক্লাউড-মেটাডেটা এন্ডপয়েন্ট ব্লক করে — পুরো 169.254.0.0/16 এবং পরিচিত মেটাডেটা হোস্টনেমগুলো (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254)। শুধু-পাবলিক মোডের জন্য `false` সেট করুন — এটি প্রাইভেট ও লুপব্যাক হোস্টও ব্লক করে। গার্ড হোস্টনেম বা IP লিটারেল যেভাবে লেখা হয়েছে সেভাবেই, যেকোনো DNS লুকআপের আগে পরীক্ষা করে; শুধু-পাবলিক মোডে safe-fetch র‍্যাপার অতিরিক্তভাবে হোস্টনেম রিজলভ করে যেকোনো প্রাইভেট উত্তর প্রত্যাখ্যান করে। `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` এই চেকগুলো তার সারিতে তালিকাভুক্ত পাথগুলোতে বন্ধ করে দেয়। LM Studio, Ollama, vLLM, Llamafile ও Triton-এর মতো বিল্ট-ইন লোকাল প্রোভাইডারগুলো কী ভ্যালিডেশন ও চ্যাটের জন্য সব হোস্ট চেক এড়িয়ে যায়। ড্যাশবোর্ড টগল থেকে সেভ করা মান এই ভেরিয়েবলের চেয়ে অগ্রাধিকার পায়। (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` রুটগুলোকে (ট্রান্সক্রিপশন, স্পিচ, অনুবাদ) localhost-এর বাইরে হোস্ট করা OpenAI-সামঞ্জস্যপূর্ণ প্রোভাইডার নোড ব্যবহার করতে দিন। ডিফল্টভাবে বন্ধ — অডিওকে রিমোট হোস্টে রাউট করলে ইগ্রেস পরিচয় বদলে যায় এবং এটি অপারেটরের স্পষ্ট সিদ্ধান্ত হতে হবে। লুপব্যাক/প্রাইভেট নোডগুলো (localhost, 127.0.0.1, 172.16-31.x) সর্বদা অনুমোদিত এবং প্রভাবিত হয় না। (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`-কে (এবং মেমরি ইঞ্জিনের লুপব্যাক রির্যাঙ্ক ধাপকে) localhost-এর বাইরে হোস্ট করা OpenAI-সামঞ্জস্যপূর্ণ প্রোভাইডার নোড ব্যবহার করতে দিন — যেমন TEI, Infinity, vLLM ইত্যাদি চালানো কোনো LAN বক্স বা Tailscale পিয়ার। ডিফল্টভাবে বন্ধ — রিমোট হোস্টে রাউট করলে ইগ্রেস পরিচয় বদলে যায় এবং এটি অপারেটরের স্পষ্ট সিদ্ধান্ত হতে হবে। লুপব্যাক নোডগুলো (localhost, 127.0.0.1, 172.16-31.x) সর্বদা অনুমোদিত এবং প্রভাবিত হয় না। রিমোট নোডের base URL-কেও প্রোভাইডারের আউটবাউন্ড URL নীতি (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) পাস করতে হবে। | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC সক্রিয় থাকলে পাসওয়ার্ড লগইন নিষ্ক্রিয় করুন, যাতে ব্যবহারকারীরা কেবল OIDC Single Sign-On-এর মাধ্যমে প্রমাণীকরণ করতে পারেন। সংক্ষিপ্ত অ্যালিয়াস `OIDC_DISABLE_PASSWORD_LOGIN`-ও গ্রহণযোগ্য; একই কী-এর Dashboard Feature Flag অগ্রাধিকার পায়। (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-এর সংক্ষিপ্ত অ্যালিয়াস (#10889)। | ### নিরাপত্তা জোরদারকরণ চেকলিস্ট diff --git a/docs/i18n/bs/CONTRIBUTING.md b/docs/i18n/bs/CONTRIBUTING.md index 73c7625a4164..4241e63b320a 100644 --- a/docs/i18n/bs/CONTRIBUTING.md +++ b/docs/i18n/bs/CONTRIBUTING.md @@ -394,22 +394,22 @@ Napišite unit testove u `tests/unit/` koji pokrivaju najmanje: --- -## Pull Request Checklist +## Kontrolna lista za Pull Request - [ ] Testovi prolaze (`npm test`) -- [ ] Linting prolazi (`npm run lint`) -- [ ] Build uspijeva (`npm run build`) -- [ ] TypeScript tipovi su dodani za nove javne funkcije i interfejse -- [ ] Nema hardkodiranih tajni (secrets) ili fallback vrijednosti -- [ ] Javni upstream kredencijali su ugrađeni putem `resolvePublicCred()` (vidi [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikada kao literali -- [ ] Odgovori o greškama prolaze kroz `buildErrorBody()` / `sanitizeErrorMessage()` — bez sirovih stack trace-ova u tijelima odgovora (vidi [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell komande (`exec` / `spawn`) prenose runtime vrijednosti putem `env`, a ne putem string interpolacije -- [ ] Svi inputi su validirani Zod shemama -- [ ] **Fragment** changeloga je dodan pod `changelog.d/{features|fixes|maintenance}/-.md` za promjene vidljive korisnicima (vidi [`changelog.d/README.md`](./changelog.d/README.md)) — **nemojte** direktno uređivati `CHANGELOG.md`; fragmenti se agregiraju prilikom release-a i nikada ne dolazi do konflikata između PR-ova +- [ ] Provjera lintinga prolazi (`npm run lint`) +- [ ] Build je uspješan (`npm run build`) +- [ ] Dodani su TypeScript tipovi za nove javne funkcije i interfejse +- [ ] Nema hardkodiranih tajni ili rezervnih vrijednosti +- [ ] Javni pristupni podaci uzvodnog sistema ugrađeni su putem `resolvePublicCred()` (pogledajte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikada kao literali +- [ ] Odgovori s greškama prolaze kroz `buildErrorBody()` / `sanitizeErrorMessage()` — nema neobrađenih stack traceova u tijelima odgovora (pogledajte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell komande (`exec` / `spawn`) prosljeđuju vrijednosti tokom izvršavanja putem `env`, a ne putem interpolacije stringova +- [ ] Svi ulazi validirani su pomoću Zod schema +- [ ] **Fragment** dnevnika promjena dodan je u `changelog.d/{features|fixes|maintenance}/-.md` za promjene vidljive korisnicima (pogledajte [`changelog.d/README.md`](./changelog.d/README.md)) — **nemojte** direktno uređivati `CHANGELOG.md`; fragmenti se objedinjuju prilikom izdavanja i nikada ne uzrokuju konflikte između PR-ova - [ ] Dokumentacija je ažurirana (ako je primjenjivo) -- [ ] Nema novih CodeQL / Secret-Scanning upozorenja, ili je svako od njih odbijeno uz tehničko obrazloženje koje referencira relevantni `docs/security/` dokument -- [ ] Rute koje pokreću child procese (`/api/mcp/`, `/api/cli-tools/runtime/`) su klasifikovane kao `isLocalOnlyPath()` u `src/server/authz/routeGuard.ts` — vidi [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Nema `Co-Authored-By` trailera u commit porukama — commitovi moraju pojaviti se isključivo pod Git identitetom vlasnika repozitorija (Hard Rule #16) +- [ ] Nisu otvorena nova CodeQL upozorenja / upozorenja skeniranja tajni ili je svako od njih odbačeno uz tehničko obrazloženje koje upućuje na relevantni dokument u `docs/security/` +- [ ] Rute koje pokreću podređene procese (`/api/mcp/`, `/api/cli-tools/runtime/`) klasificirane su kao `isLocalOnlyPath()` u `src/server/authz/routeGuard.ts` — pogledajte [Strogo pravilo #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Nema AI/bot `Co-authored-by` završnih redova u porukama commita (Strogo pravilo #16) — ljudski saradnici čiji se rad ponovo koristi navode se standardnim `Co-authored-by: Name ` završnim redovima --- diff --git a/docs/i18n/bs/docs/architecture/QUALITY_GATES.md b/docs/i18n/bs/docs/architecture/QUALITY_GATES.md index 9a9bd2ed6a43..a780f2ca55ca 100644 --- a/docs/i18n/bs/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/bs/docs/architecture/QUALITY_GATES.md @@ -17,264 +17,268 @@ istog sistema nezavisan od alata, pogledajte --- -## Inventar kontrolnih tačaka i profili izvršavanja +## Inventar kontrola i profili izvršavanja -### Prihvat kandidata +### Prihvatanje kandidata -CI i Quality Gates tokovi rada daju stabilan rezultat: `Gate / CI` i -`Gate / Quality`. Njihova verzionirana politika prihvata navodi svaki prethodni posao -kao obavezan ili savjetodavan. Primjenjivi obavezni posao mora biti uspješan: nedostajući, -otkazani, preskočeni, neriješeni i nepoznati rezultati ne mogu uspostaviti PASS. Važeća +CI i Quality Gates radni tokovi emituju stabilan ishod: `Gate / CI` i +`Gate / Quality`. Njihova verzionirana politika prihvatanja navodi svaki prethodni posao +kao obavezan ili savjetodavan. Primjenjivi obavezni posao mora biti uspješan: rezultati koji +nedostaju, koji su otkazani, preskočeni, na čekanju ili nepoznati ne mogu uspostaviti PASS. Važeća klasifikacija samo za dokumentaciju ili samo za katalog može učiniti programsku putanju neprimjenjivom; nacrt PR-a nije prihvaćen kandidat. Oznaka `hotfix` ne ukida zahtjev za dokazima. -Oba toka rada obuhvataju PR-ove i slanja na glavne/release grane, ručno pokretanje i -događaje grupe spajanja. Slanje, pokretanje i grupa spajanja izvršavaju puni odabir. Forkovi +Oba radna toka obuhvataju PR-ove i slanja na glavne/release grane, ručno pokretanje i +događaje grupe spajanja. Slanje, ručno pokretanje i grupa spajanja izvršavaju puni izbor. Forkovi i grupe spajanja koriste hostovane izvršioce za poslove koji bi inače odabrali samostalno hostovane -izvršioce; dovoljan hostovani kapacitet mora biti provjeren prije uvođenja. +izvršioce; prije uvođenja mora se provjeriti dovoljan hostovani kapacitet. -Svaka JSON potvrda identificira preuzeti SHA, izvršavanje toka rada i pokušaj. -CLI odbija nepodudaranje SHA vrijednosti preuzete verzije i događaja. Testovi toka rada vežu članstvo u politici -za listu `needs` posla koji daje rezultat, tako da nova ili uklonjena putanja ne može neprimjetno nestati. -Potvrde obuhvataju vlastiti tok rada, a ne objavljivanje, implementaciju ili interne detalje -postojećeg savjetodavnog skenera. Aktiviranje oba naziva provjera u pravilima grane predstavlja -zasebnu administrativnu promjenu; dodavanje ovih poslova samo po sebi ne štiti granu. +Svaka JSON potvrda identifikuje odjavljeni SHA, izvršavanje radnog toka i pokušaj. +CLI odbija nepodudaranje SHA-a odjave i događaja. Testovi radnog toka vežu članstvo u politici +za listu `needs` posla koji daje ishod, tako da nova ili uklonjena putanja ne može neprimjetno nestati. +Potvrde obuhvataju vlastiti radni tok, a ne objavljivanje, implementaciju ili internu logiku +postojećeg savjetodavnog skenera. Aktiviranje oba naziva provjera u pravilima grane +zasebna je administrativna promjena; dodavanje ovih poslova samo po sebi ne štiti granu. ### Inventar statičkog skeniranja Verzionirani inventar npm aliasa i članstvo u statičkom skeniranju nalaze se u -`config/quality/gate-manifest.json`. Pokrenite `npm run check:gate-manifest` kako biste provjerili -nazive skripti i tačne naredbe u odnosu na `package.json`; dodavanja, uklanjanja i -odstupanja naredbi uzrokuju neuspjeh i lokalne kuke i poslova klasifikacije promjena u CI-ju. -Alias nije posao toka rada, instanca matrice niti testni slučaj: ovi brojevi se +`config/quality/gate-manifest.json`. Pokrenite `npm run check:gate-manifest` da biste provjerili +nazive skripti i tačne naredbe prema datoteci `package.json`; dodavanja, uklanjanja i +odstupanja naredbi uzrokuju neuspjeh i lokalne kuke i poslova klasifikacije promjena u CI-u. +Alias nije posao radnog toka, instanca matrice niti testni slučaj: ti se brojevi ne smiju predstavljati kao međusobno zamjenjivi. Koristite `npm run quality:scan -- --list` ili `npm run quality:scan:fast -- --list` da pregledate odabrane aliase bez njihovog izvršavanja. Izvršilac poziva -npm ulaznu tačku, pa je njeno okruženje izvršavanja (uključujući Bun gdje je konfiguriran) očuvano. -Manifest evidentira aliase izvan tih profila kao zasebno pozvane, a +npm ulaznu tačku, pa se njeno izvršno okruženje (uključujući Bun gdje je konfigurisan) zadržava. +Manifest bilježi aliase izvan tih profila kao zasebno pozvane, a naredbe za održavanje zabranjene su u profilima skeniranja samo za čitanje. -Ovi profili obuhvataju samo statičko skeniranje. Oni ne certificiraju testove proizvoda, -pokrivenost, pakovanje, vanjske provjere niti potpuno prihvatanje kandidata za izdavanje. -Prihvat u toku rada koristi povezane `config/quality/admission-policy.json` i -`scripts/quality/admission-verdict.mjs`. Profili posmatrača izdanja ostaju odvojeni; -njihove primjenjive provjere i potvrde pregledajte nezavisno. Tekstualni -inventar u nastavku služi kao referenca, a ne kao dokaz da je kontrolna tačka zaista izvršena. +Ovi profili obuhvataju samo statičko skeniranje. Oni ne potvrđuju testove proizvoda, +pokrivenost, pakovanje, vanjske provjere niti potpuno prihvatanje kandidata za izdanje. +Prihvatanje u radnom toku koristi povezane `config/quality/admission-policy.json` i +`scripts/quality/admission-verdict.mjs`. Profili posmatrača izdanja ostaju zasebni; +nezavisno pregledajte njihove primjenjive provjere i potvrde. Tekstualni +inventar u nastavku služi kao referenca, a ne kao dokaz da je kontrola zaista izvršena. -Skripte se nalaze u `scripts/check/` (kontrolne tačke politike) i `scripts/quality/` (mehanizam postepenog pooštravanja). +Skripte se nalaze u `scripts/check/` (kontrole politika) i `scripts/quality/` (mehanizam zapornog napretka). CI izvor istine je `.github/workflows/ci.yml`. -### Brza putanja za release PR (`quality.yml`) +### Brza putanja za PR izdanja (`quality.yml`) -`.github/workflows/quality.yml` dopunjuje CI za PR-ove prema glavnim/release granama, slanja na zaštićene grane, -pokretanja i grupe spajanja. PR-ovi koriste brze provjere filtrirane prema putanjama. Trajno -onemogućena duplikatna izgradnja je uklonjena; stvarne provjere izgradnje/pakovanja/pokretanja ostaju u CI-ju. +`.github/workflows/quality.yml` dopunjuje CI za PR-ove na glavnim/release granama, slanja na zaštićene +grane, ručno pokretanje i grupe spajanja. PR-ovi koriste brze provjere filtrirane prema putanjama. Trajno +onemogućena duplicirana izgradnja je uklonjena; stvarne provjere izgradnje/pakovanja/pokretanja ostaju u CI-u. -| Posao | Opseg | Blokirajuće | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | -| `Docs Gates (fast-path)` | PR-ovi dokumentacije/koda; reference API dokumentacije i sva dokumentacija | Da | -| `Fast Quality Gates` | PR-ovi koda; statičke provjere, provjera tipova, provjera tipova kontrolne ploče, pogođeni jedinični testovi | Da | -| `Forgotten sibling tests` | PR-ovi koda; promijenjeni moduli praćeni do statičkih potrošača i kandidatskih srodnih testova; putanje kroz barrel module i dinamički import prijavljuju se kao savjetodavna dijagnostika, uz navedene izuzetke s liste dozvoljenih | **Savjetodavno** | -| `Vitest (fast-path)` | PR-ovi koda; brzi vitest paket | Da | -| `Unit Tests fast-path` | PR-ovi koda; jedinični paket podijeljen na 4 dijela | Da | -| `No new ESLint warnings` | PR-ovi koda; zaštita lintera koja uzima u obzir potiskivanja | Da, uključujući forkove | -| `Merge integrity (changelog + generated skills)` | PR-ovi koji nisu nacrti; sinhronizacija dnevnika promjena i generiranih vještina | Da, uključujući forkove | +| Posao | Opseg | Blokirajuće | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `Docs Gates (fast-path)` | PR-ovi dokumentacije/koda; reference API dokumentacije i sva dokumentacija | Da | +| `Fast Quality Gates` | PR-ovi koda; statičke provjere, provjera tipova, provjera tipova kontrolne ploče, pogođeni jedinični testovi | Da | +| `Forgotten sibling tests` | PR-ovi koda; promijenjeni moduli praćeni do statičkih potrošača i potencijalnih srodnih testova; putanje barrel modula i dinamičkog uvoza prijavljuju se kao savjetodavna dijagnostika, uz navedene izuzetke s liste dopuštenja | **Savjetodavno** | +| `Vitest (fast-path)` | PR-ovi koda; brzi vitest paket | Da | +| `Unit Tests fast-path` | PR-ovi koda; jedinični paket podijeljen u 4 segmenta | Da | +| `No new ESLint warnings` | PR-ovi koda; zaštita od lint upozorenja koja uvažava potiskivanja | Da, uključujući forkove | +| `Merge integrity (changelog + generated skills)` | PR-ovi koji nisu nacrti; sinhronizacija evidencije promjena i generisanih vještina | Da, uključujući forkove | #### Izvještaj o zaboravljenim srodnim testovima -`npm run check:forgotten-sibling-tests` ponovo koristi razrješivač importa koji stoji iza mape uticaja testova. +`npm run check:forgotten-sibling-tests` ponovo koristi razrješavač uvoza na kojem se zasniva mapa uticaja testova. Za svaki promijenjeni produkcijski modul prijavljuje determinističke lance -`promijenjeni modul/simbol -> statički potrošač -> kandidatski srodni test` kada kandidatski -test nije prisutan u razlikama pull requesta. Markdown sažetak i JSON rezultat zadržavaju se kao -artefakt toka rada `forgotten-sibling-tests` radi kalibracije prije bilo kakvog uvođenja blokiranja. +`promijenjeni modul/simbol -> statički potrošač -> potencijalni srodni test` kada potencijalni +test nije prisutan u razlici zahtjeva za povlačenje. Markdown sažetak i JSON rezultat zadržavaju se kao +artefakt radnog toka `forgotten-sibling-tests` radi kalibracije prije bilo kakvog uvođenja blokiranja. -Barrel ponovni izvozi i dinamički uvozi služe samo kao dijagnostika razrješavanja; oni nikada ne stvaraju +Barrel ponovni izvozi i dinamički uvozi služe samo kao dijagnostika razrješavanja; nikada ne stvaraju blokirajući nalaz. Pregledani izuzeci nalaze se u -`config/quality/forgotten-sibling-allowlist.json`. Svaki unos mora navesti potrošača i kandidatski -test, dati konkretno obrazloženje i sadržavati poveznicu na GitHub issue ili pull request. Neispravni unosi rezultiraju -odbijanjem. Izuzeci ne mogu zanemariti izbrisani kandidatski test niti diff koji dodaje `.skip`/`.todo`; -slabljenje tvrdnji i druga prikrivanja ostaju u nadležnosti zasebno blokirajuće +`config/quality/forgotten-sibling-allowlist.json`. Svaki unos mora navesti potrošački i kandidatski +test, dati konkretno obrazloženje i sadržavati poveznicu na GitHub problem ili zahtjev za povlačenje. Neispravni unosi se +odbijaju. Izuzeci ne mogu zanemariti izbrisani kandidatski test niti razliku koja dodaje `.skip`/`.todo`; +slabljenje tvrdnji i drugo maskiranje ostaju u nadležnosti zasebno blokirajuće `check:test-masking` provjere. ### Zadatak: `lint` -Pokreće se na svakom PR-u prema grani `main`. U slučaju neuspjeha blokira spajanje. - -| Skripta (`npm run ...`) | Provjerava | Blokira | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Verzija Node.js-a nalazi se unutar podržanog raspona | Da | -| `check:cycles` | Cikličke uvoze — svi moduli u `src/` + `open-sse/` | Da | -| `check:route-validation:t06` | Zod sheme prisutne su na svim rutama (politika nivoa 6) | Da | -| `check:any-budget:t11` | Broj `@ts-expect-error // any` ne prelazi dozvoljeni prag (catraca nivoa 11) | Da | -| `check:provider-consistency` | Svaki dobavljač u `providers.ts` ima odgovarajući unos u `providerRegistry.ts` (i obrnuto, unutar liste dozvoljenih) | Da | -| `check:model-lifecycle` | Tri ručno održavane tabele usmjeravanja ostaju usklađene sa snapshotom životnog ciklusa pohranjenim u repozitoriju (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ne boduje nijedan povučeni id koji `REGISTRY` može usmjeriti; svaki cilj iz `BUILT_IN_ALIASES` prisutan je u `REGISTRY` i nije prisutan u snapshotu povučenih id-ova; svaki povučeni id koji je još uvijek u `REGISTRY` prosljeđuje se ili je naveden u `allowedRetiredInCatalog`; te se nijedan izvor ili cilj iz `DEFAULT_DEGRADATION_MAP` ne pojavljuje kao povučen u tom snapshotu. Ovo ne dokazuje da model trenutno poslužuje aktivni vanjski servis. Van mreže — poredi sa `config/quality/model-lifecycle.json`, koji se ručno osvježava pomoću `npm run quality:refresh-model-lifecycle` (mreža; nije uključen u CI). `allowedRetiredInCatalog` je jednosmjerna kontrola postepenog smanjivanja: unos dodajte samo uz problem za praćenje. | Da | -| `check:fetch-targets` | Svaki `fetch("/api/...")` u klijentskom `src/` razrješava se na stvarni `route.ts` | Da | -| `check:deps` | Sve zavisnosti koje se mogu instalirati pomoću `npm install` u svakom `package.json` u repozitoriju nalaze se u `dependency-allowlist.json`; nove neprikvačene ili slopsquattovane pakete označava kao problem | Da | -| `audit:deps` | `npm audit` (korijenski direktorij + electron) — nema upozorenja visoke/kritične ozbiljnosti (preklapa se s osv `check:vuln-ratchet`; pogledajte Backlog racionalizacije) | Da | -| `check:lockfile` | Integritet datoteke `package-lock.json` — https registar, hash vrijednosti integriteta, bez zamjena hosta | Da | -| `check:licenses` | Lista dozvoljenih SPDX licenci za produkcijske zavisnosti | Da | -| `check:tracked-artifacts` | Nema artefakata izgradnje / commitovanih `node_modules` simboličkih linkova (pokreće se i u husky pre-commit hooku; pre-push je namjerno minimalan — #6716) | Da | -| `check:ai-attribution` | Nema AI/bot `Co-Authored-By` završnog retka niti podnožja o AI generisanju u commitima, naslovu ili tijelu PR-a — Strogo pravilo #16 (u petlji brzih provjera u `quality.yml` za PR→`release/**` — čita sadržaj događaja, ne radi ništa izvan PR-ova — i korak samo za PR u lint provjeri u `ci.yml` za PR→`main`; također husky `commit-msg` hook; ljudski koautori su dozvoljeni; #14436) | -| `check:vitest-exclusions` | Svako Vitest izuzimanje navodi prateći issue i nalazi se u `config/quality/vitest-exclusions.json` (#13204) | Da | -| `check:file-size` | Nijedna izvorna datoteka ne premašuje ograničenje za odgovarajuću ekstenziju (ratchet: zamrznute velike datoteke na listi `frozen`) | Da | -| `check:error-helper` | Odgovori o greškama u executorima/handlerima koriste `buildErrorBody()` / `sanitizeErrorMessage()` (Strogo pravilo #12) | Da | -| `check:migration-numbering` | Migracijske SQL datoteke su numerisane uzastopno, bez praznina ili duplikata | Da | -| `check:public-creds` | Nema doslovnih OAuth `client_id`/`client_secret` ili Firebase Web ključeva izvan `publicCreds.ts` (Strogo pravilo #11) | Da | -| `check:db-rules` | Nema sirovog SQL-a izvan modula `src/lib/db/`; nema zbirnih uvoza iz `localDb.ts` (Stroga pravila #2/#5) | Da | -| `check:known-symbols` | Izvršitelji pružalaca usluga, strategije usmjeravanja i prevodioci registrirani u svojim tabelama za otpremu odgovaraju datotekama na disku — nema simbola bez pripadajuće datoteke ili deklaracije | Da | -| `check:route-guard-membership` | Svaka ruta koja pokreće podređeni proces klasificirana je pomoću `isLocalOnlyPath()` (Stroga pravila #15/#17) | Da | -| `check:test-discovery` | Svaku datoteku `*.test.ts` / `*.spec.ts` u repozitoriju prikuplja barem jedan pokretač testova (mehanizam s čegrtaljkom: lista nepridruženih datoteka u `test-discovery-baseline.json` može se samo smanjivati) | Da | -| `check:agent-skills-sync` | Generirani agent-skills artefakti odgovaraju svom izvornom katalogu (bez odstupanja) | -| `check:provider-asset-provenance` | Logotipi/resursi pružatelja imaju evidentiran zapis o porijeklu | -| `lint:json` | JSON konfiguracijske datoteke mogu se parsirati i zadovoljavaju pravila lintanja repozitorija | -| `typecheck:core` | TypeScript kompilacija bez grešaka (samo savjetodavna upozorenja) | Da | -| `typecheck:noimplicit:core` | Strogi `noImplicitAny` — usmjeren na budućnost; mnoga postojeća mjesta poziva još uvijek trebaju anotacije | **Savjetodavno** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` ograničen na `src/app/(dashboard)/**` (#7033) — pažljivo odabrana lista od 27 datoteka u `typecheck:core` ne uključuje nijedan dashboard TSX, a ni `next build` ga nikada ne provjerava po tipovima (`next.config.mjs` postavlja `ignoreBuildErrors: true`), pa regresije s identifikatorima bez odgovarajućih definicija tamo (#6625/#6909) nisu bile vidljive CI-ju. Razlike se porede sa zamrznutom početnom vrijednošću broja grešaka po datoteci i TS kodu (`config/quality/dashboard-typecheck-baseline.json`, isti obrazac provođenja za zastarjele unose kao kod `check:known-symbols`) — provjera ne prolazi samo za NOVE greške iznad početnog broja; smanjite početnu vrijednost pomoću `--update` kada se ispravi postojeća greška. | Da | +Pokreće se za svaki PR prema grani `main`. U slučaju neuspjeha blokira spajanje. + +| Skripta (`npm run ...`) | Provjerava | Blokira | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Verzija Node.js-a je unutar podržanog raspona | Da | +| `check:cycles` | Kružne uvoze u cijelom `src/` + `open-sse/` (zasnovano na AST-u, razriješeni tsconfig `paths`). Osnovna provjera = savjetodavna, navodi cikluse. `check:cycles:ratchet` (koji pokreće CI) blokira kada broj premaši gornju granicu `metrics.cycles` u `quality-baseline.json` — trenutno 14, `direction: down`, pa se može samo smanjivati (#15159 G-01/G-02) | Da (postepeno ograničenje) | +| `check:route-validation:t06` | Zod sheme postoje na svim rutama (pravilo nivoa 6) | Da | +| `check:any-budget:t11` | Broj `@ts-expect-error // any` ne premašuje budžet (catraca nivoa 11) | Da | +| `check:provider-consistency` | Svaki pružalac u `providers.ts` ima odgovarajući unos u `providerRegistry.ts` (i obrnuto, unutar liste dozvoljenih) | Da | +| `check:model-lifecycle` | Tri ručno održavane tabele usmjeravanja ostaju usklađene s pohranjenim snimkom životnog ciklusa (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ne boduje nijedan povučeni id koji `REGISTRY` može usmjeriti; svaki cilj iz `BUILT_IN_ALIASES` prisutan je u `REGISTRY` i nije prisutan u snimku povučenih id-ova; svaki povučeni id koji je još uvijek u `REGISTRY` prosljeđuje se ili je naveden u `allowedRetiredInCatalog`; te se nijedan izvor ili cilj iz `DEFAULT_DEGRADATION_MAP` ne pojavljuje kao povučen u tom snimku. Ovo ne dokazuje da model trenutno poslužuje aktivni uzvodni servis. Izvan mreže — poredi s `config/quality/model-lifecycle.json`, koji se ručno osvježava pomoću `npm run quality:refresh-model-lifecycle` (mreža; nije uključen u CI). `allowedRetiredInCatalog` je jednosmjerna kontrola postepenog smanjenja: unos dodajte samo uz povezani zadatak za praćenje. | Da | +| `check:fetch-targets` | Svaki `fetch("/api/...")` u klijentskom direktoriju `src/` razrješava se na stvarni `route.ts` | Da | +| `check:deps` | Sve zavisnosti koje se mogu instalirati pomoću `npm install` iz svakog `package.json` u repozitoriju nalaze se u `dependency-allowlist.json`; nove verzijski nefiksirane ili typosquatting pakete označava kao sumnjive | Da | +| `audit:deps` | `npm audit` (korijenski direktorij + electron) — nema upozorenja visokog/kritičnog nivoa (preklapa se s osv `check:vuln-ratchet`; pogledajte Rationalization Backlog) | Da | +| `check:lockfile` | Integritet datoteke `package-lock.json` — https registar, sažeci integriteta, bez nadjačavanja hostova | Da | +| `check:licenses` | Lista dopuštenih SPDX licenci za produkcijske zavisnosti | Da | +| `check:tracked-artifacts` | Nema artefakata izgradnje / predanih `node_modules` simboličkih veza (također se pokreće u husky pre-commit; pre-push je namjerno pojednostavljen — #6716) | Da | +| `check:ai-attribution` | Nema AI/bot `Co-Authored-By` završnog retka niti podnožja o AI generiranju u commitovima, naslovu ili tijelu PR-a — Strogo pravilo #16 (u petlji brzih provjera `quality.yml` za PR→`release/**` — čita sadržaj događaja, ne radi ništa izvan PR-ova — i korak samo za PR u lint provjeri datoteke `ci.yml` za PR→`main`; također husky `commit-msg` hook; ljudski koautori su dozvoljeni; #14436) | +| `check:vitest-exclusions` | Svako Vitest izuzimanje navodi problem za praćenje i nalazi se u `config/quality/vitest-exclusions.json` (#13204) | Da | +| `check:file-size` | Nijedna datoteka izvornog koda ne premašuje ograničenje za odgovarajuću ekstenziju (mehanizam zapornog zupčanika: velike datoteke zamrznute su na listi `frozen`) | Da | +| `check:error-helper` | Odgovori o greškama u izvršiteljima/rukovateljima koriste `buildErrorBody()` / `sanitizeErrorMessage()` (Strogo pravilo #12) | Da | +| `check:migration-numbering` | Migracijske SQL datoteke su numerisane uzastopno, bez praznina ili duplikata | Da | +| `check:public-creds` | Nema doslovnih OAuth vrijednosti `client_id`/`client_secret` niti Firebase Web ključeva izvan `publicCreds.ts` (Strogo pravilo #11) | Da | +| `check:db-rules` | Nema sirovog SQL-a izvan modula `src/lib/db/`; nema zbirnih uvoza iz `localDb.ts` (Stroga pravila #2/#5) | Da | +| `check:known-symbols` | Izvršitelji pružalaca usluga, strategije usmjeravanja i prevodioci registrovani u svojim tabelama za otpremu podudaraju se s datotekama na disku — nema nepovezanih ili nedeklarisanih simbola | Da | +| `check:route-guard-membership` | Svaka ruta koja pokreće podređeni proces klasifikovana je pomoću `isLocalOnlyPath()` (Stroga pravila #15/#17) | Da | +| `check:test-discovery` | Svaku datoteku `*.test.ts` / `*.spec.ts` u repozitoriju prikuplja najmanje jedan pokretač testova (mehanizam zapornog zupčanika: lista nepovezanih datoteka u `test-discovery-baseline.json` može se samo smanjivati) | Da | +| `check:agent-skills-sync` | Generisani artefakti agent-skills odgovaraju svom izvornom katalogu (bez odstupanja) | +| `check:provider-asset-provenance` | Logotipi/resursi pružatelja imaju evidentiran zapis o porijeklu | +| `lint:json` | JSON konfiguracijske datoteke se mogu parsirati i zadovoljavaju lint pravila repozitorija | +| `typecheck:core` | TypeScript kompilacija bez grešaka (samo savjetodavna upozorenja) | Da | +| `typecheck:noimplicit:core` | Strogi `noImplicitAny` — usmjeren na budućnost; mnoga postojeća mjesta poziva i dalje trebaju anotacije | **Savjetodavno** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` ograničen na `src/app/(dashboard)/**` (#7033) — pažljivo odabrana lista od 27 datoteka u `typecheck:core` ne uključuje nijedan dashboard TSX, a ni `next build` ga nikada ne provjerava po tipovima (`next.config.mjs` postavlja `ignoreBuildErrors: true`), pa su regresije vezane za nepovezane identifikatore (#6625/#6909) tamo bile nevidljive CI-ju. Razlike se porede sa zamrznutom osnovnom vrijednošću broja grešaka po datoteci i TS kodu (`config/quality/dashboard-typecheck-baseline.json`, isti obrazac provođenja za zastarjele stavke kao kod `check:known-symbols`) — samo NOVE greške iznad osnovnog broja dovode do pada provjere; smanjite osnovnu vrijednost pomoću `--update` kada se postojeća greška ispravi. | Da | ### Zadatak: `quality-gate` Pokreće se nakon `test-coverage`. Blokira spajanje u slučaju neuspjeha. -| Skripta | Provjerava | Blokirajuće | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | -| `quality:collect` | Generira `quality-metrics.json` (broj ESLint upozorenja, pokrivenost iz objedinjenog izvještaja fragmenata) | Da (prethodi mehanizmu ratcheta) | -| `quality:ratchet` | Nijedna metrika u `quality-baseline.json` nije nazadovala (ESLint upozorenja ≤ početna vrijednost; pokrivenost ≥ početna vrijednost) | Da | -| `check:duplication` | Dupliciranje koda (jscpd@4) ne premašuje početnu vrijednost u `quality-baseline.json` | Da | -| `check:complexity` | Ciklomatska složenost na nivou datoteke ne premašuje ograničenje (osnovni ESLint `complexity` + `max-lines-per-function`) | Da | -| `check:cognitive-complexity` | Ratchet kognitivne složenosti (`eslint-plugin-sonarjs`) — zasebno pokretanje ESLint-a; CI pokreće oba objedinjena u jednom koraku `check:complexity-ratchets` | Da | -| `check:dead-code` | Ratchet nekorištenih izvoza / datoteka (knip) ne nazaduje u odnosu na početnu vrijednost | Da | -| `check:compression-budget` | Budžet mjerenja performansi kompresije — minimalne uštede tokena po mehanizmu ne smiju nazadovati | Da | -| `check:type-coverage` | Ratchet procenta tipiziranog koda (`type-coverage`) ne nazaduje; u velikoj mjeri obuhvata `typecheck:noimplicit:core` | Da | -| `check:codeql-ratchet` | Broj otvorenih CodeQL upozorenja ne nazaduje (čita putem `gh api`; uredno preskače bez tokena) — učestalost osvježavanja i ručno pokretanje: pogledajte „CodeQL ratchet“ ispod | Da | - -### Zadatak: `quality-extended` - -Cijeli zadatak je savjetodavan (`continue-on-error: true`). Ratcheti zasnovani na npm-u -zaista se izvršavaju; vanjski skeneri instaliraju se putem `gh release download` i sami -se preskaču (exit 0) kada binarna datoteka još uvijek nije prisutna. - -| Skripta | Provjerava | Blokirajuće | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | -| `check:circular-deps` | Nema kružnih zavisnosti (dpdm) | **Savjetodavno** | -| `check:bundle-size` | Veličina paketa ne premašuje ograničenje | **Savjetodavno** | -| `check:secrets` | Skeniranje tajni (gitleaks) — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | -| `check:vuln-ratchet` | Ranjivosti zavisnosti (osv-scanner) ne nazaduju — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | -| `check:workflows` | Provjera toka rada (actionlint + zizmor); nedostajući/neispravni skeneri, nevažeći izvještaji ili nedostajuća početna vrijednost ratcheta završavaju kao INCOMPLETE. Važeći nalazi slijede odabranu strogu/savjetodavnu/ratchet politiku | Izvršavanje je obavezno; zizmor ratchet blokira u CI-ju | -| `check:openapi-breaking` | Promjene javnog API ugovora (`openapi.yaml`) koje narušavaju kompatibilnost u odnosu na osnovnu granu (oasdiff) — generira `openapiBreaking=N`; preskače se ako oasdiff nije prisutan ili se osnovna specifikacija ne može razriješiti | **Savjetodavno** | - -### Zadatak: `docs-sync-strict` - -Pokreće se za svaki PR prema `main`. Blokira spajanje u slučaju neuspjeha. - -| Skripta | Provjerava | Blokira | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| `check:docs-all` | Meta-kontrola koja redom pokreće 6 podkontrola u nastavku | Da | -| ↳ `check:docs-sync` | Usklađenost verzija između CHANGELOG-a / OpenAPI-ja / llm.txt-a | Da | -| ↳ `check:docs-counts` | Brojevi u tekstu (broj pružalaca, broj migracija itd.) nalaze se unutar pragom definisanog raspona stvarnih brojeva | Da | -| ↳ `check:env-doc-sync` | Svaka varijabla okruženja iz `.env.example` dokumentovana je u tabeli dokumentacije i obrnuto | Da | -| ↳ `check:deprecated-versions` | U dokumentaciji nema zastarjelih oznaka verzija | Da | -| ↳ `check:doc-links` | Interne markdown poveznice u dokumentaciji vode do stvarnih datoteka (oblik `[tekst]`/`(putanja)`) | Da | -| ↳ `check:fabricated-docs` | Rute, varijable okruženja, CLI naredbe, nazivi hookova i putanje datoteka navedeni u dokumentaciji postoje u bazi koda. Stroga kontrola putem `--strict`; bez te zastavice greška nije blokirajuća. | Da (putem `--strict` u CI-ju) | -| `check:cli-i18n` | Nizovi CLI naredbi prisutni su u svim i18n datotekama lokalizacije | Da | -| `check:openapi-coverage` | OpenAPI specifikacija pokriva najmanje prag stvarnih ruta definisan postepenim povećavanjem | Da | -| `check:openapi-security-tiers` | Oznake sigurnosnih nivoa u `openapi.yaml` usklađene su s klasifikacijama u `routeGuard.ts` | **Savjetodavno** | -| `check:openapi-routes` | Svaka putanja u `openapi.yaml` odgovara stvarnoj datoteci `route.ts` (sprečavanje halucinacija) | Da | -| `check:docs-symbols` | Svaka referenca `/api/...` u `docs/**/*.md` odgovara stvarnoj datoteci `route.ts` (sprečavanje halucinacija) | Da | -| `i18n translation drift` | Neprevedeni ključevi u i18n datotekama lokalizacije — samo upozorenje | **Savjetodavno** | - -### Zadatak: `i18n-ui-coverage` - -| Skripta | Provjerava | Blokira | -| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (ugrađeno) | Pokrivenost UI i18n ključeva iznosi ≥ 65% | Da | -| `check-ui-value-drift` (ugrađeno) | Izmijenjena engleska **vrijednost** ne ostavlja nijedan zastarjeli prijevod | Da | -| `check-new-key-coverage` (ugrađeno) | **Novi** engleski ključ preveden je u svakoj lokalizaciji — oznaka `__MISSING__:` se odbija | Da | -| `check-translation-ratio` | Omjer stvarnih prijevoda po lokalizaciji (stavke identične engleskom / rezervirana mjesta / nedostajuće stavke izvan liste dopuštenih izuzetaka) ne smije premašiti `config/quality/i18n-translation-baseline.json` + toleranciju | **Savjetodavno** | - -Zahtijeva `fetch-depth: 0` — kontrola promjene vrijednosti poredi `en.json` s bazom spajanja. +| Skripta | Provjerava | Blokira | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| `quality:collect` | Generiše `quality-metrics.json` (broj ESLint upozorenja, pokrivenost iz objedinjenog izvještaja segmenata) | Da (prethodi mehanizmu postepenog pooštravanja) | +| `quality:ratchet` | Nijedna metrika u `quality-baseline.json` nije nazadovala (ESLint upozorenja ≤ početna vrijednost; pokrivenost ≥ početna vrijednost) | Da | +| `check:duplication` | Dupliciranje koda (jscpd@4) ne prelazi početnu vrijednost u `quality-baseline.json` | Da | +| `check:complexity` | Ciklomatska složenost na nivou datoteke ne prelazi ograničenje (osnovni ESLint `complexity` + `max-lines-per-function`) | Da | +| `check:cognitive-complexity` | Postepeno pooštravanje kognitivne složenosti (`eslint-plugin-sonarjs`) — zasebno ESLint izvršavanje; CI izvršava oba objedinjena kao jedan korak `check:complexity-ratchets` | Da | +| `check:dead-code` | Postepeno pooštravanje za nekorištene izvoze/datoteke (knip) ne nazaduje u odnosu na početnu vrijednost | Da | +| `check:compression-budget` | Budžet mjerenja performansi kompresije — minimalne uštede tokena po mehanizmu ne smiju nazadovati | Da | +| `check:type-coverage` | Postotak tipiziranog koda (`type-coverage`) ne nazaduje; uglavnom obuhvata `typecheck:noimplicit:core` | Da | +| `check:codeql-ratchet` | Broj otvorenih CodeQL upozorenja ne nazaduje (čita putem `gh api`; uredno preskače bez tokena) — učestalost osvježavanja i ručno pokretanje: pogledajte „Postepeno pooštravanje za CodeQL“ u nastavku | Da | + +### Posao: `quality-extended` + +Cijeli posao je savjetodavan (`continue-on-error: true`). Mehanizmi postepenog pooštravanja zasnovani na npm-u +izvršavaju se stvarno; vanjski skeneri instaliraju se putem `gh release download` i sami se preskaču (izlazni kod 0) +kada binarna datoteka još uvijek nije prisutna. + +| Skripta | Provjerava | Blokira | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `check:circular-deps` | Nema kružnih zavisnosti (dpdm) | **Savjetodavno** | +| `check:bundle-size` | Veličina paketa ne prelazi ograničenje | **Savjetodavno** | +| `check:secrets` | Skeniranje tajni (gitleaks) — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | +| `check:vuln-ratchet` | Ranjivosti zavisnosti (osv-scanner) ne nazaduju — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | +| `check:workflows` | Provjera radnih tokova (actionlint + zizmor); nedostajući/neispravni skeneri, nevažeći izvještaji ili nedostajuća početna vrijednost postepenog pooštravanja uzrokuju neuspjeh sa statusom INCOMPLETE. Važeći nalazi slijede odabranu strogu/savjetodavnu politiku ili politiku postepenog pooštravanja | Izvršavanje je obavezno; postepeno pooštravanje za zizmor blokira u CI-ju | +| `check:openapi-breaking` | Kompatibilnost javnog API ugovora (`openapi.yaml`) u odnosu na osnovnu granu (oasdiff) — generiše `openapiBreaking=N`; preskače se ako oasdiff nije prisutan ili se osnovna specifikacija ne može razriješiti | **Savjetodavno** | + +### Posao: `docs-sync-strict` + +Izvršava se za svaki PR prema grani `main`. Blokira spajanje u slučaju neuspjeha. + +| Skripta | Provjerava | Blokirajuće | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `check:docs-all` | Meta-kontrola koja redom pokreće 6 podkontrola navedenih ispod | Da | +| ↳ `check:docs-sync` | Usklađenost verzija u CHANGELOG-u / OpenAPI-ju / llm.txt-u | Da | +| ↳ `check:docs-counts` | Brojevi u tekstu (broj pružalaca, broj migracija itd.) nalaze se unutar granica postepenog praga u odnosu na stvarne brojeve | Da | +| ↳ `check:env-doc-sync` | Svaka varijabla okruženja iz `.env.example` dokumentovana je u tabeli dokumentacije i obrnuto | Da | +| ↳ `check:deprecated-versions` | U dokumentaciji nema zastarjelih oznaka verzija | Da | +| ↳ `check:doc-links` | Interne markdown veze u dokumentaciji upućuju na stvarne datoteke (oblik `[tekst]`/`(putanja)`) | Da | +| ↳ `check:fabricated-docs` | Rute, varijable okruženja, CLI naredbe, nazivi hookova i putanje datoteka navedeni u dokumentaciji postoje u bazi koda. Stroga kontrola putem `--strict`; bez zastavice neuspjeh je samo upozorenje. | Da (putem `--strict` u CI-ju) | +| `check:cli-i18n` | Tekstovi CLI naredbi prisutni su u svim i18n datotekama lokalizacije | Da | +| `check:openapi-coverage` | OpenAPI specifikacija pokriva najmanje postepeno utvrđeni minimalni prag stvarnih ruta | Da | +| `check:openapi-security-tiers` | Oznake sigurnosnih nivoa u `openapi.yaml` usklađene su s klasifikacijama u `routeGuard.ts` | **Savjetodavno** | +| `check:openapi-routes` | Svaka putanja u `openapi.yaml` odgovara stvarnom `route.ts` (sprečavanje halucinacija) | Da | +| `check:docs-symbols` | Svaka `/api/...` referenca u `docs/**/*.md` odgovara stvarnom `route.ts` (sprečavanje halucinacija) | Da | +| `i18n translation drift` | Neprevedeni ključevi u i18n datotekama lokalizacije — samo upozorenje | **Savjetodavno** | + +### Posao: `i18n-ui-coverage` + +| Skripta | Provjerava | Blokirajuće | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | +| `check-ui-keys-coverage` (ugrađeno) | Pokrivenost UI i18n ključeva iznosi ≥ 65% | Da | +| `check-ui-value-drift` (ugrađeno) | Izmijenjena engleska **vrijednost** ne ostavlja za sobom zastarjeli prijevod | Da | +| `check-new-key-coverage` (ugrađeno) | **Novi** engleski ključ preveden je u svakoj lokalizaciji — oznaka `__MISSING__:` se odbija | Da | +| `check-translation-ratio` | Omjer stvarnih prijevoda po lokalizaciji (vrijednosti identične engleskim / rezervirana mjesta / nedostajući listovi izvan liste dozvoljenih) ne smije premašiti `config/quality/i18n-translation-baseline.json` + toleranciju | **Savjetodavno** | + +Zahtijeva `fetch-depth: 0` — kontrola odstupanja vrijednosti poredi `en.json` s osnovom spajanja. #### `check-ui-value-drift` — kontrola zastarjelih prijevoda -Otkriva onu i18n regresiju koju ostale kontrole strukturno ne mogu uočiti: engleska vrijednost -se izmijeni, a prijevodi izvedeni iz _prethodnog_ engleskog ostanu nepromijenjeni, pa -korisnici koji ne koriste engleski nastavljaju čitati uvjerljivo sročen, ali sada netačan tekst. +Otkriva onu i18n regresiju koju druge kontrole strukturno ne mogu uočiti: engleska vrijednost +se prepravi, dok prijevodi izvedeni iz _prethodnog_ engleskog ostanu nepromijenjeni, pa +korisnici koji ne koriste engleski nastavljaju čitati uvjerljivo sročen, ali sada pogrešan tekst. -Ovo je zaista dospjelo u produkciju. `oauthModal.googleOAuthWarning` je izmijenjen kada je -uveden pomoćnik za prijavu Antigravity (#5203); **39 od 43 lokalizacije** zadržalo je tekst koji -je operaterima govorio da „kopiraju puni URL i zalijepe ga ispod“ — tok koji se ne može dovršiti -za tog pružaoca. Problem je ostao neprimijećen sve do #8463 jer: +Ovo se zaista pojavilo u izdanju. `oauthModal.googleOAuthWarning` je prepravljen kada je uveden +Antigravity pomoćnik za prijavu (#5203); **39 od 43 lokalizacije** zadržalo je tekst koji je +operaterima govorio da „kopiraju cijeli URL i zalijepe ga ispod” — tok koji se za tog pružaoca +ne može dovršiti. Problem je ostao neprimijećen sve do #8463 zato što: -- `sync-ui-keys` popunjava samo ključeve koji **nedostaju**, nikada one koji su **zastarjeli**; -- `check-ui-keys-coverage` broji _prisutnost_ ključeva, pa se zastarjeli prijevod računa kao pokriven; -- `check-translation-drift` prati dokumentacijske kopije `docs/i18n//**.md` — - nikada ne čita `src/i18n/messages/*.json`. Blokira u zadatku `docs-sync-strict` od - ponovne sinhronizacije 2026-09: izmijenite osnovni dokument → `npm run i18n:run -- --files=` (na nivou odjeljka, brzo). +- `sync-ui-keys` samo naknadno dodaje ključeve koji **nedostaju**, a nikada one koji su **zastarjeli**; +- `check-ui-keys-coverage` broji _prisutnost_ ključa, pa se zastarjeli prijevod računa kao pokriven; +- `check-translation-drift` prati dokumentacijska ogledala `docs/i18n//**.md` — + nikada ne čita `src/i18n/messages/*.json`. Blokira u poslu `docs-sync-strict` od + ponovne sinhronizacije 2026-09: uredite osnovni dokument → `npm run i18n:run -- --files=` (na nivou odjeljka, brzo). -**Svjestan razlika, bez oslanjanja na bazno stanje.** Poredi `en.json` na bazi spajanja s -radnim stablom; za svaki ključ čija se engleska vrijednost promijenila, svaki lokalitet koji još uvijek sadrži -netaknuti prijevod smatra se zastarjelim. Ovo namjerno **zamrzava prethodno postojeći dug** — razlika -ne može otkriti iz koje je stare engleske vrijednosti nastao dugotrajni prijevod, pa kontrola procjenjuje -samo ono što trenutna izmjena dotiče. Alternativa (bazno stanje hash vrijednosti za svaki ključ) zahtijevala bi -generisanu datoteku od ~600 KB, 3× veću od najvećeg postojećeg baznog stanja, koja bi se mijenjala pri svakom i18n PR-u. +**Uvažava razlike, nije zasnovano na baznoj liniji.** Poredi `en.json` na osnovi spajanja s +radnim stablom; za svaki ključ čija se engleska vrijednost promijenila, svaki lokalitet koji +još uvijek sadrži neizmijenjen prijevod smatra se zastarjelim. Ovo namjerno **zamrzava +prethodno postojeći dug** — razlika ne može otkriti iz koje je stare engleske vrijednosti +nastao dugogodišnji prijevod, pa kontrola procjenjuje samo ono na šta trenutna izmjena +utiče. Alternativa (bazna linija heša za svaki ključ) zahtijevala bi generisanu datoteku +veličine ~600 KB, 3× veću od najveće postojeće bazne linije, koja bi se mijenjala pri svakom +i18n PR-u. -Postoje dva načina da se kontrola zadovolji: +Postoje dva načina da se zahtjev ispuni: -1. ažurirajte zahvaćene prijevode ili -2. postavite ih na `__MISSING__:` — izvršno okruženje tada poslužuje ispravljeni engleski - (`src/i18n/request.ts::deepMergeFallback`, #7258), a ključ se stavlja u red za prevođenje. +1. ažurirati obuhvaćene prijevode ili +2. postaviti ih na `__MISSING__:` — izvršno okruženje tada isporučuje + ispravljeni engleski tekst (`src/i18n/request.ts::deepMergeFallback`, #7258), a ključ se + stavlja u red za prevođenje. -Ako se promijenilo **značenje** stringa, poželjno je **preimenovati ključ**: novi ključ ne može naslijediti -zastarjeli prijevod. To je obrazac korišten u #8463. +Ako se promijenilo **značenje** niza, poželjno je **preimenovati ključ**: novi ključ ne može +naslijediti zastarjeli prijevod. To je obrazac korišten u #8463. ```bash -npm run i18n:check-value-drift # strogo (ono što CI izvršava) +npm run i18n:check-value-drift # strogo (ono što CI pokreće) npm run i18n:check-value-drift:warn # samo izvještaj BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Završava s kodom 0 i porukom `SKIP reason=base-unresolved` kada se osnovni katalog ne može pročitati (plitki -klon bez osnovne reference), prateći ponašanje `check-openapi-breaking`. +Završava s kodom 0 i porukom `SKIP reason=base-unresolved` kada se osnovni katalog ne može +pročitati (plitko kloniranje bez osnovne reference), po uzoru na `check-openapi-breaking`. ### Zadatak: `i18n` -Potpuna matrica i18n validacije (jedan zadatak po lokalitetu). Cijeli zadatak je savjetodavan. +Potpuna matrica i18n provjere (jedan zadatak po lokalitetu). Cijeli zadatak je savjetodavan. -| Skripta | Validira | Blokiranje | -| ------------------------------- | --------------------------------- | --------------------------------------------------------------- | -| `validate_translation.py quick` | Potpunost prijevoda po lokalitetu | **Savjetodavno** (`continue-on-error: true` na cijelom zadatku) | +| Skripta | Provjerava | Blokira | +| ------------------------------- | -------------------------------------- | -------------------------------------------------------------- | +| `validate_translation.py quick` | Potpunost prijevoda za svaki lokalitet | **Savjetodavno** (`continue-on-error: true` za cijeli zadatak) | ### Zadatak: `pr-test-policy` -Izvršava se samo za zahtjeve za spajanje. +Pokreće se samo za zahtjeve za spajanje. -| Skripta | Validira | Blokiranje | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check:pr-test-policy` | PR-ovi koji mijenjaju produkcijski kod u `src/`, `open-sse/`, `electron/` ili `bin/` moraju uključiti ili ažurirati testove (Strogo pravilo #8) | Da | -| `check:test-masking` | Izmijenjene testne datoteke ne smanjuju neto broj provjera niti dodaju tautologije `assert.ok(true)` | Da | -| `check:pr-evidence` | Tijelo PR-a navodi dokaze testiranja/VPS-a za izmjenu (automatizira Strogo pravilo #18 pretraživanjem teksta PR-a — krhko, pogledajte Backlog) | Da | +| Skripta | Provjerava | Blokira | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `check:pr-test-policy` | PR-ovi koji mijenjaju produkcijski kod u `src/`, `open-sse/`, `electron/` ili `bin/` moraju uključiti ili ažurirati testove (Strogo pravilo #8) | Da | +| `check:test-masking` | Izmijenjene testne datoteke ne smanjuju neto broj provjera niti dodaju tautologije `assert.ok(true)` | Da | +| `check:pr-evidence` | Opis PR-a navodi dokaze iz testova/VPS-a za izmjenu (automatizira Strogo pravilo #18 pretraživanjem teksta PR-a — nepouzdano, pogledajte Backlog) | Da | ### Zadatak: `test-vitest` -Izvršava se nakon `build`. Blokira spajanje u slučaju neuspjeha. +Pokreće se nakon `build`. Neuspjeh blokira spajanje. -| Paket testova | Validira | Blokiranje | +| Paket testova | Provjerava | Blokira | | ---------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `test:vitest` | MCP server (110 alata), autoCombo, predmemorija — vitest pokretač | Da | | `test:vitest:ui` | Testovi UI komponenti — vitest pokretač | **Blokira** — prethodno postojeći neuspjesi izričito su izuzeti u `vitest.config.ts`; novi neuspjesi obaraju zadatak | ### Noćni radni tokovi (zakazani, savjetodavni) -Izvršavaju se prema cron rasporedu (i putem `workflow_dispatch`), nikada na PR-ovima. Svi su savjetodavni. +Pokreću se prema cron rasporedu (i putem `workflow_dispatch`), nikada na PR-ovima. Svi su +savjetodavni. -| Radni tok | Validira | Blokiranje | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | -| `nightly-property` | fast-check testove svojstava s nasumičnom početnom vrijednošću + velikim brojem izvršavanja | **Savjetodavno** | -| `nightly-resilience` | kontrolu rasta heap memorije, ubrizgavanje haotičnih grešaka, k6 testove opterećenja/izdržljivosti | **Savjetodavno** | -| `nightly-llm-security` | promptfoo zaštitu od ubrizgavanja (režim blokiranja) + garak provjere (preskaču se bez tajne pružaoca usluge) | **Savjetodavno** | -| `nightly-schemathesis` | fuzzing OpenAPI ugovora (schemathesis) nad aktivnim OmniRouteom koristeći `docs/openapi.yaml` — otkriva kršenja specifikacije / neobrađene greške 500 (Faza 8 B.4) | **Savjetodavno** | -| `nightly-mutation` | rezultat Stryker mutacijskog testiranja nad brzom jedinicom testnom granom — preživjeli mutanti otkrivaju slabe provjere | **Savjetodavno** | -| `nightly-compat` | matricu kompatibilnosti Node pokretača u podržanim rasponima `engines.node` | **Savjetodavno** | +| Radni tok | Provjerava | Blokira | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `nightly-property` | fast-check testove svojstava s nasumičnim početnim brojem + velikim brojem pokretanja | **Savjetodavno** | +| `nightly-resilience` | kontrolu rasta heap memorije, chaos ubacivanje grešaka, k6 testiranje opterećenja/izdržljivosti | **Savjetodavno** | +| `nightly-llm-security` | promptfoo zaštitu od ubacivanja (blokirajući režim) + garak sonde (preskaču se bez tajne pružaoca usluge) | **Savjetodavno** | +| `nightly-schemathesis` | Fuzz testiranje OpenAPI ugovora (schemathesis) nad aktivnim OmniRouteom koristeći `docs/openapi.yaml` — otkriva kršenja specifikacije / neobrađene greške 500 (Faza 8 B.4) | **Savjetodavno** | +| `nightly-mutation` | Rezultat Stryker mutacijskog testiranja nad brzom trakom jediničnih testova — preživjeli mutanti otkrivaju slabe provjere | **Savjetodavno** | +| `nightly-compat` | Matricu kompatibilnosti Node pogona kroz podržane raspone `engines.node` | **Savjetodavno** | --- @@ -491,32 +495,43 @@ nije automatski učitan (vi birate i verifikujete MCP↔LSP most); neispravan un --- -## Backlog racionalizacije (ROI pregled — Faza 9 Talas 3) - -Ovaj inventar je usklađen sa `ci.yml` dana 17.06.2026. (prethodna verzija je izostavila `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). ROI pregled usklađenog skupa identifikovao je sljedeće kandidate za racionalizaciju. **Spajanja su mehaničke CI izmjene; promjene/odbacivanja su odluke o politici rezervisane za operatera.** Ništa od navedenog još nije primijenjeno. - -**Također, gore nije dokumentovano** (savjetodavno, nizak signal): `docs-lint` posao (markdownlint + Vale, cijeli posao `continue-on-error`) i samostalni radni tokovi skenera `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` se nalazi u `quality-baseline.json`, ali nije povezan sa blokirajućim ratchet-om u `ci.yml` — metrika je trenutno bez vlasnika. - -### Spajanje / dedup (mehaničko, manji rizik) - -Svaki kandidat je validiran u odnosu na stanje "live gate" na dan 17.06.2026. (povjerenje-ali-provjera); nekoliko "očiglednih" spajanja se ispostavilo da skrivaju dug i **nisu** čiste zamjene. - -- **`check:docs-sync` se pokreće dvaput** — samostalno u `lint` poslu i ponovo unutar `check:docs-all` (`docs-sync-strict`) i husky pre-commit hook-a. ✅ **GOTOVO** — samostalna `lint` invokacija uklonjena. -- **CVE skeniranje** — ❌ **NIJE čisto spajanje.** `audit:deps` strogo pada na bilo kojem visokom/kritičnom CVE-u; `check:vuln-ratchet` (osv) pada samo na _regresiji_ u odnosu na osnovnu vrijednost (trenutno 1 MODERATE). Različita semantika — odbacivanje `audit:deps` bi značilo gubitak apsolutne visoke/kritične kapije. Zadržati oba. -- **Detekcija ciklusa** — ❌ **NIJE čisto spajanje.** `check:circular-deps` (dpdm) prijavljuje **91 ciklus** (zato je savjetodavno); ne može se promovisati u blokirajuće bez prethodnog rješavanja, a ima širi obim od zelenog, kuriranog `check:cycles`. Zadržati `check:cycles` kao blokirajući; rješavanje 91 dpdm ciklusa je poseban zaostatak. -- **Kompleksnost** — ✅ **GOTOVO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jedan ESLint prolaz, broji po ruleId tako da ciklomatske+max-lines i kognitivne osnovne vrijednosti ostaju nezavisne; pojedinačni `check:complexity` / `check:cognitive-complexity` ostaju za lokalni `--update`. -- **`/api` anti-halucinacija** — ✅ **GOTOVO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jedan FS inventar `src/app/api`, openapi-routes + docs-symbols i dalje izvještavaju nezavisno; pojedinačni ostaju za lokalna pokretanja. -- **`check:node-runtime` se pokreće u 11 poslova** — ⚠️ **nizak ROI.** Svaki je poseban runner i provjera traje <1s; ukupne uštede ~10s, naspram gubitka jeftine zaštite po poslu. Nije vrijedno truda. -- **`typecheck:noimplicit:core` na CI lint-u** — ✅ **uklonjeno iz lint posla** (bilo je savjetodavno `continue-on-error`); blokirajuća površina tipova je `typecheck:core` + `check:type-coverage`. Lokalna skripta zadržana. - -### Promjena / odluka (politika operatera) - -- `check:openapi-security-tiers` (savjetodavno) — ❌ **NIJE lako promjenjivo.** Izlazi sa 0, ali upozorava da nekoliko `traffic-inspector` ruta pod `LOCAL_ONLY_API_PREFIXES` nema `x-loopback-only: true` anotaciju. Sprovođenje zahtijeva prvo dodavanje tih anotacija u `openapi.yaml`. -- `typecheck:noimplicit:core` (savjetodavno) — uglavnom obuhvaćeno blokirajućim `check:type-coverage` ratchet-om. Promijeniti u ratchet ili odbaciti redundantni drugi `tsc` prolaz. -- `test:vitest:ui` (sada **blokirajuće**) — postojeći kvarovi su eksplicitno isključeni u `vitest.config.ts` sa `// #8618` komentarima za praćenje; novi kvarovi obaraju posao. -- `check:secrets` (gitleaks, blokirajući ratchet zamrznut na 3 dokumentovana lažna pozitivna rezultata) — staviti 3 na listu dozvoljenih da bi se došlo do 0, ili degradirati na savjetodavno. Preklapa se sa GitHub izvornim skeniranjem tajni + `check:public-creds`. -- `check:pr-evidence` (blokirajuće, grepuje prozu u tijelu PR-a) — visok rizik od lažnih pozitivnih rezultata; slabi sprovođenje Hard Rule #18 ako se odbaci, tako da je ovo istinska odluka o politici. -- `semgrep` (savjetodavno samostalno) — preklapa se sa CodeQL za OWASP familije; povezati njegovu osnovnu vrijednost sa ratchet-om ili odbaciti. +## Zaostatak racionalizacije (pregled ROI-ja — Faza 9 Talas 3) + +Ovaj inventar je usklađen sa `ci.yml` 2026-06-17 (prethodna verzija je izostavila +`audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, +`check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, +`check:codeql-ratchet`, `check:pr-evidence`). Pregled ROI-ja usklađenog skupa +identificirao je sljedeće kandidate za racionalizaciju. **Spajanja su mehaničke CI +izmjene; promjene statusa/uklanjanja su odluke o pravilima rezervirane za operatera.** Ništa od navedenog +još nije primijenjeno. + +**Također nije dokumentirano iznad** (savjetodavno, slab signal): zadatak `docs-lint` +(markdownlint + Vale, cijeli zadatak ima `continue-on-error`) i samostalni tokovi rada skenera +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` se nalazi u +`quality-baseline.json`, ali nije povezan s blokirajućim mehanizmom postepenog pooštravanja u `ci.yml` — metrika je +trenutno nepovezana. + +### Spajanje / uklanjanje duplikata (mehanički, manji rizik) + +Svaki kandidat je provjeren u odnosu na aktivno stanje kontrolnih tačaka 2026-06-17 (vjeruj, ali provjeri); +ispostavilo se da nekoliko „očiglednih” spajanja skriva dug i **nisu** neposredno primjenjiva. + +- **`check:docs-sync` se pokreće dvaput** — samostalno u zadatku `lint` i ponovo unutar `check:docs-all` (`docs-sync-strict`) te u husky pre-commit kuki. ✅ **ZAVRŠENO** — samostalno pokretanje u zadatku `lint` je uklonjeno. +- **CVE skeniranje** — ❌ **NIJE jednostavno spajanje.** `audit:deps` bezuslovno ne prolazi za bilo koji CVE visoke/kritične ozbiljnosti; `check:vuln-ratchet` (osv) ne prolazi samo pri _regresiji_ u odnosu na osnovno stanje (trenutno 1 MODERATE). Različite semantike — uklanjanjem `audit:deps` izgubila bi se apsolutna kontrolna tačka za visoku/kritičnu ozbiljnost. Zadržati oba. +- **Otkrivanje ciklusa** — ✅ **ZAVRŠENO** (#15159 G-01/G-02). Stari tekst je ovdje nazivao `check:cycles` „zelenom, pažljivo odabranom” kontrolnom tačkom i opravdavao njeno zadržavanje kao blokirajuće jer je `check:circular-deps` (dpdm) prijavljivao 91 ciklus. To zeleno stanje bilo je **lažno zeleno**: `check:cycles` je skenirao 5 poddirektorija (450 datoteka), prepoznavao samo statičke izraze `import|export … from` i odbacivao svaki specifikator `@/` i `@omniroute/open-sse/`, pa nije mogao uočiti cikluse dinamičkog uvoza + aliasa koji su dominirali repozitorijem. Ispravljeno: kontrolna tačka sada prolazi kroz `src` + `open-sse` (5023 datoteke), prikuplja specifikatore iz TypeScript AST-a (tako da se `import("…")` računa, dok se `typeof import("…")` na poziciji tipa ne računa) i razrješava tsconfig `paths`. Pronalazi **14** ciklusa, a ne 0. Budući da se 14 prethodno postojećih ciklusa ne može ispraviti u PR-u kontrolne tačke, `check:cycles` je sada **mehanizam postepenog pooštravanja** (`--ratchet`, gornja granica `metrics.cycles.value = 14` u `quality-baseline.json`, `direction: down`) — blokira svaku _regresiju_, a broj se može samo smanjivati. CI pokreće `npm run check:cycles:ratchet`. Postepeno uklanjanje duga odvija se u sklopu **A-01**. `check:circular-deps` (dpdm) ostaje savjetodavan kao šire drugo mišljenje. +- **Složenost** — ✅ **ZAVRŠENO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jedan ESLint prolaz, brojanje prema ruleId-u kako bi osnovna stanja ciklomatske složenosti + maksimalnog broja linija i kognitivne složenosti ostala nezavisna; pojedinačne provjere `check:complexity` / `check:cognitive-complexity` ostaju za lokalni `--update`. +- **`/api` zaštita od halucinacija** — ✅ **ZAVRŠENO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jedan FS inventar za `src/app/api`, openapi-routes + docs-symbols i dalje izvještavaju nezavisno; pojedinačne provjere ostaju za lokalna pokretanja. +- **`check:node-runtime` se pokreće u 11 zadataka** — ⚠️ **nizak ROI.** Svaki je zaseban izvršitelj, a provjera traje <1s; ukupna ušteda je ~10s, uz gubitak jeftine zaštite za svaki zadatak. Nije vrijedno izmjena. +- **`typecheck:noimplicit:core` u CI lint zadatku** — ✅ **uklonjeno iz lint zadatka** (bilo je savjetodavno uz `continue-on-error`); blokirajuću površinu tipova čine `typecheck:core` + `check:type-coverage`. Lokalna skripta je zadržana. + +### Promijeniti status / odlučiti (pravila operatera) + +- `check:openapi-security-tiers` (savjetodavno) — ❌ **NE MOŽE se jednostavno učiniti blokirajućim.** Završava s kodom 0, ali upozorava da nekolicini `traffic-inspector` ruta pod `LOCAL_ONLY_API_PREFIXES` nedostaje anotacija `x-loopback-only: true`. Njegovo nametanje prvo zahtijeva dodavanje tih anotacija u `openapi.yaml`. +- `typecheck:noimplicit:core` (savjetodavno) — u velikoj mjeri ga zamjenjuje blokirajući mehanizam postepenog pooštravanja `check:type-coverage`. Pretvoriti ga u mehanizam postepenog pooštravanja ili ukloniti redundantni drugi `tsc` prolaz. +- `test:vitest:ui` (sada **blokirajuće**) — prethodno postojeći neuspjesi izričito su isključeni u `vitest.config.ts` pomoću komentara za praćenje `// #8618`; novi neuspjesi obaraju zadatak. +- `check:secrets` (gitleaks, blokirajući mehanizam postepenog pooštravanja zamrznut na 3 dokumentirana lažno pozitivna rezultata) — dodati ta 3 rezultata na listu dozvoljenih kako bi se došlo do 0 ili ga spustiti na savjetodavni status. Preklapa se s GitHubovim izvornim skeniranjem tajni + `check:public-creds`. +- `check:pr-evidence` (blokirajuće, pretražuje prozni tekst tijela PR-a) — visok rizik od lažno pozitivnih rezultata; uklanjanje bi oslabilo provođenje strogog pravila br. 18, tako da je ovo stvarna odluka o pravilima. +- `semgrep` (savjetodavni samostalni tok) — preklapa se s CodeQL-om za OWASP porodice; povezati njegovo osnovno stanje s mehanizmom postepenog pooštravanja ili ga ukloniti. --- diff --git a/docs/i18n/bs/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/bs/docs/compression/COMPRESSION_GUIDE.md index 4b39041f55ec..819500cf6bd9 100644 --- a/docs/i18n/bs/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/bs/docs/compression/COMPRESSION_GUIDE.md @@ -434,8 +434,7 @@ ubrizgava umjesto engleske. Na tijelu sa `messages`, zaobilaženje sadržaja (`shouldBypassCavemanOutputMode()` u `open-sse/services/compression/outputMode.ts`) provjerava posljednje tri poruke i preskače stilove za cijeli krug kada se podudaraju s njegovim ključnim riječima za sigurnost, nepovratnu akciju, -pojašnjenje ili osjetljivost na redoslijed. Zaobilaženje se izvršava bez obzira na to kako je -postavljen prekidač **Automatsko zaobilaženje jasnoće** na kontrolnoj tabli (`cavemanOutputMode.autoClarity`). +pojašnjenje ili osjetljivost na redoslijed. Zaobilaženje se izvršava dok je prekidač **Automatsko zaobilaženje jasnoće** (`cavemanOutputMode.autoClarity`) na kontrolnoj tabli uključen, što je zadana postavka; kad je prekidač isključen, odabrani stilovi primjenjuju se i na tim krugovima. Kada zaobilaženje propusti krug, `placeSystemInstruction()` (ista datoteka), koja nikada ne stvara novu `messages[0]`, postavlja blok u prvu od ovih opcija koju pronađe: diff --git a/docs/i18n/bs/docs/reference/ENVIRONMENT.md b/docs/i18n/bs/docs/reference/ENVIRONMENT.md index 5210b0f61a0e..6f2b11f1cb25 100644 --- a/docs/i18n/bs/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/bs/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Obavezne tajne -Ove vrijednosti se **moraju** postaviti prije prvog pokretanja. Bez njih će aplikacija odbiti pokretanje ili će raditi s nesigurnim zadanim vrijednostima. - -| Varijabla | Obavezna | Zadano | Izvorna datoteka | Opis | -| ---------------------------- | ------------------- | ------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Da** | _(nema)_ | `src/lib/auth` | Potpisuje/provjerava sve kolačiće sesije kontrolne ploče (JWT). Generišite pomoću `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Da** | _(nema)_ | `src/lib/db/apiKeys.ts` | AES ključ za šifriranje vrijednosti API ključeva pohranjenih u SQLite bazi. Generišite pomoću `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Da** | `CHANGEME` | Skripta za inicijalizaciju | Postavlja početnu administratorsku lozinku za kontrolnu ploču (podudara se sa zadanom vrijednošću u `.env.example` — namjerno je očigledno nesigurna kako bi se zahtijevala promjena). **Promijenite prije prve upotrebe.** Nakon prijave promijenite je putem Kontrolna ploča → Postavke → Sigurnost. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (produkcija) | _(nije postavljeno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Dijeljena tajna za interni WebSocket most Codex Responses. Autentificira zahtjeve mosta između Electron/browser WS releja i OmniRoutea. ⚠️ **OBAVEZNO u produkciji — kada nije postavljena, svi zahtjevi WS mosta se odbijaju.** Generišite pomoću `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplicitni identifikator za poništavanje keša servisnog radnika za PWA ljusku (#11779); prvi u lancu razrješavanja. | -| `SOURCE_VERSION` | Ne | _(nije postavljeno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi u lancu — postavljaju ga PaaS sistemi za izgradnju (npr. u stilu Herokua) kao implementirani commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(izvedeno)_ | `src/shared/components/PwaRegister.tsx` | Javna vrijednost u vrijeme izgradnje koju klijent koristi za registraciju `/sw.js?v=…`; izvodi se iz prethodne dvije vrijednosti, a zatim iz git SHA vrijednosti. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automatski) | _(automatski pri svakom pokretanju)_ | `src/server/authz/policies/management.ts` | Tajna po procesu koja dokazuje da je oznaka pouzdane IP adrese ravnopravnog uređaja došla s OmniRouteovog vlastitog HTTP servera (`scripts/dev/peer-stamp.mjs`). Authz međuprogram vjeruje lokalnosti zahtjeva (ograničavanje LOCAL_ONLY ruta na loopback/LAN) samo kada oznaka sadrži ovaj token. Automatski se generiše pri svakom pokretanju — ostavite je nepostavljenom; fiksirajte je samo za konfiguracije s više procesa koje moraju dijeliti oznaku. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nije postavljeno)_ | `scripts/dev/peer-stamp.mjs` | IP adrese ili CIDR rasponi reverznih proxyja, odvojeni zarezima, čijim vrijednostima `X-Forwarded-For` / `X-Real-IP` lista dozvoljenih/zabranjenih IP adresa može vjerovati, pored loopback adresa, adresa privatne mreže i Cloudflare rubnih čvorova, koji su uvijek pouzdani. Potrebno je samo za proxy na bilo kojoj drugoj javnoj adresi; u suprotnom se procjenjuje vlastita adresa tog proxyja. | +Nepostavljene tajne označene s **Da** automatski se generišu pri prvom pokretanju (`JWT_SECRET`, `API_KEY_SECRET`) i pohranjuju u `server.env` u direktoriju podataka — postavite ih izričito kada je taj direktorij privremen (ephemeral). Izuzetak je `OMNIROUTE_WS_BRIDGE_SECRET`: ona se ne pohranjuje u `server.env` — ulazne točke servera (entrypoints) generišu privremenu vrijednost po pokretanju koju može koristiti samo most unutar samog procesa, pa je vanjski klijenti mosta moraju postaviti izričito. + +| Varijabla | Obavezna | Zadano | Izvorna datoteka | Opis | +| ---------------------------- | ------------------- | ------------------------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Da** | _(nema)_ | `src/lib/auth` | Potpisuje/provjerava sve kolačiće sesije kontrolne ploče (JWT). Generišite pomoću `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Da** | _(nema)_ | `src/lib/db/apiKeys.ts` | AES ključ za šifriranje vrijednosti API ključeva pohranjenih u SQLite bazi. Generišite pomoću `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ne | _(nije postavljeno)_ | `src/lib/auth/managementPassword.ts` | Postavlja lozinku kontrolne ploče pri pokretanju dok još nijedna lozinka nije sačuvana; zanemaruje se nakon što se lozinka sačuva. npm instalacije i instalacije iz izvornog koda kopiraju `.env.example` u `.env`, pa počinju s njegovim javno poznatim rezervnim mjestom `CHANGEME`: zamijenite ga prije prvog pokretanja ili postavite `INITIAL_PASSWORD=` (prazno) u tom `.env` da biste lozinku kreirali u čarobnjaku za početno postavljanje pri prvoj posjeti, kao što to čini i nepostavljena vrijednost (zadana u Docker slici). Sačuvanu lozinku mijenjajte u Kontrolna ploča → Postavke → Sigurnost, ili pokrenite `omniroute reset-password` (`node bin/reset-password.mjs` u izvornom checkoutu) s `DATA_DIR` postavljenim na direktorij podataka servera. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (produkcija) | _(nije postavljeno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Dijeljena tajna za interni WebSocket most Codex Responses. Autentificira zahtjeve mosta između Electron/browser WS releja i OmniRoutea. ⚠️ **OBAVEZNO u produkciji — kada nije postavljena, svi zahtjevi WS mosta se odbijaju.** Generišite pomoću `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplicitni identifikator za poništavanje keša servisnog radnika za PWA ljusku (#11779); prvi u lancu razrješavanja. | +| `SOURCE_VERSION` | Ne | _(nije postavljeno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi u lancu — postavljaju ga PaaS sistemi za izgradnju (npr. u stilu Herokua) kao implementirani commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(izvedeno)_ | `src/shared/components/PwaRegister.tsx` | Javna vrijednost u vrijeme izgradnje koju klijent koristi za registraciju `/sw.js?v=…`; izvodi se iz prethodne dvije vrijednosti, a zatim iz git SHA vrijednosti. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automatski) | _(automatski pri svakom pokretanju)_ | `src/server/authz/policies/management.ts` | Tajna po procesu koja dokazuje da je oznaka pouzdane IP adrese ravnopravnog uređaja došla s OmniRouteovog vlastitog HTTP servera (`scripts/dev/peer-stamp.mjs`). Authz međuprogram vjeruje lokalnosti zahtjeva (ograničavanje LOCAL_ONLY ruta na loopback/LAN) samo kada oznaka sadrži ovaj token. Automatski se generiše pri svakom pokretanju — ostavite je nepostavljenom; fiksirajte je samo za konfiguracije s više procesa koje moraju dijeliti oznaku. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nije postavljeno)_ | `scripts/dev/peer-stamp.mjs` | IP adrese ili CIDR rasponi reverznih proxyja, odvojeni zarezima, čijim vrijednostima `X-Forwarded-For` / `X-Real-IP` lista dozvoljenih/zabranjenih IP adresa može vjerovati, pored loopback adresa, adresa privatne mreže i Cloudflare rubnih čvorova, koji su uvijek pouzdani. Potrebno je samo za proxy na bilo kojoj drugoj javnoj adresi; u suprotnom se procjenjuje vlastita adresa tog proxyja. | ### Naredbe za generisanje @@ -216,38 +216,38 @@ OmniRoute koristi **SQLite** (putem `better-sqlite3`) za svu trajnu pohranu. Ove ## 4. Sigurnost i autentifikacija -| Varijabla | Zadana vrijednost | Izvorna datoteka | Opis | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt koji se kombinuje s hardverskim identifikatorima radi izrade otiska uređaja. Promijenite ga za svaku implementaciju radi izolacije. | -| `OMNIROUTE_CLI_SALT` | _(nije postavljeno = nasumični salt za svaku instalaciju, sačuvan u `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt za izvođenje lokalnog CLI tokena za autentifikaciju. Postavljanje ove vrijednosti rotira sve CLI tokene na uređaju i uvijek ima prioritet nad sačuvanim saltom. Pogledajte `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Postavlja oznaku `Secure` na kolačićima sesije. **Mora biti `true`** kada se koristi iza HTTPS-a. | -| `REQUIRE_API_KEY` | `false` | API međusloj | Kada je `true`, svi `/v1/*` proxy zahtjevi moraju sadržavati važeći API ključ. **Ova oznaka ne kontroliše `GET /v1/models`**, koji umjesto toga slijedi postavku prijave kontrolne ploče (`requireAuthForModels`) — stoga `401` od `/v1/models` NE znači da je izvođenje zaključivanja zaštićeno. Pogledajte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Omogućava prikazivanje punih vrijednosti API ključeva u korisničkom interfejsu kontrolne ploče. Može se konfigurirati putem oznaka funkcionalnosti na kontrolnoj ploči; predstavlja sigurnosni rizik na dijeljenim instancama. | -| `NO_LOG_API_KEY_IDS` | _(prazno)_ | `src/lib/compliance/index.ts` | ID-ovi API ključeva, odvojeni zarezima, koji zaobilaze evidentiranje zahtjeva (usklađenost s GDPR-om). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nije postavljeno = neograničeno)_ | `src/shared/utils/apiKeyPolicy.ts` | Rezervni dnevni budžet zahtjeva koji se primjenjuje na API ključeve čija je kolona `rate_limits` null. Nije postavljeno ili je prazno: nema implicitnog ograničenja (#2289, #11017). `0` ima isto značenje (neograničeno). Pozitivan cijeli broj N omogućava N/dan, 5N/sedmica, 20N/mjesec. Neispravne vrijednosti koje nisu prazne vraćaju se na naslijeđene intervale od 1000/dan, 5000/sedmica i 20000/mjesec. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimalna dozvoljena veličina tijela zahtjeva. Odbacuje korisne podatke koji premašuju ovo ograničenje. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Stvarna tijela zahtjeva koja dosežu ili premašuju ovaj prag preuzimaju atomsku procesno-lokalnu dozvolu za zahtjevno opterećenje prije parsiranja JSON-a (BYTE putanja, uključujući `POST /v1/responses`). Isti mehanizam izbjegavanja pri dovoljnom raspoloživom kapacitetu iz [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kao za strukturno zahtjevne zahtjeve; i dalje je ograničen putem `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), tako da se [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne ponovi. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Strogo ograničenje chat rute koje se primjenjuje na bajtove pročitane tokom ograničenog učitavanja, uključujući zahtjeve bez zaglavlja `Content-Length` te s nevažećim ili lažnim zaglavljem `Content-Length`; prekoračenje dobija odgovor `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nije postavljeno — nema ograničenja broja zahtjeva)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ovo naslijeđeno ograničenje BROJA zahtjeva sada se primjenjuje samo kada je izričito postavljeno. Kada nije postavljeno (zadano), prihvat zahtjevnih chat zahtjeva umjesto toga ograničava `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatski izveden BAJTOVNI budžet dimenzioniran prema stvarnom memorijskom ograničenju procesa u **jednom procesu** (jedna V8 hrpa). Dva preklapajuća `/v1/responses` zahtjeva od približno 750.000 tokena ruše hrpe od približno 12 GiB (#7849) — to je upozorenje o **memorijskom budžetu**, a ne strogi maksimalni broj od 2 za proizvod. Zdrav proces (hrpa ispod omjera rasterećenja) MOŽE prihvatiti više istovremenih dugih `/v1/responses` putem `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Deseci dugih SSE klijenata (40–50) zavise od hrpe + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, a ne od „najviše 2“. Slijepo povećavanje ove vrijednosti radi „korištenja hosta“ ponovo uvodi #7849. Umnožavajte **hrpe** pomoću **N nezavisnih `DATA_DIR` direktorija** (#11024); nikada nemojte koristiti `replicas>1` nad jednom SQLite datotekom. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatski izvedeno)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nadjačavanje automatski izvedenog bajtovnog budžeta učitavanja (25% strožeg V8/cgroup memorijskog ograničenja podijeljeno s 8x prolaznim uvećanjem). Izvedene i eksplicitne vrijednosti ograničavaju se na 8 MiB–2 GiB. Tijelo veće od efektivnog budžeta odmah dobija grešku `413 body_exceeds_budget`; nadmetanje između tijela koja se pojedinačno mogu obraditi i dalje daje ponovljivu grešku `503`. 40–50 istovremenih dugih SSE klijenata zavisi od ovog budžeta + hrpe, a ne od strogog ograničenja „najviše 2“. Prije podešavanja provjerite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Omjer rasterećenja uslijed pritiska na hrpu (`heapUsed / heap_size_limit`) za prihvat zahtjevnih zahtjeva prema BAJTOVIMA i STRUKTURI (#10183, #10268, #10437). Istovremeni zahtjevni zahtjev koji prekoračuje `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` odbacuje se s ponovljivom greškom `503` samo kada je hrpa TAKOĐER na ovom omjeru ili iznad njega; uz zdravu hrpu prihvata se putem dodatnog kapaciteta zdrave hrpe. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (zadano `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ograničeni dodatni kapacitet za brzu putanju zdrave hrpe (#10437) i za STRUKTURU i za BAJTOVE (`admitChatRequest`, uključujući tijela ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez ovog ograničenja svaki zahtjev pri zauzetom, ali zdravom stanju hrpe zaobilazio je kontrolu prihvata bez gornje granice. Kada ovoliko istovremenih zakupa postane aktivno putem putanje zdrave hrpe, daljnji zahtjevi pri zauzetom stanju prelaze na ISTU putanju ograničenog čekanja/rasterećenja koja se koristi pri stvarnom pritisku na hrpu. `0` potpuno onemogućava zaobilaženje. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Broj poruka pri kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod bajtovnog praga. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Broj alata pri kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod bajtovnog praga. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativna procjena broja tokena na osnovu veličine niza koja zahtjev klasificira kao zahtjevan; ovo je zamjenska vrijednost za trošak prihvata, a ne tokenizacija za obračun pružaoca usluge. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (onemogućeno) | `src/shared/middleware/chatBodyAdmission.ts` | Opcionalno ograničenje historije chata koje se mora izričito uključiti. Zadano je onemogućeno: broj poruka je politika implementacije, a ne univerzalno svojstvo zahtjeva, a ograničavanje na ovom mjestu odbacuje razgovore konačnom greškom `413` prije nego što ih cjevovod kompresije može učiniti obradivim. Rast hrpe ograničen je putem `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` i rasterećenjem uslijed pritiska na hrpu. Postavite pozitivnu vrijednost u implementacijama s ograničenom memorijom kojima je potrebna stroga gornja granica; prekoračenje tada dobija strukturiranu grešku `413` koja zahtijeva sažimanje. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Strogo ograničenje za odgovor uzvodnog sistema bez streaminga koji se u cijelosti pohranjuje u memoriju. Nakon prekoračenja čitač uzvodnog odgovora se otkazuje i zahtjev brzo završava neuspjehom umjesto da neograničeni niz raste sve dok se hrpa ne iscrpi. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalan broj bajtova na mreži proslijeđenih iz zaglavlja odgovora nadređenog servisa. Kada se budžet prekorači, zaglavlja nižeg prioriteta (npr. prilagođena `x-codex-*`, `x-oai-request-id`) odbacuju se kako bi se ostalo unutar uobičajenih ograničenja zaglavlja obrnutog proxyja. Postavite veću vrijednost za prosljeđivanje više metapodataka nadređenog servisa po cijenu većeg zaglavlja odgovora. | -| `CORS_ORIGIN` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | Zastarjela CORS lista dozvoljenih izvora s jednim izvorom. Za nova postavljanja preferirajte `CORS_ALLOWED_ORIGINS`. CORS služi samo za višeizvorne klijente API-ja u pregledniku; autentificirani upisi kontrolne ploče umjesto toga koriste zahtjeve istog izvora uz CSRF zaštitu vezanu za sesiju. | -| `CORS_ALLOWED_ORIGINS` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | CORS lista dozvoljenih izvora odvojenih zarezima. Zamjenski znak se ne šalje osim ako je izričito konfigurirano `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Izlaz u nuždi, namijenjen samo za razvoj, koji vraća bilo koji `Origin` preglednika. Nemojte ga omogućavati na dijeljenim ili produkcijskim postavljanjima. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokira pozive pružatelja usmjerene na privatne/povratne/link-local raspone IP adresa. Onemogućite samo u izoliranim testnim okruženjima. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Dozvoljava URL-ove pružatelja koji upućuju na privatne/lokalne mreže (localhost, 192.168.x.x, 10.x.x.x itd.). **OBAVEZNO za samostalno hostovane pružatelje** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Kada je postavljeno na `false`, kontrolna ploča odbija validaciju lokalnih URL-ova. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Dozvoljava dodavanje/validaciju pružatelja na lokalnim/privatnim adresama (127.0.0.1, localhost, LAN, privatni rasponi) — ograničeno na putanju validacije pružatelja. **Zadana vrijednost je `true`** (lokalno na prvom mjestu); postavite na `false` radi nametanja strogog blokiranja svega osim javnih adresa. Krajnje tačke metapodataka u oblaku (169.254.169.254, metadata.google.internal) ostaju blokirane bez obzira na postavku. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Dozvoljava rutama `/v1/audio/*` (transkripcije, govor, prijevodi) da koriste čvor pružatelja kompatibilan s OpenAI-jem koji je hostovan izvan localhosta. Zadano je isključeno — usmjeravanje zvuka na udaljeni host mijenja identitet izlaznog saobraćaja i mora biti izričita odluka operatera. Povratni/privatni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvijek su dozvoljeni i ova postavka na njih ne utiče. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Dozvoljava da `POST /v1/rerank` (i korak ponovnog rangiranja preko povratne veze memorijskog mehanizma) koristi čvor pružatelja kompatibilan s OpenAI-jem koji je hostovan izvan localhosta — uređaj na LAN-u ili Tailscale ravnopravni čvor na kojem se izvršava TEI, Infinity, vLLM itd. Zadano je isključeno — usmjeravanje na udaljeni host mijenja identitet izlaznog saobraćaja i mora biti izričita odluka operatera. Čvorovi povratne veze (localhost, 127.0.0.1, 172.16-31.x) uvijek su dozvoljeni i ova postavka na njih ne utiče. Udaljeni čvorovi također moraju proći pravila za izlazne URL-ove pružatelja (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); saobraćaj se nikada ne usmjerava prema hostovima metapodataka u oblaku. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kada je OIDC omogućen, onemogućava prijavu lozinkom kako bi se korisnici mogli autentificirati samo putem OIDC jedinstvene prijave. Prihvaća se i osnovni pseudonim `OIDC_DISABLE_PASSWORD_LOGIN`; zastavica funkcionalnosti kontrolne ploče s istim ključem ima prednost. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Osnovni pseudonim za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Varijabla | Zadana vrijednost | Izvorna datoteka | Opis | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt koji se kombinuje s hardverskim identifikatorima radi izrade otiska uređaja. Promijenite ga za svaku implementaciju radi izolacije. | +| `OMNIROUTE_CLI_SALT` | _(nije postavljeno = nasumični salt za svaku instalaciju, sačuvan u `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt za izvođenje lokalnog CLI tokena za autentifikaciju. Postavljanje ove vrijednosti rotira sve CLI tokene na uređaju i uvijek ima prioritet nad sačuvanim saltom. Pogledajte `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Postavlja oznaku `Secure` na kolačićima sesije. **Mora biti `true`** kada se koristi iza HTTPS-a. | +| `REQUIRE_API_KEY` | `false` | API međusloj | Kada je `true`, svi `/v1/*` proxy zahtjevi moraju sadržavati važeći API ključ. **Ova oznaka ne kontroliše `GET /v1/models`**, koji umjesto toga slijedi postavku prijave kontrolne ploče (`requireAuthForModels`) — stoga `401` od `/v1/models` NE znači da je izvođenje zaključivanja zaštićeno. Pogledajte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Omogućava prikazivanje punih vrijednosti API ključeva u korisničkom interfejsu kontrolne ploče. Može se konfigurirati putem oznaka funkcionalnosti na kontrolnoj ploči; predstavlja sigurnosni rizik na dijeljenim instancama. | +| `NO_LOG_API_KEY_IDS` | _(prazno)_ | `src/lib/compliance/index.ts` | ID-ovi API ključeva, odvojeni zarezima, koji zaobilaze evidentiranje zahtjeva (usklađenost s GDPR-om). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nije postavljeno = neograničeno)_ | `src/shared/utils/apiKeyPolicy.ts` | Rezervni dnevni budžet zahtjeva koji se primjenjuje na API ključeve čija je kolona `rate_limits` null. Nije postavljeno ili je prazno: nema implicitnog ograničenja (#2289, #11017). `0` ima isto značenje (neograničeno). Pozitivan cijeli broj N omogućava N/dan, 5N/sedmica, 20N/mjesec. Neispravne vrijednosti koje nisu prazne vraćaju se na naslijeđene intervale od 1000/dan, 5000/sedmica i 20000/mjesec. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimalna dozvoljena veličina tijela zahtjeva. Odbacuje korisne podatke koji premašuju ovo ograničenje. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Stvarna tijela zahtjeva koja dosežu ili premašuju ovaj prag preuzimaju atomsku procesno-lokalnu dozvolu za zahtjevno opterećenje prije parsiranja JSON-a (BYTE putanja, uključujući `POST /v1/responses`). Isti mehanizam izbjegavanja pri dovoljnom raspoloživom kapacitetu iz [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kao za strukturno zahtjevne zahtjeve; i dalje je ograničen putem `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), tako da se [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne ponovi. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Strogo ograničenje chat rute koje se primjenjuje na bajtove pročitane tokom ograničenog učitavanja, uključujući zahtjeve bez zaglavlja `Content-Length` te s nevažećim ili lažnim zaglavljem `Content-Length`; prekoračenje dobija odgovor `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nije postavljeno — nema ograničenja broja zahtjeva)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ovo naslijeđeno ograničenje BROJA zahtjeva sada se primjenjuje samo kada je izričito postavljeno. Kada nije postavljeno (zadano), prihvat zahtjevnih chat zahtjeva umjesto toga ograničava `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatski izveden BAJTOVNI budžet dimenzioniran prema stvarnom memorijskom ograničenju procesa u **jednom procesu** (jedna V8 hrpa). Dva preklapajuća `/v1/responses` zahtjeva od približno 750.000 tokena ruše hrpe od približno 12 GiB (#7849) — to je upozorenje o **memorijskom budžetu**, a ne strogi maksimalni broj od 2 za proizvod. Zdrav proces (hrpa ispod omjera rasterećenja) MOŽE prihvatiti više istovremenih dugih `/v1/responses` putem `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Deseci dugih SSE klijenata (40–50) zavise od hrpe + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, a ne od „najviše 2“. Slijepo povećavanje ove vrijednosti radi „korištenja hosta“ ponovo uvodi #7849. Umnožavajte **hrpe** pomoću **N nezavisnih `DATA_DIR` direktorija** (#11024); nikada nemojte koristiti `replicas>1` nad jednom SQLite datotekom. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatski izvedeno)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nadjačavanje automatski izvedenog bajtovnog budžeta učitavanja (25% strožeg V8/cgroup memorijskog ograničenja podijeljeno s 8x prolaznim uvećanjem). Izvedene i eksplicitne vrijednosti ograničavaju se na 8 MiB–2 GiB. Tijelo veće od efektivnog budžeta odmah dobija grešku `413 body_exceeds_budget`; nadmetanje između tijela koja se pojedinačno mogu obraditi i dalje daje ponovljivu grešku `503`. 40–50 istovremenih dugih SSE klijenata zavisi od ovog budžeta + hrpe, a ne od strogog ograničenja „najviše 2“. Prije podešavanja provjerite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Omjer rasterećenja uslijed pritiska na hrpu (`heapUsed / heap_size_limit`) za prihvat zahtjevnih zahtjeva prema BAJTOVIMA i STRUKTURI (#10183, #10268, #10437). Istovremeni zahtjevni zahtjev koji prekoračuje `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` odbacuje se s ponovljivom greškom `503` samo kada je hrpa TAKOĐER na ovom omjeru ili iznad njega; uz zdravu hrpu prihvata se putem dodatnog kapaciteta zdrave hrpe. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (zadano `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ograničeni dodatni kapacitet za brzu putanju zdrave hrpe (#10437) i za STRUKTURU i za BAJTOVE (`admitChatRequest`, uključujući tijela ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez ovog ograničenja svaki zahtjev pri zauzetom, ali zdravom stanju hrpe zaobilazio je kontrolu prihvata bez gornje granice. Kada ovoliko istovremenih zakupa postane aktivno putem putanje zdrave hrpe, daljnji zahtjevi pri zauzetom stanju prelaze na ISTU putanju ograničenog čekanja/rasterećenja koja se koristi pri stvarnom pritisku na hrpu. `0` potpuno onemogućava zaobilaženje. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Broj poruka pri kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod bajtovnog praga. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Broj alata pri kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod bajtovnog praga. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativna procjena broja tokena na osnovu veličine niza koja zahtjev klasificira kao zahtjevan; ovo je zamjenska vrijednost za trošak prihvata, a ne tokenizacija za obračun pružaoca usluge. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (onemogućeno) | `src/shared/middleware/chatBodyAdmission.ts` | Opcionalno ograničenje historije chata koje se mora izričito uključiti. Zadano je onemogućeno: broj poruka je politika implementacije, a ne univerzalno svojstvo zahtjeva, a ograničavanje na ovom mjestu odbacuje razgovore konačnom greškom `413` prije nego što ih cjevovod kompresije može učiniti obradivim. Rast hrpe ograničen je putem `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` i rasterećenjem uslijed pritiska na hrpu. Postavite pozitivnu vrijednost u implementacijama s ograničenom memorijom kojima je potrebna stroga gornja granica; prekoračenje tada dobija strukturiranu grešku `413` koja zahtijeva sažimanje. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Strogo ograničenje za odgovor uzvodnog sistema bez streaminga koji se u cijelosti pohranjuje u memoriju. Nakon prekoračenja čitač uzvodnog odgovora se otkazuje i zahtjev brzo završava neuspjehom umjesto da neograničeni niz raste sve dok se hrpa ne iscrpi. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalan broj bajtova na mreži proslijeđenih iz zaglavlja odgovora nadređenog servisa. Kada se budžet prekorači, zaglavlja nižeg prioriteta (npr. prilagođena `x-codex-*`, `x-oai-request-id`) odbacuju se kako bi se ostalo unutar uobičajenih ograničenja zaglavlja obrnutog proxyja. Postavite veću vrijednost za prosljeđivanje više metapodataka nadređenog servisa po cijenu većeg zaglavlja odgovora. | +| `CORS_ORIGIN` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | Zastarjela CORS lista dozvoljenih izvora s jednim izvorom. Za nova postavljanja preferirajte `CORS_ALLOWED_ORIGINS`. CORS služi samo za višeizvorne klijente API-ja u pregledniku; autentificirani upisi kontrolne ploče umjesto toga koriste zahtjeve istog izvora uz CSRF zaštitu vezanu za sesiju. | +| `CORS_ALLOWED_ORIGINS` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | CORS lista dozvoljenih izvora odvojenih zarezima. Zamjenski znak se ne šalje osim ako je izričito konfigurirano `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Izlaz u nuždi, namijenjen samo za razvoj, koji vraća bilo koji `Origin` preglednika. Nemojte ga omogućavati na dijeljenim ili produkcijskim postavljanjima. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Naslijeđeni alias (legacy). Njegova sklopka na kontrolnoj ploči (override u bazi podataka) čita se prije ove vrijednosti okoline; `false`, `0`, `no` ili `off` u bilo kojoj od njih isključuje provjere hostova na isti način na koji to čini `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, čak i kada je sklopka te oznake na kontrolnoj ploči sačuvana kao isključena; ostale vrijednosti ostavljaju zaštitniku dvije oznake niže. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Isključuje provjere hostova zaštitnika izlaznih URL-ova, uključujući blokadu metapodataka u oblaku (cijeli raspon 169.254.0.0/16, metadata.google.internal), na putanjama pružatelja koje slijede režim zaštitnika: validacija URL-ova, otkrivanje modela, osnovni URL-ovi čvorova pružatelja (uključujući udaljene čvorove za ponovno rangiranje) i test proxy povratnog spajanja, među ostalim izlaznim putanjama u blizini pružatelja kao što je dohvat udaljenih slika/medija. Dozvoljava i privatne webhook ciljeve, izuzev krajnjih tačaka metapodataka u oblaku/link-local, koje ostaju blokirane čak i dok je ova oznaka uključena. Zahtjevi za razgovor proslijeđeni kroz `BaseExecutor.execute()` zadržavaju svoju provjeru metapodataka u oblaku. Lokalni pružatelji rade sa zadanim vrijednostima: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (zadano `true`) već dozvoljava lokalne i LAN URL-ove na putanjama validacije, otkrivanja i čvorova pružatelja; test proxy povratnog spajanja i privatni webhook ciljevi konsultuju samo ovu oznaku i blokiraju lokalne/LAN hostove dok je isključena. Nadjačava `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Vrijednost sačuvana sa sklopke na kontrolnoj ploči ima prednost nad ovom varijablom. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Dozvoljava URL-ove pružatelja na lokalnim/privatnim adresama (127.0.0.1, localhost, LAN, privatni rasponi) za validaciju pružatelja, otkrivanje modela, razgovor i ostale izlazne putanje u blizini pružatelja kojima upravlja režim zaštitnika. **Zadana vrijednost je `true`** (lokalno na prvom mjestu): zaštitnik tada blokira krajnje tačke metapodataka u oblaku — cijeli raspon 169.254.0.0/16 plus poznata imena hostova metapodataka (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Postavite na `false` za režim samo javnih adresa, koji blokira i privatne i povratne (loopback) hostove. Zaštitnik provjerava ime hosta ili IP literal onako kako je zapisano, prije bilo kakvog DNS upita; u režimu samo javnih adresa omotač sigurnog dohvata dodatno razrješava ime hosta i odbija bilo koji privatni odgovor. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` isključuje ove provjere na putanjama navedenim u njegovom redu. Ugrađeni lokalni pružatelji, kao što su LM Studio, Ollama, vLLM, Llamafile i Triton, preskaču sve provjere hostova za validaciju ključeva i razgovor. Vrijednost sačuvana sa sklopke na kontrolnoj ploči ima prednost nad ovom varijablom. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Dozvoljava rutama `/v1/audio/*` (transkripcije, govor, prijevodi) da koriste čvor pružatelja kompatibilan s OpenAI-jem koji je hostovan izvan localhosta. Zadano je isključeno — usmjeravanje zvuka na udaljeni host mijenja identitet izlaznog saobraćaja i mora biti izričita odluka operatera. Povratni/privatni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvijek su dozvoljeni i ova postavka na njih ne utiče. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Dozvoljava da `POST /v1/rerank` (i korak ponovnog rangiranja preko povratne veze memorijskog mehanizma) koristi čvor pružatelja kompatibilan s OpenAI-jem koji je hostovan izvan localhosta — uređaj na LAN-u ili Tailscale ravnopravni čvor na kojem se izvršava TEI, Infinity, vLLM itd. Zadano je isključeno — usmjeravanje na udaljeni host mijenja identitet izlaznog saobraćaja i mora biti izričita odluka operatera. Čvorovi povratne veze (localhost, 127.0.0.1, 172.16-31.x) uvijek su dozvoljeni i ova postavka na njih ne utiče. Osnovni URL udaljenog čvora također mora proći pravila za izlazne URL-ove pružatelja (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kada je OIDC omogućen, onemogućava prijavu lozinkom kako bi se korisnici mogli autentificirati samo putem OIDC jedinstvene prijave. Prihvaća se i osnovni pseudonim `OIDC_DISABLE_PASSWORD_LOGIN`; zastavica funkcionalnosti kontrolne ploče s istim ključem ima prednost. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Osnovni pseudonim za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Kontrolna lista za sigurnosno ojačavanje diff --git a/docs/i18n/cs/CONTRIBUTING.md b/docs/i18n/cs/CONTRIBUTING.md index 210ac23b1e34..29148431eea1 100644 --- a/docs/i18n/cs/CONTRIBUTING.md +++ b/docs/i18n/cs/CONTRIBUTING.md @@ -391,18 +391,18 @@ Napište unit testy v `tests/unit/`, které budou pokrývat minimálně: - [ ] Testy procházejí (`npm test`) - [ ] Kontrola lintování prochází (`npm run lint`) -- [ ] Sestavení je úspěšné (`npm run build`) +- [ ] Sestavení proběhne úspěšně (`npm run build`) - [ ] Pro nové veřejné funkce a rozhraní byly přidány typy TypeScriptu -- [ ] Žádná natvrdo zadaná tajemství ani záložní hodnoty -- [ ] Veřejné přihlašovací údaje upstreamu jsou vloženy prostřednictvím `resolvePublicCred()` (viz [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikdy jako literály -- [ ] Chybové odpovědi procházejí přes `buildErrorBody()` / `sanitizeErrorMessage()` — těla odpovědí neobsahují nezpracované výpisy zásobníku (viz [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Příkazy shellu (`exec` / `spawn`) předávají hodnoty za běhu prostřednictvím `env`, nikoli pomocí interpolace řetězců +- [ ] Žádné pevně zadané tajné údaje ani záložní hodnoty +- [ ] Veřejné přihlašovací údaje upstreamu jsou vloženy pomocí `resolvePublicCred()` (viz [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikdy jako literály +- [ ] Chybové odpovědi procházejí přes `buildErrorBody()` / `sanitizeErrorMessage()` — v tělech odpovědí nejsou žádné nezpracované výpisy zásobníku (viz [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shellové příkazy (`exec` / `spawn`) předávají hodnoty za běhu prostřednictvím `env`, nikoli pomocí interpolace řetězců - [ ] Všechny vstupy jsou validovány pomocí schémat Zod - [ ] Pro změny viditelné uživatelům byl přidán **fragment** přehledu změn do `changelog.d/{features|fixes|maintenance}/-.md` (viz [`changelog.d/README.md`](./changelog.d/README.md)) — neupravujte přímo `CHANGELOG.md`; fragmenty se slučují při vydání a mezi Pull Requesty nikdy nevznikají konflikty - [ ] Dokumentace byla aktualizována (pokud je to relevantní) -- [ ] Nebyla vytvořena žádná nová upozornění CodeQL / Secret-Scanning, případně bylo každé z nich zamítnuto s technickým odůvodněním odkazujícím na příslušný dokument v `docs/security/` -- [ ] Trasy, které spouštějí podřízené procesy (`/api/mcp/`, `/api/cli-tools/runtime/`), jsou v `src/server/authz/routeGuard.ts` klasifikovány jako `isLocalOnlyPath()` — viz [Pevné pravidlo č. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Zprávy commitů neobsahují patičky `Co-Authored-By` — commity musí být uvedeny výhradně pod identitou Git vlastníka repozitáře (Pevné pravidlo č. 16) +- [ ] Nebyla vytvořena žádná nová upozornění CodeQL / Secret-Scanning, případně byla všechna zamítnuta s technickým odůvodněním odkazujícím na příslušný dokument v `docs/security/` +- [ ] Trasy, které spouštějí podřízené procesy (`/api/mcp/`, `/api/cli-tools/runtime/`), jsou klasifikovány jako `isLocalOnlyPath()` v `src/server/authz/routeGuard.ts` — viz [Pevné pravidlo č. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Zprávy commitů neobsahují patičky AI/botů `Co-authored-by` (Pevné pravidlo č. 16) — lidští spolupracovníci, jejichž práce je znovu použita, jsou uvedeni pomocí standardních patiček `Co-authored-by: Jméno ` --- diff --git a/docs/i18n/cs/docs/architecture/QUALITY_GATES.md b/docs/i18n/cs/docs/architecture/QUALITY_GATES.md index c4e663b121b2..05d98e5c38b3 100644 --- a/docs/i18n/cs/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/cs/docs/architecture/QUALITY_GATES.md @@ -15,263 +15,267 @@ stejného systému nezávislý na konkrétních nástrojích naleznete v dokumen --- -## Inventář kontrolních bran a profily spouštění +## Inventář bran a profily spouštění ### Přijetí kandidáta -Workflowy CI a Quality Gates vydávají stabilní verdikt: `Gate / CI` a -`Gate / Quality`. Jejich verzované zásady přijímání uvádějí každou upstreamovou úlohu -jako povinnou nebo poradní. Každá relevantní povinná úloha musí uspět: chybějící, -zrušené, přeskočené, čekající a neznámé výsledky nemohou potvrdit stav PASS. Platná -klasifikace pouze pro dokumentaci nebo pouze pro katalog může způsobit, že větev pro kód nebude relevantní; -koncept PR není přijatelným kandidátem. Štítek `hotfix` neruší požadavek na důkazy. +Workflowy CI a Quality Gates poskytují stabilní verdikt: `Gate / CI` a +`Gate / Quality`. Jejich verzovaná politika přijetí označuje každou nadřazenou úlohu +jako povinnou nebo doporučující. Příslušná povinná úloha musí být úspěšná: chybějící, +zrušené, přeskočené, čekající ani neznámé výsledky nemohou stanovit stav PASS. Platná +klasifikace pouze pro dokumentaci nebo pouze pro katalog může způsobit, že větev +pro kód nebude použitelná; koncept PR není přijatelným kandidátem. Štítek `hotfix` +neruší požadavky na důkazy. -Oba workflowy pokrývají PR a pushnutí do hlavních/release větví, ruční spuštění a -události skupin slučování. Pushnutí, ruční spuštění a skupiny slučování spouštějí úplný výběr. Forky -a skupiny slučování používají hostované runnery pro úlohy, které by jinak zvolily self-hosted -runnery; před nasazením je nutné ověřit dostatečnou hostovanou kapacitu. +Oba workflowy pokrývají PR a pushnutí do hlavních/release větví, ruční spuštění +a události skupin slučování. Pushnutí, ruční spuštění a skupiny slučování spouštějí +úplný výběr. Forky a skupiny slučování používají hostované runnery pro úlohy, které +by jinak vybíraly self-hosted runnery; před zavedením musí být ověřena dostatečná +kapacita hostovaných runnerů. Každá účtenka JSON identifikuje SHA checkoutu, běh workflow a pokus. -CLI odmítne nesoulad mezi SHA checkoutu a události. Testy workflow vážou členství v zásadách -na seznam `needs` úlohy s verdiktem, takže nová nebo odebraná větev nemůže tiše zmizet. -Účtenky pokrývají vlastní workflow, nikoli publikování, nasazení ani vnitřní fungování -existujícího poradního skeneru. Aktivace obou názvů kontrol v pravidlech větve je -samostatná administrativní změna; přidání těchto úloh samo o sobě větev nechrání. +CLI odmítne nesoulad SHA checkoutu a události. Testy workflow vážou členství v politice +na seznam `needs` úlohy s verdiktem, takže nová nebo odebraná větev nemůže nepozorovaně +zmizet. Účtenky pokrývají vlastní workflow, nikoli publikování, nasazení ani interní +fungování existujícího doporučujícího skeneru. Aktivace obou názvů kontrol v pravidlech +větví je samostatnou administrativní změnou; samotné přidání těchto úloh větev nechrání. ### Inventář statických kontrol Verzovaný inventář aliasů npm a členství ve statických kontrolách se nacházejí v -`config/quality/gate-manifest.json`. Spusťte `npm run check:gate-manifest` a ověřte -názvy skriptů a přesné příkazy vůči souboru `package.json`; přidání, odebrání a -změny příkazů způsobí selhání lokálního hooku i úloh klasifikace změn v CI. -Alias není úlohou workflow, instancí matice ani testovacím případem: tyto počty +`config/quality/gate-manifest.json`. Spuštěním `npm run check:gate-manifest` ověřte +názvy skriptů a přesné příkazy vůči `package.json`; přidání, odebrání a změny příkazů +způsobí selhání jak lokálního hooku, tak úloh klasifikace změn v CI. +Alias není úloha workflow, instance matice ani testovací případ: počty těchto položek nesmějí být prezentovány jako vzájemně zaměnitelné. Pomocí `npm run quality:scan -- --list` nebo `npm run quality:scan:fast -- --list` -můžete vybrané aliasy zkontrolovat bez jejich spuštění. Runner volá vstupní bod -npm, takže jeho běhové prostředí (včetně Bun, kde je nakonfigurován) zůstává zachováno. -Manifest eviduje aliasy mimo tyto profily jako spouštěné samostatně a -v profilech kontrol určených pouze pro čtení jsou příkazy údržby zakázány. +můžete zkontrolovat vybrané aliasy bez jejich spuštění. Runner volá vstupní bod +npm, takže je zachováno jeho běhové prostředí (včetně Bun tam, kde je nakonfigurován). +Manifest zaznamenává aliasy mimo tyto profily jako spouštěné samostatně a +v profilech kontrol pouze pro čtení jsou příkazy údržby zakázány. Tyto profily pokrývají pouze statické kontroly. Necertifikují produktové testy, pokrytí, balení, externí kontroly ani úplné přijetí kandidáta k vydání. -Přijímání ve workflow používá propojené soubory `config/quality/admission-policy.json` a -`scripts/quality/admission-verdict.mjs`. Profily release observeru zůstávají oddělené; -jejich relevantní kontroly a účtenky prověřujte nezávisle. Níže uvedený textový -inventář slouží jako reference, nikoli jako důkaz, že kontrolní brána skutečně proběhla. +Přijetí workflow používá propojené soubory `config/quality/admission-policy.json` a +`scripts/quality/admission-verdict.mjs`. Profily pozorovatele vydání zůstávají oddělené; +jejich příslušné kontroly a účtenky prověřujte nezávisle. Níže uvedený textový +inventář slouží jako reference, nikoli jako důkaz, že byla brána skutečně spuštěna. -Skripty se nacházejí v `scripts/check/` (kontrolní brány zásad) a `scripts/quality/` (ratchet engine). +Skripty se nacházejí v `scripts/check/` (brány politik) a `scripts/quality/` (ratchet engine). Zdrojem pravdy pro CI je `.github/workflows/ci.yml`. -### Zrychlený průchod pro release PR (`quality.yml`) +### Zrychlená cesta pro release PR (`quality.yml`) `.github/workflows/quality.yml` doplňuje CI pro PR do hlavních/release větví, pushnutí do chráněných větví, ruční spuštění a skupiny slučování. PR používají rychlé kontroly -filtrované podle cest. Trvale zakázaný duplicitní build byl odstraněn; skutečné kontroly -buildu, balíčku a spuštění zůstávají v CI. - -| Úloha | Rozsah | Blokující | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `Docs Gates (fast-path)` | PR dokumentace/kódu; reference dokumentace API a veškerá dokumentace | Ano | -| `Fast Quality Gates` | PR s kódem; statické kontroly, typecheck, typecheck dashboardu, ovlivněné jednotkové testy | Ano | -| `Forgotten sibling tests` | PR s kódem; změněné moduly dohledané ke statickým konzumentům a kandidátním souvisejícím testům; cesty přes barrel soubory a dynamické importy jsou hlášeny jako poradní diagnostika s odkazovanými výjimkami v allowlistu | **Poradní** | -| `Vitest (fast-path)` | PR s kódem; rychlá sada vitest | Ano | -| `Unit Tests fast-path` | PR s kódem; sada jednotkových testů rozdělená do 4 shardů | Ano | -| `No new ESLint warnings` | PR s kódem; lintovací kontrola zohledňující potlačení | Ano, včetně forků | -| `Merge integrity (changelog + generated skills)` | PR, které nejsou koncepty; synchronizace changelogu a generovaných skills | Ano, včetně forků | +filtrované podle cest. Trvale zakázané duplicitní sestavení bylo odebráno; skutečné +kontroly sestavení, balíčku a spuštění zůstávají v CI. + +| Úloha | Rozsah | Blokující | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `Docs Gates (fast-path)` | PR dokumentace/kódu; odkazy na dokumentaci API a veškerá dokumentace | Ano | +| `Fast Quality Gates` | PR kódu; statické kontroly, kontrola typů, kontrola typů dashboardu, dotčené jednotkové testy | Ano | +| `Forgotten sibling tests` | PR kódu; změněné moduly vysledované ke statickým konzumentům a kandidátním souvisejícím testům; cesty přes barrel soubory a dynamické importy jsou hlášeny jako doporučující diagnostika s uvedenými výjimkami ze seznamu povolených položek | **Doporučující** | +| `Vitest (fast-path)` | PR kódu; rychlá sada Vitest | Ano | +| `Unit Tests fast-path` | PR kódu; jednotková sada rozdělená do 4 shardů | Ano | +| `No new ESLint warnings` | PR kódu; kontrola lintu zohledňující potlačení | Ano, včetně forků | +| `Merge integrity (changelog + generated skills)` | PR, které nejsou koncepty; synchronizace changelogu a generovaných dovedností | Ano, včetně forků | #### Report zapomenutých souvisejících testů -`npm run check:forgotten-sibling-tests` znovu používá resolver importů, na němž je založena mapa dopadu testů. +`npm run check:forgotten-sibling-tests` znovu používá resolver importů, na kterém je založena mapa dopadu testů. Pro každý změněný produkční modul hlásí deterministické řetězce `změněný modul/symbol -> statický konzument -> kandidátní související test`, pokud kandidátní test chybí v diffu pull requestu. Souhrn ve formátu Markdown a výsledek JSON jsou uchovány jako -artefakt workflow `forgotten-sibling-tests` pro kalibraci před případným zavedením blokujícího režimu. +artefakt workflow `forgotten-sibling-tests` pro kalibraci před případným zavedením blokující kontroly. -Reexporty z barrel souborů a dynamické importy slouží pouze k diagnostice rozlišení; nikdy nevytvářejí -blokující nález. Zkontrolované výjimky se nacházejí v +Opětovné exporty ze souborů barrel a dynamické importy slouží pouze jako diagnostika rozlišení; nikdy nevytvářejí +blokující nález. Posouzené výjimky se nacházejí v `config/quality/forgotten-sibling-allowlist.json`. Každá položka musí uvádět spotřebitele a kandidátní -test, obsahovat konkrétní zdůvodnění a odkazovat na issue nebo pull request na GitHubu. Chybně -formátované položky vedou k zamítnutí. Výjimky nemohou potlačit odstraněný kandidátní test ani změnu, -která přidává `.skip`/`.todo`; oslabování asercí a další maskování zůstávají v působnosti nezávisle -blokující kontroly `check:test-masking`. +test, obsahovat konkrétní odůvodnění a odkazovat na issue nebo pull request na GitHubu. Chybně vytvořené položky způsobí +zamítnutí. Výjimky nemohou potlačit smazaný kandidátní test ani změnu, která přidává `.skip`/`.todo`; +oslabování asercí a další maskování nadále spadá pod samostatně blokující +bránu `check:test-masking`. ### Úloha: `lint` Spouští se při každém PR do `main`. Při selhání blokuje sloučení. -| Skript (`npm run ...`) | Ověřuje | Blokující | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Verze Node.js je v podporovaném rozsahu | Ano | -| `check:cycles` | Cyklické importy — všechny moduly v `src/` + `open-sse/` | Ano | -| `check:route-validation:t06` | Schémata Zod jsou přítomna na všech trasách (zásady úrovně 6) | Ano | -| `check:any-budget:t11` | Počet `@ts-expect-error // any` nepřekračuje limit (catraca úrovně 11) | Ano | -| `check:provider-consistency` | Každý poskytovatel v `providers.ts` má odpovídající záznam v `providerRegistry.ts` (a naopak, v rámci seznamu povolených položek) | Ano | -| `check:model-lifecycle` | Tři ručně udržované směrovací tabulky zůstávají konzistentní se zařazeným snímkem životního cyklu (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nepřiřazuje skóre žádnému vyřazenému id, které může `REGISTRY` směrovat; každý cíl `BUILT_IN_ALIASES` je přítomen v `REGISTRY` a chybí ve snímku vyřazených id; každé vyřazené id, které je stále v `REGISTRY`, je přesměrováno nebo uvedeno v `allowedRetiredInCatalog`; a žádný zdroj ani cíl `DEFAULT_DEGRADATION_MAP` není v tomto snímku označen jako vyřazený. Toto nedokazuje, že je model aktuálně poskytován aktivní nadřazenou službou. Offline — porovnává se s `config/quality/model-lifecycle.json`, který se ručně aktualizuje pomocí `npm run quality:refresh-model-lifecycle` (vyžaduje síť; není zapojeno do CI). `allowedRetiredInCatalog` je postupně zpřísňovaná pojistka: záznam přidávejte pouze s odpovídajícím sledovacím problémem. | Ano | -| `check:fetch-targets` | Každé `fetch("/api/...")` v klientské části `src/` odkazuje na skutečný soubor `route.ts` | Ano | -| `check:deps` | Všechny závislosti instalovatelné pomocí `npm install` napříč všemi soubory `package.json` v repozitáři jsou uvedeny v `dependency-allowlist.json`; nové závislosti bez pevně určené verze nebo balíčky podezřelé z překlepu v názvu jsou označeny | Ano | -| `audit:deps` | `npm audit` (kořenový projekt + electron) — žádná upozornění vysoké ani kritické závažnosti (překrývá se s osv `check:vuln-ratchet`; viz backlog racionalizace) | Ano | -| `check:lockfile` | Integrita souboru `package-lock.json` — registr https, kontrolní součty integrity, žádná přepsání hostitele | Ano | -| `check:licenses` | Seznam povolených licencí SPDX pro produkční závislosti | Ano | -| `check:tracked-artifacts` | Žádné artefakty sestavení / verzované symbolické odkazy na `node_modules` (spouští se také v husky pre-commit; pre-push je záměrně odlehčený — #6716) | Ano | -| `check:ai-attribution` | Žádný dovětek `Co-Authored-By` od AI/bota ani poznámka o vygenerování AI v commitech, názvu nebo těle PR — Pevné pravidlo č. 16 (ve smyčce rychlých kontrol `quality.yml` pro PR→`release/**` — čte data události, mimo PR neprovádí žádnou operaci — a v kroku lint určeném pouze pro PR v `ci.yml` pro PR→`main`; také hook husky `commit-msg`; lidští spoluautoři jsou povoleni; #14436) | -| `check:vitest-exclusions` | Každá výjimka Vitest uvádí sledovaný problém a je uvedena v `config/quality/vitest-exclusions.json` (#13204) | Ano | -| `check:file-size` | Žádný zdrojový soubor nepřekračuje limit pro danou příponu (ráčna: velké soubory zmrazené v seznamu `frozen`) | Ano | -| `check:error-helper` | Chybové odpovědi v executorech/handlerech používají `buildErrorBody()` / `sanitizeErrorMessage()` (Pevné pravidlo č. 12) | Ano | -| `check:migration-numbering` | SQL soubory migrací jsou číslovány sekvenčně, bez mezer či duplicit | Ano | -| `check:public-creds` | Žádné literály OAuth `client_id`/`client_secret` ani webové klíče Firebase mimo `publicCreds.ts` (Pevné pravidlo č. 11) | Ano | -| `check:db-rules` | Žádné přímé SQL mimo moduly `src/lib/db/`; žádné souhrnné importy z `localDb.ts` (Pevná pravidla č. 2/5) | Ano | -| `check:known-symbols` | Exekutory poskytovatelů, strategie směrování a překladače zaregistrované v jejich dispatch tabulkách odpovídají souborům na disku — žádné osiřelé ani nedeklarované symboly | Ano | -| `check:route-guard-membership` | Každá trasa, která spouští podřízený proces, je klasifikována pomocí `isLocalOnlyPath()` (Pevná pravidla č. 15/17) | Ano | -| `check:test-discovery` | Každý soubor `*.test.ts` / `*.spec.ts` v repozitáři je zahrnut alespoň jedním nástrojem pro spouštění testů (ráčna: seznam osiřelých souborů v `test-discovery-baseline.json` se může pouze zmenšovat) | Ano | -| `check:agent-skills-sync` | Vygenerované artefakty agent-skills odpovídají jejich zdrojovému katalogu (bez rozdílů) | -| `check:provider-asset-provenance` | Loga/asset-y poskytovatelů mají zaznamenaný údaj o původu | -| `lint:json` | Konfigurační soubory JSON lze analyzovat a splňují lintovací pravidla repozitáře | -| `typecheck:core` | Kompilace TypeScriptu bez chyb (pouze informativní varování) | Ano | -| `typecheck:noimplicit:core` | Striktní `noImplicitAny` — výhledově; mnoho již existujících míst volání stále potřebuje anotace | **Informativní** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` omezené na `src/app/(dashboard)/**` (#7033) — pečlivě vybraný seznam 27 souborů v `typecheck:core` neobsahuje žádný dashboardový TSX a ani `next build` u nich nikdy neprovádí kontrolu typů (`next.config.mjs` nastavuje `ignoreBuildErrors: true`), takže regrese s osiřelými identifikátory (#6625/#6909) tam byly pro CI neviditelné. Rozdíly se porovnávají se zmrazenou výchozí hodnotou počtu chyb pro jednotlivé soubory a kódy TS (`config/quality/dashboard-typecheck-baseline.json`, stejný vzor vynucování zastaralosti jako u `check:known-symbols`) — brána selže pouze při NOVÝCH chybách nad rámec výchozího počtu; když je opravena již existující chyba, snižte výchozí hodnotu pomocí `--update`. | Ano | +| Skript (`npm run ...`) | Ověřuje | Blokující | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Verze Node.js je v podporovaném rozsahu | Ano | +| `check:cycles` | Cyklické importy napříč celými `src/` + `open-sse/` (založeno na AST, `paths` z tsconfig jsou vyřešeny). Samostatné spuštění = informativní, vypíše cykly. `check:cycles:ratchet` (varianta spouštěná v CI) blokuje, když počet překročí strop `metrics.cycles` v `quality-baseline.json` — aktuálně 14, `direction: down`, takže může pouze klesat (#15159 G-01/G-02) | Ano (ratchet) | +| `check:route-validation:t06` | Přítomnost schémat Zod na všech routách (zásada úrovně 6) | Ano | +| `check:any-budget:t11` | Počet `@ts-expect-error // any` nepřekračuje rozpočet (catraca úrovně 11) | Ano | +| `check:provider-consistency` | Každý poskytovatel v `providers.ts` má odpovídající záznam v `providerRegistry.ts` (a naopak, v rámci seznamu povolených položek) | Ano | +| `check:model-lifecycle` | Tři ručně udržované směrovací tabulky zůstávají konzistentní se snapshotem životního cyklu uloženým v repozitáři (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nehodnotí žádné vyřazené id, na které může `REGISTRY` směrovat; každý cíl `BUILT_IN_ALIASES` je přítomen v `REGISTRY` a chybí ve snapshotu vyřazených id; každé vyřazené id, které je stále v `REGISTRY`, je přesměrováno nebo uvedeno v `allowedRetiredInCatalog`; a žádný zdroj ani cíl `DEFAULT_DEGRADATION_MAP` není v tomto snapshotu označen jako vyřazený. Toto nedokazuje, že je model aktuálně poskytován aktivní upstream službou. Offline — porovnává se s `config/quality/model-lifecycle.json`, který se ručně aktualizuje pomocí `npm run quality:refresh-model-lifecycle` (vyžaduje síť; není zapojeno do CI). `allowedRetiredInCatalog` je postupně zpřísňovaná pojistka: záznam přidávejte pouze spolu se sledovacím issue. | Ano | +| `check:fetch-targets` | Každé `fetch("/api/...")` v klientské části `src/` se překládá na skutečný soubor `route.ts` | Ano | +| `check:deps` | Všechny závislosti instalovatelné pomocí `npm install` ze všech souborů `package.json` v repozitáři jsou uvedeny v `dependency-allowlist.json`; nové závislosti bez připnuté verze nebo balíčky podezřelé ze slopsquattingu jsou označeny | Ano | +| `audit:deps` | `npm audit` (kořenový adresář + electron) — žádná upozornění s vysokou nebo kritickou závažností (překrývá se s osv `check:vuln-ratchet`; viz backlog racionalizace) | Ano | +| `check:lockfile` | Integrita souboru `package-lock.json` — registr HTTPS, integritní hashe, žádná přepsání hostitelů | Ano | +| `check:licenses` | Seznam povolených licencí SPDX pro produkční závislosti | Ano | +| `check:tracked-artifacts` | Žádné artefakty sestavení / verzované symbolické odkazy na `node_modules` (spouští se také v husky pre-commit; pre-push je záměrně odlehčený — #6716) | Ano | +| `check:ai-attribution` | Žádná patička `Co-Authored-By` od AI/bota ani patička o vygenerování pomocí AI v commitech, názvu nebo popisu PR — Pevné pravidlo č. 16 (ve smyčce rychlých kontrol v `quality.yml` pro PR→`release/**` — čte datovou část události, mimo PR nic neprovádí — a krok pouze pro PR v lint kontrole v `ci.yml` pro PR→`main`; také hook husky `commit-msg`; lidští spoluautoři jsou povoleni; #14436) | +| `check:vitest-exclusions` | Každá výjimka Vitest uvádí sledovaný problém a je uvedena v `config/quality/vitest-exclusions.json` (#13204) | Ano | +| `check:file-size` | Žádný zdrojový soubor nepřekračuje limit pro danou příponu (rohatka: velké soubory zmrazené v seznamu `frozen`) | Ano | +| `check:error-helper` | Chybové odpovědi v executorech/handlerech používají `buildErrorBody()` / `sanitizeErrorMessage()` (Pevné pravidlo č. 12) | Ano | +| `check:migration-numbering` | Migrační SQL soubory jsou číslovány sekvenčně, bez mezer či duplicit | Ano | +| `check:public-creds` | Žádné literály OAuth `client_id`/`client_secret` ani webové klíče Firebase mimo `publicCreds.ts` (Pevné pravidlo č. 11) | Ano | +| `check:db-rules` | Žádné přímé SQL mimo moduly `src/lib/db/`; žádné hromadné importy z `localDb.ts` (Pevná pravidla č. 2/5) | Ano | +| `check:known-symbols` | Spouštěče poskytovatelů, strategie směrování a překladače registrované v příslušných dispečerských tabulkách odpovídají souborům na disku — žádné osiřelé ani nedeklarované symboly | Ano | +| `check:route-guard-membership` | Každá trasa, která spouští podřízený proces, je klasifikována pomocí `isLocalOnlyPath()` (Pevná pravidla č. 15/17) | Ano | +| `check:test-discovery` | Každý soubor `*.test.ts` / `*.spec.ts` v repozitáři je zahrnut alespoň jedním nástrojem pro spouštění testů (rohatka: seznam osiřelých souborů v `test-discovery-baseline.json` se může pouze zmenšovat) | Ano | +| `check:agent-skills-sync` | Vygenerované artefakty agent-skills odpovídají svému zdrojovému katalogu (bez odchylek) | +| `check:provider-asset-provenance` | Loga/assetty poskytovatelů mají zaznamenaný údaj o původu | +| `lint:json` | Konfigurační soubory JSON lze analyzovat a splňují pravidla lintování repozitáře | +| `typecheck:core` | Kompilace TypeScriptu bez chyb (pouze informativní varování) | Ano | +| `typecheck:noimplicit:core` | Striktní `noImplicitAny` — zaměřeno do budoucna; mnoho již existujících míst volání stále potřebuje anotace | **Informativní** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` omezené na `src/app/(dashboard)/**` (#7033) — kurátorovaný seznam 27 souborů v `typecheck:core` neobsahuje žádný dashboardový TSX a ani `next build` jej nikdy typově nekontroluje (`next.config.mjs` nastavuje `ignoreBuildErrors: true`), takže regrese osiřelých identifikátorů v této oblasti (#6625/#6909) nebyly v CI viditelné. Rozdíly se porovnávají se zmrazenou výchozí hodnotou počtu chyb pro každý soubor a každý kód TS (`config/quality/dashboard-typecheck-baseline.json`, stejný vzor vynucování aktuálnosti jako u `check:known-symbols`) — kontrola selže pouze u NOVÝCH chyb nad rámec výchozího počtu; při opravě již existující chyby výchozí hodnotu snižte pomocí `--update`. | Ano | ### Úloha: `quality-gate` Spouští se po `test-coverage`. Při selhání blokuje sloučení. -| Skript | Ověřuje | Blokující | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | Vytváří `quality-metrics.json` (počet varování ESLint, pokrytí ze sloučeného reportu shardů) | Ano (předchází ratchetu) | -| `quality:ratchet` | Žádná metrika v `quality-baseline.json` se nezhoršila (varování ESLint ≤ výchozí hodnota; pokrytí ≥ výchozí hodnota) | Ano | -| `check:duplication` | Duplikace kódu (jscpd@4) nepřekračuje výchozí hodnotu v `quality-baseline.json` | Ano | -| `check:complexity` | Cyklomatická složitost na úrovni souboru nepřekračuje limit (základní pravidla ESLint `complexity` + `max-lines-per-function`) | Ano | -| `check:cognitive-complexity` | Ratchet kognitivní složitosti (`eslint-plugin-sonarjs`) — samostatný průchod ESLint; CI spouští oba sloučené do jediného kroku `check:complexity-ratchets` | Ano | -| `check:dead-code` | Ratchet nepoužitých exportů / souborů (knip) se nezhoršil oproti výchozí hodnotě | Ano | -| `check:compression-budget` | Rozpočet benchmarku komprese — minimální úspory tokenů pro jednotlivé enginy se nesmí zhoršit | Ano | -| `check:type-coverage` | Ratchet procenta typovaného kódu (`type-coverage`) se nezhoršil; z velké části nahrazuje `typecheck:noimplicit:core` | Ano | -| `check:codeql-ratchet` | Počet otevřených upozornění CodeQL se nezhoršil (načítá se pomocí `gh api`; bez tokenu se kontrola korektně přeskočí) — frekvence aktualizace a ruční spuštění: viz „Ratchet CodeQL“ níže | Ano | +| Skript | Ověřuje | Blokující | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | Vytváří `quality-metrics.json` (počet varování ESLint, pokrytí ze sloučeného reportu dílčích běhů) | Ano (předchází ratchetu) | +| `quality:ratchet` | Žádná metrika v `quality-baseline.json` se nezhoršila (varování ESLint ≤ výchozí hodnota; pokrytí ≥ výchozí hodnota) | Ano | +| `check:duplication` | Duplikace kódu (jscpd@4) nepřekračuje výchozí hodnotu v `quality-baseline.json` | Ano | +| `check:complexity` | Cyklomatická složitost na úrovni souboru nepřekračuje limit (základní pravidla ESLint `complexity` + `max-lines-per-function`) | Ano | +| `check:cognitive-complexity` | Ratchet kognitivní složitosti (`eslint-plugin-sonarjs`) — samostatný průchod ESLint; CI spouští oba společně jako jediný krok `check:complexity-ratchets` | Ano | +| `check:dead-code` | Ratchet nepoužitých exportů / souborů (knip) se oproti výchozímu stavu nezhoršil | Ano | +| `check:compression-budget` | Rozpočet benchmarku komprese — minimální úspory tokenů pro jednotlivé enginy se nesmějí zhoršit | Ano | +| `check:type-coverage` | Ratchet procenta typovaného kódu (`type-coverage`) se nezhoršil; z velké části nahrazuje `typecheck:noimplicit:core` | Ano | +| `check:codeql-ratchet` | Počet otevřených výstrah CodeQL se nezhoršil (načítá se přes `gh api`; bez tokenu se korektně přeskočí) — interval aktualizace a ruční spuštění: viz níže „Ratchet CodeQL“ | Ano | ### Úloha: `quality-extended` -Celá úloha je informativní (`continue-on-error: true`). Ratchety založené na npm se spouštějí -skutečně; externí skenery se instalují pomocí `gh release download` a samy se přeskočí (ukončení s kódem 0), -pokud binární soubor stále chybí. +Celá úloha je pouze informativní (`continue-on-error: true`). Ratchety založené na npm se +spouštějí skutečně; externí skenery se instalují pomocí `gh release download` a samy se přeskočí (ukončení s kódem 0), +pokud binární soubor stále není k dispozici. | Skript | Ověřuje | Blokující | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | `check:circular-deps` | Žádné cyklické závislosti (dpdm) | **Informativní** | | `check:bundle-size` | Velikost balíčku nepřekračuje limit | **Informativní** | -| `check:secrets` | Kontrola tajných údajů (gitleaks) — přeskočí se, pokud binární soubor chybí | **Informativní** | -| `check:vuln-ratchet` | Zranitelnosti závislostí (osv-scanner) se nezhoršily — přeskočí se, pokud binární soubor chybí | **Informativní** | -| `check:workflows` | Lint pracovních postupů (actionlint + zizmor); chybějící/nefunkční skenery, neplatné reporty nebo chybějící výchozí hodnota ratchetu způsobí selhání se stavem INCOMPLETE. Platné nálezy se řídí zvolenými zásadami strict/advisory/ratchet | Spuštění je povinné; ratchet zizmor je v CI blokující | -| `check:openapi-breaking` | Zpětně nekompatibilní změny veřejného kontraktu API (`openapi.yaml`) oproti základní větvi (oasdiff) — vytváří `openapiBreaking=N`; přeskočí se, pokud oasdiff chybí nebo nelze načíst základní specifikaci | **Informativní** | +| `check:secrets` | Vyhledávání tajných údajů (gitleaks) — přeskočí se, pokud binární soubor není k dispozici | **Informativní** | +| `check:vuln-ratchet` | Zranitelnosti závislostí (osv-scanner) se nezhoršily — přeskočí se, pokud binární soubor není k dispozici | **Informativní** | +| `check:workflows` | Kontrola workflow (actionlint + zizmor); chybějící/nefunkční skenery, neplatné reporty nebo chybějící výchozí stav ratchetu způsobí selhání se stavem INCOMPLETE. Platná zjištění se řídí vybranou přísnou/informativní/ratchetovou zásadou | Spuštění je povinné; ratchet zizmor je v CI blokující | +| `check:openapi-breaking` | Zpětně nekompatibilní změny veřejného kontraktu API (`openapi.yaml`) oproti základní větvi (oasdiff) — vytváří `openapiBreaking=N`; přeskočí se, pokud oasdiff není k dispozici nebo nelze načíst základní specifikaci | **Informativní** | ### Úloha: `docs-sync-strict` -Spouští se při každém PR do `main`. Při selhání blokuje sloučení. - -| Skript | Ověřuje | Blokující | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `check:docs-all` | Meta-brána, která postupně spouští 6 dílčích bran uvedených níže | Ano | -| ↳ `check:docs-sync` | Konzistence verzí mezi CHANGELOGem / OpenAPI / llm.txt | Ano | -| ↳ `check:docs-counts` | Počty v textu (počet poskytovatelů, počet migrací atd.) jsou v rámci zpřísňujícího se rozmezí skutečných počtů | Ano | -| ↳ `check:env-doc-sync` | Každá proměnná prostředí v `.env.example` je zdokumentována v tabulce dokumentace a naopak | Ano | -| ↳ `check:deprecated-versions` | V dokumentaci nejsou žádné řetězce zastaralých verzí | Ano | -| ↳ `check:doc-links` | Interní markdownové odkazy v dokumentaci odkazují na skutečné soubory (ve formátu `[text]`/`(path)`) | Ano | -| ↳ `check:fabricated-docs` | Trasy, proměnné prostředí, příkazy CLI, názvy hooků a cesty k souborům uvedené v dokumentaci existují v kódové základně. Tvrdá brána přes `--strict`; bez příznaku pouze nezávazné selhání. | Ano (přes `--strict` v CI) | -| `check:cli-i18n` | Řetězce příkazů CLI jsou přítomny ve všech souborech lokalizací i18n | Ano | -| `check:openapi-coverage` | Specifikace OpenAPI pokrývá alespoň postupně zpřísňovanou minimální hranici skutečných tras | Ano | -| `check:openapi-security-tiers` | Anotace úrovní zabezpečení v `openapi.yaml` jsou konzistentní s klasifikacemi v `routeGuard.ts` | **Informativní** | -| `check:openapi-routes` | Každá cesta v `openapi.yaml` odpovídá skutečnému souboru `route.ts` (ochrana proti halucinacím) | Ano | -| `check:docs-symbols` | Každý odkaz `/api/...` v `docs/**/*.md` odpovídá skutečnému souboru `route.ts` (ochrana proti halucinacím) | Ano | -| `i18n translation drift` | Nepřeložené klíče v souborech lokalizací i18n — pouze varování | **Informativní** | +Spouští se při každém PR do `main`. Při selhání zablokuje sloučení. + +| Skript | Ověřuje | Blokující | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | Metabránu, která postupně spouští 6 níže uvedených dílčích bran | Ano | +| ↳ `check:docs-sync` | Konzistenci verzí v CHANGELOGu / OpenAPI / llm.txt | Ano | +| ↳ `check:docs-counts` | Počty v textu (počet poskytovatelů, počet migrací atd.) se nacházejí v tolerančním rozsahu skutečných počtů | Ano | +| ↳ `check:env-doc-sync` | Každá proměnná prostředí v `.env.example` je zdokumentována v tabulce dokumentace a naopak | Ano | +| ↳ `check:deprecated-versions` | V dokumentaci nejsou žádné řetězce zastaralých verzí | Ano | +| ↳ `check:doc-links` | Interní markdownové odkazy v dokumentaci odkazují na existující soubory (ve formátu `[text]`/`(path)`) | Ano | +| ↳ `check:fabricated-docs` | Trasy, proměnné prostředí, příkazy CLI, názvy hooků a cesty k souborům uvedené v dokumentaci existují v kódové základně. Tvrdá brána přes `--strict`; bez tohoto příznaku nezpůsobí selhání. | Ano (přes `--strict` v CI) | +| `check:cli-i18n` | Řetězce příkazů CLI jsou přítomny ve všech souborech lokalizací i18n | Ano | +| `check:openapi-coverage` | Specifikace OpenAPI pokrývá alespoň postupně zpřísňovanou minimální hranici skutečných tras | Ano | +| `check:openapi-security-tiers` | Anotace úrovní zabezpečení v `openapi.yaml` jsou konzistentní s klasifikacemi v `routeGuard.ts` | **Informativní** | +| `check:openapi-routes` | Každá cesta v `openapi.yaml` odpovídá skutečnému souboru `route.ts` (ochrana proti halucinacím) | Ano | +| `check:docs-symbols` | Každý odkaz `/api/...` v `docs/**/*.md` odpovídá skutečnému souboru `route.ts` (ochrana proti halucinacím) | Ano | +| `i18n translation drift` | Nepřeložené klíče v souborech lokalizací i18n — pouze upozornění | **Informativní** | ### Úloha: `i18n-ui-coverage` -| Skript | Ověřuje | Blokující | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (inline) | Pokrytí klíčů i18n uživatelského rozhraní je ≥ 65 % | Ano | -| `check-ui-value-drift` (inline) | Přepsaná anglická **hodnota** po sobě nezanechá žádný zastaralý překlad | Ano | -| `check-new-key-coverage` (inline) | **Nový** anglický klíč je přeložen ve všech lokalizacích — značka `__MISSING__:` je odmítnuta | Ano | -| `check-translation-ratio` | Poměr skutečných překladů v jednotlivých lokalizacích (položky shodné s angličtinou / zástupné / chybějící mimo seznam povolených výjimek) nesmí překročit `config/quality/i18n-translation-baseline.json` + toleranci | **Informativní** | +| Skript | Ověřuje | Blokující | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | +| `check-ui-keys-coverage` (inline) | Pokrytí klíčů i18n uživatelského rozhraní je ≥ 65 % | Ano | +| `check-ui-value-drift` (inline) | Po přepsání anglické **hodnoty** nezůstane žádný zastaralý překlad | Ano | +| `check-new-key-coverage` (inline) | **Nový** anglický klíč je přeložen ve všech lokalizacích — značka `__MISSING__:` je zamítnuta | Ano | +| `check-translation-ratio` | Podíl skutečných překladů v každé lokalizaci (položky mimo seznam výjimek, které jsou shodné s angličtinou, zástupné nebo chybějí) nesmí překročit `config/quality/i18n-translation-baseline.json` + rezervu | **Informativní** | -Vyžaduje `fetch-depth: 0` — brána kontroly změn hodnot porovnává `en.json` se společným předkem větví. +Vyžaduje `fetch-depth: 0` — brána kontroly změn hodnot porovnává `en.json` se společným předkem. #### `check-ui-value-drift` — brána proti zastaralým překladům -Odhaluje jedinou regresi i18n, kterou ostatní brány ze své podstaty nedokážou rozpoznat: anglická hodnota -je přepsána, ale překlady odvozené z _předchozího_ anglického znění zůstanou zachovány, takže +Zachytává jednu regresi i18n, kterou ostatní brány ze své podstaty nedokážou odhalit: anglická hodnota +je přepsána, ale překlady odvozené od _předchozího_ anglického znění zůstanou beze změny, takže neanglicky mluvící uživatelé nadále čtou sebejistě formulovaný, avšak nyní nesprávný text. -K tomu skutečně došlo v produkční verzi. `oauthModal.googleOAuthWarning` byl přepsán při zavedení pomocníka -pro přihlášení Antigravity (#5203); **39 ze 43 lokalizací** nadále obsahovalo text, který operátorům říkal, aby „zkopírovali -celou adresu URL a vložili ji níže“ — postup, který u tohoto poskytovatele nelze dokončit. Problém zůstal -bez povšimnutí až do #8463, protože: +K tomu skutečně došlo v produkční verzi. `oauthModal.googleOAuthWarning` byl přepsán při přidání +pomocníka pro přihlášení Antigravity (#5203); **39 ze 43 lokalizací** si ponechalo text, který správcům +říkal, aby „zkopírovali celou adresu URL a vložili ji níže“ — postup, který u tohoto poskytovatele nelze +dokončit. Až do #8463 to zůstalo bez povšimnutí, protože: -- `sync-ui-keys` doplňuje pouze klíče, které **chybějí**, nikdy ty, které jsou **zastaralé**; +- `sync-ui-keys` doplňuje pouze klíče, které **chybějí**, nikdy ne ty, které jsou **zastaralé**; - `check-ui-keys-coverage` počítá _přítomnost_ klíče, takže zastaralý překlad je započítán jako pokrytý; -- `check-translation-drift` sleduje zrcadlené kopie dokumentace `docs/i18n//**.md` — - soubory `src/i18n/messages/*.json` nikdy nečte. Od opětovné synchronizace 2026-09 je úloha `docs-sync-strict` blokující: upravte základní dokument → `npm run i18n:run -- --files=` (na úrovni sekcí, nenáročné). - -**Zohledňuje diff, neopírá se o baseline.** Porovnává `en.json` v merge base -s pracovním stromem; pro každý klíč, jehož anglická hodnota se změnila, je každý locale, -který stále obsahuje nezměněný překlad, zastaralý. Tím se záměrně **zmrazí již existující -dluh** — z diffu nelze zjistit, ze které starší anglické verze dlouhodobě existující překlad -vychází, takže kontrola posuzuje pouze to, čeho se aktuální změna dotýká. Alternativa -(baseline s hashem pro každý klíč) by vyžadovala přibližně 600KB generovaný soubor, -3× větší než největší existující baseline, který by se měnil při každém i18n PR. +- `check-translation-drift` sleduje dokumentační zrcadla `docs/i18n//**.md` — + soubory `src/i18n/messages/*.json` nikdy nečte. V úloze `docs-sync-strict` je tato kontrola blokující od + opětovné synchronizace 2026-09: upravte základní dokument → `npm run i18n:run -- --files=` (na úrovni sekce, nenáročné). + +**Zohledňuje rozdíly, není založeno na výchozím stavu.** Porovnává `en.json` v bodě sloučení +s pracovním stromem; pro každý klíč, jehož anglická hodnota se změnila, je každý jazyk, který +stále obsahuje nezměněný překlad, zastaralý. Tím se záměrně **zmrazí již existující dluh** — +z rozdílu nelze zjistit, ze které staré anglické verze dlouhodobě existující překlad pochází, +takže kontrola posuzuje pouze to, čeho se aktuální změna dotýká. Alternativa (výchozí stav +s hashem pro každý klíč) by vyžadovala přibližně 600KB generovaný soubor, 3× větší než +největší existující výchozí stav, který by se měnil při každém i18n PR. Kontrolu lze splnit dvěma způsoby: 1. aktualizovat dotčené překlady, nebo -2. nastavit je na `__MISSING__:` — runtime pak vrátí opravený anglický text - (`src/i18n/request.ts::deepMergeFallback`, #7258) a klíč zařadí do fronty k překladu. +2. nastavit je na `__MISSING__:` — běhové prostředí pak poskytne + opravený anglický text (`src/i18n/request.ts::deepMergeFallback`, #7258) a klíč zařadí + do fronty k překladu. -Pokud se změnil **význam** řetězce, dejte přednost **přejmenování klíče**: nový klíč nemůže +Pokud se změnil **význam** řetězce, upřednostněte **přejmenování klíče**: nový klíč nemůže zdědit zastaralý překlad. Tento postup byl použit v #8463. ```bash -npm run i18n:check-value-drift # striktní (spouští CI) +npm run i18n:check-value-drift # striktní režim (spouštěný v CI) npm run i18n:check-value-drift:warn # pouze hlášení BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Pokud nelze načíst základní katalog (mělký klon bez základního refu), skončí s kódem 0 -a hlášením `SKIP reason=base-unresolved`, obdobně jako `check-openapi-breaking`. +Ukončí se s kódem 0 a hlášením `SKIP reason=base-unresolved`, pokud nelze načíst základní +katalog (mělký klon bez základní reference), obdobně jako `check-openapi-breaking`. ### Úloha: `i18n` -Úplná matice validace i18n (jedna úloha pro každý locale). Celá úloha je informativní. +Úplná matice ověřování i18n (jedna úloha pro každý jazyk). Celá úloha je informativní. -| Skript | Ověřuje | Blokující | -| ------------------------------- | --------------------------------- | ------------------------------------------------------------ | -| `validate_translation.py quick` | Úplnost překladů pro každý locale | **Informativní** (`continue-on-error: true` pro celou úlohu) | +| Skript | Ověřuje | Blokující | +| ------------------------------- | -------------------------------- | ------------------------------------------------------------ | +| `validate_translation.py quick` | Úplnost překladu pro každý jazyk | **Informativní** (`continue-on-error: true` pro celou úlohu) | ### Úloha: `pr-test-policy` Spouští se pouze pro pull requesty. -| Skript | Ověřuje | Blokující | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | PR měnící produkční kód v `src/`, `open-sse/`, `electron/` nebo `bin/` musí zahrnovat nové či aktualizované testy (Tvrdé pravidlo č. 8) | Ano | -| `check:test-masking` | Změněné testovací soubory nesnižují čistý počet asercí ani nepřidávají tautologie `assert.ok(true)` | Ano | -| `check:pr-evidence` | Tělo PR uvádí důkazy z testů/VPS pro danou změnu (automatizuje Tvrdé pravidlo č. 18 pomocí vyhledávání v textu PR — křehké, viz Backlog) | Ano | +| Skript | Ověřuje | Blokující | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `check:pr-test-policy` | PR měnící produkční kód v `src/`, `open-sse/`, `electron/` nebo `bin/` musí obsahovat nové či aktualizované testy (Pevné pravidlo č. 8) | Ano | +| `check:test-masking` | Změněné testovací soubory nesnižují čistý počet asercí ani nepřidávají tautologie `assert.ok(true)` | Ano | +| `check:pr-evidence` | Tělo PR uvádí doklady o testování/VPS dané změny (automatizuje Pevné pravidlo č. 18 prohledáváním textu PR — křehké, viz Backlog) | Ano | ### Úloha: `test-vitest` Spouští se po `build`. Při selhání blokuje sloučení. -| Sada | Ověřuje | Blokující | -| ---------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Server MCP (110 nástrojů), autoCombo, cache — testovací nástroj vitest | Ano | -| `test:vitest:ui` | Testy komponent uživatelského rozhraní — testovací nástroj vitest | **Blokující** — již existující selhání jsou explicitně vyloučena v `vitest.config.ts`; nová selhání způsobí selhání úlohy | +| Sada | Ověřuje | Blokující | +| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Server MCP (110 nástrojů), autoCombo, mezipaměť — vitest | Ano | +| `test:vitest:ui` | Testy komponent uživatelského rozhraní — vitest | **Blokující** — již existující selhání jsou v `vitest.config.ts` explicitně vyloučena; nová selhání způsobí pád úlohy | -### Noční workflow (plánované, informativní) +### Noční pracovní postupy (plánované, informativní) Spouštějí se podle plánu cron (a pomocí `workflow_dispatch`), nikdy pro PR. Všechny jsou informativní. -| Workflow | Ověřuje | Blokující | +| Pracovní postup | Ověřuje | Blokující | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | | `nightly-property` | Testy vlastností fast-check s náhodným seedem a vysokým počtem běhů | **Informativní** | -| `nightly-resilience` | Kontrolu růstu haldy, vkládání chaotických poruch, zátěžové/dlouhodobé testy k6 | **Informativní** | -| `nightly-llm-security` | Ochranu proti prompt injection pomocí promptfoo (režim blokování) + sondy garak (bez tajného klíče poskytovatele se přeskočí) | **Informativní** | -| `nightly-schemathesis` | Fuzzing kontraktu OpenAPI (schemathesis) proti živé instanci OmniRoute pomocí `docs/openapi.yaml` — odhaluje porušení specifikace / neošetřené chyby 500 (Fáze 8 B.4) | **Informativní** | +| `nightly-resilience` | Kontrolu růstu haldy, chaos fault-injection a zátěžové/dlouhodobé testy k6 | **Informativní** | +| `nightly-llm-security` | Ochranu proti injekcím promptfoo (režim blokování) a sondy garak (přeskočeno bez tajného klíče poskytovatele) | **Informativní** | +| `nightly-schemathesis` | Fuzzing kontraktu OpenAPI (schemathesis) proti běžící službě OmniRoute pomocí `docs/openapi.yaml` — odhaluje porušení specifikace / neošetřené chyby 500 (Fáze 8 B.4) | **Informativní** | | `nightly-mutation` | Skóre mutačního testování Stryker pro rychlou větev jednotkových testů — přeživší mutanty odhalují slabé aserce | **Informativní** | | `nightly-compat` | Matici kompatibility enginu Node napříč podporovanými rozsahy `engines.node` | **Informativní** | @@ -540,43 +544,44 @@ chybu připojení a nikdy nenaruší relace. --- -## Backlog racionalizace (revize ROI — fáze 9, vlna 3) +## Seznam racionalizačních úkolů (revize ROI — fáze 9, vlna 3) -Tento inventář byl 2026-06-17 porovnán s `ci.yml` (předchozí verze vynechávala +Tento inventář byl dne 2026-06-17 porovnán s `ci.yml` (předchozí verze vynechala `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Revize ROI sjednocené sady -identifikovala následující kandidáty na racionalizaci. **Sloučení jsou mechanické změny CI; -přepnutí či odstranění jsou politická rozhodnutí vyhrazená provozovateli.** Nic z níže -uvedeného dosud nebylo aplikováno. - -**Výše rovněž nezdokumentováno** (informativní, slabý signál): úloha `docs-lint` -(markdownlint + Vale, celá úloha s `continue-on-error`) a samostatné pracovní postupy skenerů -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. Hodnota `semgrepFindings: 0` se nachází v -`quality-baseline.json`, ale není v `ci.yml` napojena na blokující západku — metrika je -momentálně osiřelá. - -### Sloučení / deduplikace (mechanické, nižší riziko) - -Každý kandidát byl 2026-06-17 ověřen vůči aktuálnímu stavu kontrol (důvěřuj, ale prověřuj); -ukázalo se, že několik „zjevných“ sloučení skrývá dluh a **nelze** je provést jako přímé náhrady. - -- **`check:docs-sync` se spouští dvakrát** — samostatně v úloze `lint` a znovu uvnitř `check:docs-all` (`docs-sync-strict`) i v pre-commit hooku husky. ✅ **HOTOVO** — samostatné spuštění v úloze `lint` bylo odstraněno. -- **Skenování CVE** — ❌ **Sloučení NELZE provést přímo.** `audit:deps` tvrdě selže při jakékoli CVE vysoké nebo kritické závažnosti; `check:vuln-ratchet` (osv) selže pouze při _regresi_ oproti výchozímu stavu (aktuálně 1 MODERATE). Odlišná sémantika — odstraněním `audit:deps` by se ztratila absolutní kontrola vysoké/kritické závažnosti. Ponechat obojí. -- **Detekce cyklů** — ❌ **Sloučení NELZE provést přímo.** `check:circular-deps` (dpdm) hlásí **91 cyklů** (proto je pouze informativní); nelze jej změnit na blokující bez jejich předchozího vyřešení a má širší rozsah než úspěšná, pečlivě vybraná kontrola `check:cycles`. Ponechat `check:cycles` jako blokující; vyřešení 91 cyklů dpdm je samostatná položka backlogu. -- **Složitost** — ✅ **HOTOVO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jeden průchod ESLint, počítání podle ruleId, takže výchozí hodnoty cyklomatické složitosti + maximálního počtu řádků a kognitivní složitosti zůstávají nezávislé; jednotlivé kontroly `check:complexity` / `check:cognitive-complexity` zůstávají zachovány pro místní spuštění s `--update`. -- **Ochrana `/api` proti halucinacím** — ✅ **HOTOVO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jeden inventář FS pro `src/app/api`, openapi-routes + docs-symbols nadále hlásí výsledky nezávisle; jednotlivé kontroly zůstávají zachovány pro místní spuštění. -- **`check:node-runtime` se spouští v 11 úlohách** — ⚠️ **nízké ROI.** Každá používá samostatný runner a kontrola trvá <1 s; celková úspora činí ~10 s, ovšem za cenu ztráty levné ochrany pro každou úlohu. Nestojí to za komplikace. -- **`typecheck:noimplicit:core` v CI lint** — ✅ **odstraněno z úlohy lint** (bylo informativní s `continue-on-error`); blokující typovou plochu tvoří `typecheck:core` + `check:type-coverage`. Místní skript byl zachován. - -### Přepnout / rozhodnout (politika provozovatele) - -- `check:openapi-security-tiers` (informativní) — ❌ **Nelze jednoduše přepnout.** Končí s kódem 0, ale varuje, že několika trasám `traffic-inspector` pod `LOCAL_ONLY_API_PREFIXES` chybí anotace `x-loopback-only: true`. Její vynucení nejprve vyžaduje přidat tyto anotace do `openapi.yaml`. -- `typecheck:noimplicit:core` (informativní) — z velké části jej nahrazuje blokující západka `check:type-coverage`. Přepnout na západku, nebo odstranit redundantní druhý průchod `tsc`. -- `test:vitest:ui` (nyní **blokující**) — dříve existující selhání jsou explicitně vyloučena v `vitest.config.ts` pomocí sledovacích komentářů `// #8618`; nová selhání způsobí neúspěch úlohy. -- `check:secrets` (gitleaks, blokující západka zmrazená na 3 zdokumentovaných falešně pozitivních nálezech) — přidat tyto 3 nálezy na seznam povolených, aby se dosáhlo hodnoty 0, nebo změnit kontrolu na informativní. Překrývá se s nativním skenováním tajných údajů na GitHubu + `check:public-creds`. -- `check:pr-evidence` (blokující, prohledává prózu v těle PR) — vysoké riziko falešně pozitivních nálezů; odstranění oslabí vynucování pevného pravidla č. 18, takže jde o skutečné politické rozhodnutí. -- `semgrep` (samostatná informativní kontrola) — překrývá se s CodeQL u kategorií OWASP; napojit její výchozí hodnotu na západku, nebo ji odstranit. +identifikovala následující kandidáty na racionalizaci. **Sloučení jsou mechanické změny +CI; změny režimu či odstranění jsou politická rozhodnutí vyhrazená provozovateli.** Nic +z níže uvedeného zatím nebylo aplikováno. + +**Výše rovněž nezdokumentováno** (poradní, nízká vypovídací hodnota): úloha `docs-lint` +(markdownlint + Vale, celá úloha s `continue-on-error`) a samostatné workflow skenerů +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. Hodnota `semgrepFindings: 0` se nachází +v `quality-baseline.json`, ale není v `ci.yml` napojena na blokující západku — tato metrika +je aktuálně osiřelá. + +### Sloučení / odstranění duplicit (mechanické, nižší riziko) + +Každý kandidát byl dne 2026-06-17 ověřen vůči aktuálnímu stavu kontrol (důvěřuj, ale +prověřuj); ukázalo se, že několik „zjevných“ sloučení skrývá dluh a **nelze** je čistě +nahradit. + +- **`check:docs-sync` se spouští dvakrát** — samostatně v úloze `lint` a znovu v rámci `check:docs-all` (`docs-sync-strict`) a husky pre-commit hooku. ✅ **HOTOVO** — samostatné spuštění v `lint` bylo odstraněno. +- **Skenování CVE** — ❌ **NEJDE o čisté sloučení.** `audit:deps` skončí tvrdou chybou při jakékoli CVE se závažností high/critical; `check:vuln-ratchet` (osv) selže pouze při _regresi_ oproti výchozímu stavu (aktuálně 1 MODERATE). Odlišná sémantika — odstraněním `audit:deps` by se ztratila absolutní kontrola závažností high/critical. Ponechat obě. +- **Detekce cyklů** — ✅ **HOTOVO** (#15159 G-01/G-02). Původní text zde označoval `check:cycles` za „zelenou, pečlivě vybranou“ kontrolu a její blokující režim zdůvodňoval tím, že `check:circular-deps` (dpdm) hlásil 91 cyklů. Tento zelený stav byl **falešně pozitivní**: `check:cycles` kontroloval 5 podadresářů (450 souborů), vyhledával pouze statické `import|export … from` a zahazoval všechny specifikátory `@/` a `@omniroute/open-sse/`, takže nemohl odhalit cykly dynamických importů a aliasů, které v repozitáři převažovaly. Opraveno: kontrola nyní prochází `src` + `open-sse` (5023 souborů), shromažďuje specifikátory z AST TypeScriptu (takže `import("…")` se započítává, zatímco `typeof import("…")` na pozici typu nikoli) a překládá `paths` z tsconfigu. Nachází **14** cyklů, nikoli 0. Protože 14 již existujících cyklů nelze opravit v PR zavádějícím kontrolu, je nyní `check:cycles` **západkou** (`--ratchet`, strop `metrics.cycles.value = 14` v `quality-baseline.json`, `direction: down`) — blokuje každou _regresi_ a počet může pouze klesat. CI spouští `npm run check:cycles:ratchet`. Postupné snižování probíhá společně s **A-01**. `check:circular-deps` (dpdm) zůstává poradní jako širší nezávislá kontrola. +- **Složitost** — ✅ **HOTOVO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jeden průchod ESLintu, počítání podle ruleId, takže výchozí hodnoty cyklomatické složitosti + maximálního počtu řádků a kognitivní složitosti zůstávají nezávislé; jednotlivé kontroly `check:complexity` / `check:cognitive-complexity` zůstávají zachovány pro místní použití s `--update`. +- **Ochrana `/api` proti halucinacím** — ✅ **HOTOVO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jeden inventář souborového systému pro `src/app/api`; openapi-routes + docs-symbols nadále hlásí výsledky nezávisle; jednotlivé kontroly zůstávají zachovány pro místní spuštění. +- **`check:node-runtime` se spouští v 11 úlohách** — ⚠️ **nízké ROI.** Každá běží na samostatném runneru a kontrola trvá <1 s; celková úspora činí přibližně 10 s za cenu ztráty levné ochrany pro každou úlohu. Nestojí to za související změny. +- **`typecheck:noimplicit:core` v lintování CI** — ✅ **odstraněno z úlohy lint** (dříve poradní s `continue-on-error`); blokující typový povrch tvoří `typecheck:core` + `check:type-coverage`. Místní skript byl zachován. + +### Změna režimu / rozhodnutí (politika provozovatele) + +- `check:openapi-security-tiers` (poradní) — ❌ **NELZE čistě přepnout.** Končí s kódem 0, ale upozorňuje, že několika trasám `traffic-inspector` pod `LOCAL_ONLY_API_PREFIXES` chybí anotace `x-loopback-only: true`. Jeho vynucení nejprve vyžaduje přidání těchto anotací do `openapi.yaml`. +- `typecheck:noimplicit:core` (poradní) — z velké části nahrazeno blokující západkou `check:type-coverage`. Přepnout na západku, nebo odstranit redundantní druhý průchod `tsc`. +- `test:vitest:ui` (nyní **blokující**) — již existující selhání jsou explicitně vyloučena ve `vitest.config.ts` pomocí sledovacích komentářů `// #8618`; nová selhání způsobí neúspěch úlohy. +- `check:secrets` (gitleaks, blokující západka zmrazená na 3 zdokumentovaných falešně pozitivních nálezech) — přidat tyto 3 případy na seznam povolených a dosáhnout hodnoty 0, nebo kontrolu degradovat na poradní. Překrývá se s nativním skenováním tajných údajů GitHubu + `check:public-creds`. +- `check:pr-evidence` (blokující, vyhledává vzory v textu těla PR) — vysoké riziko falešně pozitivních výsledků; odstranění by oslabilo vynucování závazného pravidla č. 18, takže jde o skutečné politické rozhodnutí. +- `semgrep` (poradní, samostatný) — u rodin OWASP se překrývá s CodeQL; napojit jeho výchozí hodnotu na západku, nebo jej odstranit. --- diff --git a/docs/i18n/cs/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/cs/docs/compression/COMPRESSION_GUIDE.md index 3079de7f7f2c..9c2ac2f9f787 100644 --- a/docs/i18n/cs/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/cs/docs/compression/COMPRESSION_GUIDE.md @@ -481,7 +481,7 @@ jazyk (viz výběr jazyka níže) překlad, je místo angličtiny vložena lokal U těla s `messages` kontroluje obcházení obsahu (`shouldBypassCavemanOutputMode()` v `open-sse/services/compression/outputMode.ts`) poslední tři zprávy a přeskočí styly pro celé kolo, pokud odpovídají jeho klíčovým slovům pro bezpečnost, nevratnou akci, -objasnění nebo citlivost na pořadí. Obcházení se spustí bez ohledu na to, jak je nastaven přepínač **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) na ovládacím panelu. +objasnění nebo citlivost na pořadí. Obcházení se spustí, dokud je přepínač **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) na ovládacím panelu zapnutý, což je výchozí nastavení; při vypnutém přepínači se vybrané styly použijí i v těchto kolech. Když obcházení povolí průchod, `placeSystemInstruction()` (stejný soubor), která nikdy nevytvoří novou `messages[0]`, umístí blok do prvního z následujících, které najde: diff --git a/docs/i18n/cs/docs/reference/ENVIRONMENT.md b/docs/i18n/cs/docs/reference/ENVIRONMENT.md index 6581102d77da..96a6706b7e52 100644 --- a/docs/i18n/cs/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/cs/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Povinné tajné údaje -Tyto údaje **musí** být nastaveny před prvním spuštěním. Bez nich se aplikace buď odmítne spustit, nebo bude používat nezabezpečené výchozí hodnoty. - -| Proměnná | Povinná | Výchozí hodnota | Zdrojový soubor | Popis | -| ---------------------------- | -------------------- | ----------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Ano** | _(žádná)_ | `src/lib/auth` | Podepisuje a ověřuje všechny soubory cookie relace ovládacího panelu (JWT). Vygenerujte pomocí `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ano** | _(žádná)_ | `src/lib/db/apiKeys.ts` | Šifrovací klíč AES pro hodnoty API klíčů uložené v databázi SQLite. Vygenerujte pomocí `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ano** | `CHANGEME` | Inicializační skript | Nastavuje počáteční heslo správce ovládacího panelu (odpovídá výchozí hodnotě v `.env.example` — záměrně je zjevně nezabezpečené, aby si vynutilo změnu). **Změňte je před prvním použitím.** Po přihlášení je změňte prostřednictvím Ovládací panel → Nastavení → Zabezpečení. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ano** (v produkci) | _(nenastaveno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sdílený tajný údaj pro interní most WebSocket služby Codex Responses. Ověřuje požadavky mostu mezi přenosem WS v Electronu/prohlížeči a OmniRoute. ⚠️ **V produkci je POVINNÝ — pokud není nastaven, všechny požadavky na most WS jsou odmítnuty.** Vygenerujte pomocí `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicitní identifikátor pro zneplatnění mezipaměti service workeru pro prostředí PWA (#11779); první v řetězci vyhodnocování. | -| `SOURCE_VERSION` | Ne | _(nenastaveno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Druhý v řetězci — nastavují jej sestavovací systémy PaaS (např. ve stylu Heroku) na nasazený commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(odvozeno)_ | `src/shared/components/PwaRegister.tsx` | Veřejná hodnota z doby sestavení, kterou klient používá k registraci `/sw.js?v=…`; odvozuje se z předchozích dvou hodnot a následně z git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automaticky) | _(automaticky při každém spuštění)_ | `src/server/authz/policies/management.ts` | Tajný údaj specifický pro proces, který dokládá, že důvěryhodná značka IP adresy protějšku pochází z vlastního HTTP serveru OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware pro autorizaci důvěřuje lokálnosti požadavku (omezení tras LOCAL_ONLY na zpětnou smyčku/LAN) pouze tehdy, když značka obsahuje tento token. Při každém spuštění se automaticky vygeneruje — ponechte jej nenastavený; nastavte jej napevno pouze u víceprocesových konfigurací, které musí značku sdílet. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nenastaveno)_ | `scripts/dev/peer-stamp.mjs` | Čárkami oddělené IP adresy nebo rozsahy CIDR reverzních proxy serverů, jejichž hlavičkám `X-Forwarded-For` / `X-Real-IP` může seznam povolených/zakázaných IP adres důvěřovat, kromě adres zpětné smyčky, privátních sítí a hraničních serverů Cloudflare, které jsou vždy důvěryhodné. Je potřeba pouze pro proxy server na jakékoli jiné veřejné adrese; jinak filtr posuzuje vlastní adresu daného proxy serveru. | +Nenastavené tajné údaje označené jako **Ano** se při prvním spuštění automaticky vygenerují (`JWT_SECRET`, `API_KEY_SECRET`) a uloží se do souboru `server.env` v adresáři dat — nastavte je explicitně, pokud je tento adresář dočasný (ephemeral). Výjimkou je `OMNIROUTE_WS_BRIDGE_SECRET`: ten se do `server.env` neukládá — vstupní body serveru (entrypoints) vytvářejí dočasnou hodnotu platnou pouze pro dané spuštění, kterou může použít jen most uvnitř samotného procesu, takže ho externí klienti mostu musí nastavit explicitně. + +| Proměnná | Povinná | Výchozí hodnota | Zdrojový soubor | Popis | +| ---------------------------- | -------------------- | ----------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ano** | _(žádná)_ | `src/lib/auth` | Podepisuje a ověřuje všechny soubory cookie relace ovládacího panelu (JWT). Vygenerujte pomocí `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ano** | _(žádná)_ | `src/lib/db/apiKeys.ts` | Šifrovací klíč AES pro hodnoty API klíčů uložené v databázi SQLite. Vygenerujte pomocí `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ne | _(nenastaveno)_ | `src/lib/auth/managementPassword.ts` | Nastavuje heslo ovládacího panelu při spuštění, dokud žádné heslo není uloženo; po uložení hesla je ignorováno. Instalace přes npm a ze zdrojového kódu kopírují `.env.example` do `.env`, takže začínají s jeho veřejně známou zástupnou hodnotou `CHANGEME`: nahraďte ji před prvním spuštěním, nebo nastavte `INITIAL_PASSWORD=` (prázdné) v tomto souboru `.env`, aby se heslo vytvořilo v průvodci prvním nastavením při první návštěvě — stejně jako u nenastavené hodnoty (výchozí v obrazu Docker). Uložené heslo měňte v Ovládací panel → Nastavení → Zabezpečení, nebo spusťte `omniroute reset-password` (`node bin/reset-password.mjs` ve zdrojovém checkoutu) s proměnnou `DATA_DIR` nastavenou na datový adresář serveru. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ano** (v produkci) | _(nenastaveno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sdílený tajný údaj pro interní most WebSocket služby Codex Responses. Ověřuje požadavky mostu mezi přenosem WS v Electronu/prohlížeči a OmniRoute. ⚠️ **V produkci je POVINNÝ — pokud není nastaven, všechny požadavky na most WS jsou odmítnuty.** Vygenerujte pomocí `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicitní identifikátor pro zneplatnění mezipaměti service workeru pro prostředí PWA (#11779); první v řetězci vyhodnocování. | +| `SOURCE_VERSION` | Ne | _(nenastaveno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Druhý v řetězci — nastavují jej sestavovací systémy PaaS (např. ve stylu Heroku) na nasazený commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(odvozeno)_ | `src/shared/components/PwaRegister.tsx` | Veřejná hodnota z doby sestavení, kterou klient používá k registraci `/sw.js?v=…`; odvozuje se z předchozích dvou hodnot a následně z git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automaticky) | _(automaticky při každém spuštění)_ | `src/server/authz/policies/management.ts` | Tajný údaj specifický pro proces, který dokládá, že důvěryhodná značka IP adresy protějšku pochází z vlastního HTTP serveru OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware pro autorizaci důvěřuje lokálnosti požadavku (omezení tras LOCAL_ONLY na zpětnou smyčku/LAN) pouze tehdy, když značka obsahuje tento token. Při každém spuštění se automaticky vygeneruje — ponechte jej nenastavený; nastavte jej napevno pouze u víceprocesových konfigurací, které musí značku sdílet. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nenastaveno)_ | `scripts/dev/peer-stamp.mjs` | Čárkami oddělené IP adresy nebo rozsahy CIDR reverzních proxy serverů, jejichž hlavičkám `X-Forwarded-For` / `X-Real-IP` může seznam povolených/zakázaných IP adres důvěřovat, kromě adres zpětné smyčky, privátních sítí a hraničních serverů Cloudflare, které jsou vždy důvěryhodné. Je potřeba pouze pro proxy server na jakékoli jiné veřejné adrese; jinak filtr posuzuje vlastní adresu daného proxy serveru. | ### Příkazy pro vygenerování @@ -215,38 +215,38 @@ OmniRoute používá pro veškerou perzistenci **SQLite** (prostřednictvím `be ## 4. Zabezpečení a ověřování -| Proměnná | Výchozí hodnota | Zdrojový soubor | Popis | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sůl kombinovaná s hardwarovými identifikátory pro vytváření otisku zařízení. Pro izolaci ji změňte pro každé nasazení. | -| `OMNIROUTE_CLI_SALT` | _(nenastaveno = náhodná sůl pro každou instalaci, uložená v `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sůl HMAC pro odvození místního ověřovacího tokenu CLI. Nastavení této hodnoty změní všechny tokeny CLI v zařízení a má vždy přednost před uloženou solí. Viz `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Nastavuje příznak `Secure` u relačních souborů cookie. Při provozu za HTTPS **musí být nastaveno na `true`**. | -| `REQUIRE_API_KEY` | `false` | Middleware API | Pokud je nastaveno na `true`, musí všechny proxy požadavky `/v1/*` obsahovat platný klíč API. **Tento příznak neomezuje přístup k `GET /v1/models`**, který se místo toho řídí nastavením přihlášení k řídicímu panelu (`requireAuthForModels`) — odpověď `401` z `/v1/models` proto NEZNAMENÁ, že je inference chráněna. Viz `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Umožňuje zobrazit úplné hodnoty klíčů API v uživatelském rozhraní řídicího panelu. Lze konfigurovat pomocí příznaků funkcí řídicího panelu; ve sdílených instancích představuje bezpečnostní riziko. | -| `NO_LOG_API_KEY_IDS` | _(prázdné)_ | `src/lib/compliance/index.ts` | Čárkami oddělené identifikátory klíčů API, pro které se neprovádí protokolování požadavků (soulad s GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenastaveno = bez omezení)_ | `src/shared/utils/apiKeyPolicy.ts` | Záložní denní limit požadavků použitý na klíče API, jejichž sloupec `rate_limits` má hodnotu null. Nenastavená nebo prázdná hodnota: žádný implicitní limit (#2289, #11017). Hodnota `0` má stejný význam (bez omezení). Kladné celé číslo N povolí N/den, 5N/týden a 20N/měsíc. Neplatné neprázdné hodnoty použijí původní limity 1000/den, 5000/týden a 20000/měsíc. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximální povolená velikost těla požadavku. Datové části překračující tento limit jsou odmítnuty. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Skutečná těla požadavků o velikosti rovné této prahové hodnotě nebo vyšší před parsováním JSON atomicky získají v rámci procesu oprávnění pro náročné zpracování (cesta BYTE, včetně `POST /v1/responses`). Platí stejná možnost pokračovat při dostatečné rezervě jako u strukturálně náročných požadavků [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437); stále je omezena hodnotou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), aby se problém [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) nevrátil. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Pevný limit pro chatovací trasy vynucovaný vůči bajtům načteným během omezeného příjmu, včetně požadavků s chybějící, neplatnou nebo nepravdivou hlavičkou `Content-Length`; překročení obdrží `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nenastaveno — bez limitu počtu požadavků)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** tento starší limit POČTU požadavků se nyní uplatní pouze tehdy, když je explicitně nastaven. Pokud zůstane nenastavený (výchozí stav), přijímání náročných chatovacích požadavků je místo toho řízeno proměnnou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaticky odvozeným BAJTOVÝM rozpočtem dimenzovaným podle skutečného paměťového limitu procesu v **jednom procesu** (jedna halda V8). Dva překrývající se požadavky `/v1/responses` o přibližně 750 tisících tokenech způsobí pád hald o velikosti ~12 Gi (#7849) — jde o varování týkající se **paměťového rozpočtu**, nikoli o pevné produktové maximum 2. Proces v dobrém stavu (halda pod poměrem pro odmítání) SMÍ přijmout více souběžných dlouhých požadavků `/v1/responses` prostřednictvím `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Desítky dlouhých klientů SSE (40–50) jsou otázkou haldy + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nikoli „maximálně 2“. Slepé zvýšení této hodnoty za účelem „využití hostitele“ znovu zavádí problém #7849. Násobte **haldy** pomocí **N nezávislých `DATA_DIR`** (#11024); nikdy nepoužívejte `replicas>1` nad jedním souborem SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automaticky odvozeno)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** přepsání automaticky odvozeného bajtového rozpočtu pro příjem (25 % z přísnějšího paměťového limitu V8/cgroup děleno osminásobným přechodným navýšením). Odvozené i explicitní hodnoty jsou omezeny na 8 MiB–2 GiB. Tělo větší než efektivní rozpočet okamžitě selže s `413 body_exceeds_budget`; soupeření mezi jednotlivě obsloužitelnými těly nadále vede k opakovatelnému `503`. 40–50 souběžných dlouhých klientů SSE je otázkou tohoto rozpočtu + haldy, nikoli pevného „maxima 2“. Před laděním si na `/api/monitoring/health` přečtěte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Poměr tlaku na haldu (`heapUsed / heap_size_limit`) pro odmítání náročných požadavků podle BAJTŮ a STRUKTURY (#10183, #10268, #10437). Souběžný náročný požadavek překračující `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` je odmítnut opakovatelným `503` pouze tehdy, když je halda ZÁROVEŇ na tomto poměru nebo nad ním; při zdravé haldě je přijat prostřednictvím rezervy pro zdravý stav. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (výchozí `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Omezená dodatečná kapacita rychlé cesty pro zdravou haldu (#10437) pro STRUKTURU i BAJTY (`admitChatRequest`, včetně těl ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez tohoto omezení by každý požadavek při vytížení, ale se zdravou haldou, obcházel řízení přijímání bez jakéhokoli stropu. Jakmile je prostřednictvím cesty pro zdravou haldu aktivní tento počet souběžných pronájmů, další požadavky při vytížení přejdou na STEJNOU cestu omezeného čekání/odmítnutí, která se používá při skutečném tlaku na haldu. `0` toto obcházení zcela zakáže. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Počet zpráv, při kterém je chatovací požadavek klasifikován jako náročný, i když je jeho tělo pod bajtovým prahem. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Počet nástrojů, při kterém je chatovací požadavek klasifikován jako náročný, i když je jeho tělo pod bajtovým prahem. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativní odhad počtu tokenů podle velikosti řetězců, při kterém je požadavek klasifikován jako náročný; jde o aproximaci nákladů pro řízení přijímání, nikoli o tokenizaci poskytovatele pro účely účtování. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (zakázáno) | `src/shared/middleware/chatBodyAdmission.ts` | Volitelný limit historie chatu, který je nutné explicitně zapnout. Ve výchozím nastavení je zakázán: počet zpráv je zásadou konkrétního nasazení, nikoli univerzální vlastností požadavku, a omezení na tomto místě odmítá konverzace konečným stavem `413` dříve, než je kompresní zpracování může učinit obsloužitelnými. Růst haldy je omezen prostřednictvím `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` a odmítání při tlaku na haldu. V nasazeních s omezenou pamětí, která potřebují pevný strop, nastavte kladnou hodnotu; překročení pak obdrží strukturovanou odpověď `413` vyžadující kompresi. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Pevný limit pro neproudovou odpověď nadřazené služby, která je celá ukládána do vyrovnávací paměti. Po jeho překročení je čtečka odpovědi nadřazené služby zrušena a požadavek rychle selže, místo aby neomezený řetězec rostl až do vyčerpání haldy. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximální počet bajtů přenesených z hlaviček odpovědi upstreamu. Při překročení limitu jsou hlavičky s nižší prioritou (např. vlastní `x-codex-*`, `x-oai-request-id`) vynechány, aby byly dodrženy běžné limity hlaviček reverzních proxy serverů. Nastavením vyšší hodnoty lze přenášet více metadat upstreamu za cenu větší velikosti hlaviček odpovědi. | -| `CORS_ORIGIN` | _(nenastaveno)_ | `src/server/cors/origins.ts` | Zastaralý seznam povolených CORS zdrojů s jediným zdrojem. Pro nová nasazení upřednostněte `CORS_ALLOWED_ORIGINS`. CORS slouží pouze pro klienty API v prohlížeči komunikující napříč zdroji; ověřené zápisy v řídicím panelu místo toho používají požadavky ze stejného zdroje spolu s ochranou CSRF vázanou na relaci. | -| `CORS_ALLOWED_ORIGINS` | _(nenastaveno)_ | `src/server/cors/origins.ts` | Čárkami oddělený seznam povolených CORS zdrojů. Zástupný znak se neodesílá, pokud není explicitně nakonfigurováno `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Nouzová možnost určená pouze pro vývoj, která vrací libovolnou hodnotu `Origin` prohlížeče. Nezapínejte ji ve sdílených ani produkčních nasazeních. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokuje volání poskytovatelů cílená na privátní, loopback nebo link-local rozsahy IP adres. Vypínejte pouze v izolovaných testovacích prostředích. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Povoluje URL poskytovatelů směřující do privátních či místních sítí (localhost, 192.168.x.x, 10.x.x.x atd.). **VYŽADOVÁNO pro poskytovatele s vlastním hostingem** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Pokud je nastaveno na `false`, řídicí panel odmítne ověření místních URL. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Povoluje přidávání a ověřování poskytovatelů na místních či privátních adresách (127.0.0.1, localhost, LAN, privátní rozsahy) — omezeno na cestu ověřování poskytovatele. **Výchozí hodnota je `true`** (upřednostnění místních prostředků); nastavením na `false` vynutíte striktní blokování všeho kromě veřejných adres. Koncové body cloudových metadat (169.254.169.254, metadata.google.internal) zůstávají blokovány bez ohledu na toto nastavení. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Umožňuje trasám `/v1/audio/*` (přepisy, syntéza řeči, překlady) používat uzel poskytovatele kompatibilní s OpenAI, který je hostován mimo localhost. Ve výchozím nastavení je vypnuto — směrování zvuku na vzdáleného hostitele mění identitu výstupního připojení a musí jít o explicitní rozhodnutí provozovatele. Loopback a privátní uzly (localhost, 127.0.0.1, 172.16-31.x) jsou vždy povoleny a toto nastavení je neovlivňuje. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Umožňuje požadavku `POST /v1/rerank` (a kroku přehodnocení pořadí přes loopback v paměťovém enginu) používat uzel poskytovatele kompatibilní s OpenAI, který je hostován mimo localhost — zařízení v síti LAN nebo partnerský uzel Tailscale provozující TEI, Infinity, vLLM atd. Ve výchozím nastavení je vypnuto — směrování na vzdáleného hostitele mění identitu výstupního připojení a musí jít o explicitní rozhodnutí provozovatele. Loopback uzly (localhost, 127.0.0.1, 172.16-31.x) jsou vždy povoleny a toto nastavení je neovlivňuje. Vzdálené uzly musí rovněž splňovat zásady pro odchozí URL poskytovatele (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); na hostitele cloudových metadat se požadavky nikdy nesměrují. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Pokud je povoleno OIDC, zakáže přihlašování pomocí hesla, takže se uživatelé mohou ověřovat pouze prostřednictvím jednotného přihlašování OIDC. Je podporován také nezkrášlený alias `OIDC_DISABLE_PASSWORD_LOGIN`; příznak funkce řídicího panelu se stejným klíčem má přednost. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Nezkrášlený alias proměnné `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Proměnná | Výchozí hodnota | Zdrojový soubor | Popis | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sůl kombinovaná s hardwarovými identifikátory pro vytváření otisku zařízení. Pro izolaci ji změňte pro každé nasazení. | +| `OMNIROUTE_CLI_SALT` | _(nenastaveno = náhodná sůl pro každou instalaci, uložená v `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sůl HMAC pro odvození místního ověřovacího tokenu CLI. Nastavení této hodnoty změní všechny tokeny CLI v zařízení a má vždy přednost před uloženou solí. Viz `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Nastavuje příznak `Secure` u relačních souborů cookie. Při provozu za HTTPS **musí být nastaveno na `true`**. | +| `REQUIRE_API_KEY` | `false` | Middleware API | Pokud je nastaveno na `true`, musí všechny proxy požadavky `/v1/*` obsahovat platný klíč API. **Tento příznak neomezuje přístup k `GET /v1/models`**, který se místo toho řídí nastavením přihlášení k řídicímu panelu (`requireAuthForModels`) — odpověď `401` z `/v1/models` proto NEZNAMENÁ, že je inference chráněna. Viz `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Umožňuje zobrazit úplné hodnoty klíčů API v uživatelském rozhraní řídicího panelu. Lze konfigurovat pomocí příznaků funkcí řídicího panelu; ve sdílených instancích představuje bezpečnostní riziko. | +| `NO_LOG_API_KEY_IDS` | _(prázdné)_ | `src/lib/compliance/index.ts` | Čárkami oddělené identifikátory klíčů API, pro které se neprovádí protokolování požadavků (soulad s GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenastaveno = bez omezení)_ | `src/shared/utils/apiKeyPolicy.ts` | Záložní denní limit požadavků použitý na klíče API, jejichž sloupec `rate_limits` má hodnotu null. Nenastavená nebo prázdná hodnota: žádný implicitní limit (#2289, #11017). Hodnota `0` má stejný význam (bez omezení). Kladné celé číslo N povolí N/den, 5N/týden a 20N/měsíc. Neplatné neprázdné hodnoty použijí původní limity 1000/den, 5000/týden a 20000/měsíc. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximální povolená velikost těla požadavku. Datové části překračující tento limit jsou odmítnuty. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Skutečná těla požadavků o velikosti rovné této prahové hodnotě nebo vyšší před parsováním JSON atomicky získají v rámci procesu oprávnění pro náročné zpracování (cesta BYTE, včetně `POST /v1/responses`). Platí stejná možnost pokračovat při dostatečné rezervě jako u strukturálně náročných požadavků [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437); stále je omezena hodnotou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), aby se problém [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) nevrátil. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Pevný limit pro chatovací trasy vynucovaný vůči bajtům načteným během omezeného příjmu, včetně požadavků s chybějící, neplatnou nebo nepravdivou hlavičkou `Content-Length`; překročení obdrží `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nenastaveno — bez limitu počtu požadavků)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** tento starší limit POČTU požadavků se nyní uplatní pouze tehdy, když je explicitně nastaven. Pokud zůstane nenastavený (výchozí stav), přijímání náročných chatovacích požadavků je místo toho řízeno proměnnou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaticky odvozeným BAJTOVÝM rozpočtem dimenzovaným podle skutečného paměťového limitu procesu v **jednom procesu** (jedna halda V8). Dva překrývající se požadavky `/v1/responses` o přibližně 750 tisících tokenech způsobí pád hald o velikosti ~12 Gi (#7849) — jde o varování týkající se **paměťového rozpočtu**, nikoli o pevné produktové maximum 2. Proces v dobrém stavu (halda pod poměrem pro odmítání) SMÍ přijmout více souběžných dlouhých požadavků `/v1/responses` prostřednictvím `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Desítky dlouhých klientů SSE (40–50) jsou otázkou haldy + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nikoli „maximálně 2“. Slepé zvýšení této hodnoty za účelem „využití hostitele“ znovu zavádí problém #7849. Násobte **haldy** pomocí **N nezávislých `DATA_DIR`** (#11024); nikdy nepoužívejte `replicas>1` nad jedním souborem SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automaticky odvozeno)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** přepsání automaticky odvozeného bajtového rozpočtu pro příjem (25 % z přísnějšího paměťového limitu V8/cgroup děleno osminásobným přechodným navýšením). Odvozené i explicitní hodnoty jsou omezeny na 8 MiB–2 GiB. Tělo větší než efektivní rozpočet okamžitě selže s `413 body_exceeds_budget`; soupeření mezi jednotlivě obsloužitelnými těly nadále vede k opakovatelnému `503`. 40–50 souběžných dlouhých klientů SSE je otázkou tohoto rozpočtu + haldy, nikoli pevného „maxima 2“. Před laděním si na `/api/monitoring/health` přečtěte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Poměr tlaku na haldu (`heapUsed / heap_size_limit`) pro odmítání náročných požadavků podle BAJTŮ a STRUKTURY (#10183, #10268, #10437). Souběžný náročný požadavek překračující `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` je odmítnut opakovatelným `503` pouze tehdy, když je halda ZÁROVEŇ na tomto poměru nebo nad ním; při zdravé haldě je přijat prostřednictvím rezervy pro zdravý stav. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (výchozí `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Omezená dodatečná kapacita rychlé cesty pro zdravou haldu (#10437) pro STRUKTURU i BAJTY (`admitChatRequest`, včetně těl ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez tohoto omezení by každý požadavek při vytížení, ale se zdravou haldou, obcházel řízení přijímání bez jakéhokoli stropu. Jakmile je prostřednictvím cesty pro zdravou haldu aktivní tento počet souběžných pronájmů, další požadavky při vytížení přejdou na STEJNOU cestu omezeného čekání/odmítnutí, která se používá při skutečném tlaku na haldu. `0` toto obcházení zcela zakáže. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Počet zpráv, při kterém je chatovací požadavek klasifikován jako náročný, i když je jeho tělo pod bajtovým prahem. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Počet nástrojů, při kterém je chatovací požadavek klasifikován jako náročný, i když je jeho tělo pod bajtovým prahem. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativní odhad počtu tokenů podle velikosti řetězců, při kterém je požadavek klasifikován jako náročný; jde o aproximaci nákladů pro řízení přijímání, nikoli o tokenizaci poskytovatele pro účely účtování. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (zakázáno) | `src/shared/middleware/chatBodyAdmission.ts` | Volitelný limit historie chatu, který je nutné explicitně zapnout. Ve výchozím nastavení je zakázán: počet zpráv je zásadou konkrétního nasazení, nikoli univerzální vlastností požadavku, a omezení na tomto místě odmítá konverzace konečným stavem `413` dříve, než je kompresní zpracování může učinit obsloužitelnými. Růst haldy je omezen prostřednictvím `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` a odmítání při tlaku na haldu. V nasazeních s omezenou pamětí, která potřebují pevný strop, nastavte kladnou hodnotu; překročení pak obdrží strukturovanou odpověď `413` vyžadující kompresi. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Pevný limit pro neproudovou odpověď nadřazené služby, která je celá ukládána do vyrovnávací paměti. Po jeho překročení je čtečka odpovědi nadřazené služby zrušena a požadavek rychle selže, místo aby neomezený řetězec rostl až do vyčerpání haldy. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximální počet bajtů přenesených z hlaviček odpovědi upstreamu. Při překročení limitu jsou hlavičky s nižší prioritou (např. vlastní `x-codex-*`, `x-oai-request-id`) vynechány, aby byly dodrženy běžné limity hlaviček reverzních proxy serverů. Nastavením vyšší hodnoty lze přenášet více metadat upstreamu za cenu větší velikosti hlaviček odpovědi. | +| `CORS_ORIGIN` | _(nenastaveno)_ | `src/server/cors/origins.ts` | Zastaralý seznam povolených CORS zdrojů s jediným zdrojem. Pro nová nasazení upřednostněte `CORS_ALLOWED_ORIGINS`. CORS slouží pouze pro klienty API v prohlížeči komunikující napříč zdroji; ověřené zápisy v řídicím panelu místo toho používají požadavky ze stejného zdroje spolu s ochranou CSRF vázanou na relaci. | +| `CORS_ALLOWED_ORIGINS` | _(nenastaveno)_ | `src/server/cors/origins.ts` | Čárkami oddělený seznam povolených CORS zdrojů. Zástupný znak se neodesílá, pokud není explicitně nakonfigurováno `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Nouzová možnost určená pouze pro vývoj, která vrací libovolnou hodnotu `Origin` prohlížeče. Nezapínejte ji ve sdílených ani produkčních nasazeních. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Starší alias (legacy). Jeho přepínač v řídicím panelu (přepsání uložené v databázi) se čte před touto hodnotou prostředí; `false`, `0`, `no` nebo `off` v kterýchkoli z nich vypne kontroly hostitelů stejným způsobem jako `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, a to i tehdy, když je přepínač onoho příznaku v řídicím panelu uložen ve vypnutém stavu; ostatní hodnoty ponechávají strážce na dvou příznacích níže. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Vypíná kontroly hostitelů strážce odchozích URL (outbound URL guard), včetně blokování cloudových metadat (celý rozsah 169.254.0.0/16, metadata.google.internal), na cestách poskytovatele, které se řídí režimem strážce: ověřování URL, zjišťování modelů, základní URL uzlů poskytovatele (včetně vzdálených uzlů přehodnocení pořadí) a test záložního proxy, a také na dalších odchozích cestách souvisejících s poskytovatelem, jako je stahování vzdálených obrázků/médií. Povoluje rovněž privátní cíle webhooků, s výjimkou koncových bodů cloudových metadat a link-local, které zůstávají blokovány i při zapnutém příznaku. Požadavky na chat odesílané přes `BaseExecutor.execute()` si svou kontrolu cloudových metadat ponechávají. Místní poskytovatelé fungují s výchozími hodnotami: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (výchozí `true`) již povoluje místní a LAN URL na cestách ověřování, zjišťování a uzlů poskytovatele; test záložního proxy a privátní cíle webhooků se řídí pouze tímto příznakem a dokud je vypnutý, blokují místní/LAN hostitele. Přepisuje `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Hodnota uložená z přepínače v řídicím panelu má před touto proměnnou přednost. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Povoluje URL poskytovatelů na místních či privátních adresách (127.0.0.1, localhost, LAN, privátní rozsahy) pro ověřování poskytovatelů, zjišťování modelů, chat a další odchozí cesty související s poskytovatelem, které řídí režim strážce. **Výchozí hodnota je `true`** (upřednostnění místních prostředků): strážce pak blokuje koncové body cloudových metadat — celý rozsah 169.254.0.0/16 plus známé názvy hostitelů metadat (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Nastavte `false` pro režim pouze veřejných adres, který blokuje také privátní a loopback hostitele. Strážce kontroluje název hostitele nebo IP literal přesně tak, jak je zapsán, před jakýmkoli DNS dotazem; v režimu pouze veřejných adres navíc obálka bezpečného načítání (safe-fetch wrapper) překládá název hostitele a odmítá jakoukoli privátní odpověď. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` tyto kontroly vypíná na cestách uvedených v jeho řádku. Vestavěné místní poskytovatelé, jako jsou LM Studio, Ollama, vLLM, Llamafile a Triton, přeskočí všechny kontroly hostitelů při ověřování klíčů a chatu. Hodnota uložená z přepínače v řídicím panelu má před touto proměnnou přednost. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Umožňuje trasám `/v1/audio/*` (přepisy, syntéza řeči, překlady) používat uzel poskytovatele kompatibilní s OpenAI, který je hostován mimo localhost. Ve výchozím nastavení je vypnuto — směrování zvuku na vzdáleného hostitele mění identitu výstupního připojení a musí jít o explicitní rozhodnutí provozovatele. Loopback a privátní uzly (localhost, 127.0.0.1, 172.16-31.x) jsou vždy povoleny a toto nastavení je neovlivňuje. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Umožňuje požadavku `POST /v1/rerank` (a kroku přehodnocení pořadí přes loopback v paměťovém enginu) používat uzel poskytovatele kompatibilní s OpenAI, který je hostován mimo localhost — zařízení v síti LAN nebo partnerský uzel Tailscale provozující TEI, Infinity, vLLM atd. Ve výchozím nastavení je vypnuto — směrování na vzdáleného hostitele mění identitu výstupního připojení a musí jít o explicitní rozhodnutí provozovatele. Loopback uzly (localhost, 127.0.0.1, 172.16-31.x) jsou vždy povoleny a toto nastavení je neovlivňuje. Základní URL vzdáleného uzlu musí rovněž splňovat zásady pro odchozí URL poskytovatele (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Pokud je povoleno OIDC, zakáže přihlašování pomocí hesla, takže se uživatelé mohou ověřovat pouze prostřednictvím jednotného přihlašování OIDC. Je podporován také nezkrášlený alias `OIDC_DISABLE_PASSWORD_LOGIN`; příznak funkce řídicího panelu se stejným klíčem má přednost. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Nezkrášlený alias proměnné `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Kontrolní seznam zabezpečení diff --git a/docs/i18n/da/CONTRIBUTING.md b/docs/i18n/da/CONTRIBUTING.md index de21ccbd5271..2719f740594f 100644 --- a/docs/i18n/da/CONTRIBUTING.md +++ b/docs/i18n/da/CONTRIBUTING.md @@ -393,16 +393,16 @@ Skriv enhedstests i `tests/unit/`, som som minimum dækker: - [ ] Linting består (`npm run lint`) - [ ] Build lykkes (`npm run build`) - [ ] TypeScript-typer er tilføjet for nye offentlige funktioner og interfaces -- [ ] Ingen hardcodede hemmeligheder eller reserveværdier -- [ ] Offentlige upstream-legitimationsoplysninger indlejres via `resolvePublicCred()` (se [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), aldrig som litteraler -- [ ] Fejlsvar føres gennem `buildErrorBody()` / `sanitizeErrorMessage()` — ingen rå stack traces i svarenes bodies (se [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Ingen hardkodede hemmeligheder eller fallback-værdier +- [ ] Offentlige upstream-legitimationsoplysninger indlejres via `resolvePublicCred()` (se [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), aldrig som literals +- [ ] Fejlsvar sendes gennem `buildErrorBody()` / `sanitizeErrorMessage()` — ingen rå stack traces i svartekster (se [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Shell-kommandoer (`exec` / `spawn`) videregiver runtime-værdier via `env`, ikke via strenginterpolation - [ ] Alle input valideres med Zod-skemaer -- [ ] Changelog-**fragment** tilføjet under `changelog.d/{features|fixes|maintenance}/-.md` for brugerrettede ændringer (se [`changelog.d/README.md`](./changelog.d/README.md)) — rediger **ikke** `CHANGELOG.md` direkte; fragmenter samles ved udgivelsestidspunktet og skaber aldrig konflikter mellem pull requests -- [ ] Dokumentationen er opdateret (hvis relevant) -- [ ] Ingen nye CodeQL- / Secret-Scanning-advarsler er åbnet, eller hver enkelt er afvist med en teknisk begrundelse, der henviser til det relevante dokument under `docs/security/` +- [ ] Changelog-**fragment** tilføjet under `changelog.d/{features|fixes|maintenance}/-.md` for brugervendte ændringer (se [`changelog.d/README.md`](./changelog.d/README.md)) — rediger **ikke** `CHANGELOG.md` direkte; fragmenter samles ved udgivelsen og skaber aldrig konflikter mellem pull requests +- [ ] Dokumentation opdateret (hvis relevant) +- [ ] Ingen nye CodeQL- eller Secret-Scanning-advarsler oprettet, eller hver enkelt er afvist med en teknisk begrundelse, der henviser til det relevante dokument under `docs/security/` - [ ] Ruter, der starter underprocesser (`/api/mcp/`, `/api/cli-tools/runtime/`), er klassificeret som `isLocalOnlyPath()` i `src/server/authz/routeGuard.ts` — se [ufravigelig regel nr. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Ingen `Co-Authored-By`-trailere i commitbeskeder — commits må udelukkende fremstå under repository-ejerens Git-identitet (ufravigelig regel nr. 16) +- [ ] Ingen AI-/bot-`Co-authored-by`-trailere i commit-meddelelser (ufravigelig regel nr. 16) — menneskelige samarbejdspartnere, hvis arbejde genbruges, krediteres med standardtrailere i formatet `Co-authored-by: Name ` --- diff --git a/docs/i18n/da/docs/architecture/QUALITY_GATES.md b/docs/i18n/da/docs/architecture/QUALITY_GATES.md index 66d415c550b0..73554007f8d0 100644 --- a/docs/i18n/da/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/da/docs/architecture/QUALITY_GATES.md @@ -15,175 +15,171 @@ plan til replikering af det samme system. --- -## Gate-inventar og eksekveringsprofiler +## Oversigt over gates og eksekveringsprofiler ### Godkendelse af kandidater -CI- og Quality Gates-workflowene udsender hver især en stabil afgørelse: `Gate / CI` og -`Gate / Quality`. Deres versionsstyrede godkendelsespolitik angiver hvert upstream-job -som påkrævet eller vejledende. Et relevant påkrævet job skal gennemføres korrekt: -Manglende, annullerede, oversprungne, afventende og ukendte resultater kan ikke fastslå -PASS. En gyldig klassifikation som kun dokumentation eller kun katalog kan gøre et -kodeforløb irrelevant; en kladde-PR accepteres ikke som kandidat. En `hotfix`-etiket -fritager ikke for dokumentation. - -Begge workflows dækker PR'er og pushes til main-/release-grene, manuel dispatch og -merge-group-hændelser. Push, dispatch og merge-group kører den fulde udvælgelse. Forks -og merge groups bruger hostede runners til jobs, der ellers vælger self-hosted runners; -det skal før udrulning verificeres, at der er tilstrækkelig hostet kapacitet. - -Hver JSON-kvittering identificerer den udcheckede SHA, workflow-kørslen og forsøget. -CLI'en afviser uoverensstemmelser mellem checkout- og hændelses-SHA. Workflow-tests -binder medlemskab af politikken til afgørelsesjobbets `needs`-liste, så et nyt eller -fjernet forløb ikke ubemærket kan forsvinde. Kvitteringerne dækker deres eget workflow, -ikke publicering, udrulning eller de interne funktioner i en eksisterende vejledende -scanner. Aktivering af begge kontrolnavne i branch-regler er en separat administrativ -ændring; tilføjelse af disse jobs beskytter ikke i sig selv en branch. - -### Inventar over statisk scanning - -Det versionsstyrede npm-aliasinventar og medlemskab af statiske scanninger findes i +CI- og Quality Gates-workflowene udsender hver en stabil afgørelse: `Gate / CI` og +`Gate / Quality`. Deres versionerede godkendelsespolitik angiver hvert upstream-job +som påkrævet eller vejledende. Et relevant påkrævet job skal gennemføres: manglende, +annullerede, oversprungne, afventende og ukendte resultater kan ikke etablere PASS. En gyldig +klassificering som kun dokumentation eller kun katalog kan gøre et kodeforløb irrelevant; +en kladde-PR er ikke en accepteret kandidat. Et `hotfix`-label fritager ikke for dokumentationskrav. + +Begge workflows dækker PR'er og pushes til main/release-branches, manuel aktivering og +merge-group-hændelser. Push, aktivering og merge-group kører det fulde udvalg. Forks +og merge groups bruger hostede runners til jobs, der ellers vælger self-hosted +runners; tilstrækkelig hostet kapacitet skal verificeres før udrulning. + +Hver JSON-kvittering identificerer den checkoutede SHA, workflow-kørslen og forsøget. +CLI'en afviser uoverensstemmelse mellem checkout- og hændelses-SHA. Workflow-tests knytter politikkens medlemskab +til afgørelsesjobbets `needs`-liste, så et nyt eller fjernet forløb ikke kan forsvinde ubemærket. +Kvitteringerne dækker deres eget workflow, ikke publicering, udrulning eller de interne +detaljer i en eksisterende vejledende scanner. Aktivering af begge kontrolnavne i branch-regler er en +separat administrativ ændring; tilføjelse af disse jobs beskytter ikke i sig selv en branch. + +### Oversigt over statisk scanning + +Den versionerede oversigt over npm-aliasser og medlemskabet af statiske scanninger findes i `config/quality/gate-manifest.json`. Kør `npm run check:gate-manifest` for at validere scriptnavne og nøjagtige kommandoer mod `package.json`; tilføjelser, fjernelser og -kommandoafvigelser får både den lokale hook og ændringsklassificeringsjobbene i CI -til at fejle. Et alias er ikke et workflow-job, en matrixinstans eller en testcase: -Disse antal må ikke præsenteres, som om de var indbyrdes udskiftelige. +kommandoafvigelser får både den lokale hook og ændringsklassificeringsjobbene i CI til at fejle. +Et alias er ikke et workflow-job, en matrixinstans eller en testcase: disse antal må +ikke præsenteres som indbyrdes udskiftelige. Brug `npm run quality:scan -- --list` eller `npm run quality:scan:fast -- --list` -til at inspicere de valgte aliasser uden at eksekvere dem. Runneren kalder +til at gennemgå de valgte aliasser uden at køre dem. Runneren aktiverer npm-entrypointet, så dets runtime (herunder Bun, hvor det er konfigureret) bevares. -Manifestet registrerer aliasser uden for disse profiler som separat kaldte, og +Manifestet registrerer aliasser uden for disse profiler som aktiveret separat, og vedligeholdelseskommandoer er forbudt i skrivebeskyttede scanningsprofiler. Disse profiler dækker kun den statiske scanning. De certificerer ikke produkttests, dækning, pakning, eksterne kontroller eller en kandidats fulde release-godkendelse. -Workflow-godkendelse bruger de tilknyttede `config/quality/admission-policy.json` og +Workflow-godkendelse bruger den tilknyttede `config/quality/admission-policy.json` og `scripts/quality/admission-verdict.mjs`. Release-observer-profiler forbliver separate; -inspicér deres relevante kontroller og kvitteringer uafhængigt. Prosaoversigten -nedenfor er en reference, ikke et bevis på, at en gate faktisk blev kørt. +gennemgå deres relevante kontroller og kvitteringer separat. Oversigten i prosa +nedenfor er en reference, ikke dokumentation for, at en gate faktisk blev kørt. Scripts findes under `scripts/check/` (politik-gates) og `scripts/quality/` (ratchet-motor). CI's autoritative kilde er `.github/workflows/ci.yml`. ### Hurtigt forløb for release-PR'er (`quality.yml`) -`.github/workflows/quality.yml` supplerer CI for main-/release-PR'er, pushes til -beskyttede branches, dispatch og merge groups. PR'er bruger stifiltrerede hurtige -kontroller. Det permanent deaktiverede duplikerede build blev fjernet; de reelle -build-/pakke-/opstartskontroller forbliver i CI. - -| Job | Omfang | Blokerende | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | -| `Docs Gates (fast-path)` | Dokumentations-/kode-PR'er; API-dokumentationsreferencer og al dokumentation | Ja | -| `Fast Quality Gates` | Kode-PR'er; statiske kontroller, typekontrol, typekontrol af dashboard og berørte enhedstests | Ja | -| `Forgotten sibling tests` | Kode-PR'er; ændrede moduler spores til statiske forbrugere og mulige relaterede tests; barrel- og dynamic-import-stier rapporteres som vejledende diagnostik med refererede undtagelser på tilladelseslisten | **Vejledende** | -| `Vitest (fast-path)` | Kode-PR'er; hurtig vitest-suite | Ja | -| `Unit Tests fast-path` | Kode-PR'er; enhedstestsuite med 4 shards | Ja | -| `No new ESLint warnings` | Kode-PR'er; undertrykkelsesbevidst lint-kontrol | Ja, inklusive forks | -| `Merge integrity (changelog + generated skills)` | PR'er, der ikke er kladder; synkronisering af changelog og genererede skills | Ja, inklusive forks | - -#### Rapport om glemte relaterede tests - -`npm run check:forgotten-sibling-tests` genbruger import-resolveren bag kortet over -testpåvirkning. For hvert ændret produktionsmodul rapporterer den deterministiske -kæder af typen `changed module/symbol -> static consumer -> candidate sibling test`, -når den mulige test ikke findes i pull request-diffen. Markdown-oversigten og -JSON-resultatet gemmes som workflow-artefaktet `forgotten-sibling-tests` med henblik -på kalibrering før en eventuel udrulning som blokerende kontrol. - -Barrel-re-eksporter og dynamiske importer er kun diagnosticering af modulopløsning; de skaber aldrig et +`.github/workflows/quality.yml` supplerer CI på main/release-PR'er, pushes til beskyttede branches, +aktivering og merge groups. PR'er bruger stifiltrerede hurtige kontroller. Det permanent +deaktiverede duplikerede build blev fjernet; de reelle build-, pakke- og opstartskontroller forbliver i CI. + +| Job | Omfang | Blokerende | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `Docs Gates (fast-path)` | Dokumentations-/kode-PR'er; referencer til API-dokumentation og al dokumentation | Ja | +| `Fast Quality Gates` | Kode-PR'er; statiske kontroller, typekontrol, typekontrol af dashboard, berørte enhedstests | Ja | +| `Forgotten sibling tests` | Kode-PR'er; ændrede moduler spores til statiske forbrugere og mulige søskendetests; barrel- og dynamic-import-stier rapporteres som vejledende diagnosticering med refererede allowlist-undtagelser | **Vejledende** | +| `Vitest (fast-path)` | Kode-PR'er; hurtig vitest-suite | Ja | +| `Unit Tests fast-path` | Kode-PR'er; enhedstestsuite med 4 shards | Ja | +| `No new ESLint warnings` | Kode-PR'er; suppressions-bevidst lint-kontrol | Ja, inklusive forks | +| `Merge integrity (changelog + generated skills)` | PR'er, der ikke er kladder; changelog og synkronisering af genererede skills | Ja, inklusive forks | + +#### Rapport om glemte søskendetests + +`npm run check:forgotten-sibling-tests` genbruger import-resolveren bag kortet over testpåvirkning. +For hvert ændret produktionsmodul rapporterer den deterministiske +`changed module/symbol -> static consumer -> candidate sibling test`-kæder, når den mulige +test ikke findes i pull request-diffen. Markdown-oversigten og JSON-resultatet gemmes som +workflow-artefaktet `forgotten-sibling-tests` til kalibrering før en eventuel blokerende udrulning. + +Barrel-re-exports og dynamiske imports er kun diagnosticering af modulopløsning; de skaber aldrig et blokerende fund. Gennemgåede undtagelser findes i `config/quality/forgotten-sibling-allowlist.json`. Hver post skal angive forbrugeren og den mulige -test, give en specifik begrundelse og linke til et GitHub-issue eller en pull request. Fejlformaterede poster afvises -som standard. Undtagelser kan ikke undertrykke en slettet mulig test eller en diff, der tilføjer `.skip`/`.todo`; -svækkelse af assertions og anden maskering håndteres fortsat af den uafhængigt blokerende -`check:test-masking`-kontrol. +test, give en specifik begrundelse og linke til et GitHub-issue eller en pull request. Fejlformaterede poster får +kontrollen til at fejle lukket. Undtagelser kan ikke undertrykke en slettet mulig test eller en diff, der tilføjer `.skip`/`.todo`; +svækkelse af assertions og anden maskering hører fortsat under den uafhængigt blokerende +`check:test-masking`-gate. ### Job: `lint` -Kører ved hver PR til `main`. Blokerer sammenfletning ved fejl. - -| Script (`npm run ...`) | Validerer | Blokerende | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | -| `check:node-runtime` | Node.js-versionen er inden for det understøttede interval | Ja | -| `check:cycles` | Cirkulære importer — alle moduler i `src/` + `open-sse/` | Ja | -| `check:route-validation:t06` | Zod-skemaer findes på alle routes (Tier 6-politik) | Ja | -| `check:any-budget:t11` | Antallet af `@ts-expect-error // any` overstiger ikke budgettet (Tier 11-catraca) | Ja | -| `check:provider-consistency` | Hver provider i `providers.ts` har en tilsvarende post i `providerRegistry.ts` (og omvendt, inden for allowlisten) | Ja | -| `check:model-lifecycle` | De tre manuelt vedligeholdte routingtabeller forbliver konsistente med det indcheckede livscyklus-snapshot (#11503): `FITNESS_TABLE` (`taskFitness.ts`) scorer ikke noget udfaset id, som `REGISTRY` kan route; hvert mål i `BUILT_IN_ALIASES` findes i `REGISTRY` og er ikke med i snapshottet over udfasede id'er; hvert udfaset id, der stadig findes i `REGISTRY`, videresendes eller er angivet i `allowedRetiredInCatalog`; og ingen kilde eller destination i `DEFAULT_DEGRADATION_MAP` fremgår som udfaset i dette snapshot. Dette beviser ikke, at en model aktuelt leveres af en aktiv upstream-tjeneste. Offline — sammenligner med `config/quality/model-lifecycle.json`, som opdateres manuelt med `npm run quality:refresh-model-lifecycle` (netværk; ikke integreret i CI). `allowedRetiredInCatalog` er en gradvis afviklingsmekanisme: Tilføj kun en post med en tilknyttet tracking-issue. | Ja | -| `check:fetch-targets` | Hvert `fetch("/api/...")` i `src/` på klientsiden peger på en faktisk `route.ts` | Ja | -| `check:deps` | Alle afhængigheder, der kan installeres med `npm install`, på tværs af hver `package.json` i repositoriet findes i `dependency-allowlist.json`; nye ikke-fastlåste pakker eller pakker med navne, der kan være resultatet af slopsquatting, markeres | Ja | -| `audit:deps` | `npm audit` (rod + electron) — ingen advarsler med høj eller kritisk alvorlighedsgrad (overlapper osv `check:vuln-ratchet`; se rationaliseringsbackloggen) | Ja | -| `check:lockfile` | Integriteten af `package-lock.json` — https-register, integritetshashes, ingen værtstilsidesættelser | Ja | -| `check:licenses` | SPDX-licenstilladelsesliste for produktionsafhængigheder | Ja | -| `check:tracked-artifacts` | Ingen build-artefakter / committede `node_modules`-symlinks (kører også i husky pre-commit; pre-push er bevidst let — #6716) | Ja | -| `check:ai-attribution` | Ingen AI/bot-`Co-Authored-By`-trailer eller AI-genereret sidefod i PR-commits, titel eller brødtekst — fast regel #16 (i `quality.yml`-løkken med hurtige kontroller for PR→`release/**` — læser event-payloaden, gør intet uden for PR'er — og et trin kun for PR'er i `ci.yml`-lint for PR→`main`; også husky-`commit-msg`-hooken; menneskelige medforfattere er tilladt; #14436) | -| `check:vitest-exclusions` | Hver Vitest-undtagelse angiver en sporingsissue og findes i `config/quality/vitest-exclusions.json` (#13204) | Ja | -| `check:file-size` | Ingen kildefil overskrider grænsen for den pågældende filendelse (skraldeprincip: fastlåste store filer på listen `frozen`) | Ja | -| `check:error-helper` | Fejlsvar i executors/handlers bruger `buildErrorBody()` / `sanitizeErrorMessage()` (fast regel #12) | Ja | -| `check:migration-numbering` | Migrations-SQL-filer er fortløbende nummereret uden huller eller dubletter | Ja | -| `check:public-creds` | Ingen bogstavelige OAuth-`client_id`/`client_secret`-værdier eller Firebase Web-nøgler uden for `publicCreds.ts` (fast regel #11) | Ja | -| `check:db-rules` | Ingen rå SQL uden for moduler i `src/lib/db/`; ingen barrel-importer fra `localDb.ts` (faste regler #2/#5) | Ja | -| `check:known-symbols` | Provider-eksekveringsfunktioner, routingstrategier og oversættere, der er registreret i deres dispatch-tabeller, matcher filerne på disken — ingen forældreløse eller ikke-deklarerede symboler | Ja | -| `check:route-guard-membership` | Hver route, der opretter en underproces, er klassificeret af `isLocalOnlyPath()` (faste regler #15/#17) | Ja | -| `check:test-discovery` | Hver `*.test.ts`- / `*.spec.ts`-fil i repoet indsamles af mindst én test-runner (ratchet: listen over forældreløse filer i `test-discovery-baseline.json` kan kun blive kortere) | Ja | -| `check:agent-skills-sync` | Genererede agent-skills-artefakter matcher deres kildekatalog (ingen afvigelser) | -| `check:provider-asset-provenance` | Udbyderlogoer/-aktiver har en registreret provenienspost | -| `lint:json` | JSON-konfigurationsfiler kan parses og overholder repositoriets lint-regler | -| `typecheck:core` | TypeScript-kompilering uden fejl (kun vejledende advarsler) | Ja | -| `typecheck:noimplicit:core` | Streng `noImplicitAny` — fremadskuende; mange eksisterende kaldesteder mangler stadig annoteringer | **Vejledende** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` afgrænset til `src/app/(dashboard)/**` (#7033) — den kuraterede tilladelsesliste med 27 filer i `typecheck:core` inkluderer ingen dashboard-TSX-filer, og `next build` typekontrollerer dem heller aldrig (`next.config.mjs` angiver `ignoreBuildErrors: true`), så regressioner med forældreløse identifikatorer dér (#6625/#6909) var usynlige for CI. Sammenligner med en fastfrosset baseline for antal pr. fil og pr. TS-kode (`config/quality/dashboard-typecheck-baseline.json`, samme mønster for håndhævelse af forældede poster som `check:known-symbols`) — kun NYE fejl ud over det baselinefastsatte antal får kontrollen til at fejle; reducer gradvist med `--update`, når en eksisterende fejl rettes. | Ja | +Kører på hver PR til `main`. Blokerer fletning ved fejl. + +| Script (`npm run ...`) | Validerer | Blokerende | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | Node.js-versionen er inden for det understøttede interval | Ja | +| `check:cycles` | Cirkulære imports på tværs af hele `src/` + `open-sse/` (AST-baseret, tsconfig-`paths` opløst). Uden yderligere argumenter = vejledende, viser cyklusserne. `check:cycles:ratchet` (som CI kører) blokerer, når antallet overstiger loftet `metrics.cycles` i `quality-baseline.json` — i øjeblikket 14, `direction: down`, så det kan kun falde (#15159 G-01/G-02) | Ja (ratchet) | +| `check:route-validation:t06` | Zod-skemaer findes på alle routes (Tier 6-politik) | Ja | +| `check:any-budget:t11` | Antallet af `@ts-expect-error // any` overstiger ikke budgettet (Tier 11-catraca) | Ja | +| `check:provider-consistency` | Hver provider i `providers.ts` har en tilsvarende post i `providerRegistry.ts` (og omvendt inden for tilladelseslisten) | Ja | +| `check:model-lifecycle` | De tre manuelt vedligeholdte routingtabeller forbliver konsistente med det indcheckede livscyklusøjebliksbillede (#11503): `FITNESS_TABLE` (`taskFitness.ts`) scorer ikke noget udfaset id, som `REGISTRY` kan route; hvert `BUILT_IN_ALIASES`-mål findes i `REGISTRY` og er fraværende i øjebliksbilledet over udfasede id'er; hvert udfaset id, der stadig findes i `REGISTRY`, videresendes eller er angivet i `allowedRetiredInCatalog`; og ingen kilde eller intet mål i `DEFAULT_DEGRADATION_MAP` fremstår som udfaset i dette øjebliksbillede. Dette beviser ikke, at en model aktuelt leveres af en aktiv upstream-tjeneste. Offline — sammenligner med `config/quality/model-lifecycle.json`, som opdateres manuelt med `npm run quality:refresh-model-lifecycle` (netværk; ikke integreret i CI). `allowedRetiredInCatalog` er en gradvist strammende skraldemekanisme: Tilføj kun en post sammen med en tracking-sag. | Ja | +| `check:fetch-targets` | Hvert `fetch("/api/...")` i `src/` på klientsiden peger på en faktisk `route.ts` | Ja | +| `check:deps` | Alle afhængigheder, der kan installeres med `npm install`, på tværs af samtlige `package.json`-filer i repoet findes i `dependency-allowlist.json`; nye ikke-fastlåste pakker eller pakker med navne, der kan være resultatet af slopsquatting, markeres | Ja | +| `audit:deps` | `npm audit` (rod + Electron) — ingen rådgivningsmeddelelser med høj/kritisk alvorlighedsgrad (overlapper OSV's `check:vuln-ratchet`; se rationaliseringsbackloggen) | Ja | +| `check:lockfile` | Integriteten af `package-lock.json` — HTTPS-register, integritetshashes, ingen værtsoverskrivninger | Ja | +| `check:licenses` | SPDX-licenstilladelsesliste for produktionsafhængigheder | Ja | +| `check:tracked-artifacts` | Ingen build-artefakter / committede `node_modules`-symbolske links (kører også i husky pre-commit; pre-push er bevidst let — #6716) | Ja | +| `check:ai-attribution` | Ingen AI/bot-`Co-Authored-By`-trailer eller AI-genereringsfooter i PR-commits, titel eller brødtekst — fast regel #16 (i `quality.yml`-løkken med hurtige kontroller for PR→`release/**` — læser hændelses-payloaden, foretager ingen handling uden for PR'er — og et trin kun for PR'er i `ci.yml`-lint for PR→`main`; også husky-`commit-msg`-hooket; menneskelige medforfattere er tilladt; #14436) | +| `check:vitest-exclusions` | Hver Vitest-ekskludering angiver en tracking-issue og findes i `config/quality/vitest-exclusions.json` (#13204) | Ja | +| `check:file-size` | Ingen kildefil overskrider grænsen for den pågældende filendelse (skraldehjulsmekanisme: fastlåste store filer på listen `frozen`) | Ja | +| `check:error-helper` | Fejlsvar i executors/handlers bruger `buildErrorBody()` / `sanitizeErrorMessage()` (fast regel #12) | Ja | +| `check:migration-numbering` | Migrations-SQL-filer er fortløbende nummereret uden huller eller dubletter | Ja | +| `check:public-creds` | Ingen bogstavelige OAuth-`client_id`/`client_secret`-værdier eller Firebase Web-nøgler uden for `publicCreds.ts` (fast regel nr. 11) | Ja | +| `check:db-rules` | Ingen rå SQL uden for modulerne i `src/lib/db/`; ingen barrel-importer fra `localDb.ts` (faste regler nr. 2/nr. 5) | Ja | +| `check:known-symbols` | Provider-eksekveringsfunktioner, routingstrategier og oversættere, der er registreret i deres dispatch-tabeller, matcher filerne på disken — ingen forældreløse eller ikke-deklarerede symboler | Ja | +| `check:route-guard-membership` | Alle ruter, der starter en underproces, er klassificeret af `isLocalOnlyPath()` (faste regler nr. 15/nr. 17) | Ja | +| `check:test-discovery` | Alle `*.test.ts`- / `*.spec.ts`-filer i repoet indsamles af mindst én testkører (ratchet: listen over forældreløse filer i `test-discovery-baseline.json` kan kun blive kortere) | Ja | +| `check:agent-skills-sync` | Genererede agent-skills-artefakter matcher deres kildekatalog (ingen afvigelser) | +| `check:provider-asset-provenance` | Udbyderlogoer/-aktiver har en registreret oprindelsespost | +| `lint:json` | JSON-konfigurationsfiler kan parses og overholder repoets lintregler | +| `typecheck:core` | TypeScript-kompilering uden fejl (kun vejledende advarsler) | Ja | +| `typecheck:noimplicit:core` | Streng `noImplicitAny` — fremadrettet; mange eksisterende kaldesteder mangler stadig annoteringer | **Vejledende** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` afgrænset til `src/app/(dashboard)/**` (#7033) — `typecheck:core`'s kuraterede tilladelsesliste med 27 filer indeholder ingen dashboard-TSX-filer, og `next build` typekontrollerer dem heller aldrig (`next.config.mjs` angiver `ignoreBuildErrors: true`), så regressioner med ikke-tilknyttede identifikatorer dér (#6625/#6909) var usynlige for CI. Sammenligner med en fastfrosset baseline for antallet pr. fil/pr. TS-kode (`config/quality/dashboard-typecheck-baseline.json`, samme mønster for håndhævelse af forældelse som `check:known-symbols`) — kun NYE fejl ud over baseline-antallet får kontrollen til at fejle; reducer gradvist med `--update`, når en eksisterende fejl rettes. | Ja | ### Job: `quality-gate` Kører efter `test-coverage`. Blokerer sammenfletning ved fejl. -| Script | Validerer | Blokerende | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | -| `quality:collect` | Genererer `quality-metrics.json` (antal ESLint-advarsler, dækning fra flettet shard-rapport) | Ja (forud for ratchet) | -| `quality:ratchet` | Ingen metrik i `quality-baseline.json` er blevet forringet (ESLint-advarsler ≤ baseline; dækning ≥ baseline) | Ja | -| `check:duplication` | Kodeduplikering (jscpd@4) overstiger ikke baseline i `quality-baseline.json` | Ja | -| `check:complexity` | Cyklomatisk kompleksitet på filniveau overstiger ikke grænsen (ESLint-kernereglerne `complexity` + `max-lines-per-function`) | Ja | -| `check:cognitive-complexity` | Ratchet for kognitiv kompleksitet (`eslint-plugin-sonarjs`) — separat ESLint-kørsel; CI kører begge samlet som det enkelte trin `check:complexity-ratchets` | Ja | -| `check:dead-code` | Ratchet for ubrugte eksporter/filer (knip) forringes ikke i forhold til baseline | Ja | -| `check:compression-budget` | Budget for komprimeringsbenchmark — minimumskrav til tokenbesparelser pr. motor må ikke forringes | Ja | -| `check:type-coverage` | Ratchet for procentvist typet kode (`type-coverage`) forringes ikke; erstatter i vid udstrækning `typecheck:noimplicit:core` | Ja | -| `check:codeql-ratchet` | Antallet af åbne CodeQL-advarsler forringes ikke (læses via `gh api`; springes kontrolleret over uden token) — opdateringsfrekvens og manuel udløsning: se "CodeQL-ratchet" nedenfor | Ja | +| Script | Validerer | Blokerende | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `quality:collect` | Genererer `quality-metrics.json` (antal ESLint-advarsler, dækning fra flettet shard-rapport) | Ja (køres før ratchet) | +| `quality:ratchet` | Ingen metrik i `quality-baseline.json` er blevet forringet (ESLint-advarsler ≤ baseline; dækning ≥ baseline) | Ja | +| `check:duplication` | Kodeduplikering (jscpd@4) overskrider ikke baselinen i `quality-baseline.json` | Ja | +| `check:complexity` | Cyklomatisk kompleksitet på filniveau overskrider ikke grænsen (ESLint-kernereglen `complexity` + `max-lines-per-function`) | Ja | +| `check:cognitive-complexity` | Ratchet for kognitiv kompleksitet (`eslint-plugin-sonarjs`) — separat ESLint-kørsel; CI kører begge samlet som det enkelte trin `check:complexity-ratchets` | Ja | +| `check:dead-code` | Ratchet for ubrugte eksporter/filer (knip) forringes ikke i forhold til baselinen | Ja | +| `check:compression-budget` | Budget for komprimeringsbenchmark — minimumskravene til tokenbesparelser pr. motor må ikke forringes | Ja | +| `check:type-coverage` | Ratchet for procentvis typning (`type-coverage`) forringes ikke; erstatter i vid udstrækning `typecheck:noimplicit:core` | Ja | +| `check:codeql-ratchet` | Antallet af åbne CodeQL-advarsler forringes ikke (læses via `gh api`; springes uden fejl over uden token) — opdateringsfrekvens og manuel udløsning: se "CodeQL-ratchet" nedenfor | Ja | ### Job: `quality-extended` Hele jobbet er vejledende (`continue-on-error: true`). De npm-baserede ratchets køres reelt; de eksterne scannere installeres via `gh release download` og springer selv over (exit 0), -når en binær fil stadig mangler. +hvis en binær fil stadig mangler. -| Script | Validerer | Blokerende | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| `check:circular-deps` | Ingen cirkulære afhængigheder (dpdm) | **Vejledende** | -| `check:bundle-size` | Bundtstørrelsen overstiger ikke grænsen | **Vejledende** | -| `check:secrets` | Scanning efter hemmeligheder (gitleaks) — springes over, hvis den binære fil mangler | **Vejledende** | -| `check:vuln-ratchet` | Sårbarheder i afhængigheder (osv-scanner) forringes ikke — springes over, hvis den binære fil mangler | **Vejledende** | -| `check:workflows` | Linting af workflows (actionlint + zizmor); manglende/defekte scannere, ugyldige rapporter eller manglende ratchet-baseline medfører fejlen INCOMPLETE. Gyldige fund følger den valgte strenge/vejledende/ratchet-politik | Kørsel påkrævet; zizmor-ratchet blokerer i CI | -| `check:openapi-breaking` | Inkompatible ændringer i den offentlige API-kontrakt (`openapi.yaml`) i forhold til basisgrenen (oasdiff) — genererer `openapiBreaking=N`; springes over, hvis oasdiff mangler, eller basisspecifikationen ikke kan findes | **Vejledende** | +| Script | Validerer | Blokerende | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `check:circular-deps` | Ingen cirkulære afhængigheder (dpdm) | **Vejledende** | +| `check:bundle-size` | Bundtstørrelsen overskrider ikke grænsen | **Vejledende** | +| `check:secrets` | Scanning efter hemmeligheder (gitleaks) — springes over, hvis den binære fil mangler | **Vejledende** | +| `check:vuln-ratchet` | Sårbarheder i afhængigheder (osv-scanner) forringes ikke — springes over, hvis den binære fil mangler | **Vejledende** | +| `check:workflows` | Linting af workflows (actionlint + zizmor); manglende/defekte scannere, ugyldige rapporter eller en manglende ratchet-baseline medfører fejl som INCOMPLETE. Gyldige fund følger den valgte strenge/vejledende/ratchet-politik | Kørsel påkrævet; zizmor-ratchet er blokerende i CI | +| `check:openapi-breaking` | Bagudinkompatible ændringer i den offentlige API-kontrakt (`openapi.yaml`) i forhold til basisgrenen (oasdiff) — genererer `openapiBreaking=N`; springes over, hvis oasdiff mangler, eller basisspecifikationen ikke kan findes | **Vejledende** | ### Job: `docs-sync-strict` -Kører ved hver PR til `main`. Blokerer sammenfletning ved fejl. - -| Script | Validerer | Blokerende | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `check:docs-all` | Meta-gate, der kører de 6 underliggende gates nedenfor sekventielt | Ja | -| ↳ `check:docs-sync` | Versionskonsistens mellem CHANGELOG / OpenAPI / llm.txt | Ja | -| ↳ `check:docs-counts` | Antal angivet i prosa (antal udbydere, antal migreringer osv.) ligger inden for ratchet-intervallet for de faktiske antal | Ja | -| ↳ `check:env-doc-sync` | Hver miljøvariabel i `.env.example` er dokumenteret i en dokumentationstabel og omvendt | Ja | -| ↳ `check:deprecated-versions` | Ingen udfasede versionsstrenge i dokumentationen | Ja | -| ↳ `check:doc-links` | Interne markdown-links i dokumentationen peger på eksisterende filer (`[tekst]`/`(sti)`-format) | Ja | -| ↳ `check:fabricated-docs` | Ruter, miljøvariabler, CLI-kommandoer, hook-navne og filstier nævnt i dokumentationen findes i kodebasen. Hård gate via `--strict`; soft-fail uden flag. | Ja (via `--strict` i CI) | -| `check:cli-i18n` | CLI-kommandostrenge findes i alle i18n-lokalitetsfiler | Ja | -| `check:openapi-coverage` | OpenAPI-specifikationen dækker mindst en ratchet-baseret minimumsgrænse af de faktiske ruter | Ja | -| `check:openapi-security-tiers` | Sikkerhedsniveauannoteringer i `openapi.yaml` stemmer overens med klassifikationerne i `routeGuard.ts` | **Rådgivende** | -| `check:openapi-routes` | Hver sti i `openapi.yaml` svarer til en faktisk `route.ts` (modvirker hallucinationer) | Ja | -| `check:docs-symbols` | Hver `/api/...`-reference i `docs/**/*.md` svarer til en faktisk `route.ts` (modvirker hallucinationer) | Ja | -| `i18n translation drift` | Ikke-oversatte nøgler i i18n-lokalitetsfiler — kun advarsel | **Rådgivende** | +Køres for hver PR til `main`. Blokerer fletning ved fejl. + +| Script | Validerer | Blokerende | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `check:docs-all` | Metakontrol, der kører de 6 delkontroller nedenfor sekventielt | Ja | +| ↳ `check:docs-sync` | Versionsoverensstemmelse mellem CHANGELOG / OpenAPI / llm.txt | Ja | +| ↳ `check:docs-counts` | Antal i prosa (antal udbydere, antal migreringer osv.) ligger inden for skraldevinduet for de faktiske antal | Ja | +| ↳ `check:env-doc-sync` | Hver miljøvariabel i `.env.example` er dokumenteret i en dokumentationstabel og omvendt | Ja | +| ↳ `check:deprecated-versions` | Ingen forældede versionsstrenge i dokumentationen | Ja | +| ↳ `check:doc-links` | Interne markdownlinks i dokumentationen peger på faktiske filer (`[text]`/`(path)`-format) | Ja | +| ↳ `check:fabricated-docs` | Ruter, miljøvariabler, CLI-kommandoer, hooknavne og filstier, der nævnes i dokumentationen, findes i kodebasen. Hård kontrol via `--strict`; blød fejl uden flag. | Ja (via `--strict` i CI) | +| `check:cli-i18n` | CLI-kommandostrenge findes i alle i18n-lokalitetsfiler | Ja | +| `check:openapi-coverage` | OpenAPI-specifikationen dækker som minimum en gradvist hævet nedre grænse for faktiske ruter | Ja | +| `check:openapi-security-tiers` | Sikkerhedsniveauannoteringer i `openapi.yaml` stemmer overens med klassifikationerne i `routeGuard.ts` | **Vejledende** | +| `check:openapi-routes` | Hver sti i `openapi.yaml` peger på en faktisk `route.ts` (forebyggelse af hallucinationer) | Ja | +| `check:docs-symbols` | Hver `/api/...`-reference i `docs/**/*.md` peger på en faktisk `route.ts` (forebyggelse af hallucinationer) | Ja | +| `i18n translation drift` | Uoversatte nøgler i i18n-lokalitetsfiler — kun advarsel | **Vejledende** | ### Job: `i18n-ui-coverage` @@ -192,42 +188,42 @@ Kører ved hver PR til `main`. Blokerer sammenfletning ved fejl. | `check-ui-keys-coverage` (inline) | Dækningen af UI-i18n-nøgler er ≥ 65 % | Ja | | `check-ui-value-drift` (inline) | En omskrevet engelsk **værdi** efterlader ingen forældet oversættelse | Ja | | `check-new-key-coverage` (inline) | En **ny** engelsk nøgle er oversat i alle lokaliteter — en `__MISSING__:`-markør afvises | Ja | -| `check-translation-ratio` | Andelen af reelle oversættelser pr. lokalitet (blade uden for tilladelseslisten, der er identiske med engelsk / pladsholdere / mangler) må ikke overstige `config/quality/i18n-translation-baseline.json` + margen | **Rådgivende** | +| `check-translation-ratio` | Andelen af reelle oversættelser pr. lokalitet (blade uden for tilladelseslisten, der er identiske med engelsk / pladsholdere / mangler) må ikke overstige `config/quality/i18n-translation-baseline.json` + margen | **Vejledende** | -Kræver `fetch-depth: 0` — value-drift-gaten sammenligner `en.json` med merge-basen. +Kræver `fetch-depth: 0` — kontrollen for værdiafvigelser sammenligner `en.json` med fletningsbasen. -#### `check-ui-value-drift` — gate for forældede oversættelser +#### `check-ui-value-drift` — kontrol for forældede oversættelser -Opfanger den ene i18n-regression, som de andre gates strukturelt ikke kan se: En engelsk værdi -omskrives, mens oversættelserne, der er afledt af den _tidligere_ engelske tekst, bliver stående, så -ikke-engelske brugere fortsat læser en selvsikkert formuleret, men nu forkert tekst. +Opfanger den ene i18n-regression, som de andre kontroller strukturelt ikke kan se: En engelsk værdi +omskrives, mens oversættelserne baseret på den _tidligere_ engelske tekst bliver stående, så +ikke-engelske brugere fortsat læser selvsikkert formuleret, men nu forkert tekst. Dette blev faktisk udgivet. `oauthModal.googleOAuthWarning` blev omskrevet, da Antigravity- -loginhjælperen blev introduceret (#5203); **39 ud af 43 lokaliteter** beholdt en tekst, der bad operatører om at "kopiere -hele URL'en og indsætte den nedenfor" — et flow, der ikke kan gennemføres for den pågældende udbyder. Det +loginhjælperen blev tilføjet (#5203); **39 af 43 lokaliteter** beholdt tekst, der bad operatører om at "kopiere den +fulde URL og indsætte den nedenfor" — et flow, der ikke kan gennemføres for denne udbyder. Det blev ikke opdaget før #8463, fordi: - `sync-ui-keys` udfylder kun nøgler, der **mangler**, aldrig nøgler, der er **forældede**; -- `check-ui-keys-coverage` tæller nøglers _tilstedeværelse_, så en forældet oversættelse tæller som dækket; +- `check-ui-keys-coverage` tæller nøglers _tilstedeværelse_, så en forældet oversættelse regnes som dækket; - `check-translation-drift` sporer dokumentationsspejlene i `docs/i18n//**.md` — den læser aldrig `src/i18n/messages/*.json`. Blokerende i jobbet `docs-sync-strict` siden - gensynkroniseringen i 2026-09: Rediger et kernedokument → `npm run i18n:run -- --files=` (på sektionsniveau, billigt). + gensynkroniseringen i 2026-09: rediger et kernedokument → `npm run i18n:run -- --files=` (på sektionsniveau, billigt). -**Diff-bevidst, ikke understøttet af en baseline.** Den sammenligner `en.json` ved merge-basen med -arbejdstræet; for hver nøgle, hvis engelske værdi er ændret, er enhver lokalitet, der stadig indeholder en +**Diff-bevidst, ikke baseline-understøttet.** Den sammenligner `en.json` ved merge-basen med +arbejdstræet; for hver nøgle, hvis engelske værdi er ændret, er enhver lokalitet, der stadig har en uberørt oversættelse, forældet. Dette **fastfryser bevidst eksisterende gæld** — en diff kan ikke afsløre, hvilken gammel engelsk tekst en eksisterende oversættelse stammer fra, så kontrollen vurderer -kun det, den aktuelle ændring berører. Alternativet (en hash-baseline pr. nøgle) ville kræve -en genereret fil på ~600 KB, 3× større end den største eksisterende baseline, og medføre ændringer ved hver i18n-PR. +kun det, som den aktuelle ændring berører. Alternativet (en hash-baseline pr. nøgle) ville kræve +en genereret fil på ~600 KB, 3× større end den største eksisterende baseline, med ændringer ved hver i18n-PR. -Der er to måder at opfylde kravet på: +Det kan opfyldes på to måder: 1. opdater de berørte oversættelser, eller -2. indstil dem til `__MISSING__:` — kørselstiden leverer derefter den rettede engelske tekst +2. angiv dem som `__MISSING__:` — kørselssystemet leverer derefter den rettede engelske tekst (`src/i18n/request.ts::deepMergeFallback`, #7258), og nøglen sættes i kø til oversættelse. Hvis strengens **betydning** er ændret, bør du foretrække at **omdøbe nøglen**: En ny nøgle kan ikke arve -en forældet oversættelse. Det er det mønster, #8463 brugte. +en forældet oversættelse. Det er det mønster, som #8463 anvendte. ```bash npm run i18n:check-value-drift # streng (det, som CI kører) @@ -236,11 +232,11 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` Afslutter med 0 og `SKIP reason=base-unresolved`, når basiskataloget ikke kan læses (overfladisk -klon uden basereferencen), på samme måde som `check-openapi-breaking`. +klon uden basereferencen), i lighed med `check-openapi-breaking`. ### Job: `i18n` -Komplet i18n-valideringsmatrix (ét job pr. lokalitet). Hele jobbet er vejledende. +Fuld i18n-valideringsmatrix (ét job pr. lokalitet). Hele jobbet er vejledende. | Script | Validerer | Blokerende | | ------------------------------- | ----------------------------------------- | ---------------------------------------------------------- | @@ -250,20 +246,20 @@ Komplet i18n-valideringsmatrix (ét job pr. lokalitet). Hele jobbet er vejledend Kører kun på pull requests. -| Script | Validerer | Blokerende | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check:pr-test-policy` | PR'er, der ændrer produktionskode i `src/`, `open-sse/`, `electron/` eller `bin/`, skal inkludere eller opdatere test (hård regel #8) | Ja | -| `check:test-masking` | Ændrede testfiler reducerer ikke nettoantallet af asserts og tilføjer ikke `assert.ok(true)`-tautologier | Ja | -| `check:pr-evidence` | PR-beskrivelsen henviser til test-/VPS-dokumentation for ændringen (automatiserer hård regel #18 ved at søge i PR-teksten — skrøbeligt, se Backlog) | Ja | +| Script | Validerer | Blokerende | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check:pr-test-policy` | PR'er, der ændrer produktionskode i `src/`, `open-sse/`, `electron/` eller `bin/`, skal inkludere eller opdatere tests (ufravigelig regel nr. 8) | Ja | +| `check:test-masking` | Ændrede testfiler reducerer ikke nettoantallet af assertions og tilføjer ikke `assert.ok(true)`-tautologier | Ja | +| `check:pr-evidence` | PR-beskrivelsen henviser til test-/VPS-dokumentation for ændringen (automatiserer ufravigelig regel nr. 18 ved at gennemsøge PR-teksten — skrøbeligt, se Backlog) | Ja | ### Job: `test-vitest` -Kører efter `build`. Blokerer fletning ved fejl. +Kører efter `build`. Blokerer sammenfletning ved fejl. -| Suite | Validerer | Blokerende | -| ---------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP-server (110 værktøjer), autoCombo, cache — vitest-kørsel | Ja | -| `test:vitest:ui` | UI-komponenttest — vitest-kørsel | **Blokerende** — eksisterende fejl er udtrykkeligt ekskluderet i `vitest.config.ts`; nye fejl får jobbet til at fejle | +| Pakke | Validerer | Blokerende | +| ---------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP-server (110 værktøjer), autoCombo, cache — vitest-kørsel | Ja | +| `test:vitest:ui` | UI-komponenttests — vitest-kørsel | **Blokerende** — eksisterende fejl er eksplicit udeladt i `vitest.config.ts`; nye fejl får jobbet til at fejle | ### Natlige workflows (planlagte, vejledende) @@ -271,11 +267,11 @@ Disse kører efter en cron-plan (og `workflow_dispatch`), aldrig på PR'er. De e | Workflow | Validerer | Blokerende | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | fast-check-egenskabstest med en tilfældig seed-værdi + et højt antal kørsler | **Vejledende** | -| `nightly-resilience` | kontrol af heap-vækst, chaos-fejlinjektion, k6-belastnings-/udholdenhedstest | **Vejledende** | +| `nightly-property` | fast-check-egenskabstests med et tilfældigt seed og et højt antal kørsler | **Vejledende** | +| `nightly-resilience` | kontrol af heap-vækst, chaos fault-injection, k6-belastnings-/udholdenhedstest | **Vejledende** | | `nightly-llm-security` | promptfoo-injektionsbeskyttelse (blokeringstilstand) + garak-prober (springes over uden en udbyderhemmelighed) | **Vejledende** | -| `nightly-schemathesis` | OpenAPI-kontraktfuzzing (schemathesis) mod en aktiv OmniRoute ved hjælp af `docs/openapi.yaml` — afdækker specifikationsbrud/uhåndterede 500-fejl (Fase 8 B.4) | **Vejledende** | -| `nightly-mutation` | Stryker-mutationstestscore for den hurtige enhedstestbane — overlevende mutanter afdækker svage asserts | **Vejledende** | +| `nightly-schemathesis` | OpenAPI-kontraktfuzzing (schemathesis) mod en aktiv OmniRoute ved brug af `docs/openapi.yaml` — synliggør specifikationsbrud/uhåndterede 500-fejl (Fase 8 B.4) | **Vejledende** | +| `nightly-mutation` | Stryker-mutationstestscore for den hurtige enhedstestbane — overlevende mutanter synliggør svage assertions | **Vejledende** | | `nightly-compat` | Kompatibilitetsmatrix for Node-motoren på tværs af de understøttede `engines.node`-intervaller | **Vejledende** | --- @@ -540,43 +536,43 @@ forbindelsesfejl og afbryder aldrig sessioner. --- -## Rationaliserings-backlog (ROI-gennemgang — Fase 9 Bølge 3) +## Rationaliserings-backlog (ROI-gennemgang — Fase 9 Onda 3) Denne oversigt blev afstemt mod `ci.yml` den 2026-06-17 (den tidligere version udelod `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). En ROI-gennemgang af det afstemte sæt -identificerede følgende kandidater til rationalisering. **Sammenlægningerne er mekaniske -CI-ændringer; ændringerne/fjernelserne er politiske beslutninger, som er forbeholdt operatøren.** Intet nedenfor +identificerede følgende rationaliseringskandidater. **Sammenlægningerne er mekaniske +CI-ændringer; ændringerne/fjernelserne er politikbeslutninger, der er forbeholdt operatøren.** Intet nedenfor er anvendt endnu. -**Også udokumenteret ovenfor** (vejledende, lavt signal): jobbet `docs-lint` -(markdownlint + Vale, hele jobbet med `continue-on-error`) og de separate scanner-workflows +**Også udokumenteret ovenfor** (vejledende, lavt signalniveau): jobbet `docs-lint` +(markdownlint + Vale, hele jobbet har `continue-on-error`) og de selvstændige scanner-workflows `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` findes i -`quality-baseline.json`, men er ikke koblet til en blokerende skralde i `ci.yml` — metrikken er +`quality-baseline.json`, men er ikke koblet til en blokerende ratchet i `ci.yml` — metrikken er i øjeblikket forældreløs. ### Sammenlægning / deduplikering (mekanisk, lavere risiko) -Hver kandidat blev valideret mod den aktuelle gate-tilstand den 2026-06-17 (stol på, men verificér); -flere "åbenlyse" sammenlægninger viste sig at skjule gæld og er **ikke** direkte anvendelige erstatninger. +Hver kandidat blev valideret mod den aktive gate-tilstand den 2026-06-17 (hav tillid, men verificér); +flere "indlysende" sammenlægninger viste sig at skjule gæld og er **ikke** direkte anvendelige erstatninger. -- **`check:docs-sync` køres to gange** — separat i jobbet `lint` og igen i `check:docs-all` (`docs-sync-strict`) samt i husky pre-commit-hooken. ✅ **FÆRDIG** — separat `lint`-kørsel fjernet. -- **CVE-scanning** — ❌ **IKKE en ukompliceret sammenlægning.** `audit:deps` fejler hårdt ved enhver CVE med høj/kritisk alvorlighed; `check:vuln-ratchet` (osv) fejler kun ved en _regression_ i forhold til baseline (i øjeblikket 1 MODERATE). Forskellig semantik — fjernelse af `audit:deps` ville fjerne den absolutte gate for høj/kritisk alvorlighed. Behold begge. -- **Cyklusregistrering** — ❌ **IKKE en ukompliceret sammenlægning.** `check:circular-deps` (dpdm) rapporterer **91 cyklusser** (det er derfor, den er vejledende); den kan ikke gøres blokerende uden først at løse dem, og den har et bredere omfang end den grønne, kuraterede `check:cycles`. Behold `check:cycles` som blokerende; løsning af de 91 dpdm-cyklusser er sin egen backlog. -- **Kompleksitet** — ✅ **FÆRDIG** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): én ESLint-gennemgang, optællinger efter ruleId, så baselines for cyklomatisk kompleksitet+maksimalt antal linjer og kognitiv kompleksitet forbliver uafhængige; individuelle `check:complexity` / `check:cognitive-complexity` bibeholdes til lokal `--update`. -- **`/api`-antihallucination** — ✅ **FÆRDIG** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): én FS-oversigt over `src/app/api`; openapi-routes + docs-symbols rapporterer stadig uafhængigt; individuelle tjek bibeholdes til lokale kørsler. -- **`check:node-runtime` køres i 11 jobs** — ⚠️ **lav ROI.** Hvert job bruger en separat runner, og tjekket tager <1s; samlet besparelse ~10s mod tabet af en billig beskyttelse pr. job. Ikke besværet værd. +- **`check:docs-sync` kører to gange** — selvstændigt i jobbet `lint` og igen inde i `check:docs-all` (`docs-sync-strict`) samt i husky pre-commit-hooket. ✅ **FÆRDIG** — den selvstændige `lint`-kørsel er fjernet. +- **CVE-scanning** — ❌ **IKKE en problemfri sammenlægning.** `audit:deps` fejler hårdt ved enhver CVE med høj/kritisk alvorlighed; `check:vuln-ratchet` (osv) fejler kun ved en _forværring_ i forhold til baseline (aktuelt 1 MODERATE). Forskellig semantik — fjernelse af `audit:deps` ville fjerne den absolutte gate for høj/kritisk alvorlighed. Behold begge. +- **Cyklusdetektering** — ✅ **FÆRDIG** (#15159 G-01/G-02). Den gamle tekst her kaldte `check:cycles` for "den grønne, kuraterede" gate og begrundede, at den fortsat skulle være blokerende, fordi `check:circular-deps` (dpdm) rapporterede 91 cyklusser. Det grønne resultat var **falsk grønt**: `check:cycles` scannede 5 undermapper (450 filer), matchede kun statiske `import|export … from` og ignorerede alle `@/`- og `@omniroute/open-sse/`-specifikatorer, så den kunne ikke se de cyklusser med dynamiske imports og aliaser, der dominerede repoet. Rettet: gaten gennemgår nu `src` + `open-sse` (5023 filer), indsamler specifikatorer fra TypeScript-AST'en (så `import("…")` tæller, mens `typeof import("…")` i typeposition ikke gør) og opløser tsconfig-`paths`. Den finder **14** cyklusser, ikke 0. Da 14 allerede eksisterende cyklusser ikke kan rettes i en gate-PR, er `check:cycles` nu en **ratchet** (`--ratchet`, loft `metrics.cycles.value = 14` i `quality-baseline.json`, `direction: down`) — den blokerer enhver _forværring_, og antallet kan kun falde. CI kører `npm run check:cycles:ratchet`. Nedbringelsen følger med **A-01**. `check:circular-deps` (dpdm) forbliver vejledende som den bredere sekundære vurdering. +- **Kompleksitet** — ✅ **FÆRDIG** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): én ESLint-gennemgang, optælling efter ruleId, så cyclomatic+max-lines- og cognitive-baselines forbliver uafhængige; de individuelle `check:complexity` / `check:cognitive-complexity` bibeholdes til lokal `--update`. +- **`/api`-antihallucination** — ✅ **FÆRDIG** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): én FS-oversigt over `src/app/api`; openapi-routes + docs-symbols rapporterer stadig uafhængigt; de individuelle kontroller bibeholdes til lokale kørsler. +- **`check:node-runtime` kører i 11 jobs** — ⚠️ **lav ROI.** Hvert job bruger en separat runner, og kontrollen tager <1s; samlet besparelse ~10s mod at miste en billig sikkerhedskontrol pr. job. Ikke besværet værd. - **`typecheck:noimplicit:core` i CI-lint** — ✅ **fjernet fra lint-jobbet** (var vejledende med `continue-on-error`); den blokerende typeoverflade er `typecheck:core` + `check:type-coverage`. Det lokale script er bibeholdt. -### Skift / beslut (operatørpolitik) +### Ændr / beslut (operatørpolitik) -- `check:openapi-security-tiers` (vejledende) — ❌ **KAN IKKE ukompliceret gøres blokerende.** Den afslutter med 0, men advarer om, at flere `traffic-inspector`-ruter under `LOCAL_ONLY_API_PREFIXES` mangler annoteringen `x-loopback-only: true`. Håndhævelse kræver, at disse annoteringer først føjes til `openapi.yaml`. -- `typecheck:noimplicit:core` (vejledende) — er i vid udstrækning overflødiggjort af den blokerende `check:type-coverage`-skralde. Gør den til en skralde, eller fjern den redundante anden `tsc`-gennemgang. -- `test:vitest:ui` (nu **blokerende**) — allerede eksisterende fejl er eksplicit udelukket i `vitest.config.ts` med `// #8618`-sporingskommentarer; nye fejl får jobbet til at fejle. -- `check:secrets` (gitleaks, blokerende skralde fastfrosset ved 3 dokumenterede falske positiver) — føj de 3 til tilladelseslisten for at nå 0, eller nedgradér til vejledende. Overlapper GitHubs indbyggede hemmelighedsscanning + `check:public-creds`. -- `check:pr-evidence` (blokerende, søger i PR-brødtekstens prosa med grep) — høj risiko for falske positiver; hvis den fjernes, svækkes håndhævelsen af Hård regel #18, så dette er en reel politisk beslutning. -- `semgrep` (separat vejledende workflow) — overlapper CodeQL for OWASP-familierne; kobl dens baseline til en skralde, eller fjern den. +- `check:openapi-security-tiers` (vejledende) — ❌ **KAN IKKE problemfrit gøres blokerende.** Den afslutter med 0, men advarer om, at flere `traffic-inspector`-ruter under `LOCAL_ONLY_API_PREFIXES` mangler annoteringen `x-loopback-only: true`. Håndhævelse kræver, at disse annoteringer først føjes til `openapi.yaml`. +- `typecheck:noimplicit:core` (vejledende) — er i vid udstrækning dækket af den blokerende `check:type-coverage`-ratchet. Gør den til en ratchet, eller fjern den redundante anden `tsc`-kørsel. +- `test:vitest:ui` (nu **blokerende**) — allerede eksisterende fejl er eksplicit ekskluderet i `vitest.config.ts` med `// #8618`-sporingskommentarer; nye fejl får jobbet til at fejle. +- `check:secrets` (gitleaks, blokerende ratchet fastlåst ved 3 dokumenterede falske positiver) — tilføj de 3 til allowlisten for at nå 0, eller nedgradér til vejledende. Overlapper GitHubs indbyggede secret-scanning + `check:public-creds`. +- `check:pr-evidence` (blokerende, søger med grep i PR-brødteksten) — høj risiko for falske positiver; fjernelse svækker håndhævelsen af Hard Rule #18, så dette er en reel politikbeslutning. +- `semgrep` (selvstændig og vejledende) — overlapper CodeQL for OWASP-familierne; forbind dens baseline med en ratchet, eller fjern den. --- diff --git a/docs/i18n/da/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/da/docs/compression/COMPRESSION_GUIDE.md index b46c619b0709..0927689c01e5 100644 --- a/docs/i18n/da/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/da/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ i stedet for engelsk. På en krop med `messages` kontrollerer en indholdsbypass (`shouldBypassCavemanOutputMode()` i `open-sse/services/compression/outputMode.ts`) de sidste tre meddelelser og springer stilarterne over for hele turen, når de matcher dens sikkerheds-, irreversibel-handling-, -afklaring- eller rækkefølge-følsomme nøgleord. Bypassen kører uanset hvad dashboardets -**Auto-Clarity Bypass**-skifte (`cavemanOutputMode.autoClarity`) er indstillet til. +afklaring- eller rækkefølge-følsomme nøgleord. Bypassen kører, mens dashboardets **Auto-Clarity Bypass**-skifte (`cavemanOutputMode.autoClarity`) er slået til, hvilket er standarden; med skiftet slået fra gælder de valgte stilarter også på de ture. Når bypassen tillader turen at passere, placerer `placeSystemInstruction()` (samme fil), som aldrig opretter en ny `messages[0]`, blokken i den første af disse, den finder: diff --git a/docs/i18n/da/docs/reference/ENVIRONMENT.md b/docs/i18n/da/docs/reference/ENVIRONMENT.md index fb42994725aa..b05195737c1b 100644 --- a/docs/i18n/da/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/da/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Påkrævede hemmeligheder -Disse **skal** være angivet før den første kørsel. Uden dem vil applikationen enten nægte at starte eller køre med usikre standardindstillinger. - -| Variabel | Påkrævet | Standardværdi | Kildefil | Beskrivelse | -| ---------------------------- | ------------------- | -------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Ja** | _(ingen)_ | `src/lib/auth` | Signerer/verificerer alle sessionscookies til dashboardet (JWT). Generér med `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ja** | _(ingen)_ | `src/lib/db/apiKeys.ts` | AES-krypteringsnøgle til API-nøgleværdier gemt i SQLite. Generér med `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ja** | `CHANGEME` | Bootstrap-script | Angiver den oprindelige administratoradgangskode til dashboardet (svarer til standardværdien i `.env.example` — den er bevidst åbenlyst usikker for at fremtvinge en ændring). **Skift den før første brug.** Efter login kan den ændres via Dashboard → Indstillinger → Sikkerhed. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (produktion) | _(ikke angivet)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Delt hemmelighed til den interne WebSocket-bro for Codex Responses. Godkender broanmodninger mellem Electron/browserens WS-relæ og OmniRoute. ⚠️ **PÅKRÆVET i produktion — når den ikke er angivet, afvises alle WS-broanmodninger.** Generér med `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nej | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplicit id til cache-invalidering af service workeren for PWA-skallen (#11779); først i opløsningsrækkefølgen. | -| `SOURCE_VERSION` | Nej | _(ikke angivet)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nummer to i rækkefølgen — angives af PaaS-buildere (f.eks. Heroku-lignende) som den udrullede commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nej | _(afledt)_ | `src/shared/components/PwaRegister.tsx` | Offentlig buildværdi, som klienten bruger ved buildtidspunktet til at registrere `/sw.js?v=…`; afledes af de to ovenstående og derefter git-SHA'en. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nej (automatisk) | _(automatisk pr. opstart)_ | `src/server/authz/policies/management.ts` | Hemmelighed pr. proces, der beviser, at det betroede peer-IP-stempel kom fra OmniRoutes egen HTTP-server (`scripts/dev/peer-stamp.mjs`). Authz-middlewaren har kun tillid til anmodningens lokalitet (loopback/LAN-begrænsning af LOCAL_ONLY-ruter), når stemplet indeholder dette token. Genereres automatisk ved hver opstart — lad den være udefineret; fastlås den kun for multiprocesopsætninger, der skal dele stemplet. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nej | _(ikke angivet)_ | `scripts/dev/peer-stamp.mjs` | Kommaseparerede IP-adresser eller CIDR-intervaller for reverse proxyer, hvis `X-Forwarded-For` / `X-Real-IP` IP-tilladelses-/afvisningslisten må stole på, ud over loopback, private netværksadresser og Cloudflare-edge-servere, som der altid er tillid til. Kun nødvendigt for en proxy på en anden offentlig adresse; ellers er det proxyens egen adresse, som filteret vurderer. | +Hemmeligheder, der er markeret med **Ja**, men ikke er angivet, genereres automatisk ved den første kørsel (`JWT_SECRET`, `API_KEY_SECRET`) og gemmes i `server.env` i datakataloget — angiv dem eksplicit, når kataloget er kortlivet (efemært). Undtagelsen er `OMNIROUTE_WS_BRIDGE_SECRET`: den gemmes ikke i `server.env` — serverens indgangspunkter (entrypoints) skaber en kortlevet værdi pr. opstart, som kun broen inde i processen kan bruge, så eksterne broklienter skal angive den eksplicit. + +| Variabel | Påkrævet | Standardværdi | Kildefil | Beskrivelse | +| ---------------------------- | ------------------- | -------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ja** | _(ingen)_ | `src/lib/auth` | Signerer/verificerer alle sessionscookies til dashboardet (JWT). Generér med `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ja** | _(ingen)_ | `src/lib/db/apiKeys.ts` | AES-krypteringsnøgle til API-nøgleværdier gemt i SQLite. Generér med `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nej | _(ikke angivet)_ | `src/lib/auth/managementPassword.ts` | Angiver dashboard-adgangskoden ved opstart, så længe der ikke er gemt nogen adgangskode; ignoreres, når en adgangskode er gemt. npm- og kildeinstallationer kopierer `.env.example` til `.env`, så de starter med dens offentligt kendte pladsholder `CHANGEME`: udskift den før første opstart, eller angiv `INITIAL_PASSWORD=` (tom) i den `.env` for at oprette adgangskoden i onboarding-guiden ved første besøg, ligesom en ikke-angivet værdi (standarden i Docker-afbildningen) gør. Skift en gemt adgangskode under Dashboard → Indstillinger → Sikkerhed, eller kør `omniroute reset-password` (`node bin/reset-password.mjs` i et kilde-checkout) med `DATA_DIR` angivet til serverens datakatalog. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (produktion) | _(ikke angivet)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Delt hemmelighed til den interne WebSocket-bro for Codex Responses. Godkender broanmodninger mellem Electron/browserens WS-relæ og OmniRoute. ⚠️ **PÅKRÆVET i produktion — når den ikke er angivet, afvises alle WS-broanmodninger.** Generér med `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nej | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplicit id til cache-invalidering af service workeren for PWA-skallen (#11779); først i opløsningsrækkefølgen. | +| `SOURCE_VERSION` | Nej | _(ikke angivet)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nummer to i rækkefølgen — angives af PaaS-buildere (f.eks. Heroku-lignende) som den udrullede commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nej | _(afledt)_ | `src/shared/components/PwaRegister.tsx` | Offentlig buildværdi, som klienten bruger ved buildtidspunktet til at registrere `/sw.js?v=…`; afledes af de to ovenstående og derefter git-SHA'en. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nej (automatisk) | _(automatisk pr. opstart)_ | `src/server/authz/policies/management.ts` | Hemmelighed pr. proces, der beviser, at det betroede peer-IP-stempel kom fra OmniRoutes egen HTTP-server (`scripts/dev/peer-stamp.mjs`). Authz-middlewaren har kun tillid til anmodningens lokalitet (loopback/LAN-begrænsning af LOCAL_ONLY-ruter), når stemplet indeholder dette token. Genereres automatisk ved hver opstart — lad den være udefineret; fastlås den kun for multiprocesopsætninger, der skal dele stemplet. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nej | _(ikke angivet)_ | `scripts/dev/peer-stamp.mjs` | Kommaseparerede IP-adresser eller CIDR-intervaller for reverse proxyer, hvis `X-Forwarded-For` / `X-Real-IP` IP-tilladelses-/afvisningslisten må stole på, ud over loopback, private netværksadresser og Cloudflare-edge-servere, som der altid er tillid til. Kun nødvendigt for en proxy på en anden offentlig adresse; ellers er det proxyens egen adresse, som filteret vurderer. | ### Kommandoer til generering @@ -216,38 +216,38 @@ OmniRoute bruger **SQLite** (via `better-sqlite3`) til al persistens. Disse vari ## 4. Sikkerhed og godkendelse -| Variabel | Standardværdi | Kildefil | Beskrivelse | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt, der kombineres med hardwareidentifikatorer til maskinfingeraftryk. Skift den for hver installation for at sikre isolation. | -| `OMNIROUTE_CLI_SALT` | _(ikke angivet = tilfældigt salt pr. installation, som gemmes i `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt til afledning af det lokale CLI-godkendelsestoken. Hvis denne værdi angives, roteres alle CLI-tokens på maskinen, og værdien har altid forrang for det gemte salt. Se `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Angiver flaget `Secure` på sessionscookies. **Skal være `true`**, når der køres bag HTTPS. | -| `REQUIRE_API_KEY` | `false` | API-middleware | Når værdien er `true`, skal alle proxyanmodninger til `/v1/*` indeholde en gyldig API-nøgle. **Dette flag beskytter ikke `GET /v1/models`**, som i stedet følger dashboardets loginindstilling (`requireAuthForModels`) — derfor betyder et `401`-svar fra `/v1/models` IKKE, at inferens er beskyttet. Se `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Tillader visning af komplette API-nøgleværdier i dashboardets brugergrænseflade. Kan konfigureres via dashboardets funktionsflag; udgør en sikkerhedsrisiko på delte instanser. | -| `NO_LOG_API_KEY_IDS` | _(tom)_ | `src/lib/compliance/index.ts` | Kommaseparerede API-nøgle-id'er, som ikke omfattes af logning af anmodninger (overholdelse af GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ikke angivet = ubegrænset)_ | `src/shared/utils/apiKeyPolicy.ts` | Reservebudget for anmodninger pr. dag, der anvendes på API-nøgler, hvis kolonne `rate_limits` er null. Ikke angivet eller tom: ingen implicit grænse (#2289, #11017). `0` betyder det samme (ubegrænset). Et positivt heltal N aktiverer N/dag, 5N/uge og 20N/måned. Ugyldige ikke-tomme værdier falder tilbage til de ældre grænser på 1000/dag, 5000/uge og 20000/måned. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimal tilladt størrelse på anmodningens brødtekst. Afviser nyttelaster, der overskrider denne grænse. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiske anmodningsbrødtekster på eller over denne tærskel optager den atomiske, proceslokale adgangslås for tunge anmodninger før JSON-fortolkning (BYTE-stien, inklusive `POST /v1/responses`). Samme [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-undtagelse ved tilstrækkelig ledig kapacitet som for strukturtunge anmodninger; stadig begrænset af `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), så [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ikke vender tilbage. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Hård grænse for chatruter, som håndhæves på baggrund af bytes læst under begrænset indlæsning, herunder forespørgsler med manglende, ugyldig eller misvisende `Content-Length`; overskridelse medfører `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ikke angivet — ingen grænse for antal forespørgsler)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** Denne ældre grænse for ANTAL forespørgsler er nu kun gældende, når den angives eksplicit. Når den ikke er angivet (standardindstillingen), styres adgang for tunge chatforespørgsler i stedet af `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — et automatisk afledt BYTE-budget, som dimensioneres ud fra processens reelle hukommelsesgrænse i **én proces** (én V8-heap). To overlappende `/v1/responses` med ~750.000 tokens får heaps på ~12 Gi til at afbryde (#7849) — en advarsel om **hukommelsesbudgettet**, ikke en fast produktgrænse på 2. En sund proces (heap under aflastningsforholdet) KAN tillade flere samtidige, lange `/v1/responses` via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Titusindvis af lange SSE-klienter (40–50) afhænger af heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ikke »maks. 2«. Hvis denne værdi hæves blindt for at »udnytte værten«, genintroduceres #7849. Multiplicer **heaps** med **N uafhængige `DATA_DIR`-mapper** (#11024); brug aldrig `replicas>1` på én SQLite-fil. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatisk afledt)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** Tilsidesættelse af det automatisk afledte bytebudget for indlæsning (25 % af den laveste V8-/cgroup-hukommelsesgrænse divideret med 8x midlertidig forstærkning). Afledte og eksplicitte værdier begrænses til 8 MiB–2 GiB. En body, der er større end det effektive budget, afvises straks med `413 body_exceeds_budget`; konkurrence mellem bodies, der hver især kan behandles, forbliver en `503`, som kan forsøges igen. 40–50 samtidige, langvarige SSE-klienter afhænger af dette budget + heap, ikke en fast grænse på »maks. 2«. Se `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` på `/api/monitoring/health` før justering. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Aflastningsforhold ved heappres (`heapUsed / heap_size_limit`) for adgang til tunge forespørgsler af typen BYTE og STRUCTURE (#10183, #10268, #10437). En samtidig tung forespørgsel ud over `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` afvises kun med en `503`, der kan forsøges igen, når heapen OGSÅ er på eller over dette forhold; på en sund heap tillades den i stedet via sund ekstrakapacitet. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standard `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begrænset ekstrakapacitet til den hurtige sti ved en sund heap (#10437) for **både** STRUCTURE og BYTE (`admitChatRequest`, inklusive bodies ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Uden denne grænse ville enhver forespørgsel med en travl, men sund heap omgå adgangskontrollen uden nogen øvre grænse. Når så mange samtidige leases er aktive via stien for en sund heap, fortsætter yderligere forespørgsler under belastning til den SAMME begrænsede vente-/aflastningssti, som bruges under reelt heappres. `0` deaktiverer omgåelsen helt. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Det antal beskeder, der klassificerer en chatforespørgsel som tung, selv når dens body ligger under bytegrænsen. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Det antal værktøjer, der klassificerer en chatforespørgsel som tung, selv når dens body ligger under bytegrænsen. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservativt tokenestimat baseret på strengstørrelse, som klassificerer en forespørgsel som tung; dette er en proxy for adgangsomkostninger, ikke udbyderens tokenisering til fakturering. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (deaktiveret) | `src/shared/middleware/chatBodyAdmission.ts` | Valgfri, eksplicit aktiveret grænse for chathistorik. Deaktiveret som standard: Et antal beskeder er en implementeringspolitik, ikke en universel egenskab ved en forespørgsel, og en grænse her afviser samtaler med en endelig `413`, før komprimeringspipelinen kan gøre dem håndterbare. Heapvækst begrænses af `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` og aflastning ved heappres. Angiv en positiv værdi i implementeringer med begrænset hukommelse, som kræver en fast øvre grænse; overskridelse medfører derefter en struktureret `413`, der angiver, at komprimering er påkrævet. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Hård grænse for et ikke-streamet upstream-svar, der bufferlagres fuldt ud i hukommelsen. Ved overskridelse annulleres upstream-læseren, og forespørgslen fejler hurtigt i stedet for at lade en ubegrænset streng vokse, indtil heapen er opbrugt. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalt antal wire-bytes, der videresendes fra upstream-svarheadere. Når budgettet overskrides, fjernes headere med lavere prioritet (f.eks. brugerdefinerede `x-codex-*`, `x-oai-request-id`) for at holde sig inden for almindelige reverse proxy-grænser for headere. Angiv en højere værdi for at videresende flere upstream-metadata på bekostning af større svarheadere. | -| `CORS_ORIGIN` | _(ikke angivet)_ | `src/server/cors/origins.ts` | Ældre CORS-tilladelsesliste for én origin. Foretræk `CORS_ALLOWED_ORIGINS` til nye installationer. CORS er kun beregnet til browserbaserede API-klienter på tværs af origins; godkendte skrivehandlinger i kontrolpanelet bruger i stedet forespørgsler fra samme origin samt sessionsbundet CSRF-beskyttelse. | -| `CORS_ALLOWED_ORIGINS` | _(ikke angivet)_ | `src/server/cors/origins.ts` | Kommasepareret CORS-tilladelsesliste. Der sendes intet jokertegn, medmindre `CORS_ALLOW_ALL=true` er eksplicit konfigureret. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Nødudvej, der kun er beregnet til udvikling, og som gengiver enhver browser-`Origin`. Aktivér den ikke i delte installationer eller produktionsinstallationer. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Bloker udbyderkald, der er rettet mod private IP-intervaller, loopback-intervaller eller link-local-intervaller. Deaktivér kun i isolerede testmiljøer. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Tillad udbyder-URL'er, der peger på private/lokale netværk (localhost, 192.168.x.x, 10.x.x.x osv.). **PÅKRÆVET for selvhostede udbydere** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Når værdien er `false`, afviser kontrolpanelet validering af lokale URL'er. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Tillad tilføjelse/validering af udbydere på lokale/private adresser (127.0.0.1, localhost, LAN, private intervaller) — begrænset til valideringsstien for udbydere. **Standardværdien er `true`** (lokalt først); angiv `false` for at håndhæve streng blokering, så kun offentlige adresser tillades. Cloud-metadataendpoints (169.254.169.254, metadata.google.internal) forbliver blokeret uanset indstillingen. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Lad `/v1/audio/*`-ruterne (transskriptioner, tale, oversættelser) bruge en OpenAI-kompatibel udbydernode, der hostes uden for localhost. Deaktiveret som standard — routing af lyd til en ekstern vært ændrer udgående identitet og skal være en eksplicit operatørbeslutning. Loopback-/private noder (localhost, 127.0.0.1, 172.16-31.x) er altid tilladt og påvirkes ikke. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Lad `POST /v1/rerank` (og loopback-trinnet til omrangering i hukommelsesmotoren) bruge en OpenAI-kompatibel udbydernode, der hostes uden for localhost — en LAN-maskine eller Tailscale-peer, som kører TEI, Infinity, vLLM osv. Deaktiveret som standard — routing til en ekstern vært ændrer udgående identitet og skal være en eksplicit operatørbeslutning. Loopback-noder (localhost, 127.0.0.1, 172.16-31.x) er altid tilladt og påvirkes ikke. Eksterne noder skal også overholde politikken for udbyderes udgående URL'er (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); der routes aldrig til cloud-metadatahosts. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Når OIDC er aktiveret, deaktiveres adgangskodelogin, så brugere kun kan godkende sig via OIDC Single Sign-On. Det korte alias `OIDC_DISABLE_PASSWORD_LOGIN` accepteres også; kontrolpanelets funktionsflag med samme nøgle har forrang. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kort alias for `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variabel | Standardværdi | Kildefil | Beskrivelse | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt, der kombineres med hardwareidentifikatorer til maskinfingeraftryk. Skift den for hver installation for at sikre isolation. | +| `OMNIROUTE_CLI_SALT` | _(ikke angivet = tilfældigt salt pr. installation, som gemmes i `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt til afledning af det lokale CLI-godkendelsestoken. Hvis denne værdi angives, roteres alle CLI-tokens på maskinen, og værdien har altid forrang for det gemte salt. Se `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Angiver flaget `Secure` på sessionscookies. **Skal være `true`**, når der køres bag HTTPS. | +| `REQUIRE_API_KEY` | `false` | API-middleware | Når værdien er `true`, skal alle proxyanmodninger til `/v1/*` indeholde en gyldig API-nøgle. **Dette flag beskytter ikke `GET /v1/models`**, som i stedet følger dashboardets loginindstilling (`requireAuthForModels`) — derfor betyder et `401`-svar fra `/v1/models` IKKE, at inferens er beskyttet. Se `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Tillader visning af komplette API-nøgleværdier i dashboardets brugergrænseflade. Kan konfigureres via dashboardets funktionsflag; udgør en sikkerhedsrisiko på delte instanser. | +| `NO_LOG_API_KEY_IDS` | _(tom)_ | `src/lib/compliance/index.ts` | Kommaseparerede API-nøgle-id'er, som ikke omfattes af logning af anmodninger (overholdelse af GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ikke angivet = ubegrænset)_ | `src/shared/utils/apiKeyPolicy.ts` | Reservebudget for anmodninger pr. dag, der anvendes på API-nøgler, hvis kolonne `rate_limits` er null. Ikke angivet eller tom: ingen implicit grænse (#2289, #11017). `0` betyder det samme (ubegrænset). Et positivt heltal N aktiverer N/dag, 5N/uge og 20N/måned. Ugyldige ikke-tomme værdier falder tilbage til de ældre grænser på 1000/dag, 5000/uge og 20000/måned. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimal tilladt størrelse på anmodningens brødtekst. Afviser nyttelaster, der overskrider denne grænse. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiske anmodningsbrødtekster på eller over denne tærskel optager den atomiske, proceslokale adgangslås for tunge anmodninger før JSON-fortolkning (BYTE-stien, inklusive `POST /v1/responses`). Samme [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-undtagelse ved tilstrækkelig ledig kapacitet som for strukturtunge anmodninger; stadig begrænset af `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), så [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ikke vender tilbage. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Hård grænse for chatruter, som håndhæves på baggrund af bytes læst under begrænset indlæsning, herunder forespørgsler med manglende, ugyldig eller misvisende `Content-Length`; overskridelse medfører `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ikke angivet — ingen grænse for antal forespørgsler)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** Denne ældre grænse for ANTAL forespørgsler er nu kun gældende, når den angives eksplicit. Når den ikke er angivet (standardindstillingen), styres adgang for tunge chatforespørgsler i stedet af `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — et automatisk afledt BYTE-budget, som dimensioneres ud fra processens reelle hukommelsesgrænse i **én proces** (én V8-heap). To overlappende `/v1/responses` med ~750.000 tokens får heaps på ~12 Gi til at afbryde (#7849) — en advarsel om **hukommelsesbudgettet**, ikke en fast produktgrænse på 2. En sund proces (heap under aflastningsforholdet) KAN tillade flere samtidige, lange `/v1/responses` via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Titusindvis af lange SSE-klienter (40–50) afhænger af heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ikke »maks. 2«. Hvis denne værdi hæves blindt for at »udnytte værten«, genintroduceres #7849. Multiplicer **heaps** med **N uafhængige `DATA_DIR`-mapper** (#11024); brug aldrig `replicas>1` på én SQLite-fil. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatisk afledt)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** Tilsidesættelse af det automatisk afledte bytebudget for indlæsning (25 % af den laveste V8-/cgroup-hukommelsesgrænse divideret med 8x midlertidig forstærkning). Afledte og eksplicitte værdier begrænses til 8 MiB–2 GiB. En body, der er større end det effektive budget, afvises straks med `413 body_exceeds_budget`; konkurrence mellem bodies, der hver især kan behandles, forbliver en `503`, som kan forsøges igen. 40–50 samtidige, langvarige SSE-klienter afhænger af dette budget + heap, ikke en fast grænse på »maks. 2«. Se `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` på `/api/monitoring/health` før justering. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Aflastningsforhold ved heappres (`heapUsed / heap_size_limit`) for adgang til tunge forespørgsler af typen BYTE og STRUCTURE (#10183, #10268, #10437). En samtidig tung forespørgsel ud over `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` afvises kun med en `503`, der kan forsøges igen, når heapen OGSÅ er på eller over dette forhold; på en sund heap tillades den i stedet via sund ekstrakapacitet. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standard `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begrænset ekstrakapacitet til den hurtige sti ved en sund heap (#10437) for **både** STRUCTURE og BYTE (`admitChatRequest`, inklusive bodies ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Uden denne grænse ville enhver forespørgsel med en travl, men sund heap omgå adgangskontrollen uden nogen øvre grænse. Når så mange samtidige leases er aktive via stien for en sund heap, fortsætter yderligere forespørgsler under belastning til den SAMME begrænsede vente-/aflastningssti, som bruges under reelt heappres. `0` deaktiverer omgåelsen helt. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Det antal beskeder, der klassificerer en chatforespørgsel som tung, selv når dens body ligger under bytegrænsen. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Det antal værktøjer, der klassificerer en chatforespørgsel som tung, selv når dens body ligger under bytegrænsen. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservativt tokenestimat baseret på strengstørrelse, som klassificerer en forespørgsel som tung; dette er en proxy for adgangsomkostninger, ikke udbyderens tokenisering til fakturering. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (deaktiveret) | `src/shared/middleware/chatBodyAdmission.ts` | Valgfri, eksplicit aktiveret grænse for chathistorik. Deaktiveret som standard: Et antal beskeder er en implementeringspolitik, ikke en universel egenskab ved en forespørgsel, og en grænse her afviser samtaler med en endelig `413`, før komprimeringspipelinen kan gøre dem håndterbare. Heapvækst begrænses af `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` og aflastning ved heappres. Angiv en positiv værdi i implementeringer med begrænset hukommelse, som kræver en fast øvre grænse; overskridelse medfører derefter en struktureret `413`, der angiver, at komprimering er påkrævet. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Hård grænse for et ikke-streamet upstream-svar, der bufferlagres fuldt ud i hukommelsen. Ved overskridelse annulleres upstream-læseren, og forespørgslen fejler hurtigt i stedet for at lade en ubegrænset streng vokse, indtil heapen er opbrugt. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalt antal wire-bytes, der videresendes fra upstream-svarheadere. Når budgettet overskrides, fjernes headere med lavere prioritet (f.eks. brugerdefinerede `x-codex-*`, `x-oai-request-id`) for at holde sig inden for almindelige reverse proxy-grænser for headere. Angiv en højere værdi for at videresende flere upstream-metadata på bekostning af større svarheadere. | +| `CORS_ORIGIN` | _(ikke angivet)_ | `src/server/cors/origins.ts` | Ældre CORS-tilladelsesliste for én origin. Foretræk `CORS_ALLOWED_ORIGINS` til nye installationer. CORS er kun beregnet til browserbaserede API-klienter på tværs af origins; godkendte skrivehandlinger i kontrolpanelet bruger i stedet forespørgsler fra samme origin samt sessionsbundet CSRF-beskyttelse. | +| `CORS_ALLOWED_ORIGINS` | _(ikke angivet)_ | `src/server/cors/origins.ts` | Kommasepareret CORS-tilladelsesliste. Der sendes intet jokertegn, medmindre `CORS_ALLOW_ALL=true` er eksplicit konfigureret. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Nødudvej, der kun er beregnet til udvikling, og som gengiver enhver browser-`Origin`. Aktivér den ikke i delte installationer eller produktionsinstallationer. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ældre alias. Dets kontakt i kontrolpanelet (en DB-override) læses før denne miljøværdi; `false`, `0`, `no` eller `off` i en af dem slår værtskontrollerne fra på samme måde, som `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` gør, selv når kontakten til det flag i kontrolpanelet er gemt som slået fra; andre værdier overlader vagten til de to flag nedenfor. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Slår vagtens værtskontroller for udgående URL'er fra — herunder blokering af cloud-metadata (hele 169.254.0.0/16, metadata.google.internal) — på de udbyderstier, der følger vagttilstanden: URL-validering, modelopdagelse, basis-URL'er for udbydernoder (herunder eksterne omrangeringsnoder) og proxy-fallback-testen, samt andre udgående stier tæt på udbyderen, såsom hentning af eksterne billeder/medier. Den tillader også private webhook-mål, bortset fra cloud-metadata-/link-local-endpoints, som forbliver blokeret, selv når flaget er slået til. Chatanmodninger, der sendes gennem `BaseExecutor.execute()`, bevarer deres cloud-metadatakontrol. Lokale udbydere fungerer med standardindstillingerne: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (standard `true`) tillader allerede lokale og LAN-URL'er på validerings-, opdagelses- og udbydernodestierne; proxy-fallback-testen og private webhook-mål konsulterer kun dette flag og blokerer lokale/LAN-værter, mens det er slået fra. Tilsidesætter `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. En værdi, der er gemt fra kontakten i kontrolpanelet, har forrang frem for denne variabel. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Tillad udbyder-URL'er på lokale/private adresser (127.0.0.1, localhost, LAN, private intervaller) til udbydervalidering, modelopdagelse, chat og de andre udgående stier tæt på udbyderen, som vagttilstanden styrer. **Standardværdien er `true`** (lokalt først): vagten blokerer da cloud-metadataendpoints — hele 169.254.0.0/16 plus de kendte metadata-værtsnavne (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Angiv `false` for kun-offentlig tilstand, som også blokerer private adresser og loopback-værter. Vagten kontrollerer værtsnavnet eller IP-litteralen, som det er skrevet, før ethvert DNS-opslag; i kun-offentlig tilstand opløser safe-fetch-wrapperen desuden værtsnavnet og afviser alle private svar. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` slår disse kontroller fra på de stier, som dens række nævner. Indbyggede lokale udbydere som LM Studio, Ollama, vLLM, Llamafile og Triton springer alle værtskontroller over ved nøglevalidering og chat. En værdi, der er gemt fra kontakten i kontrolpanelet, har forrang frem for denne variabel. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Lad `/v1/audio/*`-ruterne (transskriptioner, tale, oversættelser) bruge en OpenAI-kompatibel udbydernode, der hostes uden for localhost. Deaktiveret som standard — routing af lyd til en ekstern vært ændrer udgående identitet og skal være en eksplicit operatørbeslutning. Loopback-/private noder (localhost, 127.0.0.1, 172.16-31.x) er altid tilladt og påvirkes ikke. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Lad `POST /v1/rerank` (og loopback-trinnet til omrangering i hukommelsesmotoren) bruge en OpenAI-kompatibel udbydernode, der hostes uden for localhost — en LAN-maskine eller Tailscale-peer, som kører TEI, Infinity, vLLM osv. Deaktiveret som standard — routing til en ekstern vært ændrer udgående identitet og skal være en eksplicit operatørbeslutning. Loopback-noder (localhost, 127.0.0.1, 172.16-31.x) er altid tilladt og påvirkes ikke. En ekstern nodes basis-URL skal også overholde politikken for udbyderes udgående URL'er (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Når OIDC er aktiveret, deaktiveres adgangskodelogin, så brugere kun kan godkende sig via OIDC Single Sign-On. Det korte alias `OIDC_DISABLE_PASSWORD_LOGIN` accepteres også; kontrolpanelets funktionsflag med samme nøgle har forrang. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kort alias for `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Tjekliste til sikkerhedshærdning diff --git a/docs/i18n/de/CONTRIBUTING.md b/docs/i18n/de/CONTRIBUTING.md index 8f11eaf2b369..6d34ed40e377 100644 --- a/docs/i18n/de/CONTRIBUTING.md +++ b/docs/i18n/de/CONTRIBUTING.md @@ -389,20 +389,20 @@ Unit-Tests in `tests/unit/` schreiben, die mindestens Folgendes abdecken: ## Pull-Request-Checkliste -- [ ] Tests erfolgreich (`npm test`) -- [ ] Linting erfolgreich (`npm run lint`) -- [ ] Build erfolgreich (`npm run build`) +- [ ] Tests sind erfolgreich (`npm test`) +- [ ] Linting ist erfolgreich (`npm run lint`) +- [ ] Build ist erfolgreich (`npm run build`) - [ ] TypeScript-Typen für neue öffentliche Funktionen und Schnittstellen hinzugefügt - [ ] Keine hartcodierten Geheimnisse oder Fallback-Werte -- [ ] Öffentliche Upstream-Zugangsdaten über `resolvePublicCred()` eingebettet (siehe [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), niemals als Literale -- [ ] Fehlerantworten werden über `buildErrorBody()` / `sanitizeErrorMessage()` geleitet – keine unverarbeiteten Stacktraces in Antworttexten (siehe [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Öffentliche Zugangsdaten von Upstream-Diensten über `resolvePublicCred()` eingebettet (siehe [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), niemals als Literale +- [ ] Fehlerantworten werden über `buildErrorBody()` / `sanitizeErrorMessage()` geleitet — keine unbearbeiteten Stacktraces in Antwortinhalten (siehe [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Shell-Befehle (`exec` / `spawn`) übergeben Laufzeitwerte über `env`, nicht über String-Interpolation - [ ] Alle Eingaben mit Zod-Schemas validiert -- [ ] Changelog-**Fragment** für benutzersichtbare Änderungen unter `changelog.d/{features|fixes|maintenance}/-.md` hinzugefügt (siehe [`changelog.d/README.md`](./changelog.d/README.md)) – `CHANGELOG.md` **nicht** direkt bearbeiten; Fragmente werden zum Veröffentlichungszeitpunkt zusammengeführt und verursachen niemals Konflikte zwischen PRs +- [ ] Changelog-**Fragment** für benutzersichtbare Änderungen unter `changelog.d/{features|fixes|maintenance}/-.md` hinzugefügt (siehe [`changelog.d/README.md`](./changelog.d/README.md)) — `CHANGELOG.md` **nicht** direkt bearbeiten; Fragmente werden zum Veröffentlichungszeitpunkt zusammengeführt und verursachen niemals Konflikte zwischen PRs - [ ] Dokumentation aktualisiert (falls zutreffend) -- [ ] Keine neuen CodeQL- / Secret-Scanning-Warnungen ausgelöst oder jede einzelne mit einer technischen Begründung unter Verweis auf das relevante Dokument unter `docs/security/` verworfen -- [ ] Routen, die untergeordnete Prozesse starten (`/api/mcp/`, `/api/cli-tools/runtime/`), in `src/server/authz/routeGuard.ts` als `isLocalOnlyPath()` klassifiziert – siehe [Strikte Regel Nr. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Keine `Co-Authored-By`-Trailer in Commit-Nachrichten – Commits dürfen ausschließlich unter der Git-Identität des Repository-Eigentümers erscheinen (Strikte Regel Nr. 16) +- [ ] Keine neuen CodeQL-/Secret-Scanning-Warnungen ausgelöst oder jede Warnung mit einer technischen Begründung unter Verweis auf das relevante Dokument unter `docs/security/` verworfen +- [ ] Routen, die untergeordnete Prozesse starten (`/api/mcp/`, `/api/cli-tools/runtime/`), in `src/server/authz/routeGuard.ts` als `isLocalOnlyPath()` klassifiziert — siehe [Strikte Regel Nr. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Keine `Co-authored-by`-Trailer für KI/Bots in Commit-Nachrichten (strikte Regel Nr. 16) — menschliche Mitwirkende, deren Arbeit wiederverwendet wird, werden mit standardmäßigen `Co-authored-by: Name `-Trailern gewürdigt --- diff --git a/docs/i18n/de/docs/architecture/QUALITY_GATES.md b/docs/i18n/de/docs/architecture/QUALITY_GATES.md index 09ce2cf60f76..686e862f9a56 100644 --- a/docs/i18n/de/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/de/docs/architecture/QUALITY_GATES.md @@ -19,146 +19,153 @@ Replikationsplan desselben Systems finden Sie im ### Kandidatenzulassung -Die Workflows für CI- und Quality Gates geben jeweils ein stabiles Ergebnis aus: `Gate / CI` und +Die Workflows „CI“ und „Quality Gates“ geben jeweils ein stabiles Ergebnis aus: `Gate / CI` und `Gate / Quality`. Ihre versionierte Zulassungsrichtlinie führt jeden vorgelagerten Job -als erforderlich oder beratend auf. Ein zutreffender erforderlicher Job muss erfolgreich sein: -Fehlende, abgebrochene, übersprungene, ausstehende und unbekannte Ergebnisse können keinen PASS-Status begründen. Eine gültige Klassifizierung als „nur Dokumentation“ oder „nur Katalog“ kann einen Code-Ausführungspfad unzutreffend machen; -ein PR im Entwurfsstatus wird nicht als Kandidat akzeptiert. Ein `hotfix`-Label setzt die Nachweispflicht nicht außer Kraft. - -Beide Workflows decken PRs und Pushes auf main-/release-Branches, manuelle Auslösungen und -Merge-Group-Ereignisse ab. Bei Pushes, manuellen Auslösungen und Merge Groups wird die vollständige Auswahl ausgeführt. Forks -und Merge Groups verwenden gehostete Runner für Jobs, die andernfalls selbst gehostete -Runner auswählen; vor dem Rollout muss eine ausreichende gehostete Kapazität sichergestellt werden. +als erforderlich oder optional auf. Ein zutreffender erforderlicher Job muss erfolgreich sein: +Fehlende, abgebrochene, übersprungene, ausstehende und unbekannte Ergebnisse können keinen PASS +begründen. Eine gültige Klassifizierung als reine Dokumentations- oder Katalogänderung kann dazu +führen, dass ein Code-Ausführungspfad nicht anwendbar ist; ein PR-Entwurf ist kein akzeptierter +Kandidat. Ein `hotfix`-Label hebt die Nachweispflicht nicht auf. + +Beide Workflows decken PRs und Pushes auf main-/release-Branches, manuelle Ausführungen und +Merge-Group-Ereignisse ab. Bei Pushes, manuellen Ausführungen und Merge Groups wird die vollständige +Auswahl ausgeführt. Forks und Merge Groups verwenden gehostete Runner für Jobs, die andernfalls +selbst gehostete Runner auswählen; vor dem Rollout muss eine ausreichende gehostete Kapazität +verifiziert werden. Jeder JSON-Beleg identifiziert den ausgecheckten SHA, den Workflow-Lauf und den Versuch. -Die CLI weist eine Abweichung zwischen Checkout- und Ereignis-SHA zurück. Workflow-Tests binden die Richtlinienzugehörigkeit -an die `needs`-Liste des Ergebnis-Jobs, sodass ein neuer oder entfernter Ausführungspfad nicht unbemerkt verschwinden kann. -Die Belege decken den jeweils eigenen Workflow ab, nicht die Veröffentlichung, das Deployment oder die internen Abläufe -eines bestehenden beratenden Scanners. Die Aktivierung beider Prüfungsnamen in Branch-Regeln ist eine -separate administrative Änderung; das Hinzufügen dieser Jobs schützt einen Branch nicht automatisch. +Die CLI weist eine Abweichung zwischen Checkout- und Ereignis-SHA zurück. Workflow-Tests binden +die Richtlinienzugehörigkeit an die `needs`-Liste des Ergebnis-Jobs, sodass ein neuer oder entfernter +Ausführungspfad nicht unbemerkt verschwinden kann. Die Belege decken den eigenen Workflow ab, nicht +die Veröffentlichung, die Bereitstellung oder die Interna eines bestehenden optionalen Scanners. +Die Aktivierung beider Prüfnamen in Branch-Regeln ist eine separate administrative Änderung; +das Hinzufügen dieser Jobs schützt einen Branch nicht automatisch. ### Inventar der statischen Scans Das versionierte npm-Alias-Inventar und die Zugehörigkeit zu statischen Scans befinden sich in `config/quality/gate-manifest.json`. Führen Sie `npm run check:gate-manifest` aus, um -Skriptnamen und exakte Befehle anhand von `package.json` zu validieren; Ergänzungen, Entfernungen und -Befehlsabweichungen führen sowohl im lokalen Hook als auch in den Jobs zur Änderungsklassifizierung in CI zu einem Fehler. -Ein Alias ist weder ein Workflow-Job noch eine Matrixinstanz oder ein Testfall: Diese Anzahlen dürfen -nicht als austauschbar dargestellt werden. +Skriptnamen und exakte Befehle anhand von `package.json` zu validieren; Ergänzungen, Entfernungen +und Befehlsabweichungen lassen sowohl den lokalen Hook als auch die Jobs zur Änderungsklassifizierung +in CI fehlschlagen. Ein Alias ist weder ein Workflow-Job noch eine Matrixinstanz oder ein Testfall: +Diese Anzahlen dürfen nicht als austauschbar dargestellt werden. Verwenden Sie `npm run quality:scan -- --list` oder `npm run quality:scan:fast -- --list`, um die ausgewählten Aliasse zu prüfen, ohne sie auszuführen. Der Runner ruft den -npm-Einstiegspunkt auf, sodass dessen Laufzeitumgebung (einschließlich Bun, sofern konfiguriert) beibehalten wird. -Das Manifest erfasst Aliasse außerhalb dieser Profile als separat aufgerufen, und -Wartungsbefehle sind in schreibgeschützten Scanprofilen verboten. +npm-Einstiegspunkt auf, sodass dessen Laufzeitumgebung (einschließlich Bun, sofern konfiguriert) +beibehalten wird. Das Manifest erfasst Aliasse außerhalb dieser Profile als separat aufgerufen, +und Wartungsbefehle sind in schreibgeschützten Scanprofilen nicht zulässig. -Diese Profile decken nur den statischen Scan ab. Sie bestätigen weder Produkttests, -Abdeckung, Paketierung, externe Prüfungen noch die vollständige Release-Zulassung eines Kandidaten. -Die Workflow-Zulassung verwendet die verknüpften Dateien `config/quality/admission-policy.json` und -`scripts/quality/admission-verdict.mjs`. Profile für Release-Beobachter bleiben separat; -prüfen Sie deren zutreffende Prüfungen und Belege unabhängig. Das nachstehende -Textinventar dient als Referenz und ist kein Nachweis dafür, dass ein Gate tatsächlich ausgeführt wurde. +Diese Profile decken nur den statischen Scan ab. Sie zertifizieren weder Produkttests, +Testabdeckung, Paketierung, externe Prüfungen noch die vollständige Release-Zulassung eines +Kandidaten. Die Workflow-Zulassung verwendet die verknüpfte Datei +`config/quality/admission-policy.json` und `scripts/quality/admission-verdict.mjs`. +Release-Observer-Profile bleiben davon getrennt; prüfen Sie ihre zutreffenden Prüfungen und +Belege unabhängig voneinander. Das nachfolgende textuelle Inventar dient als Referenz und ist +kein Nachweis dafür, dass ein Gate tatsächlich ausgeführt wurde. -Skripte befinden sich unter `scripts/check/` (Richtlinien-Gates) und `scripts/quality/` (Ratchet-Engine). -Die maßgebliche CI-Quelle ist `.github/workflows/ci.yml`. +Skripte befinden sich unter `scripts/check/` (Richtlinien-Gates) und `scripts/quality/` +(Ratchet-Engine). Die maßgebliche CI-Quelle ist `.github/workflows/ci.yml`. -### Schneller Ausführungspfad für Release-PRs (`quality.yml`) +### Schneller Pfad für Release-PRs (`quality.yml`) `.github/workflows/quality.yml` ergänzt CI bei main-/release-PRs, Pushes auf geschützte Branches, -manuellen Auslösungen und Merge Groups. PRs verwenden pfadgefilterte Schnellprüfungen. Der dauerhaft -deaktivierte duplizierte Build wurde entfernt; die tatsächlichen Build-, Paketierungs- und Boot-Prüfungen verbleiben in CI. - -| Job | Umfang | Blockierend | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | -| `Docs Gates (fast-path)` | Dokumentations-/Code-PRs; API-Dokumentationsreferenzen und vollständige Dokumentation | Ja | -| `Fast Quality Gates` | Code-PRs; statische Prüfungen, Typprüfung, Dashboard-Typprüfung, betroffene Unit-Tests | Ja | -| `Forgotten sibling tests` | Code-PRs; geänderte Module werden bis zu statischen Konsumenten und potenziellen Geschwistertests nachverfolgt; Barrel- und dynamische Importpfade werden als beratende Diagnosen gemeldet, mit referenzierten Ausnahmen aus der Zulassungsliste | **Beratend** | -| `Vitest (fast-path)` | Code-PRs; schnelle Vitest-Suite | Ja | -| `Unit Tests fast-path` | Code-PRs; Unit-Suite mit 4 Shards | Ja | -| `No new ESLint warnings` | Code-PRs; unterdrückungsbewusste Lint-Schutzprüfung | Ja, einschließlich Forks | -| `Merge integrity (changelog + generated skills)` | PRs ohne Entwurfsstatus; Synchronisierung des Changelogs und der generierten Skills | Ja, einschließlich Forks | - -#### Bericht zu vergessenen Geschwistertests - -`npm run check:forgotten-sibling-tests` verwendet den Import-Resolver wieder, der der Zuordnung der Testauswirkungen zugrunde liegt. -Für jedes geänderte Produktionsmodul meldet er deterministische Ketten vom Typ -`geändertes Modul/Symbol -> statischer Konsument -> potenzieller Geschwistertest`, wenn der potenzielle -Test nicht im Diff des Pull Requests enthalten ist. Die Markdown-Zusammenfassung und das JSON-Ergebnis werden als -Workflow-Artefakt `forgotten-sibling-tests` zur Kalibrierung vor einem möglichen blockierenden Rollout aufbewahrt. - -Barrel-Re-Exports und dynamische Importe dienen ausschließlich der Auflösungsdiagnose; sie erzeugen niemals einen +manuellen Ausführungen und Merge Groups. PRs verwenden pfadgefilterte Schnellprüfungen. Der dauerhaft +deaktivierte doppelte Build wurde entfernt; die tatsächlichen Build-, Paketierungs- und +Startprüfungen verbleiben in CI. + +| Job | Umfang | Blockierend | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | +| `Docs Gates (fast-path)` | Dokumentations-/Code-PRs; API-Dokumentationsreferenzen und die gesamte Dokumentation | Ja | +| `Fast Quality Gates` | Code-PRs; statische Prüfungen, Typprüfung, Dashboard-Typprüfung, betroffene Unit-Tests | Ja | +| `Forgotten sibling tests` | Code-PRs; geänderte Module werden zu statischen Konsumenten und potenziellen zugehörigen Tests zurückverfolgt; Barrel- und dynamische Importpfade werden als optionale Diagnosen gemeldet, einschließlich referenzierter Ausnahmen in der Positivliste | **Optional** | +| `Vitest (fast-path)` | Code-PRs; schnelle Vitest-Suite | Ja | +| `Unit Tests fast-path` | Code-PRs; Unit-Test-Suite mit 4 Shards | Ja | +| `No new ESLint warnings` | Code-PRs; unterdrückungsbewusste Lint-Schutzprüfung | Ja, einschließlich Forks | +| `Merge integrity (changelog + generated skills)` | PRs, die keine Entwürfe sind; Synchronisierung von Changelog und generierten Skills | Ja, einschließlich Forks | + +#### Bericht zu vergessenen zugehörigen Tests + +`npm run check:forgotten-sibling-tests` verwendet den Import-Resolver hinter der +Testauswirkungszuordnung erneut. Für jedes geänderte Produktionsmodul meldet er deterministische +Ketten der Form `changed module/symbol -> static consumer -> candidate sibling test`, wenn der +potenzielle Test im Diff des Pull Requests fehlt. Die Markdown-Zusammenfassung und das JSON-Ergebnis +werden zur Kalibrierung vor einem blockierenden Rollout als Workflow-Artefakt +`forgotten-sibling-tests` aufbewahrt. + +Barrel-Re-Exporte und dynamische Importe dienen ausschließlich der Auflösungsdiagnose; sie erzeugen niemals einen blockierenden Befund. Geprüfte Ausnahmen befinden sich in -`config/quality/forgotten-sibling-allowlist.json`. Jeder Eintrag muss den Consumer und den infrage kommenden -Test benennen, eine konkrete Begründung enthalten und auf ein GitHub-Issue oder einen Pull Request verweisen. Fehlerhafte Einträge führen -standardmäßig zum Fehlschlagen. Ausnahmen können weder einen gelöschten infrage kommenden Test noch einen Diff unterdrücken, der `.skip`/`.todo` hinzufügt; -das Abschwächen von Assertions und andere Verschleierungen fallen weiterhin in den Zuständigkeitsbereich des unabhängig blockierenden +`config/quality/forgotten-sibling-allowlist.json`. Jeder Eintrag muss den Consumer und den +Kandidaten-Test benennen, eine konkrete Begründung enthalten und auf ein GitHub-Issue oder einen Pull Request verweisen. Fehlerhafte Einträge führen +standardmäßig zu einem Fehler. Ausnahmen können weder einen gelöschten Kandidaten-Test noch einen Diff unterdrücken, der `.skip`/`.todo` +hinzufügt; das Abschwächen von Assertions und andere Verschleierungen fallen weiterhin in den Zuständigkeitsbereich des unabhängig blockierenden `check:test-masking`-Gates. ### Job: `lint` -Wird bei jedem PR für `main` ausgeführt. Blockiert den Merge bei einem Fehler. - -| Skript (`npm run ...`) | Validiert | Blockierend | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | Die Node.js-Version liegt innerhalb des unterstützten Bereichs | Ja | -| `check:cycles` | Zirkuläre Importe — alle Module unter `src/` + `open-sse/` | Ja | -| `check:route-validation:t06` | Zod-Schemas sind für alle Routen vorhanden (Tier-6-Richtlinie) | Ja | -| `check:any-budget:t11` | Die Anzahl von `@ts-expect-error // any` überschreitet das Budget nicht (Tier-11-Catraca) | Ja | -| `check:provider-consistency` | Jeder Provider in `providers.ts` hat einen entsprechenden Eintrag in `providerRegistry.ts` (und umgekehrt, innerhalb der Allowlist) | Ja | -| `check:model-lifecycle` | Die drei manuell gepflegten Routing-Tabellen bleiben mit dem eingecheckten Lifecycle-Snapshot (#11503) konsistent: `FITNESS_TABLE` (`taskFitness.ts`) bewertet keine außer Betrieb genommene ID, die `REGISTRY` routen kann; jedes Ziel von `BUILT_IN_ALIASES` ist in `REGISTRY` vorhanden und fehlt im Snapshot der außer Betrieb genommenen IDs; jede außer Betrieb genommene ID, die noch in `REGISTRY` enthalten ist, wird weitergeleitet oder ist in `allowedRetiredInCatalog` aufgeführt; und weder Quelle noch Ziel in `DEFAULT_DEGRADATION_MAP` ist in diesem Snapshot als außer Betrieb genommen verzeichnet. Dies beweist nicht, dass ein Modell derzeit von einem aktiven Upstream bereitgestellt wird. Offline — Vergleich mit `config/quality/model-lifecycle.json`, manuell aktualisiert mit `npm run quality:refresh-model-lifecycle` (Netzwerk; nicht in CI eingebunden). `allowedRetiredInCatalog` ist eine Burn-down-Ratsche: Einen Eintrag nur zusammen mit einem Tracking-Issue hinzufügen. | Ja | -| `check:fetch-targets` | Jeder Aufruf von `fetch("/api/...")` im clientseitigen `src/` wird zu einer tatsächlich vorhandenen `route.ts` aufgelöst | Ja | -| `check:deps` | Alle mit `npm install` installierbaren Abhängigkeiten aus jeder `package.json` im Repository sind in `dependency-allowlist.json` enthalten; neue nicht fixierte oder durch Slopsquatting entstandene Pakete werden markiert | Ja | -| `audit:deps` | `npm audit` (Root + Electron) — keine Warnungen mit hohem/kritischem Schweregrad (überschneidet sich mit OSV `check:vuln-ratchet`; siehe Rationalisierungs-Backlog) | Ja | -| `check:lockfile` | Integrität von `package-lock.json` — HTTPS-Registry, Integritäts-Hashes, keine Host-Überschreibungen | Ja | -| `check:licenses` | SPDX-Lizenz-Zulassungsliste für Produktionsabhängigkeiten | Ja | -| `check:tracked-artifacts` | Keine Build-Artefakte / eingecheckten `node_modules`-Symlinks (wird auch beim Husky-Pre-Commit ausgeführt; Pre-Push ist absichtlich schlank — #6716) | Ja | -| `check:ai-attribution` | Kein AI-/Bot-`Co-Authored-By`-Trailer oder Hinweis auf AI-Generierung in PR-Commits, Titel oder Beschreibung — Strikte Regel Nr. 16 (in der Fast-Gates-Schleife von `quality.yml` für PR→`release/**` — liest die Event-Nutzlast, außerhalb von PRs keine Aktion — sowie als reiner PR-Schritt im Lint-Job von `ci.yml` für PR→`main`; außerdem der Husky-Hook `commit-msg`; menschliche Co-Autoren sind zulässig; #14436) | -| `check:vitest-exclusions` | Jeder Vitest-Ausschluss nennt ein Tracking-Issue und ist in `config/quality/vitest-exclusions.json` aufgeführt (#13204) | Ja | -| `check:file-size` | Keine Quelldatei überschreitet die Obergrenze der jeweiligen Dateiendung (Ratchet-Prinzip: unveränderliche große Dateien in der Liste `frozen`) | Ja | -| `check:error-helper` | Fehlerantworten in Executors/Handlern verwenden `buildErrorBody()` / `sanitizeErrorMessage()` (Strikte Regel Nr. 12) | Ja | -| `check:migration-numbering` | Migrations-SQL-Dateien sind fortlaufend nummeriert, ohne Lücken oder Duplikate | Ja | -| `check:public-creds` | Keine fest eingetragenen OAuth-`client_id`/`client_secret`- oder Firebase-Web-Schlüssel außerhalb von `publicCreds.ts` (Strikte Regel Nr. 11) | Ja | -| `check:db-rules` | Kein direktes SQL außerhalb der Module in `src/lib/db/`; keine Barrel-Imports aus `localDb.ts` (Strikte Regeln Nr. 2/Nr. 5) | Ja | -| `check:known-symbols` | In ihren Dispatch-Tabellen registrierte Provider-Executors, Routing-Strategien und Übersetzer stimmen mit den Dateien auf dem Datenträger überein – keine verwaisten oder nicht deklarierten Symbole | Ja | -| `check:route-guard-membership` | Jede Route, die einen untergeordneten Prozess erzeugt, ist durch `isLocalOnlyPath()` klassifiziert (Strikte Regeln Nr. 15/Nr. 17) | Ja | -| `check:test-discovery` | Jede `*.test.ts`- / `*.spec.ts`-Datei im Repository wird von mindestens einem Test-Runner erfasst (Ratschenprinzip: Die Liste verwaister Dateien in `test-discovery-baseline.json` kann nur kleiner werden) | Ja | -| `check:agent-skills-sync` | Generierte Agent-Skills-Artefakte stimmen mit ihrem Quellkatalog überein (keine Abweichungen) | -| `check:provider-asset-provenance` | Anbieterlogos/-Assets verfügen über einen dokumentierten Herkunftsnachweis | -| `lint:json` | JSON-Konfigurationsdateien lassen sich parsen und erfüllen die Lint-Regeln des Repositorys | -| `typecheck:core` | TypeScript-Kompilierung ohne Fehler (nur informative Warnungen) | Ja | -| `typecheck:noimplicit:core` | Striktes `noImplicitAny` — zukunftsorientiert; viele bereits vorhandene Aufrufstellen benötigen noch Annotationen | **Informativ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | Auf `src/app/(dashboard)/**` beschränktes `tsc` (#7033) — die kuratierte Positivliste von `typecheck:core` mit 27 Dateien enthält keine Dashboard-TSX-Dateien, und `next build` führt dafür ebenfalls nie eine Typprüfung durch (`next.config.mjs` setzt `ignoreBuildErrors: true`), sodass Regressionen durch verwaiste Bezeichner dort (#6625/#6909) für CI unsichtbar waren. Abgleich mit einer eingefrorenen Baseline der Anzahl pro Datei und TS-Code (`config/quality/dashboard-typecheck-baseline.json`, dasselbe Muster zur Erkennung veralteter Einträge wie bei `check:known-symbols`) — nur NEUE Fehler, die über die in der Baseline festgehaltene Anzahl hinausgehen, lassen die Prüfung fehlschlagen; mit `--update` schrittweise reduzieren, wenn ein bereits vorhandener Fehler behoben wird. | Ja | +Wird bei jedem PR für `main` ausgeführt. Blockiert bei einem Fehler das Mergen. + +| Skript (`npm run ...`) | Validiert | Blockierend | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | Die Node.js-Version liegt innerhalb des unterstützten Bereichs | Ja | +| `check:cycles` | Zirkuläre Importe in `src/` + `open-sse/` (AST-basiert, `paths` aus tsconfig werden aufgelöst). Ohne Zusatz = nur Hinweis, listet die Zyklen auf. `check:cycles:ratchet` (wird von CI ausgeführt) blockiert, wenn die Anzahl den Grenzwert `metrics.cycles` in `quality-baseline.json` überschreitet — derzeit 14, `direction: down`, sodass sie nur sinken kann (#15159 G-01/G-02) | Ja (Ratchet) | +| `check:route-validation:t06` | Zod-Schemas sind für alle Routen vorhanden (Tier-6-Richtlinie) | Ja | +| `check:any-budget:t11` | Die Anzahl von `@ts-expect-error // any` überschreitet das Budget nicht (Tier-11-Catraca) | Ja | +| `check:provider-consistency` | Jeder Provider in `providers.ts` hat einen entsprechenden Eintrag in `providerRegistry.ts` (und umgekehrt, innerhalb der Zulassungsliste) | Ja | +| `check:model-lifecycle` | Die drei manuell gepflegten Routing-Tabellen bleiben mit dem eingecheckten Lebenszyklus-Snapshot (#11503) konsistent: `FITNESS_TABLE` (`taskFitness.ts`) bewertet keine eingestellte ID, die `REGISTRY` routen kann; jedes Ziel in `BUILT_IN_ALIASES` ist in `REGISTRY` vorhanden und fehlt im Snapshot der eingestellten IDs; jede eingestellte ID, die noch in `REGISTRY` enthalten ist, wird weitergeleitet oder ist in `allowedRetiredInCatalog` aufgeführt; und keine Quelle oder kein Ziel aus `DEFAULT_DEGRADATION_MAP` erscheint in diesem Snapshot als eingestellt. Dies beweist nicht, dass ein Modell derzeit von einem aktiven Upstream bereitgestellt wird. Offline — vergleicht mit `config/quality/model-lifecycle.json`, das manuell mit `npm run quality:refresh-model-lifecycle` aktualisiert wird (Netzwerk; nicht in CI eingebunden). `allowedRetiredInCatalog` ist eine schrittweise abzuarbeitende Sperrklinke: Einen Eintrag nur zusammen mit einem Tracking-Issue hinzufügen. | Ja | +| `check:fetch-targets` | Jedes `fetch("/api/...")` im clientseitigen `src/` wird in eine tatsächlich vorhandene `route.ts` aufgelöst | Ja | +| `check:deps` | Alle mit `npm install` installierbaren Abhängigkeiten in jeder `package.json` im Repository sind in `dependency-allowlist.json` enthalten; neue nicht fixierte oder durch Slopsquatting verdächtige Pakete werden gekennzeichnet | Ja | +| `audit:deps` | `npm audit` (Root + Electron) — keine Sicherheitshinweise mit hohem/kritischem Schweregrad (überschneidet sich mit OSV-`check:vuln-ratchet`; siehe Rationalisierungs-Backlog) | Ja | +| `check:lockfile` | Integrität von `package-lock.json` — HTTPS-Registry, Integritäts-Hashes, keine Host-Überschreibungen | Ja | +| `check:licenses` | SPDX-Lizenz-Positivliste für Produktionsabhängigkeiten | Ja | +| `check:tracked-artifacts` | Keine Build-Artefakte / eingecheckten `node_modules`-Symlinks (wird auch beim Husky-Pre-Commit ausgeführt; Pre-Push ist absichtlich schlank — #6716) | Ja | +| `check:ai-attribution` | Keine KI-/Bot-`Co-Authored-By`-Trailer oder KI-Generierungshinweise in PR-Commits, Titel oder Beschreibung — Strikte Regel Nr. 16 (in der Fast-Gates-Schleife von `quality.yml` für PR→`release/**` — liest die Event-Nutzlast, außerhalb von PRs keine Aktion — sowie als reiner PR-Schritt beim Linting in `ci.yml` für PR→`main`; außerdem der Husky-Hook `commit-msg`; menschliche Mitautoren sind zulässig; #14436) | +| `check:vitest-exclusions` | Jeder Vitest-Ausschluss nennt ein Tracking-Issue und ist in `config/quality/vitest-exclusions.json` aufgeführt (#13204) | Ja | +| `check:file-size` | Keine Quelldatei überschreitet die für ihre Erweiterung festgelegte Obergrenze (Ratsche: eingefrorene große Dateien in der Liste `frozen`) | Ja | +| `check:error-helper` | Fehlerantworten in Executors/Handlern verwenden `buildErrorBody()` / `sanitizeErrorMessage()` (Strikte Regel Nr. 12) | Ja | +| `check:migration-numbering` | Migration-SQL-Dateien sind fortlaufend nummeriert, ohne Lücken oder Duplikate | Ja | +| `check:public-creds` | Keine hartcodierten OAuth-`client_id`-/`client_secret`-Werte oder Firebase-Webschlüssel außerhalb von `publicCreds.ts` (Feste Regel Nr. 11) | Ja | +| `check:db-rules` | Kein unverarbeitetes SQL außerhalb der Module in `src/lib/db/`; keine Barrel-Importe aus `localDb.ts` (Feste Regeln Nr. 2/Nr. 5) | Ja | +| `check:known-symbols` | In ihren Dispatch-Tabellen registrierte Provider-Executors, Routing-Strategien und Übersetzer stimmen mit den Dateien auf dem Datenträger überein – keine verwaisten oder nicht deklarierten Symbole | Ja | +| `check:route-guard-membership` | Jede Route, die einen untergeordneten Prozess startet, wird durch `isLocalOnlyPath()` klassifiziert (Feste Regeln Nr. 15/Nr. 17) | Ja | +| `check:test-discovery` | Jede `*.test.ts`- / `*.spec.ts`-Datei im Repository wird von mindestens einem Test-Runner erfasst (Ratsche: Die Liste verwaister Dateien in `test-discovery-baseline.json` darf nur kleiner werden) | Ja | +| `check:agent-skills-sync` | Generierte Agent-Skills-Artefakte stimmen mit ihrem Quellkatalog überein (keine Abweichungen) | +| `check:provider-asset-provenance` | Anbieterlogos/-Assets verfügen über einen dokumentierten Herkunftsnachweis | +| `lint:json` | JSON-Konfigurationsdateien können geparst werden und erfüllen die Lint-Regeln des Repositorys | +| `typecheck:core` | TypeScript-Kompilierung ohne Fehler (nur hinweisende Warnungen) | Ja | +| `typecheck:noimplicit:core` | Striktes `noImplicitAny` — zukunftsorientiert; viele bereits vorhandene Aufrufstellen benötigen noch Annotationen | **Hinweisend** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | Auf `src/app/(dashboard)/**` beschränktes `tsc` (#7033) — die kuratierte Allowlist mit 27 Dateien von `typecheck:core` enthält keine Dashboard-TSX-Dateien, und `next build` führt dafür ebenfalls nie eine Typprüfung durch (`next.config.mjs` setzt `ignoreBuildErrors: true`), sodass Regressionen durch verwaiste Bezeichner dort (#6625/#6909) für CI unsichtbar waren. Abgleich mit einer eingefrorenen Baseline der Anzahl pro Datei und TS-Code (`config/quality/dashboard-typecheck-baseline.json`, dasselbe Muster zur Erkennung veralteter Einträge wie bei `check:known-symbols`) — nur NEUE Fehler, die über die in der Baseline erfasste Anzahl hinausgehen, lassen das Gate fehlschlagen; mit `--update` schrittweise reduzieren, wenn ein bereits vorhandener Fehler behoben wurde. | Ja | ### Job: `quality-gate` Wird nach `test-coverage` ausgeführt. Blockiert das Zusammenführen bei einem Fehler. -| Skript | Validiert | Blockierend | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `quality:collect` | Erzeugt `quality-metrics.json` (Anzahl der ESLint-Warnungen, Abdeckung aus dem zusammengeführten Shard-Bericht) | Ja (vorgelagert zum Ratchet) | -| `quality:ratchet` | Keine Metrik in `quality-baseline.json` hat sich verschlechtert (ESLint-Warnungen ≤ Baseline; Abdeckung ≥ Baseline) | Ja | -| `check:duplication` | Codeduplizierung (jscpd@4) überschreitet die Baseline in `quality-baseline.json` nicht | Ja | -| `check:complexity` | Zyklomatische Komplexität auf Dateiebene überschreitet den Grenzwert nicht (ESLint-Kernregeln `complexity` + `max-lines-per-function`) | Ja | -| `check:cognitive-complexity` | Ratchet für kognitive Komplexität (`eslint-plugin-sonarjs`) — separater ESLint-Durchlauf; CI führt beide zusammen als einzelnen Schritt `check:complexity-ratchets` aus | Ja | -| `check:dead-code` | Ratchet für ungenutzte Exporte/Dateien (knip) verschlechtert sich gegenüber der Baseline nicht | Ja | -| `check:compression-budget` | Budget für den Komprimierungsbenchmark — Mindestwerte für Token-Einsparungen je Engine dürfen sich nicht verschlechtern | Ja | -| `check:type-coverage` | Ratchet für den prozentual typisierten Anteil (`type-coverage`) verschlechtert sich nicht; deckt `typecheck:noimplicit:core` weitgehend ab | Ja | -| `check:codeql-ratchet` | Anzahl offener CodeQL-Warnungen verschlechtert sich nicht (Abruf über `gh api`; kontrolliertes Überspringen ohne Token) — Aktualisierungsintervall und manueller Auslöser: siehe „CodeQL-Ratchet“ unten | Ja | +| Skript | Validiert | Blockierend | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| `quality:collect` | Erzeugt `quality-metrics.json` (Anzahl der ESLint-Warnungen, Abdeckung aus dem zusammengeführten Shard-Bericht) | Ja (dem Ratchet vorgeschaltet) | +| `quality:ratchet` | Keine Metrik in `quality-baseline.json` hat sich verschlechtert (ESLint-Warnungen ≤ Baseline; Abdeckung ≥ Baseline) | Ja | +| `check:duplication` | Codeduplizierung (jscpd@4) überschreitet die Baseline in `quality-baseline.json` nicht | Ja | +| `check:complexity` | Zyklomatische Komplexität auf Dateiebene überschreitet die Obergrenze nicht (ESLint-Core-Regeln `complexity` + `max-lines-per-function`) | Ja | +| `check:cognitive-complexity` | Ratchet für kognitive Komplexität (`eslint-plugin-sonarjs`) — separater ESLint-Durchlauf; CI führt beide zusammen als den einzelnen Schritt `check:complexity-ratchets` aus | Ja | +| `check:dead-code` | Ratchet für ungenutzte Exporte/Dateien (knip) verschlechtert sich gegenüber der Baseline nicht | Ja | +| `check:compression-budget` | Budget für den Komprimierungs-Benchmark — Mindestwerte für Token-Einsparungen pro Engine dürfen sich nicht verschlechtern | Ja | +| `check:type-coverage` | Ratchet für den Anteil typisierten Codes (`type-coverage`) verschlechtert sich nicht; ersetzt weitgehend `typecheck:noimplicit:core` | Ja | +| `check:codeql-ratchet` | Anzahl offener CodeQL-Warnmeldungen verschlechtert sich nicht (Abruf über `gh api`; ordnungsgemäßes Überspringen ohne Token) — Aktualisierungsintervall und manuelle Auslösung: siehe „CodeQL-Ratchet“ unten | Ja | ### Job: `quality-extended` -Der gesamte Job dient nur als Hinweis (`continue-on-error: true`). Die npm-basierten Ratchets werden -tatsächlich ausgeführt; die externen Scanner werden über `gh release download` installiert und überspringen sich selbst (Exit-Code 0), -wenn eine Binärdatei weiterhin fehlt. +Der gesamte Job dient nur der Information (`continue-on-error: true`). Die npm-basierten Ratchets werden +tatsächlich ausgeführt; die externen Scanner werden über `gh release download` installiert und überspringen +sich selbst (Exit-Code 0), wenn eine Binärdatei weiterhin fehlt. -| Skript | Validiert | Blockierend | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| `check:circular-deps` | Keine zirkulären Abhängigkeiten (dpdm) | **Nur Hinweis** | -| `check:bundle-size` | Bundle-Größe überschreitet den Grenzwert nicht | **Nur Hinweis** | -| `check:secrets` | Suche nach Geheimnissen (gitleaks) — wird übersprungen, wenn die Binärdatei fehlt | **Nur Hinweis** | -| `check:vuln-ratchet` | Schwachstellen in Abhängigkeiten (osv-scanner) verschlechtern sich nicht — wird übersprungen, wenn die Binärdatei fehlt | **Nur Hinweis** | -| `check:workflows` | Workflow-Linting (actionlint + zizmor); fehlende/defekte Scanner, ungültige Berichte oder eine fehlende Ratchet-Baseline führen zum Status INCOMPLETE. Gültige Befunde folgen der ausgewählten strikten, hinweisenden oder Ratchet-Richtlinie | Ausführung erforderlich; zizmor-Ratchet in CI blockierend | -| `check:openapi-breaking` | Inkompatible Änderungen am öffentlichen API-Vertrag (`openapi.yaml`) gegenüber dem Basis-Branch (oasdiff) — erzeugt `openapiBreaking=N`; wird übersprungen, wenn oasdiff fehlt oder die Basisspezifikation nicht aufgelöst werden kann | **Nur Hinweis** | +| Skript | Validiert | Blockierend | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| `check:circular-deps` | Keine zirkulären Abhängigkeiten (dpdm) | **Nur informativ** | +| `check:bundle-size` | Bundle-Größe überschreitet die Obergrenze nicht | **Nur informativ** | +| `check:secrets` | Suche nach Geheimnissen (gitleaks) — wird übersprungen, wenn die Binärdatei fehlt | **Nur informativ** | +| `check:vuln-ratchet` | Sicherheitslücken in Abhängigkeiten (osv-scanner) verschlechtern sich nicht — wird übersprungen, wenn die Binärdatei fehlt | **Nur informativ** | +| `check:workflows` | Workflow-Linting (actionlint + zizmor); fehlende/defekte Scanner, ungültige Berichte oder eine fehlende Ratchet-Baseline führen zum Status INCOMPLETE. Gültige Funde folgen der ausgewählten strikten/informativen/Ratchet-Richtlinie | Ausführung erforderlich; zizmor-Ratchet blockiert in CI | +| `check:openapi-breaking` | Inkompatible Änderungen am öffentlichen API-Vertrag (`openapi.yaml`) gegenüber dem Basis-Branch (oasdiff) — erzeugt `openapiBreaking=N`; wird übersprungen, wenn oasdiff fehlt oder die Basisspezifikation nicht aufgelöst werden kann | **Nur informativ** | ### Job: `docs-sync-strict` @@ -168,61 +175,61 @@ Wird bei jedem PR an `main` ausgeführt. Blockiert das Zusammenführen bei einem | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `check:docs-all` | Meta-Gate, das die 6 nachstehenden Sub-Gates nacheinander ausführt | Ja | | ↳ `check:docs-sync` | Versionskonsistenz zwischen CHANGELOG / OpenAPI / llm.txt | Ja | -| ↳ `check:docs-counts` | Zahlenangaben im Fließtext (Anbieteranzahl, Migrationsanzahl usw.) liegen innerhalb des Ratchet-Fensters der tatsächlichen Zahlen | Ja | +| ↳ `check:docs-counts` | Zahlenangaben im Fließtext (Anbieteranzahl, Migrationsanzahl usw.) liegen innerhalb des Ratchet-Fensters der tatsächlichen Werte | Ja | | ↳ `check:env-doc-sync` | Jede Umgebungsvariable in `.env.example` ist in einer Dokumentationstabelle dokumentiert und umgekehrt | Ja | | ↳ `check:deprecated-versions` | Keine veralteten Versionszeichenfolgen in der Dokumentation | Ja | -| ↳ `check:doc-links` | Interne Markdown-Links in der Dokumentation verweisen auf vorhandene Dateien (Form `[text]`/`(path)`) | Ja | -| ↳ `check:fabricated-docs` | In der Dokumentation erwähnte Routen, Umgebungsvariablen, CLI-Befehle, Hook-Namen und Dateipfade sind in der Codebasis vorhanden. Hartes Gate mit `--strict`; ohne Flag nur weicher Fehler. | Ja (über `--strict` in CI) | +| ↳ `check:doc-links` | Interne Markdown-Links in der Dokumentation verweisen auf vorhandene Dateien (Format `[text]`/`(path)`) | Ja | +| ↳ `check:fabricated-docs` | In der Dokumentation genannte Routen, Umgebungsvariablen, CLI-Befehle, Hook-Namen und Dateipfade sind in der Codebasis vorhanden. Hartes Gate mit `--strict`; ohne Flag nur weicher Fehler. | Ja (über `--strict` in CI) | | `check:cli-i18n` | CLI-Befehlszeichenfolgen sind in allen i18n-Locale-Dateien vorhanden | Ja | -| `check:openapi-coverage` | Die OpenAPI-Spezifikation deckt mindestens einen per Ratchet festgelegten Mindestwert realer Routen ab | Ja | -| `check:openapi-security-tiers` | Sicherheitsebenen-Annotationen in `openapi.yaml` stimmen mit den Klassifizierungen in `routeGuard.ts` überein | **Hinweis** | -| `check:openapi-routes` | Jeder Pfad in `openapi.yaml` verweist auf eine reale `route.ts` (Schutz vor Halluzinationen) | Ja | -| `check:docs-symbols` | Jede `/api/...`-Referenz in `docs/**/*.md` verweist auf eine reale `route.ts` (Schutz vor Halluzinationen) | Ja | -| `i18n translation drift` | Nicht übersetzte Schlüssel in i18n-Locale-Dateien — nur Warnung | **Hinweis** | +| `check:openapi-coverage` | Die OpenAPI-Spezifikation deckt mindestens einen per Ratchet festgelegten Mindestanteil der tatsächlichen Routen ab | Ja | +| `check:openapi-security-tiers` | Sicherheitsstufen-Annotationen in `openapi.yaml` stimmen mit den Klassifizierungen in `routeGuard.ts` überein | **Hinweis** | +| `check:openapi-routes` | Jeder Pfad in `openapi.yaml` verweist auf eine vorhandene `route.ts` (Anti-Halluzination) | Ja | +| `check:docs-symbols` | Jede `/api/...`-Referenz in `docs/**/*.md` verweist auf eine vorhandene `route.ts` (Anti-Halluzination) | Ja | +| `i18n translation drift` | Nicht übersetzte Schlüssel in i18n-Locale-Dateien – nur Warnung | **Hinweis** | ### Job: `i18n-ui-coverage` -| Skript | Validiert | Blockierend | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check-ui-keys-coverage` (inline) | Die Abdeckung der UI-i18n-Schlüssel beträgt ≥ 65 % | Ja | -| `check-ui-value-drift` (inline) | Ein umformulierter englischer **Wert** hinterlässt keine veraltete Übersetzung | Ja | -| `check-new-key-coverage` (inline) | Ein **neuer** englischer Schlüssel ist in jeder Locale übersetzt — eine `__MISSING__:`-Markierung wird abgelehnt | Ja | -| `check-translation-ratio` | Der Anteil echter Übersetzungen pro Locale (mit dem Englischen identische / Platzhalter- / fehlende Blätter außerhalb der Positivliste) darf `config/quality/i18n-translation-baseline.json` + Toleranz nicht überschreiten | **Hinweis** | +| Skript | Validiert | Blockierend | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `check-ui-keys-coverage` (inline) | Die Abdeckung der UI-i18n-Schlüssel beträgt ≥ 65 % | Ja | +| `check-ui-value-drift` (inline) | Ein neu formulierter englischer **Wert** hinterlässt keine veraltete Übersetzung | Ja | +| `check-new-key-coverage` (inline) | Ein **neuer** englischer Schlüssel ist in jeder Locale übersetzt – ein `__MISSING__:`-Marker wird abgelehnt | Ja | +| `check-translation-ratio` | Der Anteil echter Übersetzungen pro Locale (mit dem Englischen identische / Platzhalter- / fehlende Blätter außerhalb der Zulassungsliste) darf `config/quality/i18n-translation-baseline.json` + Toleranz nicht überschreiten | **Hinweis** | -Erfordert `fetch-depth: 0` — das Value-Drift-Gate vergleicht `en.json` mit der Merge-Basis. +Benötigt `fetch-depth: 0` – das Value-Drift-Gate vergleicht `en.json` per Diff mit der Merge-Basis. -#### `check-ui-value-drift` — Gate gegen veraltete Übersetzungen +#### `check-ui-value-drift` – Gate gegen veraltete Übersetzungen -Erkennt die eine i18n-Regression, die die anderen Gates strukturell nicht erkennen können: Ein englischer Wert -wird umformuliert, während die aus dem _vorherigen_ englischen Text abgeleiteten Übersetzungen bestehen bleiben, sodass -nicht englischsprachige Benutzer weiterhin überzeugend formulierte, aber inzwischen falsche Texte lesen. +Erkennt die eine i18n-Regression, die für die anderen Gates strukturell unsichtbar ist: Ein englischer Wert +wird neu formuliert, während die aus dem _vorherigen_ englischen Text abgeleiteten Übersetzungen bestehen bleiben, sodass +nicht englischsprachige Benutzer weiterhin einen selbstsicher formulierten, aber nun falschen Text lesen. -Dies ist tatsächlich in die Produktion gelangt. `oauthModal.googleOAuthWarning` wurde umformuliert, als der Antigravity- -Anmeldehelfer eingeführt wurde (#5203); in **39 von 43 Locales** blieb ein Text erhalten, der Betreiber anwies, „die -vollständige URL zu kopieren und unten einzufügen“ — ein Ablauf, der für diesen Anbieter nicht abgeschlossen werden kann. Dies blieb +Dies wurde tatsächlich so ausgeliefert. `oauthModal.googleOAuthWarning` wurde neu formuliert, als der Antigravity- +Anmeldehelfer eingeführt wurde (#5203); in **39 von 43 Locales** blieb der Text bestehen, der Betreiber anwies, „die +vollständige URL zu kopieren und unten einzufügen“ – ein Ablauf, der bei diesem Anbieter nicht abgeschlossen werden kann. Dies blieb bis #8463 unbemerkt, weil: -- `sync-ui-keys` nur **fehlende** Schlüssel ergänzt, niemals **veraltete**; -- `check-ui-keys-coverage` die _Anwesenheit_ von Schlüsseln zählt, sodass eine veraltete Übersetzung als abgedeckt gilt; -- `check-translation-drift` die Dokumentationsspiegel unter `docs/i18n//**.md` verfolgt — - `src/i18n/messages/*.json` wird nie gelesen. Seit der 2026-09-Neusynchronisierung im Job `docs-sync-strict` blockierend: - Kerndokument bearbeiten → `npm run i18n:run -- --files=` (abschnittsweise, kostengünstig). - -**Diff-basiert, nicht durch eine Baseline gestützt.** Es vergleicht `en.json` am Merge-Base mit dem -Arbeitsbaum; für jeden Schlüssel, dessen englischer Wert geändert wurde, gilt jede Locale, die -noch eine unveränderte Übersetzung enthält, als veraltet. Dadurch werden **bereits vorhandene -Altlasten bewusst eingefroren** — aus einem Diff lässt sich nicht erkennen, von welchem alten -englischen Text eine bereits lange bestehende Übersetzung stammt. Daher bewertet das Gate nur, -was durch die aktuelle Änderung betroffen ist. Die Alternative (eine Hash-Baseline pro Schlüssel) -würde eine generierte Datei von ~600 KB erfordern, dreimal so groß wie die größte vorhandene -Baseline, und bei jedem i18n-PR Änderungen verursachen. +- `sync-ui-keys` nur Schlüssel ergänzt, die **fehlen**, niemals solche, die **veraltet** sind; +- `check-ui-keys-coverage` die _Existenz_ von Schlüsseln zählt, sodass eine veraltete Übersetzung als abgedeckt gewertet wird; +- `check-translation-drift` die Dokumentationsspiegel unter `docs/i18n//**.md` verfolgt – + `src/i18n/messages/*.json` wird niemals gelesen. Seit der erneuten Synchronisierung 2026-09 im Job `docs-sync-strict` blockierend: + Kerndokument bearbeiten → `npm run i18n:run -- --files=` (auf Abschnittsebene, kostengünstig). + +**Diff-basiert, nicht durch eine Baseline gestützt.** Dabei wird `en.json` am Merge-Base mit dem +Arbeitsbaum verglichen; für jeden Schlüssel, dessen englischer Wert geändert wurde, gilt jede +Locale, die noch eine unveränderte Übersetzung enthält, als veraltet. Dadurch werden +**bereits vorhandene Altlasten bewusst eingefroren** — aus einem Diff lässt sich nicht erkennen, +von welchem alten englischen Text eine bereits lange bestehende Übersetzung stammt. Daher +bewertet das Gate nur, was von der aktuellen Änderung betroffen ist. Die Alternative (eine +Hash-Baseline pro Schlüssel) würde eine generierte Datei von ~600 KB erfordern, dreimal so +groß wie die größte bestehende Baseline, und würde bei jedem i18n-PR geändert. Es gibt zwei Möglichkeiten, die Prüfung zu erfüllen: 1. die betroffenen Übersetzungen aktualisieren oder 2. sie auf `__MISSING__:` setzen — zur Laufzeit wird dann der korrigierte englische Text ausgeliefert (`src/i18n/request.ts::deepMergeFallback`, #7258) und der Schlüssel zur - Übersetzung vorgemerkt. + Übersetzung eingereiht. Wenn sich die **Bedeutung** des Textes geändert hat, sollte der **Schlüssel umbenannt** werden: Ein neuer Schlüssel kann keine veraltete Übersetzung übernehmen. Dieses Muster wurde in #8463 @@ -234,50 +241,49 @@ npm run i18n:check-value-drift:warn # nur Bericht BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Wird der Basiskatalog nicht gefunden, endet die Prüfung mit Code 0 und -`SKIP reason=base-unresolved` (flacher Klon ohne Basis-Ref), analog zu -`check-openapi-breaking`. +Wird der Basiskatalog nicht gefunden (flacher Klon ohne Basis-Ref), endet die Prüfung mit +Status 0 und `SKIP reason=base-unresolved`, entsprechend `check-openapi-breaking`. ### Job: `i18n` -Vollständige i18n-Validierungsmatrix (ein Job pro Locale). Der gesamte Job ist nicht blockierend. +Vollständige i18n-Validierungsmatrix (ein Job pro Locale). Der gesamte Job ist informativ. -| Skript | Validiert | Blockierend | -| ------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------- | -| `validate_translation.py quick` | Vollständigkeit der Übersetzung je Locale | **Nicht blockierend** (`continue-on-error: true` für den gesamten Job) | +| Skript | Validiert | Blockierend | +| ------------------------------- | ------------------------------------------ | ----------------------------------------------------------- | +| `validate_translation.py quick` | Vollständigkeit der Übersetzung pro Locale | **Informativ** (`continue-on-error: true` für gesamten Job) | ### Job: `pr-test-policy` Wird nur bei Pull Requests ausgeführt. -| Skript | Validiert | Blockierend | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | PRs, die Produktionscode in `src/`, `open-sse/`, `electron/` oder `bin/` ändern, müssen Tests enthalten oder aktualisieren (harte Regel #8) | Ja | -| `check:test-masking` | Geänderte Testdateien reduzieren nicht die Nettoanzahl der Assertions und fügen keine `assert.ok(true)`-Tautologien hinzu | Ja | -| `check:pr-evidence` | Der PR-Text nennt Test-/VPS-Nachweise für die Änderung (automatisiert harte Regel #18 durch Durchsuchen des PR-Textes — fragil, siehe Backlog) | Ja | +| Skript | Validiert | Blockierend | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check:pr-test-policy` | PRs, die Produktionscode in `src/`, `open-sse/`, `electron/` oder `bin/` ändern, müssen Tests enthalten oder aktualisieren (Feste Regel Nr. 8) | Ja | +| `check:test-masking` | Geänderte Testdateien verringern nicht die Nettoanzahl der Assertions und fügen keine `assert.ok(true)`-Tautologien hinzu | Ja | +| `check:pr-evidence` | Der PR-Text nennt Test-/VPS-Nachweise für die Änderung (automatisiert Feste Regel Nr. 18 durch Durchsuchen des PR-Textes — fragil, siehe Backlog) | Ja | ### Job: `test-vitest` -Wird nach `build` ausgeführt. Blockiert bei einem Fehler den Merge. +Wird nach `build` ausgeführt. Blockiert den Merge bei einem Fehler. -| Suite | Validiert | Blockierend | -| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP-Server (110 Tools), autoCombo, Cache — vitest-Runner | Ja | -| `test:vitest:ui` | UI-Komponententests — vitest-Runner | **Blockierend** — bereits vorhandene Fehler sind in `vitest.config.ts` explizit ausgeschlossen; neue Fehler lassen den Job fehlschlagen | +| Suite | Validiert | Blockierend | +| ---------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP-Server (110 Tools), autoCombo, Cache — vitest-Test-Runner | Ja | +| `test:vitest:ui` | UI-Komponententests — vitest-Test-Runner | **Blockierend** — bereits vorhandene Fehler sind in `vitest.config.ts` ausdrücklich ausgeschlossen; neue Fehler lassen den Job fehlschlagen | -### Nächtliche Workflows (zeitgesteuert, nicht blockierend) +### Nächtliche Workflows (zeitgesteuert, informativ) -Diese werden nach einem Cron-Zeitplan (und per `workflow_dispatch`) ausgeführt, niemals bei PRs. -Sie sind alle nicht blockierend. +Diese werden nach einem Cron-Zeitplan (und über `workflow_dispatch`) ausgeführt, niemals bei PRs. +Sie sind alle informativ. -| Workflow | Validiert | Blockierend | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `nightly-property` | fast-check-Property-Tests mit zufälligem Seed und hoher Anzahl von Durchläufen | **Nicht blockierend** | -| `nightly-resilience` | Heap-Wachstums-Gate, Chaos-Fehlerinjektion, k6-Last-/Dauertests | **Nicht blockierend** | -| `nightly-llm-security` | promptfoo-Injection-Guard (Blockiermodus) + garak-Probes (werden ohne Provider-Secret übersprungen) | **Nicht blockierend** | -| `nightly-schemathesis` | OpenAPI-Contract-Fuzzing (schemathesis) gegen eine aktive OmniRoute mit `docs/openapi.yaml` — deckt Spezifikationsverstöße / unbehandelte 500er auf (Phase 8 B.4) | **Nicht blockierend** | -| `nightly-mutation` | Stryker-Mutation-Testing-Score für die schnelle Unit-Test-Spur — überlebende Mutanten weisen auf schwache Assertions hin | **Nicht blockierend** | -| `nightly-compat` | Node-Engine-Kompatibilitätsmatrix über die unterstützten `engines.node`-Bereiche hinweg | **Nicht blockierend** | +| Workflow | Validiert | Blockierend | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | fast-check-Property-Tests mit zufälligem Seed und hoher Anzahl an Durchläufen | **Informativ** | +| `nightly-resilience` | Heap-Wachstums-Gate, Chaos-Fehlerinjektion, k6-Last-/Dauertests | **Informativ** | +| `nightly-llm-security` | promptfoo-Injection-Guard (Blockiermodus) und garak-Prüfungen (werden ohne Provider-Secret übersprungen) | **Informativ** | +| `nightly-schemathesis` | OpenAPI-Contract-Fuzzing (schemathesis) gegen eine laufende OmniRoute-Instanz unter Verwendung von `docs/openapi.yaml` — deckt Spezifikationsverstöße / unbehandelte 500er auf (Phase 8 B.4) | **Informativ** | +| `nightly-mutation` | Stryker-Mutation-Testing-Bewertung für die schnelle Unit-Test-Spur — überlebende Mutanten decken schwache Assertions auf | **Informativ** | +| `nightly-compat` | Kompatibilitätsmatrix der Node-Engine über die unterstützten `engines.node`-Bereiche hinweg | **Informativ** | --- @@ -547,43 +553,44 @@ Verbindungsfehler und unterbricht niemals Sitzungen. --- -## Rationalisierungs-Backlog (ROI-Überprüfung — Phase 9 Welle 3) +## Rationalisierungs-Backlog (ROI-Prüfung — Phase 9 Welle 3) Dieses Inventar wurde am 2026-06-17 mit `ci.yml` abgeglichen (in der vorherigen Version fehlten `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Eine ROI-Überprüfung des abgeglichenen Bestands -ergab die folgenden Rationalisierungskandidaten. **Die Zusammenführungen sind mechanische CI- -Änderungen; die Umstellungen/Streichungen sind Richtlinienentscheidungen, die dem Betreiber vorbehalten sind.** Keine der nachstehenden -Änderungen wurde bisher angewendet. +`check:codeql-ratchet`, `check:pr-evidence`). Eine ROI-Prüfung des abgeglichenen Bestands +ergab die folgenden Rationalisierungskandidaten. **Die Zusammenführungen sind mechanische +CI-Änderungen; die Umstellungen/Entfernungen sind Richtlinienentscheidungen, die dem Betreiber +vorbehalten sind.** Nichts davon wurde bisher umgesetzt. -**Oben ebenfalls nicht dokumentiert** (nur hinweisend, geringe Aussagekraft): der Job `docs-lint` -(markdownlint + Vale, gesamter Job mit `continue-on-error`) und die eigenständigen Scanner-Workflows -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` ist in -`quality-baseline.json` enthalten, aber in `ci.yml` nicht mit einem blockierenden Ratchet verknüpft — die Metrik ist -derzeit verwaist. +**Oben ebenfalls nicht dokumentiert** (nur Hinweischarakter, geringe Aussagekraft): der Job +`docs-lint` (markdownlint + Vale, gesamter Job mit `continue-on-error`) und die eigenständigen +Scanner-Workflows `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` ist in +`quality-baseline.json` enthalten, aber nicht mit einer blockierenden Ratchet-Prüfung in `ci.yml` +verknüpft — die Metrik ist derzeit verwaist. ### Zusammenführen / Deduplizieren (mechanisch, geringeres Risiko) -Jeder Kandidat wurde am 2026-06-17 anhand des aktuellen Gate-Status validiert (Vertrauen ist gut, Kontrolle ist besser); -mehrere „offensichtliche“ Zusammenführungen erwiesen sich als versteckte Altlasten und sind **keine** direkt einsetzbaren Ersetzungen. +Jeder Kandidat wurde am 2026-06-17 anhand des aktiven Gate-Status validiert (Vertrauen ist gut, +Kontrolle ist besser); mehrere „offensichtliche“ Zusammenführungen verbargen tatsächlich +Altlasten und sind **kein** sauberer direkter Ersatz. -- **`check:docs-sync` wird zweimal ausgeführt** — eigenständig im Job `lint` und erneut innerhalb von `check:docs-all` (`docs-sync-strict`) sowie im Husky-Pre-Commit-Hook. ✅ **ERLEDIGT** — eigenständiger Aufruf in `lint` entfernt. -- **CVE-Scanning** — ❌ **KEINE saubere Zusammenführung.** `audit:deps` schlägt bei jeder CVE mit hohem/kritischem Schweregrad zwingend fehl; `check:vuln-ratchet` (osv) schlägt nur bei einer _Regression_ gegenüber der Baseline fehl (derzeit 1 MODERATE). Unterschiedliche Semantik — durch das Entfernen von `audit:deps` ginge das absolute Gate für hohe/kritische Schweregrade verloren. Beide beibehalten. -- **Zyklenerkennung** — ❌ **KEINE saubere Zusammenführung.** `check:circular-deps` (dpdm) meldet **91 Zyklen** (deshalb ist die Prüfung nur hinweisend); sie kann nicht auf blockierend hochgestuft werden, ohne diese zuvor zu beheben, und ihr Umfang ist größer als der der erfolgreichen, kuratierten Prüfung `check:cycles`. `check:cycles` als blockierend beibehalten; die Behebung der 91 dpdm-Zyklen ist ein eigener Backlog-Eintrag. +- **`check:docs-sync` wird zweimal ausgeführt** — eigenständig im Job `lint` und nochmals innerhalb von `check:docs-all` (`docs-sync-strict`) sowie im husky-Pre-Commit-Hook. ✅ **ERLEDIGT** — eigenständiger Aufruf in `lint` entfernt. +- **CVE-Scanning** — ❌ **KEINE saubere Zusammenführung.** `audit:deps` schlägt bei jeder CVE mit hohem/kritischem Schweregrad hart fehl; `check:vuln-ratchet` (osv) schlägt nur bei einer _Regression_ gegenüber der Baseline fehl (derzeit 1 MODERATE). Unterschiedliche Semantik — durch das Entfernen von `audit:deps` würde das absolute Gate für hohe/kritische Schweregrade verloren gehen. Beide beibehalten. +- **Zyklenerkennung** — ✅ **ERLEDIGT** (#15159 G-01/G-02). Der alte Text bezeichnete `check:cycles` als „das grüne, kuratierte“ Gate und begründete dessen blockierende Beibehaltung damit, dass `check:circular-deps` (dpdm) 91 Zyklen meldete. Dieses Grün war ein **falsch positives Grün**: `check:cycles` scannte 5 Unterverzeichnisse (450 Dateien), erfasste nur statische `import|export … from`-Anweisungen und verwarf jeden `@/`- und `@omniroute/open-sse/`-Spezifizierer, sodass die im Repository vorherrschenden Zyklen aus dynamischen Imports und Aliasen nicht erkannt werden konnten. Behoben: Das Gate durchsucht nun `src` + `open-sse` (5023 Dateien), erfasst Spezifizierer aus dem TypeScript-AST (sodass `import("…")` zählt, `typeof import("…")` an Typpositionen hingegen nicht) und löst `paths` aus tsconfig auf. Es findet **14** Zyklen, nicht 0. Da 14 bereits vorhandene Zyklen nicht in einem Gate-PR behoben werden können, ist `check:cycles` nun eine **Ratchet-Prüfung** (`--ratchet`, Obergrenze `metrics.cycles.value = 14` in `quality-baseline.json`, `direction: down`) — sie blockiert jede _Regression_, und die Anzahl kann nur sinken. CI führt `npm run check:cycles:ratchet` aus. Der schrittweise Abbau erfolgt zusammen mit **A-01**. `check:circular-deps` (dpdm) bleibt als umfassendere Zweitmeinung im Hinweisstatus. - **Komplexität** — ✅ **ERLEDIGT** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ein ESLint-Durchlauf, Zählung nach ruleId, sodass die Baselines für zyklomatische Komplexität + maximale Zeilenzahl und kognitive Komplexität unabhängig bleiben; die einzelnen Prüfungen `check:complexity` / `check:cognitive-complexity` bleiben für lokale Ausführungen mit `--update` erhalten. -- **`/api`-Anti-Halluzination** — ✅ **ERLEDIGT** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): ein einziges FS-Inventar von `src/app/api`; openapi-routes + docs-symbols melden weiterhin unabhängig; die einzelnen Prüfungen bleiben für lokale Ausführungen erhalten. -- **`check:node-runtime` wird in 11 Jobs ausgeführt** — ⚠️ **geringer ROI.** Jeder Job verwendet einen separaten Runner und die Prüfung dauert <1 s; Gesamtersparnis etwa 10 s, bei gleichzeitigem Verlust einer kostengünstigen Schutzprüfung pro Job. Den Änderungsaufwand nicht wert. -- **`typecheck:noimplicit:core` im CI-Linting** — ✅ **aus dem Job `lint` entfernt** (war mit `continue-on-error` nur hinweisend); die blockierende Typoberfläche besteht aus `typecheck:core` + `check:type-coverage`. Lokales Skript beibehalten. +- **`/api`-Anti-Halluzination** — ✅ **ERLEDIGT** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): eine einzige Dateisystem-Inventarisierung von `src/app/api`; openapi-routes + docs-symbols berichten weiterhin unabhängig; die einzelnen Prüfungen bleiben für lokale Ausführungen erhalten. +- **`check:node-runtime` wird in 11 Jobs ausgeführt** — ⚠️ **geringer ROI.** Jeder Job verwendet einen separaten Runner, und die Prüfung dauert <1 s; die Gesamtersparnis läge bei ~10 s, während dabei eine kostengünstige Schutzprüfung pro Job verloren ginge. Den Änderungsaufwand nicht wert. +- **`typecheck:noimplicit:core` im CI-Linting** — ✅ **aus dem Job `lint` entfernt** (war mit `continue-on-error` nur hinweisend); die blockierende Typabdeckung besteht aus `typecheck:core` + `check:type-coverage`. Lokales Skript beibehalten. ### Umstellen / Entscheiden (Betreiberrichtlinie) -- `check:openapi-security-tiers` (hinweisend) — ❌ **NICHT ohne Weiteres umstellbar.** Die Prüfung wird mit 0 beendet, warnt jedoch, dass bei mehreren `traffic-inspector`-Routen unter `LOCAL_ONLY_API_PREFIXES` die Annotation `x-loopback-only: true` fehlt. Für eine Erzwingung müssen diese Annotationen zuerst zu `openapi.yaml` hinzugefügt werden. -- `typecheck:noimplicit:core` (hinweisend) — weitgehend durch das blockierende Ratchet `check:type-coverage` abgedeckt. Auf ein Ratchet umstellen oder den redundanten zweiten `tsc`-Durchlauf entfernen. -- `test:vitest:ui` (jetzt **blockierend**) — bereits bestehende Fehler werden in `vitest.config.ts` explizit mit `// #8618`-Nachverfolgungskommentaren ausgeschlossen; neue Fehler lassen den Job fehlschlagen. -- `check:secrets` (gitleaks, blockierendes Ratchet, bei 3 dokumentierten Falschmeldungen eingefroren) — die 3 auf die Positivliste setzen, um 0 zu erreichen, oder auf hinweisend herabstufen. Überschneidet sich mit dem nativen GitHub-Secret-Scanning + `check:public-creds`. -- `check:pr-evidence` (blockierend, durchsucht den Fließtext des PR-Bodys mit grep) — hohes Falsch-positiv-Risiko; eine Entfernung schwächt die Durchsetzung von Hard Rule #18, daher handelt es sich um eine echte Richtlinienentscheidung. -- `semgrep` (eigenständig, hinweisend) — überschneidet sich bei den OWASP-Familien mit CodeQL; die Baseline mit einem Ratchet verknüpfen oder entfernen. +- `check:openapi-security-tiers` (hinweisend) — ❌ **NICHT ohne Weiteres umstellbar.** Die Prüfung beendet sich mit 0, warnt jedoch, dass mehreren `traffic-inspector`-Routen unter `LOCAL_ONLY_API_PREFIXES` die Annotation `x-loopback-only: true` fehlt. Vor einer Erzwingung müssen diese Annotationen zuerst zu `openapi.yaml` hinzugefügt werden. +- `typecheck:noimplicit:core` (hinweisend) — wird weitgehend durch die blockierende Ratchet-Prüfung `check:type-coverage` abgedeckt. In eine Ratchet-Prüfung umwandeln oder den redundanten zweiten `tsc`-Durchlauf entfernen. +- `test:vitest:ui` (jetzt **blockierend**) — bereits vorhandene Fehler sind in `vitest.config.ts` ausdrücklich mit `// #8618`-Tracking-Kommentaren ausgeschlossen; neue Fehler lassen den Job fehlschlagen. +- `check:secrets` (gitleaks, blockierende Ratchet-Prüfung, eingefroren bei 3 dokumentierten Falschmeldungen) — die 3 Einträge auf die Zulassungsliste setzen, um 0 zu erreichen, oder auf Hinweisstatus herabstufen. Überschneidet sich mit dem nativen Secret-Scanning von GitHub + `check:public-creds`. +- `check:pr-evidence` (blockierend, durchsucht den Fließtext des PR-Texts) — hohes Falsch-Positiv-Risiko; eine Entfernung würde die Durchsetzung der festen Regel Nr. 18 schwächen, daher handelt es sich um eine echte Richtlinienentscheidung. +- `semgrep` (eigenständig, hinweisend) — überschneidet sich bei den OWASP-Kategorien mit CodeQL; die Baseline mit einer Ratchet-Prüfung verknüpfen oder entfernen. --- diff --git a/docs/i18n/de/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/de/docs/compression/COMPRESSION_GUIDE.md index 8a353e093ec6..f5bd92ffc931 100644 --- a/docs/i18n/de/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/de/docs/compression/COMPRESSION_GUIDE.md @@ -454,9 +454,7 @@ anstelle von Englisch injiziert. Bei einem Body mit `messages` prüft ein Inhalts-Bypass (`shouldBypassCavemanOutputMode()` in `open-sse/services/compression/outputMode.ts`) die letzten drei Nachrichten und überspringt die Stile für den gesamten Durchlauf, wenn sie seinen sicherheitsrelevanten, irreversiblen Aktions-, -Klärungs- oder reihenfolgesensiblen Schlüsselwörtern entsprechen. Der Bypass läuft unabhängig -davon, wie der **Auto-Clarity Bypass**-Schalter (`cavemanOutputMode.autoClarity`) des Dashboards -eingestellt ist. +Klärungs- oder reihenfolgesensiblen Schlüsselwörtern entsprechen. Der Bypass läuft, solange der **Auto-Clarity Bypass**-Schalter (`cavemanOutputMode.autoClarity`) des Dashboards eingeschaltet ist, was der Standard ist; bei ausgeschaltetem Schalter gelten die ausgewählten Stile auch bei diesen Durchläufen. Wenn der Bypass den Durchlauf zulässt, platziert `placeSystemInstruction()` (gleiche Datei), das niemals eine neue `messages[0]` erstellt, den Block in der ersten der folgenden Stellen, die es findet: diff --git a/docs/i18n/de/docs/reference/ENVIRONMENT.md b/docs/i18n/de/docs/reference/ENVIRONMENT.md index 31fef5a26e3c..95e3e43858bb 100644 --- a/docs/i18n/de/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/de/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Erforderliche Geheimnisse -Diese **müssen** vor dem ersten Start festgelegt werden. Andernfalls verweigert die Anwendung entweder den Start oder verwendet unsichere Standardwerte. - -| Variable | Erforderlich | Standardwert | Quelldatei | Beschreibung | -| ---------------------------- | ------------------- | ------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ja** | _(keiner)_ | `src/lib/auth` | Signiert/verifiziert alle Dashboard-Sitzungscookies (JWT). Mit `openssl rand -base64 48` generieren. | -| `API_KEY_SECRET` | **Ja** | _(keiner)_ | `src/lib/db/apiKeys.ts` | AES-Verschlüsselungsschlüssel für in SQLite gespeicherte API-Schlüsselwerte. Mit `openssl rand -hex 32` generieren. | -| `INITIAL_PASSWORD` | **Ja** | `CHANGEME` | Bootstrap-Skript | Legt das anfängliche Administratorpasswort für das Dashboard fest (entspricht dem Standardwert in `.env.example` – absichtlich offensichtlich unsicher, um eine Änderung zu erzwingen). **Vor der ersten Verwendung ändern.** Nach der Anmeldung über Dashboard → Einstellungen → Sicherheit ändern. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (Produktion) | _(nicht gesetzt)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Gemeinsames Geheimnis für die interne Codex-Responses-WebSocket-Bridge. Authentifiziert Bridge-Anfragen zwischen dem Electron-/Browser-WS-Relay und OmniRoute. ⚠️ **In der Produktion ERFORDERLICH – wenn nicht gesetzt, werden alle WS-Bridge-Anfragen abgelehnt.** Mit `openssl rand -base64 32` generieren. | -| `OMNIROUTE_SW_BUILD_ID` | Nein | _(Git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explizite ID zur Cache-Invalidierung des Service Workers für die PWA-Shell (#11779); steht an erster Stelle in der Auflösungskette. | -| `SOURCE_VERSION` | Nein | _(nicht gesetzt)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zweite Stelle in der Kette – wird von PaaS-Build-Systemen (z. B. im Heroku-Stil) auf den bereitgestellten Commit gesetzt. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nein | _(abgeleitet)_ | `src/shared/components/PwaRegister.tsx` | Öffentlicher Build-Zeit-Wert, den der Client zur Registrierung von `/sw.js?v=…` verwendet; wird aus den beiden obigen Werten und anschließend aus der Git-SHA abgeleitet. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nein (automatisch) | _(automatisch pro Start)_ | `src/server/authz/policies/management.ts` | Prozessspezifisches Geheimnis, das belegt, dass der vertrauenswürdige Peer-IP-Stempel vom eigenen HTTP-Server von OmniRoute (`scripts/dev/peer-stamp.mjs`) stammt. Die Authz-Middleware vertraut der Lokalität einer Anfrage (Loopback-/LAN-Beschränkung von LOCAL_ONLY-Routen) nur, wenn der Stempel dieses Token enthält. Wird bei jedem Start automatisch generiert – nicht setzen; nur für Mehrprozesskonfigurationen fest vorgeben, die denselben Stempel verwenden müssen. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nein | _(nicht gesetzt)_ | `scripts/dev/peer-stamp.mjs` | Kommagetrennte IPs oder CIDR-Bereiche von Reverse-Proxys, deren `X-Forwarded-For` / `X-Real-IP` die IP-Zulassungs-/Sperrliste zusätzlich zu Loopback-, privaten Netzwerkadressen und Cloudflare-Edges vertrauen darf, die immer als vertrauenswürdig gelten. Nur für einen Proxy mit einer anderen öffentlichen Adresse erforderlich; andernfalls wird die eigene Adresse dieses Proxys vom Filter bewertet. | +Nicht gesetzte Geheimnisse mit der Markierung **Ja** werden beim ersten Start automatisch generiert (`JWT_SECRET`, `API_KEY_SECRET`) und im Datenverzeichnis in `server.env` persistiert – legen Sie sie explizit fest, wenn dieses Verzeichnis kurzlebig (ephemeral) ist. Die Ausnahme ist `OMNIROUTE_WS_BRIDGE_SECRET`: Es wird nicht in `server.env` persistiert – die Server-Einstiegspunkte (Entrypoints) erzeugen einen kurzlebigen Wert pro Start, den nur die prozessinterne Bridge verwenden kann; externe Bridge-Clients müssen ihn daher explizit setzen. + +| Variable | Erforderlich | Standardwert | Quelldatei | Beschreibung | +| ---------------------------- | ------------------- | ------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Ja** | _(keiner)_ | `src/lib/auth` | Signiert/verifiziert alle Dashboard-Sitzungscookies (JWT). Mit `openssl rand -base64 48` generieren. | +| `API_KEY_SECRET` | **Ja** | _(keiner)_ | `src/lib/db/apiKeys.ts` | AES-Verschlüsselungsschlüssel für in SQLite gespeicherte API-Schlüsselwerte. Mit `openssl rand -hex 32` generieren. | +| `INITIAL_PASSWORD` | Nein | _(nicht gesetzt)_ | `src/lib/auth/managementPassword.ts` | Setzt das Dashboard-Passwort beim Start, solange noch keines gespeichert ist; wird ignoriert, sobald ein Passwort gespeichert ist. npm- und Quellcode-Installationen kopieren `.env.example` nach `.env` und starten daher mit dessen öffentlich bekanntem Platzhalter `CHANGEME`: Ersetzen Sie ihn vor dem ersten Start oder setzen Sie in dieser `.env` `INITIAL_PASSWORD=` (leer), um das Passwort beim ersten Besuch im Onboarding-Assistenten erstellen zu lassen – so wie es ein nicht gesetzter Wert (der Standard im Docker-Image) tut. Ein gespeichertes Passwort ändern Sie unter Dashboard → Einstellungen → Sicherheit, oder führen Sie `omniroute reset-password` (`node bin/reset-password.mjs` im Quellcode-Checkout) aus, mit `DATA_DIR` auf das Datenverzeichnis des Servers gesetzt. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (Produktion) | _(nicht gesetzt)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Gemeinsames Geheimnis für die interne Codex-Responses-WebSocket-Bridge. Authentifiziert Bridge-Anfragen zwischen dem Electron-/Browser-WS-Relay und OmniRoute. ⚠️ **In der Produktion ERFORDERLICH – wenn nicht gesetzt, werden alle WS-Bridge-Anfragen abgelehnt.** Mit `openssl rand -base64 32` generieren. | +| `OMNIROUTE_SW_BUILD_ID` | Nein | _(Git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explizite ID zur Cache-Invalidierung des Service Workers für die PWA-Shell (#11779); steht an erster Stelle in der Auflösungskette. | +| `SOURCE_VERSION` | Nein | _(nicht gesetzt)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zweite Stelle in der Kette – wird von PaaS-Build-Systemen (z. B. im Heroku-Stil) auf den bereitgestellten Commit gesetzt. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nein | _(abgeleitet)_ | `src/shared/components/PwaRegister.tsx` | Öffentlicher Build-Zeit-Wert, den der Client zur Registrierung von `/sw.js?v=…` verwendet; wird aus den beiden obigen Werten und anschließend aus der Git-SHA abgeleitet. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nein (automatisch) | _(automatisch pro Start)_ | `src/server/authz/policies/management.ts` | Prozessspezifisches Geheimnis, das belegt, dass der vertrauenswürdige Peer-IP-Stempel vom eigenen HTTP-Server von OmniRoute (`scripts/dev/peer-stamp.mjs`) stammt. Die Authz-Middleware vertraut der Lokalität einer Anfrage (Loopback-/LAN-Beschränkung von LOCAL_ONLY-Routen) nur, wenn der Stempel dieses Token enthält. Wird bei jedem Start automatisch generiert – nicht setzen; nur für Mehrprozesskonfigurationen fest vorgeben, die denselben Stempel verwenden müssen. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nein | _(nicht gesetzt)_ | `scripts/dev/peer-stamp.mjs` | Kommagetrennte IPs oder CIDR-Bereiche von Reverse-Proxys, deren `X-Forwarded-For` / `X-Real-IP` die IP-Zulassungs-/Sperrliste zusätzlich zu Loopback-, privaten Netzwerkadressen und Cloudflare-Edges vertrauen darf, die immer als vertrauenswürdig gelten. Nur für einen Proxy mit einer anderen öffentlichen Adresse erforderlich; andernfalls wird die eigene Adresse dieses Proxys vom Filter bewertet. | ### Generierungsbefehle @@ -242,11 +242,11 @@ OmniRoute verwendet **SQLite** (über `better-sqlite3`) für die gesamte Persist | `CORS_ORIGIN` | _(nicht gesetzt)_ | `src/server/cors/origins.ts` | Veraltete CORS-Zulassungsliste für einen einzelnen Ursprung. Bevorzugen Sie für neue Bereitstellungen `CORS_ALLOWED_ORIGINS`. CORS ist nur für ursprungsübergreifende Browser-API-Clients vorgesehen; authentifizierte Schreibvorgänge im Dashboard verwenden stattdessen Anfragen gleichen Ursprungs sowie sitzungsgebundenen CSRF-Schutz. | | `CORS_ALLOWED_ORIGINS` | _(nicht gesetzt)_ | `src/server/cors/origins.ts` | Kommagetrennte CORS-Zulassungsliste. Es wird kein Platzhalter gesendet, sofern nicht ausdrücklich `CORS_ALLOW_ALL=true` konfiguriert ist. | | `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Nur für die Entwicklung vorgesehene Ausweichoption, um jeden Browser-`Origin` zurückzugeben. Nicht in gemeinsam genutzten oder Produktionsbereitstellungen aktivieren. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blockiert Provider-Aufrufe, die auf private, Loopback- oder Link-Local-IP-Bereiche abzielen. Nur in isolierten Testumgebungen deaktivieren. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Erlaubt Provider-URLs, die auf private/lokale Netzwerke verweisen (localhost, 192.168.x.x, 10.x.x.x usw.). **ERFORDERLICH für selbst gehostete Provider** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Bei `false` lehnt das Dashboard die Validierung lokaler URLs ab. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Erlaubt das Hinzufügen/Validieren von Providern unter lokalen/privaten Adressen (127.0.0.1, localhost, LAN, private Bereiche) – beschränkt auf den Provider-Validierungspfad. **Standardwert `true`** (Local-first); auf `false` setzen, um eine strikte Blockierung aller nicht öffentlichen Adressen durchzusetzen. Cloud-Metadaten-Endpunkte (169.254.169.254, metadata.google.internal) bleiben unabhängig davon blockiert. (#5066) | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alter Alias (Legacy). Sein Dashboard-Schalter (ein DB-Override) wird vor diesem Umgebungswert gelesen; `false`, `0`, `no` oder `off` in einem von beiden schaltet die Host-Prüfungen so aus, wie es `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` tut – selbst wenn der Dashboard-Schalter dieses Flags als aus gespeichert ist; andere Werte überlassen den Guard den beiden Flags darunter. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Schaltet die Host-Prüfungen des Guards für ausgehende URLs aus – einschließlich der Cloud-Metadaten-Blockierung (der gesamte Bereich 169.254.0.0/16, metadata.google.internal) – auf den Provider-Pfaden, die dem Guard-Modus folgen: URL-Validierung, Modellermittlung, Basis-URLs von Provider-Knoten (einschließlich entfernter Rerank-Knoten) und der Proxy-Fallback-Test, sowie weitere providernahe ausgehende Pfade wie das Abrufen entfernter Bilder/Medien. Erlaubt außerdem private Webhook-Ziele, mit Ausnahme von Cloud-Metadaten-/Link-Local-Endpunkten, die auch bei aktiviertem Flag blockiert bleiben. Chat-Anfragen, die über `BaseExecutor.execute()` versendet werden, behalten ihre Cloud-Metadaten-Prüfung. Lokale Provider funktionieren mit den Standardwerten: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (Standard `true`) erlaubt lokale und LAN-URLs auf den Validierungs-, Ermittlungs- und Provider-Knoten-Pfaden bereits; der Proxy-Fallback-Test und private Webhook-Ziele beachten nur dieses Flag und blockieren lokale/LAN-Hosts, solange es aus ist. Überschreibt `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Ein über den Dashboard-Schalter gespeicherter Wert hat Vorrang vor dieser Variable. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Erlaubt Provider-URLs unter lokalen/privaten Adressen (127.0.0.1, localhost, LAN, private Bereiche) für die Provider-Validierung, Modellermittlung, Chat und die weiteren providernahen ausgehenden Pfade, die der Guard-Modus regelt. **Standardwert `true`** (Local-first): Der Guard blockiert dann Cloud-Metadaten-Endpunkte – der gesamte Bereich 169.254.0.0/16 plus die bekannten Metadaten-Hostnamen (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Für den Nur-öffentlich-Modus auf `false` setzen – dieser blockiert zusätzlich private und Loopback-Hosts. Der Guard prüft den Hostnamen oder das IP-Literal wie geschrieben, vor jeder DNS-Auflösung; im Nur-öffentlich-Modus löst der Safe-Fetch-Wrapper zusätzlich den Hostnamen auf und lehnt jede private Antwort ab. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` schaltet diese Prüfungen auf den in seiner Zeile aufgeführten Pfaden aus. Eingebaute lokale Provider wie LM Studio, Ollama, vLLM, Llamafile und Triton überspringen sämtliche Host-Prüfungen bei Schlüsselvalidierung und Chat. Ein über den Dashboard-Schalter gespeicherter Wert hat Vorrang vor dieser Variable. (#5066) | | `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ermöglicht den `/v1/audio/*`-Routen (Transkriptionen, Sprachausgabe, Übersetzungen), einen außerhalb von localhost gehosteten OpenAI-kompatiblen Provider-Knoten zu verwenden. Standardmäßig deaktiviert – das Weiterleiten von Audio an einen entfernten Host ändert die Egress-Identität und muss eine ausdrückliche Entscheidung des Betreibers sein. Loopback-/private Knoten (localhost, 127.0.0.1, 172.16-31.x) sind stets zulässig und nicht betroffen. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ermöglicht `POST /v1/rerank` (und dem Loopback-Rerank-Schritt der Memory Engine), einen außerhalb von localhost gehosteten OpenAI-kompatiblen Provider-Knoten zu verwenden – einen LAN-Rechner oder Tailscale-Peer, auf dem TEI, Infinity, vLLM usw. ausgeführt wird. Standardmäßig deaktiviert – das Routing zu einem entfernten Host ändert die Egress-Identität und muss eine ausdrückliche Entscheidung des Betreibers sein. Loopback-Knoten (localhost, 127.0.0.1, 172.16-31.x) sind stets zulässig und nicht betroffen. Entfernte Knoten müssen außerdem die Richtlinie für ausgehende Provider-URLs erfüllen (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); Cloud-Metadaten-Hosts werden niemals als Routing-Ziel verwendet. | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ermöglicht `POST /v1/rerank` (und dem Loopback-Rerank-Schritt der Memory Engine), einen außerhalb von localhost gehosteten OpenAI-kompatiblen Provider-Knoten zu verwenden – einen LAN-Rechner oder Tailscale-Peer, auf dem TEI, Infinity, vLLM usw. ausgeführt wird. Standardmäßig deaktiviert – das Routing zu einem entfernten Host ändert die Egress-Identität und muss eine ausdrückliche Entscheidung des Betreibers sein. Loopback-Knoten (localhost, 127.0.0.1, 172.16-31.x) sind stets zulässig und nicht betroffen. Die Basis-URL eines entfernten Knotens muss außerdem die Richtlinie für ausgehende Provider-URLs erfüllen (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Wenn OIDC aktiviert ist, wird die Passwortanmeldung deaktiviert, sodass sich Benutzer nur über OIDC Single Sign-On authentifizieren können. Der einfache Alias `OIDC_DISABLE_PASSWORD_LOGIN` wird ebenfalls akzeptiert; das Dashboard-Feature-Flag mit demselben Schlüssel hat Vorrang. (#10889) | | `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Einfacher Alias von `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | diff --git a/docs/i18n/el/CONTRIBUTING.md b/docs/i18n/el/CONTRIBUTING.md index 0dde3acf9cb0..b56ae64832ac 100644 --- a/docs/i18n/el/CONTRIBUTING.md +++ b/docs/i18n/el/CONTRIBUTING.md @@ -392,22 +392,24 @@ docs/ --- -## Λίστα Ελέγχου Pull Request +## Λίστα ελέγχου Pull Request -- [ ] Τα tests περνούν (`npm test`) -- [ ] Το linting περνά (`npm run lint`) +- [ ] Οι δοκιμές ολοκληρώνονται επιτυχώς (`npm test`) +- [ ] Ο έλεγχος lint ολοκληρώνεται επιτυχώς (`npm run lint`) - [ ] Το build ολοκληρώνεται επιτυχώς (`npm run build`) -- [ ] Προστέθηκαν TypeScript types για νέες δημόσιες συναρτήσεις και interfaces -- [ ] Δεν υπάρχουν hardcoded secrets ή fallback τιμές -- [ ] Τα δημόσια upstream διαπιστευτήρια ενσωματώνονται μέσω `resolvePublicCred()` (δείτε [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ποτέ ως literals -- [ ] Οι αποκρίσεις σφαλμάτων δρομολογούνται μέσω `buildErrorBody()` / `sanitizeErrorMessage()` — χωρίς raw stack traces στα σώματα απόκρισης (δείτε [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Οι εντολές shell (`exec` / `spawn`) μεταβιβάζουν τιμές runtime μέσω `env`, όχι μέσω string interpolation -- [ ] Όλες οι εισόδοι επικυρώνονται με Zod schemas -- [ ] Προστέθηκε **fragment** Changelog στο `changelog.d/{features|fixes|maintenance}/-.md` για αλλαγές που επηρεάζουν τον χρήστη (δείτε [`changelog.d/README.md`](./changelog.d/README.md)) — **μην** επεξεργάζεστε απευθείας το `CHANGELOG.md`· τα fragments συγκεντρώνονται κατά την έκδοση και δεν δημιουργούν ποτέ συγκρούσεις μεταξύ PRs -- [ ] Η τεκμηρίωση ενημερώθηκε (εάν απαιτείται) -- [ ] Δεν ανοίχτηκαν νέες ειδοποιήσεις CodeQL / Secret-Scanning, ή κάθε μία απορρίφθηκε με τεχνική αιτιολόγηση που παραπέμπει στο σχετικό έγγραφο `docs/security/` -- [ ] Τα routes που εκκινούν child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) ταξινομούνται ως `isLocalOnlyPath()` στο `src/server/authz/routeGuard.ts` — δείτε [Κανόνα #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Δεν υπάρχουν trailers `Co-Authored-By` στα μηνύματα commit — τα commits πρέπει να εμφανίζονται αποκλειστικά υπό την Git ταυτότητα του ιδιοκτήτη του αποθετηρίου (Κανόνας #16) +- [ ] Προστέθηκαν τύποι TypeScript για νέες δημόσιες συναρτήσεις και διεπαφές +- [ ] Δεν υπάρχουν hardcoded μυστικά ή εφεδρικές τιμές +- [ ] Τα δημόσια διαπιστευτήρια upstream ενσωματώνονται μέσω `resolvePublicCred()` (βλ. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) και ποτέ ως literals +- [ ] Οι αποκρίσεις σφαλμάτων δρομολογούνται μέσω `buildErrorBody()` / `sanitizeErrorMessage()` — δεν υπάρχουν ανεπεξέργαστα stack traces στα σώματα αποκρίσεων (βλ. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Οι εντολές shell (`exec` / `spawn`) μεταβιβάζουν τις τιμές χρόνου εκτέλεσης μέσω `env` και όχι μέσω παρεμβολής συμβολοσειρών +- [ ] Όλες οι είσοδοι επικυρώνονται με σχήματα Zod +- [ ] Προστέθηκε **τμήμα** changelog στον κατάλογο `changelog.d/{features|fixes|maintenance}/-.md` για αλλαγές που επηρεάζουν τους χρήστες (βλ. [`changelog.d/README.md`](./changelog.d/README.md)) — **μην** επεξεργάζεστε απευθείας το `CHANGELOG.md`· τα τμήματα συγκεντρώνονται κατά την έκδοση και δεν δημιουργούν ποτέ διενέξεις μεταξύ PR +- [ ] Η τεκμηρίωση ενημερώθηκε (εφόσον απαιτείται) +- [ ] Δεν δημιουργήθηκαν νέες ειδοποιήσεις CodeQL / Secret-Scanning ή καθεμία απορρίφθηκε με τεχνική αιτιολόγηση που παραπέμπει στο σχετικό έγγραφο του `docs/security/` +- [ ] Οι διαδρομές που εκκινούν θυγατρικές διεργασίες (`/api/mcp/`, `/api/cli-tools/runtime/`) ταξινομούνται ως `isLocalOnlyPath()` στο `src/server/authz/routeGuard.ts` — βλ. [Αυστηρός κανόνας #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Δεν υπάρχουν trailers `Co-authored-by` από AI/bot στα μηνύματα commit (Αυστηρός κανόνας #16) — οι άνθρωποι συνεργάτες των οποίων η εργασία επαναχρησιμοποιείται αναφέρονται με τα τυπικά trailers `Co-authored-by: Name ` + +--- ## Κυκλοφορία Εκδόσεων diff --git a/docs/i18n/el/docs/architecture/QUALITY_GATES.md b/docs/i18n/el/docs/architecture/QUALITY_GATES.md index 423379a814b1..3b9076971ff9 100644 --- a/docs/i18n/el/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/el/docs/architecture/QUALITY_GATES.md @@ -17,274 +17,262 @@ ## Κατάλογος πυλών και προφίλ εκτέλεσης -### Αποδοχή υποψηφίου +### Αποδοχή υποψηφίων Οι ροές εργασίας CI και Quality Gates παράγουν καθεμία μια σταθερή ετυμηγορία: `Gate / CI` και -`Gate / Quality`. Η πολιτική αποδοχής τους με εκδόσεις απαριθμεί κάθε ανάντη εργασία -ως υποχρεωτική ή συμβουλευτική. Μια ισχύουσα υποχρεωτική εργασία πρέπει να ολοκληρωθεί επιτυχώς: -αποτελέσματα που λείπουν, έχουν ακυρωθεί, παραλειφθεί, εκκρεμούν ή είναι άγνωστα δεν μπορούν να -θεμελιώσουν κατάσταση PASS. Μια έγκυρη ταξινόμηση μόνο για τεκμηρίωση ή μόνο για τον κατάλογο -μπορεί να καταστήσει μη εφαρμόσιμη μια διαδρομή κώδικα· ένα πρόχειρο PR δεν αποτελεί αποδεκτό -υποψήφιο. Μια ετικέτα `hotfix` δεν καταργεί την απαίτηση παροχής αποδεικτικών στοιχείων. - -Και οι δύο ροές εργασίας καλύπτουν PR και push προς τους κύριους κλάδους/κλάδους έκδοσης, -χειροκίνητη εκτέλεση και συμβάντα ομάδας συγχώνευσης. Τα push, η χειροκίνητη εκτέλεση και οι -ομάδες συγχώνευσης εκτελούν την πλήρη επιλογή. Τα fork και οι ομάδες συγχώνευσης χρησιμοποιούν -φιλοξενούμενους runner για εργασίες που διαφορετικά θα επέλεγαν αυτοφιλοξενούμενους runner· -πριν από τη διάθεση πρέπει να επαληθευτεί ότι υπάρχει επαρκής φιλοξενούμενη χωρητικότητα. - -Κάθε απόδειξη JSON προσδιορίζει το SHA που έγινε checkout, την εκτέλεση της ροής εργασίας και -την απόπειρα. Το CLI απορρίπτει μια ασυμφωνία SHA μεταξύ checkout και συμβάντος. Οι δοκιμές της -ροής εργασίας συνδέουν τη συμμετοχή στην πολιτική με τη λίστα `needs` της εργασίας ετυμηγορίας, -ώστε μια νέα ή καταργημένη διαδρομή να μην μπορεί να εξαφανιστεί σιωπηρά. Οι αποδείξεις -καλύπτουν τη δική τους ροή εργασίας, όχι τη δημοσίευση, την ανάπτυξη ή τα εσωτερικά στοιχεία -ενός υπάρχοντος συμβουλευτικού σαρωτή. Η ενεργοποίηση και των δύο ονομάτων ελέγχου στους -κανόνες κλάδων αποτελεί ξεχωριστή διαχειριστική αλλαγή· η προσθήκη αυτών των εργασιών δεν -προστατεύει από μόνη της έναν κλάδο. +`Gate / Quality`. Η πολιτική αποδοχής τους, με διαχείριση εκδόσεων, απαριθμεί κάθε προηγούμενη εργασία +ως υποχρεωτική ή συμβουλευτική. Μια εφαρμοστέα υποχρεωτική εργασία πρέπει να ολοκληρωθεί επιτυχώς: +αποτελέσματα που λείπουν, έχουν ακυρωθεί, παραλειφθεί, εκκρεμούν ή είναι άγνωστα δεν μπορούν να θεμελιώσουν PASS. Μια έγκυρη +ταξινόμηση μόνο για τεκμηρίωση ή μόνο για κατάλογο μπορεί να καταστήσει μη εφαρμοστέα μια λωρίδα κώδικα· +ένα πρόχειρο PR δεν αποτελεί αποδεκτό υποψήφιο. Μια ετικέτα `hotfix` δεν αίρει την απαίτηση αποδεικτικών στοιχείων. + +Και οι δύο ροές εργασίας καλύπτουν PR και push προς τους κύριους κλάδους/κλάδους έκδοσης, χειροκίνητη ενεργοποίηση και +συμβάντα ομάδας συγχώνευσης. Τα push, dispatch και merge-group εκτελούν την πλήρη επιλογή. Τα fork +και οι ομάδες συγχώνευσης χρησιμοποιούν φιλοξενούμενους runners για εργασίες που διαφορετικά επιλέγουν αυτοφιλοξενούμενους +runners· πριν από τη διάθεση πρέπει να επαληθεύεται η επαρκής φιλοξενούμενη χωρητικότητα. + +Κάθε απόδειξη JSON προσδιορίζει το SHA που έχει ανακτηθεί, την εκτέλεση της ροής εργασίας και την απόπειρα. +Το CLI απορρίπτει αναντιστοιχία SHA μεταξύ checkout και συμβάντος. Οι δοκιμές ροής εργασίας συνδέουν τη συμμετοχή στην πολιτική +με τη λίστα `needs` της εργασίας ετυμηγορίας, ώστε μια νέα ή καταργημένη λωρίδα να μην μπορεί να εξαφανιστεί σιωπηρά. +Οι αποδείξεις καλύπτουν τη δική τους ροή εργασίας, όχι τη δημοσίευση, την ανάπτυξη ή τις εσωτερικές λειτουργίες +ενός υφιστάμενου συμβουλευτικού σαρωτή. Η ενεργοποίηση και των δύο ονομάτων ελέγχου στους κανόνες κλάδων αποτελεί +ξεχωριστή διαχειριστική αλλαγή· η προσθήκη αυτών των εργασιών δεν προστατεύει από μόνη της έναν κλάδο. ### Κατάλογος στατικής σάρωσης -Ο κατάλογος ψευδωνύμων npm με εκδόσεις και η συμμετοχή στη στατική σάρωση βρίσκονται στο -`config/quality/gate-manifest.json`. Εκτελέστε `npm run check:gate-manifest` για να -επικυρώσετε τα ονόματα των script και τις ακριβείς εντολές σε αντιπαραβολή με το -`package.json`· προσθήκες, καταργήσεις και αποκλίσεις εντολών προκαλούν αποτυχία τόσο στο -τοπικό hook όσο και στις εργασίες ταξινόμησης αλλαγών στο CI. Ένα ψευδώνυμο δεν είναι -εργασία ροής εργασίας, στιγμιότυπο matrix ή περίπτωση δοκιμής: αυτοί οι αριθμοί δεν πρέπει -να παρουσιάζονται ως εναλλάξιμοι. +Ο κατάλογος npm-alias με διαχείριση εκδόσεων και η συμμετοχή στη στατική σάρωση βρίσκονται στο +`config/quality/gate-manifest.json`. Εκτελέστε `npm run check:gate-manifest` για να επικυρώσετε +τα ονόματα των script και τις ακριβείς εντολές σε σχέση με το `package.json`· προσθήκες, καταργήσεις και +αποκλίσεις εντολών προκαλούν αποτυχία τόσο του τοπικού hook όσο και των εργασιών ταξινόμησης αλλαγών στο CI. +Ένα alias δεν είναι εργασία ροής εργασίας, στιγμιότυπο πίνακα ή περίπτωση δοκιμής: αυτές οι μετρήσεις δεν πρέπει +να παρουσιάζονται ως εναλλάξιμες. Χρησιμοποιήστε `npm run quality:scan -- --list` ή `npm run quality:scan:fast -- --list` -για να εξετάσετε τα επιλεγμένα ψευδώνυμα χωρίς να τα εκτελέσετε. Ο runner καλεί το -σημείο εισόδου npm, επομένως διατηρείται το περιβάλλον εκτέλεσής του (συμπεριλαμβανομένου -του Bun όπου έχει ρυθμιστεί). Το manifest καταγράφει τα ψευδώνυμα εκτός αυτών των προφίλ -ως καλούμενα ξεχωριστά, ενώ οι εντολές συντήρησης απαγορεύονται σε προφίλ σάρωσης μόνο -για ανάγνωση. - -Αυτά τα προφίλ καλύπτουν μόνο τη στατική σάρωση. Δεν πιστοποιούν τις δοκιμές του προϊόντος, -την κάλυψη, τη δημιουργία πακέτων, τους εξωτερικούς ελέγχους ή την πλήρη αποδοχή έκδοσης -ενός υποψηφίου. Η αποδοχή από τη ροή εργασίας χρησιμοποιεί τα συνδεδεμένα -`config/quality/admission-policy.json` και `scripts/quality/admission-verdict.mjs`. -Τα προφίλ παρατήρησης έκδοσης παραμένουν ξεχωριστά· εξετάστε ανεξάρτητα τους ισχύοντες -ελέγχους και τις αποδείξεις τους. Ο παρακάτω περιγραφικός κατάλογος αποτελεί αναφορά και -όχι απόδειξη ότι μια πύλη εκτελέστηκε πράγματι. - -Τα script βρίσκονται στους καταλόγους `scripts/check/` (πύλες πολιτικής) και -`scripts/quality/` (μηχανισμός ratchet). +για να επιθεωρήσετε τα επιλεγμένα alias χωρίς να τα εκτελέσετε. Ο runner καλεί το +σημείο εισόδου npm, επομένως διατηρείται το περιβάλλον εκτέλεσής του (συμπεριλαμβανομένου του Bun όπου έχει ρυθμιστεί). +Το manifest καταγράφει τα alias εκτός αυτών των προφίλ ως εκτελούμενα ξεχωριστά, ενώ +οι εντολές συντήρησης απαγορεύονται σε προφίλ σάρωσης μόνο για ανάγνωση. + +Αυτά τα προφίλ καλύπτουν μόνο τη στατική σάρωση. Δεν πιστοποιούν τις δοκιμές προϊόντος, +την κάλυψη, τη δημιουργία πακέτων, τους εξωτερικούς ελέγχους ή την πλήρη αποδοχή έκδοσης ενός υποψηφίου. +Η αποδοχή από τη ροή εργασίας χρησιμοποιεί τα συνδεδεμένα `config/quality/admission-policy.json` και +`scripts/quality/admission-verdict.mjs`. Τα προφίλ παρατηρητή έκδοσης παραμένουν ξεχωριστά· +επιθεωρείτε ανεξάρτητα τους εφαρμοστέους ελέγχους και τις αποδείξεις τους. Ο παρακάτω περιγραφικός +κατάλογος αποτελεί αναφορά και όχι απόδειξη ότι μια πύλη πράγματι εκτελέστηκε. + +Τα script βρίσκονται στους καταλόγους `scripts/check/` (πύλες πολιτικής) και `scripts/quality/` (μηχανισμός ratchet). Η πηγή αλήθειας του CI είναι το `.github/workflows/ci.yml`. ### Ταχεία διαδρομή PR έκδοσης (`quality.yml`) -Το `.github/workflows/quality.yml` συμπληρώνει το CI για PR προς τους κύριους κλάδους/κλάδους -έκδοσης, push σε προστατευμένους κλάδους, χειροκίνητη εκτέλεση και ομάδες συγχώνευσης. -Τα PR χρησιμοποιούν γρήγορους ελέγχους φιλτραρισμένους βάσει διαδρομών. Η μόνιμα -απενεργοποιημένη διπλότυπη δημιουργία αφαιρέθηκε· οι πραγματικοί έλεγχοι -δημιουργίας/πακεταρίσματος/εκκίνησης παραμένουν στο CI. - -| Εργασία | Πεδίο εφαρμογής | Αποκλεισμός | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| `Docs Gates (fast-path)` | PR τεκμηρίωσης/κώδικα· αναφορές τεκμηρίωσης API και συνολικός έλεγχος τεκμηρίωσης | Ναι | -| `Fast Quality Gates` | PR κώδικα· στατικοί έλεγχοι, έλεγχος τύπων, έλεγχος τύπων πίνακα ελέγχου, επηρεαζόμενες δοκιμές μονάδας | Ναι | -| `Forgotten sibling tests` | PR κώδικα· τα τροποποιημένα αρθρώματα ανιχνεύονται έως τους στατικούς καταναλωτές και τις υποψήφιες συγγενικές δοκιμές· οι διαδρομές barrel και δυναμικής εισαγωγής αναφέρονται ως συμβουλευτικά διαγνωστικά, με τις αναφερόμενες εξαιρέσεις λίστας επιτρεπόμενων | **Συμβουλευτικό** | -| `Vitest (fast-path)` | PR κώδικα· γρήγορη σουίτα vitest | Ναι | -| `Unit Tests fast-path` | PR κώδικα· σουίτα δοκιμών μονάδας σε 4 shard | Ναι | -| `No new ESLint warnings` | PR κώδικα· έλεγχος lint με επίγνωση των καταστολών | Ναι, συμπεριλαμβανομένων των fork | -| `Merge integrity (changelog + generated skills)` | Μη πρόχειρα PR· συγχρονισμός changelog και παραγόμενων skill | Ναι, συμπεριλαμβανομένων των fork | +Το `.github/workflows/quality.yml` συμπληρώνει το CI για PR προς κύριους κλάδους/κλάδους έκδοσης, push σε προστατευμένους κλάδους, +dispatch και ομάδες συγχώνευσης. Τα PR χρησιμοποιούν γρήγορους ελέγχους φιλτραρισμένους βάσει διαδρομής. Η μόνιμα +απενεργοποιημένη διπλότυπη δημιουργία αφαιρέθηκε· οι πραγματικοί έλεγχοι δημιουργίας/πακεταρίσματος/εκκίνησης παραμένουν στο CI. + +| Εργασία | Πεδίο εφαρμογής | Αποκλεισμός | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| `Docs Gates (fast-path)` | PR τεκμηρίωσης/κώδικα· αναφορές τεκμηρίωσης API και docs-all | Ναι | +| `Fast Quality Gates` | PR κώδικα· στατικοί έλεγχοι, typecheck, typecheck πίνακα εργαλείων, επηρεαζόμενες δοκιμές μονάδας | Ναι | +| `Forgotten sibling tests` | PR κώδικα· τα αλλαγμένα module ανιχνεύονται έως τους στατικούς καταναλωτές και τις υποψήφιες συγγενικές δοκιμές· οι διαδρομές barrel και δυναμικού import αναφέρονται ως συμβουλευτικά διαγνωστικά, με αναφερόμενες εξαιρέσεις allowlist | **Συμβουλευτικό** | +| `Vitest (fast-path)` | PR κώδικα· γρήγορη σουίτα vitest | Ναι | +| `Unit Tests fast-path` | PR κώδικα· σουίτα δοκιμών μονάδας 4 shard | Ναι | +| `No new ESLint warnings` | PR κώδικα· προστασία lint που λαμβάνει υπόψη τις καταστολές | Ναι, συμπεριλαμβανομένων των fork | +| `Merge integrity (changelog + generated skills)` | Μη πρόχειρα PR· συγχρονισμός changelog και παραγόμενων skill | Ναι, συμπεριλαμβανομένων των fork | #### Αναφορά ξεχασμένων συγγενικών δοκιμών -Το `npm run check:forgotten-sibling-tests` επαναχρησιμοποιεί τον επιλυτή εισαγωγών στον οποίο -βασίζεται ο χάρτης επίδρασης δοκιμών. Για κάθε τροποποιημένο άρθρωμα παραγωγής, αναφέρει -ντετερμινιστικές αλυσίδες +Το `npm run check:forgotten-sibling-tests` επαναχρησιμοποιεί τον επιλυτή import που βρίσκεται πίσω από τον χάρτη επίδρασης δοκιμών. +Για κάθε αλλαγμένο module παραγωγής, αναφέρει ντετερμινιστικές αλυσίδες `changed module/symbol -> static consumer -> candidate sibling test` όταν η υποψήφια -δοκιμή απουσιάζει από το diff του pull request. Η σύνοψη Markdown και το αποτέλεσμα JSON -διατηρούνται ως artifact ροής εργασίας `forgotten-sibling-tests` για βαθμονόμηση πριν από -οποιαδήποτε διάθεση με αποκλεισμό. +δοκιμή απουσιάζει από το diff του pull request. Η σύνοψη Markdown και το αποτέλεσμα JSON διατηρούνται ως +το artifact ροής εργασίας `forgotten-sibling-tests` για βαθμονόμηση πριν από οποιαδήποτε διάθεση με αποκλεισμό. -Οι επανεξαγωγές barrel και οι δυναμικές εισαγωγές χρησιμοποιούνται μόνο για διαγνωστικά επίλυσης· δεν δημιουργούν ποτέ +Οι επανεξαγωγές barrel και τα δυναμικά imports αποτελούν μόνο διαγνωστικά επίλυσης· δεν δημιουργούν ποτέ εύρημα που εμποδίζει τη συγχώνευση. Οι εγκεκριμένες εξαιρέσεις βρίσκονται στο `config/quality/forgotten-sibling-allowlist.json`. Κάθε καταχώριση πρέπει να κατονομάζει το consumer και το υποψήφιο -test, να παρέχει συγκεκριμένη αιτιολόγηση και να παραπέμπει σε issue ή pull request στο GitHub. Οι κακοσχηματισμένες καταχωρίσεις +test, να παρέχει συγκεκριμένη αιτιολόγηση και να παραπέμπει σε GitHub issue ή pull request. Οι εσφαλμένα διαμορφωμένες καταχωρίσεις αποτυγχάνουν με ασφαλή τρόπο. Οι εξαιρέσεις δεν μπορούν να παρακάμψουν ένα διαγραμμένο υποψήφιο test ή ένα diff που προσθέτει `.skip`/`.todo`· -η αποδυνάμωση των assertions και άλλες μορφές απόκρυψης παραμένουν υπό την ευθύνη της ανεξάρτητα δεσμευτικής +η αποδυνάμωση assertions και άλλες μορφές συγκάλυψης παραμένουν στην αρμοδιότητα της ανεξάρτητα δεσμευτικής πύλης `check:test-masking`. ### Εργασία: `lint` Εκτελείται σε κάθε PR προς το `main`. Εμποδίζει τη συγχώνευση σε περίπτωση αποτυχίας. -| Script (`npm run ...`) | Επικυρώνει | Δεσμευτικό | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| `check:node-runtime` | Η έκδοση του Node.js βρίσκεται εντός του υποστηριζόμενου εύρους | Ναι | -| `check:cycles` | Κυκλικές εισαγωγές — όλες οι μονάδες των `src/` + `open-sse/` | Ναι | -| `check:route-validation:t06` | Υπάρχουν σχήματα Zod σε όλες τις διαδρομές (πολιτική Tier 6) | Ναι | -| `check:any-budget:t11` | Το πλήθος των `@ts-expect-error // any` δεν υπερβαίνει το όριο (catraca Tier 11) | Ναι | -| `check:provider-consistency` | Κάθε πάροχος στο `providers.ts` έχει αντίστοιχη καταχώριση στο `providerRegistry.ts` (και αντίστροφα, εντός της λίστας επιτρεπόμενων) | Ναι | -| `check:model-lifecycle` | Οι τρεις πίνακες δρομολόγησης που συντηρούνται χειροκίνητα παραμένουν συνεπείς με το στιγμιότυπο κύκλου ζωής που έχει καταχωριστεί (#11503): το `FITNESS_TABLE` (`taskFitness.ts`) δεν βαθμολογεί κανένα αποσυρμένο id που μπορεί να δρομολογήσει το `REGISTRY`· κάθε προορισμός του `BUILT_IN_ALIASES` υπάρχει στο `REGISTRY` και απουσιάζει από το στιγμιότυπο αποσυρμένων id· κάθε αποσυρμένο id που εξακολουθεί να βρίσκεται στο `REGISTRY` προωθείται ή παρατίθεται στο `allowedRetiredInCatalog`· και καμία πηγή ή προορισμός του `DEFAULT_DEGRADATION_MAP` δεν εμφανίζεται ως αποσυρμένος σε αυτό το στιγμιότυπο. Αυτό δεν αποδεικνύει ότι ένα μοντέλο εξυπηρετείται επί του παρόντος από ενεργή ανάντη υπηρεσία. Εκτός σύνδεσης — συγκρίνει με το `config/quality/model-lifecycle.json`, το οποίο ανανεώνεται χειροκίνητα με `npm run quality:refresh-model-lifecycle` (απαιτεί δίκτυο· δεν είναι ενσωματωμένο στο CI). Το `allowedRetiredInCatalog` είναι ένας μηχανισμός σταδιακής μείωσης: προσθέστε μια καταχώριση μόνο μαζί με ένα ζήτημα παρακολούθησης. | Ναι | -| `check:fetch-targets` | Κάθε `fetch("/api/...")` στο `src/` της πλευράς πελάτη αντιστοιχεί σε ένα πραγματικό `route.ts` | Ναι | -| `check:deps` | Όλες οι εξαρτήσεις που μπορούν να εγκατασταθούν με `npm install` σε κάθε `package.json` του αποθετηρίου περιλαμβάνονται στο `dependency-allowlist.json`· επισημαίνονται τα νέα πακέτα χωρίς κλειδωμένη έκδοση ή με ονόματα που ενδέχεται να αποτελούν slopsquatting | Ναι | -| `audit:deps` | `npm audit` (ριζικός κατάλογος + electron) — χωρίς ειδοποιήσεις υψηλής/κρίσιμης σοβαρότητας (επικαλύπτεται με το osv `check:vuln-ratchet`· βλ. Αν backlog εξορθολογισμού) | Ναι | -| `check:lockfile` | Ακεραιότητα του `package-lock.json` — μητρώο https, κατακερματισμοί ακεραιότητας, χωρίς παρακάμψεις κεντρικού υπολογιστή | Ναι | -| `check:licenses` | Λίστα επιτρεπόμενων αδειών SPDX για εξαρτήσεις παραγωγής | Ναι | -| `check:tracked-artifacts` | Χωρίς τεχνουργήματα μεταγλώττισης / δεσμευμένους συμβολικούς συνδέσμους `node_modules` (εκτελείται επίσης στο husky pre-commit· το pre-push είναι σκόπιμα ελαφρύ — #6716) | Ναι | -| `check:ai-attribution` | Χωρίς καταληκτική γραμμή `Co-Authored-By` από AI/bot ή υποσέλιδο δημιουργίας μέσω AI στα commits, στον τίτλο ή στο σώμα των PR — Αυστηρός Κανόνας #16 (στον βρόχο fast-gates του `quality.yml` για PR→`release/**` — διαβάζει το payload του συμβάντος, δεν εκτελεί καμία ενέργεια εκτός PR — και σε ένα βήμα μόνο για PR στο lint του `ci.yml` για PR→`main`· επίσης στο hook `commit-msg` του husky· επιτρέπονται ανθρώπινοι συν-συγγραφείς· #14436) | -| `check:vitest-exclusions` | Κάθε εξαίρεση του Vitest κατονομάζει ένα ζήτημα παρακολούθησης και εμφανίζεται στο `config/quality/vitest-exclusions.json` (#13204) | Ναι | -| `check:file-size` | Κανένα αρχείο πηγαίου κώδικα δεν υπερβαίνει το όριο ανά επέκταση (καστάνια: παγωμένα μεγάλα αρχεία στη λίστα `frozen`) | Ναι | -| `check:error-helper` | Οι αποκρίσεις σφάλματος σε εκτελεστές/χειριστές χρησιμοποιούν `buildErrorBody()` / `sanitizeErrorMessage()` (Αυστηρός Κανόνας #12) | Ναι | -| `check:migration-numbering` | Τα αρχεία SQL μετεγκατάστασης έχουν διαδοχική αρίθμηση, χωρίς κενά ή διπλότυπα | Ναι | -| `check:public-creds` | Δεν υπάρχουν κυριολεκτικές τιμές OAuth `client_id`/`client_secret` ή κλειδιά Firebase Web εκτός του `publicCreds.ts` (Απαράβατος κανόνας #11) | Ναι | -| `check:db-rules` | Δεν υπάρχει ανεπεξέργαστη SQL εκτός των αρθρωμάτων `src/lib/db/`· δεν υπάρχουν συγκεντρωτικές εισαγωγές από το `localDb.ts` (Απαράβατοι κανόνες #2/#5) | Ναι | -| `check:known-symbols` | Οι εκτελεστές παρόχων, οι στρατηγικές δρομολόγησης και οι μεταφραστές που είναι καταχωρισμένοι στους πίνακες διεκπεραίωσής τους αντιστοιχούν στα αρχεία στον δίσκο — χωρίς ορφανά ή μη δηλωμένα σύμβολα | Ναι | -| `check:route-guard-membership` | Κάθε διαδρομή που δημιουργεί μια θυγατρική διεργασία ταξινομείται από τη `isLocalOnlyPath()` (Απαράβατοι κανόνες #15/#17) | Ναι | -| `check:test-discovery` | Κάθε αρχείο `*.test.ts` / `*.spec.ts` στο αποθετήριο συλλέγεται από τουλάχιστον έναν εκτελεστή δοκιμών (καστάνια: η λίστα ορφανών στο `test-discovery-baseline.json` μπορεί μόνο να μειώνεται) | Ναι | -| `check:agent-skills-sync` | Τα παραγόμενα τεχνουργήματα agent-skills αντιστοιχούν στον κατάλογο προέλευσής τους (χωρίς αποκλίσεις) | -| `check:provider-asset-provenance` | Τα λογότυπα/τεχνουργήματα των παρόχων διαθέτουν καταγεγραμμένη καταχώριση προέλευσης | -| `lint:json` | Τα αρχεία ρυθμίσεων JSON αναλύονται επιτυχώς και πληρούν τους κανόνες lint του αποθετηρίου | -| `typecheck:core` | Μεταγλώττιση TypeScript χωρίς σφάλματα (μόνο συμβουλευτικές προειδοποιήσεις) | Ναι | -| `typecheck:noimplicit:core` | Αυστηρό `noImplicitAny` — μελλοντικός στόχος· πολλά προϋπάρχοντα σημεία κλήσης εξακολουθούν να χρειάζονται επισημειώσεις | **Συμβουλευτικό** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | Το `tsc` περιορίζεται στο `src/app/(dashboard)/**` (#7033) — η επιμελημένη λίστα επιτρεπόμενων 27 αρχείων του `typecheck:core` δεν περιλαμβάνει κανένα TSX του πίνακα ελέγχου, ενώ ούτε το `next build` εκτελεί ποτέ έλεγχο τύπων σε αυτά (το `next.config.mjs` ορίζει `ignoreBuildErrors: true`), επομένως οι παλινδρομήσεις ορφανών αναγνωριστικών εκεί (#6625/#6909) παρέμεναν αόρατες στο CI. Γίνεται σύγκριση με μια παγιωμένη γραμμή βάσης καταμέτρησης ανά αρχείο/ανά κωδικό TS (`config/quality/dashboard-typecheck-baseline.json`, με το ίδιο μοτίβο επιβολής για παρωχημένες καταχωρίσεις όπως το `check:known-symbols`) — μόνο ΝΕΑ σφάλματα πέρα από την καταγεγραμμένη γραμμή βάσης αποτυγχάνουν την πύλη· μειώστε σταδιακά τη γραμμή βάσης με `--update` όταν διορθώνεται ένα προϋπάρχον σφάλμα. | Ναι | +| Script (`npm run ...`) | Επικυρώνει | Δεσμευτικό | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `check:node-runtime` | Η έκδοση του Node.js βρίσκεται εντός του υποστηριζόμενου εύρους | Ναι | +| `check:cycles` | Κυκλικά imports σε ολόκληρα τα `src/` + `open-sse/` (βασισμένο σε AST, με επίλυση των `paths` του tsconfig). Η απλή εκτέλεση είναι συμβουλευτική και παραθέτει τους κύκλους. Το `check:cycles:ratchet` (αυτό που εκτελεί το CI) εμποδίζει τη συγχώνευση όταν ο αριθμός υπερβαίνει το ανώτατο όριο `metrics.cycles` στο `quality-baseline.json` — επί του παρόντος 14, `direction: down`, επομένως μπορεί μόνο να μειωθεί (#15159 G-01/G-02) | Ναι (ratchet) | +| `check:route-validation:t06` | Παρουσία σχημάτων Zod σε όλα τα routes (πολιτική Tier 6) | Ναι | +| `check:any-budget:t11` | Ο αριθμός των `@ts-expect-error // any` δεν υπερβαίνει το επιτρεπόμενο όριο (catraca Tier 11) | Ναι | +| `check:provider-consistency` | Κάθε πάροχος στο `providers.ts` έχει μια αντίστοιχη καταχώριση στο `providerRegistry.ts` (και αντιστρόφως, εντός της λίστας επιτρεπόμενων) | Ναι | +| `check:model-lifecycle` | Οι τρεις πίνακες δρομολόγησης που συντηρούνται χειροκίνητα παραμένουν συνεπείς με το στιγμιότυπο κύκλου ζωής που έχει υποβληθεί στο αποθετήριο (#11503): το `FITNESS_TABLE` (`taskFitness.ts`) δεν βαθμολογεί κανένα καταργημένο id που μπορεί να δρομολογήσει το `REGISTRY`· κάθε προορισμός του `BUILT_IN_ALIASES` υπάρχει στο `REGISTRY` και απουσιάζει από το στιγμιότυπο καταργημένων id· κάθε καταργημένο id που εξακολουθεί να βρίσκεται στο `REGISTRY` προωθείται ή παρατίθεται στο `allowedRetiredInCatalog`· και καμία πηγή ή προορισμός του `DEFAULT_DEGRADATION_MAP` δεν εμφανίζεται ως καταργημένος σε αυτό το στιγμιότυπο. Αυτό δεν αποδεικνύει ότι ένα μοντέλο εξυπηρετείται επί του παρόντος από μια ενεργή υπηρεσία ανώτερου επιπέδου. Εκτός σύνδεσης — συγκρίνει με το `config/quality/model-lifecycle.json`, το οποίο ανανεώνεται χειροκίνητα με `npm run quality:refresh-model-lifecycle` (απαιτεί δίκτυο· δεν είναι ενσωματωμένο στο CI). Το `allowedRetiredInCatalog` είναι ένας μηχανισμός σταδιακής μείωσης: προσθέστε μια καταχώριση μόνο μαζί με ένα ζήτημα παρακολούθησης. | Ναι | +| `check:fetch-targets` | Κάθε `fetch("/api/...")` στο `src/` της πλευράς πελάτη αντιστοιχεί σε ένα πραγματικό `route.ts` | Ναι | +| `check:deps` | Όλες οι εξαρτήσεις που μπορούν να εγκατασταθούν με `npm install` σε κάθε `package.json` του αποθετηρίου περιλαμβάνονται στο `dependency-allowlist.json`· επισημαίνονται νέα πακέτα χωρίς καρφιτσωμένη έκδοση ή πακέτα που ενδέχεται να αποτελούν slopsquatting | Ναι | +| `audit:deps` | `npm audit` (ριζικός κατάλογος + electron) — καμία ειδοποίηση υψηλής/κρίσιμης σοβαρότητας (επικαλύπτεται με το osv `check:vuln-ratchet`· βλ. Λίστα εκκρεμοτήτων εξορθολογισμού) | Ναι | +| `check:lockfile` | Ακεραιότητα του `package-lock.json` — μητρώο https, κατακερματισμοί ακεραιότητας, χωρίς παρακάμψεις κεντρικού υπολογιστή | Ναι | +| `check:licenses` | Λίστα επιτρεπόμενων αδειών SPDX για εξαρτήσεις παραγωγής | Ναι | +| `check:tracked-artifacts` | Χωρίς τεχνουργήματα μεταγλώττισης / δεσμευμένους συμβολικούς συνδέσμους `node_modules` (εκτελείται επίσης στο husky pre-commit· το pre-push είναι σκόπιμα ελαφρύ — #6716) | Ναι | +| `check:ai-attribution` | Χωρίς υποσέλιδο `Co-Authored-By` από AI/bot ή υποσέλιδο δημιουργίας από AI στα commits, στον τίτλο ή στο σώμα του PR — Αυστηρός Κανόνας #16 (στον βρόχο γρήγορων ελέγχων του `quality.yml` για PR→`release/**` — διαβάζει το ωφέλιμο φορτίο του συμβάντος, δεν εκτελεί καμία ενέργεια εκτός PR — και ως βήμα μόνο για PR στο lint του `ci.yml` για PR→`main`· επίσης στο hook `commit-msg` του husky· επιτρέπονται ανθρώπινοι συν-συγγραφείς· #14436) | +| `check:vitest-exclusions` | Κάθε εξαίρεση του Vitest αναφέρει ένα ζήτημα παρακολούθησης και εμφανίζεται στο `config/quality/vitest-exclusions.json` (#13204) | Ναι | +| `check:file-size` | Κανένα αρχείο πηγαίου κώδικα δεν υπερβαίνει το όριο ανά επέκταση (καστάνια: παγωμένα μεγάλα αρχεία στη λίστα `frozen`) | Ναι | +| `check:error-helper` | Οι αποκρίσεις σφάλματος σε executors/handlers χρησιμοποιούν `buildErrorBody()` / `sanitizeErrorMessage()` (Αυστηρός Κανόνας #12) | Ναι | +| `check:migration-numbering` | Τα αρχεία SQL μετεγκατάστασης είναι αριθμημένα διαδοχικά, χωρίς κενά ή διπλότυπα | Ναι | +| `check:public-creds` | Δεν υπάρχουν κυριολεκτικές τιμές OAuth `client_id`/`client_secret` ή κλειδιά Firebase Web εκτός του `publicCreds.ts` (Αυστηρός Κανόνας #11) | Ναι | +| `check:db-rules` | Δεν υπάρχει ακατέργαστη SQL εκτός των αρθρωμάτων `src/lib/db/`· δεν υπάρχουν συγκεντρωτικές εισαγωγές από το `localDb.ts` (Αυστηροί Κανόνες #2/#5) | Ναι | +| `check:known-symbols` | Οι εκτελεστές παρόχων, οι στρατηγικές δρομολόγησης και οι μεταφραστές που είναι καταχωρισμένοι στους πίνακες αποστολής τους αντιστοιχούν στα αρχεία του δίσκου — χωρίς ορφανά ή μη δηλωμένα σύμβολα | Ναι | +| `check:route-guard-membership` | Κάθε διαδρομή που δημιουργεί θυγατρική διεργασία ταξινομείται από τη `isLocalOnlyPath()` (Αυστηροί Κανόνες #15/#17) | Ναι | +| `check:test-discovery` | Κάθε αρχείο `*.test.ts` / `*.spec.ts` στο αποθετήριο συλλέγεται από τουλάχιστον έναν εκτελεστή δοκιμών (καστάνια: η λίστα ορφανών στο `test-discovery-baseline.json` μπορεί μόνο να συρρικνωθεί) | Ναι | +| `check:agent-skills-sync` | Τα παραγόμενα τεχνουργήματα agent-skills αντιστοιχούν στον κατάλογο προέλευσής τους (χωρίς αποκλίσεις) | +| `check:provider-asset-provenance` | Τα λογότυπα/στοιχεία των παρόχων διαθέτουν καταγεγραμμένη καταχώριση προέλευσης | +| `lint:json` | Τα αρχεία ρυθμίσεων JSON αναλύονται επιτυχώς και συμμορφώνονται με τους κανόνες lint του αποθετηρίου | +| `typecheck:core` | Μεταγλώττιση TypeScript χωρίς σφάλματα (μόνο συμβουλευτικές προειδοποιήσεις) | Ναι | +| `typecheck:noimplicit:core` | Αυστηρό `noImplicitAny` — μελλοντικός στόχος· πολλά προϋπάρχοντα σημεία κλήσης εξακολουθούν να χρειάζονται επισημειώσεις | **Συμβουλευτικό** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` περιορισμένο στο `src/app/(dashboard)/**` (#7033) — η επιμελημένη λίστα επιτρεπόμενων 27 αρχείων του `typecheck:core` δεν περιλαμβάνει κανένα αρχείο TSX του dashboard, ενώ ούτε το `next build` εκτελεί ποτέ έλεγχο τύπων σε αυτά (`next.config.mjs` ορίζει `ignoreBuildErrors: true`), με αποτέλεσμα οι παλινδρομήσεις ορφανών αναγνωριστικών εκεί (#6625/#6909) να μην είναι ορατές στο CI. Οι διαφορές συγκρίνονται με μια παγωμένη γραμμή βάσης πλήθους ανά αρχείο/ανά κωδικό TS (`config/quality/dashboard-typecheck-baseline.json`, με το ίδιο μοτίβο επιβολής παλαιότητας όπως το `check:known-symbols`) — μόνο ΝΕΑ σφάλματα πέρα από το πλήθος της γραμμής βάσης αποτυγχάνουν την πύλη· μειώστε σταδιακά τη γραμμή βάσης με `--update` όταν διορθώνεται ένα προϋπάρχον σφάλμα. | Ναι | ### Εργασία: `quality-gate` Εκτελείται μετά το `test-coverage`. Αποκλείει τη συγχώνευση σε περίπτωση αποτυχίας. -| Script | Επικυρώνει | Αποκλεισμός | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `quality:collect` | Δημιουργεί το `quality-metrics.json` (πλήθος προειδοποιήσεων ESLint, κάλυψη από τη συγχωνευμένη αναφορά shards) | Ναι (προηγείται του ratchet) | -| `quality:ratchet` | Κάθε μετρική στο `quality-baseline.json` δεν έχει υποχωρήσει (προειδοποιήσεις ESLint ≤ baseline· κάλυψη ≥ baseline) | Ναι | -| `check:duplication` | Η επανάληψη κώδικα (jscpd@4) δεν υπερβαίνει το baseline στο `quality-baseline.json` | Ναι | -| `check:complexity` | Η κυκλωματική πολυπλοκότητα σε επίπεδο αρχείου δεν υπερβαίνει το όριο (βασικοί κανόνες ESLint `complexity` + `max-lines-per-function`) | Ναι | -| `check:cognitive-complexity` | Ratchet γνωστικής πολυπλοκότητας (`eslint-plugin-sonarjs`) — ξεχωριστή εκτέλεση ESLint· το CI εκτελεί και τα δύο συγχωνευμένα στο ενιαίο βήμα `check:complexity-ratchets` | Ναι | -| `check:dead-code` | Το ratchet μη χρησιμοποιούμενων exports / αρχείων (knip) δεν υποχωρεί σε σχέση με το baseline | Ναι | -| `check:compression-budget` | Προϋπολογισμός benchmark συμπίεσης — τα ελάχιστα όρια εξοικονόμησης tokens ανά engine δεν πρέπει να υποχωρούν | Ναι | -| `check:type-coverage` | Το ratchet ποσοστού τυποποιημένου κώδικα (`type-coverage`) δεν υποχωρεί· σε μεγάλο βαθμό υπερκαλύπτει το `typecheck:noimplicit:core` | Ναι | -| `check:codeql-ratchet` | Το πλήθος ανοικτών ειδοποιήσεων CodeQL δεν υποχωρεί (ανάγνωση μέσω `gh api`· ομαλή παράλειψη χωρίς token) — για τη συχνότητα ανανέωσης και τη μη αυτόματη ενεργοποίηση: δείτε το "CodeQL ratchet" παρακάτω | Ναι | +| Script | Επαληθεύει | Αποκλεισμός συγχώνευσης | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `quality:collect` | Παράγει το `quality-metrics.json` (πλήθος προειδοποιήσεων ESLint, κάλυψη από τη συγχωνευμένη αναφορά τμημάτων) | Ναι (προηγείται του ratchet) | +| `quality:ratchet` | Κάθε μετρική στο `quality-baseline.json` δεν έχει υποχωρήσει (προειδοποιήσεις ESLint ≤ γραμμή βάσης· κάλυψη ≥ γραμμή βάσης) | Ναι | +| `check:duplication` | Η επανάληψη κώδικα (jscpd@4) δεν υπερβαίνει τη γραμμή βάσης στο `quality-baseline.json` | Ναι | +| `check:complexity` | Η κυκλωματική πολυπλοκότητα σε επίπεδο αρχείου δεν υπερβαίνει το όριο (βασικοί κανόνες ESLint `complexity` + `max-lines-per-function`) | Ναι | +| `check:cognitive-complexity` | Ratchet γνωστικής πολυπλοκότητας (`eslint-plugin-sonarjs`) — ξεχωριστή εκτέλεση ESLint· το CI εκτελεί και τα δύο συγχωνευμένα στο ενιαίο βήμα `check:complexity-ratchets` | Ναι | +| `check:dead-code` | Το ratchet μη χρησιμοποιούμενων exports / αρχείων (knip) δεν υποχωρεί σε σχέση με τη γραμμή βάσης | Ναι | +| `check:compression-budget` | Προϋπολογισμός συγκριτικής αξιολόγησης συμπίεσης — τα κατώτατα όρια εξοικονόμησης tokens ανά μηχανή δεν πρέπει να υποχωρούν | Ναι | +| `check:type-coverage` | Το ratchet ποσοστού τυποποιημένου κώδικα (`type-coverage`) δεν υποχωρεί· σε μεγάλο βαθμό υπερκαλύπτει το `typecheck:noimplicit:core` | Ναι | +| `check:codeql-ratchet` | Το πλήθος ανοιχτών ειδοποιήσεων CodeQL δεν υποχωρεί (ανάγνωση μέσω `gh api`· ομαλή παράλειψη χωρίς token) — για τη συχνότητα ανανέωσης και τη μη αυτόματη ενεργοποίηση: δείτε «CodeQL ratchet» παρακάτω | Ναι | ### Εργασία: `quality-extended` Ολόκληρη η εργασία είναι συμβουλευτική (`continue-on-error: true`). Τα ratchets που βασίζονται στο npm εκτελούνται -κανονικά· οι εξωτερικοί σαρωτές εγκαθίστανται μέσω `gh release download` και αυτοπαραλείπονται (exit 0) -όταν ένα εκτελέσιμο εξακολουθεί να απουσιάζει. - -| Script | Επικυρώνει | Αποκλεισμός | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | -| `check:circular-deps` | Δεν υπάρχουν κυκλικές εξαρτήσεις (dpdm) | **Συμβουλευτικό** | -| `check:bundle-size` | Το μέγεθος του bundle δεν υπερβαίνει το όριο | **Συμβουλευτικό** | -| `check:secrets` | Σάρωση για μυστικά (gitleaks) — παραλείπεται εάν απουσιάζει το εκτελέσιμο | **Συμβουλευτικό** | -| `check:vuln-ratchet` | Οι ευπάθειες εξαρτήσεων (osv-scanner) δεν υποχωρούν — παραλείπεται εάν απουσιάζει το εκτελέσιμο | **Συμβουλευτικό** | -| `check:workflows` | Έλεγχος workflow (actionlint + zizmor)· σαρωτές που λείπουν/δεν λειτουργούν, μη έγκυρες αναφορές ή baseline ratchet που λείπει αποτυγχάνουν ως INCOMPLETE. Τα έγκυρα ευρήματα ακολουθούν την επιλεγμένη πολιτική strict/advisory/ratchet | Απαιτείται εκτέλεση· το ratchet zizmor αποκλείει στο CI | -| `check:openapi-breaking` | Μη συμβατές αλλαγές στο δημόσιο συμβόλαιο API (`openapi.yaml`) σε σχέση με τον βασικό κλάδο (oasdiff) — δημιουργεί `openapiBreaking=N`· παραλείπεται εάν απουσιάζει το oasdiff ή δεν μπορεί να επιλυθεί το βασικό spec | **Συμβουλευτικό** | +κανονικά· οι εξωτερικοί σαρωτές εγκαθίστανται μέσω `gh release download` και παραλείπονται αυτόματα (exit 0) +όταν εξακολουθεί να απουσιάζει κάποιο εκτελέσιμο αρχείο. + +| Script | Επαληθεύει | Αποκλεισμός συγχώνευσης | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| `check:circular-deps` | Δεν υπάρχουν κυκλικές εξαρτήσεις (dpdm) | **Συμβουλευτικό** | +| `check:bundle-size` | Το μέγεθος του bundle δεν υπερβαίνει το όριο | **Συμβουλευτικό** | +| `check:secrets` | Σάρωση μυστικών (gitleaks) — παραλείπεται αν απουσιάζει το εκτελέσιμο αρχείο | **Συμβουλευτικό** | +| `check:vuln-ratchet` | Τα τρωτά σημεία εξαρτήσεων (osv-scanner) δεν υποχωρούν — παραλείπεται αν απουσιάζει το εκτελέσιμο αρχείο | **Συμβουλευτικό** | +| `check:workflows` | Έλεγχος ροών εργασίας (actionlint + zizmor)· σαρωτές που απουσιάζουν/δεν λειτουργούν, μη έγκυρες αναφορές ή γραμμή βάσης ratchet που απουσιάζει αποτυγχάνουν ως INCOMPLETE. Τα έγκυρα ευρήματα ακολουθούν την επιλεγμένη πολιτική strict/advisory/ratchet | Απαιτείται εκτέλεση· το zizmor ratchet αποκλείει στο CI | +| `check:openapi-breaking` | Αλλαγές που διακόπτουν τη συμβατότητα του δημόσιου συμβολαίου API (`openapi.yaml`) έναντι του βασικού branch (oasdiff) — παράγει `openapiBreaking=N`· παραλείπεται αν απουσιάζει το oasdiff ή δεν μπορεί να επιλυθεί η βασική προδιαγραφή | **Συμβουλευτικό** | ### Εργασία: `docs-sync-strict` Εκτελείται σε κάθε PR προς το `main`. Αποκλείει τη συγχώνευση σε περίπτωση αποτυχίας. -| Script | Επαληθεύει | Μπλοκάρει | +| Script | Ελέγχει | Αποκλειστικό | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `check:docs-all` | Μετα-πύλη που εκτελεί διαδοχικά τις 6 υπο-πύλες παρακάτω | Ναι | +| `check:docs-all` | Μετα-πύλη που εκτελεί διαδοχικά τις 6 παρακάτω υπο-πύλες | Ναι | | ↳ `check:docs-sync` | Συνέπεια εκδόσεων μεταξύ CHANGELOG / OpenAPI / llm.txt | Ναι | -| ↳ `check:docs-counts` | Οι αριθμοί στο κείμενο (πλήθος παρόχων, πλήθος μετεγκαταστάσεων κ.λπ.) βρίσκονται εντός του παραθύρου ratchet των πραγματικών αριθμών | Ναι | -| ↳ `check:env-doc-sync` | Κάθε μεταβλητή περιβάλλοντος στο `.env.example` τεκμηριώνεται σε πίνακα τεκμηρίωσης και αντιστρόφως | Ναι | -| ↳ `check:deprecated-versions` | Δεν υπάρχουν συμβολοσειρές παρωχημένων εκδόσεων στην τεκμηρίωση | Ναι | -| ↳ `check:doc-links` | Οι εσωτερικοί σύνδεσμοι markdown στην τεκμηρίωση αντιστοιχούν σε πραγματικά αρχεία (μορφή `[text]`/`(path)`) | Ναι | +| ↳ `check:docs-counts` | Οι αριθμοί στο κείμενο (πλήθος παρόχων, πλήθος μεταβιβάσεων κ.λπ.) βρίσκονται εντός του παραθύρου σταδιακής αύξησης των πραγματικών τιμών | Ναι | +| ↳ `check:env-doc-sync` | Κάθε μεταβλητή περιβάλλοντος στο `.env.example` τεκμηριώνεται σε πίνακα της τεκμηρίωσης και αντιστρόφως | Ναι | +| ↳ `check:deprecated-versions` | Δεν υπάρχουν παρωχημένες συμβολοσειρές εκδόσεων στην τεκμηρίωση | Ναι | +| ↳ `check:doc-links` | Οι εσωτερικοί σύνδεσμοι markdown στην τεκμηρίωση αντιστοιχούν σε πραγματικά αρχεία (μορφή `[κείμενο]`/`(διαδρομή)`) | Ναι | | ↳ `check:fabricated-docs` | Οι διαδρομές, οι μεταβλητές περιβάλλοντος, οι εντολές CLI, τα ονόματα hook και οι διαδρομές αρχείων που αναφέρονται στην τεκμηρίωση υπάρχουν στη βάση κώδικα. Αυστηρή πύλη μέσω `--strict`· ήπια αποτυχία χωρίς τη σημαία. | Ναι (μέσω `--strict` στο CI) | | `check:cli-i18n` | Οι συμβολοσειρές εντολών CLI υπάρχουν σε όλα τα αρχεία τοπικών ρυθμίσεων i18n | Ναι | -| `check:openapi-coverage` | Η προδιαγραφή OpenAPI καλύπτει τουλάχιστον ένα κατώτατο όριο πραγματικών διαδρομών που ορίζεται μέσω ratchet | Ναι | +| `check:openapi-coverage` | Η προδιαγραφή OpenAPI καλύπτει τουλάχιστον ένα σταδιακά αυξανόμενο κατώτατο όριο πραγματικών διαδρομών | Ναι | | `check:openapi-security-tiers` | Οι επισημάνσεις επιπέδων ασφαλείας στο `openapi.yaml` είναι συνεπείς με τις ταξινομήσεις του `routeGuard.ts` | **Συμβουλευτικό** | -| `check:openapi-routes` | Κάθε διαδρομή στο `openapi.yaml` αντιστοιχεί σε πραγματικό `route.ts` (αποτροπή επινοημένων στοιχείων) | Ναι | -| `check:docs-symbols` | Κάθε αναφορά `/api/...` στο `docs/**/*.md` αντιστοιχεί σε πραγματικό `route.ts` (αποτροπή επινοημένων στοιχείων) | Ναι | -| `i18n translation drift` | Αμετάφραστα κλειδιά στα αρχεία τοπικών ρυθμίσεων i18n — μόνο προειδοποίηση | **Συμβουλευτικό** | +| `check:openapi-routes` | Κάθε διαδρομή στο `openapi.yaml` αντιστοιχεί σε πραγματικό `route.ts` (αποτροπή επινόησης) | Ναι | +| `check:docs-symbols` | Κάθε αναφορά `/api/...` στο `docs/**/*.md` αντιστοιχεί σε πραγματικό `route.ts` (αποτροπή επινόησης) | Ναι | +| `i18n translation drift` | Μη μεταφρασμένα κλειδιά στα αρχεία τοπικών ρυθμίσεων i18n — μόνο προειδοποίηση | **Συμβουλευτικό** | ### Εργασία: `i18n-ui-coverage` -| Script | Επαληθεύει | Μπλοκάρει | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `check-ui-keys-coverage` (inline) | Η κάλυψη κλειδιών i18n του UI είναι ≥ 65% | Ναι | -| `check-ui-value-drift` (inline) | Μια επανεγγεγραμμένη αγγλική **τιμή** δεν αφήνει πίσω της παρωχημένη μετάφραση | Ναι | -| `check-new-key-coverage` (inline) | Ένα **νέο** αγγλικό κλειδί μεταφράζεται σε κάθε τοπική ρύθμιση — ένας δείκτης `__MISSING__:` απορρίπτεται | Ναι | -| `check-translation-ratio` | Η αναλογία πραγματικών μεταφράσεων ανά τοπική ρύθμιση (φύλλα πανομοιότυπα με τα αγγλικά / placeholder / απόντα εκτός λίστας επιτρεπόμενων) δεν πρέπει να υπερβαίνει το `config/quality/i18n-translation-baseline.json` + περιθώριο | **Συμβουλευτικό** | +| Script | Ελέγχει | Αποκλειστικό | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `check-ui-keys-coverage` (inline) | Η κάλυψη κλειδιών i18n του UI είναι ≥ 65% | Ναι | +| `check-ui-value-drift` (inline) | Μια αναδιατυπωμένη αγγλική **τιμή** δεν αφήνει πίσω της παρωχημένη μετάφραση | Ναι | +| `check-new-key-coverage` (inline) | Ένα **νέο** αγγλικό κλειδί είναι μεταφρασμένο σε κάθε τοπική ρύθμιση — ένας δείκτης `__MISSING__:` απορρίπτεται | Ναι | +| `check-translation-ratio` | Η αναλογία πραγματικών μεταφράσεων ανά τοπική ρύθμιση (τιμές ίδιες με τα αγγλικά / σύμβολα κράτησης θέσης / ελλείπουσες τιμές εκτός της λίστας εξαιρέσεων) δεν πρέπει να υπερβαίνει το `config/quality/i18n-translation-baseline.json` + περιθώριο | **Συμβουλευτικό** | -Απαιτεί `fetch-depth: 0` — η πύλη value-drift συγκρίνει το `en.json` με τη βάση συγχώνευσης. +Απαιτεί `fetch-depth: 0` — η πύλη απόκλισης τιμών συγκρίνει το `en.json` με τη βάση συγχώνευσης. #### `check-ui-value-drift` — πύλη παρωχημένων μεταφράσεων -Εντοπίζει τη μία παλινδρόμηση i18n που οι άλλες πύλες δεν μπορούν να ανιχνεύσουν δομικά: μια αγγλική τιμή -επανεγγράφεται και οι μεταφράσεις που προέρχονται από τα _προηγούμενα_ αγγλικά παραμένουν, με αποτέλεσμα -οι μη αγγλόφωνοι χρήστες να συνεχίζουν να διαβάζουν ένα κείμενο με βέβαιη διατύπωση, το οποίο πλέον είναι λανθασμένο. +Εντοπίζει τη μία παλινδρόμηση i18n που οι άλλες πύλες δεν μπορούν από τη δομή τους να δουν: μια αγγλική τιμή +αναδιατυπώνεται και οι μεταφράσεις που προέρχονται από τα _προηγούμενα_ αγγλικά παραμένουν, με αποτέλεσμα +οι μη αγγλόφωνοι χρήστες να συνεχίζουν να διαβάζουν κείμενο με βέβαιη διατύπωση, το οποίο πλέον είναι λανθασμένο. -Αυτό κυκλοφόρησε πράγματι σε παραγωγή. Το `oauthModal.googleOAuthWarning` επανεγγράφηκε όταν προστέθηκε το βοήθημα +Αυτό κυκλοφόρησε πράγματι στην παραγωγή. Το `oauthModal.googleOAuthWarning` αναδιατυπώθηκε όταν προστέθηκε το βοηθητικό πρόγραμμα σύνδεσης Antigravity (#5203)· **39 από τις 43 τοπικές ρυθμίσεις** διατήρησαν κείμενο που έλεγε στους διαχειριστές να «αντιγράψουν -ολόκληρο το URL και να το επικολλήσουν παρακάτω» — μια ροή που δεν μπορεί να ολοκληρωθεί για τον συγκεκριμένο πάροχο. Παρέμεινε -απαρατήρητο έως το #8463, επειδή: +ολόκληρο το URL και να το επικολλήσουν παρακάτω» — μια ροή που δεν μπορεί να ολοκληρωθεί για αυτόν τον πάροχο. Παρέμεινε +απαρατήρητο μέχρι το #8463 επειδή: -- το `sync-ui-keys` συμπληρώνει μόνο κλειδιά που **απουσιάζουν**, ποτέ κλειδιά που είναι **παρωχημένα**· +- το `sync-ui-keys` συμπληρώνει μόνο κλειδιά που **απουσιάζουν**, ποτέ όσα είναι **παρωχημένα**· - το `check-ui-keys-coverage` μετρά την _παρουσία_ κλειδιών, επομένως μια παρωχημένη μετάφραση υπολογίζεται ως καλυμμένη· - το `check-translation-drift` παρακολουθεί τα αντίγραφα τεκμηρίωσης `docs/i18n//**.md` — - δεν διαβάζει ποτέ τα `src/i18n/messages/*.json`. Μπλοκάρει στην εργασία `docs-sync-strict` από τον - επανασυγχρονισμό του 2026-09: επεξεργασία βασικού εγγράφου → `npm run i18n:run -- --files=` (σε επίπεδο ενότητας, χαμηλού κόστους). + δεν διαβάζει ποτέ το `src/i18n/messages/*.json`. Είναι αποκλειστικό στην εργασία `docs-sync-strict` από τον + επανασυγχρονισμό 2026-09: επεξεργασία βασικού εγγράφου → `npm run i18n:run -- --files=` (ανά ενότητα, χαμηλού κόστους). -**Λαμβάνει υπόψη τις διαφορές, δεν βασίζεται σε baseline.** Συγκρίνει το `en.json` στη βάση συγχώνευσης με το -δέντρο εργασίας· για κάθε κλειδί του οποίου άλλαξε η αγγλική τιμή, οποιαδήποτε τοπική προσαρμογή εξακολουθεί να περιέχει μια -ανέγγιχτη μετάφραση θεωρείται παρωχημένη. Αυτό σκόπιμα **παγώνει το προϋπάρχον χρέος** — ένα diff +**Λαμβάνει υπόψη το diff, δεν βασίζεται σε baseline.** Συγκρίνει το `en.json` στη βάση συγχώνευσης με το +δέντρο εργασίας· για κάθε κλειδί του οποίου άλλαξε η αγγλική τιμή, κάθε locale που εξακολουθεί να περιέχει μια +ανέγγιχτη μετάφραση θεωρείται παρωχημένο. Αυτό σκόπιμα **παγώνει το προϋπάρχον χρέος** — ένα diff δεν μπορεί να αποκαλύψει από ποιο παλιό αγγλικό κείμενο προήλθε μια μακροχρόνια μετάφραση, επομένως η πύλη αξιολογεί μόνο ό,τι επηρεάζει η τρέχουσα αλλαγή. Η εναλλακτική λύση (ένα baseline hash ανά κλειδί) θα απαιτούσε -ένα παραγόμενο αρχείο ~600 KB, 3× μεγαλύτερο από το μεγαλύτερο υπάρχον baseline, το οποίο θα άλλαζε σε κάθε PR διεθνοποίησης. +ένα παραγόμενο αρχείο ~600 KB, 3× μεγαλύτερο από το μεγαλύτερο υπάρχον baseline, το οποίο θα άλλαζε σε κάθε i18n PR. Υπάρχουν δύο τρόποι για να ικανοποιηθεί: 1. ενημερώστε τις επηρεαζόμενες μεταφράσεις ή -2. ορίστε τις σε `__MISSING__:` — κατά την εκτέλεση παρέχεται τότε το διορθωμένο αγγλικό κείμενο - (`src/i18n/request.ts::deepMergeFallback`, #7258) και το κλειδί εισάγεται στην ουρά προς μετάφραση. +2. ορίστε τις σε `__MISSING__:` — τότε, κατά την εκτέλεση, παρέχεται το διορθωμένο αγγλικό κείμενο + (`src/i18n/request.ts::deepMergeFallback`, #7258) και το κλειδί μπαίνει στην ουρά για μετάφραση. Αν άλλαξε το **νόημα** της συμβολοσειράς, προτιμήστε τη **μετονομασία του κλειδιού**: ένα νέο κλειδί δεν μπορεί να κληρονομήσει μια παρωχημένη μετάφραση. Αυτό είναι το μοτίβο που χρησιμοποιήθηκε στο #8463. ```bash -npm run i18n:check-value-drift # αυστηρός έλεγχος (αυτό εκτελεί το CI) +npm run i18n:check-value-drift # αυστηρός έλεγχος (όπως εκτελείται στο CI) npm run i18n:check-value-drift:warn # μόνο αναφορά BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Τερματίζει με κωδικό 0 και `SKIP reason=base-unresolved` όταν δεν είναι δυνατή η ανάγνωση του βασικού καταλόγου (ρηχό -clone χωρίς το βασικό ref), ακολουθώντας τη συμπεριφορά του `check-openapi-breaking`. +Τερματίζεται με κωδικό 0 και `SKIP reason=base-unresolved` όταν δεν είναι δυνατή η ανάγνωση του βασικού καταλόγου (ρηχός +κλώνος χωρίς το base ref), ακολουθώντας τη συμπεριφορά του `check-openapi-breaking`. ### Εργασία: `i18n` -Πλήρης πίνακας επικύρωσης διεθνοποίησης (μία εργασία ανά τοπική προσαρμογή). Ολόκληρη η εργασία είναι συμβουλευτική. +Πλήρης μήτρα επικύρωσης i18n (μία εργασία ανά locale). Ολόκληρη η εργασία είναι συμβουλευτική. -| Script | Επικυρώνει | Αποκλεισμός | -| ------------------------------- | ------------------------------------------ | --------------------------------------------------------------------- | -| `validate_translation.py quick` | Πληρότητα μετάφρασης ανά τοπική προσαρμογή | **Συμβουλευτικό** (`continue-on-error: true` σε ολόκληρη την εργασία) | +| Script | Τι επικυρώνει | Αποκλεισμός | +| ------------------------------- | ------------------------------- | --------------------------------------------------------------------- | +| `validate_translation.py quick` | Πληρότητα μετάφρασης ανά locale | **Συμβουλευτικό** (`continue-on-error: true` σε ολόκληρη την εργασία) | ### Εργασία: `pr-test-policy` Εκτελείται μόνο σε pull requests. -| Script | Επικυρώνει | Αποκλεισμός | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | Τα PR που αλλάζουν κώδικα παραγωγής στα `src/`, `open-sse/`, `electron/` ή `bin/` πρέπει να περιλαμβάνουν ή να ενημερώνουν δοκιμές (Αυστηρός Κανόνας #8) | Ναι | -| `check:test-masking` | Τα τροποποιημένα αρχεία δοκιμών δεν μειώνουν το καθαρό πλήθος assertions ούτε προσθέτουν ταυτολογίες `assert.ok(true)` | Ναι | -| `check:pr-evidence` | Το σώμα του PR παραθέτει τεκμήρια δοκιμών/VPS για την αλλαγή (αυτοματοποιεί τον Αυστηρό Κανόνα #18 μέσω αναζήτησης στο κείμενο του PR — εύθραυστο, βλ. Backlog) | Ναι | +| Script | Τι επικυρώνει | Αποκλεισμός | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check:pr-test-policy` | Τα PR που αλλάζουν κώδικα παραγωγής στα `src/`, `open-sse/`, `electron/` ή `bin/` πρέπει να περιλαμβάνουν ή να ενημερώνουν δοκιμές (Αυστηρός Κανόνας #8) | Ναι | +| `check:test-masking` | Τα αλλαγμένα αρχεία δοκιμών δεν μειώνουν τον καθαρό αριθμό assertions ούτε προσθέτουν ταυτολογίες `assert.ok(true)` | Ναι | +| `check:pr-evidence` | Το σώμα του PR παραθέτει τεκμήρια δοκιμών/VPS για την αλλαγή (αυτοματοποιεί τον Αυστηρό Κανόνα #18 αναζητώντας στο κείμενο του PR — εύθραυστο, βλ. Backlog) | Ναι | ### Εργασία: `test-vitest` Εκτελείται μετά το `build`. Αποκλείει τη συγχώνευση σε περίπτωση αποτυχίας. -| Σουίτα | Επικυρώνει | Αποκλεισμός | -| ---------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Διακομιστή MCP (110 εργαλεία), autoCombo, cache — εκτελεστής vitest | Ναι | -| `test:vitest:ui` | Δοκιμές στοιχείων UI — εκτελεστής vitest | **Αποκλεισμός** — οι προϋπάρχουσες αποτυχίες εξαιρούνται ρητά στο `vitest.config.ts`· οι νέες αποτυχίες αποτυγχάνουν την εργασία | +| Σουίτα | Τι επικυρώνει | Αποκλεισμός | +| ---------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Διακομιστής MCP (110 εργαλεία), autoCombo, cache — vitest runner | Ναι | +| `test:vitest:ui` | Δοκιμές στοιχείων UI — vitest runner | **Αποκλεισμός** — οι προϋπάρχουσες αποτυχίες εξαιρούνται ρητά στο `vitest.config.ts`· οι νέες αποτυχίες αποτυγχάνουν την εργασία | ### Νυχτερινές ροές εργασίας (προγραμματισμένες, συμβουλευτικές) -Αυτές εκτελούνται βάσει προγράμματος cron (και μέσω `workflow_dispatch`), ποτέ σε PR. Όλες είναι συμβουλευτικές. +Αυτές εκτελούνται βάσει χρονοδιαγράμματος cron (και μέσω `workflow_dispatch`), ποτέ σε PR. Όλες είναι συμβουλευτικές. -| Ροή εργασίας | Επικυρώνει | Αποκλεισμός | +| Ροή εργασίας | Τι επικυρώνει | Αποκλεισμός | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `nightly-property` | Δοκιμές ιδιοτήτων fast-check με τυχαίο seed + μεγάλο αριθμό εκτελέσεων | **Συμβουλευτικό** | -| `nightly-resilience` | Πύλη αύξησης heap, εισαγωγή σφαλμάτων χάους, δοκιμές φόρτου/παρατεταμένου φορτίου k6 | **Συμβουλευτικό** | +| `nightly-property` | Δοκιμές ιδιοτήτων fast-check με τυχαίο seed + υψηλό αριθμό εκτελέσεων | **Συμβουλευτικό** | +| `nightly-resilience` | Πύλη αύξησης heap, έγχυση σφαλμάτων chaos, δοκιμές φόρτου/παρατεταμένου φορτίου k6 | **Συμβουλευτικό** | | `nightly-llm-security` | Προστασία από injection του promptfoo (λειτουργία αποκλεισμού) + ανιχνεύσεις garak (παραλείπονται χωρίς μυστικό παρόχου) | **Συμβουλευτικό** | | `nightly-schemathesis` | Fuzzing συμβολαίου OpenAPI (schemathesis) σε ενεργό OmniRoute με χρήση του `docs/openapi.yaml` — αναδεικνύει παραβιάσεις προδιαγραφών / μη διαχειριζόμενα 500 (Φάση 8 B.4) | **Συμβουλευτικό** | -| `nightly-mutation` | Βαθμολογία δοκιμών μεταλλάξεων Stryker στη γρήγορη λωρίδα δοκιμών μονάδας — οι επιζώσες μεταλλάξεις αναδεικνύουν αδύναμα assertions | **Συμβουλευτικό** | -| `nightly-compat` | Πίνακας συμβατότητας μηχανής Node για τα υποστηριζόμενα εύρη `engines.node` | **Συμβουλευτικό** | +| `nightly-mutation` | Βαθμολογία δοκιμών μεταλλάξεων Stryker στη γρήγορη λωρίδα μονάδων — οι μεταλλάξεις που επιβιώνουν αναδεικνύουν αδύναμα assertions | **Συμβουλευτικό** | +| `nightly-compat` | Μήτρα συμβατότητας της μηχανής Node σε όλα τα υποστηριζόμενα εύρη `engines.node` | **Συμβουλευτικό** | --- @@ -550,41 +538,41 @@ push (`release-green`)· μόνο η αποθήκευση γίνεται σε π ## Εκκρεμότητες εξορθολογισμού (αξιολόγηση ROI — Φάση 9 Κύμα 3) -Αυτή η απογραφή διασταυρώθηκε με το `ci.yml` στις 2026-06-17 (η προηγούμενη έκδοση παρέλειπε τα +Αυτή η απογραφή αντιπαραβλήθηκε με το `ci.yml` στις 2026-06-17 (η προηγούμενη έκδοση παρέλειπε τα `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Μια αξιολόγηση ROI του ενοποιημένου συνόλου +`check:codeql-ratchet`, `check:pr-evidence`). Μια αξιολόγηση ROI του αντιπαραβλημένου συνόλου εντόπισε τους ακόλουθους υποψηφίους για εξορθολογισμό. **Οι συγχωνεύσεις είναι μηχανικές αλλαγές -CI· οι ενεργοποιήσεις/καταργήσεις είναι αποφάσεις πολιτικής που προορίζονται για τον χειριστή.** Τίποτα από τα παρακάτω +CI· οι αλλαγές κατάστασης/καταργήσεις είναι αποφάσεις πολιτικής που προορίζονται για τον διαχειριστή.** Τίποτα από τα παρακάτω δεν έχει εφαρμοστεί ακόμη. -**Επίσης δεν τεκμηριώνονται παραπάνω** (συμβουλευτικά, χαμηλό σήμα): η εργασία `docs-lint` +**Επίσης δεν τεκμηριώθηκαν παραπάνω** (συμβουλευτικά, χαμηλό σήμα): η εργασία `docs-lint` (markdownlint + Vale, ολόκληρη η εργασία με `continue-on-error`) και οι αυτόνομες ροές εργασίας σάρωσης `semgrep.yml` / `codeql.yml` / `scorecard.yml`. Το `semgrepFindings: 0` βρίσκεται στο -`quality-baseline.json`, αλλά δεν είναι συνδεδεμένο με μηχανισμό αποκλεισμού βάσει ορίου στο `ci.yml` — η μετρική είναι +`quality-baseline.json`, αλλά δεν είναι συνδεδεμένο με ανασταλτικό μηχανισμό καστάνιας στο `ci.yml` — η μετρική είναι προς το παρόν ορφανή. ### Συγχώνευση / αποδιπλοποίηση (μηχανική, χαμηλότερου κινδύνου) -Κάθε υποψήφιος επικυρώθηκε έναντι της ενεργής κατάστασης των πυλών στις 2026-06-17 (εμπιστοσύνη με επαλήθευση)· -αρκετές «προφανείς» συγχωνεύσεις αποδείχθηκε ότι έκρυβαν τεχνικό χρέος και **δεν** αποτελούν άμεσες, ασφαλείς αντικαταστάσεις. - -- **Το `check:docs-sync` εκτελείται δύο φορές** — αυτόνομα στην εργασία `lint` και ξανά μέσα στο `check:docs-all` (`docs-sync-strict`) και στο hook pre-commit του husky. ✅ **ΟΛΟΚΛΗΡΩΘΗΚΕ** — αφαιρέθηκε η αυτόνομη κλήση από το `lint`. -- **Σάρωση CVE** — ❌ **ΔΕΝ είναι καθαρή συγχώνευση.** Το `audit:deps` αποτυγχάνει υποχρεωτικά για οποιοδήποτε CVE υψηλής/κρίσιμης σοβαρότητας· το `check:vuln-ratchet` (osv) αποτυγχάνει μόνο σε περίπτωση _επιδείνωσης_ σε σχέση με τη γραμμή βάσης (επί του παρόντος 1 MODERATE). Διαφορετική σημασιολογία — η κατάργηση του `audit:deps` θα αφαιρούσε την απόλυτη πύλη υψηλής/κρίσιμης σοβαρότητας. Διατηρήστε και τα δύο. -- **Εντοπισμός κύκλων** — ❌ **ΔΕΝ είναι καθαρή συγχώνευση.** Το `check:circular-deps` (dpdm) αναφέρει **91 κύκλους** (γι' αυτό είναι συμβουλευτικό)· δεν μπορεί να προαχθεί σε αποκλειστικό χωρίς να επιλυθούν πρώτα, ενώ έχει ευρύτερο πεδίο εφαρμογής από το επιτυχές, επιμελημένο `check:cycles`. Διατηρήστε το `check:cycles` ως αποκλειστικό· η επίλυση των 91 κύκλων του dpdm αποτελεί ξεχωριστή εκκρεμότητα. -- **Πολυπλοκότητα** — ✅ **ΟΛΟΚΛΗΡΩΘΗΚΕ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): μία διέλευση ESLint, με καταμέτρηση βάσει ruleId ώστε οι γραμμές βάσης για κυκλωματική πολυπλοκότητα+max-lines και γνωστική πολυπλοκότητα να παραμένουν ανεξάρτητες· τα μεμονωμένα `check:complexity` / `check:cognitive-complexity` διατηρούνται για τοπική εκτέλεση με `--update`. -- **Αποτροπή ψευδών αναφορών `/api`** — ✅ **ΟΛΟΚΛΗΡΩΘΗΚΕ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): μία απογραφή FS του `src/app/api`, ενώ τα openapi-routes + docs-symbols εξακολουθούν να αναφέρουν αποτελέσματα ανεξάρτητα· τα μεμονωμένα εργαλεία διατηρούνται για τοπικές εκτελέσεις. -- **Το `check:node-runtime` εκτελείται σε 11 εργασίες** — ⚠️ **χαμηλό ROI.** Κάθε εργασία χρησιμοποιεί ξεχωριστό runner και ο έλεγχος διαρκεί <1s· η συνολική εξοικονόμηση είναι ~10s, με αντίτιμο την απώλεια μιας οικονομικής δικλίδας ανά εργασία. Δεν αξίζει την αναστάτωση. -- **Το `typecheck:noimplicit:core` στο lint του CI** — ✅ **αφαιρέθηκε από την εργασία lint** (ήταν συμβουλευτικό με `continue-on-error`)· η αποκλειστική επιφάνεια τύπων είναι `typecheck:core` + `check:type-coverage`. Το τοπικό script διατηρήθηκε. - -### Ενεργοποίηση / απόφαση (πολιτική χειριστή) - -- `check:openapi-security-tiers` (συμβουλευτικό) — ❌ **ΔΕΝ μπορεί να γίνει καθαρά αποκλειστικό.** Τερματίζει με κωδικό 0, αλλά προειδοποιεί ότι αρκετές διαδρομές `traffic-inspector` κάτω από το `LOCAL_ONLY_API_PREFIXES` δεν διαθέτουν τη σημείωση `x-loopback-only: true`. Η επιβολή του απαιτεί πρώτα την προσθήκη αυτών των σημειώσεων στο `openapi.yaml`. -- `typecheck:noimplicit:core` (συμβουλευτικό) — καλύπτεται σε μεγάλο βαθμό από τον αποκλειστικό μηχανισμό ορίου `check:type-coverage`. Μετατρέψτε το σε μηχανισμό ορίου ή καταργήστε το περιττό δεύτερο πέρασμα `tsc`. -- `test:vitest:ui` (πλέον **αποκλειστικό**) — οι προϋπάρχουσες αποτυχίες εξαιρούνται ρητά στο `vitest.config.ts` με σχόλια παρακολούθησης `// #8618`· οι νέες αποτυχίες προκαλούν αποτυχία της εργασίας. -- `check:secrets` (gitleaks, αποκλειστικός μηχανισμός ορίου παγωμένος σε 3 τεκμηριωμένα ψευδώς θετικά) — προσθέστε τα 3 στη λίστα επιτρεπόμενων ώστε να φτάσουν στο 0 ή υποβιβάστε τον έλεγχο σε συμβουλευτικό. Επικαλύπτεται με την εγγενή σάρωση μυστικών του GitHub + το `check:public-creds`. -- `check:pr-evidence` (αποκλειστικό, αναζητά μοτίβα στο κείμενο του σώματος του PR) — υψηλός κίνδυνος ψευδώς θετικών· η κατάργησή του αποδυναμώνει την επιβολή του Αυστηρού Κανόνα #18, επομένως πρόκειται για ουσιαστική απόφαση πολιτικής. -- `semgrep` (συμβουλευτικό και αυτόνομο) — επικαλύπτεται με το CodeQL για τις οικογένειες OWASP· συνδέστε τη γραμμή βάσης του με μηχανισμό ορίου ή καταργήστε το. +Κάθε υποψήφιο στοιχείο επικυρώθηκε έναντι της ενεργής κατάστασης των πυλών στις 2026-06-17 (εμπιστοσύνη με επαλήθευση)· +αρκετές «προφανείς» συγχωνεύσεις αποδείχθηκε ότι έκρυβαν τεχνικό χρέος και **δεν** αποτελούν καθαρές άμεσες αντικαταστάσεις. + +- **Το `check:docs-sync` εκτελείται δύο φορές** — αυτόνομα στην εργασία `lint` και ξανά μέσα στο `check:docs-all` (`docs-sync-strict`) και στο hook pre-commit του husky. ✅ **ΟΛΟΚΛΗΡΩΘΗΚΕ** — η αυτόνομη κλήση στο `lint` αφαιρέθηκε. +- **Σάρωση CVE** — ❌ **ΔΕΝ αποτελεί καθαρή συγχώνευση.** Το `audit:deps` αποτυγχάνει υποχρεωτικά για οποιοδήποτε CVE υψηλής/κρίσιμης σοβαρότητας· το `check:vuln-ratchet` (osv) αποτυγχάνει μόνο σε περίπτωση _επιδείνωσης_ έναντι της γραμμής βάσης (επί του παρόντος 1 MODERATE). Διαφορετική σημασιολογία — η κατάργηση του `audit:deps` θα αφαιρούσε την απόλυτη πύλη υψηλής/κρίσιμης σοβαρότητας. Διατηρήστε και τα δύο. +- **Εντοπισμός κύκλων** — ✅ **ΟΛΟΚΛΗΡΩΘΗΚΕ** (#15159 G-01/G-02). Το παλιό κείμενο εδώ χαρακτήριζε το `check:cycles` ως «την πράσινη, επιμελημένη» πύλη και δικαιολογούσε τη διατήρησή της ως ανασταλτικής, επειδή το `check:circular-deps` (dpdm) ανέφερε 91 κύκλους. Αυτή η πράσινη ένδειξη ήταν **ψευδώς θετική**: το `check:cycles` σάρωνε 5 υποκαταλόγους (450 αρχεία), αντιστοίχιζε μόνο στατικό `import|export … from` και απέρριπτε κάθε προσδιοριστή `@/` και `@omniroute/open-sse/`, συνεπώς δεν μπορούσε να εντοπίσει τους κύκλους δυναμικών εισαγωγών και ψευδωνύμων που κυριαρχούσαν στο αποθετήριο. Διορθώθηκε: η πύλη πλέον διατρέχει τα `src` + `open-sse` (5023 αρχεία), συλλέγει προσδιοριστές από το AST της TypeScript (ώστε το `import("…")` να προσμετράται, ενώ το `typeof import("…")` σε θέση τύπου να μην προσμετράται) και επιλύει τα `paths` του tsconfig. Εντοπίζει **14** κύκλους, όχι 0. Επειδή 14 προϋπάρχοντες κύκλοι δεν μπορούν να διορθωθούν σε ένα PR πύλης, το `check:cycles` είναι πλέον ένας **μηχανισμός καστάνιας** (`--ratchet`, ανώτατο όριο `metrics.cycles.value = 14` στο `quality-baseline.json`, `direction: down`) — αποκλείει κάθε _επιδείνωση_ και το πλήθος μπορεί μόνο να μειώνεται. Το CI εκτελεί `npm run check:cycles:ratchet`. Η σταδιακή εξάλειψη προχωρά μαζί με το **A-01**. Το `check:circular-deps` (dpdm) παραμένει συμβουλευτικό ως ευρύτερη δεύτερη γνώμη. +- **Πολυπλοκότητα** — ✅ **ΟΛΟΚΛΗΡΩΘΗΚΕ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): μία διέλευση ESLint, καταμέτρηση ανά ruleId, ώστε οι γραμμές βάσης κυκλωματικής πολυπλοκότητας+μέγιστων γραμμών και γνωστικής πολυπλοκότητας να παραμένουν ανεξάρτητες· τα επιμέρους `check:complexity` / `check:cognitive-complexity` παραμένουν για τοπική χρήση με `--update`. +- **Αποτροπή παραισθήσεων για το `/api`** — ✅ **ΟΛΟΚΛΗΡΩΘΗΚΕ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): μία απογραφή FS του `src/app/api`, ενώ τα openapi-routes + docs-symbols εξακολουθούν να αναφέρουν αποτελέσματα ανεξάρτητα· τα επιμέρους εργαλεία παραμένουν για τοπικές εκτελέσεις. +- **Το `check:node-runtime` εκτελείται σε 11 εργασίες** — ⚠️ **χαμηλό ROI.** Καθεμία χρησιμοποιεί ξεχωριστό runner και ο έλεγχος διαρκεί <1s· συνολική εξοικονόμηση ~10s, με τίμημα την απώλεια μιας οικονομικής δικλίδας ασφαλείας ανά εργασία. Δεν αξίζει την αναστάτωση. +- **Το `typecheck:noimplicit:core` στο lint του CI** — ✅ **αφαιρέθηκε από την εργασία lint** (ήταν συμβουλευτικό με `continue-on-error`)· η ανασταλτική επιφάνεια τύπων είναι `typecheck:core` + `check:type-coverage`. Το τοπικό script διατηρήθηκε. + +### Αλλαγή κατάστασης / απόφαση (πολιτική διαχειριστή) + +- `check:openapi-security-tiers` (συμβουλευτικό) — ❌ **ΔΕΝ μπορεί να μετατραπεί καθαρά.** Τερματίζει με 0, αλλά προειδοποιεί ότι αρκετές διαδρομές `traffic-inspector` κάτω από το `LOCAL_ONLY_API_PREFIXES` δεν διαθέτουν τη σημείωση `x-loopback-only: true`. Η επιβολή του απαιτεί πρώτα την προσθήκη αυτών των σημειώσεων στο `openapi.yaml`. +- `typecheck:noimplicit:core` (συμβουλευτικό) — καλύπτεται σε μεγάλο βαθμό από τον ανασταλτικό μηχανισμό καστάνιας `check:type-coverage`. Μετατρέψτε το σε μηχανισμό καστάνιας ή καταργήστε το πλεονάζον δεύτερο πέρασμα `tsc`. +- `test:vitest:ui` (πλέον **ανασταλτικό**) — οι προϋπάρχουσες αποτυχίες εξαιρούνται ρητά στο `vitest.config.ts` με σχόλια παρακολούθησης `// #8618`· νέες αποτυχίες προκαλούν αποτυχία της εργασίας. +- `check:secrets` (gitleaks, ανασταλτικός μηχανισμός καστάνιας παγωμένος σε 3 τεκμηριωμένα ψευδώς θετικά) — προσθέστε τα 3 στη λίστα επιτρεπόμενων ώστε να φτάσουν στο 0 ή υποβαθμίστε τον έλεγχο σε συμβουλευτικό. Επικαλύπτεται με την εγγενή σάρωση μυστικών του GitHub + `check:public-creds`. +- `check:pr-evidence` (ανασταλτικό, εκτελεί grep στο πεζό κείμενο του σώματος του PR) — υψηλός κίνδυνος ψευδώς θετικών· η κατάργησή του αποδυναμώνει την επιβολή του Αυστηρού Κανόνα #18, επομένως πρόκειται για ουσιαστική απόφαση πολιτικής. +- `semgrep` (αυτόνομο συμβουλευτικό) — επικαλύπτεται με το CodeQL για τις οικογένειες OWASP· συνδέστε τη γραμμή βάσης του με έναν μηχανισμό καστάνιας ή καταργήστε το. --- diff --git a/docs/i18n/el/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/el/docs/compression/COMPRESSION_GUIDE.md index 1f48b798cf0c..237b4da168d8 100644 --- a/docs/i18n/el/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/el/docs/compression/COMPRESSION_GUIDE.md @@ -469,8 +469,7 @@ const { messages: aged, saved } = applyAging(messages, { Σε ένα σώμα με `messages`, μια παράκαμψη περιεχομένου (`shouldBypassCavemanOutputMode()` στο `open-sse/services/compression/outputMode.ts`) ελέγχει τα τρία τελευταία μηνύματα και παρακάμπτει τα στυλ για ολόκληρη τη σειρά όταν αυτά ταιριάζουν με τις λέξεις-κλειδιά ασφαλείας, μη αναστρέψιμης ενέργειας, -διευκρίνισης ή ευαίσθητης σειράς. Η παράκαμψη εκτελείται ανεξάρτητα από το τι έχει οριστεί -στον διακόπτη **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) του πίνακα ελέγχου. +διευκρίνισης ή ευαίσθητης σειράς. Η παράκαμψη εκτελείται ενώ ο διακόπτης **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) του πίνακα ελέγχου είναι ενεργοποιημένος, που είναι η προεπιλογή· με τον διακόπτη απενεργοποιημένο, τα επιλεγμένα στυλ εφαρμόζονται και σε αυτές τις σειρές. Όταν η παράκαμψη επιτρέπει τη σειρά, η `placeSystemInstruction()` (ίδιο αρχείο), η οποία ποτέ δεν δημιουργεί ένα νέο `messages[0]`, τοποθετεί το μπλοκ στο πρώτο από αυτά που βρίσκει: diff --git a/docs/i18n/el/docs/reference/ENVIRONMENT.md b/docs/i18n/el/docs/reference/ENVIRONMENT.md index 341ae29800a8..e83bb791cb91 100644 --- a/docs/i18n/el/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/el/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Απαιτούμενα μυστικά -Αυτά **πρέπει** να οριστούν πριν από την πρώτη εκτέλεση. Χωρίς αυτά, η εφαρμογή είτε θα αρνηθεί να εκκινηθεί είτε θα λειτουργήσει με μη ασφαλείς προεπιλεγμένες ρυθμίσεις. - -| Μεταβλητή | Απαιτείται | Προεπιλογή | Αρχείο προέλευσης | Περιγραφή | -| ---------------------------- | ------------------ | ------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Ναι** | _(καμία)_ | `src/lib/auth` | Υπογράφει/επαληθεύει όλα τα cookie συνεδρίας του πίνακα ελέγχου (JWT). Δημιουργήστε το με `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ναι** | _(καμία)_ | `src/lib/db/apiKeys.ts` | Κλειδί κρυπτογράφησης AES για τις τιμές των κλειδιών API που είναι αποθηκευμένες στο SQLite. Δημιουργήστε το με `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ναι** | `CHANGEME` | Σενάριο αρχικοποίησης | Ορίζει τον αρχικό κωδικό πρόσβασης διαχειριστή για τον πίνακα ελέγχου (αντιστοιχεί στην προεπιλογή του `.env.example` — διατηρείται εμφανώς μη ασφαλής ώστε να επιβάλει την αλλαγή του). **Αλλάξτε τον πριν από την πρώτη χρήση.** Μετά τη σύνδεση, αλλάξτε τον μέσω Πίνακας ελέγχου → Ρυθμίσεις → Ασφάλεια. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ναι** (παραγωγή) | _(δεν έχει οριστεί)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Κοινό μυστικό για την εσωτερική γέφυρα WebSocket του Codex Responses. Επαληθεύει τα αιτήματα της γέφυρας μεταξύ του αναμεταδότη WS του Electron/προγράμματος περιήγησης και του OmniRoute. ⚠️ **ΑΠΑΙΤΕΙΤΑΙ στην παραγωγή — όταν δεν έχει οριστεί, όλα τα αιτήματα της γέφυρας WS απορρίπτονται.** Δημιουργήστε το με `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Όχι | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Ρητό αναγνωριστικό ακύρωσης της κρυφής μνήμης του service worker για το κέλυφος PWA (#11779)· πρώτο στην αλυσίδα επίλυσης. | -| `SOURCE_VERSION` | Όχι | _(δεν έχει οριστεί)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Δεύτερο στην αλυσίδα — ορίζεται από συστήματα δημιουργίας PaaS (π.χ. τύπου Heroku) ως το commit που έχει αναπτυχθεί. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Όχι | _(παράγεται)_ | `src/shared/components/PwaRegister.tsx` | Δημόσια τιμή χρόνου δημιουργίας που χρησιμοποιεί ο client για να καταχωρίσει το `/sw.js?v=…`· παράγεται από τις δύο παραπάνω τιμές και, στη συνέχεια, από το git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Όχι (αυτόματα) | _(αυτόματα ανά εκκίνηση)_ | `src/server/authz/policies/management.ts` | Μυστικό ανά διεργασία που αποδεικνύει ότι η αξιόπιστη σήμανση IP ομότιμου προήλθε από τον ίδιο τον διακομιστή HTTP του OmniRoute (`scripts/dev/peer-stamp.mjs`). Το middleware εξουσιοδότησης εμπιστεύεται την τοπικότητα του αιτήματος (έλεγχος loopback/LAN για διαδρομές LOCAL_ONLY) μόνο όταν η σήμανση περιέχει αυτό το διακριτικό. Δημιουργείται αυτόματα σε κάθε εκκίνηση — αφήστε το χωρίς τιμή· ορίστε το μόνιμα μόνο για διαμορφώσεις πολλαπλών διεργασιών που πρέπει να μοιράζονται τη σήμανση. | -| `OMNIROUTE_TRUSTED_PROXIES` | Όχι | _(δεν έχει οριστεί)_ | `scripts/dev/peer-stamp.mjs` | Διευθύνσεις IP ή εύρη CIDR, διαχωρισμένα με κόμματα, για αντίστροφους διακομιστές μεσολάβησης των οποίων τις τιμές `X-Forwarded-For` / `X-Real-IP` μπορεί να εμπιστεύεται η λίστα επιτρεπόμενων/αποκλεισμένων IP, επιπλέον των διευθύνσεων loopback, των διευθύνσεων ιδιωτικού δικτύου και των κόμβων παρυφής Cloudflare, τα οποία θεωρούνται πάντα αξιόπιστα. Απαιτείται μόνο για διακομιστή μεσολάβησης σε οποιαδήποτε άλλη δημόσια διεύθυνση· διαφορετικά, το φίλτρο αξιολογεί τη διεύθυνση του ίδιου του διακομιστή μεσολάβησης. | +Τα μυστικά που επισημαίνονται με **Ναι** και δεν έχουν οριστεί δημιουργούνται αυτόματα κατά την πρώτη εκτέλεση (`JWT_SECRET`, `API_KEY_SECRET`) και αποθηκεύονται στο `server.env` στον κατάλογο δεδομένων — ορίστε τα ρητά όταν ο κατάλογος αυτός είναι εφήμερος. Εξαίρεση αποτελεί το `OMNIROUTE_WS_BRIDGE_SECRET`: δεν αποθηκεύεται στο `server.env` — τα σημεία εισόδου του διακομιστή (entrypoints) δημιουργούν μια εφήμερη τιμή ανά εκκίνηση που μπορεί να χρησιμοποιήσει μόνο η γέφυρα μέσα στην ίδια τη διεργασία, οπότε οι εξωτερικοί πελάτες της γέφυρας πρέπει να το ορίσουν ρητά. + +| Μεταβλητή | Απαιτείται | Προεπιλογή | Αρχείο προέλευσης | Περιγραφή | +| ---------------------------- | ------------------ | ------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ναι** | _(καμία)_ | `src/lib/auth` | Υπογράφει/επαληθεύει όλα τα cookie συνεδρίας του πίνακα ελέγχου (JWT). Δημιουργήστε το με `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ναι** | _(καμία)_ | `src/lib/db/apiKeys.ts` | Κλειδί κρυπτογράφησης AES για τις τιμές των κλειδιών API που είναι αποθηκευμένες στο SQLite. Δημιουργήστε το με `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Όχι | _(δεν έχει οριστεί)_ | `src/lib/auth/managementPassword.ts` | Ορίζει τον κωδικό πρόσβασης του πίνακα ελέγχου κατά την εκκίνηση, όσο δεν έχει αποθηκευτεί ακόμη κωδικός· αγνοείται μόλις αποθηκευτεί ένας κωδικός. Οι εγκαταστάσεις μέσω npm και από πηγαίο κώδικα αντιγράφουν το `.env.example` στο `.env`, οπότε ξεκινούν με το δημόσια γνωστό σύμβολο κράτησης θέσης (placeholder) `CHANGEME`: αντικαταστήστε το πριν από την πρώτη εκκίνηση, ή ορίστε `INITIAL_PASSWORD=` (κενό) σε αυτό το `.env` ώστε ο κωδικός να δημιουργηθεί στον οδηγό αρχικής ρύθμισης κατά την πρώτη επίσκεψη, όπως κάνει και μια μη ορισμένη τιμή (η προεπιλογή στην εικόνα Docker). Αλλάξτε έναν αποθηκευμένο κωδικό στο Πίνακας ελέγχου → Ρυθμίσεις → Ασφάλεια, ή εκτελέστε `omniroute reset-password` (`node bin/reset-password.mjs` σε ένα αντίγραφο πηγαίου κώδικα) με το `DATA_DIR` ορισμένο στον κατάλογο δεδομένων του διακομιστή. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ναι** (παραγωγή) | _(δεν έχει οριστεί)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Κοινό μυστικό για την εσωτερική γέφυρα WebSocket του Codex Responses. Επαληθεύει τα αιτήματα της γέφυρας μεταξύ του αναμεταδότη WS του Electron/προγράμματος περιήγησης και του OmniRoute. ⚠️ **ΑΠΑΙΤΕΙΤΑΙ στην παραγωγή — όταν δεν έχει οριστεί, όλα τα αιτήματα της γέφυρας WS απορρίπτονται.** Δημιουργήστε το με `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Όχι | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Ρητό αναγνωριστικό ακύρωσης της κρυφής μνήμης του service worker για το κέλυφος PWA (#11779)· πρώτο στην αλυσίδα επίλυσης. | +| `SOURCE_VERSION` | Όχι | _(δεν έχει οριστεί)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Δεύτερο στην αλυσίδα — ορίζεται από συστήματα δημιουργίας PaaS (π.χ. τύπου Heroku) ως το commit που έχει αναπτυχθεί. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Όχι | _(παράγεται)_ | `src/shared/components/PwaRegister.tsx` | Δημόσια τιμή χρόνου δημιουργίας που χρησιμοποιεί ο client για να καταχωρίσει το `/sw.js?v=…`· παράγεται από τις δύο παραπάνω τιμές και, στη συνέχεια, από το git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Όχι (αυτόματα) | _(αυτόματα ανά εκκίνηση)_ | `src/server/authz/policies/management.ts` | Μυστικό ανά διεργασία που αποδεικνύει ότι η αξιόπιστη σήμανση IP ομότιμου προήλθε από τον ίδιο τον διακομιστή HTTP του OmniRoute (`scripts/dev/peer-stamp.mjs`). Το middleware εξουσιοδότησης εμπιστεύεται την τοπικότητα του αιτήματος (έλεγχος loopback/LAN για διαδρομές LOCAL_ONLY) μόνο όταν η σήμανση περιέχει αυτό το διακριτικό. Δημιουργείται αυτόματα σε κάθε εκκίνηση — αφήστε το χωρίς τιμή· ορίστε το μόνιμα μόνο για διαμορφώσεις πολλαπλών διεργασιών που πρέπει να μοιράζονται τη σήμανση. | +| `OMNIROUTE_TRUSTED_PROXIES` | Όχι | _(δεν έχει οριστεί)_ | `scripts/dev/peer-stamp.mjs` | Διευθύνσεις IP ή εύρη CIDR, διαχωρισμένα με κόμματα, για αντίστροφους διακομιστές μεσολάβησης των οποίων τις τιμές `X-Forwarded-For` / `X-Real-IP` μπορεί να εμπιστεύεται η λίστα επιτρεπόμενων/αποκλεισμένων IP, επιπλέον των διευθύνσεων loopback, των διευθύνσεων ιδιωτικού δικτύου και των κόμβων παρυφής Cloudflare, τα οποία θεωρούνται πάντα αξιόπιστα. Απαιτείται μόνο για διακομιστή μεσολάβησης σε οποιαδήποτε άλλη δημόσια διεύθυνση· διαφορετικά, το φίλτρο αξιολογεί τη διεύθυνση του ίδιου του διακομιστή μεσολάβησης. | ### Εντολές δημιουργίας @@ -217,38 +217,38 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ## 4. Ασφάλεια & Έλεγχος ταυτότητας -| Μεταβλητή | Προεπιλογή | Αρχείο προέλευσης | Περιγραφή | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Τιμή salt που συνδυάζεται με αναγνωριστικά υλικού για τη δημιουργία αποτυπώματος του μηχανήματος. Αλλάξτε την ανά εγκατάσταση για λόγους απομόνωσης. | -| `OMNIROUTE_CLI_SALT` | _(μη ορισμένο = τυχαίο salt ανά εγκατάσταση, αποθηκευμένο μόνιμα στο `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Τιμή salt HMAC για την παραγωγή του τοπικού διακριτικού ελέγχου ταυτότητας CLI. Ο ορισμός αυτής της τιμής ανανεώνει όλα τα διακριτικά CLI στο μηχάνημα και έχει πάντα προτεραιότητα έναντι του αποθηκευμένου salt. Δείτε το `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Ορίζει τη σημαία `Secure` στα cookies συνεδρίας. **Πρέπει να είναι `true`** όταν εκτελείται πίσω από HTTPS. | -| `REQUIRE_API_KEY` | `false` | Ενδιάμεσο λογισμικό API | Όταν είναι `true`, όλα τα αιτήματα διαμεσολάβησης `/v1/*` πρέπει να περιλαμβάνουν έγκυρο κλειδί API. **Αυτή η σημαία δεν περιορίζει το `GET /v1/models`**, το οποίο ακολουθεί τη ρύθμιση σύνδεσης του πίνακα ελέγχου (`requireAuthForModels`) — επομένως, μια απόκριση `401` από το `/v1/models` ΔΕΝ σημαίνει ότι η εξαγωγή συμπερασμάτων προστατεύεται. Δείτε το `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Επιτρέπει την αποκάλυψη των πλήρων τιμών των κλειδιών API στο περιβάλλον χρήστη του Πίνακα ελέγχου. Μπορεί να ρυθμιστεί από τις Σημαίες δυνατοτήτων του Πίνακα ελέγχου· αποτελεί κίνδυνο ασφαλείας σε κοινόχρηστες εγκαταστάσεις. | -| `NO_LOG_API_KEY_IDS` | _(κενό)_ | `src/lib/compliance/index.ts` | Αναγνωριστικά κλειδιών API διαχωρισμένα με κόμματα, για τα οποία παρακάμπτεται η καταγραφή αιτημάτων (συμμόρφωση με τον GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(μη ορισμένο = απεριόριστο)_ | `src/shared/utils/apiKeyPolicy.ts` | Εφεδρικό ημερήσιο όριο αιτημάτων που εφαρμόζεται σε κλειδιά API των οποίων η στήλη `rate_limits` είναι null. Μη ορισμένη ή κενή τιμή: κανένα έμμεσο όριο (#2289, #11017). Το `0` έχει την ίδια σημασία (απεριόριστο). Ένας θετικός ακέραιος N ενεργοποιεί N/ημέρα, 5N/εβδομάδα, 20N/μήνα. Οι μη έγκυρες μη κενές τιμές χρησιμοποιούν ως εφεδρεία τα παλαιά χρονικά παράθυρα των 1000/ημέρα, 5000/εβδομάδα και 20000/μήνα. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Μέγιστο επιτρεπόμενο μέγεθος σώματος αιτήματος. Απορρίπτει ωφέλιμα φορτία που υπερβαίνουν αυτό το όριο. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Τα πραγματικά σώματα αιτημάτων που έχουν μέγεθος ίσο ή μεγαλύτερο από αυτό το όριο αποκτούν την ατομική, τοπική στη διεργασία άδεια εισαγωγής βαρέος φόρτου πριν από την ανάλυση JSON (διαδρομή BYTE, συμπεριλαμβανομένου του `POST /v1/responses`). Ισχύει η ίδια δυνατότητα διαφυγής όταν υπάρχει επαρκές διαθέσιμο περιθώριο του [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) όπως και για αιτήματα με βαριά δομή· εξακολουθεί να περιορίζεται από το `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), ώστε να μην επανεμφανιστεί το [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Ανώτατο όριο για διαδρομές συνομιλίας, το οποίο επιβάλλεται στα byte που διαβάζονται κατά την οριοθετημένη εισαγωγή, συμπεριλαμβανομένων αιτημάτων με ελλιπές, μη έγκυρο ή αναληθές `Content-Length`· η υπέρβαση λαμβάνει `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(δεν έχει οριστεί — χωρίς όριο πλήθους αιτημάτων)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** αυτό το παλαιό όριο ΠΛΗΘΟΥΣ αιτημάτων ισχύει πλέον μόνο όταν ορίζεται ρητά. Όταν δεν έχει οριστεί (η προεπιλογή), η αποδοχή βαρέων αιτημάτων συνομιλίας ελέγχεται αντ' αυτού από το `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — έναν αυτόματα παραγόμενο προϋπολογισμό BYTE, διαστασιολογημένο βάσει του πραγματικού ορίου μνήμης της διεργασίας σε **μία διεργασία** (ένας σωρός V8). Δύο επικαλυπτόμενα `/v1/responses` περίπου 750k token καταρρίπτουν σωρούς περίπου 12 GiB (#7849) — προειδοποίηση για τον **προϋπολογισμό μνήμης**, όχι αυστηρό μέγιστο προϊόντος ίσο με 2. Μια υγιής διεργασία (σωρός κάτω από την αναλογία απόρριψης) ΕΝΔΕΧΕΤΑΙ να αποδεχτεί περισσότερα ταυτόχρονα μεγάλα `/v1/responses` μέσω του `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Δεκάδες μακρόβιοι πελάτες SSE (40–50) εξαρτώνται από τον σωρό + το `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, όχι από «μέγιστο 2». Η τυφλή αύξηση αυτής της τιμής για «χρήση του host» επαναφέρει το #7849. Πολλαπλασιάστε τους **σωρούς** με **N ανεξάρτητα `DATA_DIR`** (#11024)· ποτέ `replicas>1` σε ένα αρχείο SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(αυτόματα παραγόμενο)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** παράκαμψη του αυτόματα παραγόμενου προϋπολογισμού byte εισαγωγής (25% του αυστηρότερου ορίου μνήμης V8/cgroup, διαιρεμένο με την παροδική ενίσχυση 8x). Τόσο οι παραγόμενες όσο και οι ρητές τιμές περιορίζονται στο εύρος 8 MiB–2 GiB. Ένα σώμα μεγαλύτερο από τον ισχύοντα προϋπολογισμό αποτυγχάνει αμέσως με `413 body_exceeds_budget`· ο ανταγωνισμός μεταξύ σωμάτων που μπορούν να εξυπηρετηθούν μεμονωμένα παραμένει επαναλήψιμος με `503`. Οι 40–50 ταυτόχρονοι μακρόβιοι πελάτες SSE εξαρτώνται από αυτόν τον προϋπολογισμό + τον σωρό, όχι από αυστηρό «μέγιστο 2». Διαβάστε τα `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` στο `/api/monitoring/health` πριν από τη ρύθμιση. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Αναλογία απόρριψης λόγω πίεσης σωρού (`heapUsed / heap_size_limit`) για την αποδοχή βαρέων αιτημάτων BYTE και STRUCTURE (#10183, #10268, #10437). Ένα ταυτόχρονο βαρύ αίτημα πέρα από το `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` απορρίπτεται με το επαναλήψιμο `503` μόνο όταν ο σωρός βρίσκεται ΕΠΙΣΗΣ σε αυτήν την αναλογία ή υψηλότερα· σε υγιή σωρό γίνεται αποδεκτό μέσω του επιπλέον περιθωρίου υγιούς κατάστασης. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (προεπιλογή `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Οριοθετημένη επιπλέον χωρητικότητα για τη γρήγορη διαδρομή υγιούς σωρού (#10437), τόσο για STRUCTURE όσο και για BYTE (`admitChatRequest`, συμπεριλαμβανομένων σωμάτων ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Χωρίς αυτό το όριο, κάθε αίτημα με απασχολημένο αλλά υγιή σωρό παρέκαμπτε την αποδοχή χωρίς κανένα ανώτατο όριο. Μόλις ενεργοποιηθούν τόσες ταυτόχρονες μισθώσεις μέσω της διαδρομής υγιούς σωρού, τα επόμενα αιτήματα υπό φόρτο μεταπίπτουν στην ΙΔΙΑ διαδρομή οριοθετημένης αναμονής/απόρριψης που χρησιμοποιείται υπό πραγματική πίεση σωρού. Το `0` απενεργοποιεί πλήρως την παράκαμψη. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Πλήθος μηνυμάτων που ταξινομεί ένα αίτημα συνομιλίας ως βαρύ, ακόμη και όταν το σώμα του βρίσκεται κάτω από το όριο byte. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Πλήθος εργαλείων που ταξινομεί ένα αίτημα συνομιλίας ως βαρύ, ακόμη και όταν το σώμα του βρίσκεται κάτω από το όριο byte. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Συντηρητική εκτίμηση token βάσει του μεγέθους συμβολοσειράς, η οποία ταξινομεί ένα αίτημα ως βαρύ· αποτελεί προσέγγιση του κόστους αποδοχής και όχι tokenization χρέωσης του παρόχου. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (απενεργοποιημένο) | `src/shared/middleware/chatBodyAdmission.ts` | Προαιρετικό όριο ιστορικού συνομιλίας με ρητή ενεργοποίηση. Απενεργοποιημένο από προεπιλογή: το πλήθος μηνυμάτων αποτελεί πολιτική ανάπτυξης και όχι καθολική ιδιότητα ενός αιτήματος, ενώ η επιβολή ορίου εδώ απορρίπτει συνομιλίες με οριστικό `413` προτού η διοχέτευση συμπίεσης μπορέσει να τις καταστήσει εξυπηρετήσιμες. Η αύξηση του σωρού περιορίζεται από το `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` και την απόρριψη λόγω πίεσης σωρού. Ορίστε μια θετική τιμή σε αναπτύξεις με περιορισμένη μνήμη που χρειάζονται αυστηρό ανώτατο όριο· τότε η υπέρβαση λαμβάνει δομημένο `413` που απαιτεί συμπίεση. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Ανώτατο όριο για μια απόκριση upstream χωρίς ροή, η οποία αποθηκεύεται προσωρινά εξ ολοκλήρου στη μνήμη. Μετά την υπέρβασή του, ο αναγνώστης upstream ακυρώνεται και το αίτημα αποτυγχάνει άμεσα, αντί να αυξάνεται μια μη οριοθετημένη συμβολοσειρά μέχρι την εξάντληση του σωρού. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Μέγιστος αριθμός byte που προωθούνται μέσω δικτύου από τις κεφαλίδες της απόκρισης ανάντη. Όταν γίνεται υπέρβαση του ορίου, οι κεφαλίδες χαμηλότερης προτεραιότητας (π.χ. προσαρμοσμένες `x-codex-*`, `x-oai-request-id`) απορρίπτονται, ώστε να τηρούνται τα συνήθη όρια κεφαλίδων των reverse proxy. Ορίστε υψηλότερη τιμή για να προωθούνται περισσότερα μεταδεδομένα ανάντη, με κόστος το μεγαλύτερο μέγεθος των κεφαλίδων απόκρισης. | -| `CORS_ORIGIN` | _(δεν έχει οριστεί)_ | `src/server/cors/origins.ts` | Παλαιού τύπου λίστα επιτρεπόμενων προελεύσεων CORS για μία μόνο προέλευση. Προτιμήστε το `CORS_ALLOWED_ORIGINS` για νέες εγκαταστάσεις. Το CORS προορίζεται μόνο για πελάτες API προγράμματος περιήγησης διαφορετικής προέλευσης· οι πιστοποιημένες εγγραφές του πίνακα ελέγχου χρησιμοποιούν αιτήματα ίδιας προέλευσης μαζί με προστασία CSRF δεσμευμένη στη συνεδρία. | -| `CORS_ALLOWED_ORIGINS` | _(δεν έχει οριστεί)_ | `src/server/cors/origins.ts` | Λίστα επιτρεπόμενων προελεύσεων CORS διαχωρισμένων με κόμματα. Δεν αποστέλλεται χαρακτήρας μπαλαντέρ, εκτός εάν έχει ρυθμιστεί ρητά το `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Δικλείδα διαφυγής αποκλειστικά για ανάπτυξη, η οποία επιστρέφει οποιαδήποτε τιμή `Origin` του προγράμματος περιήγησης. Μην την ενεργοποιείτε σε κοινόχρηστες εγκαταστάσεις ή εγκαταστάσεις παραγωγής. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Αποκλεισμός κλήσεων προς παρόχους που στοχεύουν ιδιωτικές περιοχές διευθύνσεων IP, περιοχές loopback ή link-local. Απενεργοποιήστε το μόνο σε απομονωμένα περιβάλλοντα δοκιμών. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Επιτρέπει URL παρόχων που παραπέμπουν σε ιδιωτικά/τοπικά δίκτυα (localhost, 192.168.x.x, 10.x.x.x κ.λπ.). **ΑΠΑΙΤΕΙΤΑΙ για αυτοφιλοξενούμενους παρόχους** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Όταν είναι `false`, ο πίνακας ελέγχου απορρίπτει την επικύρωση τοπικών URL. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Επιτρέπει την προσθήκη/επικύρωση παρόχων σε τοπικές/ιδιωτικές διευθύνσεις (127.0.0.1, localhost, LAN, ιδιωτικές περιοχές) — με πεδίο εφαρμογής περιορισμένο στη διαδρομή επικύρωσης παρόχου. **Προεπιλογή `true`** (προτεραιότητα στο τοπικό περιβάλλον)· ορίστε `false` για να επιβάλετε αυστηρό αποκλεισμό που επιτρέπει μόνο δημόσιες διευθύνσεις. Τα τελικά σημεία μεταδεδομένων cloud (169.254.169.254, metadata.google.internal) παραμένουν αποκλεισμένα ανεξαρτήτως ρύθμισης. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Επιτρέπει στις διαδρομές `/v1/audio/*` (μεταγραφές, ομιλία, μεταφράσεις) να χρησιμοποιούν έναν κόμβο παρόχου συμβατό με OpenAI, ο οποίος φιλοξενείται εκτός localhost. Είναι απενεργοποιημένο από προεπιλογή — η δρομολόγηση ήχου σε απομακρυσμένο κεντρικό υπολογιστή αλλάζει την ταυτότητα εξερχόμενης κίνησης και πρέπει να αποτελεί ρητή απόφαση του διαχειριστή. Οι κόμβοι loopback/ιδιωτικού δικτύου (localhost, 127.0.0.1, 172.16-31.x) επιτρέπονται πάντα και δεν επηρεάζονται. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Επιτρέπει στο `POST /v1/rerank` (και στο βήμα επανακατάταξης loopback της μηχανής μνήμης) να χρησιμοποιεί έναν κόμβο παρόχου συμβατό με OpenAI, ο οποίος φιλοξενείται εκτός localhost — έναν υπολογιστή LAN ή έναν ομότιμο κόμβο Tailscale που εκτελεί TEI, Infinity, vLLM κ.λπ. Είναι απενεργοποιημένο από προεπιλογή — η δρομολόγηση προς απομακρυσμένο κεντρικό υπολογιστή αλλάζει την ταυτότητα εξερχόμενης κίνησης και πρέπει να αποτελεί ρητή απόφαση του διαχειριστή. Οι κόμβοι loopback (localhost, 127.0.0.1, 172.16-31.x) επιτρέπονται πάντα και δεν επηρεάζονται. Οι απομακρυσμένοι κόμβοι πρέπει επίσης να συμμορφώνονται με την πολιτική εξερχόμενων URL παρόχων (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)· η κίνηση δεν δρομολογείται ποτέ προς κεντρικούς υπολογιστές μεταδεδομένων cloud. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Όταν είναι ενεργοποιημένο το OIDC, απενεργοποιεί τη σύνδεση με κωδικό πρόσβασης, ώστε οι χρήστες να μπορούν να πιστοποιούνται μόνο μέσω καθολικής σύνδεσης OIDC. Γίνεται επίσης αποδεκτό το απλό ψευδώνυμο `OIDC_DISABLE_PASSWORD_LOGIN`· η σημαία λειτουργίας του πίνακα ελέγχου με το ίδιο κλειδί έχει προτεραιότητα. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Απλό ψευδώνυμο του `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Μεταβλητή | Προεπιλογή | Αρχείο προέλευσης | Περιγραφή | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Τιμή salt που συνδυάζεται με αναγνωριστικά υλικού για τη δημιουργία αποτυπώματος του μηχανήματος. Αλλάξτε την ανά εγκατάσταση για λόγους απομόνωσης. | +| `OMNIROUTE_CLI_SALT` | _(μη ορισμένο = τυχαίο salt ανά εγκατάσταση, αποθηκευμένο μόνιμα στο `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Τιμή salt HMAC για την παραγωγή του τοπικού διακριτικού ελέγχου ταυτότητας CLI. Ο ορισμός αυτής της τιμής ανανεώνει όλα τα διακριτικά CLI στο μηχάνημα και έχει πάντα προτεραιότητα έναντι του αποθηκευμένου salt. Δείτε το `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Ορίζει τη σημαία `Secure` στα cookies συνεδρίας. **Πρέπει να είναι `true`** όταν εκτελείται πίσω από HTTPS. | +| `REQUIRE_API_KEY` | `false` | Ενδιάμεσο λογισμικό API | Όταν είναι `true`, όλα τα αιτήματα διαμεσολάβησης `/v1/*` πρέπει να περιλαμβάνουν έγκυρο κλειδί API. **Αυτή η σημαία δεν περιορίζει το `GET /v1/models`**, το οποίο ακολουθεί τη ρύθμιση σύνδεσης του πίνακα ελέγχου (`requireAuthForModels`) — επομένως, μια απόκριση `401` από το `/v1/models` ΔΕΝ σημαίνει ότι η εξαγωγή συμπερασμάτων προστατεύεται. Δείτε το `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Επιτρέπει την αποκάλυψη των πλήρων τιμών των κλειδιών API στο περιβάλλον χρήστη του Πίνακα ελέγχου. Μπορεί να ρυθμιστεί από τις Σημαίες δυνατοτήτων του Πίνακα ελέγχου· αποτελεί κίνδυνο ασφαλείας σε κοινόχρηστες εγκαταστάσεις. | +| `NO_LOG_API_KEY_IDS` | _(κενό)_ | `src/lib/compliance/index.ts` | Αναγνωριστικά κλειδιών API διαχωρισμένα με κόμματα, για τα οποία παρακάμπτεται η καταγραφή αιτημάτων (συμμόρφωση με τον GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(μη ορισμένο = απεριόριστο)_ | `src/shared/utils/apiKeyPolicy.ts` | Εφεδρικό ημερήσιο όριο αιτημάτων που εφαρμόζεται σε κλειδιά API των οποίων η στήλη `rate_limits` είναι null. Μη ορισμένη ή κενή τιμή: κανένα έμμεσο όριο (#2289, #11017). Το `0` έχει την ίδια σημασία (απεριόριστο). Ένας θετικός ακέραιος N ενεργοποιεί N/ημέρα, 5N/εβδομάδα, 20N/μήνα. Οι μη έγκυρες μη κενές τιμές χρησιμοποιούν ως εφεδρεία τα παλαιά χρονικά παράθυρα των 1000/ημέρα, 5000/εβδομάδα και 20000/μήνα. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Μέγιστο επιτρεπόμενο μέγεθος σώματος αιτήματος. Απορρίπτει ωφέλιμα φορτία που υπερβαίνουν αυτό το όριο. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Τα πραγματικά σώματα αιτημάτων που έχουν μέγεθος ίσο ή μεγαλύτερο από αυτό το όριο αποκτούν την ατομική, τοπική στη διεργασία άδεια εισαγωγής βαρέος φόρτου πριν από την ανάλυση JSON (διαδρομή BYTE, συμπεριλαμβανομένου του `POST /v1/responses`). Ισχύει η ίδια δυνατότητα διαφυγής όταν υπάρχει επαρκές διαθέσιμο περιθώριο του [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) όπως και για αιτήματα με βαριά δομή· εξακολουθεί να περιορίζεται από το `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), ώστε να μην επανεμφανιστεί το [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Ανώτατο όριο για διαδρομές συνομιλίας, το οποίο επιβάλλεται στα byte που διαβάζονται κατά την οριοθετημένη εισαγωγή, συμπεριλαμβανομένων αιτημάτων με ελλιπές, μη έγκυρο ή αναληθές `Content-Length`· η υπέρβαση λαμβάνει `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(δεν έχει οριστεί — χωρίς όριο πλήθους αιτημάτων)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** αυτό το παλαιό όριο ΠΛΗΘΟΥΣ αιτημάτων ισχύει πλέον μόνο όταν ορίζεται ρητά. Όταν δεν έχει οριστεί (η προεπιλογή), η αποδοχή βαρέων αιτημάτων συνομιλίας ελέγχεται αντ' αυτού από το `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — έναν αυτόματα παραγόμενο προϋπολογισμό BYTE, διαστασιολογημένο βάσει του πραγματικού ορίου μνήμης της διεργασίας σε **μία διεργασία** (ένας σωρός V8). Δύο επικαλυπτόμενα `/v1/responses` περίπου 750k token καταρρίπτουν σωρούς περίπου 12 GiB (#7849) — προειδοποίηση για τον **προϋπολογισμό μνήμης**, όχι αυστηρό μέγιστο προϊόντος ίσο με 2. Μια υγιής διεργασία (σωρός κάτω από την αναλογία απόρριψης) ΕΝΔΕΧΕΤΑΙ να αποδεχτεί περισσότερα ταυτόχρονα μεγάλα `/v1/responses` μέσω του `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Δεκάδες μακρόβιοι πελάτες SSE (40–50) εξαρτώνται από τον σωρό + το `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, όχι από «μέγιστο 2». Η τυφλή αύξηση αυτής της τιμής για «χρήση του host» επαναφέρει το #7849. Πολλαπλασιάστε τους **σωρούς** με **N ανεξάρτητα `DATA_DIR`** (#11024)· ποτέ `replicas>1` σε ένα αρχείο SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(αυτόματα παραγόμενο)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** παράκαμψη του αυτόματα παραγόμενου προϋπολογισμού byte εισαγωγής (25% του αυστηρότερου ορίου μνήμης V8/cgroup, διαιρεμένο με την παροδική ενίσχυση 8x). Τόσο οι παραγόμενες όσο και οι ρητές τιμές περιορίζονται στο εύρος 8 MiB–2 GiB. Ένα σώμα μεγαλύτερο από τον ισχύοντα προϋπολογισμό αποτυγχάνει αμέσως με `413 body_exceeds_budget`· ο ανταγωνισμός μεταξύ σωμάτων που μπορούν να εξυπηρετηθούν μεμονωμένα παραμένει επαναλήψιμος με `503`. Οι 40–50 ταυτόχρονοι μακρόβιοι πελάτες SSE εξαρτώνται από αυτόν τον προϋπολογισμό + τον σωρό, όχι από αυστηρό «μέγιστο 2». Διαβάστε τα `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` στο `/api/monitoring/health` πριν από τη ρύθμιση. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Αναλογία απόρριψης λόγω πίεσης σωρού (`heapUsed / heap_size_limit`) για την αποδοχή βαρέων αιτημάτων BYTE και STRUCTURE (#10183, #10268, #10437). Ένα ταυτόχρονο βαρύ αίτημα πέρα από το `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` απορρίπτεται με το επαναλήψιμο `503` μόνο όταν ο σωρός βρίσκεται ΕΠΙΣΗΣ σε αυτήν την αναλογία ή υψηλότερα· σε υγιή σωρό γίνεται αποδεκτό μέσω του επιπλέον περιθωρίου υγιούς κατάστασης. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (προεπιλογή `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Οριοθετημένη επιπλέον χωρητικότητα για τη γρήγορη διαδρομή υγιούς σωρού (#10437), τόσο για STRUCTURE όσο και για BYTE (`admitChatRequest`, συμπεριλαμβανομένων σωμάτων ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Χωρίς αυτό το όριο, κάθε αίτημα με απασχολημένο αλλά υγιή σωρό παρέκαμπτε την αποδοχή χωρίς κανένα ανώτατο όριο. Μόλις ενεργοποιηθούν τόσες ταυτόχρονες μισθώσεις μέσω της διαδρομής υγιούς σωρού, τα επόμενα αιτήματα υπό φόρτο μεταπίπτουν στην ΙΔΙΑ διαδρομή οριοθετημένης αναμονής/απόρριψης που χρησιμοποιείται υπό πραγματική πίεση σωρού. Το `0` απενεργοποιεί πλήρως την παράκαμψη. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Πλήθος μηνυμάτων που ταξινομεί ένα αίτημα συνομιλίας ως βαρύ, ακόμη και όταν το σώμα του βρίσκεται κάτω από το όριο byte. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Πλήθος εργαλείων που ταξινομεί ένα αίτημα συνομιλίας ως βαρύ, ακόμη και όταν το σώμα του βρίσκεται κάτω από το όριο byte. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Συντηρητική εκτίμηση token βάσει του μεγέθους συμβολοσειράς, η οποία ταξινομεί ένα αίτημα ως βαρύ· αποτελεί προσέγγιση του κόστους αποδοχής και όχι tokenization χρέωσης του παρόχου. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (απενεργοποιημένο) | `src/shared/middleware/chatBodyAdmission.ts` | Προαιρετικό όριο ιστορικού συνομιλίας με ρητή ενεργοποίηση. Απενεργοποιημένο από προεπιλογή: το πλήθος μηνυμάτων αποτελεί πολιτική ανάπτυξης και όχι καθολική ιδιότητα ενός αιτήματος, ενώ η επιβολή ορίου εδώ απορρίπτει συνομιλίες με οριστικό `413` προτού η διοχέτευση συμπίεσης μπορέσει να τις καταστήσει εξυπηρετήσιμες. Η αύξηση του σωρού περιορίζεται από το `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` και την απόρριψη λόγω πίεσης σωρού. Ορίστε μια θετική τιμή σε αναπτύξεις με περιορισμένη μνήμη που χρειάζονται αυστηρό ανώτατο όριο· τότε η υπέρβαση λαμβάνει δομημένο `413` που απαιτεί συμπίεση. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Ανώτατο όριο για μια απόκριση upstream χωρίς ροή, η οποία αποθηκεύεται προσωρινά εξ ολοκλήρου στη μνήμη. Μετά την υπέρβασή του, ο αναγνώστης upstream ακυρώνεται και το αίτημα αποτυγχάνει άμεσα, αντί να αυξάνεται μια μη οριοθετημένη συμβολοσειρά μέχρι την εξάντληση του σωρού. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Μέγιστος αριθμός byte που προωθούνται μέσω δικτύου από τις κεφαλίδες της απόκρισης ανάντη. Όταν γίνεται υπέρβαση του ορίου, οι κεφαλίδες χαμηλότερης προτεραιότητας (π.χ. προσαρμοσμένες `x-codex-*`, `x-oai-request-id`) απορρίπτονται, ώστε να τηρούνται τα συνήθη όρια κεφαλίδων των reverse proxy. Ορίστε υψηλότερη τιμή για να προωθούνται περισσότερα μεταδεδομένα ανάντη, με κόστος το μεγαλύτερο μέγεθος των κεφαλίδων απόκρισης. | +| `CORS_ORIGIN` | _(δεν έχει οριστεί)_ | `src/server/cors/origins.ts` | Παλαιού τύπου λίστα επιτρεπόμενων προελεύσεων CORS για μία μόνο προέλευση. Προτιμήστε το `CORS_ALLOWED_ORIGINS` για νέες εγκαταστάσεις. Το CORS προορίζεται μόνο για πελάτες API προγράμματος περιήγησης διαφορετικής προέλευσης· οι πιστοποιημένες εγγραφές του πίνακα ελέγχου χρησιμοποιούν αιτήματα ίδιας προέλευσης μαζί με προστασία CSRF δεσμευμένη στη συνεδρία. | +| `CORS_ALLOWED_ORIGINS` | _(δεν έχει οριστεί)_ | `src/server/cors/origins.ts` | Λίστα επιτρεπόμενων προελεύσεων CORS διαχωρισμένων με κόμματα. Δεν αποστέλλεται χαρακτήρας μπαλαντέρ, εκτός εάν έχει ρυθμιστεί ρητά το `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Δικλείδα διαφυγής αποκλειστικά για ανάπτυξη, η οποία επιστρέφει οποιαδήποτε τιμή `Origin` του προγράμματος περιήγησης. Μην την ενεργοποιείτε σε κοινόχρηστες εγκαταστάσεις ή εγκαταστάσεις παραγωγής. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Παλιό ψευδώνυμο (legacy alias). Ο διακόπτης του στον πίνακα ελέγχου (μια παράκαμψη αποθηκευμένη στη βάση δεδομένων) διαβάζεται πριν από αυτή την τιμή περιβάλλοντος· μια τιμή `false`, `0`, `no` ή `off` σε οποιοδήποτε από τα δύο απενεργοποιεί τους ελέγχους κεντρικών υπολογιστών όπως ακριβώς κάνει το `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, ακόμη κι όταν ο διακόπτης του συγκεκριμένου flag στον πίνακα ελέγχου είναι αποθηκευμένος ως ανενεργός· οι υπόλοιπες τιμές αφήνουν την προστασία στα δύο flag παρακάτω. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Απενεργοποιεί τους ελέγχους κεντρικών υπολογιστών της προστασίας εξερχόμενων URL — συμπεριλαμβανομένου του αποκλεισμού μεταδεδομένων cloud (όλο το 169.254.0.0/16, το metadata.google.internal) — στις διαδρομές παρόχων που ακολουθούν τη λειτουργία της προστασίας: επικύρωση URL, ανίχνευση μοντέλων, βασικά URL κόμβων παρόχων (συμπεριλαμβανομένων των απομακρυσμένων κόμβων επανακατάταξης) και η δοκιμή εναλλακτικής μεσολάβησης, μεταξύ άλλων εξερχόμενων διαδρομών συγγενικών με παρόχους, όπως η λήψη απομακρυσμένων εικόνων/μέσων. Επιτρέπει επίσης ιδιωτικούς προορισμούς webhook, εκτός από τα τελικά σημεία μεταδεδομένων cloud/link-local, που παραμένουν αποκλεισμένα ακόμη και με αυτό το flag ενεργό. Τα αιτήματα συνομιλίας που αποστέλλονται μέσω του `BaseExecutor.execute()` διατηρούν τον έλεγχο μεταδεδομένων cloud τους. Οι τοπικοί πάροχοι λειτουργούν με τις προεπιλογές: το `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (προεπιλογή `true`) επιτρέπει ήδη τοπικά και LAN URL στις διαδρομές επικύρωσης, ανίχνευσης και κόμβων παρόχου· η δοκιμή εναλλακτικής μεσολάβησης και οι ιδιωτικοί προορισμοί webhook λαμβάνουν υπόψη μόνο αυτό το flag και αποκλείουν τοπικούς/LAN κεντρικούς υπολογιστές όσο είναι ανενεργό. Υπερισχύει του `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Μια τιμή που έχει αποθηκευτεί από τον διακόπτη του πίνακα ελέγχου υπερισχύει αυτής της μεταβλητής. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Επιτρέπει URL παρόχων σε τοπικές/ιδιωτικές διευθύνσεις (127.0.0.1, localhost, LAN, ιδιωτικές περιοχές) για την επικύρωση παρόχων, την ανίχνευση μοντέλων, τη συνομιλία και τις υπόλοιπες εξερχόμενες διαδρομές συγγενικές με παρόχους που διέπει η λειτουργία της προστασίας. **Προεπιλογή `true`** (προτεραιότητα στο τοπικό περιβάλλον): τότε η προστασία αποκλείει τα τελικά σημεία μεταδεδομένων cloud — όλο το 169.254.0.0/16 συν τα γνωστά ονόματα κεντρικών υπολογιστών μεταδεδομένων (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Ορίστε `false` για λειτουργία μόνο δημόσιων διευθύνσεων, που αποκλείει και τους ιδιωτικούς και τους loopback κεντρικούς υπολογιστές. Η προστασία ελέγχει το όνομα κεντρικού υπολογιστή ή το IP literal όπως ακριβώς έχει γραφτεί, πριν από οποιαδήποτε αναζήτηση DNS· στη λειτουργία μόνο δημόσιων διευθύνσεων, το περιτύλιγμα ασφαλούς λήψης (safe-fetch wrapper) επιλύει επιπλέον το όνομα του κεντρικού υπολογιστή και απορρίπτει κάθε ιδιωτική απάντηση. Το `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` απενεργοποιεί αυτούς τους ελέγχους στις διαδρομές που παραθέτει η σειρά του. Οι ενσωματωμένοι τοπικοί πάροχοι, όπως τα LM Studio, Ollama, vLLM, Llamafile και Triton, παρακάμπτουν όλους τους ελέγχους κεντρικών υπολογιστών για την επικύρωση κλειδιών και τη συνομιλία. Μια τιμή που έχει αποθηκευτεί από τον διακόπτη του πίνακα ελέγχου υπερισχύει αυτής της μεταβλητής. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Επιτρέπει στις διαδρομές `/v1/audio/*` (μεταγραφές, ομιλία, μεταφράσεις) να χρησιμοποιούν έναν κόμβο παρόχου συμβατό με OpenAI, ο οποίος φιλοξενείται εκτός localhost. Είναι απενεργοποιημένο από προεπιλογή — η δρομολόγηση ήχου σε απομακρυσμένο κεντρικό υπολογιστή αλλάζει την ταυτότητα εξερχόμενης κίνησης και πρέπει να αποτελεί ρητή απόφαση του διαχειριστή. Οι κόμβοι loopback/ιδιωτικού δικτύου (localhost, 127.0.0.1, 172.16-31.x) επιτρέπονται πάντα και δεν επηρεάζονται. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Επιτρέπει στο `POST /v1/rerank` (και στο βήμα επανακατάταξης loopback της μηχανής μνήμης) να χρησιμοποιεί έναν κόμβο παρόχου συμβατό με OpenAI, ο οποίος φιλοξενείται εκτός localhost — έναν υπολογιστή LAN ή έναν ομότιμο κόμβο Tailscale που εκτελεί TEI, Infinity, vLLM κ.λπ. Είναι απενεργοποιημένο από προεπιλογή — η δρομολόγηση προς απομακρυσμένο κεντρικό υπολογιστή αλλάζει την ταυτότητα εξερχόμενης κίνησης και πρέπει να αποτελεί ρητή απόφαση του διαχειριστή. Οι κόμβοι loopback (localhost, 127.0.0.1, 172.16-31.x) επιτρέπονται πάντα και δεν επηρεάζονται. Το βασικό URL ενός απομακρυσμένου κόμβου πρέπει επίσης να συμμορφώνεται με την πολιτική εξερχόμενων URL παρόχων (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Όταν είναι ενεργοποιημένο το OIDC, απενεργοποιεί τη σύνδεση με κωδικό πρόσβασης, ώστε οι χρήστες να μπορούν να πιστοποιούνται μόνο μέσω καθολικής σύνδεσης OIDC. Γίνεται επίσης αποδεκτό το απλό ψευδώνυμο `OIDC_DISABLE_PASSWORD_LOGIN`· η σημαία λειτουργίας του πίνακα ελέγχου με το ίδιο κλειδί έχει προτεραιότητα. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Απλό ψευδώνυμο του `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Λίστα ελέγχου ενίσχυσης ασφάλειας diff --git a/docs/i18n/es/CONTRIBUTING.md b/docs/i18n/es/CONTRIBUTING.md index 294552436080..22b82c7d0f6a 100644 --- a/docs/i18n/es/CONTRIBUTING.md +++ b/docs/i18n/es/CONTRIBUTING.md @@ -387,22 +387,22 @@ Escribe pruebas unitarias en `tests/unit/` que cubran, como mínimo: --- -## Lista de verificación de la solicitud de incorporación de cambios +## Lista de comprobación del Pull Request - [ ] Las pruebas pasan (`npm test`) - [ ] El linting pasa (`npm run lint`) - [ ] La compilación se completa correctamente (`npm run build`) - [ ] Se añadieron tipos de TypeScript para las nuevas funciones e interfaces públicas -- [ ] No hay secretos ni valores de respaldo codificados de forma fija +- [ ] No hay secretos ni valores de respaldo codificados directamente - [ ] Las credenciales públicas de upstream están integradas mediante `resolvePublicCred()` (consulta [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nunca como literales -- [ ] Las respuestas de error pasan por `buildErrorBody()` / `sanitizeErrorMessage()`; no hay trazas de pila sin procesar en los cuerpos de las respuestas (consulta [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Los comandos de shell (`exec` / `spawn`) pasan los valores en tiempo de ejecución mediante `env`, no mediante interpolación de cadenas +- [ ] Las respuestas de error se procesan mediante `buildErrorBody()` / `sanitizeErrorMessage()` — no hay trazas de pila sin procesar en los cuerpos de las respuestas (consulta [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Los comandos de shell (`exec` / `spawn`) pasan los valores de tiempo de ejecución mediante `env`, no mediante interpolación de cadenas - [ ] Todas las entradas se validan con esquemas de Zod -- [ ] Se añadió un **fragmento** del registro de cambios en `changelog.d/{features|fixes|maintenance}/-.md` para los cambios visibles para el usuario (consulta [`changelog.d/README.md`](./changelog.d/README.md)); **no** edites `CHANGELOG.md` directamente; los fragmentos se agregan en el momento de la publicación y nunca generan conflictos entre solicitudes de incorporación de cambios +- [ ] Se añadió un **fragmento** del registro de cambios en `changelog.d/{features|fixes|maintenance}/-.md` para los cambios visibles para el usuario (consulta [`changelog.d/README.md`](./changelog.d/README.md)) — **no** edites `CHANGELOG.md` directamente; los fragmentos se agregan en el momento del lanzamiento y nunca generan conflictos entre Pull Requests - [ ] Se actualizó la documentación (si corresponde) -- [ ] No se abrieron nuevas alertas de CodeQL / Secret-Scanning, o cada una se descartó con una justificación técnica que hace referencia al documento pertinente de `docs/security/` -- [ ] Las rutas que generan procesos secundarios (`/api/mcp/`, `/api/cli-tools/runtime/`) están clasificadas como `isLocalOnlyPath()` en `src/server/authz/routeGuard.ts`; consulta la [Regla estricta n.º 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] No hay pies `Co-Authored-By` en los mensajes de commit; los commits deben aparecer únicamente bajo la identidad de Git del propietario del repositorio (Regla estricta n.º 16) +- [ ] No se generaron nuevas alertas de CodeQL / Secret-Scanning, o cada una se descartó con una justificación técnica que hace referencia al documento pertinente de `docs/security/` +- [ ] Las rutas que generan procesos secundarios (`/api/mcp/`, `/api/cli-tools/runtime/`) están clasificadas como `isLocalOnlyPath()` en `src/server/authz/routeGuard.ts` — consulta la [Regla estricta n.º 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] No hay líneas finales `Co-authored-by` de IA/bots en los mensajes de commit (Regla estricta n.º 16) — se reconoce a los colaboradores humanos cuyo trabajo se reutiliza mediante líneas finales estándar `Co-authored-by: Name ` --- diff --git a/docs/i18n/es/docs/architecture/QUALITY_GATES.md b/docs/i18n/es/docs/architecture/QUALITY_GATES.md index 543647f1fe7b..6cdc83319ed4 100644 --- a/docs/i18n/es/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/es/docs/architecture/QUALITY_GATES.md @@ -20,226 +20,220 @@ replicación independiente de herramientas del mismo sistema, consulta el ### Admisión de candidatos Los workflows de CI y Quality Gates emiten cada uno un veredicto estable: `Gate / CI` y -`Gate / Quality`. Su política de admisión versionada enumera cada job precedente -como obligatorio o consultivo. Un job obligatorio aplicable debe finalizar correctamente: -los resultados ausentes, cancelados, omitidos, pendientes y desconocidos no pueden establecer -un PASS. Una clasificación válida de solo documentación o solo catálogo puede hacer que -un carril de código no sea aplicable; un PR en borrador no se acepta como candidato. -Una etiqueta `hotfix` no exime de presentar evidencias. +`Gate / Quality`. Su política de admisión versionada enumera cada job previo +como obligatorio o consultivo. Un job obligatorio aplicable debe finalizar correctamente: los resultados +ausentes, cancelados, omitidos, pendientes y desconocidos no pueden establecer PASS. Una clasificación válida +de solo documentación o solo catálogo puede hacer que un lane de código no sea aplicable; +un PR en borrador no es un candidato aceptado. Una etiqueta `hotfix` no exime de aportar evidencias. Ambos workflows abarcan los PR y los pushes a las ramas main/release, la ejecución manual y -los eventos de merge group. Los pushes, la ejecución manual y los merge groups ejecutan la -selección completa. Los forks y los merge groups usan runners alojados para los jobs que, de -otro modo, seleccionarían runners autoalojados; antes del despliegue debe verificarse que haya -capacidad alojada suficiente. +los eventos de merge group. Los pushes, las ejecuciones manuales y los merge groups ejecutan la selección completa. Los forks +y los merge groups utilizan runners alojados para los jobs que, de otro modo, seleccionarían runners +autohospedados; debe verificarse que haya capacidad alojada suficiente antes del despliegue. -Cada recibo JSON identifica el SHA extraído, la ejecución del workflow y el intento. -La CLI rechaza cualquier discrepancia entre el SHA del checkout y el del evento. Las pruebas -del workflow vinculan la pertenencia a la política con la lista `needs` del job de veredicto -para que ningún carril nuevo o eliminado pueda desaparecer silenciosamente. Los recibos cubren -su propio workflow, no la publicación, el despliegue ni el funcionamiento interno de un -escáner consultivo existente. Activar ambos nombres de checks en las reglas de las ramas es -un cambio administrativo independiente; añadir estos jobs no protege una rama por sí solo. +Cada recibo JSON identifica el SHA del checkout, la ejecución del workflow y el intento. +La CLI rechaza una discrepancia entre el SHA del checkout y el del evento. Las pruebas del workflow vinculan la pertenencia a la política +con la lista `needs` del job de veredicto, de modo que un lane nuevo o eliminado no pueda desaparecer silenciosamente. +Los recibos abarcan su propio workflow, no la publicación, el despliegue ni el funcionamiento interno +de un escáner consultivo existente. Activar ambos nombres de checks en las reglas de las ramas es un +cambio administrativo independiente; añadir estos jobs no protege por sí mismo una rama. -### Inventario de análisis estático +### Inventario del análisis estático El inventario versionado de alias de npm y la pertenencia al análisis estático se encuentran en `config/quality/gate-manifest.json`. Ejecute `npm run check:gate-manifest` para validar -los nombres de los scripts y los comandos exactos con respecto a `package.json`; las adiciones, -eliminaciones y desviaciones de comandos hacen fallar tanto el hook local como los jobs de -clasificación de cambios en CI. Un alias no es un job de workflow, una instancia de matriz ni -un caso de prueba: estos recuentos no deben presentarse como intercambiables. +los nombres de los scripts y los comandos exactos con respecto a `package.json`; las adiciones, eliminaciones y +variaciones de comandos hacen fallar tanto el hook local como los jobs de clasificación de cambios en CI. +Un alias no es un job del workflow, una instancia de matriz ni un caso de prueba: estos recuentos no deben +presentarse como intercambiables. -Use `npm run quality:scan -- --list` o `npm run quality:scan:fast -- --list` +Utilice `npm run quality:scan -- --list` o `npm run quality:scan:fast -- --list` para inspeccionar los alias seleccionados sin ejecutarlos. El runner invoca el -punto de entrada de npm, por lo que se conserva su entorno de ejecución (incluido Bun cuando -está configurado). El manifiesto registra como invocados por separado los alias que quedan -fuera de esos perfiles, y los comandos de mantenimiento están prohibidos en los perfiles de -análisis de solo lectura. - -Estos perfiles cubren únicamente el análisis estático. No certifican las pruebas del producto, -la cobertura, el empaquetado, los checks externos ni la aceptación completa de una versión -candidata. La admisión del workflow utiliza los archivos vinculados -`config/quality/admission-policy.json` y -`scripts/quality/admission-verdict.mjs`. Los perfiles de observación de versiones permanecen -separados; inspeccione de forma independiente sus checks aplicables y sus recibos. El inventario -descriptivo que aparece a continuación es una referencia, no una prueba de que un gate se haya -ejecutado realmente. +punto de entrada de npm, por lo que se conserva su entorno de ejecución (incluido Bun cuando esté configurado). +El manifiesto registra los alias externos a esos perfiles como invocados por separado, y +los comandos de mantenimiento están prohibidos en los perfiles de análisis de solo lectura. + +Estos perfiles abarcan únicamente el análisis estático. No certifican las pruebas del producto, +la cobertura, el empaquetado, los checks externos ni la aceptación completa de una versión candidata. +La admisión del workflow utiliza `config/quality/admission-policy.json` y +`scripts/quality/admission-verdict.mjs`, que están vinculados. Los perfiles de release observer permanecen separados; +inspeccione de forma independiente sus checks y recibos aplicables. El inventario descriptivo +que aparece a continuación es una referencia, no una prueba de que un gate se haya ejecutado realmente. Los scripts se encuentran en `scripts/check/` (gates de políticas) y `scripts/quality/` (motor de ratchet). La fuente de verdad de CI es `.github/workflows/ci.yml`. -### Ruta rápida de PR de versión (`quality.yml`) +### Ruta rápida de PR de release (`quality.yml`) -`.github/workflows/quality.yml` complementa a CI en los PR de main/release, los pushes a ramas -protegidas, la ejecución manual y los merge groups. Los PR usan checks rápidos filtrados por rutas. -Se eliminó la compilación duplicada que estaba deshabilitada permanentemente; los checks reales -de compilación, empaquetado y arranque permanecen en CI. +`.github/workflows/quality.yml` complementa CI en los PR de main/release, los pushes a ramas +protegidas, las ejecuciones manuales y los merge groups. Los PR utilizan checks rápidos filtrados por rutas. La compilación duplicada +deshabilitada permanentemente se eliminó; los checks reales de compilación/empaquetado/arranque permanecen en CI. -| Job | Alcance | Bloqueante | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `Docs Gates (fast-path)` | PR de documentación/código; referencias de documentación de la API y toda la documentación | Sí | -| `Fast Quality Gates` | PR de código; checks estáticos, typecheck, typecheck del dashboard y pruebas unitarias afectadas | Sí | -| `Forgotten sibling tests` | PR de código; módulos modificados rastreados hasta consumidores estáticos y pruebas hermanas candidatas; las rutas de barrel e importación dinámica se notifican como diagnósticos consultivos, con las excepciones referenciadas de la lista de permitidos | **Consultivo** | -| `Vitest (fast-path)` | PR de código; suite rápida de vitest | Sí | -| `Unit Tests fast-path` | PR de código; suite unitaria de 4 shards | Sí | -| `No new ESLint warnings` | PR de código; protección de lint que tiene en cuenta las supresiones | Sí, incluidos los forks | -| `Merge integrity (changelog + generated skills)` | PR que no están en borrador; sincronización del changelog y las skills generadas | Sí, incluidos los forks | +| Job | Alcance | Bloqueante | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `Docs Gates (fast-path)` | PR de documentación/código; referencias de documentación de la API y toda la documentación | Sí | +| `Fast Quality Gates` | PR de código; checks estáticos, typecheck, typecheck del dashboard y pruebas unitarias afectadas | Sí | +| `Forgotten sibling tests` | PR de código; módulos modificados rastreados hasta consumidores estáticos y posibles pruebas hermanas; las rutas mediante barrels e importaciones dinámicas se notifican como diagnósticos consultivos, con las excepciones referenciadas de la lista de permitidos | **Consultivo** | +| `Vitest (fast-path)` | PR de código; suite rápida de vitest | Sí | +| `Unit Tests fast-path` | PR de código; suite unitaria de 4 shards | Sí | +| `No new ESLint warnings` | PR de código; protección de lint que tiene en cuenta las supresiones | Sí, incluidos los forks | +| `Merge integrity (changelog + generated skills)` | PR que no estén en borrador; sincronización del changelog y de las skills generadas | Sí, incluidos los forks | #### Informe de pruebas hermanas olvidadas -`npm run check:forgotten-sibling-tests` reutiliza el resolutor de importaciones que sustenta el mapa de impacto de las pruebas. -Para cada módulo de producción modificado, informa de cadenas deterministas -`módulo/símbolo modificado -> consumidor estático -> prueba hermana candidata` cuando la prueba -candidata no aparece en el diff del pull request. El resumen Markdown y el resultado JSON se -conservan como el artefacto de workflow `forgotten-sibling-tests` para su calibración antes de -cualquier despliegue con carácter bloqueante. +`npm run check:forgotten-sibling-tests` reutiliza el solucionador de importaciones que sustenta el mapa de impacto de las pruebas. +Para cada módulo de producción modificado, notifica cadenas deterministas +`módulo/símbolo modificado -> consumidor estático -> posible prueba hermana` cuando la prueba candidata +no está presente en el diff del pull request. El resumen en Markdown y el resultado JSON se conservan como +el artefacto del workflow `forgotten-sibling-tests` para su calibración antes de cualquier despliegue bloqueante. -Las reexportaciones mediante barrels y las importaciones dinámicas son únicamente diagnósticos de resolución; nunca generan un +Las reexportaciones mediante archivos barrel y las importaciones dinámicas son únicamente diagnósticos de resolución; nunca generan un hallazgo bloqueante. Las excepciones revisadas se encuentran en `config/quality/forgotten-sibling-allowlist.json`. Cada entrada debe indicar la prueba consumidora y la -prueba candidata, proporcionar una justificación específica y enlazar a una incidencia o solicitud de extracción de GitHub. Las entradas con formato incorrecto -producen un fallo restrictivo. Las excepciones no pueden omitir una prueba candidata eliminada ni un diff que añada `.skip`/`.todo`; -el debilitamiento de aserciones y otros tipos de enmascaramiento siguen siendo responsabilidad de la puerta bloqueante independiente +prueba candidata, proporcionar una justificación específica y enlazar una incidencia o solicitud de incorporación de cambios de GitHub. Las entradas con formato incorrecto +producen un fallo seguro. Las excepciones no pueden omitir una prueba candidata eliminada ni un diff que añada `.skip`/`.todo`; +el debilitamiento de aserciones y otras formas de enmascaramiento siguen siendo responsabilidad de la puerta de control bloqueante e independiente `check:test-masking`. ### Trabajo: `lint` Se ejecuta en cada PR a `main`. Bloquea la fusión en caso de fallo. -| Script (`npm run ...`) | Valida | Bloqueante | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | La versión de Node.js está dentro del intervalo compatible | Sí | -| `check:cycles` | Importaciones circulares: todos los módulos de `src/` + `open-sse/` | Sí | -| `check:route-validation:t06` | Hay esquemas de Zod presentes en todas las rutas (política del nivel 6) | Sí | -| `check:any-budget:t11` | El recuento de `@ts-expect-error // any` no supera el límite (catraca del nivel 11) | Sí | -| `check:provider-consistency` | Cada proveedor en `providers.ts` tiene una entrada correspondiente en `providerRegistry.ts` (y viceversa, dentro de la lista de permitidos) | Sí | -| `check:model-lifecycle` | Las tres tablas de enrutamiento mantenidas manualmente permanecen coherentes con la instantánea del ciclo de vida incluida en el repositorio (#11503): `FITNESS_TABLE` (`taskFitness.ts`) no puntúa ningún id retirado que `REGISTRY` pueda enrutar; cada destino de `BUILT_IN_ALIASES` está presente en `REGISTRY` y ausente de la instantánea de ids retirados; cada id retirado que aún figura en `REGISTRY` se redirige o aparece en `allowedRetiredInCatalog`; y ningún origen o destino de `DEFAULT_DEGRADATION_MAP` figura como retirado en esa instantánea. Esto no demuestra que un modelo esté siendo servido actualmente por un proveedor ascendente activo. Sin conexión: compara con `config/quality/model-lifecycle.json`, que se actualiza manualmente con `npm run quality:refresh-model-lifecycle` (requiere red; no está integrado en CI). `allowedRetiredInCatalog` es un mecanismo de reducción progresiva: añada una entrada únicamente con una incidencia de seguimiento. | Sí | -| `check:fetch-targets` | Cada `fetch("/api/...")` del código cliente en `src/` se resuelve en un `route.ts` real | Sí | -| `check:deps` | Todas las dependencias instalables mediante `npm install` de cada `package.json` del repositorio figuran en `dependency-allowlist.json`; se marcan los paquetes nuevos sin versión fijada o con posibles errores tipográficos maliciosos | Sí | -| `audit:deps` | `npm audit` (raíz + electron): sin avisos de gravedad alta o crítica (se solapa con `check:vuln-ratchet` de osv; consulte la lista de tareas pendientes de racionalización) | Sí | -| `check:lockfile` | Integridad de `package-lock.json`: registro https, hashes de integridad y ninguna sobrescritura de host | Sí | -| `check:licenses` | Lista permitida de licencias SPDX para dependencias de producción | Sí | -| `check:tracked-artifacts` | Sin artefactos de compilación ni enlaces simbólicos de `node_modules` confirmados (también se ejecuta en el pre-commit de husky; el pre-push es intencionadamente ligero — #6716) | Sí | -| `check:ai-attribution` | Sin tráiler `Co-Authored-By` de IA/bot ni pie de página de generación mediante IA en los commits, el título o el cuerpo del PR — Regla estricta n.º 16 (en el bucle de comprobaciones rápidas de `quality.yml` para PR→`release/**` — lee la carga útil del evento y no realiza ninguna operación fuera de los PR — y un paso exclusivo para PR en el lint de `ci.yml` para PR→`main`; también en el hook `commit-msg` de husky; se permiten coautores humanos; #14436) | -| `check:vitest-exclusions` | Cada exclusión de Vitest menciona una incidencia de seguimiento y aparece en `config/quality/vitest-exclusions.json` (#13204) | Sí | -| `check:file-size` | Ningún archivo fuente supera el límite por extensión (ajuste progresivo: archivos grandes congelados en la lista `frozen`) | Sí | -| `check:error-helper` | Las respuestas de error en ejecutores/manejadores usan `buildErrorBody()` / `sanitizeErrorMessage()` (Regla estricta n.º 12) | Sí | -| `check:migration-numbering` | Los archivos SQL de migración están numerados secuencialmente, sin saltos ni duplicados | Sí | -| `check:public-creds` | No hay valores literales de OAuth `client_id`/`client_secret` ni claves web de Firebase fuera de `publicCreds.ts` (regla estricta n.º 11) | Sí | -| `check:db-rules` | No hay SQL sin procesar fuera de los módulos de `src/lib/db/`; no hay importaciones de barril desde `localDb.ts` (reglas estrictas n.º 2/n.º 5) | Sí | -| `check:known-symbols` | Los ejecutores de proveedores, las estrategias de enrutamiento y los traductores registrados en sus tablas de despacho coinciden con los archivos del disco, sin símbolos huérfanos ni sin declarar | Sí | -| `check:route-guard-membership` | Cada ruta que genera un proceso hijo está clasificada por `isLocalOnlyPath()` (reglas estrictas n.º 15/n.º 17) | Sí | -| `check:test-discovery` | Cada archivo `*.test.ts` / `*.spec.ts` del repositorio es recopilado por al menos un ejecutor de pruebas (mecanismo de trinquete: la lista de archivos huérfanos de `test-discovery-baseline.json` solo puede reducirse) | Sí | -| `check:agent-skills-sync` | Los artefactos generados de agent-skills coinciden con su catálogo de origen (sin desviaciones) | -| `check:provider-asset-provenance` | Los logotipos/recursos de proveedores tienen registrada una entrada de procedencia | -| `lint:json` | Los archivos de configuración JSON se analizan correctamente y cumplen las reglas de lint del repositorio | -| `typecheck:core` | Compilación de TypeScript sin errores (solo advertencias informativas) | Sí | -| `typecheck:noimplicit:core` | `noImplicitAny` estricto — orientado al futuro; muchos sitios de llamada preexistentes aún necesitan anotaciones | **Informativo** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` limitado a `src/app/(dashboard)/**` (#7033): la lista de permitidos seleccionada de 27 archivos de `typecheck:core` no incluye ningún TSX del panel, y `next build` tampoco realiza nunca la comprobación de tipos (`next.config.mjs` establece `ignoreBuildErrors: true`), por lo que las regresiones de identificadores huérfanos allí (#6625/#6909) no eran visibles para CI. Compara con una línea base congelada del recuento por archivo y por código TS (`config/quality/dashboard-typecheck-baseline.json`, con el mismo patrón de detección de obsolescencia que `check:known-symbols`): solo los errores NUEVOS que superen el recuento de la línea base hacen fallar la comprobación; redúzcala progresivamente con `--update` cuando se corrija un error preexistente. | Sí | +| Script (`npm run ...`) | Valida | Bloqueante | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | La versión de Node.js se encuentra dentro del intervalo compatible | Sí | +| `check:cycles` | Importaciones circulares en todo `src/` + `open-sse/` (basado en AST, con los `paths` de tsconfig resueltos). Por sí solo = informativo; enumera los ciclos. `check:cycles:ratchet` (lo que ejecuta CI) bloquea cuando el recuento supera el límite máximo de `metrics.cycles` en `quality-baseline.json`: actualmente 14, `direction: down`, por lo que solo puede disminuir (#15159 G-01/G-02) | Sí (mecanismo de trinquete) | +| `check:route-validation:t06` | Hay esquemas Zod presentes en todas las rutas (política de nivel 6) | Sí | +| `check:any-budget:t11` | El recuento de `@ts-expect-error // any` no supera el presupuesto (mecanismo de trinquete de nivel 11) | Sí | +| `check:provider-consistency` | Cada proveedor de `providers.ts` tiene una entrada correspondiente en `providerRegistry.ts` (y viceversa, dentro de la lista de permitidos) | Sí | +| `check:model-lifecycle` | Las tres tablas de enrutamiento mantenidas manualmente permanecen coherentes con la instantánea del ciclo de vida incluida en el repositorio (#11503): `FITNESS_TABLE` (`taskFitness.ts`) no puntúa ningún id retirado que `REGISTRY` pueda enrutar; cada destino de `BUILT_IN_ALIASES` está presente en `REGISTRY` y ausente de la instantánea de ids retirados; cada id retirado que aún está en `REGISTRY` se reenvía o figura en `allowedRetiredInCatalog`; y ninguna fuente ni destino de `DEFAULT_DEGRADATION_MAP` aparece como retirado en esa instantánea. Esto no demuestra que un modelo sea servido actualmente por un servicio upstream activo. Sin conexión: compara con `config/quality/model-lifecycle.json`, actualizado manualmente mediante `npm run quality:refresh-model-lifecycle` (requiere red; no está integrado en CI). `allowedRetiredInCatalog` es un mecanismo de reducción progresiva: añade una entrada únicamente con una incidencia de seguimiento. | Sí | +| `check:fetch-targets` | Cada `fetch("/api/...")` del código del cliente en `src/` se resuelve en un `route.ts` real | Sí | +| `check:deps` | Todas las dependencias instalables mediante `npm install` de cada `package.json` del repositorio están en `dependency-allowlist.json`; se señalan los paquetes nuevos sin versión fijada o potencialmente fruto de slopsquatting | Sí | +| `audit:deps` | `npm audit` (raíz + electron): no hay avisos de gravedad alta/crítica (se solapa con `check:vuln-ratchet` de osv; consulta la Lista de racionalización pendiente) | Sí | +| `check:lockfile` | Integridad de `package-lock.json`: registro https, hashes de integridad y ninguna sobrescritura de host | Sí | +| `check:licenses` | Lista de licencias SPDX permitidas para las dependencias de producción | Sí | +| `check:tracked-artifacts` | Sin artefactos de compilación ni enlaces simbólicos de `node_modules` confirmados (también se ejecuta en el pre-commit de husky; el pre-push es intencionadamente ligero — #6716) | Sí | +| `check:ai-attribution` | Sin tráiler `Co-Authored-By` de IA/bot ni pie de página de generación mediante IA en los commits, el título o el cuerpo del PR — Regla estricta #16 (en el bucle de comprobaciones rápidas de `quality.yml` para PR→`release/**` — lee la carga útil del evento y no hace nada fuera de los PR — y en un paso exclusivo para PR de lint en `ci.yml` para PR→`main`; también en el hook `commit-msg` de husky; se permiten coautores humanos; #14436) | +| `check:vitest-exclusions` | Cada exclusión de Vitest indica una incidencia de seguimiento y aparece en `config/quality/vitest-exclusions.json` (#13204) | Sí | +| `check:file-size` | Ningún archivo fuente supera el límite por extensión (ajuste progresivo: archivos grandes inmovilizados en la lista `frozen`) | Sí | +| `check:error-helper` | Las respuestas de error en ejecutores/controladores usan `buildErrorBody()` / `sanitizeErrorMessage()` (Regla estricta #12) | Sí | +| `check:migration-numbering` | Los archivos SQL de migración están numerados secuencialmente, sin huecos ni duplicados | Sí | +| `check:public-creds` | No hay valores literales de OAuth `client_id`/`client_secret` ni claves web de Firebase fuera de `publicCreds.ts` (regla estricta n.º 11) | Sí | +| `check:db-rules` | No hay SQL sin procesar fuera de los módulos de `src/lib/db/`; no hay importaciones desde archivos de reexportación de `localDb.ts` (reglas estrictas n.º 2/5) | Sí | +| `check:known-symbols` | Los ejecutores de proveedores, las estrategias de enrutamiento y los traductores registrados en sus tablas de despacho coinciden con los archivos del disco; no hay símbolos huérfanos ni sin declarar | Sí | +| `check:route-guard-membership` | Todas las rutas que generan un proceso secundario están clasificadas por `isLocalOnlyPath()` (reglas estrictas n.º 15/17) | Sí | +| `check:test-discovery` | Cada archivo `*.test.ts` / `*.spec.ts` del repositorio es recopilado por al menos un ejecutor de pruebas (mecanismo de trinquete: la lista de archivos huérfanos de `test-discovery-baseline.json` solo puede reducirse) | Sí | +| `check:agent-skills-sync` | Los artefactos generados de agent-skills coinciden con su catálogo de origen (sin divergencias) | +| `check:provider-asset-provenance` | Los logotipos/recursos de proveedores cuentan con una entrada de procedencia registrada | +| `lint:json` | Los archivos de configuración JSON se analizan correctamente y cumplen las reglas de lint del repositorio | +| `typecheck:core` | Compilación de TypeScript sin errores (solo advertencias informativas) | Sí | +| `typecheck:noimplicit:core` | `noImplicitAny` estricto — orientado al futuro; muchos puntos de llamada preexistentes aún necesitan anotaciones | **Informativo** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` limitado a `src/app/(dashboard)/**` (#7033) — la lista permitida seleccionada de 27 archivos de `typecheck:core` no incluye ningún TSX del panel, y `next build` tampoco comprueba nunca sus tipos (`next.config.mjs` establece `ignoreBuildErrors: true`), por lo que las regresiones de identificadores huérfanos allí (#6625/#6909) eran invisibles para CI. Compara con una línea base congelada del recuento por archivo/por código TS (`config/quality/dashboard-typecheck-baseline.json`, el mismo patrón de aplicación contra obsolescencia que `check:known-symbols`): solo los errores NUEVOS que superen el recuento de la línea base hacen fallar la comprobación; reduzca progresivamente la línea base con `--update` cuando se corrija un error preexistente. | Sí | ### Trabajo: `quality-gate` Se ejecuta después de `test-coverage`. Bloquea la fusión si falla. -| Script | Valida | Bloqueante | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `quality:collect` | Genera `quality-metrics.json` (número de advertencias de ESLint, cobertura del informe combinado de fragmentos) | Sí (antes del ratchet) | -| `quality:ratchet` | Ninguna métrica de `quality-baseline.json` ha empeorado (advertencias de ESLint ≤ línea base; cobertura ≥ línea base) | Sí | -| `check:duplication` | La duplicación de código (jscpd@4) no supera la línea base de `quality-baseline.json` | Sí | -| `check:complexity` | La complejidad ciclomática a nivel de archivo no supera el límite (reglas `complexity` + `max-lines-per-function` del núcleo de ESLint) | Sí | -| `check:cognitive-complexity` | Ratchet de complejidad cognitiva (`eslint-plugin-sonarjs`): pasada de ESLint independiente; CI ejecuta ambas combinadas como el único paso `check:complexity-ratchets` | Sí | -| `check:dead-code` | El ratchet de exportaciones/archivos no utilizados (knip) no empeora con respecto a la línea base | Sí | -| `check:compression-budget` | Presupuesto del benchmark de compresión: los valores mínimos de ahorro de tokens por motor no deben empeorar | Sí | -| `check:type-coverage` | El ratchet del porcentaje de código tipado (`type-coverage`) no empeora; sustituye en gran medida a `typecheck:noimplicit:core` | Sí | -| `check:codeql-ratchet` | El número de alertas abiertas de CodeQL no empeora (se consulta mediante `gh api`; se omite correctamente si no hay token): para la frecuencia de actualización y la activación manual, véase «Ratchet de CodeQL» más abajo | Sí | +| Script | Valida | Bloqueante | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `quality:collect` | Genera `quality-metrics.json` (número de advertencias de ESLint, cobertura del informe combinado de los fragmentos) | Sí (previo al ratchet) | +| `quality:ratchet` | Ninguna métrica de `quality-baseline.json` ha empeorado (advertencias de ESLint ≤ línea base; cobertura ≥ línea base) | Sí | +| `check:duplication` | La duplicación de código (jscpd@4) no supera la línea base de `quality-baseline.json` | Sí | +| `check:complexity` | La complejidad ciclomática por archivo no supera el límite (reglas `complexity` + `max-lines-per-function` del núcleo de ESLint) | Sí | +| `check:cognitive-complexity` | Ratchet de complejidad cognitiva (`eslint-plugin-sonarjs`): pasada separada de ESLint; CI ejecuta ambas combinadas en el único paso `check:complexity-ratchets` | Sí | +| `check:dead-code` | El ratchet de exportaciones/archivos sin usar (knip) no empeora respecto a la línea base | Sí | +| `check:compression-budget` | Presupuesto del benchmark de compresión: los mínimos de ahorro de tokens por motor no deben empeorar | Sí | +| `check:type-coverage` | El ratchet del porcentaje tipado (`type-coverage`) no empeora; sustituye en gran medida a `typecheck:noimplicit:core` | Sí | +| `check:codeql-ratchet` | El número de alertas abiertas de CodeQL no empeora (se consulta mediante `gh api`; omisión controlada sin token): para la frecuencia de actualización y la activación manual, consulte «Ratchet de CodeQL» más abajo | Sí | ### Trabajo: `quality-extended` El trabajo completo es consultivo (`continue-on-error: true`). Los ratchets basados en npm se ejecutan -realmente; los analizadores externos se instalan mediante `gh release download` y se omiten automáticamente (código de salida 0) -cuando un binario sigue sin estar disponible. - -| Script | Valida | Bloqueante | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | -| `check:circular-deps` | No hay dependencias circulares (dpdm) | **Consultivo** | -| `check:bundle-size` | El tamaño del paquete no supera el límite | **Consultivo** | -| `check:secrets` | Análisis de secretos (gitleaks): se omite si el binario no está disponible | **Consultivo** | -| `check:vuln-ratchet` | Las vulnerabilidades de las dependencias (osv-scanner) no empeoran: se omite si el binario no está disponible | **Consultivo** | -| `check:workflows` | Lint de flujos de trabajo (actionlint + zizmor); los analizadores ausentes o defectuosos, los informes no válidos o la ausencia de una línea base del ratchet producen un fallo INCOMPLETE. Los hallazgos válidos siguen la política estricta, consultiva o de ratchet seleccionada | Ejecución obligatoria; el ratchet de zizmor es bloqueante en CI | -| `check:openapi-breaking` | Cambios incompatibles en el contrato de la API pública (`openapi.yaml`) con respecto a la rama base (oasdiff): genera `openapiBreaking=N`; se omite si oasdiff no está disponible o no se puede resolver la especificación base | **Consultivo** | +realmente; los escáneres externos se instalan mediante `gh release download` y se omiten por sí mismos (salida 0) +cuando aún no está disponible un binario. + +| Script | Valida | Bloqueante | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | +| `check:circular-deps` | No hay dependencias circulares (dpdm) | **Consultivo** | +| `check:bundle-size` | El tamaño del paquete no supera el límite | **Consultivo** | +| `check:secrets` | Análisis de secretos (gitleaks): se omite si el binario no está disponible | **Consultivo** | +| `check:vuln-ratchet` | Las vulnerabilidades de las dependencias (osv-scanner) no empeoran: se omite si el binario no está disponible | **Consultivo** | +| `check:workflows` | Lint de workflows (actionlint + zizmor); los escáneres ausentes o defectuosos, los informes no válidos o la ausencia de una línea base del ratchet provocan un fallo con estado INCOMPLETE. Los hallazgos válidos siguen la política estricta, consultiva o de ratchet seleccionada | Ejecución obligatoria; ratchet de zizmor bloqueante en CI | +| `check:openapi-breaking` | Cambios incompatibles en el contrato de la API pública (`openapi.yaml`) respecto a la rama base (oasdiff): genera `openapiBreaking=N`; se omite si oasdiff no está disponible o no se puede resolver la especificación base | **Consultivo** | ### Trabajo: `docs-sync-strict` -Se ejecuta en cada PR hacia `main`. Bloquea la fusión en caso de fallo. - -| Script | Valida | Bloqueante | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `check:docs-all` | Puerta de control general que ejecuta secuencialmente las 6 puertas secundarias indicadas a continuación | Sí | -| ↳ `check:docs-sync` | Coherencia de versiones entre CHANGELOG / OpenAPI / llm.txt | Sí | -| ↳ `check:docs-counts` | Los recuentos indicados en el texto (número de proveedores, número de migraciones, etc.) están dentro del intervalo de ajuste de los recuentos reales | Sí | -| ↳ `check:env-doc-sync` | Cada variable de entorno de `.env.example` está documentada en una tabla de la documentación y viceversa | Sí | -| ↳ `check:deprecated-versions` | No hay cadenas de versiones obsoletas en la documentación | Sí | -| ↳ `check:doc-links` | Los enlaces internos de Markdown de la documentación apuntan a archivos reales (formato `[texto]`/`(ruta)`) | Sí | -| ↳ `check:fabricated-docs` | Las rutas, variables de entorno, órdenes de CLI, nombres de hooks y rutas de archivos citados en la documentación existen en el código fuente. Puerta estricta mediante `--strict`; fallo no bloqueante sin la opción. | Sí (mediante `--strict` en CI) | -| `check:cli-i18n` | Las cadenas de las órdenes de CLI están presentes en todos los archivos de configuración regional de i18n | Sí | -| `check:openapi-coverage` | La especificación OpenAPI cubre al menos un mínimo ajustado de rutas reales | Sí | -| `check:openapi-security-tiers` | Las anotaciones de niveles de seguridad de `openapi.yaml` son coherentes con las clasificaciones de `routeGuard.ts` | **Informativo** | -| `check:openapi-routes` | Cada ruta de `openapi.yaml` corresponde a un archivo `route.ts` real (prevención de alucinaciones) | Sí | -| `check:docs-symbols` | Cada referencia a `/api/...` en `docs/**/*.md` corresponde a un archivo `route.ts` real (prevención de alucinaciones) | Sí | -| `i18n translation drift` | Claves sin traducir en los archivos de configuración regional de i18n; solo se emite una advertencia | **Informativo** | +Se ejecuta en cada PR hacia `main`. Bloquea la fusión si se produce un error. + +| Script | Valida | Bloqueante | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `check:docs-all` | Metapuerta que ejecuta secuencialmente las 6 subpuertas siguientes | Sí | +| ↳ `check:docs-sync` | Coherencia de versiones entre CHANGELOG / OpenAPI / llm.txt | Sí | +| ↳ `check:docs-counts` | Los recuentos mencionados en el texto (cantidad de proveedores, migraciones, etc.) están dentro del intervalo de ajuste respecto a los recuentos reales | Sí | +| ↳ `check:env-doc-sync` | Todas las variables de entorno de `.env.example` están documentadas en una tabla de la documentación y viceversa | Sí | +| ↳ `check:deprecated-versions` | No hay cadenas de versiones obsoletas en la documentación | Sí | +| ↳ `check:doc-links` | Los enlaces internos de Markdown en la documentación apuntan a archivos reales (formato `[texto]`/`(ruta)`) | Sí | +| ↳ `check:fabricated-docs` | Las rutas, variables de entorno, órdenes de CLI, nombres de hooks y rutas de archivo citados en la documentación existen en el código. Puerta estricta mediante `--strict`; fallo no bloqueante sin la opción. | Sí (mediante `--strict` en CI) | +| `check:cli-i18n` | Las cadenas de órdenes de CLI están presentes en todos los archivos de configuración regional de i18n | Sí | +| `check:openapi-coverage` | La especificación OpenAPI cubre al menos un mínimo ajustado de rutas reales | Sí | +| `check:openapi-security-tiers` | Las anotaciones de niveles de seguridad en `openapi.yaml` son coherentes con las clasificaciones de `routeGuard.ts` | **Consultivo** | +| `check:openapi-routes` | Cada ruta de `openapi.yaml` corresponde a un `route.ts` real (prevención de alucinaciones) | Sí | +| `check:docs-symbols` | Cada referencia a `/api/...` en `docs/**/*.md` corresponde a un `route.ts` real (prevención de alucinaciones) | Sí | +| `i18n translation drift` | Claves sin traducir en los archivos de configuración regional de i18n; solo advertencia | **Consultivo** | ### Trabajo: `i18n-ui-coverage` -| Script | Valida | Bloqueante | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (inline) | La cobertura de claves de i18n de la interfaz de usuario es ≥ 65 % | Sí | -| `check-ui-value-drift` (inline) | Un **valor** en inglés reescrito no deja ninguna traducción obsoleta | Sí | -| `check-new-key-coverage` (inline) | Una clave en inglés **nueva** está traducida en todas las configuraciones regionales; se rechaza un marcador `__MISSING__:` | Sí | -| `check-translation-ratio` | La proporción de traducciones reales por configuración regional (valores idénticos al inglés, marcadores de posición o ausencias fuera de la lista permitida) no debe superar `config/quality/i18n-translation-baseline.json` + margen | **Informativo** | +| Script | Valida | Bloqueante | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (inline) | La cobertura de claves de i18n de la interfaz de usuario es ≥ 65 % | Sí | +| `check-ui-value-drift` (inline) | Un **valor** en inglés reescrito no deja ninguna traducción obsoleta | Sí | +| `check-new-key-coverage` (inline) | Una **nueva** clave en inglés está traducida en todas las configuraciones regionales; se rechaza un marcador `__MISSING__:` | Sí | +| `check-translation-ratio` | La proporción de traducciones reales por configuración regional (hojas idénticas al inglés, marcadores de posición o ausentes fuera de la lista permitida) no debe superar `config/quality/i18n-translation-baseline.json` + margen | **Consultivo** | -Requiere `fetch-depth: 0`: la puerta de control de valores obsoletos compara `en.json` con la base de fusión. +Requiere `fetch-depth: 0`: la puerta de divergencia de valores compara `en.json` con la base de fusión. -#### `check-ui-value-drift` — puerta de control de traducciones obsoletas +#### `check-ui-value-drift` — puerta de traducciones obsoletas -Detecta la única regresión de i18n que las demás puertas no pueden detectar estructuralmente: se reescribe un valor en inglés y las traducciones derivadas del inglés _anterior_ permanecen, por lo que los usuarios que no usan inglés siguen leyendo un texto formulado con seguridad, pero ahora incorrecto. +Detecta la única regresión de i18n que las otras puertas no pueden detectar estructuralmente: se reescribe un valor en inglés y las traducciones derivadas del inglés _anterior_ permanecen, por lo que los usuarios que no usan inglés siguen leyendo un texto redactado con seguridad, pero ahora incorrecto. -Esto llegó a producción. `oauthModal.googleOAuthWarning` se reescribió cuando se incorporó el asistente de inicio de sesión de Antigravity (#5203); **39 de 43 configuraciones regionales** conservaron el texto que indicaba a los operadores que debían «copiar la URL completa y pegarla abajo», un flujo que no puede completarse para ese proveedor. Pasó inadvertido hasta #8463 porque: +Esto llegó realmente a producción. `oauthModal.googleOAuthWarning` se reescribió cuando se incorporó el asistente de inicio de sesión de Antigravity (#5203); **39 de 43 configuraciones regionales** conservaron un texto que indicaba a los operadores que debían «copiar la URL completa y pegarla a continuación», un flujo que no puede completarse con ese proveedor. Pasó inadvertido hasta #8463 porque: -- `sync-ui-keys` solo rellena las claves que están **ausentes**, nunca las que están **obsoletas**; -- `check-ui-keys-coverage` contabiliza la _presencia_ de claves, por lo que una traducción obsoleta cuenta como cubierta; -- `check-translation-drift` supervisa las réplicas de documentación `docs/i18n//**.md`, pero nunca lee `src/i18n/messages/*.json`. Es bloqueante en el trabajo `docs-sync-strict` desde la resincronización de 2026-09: editar un documento principal → `npm run i18n:run -- --files=` (por secciones y de bajo coste). +- `sync-ui-keys` solo completa claves que están **ausentes**, nunca las que están **obsoletas**; +- `check-ui-keys-coverage` cuenta la _presencia_ de claves, por lo que una traducción obsoleta se considera cubierta; +- `check-translation-drift` realiza el seguimiento de las réplicas de documentación `docs/i18n//**.md` — + nunca lee `src/i18n/messages/*.json`. Es bloqueante en el trabajo `docs-sync-strict` desde la + resincronización de 2026-09: edite un documento principal → `npm run i18n:run -- --files=` (por sección, económico). -**Consciente del diff, no respaldado por una línea base.** Compara `en.json` en la base de fusión con el -árbol de trabajo; por cada clave cuyo valor en inglés haya cambiado, cualquier configuración regional que aún contenga una -traducción sin modificar está desactualizada. Esto **congela deliberadamente la deuda preexistente**: un diff -no puede revelar de qué versión anterior en inglés provino una traducción existente desde hace tiempo, por lo que la validación evalúa -únicamente lo que afecta el cambio actual. La alternativa (una línea base de hashes por clave) supondría -un archivo generado de ~600 KB, 3 veces mayor que la línea base más grande existente, que cambiaría con cada PR de i18n. +**Consciente de las diferencias, sin respaldo de línea base.** Compara `en.json` en la base de fusión con el +árbol de trabajo; para cada clave cuyo valor en inglés haya cambiado, cualquier configuración regional que aún conserve una +traducción intacta queda obsoleta. Esto **congela deliberadamente la deuda preexistente**: una diferencia +no puede revelar de qué versión anterior del texto en inglés provino una traducción antigua, por lo que la validación evalúa +únicamente lo que modifica el cambio actual. La alternativa (una línea base de hashes por clave) supondría +un archivo generado de ~600 KB, 3 veces mayor que la línea base existente más grande, que cambiaría con cada PR de i18n. -Hay dos formas de satisfacerla: +Hay dos formas de cumplir la validación: 1. actualizar las traducciones afectadas, o -2. establecerlas como `__MISSING__:`; en ese caso, durante la ejecución se proporciona el texto corregido en inglés +2. establecerlas como `__MISSING__:`; en ese caso, el entorno de ejecución sirve el texto en inglés corregido (`src/i18n/request.ts::deepMergeFallback`, #7258) y la clave queda en cola para su traducción. Si cambió el **significado** de la cadena, es preferible **cambiar el nombre de la clave**: una clave nueva no puede heredar -una traducción desactualizada. Ese es el patrón utilizado en #8463. +una traducción obsoleta. Ese es el patrón que utilizó #8463. ```bash npm run i18n:check-value-drift # estricto (lo que ejecuta CI) -npm run i18n:check-value-drift:warn # solo genera un informe +npm run i18n:check-value-drift:warn # solo informar BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Finaliza con el código 0 y `SKIP reason=base-unresolved` cuando no se puede leer el catálogo base (un clon -superficial sin la referencia base), replicando el comportamiento de `check-openapi-breaking`. +Finaliza con código 0 y `SKIP reason=base-unresolved` cuando no se puede leer el catálogo base (un clon +superficial sin la referencia base), de forma análoga a `check-openapi-breaking`. ### Trabajo: `i18n` -Matriz completa de validación de i18n (un trabajo por configuración regional). El trabajo completo es informativo. +Matriz completa de validación de i18n (un trabajo por configuración regional). Todo el trabajo es informativo. -| Script | Valida | Bloqueo | +| Script | Valida | Bloqueante | | ------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------- | | `validate_translation.py quick` | Integridad de la traducción por configuración regional | **Informativo** (`continue-on-error: true` en todo el trabajo) | @@ -247,33 +241,33 @@ Matriz completa de validación de i18n (un trabajo por configuración regional). Se ejecuta únicamente en solicitudes de incorporación de cambios. -| Script | Valida | Bloqueo | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `check:pr-test-policy` | Los PR que cambien código de producción en `src/`, `open-sse/`, `electron/` o `bin/` deben incluir o actualizar pruebas (regla estricta n.º 8) | Sí | -| `check:test-masking` | Los archivos de prueba modificados no reducen el recuento neto de aserciones ni añaden tautologías `assert.ok(true)` | Sí | -| `check:pr-evidence` | El cuerpo del PR cita evidencias de pruebas/VPS para el cambio (mecaniza la regla estricta n.º 18 buscando patrones en el texto del PR; es frágil, véase la lista de tareas pendientes) | Sí | +| Script | Valida | Bloqueante | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check:pr-test-policy` | Los PR que cambian código de producción en `src/`, `open-sse/`, `electron/` o `bin/` deben incluir o actualizar pruebas (Regla estricta n.º 8) | Sí | +| `check:test-masking` | Los archivos de prueba modificados no reducen el número neto de aserciones ni añaden tautologías `assert.ok(true)` | Sí | +| `check:pr-evidence` | El cuerpo del PR cita evidencias de pruebas/VPS para el cambio (automatiza la Regla estricta n.º 18 buscando patrones en el texto del PR; es frágil, consulte el Backlog) | Sí | ### Trabajo: `test-vitest` -Se ejecuta después de `build`. Bloquea la fusión si falla. +Se ejecuta después de `build`. Bloquea la fusión en caso de fallo. -| Suite | Valida | Bloqueo | +| Conjunto | Valida | Bloqueante | | ---------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `test:vitest` | Servidor MCP (110 herramientas), autoCombo y caché; ejecutor vitest | Sí | -| `test:vitest:ui` | Pruebas de componentes de la interfaz; ejecutor vitest | **Bloqueante**: los fallos preexistentes se excluyen explícitamente en `vitest.config.ts`; los fallos nuevos hacen que el trabajo falle | +| `test:vitest:ui` | Pruebas de componentes de la IU; ejecutor vitest | **Bloqueante**: los fallos preexistentes se excluyen explícitamente en `vitest.config.ts`; los fallos nuevos hacen que el trabajo falle | ### Flujos de trabajo nocturnos (programados, informativos) Se ejecutan según una programación cron (y mediante `workflow_dispatch`), nunca en PR. Todos son informativos. -| Flujo de trabajo | Valida | Bloqueo | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | Pruebas basadas en propiedades de fast-check con una semilla aleatoria y un número elevado de ejecuciones | **Informativo** | -| `nightly-resilience` | Control de crecimiento del heap, inyección caótica de fallos y pruebas de carga/prolongadas con k6 | **Informativo** | -| `nightly-llm-security` | Protección contra inyección de promptfoo (modo de bloqueo) y sondas de garak (se omiten sin un secreto del proveedor) | **Informativo** | -| `nightly-schemathesis` | Pruebas difusas del contrato OpenAPI (schemathesis) contra una instancia activa de OmniRoute usando `docs/openapi.yaml`; detectan infracciones de la especificación y errores 500 no gestionados (Fase 8 B.4) | **Informativo** | -| `nightly-mutation` | Puntuación de pruebas de mutación de Stryker sobre la vía rápida de pruebas unitarias; los mutantes supervivientes revelan aserciones débiles | **Informativo** | -| `nightly-compat` | Matriz de compatibilidad del motor Node en los intervalos admitidos de `engines.node` | **Informativo** | +| Flujo de trabajo | Valida | Bloqueante | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `nightly-property` | Pruebas basadas en propiedades de fast-check con una semilla aleatoria y un número elevado de ejecuciones | **Informativo** | +| `nightly-resilience` | Validación del crecimiento del heap, inyección caótica de fallos y pruebas de carga/resistencia con k6 | **Informativo** | +| `nightly-llm-security` | Protección contra inyecciones de promptfoo (modo de bloqueo) y sondeos de garak (se omiten sin un secreto de proveedor) | **Informativo** | +| `nightly-schemathesis` | Pruebas difusas del contrato OpenAPI (schemathesis) contra una instancia activa de OmniRoute mediante `docs/openapi.yaml`; detectan infracciones de la especificación y errores 500 no gestionados (Fase 8 B.4) | **Informativo** | +| `nightly-mutation` | Puntuación de pruebas de mutación de Stryker sobre la vía rápida de pruebas unitarias; los mutantes supervivientes revelan aserciones débiles | **Informativo** | +| `nightly-compat` | Matriz de compatibilidad del motor Node para los intervalos admitidos de `engines.node` | **Informativo** | --- @@ -543,38 +537,38 @@ Este inventario se concilió con `ci.yml` el 2026-06-17 (la versión anterior om `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Una revisión de ROI del conjunto conciliado -identificó los siguientes candidatos para racionalización. **Las fusiones son cambios -mecánicos de CI; las activaciones/eliminaciones son decisiones de política reservadas -para el operador.** Nada de lo indicado a continuación se ha aplicado todavía. +identificó los siguientes candidatos de racionalización. **Las fusiones son cambios +mecánicos de CI; las activaciones/eliminaciones son decisiones de política reservadas al +operador.** Aún no se ha aplicado nada de lo indicado a continuación. -**También sin documentar anteriormente** (consultivo, señal débil): el trabajo `docs-lint` -(markdownlint + Vale, todo el trabajo con `continue-on-error`) y los flujos de trabajo -independientes de análisis `semgrep.yml` / `codeql.yml` / `scorecard.yml`. -`semgrepFindings: 0` está en `quality-baseline.json`, pero no está conectado a un ratchet -bloqueante en `ci.yml`; actualmente la métrica está huérfana. +**También sin documentar anteriormente** (consultivo, señal baja): el job `docs-lint` +(markdownlint + Vale, todo el job con `continue-on-error`) y los workflows independientes +de análisis `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` está en +`quality-baseline.json`, pero no está conectado a un ratchet bloqueante en `ci.yml`: la +métrica está actualmente huérfana. -### Fusión / deduplicación (mecánico, menor riesgo) +### Fusión / deduplicación (mecánica, menor riesgo) -Cada candidato se validó respecto al estado real de los controles el 2026-06-17 +Cada candidato se validó con el estado real de los gates el 2026-06-17 (confiar, pero verificar); varias fusiones «obvias» resultaron ocultar deuda y **no** son -sustituciones directas sin complicaciones. +sustituciones directas sin riesgos. -- **`check:docs-sync` se ejecuta dos veces** — de forma independiente en el trabajo `lint` y de nuevo dentro de `check:docs-all` (`docs-sync-strict`) y del hook de pre-commit de husky. ✅ **HECHO** — se eliminó la invocación independiente de `lint`. -- **Análisis de CVE** — ❌ **NO es una fusión limpia.** `audit:deps` falla de forma estricta ante cualquier CVE alta/crítica; `check:vuln-ratchet` (osv) solo falla ante una _regresión_ respecto a la línea base (actualmente 1 MODERATE). Semánticas diferentes: eliminar `audit:deps` supondría perder el control absoluto de vulnerabilidades altas/críticas. Mantener ambos. -- **Detección de ciclos** — ❌ **NO es una fusión limpia.** `check:circular-deps` (dpdm) informa de **91 ciclos** (por eso es consultivo); no puede convertirse en bloqueante sin resolverlos primero y tiene un alcance más amplio que el control verde y seleccionado `check:cycles`. Mantener `check:cycles` como bloqueante; resolver los 91 ciclos de dpdm constituye su propio backlog. -- **Complejidad** — ✅ **HECHO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): un recorrido de ESLint, con recuentos por ruleId para que las líneas base de complejidad ciclomática+máximo de líneas y complejidad cognitiva sigan siendo independientes; los controles individuales `check:complexity` / `check:cognitive-complexity` se mantienen para ejecutar `--update` localmente. -- **Antialucinación de `/api`** — ✅ **HECHO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): un inventario del sistema de archivos de `src/app/api`; openapi-routes + docs-symbols siguen informando de forma independiente; los controles individuales se mantienen para las ejecuciones locales. -- **`check:node-runtime` se ejecuta en 11 trabajos** — ⚠️ **ROI bajo.** Cada uno utiliza un ejecutor independiente y la comprobación tarda <1 s; el ahorro total sería de ~10 s, a cambio de perder una protección económica por trabajo. No merece la agitación. -- **`typecheck:noimplicit:core` en el lint de CI** — ✅ **eliminado del trabajo lint** (era consultivo con `continue-on-error`); la superficie de tipos bloqueante es `typecheck:core` + `check:type-coverage`. Se conserva el script local. +- **`check:docs-sync` se ejecuta dos veces** — de forma independiente en el job `lint` y nuevamente dentro de `check:docs-all` (`docs-sync-strict`) y del hook de pre-commit de husky. ✅ **HECHO** — se eliminó la invocación independiente de `lint`. +- **Análisis de CVE** — ❌ **NO es una fusión limpia.** `audit:deps` falla de forma bloqueante ante cualquier CVE de gravedad alta/crítica; `check:vuln-ratchet` (osv) solo falla ante una _regresión_ respecto a la línea base (actualmente 1 MODERATE). Semánticas diferentes: eliminar `audit:deps` supondría perder el gate absoluto para vulnerabilidades altas/críticas. Mantener ambos. +- **Detección de ciclos** — ✅ **HECHO** (#15159 G-01/G-02). El texto anterior llamaba a `check:cycles` el gate «verde y curado» y justificaba mantenerlo como bloqueante porque `check:circular-deps` (dpdm) notificaba 91 ciclos. Ese verde era un **falso verde**: `check:cycles` analizaba 5 subdirectorios (450 archivos), solo coincidía con `import|export … from` estáticos y descartaba todos los especificadores `@/` y `@omniroute/open-sse/`, por lo que no podía detectar los ciclos de importaciones dinámicas + alias que predominaban en el repositorio. Corregido: ahora el gate recorre `src` + `open-sse` (5023 archivos), recopila especificadores desde el AST de TypeScript (por lo que `import("…")` cuenta y `typeof import("…")` en posición de tipo no) y resuelve `paths` de tsconfig. Encuentra **14** ciclos, no 0. Como no es posible corregir 14 ciclos preexistentes en un PR del gate, `check:cycles` es ahora un **ratchet** (`--ratchet`, límite máximo `metrics.cycles.value = 14` en `quality-baseline.json`, `direction: down`): bloquea cualquier _regresión_ y el recuento solo puede disminuir. CI ejecuta `npm run check:cycles:ratchet`. La reducción progresiva se gestiona con **A-01**. `check:circular-deps` (dpdm) se mantiene como una segunda opinión consultiva más amplia. +- **Complejidad** — ✅ **HECHO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): un único recorrido de ESLint, con recuentos por ruleId para que las líneas base de complejidad ciclomática+max-lines y cognitiva se mantengan independientes; `check:complexity` / `check:cognitive-complexity` individuales se conservan para el uso local de `--update`. +- **Antialucinación de `/api`** — ✅ **HECHO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): un único inventario del sistema de archivos de `src/app/api`; openapi-routes + docs-symbols siguen generando informes independientes; los comandos individuales se conservan para ejecuciones locales. +- **`check:node-runtime` se ejecuta en 11 jobs** — ⚠️ **ROI bajo.** Cada uno usa un runner independiente y la comprobación tarda <1 s; el ahorro total sería de ~10 s, a costa de perder una protección económica por job. No merece la inestabilidad del cambio. +- **`typecheck:noimplicit:core` en el lint de CI** — ✅ **eliminado del job lint** (era consultivo con `continue-on-error`); la superficie de tipos bloqueante es `typecheck:core` + `check:type-coverage`. Se conserva el script local. ### Activar / decidir (política del operador) -- `check:openapi-security-tiers` (consultivo) — ❌ **NO se puede activar limpiamente.** Termina con 0, pero advierte que varias rutas de `traffic-inspector` bajo `LOCAL_ONLY_API_PREFIXES` carecen de la anotación `x-loopback-only: true`. Aplicarlo de forma obligatoria requiere añadir primero esas anotaciones a `openapi.yaml`. -- `typecheck:noimplicit:core` (consultivo) — sustituido en gran medida por el ratchet bloqueante `check:type-coverage`. Convertirlo en ratchet o eliminar la segunda pasada redundante de `tsc`. -- `test:vitest:ui` (ahora **bloqueante**) — los fallos preexistentes están excluidos explícitamente en `vitest.config.ts` mediante comentarios de seguimiento `// #8618`; los fallos nuevos hacen que el trabajo falle. -- `check:secrets` (gitleaks, ratchet bloqueante fijado en 3 falsos positivos documentados) — añadir los 3 a la lista de permitidos para llegar a 0, o degradarlo a consultivo. Se solapa con el análisis de secretos nativo de GitHub + `check:public-creds`. -- `check:pr-evidence` (bloqueante, busca patrones en el texto del cuerpo del PR) — alto riesgo de falsos positivos; eliminarlo debilita la aplicación de la Regla estricta n.º 18, por lo que se trata de una auténtica decisión de política. -- `semgrep` (independiente y consultivo) — se solapa con CodeQL en las familias de OWASP; conectar su línea base a un ratchet o eliminarlo. +- `check:openapi-security-tiers` (consultivo) — ❌ **NO se puede convertir limpiamente en bloqueante.** Finaliza con 0, pero advierte que varias rutas de `traffic-inspector` bajo `LOCAL_ONLY_API_PREFIXES` carecen de la anotación `x-loopback-only: true`. Para aplicarlo de forma obligatoria, primero es necesario añadir esas anotaciones a `openapi.yaml`. +- `typecheck:noimplicit:core` (consultivo) — queda ampliamente cubierto por el ratchet bloqueante `check:type-coverage`. Convertirlo en ratchet o eliminar la segunda pasada redundante de `tsc`. +- `test:vitest:ui` (ahora **bloqueante**) — los fallos preexistentes están excluidos explícitamente en `vitest.config.ts` con comentarios de seguimiento `// #8618`; los fallos nuevos hacen que el job falle. +- `check:secrets` (gitleaks, ratchet bloqueante fijado en 3 falsos positivos documentados) — incluir los 3 en la lista de permitidos para llegar a 0, o degradarlo a consultivo. Se solapa con el análisis nativo de secretos de GitHub + `check:public-creds`. +- `check:pr-evidence` (bloqueante, busca patrones en la prosa del cuerpo del PR) — alto riesgo de falsos positivos; eliminarlo debilitaría la aplicación de la Hard Rule #18, por lo que se trata de una auténtica decisión de política. +- `semgrep` (independiente y consultivo) — se solapa con CodeQL para las familias OWASP; conectar su línea base a un ratchet o eliminarlo. --- diff --git a/docs/i18n/es/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/es/docs/compression/COMPRESSION_GUIDE.md index d572a57fc02f..45b51bd6e9fe 100644 --- a/docs/i18n/es/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/es/docs/compression/COMPRESSION_GUIDE.md @@ -461,7 +461,7 @@ Cada estilo ofrece tres niveles de intensidad — `lite`, `full`, `ultra` — y `applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`) resuelve la selección contra el catálogo (los IDs desconocidos y los estilos que no coinciden con la configuración regional se descartan, nunca es un error), concatena las instrucciones seleccionadas en el orden del catálogo, añade la cláusula de límites **una vez**, y comienza el bloque con un único marcador de idempotencia (`[OmniRoute Output Styles]`), de modo que volver a aplicarlo no tiene efecto. Cuando el idioma resuelto (ver Selección de idioma a continuación) tiene una traducción, se inyecta la instrucción localizada en lugar del inglés. -En un cuerpo con `messages`, un bypass de contenido (`shouldBypassCavemanOutputMode()` en `open-sse/services/compression/outputMode.ts`) verifica los últimos tres mensajes y omite los estilos para todo el turno cuando coinciden con sus palabras clave de seguridad, acción irreversible, clarificación o sensibles al orden. El bypass se ejecuta independientemente de la configuración del interruptor **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) del panel. +En un cuerpo con `messages`, un bypass de contenido (`shouldBypassCavemanOutputMode()` en `open-sse/services/compression/outputMode.ts`) verifica los últimos tres mensajes y omite los estilos para todo el turno cuando coinciden con sus palabras clave de seguridad, acción irreversible, clarificación o sensibles al orden. El bypass se ejecuta mientras el interruptor **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) del panel esté activado, que es el valor predeterminado; con el interruptor desactivado, los estilos seleccionados también se aplican en esos turnos. Cuando el bypass permite el paso del turno, `placeSystemInstruction()` (mismo archivo), que nunca crea un nuevo `messages[0]`, coloca el bloque en el primero de estos que encuentra: diff --git a/docs/i18n/es/docs/reference/ENVIRONMENT.md b/docs/i18n/es/docs/reference/ENVIRONMENT.md index b418dd6e5265..05d4acd3afe0 100644 --- a/docs/i18n/es/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/es/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Secretos obligatorios -Estos valores **deben** configurarse antes de la primera ejecución. Sin ellos, la aplicación se negará a iniciarse o funcionará con valores predeterminados inseguros. - -| Variable | Obligatoria | Valor predeterminado | Archivo de origen | Descripción | -| ---------------------------- | ------------------- | ------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Sí** | _(ninguno)_ | `src/lib/auth` | Firma y verifica todas las cookies de sesión del panel (JWT). Genérelo con `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Sí** | _(ninguno)_ | `src/lib/db/apiKeys.ts` | Clave de cifrado AES para los valores de claves de API almacenados en SQLite. Genérela con `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Sí** | `CHANGEME` | Script de inicialización | Establece la contraseña inicial de administración del panel (coincide con el valor predeterminado de `.env.example`; se mantiene deliberadamente insegura para obligar a cambiarla). **Cámbiela antes del primer uso.** Después de iniciar sesión, cámbiela mediante Panel → Configuración → Seguridad. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sí** (producción) | _(sin configurar)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secreto compartido para el puente WebSocket interno de Codex Responses. Autentica las solicitudes del puente entre el relé WS de Electron/navegador y OmniRoute. ⚠️ **OBLIGATORIO en producción: cuando no está configurado, se rechazan todas las solicitudes del puente WS.** Genérelo con `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | No | _(SHA de git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identificador explícito para invalidar la caché del service worker del shell de la PWA (#11779); es el primero en la cadena de resolución. | -| `SOURCE_VERSION` | No | _(sin configurar)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Segundo en la cadena; lo establecen los sistemas de compilación PaaS (p. ej., al estilo de Heroku) como el commit desplegado. | -| `NEXT_PUBLIC_SW_BUILD_ID` | No | _(derivado)_ | `src/shared/components/PwaRegister.tsx` | Valor público en tiempo de compilación que el cliente utiliza para registrar `/sw.js?v=…`; se deriva de los dos anteriores y, después, del SHA de git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | No (automático) | _(automático en cada arranque)_ | `src/server/authz/policies/management.ts` | Secreto por proceso que demuestra que la marca de IP del par de confianza procede del propio servidor HTTP de OmniRoute (`scripts/dev/peer-stamp.mjs`). El middleware de autorización solo confía en la localidad de la solicitud (restricción por loopback/LAN de las rutas LOCAL_ONLY) cuando la marca contiene este token. Se genera automáticamente en cada arranque; déjelo sin configurar y fíjelo únicamente en configuraciones multiproceso que deban compartir la marca. | -| `OMNIROUTE_TRUSTED_PROXIES` | No | _(sin configurar)_ | `scripts/dev/peer-stamp.mjs` | Direcciones IP o rangos CIDR, separados por comas, de proxies inversos cuyos valores `X-Forwarded-For` / `X-Real-IP` puede aceptar la lista de permitidos/denegados de IP, además de las direcciones de loopback, las direcciones de redes privadas y los nodos perimetrales de Cloudflare, que siempre son de confianza. Solo es necesario para un proxy situado en cualquier otra dirección pública; de lo contrario, el filtro evalúa la dirección del propio proxy. | +Los secretos sin configurar marcados como **Sí** se generan automáticamente en la primera ejecución (`JWT_SECRET`, `API_KEY_SECRET`) y se guardan en `server.env`, en el directorio de datos — establézcalos explícitamente cuando ese directorio sea efímero. La excepción es `OMNIROUTE_WS_BRIDGE_SECRET`: no se guarda en `server.env` — los puntos de entrada del servidor generan un valor efímero por arranque que solo puede usar el puente interno del proceso, por lo que los clientes externos del puente deben configurarlo explícitamente. + +| Variable | Obligatoria | Valor predeterminado | Archivo de origen | Descripción | +| ---------------------------- | ------------------- | ------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Sí** | _(ninguno)_ | `src/lib/auth` | Firma y verifica todas las cookies de sesión del panel (JWT). Genérelo con `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Sí** | _(ninguno)_ | `src/lib/db/apiKeys.ts` | Clave de cifrado AES para los valores de claves de API almacenados en SQLite. Genérela con `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | No | _(sin configurar)_ | `src/lib/auth/managementPassword.ts` | Establece la contraseña del panel al arrancar mientras no haya ninguna guardada; se ignora una vez que existe una contraseña guardada. Las instalaciones por npm y desde el código fuente copian `.env.example` a `.env`, por lo que arrancan con su marcador públicamente conocido `CHANGEME`: sustitúyalo antes del primer arranque, o defina `INITIAL_PASSWORD=` (vacío) en ese `.env` para crear la contraseña en el asistente de incorporación en la primera visita, igual que hace un valor sin definir (el predeterminado de la imagen Docker). Cambie una contraseña guardada en Panel → Configuración → Seguridad, o ejecute `omniroute reset-password` (`node bin/reset-password.mjs` en un checkout del código fuente) con `DATA_DIR` apuntando al directorio de datos del servidor. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sí** (producción) | _(sin configurar)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secreto compartido para el puente WebSocket interno de Codex Responses. Autentica las solicitudes del puente entre el relé WS de Electron/navegador y OmniRoute. ⚠️ **OBLIGATORIO en producción: cuando no está configurado, se rechazan todas las solicitudes del puente WS.** Genérelo con `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | No | _(SHA de git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identificador explícito para invalidar la caché del service worker del shell de la PWA (#11779); es el primero en la cadena de resolución. | +| `SOURCE_VERSION` | No | _(sin configurar)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Segundo en la cadena; lo establecen los sistemas de compilación PaaS (p. ej., al estilo de Heroku) como el commit desplegado. | +| `NEXT_PUBLIC_SW_BUILD_ID` | No | _(derivado)_ | `src/shared/components/PwaRegister.tsx` | Valor público en tiempo de compilación que el cliente utiliza para registrar `/sw.js?v=…`; se deriva de los dos anteriores y, después, del SHA de git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | No (automático) | _(automático en cada arranque)_ | `src/server/authz/policies/management.ts` | Secreto por proceso que demuestra que la marca de IP del par de confianza procede del propio servidor HTTP de OmniRoute (`scripts/dev/peer-stamp.mjs`). El middleware de autorización solo confía en la localidad de la solicitud (restricción por loopback/LAN de las rutas LOCAL_ONLY) cuando la marca contiene este token. Se genera automáticamente en cada arranque; déjelo sin configurar y fíjelo únicamente en configuraciones multiproceso que deban compartir la marca. | +| `OMNIROUTE_TRUSTED_PROXIES` | No | _(sin configurar)_ | `scripts/dev/peer-stamp.mjs` | Direcciones IP o rangos CIDR, separados por comas, de proxies inversos cuyos valores `X-Forwarded-For` / `X-Real-IP` puede aceptar la lista de permitidos/denegados de IP, además de las direcciones de loopback, las direcciones de redes privadas y los nodos perimetrales de Cloudflare, que siempre son de confianza. Solo es necesario para un proxy situado en cualquier otra dirección pública; de lo contrario, el filtro evalúa la dirección del propio proxy. | ### Comandos de generación @@ -216,38 +216,38 @@ OmniRoute utiliza **SQLite** (mediante `better-sqlite3`) para toda la persistenc ## 4. Seguridad y autenticación -| Variable | Valor predeterminado | Archivo de origen | Descripción | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sal combinada con identificadores de hardware para generar la huella digital de la máquina. Cámbiela en cada despliegue para garantizar el aislamiento. | -| `OMNIROUTE_CLI_SALT` | _(sin establecer = sal aleatoria por instalación, almacenada de forma persistente en `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sal HMAC para derivar el token de autenticación de la CLI local. Establecer este valor rota todos los tokens de la CLI en la máquina y siempre tiene prioridad sobre la sal almacenada. Consulte `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Establece el atributo `Secure` en las cookies de sesión. **Debe ser `true`** cuando se ejecute detrás de HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware de la API | Cuando es `true`, todas las solicitudes del proxy a `/v1/*` deben incluir una clave de API válida. **Este indicador no controla `GET /v1/models`**, que sigue la configuración de inicio de sesión del panel (`requireAuthForModels`); por lo tanto, un `401` de `/v1/models` NO significa que la inferencia esté protegida. Consulte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite revelar los valores completos de las claves de API en la interfaz del panel. Se puede configurar desde los indicadores de funcionalidades del panel; supone un riesgo de seguridad en instancias compartidas. | -| `NO_LOG_API_KEY_IDS` | _(vacío)_ | `src/lib/compliance/index.ts` | IDs de claves de API separados por comas que omiten el registro de solicitudes (cumplimiento del RGPD). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(sin establecer = ilimitado)_ | `src/shared/utils/apiKeyPolicy.ts` | Presupuesto alternativo de solicitudes por día aplicado a las claves de API cuya columna `rate_limits` es nula. Sin establecer o vacío: sin límite implícito (#2289, #11017). `0` equivale a ilimitado. Un entero positivo N habilita N/día, 5N/semana y 20N/mes. Los valores no vacíos con formato incorrecto recurren a los límites heredados de 1000/día, 5000/semana y 20000/mes. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Tamaño máximo permitido del cuerpo de la solicitud. Rechaza las cargas útiles que superen este límite. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Los cuerpos reales de las solicitudes que alcancen o superen este umbral adquieren, antes del análisis de JSON, la concesión atómica local al proceso para operaciones pesadas (ruta BYTE, incluido `POST /v1/responses`). Usa la misma vía de escape por margen saludable de [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) que las solicitudes estructuralmente pesadas; sigue estando limitada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), para que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) no vuelva a producirse. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Límite estricto de las rutas de chat aplicado a los bytes leídos durante la ingesta acotada, incluidas las solicitudes con `Content-Length` ausente, no válido o falseado; el exceso recibe `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(sin establecer — sin límite por número de solicitudes)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** este límite heredado de NÚMERO de solicitudes ahora solo se aplica cuando se establece explícitamente. Si se deja sin establecer (el valor predeterminado), la admisión de solicitudes de chat pesadas queda controlada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`: un presupuesto de BYTES derivado automáticamente y dimensionado según el límite real de memoria del proceso en **un proceso** (un heap de V8). Dos solicitudes `/v1/responses` solapadas de ~750 000 tokens hacen abortar heaps de ~12 GiB (#7849): una advertencia sobre el **presupuesto de memoria**, no un máximo estricto de producto de 2. Un proceso saludable (con el heap por debajo de la proporción de descarte) PUEDE admitir más solicitudes `/v1/responses` largas simultáneas mediante `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Decenas de clientes SSE de larga duración (40–50) dependen del heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, no de un «máximo de 2». Aumentar esto a ciegas para «usar el host» vuelve a introducir #7849. Multiplique los **heaps** con **N `DATA_DIR` independientes** (#11024); nunca use `replicas>1` sobre un único archivo SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivado automáticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** sobrescritura del presupuesto de bytes de ingesta derivado automáticamente (el 25 % del límite más restrictivo de memoria de V8/cgroup, dividido por una amplificación transitoria de 8x). Los valores derivados y explícitos se restringen al intervalo de 8 MiB a 2 GiB. Un cuerpo mayor que el presupuesto efectivo falla inmediatamente con `413 body_exceeds_budget`; la contención entre cuerpos que podrían procesarse individualmente sigue siendo reintentable mediante `503`. La concurrencia de 40–50 clientes SSE de larga duración depende de este presupuesto + el heap, no de un «máximo de 2» estricto. Consulte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` en `/api/monitoring/health` antes de ajustar la configuración. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Proporción de descarte por presión del heap (`heapUsed / heap_size_limit`) para la admisión de solicitudes pesadas por BYTES y ESTRUCTURA (#10183, #10268, #10437). Una solicitud pesada simultánea que supere `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` solo se descarta con el `503` reintentable cuando el heap TAMBIÉN se encuentra en esta proporción o por encima de ella; con un heap saludable, se admite mediante el margen de capacidad saludable. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valor predeterminado `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacidad adicional acotada para la ruta rápida con un heap saludable (#10437), tanto para ESTRUCTURA como para BYTES (`admitChatRequest`, incluidos los cuerpos ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sin este límite, todas las solicitudes con el sistema ocupado pero con un heap saludable eludían la admisión sin tope alguno. Cuando hay tantas concesiones simultáneas activas mediante la ruta de heap saludable, las siguientes solicitudes realizadas mientras el sistema está ocupado pasan a la MISMA ruta de espera acotada/descarte utilizada bajo una presión real del heap. `0` desactiva por completo esta omisión. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Número de mensajes que clasifica una solicitud de chat como pesada, incluso cuando su cuerpo está por debajo del umbral de bytes. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Número de herramientas que clasifica una solicitud de chat como pesada, incluso cuando su cuerpo está por debajo del umbral de bytes. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimación conservadora de tokens basada en el tamaño de las cadenas que clasifica una solicitud como pesada; se trata de una aproximación del coste de admisión, no de la tokenización de facturación del proveedor. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (desactivado) | `src/shared/middleware/chatBodyAdmission.ts` | Límite opcional y voluntario del historial de chat. Está desactivado de forma predeterminada: el número de mensajes es una política de implementación, no una propiedad universal de una solicitud, y establecer aquí un límite rechaza las conversaciones con un `413` definitivo antes de que el proceso de compresión pueda hacerlas procesables. El crecimiento del heap está limitado por `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` y el descarte por presión del heap. Establezca un valor positivo en implementaciones con memoria limitada que necesiten un límite estricto; el exceso recibirá entonces un `413` estructurado que indica que se requiere compactación. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Límite estricto para una respuesta ascendente sin streaming almacenada íntegramente en memoria. Al superarlo, se cancela el lector ascendente y la solicitud falla de inmediato, en lugar de hacer crecer una cadena sin límites hasta agotar el heap. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Máximo de bytes transmitidos que se reenvían desde las cabeceras de respuesta del servidor de origen. Cuando se supera el límite, se descartan las cabeceras de menor prioridad (p. ej., las personalizadas `x-codex-*`, `x-oai-request-id`) para mantenerse dentro de los límites habituales de cabeceras de los proxies inversos. Establézcalo en un valor más alto para reenviar más metadatos del servidor de origen a costa de aumentar el tamaño de las cabeceras de respuesta. | -| `CORS_ORIGIN` | _(sin definir)_ | `src/server/cors/origins.ts` | Lista de orígenes permitidos de CORS heredada para un único origen. Para nuevos despliegues, se recomienda usar `CORS_ALLOWED_ORIGINS`. CORS solo se utiliza para clientes de API de navegador entre orígenes; en su lugar, las operaciones de escritura autenticadas del panel usan solicitudes del mismo origen junto con protección CSRF vinculada a la sesión. | -| `CORS_ALLOWED_ORIGINS` | _(sin definir)_ | `src/server/cors/origins.ts` | Lista de orígenes permitidos de CORS separados por comas. No se envía ningún comodín a menos que se configure explícitamente `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanismo de escape exclusivo para desarrollo que permite reflejar cualquier `Origin` del navegador. No lo habilite en despliegues compartidos ni de producción. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Bloquea las llamadas a proveedores dirigidas a rangos de IP privados, de bucle invertido o locales de enlace. Deshabilítelo únicamente en entornos de prueba aislados. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Permite URLs de proveedores que apunten a redes privadas/locales (localhost, 192.168.x.x, 10.x.x.x, etc.). **REQUERIDO para proveedores autoalojados** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Cuando es `false`, el panel rechaza la validación de URLs locales. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Permite añadir/validar proveedores en direcciones locales/privadas (127.0.0.1, localhost, LAN, rangos privados); se limita a la ruta de validación del proveedor. **Valor predeterminado: `true`** (prioridad local); establézcalo en `false` para imponer un bloqueo estricto que solo permita direcciones públicas. Los endpoints de metadatos de la nube (169.254.169.254, metadata.google.internal) permanecen bloqueados en todos los casos. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permite que las rutas `/v1/audio/*` (transcripciones, voz, traducciones) utilicen un nodo proveedor compatible con OpenAI alojado fuera de localhost. Está desactivado de forma predeterminada: enrutar el audio a un host remoto cambia la identidad de salida y debe ser una decisión explícita del operador. Los nodos de bucle invertido/privados (localhost, 127.0.0.1, 172.16-31.x) siempre están permitidos y no se ven afectados. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permite que `POST /v1/rerank` (y el paso de reclasificación mediante bucle invertido del motor de memoria) utilice un nodo proveedor compatible con OpenAI alojado fuera de localhost: un equipo en la LAN o un par de Tailscale que ejecute TEI, Infinity, vLLM, etc. Está desactivado de forma predeterminada: enrutar a un host remoto cambia la identidad de salida y debe ser una decisión explícita del operador. Los nodos de bucle invertido (localhost, 127.0.0.1, 172.16-31.x) siempre están permitidos y no se ven afectados. Los nodos remotos también deben cumplir la política de URLs salientes del proveedor (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); nunca se enruta a hosts de metadatos de la nube. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Cuando OIDC está habilitado, deshabilita el inicio de sesión mediante contraseña para que los usuarios solo puedan autenticarse mediante el inicio de sesión único de OIDC. También se acepta el alias simple `OIDC_DISABLE_PASSWORD_LOGIN`; la marca de funcionalidad del panel con la misma clave tiene prioridad. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simple de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variable | Valor predeterminado | Archivo de origen | Descripción | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sal combinada con identificadores de hardware para generar la huella digital de la máquina. Cámbiela en cada despliegue para garantizar el aislamiento. | +| `OMNIROUTE_CLI_SALT` | _(sin establecer = sal aleatoria por instalación, almacenada de forma persistente en `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sal HMAC para derivar el token de autenticación de la CLI local. Establecer este valor rota todos los tokens de la CLI en la máquina y siempre tiene prioridad sobre la sal almacenada. Consulte `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Establece el atributo `Secure` en las cookies de sesión. **Debe ser `true`** cuando se ejecute detrás de HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware de la API | Cuando es `true`, todas las solicitudes del proxy a `/v1/*` deben incluir una clave de API válida. **Este indicador no controla `GET /v1/models`**, que sigue la configuración de inicio de sesión del panel (`requireAuthForModels`); por lo tanto, un `401` de `/v1/models` NO significa que la inferencia esté protegida. Consulte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite revelar los valores completos de las claves de API en la interfaz del panel. Se puede configurar desde los indicadores de funcionalidades del panel; supone un riesgo de seguridad en instancias compartidas. | +| `NO_LOG_API_KEY_IDS` | _(vacío)_ | `src/lib/compliance/index.ts` | IDs de claves de API separados por comas que omiten el registro de solicitudes (cumplimiento del RGPD). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(sin establecer = ilimitado)_ | `src/shared/utils/apiKeyPolicy.ts` | Presupuesto alternativo de solicitudes por día aplicado a las claves de API cuya columna `rate_limits` es nula. Sin establecer o vacío: sin límite implícito (#2289, #11017). `0` equivale a ilimitado. Un entero positivo N habilita N/día, 5N/semana y 20N/mes. Los valores no vacíos con formato incorrecto recurren a los límites heredados de 1000/día, 5000/semana y 20000/mes. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Tamaño máximo permitido del cuerpo de la solicitud. Rechaza las cargas útiles que superen este límite. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Los cuerpos reales de las solicitudes que alcancen o superen este umbral adquieren, antes del análisis de JSON, la concesión atómica local al proceso para operaciones pesadas (ruta BYTE, incluido `POST /v1/responses`). Usa la misma vía de escape por margen saludable de [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) que las solicitudes estructuralmente pesadas; sigue estando limitada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), para que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) no vuelva a producirse. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Límite estricto de las rutas de chat aplicado a los bytes leídos durante la ingesta acotada, incluidas las solicitudes con `Content-Length` ausente, no válido o falseado; el exceso recibe `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(sin establecer — sin límite por número de solicitudes)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** este límite heredado de NÚMERO de solicitudes ahora solo se aplica cuando se establece explícitamente. Si se deja sin establecer (el valor predeterminado), la admisión de solicitudes de chat pesadas queda controlada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`: un presupuesto de BYTES derivado automáticamente y dimensionado según el límite real de memoria del proceso en **un proceso** (un heap de V8). Dos solicitudes `/v1/responses` solapadas de ~750 000 tokens hacen abortar heaps de ~12 GiB (#7849): una advertencia sobre el **presupuesto de memoria**, no un máximo estricto de producto de 2. Un proceso saludable (con el heap por debajo de la proporción de descarte) PUEDE admitir más solicitudes `/v1/responses` largas simultáneas mediante `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Decenas de clientes SSE de larga duración (40–50) dependen del heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, no de un «máximo de 2». Aumentar esto a ciegas para «usar el host» vuelve a introducir #7849. Multiplique los **heaps** con **N `DATA_DIR` independientes** (#11024); nunca use `replicas>1` sobre un único archivo SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivado automáticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** sobrescritura del presupuesto de bytes de ingesta derivado automáticamente (el 25 % del límite más restrictivo de memoria de V8/cgroup, dividido por una amplificación transitoria de 8x). Los valores derivados y explícitos se restringen al intervalo de 8 MiB a 2 GiB. Un cuerpo mayor que el presupuesto efectivo falla inmediatamente con `413 body_exceeds_budget`; la contención entre cuerpos que podrían procesarse individualmente sigue siendo reintentable mediante `503`. La concurrencia de 40–50 clientes SSE de larga duración depende de este presupuesto + el heap, no de un «máximo de 2» estricto. Consulte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` en `/api/monitoring/health` antes de ajustar la configuración. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Proporción de descarte por presión del heap (`heapUsed / heap_size_limit`) para la admisión de solicitudes pesadas por BYTES y ESTRUCTURA (#10183, #10268, #10437). Una solicitud pesada simultánea que supere `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` solo se descarta con el `503` reintentable cuando el heap TAMBIÉN se encuentra en esta proporción o por encima de ella; con un heap saludable, se admite mediante el margen de capacidad saludable. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valor predeterminado `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacidad adicional acotada para la ruta rápida con un heap saludable (#10437), tanto para ESTRUCTURA como para BYTES (`admitChatRequest`, incluidos los cuerpos ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sin este límite, todas las solicitudes con el sistema ocupado pero con un heap saludable eludían la admisión sin tope alguno. Cuando hay tantas concesiones simultáneas activas mediante la ruta de heap saludable, las siguientes solicitudes realizadas mientras el sistema está ocupado pasan a la MISMA ruta de espera acotada/descarte utilizada bajo una presión real del heap. `0` desactiva por completo esta omisión. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Número de mensajes que clasifica una solicitud de chat como pesada, incluso cuando su cuerpo está por debajo del umbral de bytes. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Número de herramientas que clasifica una solicitud de chat como pesada, incluso cuando su cuerpo está por debajo del umbral de bytes. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimación conservadora de tokens basada en el tamaño de las cadenas que clasifica una solicitud como pesada; se trata de una aproximación del coste de admisión, no de la tokenización de facturación del proveedor. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (desactivado) | `src/shared/middleware/chatBodyAdmission.ts` | Límite opcional y voluntario del historial de chat. Está desactivado de forma predeterminada: el número de mensajes es una política de implementación, no una propiedad universal de una solicitud, y establecer aquí un límite rechaza las conversaciones con un `413` definitivo antes de que el proceso de compresión pueda hacerlas procesables. El crecimiento del heap está limitado por `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` y el descarte por presión del heap. Establezca un valor positivo en implementaciones con memoria limitada que necesiten un límite estricto; el exceso recibirá entonces un `413` estructurado que indica que se requiere compactación. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Límite estricto para una respuesta ascendente sin streaming almacenada íntegramente en memoria. Al superarlo, se cancela el lector ascendente y la solicitud falla de inmediato, en lugar de hacer crecer una cadena sin límites hasta agotar el heap. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Máximo de bytes transmitidos que se reenvían desde las cabeceras de respuesta del servidor de origen. Cuando se supera el límite, se descartan las cabeceras de menor prioridad (p. ej., las personalizadas `x-codex-*`, `x-oai-request-id`) para mantenerse dentro de los límites habituales de cabeceras de los proxies inversos. Establézcalo en un valor más alto para reenviar más metadatos del servidor de origen a costa de aumentar el tamaño de las cabeceras de respuesta. | +| `CORS_ORIGIN` | _(sin definir)_ | `src/server/cors/origins.ts` | Lista de orígenes permitidos de CORS heredada para un único origen. Para nuevos despliegues, se recomienda usar `CORS_ALLOWED_ORIGINS`. CORS solo se utiliza para clientes de API de navegador entre orígenes; en su lugar, las operaciones de escritura autenticadas del panel usan solicitudes del mismo origen junto con protección CSRF vinculada a la sesión. | +| `CORS_ALLOWED_ORIGINS` | _(sin definir)_ | `src/server/cors/origins.ts` | Lista de orígenes permitidos de CORS separados por comas. No se envía ningún comodín a menos que se configure explícitamente `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanismo de escape exclusivo para desarrollo que permite reflejar cualquier `Origin` del navegador. No lo habilite en despliegues compartidos ni de producción. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias heredado. Su interruptor del panel (una anulación guardada en la base de datos) se lee antes que este valor de entorno; `false`, `0`, `no` u `off` en cualquiera de los dos desactiva las comprobaciones de host igual que lo hace `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, incluso cuando el interruptor del panel de ese indicador está guardado como desactivado; el resto de valores deja la protección en manos de los dos indicadores siguientes. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Desactiva las comprobaciones de host de la protección de URL salientes, incluido el bloqueo de metadatos de la nube (todo 169.254.0.0/16, metadata.google.internal), en las rutas de proveedor que siguen el modo de la protección: validación de URL, descubrimiento de modelos, URL base de nodos de proveedor (incluidos los nodos remotos de reclasificación) y la prueba de proxy de respaldo, entre otras rutas salientes adyacentes al proveedor, como la obtención remota de imágenes/medios. También permite destinos de webhook privados, salvo los endpoints de metadatos de la nube/enlace local, que permanecen bloqueados incluso con este indicador activado. Las solicitudes de chat despachadas a través de `BaseExecutor.execute()` conservan su comprobación de metadatos de la nube. Los proveedores locales funcionan con los valores predeterminados: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (predeterminado `true`) ya permite URLs locales y de LAN en las rutas de validación, descubrimiento y nodos de proveedor; la prueba de proxy de respaldo y los destinos de webhook privados solo consultan este indicador y bloquean los hosts locales/LAN mientras esté desactivado. Prevalece sobre `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Un valor guardado desde el interruptor del panel tiene precedencia sobre esta variable. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Permite URLs de proveedor en direcciones locales/privadas (127.0.0.1, localhost, LAN, rangos privados) para la validación de proveedores, el descubrimiento de modelos, el chat y las demás rutas salientes adyacentes al proveedor que gobierna el modo de la protección. **Valor predeterminado `true`** (prioridad local): la protección bloquea entonces los endpoints de metadatos de la nube — todo 169.254.0.0/16 más los nombres de host de metadatos conocidos (metadata.google.internal, metadata.goog, IMDS de Alibaba/Azure/Oracle, fd00:ec2::254). Establézcalo en `false` para el modo solo público, que también bloquea los hosts privados y de bucle invertido. La protección comprueba el nombre de host o el literal IP tal como están escritos, antes de cualquier resolución DNS; en modo solo público, el envoltorio de fetch seguro además resuelve el nombre de host y rechaza cualquier respuesta privada. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` desactiva estas comprobaciones en las rutas que lista su fila. Los proveedores locales integrados, como LM Studio, Ollama, vLLM, Llamafile y Triton, omiten todas las comprobaciones de host para la validación de claves y el chat. Un valor guardado desde el interruptor del panel tiene precedencia sobre esta variable. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permite que las rutas `/v1/audio/*` (transcripciones, voz, traducciones) utilicen un nodo proveedor compatible con OpenAI alojado fuera de localhost. Está desactivado de forma predeterminada: enrutar el audio a un host remoto cambia la identidad de salida y debe ser una decisión explícita del operador. Los nodos de bucle invertido/privados (localhost, 127.0.0.1, 172.16-31.x) siempre están permitidos y no se ven afectados. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permite que `POST /v1/rerank` (y el paso de reclasificación mediante bucle invertido del motor de memoria) utilice un nodo proveedor compatible con OpenAI alojado fuera de localhost: un equipo en la LAN o un par de Tailscale que ejecute TEI, Infinity, vLLM, etc. Está desactivado de forma predeterminada: enrutar a un host remoto cambia la identidad de salida y debe ser una decisión explícita del operador. Los nodos de bucle invertido (localhost, 127.0.0.1, 172.16-31.x) siempre están permitidos y no se ven afectados. La URL base de un nodo remoto también debe cumplir la política de URLs salientes del proveedor (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Cuando OIDC está habilitado, deshabilita el inicio de sesión mediante contraseña para que los usuarios solo puedan autenticarse mediante el inicio de sesión único de OIDC. También se acepta el alias simple `OIDC_DISABLE_PASSWORD_LOGIN`; la marca de funcionalidad del panel con la misma clave tiene prioridad. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simple de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Lista de comprobación de refuerzo de seguridad diff --git a/docs/i18n/et/CONTRIBUTING.md b/docs/i18n/et/CONTRIBUTING.md index 3ab325bace74..74f3e9bab0cd 100644 --- a/docs/i18n/et/CONTRIBUTING.md +++ b/docs/i18n/et/CONTRIBUTING.md @@ -395,22 +395,22 @@ Kirjuta üksiktestid `tests/unit/` kataloogi, kattes vähemalt: --- -## Pull requesti kontrollnimekiri - -- [ ] Testid läbivad (`npm test`) -- [ ] Linting läbib (`npm run lint`) -- [ ] Ehitamine õnnestub (`npm run build`) -- [ ] TypeScripti tüübid lisatud uutele avalikele funktsioonidele ja liidestele -- [ ] Pole peidetud salajasi väärtusi ega fallback väärtusi -- [ ] Avalikud ülemised volikirjad sisestatud `resolvePublicCred()` kaudu (vaata [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), mitte kunagi sõna-sõnalt -- [ ] Vigavastused suunatakse läbi `buildErrorBody()` / `sanitizeErrorMessage()` — vastuste kehades ei ole toored stack trace'id (vaata [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Käsurea käsud (`exec` / `spawn`) edastavad käitusväärtused `env` kaudu, mitte stringi interpoleerimise teel -- [ ] Kõik sisendid valideeritud skeemidega -- [ ] Muudatuste logi **fragment** lisatud `changelog.d/{features|fixes|maintenance}/-.md` alla kasutajate suunatud muudatuste jaoks (vaata [`changelog.d/README.md`](./changelog.d/README.md)) — **ära** muuda `CHANGELOG.md` otse; fragmendid kogutakse väljaandmise ajal ja ei tekki kunagi konflikte -- [ ] Dokumentatsioon uuendatud (kohaldatavatel juhtudel) -- [ ] Uusi CodeQL / Secret-Scanning hoiatusi ei avatud, või iga neist lükati tagasi tehnilise põhjendusega, viidates asjakohasele `docs/security/` dokumendile -- [ ] Marsruudid, mis käivitavad lapseprotsesse (`/api/mcp/`, `/api/cli-tools/runtime/`), on klassifitseeritud `isLocalOnlyPath()` abil `src/server/authz/routeGuard.ts` failis — vaata [Rangedreegel nr 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Kommitedetsioonides pole `Co-Authored-By` trailerid — kommid peavad ilmuma ainult hoidla omaniku Git identiteedi all (Rangedreegel nr 16) +## Tõmbetaotluse kontrollnimekiri + +- [ ] Testid läbivad edukalt (`npm test`) +- [ ] Lintimine läbib edukalt (`npm run lint`) +- [ ] Järk õnnestub (`npm run build`) +- [ ] Uutele avalikele funktsioonidele ja liidestele on lisatud TypeScripti tüübid +- [ ] Puuduvad kõvakodeeritud saladused või varuväärtused +- [ ] Avalikud ülesvoolu pääsutunnused on manustatud `resolvePublicCred()` kaudu (vt [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), mitte kunagi literaalidena +- [ ] Veavastused läbivad `buildErrorBody()` / `sanitizeErrorMessage()` — vastuste kehades pole töötlemata pinujälgi (vt [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Kestakäsud (`exec` / `spawn`) edastavad käitusaegsed väärtused `env` kaudu, mitte stringinterpolatsiooni abil +- [ ] Kõik sisendid on valideeritud Zodi skeemidega +- [ ] Kasutajale nähtavate muudatuste jaoks on lisatud muudatuste logi **fragment** asukohta `changelog.d/{features|fixes|maintenance}/-.md` (vt [`changelog.d/README.md`](./changelog.d/README.md)) — ärge muutke faili `CHANGELOG.md` otse; fragmendid koondatakse väljalaske ajal ega lähe tõmbetaotluste vahel kunagi konflikti +- [ ] Dokumentatsioon on värskendatud (kui kohaldatav) +- [ ] Uusi CodeQL-i / saladuste skannimise hoiatusi pole avatud või iga hoiatus on tagasi lükatud tehnilise põhjendusega, mis viitab asjakohasele dokumendile kataloogis `docs/security/` +- [ ] Alamprotsesse käivitavad marsruudid (`/api/mcp/`, `/api/cli-tools/runtime/`) on failis `src/server/authz/routeGuard.ts` liigitatud funktsiooniga `isLocalOnlyPath()` — vt [ranget reeglit nr 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Sissekannete sõnumites pole tehisintellekti/roboti `Co-authored-by` järelridu (range reegel nr 16) — inimkaastöötajaid, kelle tööd taaskasutatakse, tunnustatakse standardsete `Co-authored-by: Name ` järelridadega --- diff --git a/docs/i18n/et/docs/architecture/QUALITY_GATES.md b/docs/i18n/et/docs/architecture/QUALITY_GATES.md index 7f139dcda622..78b2b8164839 100644 --- a/docs/i18n/et/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/et/docs/architecture/QUALITY_GATES.md @@ -19,210 +19,210 @@ replikatsiooniplaani leiate dokumendist ### Kandidaadi vastuvõtmine -CI ja Quality Gates töövood väljastavad kumbki stabiilse otsuse: `Gate / CI` ja +CI ja Quality Gatesi töövood väljastavad kumbki stabiilse otsuse: `Gate / CI` ja `Gate / Quality`. Nende versioonitud vastuvõtupoliitika loetleb iga eelneva töö -kohustusliku või soovituslikuna. Kohaldatav kohustuslik töö peab õnnestuma: puuduv, +kohustusliku või nõuandvana. Kohaldatav kohustuslik töö peab õnnestuma: puuduv, tühistatud, vahele jäetud, ootel või tundmatu tulemus ei saa kinnitada olekut PASS. Kehtiv -ainult dokumentatsiooni või ainult kataloogi puudutav klassifikatsioon võib muuta koodiraja mittekohaldatavaks; -mustand-PR ei ole vastuvõetav kandidaat. Silt `hotfix` ei vabasta tõendite esitamisest. +ainult dokumentatsiooni või ainult kataloogi muudatuse klassifikatsioon võib muuta koodiraja mittekohaldatavaks; +mustand-PR ei ole aktsepteeritud kandidaat. Silt `hotfix` ei vabasta tõendite esitamisest. Mõlemad töövood hõlmavad PR-e ja tõukeid põhi-/väljalaskeharudesse, käsitsi käivitamist ning -ühendamisrühma sündmusi. Tõuge, käsitsi käivitamine ja ühendamisrühm käitavad täieliku valiku. Harud -ja ühendamisrühmad kasutavad GitHubi hostitud käitajaid tööde jaoks, mis muidu valiksid isemajutatud -käitajad; enne kasutuselevõttu tuleb kontrollida piisava hostitud ressursimahu olemasolu. +liitmisrühma sündmusi. Tõuke, käsitsi käivitamise ja liitmisrühma korral käivitatakse täielik valik. Forkid +ja liitmisrühmad kasutavad majutatud käitajaid tööde jaoks, mis muidu valiksid isemajutatud +käitajad; enne kasutuselevõttu tuleb kontrollida piisava majutatud võimsuse olemasolu. Iga JSON-kviitung tuvastab väljavõetud SHA, töövoo käivituse ja katse. -CLI lükkab väljavõtte/sündmuse SHA mittevastavuse tagasi. Töövootestid seovad poliitika liikmesuse +CLI lükkab tagasi väljavõtte/sündmuse SHA mittevastavuse. Töövootestid seovad poliitika liikmesuse otsusetöö `needs` loendiga, et uus või eemaldatud rada ei saaks märkamatult kaduda. -Kviitungid hõlmavad oma töövoogu, mitte avaldamist, juurutamist ega olemasoleva -soovitusliku skanneri sisemist toimimist. Mõlema kontrollnime aktiveerimine harureeglites on -eraldi administratiivne muudatus; nende tööde lisamine ei kaitse iseenesest haru. +Kviitungid hõlmavad nende enda töövoogu, mitte avaldamist, juurutamist ega olemasoleva +nõuandva skanneri sisemist tööd. Mõlema kontrollnime aktiveerimine harureeglites on +eraldi haldusmuudatus; nende tööde lisamine ei kaitse iseenesest haru. ### Staatilise skannimise loend -Versioonitud npm-aliaste loend ja staatilise skannimise koosseis asuvad failis +Versioonitud npm-i aliaste loend ja staatilise skannimise liikmesus asuvad failis `config/quality/gate-manifest.json`. Käivitage `npm run check:gate-manifest`, et valideerida -skriptide nimed ja täpsed käsud faili `package.json` suhtes; lisamised, eemaldamised ja -käskude kõrvalekalded põhjustavad tõrke nii kohalikus haagis kui ka CI muudatuste klassifitseerimise töödes. +skriptinimed ja täpsed käsud faili `package.json` suhtes; lisamised, eemaldamised ja +käskude lahknevused nurjavad nii kohaliku hook'i kui ka CI muudatuste klassifitseerimise tööd. Alias ei ole töövootöö, maatriksi eksemplar ega testjuhtum: neid arve ei tohi -esitada omavahel asendatavatena. +esitada samaväärsetena. -Kasutage valitud aliaste kontrollimiseks ilma neid käivitamata käsku `npm run quality:scan -- --list` või -`npm run quality:scan:fast -- --list`. -Käitaja kutsub välja npm-i sisenemispunkti, mistõttu säilib selle käituskeskkond -(sh Bun, kui see on seadistatud). -Manifest salvestab nendest profiilidest väljapoole jäävad aliased eraldi käivitatavatena ning +Kasutage `npm run quality:scan -- --list` või `npm run quality:scan:fast -- --list`, +et valitud aliaseid ilma neid käivitamata kontrollida. Käitaja kutsub välja +npm-i sisenemispunkti, mistõttu säilib selle käituskeskkond (sh Bun seal, kus see on seadistatud). +Manifest märgib neist profiilidest väljapoole jäävad aliased eraldi käivitatavatena ning hoolduskäsud on kirjutuskaitstud skannimisprofiilides keelatud. -Need profiilid hõlmavad ainult staatilist skannimist. Need ei sertifitseeri toote teste, -katvust, pakendamist, väliseid kontrolle ega kandidaadi täielikku väljalaskekõlblikkust. -Töövoo vastuvõtt kasutab lingitud faili `config/quality/admission-policy.json` ja -skripti `scripts/quality/admission-verdict.mjs`. Väljalaskevaatleja profiilid jäävad eraldiseisvaks; -kontrollige nende kohaldatavaid kontrolle ja kviitungeid eraldi. Allolev kirjeldav -loend on viitematerjal, mitte tõend selle kohta, et kontrollvärav tegelikult käivitus. +Need profiilid hõlmavad ainult staatilist skannimist. Need ei sertifitseeri tooteteste, +katvust, pakendamist, väliseid kontrolle ega kandidaadi täielikku väljalaske heakskiitu. +Töövoo vastuvõtmisel kasutatakse lingitud faili `config/quality/admission-policy.json` ja +`scripts/quality/admission-verdict.mjs`. Väljalaskevaatleja profiilid jäävad eraldiseisvaks; +kontrollige nende kohaldatavaid kontrolle ja kviitungeid eraldi. Allolev tekstiline +loend on viide, mitte tõend selle kohta, et kontrollvärav tegelikult käivitati. -Skriptid asuvad kataloogides `scripts/check/` (poliitika kontrollväravad) ja `scripts/quality/` (rangemaks muutmise mootor). +Skriptid asuvad kataloogides `scripts/check/` (poliitika kontrollväravad) ja `scripts/quality/` (ratchet-mootor). CI tõeallikas on `.github/workflows/ci.yml`. ### Väljalaske-PR-i kiirtee (`quality.yml`) -`.github/workflows/quality.yml` täiendab CI-d põhi-/väljalaskeharude PR-ide, kaitstud harudesse -tehtavate tõugete, käsitsi käivitamise ja ühendamisrühmade korral. PR-id kasutavad teepõhiselt filtreeritud kiirkontrolle. Püsivalt -keelatud dubleeriv koostamine eemaldati; tegelikud koostamise/pakendamise/käivitamise kontrollid jäävad CI-sse. - -| Töö | Ulatus | Blokeeriv | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `Docs Gates (fast-path)` | Dokumentatsiooni/koodi PR-id; API dokumentatsiooni viited ja kogu dokumentatsioon | Jah | -| `Fast Quality Gates` | Koodi PR-id; staatilised kontrollid, tüübikontroll, juhtpaneeli tüübikontroll, mõjutatud ühiktestid | Jah | -| `Forgotten sibling tests` | Koodi PR-id; muudetud moodulite seostamine staatiliste tarbijate ja võimalike sõsar-testidega; tünn- ja dünaamilise impordi teed esitatakse soovituslike diagnostikatena koos viidatud lubatud eranditega | **Soovituslik** | -| `Vitest (fast-path)` | Koodi PR-id; kiire vitesti komplekt | Jah | -| `Unit Tests fast-path` | Koodi PR-id; neljaks killuks jaotatud ühiktestide komplekt | Jah | -| `No new ESLint warnings` | Koodi PR-id; välistusi arvestav lintimise kaitse | Jah, sh harude puhul | -| `Merge integrity (changelog + generated skills)` | PR-id, mis pole mustandid; muudatuste logi ja genereeritud oskuste sünkroonimine | Jah, sh harude puhul | - -#### Unustatud sõsar-testide aruanne - -`npm run check:forgotten-sibling-tests` taaskasutab testimõjude kaardi aluseks olevat impordilahendajat. -Iga muudetud tootmismooduli puhul esitab see deterministlikud -`muudetud moodul/sümbol -> staatiline tarbija -> võimalik sõsar-test` ahelad, kui võimalik -test puudub tõmbetaotluse erinevustest. Markdowni kokkuvõte ja JSON-tulemus säilitatakse -töövooartefaktina `forgotten-sibling-tests`, et neid saaks enne blokeerivat kasutuselevõttu kalibreerida. - -Barrel-re-ekspordid ja dünaamilised impordid on ainult lahendamise diagnostika; need ei tekita kunagi +`.github/workflows/quality.yml` täiendab CI-d põhi-/väljalaske-PR-ide, kaitstud harudesse +tehtavate tõugete, käsitsi käivitamise ja liitmisrühmade korral. PR-id kasutavad teepõhise filtriga kiirkontrolle. Püsivalt +keelatud dubleeriv kooste eemaldati; tegelikud kooste-, pakendamis- ja käivituskontrollid jäävad CI-sse. + +| Töö | Ulatus | Blokeeriv | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `Docs Gates (fast-path)` | Dokumentatsiooni-/koodi-PR-id; API dokumentatsiooni viited ja docs-all | Jah | +| `Fast Quality Gates` | Koodi-PR-id; staatilised kontrollid, tüübikontroll, töölaua tüübikontroll, mõjutatud ühiktestid | Jah | +| `Forgotten sibling tests` | Koodi-PR-id; muudetud moodulid, mis on jälitatud staatiliste tarbijate ja võimalike sõsartestideni; barrel- ja dünaamilise impordi teed esitatakse nõuandva diagnostikana koos viidatud lubatud loendi eranditega | **Nõuandev** | +| `Vitest (fast-path)` | Koodi-PR-id; kiire vitest-komplekt | Jah | +| `Unit Tests fast-path` | Koodi-PR-id; neljaks killuks jaotatud ühiktestide komplekt | Jah | +| `No new ESLint warnings` | Koodi-PR-id; mahavaigistusi arvestav lint-kaitse | Jah, sh forkide puhul | +| `Merge integrity (changelog + generated skills)` | PR-id, mis pole mustandid; muudatuste logi ja genereeritud oskuste sünkroonimine | Jah, sh forkide puhul | + +#### Unustatud sõsartestide aruanne + +`npm run check:forgotten-sibling-tests` taaskasutab testimõju kaardi aluseks olevat impordilahendajat. +Iga muudetud tootmismooduli kohta esitab see deterministlikud +`muudetud moodul/sümbol -> staatiline tarbija -> võimalik sõsartest` ahelad, kui võimalik +test puudub pull request'i erinevustest. Markdowni kokkuvõte ja JSON-tulemus säilitatakse +töövoo artefaktina `forgotten-sibling-tests`, et neid enne blokeeriva kasutuselevõtu alustamist kalibreerida. + +Barrel-taasekspordid ja dünaamilised impordid on ainult lahendamise diagnostika; need ei tekita kunagi blokeerivat leidu. Läbivaadatud erandid asuvad failis `config/quality/forgotten-sibling-allowlist.json`. Iga kirje peab nimetama tarbija ja kandidaat- -testi, esitama konkreetse põhjenduse ning linkima GitHubi probleemi või tõmbetaotluse. Vigase vorminguga kirjed -lükatakse vaikimisi tagasi. Erandid ei saa eirata kustutatud kandidaattesti ega muudatust, mis lisab `.skip`/`.todo`; -assertide nõrgendamine ja muu varjamine jäävad sõltumatult blokeeriva +testi, esitama konkreetse põhjenduse ning viitama GitHubi probleemile või tõmbetaotlusele. Vigased kirjed +põhjustavad tõrke vaikimisi. Erandid ei saa peita kustutatud kandidaattesti ega muudatust, mis lisab `.skip`/`.todo`; +väidete nõrgendamine ja muu varjamine jäävad eraldi blokeeriva `check:test-masking` kontrollvärava vastutusalasse. ### Töö: `lint` -Käivitatakse iga `main`-haru tõmbetaotluse korral. Tõrke korral blokeerib ühendamise. - -| Skript (`npm run ...`) | Kontrollib | Blokeeriv | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | Node.js-i versioon jääb toetatud vahemikku | Jah | -| `check:cycles` | Tsüklilised impordid — kõik `src/` + `open-sse/` moodulid | Jah | -| `check:route-validation:t06` | Zod-skeemid on olemas kõigil marsruutidel (6. taseme reegel) | Jah | -| `check:any-budget:t11` | `@ts-expect-error // any` arv ei ületa eelarvet (11. taseme catraca) | Jah | -| `check:provider-consistency` | Igal teenusepakkujal failis `providers.ts` on vastav kirje failis `providerRegistry.ts` (ja vastupidi, lubatud loendi piires) | Jah | -| `check:model-lifecycle` | Kolm käsitsi hooldatavat marsruutimistabelit püsivad kooskõlas repositooriumisse talletatud elutsükli hetktõmmisega (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ei anna skoori ühelegi kasutuselt kõrvaldatud ID-le, mida `REGISTRY` saab marsruutida; iga `BUILT_IN_ALIASES`-e sihtmärk on olemas registris `REGISTRY` ja puudub kasutuselt kõrvaldatud ID-de hetktõmmisest; iga kasutuselt kõrvaldatud ID, mis on endiselt registris `REGISTRY`, suunatakse edasi või on loetletud loendis `allowedRetiredInCatalog`; ning ükski `DEFAULT_DEGRADATION_MAP`-i allikas ega sihtmärk ei ole selles hetktõmmises märgitud kasutuselt kõrvaldatuks. See ei tõesta, et aktiivne välisteenus mudelit praegu teenindab. Võrguühenduseta — võrdleb failiga `config/quality/model-lifecycle.json`, mida värskendatakse käsitsi käsuga `npm run quality:refresh-model-lifecycle` (vajab võrku; pole CI-ga ühendatud). `allowedRetiredInCatalog` on järkjärgulise vähendamise põrkmehhanism: lisa kirje ainult koos jälgimisprobleemiga. | Jah | -| `check:fetch-targets` | Iga kliendipoolses kataloogis `src/` olev `fetch("/api/...")` viib tegeliku failini `route.ts` | Jah | -| `check:deps` | Kõik repositooriumi igas failis `package.json` olevad käsuga `npm install` installitavad sõltuvused sisalduvad failis `dependency-allowlist.json`; uued fikseerimata versiooniga või kirjaveaga paketina maskeeritud sõltuvused märgistatakse | Jah | -| `audit:deps` | `npm audit` (juurkataloog + Electron) — puuduvad kõrge või kriitilise raskusastmega turvanõuanded (kattub OSV kontrolliga `check:vuln-ratchet`; vt ratsionaliseerimise ootenimekirja) | Jah | -| `check:lockfile` | Faili `package-lock.json` terviklus — HTTPS-register, terviklusräsid, hosti ülekirjutused puuduvad | Jah | -| `check:licenses` | Tootmissõltuvuste SPDX-litsentside lubatud loend | Jah | -| `check:tracked-artifacts` | Ei mingeid järgitavaid ehitusartefakte ega versioonihaldusse lisatud `node_modules`-i sümbollinke (käivitatakse ka husky pre-commit konksus; pre-push on tahtlikult kerge — #6716) | Jah | -| `check:ai-attribution` | PR-i commit'ides, pealkirjas ega sisus ei tohi olla tehisintellekti/boti `Co-Authored-By` järelkirjet ega tehisintellektiga genereerimise jalust — range reegel #16 (`quality.yml`-i kiirete kontrollide tsüklis PR→`release/**` jaoks — loeb sündmuse andmeid, väljaspool PR-e ei tee midagi — ning `ci.yml`-i lintimises ainult PR-idele mõeldud sammuna PR→`main` jaoks; samuti husky `commit-msg` konksus; inimestest kaasautorid on lubatud; #14436) | -| `check:vitest-exclusions` | Iga Vitesti välistus nimetab jälgimisprobleemi ja esineb failis `config/quality/vitest-exclusions.json` (#13204) | Jah | -| `check:file-size` | Ükski lähtefail ei ületa faililaiendipõhist piirangut (põrkmehhanism: fikseeritud suured failid loendis `frozen`) | Jah | -| `check:error-helper` | Täiturite/käsitlejate veavastused kasutavad `buildErrorBody()` / `sanitizeErrorMessage()` (range reegel #12) | Jah | -| `check:migration-numbering` | Migratsiooni SQL-failid on järjest nummerdatud, ilma lünkade ja duplikaatideta | Jah | -| `check:public-creds` | Väljaspool faili `publicCreds.ts` ei ole literaalseid OAuthi `client_id`/`client_secret` väärtusi ega Firebase Webi võtmeid (range reegel nr 11) | Jah | -| `check:db-rules` | Väljaspool `src/lib/db/` mooduleid ei ole toorest SQL-i; failist `localDb.ts` ei kasutata koondimporti (ranged reeglid nr 2 ja 5) | Jah | -| `check:known-symbols` | Dispetšertabelites registreeritud teenusepakkujate täiturid, marsruutimisstrateegiad ja tõlkijad vastavad kettal olevatele failidele — pole orvuks jäänud ega deklareerimata sümboleid | Jah | -| `check:route-guard-membership` | Iga alamprotsessi käivitav marsruut on funktsiooni `isLocalOnlyPath()` abil klassifitseeritud (ranged reeglid nr 15 ja 17) | Jah | -| `check:test-discovery` | Vähemalt üks testikäitaja kogub repositooriumist kõik `*.test.ts` / `*.spec.ts` failid (põrkmehhanism: orbfailide loend failis `test-discovery-baseline.json` saab ainult kahaneda) | Jah | -| `check:agent-skills-sync` | Genereeritud agent-skills artefaktid vastavad nende lähtekataloogile (lahknevusi pole) | -| `check:provider-asset-provenance` | Teenusepakkujate logode/varade päritolu on dokumenteeritud | -| `lint:json` | JSON-i konfiguratsioonifailid on süntaktiliselt korrektsed ja vastavad repo lintimisreeglitele | -| `typecheck:core` | TypeScripti kompileerimine vigadeta (ainult soovituslikud hoiatused) | Jah | -| `typecheck:noimplicit:core` | Range `noImplicitAny` — tulevikku suunatud; paljud olemasolevad väljakutsekohad vajavad endiselt annotatsioone | **Soovituslik** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, mille ulatus on piiratud teega `src/app/(dashboard)/**` (#7033) — `typecheck:core`-i hoolikalt valitud 27 faili lubamisloend ei sisalda ühtegi töölaua TSX-faili ning ka `next build` ei tee neile kunagi tüübikontrolli (`next.config.mjs` määrab `ignoreBuildErrors: true`), mistõttu jäid sealsete orvuks jäänud identifikaatorite regressioonid (#6625/#6909) CI-le nähtamatuks. Erinevusi võrreldakse fikseeritud faili- ja TS-koodi põhiste loendurite lähteväärtustega (`config/quality/dashboard-typecheck-baseline.json`, sama aegunud kirjete jõustamise muster nagu käsul `check:known-symbols`) — kontroll ebaõnnestub ainult siis, kui esineb lähteväärtuses fikseeritud arvust rohkem UUSI vigu; olemasoleva vea parandamisel vähenda lähteväärtust käsuga `--update`. | Jah | +Käivitatakse iga `main`-haru PR-i puhul. Tõrke korral blokeerib ühendamise. + +| Skript (`npm run ...`) | Kontrollib | Blokeeriv | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | +| `check:node-runtime` | Node.js-i versioon jääb toetatud vahemikku | Jah | +| `check:cycles` | Ringimpordid kogu kataloogis `src/` + `open-sse/` (AST-põhine, tsconfigi `paths` lahendatud). Ilma lisavalikuteta käivitamine on nõuandev ja loetleb tsüklid. `check:cycles:ratchet` (mida CI käitab) blokeerib, kui arv ületab faili `quality-baseline.json` ülempiiri `metrics.cycles` — praegu 14, `direction: down`, seega saab see ainult väheneda (#15159 G-01/G-02) | Jah (ratchet) | +| `check:route-validation:t06` | Zodi skeemide olemasolu kõigil marsruutidel (6. taseme poliitika) | Jah | +| `check:any-budget:t11` | `@ts-expect-error // any` arv ei ületa eelarvet (11. taseme catraca) | Jah | +| `check:provider-consistency` | Igal `providers.ts` failis oleval teenusepakkujal on vastav kirje failis `providerRegistry.ts` (ja vastupidi, lubatud loendi piires) | Jah | +| `check:model-lifecycle` | Kolm käsitsi hallatavat marsruutimistabelit püsivad kooskõlas repositooriumis talletatud elutsükli hetktõmmisega (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ei anna skoori ühelegi käigust kõrvaldatud ID-le, mida `REGISTRY` saab marsruutida; iga `BUILT_IN_ALIASES` sihtmärk leidub registris `REGISTRY` ja puudub käigust kõrvaldatud ID-de hetktõmmisest; iga käigust kõrvaldatud ID, mis on endiselt registris `REGISTRY`, suunatakse edasi või on loetletud loendis `allowedRetiredInCatalog`; ning ükski `DEFAULT_DEGRADATION_MAP` lähte- ega sihtüksus ei ole selles hetktõmmises märgitud käigust kõrvaldatuks. See ei tõesta, et mudelit teenindab praegu toimiv ülesvooluteenus. Võrguühenduseta — võrdleb failiga `config/quality/model-lifecycle.json`, mida värskendatakse käsitsi käsuga `npm run quality:refresh-model-lifecycle` (vajab võrku; pole CI-sse ühendatud). `allowedRetiredInCatalog` on järkjärgulise vähendamise põrkmehhanism: lisa kirje ainult koos jälgimisprobleemiga. | Jah | +| `check:fetch-targets` | Iga kliendipoolses kataloogis `src/` olev `fetch("/api/...")` viitab tegelikule failile `route.ts` | Jah | +| `check:deps` | Kõik repositooriumi igas failis `package.json` olevad käsuga `npm install` installitavad sõltuvused on loendis `dependency-allowlist.json`; uued fikseerimata versiooniga või kirjaveopettuse kahtlusega paketid märgistatakse | Jah | +| `audit:deps` | `npm audit` (juurkataloog + electron) — kõrge ega kriitilise tasemega turvahoiatusi pole (kattub OSV kontrolliga `check:vuln-ratchet`; vt ratsionaliseerimise tööjärge) | Jah | +| `check:lockfile` | Faili `package-lock.json` terviklus — HTTPS-register, terviklusräsid, hosti ülekirjutused puuduvad | Jah | +| `check:licenses` | Tootmissõltuvuste SPDX-litsentside lubatud loend | Jah | +| `check:tracked-artifacts` | Ei mingeid järgitavaid ehitusartefakte / hoidlasse lisatud `node_modules` sümbollinke (käivitatakse ka husky pre-commit-konksus; pre-push on tahtlikult kerge — #6716) | Jah | +| `check:ai-attribution` | PR-i commit'ides, pealkirjas ega sisus ei tohi olla AI/boti `Co-Authored-By` järelrida ega AI-ga genereerimise jalust — range reegel #16 (`quality.yml` kiirkontrollide tsüklis PR→`release/**` jaoks — loeb sündmuse andmestikku, väljaspool PR-e ei tee midagi — ning ainult PR-idele mõeldud sammuna `ci.yml` lintimises PR→`main` jaoks; samuti husky `commit-msg` konksus; inimestest kaasautorid on lubatud; #14436) | +| `check:vitest-exclusions` | Iga Vitesti välistus nimetab jälgimisülesande ja esineb failis `config/quality/vitest-exclusions.json` (#13204) | Jah | +| `check:file-size` | Ükski lähtekoodifail ei ületa faililaiendipõhist ülempiiri (põrkmehhanism: fikseeritud suurusega failid loendis `frozen`) | Jah | +| `check:error-helper` | Täiturite/käitlejate veavastused kasutavad funktsiooni `buildErrorBody()` / `sanitizeErrorMessage()` (range reegel #12) | Jah | +| `check:migration-numbering` | Migratsiooni SQL-failid on järjestikku nummerdatud, ilma lünkade või duplikaatideta | Jah | +| `check:public-creds` | Väljaspool faili `publicCreds.ts` ei ole literaalseid OAuthi `client_id`/`client_secret` väärtusi ega Firebase Webi võtmeid (range reegel nr 11) | Jah | +| `check:db-rules` | Väljaspool kataloogi `src/lib/db/` mooduleid ei ole töötlemata SQL-i; failist `localDb.ts` ei tehta koondimporti (ranged reeglid nr 2 / nr 5) | Jah | +| `check:known-symbols` | Teenusepakkujate täiturid, marsruutimisstrateegiad ja tõlkijad, mis on registreeritud nende väljastustabelites, vastavad kettal olevatele failidele — orvuks jäänud või deklareerimata sümboleid pole | Jah | +| `check:route-guard-membership` | Iga alamprotsessi käivitav marsruut on funktsiooni `isLocalOnlyPath()` alusel klassifitseeritud (ranged reeglid nr 15 / nr 17) | Jah | +| `check:test-discovery` | Iga repos olev fail `*.test.ts` / `*.spec.ts` kaasatakse vähemalt ühe testikäitaja poolt (põrkmehhanism: orbfailide loend failis `test-discovery-baseline.json` võib ainult kahaneda) | Jah | +| `check:agent-skills-sync` | Loodud agendioskuste artefaktid vastavad nende lähtekataloogile (lahknevusi pole) | +| `check:provider-asset-provenance` | Teenusepakkujate logodel/varadel on registreeritud päritolukirje | +| `lint:json` | JSON-i konfiguratsioonifailid on parsitavad ja vastavad hoidla lintimisreeglitele | +| `typecheck:core` | TypeScripti kompileerimine vigadeta (ainult nõuandvad hoiatused) | Jah | +| `typecheck:noimplicit:core` | Range `noImplicitAny` — tulevikku suunatud; paljud olemasolevad väljakutsed vajavad endiselt annotatsioone | **Nõuandev** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, mille ulatus on piiratud kataloogiga `src/app/(dashboard)/**` (#7033) — `typecheck:core`-i kureeritud 27 faili lubatud loend ei sisalda ühtegi dashboardi TSX-faili ning `next build` ei tee neile samuti kunagi tüübikontrolli (`next.config.mjs` määrab `ignoreBuildErrors: true`), mistõttu jäid sealsed orvuks jäänud identifikaatorite regressioonid (#6625/#6909) CI jaoks nähtamatuks. Erinevusi võrreldakse fikseeritud faili- ja TS-koodipõhise vigade arvu lähteolukorraga (`config/quality/dashboard-typecheck-baseline.json`, sama aegunud kirjete jõustamise muster nagu `check:known-symbols`-is) — kontroll ebaõnnestub ainult uute vigade korral, mis ületavad lähteolukorras määratud arvu; kui olemasolev viga parandatakse, vähendage väärtust käsuga `--update`. | Jah | ### Töö: `quality-gate` -Käivitub pärast tööd `test-coverage`. Ebaõnnestumise korral blokeerib ühendamise. +Käivitub pärast `test-coverage`-it. Ebaõnnestumise korral blokeerib ühendamise. -| Skript | Kontrollib | Blokeeriv | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `quality:collect` | Väljastab faili `quality-metrics.json` (ESLinti hoiatuste arv, katvus ühendatud osaaruandest) | Jah (ratchet'i eeltingimus) | -| `quality:ratchet` | Ükski faili `quality-baseline.json` mõõdik pole halvenenud (ESLinti hoiatusi ≤ lähtetase; katvus ≥ lähtetase) | Jah | -| `check:duplication` | Koodi dubleerimine (jscpd@4) ei ületa failis `quality-baseline.json` määratud lähtetaset | Jah | -| `check:complexity` | Failitaseme tsüklomaatiline keerukus ei ületa ülempiiri (ESLinti põhivahendid `complexity` + `max-lines-per-function`) | Jah | -| `check:cognitive-complexity` | Kognitiivse keerukuse ratchet (`eslint-plugin-sonarjs`) — eraldi ESLinti läbimine; CI käitab mõlemat ühendatult ühe sammuna `check:complexity-ratchets` | Jah | -| `check:dead-code` | Kasutamata eksporditud elementide / failide ratchet (knip) ei halvene võrreldes lähtetasemega | Jah | -| `check:compression-budget` | Tihendamise jõudlustesti eelarve — mootoripõhised tokenisäästu alampiirid ei tohi halveneda | Jah | -| `check:type-coverage` | Tüüpidega kaetuse protsendi ratchet (`type-coverage`) ei halvene; asendab suures osas kontrolli `typecheck:noimplicit:core` | Jah | -| `check:codeql-ratchet` | Avatud CodeQL-i hoiatuste arv ei halvene (loeb käsuga `gh api`; jätab tokeni puudumisel tõrgeteta vahele) — värskendamissageduse ja käsitsi käivitamise kohta vt allpool „CodeQL-i ratchet“ | Jah | +| Skript | Kontrollib | Blokeeriv | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `quality:collect` | Väljastab faili `quality-metrics.json` (ESLinti hoiatuste arv, ühendatud osaaruande põhjal arvutatud koodikate) | Jah (ratchet’i eelnev etapp) | +| `quality:ratchet` | Ükski faili `quality-baseline.json` mõõdik pole halvenenud (ESLinti hoiatusi ≤ lähtetase; koodikate ≥ lähtetase) | Jah | +| `check:duplication` | Koodi dubleerimine (jscpd@4) ei ületa failis `quality-baseline.json` määratud lähtetaset | Jah | +| `check:complexity` | Failitaseme tsüklomaatiline keerukus ei ületa ülempiiri (ESLinti põhireeglid `complexity` + `max-lines-per-function`) | Jah | +| `check:cognitive-complexity` | Kognitiivse keerukuse ratchet (`eslint-plugin-sonarjs`) — eraldi ESLinti läbimine; CI käitab mõlemat ühendatuna ühe etapina `check:complexity-ratchets` | Jah | +| `check:dead-code` | Kasutamata eksporditud elementide / failide ratchet (knip) ei halvene võrreldes lähtetasemega | Jah | +| `check:compression-budget` | Tihendamise võrdlustesti eelarve — mootoripõhised tokenisäästu alampiirid ei tohi halveneda | Jah | +| `check:type-coverage` | Tüübitud koodi osakaalu ratchet (`type-coverage`) ei halvene; asendab suures osas kontrolli `typecheck:noimplicit:core` | Jah | +| `check:codeql-ratchet` | Avatud CodeQL-i hoiatuste arv ei suurene (loeb käsuga `gh api`; tokeni puudumisel jäetakse sujuvalt vahele) — värskendamissagedus ja käsitsi käivitamine: vt allpool „CodeQL-i ratchet” | Jah | ### Töö: `quality-extended` -Kogu töö on nõuandev (`continue-on-error: true`). npm-il põhinevad ratchet'id -käivitatakse tegelikult; välised skannerid installitakse käsuga `gh release download` ja jätavad -binaarfaili jätkuva puudumise korral ise kontrolli vahele (väljumiskood 0). +Kogu töö on nõuandev (`continue-on-error: true`). npm-il põhinevad ratchet’id käivitatakse +tegelikult; välised skannerid installitakse käsuga `gh release download` ja jätavad kontrolli ise vahele (väljumiskood 0), +kui kahendfail endiselt puudub. -| Skript | Kontrollib | Blokeeriv | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| `check:circular-deps` | Ringseid sõltuvusi pole (dpdm) | **Nõuandev** | -| `check:bundle-size` | Paketi suurus ei ületa ülempiiri | **Nõuandev** | -| `check:secrets` | Saladuste skannimine (gitleaks) — jäetakse binaarfaili puudumisel vahele | **Nõuandev** | -| `check:vuln-ratchet` | Sõltuvuste haavatavused (osv-scanner) ei halvene — jäetakse binaarfaili puudumisel vahele | **Nõuandev** | -| `check:workflows` | Töövoogude lintimine (actionlint + zizmor); puuduvad või katkised skannerid, vigased aruanded või puuduv ratchet'i lähtetase põhjustavad tulemuse INCOMPLETE. Leitud probleemidele rakendatakse valitud ranget/nõuandvat/ratchet'i poliitikat | Käivitamine on nõutav; zizmor'i ratchet on CI-s blokeeriv | -| `check:openapi-breaking` | Avaliku API lepingu (`openapi.yaml`) katkestavad muudatused võrreldes baasharuga (oasdiff) — väljastab `openapiBreaking=N`; jäetakse vahele, kui oasdiff puudub või põhispetsifikatsiooni ei saa lahendada | **Nõuandev** | +| Skript | Kontrollib | Blokeeriv | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | +| `check:circular-deps` | Ringikujulisi sõltuvusi pole (dpdm) | **Nõuandev** | +| `check:bundle-size` | Paketi suurus ei ületa ülempiiri | **Nõuandev** | +| `check:secrets` | Saladuste skannimine (gitleaks) — jäetakse vahele, kui kahendfail puudub | **Nõuandev** | +| `check:vuln-ratchet` | Sõltuvuste haavatavused (osv-scanner) ei halvene — jäetakse vahele, kui kahendfail puudub | **Nõuandev** | +| `check:workflows` | Töövoogude lintimine (actionlint + zizmor); puuduvad või katkised skannerid, vigased aruanded või puuduv ratchet’i lähtetase põhjustavad oleku INCOMPLETE. Kehtivate leidude puhul järgitakse valitud ranget, nõuandvat või ratchet’i poliitikat | Käivitamine on kohustuslik; zizmor’i ratchet on CI-s blokeeriv | +| `check:openapi-breaking` | Avaliku API lepingu (`openapi.yaml`) murrangulised muudatused võrreldes baasharuga (oasdiff) — väljastab `openapiBreaking=N`; jäetakse vahele, kui oasdiff puudub või baasspetsifikatsiooni ei saa lahendada | **Nõuandev** | ### Töö: `docs-sync-strict` -Käivitatakse iga harule `main` suunatud PR-i puhul. Tõrke korral blokeerib ühendamise. - -| Skript | Kontrollib | Blokeeriv | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| `check:docs-all` | Metavärav, mis käitab allolevat kuut alamväravat järjestikku | Jah | -| ↳ `check:docs-sync` | CHANGELOG-i / OpenAPI / llm.txt versioonide kooskõla | Jah | -| ↳ `check:docs-counts` | Tekstis esitatud arvud (pakkujate arv, migratsioonide arv jne) jäävad tegelike arvude põrkmehhanismi lubatud vahemikku | Jah | -| ↳ `check:env-doc-sync` | Iga keskkonnamuutuja failis `.env.example` on dokumentatsiooni tabelis kirjeldatud ja vastupidi | Jah | -| ↳ `check:deprecated-versions` | Dokumentatsioonis puuduvad aegunud versioonistringid | Jah | -| ↳ `check:doc-links` | Dokumentatsiooni sisemised markdown-lingid osutavad olemasolevatele failidele (`[tekst]`/`(path)`-kujul) | Jah | -| ↳ `check:fabricated-docs` | Dokumentatsioonis viidatud marsruudid, keskkonnamuutujad, CLI-käsud, konksude nimed ja failiteed on koodibaasis olemas. Range värav lipuga `--strict`; ilma liputa pehme tõrge. | Jah (CI-s lipu `--strict` kaudu) | -| `check:cli-i18n` | CLI-käskude stringid on olemas kõigis i18n-i lokaadifailides | Jah | -| `check:openapi-coverage` | OpenAPI spetsifikatsioon hõlmab vähemalt põrkmehhanismiga määratud miinimumarvu tegelikke marsruute | Jah | -| `check:openapi-security-tiers` | Turbetaseme annotatsioonid failis `openapi.yaml` on kooskõlas faili `routeGuard.ts` klassifikatsioonidega | **Nõuandev** | -| `check:openapi-routes` | Iga faili `openapi.yaml` tee vastab tegelikule failile `route.ts` (hallutsinatsioonivastane kontroll) | Jah | -| `check:docs-symbols` | Iga viide `/api/...` failides `docs/**/*.md` vastab tegelikule failile `route.ts` (hallutsinatsioonivastane kontroll) | Jah | -| `i18n translation drift` | Tõlkimata võtmed i18n-i lokaadifailides — ainult hoiatus | **Nõuandev** | +Käivitatakse iga `main`-harule suunatud PR-i puhul. Tõrke korral blokeerib ühendamise. + +| Skript | Kontrollib | Blokeeriv | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `check:docs-all` | Metavärav, mis käitab alltoodud 6 alamväravat järjest | Jah | +| ↳ `check:docs-sync` | CHANGELOG-i / OpenAPI / llm.txt-i versioonide kooskõla | Jah | +| ↳ `check:docs-counts` | Proosas esitatud arvud (pakkujate arv, migratsioonide arv jne) jäävad tegelike arvude põrkmehhanismiga määratud vahemikku | Jah | +| ↳ `check:env-doc-sync` | Iga keskkonnamuutuja failis `.env.example` on dokumentatsiooni tabelis dokumenteeritud ja vastupidi | Jah | +| ↳ `check:deprecated-versions` | Dokumentatsioonis puuduvad aegunud versioonistringid | Jah | +| ↳ `check:doc-links` | Dokumentatsiooni sisemised markdown-lingid viitavad olemasolevatele failidele (`[text]`/`(path)` vormingus) | Jah | +| ↳ `check:fabricated-docs` | Dokumentatsioonis nimetatud marsruudid, keskkonnamuutujad, CLI-käsud, konksude nimed ja failiteed on koodibaasis olemas. Range värav lipuga `--strict`; ilma liputa lubatakse tõrget. | Jah (CI-s lipuga `--strict`) | +| `check:cli-i18n` | CLI-käskude stringid on olemas kõigis i18n-i lokaadifailides | Jah | +| `check:openapi-coverage` | OpenAPI spetsifikatsioon katab vähemalt põrkmehhanismiga määratud minimaalse arvu tegelikke marsruute | Jah | +| `check:openapi-security-tiers` | Turbetasemete annotatsioonid failis `openapi.yaml` on kooskõlas faili `routeGuard.ts` klassifikatsioonidega | **Soovituslik** | +| `check:openapi-routes` | Iga faili `openapi.yaml` tee viitab olemasolevale failile `route.ts` (hallutsinatsioonivastane kontroll) | Jah | +| `check:docs-symbols` | Iga viide kujul `/api/...` failides `docs/**/*.md` viitab olemasolevale failile `route.ts` (hallutsinatsioonivastane kontroll) | Jah | +| `i18n translation drift` | Tõlkimata võtmed i18n-i lokaadifailides — ainult hoiatus | **Soovituslik** | ### Töö: `i18n-ui-coverage` -| Skript | Kontrollib | Blokeeriv | -| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `check-ui-keys-coverage` (tekstisisene) | Kasutajaliidese i18n-i võtmete kaetus on ≥ 65% | Jah | -| `check-ui-value-drift` (tekstisisene) | Ümber kirjutatud ingliskeelne **väärtus** ei jäta endast maha aegunud tõlget | Jah | -| `check-new-key-coverage` (tekstisisene) | **Uus** ingliskeelne võti on tõlgitud igas lokaadis — marker `__MISSING__:` lükatakse tagasi | Jah | -| `check-translation-ratio` | Tegelike tõlgete suhtarv lokaadi kohta (ingliskeelsega identsed / kohatäitega / puuduvad lõppvõtmed väljaspool lubatud loendit) ei tohi ületada faili `config/quality/i18n-translation-baseline.json` väärtust koos lõtkuga | **Nõuandev** | +| Skript | Kontrollib | Blokeeriv | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | +| `check-ui-keys-coverage` (tekstisisene) | Kasutajaliidese i18n-võtmete katvus on ≥ 65% | Jah | +| `check-ui-value-drift` (tekstisisene) | Ümber kirjutatud ingliskeelne **väärtus** ei jäta maha aegunud tõlget | Jah | +| `check-new-key-coverage` (tekstisisene) | **Uus** ingliskeelne võti on tõlgitud igas lokaadis — marker `__MISSING__:` lükatakse tagasi | Jah | +| `check-translation-ratio` | Tegelike tõlgete suhe lokaadi kohta (ingliskeelsega identsed / kohatäite / puuduvad lõppvõtmed väljaspool lubatute loendit) ei tohi ületada faili `config/quality/i18n-translation-baseline.json` väärtust koos lubatud varuga | **Soovituslik** | -Vajab `fetch-depth: 0` — väärtuste triivi värav võrdleb faili `en.json` ühendamise lähtepunktiga. +Vajab sätet `fetch-depth: 0` — väärtuste triivi värav võrdleb faili `en.json` ühendamise lähtepunktiga. #### `check-ui-value-drift` — aegunud tõlgete värav -Tuvastab ühe i18n-i regressiooni, mida teised väravad struktuurselt tuvastada ei suuda: ingliskeelne väärtus +Tuvastab ühe i18n-i regressiooni, mida teised väravad struktuurselt ei näe: ingliskeelne väärtus kirjutatakse ümber, kuid _eelmisest_ ingliskeelsest tekstist tuletatud tõlked jäävad alles, mistõttu muukeelsed kasutajad loevad jätkuvalt enesekindlas sõnastuses, kuid nüüdseks valet teksti. -See jõudis päriselt väljalaskesse. `oauthModal.googleOAuthWarning` kirjutati ümber, kui lisati Antigravity -sisselogimisabiline (#5203); **39 lokaadis 43-st** jäi alles tekst, mis käskis operaatoritel „kopeerida -täielik URL ja kleepida see allapoole” — töövoog, mida selle pakkuja puhul ei saa lõpule viia. See jäi -kuni #8463-ni märkamata, sest: +See jõudis päriselt väljalaskesse. `oauthModal.googleOAuthWarning` kirjutati ümber, kui lisati +Antigravity sisselogimisabiline (#5203); **39 lokaati 43-st** säilitasid teksti, mis käskis operaatoritel +„kopeerida täielik URL ja kleepida see allapoole” — selle pakkuja puhul ei saa seda voogu lõpule viia. +Probleemi ei märgatud enne #8463, sest: -- `sync-ui-keys` lisab ainult võtmed, mis **puuduvad**, mitte neid, mis on **aegunud**; +- `sync-ui-keys` lisab tagantjärele ainult **puuduvad** võtmed, mitte kunagi **aegunud** võtmeid; - `check-ui-keys-coverage` loendab võtme _olemasolu_, seega läheb aegunud tõlge arvesse kaetuna; -- `check-translation-drift` jälgib dokumentatsiooni peegelkoopiaid `docs/i18n//**.md` — - see ei loe kunagi faile `src/i18n/messages/*.json`. Blokeeriv töös `docs-sync-strict` alates - 2026-09 uuesti sünkroonimisest: muuda põhidokumenti → `npm run i18n:run -- --files=` (jaotisepõhine, odav). +- `check-translation-drift` jälgib dokumentatsiooni peegelkoopiaid asukohas `docs/i18n//**.md` — + see ei loe kunagi faile `src/i18n/messages/*.json`. Töö `docs-sync-strict` blokeerib alates + 2026-09 uuesti sünkroonimisest: põhiteabe dokumendi muutmisel käita `npm run i18n:run -- --files=` (jaotisepõhine, kiire). -**Muudatusteadlik, mitte lähtetasemel põhinev.** See võrdleb ühendamisbaasi faili `en.json` +**Muudatuste erinevust arvestav, mitte etalonil põhinev.** See võrdleb liitmislähte `en.json`-i tööpuuga; iga võtme puhul, mille ingliskeelne väärtus muutus, loetakse aegunuks kõik lokaadid, -milles on endiselt muutmata tõlge. See **külmutab tahtlikult olemasoleva võla** — diff ei suuda -näidata, millisest vanast ingliskeelsest tekstist pärineb kaua kasutusel olnud tõlge, seega hindab -kontroll ainult seda, mida praegune muudatus puudutab. Alternatiiv (võtmepõhine räsi lähtetase) -nõuaks umbes 600 KB suurust genereeritud faili, mis on suurimast olemasolevast lähtetasemest -kolm korda suurem ja muutuks iga i18n-i PR-iga. +kus on endiselt muutmata tõlge. See **külmutab tahtlikult olemasoleva võla** — muudatuste +erinevusest ei saa tuvastada, millisest varasemast ingliskeelsest tekstist ammu olemas olnud +tõlge pärineb, seega kontroll hindab ainult seda, mida praegune muudatus puudutab. Alternatiiv +(võtmepõhine räsi-etalon) nõuaks ligikaudu 600 KB suurust genereeritud faili, mis oleks +suurimast olemasolevast etalonist kolm korda suurem ja muutuks iga i18n-i PR-iga. Kontrolli läbimiseks on kaks võimalust: 1. värskendada mõjutatud tõlkeid või -2. määrata nende väärtuseks `__MISSING__:` — käituskeskkond tagastab siis parandatud - ingliskeelse teksti (`src/i18n/request.ts::deepMergeFallback`, #7258) ja võti lisatakse tõlkejärjekorda. +2. määrata nende väärtuseks `__MISSING__:` — käituskeskkond väljastab seejärel + parandatud ingliskeelse teksti (`src/i18n/request.ts::deepMergeFallback`, #7258) ja võti + lisatakse tõlkejärjekorda. Kui stringi **tähendus** muutus, eelista **võtme ümbernimetamist**: uus võti ei saa pärida aegunud tõlget. Seda mustrit kasutati muudatuses #8463. @@ -233,48 +233,48 @@ npm run i18n:check-value-drift:warn # ainult aruanne BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Lõpetab koodiga 0 ja väljundiga `SKIP reason=base-unresolved`, kui baaskataloogi ei saa lugeda -(pinnapealne kloon ilma baasviiteta), järgides kontrolli `check-openapi-breaking` käitumist. +Kui aluskataloogi ei saa lugeda (pinnapealne kloon ilma alusviiteta), lõpetab olekukoodiga 0 +ja väljundiga `SKIP reason=base-unresolved`, järgides `check-openapi-breaking` käitumist. ### Töö: `i18n` -Täielik i18n-i valideerimismaatriks (üks töö iga lokaadi kohta). Kogu töö on nõuandev. +Täielik i18n-i valideerimismaatriks (üks töö lokaadi kohta). Kogu töö on nõuandev. -| Skript | Valideerib | Blokeeriv | -| ------------------------------- | ------------------------------ | ------------------------------------------------------- | -| `validate_translation.py quick` | Tõlke täielikkus lokaadi kaupa | **Nõuandev** (`continue-on-error: true` kogu töö puhul) | +| Skript | Valideerib | Blokeeriv | +| ------------------------------- | ------------------------------- | ------------------------------------------------------- | +| `validate_translation.py quick` | Tõlke täielikkust lokaadi kaupa | **Nõuandev** (`continue-on-error: true` kogu töö puhul) | ### Töö: `pr-test-policy` Käivitatakse ainult tõmbetaotluste puhul. -| Skript | Valideerib | Blokeeriv | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | -| `check:pr-test-policy` | PR-id, mis muudavad tootmiskoodi kataloogides `src/`, `open-sse/`, `electron/` või `bin/`, peavad lisama või värskendama teste (range reegel nr 8) | Jah | -| `check:test-masking` | Muudetud testifailid ei vähenda kontrolllausete netoarvu ega lisa tautoloogiaid `assert.ok(true)` | Jah | -| `check:pr-evidence` | PR-i kirjeldus viitab muudatuse testi-/VPS-tõenditele (automatiseerib range reegli nr 18, otsides vasteid PR-i tekstist — habras, vt tegemata tööde loendit) | Jah | +| Skript | Valideerib | Blokeeriv | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `check:pr-test-policy` | PR-id, mis muudavad tootmiskoodi kaustades `src/`, `open-sse/`, `electron/` või `bin/`, peavad lisama või uuendama teste (range reegel nr 8) | Jah | +| `check:test-masking` | Muudetud testifailid ei vähenda kontrollväidete netoarvu ega lisa tautoloogiaid `assert.ok(true)` | Jah | +| `check:pr-evidence` | PR-i kirjeldus viitab muudatuse testi-/VPS-tõenditele (automatiseerib range reegli nr 18, otsides vasteid PR-i tekstist — habras, vt tööjärge) | Jah | ### Töö: `test-vitest` -Käivitatakse pärast tööd `build`. Tõrke korral blokeerib ühendamise. +Käivitatakse pärast tööd `build`. Ebaõnnestumise korral blokeerib liitmise. -| Testikomplekt | Valideerib | Blokeeriv | +| Testikogum | Valideerib | Blokeeriv | | ---------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `test:vitest` | MCP-server (110 tööriista), autoCombo, vahemälu — vitesti testikäitaja | Jah | | `test:vitest:ui` | Kasutajaliidese komponenditestid — vitesti testikäitaja | **Blokeeriv** — olemasolevad tõrked on failis `vitest.config.ts` sõnaselgelt välistatud; uued tõrked nurjavad töö | -### Igaöised töövood (ajastatud, nõuandvad) +### Öised töövood (ajakava alusel, nõuandvad) -Need käivitatakse croni ajakava alusel (ja käsuga `workflow_dispatch`), mitte kunagi PR-ide puhul. Kõik on nõuandvad. +Need käivitatakse cron-ajakava alusel (ja sündmusega `workflow_dispatch`), mitte kunagi PR-ide puhul. Kõik on nõuandvad. -| Töövoog | Valideerib | Blokeeriv | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | -| `nightly-property` | fast-checki omaduspõhised testid juhusliku algväärtuse ja suure käituskordade arvuga | **Nõuandev** | -| `nightly-resilience` | mälukuhja kasvu kontroll, kaosepõhine tõrgete sisestamine, k6 koormus-/kestustestid | **Nõuandev** | -| `nightly-llm-security` | promptfoo sisestusründe kaitse (blokeerimisrežiim) + garaki sondid (teenusepakkuja saladuse puudumisel jäetakse vahele) | **Nõuandev** | -| `nightly-schemathesis` | OpenAPI lepingu hägustestimine (schemathesis) töötava OmniRoute'i suhtes, kasutades faili `docs/openapi.yaml` — toob esile spetsifikatsioonirikkumised / töötlemata 500-vead (etapp 8 B.4) | **Nõuandev** | -| `nightly-mutation` | Strykeri mutatsioonitestimise skoor kiirete ühiktestide harus — ellujäänud mutandid toovad esile nõrgad kontrolllaused | **Nõuandev** | -| `nightly-compat` | Node'i mootori ühilduvusmaatriks kõigis toetatud `engines.node` vahemikes | **Nõuandev** | +| Töövoog | Valideerib | Blokeeriv | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `nightly-property` | fast-checki omaduspõhised testid juhusliku seemne ja suure käivituskordade arvuga | **Nõuandev** | +| `nightly-resilience` | kuhjamälu kasvu kontroll, kaosepõhine tõrgete sisestamine, k6 koormus-/kestustestid | **Nõuandev** | +| `nightly-llm-security` | promptfoo sisestusrünnete kaitse (blokeerimisrežiim) + garaki sondid (jäetakse teenusepakkuja saladuse puudumisel vahele) | **Nõuandev** | +| `nightly-schemathesis` | OpenAPI lepingu hägustestimine (schemathesis) töötava OmniRoute'i vastu, kasutades faili `docs/openapi.yaml` — toob esile spetsifikatsioonirikkumised / töötlemata 500-vead (etapp 8 B.4) | **Nõuandev** | +| `nightly-mutation` | Strykeri mutatsioonitestimise skoor kiirete ühiktestide harus — ellujäänud mutandid toovad esile nõrgad kontrollväited | **Nõuandev** | +| `nightly-compat` | Node'i mootori ühilduvusmaatriks toetatud `engines.node` vahemikes | **Nõuandev** | --- @@ -536,43 +536,43 @@ automaatselt (MCP↔LSP silla valite ja kontrollite teie); vigane kirje logib ai --- -## Ratsionaliseerimise tööjärg (ROI ülevaatus — 9. etapp, 3. laine) +## Ratsionaliseerimise mahajäämus (ROI ülevaatus — 9. etapp, 3. laine) See inventuur viidi 2026-06-17 vastavusse failiga `ci.yml` (eelmisest versioonist puudusid `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Kooskõlastatud komplekti ROI ülevaatus -tuvastas järgmised ratsionaliseerimise kandidaadid. **Liitmised on mehaanilised CI -muudatused; jõustamised/eemaldamised on operaatorile jäetud poliitikaotsused.** Midagi -alltoodust ei ole veel rakendatud. +`check:codeql-ratchet`, `check:pr-evidence`). Kooskõlastatud kogumi ROI ülevaatus +tuvastas järgmised ratsionaliseerimise kandidaadid. **Ühendamised on mehaanilised CI +muudatused; ümberlülitamised/eemaldamised on käitaja otsustada olevad poliitikavalikud.** Midagi alltoodust +pole veel rakendatud. -**Eespool samuti dokumenteerimata** (nõuandev, nõrk signaal): töö `docs-lint` +**Ülalpool on dokumenteerimata ka** (soovituslik, madala signaaliga): töö `docs-lint` (markdownlint + Vale, kogu tööl `continue-on-error`) ja eraldiseisvad skanneri töövood `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` asub failis -`quality-baseline.json`, kuid pole failis `ci.yml` seotud blokeeriva regressioonilävendiga — mõõdik on +`quality-baseline.json`, kuid pole failis `ci.yml` ühendatud blokeeriva põrkmehhanismiga — mõõdik on praegu orvuks jäänud. -### Liitmine / dubleerimise eemaldamine (mehaaniline, väiksem risk) +### Ühendamine / dubleerimise eemaldamine (mehaaniline, väiksem risk) -Iga kandidaati kontrolliti 2026-06-17 aktiivsete kontrollväravate oleku suhtes (usalda, aga kontrolli); -mitu „ilmset” liitmist peitsid tegelikult võlga ega ole **puhtad** asendused. +Iga kandidaati kontrolliti 2026-06-17 aktiivse kontrollvärava oleku suhtes (usalda, kuid kontrolli); +mitu „ilmselget“ ühendamist osutusid varjatud võlga sisaldavaks ega ole **puhtad** asendused. -- **`check:docs-sync` käivitub kaks korda** — eraldiseisvana töös `lint` ning uuesti `check:docs-all` (`docs-sync-strict`) sees ja husky commit'i-eelses konksus. ✅ **TEHTUD** — eraldiseisev käivitus tööst `lint` eemaldatud. -- **CVE skannimine** — ❌ **EI OLE puhas liitmine.** `audit:deps` nurjub rangelt iga kõrge/kriitilise CVE korral; `check:vuln-ratchet` (osv) nurjub ainult võrreldes baasjoonega toimunud _regressiooni_ korral (praegu 1 MODERATE). Semantika on erinev — `audit:deps` eemaldamine kaotaks absoluutse kõrge/kriitilise taseme kontrollvärava. Säilitada mõlemad. -- **Tsüklite tuvastamine** — ❌ **EI OLE puhas liitmine.** `check:circular-deps` (dpdm) raporteerib **91 tsüklit** (seetõttu on see nõuandev); seda ei saa enne nende lahendamist blokeerivaks muuta ning selle ulatus on laiem kui edukalt läbival, kureeritud kontrollil `check:cycles`. Säilitada `check:cycles` blokeerivana; 91 dpdm-tsükli lahendamine on eraldi tööjärje üksus. -- **Keerukus** — ✅ **TEHTUD** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): üks ESLint-i läbikäik, loendamine `ruleId` järgi, et tsüklomaatilise keerukuse + max-lines ja kognitiivse keerukuse baasjooned jääksid sõltumatuks; eraldiseisvad `check:complexity` / `check:cognitive-complexity` säilivad kohalikuks käivitamiseks võtmega `--update`. -- **`/api` hallutsineerimisvastane kontroll** — ✅ **TEHTUD** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): üks faili `src/app/api` failisüsteemi inventuur, openapi-routes + docs-symbols raporteerivad endiselt sõltumatult; eraldiseisvad kontrollid säilivad kohalikuks käivitamiseks. -- **`check:node-runtime` käivitub 11 töös** — ⚠️ **madal ROI.** Igaüks töötab eraldi täituris ja kontroll võtab <1 s; kogusääst on ~10 s, kuid selle hinnaks oleks odava tööpõhise kaitse kaotamine. Pole muudatustest tingitud vaeva väärt. -- **`typecheck:noimplicit:core` CI lintimisel** — ✅ **lintimise tööst eemaldatud** (oli nõuandev valikuga `continue-on-error`); blokeeriva tüübipinna moodustavad `typecheck:core` + `check:type-coverage`. Kohalik skript säilitati. +- **`check:docs-sync` käivitub kaks korda** — eraldiseisvalt töös `lint` ning uuesti `check:docs-all` (`docs-sync-strict`) sees ja husky commit'i-eelses konksus. ✅ **TEHTUD** — eraldiseisev `lint`-käivitus eemaldatud. +- **CVE-skannimine** — ❌ **EI OLE puhas ühendamine.** `audit:deps` nurjub kohe iga kõrge/kriitilise CVE korral; `check:vuln-ratchet` (osv) nurjub ainult baasjoonega võrreldes toimunud _regressiooni_ korral (praegu 1 MODERATE). Semantika on erinev — `audit:deps` eemaldamine kaotaks absoluutse kõrge/kriitilise taseme kontrollvärava. Säilitage mõlemad. +- **Tsüklite tuvastamine** — ✅ **TEHTUD** (#15159 G-01/G-02). Siinne vana tekst nimetas `check:cycles` kontrolli „roheliseks, kureeritud“ kontrollväravaks ja põhjendas selle blokeerivana hoidmist sellega, et `check:circular-deps` (dpdm) tuvastas 91 tsüklit. See roheline tulemus oli **valepositiivne roheline**: `check:cycles` skannis 5 alamkataloogi (450 faili), sobitas ainult staatilisi `import|export … from` konstruktsioone ning jättis välja kõik `@/` ja `@omniroute/open-sse/` spetsifikaatorid, mistõttu ei suutnud see näha dünaamilise impordi ja aliase tsükleid, mis repositooriumis domineerisid. Parandatud: kontrollvärav läbib nüüd kataloogid `src` + `open-sse` (5023 faili), kogub spetsifikaatorid TypeScripti AST-st (seega läheb `import("…")` arvesse, kuid tüübipositsioonis `typeof import("…")` mitte) ning lahendab tsconfigi `paths`-väärtused. See leiab **14** tsüklit, mitte 0. Kuna 14 olemasolevat tsüklit ei saa kontrollvärava PR-is parandada, on `check:cycles` nüüd **põrkmehhanism** (`--ratchet`, ülempiir `metrics.cycles.value = 14` failis `quality-baseline.json`, `direction: down`) — see blokeerib iga _regressiooni_ ja arv saab ainult väheneda. CI käivitab `npm run check:cycles:ratchet`. Vähendamine toimub koos üksusega **A-01**. `check:circular-deps` (dpdm) jääb laiema teise arvamusena soovituslikuks. +- **Keerukus** — ✅ **TEHTUD** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): üks ESLinti läbimine, loendamine `ruleId` järgi, et tsüklomaatilise keerukuse + maksimaalse ridade arvu ja kognitiivse keerukuse baasjooned püsiksid sõltumatuna; eraldiseisvad `check:complexity` / `check:cognitive-complexity` säilivad kohaliku `--update` jaoks. +- **`/api` hallutsinatsioonivastane kontroll** — ✅ **TEHTUD** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): üks `src/app/api` failisüsteemi inventuur, openapi-routes + docs-symbols annavad endiselt aru sõltumatult; eraldiseisvad kontrollid säilivad kohalikeks käivitusteks. +- **`check:node-runtime` käivitub 11 töös** — ⚠️ **madal ROI.** Igaüks kasutab eraldi käitajat ja kontroll võtab <1 s; kogusääst ~10 s, kuid selle hinnaks oleks odava tööpõhise kaitse kaotamine. Muudatuste vaeva ei väärt. +- **`typecheck:noimplicit:core` CI lintimisel** — ✅ **lint-tööst eemaldatud** (oli soovituslik `continue-on-error`); blokeeriv tüübipind on `typecheck:core` + `check:type-coverage`. Kohalik skript säilitati. -### Jõustamine / otsustamine (operaatori poliitika) +### Ümberlülitamine / otsustamine (käitaja poliitika) -- `check:openapi-security-tiers` (nõuandev) — ❌ **EI OLE puhtalt jõustatav.** See lõpetab koodiga 0, kuid hoiatab, et mitmel `traffic-inspector` marsruudil loendis `LOCAL_ONLY_API_PREFIXES` puudub annotatsioon `x-loopback-only: true`. Selle jõustamiseks tuleb need annotatsioonid esmalt faili `openapi.yaml` lisada. -- `typecheck:noimplicit:core` (nõuandev) — blokeeriv regressioonilävend `check:type-coverage` katab selle suures osas. Muuta regressioonilävendiks või eemaldada üleliigne teine `tsc` läbimine. -- `test:vitest:ui` (nüüd **blokeeriv**) — olemasolevad nurjumised on failis `vitest.config.ts` sõnaselgelt välistatud jälgimiskommentaaridega `// #8618`; uued nurjumised põhjustavad töö nurjumise. -- `check:secrets` (gitleaks, blokeeriv regressioonilävend, mis on fikseeritud 3 dokumenteeritud valepositiivse leiu juures) — lisada need 3 lubatud loendisse, et jõuda 0-ni, või muuta kontroll nõuandvaks. Kattub GitHubi sisseehitatud saladuste skannimisega + `check:public-creds`. -- `check:pr-evidence` (blokeeriv, otsib PR-i kirjelduse proosast vasteid) — suur valepositiivsete tulemuste risk; selle eemaldamine nõrgendab range reegli nr 18 jõustamist, seega on see tegelik poliitikaotsus. -- `semgrep` (nõuandev eraldiseisev kontroll) — kattub OWASP-i kategooriate puhul CodeQL-iga; siduda selle baasjoon regressioonilävendiga või eemaldada. +- `check:openapi-security-tiers` (soovituslik) — ❌ **EI OLE puhtalt ümberlülitatav.** See lõpetab koodiga 0, kuid hoiatab, et mitmel `traffic-inspector` marsruudil jaotises `LOCAL_ONLY_API_PREFIXES` puudub annotatsioon `x-loopback-only: true`. Selle jõustamiseks tuleb esmalt lisada need annotatsioonid faili `openapi.yaml`. +- `typecheck:noimplicit:core` (soovituslik) — blokeeriv `check:type-coverage` põrkmehhanism katab seda suuresti. Muutke see põrkmehhanismiks või eemaldage üleliigne teine `tsc` läbimine. +- `test:vitest:ui` (nüüd **blokeeriv**) — olemasolevad tõrked on failis `vitest.config.ts` selgesõnaliselt välistatud jälgimiskommentaaridega `// #8618`; uued tõrked nurjavad töö. +- `check:secrets` (gitleaks, blokeeriv põrkmehhanism, mis on külmutatud 3 dokumenteeritud valepositiivse tulemuse juures) — lisage need 3 lubatud loendisse, et jõuda nullini, või alandage kontroll soovituslikuks. Kattub GitHubi sisseehitatud saladuste skannimisega + `check:public-creds`. +- `check:pr-evidence` (blokeeriv, otsib PR-i kirjeldusest teksti) — suur valepositiivsete tulemuste risk; eemaldamine nõrgendaks range reegli nr 18 jõustamist, seega on see tõeline poliitikaotsus. +- `semgrep` (soovituslik eraldiseisev kontroll) — kattub OWASP-i kategooriate puhul CodeQL-iga; ühendage selle baasjoon põrkmehhanismiga või eemaldage. --- diff --git a/docs/i18n/et/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/et/docs/compression/COMPRESSION_GUIDE.md index 0d4cd054ffeb..2061db4a4dc3 100644 --- a/docs/i18n/et/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/et/docs/compression/COMPRESSION_GUIDE.md @@ -453,9 +453,7 @@ inglise keele asemel. `messages`-ga kehal kontrollib sisu möödaviik (`shouldBypassCavemanOutputMode()` failis `open-sse/services/compression/outputMode.ts`) kolme viimast sõnumit ja jätab stiilid kogu pöördeks vahele, kui need vastavad selle turvalisuse, pöördumatute toimingute, -selgituste või järjekorra suhtes tundlikele märksõnadele. Möödaviik töötab olenemata sellest, -mis on armatuurlaua **Auto-Clarity Bypass** lüliti (`cavemanOutputMode.autoClarity`) -seadistatud. +selgituste või järjekorra suhtes tundlikele märksõnadele. Möödaviik töötab, kui armatuurlaua **Auto-Clarity Bypass** lüliti (`cavemanOutputMode.autoClarity`) on sisse lülitatud, mis on vaikeväärtus; kui lüliti on välja lülitatud, rakenduvad valitud stiilid ka nendes pööretes. Kui möödaviik laseb pöörde läbi, paigutab `placeSystemInstruction()` (sama fail), mis kunagi ei loo uut `messages[0]`, ploki esimesse neist, mille ta leiab: diff --git a/docs/i18n/et/docs/reference/ENVIRONMENT.md b/docs/i18n/et/docs/reference/ENVIRONMENT.md index 9e25763fc25c..d4d7dc4389b1 100644 --- a/docs/i18n/et/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/et/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Nõutavad saladused -Need **peavad** olema määratud enne esmakordset käivitamist. Ilma nendeta rakendus kas keeldub käivitumast või töötab ebaturvaliste vaikeväärtustega. - -| Muutuja | Nõutav | Vaikeväärtus | Lähtefail | Kirjeldus | -| ---------------------------- | --------------------------- | ---------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Jah** | _(puudub)_ | `src/lib/auth` | Allkirjastab ja kontrollib kõiki halduspaneeli seansiküpsiseid (JWT). Genereerige käsuga `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Jah** | _(puudub)_ | `src/lib/db/apiKeys.ts` | AES-krüptovõti SQLite'is talletatavate API-võtmete väärtuste kaitsmiseks. Genereerige käsuga `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Jah** | `CHANGEME` | Algkäivitusskript | Määrab halduspaneeli esialgse administraatori parooli (vastab `.env.example` vaikeväärtusele — see on jäetud teadlikult ebaturvaliseks, et sundida seda muutma). **Muutke enne esmakordset kasutamist.** Pärast sisselogimist muutke seda menüüs Halduspaneel → Seaded → Turvalisus. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Jah** (tootmiskeskkonnas) | _(määramata)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sisemise Codex Responses WebSocketi silla jagatud saladus. Autendib sillapäringud Electroni/brauseri WS-relee ja OmniRoute'i vahel. ⚠️ **TOOTMISKESKKONNAS NÕUTAV — kui see on määramata, lükatakse kõik WS-silla päringud tagasi.** Genereerige käsuga `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ei | _(giti SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA kesta teenusetöötaja vahemälu sundvärskendamise selgesõnaline identifikaator (#11779); eraldusahela esimene väärtus. | -| `SOURCE_VERSION` | Ei | _(määramata)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Ahela teine väärtus — PaaS-i koostajad (nt Heroku-laadsed) määravad selle juurutatud kinnituseks. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ei | _(tuletatud)_ | `src/shared/components/PwaRegister.tsx` | Koostamisaegne avalik väärtus, mida klient kasutab `/sw.js?v=…` registreerimiseks; tuletatakse kahest eelmisest väärtusest ja seejärel giti SHA-st. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ei (automaatne) | _(automaatselt igal käivitamisel)_ | `src/server/authz/policies/management.ts` | Protsessipõhine saladus, mis tõendab, et usaldusväärne partneri IP-tempel pärineb OmniRoute'i enda HTTP-serverist (`scripts/dev/peer-stamp.mjs`). Authz-vahevara usaldab päringu lokaalsust (LOCAL_ONLY marsruutide lubamine ainult loopback-/LAN-ühendustele) üksnes siis, kui tempel sisaldab seda luba. Genereeritakse automaatselt igal käivitamisel — jätke määramata; fikseerige see ainult mitme protsessiga konfiguratsioonides, kus protsessid peavad templit jagama. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ei | _(määramata)_ | `scripts/dev/peer-stamp.mjs` | Komadega eraldatud pöördprokside IP-aadressid või CIDR-vahemikud, mille `X-Forwarded-For` / `X-Real-IP` päiseid võib IP lubamis-/keelamisloend usaldada lisaks loopback-aadressidele, privaatvõrgu aadressidele ja Cloudflare'i servaserveritele, mida usaldatakse alati. Vajalik ainult mõnel muul avalikul aadressil asuva proksi puhul; vastasel juhul hindab filter proksi enda aadressi. | +Määramata saladused, mis on märgitud **Jah**, luuakse esmakordsel käivitamisel automaatselt (`JWT_SECRET`, `API_KEY_SECRET`) ja püsivad andmekataloogis failis `server.env` — määrake need selgesõnaliselt, kui see kataloog on lühiajaline. Erandiks on `OMNIROUTE_WS_BRIDGE_SECRET`: seda faili `server.env` ei salvestata — serveri sissepääsupunktid loovad igaks käivituskorraks lühiajalise väärtuse, mida saab kasutada ainult protsessisiseselt sild, seega peavad välised silla kliendid selle selgesõnaliselt määrama. + +| Muutuja | Nõutav | Vaikeväärtus | Lähtefail | Kirjeldus | +| ---------------------------- | --------------------------- | ---------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Jah** | _(puudub)_ | `src/lib/auth` | Allkirjastab ja kontrollib kõiki halduspaneeli seansiküpsiseid (JWT). Genereerige käsuga `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Jah** | _(puudub)_ | `src/lib/db/apiKeys.ts` | AES-krüptovõti SQLite'is talletatavate API-võtmete väärtuste kaitsmiseks. Genereerige käsuga `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ei | _(määramata)_ | `src/lib/auth/managementPassword.ts` | Määrab käivitamisel halduspaneeli parooli, kui ühtegi pole veel salvestatud; pärast parooli salvestamist seda ignoreeritakse. npm- ja lähtekoodipaigaldused kopeerivad `.env.example` faili `.env`, seega algavad nad selle avalikult teada oleva kohatäiega `CHANGEME`: asendage see enne esimest käivitust või määrake selles `.env`-is `INITIAL_PASSWORD=` (tühi), et parool luuakse esmakülastusel seadistusviisardis, nagu teeb määramata väärtus (Dockeri image'i vaikesäte). Salvestatud parooli muutke menüüs Halduspaneel → Seaded → Turvalisus või käivitage `omniroute reset-password` (`node bin/reset-password.mjs` lähtekoodi versioonis), määrates `DATA_DIR` serveri andmekataloogile. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Jah** (tootmiskeskkonnas) | _(määramata)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sisemise Codex Responses WebSocketi silla jagatud saladus. Autendib sillapäringud Electroni/brauseri WS-relee ja OmniRoute'i vahel. ⚠️ **TOOTMISKESKKONNAS NÕUTAV — kui see on määramata, lükatakse kõik WS-silla päringud tagasi.** Genereerige käsuga `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ei | _(giti SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA kesta teenusetöötaja vahemälu sundvärskendamise selgesõnaline identifikaator (#11779); eraldusahela esimene väärtus. | +| `SOURCE_VERSION` | Ei | _(määramata)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Ahela teine väärtus — PaaS-i koostajad (nt Heroku-laadsed) määravad selle juurutatud kinnituseks. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ei | _(tuletatud)_ | `src/shared/components/PwaRegister.tsx` | Koostamisaegne avalik väärtus, mida klient kasutab `/sw.js?v=…` registreerimiseks; tuletatakse kahest eelmisest väärtusest ja seejärel giti SHA-st. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ei (automaatne) | _(automaatselt igal käivitamisel)_ | `src/server/authz/policies/management.ts` | Protsessipõhine saladus, mis tõendab, et usaldusväärne partneri IP-tempel pärineb OmniRoute'i enda HTTP-serverist (`scripts/dev/peer-stamp.mjs`). Authz-vahevara usaldab päringu lokaalsust (LOCAL_ONLY marsruutide lubamine ainult loopback-/LAN-ühendustele) üksnes siis, kui tempel sisaldab seda luba. Genereeritakse automaatselt igal käivitamisel — jätke määramata; fikseerige see ainult mitme protsessiga konfiguratsioonides, kus protsessid peavad templit jagama. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ei | _(määramata)_ | `scripts/dev/peer-stamp.mjs` | Komadega eraldatud pöördprokside IP-aadressid või CIDR-vahemikud, mille `X-Forwarded-For` / `X-Real-IP` päiseid võib IP lubamis-/keelamisloend usaldada lisaks loopback-aadressidele, privaatvõrgu aadressidele ja Cloudflare'i servaserveritele, mida usaldatakse alati. Vajalik ainult mõnel muul avalikul aadressil asuva proksi puhul; vastasel juhul hindab filter proksi enda aadressi. | ### Genereerimiskäsud @@ -215,38 +215,38 @@ OmniRoute kasutab kogu püsiva andmesalvestuse jaoks **SQLite’i** (`better-sql ## 4. Turvalisus ja autentimine -| Muutuja | Vaikeväärtus | Lähtefail | Kirjeldus | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sool, mis kombineeritakse masina sõrmejälje loomiseks riistvaraidentifikaatoritega. Isolatsiooni tagamiseks muutke seda iga juurutuse puhul. | -| `OMNIROUTE_CLI_SALT` | _(määramata = juhuslik paigalduspõhine sool, mis talletatakse asukohas `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-sool kohaliku CLI autentimistõendi tuletamiseks. Selle väärtuse määramine uuendab kõiki masina CLI tõendeid ja on alati talletatud soola suhtes prioriteetne. Vaadake `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Määrab seansiküpsistele lipu `Secure`. HTTPS-i taga töötamisel **peab olema `true`**. | -| `REQUIRE_API_KEY` | `false` | API vahevara | Kui väärtus on `true`, peavad kõik `/v1/*` puhverserveri päringud sisaldama kehtivat API-võtit. **See lipp ei piira päringut `GET /v1/models`**, mis järgib selle asemel juhtpaneeli sisselogimise seadistust (`requireAuthForModels`) — seega `/v1/models` vastus `401` EI tähenda, et järelduspäringud on kaitstud. Vaadake `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Võimaldab juhtpaneeli kasutajaliideses kuvada API-võtmete täisväärtusi. Seadistatav juhtpaneeli funktsioonilippude kaudu; jagatud eksemplarides kujutab see endast turvariski. | -| `NO_LOG_API_KEY_IDS` | _(tühi)_ | `src/lib/compliance/index.ts` | Komadega eraldatud API-võtmete ID-d, mille puhul päringute logimine vahele jäetakse (GDPR-i nõuetele vastavuse tagamiseks). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(määramata = piiramatu)_ | `src/shared/utils/apiKeyPolicy.ts` | Päevase päringumahu varuväärtus, mida rakendatakse API-võtmetele, mille veeru `rate_limits` väärtus on null. Määramata või tühi: kaudset piirangut pole (#2289, #11017). `0` tähendab sama (piiramatu). Positiivne täisarv N lubab N päringut päevas, 5N nädalas ja 20N kuus. Vigased mittetühjad väärtused kasutavad varasemaid piiranguid: 1000 päevas, 5000 nädalas ja 20000 kuus. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Päringu keha suurim lubatud maht. Seda piirangut ületavad andmekogumid lükatakse tagasi. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Selle läveni ulatuvad või seda ületavad tegelikud päringukehad võtavad enne JSON-i sõelumist atomaarse protsessisisese raskekaalulise vastuvõtuloa (BYTE-tee, sealhulgas `POST /v1/responses`). Kehtib sama [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) piisava vaba mahu erand nagu struktuurimahukate päringute puhul; maht on endiselt piiratud väärtusega `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), et probleem [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ei korduks. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Vestluse marsruudi jäik piirang, mida rakendatakse piiratud vastuvõtu käigus loetud baitidele, sealhulgas puuduva, vigase või ebaausa `Content-Length`-iga päringutele; piiri ületamisel tagastatakse `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(määramata — päringute arvu piirang puudub)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** see pärandina säilinud päringute ARVU piirang rakendub nüüd ainult siis, kui see on sõnaselgelt määratud. Kui see jäetakse määramata (vaikeseade), reguleerib suure ressursikuluga vestluspäringute vastuvõttu selle asemel `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaatselt tuletatud BAITIDE eelarve, mille suurus põhineb protsessi tegelikul mälupiirangul **ühes protsessis** (üks V8 kuhi). Kaks kattuvat ~750 000 tokeniga `/v1/responses`-päringut katkestavad ~12 GiB kuhjad (#7849) — see on **mälueelarve** hoiatus, mitte toote jäik maksimum 2. Heas seisundis protsess (kuhi on allpool koormuse vähendamise suhtarvu) VÕIB lubada rohkem samaaegseid pikki `/v1/responses`-päringuid väärtuse `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` kaudu. Kümned pikad SSE-kliendid (40–50) sõltuvad kuhjast + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, mitte „maksimaalselt 2“. Selle väärtuse pimesi suurendamine „hosti kasutamiseks“ toob probleemi #7849 tagasi. Suurendage **kuhjade** arvu **N sõltumatu `DATA_DIR`-iga** (#11024); ärge kunagi kasutage ühe SQLite'i faili puhul `replicas>1`. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automaatselt tuletatud)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automaatselt tuletatud vastuvõtu baidieelarve alistamine (25% rangemast V8/cgroupi mälupiirangust, jagatuna 8-kordse ajutise võimendusega). Tuletatud ja sõnaselged väärtused piiratakse vahemikku 8 MiB–2 GiB. Efektiivsest eelarvest suurem keha nurjub kohe veaga `413 body_exceeds_budget`; konkurents eraldi teenindatavate kehade vahel jääb uuesti proovitavaks veaks `503`. 40–50 samaaegset pikka SSE-klienti sõltuvad sellest eelarvest + kuhjast, mitte jäigast piirangust „maksimaalselt 2“. Enne häälestamist vaadake lõpp-punktis `/api/monitoring/health` väärtusi `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kuhjasurve koormuse vähendamise suhtarv (`heapUsed / heap_size_limit`) suure ressursikuluga BAIDI- ja STRUKTUURI-põhise vastuvõtu jaoks (#10183, #10268, #10437). Samaaegne suure ressursikuluga päring, mis ületab `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` väärtuse, lükatakse uuesti proovitava veaga `503` tagasi ainult siis, kui kuhi on SAMUTI selle suhtarvuga võrdne või sellest suurem; heas seisundis kuhja korral võetakse päring vastu terve kuhja lisavaru kaudu. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (vaikimisi `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Piiratud lisamaht terve kuhja kiirtee jaoks (#10437) nii STRUKTUURI kui ka BAIDI puhul (`admitChatRequest`, sealhulgas kehad ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Ilma selle piiranguta möödus iga hõivatud, kuid terve kuhja päring vastuvõtu reguleerimisest ilma ülempiirita. Kui terve kuhja tee kaudu on aktiivsed nii paljud samaaegsed rendid, suunatakse järgmised hõivatud päringud SAMASSE piiratud ootamise / koormuse vähendamise teesse, mida kasutatakse tegeliku kuhjasurve korral. `0` keelab möödaviigu täielikult. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Sõnumite arv, mille puhul liigitatakse vestluspäring suure ressursikuluga päringuks isegi siis, kui selle keha jääb alla baidiläve. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Tööriistade arv, mille puhul liigitatakse vestluspäring suure ressursikuluga päringuks isegi siis, kui selle keha jääb alla baidiläve. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatiivne stringi suurusel põhinev tokenite hinnang, mille puhul liigitatakse päring suure ressursikuluga päringuks; see on vastuvõtukulu lähend, mitte teenusepakkuja arvelduse tokeniseerimine. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (keelatud) | `src/shared/middleware/chatBodyAdmission.ts` | Valikuline sõnaselgelt lubatav vestlusajaloo piirang. Vaikimisi keelatud: sõnumite arv on juurutuspoliitika, mitte päringu universaalne omadus, ning siin piiramine lükkab vestlused lõpliku veaga `413` tagasi enne, kui tihenduskonveier saab need teenindatavaks muuta. Kuhja kasvu piiravad `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ja kuhjasurvepõhine koormuse vähendamine. Määrake positiivne väärtus piiratud mäluga juurutustes, mis vajavad jäika ülempiiri; piiri ületamisel tagastatakse struktureeritud tihendamisnõudega `413`. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Jäik piirang mittevoogedastatavale ülesvoolu vastusele, mis puhverdatakse täielikult mällu. Selle ületamisel tühistatakse ülesvoolu lugeja ja päring nurjub kiiresti, selle asemel et kasvatada piiramatult stringi kuni kuhja ammendumiseni. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimaalne baitide arv, mis edastatakse ülesvoolu vastuse päistest. Eelarve ületamisel jäetakse levinud pöördpuhverserverite päisepiirangutesse jäämiseks madalama prioriteediga päised (nt kohandatud `x-codex-*`, `x-oai-request-id`) välja. Suurema hulga ülesvoolu metaandmete edastamiseks määrake suurem väärtus, arvestades vastuse päiste suuremat mahtu. | -| `CORS_ORIGIN` | _(määramata)_ | `src/server/cors/origins.ts` | Pärandina kasutatav ühe lähtekoha CORS-i lubatud loend. Uute juurutuste puhul eelistage muutujat `CORS_ALLOWED_ORIGINS`. CORS on mõeldud ainult eri lähtekohtadest pärit brauseripõhistele API-klientidele; autenditud juhtpaneeli kirjutuspäringud kasutavad selle asemel sama lähtekoha päringuid koos seansiga seotud CSRF-kaitsega. | -| `CORS_ALLOWED_ORIGINS` | _(määramata)_ | `src/server/cors/origins.ts` | Komadega eraldatud CORS-i lubatud loend. Metamärki ei saadeta, välja arvatud juhul, kui `CORS_ALLOW_ALL=true` on selgesõnaliselt seadistatud. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Ainult arenduseks mõeldud pääsetee mis tahes brauseri `Origin`-päise väärtuse tagastamiseks. Ärge lubage seda jagatud ega tootmisjuurutustes. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokeerib teenusepakkuja kutsed, mis sihivad privaatseid, tagasisideahela või kohaliku lingi IP-vahemikke. Keelake ainult isoleeritud testkeskkondades. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Lubab privaatsetele/kohalikele võrkudele osutavaid teenusepakkuja URL-e (localhost, 192.168.x.x, 10.x.x.x jne). **NÕUTAV isemajutatud teenusepakkujate jaoks** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Kui väärtus on `false`, lükkab juhtpaneel kohalike URL-ide valideerimise tagasi. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Lubab lisada/valideerida kohalikel või privaatsetel aadressidel (127.0.0.1, localhost, LAN, privaatvahemikud) asuvaid teenusepakkujaid — piiratud teenusepakkuja valideerimisteega. **Vaikeväärtus on `true`** (kohalik eelisjärjekorras); ainult avalike aadresside range nõude jõustamiseks määrake `false`. Pilve metaandmete lõpp-punktid (169.254.169.254, metadata.google.internal) jäävad sellest olenemata blokeerituks. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Lubab marsruutidel `/v1/audio/*` (transkriptsioonid, kõne, tõlked) kasutada väljaspool localhosti majutatud OpenAI-ga ühilduvat teenusepakkuja sõlme. Vaikimisi välja lülitatud — heli marsruutimine kaughosti muudab väljamineva liikluse identiteeti ja peab olema operaatori selgesõnaline otsus. Tagasisideahela/privaatsed sõlmed (localhost, 127.0.0.1, 172.16-31.x) on alati lubatud ja see seadistus neid ei mõjuta. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Lubab käsul `POST /v1/rerank` (ja mälumootori tagasisideahela ümberjärjestamise etapil) kasutada väljaspool localhosti majutatud OpenAI-ga ühilduvat teenusepakkuja sõlme — LAN-is asuvat masinat või Tailscale'i partnermasinat, kus töötab TEI, Infinity, vLLM jne. Vaikimisi välja lülitatud — kaughosti marsruutimine muudab väljamineva liikluse identiteeti ja peab olema operaatori selgesõnaline otsus. Tagasisideahela sõlmed (localhost, 127.0.0.1, 172.16-31.x) on alati lubatud ja see seadistus neid ei mõjuta. Kaugsõlmed peavad läbima ka teenusepakkuja väljamineva URL-i poliitika kontrolli (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); pilve metaandmete hostidele ei marsruudita kunagi. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kui OIDC on lubatud, keelab parooliga sisselogimise, et kasutajad saaksid autentida ainult OIDC ühekordse sisselogimise kaudu. Aktsepteeritakse ka lihtsat aliast `OIDC_DISABLE_PASSWORD_LOGIN`; sama võtmega juhtpaneeli funktsioonilipp on ülimuslik. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Muutuja `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` lihtne alias (#10889). | +| Muutuja | Vaikeväärtus | Lähtefail | Kirjeldus | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sool, mis kombineeritakse masina sõrmejälje loomiseks riistvaraidentifikaatoritega. Isolatsiooni tagamiseks muutke seda iga juurutuse puhul. | +| `OMNIROUTE_CLI_SALT` | _(määramata = juhuslik paigalduspõhine sool, mis talletatakse asukohas `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-sool kohaliku CLI autentimistõendi tuletamiseks. Selle väärtuse määramine uuendab kõiki masina CLI tõendeid ja on alati talletatud soola suhtes prioriteetne. Vaadake `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Määrab seansiküpsistele lipu `Secure`. HTTPS-i taga töötamisel **peab olema `true`**. | +| `REQUIRE_API_KEY` | `false` | API vahevara | Kui väärtus on `true`, peavad kõik `/v1/*` puhverserveri päringud sisaldama kehtivat API-võtit. **See lipp ei piira päringut `GET /v1/models`**, mis järgib selle asemel juhtpaneeli sisselogimise seadistust (`requireAuthForModels`) — seega `/v1/models` vastus `401` EI tähenda, et järelduspäringud on kaitstud. Vaadake `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Võimaldab juhtpaneeli kasutajaliideses kuvada API-võtmete täisväärtusi. Seadistatav juhtpaneeli funktsioonilippude kaudu; jagatud eksemplarides kujutab see endast turvariski. | +| `NO_LOG_API_KEY_IDS` | _(tühi)_ | `src/lib/compliance/index.ts` | Komadega eraldatud API-võtmete ID-d, mille puhul päringute logimine vahele jäetakse (GDPR-i nõuetele vastavuse tagamiseks). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(määramata = piiramatu)_ | `src/shared/utils/apiKeyPolicy.ts` | Päevase päringumahu varuväärtus, mida rakendatakse API-võtmetele, mille veeru `rate_limits` väärtus on null. Määramata või tühi: kaudset piirangut pole (#2289, #11017). `0` tähendab sama (piiramatu). Positiivne täisarv N lubab N päringut päevas, 5N nädalas ja 20N kuus. Vigased mittetühjad väärtused kasutavad varasemaid piiranguid: 1000 päevas, 5000 nädalas ja 20000 kuus. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Päringu keha suurim lubatud maht. Seda piirangut ületavad andmekogumid lükatakse tagasi. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Selle läveni ulatuvad või seda ületavad tegelikud päringukehad võtavad enne JSON-i sõelumist atomaarse protsessisisese raskekaalulise vastuvõtuloa (BYTE-tee, sealhulgas `POST /v1/responses`). Kehtib sama [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) piisava vaba mahu erand nagu struktuurimahukate päringute puhul; maht on endiselt piiratud väärtusega `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), et probleem [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ei korduks. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Vestluse marsruudi jäik piirang, mida rakendatakse piiratud vastuvõtu käigus loetud baitidele, sealhulgas puuduva, vigase või ebaausa `Content-Length`-iga päringutele; piiri ületamisel tagastatakse `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(määramata — päringute arvu piirang puudub)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** see pärandina säilinud päringute ARVU piirang rakendub nüüd ainult siis, kui see on sõnaselgelt määratud. Kui see jäetakse määramata (vaikeseade), reguleerib suure ressursikuluga vestluspäringute vastuvõttu selle asemel `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaatselt tuletatud BAITIDE eelarve, mille suurus põhineb protsessi tegelikul mälupiirangul **ühes protsessis** (üks V8 kuhi). Kaks kattuvat ~750 000 tokeniga `/v1/responses`-päringut katkestavad ~12 GiB kuhjad (#7849) — see on **mälueelarve** hoiatus, mitte toote jäik maksimum 2. Heas seisundis protsess (kuhi on allpool koormuse vähendamise suhtarvu) VÕIB lubada rohkem samaaegseid pikki `/v1/responses`-päringuid väärtuse `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` kaudu. Kümned pikad SSE-kliendid (40–50) sõltuvad kuhjast + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, mitte „maksimaalselt 2“. Selle väärtuse pimesi suurendamine „hosti kasutamiseks“ toob probleemi #7849 tagasi. Suurendage **kuhjade** arvu **N sõltumatu `DATA_DIR`-iga** (#11024); ärge kunagi kasutage ühe SQLite'i faili puhul `replicas>1`. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automaatselt tuletatud)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automaatselt tuletatud vastuvõtu baidieelarve alistamine (25% rangemast V8/cgroupi mälupiirangust, jagatuna 8-kordse ajutise võimendusega). Tuletatud ja sõnaselged väärtused piiratakse vahemikku 8 MiB–2 GiB. Efektiivsest eelarvest suurem keha nurjub kohe veaga `413 body_exceeds_budget`; konkurents eraldi teenindatavate kehade vahel jääb uuesti proovitavaks veaks `503`. 40–50 samaaegset pikka SSE-klienti sõltuvad sellest eelarvest + kuhjast, mitte jäigast piirangust „maksimaalselt 2“. Enne häälestamist vaadake lõpp-punktis `/api/monitoring/health` väärtusi `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kuhjasurve koormuse vähendamise suhtarv (`heapUsed / heap_size_limit`) suure ressursikuluga BAIDI- ja STRUKTUURI-põhise vastuvõtu jaoks (#10183, #10268, #10437). Samaaegne suure ressursikuluga päring, mis ületab `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` väärtuse, lükatakse uuesti proovitava veaga `503` tagasi ainult siis, kui kuhi on SAMUTI selle suhtarvuga võrdne või sellest suurem; heas seisundis kuhja korral võetakse päring vastu terve kuhja lisavaru kaudu. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (vaikimisi `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Piiratud lisamaht terve kuhja kiirtee jaoks (#10437) nii STRUKTUURI kui ka BAIDI puhul (`admitChatRequest`, sealhulgas kehad ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Ilma selle piiranguta möödus iga hõivatud, kuid terve kuhja päring vastuvõtu reguleerimisest ilma ülempiirita. Kui terve kuhja tee kaudu on aktiivsed nii paljud samaaegsed rendid, suunatakse järgmised hõivatud päringud SAMASSE piiratud ootamise / koormuse vähendamise teesse, mida kasutatakse tegeliku kuhjasurve korral. `0` keelab möödaviigu täielikult. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Sõnumite arv, mille puhul liigitatakse vestluspäring suure ressursikuluga päringuks isegi siis, kui selle keha jääb alla baidiläve. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Tööriistade arv, mille puhul liigitatakse vestluspäring suure ressursikuluga päringuks isegi siis, kui selle keha jääb alla baidiläve. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatiivne stringi suurusel põhinev tokenite hinnang, mille puhul liigitatakse päring suure ressursikuluga päringuks; see on vastuvõtukulu lähend, mitte teenusepakkuja arvelduse tokeniseerimine. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (keelatud) | `src/shared/middleware/chatBodyAdmission.ts` | Valikuline sõnaselgelt lubatav vestlusajaloo piirang. Vaikimisi keelatud: sõnumite arv on juurutuspoliitika, mitte päringu universaalne omadus, ning siin piiramine lükkab vestlused lõpliku veaga `413` tagasi enne, kui tihenduskonveier saab need teenindatavaks muuta. Kuhja kasvu piiravad `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ja kuhjasurvepõhine koormuse vähendamine. Määrake positiivne väärtus piiratud mäluga juurutustes, mis vajavad jäika ülempiiri; piiri ületamisel tagastatakse struktureeritud tihendamisnõudega `413`. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Jäik piirang mittevoogedastatavale ülesvoolu vastusele, mis puhverdatakse täielikult mällu. Selle ületamisel tühistatakse ülesvoolu lugeja ja päring nurjub kiiresti, selle asemel et kasvatada piiramatult stringi kuni kuhja ammendumiseni. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimaalne baitide arv, mis edastatakse ülesvoolu vastuse päistest. Eelarve ületamisel jäetakse levinud pöördpuhverserverite päisepiirangutesse jäämiseks madalama prioriteediga päised (nt kohandatud `x-codex-*`, `x-oai-request-id`) välja. Suurema hulga ülesvoolu metaandmete edastamiseks määrake suurem väärtus, arvestades vastuse päiste suuremat mahtu. | +| `CORS_ORIGIN` | _(määramata)_ | `src/server/cors/origins.ts` | Pärandina kasutatav ühe lähtekoha CORS-i lubatud loend. Uute juurutuste puhul eelistage muutujat `CORS_ALLOWED_ORIGINS`. CORS on mõeldud ainult eri lähtekohtadest pärit brauseripõhistele API-klientidele; autenditud juhtpaneeli kirjutuspäringud kasutavad selle asemel sama lähtekoha päringuid koos seansiga seotud CSRF-kaitsega. | +| `CORS_ALLOWED_ORIGINS` | _(määramata)_ | `src/server/cors/origins.ts` | Komadega eraldatud CORS-i lubatud loend. Metamärki ei saadeta, välja arvatud juhul, kui `CORS_ALLOW_ALL=true` on selgesõnaliselt seadistatud. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Ainult arenduseks mõeldud pääsetee mis tahes brauseri `Origin`-päise väärtuse tagastamiseks. Ärge lubage seda jagatud ega tootmisjuurutustes. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Pärandalias. Selle juhtpaneeli lüliti (andmebaasi tühistus) loetakse enne seda keskkonnaväärtust; `false`, `0`, `no` või `off` kummaski lülitab hostikontrollid välja samamoodi nagu `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, isegi kui selle lipu juhtpaneeli lüliti on salvestatud välja; muud väärtused jätavad kaitse kahe järgneva lipu hooleks. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Lülitab välja väljamineva URL-i kaitse hostikontrollid, pilve metaandmete blokeering kaasa arvatud (kogu 169.254.0.0/16, metadata.google.internal), teenusepakkuja teedel, mis järgivad kaitse režiimi: URL-i valideerimine, mudelite avastamine, teenusepakkuja sõlmede baas-URL-id (kaasa arvatud kaugsed ümberjärjestamise sõlmed) ja proksi-varu test, teiste teenusepakkujaga seotud väljaminevate teede hulgas nagu kaugpiltide ja -meediumi hankimine. Lubab ka privaatsed webhooki sihtkohad, välja arvatud pilve metaandmete/kohaliku lingi lõpp-punktid, mis jäävad blokeerituks ka selle lipu sisselülitamisel. `BaseExecutor.execute()` kaudu saadetud vestluspäringud säilitavad oma pilve metaandmete kontrolli. Kohalikud teenusepakkujad töötavad vaikeväärtustega: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (vaikimisi `true`) lubab kohaliku ja LAN-i URL-id juba valideerimis-, avastamis- ja teenusepakkuja sõlmede teedel; proksi-varu test ja privaatsed webhooki sihtkohad arvestavad ainult seda lippu ja blokeerivad kohalikke/LAN-i hoste, kuni lipp on välja lülitatud. Tühistab väärtuse `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Juhtpaneeli lülitilt salvestatud väärtus on sellest muutujast tähtsam. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Lubab teenusepakkuja URL-id kohalikel/privaatsetel aadressitel (127.0.0.1, localhost, LAN, privaatvahemikud) teenusepakkuja valideerimiseks, mudelite avastamiseks, vestluseks ja muudeks teenusepakkujaga seotud väljaminevateks teedeks, mida kaitse režiim reguleerib. **Vaikimisi `true`** (kohalik eesõigus): kaitse blokeerib siis pilve metaandmete lõpp-punktid — kogu 169.254.0.0/16 pluss teadaolevad metaandmete hostinimed (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Määrake `false` ainult-avalikuks režiimiks, mis blokeerib ka privaatsed ja tagasisideahela hostid. Kaitse kontrollib hostinime või IP-literaali nii, nagu kirjutatud, enne mis tahes DNS-päringut; ainult-avalikus režiimis lahendab turvalise fetch-i mähis hostinime lisaks lahti ja keeldub igast privaatsest vastusest. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` lülitab need kontrollid välja teedel, mis selle rea loetelu nimetab. Sisseehitatud kohalikud teenusepakkujad, nagu LM Studio, Ollama, vLLM, Llamafile ja Triton, jätavad võtmete valideerimisel ja vestluses kõik hostikontrollid vahele. Juhtpaneeli lülitilt salvestatud väärtus on sellest muutujast tähtsam. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Lubab marsruutidel `/v1/audio/*` (transkriptsioonid, kõne, tõlked) kasutada väljaspool localhosti majutatud OpenAI-ga ühilduvat teenusepakkuja sõlme. Vaikimisi välja lülitatud — heli marsruutimine kaughosti muudab väljamineva liikluse identiteeti ja peab olema operaatori selgesõnaline otsus. Tagasisideahela/privaatsed sõlmed (localhost, 127.0.0.1, 172.16-31.x) on alati lubatud ja see seadistus neid ei mõjuta. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Lubab käsul `POST /v1/rerank` (ja mälumootori tagasisideahela ümberjärjestamise etapil) kasutada väljaspool localhosti majutatud OpenAI-ga ühilduvat teenusepakkuja sõlme — LAN-is asuvat masinat või Tailscale'i partnermasinat, kus töötab TEI, Infinity, vLLM jne. Vaikimisi välja lülitatud — kaughosti marsruutimine muudab väljamineva liikluse identiteeti ja peab olema operaatori selgesõnaline otsus. Tagasisideahela sõlmed (localhost, 127.0.0.1, 172.16-31.x) on alati lubatud ja see seadistus neid ei mõjuta. Kaugsõlme baas-URL peab läbima ka teenusepakkuja väljamineva URL-i poliitika kontrolli (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kui OIDC on lubatud, keelab parooliga sisselogimise, et kasutajad saaksid autentida ainult OIDC ühekordse sisselogimise kaudu. Aktsepteeritakse ka lihtsat aliast `OIDC_DISABLE_PASSWORD_LOGIN`; sama võtmega juhtpaneeli funktsioonilipp on ülimuslik. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Muutuja `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` lihtne alias (#10889). | ### Turvalisuse tugevdamise kontrollnimekiri diff --git a/docs/i18n/fa/CONTRIBUTING.md b/docs/i18n/fa/CONTRIBUTING.md index ab74e21fd0b8..e1efa898da31 100644 --- a/docs/i18n/fa/CONTRIBUTING.md +++ b/docs/i18n/fa/CONTRIBUTING.md @@ -388,16 +388,16 @@ docs/ # Documentation - [ ] بررسی Lint با موفقیت انجام میشود (`npm run lint`) - [ ] Build با موفقیت انجام میشود (`npm run build`) - [ ] نوعهای TypeScript برای توابع و رابطهای عمومی جدید اضافه شدهاند -- [ ] هیچ secret یا مقدار fallback بهصورت hardcoded وجود ندارد -- [ ] اطلاعات احراز هویت عمومی upstream از طریق `resolvePublicCred()` تعبیه شدهاند (نگاه کنید به [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) و هرگز بهصورت مقادیر literal نیستند -- [ ] پاسخهای خطا از طریق `buildErrorBody()` / `sanitizeErrorMessage()` هدایت میشوند — هیچ stack trace خامی در بدنه پاسخها وجود ندارد (نگاه کنید به [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] فرمانهای Shell (`exec` / `spawn`) مقادیر زمان اجرا را از طریق `env` ارسال میکنند، نه از طریق string interpolation +- [ ] هیچ مقدار محرمانه یا مقدار جایگزینی بهصورت hardcoded وجود ندارد +- [ ] اطلاعات احراز هویت عمومی upstream از طریق `resolvePublicCred()` تعبیه شدهاند (به [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) مراجعه کنید) و هرگز بهصورت literal درج نشدهاند +- [ ] پاسخهای خطا از طریق `buildErrorBody()` / `sanitizeErrorMessage()` پردازش میشوند — هیچ stack trace خامی در بدنه پاسخها وجود ندارد (به [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) مراجعه کنید) +- [ ] فرمانهای Shell (`exec` / `spawn`) مقادیر زمان اجرا را از طریق `env` ارسال میکنند، نه از طریق درونیابی رشتهای - [ ] همه ورودیها با schemaهای Zod اعتبارسنجی شدهاند -- [ ] برای تغییرات قابل مشاهده توسط کاربر، **fragment** مربوط به Changelog در مسیر `changelog.d/{features|fixes|maintenance}/-.md` اضافه شده است (نگاه کنید به [`changelog.d/README.md`](./changelog.d/README.md)) — فایل `CHANGELOG.md` را مستقیماً ویرایش **نکنید**؛ fragmentها هنگام انتشار تجمیع میشوند و هرگز میان PRها تداخل ایجاد نمیکنند +- [ ] برای تغییرات قابل مشاهده برای کاربر، یک **قطعه** Changelog در مسیر `changelog.d/{features|fixes|maintenance}/-.md` اضافه شده است (به [`changelog.d/README.md`](./changelog.d/README.md) مراجعه کنید) — فایل `CHANGELOG.md` را مستقیماً ویرایش **نکنید**؛ قطعهها هنگام انتشار تجمیع میشوند و هرگز میان PRها تداخل ایجاد نمیکنند - [ ] مستندات بهروزرسانی شدهاند (در صورت نیاز) -- [ ] هیچ هشدار جدیدی از CodeQL / Secret-Scanning ایجاد نشده است، یا هر مورد با توجیه فنی و ارجاع به سند مرتبط در `docs/security/` رد شده است -- [ ] مسیرهایی که فرایندهای فرزند ایجاد میکنند (`/api/mcp/`، `/api/cli-tools/runtime/`) در `src/server/authz/routeGuard.ts` بهعنوان `isLocalOnlyPath()` طبقهبندی شدهاند — نگاه کنید به [قانون قطعی شماره ۱۵](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] هیچ trailer از نوع `Co-Authored-By` در پیامهای commit وجود ندارد — commitها باید صرفاً تحت هویت Git مالک repository ثبت شوند (قانون قطعی شماره ۱۶) +- [ ] هیچ هشدار جدید CodeQL / Secret-Scanning ایجاد نشده است، یا هر هشدار با توجیه فنی و ارجاع به سند مرتبط در `docs/security/` رد شده است +- [ ] مسیرهایی که فرایندهای فرزند ایجاد میکنند (`/api/mcp/`، `/api/cli-tools/runtime/`) در `src/server/authz/routeGuard.ts` بهعنوان `isLocalOnlyPath()` طبقهبندی شدهاند — به [قانون سختگیرانه شماره ۱۵](docs/security/ROUTE_GUARD_TIERS.md) مراجعه کنید +- [ ] هیچ trailer از نوع `Co-authored-by` برای AI/bot در پیامهای commit وجود ندارد (قانون سختگیرانه شماره ۱۶) — از همکاران انسانی که از کارشان دوباره استفاده شده است، با trailerهای استاندارد `Co-authored-by: Name ` قدردانی میشود --- diff --git a/docs/i18n/fa/docs/architecture/QUALITY_GATES.md b/docs/i18n/fa/docs/architecture/QUALITY_GATES.md index 33a51dd23b0c..516d10d706aa 100644 --- a/docs/i18n/fa/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/fa/docs/architecture/QUALITY_GATES.md @@ -17,121 +17,117 @@ ## موجودی گیتها و پروفایلهای اجرا -### پذیرش کاندیدا - -گردشکارهای CI و Quality Gates هرکدام یک نتیجهٔ پایدار منتشر میکنند: `Gate / CI` و -`Gate / Quality`. سیاست پذیرش نسخهبندیشدهٔ آنها، تمام jobهای بالادستی را بهعنوان -الزامی یا توصیهای فهرست میکند. یک job الزامی و قابلاعمال باید با موفقیت اجرا شود: -نتایج مفقود، لغوشده، ردشده، در انتظار و ناشناخته نمیتوانند وضعیت PASS را اثبات کنند. -یک طبقهبندی معتبرِ صرفاً مستندات یا صرفاً کاتالوگ میتواند یک مسیر کد را غیرقابلاعمال -کند؛ یک PR پیشنویس، کاندیدای پذیرفتهشده نیست. برچسب `hotfix` الزام ارائهٔ شواهد را -لغو نمیکند. - -هر دو گردشکار، PRها و pushها به شاخههای main/release، اجرای دستی و رویدادهای -merge-group را پوشش میدهند. push، dispatch و merge-group مجموعهٔ کامل را اجرا -میکنند. forkها و merge groupها برای jobهایی که در حالت عادی runnerهای self-hosted -را انتخاب میکنند، از runnerهای میزبانیشده استفاده میکنند؛ پیش از عرضه باید از -کافیبودن ظرفیت میزبانیشده اطمینان حاصل شود. - -هر رسید JSON، SHA مربوط به checkout، اجرای گردشکار و تلاش را مشخص میکند. -CLI ناهماهنگی میان SHA مربوط به checkout/event را رد میکند. آزمونهای گردشکار، -عضویت در سیاست را به فهرست `needs` مربوط به job نتیجه متصل میکنند تا یک مسیر جدید -یا حذفشده نتواند بیسروصدا ناپدید شود. رسیدها گردشکار خود را پوشش میدهند، نه -انتشار، استقرار یا سازوکارهای داخلی یک اسکنر توصیهای موجود را. فعالسازی هر دو نام -بررسی در قوانین شاخه، یک تغییر مدیریتی جداگانه است؛ افزودن این jobها بهخودیخود از -یک شاخه محافظت نمیکند. +### پذیرش نامزد + +گردشکارهای CI و Quality Gates هرکدام یک نتیجه پایدار منتشر میکنند: `Gate / CI` و +`Gate / Quality`. خطمشی پذیرش نسخهبندیشده آنها، همه jobهای بالادستی را بهعنوان +الزامی یا مشورتی فهرست میکند. هر job الزامیِ قابلاعمال باید با موفقیت اجرا شود: +نتایج مفقود، لغوشده، ردشده، در انتظار و ناشناخته نمیتوانند وضعیت PASS را محرز کنند. +یک طبقهبندی معتبر docs-only یا catalog-only میتواند یک مسیر کد را غیرقابلاعمال کند؛ +یک PR پیشنویس بهعنوان نامزد پذیرفته نمیشود. برچسب `hotfix` نیاز به شواهد را لغو نمیکند. + +هر دو گردشکار، PRها و pushها به شاخههای main/release، اجرای دستی و +رویدادهای merge-group را پوشش میدهند. push، dispatch و merge-group انتخاب کامل را اجرا میکنند. forkها +و merge groupها برای jobهایی که در حالت عادی runnerهای self-hosted را انتخاب میکنند، از runnerهای hosted +استفاده میکنند؛ پیش از عرضه، باید کافیبودن ظرفیت hosted تأیید شود. + +هر رسید JSON، SHA مربوط به checkout، اجرای گردشکار و attempt را مشخص میکند. +CLI عدم تطابق SHA میان checkout/event را رد میکند. آزمونهای گردشکار، عضویت در خطمشی را +به فهرست `needs` مربوط به job نتیجه متصل میکنند تا یک مسیر جدید یا حذفشده نتواند بیسروصدا ناپدید شود. +رسیدها گردشکار خود را پوشش میدهند، نه انتشار، استقرار یا جزئیات داخلی +یک اسکنر مشورتی موجود را. فعالکردن هر دو نام check در قوانین شاخه، +یک تغییر مدیریتی جداگانه است؛ افزودن این jobها بهخودیخود از یک شاخه محافظت نمیکند. ### موجودی اسکن ایستا -موجودی نسخهبندیشدهٔ aliasهای npm و عضویت در اسکن ایستا در -`config/quality/gate-manifest.json` قرار دارند. برای اعتبارسنجی نام اسکریپتها و -دستورهای دقیق در برابر `package.json`، دستور `npm run check:gate-manifest` را اجرا -کنید؛ افزودهشدن، حذفشدن و تغییر دستور باعث شکست هم hook محلی و هم jobهای -طبقهبندی تغییرات در CI میشود. یک alias، job گردشکار، نمونهٔ matrix یا test case -نیست: این شمارشها نباید همارز یکدیگر ارائه شوند. - -برای بررسی aliasهای انتخابشده بدون اجرای آنها، از -`npm run quality:scan -- --list` یا `npm run quality:scan:fast -- --list` -استفاده کنید. runner نقطهٔ ورود npm را فراخوانی میکند، بنابراین runtime آن -(از جمله Bun در محلهایی که پیکربندی شده است) حفظ میشود. manifest، aliasهای خارج -از این پروفایلها را بهعنوان مواردی که جداگانه فراخوانی میشوند ثبت میکند و -دستورهای نگهداری در پروفایلهای اسکن فقطخواندنی ممنوع هستند. - -این پروفایلها فقط اسکن ایستا را پوشش میدهند. آنها آزمونهای محصول، پوشش، -بستهبندی، بررسیهای خارجی یا پذیرش کامل انتشار یک کاندیدا را تأیید نمیکنند. -پذیرش گردشکار از `config/quality/admission-policy.json` پیوندشده و -`scripts/quality/admission-verdict.mjs` استفاده میکند. پروفایلهای release-observer -جدا باقی میمانند؛ بررسیها و رسیدهای قابلاعمال آنها را مستقل بررسی کنید. موجودی -متنی زیر صرفاً یک مرجع است و اثبات نمیکند که یک گیت واقعاً اجرا شده است. - -اسکریپتها در `scripts/check/` (گیتهای سیاست) و `scripts/quality/` (موتور ratchet) -قرار دارند. منبع حقیقت CI فایل `.github/workflows/ci.yml` است. +موجودی نسخهبندیشده npm-alias و عضویت در اسکن ایستا در +`config/quality/gate-manifest.json` قرار دارند. برای اعتبارسنجی نام اسکریپتها و فرمانهای دقیق +در برابر `package.json`، دستور `npm run check:gate-manifest` را اجرا کنید؛ افزودنها، حذفها و +انحراف فرمانها باعث شکست hook محلی و jobهای طبقهبندی تغییر در CI میشوند. +یک alias، job گردشکار، نمونه matrix یا test case نیست: این تعدادها نباید +بهعنوان موارد قابلجایگزینی با یکدیگر ارائه شوند. + +برای بررسی aliasهای انتخابشده بدون اجرای آنها، از `npm run quality:scan -- --list` یا +`npm run quality:scan:fast -- --list` استفاده کنید. runner، نقطه ورود npm را فراخوانی میکند، +بنابراین runtime آن (از جمله Bun در محلهایی که پیکربندی شده است) حفظ میشود. +manifest، aliasهای خارج از این پروفایلها را بهعنوان مواردی که جداگانه فراخوانی میشوند ثبت میکند و +فرمانهای نگهداری در پروفایلهای اسکن فقطخواندنی ممنوع هستند. + +این پروفایلها فقط اسکن ایستا را پوشش میدهند. آنها آزمونهای محصول، +پوشش، بستهبندی، بررسیهای خارجی یا پذیرش کامل انتشار یک نامزد را تأیید نمیکنند. +پذیرش گردشکار از `config/quality/admission-policy.json` و +`scripts/quality/admission-verdict.mjs` پیوندشده استفاده میکند. پروفایلهای release-observer جدا باقی میمانند؛ +بررسیها و رسیدهای قابلاعمال آنها را مستقل بررسی کنید. موجودی متنی +زیر یک مرجع است، نه مدرکی دال بر اینکه یک گیت واقعاً اجرا شده است. + +اسکریپتها در `scripts/check/` (گیتهای خطمشی) و `scripts/quality/` (موتور ratchet) قرار دارند. +منبع حقیقت CI، فایل `.github/workflows/ci.yml` است. ### مسیر سریع PR انتشار (`quality.yml`) -`.github/workflows/quality.yml` مکمل CI برای PRهای main/release، pushهای شاخهٔ -محافظتشده، dispatch و merge groupها است. PRها از بررسیهای سریعِ فیلترشده بر اساس -مسیر استفاده میکنند. build تکراری که بهطور دائمی غیرفعال بود حذف شده است؛ -بررسیهای واقعی build/package/boot همچنان در CI باقی ماندهاند. - -| Job | دامنه | مسدودکننده | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | PRهای مستندات/کد؛ ارجاعات مستندات API و docs-all | بله | -| `Fast Quality Gates` | PRهای کد؛ بررسیهای ایستا، typecheck، typecheck داشبورد و آزمونهای واحد تحتتأثیر | بله | -| `Forgotten sibling tests` | PRهای کد؛ ردیابی ماژولهای تغییریافته تا مصرفکنندگان ایستا و آزمونهای sibling کاندیدا؛ مسیرهای barrel و dynamic-import بهعنوان تشخیصهای توصیهای، همراه با استثناهای ارجاعشدهٔ allowlist گزارش میشوند | **توصیهای** | -| `Vitest (fast-path)` | PRهای کد؛ مجموعهٔ سریع vitest | بله | -| `Unit Tests fast-path` | PRهای کد؛ مجموعهٔ آزمون واحد ۴-shard | بله | -| `No new ESLint warnings` | PRهای کد؛ محافظ lint آگاه از suppressionها | بله، شامل forkها | -| `Merge integrity (changelog + generated skills)` | PRهای غیرپیشنویس؛ همگامسازی changelog و skillهای تولیدشده | بله، شامل forkها | +`.github/workflows/quality.yml` مکمل CI برای PRهای main/release، pushهای شاخه محافظتشده، +dispatch و merge groupها است. PRها از بررسیهای سریع فیلترشده بر اساس مسیر استفاده میکنند. build تکراری که برای همیشه +غیرفعال بود حذف شده است؛ بررسیهای واقعی build/package/boot همچنان در CI باقی میمانند. + +| Job | دامنه | مسدودکننده | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `Docs Gates (fast-path)` | PRهای docs/code؛ ارجاعهای مستندات API و docs-all | بله | +| `Fast Quality Gates` | PRهای کد؛ بررسیهای ایستا، typecheck، typecheck داشبورد و آزمونهای واحد متأثر | بله | +| `Forgotten sibling tests` | PRهای کد؛ ردیابی ماژولهای تغییریافته تا مصرفکنندگان ایستا و آزمونهای sibling نامزد؛ مسیرهای barrel و dynamic-import بهعنوان تشخیصهای مشورتی گزارش میشوند و استثناهای allowlist ارجاعشده را نیز شامل میشوند | **مشورتی** | +| `Vitest (fast-path)` | PRهای کد؛ مجموعه سریع vitest | بله | +| `Unit Tests fast-path` | PRهای کد؛ مجموعه آزمون واحد 4-shard | بله | +| `No new ESLint warnings` | PRهای کد؛ محافظ lint آگاه از suppressionها | بله، شامل forkها | +| `Merge integrity (changelog + generated skills)` | PRهای غیرپیشنویس؛ همگامسازی changelog و skillهای تولیدشده | بله، شامل forkها | #### گزارش آزمونهای sibling فراموششده -`npm run check:forgotten-sibling-tests` از resolver مربوط به import که پشت نگاشت -تأثیر آزمون قرار دارد، مجدداً استفاده میکند. برای هر ماژول production تغییریافته، -هنگامی که آزمون کاندیدا در diff مربوط به pull request وجود نداشته باشد، زنجیرههای -قطعی `changed module/symbol -> static consumer -> candidate sibling test` را گزارش -میکند. خلاصهٔ Markdown و نتیجهٔ JSON بهعنوان artifact گردشکار -`forgotten-sibling-tests` برای کالیبراسیون پیش از هر عرضهٔ مسدودکننده نگهداری -میشوند. +`npm run check:forgotten-sibling-tests` از resolver مربوط به import که پشت نگاشت تأثیر آزمون قرار دارد، دوباره استفاده میکند. +برای هر ماژول production تغییریافته، زمانی که آزمون نامزد +در diff مربوط به pull request وجود نداشته باشد، زنجیرههای قطعی +`changed module/symbol -> static consumer -> candidate sibling test` را گزارش میکند. خلاصه Markdown و نتیجه JSON بهعنوان +artifact گردشکار `forgotten-sibling-tests` برای کالیبراسیون پیش از هرگونه عرضه مسدودکننده نگهداری میشوند. -بازصادراتهای barrel و importهای پویا فقط برای تشخیص resolution هستند؛ آنها هرگز یافتهای مسدودکننده ایجاد نمیکنند. استثناهای بازبینیشده در -`config/quality/forgotten-sibling-allowlist.json` قرار دارند. هر ورودی باید نام تست مصرفکننده و تست کاندید را مشخص کند، دلیل مشخصی ارائه دهد و به یک issue یا pull request در GitHub پیوند داشته باشد. ورودیهای ناقص یا نادرست بهصورت بسته fail میشوند. استثناها نمیتوانند تست کاندیدی را که حذف شده است یا diffای را که `.skip`/`.todo` اضافه میکند، نادیده بگیرند؛ تضعیف assertion و دیگر روشهای پنهانسازی همچنان تحت مسئولیت gate مسدودکننده و مستقل -`check:test-masking` باقی میمانند. +بازصادراتهای Barrel و importهای پویا فقط برای تشخیص مشکلات resolution هستند؛ آنها هرگز یک یافتهٔ مسدودکننده ایجاد نمیکنند. استثناهای بازبینیشده در +`config/quality/forgotten-sibling-allowlist.json` قرار دارند. هر ورودی باید consumer و candidate +test را مشخص کند، دلیل مشخصی ارائه دهد و به یک GitHub issue یا pull request پیوند داشته باشد. ورودیهای نادرست بهصورت +fail-closed عمل میکنند. استثناها نمیتوانند یک candidate test حذفشده یا diffای را که `.skip`/`.todo` اضافه میکند، نادیده بگیرند؛ +تضعیف assertion و سایر شیوههای پنهانسازی همچنان تحت مسئولیت gate مسدودکننده و مستقل +`check:test-masking` هستند. ### Job: `lint` -در هر PR به `main` اجرا میشود. در صورت شکست، merge را مسدود میکند. - -| Script (`npm run ...`) | اعتبارسنجی میکند | مسدودکننده | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -| `check:node-runtime` | نسخه Node.js در محدوده پشتیبانیشده قرار دارد | بله | -| `check:cycles` | importهای حلقوی — همه ماژولهای `src/` + `open-sse/` | بله | -| `check:route-validation:t06` | وجود schemaهای Zod در همه routeها (سیاست Tier 6) | بله | -| `check:any-budget:t11` | تعداد `@ts-expect-error // any` از بودجه فراتر نمیرود (catraca سطح 11) | بله | -| `check:provider-consistency` | هر provider در `providers.ts` دارای یک ورودی متناظر در `providerRegistry.ts` است (و بالعکس، در محدوده allowlist) | بله | -| `check:model-lifecycle` | سه جدول مسیریابی که بهصورت دستی نگهداری میشوند، با snapshot چرخه عمر ثبتشده (#11503) سازگار میمانند: `FITNESS_TABLE` (`taskFitness.ts`) به هیچ id بازنشستهای که `REGISTRY` قادر به مسیریابی آن است امتیاز نمیدهد؛ هر target در `BUILT_IN_ALIASES` در `REGISTRY` وجود دارد و در snapshot مربوط به idهای بازنشسته وجود ندارد؛ هر id بازنشستهای که همچنان در `REGISTRY` است، به مقصد دیگری هدایت شده یا در `allowedRetiredInCatalog` فهرست شده است؛ و هیچ source یا target در `DEFAULT_DEGRADATION_MAP` در آن snapshot بازنشسته محسوب نمیشود. این بررسی ثابت نمیکند که یک model در حال حاضر توسط یک upstream فعال ارائه میشود. آفلاین — با `config/quality/model-lifecycle.json` مقایسه میشود؛ این فایل بهصورت دستی و با `npm run quality:refresh-model-lifecycle` بهروزرسانی میشود (نیازمند شبکه؛ به CI متصل نیست). `allowedRetiredInCatalog` یک ضامن کاهش تدریجی است: تنها در صورت وجود یک tracking issue، ورودی جدیدی به آن اضافه کنید. | بله | -| `check:fetch-targets` | هر `fetch("/api/...")` در `src/` سمت client به یک `route.ts` واقعی resolve میشود | بله | -| `check:deps` | همه dependencyهای قابل نصب با `npm install` در تمام فایلهای `package.json` در repo، در `dependency-allowlist.json` وجود دارند؛ packageهای جدیدی که pin نشدهاند یا دچار slopsquatting هستند علامتگذاری میشوند | بله | -| `audit:deps` | `npm audit` (root و electron) — بدون advisory با شدت بالا/بحرانی (با `check:vuln-ratchet` مربوط به osv همپوشانی دارد؛ Rationalization Backlog را ببینید) | بله | -| `check:lockfile` | یکپارچگی `package-lock.json` — registry مبتنی بر https، hashهای یکپارچگی، بدون override میزبان | بله | -| `check:licenses` | فهرست مجاز مجوزهای SPDX برای وابستگیهای محیط تولید | بله | -| `check:tracked-artifacts` | نبود مصنوعات ساخت / پیوندهای نمادین `node_modules` ثبتشده (همچنین در pre-commit مربوط به husky اجرا میشود؛ pre-push عمداً سبک نگه داشته شده است — #6716) | بله | -| `check:ai-attribution` | نبود دنبالهٔ `Co-Authored-By` مربوط به هوش مصنوعی/بات یا پانوشت تولیدشده با هوش مصنوعی در commitها، عنوان یا متن PR — قانون قطعی #16 (در حلقهٔ دروازههای سریع `quality.yml` برای PR→`release/**` — payload رویداد را میخواند و خارج از PRها کاری انجام نمیدهد — و یک مرحلهٔ مختص PR در lint فایل `ci.yml` برای PR→`main`؛ همچنین hook مربوط به `commit-msg` در husky؛ همنویسندگان انسانی مجازند؛ #14436) | -| `check:vitest-exclusions` | هر مورد استثنای Vitest باید یک issue رهگیری را ذکر کند و در `config/quality/vitest-exclusions.json` وجود داشته باشد (#13204) | بله | -| `check:file-size` | هیچ فایل منبعی از سقف تعیینشده برای پسوند خود فراتر نمیرود (ضامن یکطرفه: فایلهای بزرگ ثابتشده در فهرست `frozen`) | بله | -| `check:error-helper` | پاسخهای خطا در اجراکنندهها/مدیریتکنندهها از `buildErrorBody()` / `sanitizeErrorMessage()` استفاده میکنند (قانون قطعی #12) | بله | -| `check:migration-numbering` | فایلهای SQL مهاجرت بهصورت متوالی شمارهگذاری شدهاند و هیچ شکاف یا شمارهٔ تکراری وجود ندارد | بله | -| `check:public-creds` | هیچ `client_id`/`client_secret` مربوط به OAuth یا کلید وب Firebase بهصورت صریح خارج از `publicCreds.ts` وجود ندارد (قانون سختگیرانهٔ شمارهٔ ۱۱) | بله | -| `check:db-rules` | هیچ SQL خامی خارج از ماژولهای `src/lib/db/` وجود ندارد؛ هیچ واردسازی barrel از `localDb.ts` انجام نمیشود (قوانین سختگیرانهٔ شمارهٔ ۲/۵) | بله | -| `check:known-symbols` | اجراکنندههای ارائهدهنده، راهبردهای مسیریابی و مترجمهای ثبتشده در جدولهای dispatch مربوطه با فایلهای روی دیسک مطابقت دارند — هیچ نماد بدون استفاده یا اعلاننشدهای وجود ندارد | بله | -| `check:route-guard-membership` | هر مسیری که یک فرایند فرزند ایجاد میکند، توسط `isLocalOnlyPath()` طبقهبندی شده است (قوانین سختگیرانهٔ شمارهٔ ۱۵/۱۷) | بله | -| `check:test-discovery` | هر فایل `*.test.ts` / `*.spec.ts` در مخزن توسط حداقل یک اجراکنندهٔ آزمون جمعآوری میشود (سازوکار ratchet: فهرست فایلهای بدون اجراکننده در `test-discovery-baseline.json` فقط میتواند کوچکتر شود) | بله | -| `check:agent-skills-sync` | مصنوعات تولیدشدهٔ agent-skills با کاتالوگ منبع خود مطابقت دارند (بدون انحراف) | -| `check:provider-asset-provenance` | لوگوها/داراییهای ارائهدهنده دارای یک ورودی ثبتشده برای منشأ هستند | -| `lint:json` | فایلهای پیکربندی JSON تجزیه میشوند و با قواعد lint مخزن مطابقت دارند | -| `typecheck:core` | کامپایل TypeScript بدون خطا (فقط هشدارهای مشورتی) | بله | -| `typecheck:noimplicit:core` | `noImplicitAny` سختگیرانه — با نگاه به آینده؛ بسیاری از محلهای فراخوانی از قبل موجود همچنان به حاشیهنویسی نوع نیاز دارند | **مشورتی** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | اجرای `tsc` محدود به `src/app/(dashboard)/**` (#7033) — فهرست مجازِ گزینششدهٔ ۲۷ فایلیِ `typecheck:core` هیچ فایل TSX داشبوردی را شامل نمیشود و `next build` نیز هرگز آن را از نظر نوع بررسی نمیکند (`next.config.mjs` مقدار `ignoreBuildErrors: true` را تنظیم میکند)، بنابراین پسرفتهای شناسههای جداافتاده در آن بخش (#6625/#6909) برای CI نامرئی بودند. تفاوتها با یک خط مبنای ثابت از تعداد خطاها بهازای هر فایل/کد TS مقایسه میشوند (`config/quality/dashboard-typecheck-baseline.json`، با همان الگوی اعمال کهنگیِ `check:known-symbols`) — فقط خطاهای جدیدی که از تعداد ثبتشده در خط مبنا فراتر بروند باعث شکست دروازه میشوند؛ وقتی یک خطای از قبل موجود برطرف شد، با `--update` خط مبنا را بهتدریج کاهش دهید. | بله | +روی هر PR به `main` اجرا میشود. در صورت شکست، merge را مسدود میکند. + +| Script (`npm run ...`) | اعتبارسنجی میکند | مسدودکننده | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | +| `check:node-runtime` | نسخهٔ Node.js در محدودهٔ پشتیبانیشده قرار دارد | بله | +| `check:cycles` | importهای حلقوی در سراسر `src/` + `open-sse/` (مبتنی بر AST، با resolve شدن `paths` در tsconfig). حالت Bare صرفاً advisory است و چرخهها را فهرست میکند. `check:cycles:ratchet` (چیزی که CI اجرا میکند) زمانی مسدود میکند که تعداد از سقف `metrics.cycles` در `quality-baseline.json` فراتر رود — در حال حاضر 14، با `direction: down`، بنابراین فقط میتواند کاهش یابد (#15159 G-01/G-02) | بله (ratchet) | +| `check:route-validation:t06` | وجود schemaهای Zod روی همهٔ routeها (سیاست Tier 6) | بله | +| `check:any-budget:t11` | تعداد `@ts-expect-error // any` از budget فراتر نمیرود (catraca در Tier 11) | بله | +| `check:provider-consistency` | هر ارائهدهنده در `providers.ts` دارای یک ورودی متناظر در `providerRegistry.ts` است (و برعکس، در محدوده فهرست مجاز) | بله | +| `check:model-lifecycle` | سه جدول مسیریابی که بهصورت دستی نگهداری میشوند، با تصویر لحظهای چرخهعمر ثبتشده (#11503) سازگار میمانند: `FITNESS_TABLE` (`taskFitness.ts`) به هیچ شناسه بازنشستهای که `REGISTRY` قادر به مسیریابی آن باشد امتیاز نمیدهد؛ هر مقصد `BUILT_IN_ALIASES` در `REGISTRY` وجود دارد و در تصویر لحظهای شناسههای بازنشسته نیست؛ هر شناسه بازنشستهای که همچنان در `REGISTRY` است، هدایت میشود یا در `allowedRetiredInCatalog` فهرست شده است؛ و هیچ مبدأ یا مقصدی در `DEFAULT_DEGRADATION_MAP` در آن تصویر لحظهای بازنشسته محسوب نمیشود. این بررسی ثابت نمیکند که یک مدل در حال حاضر توسط یک بالادست فعال ارائه میشود. آفلاین — با `config/quality/model-lifecycle.json` مقایسه میکند؛ این فایل بهصورت دستی با `npm run quality:refresh-model-lifecycle` تازهسازی میشود (نیازمند شبکه؛ به CI متصل نیست). `allowedRetiredInCatalog` یک ضامن کاهش تدریجی است: فقط همراه با یک مسئله رهگیری، ورودی جدیدی اضافه کنید. | بله | +| `check:fetch-targets` | هر `fetch("/api/...")` در `src/` سمت کلاینت به یک `route.ts` واقعی منتهی میشود | بله | +| `check:deps` | تمام وابستگیهای قابل نصب با `npm install` در همه فایلهای `package.json` مخزن، در `dependency-allowlist.json` قرار دارند؛ بستههای جدیدِ بدون نسخه ثابت یا دارای نامهای مستعد slopsquatting علامتگذاری میشوند | بله | +| `audit:deps` | `npm audit` (ریشه + electron) — بدون هشدارهای سطح بالا/بحرانی (با osv `check:vuln-ratchet` همپوشانی دارد؛ بخش Rationalization Backlog را ببینید) | بله | +| `check:lockfile` | یکپارچگی `package-lock.json` — رجیستری https، هشهای یکپارچگی، بدون بازنویسی میزبان | بله | +| `check:licenses` | فهرست مجاز مجوزهای SPDX برای وابستگیهای محیط تولید | بله | +| `check:tracked-artifacts` | بدون مصنوعات ساخت / پیوندهای نمادین `node_modules` ثبتشده (در pre-commit مربوط به husky نیز اجرا میشود؛ pre-push عمداً سبک نگه داشته شده است — #6716) | بله | +| `check:ai-attribution` | نبود تریلر `Co-Authored-By` مربوط به هوش مصنوعی/بات یا پانوشت تولید توسط هوش مصنوعی در commitها، عنوان یا بدنه PR — قانون سختگیرانه #16 (در حلقه fast-gates فایل `quality.yml` برای PR→`release/**` — payload رویداد را میخواند و خارج از PR هیچ عملی انجام نمیدهد — و یک مرحله مختص PR در lint فایل `ci.yml` برای PR→`main`؛ همچنین hook از نوع `commit-msg` در husky؛ همنویسندگان انسانی مجازند؛ #14436) | +| `check:vitest-exclusions` | هر مورد استثنای Vitest یک issue رهگیری را ذکر میکند و در `config/quality/vitest-exclusions.json` وجود دارد (#13204) | بله | +| `check:file-size` | هیچ فایل منبعی از سقف تعیینشده برای پسوند خود فراتر نمیرود (ratchet: فایلهای بزرگ تثبیتشده در فهرست `frozen`) | بله | +| `check:error-helper` | پاسخهای خطا در executorها/handlerها از `buildErrorBody()` / `sanitizeErrorMessage()` استفاده میکنند (قانون سختگیرانه #12) | بله | +| `check:migration-numbering` | فایلهای SQL مهاجرت بهترتیب شمارهگذاری شدهاند و هیچ فاصله یا شماره تکراری وجود ندارد | بله | +| `check:public-creds` | هیچ مقدار صریح OAuth برای `client_id`/`client_secret` یا کلید Firebase Web خارج از `publicCreds.ts` وجود ندارد (قانون سختگیرانه شماره ۱۱) | بله | +| `check:db-rules` | هیچ SQL خامی خارج از ماژولهای `src/lib/db/` وجود ندارد؛ هیچ درونریزی تجمیعی از `localDb.ts` انجام نمیشود (قوانین سختگیرانه شماره ۲/۵) | بله | +| `check:known-symbols` | اجراکنندههای ارائهدهنده، راهبردهای مسیریابی و مترجمهای ثبتشده در جدولهای توزیع مربوطه با فایلهای روی دیسک مطابقت دارند — هیچ نماد یتیم یا اعلاننشدهای وجود ندارد | بله | +| `check:route-guard-membership` | هر مسیری که یک فرایند فرزند ایجاد میکند، توسط `isLocalOnlyPath()` طبقهبندی شده است (قوانین سختگیرانه شماره ۱۵/۱۷) | بله | +| `check:test-discovery` | هر فایل `*.test.ts` / `*.spec.ts` در مخزن توسط حداقل یک اجراکننده آزمون جمعآوری میشود (سازوکار ضامن: فهرست فایلهای یتیم در `test-discovery-baseline.json` فقط میتواند کوچکتر شود) | بله | +| `check:agent-skills-sync` | مصنوعات تولیدشدهٔ agent-skills با کاتالوگ منبع خود مطابقت دارند (بدون انحراف) | +| `check:provider-asset-provenance` | لوگوها/داراییهای ارائهدهنده دارای یک ورودی ثبتشده برای منشأ هستند | +| `lint:json` | فایلهای پیکربندی JSON تجزیه میشوند و قوانین lint مخزن را رعایت میکنند | +| `typecheck:core` | کامپایل TypeScript بدون خطا (فقط هشدارهای مشورتی) | بله | +| `typecheck:noimplicit:core` | `noImplicitAny` سختگیرانه — آیندهنگر؛ بسیاری از محلهای فراخوانی از قبل موجود همچنان به حاشیهنویسی نوع نیاز دارند | **مشورتی** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | اجرای `tsc` محدود به `src/app/(dashboard)/**` (#7033) — فهرست مجاز گزینششدهٔ ۲۷ فایلیِ `typecheck:core` هیچ فایل TSX داشبوردی را شامل نمیشود و `next build` نیز هرگز آن را از نظر نوع بررسی نمیکند (`next.config.mjs` مقدار `ignoreBuildErrors: true` را تنظیم میکند)؛ بنابراین پسرفتهای شناسههای بدون مرجع در آن بخش (#6625/#6909) برای CI نامرئی بودند. تفاوتها با یک خط مبنای ثابتِ شمارش بر اساس فایل/کد TS مقایسه میشوند (`config/quality/dashboard-typecheck-baseline.json`، با همان الگوی اعمال منسوخشدگیِ `check:known-symbols`) — فقط خطاهای جدید فراتر از تعداد ثبتشده در خط مبنا باعث شکست این دروازه میشوند؛ هنگامی که یک خطای از قبل موجود رفع شد، با `--update` خط مبنا را کاهش دهید. | بله | ### کار: `quality-gate` @@ -139,91 +135,91 @@ CLI ناهماهنگی میان SHA مربوط به checkout/event را رد م | اسکریپت | اعتبارسنجی | مسدودکننده | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `quality:collect` | فایل `quality-metrics.json` را تولید میکند (تعداد هشدارهای ESLint، پوشش برگرفته از گزارش ادغامشدهٔ shardها) | بله (پیشنیاز ratchet) | +| `quality:collect` | فایل `quality-metrics.json` را تولید میکند (تعداد هشدارهای ESLint، پوشش از گزارش ادغامشدهٔ شاردها) | بله (پیشنیاز ratchet) | | `quality:ratchet` | هیچیک از معیارهای موجود در `quality-baseline.json` پسرفت نکرده باشد (هشدارهای ESLint ≤ خط مبنا؛ پوشش ≥ خط مبنا) | بله | -| `check:duplication` | تکرار کد (jscpd@4) از خط مبنای موجود در `quality-baseline.json` فراتر نرود | بله | +| `check:duplication` | میزان تکرار کد (jscpd@4) از خط مبنای موجود در `quality-baseline.json` فراتر نرود | بله | | `check:complexity` | پیچیدگی حلقوی در سطح فایل از سقف تعیینشده فراتر نرود (`complexity` هستهٔ ESLint + `max-lines-per-function`) | بله | -| `check:cognitive-complexity` | ratchet پیچیدگی شناختی (`eslint-plugin-sonarjs`) — اجرای جداگانهٔ ESLint؛ CI هر دو را بهصورت مرحلهٔ واحد `check:complexity-ratchets` اجرا میکند | بله | +| `check:cognitive-complexity` | ratchet پیچیدگی شناختی (`eslint-plugin-sonarjs`) — اجرای جداگانهٔ ESLint؛ CI هر دو را بهصورت ادغامشده در یک مرحلهٔ `check:complexity-ratchets` اجرا میکند | بله | | `check:dead-code` | ratchet مربوط به exportها / فایلهای استفادهنشده (knip) نسبت به خط مبنا پسرفت نکند | بله | -| `check:compression-budget` | بودجهٔ بنچمارک فشردهسازی — حداقل صرفهجویی توکن برای هر engine نباید پسرفت کند | بله | -| `check:type-coverage` | ratchet درصد کد دارای type (`type-coverage`) پسرفت نکند؛ تا حد زیادی `typecheck:noimplicit:core` را پوشش میدهد | بله | -| `check:codeql-ratchet` | تعداد هشدارهای باز CodeQL پسرفت نکند (خواندن از طریق `gh api`؛ در نبود token بدون خطا رد میشود) — تناوب بهروزرسانی و اجرای دستی: بخش «ratchet مربوط به CodeQL» را در ادامه ببینید | بله | +| `check:compression-budget` | بودجهٔ بنچمارک فشردهسازی — حداقل میزان صرفهجویی توکن برای هر موتور نباید پسرفت کند | بله | +| `check:type-coverage` | ratchet درصد کد تایپشده (`type-coverage`) پسرفت نکند؛ تا حد زیادی `typecheck:noimplicit:core` را پوشش میدهد | بله | +| `check:codeql-ratchet` | تعداد هشدارهای باز CodeQL پسرفت نکند (خواندن از طریق `gh api`؛ در نبود توکن با موفقیت رد میشود) — تناوب بهروزرسانی و اجرای دستی: بخش «ratchet مربوط به CodeQL» را در ادامه ببینید | بله | ### Job: `quality-extended` کل این job جنبهٔ مشورتی دارد (`continue-on-error: true`). ratchetهای مبتنی بر npm -بهصورت واقعی اجرا میشوند؛ اسکنرهای خارجی از طریق `gh release download` نصب میشوند و -اگر فایل اجرایی همچنان موجود نباشد، خودشان با موفقیت رد میشوند (exit 0). +واقعاً اجرا میشوند؛ اسکنرهای خارجی از طریق `gh release download` نصب میشوند و +اگر فایل باینری همچنان موجود نباشد، خودشان اجرا را رد میکنند (exit 0). | اسکریپت | اعتبارسنجی | مسدودکننده | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | `check:circular-deps` | نبود وابستگیهای دوری (dpdm) | **مشورتی** | | `check:bundle-size` | اندازهٔ bundle از سقف تعیینشده فراتر نرود | **مشورتی** | -| `check:secrets` | اسکن اطلاعات محرمانه (gitleaks) — در نبود فایل اجرایی رد میشود | **مشورتی** | -| `check:vuln-ratchet` | آسیبپذیریهای وابستگیها (osv-scanner) پسرفت نکنند — در نبود فایل اجرایی رد میشود | **مشورتی** | +| `check:secrets` | اسکن اسرار (gitleaks) — در نبود فایل باینری رد میشود | **مشورتی** | +| `check:vuln-ratchet` | آسیبپذیریهای وابستگیها (osv-scanner) پسرفت نکنند — در نبود فایل باینری رد میشود | **مشورتی** | | `check:workflows` | lint کردن workflowها (actionlint + zizmor)؛ اسکنرهای مفقود/خراب، گزارشهای نامعتبر یا نبود خط مبنای ratchet با وضعیت INCOMPLETE شکست میخورند. یافتههای معتبر از سیاست strict/advisory/ratchet انتخابشده پیروی میکنند | اجرا الزامی است؛ ratchet مربوط به zizmor در CI مسدودکننده است | -| `check:openapi-breaking` | تغییرات ناسازگارشکن در قرارداد API عمومی (`openapi.yaml`) نسبت به شاخهٔ پایه (oasdiff) — مقدار `openapiBreaking=N` را تولید میکند؛ اگر oasdiff موجود نباشد یا مشخصات پایه قابل resolve نباشد، رد میشود | **مشورتی** | +| `check:openapi-breaking` | تغییرات ناسازگار در قرارداد API عمومی (`openapi.yaml`) نسبت به شاخهٔ پایه (oasdiff) — مقدار `openapiBreaking=N` را تولید میکند؛ در نبود oasdiff یا در صورت غیرقابلدسترسی بودن مشخصات پایه رد میشود | **مشورتی** | ### Job: `docs-sync-strict` -در هر PR به `main` اجرا میشود. در صورت شکست، merge را مسدود میکند. - -| اسکریپت | موارد اعتبارسنجیشده | مسدودکننده | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | -| `check:docs-all` | دروازهٔ فراگیری که ۶ زیردروازهٔ زیر را بهترتیب اجرا میکند | بله | -| ↳ `check:docs-sync` | سازگاری نسخه میان CHANGELOG / OpenAPI / llm.txt | بله | -| ↳ `check:docs-counts` | تعدادهای ذکرشده در متن (تعداد ارائهدهندگان، تعداد مهاجرتها و غیره) در محدودهٔ افزایشی تعدادهای واقعی قرار دارند | بله | -| ↳ `check:env-doc-sync` | هر متغیر محیطی در `.env.example` در یکی از جدولهای مستندات ثبت شده است و برعکس | بله | -| ↳ `check:deprecated-versions` | هیچ رشتهٔ نسخهٔ منسوخی در مستندات وجود ندارد | بله | -| ↳ `check:doc-links` | پیوندهای داخلی markdown در مستندات به فایلهای واقعی ارجاع میدهند (با قالب `[text]`/`(path)`) | بله | -| ↳ `check:fabricated-docs` | مسیرها، متغیرهای محیطی، فرمانهای CLI، نام hookها و مسیرهای فایل ذکرشده در مستندات، در پایگاه کد وجود دارند. دروازهٔ سخت با `--strict`؛ شکست غیرمسدودکننده بدون این پرچم. | بله (از طریق `--strict` در CI) | -| `check:cli-i18n` | رشتههای فرمان CLI در همهٔ فایلهای locale مربوط به i18n وجود دارند | بله | -| `check:openapi-coverage` | مشخصات OpenAPI دستکم کفِ افزایشی تعیینشدهای از مسیرهای واقعی را پوشش میدهد | بله | -| `check:openapi-security-tiers` | حاشیهنویسیهای سطح امنیتی در `openapi.yaml` با طبقهبندیهای `routeGuard.ts` سازگار هستند | **مشورتی** | -| `check:openapi-routes` | هر مسیر در `openapi.yaml` به یک `route.ts` واقعی ارجاع میدهد (ضد توهم) | بله | -| `check:docs-symbols` | هر ارجاع `/api/...` در `docs/**/*.md` به یک `route.ts` واقعی ارجاع میدهد (ضد توهم) | بله | -| `i18n translation drift` | کلیدهای ترجمهنشده در فایلهای locale مربوط به i18n — فقط هشدار | **مشورتی** | - -### کار: `i18n-ui-coverage` - -| اسکریپت | موارد اعتبارسنجیشده | مسدودکننده | -| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check-ui-keys-coverage` (درونخطی) | پوشش کلیدهای i18n رابط کاربری ≥ ۶۵٪ است | بله | -| `check-ui-value-drift` (درونخطی) | بازنویسی یک **مقدار** انگلیسی، هیچ ترجمهٔ کهنهای بر جای نمیگذارد | بله | -| `check-new-key-coverage` (درونخطی) | یک کلید انگلیسی **جدید** در همهٔ localeها ترجمه شده است — نشانگر `__MISSING__:` رد میشود | بله | -| `check-translation-ratio` | نسبت ترجمهٔ واقعی در هر locale (برگهای یکسان با انگلیسی / جاینگهدار / مفقود خارج از فهرست مجاز) نباید از `config/quality/i18n-translation-baseline.json` بهعلاوهٔ حاشیهٔ مجاز فراتر رود | **مشورتی** | - -به `fetch-depth: 0` نیاز دارد — دروازهٔ انحراف مقدار، `en.json` را با مبنای ادغام مقایسه میکند. - -#### `check-ui-value-drift` — دروازهٔ ترجمهٔ کهنه - -آن رگرسیون i18n را شناسایی میکند که سایر دروازهها از نظر ساختاری قادر به دیدن آن نیستند: یک مقدار انگلیسی +در هر PR به `main` اجرا میشود. در صورت شکست، ادغام را مسدود میکند. + +| اسکریپت | موارد اعتبارسنجی | مسدودکننده | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| `check:docs-all` | دروازهٔ کلانی که ۶ زیردروازهٔ زیر را بهترتیب اجرا میکند | بله | +| ↳ `check:docs-sync` | سازگاری نسخه میان CHANGELOG / OpenAPI / llm.txt | بله | +| ↳ `check:docs-counts` | تعدادهای ذکرشده در متن (تعداد ارائهدهندگان، تعداد مهاجرتها و غیره) در محدودهٔ ضامندار نسبت به تعدادهای واقعی قرار دارند | بله | +| ↳ `check:env-doc-sync` | تمام متغیرهای محیطی موجود در `.env.example` در یک جدول مستندات ثبت شدهاند و بالعکس | بله | +| ↳ `check:deprecated-versions` | هیچ رشتهٔ نسخهٔ منسوخی در مستندات وجود ندارد | بله | +| ↳ `check:doc-links` | پیوندهای داخلی markdown در مستندات به فایلهای واقعی اشاره میکنند (با قالب `[text]`/`(path)`) | بله | +| ↳ `check:fabricated-docs` | مسیرها، متغیرهای محیطی، فرمانهای CLI، نام hookها و مسیر فایلهای ذکرشده در مستندات در پایگاه کد وجود دارند. دروازهٔ سخت با `--strict`؛ بدون این پرچم، شکست نرم است. | بله (از طریق `--strict` در CI) | +| `check:cli-i18n` | رشتههای فرمان CLI در همهٔ فایلهای locale مربوط به i18n وجود دارند | بله | +| `check:openapi-coverage` | مشخصات OpenAPI دستکم کف ضامنگذاریشدهای از مسیرهای واقعی را پوشش میدهد | بله | +| `check:openapi-security-tiers` | حاشیهنویسیهای سطح امنیت در `openapi.yaml` با طبقهبندیهای `routeGuard.ts` سازگارند | **مشورتی** | +| `check:openapi-routes` | هر path در `openapi.yaml` به یک `route.ts` واقعی منتهی میشود (ضد توهم) | بله | +| `check:docs-symbols` | هر ارجاع `/api/...` در `docs/**/*.md` به یک `route.ts` واقعی منتهی میشود (ضد توهم) | بله | +| `i18n translation drift` | کلیدهای ترجمهنشده در فایلهای locale مربوط به i18n — فقط هشدار | **مشورتی** | + +### Job: `i18n-ui-coverage` + +| اسکریپت | موارد اعتبارسنجی | مسدودکننده | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check-ui-keys-coverage` (درونخطی) | پوشش کلیدهای i18n رابط کاربری ≥ ۶۵٪ است | بله | +| `check-ui-value-drift` (درونخطی) | بازنویسی یک **مقدار** انگلیسی هیچ ترجمهٔ قدیمیای بر جای نمیگذارد | بله | +| `check-new-key-coverage` (درونخطی) | یک کلید انگلیسی **جدید** در همهٔ localeها ترجمه شده است — نشانگر `__MISSING__:` رد میشود | بله | +| `check-translation-ratio` | نسبت ترجمهٔ واقعی در هر locale (موارد یکسان با انگلیسی / placeholder / مقادیر ازدسترفته خارج از allowlist) نباید از `config/quality/i18n-translation-baseline.json` + حاشیهٔ مجاز فراتر رود | **مشورتی** | + +به `fetch-depth: 0` نیاز دارد — دروازهٔ value-drift، تفاوت `en.json` را با merge base بررسی میکند. + +#### `check-ui-value-drift` — دروازهٔ ترجمهٔ قدیمی + +آن خطای بازگشتی i18n را شناسایی میکند که دیگر دروازهها از نظر ساختاری قادر به دیدنش نیستند: یک مقدار انگلیسی بازنویسی میشود و ترجمههای برگرفته از مقدار انگلیسی _قبلی_ باقی میمانند؛ در نتیجه کاربران غیرانگلیسی همچنان متنی با لحنی مطمئن اما اکنون نادرست میخوانند. -این مشکل واقعاً منتشر شد. `oauthModal.googleOAuthWarning` هنگام اضافهشدن ابزار کمکی ورود Antigravity -بازنویسی شد (#5203)؛ **۳۹ مورد از ۴۳ locale** همچنان متنی را حفظ کردند که به اپراتورها میگفت «URL کامل را کپی +این مشکل واقعاً در نسخهٔ منتشرشده رخ داد. `oauthModal.googleOAuthWarning` هنگام اضافهشدن ابزار کمکی ورود Antigravity +بازنویسی شد (#5203)؛ **۳۹ مورد از ۴۳ locale** همچنان متنی داشتند که به اپراتورها میگفت «URL کامل را کپی کنید و در پایین جایگذاری کنید» — جریانی که برای آن ارائهدهنده قابل تکمیل نیست. این مشکل -تا #8463 نادیده ماند، زیرا: - -- `sync-ui-keys` فقط کلیدهایی را که **وجود ندارند** تکمیل میکند، نه کلیدهایی که **کهنه** هستند؛ -- `check-ui-keys-coverage` _وجود_ کلید را میشمارد، بنابراین یک ترجمهٔ کهنه بهعنوان پوششدادهشده امتیاز میگیرد؛ -- `check-translation-drift` نسخههای آینهای مستندات در `docs/i18n//**.md` را ردیابی میکند — - و هرگز `src/i18n/messages/*.json` را نمیخواند. از همگامسازی مجدد 2026-09 در کار `docs-sync-strict` مسدودکننده است: - یک سند اصلی را ویرایش کنید ← `npm run i18n:run -- --files=` (در سطح بخش و کمهزینه). - -**آگاه از diff، نه متکی به baseline.** این سازوکار `en.json` در merge base را با -working tree مقایسه میکند؛ برای هر کلیدی که مقدار انگلیسی آن تغییر کرده باشد، هر locale که همچنان -ترجمهای دستنخورده داشته باشد، منسوخ محسوب میشود. این کار عمداً **بدهی از قبل موجود را ثابت نگه میدارد** — یک diff -نمیتواند مشخص کند ترجمهای قدیمی و دیرپا از کدام متن انگلیسی قبلی منشأ گرفته است، بنابراین gate -فقط مواردی را ارزیابی میکند که تغییر فعلی به آنها دست میزند. راهکار جایگزین (یک hash baseline برای هر کلید) -به یک فایل تولیدشده با حجم حدود 600 KB، یعنی 3× بزرگتر از بزرگترین baseline موجود، نیاز دارد که با هر PR مربوط به i18n +تا #8463 شناسایی نشد، زیرا: + +- `sync-ui-keys` فقط کلیدهایی را که **وجود ندارند** تکمیل میکند، نه کلیدهایی را که **قدیمی** شدهاند؛ +- `check-ui-keys-coverage` _وجود_ کلید را میشمارد، بنابراین یک ترجمهٔ قدیمی نیز پوششدادهشده محسوب میشود؛ +- `check-translation-drift` همتاهای مستندات در `docs/i18n//**.md` را ردیابی میکند — + و هرگز `src/i18n/messages/*.json` را نمیخواند. از همگامسازی مجدد 2026-09 در job با نام `docs-sync-strict` مسدودکننده است: + یک سند اصلی را ویرایش کنید → `npm run i18n:run -- --files=` (در سطح بخش و کمهزینه). + +**آگاه از diff، بدون اتکا به baseline.** این سازوکار `en.json` را در merge base با +درخت کاری مقایسه میکند؛ برای هر کلیدی که مقدار انگلیسی آن تغییر کرده است، هر locale که همچنان +ترجمهای دستنخورده داشته باشد، منسوخ محسوب میشود. این کار عمداً **بدهی ازپیشموجود را ثابت نگه میدارد** — یک diff +نمیتواند مشخص کند که یک ترجمهٔ قدیمی و باسابقه از کدام متن انگلیسی قدیمی ایجاد شده است، بنابراین gate +فقط مواردی را ارزیابی میکند که تغییر فعلی به آنها دست میزند. راهکار جایگزین (یک baseline شامل hash برای هر کلید) +به یک فایل تولیدشدهٔ حدوداً 600 KB نیاز دارد که 3 برابر بزرگترین baseline موجود است و در هر PR مربوط به i18n دستخوش تغییر میشود. دو راه برای برآوردهکردن آن وجود دارد: 1. ترجمههای تحتتأثیر را بهروزرسانی کنید، یا -2. آنها را روی `__MISSING__:` تنظیم کنید — سپس runtime متن انگلیسی اصلاحشده را ارائه میکند +2. آنها را روی `__MISSING__:` تنظیم کنید — سپس runtime متن انگلیسی اصلاحشده را ارائه میدهد (`src/i18n/request.ts::deepMergeFallback`، #7258) و کلید برای ترجمه در صف قرار میگیرد. اگر **معنای** رشته تغییر کرده است، **تغییر نام کلید** را ترجیح دهید: یک کلید جدید نمیتواند @@ -235,8 +231,8 @@ npm run i18n:check-value-drift:warn # فقط گزارش BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -هنگامی که catalog پایه قابل خواندن نباشد، با `SKIP reason=base-unresolved` و کد 0 خارج میشود (clone کمعمق -بدون base ref) و رفتار `check-openapi-breaking` را منعکس میکند. +وقتی catalog پایه قابل خواندن نباشد (clone کمعمق بدون ref پایه)، با `SKIP reason=base-unresolved` +و کد 0 خارج میشود؛ مشابه رفتار `check-openapi-breaking`. ### Job: `i18n` @@ -250,33 +246,33 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift فقط روی pull requestها اجرا میشود. -| Script | اعتبارسنجی میکند | مسدودکننده | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check:pr-test-policy` | PRهایی که کد production را در `src/`، `open-sse/`، `electron/` یا `bin/` تغییر میدهند باید شامل testهای جدید یا بهروزشده باشند (Hard Rule #8) | بله | -| `check:test-masking` | فایلهای test تغییریافته، تعداد خالص assertها را کاهش نمیدهند یا همانگوییهای `assert.ok(true)` اضافه نمیکنند | بله | -| `check:pr-evidence` | بدنه PR به شواهد test/VPS برای تغییر ارجاع میدهد (Hard Rule #18 را با grep کردن متن PR خودکار میکند — شکننده است، Backlog را ببینید) | بله | +| Script | اعتبارسنجی میکند | مسدودکننده | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check:pr-test-policy` | PRهایی که کد production را در `src/`، `open-sse/`، `electron/` یا `bin/` تغییر میدهند، باید تستها را اضافه یا بهروزرسانی کنند (Hard Rule #8) | بله | +| `check:test-masking` | فایلهای تست تغییرکرده، تعداد خالص assertها را کاهش نمیدهند و همانگوییهای `assert.ok(true)` را اضافه نمیکنند | بله | +| `check:pr-evidence` | بدنهٔ PR به شواهد تست/VPS برای تغییر اشاره میکند (Hard Rule #18 را با grepکردن نثر PR مکانیزه میکند — شکننده است؛ Backlog را ببینید) | بله | ### Job: `test-vitest` پس از `build` اجرا میشود. در صورت شکست، merge را مسدود میکند. -| Suite | اعتبارسنجی میکند | مسدودکننده | -| ---------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | سرور MCP (110 ابزار)، autoCombo، cache — اجراکننده vitest | بله | -| `test:vitest:ui` | testهای مؤلفه UI — اجراکننده vitest | **مسدودکننده** — شکستهای از قبل موجود صراحتاً در `vitest.config.ts` مستثنا شدهاند؛ شکستهای جدید باعث شکست job میشوند | +| Suite | اعتبارسنجی میکند | مسدودکننده | +| ---------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | سرور MCP (110 ابزار)، autoCombo، cache — اجراکنندهٔ vitest | بله | +| `test:vitest:ui` | تستهای مؤلفههای UI — اجراکنندهٔ vitest | **مسدودکننده** — شکستهای ازپیشموجود صراحتاً در `vitest.config.ts` مستثنا شدهاند؛ شکستهای جدید باعث شکست job میشوند | ### Workflowهای شبانه (زمانبندیشده، مشورتی) -این موارد طبق زمانبندی cron (و `workflow_dispatch`) اجرا میشوند و هرگز روی PRها اجرا نمیشوند. همه آنها مشورتی هستند. +این موارد طبق یک زمانبندی cron (و `workflow_dispatch`) اجرا میشوند و هرگز روی PRها اجرا نمیشوند. همهٔ آنها مشورتی هستند. -| Workflow | اعتبارسنجی میکند | مسدودکننده | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `nightly-property` | testهای property مبتنی بر fast-check با seed تصادفی + تعداد اجرای بالا | **مشورتی** | -| `nightly-resilience` | gate رشد heap، تزریق خطای chaos، بارگذاری/soak با k6 | **مشورتی** | -| `nightly-llm-security` | محافظ تزریق promptfoo (حالت block) + probeهای garak (بدون secret ارائهدهنده رد میشود) | **مشورتی** | -| `nightly-schemathesis` | fuzzing قرارداد OpenAPI (schemathesis) روی یک OmniRoute فعال با استفاده از `docs/openapi.yaml` — تخطی از spec / خطاهای 500 مدیریتنشده را آشکار میکند (Fase 8 B.4) | **مشورتی** | -| `nightly-mutation` | امتیاز mutation testing مربوط به Stryker روی مسیر سریع unit — mutantهای باقیمانده، assertهای ضعیف را آشکار میکنند | **مشورتی** | -| `nightly-compat` | ماتریس سازگاری موتور Node در محدودههای پشتیبانیشده `engines.node` | **مشورتی** | +| Workflow | اعتبارسنجی میکند | مسدودکننده | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | +| `nightly-property` | تستهای property مبتنی بر fast-check با seed تصادفی و تعداد اجرای بالا | **مشورتی** | +| `nightly-resilience` | gate رشد heap، تزریق خطای chaos و تست بار/دوام k6 | **مشورتی** | +| `nightly-llm-security` | محافظ تزریق promptfoo (حالت block) + probeهای garak (بدون secret ارائهدهنده رد میشود) | **مشورتی** | +| `nightly-schemathesis` | fuzzing قرارداد OpenAPI با schemathesis علیه یک OmniRoute فعال با استفاده از `docs/openapi.yaml` — نقضهای spec / خطاهای 500 مدیریتنشده را آشکار میکند (Fase 8 B.4) | **مشورتی** | +| `nightly-mutation` | امتیاز تست mutation با Stryker روی مسیر سریع unit — mutantهای باقیمانده، assertهای ضعیف را آشکار میکنند | **مشورتی** | +| `nightly-compat` | ماتریس سازگاری engine مربوط به Node در بازههای پشتیبانیشدهٔ `engines.node` | **مشورتی** | --- @@ -546,36 +542,36 @@ SLOهای هدف پس از راهاندازی تلهمتری آزمونهای ن `check:dead-code`، `check:cognitive-complexity`، `check:type-coverage`، `check:codeql-ratchet` و `check:pr-evidence` را از قلم انداخته بود). بازبینی ROI مجموعهی تطبیقدادهشده، گزینههای زیر را برای منطقیسازی مشخص کرد. **ادغامها تغییرات مکانیکی CI -هستند؛ تغییر وضعیتها/حذفها تصمیمهای سیاستیاند که در اختیار اپراتور قرار دارند.** هنوز هیچیک -از موارد زیر اعمال نشده است. +هستند؛ تغییر وضعیتها/حذفها تصمیمهای سیاستی محفوظ برای اپراتورند.** هنوز هیچیک از موارد +زیر اعمال نشده است. **موارد دیگری که در بالا مستند نشدهاند** (مشورتی، با سیگنال ضعیف): job مربوط به `docs-lint` -(markdownlint + Vale، با `continue-on-error` برای کل job) و workflowهای اسکنر مستقل +(markdownlint + Vale، با `continue-on-error` برای کل job) و workflowهای مستقل اسکنر `semgrep.yml` / `codeql.yml` / `scorecard.yml`. مقدار `semgrepFindings: 0` در -`quality-baseline.json` وجود دارد، اما در `ci.yml` به ratchet مسدودکنندهای متصل نیست — این معیار -در حال حاضر بدون استفاده مانده است. +`quality-baseline.json` وجود دارد، اما در `ci.yml` به یک ضامن افزایشی مسدودکننده متصل +نیست — این معیار در حال حاضر بدون استفاده مانده است. -### ادغام / حذف موارد تکراری (مکانیکی، با ریسک کمتر) +### ادغام / حذف موارد تکراری (مکانیکی، کمریسکتر) -هر گزینه در 2026-06-17 با وضعیت زندهی gate اعتبارسنجی شد (اعتماد کن، اما راستیآزمایی هم بکن)؛ -چند ادغام «بدیهی» مشخص شد که بدهی پنهانی دارند و **جایگزینهای مستقیم و بیدردسری نیستند**. +هر گزینه در 2026-06-17 در برابر وضعیت زندهی gate اعتبارسنجی شد (اعتماد، همراه با راستیآزمایی)؛ +مشخص شد چندین ادغام «بدیهی» بدهی پنهان دارند و جایگزینهای مستقیم و **پاکی نیستند**. -- **`check:docs-sync` دو بار اجرا میشود** — یکبار بهصورت مستقل در job مربوط به `lint` و بار دیگر داخل `check:docs-all` (`docs-sync-strict`) و hook مربوط به pre-commit در husky. ✅ **انجام شد** — فراخوانی مستقل `lint` حذف شد. -- **اسکن CVE** — ❌ **ادغام تمیزی نیست.** `audit:deps` در صورت وجود هر CVE با شدت بالا/بحرانی، hard-fail میشود؛ `check:vuln-ratchet` (osv) فقط در صورت وقوع _پسرفت_ نسبت به baseline شکست میخورد (در حال حاضر 1 مورد MODERATE). معناشناسی آنها متفاوت است — حذف `audit:deps` باعث از دست رفتن gate مطلق مربوط به شدت بالا/بحرانی میشود. هر دو حفظ شوند. -- **تشخیص چرخه** — ❌ **ادغام تمیزی نیست.** `check:circular-deps` (dpdm) تعداد **91 چرخه** گزارش میکند (به همین دلیل مشورتی است)؛ بدون برطرفکردن آنها نمیتوان آن را به حالت مسدودکننده ارتقا داد و دامنهی آن از `check:cycles` سبز و گزینششده گستردهتر است. `check:cycles` بهصورت مسدودکننده حفظ شود؛ رفع 91 چرخهی dpdm خود یک مورد مستقل در فهرست معوقه است. -- **پیچیدگی** — ✅ **انجام شد** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): یک پیمایش ESLint، با شمارش بر اساس ruleId تا baselineهای cyclomatic+max-lines و cognitive مستقل بمانند؛ `check:complexity` / `check:cognitive-complexity` جداگانه برای اجرای محلی با `--update` حفظ میشوند. -- **ضدتوهم `/api`** — ✅ **انجام شد** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): یک فهرستبرداری FS از `src/app/api`؛ openapi-routes و docs-symbols همچنان بهطور مستقل گزارش میدهند؛ موارد جداگانه برای اجراهای محلی حفظ میشوند. -- **`check:node-runtime` در 11 job اجرا میشود** — ⚠️ **ROI پایین.** هرکدام روی runner جداگانهای اجرا میشوند و زمان بررسی کمتر از 1 ثانیه است؛ صرفهجویی کلی حدود 10 ثانیه خواهد بود، درحالیکه یک محافظ ارزانقیمت برای هر job از دست میرود. ارزش این تغییر را ندارد. -- **`typecheck:noimplicit:core` در lint مربوط به CI** — ✅ **از job مربوط به lint حذف شد** (قبلاً با `continue-on-error` مشورتی بود)؛ سطح type مسدودکننده شامل `typecheck:core` + `check:type-coverage` است. اسکریپت محلی حفظ شد. +- **`check:docs-sync` دو بار اجرا میشود** — یک بار بهصورت مستقل در job مربوط به `lint` و بار دیگر درون `check:docs-all` (`docs-sync-strict`) و hook مربوط به pre-commit در husky. ✅ **انجام شد** — فراخوانی مستقل از `lint` حذف شد. +- **اسکن CVE** — ❌ **ادغام پاکی نیست.** `audit:deps` در صورت وجود هر CVE با شدت بالا/بحرانی، قاطعانه شکست میخورد؛ `check:vuln-ratchet` (osv) تنها در صورت وجود یک _پسرفت_ نسبت به خط مبنا شکست میخورد (در حال حاضر 1 مورد MODERATE). معناشناسی آنها متفاوت است — حذف `audit:deps` باعث از دست رفتن gate مطلق مربوط به شدت بالا/بحرانی میشود. هر دو حفظ شوند. +- **تشخیص چرخه** — ✅ **انجام شد** (#15159 G-01/G-02). متن قدیمی این بخش، `check:cycles` را gate «سبز و گزینششده» مینامید و مسدودکننده ماندن آن را با این استدلال توجیه میکرد که `check:circular-deps` (dpdm) تعداد 91 چرخه گزارش میدهد. آن وضعیت سبز، یک **سبز کاذب** بود: `check:cycles` تعداد 5 زیرشاخه (450 فایل) را اسکن میکرد، تنها با `import|export … from` ایستا تطبیق مییافت و همهی specifierهای `@/` و `@omniroute/open-sse/` را کنار میگذاشت؛ بنابراین نمیتوانست چرخههای import پویا + alias را که در مخزن غالب بودند ببیند. اصلاح شد: اکنون gate در `src` + `open-sse` پیمایش میکند (5023 فایل)، specifierها را از AST مربوط به TypeScript جمعآوری میکند (بنابراین `import("…")` محاسبه میشود، اما `typeof import("…")` در جایگاه type محاسبه نمیشود) و `paths` مربوط به tsconfig را resolve میکند. این gate **14** چرخه پیدا میکند، نه 0. از آنجا که 14 چرخهی ازپیشموجود را نمیتوان در یک PR مربوط به gate اصلاح کرد، اکنون `check:cycles` یک **ضامن افزایشی** است (`--ratchet`، سقف `metrics.cycles.value = 14` در `quality-baseline.json`، با `direction: down`) — هرگونه _پسرفت_ را مسدود میکند و تعداد چرخهها فقط میتواند کاهش یابد. CI دستور `npm run check:cycles:ratchet` را اجرا میکند. کاهش تدریجی همراه با **A-01** انجام میشود. `check:circular-deps` (dpdm) بهعنوان دیدگاه دوم و گستردهتر، مشورتی باقی میماند. +- **پیچیدگی** — ✅ **انجام شد** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): یک پیمایش ESLint، با شمارش بر اساس ruleId تا خطوط مبنای cyclomatic+max-lines و cognitive مستقل بمانند؛ `check:complexity` / `check:cognitive-complexity` منفرد برای اجرای محلی `--update` باقی میمانند. +- **ضد توهمزایی `/api`** — ✅ **انجام شد** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): یک فهرستبرداری FS از `src/app/api`؛ openapi-routes + docs-symbols همچنان مستقل گزارش میدهند؛ موارد منفرد برای اجراهای محلی باقی میمانند. +- **`check:node-runtime` در 11 job اجرا میشود** — ⚠️ **ROI پایین.** هرکدام runner جداگانهای دارند و بررسی کمتر از 1 ثانیه طول میکشد؛ صرفهجویی کل حدود 10 ثانیه است، در مقابل از دست دادن یک محافظ ارزان برای هر job. ارزش آشفتگی ناشی از تغییر را ندارد. +- **`typecheck:noimplicit:core` در lint مربوط به CI** — ✅ **از job مربوط به lint حذف شد** (مشورتی و دارای `continue-on-error` بود)؛ سطح type مسدودکننده عبارت است از `typecheck:core` + `check:type-coverage`. اسکریپت محلی حفظ شد. ### تغییر وضعیت / تصمیمگیری (سیاست اپراتور) -- `check:openapi-security-tiers` (مشورتی) — ❌ **نمیتوان وضعیت آن را بهسادگی تغییر داد.** با کد 0 خارج میشود، اما هشدار میدهد که چند route مربوط به `traffic-inspector` تحت `LOCAL_ONLY_API_PREFIXES` فاقد annotation مربوط به `x-loopback-only: true` هستند. اجباریکردن آن مستلزم افزودن آن annotationها به `openapi.yaml` است. -- `typecheck:noimplicit:core` (مشورتی) — تا حد زیادی توسط ratchet مسدودکنندهی `check:type-coverage` پوشش داده میشود. آن را به ratchet تبدیل کنید یا اجرای دوم و تکراری `tsc` را حذف کنید. -- `test:vitest:ui` (اکنون **مسدودکننده**) — شکستهای ازپیشموجود در `vitest.config.ts` بهطور صریح با کامنتهای پیگیری `// #8618` مستثنا شدهاند؛ شکستهای جدید باعث شکست job میشوند. -- `check:secrets` (gitleaks، یک ratchet مسدودکننده که روی 3 مثبت کاذب مستندشده ثابت شده است) — آن 3 مورد را در allowlist قرار دهید تا به 0 برسد، یا آن را به حالت مشورتی تنزل دهید. با secret-scanning بومی GitHub و `check:public-creds` همپوشانی دارد. -- `check:pr-evidence` (مسدودکننده، متن بدنهی PR را با grep جستوجو میکند) — ریسک مثبت کاذب بالایی دارد؛ حذف آن اجرای Hard Rule #18 را تضعیف میکند، بنابراین این واقعاً یک تصمیم سیاستی است. -- `semgrep` (مستقل و مشورتی) — برای خانوادههای OWASP با CodeQL همپوشانی دارد؛ baseline آن را به یک ratchet متصل کنید یا حذفش کنید. +- `check:openapi-security-tiers` (مشورتی) — ❌ **نمیتوان وضعیت آن را بهصورت پاک تغییر داد.** با کد 0 خارج میشود، اما هشدار میدهد که چندین route مربوط به `traffic-inspector` ذیل `LOCAL_ONLY_API_PREFIXES` فاقد annotation مربوط به `x-loopback-only: true` هستند. اعمال اجباری آن مستلزم افزودن این annotationها به `openapi.yaml` است. +- `typecheck:noimplicit:core` (مشورتی) — تا حد زیادی تحت پوشش ضامن افزایشی مسدودکنندهی `check:type-coverage` قرار دارد. آن را به ضامن افزایشی تبدیل کنید یا دومین اجرای زائد `tsc` را حذف کنید. +- `test:vitest:ui` (اکنون **مسدودکننده**) — شکستهای ازپیشموجود در `vitest.config.ts` با commentهای ردیابی `// #8618` صراحتاً مستثنا شدهاند؛ شکستهای جدید باعث شکست job میشوند. +- `check:secrets` (gitleaks، ضامن افزایشی مسدودکننده که روی 3 مثبت کاذب مستندشده ثابت شده است) — آن 3 مورد را در allowlist قرار دهید تا به 0 برسد، یا آن را به حالت مشورتی تنزل دهید. با secret-scanning بومی GitHub و `check:public-creds` همپوشانی دارد. +- `check:pr-evidence` (مسدودکننده، متن بدنهی PR را با grep بررسی میکند) — ریسک مثبت کاذب بالایی دارد؛ حذف آن اجرای Hard Rule #18 را تضعیف میکند، بنابراین این مورد واقعاً یک تصمیم سیاستی است. +- `semgrep` (مستقل و مشورتی) — برای خانوادههای OWASP با CodeQL همپوشانی دارد؛ خط مبنای آن را به یک ضامن افزایشی متصل کنید یا حذفش کنید. --- diff --git a/docs/i18n/fa/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/fa/docs/compression/COMPRESSION_GUIDE.md index 5a80d6821ac2..3122cfda7f9d 100644 --- a/docs/i18n/fa/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/fa/docs/compression/COMPRESSION_GUIDE.md @@ -455,9 +455,7 @@ const { messages: aged, saved } = applyAging(messages, { در یک بدنه با `messages`، یک دور زدن محتوا (`shouldBypassCavemanOutputMode()` در `open-sse/services/compression/outputMode.ts`) سه پیام آخر را بررسی میکند و در صورتی که با کلمات کلیدی امنیتی، اقدام برگشتناپذیر، شفافسازی یا حساس به ترتیب -آن مطابقت داشته باشند، سبکها را برای کل نوبت نادیده میگیرد. این دور زدن -صرف نظر از تنظیمات **Auto-Clarity Bypass** داشبورد (`cavemanOutputMode.autoClarity`) -اجرا میشود. +آن مطابقت داشته باشند، سبکها را برای کل نوبت نادیده میگیرد. این دور زدن تا زمانی اجرا میشود که کلید **Auto-Clarity Bypass** داشبورد (`cavemanOutputMode.autoClarity`) روشن باشد که حالت پیشفرض است؛ با خاموش بودن کلید، سبکهای انتخابشده روی آن نوبتها هم اعمال میشوند. هنگامی که دور زدن اجازه عبور نوبت را میدهد، `placeSystemInstruction()` (همان فایل)، که هرگز یک `messages[0]` جدید ایجاد نمیکند، بلوک را در اولین مورد از موارد زیر که پیدا میکند، قرار میدهد: diff --git a/docs/i18n/fa/docs/reference/ENVIRONMENT.md b/docs/i18n/fa/docs/reference/ENVIRONMENT.md index 482fbfd32efa..cb1d62e9ae35 100644 --- a/docs/i18n/fa/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/fa/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. اسرار الزامی -این مقادیر **باید** پیش از نخستین اجرا تنظیم شوند. بدون آنها، برنامه یا از اجرا خودداری میکند یا با تنظیمات پیشفرض ناامن کار خواهد کرد. - -| متغیر | الزامی | پیشفرض | فایل منبع | توضیحات | -| ---------------------------- | ---------------------- | -------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **بله** | _(هیچکدام)_ | `src/lib/auth` | برای امضا/اعتبارسنجی تمام کوکیهای نشست داشبورد (JWT) استفاده میشود. با `openssl rand -base64 48` تولید کنید. | -| `API_KEY_SECRET` | **بله** | _(هیچکدام)_ | `src/lib/db/apiKeys.ts` | کلید رمزنگاری AES برای مقادیر کلید API ذخیرهشده در SQLite. با `openssl rand -hex 32` تولید کنید. | -| `INITIAL_PASSWORD` | **بله** | `CHANGEME` | اسکریپت راهاندازی اولیه | گذرواژه اولیه مدیر داشبورد را تعیین میکند (با مقدار پیشفرض `.env.example` مطابقت دارد — عمداً ناامن نگه داشته شده است تا تغییر آن اجباری باشد). **پیش از نخستین استفاده آن را تغییر دهید.** پس از ورود، از مسیر داشبورد ← تنظیمات ← امنیت آن را تغییر دهید. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **بله** (محیط عملیاتی) | _(تنظیمنشده)_ | `src/app/api/internal/codex-responses-ws/route.ts` | راز مشترک برای پل داخلی WebSocket مربوط به Codex Responses. درخواستهای پل میان رله WS در Electron/مرورگر و OmniRoute را احراز هویت میکند. ⚠️ **در محیط عملیاتی الزامی است — اگر تنظیم نشده باشد، تمام درخواستهای پل WS رد میشوند.** با `openssl rand -base64 32` تولید کنید. | -| `OMNIROUTE_SW_BUILD_ID` | خیر | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | شناسه صریح ابطال کش service worker برای پوسته PWA (#11779)؛ نخستین گزینه در زنجیره تعیین مقدار. | -| `SOURCE_VERSION` | خیر | _(تنظیمنشده)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | دومین گزینه در زنجیره — توسط سازندگان PaaS (برای مثال، به سبک Heroku) روی commit استقراریافته تنظیم میشود. | -| `NEXT_PUBLIC_SW_BUILD_ID` | خیر | _(مشتقشده)_ | `src/shared/components/PwaRegister.tsx` | مقدار عمومی زمان build که کلاینت برای ثبت `/sw.js?v=…` استفاده میکند؛ ابتدا از دو مقدار بالا و سپس از git SHA مشتق میشود. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | خیر (خودکار) | _(خودکار در هر راهاندازی)_ | `src/server/authz/policies/management.ts` | راز مختص هر فرایند که ثابت میکند مُهر IP همتای مورداعتماد از سرور HTTP خود OmniRoute (`scripts/dev/peer-stamp.mjs`) آمده است. میانافزار authz تنها زمانی به محلیبودن درخواست اعتماد میکند (محدودسازی مسیرهای LOCAL_ONLY به loopback/LAN) که مُهر حاوی این توکن باشد. در هر راهاندازی بهطور خودکار تولید میشود — آن را تنظیمنشده باقی بگذارید؛ فقط برای پیکربندیهای چندفرایندی که باید مُهر مشترکی داشته باشند، مقدار آن را ثابت کنید. | -| `OMNIROUTE_TRUSTED_PROXIES` | خیر | _(تنظیمنشده)_ | `scripts/dev/peer-stamp.mjs` | فهرستی با جداکننده ویرگول از IPها یا محدودههای CIDR مربوط به reverse proxyهایی که فهرست مجاز/مسدود IP میتواند به `X-Forwarded-For` / `X-Real-IP` آنها اعتماد کند؛ علاوه بر آدرسهای loopback، شبکه خصوصی و لبههای Cloudflare که همیشه مورداعتماد هستند. این مقدار فقط برای proxy روی هر آدرس عمومی دیگری لازم است؛ در غیر این صورت، فیلتر آدرس خود آن proxy را ارزیابی میکند. | +رمزهای تنظیمنشده که با **بله** علامت خوردهاند، در نخستین اجرا بهطور خودکار ساخته میشوند (`JWT_SECRET`, `API_KEY_SECRET`) و در `server.env` در پوشه داده ذخیره میشوند — اگر آن پوشه موقتی است، آنها را صریحاً تنظیم کنید. استثنا `OMNIROUTE_WS_BRIDGE_SECRET` است: در `server.env` ذخیره نمیشود — نقاط ورودی سرور در هر بوت مقداری موقت میسازند که فقط پل درون فرآیند میتواند از آن استفاده کند؛ بنابراین کلاینتهای بیرونی پل باید آن را صریحاً تنظیم کنند. + +| متغیر | الزامی | پیشفرض | فایل منبع | توضیحات | +| ---------------------------- | ---------------------- | -------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **بله** | _(هیچکدام)_ | `src/lib/auth` | برای امضا/اعتبارسنجی تمام کوکیهای نشست داشبورد (JWT) استفاده میشود. با `openssl rand -base64 48` تولید کنید. | +| `API_KEY_SECRET` | **بله** | _(هیچکدام)_ | `src/lib/db/apiKeys.ts` | کلید رمزنگاری AES برای مقادیر کلید API ذخیرهشده در SQLite. با `openssl rand -hex 32` تولید کنید. | +| `INITIAL_PASSWORD` | خیر | _(تنظیمنشده)_ | `src/lib/auth/managementPassword.ts` | در صورت اینکه هنوز گذرواژهای ذخیره نشده باشد، گذرواژه داشبورد را هنگام راهاندازی تعیین میکند؛ پس از ذخیرهشدن گذرواژه نادیده گرفته میشود. نصبهای npm و سورس، `.env.example` را در `.env` کپی میکنند و بنابراین با جایگزین شناختهشده عمومی آن یعنی `CHANGEME` شروع میشوند: پیش از نخستین بوت آن را جایگزین کنید، یا در همان `.env` مقدار `INITIAL_PASSWORD=` (خالی) تنظیم کنید تا گذرواژه در نخستین بازدید در راهنمای راهاندازی ساخته شود، همانطور که مقدار تنظیمنشده (پیشفرض ایمیج Docker) این کار را میکند. گذرواژه ذخیرهشده را در داشبورد ← تنظیمات ← امنیت تغییر دهید، یا دستور `omniroute reset-password` را (`node bin/reset-password.mjs` در نسخه سورس) با `DATA_DIR` برابر پوشه داده سرور اجرا کنید. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **بله** (محیط عملیاتی) | _(تنظیمنشده)_ | `src/app/api/internal/codex-responses-ws/route.ts` | راز مشترک برای پل داخلی WebSocket مربوط به Codex Responses. درخواستهای پل میان رله WS در Electron/مرورگر و OmniRoute را احراز هویت میکند. ⚠️ **در محیط عملیاتی الزامی است — اگر تنظیم نشده باشد، تمام درخواستهای پل WS رد میشوند.** با `openssl rand -base64 32` تولید کنید. | +| `OMNIROUTE_SW_BUILD_ID` | خیر | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | شناسه صریح ابطال کش service worker برای پوسته PWA (#11779)؛ نخستین گزینه در زنجیره تعیین مقدار. | +| `SOURCE_VERSION` | خیر | _(تنظیمنشده)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | دومین گزینه در زنجیره — توسط سازندگان PaaS (برای مثال، به سبک Heroku) روی commit استقراریافته تنظیم میشود. | +| `NEXT_PUBLIC_SW_BUILD_ID` | خیر | _(مشتقشده)_ | `src/shared/components/PwaRegister.tsx` | مقدار عمومی زمان build که کلاینت برای ثبت `/sw.js?v=…` استفاده میکند؛ ابتدا از دو مقدار بالا و سپس از git SHA مشتق میشود. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | خیر (خودکار) | _(خودکار در هر راهاندازی)_ | `src/server/authz/policies/management.ts` | راز مختص هر فرایند که ثابت میکند مُهر IP همتای مورداعتماد از سرور HTTP خود OmniRoute (`scripts/dev/peer-stamp.mjs`) آمده است. میانافزار authz تنها زمانی به محلیبودن درخواست اعتماد میکند (محدودسازی مسیرهای LOCAL_ONLY به loopback/LAN) که مُهر حاوی این توکن باشد. در هر راهاندازی بهطور خودکار تولید میشود — آن را تنظیمنشده باقی بگذارید؛ فقط برای پیکربندیهای چندفرایندی که باید مُهر مشترکی داشته باشند، مقدار آن را ثابت کنید. | +| `OMNIROUTE_TRUSTED_PROXIES` | خیر | _(تنظیمنشده)_ | `scripts/dev/peer-stamp.mjs` | فهرستی با جداکننده ویرگول از IPها یا محدودههای CIDR مربوط به reverse proxyهایی که فهرست مجاز/مسدود IP میتواند به `X-Forwarded-For` / `X-Real-IP` آنها اعتماد کند؛ علاوه بر آدرسهای loopback، شبکه خصوصی و لبههای Cloudflare که همیشه مورداعتماد هستند. این مقدار فقط برای proxy روی هر آدرس عمومی دیگری لازم است؛ در غیر این صورت، فیلتر آدرس خود آن proxy را ارزیابی میکند. | ### فرمانهای تولید @@ -215,38 +215,38 @@ OmniRoute برای تمام دادههای ماندگار از **SQLite** (از ## 4. امنیت و احراز هویت -| متغیر | مقدار پیشفرض | فایل منبع | توضیحات | -| ------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt ترکیبشده با شناسههای سختافزاری برای ایجاد اثر انگشت ماشین. برای جداسازی، آن را در هر استقرار تغییر دهید. | -| `OMNIROUTE_CLI_SALT` | _(تنظیمنشده = Salt تصادفی مختص هر نصب که در `/cli-token-salt.json` ذخیره میشود)_ | `src/lib/machineToken.ts` | Salt مربوط به HMAC برای استخراج توکن احراز هویت CLI محلی. تنظیم این مقدار، همه توکنهای CLI روی ماشین را تعویض میکند و همیشه نسبت به Salt ذخیرهشده اولویت دارد. به `docs/security/CLI_TOKEN.md` مراجعه کنید. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | پرچم `Secure` را روی کوکیهای نشست تنظیم میکند. هنگام اجرا پشت HTTPS، **باید `true` باشد**. | -| `REQUIRE_API_KEY` | `false` | میانافزار API | وقتی `true` باشد، همه درخواستهای پروکسی `/v1/*` باید شامل یک کلید API معتبر باشند. **این پرچم دسترسی به `GET /v1/models` را کنترل نمیکند**؛ این مسیر در عوض از وضعیت ورود داشبورد (`requireAuthForModels`) پیروی میکند—بنابراین دریافت `401` از `/v1/models` به این معنا نیست که استنتاج محافظت شده است. به `docs/security/INFERENCE_AUTH_POSTURE.md` مراجعه کنید (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | امکان نمایش کامل مقادیر کلید API را در رابط کاربری داشبورد فراهم میکند. از طریق پرچمهای قابلیت داشبورد قابل پیکربندی است؛ در نمونههای اشتراکی خطر امنیتی دارد. | -| `NO_LOG_API_KEY_IDS` | _(خالی)_ | `src/lib/compliance/index.ts` | شناسههای کلید API جداشده با ویرگول که ثبت درخواست را دور میزنند (برای انطباق با GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(تنظیمنشده = نامحدود)_ | `src/shared/utils/apiKeyPolicy.ts` | بودجه جایگزین روزانه درخواست که برای کلیدهای API با ستون `rate_limits` برابر با null اعمال میشود. تنظیمنشده یا خالی: بدون محدودیت ضمنی (#2289، #11017). مقدار `0` نیز همین معنا را دارد (نامحدود). عدد صحیح مثبت N، محدودیت N در روز، 5N در هفته و 20N در ماه را فعال میکند. مقادیر غیرخالیِ نامعتبر به محدودیتهای قدیمی 1000 در روز، 5000 در هفته و 20000 در ماه بازمیگردند. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | حداکثر اندازه مجاز بدنه درخواست. محمولههایی را که از این محدودیت فراتر روند رد میکند. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | بدنههای واقعی درخواست با اندازه برابر یا بیشتر از این آستانه، پیش از تجزیه JSON، اجاره پذیرش سنگینوزن اتمی و محلیِ فرایند را دریافت میکنند (مسیر BYTE، شامل `POST /v1/responses`). همان راه گریزِ فضای آزاد سالم [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) که برای ساختارهای سنگین استفاده میشود؛ همچنان توسط `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) محدود است تا مشکل [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) دوباره رخ ندهد. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | سقف سخت مسیر چت که بر اساس بایتهای خواندهشده طی دریافت کراندار اعمال میشود؛ از جمله برای درخواستهایی که `Content-Length` ندارند یا مقدار آن نامعتبر یا خلاف واقع است؛ موارد بیشازحد پاسخ `413` دریافت میکنند. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(تنظیمنشده — بدون سقف تعداد درخواست)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** این سقف قدیمیِ تعداد درخواست اکنون فقط زمانی اعمال میشود که صراحتاً تنظیم شده باشد. وقتی تنظیم نشده باقی بماند (حالت پیشفرض)، پذیرش چتهای سنگین در عوض با `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` کنترل میشود — بودجهای بر حسب بایت که بهطور خودکار بر اساس سقف واقعی حافظهٔ فرایند در **یک فرایند** (یک heap از V8) تعیین میشود. دو درخواست همپوشان `/v1/responses` با حدود 750k توکن، heapهای تقریباً 12 GiB را از کار میاندازند (#7849) — این یک هشدار دربارهٔ **بودجهٔ حافظه** است، نه سقف سخت محصول برابر با 2. یک فرایند سالم (با heap پایینتر از نسبت کاهش بار) ممکن است از طریق `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` تعداد بیشتری درخواست طولانیِ همزمان `/v1/responses` را بپذیرد. دهها کلاینت طولانی SSE (40–50) به heap و `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 مربوط است، نه «حداکثر 2». افزایش کورکورانهٔ این مقدار برای «استفاده از میزبان» دوباره مشکل #7849 را ایجاد میکند. **heapها** را با **N عدد `DATA_DIR` مستقل** تکثیر کنید (#11024)؛ هرگز روی یک فایل SQLite از `replicas>1` استفاده نکنید. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(مشتقشده بهصورت خودکار)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** بازنویسی بودجهٔ بایتی دریافت که بهصورت خودکار مشتق میشود (25٪ از محدودترِ سقف حافظهٔ V8/cgroup، تقسیم بر ضریب تقویت موقتی 8x). مقادیر مشتقشده و صریح به بازهٔ 8 MiB تا 2 GiB محدود میشوند. بدنهای بزرگتر از بودجهٔ مؤثر، بلافاصله با `413 body_exceeds_budget` شکست میخورد؛ رقابت میان بدنههایی که هرکدام بهتنهایی قابل سرویسدهی هستند همچنان با `503` قابل تلاش مجدد است. 40–50 کلاینت طولانیِ همزمان SSE حاصل این بودجه بهعلاوهٔ heap است، نه یک «حداکثر 2» سخت. پیش از تنظیم، `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` را در `/api/monitoring/health` بخوانید. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | نسبت کاهش بار تحت فشار heap (`heapUsed / heap_size_limit`) برای پذیرش سنگینِ BYTE و STRUCTURE (#10183، #10268، #10437). یک درخواست سنگین همزمان که از `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` عبور کرده باشد، فقط زمانی با `503` قابل تلاش مجدد کنار گذاشته میشود که heap نیز در این نسبت یا بالاتر از آن باشد؛ در heap سالم، درخواست از طریق فضای اضافهٔ سالم پذیرفته میشود. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (پیشفرض `1`) | `src/shared/middleware/chatBodyAdmission.ts` | ظرفیت اضافی کراندار برای مسیر سریعِ heap سالم (#10437)، هم برای STRUCTURE و هم BYTE (`admitChatRequest`، شامل بدنههای ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). بدون این کران، هر درخواست در حالت شلوغ ولی با heap سالم، پذیرش را بدون هیچ سقفی دور میزد. پس از فعال شدن این تعداد lease همزمان از طریق مسیر heap سالم، درخواستهای شلوغِ بعدی به همان مسیر انتظار کراندار/کاهش بار میروند که هنگام فشار واقعی heap استفاده میشود. `0` این میانبُر را کاملاً غیرفعال میکند. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | تعداد پیامهایی که باعث میشود یک درخواست چت حتی در صورت کمتر بودن بدنهاش از آستانهٔ بایتی، سنگین طبقهبندی شود. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | تعداد ابزارهایی که باعث میشود یک درخواست چت حتی در صورت کمتر بودن بدنهاش از آستانهٔ بایتی، سنگین طبقهبندی شود. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | برآورد محافظهکارانهٔ توکن بر اساس اندازهٔ رشته که یک درخواست را سنگین طبقهبندی میکند؛ این یک شاخص تقریبی هزینهٔ پذیرش است، نه توکنسازی صورتحساب ارائهدهنده. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (غیرفعال) | `src/shared/middleware/chatBodyAdmission.ts` | سقف اختیاری و نیازمند فعالسازی برای تاریخچهٔ چت. بهطور پیشفرض غیرفعال است: تعداد پیام یک سیاست استقرار است، نه ویژگی همگانی یک درخواست، و اعمال سقف در اینجا مکالمهها را پیش از آنکه خط لولهٔ فشردهسازی بتواند آنها را قابل سرویسدهی کند، با `413` نهایی رد میکند. رشد heap با `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` و کاهش بار تحت فشار heap کراندار میشود. در استقرارهای دارای محدودیت حافظه که به سقف سخت نیاز دارند، یک مقدار مثبت تنظیم کنید؛ موارد بیشازحد سپس پاسخ ساختیافتهٔ `413` با الزام فشردهسازی دریافت میکنند. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | سقف سخت برای پاسخ بالادستیِ غیرجریانی که بهطور کامل در حافظه بافر میشود. پس از عبور از این سقف، خوانندهٔ بالادستی لغو میشود و درخواست سریعاً شکست میخورد، بهجای آنکه یک رشتهٔ بدون کران تا زمان تمام شدن heap رشد کند. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | حداکثر تعداد بایتهای ارسالی که از سرآیندهای پاسخ بالادستی منتقل میشوند. وقتی از این سقف عبور شود، سرآیندهای با اولویت پایینتر (برای مثال، `x-codex-*` سفارشی و `x-oai-request-id`) حذف میشوند تا محدودیتهای رایج سرآیند در پروکسی معکوس رعایت شوند. برای انتقال فرادادههای بیشتر از بالادست، این مقدار را افزایش دهید؛ البته به بهای افزایش اندازه سرآیند پاسخ. | -| `CORS_ORIGIN` | _(تنظیمنشده)_ | `src/server/cors/origins.ts` | فهرست مجاز قدیمی CORS برای یک مبدأ واحد. برای استقرارهای جدید، `CORS_ALLOWED_ORIGINS` را ترجیح دهید. CORS فقط برای کلاینتهای API مرورگر با مبدأ متفاوت است؛ عملیات نوشتن احراز هویتشده در داشبورد، در عوض از درخواستهای هممبدأ بههمراه محافظت CSRF متصل به نشست استفاده میکنند. | -| `CORS_ALLOWED_ORIGINS` | _(تنظیمنشده)_ | `src/server/cors/origins.ts` | فهرست مجاز CORS با مقادیر جداشده با ویرگول. هیچ نویسه عامی ارسال نمیشود، مگر اینکه `CORS_ALLOW_ALL=true` صراحتاً پیکربندی شده باشد. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | راه گریز مختص محیط توسعه برای بازتاب هر `Origin` مرورگر. آن را در استقرارهای اشتراکی یا عملیاتی فعال نکنید. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | فراخوانیهای ارائهدهنده را که محدودههای IP خصوصی، loopback یا link-local را هدف میگیرند، مسدود میکند. فقط در محیطهای آزمایشی ایزوله غیرفعال کنید. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | URLهای ارائهدهنده را که به شبکههای خصوصی/محلی اشاره میکنند (localhost، 192.168.x.x، 10.x.x.x و غیره) مجاز میکند. **برای ارائهدهندگان خودمیزبان الزامی است** (LM Studio، Ollama، vLLM، Llamafile، Triton، SearXNG). وقتی مقدار `false` باشد، داشبورد اعتبارسنجی URLهای محلی را رد میکند. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | افزودن/اعتبارسنجی ارائهدهندگان روی آدرسهای محلی/خصوصی (127.0.0.1، localhost، LAN، محدودههای خصوصی) را مجاز میکند — با دامنه محدود به مسیر اعتبارسنجی ارائهدهنده. **مقدار پیشفرض `true` است** (اولویت با محیط محلی)؛ برای اعمال مسدودسازی سختگیرانه و صرفاً عمومی، آن را روی `false` تنظیم کنید. نقاط پایانی فراداده ابری (169.254.169.254، metadata.google.internal) در هر صورت مسدود باقی میمانند. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | به مسیرهای `/v1/audio/*` (رونویسی، گفتار و ترجمه) اجازه میدهد از یک گره ارائهدهنده سازگار با OpenAI که خارج از localhost میزبانی میشود، استفاده کنند. بهطور پیشفرض غیرفعال است — مسیریابی صوت به میزبان راهدور، هویت خروجی را تغییر میدهد و باید تصمیمی صریح از سوی اپراتور باشد. گرههای loopback/خصوصی (localhost، 127.0.0.1، 172.16-31.x) همیشه مجاز و بدون تأثیر هستند. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | به `POST /v1/rerank` (و مرحله بازرتبهبندی loopback موتور حافظه) اجازه میدهد از یک گره ارائهدهنده سازگار با OpenAI که خارج از localhost میزبانی میشود استفاده کند — یک دستگاه LAN یا همتای Tailscale که TEI، Infinity، vLLM و غیره را اجرا میکند. بهطور پیشفرض غیرفعال است — مسیریابی به میزبان راهدور، هویت خروجی را تغییر میدهد و باید تصمیمی صریح از سوی اپراتور باشد. گرههای loopback (localhost، 127.0.0.1، 172.16-31.x) همیشه مجاز و بدون تأثیر هستند. گرههای راهدور همچنین باید از سیاست URL خروجی ارائهدهنده (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) عبور کنند؛ هیچگاه به میزبانهای فراداده ابری مسیریابی نمیشود. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | وقتی OIDC فعال است، ورود با گذرواژه را غیرفعال میکند تا کاربران فقط بتوانند از طریق ورود یکپارچه OIDC احراز هویت کنند. نام مستعار ساده `OIDC_DISABLE_PASSWORD_LOGIN` نیز پذیرفته میشود؛ پرچم قابلیت داشبورد با همین کلید اولویت دارد. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | نام مستعار ساده `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| متغیر | مقدار پیشفرض | فایل منبع | توضیحات | +| ------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt ترکیبشده با شناسههای سختافزاری برای ایجاد اثر انگشت ماشین. برای جداسازی، آن را در هر استقرار تغییر دهید. | +| `OMNIROUTE_CLI_SALT` | _(تنظیمنشده = Salt تصادفی مختص هر نصب که در `/cli-token-salt.json` ذخیره میشود)_ | `src/lib/machineToken.ts` | Salt مربوط به HMAC برای استخراج توکن احراز هویت CLI محلی. تنظیم این مقدار، همه توکنهای CLI روی ماشین را تعویض میکند و همیشه نسبت به Salt ذخیرهشده اولویت دارد. به `docs/security/CLI_TOKEN.md` مراجعه کنید. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | پرچم `Secure` را روی کوکیهای نشست تنظیم میکند. هنگام اجرا پشت HTTPS، **باید `true` باشد**. | +| `REQUIRE_API_KEY` | `false` | میانافزار API | وقتی `true` باشد، همه درخواستهای پروکسی `/v1/*` باید شامل یک کلید API معتبر باشند. **این پرچم دسترسی به `GET /v1/models` را کنترل نمیکند**؛ این مسیر در عوض از وضعیت ورود داشبورد (`requireAuthForModels`) پیروی میکند—بنابراین دریافت `401` از `/v1/models` به این معنا نیست که استنتاج محافظت شده است. به `docs/security/INFERENCE_AUTH_POSTURE.md` مراجعه کنید (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | امکان نمایش کامل مقادیر کلید API را در رابط کاربری داشبورد فراهم میکند. از طریق پرچمهای قابلیت داشبورد قابل پیکربندی است؛ در نمونههای اشتراکی خطر امنیتی دارد. | +| `NO_LOG_API_KEY_IDS` | _(خالی)_ | `src/lib/compliance/index.ts` | شناسههای کلید API جداشده با ویرگول که ثبت درخواست را دور میزنند (برای انطباق با GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(تنظیمنشده = نامحدود)_ | `src/shared/utils/apiKeyPolicy.ts` | بودجه جایگزین روزانه درخواست که برای کلیدهای API با ستون `rate_limits` برابر با null اعمال میشود. تنظیمنشده یا خالی: بدون محدودیت ضمنی (#2289، #11017). مقدار `0` نیز همین معنا را دارد (نامحدود). عدد صحیح مثبت N، محدودیت N در روز، 5N در هفته و 20N در ماه را فعال میکند. مقادیر غیرخالیِ نامعتبر به محدودیتهای قدیمی 1000 در روز، 5000 در هفته و 20000 در ماه بازمیگردند. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | حداکثر اندازه مجاز بدنه درخواست. محمولههایی را که از این محدودیت فراتر روند رد میکند. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | بدنههای واقعی درخواست با اندازه برابر یا بیشتر از این آستانه، پیش از تجزیه JSON، اجاره پذیرش سنگینوزن اتمی و محلیِ فرایند را دریافت میکنند (مسیر BYTE، شامل `POST /v1/responses`). همان راه گریزِ فضای آزاد سالم [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) که برای ساختارهای سنگین استفاده میشود؛ همچنان توسط `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) محدود است تا مشکل [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) دوباره رخ ندهد. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | سقف سخت مسیر چت که بر اساس بایتهای خواندهشده طی دریافت کراندار اعمال میشود؛ از جمله برای درخواستهایی که `Content-Length` ندارند یا مقدار آن نامعتبر یا خلاف واقع است؛ موارد بیشازحد پاسخ `413` دریافت میکنند. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(تنظیمنشده — بدون سقف تعداد درخواست)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** این سقف قدیمیِ تعداد درخواست اکنون فقط زمانی اعمال میشود که صراحتاً تنظیم شده باشد. وقتی تنظیم نشده باقی بماند (حالت پیشفرض)، پذیرش چتهای سنگین در عوض با `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` کنترل میشود — بودجهای بر حسب بایت که بهطور خودکار بر اساس سقف واقعی حافظهٔ فرایند در **یک فرایند** (یک heap از V8) تعیین میشود. دو درخواست همپوشان `/v1/responses` با حدود 750k توکن، heapهای تقریباً 12 GiB را از کار میاندازند (#7849) — این یک هشدار دربارهٔ **بودجهٔ حافظه** است، نه سقف سخت محصول برابر با 2. یک فرایند سالم (با heap پایینتر از نسبت کاهش بار) ممکن است از طریق `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` تعداد بیشتری درخواست طولانیِ همزمان `/v1/responses` را بپذیرد. دهها کلاینت طولانی SSE (40–50) به heap و `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 مربوط است، نه «حداکثر 2». افزایش کورکورانهٔ این مقدار برای «استفاده از میزبان» دوباره مشکل #7849 را ایجاد میکند. **heapها** را با **N عدد `DATA_DIR` مستقل** تکثیر کنید (#11024)؛ هرگز روی یک فایل SQLite از `replicas>1` استفاده نکنید. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(مشتقشده بهصورت خودکار)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** بازنویسی بودجهٔ بایتی دریافت که بهصورت خودکار مشتق میشود (25٪ از محدودترِ سقف حافظهٔ V8/cgroup، تقسیم بر ضریب تقویت موقتی 8x). مقادیر مشتقشده و صریح به بازهٔ 8 MiB تا 2 GiB محدود میشوند. بدنهای بزرگتر از بودجهٔ مؤثر، بلافاصله با `413 body_exceeds_budget` شکست میخورد؛ رقابت میان بدنههایی که هرکدام بهتنهایی قابل سرویسدهی هستند همچنان با `503` قابل تلاش مجدد است. 40–50 کلاینت طولانیِ همزمان SSE حاصل این بودجه بهعلاوهٔ heap است، نه یک «حداکثر 2» سخت. پیش از تنظیم، `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` را در `/api/monitoring/health` بخوانید. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | نسبت کاهش بار تحت فشار heap (`heapUsed / heap_size_limit`) برای پذیرش سنگینِ BYTE و STRUCTURE (#10183، #10268، #10437). یک درخواست سنگین همزمان که از `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` عبور کرده باشد، فقط زمانی با `503` قابل تلاش مجدد کنار گذاشته میشود که heap نیز در این نسبت یا بالاتر از آن باشد؛ در heap سالم، درخواست از طریق فضای اضافهٔ سالم پذیرفته میشود. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (پیشفرض `1`) | `src/shared/middleware/chatBodyAdmission.ts` | ظرفیت اضافی کراندار برای مسیر سریعِ heap سالم (#10437)، هم برای STRUCTURE و هم BYTE (`admitChatRequest`، شامل بدنههای ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). بدون این کران، هر درخواست در حالت شلوغ ولی با heap سالم، پذیرش را بدون هیچ سقفی دور میزد. پس از فعال شدن این تعداد lease همزمان از طریق مسیر heap سالم، درخواستهای شلوغِ بعدی به همان مسیر انتظار کراندار/کاهش بار میروند که هنگام فشار واقعی heap استفاده میشود. `0` این میانبُر را کاملاً غیرفعال میکند. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | تعداد پیامهایی که باعث میشود یک درخواست چت حتی در صورت کمتر بودن بدنهاش از آستانهٔ بایتی، سنگین طبقهبندی شود. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | تعداد ابزارهایی که باعث میشود یک درخواست چت حتی در صورت کمتر بودن بدنهاش از آستانهٔ بایتی، سنگین طبقهبندی شود. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | برآورد محافظهکارانهٔ توکن بر اساس اندازهٔ رشته که یک درخواست را سنگین طبقهبندی میکند؛ این یک شاخص تقریبی هزینهٔ پذیرش است، نه توکنسازی صورتحساب ارائهدهنده. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (غیرفعال) | `src/shared/middleware/chatBodyAdmission.ts` | سقف اختیاری و نیازمند فعالسازی برای تاریخچهٔ چت. بهطور پیشفرض غیرفعال است: تعداد پیام یک سیاست استقرار است، نه ویژگی همگانی یک درخواست، و اعمال سقف در اینجا مکالمهها را پیش از آنکه خط لولهٔ فشردهسازی بتواند آنها را قابل سرویسدهی کند، با `413` نهایی رد میکند. رشد heap با `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` و کاهش بار تحت فشار heap کراندار میشود. در استقرارهای دارای محدودیت حافظه که به سقف سخت نیاز دارند، یک مقدار مثبت تنظیم کنید؛ موارد بیشازحد سپس پاسخ ساختیافتهٔ `413` با الزام فشردهسازی دریافت میکنند. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | سقف سخت برای پاسخ بالادستیِ غیرجریانی که بهطور کامل در حافظه بافر میشود. پس از عبور از این سقف، خوانندهٔ بالادستی لغو میشود و درخواست سریعاً شکست میخورد، بهجای آنکه یک رشتهٔ بدون کران تا زمان تمام شدن heap رشد کند. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | حداکثر تعداد بایتهای ارسالی که از سرآیندهای پاسخ بالادستی منتقل میشوند. وقتی از این سقف عبور شود، سرآیندهای با اولویت پایینتر (برای مثال، `x-codex-*` سفارشی و `x-oai-request-id`) حذف میشوند تا محدودیتهای رایج سرآیند در پروکسی معکوس رعایت شوند. برای انتقال فرادادههای بیشتر از بالادست، این مقدار را افزایش دهید؛ البته به بهای افزایش اندازه سرآیند پاسخ. | +| `CORS_ORIGIN` | _(تنظیمنشده)_ | `src/server/cors/origins.ts` | فهرست مجاز قدیمی CORS برای یک مبدأ واحد. برای استقرارهای جدید، `CORS_ALLOWED_ORIGINS` را ترجیح دهید. CORS فقط برای کلاینتهای API مرورگر با مبدأ متفاوت است؛ عملیات نوشتن احراز هویتشده در داشبورد، در عوض از درخواستهای هممبدأ بههمراه محافظت CSRF متصل به نشست استفاده میکنند. | +| `CORS_ALLOWED_ORIGINS` | _(تنظیمنشده)_ | `src/server/cors/origins.ts` | فهرست مجاز CORS با مقادیر جداشده با ویرگول. هیچ نویسه عامی ارسال نمیشود، مگر اینکه `CORS_ALLOW_ALL=true` صراحتاً پیکربندی شده باشد. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | راه گریز مختص محیط توسعه برای بازتاب هر `Origin` مرورگر. آن را در استقرارهای اشتراکی یا عملیاتی فعال نکنید. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | نام مستعار قدیمی. کلید تغییر وضعیت داشبورد آن (یک لغو ذخیرهشده در پایگاه داده) پیش از این مقدار محیطی خوانده میشود؛ `false`، `0`، `no` یا `off` در هرکدام، بررسیهای میزبان را همانند `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` خاموش میکند، حتی وقتی کلید داشبوردِ آن پرچم خاموش ذخیره شده باشد؛ بقیه مقادیر محافظ را به دو پرچم زیر واگذار میکنند. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | بررسیهای میزبان محافظ URL خروجی، از جمله مسدودسازی فراداده ابری (کل 169.254.0.0/16 و metadata.google.internal)، را در مسیرهای ارائهدهنده که از حالت محافظ پیروی میکنند خاموش میکند: اعتبارسنجی URL، کشف مدلها، URLهای پایه گرههای ارائهدهنده (شامل گرههای راهدور بازرتبهبندی) و آزمون proxy-fallback، در میان مسیرهای خروجی دیگر مجاور ارائهدهنده مانند واکشی remote تصویر/رسانه. مقاصد خصوصی webhook را نیز مجاز میکند، بهجز نقاط پایانی فراداده ابری/link-local که حتی با روشنبودن این پرچم مسدود میمانند. درخواستهای چتی که از `BaseExecutor.execute()` ارسال میشوند بررسی فراداده ابری خود را حفظ میکنند. ارائهدهندگان محلی با پیشفرضها کار میکنند: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (پیشفرض `true`) از قبل URLهای محلی و LAN را در مسیرهای اعتبارسنجی، کشف و گرههای ارائهدهنده مجاز میکند؛ آزمون proxy-fallback و مقاصد خصوصی webhook فقط این پرچم را میبینند و تا زمانی که خاموش است میزبانهای محلی/LAN را مسدود میکنند. بر `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` اولویت دارد. مقدار ذخیرهشده از کلید داشبورد بر این متغیر مقدم است. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | URLهای ارائهدهنده روی آدرسهای محلی/خصوصی (127.0.0.1، localhost، LAN، محدودههای خصوصی) را برای اعتبارسنجی ارائهدهنده، کشف مدلها، چت و سایر مسیرهای خروجی مجاور ارائهدهنده که حالت محافظ بر آنها حاکم است مجاز میکند. **پیشفرض `true`** (اولویت با محیط محلی): در این حالت محافظ نقاط پایانی فراداده ابری را مسدود میکند — کل 169.254.0.0/16 بهعلاوه نامهای میزبان فراداده شناختهشده (metadata.google.internal، metadata.goog، IMDSهای Alibaba/Azure/Oracle، fd00:ec2::254). برای حالت فقطعمومی روی `false` تنظیم کنید که میزبانهای خصوصی و loopback را هم مسدود میکند. محافظ نام میزبان یا لیترال IP را همانطور که نوشته شده، پیش از هر پرسوجوی DNS بررسی میکند؛ در حالت فقطعمومی، پوشش fetch امن همچنین نام میزبان را resolve میکند و هر پاسخ خصوصی را رد میکند. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` این بررسیها را در مسیرهای فهرستشده در ردیف آن خاموش میکند. ارائهدهندگان محلی داخلی مانند LM Studio، Ollama، vLLM، Llamafile و Triton برای اعتبارسنجی کلید و چت همه بررسیهای میزبان را دور میزنند. مقدار ذخیرهشده از کلید داشبورد بر این متغیر مقدم است. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | به مسیرهای `/v1/audio/*` (رونویسی، گفتار و ترجمه) اجازه میدهد از یک گره ارائهدهنده سازگار با OpenAI که خارج از localhost میزبانی میشود، استفاده کنند. بهطور پیشفرض غیرفعال است — مسیریابی صوت به میزبان راهدور، هویت خروجی را تغییر میدهد و باید تصمیمی صریح از سوی اپراتور باشد. گرههای loopback/خصوصی (localhost، 127.0.0.1، 172.16-31.x) همیشه مجاز و بدون تأثیر هستند. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | به `POST /v1/rerank` (و مرحله بازرتبهبندی loopback موتور حافظه) اجازه میدهد از یک گره ارائهدهنده سازگار با OpenAI که خارج از localhost میزبانی میشود استفاده کند — یک دستگاه LAN یا همتای Tailscale که TEI، Infinity، vLLM و غیره را اجرا میکند. بهطور پیشفرض غیرفعال است — مسیریابی به میزبان راهدور، هویت خروجی را تغییر میدهد و باید تصمیمی صریح از سوی اپراتور باشد. گرههای loopback (localhost، 127.0.0.1، 172.16-31.x) همیشه مجاز و بدون تأثیر هستند. URL پایه یک گره راهدور نیز باید از سیاست URL خروجی ارائهدهنده (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) عبور کند. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | وقتی OIDC فعال است، ورود با گذرواژه را غیرفعال میکند تا کاربران فقط بتوانند از طریق ورود یکپارچه OIDC احراز هویت کنند. نام مستعار ساده `OIDC_DISABLE_PASSWORD_LOGIN` نیز پذیرفته میشود؛ پرچم قابلیت داشبورد با همین کلید اولویت دارد. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | نام مستعار ساده `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### چکلیست مقاومسازی diff --git a/docs/i18n/fi/CONTRIBUTING.md b/docs/i18n/fi/CONTRIBUTING.md index a9fa028c96ec..93070c576a65 100644 --- a/docs/i18n/fi/CONTRIBUTING.md +++ b/docs/i18n/fi/CONTRIBUTING.md @@ -388,20 +388,20 @@ Kirjoita hakemistoon `tests/unit/` yksikkötestit, jotka kattavat vähintään s ## Pull Request -tarkistuslista -- [ ] Testit läpäisty (`npm test`) -- [ ] Linttaus läpäisty (`npm run lint`) +- [ ] Testit läpäisevät (`npm test`) +- [ ] Linttaus läpäisee (`npm run lint`) - [ ] Koonti onnistuu (`npm run build`) - [ ] TypeScript-tyypit lisätty uusille julkisille funktioille ja rajapinnoille -- [ ] Ei kovakoodattuja salaisuuksia tai varaarvoja -- [ ] Julkiset upstream-tunnistetiedot upotettu `resolvePublicCred()`-funktion kautta (katso [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ei koskaan literaaleina -- [ ] Virhevastaukset kulkevat `buildErrorBody()`- / `sanitizeErrorMessage()`-funktioiden kautta — vastausten sisällöissä ei ole käsittelemättömiä pinojäljityksiä (katso [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Komentotulkkikomennot (`exec` / `spawn`) välittävät suorituksenaikaiset arvot `env`-ympäristön kautta, eivät merkkijonointerpoloinnilla +- [ ] Ei kovakoodattuja salaisuuksia tai varmistusarvoja +- [ ] Julkiset upstream-tunnistetiedot upotettu `resolvePublicCred()`-funktion avulla (katso [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ei koskaan literaaleina +- [ ] Virhevastaukset käsitellään `buildErrorBody()`- / `sanitizeErrorMessage()`-funktioiden kautta — vastausten sisältö ei sisällä käsittelemättömiä pinojälkiä (katso [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Komentotulkin komennot (`exec` / `spawn`) välittävät ajonaikaiset arvot `env`-muuttujan kautta, eivät merkkijonointerpolaatiolla - [ ] Kaikki syötteet validoitu Zod-skeemoilla -- [ ] Käyttäjille näkyvistä muutoksista lisätty muutoslokin **fragmentti** hakemistoon `changelog.d/{features|fixes|maintenance}/-.md` (katso [`changelog.d/README.md`](./changelog.d/README.md)) — **älä** muokkaa tiedostoa `CHANGELOG.md` suoraan; fragmentit yhdistetään julkaisun yhteydessä, eivätkä ne koskaan aiheuta ristiriitoja PR:ien välillä +- [ ] Käyttäjille näkyvistä muutoksista lisätty muutoslokin **katkelma** hakemistoon `changelog.d/{features|fixes|maintenance}/-.md` (katso [`changelog.d/README.md`](./changelog.d/README.md)) — älä muokkaa `CHANGELOG.md`-tiedostoa suoraan; katkelmat yhdistetään julkaisuhetkellä, eivätkä ne koskaan aiheuta ristiriitoja PR:ien välillä - [ ] Dokumentaatio päivitetty (tarvittaessa) -- [ ] Uusia CodeQL- tai Secret-Scanning-hälytyksiä ei ole avattu, tai jokainen niistä on hylätty teknisin perusteluin ja viittauksin asiaankuuluvaan `docs/security/`-dokumenttiin +- [ ] Uusia CodeQL- tai Secret-Scanning-hälytyksiä ei ole avattu, tai jokainen niistä on kuitattu teknisellä perustelulla, jossa viitataan asianmukaiseen `docs/security/`-dokumenttiin - [ ] Aliprosesseja käynnistävät reitit (`/api/mcp/`, `/api/cli-tools/runtime/`) luokiteltu `isLocalOnlyPath()`-funktion avulla tiedostossa `src/server/authz/routeGuard.ts` — katso [ehdoton sääntö #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Commit-viesteissä ei ole `Co-Authored-By`-trailereita — commitien on näyttävä yksinomaan repositorion omistajan Git-identiteetin alla (ehdoton sääntö #16) +- [ ] Commit-viesteissä ei ole tekoäly-/bottiavustajien `Co-authored-by`-lopputunnisteita (ehdoton sääntö #16) — ihmiskollaboraattorit, joiden työtä hyödynnetään, mainitaan tavanomaisilla `Co-authored-by: Name ` -lopputunnisteilla --- diff --git a/docs/i18n/fi/docs/architecture/QUALITY_GATES.md b/docs/i18n/fi/docs/architecture/QUALITY_GATES.md index 5cf30d35e509..2ed398d2d33e 100644 --- a/docs/i18n/fi/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/fi/docs/architecture/QUALITY_GATES.md @@ -15,50 +15,50 @@ työkaluista riippumaton toisintamissuunnitelma ovat --- -## Porttien inventaario ja suoritusprofiilit +## Porttien luettelo ja suoritusprofiilit ### Ehdokkaiden hyväksyntä CI- ja Quality Gates -työnkulut tuottavat kumpikin vakaan tuloksen: `Gate / CI` ja -`Gate / Quality`. Niiden versioitu hyväksyntäkäytäntö luettelee jokaisen edeltävän työn -pakolliseksi tai neuvoa-antavaksi. Soveltuvan pakollisen työn on onnistuttava: puuttuvat, -peruutetut, ohitetut, odottavat ja tuntemattomat tulokset eivät voi muodostaa PASS-tulosta. Kelvollinen -vain dokumentaatiota tai vain luetteloa koskeva luokitus voi tehdä koodikaistasta soveltumattoman; -luonnos-PR ei ole hyväksytty ehdokas. `hotfix`-tunniste ei poista näyttövaatimusta. +`Gate / Quality`. Niiden versioitu hyväksyntäkäytäntö määrittää jokaisen edeltävän työn +pakolliseksi tai neuvoa-antavaksi. Soveltuvan pakollisen työn on onnistuttava: puuttuva, +peruutettu, ohitettu, keskeneräinen tai tuntematon tulos ei voi vahvistaa PASS-tulosta. Kelvollinen +vain dokumentaatiota tai vain luetteloa koskeva luokitus voi tehdä koodilinjasta soveltumattoman; +luonnostilassa oleva PR ei ole hyväksytty ehdokas. `hotfix`-tunniste ei poista näyttövaatimusta. -Molemmat työnkulut kattavat PR:t ja työnnöt main/release-haaroihin, manuaaliset käynnistykset sekä -merge-group-tapahtumat. Työnnöt, käynnistykset ja merge-group-tapahtumat suorittavat koko valikoiman. Forkit -ja yhdistämisryhmät käyttävät ylläpidettyjä suorittimia töissä, jotka muutoin valitsisivat itse ylläpidetyt -suorittimet; riittävä ylläpidetty kapasiteetti on varmistettava ennen käyttöönottoa. +Molemmat työnkulut kattavat PR:t ja pushit main/release-haaroihin, manuaalisen käynnistyksen sekä +merge-group-tapahtumat. Push, dispatch ja merge-group suorittavat täyden valinnan. Forkit +ja yhdistämisryhmät käyttävät ylläpidettyjä suorittimia töissä, jotka muutoin valitsevat itse ylläpidetyt +suorittimet; ylläpidetyn kapasiteetin riittävyys on varmistettava ennen käyttöönottoa. Jokainen JSON-kuitti yksilöi uloskuitatun SHA:n, työnkulun suorituksen ja yrityksen. -CLI hylkää uloskuittauksen ja tapahtuman SHA-arvojen ristiriidan. Työnkulkutestit sitovat käytäntöön kuulumisen -tulostyön `needs`-luetteloon, jotta uusi tai poistettu kaista ei voi kadota huomaamatta. -Kuitit kattavat oman työnkulkunsa, eivät julkaisua, käyttöönottoa tai olemassa olevan -neuvoa-antavan skannerin sisäistä toimintaa. Molempien tarkistusnimien aktivointi haarasäännöissä on +CLI hylkää tilanteen, jossa uloskuittauksen SHA ja tapahtuman SHA eivät täsmää. Työnkulkutestit sitovat käytäntöjäsenyyden +tulostyön `needs`-luetteloon, jotta uusi tai poistettu linja ei voi kadota huomaamatta. +Kuitit kattavat oman työnkulkunsa, eivät julkaisua, käyttöönottoa tai +olemassa olevan neuvoa-antavan skannerin sisäistä toimintaa. Molempien tarkistusnimien aktivoiminen haarasäännöissä on erillinen hallinnollinen muutos; näiden töiden lisääminen ei itsessään suojaa haaraa. -### Staattisen tarkistuksen inventaario +### Staattisen tarkistuksen luettelo -Versioitu npm-aliasten inventaario ja staattisen tarkistuksen jäsenyys sijaitsevat tiedostossa +Versioitu npm-aliasten luettelo ja staattisen tarkistuksen jäsenyydet sijaitsevat tiedostossa `config/quality/gate-manifest.json`. Suorita `npm run check:gate-manifest` vahvistaaksesi -skriptien nimet ja täsmälliset komennot tiedostoa `package.json` vasten; lisäykset, poistot ja -komentojen poikkeamat aiheuttavat virheen sekä paikallisessa koukussa että CI:n muutosluokittelutöissä. -Alias ei ole työnkulkutyö, matriisi-ilmentymä tai testitapaus: näitä määriä -ei pidä esittää keskenään vaihdettavina. +skriptien nimet ja tarkat komennot suhteessa tiedostoon `package.json`; lisäykset, poistot ja +komentojen muutokset aiheuttavat virheen sekä paikallisessa hookissa että CI:n muutosluokittelutöissä. +Alias ei ole työnkulun työ, matriisi-instanssi tai testitapaus: näitä määriä ei saa +esittää keskenään vaihdettavina. Käytä komentoa `npm run quality:scan -- --list` tai `npm run quality:scan:fast -- --list` tarkastellaksesi valittuja aliaksia suorittamatta niitä. Suoritin kutsuu -npm-aloituskohtaa, joten sen suoritusympäristö (mukaan lukien Bun, jos määritetty) säilyy. -Manifesti kirjaa näiden profiilien ulkopuoliset aliakset erikseen käynnistettäviksi, ja -ylläpitokomennot on kielletty vain luku -tarkistusprofiileissa. +npm-aloituskohtaa, joten sen suoritusympäristö (mukaan lukien Bun, jos se on määritetty) säilyy. +Manifesti kirjaa näiden profiilien ulkopuoliset aliakset erikseen kutsuttaviksi, ja +ylläpitokomennot ovat kiellettyjä vain luku -tarkistusprofiileissa. -Nämä profiilit kattavat vain staattisen tarkistuksen. Ne eivät varmenna tuotetestejä, +Nämä profiilit kattavat vain staattisen tarkistuksen. Ne eivät sertifioi tuotetestejä, kattavuutta, paketointia, ulkoisia tarkistuksia tai ehdokkaan täyttä julkaisuhyväksyntää. -Työnkulun hyväksyntä käyttää linkitettyjä tiedostoa `config/quality/admission-policy.json` ja -`scripts/quality/admission-verdict.mjs`. Julkaisun tarkkailuprofiilit pysyvät erillisinä; +Työnkulun hyväksyntä käyttää linkitettyä tiedostoa `config/quality/admission-policy.json` ja +skriptiä `scripts/quality/admission-verdict.mjs`. Release-observer-profiilit pysyvät erillisinä; tarkasta niiden soveltuvat tarkistukset ja kuitit itsenäisesti. Alla oleva sanallinen -inventaario on viite, ei todiste siitä, että portti todella suoritettiin. +luettelo on viite, ei todiste siitä, että portti todella suoritettiin. Skriptit sijaitsevat hakemistoissa `scripts/check/` (käytäntöportit) ja `scripts/quality/` (ratchet-moottori). CI:n totuuden lähde on `.github/workflows/ci.yml`. @@ -66,213 +66,217 @@ CI:n totuuden lähde on `.github/workflows/ci.yml`. ### Julkaisu-PR:n nopea polku (`quality.yml`) `.github/workflows/quality.yml` täydentää CI:tä main/release-PR:issä, suojattujen haarojen -työnnöissä, käynnistyksissä ja yhdistämisryhmissä. PR:t käyttävät polkusuodatettuja nopeita tarkistuksia. Pysyvästi -käytöstä poistettu päällekkäinen koonti poistettiin; varsinaiset koonti-/paketointi-/käynnistystarkistukset säilyvät CI:ssä. - -| Työ | Laajuus | Estävä | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `Docs Gates (fast-path)` | Dokumentaatio-/koodi-PR:t; API-dokumentaation viitteet ja kaikki dokumentaatiot | Kyllä | -| `Fast Quality Gates` | Koodi-PR:t; staattiset tarkistukset, tyyppitarkistus, koontinäytön tyyppitarkistus, vaikutuksen alaiset yksikkötestit | Kyllä | -| `Forgotten sibling tests` | Koodi-PR:t; muuttuneiden moduulien jäljitys staattisiin kuluttajiin ja ehdolla oleviin rinnakkaistesteihin; barrel- ja dynaamisen tuonnin polut raportoidaan neuvoa-antavana diagnostiikkana viitattuine sallittujen poikkeusten luetteloineen | **Neuvoa-antava** | -| `Vitest (fast-path)` | Koodi-PR:t; nopea vitest-testikokoelma | Kyllä | -| `Unit Tests fast-path` | Koodi-PR:t; neljään osaan jaettu yksikkötestikokoelma | Kyllä | -| `No new ESLint warnings` | Koodi-PR:t; poikkeusten huomioiva lint-suoja | Kyllä, myös forkeissa | -| `Merge integrity (changelog + generated skills)` | Muut kuin luonnos-PR:t; muutoslokin ja generoitujen taitojen synkronointi | Kyllä, myös forkeissa | +pusheissa, dispatch-käynnistyksissä ja yhdistämisryhmissä. PR:t käyttävät polkusuodatettuja nopeita tarkistuksia. Pysyvästi +käytöstä poistettu päällekkäinen koonti poistettiin; varsinaiset koonti-, paketointi- ja käynnistystarkistukset säilyvät CI:ssä. + +| Työ | Laajuus | Estävä | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `Docs Gates (fast-path)` | Dokumentaatio-/koodi-PR:t; API-dokumentaation viitteet ja kaikki dokumentaatiot | Kyllä | +| `Fast Quality Gates` | Koodi-PR:t; staattiset tarkistukset, tyyppitarkistus, koontinäytön tyyppitarkistus, vaikutuksenalaiset yksikkötestit | Kyllä | +| `Forgotten sibling tests` | Koodi-PR:t; muuttuneet moduulit jäljitetään staattisiin käyttäjiin ja mahdollisiin rinnakkaistesteihin; barrel- ja dynaamisen tuonnin polut raportoidaan neuvoa-antavina diagnostiikkoina yhdessä viitattujen sallittujen poikkeusten kanssa | **Neuvoa-antava** | +| `Vitest (fast-path)` | Koodi-PR:t; nopea vitest-testikokonaisuus | Kyllä | +| `Unit Tests fast-path` | Koodi-PR:t; neljään osaan jaettu yksikkötestikokonaisuus | Kyllä | +| `No new ESLint warnings` | Koodi-PR:t; ohitukset huomioiva lint-suojatarkistus | Kyllä, myös forkeissa | +| `Merge integrity (changelog + generated skills)` | Muut kuin luonnostilassa olevat PR:t; muutoslokin ja generoitujen taitojen synkronointi | Kyllä, myös forkeissa | #### Unohtuneiden rinnakkaistestien raportti -`npm run check:forgotten-sibling-tests` käyttää uudelleen testivaikutuskartan taustalla olevaa tuontien selvittäjää. -Jokaisesta muuttuneesta tuotantomoduulista se raportoi deterministiset -`muuttunut moduuli/symboli -> staattinen kuluttaja -> ehdolla oleva rinnakkaistesti` -ketjut, kun ehdolla oleva +`npm run check:forgotten-sibling-tests` käyttää uudelleen testivaikutuskartan taustalla olevaa tuontiresolveria. +Jokaiselle muuttuneelle tuotantomoduulille se raportoi deterministiset +`muuttunut moduuli/symboli -> staattinen käyttäjä -> mahdollinen rinnakkaistesti` -ketjut, kun mahdollinen testi puuttuu pull requestin diffistä. Markdown-yhteenveto ja JSON-tulos säilytetään `forgotten-sibling-tests`-työnkulkuartefaktina kalibrointia varten ennen mahdollista estävää käyttöönottoa. -Barrel-uudelleenviennit ja dynaamiset tuonnit ovat vain moduulien selvityksen diagnostiikkaa; ne eivät koskaan luo +Barrel-uudelleenviennit ja dynaamiset tuonnit ovat vain ratkaisudiagnostiikkaa; ne eivät koskaan luo estävää havaintoa. Tarkistetut poikkeukset sijaitsevat tiedostossa `config/quality/forgotten-sibling-allowlist.json`. Jokaisessa merkinnässä on nimettävä kuluttaja ja ehdolla oleva -testi, annettava täsmällinen perustelu ja linkitettävä GitHub-ongelmaan tai pull request -pyyntöön. Virheelliset merkinnät -hylätään turvallisesti. Poikkeukset eivät voi sivuuttaa poistettua ehdokastestiä tai diffiä, joka lisää `.skip`- tai `.todo`-merkinnän; -assertioiden heikentäminen ja muu peittäminen kuuluvat edelleen itsenäisesti estävälle +testi, annettava täsmällinen perustelu ja linkitettävä GitHub-ongelmaan tai vetopyyntöön. Virheelliset merkinnät +hylätään turvallisesti. Poikkeukset eivät voi ohittaa poistettua ehdokastestiä tai diffiä, joka lisää `.skip`- tai `.todo`-määrityksen; +väitteiden heikentäminen ja muu peittäminen kuuluvat edelleen itsenäisesti estävälle `check:test-masking`-portille. ### Työ: `lint` -Suoritetaan jokaiselle `main`-haaraan kohdistuvalle PR:lle. Epäonnistuminen estää yhdistämisen. - -| Skripti (`npm run ...`) | Tarkistaa | Estävä | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| `check:node-runtime` | Node.js-versio on tuetulla alueella | Kyllä | -| `check:cycles` | Kehämäiset tuonnit — kaikki `src/`- ja `open-sse/`-moduulit | Kyllä | -| `check:route-validation:t06` | Zod-skeemat ovat käytössä kaikilla reiteillä (tason 6 käytäntö) | Kyllä | -| `check:any-budget:t11` | `@ts-expect-error // any` -määrä ei ylitä budjettia (tason 11 catraca) | Kyllä | -| `check:provider-consistency` | Jokaisella tiedoston `providers.ts` tarjoajalla on vastaava merkintä tiedostossa `providerRegistry.ts` (ja päinvastoin sallittujen luettelon puitteissa) | Kyllä | -| `check:model-lifecycle` | Kolme käsin ylläpidettävää reititystaulukkoa pysyvät yhdenmukaisina versionhallintaan tallennetun elinkaarikatselmuksen (#11503) kanssa: `FITNESS_TABLE` (`taskFitness.ts`) ei pisteytä yhtäkään poistunutta tunnistetta, jonka `REGISTRY` voi reitittää; jokainen `BUILT_IN_ALIASES`-kohde on mukana `REGISTRY`-rekisterissä eikä sisälly poistuneiden tunnisteiden katselmukseen; jokainen poistunut tunniste, joka on yhä `REGISTRY`-rekisterissä, välitetään eteenpäin tai luetellaan `allowedRetiredInCatalog`-luettelossa; eikä yksikään `DEFAULT_DEGRADATION_MAP`-lähde tai -kohde näy poistuneena kyseisessä katselmuksessa. Tämä ei todista, että malli on tällä hetkellä saatavilla toimivan ylävirran kautta. Suoritetaan offline-tilassa — vertaa tiedostoon `config/quality/model-lifecycle.json`, joka päivitetään käsin komennolla `npm run quality:refresh-model-lifecycle` (vaatii verkkoyhteyden; ei ole kytketty CI-putkeen). `allowedRetiredInCatalog` on asteittain tyhjennettävä yksisuuntainen rajoitin: lisää merkintä vain, jos sille on seurantatehtävä. | Kyllä | -| `check:fetch-targets` | Jokainen asiakaspuolen hakemiston `src/` kutsu `fetch("/api/...")` viittaa olemassa olevaan `route.ts`-tiedostoon | Kyllä | -| `check:deps` | Kaikki `npm install` -komennolla asennettavat riippuvuudet jokaisessa repositorion `package.json`-tiedostossa ovat mukana tiedostossa `dependency-allowlist.json`; uudet versioon lukitsemattomat tai kirjoitusvirheeseen perustuvaa pakettikaappausta hyödyntävät paketit merkitään | Kyllä | -| `audit:deps` | `npm audit` (juuri + electron) — ei vakavuudeltaan korkeita tai kriittisiä varoituksia (päällekkäinen osv-tarkistuksen `check:vuln-ratchet` kanssa; katso rationalisointijono) | Kyllä | -| `check:lockfile` | `package-lock.json`-tiedoston eheys — https-rekisteri, eheystiivisteet, ei isäntäkohtaisia ohituksia | Kyllä | -| `check:licenses` | Tuotantoriippuvuuksien SPDX-lisenssien sallittujen luettelo | Kyllä | -| `check:tracked-artifacts` | Ei koontiartefakteja / versionhallintaan lisättyjä `node_modules`-symbolisia linkkejä (suoritetaan myös huskyn pre-commit-koukussa; pre-push on tarkoituksella kevyt — #6716) | Kyllä | -| `check:ai-attribution` | Ei tekoälyn/botin `Co-Authored-By`-alatunnistetta tai tekoälyllä luomisesta kertovaa alatunnistetta PR:n commiteissa, otsikossa tai rungossa — ehdoton sääntö #16 (`quality.yml`-tiedoston nopeiden tarkistusten silmukassa PR→`release/**` — lukee tapahtuman hyötykuorman, ei tee mitään muille kuin PR:ille — sekä vain PR:ille suoritettavassa `ci.yml`-tiedoston lint-vaiheessa PR→`main`; myös huskyn `commit-msg`-koukussa; ihmiskirjoittajat sallitaan; #14436) | -| `check:vitest-exclusions` | Jokainen Vitest-poissulku nimeää seurantatehtävän ja esiintyy tiedostossa `config/quality/vitest-exclusions.json` (#13204) | Kyllä | -| `check:file-size` | Mikään lähdetiedosto ei ylitä tiedostopäätekohtaista enimmäiskokoa (räikkä: jäädytetyt suuret tiedostot `frozen`-luettelossa) | Kyllä | -| `check:error-helper` | Suorittimien/käsittelijöiden virhevastaukset käyttävät `buildErrorBody()`- / `sanitizeErrorMessage()`-funktiota (ehdoton sääntö #12) | Kyllä | -| `check:migration-numbering` | Migraatioiden SQL-tiedostot on numeroitu peräkkäin ilman aukkoja tai kaksoiskappaleita | Kyllä | -| `check:public-creds` | Ei literaaleja OAuth-`client_id`/`client_secret`-arvoja tai Firebase Web -avaimia `publicCreds.ts`-tiedoston ulkopuolella (ehdoton sääntö #11) | Kyllä | -| `check:db-rules` | Ei raakaa SQL:ää `src/lib/db/`-moduulien ulkopuolella; ei barrel-tuonteja `localDb.ts`-tiedostosta (ehdottomat säännöt #2/#5) | Kyllä | -| `check:known-symbols` | Välitystauluihinsa rekisteröidyt palveluntarjoajien suorittimet, reititysstrategiat ja muuntimet vastaavat levyllä olevia tiedostoja — ei irrallisia tai määrittelemättömiä symboleja | Kyllä | -| `check:route-guard-membership` | Jokainen aliprosessin käynnistävä reitti on luokiteltu `isLocalOnlyPath()`-funktiolla (ehdottomat säännöt #15/#17) | Kyllä | -| `check:test-discovery` | Vähintään yksi testiajo-ohjelma kerää jokaisen repositoriossa olevan `*.test.ts`- / `*.spec.ts`-tiedoston (ratchet: `test-discovery-baseline.json`-tiedoston irrallisten testien luettelo voi vain lyhentyä) | Kyllä | -| `check:agent-skills-sync` | Luodut agent-skills-artefaktit vastaavat lähdeluetteloaan (ei poikkeamia) | -| `check:provider-asset-provenance` | Palveluntarjoajien logoilla/resursseilla on kirjattu alkuperätieto | -| `lint:json` | JSON-määritystiedostot jäsentyvät ja täyttävät repositorion lint-säännöt | -| `typecheck:core` | TypeScript-käännös ilman virheitä (vain neuvoa-antavia varoituksia) | Kyllä | -| `typecheck:noimplicit:core` | Tiukka `noImplicitAny` — tulevaisuuteen suuntautuva; monet jo olemassa olevat kutsukohdat tarvitsevat edelleen annotaatioita | **Neuvoa-antava** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, jonka kohteena on `src/app/(dashboard)/**` (#7033) — `typecheck:core`-komennon tarkoin valittu 27 tiedoston sallittujen luettelo ei sisällä yhtään hallintapaneelin TSX-tiedostoa, eikä `next build` myöskään koskaan tee niille tyyppitarkistusta (`next.config.mjs` asettaa `ignoreBuildErrors: true`), joten irrallisiin tunnisteisiin liittyvät regressiot (#6625/#6909) eivät näkyneet CI:ssä. Eroja verrataan jäädytettyyn tiedosto- ja TS-koodikohtaisten määrien vertailutasoon (`config/quality/dashboard-typecheck-baseline.json`, sama vanhentuneisuuden valvontamalli kuin komennolla `check:known-symbols`) — vain vertailutason määrän ylittävät UUDET virheet hylkäävät tarkistuksen; alenna vertailutasoa komennolla `--update`, kun aiempi virhe on korjattu. | Kyllä | +Suoritetaan jokaisessa `main`-haaraan kohdistuvassa PR:ssä. Estää yhdistämisen epäonnistuessaan. + +| Skripti (`npm run ...`) | Tarkistaa | Estävä | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `check:node-runtime` | Node.js-versio on tuetulla alueella | Kyllä | +| `check:cycles` | Kehämäiset tuonnit koko `src/`- ja `open-sse/`-hakemistoissa (AST-pohjainen, tsconfigin `paths` ratkaistu). Pelkkä tarkistus on neuvoa-antava ja luettelee kehät. `check:cycles:ratchet` (jonka CI suorittaa) estää, kun määrä ylittää `quality-baseline.json`-tiedoston `metrics.cycles`-enimmäisrajan — tällä hetkellä 14, `direction: down`, joten määrä voi vain pienentyä (#15159 G-01/G-02) | Kyllä (räikkä) | +| `check:route-validation:t06` | Zod-skeemat ovat käytössä kaikilla reiteillä (tason 6 käytäntö) | Kyllä | +| `check:any-budget:t11` | `@ts-expect-error // any`-määrä ei ylitä budjettia (tason 11 räikkä) | Kyllä | +| `check:provider-consistency` | Jokaisella tiedoston `providers.ts` palveluntarjoajalla on vastaava merkintä tiedostossa `providerRegistry.ts` (ja päinvastoin sallittujen luettelon rajoissa) | Kyllä | +| `check:model-lifecycle` | Kolme käsin ylläpidettävää reititystaulukkoa pysyvät yhdenmukaisina versionhallintaan tallennetun elinkaarivedoksen (#11503) kanssa: `FITNESS_TABLE` (`taskFitness.ts`) ei pisteytä yhtäkään käytöstä poistettua tunnistetta, jonka `REGISTRY` voi reitittää; jokainen `BUILT_IN_ALIASES`-kohde sisältyy `REGISTRY`-rekisteriin eikä esiinny käytöstä poistettujen tunnisteiden vedoksessa; jokainen käytöstä poistettu tunniste, joka on yhä `REGISTRY`-rekisterissä, välitetään eteenpäin tai luetellaan kohdassa `allowedRetiredInCatalog`; eikä yksikään `DEFAULT_DEGRADATION_MAP`-lähde tai -kohde esiinny käytöstä poistettuna kyseisessä vedoksessa. Tämä ei todista, että aktiivinen upstream-palvelu tarjoaa mallia tällä hetkellä. Offline — vertaa tiedostoon `config/quality/model-lifecycle.json`, joka päivitetään käsin komennolla `npm run quality:refresh-model-lifecycle` (vaatii verkkoyhteyden; ei ole kytketty CI-järjestelmään). `allowedRetiredInCatalog` on asteittain supistettava lista: lisää merkintä vain, jos sille on seurantatehtävä. | Kyllä | +| `check:fetch-targets` | Jokainen asiakaspuolen `src/`-hakemistossa oleva `fetch("/api/...")` viittaa todelliseen `route.ts`-tiedostoon | Kyllä | +| `check:deps` | Kaikki repositorion jokaisen `package.json`-tiedoston `npm install` -komennolla asennettavat riippuvuudet sisältyvät tiedostoon `dependency-allowlist.json`; uudet versioon sitomattomat tai kirjoitusvirhetyyppisellä nimikaappauksella lisätyt paketit merkitään | Kyllä | +| `audit:deps` | `npm audit` (juuri + electron) — ei vakavia/kriittisiä varoituksia (osittain päällekkäinen OSV-tarkistuksen `check:vuln-ratchet` kanssa; katso Rationalisointijono) | Kyllä | +| `check:lockfile` | `package-lock.json`-tiedoston eheys — HTTPS-rekisteri, eheystiivisteet, ei palvelinkohtaisia ohituksia | Kyllä | +| `check:licenses` | Tuotantoriippuvuuksien SPDX-lisenssien sallittujen luettelo | Kyllä | +| `check:tracked-artifacts` | Ei koontiartefakteja / versionhallintaan lisättyjä `node_modules`-symbolisia linkkejä (suoritetaan myös husky pre-commit -koukussa; pre-push on tarkoituksella kevyt — #6716) | Kyllä | +| `check:ai-attribution` | Ei tekoälyn/botin `Co-Authored-By`-lopuketta tai tekoälygeneroinnin alatunnistetta PR-commiteissa, otsikossa tai kuvauksessa — ehdoton sääntö #16 (`quality.yml`-tiedoston nopeiden tarkistusten silmukassa PR→`release/**` — lukee tapahtuman hyötykuorman, ei tee mitään PR:ien ulkopuolella — sekä vain PR:ille suoritettavana vaiheena `ci.yml`-tiedoston lint-tarkistuksessa PR→`main`; myös husky `commit-msg` -koukussa; ihmiset sallitaan kanssakirjoittajina; #14436) | +| `check:vitest-exclusions` | Jokainen Vitest-poissulku nimeää seurantatehtävän ja esiintyy tiedostossa `config/quality/vitest-exclusions.json` (#13204) | Kyllä | +| `check:file-size` | Mikään lähdetiedosto ei ylitä tiedostopäätekohtaista enimmäiskokoa (räikkä: suuret tiedostot jäädytetty `frozen`-luettelossa) | Kyllä | +| `check:error-helper` | Suorittimien/käsittelijöiden virhevastaukset käyttävät funktioita `buildErrorBody()` / `sanitizeErrorMessage()` (ehdoton sääntö #12) | Kyllä | +| `check:migration-numbering` | Migraatioiden SQL-tiedostot on numeroitu peräkkäin ilman aukkoja tai päällekkäisyyksiä | Kyllä | +| `check:public-creds` | Ei literaaleja OAuth-`client_id`/`client_secret`-arvoja tai Firebase Web -avaimia `publicCreds.ts`-tiedoston ulkopuolella (ehdoton sääntö #11) | Kyllä | +| `check:db-rules` | Ei raakaa SQL:ää `src/lib/db/`-moduulien ulkopuolella; ei barrel-tuonteja `localDb.ts`-tiedostosta (ehdottomat säännöt #2/#5) | Kyllä | +| `check:known-symbols` | Välitystauluihinsa rekisteröidyt palveluntarjoajien suorittimet, reititysstrategiat ja muuntimet vastaavat levyllä olevia tiedostoja — ei irrallisia tai ilmoittamattomia symboleja | Kyllä | +| `check:route-guard-membership` | Jokainen aliprosessin käynnistävä reitti on luokiteltu `isLocalOnlyPath()`-funktiolla (ehdottomat säännöt #15/#17) | Kyllä | +| `check:test-discovery` | Jokainen repositorion `*.test.ts`- / `*.spec.ts`-tiedosto sisältyy vähintään yhden testiajon keräämiin testeihin (räikkäperiaate: `test-discovery-baseline.json`-tiedoston irrallisten testien lista voi vain lyhentyä) | Kyllä | +| `check:agent-skills-sync` | Luodut agent-skills-artefaktit vastaavat lähdeluetteloaan (ei poikkeamia) | +| `check:provider-asset-provenance` | Palveluntarjoajien logoilla/resursseilla on kirjattu alkuperätietue | +| `lint:json` | JSON-määritystiedostot jäsentyvät ja täyttävät repositorion lint-säännöt | +| `typecheck:core` | TypeScript-käännös ilman virheitä (vain neuvoa-antavia varoituksia) | Kyllä | +| `typecheck:noimplicit:core` | Tiukka `noImplicitAny` — tulevaisuuteen suuntautuva; monet aiemmin olemassa olleet kutsukohdat tarvitsevat yhä annotaatioita | **Neuvoa-antava** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, jonka kohteena on `src/app/(dashboard)/**` (#7033) — `typecheck:core`-tarkistuksen rajattu 27 tiedoston sallittujen luettelo ei sisällä yhtään dashboard-TSX-tiedostoa, eikä `next build` myöskään koskaan tyyppitarkista niitä (`next.config.mjs` määrittää asetuksen `ignoreBuildErrors: true`), joten irrallisiin tunnisteisiin liittyvät regressiot (#6625/#6909) jäivät CI:ltä havaitsematta. Eroja verrataan jäädytettyyn tiedosto- ja TS-koodikohtaiseen lukumäärän perustasoon (`config/quality/dashboard-typecheck-baseline.json`, sama vanhentuneisuuden valvontamalli kuin tarkistuksessa `check:known-symbols`) — vain perustason määrän ylittävät UUDET virheet aiheuttavat portin epäonnistumisen; alenna perustasoa valitsimella `--update`, kun aiemmin olemassa ollut virhe korjataan. | Kyllä | ### Työ: `quality-gate` -Suoritetaan `test-coverage`-työn jälkeen. Epäonnistuminen estää yhdistämisen. +Suoritetaan `test-coverage`-työn jälkeen. Estää yhdistämisen epäonnistuessaan. -| Skripti | Tarkistaa | Estävä | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `quality:collect` | Tuottaa tiedoston `quality-metrics.json` (ESLint-varoitusten määrä, yhdistetystä osaraportista saatu kattavuus) | Kyllä (ennen ratchet-vaihetta) | -| `quality:ratchet` | Yksikään tiedoston `quality-baseline.json` mittari ei ole heikentynyt (ESLint-varoitukset ≤ vertailutaso; kattavuus ≥ vertailutaso) | Kyllä | -| `check:duplication` | Koodin monistus (jscpd@4) ei ylitä tiedostossa `quality-baseline.json` määritettyä vertailutasoa | Kyllä | -| `check:complexity` | Tiedostotason syklomaattinen monimutkaisuus ei ylitä ylärajaa (ESLint-ytimen `complexity` + `max-lines-per-function`) | Kyllä | -| `check:cognitive-complexity` | Kognitiivisen monimutkaisuuden ratchet-tarkistus (`eslint-plugin-sonarjs`) — erillinen ESLint-ajo; CI ajaa molemmat yhdistettynä yhtenä `check:complexity-ratchets`-vaiheena | Kyllä | -| `check:dead-code` | Käyttämättömien vientien / tiedostojen ratchet-tarkistus (knip) ei heikkene vertailutasoon nähden | Kyllä | -| `check:compression-budget` | Pakkausvertailun budjetti — moottorikohtaiset token-säästöjen vähimmäistasot eivät saa heikentyä | Kyllä | -| `check:type-coverage` | Tyypitetyn osuuden ratchet-tarkistus (`type-coverage`) ei heikkene; korvaa suurelta osin tarkistuksen `typecheck:noimplicit:core` | Kyllä | -| `check:codeql-ratchet` | Avointen CodeQL-hälytysten määrä ei kasva (lukee komennolla `gh api`; ohitetaan hallitusti ilman tokenia) — päivitystiheys ja manuaalinen käynnistys: katso alta "CodeQL-ratchet" | Kyllä | +| Skripti | Tarkistaa | Estävä | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| `quality:collect` | Tuottaa tiedoston `quality-metrics.json` (ESLint-varoitusten määrä, yhdistetystä osaraportista saatu kattavuus) | Kyllä (ratchet-vaihetta edeltävä) | +| `quality:ratchet` | Yksikään tiedoston `quality-baseline.json` mittari ei ole heikentynyt (ESLint-varoitukset ≤ vertailutaso; kattavuus ≥ vertailutaso) | Kyllä | +| `check:duplication` | Koodin duplikaatio (jscpd@4) ei ylitä tiedostossa `quality-baseline.json` määritettyä vertailutasoa | Kyllä | +| `check:complexity` | Tiedostotason syklomaattinen kompleksisuus ei ylitä ylärajaa (ESLint-ytimen `complexity` + `max-lines-per-function`) | Kyllä | +| `check:cognitive-complexity` | Kognitiivisen kompleksisuuden ratchet-tarkistus (`eslint-plugin-sonarjs`) — erillinen ESLint-ajo; CI suorittaa molemmat yhdistettynä yhtenä `check:complexity-ratchets`-vaiheena | Kyllä | +| `check:dead-code` | Käyttämättömien vientien/tiedostojen ratchet-tarkistus (knip) ei heikkene vertailutasoon nähden | Kyllä | +| `check:compression-budget` | Pakkauksen suorituskykytestin budjetti — moottorikohtaiset token-säästöjen vähimmäisrajat eivät saa heikentyä | Kyllä | +| `check:type-coverage` | Tyypitetyn osuuden ratchet-tarkistus (`type-coverage`) ei heikkene; korvaa suurelta osin tarkistuksen `typecheck:noimplicit:core` | Kyllä | +| `check:codeql-ratchet` | Avointen CodeQL-hälytysten määrä ei kasva (lukee komennolla `gh api`; ohitetaan hallitusti ilman tunnistetta) — päivitystiheys ja manuaalinen käynnistys: katso alta ”CodeQL-ratchet” | Kyllä | ### Työ: `quality-extended` Koko työ on neuvoa-antava (`continue-on-error: true`). npm-pohjaiset ratchet-tarkistukset suoritetaan oikeasti; ulkoiset skannerit asennetaan komennolla `gh release download`, ja ne ohittavat itsensä (exit 0), -jos binääri puuttuu edelleen. +jos binääriä ei vieläkään ole saatavilla. -| Skripti | Tarkistaa | Estävyys | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- | -| `check:circular-deps` | Ei syklisiä riippuvuuksia (dpdm) | **Neuvoa-antava** | -| `check:bundle-size` | Paketin koko ei ylitä ylärajaa | **Neuvoa-antava** | -| `check:secrets` | Salaisuuksien skannaus (gitleaks) — ohitetaan, jos binääri puuttuu | **Neuvoa-antava** | -| `check:vuln-ratchet` | Riippuvuuksien haavoittuvuudet (osv-scanner) eivät lisäänny — ohitetaan, jos binääri puuttuu | **Neuvoa-antava** | -| `check:workflows` | Työnkulkujen lint-tarkistus (actionlint + zizmor); puuttuvat/rikkinäiset skannerit, virheelliset raportit tai puuttuva ratchet-vertailutaso epäonnistuvat tilassa INCOMPLETE. Kelvollisiin löydöksiin sovelletaan valittua tiukkaa, neuvoa-antavaa tai ratchet-käytäntöä | Suoritus vaaditaan; zizmor-ratchet estää CI:ssä | -| `check:openapi-breaking` | Julkisen API-sopimuksen (`openapi.yaml`) rikkovat muutokset suhteessa perushaaraan (oasdiff) — tuottaa arvon `openapiBreaking=N`; ohitetaan, jos oasdiff puuttuu tai perusmääritystä ei voida selvittää | **Neuvoa-antava** | +| Skripti | Tarkistaa | Estävä | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| `check:circular-deps` | Ei syklisiä riippuvuuksia (dpdm) | **Neuvoa-antava** | +| `check:bundle-size` | Paketin koko ei ylitä ylärajaa | **Neuvoa-antava** | +| `check:secrets` | Salaisuuksien skannaus (gitleaks) — ohitetaan, jos binääri puuttuu | **Neuvoa-antava** | +| `check:vuln-ratchet` | Riippuvuuksien haavoittuvuudet (osv-scanner) eivät lisäänny — ohitetaan, jos binääri puuttuu | **Neuvoa-antava** | +| `check:workflows` | Työnkulkujen lint-tarkistus (actionlint + zizmor); puuttuvat tai vialliset skannerit, virheelliset raportit tai puuttuva ratchet-vertailutaso epäonnistuvat tilalla INCOMPLETE. Kelvollisiin löydöksiin sovelletaan valittua tiukkaa, neuvoa-antavaa tai ratchet-käytäntöä | Suoritus vaaditaan; zizmor-ratchet on estävä CI:ssä | +| `check:openapi-breaking` | Julkisen API-sopimuksen (`openapi.yaml`) rikkovat muutokset verrattuna perushaaraan (oasdiff) — tuottaa arvon `openapiBreaking=N`; ohitetaan, jos oasdiff puuttuu tai perusmääritystä ei voida selvittää | **Neuvoa-antava** | ### Työ: `docs-sync-strict` -Suoritetaan jokaisessa `main`-haaraan kohdistuvassa PR:ssä. Epäonnistuminen estää yhdistämisen. +Suoritetaan jokaiselle `main`-haaraan kohdistuvalle PR:lle. Epäonnistuminen estää yhdistämisen. | Skripti | Tarkistaa | Estävä | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | `check:docs-all` | Metaportti, joka suorittaa alla olevat 6 aliporttia peräkkäin | Kyllä | -| ↳ `check:docs-sync` | CHANGELOGin / OpenAPIn / llm.txt:n versioiden yhdenmukaisuus | Kyllä | -| ↳ `check:docs-counts` | Tekstissä ilmoitetut määrät (palveluntarjoajien määrä, migraatioiden määrä jne.) ovat todellisiin määriin perustuvan ratchet-ikkunan sisällä | Kyllä | +| ↳ `check:docs-sync` | CHANGELOGin / OpenAPIn / llm.txt:n versioiden yhdenmukaisuuden | Kyllä | +| ↳ `check:docs-counts` | Tekstissä ilmoitetut lukumäärät (palveluntarjoajien määrä, migraatioiden määrä jne.) ovat todellisten lukumäärien räikkäikkunan sisällä | Kyllä | | ↳ `check:env-doc-sync` | Jokainen `.env.example`-tiedoston ympäristömuuttuja on dokumentoitu dokumentaation taulukossa ja päinvastoin | Kyllä | | ↳ `check:deprecated-versions` | Dokumentaatiossa ei ole vanhentuneita versiomerkkijonoja | Kyllä | | ↳ `check:doc-links` | Dokumentaation sisäiset markdown-linkit viittaavat olemassa oleviin tiedostoihin (`[teksti]`/`(polku)`-muoto) | Kyllä | | ↳ `check:fabricated-docs` | Dokumentaatiossa mainitut reitit, ympäristömuuttujat, CLI-komennot, hook-nimet ja tiedostopolut ovat olemassa koodikannassa. Kova portti `--strict`-valitsimella; pehmeä epäonnistuminen ilman valitsinta. | Kyllä (`--strict` CI:ssä) | | `check:cli-i18n` | CLI-komentomerkkijonot ovat mukana kaikissa i18n-lokaalitiedostoissa | Kyllä | -| `check:openapi-coverage` | OpenAPI-määritys kattaa vähintään ratchet-menetelmällä määritetyn vähimmäismäärän todellisista reiteistä | Kyllä | -| `check:openapi-security-tiers` | `openapi.yaml`-tiedoston turvallisuustasomerkinnät ovat yhdenmukaisia `routeGuard.ts`-luokitusten kanssa | **Neuvoa-antava** | +| `check:openapi-coverage` | OpenAPI-määritys kattaa vähintään räikällä määritetyn vähimmäismäärän todellisista reiteistä | Kyllä | +| `check:openapi-security-tiers` | `openapi.yaml`-tiedoston suojaustasomerkinnät ovat yhdenmukaisia `routeGuard.ts`-tiedoston luokitusten kanssa | **Ohjeellinen** | | `check:openapi-routes` | Jokainen `openapi.yaml`-tiedoston polku viittaa todelliseen `route.ts`-tiedostoon (hallusinaatioiden esto) | Kyllä | -| `check:docs-symbols` | Jokainen `docs/**/*.md`-tiedostojen `/api/...`-viittaus viittaa todelliseen `route.ts`-tiedostoon (hallusinaatioiden esto) | Kyllä | -| `i18n translation drift` | Kääntämättömät avaimet i18n-lokaalitiedostoissa — vain varoitus | **Neuvoa-antava** | +| `check:docs-symbols` | Jokainen `/api/...`-viittaus tiedostoissa `docs/**/*.md` viittaa todelliseen `route.ts`-tiedostoon (hallusinaatioiden esto) | Kyllä | +| `i18n translation drift` | Kääntämättömät avaimet i18n-lokaalitiedostoissa — vain varoitus | **Ohjeellinen** | ### Työ: `i18n-ui-coverage` -| Skripti | Tarkistaa | Estävä | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `check-ui-keys-coverage` (inline) | Käyttöliittymän i18n-avainten kattavuus on ≥ 65 % | Kyllä | -| `check-ui-value-drift` (inline) | Uudelleenkirjoitetusta englanninkielisestä **arvosta** ei jää jäljelle vanhentunutta käännöstä | Kyllä | -| `check-new-key-coverage` (inline) | **Uusi** englanninkielinen avain on käännetty kaikissa lokaaleissa — `__MISSING__:`-merkintää ei hyväksytä | Kyllä | -| `check-translation-ratio` | Todellisten käännösten suhde lokaaleittain (englanninkielisen kanssa identtiset / paikkamerkit / sallittujen luettelon ulkopuolella puuttuvat lehdet) ei saa ylittää arvoa `config/quality/i18n-translation-baseline.json` + liikkumavara | **Neuvoa-antava** | +| Skripti | Tarkistaa | Estävä | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | +| `check-ui-keys-coverage` (sisäinen) | Käyttöliittymän i18n-avainten kattavuus on ≥ 65 % | Kyllä | +| `check-ui-value-drift` (sisäinen) | Uudelleen kirjoitettu englanninkielinen **arvo** ei jätä jälkeensä vanhentunutta käännöstä | Kyllä | +| `check-new-key-coverage` (sisäinen) | **Uusi** englanninkielinen avain on käännetty jokaisessa lokaalissa — `__MISSING__:`-merkintää ei hyväksytä | Kyllä | +| `check-translation-ratio` | Todellisten käännösten osuus lokaaleittain (englannin kanssa identtiset / paikkamerkinnät / puuttuvat lehdet sallittujen luettelon ulkopuolella) ei saa ylittää arvoa `config/quality/i18n-translation-baseline.json` + liikkumavara | **Ohjeellinen** | -Edellyttää asetusta `fetch-depth: 0` — arvon vanhentumisen tarkistus vertaa `en.json`-tiedostoa yhdistämiskantaan. +Vaatii asetuksen `fetch-depth: 0` — arvomuutosten portti vertaa `en.json`-tiedostoa yhdistämiskantaan. #### `check-ui-value-drift` — vanhentuneiden käännösten portti -Havaitsee yhden i18n-regression, jota muut portit eivät rakenteellisesti pysty havaitsemaan: englanninkielinen arvo -kirjoitetaan uudelleen, mutta _aiempaan_ englanninkieliseen tekstiin perustuvat käännökset jäävät ennalleen, jolloin +Havaitsee sen yhden i18n-regression, jota muut portit eivät rakenteellisesti pysty näkemään: englanninkielinen arvo +kirjoitetaan uudelleen, mutta _aiemmasta_ englanninkielisestä tekstistä johdetut käännökset jäävät ennalleen, joten muunkieliset käyttäjät lukevat edelleen vakuuttavasti muotoiltua mutta nyt virheellistä tekstiä. -Tämä päätyi oikeasti tuotantoon. `oauthModal.googleOAuthWarning` kirjoitettiin uudelleen, kun Antigravity- -kirjautumisavustaja otettiin käyttöön (#5203); **39 lokaalia 43:sta** säilytti tekstin, jossa ylläpitäjiä kehotettiin "kopioimaan -koko URL-osoite ja liittämään se alle" — työnkulku, jota ei voi suorittaa loppuun kyseisellä palveluntarjoajalla. Se jäi -huomaamatta aina kohtaan #8463 asti, koska: +Tämä päätyi oikeasti julkaisuun. `oauthModal.googleOAuthWarning` kirjoitettiin uudelleen, kun Antigravity- +kirjautumisavustaja otettiin käyttöön (#5203); **39 lokaalia 43:sta** säilytti tekstin, joka kehotti ylläpitäjiä "kopioimaan +koko URL-osoitteen ja liittämään sen alle" — työnkulku ei voi valmistua kyseisen palveluntarjoajan kanssa. Ongelma +jäi huomaamatta tapaukseen #8463 asti, koska: -- `sync-ui-keys` täydentää vain avaimet, jotka **puuttuvat**, ei koskaan niitä, jotka ovat **vanhentuneita**; -- `check-ui-keys-coverage` laskee avaimen _olemassaolon_, joten vanhentunut käännös katsotaan katetuksi; +- `sync-ui-keys` täydentää vain **puuttuvat** avaimet, ei koskaan **vanhentuneita**; +- `check-ui-keys-coverage` laskee avaimen _olemassaolon_, joten vanhentunut käännös lasketaan katetuksi; - `check-translation-drift` seuraa dokumentaation `docs/i18n//**.md`-peilejä — se ei koskaan lue `src/i18n/messages/*.json`-tiedostoja. Estävä työssä `docs-sync-strict` vuoden - 2026-09 uudelleensynkronoinnista lähtien: muokkaa ydindokumenttia → `npm run i18n:run -- --files=` (osiotasoinen, kevyt). + 2026-09 uudelleensynkronoinnista alkaen: muokkaa ydindokumenttia → `npm run i18n:run -- --files=` (osiotasoinen, kevyt). -**Diff-tietoinen, ei perustasoon sidottu.** Se vertaa yhdistämiskohdan `en.json`-tiedostoa -työpuuhun; jokaisen avaimen kohdalla, jonka englanninkielinen arvo muuttui, kaikki lokalisaatiot, joissa on yhä -muuttamaton käännös, ovat vanhentuneita. Tämä tarkoituksellisesti **jäädyttää aiemmin kertyneen velan** — diff -ei voi paljastaa, mistä vanhasta englanninkielisestä tekstistä pitkään käytössä ollut käännös on peräisin, joten tarkistus arvioi -vain nykyisen muutoksen koskettamia kohtia. Vaihtoehto (avainkohtainen hajautusarvojen perustaso) vaatisi -noin 600 KB:n generoidun tiedoston, joka olisi 3× suurin nykyinen perustaso ja muuttuisi jokaisessa i18n-PR:ssä. +**Diff-tietoinen, ei perustasoon sidottu.** Se vertaa yhdistämiskannan `en.json`-tiedostoa +työpuuhun. Jos avaimen englanninkielinen arvo on muuttunut ja jossakin kieliversiossa +on edelleen koskematon käännös, käännös on vanhentunut. Tämä tarkoituksella **jäädyttää +aiemmin kertyneen velan** — diff ei voi paljastaa, mistä vanhasta englanninkielisestä +tekstistä pitkään käytössä ollut käännös on peräisin, joten portti arvioi vain nykyisen +muutoksen koskettamia kohtia. Vaihtoehto (avainkohtainen hajautusarvojen perustaso) vaatisi +noin 600 KB:n generoidun tiedoston, joka olisi kolme kertaa suurinta nykyistä perustasoa +suurempi ja muuttuisi jokaisessa i18n-PR:ssä. -Sen voi läpäistä kahdella tavalla: +Sen vaatimukset voi täyttää kahdella tavalla: 1. päivitä kyseiset käännökset tai -2. aseta niiden arvoksi `__MISSING__:` — suoritusympäristö tarjoaa tällöin korjatun englanninkielisen tekstin - (`src/i18n/request.ts::deepMergeFallback`, #7258), ja avain siirtyy käännösjonoon. +2. aseta niiden arvoksi `__MISSING__:` — suoritusympäristö näyttää tällöin + korjatun englanninkielisen tekstin (`src/i18n/request.ts::deepMergeFallback`, #7258), + ja avain asetetaan käännösjonoon. -Jos merkkijonon **merkitys** muuttui, suosi **avaimen uudelleennimeämistä**: uusi avain ei voi periä -vanhentunutta käännöstä. Tätä mallia käytettiin muutoksessa #8463. +Jos merkkijonon **merkitys** muuttui, suosi **avaimen nimeämistä uudelleen**: uusi avain +ei voi periä vanhentunutta käännöstä. Tätä mallia käytettiin muutoksessa #8463. ```bash -npm run i18n:check-value-drift # tiukka (CI:n suorittama tarkistus) +npm run i18n:check-value-drift # tiukka (CI suorittaa tämän) npm run i18n:check-value-drift:warn # vain raportointi BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Palauttaa poistumiskoodin 0 ja viestin `SKIP reason=base-unresolved`, kun perustason luetteloa ei voida lukea (matala -klooni ilman perustason viitettä), samalla tavoin kuin `check-openapi-breaking`. +Päättyy tilakoodilla 0 ja tulosteella `SKIP reason=base-unresolved`, kun perusluetteloa +ei voida lukea (matala klooni ilman perusviittausta), kuten `check-openapi-breaking`. ### Työ: `i18n` -Täysi i18n-validointimatriisi (yksi työ lokalisaatiota kohden). Koko työ on neuvoa-antava. +Täysi i18n-validointimatriisi (yksi työ kutakin kieliversiota kohden). Koko työ on neuvoa-antava. -| Skripti | Validoi | Estää yhdistämisen | -| ------------------------------- | --------------------------------------- | --------------------------------------------------------- | -| `validate_translation.py quick` | Käännösten kattavuus lokalisaatioittain | **Neuvoa-antava** (`continue-on-error: true` koko työssä) | +| Skripti | Validoi | Estää yhdistämisen | +| ------------------------------- | -------------------------------------- | --------------------------------------------------------- | +| `validate_translation.py quick` | Käännösten kattavuus kieliversioittain | **Neuvoa-antava** (`continue-on-error: true` koko työlle) | ### Työ: `pr-test-policy` -Suoritetaan vain vetopyynnöille. +Suoritetaan vain pull requesteille. -| Skripti | Validoi | Estää yhdistämisen | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `check:pr-test-policy` | PR:t, jotka muuttavat tuotantokoodia hakemistoissa `src/`, `open-sse/`, `electron/` tai `bin/`, sisältävät tai päivittävät testejä (Tiukka sääntö #8) | Kyllä | -| `check:test-masking` | Muutetut testitiedostot eivät vähennä väitteiden nettomäärää eivätkä lisää `assert.ok(true)`-tautologioita | Kyllä | -| `check:pr-evidence` | PR:n kuvaus viittaa muutoksen testi-/VPS-todisteisiin (automatisoi Tiukan säännön #18 etsimällä tekstiä PR:n kuvauksesta — hauras, katso kehitysjono) | Kyllä | +| Skripti | Validoi | Estää yhdistämisen | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `check:pr-test-policy` | PR:t, jotka muuttavat tuotantokoodia hakemistoissa `src/`, `open-sse/`, `electron/` tai `bin/`, sisältävät uusia tai päivitettyjä testejä (kova sääntö #8) | Kyllä | +| `check:test-masking` | Muutetut testitiedostot eivät vähennä assert-lauseiden nettomäärää eivätkä lisää `assert.ok(true)`-tautologioita | Kyllä | +| `check:pr-evidence` | PR:n kuvaus sisältää viittaukset muutoksen testi-/VPS-näyttöön (automatisoi kovaa sääntöä #18 etsimällä ilmauksia PR:n tekstistä — hauras, katso Backlog) | Kyllä | ### Työ: `test-vitest` Suoritetaan `build`-työn jälkeen. Epäonnistuminen estää yhdistämisen. -| Testikokonaisuus | Validoi | Estää yhdistämisen | -| ---------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `test:vitest` | MCP-palvelin (110 työkalua), autoCombo, välimuisti — vitest-testiajuri | Kyllä | -| `test:vitest:ui` | Käyttöliittymäkomponenttien testit — vitest-testiajuri | **Estää yhdistämisen** — ennestään epäonnistuvat testit on nimenomaisesti ohitettu tiedostossa `vitest.config.ts`; uudet epäonnistumiset kaatavat työn | +| Testikokonaisuus | Validoi | Estää yhdistämisen | +| ---------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP-palvelin (110 työkalua), autoCombo, välimuisti — vitest-testiajuri | Kyllä | +| `test:vitest:ui` | Käyttöliittymäkomponenttien testit — vitest-testiajuri | **Estää yhdistämisen** — ennestään epäonnistuvat testit on nimenomaisesti suljettu pois tiedostossa `vitest.config.ts`; uudet epäonnistumiset kaatavat työn | ### Öiset työnkulut (ajastettuja, neuvoa-antavia) -Nämä suoritetaan cron-aikataulun mukaisesti (ja `workflow_dispatch`-toiminnolla), ei koskaan PR:issä. Kaikki ovat neuvoa-antavia. +Nämä suoritetaan cron-aikataulun mukaisesti (ja `workflow_dispatch`-toiminnolla), ei koskaan +PR:ille. Kaikki ovat neuvoa-antavia. -| Työnkulku | Validoi | Estää yhdistämisen | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | -| `nightly-property` | fast-check-ominaisuustestit satunnaisella siemenluvulla ja suurella suorituskertojen määrällä | **Neuvoa-antava** | -| `nightly-resilience` | keon kasvun tarkistus, vikojen chaos-injektio, k6-kuormitus-/kestävyystestaus | **Neuvoa-antava** | -| `nightly-llm-security` | promptfoo-injektiosuojaus (estotila) + garak-koetukset (ohitetaan ilman palveluntarjoajan salaisuutta) | **Neuvoa-antava** | -| `nightly-schemathesis` | OpenAPI-sopimuksen fuzz-testaus (schemathesis) toimivaa OmniRoutea vasten käyttäen tiedostoa `docs/openapi.yaml` — tuo esiin määrittelyrikkomukset / käsittelemättömät 500-virheet (Vaihe 8 B.4) | **Neuvoa-antava** | -| `nightly-mutation` | Stryker-mutaatiotestauksen pisteet nopeasta yksikkötestilinjasta — selviytyvät mutantit paljastavat heikot väitteet | **Neuvoa-antava** | -| `nightly-compat` | Node-moottorin yhteensopivuusmatriisi tuetuilla `engines.node`-alueilla | **Neuvoa-antava** | +| Työnkulku | Validoi | Estää yhdistämisen | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `nightly-property` | fast-check-ominaisuustestit satunnaisella siemenluvulla ja suurella suorituskerralla | **Neuvoa-antava** | +| `nightly-resilience` | keon kasvun portti, häiriöiden kaaosinjektointi, k6-kuormitus-/pitkäkestoisuustestaus | **Neuvoa-antava** | +| `nightly-llm-security` | promptfoo-injektiosuojaus (estotila) + garak-tarkistukset (ohitetaan ilman palveluntarjoajan salaisuutta) | **Neuvoa-antava** | +| `nightly-schemathesis` | OpenAPI-sopimuksen fuzz-testaus (schemathesis) elävää OmniRoute-instanssia vasten käyttäen tiedostoa `docs/openapi.yaml` — tuo esiin spesifikaatiorikkomukset / käsittelemättömät 500-virheet (vaihe 8 B.4) | **Neuvoa-antava** | +| `nightly-mutation` | Stryker-mutaatiotestauksen pisteet nopealta yksikkötestikaistalta — selviytyvät mutantit paljastavat heikot assert-lauseet | **Neuvoa-antava** | +| `nightly-compat` | Node-moottorin yhteensopivuusmatriisi tuetuilla `engines.node`-alueilla | **Neuvoa-antava** | --- @@ -538,44 +542,44 @@ yhteysvirheen lokiin eikä koskaan riko istuntoja. --- -## Rationalisointijono (ROI-katselmus — vaihe 9, aalto 3) +## Rationalisointijono (ROI-katselmointi — vaihe 9, aalto 3) -Tämä inventaario täsmäytettiin `ci.yml`-tiedostoon 2026-06-17 (aiemmasta versiosta puuttuivat +Tämä luettelo täsmäytettiin `ci.yml`-tiedostoon 2026-06-17 (aiemmasta versiosta puuttuivat `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Täsmäytetylle joukolle tehty ROI-katselmus -tunnisti seuraavat rationalisointiehdokkaat. **Yhdistämiset ovat mekaanisia CI-muutoksia; -vaihdot ja poistot ovat operaattorille varattuja käytäntöpäätöksiä.** Mitään alla mainittua -ei ole vielä otettu käyttöön. +`check:codeql-ratchet`, `check:pr-evidence`). Täsmäytetyn kokonaisuuden ROI-katselmoinnissa +tunnistettiin seuraavat rationalisointiehdokkaat. **Yhdistämiset ovat mekaanisia CI-muutoksia; +käännöt ja poistot ovat operaattorille varattuja käytäntöpäätöksiä.** Mitään alla olevista +ei ole vielä toteutettu. -**Myös edellä dokumentoimatta** (neuvoa-antavia, signaali heikko): `docs-lint`-työ -(markdownlint + Vale, koko työssä `continue-on-error`) sekä erilliset tarkistustyönkulut +**Lisäksi yllä dokumentoimatta** (neuvoa-antavia, signaaliltaan heikkoja): `docs-lint`-työ +(markdownlint + Vale, koko työllä `continue-on-error`) ja erilliset tarkistustyönkulut `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` on -`quality-baseline.json`-tiedostossa, mutta sitä ei ole kytketty estävään räikkään -`ci.yml`-tiedostossa — mittari on tällä hetkellä irrallinen. +`quality-baseline.json`-tiedostossa, mutta sitä ei ole kytketty estävään räikkään `ci.yml`-tiedostossa — mittari +on tällä hetkellä irrallinen. -### Yhdistäminen / päällekkäisyyksien poistaminen (mekaaninen, pienempi riski) +### Yhdistäminen / päällekkäisyyksien poisto (mekaaninen, pienempi riski) Jokainen ehdokas validoitiin aktiivista porttitilaa vasten 2026-06-17 (luota, mutta varmista); -useat ”ilmeiset” yhdistämiset osoittautuivat peittävän teknistä velkaa, eivätkä ne ole -**suoraan korvaavia**. - -- **`check:docs-sync` suoritetaan kahdesti** — erikseen `lint`-työssä sekä uudelleen osana `check:docs-all`-tarkistusta (`docs-sync-strict`) ja huskyn pre-commit-koukkua. ✅ **TEHTY** — erillinen `lint`-suoritus poistettu. -- **CVE-tarkistus** — ❌ **EI ole siisti yhdistäminen.** `audit:deps` epäonnistuu ehdottomasti mistä tahansa vakavasta tai kriittisestä CVE:stä; `check:vuln-ratchet` (osv) epäonnistuu vain, jos perustasoon nähden tapahtuu _heikennys_ (tällä hetkellä 1 MODERATE). Semantiikka on erilainen — `audit:deps`-tarkistuksen poistaminen poistaisi ehdottoman vakavien ja kriittisten haavoittuvuuksien portin. Säilytä molemmat. -- **Syklien tunnistus** — ❌ **EI ole siisti yhdistäminen.** `check:circular-deps` (dpdm) raportoi **91 sykliä** (siksi se on neuvoa-antava); sitä ei voida muuttaa estäväksi ennen niiden ratkaisemista, ja sen kattavuus on laajempi kuin vihreän, kuratoidun `check:cycles`-tarkistuksen. Säilytä `check:cycles` estävänä; 91 dpdm-syklin ratkaiseminen kuuluu omaan työjonoonsa. -- **Monimutkaisuus** — ✅ **TEHTY** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): yksi ESLint-läpikäynti, laskenta ruleId-arvon mukaan, jotta syklomaattisuuden ja rivien enimmäismäärän sekä kognitiivisen monimutkaisuuden perustasot pysyvät erillisinä; yksittäiset `check:complexity` / `check:cognitive-complexity` säilyvät paikallista `--update`-ajoa varten. -- **`/api`-antihallusinaatio** — ✅ **TEHTY** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): yksi `src/app/api`-hakemiston tiedostojärjestelmäinventaario, openapi-routes + docs-symbols raportoivat edelleen erikseen; yksittäiset tarkistukset säilyvät paikallisia ajoja varten. -- **`check:node-runtime` suoritetaan 11 työssä** — ⚠️ **pieni ROI.** Jokainen on erillinen suoritusympäristö, ja tarkistus kestää alle sekunnin; kokonaissäästö olisi noin 10 sekuntia, mutta samalla menetettäisiin edullinen työkohtainen suojaus. Ei muutostyön arvoinen. -- **`typecheck:noimplicit:core` CI:n lint-tarkistuksessa** — ✅ **poistettu lint-työstä** (oli neuvoa-antava `continue-on-error`); estävä tyyppipinta muodostuu tarkistuksista `typecheck:core` + `check:type-coverage`. Paikallinen komentosarja säilytettiin. - -### Vaihda / päätä (operaattorin käytäntö) - -- `check:openapi-security-tiers` (neuvoa-antava) — ❌ **EI voida siististi muuttaa estäväksi.** Se päättyy koodilla 0, mutta varoittaa, että useilta `traffic-inspector`-reiteiltä kohdassa `LOCAL_ONLY_API_PREFIXES` puuttuu `x-loopback-only: true`-annotaatio. Sen pakottaminen edellyttää ensin näiden annotaatioiden lisäämistä `openapi.yaml`-tiedostoon. -- `typecheck:noimplicit:core` (neuvoa-antava) — estävä `check:type-coverage`-räikkä korvaa sen suurelta osin. Muuta se räikäksi tai poista tarpeeton toinen `tsc`-ajo. -- `test:vitest:ui` (nyt **estävä**) — aiemmat virheet on nimenomaisesti suljettu pois `vitest.config.ts`-tiedostossa `// #8618`-seurantakommenteilla; uudet virheet kaatavat työn. -- `check:secrets` (gitleaks, estävä räikkä jäädytetty kolmeen dokumentoituun väärään positiiviseen) — lisää kolme löydöstä sallittujen luetteloon nollatason saavuttamiseksi tai alenna tarkistus neuvoa-antavaksi. Päällekkäinen GitHubin oman salaisuuksien tarkistuksen ja `check:public-creds`-tarkistuksen kanssa. -- `check:pr-evidence` (estävä, tekee grep-haun PR-kuvauksen tekstistä) — suuri väärien positiivisten riski; poistaminen heikentäisi ehdottoman säännön #18 valvontaa, joten kyseessä on aito käytäntöpäätös. -- `semgrep` (neuvoa-antava erillinen tarkistus) — päällekkäinen CodeQL:n kanssa OWASP-perheiden osalta; kytke sen perustaso räikkään tai poista se. +useat ”ilmeiset” yhdistämiset osoittautuivat peittävän velkaa, eivätkä ne ole **suoraan** +korvattavissa. + +- **`check:docs-sync` suoritetaan kahdesti** — erillisenä `lint`-työssä ja uudelleen `check:docs-all`-tarkistuksen (`docs-sync-strict`) sekä husky-esitoimituskoukun sisällä. ✅ **VALMIS** — erillinen `lint`-kutsu poistettu. +- **CVE-tarkistus** — ❌ **EI suoraan yhdistettävissä.** `audit:deps` epäonnistuu ehdottomasti minkä tahansa vakavan/kriittisen CVE:n kohdalla; `check:vuln-ratchet` (osv) epäonnistuu vain baselineen nähden tapahtuvasta _regressiosta_ (tällä hetkellä 1 MODERATE). Semantiikka on erilainen — `audit:deps`-tarkistuksen poistaminen poistaisi ehdottoman vakavien/kriittisten haavoittuvuuksien portin. Säilytä molemmat. +- **Syklien tunnistus** — ✅ **VALMIS** (#15159 G-01/G-02). Vanha teksti kutsui `check:cycles`-tarkistusta ”vihreäksi, kuratoiduksi” portiksi ja perusteli sen säilyttämistä estävänä, koska `check:circular-deps` (dpdm) raportoi 91 sykliä. Tuo vihreä tulos oli **väärä positiivinen onnistuminen**: `check:cycles` tarkisti 5 alihakemistoa (450 tiedostoa), tunnisti vain staattiset `import|export … from` -rakenteet ja hylkäsi kaikki `@/`- ja `@omniroute/open-sse/`-määritteet, joten se ei voinut havaita dynaamisten tuontien ja aliasten muodostamia syklejä, jotka hallitsivat tietovarastoa. Korjattu: portti käy nyt läpi `src`- ja `open-sse`-hakemistot (5023 tiedostoa), kerää määritteet TypeScriptin AST:stä (joten `import("…")` lasketaan, mutta tyyppisijainnissa olevaa `typeof import("…")` -rakennetta ei) ja selvittää tsconfigin `paths`-määritykset. Se löytää **14** sykliä, ei 0:aa. Koska 14:ää jo olemassa olevaa sykliä ei voida korjata portin PR:ssä, `check:cycles` on nyt **räikkä** (`--ratchet`, yläraja `metrics.cycles.value = 14` tiedostossa `quality-baseline.json`, `direction: down`) — se estää minkä tahansa _regression_, ja määrä voi vain laskea. CI suorittaa komennon `npm run check:cycles:ratchet`. Velan vähentäminen etenee kohteen **A-01** mukana. `check:circular-deps` (dpdm) säilyy neuvoa-antavana laajempana toisena arviona. +- **Monimutkaisuus** — ✅ **VALMIS** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): yksi ESLint-läpikäynti, laskenta ruleId:n mukaan, jotta syklomaattisen monimutkaisuuden ja enimmäisrivimäärän sekä kognitiivisen monimutkaisuuden baselinet pysyvät erillisinä; yksittäiset `check:complexity` / `check:cognitive-complexity` säilyvät paikallista `--update`-käyttöä varten. +- **`/api`-hallusinaationesto** — ✅ **VALMIS** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): yksi `src/app/api`-hakemiston tiedostojärjestelmäinventaario; openapi-routes ja docs-symbols raportoivat edelleen itsenäisesti; yksittäiset tarkistukset säilyvät paikallisia suorituksia varten. +- **`check:node-runtime` suoritetaan 11 työssä** — ⚠️ **pieni ROI.** Jokainen käyttää erillistä suoritusympäristöä ja tarkistus kestää alle sekunnin; kokonaissäästö olisi noin 10 sekuntia, mutta samalla menetettäisiin edullinen työkohtainen suojaus. Ei muutostyön arvoista. +- **`typecheck:noimplicit:core` CI:n lint-tarkistuksessa** — ✅ **poistettu lint-työstä** (oli neuvoa-antava `continue-on-error`); estävä tyyppipinta muodostuu tarkistuksista `typecheck:core` + `check:type-coverage`. Paikallinen komentosarja säilytetty. + +### Käännä / päätä (operaattorin käytäntö) + +- `check:openapi-security-tiers` (neuvoa-antava) — ❌ **EI voida siirtää suoraan estäväksi.** Se palauttaa arvon 0, mutta varoittaa, että useilta `LOCAL_ONLY_API_PREFIXES`-määrityksen alaisilta `traffic-inspector`-reiteiltä puuttuu `x-loopback-only: true` -annotaatio. Sen pakottaminen edellyttää näiden annotaatioiden lisäämistä ensin `openapi.yaml`-tiedostoon. +- `typecheck:noimplicit:core` (neuvoa-antava) — estävä `check:type-coverage`-räikkä korvaa sen suurelta osin. Muuta räikäksi tai poista redundantti toinen `tsc`-läpikäynti. +- `test:vitest:ui` (nyt **estävä**) — jo olemassa olevat epäonnistumiset on nimenomaisesti rajattu pois `vitest.config.ts`-tiedostossa `// #8618`-seurantakommenteilla; uudet epäonnistumiset kaatavat työn. +- `check:secrets` (gitleaks, estävä räikkä lukittuna kolmeen dokumentoituun väärään positiiviseen) — lisää nämä 3 sallittujen luetteloon nollatason saavuttamiseksi tai alenna tarkistus neuvoa-antavaksi. Päällekkäinen GitHubin sisäänrakennetun salaisuuksien tarkistuksen ja `check:public-creds`-tarkistuksen kanssa. +- `check:pr-evidence` (estävä, hakee PR-kuvauksen proosasta) — suuri väärien positiivisten riski; poistaminen heikentää ehdottoman säännön nro 18 valvontaa, joten tämä on todellinen käytäntöpäätös. +- `semgrep` (neuvoa-antava erillistarkistus) — päällekkäinen CodeQL:n kanssa OWASP-perheiden osalta; kytke sen baseline räikkään tai poista. --- diff --git a/docs/i18n/fi/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/fi/docs/compression/COMPRESSION_GUIDE.md index 750a0a67e357..c7db80bb1fec 100644 --- a/docs/i18n/fi/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/fi/docs/compression/COMPRESSION_GUIDE.md @@ -455,8 +455,7 @@ injektoidaan englannin sijaan. Rungossa, jossa on `messages`, sisällön ohitus (`shouldBypassCavemanOutputMode()` tiedostossa `open-sse/services/compression/outputMode.ts`) tarkistaa kolme viimeisintä viestiä ja ohittaa tyylit koko vuoron ajaksi, kun ne vastaavat sen turvallisuus-, peruuttamattoman toiminnan, -selvennys- tai järjestysherkkiä avainsanoja. Ohitus suoritetaan riippumatta siitä, mihin -hallintapaneelin **Auto-Clarity Bypass** -kytkin (`cavemanOutputMode.autoClarity`) on asetettu. +selvennys- tai järjestysherkkiä avainsanoja. Ohitus suoritetaan, kun hallintapaneelin **Auto-Clarity Bypass** -kytkin (`cavemanOutputMode.autoClarity`) on päällä, mikä on oletus; kytkimen ollessa pois päältä valitut tyylit otetaan käyttöön myös näillä vuoroilla. Kun ohitus sallii vuoron läpi, `placeSystemInstruction()` (sama tiedosto), joka ei koskaan luo uutta `messages[0]`, sijoittaa lohkon ensimmäiseen näistä, jonka se löytää: diff --git a/docs/i18n/fi/docs/reference/ENVIRONMENT.md b/docs/i18n/fi/docs/reference/ENVIRONMENT.md index 9baa0e8affbb..9a17a6b1f5db 100644 --- a/docs/i18n/fi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/fi/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Vaaditut salaisuudet -Nämä **on** asetettava ennen ensimmäistä käynnistystä. Ilman niitä sovellus joko kieltäytyy käynnistymästä tai käyttää turvattomia oletusasetuksia. - -| Muuttuja | Vaaditaan | Oletusarvo | Lähdetiedosto | Kuvaus | -| ---------------------------- | ----------------------- | ----------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Kyllä** | _(ei mitään)_ | `src/lib/auth` | Allekirjoittaa ja vahvistaa kaikki hallintapaneelin istuntoevästeet (JWT). Luo komennolla `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Kyllä** | _(ei mitään)_ | `src/lib/db/apiKeys.ts` | AES-salausavain SQLite-tietokantaan tallennettujen API-avainarvojen salaamiseen. Luo komennolla `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Kyllä** | `CHANGEME` | Alustusskripti | Asettaa hallintapaneelin alkuperäisen järjestelmänvalvojan salasanan (vastaa `.env.example`-tiedoston oletusarvoa — se on tarkoituksella selvästi turvaton, jotta salasana vaihdetaan). **Vaihda ennen ensimmäistä käyttökertaa.** Vaihda salasana kirjautumisen jälkeen valitsemalla Hallintapaneeli → Asetukset → Suojaus. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Kyllä** (tuotannossa) | _(ei asetettu)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sisäisen Codex Responses WebSocket -sillan jaettu salaisuus. Todentaa Electronin/selaimen WS-välityspalvelimen ja OmniRouten väliset siltapyynnöt. ⚠️ **VAADITAAN tuotannossa — jos arvoa ei ole asetettu, kaikki WS-siltapyynnöt hylätään.** Luo komennolla `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ei | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA-kuoren palvelutyöntekijän välimuistin nollaamiseen käytettävä eksplisiittinen tunniste (#11779); ensimmäinen määritysjärjestyksessä. | -| `SOURCE_VERSION` | Ei | _(ei asetettu)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Määritysjärjestyksen toinen arvo — PaaS-koontijärjestelmät (esimerkiksi Heroku-tyyliset) asettavat tämän käyttöönotetuksi commitiksi. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ei | _(johdettu)_ | `src/shared/components/PwaRegister.tsx` | Koontiaikainen julkinen arvo, jota asiakas käyttää `/sw.js?v=…`-tiedoston rekisteröintiin; johdetaan kahdesta yllä olevasta arvosta ja sen jälkeen git SHA:sta. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ei (automaattinen) | _(automaattinen jokaisella käynnistyskerralla)_ | `src/server/authz/policies/management.ts` | Prosessikohtainen salaisuus, joka todistaa luotetun vertais-IP-leiman olevan peräisin OmniRouten omalta HTTP-palvelimelta (`scripts/dev/peer-stamp.mjs`). Authz-väliohjelmisto luottaa pyynnön paikallisuuteen (LOCAL_ONLY-reittien loopback-/LAN-rajaus) vain, kun leima sisältää tämän tunnuksen. Luodaan automaattisesti jokaisella käynnistyskerralla — jätä asettamatta; määritä kiinteäksi vain moniprosessikokoonpanoissa, joiden on jaettava leima. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ei | _(ei asetettu)_ | `scripts/dev/peer-stamp.mjs` | Pilkuilla erotellut käänteisten välityspalvelinten IP-osoitteet tai CIDR-alueet, joiden `X-Forwarded-For`- / `X-Real-IP`-arvoihin IP-osoitteiden sallimis- ja estoluettelo voi luottaa aina luotettujen loopback-osoitteiden, yksityisverkko-osoitteiden ja Cloudflare-reunapalvelinten lisäksi. Tätä tarvitaan vain välityspalvelimelle, jolla on jokin muu julkinen osoite; muussa tapauksessa suodatin arvioi välityspalvelimen omaa osoitetta. | +Asettamattomat salaisuudet, jotka on merkitty **Kyllä**, luodaan automaattisesti ensimmäisellä käynnistyskerralla (`JWT_SECRET`, `API_KEY_SECRET`) ja tallennetaan datahakemistossa olevaan tiedostoon `server.env` — aseta ne erikseen, jos hakemisto on lyhytikäinen. Poikkeuksena on `OMNIROUTE_WS_BRIDGE_SECRET`: sitä ei tallenneta tiedostoon `server.env` — palvelimen käynnistyspisteet luovat jokaisella käynnistyksellä kertakäyttöisen arvon, jota vain prosessin sisällä toimiva silta voi käyttää, joten ulkoisten silta-asiakasohjelmien on asetettava se erikseen. + +| Muuttuja | Vaaditaan | Oletusarvo | Lähdetiedosto | Kuvaus | +| ---------------------------- | ----------------------- | ----------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Kyllä** | _(ei mitään)_ | `src/lib/auth` | Allekirjoittaa ja vahvistaa kaikki hallintapaneelin istuntoevästeet (JWT). Luo komennolla `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Kyllä** | _(ei mitään)_ | `src/lib/db/apiKeys.ts` | AES-salausavain SQLite-tietokantaan tallennettujen API-avainarvojen salaamiseen. Luo komennolla `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ei | _(ei asetettu)_ | `src/lib/auth/managementPassword.ts` | Asettaa hallintapaneelin salasanan käynnistyksen yhteydessä, jos yhtään salasanaa ei ole vielä tallennettu; kun salasana on tallennettu, asetusta ei enää huomioida. npm- ja lähdekoodiasennukset kopioivat `.env.example`-tiedoston tiedostoksi `.env`, joten ne käynnistyvät sen julkisesti tunnetun paikkamerkin `CHANGEME` kanssa: korvaa se ennen ensimmäistä käynnistystä, tai aseta kyseiseen `.env`-tiedostoon `INITIAL_PASSWORD=` (tyhjä), jolloin salasana luodaan ohjatussa asennustoiminnossa ensimmäisellä käyntikerralla, kuten asettamaton arvo (Docker-kuvan oletus) tekee. Vaihda tallennettu salasana kohdasta Hallintapaneeli → Asetukset → Suojaus tai suorita `omniroute reset-password` (`node bin/reset-password.mjs` lähdekoodiversiossa) asettaen `DATA_DIR` palvelimen datahakemistoon. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Kyllä** (tuotannossa) | _(ei asetettu)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sisäisen Codex Responses WebSocket -sillan jaettu salaisuus. Todentaa Electronin/selaimen WS-välityspalvelimen ja OmniRouten väliset siltapyynnöt. ⚠️ **VAADITAAN tuotannossa — jos arvoa ei ole asetettu, kaikki WS-siltapyynnöt hylätään.** Luo komennolla `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ei | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA-kuoren palvelutyöntekijän välimuistin nollaamiseen käytettävä eksplisiittinen tunniste (#11779); ensimmäinen määritysjärjestyksessä. | +| `SOURCE_VERSION` | Ei | _(ei asetettu)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Määritysjärjestyksen toinen arvo — PaaS-koontijärjestelmät (esimerkiksi Heroku-tyyliset) asettavat tämän käyttöönotetuksi commitiksi. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ei | _(johdettu)_ | `src/shared/components/PwaRegister.tsx` | Koontiaikainen julkinen arvo, jota asiakas käyttää `/sw.js?v=…`-tiedoston rekisteröintiin; johdetaan kahdesta yllä olevasta arvosta ja sen jälkeen git SHA:sta. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ei (automaattinen) | _(automaattinen jokaisella käynnistyskerralla)_ | `src/server/authz/policies/management.ts` | Prosessikohtainen salaisuus, joka todistaa luotetun vertais-IP-leiman olevan peräisin OmniRouten omalta HTTP-palvelimelta (`scripts/dev/peer-stamp.mjs`). Authz-väliohjelmisto luottaa pyynnön paikallisuuteen (LOCAL_ONLY-reittien loopback-/LAN-rajaus) vain, kun leima sisältää tämän tunnuksen. Luodaan automaattisesti jokaisella käynnistyskerralla — jätä asettamatta; määritä kiinteäksi vain moniprosessikokoonpanoissa, joiden on jaettava leima. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ei | _(ei asetettu)_ | `scripts/dev/peer-stamp.mjs` | Pilkuilla erotellut käänteisten välityspalvelinten IP-osoitteet tai CIDR-alueet, joiden `X-Forwarded-For`- / `X-Real-IP`-arvoihin IP-osoitteiden sallimis- ja estoluettelo voi luottaa aina luotettujen loopback-osoitteiden, yksityisverkko-osoitteiden ja Cloudflare-reunapalvelinten lisäksi. Tätä tarvitaan vain välityspalvelimelle, jolla on jokin muu julkinen osoite; muussa tapauksessa suodatin arvioi välityspalvelimen omaa osoitetta. | ### Luontikomennot @@ -217,38 +217,38 @@ OmniRoute käyttää kaikkeen pysyvään tallennukseen **SQLitea** (`better-sqli ## 4. Tietoturva ja todennus -| Muuttuja | Oletusarvo | Lähdetiedosto | Kuvaus | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Suola, joka yhdistetään laitteistotunnisteisiin koneen sormenjäljen muodostamista varten. Vaihda arvo käyttöönottokohtaisesti ympäristöjen eristämiseksi. | -| `OMNIROUTE_CLI_SALT` | _(asettamaton = satunnainen asennuskohtainen suola, joka säilytetään tiedostossa `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-suola paikallisen CLI-todennustunnuksen johtamiseen. Tämän arvon asettaminen vaihtaa kaikki koneen CLI-tunnukset ja ohittaa aina säilytetyn suolan. Katso `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Asettaa istuntoevästeiden `Secure`-lipun. Arvon **on oltava `true`**, kun palvelua käytetään HTTPS:n takana. | -| `REQUIRE_API_KEY` | `false` | API-väliohjelmisto | Kun arvo on `true`, kaikkien `/v1/*`-välityspyyntöjen on sisällettävä kelvollinen API-avain. **Tämä lippu ei rajoita `GET /v1/models` -päätepistettä**, vaan se noudattaa hallintapaneelin kirjautumiskäytäntöä (`requireAuthForModels`) — siksi `/v1/models`-päätepisteen palauttama `401` EI tarkoita, että päättely olisi suojattu. Katso `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Sallii API-avainten täydellisten arvojen näyttämisen hallintapaneelin käyttöliittymässä. Määritettävissä hallintapaneelin ominaisuuslipuista; aiheuttaa tietoturvariskin jaetuissa instansseissa. | -| `NO_LOG_API_KEY_IDS` | _(tyhjä)_ | `src/lib/compliance/index.ts` | Pilkuilla erotetut API-avaintunnukset, joiden pyyntöjä ei kirjata lokiin (GDPR-vaatimustenmukaisuus). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(asettamaton = rajoittamaton)_ | `src/shared/utils/apiKeyPolicy.ts` | Päiväkohtainen pyyntöbudjetin vara-arvo, jota sovelletaan API-avaimiin, joiden `rate_limits`-sarake on null. Asettamaton tai tyhjä: ei implisiittistä rajoitusta (#2289, #11017). Myös `0` tarkoittaa samaa (rajoittamaton). Positiivinen kokonaisluku N ottaa käyttöön rajat N/päivä, 5N/viikko ja 20N/kuukausi. Virheellisesti muotoillut ei-tyhjät arvot käyttävät vanhoja rajoja 1000/päivä, 5000/viikko ja 20000/kuukausi. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Pyynnön rungon suurin sallittu koko. Tämän rajan ylittävät hyötykuormat hylätään. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Tämän raja-arvon kokoiset tai sitä suuremmat varsinaiset pyyntörungot varaavat atomisesti prosessipaikallisen raskaan käsittelyn pääsyluvan ennen JSON-jäsennystä (BYTE-polku, mukaan lukien `POST /v1/responses`). Sama [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-muutoksen terveen kapasiteettimarginaalin poikkeus kuin rakenteellisesti raskaissa pyynnöissä; edelleen `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-arvon / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110)-muutoksen rajoittama, jotta [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ei toistu. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 Mt) | `src/shared/middleware/chatBodyAdmission.ts` | Chat-reitin ehdoton raja, joka tarkistetaan rajatun vastaanoton aikana luettujen tavujen perusteella, mukaan lukien pyynnöt, joiden `Content-Length` puuttuu, on virheellinen tai ilmoittaa koon totuudenvastaisesti; rajan ylityksestä palautetaan `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ei asetettu — ei pyyntömäärän rajaa)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** tämä vanha pyyntöjen MÄÄRÄÄ rajoittava asetus vaikuttaa nyt vain, kun se on asetettu eksplisiittisesti. Kun sitä ei ole asetettu (oletus), raskaiden chat-pyyntöjen hyväksyntää rajoittaa sen sijaan `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaattisesti johdettu TAVUbudjetti, joka mitoitetaan prosessin todellisen muistirajan perusteella **yhdessä prosessissa** (yksi V8-keko). Kaksi päällekkäistä, noin 750 000 tunnisteen `/v1/responses`-pyyntöä kaataa noin 12 Gi:n keot (#7849) — tämä on **muistibudjettia** koskeva varoitus, ei tuotteen ehdoton kahden pyynnön enimmäismäärä. Terve prosessi (keko hylkäyssuhteen alapuolella) VOI hyväksyä useampia samanaikaisia pitkiä `/v1/responses`-pyyntöjä asetuksen `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` kautta. Kymmenissä pitkissä SSE-asiakkaissa (40–50) on kyse keosta + asetuksesta `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ei ”enintään kahdesta”. Tämän arvon sokea kasvattaminen ”isäntäkoneen hyödyntämiseksi” tuo ongelman #7849 takaisin. Moninkertaista **keot** käyttämällä **N:ää riippumatonta `DATA_DIR`-hakemistoa** (#11024); älä koskaan käytä asetusta `replicas>1` yhdelle SQLite-tiedostolle. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(johdetaan automaattisesti)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automaattisesti johdetun vastaanoton tavubudjetin ohitus (25 % tiukemmasta V8-/cgroup-muistirajasta jaettuna kahdeksankertaisella tilapäisellä vahvistuskertoimella). Johdetut ja eksplisiittiset arvot rajataan välille 8 MiB–2 GiB. Tehollista budjettia suurempi runko hylätään välittömästi virheellä `413 body_exceeds_budget`; yksittäin käsiteltävissä olevien runkojen välinen resurssikilpailu palauttaa edelleen uudelleenyrityksen sallivan virheen `503`. 40–50 samanaikaisessa pitkässä SSE-asiakkaassa on kyse tästä budjetista + keosta, ei ehdottomasta ”enintään kahden” rajasta. Tarkista arvot `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` osoitteesta `/api/monitoring/health` ennen säätämistä. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kekopaineen hylkäyssuhde (`heapUsed / heap_size_limit`) TAVU- ja RAKENNEperusteiselle raskaiden pyyntöjen hyväksynnälle (#10183, #10268, #10437). Raja-arvon `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ylittävä samanaikainen raskas pyyntö hylätään uudelleenyrityksen sallivalla virheellä `503` vain, kun keko on MYÖS tämän suhteen tasolla tai sen yläpuolella; terveellä keolla se hyväksytään terveen tilan lisäkapasiteetin kautta. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (oletus `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Rajattu lisäkapasiteetti terveen keon nopealle polulle (#10437) **sekä** RAKENNE- että TAVUperusteisessa hyväksynnässä (`admitChatRequest`, mukaan lukien rungot ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Ilman tätä rajaa jokainen ruuhka-aikana saapuva pyyntö ohitti hyväksyntärajoituksen ilman ylärajaa, jos keko oli terve. Kun näin monta samanaikaista varausta on aktiivisena terveen keon polulla, uudet ruuhka-aikana saapuvat pyynnöt siirtyvät SAMAAN rajatun odotuksen ja hylkäyksen polkuun, jota käytetään todellisen kekopaineen aikana. `0` poistaa ohituksen kokonaan käytöstä. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Viestimäärä, jonka perusteella chat-pyyntö luokitellaan raskaaksi, vaikka sen runko alittaisi tavurajan. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Työkalumäärä, jonka perusteella chat-pyyntö luokitellaan raskaaksi, vaikka sen runko alittaisi tavurajan. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatiivinen merkkijonon kokoon perustuva tunnistearvio, jonka perusteella pyyntö luokitellaan raskaaksi; tämä on hyväksyntäkustannuksen likiarvo, ei palveluntarjoajan laskutuksessa käyttämä tokenisointi. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (poistettu käytöstä) | `src/shared/middleware/chatBodyAdmission.ts` | Valinnainen erikseen käyttöönotettava chat-historian raja. Oletusarvoisesti poissa käytöstä: viestimäärä on käyttöympäristökohtainen käytäntö, ei pyynnön yleispätevä ominaisuus, ja tässä asetettu raja hylkäisi keskustelut lopullisella virheellä `413` ennen kuin pakkausputki ehtisi tehdä niistä käsiteltäviä. Keon kasvua rajoittavat `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ja kekopaineeseen perustuva hylkäys. Aseta positiivinen arvo muistirajoitteisissa käyttöympäristöissä, joissa tarvitaan ehdoton yläraja; rajan ylityksestä palautetaan tällöin rakenteinen, pakkauksen tarpeesta ilmoittava virhe `413`. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 Mt) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Ehdoton raja suoratoistamattomalle ylävirran vastaukselle, joka puskuroidaan kokonaan muistiin. Kun raja ylittyy, ylävirran lukija peruutetaan ja pyyntö epäonnistuu nopeasti sen sijaan, että rajattomasti kasvava merkkijono jatkaisi kasvuaan keon loppumiseen asti. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Ylävirran vastauksen otsakkeista välitettävien tavujen enimmäismäärä siirtomuodossa. Kun budjetti ylittyy, alemman prioriteetin otsakkeet (esim. mukautetut `x-codex-*`-otsakkeet ja `x-oai-request-id`) poistetaan, jotta pysytään yleisten käänteisten välityspalvelinten otsakerajojen sisällä. Aseta suuremmaksi, jos haluat välittää enemmän ylävirran metatietoja suuremman vastausotsakekoon kustannuksella. | -| `CORS_ORIGIN` | _(ei asetettu)_ | `src/server/cors/origins.ts` | Vanha yhden alkuperän CORS-sallittujen luettelo. Käytä uusissa käyttöönotoissa mieluummin asetusta `CORS_ALLOWED_ORIGINS`. CORS on tarkoitettu vain eri alkuperästä tuleville selaimen API-asiakkaille; todennetut hallintapaneelin kirjoituspyynnöt käyttävät sen sijaan saman alkuperän pyyntöjä sekä istuntoon sidottua CSRF-suojausta. | -| `CORS_ALLOWED_ORIGINS` | _(ei asetettu)_ | `src/server/cors/origins.ts` | Pilkuilla eroteltu CORS-sallittujen luettelo. Jokerimerkkiä ei lähetetä, ellei asetusta `CORS_ALLOW_ALL=true` ole määritetty nimenomaisesti. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Vain kehityskäyttöön tarkoitettu vararatkaisu, joka palauttaa minkä tahansa selaimen `Origin`-arvon. Älä ota tätä käyttöön jaetuissa ympäristöissä tai tuotantokäyttöönotoissa. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Estä palveluntarjoajakutsut, jotka kohdistuvat yksityisiin, loopback- tai link-local-IP-osoitealueisiin. Poista käytöstä vain eristetyissä testiympäristöissä. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Salli yksityisiin tai paikallisiin verkkoihin (localhost, 192.168.x.x, 10.x.x.x jne.) osoittavat palveluntarjoajien URL-osoitteet. **VAADITAAN itse ylläpidetyille palveluntarjoajille** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Kun arvo on `false`, hallintapaneeli hylkää paikallisten URL-osoitteiden validoinnin. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Salli palveluntarjoajien lisääminen ja validointi paikallisissa tai yksityisissä osoitteissa (127.0.0.1, localhost, lähiverkko, yksityiset osoitealueet) — koskee vain palveluntarjoajan validointipolkua. **Oletusarvo on `true`** (paikallisuus ensisijaisena); aseta arvoksi `false`, jos haluat sallia tiukasti vain julkiset osoitteet. Pilvipalvelujen metatietopäätepisteet (169.254.169.254, metadata.google.internal) pysyvät estettyinä asetuksesta riippumatta. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Salli `/v1/audio/*`-reittien (litterointi, puhe, käännökset) käyttää localhostin ulkopuolella ylläpidettyä OpenAI-yhteensopivaa palveluntarjoajasolmua. Oletusarvoisesti pois käytöstä — äänen reitittäminen etäisännälle muuttaa ulospäin suuntautuvan liikenteen identiteettiä, joten operaattorin on tehtävä siitä nimenomainen päätös. Loopback- ja yksityiset solmut (localhost, 127.0.0.1, 172.16-31.x) ovat aina sallittuja, eikä asetus vaikuta niihin. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Salli `POST /v1/rerank`-pyynnön (sekä muistimoottorin loopback-uudelleenjärjestelyvaiheen) käyttää localhostin ulkopuolella ylläpidettyä OpenAI-yhteensopivaa palveluntarjoajasolmua — esimerkiksi TEI:tä, Infinityä, vLLM:ää tai muuta vastaavaa suorittavaa lähiverkkolaitetta tai Tailscale-vertaisjärjestelmää. Oletusarvoisesti pois käytöstä — etäisännälle reitittäminen muuttaa ulospäin suuntautuvan liikenteen identiteettiä, joten operaattorin on tehtävä siitä nimenomainen päätös. Loopback-solmut (localhost, 127.0.0.1, 172.16-31.x) ovat aina sallittuja, eikä asetus vaikuta niihin. Etäsolmujen on myös läpäistävä palveluntarjoajan lähtevien URL-osoitteiden käytäntö (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); pilvipalvelujen metatieto-isäntiin ei koskaan reititetä. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kun OIDC on käytössä, poista salasanakirjautuminen käytöstä, jotta käyttäjät voivat tunnistautua vain OIDC-kertakirjautumisella. Myös pelkkä alias `OIDC_DISABLE_PASSWORD_LOGIN` hyväksytään; saman avaimen hallintapaneelin ominaisuuslippu on etusijalla. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Asetuksen `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` pelkkä alias (#10889). | +| Muuttuja | Oletusarvo | Lähdetiedosto | Kuvaus | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Suola, joka yhdistetään laitteistotunnisteisiin koneen sormenjäljen muodostamista varten. Vaihda arvo käyttöönottokohtaisesti ympäristöjen eristämiseksi. | +| `OMNIROUTE_CLI_SALT` | _(asettamaton = satunnainen asennuskohtainen suola, joka säilytetään tiedostossa `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-suola paikallisen CLI-todennustunnuksen johtamiseen. Tämän arvon asettaminen vaihtaa kaikki koneen CLI-tunnukset ja ohittaa aina säilytetyn suolan. Katso `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Asettaa istuntoevästeiden `Secure`-lipun. Arvon **on oltava `true`**, kun palvelua käytetään HTTPS:n takana. | +| `REQUIRE_API_KEY` | `false` | API-väliohjelmisto | Kun arvo on `true`, kaikkien `/v1/*`-välityspyyntöjen on sisällettävä kelvollinen API-avain. **Tämä lippu ei rajoita `GET /v1/models` -päätepistettä**, vaan se noudattaa hallintapaneelin kirjautumiskäytäntöä (`requireAuthForModels`) — siksi `/v1/models`-päätepisteen palauttama `401` EI tarkoita, että päättely olisi suojattu. Katso `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Sallii API-avainten täydellisten arvojen näyttämisen hallintapaneelin käyttöliittymässä. Määritettävissä hallintapaneelin ominaisuuslipuista; aiheuttaa tietoturvariskin jaetuissa instansseissa. | +| `NO_LOG_API_KEY_IDS` | _(tyhjä)_ | `src/lib/compliance/index.ts` | Pilkuilla erotetut API-avaintunnukset, joiden pyyntöjä ei kirjata lokiin (GDPR-vaatimustenmukaisuus). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(asettamaton = rajoittamaton)_ | `src/shared/utils/apiKeyPolicy.ts` | Päiväkohtainen pyyntöbudjetin vara-arvo, jota sovelletaan API-avaimiin, joiden `rate_limits`-sarake on null. Asettamaton tai tyhjä: ei implisiittistä rajoitusta (#2289, #11017). Myös `0` tarkoittaa samaa (rajoittamaton). Positiivinen kokonaisluku N ottaa käyttöön rajat N/päivä, 5N/viikko ja 20N/kuukausi. Virheellisesti muotoillut ei-tyhjät arvot käyttävät vanhoja rajoja 1000/päivä, 5000/viikko ja 20000/kuukausi. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Pyynnön rungon suurin sallittu koko. Tämän rajan ylittävät hyötykuormat hylätään. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Tämän raja-arvon kokoiset tai sitä suuremmat varsinaiset pyyntörungot varaavat atomisesti prosessipaikallisen raskaan käsittelyn pääsyluvan ennen JSON-jäsennystä (BYTE-polku, mukaan lukien `POST /v1/responses`). Sama [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-muutoksen terveen kapasiteettimarginaalin poikkeus kuin rakenteellisesti raskaissa pyynnöissä; edelleen `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-arvon / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110)-muutoksen rajoittama, jotta [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ei toistu. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 Mt) | `src/shared/middleware/chatBodyAdmission.ts` | Chat-reitin ehdoton raja, joka tarkistetaan rajatun vastaanoton aikana luettujen tavujen perusteella, mukaan lukien pyynnöt, joiden `Content-Length` puuttuu, on virheellinen tai ilmoittaa koon totuudenvastaisesti; rajan ylityksestä palautetaan `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ei asetettu — ei pyyntömäärän rajaa)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** tämä vanha pyyntöjen MÄÄRÄÄ rajoittava asetus vaikuttaa nyt vain, kun se on asetettu eksplisiittisesti. Kun sitä ei ole asetettu (oletus), raskaiden chat-pyyntöjen hyväksyntää rajoittaa sen sijaan `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaattisesti johdettu TAVUbudjetti, joka mitoitetaan prosessin todellisen muistirajan perusteella **yhdessä prosessissa** (yksi V8-keko). Kaksi päällekkäistä, noin 750 000 tunnisteen `/v1/responses`-pyyntöä kaataa noin 12 Gi:n keot (#7849) — tämä on **muistibudjettia** koskeva varoitus, ei tuotteen ehdoton kahden pyynnön enimmäismäärä. Terve prosessi (keko hylkäyssuhteen alapuolella) VOI hyväksyä useampia samanaikaisia pitkiä `/v1/responses`-pyyntöjä asetuksen `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` kautta. Kymmenissä pitkissä SSE-asiakkaissa (40–50) on kyse keosta + asetuksesta `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ei ”enintään kahdesta”. Tämän arvon sokea kasvattaminen ”isäntäkoneen hyödyntämiseksi” tuo ongelman #7849 takaisin. Moninkertaista **keot** käyttämällä **N:ää riippumatonta `DATA_DIR`-hakemistoa** (#11024); älä koskaan käytä asetusta `replicas>1` yhdelle SQLite-tiedostolle. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(johdetaan automaattisesti)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automaattisesti johdetun vastaanoton tavubudjetin ohitus (25 % tiukemmasta V8-/cgroup-muistirajasta jaettuna kahdeksankertaisella tilapäisellä vahvistuskertoimella). Johdetut ja eksplisiittiset arvot rajataan välille 8 MiB–2 GiB. Tehollista budjettia suurempi runko hylätään välittömästi virheellä `413 body_exceeds_budget`; yksittäin käsiteltävissä olevien runkojen välinen resurssikilpailu palauttaa edelleen uudelleenyrityksen sallivan virheen `503`. 40–50 samanaikaisessa pitkässä SSE-asiakkaassa on kyse tästä budjetista + keosta, ei ehdottomasta ”enintään kahden” rajasta. Tarkista arvot `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` osoitteesta `/api/monitoring/health` ennen säätämistä. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kekopaineen hylkäyssuhde (`heapUsed / heap_size_limit`) TAVU- ja RAKENNEperusteiselle raskaiden pyyntöjen hyväksynnälle (#10183, #10268, #10437). Raja-arvon `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ylittävä samanaikainen raskas pyyntö hylätään uudelleenyrityksen sallivalla virheellä `503` vain, kun keko on MYÖS tämän suhteen tasolla tai sen yläpuolella; terveellä keolla se hyväksytään terveen tilan lisäkapasiteetin kautta. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (oletus `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Rajattu lisäkapasiteetti terveen keon nopealle polulle (#10437) **sekä** RAKENNE- että TAVUperusteisessa hyväksynnässä (`admitChatRequest`, mukaan lukien rungot ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Ilman tätä rajaa jokainen ruuhka-aikana saapuva pyyntö ohitti hyväksyntärajoituksen ilman ylärajaa, jos keko oli terve. Kun näin monta samanaikaista varausta on aktiivisena terveen keon polulla, uudet ruuhka-aikana saapuvat pyynnöt siirtyvät SAMAAN rajatun odotuksen ja hylkäyksen polkuun, jota käytetään todellisen kekopaineen aikana. `0` poistaa ohituksen kokonaan käytöstä. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Viestimäärä, jonka perusteella chat-pyyntö luokitellaan raskaaksi, vaikka sen runko alittaisi tavurajan. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Työkalumäärä, jonka perusteella chat-pyyntö luokitellaan raskaaksi, vaikka sen runko alittaisi tavurajan. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatiivinen merkkijonon kokoon perustuva tunnistearvio, jonka perusteella pyyntö luokitellaan raskaaksi; tämä on hyväksyntäkustannuksen likiarvo, ei palveluntarjoajan laskutuksessa käyttämä tokenisointi. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (poistettu käytöstä) | `src/shared/middleware/chatBodyAdmission.ts` | Valinnainen erikseen käyttöönotettava chat-historian raja. Oletusarvoisesti poissa käytöstä: viestimäärä on käyttöympäristökohtainen käytäntö, ei pyynnön yleispätevä ominaisuus, ja tässä asetettu raja hylkäisi keskustelut lopullisella virheellä `413` ennen kuin pakkausputki ehtisi tehdä niistä käsiteltäviä. Keon kasvua rajoittavat `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ja kekopaineeseen perustuva hylkäys. Aseta positiivinen arvo muistirajoitteisissa käyttöympäristöissä, joissa tarvitaan ehdoton yläraja; rajan ylityksestä palautetaan tällöin rakenteinen, pakkauksen tarpeesta ilmoittava virhe `413`. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 Mt) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Ehdoton raja suoratoistamattomalle ylävirran vastaukselle, joka puskuroidaan kokonaan muistiin. Kun raja ylittyy, ylävirran lukija peruutetaan ja pyyntö epäonnistuu nopeasti sen sijaan, että rajattomasti kasvava merkkijono jatkaisi kasvuaan keon loppumiseen asti. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Ylävirran vastauksen otsakkeista välitettävien tavujen enimmäismäärä siirtomuodossa. Kun budjetti ylittyy, alemman prioriteetin otsakkeet (esim. mukautetut `x-codex-*`-otsakkeet ja `x-oai-request-id`) poistetaan, jotta pysytään yleisten käänteisten välityspalvelinten otsakerajojen sisällä. Aseta suuremmaksi, jos haluat välittää enemmän ylävirran metatietoja suuremman vastausotsakekoon kustannuksella. | +| `CORS_ORIGIN` | _(ei asetettu)_ | `src/server/cors/origins.ts` | Vanha yhden alkuperän CORS-sallittujen luettelo. Käytä uusissa käyttöönotoissa mieluummin asetusta `CORS_ALLOWED_ORIGINS`. CORS on tarkoitettu vain eri alkuperästä tuleville selaimen API-asiakkaille; todennetut hallintapaneelin kirjoituspyynnöt käyttävät sen sijaan saman alkuperän pyyntöjä sekä istuntoon sidottua CSRF-suojausta. | +| `CORS_ALLOWED_ORIGINS` | _(ei asetettu)_ | `src/server/cors/origins.ts` | Pilkuilla eroteltu CORS-sallittujen luettelo. Jokerimerkkiä ei lähetetä, ellei asetusta `CORS_ALLOW_ALL=true` ole määritetty nimenomaisesti. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Vain kehityskäyttöön tarkoitettu vararatkaisu, joka palauttaa minkä tahansa selaimen `Origin`-arvon. Älä ota tätä käyttöön jaetuissa ympäristöissä tai tuotantokäyttöönotoissa. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Vanha alias. Sen hallintapaneelinkytkin (tietokantaan tallennettu ohitus) luetaan ennen tätä ympäristöarvoa; arvo `false`, `0`, `no` tai `off` jommassakummassa sammuttaa isäntätarkistukset samalla tavalla kuin `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, silloinkin kun kyseisen lipun hallintapaneelinkytkin on tallennettu pois päältä; muut arvot jättävät suojan kahden alla olevan lipun varaan. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Sammuttaa lähtevien URL-osoitteiden suojan isäntätarkistukset pilvipalvelujen metatietojen estoa myöten (koko 169.254.0.0/16, metadata.google.internal) niillä palveluntarjoajapoluilla, jotka noudattavat suojan tilaa: URL-osoitteiden validointi, mallien löytäminen, palveluntarjoajasolmujen perus-URL-osoitteet (mukaan lukien etäiset uudelleenjärjestelysolmut) ja proxy-varajärjestelmän testi sekä muut palveluntarjoajaan liittyvät lähtevät polut kuten etäkuvien ja -median nouto. Se sallii myös yksityiset webhook-kohteet paitsi pilvipalvelujen metatietojen ja link-local-päätepisteet, jotka pysyvät estettyinä silloinkin, kun tämä lippu on päällä. `BaseExecutor.execute()`-kutsun kautta lähetetyt keskustelupyynnöt säilyttävät pilvipalvelujen metatietotarkistuksensa. Paikalliset palveluntarjoajat toimivat oletusarvoilla: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (oletus `true`) sallii paikalliset ja lähiverkon URL-osoitteet jo validointi-, löytämis- ja palveluntarjoajasolmupoluilla; proxy-varajärjestelmän testi ja yksityiset webhook-kohteet kysyvät vain tätä lippua ja estävät paikalliset ja lähiverkon isännät, kunnes lippu on päällä. Ohittaa arvon `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Hallintapaneelinkytkimestä tallennettu arvo on tätä muuttujaa tärkeämpi. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Sallii palveluntarjoajien URL-osoitteet paikallisissa ja yksityisissä osoitteissa (127.0.0.1, localhost, lähiverkko, yksityiset osoitealueet) palveluntarjoajien validointiin, mallien löytämiseen, keskusteluun ja muille palveluntarjoajaan liittyville lähteville poluille, joita suojan tila säätelee. **Oletusarvo `true`** (paikallisuus ensisijaisena): suoja estää silloin pilvipalvelujen metatietopäätepisteet — koko 169.254.0.0/16 sekä tunnetut metatietoisäntänimet (metadata.google.internal, metadata.goog, Alibaban, Azuren ja Oraclen IMDS, fd00:ec2::254). Aseta `false`, jos haluat vain julkiset -tila, joka estää myös yksityiset ja loopback-isännät. Tarkistus tehdään isäntänimelle tai IP-literaalille kirjoitetussa muodossa ennen mitään DNS-hakua; vain julkiset -tilassa turvallisen fetch-kääreen lisäksi selvittää isäntänimen ja hylkää jokaisen yksityisen vastauksen. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` sammuttaa nämä tarkistukset sen rivin luetteleilta poluilta. Sisäänrakennetut paikalliset palveluntarjoajat, kuten LM Studio, Ollama, vLLM, Llamafile ja Triton, ohittavat kaikki isäntätarkistukset avainten validoinnissa ja keskustelussa. Hallintapaneelinkytkimestä tallennettu arvo on tätä muuttujaa tärkeämpi. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Salli `/v1/audio/*`-reittien (litterointi, puhe, käännökset) käyttää localhostin ulkopuolella ylläpidettyä OpenAI-yhteensopivaa palveluntarjoajasolmua. Oletusarvoisesti pois käytöstä — äänen reitittäminen etäisännälle muuttaa ulospäin suuntautuvan liikenteen identiteettiä, joten operaattorin on tehtävä siitä nimenomainen päätös. Loopback- ja yksityiset solmut (localhost, 127.0.0.1, 172.16-31.x) ovat aina sallittuja, eikä asetus vaikuta niihin. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Salli `POST /v1/rerank`-pyynnön (sekä muistimoottorin loopback-uudelleenjärjestelyvaiheen) käyttää localhostin ulkopuolella ylläpidettyä OpenAI-yhteensopivaa palveluntarjoajasolmua — esimerkiksi TEI:tä, Infinityä, vLLM:ää tai muuta vastaavaa suorittavaa lähiverkkolaitetta tai Tailscale-vertaisjärjestelmää. Oletusarvoisesti pois käytöstä — etäisännälle reitittäminen muuttaa ulospäin suuntautuvan liikenteen identiteettiä, joten operaattorin on tehtävä siitä nimenomainen päätös. Loopback-solmut (localhost, 127.0.0.1, 172.16-31.x) ovat aina sallittuja, eikä asetus vaikuta niihin. Etäsolmun perus-URL-osoitteen on myös läpäistävä palveluntarjoajan lähtevien URL-osoitteiden käytäntö (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kun OIDC on käytössä, poista salasanakirjautuminen käytöstä, jotta käyttäjät voivat tunnistautua vain OIDC-kertakirjautumisella. Myös pelkkä alias `OIDC_DISABLE_PASSWORD_LOGIN` hyväksytään; saman avaimen hallintapaneelin ominaisuuslippu on etusijalla. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Asetuksen `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` pelkkä alias (#10889). | ### Suojauksen tarkistuslista diff --git a/docs/i18n/fr/CONTRIBUTING.md b/docs/i18n/fr/CONTRIBUTING.md index f17dc9aa066e..1b8c924953f5 100644 --- a/docs/i18n/fr/CONTRIBUTING.md +++ b/docs/i18n/fr/CONTRIBUTING.md @@ -387,20 +387,20 @@ Ajoutez les définitions des modèles dans `open-sse/config/providerRegistry.ts` ## Liste de contrôle de la Pull Request -- [ ] Les tests réussissent (`npm test`) -- [ ] Le linting réussit (`npm run lint`) +- [ ] Les tests passent (`npm test`) +- [ ] Le linting passe (`npm run lint`) - [ ] Le build réussit (`npm run build`) -- [ ] Les types TypeScript ont été ajoutés pour les nouvelles fonctions et interfaces publiques +- [ ] Des types TypeScript ont été ajoutés pour les nouvelles fonctions et interfaces publiques - [ ] Aucun secret ni aucune valeur de repli codés en dur -- [ ] Les identifiants publics des services en amont sont intégrés via `resolvePublicCred()` (voir [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), jamais sous forme de littéraux -- [ ] Les réponses d'erreur passent par `buildErrorBody()` / `sanitizeErrorMessage()` — aucune trace de pile brute dans le corps des réponses (voir [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Les commandes shell (`exec` / `spawn`) transmettent les valeurs d'exécution via `env`, et non par interpolation de chaînes +- [ ] Les identifiants publics en amont sont intégrés via `resolvePublicCred()` (voir [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), jamais sous forme de littéraux +- [ ] Les réponses d’erreur passent par `buildErrorBody()` / `sanitizeErrorMessage()` — aucune trace de pile brute dans le corps des réponses (voir [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Les commandes shell (`exec` / `spawn`) transmettent les valeurs d’exécution via `env`, et non par interpolation de chaînes - [ ] Toutes les entrées sont validées avec des schémas Zod -- [ ] Un **fragment** de journal des modifications a été ajouté sous `changelog.d/{features|fixes|maintenance}/-.md` pour les changements visibles par les utilisateurs (voir [`changelog.d/README.md`](./changelog.d/README.md)) — ne modifiez **pas** directement `CHANGELOG.md` ; les fragments sont agrégés au moment de la publication et ne créent jamais de conflits entre les Pull Requests +- [ ] Un **fragment** de journal des modifications a été ajouté sous `changelog.d/{features|fixes|maintenance}/-.md` pour les changements visibles par les utilisateurs (voir [`changelog.d/README.md`](./changelog.d/README.md)) — ne modifiez **pas** directement `CHANGELOG.md` ; les fragments sont regroupés au moment de la publication et ne provoquent jamais de conflits entre les PR - [ ] La documentation a été mise à jour (le cas échéant) -- [ ] Aucune nouvelle alerte CodeQL / Secret-Scanning n'a été ouverte, ou chacune a été rejetée avec une justification technique faisant référence au document `docs/security/` pertinent +- [ ] Aucune nouvelle alerte CodeQL / Secret-Scanning n’a été ouverte, ou chacune a été rejetée avec une justification technique faisant référence au document `docs/security/` pertinent - [ ] Les routes qui lancent des processus enfants (`/api/mcp/`, `/api/cli-tools/runtime/`) sont classées comme `isLocalOnlyPath()` dans `src/server/authz/routeGuard.ts` — voir la [Règle stricte nº 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Aucun pied de message `Co-Authored-By` dans les messages de commit — les commits doivent apparaître uniquement sous l'identité Git du propriétaire du dépôt (Règle stricte nº 16) +- [ ] Aucun pied de message `Co-authored-by` attribué à une IA ou à un bot dans les messages de commit (Règle stricte nº 16) — les collaborateurs humains dont le travail est réutilisé sont crédités avec des pieds de message standard `Co-authored-by: Name ` --- diff --git a/docs/i18n/fr/docs/architecture/QUALITY_GATES.md b/docs/i18n/fr/docs/architecture/QUALITY_GATES.md index bc1841ecbc90..ebd8ebf767bb 100644 --- a/docs/i18n/fr/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/fr/docs/architecture/QUALITY_GATES.md @@ -15,229 +15,246 @@ plan de réplication indépendant des outils du même système, consultez le --- -## Inventaire des contrôles et profils d’exécution +## Inventaire des gates et profils d’exécution ### Admission des candidats Les workflows CI et Quality Gates émettent chacun un verdict stable : `Gate / CI` et `Gate / Quality`. Leur politique d’admission versionnée répertorie chaque job en amont -comme obligatoire ou consultatif. Un job obligatoire applicable doit réussir : les résultats -manquants, annulés, ignorés, en attente ou inconnus ne permettent pas d’établir un PASS. Une -classification valide limitée à la documentation ou au catalogue peut rendre une voie de code -non applicable ; une PR en brouillon n’est pas un candidat accepté. Un label `hotfix` ne -dispense pas de fournir des preuves. - -Les deux workflows couvrent les PR et les poussées vers les branches main/release, le -déclenchement manuel et les événements de groupe de fusion. Les poussées, les déclenchements -et les groupes de fusion exécutent la sélection complète. Les forks et les groupes de fusion -utilisent des runners hébergés pour les jobs qui sélectionnent normalement des runners -auto-hébergés ; une capacité hébergée suffisante doit être vérifiée avant le déploiement. +comme obligatoire ou consultatif. Un job obligatoire applicable doit réussir : les +résultats manquants, annulés, ignorés, en attente ou inconnus ne peuvent pas établir +un PASS. Une classification valide limitée à la documentation ou au catalogue peut +rendre une voie de code non applicable ; une PR en brouillon n’est pas un candidat +accepté. Un label `hotfix` ne dispense pas de fournir des preuves. + +Les deux workflows couvrent les PR et les push vers les branches principales/de release, +les déclenchements manuels et les événements de groupe de fusion. Les push, les +déclenchements manuels et les groupes de fusion exécutent la sélection complète. Les +forks et les groupes de fusion utilisent des runners hébergés pour les jobs qui +sélectionneraient autrement des runners auto-hébergés ; une capacité hébergée suffisante +doit être vérifiée avant le déploiement. Chaque reçu JSON identifie le SHA extrait, l’exécution du workflow et la tentative. -La CLI rejette toute non-correspondance entre le SHA extrait et celui de l’événement. Les tests -de workflow lient l’appartenance à la politique à la liste `needs` du job de verdict, afin qu’une -voie nouvelle ou supprimée ne puisse pas disparaître silencieusement. Les reçus couvrent leur -propre workflow, et non la publication, le déploiement ou le fonctionnement interne d’un scanner -consultatif existant. L’activation des deux noms de contrôle dans les règles de branche constitue -une modification administrative distincte ; l’ajout de ces jobs ne protège pas, à lui seul, une branche. +La CLI rejette toute non-concordance entre le SHA extrait et celui de l’événement. Les +tests de workflow lient l’appartenance à la politique à la liste `needs` du job de +verdict, afin qu’une voie ajoutée ou supprimée ne puisse pas disparaître silencieusement. +Les reçus couvrent leur propre workflow, et non la publication, le déploiement ou les +mécanismes internes d’un scanner consultatif existant. L’activation des deux noms de +vérification dans les règles de branche constitue une modification administrative +distincte ; l’ajout de ces jobs ne protège pas, à lui seul, une branche. ### Inventaire de l’analyse statique -L’inventaire versionné des alias npm et l’appartenance à l’analyse statique se trouvent dans -`config/quality/gate-manifest.json`. Exécutez `npm run check:gate-manifest` pour valider -les noms des scripts et les commandes exactes par rapport à `package.json` ; les ajouts, -les suppressions et les dérives de commandes font échouer à la fois le hook local et les jobs -de classification des modifications dans CI. Un alias n’est ni un job de workflow, ni une -instance de matrice, ni un cas de test : ces nombres ne doivent pas être présentés comme -interchangeables. +L’inventaire versionné des alias npm et l’appartenance à l’analyse statique se trouvent +dans `config/quality/gate-manifest.json`. Exécutez `npm run check:gate-manifest` pour +valider les noms de scripts et les commandes exactes par rapport à `package.json` ; les +ajouts, les suppressions et les divergences de commandes font échouer à la fois le hook +local et les jobs de classification des modifications dans CI. Un alias n’est ni un job +de workflow, ni une instance de matrice, ni un cas de test : ces décomptes ne doivent +pas être présentés comme interchangeables. Utilisez `npm run quality:scan -- --list` ou `npm run quality:scan:fast -- --list` -pour inspecter les alias sélectionnés sans les exécuter. Le runner invoque le -point d’entrée npm, de sorte que son environnement d’exécution (y compris Bun lorsqu’il est -configuré) est préservé. Le manifeste enregistre comme invoqués séparément les alias situés -hors de ces profils, et les commandes de maintenance sont interdites dans les profils d’analyse +pour inspecter les alias sélectionnés sans les exécuter. Le runner appelle le point +d’entrée npm, ce qui préserve son environnement d’exécution (y compris Bun lorsqu’il est +configuré). Le manifeste consigne les alias extérieurs à ces profils comme étant invoqués +séparément, et les commandes de maintenance sont interdites dans les profils d’analyse en lecture seule. -Ces profils couvrent uniquement l’analyse statique. Ils ne certifient ni les tests du produit, -ni la couverture, ni la création de paquets, ni les contrôles externes, ni l’acceptation complète -d’un candidat à la publication. L’admission par workflow utilise le fichier lié -`config/quality/admission-policy.json` et -`scripts/quality/admission-verdict.mjs`. Les profils d’observation de publication restent distincts ; -inspectez séparément leurs contrôles applicables et leurs reçus. L’inventaire descriptif -ci-dessous est une référence, et non une preuve qu’un contrôle a réellement été exécuté. - -Les scripts se trouvent sous `scripts/check/` (contrôles de politique) et `scripts/quality/` (moteur de progression). -La source de vérité de CI est `.github/workflows/ci.yml`. - -### Chemin rapide des PR de publication (`quality.yml`) - -`.github/workflows/quality.yml` complète CI pour les PR main/release, les poussées vers les -branches protégées, les déclenchements et les groupes de fusion. Les PR utilisent des contrôles -rapides filtrés selon les chemins. La compilation en double, désactivée de manière permanente, -a été supprimée ; les véritables contrôles de compilation, de création de paquets et de démarrage -restent dans CI. - -| Job | Portée | Bloquant | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `Docs Gates (fast-path)` | PR de documentation/code ; références de documentation API et ensemble de la documentation | Oui | -| `Fast Quality Gates` | PR de code ; contrôles statiques, vérification des types, vérification des types du tableau de bord, tests unitaires affectés | Oui | -| `Forgotten sibling tests` | PR de code ; modules modifiés reliés aux consommateurs statiques et aux tests frères candidats ; les chemins de barrel et d’import dynamique sont signalés comme diagnostics consultatifs, avec les exceptions de la liste d’autorisation référencée | **Consultatif** | -| `Vitest (fast-path)` | PR de code ; suite vitest rapide | Oui | -| `Unit Tests fast-path` | PR de code ; suite unitaire répartie en 4 fragments | Oui | -| `No new ESLint warnings` | PR de code ; garde-fou de lint tenant compte des suppressions | Oui, y compris les forks | -| `Merge integrity (changelog + generated skills)` | PR hors brouillon ; synchronisation du journal des modifications et des compétences générées | Oui, y compris les forks | +Ces profils couvrent uniquement l’analyse statique. Ils ne certifient ni les tests du +produit, ni la couverture, ni la création de packages, ni les vérifications externes, ni +l’acceptation complète d’un candidat à la release. L’admission du workflow utilise +`config/quality/admission-policy.json`, auquel elle est liée, ainsi que +`scripts/quality/admission-verdict.mjs`. Les profils d’observation de release restent +distincts ; inspectez séparément leurs vérifications applicables et leurs reçus. +L’inventaire textuel ci-dessous est une référence, et non la preuve qu’un gate a +réellement été exécuté. + +Les scripts se trouvent sous `scripts/check/` (gates de politique) et +`scripts/quality/` (moteur de progression). La source de vérité de CI est +`.github/workflows/ci.yml`. + +### Voie rapide des PR de release (`quality.yml`) + +`.github/workflows/quality.yml` complète CI pour les PR vers les branches principales/de +release, les push vers les branches protégées, les déclenchements manuels et les groupes +de fusion. Les PR utilisent des vérifications rapides filtrées selon les chemins. Le +build dupliqué désactivé de manière permanente a été supprimé ; les véritables +vérifications de build, de création de package et de démarrage restent dans CI. + +| Job | Portée | Bloquant | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `Docs Gates (fast-path)` | PR de documentation/code ; références de documentation d’API et ensemble de la documentation | Oui | +| `Fast Quality Gates` | PR de code ; vérifications statiques, vérification des types, vérification des types du tableau de bord, tests unitaires affectés | Oui | +| `Forgotten sibling tests` | PR de code ; modules modifiés reliés aux consommateurs statiques et aux tests frères candidats ; les chemins de barrel et d’import dynamique sont signalés comme diagnostics consultatifs, avec les exceptions référencées de la liste d’autorisation | **Consultatif** | +| `Vitest (fast-path)` | PR de code ; suite Vitest rapide | Oui | +| `Unit Tests fast-path` | PR de code ; suite de tests unitaires en 4 shards | Oui | +| `No new ESLint warnings` | PR de code ; garde de lint tenant compte des suppressions | Oui, y compris les forks | +| `Merge integrity (changelog + generated skills)` | PR hors brouillon ; synchronisation du changelog et des compétences générées | Oui, y compris les forks | #### Rapport sur les tests frères oubliés -`npm run check:forgotten-sibling-tests` réutilise le résolveur d’imports à l’origine de la carte d’impact des tests. -Pour chaque module de production modifié, il signale des chaînes déterministes -`module/symbole modifié -> consommateur statique -> test frère candidat` lorsque le test candidat -est absent du diff de la pull request. Le résumé Markdown et le résultat JSON sont conservés comme -artefact de workflow `forgotten-sibling-tests` à des fins d’étalonnage avant tout déploiement bloquant. +`npm run check:forgotten-sibling-tests` réutilise le résolveur d’imports sur lequel repose +la carte d’impact des tests. Pour chaque module de production modifié, il signale des chaînes +déterministes `module/symbole modifié -> consommateur statique -> test frère candidat` lorsque +le test candidat est absent du diff de la pull request. Le résumé Markdown et le résultat JSON +sont conservés en tant qu’artefact de workflow `forgotten-sibling-tests` à des fins de +calibrage avant tout déploiement bloquant. -Les réexportations via des fichiers barrel et les importations dynamiques servent uniquement au diagnostic de résolution ; elles ne créent jamais de +Les réexportations via des barrels et les imports dynamiques servent uniquement au diagnostic de résolution ; ils ne créent jamais de constat bloquant. Les exceptions examinées se trouvent dans `config/quality/forgotten-sibling-allowlist.json`. Chaque entrée doit nommer le test consommateur et le test candidat, fournir une justification précise et contenir un lien vers une issue ou une pull request GitHub. Les entrées mal formées entraînent -un refus par défaut. Les exceptions ne peuvent pas masquer la suppression d’un test candidat ni un diff qui ajoute `.skip`/`.todo` ; -l’affaiblissement des assertions et les autres formes de masquage restent sous la responsabilité du contrôle bloquant indépendant +un refus par défaut. Les exceptions ne peuvent pas masquer la suppression d’un test candidat ni un diff ajoutant `.skip`/`.todo` ; +l’affaiblissement des assertions et les autres formes de masquage restent sous la responsabilité de la barrière bloquante indépendante `check:test-masking`. -### Job : `lint` +### Tâche : `lint` S’exécute sur chaque PR vers `main`. Bloque la fusion en cas d’échec. -| Script (`npm run ...`) | Vérifie | Bloquant | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | La version de Node.js se trouve dans la plage prise en charge | Oui | -| `check:cycles` | Les importations circulaires — tous les modules de `src/` + `open-sse/` | Oui | -| `check:route-validation:t06` | La présence de schémas Zod sur toutes les routes (politique de niveau 6) | Oui | -| `check:any-budget:t11` | Le nombre de `@ts-expect-error // any` ne dépasse pas le budget (catraca de niveau 11) | Oui | -| `check:provider-consistency` | Chaque fournisseur dans `providers.ts` possède une entrée correspondante dans `providerRegistry.ts` (et réciproquement, dans les limites de la liste d’autorisation) | Oui | -| `check:model-lifecycle` | Les trois tables de routage maintenues manuellement restent cohérentes avec l’instantané du cycle de vie archivé (#11503) : `FITNESS_TABLE` (`taskFitness.ts`) n’attribue de score à aucun identifiant retiré que `REGISTRY` peut acheminer ; chaque cible de `BUILT_IN_ALIASES` est présente dans `REGISTRY` et absente de l’instantané des identifiants retirés ; chaque identifiant retiré encore présent dans `REGISTRY` est redirigé ou répertorié dans `allowedRetiredInCatalog` ; et aucune source ni cible de `DEFAULT_DEGRADATION_MAP` n’apparaît comme retirée dans cet instantané. Cela ne prouve pas qu’un modèle est actuellement fourni par un service amont actif. Hors ligne — compare avec `config/quality/model-lifecycle.json`, actualisé manuellement avec `npm run quality:refresh-model-lifecycle` (réseau ; non intégré à la CI). `allowedRetiredInCatalog` est un mécanisme de réduction progressive : n’ajoutez une entrée qu’avec un ticket de suivi. | Oui | -| `check:fetch-targets` | Chaque `fetch("/api/...")` dans le répertoire client `src/` correspond à un véritable fichier `route.ts` | Oui | -| `check:deps` | Toutes les dépendances installables avec `npm install` dans chaque `package.json` du dépôt figurent dans `dependency-allowlist.json` ; les nouveaux paquets dont la version n’est pas épinglée ou qui présentent un risque de slopsquatting sont signalés | Oui | -| `audit:deps` | `npm audit` (racine + Electron) — aucun avis de sécurité de gravité élevée/critique (chevauche le contrôle OSV `check:vuln-ratchet` ; voir le backlog de rationalisation) | Oui | -| `check:lockfile` | Intégrité de `package-lock.json` — registre HTTPS, empreintes d’intégrité, aucune substitution d’hôte | Oui | -| `check:licenses` | Liste blanche de licences SPDX pour les dépendances de production | Oui | -| `check:tracked-artifacts` | Aucun artefact de build ni lien symbolique `node_modules` suivi dans le dépôt (également exécuté par le hook husky de pré-commit ; le pré-push est intentionnellement léger — #6716) | Oui | -| `check:ai-attribution` | Aucun pied de message `Co-Authored-By` d’IA/bot ni mention de génération par IA dans les commits, le titre ou le corps de la PR — Règle stricte n° 16 (dans la boucle de contrôles rapides de `quality.yml` pour les PR vers `release/**` — lit la charge utile de l’événement, sans effet hors PR — et dans une étape réservée aux PR du lint de `ci.yml` pour les PR vers `main` ; également dans le hook husky `commit-msg` ; les co-auteurs humains sont autorisés ; #14436) | -| `check:vitest-exclusions` | Chaque exclusion Vitest mentionne une issue de suivi et figure dans `config/quality/vitest-exclusions.json` (#13204) | Oui | -| `check:file-size` | Aucun fichier source ne dépasse la limite propre à son extension (limitation progressive : grands fichiers figés dans la liste `frozen`) | Oui | -| `check:error-helper` | Les réponses d’erreur dans les exécuteurs/gestionnaires utilisent `buildErrorBody()` / `sanitizeErrorMessage()` (Règle stricte n° 12) | Oui | -| `check:migration-numbering` | Les fichiers SQL de migration sont numérotés séquentiellement, sans numéro manquant ni doublon | Oui | -| `check:public-creds` | Aucun `client_id`/`client_secret` OAuth littéral ni aucune clé Web Firebase en dehors de `publicCreds.ts` (règle stricte nº 11) | Oui | -| `check:db-rules` | Aucun SQL brut en dehors des modules `src/lib/db/` ; aucun import depuis le fichier d'agrégation `localDb.ts` (règles strictes nº 2/5) | Oui | -| `check:known-symbols` | Les exécuteurs de fournisseurs, les stratégies de routage et les traducteurs enregistrés dans leurs tables de répartition correspondent aux fichiers présents sur le disque — aucun symbole orphelin ou non déclaré | Oui | -| `check:route-guard-membership` | Chaque route qui lance un processus enfant est classée par `isLocalOnlyPath()` (règles strictes nº 15/17) | Oui | -| `check:test-discovery` | Chaque fichier `*.test.ts` / `*.spec.ts` du dépôt est collecté par au moins un exécuteur de tests (mécanisme à cliquet : la liste des fichiers orphelins dans `test-discovery-baseline.json` ne peut que diminuer) | Oui | -| `check:agent-skills-sync` | Les artefacts agent-skills générés correspondent à leur catalogue source (aucune dérive) | -| `check:provider-asset-provenance` | Les logos/ressources des fournisseurs disposent d’une entrée de provenance enregistrée | -| `lint:json` | Les fichiers de configuration JSON sont analysés correctement et respectent les règles de lint du dépôt | -| `typecheck:core` | Compilation TypeScript sans erreur (avertissements consultatifs uniquement) | Oui | -| `typecheck:noimplicit:core` | Mode strict `noImplicitAny` — mesure prospective ; de nombreux sites d’appel préexistants nécessitent encore des annotations | **Consultatif** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` limité à `src/app/(dashboard)/**` (#7033) — la liste d’autorisation organisée de 27 fichiers de `typecheck:core` n’inclut aucun TSX du tableau de bord, et `next build` n’effectue jamais non plus de vérification de types sur celui-ci (`next.config.mjs` définit `ignoreBuildErrors: true`) ; les régressions d’identifiants orphelins à cet endroit (#6625/#6909) étaient donc invisibles pour la CI. Comparaison avec une référence figée du nombre d’erreurs par fichier et par code TS (`config/quality/dashboard-typecheck-baseline.json`, selon le même modèle de contrôle de l’obsolescence que `check:known-symbols`) — seules les NOUVELLES erreurs dépassant le nombre de référence font échouer le contrôle ; réduisez progressivement la référence avec `--update` lorsqu’une erreur préexistante est corrigée. | Oui | +| Script (`npm run ...`) | Valide | Bloquant | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | La version de Node.js se situe dans la plage prise en charge | Oui | +| `check:cycles` | Les imports circulaires dans l’ensemble de `src/` + `open-sse/` (fondé sur l’AST, avec résolution des `paths` de tsconfig). Sans argument = informatif, répertorie les cycles. `check:cycles:ratchet` (ce que la CI exécute) bloque lorsque le nombre dépasse le plafond `metrics.cycles` défini dans `quality-baseline.json` — actuellement 14, `direction: down`, il ne peut donc que diminuer (#15159 G-01/G-02) | Oui (cliquet) | +| `check:route-validation:t06` | Présence de schémas Zod sur toutes les routes (politique de niveau 6) | Oui | +| `check:any-budget:t11` | Le nombre de `@ts-expect-error // any` ne dépasse pas le budget (cliquet de niveau 11) | Oui | +| `check:provider-consistency` | Chaque fournisseur dans `providers.ts` possède une entrée correspondante dans `providerRegistry.ts` (et inversement, dans les limites de la liste d’autorisation) | Oui | +| `check:model-lifecycle` | Les trois tables de routage maintenues manuellement restent cohérentes avec l’instantané du cycle de vie archivé dans le dépôt (#11503) : `FITNESS_TABLE` (`taskFitness.ts`) n’attribue aucun score à un identifiant retiré que `REGISTRY` peut acheminer ; chaque cible de `BUILT_IN_ALIASES` est présente dans `REGISTRY` et absente de l’instantané des identifiants retirés ; chaque identifiant retiré encore présent dans `REGISTRY` est redirigé ou répertorié dans `allowedRetiredInCatalog` ; et aucune source ni cible de `DEFAULT_DEGRADATION_MAP` n’apparaît comme retirée dans cet instantané. Cela ne prouve pas qu’un modèle est actuellement servi par un service amont actif. Hors ligne — effectue une comparaison avec `config/quality/model-lifecycle.json`, actualisé manuellement avec `npm run quality:refresh-model-lifecycle` (réseau ; non intégré à la CI). `allowedRetiredInCatalog` est un mécanisme de réduction progressive : n’ajoutez une entrée qu’avec un ticket de suivi. | Oui | +| `check:fetch-targets` | Chaque `fetch("/api/...")` dans le répertoire client `src/` correspond à un véritable `route.ts` | Oui | +| `check:deps` | Toutes les dépendances installables avec `npm install` de chaque fichier `package.json` du dépôt figurent dans `dependency-allowlist.json` ; les nouveaux paquets non épinglés ou issus de typosquattage sont signalés | Oui | +| `audit:deps` | `npm audit` (racine + Electron) — aucun avis de sécurité de gravité élevée/critique (chevauche le contrôle OSV `check:vuln-ratchet` ; voir le backlog de rationalisation) | Oui | +| `check:lockfile` | Intégrité de `package-lock.json` — registre HTTPS, empreintes d’intégrité, aucune substitution d’hôte | Oui | +| `check:licenses` | Liste d’autorisation des licences SPDX pour les dépendances de production | Oui | +| `check:tracked-artifacts` | Aucun artefact de build ni lien symbolique `node_modules` versionné (également exécuté dans le hook de pré-commit husky ; le pré-push est intentionnellement léger — #6716) | Oui | +| `check:ai-attribution` | Aucun trailer `Co-Authored-By` d’IA/de bot ni pied de page indiquant une génération par IA dans les commits, le titre ou le corps de la PR — Règle stricte no 16 (dans la boucle de contrôles rapides de `quality.yml` pour les PR→`release/**` — lit la charge utile de l’événement, sans effet hors des PR — et dans une étape réservée aux PR de l’analyse lint de `ci.yml` pour les PR→`main` ; également dans le hook `commit-msg` de husky ; coauteurs humains autorisés ; #14436) | +| `check:vitest-exclusions` | Chaque exclusion Vitest mentionne une issue de suivi et figure dans `config/quality/vitest-exclusions.json` (#13204) | Oui | +| `check:file-size` | Aucun fichier source ne dépasse la limite propre à son extension (mécanisme à cliquet : fichiers volumineux figés dans la liste `frozen`) | Oui | +| `check:error-helper` | Les réponses d’erreur dans les exécuteurs/gestionnaires utilisent `buildErrorBody()` / `sanitizeErrorMessage()` (Règle stricte no 12) | Oui | +| `check:migration-numbering` | Les fichiers SQL de migration sont numérotés séquentiellement, sans interruption ni doublon | Oui | +| `check:public-creds` | Aucun `client_id`/`client_secret` OAuth littéral ni aucune clé Web Firebase en dehors de `publicCreds.ts` (Règle stricte nº 11) | Oui | +| `check:db-rules` | Aucun SQL brut en dehors des modules de `src/lib/db/` ; aucun import depuis le fichier d’agrégation `localDb.ts` (Règles strictes nº 2/5) | Oui | +| `check:known-symbols` | Les exécuteurs de fournisseurs, les stratégies de routage et les traducteurs enregistrés dans leurs tables de répartition correspondent aux fichiers présents sur le disque — aucun symbole orphelin ou non déclaré | Oui | +| `check:route-guard-membership` | Chaque route qui lance un processus enfant est classée par `isLocalOnlyPath()` (Règles strictes nº 15/17) | Oui | +| `check:test-discovery` | Chaque fichier `*.test.ts` / `*.spec.ts` du dépôt est collecté par au moins un exécuteur de tests (mécanisme de verrouillage : la liste des fichiers orphelins dans `test-discovery-baseline.json` ne peut que diminuer) | Oui | +| `check:agent-skills-sync` | Les artefacts agent-skills générés correspondent à leur catalogue source (aucune dérive) | +| `check:provider-asset-provenance` | Les logos/ressources des fournisseurs disposent d’une entrée de provenance enregistrée | +| `lint:json` | Les fichiers de configuration JSON sont analysés correctement et respectent les règles de lint du dépôt | +| `typecheck:core` | Compilation TypeScript sans erreur (avertissements consultatifs uniquement) | Oui | +| `typecheck:noimplicit:core` | Mode strict `noImplicitAny` — objectif à terme ; de nombreux sites d’appel préexistants nécessitent encore des annotations | **Consultatif** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` limité à `src/app/(dashboard)/**` (#7033) — la liste d’autorisation organisée de 27 fichiers de `typecheck:core` n’inclut aucun fichier TSX du tableau de bord, et `next build` n’effectue pas non plus de vérification des types sur celui-ci (`next.config.mjs` définit `ignoreBuildErrors: true`) ; les régressions d’identifiants orphelins qui s’y trouvent (#6625/#6909) étaient donc invisibles pour la CI. Comparaison avec une référence figée du nombre d’erreurs par fichier et par code TS (`config/quality/dashboard-typecheck-baseline.json`, selon le même modèle de contrôle de l’obsolescence que `check:known-symbols`) — seules les NOUVELLES erreurs dépassant le nombre de référence font échouer le contrôle ; réduisez progressivement la référence avec `--update` lorsqu’une erreur préexistante est corrigée. | Oui | ### Tâche : `quality-gate` S’exécute après `test-coverage`. Bloque la fusion en cas d’échec. -| Script | Valide | Bloquant | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `quality:collect` | Génère `quality-metrics.json` (nombre d’avertissements ESLint, couverture issue du rapport fusionné des shards) | Oui (en amont du cliquet) | -| `quality:ratchet` | Aucune métrique de `quality-baseline.json` n’a régressé (avertissements ESLint ≤ référence ; couverture ≥ référence) | Oui | -| `check:duplication` | La duplication de code (jscpd@4) ne dépasse pas la référence définie dans `quality-baseline.json` | Oui | -| `check:complexity` | La complexité cyclomatique au niveau des fichiers ne dépasse pas le plafond (`complexity` d’ESLint core + `max-lines-per-function`) | Oui | -| `check:cognitive-complexity` | Cliquet de complexité cognitive (`eslint-plugin-sonarjs`) — passe ESLint distincte ; la CI exécute les deux ensemble dans l’unique étape `check:complexity-ratchets` | Oui | -| `check:dead-code` | Le cliquet des exports/fichiers inutilisés (knip) ne régresse pas par rapport à la référence | Oui | -| `check:compression-budget` | Budget du benchmark de compression — les seuils minimaux d’économie de jetons par moteur ne doivent pas régresser | Oui | -| `check:type-coverage` | Le cliquet du pourcentage typé (`type-coverage`) ne régresse pas ; remplace en grande partie `typecheck:noimplicit:core` | Oui | -| `check:codeql-ratchet` | Le nombre d’alertes CodeQL ouvertes ne régresse pas (lecture via `gh api` ; omission sans échec en l’absence de jeton) — cadence d’actualisation et déclenchement manuel : voir « Cliquet CodeQL » ci-dessous | Oui | +| Script | Valide | Bloquant | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | +| `quality:collect` | Génère `quality-metrics.json` (nombre d’avertissements ESLint, couverture issue du rapport fusionné des fragments) | Oui (en amont du mécanisme de cliquet) | +| `quality:ratchet` | Chaque métrique dans `quality-baseline.json` n’a pas régressé (avertissements ESLint ≤ référence ; couverture ≥ référence) | Oui | +| `check:duplication` | La duplication de code (jscpd@4) ne dépasse pas la référence définie dans `quality-baseline.json` | Oui | +| `check:complexity` | La complexité cyclomatique au niveau des fichiers ne dépasse pas la limite (`complexity` d’ESLint natif + `max-lines-per-function`) | Oui | +| `check:cognitive-complexity` | Cliquet de complexité cognitive (`eslint-plugin-sonarjs`) — passe ESLint distincte ; la CI exécute les deux conjointement dans l’unique étape `check:complexity-ratchets` | Oui | +| `check:dead-code` | Le cliquet des exports/fichiers inutilisés (knip) ne régresse pas par rapport à la référence | Oui | +| `check:compression-budget` | Budget du benchmark de compression — les seuils minimaux d’économie de jetons par moteur ne doivent pas régresser | Oui | +| `check:type-coverage` | Le cliquet du pourcentage de code typé (`type-coverage`) ne régresse pas ; remplace en grande partie `typecheck:noimplicit:core` | Oui | +| `check:codeql-ratchet` | Le nombre d’alertes CodeQL ouvertes ne régresse pas (lecture via `gh api` ; omission propre sans jeton) — fréquence d’actualisation et déclenchement manuel : voir « Cliquet CodeQL » ci-dessous | Oui | ### Tâche : `quality-extended` -L’ensemble de la tâche est indicatif (`continue-on-error: true`). Les cliquets basés sur npm -s’exécutent réellement ; les analyseurs externes s’installent via `gh release download` et +La tâche entière est consultative (`continue-on-error: true`). Les cliquets basés sur npm +s’exécutent réellement ; les analyseurs externes sont installés via `gh release download` et s’ignorent eux-mêmes (code de sortie 0) lorsqu’un binaire est toujours absent. -| Script | Valide | Bloquant | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| `check:circular-deps` | Aucune dépendance circulaire (dpdm) | **Indicatif** | -| `check:bundle-size` | La taille du bundle ne dépasse pas le plafond | **Indicatif** | -| `check:secrets` | Analyse des secrets (gitleaks) — ignorée si le binaire est absent | **Indicatif** | -| `check:vuln-ratchet` | Les vulnérabilités des dépendances (osv-scanner) ne régressent pas — ignoré si le binaire est absent | **Indicatif** | -| `check:workflows` | Validation des workflows (actionlint + zizmor) ; les analyseurs absents/défectueux, les rapports invalides ou l’absence de référence du cliquet entraînent un échec avec le statut INCOMPLETE. Les résultats valides suivent la politique stricte/indicative/cliquet sélectionnée | Exécution requise ; cliquet zizmor bloquant dans la CI | -| `check:openapi-breaking` | Modifications incompatibles du contrat d’API publique (`openapi.yaml`) par rapport à la branche de base (oasdiff) — génère `openapiBreaking=N` ; ignoré si oasdiff est absent ou si la spécification de base ne peut pas être résolue | **Indicatif** | +| Script | Valide | Bloquant | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| `check:circular-deps` | Aucune dépendance circulaire (dpdm) | **Consultatif** | +| `check:bundle-size` | La taille du bundle ne dépasse pas la limite | **Consultatif** | +| `check:secrets` | Analyse des secrets (gitleaks) — ignorée si le binaire est absent | **Consultatif** | +| `check:vuln-ratchet` | Les vulnérabilités des dépendances (osv-scanner) ne régressent pas — ignoré si le binaire est absent | **Consultatif** | +| `check:workflows` | Analyse des workflows (actionlint + zizmor) ; des analyseurs manquants/défaillants, des rapports invalides ou une référence de cliquet manquante entraînent un échec INCOMPLETE. Les constats valides suivent la politique stricte/consultative/de cliquet sélectionnée | Exécution requise ; cliquet zizmor bloquant dans la CI | +| `check:openapi-breaking` | Modifications incompatibles du contrat d’API public (`openapi.yaml`) par rapport à la branche de base (oasdiff) — génère `openapiBreaking=N` ; ignoré si oasdiff est absent ou si la spécification de base ne peut pas être résolue | **Consultatif** | ### Tâche : `docs-sync-strict` S’exécute sur chaque PR vers `main`. Bloque la fusion en cas d’échec. -| Script | Valide | Bloquant | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `check:docs-all` | Méta-barrière qui exécute séquentiellement les 6 sous-barrières ci-dessous | Oui | -| ↳ `check:docs-sync` | Cohérence des versions entre CHANGELOG / OpenAPI / llm.txt | Oui | -| ↳ `check:docs-counts` | Les décomptes dans le texte (nombre de fournisseurs, nombre de migrations, etc.) restent dans la fenêtre de progression des décomptes réels | Oui | -| ↳ `check:env-doc-sync` | Chaque variable d’environnement de `.env.example` est documentée dans un tableau de la documentation, et inversement | Oui | -| ↳ `check:deprecated-versions` | Aucune chaîne de version obsolète dans la documentation | Oui | -| ↳ `check:doc-links` | Les liens Markdown internes de la documentation renvoient vers des fichiers réels (forme `[texte]`/`(chemin)`) | Oui | -| ↳ `check:fabricated-docs` | Les routes, variables d’environnement, commandes CLI, noms de hooks et chemins de fichiers cités dans la documentation existent dans la base de code. Barrière stricte via `--strict` ; échec non bloquant sans cette option. | Oui (via `--strict` dans la CI) | -| `check:cli-i18n` | Les chaînes des commandes CLI sont présentes dans tous les fichiers de paramètres régionaux i18n | Oui | -| `check:openapi-coverage` | La spécification OpenAPI couvre au moins un seuil progressif de routes réelles | Oui | -| `check:openapi-security-tiers` | Les annotations des niveaux de sécurité dans `openapi.yaml` sont cohérentes avec les classifications de `routeGuard.ts` | **Consultatif** | -| `check:openapi-routes` | Chaque chemin de `openapi.yaml` correspond à un véritable fichier `route.ts` (anti-hallucination) | Oui | -| `check:docs-symbols` | Chaque référence `/api/...` dans `docs/**/*.md` correspond à un véritable fichier `route.ts` (anti-hallucination) | Oui | -| `i18n translation drift` | Clés non traduites dans les fichiers de paramètres régionaux i18n — avertissement uniquement | **Consultatif** | +| Script | Valide | Bloquant | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `check:docs-all` | Méta-contrôle qui exécute séquentiellement les 6 sous-contrôles ci-dessous | Oui | +| ↳ `check:docs-sync` | Cohérence des versions entre CHANGELOG / OpenAPI / llm.txt | Oui | +| ↳ `check:docs-counts` | Les nombres mentionnés dans le texte (nombre de fournisseurs, nombre de migrations, etc.) se situent dans la fenêtre de progression des nombres réels | Oui | +| ↳ `check:env-doc-sync` | Chaque variable d'environnement de `.env.example` est documentée dans un tableau de la documentation, et inversement | Oui | +| ↳ `check:deprecated-versions` | Aucune chaîne de version obsolète dans la documentation | Oui | +| ↳ `check:doc-links` | Les liens Markdown internes de la documentation pointent vers des fichiers réels (forme `[text]`/`(path)`) | Oui | +| ↳ `check:fabricated-docs` | Les routes, variables d'environnement, commandes CLI, noms de hooks et chemins de fichiers cités dans la documentation existent dans la base de code. Contrôle strict via `--strict` ; échec non bloquant sans cette option. | Oui (via `--strict` dans la CI) | +| `check:cli-i18n` | Les chaînes des commandes CLI sont présentes dans tous les fichiers de paramètres régionaux i18n | Oui | +| `check:openapi-coverage` | La spécification OpenAPI couvre au moins un seuil progressif de routes réelles | Oui | +| `check:openapi-security-tiers` | Les annotations de niveau de sécurité dans `openapi.yaml` sont cohérentes avec les classifications de `routeGuard.ts` | **Consultatif** | +| `check:openapi-routes` | Chaque chemin dans `openapi.yaml` correspond à un véritable fichier `route.ts` (anti-hallucination) | Oui | +| `check:docs-symbols` | Chaque référence `/api/...` dans `docs/**/*.md` correspond à un véritable fichier `route.ts` (anti-hallucination) | Oui | +| `i18n translation drift` | Clés non traduites dans les fichiers de paramètres régionaux i18n — avertissement uniquement | **Consultatif** | ### Tâche : `i18n-ui-coverage` -| Script | Valide | Bloquant | -| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (en ligne) | La couverture des clés i18n de l’interface utilisateur est ≥ 65 % | Oui | -| `check-ui-value-drift` (en ligne) | Une **valeur** anglaise réécrite ne laisse aucune traduction obsolète | Oui | -| `check-new-key-coverage` (en ligne) | Une **nouvelle** clé anglaise est traduite dans chaque langue — un marqueur `__MISSING__:` est rejeté | Oui | -| `check-translation-ratio` | Le taux de traductions réelles par langue (valeurs identiques à l’anglais / espaces réservés / éléments manquants hors de la liste d’autorisation) ne doit pas dépasser `config/quality/i18n-translation-baseline.json` + la marge | **Consultatif** | +| Script | Valide | Bloquant | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (inline) | La couverture des clés i18n de l'interface utilisateur est ≥ 65 % | Oui | +| `check-ui-value-drift` (inline) | La réécriture d'une **valeur** anglaise ne laisse aucune traduction obsolète | Oui | +| `check-new-key-coverage` (inline) | Une **nouvelle** clé anglaise est traduite dans chaque paramètre régional — un marqueur `__MISSING__:` est rejeté | Oui | +| `check-translation-ratio` | Le taux de traductions réelles par paramètre régional (valeurs identiques à l'anglais / espaces réservés / éléments manquants hors de la liste d'autorisation) ne doit pas dépasser `config/quality/i18n-translation-baseline.json` + la marge | **Consultatif** | -Nécessite `fetch-depth: 0` — la barrière de dérive des valeurs compare `en.json` à la base de fusion. +Nécessite `fetch-depth: 0` — le contrôle de dérive des valeurs compare `en.json` à la base de fusion. -#### `check-ui-value-drift` — barrière contre les traductions obsolètes +#### `check-ui-value-drift` — contrôle des traductions obsolètes -Détecte la seule régression i18n que les autres barrières ne peuvent structurellement pas voir : une valeur anglaise +Détecte la seule régression i18n que les autres contrôles ne peuvent structurellement pas voir : une valeur anglaise est réécrite tandis que les traductions dérivées de la valeur anglaise _précédente_ restent en place, si bien que les utilisateurs non anglophones continuent de lire un texte formulé avec assurance, mais désormais erroné. -Cela s’est réellement produit en production. `oauthModal.googleOAuthWarning` a été réécrit lors de l’arrivée de -l’assistant de connexion Antigravity (#5203) ; **39 langues sur 43** ont conservé un texte demandant aux opérateurs de « copier -l’URL complète et la coller ci-dessous » — un parcours qui ne peut pas aboutir pour ce fournisseur. Le problème est -passé inaperçu jusqu’à #8463, car : +Cela s'est réellement produit en production. `oauthModal.googleOAuthWarning` a été réécrit lors de l'arrivée de +l'assistant de connexion Antigravity (#5203) ; **39 paramètres régionaux sur 43** ont conservé un texte indiquant aux opérateurs de « copier +l'URL complète et la coller ci-dessous » — un flux qui ne peut pas aboutir pour ce fournisseur. Le problème est +passé inaperçu jusqu'au #8463 parce que : - `sync-ui-keys` ne complète que les clés **absentes**, jamais celles qui sont **obsolètes** ; -- `check-ui-keys-coverage` comptabilise la _présence_ des clés, si bien qu’une traduction obsolète est considérée comme couverte ; -- `check-translation-drift` suit les miroirs de documentation `docs/i18n//**.md` — +- `check-ui-keys-coverage` compte la _présence_ des clés, de sorte qu'une traduction obsolète est considérée comme couverte ; +- `check-translation-drift` suit les copies de la documentation dans `docs/i18n//**.md` — il ne lit jamais `src/i18n/messages/*.json`. Bloquant dans la tâche `docs-sync-strict` depuis la - resynchronisation 2026-09 : modifier un document principal → `npm run i18n:run -- --files=` (au niveau de la section, peu coûteux). + resynchronisation de 2026-09 : modifiez un document principal → `npm run i18n:run -- --files=` (au niveau de la section, peu coûteux). -**Prend en compte les différences, sans s'appuyer sur une référence.** Ce contrôle compare `en.json` à la base de fusion avec l'arborescence de travail ; pour chaque clé dont la valeur anglaise a changé, toute locale contenant encore une traduction inchangée est obsolète. Cela **fige délibérément la dette préexistante** — une différence ne permet pas de déterminer de quelle ancienne version anglaise provient une traduction de longue date, donc le contrôle porte uniquement sur ce que la modification actuelle affecte. L'autre solution (une référence de hachage par clé) nécessiterait un fichier généré d'environ 600 Ko, soit 3× la plus grande référence existante, et celui-ci serait modifié à chaque PR d'i18n. +**Sensible aux diffs, sans baseline.** Il compare `en.json` au point de fusion avec +l’arborescence de travail ; pour chaque clé dont la valeur anglaise a changé, toute locale +contenant encore une traduction non modifiée est obsolète. Cela **fige délibérément la dette +préexistante** — un diff ne permet pas de déterminer de quelle ancienne version anglaise +provient une traduction existante depuis longtemps ; le contrôle évalue donc uniquement ce +que la modification actuelle affecte. L’autre solution (une baseline de hachage par clé) +nécessiterait un fichier généré d’environ 600 Ko, soit 3 fois la plus grande baseline +existante, et entraînerait des modifications parasites à chaque PR d’i18n. Deux façons de satisfaire ce contrôle : 1. mettre à jour les traductions concernées, ou -2. les définir sur `__MISSING__:` — à l'exécution, le texte anglais corrigé est alors utilisé - (`src/i18n/request.ts::deepMergeFallback`, #7258) et la clé est ajoutée à la file d'attente de traduction. +2. les définir sur `__MISSING__:` — l’exécution fournit alors la version + anglaise corrigée (`src/i18n/request.ts::deepMergeFallback`, #7258) et place la clé dans + la file d’attente de traduction. -Si le **sens** de la chaîne a changé, privilégiez le **renommage de la clé** : une nouvelle clé ne peut pas hériter d'une traduction obsolète. C'est le modèle utilisé par #8463. +Si le **sens** de la chaîne a changé, privilégiez le **renommage de la clé** : une nouvelle +clé ne peut pas hériter d’une traduction obsolète. C’est le modèle utilisé par #8463. ```bash -npm run i18n:check-value-drift # strict (ce qu'exécute la CI) +npm run i18n:check-value-drift # strict (ce que la CI exécute) npm run i18n:check-value-drift:warn # rapport uniquement BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Se termine avec le code 0 et `SKIP reason=base-unresolved` lorsque le catalogue de base ne peut pas être lu (clone superficiel sans la référence de base), comme `check-openapi-breaking`. +Se termine avec le code 0 et `SKIP reason=base-unresolved` lorsque le catalogue de base ne +peut pas être lu (clone superficiel sans la référence de base), comme +`check-openapi-breaking`. ### Tâche : `i18n` -Matrice complète de validation i18n (une tâche par locale). La tâche entière est consultative. +Matrice complète de validation i18n (une tâche par locale). La tâche entière est +consultative. | Script | Valide | Bloquant | | ------------------------------- | --------------------------------------- | -------------------------------------------------------------- | @@ -245,35 +262,36 @@ Matrice complète de validation i18n (une tâche par locale). La tâche entière ### Tâche : `pr-test-policy` -S'exécute uniquement sur les pull requests. +S’exécute uniquement sur les pull requests. -| Script | Valide | Bloquant | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | Les PR qui modifient le code de production dans `src/`, `open-sse/`, `electron/` ou `bin/` doivent inclure ou mettre à jour des tests (Règle stricte nº 8) | Oui | -| `check:test-masking` | Les fichiers de test modifiés ne réduisent pas le nombre net d'assertions et n'ajoutent pas de tautologies `assert.ok(true)` | Oui | -| `check:pr-evidence` | Le corps de la PR mentionne des preuves de test/VPS pour la modification (automatise la Règle stricte nº 18 en recherchant des motifs dans le texte de la PR — fragile, voir la liste des tâches) | Oui | +| Script | Valide | Bloquant | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | Les PR qui modifient le code de production dans `src/`, `open-sse/`, `electron/` ou `bin/` doivent inclure ou mettre à jour des tests (Règle stricte nº 8) | Oui | +| `check:test-masking` | Les fichiers de test modifiés ne réduisent pas le nombre net d’assertions et n’ajoutent pas de tautologies `assert.ok(true)` | Oui | +| `check:pr-evidence` | Le corps de la PR cite des preuves de test/VPS pour la modification (automatise la Règle stricte nº 18 en analysant le texte de la PR — fragile, voir Backlog) | Oui | ### Tâche : `test-vitest` -S'exécute après `build`. Bloque la fusion en cas d'échec. +S’exécute après `build`. Bloque la fusion en cas d’échec. | Suite | Valide | Bloquant | | ---------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `test:vitest` | Serveur MCP (110 outils), autoCombo, cache — exécuteur vitest | Oui | -| `test:vitest:ui` | Tests des composants de l'interface — exécuteur vitest | **Bloquant** — les échecs préexistants sont explicitement exclus dans `vitest.config.ts` ; les nouveaux échecs font échouer la tâche | +| `test:vitest:ui` | Tests des composants d’interface — exécuteur vitest | **Bloquant** — les échecs préexistants sont explicitement exclus dans `vitest.config.ts` ; les nouveaux échecs font échouer la tâche | ### Workflows nocturnes (planifiés, consultatifs) -Ils s'exécutent selon une planification cron (ainsi que via `workflow_dispatch`), jamais sur les PR. Ils sont tous consultatifs. +Ils s’exécutent selon une planification cron (et avec `workflow_dispatch`), jamais sur les +PR. Ils sont tous consultatifs. -| Workflow | Valide | Bloquant | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | Tests fondés sur les propriétés avec fast-check, utilisant une graine aléatoire et un nombre élevé d'exécutions | **Consultatif** | -| `nightly-resilience` | Contrôle de la croissance du tas, injection chaotique de défaillances, tests de charge/d'endurance k6 | **Consultatif** | -| `nightly-llm-security` | Protection contre les injections promptfoo (mode blocage) + sondes garak (ignorées sans secret de fournisseur) | **Consultatif** | -| `nightly-schemathesis` | Fuzzing du contrat OpenAPI (schemathesis) sur une instance OmniRoute active à l'aide de `docs/openapi.yaml` — met en évidence les violations de la spécification / erreurs 500 non gérées (Phase 8 B.4) | **Consultatif** | -| `nightly-mutation` | Score des tests par mutation Stryker sur le parcours rapide des tests unitaires — les mutants survivants mettent en évidence les assertions faibles | **Consultatif** | -| `nightly-compat` | Matrice de compatibilité du moteur Node pour les plages `engines.node` prises en charge | **Consultatif** | +| Workflow | Valide | Bloquant | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `nightly-property` | Tests de propriétés fast-check avec une graine aléatoire et un nombre élevé d’exécutions | **Consultatif** | +| `nightly-resilience` | Contrôle de la croissance du tas, injection chaotique de défaillances, tests de charge/d’endurance k6 | **Consultatif** | +| `nightly-llm-security` | Protection contre l’injection promptfoo (mode blocage) + sondes garak (ignorées sans secret de fournisseur) | **Consultatif** | +| `nightly-schemathesis` | Fuzzing du contrat OpenAPI (schemathesis) sur une instance OmniRoute active à l’aide de `docs/openapi.yaml` — révèle les violations de la spécification / erreurs 500 non gérées (Phase 8 B.4) | **Consultatif** | +| `nightly-mutation` | Score des tests par mutation Stryker sur la voie rapide des tests unitaires — les mutants survivants révèlent les assertions faibles | **Consultatif** | +| `nightly-compat` | Matrice de compatibilité du moteur Node pour les plages `engines.node` prises en charge | **Consultatif** | --- @@ -547,36 +565,36 @@ Cet inventaire a été rapproché de `ci.yml` le 2026-06-17 (la version précéd `check:codeql-ratchet`, `check:pr-evidence`). Une revue du ROI de l’ensemble rapproché a permis d’identifier les candidats à la rationalisation suivants. **Les fusions sont des modifications mécaniques de la CI ; les basculements/suppressions sont des décisions de -politique réservées à l’opérateur.** Aucun des éléments ci-dessous n’est encore appliqué. +politique réservées à l’opérateur.** Rien de ce qui suit n’est encore appliqué. **Également non documentés ci-dessus** (consultatifs, signal faible) : la tâche `docs-lint` -(markdownlint + Vale, avec `continue-on-error` pour l’ensemble de la tâche) et les workflows -d’analyse autonomes `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` -figure dans `quality-baseline.json`, mais n’est relié à aucun cliquet bloquant dans -`ci.yml` — cette métrique est actuellement orpheline. +(markdownlint + Vale, tâche entière avec `continue-on-error`) et les workflows d’analyse +autonomes `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` figure dans +`quality-baseline.json`, mais n’est relié à aucun cliquet bloquant dans `ci.yml` — la métrique +est actuellement orpheline. -### Fusion / déduplication (mécanique, risque moindre) +### Fusion / déduplication (mécanique, risque plus faible) -Chaque candidat a été validé par rapport à l’état réel des contrôles le 2026-06-17 -(vérifier malgré la confiance) ; plusieurs fusions « évidentes » se sont avérées masquer -de la dette et **ne** peuvent **pas** être appliquées directement. +Chaque candidat a été validé par rapport à l’état réel des contrôles le 2026-06-17 (faire +confiance, mais vérifier) ; plusieurs fusions « évidentes » se sont avérées masquer de la +dette et ne sont **pas** des remplacements directs sans risque. -- **`check:docs-sync` est exécuté deux fois** — de manière autonome dans la tâche `lint`, puis à nouveau dans `check:docs-all` (`docs-sync-strict`) et dans le hook de pré-commit husky. ✅ **TERMINÉ** — invocation autonome supprimée de `lint`. -- **Analyse des CVE** — ❌ **PAS une fusion directe.** `audit:deps` échoue de manière bloquante pour toute CVE de gravité élevée/critique ; `check:vuln-ratchet` (osv) n’échoue qu’en cas de _régression_ par rapport à la référence (actuellement 1 MODERATE). Les sémantiques sont différentes — supprimer `audit:deps` ferait perdre le contrôle absolu des vulnérabilités élevées/critiques. Conserver les deux. -- **Détection des cycles** — ❌ **PAS une fusion directe.** `check:circular-deps` (dpdm) signale **91 cycles** (c’est pourquoi il est consultatif) ; il ne peut pas devenir bloquant avant leur résolution et sa portée est plus large que celle du contrôle `check:cycles`, ciblé et au vert. Conserver `check:cycles` comme contrôle bloquant ; la résolution des 91 cycles dpdm constitue un backlog distinct. -- **Complexité** — ✅ **TERMINÉ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`) : un seul parcours ESLint, avec comptage par ruleId afin que les références de complexité cyclomatique et de nombre maximal de lignes restent indépendantes des références cognitives ; les contrôles individuels `check:complexity` / `check:cognitive-complexity` sont conservés pour l’utilisation locale de `--update`. -- **Anti-hallucination de `/api`** — ✅ **TERMINÉ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`) : un seul inventaire du système de fichiers de `src/app/api` ; openapi-routes + docs-symbols continuent de produire des rapports indépendants ; les contrôles individuels sont conservés pour les exécutions locales. -- **`check:node-runtime` est exécuté dans 11 tâches** — ⚠️ **ROI faible.** Chacune s’exécute sur un runner distinct et le contrôle prend moins de 1 s ; l’économie totale est d’environ 10 s, au prix de la perte d’une protection peu coûteuse pour chaque tâche. Le bouleversement n’en vaut pas la peine. -- **`typecheck:noimplicit:core` dans le lint de la CI** — ✅ **supprimé de la tâche lint** (était consultatif avec `continue-on-error`) ; la surface de typage bloquante est assurée par `typecheck:core` + `check:type-coverage`. Script local conservé. +- **`check:docs-sync` s’exécute deux fois** — de manière autonome dans la tâche `lint`, puis à nouveau dans `check:docs-all` (`docs-sync-strict`) et dans le hook de pré-commit husky. ✅ **TERMINÉ** — invocation autonome supprimée de `lint`. +- **Analyse des CVE** — ❌ **PAS une fusion sans risque.** `audit:deps` échoue de manière bloquante pour toute CVE élevée/critique ; `check:vuln-ratchet` (osv) échoue uniquement en cas de _régression_ par rapport à la référence (actuellement 1 MODERATE). Sémantiques différentes — supprimer `audit:deps` ferait perdre le contrôle absolu des niveaux élevé/critique. Conserver les deux. +- **Détection des cycles** — ✅ **TERMINÉ** (#15159 G-01/G-02). L’ancien texte qualifiait ici `check:cycles` de contrôle « vert, organisé » et justifiait son maintien comme contrôle bloquant parce que `check:circular-deps` (dpdm) signalait 91 cycles. Ce vert était un **faux vert** : `check:cycles` analysait 5 sous-répertoires (450 fichiers), ne détectait que les instructions statiques `import|export … from` et ignorait tous les spécificateurs `@/` et `@omniroute/open-sse/`, de sorte qu’il ne pouvait pas voir les cycles d’import dynamique et d’alias qui dominaient le dépôt. Correction effectuée : le contrôle parcourt désormais `src` + `open-sse` (5023 fichiers), collecte les spécificateurs depuis l’AST TypeScript (ainsi, `import("…")` est pris en compte, contrairement à `typeof import("…")` en position de type) et résout les `paths` de tsconfig. Il détecte **14** cycles, et non 0. Comme 14 cycles préexistants ne peuvent pas être corrigés dans une PR consacrée aux contrôles, `check:cycles` est désormais un **cliquet** (`--ratchet`, plafond `metrics.cycles.value = 14` dans `quality-baseline.json`, `direction: down`) — il bloque toute _régression_ et le nombre ne peut que diminuer. La CI exécute `npm run check:cycles:ratchet`. La résorption progressive est intégrée à **A-01**. `check:circular-deps` (dpdm) reste consultatif en tant que deuxième avis plus large. +- **Complexité** — ✅ **TERMINÉ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`) : un seul parcours ESLint, avec des comptages par ruleId afin que les références cyclomatique+max-lines et cognitive restent indépendantes ; les commandes individuelles `check:complexity` / `check:cognitive-complexity` sont conservées pour les exécutions locales avec `--update`. +- **Anti-hallucination `/api`** — ✅ **TERMINÉ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`) : un seul inventaire du système de fichiers pour `src/app/api`, openapi-routes + docs-symbols continuent à produire des rapports indépendants ; les commandes individuelles sont conservées pour les exécutions locales. +- **`check:node-runtime` s’exécute dans 11 tâches** — ⚠️ **faible ROI.** Chacune utilise un exécuteur distinct et le contrôle dure moins de 1 s ; économies totales d’environ 10 s, au prix de la perte d’une protection peu coûteuse pour chaque tâche. Cela ne justifie pas les perturbations. +- **`typecheck:noimplicit:core` dans le lint de la CI** — ✅ **supprimé de la tâche lint** (était consultatif avec `continue-on-error`) ; la surface de typage bloquante est `typecheck:core` + `check:type-coverage`. Script local conservé. ### Basculer / décider (politique de l’opérateur) -- `check:openapi-security-tiers` (consultatif) — ❌ **PAS directement basculable.** Il se termine avec le code 0, mais avertit que plusieurs routes `traffic-inspector` sous `LOCAL_ONLY_API_PREFIXES` ne disposent pas de l’annotation `x-loopback-only: true`. Son application stricte nécessite d’abord l’ajout de ces annotations à `openapi.yaml`. -- `typecheck:noimplicit:core` (consultatif) — largement couvert par le cliquet bloquant `check:type-coverage`. Le convertir en cliquet ou supprimer ce second passage `tsc` redondant. -- `test:vitest:ui` (désormais **bloquant**) — les échecs préexistants sont explicitement exclus dans `vitest.config.ts` avec des commentaires de suivi `// #8618` ; tout nouvel échec fait échouer la tâche. -- `check:secrets` (gitleaks, cliquet bloquant figé à 3 faux positifs documentés) — ajouter les 3 à la liste d’exclusion pour atteindre 0, ou rétrograder le contrôle en consultatif. Chevauche l’analyse native des secrets de GitHub + `check:public-creds`. -- `check:pr-evidence` (bloquant, recherche des motifs dans le texte du corps de la PR) — risque élevé de faux positifs ; sa suppression affaiblirait l’application de la règle stricte nº 18, il s’agit donc d’une véritable décision de politique. -- `semgrep` (contrôle autonome consultatif) — chevauche CodeQL pour les familles OWASP ; relier sa référence à un cliquet ou le supprimer. +- `check:openapi-security-tiers` (consultatif) — ❌ **PAS directement basculable.** Il se termine avec le code 0, mais avertit que plusieurs routes `traffic-inspector` sous `LOCAL_ONLY_API_PREFIXES` ne comportent pas l’annotation `x-loopback-only: true`. Son application obligatoire nécessite d’abord l’ajout de ces annotations dans `openapi.yaml`. +- `typecheck:noimplicit:core` (consultatif) — largement couvert par le cliquet bloquant `check:type-coverage`. Le convertir en cliquet ou supprimer le second passage `tsc` redondant. +- `test:vitest:ui` (désormais **bloquant**) — les échecs préexistants sont explicitement exclus dans `vitest.config.ts` avec des commentaires de suivi `// #8618` ; les nouveaux échecs font échouer la tâche. +- `check:secrets` (gitleaks, cliquet bloquant figé à 3 faux positifs documentés) — ajouter les 3 à la liste d’autorisation pour atteindre 0, ou rétrograder le contrôle au statut consultatif. Recoupe l’analyse native des secrets de GitHub + `check:public-creds`. +- `check:pr-evidence` (bloquant, recherche du texte libre dans le corps de la PR avec grep) — risque élevé de faux positifs ; sa suppression affaiblirait l’application de la Règle stricte n° 18, il s’agit donc d’une véritable décision de politique. +- `semgrep` (workflow autonome consultatif) — recoupe CodeQL pour les familles OWASP ; relier sa référence à un cliquet ou le supprimer. --- diff --git a/docs/i18n/fr/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/fr/docs/compression/COMPRESSION_GUIDE.md index 1fa075bfae7d..fa85b4ddc531 100644 --- a/docs/i18n/fr/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/fr/docs/compression/COMPRESSION_GUIDE.md @@ -433,8 +433,7 @@ injectée à la place de l'anglais. Sur un corps avec `messages`, un contournement de contenu (`shouldBypassCavemanOutputMode()` dans `open-sse/services/compression/outputMode.ts`) vérifie les trois derniers messages et ignore les styles pour tout le tour lorsque ceux-ci correspondent à ses mots-clés de sécurité, d'action irréversible, -de clarification ou de sensibilité à l'ordre. Le contournement s'exécute quel que soit le réglage du -bouton **Auto-Clarity Bypass** du tableau de bord (`cavemanOutputMode.autoClarity`). +de clarification ou de sensibilité à l'ordre. Le contournement s'exécute tant que le bouton **Auto-Clarity Bypass** du tableau de bord (`cavemanOutputMode.autoClarity`) est activé, ce qui constitue le réglage par défaut ; lorsque le bouton est désactivé, les styles sélectionnés s'appliquent également à ces tours. Lorsque le contournement autorise le tour, `placeSystemInstruction()` (même fichier), qui ne crée jamais un nouveau `messages[0]`, place le bloc dans le premier de ces éléments qu'il trouve : diff --git a/docs/i18n/fr/docs/reference/ENVIRONMENT.md b/docs/i18n/fr/docs/reference/ENVIRONMENT.md index 86ea2ce9cfd2..e4b1376e6b05 100644 --- a/docs/i18n/fr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/fr/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Secrets requis -Ceux-ci **doivent** être définis avant la première exécution. Sans eux, l’application refusera de démarrer ou fonctionnera avec des valeurs par défaut non sécurisées. - -| Variable | Requis | Valeur par défaut | Fichier source | Description | -| ---------------------------- | -------------------- | --------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Oui** | _(aucune)_ | `src/lib/auth` | Signe/vérifie tous les cookies de session du tableau de bord (JWT). Générez-le avec `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Oui** | _(aucune)_ | `src/lib/db/apiKeys.ts` | Clé de chiffrement AES pour les valeurs de clés API stockées dans SQLite. Générez-la avec `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Oui** | `CHANGEME` | Script d’amorçage | Définit le mot de passe initial d’administration du tableau de bord (correspond à la valeur par défaut de `.env.example` — volontairement non sécurisée afin d’imposer sa modification). **Modifiez-le avant la première utilisation.** Après la connexion, modifiez-le via Tableau de bord → Paramètres → Sécurité. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Oui** (production) | _(non défini)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secret partagé pour le pont WebSocket interne de Codex Responses. Authentifie les requêtes du pont entre le relais WS Electron/navigateur et OmniRoute. ⚠️ **REQUIS en production — lorsqu’il n’est pas défini, toutes les requêtes du pont WS sont rejetées.** Générez-le avec `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Non | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identifiant explicite d’invalidation du cache du service worker pour l’enveloppe PWA (#11779) ; premier dans la chaîne de résolution. | -| `SOURCE_VERSION` | Non | _(non défini)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Deuxième dans la chaîne — défini par les systèmes de build PaaS (par exemple, de type Heroku) comme le commit déployé. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Non | _(dérivé)_ | `src/shared/components/PwaRegister.tsx` | Valeur publique définie lors du build, utilisée par le client pour enregistrer `/sw.js?v=…` ; dérivée des deux valeurs ci-dessus, puis du SHA git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Non (auto) | _(auto à chaque démarrage)_ | `src/server/authz/policies/management.ts` | Secret propre au processus prouvant que l’empreinte d’adresse IP du pair de confiance provient du propre serveur HTTP d’OmniRoute (`scripts/dev/peer-stamp.mjs`). Le middleware authz ne considère la requête comme locale (filtrage boucle locale/LAN des routes LOCAL_ONLY) que lorsque l’empreinte contient ce jeton. Généré automatiquement à chaque démarrage — laissez-le non défini ; ne le fixez que pour les configurations multiprocessus devant partager l’empreinte. | -| `OMNIROUTE_TRUSTED_PROXIES` | Non | _(non défini)_ | `scripts/dev/peer-stamp.mjs` | Adresses IP ou plages CIDR, séparées par des virgules, des proxys inverses dont les valeurs `X-Forwarded-For` / `X-Real-IP` peuvent être considérées comme fiables par la liste d’autorisation/de refus d’adresses IP, en plus des adresses de boucle locale, des adresses de réseaux privés et des nœuds périphériques Cloudflare, qui sont toujours considérés comme fiables. Nécessaire uniquement pour un proxy utilisant une autre adresse publique ; sinon, c’est l’adresse propre du proxy qui est évaluée par le filtre. | +Les secrets non définis marqués **Oui** sont générés automatiquement au premier lancement (`JWT_SECRET`, `API_KEY_SECRET`) et conservés dans `server.env`, dans le répertoire de données — définissez-les explicitement lorsque ce répertoire est éphémère. L’exception est `OMNIROUTE_WS_BRIDGE_SECRET` : il n’est pas conservé dans `server.env` — les points d’entrée du serveur génèrent une valeur éphémère propre à chaque démarrage que seul le pont interne au processus peut utiliser ; les clients externes du pont doivent donc le définir explicitement. + +| Variable | Requis | Valeur par défaut | Fichier source | Description | +| ---------------------------- | -------------------- | --------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Oui** | _(aucune)_ | `src/lib/auth` | Signe/vérifie tous les cookies de session du tableau de bord (JWT). Générez-le avec `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Oui** | _(aucune)_ | `src/lib/db/apiKeys.ts` | Clé de chiffrement AES pour les valeurs de clés API stockées dans SQLite. Générez-la avec `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Non | _(non défini)_ | `src/lib/auth/managementPassword.ts` | Définit le mot de passe du tableau de bord au démarrage tant qu’aucun mot de passe n’est enregistré ; il est ignoré dès qu’un mot de passe est enregistré. Les installations npm et à partir des sources copient `.env.example` vers `.env` : elles démarrent donc avec son espace réservé publiquement connu `CHANGEME` — remplacez-le avant le premier démarrage, ou définissez `INITIAL_PASSWORD=` (vide) dans ce `.env` pour créer le mot de passe dans l’assistant de configuration initiale à la première visite, comme le fait une valeur non définie (celle par défaut de l’image Docker). Modifiez un mot de passe enregistré dans Tableau de bord → Paramètres → Sécurité, ou exécutez `omniroute reset-password` (`node bin/reset-password.mjs` dans une copie des sources) avec `DATA_DIR` pointant vers le répertoire de données du serveur. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Oui** (production) | _(non défini)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secret partagé pour le pont WebSocket interne de Codex Responses. Authentifie les requêtes du pont entre le relais WS Electron/navigateur et OmniRoute. ⚠️ **REQUIS en production — lorsqu’il n’est pas défini, toutes les requêtes du pont WS sont rejetées.** Générez-le avec `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Non | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identifiant explicite d’invalidation du cache du service worker pour l’enveloppe PWA (#11779) ; premier dans la chaîne de résolution. | +| `SOURCE_VERSION` | Non | _(non défini)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Deuxième dans la chaîne — défini par les systèmes de build PaaS (par exemple, de type Heroku) comme le commit déployé. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Non | _(dérivé)_ | `src/shared/components/PwaRegister.tsx` | Valeur publique définie lors du build, utilisée par le client pour enregistrer `/sw.js?v=…` ; dérivée des deux valeurs ci-dessus, puis du SHA git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Non (auto) | _(auto à chaque démarrage)_ | `src/server/authz/policies/management.ts` | Secret propre au processus prouvant que l’empreinte d’adresse IP du pair de confiance provient du propre serveur HTTP d’OmniRoute (`scripts/dev/peer-stamp.mjs`). Le middleware authz ne considère la requête comme locale (filtrage boucle locale/LAN des routes LOCAL_ONLY) que lorsque l’empreinte contient ce jeton. Généré automatiquement à chaque démarrage — laissez-le non défini ; ne le fixez que pour les configurations multiprocessus devant partager l’empreinte. | +| `OMNIROUTE_TRUSTED_PROXIES` | Non | _(non défini)_ | `scripts/dev/peer-stamp.mjs` | Adresses IP ou plages CIDR, séparées par des virgules, des proxys inverses dont les valeurs `X-Forwarded-For` / `X-Real-IP` peuvent être considérées comme fiables par la liste d’autorisation/de refus d’adresses IP, en plus des adresses de boucle locale, des adresses de réseaux privés et des nœuds périphériques Cloudflare, qui sont toujours considérés comme fiables. Nécessaire uniquement pour un proxy utilisant une autre adresse publique ; sinon, c’est l’adresse propre du proxy qui est évaluée par le filtre. | ### Commandes de génération @@ -217,38 +217,38 @@ OmniRoute utilise **SQLite** (via `better-sqlite3`) pour toute la persistance. C ## 4. Sécurité et authentification -| Variable | Valeur par défaut | Fichier source | Description | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sel combiné aux identifiants matériels pour générer l’empreinte de la machine. À modifier pour chaque déploiement afin d’assurer l’isolation. | -| `OMNIROUTE_CLI_SALT` | _(non défini = sel aléatoire propre à l’installation, conservé dans `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sel HMAC utilisé pour dériver le jeton d’authentification local de la CLI. La définition de cette valeur renouvelle tous les jetons CLI de la machine et prévaut toujours sur le sel conservé. Voir `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Définit l’attribut `Secure` des cookies de session. **Doit être défini sur `true`** lorsque l’application s’exécute derrière HTTPS. | -| `REQUIRE_API_KEY` | `false` | Intergiciel d’API | Lorsque la valeur est `true`, toutes les requêtes proxy `/v1/*` doivent inclure une clé d’API valide. **Cet indicateur ne contrôle pas `GET /v1/models`**, qui suit à la place la configuration de connexion du tableau de bord (`requireAuthForModels`) — ainsi, une erreur `401` provenant de `/v1/models` ne signifie PAS que l’inférence est protégée. Voir `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Autorise l’affichage des valeurs complètes des clés d’API dans l’interface du tableau de bord. Configurable dans les indicateurs de fonctionnalité du tableau de bord ; présente un risque de sécurité sur les instances partagées. | -| `NO_LOG_API_KEY_IDS` | _(vide)_ | `src/lib/compliance/index.ts` | Identifiants de clés d’API, séparés par des virgules, pour lesquels la journalisation des requêtes est désactivée (conformité au RGPD). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(non défini = illimité)_ | `src/shared/utils/apiKeyPolicy.ts` | Budget quotidien de requêtes de secours appliqué aux clés d’API dont la colonne `rate_limits` est nulle. Valeur non définie ou vide : aucune limite implicite (#2289, #11017). `0` produit le même résultat (illimité). Un entier positif N active N requêtes/jour, 5N/semaine et 20N/mois. Les valeurs non vides mal formées utilisent les limites historiques de 1000/jour, 5000/semaine et 20000/mois. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 Mo) | `src/shared/middleware/bodySizeGuard.ts` | Taille maximale autorisée du corps d’une requête. Rejette les charges utiles dépassant cette limite. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 Ko) | `src/shared/middleware/chatBodyAdmission.ts` | Les corps de requête réels dont la taille atteint ou dépasse ce seuil acquièrent le bail d’admission atomique local au processus pour les charges lourdes avant l’analyse JSON (chemin BYTE, y compris `POST /v1/responses`). Utilise le même mécanisme d’échappement lorsque la marge disponible est suffisante que [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) pour les charges structurellement lourdes ; reste limité par `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), afin que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne se reproduise pas. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite stricte des routes de chat, appliquée aux octets lus pendant l'ingestion bornée, y compris pour les requêtes dont l'en-tête `Content-Length` est absent, non valide ou mensonger ; tout dépassement reçoit une réponse `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(non défini — aucune limite du nombre de requêtes)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout :** cette ancienne limite en NOMBRE de requêtes ne s'applique désormais que lorsqu'elle est explicitement définie. Lorsqu'elle n'est pas définie (comportement par défaut), l'admission des requêtes de chat lourdes est plutôt contrôlée par `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — un budget en OCTETS calculé automatiquement à partir de la limite de mémoire réelle du processus dans **un seul processus** (un seul tas V8). Deux requêtes `/v1/responses` simultanées d'environ 750 000 jetons font planter des tas d'environ 12 Gio (#7849) — il s'agit d'un avertissement lié au **budget mémoire**, et non d'une limite produit stricte de 2. Un processus sain (tas sous le ratio de délestage) PEUT accepter davantage de requêtes `/v1/responses` longues simultanées via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Des dizaines de clients SSE de longue durée (40–50) dépendent du tas + de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, et non d'un « maximum de 2 ». Augmenter aveuglément cette valeur pour « utiliser l'hôte » réintroduit #7849. Multipliez les **tas** avec **N `DATA_DIR` indépendants** (#11024) ; n'utilisez jamais `replicas>1` avec un seul fichier SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(calculé automatiquement)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout :** remplacement du budget d'octets d'ingestion calculé automatiquement (25 % de la plus restrictive des limites de mémoire V8/cgroup, divisés par une amplification transitoire de 8x). Les valeurs calculées et explicites sont limitées à une plage de 8 Mio à 2 Gio. Un corps plus volumineux que le budget effectif échoue immédiatement avec `413 body_exceeds_budget` ; la contention entre des corps pouvant individuellement être traités reste retentable avec `503`. La prise en charge de 40 à 50 clients SSE de longue durée dépend de ce budget + du tas, et non d'un « maximum de 2 » strict. Consultez `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` sur `/api/monitoring/health` avant tout réglage. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Ratio de délestage en cas de pression sur le tas (`heapUsed / heap_size_limit`) pour l'admission des requêtes lourdes de type OCTETS et STRUCTURE (#10183, #10268, #10437). Une requête lourde simultanée dépassant `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` n'est délestée avec la réponse retentable `503` que lorsque le tas atteint ou dépasse ÉGALEMENT ce ratio ; si le tas est sain, elle est admise via la marge de capacité saine. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valeur par défaut `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacité supplémentaire bornée pour le chemin rapide avec tas sain (#10437), à la fois pour STRUCTURE et OCTETS (`admitChatRequest`, y compris les corps ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sans cette limite, chaque requête reçue pendant une période chargée, mais avec un tas sain, contournait l'admission sans aucun plafond. Une fois ce nombre de baux simultanés actifs via le chemin avec tas sain atteint, les requêtes supplémentaires reçues pendant la période chargée basculent vers le MÊME chemin d'attente bornée/de délestage que celui utilisé sous une véritable pression sur le tas. `0` désactive entièrement le contournement. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Nombre de messages à partir duquel une requête de chat est classée comme lourde, même lorsque son corps est inférieur au seuil en octets. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Nombre d'outils à partir duquel une requête de chat est classée comme lourde, même lorsque son corps est inférieur au seuil en octets. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimation prudente du nombre de jetons d'après la taille des chaînes, utilisée pour classer une requête comme lourde ; il s'agit d'une approximation du coût d'admission, et non de la tokenisation utilisée pour la facturation du fournisseur. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (désactivé) | `src/shared/middleware/chatBodyAdmission.ts` | Limite facultative et volontaire de l'historique de chat. Désactivée par défaut : une limite du nombre de messages relève de la politique de déploiement, et non d'une propriété universelle d'une requête ; imposer cette limite ici rejette les conversations avec une réponse terminale `413` avant que le pipeline de compression puisse les rendre traitables. La croissance du tas est bornée par `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` et par le délestage en cas de pression sur le tas. Définissez une valeur positive pour les déploiements contraints en mémoire qui nécessitent un plafond strict ; tout dépassement reçoit alors une réponse structurée `413` indiquant qu'une compression est requise. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite stricte pour une réponse amont non diffusée en continu, entièrement mise en mémoire tampon. Au-delà de cette limite, la lecture en amont est annulée et la requête échoue rapidement au lieu de faire croître une chaîne sans limite jusqu'à épuisement du tas. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Nombre maximal d’octets transmis depuis les en-têtes de réponse en amont. Lorsque le budget est dépassé, les en-têtes de priorité inférieure (par ex., les en-têtes personnalisés `x-codex-*`, `x-oai-request-id`) sont supprimés afin de respecter les limites courantes des en-têtes des proxys inverses. Définissez une valeur plus élevée pour transmettre davantage de métadonnées en amont, au prix d’une taille accrue des en-têtes de réponse. | -| `CORS_ORIGIN` | _(non défini)_ | `src/server/cors/origins.ts` | Liste d’autorisation CORS historique pour une origine unique. Préférez `CORS_ALLOWED_ORIGINS` pour les nouveaux déploiements. CORS est uniquement destiné aux clients d’API de navigateur inter-origines ; les écritures authentifiées du tableau de bord utilisent plutôt des requêtes de même origine ainsi qu’une protection CSRF liée à la session. | -| `CORS_ALLOWED_ORIGINS` | _(non défini)_ | `src/server/cors/origins.ts` | Liste d’autorisation CORS séparée par des virgules. Aucun caractère générique n’est envoyé, sauf si `CORS_ALLOW_ALL=true` est explicitement configuré. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Échappatoire réservée au développement permettant de renvoyer n’importe quel `Origin` de navigateur. Ne l’activez pas dans les déploiements partagés ou de production. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Bloque les appels aux fournisseurs ciblant des plages d’adresses IP privées, de bouclage ou lien-local. Désactivez cette option uniquement dans des environnements de test isolés. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Autorise les URL de fournisseurs pointant vers des réseaux privés/locaux (localhost, 192.168.x.x, 10.x.x.x, etc.). **REQUIS pour les fournisseurs auto-hébergés** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Lorsque la valeur est `false`, le tableau de bord refuse la validation des URL locales. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Autorise l’ajout/la validation de fournisseurs sur des adresses locales/privées (127.0.0.1, localhost, LAN, plages privées) — limité au chemin de validation des fournisseurs. **Valeur par défaut : `true`** (priorité au local) ; définissez `false` pour imposer un blocage strict limité aux adresses publiques. Les points de terminaison des métadonnées cloud (169.254.169.254, metadata.google.internal) restent bloqués dans tous les cas. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permet aux routes `/v1/audio/*` (transcriptions, synthèse vocale, traductions) d’utiliser un nœud fournisseur compatible avec OpenAI hébergé en dehors de localhost. Désactivé par défaut — le routage audio vers un hôte distant modifie l’identité de sortie et doit résulter d’une décision explicite de l’opérateur. Les nœuds de bouclage/privés (localhost, 127.0.0.1, 172.16-31.x) sont toujours autorisés et ne sont pas affectés. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permet à `POST /v1/rerank` (ainsi qu’à l’étape de reclassement en bouclage du moteur de mémoire) d’utiliser un nœud fournisseur compatible avec OpenAI hébergé en dehors de localhost — une machine du LAN ou un pair Tailscale exécutant TEI, Infinity, vLLM, etc. Désactivé par défaut — le routage vers un hôte distant modifie l’identité de sortie et doit résulter d’une décision explicite de l’opérateur. Les nœuds de bouclage (localhost, 127.0.0.1, 172.16-31.x) sont toujours autorisés et ne sont pas affectés. Les nœuds distants doivent également respecter la politique relative aux URL sortantes des fournisseurs (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ; les hôtes de métadonnées cloud ne sont jamais utilisés pour le routage. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Lorsque OIDC est activé, désactive la connexion par mot de passe afin que les utilisateurs puissent uniquement s’authentifier via l’authentification unique OIDC. L’alias court `OIDC_DISABLE_PASSWORD_LOGIN` est également accepté ; l’indicateur de fonctionnalité du tableau de bord portant la même clé est prioritaire. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias court de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variable | Valeur par défaut | Fichier source | Description | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sel combiné aux identifiants matériels pour générer l’empreinte de la machine. À modifier pour chaque déploiement afin d’assurer l’isolation. | +| `OMNIROUTE_CLI_SALT` | _(non défini = sel aléatoire propre à l’installation, conservé dans `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sel HMAC utilisé pour dériver le jeton d’authentification local de la CLI. La définition de cette valeur renouvelle tous les jetons CLI de la machine et prévaut toujours sur le sel conservé. Voir `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Définit l’attribut `Secure` des cookies de session. **Doit être défini sur `true`** lorsque l’application s’exécute derrière HTTPS. | +| `REQUIRE_API_KEY` | `false` | Intergiciel d’API | Lorsque la valeur est `true`, toutes les requêtes proxy `/v1/*` doivent inclure une clé d’API valide. **Cet indicateur ne contrôle pas `GET /v1/models`**, qui suit à la place la configuration de connexion du tableau de bord (`requireAuthForModels`) — ainsi, une erreur `401` provenant de `/v1/models` ne signifie PAS que l’inférence est protégée. Voir `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Autorise l’affichage des valeurs complètes des clés d’API dans l’interface du tableau de bord. Configurable dans les indicateurs de fonctionnalité du tableau de bord ; présente un risque de sécurité sur les instances partagées. | +| `NO_LOG_API_KEY_IDS` | _(vide)_ | `src/lib/compliance/index.ts` | Identifiants de clés d’API, séparés par des virgules, pour lesquels la journalisation des requêtes est désactivée (conformité au RGPD). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(non défini = illimité)_ | `src/shared/utils/apiKeyPolicy.ts` | Budget quotidien de requêtes de secours appliqué aux clés d’API dont la colonne `rate_limits` est nulle. Valeur non définie ou vide : aucune limite implicite (#2289, #11017). `0` produit le même résultat (illimité). Un entier positif N active N requêtes/jour, 5N/semaine et 20N/mois. Les valeurs non vides mal formées utilisent les limites historiques de 1000/jour, 5000/semaine et 20000/mois. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 Mo) | `src/shared/middleware/bodySizeGuard.ts` | Taille maximale autorisée du corps d’une requête. Rejette les charges utiles dépassant cette limite. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 Ko) | `src/shared/middleware/chatBodyAdmission.ts` | Les corps de requête réels dont la taille atteint ou dépasse ce seuil acquièrent le bail d’admission atomique local au processus pour les charges lourdes avant l’analyse JSON (chemin BYTE, y compris `POST /v1/responses`). Utilise le même mécanisme d’échappement lorsque la marge disponible est suffisante que [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) pour les charges structurellement lourdes ; reste limité par `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), afin que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne se reproduise pas. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite stricte des routes de chat, appliquée aux octets lus pendant l'ingestion bornée, y compris pour les requêtes dont l'en-tête `Content-Length` est absent, non valide ou mensonger ; tout dépassement reçoit une réponse `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(non défini — aucune limite du nombre de requêtes)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout :** cette ancienne limite en NOMBRE de requêtes ne s'applique désormais que lorsqu'elle est explicitement définie. Lorsqu'elle n'est pas définie (comportement par défaut), l'admission des requêtes de chat lourdes est plutôt contrôlée par `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — un budget en OCTETS calculé automatiquement à partir de la limite de mémoire réelle du processus dans **un seul processus** (un seul tas V8). Deux requêtes `/v1/responses` simultanées d'environ 750 000 jetons font planter des tas d'environ 12 Gio (#7849) — il s'agit d'un avertissement lié au **budget mémoire**, et non d'une limite produit stricte de 2. Un processus sain (tas sous le ratio de délestage) PEUT accepter davantage de requêtes `/v1/responses` longues simultanées via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Des dizaines de clients SSE de longue durée (40–50) dépendent du tas + de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, et non d'un « maximum de 2 ». Augmenter aveuglément cette valeur pour « utiliser l'hôte » réintroduit #7849. Multipliez les **tas** avec **N `DATA_DIR` indépendants** (#11024) ; n'utilisez jamais `replicas>1` avec un seul fichier SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(calculé automatiquement)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout :** remplacement du budget d'octets d'ingestion calculé automatiquement (25 % de la plus restrictive des limites de mémoire V8/cgroup, divisés par une amplification transitoire de 8x). Les valeurs calculées et explicites sont limitées à une plage de 8 Mio à 2 Gio. Un corps plus volumineux que le budget effectif échoue immédiatement avec `413 body_exceeds_budget` ; la contention entre des corps pouvant individuellement être traités reste retentable avec `503`. La prise en charge de 40 à 50 clients SSE de longue durée dépend de ce budget + du tas, et non d'un « maximum de 2 » strict. Consultez `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` sur `/api/monitoring/health` avant tout réglage. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Ratio de délestage en cas de pression sur le tas (`heapUsed / heap_size_limit`) pour l'admission des requêtes lourdes de type OCTETS et STRUCTURE (#10183, #10268, #10437). Une requête lourde simultanée dépassant `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` n'est délestée avec la réponse retentable `503` que lorsque le tas atteint ou dépasse ÉGALEMENT ce ratio ; si le tas est sain, elle est admise via la marge de capacité saine. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valeur par défaut `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacité supplémentaire bornée pour le chemin rapide avec tas sain (#10437), à la fois pour STRUCTURE et OCTETS (`admitChatRequest`, y compris les corps ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sans cette limite, chaque requête reçue pendant une période chargée, mais avec un tas sain, contournait l'admission sans aucun plafond. Une fois ce nombre de baux simultanés actifs via le chemin avec tas sain atteint, les requêtes supplémentaires reçues pendant la période chargée basculent vers le MÊME chemin d'attente bornée/de délestage que celui utilisé sous une véritable pression sur le tas. `0` désactive entièrement le contournement. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Nombre de messages à partir duquel une requête de chat est classée comme lourde, même lorsque son corps est inférieur au seuil en octets. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Nombre d'outils à partir duquel une requête de chat est classée comme lourde, même lorsque son corps est inférieur au seuil en octets. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimation prudente du nombre de jetons d'après la taille des chaînes, utilisée pour classer une requête comme lourde ; il s'agit d'une approximation du coût d'admission, et non de la tokenisation utilisée pour la facturation du fournisseur. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (désactivé) | `src/shared/middleware/chatBodyAdmission.ts` | Limite facultative et volontaire de l'historique de chat. Désactivée par défaut : une limite du nombre de messages relève de la politique de déploiement, et non d'une propriété universelle d'une requête ; imposer cette limite ici rejette les conversations avec une réponse terminale `413` avant que le pipeline de compression puisse les rendre traitables. La croissance du tas est bornée par `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` et par le délestage en cas de pression sur le tas. Définissez une valeur positive pour les déploiements contraints en mémoire qui nécessitent un plafond strict ; tout dépassement reçoit alors une réponse structurée `413` indiquant qu'une compression est requise. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite stricte pour une réponse amont non diffusée en continu, entièrement mise en mémoire tampon. Au-delà de cette limite, la lecture en amont est annulée et la requête échoue rapidement au lieu de faire croître une chaîne sans limite jusqu'à épuisement du tas. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Nombre maximal d’octets transmis depuis les en-têtes de réponse en amont. Lorsque le budget est dépassé, les en-têtes de priorité inférieure (par ex., les en-têtes personnalisés `x-codex-*`, `x-oai-request-id`) sont supprimés afin de respecter les limites courantes des en-têtes des proxys inverses. Définissez une valeur plus élevée pour transmettre davantage de métadonnées en amont, au prix d’une taille accrue des en-têtes de réponse. | +| `CORS_ORIGIN` | _(non défini)_ | `src/server/cors/origins.ts` | Liste d’autorisation CORS historique pour une origine unique. Préférez `CORS_ALLOWED_ORIGINS` pour les nouveaux déploiements. CORS est uniquement destiné aux clients d’API de navigateur inter-origines ; les écritures authentifiées du tableau de bord utilisent plutôt des requêtes de même origine ainsi qu’une protection CSRF liée à la session. | +| `CORS_ALLOWED_ORIGINS` | _(non défini)_ | `src/server/cors/origins.ts` | Liste d’autorisation CORS séparée par des virgules. Aucun caractère générique n’est envoyé, sauf si `CORS_ALLOW_ALL=true` est explicitement configuré. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Échappatoire réservée au développement permettant de renvoyer n’importe quel `Origin` de navigateur. Ne l’activez pas dans les déploiements partagés ou de production. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias historique. Son interrupteur du tableau de bord (un remplacement enregistré en base de données) est lu avant cette variable d’environnement ; `false`, `0`, `no` ou `off` sur l’un ou l’autre désactive les contrôles d’hôte comme le fait `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, même lorsque l’interrupteur du tableau de bord de cet indicateur est enregistré comme désactivé ; les autres valeurs laissent la protection aux deux indicateurs ci-dessous. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Désactive les contrôles d’hôte de la protection des URL sortantes, blocage des métadonnées cloud inclus (la totalité de 169.254.0.0/16, metadata.google.internal), sur les chemins des fournisseurs qui suivent le mode de la protection : validation des URL, découverte des modèles, URL de base des nœuds fournisseurs (y compris les nœuds de reclassement distants) et le test de repli du proxy, parmi d’autres chemins sortants adjacents aux fournisseurs tels que la récupération d’images ou de médias distants. Il autorise aussi les cibles de webhook privées, à l’exception des points de terminaison de métadonnées cloud ou lien-local, qui restent bloqués même si cet indicateur est activé. Les requêtes de chat envoyées via `BaseExecutor.execute()` conservent leur contrôle des métadonnées cloud. Les fournisseurs locaux fonctionnent avec les valeurs par défaut : `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (par défaut `true`) autorise déjà les URL locales et LAN sur les chemins de validation, de découverte et de nœuds fournisseurs ; le test de repli du proxy et les cibles de webhook privées ne consultent que cet indicateur et bloquent les hôtes locaux/LAN tant qu’il est désactivé. Prévaut sur `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Une valeur enregistrée depuis l’interrupteur du tableau de bord prime sur cette variable. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Autorise les URL de fournisseurs sur des adresses locales/privées (127.0.0.1, localhost, LAN, plages privées) pour la validation des fournisseurs, la découverte des modèles, le chat et les autres chemins sortants adjacents aux fournisseurs régis par le mode de la protection. **Valeur par défaut : `true`** (priorité au local) : la protection bloque alors les points de terminaison de métadonnées cloud — la totalité de 169.254.0.0/16 plus les noms d’hôtes de métadonnées connus (metadata.google.internal, metadata.goog, IMDS d’Alibaba/Azure/Oracle, fd00:ec2::254). Définissez `false` pour le mode public uniquement, qui bloque aussi les hôtes privés et de bouclage. La protection vérifie le nom d’hôte ou le littéral IP tel qu’écrit, avant toute résolution DNS ; en mode public uniquement, l’enveloppe de récupération sécurisée résout en plus le nom d’hôte et refuse toute réponse privée. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` désactive ces contrôles sur les chemins listés par sa ligne. Les fournisseurs locaux intégrés, comme LM Studio, Ollama, vLLM, Llamafile et Triton, ignorent tous les contrôles d’hôte pour la validation des clés et le chat. Une valeur enregistrée depuis l’interrupteur du tableau de bord prime sur cette variable. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permet aux routes `/v1/audio/*` (transcriptions, synthèse vocale, traductions) d’utiliser un nœud fournisseur compatible avec OpenAI hébergé en dehors de localhost. Désactivé par défaut — le routage audio vers un hôte distant modifie l’identité de sortie et doit résulter d’une décision explicite de l’opérateur. Les nœuds de bouclage/privés (localhost, 127.0.0.1, 172.16-31.x) sont toujours autorisés et ne sont pas affectés. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permet à `POST /v1/rerank` (ainsi qu’à l’étape de reclassement en bouclage du moteur de mémoire) d’utiliser un nœud fournisseur compatible avec OpenAI hébergé en dehors de localhost — une machine du LAN ou un pair Tailscale exécutant TEI, Infinity, vLLM, etc. Désactivé par défaut — le routage vers un hôte distant modifie l’identité de sortie et doit résulter d’une décision explicite de l’opérateur. Les nœuds de bouclage (localhost, 127.0.0.1, 172.16-31.x) sont toujours autorisés et ne sont pas affectés. L’URL de base d’un nœud distant doit également respecter la politique relative aux URL sortantes des fournisseurs (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Lorsque OIDC est activé, désactive la connexion par mot de passe afin que les utilisateurs puissent uniquement s’authentifier via l’authentification unique OIDC. L’alias court `OIDC_DISABLE_PASSWORD_LOGIN` est également accepté ; l’indicateur de fonctionnalité du tableau de bord portant la même clé est prioritaire. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias court de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Liste de contrôle du renforcement de la sécurité diff --git a/docs/i18n/ga/CONTRIBUTING.md b/docs/i18n/ga/CONTRIBUTING.md index 7e7d38588f04..dec16f289523 100644 --- a/docs/i18n/ga/CONTRIBUTING.md +++ b/docs/i18n/ga/CONTRIBUTING.md @@ -404,18 +404,18 @@ Scríobh tástálacha aonaid in `tests/unit/` a chlúdaíonn, ar a laghad: - [ ] Éiríonn leis na tástálacha (`npm test`) - [ ] Éiríonn leis an lintáil (`npm run lint`) -- [ ] Éiríonn leis an tógáil (`npm run build`) +- [ ] Éiríonn leis an tiomsú (`npm run build`) - [ ] Cineálacha TypeScript curtha leis le haghaidh feidhmeanna agus comhéadain phoiblí nua - [ ] Gan aon rúin ná luachanna cúltaca crua-chódaithe -- [ ] Dintiúir phoiblí réamhtheachtacha leabaithe trí `resolvePublicCred()` (féach [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), agus ní mar theaghráin litriúla choíche -- [ ] Freagairtí earráide seolta trí `buildErrorBody()` / `sanitizeErrorMessage()` — gan aon lorg cruachta amh i gcoirp freagartha (féach [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Tugann orduithe bhlaosc (`exec` / `spawn`) luachanna ama rite ar aghaidh trí `env`, agus ní trí idirshuíomh teaghrán +- [ ] Dintiúir phoiblí réamhtheachtacha leabaithe trí `resolvePublicCred()` (féach [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), agus ní mar litearáil riamh +- [ ] Freagraí earráide seolta trí `buildErrorBody()` / `sanitizeErrorMessage()` — gan aon rianta amhchruaiche i gcorp na bhfreagraí (féach [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Orduithe blaoisce (`exec` / `spawn`) a chuireann luachanna ama rite ar aghaidh trí `env`, ní trí idirshuíomh teaghrán - [ ] Gach ionchur bailíochtaithe le scéimeanna Zod -- [ ] **Blúire** den loga athruithe curtha faoi `changelog.d/{features|fixes|maintenance}/-.md` le haghaidh athruithe atá le feiceáil ag úsáideoirí (féach [`changelog.d/README.md`](./changelog.d/README.md)) — **ná** cuir `CHANGELOG.md` in eagar go díreach; déantar na blúirí a chomhiomlánú tráth eisiúna agus ní bhíonn coinbhleacht eatarthu idir iarratais tarraingthe choíche +- [ ] **Blúire** den loga athruithe curtha leis faoi `changelog.d/{features|fixes|maintenance}/-.md` le haghaidh athruithe atá infheicthe ag úsáideoirí (féach [`changelog.d/README.md`](./changelog.d/README.md)) — ná cuir `CHANGELOG.md` in eagar go díreach; déantar na blúirí a chomhiomlánú tráth an eisiúna agus ní bhíonn coinbhleacht eatarthu riamh i measc PRanna - [ ] Doiciméadacht nuashonraithe (más infheidhme) -- [ ] Gan aon fholáirimh nua CodeQL / Secret-Scanning oscailte, nó gach ceann díobh diúltaithe le bonn cirt teicniúil a thagraíonn don doiciméad ábhartha in `docs/security/` +- [ ] Gan aon fholáirimh nua CodeQL / Secret-Scanning oscailte, nó gach ceann acu diúltaithe le bonn cirt teicniúil a thagraíonn don doiciméad ábhartha in `docs/security/` - [ ] Bealaí a sceitheann próisis mhac (`/api/mcp/`, `/api/cli-tools/runtime/`) aicmithe mar `isLocalOnlyPath()` in `src/server/authz/routeGuard.ts` — féach [Riail Dhocht #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Gan aon leantóirí `Co-Authored-By` i dteachtaireachtaí tiomantais — ní mór do thiomantais a bheith le feiceáil faoi aitheantas Git úinéir na stórtha amháin (Riail Dhocht #16) +- [ ] Gan aon leantóirí `Co-authored-by` ó IS/róbónna i dteachtaireachtaí tiomantais (Riail Dhocht #16) — tugtar aitheantas do chomhoibrithe daonna a n-athúsáidtear a gcuid oibre le leantóirí caighdeánacha `Co-authored-by: Name ` --- diff --git a/docs/i18n/ga/docs/architecture/QUALITY_GATES.md b/docs/i18n/ga/docs/architecture/QUALITY_GATES.md index c778fbc92533..03b86b0eca4e 100644 --- a/docs/i18n/ga/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ga/docs/architecture/QUALITY_GATES.md @@ -15,205 +15,213 @@ neamhspleách ar uirlisí don chóras céanna, féach an --- -## Fardal geataí agus próifílí forghníomhaithe - -### Glacadh iarrthóra - -Eisíonn na sreafaí oibre CI agus Quality Gates breithiúnas cobhsaí araon: `Gate / CI` agus -`Gate / Quality`. Áirítear ina mbeartas glactha leaganaithe gach tasc réamhtheachtach -mar riachtanach nó mar chomhairleach. Ní mór do thasc riachtanach infheidhme éirí leis: ní féidir -PASS a bhunú le torthaí atá ar iarraidh, curtha ar ceal, scipeáilte, ar feitheamh nó anaithnid. Is féidir -le haicmiú bailí do dhoiciméid amháin nó don chatalóg amháin lána cóid a dhéanamh neamh-infheidhme; -ní ghlactar le dréacht-PR mar iarrthóir. Ní thugann lipéad `hotfix` díolúine ó fhianaise. - -Clúdaíonn an dá shreabhadh oibre PRanna agus brúnna chuig brainsí main/release, seoladh láimhe agus -imeachtaí grúpa cumaisc. Ritheann brú, seoladh agus grúpa cumaisc an roghnú iomlán. Úsáideann forcanna -agus grúpaí cumaisc riteoirí óstáilte le haghaidh tascanna a roghnódh riteoirí féinóstáilte murach sin; -ní mór dóthain acmhainneachta óstáilte a dheimhniú roimh an rolladh amach. - -Aithníonn gach admháil JSON an SHA atá seiceáilte amach, an rith sreafa oibre agus an iarracht. -Diúltaíonn an CLI do neamhréir idir SHA an tseiceáil amach agus SHA an imeachta. Ceanglaíonn tástálacha sreafa oibre ballraíocht an bheartais -le liosta `needs` thasc an bhreithiúnais ionas nach féidir le lána nua nó bainte imeacht gan rabhadh. -Clúdaíonn na hadmhálacha a sreabhadh oibre féin, ní foilsiú, imscaradh ná oibriú inmheánach -scanóra chomhairligh atá ann cheana. Is athrú riaracháin ar leith é an dá ainm seiceála a ghníomhachtú i rialacha brainse; -ní chosnaíonn cur leis na tascanna seo brainse ann féin. - -### Fardal scanta stataigh - -Tá an fardal npm-alias leaganaithe agus ballraíocht an scanta stataigh le fáil in -`config/quality/gate-manifest.json`. Rith `npm run check:gate-manifest` chun ainmneacha scripte agus orduithe beachta -a bhailíochtú in aghaidh `package.json`; má chuirtear rud leis, má bhaintear rud nó má athraíonn ordú, teipfidh -an crúca áitiúil agus na tascanna aicmithe athruithe in CI araon. -Ní tasc sreafa oibre, ásc maitríse ná cás tástála é ailias: níor cheart -na comhairimh seo a chur i láthair amhail is dá mbeidís inmhalartaithe. +## Fardal na ngeataí agus próifílí forghníomhaithe + +### Iarrthóirí a ghlacadh isteach + +Astaíonn sreafaí oibre CI agus Quality Gates breithiúnas cobhsaí araon: `Gate / CI` agus +`Gate / Quality`. Áirítear ina mbeartas iontrála le leaganacha gach jab réamhtheachtach +mar jab riachtanach nó comhairleach. Ní mór do jab riachtanach infheidhme a bheith rathúil: +ní féidir le torthaí atá ar iarraidh, curtha ar ceal, scipeáilte, ar feitheamh nó anaithnid +PASS a dheimhniú. Is féidir le haicmiú bailí do dhoiciméid amháin nó don chatalóg amháin +lána cóid a dhéanamh neamh-infheidhme; ní ghlactar le dréacht-PR mar iarrthóir. Ní +tharscaoileann lipéad `hotfix` an riachtanas fianaise. + +Clúdaíonn an dá shreabhadh oibre PRanna agus brúite chuig brainsí main/release, seoladh +láimhe agus teagmhais ghrúpa cumaisc. Ritheann brú, seoladh agus grúpa cumaisc an roghnú +iomlán. Úsáideann forcanna agus grúpaí cumaisc riteoirí óstáilte le haghaidh jabanna a +roghnódh riteoirí féinóstáilte murach sin; ní mór dóthain acmhainneachta óstáilte a +fhíorú roimh an rolladh amach. + +Sainaithníonn gach admháil JSON an SHA a seiceáladh amach, rith an tsreafa oibre agus an +iarracht. Diúltaíonn an CLI do neamhréir idir SHA an tseiceáil amach agus SHA an teagmhais. +Ceanglaíonn tástálacha sreafa oibre ballraíocht an bheartais le liosta `needs` an jab +breithiúnais ionas nach féidir le lána nua ná le lána bainte imeacht gan rabhadh. +Clúdaíonn na hadmhálacha a sreabhadh oibre féin, seachas foilsiú, imscaradh nó oibriú +inmheánach scanóra chomhairligh atá ann cheana. Is athrú riaracháin ar leith é an dá ainm +seiceála a ghníomhachtú i rialacha brainse; ní chosnaíonn cur leis na jabanna seo brainse +ann féin. + +### Fardal na scananna statacha + +Tá an fardal npm-alias le leaganacha agus ballraíocht an scanadh statach le fáil in +`config/quality/gate-manifest.json`. Rith `npm run check:gate-manifest` chun ainmneacha +scripte agus orduithe beachta a bhailíochtú in aghaidh `package.json`; cuirfidh breisithe, +bainte agus imeacht orduithe an duán áitiúil agus na jabanna aicmithe athruithe in CI +araon ó rath. Ní jab sreafa oibre, ásc maitríse ná cás tástála é ailias: níor cheart na +comhairimh seo a chur i láthair mar rudaí idirmhalartaithe. Úsáid `npm run quality:scan -- --list` nó `npm run quality:scan:fast -- --list` chun na hailiasanna roghnaithe a iniúchadh gan iad a fhorghníomhú. Glaonn an riteoir ar -phointe iontrála npm, mar sin caomhnaítear a thimpeallacht rite (Bun san áireamh nuair atá sé cumraithe). -Taifeadann an lastliosta ailiasanna lasmuigh de na próifílí sin mar chinn a ghlaoitear orthu ar leithligh, agus -tá cosc ar orduithe cothabhála i bpróifílí scanta inléite amháin. - -Ní chlúdaíonn na próifílí seo ach an scanadh statach. Ní dheimhníonn siad tástálacha táirge, -clúdach, pacáistiú, seiceálacha seachtracha ná glacadh iomlán iarrthóra le haghaidh eisiúna. -Úsáideann glacadh sreafa oibre na comhaid nasctha `config/quality/admission-policy.json` agus +an bpointe iontrála npm, agus mar sin caomhnaítear a thimpeallacht rite (Bun san áireamh +san áit a bhfuil sé cumraithe). Taifeadann an lastliosta ailiasanna lasmuigh de na +próifílí sin mar ailiasanna a ghlaoitear orthu ar leithligh, agus tá cosc ar orduithe +cothabhála i bpróifílí scanta inléite amháin. + +Ní chlúdaíonn na próifílí seo ach an scanadh statach. Ní dheimhníonn siad tástálacha +táirge, clúdach, pacáistiú, seiceálacha seachtracha ná glacadh iomlán le heisiúint +iarrthóra. Úsáideann ligean isteach sa sreabhadh oibre +`config/quality/admission-policy.json` nasctha agus `scripts/quality/admission-verdict.mjs`. Fanann próifílí breathnóra eisiúna ar leithligh; -scrúdaigh a seiceálacha agus a n-admhálacha infheidhme go neamhspleách. Is tagairt é an fardal -próis thíos, ní cruthúnas gur rith geata i ndáiríre. +iniúch a seiceálacha agus a n-admhálacha infheidhme go neamhspleách. Is tagairt é an +fardal próis thíos, ní cruthúnas gur ritheadh geata i ndáiríre. Tá scripteanna faoi `scripts/check/` (geataí beartais) agus `scripts/quality/` (inneall raicéid). Is é `.github/workflows/ci.yml` foinse fírinne CI. ### Mearchonair PR eisiúna (`quality.yml`) -Comhlánaíonn `.github/workflows/quality.yml` CI ar PRanna main/release, brúnna chuig brainsí cosanta, -seoladh agus grúpaí cumaisc. Úsáideann PRanna mearseiceálacha scagtha de réir conaire. Baineadh an tógáil dhúblach -a bhí díchumasaithe go buan; fanann na fíorsheiceálacha tógála/pacáistithe/tosaithe in CI. - -| Tasc | Raon feidhme | Blocála | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `Docs Gates (fast-path)` | PRanna do dhoiciméid/chód; tagairtí do dhoiciméid API agus na doiciméid uile | Tá | -| `Fast Quality Gates` | PRanna cóid; seiceálacha statacha, seiceáil cineálacha, seiceáil cineálacha an deais, tástálacha aonaid a ndearnadh difear dóibh | Tá | -| `Forgotten sibling tests` | PRanna cóid; modúil athraithe rianaithe chuig tomhaltóirí statacha agus tástálacha siblín féideartha; tuairiscítear conairí bairille agus iompórtála dhinimiciúil mar dhiagnóisic chomhairleach, le heisceachtaí ceadaithe a dtagraítear dóibh | **Comhairleach** | -| `Vitest (fast-path)` | PRanna cóid; sraith thapa vitest | Tá | -| `Unit Tests fast-path` | PRanna cóid; sraith aonadtástálacha le 4 sharda | Tá | -| `No new ESLint warnings` | PRanna cóid; cosaint lint atá feasach ar cheiltí | Tá, forcanna san áireamh | -| `Merge integrity (changelog + generated skills)` | PRanna nach dréachtaí iad; loga athruithe agus sioncrónú scileanna ginte | Tá, forcanna san áireamh | - -#### Tuairisc ar thástálacha siblín dearmadta - -Athúsáideann `npm run check:forgotten-sibling-tests` an réiteoir iompórtála taobh thiar den mhapa tionchair tástála. -I gcás gach modúil táirgeachta athraithe, tuairiscíonn sé slabhraí cinntitheacha -`changed module/symbol -> static consumer -> candidate sibling test` nuair nach bhfuil an tástáil fhéideartha -i ndifríocht an iarratais tarraingthe. Coinnítear an achoimre Markdown agus toradh JSON mar -dhéantán sreafa oibre `forgotten-sibling-tests` le haghaidh calabrúcháin roimh aon rolladh amach blocála. - -Is diagnóis réitigh amháin iad ath-easpórtálacha bairille agus iompórtálacha dinimiciúla; ní chruthaíonn siad toradh blocála riamh. Tá eisceachtaí athbhreithnithe in +Comhlánaíonn `.github/workflows/quality.yml` CI ar PRanna main/release, brúite chuig +brainsí cosanta, seoladh agus grúpaí cumaisc. Úsáideann PRanna seiceálacha tapa scagtha +de réir conairí. Baineadh an tógáil dhúblach a bhí díchumasaithe go buan; fanann na +fíorsheiceálacha tógála/pacáistithe/tosaithe in CI. + +| Jab | Raon feidhme | Blocáil | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `Docs Gates (fast-path)` | PRanna do dhoiciméid/do chód; tagairtí do dhoiciméid API agus gach doiciméad | Tá | +| `Fast Quality Gates` | PRanna cóid; seiceálacha statacha, seiceáil cineálacha, seiceáil cineálacha an deais, tástálacha aonaid a ndearnadh difear dóibh | Tá | +| `Forgotten sibling tests` | PRanna cóid; modúil athraithe arna rianú chuig tomhaltóirí statacha agus tástálacha siblíneacha féideartha; tuairiscítear conairí bairille agus iompórtála dinimiciúla mar dhiagnóisic chomhairleach, le heisceachtaí tagartha ón liosta ceadaithe | **Comhairleach** | +| `Vitest (fast-path)` | PRanna cóid; sraith thapa vitest | Tá | +| `Unit Tests fast-path` | PRanna cóid; sraith aonad 4-shard | Tá | +| `No new ESLint warnings` | PRanna cóid; cosaint lint a chuireann sochtaí san áireamh | Tá, forcanna san áireamh | +| `Merge integrity (changelog + generated skills)` | PRanna nach dréachtaí iad; changelog agus sioncronú scileanna ginte | Tá, forcanna san áireamh | + +#### Tuairisc ar thástálacha siblíneacha dearmadta + +Athúsáideann `npm run check:forgotten-sibling-tests` an réiteoir iompórtála taobh thiar +den mhapa tionchair tástála. I gcás gach modúil táirgeachta athraithe, tuairiscíonn sé +slabhraí cinntitheacha `changed module/symbol -> static consumer -> candidate sibling test` +nuair nach bhfuil an tástáil fhéideartha i ndifear na hiarrata tarraingthe. Coinnítear an +achoimre Markdown agus an toradh JSON mar dhéantán sreafa oibre +`forgotten-sibling-tests` lena gcalabrú sula ndéantar aon rolladh amach blocála. + +Is diagnóisic réitigh amháin iad athonnmhairí bairille agus iompórtálacha dinimiciúla; ní chruthaíonn siad toradh blocála riamh. Coinnítear eisceachtaí athbhreithnithe in `config/quality/forgotten-sibling-allowlist.json`. Ní mór do gach iontráil an tomhaltóir agus an tástáil iarrthóra a ainmniú, -réasúnaíocht shonrach a thabhairt, agus nasc a dhéanamh le saincheist nó iarratas tarraingthe GitHub. Diúltaítear -d’iontrálacha míchumtha de réir réamhshocraithe. Ní féidir le heisceachtaí tástáil iarrthóra scriosta ná difríocht a chuireann `.skip`/`.todo` leis a cheilt; +réasúnaíocht shonrach a thabhairt, agus nasc a dhéanamh le heisiúint nó iarratas tarraingthe GitHub. Teipeann ar iontrálacha míchumtha +ar bhealach dúnta. Ní féidir le heisceachtaí tástáil iarrthóra scriosta ná diff a chuireann `.skip`/`.todo` leis a cheilt; is leis an ngeata blocála neamhspleách -`check:test-masking` i gcónaí lagú dearbhuithe agus cineálacha eile ceilte. +`check:test-masking` i gcónaí lagú dearbhuithe agus cineálacha eile folaithe. ### Jab: `lint` -Ritear é ar gach PR chuig `main`. Cuireann sé bac ar chumasc má theipeann air. - -| Script (`npm run ...`) | Bailíochtaíonn | Blocála | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Tá leagan Node.js laistigh den raon tacaithe | Tá | -| `check:cycles` | Iompórtálacha ciorclacha — gach modúl `src/` + `open-sse/` | Tá | -| `check:route-validation:t06` | Tá scéimeanna Zod i láthair ar gach ródaire (beartas Shraith 6) | Tá | -| `check:any-budget:t11` | Ní sháraíonn líon `@ts-expect-error // any` an buiséad (catraca Shraith 11) | Tá | -| `check:provider-consistency` | Tá iontráil chomhfhreagrach in `providerRegistry.ts` ag gach soláthraí in `providers.ts` (agus a mhalairt, laistigh den liosta ceadaithe) | Tá | -| `check:model-lifecycle` | Fanann na trí thábla ródaithe a choinnítear de láimh comhsheasmhach leis an léargas saolré atá seiceáilte isteach (#11503): ní thugann `FITNESS_TABLE` (`taskFitness.ts`) scór d'aon aitheantas scortha is féidir le `REGISTRY` a ródú; tá gach sprioc `BUILT_IN_ALIASES` i láthair in `REGISTRY` agus as láthair ón léargas d'aitheantais scortha; déantar gach aitheantas scortha atá fós in `REGISTRY` a chur ar aghaidh nó a liostú in `allowedRetiredInCatalog`; agus níl aon fhoinse ná sprioc de chuid `DEFAULT_DEGRADATION_MAP` marcáilte mar scortha sa léargas sin. Ní chruthaíonn sé seo go bhfuil samhail á soláthar faoi láthair ag seirbhís réamhtheachtach bheo. As líne — déantar comparáid le `config/quality/model-lifecycle.json`, a athnuachan de láimh le `npm run quality:refresh-model-lifecycle` (líonra; níl sé ceangailte le CI). Is raicín laghdaitheach é `allowedRetiredInCatalog`: ná cuir iontráil leis ach amháin má tá saincheist rianaithe ann. | Tá | -| `check:fetch-targets` | Réitíonn gach `fetch("/api/...")` in `src/` ar thaobh an chliaint chuig `route.ts` atá ann i ndáiríre | Tá | -| `check:deps` | Tá gach spleáchas is féidir a shuiteáil le `npm install` thar gach `package.json` sa stór in `dependency-allowlist.json`; cuirtear bratach le pacáistí nua nach bhfuil pionnáilte nó a d'fhéadfadh a bheith slopsquattáilte | Tá | -| `audit:deps` | `npm audit` (fréamh + electron) — gan aon chomhairleoir ardriosca/criticiúil (forluíonn sé le `check:vuln-ratchet` osv; féach Rationalization Backlog) | Tá | -| `check:lockfile` | Sláine `package-lock.json` — clárlann https, haiseanna sláine, gan aon sáruithe óstaigh | Tá | -| `check:licenses` | Liosta ceadaithe ceadúnas SPDX le haghaidh spleáchais táirgthe | Tá | -| `check:tracked-artifacts` | Gan aon déantáin tiomsaithe / naisc shiombalacha `node_modules` geallta (ritear é freisin i réamh-ghealltanas husky; coinnítear an réamh-bhrú éadrom d’aon ghnó — #6716) | Tá | -| `check:ai-attribution` | Gan aon leantóir `Co-Authored-By` ó IS/bot ná buntásc giniúna IS i ngealltanais, teideal ná corp PR — Riail Dhocht #16 (sa lúb tapa-geataí `quality.yml` le haghaidh PR→`release/**` — léann sé pálasta an teagmhais, ní dhéanann sé aon rud lasmuigh de PRanna — agus céim do PR amháin i lint `ci.yml` le haghaidh PR→`main`; chomh maith leis sin, crúca `commit-msg` husky; ceadaítear comhúdair dhaonna; #14436) | -| `check:vitest-exclusions` | Ainmníonn gach eisiamh Vitest saincheist rianaithe agus tá sé le feiceáil in `config/quality/vitest-exclusions.json` (#13204) | Tá | -| `check:file-size` | Ní sháraíonn aon chomhad foinse an uasteorainn de réir iarmhíre (raicín: comhaid mhóra reoite sa liosta `frozen`) | Tá | -| `check:error-helper` | Úsáideann freagraí earráide i seiceadóirí/láimhseálaithe `buildErrorBody()` / `sanitizeErrorMessage()` (Riail Dhocht #12) | Tá | -| `check:migration-numbering` | Tá comhaid SQL imirce uimhrithe go seicheamhach, gan aon bhearnaí ná dúblaigh | Tá | -| `check:public-creds` | Níl aon `client_id`/`client_secret` OAuth litriúil ná eochracha Gréasáin Firebase lasmuigh de `publicCreds.ts` (Riail Dhocht #11) | Tá | -| `check:db-rules` | Níl aon SQL amh lasmuigh de mhodúil `src/lib/db/`; níl aon iompórtálacha bairille ó `localDb.ts` (Rialacha Dochta #2/#5) | Tá | -| `check:known-symbols` | Meaitseálann riteoirí soláthraithe, straitéisí ródúcháin agus aistritheoirí atá cláraithe ina dtáblaí seolta na comhaid ar an diosca — níl aon siombailí dílleachtacha ná neamhdhearbhaithe ann | Tá | -| `check:route-guard-membership` | Aicmítear gach bealach a sheolann próiseas páiste le `isLocalOnlyPath()` (Rialacha Dochta #15/#17) | Tá | -| `check:test-discovery` | Bailíonn riteoir tástála amháin ar a laghad gach comhad `*.test.ts` / `*.spec.ts` sa stór (raicín: ní féidir ach laghdú teacht ar liosta na ndílleachtaí in `test-discovery-baseline.json`) | Tá | -| `check:agent-skills-sync` | Meaitseálann déantáin ghinte agent-skills lena gcatalóg foinseach (gan aon imeacht) | -| `check:provider-asset-provenance` | Tá iontráil bhunáitíochta taifeadta ag lógónna/sócmhainní soláthraithe | -| `lint:json` | Parsálann comhaid chumraíochta JSON agus comhlíonann siad rialacha lint na stórtha | -| `typecheck:core` | Tiomsú TypeScript gan earráidí (rabhaidh chomhairleacha amháin) | Tá | -| `typecheck:noimplicit:core` | `noImplicitAny` docht — dírithe ar an todhchaí; tá anótálacha fós de dhíth ar go leor suíomhanna glaonna atá ann cheana | **Comhairleach** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` teoranta do `src/app/(dashboard)/**` (#7033) — ní chuimsíonn liosta ceadaithe coimeádta 27 gcomhad `typecheck:core` aon TSX de chuid an deais, agus ní dhéanann `next build` seiceáil cineáil air ach oiread (socraíonn `next.config.mjs` `ignoreBuildErrors: true`), mar sin ní raibh aischéimnithe aitheantóirí dílleachtacha ann (#6625/#6909) le feiceáil ag CI. Déantar difreálacha i gcoinne bonnlíne reoite comhairimh de réir comhaid/de réir chóid TS (`config/quality/dashboard-typecheck-baseline.json`, an patrún céanna forfheidhmithe seanda agus atá ag `check:known-symbols`) — ní theipeann ar an ngeata ach de bharr earráidí NUA sa bhreis ar an gcomhaireamh bonnlínithe; ísligh de réir a chéile é le `--update` nuair a cheartaítear earráid atá ann cheana. | Tá | +Ritear é ar gach PR chuig `main`. Cuireann teip bac ar chumasc. + +| Script (`npm run ...`) | Bailíochtaíonn | Blocála | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Tá an leagan de Node.js laistigh den raon tacaithe | Tá | +| `check:cycles` | Iompórtálacha ciorclacha ar fud `src/` + `open-sse/` ar fad (bunaithe ar AST, `paths` tsconfig réitithe). Lom = comhairleach, liostaíonn sé na timthriallta. Cuireann `check:cycles:ratchet` (an ceann a ritheann CI) bac nuair a sháraíonn an líon an uasteorainn `metrics.cycles` in `quality-baseline.json` — 14 faoi láthair, `direction: down`, mar sin ní féidir leis ach titim (#15159 G-01/G-02) | Tá (raicín) | +| `check:route-validation:t06` | Scéimeanna Zod i láthair ar gach bealach (beartas Shraith 6) | Tá | +| `check:any-budget:t11` | Ní sháraíonn líon `@ts-expect-error // any` an buiséad (catraca Shraith 11) | Tá | +| `check:provider-consistency` | Tá iontráil chomhfhreagrach in `providerRegistry.ts` ag gach soláthraí in `providers.ts` (agus a mhalairt, laistigh den liosta ceadaithe) | Tá | +| `check:model-lifecycle` | Fanann na trí thábla ródaithe a choinnítear de láimh comhsheasmhach leis an léargas saolré a seiceáladh isteach (#11503): ní thugann `FITNESS_TABLE` (`taskFitness.ts`) scór d'aon aitheantas scortha is féidir le `REGISTRY` a ródú; tá gach sprioc `BUILT_IN_ALIASES` i láthair in `REGISTRY` agus as láthair ón léargas ar aitheantais scortha; cuirtear gach aitheantas scortha atá fós in `REGISTRY` ar aghaidh nó liostaítear é in `allowedRetiredInCatalog`; agus níl aon fhoinse ná sprioc `DEFAULT_DEGRADATION_MAP` marcáilte mar scortha sa léargas sin. Ní chruthaíonn sé seo go bhfuil samhail á soláthar faoi láthair ag réamhsheirbhís bheo. As líne — déantar comparáid le `config/quality/model-lifecycle.json`, a athnuachan de láimh le `npm run quality:refresh-model-lifecycle` (líonra; níl sé comhtháite le CI). Is raicín laghdaithe é `allowedRetiredInCatalog`: ná cuir iontráil leis ach amháin má tá saincheist rianaithe ann. | Tá | +| `check:fetch-targets` | Réitíonn gach `fetch("/api/...")` i `src/` ar thaobh an chliaint chuig `route.ts` atá ann i ndáiríre | Tá | +| `check:deps` | Tá gach spleáchas is féidir a shuiteáil le `npm install`, ar fud gach `package.json` sa stór, in `dependency-allowlist.json`; cuirtear bratach le pacáistí nua nach bhfuil pionnáilte nó a bhfuil ainmneacha mealltacha orthu | Tá | +| `audit:deps` | `npm audit` (fréamh + electron) — gan aon chomhairleacht ardriosca/chriticiúil (forluíonn sé le `check:vuln-ratchet` de chuid osv; féach Riaráiste an Réasúnaithe) | Tá | +| `check:lockfile` | Sláine `package-lock.json` — clárlann https, hais sláine, gan aon sáruithe óstach | Tá | +| `check:licenses` | Liosta ceadaithe ceadúnas SPDX do spleáchais táirgthe | Tá | +| `check:tracked-artifacts` | Gan déantáin tógála / siomnaisc `node_modules` tiomnaithe (ritear é freisin i réamh-thiomnú husky; coinnítear an réamhbhrú éadrom d’aon ghnó — #6716) | Tá | +| `check:ai-attribution` | Gan leantóir `Co-Authored-By` ó IS/róbat ná buntásc giniúna IS i dtiomnúcháin, teideal ná corp PR — Riail Dhocht #16 (sa lúb mearthairsí `quality.yml` do PR→`release/**` — léann sé pálasta an teagmhais, agus ní dhéanann sé aon ní lasmuigh de PRanna — agus céim do PRanna amháin i lint `ci.yml` do PR→`main`; chomh maith leis sin, crúca `commit-msg` husky; ceadaítear comhúdair dhaonna; #14436) | +| `check:vitest-exclusions` | Ainmníonn gach eisiamh Vitest saincheist rianaithe agus tá sé le feiceáil in `config/quality/vitest-exclusions.json` (#13204) | Tá | +| `check:file-size` | Ní sháraíonn aon chomhad foinseach an uasteorainn de réir iarmhíre (raicín: comhaid mhóra reoite sa liosta `frozen`) | Tá | +| `check:error-helper` | Úsáideann freagraí earráide i seiceadóirí/láimhseálaithe `buildErrorBody()` / `sanitizeErrorMessage()` (Riail Dhocht #12) | Tá | +| `check:migration-numbering` | Tá comhaid SQL imirce uimhrithe go seicheamhach, gan bearnaí ná dúblaigh | Tá | +| `check:public-creds` | Níl aon `client_id`/`client_secret` OAuth ná eochracha Gréasáin Firebase litriúla lasmuigh de `publicCreds.ts` (Riail Dhocht #11) | Tá | +| `check:db-rules` | Níl aon SQL amh lasmuigh de mhodúil `src/lib/db/`; níl aon allmhairí bairille ó `localDb.ts` (Rialacha Dochta #2/#5) | Tá | +| `check:known-symbols` | Meaitseálann na riteoirí soláthraí, na straitéisí ródúcháin, agus na haistritheoirí atá cláraithe ina dtáblaí seolta na comhaid ar diosca — níl aon siombailí dílleachtacha ná neamhdhearbhaithe ann | Tá | +| `check:route-guard-membership` | Tá gach bealach a thionscnaíonn próiseas linbh aicmithe ag `isLocalOnlyPath()` (Rialacha Dochta #15/#17) | Tá | +| `check:test-discovery` | Bailíonn reathaí tástála amháin ar a laghad gach comhad `*.test.ts` / `*.spec.ts` sa stór (raicín: ní féidir ach laghdú teacht ar liosta na gcomhad dílleachtach in `test-discovery-baseline.json`) | Tá | +| `check:agent-skills-sync` | Meaitseálann déantáin ghinte agent-skills a gcatalóg fhoinseach (gan aon imeacht) | +| `check:provider-asset-provenance` | Tá iontráil bhunáite taifeadta ag lógónna/sócmhainní soláthraithe | +| `lint:json` | Parsálann comhaid chumraíochta JSON agus comhlíonann siad rialacha lint an taisclainne | +| `typecheck:core` | Tiomsú TypeScript gan earráidí (rabhaidh chomhairleacha amháin) | Tá | +| `typecheck:noimplicit:core` | `noImplicitAny` docht — ag féachaint chun cinn; tá anótálacha fós de dhíth ar go leor suíomhanna glao réamhreatha | **Comhairleach** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` teoranta do `src/app/(dashboard)/**` (#7033) — ní chuimsíonn liosta ceada coimeádta 27 gcomhad `typecheck:core` aon TSX de chuid an deais, agus ní dhéanann `next build` seiceáil cineáil air ach oiread (socraíonn `next.config.mjs` `ignoreBuildErrors: true`), mar sin ní raibh cúlchéimnithe aitheantóirí dílleachtaithe ann (#6625/#6909) infheicthe ag CI. Déantar difreálacha i gcoinne bunlíne reoite comhairimh de réir comhaid/de réir cód TS (`config/quality/dashboard-typecheck-baseline.json`, an patrún céanna forfheidhmithe seanaimsearthachta agus atá ag `check:known-symbols`) — ní theipeann ar an ngeata ach amháin de bharr earráidí NUA os cionn an chomhairimh bhunlíne; ísligh an raicín le `--update` nuair a shocraítear earráid réamhreatha. | Tá | ### Jab: `quality-gate` -Ritheann sé tar éis `test-coverage`. Cuireann sé bac ar chumasc má theipeann air. +Ritear é tar éis `test-coverage`. Cuireann teip bac ar chumasc. -| Script | Bailíochtaíonn sé | Blocála | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `quality:collect` | Gineann sé `quality-metrics.json` (líon rabhaidh ESLint, clúdach ón tuairisc chumaiscthe maidir le codanna) | Tá (roimh an raicín) | -| `quality:ratchet` | Níor chúlaigh aon mhéadracht in `quality-baseline.json` (rabhaidh ESLint ≤ bonnlíne; clúdach ≥ bonnlíne) | Tá | -| `check:duplication` | Ní sháraíonn dúbláil cóid (jscpd@4) an bhonnlíne in `quality-baseline.json` | Tá | -| `check:complexity` | Ní sháraíonn castacht chioglamatach ar leibhéal comhaid an uasteorainn (`complexity` croíúil ESLint + `max-lines-per-function`) | Tá | -| `check:cognitive-complexity` | Raicín castachta cognaíche (`eslint-plugin-sonarjs`) — pas ESLint ar leith; ritheann CI an dá cheann cumaiscthe mar an chéim aonair `check:complexity-ratchets` | Tá | -| `check:dead-code` | Ní théann raicín easpórtálacha / comhad neamhúsáidte (knip) in olcas i gcomparáid leis an mbonnlíne | Tá | -| `check:compression-budget` | Buiséad tagarmharcála comhbhrúite — ní mór nach rachadh íosleibhéil choigilteas comharthaí gach innill in olcas | Tá | -| `check:type-coverage` | Ní théann an raicín céatadáin chlóscríofa (`type-coverage`) in olcas; cuimsíonn sé `typecheck:noimplicit:core` den chuid is mó | Tá | -| `check:codeql-ratchet` | Ní théann líon foláireamh oscailte CodeQL in olcas (léann sé trí `gh api`; scipeáil mhaiseach gan chomhartha) — minicíocht athnuachana agus truicear láimhe: féach "Raicín CodeQL" thíos | Tá | +| Script | Bailíochtaíonn | Blocála | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `quality:collect` | Astaíonn `quality-metrics.json` (líon rabhaidh ESLint, clúdach ón tuairisc chomhcheangailte shardanna) | Tá (roimh an ratchet) | +| `quality:ratchet` | Níor chúlaigh aon mhéadracht in `quality-baseline.json` (rabhaidh ESLint ≤ bunlíne; clúdach ≥ bunlíne) | Tá | +| `check:duplication` | Ní sháraíonn dúbláil cóid (jscpd@4) an bhunlíne in `quality-baseline.json` | Tá | +| `check:complexity` | Ní sháraíonn castacht chioclamatach ar leibhéal comhaid an uasteorainn (croí-rialacha ESLint `complexity` + `max-lines-per-function`) | Tá | +| `check:cognitive-complexity` | Ratchet castachta cognaíche (`eslint-plugin-sonarjs`) — pas ESLint ar leith; ritheann CI an dá cheann cumaiscthe mar an chéim aonair `check:complexity-ratchets` | Tá | +| `check:dead-code` | Ní théann ratchet easpórtálacha / comhad neamhúsáidte (knip) in olcas i gcomparáid leis an mbunlíne | Tá | +| `check:compression-budget` | Buiséad tagarmhairc comhbhrúite — ní ceadmhach d’íosleibhéil choigilteas comharthaí in aghaidh an innill dul in olcas | Tá | +| `check:type-coverage` | Ní théann an ratchet céatadáin chlóscríofa (`type-coverage`) in olcas; tagann `typecheck:noimplicit:core` faoina raon den chuid is mó | Tá | +| `check:codeql-ratchet` | Ní théann líon na bhfoláireamh oscailte CodeQL in olcas (léann sé trí `gh api`; scipeáil shéimh gan chomhartha) — le haghaidh minicíocht athnuachana agus truicear láimhe: féach "Ratchet CodeQL" thíos | Tá | ### Jab: `quality-extended` -Is jab comhairleach é an jab iomlán (`continue-on-error: true`). Ritheann na raicíní atá bunaithe ar npm -i ndáiríre; suiteálann na scanóirí seachtracha trí `gh release download` agus scipeálann siad iad féin (cód scortha 0) -nuair atá dénártha fós in easnamh. +Is comhairleach é an jab iomlán (`continue-on-error: true`). Ritheann na ratchets bunaithe ar npm +i gceart; suiteálann na scanóirí seachtracha trí `gh release download` agus scipeálann siad iad féin (cód scoir 0) +nuair atá dénárthach fós in easnamh. -| Script | Bailíochtaíonn sé | Blocála | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:circular-deps` | Gan aon spleáchais chiorclacha (dpdm) | **Comhairleach** | -| `check:bundle-size` | Ní sháraíonn méid an bheartáin an uasteorainn | **Comhairleach** | -| `check:secrets` | Scanadh rúin (gitleaks) — scipeálann sé má tá an dénártha in easnamh | **Comhairleach** | -| `check:vuln-ratchet` | Ní théann leochaileachtaí spleáchais (osv-scanner) in olcas — scipeálann sé má tá an dénártha in easnamh | **Comhairleach** | -| `check:workflows` | Lintiú sreafa oibre (actionlint + zizmor); teipeann ar scanóirí in easnamh/briste, tuairiscí neamhbhailí nó bonnlíne raicín in easnamh mar INCOMPLETE. Leanann torthaí bailí an polasaí docht/comhairleach/raicín roghnaithe | Rith de dhíth; raicín zizmor blocála in CI | -| `check:openapi-breaking` | Athruithe bristeacha ar chonradh an API phoiblí (`openapi.yaml`) i gcomparáid leis an mbrainse bonn (oasdiff) — gineann sé `openapiBreaking=N`; scipeálann sé má tá oasdiff in easnamh nó mura féidir an tsonraíocht bhonn a réiteach | **Comhairleach** | +| Script | Bailíochtaíonn | Blocála | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:circular-deps` | Gan aon spleáchais chiorclacha (dpdm) | **Comhairleach** | +| `check:bundle-size` | Ní sháraíonn méid an bheartáin an uasteorainn | **Comhairleach** | +| `check:secrets` | Scanadh rúin (gitleaks) — scipeálann sé má tá an dénárthach in easnamh | **Comhairleach** | +| `check:vuln-ratchet` | Ní théann leochaileachtaí spleáchais (osv-scanner) in olcas — scipeálann sé má tá an dénárthach in easnamh | **Comhairleach** | +| `check:workflows` | Lintiú sreafa oibre (actionlint + zizmor); má bhíonn scanóirí in easnamh/briste, tuairiscí neamhbhailí nó bunlíne ratchet in easnamh, teipeann sé mar INCOMPLETE. Leanann torthaí bailí an polasaí docht/comhairleach/ratchet roghnaithe | Rith riachtanach; ratchet zizmor blocála in CI | +| `check:openapi-breaking` | Athruithe bristeacha ar chonradh poiblí an API (`openapi.yaml`) i gcomparáid leis an mbrainse bonn (oasdiff) — astaíonn `openapiBreaking=N`; scipeálann sé má tá oasdiff in easnamh nó mura féidir an tsonraíocht bhonn a réiteach | **Comhairleach** | ### Jab: `docs-sync-strict` -Ritheann sé ar gach PR chuig `main`. Cuireann sé bac ar chumasc má theipeann air. - -| Script | Bailíochtaíonn sé | Blocála | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `check:docs-all` | Meitigeata a ritheann na 6 fho-gheata thíos go seicheamhach | Tá | -| ↳ `check:docs-sync` | Comhsheasmhacht leaganacha CHANGELOG / OpenAPI / llm.txt | Tá | -| ↳ `check:docs-counts` | Tá na comhairimh sa phrós (líon soláthraithe, líon aistrithe, etc.) laistigh d'fhuinneog raicéid na bhfíorchomhaireamh | Tá | -| ↳ `check:env-doc-sync` | Tá gach athróg timpeallachta in `.env.example` doiciméadaithe i dtábla doiciméad, agus a mhalairt | Tá | -| ↳ `check:deprecated-versions` | Níl aon teaghráin leagain dhímheasta sna doiciméid | Tá | -| ↳ `check:doc-links` | Réitíonn naisc inmheánacha markdown sna doiciméid chuig fíorchomhaid (foirm `[text]`/`(path)`) | Tá | -| ↳ `check:fabricated-docs` | Tá na bealaí, na hathróga timpeallachta, na horduithe CLI, ainmneacha na gcrúcaí, agus na conairí comhaid a luaitear sna doiciméid sa bhunachar cód. Geata docht trí `--strict`; teip bhog gan an bhratach. | Tá (trí `--strict` in CI) | -| `check:cli-i18n` | Tá teaghráin orduithe CLI i ngach comhad logchaighdeáin i18n | Tá | -| `check:openapi-coverage` | Clúdaíonn sonraíocht OpenAPI íosleibhéal raicéadaithe de na fíorbhealaí ar a laghad | Tá | -| `check:openapi-security-tiers` | Tá nótaí na sraitheanna slándála in `openapi.yaml` comhsheasmhach le haicmithe `routeGuard.ts` | **Comhairleach** | -| `check:openapi-routes` | Réitíonn gach conair in `openapi.yaml` chuig fíorchomhad `route.ts` (frithshamhlú) | Tá | -| `check:docs-symbols` | Réitíonn gach tagairt `/api/...` in `docs/**/*.md` chuig fíorchomhad `route.ts` (frithshamhlú) | Tá | -| `i18n translation drift` | Eochracha neamhaistrithe i gcomhaid logchaighdeáin i18n — rabhadh amháin | **Comhairleach** | - -### Tasc: `i18n-ui-coverage` - -| Script | Bailíochtaíonn sé | Blocála | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | -| `check-ui-keys-coverage` (inlíne) | Tá clúdach eochracha i18n an chomhéadain úsáideora ≥ 65% | Tá | -| `check-ui-value-drift` (inlíne) | Ní fhágann **luach** Béarla athscríofa aon aistriúchán seanchaite ina dhiaidh | Tá | -| `check-new-key-coverage` (inlíne) | Aistrítear eochair **nua** Bhéarla i ngach logchaighdeán — diúltaítear do mharcóir `__MISSING__:` | Tá | -| `check-translation-ratio` | Ní mór nach sáródh cóimheas na bhfíoraistriúchán de réir logchaighdeáin (duilleoga atá comhionann leis an mBéarla / ionadchoinneála / ar iarraidh lasmuigh den liosta ceadaithe) `config/quality/i18n-translation-baseline.json` + lamháil | **Comhairleach** | - -Teastaíonn `fetch-depth: 0` — déanann an geata athraithe luacha difríocht idir `en.json` agus an bonn cumaisc. - -#### `check-ui-value-drift` — geata aistriúchán seanchaite - -Aimsíonn sé an t-aon aischéimniú i18n nach féidir leis na geataí eile a fheiceáil ó thaobh struchtúir de: athscríobhtar luach Béarla -agus fágtar na haistriúcháin a díorthaíodh ón mBéarla _roimhe seo_ ina ndiaidh, sa chaoi go -leanann úsáideoirí nach bhfuil Béarla acu orthu ag léamh téacs atá muiníneach ach atá mícheart anois. - -Seoladh é seo i ndáiríre. Athscríobhadh `oauthModal.googleOAuthWarning` nuair a cuireadh an -cúntóir logála isteach Antigravity leis (#5203); choinnigh **39 as 43 logchaighdeán** téacs a dúirt le hoibreoirí “an -URL iomlán a chóipeáil agus é a ghreamú thíos” — sreabhadh nach féidir a chur i gcrích don soláthraí sin. Níor -tugadh faoi deara é go dtí #8463 mar: - -- ní líonann `sync-ui-keys` siar ach eochracha atá **in easnamh**, agus ní cinn atá **seanchaite** riamh; -- comhaireann `check-ui-keys-coverage` _láithreacht_ eochrach, mar sin áirítear aistriúchán seanchaite mar cheann atá clúdaithe; -- rianaíonn `check-translation-drift` na scátháin doiciméadúcháin `docs/i18n//**.md` — - ní léann sé `src/i18n/messages/*.json` riamh. Tá sé blocála sa tasc `docs-sync-strict` ó rinneadh - an t-athshioncronú 2026-09: cuir croídhoiciméad in eagar → `npm run i18n:run -- --files=` (ar leibhéal rannáin, saor). - -**Feasach ar dhifir, gan bonnlíne mar thaca.** Déanann sé `en.json` ag bonn na cumaisce a chur i gcomparáid leis an -gcrann oibre; i gcás gach eochrach ar athraíodh a luach Béarla, tá aon logchaighdeán a bhfuil -aistriúchán gan athrú aige fós as dáta. Déanann sé seo **fiachas atá ann cheana a reo** d’aon ghnó — ní féidir le difríocht -a thaispeáint cén seanleagan Béarla as ar tháinig aistriúchán seanbhunaithe, mar sin ní dhéanann an geata breithiúnas -ach ar a mbaineann an t-athrú reatha leis. Chosnódh an rogha eile (bonnlíne haise in aghaidh na heochrach) -comhad ginte ~600 KB, 3× níos mó ná an bhonnlíne reatha is mó, agus bheadh sé á athrú ar gach PR i18n. +Ritheann sé ar gach PR chuig `main`. Cuireann teip bac ar chumasc. + +| Script | Bailíochtaíonn sé | Blocála | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `check:docs-all` | Meiteachomhla a ritheann na 6 fho-chomhla thíos go seicheamhach | Tá | +| ↳ `check:docs-sync` | Comhsheasmhacht leaganacha idir CHANGELOG / OpenAPI / llm.txt | Tá | +| ↳ `check:docs-counts` | Tá na háirimh sa phrós (líon soláthraithe, líon ascnaimh, srl.) laistigh d'fhuinneog raicéid na bhfíor-áireamh | Tá | +| ↳ `check:env-doc-sync` | Tá gach athróg timpeallachta in `.env.example` doiciméadaithe i dtábla doiciméadachta, agus a mhalairt | Tá | +| ↳ `check:deprecated-versions` | Níl aon teaghráin leagain atá dímholta sna doiciméid | Tá | +| ↳ `check:doc-links` | Réitíonn naisc inmheánacha markdown sna doiciméid chuig fíorchomhaid (foirm `[text]`/`(path)`) | Tá | +| ↳ `check:fabricated-docs` | Tá na bealaí, na hathróga timpeallachta, na horduithe CLI, ainmneacha na gcrúcaí, agus na conairí comhaid a luaitear sna doiciméid ann sa bhunachar cód. Comhla chrua trí `--strict`; teip bhog gan an bhratach. | Tá (trí `--strict` in CI) | +| `check:cli-i18n` | Tá teaghráin orduithe CLI i láthair i ngach comhad logchaighdeáin i18n | Tá | +| `check:openapi-coverage` | Clúdaíonn sonraíocht OpenAPI ar a laghad íoslíon fíorbhealaí atá socraithe le raicéad | Tá | +| `check:openapi-security-tiers` | Tá anótálacha na sraitheanna slándála in `openapi.yaml` comhsheasmhach le haicmithe `routeGuard.ts` | **Comhairleach** | +| `check:openapi-routes` | Réitíonn gach conair in `openapi.yaml` chuig fíorchomhad `route.ts` (frith-shiamsú) | Tá | +| `check:docs-symbols` | Réitíonn gach tagairt `/api/...` in `docs/**/*.md` chuig fíorchomhad `route.ts` (frith-shiamsú) | Tá | +| `i18n translation drift` | Eochracha neamhaistrithe i gcomhaid logchaighdeáin i18n — rabhadh amháin | **Comhairleach** | + +### Jab: `i18n-ui-coverage` + +| Script | Bailíochtaíonn sé | Blocála | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | +| `check-ui-keys-coverage` (inlíne) | Tá clúdach eochracha i18n an chomhéadain úsáideora ≥ 65% | Tá | +| `check-ui-value-drift` (inlíne) | Ní fhágann **luach** Béarla athscríofa aon aistriúchán seanchaite ina dhiaidh | Tá | +| `check-new-key-coverage` (inlíne) | Aistrítear eochair **nua** Bhéarla i ngach logchaighdeán — diúltaítear do mharcóir `__MISSING__:` | Tá | +| `check-translation-ratio` | Ní ceadmhach don chóimheas fíoraistriúcháin in aghaidh an logchaighdeáin (ionann leis an mBéarla / ionadchoinneálaí / iontrálacha in easnamh lasmuigh den liosta ceadaithe) dul thar `config/quality/i18n-translation-baseline.json` + lamháltas | **Comhairleach** | + +Teastaíonn `fetch-depth: 0` — déanann an chomhla um athrú luacha difríocht idir `en.json` agus an bonn cumaisc. + +#### `check-ui-value-drift` — comhla aistriúchán seanchaite + +Aimsíonn sé seo an t-aon aischéimniú i18n nach féidir leis na comhlaí eile a fheiceáil ó thaobh struchtúir de: athscríobhtar luach Béarla +agus fanann na haistriúcháin a díorthaíodh ón mBéarla _roimhe seo_ ina ndiaidh, sa chaoi go +leanann úsáideoirí nach labhraíonn Béarla orthu ag léamh téacs muiníneach atá mícheart anois. + +Seoladh é seo i ndáiríre. Athscríobhadh `oauthModal.googleOAuthWarning` nuair a cuireadh an cúntóir logála isteach Antigravity +i bhfeidhm (#5203); choinnigh **39 as 43 logchaighdeán** téacs a dúirt le hoibreoirí "an URL iomlán a chóipeáil +agus é a ghreamú thíos" — sreabhadh nach féidir a chríochnú don soláthraí sin. Níor tugadh +faoi deara é go dtí #8463 mar: + +- ní dhéanann `sync-ui-keys` ach eochracha atá **as láthair** a líonadh siar, ní eochracha atá **seanchaite**; +- áiríonn `check-ui-keys-coverage` _láithreacht_ eochrach, mar sin áirítear aistriúchán seanchaite mar cheann clúdaithe; +- rianaíonn `check-translation-drift` scátháin doiciméadachta `docs/i18n//**.md` — + ní léann sé `src/i18n/messages/*.json` riamh. Blocála sa jab `docs-sync-strict` ó rinneadh an + t-athshioncronú 2026-09: cuir croídhoiciméad in eagar → `npm run i18n:run -- --files=` (ar leibhéal rannáin, saor). + +**Feasach ar dhif, gan tacaíocht ó bhonnlíne.** Déanann sé comparáid idir `en.json` ag an gcomhbhonn cumaisc agus an +crann oibre; i gcás gach eochrach ar athraigh a luach Béarla, tá aon teanga ina bhfuil +aistriúchán nár athraíodh fós as dáta. Déanann sé seo **an fiachas atá ann cheana a reo** d’aon ghnó — ní féidir le dif +a nochtadh cén seanleagan Béarla as ar tháinig aistriúchán seanbhunaithe, mar sin ní dhéanann an geata breithiúnas +ach ar a mbaineann an t-athrú reatha leis. Bheadh comhad ginte ~600 KB ag teastáil ón rogha eile +(bonnlíne haise in aghaidh na heochrach), 3× níos mó ná an bhonnlíne is mó atá ann, agus é á athrú ar gach PR i18n. Tá dhá bhealach ann chun é a shásamh: @@ -221,7 +229,7 @@ Tá dhá bhealach ann chun é a shásamh: 2. socraigh iad mar `__MISSING__:` — soláthraíonn an t-am rite an Béarla ceartaithe ansin (`src/i18n/request.ts::deepMergeFallback`, #7258) agus cuirtear an eochair sa scuaine le haistriú. -Má tháinig athrú ar **bhrí** na teaghráin, is fearr **an eochair a athainmniú**: ní féidir le heochair nua +Má d’athraigh **brí** na teaghráin, is fearr **an eochair a athainmniú**: ní féidir le heochair nua aistriúchán as dáta a fháil le hoidhreacht. Sin é an patrún a d’úsáid #8463. ```bash @@ -230,48 +238,48 @@ npm run i18n:check-value-drift:warn # tuairisc amháin BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Scoireann sé le 0 agus `SKIP reason=base-unresolved` nuair nach féidir an chatalóg bhonnlíne a léamh (clón éadomhain -gan an tagairt bhonnlíne), ar aon dul le `check-openapi-breaking`. +Filleann sé le cód scoir 0 agus `SKIP reason=base-unresolved` nuair nach féidir an bunchatalóg a léamh (clón éadomhain +gan an buntagairt), ar aon dul le `check-openapi-breaking`. ### Jab: `i18n` -Maitrís iomlán bailíochtaithe i18n (jab amháin in aghaidh an logchaighdeáin). Is jab comhairleach é an jab iomlán. +Maitrís iomlán bailíochtaithe i18n (jab amháin in aghaidh na teanga). Is comhairleach an jab iomlán. -| Script | A bhailíochtaíonn sé | Bacach | -| ------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------- | -| `validate_translation.py quick` | Iomláine an aistriúcháin in aghaidh an logchaighdeáin | **Comhairleach** (`continue-on-error: true` ar an jab iomlán) | +| Script | Bailíochtaíonn sé | Blocáil | +| ------------------------------- | --------------------------------------------- | ------------------------------------------------------------- | +| `validate_translation.py quick` | Iomláine an aistriúcháin in aghaidh na teanga | **Comhairleach** (`continue-on-error: true` ar an jab iomlán) | ### Jab: `pr-test-policy` -Ní ritheann sé ach ar iarratais tarraingthe. +Ritheann sé ar iarratais tarraingthe amháin. -| Script | A bhailíochtaíonn sé | Bacach | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -| `check:pr-test-policy` | Ní mór do PRanna a athraíonn cód táirgeachta in `src/`, `open-sse/`, `electron/`, nó `bin/` tástálacha a chur san áireamh nó a nuashonrú (Riail Dhocht #8) | Tá | -| `check:test-masking` | Ní laghdaíonn comhaid tástála athraithe líon glan na ndearbhuithe ná ní chuireann siad tátaleolaíochtaí `assert.ok(true)` leis | Tá | -| `check:pr-evidence` | Luann corp an PR fianaise tástála/VPS don athrú (cuireann sé Riail Dhocht #18 i bhfeidhm go meicniúil trí phrós an PR a chuardach le grep — leochaileach, féach an Riaráiste) | Tá | +| Script | Bailíochtaíonn sé | Blocáil | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `check:pr-test-policy` | Ní mór tástálacha a chur san áireamh nó a nuashonrú i PRanna a athraíonn cód táirgthe in `src/`, `open-sse/`, `electron/`, nó `bin/` (Riail Dhocht #8) | Tá | +| `check:test-masking` | Ní laghdaíonn comhaid tástála athraithe líon glan na ndearbhuithe ná ní chuireann siad athráiteolaíochtaí `assert.ok(true)` leis | Tá | +| `check:pr-evidence` | Luann corp an PR fianaise tástála/VPS don athrú (cuireann sé Riail Dhocht #18 i bhfeidhm go meicniúil trí phrós an PR a chuardach le grep — sobhriste, féach Backlog) | Tá | ### Jab: `test-vitest` -Ritheann sé tar éis `build`. Cuireann teip bac ar chumasc. +Ritheann sé tar éis `build`. Cuireann sé bac ar chumasc má theipeann air. -| Sraith | A bhailíochtaíonn sí | Bacach | -| ---------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Freastalaí MCP (110 uirlis), autoCombo, taisce — reathaí vitest | Tá | -| `test:vitest:ui` | Tástálacha comhpháirteanna UI — reathaí vitest | **Bacach** — eisiatar teipeanna atá ann cheana go sainráite in `vitest.config.ts`; teipeann ar an jab de bharr teipeanna nua | +| Sraith | Bailíochtaíonn sé | Blocáil | +| ---------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Freastalaí MCP (110 uirlis), autoCombo, taisce — riteoir vitest | Tá | +| `test:vitest:ui` | Tástálacha comhpháirteanna UI — riteoir vitest | **Blocálach** — fágtar teipeanna a bhí ann cheana as an áireamh go sainráite in `vitest.config.ts`; cuireann teipeanna nua an jab ó rath | ### Sreafaí oibre oíche (sceidealaithe, comhairleach) -Ritheann siad seo de réir sceideal cron (agus `workflow_dispatch`), agus ní ritheann siad ar PRanna riamh. Tá siad uile comhairleach. +Ritheann siad seo de réir sceideal cron (agus `workflow_dispatch`), ach ní ritheann siad ar PRanna riamh. Tá siad uile comhairleach. -| Sreabhadh oibre | A bhailíochtaíonn sé | Bacach | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | tástálacha airí fast-check le síol randamach + líon ard rití | **Comhairleach** | -| `nightly-resilience` | geata fáis carn, instealladh lochtanna anordúla, ualach/maos k6 | **Comhairleach** | -| `nightly-llm-security` | cosaint insteallta promptfoo (mód blocála) + tóireadóirí garak (scipeáilte gan rún soláthraí) | **Comhairleach** | -| `nightly-schemathesis` | doiléiriú chonradh OpenAPI (schemathesis) i gcoinne OmniRoute beo ag úsáid `docs/openapi.yaml` — nochtann sé sáruithe sonraíochta / 500anna gan láimhseáil (Céim 8 B.4) | **Comhairleach** | -| `nightly-mutation` | scór tástála sócháin Stryker thar lána tapa na dtástálacha aonaid — nochtann sóiteáin a mhaireann dearbhuithe laga | **Comhairleach** | -| `nightly-compat` | maitrís chomhoiriúnachta innill Node thar na raonta `engines.node` a dtacaítear leo | **Comhairleach** | +| Sreabhadh oibre | Bailíochtaíonn sé | Blocáil | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `nightly-property` | Tástálacha airí fast-check le síol randamach + líon ard rití | **Comhairleach** | +| `nightly-resilience` | Geata fáis carn, instealladh lochtanna anordúla, ualach/sáithiú k6 | **Comhairleach** | +| `nightly-llm-security` | Cosaint insteallta promptfoo (mód blocála) + tóireadóirí garak (scipeáiltear iad gan rún soláthraí) | **Comhairleach** | +| `nightly-schemathesis` | Doiléiriú conartha OpenAPI (schemathesis) in aghaidh OmniRoute beo ag úsáid `docs/openapi.yaml` — nochtann sé sáruithe sonraíochta / earráidí 500 nár láimhseáladh (Céim 8 B.4) | **Comhairleach** | +| `nightly-mutation` | Scór tástála sócháin Stryker thar an lána tapa aonadtástála — nochtann sócháin a mhaireann dearbhuithe laga | **Comhairleach** | +| `nightly-compat` | Maitrís chomhoiriúnachta innill Node thar na raonta `engines.node` a dtacaítear leo | **Comhairleach** | --- @@ -538,12 +546,12 @@ earráid cheangail a logáil agus ní bhriseann sí seisiúin choíche. ## Riaráiste Cuíchóirithe (athbhreithniú ROI — Céim 9 Tonn 3) -Réitíodh an fardal seo in aghaidh `ci.yml` an 2026-06-17 (fágadh +Réitíodh an fardal seo in aghaidh `ci.yml` ar 2026-06-17 (fágadh `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence` ar lár sa leagan roimhe seo). D’aithin athbhreithniú ROI ar an tacar réitithe na hiarrthóirí cuíchóirithe seo a leanas. **Athruithe meicniúla CI atá sna cumaisc; -is cinntí beartais iad na hathruithe stádais/na baintí atá forchoimeádta don oibreoir.** Níl aon ní thíos +is cinntí beartais iad na haischuir/na baintí atá curtha in áirithe don oibreoir.** Níl aon rud thíos curtha i bhfeidhm fós. **Nach bhfuil doiciméadaithe thuas ach oiread** (comhairleach, comhartha lag): an jab `docs-lint` @@ -552,27 +560,27 @@ curtha i bhfeidhm fós. `quality-baseline.json` ach níl sé ceangailte le raicín blocála in `ci.yml` — tá an mhéadracht ina dílleachta faoi láthair. -### Cumasc / dí-dhúbailt (meicniúil, riosca níos ísle) +### Cumasc / dí-dhúbláil (meicniúil, riosca níos ísle) -Fíoraíodh gach iarrthóir in aghaidh staid bheo na ngeataí an 2026-06-17 (muinín ach fíorú); -tharla sé gur cheil roinnt cumasc “soiléir” fiachas agus **ní** ionadaithe glana iad. +Fíoraíodh gach iarrthóir in aghaidh staid bheo na ngeataí ar 2026-06-17 (muinín ach fíorú); +tharla sé gur fholaigh roinnt cumasc “soiléir” fiachas agus **ní** ionadaithe gan dua iad. -- **Ritheann `check:docs-sync` faoi dhó** — go neamhspleách sa jab `lint` agus arís laistigh de `check:docs-all` (`docs-sync-strict`) agus crúca réamh-commit husky. ✅ **DÉANTA** — baineadh an glao neamhspleách `lint`. -- **Scanadh CVE** — ❌ **NÍ cumasc glan é.** Teipeann `audit:deps` go docht ar aon CVE ard/criticiúil; ní theipeann `check:vuln-ratchet` (osv) ach ar _chúlchéimniú_ i gcomparáid leis an mbonnlíne (1 MODERATE faoi láthair). Séimeantaic éagsúil — dá mbainfí `audit:deps`, chaillfí an geata absalóideach ard/criticiúil. Coinnigh an dá cheann. -- **Aimsiú timthriallta** — ❌ **NÍ cumasc glan é.** Tuairiscíonn `check:circular-deps` (dpdm) **91 timthriall** (sin an fáth a bhfuil sé comhairleach); ní féidir é a ardú go stádas blocála gan iad a réiteach ar dtús, agus tá raon feidhme níos leithne aige ná `check:cycles`, atá glas agus coimeádta. Coinnigh `check:cycles` ina sheiceáil bhlocála; is mír riaráiste ar leith é réiteach na 91 timthriall dpdm. -- **Castacht** — ✅ **DÉANTA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): trasnú ESLint amháin, comhaireamh de réir ruleId ionas go bhfanfaidh bonnlínte cioglómatacha+uaslínte agus cognaíocha neamhspleách; fanann `check:complexity` / `check:cognitive-complexity` aonair ann le haghaidh `--update` áitiúil. -- **Frithshiamsaíocht `/api`** — ✅ **DÉANTA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): fardal FS amháin de `src/app/api`, agus leanann openapi-routes + docs-symbols de bheith ag tuairisciú go neamhspleách; fanann na cinn aonair ann le haghaidh rite áitiúla. -- **Ritheann `check:node-runtime` in 11 jab** — ⚠️ **ROI íseal.** Is riteoir ar leith gach ceann acu agus tógann an tseiceáil <1s; coigilteas iomlán ~10s, ach chaillfí cosaint shaor in aghaidh an jаб dá bharr. Ní fiú an corraí é. +- **Ritheann `check:docs-sync` faoi dhó** — go neamhspleách sa jab `lint` agus arís laistigh de `check:docs-all` (`docs-sync-strict`) agus sa chrúca réamh-churtha husky. ✅ **DÉANTA** — baineadh an glao neamhspleách `lint`. +- **Scanadh CVE** — ❌ **NÍ cumasc glan é.** Teipeann `audit:deps` go crua ar aon CVE ard/criticiúil; ní theipeann `check:vuln-ratchet` (osv) ach ar _aischéimniú_ i gcomparáid leis an mbunlíne (1 MODERATE faoi láthair). Séimeantaic dhifriúil — dá mbainfí `audit:deps`, chaillfí an geata absalóideach ard/criticiúil. Coinnigh an dá cheann. +- **Aimsiú timthriallta** — ✅ **DÉANTA** (#15159 G-01/G-02). Thug an seantéacs anseo “an geata glas, coimeádta” ar `check:cycles` agus thug sé mar údar lena choinneáil blocála gur thuairiscigh `check:circular-deps` (dpdm) 91 timthriall. **Glas bréagach** a bhí sa ghlas sin: rinne `check:cycles` scanadh ar 5 fhochomhadlann (450 comhad), níor mheaitseáil sé ach `import|export … from` statach, agus chaith sé gach sonraitheoir `@/` agus `@omniroute/open-sse/` i leataobh, mar sin ní raibh sé in ann na timthriallta iompórtála dhinimiciúil + ailias a bhí chun tosaigh sa stór a fheiceáil. Ceartaithe: siúlann an geata trí `src` + `open-sse` anois (5023 comhad), bailíonn sé sonraitheoirí ón AST TypeScript (mar sin áirítear `import("…")` ach ní áirítear `typeof import("…")` i suíomh cineáil), agus réitíonn sé `paths` tsconfig. Aimsíonn sé **14** thimthriall, ní 0. Toisc nach féidir 14 thimthriall réamhbheitheacha a cheartú in PR geata, is **raicín** é `check:cycles` anois (`--ratchet`, uasteorainn `metrics.cycles.value = 14` in `quality-baseline.json`, `direction: down`) — blocálann sé aon _aischéimniú_ agus ní féidir leis an gcomhaireamh ach titim. Ritheann CI `npm run check:cycles:ratchet`. Déantar an laghdú de réir a chéile le **A-01**. Fanann `check:circular-deps` (dpdm) comhairleach mar an dara tuairim níos leithne. +- **Castacht** — ✅ **DÉANTA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): siúlóid ESLint amháin, comhaireamh de réir ruleId ionas go bhfanfaidh bunlínte cioglamatacha+uaslínte agus bunlínte cognaíocha neamhspleách; fanann `check:complexity` / `check:cognitive-complexity` aonair le haghaidh `--update` áitiúil. +- **Frithshamhlaíocht `/api`** — ✅ **DÉANTA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): fardal FS amháin de `src/app/api`; leanann openapi-routes + docs-symbols ag tuairisciú go neamhspleách; fanann na cinn aonair le haghaidh rití áitiúla. +- **Ritheann `check:node-runtime` in 11 jab** — ⚠️ **ROI íseal.** Is riteoir ar leith é gach ceann agus tógann an tseiceáil <1s; coigilteas iomlán ~10s, i gcoinne cosantóir saor in aghaidh an jab a chailleadh. Ní fiú an corraí é. - **`typecheck:noimplicit:core` ar lint CI** — ✅ **baineadh den jab lint é** (bhí sé comhairleach le `continue-on-error`); is iad `typecheck:core` + `check:type-coverage` an dromchla cineáil blocála. Coinníodh an script áitiúil. -### Athraigh an stádas / déan cinneadh (beartas oibreora) +### Athraigh / déan cinneadh (beartas oibreora) -- `check:openapi-security-tiers` (comhairleach) — ❌ **NÍ féidir a stádas a athrú go glan.** Críochnaíonn sé le 0 ach tugann sé rabhadh nach bhfuil an nóta `x-loopback-only: true` ar roinnt bealaí `traffic-inspector` faoi `LOCAL_ONLY_API_PREFIXES`. Chun é a fhorfheidhmiú, ní mór na nótaí sin a chur le `openapi.yaml` ar dtús. -- `typecheck:noimplicit:core` (comhairleach) — tá sé ionadaithe den chuid is mó ag an raicín blocála `check:type-coverage`. Athraigh go raicín é nó bain an dara pas iomarcach `tsc`. -- `test:vitest:ui` (**blocála** anois) — tá teipeanna a bhí ann cheana eisiata go sainráite in `vitest.config.ts` le nótaí tráchta rianaithe `// #8618`; cuirfidh teipeanna nua an jab ó rath. -- `check:secrets` (gitleaks, raicín blocála reoite ag 3 dhearbhú bréagacha doiciméadaithe) — cuir na 3 cinn ar an liosta ceadaithe chun 0 a bhaint amach, nó ísligh go stádas comhairleach é. Tá forluí ann le scanadh dúchasach rúin GitHub + `check:public-creds`. -- `check:pr-evidence` (blocála, déanann sé grep ar phrós chorp PR) — riosca ard dearbhuithe bréagacha; lagófaí forfheidhmiú Riail Dhocht #18 dá mbainfí é, mar sin is fíorchinneadh beartais é seo. -- `semgrep` (neamhspleách comhairleach) — tá forluí aige le CodeQL maidir le teaghlaigh OWASP; ceangail a bhonnlíne le raicín nó bain é. +- `check:openapi-security-tiers` (comhairleach) — ❌ **NÍ féidir é a athrú go glan.** Scoireann sé le 0 ach tugann sé rabhadh nach bhfuil an nóta `x-loopback-only: true` ag roinnt bealaí `traffic-inspector` faoi `LOCAL_ONLY_API_PREFIXES`. Chun é a fhorfheidhmiú, ní mór na nótaí sin a chur le `openapi.yaml` ar dtús. +- `typecheck:noimplicit:core` (comhairleach) — tá sé cuimsithe den chuid is mó ag an raicín blocála `check:type-coverage`. Athraigh go raicín é nó bain an dara pas iomarcach `tsc`. +- `test:vitest:ui` (**blocála** anois) — tá teipeanna réamhbheitheacha eisiata go sainráite in `vitest.config.ts` le nótaí tráchta rianaithe `// #8618`; cuireann teipeanna nua an jab ó rath. +- `check:secrets` (gitleaks, raicín blocála reoite ag 3 thoradh bréagacha doiciméadaithe) — cuir na 3 cinn ar an liosta ceadaithe chun 0 a bhaint amach, nó ísligh go stádas comhairleach é. Tá forluí aige le scanadh dúchasach rúin GitHub + `check:public-creds`. +- `check:pr-evidence` (blocála, déanann sé grep ar phrós i gcorp PR) — riosca ard torthaí bréagacha; lagófaí forfheidhmiú Riail Dhocht #18 dá mbainfí é, mar sin is fíorchinneadh beartais é seo. +- `semgrep` (neamhspleách comhairleach) — tá forluí aige le CodeQL i gcás theaghlaigh OWASP; ceangail a bhunlíne le raicín nó bain é. --- diff --git a/docs/i18n/ga/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ga/docs/compression/COMPRESSION_GUIDE.md index 9c79dfbcd367..5f96ccb6a76d 100644 --- a/docs/i18n/ga/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ga/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ instealltar an treoir logánta in ionad an Bhéarla. Ar chorp le `messages`, seiceálann seachbhóthar ábhair (`shouldBypassCavemanOutputMode()` in `open-sse/services/compression/outputMode.ts`) na trí theachtaireacht dheireanacha agus scipeann sé na stíleanna don chasadh iomlán nuair a mheaitseálann siad a eochairfhocail slándála, gníomhaíochta -do-aisiompaithe, soiléirithe, nó íogaire ó thaobh ordaithe de. Ritheann an seachbhóthar is cuma cén -socrú atá ar scorán **Seachbhóthar Auto-Soiléireachta** an deais (`cavemanOutputMode.autoClarity`). +do-aisiompaithe, soiléirithe, nó íogaire ó thaobh ordaithe de. Ritheann an seachbhóthar fad a bhíonn scorán **Seachbhóthar Auto-Soiléireachta** an deais (`cavemanOutputMode.autoClarity`) ar siúl, arb é sin an réamhshocrú; nuair a bhíonn an scorán múchta, cuirtear na stíleanna roghnaithe i bhfeidhm ar na casadh sin freisin. Nuair a ligeann an seachbhóthar an casadh tríd, cuireann `placeSystemInstruction()` (an comhad céanna), nach gcruthaíonn `messages[0]` nua riamh, an bloc sa chéad cheann díobh seo a aimsíonn sé: diff --git a/docs/i18n/ga/docs/reference/ENVIRONMENT.md b/docs/i18n/ga/docs/reference/ENVIRONMENT.md index b8cefa4a9e3e..6f44e64c0bb5 100644 --- a/docs/i18n/ga/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ga/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Rúin Riachtanacha -**Ní mór** iad seo a shocrú roimh an gcéad rith. Gan iad, diúltóidh an feidhmchlár tosú nó oibreoidh sé le réamhshocruithe neamhshlána. - -| Athróg | Riachtanach | Réamhshocrú | Comhad Foinse | Cur Síos | -| ---------------------------- | --------------------------------- | ------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Tá** | _(níl ceann ann)_ | `src/lib/auth` | Síníonn/fíoraíonn sé gach fianán seisiúin de chuid an deais (JWT). Gin le `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Tá** | _(níl ceann ann)_ | `src/lib/db/apiKeys.ts` | Eochair chriptithe AES le haghaidh luachanna eochracha API ar fos in SQLite. Gin le `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Tá** | `CHANGEME` | Script bútstrapála | Socraíonn sé pasfhocal tosaigh riarthóra na deaise (comhoireann sé réamhshocrú `.env.example` — coinnítear é neamhshlán go follasach chun athrú a éileamh). **Athraigh é roimh an gcéad úsáid.** Tar éis logáil isteach, athraigh é trí Dheais → Socruithe → Slándáil. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Tá** (i dtimpeallacht táirgthe) | _(gan socrú)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Rún comhroinnte le haghaidh dhroichead inmheánach WebSocket Codex Responses. Fíordheimhníonn sé iarratais droichid idir athsheachadán WS Electron/an bhrabhsálaí agus OmniRoute. ⚠️ **RIACHTANACH i dtimpeallacht táirgthe — nuair nach mbíonn sé socraithe, diúltaítear do gach iarratas droichid WS.** Gin le `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Níl | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Aitheantas follasach bustála taisce an oibrí seirbhíse le haghaidh bhlaosc PWA (#11779); an chéad cheann sa slabhra réitigh. | -| `SOURCE_VERSION` | Níl | _(gan socrú)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | An dara ceann sa slabhra — socraíonn tógálaithe PaaS (m.sh. ar nós Heroku) é mar an gcur i bhfeidhm imscartha. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Níl | _(díorthaithe)_ | `src/shared/components/PwaRegister.tsx` | Luach poiblí ag am tógála a úsáideann an cliant chun `/sw.js?v=…` a chlárú; díorthaítear é ón dá cheann thuas, agus ansin ón git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Níl (uathoibríoch) | _(uathoibríoch le gach bútáil)_ | `src/server/authz/policies/management.ts` | Rún in aghaidh an phróisis a chruthaíonn gur ó fhreastalaí HTTP OmniRoute féin (`scripts/dev/peer-stamp.mjs`) a tháinig stampa iontaofa IP an phiara. Ní chuireann an meánearra authz muinín i logántacht an iarratais (geatáil aisfhillte/LAN ar bhealaí `LOCAL_ONLY`) ach amháin nuair a bhíonn an comhartha seo ar an stampa. Gintear go huathoibríoch é le gach bútáil — fág gan socrú é; ná socraigh go buan é ach amháin do chumraíochtaí ilphróisis nach mór dóibh an stampa a chomhroinnt. | -| `OMNIROUTE_TRUSTED_PROXIES` | Níl | _(gan socrú)_ | `scripts/dev/peer-stamp.mjs` | IPanna nó raonta CIDR, scartha le camóga, de sheachfhreastalaithe a bhféadfaidh liosta ceadaithe/diúltaithe na IPanna muinín a chur ina gcuid `X-Forwarded-For` / `X-Real-IP`, anuas ar sheoltaí aisfhillte, seoltaí líonra phríobháidigh agus nóid imeallacha Cloudflare, a mbíonn muinín astu i gcónaí. Ní theastaíonn sé ach amháin do sheachfhreastalaí ar aon seoladh poiblí eile; murach sin, is é seoladh an tseachfhreastalaí féin a mheasann an scagaire. | +Cruthaítear rúin gan socrú atá marcáilte **Tá** go huathoibríoch ar an gcéad rith (`JWT_SECRET`, `API_KEY_SECRET`) agus coinnítear iad i `server.env` sa chomhadlann sonraí — socraigh iad go follasach nuair atá an chomhadlann sin sealadach. Is é `OMNIROUTE_WS_BRIDGE_SECRET` an eisceacht: ní choinnítear é i `server.env` — cruthaíonn pointí iontrála an fhreastalaí luach sealadach in aghaidh an tosaithe is féidir leis an droichead laistigh den phróiséas amháin a úsáid, agus mar sin ní mór do chliainteachtaí seachtracha an droichid é a shocrú go follasach. + +| Athróg | Riachtanach | Réamhshocrú | Comhad Foinse | Cur Síos | +| ---------------------------- | --------------------------------- | ------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Tá** | _(níl ceann ann)_ | `src/lib/auth` | Síníonn/fíoraíonn sé gach fianán seisiúin de chuid an deais (JWT). Gin le `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Tá** | _(níl ceann ann)_ | `src/lib/db/apiKeys.ts` | Eochair chriptithe AES le haghaidh luachanna eochracha API ar fos in SQLite. Gin le `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Níl | _(gan socrú)_ | `src/lib/auth/managementPassword.ts` | Socraíonn sé pasfhocal na deaise ag am tosaithe fad is nach bhfuil aon cheann sábháilte fós; déantar neamhaird de a luaithe a bhíonn pasfhocal sábháilte. Cuirteann suiteálacha npm agus ón bhfoinse `.env.example` in `.env`, mar sin tosaíonn siad lena shealbhóir poiblí aitheanta `CHANGEME`: cuir é in ionad roimh an gcéad tosú, nó socraigh `INITIAL_PASSWORD=` (folamh) sa chomhad `.env` sin chun an pasfhocal a chruthú i ndraoi socraithe ar an gcéad chuairt, mar a dhéanann luach gan socrú (réamhshocrú íomhá Docker). Athraigh pasfhocal sábháilte trí Dheais → Socruithe → Slándáil, nó rith `omniroute reset-password` (`node bin/reset-password.mjs` i gcóip foinseach) le `DATA_DIR` socraithe go comhadlann sonraí an fhreastalaí. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Tá** (i dtimpeallacht táirgthe) | _(gan socrú)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Rún comhroinnte le haghaidh dhroichead inmheánach WebSocket Codex Responses. Fíordheimhníonn sé iarratais droichid idir athsheachadán WS Electron/an bhrabhsálaí agus OmniRoute. ⚠️ **RIACHTANACH i dtimpeallacht táirgthe — nuair nach mbíonn sé socraithe, diúltaítear do gach iarratas droichid WS.** Gin le `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Níl | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Aitheantas follasach bustála taisce an oibrí seirbhíse le haghaidh bhlaosc PWA (#11779); an chéad cheann sa slabhra réitigh. | +| `SOURCE_VERSION` | Níl | _(gan socrú)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | An dara ceann sa slabhra — socraíonn tógálaithe PaaS (m.sh. ar nós Heroku) é mar an gcur i bhfeidhm imscartha. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Níl | _(díorthaithe)_ | `src/shared/components/PwaRegister.tsx` | Luach poiblí ag am tógála a úsáideann an cliant chun `/sw.js?v=…` a chlárú; díorthaítear é ón dá cheann thuas, agus ansin ón git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Níl (uathoibríoch) | _(uathoibríoch le gach bútáil)_ | `src/server/authz/policies/management.ts` | Rún in aghaidh an phróisis a chruthaíonn gur ó fhreastalaí HTTP OmniRoute féin (`scripts/dev/peer-stamp.mjs`) a tháinig stampa iontaofa IP an phiara. Ní chuireann an meánearra authz muinín i logántacht an iarratais (geatáil aisfhillte/LAN ar bhealaí `LOCAL_ONLY`) ach amháin nuair a bhíonn an comhartha seo ar an stampa. Gintear go huathoibríoch é le gach bútáil — fág gan socrú é; ná socraigh go buan é ach amháin do chumraíochtaí ilphróisis nach mór dóibh an stampa a chomhroinnt. | +| `OMNIROUTE_TRUSTED_PROXIES` | Níl | _(gan socrú)_ | `scripts/dev/peer-stamp.mjs` | IPanna nó raonta CIDR, scartha le camóga, de sheachfhreastalaithe a bhféadfaidh liosta ceadaithe/diúltaithe na IPanna muinín a chur ina gcuid `X-Forwarded-For` / `X-Real-IP`, anuas ar sheoltaí aisfhillte, seoltaí líonra phríobháidigh agus nóid imeallacha Cloudflare, a mbíonn muinín astu i gcónaí. Ní theastaíonn sé ach amháin do sheachfhreastalaí ar aon seoladh poiblí eile; murach sin, is é seoladh an tseachfhreastalaí féin a mheasann an scagaire. | ### Orduithe Giniúna @@ -215,38 +215,38 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ## 4. Slándáil & Fíordheimhniú -| Athróg | Réamhshocrú | Comhad Foinseach | Cur Síos | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salann a chomhcheanglaítear le haitheantóirí crua-earraí chun méarlorg meaisín a chruthú. Athraigh é do gach imscaradh ar leith chun aonrú a chinntiú. | -| `OMNIROUTE_CLI_SALT` | _(gan socrú = salann randamach in aghaidh na suiteála a choinnítear ag `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salann HMAC chun an comhartha fíordheimhnithe CLI áitiúil a dhíorthú. Má shocraítear an luach seo, rothlaítear gach comhartha CLI ar an meaisín agus tugtar tosaíocht dó i gcónaí ar an salann marthanach. Féach `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Socraíonn sé an bhratach `Secure` ar fhianáin seisiúin. **Ní mór dó a bheith `true`** nuair atá sé ag rith taobh thiar de HTTPS. | -| `REQUIRE_API_KEY` | `false` | Meánearra API | Nuair atá sé `true`, ní mór eochair bhailí API a bheith san áireamh i ngach iarratas seachfhreastalaí `/v1/*`. **Ní rialaíonn an bhratach seo `GET /v1/models`**, a leanann staidiúir logála isteach an deais (`requireAuthForModels`) ina ionad sin — mar sin, NÍ chiallaíonn `401` ó `/v1/models` go bhfuil tátal cosanta. Féach `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Ceadaíonn sé luachanna iomlána eochracha API a nochtadh i gcomhéadan úsáideora na Deaise. Is féidir é a chumrú ó Bhratacha Gné na Deaise; is riosca slándála é ar áscanna comhroinnte. | -| `NO_LOG_API_KEY_IDS` | _(folamh)_ | `src/lib/compliance/index.ts` | Aitheantais eochracha API, deighilte le camóga, a sheachnaíonn logáil iarratas (comhlíonadh GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(gan socrú = gan teorainn)_ | `src/shared/utils/apiKeyPolicy.ts` | Buiséad cúltaca iarratas in aghaidh an lae a chuirtear i bhfeidhm ar eochracha API a bhfuil luach null ina gcolún `rate_limits`. Gan socrú nó folamh: gan uasteorainn intuigthe (#2289, #11017). Is ionann `0` agus sin (gan teorainn). Cumasaíonn slánuimhir dhearfach N N/lá, 5N/seachtain, 20N/mí. Má bhíonn luachanna neamhfholmha míchumtha ann, úsáidtear na fuinneoga oidhreachta 1000/lá, 5000/seachtain, 20000/mí mar chúltaca. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Uasmhéid ceadaithe chorp an iarratais. Diúltaítear d’ualaí pá a sháraíonn an teorainn seo. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Glacann coirp iarratais iarbhír atá ag an tairseach seo nó os a cionn an léas iontrála tromualach adamhach atá áitiúil don phróiseas sula ndéantar parsáil JSON (conair BYTE, lena n-áirítear `POST /v1/responses`). Tá an bealach éalaithe céanna [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) nuair atá dóthain spáis shláintiúil ar fáil agus atá ann d’ualaí troma ó thaobh struchtúir de; tá sé fós faoi cheangal ag `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), ionas nach bhfillfidh [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Uasteorainn chrua do bhealaí comhrá a chuirtear i bhfeidhm ar na bearta a léitear le linn ionghabhála teorannaithe, lena n-áirítear iarratais ina bhfuil `Content-Length` ar iarraidh, neamhbhailí nó bréagach; faigheann farasbarr `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(gan socrú — gan uasteorainn ar líon na n-iarratas)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ní bheidh an uasteorainn oidhreachta seo ar LÍON na n-iarratas ceangailteach anois ach amháin nuair a shocraítear go sainráite í. Má fhágtar gan socrú í (an réamhshocrú), déantar ligean isteach comhrá trom a rialú ina ionad sin le `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — buiséad BEART a dhíorthaítear go huathoibríoch, arna mhéidsiú ó fhíor-uasteorainn chuimhne an phróisis in **aon phróiseas amháin** (carn V8 amháin). Cuireann dhá iarratas `/v1/responses` forluiteacha de thart ar 750k comhartha deireadh le cairn ~12 Gi (#7849) — rabhadh faoi **bhuiséad cuimhne**, ní uasmhéid docht táirge de 2. FÉADFAIDH próiseas sláintiúil (carn faoi bhun an chóimheasa maolaithe) níos mó iarratas fada comhthráthach `/v1/responses` a ligean isteach trí `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Baineann na deicheanna de chliaint fhada SSE (40–50) leis an gcarn + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ní le “uasmhéid 2”. Má ardaítear é seo go dall chun “an t-óstach a úsáid”, tabharfar #7849 ar ais. Méadaigh líon na **gcarn** le **N `DATA_DIR` neamhspleácha** (#11024); ná húsáid `replicas>1` riamh ar chomhad SQLite amháin. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(díorthaithe go huathoibríoch)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** sárú ar an mbuiséad beart ionghabhála a dhíorthaítear go huathoibríoch (25% den uasteorainn chuimhne V8/cgroup is sriantaí, roinnte ar aimpliú neamhbhuan 8x). Cuirtear teorainn 8 MiB–2 GiB le luachanna díorthaithe agus sainráite. Teipeann corp atá níos mó ná an buiséad éifeachtach láithreach le `413 body_exceeds_budget`; fanann coimhlint idir coirp is féidir a sheirbhísiú astu féin in-atriailte le `503`. Baineann 40–50 cliant fada SSE leis an mbuiséad seo + an carn, ní le “uasmhéid 2” docht. Léigh `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ag `/api/monitoring/health` sula ndéanann tú tiúnadh. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Cóimheas maolaithe brú cairn (`heapUsed / heap_size_limit`) le haghaidh ligean isteach trom de chineál BEART agus STRUCHTÚR (#10183, #10268, #10437). Ní mhaolaítear iarratas trom comhthráthach thar `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` leis an `503` in-atriailte ach amháin nuair atá an carn ag an gcóimheas seo nó os a chionn FREISIN; ar charn sláintiúil ligtear isteach é trí spás sláintiúil breise ina ionad sin. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (réamhshocrú `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Acmhainn bhreise theorannaithe do chonair thapa an chairn shláintiúil (#10437) ar **STRUCHTÚR** agus **BEART** araon (`admitChatRequest`, lena n-áirítear coirp ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Gan an teorainn seo, sheachnódh gach iarratas gnóthach ach le carn sláintiúil an ligean isteach gan aon uasteorainn. Nuair a bhíonn an líon seo léasanna comhthráthacha gníomhach trí chonair an chairn shláintiúil, téann iarratais ghnóthacha eile ar aghaidh chuig an gconair CHÉANNA feithimh theorannaithe/maolaithe a úsáidtear faoi fhíorbhrú cairn. Díchumasaíonn `0` an seachbhóthar go hiomlán. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Líon teachtaireachtaí a aicmíonn iarratas comhrá mar iarratas trom fiú nuair atá a chorp faoi bhun thairseach na mbeart. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Líon uirlisí a aicmíonn iarratas comhrá mar iarratas trom fiú nuair atá a chorp faoi bhun thairseach na mbeart. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Meastachán coimeádach ar chomharthaí bunaithe ar mhéid teaghráin a aicmíonn iarratas mar iarratas trom; seachfhreastalaí costais ligean isteach é seo, ní comharthú billeála an tsoláthraí. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (díchumasaithe) | `src/shared/middleware/chatBodyAdmission.ts` | Uasteorainn roghnach rogha-isteach ar stair chomhrá. Díchumasaithe de réir réamhshocraithe: is beartas imlonnaithe é líon teachtaireachtaí, ní airí uilíoch de chuid iarratais, agus má chuirtear uasteorainn anseo diúltaítear do chomhráite le `413` críochnaitheach sula bhféadfaidh an phíblíne comhbhrúite iad a dhéanamh inseirbhíse. Cuireann `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` agus maolú an bhrú cairn teorainn le fás an chairn. Socraigh luach deimhneach ar imlonnuithe atá srianta ó thaobh cuimhne de agus a dteastaíonn uasteorainn chrua uathu; gheobhaidh farasbarr `413` struchtúrtha a éilíonn dlúthú ansin. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Uasteorainn chrua do fhreagra neamhsruthaithe réamhtheachtach a mhaolánaítear go hiomlán sa chuimhne. Thar an teorainn seo, cealaítear an léitheoir réamhtheachtach agus teipeann an t-iarratas go tapa in ionad teaghrán gan teorainn a fhás go dtí go n-ídítear an carn. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Uasmhéid beart ar an sreang a chuirtear ar aghaidh ó cheanntásca freagartha réamhtheachtacha. Nuair a sháraítear an buiséad, fágtar ceanntásca ar thosaíocht níos ísle (m.sh., `x-codex-*`, `x-oai-request-id`) ar lár chun fanacht laistigh de ghnáth-theorainneacha ceanntásca seachfhreastalaí droim ar ais. Socraigh níos airde é chun tuilleadh meiteashonraí réamhtheachtacha a chur ar aghaidh, ar chostas ceanntásca freagartha níos mó. | -| `CORS_ORIGIN` | _(gan socrú)_ | `src/server/cors/origins.ts` | Liosta ceadaithe CORS oidhreachta d'aon bhunús amháin. Moltar `CORS_ALLOWED_ORIGINS` le haghaidh imscaradh nua. Ní bhaineann CORS ach le cliaint API brabhsálaí trastionscnaimh; úsáideann scríbhinní fíordheimhnithe an deais iarratais den tionscnamh céanna agus cosaint CSRF atá ceangailte leis an seisiún ina ionad sin. | -| `CORS_ALLOWED_ORIGINS` | _(gan socrú)_ | `src/server/cors/origins.ts` | Liosta ceadaithe CORS scartha le camóga. Ní sheoltar saoróg mura bhfuil `CORS_ALLOW_ALL=true` cumraithe go sainráite. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Bealach éalaithe forbartha amháin chun aon `Origin` brabhsálaí a athshondasú. Ná cumasaigh é ar imscaradh comhroinnte ná táirgeachta. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Cuir bac ar ghlaonna soláthraí atá dírithe ar raonta IP príobháideacha/aislúbtha/nasc-logánta. Ná díchumasaigh ach amháin i dtimpeallachtaí tástála leithlisithe. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Ceadaigh URLanna soláthraí a dhíríonn ar líonraí príobháideacha/áitiúla (localhost, 192.168.x.x, 10.x.x.x, etc.). **RIACHTANACH do sholáthraithe féinóstáilte** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Nuair atá sé `false`, diúltaíonn an deais do bhailíochtú URLanna áitiúla. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Ceadaigh soláthraithe ar sheoltaí áitiúla/príobháideacha (127.0.0.1, localhost, LAN, raonta príobháideacha) a chur leis/a bhailíochtú — teoranta do chonair bhailíochtaithe an tsoláthraí. **Réamhshocrú `true`** (áitiúil ar dtús); socraigh go `false` é chun dianbhlocáil phoiblí amháin a chur i bhfeidhm. Fanann críochphointí meiteashonraí néil (169.254.169.254, metadata.google.internal) blocáilte beag beann air sin. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ceadaigh do na bealaí `/v1/audio/*` (tras-scríbhinní, urlabhra, aistriúcháin) nód soláthraí atá comhoiriúnach le OpenAI agus óstáilte lasmuigh de localhost a úsáid. Díchumasaithe de réir réamhshocraithe — athraíonn ródú fuaime chuig óstríomhaire cianda céannacht eis-sreafa agus ní mór gur cinneadh sainráite oibreora é. Ceadaítear nóid aislúbtha/phríobháideacha (localhost, 127.0.0.1, 172.16-31.x) i gcónaí agus ní théann sé seo i bhfeidhm orthu. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ceadaigh do `POST /v1/rerank` (agus céim athrangaithe aislúbtha an innill chuimhne) nód soláthraí atá comhoiriúnach le OpenAI agus óstáilte lasmuigh de localhost a úsáid — bosca LAN nó piara Tailscale a ritheann TEI, Infinity, vLLM, etc. Díchumasaithe de réir réamhshocraithe — athraíonn ródú chuig óstríomhaire cianda céannacht eis-sreafa agus ní mór gur cinneadh sainráite oibreora é. Ceadaítear nóid aislúbtha (localhost, 127.0.0.1, 172.16-31.x) i gcónaí agus ní théann sé seo i bhfeidhm orthu. Ní mór do nóid chianda polasaí URL amach an tsoláthraí (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) a chomhlíonadh freisin; ní dhéantar ródú chuig óstríomhairí meiteashonraí néil riamh. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Nuair atá OIDC cumasaithe, díchumasaigh logáil isteach le pasfhocal ionas nach féidir le húsáideoirí fíordheimhniú ach trí Shíniú Aonair OIDC. Glactar leis an ailias lom `OIDC_DISABLE_PASSWORD_LOGIN` freisin; tugtar tús áite do Bhratach Gné na Deaise a bhfuil an eochair chéanna aici. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ailias lom de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Athróg | Réamhshocrú | Comhad Foinseach | Cur Síos | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salann a chomhcheanglaítear le haitheantóirí crua-earraí chun méarlorg meaisín a chruthú. Athraigh é do gach imscaradh ar leith chun aonrú a chinntiú. | +| `OMNIROUTE_CLI_SALT` | _(gan socrú = salann randamach in aghaidh na suiteála a choinnítear ag `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salann HMAC chun an comhartha fíordheimhnithe CLI áitiúil a dhíorthú. Má shocraítear an luach seo, rothlaítear gach comhartha CLI ar an meaisín agus tugtar tosaíocht dó i gcónaí ar an salann marthanach. Féach `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Socraíonn sé an bhratach `Secure` ar fhianáin seisiúin. **Ní mór dó a bheith `true`** nuair atá sé ag rith taobh thiar de HTTPS. | +| `REQUIRE_API_KEY` | `false` | Meánearra API | Nuair atá sé `true`, ní mór eochair bhailí API a bheith san áireamh i ngach iarratas seachfhreastalaí `/v1/*`. **Ní rialaíonn an bhratach seo `GET /v1/models`**, a leanann staidiúir logála isteach an deais (`requireAuthForModels`) ina ionad sin — mar sin, NÍ chiallaíonn `401` ó `/v1/models` go bhfuil tátal cosanta. Féach `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Ceadaíonn sé luachanna iomlána eochracha API a nochtadh i gcomhéadan úsáideora na Deaise. Is féidir é a chumrú ó Bhratacha Gné na Deaise; is riosca slándála é ar áscanna comhroinnte. | +| `NO_LOG_API_KEY_IDS` | _(folamh)_ | `src/lib/compliance/index.ts` | Aitheantais eochracha API, deighilte le camóga, a sheachnaíonn logáil iarratas (comhlíonadh GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(gan socrú = gan teorainn)_ | `src/shared/utils/apiKeyPolicy.ts` | Buiséad cúltaca iarratas in aghaidh an lae a chuirtear i bhfeidhm ar eochracha API a bhfuil luach null ina gcolún `rate_limits`. Gan socrú nó folamh: gan uasteorainn intuigthe (#2289, #11017). Is ionann `0` agus sin (gan teorainn). Cumasaíonn slánuimhir dhearfach N N/lá, 5N/seachtain, 20N/mí. Má bhíonn luachanna neamhfholmha míchumtha ann, úsáidtear na fuinneoga oidhreachta 1000/lá, 5000/seachtain, 20000/mí mar chúltaca. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Uasmhéid ceadaithe chorp an iarratais. Diúltaítear d’ualaí pá a sháraíonn an teorainn seo. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Glacann coirp iarratais iarbhír atá ag an tairseach seo nó os a cionn an léas iontrála tromualach adamhach atá áitiúil don phróiseas sula ndéantar parsáil JSON (conair BYTE, lena n-áirítear `POST /v1/responses`). Tá an bealach éalaithe céanna [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) nuair atá dóthain spáis shláintiúil ar fáil agus atá ann d’ualaí troma ó thaobh struchtúir de; tá sé fós faoi cheangal ag `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), ionas nach bhfillfidh [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Uasteorainn chrua do bhealaí comhrá a chuirtear i bhfeidhm ar na bearta a léitear le linn ionghabhála teorannaithe, lena n-áirítear iarratais ina bhfuil `Content-Length` ar iarraidh, neamhbhailí nó bréagach; faigheann farasbarr `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(gan socrú — gan uasteorainn ar líon na n-iarratas)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ní bheidh an uasteorainn oidhreachta seo ar LÍON na n-iarratas ceangailteach anois ach amháin nuair a shocraítear go sainráite í. Má fhágtar gan socrú í (an réamhshocrú), déantar ligean isteach comhrá trom a rialú ina ionad sin le `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — buiséad BEART a dhíorthaítear go huathoibríoch, arna mhéidsiú ó fhíor-uasteorainn chuimhne an phróisis in **aon phróiseas amháin** (carn V8 amháin). Cuireann dhá iarratas `/v1/responses` forluiteacha de thart ar 750k comhartha deireadh le cairn ~12 Gi (#7849) — rabhadh faoi **bhuiséad cuimhne**, ní uasmhéid docht táirge de 2. FÉADFAIDH próiseas sláintiúil (carn faoi bhun an chóimheasa maolaithe) níos mó iarratas fada comhthráthach `/v1/responses` a ligean isteach trí `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Baineann na deicheanna de chliaint fhada SSE (40–50) leis an gcarn + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ní le “uasmhéid 2”. Má ardaítear é seo go dall chun “an t-óstach a úsáid”, tabharfar #7849 ar ais. Méadaigh líon na **gcarn** le **N `DATA_DIR` neamhspleácha** (#11024); ná húsáid `replicas>1` riamh ar chomhad SQLite amháin. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(díorthaithe go huathoibríoch)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** sárú ar an mbuiséad beart ionghabhála a dhíorthaítear go huathoibríoch (25% den uasteorainn chuimhne V8/cgroup is sriantaí, roinnte ar aimpliú neamhbhuan 8x). Cuirtear teorainn 8 MiB–2 GiB le luachanna díorthaithe agus sainráite. Teipeann corp atá níos mó ná an buiséad éifeachtach láithreach le `413 body_exceeds_budget`; fanann coimhlint idir coirp is féidir a sheirbhísiú astu féin in-atriailte le `503`. Baineann 40–50 cliant fada SSE leis an mbuiséad seo + an carn, ní le “uasmhéid 2” docht. Léigh `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ag `/api/monitoring/health` sula ndéanann tú tiúnadh. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Cóimheas maolaithe brú cairn (`heapUsed / heap_size_limit`) le haghaidh ligean isteach trom de chineál BEART agus STRUCHTÚR (#10183, #10268, #10437). Ní mhaolaítear iarratas trom comhthráthach thar `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` leis an `503` in-atriailte ach amháin nuair atá an carn ag an gcóimheas seo nó os a chionn FREISIN; ar charn sláintiúil ligtear isteach é trí spás sláintiúil breise ina ionad sin. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (réamhshocrú `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Acmhainn bhreise theorannaithe do chonair thapa an chairn shláintiúil (#10437) ar **STRUCHTÚR** agus **BEART** araon (`admitChatRequest`, lena n-áirítear coirp ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Gan an teorainn seo, sheachnódh gach iarratas gnóthach ach le carn sláintiúil an ligean isteach gan aon uasteorainn. Nuair a bhíonn an líon seo léasanna comhthráthacha gníomhach trí chonair an chairn shláintiúil, téann iarratais ghnóthacha eile ar aghaidh chuig an gconair CHÉANNA feithimh theorannaithe/maolaithe a úsáidtear faoi fhíorbhrú cairn. Díchumasaíonn `0` an seachbhóthar go hiomlán. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Líon teachtaireachtaí a aicmíonn iarratas comhrá mar iarratas trom fiú nuair atá a chorp faoi bhun thairseach na mbeart. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Líon uirlisí a aicmíonn iarratas comhrá mar iarratas trom fiú nuair atá a chorp faoi bhun thairseach na mbeart. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Meastachán coimeádach ar chomharthaí bunaithe ar mhéid teaghráin a aicmíonn iarratas mar iarratas trom; seachfhreastalaí costais ligean isteach é seo, ní comharthú billeála an tsoláthraí. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (díchumasaithe) | `src/shared/middleware/chatBodyAdmission.ts` | Uasteorainn roghnach rogha-isteach ar stair chomhrá. Díchumasaithe de réir réamhshocraithe: is beartas imlonnaithe é líon teachtaireachtaí, ní airí uilíoch de chuid iarratais, agus má chuirtear uasteorainn anseo diúltaítear do chomhráite le `413` críochnaitheach sula bhféadfaidh an phíblíne comhbhrúite iad a dhéanamh inseirbhíse. Cuireann `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` agus maolú an bhrú cairn teorainn le fás an chairn. Socraigh luach deimhneach ar imlonnuithe atá srianta ó thaobh cuimhne de agus a dteastaíonn uasteorainn chrua uathu; gheobhaidh farasbarr `413` struchtúrtha a éilíonn dlúthú ansin. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Uasteorainn chrua do fhreagra neamhsruthaithe réamhtheachtach a mhaolánaítear go hiomlán sa chuimhne. Thar an teorainn seo, cealaítear an léitheoir réamhtheachtach agus teipeann an t-iarratas go tapa in ionad teaghrán gan teorainn a fhás go dtí go n-ídítear an carn. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Uasmhéid beart ar an sreang a chuirtear ar aghaidh ó cheanntásca freagartha réamhtheachtacha. Nuair a sháraítear an buiséad, fágtar ceanntásca ar thosaíocht níos ísle (m.sh., `x-codex-*`, `x-oai-request-id`) ar lár chun fanacht laistigh de ghnáth-theorainneacha ceanntásca seachfhreastalaí droim ar ais. Socraigh níos airde é chun tuilleadh meiteashonraí réamhtheachtacha a chur ar aghaidh, ar chostas ceanntásca freagartha níos mó. | +| `CORS_ORIGIN` | _(gan socrú)_ | `src/server/cors/origins.ts` | Liosta ceadaithe CORS oidhreachta d'aon bhunús amháin. Moltar `CORS_ALLOWED_ORIGINS` le haghaidh imscaradh nua. Ní bhaineann CORS ach le cliaint API brabhsálaí trastionscnaimh; úsáideann scríbhinní fíordheimhnithe an deais iarratais den tionscnamh céanna agus cosaint CSRF atá ceangailte leis an seisiún ina ionad sin. | +| `CORS_ALLOWED_ORIGINS` | _(gan socrú)_ | `src/server/cors/origins.ts` | Liosta ceadaithe CORS scartha le camóga. Ní sheoltar saoróg mura bhfuil `CORS_ALLOW_ALL=true` cumraithe go sainráite. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Bealach éalaithe forbartha amháin chun aon `Origin` brabhsálaí a athshondasú. Ná cumasaigh é ar imscaradh comhroinnte ná táirgeachta. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ailias oidhreachta. Léitear a scorán deise (sáraitheoir a sábháiltear sa bhunachar sonraí) roimh an luach timpeallachta seo; cuireann `false`, `0`, `no` nó `off` ceachtar acu na seiceálacha óstríomhaire as ar an mbealach céanna a dhéanann `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, fiú nuair atá scorán deise na brataí sin sábháilte as; fágann luachanna eile an cosantóir ag an dá bhratach thíos. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Múchann sé seiceálacha óstríomhaire chosaint URL amach, an bloc meiteashonraí néil san áireamh (169.254.0.0/16 ina iomlán, metadata.google.internal), ar chonairí soláthraithe a leanann mód an chosantóra: bailíochtú URL, aimsiú samhlacha, bun-URLanna nóid soláthraí (nóid athrangaithe chianda san áireamh), agus tástáil chúltaca an proxy, i measc conairí amach eile in aice le soláthraithe amhail fáil íomhánna agus meáin cianda. Ceadaíonn sé spriceanna webhook phríobháideacha freisin, ach amháin críochphointí meiteashonraí néil nó nasc-logánta, a fhanann blocáilte fiú nuair atá an bhratach seo air. Coinníonn iarratais chomhrá a sheoltar trí `BaseExecutor.execute()` a seiceáil meiteashonraí néil féin. Oibríonn soláthraithe áitiúla leis na réamhshocruithe: cheadaíonn `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (réamhshocrú `true`) URLanna áitiúla agus LAN cheana féin ar chonairí bhailíochtaithe, aimsithe agus nóid soláthraí; ní fhéachann tástáil chúltaca an proxy ná spriceanna webhook phríobháideacha ach leis an mbratach seo, agus blocálann siad óstríomhairí áitiúla agus LAN fad is atá sí as. Sáraíonn sé `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Bíonn tosaíocht ag luach a shábháiltear ó scorán na deaise thar an athróg seo. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ceadaigh URLanna soláthraí ar sheoltaí áitiúla nó phríobháideacha (127.0.0.1, localhost, LAN, raonta príobháideacha) do bhailíochtú soláthraithe, aimsiú samhlacha, comhrá, agus na conairí amach eile in aice le soláthraithe a rialaíonn mód an chosantóra. **Réamhshocrú `true`** (áitiúil ar dtús): blocálann an cosantóir ansin críochphointí meiteashonraí néil — 169.254.0.0/16 ina iomlán chomh maith le hóstainmneacha meiteashonraí aithnid (metadata.google.internal, metadata.goog, IMDS Alibaba/Azure/Oracle, fd00:ec2::254). Socraigh `false` le haghaidh mód poiblí amháin, a bhlocálann óstríomhairí príobháideacha agus aislúbhta freisin. Seiceálann an cosantóir an t-óstainm nó an litriúil IP mar atá scríofa, roimh aon chuardach DNS; i mód poiblí amháin, réitíonn filltéan tairisceana sábháilte an t-óstainm freisin agus diúltaíonn sé d’aon fhreagra príobháideach. Múchann `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` na seiceálacha seo ar na conairí atá liostaithe ina ró féin. Ní bhaineann soláthraithe áitiúla ionsuite, ar nós LM Studio, Ollama, vLLM, Llamafile agus Triton, le seiceálacha óstríomhaire ar bith do bhailíochtú eochracha ná comhrá. Bíonn tosaíocht ag luach a shábháiltear ó scorán na deaise thar an athróg seo. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ceadaigh do na bealaí `/v1/audio/*` (tras-scríbhinní, urlabhra, aistriúcháin) nód soláthraí atá comhoiriúnach le OpenAI agus óstáilte lasmuigh de localhost a úsáid. Díchumasaithe de réir réamhshocraithe — athraíonn ródú fuaime chuig óstríomhaire cianda céannacht eis-sreafa agus ní mór gur cinneadh sainráite oibreora é. Ceadaítear nóid aislúbtha/phríobháideacha (localhost, 127.0.0.1, 172.16-31.x) i gcónaí agus ní théann sé seo i bhfeidhm orthu. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ceadaigh do `POST /v1/rerank` (agus céim athrangaithe aislúbtha an innill chuimhne) nód soláthraí atá comhoiriúnach le OpenAI agus óstáilte lasmuigh de localhost a úsáid — bosca LAN nó piara Tailscale a ritheann TEI, Infinity, vLLM, etc. Díchumasaithe de réir réamhshocraithe — athraíonn ródú chuig óstríomhaire cianda céannacht eis-sreafa agus ní mór gur cinneadh sainráite oibreora é. Ceadaítear nóid aislúbtha (localhost, 127.0.0.1, 172.16-31.x) i gcónaí agus ní théann sé seo i bhfeidhm orthu. Ní mór do bhun-URL nóid cianda polasaí URL amach an tsoláthraí (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) a chomhlíonadh freisin. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Nuair atá OIDC cumasaithe, díchumasaigh logáil isteach le pasfhocal ionas nach féidir le húsáideoirí fíordheimhniú ach trí Shíniú Aonair OIDC. Glactar leis an ailias lom `OIDC_DISABLE_PASSWORD_LOGIN` freisin; tugtar tús áite do Bhratach Gné na Deaise a bhfuil an eochair chéanna aici. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ailias lom de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Seicliosta Cruaite diff --git a/docs/i18n/gu/CONTRIBUTING.md b/docs/i18n/gu/CONTRIBUTING.md index 16e42a5fbb39..b7e90f5d67a5 100644 --- a/docs/i18n/gu/CONTRIBUTING.md +++ b/docs/i18n/gu/CONTRIBUTING.md @@ -387,20 +387,20 @@ docs/ # Documentation ## Pull Request ચેકલિસ્ટ -- [ ] પરીક્ષણો સફળ થાય છે (`npm test`) -- [ ] લિન્ટિંગ સફળ થાય છે (`npm run lint`) +- [ ] ટેસ્ટ્સ પાસ થાય છે (`npm test`) +- [ ] લિન્ટિંગ પાસ થાય છે (`npm run lint`) - [ ] બિલ્ડ સફળ થાય છે (`npm run build`) -- [ ] નવા જાહેર ફંક્શન્સ અને ઇન્ટરફેસ માટે TypeScript પ્રકારો ઉમેર્યા છે -- [ ] કોઈ હાર્ડકોડ કરેલા સિક્રેટ્સ અથવા ફૉલબૅક મૂલ્યો નથી -- [ ] જાહેર અપસ્ટ્રીમ ક્રેડેન્શિયલ્સ `resolvePublicCred()` દ્વારા એમ્બેડ કર્યાં છે ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) જુઓ), ક્યારેય લિટરલ્સ તરીકે નહીં -- [ ] ભૂલ પ્રતિસાદો `buildErrorBody()` / `sanitizeErrorMessage()` મારફતે જાય છે — પ્રતિસાદ બોડીમાં કોઈ કાચા સ્ટૅક ટ્રેસ નથી ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) જુઓ) -- [ ] શેલ કમાન્ડ્સ (`exec` / `spawn`) રનટાઇમ મૂલ્યોને સ્ટ્રિંગ ઇન્ટરપોલેશન દ્વારા નહીં, પરંતુ `env` દ્વારા પસાર કરે છે -- [ ] તમામ ઇનપુટ્સ Zod સ્કીમા વડે માન્ય કર્યાં છે -- [ ] વપરાશકર્તાને અસર કરતા ફેરફારો માટે `changelog.d/{features|fixes|maintenance}/-.md` હેઠળ ચેન્જલૉગ **ફ્રૅગમેન્ટ** ઉમેર્યું છે ([`changelog.d/README.md`](./changelog.d/README.md) જુઓ) — `CHANGELOG.md` ને સીધું સંપાદિત **કરશો નહીં**; ફ્રૅગમેન્ટ્સ રિલીઝ સમયે એકત્રિત થાય છે અને PR વચ્ચે ક્યારેય સંઘર્ષ કરતા નથી +- [ ] નવા સાર્વજનિક ફંક્શન્સ અને ઇન્ટરફેસ માટે TypeScript પ્રકારો ઉમેર્યા છે +- [ ] કોઈ હાર્ડકોડેડ સિક્રેટ્સ અથવા ફૉલબૅક મૂલ્યો નથી +- [ ] સાર્વજનિક અપસ્ટ્રીમ ક્રેડેન્શિયલ્સ `resolvePublicCred()` મારફતે એમ્બેડ કર્યાં છે ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) જુઓ), ક્યારેય લિટરલ્સ તરીકે નહીં +- [ ] ભૂલ પ્રતિસાદો `buildErrorBody()` / `sanitizeErrorMessage()` મારફતે રૂટ થાય છે — પ્રતિસાદ બોડીમાં કોઈ કાચા સ્ટૅક ટ્રેસ નથી ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) જુઓ) +- [ ] શેલ કમાન્ડ્સ (`exec` / `spawn`) રનટાઇમ મૂલ્યોને સ્ટ્રિંગ ઇન્ટરપોલેશન મારફતે નહીં, પરંતુ `env` મારફતે પસાર કરે છે +- [ ] બધા ઇનપુટ્સ Zod સ્કીમા વડે માન્ય કરેલા છે +- [ ] વપરાશકર્તાને અસર કરતા ફેરફારો માટે `changelog.d/{features|fixes|maintenance}/-.md` હેઠળ ચેન્જલૉગ **ફ્રેગમેન્ટ** ઉમેર્યો છે ([`changelog.d/README.md`](./changelog.d/README.md) જુઓ) — `CHANGELOG.md`ને સીધું સંપાદિત કરશો **નહીં**; ફ્રેગમેન્ટ્સને રિલીઝ સમયે એકત્રિત કરવામાં આવે છે અને PRs વચ્ચે ક્યારેય સંઘર્ષ થતો નથી - [ ] દસ્તાવેજીકરણ અપડેટ કર્યું છે (જો લાગુ પડતું હોય) -- [ ] કોઈ નવા CodeQL / Secret-Scanning અલર્ટ ખોલાયા નથી, અથવા દરેકને સંબંધિત `docs/security/` દસ્તાવેજનો સંદર્ભ આપતા તકનીકી વાજબીકરણ સાથે રદ કર્યો છે -- [ ] ચાઇલ્ડ પ્રોસેસ શરૂ કરતા રૂટ્સ (`/api/mcp/`, `/api/cli-tools/runtime/`) ને `src/server/authz/routeGuard.ts` માં `isLocalOnlyPath()` તરીકે વર્ગીકૃત કર્યા છે — [કડક નિયમ #15](docs/security/ROUTE_GUARD_TIERS.md) જુઓ -- [ ] કમિટ સંદેશાઓમાં કોઈ `Co-Authored-By` ટ્રેલર્સ નથી — કમિટ્સ ફક્ત રિપોઝિટરી માલિકની Git ઓળખ હેઠળ જ દેખાવા જોઈએ (કડક નિયમ #16) +- [ ] કોઈ નવી CodeQL / Secret-Scanning ચેતવણીઓ ખોલવામાં આવી નથી, અથવા દરેકને સંબંધિત `docs/security/` દસ્તાવેજનો સંદર્ભ આપતા ટેક્નિકલ વાજબીકરણ સાથે રદ કરવામાં આવી છે +- [ ] ચાઇલ્ડ પ્રોસેસ શરૂ કરતા રૂટ્સ (`/api/mcp/`, `/api/cli-tools/runtime/`)ને `src/server/authz/routeGuard.ts`માં `isLocalOnlyPath()` તરીકે વર્ગીકૃત કર્યા છે — [કડક નિયમ #15](docs/security/ROUTE_GUARD_TIERS.md) જુઓ +- [ ] કમિટ સંદેશાઓમાં AI/બોટ `Co-authored-by` ટ્રેલર્સ નથી (કડક નિયમ #16) — જે માનવીય સહયોગીઓના કાર્યનો પુનઃઉપયોગ થયો છે તેમને પ્રમાણભૂત `Co-authored-by: Name ` ટ્રેલર્સ દ્વારા શ્રેય આપવામાં આવ્યો છે --- diff --git a/docs/i18n/gu/docs/architecture/QUALITY_GATES.md b/docs/i18n/gu/docs/architecture/QUALITY_GATES.md index fc7d897298a5..240d3ece5fb9 100644 --- a/docs/i18n/gu/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/gu/docs/architecture/QUALITY_GATES.md @@ -17,257 +17,256 @@ ratchet baseline અથવા pass/fail policy નો ઉપયોગ કરે ## ગેટ ઇન્વેન્ટરી અને એક્ઝિક્યુશન પ્રોફાઇલ્સ -### ઉમેદવારની સ્વીકૃતિ +### ઉમેદવાર સ્વીકૃતિ -CI અને Quality Gates વર્કફ્લો પ્રત્યેક એક સ્થિર ચુકાદો આપે છે: `Gate / CI` અને -`Gate / Quality`. તેમની વર્ઝન કરેલી સ્વીકૃતિ નીતિ દરેક અપસ્ટ્રીમ જૉબને -આવશ્યક અથવા સલાહરૂપ તરીકે સૂચિબદ્ધ કરે છે. લાગુ પડતી આવશ્યક જૉબ સફળ થવી જ જોઈએ: -ગેરહાજર, રદ કરાયેલા, સ્કિપ કરાયેલા, બાકી અને અજ્ઞાત પરિણામો PASS સ્થાપિત કરી શકતા નથી. +CI અને Quality Gates વર્કફ્લો દરેક એક સ્થિર ચુકાદો આપે છે: `Gate / CI` અને +`Gate / Quality`. તેમની વર્ઝન કરેલી સ્વીકૃતિ નીતિ દરેક અપસ્ટ્રીમ જોબને +આવશ્યક અથવા સલાહરૂપ તરીકે સૂચિબદ્ધ કરે છે. લાગુ પડતી આવશ્યક જોબ સફળ થવી જ જોઈએ: +ગેરહાજર, રદ થયેલ, છોડી દેવાયેલ, બાકી અને અજ્ઞાત પરિણામો PASS સ્થાપિત કરી શકતાં નથી. માન્ય માત્ર-ડૉક્સ અથવા માત્ર-કૅટલૉગ વર્ગીકરણ કોડ લેનને લાગુ ન પડતી બનાવી શકે છે; -ડ્રાફ્ટ PR સ્વીકૃત ઉમેદવાર નથી. `hotfix` લેબલ પુરાવાની આવશ્યકતામાંથી મુક્તિ આપતું નથી. - -બંને વર્કફ્લો PRs અને main/release બ્રાન્ચ પરના પુશ, મેન્યુઅલ ડિસ્પૅચ અને -merge-group ઇવેન્ટ્સને આવરી લે છે. પુશ, ડિસ્પૅચ અને merge-group સંપૂર્ણ પસંદગી ચલાવે છે. -ફોર્ક્સ અને merge groups એવી જૉબ્સ માટે હોસ્ટેડ રનર્સનો ઉપયોગ કરે છે જે અન્યથા -સેલ્ફ-હોસ્ટેડ રનર્સ પસંદ કરે છે; રોલઆઉટ પહેલાં પર્યાપ્ત હોસ્ટેડ ક્ષમતા ચકાસવી આવશ્યક છે. - -દરેક JSON રસીદ ચેકઆઉટ કરેલ SHA, વર્કફ્લો રન અને પ્રયાસ ઓળખાવે છે. -CLI ચેકઆઉટ/ઇવેન્ટ SHA અસંગતતાને નકારે છે. વર્કફ્લો પરીક્ષણો નીતિની સભ્યતાને -ચુકાદા જૉબની `needs` સૂચિ સાથે બાંધે છે, જેથી નવી અથવા દૂર કરેલી લેન મૌનપણે અદૃશ્ય ન થઈ શકે. -રસીદો તેમના પોતાના વર્કફ્લોને આવરી લે છે, પ્રકાશન, ડિપ્લોયમેન્ટ અથવા -હાલના સલાહરૂપ સ્કૅનરની આંતરિક પ્રક્રિયાઓને નહીં. બ્રાન્ચ નિયમોમાં બંને ચેક નામો સક્રિય કરવા એ -એક અલગ વહીવટી ફેરફાર છે; આ જૉબ્સ ઉમેરવાથી પોતે જ કોઈ બ્રાન્ચ સુરક્ષિત થતી નથી. - -### સ્ટૅટિક સ્કૅન ઇન્વેન્ટરી - -વર્ઝન કરેલી npm-alias ઇન્વેન્ટરી અને સ્ટૅટિક-સ્કૅન સભ્યતા -`config/quality/gate-manifest.json`માં છે. `package.json` સામે -સ્ક્રિપ્ટ નામો અને ચોક્કસ કમાન્ડ્સને માન્ય કરવા માટે `npm run check:gate-manifest` ચલાવો; -ઉમેરાઓ, દૂર કરાયેલી વસ્તુઓ અને કમાન્ડ ડ્રિફ્ટ, બંને લોકલ હૂક અને CIમાંની -ફેરફાર-વર્ગીકરણ જૉબ્સને નિષ્ફળ બનાવે છે. -એલિયાસ કોઈ વર્કફ્લો જૉબ, મેટ્રિક્સ ઇન્સ્ટન્સ અથવા ટેસ્ટ કેસ નથી: આ ગણતરીઓને -પરસ્પર વિનિમયક્ષમ તરીકે રજૂ કરવી જોઈએ નહીં. - -પસંદ કરેલા એલિયાસને ચલાવ્યા વિના તપાસવા માટે `npm run quality:scan -- --list` અથવા +ડ્રાફ્ટ PR સ્વીકૃત ઉમેદવાર નથી. `hotfix` લેબલ પુરાવાની આવશ્યકતા દૂર કરતું નથી. + +બંને વર્કફ્લો PRs અને main/release બ્રાન્ચમાં થતા પુશ, મેન્યુઅલ ડિસ્પેચ અને +મર્જ-ગ્રુપ ઇવેન્ટ્સને આવરી લે છે. પુશ, ડિસ્પેચ અને મર્જ-ગ્રુપ સંપૂર્ણ પસંદગી ચલાવે છે. +ફોર્ક્સ અને મર્જ ગ્રુપ્સ એવી જોબ્સ માટે હોસ્ટેડ રનર્સનો ઉપયોગ કરે છે જે અન્યથા +સેલ્ફ-હોસ્ટેડ રનર્સ પસંદ કરે છે; રોલઆઉટ પહેલાં પર્યાપ્ત હોસ્ટેડ ક્ષમતાની ચકાસણી કરવી આવશ્યક છે. + +દરેક JSON રસીદ ચેકઆઉટ કરેલ SHA, વર્કફ્લો રન અને પ્રયાસને ઓળખાવે છે. +CLI ચેકઆઉટ/ઇવેન્ટ SHA મિસમૅચને નકારે છે. વર્કફ્લો ટેસ્ટ્સ નીતિ સભ્યપદને +ચુકાદા જોબની `needs` સૂચિ સાથે બાંધે છે, જેથી નવી અથવા દૂર કરેલી લેન ચૂપચાપ અદૃશ્ય ન થઈ શકે. +રસીદો પોતાના વર્કફ્લોને આવરી લે છે, પ્રકાશન, ડિપ્લોયમેન્ટ અથવા +હાલના સલાહરૂપ સ્કેનરની આંતરિક કાર્યપ્રણાલીને નહીં. બ્રાન્ચ નિયમોમાં બંને ચેક નામ સક્રિય કરવાં +એક અલગ વહીવટી ફેરફાર છે; આ જોબ્સ ઉમેરવાથી આપમેળે કોઈ બ્રાન્ચ સુરક્ષિત થતી નથી. + +### સ્ટેટિક સ્કેન ઇન્વેન્ટરી + +વર્ઝન કરેલી npm-alias ઇન્વેન્ટરી અને સ્ટેટિક-સ્કેન સભ્યપદ +`config/quality/gate-manifest.json`માં રહેલાં છે. સ્ક્રિપ્ટનાં નામો અને ચોક્કસ કમાન્ડ્સને +`package.json` સામે માન્ય કરવા માટે `npm run check:gate-manifest` ચલાવો; ઉમેરાઓ, દૂર કરેલી વસ્તુઓ અને +કમાન્ડમાં ફેરફારથી લોકલ હૂક તથા CIની ચેન્જ-ક્લાસિફિકેશન જોબ્સ બંને નિષ્ફળ જાય છે. +અલિયાસ એ વર્કફ્લો જોબ, મેટ્રિક્સ ઇન્સ્ટન્સ અથવા ટેસ્ટ કેસ નથી: આ ગણતરીઓને +એકબીજાના સમકક્ષ તરીકે રજૂ કરવી જોઈએ નહીં. + +પસંદ કરેલા અલિયાસને ચલાવ્યા વિના તપાસવા માટે `npm run quality:scan -- --list` અથવા `npm run quality:scan:fast -- --list`નો ઉપયોગ કરો. રનર -npm એન્ટ્રીપૉઇન્ટને ઇન્વોક કરે છે, તેથી તેનો રનટાઇમ (કૉન્ફિગર કરેલું હોય ત્યાં Bun સહિત) જળવાઈ રહે છે. -મેનિફેસ્ટ આ પ્રોફાઇલ્સની બહારના એલિયાસને અલગથી ઇન્વોક થયેલા તરીકે નોંધે છે, અને -રીડ-ઓનલી સ્કૅન પ્રોફાઇલ્સમાં મેન્ટેનન્સ કમાન્ડ્સ પ્રતિબંધિત છે. +npm એન્ટ્રીપૉઇન્ટને ઇન્વોક કરે છે, તેથી તેનો રનટાઇમ (જ્યાં કૉન્ફિગર કરેલું હોય ત્યાં Bun સહિત) જળવાય છે. +મેનિફેસ્ટ આ પ્રોફાઇલ્સની બહારના અલિયાસને અલગથી ઇન્વોક કરાયેલા તરીકે નોંધે છે અને +રીડ-ઓનલી સ્કેન પ્રોફાઇલ્સમાં મેન્ટેનન્સ કમાન્ડ્સ પ્રતિબંધિત છે. -આ પ્રોફાઇલ્સ માત્ર સ્ટૅટિક સ્કૅનને આવરી લે છે. તેઓ પ્રોડક્ટ પરીક્ષણો, +આ પ્રોફાઇલ્સ માત્ર સ્ટેટિક સ્કેનને આવરી લે છે. તેઓ પ્રોડક્ટ ટેસ્ટ્સ, કવરેજ, પૅકેજિંગ, બાહ્ય ચેક્સ અથવા ઉમેદવારની સંપૂર્ણ રિલીઝ સ્વીકૃતિને પ્રમાણિત કરતા નથી. -વર્કફ્લો સ્વીકૃતિ લિંક કરેલી `config/quality/admission-policy.json` અને -`scripts/quality/admission-verdict.mjs`નો ઉપયોગ કરે છે. Release-observer પ્રોફાઇલ્સ અલગ રહે છે; -તેમના લાગુ પડતા ચેક્સ અને રસીદો સ્વતંત્ર રીતે તપાસો. નીચે આપેલી ગદ્યરૂપ -ઇન્વેન્ટરી સંદર્ભ છે, ગેટ ખરેખર ચાલ્યો હોવાનો પુરાવો નહીં. +વર્કફ્લો સ્વીકૃતિ લિંક કરેલા `config/quality/admission-policy.json` અને +`scripts/quality/admission-verdict.mjs`નો ઉપયોગ કરે છે. રિલીઝ-ઑબ્ઝર્વર પ્રોફાઇલ્સ અલગ રહે છે; +તેમના લાગુ પડતા ચેક્સ અને રસીદોનું સ્વતંત્ર રીતે નિરીક્ષણ કરો. નીચેની ગદ્યરૂપ +ઇન્વેન્ટરી એક સંદર્ભ છે, ગેટ ખરેખર ચાલ્યો હતો તેનો પુરાવો નહીં. -સ્ક્રિપ્ટ્સ `scripts/check/` (નીતિ ગેટ્સ) અને `scripts/quality/` (રૅચેટ એન્જિન) હેઠળ છે. +સ્ક્રિપ્ટ્સ `scripts/check/` (પૉલિસી ગેટ્સ) અને `scripts/quality/` (રૅચેટ એન્જિન) હેઠળ રહે છે. CI માટે સત્યનો અધિકૃત સ્રોત `.github/workflows/ci.yml` છે. ### રિલીઝ PR ફાસ્ટ-પાથ (`quality.yml`) `.github/workflows/quality.yml` main/release PRs, સુરક્ષિત-બ્રાન્ચ -પુશ, ડિસ્પૅચ અને merge groups પર CIને પૂરક બને છે. PRs પાથ-ફિલ્ટર કરેલા ઝડપી ચેક્સનો ઉપયોગ કરે છે. -કાયમી રીતે અક્ષમ કરાયેલ ડુપ્લિકેટ બિલ્ડ દૂર કરવામાં આવ્યું હતું; વાસ્તવિક -બિલ્ડ/પૅકેજ/બૂટ ચેક્સ CIમાં યથાવત્ છે. - -| જૉબ | વ્યાપ | અવરોધક | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | ડૉક્સ/કોડ PRs; API ડૉક્સ સંદર્ભો અને docs-all | હા | -| `Fast Quality Gates` | કોડ PRs; સ્ટૅટિક ચેક્સ, ટાઇપચેક, ડૅશબોર્ડ ટાઇપચેક, પ્રભાવિત યુનિટ ટેસ્ટ્સ | હા | -| `Forgotten sibling tests` | કોડ PRs; બદલાયેલા મોડ્યુલ્સને સ્ટૅટિક કન્ઝ્યુમર્સ અને સંભવિત સિબલિંગ ટેસ્ટ્સ સુધી ટ્રેસ કરવામાં આવે છે; બૅરલ અને ડાયનેમિક-ઇમ્પોર્ટ પાથ્સને સંદર્ભિત અલાઉલિસ્ટ અપવાદો સાથે સલાહરૂપ નિદાન તરીકે રિપોર્ટ કરવામાં આવે છે | **સલાહરૂપ** | -| `Vitest (fast-path)` | કોડ PRs; ઝડપી vitest સ્યુટ | હા | -| `Unit Tests fast-path` | કોડ PRs; 4-શાર્ડ યુનિટ સ્યુટ | હા | -| `No new ESLint warnings` | કોડ PRs; સપ્રેશન-અવેર લિન્ટ ગાર્ડ | હા, ફોર્ક્સ સહિત | -| `Merge integrity (changelog + generated skills)` | નૉન-ડ્રાફ્ટ PRs; ચેન્જલૉગ અને જનરેટેડ સ્કિલ સિંક | હા, ફોર્ક્સ સહિત | - -#### ભૂલી જવાયેલા સિબલિંગ ટેસ્ટ્સનો રિપોર્ટ - -`npm run check:forgotten-sibling-tests` ટેસ્ટ-ઇમ્પેક્ટ મૅપ પાછળના ઇમ્પોર્ટ રિઝૉલ્વરનો પુનઃઉપયોગ કરે છે. +પુશ, ડિસ્પેચ અને મર્જ ગ્રુપ્સ પર CIને પૂરક બને છે. PRs પાથ-ફિલ્ટર કરેલા ઝડપી ચેક્સનો ઉપયોગ કરે છે. +કાયમી રીતે નિષ્ક્રિય ડુપ્લિકેટ બિલ્ડ દૂર કરવામાં આવ્યું હતું; વાસ્તવિક બિલ્ડ/પૅકેજ/બૂટ ચેક્સ CIમાં રહે છે. + +| જોબ | વ્યાપ | અવરોધક | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `Docs Gates (fast-path)` | ડૉક્સ/કોડ PRs; API ડૉક્સ રેફરન્સ અને docs-all | હા | +| `Fast Quality Gates` | કોડ PRs; સ્ટેટિક ચેક્સ, ટાઇપચેક, ડૅશબોર્ડ ટાઇપચેક, અસરગ્રસ્ત યુનિટ ટેસ્ટ્સ | હા | +| `Forgotten sibling tests` | કોડ PRs; બદલાયેલા મોડ્યુલ્સથી સ્ટેટિક કન્ઝ્યુમર્સ અને સંભવિત સિબ્લિંગ ટેસ્ટ્સ સુધી ટ્રેસિંગ; બેરલ અને ડાયનેમિક-ઇમ્પોર્ટ પાથ્સ સંદર્ભિત અલાઉલિસ્ટ અપવાદો સાથે સલાહરૂપ ડાયગ્નોસ્ટિક્સ તરીકે રિપોર્ટ થાય છે | **સલાહરૂપ** | +| `Vitest (fast-path)` | કોડ PRs; ઝડપી vitest સ્યુટ | હા | +| `Unit Tests fast-path` | કોડ PRs; 4-શાર્ડ યુનિટ સ્યુટ | હા | +| `No new ESLint warnings` | કોડ PRs; સપ્રેશન-અવેર લિન્ટ ગાર્ડ | હા, ફોર્ક્સ સહિત | +| `Merge integrity (changelog + generated skills)` | નોન-ડ્રાફ્ટ PRs; ચેન્જલૉગ અને જનરેટ કરેલી સ્કિલ્સનું સિંક્રનાઇઝેશન | હા, ફોર્ક્સ સહિત | + +#### ભૂલી જવાયેલા સિબ્લિંગ ટેસ્ટ્સનો રિપોર્ટ + +`npm run check:forgotten-sibling-tests` ટેસ્ટ-ઇમ્પેક્ટ મૅપ પાછળના ઇમ્પોર્ટ રિઝોલ્વરનો પુનઃઉપયોગ કરે છે. દરેક બદલાયેલા પ્રોડક્શન મોડ્યુલ માટે, જ્યારે સંભવિત ટેસ્ટ pull-request ડિફમાં ગેરહાજર હોય ત્યારે તે નિર્ધારિત -`changed module/symbol -> static consumer -> candidate sibling test` શૃંખલાઓ રિપોર્ટ કરે છે. +`changed module/symbol -> static consumer -> candidate sibling test` ચેઇન્સ રિપોર્ટ કરે છે. કોઈપણ અવરોધક રોલઆઉટ પહેલાં કૅલિબ્રેશન માટે Markdown સારાંશ અને JSON પરિણામને -`forgotten-sibling-tests` વર્કફ્લો આર્ટિફૅક્ટ તરીકે જાળવી રાખવામાં આવે છે. +`forgotten-sibling-tests` વર્કફ્લો આર્ટિફેક્ટ તરીકે જાળવી રાખવામાં આવે છે. -Barrel રી-એક્સપોર્ટ્સ અને ડાયનેમિક ઇમ્પોર્ટ્સ માત્ર રિઝોલ્યુશન ડાયગ્નોસ્ટિક્સ છે; તેઓ ક્યારેય +બેરલ રી-એક્સપોર્ટ્સ અને ડાયનેમિક ઇમ્પોર્ટ્સ માત્ર રિઝોલ્યુશન ડાયગ્નોસ્ટિક્સ છે; તેઓ ક્યારેય અવરોધક તારણ બનાવતા નથી. સમીક્ષા કરાયેલા અપવાદો -`config/quality/forgotten-sibling-allowlist.json`માં રહે છે. દરેક એન્ટ્રીએ consumer અને candidate -ટેસ્ટનું નામ આપવું, ચોક્કસ તર્ક રજૂ કરવો અને GitHub issue અથવા pull requestની લિંક આપવી આવશ્યક છે. ખોટી રીતે રચાયેલી એન્ટ્રીઓ -બંધ સ્થિતિમાં નિષ્ફળ જાય છે. અપવાદો કાઢી નાખવામાં આવેલા candidate ટેસ્ટ અથવા `.skip`/`.todo` ઉમેરતા diffને દબાવી શકતા નથી; -assertionને નબળું બનાવવું અને અન્ય masking સ્વતંત્ર રીતે અવરોધક -`check:test-masking` gateની જવાબદારી હેઠળ રહે છે. +`config/quality/forgotten-sibling-allowlist.json`માં રહે છે. દરેક એન્ટ્રીએ કન્ઝ્યુમર અને ઉમેદવાર +ટેસ્ટનું નામ આપવું, ચોક્કસ કારણ રજૂ કરવું અને GitHub ઇશ્યૂ અથવા પુલ રિક્વેસ્ટની લિંક આપવી આવશ્યક છે. ખોટી રચનાવાળી એન્ટ્રીઓ +બંધ સ્થિતિમાં નિષ્ફળ જાય છે. અપવાદો કાઢી નાખવામાં આવેલી ઉમેદવાર ટેસ્ટ અથવા `.skip`/`.todo` ઉમેરતા ડિફને દબાવી શકતા નથી; +અસર્શનને નબળું પાડવું અને અન્ય માસ્કિંગ સ્વતંત્ર રીતે અવરોધક +`check:test-masking` ગેટની જવાબદારી હેઠળ રહે છે. ### જોબ: `lint` -`main` માટેના દરેક PR પર ચાલે છે. નિષ્ફળતા આવે તો mergeને અવરોધે છે. - -| સ્ક્રિપ્ટ (`npm run ...`) | શું માન્ય કરે છે | અવરોધક | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:node-runtime` | Node.jsનું version સમર્થિત શ્રેણીમાં છે | હા | -| `check:cycles` | Circular imports — બધા `src/` + `open-sse/` modules | હા | -| `check:route-validation:t06` | બધી routes પર Zod schemas હાજર છે (Tier 6 policy) | હા | -| `check:any-budget:t11` | `@ts-expect-error // any`ની સંખ્યા budgetથી વધારે નથી (Tier 11 catraca) | હા | -| `check:provider-consistency` | `providers.ts` માંના દરેક provider માટે `providerRegistry.ts` માં મેળ ખાતી entry છે (અને તેનાથી વિપરીત પણ, allowlist ની મર્યાદામાં) | હા | -| `check:model-lifecycle` | હાથથી જાળવવામાં આવતા ત્રણ routing table, check-in કરેલા lifecycle snapshot (#11503) સાથે સુસંગત રહે છે: `FITNESS_TABLE` (`taskFitness.ts`) એવા કોઈ retired id ને score આપતું નથી જેને `REGISTRY` route કરી શકે; દરેક `BUILT_IN_ALIASES` target `REGISTRY` માં હાજર છે અને retired-id snapshot માં ગેરહાજર છે; `REGISTRY` માં હજુ પણ રહેલા દરેક retired id ને forward કરવામાં આવ્યો છે અથવા `allowedRetiredInCatalog` માં સૂચિબદ્ધ કરવામાં આવ્યો છે; અને કોઈપણ `DEFAULT_DEGRADATION_MAP` source કે target તે snapshot માં retired તરીકે દેખાતું નથી. આ સાબિત કરતું નથી કે કોઈ model હાલમાં live upstream દ્વારા serve કરવામાં આવે છે. Offline — `config/quality/model-lifecycle.json` સાથે સરખામણી કરે છે, જેને `npm run quality:refresh-model-lifecycle` વડે હાથથી refresh કરવામાં આવે છે (network; CI સાથે જોડાયેલું નથી). `allowedRetiredInCatalog` એ burn-down ratchet છે: entry માત્ર tracking issue સાથે જ ઉમેરો. | હા | -| `check:fetch-targets` | client-side `src/` માંનું દરેક `fetch("/api/...")` વાસ્તવિક `route.ts` પર resolve થાય છે | હા | -| `check:deps` | repo માંના દરેક `package.json` માં રહેલી બધી `npm install` કરી શકાય તેવી deps `dependency-allowlist.json` માં છે; નવી unpinned અથવા slopsquatted packages ને flag કરવામાં આવે છે | હા | -| `audit:deps` | `npm audit` (root + electron) — કોઈ high/critical advisories નથી (osv `check:vuln-ratchet` સાથે overlap થાય છે; Rationalization Backlog જુઓ) | હા | -| `check:lockfile` | `package-lock.json` ની integrity — https registry, integrity hashes, કોઈ host overrides નહીં | હા | -| `check:licenses` | પ્રોડક્શન ડિપેન્ડન્સીઓ માટે SPDX લાઇસન્સ અનુમતિસૂચિ | હા | -| `check:tracked-artifacts` | કોઈ બિલ્ડ આર્ટિફેક્ટ્સ / કમિટ કરેલી `node_modules` સિમલિંક્સ નહીં (husky pre-commitમાં પણ ચાલે છે; pre-pushને ઇરાદાપૂર્વક હળવું રાખવામાં આવ્યું છે — #6716) | હા | -| `check:ai-attribution` | PR કમિટ્સ, શીર્ષક અથવા બોડીમાં કોઈ AI/bot `Co-Authored-By` ટ્રેલર અથવા AI-જનરેશન ફૂટર નહીં — કડક નિયમ #16 (`quality.yml`ના PR→`release/**` માટેના fast-gates લૂપમાં — ઇવેન્ટ પેલોડ વાંચે છે, PR ન હોય ત્યારે કંઈ કરતું નથી — અને PR→`main` માટે `ci.yml` lintમાં માત્ર-PR પગલું; તેમજ husky `commit-msg` હૂક; માનવીય સહ-લેખકોને મંજૂરી છે; #14436) | -| `check:vitest-exclusions` | દરેક Vitest એક્સક્લૂઝનમાં ટ્રેકિંગ ઇશ્યૂનો ઉલ્લેખ હોવો જોઈએ અને તે `config/quality/vitest-exclusions.json`માં દેખાવું જોઈએ (#13204) | હા | -| `check:file-size` | કોઈ સોર્સ ફાઇલ પ્રતિ-એક્સ્ટેન્શન મર્યાદાથી મોટી ન હોવી જોઈએ (રૅચેટ: `frozen` સૂચિમાં સ્થિર કરેલી મોટી ફાઇલો) | હા | -| `check:error-helper` | એક્ઝિક્યુટર્સ/હેન્ડલર્સમાં ભૂલ પ્રતિસાદો `buildErrorBody()` / `sanitizeErrorMessage()`નો ઉપયોગ કરે છે (કડક નિયમ #12) | હા | -| `check:migration-numbering` | Migration SQL ફાઇલો ક્રમિક રીતે નંબર થયેલી છે, તેમાં કોઈ ખાલી ક્રમ અથવા ડુપ્લિકેટ નથી | હા | -| `check:public-creds` | `publicCreds.ts`ની બહાર કોઈ શાબ્દિક OAuth `client_id`/`client_secret` અથવા Firebase Web કી નથી (કડક નિયમ #11) | હા | -| `check:db-rules` | `src/lib/db/` મોડ્યુલોની બહાર કોઈ કાચું SQL નથી; `localDb.ts`માંથી કોઈ બેરલ-ઇમ્પોર્ટ નથી (કડક નિયમો #2/#5) | હા | -| `check:known-symbols` | તેમની ડિસ્પેચ કોષ્ટકોમાં નોંધાયેલા પ્રદાતા એક્ઝિક્યુટર્સ, રાઉટિંગ વ્યૂહરચનાઓ અને અનુવાદકો ડિસ્ક પરની ફાઇલો સાથે મેળ ખાય છે — કોઈ અનાથ અથવા અઘોષિત સિમ્બોલ નથી | હા | -| `check:route-guard-membership` | ચાઇલ્ડ પ્રોસેસ શરૂ કરતો દરેક રૂટ `isLocalOnlyPath()` દ્વારા વર્ગીકૃત થયેલો છે (કડક નિયમો #15/#17) | હા | -| `check:test-discovery` | રેપોમાંની દરેક `*.test.ts` / `*.spec.ts` ફાઇલ ઓછામાં ઓછા એક ટેસ્ટ રનર દ્વારા એકત્રિત કરવામાં આવે છે (રેચેટ: `test-discovery-baseline.json`માંની અનાથ સૂચિ માત્ર ઘટી શકે છે) | હા | -| `check:agent-skills-sync` | જનરેટ કરેલા agent-skills આર્ટિફેક્ટ્સ તેમના સ્રોત કેટલોગ સાથે મેળ ખાય છે (કોઈ વિસંગતિ નથી) | -| `check:provider-asset-provenance` | પ્રોવાઇડર લોગો/એસેટ્સ માટે નોંધાયેલ મૂળસ્રોત એન્ટ્રી ઉપલબ્ધ છે | -| `lint:json` | JSON કન્ફિગ ફાઇલો પાર્સ થાય છે અને repoના lint નિયમોનું પાલન કરે છે | -| `typecheck:core` | ભૂલો વિના TypeScript કમ્પાઇલેશન (માત્ર સલાહરૂપ ચેતવણીઓ) | હા | -| `typecheck:noimplicit:core` | કડક `noImplicitAny` — ભવિષ્યલક્ષી; અગાઉથી અસ્તિત્વ ધરાવતી ઘણી call sitesને હજુ પણ annotationsની જરૂર છે | **સલાહરૂપ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` સુધી મર્યાદિત `tsc` (#7033) — `typecheck:core`ની કાળજીપૂર્વક પસંદ કરેલી 27-ફાઇલ allowlistમાં કોઈ dashboard TSX સામેલ નથી અને `next build` પણ ક્યારેય તેનું type-checking કરતું નથી (`next.config.mjs`માં `ignoreBuildErrors: true` સેટ છે), તેથી ત્યાંના orphaned-identifier regressions (#6625/#6909) CIને દેખાતા નહોતા. સ્થિર કરેલા પ્રતિ-ફાઇલ/પ્રતિ-TS-code ગણતરી baseline (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` જેવી જ stale-enforcement પેટર્ન) સામેના તફાવતો — baselined ગણતરી કરતાં માત્ર નવી ભૂલો જ gateને નિષ્ફળ બનાવે છે; અગાઉથી અસ્તિત્વ ધરાવતી ભૂલ સુધારવામાં આવે ત્યારે `--update` વડે તેને ક્રમશઃ ઘટાડો. | હા | - -### જોબ: `quality-gate` +`main` માટેની દરેક PR પર ચાલે છે. નિષ્ફળતા આવે તો મર્જને અવરોધે છે. -`test-coverage` પછી ચાલે છે. નિષ્ફળતા થાય તો mergeને અવરોધે છે. - -| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `quality:collect` | `quality-metrics.json` ઉત્પન્ન કરે છે (ESLint ચેતવણીઓની સંખ્યા, મર્જ કરેલા શાર્ડ રિપોર્ટમાંથી કવરેજ) | હા (રૅચેટના અપસ્ટ્રીમમાં) | -| `quality:ratchet` | `quality-baseline.json` માંનું કોઈપણ મેટ્રિક કથળ્યું નથી (ESLint ચેતવણીઓ ≤ બેઝલાઇન; કવરેજ ≥ બેઝલાઇન) | હા | -| `check:duplication` | કોડ ડુપ્લિકેશન (jscpd@4) `quality-baseline.json` માંની બેઝલાઇન કરતાં વધી જતું નથી | હા | -| `check:complexity` | ફાઇલ-સ્તરની સાઇક્લોમેટિક જટિલતા મર્યાદાથી વધતી નથી (મુખ્ય ESLint `complexity` + `max-lines-per-function`) | હા | -| `check:cognitive-complexity` | કોગ્નિટિવ જટિલતા રૅચેટ (`eslint-plugin-sonarjs`) — અલગ ESLint પાસ; CI બંનેને એક જ `check:complexity-ratchets` સ્ટેપ તરીકે મર્જ કરીને ચલાવે છે | હા | -| `check:dead-code` | વણવપરાયેલા એક્સપોર્ટ્સ / ફાઇલોનું રૅચેટ (knip) બેઝલાઇનની સરખામણીમાં કથળતું નથી | હા | -| `check:compression-budget` | કમ્પ્રેશન બેન્ચમાર્ક બજેટ — દરેક એન્જિન માટે ટોકન-બચતની લઘુતમ મર્યાદાઓ કથળવી જોઈએ નહીં | હા | -| `check:type-coverage` | ટાઇપ કરેલી ટકાવારીનું રૅચેટ (`type-coverage`) કથળતું નથી; તે મોટાભાગે `typecheck:noimplicit:core` ને આવરી લે છે | હા | -| `check:codeql-ratchet` | ખુલ્લી CodeQL ચેતવણીઓની સંખ્યા કથળતી નથી (`gh api` દ્વારા વાંચે છે; ટોકન વિના સુગમતાથી સ્કિપ કરે છે) — રિફ્રેશ આવર્તન અને મેન્યુઅલ ટ્રિગર માટે: નીચે "CodeQL રૅચેટ" જુઓ | હા | - -### જોબ: `quality-extended` - -આખી જોબ સલાહાત્મક છે (`continue-on-error: true`). npm-આધારિત રૅચેટ્સ વાસ્તવમાં ચાલે છે; -બાહ્ય સ્કેનર્સ `gh release download` દ્વારા ઇન્સ્ટોલ થાય છે અને જો બાઇનરી હજી પણ -ગેરહાજર હોય તો આપમેળે સ્કિપ થાય છે (exit 0). - -| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | -| `check:circular-deps` | કોઈ સર્ક્યુલર ડિપેન્ડન્સી નથી (dpdm) | **સલાહાત્મક** | -| `check:bundle-size` | બંડલનું કદ મર્યાદાથી વધતું નથી | **સલાહાત્મક** | -| `check:secrets` | સિક્રેટ સ્કેનિંગ (gitleaks) — બાઇનરી ગેરહાજર હોય તો સ્કિપ કરે છે | **સલાહાત્મક** | -| `check:vuln-ratchet` | ડિપેન્ડન્સીની નબળાઈઓ (osv-scanner) કથળતી નથી — બાઇનરી ગેરહાજર હોય તો સ્કિપ કરે છે | **સલાહાત્મક** | -| `check:workflows` | વર્કફ્લો લિન્ટ (actionlint + zizmor); ગેરહાજર/ખામીયુક્ત સ્કેનર્સ, અમાન્ય રિપોર્ટ્સ અથવા ગેરહાજર રૅચેટ બેઝલાઇનને કારણે INCOMPLETE તરીકે નિષ્ફળ થાય છે. માન્ય તારણો પસંદ કરેલી કડક/સલાહાત્મક/રૅચેટ નીતિને અનુસરે છે | એક્ઝિક્યુશન આવશ્યક; CI માં zizmor રૅચેટ અવરોધક છે | -| `check:openapi-breaking` | બેઝ બ્રાન્ચની સરખામણીમાં જાહેર API કરાર (`openapi.yaml`) માં ભંગાણકારી ફેરફારો (oasdiff) — `openapiBreaking=N` ઉત્પન્ન કરે છે; oasdiff ગેરહાજર હોય અથવા બેઝ સ્પેકનું નિરાકરણ ન થઈ શકે તો સ્કિપ કરે છે | **સલાહાત્મક** | - -### જોબ: `docs-sync-strict` +| સ્ક્રિપ્ટ (`npm run ...`) | શું માન્ય કરે છે | અવરોધક | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Node.js વર્ઝન સમર્થિત શ્રેણીમાં છે | હા | +| `check:cycles` | સમગ્ર `src/` + `open-sse/`માં સર્ક્યુલર ઇમ્પોર્ટ્સ (AST-આધારિત, tsconfig `paths` રિઝોલ્વ કરેલા). બેર = સલાહરૂપ, સાયકલ્સની યાદી આપે છે. `check:cycles:ratchet` (જે CI ચલાવે છે) ત્યારે અવરોધે છે જ્યારે સંખ્યા `quality-baseline.json`માંની `metrics.cycles` મર્યાદા કરતાં વધી જાય — હાલમાં 14, `direction: down`, તેથી તે માત્ર ઘટી શકે છે (#15159 G-01/G-02) | હા (રૅચેટ) | +| `check:route-validation:t06` | તમામ રૂટ્સ પર Zod સ્કીમા હાજર છે (ટિયર 6 નીતિ) | હા | +| `check:any-budget:t11` | `@ts-expect-error // any`ની સંખ્યા બજેટ કરતાં વધી નથી (ટિયર 11 કાત્રાકા) | હા | +| `check:provider-consistency` | `providers.ts` માંનો દરેક provider `providerRegistry.ts` માં અનુરૂપ entry ધરાવે છે (અને allowlist ની મર્યાદામાં તેનાથી વિપરીત પણ) | હા | +| `check:model-lifecycle` | હાથથી જાળવવામાં આવતા ત્રણ routing tables checked-in lifecycle snapshot (#11503) સાથે સુસંગત રહે છે: `FITNESS_TABLE` (`taskFitness.ts`) એવા કોઈ retired id ને score આપતું નથી જેને `REGISTRY` route કરી શકે; દરેક `BUILT_IN_ALIASES` target `REGISTRY` માં હાજર છે અને retired-id snapshot માં ગેરહાજર છે; `REGISTRY` માં હજી રહેલી દરેક retired id ને forward કરવામાં આવી છે અથવા `allowedRetiredInCatalog` માં સૂચિબદ્ધ કરવામાં આવી છે; અને કોઈ પણ `DEFAULT_DEGRADATION_MAP` source અથવા target તે snapshot માં retired તરીકે દેખાતું નથી. આ સાબિત કરતું નથી કે કોઈ model હાલમાં live upstream દ્વારા serve કરવામાં આવે છે. Offline — `config/quality/model-lifecycle.json` સાથે તુલના કરે છે, જેને `npm run quality:refresh-model-lifecycle` વડે હાથથી refresh કરવામાં આવે છે (network; CI સાથે જોડાયેલ નથી). `allowedRetiredInCatalog` એક burn-down ratchet છે: માત્ર tracking issue સાથે જ entry ઉમેરો. | હા | +| `check:fetch-targets` | client-side `src/` માંનો દરેક `fetch("/api/...")` વાસ્તવિક `route.ts` તરફ resolve થાય છે | હા | +| `check:deps` | repo માંના દરેક `package.json` ની તમામ `npm install` કરી શકાય તેવી deps `dependency-allowlist.json` માં છે; નવા unpinned અથવા slopsquatted packages ને flag કરવામાં આવે છે | હા | +| `audit:deps` | `npm audit` (root + electron) — કોઈ high/critical advisories નથી (osv `check:vuln-ratchet` સાથે overlap થાય છે; Rationalization Backlog જુઓ) | હા | +| `check:lockfile` | `package-lock.json` ની integrity — https registry, integrity hashes, કોઈ host overrides નહીં | હા | +| `check:licenses` | પ્રોડક્શન ડિપેન્ડન્સીઓ માટે SPDX લાઇસન્સની મંજૂર સૂચિ | હા | +| `check:tracked-artifacts` | કોઈ બિલ્ડ આર્ટિફેક્ટ્સ / કમિટ કરેલી `node_modules` સિમલિંક્સ નહીં (husky pre-commitમાં પણ ચાલે છે; pre-pushને ઇરાદાપૂર્વક હળવું રાખવામાં આવ્યું છે — #6716) | હા | +| `check:ai-attribution` | PR કમિટ્સ, શીર્ષક અથવા મુખ્ય લખાણમાં કોઈ AI/bot `Co-Authored-By` ટ્રેલર અથવા AI-જનરેશન ફૂટર નહીં — કડક નિયમ #16 (`quality.yml`ના PR→`release/**` માટેના fast-gates લૂપમાં — ઇવેન્ટ પેલોડ વાંચે છે, PR ન હોય ત્યારે કંઈ કરતું નથી — અને `ci.yml` lintમાં PR→`main` માટે માત્ર-PR પગલું; તેમજ husky `commit-msg` હૂક; માનવીય સહ-લેખકોને મંજૂરી છે; #14436) | +| `check:vitest-exclusions` | દરેક Vitest બાકાત રાખવાની નોંધમાં ટ્રેકિંગ ઇશ્યૂનો ઉલ્લેખ હોવો જોઈએ અને તે `config/quality/vitest-exclusions.json`માં દેખાવી જોઈએ (#13204) | હા | +| `check:file-size` | કોઈ સોર્સ ફાઇલ પ્રતિ-એક્સ્ટેન્શન મર્યાદા કરતાં મોટી ન હોવી જોઈએ (રૅચેટ: `frozen` સૂચિમાં સ્થિર કરેલી મોટી ફાઇલો) | હા | +| `check:error-helper` | executors/handlersમાં ભૂલ પ્રતિસાદો `buildErrorBody()` / `sanitizeErrorMessage()`નો ઉપયોગ કરે છે (કડક નિયમ #12) | હા | +| `check:migration-numbering` | Migration SQL ફાઇલોને ક્રમિક રીતે નંબર આપવામાં આવ્યા છે, કોઈ ખાલી ક્રમ કે ડુપ્લિકેટ નથી | હા | +| `check:public-creds` | `publicCreds.ts` ની બહાર કોઈ લિટરલ OAuth `client_id`/`client_secret` અથવા Firebase Web કી નથી (કડક નિયમ #11) | હા | +| `check:db-rules` | `src/lib/db/` મોડ્યુલોની બહાર કોઈ રૉ SQL નથી; `localDb.ts` માંથી કોઈ બેરલ-ઇમ્પોર્ટ નથી (કડક નિયમો #2/#5) | હા | +| `check:known-symbols` | તેમના ડિસ્પેચ ટેબલમાં નોંધાયેલા પ્રોવાઇડર એક્ઝિક્યુટર્સ, રાઉટિંગ વ્યૂહરચનાઓ અને ટ્રાન્સલેટર્સ ડિસ્ક પરની ફાઇલો સાથે મેળ ખાય છે — કોઈ અનાથ અથવા અઘોષિત સિમ્બોલ નથી | હા | +| `check:route-guard-membership` | ચાઇલ્ડ પ્રોસેસ શરૂ કરતો દરેક રૂટ `isLocalOnlyPath()` દ્વારા વર્ગીકૃત થયેલો છે (કડક નિયમો #15/#17) | હા | +| `check:test-discovery` | રેપોમાંની દરેક `*.test.ts` / `*.spec.ts` ફાઇલ ઓછામાં ઓછા એક ટેસ્ટ રનર દ્વારા એકત્રિત કરવામાં આવે છે (રૅચેટ: `test-discovery-baseline.json` માંની અનાથ યાદી માત્ર નાની થઈ શકે છે) | હા | +| `check:agent-skills-sync` | જનરેટ કરેલી agent-skills આર્ટિફેક્ટ્સ તેમના સ્રોત કેટલોગ સાથે મેળ ખાય છે (કોઈ વિસંગતિ નથી) | +| `check:provider-asset-provenance` | પ્રદાતા લોગો/એસેટ્સ માટે નોંધાયેલ મૂળસ્રોતની એન્ટ્રી છે | +| `lint:json` | JSON કૉન્ફિગ ફાઇલો પાર્સ થાય છે અને રિપોઝિટરીના લિન્ટ નિયમોને સંતોષે છે | +| `typecheck:core` | ભૂલો વિનાનું TypeScript કમ્પાઇલેશન (માત્ર સલાહરૂપ ચેતવણીઓ) | હા | +| `typecheck:noimplicit:core` | કડક `noImplicitAny` — ભવિષ્યલક્ષી; અગાઉથી અસ્તિત્વમાં રહેલી ઘણી કૉલ સાઇટ્સને હજી પણ ઍનોટેશન્સની જરૂર છે | **સલાહરૂપ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` સુધી મર્યાદિત `tsc` (#7033) — `typecheck:core`ની પસંદ કરેલી 27-ફાઇલની મંજૂરીસૂચિમાં કોઈ dashboard TSX સામેલ નથી અને `next build` પણ તેને ક્યારેય ટાઇપ-ચેક કરતું નથી (`next.config.mjs`માં `ignoreBuildErrors: true` સેટ કરેલું છે), તેથી ત્યાંના અનાથ-આઇડેન્ટિફાયર રિગ્રેશન્સ (#6625/#6909) CI માટે અદૃશ્ય હતા. સ્થિર કરેલા પ્રતિ-ફાઇલ/પ્રતિ-TS-કોડ ગણતરી બેઝલાઇન (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` જેવી જ જૂની-સ્થિતિ અમલીકરણ પૅટર્ન) સામે તફાવતો તપાસે છે — બેઝલાઇન કરેલી ગણતરીથી વધુ માત્ર નવી ભૂલો જ ગેટને નિષ્ફળ બનાવે છે; અગાઉથી અસ્તિત્વમાં રહેલી ભૂલ સુધારવામાં આવે ત્યારે `--update` વડે બેઝલાઇન ઘટાડો. | હા | -`main` માટેની દરેક PR પર ચાલે છે. નિષ્ફળતા આવે તો મર્જને અવરોધે છે. +### જોબ: `quality-gate` -| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `check:docs-all` | નીચેના 6 પેટા-ગેટ્સને ક્રમશઃ ચલાવતું મેટા-ગેટ | હા | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt સંસ્કરણ સુસંગતતા | હા | -| ↳ `check:docs-counts` | ગદ્યમાં આપેલી ગણતરીઓ (પ્રદાતાની સંખ્યા, માઇગ્રેશનની સંખ્યા વગેરે) વાસ્તવિક ગણતરીઓની રેચેટ વિન્ડોની અંદર છે | હા | -| ↳ `check:env-doc-sync` | `.env.example` માંનો દરેક env var દસ્તાવેજોના કોષ્ટકમાં દસ્તાવેજીકૃત છે અને તેનાથી વિપરીત પણ | હા | -| ↳ `check:deprecated-versions` | દસ્તાવેજોમાં કોઈ અપ્રચલિત સંસ્કરણ સ્ટ્રિંગ નથી | હા | -| ↳ `check:doc-links` | દસ્તાવેજોમાંની આંતરિક markdown લિંક્સ વાસ્તવિક ફાઇલો તરફ નિર્દેશ કરે છે (`[text]`/`(path)` સ્વરૂપ) | હા | -| ↳ `check:fabricated-docs` | દસ્તાવેજોમાં ઉલ્લેખિત routes, env vars, CLI commands, hook names અને file paths કોડબેઝમાં અસ્તિત્વ ધરાવે છે. `--strict` દ્વારા હાર્ડ ગેટ; ફ્લૅગ વિના સોફ્ટ-ફેલ. | હા (CIમાં `--strict` દ્વારા) | -| `check:cli-i18n` | CLI command strings બધી i18n locale filesમાં હાજર છે | હા | -| `check:openapi-coverage` | OpenAPI spec વાસ્તવિક routesના ઓછામાં ઓછા રેચેટેડ ન્યૂનતમ પ્રમાણને આવરી લે છે | હા | -| `check:openapi-security-tiers` | `openapi.yaml` માંના security tier annotations `routeGuard.ts` classifications સાથે સુસંગત છે | **સલાહરૂપ** | -| `check:openapi-routes` | `openapi.yaml` માંનો દરેક path વાસ્તવિક `route.ts` તરફ નિર્દેશ કરે છે (ભ્રમજનિત માહિતી વિરોધી) | હા | -| `check:docs-symbols` | `docs/**/*.md` માંનો દરેક `/api/...` સંદર્ભ વાસ્તવિક `route.ts` તરફ નિર્દેશ કરે છે (ભ્રમજનિત માહિતી વિરોધી) | હા | -| `i18n translation drift` | i18n locale filesમાં અનુવાદ ન થયેલી keys — ફક્ત ચેતવણી | **સલાહરૂપ** | +`test-coverage` પછી ચાલે છે. નિષ્ફળતા આવે તો મર્જને અવરોધે છે. + +| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `quality:collect` | `quality-metrics.json` ઉત્પન્ન કરે છે (ESLint ચેતવણીઓની સંખ્યા, મર્જ કરેલા shard રિપોર્ટમાંથી coverage) | હા (ratchet પહેલાં) | +| `quality:ratchet` | `quality-baseline.json` માંનું દરેક metric ખરાબ થયું નથી (ESLint ચેતવણીઓ ≤ baseline; coverage ≥ baseline) | હા | +| `check:duplication` | કોડનું duplication (jscpd@4) `quality-baseline.json` માંના baseline કરતાં વધારે નથી | હા | +| `check:complexity` | ફાઇલ-સ્તરની cyclomatic complexity cap કરતાં વધારે નથી (મુખ્ય ESLint `complexity` + `max-lines-per-function`) | હા | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — અલગ ESLint pass; CI બંનેને એકમાત્ર `check:complexity-ratchets` પગલા તરીકે મર્જ કરીને ચલાવે છે | હા | +| `check:dead-code` | બિનઉપયોગી exports / files ratchet (knip) baseline ની સરખામણીમાં ખરાબ થતું નથી | હા | +| `check:compression-budget` | Compression benchmark budget — દરેક engine માટે token-savings ની લઘુતમ મર્યાદા ખરાબ થવી જોઈએ નહીં | હા | +| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) ખરાબ થતું નથી; મોટા ભાગે `typecheck:noimplicit:core` ને સમાવી લે છે | હા | +| `check:codeql-ratchet` | ખુલ્લા CodeQL alerts ની સંખ્યા ખરાબ થતી નથી (`gh api` દ્વારા વાંચે છે; token વિના સુગમ રીતે skip કરે છે) — refresh cadence અને manual trigger માટે: નીચે "CodeQL ratchet" જુઓ | હા | + +### Job: `quality-extended` + +સમગ્ર job સલાહરૂપ છે (`continue-on-error: true`). npm-આધારિત ratchets વાસ્તવમાં +ચાલે છે; બાહ્ય scanners `gh release download` દ્વારા install થાય છે અને binary હજુ પણ +ગેરહાજર હોય ત્યારે સ્વયં skip થાય છે (exit 0). + +| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:circular-deps` | કોઈ circular dependencies નથી (dpdm) | **સલાહરૂપ** | +| `check:bundle-size` | Bundle size cap કરતાં વધારે નથી | **સલાહરૂપ** | +| `check:secrets` | Secret scanning (gitleaks) — binary ગેરહાજર હોય તો skip કરે છે | **સલાહરૂપ** | +| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) ખરાબ થતી નથી — binary ગેરહાજર હોય તો skip કરે છે | **સલાહરૂપ** | +| `check:workflows` | Workflow lint (actionlint + zizmor); ગેરહાજર/ખામીયુક્ત scanners, અમાન્ય reports અથવા ગેરહાજર ratchet baseline ને કારણે INCOMPLETE તરીકે નિષ્ફળ થાય છે. માન્ય findings પસંદ કરેલી strict/advisory/ratchet policy અનુસરે છે | અમલ જરૂરી; CI માં zizmor ratchet અવરોધક | +| `check:openapi-breaking` | base branch (oasdiff) સામે public API contract (`openapi.yaml`) માં breaking changes — `openapiBreaking=N` ઉત્પન્ન કરે છે; oasdiff ગેરહાજર હોય અથવા base spec ઉકેલી ન શકાય તો skip કરે છે | **સલાહરૂપ** | + +### Job: `docs-sync-strict` + +`main` માટેની દરેક PR પર ચાલે છે. નિષ્ફળતા આવે તો merge અવરોધે છે. + +| સ્ક્રિપ્ટ | શું ચકાસે છે | અવરોધક | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| `check:docs-all` | નીચેના 6 સબ-ગેટ ક્રમિક રીતે ચલાવતો મેટા-ગેટ | હા | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt સંસ્કરણ સુસંગતતા | હા | +| ↳ `check:docs-counts` | ગદ્યમાં આપેલી સંખ્યાઓ (પ્રદાતા સંખ્યા, માઇગ્રેશન સંખ્યા વગેરે) વાસ્તવિક સંખ્યાઓની રેચેટ વિન્ડોની અંદર છે | હા | +| ↳ `check:env-doc-sync` | `.env.example` માંનો દરેક env var દસ્તાવેજોના કોષ્ટકમાં દસ્તાવેજીકૃત છે, અને તેનાથી વિપરીત પણ | હા | +| ↳ `check:deprecated-versions` | દસ્તાવેજોમાં કોઈ અપ્રચલિત સંસ્કરણ સ્ટ્રિંગ નથી | હા | +| ↳ `check:doc-links` | દસ્તાવેજોમાંની આંતરિક markdown લિંક્સ વાસ્તવિક ફાઇલો તરફ નિર્દેશ કરે છે (`[text]`/`(path)` સ્વરૂપ) | હા | +| ↳ `check:fabricated-docs` | દસ્તાવેજોમાં ઉલ્લેખિત રૂટ્સ, env vars, CLI કમાન્ડ્સ, હૂક નામો અને ફાઇલ પાથ્સ કોડબેઝમાં અસ્તિત્વ ધરાવે છે. `--strict` દ્વારા હાર્ડ ગેટ; ફ્લૅગ વિના સોફ્ટ-ફેલ. | હા (CI માં `--strict` દ્વારા) | +| `check:cli-i18n` | CLI કમાન્ડ સ્ટ્રિંગ્સ બધી i18n લોકેલ ફાઇલોમાં હાજર છે | હા | +| `check:openapi-coverage` | OpenAPI સ્પેક વાસ્તવિક રૂટ્સના ઓછામાં ઓછા રેચેટ કરેલા લઘુત્તમ સ્તરને આવરી લે છે | હા | +| `check:openapi-security-tiers` | `openapi.yaml` માંના સુરક્ષા સ્તરના ઍનોટેશન્સ `routeGuard.ts` ના વર્ગીકરણો સાથે સુસંગત છે | **સલાહરૂપ** | +| `check:openapi-routes` | `openapi.yaml` માંનો દરેક પાથ વાસ્તવિક `route.ts` તરફ નિર્દેશ કરે છે (ભ્રમ-વિરોધી) | હા | +| `check:docs-symbols` | `docs/**/*.md` માંનો દરેક `/api/...` સંદર્ભ વાસ્તવિક `route.ts` તરફ નિર્દેશ કરે છે (ભ્રમ-વિરોધી) | હા | +| `i18n translation drift` | i18n લોકેલ ફાઇલોમાં અનુવાદ ન થયેલી કીઓ — માત્ર ચેતવણી | **સલાહરૂપ** | ### જોબ: `i18n-ui-coverage` -| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check-ui-keys-coverage` (ઇનલાઇન) | UI i18n key coverage ≥ 65% છે | હા | -| `check-ui-value-drift` (ઇનલાઇન) | ફરી લખાયેલું અંગ્રેજી **value** કોઈ જૂનો અનુવાદ પાછળ છોડતું નથી | હા | -| `check-new-key-coverage` (ઇનલાઇન) | **નવી** અંગ્રેજી key દરેક localeમાં અનુવાદિત છે — `__MISSING__:` marker અસ્વીકારવામાં આવે છે | હા | -| `check-translation-ratio` | locale દીઠ વાસ્તવિક અનુવાદનું પ્રમાણ (allowlistની બહારનાં અંગ્રેજી-સમાન / placeholder / missing leaves) `config/quality/i18n-translation-baseline.json` + slack કરતાં વધુ ન હોવું જોઈએ | **સલાહરૂપ** | +| સ્ક્રિપ્ટ | શું ચકાસે છે | અવરોધક | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check-ui-keys-coverage` (ઇનલાઇન) | UI i18n કી કવરેજ ≥ 65% છે | હા | +| `check-ui-value-drift` (ઇનલાઇન) | ફરીથી લખાયેલું અંગ્રેજી **મૂલ્ય** પાછળ કોઈ જૂનો અનુવાદ બાકી રાખતું નથી | હા | +| `check-new-key-coverage` (ઇનલાઇન) | એક **નવી** અંગ્રેજી કી દરેક લોકેલમાં અનુવાદિત છે — `__MISSING__:` માર્કર અસ્વીકારવામાં આવે છે | હા | +| `check-translation-ratio` | લોકેલ દીઠ વાસ્તવિક અનુવાદનો ગુણોત્તર (અંગ્રેજી સમાન / પ્લેસહોલ્ડર / મંજૂરીસૂચિની બહારની ખૂટતી લાઇનો) `config/quality/i18n-translation-baseline.json` + છૂટ કરતાં વધુ ન હોવો જોઈએ | **સલાહરૂપ** | -`fetch-depth: 0` જરૂરી છે — value-drift gate merge base સામે `en.json` નો diff કરે છે. +`fetch-depth: 0` જરૂરી છે — value-drift ગેટ `en.json` ને merge base સામે diff કરે છે. -#### `check-ui-value-drift` — જૂના અનુવાદ માટેનો ગેટ +#### `check-ui-value-drift` — જૂના અનુવાદનો ગેટ -આ તે એક i18n regression પકડે છે જે બીજા gates માળખાકીય રીતે જોઈ શકતા નથી: કોઈ અંગ્રેજી value -ફરીથી લખાય છે અને _અગાઉના_ અંગ્રેજી પરથી ઉત્પન્ન થયેલા અનુવાદો યથાવત્ રહી જાય છે, જેથી -બિન-અંગ્રેજી વપરાશકર્તાઓ આત્મવિશ્વાસપૂર્વક લખાયેલું, પરંતુ હવે ખોટું લખાણ વાંચતા રહે છે. +આ એવી એક i18n રિગ્રેશન પકડે છે જેને બીજા ગેટ્સ માળખાકીય રીતે જોઈ શકતા નથી: અંગ્રેજી મૂલ્ય +ફરીથી લખાય છે અને _અગાઉના_ અંગ્રેજીમાંથી મેળવેલા અનુવાદો યથાવત્ રહી જાય છે, તેથી +બિન-અંગ્રેજી વપરાશકર્તાઓ વિશ્વાસપૂર્વક લખાયેલું, પરંતુ હવે ખોટું લખાણ વાંચતા રહે છે. -આ ખરેખર રિલીઝમાં પહોંચી ગયું હતું. Antigravity -login helper ઉમેરાયો ત્યારે (#5203) `oauthModal.googleOAuthWarning` ફરીથી લખવામાં આવ્યું હતું; **43માંથી 39 locales** માં operatorsને "સંપૂર્ણ -URL કૉપિ કરીને નીચે પેસ્ટ કરો" કહેતો ટેક્સ્ટ યથાવત્ રહ્યો — એવો flow જે તે provider માટે પૂર્ણ થઈ જ શકતો નથી. આ બાબત -#8463 સુધી ધ્યાન બહાર રહી, કારણ કે: +આ વાસ્તવમાં રિલીઝ થયું હતું. Antigravity લૉગિન હેલ્પર ઉમેરાયો ત્યારે (#5203) +`oauthModal.googleOAuthWarning` ફરીથી લખાયું હતું; **43 માંથી 39 લોકેલ્સ** માં ઓપરેટર્સને "સંપૂર્ણ +URL કૉપિ કરીને નીચે પેસ્ટ કરો" એવું કહેતો ટેક્સ્ટ યથાવત્ રહ્યો — એક એવી પ્રક્રિયા જે તે પ્રદાતા માટે પૂર્ણ થઈ શકતી નથી. આ +#8463 સુધી ધ્યાનમાં આવ્યું નહીં, કારણ કે: -- `sync-ui-keys` માત્ર **ગેરહાજર** keysને જ backfill કરે છે, **જૂની** keysને ક્યારેય નહીં; -- `check-ui-keys-coverage` keyની _હાજરી_ ગણે છે, તેથી જૂનો અનુવાદ પણ આવરી લેવાયેલો ગણાય છે; -- `check-translation-drift` `docs/i18n//**.md` documentation mirrorsને ટ્રૅક કરે છે — - તે ક્યારેય `src/i18n/messages/*.json` વાંચતું નથી. 2026-09ના પુનઃ-સિંકથી job `docs-sync-strict` માં અવરોધક છે: કોઈ core doc સંપાદિત કરો → `npm run i18n:run -- --files=` (વિભાગ-સ્તરીય, ઓછા ખર્ચવાળું). +- `sync-ui-keys` ફક્ત **ગેરહાજર** કીઓને જ બૅકફિલ કરે છે, **જૂની** કીઓને ક્યારેય નહીં; +- `check-ui-keys-coverage` કીની _હાજરી_ ગણે છે, તેથી જૂનો અનુવાદ પણ આવરી લેવાયેલો ગણાય છે; +- `check-translation-drift` `docs/i18n//**.md` દસ્તાવેજીકરણ મિરર્સને ટ્રૅક કરે છે — + તે ક્યારેય `src/i18n/messages/*.json` વાંચતું નથી. 2026-09 રિ-સિંકથી જોબ `docs-sync-strict` માં અવરોધક: + મુખ્ય દસ્તાવેજમાં ફેરફાર કરો → `npm run i18n:run -- --files=` (વિભાગ-સ્તરીય, સસ્તું). -**ડિફ-સચેત, આધારરેખા-સમર્થિત નહીં.** તે મર્જ બેઝ પરના `en.json`ની વર્કિંગ ટ્રી સાથે સરખામણી કરે છે; જે દરેક કીનું અંગ્રેજી મૂલ્ય બદલાયું હોય, તેના માટે હજુ પણ અસ્પર્શિત અનુવાદ ધરાવતું કોઈપણ લોકેલ જૂનું ગણાય છે. આ ઇરાદાપૂર્વક **પહેલેથી અસ્તિત્વમાં રહેલું ઋણ સ્થિર રાખે છે** — લાંબા સમયથી રહેલો અનુવાદ કયા જૂના અંગ્રેજી લખાણમાંથી આવ્યો હતો તે ડિફ જાહેર કરી શકતો નથી, તેથી ગેટ માત્ર વર્તમાન ફેરફાર જે ભાગને સ્પર્શે છે તેનું જ મૂલ્યાંકન કરે છે. વૈકલ્પિક રીત (પ્રતિ-કી હૅશ આધારરેખા) માટે ~600 KBની જનરેટ કરેલી ફાઇલ જરૂરી બને, જે હાલની સૌથી મોટી આધારરેખાથી 3× મોટી હોય અને દરેક i18n PR પર બદલાતી રહે. +**ડિફ-સચેત, બેઝલાઇન-આધારિત નહીં.** તે મર્જ બેઝ પરના `en.json`ની કાર્યરત ટ્રી સાથે સરખામણી કરે છે; જે દરેક કીનું અંગ્રેજી મૂલ્ય બદલાયું હોય, તેની અસ્પર્શિત અનુવાદ ધરાવતી કોઈપણ લોકેલ જૂની ગણાય છે. આ ઇરાદાપૂર્વક **પહેલેથી અસ્તિત્વમાં રહેલું ઋણ સ્થિર કરે છે** — લાંબા સમયથી રહેલો અનુવાદ કયા જૂના અંગ્રેજીમાંથી આવ્યો હતો તે ડિફ જણાવી શકતું નથી, તેથી ગેટ ફક્ત વર્તમાન ફેરફાર જે સ્પર્શે છે તેનું જ મૂલ્યાંકન કરે છે. વિકલ્પ (દરેક કી દીઠ હૅશ બેઝલાઇન) માટે ~600 KBની જનરેટ કરેલી ફાઇલની જરૂર પડે, જે હાલની સૌથી મોટી બેઝલાઇન કરતાં 3× મોટી હોય અને દરેક i18n PR પર બદલાતી રહે. તેને સંતોષવાની બે રીતો: -1. અસરગ્રસ્ત અનુવાદો અપડેટ કરો, અથવા +1. અસરગ્રસ્ત અનુવાદોને અપડેટ કરો, અથવા 2. તેમને `__MISSING__:` પર સેટ કરો — ત્યારબાદ રનટાઇમ સુધારેલું અંગ્રેજી પ્રદાન કરે છે - (`src/i18n/request.ts::deepMergeFallback`, #7258) અને કી અનુવાદ માટે કતારમાં ઉમેરાય છે. + (`src/i18n/request.ts::deepMergeFallback`, #7258) અને કી અનુવાદ માટે કતારમાં જાય છે. જો સ્ટ્રિંગનો **અર્થ** બદલાયો હોય, તો **કીનું નામ બદલવાનું** પસંદ કરો: નવી કી જૂનો અનુવાદ વારસામાં મેળવી શકતી નથી. #8463માં આ જ પેટર્નનો ઉપયોગ થયો હતો. ```bash npm run i18n:check-value-drift # કડક (CI જે ચલાવે છે) -npm run i18n:check-value-drift:warn # માત્ર અહેવાલ +npm run i18n:check-value-drift:warn # ફક્ત અહેવાલ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -જ્યારે આધાર કૅટલૉગ વાંચી શકાતો નથી (બેઝ રેફ વિનાનું શૅલો ક્લોન), ત્યારે `check-openapi-breaking`ની જેમ `SKIP reason=base-unresolved` સાથે 0 પર બહાર નીકળે છે. +જ્યારે બેઝ કૅટલૉગ વાંચી શકાતો નથી (બેઝ ref વિનાનો શેલો ક્લોન), ત્યારે `check-openapi-breaking`ની જેમ `SKIP reason=base-unresolved` સાથે 0 પર બહાર નીકળે છે. -### જોબ: `i18n` +### જૉબ: `i18n` -સંપૂર્ણ i18n માન્યતા મેટ્રિક્સ (દરેક લોકેલ માટે એક જોબ). આખી જોબ સલાહરૂપ છે. +સંપૂર્ણ i18n માન્યતા મેટ્રિક્સ (દરેક લોકેલ દીઠ એક જૉબ). આખી જૉબ સલાહાત્મક છે. -| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | -| ------------------------------- | -------------------------------- | -------------------------------------------------- | -| `validate_translation.py quick` | દરેક લોકેલ માટે અનુવાદની પૂર્ણતા | **સલાહરૂપ** (આખી જોબ પર `continue-on-error: true`) | +| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | +| ------------------------------- | ------------------------------- | ---------------------------------------------------- | +| `validate_translation.py quick` | દરેક લોકેલ દીઠ અનુવાદની પૂર્ણતા | **સલાહાત્મક** (આખી જૉબ પર `continue-on-error: true`) | -### જોબ: `pr-test-policy` +### જૉબ: `pr-test-policy` -માત્ર પુલ રિક્વેસ્ટ્સ પર ચાલે છે. +ફક્ત પુલ રિક્વેસ્ટ્સ પર ચાલે છે. -| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------ | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, અથવા `bin/`માં પ્રોડક્શન કોડ બદલતા PRમાં ટેસ્ટ સામેલ કે અપડેટ થયેલા હોવા જોઈએ (કડક નિયમ #8) | હા | -| `check:test-masking` | બદલાયેલી ટેસ્ટ ફાઇલો કુલ assert સંખ્યા ઘટાડતી નથી અથવા `assert.ok(true)` જેવી પુનરુક્તિઓ ઉમેરતી નથી | હા | -| `check:pr-evidence` | PR બોડી ફેરફાર માટે ટેસ્ટ/VPS પુરાવાનો ઉલ્લેખ કરે છે (PRના લખાણમાં grep કરીને કડક નિયમ #18ને યાંત્રિક બનાવે છે — નાજુક, Backlog જુઓ) | હા | +| સ્ક્રિપ્ટ | શું માન્ય કરે છે | અવરોધક | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, અથવા `bin/`માં પ્રોડક્શન કોડ બદલતા PRમાં ટેસ્ટ્સ સામેલ અથવા અપડેટ થયેલા હોવા જોઈએ (સખત નિયમ #8) | હા | +| `check:test-masking` | બદલાયેલી ટેસ્ટ ફાઇલો નેટ અસર્ટ ગણતરી ઘટાડતી નથી અથવા `assert.ok(true)` જેવી પુનરુક્તિઓ ઉમેરતી નથી | હા | +| `check:pr-evidence` | PR બોડીમાં ફેરફાર માટેના ટેસ્ટ/VPS પુરાવાનો ઉલ્લેખ હોય છે (PRના ગદ્યમાં grep કરીને સખત નિયમ #18ને યાંત્રિક બનાવે છે — નાજુક, Backlog જુઓ) | હા | -### જોબ: `test-vitest` +### જૉબ: `test-vitest` -`build` પછી ચાલે છે. નિષ્ફળતા થાય તો મર્જને અવરોધે છે. +`build` પછી ચાલે છે. નિષ્ફળતા આવે તો મર્જને અવરોધે છે. -| સ્યુટ | શું માન્ય કરે છે | અવરોધક | -| ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| `test:vitest` | MCP સર્વર (110 ટૂલ્સ), autoCombo, કૅશ — vitest રનર | હા | -| `test:vitest:ui` | UI કમ્પોનન્ટ ટેસ્ટ્સ — vitest રનર | **અવરોધક** — પહેલેથી અસ્તિત્વમાં રહેલી નિષ્ફળતાઓ `vitest.config.ts`માં સ્પષ્ટ રીતે બાકાત છે; નવી નિષ્ફળતાઓ જોબને નિષ્ફળ કરે છે | +| સ્યુટ | શું માન્ય કરે છે | અવરોધક | +| ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP સર્વર (110 ટૂલ્સ), autoCombo, કૅશ — vitest રનર | હા | +| `test:vitest:ui` | UI કમ્પોનન્ટ ટેસ્ટ્સ — vitest રનર | **અવરોધક** — પહેલેથી અસ્તિત્વમાં રહેલી નિષ્ફળતાઓ `vitest.config.ts`માં સ્પષ્ટપણે બાકાત રાખવામાં આવી છે; નવી નિષ્ફળતાઓ જૉબને નિષ્ફળ બનાવે છે | -### રાત્રિકાલીન વર્કફ્લોઝ (નિર્ધારિત, સલાહરૂપ) +### રાત્રિકાલીન વર્કફ્લોઝ (નિર્ધારિત, સલાહાત્મક) -આ cron શેડ્યૂલ પર (અને `workflow_dispatch` દ્વારા) ચાલે છે, PR પર ક્યારેય નહીં. બધા સલાહરૂપ છે. +આ cron શેડ્યૂલ પર (અને `workflow_dispatch` દ્વારા) ચાલે છે, PR પર ક્યારેય નહીં. બધા સલાહાત્મક છે. -| વર્કફ્લો | શું માન્ય કરે છે | અવરોધક | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `nightly-property` | રૅન્ડમ સીડ + વધુ રન સંખ્યા સાથેના fast-check પ્રોપર્ટી ટેસ્ટ્સ | **સલાહરૂપ** | -| `nightly-resilience` | હીપ-વૃદ્ધિ ગેટ, કેઓસ ફૉલ્ટ-ઇન્જેક્શન, k6 લોડ/સોક | **સલાહરૂપ** | -| `nightly-llm-security` | promptfoo ઇન્જેક્શન ગાર્ડ (બ્લૉક મોડ) + garak પ્રોબ્સ (પ્રોવાઇડર સિક્રેટ વિના છોડી દેવાય છે) | **સલાહરૂપ** | -| `nightly-schemathesis` | `docs/openapi.yaml`નો ઉપયોગ કરીને લાઇવ OmniRoute સામે OpenAPI કૉન્ટ્રાક્ટ ફઝિંગ (schemathesis) — સ્પેક ઉલ્લંઘનો / હેન્ડલ ન થયેલી 500 ભૂલો સામે લાવે છે (Fase 8 B.4) | **સલાહરૂપ** | -| `nightly-mutation` | ઝડપી યુનિટ લેન પર Stryker મ્યુટેશન-ટેસ્ટિંગ સ્કોર — ટકી રહેલા મ્યુટન્ટ્સ નબળા asserts સામે લાવે છે | **સલાહરૂપ** | -| `nightly-compat` | સમર્થિત `engines.node` રેન્જોમાં Node એન્જિન સુસંગતતા મેટ્રિક્સ | **સલાહરૂપ** | +| વર્કફ્લો | શું માન્ય કરે છે | અવરોધક | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `nightly-property` | રૅન્ડમ સીડ + વધુ રન ગણતરી સાથેના fast-check પ્રોપર્ટી ટેસ્ટ્સ | **સલાહાત્મક** | +| `nightly-resilience` | હીપ-વૃદ્ધિ ગેટ, કેઓસ ફૉલ્ટ-ઇન્જેક્શન, k6 લોડ/સોક | **સલાહાત્મક** | +| `nightly-llm-security` | promptfoo ઇન્જેક્શન ગાર્ડ (બ્લૉક મોડ) + garak પ્રોબ્સ (પ્રોવાઇડર સિક્રેટ વિના છોડવામાં આવે છે) | **સલાહાત્મક** | +| `nightly-schemathesis` | `docs/openapi.yaml`નો ઉપયોગ કરીને લાઇવ OmniRoute સામે OpenAPI કૉન્ટ્રૅક્ટ ફઝિંગ (schemathesis) — સ્પેક ઉલ્લંઘનો / ન સંભાળાયેલી 500 ભૂલો ઉજાગર કરે છે (તબક્કો 8 B.4) | **સલાહાત્મક** | +| `nightly-mutation` | ઝડપી યુનિટ લેન પર Stryker મ્યુટેશન-ટેસ્ટિંગ સ્કોર — બચી ગયેલા મ્યુટન્ટ્સ નબળા અસર્ટ્સને ઉજાગર કરે છે | **સલાહાત્મક** | +| `nightly-compat` | સમર્થિત `engines.node` રેન્જમાં Node એન્જિન સુસંગતતા મેટ્રિક્સ | **સલાહાત્મક** | --- @@ -531,43 +530,43 @@ CI ગેટ ઉપરાંત, OmniRoute એક **વૈકલ્પિક** ` --- -## તર્કસંગતીકરણ બેકલૉગ (ROI સમીક્ષા — તબક્કો 9 તરંગ 3) +## તર્કસંગતીકરણ બૅકલૉગ (ROI સમીક્ષા — તબક્કો 9 તરંગ 3) -આ ઇન્વેન્ટરીનું 2026-06-17ના રોજ `ci.yml` સાથે સમાધાન કરવામાં આવ્યું હતું (અગાઉના સંસ્કરણમાં +આ ઇન્વેન્ટરીનું 2026-06-17ના રોજ `ci.yml` સામે સમાધાન કરવામાં આવ્યું હતું (અગાઉની આવૃત્તિમાં `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` રહી ગયા હતા). સમાધાન કરેલા સમૂહની ROI સમીક્ષાએ -નીચેના તર્કસંગતીકરણ ઉમેદવારો ઓળખ્યા. **મર્જ યાંત્રિક CI -ફેરફારો છે; ફ્લિપ/ડ્રૉપ એ ઑપરેટર માટે આરક્ષિત નીતિગત નિર્ણયો છે.** નીચેનામાંથી કશું પણ -હજુ લાગુ કરવામાં આવ્યું નથી. +`check:codeql-ratchet`, `check:pr-evidence` છૂટી ગયાં હતાં). સમાધાન કરાયેલા સમૂહની ROI સમીક્ષાએ +નીચેના તર્કસંગતીકરણ ઉમેદવારો ઓળખ્યા. **મર્જ યાંત્રિક CI ફેરફારો છે; +ફ્લિપ/ડ્રૉપ ઓપરેટર માટે અનામત નીતિગત નિર્ણયો છે.** નીચેનું કશું હજી +લાગુ કરવામાં આવ્યું નથી. -**ઉપર દસ્તાવેજીકૃત ન થયેલું પણ** (સલાહરૂપ, ઓછું સિગ્નલ): `docs-lint` જૉબ -(markdownlint + Vale, સમગ્ર જૉબ માટે `continue-on-error`) અને સ્વતંત્ર સ્કૅનર વર્કફ્લો +**ઉપર દસ્તાવેજીકૃત ન થયેલું પણ** (સલાહરૂપ, ઓછો સંકેત): `docs-lint` જોબ +(markdownlint + Vale, સમગ્ર જોબ `continue-on-error`) અને સ્વતંત્ર સ્કૅનર વર્કફ્લો `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` -`quality-baseline.json`માં છે, પરંતુ `ci.yml`માં કોઈ બ્લૉકિંગ રૅચેટ સાથે જોડાયેલું નથી — આ મેટ્રિક +`quality-baseline.json`માં છે, પરંતુ `ci.yml`માં અવરોધક રૅચેટ સાથે જોડાયેલું નથી — મેટ્રિક હાલમાં અનાથ છે. -### મર્જ / ડીડુપ્લિકેશન (યાંત્રિક, ઓછું જોખમ) +### મર્જ / ડિડુપ્લિકેશન (યાંત્રિક, ઓછું જોખમ) -દરેક ઉમેદવારને 2026-06-17ના રોજ લાઇવ ગેટની સ્થિતિ સામે માન્ય કરવામાં આવ્યો હતો (વિશ્વાસ કરો, પણ ચકાસો); -કેટલાક "સ્પષ્ટ" મર્જ વાસ્તવમાં છુપાયેલું ઋણ ધરાવતા હોવાનું બહાર આવ્યું અને તે **સ્વચ્છ ડ્રૉપ-ઇન નથી**. +દરેક ઉમેદવારને 2026-06-17ના રોજ લાઇવ ગેટ સ્થિતિ સામે માન્ય કરવામાં આવ્યો હતો (વિશ્વાસ કરો, પણ ચકાસો); +કેટલાક "સ્પષ્ટ" મર્જ હકીકતમાં ટેક્નિકલ ઋણ છુપાવતા હોવાનું બહાર આવ્યું અને તે **સ્વચ્છ ડ્રૉપ-ઇન નથી**. -- **`check:docs-sync` બે વખત ચાલે છે** — `lint` જૉબમાં સ્વતંત્ર રીતે અને ફરી `check:docs-all` (`docs-sync-strict`)ની અંદર તથા husky pre-commit hookમાં. ✅ **પૂર્ણ** — સ્વતંત્ર `lint` ઇન્વોકેશન દૂર કરવામાં આવ્યું. -- **CVE સ્કૅનિંગ** — ❌ **સ્વચ્છ મર્જ નથી.** `audit:deps` કોઈપણ high/critical CVE પર નિષ્ફળતા સાથે અટકે છે; `check:vuln-ratchet` (osv) માત્ર બેઝલાઇનની સરખામણીમાં _રીગ્રેશન_ થાય ત્યારે નિષ્ફળ જાય છે (હાલમાં 1 MODERATE). અર્થવિધિ અલગ છે — `audit:deps` દૂર કરવાથી નિરપેક્ષ high/critical ગેટ ખોવાઈ જશે. બંને રાખો. -- **સાઇકલ શોધ** — ❌ **સ્વચ્છ મર્જ નથી.** `check:circular-deps` (dpdm) **91 સાઇકલ** દર્શાવે છે (એટલે જ તે સલાહરૂપ છે); પહેલાં તેમનું નિરાકરણ કર્યા વિના તેને બ્લૉકિંગ તરીકે પ્રમોટ કરી શકાતું નથી, અને તેનો વ્યાપ લીલા, ક્યુરેટ કરેલા `check:cycles` કરતાં વધુ વિસ્તૃત છે. `check:cycles`ને બ્લૉકિંગ રાખો; 91 dpdm સાઇકલનું નિરાકરણ પોતે એક અલગ બેકલૉગ છે. -- **જટિલતા** — ✅ **પૂર્ણ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): એક ESLint વૉક, ruleId પ્રમાણે ગણતરી કરે છે જેથી cyclomatic+max-lines અને cognitive બેઝલાઇન સ્વતંત્ર રહે; વ્યક્તિગત `check:complexity` / `check:cognitive-complexity` સ્થાનિક `--update` માટે યથાવત્ રહે છે. -- **`/api` એન્ટિ-હેલ્યુસિનેશન** — ✅ **પૂર્ણ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`ની એક FS ઇન્વેન્ટરી, openapi-routes + docs-symbols હજી પણ સ્વતંત્ર રીતે રિપોર્ટ કરે છે; વ્યક્તિગત ચેક્સ સ્થાનિક રન માટે યથાવત્ રહે છે. -- **`check:node-runtime` 11 જૉબમાં ચાલે છે** — ⚠️ **ઓછો ROI.** દરેક અલગ રનર છે અને ચેક <1s છે; સસ્તું પ્રતિ-જૉબ રક્ષણ ગુમાવવાની સામે કુલ બચત ~10s છે. આટલી ઉથલપાથલ યોગ્ય નથી. -- **CI lint પર `typecheck:noimplicit:core`** — ✅ **lint જૉબમાંથી દૂર કરાયું** (તે સલાહરૂપ `continue-on-error` હતું); બ્લૉકિંગ ટાઇપ સપાટી `typecheck:core` + `check:type-coverage` છે. સ્થાનિક સ્ક્રિપ્ટ જાળવી રાખવામાં આવી છે. +- **`check:docs-sync` બે વાર ચાલે છે** — `lint` જોબમાં સ્વતંત્ર રીતે અને ફરીથી `check:docs-all` (`docs-sync-strict`) તથા husky pre-commit હૂકની અંદર. ✅ **પૂર્ણ** — સ્વતંત્ર `lint` ઇન્વોકેશન દૂર કરવામાં આવ્યું. +- **CVE સ્કૅનિંગ** — ❌ **સ્વચ્છ મર્જ નથી.** `audit:deps` કોઈપણ high/critical CVE પર હાર્ડ-ફેઇલ થાય છે; `check:vuln-ratchet` (osv) માત્ર બેઝલાઇનની સરખામણીએ _રીગ્રેશન_ પર નિષ્ફળ થાય છે (હાલમાં 1 MODERATE). સિમેન્ટિક્સ અલગ છે — `audit:deps` દૂર કરવાથી નિરપેક્ષ high/critical ગેટ ગુમાવવો પડે. બંને રાખો. +- **સાઇકલ શોધ** — ✅ **પૂર્ણ** (#15159 G-01/G-02). અહીંના જૂના લખાણમાં `check:cycles`ને "લીલું, કાળજીપૂર્વક પસંદ કરેલું" ગેટ કહેવાયું હતું અને તેને અવરોધક રાખવાનું સમર્થન એ આધારે કરાયું હતું કે `check:circular-deps` (dpdm)એ 91 સાઇકલ દર્શાવ્યાં હતાં. એ લીલું પરિણામ **ખોટું લીલું પરિણામ** હતું: `check:cycles`એ 5 સબડિરેક્ટરી (450 ફાઇલ) સ્કૅન કરી હતી, માત્ર સ્થિર `import|export … from` સાથે મેળ સાધ્યો હતો અને દરેક `@/` તથા `@omniroute/open-sse/` સ્પેસિફાયર કાઢી નાખ્યો હતો, તેથી તે રિપોઝિટરીમાં પ્રભુત્વ ધરાવતી dynamic-import + alias સાઇકલ જોઈ શકતું નહોતું. સુધાર્યું: ગેટ હવે `src` + `open-sse` (5023 ફાઇલ)માં ફરે છે, TypeScript ASTમાંથી સ્પેસિફાયર એકત્ર કરે છે (જેથી `import("…")` ગણાય છે અને ટાઇપ-સ્થાનમાં `typeof import("…")` ગણાતું નથી), અને tsconfig `paths`નું રિઝોલ્યુશન કરે છે. તેને 0 નહીં, પરંતુ **14** સાઇકલ મળે છે. અગાઉથી અસ્તિત્વમાં રહેલી 14 સાઇકલને ગેટ PRમાં સુધારી શકાતી ન હોવાથી, `check:cycles` હવે **રૅચેટ** છે (`--ratchet`, `quality-baseline.json`માં ઉચ્ચતમ મર્યાદા `metrics.cycles.value = 14`, `direction: down`) — તે કોઈપણ _રીગ્રેશન_ને અવરોધે છે અને ગણતરી માત્ર ઘટી શકે છે. CI `npm run check:cycles:ratchet` ચલાવે છે. ઘટાડાની કામગીરી **A-01** સાથે આગળ વધે છે. વધુ વ્યાપક બીજા અભિપ્રાય તરીકે `check:circular-deps` (dpdm) સલાહરૂપ રહે છે. +- **જટિલતા** — ✅ **પૂર્ણ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): એક ESLint વૉક, ruleId પ્રમાણે ગણતરી કરે છે જેથી cyclomatic+max-lines અને cognitive બેઝલાઇન સ્વતંત્ર રહે; સ્થાનિક `--update` માટે વ્યક્તિગત `check:complexity` / `check:cognitive-complexity` યથાવત્ રહે છે. +- **`/api` ઍન્ટિ-હેલ્યુસિનેશન** — ✅ **પૂર્ણ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`ની એક FS ઇન્વેન્ટરી, openapi-routes + docs-symbols હજી પણ સ્વતંત્ર રીતે અહેવાલ આપે છે; સ્થાનિક રન માટે વ્યક્તિગત ચેક્સ યથાવત્ રહે છે. +- **`check:node-runtime` 11 જોબમાં ચાલે છે** — ⚠️ **ઓછું ROI.** દરેક અલગ રનર છે અને ચેક <1sનો છે; સસ્તું પ્રતિ-જોબ સુરક્ષા નિયંત્રણ ગુમાવવાની સામે કુલ બચત ~10s છે. આટલી ઉથલપાથલ યોગ્ય નથી. +- **CI lint પર `typecheck:noimplicit:core`** — ✅ **lint જોબમાંથી દૂર કરાયું** (સલાહરૂપ `continue-on-error` હતું); અવરોધક ટાઇપ સપાટી `typecheck:core` + `check:type-coverage` છે. સ્થાનિક સ્ક્રિપ્ટ જાળવી રાખવામાં આવી છે. -### ફ્લિપ / નિર્ણય (ઑપરેટર નીતિ) +### ફ્લિપ / નિર્ણય (ઓપરેટર નીતિ) -- `check:openapi-security-tiers` (સલાહરૂપ) — ❌ **સ્વચ્છ રીતે ફ્લિપ કરી શકાય તેમ નથી.** તે 0 સાથે બહાર નીકળે છે, પરંતુ ચેતવણી આપે છે કે `LOCAL_ONLY_API_PREFIXES` હેઠળના કેટલાક `traffic-inspector` રૂટમાં `x-loopback-only: true` ઍનોટેશન નથી. તેને અમલમાં મૂકવા માટે પહેલાં `openapi.yaml`માં તે ઍનોટેશનો ઉમેરવા જરૂરી છે. -- `typecheck:noimplicit:core` (સલાહરૂપ) — મોટા ભાગે બ્લૉકિંગ `check:type-coverage` રૅચેટમાં સમાવિષ્ટ છે. તેને રૅચેટમાં ફ્લિપ કરો અથવા વધારાનો બિનજરૂરી બીજો `tsc` પાસ દૂર કરો. -- `test:vitest:ui` (હવે **બ્લૉકિંગ**) — અગાઉથી રહેલી નિષ્ફળતાઓને `vitest.config.ts`માં `// #8618` ટ્રૅકિંગ ટિપ્પણીઓ સાથે સ્પષ્ટ રીતે બાકાત રાખવામાં આવી છે; નવી નિષ્ફળતાઓ જૉબને નિષ્ફળ બનાવે છે. -- `check:secrets` (gitleaks, 3 દસ્તાવેજીકૃત false-positive પર સ્થિર કરાયેલું બ્લૉકિંગ રૅચેટ) — 0 સુધી પહોંચવા માટે 3ને allowlistમાં ઉમેરો અથવા તેને સલાહરૂપ તરીકે ડિમોટ કરો. GitHubના મૂળ secret-scanning + `check:public-creds` સાથે ઓવરલેપ થાય છે. -- `check:pr-evidence` (બ્લૉકિંગ, PR-body ગદ્યને greps કરે છે) — false-positiveનું જોખમ ઊંચું છે; તેને દૂર કરવાથી Hard Rule #18નો અમલ નબળો પડે છે, તેથી આ એક વાસ્તવિક નીતિગત નિર્ણય છે. -- `semgrep` (સલાહરૂપ સ્વતંત્ર) — OWASP પરિવારો માટે CodeQL સાથે ઓવરલેપ થાય છે; તેની બેઝલાઇનને રૅચેટ સાથે જોડો અથવા તેને દૂર કરો. +- `check:openapi-security-tiers` (સલાહરૂપ) — ❌ **સ્વચ્છ રીતે ફ્લિપ કરી શકાય તેમ નથી.** તે 0 સાથે એક્ઝિટ થાય છે, પરંતુ ચેતવણી આપે છે કે `LOCAL_ONLY_API_PREFIXES` હેઠળના કેટલાક `traffic-inspector` રૂટમાં `x-loopback-only: true` ઍનોટેશન નથી. તેને અમલમાં મૂકતાં પહેલાં `openapi.yaml`માં એ ઍનોટેશન ઉમેરવા જરૂરી છે. +- `typecheck:noimplicit:core` (સલાહરૂપ) — અવરોધક `check:type-coverage` રૅચેટ દ્વારા મોટા ભાગે આવરી લેવાય છે. તેને રૅચેટમાં ફ્લિપ કરો અથવા બિનજરૂરી બીજા `tsc` પાસને દૂર કરો. +- `test:vitest:ui` (હવે **અવરોધક**) — અગાઉથી અસ્તિત્વમાં રહેલી નિષ્ફળતાઓને `vitest.config.ts`માં `// #8618` ટ્રૅકિંગ ટિપ્પણીઓ સાથે સ્પષ્ટ રીતે બાકાત રાખવામાં આવી છે; નવી નિષ્ફળતાઓ જોબને નિષ્ફળ બનાવે છે. +- `check:secrets` (gitleaks, 3 દસ્તાવેજીકૃત false-positive પર સ્થિર કરાયેલ અવરોધક રૅચેટ) — 0 સુધી પહોંચવા માટે એ 3ને allowlist કરો અથવા તેને સલાહરૂપ સ્તરે ઉતારો. GitHubના મૂળ secret-scanning + `check:public-creds` સાથે ઓવરલેપ થાય છે. +- `check:pr-evidence` (અવરોધક, PR-bodyના ગદ્યમાં greps કરે છે) — false-positiveનું ઊંચું જોખમ; તેને દૂર કરવાથી Hard Rule #18નો અમલ નબળો પડે છે, તેથી આ ખરેખર નીતિગત નિર્ણય છે. +- `semgrep` (સલાહરૂપ સ્વતંત્ર) — OWASP ફૅમિલી માટે CodeQL સાથે ઓવરલેપ થાય છે; તેની બેઝલાઇનને રૅચેટ સાથે જોડો અથવા તેને દૂર કરો. --- diff --git a/docs/i18n/gu/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/gu/docs/compression/COMPRESSION_GUIDE.md index 6974acb411d9..3017bfa01b6b 100644 --- a/docs/i18n/gu/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/gu/docs/compression/COMPRESSION_GUIDE.md @@ -433,8 +433,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages` સાથેના બોડી પર, કન્ટેન્ટ બાયપાસ (`open-sse/services/compression/outputMode.ts` માં `shouldBypassCavemanOutputMode()`) છેલ્લા ત્રણ સંદેશાઓને તપાસે છે અને જ્યારે તેઓ તેની સુરક્ષા, અપરિવર્તનશીલ-ક્રિયા, સ્પષ્ટતા અથવા ઓર્ડર-સંવેદનશીલ કીવર્ડ્સ સાથે મેળ ખાય છે ત્યારે -આખા ટર્ન માટે શૈલીઓને છોડી દે છે. બાયપાસ ડેશબોર્ડના **ઓટો-ક્લેરિટી બાયપાસ** ટોગલ -(`cavemanOutputMode.autoClarity`) જે પણ સેટ હોય તે ચલાવે છે. +આખા ટર્ન માટે શૈલીઓને છોડી દે છે. બાયપાસ ડેશબોર્ડના **ઓટો-ક્લેરિટી બાયપાસ** ટોગલ (`cavemanOutputMode.autoClarity`) ચાલુ હોય ત્યાં સુધી ચલાવે છે, જે ડિફોલ્ટ છે; ટોગલ બંધ હોય ત્યારે પસંદ કરેલી શૈલીઓ એ ટર્ન પર પણ લાગુ પડે છે. જ્યારે બાયપાસ ટર્નને પસાર થવા દે છે, ત્યારે `placeSystemInstruction()` (એ જ ફાઇલ), જે ક્યારેય નવો `messages[0]` બનાવતો નથી, બ્લોકને તેને મળેલી પ્રથમ વસ્તુમાં મૂકે છે: diff --git a/docs/i18n/gu/docs/reference/ENVIRONMENT.md b/docs/i18n/gu/docs/reference/ENVIRONMENT.md index f4d4b722722a..48c3c5559880 100644 --- a/docs/i18n/gu/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/gu/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. આવશ્યક સિક્રેટ્સ -આ **પ્રથમ વખત ચલાવતા પહેલાં ફરજિયાતપણે** સેટ કરેલાં હોવા જોઈએ. તેમના વિના, એપ્લિકેશન કાં તો શરૂ થવાનો ઇનકાર કરશે અથવા અસુરક્ષિત ડિફૉલ્ટ્સ સાથે કાર્ય કરશે. - -| વેરિએબલ | આવશ્યક | ડિફૉલ્ટ | સોર્સ ફાઇલ | વર્ણન | -| ---------------------------- | ------------------ | ------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **હા** | _(કોઈ નહીં)_ | `src/lib/auth` | બધા ડૅશબોર્ડ સેશન કુકીઝ (JWT) ને સાઇન/ચકાસે છે. `openssl rand -base64 48` વડે જનરેટ કરો. | -| `API_KEY_SECRET` | **હા** | _(કોઈ નહીં)_ | `src/lib/db/apiKeys.ts` | SQLite માં સ્થિર સ્થિતિમાં રહેલાં API કી મૂલ્યો માટે AES એન્ક્રિપ્શન કી. `openssl rand -hex 32` વડે જનરેટ કરો. | -| `INITIAL_PASSWORD` | **હા** | `CHANGEME` | બુટસ્ટ્રૅપ સ્ક્રિપ્ટ | પ્રારંભિક એડમિન ડૅશબોર્ડ પાસવર્ડ સેટ કરે છે (`.env.example` ડિફૉલ્ટ સાથે મેળ ખાય છે — ફેરફાર કરવા માટે મજબૂર કરવાના હેતુથી તેને સ્પષ્ટપણે અસુરક્ષિત રાખવામાં આવ્યો છે). **પ્રથમ ઉપયોગ પહેલાં બદલો.** લૉગિન કર્યા પછી, Dashboard → Settings → Security મારફતે બદલો. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **હા** (પ્રોડક્શન) | _(સેટ કરેલું નથી)_ | `src/app/api/internal/codex-responses-ws/route.ts` | આંતરિક Codex Responses WebSocket બ્રિજ માટે શેર કરેલું સિક્રેટ. Electron/બ્રાઉઝર WS રિલે અને OmniRoute વચ્ચેની બ્રિજ વિનંતીઓને પ્રમાણિત કરે છે. ⚠️ **પ્રોડક્શનમાં આવશ્યક — જ્યારે સેટ ન હોય, ત્યારે બધી WS બ્રિજ વિનંતીઓ નકારવામાં આવે છે.** `openssl rand -base64 32` વડે જનરેટ કરો. | -| `OMNIROUTE_SW_BUILD_ID` | ના | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA શેલ (#11779) માટે સ્પષ્ટ સર્વિસ-વર્કર કૅશ-બસ્ટિંગ id; રિઝોલ્યુશન ચેઇનમાં પ્રથમ. | -| `SOURCE_VERSION` | ના | _(સેટ કરેલું નથી)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ચેઇનમાં બીજું — ડિપ્લોય કરેલા કમિટ તરીકે PaaS બિલ્ડર્સ (દા.ત. Heroku-શૈલી) દ્વારા સેટ કરવામાં આવે છે. | -| `NEXT_PUBLIC_SW_BUILD_ID` | ના | _(પ્રાપ્ત કરેલું)_ | `src/shared/components/PwaRegister.tsx` | બિલ્ડ-સમયનું જાહેર મૂલ્ય, જેનો ઉપયોગ ક્લાયન્ટ `/sw.js?v=…` રજિસ્ટર કરવા માટે કરે છે; ઉપરનાં બે મૂલ્યોમાંથી અને ત્યારબાદ git SHA માંથી મેળવવામાં આવે છે. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | ના (આપમેળે) | _(દરેક બુટ વખતે આપમેળે)_ | `src/server/authz/policies/management.ts` | વિશ્વસનીય peer-IP સ્ટેમ્પ OmniRoute ના પોતાના HTTP સર્વર (`scripts/dev/peer-stamp.mjs`) તરફથી આવ્યો છે તે સાબિત કરતું પ્રતિ-પ્રક્રિયા સિક્રેટ. authz મિડલવેર ફક્ત ત્યારે જ વિનંતીના સ્થાનિક હોવા પર વિશ્વાસ કરે છે (LOCAL_ONLY રૂટ્સનું loopback/LAN ગેટિંગ), જ્યારે સ્ટેમ્પમાં આ ટોકન હોય. દરેક બુટ વખતે આપમેળે જનરેટ થાય છે — તેને સેટ કર્યા વિના છોડો; ફક્ત સ્ટેમ્પ શેર કરવો આવશ્યક હોય તેવા બહુ-પ્રક્રિયા સેટઅપ્સ માટે જ તેને નિશ્ચિત કરો. | -| `OMNIROUTE_TRUSTED_PROXIES` | ના | _(સેટ કરેલું નથી)_ | `scripts/dev/peer-stamp.mjs` | એવા રિવર્સ પ્રોક્સીઝના અલ્પવિરામથી અલગ કરેલા IPs અથવા CIDR રેન્જિસ, જેમનાં `X-Forwarded-For` / `X-Real-IP` પર IP મંજૂરી/પ્રતિબંધ સૂચિ વિશ્વાસ કરી શકે છે; આ loopback, ખાનગી-નેટવર્ક સરનામાંઓ અને Cloudflare એજિસ ઉપરાંત છે, જે હંમેશાં વિશ્વસનીય હોય છે. અન્ય કોઈપણ જાહેર સરનામા પર રહેલા પ્રોક્સી માટે જ જરૂરી છે; અન્યથા ફિલ્ટર તે પ્રોક્સીના પોતાના સરનામાનું મૂલ્યાંકન કરે છે. | +**હા** તરીકે ચિહ્નિત અને સેટ ન કરેલા સિક્રેટ્સ પ્રથમ વખત ચલાવતી વખતે આપોઆપ બનાવવામાં આવે છે (`JWT_SECRET`, `API_KEY_SECRET`) અને ડેટા ડિરેક્ટરીમાં `server.env`માં સાચવવામાં આવે છે — જો તે ડિરેક્ટરી ક્ષણિક હોય તો તેમને સ્પષ્ટપણે સેટ કરો. અપવાદ `OMNIROUTE_WS_BRIDGE_SECRET` છે: તે `server.env`માં સાચવાતું નથી — સર્વર એન્ટ્રીપોઇન્ટ્સ દરેક બૂટ પર એક ક્ષણિક મૂલ્ય બનાવે છે જેનો ઉપયોગ પ્રક્રિયાની અંદરનું બ્રિજ જ કરી શકે છે, તેથી બાહ્ય બ્રિજ ક્લાયન્ટ્સે તેને સ્પષ્ટપણે સેટ કરવું પડે છે. + +| વેરિએબલ | આવશ્યક | ડિફૉલ્ટ | સોર્સ ફાઇલ | વર્ણન | +| ---------------------------- | ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **હા** | _(કોઈ નહીં)_ | `src/lib/auth` | બધા ડૅશબોર્ડ સેશન કુકીઝ (JWT) ને સાઇન/ચકાસે છે. `openssl rand -base64 48` વડે જનરેટ કરો. | +| `API_KEY_SECRET` | **હા** | _(કોઈ નહીં)_ | `src/lib/db/apiKeys.ts` | SQLite માં સ્થિર સ્થિતિમાં રહેલાં API કી મૂલ્યો માટે AES એન્ક્રિપ્શન કી. `openssl rand -hex 32` વડે જનરેટ કરો. | +| `INITIAL_PASSWORD` | ના | _(સેટ કરેલું નથી)_ | `src/lib/auth/managementPassword.ts` | જ્યાં સુધી કોઈ પાસવર્ડ સાચવેલો ન હોય ત્યારે શરૂઆતમાં ડૅશબોર્ડ પાસવર્ડ સેટ કરે છે; એક વાર પાસવર્ડ સાચવાઈ જાય પછી તેની અવગણના થાય છે. npm અને સોર્સ ઇન્સ્ટોલ `.env.example` ને `.env`માં કૉપિ કરે છે, તેથી તે તેના જાહેરમાં જાણીતા પ્લેસહોલ્ડર `CHANGEME` સાથે શરૂ થાય છે: પ્રથમ બૂટ પહેલાં તેને બદલો, અથવા તે `.env`માં `INITIAL_PASSWORD=` (ખાલી) સેટ કરો જેથી પ્રથમ મુલાકાતે ઑનબોર્ડિંગ વિઝાર્ડમાં પાસવર્ડ બનાવી શકાય, જેમ સેટ ન કરેલું મૂલ્ય (Docker ઇમેજનું ડિફૉલ્ટ) કરે છે. સાચવેલો પાસવર્ડ Dashboard → Settings → Security માં બદલો, અથવા સર્વરની ડેટા ડિરેક્ટરી પર `DATA_DIR` સેટ કરીને `omniroute reset-password` ચલાવો (`node bin/reset-password.mjs` સોર્સ ચેકઆઉટમાં). | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **હા** (પ્રોડક્શન) | _(સેટ કરેલું નથી)_ | `src/app/api/internal/codex-responses-ws/route.ts` | આંતરિક Codex Responses WebSocket બ્રિજ માટે શેર કરેલું સિક્રેટ. Electron/બ્રાઉઝર WS રિલે અને OmniRoute વચ્ચેની બ્રિજ વિનંતીઓને પ્રમાણિત કરે છે. ⚠️ **પ્રોડક્શનમાં આવશ્યક — જ્યારે સેટ ન હોય, ત્યારે બધી WS બ્રિજ વિનંતીઓ નકારવામાં આવે છે.** `openssl rand -base64 32` વડે જનરેટ કરો. | +| `OMNIROUTE_SW_BUILD_ID` | ના | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA શેલ (#11779) માટે સ્પષ્ટ સર્વિસ-વર્કર કૅશ-બસ્ટિંગ id; રિઝોલ્યુશન ચેઇનમાં પ્રથમ. | +| `SOURCE_VERSION` | ના | _(સેટ કરેલું નથી)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ચેઇનમાં બીજું — ડિપ્લોય કરેલા કમિટ તરીકે PaaS બિલ્ડર્સ (દા.ત. Heroku-શૈલી) દ્વારા સેટ કરવામાં આવે છે. | +| `NEXT_PUBLIC_SW_BUILD_ID` | ના | _(પ્રાપ્ત કરેલું)_ | `src/shared/components/PwaRegister.tsx` | બિલ્ડ-સમયનું જાહેર મૂલ્ય, જેનો ઉપયોગ ક્લાયન્ટ `/sw.js?v=…` રજિસ્ટર કરવા માટે કરે છે; ઉપરનાં બે મૂલ્યોમાંથી અને ત્યારબાદ git SHA માંથી મેળવવામાં આવે છે. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | ના (આપમેળે) | _(દરેક બુટ વખતે આપમેળે)_ | `src/server/authz/policies/management.ts` | વિશ્વસનીય peer-IP સ્ટેમ્પ OmniRoute ના પોતાના HTTP સર્વર (`scripts/dev/peer-stamp.mjs`) તરફથી આવ્યો છે તે સાબિત કરતું પ્રતિ-પ્રક્રિયા સિક્રેટ. authz મિડલવેર ફક્ત ત્યારે જ વિનંતીના સ્થાનિક હોવા પર વિશ્વાસ કરે છે (LOCAL_ONLY રૂટ્સનું loopback/LAN ગેટિંગ), જ્યારે સ્ટેમ્પમાં આ ટોકન હોય. દરેક બુટ વખતે આપમેળે જનરેટ થાય છે — તેને સેટ કર્યા વિના છોડો; ફક્ત સ્ટેમ્પ શેર કરવો આવશ્યક હોય તેવા બહુ-પ્રક્રિયા સેટઅપ્સ માટે જ તેને નિશ્ચિત કરો. | +| `OMNIROUTE_TRUSTED_PROXIES` | ના | _(સેટ કરેલું નથી)_ | `scripts/dev/peer-stamp.mjs` | એવા રિવર્સ પ્રોક્સીઝના અલ્પવિરામથી અલગ કરેલા IPs અથવા CIDR રેન્જિસ, જેમનાં `X-Forwarded-For` / `X-Real-IP` પર IP મંજૂરી/પ્રતિબંધ સૂચિ વિશ્વાસ કરી શકે છે; આ loopback, ખાનગી-નેટવર્ક સરનામાંઓ અને Cloudflare એજિસ ઉપરાંત છે, જે હંમેશાં વિશ્વસનીય હોય છે. અન્ય કોઈપણ જાહેર સરનામા પર રહેલા પ્રોક્સી માટે જ જરૂરી છે; અન્યથા ફિલ્ટર તે પ્રોક્સીના પોતાના સરનામાનું મૂલ્યાંકન કરે છે. | ### જનરેશન કમાન્ડ્સ @@ -215,38 +215,38 @@ OmniRoute તમામ સ્થાયી સંગ્રહ માટે **SQLi ## 4. સુરક્ષા અને પ્રમાણીકરણ -| વેરિએબલ | ડિફૉલ્ટ | સોર્સ ફાઇલ | વર્ણન | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | મશીન ફિંગરપ્રિન્ટિંગ માટે હાર્ડવેર ઓળખકર્તાઓ સાથે સંયોજિત કરવામાં આવતું સૉલ્ટ. અલગતા માટે દરેક ડિપ્લોયમેન્ટમાં તેને બદલો. | -| `OMNIROUTE_CLI_SALT` | _(સેટ ન હોય = દરેક ઇન્સ્ટોલેશન માટે રૅન્ડમ સૉલ્ટ, જે `/cli-token-salt.json` પર સંગ્રહિત રહે છે)_ | `src/lib/machineToken.ts` | સ્થાનિક CLI પ્રમાણીકરણ ટોકન મેળવવા માટેનું HMAC સૉલ્ટ. આ મૂલ્ય સેટ કરવાથી મશીન પરના બધા CLI ટોકન રોટેટ થાય છે અને તેને હંમેશાં સંગ્રહિત સૉલ્ટ કરતાં પ્રાથમિકતા મળે છે. `docs/security/CLI_TOKEN.md` જુઓ. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | સેશન કૂકીઝ પર `Secure` ફ્લૅગ સેટ કરે છે. HTTPS પાછળ ચલાવતી વખતે આ **`true` હોવું જ જોઈએ**. | -| `REQUIRE_API_KEY` | `false` | API મિડલવેર | જ્યારે `true` હોય, ત્યારે બધી `/v1/*` પ્રૉક્સી વિનંતીઓમાં માન્ય API કી સામેલ હોવી આવશ્યક છે. **આ ફ્લૅગ `GET /v1/models`ને નિયંત્રિત કરતું નથી**, જે તેના બદલે ડૅશબોર્ડ લૉગિન સ્થિતિ (`requireAuthForModels`)ને અનુસરે છે — તેથી `/v1/models` તરફથી મળતો `401` એ અનુમાન સુરક્ષિત હોવાનું દર્શાવતો નથી. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) જુઓ. | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ડૅશબોર્ડ UIમાં API કીનાં સંપૂર્ણ મૂલ્યો જાહેર કરવાની મંજૂરી આપે છે. ડૅશબોર્ડ ફીચર ફ્લૅગ્સમાંથી રૂપરેખાંકિત કરી શકાય છે; શેર કરેલા ઇન્સ્ટન્સ પર સુરક્ષાનું જોખમ છે. | -| `NO_LOG_API_KEY_IDS` | _(ખાલી)_ | `src/lib/compliance/index.ts` | વિનંતી લૉગિંગને બાયપાસ કરતા, અલ્પવિરામથી અલગ કરેલા API કી ID (GDPR અનુપાલન). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(સેટ ન હોય = અમર્યાદિત)_ | `src/shared/utils/apiKeyPolicy.ts` | એવી API કીઓ માટે લાગુ થતું પ્રતિ-દિવસ વિનંતી બજેટનું ફૉલબૅક, જેમની `rate_limits` કૉલમ null છે. સેટ ન હોય અથવા ખાલી હોય: કોઈ ગર્ભિત મર્યાદા નહીં (#2289, #11017). `0` પણ સમાન છે (અમર્યાદિત). ધન પૂર્ણાંક Nથી N/દિવસ, 5N/અઠવાડિયું, 20N/મહિનો સક્ષમ થાય છે. ખામીયુક્ત બિન-ખાલી મૂલ્યો લેગસી 1000/દિવસ, 5000/અઠવાડિયું, 20000/મહિનો વિન્ડોઝ પર ફૉલબૅક થાય છે. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | વિનંતી બૉડીનું મહત્તમ મંજૂર કદ. આ મર્યાદાથી મોટા પેલોડ્સને નકારે છે. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | આ થ્રેશોલ્ડ જેટલી અથવા તેનાથી મોટી વાસ્તવિક વિનંતી બૉડીઝ JSON પાર્સિંગ પહેલાં ઍટમિક, પ્રોસેસ-લોકલ હેવીવેઇટ ઍડમિશન લીઝ મેળવે છે (BYTE પાથ, જેમાં `POST /v1/responses` સામેલ છે). સ્ટ્રક્ચર-હેવીની જેમ જ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) હેલ્ધી-હેડરૂમ એસ્કેપ; તેમ છતાં `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) દ્વારા મર્યાદિત રહે છે, જેથી [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ફરી ન આવે. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | મર્યાદિત ઇન્જેશન દરમિયાન વાંચવામાં આવેલા બાઇટ્સ સામે લાગુ કરવામાં આવતી ચેટ-રૂટની કડક મર્યાદા, જેમાં ગેરહાજર, અમાન્ય અથવા ભ્રામક `Content-Length` ધરાવતી વિનંતીઓ પણ સામેલ છે; મર્યાદા ઓળંગવા પર `413` મળે છે. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(અસેટ — વિનંતી-ગણતરીની કોઈ મર્યાદા નથી)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** આ લેગસી વિનંતી-COUNT મર્યાદા હવે માત્ર સ્પષ્ટ રીતે સેટ કરવામાં આવે ત્યારે જ લાગુ પડે છે. તેને અસેટ રાખવામાં આવે ત્યારે (ડિફૉલ્ટ), ભારે ચેટ પ્રવેશને તેના બદલે `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` દ્વારા નિયંત્રિત કરવામાં આવે છે — જે **એક પ્રોસેસમાં** (એક V8 હીપ) પ્રોસેસની વાસ્તવિક મેમરી મર્યાદામાંથી માપ નક્કી કરાયેલ, આપમેળે વ્યૂત્પન્ન BYTE બજેટ છે. એકબીજા સાથે ઓવરલેપ થતી આશરે 750k-ટોકનવાળી બે `/v1/responses` વિનંતીઓ આશરે 12 Gi હીપ્સને અબોર્ટ કરે છે (#7849) — આ **મેમરી-બજેટ** ચેતવણી છે, 2ની કડક પ્રોડક્ટ મર્યાદા નથી. સ્વસ્થ પ્રોસેસ (શેડ રેશિયોથી નીચેનો હીપ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` દ્વારા વધુ સમકાલીન લાંબી `/v1/responses` વિનંતીઓને પ્રવેશ આપી શકે છે. દસકાઓ લાંબા SSE ક્લાયન્ટ્સ (40–50) એ હીપ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110નો વિષય છે, “મહત્તમ 2”નો નહીં. “હોસ્ટનો ઉપયોગ કરવા” આ મૂલ્યને વિચાર્યા વિના વધારવાથી #7849 ફરી સર્જાય છે. **N સ્વતંત્ર `DATA_DIR`s** સાથે **હીપ્સ**ની સંખ્યા વધારો (#11024); એક SQLite ફાઇલ પર ક્યારેય `replicas>1` ન રાખો. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(આપમેળે વ્યૂત્પન્ન)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** આપમેળે વ્યૂત્પન્ન ઇન્જેસ્ટ બાઇટ બજેટ માટેનું ઓવરરાઇડ (વધુ કડક V8/cgroup મેમરી મર્યાદાના 25%ને 8x ક્ષણિક એમ્પ્લિફિકેશન વડે ભાગીને મેળવેલું). વ્યૂત્પન્ન અને સ્પષ્ટ મૂલ્યોને 8 MiB–2 GiBની મર્યાદામાં ક્લેમ્પ કરવામાં આવે છે. અસરકારક બજેટથી મોટું બોડી તરત જ `413 body_exceeds_budget` સાથે નિષ્ફળ જાય છે; વ્યક્તિગત રીતે સેવા આપી શકાય તેવા બોડીઝ વચ્ચેનો વિવાદ ફરી પ્રયાસ કરી શકાય તેવો `503` જ રહે છે. 40–50 સમકાલીન લાંબા SSE ક્લાયન્ટ્સ આ બજેટ + હીપનો વિષય છે, કડક “મહત્તમ 2”નો નહીં. ટ્યુનિંગ કરતાં પહેલાં `/api/monitoring/health` પર `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` વાંચો. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE અને STRUCTURE ભારે પ્રવેશ માટે હીપ-પ્રેશર શેડ રેશિયો (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`થી આગળની સમકાલીન ભારે વિનંતીને ફરી પ્રયાસ કરી શકાય તેવા `503` સાથે માત્ર ત્યારે જ શેડ કરવામાં આવે છે જ્યારે હીપ પણ આ રેશિયો પર અથવા તેનાથી ઉપર હોય; સ્વસ્થ હીપ પર તેને તેના બદલે healthy-headroom દ્વારા પ્રવેશ આપવામાં આવે છે. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ડિફૉલ્ટ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE અને BYTE **બંને** (`admitChatRequest`, જેમાં ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` બોડીઝ સામેલ છે) પર healthy-heap ફાસ્ટ પાથ (#10437) માટે મર્યાદિત વધારાની ક્ષમતા. આ મર્યાદા વિના, દરેક વ્યસ્ત-પરંતુ-સ્વસ્થ-હીપ વિનંતી કોઈ મહત્તમ મર્યાદા વિના પ્રવેશ નિયંત્રણને બાયપાસ કરતી હતી. healthy-heap પાથ મારફતે આટલા સમકાલીન લીઝ સક્રિય થયા પછી, આગળની વ્યસ્ત વિનંતીઓ વાસ્તવિક હીપ દબાણ હેઠળ વપરાતા સમાન મર્યાદિત-પ્રતીક્ષા/શેડ પાથ પર જાય છે. `0` બાયપાસને સંપૂર્ણપણે અક્ષમ કરે છે. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | સંદેશાઓની એવી સંખ્યા જે ચેટ વિનંતીનું બોડી બાઇટ થ્રેશોલ્ડથી નીચે હોય ત્યારે પણ તેને ભારે તરીકે વર્ગીકૃત કરે છે. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ટૂલ્સની એવી સંખ્યા જે ચેટ વિનંતીનું બોડી બાઇટ થ્રેશોલ્ડથી નીચે હોય ત્યારે પણ તેને ભારે તરીકે વર્ગીકૃત કરે છે. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | વિનંતીને ભારે તરીકે વર્ગીકૃત કરતો સાવચેતીભર્યો સ્ટ્રિંગ-સાઇઝ ટોકન અંદાજ; આ પ્રવેશ-ખર્ચ માટેનું પ્રૉક્સી છે, પ્રદાતાનું બિલિંગ ટોકનાઇઝેશન નથી. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (અક્ષમ) | `src/shared/middleware/chatBodyAdmission.ts` | વૈકલ્પિક ઑપ્ટ-ઇન ચેટ ઇતિહાસ મર્યાદા. ડિફૉલ્ટ રૂપે અક્ષમ: સંદેશાઓની સંખ્યા ડિપ્લોયમેન્ટ નીતિ છે, વિનંતીની સાર્વત્રિક લાક્ષણિકતા નથી, અને અહીં મર્યાદા મૂકવાથી કમ્પ્રેશન પાઇપલાઇન વાર્તાલાપને સેવાયોગ્ય બનાવે તે પહેલાં જ ટર્મિનલ `413` સાથે તેને નકારવામાં આવે છે. હીપ વૃદ્ધિ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` અને હીપ-પ્રેશર શેડ દ્વારા મર્યાદિત છે. કડક મહત્તમ મર્યાદાની જરૂર ધરાવતા મેમરી-મર્યાદિત ડિપ્લોયમેન્ટ્સ પર હકારાત્મક મૂલ્ય સેટ કરો; ત્યારબાદ મર્યાદા ઓળંગવા પર સંરચિત compact-required `413` મળે છે. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | મેમરીમાં સંપૂર્ણપણે બફર કરવામાં આવતા નોન-સ્ટ્રીમિંગ અપસ્ટ્રીમ પ્રતિસાદ માટેની કડક મર્યાદા. આ મર્યાદા પછી અપસ્ટ્રીમ રીડર રદ કરવામાં આવે છે અને હીપ ખાલી થાય ત્યાં સુધી અમર્યાદિત સ્ટ્રિંગ વધવા દેવાને બદલે વિનંતી તરત નિષ્ફળ જાય છે. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | અપસ્ટ્રીમ પ્રતિસાદ હેડર્સમાંથી ફોરવર્ડ કરવામાં આવતા મહત્તમ વાયર બાઇટ્સ. જ્યારે બજેટ વટાવી જાય છે, ત્યારે સામાન્ય રિવર્સ-પ્રોક્સી હેડર મર્યાદાઓની અંદર રહેવા માટે ઓછી પ્રાથમિકતાવાળા હેડર્સ (દા.ત., કસ્ટમ `x-codex-*`, `x-oai-request-id`) પડતા મૂકવામાં આવે છે. મોટા પ્રતિસાદ હેડર કદના ભોગે વધુ અપસ્ટ્રીમ મેટાડેટા ફોરવર્ડ કરવા માટે આને ઊંચું સેટ કરો. | -| `CORS_ORIGIN` | _(સેટ કરેલું નથી)_ | `src/server/cors/origins.ts` | લેગસી સિંગલ-ઓરિજિન CORS અનુમતિસૂચિ. નવા ડિપ્લોયમેન્ટ્સ માટે `CORS_ALLOWED_ORIGINS`ને પ્રાધાન્ય આપો. CORS ફક્ત ક્રોસ-ઓરિજિન બ્રાઉઝર API ક્લાયન્ટ્સ માટે છે; તેના બદલે પ્રમાણિત ડૅશબોર્ડ રાઇટ્સ સમાન-ઓરિજિન વિનંતીઓ સાથે સેશન-બાઉન્ડ CSRF સુરક્ષાનો ઉપયોગ કરે છે. | -| `CORS_ALLOWED_ORIGINS` | _(સેટ કરેલું નથી)_ | `src/server/cors/origins.ts` | કૉમાથી અલગ કરેલી CORS અનુમતિસૂચિ. જ્યાં સુધી `CORS_ALLOW_ALL=true` સ્પષ્ટ રીતે કૉન્ફિગર ન કરવામાં આવે ત્યાં સુધી કોઈ વાઇલ્ડકાર્ડ મોકલવામાં આવતું નથી. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | કોઈપણ બ્રાઉઝર `Origin`ને પ્રતિધ્વનિત કરવા માટે માત્ર ડેવલપમેન્ટ માટેનો બચાવ માર્ગ. શેર કરેલા અથવા પ્રોડક્શન ડિપ્લોયમેન્ટ્સ પર તેને સક્ષમ કરશો નહીં. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | ખાનગી/લૂપબૅક/લિંક-લોકલ IP શ્રેણીઓને લક્ષ્ય કરતી પ્રોવાઇડર કૉલ્સને અવરોધો. ફક્ત અલગ રાખેલા ટેસ્ટ એન્વાયર્નમેન્ટ્સમાં જ અક્ષમ કરો. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | ખાનગી/સ્થાનિક નેટવર્ક્સ (localhost, 192.168.x.x, 10.x.x.x, વગેરે) તરફ નિર્દેશ કરતા પ્રોવાઇડર URLsને મંજૂરી આપો. **સેલ્ફ-હોસ્ટેડ પ્રોવાઇડર્સ માટે આવશ્યક છે** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). જ્યારે `false` હોય, ત્યારે ડૅશબોર્ડ સ્થાનિક URLsનું વૅલિડેશન નકારે છે. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | સ્થાનિક/ખાનગી સરનામાંઓ (127.0.0.1, localhost, LAN, ખાનગી શ્રેણીઓ) પર પ્રોવાઇડર્સ ઉમેરવા/વૅલિડેટ કરવાની મંજૂરી આપો — જે પ્રોવાઇડર વૅલિડેશન પાથ પૂરતું મર્યાદિત છે. **ડિફૉલ્ટ `true`** (સ્થાનિક-પ્રથમ); ફક્ત જાહેર સરનામાંઓને મંજૂરી આપતું કડક અવરોધન લાગુ કરવા માટે `false` પર સેટ કરો. ક્લાઉડ-મેટાડેટા એન્ડપોઇન્ટ્સ (169.254.169.254, metadata.google.internal) કોઈપણ સંજોગોમાં અવરોધિત જ રહે છે. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` રૂટ્સને (ટ્રાન્સક્રિપ્શન્સ, સ્પીચ, ટ્રાન્સલેશન્સ) localhostની બહાર હોસ્ટ કરેલા OpenAI-સુસંગત પ્રોવાઇડર નોડનો ઉપયોગ કરવા દો. ડિફૉલ્ટ રૂપે બંધ — ઑડિયોને રિમોટ હોસ્ટ તરફ રૂટ કરવાથી ઇગ્રેસ ઓળખ બદલાય છે અને તે ઑપરેટરનો સ્પષ્ટ નિર્ણય હોવો આવશ્યક છે. લૂપબૅક/ખાનગી નોડ્સ (localhost, 127.0.0.1, 172.16-31.x) હંમેશાં માન્ય છે અને તેનાથી અપ્રભાવિત રહે છે. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`ને (અને મેમરી એન્જિનના લૂપબૅક રીરૅન્ક પગલાને) localhostની બહાર હોસ્ટ કરેલા OpenAI-સુસંગત પ્રોવાઇડર નોડનો ઉપયોગ કરવા દો — TEI, Infinity, vLLM વગેરે ચલાવતું LAN બૉક્સ અથવા Tailscale પીઅર. ડિફૉલ્ટ રૂપે બંધ — રિમોટ હોસ્ટ તરફ રૂટ કરવાથી ઇગ્રેસ ઓળખ બદલાય છે અને તે ઑપરેટરનો સ્પષ્ટ નિર્ણય હોવો આવશ્યક છે. લૂપબૅક નોડ્સ (localhost, 127.0.0.1, 172.16-31.x) હંમેશાં માન્ય છે અને તેનાથી અપ્રભાવિત રહે છે. રિમોટ નોડ્સે પ્રોવાઇડરની આઉટબાઉન્ડ URL નીતિ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) પણ પાસ કરવી આવશ્યક છે; ક્લાઉડ-મેટાડેટા હોસ્ટ્સ તરફ ક્યારેય રૂટ કરવામાં આવતું નથી. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC સક્ષમ હોય ત્યારે પાસવર્ડ લૉગિન અક્ષમ કરો, જેથી વપરાશકર્તાઓ ફક્ત OIDC Single Sign-On મારફતે જ પ્રમાણિત થઈ શકે. સાદો ઉપનામ `OIDC_DISABLE_PASSWORD_LOGIN` પણ સ્વીકારવામાં આવે છે; સમાન કી ધરાવતા Dashboard Feature Flagને પ્રાથમિકતા મળે છે. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`નું સાદું ઉપનામ (#10889). | +| વેરિએબલ | ડિફૉલ્ટ | સોર્સ ફાઇલ | વર્ણન | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | મશીન ફિંગરપ્રિન્ટિંગ માટે હાર્ડવેર ઓળખકર્તાઓ સાથે સંયોજિત કરવામાં આવતું સૉલ્ટ. અલગતા માટે દરેક ડિપ્લોયમેન્ટમાં તેને બદલો. | +| `OMNIROUTE_CLI_SALT` | _(સેટ ન હોય = દરેક ઇન્સ્ટોલેશન માટે રૅન્ડમ સૉલ્ટ, જે `/cli-token-salt.json` પર સંગ્રહિત રહે છે)_ | `src/lib/machineToken.ts` | સ્થાનિક CLI પ્રમાણીકરણ ટોકન મેળવવા માટેનું HMAC સૉલ્ટ. આ મૂલ્ય સેટ કરવાથી મશીન પરના બધા CLI ટોકન રોટેટ થાય છે અને તેને હંમેશાં સંગ્રહિત સૉલ્ટ કરતાં પ્રાથમિકતા મળે છે. `docs/security/CLI_TOKEN.md` જુઓ. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | સેશન કૂકીઝ પર `Secure` ફ્લૅગ સેટ કરે છે. HTTPS પાછળ ચલાવતી વખતે આ **`true` હોવું જ જોઈએ**. | +| `REQUIRE_API_KEY` | `false` | API મિડલવેર | જ્યારે `true` હોય, ત્યારે બધી `/v1/*` પ્રૉક્સી વિનંતીઓમાં માન્ય API કી સામેલ હોવી આવશ્યક છે. **આ ફ્લૅગ `GET /v1/models`ને નિયંત્રિત કરતું નથી**, જે તેના બદલે ડૅશબોર્ડ લૉગિન સ્થિતિ (`requireAuthForModels`)ને અનુસરે છે — તેથી `/v1/models` તરફથી મળતો `401` એ અનુમાન સુરક્ષિત હોવાનું દર્શાવતો નથી. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) જુઓ. | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ડૅશબોર્ડ UIમાં API કીનાં સંપૂર્ણ મૂલ્યો જાહેર કરવાની મંજૂરી આપે છે. ડૅશબોર્ડ ફીચર ફ્લૅગ્સમાંથી રૂપરેખાંકિત કરી શકાય છે; શેર કરેલા ઇન્સ્ટન્સ પર સુરક્ષાનું જોખમ છે. | +| `NO_LOG_API_KEY_IDS` | _(ખાલી)_ | `src/lib/compliance/index.ts` | વિનંતી લૉગિંગને બાયપાસ કરતા, અલ્પવિરામથી અલગ કરેલા API કી ID (GDPR અનુપાલન). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(સેટ ન હોય = અમર્યાદિત)_ | `src/shared/utils/apiKeyPolicy.ts` | એવી API કીઓ માટે લાગુ થતું પ્રતિ-દિવસ વિનંતી બજેટનું ફૉલબૅક, જેમની `rate_limits` કૉલમ null છે. સેટ ન હોય અથવા ખાલી હોય: કોઈ ગર્ભિત મર્યાદા નહીં (#2289, #11017). `0` પણ સમાન છે (અમર્યાદિત). ધન પૂર્ણાંક Nથી N/દિવસ, 5N/અઠવાડિયું, 20N/મહિનો સક્ષમ થાય છે. ખામીયુક્ત બિન-ખાલી મૂલ્યો લેગસી 1000/દિવસ, 5000/અઠવાડિયું, 20000/મહિનો વિન્ડોઝ પર ફૉલબૅક થાય છે. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | વિનંતી બૉડીનું મહત્તમ મંજૂર કદ. આ મર્યાદાથી મોટા પેલોડ્સને નકારે છે. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | આ થ્રેશોલ્ડ જેટલી અથવા તેનાથી મોટી વાસ્તવિક વિનંતી બૉડીઝ JSON પાર્સિંગ પહેલાં ઍટમિક, પ્રોસેસ-લોકલ હેવીવેઇટ ઍડમિશન લીઝ મેળવે છે (BYTE પાથ, જેમાં `POST /v1/responses` સામેલ છે). સ્ટ્રક્ચર-હેવીની જેમ જ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) હેલ્ધી-હેડરૂમ એસ્કેપ; તેમ છતાં `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) દ્વારા મર્યાદિત રહે છે, જેથી [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ફરી ન આવે. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | મર્યાદિત ઇન્જેશન દરમિયાન વાંચવામાં આવેલા બાઇટ્સ સામે લાગુ કરવામાં આવતી ચેટ-રૂટની કડક મર્યાદા, જેમાં ગેરહાજર, અમાન્ય અથવા ભ્રામક `Content-Length` ધરાવતી વિનંતીઓ પણ સામેલ છે; મર્યાદા ઓળંગવા પર `413` મળે છે. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(અસેટ — વિનંતી-ગણતરીની કોઈ મર્યાદા નથી)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** આ લેગસી વિનંતી-COUNT મર્યાદા હવે માત્ર સ્પષ્ટ રીતે સેટ કરવામાં આવે ત્યારે જ લાગુ પડે છે. તેને અસેટ રાખવામાં આવે ત્યારે (ડિફૉલ્ટ), ભારે ચેટ પ્રવેશને તેના બદલે `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` દ્વારા નિયંત્રિત કરવામાં આવે છે — જે **એક પ્રોસેસમાં** (એક V8 હીપ) પ્રોસેસની વાસ્તવિક મેમરી મર્યાદામાંથી માપ નક્કી કરાયેલ, આપમેળે વ્યૂત્પન્ન BYTE બજેટ છે. એકબીજા સાથે ઓવરલેપ થતી આશરે 750k-ટોકનવાળી બે `/v1/responses` વિનંતીઓ આશરે 12 Gi હીપ્સને અબોર્ટ કરે છે (#7849) — આ **મેમરી-બજેટ** ચેતવણી છે, 2ની કડક પ્રોડક્ટ મર્યાદા નથી. સ્વસ્થ પ્રોસેસ (શેડ રેશિયોથી નીચેનો હીપ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` દ્વારા વધુ સમકાલીન લાંબી `/v1/responses` વિનંતીઓને પ્રવેશ આપી શકે છે. દસકાઓ લાંબા SSE ક્લાયન્ટ્સ (40–50) એ હીપ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110નો વિષય છે, “મહત્તમ 2”નો નહીં. “હોસ્ટનો ઉપયોગ કરવા” આ મૂલ્યને વિચાર્યા વિના વધારવાથી #7849 ફરી સર્જાય છે. **N સ્વતંત્ર `DATA_DIR`s** સાથે **હીપ્સ**ની સંખ્યા વધારો (#11024); એક SQLite ફાઇલ પર ક્યારેય `replicas>1` ન રાખો. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(આપમેળે વ્યૂત્પન્ન)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** આપમેળે વ્યૂત્પન્ન ઇન્જેસ્ટ બાઇટ બજેટ માટેનું ઓવરરાઇડ (વધુ કડક V8/cgroup મેમરી મર્યાદાના 25%ને 8x ક્ષણિક એમ્પ્લિફિકેશન વડે ભાગીને મેળવેલું). વ્યૂત્પન્ન અને સ્પષ્ટ મૂલ્યોને 8 MiB–2 GiBની મર્યાદામાં ક્લેમ્પ કરવામાં આવે છે. અસરકારક બજેટથી મોટું બોડી તરત જ `413 body_exceeds_budget` સાથે નિષ્ફળ જાય છે; વ્યક્તિગત રીતે સેવા આપી શકાય તેવા બોડીઝ વચ્ચેનો વિવાદ ફરી પ્રયાસ કરી શકાય તેવો `503` જ રહે છે. 40–50 સમકાલીન લાંબા SSE ક્લાયન્ટ્સ આ બજેટ + હીપનો વિષય છે, કડક “મહત્તમ 2”નો નહીં. ટ્યુનિંગ કરતાં પહેલાં `/api/monitoring/health` પર `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` વાંચો. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE અને STRUCTURE ભારે પ્રવેશ માટે હીપ-પ્રેશર શેડ રેશિયો (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`થી આગળની સમકાલીન ભારે વિનંતીને ફરી પ્રયાસ કરી શકાય તેવા `503` સાથે માત્ર ત્યારે જ શેડ કરવામાં આવે છે જ્યારે હીપ પણ આ રેશિયો પર અથવા તેનાથી ઉપર હોય; સ્વસ્થ હીપ પર તેને તેના બદલે healthy-headroom દ્વારા પ્રવેશ આપવામાં આવે છે. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ડિફૉલ્ટ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE અને BYTE **બંને** (`admitChatRequest`, જેમાં ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` બોડીઝ સામેલ છે) પર healthy-heap ફાસ્ટ પાથ (#10437) માટે મર્યાદિત વધારાની ક્ષમતા. આ મર્યાદા વિના, દરેક વ્યસ્ત-પરંતુ-સ્વસ્થ-હીપ વિનંતી કોઈ મહત્તમ મર્યાદા વિના પ્રવેશ નિયંત્રણને બાયપાસ કરતી હતી. healthy-heap પાથ મારફતે આટલા સમકાલીન લીઝ સક્રિય થયા પછી, આગળની વ્યસ્ત વિનંતીઓ વાસ્તવિક હીપ દબાણ હેઠળ વપરાતા સમાન મર્યાદિત-પ્રતીક્ષા/શેડ પાથ પર જાય છે. `0` બાયપાસને સંપૂર્ણપણે અક્ષમ કરે છે. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | સંદેશાઓની એવી સંખ્યા જે ચેટ વિનંતીનું બોડી બાઇટ થ્રેશોલ્ડથી નીચે હોય ત્યારે પણ તેને ભારે તરીકે વર્ગીકૃત કરે છે. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ટૂલ્સની એવી સંખ્યા જે ચેટ વિનંતીનું બોડી બાઇટ થ્રેશોલ્ડથી નીચે હોય ત્યારે પણ તેને ભારે તરીકે વર્ગીકૃત કરે છે. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | વિનંતીને ભારે તરીકે વર્ગીકૃત કરતો સાવચેતીભર્યો સ્ટ્રિંગ-સાઇઝ ટોકન અંદાજ; આ પ્રવેશ-ખર્ચ માટેનું પ્રૉક્સી છે, પ્રદાતાનું બિલિંગ ટોકનાઇઝેશન નથી. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (અક્ષમ) | `src/shared/middleware/chatBodyAdmission.ts` | વૈકલ્પિક ઑપ્ટ-ઇન ચેટ ઇતિહાસ મર્યાદા. ડિફૉલ્ટ રૂપે અક્ષમ: સંદેશાઓની સંખ્યા ડિપ્લોયમેન્ટ નીતિ છે, વિનંતીની સાર્વત્રિક લાક્ષણિકતા નથી, અને અહીં મર્યાદા મૂકવાથી કમ્પ્રેશન પાઇપલાઇન વાર્તાલાપને સેવાયોગ્ય બનાવે તે પહેલાં જ ટર્મિનલ `413` સાથે તેને નકારવામાં આવે છે. હીપ વૃદ્ધિ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` અને હીપ-પ્રેશર શેડ દ્વારા મર્યાદિત છે. કડક મહત્તમ મર્યાદાની જરૂર ધરાવતા મેમરી-મર્યાદિત ડિપ્લોયમેન્ટ્સ પર હકારાત્મક મૂલ્ય સેટ કરો; ત્યારબાદ મર્યાદા ઓળંગવા પર સંરચિત compact-required `413` મળે છે. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | મેમરીમાં સંપૂર્ણપણે બફર કરવામાં આવતા નોન-સ્ટ્રીમિંગ અપસ્ટ્રીમ પ્રતિસાદ માટેની કડક મર્યાદા. આ મર્યાદા પછી અપસ્ટ્રીમ રીડર રદ કરવામાં આવે છે અને હીપ ખાલી થાય ત્યાં સુધી અમર્યાદિત સ્ટ્રિંગ વધવા દેવાને બદલે વિનંતી તરત નિષ્ફળ જાય છે. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | અપસ્ટ્રીમ પ્રતિસાદ હેડર્સમાંથી ફોરવર્ડ કરવામાં આવતા મહત્તમ વાયર બાઇટ્સ. જ્યારે બજેટ વટાવી જાય છે, ત્યારે સામાન્ય રિવર્સ-પ્રોક્સી હેડર મર્યાદાઓની અંદર રહેવા માટે ઓછી પ્રાથમિકતાવાળા હેડર્સ (દા.ત., કસ્ટમ `x-codex-*`, `x-oai-request-id`) પડતા મૂકવામાં આવે છે. મોટા પ્રતિસાદ હેડર કદના ભોગે વધુ અપસ્ટ્રીમ મેટાડેટા ફોરવર્ડ કરવા માટે આને ઊંચું સેટ કરો. | +| `CORS_ORIGIN` | _(સેટ કરેલું નથી)_ | `src/server/cors/origins.ts` | લેગસી સિંગલ-ઓરિજિન CORS અનુમતિસૂચિ. નવા ડિપ્લોયમેન્ટ્સ માટે `CORS_ALLOWED_ORIGINS`ને પ્રાધાન્ય આપો. CORS ફક્ત ક્રોસ-ઓરિજિન બ્રાઉઝર API ક્લાયન્ટ્સ માટે છે; તેના બદલે પ્રમાણિત ડૅશબોર્ડ રાઇટ્સ સમાન-ઓરિજિન વિનંતીઓ સાથે સેશન-બાઉન્ડ CSRF સુરક્ષાનો ઉપયોગ કરે છે. | +| `CORS_ALLOWED_ORIGINS` | _(સેટ કરેલું નથી)_ | `src/server/cors/origins.ts` | કૉમાથી અલગ કરેલી CORS અનુમતિસૂચિ. જ્યાં સુધી `CORS_ALLOW_ALL=true` સ્પષ્ટ રીતે કૉન્ફિગર ન કરવામાં આવે ત્યાં સુધી કોઈ વાઇલ્ડકાર્ડ મોકલવામાં આવતું નથી. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | કોઈપણ બ્રાઉઝર `Origin`ને પ્રતિધ્વનિત કરવા માટે માત્ર ડેવલપમેન્ટ માટેનો બચાવ માર્ગ. શેર કરેલા અથવા પ્રોડક્શન ડિપ્લોયમેન્ટ્સ પર તેને સક્ષમ કરશો નહીં. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | લેગસી એલિયાસ. તેનું ડૅશબોર્ડ ટૉગલ (ડેટાબેઝ ઓવરરાઇડ) આ env મૂલ્ય કરતાં પહેલાં વાંચવામાં આવે છે; બંનેમાંની કોઈપણ જગ્યાએ `false`, `0`, `no` કે `off` હોસ્ટ તપાસોને એ જ રીતે બંધ કરી દે છે જે રીતે `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` કરે છે — ત્યારે પણ જ્યારે તે ફ્લેગનું ડૅશબોર્ડ ટૉગલ બંધ સાચવેલું હોય; બાકીના મૂલ્યો ગાર્ડને નીચેના બે ફ્લેગને સોંપે છે. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | આઉટબાઉન્ડ URL ગાર્ડની હોસ્ટ તપાસો, ક્લાઉડ-મેટાડેટા બ્લોકને પણ આવરી લેતી (આખો 169.254.0.0/16, metadata.google.internal), ગાર્ડ મોડને અનુસરતા પ્રોવાઇડર પાથ્સ પર બંધ કરે છે: URL વૅલિડેશન, મૉડલ ડિસ્કવરી, પ્રોવાઇડર-નોડ બેઝ URLs (રિમોટ રીરૅન્ક નોડ્સ સહિત) અને પ્રોક્સી-ફૉલબૅક ટેસ્ટ, તેમજ રિમોટ ઇમેજ/મીડિયા ફેચ જેવા પ્રોવાઇડર-સંબંધિત અન્ય આઉટબાઉન્ડ પાથ્સ. તે ખાનગી વેબહૂક લક્ષ્યોને પણ મંજૂરી આપે છે, પરંતુ ક્લાઉડ-મેટાડેટા/લિંક-લોકલ એન્ડપોઇન્ટ્સનો અપવાદ છે, જે આ ફ્લેગ ચાલુ હોય ત્યારે પણ અવરોધિત રહે છે. `BaseExecutor.execute()` મારફતે મોકલાતી ચેટ વિનંતિઓ તેમની ક્લાઉડ-મેટાડેટા તપાસ જાળવી રાખે છે. લોકલ પ્રોવાઇડર્સ ડિફૉલ્ટ સાથે કામ કરે છે: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ડિફૉલ્ટ `true`) વૅલિડેશન, ડિસ્કવરી અને પ્રોવાઇડર-નોડ પાથ્સ પર લોકલ અને LAN URLs પહેલેથી મંજૂર કરે છે; પ્રોક્સી-ફૉલબૅક ટેસ્ટ અને ખાનગી વેબહૂક લક્ષ્યો ફક્ત આ ફ્લેગને જુએ છે અને તે બંધ હોય ત્યાર સુધી લોકલ/LAN હોસ્ટ્સને અવરોધે છે. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` પર પ્રભાવી થાય છે. ડૅશબોર્ડ ટૉગલમાંથી સાચવેલું મૂલ્ય આ વેરિએબલ કરતાં પ્રાથમિકતા ધરાવે છે. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | પ્રોવાઇડર વૅલિડેશન, મૉડલ ડિસ્કવરી, ચેટ અને ગાર્ડ મોડ દ્વારા નિયંત્રિત થતા પ્રોવાઇડર-સંબંધિત અન્ય આઉટબાઉન્ડ પાથ્સ માટે લોકલ/ખાનગી સરનામાંઓ (127.0.0.1, localhost, LAN, ખાનગી શ્રેણીઓ) પર પ્રોવાઇડર URLsને મંજૂરી આપે છે. **ડિફૉલ્ટ `true`** (લોકલ-પ્રથમ): ત્યારે ગાર્ડ ક્લાઉડ-મેટાડેટા એન્ડપોઇન્ટ્સને અવરોધે છે — આખો 169.254.0.0/16 ઉપરાંત જાણીતા મેટાડેટા હોસ્ટનામો (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). ફક્ત જાહેર-માત્ર મોડ માટે `false` સેટ કરો, જે ખાનગી અને લૂપબૅક હોસ્ટ્સને પણ અવરોધે છે. ગાર્ડ હોસ્ટનામ કે IP લિટરલને લખ્યા તેમ જ, કોઈપણ DNS લુકઅપ પહેલાં તપાસે છે; ફક્ત જાહેર-માત્ર મોડમાં સેફ-ફેચ રૅપર વધુમાં હોસ્ટનામને રિઝોલ્વ કરે છે અને કોઈપણ ખાનગી જવાબને નકારે છે. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` તેની પંક્તિમાં સૂચવેલા પાથ્સ પર આ તપાસો બંધ કરે છે. LM Studio, Ollama, vLLM, Llamafile અને Triton જેવા બિલ્ટ-ઇન લોકલ પ્રોવાઇડર્સ કી વૅલિડેશન અને ચેટ માટે બધી હોસ્ટ તપાસો છોડી દે છે. ડૅશબોર્ડ ટૉગલમાંથી સાચવેલું મૂલ્ય આ વેરિએબલ કરતાં પ્રાથમિકતા ધરાવે છે. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` રૂટ્સને (ટ્રાન્સક્રિપ્શન્સ, સ્પીચ, ટ્રાન્સલેશન્સ) localhostની બહાર હોસ્ટ કરેલા OpenAI-સુસંગત પ્રોવાઇડર નોડનો ઉપયોગ કરવા દો. ડિફૉલ્ટ રૂપે બંધ — ઑડિયોને રિમોટ હોસ્ટ તરફ રૂટ કરવાથી ઇગ્રેસ ઓળખ બદલાય છે અને તે ઑપરેટરનો સ્પષ્ટ નિર્ણય હોવો આવશ્યક છે. લૂપબૅક/ખાનગી નોડ્સ (localhost, 127.0.0.1, 172.16-31.x) હંમેશાં માન્ય છે અને તેનાથી અપ્રભાવિત રહે છે. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`ને (અને મેમરી એન્જિનના લૂપબૅક રીરૅન્ક પગલાને) localhostની બહાર હોસ્ટ કરેલા OpenAI-સુસંગત પ્રોવાઇડર નોડનો ઉપયોગ કરવા દો — TEI, Infinity, vLLM વગેરે ચલાવતું LAN બૉક્સ અથવા Tailscale પીઅર. ડિફૉલ્ટ રૂપે બંધ — રિમોટ હોસ્ટ તરફ રૂટ કરવાથી ઇગ્રેસ ઓળખ બદલાય છે અને તે ઑપરેટરનો સ્પષ્ટ નિર્ણય હોવો આવશ્યક છે. લૂપબૅક નોડ્સ (localhost, 127.0.0.1, 172.16-31.x) હંમેશાં માન્ય છે અને તેનાથી અપ્રભાવિત રહે છે. રિમોટ નોડના બેઝ URL પણ પ્રોવાઇડરની આઉટબાઉન્ડ URL નીતિ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) પાસ કરવી આવશ્યક છે. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC સક્ષમ હોય ત્યારે પાસવર્ડ લૉગિન અક્ષમ કરો, જેથી વપરાશકર્તાઓ ફક્ત OIDC Single Sign-On મારફતે જ પ્રમાણિત થઈ શકે. સાદો ઉપનામ `OIDC_DISABLE_PASSWORD_LOGIN` પણ સ્વીકારવામાં આવે છે; સમાન કી ધરાવતા Dashboard Feature Flagને પ્રાથમિકતા મળે છે. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`નું સાદું ઉપનામ (#10889). | ### સુરક્ષા સખ્તીકરણ ચેકલિસ્ટ diff --git a/docs/i18n/ha/CONTRIBUTING.md b/docs/i18n/ha/CONTRIBUTING.md index f158416eb676..4ec7f2f5a49b 100644 --- a/docs/i18n/ha/CONTRIBUTING.md +++ b/docs/i18n/ha/CONTRIBUTING.md @@ -401,20 +401,20 @@ Rubuta unit tests a `tests/unit/` waɗanda aƙalla za su rufe: ## Jerin Dubawa na Pull Request -- [ ] Tests sun yi nasara (`npm test`) -- [ ] Linting ya yi nasara (`npm run lint`) -- [ ] Build ya yi nasara (`npm run build`) -- [ ] An ƙara TypeScript types don sabbin public functions da interfaces -- [ ] Babu hardcoded secrets ko fallback values -- [ ] An saka public upstream credentials ta hanyar `resolvePublicCred()` (duba [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ba a matsayin literals ba -- [ ] Error responses suna bi ta `buildErrorBody()` / `sanitizeErrorMessage()` — babu ainihin stack traces a jikin responses (duba [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell commands (`exec` / `spawn`) suna tura runtime values ta `env`, ba ta string interpolation ba -- [ ] An tabbatar da ingancin duk inputs da Zod schemas -- [ ] An ƙara **fragment** na changelog a ƙarƙashin `changelog.d/{features|fixes|maintenance}/-.md` don sauye-sauyen da masu amfani za su gani (duba [`changelog.d/README.md`](./changelog.d/README.md)) — **kar a** gyara `CHANGELOG.md` kai tsaye; ana haɗa fragments a lokacin release kuma ba sa taɓa samun rikici tsakanin PRs -- [ ] An sabunta documentation (idan ya dace) -- [ ] Babu sababbin faɗakarwar CodeQL / Secret-Scanning da aka buɗe, ko kuma an yi watsi da kowacce tare da hujjar fasaha mai nuni ga takardar `docs/security/` da ta dace -- [ ] Routes da ke ƙaddamar da child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) an rarraba su a matsayin `isLocalOnlyPath()` a `src/server/authz/routeGuard.ts` — duba [Ƙa'ida Mai Tsauri #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Babu trailers na `Co-Authored-By` a cikin commit messages — dole commits su bayyana a ƙarƙashin Git identity na mai repository kawai (Ƙa'ida Mai Tsauri #16) +- [ ] Gwaje-gwaje sun yi nasara (`npm test`) +- [ ] Binciken lint ya yi nasara (`npm run lint`) +- [ ] Gina ya yi nasara (`npm run build`) +- [ ] An ƙara nau'ikan TypeScript don sabbin public functions da interfaces +- [ ] Babu secrets ko fallback values da aka rubuta kai tsaye +- [ ] An saka public upstream credentials ta hanyar `resolvePublicCred()` (duba [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ba a taɓa saka su kai tsaye a matsayin literals ba +- [ ] Error responses suna bi ta `buildErrorBody()` / `sanitizeErrorMessage()` — babu raw stack traces a cikin response bodies (duba [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell commands (`exec` / `spawn`) suna aika runtime values ta `env`, ba ta hanyar string interpolation ba +- [ ] An tantance dukkan inputs da Zod schemas +- [ ] An ƙara **fragment** na changelog a ƙarƙashin `changelog.d/{features|fixes|maintenance}/-.md` don canje-canjen da masu amfani za su gani (duba [`changelog.d/README.md`](./changelog.d/README.md)) — **kada** a gyara `CHANGELOG.md` kai tsaye; ana haɗa fragments lokacin release kuma ba sa taɓa cin karo tsakanin PRs +- [ ] An sabunta takardu (idan ya dace) +- [ ] Ba a buɗe sabbin faɗakarwar CodeQL / Secret-Scanning ba, ko kuma an yi watsi da kowannensu tare da hujjar fasaha mai nuni zuwa takardar `docs/security/` da ta dace +- [ ] An rarraba routes da ke ƙaddamar da child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) a matsayin `isLocalOnlyPath()` a cikin `src/server/authz/routeGuard.ts` — duba [Ƙa'ida Mai Tsauri #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Babu trailers na AI/bot na `Co-authored-by` a cikin commit messages (Ƙa'ida Mai Tsauri #16) — ana yaba wa human collaborators waɗanda aka sake amfani da aikinsu da daidaitattun trailers na `Co-authored-by: Name ` --- diff --git a/docs/i18n/ha/docs/architecture/QUALITY_GATES.md b/docs/i18n/ha/docs/architecture/QUALITY_GATES.md index a867a28cc3d2..e2c27beefc30 100644 --- a/docs/i18n/ha/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ha/docs/architecture/QUALITY_GATES.md @@ -15,264 +15,257 @@ maimaitawa wanda bai dogara da takamaiman kayan aiki ba na wannan tsarin, duba --- -## Kundin ƙofofin tantancewa da bayanan martabar aiwatarwa +## Jerin gate da bayanan martabar aiwatarwa ### Karɓar ɗan takara -Tsarukan aiki na CI da Quality Gates kowannensu yana fitar da tabbataccen hukunci: `Gate / CI` da -`Gate / Quality`. Manufofin karɓarsu masu sigar da aka kayyade suna lissafa kowane aikin sama -a matsayin wajibi ko na shawara. Duk wani aikin wajibi da ya dace dole ne ya yi nasara: sakamakon da -babu, aka soke, aka tsallake, yake jira, ko ba a sani ba ba zai iya tabbatar da PASS ba. Ingantaccen -rarrabuwar docs-only ko catalog-only na iya sa layin lamba bai dace ba; -amma draft PR ba ɗan takarar da aka amince da shi ba ne. Alamar `hotfix` ba ta kawar da buƙatar hujja. - -Dukkan tsarukan aikin biyu suna rufe PRs da turawa zuwa rassan main/release, farawa da hannu da -abubuwan merge-group. Push, dispatch da merge-group suna gudanar da cikakken zaɓi. Forks -da merge groups suna amfani da hosted runners ga ayyukan da in ba haka ba za su zaɓi self-hosted -runners; dole ne a tabbatar da isasshen ƙarfin hosted kafin ƙaddamarwa. - -Kowane rasidin JSON yana bayyana SHA da aka checkout, workflow run da attempt. -CLI yana ƙin rashin daidaituwar SHA na checkout/event. Gwaje-gwajen workflow suna ɗaure kasancewa cikin manufa -da jerin `needs` na aikin hukunci don kada sabon layi ko wanda aka cire ya ɓace ba tare da an lura ba. -Rasidodin suna rufe workflow ɗinsu ne kawai, ba wallafawa, turawa, ko bayanan cikin gida -na advisory scanner da ya riga ya wanzu ba. Kunna sunayen binciken biyu a cikin dokokin reshe -wani sauyin gudanarwa ne daban; ƙara waɗannan ayyukan ba ya kare reshe da kansa. - -### Kundin binciken tsaye - -Kundin npm-alias mai sigar da aka kayyade da membobin static-scan suna cikin -`config/quality/gate-manifest.json`. Gudanar da `npm run check:gate-manifest` don tabbatar da -sunayen scripts da ainihin umarni bisa `package.json`; ƙari, cirewa da -sauyin umarni suna sa duka local hook da ayyukan rarraba sauyi a CI su gaza. -Alias ba workflow job ba ne, matrix instance ba ne, kuma test case ba ne: ba dole ba ne -a gabatar da waɗannan ƙididdiga kamar za a iya musanya su. +Tsarukan aikin CI da Quality Gates kowannensu yana fitar da tabbataccen hukunci: `Gate / CI` da +`Gate / Quality`. Manufar karɓarsu mai sigar da aka ƙayyade tana lissafa kowane aikin upstream +a matsayin wajibi ko na shawarwari. Dole ne aikin wajibi da ya dace ya yi nasara: sakamakon da +babu shi, aka soke, aka tsallake, yake jiran kammalawa ko ba a san shi ba ba zai iya tabbatar da PASS ba. Ingantaccen +rarrabuwar docs-only ko catalog-only na iya sa wani layin code bai dace ba; +draft PR ba ɗan takarar da aka amince da shi ba ne. Alamar `hotfix` ba ta soke buƙatar hujja. + +Dukkan tsarukan aikin suna rufe PRs da pushes zuwa rassan main/release, manual dispatch da +merge-group events. Push, dispatch da merge-group suna gudanar da cikakken zaɓi. Forks +da merge groups suna amfani da hosted runners don ayyukan da in ba haka ba za su zaɓi self-hosted +runners; dole ne a tabbatar da isasshen hosted capacity kafin rollout. + +Kowane JSON receipt yana tantance SHA da aka checkout, workflow run da attempt. +CLI yana ƙin checkout/event SHA mismatch. Gwaje-gwajen workflow suna ɗaure kasancewa cikin policy +da jerin `needs` na aikin verdict domin sabon lane ko wanda aka cire kada ya ɓace a ɓoye. +Receipts ɗin suna rufe workflow nasu ne kawai, ba publication, deployment, ko internals +na advisory scanner da yake akwai ba. Kunna dukkan check names biyun a cikin branch rules +wani canjin gudanarwa ne na daban; ƙara waɗannan jobs ba ya kare branch da kansa. + +### Jerin static scan + +Jerin npm-alias mai sigar da aka ƙayyade da kasancewar static-scan suna cikin +`config/quality/gate-manifest.json`. Gudanar da `npm run check:gate-manifest` domin tabbatar da +script names da ainihin commands ta hanyar kwatanta su da `package.json`; additions, removals da +command drift suna sa local hook da change-classification jobs a CI su gaza. +Alias ba workflow job, matrix instance ko test case ba ne: kada a gabatar da waɗannan ƙididdiga +kamar suna musanyawa da juna. Yi amfani da `npm run quality:scan -- --list` ko `npm run quality:scan:fast -- --list` -don duba aliases da aka zaɓa ba tare da aiwatar da su ba. Runner yana kiran -npm entrypoint, don haka ana kiyaye runtime ɗinsa (har da Bun inda aka saita shi). -Manifest yana rubuta aliases da ke wajen waɗannan profiles a matsayin waɗanda ake kira dabam, kuma +domin duba aliases da aka zaɓa ba tare da aiwatar da su ba. Runner yana kiran +npm entrypoint, don haka runtime ɗinsa (har da Bun inda aka saita shi) yana nan yadda yake. +Manifest yana rubuta aliases da ke wajen waɗannan profiles a matsayin waɗanda ake kira daban, kuma an hana maintenance commands a cikin read-only scan profiles. Waɗannan profiles suna rufe static scan ne kawai. Ba sa tabbatar da product tests, -coverage, packaging, external checks ko cikakken amincewar fitar da ɗan takara. -Karɓar workflow yana amfani da `config/quality/admission-policy.json` mai alaƙa da +coverage, packaging, external checks ko cikakken release acceptance na ɗan takara. +Workflow admission yana amfani da `config/quality/admission-policy.json` da aka haɗa da `scripts/quality/admission-verdict.mjs`. Release-observer profiles suna ci gaba da kasancewa daban; -a duba applicable checks da receipts nasu daban. Kundin bayani -da ke ƙasa manuni ne, ba hujjar cewa an gudanar da gate ba. +a duba applicable checks da receipts nasu da kansu. Bayanin rubutu +da ke ƙasa madogara ce, ba hujjar cewa an gudanar da gate a zahiri ba. Scripts suna ƙarƙashin `scripts/check/` (policy gates) da `scripts/quality/` (ratchet engine). -Tushen gaskiyar CI shi ne `.github/workflows/ci.yml`. +Madogarar gaskiya ta CI ita ce `.github/workflows/ci.yml`. ### Hanya mai sauri ta Release PR (`quality.yml`) -`.github/workflows/quality.yml` yana ƙara wa CI ƙarfi a kan main/release PRs, turawa zuwa protected-branch, -dispatch da merge groups. PRs suna amfani da fast checks da aka tace bisa path. An cire duplicate build -da aka kashe dindindin; ainihin build/package/boot checks suna nan a CI. +`.github/workflows/quality.yml` yana cika CI a kan main/release PRs, protected-branch +pushes, dispatch da merge groups. PRs suna amfani da fast checks da aka tace bisa path. An cire +duplicate build da aka kashe na dindindin; ainihin build/package/boot checks suna nan a CI. -| Aiki | Iyaka | Mai hana wucewa | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `Docs Gates (fast-path)` | Docs/code PRs; bayanan API docs da docs-all | E | -| `Fast Quality Gates` | Code PRs; static checks, typecheck, dashboard typecheck, impacted unit tests | E | -| `Forgotten sibling tests` | Code PRs; ana bi sawun changed modules zuwa static consumers da candidate sibling tests; ana bayar da rahoton barrel da dynamic-import paths a matsayin advisory diagnostics, tare da referenced allowlist exceptions | **Na shawara** | -| `Vitest (fast-path)` | Code PRs; rukunin fast vitest | E | -| `Unit Tests fast-path` | Code PRs; rukunin unit mai shards 4 | E | -| `No new ESLint warnings` | Code PRs; lint guard mai la'akari da suppressions | E, har da forks | -| `Merge integrity (changelog + generated skills)` | PRs da ba draft ba; changelog da generated skill sync | E, har da forks | +| Aiki | Iyaka | Mai hana ci gaba | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `Docs Gates (fast-path)` | Docs/code PRs; API docs refs da docs-all | Ee | +| `Fast Quality Gates` | Code PRs; static checks, typecheck, dashboard typecheck, impacted unit tests | Ee | +| `Forgotten sibling tests` | Code PRs; changed modules da aka bi zuwa static consumers da candidate sibling tests; barrel da dynamic-import paths ana bayar da rahotonsu a matsayin advisory diagnostics, tare da referenced allowlist exceptions | **Na shawarwari** | +| `Vitest (fast-path)` | Code PRs; fast vitest suite | Ee | +| `Unit Tests fast-path` | Code PRs; 4-shard unit suite | Ee | +| `No new ESLint warnings` | Code PRs; suppressions-aware lint guard | Ee, har da forks | +| `Merge integrity (changelog + generated skills)` | Non-draft PRs; changelog da generated skill sync | Ee, har da forks | #### Rahoton forgotten sibling tests `npm run check:forgotten-sibling-tests` yana sake amfani da import resolver da ke bayan test-impact map. -Ga kowane changed production module, yana bayar da rahoton tabbatattun jerin -`changed module/symbol -> static consumer -> candidate sibling test` lokacin da candidate -test ba ya cikin pull-request diff. Ana adana taƙaitaccen Markdown da sakamakon JSON a matsayin -workflow artifact na `forgotten-sibling-tests` don daidaitawa kafin duk wani blocking rollout. - -Sake-fitarwa na barrel da shigo da kayayyaki masu sauyawa binciken gano matsalar resolution ne kawai; ba sa taɓa haifar da -wani sakamakon da zai hana ci gaba. Keɓantattun abubuwan da aka duba suna cikin -`config/quality/forgotten-sibling-allowlist.json`. Dole ne kowace shigarwa ta ambaci consumer da candidate -test, ta bayar da takamaiman dalili, sannan ta haɗa mahaɗin GitHub issue ko pull request. Shigarwar da ba ta da ingantaccen tsari tana haifar da -rashin amincewa kai tsaye. Keɓantattun abubuwa ba za su iya ɓoye candidate test da aka goge ko diff da ya ƙara `.skip`/`.todo` ba; -raunana assertion da sauran hanyoyin ɓoyewa suna ci gaba da kasancewa ƙarƙashin gate mai zaman kansa kuma mai hana ci gaba na -`check:test-masking`. +Ga kowane changed production module, yana bayar da deterministic +`changed module/symbol -> static consumer -> candidate sibling test` chains idan candidate +test ba ya cikin pull-request diff. Ana adana Markdown summary da JSON result a matsayin +`forgotten-sibling-tests` workflow artifact domin calibration kafin kowane blocking rollout. + +Sake-fitarwa ta barrel da shigo da kayayyaki masu sauyawa binciken warwarewa ne kawai; ba sa taɓa haifar da +wani sakamakon da zai hana ci gaba. Keɓance-keɓancen da aka duba suna cikin +`config/quality/forgotten-sibling-allowlist.json`. Dole ne kowace shigarwa ta ambaci mabukaci da gwajin +ɗan takara, ta bayar da takamaiman dalili, sannan ta haɗa da hanyar haɗin wata matsalar GitHub ko buƙatar haɗa canji. Shigarwar da ba ta da ingantaccen tsari tana haifar da +ƙin amincewa ta tsohuwa. Keɓance-keɓance ba za su iya danne gwajin ɗan takara da aka goge ko diff da ke ƙara `.skip`/`.todo` ba; +raunana assertion da sauran hanyoyin ɓoye matsala suna ci gaba da kasancewa ƙarƙashin shingen +`check:test-masking` mai zaman kansa wanda ke hana ci gaba. ### Aiki: `lint` -Yana gudana a kan kowane PR zuwa `main`. Yana hana merge idan ya gaza. - -| Script (`npm run ...`) | Abin da yake tabbatarwa | Mai hana ci gaba | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | Sigar Node.js tana cikin kewayon da ake tallafawa | E | -| `check:cycles` | Shigo da kayayyaki masu zagayawa — dukkan modules na `src/` + `open-sse/` | E | -| `check:route-validation:t06` | Akwai schemas na Zod a kan dukkan routes (manufar Tier 6) | E | -| `check:any-budget:t11` | Adadin `@ts-expect-error // any` bai wuce budget ba (catraca na Tier 11) | E | -| `check:provider-consistency` | Kowane mai samarwa a cikin `providers.ts` yana da shigarwa mai dacewa a cikin `providerRegistry.ts` (haka ma akasin haka, a cikin jerin izini) | Eh | -| `check:model-lifecycle` | Teburan turawa guda uku da ake kula da su da hannu suna ci gaba da daidaituwa da hoton yanayin rayuwa da aka shigar (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ba ya ba wa kowane tsohon id da `REGISTRY` zai iya turawa maki; kowace manufa ta `BUILT_IN_ALIASES` tana cikin `REGISTRY` kuma ba ta cikin hoton tsofaffin id; kowane tsohon id da har yanzu yake cikin `REGISTRY` ana tura shi gaba ko kuma an jera shi a cikin `allowedRetiredInCatalog`; sannan babu tushen ko manufa ta `DEFAULT_DEGRADATION_MAP` da ta bayyana a matsayin tsohuwa a cikin wannan hoton. Wannan ba ya tabbatar da cewa a halin yanzu wata hanyar sama mai aiki tana samar da samfurin. Ba tare da intanet ba — yana kwatantawa da `config/quality/model-lifecycle.json`, wanda ake sabunta shi da hannu ta amfani da `npm run quality:refresh-model-lifecycle` (yana buƙatar hanyar sadarwa; ba a haɗa shi cikin CI ba). `allowedRetiredInCatalog` wata makamar ragewa ce: a ƙara shigarwa kawai tare da batun bin diddigi. | Eh | -| `check:fetch-targets` | Kowane `fetch("/api/...")` a cikin `src/` na ɓangaren abokin hulɗa yana kaiwa ga ainihin `route.ts` | Eh | -| `check:deps` | Duk abubuwan dogaro da za a iya girkawa ta `npm install` a cikin kowane `package.json` da ke ma’ajiyar lambar suna cikin `dependency-allowlist.json`; ana yi wa sabbin fakitoci marasa tsayayyen sigar ko masu kama da sunan wani fakiti alama | Eh | -| `audit:deps` | `npm audit` (tushe + electron) — babu shawarwarin tsaro masu haɗari sosai/masu matuƙar haɗari (yana cakuɗuwa da osv `check:vuln-ratchet`; duba Rationalization Backlog) | Eh | -| `check:lockfile` | Ingancin `package-lock.json` — rajistar https, hash-hash na tabbatar da inganci, babu sauya runduna | Eh | -| `check:licenses` | Jerin izinin lasisin SPDX don abubuwan dogaro na samarwa | Eh | -| `check:tracked-artifacts` | Babu kayan gini / symlink na `node_modules` da aka commit (yana kuma gudana a husky pre-commit; an sauƙaƙa pre-push da gangan — #6716) | Eh | -| `check:ai-attribution` | Babu trailer na AI/bot `Co-Authored-By` ko footer na ƙirƙirawa ta AI a cikin commit, take ko bayanin PR — Ƙa'ida Mai Tsauri #16 (a cikin madaukin fast-gates na `quality.yml` don PR→`release/**` — yana karanta event payload, ba ya yin komai a wajen PR — da kuma mataki na PR-kawai a lint na `ci.yml` don PR→`main`; haka kuma hook na husky `commit-msg`; an yarda da marubuta-tare na ɗan adam; #14436) | -| `check:vitest-exclusions` | Kowane keɓancewar Vitest yana ambaton tracking issue kuma yana bayyana a cikin `config/quality/vitest-exclusions.json` (#13204) | Eh | -| `check:file-size` | Babu fayil ɗin tushe da ya wuce iyakar da aka sa wa kowane extension (ratchet: manyan fayilolin da aka daskare a jerin `frozen`) | Eh | -| `check:error-helper` | Amsoshin kuskure a cikin executors/handlers suna amfani da `buildErrorBody()` / `sanitizeErrorMessage()` (Ƙa'ida Mai Tsauri #12) | Eh | -| `check:migration-numbering` | Fayilolin Migration SQL suna da lambobi a jere, babu gibi ko maimaitawa | E | -| `check:public-creds` | Babu ƙayyadaddun OAuth `client_id`/`client_secret` ko maɓallan Firebase Web a wajen `publicCreds.ts` (Ƙa'ida Mai Tsauri #11) | E | -| `check:db-rules` | Babu ɗanyen SQL a wajen modulolin `src/lib/db/`; babu shigo-da-su-gaba-ɗaya daga `localDb.ts` (Ƙa'idoji Masu Tsauri #2/#5) | E | -| `check:known-symbols` | Masu aiwatar da provider, dabarun routing, da masu fassara da aka yi wa rajista a cikin dispatch tables ɗinsu sun dace da fayilolin da ke kan faifai — babu symbols marasa alaƙa ko waɗanda ba a ayyana ba | E | -| `check:route-guard-membership` | Kowace route da ke ƙaddamar da child process an rarraba ta ta hanyar `isLocalOnlyPath()` (Ƙa'idoji Masu Tsauri #15/#17) | E | -| `check:test-discovery` | Kowane fayil na `*.test.ts` / `*.spec.ts` a cikin repo ana tattara shi ta aƙalla test runner ɗaya (ratchet: jerin fayilolin marasa alaƙa a cikin `test-discovery-baseline.json` zai iya raguwa kawai) | E | -| `check:agent-skills-sync` | Abubuwan agent-skills da aka samar sun yi daidai da kundin tushensu (babu karkacewa) | -| `check:provider-asset-provenance` | Tambura/kadarorin masu samarwa suna ɗauke da shigarwar asali da aka rubuta | -| `lint:json` | Ana iya fassara fayilolin saitin JSON kuma sun cika ƙa'idodin lint na repo | -| `typecheck:core` | Harhada TypeScript ba tare da kurakurai ba (gargaɗin shawarwari kawai) | Eh | -| `typecheck:noimplicit:core` | Tsauraran `noImplicitAny` — don gaba; wuraren kira da yawa da suka riga suka kasance har yanzu suna buƙatar bayanin nau'i | **Na shawara** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` da aka iyakance ga `src/app/(dashboard)/**` (#7033) — zaɓaɓɓen jerin fayiloli 27 da aka yarda da su na `typecheck:core` bai haɗa da kowane dashboard TSX ba, kuma `next build` ma ba ya duba nau'insa (`next.config.mjs` yana saita `ignoreBuildErrors: true`), don haka koma-bayan masu gano suna marasa tushe a can (#6625/#6909) ba su bayyana ga CI ba. Ana kwatanta bambance-bambancen da daskararren ma'aunin asali na adadin kowace fayil/kowace lambar TS (`config/quality/dashboard-typecheck-baseline.json`, irin tsarin tilasta tsufa ɗaya da `check:known-symbols`) — SABABBIN kurakurai kawai da suka zarce adadin da aka kafa a ma'aunin asali ne ke sa ƙofar ta gaza; rage ma'aunin da `--update` idan an gyara kuskuren da ya riga ya kasance. | Eh | +Yana gudana a kan kowace PR zuwa `main`. Yana hana haɗawa idan an samu gazawa. + +| Skrif (`npm run ...`) | Abin da yake tabbatarwa | Mai hana ci gaba | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | Sigar Node.js tana cikin kewayon da ake tallafawa | E | +| `check:cycles` | Shigo da kayayyaki masu zagaye a duk faɗin `src/` + `open-sse/` (wanda ya dogara da AST, an warware `paths` na tsconfig). Bare = shawara kawai, yana jera zagayen. `check:cycles:ratchet` (abin da CI ke gudanarwa) yana hana ci gaba idan adadin ya wuce iyakar `metrics.cycles` a cikin `quality-baseline.json` — a halin yanzu 14, `direction: down`, don haka zai iya raguwa kawai (#15159 G-01/G-02) | E (ratchet) | +| `check:route-validation:t06` | Akwai schemas na Zod a kan dukkan routes (manufar Tier 6) | E | +| `check:any-budget:t11` | Adadin `@ts-expect-error // any` bai wuce kasafin da aka ware ba (catraca na Tier 11) | E | +| `check:provider-consistency` | Kowane mai samarwa a cikin `providers.ts` yana da shigarwar da ta dace a cikin `providerRegistry.ts` (haka kuma akasin haka, a cikin jerin izini) | Ee | +| `check:model-lifecycle` | Teburan sarrafa hanya guda uku da ake kula da su da hannu suna ci gaba da dacewa da hoton yanayin rayuwa da aka ajiye (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ba ya ba wa wani id da aka yi ritaya wanda `REGISTRY` zai iya tura masa hanya maki; kowace manufa ta `BUILT_IN_ALIASES` tana cikin `REGISTRY` kuma ba ta cikin hoton id ɗin da aka yi wa ritaya; kowane id da aka yi wa ritaya amma har yanzu yana cikin `REGISTRY` ana tura shi gaba ko an jera shi a cikin `allowedRetiredInCatalog`; kuma babu tushen ko manufar `DEFAULT_DEGRADATION_MAP` da ta bayyana a matsayin wadda aka yi wa ritaya a cikin wannan hoton. Wannan ba ya tabbatar da cewa a halin yanzu wani tsarin yana samun sabis daga tushen sama mai aiki. Ba tare da intanet ba — yana kwatantawa da `config/quality/model-lifecycle.json`, wanda ake sabunta shi da hannu ta amfani da `npm run quality:refresh-model-lifecycle` (yana buƙatar hanyar sadarwa; ba a haɗa shi da CI ba). `allowedRetiredInCatalog` mataki ne na ragewa a hankali: ƙara shigarwa kawai tare da batun bin diddigi. | Ee | +| `check:fetch-targets` | Kowane `fetch("/api/...")` a cikin `src/` na ɓangaren abokin ciniki yana kaiwa ga ainihin `route.ts` | Ee | +| `check:deps` | Duk abubuwan dogaro da za a iya sakawa ta `npm install` a duk `package.json` da ke cikin ma'ajiyar lambar suna cikin `dependency-allowlist.json`; ana nuna sabbin fakitoci marasa ƙayyadadden siga ko waɗanda aka yi wa slopsquatting | Ee | +| `audit:deps` | `npm audit` (tushen aiki + electron) — babu sanarwar haɗari mai tsanani/matukar tsanani (yana yin ruɓanya da osv `check:vuln-ratchet`; duba Jerin Ayyukan Daidaita Dalilai) | Ee | +| `check:lockfile` | Ingancin `package-lock.json` — wurin rajista na https, hashes na inganci, babu sauya rundunar mai masauki | Ee | +| `check:licenses` | Jerin amincewar lasisin SPDX don abubuwan dogaro na samarwa | Ee | +| `check:tracked-artifacts` | Babu kayan aikin gini / mahaɗan alama na `node_modules` da aka tura (ana kuma gudanar da shi a husky pre-commit; da gangan aka sauƙaƙa pre-push — #6716) | Ee | +| `check:ai-attribution` | Babu ƙarshen `Co-Authored-By` na AI/bot ko bayanin ƙasa na samarwa ta AI a cikin commits, take, ko jikin PR — Ƙa'ida Mai Tsauri #16 (a cikin madaukin ƙofofin sauri na `quality.yml` don PR→`release/**` — yana karanta bayanan event, ba ya yin komai idan ba PR ba ne — da kuma matakin PR-kawai a lint na `ci.yml` don PR→`main`; haka kuma hook ɗin husky na `commit-msg`; an yarda da marubuta-'yan-adam na haɗin gwiwa; #14436) | +| `check:vitest-exclusions` | Kowane keɓancewar Vitest yana ambaton batun sa ido kuma yana bayyana a cikin `config/quality/vitest-exclusions.json` (#13204) | Ee | +| `check:file-size` | Babu fayil ɗin tushe da ya wuce iyakar da aka sa wa kowace extension (ratchet: manyan fayilolin da aka daskarar a jerin `frozen`) | Ee | +| `check:error-helper` | Amsoshin kuskure a cikin executors/handlers suna amfani da `buildErrorBody()` / `sanitizeErrorMessage()` (Ƙa'ida Mai Tsauri #12) | Ee | +| `check:migration-numbering` | Fayilolin SQL na migration suna da lambobi a jere, ba tare da gibi ko maimaitawa ba | Ee | +| `check:public-creds` | Babu OAuth `client_id`/`client_secret` ko maɓallan Firebase Web na zahiri a wajen `publicCreds.ts` (Ƙa'ida Mai Tsauri #11) | Ee | +| `check:db-rules` | Babu ɗanyen SQL a wajen modules na `src/lib/db/`; babu barrel-imports daga `localDb.ts` (Ƙa'idoji Masu Tsauri #2/#5) | Ee | +| `check:known-symbols` | Masu aiwatar da provider, dabarun routing, da translators da aka yi wa rajista a cikin dispatch tables ɗinsu sun dace da fayilolin da ke kan disk — babu symbols marasa alaƙa ko waɗanda ba a ayyana ba | Ee | +| `check:route-guard-membership` | Kowace route da ke ƙaddamar da child process an rarraba ta da `isLocalOnlyPath()` (Ƙa'idoji Masu Tsauri #15/#17) | Ee | +| `check:test-discovery` | Kowane fayil na `*.test.ts` / `*.spec.ts` a cikin repo yana tattaruwa ta aƙalla test runner guda ɗaya (ratchet: jerin marasa alaƙa a cikin `test-discovery-baseline.json` zai iya raguwa kawai) | Ee | +| `check:agent-skills-sync` | Abubuwan agent-skills da aka samar sun yi daidai da kundin tushensu (babu sauyi marar izini) | +| `check:provider-asset-provenance` | Tambura/kadarorin masu samarwa suna ɗauke da shigarwar bayanin asalinsu da aka rubuta | +| `lint:json` | Fayilolin saitin JSON suna iya parse kuma suna bin ƙa'idodin lint na repo | +| `typecheck:core` | Harhada TypeScript ba tare da kurakurai ba (gargaɗin shawarwari kawai) | Ee | +| `typecheck:noimplicit:core` | Tsauraran `noImplicitAny` — don ci gaba ne; wuraren kira da yawa da suka riga suka kasance har yanzu suna buƙatar annotations | **Na shawara** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` da aka taƙaita zuwa `src/app/(dashboard)/**` (#7033) — jerin fayiloli 27 da `typecheck:core` ya ware bai haɗa da kowane dashboard TSX ba, sannan `next build` ma ba ya yin type-check ɗinsa (`next.config.mjs` yana saita `ignoreBuildErrors: true`), don haka koma-bayan orphaned-identifier a wurin (#6625/#6909) ba su bayyana ga CI ba. Ana kwatanta bambance-bambancen da baseline daskararre na adadin kurakurai na kowane fayil/kowane lambar TS (`config/quality/dashboard-typecheck-baseline.json`, irin tsarin tilasta sabunta bayanai na `check:known-symbols`) — sabbin kurakurai KAWAI da suka zarce adadin da ke cikin baseline ne ke sa matakin ya gaza; rage baseline ɗin a hankali da `--update` idan an gyara kuskuren da ya riga ya kasance. | Ee | ### Aiki: `quality-gate` -Yana gudana bayan `test-coverage`. Yana hana haɗewa idan an samu gazawa. +Yana gudana bayan `test-coverage`. Yana hana merge idan ya gaza. -| Script | Abin da yake tabbatarwa | Mai toshewa | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `quality:collect` | Yana fitar da `quality-metrics.json` (adadin gargadin ESLint, ɗaukar gwaji daga rahoton shard da aka haɗe) | Eh (yana gaban ratchet) | -| `quality:ratchet` | Kowace ma'auni a cikin `quality-baseline.json` ba ta taɓarɓare ba (gargadin ESLint ≤ baseline; ɗaukar gwaji ≥ baseline) | Eh | -| `check:duplication` | Maimaituwar lamba (jscpd@4) ba ta wuce baseline da ke cikin `quality-baseline.json` ba | Eh | -| `check:complexity` | Rikitarwar cyclomatic a matakin fayil ba ta wuce iyakar da aka sa ba (ainihin ESLint `complexity` + `max-lines-per-function`) | Eh | -| `check:cognitive-complexity` | Ratchet na rikitarwar fahimta (`eslint-plugin-sonarjs`) — zagayen ESLint na daban; CI yana gudanar da duka biyun a haɗe a matsayin mataki guda na `check:complexity-ratchets` | Eh | -| `check:dead-code` | Ratchet na exports / fayilolin da ba a amfani da su (knip) ba ya taɓarɓarewa idan aka kwatanta da baseline | Eh | -| `check:compression-budget` | Kasafin ma'aunin gwajin matsawa — dole ne mafi ƙarancin adana token na kowane engine kada ya taɓarɓare | Eh | -| `check:type-coverage` | Ratchet na kason da aka sanya wa nau'i (`type-coverage`) ba ya taɓarɓarewa; ya kusan maye gurbin `typecheck:noimplicit:core` gaba ɗaya | Eh | -| `check:codeql-ratchet` | Adadin buɗaɗɗun faɗakarwar CodeQL ba ya taɓarɓarewa (yana karantawa ta `gh api`; yana tsallakewa cikin aminci idan babu token) — don jadawalin sabuntawa da kunnawa da hannu: duba "Ratchet na CodeQL" a ƙasa | Eh | +| Script | Abin da yake tantancewa | Toshewa | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | +| `quality:collect` | Yana fitar da `quality-metrics.json` (adadin gargadin ESLint, coverage daga rahoton shard da aka haɗe) | Eh (kafin ratchet) | +| `quality:ratchet` | Kowace metric a cikin `quality-baseline.json` ba ta taɓarɓare ba (gargadin ESLint ≤ baseline; coverage ≥ baseline) | Eh | +| `check:duplication` | Maimaita lamba (jscpd@4) bai wuce baseline da ke cikin `quality-baseline.json` ba | Eh | +| `check:complexity` | Cyclomatic complexity na matakin fayil bai wuce iyaka ba (ainihin ESLint `complexity` + `max-lines-per-function`) | Eh | +| `check:cognitive-complexity` | Ratchet na cognitive complexity (`eslint-plugin-sonarjs`) — gwajin ESLint na daban; CI yana gudanar da duka biyun a haɗe a matsayin mataki guda na `check:complexity-ratchets` | Eh | +| `check:dead-code` | Ratchet na exports / fayilolin da ba a amfani da su (knip) bai taɓarɓare idan aka kwatanta da baseline ba | Eh | +| `check:compression-budget` | Kasafin benchmark na matsewa — mafi ƙarancin adana tokens na kowane engine kada ya taɓarɓare | Eh | +| `check:type-coverage` | Ratchet na yawan kaso mai type (`type-coverage`) bai taɓarɓare ba; galibi yana maye gurbin `typecheck:noimplicit:core` | Eh | +| `check:codeql-ratchet` | Adadin faɗakarwar CodeQL da suke buɗe bai taɓarɓare ba (yana karantawa ta `gh api`; yana tsallakewa cikin sauƙi idan babu token) — don jadawalin sabuntawa da kunnawa da hannu: duba "CodeQL ratchet" a ƙasa | Eh | ### Aiki: `quality-extended` -Dukkan aikin na ba da shawara ne (`continue-on-error: true`). Ratchets masu tushen npm suna aiki +Dukkan aikin na shawarwari ne (`continue-on-error: true`). Ratchets masu amfani da npm suna gudana da gaske; external scanners suna shigarwa ta `gh release download` kuma suna tsallake kansu (exit 0) idan har yanzu babu binary. -| Script | Abin da yake tabbatarwa | Mai toshewa | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | -| `check:circular-deps` | Babu circular dependencies (dpdm) | **Na shawara** | -| `check:bundle-size` | Girman bundle bai wuce iyakar da aka sa ba | **Na shawara** | -| `check:secrets` | Binciken sirrika (gitleaks) — yana tsallakewa idan babu binary | **Na shawara** | -| `check:vuln-ratchet` | Raunukan tsaro na dependencies (osv-scanner) ba sa taɓarɓarewa — yana tsallakewa idan babu binary | **Na shawara** | -| `check:workflows` | Lint na workflow (actionlint + zizmor); scanners da suka ɓace/lalace, rahotanni marasa inganci ko rashin baseline na ratchet suna haifar da gazawar INCOMPLETE. Ingantattun sakamakon bincike suna bin ƙa'idar strict/advisory/ratchet da aka zaɓa | Ana buƙatar aiwatarwa; ratchet na zizmor yana toshewa a CI | -| `check:openapi-breaking` | Sauye-sauye masu karya yarjejeniyar API ta jama'a (`openapi.yaml`) idan aka kwatanta da base branch (oasdiff) — yana fitar da `openapiBreaking=N`; yana tsallakewa idan babu oasdiff ko ba za a iya warware base spec ba | **Na shawara** | +| Script | Abin da yake tantancewa | Toshewa | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | +| `check:circular-deps` | Babu circular dependencies (dpdm) | **Na shawarwari** | +| `check:bundle-size` | Girman bundle bai wuce iyaka ba | **Na shawarwari** | +| `check:secrets` | Binciken sirrika (gitleaks) — yana tsallakewa idan babu binary | **Na shawarwari** | +| `check:vuln-ratchet` | Raunin tsaro na dependencies (osv-scanner) bai taɓarɓare ba — yana tsallakewa idan babu binary | **Na shawarwari** | +| `check:workflows` | Lint na workflow (actionlint + zizmor); scanners da suka ɓace/lalace, rahotanni marasa inganci ko rashin ratchet baseline suna sa gazawa a matsayin INCOMPLETE. Sakamakon bincike masu inganci suna bin manufofin strict/advisory/ratchet da aka zaɓa | Ana buƙatar aiwatarwa; zizmor ratchet yana toshewa a CI | +| `check:openapi-breaking` | Sauye-sauyen da ke karya public API contract (`openapi.yaml`) idan aka kwatanta da base branch (oasdiff) — yana fitar da `openapiBreaking=N`; yana tsallakewa idan babu oasdiff ko ba za a iya warware base spec ba | **Na shawarwari** | ### Aiki: `docs-sync-strict` -Yana gudana a kan kowane PR zuwa `main`. Yana hana merge idan an samu gazawa. - -| Skrif | Abin da yake tabbatarwa | Mai hana ci gaba | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `check:docs-all` | Babbar ƙofa da ke gudanar da ƙananan ƙofofi 6 da ke ƙasa ɗaya bayan ɗaya | Ee | -| ↳ `check:docs-sync` | Daidaiton sigar CHANGELOG / OpenAPI / llm.txt | Ee | -| ↳ `check:docs-counts` | Ƙididdiga a cikin rubutu (adadin masu samarwa, adadin ƙaura, da sauransu) suna cikin iyakar ratchet ta ainihin ƙididdigar | Ee | -| ↳ `check:env-doc-sync` | Kowace env var da ke cikin `.env.example` tana da bayanai a teburin takardu, haka kuma akasin haka | Ee | -| ↳ `check:deprecated-versions` | Babu tsoffin sigogin da aka daina amfani da su a cikin takardu | Ee | -| ↳ `check:doc-links` | Mahaɗan markdown na ciki a cikin takardu suna kaiwa ga fayiloli na gaske (tsarin `[text]`/`(path)`) | Ee | -| ↳ `check:fabricated-docs` | Routes, env vars, umarnin CLI, sunayen hook, da file paths da aka ambata a takardu suna cikin codebase. Ƙofa mai tsauri ta `--strict`; gazawa mai laushi ba tare da wannan flag ba. | Ee (ta `--strict` a CI) | -| `check:cli-i18n` | Kirtanin umarnin CLI suna cikin dukkan fayilolin locale na i18n | Ee | -| `check:openapi-coverage` | Ƙayyadaddun OpenAPI sun ƙunshi aƙalla mafi ƙarancin matakin routes na gaske da aka sa wa ratchet | Ee | -| `check:openapi-security-tiers` | Bayanan matakan tsaro a cikin `openapi.yaml` sun yi daidai da rarrabewar `routeGuard.ts` | **Na shawara** | -| `check:openapi-routes` | Kowace path a cikin `openapi.yaml` tana kaiwa ga `route.ts` na gaske (hana ƙirƙirar bayanan bogi) | Ee | -| `check:docs-symbols` | Kowace manuniya ta `/api/...` a cikin `docs/**/*.md` tana kaiwa ga `route.ts` na gaske (hana ƙirƙirar bayanan bogi) | Ee | -| `i18n translation drift` | Maɓallan da ba a fassara ba a cikin fayilolin locale na i18n — gargaɗi kawai | **Na shawara** | +Yana gudana a kan kowane PR zuwa `main`. Yana hana haɗawa idan an samu gazawa. + +| Script | Abin da yake tantancewa | Mai hana ci gaba | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `check:docs-all` | Babban ƙofa da ke gudanar da ƙananan ƙofofi 6 da ke ƙasa ɗaya bayan ɗaya | Ee | +| ↳ `check:docs-sync` | Daidaiton sigar CHANGELOG / OpenAPI / llm.txt | Ee | +| ↳ `check:docs-counts` | Ƙididdiga a cikin rubutu (yawan masu samarwa, yawan ƙaura, da sauransu) suna cikin iyakar ratchet ta ainihin ƙididdiga | Ee | +| ↳ `check:env-doc-sync` | Kowace env var a cikin `.env.example` an rubuta bayaninta a teburin takardu, kuma haka ma akasin haka | Ee | +| ↳ `check:deprecated-versions` | Babu tsofaffin kirtanin siga da aka daina amfani da su a cikin takardu | Ee | +| ↳ `check:doc-links` | Mahaɗan markdown na cikin gida a takardu suna kaiwa ga fayiloli na ainihi (tsarin `[text]`/`(path)`) | Ee | +| ↳ `check:fabricated-docs` | Routes, env vars, umarnin CLI, sunayen hook, da hanyoyin fayil da aka ambata a takardu suna cikin codebase. Ƙofa mai tsauri ta `--strict`; gazawa mai sauƙi ba tare da flag ba. | Ee (ta `--strict` a CI) | +| `check:cli-i18n` | Kirtanin umarnin CLI suna cikin duk fayilolin locale na i18n | Ee | +| `check:openapi-coverage` | Bayanin OpenAPI ya ƙunshi aƙalla mafi ƙarancin adadin routes na ainihi da aka ƙayyade ta ratchet | Ee | +| `check:openapi-security-tiers` | Bayanan matakan tsaro a cikin `openapi.yaml` sun yi daidai da rabe-raben `routeGuard.ts` | **Na shawara** | +| `check:openapi-routes` | Kowace path a cikin `openapi.yaml` tana kaiwa ga ainihin `route.ts` (hana ƙirƙirar bayanan bogi) | Ee | +| `check:docs-symbols` | Kowace maƙalar `/api/...` a cikin `docs/**/*.md` tana kaiwa ga ainihin `route.ts` (hana ƙirƙirar bayanan bogi) | Ee | +| `i18n translation drift` | Keys da ba a fassara ba a cikin fayilolin locale na i18n — gargadi kawai | **Na shawara** | ### Aiki: `i18n-ui-coverage` -| Skrif | Abin da yake tabbatarwa | Mai hana ci gaba | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (a ciki) | Rufin maɓallan i18n na UI ya kai ≥ 65% | Ee | -| `check-ui-value-drift` (a ciki) | Sake rubuta **ƙima** ta Turanci ba ya barin wata tsohuwar fassara a baya | Ee | -| `check-new-key-coverage` (a ciki) | An fassara **sabon** maɓallin Turanci a kowane locale — ana ƙin alamar `__MISSING__:` | Ee | -| `check-translation-ratio` | Rabon ainihin fassara na kowane locale (daidai-da-Turanci / placeholder / abubuwan da suka ɓace a wajen allowlist) ba dole ya wuce `config/quality/i18n-translation-baseline.json` + slack ba | **Na shawara** | +| Script | Abin da yake tantancewa | Mai hana ci gaba | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `check-ui-keys-coverage` (inline) | Rufin key na i18n na UI ya kai ≥ 65% | Ee | +| `check-ui-value-drift` (inline) | Sake rubuta **value** na Turanci ba ya barin wani tsohon fassara a baya | Ee | +| `check-new-key-coverage` (inline) | An fassara **sabon** key na Turanci a kowane locale — ana ƙin alamar `__MISSING__:` | Ee | +| `check-translation-ratio` | Rabon fassara na ainihi ga kowane locale (daidai da Turanci / placeholder / abubuwan da suka ɓace waɗanda ba sa cikin allowlist) kada ya wuce `config/quality/i18n-translation-baseline.json` + slack | **Na shawara** | Yana buƙatar `fetch-depth: 0` — ƙofar value-drift tana kwatanta bambancin `en.json` da merge base. #### `check-ui-value-drift` — ƙofar tsohuwar fassara -Tana gano koma-bayan i18n guda ɗaya da sauran ƙofofi ba za su iya gani ta tsarin su ba: ana sake rubuta wata ƙima ta Turanci +Tana gano matsalar koma-bayan i18n guda ɗaya da sauran ƙofofi ba za su iya gani ta fuskar tsari ba: ana sake rubuta value na Turanci, amma fassarorin da aka samo daga Turancin _da ya gabata_ suna nan, don haka -masu amfani da ba Turanci ba suna ci gaba da karanta rubutu mai nuna tabbaci amma wanda yanzu ya zama kuskure. - -Wannan ya taɓa shiga samfurin gaske. An sake rubuta `oauthModal.googleOAuthWarning` lokacin da -mataimakin shiga na Antigravity ya iso (#5203); **39 daga cikin locales 43** sun ci gaba da riƙe rubutun da ke gaya wa masu gudanarwa su "kwafi -cikakken URL sannan su liƙa shi a ƙasa" — tsarin da ba zai iya kammaluwa ga wannan provider ba. Ba a -lura da shi ba sai #8463 saboda: - -- `sync-ui-keys` yana cike maɓallan da suka **ɓace** ne kawai, ba waɗanda suka zama **tsofaffi** ba; -- `check-ui-keys-coverage` yana ƙirga _kasancewar_ maɓalli, don haka tsohuwar fassara tana samun maki a matsayin wadda aka rufe; -- `check-translation-drift` yana bibiyar kwafin takardu na `docs/i18n//**.md` — - ba ya taɓa karanta `src/i18n/messages/*.json`. Yana hana ci gaba a aikin `docs-sync-strict` tun daga - sake daidaitawar 2026-09: gyara babban takarda → `npm run i18n:run -- --files=` (a matakin sashe, mara tsada). - -**Yana lura da diff, ba ya dogaro da baseline.** Yana kwatanta `en.json` a merge base da -working tree; ga kowane key da ƙimar Turancinsa ta canza, duk locale da har yanzu yake riƙe da -fassarar da ba a taɓa ba, ta tsufa. Wannan da gangan yana **daskare bashin da ya riga ya kasance** — diff -ba zai iya bayyana daga wane tsohon rubutun Turanci fassarar da ta daɗe ta samo asali ba, don haka gate ɗin yana tantance -abin da canjin yanzu ya taɓa kawai. Madadin (baseline na hash ga kowane key) zai buƙaci -generated file mai kusan 600 KB, wanda ya ninka baseline mafi girma da ake da shi sau 3, yana canzawa a kowane i18n PR. +masu amfani da ba sa amfani da Turanci suna ci gaba da karanta rubutu mai cike da tabbaci amma wanda yanzu ba daidai ba ne. + +Wannan ya taɓa fitowa a zahiri. An sake rubuta `oauthModal.googleOAuthWarning` lokacin da aka ƙara +mataimakin shiga na Antigravity (#5203); **39 daga cikin locales 43** sun ci gaba da riƙe rubutun da ke gaya wa masu gudanarwa su "kwafi +cikakken URL su liƙa shi a ƙasa" — tsarin da ba zai iya kammaluwa ga wannan provider ba. Ba a +lura da shi ba har sai #8463 saboda: + +- `sync-ui-keys` yana cike keys ne kawai idan **ba su nan**, ba waɗanda suka zama **tsofaffi** ba; +- `check-ui-keys-coverage` yana ƙirga _kasancewar_ key, don haka ana ɗaukar tsohuwar fassara a matsayin wadda aka rufe; +- `check-translation-drift` yana bibiyar madubin takardu na `docs/i18n//**.md` — + ba ya taɓa karanta `src/i18n/messages/*.json`. Yana hana ci gaba a aikin `docs-sync-strict` tun bayan + sake daidaitawar 2026-09: gyara babban takarda → `npm run i18n:run -- --files=` (a matakin sashe, mai sauƙin aiwatarwa). + +**Mai lura da diff, ba mai dogaro da baseline ba.** Yana kwatanta `en.json` a merge base da working tree; ga kowane key da ƙimar Turancinsa ta canza, duk locale da har yanzu yake riƙe da fassarar da ba a taɓa ba ya zama tsohon yayi. Wannan da gangan yana **daskarar da bashin da ya riga ya kasance** — diff ba zai iya bayyana daga wane tsohon rubutun Turanci wata fassara da ta daɗe ta fito ba, don haka gate ɗin yana tantance abin da canjin yanzu ya taɓa ne kawai. Madadin (baseline na hash ga kowane key) zai buƙaci generated file mai girman kusan 600 KB, wanda ya ninka baseline mafi girma da ake da shi sau 3, kuma yana canzawa a kowane i18n PR. Akwai hanyoyi biyu na cika wannan: 1. sabunta fassarorin da abin ya shafa, ko -2. saita su zuwa `__MISSING__:` — runtime zai gabatar da ingantaccen Turancin +2. saita su zuwa `__MISSING__:` — daga nan runtime zai nuna ingantaccen Turancin (`src/i18n/request.ts::deepMergeFallback`, #7258), sannan key ɗin ya shiga jerin jiran fassara. -Idan **ma’anar** string ɗin ta canza, a fi son **sake wa key suna**: sabon key ba zai iya gaji -fassarar da ta tsufa ba. Wannan shi ne tsarin da #8463 ya yi amfani da shi. +Idan **ma’anar** string ɗin ta canza, ya fi kyau a **sake wa key suna**: sabon key ba zai iya gado tsohuwar fassara ba. Wannan shi ne tsarin da #8463 ya yi amfani da shi. ```bash -npm run i18n:check-value-drift # tsaurara (abin da CI ke gudanarwa) -npm run i18n:check-value-drift:warn # bayar da rahoto kawai +npm run i18n:check-value-drift # mai tsauri (abin da CI ke gudanarwa) +npm run i18n:check-value-drift:warn # rahoto kawai BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Yana fita da 0 tare da `SKIP reason=base-unresolved` idan ba za a iya karanta base catalog ba (shallow -clone ba tare da base ref ba), daidai da yadda `check-openapi-breaking` yake yi. +Yana fita da 0 tare da `SKIP reason=base-unresolved` idan ba za a iya karanta base catalog ba (shallow clone ba tare da base ref ba), daidai da halayen `check-openapi-breaking`. -### Job: `i18n` +### Aiki: `i18n` -Cikakken matrix na tabbatar da i18n (job ɗaya ga kowane locale). Dukkan job ɗin na shawara ne. +Cikakken matrix na tantance i18n (aiki ɗaya ga kowane locale). Dukkan aikin na ba da shawara ne kawai. -| Script | Abin da yake tabbatarwa | Toshewa | -| ------------------------------- | ------------------------------ | --------------------------------------------------------------- | -| `validate_translation.py quick` | Cikar fassara ga kowane locale | **Na shawara** (`continue-on-error: true` a kan dukkan job ɗin) | +| Script | Abin da yake tantancewa | Toshewa | +| ------------------------------- | ------------------------------ | ------------------------------------------------------ | +| `validate_translation.py quick` | Cikar fassara ga kowane locale | **Na shawara** (`continue-on-error: true` a duk aikin) | -### Job: `pr-test-policy` +### Aiki: `pr-test-policy` Yana gudana a kan pull requests kawai. -| Script | Abin da yake tabbatarwa | Toshewa | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `check:pr-test-policy` | PRs da suka canza production code a cikin `src/`, `open-sse/`, `electron/`, ko `bin/` dole ne su haɗa ko su sabunta tests (Hard Rule #8) | Eh | -| `check:test-masking` | Test files da aka canza ba sa rage jimillar adadin assert ko ƙara tautologies na `assert.ok(true)` | Eh | -| `check:pr-evidence` | Jikin PR ya ambaci shaidar test/VPS don canjin (yana sarrafa Hard Rule #18 ta hanyar binciken rubutun PR — mai rauni, duba Backlog) | Eh | +| Script | Abin da yake tantancewa | Toshewa | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `check:pr-test-policy` | PRs da suka canza production code a `src/`, `open-sse/`, `electron/`, ko `bin/` dole ne su haɗa ko su sabunta gwaje-gwaje (Doka Mai Tsauri #8) | Eh | +| `check:test-masking` | Test files da aka canza ba sa rage jimillar adadin assert ko ƙara tautologies na `assert.ok(true)` | Eh | +| `check:pr-evidence` | Jikin PR yana ambaton shaidar test/VPS ta canjin (yana sarrafa Doka Mai Tsauri #18 ta hanyar binciken rubutun PR — mai rauni, duba Backlog) | Eh | -### Job: `test-vitest` +### Aiki: `test-vitest` Yana gudana bayan `build`. Yana hana merge idan ya gaza. -| Suite | Abin da yake tabbatarwa | Toshewa | -| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (tools 110), autoCombo, cache — vitest runner | Eh | -| `test:vitest:ui` | Tests na UI components — vitest runner | **Yana toshewa** — an cire gazawar da ta riga ta kasance a sarari cikin `vitest.config.ts`; sabbin gazawa suna sa job ɗin ya gaza | +| Suite | Abin da yake tantancewa | Toshewa | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | MCP server (tools 110), autoCombo, cache — vitest runner | Eh | +| `test:vitest:ui` | Gwaje-gwajen UI component — vitest runner | **Mai toshewa** — an cire gazawar da ta riga ta kasance a fili a `vitest.config.ts`; sabbin gazawa suna sa aikin ya gaza | ### Nightly workflows (masu jadawali, na shawara) -Waɗannan suna gudana bisa cron schedule (da `workflow_dispatch`), ba sa gudana a kan PRs. Dukkansu na shawara ne. +Waɗannan suna gudana bisa cron schedule (da `workflow_dispatch`), ba sa taɓa gudana a kan PRs. Dukkansu na shawara ne kawai. -| Workflow | Abin da yake tabbatarwa | Toshewa | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | fast-check property tests tare da random seed + adadin run mai yawa | **Na shawara** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Na shawara** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (ana tsallake su idan babu provider secret) | **Na shawara** | -| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) a kan OmniRoute mai aiki ta amfani da `docs/openapi.yaml` — yana fito da keta spec / 500s da ba a sarrafa ba (Mataki 8 B.4) | **Na shawara** | -| `nightly-mutation` | Stryker mutation-testing score a kan fast unit lane — mutants da suka tsira suna nuna asserts marasa ƙarfi | **Na shawara** | -| `nightly-compat` | Matrix na dacewar Node engine a duk faɗin ranges na `engines.node` da ake tallafawa | **Na shawara** | +| Workflow | Abin da yake tantancewa | Toshewa | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | fast-check property tests tare da random seed + adadin runs mai yawa | **Na shawara** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Na shawara** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (ana tsallake su idan babu provider secret) | **Na shawara** | +| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) a kan OmniRoute mai aiki ta amfani da `docs/openapi.yaml` — yana fito da take-taken spec / 500s da ba a sarrafa ba (Mataki 8 B.4) | **Na shawara** | +| `nightly-mutation` | Stryker mutation-testing score a kan fast unit lane — mutants da suka tsira suna bayyana assert marasa ƙarfi | **Na shawara** | +| `nightly-compat` | Matrix na dacewar Node engine a duk ranges na `engines.node` da ake tallafawa | **Na shawara** | --- @@ -536,43 +529,43 @@ kuskuren haɗi ne kawai kuma ba ta taɓa karya zaman aiki ba. --- -## Jerin Ayyukan Da Aka Jinkirta Don Daidaitawa (bita ta ROI — Mataki na 9 Zango na 3) +## Jerin Ayyukan Da Aka Jira Don Daidaitawa (bitar ROI — Mataki 9 Zagaye 3) An daidaita wannan kundin da `ci.yml` a ranar 2026-06-17 (sigar da ta gabata ba ta haɗa da `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` ba). Bitar ROI ta jerin da aka daidaita -ta gano waɗannan abubuwan da za a iya sauƙaƙawa. **Haɗe-haɗen sauye-sauyen CI ne na -kai-tsaye; sauya matsayi/cirewa kuwa shawarwarin manufofi ne da aka keɓe wa mai gudanarwa.** Har yanzu ba a -aiwatar da komai daga cikin abubuwan da ke ƙasa ba. +`check:codeql-ratchet`, `check:pr-evidence` ba). Bitar ROI kan jerin da aka daidaita +ta gano waɗannan ayyukan da za a iya sauƙaƙawa. **Haɗe-haɗen sauye-sauyen CI ne na +inji kawai; sauya matsayi/cirewa kuwa shawarwarin manufofi ne da aka keɓe wa mai gudanarwa.** Babu wani abu a ƙasa +da aka fara aiwatarwa tukuna. -**Abubuwan da su ma ba a rubuta bayaninsu a sama ba** (na shawara, ƙarancin sigina): aikin `docs-lint` -(markdownlint + Vale, gaba ɗaya aikin yana da `continue-on-error`) da kuma ayyukan bincike masu zaman kansu +**Haka kuma ba a rubuta bayaninsu a sama ba** (na shawara, alama mai rauni): aikin `docs-lint` +(markdownlint + Vale, dukan aikin yana da `continue-on-error`) da kuma workflows na na'urorin dubawa masu zaman kansu `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` yana cikin -`quality-baseline.json` amma ba a haɗa shi da wani ratchet mai toshewa a `ci.yml` ba — a halin yanzu ma'aunin -ba shi da abin da yake sarrafawa. +`quality-baseline.json` amma ba a haɗa shi da ratchet mai toshewa a `ci.yml` ba — a halin yanzu +ma'aunin ba shi da abin da yake sarrafawa. -### Haɗewa / cire maimaitawa (na kai-tsaye, ƙarancin haɗari) +### Haɗewa / cire maimaituwa (na inji, haɗari kaɗan) -An tabbatar da kowane ɗan takara ta hanyar kwatanta shi da yanayin gate na ainihi a ranar 2026-06-17 (amince amma a tabbatar); -haɗe-haɗe da dama da suka yi kama da “bayyane” sun zama suna ɓoye bashin fasaha kuma **ba** sauye-sauye ne masu sauƙin maye gurbi ba. +An tabbatar da kowane zaɓi bisa halin gate na ainihi a ranar 2026-06-17 (amince amma a tabbatar); +haɗe-haɗe da dama da suka yi kama da “bayyane” sun ɓoye bashi, don haka **ba** za a iya maye gurbinsu kai tsaye cikin sauƙi ba. -- **`check:docs-sync` yana gudana sau biyu** — shi kaɗai a aikin `lint`, sannan kuma a cikin `check:docs-all` (`docs-sync-strict`) da husky pre-commit hook. ✅ **AN KAMMALA** — an cire kiran da ke gudana shi kaɗai na `lint`. -- **Binciken CVE** — ❌ **BA haɗewa ce mai sauƙi ba.** `audit:deps` yana kasa kai-tsaye idan aka samu kowace CVE mai high/critical; `check:vuln-ratchet` (osv) kuwa yana kasa ne kawai idan an sami _koma-baya_ idan aka kwatanta da baseline (a halin yanzu 1 MODERATE). Ma'anarsu ta bambanta — cire `audit:deps` zai kawar da cikakken gate na high/critical. A bar dukansu. -- **Gano zagaye** — ❌ **BA haɗewa ce mai sauƙi ba.** `check:circular-deps` (dpdm) yana bayar da rahoton **zagaye 91** (shi ya sa yake na shawara); ba za a iya mayar da shi mai toshewa ba sai an fara warware su, kuma iyakar abin da yake dubawa ta fi ta koren `check:cycles` da aka zaɓa da kyau faɗi. A bar `check:cycles` yana toshewa; warware zagayen dpdm guda 91 wani aikin backlog ne na daban. -- **Sarƙaƙƙiya** — ✅ **AN KAMMALA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): zagayen ESLint guda ɗaya, yana ƙirgawa bisa ruleId domin cyclomatic+max-lines da cognitive baselines su ci gaba da kasancewa masu zaman kansu; `check:complexity` / `check:cognitive-complexity` na ɗaiɗaiku sun ci gaba da kasancewa don `--update` na cikin gida. -- **Kariya daga ƙagaggen `/api`** — ✅ **AN KAMMALA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): kundin FS guda ɗaya na `src/app/api`, openapi-routes + docs-symbols har yanzu suna bayar da rahoto daban-daban; na ɗaiɗaiku sun ci gaba da kasancewa don gudanarwa a cikin gida. -- **`check:node-runtime` yana gudana a ayyuka 11** — ⚠️ **ƙarancin ROI.** Kowannensu yana kan runner daban kuma binciken bai kai 1s ba; jimillar tanadi ~10s, amma hakan zai sa a rasa kariya mai sauƙi a kowane aiki. Bai cancanci hargitsin sauyin ba. -- **`typecheck:noimplicit:core` a CI lint** — ✅ **an cire daga aikin lint** (a baya na shawara ne da `continue-on-error`); shimfidar nau'ikan da ke toshewa ita ce `typecheck:core` + `check:type-coverage`. An bar script na cikin gida. +- **`check:docs-sync` yana gudana sau biyu** — shi kaɗai a aikin `lint`, sannan kuma a cikin `check:docs-all` (`docs-sync-strict`) da husky pre-commit hook. ✅ **AN KAMMALA** — an cire kiran da ke gudana shi kaɗai daga `lint`. +- **Binciken CVE** — ❌ **BA haɗewa ce mai sauƙi ba.** `audit:deps` yana gaza kai tsaye idan ya ga kowane CVE mai haɗari/babban haɗari; `check:vuln-ratchet` (osv) kuwa yana gaza ne kawai idan an samu _taɓarɓarewa_ idan aka kwatanta da baseline (a halin yanzu 1 MODERATE). Ma'anoninsu sun bambanta — cire `audit:deps` zai kawar da cikakken gate na haɗari/babban haɗari. A bar su duka. +- **Gano madauwari** — ✅ **AN KAMMALA** (#15159 G-01/G-02). Tsohon rubutun nan ya kira `check:cycles` gate “mai kore, da aka zaɓa a tsanake” kuma ya ba da hujjar ci gaba da sanya shi mai toshewa saboda `check:circular-deps` (dpdm) ya bayar da rahoton madauwari 91. Wannan koren sakamakon **koren ƙarya ne**: `check:cycles` ya binciki ƙananan kundin adireshi 5 (fayiloli 450), ya dace da static `import|export … from` kawai, sannan ya yi watsi da duk wani specifier na `@/` da `@omniroute/open-sse/`, don haka bai iya ganin madauwuran dynamic-import + alias da suka mamaye repo ba. An gyara: yanzu gate ɗin yana ratsa `src` + `open-sse` (fayiloli 5023), yana tattara specifiers daga TypeScript AST (don haka `import("…")` yana ƙidayuwa amma `typeof import("…")` da ke matsayin type ba ya ƙidayuwa), kuma yana warware tsconfig `paths`. Yana gano madauwari **14**, ba 0 ba. Saboda ba za a iya gyara madauwari 14 da suka riga suka kasance a cikin gate PR ba, yanzu `check:cycles` ya zama **ratchet** (`--ratchet`, iyaka `metrics.cycles.value = 14` a cikin `quality-baseline.json`, `direction: down`) — yana toshe duk wata _taɓarɓarewa_ kuma adadin zai iya raguwa ne kawai. CI yana gudanar da `npm run check:cycles:ratchet`. Rage adadin a hankali yana tafiya tare da **A-01**. `check:circular-deps` (dpdm) zai ci gaba da zama na shawara a matsayin ra'ayi na biyu mai faɗi. +- **Sarƙaƙiya** — ✅ **AN KAMMALA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): zagayen ESLint guda ɗaya, yana ƙirga bisa ruleId domin cyclomatic+max-lines da cognitive baselines su ci gaba da kasancewa masu zaman kansu; `check:complexity` / `check:cognitive-complexity` na ɗaiɗaiku suna nan don `--update` na gida. +- **Hana ruɗin `/api`** — ✅ **AN KAMMALA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): lissafin FS guda ɗaya na `src/app/api`, openapi-routes + docs-symbols har yanzu suna bayar da rahoto daban-daban; na ɗaiɗaiku suna nan don gudanarwa ta gida. +- **`check:node-runtime` yana gudana a ayyuka 11** — ⚠️ **ROI kaɗan.** Kowanne yana da runner daban kuma binciken bai kai sakan 1 ba; jimillar abin da za a adana kusan sakan 10 ne, amma hakan zai sa a rasa kariyar kowane aiki mai arha. Bai cancanci rikicin sauyin ba. +- **`typecheck:noimplicit:core` a CI lint** — ✅ **an cire daga aikin lint** (a baya na shawara ne da `continue-on-error`); ɓangaren type mai toshewa shi ne `typecheck:core` + `check:type-coverage`. An riƙe script na gida. ### Sauya matsayi / yanke shawara (manufar mai gudanarwa) -- `check:openapi-security-tiers` (na shawara) — ❌ **BA za a iya sauya matsayinsa kai-tsaye ba.** Yana fita da 0 amma yana gargadin cewa wasu routes na `traffic-inspector` da ke ƙarƙashin `LOCAL_ONLY_API_PREFIXES` ba su da alamar `x-loopback-only: true`. Tilasta shi yana buƙatar fara ƙara waɗannan alamomin zuwa `openapi.yaml`. -- `typecheck:noimplicit:core` (na shawara) — ratchet mai toshewa na `check:type-coverage` ya riga ya rufe galibinsa. A sauya shi zuwa ratchet ko a cire zagayen `tsc` na biyu mai maimaituwa. -- `test:vitest:ui` (yanzu **mai toshewa**) — an ware gazawowin da suka riga suka wanzu a `vitest.config.ts` tare da sharhin bibiyar `// #8618`; sabbin gazawowi suna sa aikin ya kasa. -- `check:secrets` (gitleaks, ratchet mai toshewa da aka daskarar a kan false-positives 3 da aka rubuta bayaninsu) — a sanya ukun a allowlist domin a kai 0, ko a saukar da shi zuwa na shawara. Yana yin aikin da ya yi karo da GitHub native secret-scanning + `check:public-creds`. -- `check:pr-evidence` (mai toshewa, yana amfani da greps a rubutun jikin PR) — haɗarin false-positive mai yawa; cire shi zai raunana tilasta Hard Rule #18, don haka wannan shawarar manufa ce ta gaske. -- `semgrep` (na shawara mai zaman kansa) — yana yin aikin da ya yi karo da CodeQL ga rukunonin OWASP; a haɗa baseline ɗinsa da ratchet ko a cire shi. +- `check:openapi-security-tiers` (na shawara) — ❌ **BA za a iya sauya matsayinsa kai tsaye cikin sauƙi ba.** Yana fita da 0 amma yana gargadin cewa routes na `traffic-inspector` da dama a ƙarƙashin `LOCAL_ONLY_API_PREFIXES` ba su da annotation na `x-loopback-only: true`. Tilasta shi yana buƙatar fara ƙara waɗannan annotations zuwa `openapi.yaml`. +- `typecheck:noimplicit:core` (na shawara) — ratchet mai toshewa na `check:type-coverage` ya riga ya rufe yawancin aikinsa. A sauya shi zuwa ratchet ko a cire zagayen `tsc` na biyu mai maimaituwa. +- `test:vitest:ui` (yanzu **mai toshewa**) — an ware gazawar da ta riga ta kasance a sarari cikin `vitest.config.ts` tare da sharhin bin diddigi na `// #8618`; sababbin gazawa za su sa aikin ya gaza. +- `check:secrets` (gitleaks, ratchet mai toshewa da aka daskarar a sakamakon ƙarya 3 da aka rubuta bayaninsu) — a saka ukun cikin allowlist domin a kai 0, ko a saukar da shi zuwa na shawara. Yana maimaita wani ɓangare na aikin GitHub native secret-scanning + `check:public-creds`. +- `check:pr-evidence` (mai toshewa, yana grep rubutun PR-body) — haɗarin sakamakon ƙarya yana da yawa; cire shi zai raunana aiwatar da Hard Rule #18, don haka wannan shawarar manufa ce ta gaske. +- `semgrep` (na shawara mai zaman kansa) — yana maimaita aikin CodeQL ga rukunin OWASP; a haɗa baseline ɗinsa da ratchet ko a cire shi. --- diff --git a/docs/i18n/ha/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ha/docs/compression/COMPRESSION_GUIDE.md index dae18cb00010..bbf153cbe920 100644 --- a/docs/i18n/ha/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ha/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ maimakon Turanci. A jiki mai `messages`, wucewar abun ciki (`shouldBypassCavemanOutputMode()` a cikin `open-sse/services/compression/outputMode.ts`) yana duba saƙonni uku na ƙarshe kuma yana tsallake salo don dukkan juyawar lokacin da suka dace da kalmomin tsaro, aiki maras juyawa, -bayani, ko kalmomin da ke da mahimmancin tsari. Wucewar yana gudana komai abin da -aka saita **Auto-Clarity Bypass** na dashboard (`cavemanOutputMode.autoClarity`) zuwa. +bayani, ko kalmomin da ke da mahimmancin tsari. Wucewar tana gudana yayin da **Auto-Clarity Bypass** na dashboard (`cavemanOutputMode.autoClarity`) yake a kunnawa, wanda shine tsoho; idan an kashe shi, salon da aka zaɓa suna amfani a waɗancan juyawa ma. Lokacin da wucewar ya bar juyawar ta wuce, `placeSystemInstruction()` (fayil ɗaya), wanda baya taɓa ƙirƙirar sabon `messages[0]`, yana sanya toshewar a cikin na farko daga waɗannan da ya samu: diff --git a/docs/i18n/ha/docs/reference/ENVIRONMENT.md b/docs/i18n/ha/docs/reference/ENVIRONMENT.md index 3bb43be74c71..d64f009655a8 100644 --- a/docs/i18n/ha/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ha/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Sirrukan da Ake Buƙata -**Dole ne** a saita waɗannan kafin fara gudanarwa na farko. Idan babu su, manhajar za ta ƙi farawa ko kuma ta yi aiki da saitunan tsoho marasa tsaro. - -| Sauyi | Ana Buƙata | Tsoho | Fayil ɗin Tushe | Bayani | -| ---------------------------- | ------------------- | ------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Eh** | _(babu)_ | `src/lib/auth` | Yana sanya hannu/tabbatar da duk kukis na zaman dashboard (JWT). Ƙirƙira da `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Eh** | _(babu)_ | `src/lib/db/apiKeys.ts` | Maɓallin ɓoyewar AES don ƙimomin maɓallan API da aka adana a SQLite. Ƙirƙira da `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Eh** | `CHANGEME` | Rubutun bootstrap | Yana saita kalmar sirrin farko ta dashboard na mai gudanarwa (ta yi daidai da tsohuwar ƙimar `.env.example` — an bar ta a bayyane marar tsaro ne domin tilasta sauyawa). **Sauya kafin amfani na farko.** Bayan shiga, sauya ta ta Dashboard → Settings → Security. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Eh** (production) | _(ba a saita ba)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sirrin haɗin gwiwa na gadar WebSocket ta Codex Responses ta ciki. Yana tantance buƙatun gada tsakanin Electron/browser WS relay da OmniRoute. ⚠️ **ANA BUƘATARSA a production — idan ba a saita shi ba, za a ƙi duk buƙatun gadar WS.** Ƙirƙira da `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | A'a | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID na musamman na kawar da tsohon cache na service-worker don PWA shell (#11779); shi ne na farko a jerin tantancewa. | -| `SOURCE_VERSION` | A'a | _(ba a saita ba)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Na biyu a jerin — masu gina PaaS (misali irin na Heroku) ne ke saita shi a matsayin commit ɗin da aka tura. | -| `NEXT_PUBLIC_SW_BUILD_ID` | A'a | _(an samo)_ | `src/shared/components/PwaRegister.tsx` | Ƙimar jama'a ta lokacin ginawa da client ke amfani da ita wajen rajistar `/sw.js?v=…`; ana samo ta daga biyun da ke sama, sannan git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | A'a (ta atomatik) | _(ta atomatik a kowane farawa)_ | `src/server/authz/policies/management.ts` | Sirrin kowane process da ke tabbatar da cewa amintaccen tambarin peer-IP ya fito daga HTTP server na OmniRoute kansa (`scripts/dev/peer-stamp.mjs`). authz middleware yana amincewa da kasancewar buƙata a gida (loopback/LAN gating na hanyoyin LOCAL_ONLY) ne kawai idan tambarin yana ɗauke da wannan token. Ana ƙirƙira shi ta atomatik a kowane farawa — bar shi ba a saita ba; saita shi kawai don tsarin multi-process da dole ne su raba tambarin. | -| `OMNIROUTE_TRUSTED_PROXIES` | A'a | _(ba a saita ba)_ | `scripts/dev/peer-stamp.mjs` | IPs ko CIDR ranges na reverse proxies da aka raba da waƙafi, waɗanda jerin yarda/ƙin IP zai iya amincewa da `X-Forwarded-For` / `X-Real-IP` nasu, ban da loopback, adiresoshin private-network da Cloudflare edges, waɗanda a koyaushe ake amincewa da su. Ana buƙatarsa ne kawai don proxy da ke kan wani adiresoshin jama'a dabam; idan ba haka ba, adiresoshin proxy ɗin kansa ne filter zai tantance. | +Sirrai da ba a saita ba waɗanda aka alama da **Eh** ana ƙirƙira su ta atomatik a farkon gwudanarwa (`JWT_SECRET`, `API_KEY_SECRET`) kuma a adana su a cikin `server.env` a babban kundin bayanai — saita su a bayyane idan wannan kundin na ɗan gajeren lokaci ne. Keɓance ita ce `OMNIROUTE_WS_BRIDGE_SECRET`: ba a adana shi a cikin `server.env` ba — shigarwar uwar garken (entrypoints) na ƙirƙira ƙima ta ɗan lokaci a kowane boots wanda gada cikin tsari kawai za ta iya amfani da shi, saboda haka abokan ciniki na wajen gada na buƙatar a saita shi a bayyane. + +| Sauyi | Ana Buƙata | Tsoho | Fayil ɗin Tushe | Bayani | +| ---------------------------- | ------------------- | ------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Eh** | _(babu)_ | `src/lib/auth` | Yana sanya hannu/tabbatar da duk kukis na zaman dashboard (JWT). Ƙirƙira da `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Eh** | _(babu)_ | `src/lib/db/apiKeys.ts` | Maɓallin ɓoyewar AES don ƙimomin maɓallan API da aka adana a SQLite. Ƙirƙira da `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | A'a | _(ba a saita ba)_ | `src/lib/auth/managementPassword.ts` | Yana saita kalmar sirrin dashboard lokacin farawa har yanzu babu wanda aka ajiye; ana hunfa da shi da zarar an ajiye kalmar sirri. npm da shigarwa daga tushen code na kwafi `.env.example` zuwa `.env`, don haka suna farawa da placeholder ɗinsa da aka sani a fili `CHANGEME`: maye shi kafin farkon boots, ko saita `INITIAL_PASSWORD=` (mudu) a cikin wannan `.env` domin a ƙirƙiri kalmar sirri a cikin onboarding wizard a ziyarar farko, kamar yadda ƙimar da ba a saita ba (tsohuwar Docker image) ke yi. Sauya kalmar sirri da aka ajiye a Dashboard → Settings → Security, ko ka gudanar da `omniroute reset-password` (`node bin/reset-password.mjs` a cikin source checkout) da `DATA_DIR` da aka saita zuwa babban kundin bayanan uwar garken. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Eh** (production) | _(ba a saita ba)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sirrin haɗin gwiwa na gadar WebSocket ta Codex Responses ta ciki. Yana tantance buƙatun gada tsakanin Electron/browser WS relay da OmniRoute. ⚠️ **ANA BUƘATARSA a production — idan ba a saita shi ba, za a ƙi duk buƙatun gadar WS.** Ƙirƙira da `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | A'a | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID na musamman na kawar da tsohon cache na service-worker don PWA shell (#11779); shi ne na farko a jerin tantancewa. | +| `SOURCE_VERSION` | A'a | _(ba a saita ba)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Na biyu a jerin — masu gina PaaS (misali irin na Heroku) ne ke saita shi a matsayin commit ɗin da aka tura. | +| `NEXT_PUBLIC_SW_BUILD_ID` | A'a | _(an samo)_ | `src/shared/components/PwaRegister.tsx` | Ƙimar jama'a ta lokacin ginawa da client ke amfani da ita wajen rajistar `/sw.js?v=…`; ana samo ta daga biyun da ke sama, sannan git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | A'a (ta atomatik) | _(ta atomatik a kowane farawa)_ | `src/server/authz/policies/management.ts` | Sirrin kowane process da ke tabbatar da cewa amintaccen tambarin peer-IP ya fito daga HTTP server na OmniRoute kansa (`scripts/dev/peer-stamp.mjs`). authz middleware yana amincewa da kasancewar buƙata a gida (loopback/LAN gating na hanyoyin LOCAL_ONLY) ne kawai idan tambarin yana ɗauke da wannan token. Ana ƙirƙira shi ta atomatik a kowane farawa — bar shi ba a saita ba; saita shi kawai don tsarin multi-process da dole ne su raba tambarin. | +| `OMNIROUTE_TRUSTED_PROXIES` | A'a | _(ba a saita ba)_ | `scripts/dev/peer-stamp.mjs` | IPs ko CIDR ranges na reverse proxies da aka raba da waƙafi, waɗanda jerin yarda/ƙin IP zai iya amincewa da `X-Forwarded-For` / `X-Real-IP` nasu, ban da loopback, adiresoshin private-network da Cloudflare edges, waɗanda a koyaushe ake amincewa da su. Ana buƙatarsa ne kawai don proxy da ke kan wani adiresoshin jama'a dabam; idan ba haka ba, adiresoshin proxy ɗin kansa ne filter zai tantance. | ### Umarnin Ƙirƙirawa @@ -215,38 +215,38 @@ OmniRoute yana amfani da **SQLite** (ta hanyar `better-sqlite3`) don adana dukka ## 4. Tsaro & Tantancewa -| Ma'auni | Tsoho | Fayil ɗin Tushe | Bayani | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt da ake haɗawa da masu gano kayan masarufi domin ƙirƙirar sawun na'ura. Canza shi a kowace turawa domin keɓancewa. | -| `OMNIROUTE_CLI_SALT` | _(ba a saita ba = salt na bazuwar da ake samarwa a kowace girkawa kuma a adana a `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt na HMAC don samar da alamar tantancewar CLI ta gida. Saita wannan ƙimar yana sauya duk alamomin CLI da ke kan na'urar, kuma koyaushe yana da fifiko a kan salt ɗin da aka adana. Duba `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Yana saita tutar `Secure` a kan kukis na zama. **Dole ne ya kasance `true`** yayin aiki a bayan HTTPS. | -| `REQUIRE_API_KEY` | `false` | Tsakiyar manhajar API | Idan yana `true`, dole ne duk buƙatun wakili na `/v1/*` su ƙunshi ingantaccen maɓallin API. **Wannan tutar ba ta sarrafa damar `GET /v1/models`**, wanda a maimakon haka yake bin tsarin shiga na allon sarrafawa (`requireAuthForModels`) — saboda haka, samun `401` daga `/v1/models` BA yana nufin an kare inference ba. Duba `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Yana ba da damar bayyana cikakkun ƙimomin maɓallan API a cikin UI na Allon Sarrafawa. Ana iya daidaita shi daga Tutocin Fasali na Allon Sarrafawa; haɗarin tsaro ne a kan instans ɗin da ake rabawa. | -| `NO_LOG_API_KEY_IDS` | _(fanko)_ | `src/lib/compliance/index.ts` | ID na maɓallan API da waƙafi ya raba waɗanda ke tsallake rubuta bayanan buƙata (bin ƙa'idar GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ba a saita ba = babu iyaka)_ | `src/shared/utils/apiKeyPolicy.ts` | Kasafin buƙatu na kowace rana da ake amfani da shi a matsayin mafita ga maɓallan API waɗanda ginshiƙin `rate_limits` ɗinsu yake null. Idan ba a saita ba ko yana fanko: babu iyakar ɓoye (#2289, #11017). `0` ma haka yake (babu iyaka). Cikakkiyar lamba tabbatacciya N tana kunna N/rana, 5N/mako, 20N/wata. Ƙimomin da ba fanko ba amma tsarinsu ya lalace suna komawa tsoffin iyakokin 1000/rana, 5000/mako, 20000/wata. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Matsakaicin girman jikin buƙata da aka yarda da shi. Yana ƙin payload da suka wuce wannan iyaka. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ainihin jikin buƙata da ya kai ko ya wuce wannan mataki yana karɓar hayar izinin aiki mai nauyi ta atomatik kuma ta cikin process ɗin gida kafin fassara JSON (hanyar BYTE, ciki har da `POST /v1/responses`). Yana da irin hanyar kaucewa ta healthy-headroom ta [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kamar mai nauyin tsari; har yanzu `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) suna iyakance shi don kada [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ya dawo. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Ƙaƙƙarfan iyakar hanyar chat da ake tilastawa bisa bytes da aka karanta yayin karɓa mai iyaka, ciki har da buƙatun da ba su da `Content-Length`, ko suke da marar inganci, ko na yaudara; abin da ya wuce iyaka yana karɓar `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ba a saita ba — babu iyakar adadin buƙatu)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** wannan tsohuwar iyakar ADADIN buƙatu yanzu tana aiki ne kawai idan an saita ta a bayyane. Idan aka bar ta ba a saita ba (tsohon zaɓi), karɓar manyan buƙatun chat a maimakon haka yana ƙarƙashin `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — kasafin BYTE da ake ƙirƙirawa ta atomatik bisa ainihin iyakar ƙwaƙwalwar aikin a cikin **process guda ɗaya** (V8 heap guda ɗaya). Buƙatun `/v1/responses` guda biyu masu cin karo, kowanne da kusan tokens 750k, suna sa heaps na ~12 Gi su gaza (#7849) — gargadin **kasafin ƙwaƙwalwa** ne, ba ƙaƙƙarfan iyakar samfur ta 2 ba. Process mai ƙoshin lafiya (heap ɗinsa yana ƙasa da shed ratio) ZAI IYA karɓar ƙarin dogayen `/v1/responses` masu gudana lokaci guda ta `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dogayen clients na SSE masu yawa (40–50) ya shafi heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ba “iyakar 2” ba. Ƙara wannan babu tunani don “amfani da host” yana sake haifar da #7849. Ninka **heaps** ta amfani da **`DATA_DIR`s`masu zaman kansu guda N** (#11024); kada a taɓa amfani da`replicas>1` a kan fayil ɗin SQLite guda ɗaya. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ana ƙirƙirawa ta atomatik)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ƙimar maye gurbin kasafin ingest bytes da ake ƙirƙirawa ta atomatik (25% na mafi ƙanƙantar iyakar ƙwaƙwalwar V8/cgroup, a raba da ninkawar wucin-gadi ta 8x). Ana taƙaita ƙimomin da aka ƙirƙira da waɗanda aka saita a bayyane zuwa 8 MiB–2 GiB. Body da ya fi kasafin da ke aiki girma yana gaza nan take da `413 body_exceeds_budget`; takara tsakanin bodies waɗanda kowannensu za a iya sarrafawa tana ci gaba da zama abin sake gwadawa da `503`. Dogayen clients na SSE guda 40–50 masu gudana lokaci guda ya shafi wannan kasafi + heap, ba ƙaƙƙarfan “iyakar 2” ba. Karanta `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` a `/api/monitoring/health` kafin daidaitawa. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Shed ratio na matsin heap (`heapUsed / heap_size_limit`) don karɓar manyan buƙatun BYTE da STRUCTURE (#10183, #10268, #10437). Ana kin karɓar wata babbar buƙata mai gudana lokaci guda wadda ta wuce `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` da `503` mai yiwuwar sake gwadawa ne kawai idan heap SHIMA ya kai ko ya wuce wannan ratio; idan heap yana da ƙoshin lafiya, ana karɓar ta ta healthy-headroom maimakon haka. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (tsohon zaɓi `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ƙarin ƙarfin da aka iyakance don fast path na healthy-heap (#10437) a kan **dukansu** STRUCTURE da BYTE (`admitChatRequest`, ciki har da bodies ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Idan babu wannan iyaka, duk wata buƙata yayin cunkoso amma heap yana da ƙoshin lafiya tana tsallake tsarin karɓa ba tare da iyaka ba. Da zarar leases masu gudana lokaci guda sun kai wannan adadi ta hanyar healthy-heap, ƙarin buƙatun lokacin cunkoso suna komawa ga HANYAR jira mai iyaka/ƙin karɓa ƊAYA da ake amfani da ita ƙarƙashin ainihin matsin heap. `0` yana kashe wannan hanyar tsallakewa gaba ɗaya. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Adadin saƙonnin da ke ware buƙatar chat a matsayin babba ko da body ɗinta yana ƙasa da iyakar bytes. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Adadin tools da ke ware buƙatar chat a matsayin babba ko da body ɗinta yana ƙasa da iyakar bytes. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Ƙiyasin tokens mai taka-tsantsan bisa girman string wanda ke ware buƙata a matsayin babba; wannan wakilin kuɗin karɓa ne, ba tsarin tokenization na lissafin kuɗin provider ba. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (an kashe) | `src/shared/middleware/chatBodyAdmission.ts` | Iyakar tarihin chat ta zaɓi. An kashe ta a tsohon zaɓi: adadin saƙonni manufar deployment ce, ba siffar gama-gari ta buƙata ba, kuma sanya iyaka a nan yana ƙin conversations da `413` na ƙarshe kafin pipeline na compression ya iya sa a sarrafa su. Ana iyakance haɓakar heap ta `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` da ƙin karɓa saboda matsin heap. Saita ƙima tabbatacciya a deployments masu ƙarancin ƙwaƙwalwa waɗanda suke buƙatar ƙaƙƙarfan iyaka; abin da ya wuce sai ya karɓi `413` mai tsari wanda ke nuna ana buƙatar compact. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Ƙaƙƙarfan iyaka don upstream response mara streaming wanda ake buffering gaba ɗaya cikin ƙwaƙwalwa. Bayan wannan, ana soke upstream reader kuma buƙatar tana gaza nan take maimakon ci gaba da faɗaɗa string mara iyaka har heap ya ƙare. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Matsakaicin wire bytes da ake turawa daga kanun martanin upstream. Idan an wuce kasafin, ana cire kanun da ba su da fifiko sosai (misali, `x-codex-*`, `x-oai-request-id`) domin kasancewa cikin iyakokin kanun reverse-proxy na yau da kullum. Saita ƙima mafi girma don tura ƙarin metadata na upstream, amma hakan zai ƙara girman kanun martani. | -| `CORS_ORIGIN` | _(ba a saita ba)_ | `src/server/cors/origins.ts` | Tsohon jerin izinin CORS na asali guda ɗaya. Fi son `CORS_ALLOWED_ORIGINS` don sabbin turawa. CORS na abokan hulɗar API na burauza masu asali daban-daban ne kawai; rubuce-rubucen dashboard masu buƙatar tantancewa suna amfani da buƙatun asali ɗaya tare da kariyar CSRF mai alaƙa da zaman. | -| `CORS_ALLOWED_ORIGINS` | _(ba a saita ba)_ | `src/server/cors/origins.ts` | Jerin izinin CORS da waƙafi ya raba. Ba a aika wildcard sai an saita `CORS_ALLOW_ALL=true` a bayyane. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mafitar gaggawa ta yanayin haɓakawa kawai don mayar da amsa ga kowane `Origin` na burauza. Kada a kunna ta a turawa na haɗin gwiwa ko na samarwa. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Toshe kiran masu samarwa da ke nufin jeren IP na private/loopback/link-local. Kashe kawai a keɓantattun muhallan gwaji. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Ba da izinin URLs na masu samarwa da ke nuni zuwa cibiyoyin sadarwa na private/local (localhost, 192.168.x.x, 10.x.x.x, da sauransu). **ANA BUƘATA don masu samarwa da aka karɓi bakuncinsu da kai** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Lokacin da yake `false`, dashboard yana ƙin tabbatar da URLs na gida. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Ba da izinin ƙara/tabbatar da masu samarwa a adireshin local/private (127.0.0.1, localhost, LAN, jeren private) — an iyakance shi ga hanyar tabbatar da mai samarwa. **Tsoho `true`** (local-first); saita `false` don tilasta tsauraran matakan toshe duk abin da ba na jama'a ba. Ana ci gaba da toshe endpoints na cloud-metadata (169.254.169.254, metadata.google.internal) ba tare da la'akari da wannan ba. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Bari hanyoyin `/v1/audio/*` (transcriptions, speech, translations) su yi amfani da node na mai samarwa mai dacewa da OpenAI wanda aka karɓi bakuncinsa a wajen localhost. A kashe yake ta tsohuwa — tura audio zuwa remote host yana canza shaidar egress kuma dole ne ya zama shawarar mai gudanarwa da aka yi a bayyane. Ana ba da izini koyaushe ga nodes na loopback/private (localhost, 127.0.0.1, 172.16-31.x), kuma wannan ba ya shafarsu. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Bari `POST /v1/rerank` (da matakin loopback rerank na injin ƙwaƙwalwa) ya yi amfani da node na mai samarwa mai dacewa da OpenAI wanda aka karɓi bakuncinsa a wajen localhost — na'urar LAN ko abokin Tailscale da ke gudanar da TEI, Infinity, vLLM, da sauransu. A kashe yake ta tsohuwa — tura zirga-zirga zuwa remote host yana canza shaidar egress kuma dole ne ya zama shawarar mai gudanarwa da aka yi a bayyane. Ana ba da izini koyaushe ga nodes na loopback (localhost, 127.0.0.1, 172.16-31.x), kuma wannan ba ya shafarsu. Dole ne remote nodes su kuma bi ƙa'idar URL na outbound na mai samarwa (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); ba a taɓa tura zirga-zirga zuwa hosts na cloud-metadata ba. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Lokacin da aka kunna OIDC, kashe shiga da kalmar sirri domin masu amfani su iya tantance kansu ta OIDC Single Sign-On kawai. Haka kuma ana karɓar alias ɗin tsirara `OIDC_DISABLE_PASSWORD_LOGIN`; Dashboard Feature Flag mai key iri ɗaya yana da fifiko. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias ɗin tsirara na `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Ma'auni | Tsoho | Fayil ɗin Tushe | Bayani | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt da ake haɗawa da masu gano kayan masarufi domin ƙirƙirar sawun na'ura. Canza shi a kowace turawa domin keɓancewa. | +| `OMNIROUTE_CLI_SALT` | _(ba a saita ba = salt na bazuwar da ake samarwa a kowace girkawa kuma a adana a `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt na HMAC don samar da alamar tantancewar CLI ta gida. Saita wannan ƙimar yana sauya duk alamomin CLI da ke kan na'urar, kuma koyaushe yana da fifiko a kan salt ɗin da aka adana. Duba `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Yana saita tutar `Secure` a kan kukis na zama. **Dole ne ya kasance `true`** yayin aiki a bayan HTTPS. | +| `REQUIRE_API_KEY` | `false` | Tsakiyar manhajar API | Idan yana `true`, dole ne duk buƙatun wakili na `/v1/*` su ƙunshi ingantaccen maɓallin API. **Wannan tutar ba ta sarrafa damar `GET /v1/models`**, wanda a maimakon haka yake bin tsarin shiga na allon sarrafawa (`requireAuthForModels`) — saboda haka, samun `401` daga `/v1/models` BA yana nufin an kare inference ba. Duba `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Yana ba da damar bayyana cikakkun ƙimomin maɓallan API a cikin UI na Allon Sarrafawa. Ana iya daidaita shi daga Tutocin Fasali na Allon Sarrafawa; haɗarin tsaro ne a kan instans ɗin da ake rabawa. | +| `NO_LOG_API_KEY_IDS` | _(fanko)_ | `src/lib/compliance/index.ts` | ID na maɓallan API da waƙafi ya raba waɗanda ke tsallake rubuta bayanan buƙata (bin ƙa'idar GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ba a saita ba = babu iyaka)_ | `src/shared/utils/apiKeyPolicy.ts` | Kasafin buƙatu na kowace rana da ake amfani da shi a matsayin mafita ga maɓallan API waɗanda ginshiƙin `rate_limits` ɗinsu yake null. Idan ba a saita ba ko yana fanko: babu iyakar ɓoye (#2289, #11017). `0` ma haka yake (babu iyaka). Cikakkiyar lamba tabbatacciya N tana kunna N/rana, 5N/mako, 20N/wata. Ƙimomin da ba fanko ba amma tsarinsu ya lalace suna komawa tsoffin iyakokin 1000/rana, 5000/mako, 20000/wata. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Matsakaicin girman jikin buƙata da aka yarda da shi. Yana ƙin payload da suka wuce wannan iyaka. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ainihin jikin buƙata da ya kai ko ya wuce wannan mataki yana karɓar hayar izinin aiki mai nauyi ta atomatik kuma ta cikin process ɗin gida kafin fassara JSON (hanyar BYTE, ciki har da `POST /v1/responses`). Yana da irin hanyar kaucewa ta healthy-headroom ta [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kamar mai nauyin tsari; har yanzu `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) suna iyakance shi don kada [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ya dawo. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Ƙaƙƙarfan iyakar hanyar chat da ake tilastawa bisa bytes da aka karanta yayin karɓa mai iyaka, ciki har da buƙatun da ba su da `Content-Length`, ko suke da marar inganci, ko na yaudara; abin da ya wuce iyaka yana karɓar `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ba a saita ba — babu iyakar adadin buƙatu)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** wannan tsohuwar iyakar ADADIN buƙatu yanzu tana aiki ne kawai idan an saita ta a bayyane. Idan aka bar ta ba a saita ba (tsohon zaɓi), karɓar manyan buƙatun chat a maimakon haka yana ƙarƙashin `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — kasafin BYTE da ake ƙirƙirawa ta atomatik bisa ainihin iyakar ƙwaƙwalwar aikin a cikin **process guda ɗaya** (V8 heap guda ɗaya). Buƙatun `/v1/responses` guda biyu masu cin karo, kowanne da kusan tokens 750k, suna sa heaps na ~12 Gi su gaza (#7849) — gargadin **kasafin ƙwaƙwalwa** ne, ba ƙaƙƙarfan iyakar samfur ta 2 ba. Process mai ƙoshin lafiya (heap ɗinsa yana ƙasa da shed ratio) ZAI IYA karɓar ƙarin dogayen `/v1/responses` masu gudana lokaci guda ta `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dogayen clients na SSE masu yawa (40–50) ya shafi heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ba “iyakar 2” ba. Ƙara wannan babu tunani don “amfani da host” yana sake haifar da #7849. Ninka **heaps** ta amfani da **`DATA_DIR`s`masu zaman kansu guda N** (#11024); kada a taɓa amfani da`replicas>1` a kan fayil ɗin SQLite guda ɗaya. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ana ƙirƙirawa ta atomatik)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ƙimar maye gurbin kasafin ingest bytes da ake ƙirƙirawa ta atomatik (25% na mafi ƙanƙantar iyakar ƙwaƙwalwar V8/cgroup, a raba da ninkawar wucin-gadi ta 8x). Ana taƙaita ƙimomin da aka ƙirƙira da waɗanda aka saita a bayyane zuwa 8 MiB–2 GiB. Body da ya fi kasafin da ke aiki girma yana gaza nan take da `413 body_exceeds_budget`; takara tsakanin bodies waɗanda kowannensu za a iya sarrafawa tana ci gaba da zama abin sake gwadawa da `503`. Dogayen clients na SSE guda 40–50 masu gudana lokaci guda ya shafi wannan kasafi + heap, ba ƙaƙƙarfan “iyakar 2” ba. Karanta `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` a `/api/monitoring/health` kafin daidaitawa. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Shed ratio na matsin heap (`heapUsed / heap_size_limit`) don karɓar manyan buƙatun BYTE da STRUCTURE (#10183, #10268, #10437). Ana kin karɓar wata babbar buƙata mai gudana lokaci guda wadda ta wuce `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` da `503` mai yiwuwar sake gwadawa ne kawai idan heap SHIMA ya kai ko ya wuce wannan ratio; idan heap yana da ƙoshin lafiya, ana karɓar ta ta healthy-headroom maimakon haka. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (tsohon zaɓi `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ƙarin ƙarfin da aka iyakance don fast path na healthy-heap (#10437) a kan **dukansu** STRUCTURE da BYTE (`admitChatRequest`, ciki har da bodies ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Idan babu wannan iyaka, duk wata buƙata yayin cunkoso amma heap yana da ƙoshin lafiya tana tsallake tsarin karɓa ba tare da iyaka ba. Da zarar leases masu gudana lokaci guda sun kai wannan adadi ta hanyar healthy-heap, ƙarin buƙatun lokacin cunkoso suna komawa ga HANYAR jira mai iyaka/ƙin karɓa ƊAYA da ake amfani da ita ƙarƙashin ainihin matsin heap. `0` yana kashe wannan hanyar tsallakewa gaba ɗaya. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Adadin saƙonnin da ke ware buƙatar chat a matsayin babba ko da body ɗinta yana ƙasa da iyakar bytes. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Adadin tools da ke ware buƙatar chat a matsayin babba ko da body ɗinta yana ƙasa da iyakar bytes. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Ƙiyasin tokens mai taka-tsantsan bisa girman string wanda ke ware buƙata a matsayin babba; wannan wakilin kuɗin karɓa ne, ba tsarin tokenization na lissafin kuɗin provider ba. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (an kashe) | `src/shared/middleware/chatBodyAdmission.ts` | Iyakar tarihin chat ta zaɓi. An kashe ta a tsohon zaɓi: adadin saƙonni manufar deployment ce, ba siffar gama-gari ta buƙata ba, kuma sanya iyaka a nan yana ƙin conversations da `413` na ƙarshe kafin pipeline na compression ya iya sa a sarrafa su. Ana iyakance haɓakar heap ta `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` da ƙin karɓa saboda matsin heap. Saita ƙima tabbatacciya a deployments masu ƙarancin ƙwaƙwalwa waɗanda suke buƙatar ƙaƙƙarfan iyaka; abin da ya wuce sai ya karɓi `413` mai tsari wanda ke nuna ana buƙatar compact. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Ƙaƙƙarfan iyaka don upstream response mara streaming wanda ake buffering gaba ɗaya cikin ƙwaƙwalwa. Bayan wannan, ana soke upstream reader kuma buƙatar tana gaza nan take maimakon ci gaba da faɗaɗa string mara iyaka har heap ya ƙare. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Matsakaicin wire bytes da ake turawa daga kanun martanin upstream. Idan an wuce kasafin, ana cire kanun da ba su da fifiko sosai (misali, `x-codex-*`, `x-oai-request-id`) domin kasancewa cikin iyakokin kanun reverse-proxy na yau da kullum. Saita ƙima mafi girma don tura ƙarin metadata na upstream, amma hakan zai ƙara girman kanun martani. | +| `CORS_ORIGIN` | _(ba a saita ba)_ | `src/server/cors/origins.ts` | Tsohon jerin izinin CORS na asali guda ɗaya. Fi son `CORS_ALLOWED_ORIGINS` don sabbin turawa. CORS na abokan hulɗar API na burauza masu asali daban-daban ne kawai; rubuce-rubucen dashboard masu buƙatar tantancewa suna amfani da buƙatun asali ɗaya tare da kariyar CSRF mai alaƙa da zaman. | +| `CORS_ALLOWED_ORIGINS` | _(ba a saita ba)_ | `src/server/cors/origins.ts` | Jerin izinin CORS da waƙafi ya raba. Ba a aika wildcard sai an saita `CORS_ALLOW_ALL=true` a bayyane. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mafitar gaggawa ta yanayin haɓakawa kawai don mayar da amsa ga kowane `Origin` na burauza. Kada a kunna ta a turawa na haɗin gwiwa ko na samarwa. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Tsohon alias. Ana karantawa toggle ɗin dashboard nasa (override a cikin database) kafin wannan ƙimar muhalli; `false`, `0`, `no`, ko `off` a dayansu na kashe binciken host ta yadda `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ke yi, ko da lokacin da toggle ɗin dashboard na wannan tutar aka ajiye a kashe; sauran ƙimomi na barin guard ga tutocin biyu da ke ƙasa. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Yana kashe binciken host na guard ɗin URL na outbound, tare da toshe cloud-metadata (duk 169.254.0.0/16, metadata.google.internal), a kan hanyoyin masu samarwa masu biye da yanayin guard: tabbatar da URL, gano models, base URLs na nodes na masu samarwa (ciki har da nodes na rerank na remote), da gwajin proxy-fallback, da sauran hanyoyin outbound masu kusa da masu samarwa kamar dawo da hotuna/midiya na remote. Har ila yau yana ba da izinin manzolin webhook na private, face endpoints na cloud-metadata/link-local, waɗanda ke tsaya a toshe ko da wannan tuta tana aiki. Buƙatun chat da ake tura ta `BaseExecutor.execute()` na ci gaba da binciken cloud-metadata nasu. Masu samarwa na gida suna aiki da tsohuwar data: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (tsoho `true`) riga yana ba da izinin URLs na gida da LAN a hanyoyin tabbatarwa, gano abubuwa, da nodes na masu samarwa; gwajin proxy-fallback da manzolin webhook na private suna duba wannan tutar kaɗai kuma suna toshe hosts na gida/LAN har tana kashe. Yana ƙarfi a kan `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Ƙimar da aka ajiye daga toggle ɗin dashboard ta fi wannan sauyi muhimmanci. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Yana ba da izinin URLs na masu samarwa a adireshin local/private (127.0.0.1, localhost, LAN, jeren private) don tabbatar da mai samarwa, gano models, chat, da sauran hanyoyin outbound masu kusa da masu samarwa da yanayin guard ke jagoranta. **Tsoho `true`** (local-first): sa'an nan guard na toshe endpoints na cloud-metadata — duk 169.254.0.0/16 tare da sunayen host na metadata da aka sani (metadata.google.internal, metadata.goog, IMDS na Alibaba/Azure/Oracle, fd00:ec2::254). Saita `false` don yanayin public-kawai, wanda ke toshe hosts na private da loopback ma. Guard na duba sunan host ko IP kamar yadda aka rubuta, kafin duk binciken DNS; a yanayin public-kawai, wrapper ɗin safe-fetch yana ƙarin warware sunan host kuma ya ƙi duk amsar private. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` na kashe waɗannan binciken a hanyoyin da layinsa ya lissafta. Masu samarwa na gida na daga ciki, kamar LM Studio, Ollama, vLLM, Llamafile, da Triton, na tsallake duk binciken host don tabbatar da maɓalli da chat. Ƙimar da aka ajiye daga toggle ɗin dashboard ta fi wannan sauyi muhimmanci. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Bari hanyoyin `/v1/audio/*` (transcriptions, speech, translations) su yi amfani da node na mai samarwa mai dacewa da OpenAI wanda aka karɓi bakuncinsa a wajen localhost. A kashe yake ta tsohuwa — tura audio zuwa remote host yana canza shaidar egress kuma dole ne ya zama shawarar mai gudanarwa da aka yi a bayyane. Ana ba da izini koyaushe ga nodes na loopback/private (localhost, 127.0.0.1, 172.16-31.x), kuma wannan ba ya shafarsu. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Bari `POST /v1/rerank` (da matakin loopback rerank na injin ƙwaƙwalwa) ya yi amfani da node na mai samarwa mai dacewa da OpenAI wanda aka karɓi bakuncinsa a wajen localhost — na'urar LAN ko abokin Tailscale da ke gudanar da TEI, Infinity, vLLM, da sauransu. A kashe yake ta tsohuwa — tura zirga-zirga zuwa remote host yana canza shaidar egress kuma dole ne ya zama shawarar mai gudanarwa da aka yi a bayyane. Ana ba da izini koyaushe ga nodes na loopback (localhost, 127.0.0.1, 172.16-31.x), kuma wannan ba ya shafarsu. Dole ne base URL na remote node ya kuma bi ƙa'idar URL na outbound na mai samarwa (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Lokacin da aka kunna OIDC, kashe shiga da kalmar sirri domin masu amfani su iya tantance kansu ta OIDC Single Sign-On kawai. Haka kuma ana karɓar alias ɗin tsirara `OIDC_DISABLE_PASSWORD_LOGIN`; Dashboard Feature Flag mai key iri ɗaya yana da fifiko. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias ɗin tsirara na `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Jerin Abubuwan Ƙarfafa Tsaro diff --git a/docs/i18n/he/CONTRIBUTING.md b/docs/i18n/he/CONTRIBUTING.md index 3c4eb7a4af71..c66af7f0a419 100644 --- a/docs/i18n/he/CONTRIBUTING.md +++ b/docs/i18n/he/CONTRIBUTING.md @@ -392,16 +392,16 @@ docs/ # Documentation - [ ] בדיקת הסגנון עוברת (`npm run lint`) - [ ] הבנייה מצליחה (`npm run build`) - [ ] נוספו טיפוסי TypeScript עבור פונקציות וממשקים ציבוריים חדשים -- [ ] אין סודות או ערכי ברירת מחדל המקודדים באופן קשיח -- [ ] פרטי גישה ציבוריים לשירותי upstream מוטמעים באמצעות `resolvePublicCred()` (ראו [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ולעולם לא כליטרלים -- [ ] תגובות שגיאה מנותבות דרך `buildErrorBody()` / `sanitizeErrorMessage()` — ללא עקבות מחסנית גולמיים בגופי התגובות (ראו [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] פקודות מעטפת (`exec` / `spawn`) מעבירות ערכי זמן ריצה באמצעות `env`, ולא באמצעות אינטרפולציית מחרוזות +- [ ] אין סודות או ערכי ברירת מחדל מקודדים בקוד +- [ ] פרטי גישה ציבוריים של upstream מוטמעים באמצעות `resolvePublicCred()` (ראו [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ולעולם לא כערכים מילוליים +- [ ] תגובות שגיאה עוברות דרך `buildErrorBody()` / `sanitizeErrorMessage()` — אין עקבות מחסנית גולמיים בגופי התגובות (ראו [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] פקודות מעטפת (`exec` / `spawn`) מעבירות ערכים בזמן ריצה באמצעות `env`, ולא באמצעות אינטרפולציית מחרוזות - [ ] כל הקלטים מאומתים באמצעות סכמות Zod -- [ ] נוסף **מקטע** יומן שינויים תחת `changelog.d/{features|fixes|maintenance}/-.md` עבור שינויים הנראים למשתמש (ראו [`changelog.d/README.md`](./changelog.d/README.md)) — **אין** לערוך את `CHANGELOG.md` ישירות; המקטעים מאוגדים בזמן ההפצה ולעולם אינם יוצרים התנגשויות בין בקשות משיכה +- [ ] נוסף **מקטע** יומן שינויים תחת `changelog.d/{features|fixes|maintenance}/-.md` עבור שינויים הנראים למשתמשים (ראו [`changelog.d/README.md`](./changelog.d/README.md)) — **אין** לערוך את `CHANGELOG.md` ישירות; המקטעים מאוגדים בעת ההפצה ולעולם אינם יוצרים התנגשויות בין בקשות משיכה - [ ] התיעוד עודכן (אם רלוונטי) -- [ ] לא נפתחו התראות CodeQL / Secret-Scanning חדשות, או שכל אחת מהן נדחתה בצירוף הצדקה טכנית המפנה למסמך הרלוונטי תחת `docs/security/` -- [ ] נתיבים שמפעילים תהליכי־בן (`/api/mcp/`, `/api/cli-tools/runtime/`) מסווגים כ־`isLocalOnlyPath()` בתוך `src/server/authz/routeGuard.ts` — ראו [כלל מחייב מס' 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] אין שורות סיום מסוג `Co-Authored-By` בהודעות commit — ה־commits חייבים להופיע אך ורק תחת זהות ה־Git של בעלי המאגר (כלל מחייב מס' 16) +- [ ] לא נפתחו התראות CodeQL / סריקת סודות חדשות, או שכל אחת מהן נדחתה בצירוף הצדקה טכנית המפנה למסמך הרלוונטי תחת `docs/security/` +- [ ] נתיבים שמפעילים תהליכי-בן (`/api/mcp/`, `/api/cli-tools/runtime/`) מסווגים כ-`isLocalOnlyPath()` בתוך `src/server/authz/routeGuard.ts` — ראו [כלל מחייב מס' 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] אין סיומות `Co-authored-by` של AI/בוט בהודעות commit (כלל מחייב מס' 16) — שותפים אנושיים שעבודתם נעשה שימוש חוזר מקבלים קרדיט באמצעות סיומות `Co-authored-by: Name ` תקניות --- diff --git a/docs/i18n/he/docs/architecture/QUALITY_GATES.md b/docs/i18n/he/docs/architecture/QUALITY_GATES.md index ab762e88c3e9..6e160cfd0fe4 100644 --- a/docs/i18n/he/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/he/docs/architecture/QUALITY_GATES.md @@ -15,193 +15,193 @@ --- -## מלאי שערים ופרופילי הרצה +## מלאי השערים ופרופילי ההרצה ### קבלת מועמדים -תהליכי העבודה של CI ושל שערי האיכות מפיקים כל אחד פסק דין יציב: `Gate / CI` ו־ -`Gate / Quality`. מדיניות הקבלה שלהם, המנוהלת לפי גרסאות, מונה כל משימת upstream -כנדרשת או כמייעצת. משימה נדרשת ורלוונטית חייבת להצליח: תוצאות חסרות, -מבוטלות, מדולגות, ממתינות או לא ידועות אינן יכולות לקבוע PASS. סיווג תקין +תהליכי העבודה של CI ושל שערי האיכות מפיקים כל אחד הכרעה יציבה: `Gate / CI` ו- +`Gate / Quality`. מדיניות הקבלה שלהם, שלה יש גרסאות, מונה כל משימה במעלה הזרם +כנדרשת או כמייעצת. משימה נדרשת ורלוונטית חייבת להסתיים בהצלחה: תוצאות חסרות, +מבוטלות, שדולגו, ממתינות או לא ידועות אינן יכולות לקבוע PASS. סיווג תקף של תיעוד בלבד או קטלוג בלבד יכול להפוך מסלול קוד ללא רלוונטי; -PR במצב טיוטה אינו מועמד קביל. תווית `hotfix` אינה פוטרת מהצגת ראיות. +טיוטת PR אינה מועמדת קבילה. תווית `hotfix` אינה פוטרת מהצגת ראיות. -שני תהליכי העבודה מכסים PR-ים ודחיפות לענפי main/release, הפעלה ידנית ואירועי -merge-group. דחיפה, הפעלה ו־merge-group מריצים את הבחירה המלאה. פיצולים -וקבוצות מיזוג משתמשים במריצים מתארחים עבור משימות שאחרת היו בוחרות מריצים -באירוח עצמי; יש לוודא קיבולת מתארחת מספקת לפני ההשקה. +שני תהליכי העבודה מכסים בקשות PR ודחיפות לענפי main/release, הפעלה ידנית ואירועי +merge-group. דחיפה, הפעלה ו-merge-group מריצים את הבחירה המלאה. מזלגות +וקבוצות מיזוג משתמשים ב-hosted runners עבור משימות שאחרת היו בוחרות ב-self-hosted +runners; יש לוודא קיבולת מספקת ב-hosted runners לפני הפריסה. -כל קבלת JSON מזהה את ה־SHA שנשלף, את הרצת תהליך העבודה ואת ניסיון ההרצה. -ה־CLI דוחה אי־התאמה בין SHA של השליפה לזה של האירוע. בדיקות תהליך העבודה קושרות את החברות במדיניות -לרשימת `needs` של משימת פסק הדין, כך שמסלול חדש או שהוסר לא יוכל להיעלם ללא התרעה. -הקבלות מכסות את תהליך העבודה שלהן עצמן, ולא פרסום, פריסה או את המנגנונים הפנימיים +כל קבלת JSON מזהה את ה-SHA שנשלף, את הרצת תהליך העבודה ואת ניסיון ההרצה. +ה-CLI דוחה אי-התאמה בין ה-SHA של השליפה לזה של האירוע. בדיקות תהליך העבודה קושרות את החברות במדיניות +לרשימת `needs` של משימת ההכרעה, כך שמסלול חדש או שהוסר לא יוכל להיעלם ללא התרעה. +הקבלות מכסות את תהליך העבודה שלהן בלבד, ולא פרסום, פריסה או את הרכיבים הפנימיים של סורק מייעץ קיים. הפעלת שני שמות הבדיקות בכללי הענף היא -שינוי מנהלי נפרד; הוספת המשימות האלה אינה מגינה כשלעצמה על ענף. +שינוי ניהולי נפרד; הוספת המשימות האלה אינה מגינה על ענף בפני עצמה. -### מלאי סריקות סטטיות +### מלאי הסריקות הסטטיות -מלאי כינויי npm המנוהל לפי גרסאות והחברות בסריקה הסטטית נמצאים ב־ +מלאי כינויי npm בעל הגרסאות והחברות בסריקה הסטטית נמצאים ב- `config/quality/gate-manifest.json`. הריצו `npm run check:gate-manifest` כדי לאמת -שמות סקריפטים ופקודות מדויקות מול `package.json`; הוספות, הסרות ושינויים -בפקודות מכשילים הן את ה־hook המקומי והן את משימות סיווג השינויים ב־CI. +שמות סקריפטים ופקודות מדויקות מול `package.json`; הוספות, הסרות +וסטייה בפקודות מכשילות הן את ה-hook המקומי והן את משימות סיווג השינויים ב-CI. כינוי אינו משימת תהליך עבודה, מופע מטריצה או מקרה בדיקה: אין -להציג ספירות אלה כאילו הן ניתנות להחלפה זו בזו. +להציג ספירות אלה כבנות-החלפה. -השתמשו ב־`npm run quality:scan -- --list` או ב־`npm run quality:scan:fast -- --list` -כדי לבחון את הכינויים שנבחרו מבלי להריץ אותם. המריץ מפעיל את -נקודת הכניסה של npm, ולכן סביבת זמן הריצה שלה (כולל Bun כאשר הוא מוגדר) נשמרת. -המניפסט מתעד כינויים שמחוץ לפרופילים האלה כמופעלים בנפרד, -ופקודות תחזוקה אסורות בפרופילי סריקה לקריאה בלבד. +השתמשו ב-`npm run quality:scan -- --list` או ב-`npm run quality:scan:fast -- --list` +כדי לבדוק את הכינויים שנבחרו מבלי להפעיל אותם. ה-runner מפעיל את +נקודת הכניסה של npm, ולכן סביבת הריצה שלה (כולל Bun, כאשר הוגדר) נשמרת. +המניפסט מתעד כינויים שמחוץ לפרופילים האלה כמופעלים בנפרד, ופקודות +תחזוקה אסורות בפרופילי סריקה לקריאה בלבד. -פרופילים אלה מכסים רק את הסריקה הסטטית. הם אינם מאשרים בדיקות מוצר, -כיסוי, אריזה, בדיקות חיצוניות או קבלת גרסה מלאה של מועמד. -קבלה בתהליך העבודה משתמשת ב־`config/quality/admission-policy.json` המקושר וב־ +פרופילים אלה מכסים את הסריקה הסטטית בלבד. הם אינם מאשרים בדיקות מוצר, +כיסוי, אריזה, בדיקות חיצוניות או קבלה מלאה של מועמד להפצה. +הקבלה בתהליך העבודה משתמשת ב-`config/quality/admission-policy.json` המקושר וב- `scripts/quality/admission-verdict.mjs`. פרופילי release-observer נשארים נפרדים; -יש לבחון באופן עצמאי את הבדיקות והקבלות הרלוונטיות שלהם. המלאי -המילולי שלהלן הוא חומר עזר, ולא הוכחה לכך ששער אכן הופעל. +יש לבדוק באופן עצמאי את הבדיקות והקבלות הרלוונטיות שלהם. המלאי +הטקסטואלי שלהלן הוא חומר עזר, ולא הוכחה לכך ששער אכן הופעל. -הסקריפטים נמצאים תחת `scripts/check/` (שערי מדיניות) ו־`scripts/quality/` (מנוע ratchet). +הסקריפטים נמצאים תחת `scripts/check/` (שערי מדיניות) ו-`scripts/quality/` (מנוע ההקשחה). מקור האמת של CI הוא `.github/workflows/ci.yml`. -### נתיב מהיר ל־PR של גרסה (`quality.yml`) +### הנתיב המהיר ל-PR של הפצה (`quality.yml`) -`.github/workflows/quality.yml` משלים את CI ב־PR-ים של main/release, בדחיפות לענפים -מוגנים, בהפעלה ובקבוצות מיזוג. PR-ים משתמשים בבדיקות מהירות המסוננות לפי נתיבים. בניית -הכפילות שהושבתה לצמיתות הוסרה; בדיקות הבנייה/האריזה/האתחול האמיתיות נשארות ב־CI. +`.github/workflows/quality.yml` משלים את CI בבקשות PR לענפי main/release, בדחיפות +לענפים מוגנים, בהפעלה ובקבוצות מיזוג. בקשות PR משתמשות בבדיקות מהירות המסוננות לפי נתיב. בניית +הכפילות שהושבתה לצמיתות הוסרה; בדיקות הבנייה/האריזה/האתחול האמיתיות נשארות ב-CI. -| משימה | תחום | חוסמת | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | PR-ים של תיעוד/קוד; הפניות לתיעוד API ו־docs-all | כן | -| `Fast Quality Gates` | PR-ים של קוד; בדיקות סטטיות, בדיקת טיפוסים, בדיקת טיפוסים של לוח המחוונים, בדיקות יחידה מושפעות | כן | -| `Forgotten sibling tests` | PR-ים של קוד; מעקב אחר מודולים ששונו עד לצרכנים סטטיים ולבדיקות sibling מועמדות; נתיבי barrel וייבוא דינמי מדווחים כאבחונים מייעצים, עם חריגים המופנים מרשימת ההיתרים | **מייעצת** | -| `Vitest (fast-path)` | PR-ים של קוד; חבילת בדיקות vitest מהירה | כן | -| `Unit Tests fast-path` | PR-ים של קוד; חבילת בדיקות יחידה המחולקת ל־4 shards | כן | -| `No new ESLint warnings` | PR-ים של קוד; שומר lint המתחשב בהשתקות | כן, כולל פיצולים | -| `Merge integrity (changelog + generated skills)` | PR-ים שאינם טיוטה; סנכרון changelog ומיומנויות שנוצרו | כן, כולל פיצולים | +| משימה | היקף | חוסמת | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `Docs Gates (fast-path)` | בקשות PR של תיעוד/קוד; הפניות לתיעוד API ו-docs-all | כן | +| `Fast Quality Gates` | בקשות PR של קוד; בדיקות סטטיות, typecheck,‏ typecheck של לוח המחוונים ובדיקות יחידה מושפעות | כן | +| `Forgotten sibling tests` | בקשות PR של קוד; מעקב ממודולים שהשתנו אל צרכנים סטטיים ואל בדיקות sibling מועמדות; נתיבי barrel ו-dynamic-import מדווחים כאבחונים מייעצים, עם חריגים מתוך allowlist שהוזכרו | **מייעצת** | +| `Vitest (fast-path)` | בקשות PR של קוד; חבילת vitest מהירה | כן | +| `Unit Tests fast-path` | בקשות PR של קוד; חבילת בדיקות יחידה המחולקת ל-4 shards | כן | +| `No new ESLint warnings` | בקשות PR של קוד; הגנת lint המודעת ל-suppressions | כן, כולל מזלגות | +| `Merge integrity (changelog + generated skills)` | בקשות PR שאינן טיוטה; סנכרון changelog ומיומנויות שנוצרו | כן, כולל מזלגות | #### דוח בדיקות sibling שנשכחו -`npm run check:forgotten-sibling-tests` עושה שימוש חוזר בפותר הייבואים שמאחורי מפת השפעת הבדיקות. -עבור כל מודול ייצור ששונה, הוא מדווח על שרשראות דטרמיניסטיות של -`changed module/symbol -> static consumer -> candidate sibling test` כאשר בדיקת המועמד -אינה קיימת בהבדלי בקשת המשיכה. תקציר ה־Markdown ותוצאת ה־JSON נשמרים כארטיפקט -`forgotten-sibling-tests` של תהליך העבודה לצורך כיול לפני כל השקה במצב חוסם. +`npm run check:forgotten-sibling-tests` עושה שימוש חוזר בפותר הייבוא שמאחורי מפת השפעת הבדיקות. +עבור כל מודול ייצור שהשתנה, הוא מדווח על שרשראות דטרמיניסטיות של +`changed module/symbol -> static consumer -> candidate sibling test` כאשר הבדיקה המועמדת +חסרה מה-diff של בקשת המשיכה. סיכום ה-Markdown ותוצאת ה-JSON נשמרים כארטיפקט +תהליך העבודה `forgotten-sibling-tests` לצורך כיול לפני פריסה חוסמת כלשהי. -ייצוא מחדש מסוג barrel וייבוא דינמי משמשים לאבחון רזולוציה בלבד; הם לעולם אינם יוצרים +ייצואים מחדש מסוג barrel וייבואים דינמיים משמשים לאבחון רזולוציה בלבד; הם לעולם אינם יוצרים ממצא חוסם. חריגים שנבדקו נמצאים ב- `config/quality/forgotten-sibling-allowlist.json`. כל רשומה חייבת לציין את בדיקת הצרכן ואת הבדיקה המועמדת, לספק נימוק ספציפי ולקשר לבעיה או לבקשת משיכה ב-GitHub. רשומות פגומות נכשלות -באופן סגור. חריגים אינם יכולים להסתיר בדיקה מועמדת שנמחקה או שינוי שמוסיף `.skip`/`.todo`; -החלשת טענות אימות ודרכי הסתרה אחרות נותרות באחריות שער +במצב סגור. חריגים אינם יכולים להסתיר בדיקה מועמדת שנמחקה או diff שמוסיף `.skip`/`.todo`; +החלשת assertions והסתרות אחרות נשארות באחריות שער `check:test-masking`, שחוסם באופן עצמאי. ### משימה: `lint` -מופעלת בכל PR אל `main`. כשל חוסם מיזוג. - -| סקריפט (`npm run ...`) | מה נבדק | חוסם | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -| `check:node-runtime` | גרסת Node.js נמצאת בטווח הנתמך | כן | -| `check:cycles` | ייבואים מעגליים — כל המודולים תחת `src/` + `open-sse/` | כן | -| `check:route-validation:t06` | סכמות Zod קיימות בכל הנתיבים (מדיניות שכבה 6) | כן | -| `check:any-budget:t11` | מספר מופעי `@ts-expect-error // any` אינו חורג מהתקציב (מחסום שכבה 11) | כן | -| `check:provider-consistency` | לכל ספק ב-`providers.ts` קיימת רשומה תואמת ב-`providerRegistry.ts` (ולהפך, במסגרת רשימת ההיתרים) | כן | -| `check:model-lifecycle` | שלוש טבלאות הניתוב המתוחזקות ידנית נשארות עקביות עם תמונת המצב של מחזור החיים שבמאגר (#11503): ‏`FITNESS_TABLE` ‏(`taskFitness.ts`) אינה מעניקה ציון למזהה שהוצא משימוש וש-`REGISTRY` יכולה לנתב; כל יעד של `BUILT_IN_ALIASES` קיים ב-`REGISTRY` ואינו מופיע בתמונת המצב של המזהים שהוצאו משימוש; כל מזהה שהוצא משימוש ועדיין נמצא ב-`REGISTRY` מועבר הלאה או רשום ב-`allowedRetiredInCatalog`; ואף מקור או יעד של `DEFAULT_DEGRADATION_MAP` אינו מופיע כמזהה שהוצא משימוש בתמונת מצב זו. בדיקה זו אינה מוכיחה שמודל מסופק כעת על ידי שירות מקור פעיל. לא מקוון — משווה מול `config/quality/model-lifecycle.json`, שמתעדכן ידנית באמצעות `npm run quality:refresh-model-lifecycle` (דורש רשת; אינו משולב ב-CI). ‏`allowedRetiredInCatalog` הוא מנגנון צמצום הדרגתי: יש להוסיף רשומה רק בצירוף סוגיית מעקב. | כן | -| `check:fetch-targets` | כל `fetch("/api/...")` בצד הלקוח תחת `src/` מופנה אל `route.ts` ממשי | כן | -| `check:deps` | כל התלויות שניתן להתקין באמצעות `npm install` בכל קובצי `package.json` במאגר נכללות ב-`dependency-allowlist.json`; חבילות חדשות ללא הצמדת גרסה או חבילות החשודות ב-slopsquatting מסומנות | כן | -| `audit:deps` | `npm audit` (שורש + electron) — אין התרעות בחומרה גבוהה/קריטית (חופף ל-`check:vuln-ratchet` של osv; ראו רשימת משימות לרציונליזציה) | כן | -| `check:lockfile` | תקינות `package-lock.json` — מאגר https, גיבובי תקינות, ללא דריסות מארח | כן | -| `check:licenses` | רשימת היתרים של רישיונות SPDX עבור תלויות ייצור | כן | -| `check:tracked-artifacts` | ללא תוצרי בנייה / קישורי `node_modules` שנשמרו במאגר (רץ גם ב־pre-commit של husky; ‏pre-push קל בכוונה — #6716) | כן | -| `check:ai-attribution` | ללא שורת סיום `Co-Authored-By` של AI/בוט או כיתוב המעיד על יצירה באמצעות AI ב־commits, בכותרת או בגוף של PR — כלל קשיח מס' 16 (בלולאת הבדיקות המהירות של `quality.yml` עבור PR→`release/**` — קורא את מטען האירוע ואינו עושה דבר מחוץ ל־PR — ובשלב המיועד ל־PR בלבד בבדיקת ה־lint של `ci.yml` עבור PR→`main`; גם ב־hook ‏`commit-msg` של husky; מחברים־שותפים אנושיים מותרים; #14436) | -| `check:vitest-exclusions` | כל החרגה של Vitest מציינת סוגיית מעקב ומופיעה ב־`config/quality/vitest-exclusions.json` (#13204) | כן | -| `check:file-size` | אף קובץ מקור אינו חורג מהמגבלה לפי סיומת (מנגנון החמרה הדרגתי: קבצים גדולים שהוקפאו ברשימת `frozen`) | כן | -| `check:error-helper` | תגובות שגיאה ב־executors/handlers משתמשות ב־`buildErrorBody()` / `sanitizeErrorMessage()` (כלל קשיח מס' 12) | כן | -| `check:migration-numbering` | קובצי ה-SQL של המיגרציות ממוספרים ברצף, ללא פערים או כפילויות | כן | -| `check:public-creds` | אין ערכי OAuth מילוליים של `client_id`/`client_secret` או מפתחות Firebase Web מחוץ ל-`publicCreds.ts` (כלל קשיח #11) | כן | -| `check:db-rules` | אין SQL גולמי מחוץ למודולים שב-`src/lib/db/`; אין ייבואי barrel מ-`localDb.ts` (כללים קשיחים #2/#5) | כן | -| `check:known-symbols` | מבצעי ספקים, אסטרטגיות ניתוב ומתרגמים הרשומים בטבלאות הניתוב שלהם תואמים לקבצים בדיסק — ללא סמלים יתומים או לא מוצהרים | כן | -| `check:route-guard-membership` | כל נתיב שמפעיל תהליך צאצא מסווג על ידי `isLocalOnlyPath()` (כללים קשיחים #15/#17) | כן | -| `check:test-discovery` | כל קובץ `*.test.ts` / `*.spec.ts` במאגר נאסף על ידי לפחות מריץ בדיקות אחד (מנגנון הידוק: רשימת היתומים ב-`test-discovery-baseline.json` יכולה רק להצטמצם) | כן | -| `check:agent-skills-sync` | ארטיפקטי agent-skills שנוצרו תואמים לקטלוג המקור שלהם (ללא סטייה) | -| `check:provider-asset-provenance` | לסמלי הלוגו/לנכסים של הספקים יש רשומת מקור מתועדת | -| `lint:json` | קובצי התצורה של JSON ניתנים לניתוח ועומדים בכללי ה-lint של המאגר | -| `typecheck:core` | הידור TypeScript ללא שגיאות (אזהרות מייעצות בלבד) | כן | -| `typecheck:noimplicit:core` | `noImplicitAny` מחמיר — כהיערכות לעתיד; אתרי קריאה קיימים רבים עדיין זקוקים להערות טיפוסים | **מייעץ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` שמוגבל ל-`src/app/(dashboard)/**` (#7033) — רשימת ההיתרים המצומצמת של `typecheck:core`, הכוללת 27 קבצים, אינה כוללת אף קובץ TSX של לוח המחוונים, וגם `next build` לעולם אינו בודק את הטיפוסים שלו (`next.config.mjs` מגדיר `ignoreBuildErrors: true`), ולכן רגרסיות של מזהים מיותמים שם (#6625/#6909) לא היו גלויות ל-CI. מתבצעת השוואה מול קו בסיס קפוא של ספירת שגיאות לפי קובץ ולפי קוד TS (`config/quality/dashboard-typecheck-baseline.json`, באותה תבנית אכיפת התיישנות כמו `check:known-symbols`) — רק שגיאות חדשות מעבר לספירה שבקו הבסיס מכשילות את השער; יש להדק את קו הבסיס באמצעות `--update` כאשר שגיאה קיימת מתוקנת. | כן | +פועלת בכל PR אל `main`. חוסמת מיזוג במקרה של כשל. + +| סקריפט (`npm run ...`) | מה מאומת | חוסם | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | +| `check:node-runtime` | גרסת Node.js נמצאת בטווח הנתמך | כן | +| `check:cycles` | ייבואים מעגליים בכל `src/` + `open-sse/` (מבוסס AST, עם רזולוציה של `paths` מתוך tsconfig). הרצה בסיסית = ייעוץ בלבד, ומציגה את המחזורים. `check:cycles:ratchet` (מה ש-CI מריץ) חוסם כאשר הכמות חורגת מהתקרה `metrics.cycles` שב-`quality-baseline.json` — נכון לעכשיו 14, עם `direction: down`, ולכן היא יכולה רק לרדת (#15159 G-01/G-02) | כן (ratchet) | +| `check:route-validation:t06` | סכמות Zod קיימות בכל הנתיבים (מדיניות Tier 6) | כן | +| `check:any-budget:t11` | מספר המופעים של `@ts-expect-error // any` אינו חורג מהתקציב (catraca של Tier 11) | כן | +| `check:provider-consistency` | לכל ספק ב-`providers.ts` קיימת רשומה תואמת ב-`providerRegistry.ts` (ולהפך, במסגרת רשימת ההיתרים) | כן | +| `check:model-lifecycle` | שלוש טבלאות הניתוב המתוחזקות ידנית נשארות עקביות עם תמונת מצב מחזור החיים שבמאגר (#11503): ‏`FITNESS_TABLE`‏ (`taskFitness.ts`) אינה מעניקה ציון למזהה שהוצא משימוש וש-`REGISTRY` יכולה לנתב אליו; כל יעד של `BUILT_IN_ALIASES` קיים ב-`REGISTRY` ואינו מופיע בתמונת המצב של המזהים שהוצאו משימוש; כל מזהה שהוצא משימוש ועדיין נמצא ב-`REGISTRY` מועבר הלאה או רשום ב-`allowedRetiredInCatalog`; ואף מקור או יעד של `DEFAULT_DEGRADATION_MAP` אינו מופיע בתמונת מצב זו כמזהה שהוצא משימוש. בדיקה זו אינה מוכיחה שמודל מסופק כעת על ידי שירות upstream פעיל. לא מקוון — מתבצעת השוואה מול `config/quality/model-lifecycle.json`, המרוענן ידנית באמצעות `npm run quality:refresh-model-lifecycle` (דורש רשת; אינו משולב ב-CI). ‏`allowedRetiredInCatalog` הוא מנגנון מחגר לצמצום הדרגתי: יש להוסיף רשומה רק בצירוף issue למעקב. | כן | +| `check:fetch-targets` | כל `fetch("/api/...")` בצד הלקוח תחת `src/` מפנה לקובץ `route.ts` קיים | כן | +| `check:deps` | כל יחסי התלות שניתן להתקין באמצעות `npm install`, בכל קובץ `package.json` במאגר, נמצאים ב-`dependency-allowlist.json`; חבילות חדשות שאינן מקובעות לגרסה או חשודות ב-slopsquatting מסומנות | כן | +| `audit:deps` | `npm audit` (שורש + Electron) — ללא התראות בדרגת חומרה גבוהה/קריטית (חופף ל-`check:vuln-ratchet` של OSV; ראו צבר הרציונליזציה) | כן | +| `check:lockfile` | תקינות `package-lock.json` — רישום HTTPS, גיבובי תקינות, ללא דריסות מארח | כן | +| `check:licenses` | רשימת היתרים של רישיונות SPDX עבור תלויות ייצור | כן | +| `check:tracked-artifacts` | ללא תוצרי בנייה / קישורי `node_modules` שנוספו למעקב (רץ גם ב־pre-commit של husky; ‏pre-push קל במכוון — #6716) | כן | +| `check:ai-attribution` | ללא שורת סיום `Co-Authored-By` של AI/בוט או כותרת תחתונה המעידה על יצירה באמצעות AI ב־commits, בכותרת או בגוף של PR — כלל מחייב #16 (בלולאת הבדיקות המהירות של `quality.yml` עבור PR→`release/**` — קורא את מטען האירוע ואינו מבצע פעולה מחוץ ל־PRs — ובשלב המיועד ל־PR בלבד בבדיקת ה־lint של `ci.yml` עבור PR→`main`; גם ב־hook‏ `commit-msg` של husky; מחברים־שותפים אנושיים מותרים; #14436) | +| `check:vitest-exclusions` | כל החרגה של Vitest מציינת issue למעקב ומופיעה ב־`config/quality/vitest-exclusions.json` (#13204) | כן | +| `check:file-size` | אף קובץ מקור אינו חורג מהמכסה לכל סיומת (מנגנון ratchet: קבצים גדולים שהוקפאו ברשימת `frozen`) | כן | +| `check:error-helper` | תגובות שגיאה ב־executors/handlers משתמשות ב־`buildErrorBody()` / `sanitizeErrorMessage()` (כלל מחייב #12) | כן | +| `check:migration-numbering` | קובצי Migration SQL ממוספרים ברצף, ללא פערים או כפילויות | כן | +| `check:public-creds` | אין ערכי OAuth מילוליים של `client_id`/`client_secret` או מפתחות Firebase Web מחוץ ל-`publicCreds.ts` (כלל קשיח #11) | כן | +| `check:db-rules` | אין SQL גולמי מחוץ למודולים שב-`src/lib/db/`; אין ייבואי barrel מ-`localDb.ts` (כללים קשיחים #2/#5) | כן | +| `check:known-symbols` | רכיבי ההרצה של הספקים, אסטרטגיות הניתוב והמתרגמים הרשומים בטבלאות הניתוב שלהם תואמים לקבצים שבדיסק — ללא סמלים יתומים או לא מוצהרים | כן | +| `check:route-guard-membership` | כל נתיב שמפעיל תהליך בן מסווג באמצעות `isLocalOnlyPath()` (כללים קשיחים #15/#17) | כן | +| `check:test-discovery` | כל קובץ `*.test.ts` / `*.spec.ts` במאגר נאסף על ידי לפחות מריץ בדיקות אחד (ratchet: רשימת הקבצים היתומים ב-`test-discovery-baseline.json` יכולה רק להצטמצם) | כן | +| `check:agent-skills-sync` | ארטיפקטים שנוצרו עבור agent-skills תואמים לקטלוג המקור שלהם (ללא סטייה) | +| `check:provider-asset-provenance` | סמלי לוגו/נכסים של ספקים כוללים רשומת מקור מתועדת | +| `lint:json` | קובצי תצורה מסוג JSON עוברים ניתוח תחבירי ועומדים בכללי ה-lint של המאגר | +| `typecheck:core` | הידור TypeScript ללא שגיאות (אזהרות מייעצות בלבד) | כן | +| `typecheck:noimplicit:core` | `noImplicitAny` מחמיר — מיועד להמשך הדרך; אתרי קריאה קיימים רבים עדיין זקוקים לאנוטציות | **מייעץ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` המוגבל ל-`src/app/(dashboard)/**` (#7033) — רשימת ההיתרים המצומצמת של `typecheck:core`, הכוללת 27 קבצים, אינה כוללת אף קובץ TSX של לוח המחוונים, וגם `next build` לעולם אינו מבצע עבורו בדיקת טיפוסים (`next.config.mjs` מגדיר `ignoreBuildErrors: true`), ולכן רגרסיות של מזהים יתומים שם (#6625/#6909) לא היו גלויות ל-CI. מתבצעת השוואה מול קו בסיס קפוא של ספירת שגיאות לכל קובץ ולכל קוד TS (`config/quality/dashboard-typecheck-baseline.json`, באותה תבנית אכיפה נגד התיישנות כמו `check:known-symbols`) — רק שגיאות חדשות מעבר לספירה שבקו הבסיס גורמות לכשל בשער; יש להדק את קו הבסיס באמצעות `--update` כאשר שגיאה קיימת מתוקנת. | כן | ### משימה: `quality-gate` -פועלת לאחר `test-coverage`. חוסמת מיזוג במקרה של כשל. +רצה לאחר `test-coverage`. חוסמת מיזוג במקרה של כשל. -| סקריפט | מה הוא מאמת | חוסם | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | -| `quality:collect` | מפיק את `quality-metrics.json` (מספר אזהרות ESLint, כיסוי מהדוח הממוזג של ה-shards) | כן (לפני ה-ratchet) | -| `quality:ratchet` | אף מדד ב-`quality-baseline.json` לא נסוג (אזהרות ESLint ≤ קו הבסיס; כיסוי ≥ קו הבסיס) | כן | -| `check:duplication` | שכפול קוד (jscpd@4) אינו חורג מקו הבסיס שב-`quality-baseline.json` | כן | -| `check:complexity` | המורכבות הציקלומטית ברמת הקובץ אינה חורגת מהתקרה (`complexity` של ליבת ESLint + `max-lines-per-function`) | כן | -| `check:cognitive-complexity` | Ratchet למורכבות קוגניטיבית (`eslint-plugin-sonarjs`) — מעבר ESLint נפרד; CI מריץ את שניהם במיזוג כשלב היחיד `check:complexity-ratchets` | כן | -| `check:dead-code` | Ratchet לייצואים / קבצים שאינם בשימוש (knip) אינו נסוג לעומת קו הבסיס | כן | -| `check:compression-budget` | תקציב benchmark לדחיסה — ערכי הסף המינימליים לחיסכון בטוקנים לכל מנוע אינם רשאים לסגת | כן | -| `check:type-coverage` | Ratchet לאחוז הקוד המוקלד (`type-coverage`) אינו נסוג; מחליף במידה רבה את `typecheck:noimplicit:core` | כן | -| `check:codeql-ratchet` | מספר התראות CodeQL הפתוחות אינו נסוג (נקרא באמצעות `gh api`; דילוג תקין ללא אסימון) — לתדירות הרענון ולהפעלה ידנית: ראו "Ratchet של CodeQL" להלן | כן | +| סקריפט | מאמת | חוסם | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `quality:collect` | מפיק את `quality-metrics.json` (מספר אזהרות ESLint, כיסוי מדוח הרסיסים הממוזג) | כן (לפני מנגנון הסף) | +| `quality:ratchet` | אף מדד ב-`quality-baseline.json` לא נסוג (אזהרות ESLint ≤ קו הבסיס; כיסוי ≥ קו הבסיס) | כן | +| `check:duplication` | שכפול קוד (jscpd@4) אינו חורג מקו הבסיס שב-`quality-baseline.json` | כן | +| `check:complexity` | המורכבות הציקלומטית ברמת הקובץ אינה חורגת מהתקרה (כללי הליבה של ESLint‏ `complexity` + `max-lines-per-function`) | כן | +| `check:cognitive-complexity` | מנגנון סף למורכבות קוגניטיבית (`eslint-plugin-sonarjs`) — מעבר ESLint נפרד; CI מריץ את שניהם במאוחד כשלב היחיד `check:complexity-ratchets` | כן | +| `check:dead-code` | מנגנון הסף לייצואים / קבצים שאינם בשימוש (knip) אינו נסוג ביחס לקו הבסיס | כן | +| `check:compression-budget` | תקציב מדד ביצועי הדחיסה — ערכי הסף המזעריים לחיסכון באסימונים עבור כל מנוע אינם יכולים לסגת | כן | +| `check:type-coverage` | מנגנון הסף לאחוז הקוד המוקלד (`type-coverage`) אינו נסוג; מחליף במידה רבה את `typecheck:noimplicit:core` | כן | +| `check:codeql-ratchet` | מספר התראות CodeQL הפתוחות אינו נסוג (נקרא באמצעות `gh api`; דילוג תקין ללא אסימון) — לתדירות הרענון ולהפעלה ידנית: ראו "מנגנון הסף של CodeQL" להלן | כן | ### משימה: `quality-extended` -המשימה כולה היא בגדר המלצה (`continue-on-error: true`). ה-ratchets המבוססים על npm אכן -רצים; הסורקים החיצוניים מותקנים באמצעות `gh release download` ומדלגים על עצמם (exit 0) +המשימה כולה היא בגדר המלצה (`continue-on-error: true`). מנגנוני הסף מבוססי npm מופעלים +בפועל; הסורקים החיצוניים מותקנים באמצעות `gh release download` ומדלגים בעצמם (יציאה עם קוד 0) כאשר קובץ בינארי עדיין חסר. -| סקריפט | מה הוא מאמת | חוסם | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | -| `check:circular-deps` | אין תלויות מעגליות (dpdm) | **בגדר המלצה** | -| `check:bundle-size` | גודל החבילה אינו חורג מהתקרה | **בגדר המלצה** | -| `check:secrets` | סריקת סודות (gitleaks) — מדלג אם הקובץ הבינארי חסר | **בגדר המלצה** | -| `check:vuln-ratchet` | פגיעויות בתלויות (osv-scanner) אינן נסוגות — מדלג אם הקובץ הבינארי חסר | **בגדר המלצה** | -| `check:workflows` | בדיקת תקינות workflows‏ (actionlint + zizmor); סורקים חסרים/תקולים, דוחות לא תקינים או קו בסיס חסר ל-ratchet גורמים לכשל מסוג INCOMPLETE. ממצאים תקינים פועלים לפי מדיניות strict/advisory/ratchet שנבחרה | נדרשת הרצה; ה-ratchet של zizmor חוסם ב-CI | -| `check:openapi-breaking` | שינויים שוברים בחוזה ה-API הציבורי (`openapi.yaml`) לעומת ענף הבסיס (oasdiff) — מפיק `openapiBreaking=N`; מדלג אם oasdiff חסר או אם לא ניתן לפענח את מפרט הבסיס | **בגדר המלצה** | +| סקריפט | מאמת | חוסם | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:circular-deps` | אין תלויות מעגליות (dpdm) | **בגדר המלצה** | +| `check:bundle-size` | גודל החבילה אינו חורג מהתקרה | **בגדר המלצה** | +| `check:secrets` | סריקת סודות (gitleaks) — מדלג אם הקובץ הבינארי חסר | **בגדר המלצה** | +| `check:vuln-ratchet` | חולשות בתלויות (osv-scanner) אינן נסוגות — מדלג אם הקובץ הבינארי חסר | **בגדר המלצה** | +| `check:workflows` | בדיקת תקינות של תהליכי עבודה (actionlint + zizmor); סורקים חסרים/תקולים, דוחות לא תקינים או קו בסיס חסר למנגנון הסף גורמים לכשל במצב INCOMPLETE. ממצאים תקפים מטופלים בהתאם למדיניות המחמירה/המייעצת/מבוססת-הסף שנבחרה | נדרשת הפעלה; מנגנון הסף של zizmor חוסם ב-CI | +| `check:openapi-breaking` | שינויים שוברים בחוזה ה-API הציבורי (`openapi.yaml`) ביחס לענף הבסיס (oasdiff) — מפיק `openapiBreaking=N`; מדלג אם oasdiff חסר או שלא ניתן לפענח את מפרט הבסיס | **בגדר המלצה** | ### משימה: `docs-sync-strict` -רצה בכל PR אל `main`. חוסמת מיזוג במקרה של כשל. - -| סקריפט | מה הוא מאמת | חוסם | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `check:docs-all` | שער־על שמריץ ברצף את 6 שערי המשנה שלהלן | כן | -| ↳ `check:docs-sync` | עקביות גרסאות בין CHANGELOG / OpenAPI / llm.txt | כן | -| ↳ `check:docs-counts` | ספירות בטקסט (מספר ספקים, מספר מיגרציות וכו') נמצאות בתוך חלון ההידוק של הספירות בפועל | כן | -| ↳ `check:env-doc-sync` | כל משתנה סביבה ב־`.env.example` מתועד בטבלת תיעוד, ולהפך | כן | -| ↳ `check:deprecated-versions` | אין מחרוזות גרסה שהוצאו משימוש בתיעוד | כן | -| ↳ `check:doc-links` | קישורי Markdown פנימיים בתיעוד מפנים לקבצים קיימים (בתבנית `[text]`/`(path)`) | כן | -| ↳ `check:fabricated-docs` | נתיבים, משתני סביבה, פקודות CLI, שמות hooks ונתיבי קבצים המוזכרים בתיעוד קיימים בבסיס הקוד. שער קשיח באמצעות `--strict`; כשל רך ללא הדגל. | כן (באמצעות `--strict` ב־CI) | -| `check:cli-i18n` | מחרוזות פקודות CLI קיימות בכל קובצי הלוקאל של i18n | כן | -| `check:openapi-coverage` | מפרט OpenAPI מכסה לפחות סף מהודק של הנתיבים הקיימים בפועל | כן | -| `check:openapi-security-tiers` | הערות רמות האבטחה ב־`openapi.yaml` עקביות עם הסיווגים ב־`routeGuard.ts` | **מייעץ** | -| `check:openapi-routes` | כל נתיב ב־`openapi.yaml` מפנה ל־`route.ts` אמיתי (מניעת הזיות) | כן | -| `check:docs-symbols` | כל הפניית `/api/...` ב־`docs/**/*.md` מפנה ל־`route.ts` אמיתי (מניעת הזיות) | כן | -| `i18n translation drift` | מפתחות שלא תורגמו בקובצי הלוקאל של i18n — אזהרה בלבד | **מייעץ** | +מופעלת בכל PR אל `main`. חוסמת מיזוג במקרה של כשל. + +| סקריפט | מה הוא מאמת | חוסם | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `check:docs-all` | שער־על שמריץ ברצף את 6 שערי־המשנה שלהלן | כן | +| ↳ `check:docs-sync` | עקביות הגרסאות בין CHANGELOG / OpenAPI / llm.txt | כן | +| ↳ `check:docs-counts` | הספירות בטקסט (מספר ספקים, מספר מיגרציות וכו׳) נמצאות בתוך חלון ההידוק של הספירות בפועל | כן | +| ↳ `check:env-doc-sync` | כל משתנה סביבה ב־`.env.example` מתועד בטבלת תיעוד, ולהפך | כן | +| ↳ `check:deprecated-versions` | אין בתיעוד מחרוזות של גרסאות שהוצאו משימוש | כן | +| ↳ `check:doc-links` | קישורי Markdown פנימיים בתיעוד מפנים לקבצים קיימים (בתבנית `[text]`/`(path)`) | כן | +| ↳ `check:fabricated-docs` | הנתיבים, משתני הסביבה, פקודות ה־CLI, שמות ה־hooks ונתיבי הקבצים המוזכרים בתיעוד קיימים בבסיס הקוד. שער קשיח עם `--strict`; כשל רך ללא הדגל. | כן (באמצעות `--strict` ב־CI) | +| `check:cli-i18n` | מחרוזות פקודות ה־CLI קיימות בכל קובצי אזורי ה־i18n | כן | +| `check:openapi-coverage` | מפרט OpenAPI מכסה לפחות סף תחתון מהודק של הנתיבים בפועל | כן | +| `check:openapi-security-tiers` | הערות רמות האבטחה ב־`openapi.yaml` תואמות לסיווגים ב־`routeGuard.ts` | **מייעץ** | +| `check:openapi-routes` | כל נתיב ב־`openapi.yaml` מפנה ל־`route.ts` אמיתי (מניעת הזיות) | כן | +| `check:docs-symbols` | כל הפניה מסוג `/api/...` בתוך `docs/**/*.md` מפנה ל־`route.ts` אמיתי (מניעת הזיות) | כן | +| `i18n translation drift` | מפתחות שלא תורגמו בקובצי אזורי ה־i18n — אזהרה בלבד | **מייעץ** | ### משימה: `i18n-ui-coverage` | סקריפט | מה הוא מאמת | חוסם | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | -| `check-ui-keys-coverage` (מוטבע) | כיסוי מפתחות i18n בממשק המשתמש הוא ≥ 65% | כן | -| `check-ui-value-drift` (מוטבע) | לאחר שכתוב **ערך** באנגלית לא נשאר מאחור תרגום מיושן | כן | -| `check-new-key-coverage` (מוטבע) | מפתח אנגלי **חדש** מתורגם בכל לוקאל — הסמן `__MISSING__:` נדחה | כן | -| `check-translation-ratio` | יחס התרגומים האמיתיים בכל לוקאל (ערכים זהים לאנגלית / מצייני מקום / ערכים חסרים שאינם ברשימת ההחרגות) אינו יכול לחרוג מ־`config/quality/i18n-translation-baseline.json` בתוספת מרווח | **מייעץ** | +| `check-ui-keys-coverage` (מוטמע) | כיסוי מפתחות ה־i18n של ממשק המשתמש הוא ≥ 65% | כן | +| `check-ui-value-drift` (מוטמע) | שינוי של **ערך** באנגלית אינו משאיר אחריו תרגום מיושן | כן | +| `check-new-key-coverage` (מוטמע) | מפתח אנגלי **חדש** מתורגם בכל אזור — הסמן `__MISSING__:` נדחה | כן | +| `check-translation-ratio` | יחס התרגומים האמיתיים בכל אזור (ערכים הזהים לאנגלית / מצייני מקום / ערכים חסרים שאינם ברשימת ההחרגות) אינו יכול לחרוג מ־`config/quality/i18n-translation-baseline.json` בתוספת מרווח | **מייעץ** | נדרש `fetch-depth: 0` — שער סטיית הערכים משווה את `en.json` מול בסיס המיזוג. #### `check-ui-value-drift` — שער תרגומים מיושנים -מזהה את רגרסיית ה־i18n היחידה שהשערים האחרים אינם יכולים לזהות מבחינה מבנית: ערך באנגלית -משוכתב, והתרגומים שנגזרו מהנוסח האנגלי _הקודם_ נשארים מאחור, כך -שמשתמשים שאינם דוברי אנגלית ממשיכים לקרוא נוסח החלטי שכעת הוא שגוי. +מאתר את נסיגת ה־i18n היחידה ששאר השערים אינם יכולים לזהות מבחינה מבנית: ערך באנגלית +נכתב מחדש, אך התרגומים שנגזרו מהאנגלית _הקודמת_ נשארים במקומם, ולכן +משתמשים שאינם דוברי אנגלית ממשיכים לקרוא נוסח נחרץ שכעת הוא שגוי. -התקלה הזו אכן הגיעה לגרסת הפצה. `oauthModal.googleOAuthWarning` שוכתב כאשר כלי העזר להתחברות של Antigravity -נוסף (#5203); ב־**39 מתוך 43 לוקאלים** נשאר טקסט שהורה למפעילים "להעתיק את -כתובת ה־URL המלאה ולהדביק אותה למטה" — תהליך שלא ניתן להשלים עבור ספק זה. הבעיה -לא התגלתה עד #8463, משום ש: +התקלה הזו אכן הגיעה לגרסה שפורסמה. `oauthModal.googleOAuthWarning` נכתב מחדש כאשר מסייע +ההתחברות של Antigravity נוסף (#5203); ב־**39 מתוך 43 אזורים** נשאר טקסט שהורה למפעילים +״להעתיק את כתובת ה־URL המלאה ולהדביק אותה למטה״ — תהליך שלא ניתן להשלים עבור ספק זה. הדבר +לא התגלה עד #8463, משום ש: - `sync-ui-keys` משלים רק מפתחות ש**חסרים**, ולעולם לא מפתחות ש**התיישנו**; - `check-ui-keys-coverage` סופר את _נוכחות_ המפתח, ולכן תרגום מיושן נחשב כמכוסה; @@ -209,24 +209,24 @@ merge-group. דחיפה, הפעלה ו־merge-group מריצים את הבחיר הוא לעולם אינו קורא את `src/i18n/messages/*.json`. חוסם במשימה `docs-sync-strict` מאז הסנכרון מחדש של 2026-09: עריכת מסמך ליבה → `npm run i18n:run -- --files=` (ברמת המקטע, זול). -**מודע להבדלים, ללא הסתמכות על קו בסיס.** הוא משווה את `en.json` בבסיס המיזוג מול -עץ העבודה; עבור כל מפתח שהערך שלו באנגלית השתנה, כל שפה שעדיין מכילה תרגום -שלא עודכן נחשבת מיושנת. הדבר **מקפיא במכוון חוב קיים** — הבדל אינו יכול לחשוף מאיזו -גרסה ישנה באנגלית הגיע תרגום ותיק, ולכן שער הבדיקה בוחן רק את מה שהשינוי הנוכחי -נוגע בו. החלופה (קו בסיס של גיבוב לכל מפתח) הייתה מוסיפה קובץ שנוצר אוטומטית בגודל -של כ־600 KB, פי 3 מקו הבסיס הקיים הגדול ביותר, ומשתנה בכל PR של i18n. +**מודע להבדלים, ללא הסתמכות על קו בסיס.** הוא משווה את `en.json` בנקודת המיזוג המשותפת לעץ +העבודה; עבור כל מפתח שהערך שלו באנגלית השתנה, כל אזור שפה שעדיין מחזיק בתרגום +שלא עודכן נחשב מיושן. כך **מוקפא במכוון חוב קיים** — הבדל אינו יכול לחשוף מאיזו +גרסה ישנה באנגלית הגיע תרגום ותיק, ולכן שער הבדיקה בוחן רק את מה שהשינוי הנוכחי נוגע +בו. החלופה (קו בסיס של גיבוב לכל מפתח) הייתה כרוכה בקובץ שנוצר אוטומטית בגודל של כ־600 KB, +פי 3 מקו הבסיס הקיים הגדול ביותר, והייתה משתנה בכל PR של i18n. יש שתי דרכים לעמוד בדרישה: 1. לעדכן את התרגומים המושפעים, או -2. להגדיר אותם כ־`__MISSING__:` — בזמן הריצה תוצג אז הגרסה המתוקנת באנגלית - (`src/i18n/request.ts::deepMergeFallback`, #7258), והמפתח ייכנס לתור לתרגום. +2. להגדיר אותם כ־`__MISSING__:` — לאחר מכן, בזמן ריצה, מוגש הטקסט המתוקן באנגלית + (`src/i18n/request.ts::deepMergeFallback`, #7258), והמפתח נכנס לתור לתרגום. -אם **משמעות** המחרוזת השתנתה, עדיף **לשנות את שם המפתח**: מפתח חדש אינו יכול לרשת +אם **המשמעות** של המחרוזת השתנתה, עדיף **לשנות את שם המפתח**: מפתח חדש אינו יכול לרשת תרגום מיושן. זהו הדפוס שבו נעשה שימוש ב־#8463. ```bash -npm run i18n:check-value-drift # מצב מחמיר (מה שמופעל ב-CI) +npm run i18n:check-value-drift # מחמיר (מה שמופעל ב־CI) npm run i18n:check-value-drift:warn # דיווח בלבד BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` @@ -236,43 +236,43 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ### משימה: `i18n` -מטריצת אימות i18n מלאה (משימה אחת לכל שפה). המשימה כולה היא בגדר המלצה. +מטריצת אימות i18n מלאה (משימה אחת לכל אזור שפה). המשימה כולה היא בגדר המלצה. -| סקריפט | מה מאומת | חוסם | -| ------------------------------- | -------------------- | --------------------------------------------------------- | -| `validate_translation.py quick` | שלמות התרגום בכל שפה | **בגדר המלצה** (`continue-on-error: true` על המשימה כולה) | +| סקריפט | מה מאומת | חוסם | +| ------------------------------- | ------------------------- | --------------------------------------------------------- | +| `validate_translation.py quick` | שלמות התרגום לכל אזור שפה | **בגדר המלצה** (`continue-on-error: true` על המשימה כולה) | ### משימה: `pr-test-policy` -מופעלת בבקשות משיכה בלבד. +פועלת בבקשות משיכה בלבד. | סקריפט | מה מאומת | חוסם | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---- | -| `check:pr-test-policy` | בקשות משיכה שמשנות קוד ייצור ב־`src/`, ב־`open-sse/`, ב־`electron/` או ב־`bin/` חייבות לכלול או לעדכן בדיקות (כלל קשיח #8) | כן | -| `check:test-masking` | קובצי בדיקה שהשתנו אינם מפחיתים את המספר הכולל נטו של טענות אימות ואינם מוסיפים טאוטולוגיות מסוג `assert.ok(true)` | כן | -| `check:pr-evidence` | גוף בקשת המשיכה מציין ראיות מבדיקות/VPS עבור השינוי (ממכן את כלל קשיח #18 באמצעות חיפוש בטקסט של בקשת המשיכה — שברירי, ראו Backlog) | כן | +| `check:pr-test-policy` | בקשות משיכה שמשנות קוד ייצור ב־`src/`, ב־`open-sse/`, ב־`electron/` או ב־`bin/` חייבות לכלול בדיקות או לעדכן אותן (כלל מחייב מס' 8) | כן | +| `check:test-masking` | קובצי בדיקות שהשתנו אינם מפחיתים את המספר הכולל נטו של טענות אימות ואינם מוסיפים טאוטולוגיות מסוג `assert.ok(true)` | כן | +| `check:pr-evidence` | גוף ה־PR מציין ראיות מבדיקות/VPS עבור השינוי (ממכן את כלל מחייב מס' 18 באמצעות חיפוש בטקסט ה־PR — שברירי, ראו רשימת משימות) | כן | ### משימה: `test-vitest` -מופעלת לאחר `build`. כישלון חוסם את המיזוג. +פועלת לאחר `build`. כשל חוסם מיזוג. | חבילת בדיקות | מה מאומת | חוסם | | ---------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| `test:vitest` | שרת MCP‏ (110 כלים), autoCombo, מטמון — מריץ vitest | כן | +| `test:vitest` | שרת MCP ‏(110 כלים), autoCombo, מטמון — מריץ vitest | כן | | `test:vitest:ui` | בדיקות רכיבי ממשק משתמש — מריץ vitest | **חוסם** — כשלים קיימים מוחרגים במפורש ב־`vitest.config.ts`; כשלים חדשים מכשילים את המשימה | ### תהליכי עבודה ליליים (מתוזמנים, בגדר המלצה) -אלה מופעלים לפי לוח זמנים של cron (וכן באמצעות `workflow_dispatch`), ולעולם לא בבקשות משיכה. כולם בגדר המלצה. +אלה פועלים לפי לוח זמנים של cron (וכן באמצעות `workflow_dispatch`), ולעולם לא בבקשות משיכה. כולם בגדר המלצה. -| תהליך עבודה | מה מאומת | חוסם | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | בדיקות מבוססות־תכונות של fast-check עם seed אקראי ומספר הרצות גבוה | **בגדר המלצה** | -| `nightly-resilience` | שער גידול ערימה, הזרקת תקלות כאוס, בדיקות עומס/מאמץ ממושך באמצעות k6 | **בגדר המלצה** | -| `nightly-llm-security` | הגנה מפני הזרקות של promptfoo (מצב חסימה) + בדיקות garak (מדולגות ללא סוד של ספק) | **בגדר המלצה** | -| `nightly-schemathesis` | בדיקות fuzzing של חוזה OpenAPI‏ (schemathesis) מול OmniRoute פעיל באמצעות `docs/openapi.yaml` — חושפות הפרות מפרט / שגיאות 500 שלא טופלו (שלב 8 B.4) | **בגדר המלצה** | -| `nightly-mutation` | ציון בדיקות מוטציה של Stryker במסלול בדיקות היחידה המהיר — מוטנטים ששרדו חושפים טענות אימות חלשות | **בגדר המלצה** | -| `nightly-compat` | מטריצת תאימות למנוע Node בטווחי `engines.node` הנתמכים | **בגדר המלצה** | +| תהליך עבודה | מה מאומת | חוסם | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | בדיקות מבוססות־מאפיינים של fast-check עם seed אקראי ומספר הרצות גבוה | **בגדר המלצה** | +| `nightly-resilience` | שער גידול ערימה, הזרקת תקלות כאוס, בדיקות עומס/שהייה ממושכת של k6 | **בגדר המלצה** | +| `nightly-llm-security` | הגנה מפני הזרקה של promptfoo (מצב חסימה) + בדיקות garak (מדולגות ללא סוד של ספק) | **בגדר המלצה** | +| `nightly-schemathesis` | בדיקות fuzzing לחוזה OpenAPI ‏(schemathesis) מול OmniRoute פעיל באמצעות `docs/openapi.yaml` — חושפות הפרות מפרט / שגיאות 500 שלא טופלו (שלב 8 B.4) | **בגדר המלצה** | +| `nightly-mutation` | ציון בדיקות מוטציה של Stryker עבור מסלול בדיקות היחידה המהיר — מוטנטים ששורדים חושפים טענות אימות חלשות | **בגדר המלצה** | +| `nightly-compat` | מטריצת תאימות למנוע Node בכל טווחי `engines.node` הנתמכים | **בגדר המלצה** | --- @@ -535,43 +535,43 @@ bot שיכול להעלות תקרה יהיה גרוע משמעותית מהמצ --- -## צבר משימות לרציונליזציה (בחינת ROI — שלב 9 גל 3) +## צבר משימות לרציונליזציה (סקירת ROI — שלב 9 גל 3) -מלאי זה הותאם מול `ci.yml` בתאריך 2026-06-17 (הגרסה הקודמת השמיטה את -`audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, -`check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). בחינת ROI של הקבוצה המותאמת -זיהתה את המועמדים הבאים לרציונליזציה. **המיזוגים הם שינויים מכניים ב-CI; -ההקשחות/ההסרות הן החלטות מדיניות השמורות למפעיל.** דבר מהאמור להלן -טרם הוחל. +מצאי זה הותאם מול `ci.yml` ב-2026-06-17 (הגרסה הקודמת השמיטה את +`audit:deps`,‏ `check:tracked-artifacts`,‏ `check:lockfile`,‏ `check:licenses`, +`check:dead-code`,‏ `check:cognitive-complexity`,‏ `check:type-coverage`, +`check:codeql-ratchet`,‏ `check:pr-evidence`). סקירת ROI של הקבוצה המותאמת +זיהתה את המועמדים הבאים לרציונליזציה. **המיזוגים הם שינויי CI מכניים; +השינויים/ההסרות הם החלטות מדיניות השמורות למפעיל.** דבר מהמפורט להלן +טרם יושם. -**פריטים נוספים שלא תועדו לעיל** (מייעצים, אות חלש): משימת `docs-lint` -(markdownlint + Vale, כל המשימה עם `continue-on-error`) ותהליכי העבודה העצמאיים של הסורקים +**גם אלה אינם מתועדים לעיל** (מייעצים, אות חלש): משימת `docs-lint` +(markdownlint + Vale, כאשר `continue-on-error` חל על המשימה כולה) ותהליכי העבודה העצמאיים של הסורקים `semgrep.yml` / `codeql.yml` / `scorecard.yml`. הערך `semgrepFindings: 0` נמצא -ב-`quality-baseline.json`, אך אינו מחובר למחגר חוסם ב-`ci.yml` — המדד -מיותם כעת. +ב-`quality-baseline.json`, אך אינו מחובר למנגנון מחגר חוסם ב-`ci.yml` — המדד +יתום כרגע. ### מיזוג / הסרת כפילויות (מכני, סיכון נמוך יותר) -כל מועמד אומת מול מצב שערי הבקרה הפעיל בתאריך 2026-06-17 (לתת אמון אך לוודא); -התברר שכמה מיזוגים "ברורים מאליהם" מסתירים חוב, ולכן הם **אינם** תחליפים ישירים ונקיים. +כל מועמד אומת מול מצב שערי הבקרה הפעיל ב-2026-06-17 (לתת אמון אך לאמת); +כמה מיזוגים "ברורים מאליהם" התגלו כמסתירים חוב, ולכן **אינם** תחליפים ישירים ונקיים. -- **`check:docs-sync` רץ פעמיים** — באופן עצמאי במשימת `lint` ושוב בתוך `check:docs-all` (`docs-sync-strict`) וב-hook של husky לפני commit. ✅ **בוצע** — ההפעלה העצמאית ב-`lint` הוסרה. -- **סריקת CVE** — ❌ **אינה מיזוג נקי.** `audit:deps` נכשל באופן קשיח בכל CVE ברמת high/critical;‏ `check:vuln-ratchet` (osv) נכשל רק במקרה של _נסיגה_ לעומת קו הבסיס (כרגע 1 ברמת MODERATE). המשמעויות שונות — הסרת `audit:deps` תבטל את שער הבקרה המוחלט לרמות high/critical. יש להשאיר את שניהם. -- **זיהוי מחזורים** — ❌ **אינו מיזוג נקי.** `check:circular-deps` (dpdm) מדווח על **91 מחזורים** (לכן הוא מייעץ); אי אפשר להפוך אותו לחוסם לפני פתרונם, וההיקף שלו רחב מזה של `check:cycles`, הירוק והמותאם. יש להשאיר את `check:cycles` כחוסם; פתרון 91 מחזורי dpdm הוא פריט נפרד בצבר המשימות. -- **מורכבות** — ✅ **בוצע** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): מעבר ESLint יחיד, עם ספירה לפי ruleId כך שקווי הבסיס של cyclomatic+max-lines ושל cognitive נשארים עצמאיים; `check:complexity` / `check:cognitive-complexity` הנפרדים נשארים לצורך `--update` מקומי. -- **מניעת הזיות ב-`/api`** — ✅ **בוצע** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): אינוונטר FS יחיד של `src/app/api`;‏ openapi-routes ו-docs-symbols עדיין מדווחים בנפרד; הפקודות הנפרדות נשארות להרצות מקומיות. -- **`check:node-runtime` רץ ב-11 משימות** — ⚠️ **ROI נמוך.** כל אחת מהן משתמשת ב-runner נפרד והבדיקה נמשכת פחות משנייה; החיסכון הכולל הוא כ-10 שניות, במחיר אובדן הגנה זולה לכל משימה. השינוי אינו מצדיק את הטלטלה. -- **`typecheck:noimplicit:core` ב-CI lint** — ✅ **הוסר ממשימת lint** (היה מייעץ עם `continue-on-error`); מעטפת הטיפוסים החוסמת היא `typecheck:core` + `check:type-coverage`. הסקריפט המקומי נשמר. +- **`check:docs-sync` מופעל פעמיים** — באופן עצמאי במשימת `lint` ושוב בתוך `check:docs-all` (`docs-sync-strict`) וב-hook של husky לפני commit. ✅ **בוצע** — ההפעלה העצמאית ב-`lint` הוסרה. +- **סריקת CVE** — ❌ **אינה מיזוג נקי.** `audit:deps` נכשל באופן קשיח בכל CVE ברמת high/critical;‏ `check:vuln-ratchet` (osv) נכשל רק במקרה של _נסיגה_ ביחס לקו הבסיס (כרגע 1 MODERATE). הסמנטיקה שונה — הסרת `audit:deps` תבטל את השער המוחלט לרמות high/critical. יש להשאיר את שניהם. +- **זיהוי מחזורים** — ✅ **בוצע** (#15159 G-01/G-02). הנוסח הישן כאן כינה את `check:cycles` שער "ירוק ואצור", והצדיק את השארתו כחוסם משום ש-`check:circular-deps` (dpdm) דיווח על 91 מחזורים. המצב הירוק הזה היה **ירוק כוזב**: `check:cycles` סרק 5 תתי-ספריות (450 קבצים), התאים רק ל-`import|export … from` סטטיים, והשמיט כל specifier מסוג `@/` ו-`@omniroute/open-sse/`, כך שלא היה יכול לזהות את מחזורי הייבוא הדינמי וה-alias ששלטו במאגר. תוקן: השער עובר כעת על `src` + `open-sse` (5023 קבצים), אוסף specifiers מתוך ה-AST של TypeScript (כך ש-`import("…")` נספר, אך `typeof import("…")` במיקום של טיפוס אינו נספר), ופותר את `paths` של tsconfig. הוא מוצא **14** מחזורים, ולא 0. מכיוון שלא ניתן לתקן 14 מחזורים קיימים מראש במסגרת PR של שער בקרה, `check:cycles` הוא כעת **מחגר** (`--ratchet`, תקרה `metrics.cycles.value = 14` בתוך `quality-baseline.json`,‏ `direction: down`) — הוא חוסם כל _נסיגה_, והספירה יכולה רק לרדת. CI מפעיל את `npm run check:cycles:ratchet`. צמצום החוב מתבצע במסגרת **A-01**. `check:circular-deps` (dpdm) נשאר מייעץ כחוות דעת שנייה רחבה יותר. +- **מורכבות** — ✅ **בוצע** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): מעבר ESLint יחיד, עם ספירה לפי ruleId כדי שקווי הבסיס של cyclomatic+max-lines ושל cognitive יישארו בלתי תלויים; `check:complexity` / `check:cognitive-complexity` הנפרדים נשארים לצורך `--update` מקומי. +- **מניעת הזיות עבור `/api`** — ✅ **בוצע** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): מצאי FS יחיד של `src/app/api`;‏ openapi-routes ו-docs-symbols עדיין מדווחים בנפרד; הבדיקות הנפרדות נשארות לצורך הרצות מקומיות. +- **`check:node-runtime` מופעל ב-11 משימות** — ⚠️ **ROI נמוך.** כל אחת מהן משתמשת ב-runner נפרד והבדיקה אורכת פחות משנייה; החיסכון הכולל הוא כ-10 שניות, במחיר אובדן אמצעי הגנה זול לכל משימה. לא שווה את הטלטלה. +- **`typecheck:noimplicit:core` ב-lint של CI** — ✅ **הוסר ממשימת lint** (היה מייעץ עם `continue-on-error`); משטח הטיפוסים החוסם הוא `typecheck:core` + `check:type-coverage`. הסקריפט המקומי נשמר. -### הקשחה / הכרעה (מדיניות המפעיל) +### שינוי / החלטה (מדיניות המפעיל) -- `check:openapi-security-tiers` (מייעץ) — ❌ **לא ניתן להפוך אותו לחוסם באופן נקי.** הוא מסיים עם 0, אך מזהיר שלכמה נתיבי `traffic-inspector` תחת `LOCAL_ONLY_API_PREFIXES` חסרה ההערה `x-loopback-only: true`. אכיפתו מחייבת הוספה מוקדמת של ההערות האלה ל-`openapi.yaml`. -- `typecheck:noimplicit:core` (מייעץ) — נכלל במידה רבה במחגר החוסם `check:type-coverage`. יש להפוך אותו למחגר או להסיר את מעבר ה-`tsc` השני והמיותר. +- `check:openapi-security-tiers` (מייעץ) — ❌ **לא ניתן להפוך אותו לחוסם באופן נקי.** הוא מסיים עם קוד 0, אך מזהיר שכמה נתיבי `traffic-inspector` תחת `LOCAL_ONLY_API_PREFIXES` חסרים את ההערה `x-loopback-only: true`. אכיפתו מחייבת להוסיף תחילה את ההערות האלה ל-`openapi.yaml`. +- `typecheck:noimplicit:core` (מייעץ) — נכלל ברובו במחגר החוסם `check:type-coverage`. יש להפוך אותו למחגר או להסיר את מעבר ה-`tsc` השני והמיותר. - `test:vitest:ui` (כעת **חוסם**) — כשלים קיימים מראש מוחרגים במפורש ב-`vitest.config.ts` באמצעות הערות מעקב `// #8618`; כשלים חדשים מכשילים את המשימה. -- `check:secrets` (gitleaks, מחגר חוסם שקפוא על 3 תוצאות חיוביות כוזבות מתועדות) — יש להוסיף את השלוש לרשימת ההחרגות כדי להגיע ל-0, או להוריד אותו למעמד מייעץ. קיימת חפיפה עם סריקת הסודות המובנית של GitHub ועם `check:public-creds`. -- `check:pr-evidence` (חוסם, מבצע grep על הפרוזה בגוף ה-PR) — סיכון גבוה לתוצאות חיוביות כוזבות; הסרתו מחלישה את אכיפת כלל קשיח מס' 18, ולכן זו החלטת מדיניות אמיתית. -- `semgrep` (תהליך עצמאי מייעץ) — חופף ל-CodeQL עבור משפחות OWASP; יש לחבר את קו הבסיס שלו למחגר או להסירו. +- `check:secrets` (gitleaks, מחגר חוסם המקובע על 3 זיהויים חיוביים כוזבים מתועדים) — יש להוסיף את השלושה לרשימת ההיתרים כדי להגיע ל-0, או להורידו למעמד מייעץ. קיימת חפיפה עם סריקת הסודות המובנית של GitHub ועם `check:public-creds`. +- `check:pr-evidence` (חוסם, מבצע grep על הטקסט החופשי בגוף ה-PR) — סיכון גבוה לזיהויים חיוביים כוזבים; הסרתו תחליש את האכיפה של כלל קשיח מס' 18, ולכן זו החלטת מדיניות אמיתית. +- `semgrep` (עצמאי ומייעץ) — חופף ל-CodeQL במשפחות OWASP; יש לחבר את קו הבסיס שלו למחגר או להסירו. --- diff --git a/docs/i18n/he/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/he/docs/compression/COMPRESSION_GUIDE.md index d16999d3b237..5fdc65126103 100644 --- a/docs/i18n/he/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/he/docs/compression/COMPRESSION_GUIDE.md @@ -453,8 +453,7 @@ const { messages: aged, saved } = applyAging(messages, { על גוף עם `messages`, עקיפת תוכן (`shouldBypassCavemanOutputMode()` ב- `open-sse/services/compression/outputMode.ts`) בודקת את שלוש ההודעות האחרונות ומדלגת על הסגנונות עבור כל הסיבוב כאשר הן תואמות למילות המפתח שלה לאבטחה, פעולה בלתי הפיכה, -הבהרה או רגישות לסדר. העקיפה פועלת ללא קשר למצב מתג ה-**Auto-Clarity Bypass** -(`cavemanOutputMode.autoClarity`) בלוח המחוונים. +הבהרה או רגישות לסדר. העקיפה פועלת כלעוד מתג ה-**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) בלוח המחוונים מופעל, וזו ברירת המחדל; כשהמתג כבוי, הסגנונות שנבחרו חלים גם בסיבובים האלה. כאשר העקיפה מאפשרת לסיבוב לעבור, `placeSystemInstruction()` (אותו קובץ), שלעולם אינו יוצר `messages[0]` חדש, ממקם את הבלוק בראשון מבין אלה שהוא מוצא: diff --git a/docs/i18n/he/docs/reference/ENVIRONMENT.md b/docs/i18n/he/docs/reference/ENVIRONMENT.md index f180379e0a8e..c54a646b7bec 100644 --- a/docs/i18n/he/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/he/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. סודות נדרשים -ערכים אלה **חייבים** להיות מוגדרים לפני ההפעלה הראשונה. בלעדיהם, היישום יסרב לפעול או יפעל עם ברירות מחדל לא מאובטחות. - -| משתנה | נדרש | ברירת מחדל | קובץ מקור | תיאור | -| ---------------------------- | --------------------- | --------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **כן** | _(ללא)_ | `src/lib/auth` | משמש לחתימה ולאימות של כל קובצי ה-Cookie של הפעלות לוח הבקרה (JWT). יש ליצור באמצעות `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **כן** | _(ללא)_ | `src/lib/db/apiKeys.ts` | מפתח הצפנת AES עבור ערכי מפתחות API המאוחסנים ב-SQLite. יש ליצור באמצעות `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **כן** | `CHANGEME` | סקריפט האתחול | מגדיר את הסיסמה הראשונית של מנהל לוח הבקרה (תואמת לברירת המחדל ב-`.env.example` — היא נשמרת כבלתי מאובטחת באופן מובהק כדי לחייב שינוי). **יש לשנות לפני השימוש הראשון.** לאחר הכניסה, ניתן לשנות דרך לוח הבקרה ← הגדרות ← אבטחה. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **כן** (בסביבת ייצור) | _(לא מוגדר)_ | `src/app/api/internal/codex-responses-ws/route.ts` | סוד משותף עבור גשר ה-WebSocket הפנימי של Codex Responses. מאמת בקשות גשר בין ממסר ה-WS של Electron/הדפדפן לבין OmniRoute. ⚠️ **נדרש בסביבת ייצור — כאשר אינו מוגדר, כל בקשות גשר ה-WS נדחות.** יש ליצור באמצעות `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | לא | _(SHA של git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | מזהה מפורש לשבירת מטמון ה-service worker עבור מעטפת ה-PWA‏ (#11779); הראשון בשרשרת הפענוח. | -| `SOURCE_VERSION` | לא | _(לא מוגדר)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | השני בשרשרת — מוגדר על ידי מערכות בנייה של PaaS (למשל, בסגנון Heroku) בתור ה-commit שנפרס. | -| `NEXT_PUBLIC_SW_BUILD_ID` | לא | _(נגזר)_ | `src/shared/components/PwaRegister.tsx` | ערך ציבורי בזמן הבנייה, שבו הלקוח משתמש כדי לרשום את `/sw.js?v=…`; נגזר משני הערכים שלעיל, ולאחר מכן מה-SHA של git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | לא (אוטומטי) | _(אוטומטי בכל אתחול)_ | `src/server/authz/policies/management.ts` | סוד ייחודי לתהליך, המוכיח שחותמת כתובת ה-IP של העמית המהימן הגיעה משרת ה-HTTP של OmniRoute עצמו (`scripts/dev/peer-stamp.mjs`). תווכת ה-authz נותנת אמון במקומיות הבקשה (חסימת נתיבי LOCAL_ONLY לפי loopback/LAN) רק כאשר החותמת מכילה אסימון זה. נוצר אוטומטית בכל אתחול — יש להשאירו לא מוגדר; יש לקבע אותו רק בתצורות מרובות תהליכים שחייבות לשתף את החותמת. | -| `OMNIROUTE_TRUSTED_PROXIES` | לא | _(לא מוגדר)_ | `scripts/dev/peer-stamp.mjs` | כתובות IP או טווחי CIDR, מופרדים בפסיקים, של שרתי reverse proxy אשר רשימת ההרשאה/חסימה לפי IP רשאית לתת אמון בערכי `X-Forwarded-For` / `X-Real-IP` שלהם, נוסף על כתובות loopback, כתובות של רשתות פרטיות ונקודות קצה של Cloudflare, אשר תמיד נחשבות מהימנות. נדרש רק עבור proxy בכתובת ציבורית אחרת כלשהי; אחרת, הכתובת של ה-proxy עצמו היא זו שהמסנן בוחן. | +סודות שאינם מוגדרים המסומנים **כן** נוצרים אוטומטית בהפעלה הראשונה (`JWT_SECRET`, `API_KEY_SECRET`) ונשמרים בקובץ `server.env` שבספריית הנתונים — יש להגדיר אותם במפורש כאשר ספרייה זו זמנית. היוצא מן הכלל הוא `OMNIROUTE_WS_BRIDGE_SECRET`: הוא אינו נשמר בקובץ `server.env` — נקודות הכניסה של השרת יוצרות ערך ארעי לכל הפעלה שרק הגשר בתוך התהליך יכול להשתמש בו, ולכן לקוחות גשר חיצוניים צריכים להגדיר אותו במפורש. + +| משתנה | נדרש | ברירת מחדל | קובץ מקור | תיאור | +| ---------------------------- | --------------------- | --------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **כן** | _(ללא)_ | `src/lib/auth` | משמש לחתימה ולאימות של כל קובצי ה-Cookie של הפעלות לוח הבקרה (JWT). יש ליצור באמצעות `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **כן** | _(ללא)_ | `src/lib/db/apiKeys.ts` | מפתח הצפנת AES עבור ערכי מפתחות API המאוחסנים ב-SQLite. יש ליצור באמצעות `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | לא | _(לא מוגדר)_ | `src/lib/auth/managementPassword.ts` | מגדיר את סיסמת לוח הבקרה בעת ההפעלה כל עוד לא נשמרה סיסמה; לאחר שנשמרה סיסמה, מתעלמים ממשתנה זה. התקנות npm והתקנות מקוד המקור מעתיקות את `.env.example` אל `.env`, ולכן הן מתחילות עם ה-placeholder הידוע לציבור שלו `CHANGEME`: החליפו אותו לפני ההפעלה הראשונה, או הגדירו `INITIAL_PASSWORD=` (ריק) בקובץ `.env` זה כדי ליצור את הסיסמה באשף ההגדרה הראשונית בביקור הראשון, כפי שעושה ערך לא מוגדר (ברירת המחדל של דמות Docker). יש לשנות סיסמה שמורה בלוח הבקרה ← הגדרות ← אבטחה, או להריץ `omniroute reset-password` (`node bin/reset-password.mjs` בעותק קוד מקור) כש-`DATA_DIR` מוגדר לספריית הנתונים של השרת. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **כן** (בסביבת ייצור) | _(לא מוגדר)_ | `src/app/api/internal/codex-responses-ws/route.ts` | סוד משותף עבור גשר ה-WebSocket הפנימי של Codex Responses. מאמת בקשות גשר בין ממסר ה-WS של Electron/הדפדפן לבין OmniRoute. ⚠️ **נדרש בסביבת ייצור — כאשר אינו מוגדר, כל בקשות גשר ה-WS נדחות.** יש ליצור באמצעות `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | לא | _(SHA של git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | מזהה מפורש לשבירת מטמון ה-service worker עבור מעטפת ה-PWA‏ (#11779); הראשון בשרשרת הפענוח. | +| `SOURCE_VERSION` | לא | _(לא מוגדר)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | השני בשרשרת — מוגדר על ידי מערכות בנייה של PaaS (למשל, בסגנון Heroku) בתור ה-commit שנפרס. | +| `NEXT_PUBLIC_SW_BUILD_ID` | לא | _(נגזר)_ | `src/shared/components/PwaRegister.tsx` | ערך ציבורי בזמן הבנייה, שבו הלקוח משתמש כדי לרשום את `/sw.js?v=…`; נגזר משני הערכים שלעיל, ולאחר מכן מה-SHA של git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | לא (אוטומטי) | _(אוטומטי בכל אתחול)_ | `src/server/authz/policies/management.ts` | סוד ייחודי לתהליך, המוכיח שחותמת כתובת ה-IP של העמית המהימן הגיעה משרת ה-HTTP של OmniRoute עצמו (`scripts/dev/peer-stamp.mjs`). תווכת ה-authz נותנת אמון במקומיות הבקשה (חסימת נתיבי LOCAL_ONLY לפי loopback/LAN) רק כאשר החותמת מכילה אסימון זה. נוצר אוטומטית בכל אתחול — יש להשאירו לא מוגדר; יש לקבע אותו רק בתצורות מרובות תהליכים שחייבות לשתף את החותמת. | +| `OMNIROUTE_TRUSTED_PROXIES` | לא | _(לא מוגדר)_ | `scripts/dev/peer-stamp.mjs` | כתובות IP או טווחי CIDR, מופרדים בפסיקים, של שרתי reverse proxy אשר רשימת ההרשאה/חסימה לפי IP רשאית לתת אמון בערכי `X-Forwarded-For` / `X-Real-IP` שלהם, נוסף על כתובות loopback, כתובות של רשתות פרטיות ונקודות קצה של Cloudflare, אשר תמיד נחשבות מהימנות. נדרש רק עבור proxy בכתובת ציבורית אחרת כלשהי; אחרת, הכתובת של ה-proxy עצמו היא זו שהמסנן בוחן. | ### פקודות יצירה @@ -215,38 +215,38 @@ OmniRoute משתמש ב-**SQLite** (באמצעות `better-sqlite3`) לכל צו ## 4. אבטחה ואימות -| משתנה | ברירת מחדל | קובץ מקור | תיאור | -| ------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt המשולב עם מזהי חומרה לצורך יצירת טביעת אצבע של המכונה. יש לשנות עבור כל פריסה כדי להבטיח בידוד. | -| `OMNIROUTE_CLI_SALT` | _(לא מוגדר = Salt אקראי לכל התקנה, הנשמר ב-`/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt מסוג HMAC לגזירת אסימון האימות המקומי של ה-CLI. הגדרת ערך זה מחליפה את כל אסימוני ה-CLI במכונה ותמיד מקבלת עדיפות על פני ה-Salt השמור. ראו `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | מגדיר את הדגל `Secure` בעוגיות הפעלה. **חייב להיות `true`** בעת הפעלה מאחורי HTTPS. | -| `REQUIRE_API_KEY` | `false` | תווכת API | כאשר הערך הוא `true`, כל בקשות ה-Proxy אל `/v1/*` חייבות לכלול מפתח API תקף. **דגל זה אינו מגביל את `GET /v1/models`**, אשר פועל בהתאם למדיניות ההתחברות של לוח הבקרה (`requireAuthForModels`) — לכן תגובת `401` מ-`/v1/models` אינה מעידה שההסקה מוגנת. ראו `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | מאפשר לחשוף ערכים מלאים של מפתחות API בממשק המשתמש של לוח הבקרה. ניתן להגדרה דרך דגלי התכונות בלוח הבקרה; מהווה סיכון אבטחה במופעים משותפים. | -| `NO_LOG_API_KEY_IDS` | _(ריק)_ | `src/lib/compliance/index.ts` | מזהי מפתחות API המופרדים בפסיקים, שעבורם רישום הבקשות נעקף (תאימות ל-GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(לא מוגדר = ללא הגבלה)_ | `src/shared/utils/apiKeyPolicy.ts` | מכסת בקשות יומית חלופית המוחלת על מפתחות API שהעמודה `rate_limits` שלהם היא null. כאשר הערך אינו מוגדר או ריק: אין מגבלה משתמעת (#2289, #11017). גם `0` משמעו ללא הגבלה. מספר שלם חיובי N מאפשר N/יום, 5N/שבוע, 20N/חודש. ערכים לא-ריקים בפורמט שגוי חוזרים לחלונות הקודמים של 1000/יום, 5000/שבוע ו-20000/חודש. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | הגודל המרבי המותר לגוף הבקשה. דוחה מטענים החורגים ממגבלה זו. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | גופי בקשות בפועל שגודלם שווה לסף זה או גדול ממנו מקבלים את הרשאת הקבלה האטומית המקומית לתהליך עבור עומס כבד לפני ניתוח JSON (נתיב BYTE, כולל `POST /v1/responses`). אותה חריגת מרווח-הראש התקין של [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) כמו בעומס מבני כבד; עדיין מוגבל על ידי `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), כדי ש-[#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) לא יחזור. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | תקרה קשיחה לנתיבי צ'אט, הנאכפת ביחס למספר הבתים שנקראו במהלך קליטה מוגבלת, לרבות בקשות שבהן `Content-Length` חסר, לא תקין או כוזב; חריגה מקבלת `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(לא מוגדר — ללא מגבלה על מספר הבקשות)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** מגבלת ספירת הבקשות (COUNT) הישנה הזו חלה כעת רק כאשר היא מוגדרת במפורש. כאשר היא אינה מוגדרת (ברירת המחדל), קבלת בקשות צ'אט כבדות נשלטת במקום זאת באמצעות `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — תקציב בתים (BYTE) הנגזר אוטומטית ומחושב לפי תקרת הזיכרון האמיתית של התהליך ב־**תהליך אחד** (ערימת V8 אחת). שתי בקשות `/v1/responses` חופפות של כ־750 אלף טוקנים גורמות לקריסת ערימות של כ־12 Gi (#7849) — זוהי אזהרת **תקציב זיכרון**, ולא מגבלת מוצר קשיחה של 2. תהליך תקין (ערימה מתחת ליחס ההשלה) עשוי לקבל יותר בקשות `/v1/responses` ארוכות במקביל באמצעות `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. עשרות לקוחות SSE ארוכים (40–50) תלויים בערימה + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ולא ב״מקסימום 2״. העלאה עיוורת של ערך זה כדי ״להשתמש במארח״ תחזיר את #7849. יש להכפיל **ערימות** באמצעות **N ערכי `DATA_DIR` עצמאיים** (#11024); לעולם אין להשתמש ב־`replicas>1` עבור קובץ SQLite יחיד. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(נגזר אוטומטית)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** דריסה של תקציב בתי הקליטה הנגזר אוטומטית (25% מהתקרה המחמירה יותר מבין תקרת הזיכרון של V8 ושל cgroup, חלקי הגברה זמנית של פי 8). ערכים נגזרים ומפורשים מוגבלים לטווח 8 MiB–2 GiB. גוף הגדול מהתקציב האפקטיבי נכשל מייד עם `413 body_exceeds_budget`; תחרות בין גופים שכל אחד מהם ניתן לטיפול בפני עצמו נותרת בת־ניסיון חוזר עם `503`. 40–50 לקוחות SSE ארוכים במקביל נקבעים לפי תקציב זה + הערימה, ולא לפי ״מקסימום 2״ קשיח. לפני כוונון, יש לקרוא את `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ב־`/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | יחס השלה עקב לחץ ערימה (`heapUsed / heap_size_limit`) עבור קבלת בקשות כבדות לפי BYTE ו־STRUCTURE (#10183, #10268, #10437). בקשה כבדה מקבילה החורגת מ־`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` מושלת עם `503` הניתן לניסיון חוזר רק כאשר הערימה נמצאת גם ביחס זה או מעליו; כאשר הערימה תקינה, היא מתקבלת במקום זאת באמצעות מרווח הקיבולת לערימה תקינה. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ברירת מחדל `1`) | `src/shared/middleware/chatBodyAdmission.ts` | קיבולת נוספת מוגבלת עבור הנתיב המהיר של ערימה תקינה (#10437), הן עבור STRUCTURE והן עבור BYTE (`admitChatRequest`, לרבות גופים בגודל ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). ללא מגבלה זו, כל בקשה במצב עומס אך עם ערימה תקינה הייתה עוקפת את מנגנון הקבלה ללא תקרה. לאחר שמספר זה של חכירות מקבילות פעיל דרך נתיב הערימה התקינה, בקשות עמוסות נוספות ממשיכות לאותו נתיב המתנה מוגבלת/השלה המשמש תחת לחץ ערימה אמיתי. `0` משבית את המעקף לחלוטין. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | מספר ההודעות המסווג בקשת צ'אט ככבדה גם כאשר גופה קטן מסף הבתים. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | מספר הכלים המסווג בקשת צ'אט ככבדה גם כאשר גופה קטן מסף הבתים. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | אומדן שמרני של מספר הטוקנים לפי גודל המחרוזת, המסווג בקשה ככבדה; זהו מדד מקורב לעלות הקבלה, ולא טוקניזציה לצורכי חיוב של הספק. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (מושבת) | `src/shared/middleware/chatBodyAdmission.ts` | תקרה אופציונלית להיסטוריית צ'אט, המופעלת במפורש. היא מושבתת כברירת מחדל: מספר הודעות הוא מדיניות פריסה, ולא מאפיין אוניברסלי של בקשה, והגבלה כאן דוחה שיחות עם `413` סופי לפני שצינור הדחיסה יכול להפוך אותן לניתנות לטיפול. גידול הערימה מוגבל באמצעות `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` והשלה עקב לחץ ערימה. יש להגדיר ערך חיובי בפריסות מוגבלות זיכרון הזקוקות לתקרה קשיחה; חריגה תקבל אז `413` מובנה המציין שנדרש צמצום. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | תקרה קשיחה לתגובת upstream שאינה מוזרמת ונאגרת במלואה בזיכרון. מעבר לתקרה זו, קורא ה־upstream מבוטל והבקשה נכשלת במהירות, במקום להגדיל מחרוזת ללא הגבלה עד למיצוי הערימה. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | מספר הבתים המרבי שיועברו בכותרות התגובה משירות המקור. כאשר חורגים מהמכסה, כותרות בעדיפות נמוכה יותר (למשל, `x-codex-*` מותאמות אישית, `x-oai-request-id`) מושמטות כדי להישאר במסגרת מגבלות הכותרות המקובלות של שרתי proxy הפוכים. הגדירו ערך גבוה יותר כדי להעביר יותר מטא-נתונים משירות המקור, במחיר של הגדלת כותרות התגובה. | -| `CORS_ORIGIN` | _(לא מוגדר)_ | `src/server/cors/origins.ts` | רשימת היתרים מדור קודם של מקור יחיד עבור CORS. בפריסות חדשות, העדיפו את `CORS_ALLOWED_ORIGINS`.‏ CORS מיועד רק ללקוחות API בדפדפן ממקור שונה; פעולות כתיבה מאומתות בלוח הבקרה משתמשות במקום זאת בבקשות מאותו מקור ובהגנת CSRF המקושרת להפעלה. | -| `CORS_ALLOWED_ORIGINS` | _(לא מוגדר)_ | `src/server/cors/origins.ts` | רשימת היתרים של CORS המופרדת בפסיקים. תו כללי אינו נשלח אלא אם `CORS_ALLOW_ALL=true` הוגדר במפורש. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | מעקף המיועד לפיתוח בלבד, המחזיר כל `Origin` של דפדפן. אין להפעיל בפריסות משותפות או בפריסות ייצור. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | חסימת קריאות לספקים המכוונות לטווחי כתובות IP פרטיות, loopback או link-local. יש להשבית רק בסביבות בדיקה מבודדות. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | מתן אפשרות לכתובות URL של ספקים המצביעות לרשתות פרטיות או מקומיות (localhost, 192.168.x.x, 10.x.x.x וכו'). **נדרש עבור ספקים באירוח עצמי** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). כאשר הערך הוא `false`, לוח הבקרה דוחה אימות של כתובות URL מקומיות. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | מתן אפשרות להוספה או לאימות של ספקים בכתובות מקומיות או פרטיות (127.0.0.1, localhost, LAN, טווחים פרטיים) — מוגבל לנתיב אימות הספק. **ברירת המחדל היא `true`** (גישה מקומית תחילה); הגדירו `false` כדי לאכוף חסימה מחמירה המאפשרת כתובות ציבוריות בלבד. נקודות קצה של מטא-נתוני ענן (169.254.169.254, metadata.google.internal) נשארות חסומות בכל מקרה. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | מתן אפשרות לנתיבי `/v1/audio/*` (תמלולים, דיבור, תרגומים) להשתמש בצומת ספק תואם OpenAI המתארח מחוץ ל-localhost. כבוי כברירת מחדל — ניתוב שמע למארח מרוחק משנה את זהות התעבורה היוצאת ומחייב החלטה מפורשת של המפעיל. צומתי loopback או צמתים פרטיים (localhost, 127.0.0.1, 172.16-31.x) מותרים תמיד ואינם מושפעים. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | מתן אפשרות ל-`POST /v1/rerank` (ולשלב הדירוג מחדש ב-loopback של מנוע הזיכרון) להשתמש בצומת ספק תואם OpenAI המתארח מחוץ ל-localhost — מחשב ברשת LAN או עמית Tailscale שמריץ TEI, Infinity, vLLM וכו'. כבוי כברירת מחדל — ניתוב למארח מרוחק משנה את זהות התעבורה היוצאת ומחייב החלטה מפורשת של המפעיל. צומתי loopback‏ (localhost, 127.0.0.1, 172.16-31.x) מותרים תמיד ואינם מושפעים. צמתים מרוחקים חייבים לעמוד גם במדיניות כתובות ה-URL היוצאות של הספק (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); לעולם לא מתבצע ניתוב למארחי מטא-נתוני ענן. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | כאשר OIDC מופעל, השבתת התחברות באמצעות סיסמה כך שמשתמשים יוכלו לבצע אימות רק באמצעות כניסה יחידה של OIDC. גם הכינוי המקוצר `OIDC_DISABLE_PASSWORD_LOGIN` נתמך; דגל התכונה בלוח הבקרה בעל אותו מפתח מקבל עדיפות. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | כינוי מקוצר של `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| משתנה | ברירת מחדל | קובץ מקור | תיאור | +| ------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt המשולב עם מזהי חומרה לצורך יצירת טביעת אצבע של המכונה. יש לשנות עבור כל פריסה כדי להבטיח בידוד. | +| `OMNIROUTE_CLI_SALT` | _(לא מוגדר = Salt אקראי לכל התקנה, הנשמר ב-`/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt מסוג HMAC לגזירת אסימון האימות המקומי של ה-CLI. הגדרת ערך זה מחליפה את כל אסימוני ה-CLI במכונה ותמיד מקבלת עדיפות על פני ה-Salt השמור. ראו `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | מגדיר את הדגל `Secure` בעוגיות הפעלה. **חייב להיות `true`** בעת הפעלה מאחורי HTTPS. | +| `REQUIRE_API_KEY` | `false` | תווכת API | כאשר הערך הוא `true`, כל בקשות ה-Proxy אל `/v1/*` חייבות לכלול מפתח API תקף. **דגל זה אינו מגביל את `GET /v1/models`**, אשר פועל בהתאם למדיניות ההתחברות של לוח הבקרה (`requireAuthForModels`) — לכן תגובת `401` מ-`/v1/models` אינה מעידה שההסקה מוגנת. ראו `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | מאפשר לחשוף ערכים מלאים של מפתחות API בממשק המשתמש של לוח הבקרה. ניתן להגדרה דרך דגלי התכונות בלוח הבקרה; מהווה סיכון אבטחה במופעים משותפים. | +| `NO_LOG_API_KEY_IDS` | _(ריק)_ | `src/lib/compliance/index.ts` | מזהי מפתחות API המופרדים בפסיקים, שעבורם רישום הבקשות נעקף (תאימות ל-GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(לא מוגדר = ללא הגבלה)_ | `src/shared/utils/apiKeyPolicy.ts` | מכסת בקשות יומית חלופית המוחלת על מפתחות API שהעמודה `rate_limits` שלהם היא null. כאשר הערך אינו מוגדר או ריק: אין מגבלה משתמעת (#2289, #11017). גם `0` משמעו ללא הגבלה. מספר שלם חיובי N מאפשר N/יום, 5N/שבוע, 20N/חודש. ערכים לא-ריקים בפורמט שגוי חוזרים לחלונות הקודמים של 1000/יום, 5000/שבוע ו-20000/חודש. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | הגודל המרבי המותר לגוף הבקשה. דוחה מטענים החורגים ממגבלה זו. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | גופי בקשות בפועל שגודלם שווה לסף זה או גדול ממנו מקבלים את הרשאת הקבלה האטומית המקומית לתהליך עבור עומס כבד לפני ניתוח JSON (נתיב BYTE, כולל `POST /v1/responses`). אותה חריגת מרווח-הראש התקין של [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) כמו בעומס מבני כבד; עדיין מוגבל על ידי `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), כדי ש-[#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) לא יחזור. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | תקרה קשיחה לנתיבי צ'אט, הנאכפת ביחס למספר הבתים שנקראו במהלך קליטה מוגבלת, לרבות בקשות שבהן `Content-Length` חסר, לא תקין או כוזב; חריגה מקבלת `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(לא מוגדר — ללא מגבלה על מספר הבקשות)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** מגבלת ספירת הבקשות (COUNT) הישנה הזו חלה כעת רק כאשר היא מוגדרת במפורש. כאשר היא אינה מוגדרת (ברירת המחדל), קבלת בקשות צ'אט כבדות נשלטת במקום זאת באמצעות `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — תקציב בתים (BYTE) הנגזר אוטומטית ומחושב לפי תקרת הזיכרון האמיתית של התהליך ב־**תהליך אחד** (ערימת V8 אחת). שתי בקשות `/v1/responses` חופפות של כ־750 אלף טוקנים גורמות לקריסת ערימות של כ־12 Gi (#7849) — זוהי אזהרת **תקציב זיכרון**, ולא מגבלת מוצר קשיחה של 2. תהליך תקין (ערימה מתחת ליחס ההשלה) עשוי לקבל יותר בקשות `/v1/responses` ארוכות במקביל באמצעות `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. עשרות לקוחות SSE ארוכים (40–50) תלויים בערימה + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ולא ב״מקסימום 2״. העלאה עיוורת של ערך זה כדי ״להשתמש במארח״ תחזיר את #7849. יש להכפיל **ערימות** באמצעות **N ערכי `DATA_DIR` עצמאיים** (#11024); לעולם אין להשתמש ב־`replicas>1` עבור קובץ SQLite יחיד. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(נגזר אוטומטית)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** דריסה של תקציב בתי הקליטה הנגזר אוטומטית (25% מהתקרה המחמירה יותר מבין תקרת הזיכרון של V8 ושל cgroup, חלקי הגברה זמנית של פי 8). ערכים נגזרים ומפורשים מוגבלים לטווח 8 MiB–2 GiB. גוף הגדול מהתקציב האפקטיבי נכשל מייד עם `413 body_exceeds_budget`; תחרות בין גופים שכל אחד מהם ניתן לטיפול בפני עצמו נותרת בת־ניסיון חוזר עם `503`. 40–50 לקוחות SSE ארוכים במקביל נקבעים לפי תקציב זה + הערימה, ולא לפי ״מקסימום 2״ קשיח. לפני כוונון, יש לקרוא את `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ב־`/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | יחס השלה עקב לחץ ערימה (`heapUsed / heap_size_limit`) עבור קבלת בקשות כבדות לפי BYTE ו־STRUCTURE (#10183, #10268, #10437). בקשה כבדה מקבילה החורגת מ־`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` מושלת עם `503` הניתן לניסיון חוזר רק כאשר הערימה נמצאת גם ביחס זה או מעליו; כאשר הערימה תקינה, היא מתקבלת במקום זאת באמצעות מרווח הקיבולת לערימה תקינה. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ברירת מחדל `1`) | `src/shared/middleware/chatBodyAdmission.ts` | קיבולת נוספת מוגבלת עבור הנתיב המהיר של ערימה תקינה (#10437), הן עבור STRUCTURE והן עבור BYTE (`admitChatRequest`, לרבות גופים בגודל ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). ללא מגבלה זו, כל בקשה במצב עומס אך עם ערימה תקינה הייתה עוקפת את מנגנון הקבלה ללא תקרה. לאחר שמספר זה של חכירות מקבילות פעיל דרך נתיב הערימה התקינה, בקשות עמוסות נוספות ממשיכות לאותו נתיב המתנה מוגבלת/השלה המשמש תחת לחץ ערימה אמיתי. `0` משבית את המעקף לחלוטין. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | מספר ההודעות המסווג בקשת צ'אט ככבדה גם כאשר גופה קטן מסף הבתים. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | מספר הכלים המסווג בקשת צ'אט ככבדה גם כאשר גופה קטן מסף הבתים. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | אומדן שמרני של מספר הטוקנים לפי גודל המחרוזת, המסווג בקשה ככבדה; זהו מדד מקורב לעלות הקבלה, ולא טוקניזציה לצורכי חיוב של הספק. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (מושבת) | `src/shared/middleware/chatBodyAdmission.ts` | תקרה אופציונלית להיסטוריית צ'אט, המופעלת במפורש. היא מושבתת כברירת מחדל: מספר הודעות הוא מדיניות פריסה, ולא מאפיין אוניברסלי של בקשה, והגבלה כאן דוחה שיחות עם `413` סופי לפני שצינור הדחיסה יכול להפוך אותן לניתנות לטיפול. גידול הערימה מוגבל באמצעות `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` והשלה עקב לחץ ערימה. יש להגדיר ערך חיובי בפריסות מוגבלות זיכרון הזקוקות לתקרה קשיחה; חריגה תקבל אז `413` מובנה המציין שנדרש צמצום. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | תקרה קשיחה לתגובת upstream שאינה מוזרמת ונאגרת במלואה בזיכרון. מעבר לתקרה זו, קורא ה־upstream מבוטל והבקשה נכשלת במהירות, במקום להגדיל מחרוזת ללא הגבלה עד למיצוי הערימה. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | מספר הבתים המרבי שיועברו בכותרות התגובה משירות המקור. כאשר חורגים מהמכסה, כותרות בעדיפות נמוכה יותר (למשל, `x-codex-*` מותאמות אישית, `x-oai-request-id`) מושמטות כדי להישאר במסגרת מגבלות הכותרות המקובלות של שרתי proxy הפוכים. הגדירו ערך גבוה יותר כדי להעביר יותר מטא-נתונים משירות המקור, במחיר של הגדלת כותרות התגובה. | +| `CORS_ORIGIN` | _(לא מוגדר)_ | `src/server/cors/origins.ts` | רשימת היתרים מדור קודם של מקור יחיד עבור CORS. בפריסות חדשות, העדיפו את `CORS_ALLOWED_ORIGINS`.‏ CORS מיועד רק ללקוחות API בדפדפן ממקור שונה; פעולות כתיבה מאומתות בלוח הבקרה משתמשות במקום זאת בבקשות מאותו מקור ובהגנת CSRF המקושרת להפעלה. | +| `CORS_ALLOWED_ORIGINS` | _(לא מוגדר)_ | `src/server/cors/origins.ts` | רשימת היתרים של CORS המופרדת בפסיקים. תו כללי אינו נשלח אלא אם `CORS_ALLOW_ALL=true` הוגדר במפורש. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | מעקף המיועד לפיתוח בלבד, המחזיר כל `Origin` של דפדפן. אין להפעיל בפריסות משותפות או בפריסות ייצור. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | כינוי מדור קודם. את המתג בלוח הבקרה שלו (עקיפה הנשמרת במסד הנתונים) קוראים לפני ערך סביבה זה; `false`, `0`, `no` או `off` בכל אחד מהם מכבה את בדיקות המארח בדיוק כפי שעושה `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, גם כאשר המתג בלוח הבקרה של דגל זה נשמר ככבוי; ערכים אחרים משאירים את ההגנה לשני הדגלים שלהלן. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | מכבה את בדיקות המארח של מנגנון ההגנה על כתובות URL יוצאות, כולל חסימת מטא-נתוני הענן (כל 169.254.0.0/16, metadata.google.internal), בנתיבי הספק הפועלים לפי מצב ההגנה: אימות כתובות URL, גילוי מודלים, כתובות URL בסיס של צמתי ספק (לרבות צמתי דירוג מחדש מרוחקים) ובדיקת הנסיגה לפרוקסי, ובנתיבי יציאה אחרים הסמוכים לספק כגון שליפת תמונות או מדיה מרוחקת. הוא גם מתיר יעדי webhook פרטיים, למעט נקודות קצה של מטא-נתוני ענן או link-local, שנשארות חסומות גם כשדגל זה דלוק. בקשות צ'אט הנשלחות דרך `BaseExecutor.execute()` שומרות על בדיקת מטא-נתוני הענן שלהן. ספקים מקומיים עובדים עם ברירות המחדל: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ברירת מחדל `true`) כבר מתיר כתובות URL מקומיות ו-LAN בנתיבי האימות, הגילוי וצמתי הספק; בדיקת הנסיגה לפרוקסי ויעדי webhook פרטיים מתייעצים רק עם דגל זה וחוסמים מארחים מקומיים ו-LAN כל עוד הוא כבוי. גובר על `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. ערך שנשמר מהמתג בלוח הבקרה גובר על משתנה זה. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | מתיר כתובות URL של ספקים בכתובות מקומיות/פרטיות (127.0.0.1, localhost, LAN, טווחים פרטיים) עבור אימות ספקים, גילוי מודלים, צ'אט ושאר נתיבי היציאה הסמוכים לספק שמצב ההגנה חל עליהם. **ברירת המחדל `true`** (מקומי תחילה): ההגנה חוסמת אז את נקודות הקצה של מטא-נתוני הענן — כל 169.254.0.0/16 וכן שמות המארחים המוכרים של המטא-נתונים (metadata.google.internal, metadata.goog, IMDS של Alibaba/Azure/Oracle, fd00:ec2::254). הגדירו `false` למצב ציבורי בלבד, החוסם גם מארחים פרטיים ו-loopback. ההגנה בודקת את שם המארח או את ה-IP הליטרלי כפי שנכתב, לפני כל פניית DNS; במצב ציבורי בלבד עטיפת ה-fetch הבטוח גם מפענחת את שם המארח ודוחה כל תשובה פרטית. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` מכבה בדיקות אלה בנתיבים המפורטים בשורה שלו. ספקים מקומיים מובנים, כגון LM Studio, Ollama, vLLM, Llamafile ו-Triton, מדלגים על כל בדיקות המארח באימות מפתחות ובצ'אט. ערך שנשמר מהמתג בלוח הבקרה גובר על משתנה זה. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | מתן אפשרות לנתיבי `/v1/audio/*` (תמלולים, דיבור, תרגומים) להשתמש בצומת ספק תואם OpenAI המתארח מחוץ ל-localhost. כבוי כברירת מחדל — ניתוב שמע למארח מרוחק משנה את זהות התעבורה היוצאת ומחייב החלטה מפורשת של המפעיל. צומתי loopback או צמתים פרטיים (localhost, 127.0.0.1, 172.16-31.x) מותרים תמיד ואינם מושפעים. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | מתן אפשרות ל-`POST /v1/rerank` (ולשלב הדירוג מחדש ב-loopback של מנוע הזיכרון) להשתמש בצומת ספק תואם OpenAI המתארח מחוץ ל-localhost — מחשב ברשת LAN או עמית Tailscale שמריץ TEI, Infinity, vLLM וכו'. כבוי כברירת מחדל — ניתוב למארח מרוחק משנה את זהות התעבורה היוצאת ומחייב החלטה מפורשת של המפעיל. צומתי loopback‏ (localhost, 127.0.0.1, 172.16-31.x) מותרים תמיד ואינם מושפעים. כתובת ה-URL הבסיסית של צומת מרוחק חייבת לעמוד גם במדיניות כתובות ה-URL היוצאות של הספק (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | כאשר OIDC מופעל, השבתת התחברות באמצעות סיסמה כך שמשתמשים יוכלו לבצע אימות רק באמצעות כניסה יחידה של OIDC. גם הכינוי המקוצר `OIDC_DISABLE_PASSWORD_LOGIN` נתמך; דגל התכונה בלוח הבקרה בעל אותו מפתח מקבל עדיפות. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | כינוי מקוצר של `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### רשימת תיוג להקשחה diff --git a/docs/i18n/hi/CONTRIBUTING.md b/docs/i18n/hi/CONTRIBUTING.md index 97463e1137d8..e7770572d1b0 100644 --- a/docs/i18n/hi/CONTRIBUTING.md +++ b/docs/i18n/hi/CONTRIBUTING.md @@ -385,22 +385,22 @@ OAuth क्रेडेंशियल्स को `src/lib/oauth/constants/oa --- -## पुल अनुरोध चेकलिस्ट +## पुल रिक्वेस्ट चेकलिस्ट - [ ] परीक्षण सफल हैं (`npm test`) - [ ] लिंटिंग सफल है (`npm run lint`) - [ ] बिल्ड सफल है (`npm run build`) -- [ ] नए सार्वजनिक फ़ंक्शन और इंटरफ़ेस के लिए TypeScript प्रकार जोड़े गए हैं -- [ ] कोई हार्डकोड किया गया सीक्रेट या फ़ॉलबैक मान नहीं है +- [ ] नए सार्वजनिक फ़ंक्शनों और इंटरफ़ेस के लिए TypeScript प्रकार जोड़े गए हैं +- [ ] कोई हार्डकोडेड सीक्रेट या फ़ॉलबैक मान नहीं है - [ ] सार्वजनिक अपस्ट्रीम क्रेडेंशियल `resolvePublicCred()` के माध्यम से एम्बेड किए गए हैं ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) देखें), कभी भी लिटरल के रूप में नहीं -- [ ] त्रुटि प्रतिक्रियाएँ `buildErrorBody()` / `sanitizeErrorMessage()` के माध्यम से भेजी जाती हैं — प्रतिक्रिया बॉडी में कोई रॉ स्टैक ट्रेस नहीं है ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) देखें) +- [ ] त्रुटि प्रतिक्रियाएँ `buildErrorBody()` / `sanitizeErrorMessage()` के माध्यम से भेजी जाती हैं — प्रतिक्रिया बॉडी में कोई रॉ स्टैक ट्रेस नहीं ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) देखें) - [ ] शेल कमांड (`exec` / `spawn`) रनटाइम मानों को स्ट्रिंग इंटरपोलेशन के बजाय `env` के माध्यम से पास करते हैं -- [ ] सभी इनपुट Zod स्कीमा से सत्यापित किए गए हैं -- [ ] उपयोगकर्ता को प्रभावित करने वाले परिवर्तनों के लिए `changelog.d/{features|fixes|maintenance}/-.md` के अंतर्गत चेंजलॉग **फ़्रैगमेंट** जोड़ा गया है ([`changelog.d/README.md`](./changelog.d/README.md) देखें) — `CHANGELOG.md` को सीधे संपादित **न करें**; फ़्रैगमेंट रिलीज़ के समय एकत्रित किए जाते हैं और PR के बीच कभी टकराव उत्पन्न नहीं करते -- [ ] दस्तावेज़ अपडेट किए गए हैं (यदि लागू हो) -- [ ] कोई नया CodeQL / Secret-Scanning अलर्ट नहीं खुला है, या प्रत्येक को संबंधित `docs/security/` दस्तावेज़ का संदर्भ देने वाले तकनीकी औचित्य के साथ खारिज किया गया है -- [ ] चाइल्ड प्रोसेस शुरू करने वाले रूट (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` में `isLocalOnlyPath()` के रूप में वर्गीकृत किए गए हैं — [कठोर नियम #15](docs/security/ROUTE_GUARD_TIERS.md) देखें -- [ ] कमिट संदेशों में कोई `Co-Authored-By` ट्रेलर नहीं है — कमिट केवल रिपॉज़िटरी स्वामी की Git पहचान के अंतर्गत दिखाई देने चाहिए (कठोर नियम #16) +- [ ] सभी इनपुट Zod स्कीमा के साथ सत्यापित किए गए हैं +- [ ] उपयोगकर्ता-दृश्य परिवर्तनों के लिए `changelog.d/{features|fixes|maintenance}/-.md` के अंतर्गत चेंजलॉग **फ़्रैगमेंट** जोड़ा गया है ([`changelog.d/README.md`](./changelog.d/README.md) देखें) — `CHANGELOG.md` को सीधे संपादित **न करें**; फ़्रैगमेंट रिलीज़ के समय एकत्रित किए जाते हैं और PR के बीच कभी टकराव नहीं करते +- [ ] दस्तावेज़ीकरण अपडेट किया गया है (यदि लागू हो) +- [ ] कोई नया CodeQL / Secret-Scanning अलर्ट नहीं खुला है, या प्रत्येक को संबंधित `docs/security/` दस्तावेज़ का संदर्भ देते हुए तकनीकी औचित्य के साथ खारिज किया गया है +- [ ] चाइल्ड प्रोसेस शुरू करने वाले रूट (`/api/mcp/`, `/api/cli-tools/runtime/`) को `src/server/authz/routeGuard.ts` में `isLocalOnlyPath()` के रूप में वर्गीकृत किया गया है — [कठोर नियम #15](docs/security/ROUTE_GUARD_TIERS.md) देखें +- [ ] कमिट संदेशों में AI/बॉट `Co-authored-by` ट्रेलर नहीं हैं (कठोर नियम #16) — जिन मानव सहयोगियों के कार्य का पुनः उपयोग किया गया है, उन्हें मानक `Co-authored-by: Name ` ट्रेलर के साथ श्रेय दिया गया है --- diff --git a/docs/i18n/hi/docs/architecture/QUALITY_GATES.md b/docs/i18n/hi/docs/architecture/QUALITY_GATES.md index dd2c737c0480..100bf73165b4 100644 --- a/docs/i18n/hi/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/hi/docs/architecture/QUALITY_GATES.md @@ -21,78 +21,78 @@ CI और Quality Gates वर्कफ़्लो में से प्रत्येक एक स्थिर निर्णय देता है: `Gate / CI` और `Gate / Quality`। उनकी संस्करणबद्ध स्वीकृति नीति प्रत्येक अपस्ट्रीम जॉब को -अनिवार्य या परामर्शात्मक के रूप में सूचीबद्ध करती है। किसी लागू अनिवार्य जॉब का सफल होना आवश्यक है: -अनुपलब्ध, रद्द, छोड़े गए, लंबित और अज्ञात परिणाम PASS स्थापित नहीं कर सकते। एक मान्य +आवश्यक या परामर्शात्मक के रूप में सूचीबद्ध करती है। लागू होने वाला आवश्यक जॉब सफल होना चाहिए: +अनुपस्थित, रद्द, छोड़े गए, लंबित और अज्ञात परिणाम PASS स्थापित नहीं कर सकते। एक मान्य केवल-दस्तावेज़ या केवल-कैटलॉग वर्गीकरण किसी कोड लेन को अनुपयुक्त बना सकता है; -ड्राफ़्ट PR को स्वीकृत उम्मीदवार नहीं माना जाता। `hotfix` लेबल साक्ष्य की आवश्यकता समाप्त नहीं करता। +ड्राफ़्ट PR स्वीकार्य उम्मीदवार नहीं है। `hotfix` लेबल साक्ष्य की आवश्यकता समाप्त नहीं करता। दोनों वर्कफ़्लो PR, main/release ब्रांच पर पुश, मैन्युअल डिस्पैच और -मर्ज-ग्रुप इवेंट को कवर करते हैं। पुश, डिस्पैच और मर्ज-ग्रुप पूर्ण चयन चलाते हैं। फ़ोर्क -और मर्ज ग्रुप उन जॉब के लिए होस्टेड रनर का उपयोग करते हैं जो अन्यथा सेल्फ-होस्टेड +merge-group इवेंट को कवर करते हैं। पुश, डिस्पैच और merge-group पूर्ण चयन चलाते हैं। फ़ोर्क +और merge group उन जॉब के लिए होस्टेड रनर का उपयोग करते हैं जो अन्यथा self-hosted रनर चुनते हैं; रोलआउट से पहले पर्याप्त होस्टेड क्षमता सत्यापित की जानी चाहिए। प्रत्येक JSON रसीद चेक-आउट किए गए SHA, वर्कफ़्लो रन और प्रयास की पहचान करती है। -CLI, चेकआउट/इवेंट SHA के बेमेल होने को अस्वीकार करता है। वर्कफ़्लो परीक्षण नीति सदस्यता को +CLI, checkout/event SHA के बेमेल को अस्वीकार करता है। वर्कफ़्लो परीक्षण नीति सदस्यता को निर्णय जॉब की `needs` सूची से बाँधते हैं, ताकि कोई नई या हटाई गई लेन चुपचाप गायब न हो सके। रसीदें अपने स्वयं के वर्कफ़्लो को कवर करती हैं, प्रकाशन, परिनियोजन या किसी मौजूदा -परामर्शात्मक स्कैनर के आंतरिक भागों को नहीं। ब्रांच नियमों में दोनों चेक नाम सक्रिय करना +परामर्शात्मक स्कैनर की आंतरिक कार्यप्रणाली को नहीं। ब्रांच नियमों में दोनों चेक नाम सक्रिय करना एक अलग प्रशासनिक परिवर्तन है; इन जॉब को जोड़ना अपने आप किसी ब्रांच को सुरक्षित नहीं करता। -### स्टैटिक स्कैन इन्वेंटरी +### स्थैतिक स्कैन इन्वेंटरी -संस्करणबद्ध npm-अलियास इन्वेंटरी और स्टैटिक-स्कैन सदस्यता +संस्करणबद्ध npm-alias इन्वेंटरी और स्थैतिक-स्कैन सदस्यता `config/quality/gate-manifest.json` में मौजूद हैं। स्क्रिप्ट नामों और सटीक कमांड को -`package.json` के विरुद्ध सत्यापित करने के लिए `npm run check:gate-manifest` चलाएँ; जोड़, -हटाने और कमांड में बदलाव होने पर लोकल हुक तथा CI में परिवर्तन-वर्गीकरण जॉब, दोनों विफल हो जाते हैं। -कोई अलियास वर्कफ़्लो जॉब, मैट्रिक्स इंस्टेंस या टेस्ट केस नहीं होता: इन गणनाओं को +`package.json` के विरुद्ध सत्यापित करने के लिए `npm run check:gate-manifest` चलाएँ; जोड़ने, हटाने +और कमांड में विचलन होने पर स्थानीय हुक और CI में परिवर्तन-वर्गीकरण जॉब, दोनों विफल हो जाते हैं। +कोई alias वर्कफ़्लो जॉब, मैट्रिक्स इंस्टेंस या परीक्षण केस नहीं होता: इन संख्याओं को परस्पर विनिमेय के रूप में प्रस्तुत नहीं किया जाना चाहिए। -चयनित अलियास को निष्पादित किए बिना उनका निरीक्षण करने के लिए -`npm run quality:scan -- --list` या `npm run quality:scan:fast -- --list` का उपयोग करें। -रनर npm एंट्रीपॉइंट को इनवोक करता है, इसलिए उसका रनटाइम (कॉन्फ़िगर किए जाने पर Bun सहित) -संरक्षित रहता है। मैनिफ़ेस्ट उन प्रोफ़ाइल से बाहर के अलियास को अलग से इनवोक किए गए के रूप में -दर्ज करता है, और केवल-पढ़ने योग्य स्कैन प्रोफ़ाइल में रखरखाव कमांड निषिद्ध हैं। +चयनित alias को निष्पादित किए बिना देखने के लिए `npm run quality:scan -- --list` या +`npm run quality:scan:fast -- --list` का उपयोग करें। रनर npm entrypoint को आमंत्रित करता है, +इसलिए उसका रनटाइम (जहाँ कॉन्फ़िगर किया गया हो वहाँ Bun सहित) संरक्षित रहता है। +मैनिफ़ेस्ट उन प्रोफ़ाइल के बाहर के alias को अलग से आमंत्रित किए गए के रूप में दर्ज करता है, और +केवल-पढ़ने योग्य स्कैन प्रोफ़ाइल में रखरखाव कमांड निषिद्ध हैं। -ये प्रोफ़ाइल केवल स्टैटिक स्कैन को कवर करती हैं। ये उत्पाद परीक्षण, +ये प्रोफ़ाइल केवल स्थैतिक स्कैन को कवर करती हैं। वे उत्पाद परीक्षण, कवरेज, पैकेजिंग, बाहरी जाँच या किसी उम्मीदवार की पूर्ण रिलीज़ स्वीकृति को प्रमाणित नहीं करतीं। वर्कफ़्लो स्वीकृति लिंक किए गए `config/quality/admission-policy.json` और `scripts/quality/admission-verdict.mjs` का उपयोग करती है। Release-observer प्रोफ़ाइल अलग रहती हैं; -उनकी लागू जाँचों और रसीदों का स्वतंत्र रूप से निरीक्षण करें। नीचे दी गई वर्णनात्मक -इन्वेंटरी एक संदर्भ है, यह इस बात का प्रमाण नहीं है कि कोई गेट वास्तव में चला था। +उनकी लागू जाँचों और रसीदों का स्वतंत्र रूप से निरीक्षण करें। नीचे दी गई गद्य +इन्वेंटरी एक संदर्भ है, इस बात का प्रमाण नहीं कि कोई गेट वास्तव में चला था। -स्क्रिप्ट `scripts/check/` (नीति गेट) और `scripts/quality/` (रैचेट इंजन) के अंतर्गत होती हैं। +स्क्रिप्ट `scripts/check/` (नीति गेट) और `scripts/quality/` (ratchet इंजन) के अंतर्गत मौजूद हैं। CI का प्रामाणिक स्रोत `.github/workflows/ci.yml` है। ### रिलीज़ PR फ़ास्ट-पाथ (`quality.yml`) `.github/workflows/quality.yml`, main/release PR, संरक्षित-ब्रांच -पुश, डिस्पैच और मर्ज ग्रुप पर CI का पूरक है। PR पाथ-फ़िल्टर की गई तेज़ जाँचों का उपयोग करते हैं। स्थायी रूप से -अक्षम डुप्लिकेट बिल्ड हटा दिया गया था; वास्तविक बिल्ड/पैकेज/बूट जाँचें CI में बनी रहती हैं। - -| जॉब | दायरा | अवरोधक | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | दस्तावेज़/कोड PR; API दस्तावेज़ संदर्भ और सभी दस्तावेज़ | हाँ | -| `Fast Quality Gates` | कोड PR; स्टैटिक जाँचें, टाइपचेक, डैशबोर्ड टाइपचेक, प्रभावित यूनिट परीक्षण | हाँ | -| `Forgotten sibling tests` | कोड PR; परिवर्तित मॉड्यूल से स्टैटिक उपभोक्ताओं और संभावित सिबलिंग परीक्षणों तक ट्रेसिंग; बैरल और डायनेमिक-इम्पोर्ट पाथ को संदर्भित अलाउलिस्ट अपवादों के साथ परामर्शात्मक डायग्नोस्टिक के रूप में रिपोर्ट किया जाता है | **परामर्शात्मक** | -| `Vitest (fast-path)` | कोड PR; तेज़ vitest सुइट | हाँ | -| `Unit Tests fast-path` | कोड PR; 4-शार्ड यूनिट सुइट | हाँ | -| `No new ESLint warnings` | कोड PR; सप्रेशन-जागरूक लिंट गार्ड | हाँ, फ़ोर्क सहित | -| `Merge integrity (changelog + generated skills)` | गैर-ड्राफ़्ट PR; चेंजलॉग और जनरेटेड स्किल सिंक | हाँ, फ़ोर्क सहित | - -#### भूले हुए सिबलिंग परीक्षणों की रिपोर्ट - -`npm run check:forgotten-sibling-tests`, टेस्ट-इम्पैक्ट मैप के पीछे मौजूद इम्पोर्ट रिज़ॉल्वर का पुनः उपयोग करता है। -प्रत्येक परिवर्तित प्रोडक्शन मॉड्यूल के लिए, जब संभावित -परीक्षण पुल-रिक्वेस्ट डिफ़ में अनुपस्थित होता है, तो यह नियतात्मक +पुश, डिस्पैच और merge group पर CI का पूरक है। PR पाथ-फ़िल्टर की गई तेज़ जाँचों का उपयोग करते हैं। स्थायी रूप से +अक्षम डुप्लिकेट बिल्ड हटा दिया गया था; वास्तविक build/package/boot जाँचें CI में बनी हुई हैं। + +| जॉब | दायरा | अवरोधक | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `Docs Gates (fast-path)` | Docs/code PR; API docs संदर्भ और docs-all | हाँ | +| `Fast Quality Gates` | Code PR; स्थैतिक जाँचें, typecheck, dashboard typecheck, प्रभावित unit tests | हाँ | +| `Forgotten sibling tests` | Code PR; बदले हुए मॉड्यूल से स्थैतिक उपभोक्ताओं और संभावित sibling tests तक अनुरेखण; barrel और dynamic-import पाथ को परामर्शात्मक निदान के रूप में रिपोर्ट किया जाता है, साथ ही संदर्भित allowlist अपवाद भी | **परामर्शात्मक** | +| `Vitest (fast-path)` | Code PR; तेज़ vitest suite | हाँ | +| `Unit Tests fast-path` | Code PR; 4-shard unit suite | हाँ | +| `No new ESLint warnings` | Code PR; suppression-aware lint guard | हाँ, फ़ोर्क सहित | +| `Merge integrity (changelog + generated skills)` | गैर-ड्राफ़्ट PR; changelog और जनरेटेड skill sync | हाँ, फ़ोर्क सहित | + +#### भूले हुए sibling tests की रिपोर्ट + +`npm run check:forgotten-sibling-tests`, test-impact map के पीछे मौजूद import resolver का पुनः उपयोग करता है। +प्रत्येक बदले हुए production module के लिए, जब संभावित +test pull-request diff में अनुपस्थित होता है, तो यह नियतात्मक `changed module/symbol -> static consumer -> candidate sibling test` शृंखलाओं की रिपोर्ट करता है। किसी भी अवरोधक रोलआउट से पहले अंशांकन के लिए Markdown सारांश और JSON परिणाम को `forgotten-sibling-tests` वर्कफ़्लो आर्टिफ़ैक्ट के रूप में बनाए रखा जाता है। -बैरल री-एक्सपोर्ट और डायनेमिक इम्पोर्ट केवल रिज़ॉल्यूशन डायग्नोस्टिक्स हैं; वे कभी भी +बैरल री-एक्सपोर्ट और डायनेमिक इंपोर्ट केवल रिज़ॉल्यूशन डायग्नोस्टिक्स हैं; वे कभी भी ब्लॉकिंग निष्कर्ष उत्पन्न नहीं करते। समीक्षा किए गए अपवाद -`config/quality/forgotten-sibling-allowlist.json` में होते हैं। प्रत्येक प्रविष्टि में कंज़्यूमर और उम्मीदवार -टेस्ट का नाम, एक विशिष्ट औचित्य, और किसी GitHub इश्यू या पुल रिक्वेस्ट का लिंक होना आवश्यक है। विकृत प्रविष्टियाँ -डिफ़ॉल्ट रूप से विफल होती हैं। अपवाद किसी हटाए गए उम्मीदवार टेस्ट या `.skip`/`.todo` जोड़ने वाले डिफ़ को दबा नहीं सकते; +`config/quality/forgotten-sibling-allowlist.json` में मौजूद हैं। प्रत्येक प्रविष्टि में उपभोक्ता और उम्मीदवार +टेस्ट का नाम होना चाहिए, एक विशिष्ट औचित्य दिया जाना चाहिए, और GitHub इश्यू या पुल रिक्वेस्ट से लिंक होना चाहिए। विकृत प्रविष्टियाँ +क्लोज़्ड स्थिति में विफल होती हैं। अपवाद किसी हटाए गए उम्मीदवार टेस्ट या `.skip`/`.todo` जोड़ने वाले डिफ़ को दबा नहीं सकते; असर्शन को कमज़ोर करना और अन्य मास्किंग स्वतंत्र रूप से ब्लॉक करने वाले `check:test-masking` गेट के अधीन रहते हैं। @@ -100,131 +100,131 @@ CI का प्रामाणिक स्रोत `.github/workflows/ci.yml` `main` के प्रत्येक PR पर चलता है। विफलता होने पर मर्ज को ब्लॉक करता है। -| स्क्रिप्ट (`npm run ...`) | सत्यापित करता है | ब्लॉकिंग | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Node.js संस्करण समर्थित सीमा के भीतर है | हाँ | -| `check:cycles` | सर्कुलर इम्पोर्ट — सभी `src/` + `open-sse/` मॉड्यूल | हाँ | -| `check:route-validation:t06` | सभी रूट पर Zod स्कीमा मौजूद हैं (टियर 6 नीति) | हाँ | -| `check:any-budget:t11` | `@ts-expect-error // any` की संख्या बजट से अधिक नहीं है (टियर 11 catraca) | हाँ | -| `check:provider-consistency` | `providers.ts` में प्रत्येक प्रदाता की `providerRegistry.ts` में एक संगत प्रविष्टि है (और इसके विपरीत, अनुमति-सूची के भीतर) | हाँ | -| `check:model-lifecycle` | हाथ से अनुरक्षित तीन रूटिंग तालिकाएँ चेक-इन किए गए जीवनचक्र स्नैपशॉट (#11503) के अनुरूप रहती हैं: `FITNESS_TABLE` (`taskFitness.ts`) ऐसे किसी सेवानिवृत्त id को स्कोर नहीं करती जिसे `REGISTRY` रूट कर सकती है; प्रत्येक `BUILT_IN_ALIASES` लक्ष्य `REGISTRY` में मौजूद है और सेवानिवृत्त-id स्नैपशॉट में अनुपस्थित है; `REGISTRY` में अब भी मौजूद प्रत्येक सेवानिवृत्त id को अग्रेषित किया गया है या `allowedRetiredInCatalog` में सूचीबद्ध किया गया है; और कोई भी `DEFAULT_DEGRADATION_MAP` स्रोत या लक्ष्य उस स्नैपशॉट में सेवानिवृत्त नहीं दिखता। इससे यह सिद्ध नहीं होता कि कोई मॉडल वर्तमान में किसी सक्रिय अपस्ट्रीम द्वारा उपलब्ध कराया जा रहा है। ऑफ़लाइन — `config/quality/model-lifecycle.json` से तुलना करता है, जिसे `npm run quality:refresh-model-lifecycle` (नेटवर्क; CI से संबद्ध नहीं) के साथ हाथ से रीफ़्रेश किया जाता है। `allowedRetiredInCatalog` एक क्रमिक कटौती वाला रैचेट है: केवल ट्रैकिंग इश्यू के साथ ही कोई प्रविष्टि जोड़ें। | हाँ | -| `check:fetch-targets` | क्लाइंट-साइड `src/` में प्रत्येक `fetch("/api/...")` किसी वास्तविक `route.ts` पर रिज़ॉल्व होता है | हाँ | -| `check:deps` | रिपॉज़िटरी में प्रत्येक `package.json` की सभी `npm install`-योग्य निर्भरताएँ `dependency-allowlist.json` में हैं; नए अनपिन किए गए या स्लॉपस्क्वाट किए गए पैकेज चिह्नित किए जाते हैं | हाँ | -| `audit:deps` | `npm audit` (रूट + इलेक्ट्रॉन) — कोई उच्च/गंभीर परामर्श नहीं (osv `check:vuln-ratchet` के साथ ओवरलैप करता है; युक्तिकरण बैकलॉग देखें) | हाँ | -| `check:lockfile` | `package-lock.json` की अखंडता — https रजिस्ट्री, अखंडता हैश, कोई होस्ट ओवरराइड नहीं | हाँ | -| `check:licenses` | प्रोडक्शन डिपेंडेंसीज़ के लिए SPDX लाइसेंस अनुमत-सूची | हाँ | -| `check:tracked-artifacts` | कोई बिल्ड आर्टिफ़ैक्ट / कमिट किए गए `node_modules` सिमलिंक नहीं (husky pre-commit में भी चलता है; pre-push को जानबूझकर हल्का रखा गया है — #6716) | हाँ | -| `check:ai-attribution` | PR कमिट्स, शीर्षक या बॉडी में कोई AI/bot `Co-Authored-By` ट्रेलर या AI-जनरेशन फ़ुटर नहीं — कठोर नियम #16 (`quality.yml` के PR→`release/**` फ़ास्ट-गेट्स लूप में — इवेंट पेलोड पढ़ता है, PR के बाहर कोई कार्रवाई नहीं करता — और PR→`main` के लिए `ci.yml` लिंट में केवल-PR चरण; साथ ही husky `commit-msg` हुक; मानव सह-लेखकों की अनुमति है; #14436) | -| `check:vitest-exclusions` | प्रत्येक Vitest अपवर्जन में एक ट्रैकिंग इश्यू का उल्लेख हो और वह `config/quality/vitest-exclusions.json` में मौजूद हो (#13204) | हाँ | -| `check:file-size` | कोई भी सोर्स फ़ाइल प्रति-एक्सटेंशन सीमा से अधिक न हो (रैचेट: `frozen` सूची में फ़्रीज़ की गई बड़ी फ़ाइलें) | हाँ | -| `check:error-helper` | एक्ज़ीक्यूटर्स/हैंडलर्स में त्रुटि प्रतिक्रियाएँ `buildErrorBody()` / `sanitizeErrorMessage()` का उपयोग करती हैं (कठोर नियम #12) | हाँ | -| `check:migration-numbering` | माइग्रेशन SQL फ़ाइलें क्रमिक रूप से क्रमांकित हैं, बिना किसी अंतराल या डुप्लिकेट के | हाँ | -| `check:public-creds` | `publicCreds.ts` के बाहर कोई भी शाब्दिक OAuth `client_id`/`client_secret` या Firebase Web कुंजी नहीं है (कठोर नियम #11) | हाँ | -| `check:db-rules` | `src/lib/db/` मॉड्यूल के बाहर कोई रॉ SQL नहीं है; `localDb.ts` से कोई बैरल-इम्पोर्ट नहीं है (कठोर नियम #2/#5) | हाँ | -| `check:known-symbols` | अपनी डिस्पैच तालिकाओं में पंजीकृत प्रोवाइडर एक्ज़ीक्यूटर, रूटिंग रणनीतियाँ और ट्रांसलेटर डिस्क पर मौजूद फ़ाइलों से मेल खाते हैं—कोई अनाथ या अघोषित सिंबल नहीं है | हाँ | -| `check:route-guard-membership` | चाइल्ड प्रोसेस शुरू करने वाला प्रत्येक रूट `isLocalOnlyPath()` द्वारा वर्गीकृत है (कठोर नियम #15/#17) | हाँ | -| `check:test-discovery` | रिपॉज़िटरी की प्रत्येक `*.test.ts` / `*.spec.ts` फ़ाइल कम-से-कम एक टेस्ट रनर द्वारा एकत्र की जाती है (रैचेट: `test-discovery-baseline.json` में अनाथ फ़ाइलों की सूची केवल छोटी हो सकती है) | हाँ | -| `check:agent-skills-sync` | जनरेट किए गए agent-skills आर्टिफ़ैक्ट उनकी स्रोत कैटलॉग से मेल खाते हैं (कोई विचलन नहीं) | -| `check:provider-asset-provenance` | Provider लोगो/एसेट के साथ एक दर्ज provenance प्रविष्टि होती है | -| `lint:json` | JSON कॉन्फ़िगरेशन फ़ाइलें सफलतापूर्वक पार्स होती हैं और repo के lint नियमों का पालन करती हैं | -| `typecheck:core` | त्रुटियों के बिना TypeScript संकलन (केवल परामर्शात्मक चेतावनियाँ) | हाँ | -| `typecheck:noimplicit:core` | सख़्त `noImplicitAny` — भविष्य-उन्मुख; पहले से मौजूद कई call sites में अब भी annotations की आवश्यकता है | **परामर्शात्मक** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, `src/app/(dashboard)/**` तक सीमित है (#7033) — `typecheck:core` की चयनित 27-फ़ाइल allowlist में कोई dashboard TSX शामिल नहीं है, और `next build` भी कभी इसका type-check नहीं करता (`next.config.mjs` में `ignoreBuildErrors: true` सेट है), इसलिए वहाँ orphaned-identifier regressions (#6625/#6909) CI को दिखाई नहीं देते थे। एक स्थिर, प्रति-फ़ाइल/प्रति-TS-code गणना baseline (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` के समान stale-enforcement पैटर्न) के विरुद्ध अंतर निकाला जाता है — baseline की गई गणना से अधिक केवल नई त्रुटियाँ ही gate को विफल करती हैं; पहले से मौजूद त्रुटि ठीक होने पर `--update` के साथ इसे क्रमशः कम करें। | हाँ | +| स्क्रिप्ट (`npm run ...`) | सत्यापित करता है | ब्लॉकिंग | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Node.js संस्करण समर्थित सीमा के भीतर है | हाँ | +| `check:cycles` | पूरे `src/` + `open-sse/` में सर्कुलर इंपोर्ट (AST-आधारित, tsconfig `paths` रिज़ॉल्व किए गए)। Bare = परामर्शात्मक, साइकल सूचीबद्ध करता है। `check:cycles:ratchet` (जिसे CI चलाता है) तब ब्लॉक करता है जब संख्या `quality-baseline.json` में `metrics.cycles` की अधिकतम सीमा से अधिक हो जाती है — वर्तमान में 14, `direction: down`, इसलिए यह केवल घट सकती है (#15159 G-01/G-02) | हाँ (रैचेट) | +| `check:route-validation:t06` | सभी रूट पर Zod स्कीमा मौजूद हैं (Tier 6 नीति) | हाँ | +| `check:any-budget:t11` | `@ts-expect-error // any` की संख्या बजट से अधिक नहीं है (Tier 11 catraca) | हाँ | +| `check:provider-consistency` | `providers.ts` में प्रत्येक provider की `providerRegistry.ts` में एक संगत entry है (और इसके विपरीत भी, allowlist के भीतर) | हाँ | +| `check:model-lifecycle` | हाथ से अनुरक्षित तीनों routing tables, checked-in lifecycle snapshot (#11503) के साथ संगत रहती हैं: `FITNESS_TABLE` (`taskFitness.ts`) ऐसे किसी retired id को score नहीं करती जिसे `REGISTRY` route कर सकती है; प्रत्येक `BUILT_IN_ALIASES` target `REGISTRY` में मौजूद है और retired-id snapshot में अनुपस्थित है; `REGISTRY` में अब भी मौजूद प्रत्येक retired id को forward किया गया है या `allowedRetiredInCatalog` में सूचीबद्ध किया गया है; और कोई भी `DEFAULT_DEGRADATION_MAP` source या target उस snapshot में retired नहीं दिखता। इससे यह सिद्ध नहीं होता कि कोई model वर्तमान में किसी live upstream द्वारा उपलब्ध कराया जा रहा है। Offline — `config/quality/model-lifecycle.json` के साथ तुलना करता है, जिसे `npm run quality:refresh-model-lifecycle` से हाथ से refresh किया जाता है (network; CI में शामिल नहीं)। `allowedRetiredInCatalog` एक burn-down ratchet है: कोई entry केवल tracking issue के साथ ही जोड़ें। | हाँ | +| `check:fetch-targets` | client-side `src/` में प्रत्येक `fetch("/api/...")` किसी वास्तविक `route.ts` पर resolve होता है | हाँ | +| `check:deps` | repo के प्रत्येक `package.json` में मौजूद सभी `npm install`-योग्य deps `dependency-allowlist.json` में हैं; नए unpinned या slopsquatted packages को flag किया जाता है | हाँ | +| `audit:deps` | `npm audit` (root + electron) — कोई high/critical advisory नहीं (`check:vuln-ratchet` osv के साथ overlap करता है; Rationalization Backlog देखें) | हाँ | +| `check:lockfile` | `package-lock.json` की integrity — https registry, integrity hashes, कोई host override नहीं | हाँ | +| `check:licenses` | प्रोडक्शन डिपेंडेंसीज़ के लिए SPDX लाइसेंस अनुमत-सूची | हाँ | +| `check:tracked-artifacts` | कोई बिल्ड आर्टिफ़ैक्ट / कमिट किए गए `node_modules` सिमलिंक नहीं (husky pre-commit में भी चलता है; pre-push को जानबूझकर हल्का रखा गया है — #6716) | हाँ | +| `check:ai-attribution` | PR कमिट्स, शीर्षक या बॉडी में कोई AI/bot `Co-Authored-By` ट्रेलर या AI-जेनरेशन फ़ुटर नहीं — कठोर नियम #16 (`quality.yml` के PR→`release/**` फ़ास्ट-गेट्स लूप में — इवेंट पेलोड पढ़ता है, PR के बाहर कोई कार्रवाई नहीं करता — और PR→`main` के लिए `ci.yml` लिंट में एक केवल-PR चरण; साथ ही husky `commit-msg` हुक; मानव सह-लेखकों की अनुमति है; #14436) | +| `check:vitest-exclusions` | प्रत्येक Vitest अपवर्जन में किसी ट्रैकिंग इश्यू का उल्लेख होता है और वह `config/quality/vitest-exclusions.json` में मौजूद होता है (#13204) | हाँ | +| `check:file-size` | कोई भी स्रोत फ़ाइल प्रति-एक्सटेंशन सीमा से अधिक नहीं होती (रैचेट: `frozen` सूची में फ़्रीज़ की गई बड़ी फ़ाइलें) | हाँ | +| `check:error-helper` | एक्ज़ीक्यूटर/हैंडलर में त्रुटि प्रतिक्रियाएँ `buildErrorBody()` / `sanitizeErrorMessage()` का उपयोग करती हैं (कठोर नियम #12) | हाँ | +| `check:migration-numbering` | Migration SQL फ़ाइलें क्रमिक रूप से क्रमांकित हैं, जिनमें कोई अंतराल या डुप्लिकेट नहीं है | हाँ | +| `check:public-creds` | `publicCreds.ts` के बाहर कोई शाब्दिक OAuth `client_id`/`client_secret` या Firebase Web कुंजी नहीं है (कठोर नियम #11) | हाँ | +| `check:db-rules` | `src/lib/db/` मॉड्यूल के बाहर कोई रॉ SQL नहीं है; `localDb.ts` से कोई बैरल-इम्पोर्ट नहीं है (कठोर नियम #2/#5) | हाँ | +| `check:known-symbols` | अपनी डिस्पैच तालिकाओं में पंजीकृत प्रदाता एक्ज़ीक्यूटर, रूटिंग रणनीतियाँ और ट्रांसलेटर डिस्क पर मौजूद फ़ाइलों से मेल खाते हैं—कोई अनाथ या अघोषित सिंबल नहीं है | हाँ | +| `check:route-guard-membership` | चाइल्ड प्रोसेस आरंभ करने वाला प्रत्येक रूट `isLocalOnlyPath()` द्वारा वर्गीकृत है (कठोर नियम #15/#17) | हाँ | +| `check:test-discovery` | रिपॉज़िटरी की प्रत्येक `*.test.ts` / `*.spec.ts` फ़ाइल कम-से-कम एक टेस्ट रनर द्वारा एकत्र की जाती है (रैचेट: `test-discovery-baseline.json` में अनाथ सूची केवल छोटी हो सकती है) | हाँ | +| `check:agent-skills-sync` | जनरेट किए गए agent-skills आर्टिफ़ैक्ट अपनी स्रोत कैटलॉग से मेल खाते हैं (कोई अंतर नहीं) | +| `check:provider-asset-provenance` | Provider लोगो/एसेट के लिए दर्ज provenance प्रविष्टि मौजूद है | +| `lint:json` | JSON कॉन्फ़िग फ़ाइलें सफलतापूर्वक पार्स होती हैं और repo के lint नियमों को पूरा करती हैं | +| `typecheck:core` | बिना त्रुटियों के TypeScript कंपाइलेशन (केवल परामर्शात्मक चेतावनियाँ) | हाँ | +| `typecheck:noimplicit:core` | सख़्त `noImplicitAny` — भविष्य-उन्मुख; पहले से मौजूद कई call sites में अभी भी annotations की आवश्यकता है | **परामर्शात्मक** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, `src/app/(dashboard)/**` तक सीमित है (#7033) — `typecheck:core` की चयनित 27-फ़ाइल allowlist में कोई dashboard TSX शामिल नहीं है, और `next build` भी कभी इसका type-check नहीं करता (`next.config.mjs` में `ignoreBuildErrors: true` सेट है), इसलिए वहाँ orphaned-identifier regressions (#6625/#6909) CI को दिखाई नहीं देते थे। एक स्थिर की गई प्रति-फ़ाइल/प्रति-TS-code गणना baseline (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` के समान stale-enforcement पैटर्न) के विरुद्ध अंतर की जाँच करता है — baseline गणना से परे केवल नई त्रुटियाँ ही gate को विफल करती हैं; पहले से मौजूद त्रुटि ठीक होने पर `--update` के साथ सीमा घटाएँ। | हाँ | ### Job: `quality-gate` `test-coverage` के बाद चलता है। विफलता होने पर merge को रोकता है। -| स्क्रिप्ट | सत्यापित करता है | अवरोधक | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -| `quality:collect` | `quality-metrics.json` उत्सर्जित करता है (ESLint चेतावनियों की संख्या, मर्ज की गई शार्ड रिपोर्ट से कवरेज) | हाँ (रैचेट से पहले) | -| `quality:ratchet` | `quality-baseline.json` में प्रत्येक मेट्रिक में गिरावट नहीं हुई है (ESLint चेतावनियाँ ≤ बेसलाइन; कवरेज ≥ बेसलाइन) | हाँ | -| `check:duplication` | कोड दोहराव (jscpd@4) `quality-baseline.json` में निर्धारित बेसलाइन से अधिक नहीं है | हाँ | -| `check:complexity` | फ़ाइल-स्तरीय साइक्लोमैटिक जटिलता निर्धारित सीमा से अधिक नहीं है (कोर ESLint `complexity` + `max-lines-per-function`) | हाँ | -| `check:cognitive-complexity` | संज्ञानात्मक जटिलता रैचेट (`eslint-plugin-sonarjs`) — अलग ESLint पास; CI दोनों को एकल `check:complexity-ratchets` चरण के रूप में मर्ज करके चलाता है | हाँ | -| `check:dead-code` | अप्रयुक्त एक्सपोर्ट / फ़ाइलों का रैचेट (knip) बेसलाइन की तुलना में खराब नहीं होता | हाँ | -| `check:compression-budget` | कम्प्रेशन बेंचमार्क बजट — प्रत्येक इंजन के लिए टोकन-बचत की न्यूनतम सीमाओं में गिरावट नहीं होनी चाहिए | हाँ | -| `check:type-coverage` | टाइप किए गए प्रतिशत का रैचेट (`type-coverage`) खराब नहीं होता; यह मोटे तौर पर `typecheck:noimplicit:core` को समाहित करता है | हाँ | -| `check:codeql-ratchet` | खुले CodeQL अलर्ट की संख्या में वृद्धि नहीं होती (`gh api` के माध्यम से पढ़ता है; टोकन के बिना सुचारु रूप से छोड़ देता है) — रीफ़्रेश आवृत्ति और मैन्युअल ट्रिगर के लिए: नीचे "CodeQL रैचेट" देखें | हाँ | +| स्क्रिप्ट | सत्यापित करता है | अवरोधक | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `quality:collect` | `quality-metrics.json` उत्सर्जित करता है (ESLint चेतावनी संख्या, मर्ज की गई शार्ड रिपोर्ट से कवरेज) | हाँ (रैचेट से पहले) | +| `quality:ratchet` | `quality-baseline.json` में प्रत्येक मेट्रिक में गिरावट नहीं हुई है (ESLint चेतावनियाँ ≤ बेसलाइन; कवरेज ≥ बेसलाइन) | हाँ | +| `check:duplication` | कोड दोहराव (jscpd@4), `quality-baseline.json` में निर्धारित बेसलाइन से अधिक नहीं है | हाँ | +| `check:complexity` | फ़ाइल-स्तरीय साइक्लोमैटिक जटिलता निर्धारित सीमा से अधिक नहीं है (कोर ESLint `complexity` + `max-lines-per-function`) | हाँ | +| `check:cognitive-complexity` | संज्ञानात्मक जटिलता रैचेट (`eslint-plugin-sonarjs`) — अलग ESLint पास; CI दोनों को एकल `check:complexity-ratchets` चरण के रूप में मर्ज करके चलाता है | हाँ | +| `check:dead-code` | अप्रयुक्त एक्सपोर्ट / फ़ाइलों का रैचेट (knip), बेसलाइन की तुलना में खराब नहीं होता | हाँ | +| `check:compression-budget` | संपीड़न बेंचमार्क बजट — प्रति-इंजन टोकन-बचत की न्यूनतम सीमाओं में गिरावट नहीं होनी चाहिए | हाँ | +| `check:type-coverage` | टाइप किए गए प्रतिशत का रैचेट (`type-coverage`) खराब नहीं होता; यह बड़े पैमाने पर `typecheck:noimplicit:core` को समाहित करता है | हाँ | +| `check:codeql-ratchet` | खुले CodeQL अलर्ट की संख्या में बढ़ोतरी नहीं होती (`gh api` के माध्यम से पढ़ता है; टोकन न होने पर सुचारु रूप से छोड़ देता है) — रीफ़्रेश आवृत्ति और मैन्युअल ट्रिगर: नीचे "CodeQL रैचेट" देखें | हाँ | ### जॉब: `quality-extended` पूरा जॉब परामर्शात्मक है (`continue-on-error: true`)। npm-आधारित रैचेट वास्तव में चलते हैं; बाहरी स्कैनर `gh release download` के माध्यम से इंस्टॉल होते हैं और बाइनरी -अभी भी अनुपस्थित होने पर स्वयं छोड़ देते हैं (exit 0)। +अब भी अनुपस्थित होने पर स्वयं को छोड़ देते हैं (exit 0)। -| स्क्रिप्ट | सत्यापित करता है | अवरोधक स्थिति | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | -| `check:circular-deps` | कोई सर्कुलर डिपेंडेंसी नहीं है (dpdm) | **परामर्शात्मक** | -| `check:bundle-size` | बंडल का आकार निर्धारित सीमा से अधिक नहीं है | **परामर्शात्मक** | -| `check:secrets` | सीक्रेट स्कैनिंग (gitleaks) — बाइनरी अनुपस्थित होने पर छोड़ देता है | **परामर्शात्मक** | -| `check:vuln-ratchet` | डिपेंडेंसी कमज़ोरियाँ (osv-scanner) नहीं बढ़तीं — बाइनरी अनुपस्थित होने पर छोड़ देता है | **परामर्शात्मक** | -| `check:workflows` | वर्कफ़्लो लिंट (actionlint + zizmor); अनुपस्थित/खराब स्कैनर, अमान्य रिपोर्ट या अनुपस्थित रैचेट बेसलाइन को INCOMPLETE मानकर विफल करता है। मान्य निष्कर्ष चयनित सख्त/परामर्शात्मक/रैचेट नीति का पालन करते हैं | निष्पादन आवश्यक; CI में zizmor रैचेट अवरोधक है | -| `check:openapi-breaking` | बेस ब्रांच की तुलना में सार्वजनिक API अनुबंध (`openapi.yaml`) में ब्रेकिंग परिवर्तन (oasdiff) — `openapiBreaking=N` उत्सर्जित करता है; oasdiff अनुपस्थित होने या बेस स्पेसिफ़िकेशन हल न हो पाने पर छोड़ देता है | **परामर्शात्मक** | +| स्क्रिप्ट | सत्यापित करता है | अवरोधक | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:circular-deps` | कोई चक्रीय निर्भरता नहीं है (dpdm) | **परामर्शात्मक** | +| `check:bundle-size` | बंडल का आकार निर्धारित सीमा से अधिक नहीं है | **परामर्शात्मक** | +| `check:secrets` | सीक्रेट स्कैनिंग (gitleaks) — बाइनरी अनुपस्थित होने पर छोड़ देता है | **परामर्शात्मक** | +| `check:vuln-ratchet` | निर्भरता संबंधी कमजोरियाँ (osv-scanner) खराब नहीं होतीं — बाइनरी अनुपस्थित होने पर छोड़ देता है | **परामर्शात्मक** | +| `check:workflows` | वर्कफ़्लो लिंट (actionlint + zizmor); अनुपस्थित/खराब स्कैनर, अमान्य रिपोर्ट या अनुपस्थित रैचेट बेसलाइन INCOMPLETE के रूप में विफल होते हैं। मान्य निष्कर्ष चयनित सख्त/परामर्शात्मक/रैचेट नीति का पालन करते हैं | निष्पादन आवश्यक; CI में zizmor रैचेट अवरोधक है | +| `check:openapi-breaking` | बेस ब्रांच की तुलना में सार्वजनिक API अनुबंध (`openapi.yaml`) में ब्रेकिंग बदलाव (oasdiff) — `openapiBreaking=N` उत्सर्जित करता है; oasdiff अनुपस्थित होने या बेस स्पेक का समाधान न हो पाने पर छोड़ देता है | **परामर्शात्मक** | ### जॉब: `docs-sync-strict` -`main` की प्रत्येक PR पर चलता है। विफलता होने पर मर्ज को अवरुद्ध करता है। +`main` के प्रत्येक PR पर चलता है। विफल होने पर मर्ज को रोकता है। -| स्क्रिप्ट | सत्यापित करता है | अवरोधक | +| Script | सत्यापित करता है | अवरोधक | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | | `check:docs-all` | मेटा-गेट, जो नीचे दिए गए 6 उप-गेट क्रमिक रूप से चलाता है | हाँ | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt संस्करण संगतता | हाँ | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt संस्करण की संगति | हाँ | | ↳ `check:docs-counts` | गद्य में दी गई संख्याएँ (प्रदाता संख्या, माइग्रेशन संख्या आदि) वास्तविक संख्याओं की रैचेट विंडो के भीतर हैं | हाँ | -| ↳ `check:env-doc-sync` | `.env.example` में मौजूद प्रत्येक env var किसी दस्तावेज़ तालिका में प्रलेखित है, और इसके विपरीत भी | हाँ | -| ↳ `check:deprecated-versions` | दस्तावेज़ों में कोई अप्रचलित संस्करण स्ट्रिंग नहीं है | हाँ | -| ↳ `check:doc-links` | दस्तावेज़ों में आंतरिक markdown लिंक वास्तविक फ़ाइलों पर रिज़ॉल्व होते हैं (`[text]`/`(path)` प्रारूप) | हाँ | -| ↳ `check:fabricated-docs` | दस्तावेज़ों में उल्लिखित रूट, env vars, CLI कमांड, हुक नाम और फ़ाइल पथ कोडबेस में मौजूद हैं। `--strict` के माध्यम से हार्ड गेट; फ़्लैग के बिना सॉफ्ट-फ़ेल। | हाँ (CI में `--strict` के माध्यम से) | -| `check:cli-i18n` | CLI कमांड स्ट्रिंग सभी i18n लोकेल फ़ाइलों में मौजूद हैं | हाँ | -| `check:openapi-coverage` | OpenAPI विनिर्देश वास्तविक रूट के कम-से-कम रैचेट किए गए न्यूनतम स्तर को कवर करता है | हाँ | +| ↳ `check:env-doc-sync` | `.env.example` का प्रत्येक env var दस्तावेज़ीकरण तालिका में दर्ज है, और इसके विपरीत भी | हाँ | +| ↳ `check:deprecated-versions` | दस्तावेज़ों में कोई बहिष्कृत संस्करण स्ट्रिंग नहीं है | हाँ | +| ↳ `check:doc-links` | दस्तावेज़ों के आंतरिक markdown लिंक वास्तविक फ़ाइलों तक पहुँचते हैं (`[text]`/`(path)` प्रारूप) | हाँ | +| ↳ `check:fabricated-docs` | दस्तावेज़ों में उल्लिखित रूट, env var, CLI कमांड, हुक नाम और फ़ाइल पथ codebase में मौजूद हैं। `--strict` के माध्यम से हार्ड गेट; फ़्लैग के बिना सॉफ्ट-फेल। | हाँ (CI में `--strict` के माध्यम से) | +| `check:cli-i18n` | CLI कमांड स्ट्रिंग सभी i18n locale फ़ाइलों में मौजूद हैं | हाँ | +| `check:openapi-coverage` | OpenAPI spec वास्तविक रूट की कम-से-कम रैचेट की गई न्यूनतम सीमा को कवर करता है | हाँ | | `check:openapi-security-tiers` | `openapi.yaml` में सुरक्षा स्तर एनोटेशन `routeGuard.ts` वर्गीकरणों के अनुरूप हैं | **परामर्शात्मक** | -| `check:openapi-routes` | `openapi.yaml` का प्रत्येक पथ वास्तविक `route.ts` पर रिज़ॉल्व होता है (भ्रम-रोधी) | हाँ | -| `check:docs-symbols` | `docs/**/*.md` में प्रत्येक `/api/...` संदर्भ वास्तविक `route.ts` पर रिज़ॉल्व होता है (भ्रम-रोधी) | हाँ | -| `i18n translation drift` | i18n लोकेल फ़ाइलों में अनूदित न की गई कुंजियाँ — केवल चेतावनी | **परामर्शात्मक** | +| `check:openapi-routes` | `openapi.yaml` का प्रत्येक पथ वास्तविक `route.ts` तक पहुँचता है (भ्रम-रोधी) | हाँ | +| `check:docs-symbols` | `docs/**/*.md` का प्रत्येक `/api/...` संदर्भ वास्तविक `route.ts` तक पहुँचता है (भ्रम-रोधी) | हाँ | +| `i18n translation drift` | i18n locale फ़ाइलों में अनूदित न की गई keys — केवल चेतावनी | **परामर्शात्मक** | ### जॉब: `i18n-ui-coverage` -| स्क्रिप्ट | सत्यापित करता है | अवरोधक | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (इनलाइन) | UI i18n कुंजी कवरेज ≥ 65% है | हाँ | -| `check-ui-value-drift` (इनलाइन) | दोबारा लिखा गया अंग्रेज़ी **मान** किसी पुराने अनुवाद को पीछे नहीं छोड़ता | हाँ | -| `check-new-key-coverage` (इनलाइन) | एक **नई** अंग्रेज़ी कुंजी प्रत्येक लोकेल में अनूदित है — `__MISSING__:` मार्कर अस्वीकार कर दिया जाता है | हाँ | -| `check-translation-ratio` | प्रति लोकेल वास्तविक-अनुवाद अनुपात (अनुमति-सूची से बाहर अंग्रेज़ी के समान / प्लेसहोल्डर / अनुपस्थित लीफ़) `config/quality/i18n-translation-baseline.json` + स्लैक से अधिक नहीं होना चाहिए | **परामर्शात्मक** | +| Script | सत्यापित करता है | अवरोधक | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | +| `check-ui-keys-coverage` (इनलाइन) | UI i18n key कवरेज ≥ 65% है | हाँ | +| `check-ui-value-drift` (इनलाइन) | दोबारा लिखा गया अंग्रेज़ी **value** कोई पुराना अनुवाद पीछे नहीं छोड़ता | हाँ | +| `check-new-key-coverage` (इनलाइन) | एक **नई** अंग्रेज़ी key प्रत्येक locale में अनूदित है — `__MISSING__:` मार्कर अस्वीकार किया जाता है | हाँ | +| `check-translation-ratio` | प्रति locale वास्तविक-अनुवाद अनुपात (allowlist के बाहर अंग्रेज़ी के समान / placeholder / अनुपस्थित leaves) `config/quality/i18n-translation-baseline.json` + slack से अधिक नहीं होना चाहिए | **परामर्शात्मक** | -`fetch-depth: 0` आवश्यक है — मान-ड्रिफ़्ट गेट मर्ज बेस के विरुद्ध `en.json` का अंतर निकालता है। +`fetch-depth: 0` आवश्यक है — value-drift गेट merge base की तुलना में `en.json` का diff करता है। -#### `check-ui-value-drift` — पुराना-अनुवाद गेट +#### `check-ui-value-drift` — पुराने अनुवाद का गेट -यह उस एक i18n रिग्रेशन को पकड़ता है जिसे अन्य गेट संरचनात्मक रूप से नहीं देख सकते: किसी अंग्रेज़ी मान को -दोबारा लिखा जाता है और _पिछले_ अंग्रेज़ी मान से बने अनुवाद पीछे रह जाते हैं, जिससे -गैर-अंग्रेज़ी उपयोगकर्ता आत्मविश्वासपूर्ण शब्दों में लिखी, लेकिन अब गलत सामग्री पढ़ते रहते हैं। +यह उस एक i18n रिग्रेशन को पकड़ता है जिसे अन्य गेट संरचनात्मक रूप से नहीं देख सकते: किसी अंग्रेज़ी value को +दोबारा लिखा जाता है और _पिछले_ अंग्रेज़ी पाठ से बनाए गए अनुवाद पीछे रह जाते हैं, जिससे +गैर-अंग्रेज़ी उपयोगकर्ता आत्मविश्वासपूर्ण शैली में लिखी, लेकिन अब गलत जानकारी पढ़ते रहते हैं। -यह वास्तव में रिलीज़ हो चुका था। Antigravity लॉगिन सहायक जोड़े जाने पर (#5203) -`oauthModal.googleOAuthWarning` को दोबारा लिखा गया था; **43 में से 39 लोकेल** में वह पाठ बना रहा जो ऑपरेटरों को -"पूरा URL कॉपी करके नीचे पेस्ट करने" के लिए कहता था — ऐसी प्रक्रिया जो उस प्रदाता के लिए पूरी नहीं हो सकती। -#8463 तक इस पर ध्यान नहीं गया, क्योंकि: +यह वास्तव में रिलीज़ हो चुका है। Antigravity लॉगिन हेल्पर जोड़े जाने (#5203) पर `oauthModal.googleOAuthWarning` +को दोबारा लिखा गया था; **43 में से 39 locales** में ऑपरेटरों को "पूरा URL कॉपी करके नीचे पेस्ट करें" बताने वाला +पाठ बना रहा — ऐसा प्रवाह जो उस प्रदाता के लिए पूरा नहीं हो सकता। #8463 तक इस पर +ध्यान नहीं गया, क्योंकि: -- `sync-ui-keys` केवल उन कुंजियों को बैकफ़िल करता है जो **अनुपस्थित** हैं, **पुरानी** कुंजियों को कभी नहीं; -- `check-ui-keys-coverage` कुंजी की _उपस्थिति_ गिनता है, इसलिए पुराने अनुवाद को भी कवर किया हुआ माना जाता है; -- `check-translation-drift` `docs/i18n//**.md` दस्तावेज़ीकरण मिरर ट्रैक करता है — - यह कभी भी `src/i18n/messages/*.json` नहीं पढ़ता। 2026-09 के पुनः-सिंक के बाद से जॉब `docs-sync-strict` में अवरोधक: - कोई मुख्य दस्तावेज़ संपादित करें → `npm run i18n:run -- --files=` (अनुभाग-स्तरीय, कम खर्चीला)। +- `sync-ui-keys` केवल **अनुपस्थित** keys को backfill करता है, **पुरानी** keys को कभी नहीं; +- `check-ui-keys-coverage` key की _उपस्थिति_ गिनता है, इसलिए पुराने अनुवाद को कवर किया हुआ माना जाता है; +- `check-translation-drift` `docs/i18n//**.md` दस्तावेज़ीकरण mirrors को ट्रैक करता है — + यह `src/i18n/messages/*.json` को कभी नहीं पढ़ता। 2026-09 re-sync से जॉब `docs-sync-strict` में अवरोधक: + किसी मुख्य दस्तावेज़ को संपादित करें → `npm run i18n:run -- --files=` (अनुभाग-स्तरीय, कम खर्चीला)। -**डिफ़-अवेयर, बेसलाइन-समर्थित नहीं।** यह मर्ज बेस पर मौजूद `en.json` की तुलना -वर्किंग ट्री से करता है; ऐसी प्रत्येक कुंजी के लिए जिसका अंग्रेज़ी मान बदला है, कोई भी लोकेल जिसमें अभी भी -अपरिवर्तित अनुवाद मौजूद है, पुराना माना जाता है। यह जानबूझकर **पहले से मौजूद ऋण को स्थिर रखता है** — कोई डिफ़ -यह नहीं बता सकता कि लंबे समय से मौजूद अनुवाद किस पुराने अंग्रेज़ी पाठ से आया था, इसलिए गेट केवल -वर्तमान बदलाव द्वारा प्रभावित हिस्से का ही आकलन करता है। विकल्प (प्रति-कुंजी हैश बेसलाइन) के लिए -लगभग 600 KB की जनरेट की गई फ़ाइल की आवश्यकता होगी, जो सबसे बड़ी मौजूदा बेसलाइन से 3× बड़ी होगी और प्रत्येक i18n PR पर बदलेगी। +**डिफ़-सजग, बेसलाइन-समर्थित नहीं।** यह मर्ज बेस पर मौजूद `en.json` की तुलना +वर्किंग ट्री से करता है; जिन कुंजियों का अंग्रेज़ी मान बदला है, उनके लिए अब भी +अपरिवर्तित अनुवाद रखने वाला कोई भी लोकेल पुराना माना जाता है। यह जानबूझकर **पहले से मौजूद ऋण को स्थिर रखता है** — कोई डिफ़ +यह नहीं बता सकता कि लंबे समय से मौजूद अनुवाद किस पुराने अंग्रेज़ी पाठ से आया था, इसलिए गेट +केवल वर्तमान बदलाव द्वारा प्रभावित चीज़ों का आकलन करता है। विकल्प (प्रति-कुंजी हैश बेसलाइन) के लिए +लगभग 600 KB की जनरेट की गई फ़ाइल चाहिए होगी, जो सबसे बड़ी मौजूदा बेसलाइन से 3× बड़ी है और प्रत्येक i18n PR पर बदलती रहेगी। इसे संतुष्ट करने के दो तरीके हैं: 1. प्रभावित अनुवादों को अपडेट करें, या -2. उन्हें `__MISSING__:` पर सेट करें — इसके बाद रनटाइम संशोधित अंग्रेज़ी पाठ प्रदान करता है - (`src/i18n/request.ts::deepMergeFallback`, #7258) और कुंजी अनुवाद की कतार में चली जाती है। +2. उन्हें `__MISSING__:` पर सेट करें — इसके बाद रनटाइम संशोधित अंग्रेज़ी + (`src/i18n/request.ts::deepMergeFallback`, #7258) प्रदान करता है और कुंजी अनुवाद के लिए कतारबद्ध हो जाती है। -यदि स्ट्रिंग का **अर्थ** बदल गया है, तो **कुंजी का नाम बदलना** बेहतर है: नई कुंजी को -पुराना अनुवाद विरासत में नहीं मिल सकता। #8463 में इसी पैटर्न का उपयोग किया गया था। +यदि स्ट्रिंग का **अर्थ** बदल गया है, तो **कुंजी का नाम बदलना** बेहतर है: नई कुंजी +पुराना अनुवाद विरासत में नहीं ले सकती। #8463 में इसी पैटर्न का उपयोग किया गया था। ```bash npm run i18n:check-value-drift # सख़्त (CI यही चलाता है) @@ -232,48 +232,48 @@ npm run i18n:check-value-drift:warn # केवल रिपोर्ट BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -जब आधार कैटलॉग को पढ़ा नहीं जा सकता (बेस रेफ़ के बिना शैलो -क्लोन), तब यह `SKIP reason=base-unresolved` के साथ 0 पर समाप्त होता है, जो `check-openapi-breaking` के व्यवहार को दर्शाता है। +जब बेस कैटलॉग को पढ़ा नहीं जा सकता (बेस रेफ़ के बिना शैलो +क्लोन), तब `SKIP reason=base-unresolved` के साथ 0 पर समाप्त होता है, जो `check-openapi-breaking` के व्यवहार को प्रतिबिंबित करता है। ### जॉब: `i18n` -पूर्ण i18n सत्यापन मैट्रिक्स (प्रति लोकेल एक जॉब)। पूरा जॉब परामर्शात्मक है। +पूर्ण i18n सत्यापन मैट्रिक्स (प्रत्येक लोकेल के लिए एक जॉब)। पूरा जॉब परामर्शात्मक है। -| स्क्रिप्ट | सत्यापन करता है | अवरोधक | -| ------------------------------- | ----------------------------- | -------------------------------------------------------- | -| `validate_translation.py quick` | प्रति लोकेल अनुवाद की पूर्णता | **परामर्शात्मक** (पूरे जॉब पर `continue-on-error: true`) | +| स्क्रिप्ट | सत्यापित करता है | अवरोधक | +| ------------------------------- | --------------------------------------- | -------------------------------------------------------- | +| `validate_translation.py quick` | प्रत्येक लोकेल के लिए अनुवाद की पूर्णता | **परामर्शात्मक** (पूरे जॉब पर `continue-on-error: true`) | ### जॉब: `pr-test-policy` केवल पुल रिक्वेस्ट पर चलता है। -| स्क्रिप्ट | सत्यापन करता है | अवरोधक | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------ | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, या `bin/` में प्रोडक्शन कोड बदलने वाले PR में टेस्ट शामिल या अपडेट होने चाहिए (कठोर नियम #8) | हाँ | -| `check:test-masking` | बदली गई टेस्ट फ़ाइलें कुल assert संख्या को कम नहीं करतीं या `assert.ok(true)` जैसी स्वतःसिद्ध अभिव्यक्तियाँ नहीं जोड़तीं | हाँ | -| `check:pr-evidence` | PR विवरण में बदलाव के लिए टेस्ट/VPS साक्ष्य दिया गया है (PR गद्य में खोज करके कठोर नियम #18 को स्वचालित करता है — नाज़ुक, Backlog देखें) | हाँ | +| स्क्रिप्ट | सत्यापित करता है | अवरोधक | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, या `bin/` में प्रोडक्शन कोड बदलने वाले PR में टेस्ट शामिल या अपडेट होने चाहिए (कठोर नियम #8) | हाँ | +| `check:test-masking` | बदली गई टेस्ट फ़ाइलें कुल assert संख्या कम नहीं करतीं या `assert.ok(true)` जैसी पुनरुक्तियाँ नहीं जोड़तीं | हाँ | +| `check:pr-evidence` | PR बॉडी बदलाव के लिए टेस्ट/VPS साक्ष्य उद्धृत करती है (PR गद्य को grep करके कठोर नियम #18 को स्वचालित बनाता है — नाज़ुक, Backlog देखें) | हाँ | ### जॉब: `test-vitest` -`build` के बाद चलता है। विफल होने पर मर्ज को अवरुद्ध करता है। +`build` के बाद चलता है। विफलता पर मर्ज को रोकता है। -| सुइट | सत्यापन करता है | अवरोधक | -| ---------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP सर्वर (110 टूल), autoCombo, कैश — vitest रनर | हाँ | -| `test:vitest:ui` | UI कंपोनेंट टेस्ट — vitest रनर | **अवरोधक** — पहले से मौजूद विफलताओं को `vitest.config.ts` में स्पष्ट रूप से बाहर रखा गया है; नई विफलताएँ जॉब को विफल कर देती हैं | +| सुइट | सत्यापित करता है | अवरोधक | +| ---------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP सर्वर (110 टूल), autoCombo, कैश — vitest रनर | हाँ | +| `test:vitest:ui` | UI कॉम्पोनेंट टेस्ट — vitest रनर | **अवरोधक** — पहले से मौजूद विफलताओं को `vitest.config.ts` में स्पष्ट रूप से बाहर रखा गया है; नई विफलताएँ जॉब को विफल करती हैं | ### रात्रिकालीन वर्कफ़्लो (शेड्यूल किए गए, परामर्शात्मक) -ये cron शेड्यूल पर (और `workflow_dispatch` के माध्यम से) चलते हैं, PR पर कभी नहीं। सभी परामर्शात्मक हैं। +ये cron शेड्यूल पर (और `workflow_dispatch` से) चलते हैं, PR पर कभी नहीं। सभी परामर्शात्मक हैं। -| वर्कफ़्लो | सत्यापन करता है | अवरोधक | +| वर्कफ़्लो | सत्यापित करता है | अवरोधक | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | यादृच्छिक seed और उच्च run count के साथ fast-check प्रॉपर्टी टेस्ट | **परामर्शात्मक** | -| `nightly-resilience` | heap-growth गेट, chaos fault-injection, k6 load/soak | **परामर्शात्मक** | -| `nightly-llm-security` | promptfoo injection guard (block मोड) + garak probes (प्रदाता secret के बिना छोड़ दिए जाते हैं) | **परामर्शात्मक** | -| `nightly-schemathesis` | `docs/openapi.yaml` का उपयोग करके लाइव OmniRoute के विरुद्ध OpenAPI कॉन्ट्रैक्ट फ़ज़िंग (schemathesis) — स्पेक उल्लंघनों / अनहैंडल्ड 500s को उजागर करता है (चरण 8 B.4) | **परामर्शात्मक** | -| `nightly-mutation` | तेज़ यूनिट लेन पर Stryker mutation-testing स्कोर — बचे हुए mutants कमज़ोर asserts को उजागर करते हैं | **परामर्शात्मक** | -| `nightly-compat` | समर्थित `engines.node` रेंज में Node engine संगतता मैट्रिक्स | **परामर्शात्मक** | +| `nightly-property` | रैंडम सीड और उच्च रन संख्या वाले fast-check प्रॉपर्टी टेस्ट | **परामर्शात्मक** | +| `nightly-resilience` | हीप-वृद्धि गेट, केऑस फ़ॉल्ट-इंजेक्शन, k6 लोड/सोक | **परामर्शात्मक** | +| `nightly-llm-security` | promptfoo इंजेक्शन गार्ड (ब्लॉक मोड) + garak प्रोब (प्रोवाइडर सीक्रेट के बिना छोड़ दिए जाते हैं) | **परामर्शात्मक** | +| `nightly-schemathesis` | `docs/openapi.yaml` का उपयोग करके लाइव OmniRoute के विरुद्ध OpenAPI कॉन्ट्रैक्ट फ़ज़िंग (schemathesis) — स्पेक उल्लंघनों / अनहैंडल्ड 500 को उजागर करता है (Fase 8 B.4) | **परामर्शात्मक** | +| `nightly-mutation` | तेज़ यूनिट लेन पर Stryker म्यूटेशन-टेस्टिंग स्कोर — जीवित बचे म्यूटेंट कमज़ोर assert को उजागर करते हैं | **परामर्शात्मक** | +| `nightly-compat` | समर्थित `engines.node` रेंज में Node इंजन संगतता मैट्रिक्स | **परामर्शात्मक** | --- @@ -536,43 +536,43 @@ CI गेट से आगे, OmniRoute एक **ऑप्ट-इन** `agent-l --- -## युक्तिसंगतीकरण बैकलॉग (ROI समीक्षा — चरण 9 लहर 3) +## युक्तिसंगतीकरण बैकलॉग (ROI समीक्षा — चरण 9 तरंग 3) -इस इन्वेंटरी का `ci.yml` के साथ 2026-06-17 को मिलान किया गया था (पिछले संस्करण में +इस इन्वेंट्री का मिलान 2026-06-17 को `ci.yml` से किया गया था (पिछले संस्करण में `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence` छूट गए थे)। मिलान किए गए सेट की ROI समीक्षा में निम्नलिखित युक्तिसंगतीकरण उम्मीदवारों की पहचान हुई। **मर्ज यांत्रिक CI -परिवर्तन हैं; फ्लिप/ड्रॉप ऑपरेटर के लिए आरक्षित नीतिगत निर्णय हैं।** नीचे दिया गया कुछ भी -अभी लागू नहीं किया गया है। +परिवर्तन हैं; फ्लिप/हटाने के निर्णय ऑपरेटर के लिए आरक्षित नीतिगत निर्णय हैं।** नीचे दिया गया +कुछ भी अभी लागू नहीं किया गया है। -**ऊपर इसका भी दस्तावेज़ीकरण नहीं किया गया है** (परामर्शात्मक, कम संकेत): `docs-lint` जॉब +**ऊपर ये भी प्रलेखित नहीं हैं** (परामर्शी, कम संकेत): `docs-lint` जॉब (markdownlint + Vale, पूरे जॉब पर `continue-on-error`) और स्वतंत्र स्कैनर वर्कफ़्लो `semgrep.yml` / `codeql.yml` / `scorecard.yml`। `semgrepFindings: 0` -`quality-baseline.json` में है, लेकिन `ci.yml` में इसे किसी ब्लॉकिंग रैचेट से नहीं जोड़ा गया है — यह मेट्रिक +`quality-baseline.json` में है, लेकिन `ci.yml` में किसी अवरोधक रैचेट से जुड़ा नहीं है — यह मेट्रिक फ़िलहाल अनाथ है। -### मर्ज / डिडुप्लिकेशन (यांत्रिक, कम जोखिम) +### मर्ज / डुप्लीकेशन हटाना (यांत्रिक, कम जोखिम) -प्रत्येक उम्मीदवार को 2026-06-17 की लाइव गेट स्थिति के विरुद्ध सत्यापित किया गया था (भरोसा करें, लेकिन सत्यापित भी करें); -कई "स्पष्ट" मर्ज वास्तव में छिपे हुए तकनीकी ऋण वाले निकले और वे **साफ़-सुथरे** ड्रॉप-इन नहीं हैं। +प्रत्येक उम्मीदवार को 2026-06-17 को लाइव गेट स्थिति के विरुद्ध सत्यापित किया गया था (भरोसा करें, लेकिन सत्यापित भी करें); +कई "स्पष्ट" मर्ज वास्तव में छिपे हुए ऋण वाले निकले और **साफ़-सुथरे प्रत्यक्ष प्रतिस्थापन नहीं** हैं। - **`check:docs-sync` दो बार चलता है** — `lint` जॉब में स्वतंत्र रूप से और फिर `check:docs-all` (`docs-sync-strict`) तथा husky pre-commit हुक के भीतर। ✅ **पूर्ण** — स्वतंत्र `lint` इनवोकेशन हटा दिया गया। -- **CVE स्कैनिंग** — ❌ **साफ़-सुथरा मर्ज नहीं है।** `audit:deps` किसी भी उच्च/गंभीर CVE पर हार्ड-फेल होता है; `check:vuln-ratchet` (osv) केवल बेसलाइन की तुलना में किसी _प्रतिगमन_ पर विफल होता है (वर्तमान में 1 MODERATE)। अर्थ-विज्ञान अलग हैं — `audit:deps` हटाने से पूर्ण उच्च/गंभीर गेट समाप्त हो जाएगा। दोनों को बनाए रखें। -- **चक्र पहचान** — ❌ **साफ़-सुथरा मर्ज नहीं है।** `check:circular-deps` (dpdm) **91 चक्रों** की रिपोर्ट करता है (इसीलिए यह परामर्शात्मक है); पहले उन्हें हल किए बिना इसे ब्लॉकिंग में प्रोमोट नहीं किया जा सकता, और इसका दायरा हरे, चयनित `check:cycles` से व्यापक है। `check:cycles` को ब्लॉकिंग बनाए रखें; 91 dpdm चक्रों का समाधान स्वयं में एक अलग बैकलॉग है। -- **जटिलता** — ✅ **पूर्ण** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): एक ESLint वॉक, ruleId के अनुसार गणना करता है ताकि cyclomatic+max-lines और cognitive बेसलाइन स्वतंत्र रहें; अलग-अलग `check:complexity` / `check:cognitive-complexity` स्थानीय `--update` के लिए बने रहेंगे। -- **`/api` एंटी-हैलुसिनेशन** — ✅ **पूर्ण** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` की एक FS इन्वेंटरी, openapi-routes + docs-symbols अभी भी स्वतंत्र रूप से रिपोर्ट करते हैं; अलग-अलग जाँच स्थानीय रन के लिए बनी रहेंगी। -- **`check:node-runtime` 11 जॉब में चलता है** — ⚠️ **कम ROI।** प्रत्येक एक अलग रनर है और जाँच <1s लेती है; सस्ती प्रति-जॉब सुरक्षा खोने के बदले कुल बचत ~10s है। इस बदलाव की उठापटक उचित नहीं है। -- **CI lint पर `typecheck:noimplicit:core`** — ✅ **lint जॉब से हटा दिया गया** (यह परामर्शात्मक `continue-on-error` था); ब्लॉकिंग टाइप सतह `typecheck:core` + `check:type-coverage` है। स्थानीय स्क्रिप्ट बरकरार रखी गई। +- **CVE स्कैनिंग** — ❌ **साफ़ मर्ज नहीं है।** `audit:deps` किसी भी high/critical CVE पर हार्ड-फेल होता है; `check:vuln-ratchet` (osv) केवल बेसलाइन की तुलना में किसी _प्रतिगमन_ पर विफल होता है (वर्तमान में 1 MODERATE)। अर्थविज्ञान अलग हैं — `audit:deps` हटाने से निरपेक्ष high/critical गेट समाप्त हो जाएगा। दोनों को रखें। +- **चक्र पहचान** — ✅ **पूर्ण** (#15159 G-01/G-02)। यहाँ पुराने पाठ में `check:cycles` को "हरा, चयनित" गेट कहा गया था और इसे अवरोधक बनाए रखने का औचित्य यह दिया गया था कि `check:circular-deps` (dpdm) ने 91 चक्र रिपोर्ट किए थे। वह हरा परिणाम **भ्रामक रूप से हरा** था: `check:cycles` ने 5 उप-डायरेक्टरियाँ (450 फ़ाइलें) स्कैन कीं, केवल स्थिर `import|export … from` का मिलान किया, और प्रत्येक `@/` तथा `@omniroute/open-sse/` स्पेसिफ़ायर को छोड़ दिया, इसलिए यह उन dynamic-import + alias चक्रों को नहीं देख सका जो रिपॉज़िटरी में प्रमुख थे। सुधार किया गया: गेट अब `src` + `open-sse` (5023 फ़ाइलें) को ट्रैवर्स करता है, TypeScript AST से स्पेसिफ़ायर एकत्र करता है (इसलिए `import("…")` गिना जाता है और टाइप-स्थिति वाला `typeof import("…")` नहीं), और tsconfig `paths` को रिज़ॉल्व करता है। इसे 0 नहीं, बल्कि **14** चक्र मिलते हैं। चूँकि गेट PR में पहले से मौजूद 14 चक्र ठीक नहीं किए जा सकते, इसलिए `check:cycles` अब एक **रैचेट** है (`--ratchet`, `quality-baseline.json` में अधिकतम सीमा `metrics.cycles.value = 14`, `direction: down`) — यह किसी भी _प्रतिगमन_ को रोकता है और संख्या केवल घट सकती है। CI `npm run check:cycles:ratchet` चलाता है। क्रमिक कमी **A-01** के साथ चलेगी। व्यापक द्वितीय राय के रूप में `check:circular-deps` (dpdm) परामर्शी बना रहेगा। +- **जटिलता** — ✅ **पूर्ण** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): एक ESLint ट्रैवर्सल, `ruleId` के आधार पर गणना ताकि cyclomatic+max-lines और cognitive बेसलाइन स्वतंत्र रहें; अलग-अलग `check:complexity` / `check:cognitive-complexity` स्थानीय `--update` के लिए बने रहेंगे। +- **`/api` एंटी-हैलुसिनेशन** — ✅ **पूर्ण** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` की एक FS इन्वेंट्री, openapi-routes + docs-symbols अब भी स्वतंत्र रूप से रिपोर्ट करते हैं; अलग-अलग जाँचें स्थानीय रन के लिए बनी रहेंगी। +- **`check:node-runtime` 11 जॉब में चलता है** — ⚠️ **कम ROI।** प्रत्येक एक अलग रनर है और जाँच <1s की है; सस्ती प्रति-जॉब सुरक्षा खोने के मुकाबले कुल बचत ~10s है। इतने बदलाव के लायक नहीं है। +- **CI lint पर `typecheck:noimplicit:core`** — ✅ **lint जॉब से हटा दिया गया** (यह परामर्शी `continue-on-error` था); अवरोधक टाइप सतह `typecheck:core` + `check:type-coverage` है। स्थानीय स्क्रिप्ट बरकरार रखी गई। ### फ्लिप / निर्णय (ऑपरेटर नीति) -- `check:openapi-security-tiers` (परामर्शात्मक) — ❌ **साफ़-सुथरे ढंग से फ्लिप नहीं किया जा सकता।** यह 0 के साथ बाहर निकलता है, लेकिन चेतावनी देता है कि `LOCAL_ONLY_API_PREFIXES` के अंतर्गत कई `traffic-inspector` रूट में `x-loopback-only: true` एनोटेशन नहीं है। इसे लागू करने के लिए पहले उन एनोटेशन को `openapi.yaml` में जोड़ना आवश्यक है। -- `typecheck:noimplicit:core` (परामर्शात्मक) — इसे ब्लॉकिंग `check:type-coverage` रैचेट बड़े पैमाने पर समाहित करता है। इसे रैचेट में फ्लिप करें या अनावश्यक दूसरे `tsc` पास को हटा दें। -- `test:vitest:ui` (अब **ब्लॉकिंग**) — पहले से मौजूद विफलताओं को `vitest.config.ts` में `// #8618` ट्रैकिंग टिप्पणियों के साथ स्पष्ट रूप से बाहर रखा गया है; नई विफलताओं से जॉब विफल होगा। -- `check:secrets` (gitleaks, 3 दस्तावेज़ीकृत false-positive पर स्थिर किया गया ब्लॉकिंग रैचेट) — 0 तक पहुँचने के लिए उन 3 को allowlist करें, या इसे परामर्शात्मक में डिमोट करें। यह GitHub की मूल secret-scanning + `check:public-creds` के साथ ओवरलैप करता है। -- `check:pr-evidence` (ब्लॉकिंग, PR-body गद्य को grep करता है) — false-positive का उच्च जोखिम; इसे हटाने से कठोर नियम #18 का प्रवर्तन कमज़ोर होता है, इसलिए यह वास्तव में नीतिगत निर्णय है। -- `semgrep` (परामर्शात्मक स्वतंत्र जाँच) — OWASP परिवारों के लिए CodeQL के साथ ओवरलैप करता है; इसकी बेसलाइन को किसी रैचेट से जोड़ें या इसे हटा दें। +- `check:openapi-security-tiers` (परामर्शी) — ❌ **साफ़ तौर पर फ्लिप करने योग्य नहीं।** यह 0 के साथ समाप्त होता है, लेकिन चेतावनी देता है कि `LOCAL_ONLY_API_PREFIXES` के अंतर्गत कई `traffic-inspector` रूट में `x-loopback-only: true` एनोटेशन नहीं है। इसे लागू करने से पहले उन एनोटेशन को `openapi.yaml` में जोड़ना आवश्यक है। +- `typecheck:noimplicit:core` (परामर्शी) — अवरोधक `check:type-coverage` रैचेट में काफ़ी हद तक समाहित है। इसे रैचेट में फ्लिप करें या अनावश्यक दूसरे `tsc` पास को हटा दें। +- `test:vitest:ui` (अब **अवरोधक**) — पहले से मौजूद विफलताओं को `vitest.config.ts` में `// #8618` ट्रैकिंग टिप्पणियों के साथ स्पष्ट रूप से बाहर रखा गया है; नई विफलताएँ जॉब को विफल करती हैं। +- `check:secrets` (gitleaks, 3 प्रलेखित false-positive पर स्थिर अवरोधक रैचेट) — 0 तक पहुँचने के लिए उन 3 को allowlist करें, या इसे परामर्शी स्तर पर घटाएँ। GitHub की मूल secret-scanning + `check:public-creds` के साथ ओवरलैप करता है। +- `check:pr-evidence` (अवरोधक, PR-body गद्य पर grep करता है) — false-positive का उच्च जोखिम; हटाने पर Hard Rule #18 का प्रवर्तन कमज़ोर होता है, इसलिए यह वास्तव में नीतिगत निर्णय है। +- `semgrep` (परामर्शी स्वतंत्र जाँच) — OWASP श्रेणियों के लिए CodeQL के साथ ओवरलैप करता है; इसकी बेसलाइन को किसी रैचेट से जोड़ें या इसे हटा दें। --- diff --git a/docs/i18n/hi/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/hi/docs/compression/COMPRESSION_GUIDE.md index d66d8edbf2d8..ad358bc7cd24 100644 --- a/docs/i18n/hi/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/hi/docs/compression/COMPRESSION_GUIDE.md @@ -439,7 +439,7 @@ const { messages: aged, saved } = applyAging(messages, { `applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`) कैटलॉग के विरुद्ध चयन को हल करता है (अज्ञात आईडी और स्थान-बेमेल शैलियों को छोड़ दिया जाता है, कभी त्रुटि नहीं होती), चयनित निर्देशों को कैटलॉग क्रम में जोड़ता है, सीमा खंड को **एक बार** जोड़ता है, और ब्लॉक को एक एकल आइडेंपोटेंसी मार्कर (`[OmniRoute Output Styles]`) के साथ शुरू करता है, ताकि पुनः लागू करना एक नो-ऑप हो। जब हल की गई भाषा (नीचे भाषा चयन देखें) का अनुवाद होता है, तो अंग्रेजी के बजाय स्थानीयकृत निर्देश इंजेक्ट किया जाता है। -`messages` वाले बॉडी पर, एक सामग्री बाईपास (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts` में) अंतिम तीन संदेशों की जाँच करता है और पूरे टर्न के लिए शैलियों को छोड़ देता है जब वे इसकी सुरक्षा, अपरिवर्तनीय-क्रिया, स्पष्टीकरण, या क्रम-संवेदनशील कीवर्ड से मेल खाते हैं। बाईपास तब चलता है जब डैशबोर्ड का **ऑटो-क्लैरिटी बाईपास** टॉगल (`cavemanOutputMode.autoClarity`) सेट होता है। +`messages` वाले बॉडी पर, एक सामग्री बाईपास (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts` में) अंतिम तीन संदेशों की जाँच करता है और पूरे टर्न के लिए शैलियों को छोड़ देता है जब वे इसकी सुरक्षा, अपरिवर्तनीय-क्रिया, स्पष्टीकरण, या क्रम-संवेदनशील कीवर्ड से मेल खाते हैं। बाईपास तब चलता है जब डैशबोर्ड का **ऑटो-क्लैरिटी बाईपास** टॉगल (`cavemanOutputMode.autoClarity`) चालू होता है, जो डिफ़ॉल्ट है; टॉगल बंद होने पर चुनी गई शैलियाँ उन टर्न पर भी लागू होती हैं। जब बाईपास टर्न को आगे बढ़ने देता है, तो `placeSystemInstruction()` (वही फ़ाइल), जो कभी नया `messages[0]` नहीं बनाता है, ब्लॉक को इनमें से पहले में रखता है जो उसे मिलता है: diff --git a/docs/i18n/hi/docs/reference/ENVIRONMENT.md b/docs/i18n/hi/docs/reference/ENVIRONMENT.md index d007f70d0de5..e2d6f6aceb1f 100644 --- a/docs/i18n/hi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/hi/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ OmniRoute द्वारा पहचाने गए प्रत्येक ## 1. आवश्यक सीक्रेट्स -इन्हें पहली बार चलाने से पहले सेट करना **अनिवार्य** है। इनके बिना, एप्लिकेशन या तो शुरू होने से इनकार कर देगा या असुरक्षित डिफ़ॉल्ट मानों के साथ काम करेगा। - -| वेरिएबल | आवश्यक | डिफ़ॉल्ट | स्रोत फ़ाइल | विवरण | -| ---------------------------- | ------------------- | ---------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **हाँ** | _(कोई नहीं)_ | `src/lib/auth` | सभी डैशबोर्ड सेशन कुकीज़ (JWT) पर हस्ताक्षर करता है और उन्हें सत्यापित करता है। `openssl rand -base64 48` से जनरेट करें। | -| `API_KEY_SECRET` | **हाँ** | _(कोई नहीं)_ | `src/lib/db/apiKeys.ts` | SQLite में स्थिर अवस्था में मौजूद API कुंजी मानों के लिए AES एन्क्रिप्शन कुंजी। `openssl rand -hex 32` से जनरेट करें। | -| `INITIAL_PASSWORD` | **हाँ** | `CHANGEME` | बूटस्ट्रैप स्क्रिप्ट | प्रारंभिक एडमिन डैशबोर्ड पासवर्ड सेट करता है (`.env.example` के डिफ़ॉल्ट से मेल खाता है — इसे स्पष्ट रूप से असुरक्षित रखा गया है ताकि इसे बदलना अनिवार्य हो)। **पहली बार उपयोग करने से पहले बदलें।** लॉगिन करने के बाद, Dashboard → Settings → Security के माध्यम से बदलें। | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **हाँ** (प्रोडक्शन) | _(सेट नहीं)_ | `src/app/api/internal/codex-responses-ws/route.ts` | आंतरिक Codex Responses WebSocket ब्रिज के लिए साझा सीक्रेट। Electron/browser WS रिले और OmniRoute के बीच ब्रिज अनुरोधों को प्रमाणित करता है। ⚠️ **प्रोडक्शन में आवश्यक — सेट न होने पर सभी WS ब्रिज अनुरोध अस्वीकार कर दिए जाते हैं।** `openssl rand -base64 32` से जनरेट करें। | -| `OMNIROUTE_SW_BUILD_ID` | नहीं | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA शेल (#11779) के लिए स्पष्ट सर्विस-वर्कर कैश-बस्टिंग आईडी; समाधान क्रम में पहला। | -| `SOURCE_VERSION` | नहीं | _(सेट नहीं)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | क्रम में दूसरा — PaaS बिल्डर (जैसे Heroku-शैली) इसे डिप्लॉय किए गए कमिट के रूप में सेट करते हैं। | -| `NEXT_PUBLIC_SW_BUILD_ID` | नहीं | _(व्युत्पन्न)_ | `src/shared/components/PwaRegister.tsx` | बिल्ड-समय का सार्वजनिक मान, जिसका उपयोग क्लाइंट `/sw.js?v=…` को रजिस्टर करने के लिए करता है; इसे ऊपर दिए गए दोनों मानों से और फिर git SHA से व्युत्पन्न किया जाता है। | -| `OMNIROUTE_PEER_STAMP_TOKEN` | नहीं (स्वचालित) | _(प्रत्येक बूट पर स्वचालित)_ | `src/server/authz/policies/management.ts` | प्रति-प्रोसेस सीक्रेट, जो प्रमाणित करता है कि विश्वसनीय पीयर-IP स्टैम्प OmniRoute के अपने HTTP सर्वर (`scripts/dev/peer-stamp.mjs`) से आया है। authz मिडलवेयर अनुरोध की स्थानीयता (LOCAL_ONLY रूट्स की लूपबैक/LAN गेटिंग) पर केवल तभी भरोसा करता है, जब स्टैम्प में यह टोकन मौजूद हो। प्रत्येक बूट पर अपने-आप जनरेट होता है — इसे सेट न करें; इसे केवल उन मल्टी-प्रोसेस सेटअप के लिए निर्धारित करें जिन्हें स्टैम्प साझा करना आवश्यक है। | -| `OMNIROUTE_TRUSTED_PROXIES` | नहीं | _(सेट नहीं)_ | `scripts/dev/peer-stamp.mjs` | रिवर्स प्रॉक्सी के कॉमा से अलग किए गए IP या CIDR रेंज, जिनके `X-Forwarded-For` / `X-Real-IP` पर IP अनुमति/अस्वीकृति सूची भरोसा कर सकती है। यह लूपबैक, निजी-नेटवर्क पतों और Cloudflare एज के अतिरिक्त है, जिन पर हमेशा भरोसा किया जाता है। इसकी आवश्यकता केवल किसी अन्य सार्वजनिक पते पर मौजूद प्रॉक्सी के लिए होती है; अन्यथा फ़िल्टर स्वयं उस प्रॉक्सी के पते का मूल्यांकन करता है। | +**हाँ** से चिह्नित और अनसेट रहस्य पहली बार चलाने पर स्वतः उत्पन्न होते हैं (`JWT_SECRET`, `API_KEY_SECRET`) और डेटा निर्देशिका में `server.env` में सहेजे जाते हैं — जब वह निर्देशिका अस्थायी हो तो इन्हें स्पष्ट रूप से सेट करें। अपवाद `OMNIROUTE_WS_BRIDGE_SECRET` है: यह `server.env` में सहेजा नहीं जाता — सर्वर एंट्रीपॉइंट हर बूट पर एक अल्पकालिक मान बनाते हैं जिसका उपयोग केवल प्रक्रिया के भीतर का ब्रिज कर सकता है, इसलिए बाहरी ब्रिज क्लाइंट्स को इसे स्पष्ट रूप से सेट करने की आवश्यकता होती है। + +| वेरिएबल | आवश्यक | डिफ़ॉल्ट | स्रोत फ़ाइल | विवरण | +| ---------------------------- | ------------------- | ---------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **हाँ** | _(कोई नहीं)_ | `src/lib/auth` | सभी डैशबोर्ड सेशन कुकीज़ (JWT) पर हस्ताक्षर करता है और उन्हें सत्यापित करता है। `openssl rand -base64 48` से जनरेट करें। | +| `API_KEY_SECRET` | **हाँ** | _(कोई नहीं)_ | `src/lib/db/apiKeys.ts` | SQLite में स्थिर अवस्था में मौजूद API कुंजी मानों के लिए AES एन्क्रिप्शन कुंजी। `openssl rand -hex 32` से जनरेट करें। | +| `INITIAL_PASSWORD` | नहीं | _(सेट नहीं)_ | `src/lib/auth/managementPassword.ts` | जब तक कोई पासवर्ड सहेजा न गया हो, तब तक स्टार्टअप पर डैशबोर्ड पासवर्ड सेट करता है; एक बार पासवर्ड सहेज दिए जाने के बाद इसे नज़रअंदाज़ कर दिया जाता है। npm और सोर्स इंस्टॉल `.env.example` को `.env` में कॉपी करते हैं, इसलिए वे उसके सार्वजनिक रूप से ज्ञात प्लेसहोल्डर `CHANGEME` के साथ शुरू होते हैं: पहले बूट से पहले इसे बदलें, या उस `.env` में `INITIAL_PASSWORD=` (खाली) सेट करें ताकि पहली विज़िट पर ऑनबोर्डिंग विज़ार्ड में पासवर्ड बनाया जा सके, जैसा कोई अनसेट मान (Docker इमेज का डिफ़ॉल्ट) करता है। सहेजे गए पासवर्ड को Dashboard → Settings → Security में बदलें, या सर्वर की डेटा निर्देशिका पर `DATA_DIR` सेट करके `omniroute reset-password` चलाएँ (`node bin/reset-password.mjs` सोर्स चेकआउट में)। | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **हाँ** (प्रोडक्शन) | _(सेट नहीं)_ | `src/app/api/internal/codex-responses-ws/route.ts` | आंतरिक Codex Responses WebSocket ब्रिज के लिए साझा सीक्रेट। Electron/browser WS रिले और OmniRoute के बीच ब्रिज अनुरोधों को प्रमाणित करता है। ⚠️ **प्रोडक्शन में आवश्यक — सेट न होने पर सभी WS ब्रिज अनुरोध अस्वीकार कर दिए जाते हैं।** `openssl rand -base64 32` से जनरेट करें। | +| `OMNIROUTE_SW_BUILD_ID` | नहीं | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA शेल (#11779) के लिए स्पष्ट सर्विस-वर्कर कैश-बस्टिंग आईडी; समाधान क्रम में पहला। | +| `SOURCE_VERSION` | नहीं | _(सेट नहीं)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | क्रम में दूसरा — PaaS बिल्डर (जैसे Heroku-शैली) इसे डिप्लॉय किए गए कमिट के रूप में सेट करते हैं। | +| `NEXT_PUBLIC_SW_BUILD_ID` | नहीं | _(व्युत्पन्न)_ | `src/shared/components/PwaRegister.tsx` | बिल्ड-समय का सार्वजनिक मान, जिसका उपयोग क्लाइंट `/sw.js?v=…` को रजिस्टर करने के लिए करता है; इसे ऊपर दिए गए दोनों मानों से और फिर git SHA से व्युत्पन्न किया जाता है। | +| `OMNIROUTE_PEER_STAMP_TOKEN` | नहीं (स्वचालित) | _(प्रत्येक बूट पर स्वचालित)_ | `src/server/authz/policies/management.ts` | प्रति-प्रोसेस सीक्रेट, जो प्रमाणित करता है कि विश्वसनीय पीयर-IP स्टैम्प OmniRoute के अपने HTTP सर्वर (`scripts/dev/peer-stamp.mjs`) से आया है। authz मिडलवेयर अनुरोध की स्थानीयता (LOCAL_ONLY रूट्स की लूपबैक/LAN गेटिंग) पर केवल तभी भरोसा करता है, जब स्टैम्प में यह टोकन मौजूद हो। प्रत्येक बूट पर अपने-आप जनरेट होता है — इसे सेट न करें; इसे केवल उन मल्टी-प्रोसेस सेटअप के लिए निर्धारित करें जिन्हें स्टैम्प साझा करना आवश्यक है। | +| `OMNIROUTE_TRUSTED_PROXIES` | नहीं | _(सेट नहीं)_ | `scripts/dev/peer-stamp.mjs` | रिवर्स प्रॉक्सी के कॉमा से अलग किए गए IP या CIDR रेंज, जिनके `X-Forwarded-For` / `X-Real-IP` पर IP अनुमति/अस्वीकृति सूची भरोसा कर सकती है। यह लूपबैक, निजी-नेटवर्क पतों और Cloudflare एज के अतिरिक्त है, जिन पर हमेशा भरोसा किया जाता है। इसकी आवश्यकता केवल किसी अन्य सार्वजनिक पते पर मौजूद प्रॉक्सी के लिए होती है; अन्यथा फ़िल्टर स्वयं उस प्रॉक्सी के पते का मूल्यांकन करता है। | ### जनरेशन कमांड @@ -215,38 +215,38 @@ OmniRoute सभी स्थायी डेटा के लिए **SQLite** ## 4. सुरक्षा और प्रमाणीकरण -| वेरिएबल | डिफ़ॉल्ट | स्रोत फ़ाइल | विवरण | -| ------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | मशीन फ़िंगरप्रिंटिंग के लिए हार्डवेयर पहचानकर्ताओं के साथ संयुक्त सॉल्ट। अलगाव के लिए प्रति-डिप्लॉयमेंट बदलें। | -| `OMNIROUTE_CLI_SALT` | _(अनसेट = प्रति-इंस्टॉल रैंडम सॉल्ट `/cli-token-salt.json` पर स्थायी)_ | `src/lib/machineToken.ts` | स्थानीय CLI प्रमाणीकरण टोकन प्राप्त करने के लिए HMAC सॉल्ट। इस मान को सेट करने से मशीन पर सभी CLI टोकन घूम जाते हैं और यह हमेशा स्थायी सॉल्ट पर प्राथमिकता लेता है। `docs/security/CLI_TOKEN.md` देखें। | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | सेशन कुकीज़ पर `Secure` फ़्लैग सेट करता है। HTTPS के पीछे चलने पर **`true` होना चाहिए**। | -| `REQUIRE_API_KEY` | `false` | API मिडलवेयर | जब `true` हो, तो सभी `/v1/*` प्रॉक्सी अनुरोधों में एक वैध API कुंजी शामिल होनी चाहिए। **यह फ़्लैग `GET /v1/models` को नियंत्रित नहीं करता है**, जो इसके बजाय डैशबोर्ड लॉगिन पोस्चर (`requireAuthForModels`) का पालन करता है — इसलिए `/v1/models` से `401` का मतलब यह नहीं है कि अनुमान सुरक्षित है। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) देखें। | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | डैशबोर्ड UI में पूर्ण API कुंजी मानों को प्रकट करने की अनुमति देता है। डैशबोर्ड फ़ीचर फ़्लैग से कॉन्फ़िगर करने योग्य; साझा इंस्टेंस पर सुरक्षा जोखिम। | -| `NO_LOG_API_KEY_IDS` | _(खाली)_ | `src/lib/compliance/index.ts` | कॉमा-सेपरेटेड API कुंजी ID जो अनुरोध लॉगिंग (GDPR अनुपालन) को बायपास करती हैं। | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(अनसेट = असीमित)_ | `src/shared/utils/apiKeyPolicy.ts` | API कुंजियों पर लागू होने वाला फ़ॉलबैक प्रति-दिन अनुरोध बजट जिनकी `rate_limits` कॉलम शून्य है। अनसेट या खाली: कोई अंतर्निहित सीमा नहीं (#2289, #11017)। `0` भी वही है (असीमित)। धनात्मक पूर्णांक N, N/दिन, 5N/सप्ताह, 20N/माह को सक्षम करता है। गलत तरीके से स्वरूपित गैर-खाली मान लीगेसी 1000/दिन, 5000/सप्ताह, 20000/माह विंडो पर वापस आ जाते हैं। | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | अनुमति प्राप्त अधिकतम अनुरोध बॉडी आकार। इस सीमा से अधिक पेलोड को अस्वीकार करता है। | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | इस सीमा पर या उससे अधिक के वास्तविक अनुरोध बॉडी JSON पार्सिंग से पहले परमाणु प्रक्रिया-स्थानीय हेवीवेट एडमिशन लीज़ लेते हैं (BYTE पाथ, जिसमें `POST /v1/responses` शामिल है)। संरचना-भारी के समान [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) स्वस्थ-हेडरूम एस्केप; अभी भी `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) द्वारा सीमित है ताकि [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) वापस न आए। | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | सीमित इनजेशन के दौरान पढ़े गए बाइट्स के विरुद्ध लागू चैट-रूट हार्ड कैप, जिसमें गुम, अमान्य या बेईमान `Content-Length` वाले अनुरोध शामिल हैं; अतिरिक्त `413` प्राप्त करता है। | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(unset — no request-count cap)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** यह लेगेसी अनुरोध-गणना कैप अब केवल तभी बाध्य होता है जब इसे स्पष्ट रूप से सेट किया जाता है। अनसेट छोड़ने पर (डिफ़ॉल्ट), हेवीवेट चैट एडमिशन को इसके बजाय `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` द्वारा नियंत्रित किया जाता है — एक ऑटो-डेरिव्ड बाइट बजट जो **एक प्रक्रिया** (एक V8 हीप) में प्रक्रिया की वास्तविक मेमोरी सीमा से आकारित होता है। दो ओवरलैपिंग ~750k-टोकन `/v1/responses` लगभग ~12 Gi हीप्स (#7849) को रद्द करते हैं — यह एक **मेमोरी-बजट** चेतावनी है, न कि 2 का हार्ड प्रोडक्ट मैक्स। एक स्वस्थ प्रक्रिया (शेड अनुपात से नीचे हीप) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` के माध्यम से अधिक समवर्ती लंबे `/v1/responses` को स्वीकार कर सकती है। दर्जनों लंबे SSE क्लाइंट (40-50) हीप + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 हैं, न कि "अधिकतम 2"। इसे "होस्ट का उपयोग करें" तक अंधाधुंध बढ़ाने से #7849 फिर से आ जाता है। **हीप्स** को **N स्वतंत्र `DATA_DIR`s** (#11024) से गुणा करें; एक SQLite फ़ाइल पर कभी भी `replicas>1` न करें। | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(auto-derived)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ऑटो-डेरिव्ड इनजेस्ट बाइट बजट के लिए ओवरराइड (टाइट V8/cgroup मेमोरी सीलिंग का 25% 8x क्षणिक प्रवर्धन से विभाजित)। व्युत्पन्न और स्पष्ट मान 8 MiB–2 GiB तक सीमित होते हैं। प्रभावी बजट से बड़ा एक बॉडी तुरंत `413 body_exceeds_budget` के साथ विफल हो जाता है; व्यक्तिगत रूप से सेवा योग्य बॉडीज़ के बीच विवाद `503` के साथ पुनः प्रयास योग्य रहता है। 40-50 समवर्ती लंबे SSE क्लाइंट इस बजट + हीप हैं, न कि एक हार्ड "अधिकतम 2"। ट्यूनिंग से पहले `/api/monitoring/health` पर `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` पढ़ें। | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | बाइट और स्ट्रक्चर हेवीवेट एडमिशन (#10183, #10268, #10437) के लिए हीप-प्रेशर शेड अनुपात (`heapUsed / heap_size_limit`)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` से अधिक एक समवर्ती हेवीवेट अनुरोध केवल तभी पुनः प्रयास योग्य `503` के साथ शेड किया जाता है जब हीप भी इस अनुपात पर या उससे ऊपर हो; एक स्वस्थ हीप पर इसे इसके बजाय स्वस्थ-हेडरूम के माध्यम से स्वीकार किया जाता है। | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (default `1`) | `src/shared/middleware/chatBodyAdmission.ts` | स्वस्थ-हीप फास्ट पाथ (#10437) के लिए **दोनों** स्ट्रक्चर और बाइट (`admitChatRequest`, जिसमें `OMNIROUTE_CHAT_LARGE_BODY_BYTES` से बड़े बॉडी शामिल हैं) पर सीमित अतिरिक्त क्षमता। इस सीमा के बिना, प्रत्येक व्यस्त-लेकिन-स्वस्थ-हीप अनुरोध बिना किसी सीमा के एडमिशन को बायपास कर देता था। एक बार जब इस स्वस्थ-हीप पाथ के माध्यम से इतने समवर्ती लीज सक्रिय हो जाते हैं, तो आगे के व्यस्त अनुरोध वास्तविक हीप दबाव के तहत उपयोग किए जाने वाले समान सीमित-प्रतीक्षा/शेड पाथ पर आ जाते हैं। `0` बायपास को पूरी तरह से अक्षम कर देता है। | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | संदेश गणना जो एक चैट अनुरोध को हेवीवेट के रूप में वर्गीकृत करती है, भले ही उसका बॉडी बाइट थ्रेशोल्ड से कम हो। | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | टूल गणना जो एक चैट अनुरोध को हेवीवेट के रूप में वर्गीकृत करती है, भले ही उसका बॉडी बाइट थ्रेशोल्ड से कम हो। | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | रूढ़िवादी स्ट्रिंग-आकार टोकन अनुमान जो एक अनुरोध को हेवीवेट के रूप में वर्गीकृत करता है; यह एक एडमिशन-लागत प्रॉक्सी है, न कि प्रदाता बिलिंग टोकनाइजेशन। | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (disabled) | `src/shared/middleware/chatBodyAdmission.ts` | वैकल्पिक ऑप्ट-इन चैट इतिहास कैप। डिफ़ॉल्ट रूप से अक्षम: संदेश गणना एक डिप्लॉयमेंट नीति है, न कि अनुरोध का एक सार्वभौमिक गुण, और यहां कैपिंग संपीड़न पाइपलाइन उन्हें सेवा योग्य बनाने से पहले टर्मिनल `413` के साथ वार्तालापों को अस्वीकार कर देती है। हीप वृद्धि `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` और हीप-प्रेशर शेड द्वारा सीमित है। मेमोरी-बाधित डिप्लॉयमेंट पर एक सकारात्मक मान सेट करें जिन्हें एक हार्ड सीलिंग की आवश्यकता है; अतिरिक्त को तब संरचित कॉम्पैक्ट-आवश्यक `413` प्राप्त होता है। | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | एक नॉन-स्ट्रीमिंग अपस्ट्रीम प्रतिक्रिया के लिए हार्ड कैप जिसे पूरी तरह से मेमोरी में बफर किया जाता है। इसके बाद अपस्ट्रीम रीडर रद्द कर दिया जाता है और हीप के समाप्त होने तक एक असीमित स्ट्रिंग बढ़ने के बजाय अनुरोध तेजी से विफल हो जाता है। | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | अपस्ट्रीम प्रतिक्रिया शीर्षलेखों से अग्रेषित अधिकतम वायर बाइट्स। जब बजट पार हो जाता है, तो सामान्य रिवर्स-प्रॉक्सी हेडर सीमाओं के भीतर रहने के लिए कम-प्राथमिकता वाले हेडर (जैसे, कस्टम `x-codex-*`, `x-oai-request-id`) हटा दिए जाते हैं। बड़े प्रतिक्रिया हेडर आकार की कीमत पर अधिक अपस्ट्रीम मेटाडेटा अग्रेषित करने के लिए उच्च सेट करें। | -| `CORS_ORIGIN` | _(अनसेट)_ | `src/server/cors/origins.ts` | लेगेसी सिंगल-ओरिजिन CORS अनुमति सूची। नई तैनाती के लिए `CORS_ALLOWED_ORIGINS` को प्राथमिकता दें। CORS केवल क्रॉस-ओरिजिन ब्राउज़र API क्लाइंट के लिए है; प्रमाणित डैशबोर्ड राइट्स इसके बजाय समान-ओरिजिन अनुरोधों और सत्र-बाउंड CSRF सुरक्षा का उपयोग करते हैं। | -| `CORS_ALLOWED_ORIGINS` | _(अनसेट)_ | `src/server/cors/origins.ts` | कॉमा-सेपरेटेड CORS अनुमति सूची। कोई वाइल्डकार्ड नहीं भेजा जाता है जब तक कि `CORS_ALLOW_ALL=true` स्पष्ट रूप से कॉन्फ़िगर न किया गया हो। | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | किसी भी ब्राउज़र `Origin` को इको करने के लिए केवल-विकास एस्केप हैच। साझा या उत्पादन तैनाती पर सक्षम न करें। | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | निजी/लूपबैक/लिंक-स्थानीय IP श्रेणियों को लक्षित करने वाले प्रदाता कॉल को ब्लॉक करें। केवल अलग-थलग परीक्षण वातावरण में अक्षम करें। | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | निजी/स्थानीय नेटवर्क (localhost, 192.168.x.x, 10.x.x.x, आदि) की ओर इशारा करने वाले प्रदाता URL की अनुमति दें। **स्व-होस्टेड प्रदाताओं के लिए आवश्यक** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)। जब `false` होता है, तो डैशबोर्ड स्थानीय URL के सत्यापन को अस्वीकार कर देता है। | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | स्थानीय/निजी पतों (127.0.0.1, localhost, LAN, निजी श्रेणियां) पर प्रदाताओं को जोड़ने/मान्य करने की अनुमति दें — प्रदाता सत्यापन पथ तक सीमित। **डिफ़ॉल्ट `true`** (स्थानीय-प्रथम); सख्त सार्वजनिक-केवल ब्लॉकिंग लागू करने के लिए `false` सेट करें। क्लाउड-मेटाडेटा एंडपॉइंट्स (169.254.169.254, metadata.google.internal) हमेशा अवरुद्ध रहते हैं। (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` मार्गों (प्रतिलेखन, भाषण, अनुवाद) को localhost के बाहर होस्ट किए गए OpenAI-संगत प्रदाता नोड का उपयोग करने दें। डिफ़ॉल्ट रूप से बंद — ऑडियो को रिमोट होस्ट पर रूट करने से निकास पहचान बदल जाती है और यह एक स्पष्ट ऑपरेटर निर्णय होना चाहिए। लूपबैक/निजी नोड्स (localhost, 127.0.0.1, 172.16-31.x) हमेशा अनुमत और अप्रभावित रहते हैं। (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (और मेमोरी इंजन का लूपबैक रीरैंक चरण) को localhost के बाहर होस्ट किए गए OpenAI-संगत प्रदाता नोड का उपयोग करने दें — एक LAN बॉक्स या Tailscale पीयर जो TEI, Infinity, vLLM, आदि चला रहा हो। डिफ़ॉल्ट रूप से बंद — रिमोट होस्ट पर रूट करने से निकास पहचान बदल जाती है और यह एक स्पष्ट ऑपरेटर निर्णय होना चाहिए। लूपबैक नोड्स (localhost, 127.0.0.1, 172.16-31.x) हमेशा अनुमत और अप्रभावित रहते हैं। रिमोट नोड्स को प्रदाता आउटबाउंड URL नीति (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) को भी पास करना होगा; क्लाउड-मेटाडेटा होस्ट कभी भी रूट नहीं किए जाते हैं। | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | जब OIDC सक्षम होता है, तो पासवर्ड लॉगिन अक्षम करें ताकि उपयोगकर्ता केवल OIDC सिंगल साइन-ऑन के माध्यम से प्रमाणित कर सकें। नंगे उपनाम `OIDC_DISABLE_PASSWORD_LOGIN` भी स्वीकार किया जाता है; उसी कुंजी का डैशबोर्ड फ़ीचर फ़्लैग प्राथमिकता लेता है। (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` का नंगे उपनाम (#10889)। | +| वेरिएबल | डिफ़ॉल्ट | स्रोत फ़ाइल | विवरण | +| ------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | मशीन फ़िंगरप्रिंटिंग के लिए हार्डवेयर पहचानकर्ताओं के साथ संयुक्त सॉल्ट। अलगाव के लिए प्रति-डिप्लॉयमेंट बदलें। | +| `OMNIROUTE_CLI_SALT` | _(अनसेट = प्रति-इंस्टॉल रैंडम सॉल्ट `/cli-token-salt.json` पर स्थायी)_ | `src/lib/machineToken.ts` | स्थानीय CLI प्रमाणीकरण टोकन प्राप्त करने के लिए HMAC सॉल्ट। इस मान को सेट करने से मशीन पर सभी CLI टोकन घूम जाते हैं और यह हमेशा स्थायी सॉल्ट पर प्राथमिकता लेता है। `docs/security/CLI_TOKEN.md` देखें। | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | सेशन कुकीज़ पर `Secure` फ़्लैग सेट करता है। HTTPS के पीछे चलने पर **`true` होना चाहिए**। | +| `REQUIRE_API_KEY` | `false` | API मिडलवेयर | जब `true` हो, तो सभी `/v1/*` प्रॉक्सी अनुरोधों में एक वैध API कुंजी शामिल होनी चाहिए। **यह फ़्लैग `GET /v1/models` को नियंत्रित नहीं करता है**, जो इसके बजाय डैशबोर्ड लॉगिन पोस्चर (`requireAuthForModels`) का पालन करता है — इसलिए `/v1/models` से `401` का मतलब यह नहीं है कि अनुमान सुरक्षित है। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) देखें। | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | डैशबोर्ड UI में पूर्ण API कुंजी मानों को प्रकट करने की अनुमति देता है। डैशबोर्ड फ़ीचर फ़्लैग से कॉन्फ़िगर करने योग्य; साझा इंस्टेंस पर सुरक्षा जोखिम। | +| `NO_LOG_API_KEY_IDS` | _(खाली)_ | `src/lib/compliance/index.ts` | कॉमा-सेपरेटेड API कुंजी ID जो अनुरोध लॉगिंग (GDPR अनुपालन) को बायपास करती हैं। | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(अनसेट = असीमित)_ | `src/shared/utils/apiKeyPolicy.ts` | API कुंजियों पर लागू होने वाला फ़ॉलबैक प्रति-दिन अनुरोध बजट जिनकी `rate_limits` कॉलम शून्य है। अनसेट या खाली: कोई अंतर्निहित सीमा नहीं (#2289, #11017)। `0` भी वही है (असीमित)। धनात्मक पूर्णांक N, N/दिन, 5N/सप्ताह, 20N/माह को सक्षम करता है। गलत तरीके से स्वरूपित गैर-खाली मान लीगेसी 1000/दिन, 5000/सप्ताह, 20000/माह विंडो पर वापस आ जाते हैं। | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | अनुमति प्राप्त अधिकतम अनुरोध बॉडी आकार। इस सीमा से अधिक पेलोड को अस्वीकार करता है। | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | इस सीमा पर या उससे अधिक के वास्तविक अनुरोध बॉडी JSON पार्सिंग से पहले परमाणु प्रक्रिया-स्थानीय हेवीवेट एडमिशन लीज़ लेते हैं (BYTE पाथ, जिसमें `POST /v1/responses` शामिल है)। संरचना-भारी के समान [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) स्वस्थ-हेडरूम एस्केप; अभी भी `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) द्वारा सीमित है ताकि [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) वापस न आए। | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | सीमित इनजेशन के दौरान पढ़े गए बाइट्स के विरुद्ध लागू चैट-रूट हार्ड कैप, जिसमें गुम, अमान्य या बेईमान `Content-Length` वाले अनुरोध शामिल हैं; अतिरिक्त `413` प्राप्त करता है। | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(unset — no request-count cap)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** यह लेगेसी अनुरोध-गणना कैप अब केवल तभी बाध्य होता है जब इसे स्पष्ट रूप से सेट किया जाता है। अनसेट छोड़ने पर (डिफ़ॉल्ट), हेवीवेट चैट एडमिशन को इसके बजाय `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` द्वारा नियंत्रित किया जाता है — एक ऑटो-डेरिव्ड बाइट बजट जो **एक प्रक्रिया** (एक V8 हीप) में प्रक्रिया की वास्तविक मेमोरी सीमा से आकारित होता है। दो ओवरलैपिंग ~750k-टोकन `/v1/responses` लगभग ~12 Gi हीप्स (#7849) को रद्द करते हैं — यह एक **मेमोरी-बजट** चेतावनी है, न कि 2 का हार्ड प्रोडक्ट मैक्स। एक स्वस्थ प्रक्रिया (शेड अनुपात से नीचे हीप) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` के माध्यम से अधिक समवर्ती लंबे `/v1/responses` को स्वीकार कर सकती है। दर्जनों लंबे SSE क्लाइंट (40-50) हीप + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 हैं, न कि "अधिकतम 2"। इसे "होस्ट का उपयोग करें" तक अंधाधुंध बढ़ाने से #7849 फिर से आ जाता है। **हीप्स** को **N स्वतंत्र `DATA_DIR`s** (#11024) से गुणा करें; एक SQLite फ़ाइल पर कभी भी `replicas>1` न करें। | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(auto-derived)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ऑटो-डेरिव्ड इनजेस्ट बाइट बजट के लिए ओवरराइड (टाइट V8/cgroup मेमोरी सीलिंग का 25% 8x क्षणिक प्रवर्धन से विभाजित)। व्युत्पन्न और स्पष्ट मान 8 MiB–2 GiB तक सीमित होते हैं। प्रभावी बजट से बड़ा एक बॉडी तुरंत `413 body_exceeds_budget` के साथ विफल हो जाता है; व्यक्तिगत रूप से सेवा योग्य बॉडीज़ के बीच विवाद `503` के साथ पुनः प्रयास योग्य रहता है। 40-50 समवर्ती लंबे SSE क्लाइंट इस बजट + हीप हैं, न कि एक हार्ड "अधिकतम 2"। ट्यूनिंग से पहले `/api/monitoring/health` पर `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` पढ़ें। | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | बाइट और स्ट्रक्चर हेवीवेट एडमिशन (#10183, #10268, #10437) के लिए हीप-प्रेशर शेड अनुपात (`heapUsed / heap_size_limit`)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` से अधिक एक समवर्ती हेवीवेट अनुरोध केवल तभी पुनः प्रयास योग्य `503` के साथ शेड किया जाता है जब हीप भी इस अनुपात पर या उससे ऊपर हो; एक स्वस्थ हीप पर इसे इसके बजाय स्वस्थ-हेडरूम के माध्यम से स्वीकार किया जाता है। | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (default `1`) | `src/shared/middleware/chatBodyAdmission.ts` | स्वस्थ-हीप फास्ट पाथ (#10437) के लिए **दोनों** स्ट्रक्चर और बाइट (`admitChatRequest`, जिसमें `OMNIROUTE_CHAT_LARGE_BODY_BYTES` से बड़े बॉडी शामिल हैं) पर सीमित अतिरिक्त क्षमता। इस सीमा के बिना, प्रत्येक व्यस्त-लेकिन-स्वस्थ-हीप अनुरोध बिना किसी सीमा के एडमिशन को बायपास कर देता था। एक बार जब इस स्वस्थ-हीप पाथ के माध्यम से इतने समवर्ती लीज सक्रिय हो जाते हैं, तो आगे के व्यस्त अनुरोध वास्तविक हीप दबाव के तहत उपयोग किए जाने वाले समान सीमित-प्रतीक्षा/शेड पाथ पर आ जाते हैं। `0` बायपास को पूरी तरह से अक्षम कर देता है। | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | संदेश गणना जो एक चैट अनुरोध को हेवीवेट के रूप में वर्गीकृत करती है, भले ही उसका बॉडी बाइट थ्रेशोल्ड से कम हो। | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | टूल गणना जो एक चैट अनुरोध को हेवीवेट के रूप में वर्गीकृत करती है, भले ही उसका बॉडी बाइट थ्रेशोल्ड से कम हो। | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | रूढ़िवादी स्ट्रिंग-आकार टोकन अनुमान जो एक अनुरोध को हेवीवेट के रूप में वर्गीकृत करता है; यह एक एडमिशन-लागत प्रॉक्सी है, न कि प्रदाता बिलिंग टोकनाइजेशन। | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (disabled) | `src/shared/middleware/chatBodyAdmission.ts` | वैकल्पिक ऑप्ट-इन चैट इतिहास कैप। डिफ़ॉल्ट रूप से अक्षम: संदेश गणना एक डिप्लॉयमेंट नीति है, न कि अनुरोध का एक सार्वभौमिक गुण, और यहां कैपिंग संपीड़न पाइपलाइन उन्हें सेवा योग्य बनाने से पहले टर्मिनल `413` के साथ वार्तालापों को अस्वीकार कर देती है। हीप वृद्धि `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` और हीप-प्रेशर शेड द्वारा सीमित है। मेमोरी-बाधित डिप्लॉयमेंट पर एक सकारात्मक मान सेट करें जिन्हें एक हार्ड सीलिंग की आवश्यकता है; अतिरिक्त को तब संरचित कॉम्पैक्ट-आवश्यक `413` प्राप्त होता है। | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | एक नॉन-स्ट्रीमिंग अपस्ट्रीम प्रतिक्रिया के लिए हार्ड कैप जिसे पूरी तरह से मेमोरी में बफर किया जाता है। इसके बाद अपस्ट्रीम रीडर रद्द कर दिया जाता है और हीप के समाप्त होने तक एक असीमित स्ट्रिंग बढ़ने के बजाय अनुरोध तेजी से विफल हो जाता है। | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | अपस्ट्रीम प्रतिक्रिया शीर्षलेखों से अग्रेषित अधिकतम वायर बाइट्स। जब बजट पार हो जाता है, तो सामान्य रिवर्स-प्रॉक्सी हेडर सीमाओं के भीतर रहने के लिए कम-प्राथमिकता वाले हेडर (जैसे, कस्टम `x-codex-*`, `x-oai-request-id`) हटा दिए जाते हैं। बड़े प्रतिक्रिया हेडर आकार की कीमत पर अधिक अपस्ट्रीम मेटाडेटा अग्रेषित करने के लिए उच्च सेट करें। | +| `CORS_ORIGIN` | _(अनसेट)_ | `src/server/cors/origins.ts` | लेगेसी सिंगल-ओरिजिन CORS अनुमति सूची। नई तैनाती के लिए `CORS_ALLOWED_ORIGINS` को प्राथमिकता दें। CORS केवल क्रॉस-ओरिजिन ब्राउज़र API क्लाइंट के लिए है; प्रमाणित डैशबोर्ड राइट्स इसके बजाय समान-ओरिजिन अनुरोधों और सत्र-बाउंड CSRF सुरक्षा का उपयोग करते हैं। | +| `CORS_ALLOWED_ORIGINS` | _(अनसेट)_ | `src/server/cors/origins.ts` | कॉमा-सेपरेटेड CORS अनुमति सूची। कोई वाइल्डकार्ड नहीं भेजा जाता है जब तक कि `CORS_ALLOW_ALL=true` स्पष्ट रूप से कॉन्फ़िगर न किया गया हो। | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | किसी भी ब्राउज़र `Origin` को इको करने के लिए केवल-विकास एस्केप हैच। साझा या उत्पादन तैनाती पर सक्षम न करें। | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | लेगेसी उपनाम। इसका डैशबोर्ड टॉगल (एक DB ओवरराइड) इस env मान से पहले पढ़ा जाता है; दोनों में से किसी में भी `false`, `0`, `no` या `off` होस्ट जांचों को उसी तरह बंद कर देता है जैसे `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` करता है, तब भी जब उस फ़्लैग का डैशबोर्ड टॉगल बंद सहेजा गया हो; अन्य मान गार्ड को नीचे के दो फ़्लैग पर छोड़ देते हैं। | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | आउटबाउंड URL गार्ड की होस्ट जांचों को, क्लाउड-मेटाडेटा ब्लॉक सहित (पूरा 169.254.0.0/16, metadata.google.internal), उन प्रदाता पथों पर बंद कर देता है जो गार्ड मोड का पालन करते हैं: URL सत्यापन, मॉडल खोज, प्रदाता-नोड बेस URL (रिमोट रीरैंक नोड्स सहित) और प्रॉक्सी-फ़ॉलबैक परीक्षण, साथ ही रिमोट इमेज/मीडिया फ़ेच जैसे अन्य प्रदाता-संबंधित आउटबाउंड पथ। यह निजी वेबहूक लक्ष्यों को भी अनुमति देता है, सिवाय क्लाउड-मेटाडेटा/लिंक-लोकल एंडपॉइंट्स के, जो इस फ़्लैग के चालू होने पर भी अवरुद्ध रहते हैं। `BaseExecutor.execute()` के माध्यम से भेजी गई चैट अनुरोध अपनी क्लाउड-मेटाडेटा जांच बनाए रखती हैं। लोकल प्रदाता डिफ़ॉल्ट के साथ काम करते हैं: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (डिफ़ॉल्ट `true`) सत्यापन, खोज और प्रदाता-नोड पथों पर लोकल और LAN URL पहले से ही अनुमति देता है; प्रॉक्सी-फ़ॉलबैक परीक्षण और निजी वेबहूक लक्ष्य केवल इस फ़्लैग को देखते हैं और जब तक यह बंद है लोकल/LAN होस्ट को अवरुद्ध करते हैं। `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` को ओवरराइड करता है। डैशबोर्ड टॉगल से सहेजा गया मान इस वेरिएबल पर प्राथमिकता रखता है। | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | प्रदाता सत्यापन, मॉडल खोज, चैट और अन्य प्रदाता-संबंधित आउटबाउंड पथों के लिए लोकल/निजी पतों (127.0.0.1, localhost, LAN, निजी श्रेणियां) पर प्रदाता URL की अनुमति देता है जिन पर गार्ड मोड लागू होता है। **डिफ़ॉल्ट `true`** (लोकल-प्रथम): तब गार्ड क्लाउड-मेटाडेटा एंडपॉइंट्स को अवरुद्ध करता है — पूरा 169.254.0.0/16 और ज्ञात मेटाडेटा होस्टनाम (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254)। केवल-सार्वजनिक मोड के लिए `false` सेट करें, जो निजी और लूपबैक होस्ट को भी अवरुद्ध करता है। गार्ड होस्टनाम या IP लिटरल को लिखे गए अनुसार, किसी भी DNS लुकअप से पहले जांचता है; केवल-सार्वजनिक मोड में सुरक्षित-fetch रैपर अतिरिक्त रूप से होस्टनाम को resolve करता है और किसी भी निजी उत्तर को अस्वीकार कर देता है। `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` इसकी पंक्ति में सूचीबद्ध पथों पर ये जांचें बंद कर देता है। बिल्ट-इन लोकल प्रदाता, जैसे LM Studio, Ollama, vLLM, Llamafile और Triton, कुंजी सत्यापन और चैट के लिए सभी होस्ट जांचों को छोड़ देते हैं। डैशबोर्ड टॉगल से सहेजा गया मान इस वेरिएबल पर प्राथमिकता रखता है। (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` मार्गों (प्रतिलेखन, भाषण, अनुवाद) को localhost के बाहर होस्ट किए गए OpenAI-संगत प्रदाता नोड का उपयोग करने दें। डिफ़ॉल्ट रूप से बंद — ऑडियो को रिमोट होस्ट पर रूट करने से निकास पहचान बदल जाती है और यह एक स्पष्ट ऑपरेटर निर्णय होना चाहिए। लूपबैक/निजी नोड्स (localhost, 127.0.0.1, 172.16-31.x) हमेशा अनुमत और अप्रभावित रहते हैं। (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (और मेमोरी इंजन का लूपबैक रीरैंक चरण) को localhost के बाहर होस्ट किए गए OpenAI-संगत प्रदाता नोड का उपयोग करने दें — एक LAN बॉक्स या Tailscale पीयर जो TEI, Infinity, vLLM, आदि चला रहा हो। डिफ़ॉल्ट रूप से बंद — रिमोट होस्ट पर रूट करने से निकास पहचान बदल जाती है और यह एक स्पष्ट ऑपरेटर निर्णय होना चाहिए। लूपबैक नोड्स (localhost, 127.0.0.1, 172.16-31.x) हमेशा अनुमत और अप्रभावित रहते हैं। रिमोट नोड के बेस URL को भी प्रदाता आउटबाउंड URL नीति (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) को पास करना होगा। | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | जब OIDC सक्षम होता है, तो पासवर्ड लॉगिन अक्षम करें ताकि उपयोगकर्ता केवल OIDC सिंगल साइन-ऑन के माध्यम से प्रमाणित कर सकें। नंगे उपनाम `OIDC_DISABLE_PASSWORD_LOGIN` भी स्वीकार किया जाता है; उसी कुंजी का डैशबोर्ड फ़ीचर फ़्लैग प्राथमिकता लेता है। (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` का नंगे उपनाम (#10889)। | ### हार्डनिंग चेकलिस्ट diff --git a/docs/i18n/hr/CONTRIBUTING.md b/docs/i18n/hr/CONTRIBUTING.md index cf19b3e31a81..ee64bc6955c7 100644 --- a/docs/i18n/hr/CONTRIBUTING.md +++ b/docs/i18n/hr/CONTRIBUTING.md @@ -397,22 +397,24 @@ Napišite jedinične testove u `tests/unit/` koji pokrivaju minimalno: --- -## Kontrolni Popis za Pull Request +## Kontrolni popis za Pull Request - [ ] Testovi prolaze (`npm test`) -- [ ] Lintanje prolazi (`npm run lint`) -- [ ] Izgradnja uspijeva (`npm run build`) -- [ ] TypeScript tipovi dodani za nove javne funkcije i sučelja -- [ ] Nema hardkodiranih tajni niti rezervnih vrijednosti -- [ ] Javne uzvodne vjerodajnice ugrađene putem `resolvePublicCred()` (pogledajte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikada kao literali -- [ ] Odgovori na pogreške prolaze kroz `buildErrorBody()` / `sanitizeErrorMessage()` — nema sirovih tragova stoga u tijelima odgovora (pogledajte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Naredbe ljuske (`exec` / `spawn`) prosljeđuju vrijednosti za izvođenje putem `env`, ne putem string interpolacije -- [ ] Svi ulazni podaci validirani Zod shemama -- [ ] Fragment zapisa promjena dodan pod `changelog.d/{features|fixes|maintenance}/-.md` za promjene vidljive korisniku (pogledajte [`changelog.d/README.md`](./changelog.d/README.md)) — **nemojte** izravno uređivati `CHANGELOG.md`; fragmenti se agregiraju pri izdavanju verzije i nikada ne dolaze u konflikt između pull requestova -- [ ] Dokumentacija ažurirana (ako je primjenjivo) -- [ ] Nema novih CodeQL / Secret-Scanning upozorenja, ili je svako odbačeno s tehničkim obrazloženjem koje upućuje na odgovarajući dokument iz `docs/security/` -- [ ] Rute koje pokreću podprocese (`/api/mcp/`, `/api/cli-tools/runtime/`) klasificirane su kao `isLocalOnlyPath()` u `src/server/authz/routeGuard.ts` — pogledajte [Strogo Pravilo #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Nema `Co-Authored-By` oznaka u porukama commita — commiti moraju biti isključivo pod Git identitetom vlasnika repozitorija (Strogo Pravilo #16) +- [ ] Linting prolazi (`npm run lint`) +- [ ] Izgradnja je uspješna (`npm run build`) +- [ ] Dodani su TypeScript tipovi za nove javne funkcije i sučelja +- [ ] Nema tvrdo kodiranih tajni ni pričuvnih vrijednosti +- [ ] Javne vjerodajnice uzvodnih sustava ugrađene su putem `resolvePublicCred()` (pogledajte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikada kao literali +- [ ] Odgovori o pogreškama prolaze kroz `buildErrorBody()` / `sanitizeErrorMessage()` — nema neobrađenih tragova stoga u tijelima odgovora (pogledajte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Naredbe ljuske (`exec` / `spawn`) prosljeđuju vrijednosti tijekom izvođenja putem `env`, a ne interpolacijom niza +- [ ] Svi su ulazni podaci validirani Zod shemama +- [ ] **Fragment** dnevnika promjena dodan je u `changelog.d/{features|fixes|maintenance}/-.md` za promjene vidljive korisnicima (pogledajte [`changelog.d/README.md`](./changelog.d/README.md)) — **nemojte** izravno uređivati `CHANGELOG.md`; fragmenti se objedinjuju pri objavi izdanja i nikada ne uzrokuju sukobe među PR-ovima +- [ ] Dokumentacija je ažurirana (ako je primjenjivo) +- [ ] Nisu otvorena nova upozorenja alata CodeQL / Secret-Scanning ili je svako odbačeno uz tehničko obrazloženje koje upućuje na relevantni dokument u `docs/security/` +- [ ] Rute koje pokreću podređene procese (`/api/mcp/`, `/api/cli-tools/runtime/`) klasificirane su kao `isLocalOnlyPath()` u `src/server/authz/routeGuard.ts` — pogledajte [Strogo pravilo #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Nema AI/bot najavnih redaka `Co-authored-by` u porukama predaje (Strogo pravilo #16) — ljudskim suradnicima čiji se rad ponovno koristi odaje se priznanje standardnim najavnim redcima `Co-authored-by: Name ` + +--- ## Objavljivanje diff --git a/docs/i18n/hr/docs/architecture/QUALITY_GATES.md b/docs/i18n/hr/docs/architecture/QUALITY_GATES.md index 90468b2e7672..91cc50b6348f 100644 --- a/docs/i18n/hr/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/hr/docs/architecture/QUALITY_GATES.md @@ -19,211 +19,216 @@ replikacije istog sustava neovisan o alatima pogledajte ### Prihvat kandidata -Radni tijekovi CI i Quality Gates objavljuju stabilne ishode: `Gate / CI` i -`Gate / Quality`. Njihova verzionirana pravila prihvata navode svaki prethodni zadatak -kao obavezan ili savjetodavan. Primjenjivi obavezni zadatak mora uspjeti: nedostajući, -otkazani, preskočeni, neriješeni i nepoznati rezultati ne mogu potvrditi PASS. Valjana -klasifikacija samo za dokumentaciju ili samo za katalog može učiniti programsku granu neprimjenjivom; -nacrt PR-a nije prihvaćen kandidat. Oznaka `hotfix` ne ukida zahtjev za dokazima. - -Oba radna tijeka obuhvaćaju PR-ove i slanja na glavne grane/grane izdanja, ručno pokretanje i -događaje grupe za spajanje. Slanje, ručno pokretanje i grupa za spajanje izvršavaju puni odabir. Forkovi -i grupe za spajanje upotrebljavaju hostirane izvršitelje za zadatke koji bi inače odabrali samostalno hostirane -izvršitelje; prije uvođenja mora se provjeriti dostatan hostirani kapacitet. - -Svaka JSON potvrda identificira odjavljeni SHA, izvršavanje radnog tijeka i pokušaj. -CLI odbija nepodudaranje SHA-a odjave i događaja. Testovi radnog tijeka povezuju članstvo u pravilima -s popisom `needs` zadatka za donošenje ishoda kako nova ili uklonjena grana izvršavanja ne bi mogla neprimjetno nestati. -Potvrde obuhvaćaju vlastiti radni tijek, a ne objavljivanje, implementaciju ili unutarnji rad -postojećeg savjetodavnog skenera. Aktiviranje oba naziva provjera u pravilima grane -zasebna je administrativna promjena; dodavanje ovih zadataka samo po sebi ne štiti granu. +Tijekovi rada CI i Quality Gates daju stabilne ishode: `Gate / CI` i +`Gate / Quality`. Njihova verzionirana pravila prihvata navode svaki prethodni posao +kao obavezan ili savjetodavan. Primjenjivi obavezni posao mora uspjeti: rezultati koji +nedostaju, koji su otkazani, preskočeni, na čekanju ili nepoznati ne mogu utvrditi PASS. +Valjana klasifikacija samo za dokumentaciju ili samo za katalog može učiniti programsku +stazu neprimjenjivom; radni PR nije prihvaćen kandidat. Oznaka `hotfix` ne ukida +obvezu pružanja dokaza. + +Oba tijeka rada obuhvaćaju PR-ove i slanja na glavne/release grane, ručno pokretanje i +događaje grupa za spajanje. Slanje, ručno pokretanje i grupa za spajanje pokreću puni +odabir. Forkovi i grupe za spajanje upotrebljavaju hostane izvršitelje za poslove koji +bi inače odabrali samostalno hostane izvršitelje; prije uvođenja mora se provjeriti +dostatan hostani kapacitet. + +Svaka JSON potvrda identificira dohvaćeni SHA, pokretanje tijeka rada i pokušaj. +CLI odbija nepodudaranje SHA-a dohvaćenog stanja i događaja. Testovi tijeka rada povezuju +članstvo u pravilima s popisom `needs` posla koji donosi ishod kako nova ili uklonjena +staza ne bi mogla neprimjetno nestati. Potvrde obuhvaćaju vlastiti tijek rada, a ne +objavljivanje, implementaciju ili unutarnji rad postojećeg savjetodavnog skenera. +Aktiviranje obaju naziva provjera u pravilima grane zasebna je administrativna promjena; +dodavanje ovih poslova samo po sebi ne štiti granu. ### Inventar statičkog skeniranja Verzionirani inventar npm aliasa i članstvo u statičkom skeniranju nalaze se u -`config/quality/gate-manifest.json`. Pokrenite `npm run check:gate-manifest` kako biste provjerili -nazive skripti i točne naredbe u odnosu na `package.json`; dodavanja, uklanjanja i -odstupanja naredbi uzrokuju neuspjeh lokalne kuke i zadataka klasifikacije promjena u CI-ju. -Alias nije zadatak radnog tijeka, instanca matrice ni testni slučaj: ti se brojevi -ne smiju prikazivati kao međusobno zamjenjivi. +`config/quality/gate-manifest.json`. Pokrenite `npm run check:gate-manifest` kako biste +provjerili nazive skripti i točne naredbe u odnosu na `package.json`; dodavanja, uklanjanja +i odstupanja naredbi uzrokuju neuspjeh i lokalne kuke i poslova za klasifikaciju promjena u CI-ju. +Alias nije posao tijeka rada, instanca matrice ni testni slučaj: ti se brojevi ne smiju +prikazivati kao međusobno zamjenjivi. Upotrijebite `npm run quality:scan -- --list` ili `npm run quality:scan:fast -- --list` kako biste pregledali odabrane aliase bez njihova izvršavanja. Izvršitelj poziva -npm ulaznu točku, pa se njezino izvršno okruženje (uključujući Bun gdje je konfiguriran) zadržava. -Manifest bilježi aliase izvan tih profila kao zasebno pozivane, a -naredbe za održavanje zabranjene su u profilima skeniranja samo za čitanje. +npm ulaznu točku, čime se čuva njezino izvršno okruženje (uključujući Bun gdje je konfiguriran). +Manifest bilježi aliase izvan tih profila kao zasebno pozvane, a naredbe za održavanje +zabranjene su u profilima skeniranja koji služe samo za čitanje. -Ovi profili obuhvaćaju samo statičko skeniranje. Ne potvrđuju testove proizvoda, +Ti profili obuhvaćaju samo statičko skeniranje. Ne certificiraju testove proizvoda, pokrivenost, pakiranje, vanjske provjere ni potpuni prihvat kandidata za izdanje. -Prihvat u radnom tijeku upotrebljava povezane `config/quality/admission-policy.json` i -`scripts/quality/admission-verdict.mjs`. Profili promatrača izdanja ostaju zasebni; -njihove primjenjive provjere i potvrde pregledajte neovisno. Tekstualni -inventar u nastavku služi kao referenca, a ne kao dokaz da je kontrola doista izvršena. +Prihvat tijeka rada upotrebljava povezane `config/quality/admission-policy.json` i +`scripts/quality/admission-verdict.mjs`. Profili za praćenje izdanja ostaju odvojeni; +neovisno pregledajte njihove primjenjive provjere i potvrde. Tekstualni +inventar u nastavku služi kao referenca, a ne kao dokaz da je kontrola doista pokrenuta. -Skripte se nalaze u `scripts/check/` (kontrole pravila) i `scripts/quality/` (mehanizam postupnog pooštravanja). +Skripte se nalaze pod `scripts/check/` (kontrole pravila) i `scripts/quality/` (mehanizam postupnog postrožavanja). Izvor istine za CI jest `.github/workflows/ci.yml`. -### Ubrzani put za PR izdanja (`quality.yml`) +### Brza putanja PR-a izdanja (`quality.yml`) -`.github/workflows/quality.yml` nadopunjuje CI za PR-ove na glavnim granama/granama izdanja, slanja -na zaštićene grane, ručna pokretanja i grupe za spajanje. PR-ovi upotrebljavaju brze provjere filtrirane prema putanji. Trajno -onemogućena duplicirana izgradnja uklonjena je; stvarne provjere izgradnje/pakiranja/pokretanja ostaju u CI-ju. +`.github/workflows/quality.yml` nadopunjuje CI za PR-ove na glavnim/release granama, slanja +na zaštićene grane, ručna pokretanja i grupe za spajanje. PR-ovi upotrebljavaju brze provjere +filtrirane prema putanjama. Trajno onemogućena duplicirana izgradnja uklonjena je; stvarne +provjere izgradnje/pakiranja/pokretanja ostaju u CI-ju. -| Zadatak | Opseg | Blokirajuće | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `Docs Gates (fast-path)` | PR-ovi dokumentacije/koda; reference API dokumentacije i cjelokupna dokumentacija | Da | -| `Fast Quality Gates` | PR-ovi koda; statičke provjere, provjera tipova, provjera tipova nadzorne ploče, obuhvaćeni jedinični testovi | Da | -| `Forgotten sibling tests` | PR-ovi koda; promijenjeni moduli praćeni do statičkih potrošača i kandidatskih srodnih testova; putanje bačvastih modula i dinamičkog uvoza prijavljuju se kao savjetodavna dijagnostika, uz navedene iznimke s popisa dopuštenih | **Savjetodavno** | -| `Vitest (fast-path)` | PR-ovi koda; brzi paket vitest testova | Da | -| `Unit Tests fast-path` | PR-ovi koda; paket jediničnih testova s 4 particije | Da | -| `No new ESLint warnings` | PR-ovi koda; zaštita lintanja koja uzima u obzir potiskivanja | Da, uključujući forkove | -| `Merge integrity (changelog + generated skills)` | PR-ovi koji nisu nacrti; sinkronizacija dnevnika promjena i generiranih vještina | Da, uključujući forkove | +| Posao | Opseg | Blokira | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | +| `Docs Gates (fast-path)` | PR-ovi dokumentacije/koda; reference API dokumentacije i cjelokupna dokumentacija | Da | +| `Fast Quality Gates` | PR-ovi koda; statičke provjere, provjera tipova, provjera tipova nadzorne ploče, pogođeni jedinični testovi | Da | +| `Forgotten sibling tests` | PR-ovi koda; promijenjeni moduli prate se do statičkih potrošača i kandidata za povezane testove; putanje bačvastih modula i dinamičkih uvoza prijavljuju se kao savjetodavna dijagnostika, uz navedene iznimke s popisa dopuštenih stavki | **Savjetodavno** | +| `Vitest (fast-path)` | PR-ovi koda; brzi paket vitest testova | Da | +| `Unit Tests fast-path` | PR-ovi koda; paket jediničnih testova u 4 segmenta | Da | +| `No new ESLint warnings` | PR-ovi koda; zaštita lintanja koja uvažava potiskivanja | Da, uključujući forkove | +| `Merge integrity (changelog + generated skills)` | PR-ovi koji nisu radni; sinkronizacija dnevnika promjena i generiranih vještina | Da, uključujući forkove | -#### Izvješće o zaboravljenim srodnim testovima +#### Izvješće o zaboravljenim povezanim testovima -`npm run check:forgotten-sibling-tests` ponovno upotrebljava razrješivač uvoza koji stoji iza mape utjecaja testova. +`npm run check:forgotten-sibling-tests` ponovno upotrebljava razrješivač uvoza na kojem se temelji karta utjecaja testova. Za svaki promijenjeni produkcijski modul prijavljuje determinističke lance -`promijenjeni modul/simbol -> statički potrošač -> kandidatski srodni test` kada kandidatski -test nije prisutan u razlici zahtjeva za povlačenje. Markdown sažetak i JSON rezultat zadržavaju se kao -artefakt radnog tijeka `forgotten-sibling-tests` radi kalibracije prije bilo kakvog uvođenja blokiranja. - -Ponovni izvozi putem barrel datoteka i dinamički uvozi služe samo za dijagnostiku razrješavanja; nikada ne stvaraju -blokirajući nalaz. Pregledane iznimke nalaze se u -`config/quality/forgotten-sibling-allowlist.json`. Svaki unos mora navesti potrošački i kandidatski -test, sadržavati konkretno obrazloženje i poveznicu na GitHub issue ili pull request. Neispravni unosi uzrokuju -neuspjeh. Iznimke ne mogu prikriti izbrisani kandidatski test ni diff koji dodaje `.skip`/`.todo`; -slabljenje tvrdnji i druga prikrivanja ostaju u nadležnosti neovisno blokirajućeg -`check:test-masking` gatea. - -### Job: `lint` - -Izvodi se za svaki PR prema `main`. Blokira spajanje u slučaju neuspjeha. - -| Skripta (`npm run ...`) | Provjerava | Blokira | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- | -| `check:node-runtime` | Verzija Node.js nalazi se unutar podržanog raspona | Da | -| `check:cycles` | Kružne uvoze — svi moduli u `src/` + `open-sse/` | Da | -| `check:route-validation:t06` | Zod sheme prisutne su na svim rutama (pravilo razine 6) | Da | -| `check:any-budget:t11` | Broj pojavljivanja `@ts-expect-error // any` ne premašuje dopušteni prag (catraca razine 11) | Da | -| `check:provider-consistency` | Svaki pružatelj u `providers.ts` ima odgovarajući unos u `providerRegistry.ts` (i obratno, unutar popisa dopuštenih) | Da | -| `check:model-lifecycle` | Tri ručno održavane tablice usmjeravanja ostaju usklađene s pohranjenom snimkom životnog ciklusa (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ne boduje nijedan povučeni id koji `REGISTRY` može usmjeriti; svaki cilj iz `BUILT_IN_ALIASES` prisutan je u `REGISTRY` i nije prisutan u snimci povučenih id-ova; svaki povučeni id koji je još uvijek u `REGISTRY` prosljeđuje se ili je naveden u `allowedRetiredInCatalog`; i nijedan izvor ili cilj iz `DEFAULT_DEGRADATION_MAP` nije označen kao povučen u toj snimci. To ne dokazuje da model trenutačno poslužuje aktivni uzvodni servis. Izvanmrežno — uspoređuje s `config/quality/model-lifecycle.json`, koji se ručno osvježava naredbom `npm run quality:refresh-model-lifecycle` (zahtijeva mrežu; nije uključen u CI). `allowedRetiredInCatalog` je jednosmjerna kontrola postupnog uklanjanja: unos dodajte samo uz problem za praćenje. | Da | -| `check:fetch-targets` | Svaki `fetch("/api/...")` u klijentskom `src/` razrješava se na stvarni `route.ts` | Da | -| `check:deps` | Sve ovisnosti koje se mogu instalirati naredbom `npm install` u svakoj datoteci `package.json` u repozitoriju nalaze se u `dependency-allowlist.json`; nove ovisnosti bez fiksirane verzije ili paketi nastali slopsquattingom označavaju se | Da | -| `audit:deps` | `npm audit` (korijenski direktorij + Electron) — nema upozorenja visoke/kritične razine (preklapa se s OSV provjerom `check:vuln-ratchet`; pogledajte Popis za naknadnu racionalizaciju) | Da | -| `check:lockfile` | Integritet datoteke `package-lock.json` — HTTPS registar, sažeci integriteta, bez nadjačavanja domaćina | Da | -| `check:licenses` | Popis dopuštenih SPDX licenci za produkcijske ovisnosti | Da | -| `check:tracked-artifacts` | Nema artefakata izgradnje ni predanih simboličkih poveznica `node_modules` (pokreće se i u husky pre-commit provjeri; pre-push je namjerno jednostavan — #6716) | Da | -| `check:ai-attribution` | Nema AI/bot `Co-Authored-By` oznake ni podnožja o AI generiranju u commitovima, naslovu ili tijelu PR-a — Strogo pravilo #16 (u petlji brzih provjera datoteke `quality.yml` za PR→`release/**` — čita sadržaj događaja, ne radi ništa izvan PR-ova — te kao korak samo za PR u lint provjeri datoteke `ci.yml` za PR→`main`; također i husky `commit-msg` hook; ljudski suautori su dopušteni; #14436) | -| `check:vitest-exclusions` | Svako Vitest izuzimanje navodi problem za praćenje i nalazi se u `config/quality/vitest-exclusions.json` (#13204) | Da | -| `check:file-size` | Nijedna izvorna datoteka ne premašuje ograničenje za svoju ekstenziju (zapinjač: velike zamrznute datoteke na popisu `frozen`) | Da | -| `check:error-helper` | Odgovori o pogreškama u izvršiteljima/rukovateljima koriste `buildErrorBody()` / `sanitizeErrorMessage()` (Strogo pravilo #12) | Da | -| `check:migration-numbering` | Migracijske SQL datoteke numerirane su uzastopno, bez praznina ili duplikata | Da | -| `check:public-creds` | Nema doslovnih OAuth vrijednosti `client_id`/`client_secret` ni Firebase Web ključeva izvan datoteke `publicCreds.ts` (strogo pravilo #11) | Da | -| `check:db-rules` | Nema neobrađenog SQL-a izvan modula `src/lib/db/`; nema zbirnih uvoza iz `localDb.ts` (stroga pravila #2/#5) | Da | -| `check:known-symbols` | Izvršitelji pružatelja usluga, strategije usmjeravanja i prevoditelji registrirani u svojim tablicama otpreme odgovaraju datotekama na disku — nema napuštenih ni nedeklariranih simbola | Da | -| `check:route-guard-membership` | Svaka ruta koja pokreće podređeni proces klasificirana je funkcijom `isLocalOnlyPath()` (stroga pravila #15/#17) | Da | -| `check:test-discovery` | Svaku datoteku `*.test.ts` / `*.spec.ts` u repozitoriju prikuplja barem jedan pokretač testova (čegrtaljka: popis napuštenih datoteka u `test-discovery-baseline.json` može se samo smanjivati) | Da | -| `check:agent-skills-sync` | Generirani artefakti agent-skills podudaraju se sa svojim izvornim katalogom (nema odstupanja) | -| `check:provider-asset-provenance` | Logotipi/resursi pružatelja imaju zabilježen unos o podrijetlu | -| `lint:json` | JSON konfiguracijske datoteke mogu se parsirati i zadovoljavaju pravila lintanja repozitorija | -| `typecheck:core` | Kompilacija TypeScripta bez pogrešaka (samo savjetodavna upozorenja) | Da | -| `typecheck:noimplicit:core` | Strogi `noImplicitAny` — usmjeren na budućnost; mnoga već postojeća mjesta poziva još trebaju anotacije | **Savjetodavno** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` ograničen na `src/app/(dashboard)/**` (#7033) — pažljivo odabran popis dopuštenih 27 datoteka u `typecheck:core` ne uključuje nijednu TSX datoteku nadzorne ploče, a ni `next build` je nikada ne provjerava tipovima (`next.config.mjs` postavlja `ignoreBuildErrors: true`), pa su regresije s nepronađenim identifikatorima u njima (#6625/#6909) bile nevidljive CI-ju. Razlike se uspoređuju sa zamrznutom osnovnom vrijednošću broja pogrešaka po datoteci i TS kodu (`config/quality/dashboard-typecheck-baseline.json`, isti obrazac provođenja zastarjelosti kao za `check:known-symbols`) — samo NOVE pogreške iznad osnovnog broja uzrokuju pad provjere; smanjite osnovnu vrijednost s `--update` kada se ispravi već postojeća pogreška. | Da | - -### Posao: `quality-gate` +`promijenjeni modul/simbol -> statički potrošač -> kandidat za povezani test` kada kandidat +za test nije prisutan u razlikama zahtjeva za povlačenje. Markdown sažetak i JSON rezultat čuvaju se kao +artefakt tijeka rada `forgotten-sibling-tests` radi kalibracije prije eventualnog uvođenja blokiranja. + +Ponovni izvozi iz zbirnih modula i dinamički uvozi služe samo za dijagnostiku razrješavanja; nikada ne stvaraju +nalaz koji blokira. Pregledane iznimke nalaze se u +`config/quality/forgotten-sibling-allowlist.json`. Svaki unos mora navesti potrošača i kandidatski +test, dati konkretno obrazloženje i sadržavati poveznicu na GitHub problem ili zahtjev za povlačenje. Neispravni unosi rezultiraju +blokiranjem. Iznimke ne mogu zanemariti izbrisani kandidatski test ni razliku koja dodaje `.skip`/`.todo`; +slabljenje tvrdnji i ostalo prikrivanje i dalje su u nadležnosti neovisno blokirajuće +kontrole `check:test-masking`. + +### Zadatak: `lint` + +Pokreće se za svaki PR prema grani `main`. U slučaju neuspjeha blokira spajanje. + +| Skripta (`npm run ...`) | Provjerava | Blokira | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Je li verzija Node.js-a unutar podržanog raspona | Da | +| `check:cycles` | Kružne uvoze kroz cijeli `src/` + `open-sse/` (na temelju AST-a, uz razriješene `paths` iz tsconfiga). Samostalno pokretanje = savjetodavno, navodi cikluse. `check:cycles:ratchet` (koji CI pokreće) blokira kada broj premaši gornju granicu `metrics.cycles` u datoteci `quality-baseline.json` — trenutačno 14, `direction: down`, pa se može samo smanjivati (#15159 G-01/G-02) | Da (ratchet) | +| `check:route-validation:t06` | Prisutnost Zod shema na svim rutama (pravilo razine 6) | Da | +| `check:any-budget:t11` | Broj pojavljivanja `@ts-expect-error // any` ne premašuje dopušteni prag (zapinjača razine 11) | Da | +| `check:provider-consistency` | Svaki pružatelj u `providers.ts` ima odgovarajući unos u `providerRegistry.ts` (i obratno, unutar popisa dopuštenih) | Da | +| `check:model-lifecycle` | Tri ručno održavane tablice usmjeravanja ostaju usklađene s pohranjenom snimkom životnog ciklusa (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ne boduje nijedan povučeni ID koji `REGISTRY` može usmjeriti; svaki cilj iz `BUILT_IN_ALIASES` prisutan je u `REGISTRY` i nije prisutan u snimci povučenih ID-ova; svaki povučeni ID koji je još uvijek u `REGISTRY` prosljeđuje se ili je naveden u `allowedRetiredInCatalog`; te se nijedan izvor ni cilj iz `DEFAULT_DEGRADATION_MAP` ne pojavljuje kao povučen u toj snimci. To ne dokazuje da model trenutačno poslužuje aktivni uzvodni servis. Izvanmrežno — uspoređuje se s `config/quality/model-lifecycle.json`, koji se ručno osvježava naredbom `npm run quality:refresh-model-lifecycle` (zahtijeva mrežu; nije uključen u CI). `allowedRetiredInCatalog` je zaporni mehanizam postupnog smanjivanja: unos dodajte samo uz problem za praćenje. | Da | +| `check:fetch-targets` | Svaki `fetch("/api/...")` u klijentskom `src/` razrješava se na stvarni `route.ts` | Da | +| `check:deps` | Sve ovisnosti koje je moguće instalirati naredbom `npm install` u svim datotekama `package.json` u repozitoriju nalaze se u `dependency-allowlist.json`; novi paketi bez fiksirane verzije ili paketi nastali slopsquattingom označavaju se | Da | +| `audit:deps` | `npm audit` (korijen + electron) — nema upozorenja visoke/kritične razine (preklapa se s osv `check:vuln-ratchet`; pogledajte popis za racionalizaciju) | Da | +| `check:lockfile` | Integritet datoteke `package-lock.json` — https registar, sažeci integriteta, bez nadjačavanja poslužitelja | Da | +| `check:licenses` | Popis dopuštenih SPDX licenci za produkcijske ovisnosti | Da | +| `check:tracked-artifacts` | Nema artefakata izgradnje / predanih simboličkih poveznica `node_modules` (također se pokreće u husky pre-commit; pre-push je namjerno jednostavan — #6716) | Da | +| `check:ai-attribution` | Nema AI/bot prikolice `Co-Authored-By` ni podnožja o AI generiranju u predajama PR-a, naslovu ili tijelu — strogo pravilo #16 (u petlji brzih provjera `quality.yml` za PR→`release/**` — čita sadržaj događaja, ne radi ništa izvan PR-ova — i koraku samo za PR u provjeri lint datoteke `ci.yml` za PR→`main`; također husky kuka `commit-msg`; ljudski suautori su dopušteni; #14436) | +| `check:vitest-exclusions` | Svako izuzimanje iz Vitesta navodi problem za praćenje i nalazi se u `config/quality/vitest-exclusions.json` (#13204) | Da | +| `check:file-size` | Nijedna izvorna datoteka ne premašuje ograničenje za svoj nastavak (postupno ograničenje: zamrznute velike datoteke na popisu `frozen`) | Da | +| `check:error-helper` | Odgovori o pogreškama u izvršiteljima/obrađivačima upotrebljavaju `buildErrorBody()` / `sanitizeErrorMessage()` (strogo pravilo #12) | Da | +| `check:migration-numbering` | Migracijske SQL datoteke numerirane su uzastopno, bez praznina ili duplikata | Da | +| `check:public-creds` | Nema doslovnih OAuth vrijednosti `client_id`/`client_secret` ni Firebase Web ključeva izvan `publicCreds.ts` (strogo pravilo #11) | Da | +| `check:db-rules` | Nema sirovog SQL-a izvan modula `src/lib/db/`; nema zbirnih uvoza iz `localDb.ts` (stroga pravila #2/#5) | Da | +| `check:known-symbols` | Izvršitelji pružatelja usluga, strategije usmjeravanja i prevoditelji registrirani u svojim tablicama otpreme odgovaraju datotekama na disku — nema nepovezanih ili nedeklariranih simbola | Da | +| `check:route-guard-membership` | Svaka ruta koja pokreće podređeni proces klasificirana je funkcijom `isLocalOnlyPath()` (stroga pravila #15/#17) | Da | +| `check:test-discovery` | Svaku datoteku `*.test.ts` / `*.spec.ts` u repozitoriju prikuplja barem jedan pokretač testova (mehanizam zapinjače: popis nepovezanih datoteka u `test-discovery-baseline.json` može se samo smanjivati) | Da | +| `check:agent-skills-sync` | Generirani artefakti agent-skills odgovaraju svojem izvornom katalogu (bez odstupanja) | +| `check:provider-asset-provenance` | Logotipi/resursi pružatelja imaju zabilježen podatak o podrijetlu | +| `lint:json` | JSON konfiguracijske datoteke mogu se parsirati i zadovoljavaju pravila lintanja repozitorija | +| `typecheck:core` | Kompilacija TypeScripta bez pogrešaka (samo savjetodavna upozorenja) | Da | +| `typecheck:noimplicit:core` | Strogi `noImplicitAny` — usmjeren na budućnost; mnoga postojeća mjesta poziva i dalje trebaju anotacije | **Savjetodavno** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` ograničen na `src/app/(dashboard)/**` (#7033) — uređeni popis dopuštenih 27 datoteka za `typecheck:core` ne uključuje nijedan TSX nadzorne ploče, a ni `next build` ga nikada ne provjerava tipovima (`next.config.mjs` postavlja `ignoreBuildErrors: true`), pa su regresije nepovezanih identifikatora ondje (#6625/#6909) bile nevidljive CI-ju. Razlike se uspoređuju sa zamrznutom osnovnom vrijednošću broja pogrešaka po datoteci i po TS kodu (`config/quality/dashboard-typecheck-baseline.json`, isti obrazac provedbe zastarjelosti kao za `check:known-symbols`) — samo NOVE pogreške iznad osnovnog broja uzrokuju neuspjeh provjere; smanjite prag s `--update` kada se ispravi postojeća pogreška. | Da | + +### Zadatak: `quality-gate` Pokreće se nakon `test-coverage`. U slučaju neuspjeha blokira spajanje. | Skripta | Provjerava | Blokira | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `quality:collect` | Generira `quality-metrics.json` (broj ESLint upozorenja, pokrivenost iz objedinjenog izvješća segmenata) | Da (prethodi mehanizmu ratchet) | -| `quality:ratchet` | Nijedna metrika u `quality-baseline.json` nije nazadovala (ESLint upozorenja ≤ početna vrijednost; pokrivenost ≥ početna vrijednost) | Da | -| `check:duplication` | Dupliciranje koda (jscpd@4) ne premašuje početnu vrijednost iz `quality-baseline.json` | Da | +| `quality:collect` | Stvara `quality-metrics.json` (broj ESLint upozorenja, pokrivenost iz objedinjenog izvješća fragmenata) | Da (prethodi mehanizmu ratchet) | +| `quality:ratchet` | Nijedna metrika u `quality-baseline.json` nije nazadovala (ESLint upozorenja ≤ referentna vrijednost; pokrivenost ≥ referentna vrijednost) | Da | +| `check:duplication` | Dupliciranje koda (jscpd@4) ne premašuje referentnu vrijednost u `quality-baseline.json` | Da | | `check:complexity` | Ciklomatska složenost na razini datoteke ne premašuje ograničenje (osnovni ESLint `complexity` + `max-lines-per-function`) | Da | -| `check:cognitive-complexity` | Ratchet kognitivne složenosti (`eslint-plugin-sonarjs`) — zasebno ESLint pokretanje; CI objedinjuje oba u jedan korak `check:complexity-ratchets` | Da | -| `check:dead-code` | Ratchet nekorištenih izvoza / datoteka (knip) ne nazaduje u odnosu na početnu vrijednost | Da | -| `check:compression-budget` | Ograničenje referentnog mjerenja kompresije — minimalne uštede tokena po mehanizmu ne smiju nazadovati | Da | +| `check:cognitive-complexity` | Ratchet kognitivne složenosti (`eslint-plugin-sonarjs`) — zaseban prolaz ESLint-a; CI oba pokreće objedinjeno kao jedan korak `check:complexity-ratchets` | Da | +| `check:dead-code` | Ratchet nekorištenih izvoza / datoteka (knip) ne nazaduje u odnosu na referentnu vrijednost | Da | +| `check:compression-budget` | Ograničenje referentnog testa kompresije — minimalne uštede tokena po mehanizmu ne smiju nazadovati | Da | | `check:type-coverage` | Ratchet postotka tipiziranog koda (`type-coverage`) ne nazaduje; uglavnom obuhvaća `typecheck:noimplicit:core` | Da | -| `check:codeql-ratchet` | Broj otvorenih CodeQL upozorenja ne raste (čita putem `gh api`; uredno se preskače bez tokena) — učestalost osvježavanja i ručno pokretanje: pogledajte „CodeQL ratchet” u nastavku | Da | - -### Zadatak: `quality-extended` - -Cijeli je zadatak savjetodavan (`continue-on-error: true`). Ratchet provjere temeljene na npm-u -izvršavaju se stvarno; vanjski se skeneri instaliraju putem `gh release download` i sami se -preskaču (izlazni kod 0) kada binarna datoteka još uvijek nije prisutna. - -| Skripta | Provjerava | Blokiranje | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| `check:circular-deps` | Nema kružnih ovisnosti (dpdm) | **Savjetodavno** | -| `check:bundle-size` | Veličina paketa ne premašuje ograničenje | **Savjetodavno** | -| `check:secrets` | Skeniranje tajni (gitleaks) — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | -| `check:vuln-ratchet` | Ranjivosti ovisnosti (osv-scanner) ne nazaduju — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | -| `check:workflows` | Provjera radnih tijekova (actionlint + zizmor); nedostajući/neispravni skeneri, nevažeća izvješća ili nedostajuća početna vrijednost ratcheta završavaju statusom INCOMPLETE. Valjani nalazi slijede odabrana stroga/savjetodavna/ratchet pravila | Izvršavanje je obvezno; zizmor ratchet blokira u CI-ju | -| `check:openapi-breaking` | Neusklađene promjene javnog API ugovora (`openapi.yaml`) u odnosu na osnovnu granu (oasdiff) — generira `openapiBreaking=N`; preskače se ako oasdiff nije prisutan ili se osnovna specifikacija ne može razriješiti | **Savjetodavno** | - -### Zadatak: `docs-sync-strict` - -Pokreće se pri svakom PR-u prema `main`. U slučaju neuspjeha blokira spajanje. - -| Skripta | Provjerava | Blokirajuće | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| `check:docs-all` | Meta-kontrola koja redom pokreće 6 podređenih kontrola u nastavku | Da | -| ↳ `check:docs-sync` | Usklađenost verzija u datotekama CHANGELOG / OpenAPI / llm.txt | Da | -| ↳ `check:docs-counts` | Brojevi u proznom tekstu (broj pružatelja, broj migracija itd.) nalaze se unutar dopuštenog raspona stvarnih vrijednosti | Da | -| ↳ `check:env-doc-sync` | Svaka varijabla okruženja iz `.env.example` dokumentirana je u tablici dokumentacije i obratno | Da | -| ↳ `check:deprecated-versions` | U dokumentaciji nema zastarjelih oznaka verzija | Da | -| ↳ `check:doc-links` | Interne markdown poveznice u dokumentaciji vode do stvarnih datoteka (oblik `[text]`/`(path)`) | Da | -| ↳ `check:fabricated-docs` | Rute, varijable okruženja, CLI naredbe, nazivi hookova i putanje datoteka navedeni u dokumentaciji postoje u bazi koda. Stroga kontrola putem `--strict`; bez te zastavice ne blokira. | Da (putem `--strict` u CI-ju) | -| `check:cli-i18n` | Nizovi CLI naredbi prisutni su u svim i18n datotekama lokalizacije | Da | -| `check:openapi-coverage` | OpenAPI specifikacija pokriva barem postupno podizani minimalni prag stvarnih ruta | Da | -| `check:openapi-security-tiers` | Oznake sigurnosnih razina u `openapi.yaml` usklađene su s klasifikacijama u `routeGuard.ts` | **Savjetodavno** | -| `check:openapi-routes` | Svaka putanja u `openapi.yaml` odgovara stvarnoj datoteci `route.ts` (zaštita od halucinacija) | Da | -| `check:docs-symbols` | Svaka referenca `/api/...` u `docs/**/*.md` odgovara stvarnoj datoteci `route.ts` (zaštita od halucinacija) | Da | -| `i18n translation drift` | Neprevedeni ključevi u i18n datotekama lokalizacije — samo upozorenje | **Savjetodavno** | +| `check:codeql-ratchet` | Broj otvorenih CodeQL upozorenja ne nazaduje (čita putem `gh api`; uredno preskače bez tokena) — učestalost osvježavanja i ručno pokretanje: pogledajte „CodeQL ratchet” u nastavku | Da | + +### Posao: `quality-extended` + +Cijeli je posao savjetodavan (`continue-on-error: true`). Ratchet provjere temeljene na npm-u zaista se +izvršavaju; vanjski se skeneri instaliraju putem `gh release download` i sami se preskaču (izlaz 0) +ako binarna datoteka i dalje nije prisutna. + +| Skripta | Provjerava | Blokira | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| `check:circular-deps` | Nema kružnih ovisnosti (dpdm) | **Savjetodavno** | +| `check:bundle-size` | Veličina paketa ne premašuje ograničenje | **Savjetodavno** | +| `check:secrets` | Skeniranje tajni (gitleaks) — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | +| `check:vuln-ratchet` | Ranjivosti ovisnosti (osv-scanner) ne nazaduju — preskače se ako binarna datoteka nije prisutna | **Savjetodavno** | +| `check:workflows` | Provjera tijekova rada (actionlint + zizmor); nedostajući/neispravni skeneri, nevaljana izvješća ili nedostajuća referentna vrijednost ratcheta završavaju kao INCOMPLETE. Valjani nalazi slijede odabrana pravila stroge/savjetodavne/ratchet provjere | Izvršavanje je obvezno; zizmor ratchet blokira u CI-ju | +| `check:openapi-breaking` | Kompatibilnost promjena javnog API ugovora (`openapi.yaml`) u odnosu na osnovnu granu (oasdiff) — stvara `openapiBreaking=N`; preskače se ako oasdiff nije prisutan ili osnovnu specifikaciju nije moguće razriješiti | **Savjetodavno** | + +### Posao: `docs-sync-strict` + +Pokreće se pri svakom PR-u prema grani `main`. U slučaju neuspjeha blokira spajanje. + +| Skripta | Provjerava | Blokirajuće | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | Meta-provjera koja redom pokreće 6 podređenih provjera | Da | +| ↳ `check:docs-sync` | Usklađenost verzija u CHANGELOG-u / OpenAPI-ju / llm.txt-u | Da | +| ↳ `check:docs-counts` | Brojevi u tekstu (broj pružatelja, broj migracija itd.) unutar su postupno postroženog raspona stvarnih vrijednosti | Da | +| ↳ `check:env-doc-sync` | Svaka varijabla okruženja u `.env.example` dokumentirana je u tablici dokumentacije i obratno | Da | +| ↳ `check:deprecated-versions` | U dokumentaciji nema zastarjelih nizova verzija | Da | +| ↳ `check:doc-links` | Interne markdown poveznice u dokumentaciji upućuju na stvarne datoteke (oblik `[tekst]`/`(putanja)`) | Da | +| ↳ `check:fabricated-docs` | Rute, varijable okruženja, CLI naredbe, nazivi hookova i putanje datoteka navedeni u dokumentaciji postoje u bazi koda. Stroga provjera uz `--strict`; bez te zastavice neuspjeh je upozorenje. | Da (uz `--strict` u CI-ju) | +| `check:cli-i18n` | Nizovi CLI naredbi prisutni su u svim datotekama i18n lokalizacija | Da | +| `check:openapi-coverage` | OpenAPI specifikacija pokriva barem postupno postroženu donju granicu stvarnih ruta | Da | +| `check:openapi-security-tiers` | Oznake sigurnosnih razina u `openapi.yaml` usklađene su s klasifikacijama u `routeGuard.ts` | **Savjetodavno** | +| `check:openapi-routes` | Svaka putanja u `openapi.yaml` upućuje na stvarni `route.ts` (sprječavanje haluciniranja) | Da | +| `check:docs-symbols` | Svaka referenca `/api/...` u `docs/**/*.md` upućuje na stvarni `route.ts` (sprječavanje haluciniranja) | Da | +| `i18n translation drift` | Neprevedeni ključevi u datotekama i18n lokalizacija — samo upozorenje | **Savjetodavno** | ### Posao: `i18n-ui-coverage` -| Skripta | Provjerava | Blokirajuće | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (inline) | Pokrivenost UI i18n ključeva iznosi ≥ 65% | Da | -| `check-ui-value-drift` (inline) | Prepisana engleska **vrijednost** ne ostavlja zastarjeli prijevod | Da | -| `check-new-key-coverage` (inline) | **Novi** engleski ključ preveden je u svakoj lokalizaciji — oznaka `__MISSING__:` nije dopuštena | Da | -| `check-translation-ratio` | Omjer stvarnih prijevoda po lokalizaciji (stavke identične engleskom izvorniku / rezervirana mjesta / nedostajuće stavke izvan popisa dopuštenih) ne smije premašiti `config/quality/i18n-translation-baseline.json` + toleranciju | **Savjetodavno** | +| Skripta | Provjerava | Blokirajuće | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `check-ui-keys-coverage` (ugrađeno) | Pokrivenost UI i18n ključeva iznosi ≥ 65% | Da | +| `check-ui-value-drift` (ugrađeno) | Nakon prepisivanja engleske **vrijednosti** ne ostaje nijedan zastarjeli prijevod | Da | +| `check-new-key-coverage` (ugrađeno) | **Novi** engleski ključ preveden je u svakoj lokalizaciji — oznaka `__MISSING__:` nije dopuštena | Da | +| `check-translation-ratio` | Omjer stvarnih prijevoda po lokalizaciji (vrijednosti identične engleskima / rezervirana mjesta / nedostajući listovi izvan popisa dopuštenih) ne smije premašiti `config/quality/i18n-translation-baseline.json` + toleranciju | **Savjetodavno** | -Zahtijeva `fetch-depth: 0` — kontrola promjene vrijednosti uspoređuje `en.json` s bazom spajanja. +Zahtijeva `fetch-depth: 0` — provjera odstupanja vrijednosti uspoređuje `en.json` s bazom spajanja. -#### `check-ui-value-drift` — kontrola zastarjelih prijevoda +#### `check-ui-value-drift` — provjera zastarjelih prijevoda -Otkriva jednu i18n regresiju koju ostale kontrole strukturno ne mogu uočiti: engleska je vrijednost -prepisana, a prijevodi izvedeni iz _prethodnog_ engleskog teksta ostaju nepromijenjeni, pa +Otkriva onu i18n regresiju koju ostale provjere strukturno ne mogu uočiti: engleska se vrijednost +prepiše, a prijevodi izvedeni iz _prethodne_ engleske vrijednosti ostanu, pa korisnici koji ne govore engleski nastavljaju čitati samouvjereno formuliran, ali sada netočan tekst. -To se doista dogodilo u produkciji. `oauthModal.googleOAuthWarning` prepisan je kada je uveden pomoćnik -za prijavu Antigravity (#5203); u **39 od 43 lokalizacije** ostao je tekst koji operaterima govori da "kopiraju -cijeli URL i zalijepe ga ispod" — tijek koji za tog pružatelja nije moguće dovršiti. Problem je -ostao neprimijećen sve do #8463 jer: +To se doista dogodilo u produkciji. `oauthModal.googleOAuthWarning` prepisan je kada je uveden pomoćnik za +prijavu Antigravity (#5203); u **39 od 43 lokalizacije** ostao je tekst koji operaterima govori da "kopiraju +cijeli URL i zalijepe ga ispod" — postupak koji se za tog pružatelja ne može dovršiti. To je ostalo +neprimijećeno sve do #8463 jer: -- `sync-ui-keys` nadopunjuje samo ključeve koji **nedostaju**, nikada one koji su **zastarjeli**; -- `check-ui-keys-coverage` broji _prisutnost_ ključeva, pa se zastarjeli prijevod smatra pokrivenim; -- `check-translation-drift` prati dokumentacijske preslike `docs/i18n//**.md` — - nikada ne čita `src/i18n/messages/*.json`. Blokira u poslu `docs-sync-strict` od ponovne - sinkronizacije 2026-09: uredite temeljni dokument → `npm run i18n:run -- --files=` (na razini odjeljka, brzo). +- `sync-ui-keys` dodaje samo ključeve koji **nedostaju**, nikada one koji su **zastarjeli**; +- `check-ui-keys-coverage` broji _prisutnost_ ključa, pa se zastarjeli prijevod smatra pokrivenim; +- `check-translation-drift` prati zrcalne kopije dokumentacije `docs/i18n//**.md` — + nikada ne čita `src/i18n/messages/*.json`. Blokirajuće u poslu `docs-sync-strict` od + ponovne sinkronizacije 2026-09: uredite temeljni dokument → `npm run i18n:run -- --files=` (na razini odjeljka, brzo). -**Uvažava razlike, ne oslanja se na osnovno stanje.** Uspoređuje `en.json` u zajedničkoj bazi spajanja s -radnim stablom; za svaki ključ čija se engleska vrijednost promijenila zastario je svaki lokalitet koji još uvijek sadrži -neizmijenjeni prijevod. Time se namjerno **zamrzava postojeći dug** — razlika -ne može otkriti iz koje je stare engleske vrijednosti nastao dugogodišnji prijevod, pa kontrola procjenjuje -samo ono što trenutačna promjena dotiče. Alternativa (osnovno stanje s hashom za svaki ključ) zahtijevala bi -generiranu datoteku od ~600 KB, 3× veću od najvećeg postojećeg osnovnog stanja, koja bi se mijenjala sa svakim i18n PR-om. +**Svjestan razlika, bez oslanjanja na osnovno stanje.** Uspoređuje `en.json` u bazi spajanja s +radnim stablom; za svaki ključ čija se engleska vrijednost promijenila zastario je svaki lokalitet +koji još sadržava neizmijenjeni prijevod. Time se namjerno **zamrzava postojeći dug** — razlika +ne može otkriti iz koje je stare engleske vrijednosti nastao dugotrajni prijevod, pa kontrola +procjenjuje samo ono što trenutačna promjena obuhvaća. Alternativa (osnovno stanje s hashom za +svaki ključ) zahtijevala bi generiranu datoteku od ~600 KB, 3× veću od najvećeg postojećeg +osnovnog stanja, koja bi se mijenjala pri svakom i18n PR-u. -Postoje dva načina da se uvjet zadovolji: +Postoje dva načina da se provjera zadovolji: -1. ažurirajte zahvaćene prijevode ili -2. postavite ih na `__MISSING__:` — tada se tijekom izvođenja poslužuje ispravljeni engleski tekst - (`src/i18n/request.ts::deepMergeFallback`, #7258), a ključ se stavlja u red za prevođenje. +1. ažurirati obuhvaćene prijevode ili +2. postaviti ih na `__MISSING__:` — izvođenje tada poslužuje ispravljeni + engleski tekst (`src/i18n/request.ts::deepMergeFallback`, #7258), a ključ se stavlja u red za prijevod. -Ako se promijenilo **značenje** niza, radije **preimenujte ključ**: novi ključ ne može naslijediti -zastarjeli prijevod. Taj je obrazac upotrijebljen u #8463. +Ako se promijenilo **značenje** teksta, preporučuje se **preimenovati ključ**: novi ključ ne može +naslijediti zastarjeli prijevod. Taj je obrazac upotrijebljen u #8463. ```bash npm run i18n:check-value-drift # strogo (ono što CI pokreće) @@ -231,16 +236,16 @@ npm run i18n:check-value-drift:warn # samo izvješće BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Završava s kodom 0 i porukom `SKIP reason=base-unresolved` kada se osnovni katalog ne može pročitati (plitki -klon bez osnovne reference), u skladu s ponašanjem `check-openapi-breaking`. +Završava s kodom 0 i porukom `SKIP reason=base-unresolved` kada se osnovni katalog ne može +pročitati (plitki klon bez osnovne reference), slijedeći ponašanje `check-openapi-breaking`. ### Zadatak: `i18n` -Potpuna matrica provjere i18n-a (jedan zadatak po lokalitetu). Cijeli je zadatak savjetodavan. +Potpuna matrica provjere valjanosti i18n-a (jedan zadatak po lokalitetu). Cijeli je zadatak savjetodavan. -| Skripta | Provjerava | Blokiranje | -| ------------------------------- | -------------------------------------- | -------------------------------------------------------------- | -| `validate_translation.py quick` | Potpunost prijevoda za svaki lokalitet | **Savjetodavno** (`continue-on-error: true` za cijeli zadatak) | +| Skripta | Provjerava | Blokiranje | +| ------------------------------- | --------------------------------- | -------------------------------------------------------------- | +| `validate_translation.py quick` | Potpunost prijevoda po lokalitetu | **Savjetodavno** (`continue-on-error: true` za cijeli zadatak) | ### Zadatak: `pr-test-policy` @@ -261,18 +266,18 @@ Pokreće se nakon `build`. U slučaju neuspjeha blokira spajanje. | `test:vitest` | MCP poslužitelj (110 alata), autoCombo, predmemorija — vitest pokretač | Da | | `test:vitest:ui` | Testovi komponenti korisničkog sučelja — vitest pokretač | **Blokira** — postojeći neuspjesi izričito su izuzeti u `vitest.config.ts`; novi neuspjesi uzrokuju neuspjeh zadatka | -### Noćni tijekovi rada (planirani, savjetodavni) +### Noćni tijekovi rada (zakazani, savjetodavni) Pokreću se prema cron rasporedu (i putem `workflow_dispatch`), nikada za PR-ove. Svi su savjetodavni. -| Tijek rada | Provjerava | Blokiranje | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | fast-check testovi svojstava s nasumičnim početnim brojem i velikim brojem izvođenja | **Savjetodavno** | -| `nightly-resilience` | kontrola rasta hrpe, ubrizgavanje kaotičnih kvarova, k6 testovi opterećenja/izdržljivosti | **Savjetodavno** | -| `nightly-llm-security` | promptfoo zaštita od umetanja (način blokiranja) + garak sonde (preskaču se bez tajne pružatelja usluge) | **Savjetodavno** | -| `nightly-schemathesis` | Neizrazito testiranje OpenAPI ugovora (schemathesis) na aktivnom OmniRouteu uz `docs/openapi.yaml` — otkriva kršenja specifikacije / neobrađene pogreške 500 (Faza 8 B.4) | **Savjetodavno** | -| `nightly-mutation` | Rezultat Stryker mutacijskog testiranja nad brzom jedinicom testiranja — preživjeli mutanti otkrivaju slabe provjere | **Savjetodavno** | -| `nightly-compat` | Matrica kompatibilnosti Node pogona u podržanim rasponima `engines.node` | **Savjetodavno** | +| Tijek rada | Provjerava | Blokiranje | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `nightly-property` | fast-check testovi svojstava s nasumičnom početnom vrijednošću i velikim brojem izvođenja | **Savjetodavno** | +| `nightly-resilience` | kontrola rasta hrpe, kaotično ubrizgavanje pogrešaka, k6 testovi opterećenja/izdržljivosti | **Savjetodavno** | +| `nightly-llm-security` | promptfoo zaštita od ubrizgavanja (način blokiranja) + garak sonde (preskaču se bez tajne pružatelja usluge) | **Savjetodavno** | +| `nightly-schemathesis` | Fuzz testiranje OpenAPI ugovora (schemathesis) na aktivnom OmniRouteu koristeći `docs/openapi.yaml` — otkriva kršenja specifikacije / neobrađene pogreške 500 (Faza 8 B.4) | **Savjetodavno** | +| `nightly-mutation` | Rezultat Stryker mutacijskog testiranja na brzoj jedinici — preživjeli mutanti otkrivaju slabe provjere | **Savjetodavno** | +| `nightly-compat` | Matrica kompatibilnosti Node pogona za podržane raspone `engines.node` | **Savjetodavno** | --- @@ -537,43 +542,43 @@ pogrešku povezivanja i nikada ne prekida sesije. --- -## Zaostatak racionalizacije (pregled ROI-ja — Faza 9 Val 3) +## Zaostatak racionalizacije (pregled ROI-ja — faza 9, val 3) -Ovaj je popis usklađen s `ci.yml` 2026-06-17 (u prethodnoj su verziji izostavljeni +Ovaj je inventar usklađen s `ci.yml` 2026-06-17 (u prethodnoj su verziji izostavljeni `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Pregled ROI-ja usklađenog skupa utvrdio je sljedeće kandidate za racionalizaciju. **Spajanja su mehaničke promjene CI-ja; -promjene statusa/uklanjanja odluke su o pravilima rezervirane za operatera.** Ništa od -navedenog još nije primijenjeno. +promjene statusa/uklanjanja političke su odluke rezervirane za operatera.** Ništa navedeno +u nastavku još nije primijenjeno. -**Također prethodno nedokumentirano** (savjetodavno, slab signal): zadatak `docs-lint` -(markdownlint + Vale, cijeli zadatak ima `continue-on-error`) i samostalni tijekovi rada skenera +**Također nije dokumentirano iznad** (savjetodavno, slab signal): posao `docs-lint` +(markdownlint + Vale, cijeli posao s `continue-on-error`) i samostalni tijekovi rada skenera `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` nalazi se u -`quality-baseline.json`, ali nije povezan s blokirajućim zapornim mehanizmom u `ci.yml` — metrika je +`quality-baseline.json`, ali nije povezan s blokirajućom čegrtaljkom u `ci.yml` — metrika je trenutačno nepovezana. -### Spajanje / uklanjanje duplikata (mehaničko, manji rizik) +### Spajanje / uklanjanje duplikata (mehanički, manji rizik) -Svaki je kandidat provjeren u odnosu na aktivno stanje kontrolnih točaka 2026-06-17 (vjeruj, ali provjeri); -pokazalo se da nekoliko „očiglednih” spajanja skriva dug i **nisu** izravne zamjene bez poteškoća. +Svaki je kandidat provjeren u odnosu na aktivno stanje kontrola 2026-06-17 (vjeruj, ali provjeri); +pokazalo se da nekoliko „očiglednih” spajanja skriva dug i **nisu** izravno primjenjive zamjene. -- **`check:docs-sync` izvodi se dvaput** — samostalno u zadatku `lint` i ponovno unutar `check:docs-all` (`docs-sync-strict`) te u husky pre-commit kuki. ✅ **DOVRŠENO** — uklonjeno je samostalno pokretanje iz zadatka `lint`. -- **Skeniranje CVE-ova** — ❌ **NIJE jednostavno spajanje.** `audit:deps` strogo ne prolazi u slučaju bilo kojeg CVE-a visoke/kritične razine; `check:vuln-ratchet` (osv) ne prolazi samo u slučaju _regresije_ u odnosu na osnovnu vrijednost (trenutačno 1 MODERATE). Različita semantika — uklanjanjem `audit:deps` izgubila bi se apsolutna kontrolna točka za visoku/kritičnu razinu. Zadržati oboje. -- **Otkrivanje ciklusa** — ❌ **NIJE jednostavno spajanje.** `check:circular-deps` (dpdm) prijavljuje **91 ciklus** (zbog toga je savjetodavan); ne može postati blokirajući bez njihova prethodnog rješavanja, a ima širi opseg od zelenog, ciljano odabranog `check:cycles`. Zadržati `check:cycles` kao blokirajući; rješavanje 91 dpdm ciklusa zasebna je stavka zaostatka. -- **Složenost** — ✅ **DOVRŠENO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jedan ESLint prolaz, brojanje po ruleId-u kako bi osnovne vrijednosti za ciklomatsku složenost + najveći broj redaka i kognitivnu složenost ostale neovisne; pojedinačni `check:complexity` / `check:cognitive-complexity` ostaju za lokalni `--update`. -- **Sprječavanje halucinacija za `/api`** — ✅ **DOVRŠENO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jedan FS popis za `src/app/api`, openapi-routes + docs-symbols i dalje izvještavaju neovisno; pojedinačne provjere ostaju za lokalna pokretanja. -- **`check:node-runtime` izvodi se u 11 zadataka** — ⚠️ **nizak ROI.** Svaki se izvršava na zasebnom izvršitelju, a provjera traje <1 s; ukupna ušteda ~10 s, uz gubitak jeftine zaštite po zadatku. Nije vrijedno promjena. -- **`typecheck:noimplicit:core` u CI zadatku lint** — ✅ **uklonjen iz zadatka lint** (bio je savjetodavan uz `continue-on-error`); blokirajuću tipsku površinu čine `typecheck:core` + `check:type-coverage`. Lokalna je skripta zadržana. +- **`check:docs-sync` izvodi se dvaput** — samostalno u poslu `lint` i ponovno unutar `check:docs-all` (`docs-sync-strict`) te husky pre-commit kuke. ✅ **GOTOVO** — uklonjeno je samostalno pozivanje iz posla `lint`. +- **Skeniranje CVE-ova** — ❌ **NIJE jednostavno spajanje.** `audit:deps` završava neuspjehom pri bilo kojem CVE-u visoke/kritične razine; `check:vuln-ratchet` (osv) završava neuspjehom samo pri _regresiji_ u odnosu na osnovnu vrijednost (trenutačno 1 MODERATE). Semantike se razlikuju — uklanjanjem `audit:deps` izgubila bi se apsolutna kontrola za visoku/kritičnu razinu. Zadržati oboje. +- **Otkrivanje ciklusa** — ✅ **GOTOVO** (#15159 G-01/G-02). Stari je tekst ovdje nazivao `check:cycles` „zelenom, selektivnom” kontrolom i opravdavao njezino zadržavanje kao blokirajuće jer je `check:circular-deps` (dpdm) prijavljivao 91 ciklus. To zeleno stanje bilo je **lažno zeleno**: `check:cycles` skenirao je 5 poddirektorija (450 datoteka), prepoznavao samo statičke obrasce `import|export … from` te odbacivao svaki specifikator `@/` i `@omniroute/open-sse/`, pa nije mogao uočiti cikluse dinamičkog uvoza i aliasa koji su prevladavali u repozitoriju. Ispravljeno: kontrola sada obilazi `src` + `open-sse` (5023 datoteke), prikuplja specifikatore iz TypeScript AST-a (tako da se `import("…")` računa, a `typeof import("…")` na položaju tipa ne računa) te razrješava tsconfig `paths`. Pronalazi **14** ciklusa, a ne 0. Budući da se 14 postojećih ciklusa ne može ispraviti u PR-u kontrole, `check:cycles` sada je **čegrtaljka** (`--ratchet`, gornja granica `metrics.cycles.value = 14` u `quality-baseline.json`, `direction: down`) — blokira svaku _regresiju_, a broj se može samo smanjivati. CI izvodi `npm run check:cycles:ratchet`. Postupno smanjivanje provodi se uz **A-01**. `check:circular-deps` (dpdm) ostaje savjetodavan kao šire drugo mišljenje. +- **Složenost** — ✅ **GOTOVO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jedan ESLint prolaz, brojanje prema ruleId-u kako bi osnovne vrijednosti ciklomatske složenosti + maksimalnog broja redaka i kognitivne složenosti ostale neovisne; pojedinačni `check:complexity` / `check:cognitive-complexity` ostaju za lokalni `--update`. +- **Zaštita `/api` od halucinacija** — ✅ **GOTOVO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jedan inventar datotečnog sustava za `src/app/api`; openapi-routes + docs-symbols i dalje izvješćuju neovisno; pojedinačne provjere ostaju za lokalna izvođenja. +- **`check:node-runtime` izvodi se u 11 poslova** — ⚠️ **nizak ROI.** Svaki je zaseban izvršitelj, a provjera traje <1 s; ukupna ušteda iznosi ~10 s, uz gubitak jeftine zaštite po poslu. Nije vrijedno previranja. +- **`typecheck:noimplicit:core` u CI poslu lint** — ✅ **uklonjeno iz posla lint** (bilo je savjetodavno uz `continue-on-error`); blokirajuću površinu tipova čine `typecheck:core` + `check:type-coverage`. Lokalna skripta je zadržana. -### Promijeniti status / odlučiti (pravila operatera) +### Promijeniti status / odlučiti (politika operatera) -- `check:openapi-security-tiers` (savjetodavan) — ❌ **NIJE moguće jednostavno učiniti blokirajućim.** Završava kodom 0, ali upozorava da nekoliko ruta `traffic-inspector` pod `LOCAL_ONLY_API_PREFIXES` nema oznaku `x-loopback-only: true`. Za njegovo provođenje najprije treba dodati te oznake u `openapi.yaml`. -- `typecheck:noimplicit:core` (savjetodavan) — u velikoj ga mjeri zamjenjuje blokirajući zaporni mehanizam `check:type-coverage`. Pretvoriti ga u zaporni mehanizam ili ukloniti suvišan drugi prolaz `tsc`. -- `test:vitest:ui` (sada **blokirajući**) — postojeći kvarovi izričito su izuzeti u `vitest.config.ts` uz komentare za praćenje `// #8618`; novi kvarovi uzrokuju neuspjeh zadatka. -- `check:secrets` (gitleaks, blokirajući zaporni mehanizam zamrznut na 3 dokumentirana lažno pozitivna nalaza) — dodati ta 3 nalaza na popis dopuštenih kako bi se došlo do 0 ili ga degradirati na savjetodavni. Preklapa se s GitHubovim izvornim skeniranjem tajni + `check:public-creds`. -- `check:pr-evidence` (blokirajući, pretražuje prozni tekst tijela PR-a) — visok rizik od lažno pozitivnih nalaza; uklanjanje slabi provedbu strogog pravila br. 18, pa je ovo stvarna odluka o pravilima. -- `semgrep` (savjetodavni samostalni tijek rada) — preklapa se s CodeQL-om za OWASP obitelji; povezati njegovu osnovnu vrijednost sa zapornim mehanizmom ili ga ukloniti. +- `check:openapi-security-tiers` (savjetodavno) — ❌ **NE može se jednostavno prebaciti.** Završava s 0, ali upozorava da nekoliko ruta `traffic-inspector` pod `LOCAL_ONLY_API_PREFIXES` nema oznaku `x-loopback-only: true`. Za njegovo nametanje najprije je potrebno dodati te oznake u `openapi.yaml`. +- `typecheck:noimplicit:core` (savjetodavno) — uglavnom ga zamjenjuje blokirajuća čegrtaljka `check:type-coverage`. Prebaciti na čegrtaljku ili ukloniti suvišni drugi prolaz `tsc`. +- `test:vitest:ui` (sada **blokirajuće**) — postojeći neuspjesi izričito su isključeni u `vitest.config.ts` uz komentare za praćenje `// #8618`; novi neuspjesi uzrokuju neuspjeh posla. +- `check:secrets` (gitleaks, blokirajuća čegrtaljka zamrznuta na 3 dokumentirana lažno pozitivna rezultata) — dodati ta 3 rezultata na popis dopuštenih kako bi se došlo do 0 ili spustiti na savjetodavni status. Preklapa se s GitHubovim izvornim skeniranjem tajni + `check:public-creds`. +- `check:pr-evidence` (blokirajuće, pretražuje prozni tekst tijela PR-a) — visok rizik od lažno pozitivnih rezultata; uklanjanje slabi provedbu strogog pravila br. 18, stoga je ovo stvarna politička odluka. +- `semgrep` (savjetodavni samostalni tijek) — preklapa se s CodeQL-om za OWASP obitelji; povezati njegovu osnovnu vrijednost s čegrtaljkom ili ga ukloniti. --- diff --git a/docs/i18n/hr/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/hr/docs/compression/COMPRESSION_GUIDE.md index e3be208bcaaf..6edf95fb411f 100644 --- a/docs/i18n/hr/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/hr/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ ubrizgava umjesto engleske. Na tijelu s `messages`, zaobilaznica sadržaja (`shouldBypassCavemanOutputMode()` u `open-sse/services/compression/outputMode.ts`) provjerava posljednje tri poruke i preskače stilove za cijeli krug kada se podudaraju s njezinim ključnim riječima za sigurnost, nepovratne radnje, -pojašnjenje ili osjetljive na redoslijed. Zaobilaznica se pokreće bez obzira na to kako je postavljena -preklopka **Auto-Clarity Bypass** na nadzornoj ploči (`cavemanOutputMode.autoClarity`). +pojašnjenje ili osjetljive na redoslijed. Zaobilaznica se pokreće dok je preklopka **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) na nadzornoj ploči uključena, što je zadana postavka; kad je preklopka isključena, odabrani stilovi primjenjuju se i na tim krugovima. Kada zaobilaznica propusti krug, `placeSystemInstruction()` (ista datoteka), koja nikada ne stvara novu `messages[0]`, postavlja blok u prvu od ovih koje pronađe: diff --git a/docs/i18n/hr/docs/reference/ENVIRONMENT.md b/docs/i18n/hr/docs/reference/ENVIRONMENT.md index e97c62463691..1e79b41ae276 100644 --- a/docs/i18n/hr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/hr/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Obavezne tajne -Ove vrijednosti **moraju** biti postavljene prije prvog pokretanja. Bez njih će se aplikacija odbiti pokrenuti ili će raditi s nesigurnim zadanim vrijednostima. - -| Varijabla | Obavezno | Zadano | Izvorna datoteka | Opis | -| ---------------------------- | ------------------- | ------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Da** | _(nema)_ | `src/lib/auth` | Potpisuje/provjerava sve kolačiće sesije nadzorne ploče (JWT). Generirajte pomoću `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Da** | _(nema)_ | `src/lib/db/apiKeys.ts` | AES ključ za šifriranje vrijednosti API ključeva pohranjenih u bazi SQLite. Generirajte pomoću `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Da** | `CHANGEME` | Skripta za početno postavljanje | Postavlja početnu administratorsku lozinku za nadzornu ploču (podudara se sa zadanom vrijednošću u `.env.example` — namjerno je očito nesigurna kako bi se prisilila promjena). **Promijenite prije prve uporabe.** Nakon prijave promijenite je putem Nadzorna ploča → Postavke → Sigurnost. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (produkcija) | _(nije postavljeno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Dijeljena tajna za interni WebSocket most Codex Responses. Autentificira zahtjeve mosta između WS releja Electron/preglednika i OmniRoutea. ⚠️ **OBAVEZNO u produkciji — kada nije postavljena, svi se zahtjevi WS mosta odbijaju.** Generirajte pomoću `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplicitni identifikator za poništavanje predmemorije servisnog radnika za PWA ljusku (#11779); prvi u lancu razrješavanja. | -| `SOURCE_VERSION` | Ne | _(nije postavljeno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi u lancu — postavljaju ga PaaS sustavi za izgradnju (npr. u stilu Herokua) na implementirani commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(izvedeno)_ | `src/shared/components/PwaRegister.tsx` | Javna vrijednost iz vremena izgradnje koju klijent upotrebljava za registriranje `/sw.js?v=…`; izvodi se iz prethodne dvije vrijednosti, a zatim iz git SHA-a. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automatski) | _(automatski pri svakom pokretanju)_ | `src/server/authz/policies/management.ts` | Tajna po procesu koja dokazuje da je pouzdana oznaka IP adrese ravnopravnog čvora stigla s vlastitog HTTP poslužitelja OmniRoutea (`scripts/dev/peer-stamp.mjs`). Posrednički softver za autorizaciju vjeruje lokalnosti zahtjeva (ograničavanje ruta LOCAL_ONLY na povratnu petlju/LAN) samo kada oznaka sadrži ovaj token. Automatski se generira pri svakom pokretanju — ostavite je nepostavljenu; fiksirajte je samo za višaprocesne konfiguracije koje moraju dijeliti oznaku. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nije postavljeno)_ | `scripts/dev/peer-stamp.mjs` | IP adrese ili CIDR rasponi obrnutih proxy poslužitelja, odvojeni zarezima, čijim zaglavljima `X-Forwarded-For` / `X-Real-IP` smije vjerovati popis dopuštenih/zabranjenih IP adresa, uz adrese povratne petlje, adrese privatnih mreža i Cloudflareove rubne poslužitelje, kojima se uvijek vjeruje. Potrebno samo za proxy na bilo kojoj drugoj javnoj adresi; u suprotnom filtar procjenjuje vlastitu adresu tog proxyja. | +Nepostavljene tajne označene s **Da** automatski se generiraju pri prvom pokretanju (`JWT_SECRET`, `API_KEY_SECRET`) i pohranjuju u `server.env` u direktoriju podataka — postavite ih izričito kada je taj direktorij privremen. Izuzetak je `OMNIROUTE_WS_BRIDGE_SECRET`: on se ne pohranjuje u `server.env` — ulazne točke poslužitelja stvaraju privremenu vrijednost po pokretanju koju može koristiti samo most unutar procesa, pa ga vanjski klijenti mosta moraju postaviti izričito. + +| Varijabla | Obavezno | Zadano | Izvorna datoteka | Opis | +| ---------------------------- | ------------------- | ------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Da** | _(nema)_ | `src/lib/auth` | Potpisuje/provjerava sve kolačiće sesije nadzorne ploče (JWT). Generirajte pomoću `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Da** | _(nema)_ | `src/lib/db/apiKeys.ts` | AES ključ za šifriranje vrijednosti API ključeva pohranjenih u bazi SQLite. Generirajte pomoću `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ne | _(nije postavljeno)_ | `src/lib/auth/managementPassword.ts` | Postavlja lozinku nadzorne ploče pri pokretanju dok god nijedna lozinka nije spremljena; zanemaruje se čim je lozinka spremljena. Instalacije putem npm-a i iz izvornog koda kopiraju `.env.example` u `.env`, pa počinju s njegovim javno poznatim rezerviranim mjestom `CHANGEME`: zamijenite ga prije prvog pokretanja ili u tom `.env` postavite `INITIAL_PASSWORD=` (prazno) da biste lozinku stvorili u čarobnjaku za početno postavljanje prilikom prve posjete, kao što čini nepostavljena vrijednost (zadana vrijednost Docker slike). Spremljenu lozinku promijenite u Nadzorna ploča → Postavke → Sigurnost ili pokrenite `omniroute reset-password` (`node bin/reset-password.mjs` u izvornoj kopiji koda) s `DATA_DIR` postavljenim na direktorij podataka poslužitelja. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (produkcija) | _(nije postavljeno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Dijeljena tajna za interni WebSocket most Codex Responses. Autentificira zahtjeve mosta između WS releja Electron/preglednika i OmniRoutea. ⚠️ **OBAVEZNO u produkciji — kada nije postavljena, svi se zahtjevi WS mosta odbijaju.** Generirajte pomoću `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplicitni identifikator za poništavanje predmemorije servisnog radnika za PWA ljusku (#11779); prvi u lancu razrješavanja. | +| `SOURCE_VERSION` | Ne | _(nije postavljeno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi u lancu — postavljaju ga PaaS sustavi za izgradnju (npr. u stilu Herokua) na implementirani commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(izvedeno)_ | `src/shared/components/PwaRegister.tsx` | Javna vrijednost iz vremena izgradnje koju klijent upotrebljava za registriranje `/sw.js?v=…`; izvodi se iz prethodne dvije vrijednosti, a zatim iz git SHA-a. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automatski) | _(automatski pri svakom pokretanju)_ | `src/server/authz/policies/management.ts` | Tajna po procesu koja dokazuje da je pouzdana oznaka IP adrese ravnopravnog čvora stigla s vlastitog HTTP poslužitelja OmniRoutea (`scripts/dev/peer-stamp.mjs`). Posrednički softver za autorizaciju vjeruje lokalnosti zahtjeva (ograničavanje ruta LOCAL_ONLY na povratnu petlju/LAN) samo kada oznaka sadrži ovaj token. Automatski se generira pri svakom pokretanju — ostavite je nepostavljenu; fiksirajte je samo za višaprocesne konfiguracije koje moraju dijeliti oznaku. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nije postavljeno)_ | `scripts/dev/peer-stamp.mjs` | IP adrese ili CIDR rasponi obrnutih proxy poslužitelja, odvojeni zarezima, čijim zaglavljima `X-Forwarded-For` / `X-Real-IP` smije vjerovati popis dopuštenih/zabranjenih IP adresa, uz adrese povratne petlje, adrese privatnih mreža i Cloudflareove rubne poslužitelje, kojima se uvijek vjeruje. Potrebno samo za proxy na bilo kojoj drugoj javnoj adresi; u suprotnom filtar procjenjuje vlastitu adresu tog proxyja. | ### Naredbe za generiranje @@ -217,38 +217,38 @@ OmniRoute koristi **SQLite** (putem `better-sqlite3`) za svu trajnu pohranu. Ove ## 4. Sigurnost i autentifikacija -| Varijabla | Zadana vrijednost | Izvorna datoteka | Opis | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt koji se kombinira s hardverskim identifikatorima radi izrade otiska uređaja. Promijenite ga za svaku implementaciju radi izolacije. | -| `OMNIROUTE_CLI_SALT` | _(nije postavljeno = nasumični salt za svaku instalaciju, spremljen u `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt za izvođenje lokalnog CLI tokena za autentifikaciju. Postavljanjem ove vrijednosti rotiraju se svi CLI tokeni na uređaju, a ona uvijek ima prednost pred spremljenim saltom. Pogledajte `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Postavlja oznaku `Secure` na kolačićima sesije. **Mora biti `true`** kada se izvodi iza HTTPS-a. | -| `REQUIRE_API_KEY` | `false` | API posrednički softver | Kada je `true`, svi proxy zahtjevi prema `/v1/*` moraju sadržavati valjani API ključ. **Ova oznaka ne kontrolira `GET /v1/models`**, koji umjesto toga slijedi postavku prijave na nadzornu ploču (`requireAuthForModels`) — stoga `401` iz `/v1/models` NE znači da je izvođenje zaključivanja zaštićeno. Pogledajte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Omogućuje otkrivanje punih vrijednosti API ključeva u korisničkom sučelju nadzorne ploče. Može se konfigurirati putem zastavica značajki na nadzornoj ploči; predstavlja sigurnosni rizik na dijeljenim instancama. | -| `NO_LOG_API_KEY_IDS` | _(prazno)_ | `src/lib/compliance/index.ts` | ID-ovi API ključeva odvojeni zarezima za koje se preskače zapisivanje zahtjeva (usklađenost s GDPR-om). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nije postavljeno = neograničeno)_ | `src/shared/utils/apiKeyPolicy.ts` | Rezervni dnevni proračun zahtjeva koji se primjenjuje na API ključeve čiji je stupac `rate_limits` null. Nije postavljeno ili je prazno: nema implicitnog ograničenja (#2289, #11017). `0` ima isto značenje (neograničeno). Pozitivan cijeli broj N omogućuje N/dan, 5N/tjedan, 20N/mjesec. Neispravne vrijednosti koje nisu prazne vraćaju se na naslijeđena razdoblja od 1000/dan, 5000/tjedan i 20000/mjesec. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Najveća dopuštena veličina tijela zahtjeva. Odbija korisne sadržaje koji premašuju ovo ograničenje. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Stvarna tijela zahtjeva čija je veličina jednaka ovom pragu ili ga premašuje prije parsiranja JSON-a preuzimaju atomsku, procesno lokalnu propusnicu za prijam zahtjevnih zahtjeva (BYTE putanja, uključujući `POST /v1/responses`). Primjenjuje se isti mehanizam izlaza pri dostatnoj rezervi kapaciteta iz [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kao za strukturno zahtjevne zahtjeve; i dalje je ograničeno s `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) kako se problem [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne bi vratio. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Čvrsto ograničenje rute za chat koje se primjenjuje na broj bajtova pročitanih tijekom ograničenog učitavanja, uključujući zahtjeve s nedostajućim, nevaljanim ili lažnim zaglavljem `Content-Length`; prekoračenje rezultira odgovorom `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nije postavljeno — nema ograničenja broja zahtjeva)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ovo naslijeđeno ograničenje BROJA zahtjeva sada se primjenjuje samo kada je izričito postavljeno. Ako nije postavljeno (zadano), prihvat zahtjevnih chat zahtjeva umjesto toga ograničava `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatski izveden proračun BAJTOVA određen prema stvarnom memorijskom ograničenju procesa u **jednom procesu** (jedna V8 hrpa). Dva preklapajuća `/v1/responses` zahtjeva s ~750 tisuća tokena ruše hrpe od ~12 GiB (#7849) — to je upozorenje o **memorijskom proračunu**, a ne čvrsto ograničenje proizvoda na 2. Zdrav proces (hrpa ispod omjera rasterećenja) MOŽE prihvatiti više istodobnih dugih `/v1/responses` putem `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Deseci dugih SSE klijenata (40–50) ovise o hrpi + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, a ne o „najviše 2”. Slijepo povećavanje ove vrijednosti radi „korištenja cijelog poslužitelja” ponovno uvodi #7849. Umnožavajte **hrpe** s **N neovisnih `DATA_DIR` direktorija** (#11024); nikada nemojte koristiti `replicas>1` nad jednom SQLite datotekom. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatski izvedeno)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nadjačavanje automatski izvedenog proračuna bajtova učitavanja (25 % strožeg V8/cgroup memorijskog ograničenja podijeljeno s 8x prolaznim uvećanjem). Izvedene i izričite vrijednosti ograničavaju se na 8 MiB–2 GiB. Tijelo veće od efektivnog proračuna odmah ne uspijeva uz `413 body_exceeds_budget`; nadmetanje između tijela koja se pojedinačno mogu obraditi i dalje rezultira ponovljivim odgovorom `503`. 40–50 istodobnih dugih SSE klijenata ovisi o ovom proračunu + hrpi, a nije čvrsto ograničenje „najviše 2”. Prije ugađanja provjerite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Omjer rasterećenja pri pritisku na hrpu (`heapUsed / heap_size_limit`) za prihvat zahtjevnih zahtjeva prema BAJTOVIMA i STRUKTURI (#10183, #10268, #10437). Istodobni zahtjevni zahtjev iznad `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` odbacuje se ponovljivim odgovorom `503` samo kada je hrpa TAKOĐER na ovom omjeru ili iznad njega; uz zdravu hrpu prihvaća se putem dodatnog prostora za zdravu hrpu. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (zadano `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ograničeni dodatni kapacitet za brzi put zdrave hrpe (#10437) za **oba** kriterija, STRUKTURU i BAJTOVE (`admitChatRequest`, uključujući tijela ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez ovog ograničenja svaki bi zahtjev pri zauzetoj, ali zdravoj hrpi zaobilazio kontrolu prihvata bez gornje granice. Kada je ovoliko istodobnih najmova aktivno putem puta zdrave hrpe, daljnji zahtjevi pri zauzetosti prelaze na ISTI put ograničenog čekanja/rasterećenja koji se koristi pri stvarnom pritisku na hrpu. `0` potpuno onemogućuje zaobilaženje. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Broj poruka prema kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod praga bajtova. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Broj alata prema kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod praga bajtova. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativna procjena broja tokena na temelju veličine niza prema kojoj se zahtjev klasificira kao zahtjevan; to je aproksimacija troška prihvata, a ne tokenizacija za potrebe naplate pružatelja usluge. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (onemogućeno) | `src/shared/middleware/chatBodyAdmission.ts` | Neobavezno ograničenje povijesti chata koje se uključuje po želji. Zadano je onemogućeno: broj poruka pravilo je implementacije, a ne univerzalno svojstvo zahtjeva, pa postavljanje ograničenja ovdje odbacuje razgovore konačnim odgovorom `413` prije nego što ih cjevovod za kompresiju može učiniti obradivima. Rast hrpe ograničen je varijablom `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` i rasterećenjem pri pritisku na hrpu. Postavite pozitivnu vrijednost u implementacijama s ograničenom memorijom kojima je potrebna čvrsta gornja granica; prekoračenje tada rezultira strukturiranim odgovorom `413` koji zahtijeva sažimanje. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Čvrsto ograničenje za odgovor uzvodnog sustava bez strujanja koji se u cijelosti sprema u memoriju. Nakon prekoračenja čitač uzvodnog sustava otkazuje se, a zahtjev brzo ne uspijeva umjesto da neograničeni niz raste sve dok se hrpa ne iscrpi. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalan broj bajtova zaglavlja odgovora proslijeđenih od uzvodnog poslužitelja. Kada se proračun premaši, zaglavlja nižeg prioriteta (npr. prilagođena `x-codex-*`, `x-oai-request-id`) odbacuju se kako bi se ostalo unutar uobičajenih ograničenja zaglavlja obrnutog proxyja. Postavite višu vrijednost kako biste proslijedili više uzvodnih metapodataka, uz cijenu veće veličine zaglavlja odgovora. | -| `CORS_ORIGIN` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | Naslijeđeni CORS popis dopuštenih vrijednosti za jedno izvorište. Za nove implementacije preferirajte `CORS_ALLOWED_ORIGINS`. CORS služi samo za pregledničke API klijente s različitih izvorišta; autentificirani upisi na nadzornoj ploči umjesto toga upotrebljavaju zahtjeve s istog izvorišta i CSRF zaštitu povezanu sa sesijom. | -| `CORS_ALLOWED_ORIGINS` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | CORS popis dopuštenih vrijednosti odvojenih zarezima. Zamjenski znak ne šalje se osim ako je izričito konfigurirano `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Zaobilazno rješenje samo za razvoj koje omogućuje vraćanje bilo koje pregledničke vrijednosti `Origin`. Nemojte ga omogućivati u dijeljenim ili produkcijskim implementacijama. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokira pozive pružatelja usluga usmjerene na privatne, povratne ili lokalne IP raspone veze. Onemogućite samo u izoliranim testnim okruženjima. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Dopušta URL-ove pružatelja usluga koji upućuju na privatne/lokalne mreže (localhost, 192.168.x.x, 10.x.x.x itd.). **OBAVEZNO za pružatelje usluga s vlastitim hostingom** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Kada je postavljeno na `false`, nadzorna ploča odbija provjeru valjanosti lokalnih URL-ova. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Dopušta dodavanje/provjeru valjanosti pružatelja usluga na lokalnim/privatnim adresama (127.0.0.1, localhost, LAN, privatni rasponi) — ograničeno na putanju provjere valjanosti pružatelja usluga. **Zadano `true`** (prednost lokalnom pristupu); postavite na `false` kako biste nametnuli strogo blokiranje svega osim javnih adresa. Krajnje točke metapodataka u oblaku (169.254.169.254, metadata.google.internal) ostaju blokirane bez obzira na postavku. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Omogućuje rutama `/v1/audio/*` (transkripcije, govor, prijevodi) upotrebu čvora pružatelja usluga kompatibilnog s OpenAI-jem koji se nalazi izvan localhosta. Zadano je isključeno — usmjeravanje zvuka na udaljeno glavno računalo mijenja izlazni identitet i mora biti izričita odluka operatora. Povratni/privatni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvijek su dopušteni i ova postavka na njih ne utječe. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Omogućuje da `POST /v1/rerank` (i korak ponovnog rangiranja preko povratne veze u memorijskom mehanizmu) upotrebljava čvor pružatelja usluga kompatibilnog s OpenAI-jem koji se nalazi izvan localhosta — računalo na LAN-u ili Tailscale ravnopravni čvor koji izvršava TEI, Infinity, vLLM itd. Zadano je isključeno — usmjeravanje na udaljeno glavno računalo mijenja izlazni identitet i mora biti izričita odluka operatora. Povratni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvijek su dopušteni i ova postavka na njih ne utječe. Udaljeni čvorovi također moraju zadovoljiti pravila za odlazne URL-ove pružatelja usluga (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); promet se nikada ne usmjerava prema glavnim računalima metapodataka u oblaku. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kada je OIDC omogućen, onemogućuje prijavu lozinkom kako bi se korisnici mogli autentificirati samo putem OIDC jedinstvene prijave. Prihvaća se i osnovni pseudonim `OIDC_DISABLE_PASSWORD_LOGIN`; zastavica značajke nadzorne ploče s istim ključem ima prednost. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Osnovni pseudonim za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Varijabla | Zadana vrijednost | Izvorna datoteka | Opis | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt koji se kombinira s hardverskim identifikatorima radi izrade otiska uređaja. Promijenite ga za svaku implementaciju radi izolacije. | +| `OMNIROUTE_CLI_SALT` | _(nije postavljeno = nasumični salt za svaku instalaciju, spremljen u `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt za izvođenje lokalnog CLI tokena za autentifikaciju. Postavljanjem ove vrijednosti rotiraju se svi CLI tokeni na uređaju, a ona uvijek ima prednost pred spremljenim saltom. Pogledajte `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Postavlja oznaku `Secure` na kolačićima sesije. **Mora biti `true`** kada se izvodi iza HTTPS-a. | +| `REQUIRE_API_KEY` | `false` | API posrednički softver | Kada je `true`, svi proxy zahtjevi prema `/v1/*` moraju sadržavati valjani API ključ. **Ova oznaka ne kontrolira `GET /v1/models`**, koji umjesto toga slijedi postavku prijave na nadzornu ploču (`requireAuthForModels`) — stoga `401` iz `/v1/models` NE znači da je izvođenje zaključivanja zaštićeno. Pogledajte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Omogućuje otkrivanje punih vrijednosti API ključeva u korisničkom sučelju nadzorne ploče. Može se konfigurirati putem zastavica značajki na nadzornoj ploči; predstavlja sigurnosni rizik na dijeljenim instancama. | +| `NO_LOG_API_KEY_IDS` | _(prazno)_ | `src/lib/compliance/index.ts` | ID-ovi API ključeva odvojeni zarezima za koje se preskače zapisivanje zahtjeva (usklađenost s GDPR-om). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nije postavljeno = neograničeno)_ | `src/shared/utils/apiKeyPolicy.ts` | Rezervni dnevni proračun zahtjeva koji se primjenjuje na API ključeve čiji je stupac `rate_limits` null. Nije postavljeno ili je prazno: nema implicitnog ograničenja (#2289, #11017). `0` ima isto značenje (neograničeno). Pozitivan cijeli broj N omogućuje N/dan, 5N/tjedan, 20N/mjesec. Neispravne vrijednosti koje nisu prazne vraćaju se na naslijeđena razdoblja od 1000/dan, 5000/tjedan i 20000/mjesec. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Najveća dopuštena veličina tijela zahtjeva. Odbija korisne sadržaje koji premašuju ovo ograničenje. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Stvarna tijela zahtjeva čija je veličina jednaka ovom pragu ili ga premašuje prije parsiranja JSON-a preuzimaju atomsku, procesno lokalnu propusnicu za prijam zahtjevnih zahtjeva (BYTE putanja, uključujući `POST /v1/responses`). Primjenjuje se isti mehanizam izlaza pri dostatnoj rezervi kapaciteta iz [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kao za strukturno zahtjevne zahtjeve; i dalje je ograničeno s `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) kako se problem [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne bi vratio. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Čvrsto ograničenje rute za chat koje se primjenjuje na broj bajtova pročitanih tijekom ograničenog učitavanja, uključujući zahtjeve s nedostajućim, nevaljanim ili lažnim zaglavljem `Content-Length`; prekoračenje rezultira odgovorom `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nije postavljeno — nema ograničenja broja zahtjeva)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ovo naslijeđeno ograničenje BROJA zahtjeva sada se primjenjuje samo kada je izričito postavljeno. Ako nije postavljeno (zadano), prihvat zahtjevnih chat zahtjeva umjesto toga ograničava `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatski izveden proračun BAJTOVA određen prema stvarnom memorijskom ograničenju procesa u **jednom procesu** (jedna V8 hrpa). Dva preklapajuća `/v1/responses` zahtjeva s ~750 tisuća tokena ruše hrpe od ~12 GiB (#7849) — to je upozorenje o **memorijskom proračunu**, a ne čvrsto ograničenje proizvoda na 2. Zdrav proces (hrpa ispod omjera rasterećenja) MOŽE prihvatiti više istodobnih dugih `/v1/responses` putem `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Deseci dugih SSE klijenata (40–50) ovise o hrpi + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, a ne o „najviše 2”. Slijepo povećavanje ove vrijednosti radi „korištenja cijelog poslužitelja” ponovno uvodi #7849. Umnožavajte **hrpe** s **N neovisnih `DATA_DIR` direktorija** (#11024); nikada nemojte koristiti `replicas>1` nad jednom SQLite datotekom. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatski izvedeno)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nadjačavanje automatski izvedenog proračuna bajtova učitavanja (25 % strožeg V8/cgroup memorijskog ograničenja podijeljeno s 8x prolaznim uvećanjem). Izvedene i izričite vrijednosti ograničavaju se na 8 MiB–2 GiB. Tijelo veće od efektivnog proračuna odmah ne uspijeva uz `413 body_exceeds_budget`; nadmetanje između tijela koja se pojedinačno mogu obraditi i dalje rezultira ponovljivim odgovorom `503`. 40–50 istodobnih dugih SSE klijenata ovisi o ovom proračunu + hrpi, a nije čvrsto ograničenje „najviše 2”. Prije ugađanja provjerite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Omjer rasterećenja pri pritisku na hrpu (`heapUsed / heap_size_limit`) za prihvat zahtjevnih zahtjeva prema BAJTOVIMA i STRUKTURI (#10183, #10268, #10437). Istodobni zahtjevni zahtjev iznad `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` odbacuje se ponovljivim odgovorom `503` samo kada je hrpa TAKOĐER na ovom omjeru ili iznad njega; uz zdravu hrpu prihvaća se putem dodatnog prostora za zdravu hrpu. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (zadano `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ograničeni dodatni kapacitet za brzi put zdrave hrpe (#10437) za **oba** kriterija, STRUKTURU i BAJTOVE (`admitChatRequest`, uključujući tijela ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez ovog ograničenja svaki bi zahtjev pri zauzetoj, ali zdravoj hrpi zaobilazio kontrolu prihvata bez gornje granice. Kada je ovoliko istodobnih najmova aktivno putem puta zdrave hrpe, daljnji zahtjevi pri zauzetosti prelaze na ISTI put ograničenog čekanja/rasterećenja koji se koristi pri stvarnom pritisku na hrpu. `0` potpuno onemogućuje zaobilaženje. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Broj poruka prema kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod praga bajtova. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Broj alata prema kojem se chat zahtjev klasificira kao zahtjevan čak i kada je njegovo tijelo ispod praga bajtova. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativna procjena broja tokena na temelju veličine niza prema kojoj se zahtjev klasificira kao zahtjevan; to je aproksimacija troška prihvata, a ne tokenizacija za potrebe naplate pružatelja usluge. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (onemogućeno) | `src/shared/middleware/chatBodyAdmission.ts` | Neobavezno ograničenje povijesti chata koje se uključuje po želji. Zadano je onemogućeno: broj poruka pravilo je implementacije, a ne univerzalno svojstvo zahtjeva, pa postavljanje ograničenja ovdje odbacuje razgovore konačnim odgovorom `413` prije nego što ih cjevovod za kompresiju može učiniti obradivima. Rast hrpe ograničen je varijablom `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` i rasterećenjem pri pritisku na hrpu. Postavite pozitivnu vrijednost u implementacijama s ograničenom memorijom kojima je potrebna čvrsta gornja granica; prekoračenje tada rezultira strukturiranim odgovorom `413` koji zahtijeva sažimanje. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Čvrsto ograničenje za odgovor uzvodnog sustava bez strujanja koji se u cijelosti sprema u memoriju. Nakon prekoračenja čitač uzvodnog sustava otkazuje se, a zahtjev brzo ne uspijeva umjesto da neograničeni niz raste sve dok se hrpa ne iscrpi. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalan broj bajtova zaglavlja odgovora proslijeđenih od uzvodnog poslužitelja. Kada se proračun premaši, zaglavlja nižeg prioriteta (npr. prilagođena `x-codex-*`, `x-oai-request-id`) odbacuju se kako bi se ostalo unutar uobičajenih ograničenja zaglavlja obrnutog proxyja. Postavite višu vrijednost kako biste proslijedili više uzvodnih metapodataka, uz cijenu veće veličine zaglavlja odgovora. | +| `CORS_ORIGIN` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | Naslijeđeni CORS popis dopuštenih vrijednosti za jedno izvorište. Za nove implementacije preferirajte `CORS_ALLOWED_ORIGINS`. CORS služi samo za pregledničke API klijente s različitih izvorišta; autentificirani upisi na nadzornoj ploči umjesto toga upotrebljavaju zahtjeve s istog izvorišta i CSRF zaštitu povezanu sa sesijom. | +| `CORS_ALLOWED_ORIGINS` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | CORS popis dopuštenih vrijednosti odvojenih zarezima. Zamjenski znak ne šalje se osim ako je izričito konfigurirano `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Zaobilazno rješenje samo za razvoj koje omogućuje vraćanje bilo koje pregledničke vrijednosti `Origin`. Nemojte ga omogućivati u dijeljenim ili produkcijskim implementacijama. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Naslijeđeni alias. Njegov prekidač na nadzornoj ploči (zaobilaženje spremljeno u bazi podataka) čita se prije ove vrijednosti okoline; `false`, `0`, `no` ili `off` na bilo kojem od njih isključuje provjere glavnog računala onako kako to čini `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, čak i kada je prekidač na nadzornoj ploči te zastavice spremljen kao isključen; ostale vrijednosti ostavljaju zaštitu dvjema zastavicama u nastavku. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Isključuje provjere glavnog računala zaštite odlaznih URL-ova, uključujući blokadu metapodataka u oblaku (cijeli 169.254.0.0/16, metadata.google.internal), na putanjama pružatelja usluga koje slijede način zaštite: provjera valjanosti URL-ova, otkrivanje modela, osnovni URL-ovi čvorova pružatelja (uključujući udaljene čvorove ponovnog rangiranja) i test zamjenskog proxyja, među ostalim odlaznim putanjama bliskim pružateljima usluga poput dohvata udaljenih slika ili medija. Dopušta i privatna odredišta webhooka, osim krajnjih točaka metapodataka u oblaku/link-local, koje ostaju blokirane čak i uz uključenu ovu zastavicu. Zahtjevi za razgovor poslani putem `BaseExecutor.execute()` zadržavaju svoju provjeru metapodataka u oblaku. Lokalni pružatelji usluga rade sa zadanim postavkama: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (zadano `true`) već dopušta lokalne i LAN URL-ove na putanjama provjere valjanosti, otkrivanja i čvorova pružatelja; test zamjenskog proxyja i privatna odredišta webhooka konzultiraju samo ovu zastavicu i blokiraju lokalna i LAN glavna računala dok je isključena. Nadjačava `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Vrijednost spremljena s prekidača na nadzornoj ploči ima prednost nad ovom varijablom. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Dopušta URL-ove pružatelja usluga na lokalnim/privatnim adresama (127.0.0.1, localhost, LAN, privatni rasponi) za provjeru valjanosti pružatelja, otkrivanje modela, razgovor i ostale odlazne putanje bliske pružateljima usluga kojima upravlja način zaštite. **Zadano `true`** (prednost lokalnom pristupu): zaštita tada blokira krajnje točke metapodataka u oblaku — cijeli 169.254.0.0/16 plus poznata imena glavnih računala metapodataka (metadata.google.internal, metadata.goog, IMDS za Alibaba/Azure/Oracle, fd00:ec2::254). Postavite `false` za način rada samo s javnim adresama, koji blokira i privatna i povratna glavna računala. Zaštita provjerava ime glavnog računala ili IP literal onako kako je zapisano, prije bilo kojeg DNS upita; u načinu rada samo s javnim adresama omotač sigurnog dohvata dodatno razrješuje ime glavnog računala i odbija svaki privatni odgovor. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` isključuje te provjere na putanjama navedenima u njegovu retku. Ugrađeni lokalni pružatelji usluga, kao što su LM Studio, Ollama, vLLM, Llamafile i Triton, preskaču sve provjere glavnih računala za provjeru valjanosti ključeva i razgovor. Vrijednost spremljena s prekidača na nadzornoj ploči ima prednost nad ovom varijablom. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Omogućuje rutama `/v1/audio/*` (transkripcije, govor, prijevodi) upotrebu čvora pružatelja usluga kompatibilnog s OpenAI-jem koji se nalazi izvan localhosta. Zadano je isključeno — usmjeravanje zvuka na udaljeno glavno računalo mijenja izlazni identitet i mora biti izričita odluka operatora. Povratni/privatni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvijek su dopušteni i ova postavka na njih ne utječe. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Omogućuje da `POST /v1/rerank` (i korak ponovnog rangiranja preko povratne veze u memorijskom mehanizmu) upotrebljava čvor pružatelja usluga kompatibilnog s OpenAI-jem koji se nalazi izvan localhosta — računalo na LAN-u ili Tailscale ravnopravni čvor koji izvršava TEI, Infinity, vLLM itd. Zadano je isključeno — usmjeravanje na udaljeno glavno računalo mijenja izlazni identitet i mora biti izričita odluka operatora. Povratni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvijek su dopušteni i ova postavka na njih ne utječe. Osnovni URL udaljenog čvora također mora zadovoljiti pravila za odlazne URL-ove pružatelja usluga (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kada je OIDC omogućen, onemogućuje prijavu lozinkom kako bi se korisnici mogli autentificirati samo putem OIDC jedinstvene prijave. Prihvaća se i osnovni pseudonim `OIDC_DISABLE_PASSWORD_LOGIN`; zastavica značajke nadzorne ploče s istim ključem ima prednost. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Osnovni pseudonim za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Kontrolni popis za sigurnosno ojačavanje diff --git a/docs/i18n/hu/CONTRIBUTING.md b/docs/i18n/hu/CONTRIBUTING.md index 2c50943ddab2..5111b52b809f 100644 --- a/docs/i18n/hu/CONTRIBUTING.md +++ b/docs/i18n/hu/CONTRIBUTING.md @@ -392,18 +392,18 @@ Adja hozzá a modelldefiníciókat az `open-sse/config/providerRegistry.ts` fáj - [ ] A tesztek sikeresen lefutnak (`npm test`) - [ ] A lintelés sikeresen lefut (`npm run lint`) -- [ ] A build sikeresen elkészül (`npm run build`) -- [ ] TypeScript-típusok hozzáadva az új nyilvános függvényekhez és interfészekhez +- [ ] A build sikeresen lefut (`npm run build`) +- [ ] Az új nyilvános függvényekhez és interfészekhez TypeScript-típusok lettek hozzáadva - [ ] Nincsenek beégetett titkos adatok vagy tartalékértékek -- [ ] A nyilvános upstream hitelesítő adatok beágyazása a `resolvePublicCred()` használatával történik (lásd: [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), soha nem literálként -- [ ] A hibaválaszok a `buildErrorBody()` / `sanitizeErrorMessage()` függvényeken keresztül haladnak — a válaszok törzsében nincsenek nyers veremkiírások (lásd: [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] A shell-parancsok (`exec` / `spawn`) a futásidejű értékeket az `env` használatával, nem pedig karakterlánc-interpolációval adják át +- [ ] A nyilvános upstream hitelesítő adatok a `resolvePublicCred()` segítségével vannak beágyazva (lásd: [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), soha nem literálként +- [ ] A hibaválaszok a `buildErrorBody()` / `sanitizeErrorMessage()` függvényeken haladnak keresztül — a válasz törzsében nincsenek nyers veremkivonatok (lásd: [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] A shell-parancsok (`exec` / `spawn`) a futásidejű értékeket az `env` segítségével, nem pedig sztringinterpolációval adják át - [ ] Minden bemenet Zod-sémákkal van validálva -- [ ] A felhasználókat érintő változásokhoz **változásnapló-töredék** hozzáadva a `changelog.d/{features|fixes|maintenance}/-.md` alatt (lásd: [`changelog.d/README.md`](./changelog.d/README.md)) — a `CHANGELOG.md` fájlt **ne** szerkeszd közvetlenül; a töredékeket a kiadáskor összesítik, és soha nem okoznak ütközést a PR-ok között -- [ ] A dokumentáció frissítve (ha alkalmazható) -- [ ] Nem keletkezett új CodeQL- vagy Secret-Scanning-riasztás, vagy mindegyik el lett utasítva a vonatkozó `docs/security/` dokumentumra hivatkozó műszaki indoklással -- [ ] A gyermekfolyamatokat indító útvonalak (`/api/mcp/`, `/api/cli-tools/runtime/`) `isLocalOnlyPath()` besorolást kaptak a `src/server/authz/routeGuard.ts` fájlban — lásd: [15. szigorú szabály](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] A commitüzenetekben nincsenek `Co-Authored-By` zárósorok — a commitoknak kizárólag a tároló tulajdonosának Git-identitása alatt kell megjelenniük (16. szigorú szabály) +- [ ] A felhasználókat érintő változásokhoz changelog-**töredék** lett hozzáadva a `changelog.d/{features|fixes|maintenance}/-.md` alatt (lásd: [`changelog.d/README.md`](./changelog.d/README.md)) — a `CHANGELOG.md` fájlt **ne** szerkeszd közvetlenül; a töredékek kiadáskor kerülnek összesítésre, és soha nem ütköznek a PR-ok között +- [ ] A dokumentáció frissítve lett (ha alkalmazható) +- [ ] Nem keletkeztek új CodeQL- vagy titokészlelési riasztások, illetve mindegyik el lett utasítva a vonatkozó `docs/security/` dokumentumra hivatkozó műszaki indoklással +- [ ] A gyermekfolyamatokat indító útvonalak (`/api/mcp/`, `/api/cli-tools/runtime/`) `isLocalOnlyPath()` besorolást kaptak az `src/server/authz/routeGuard.ts` fájlban — lásd: [15. szigorú szabály](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] A commitüzenetekben nincsenek AI-/bot-eredetű `Co-authored-by` zárósorok (16. szigorú szabály) — azok az emberi közreműködők, akiknek a munkája újra fel lett használva, szabványos `Co-authored-by: Name ` zárósorokkal kapnak elismerést --- diff --git a/docs/i18n/hu/docs/architecture/QUALITY_GATES.md b/docs/i18n/hu/docs/architecture/QUALITY_GATES.md index 57097daae3f3..587e3741cc72 100644 --- a/docs/i18n/hu/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/hu/docs/architecture/QUALITY_GATES.md @@ -17,223 +17,222 @@ replikációs tervét lásd a ## Kapukészlet és végrehajtási profilok -### Jelöltek befogadása - -A CI és a Quality Gates munkafolyamat egyaránt stabil eredményt ad: `Gate / CI` és -`Gate / Quality`. A verziózott befogadási szabályzatuk minden felsőbb szintű feladatot -kötelezőként vagy tájékoztató jellegűként sorol fel. Minden alkalmazandó kötelező -feladatnak sikeresen kell lefutnia: a hiányzó, megszakított, kihagyott, függőben lévő -és ismeretlen eredmények nem alapozhatják meg a PASS állapotot. Az érvényes, csak -dokumentációt vagy csak katalógust érintő besorolás miatt egy kódsáv nem alkalmazható; -a piszkozat állapotú PR nem elfogadott jelölt. A `hotfix` címke nem teszi mellőzhetővé -a bizonyítékokat. - -Mindkét munkafolyamat lefedi a PR-eket, a main/release ágakra történő pushokat, a kézi -indítást és a merge-group eseményeket. A push, a dispatch és a merge-group a teljes -kiválasztást futtatja. A forkok és a merge groupok hosztolt futtatókat használnak azokhoz -a feladatokhoz, amelyek egyébként saját üzemeltetésű futtatókat választanának; a bevezetés -előtt ellenőrizni kell, hogy elegendő hosztolt kapacitás áll-e rendelkezésre. - -Minden JSON-nyugta azonosítja a checkout során lekért SHA-t, a munkafolyamat futását és -a próbálkozást. A CLI elutasítja a checkout és az esemény SHA-jának eltérését. A -munkafolyamat-tesztek a szabályzathoz való tartozást az eredményfeladat `needs` listájához -kötik, így egy új vagy eltávolított sáv nem tűnhet el észrevétlenül. A nyugták a saját -munkafolyamatukat fedik le, nem pedig a közzétételt, a telepítést vagy egy meglévő, -tájékoztató jellegű ellenőrző belső működését. Mindkét ellenőrzési név aktiválása az -ágszabályokban külön adminisztratív módosítás; e feladatok hozzáadása önmagában nem védi -az ágat. - -### Statikus ellenőrzési készlet - -A verziózott npm-alias-készlet és a statikus ellenőrzés tagsága a -`config/quality/gate-manifest.json` fájlban található. Futtassa az -`npm run check:gate-manifest` parancsot a szkriptnevek és a pontos parancsok -`package.json` fájllal való összevetéséhez; a hozzáadások, eltávolítások és -parancsváltozások mind a helyi hook, mind a CI változásbesorolási feladatainak -sikertelenségét okozzák. Egy alias nem munkafolyamat-feladat, mátrixpéldány vagy -teszteset: ezek darabszámai nem tüntethetők fel egymással felcserélhetőként. - -A kiválasztott aliasok végrehajtás nélküli áttekintéséhez használja az +### Jelöltek elfogadása + +A CI és a Quality Gates munkafolyamatok egyaránt stabil eredményt adnak ki: `Gate / CI` és +`Gate / Quality`. Verziózott elfogadási szabályzatuk minden upstream feladatot +kötelezőként vagy tanácsadó jellegűként sorol fel. Minden alkalmazandó kötelező feladatnak +sikeresen kell teljesülnie: a hiányzó, megszakított, kihagyott, függőben lévő és ismeretlen +eredmények nem alapozhatnak meg PASS állapotot. Egy érvényes, csak dokumentációt vagy +csak katalógust érintő besorolás miatt egy kódsáv nem alkalmazhatóvá válhat; +egy piszkozat állapotú PR nem elfogadott jelölt. A `hotfix` címke nem teszi +elhagyhatóvá a bizonyítékokat. + +Mindkét munkafolyamat lefedi a PR-okat és a main/release ágakra történő pushokat, a kézi indítást, +valamint a merge-group eseményeket. Push, kézi indítás és merge-group esetén a teljes kiválasztás +fut le. A forkok és merge groupok hosztolt futtatókat használnak azokhoz a feladatokhoz, amelyek +egyébként saját üzemeltetésű futtatókat választanának; a bevezetés előtt ellenőrizni kell, hogy +elegendő hosztolt kapacitás áll-e rendelkezésre. + +Minden JSON-nyugta azonosítja a checkout során kivett SHA-t, a munkafolyamat futását és a próbálkozást. +A CLI elutasítja, ha a checkout és az esemény SHA-ja nem egyezik. A munkafolyamat-tesztek a szabályzati +tagságot az eredményfeladat `needs` listájához kötik, így egy új vagy eltávolított sáv nem tűnhet el +észrevétlenül. A nyugták a saját munkafolyamatukat fedik le, nem pedig a közzétételt, a telepítést +vagy egy meglévő tanácsadó szkenner belső működését. Mindkét ellenőrzési név aktiválása az ágszabályokban +külön adminisztratív módosítás; e feladatok hozzáadása önmagában nem védi az ágat. + +### Statikus vizsgálatok készlete + +A verziózott npm-alias-készlet és a statikus vizsgálatok tagsága a +`config/quality/gate-manifest.json` fájlban található. Futtassa az `npm run check:gate-manifest` +parancsot a szkriptnevek és a pontos parancsok `package.json` fájlhoz viszonyított ellenőrzéséhez; +a hozzáadások, eltávolítások és parancsváltozások egyaránt meghiúsítják a helyi hookot és a CI +változásbesorolási feladatait. Egy alias nem munkafolyamat-feladat, mátrixpéldány vagy teszteset: +ezeket a darabszámokat nem szabad egymással felcserélhetőként bemutatni. + +A kiválasztott aliasok végrehajtás nélküli megtekintéséhez használja az `npm run quality:scan -- --list` vagy az `npm run quality:scan:fast -- --list` parancsot. A futtató az npm belépési pontját hívja meg, így annak futtatókörnyezete -— beleértve a Bunt is, ahol az be van állítva — megmarad. A jegyzék a profilokon -kívüli aliasokat külön meghívottként rögzíti, és a karbantartási parancsok tiltottak -a csak olvasható ellenőrzési profilokban. - -Ezek a profilok csak a statikus ellenőrzést fedik le. Nem tanúsítják a terméktesztek, -a lefedettség, a csomagolás, a külső ellenőrzések vagy egy jelölt teljes kiadási -elfogadásának megfelelőségét. A munkafolyamat befogadása a kapcsolódó -`config/quality/admission-policy.json` és `scripts/quality/admission-verdict.mjs` -fájlokat használja. A kiadásmegfigyelő profilok továbbra is különállók; az alkalmazandó -ellenőrzéseiket és nyugtáikat egymástól függetlenül vizsgálja meg. Az alábbi szöveges -készlet referencia, nem pedig bizonyíték arra, hogy egy kapu ténylegesen lefutott. - -A szkriptek a `scripts/check/` (szabályzatkapuk) és a `scripts/quality/` (ratchet-motor) +— beleértve a Bunt is, ahol az konfigurálva van — megmarad. +A jegyzék a profilokon kívüli aliasokat külön meghívottként rögzíti, a csak olvasható +vizsgálati profilokban pedig tilosak a karbantartási parancsok. + +Ezek a profilok kizárólag a statikus vizsgálatot fedik le. Nem tanúsítják a terméktesztek, +a lefedettség, a csomagolás, a külső ellenőrzések vagy egy jelölt teljes kiadási elfogadásának +eredményét. A munkafolyamatbeli elfogadás a kapcsolódó `config/quality/admission-policy.json` +és `scripts/quality/admission-verdict.mjs` fájlokat használja. A kiadásmegfigyelő profilok +különállók maradnak; alkalmazandó ellenőrzéseiket és nyugtáikat egymástól függetlenül +vizsgálja meg. Az alábbi szöveges készlet referencia, nem pedig annak bizonyítéka, hogy egy +kapu ténylegesen lefutott. + +A szkriptek a `scripts/check/` (szabályzati kapuk) és a `scripts/quality/` (ratchet-motor) könyvtárakban találhatók. A CI hiteles forrása a `.github/workflows/ci.yml`. -### Kiadási PR gyors útvonala (`quality.yml`) +### Release PR gyorsított útvonala (`quality.yml`) -A `.github/workflows/quality.yml` kiegészíti a CI-t a main/release ágakat célzó PR-ek, -a védett ágakra történő pushok, a dispatch és a merge groupok esetén. A PR-ek -útvonalszűrt gyors ellenőrzéseket használnak. A véglegesen letiltott, duplikált -buildet eltávolították; a tényleges build-, csomag- és rendszerindítási ellenőrzések -továbbra is a CI-ban maradnak. +A `.github/workflows/quality.yml` kiegészíti a CI-t a main/release ágakra irányuló PR-ok, +a védett ágakra történő pushok, a kézi indítások és a merge groupok esetében. A PR-ok +útvonal alapján szűrt gyors ellenőrzéseket használnak. A véglegesen letiltott, duplikált +buildet eltávolították; a tényleges build-/csomagolási-/indítási ellenőrzések továbbra is +a CI-ben maradnak. -| Feladat | Hatókör | Blokkoló | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `Docs Gates (fast-path)` | Dokumentációs/kód PR-ek; API-dokumentációs hivatkozások és teljes dokumentáció | Igen | -| `Fast Quality Gates` | Kód PR-ek; statikus ellenőrzések, típusellenőrzés, az irányítópult típusellenőrzése, érintett egységtesztek | Igen | -| `Forgotten sibling tests` | Kód PR-ek; a módosított modulok visszakövetése statikus fogyasztókig és lehetséges testvértesztekig; a barrel- és dinamikusimportálási útvonalak tájékoztató diagnosztikaként jelennek meg, a hivatkozott engedélyezőlistás kivételekkel | **Tájékoztató jellegű** | -| `Vitest (fast-path)` | Kód PR-ek; gyors vitest tesztcsomag | Igen | -| `Unit Tests fast-path` | Kód PR-ek; 4 shardos egységtesztcsomag | Igen | -| `No new ESLint warnings` | Kód PR-ek; az elnyomásokat figyelembe vevő lint-őr | Igen, forkoknál is | -| `Merge integrity (changelog + generated skills)` | Nem piszkozat állapotú PR-ek; a változásnapló és a generált skillek szinkronizálása | Igen, forkoknál is | +| Feladat | Hatókör | Blokkoló | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `Docs Gates (fast-path)` | Dokumentációs/kód-PR-ok; API-dokumentációs hivatkozások és docs-all | Igen | +| `Fast Quality Gates` | Kód-PR-ok; statikus ellenőrzések, típusellenőrzés, az irányítópult típusellenőrzése, érintett egységtesztek | Igen | +| `Forgotten sibling tests` | Kód-PR-ok; a módosított modulok visszakövetése statikus fogyasztókhoz és lehetséges testvértesztekhez; a barrel- és dinamikusimport-útvonalak tanácsadó jellegű diagnosztikaként jelennek meg, a hivatkozott engedélyezésilista-kivételekkel | **Tanácsadó jellegű** | +| `Vitest (fast-path)` | Kód-PR-ok; gyors vitest tesztkészlet | Igen | +| `Unit Tests fast-path` | Kód-PR-ok; 4 shardos egységtesztkészlet | Igen | +| `No new ESLint warnings` | Kód-PR-ok; a kivételeket figyelembe vevő lint-védelem | Igen, a forkokat is beleértve | +| `Merge integrity (changelog + generated skills)` | Nem piszkozat PR-ok; a változásnapló és a generált készségek szinkronizálása | Igen, a forkokat is beleértve | #### Elfelejtett testvértesztek jelentése -Az `npm run check:forgotten-sibling-tests` újra felhasználja a teszthatás-térkép mögötti -importfeloldót. Minden módosított éles modulhoz determinisztikus -`módosított modul/szimbólum -> statikus fogyasztó -> lehetséges testvérteszt` láncokat -jelent, ha a lehetséges teszt hiányzik a pull request diffjéből. A Markdown-összefoglalót -és a JSON-eredményt a rendszer `forgotten-sibling-tests` munkafolyamat-műtermékként őrzi -meg a blokkoló bevezetést megelőző kalibráláshoz. +Az `npm run check:forgotten-sibling-tests` újra felhasználja a teszthatás-térkép mögötti importfeloldót. +Minden módosított éles modulnál determinisztikus +`módosított modul/szimbólum -> statikus fogyasztó -> lehetséges testvérteszt` láncokat jelent, ha a +lehetséges teszt hiányzik a pull request diffjéből. A Markdown-összefoglalót és a JSON-eredményt +a rendszer `forgotten-sibling-tests` munkafolyamat-műtermékként őrzi meg a blokkoló bevezetést +megelőző kalibrációhoz. -A barrel re-exportok és a dinamikus importok csak feloldási diagnosztikák; soha nem eredményeznek -blokkoló megállapítást. A felülvizsgált kivételek helye: -`config/quality/forgotten-sibling-allowlist.json`. Minden bejegyzésnek meg kell neveznie a fogyasztót és a jelölt +A barrel re-exportok és a dinamikus importok kizárólag feloldási diagnosztikák; soha nem eredményeznek +blokkoló megállapítást. A felülvizsgált kivételek a +`config/quality/forgotten-sibling-allowlist.json` fájlban találhatók. Minden bejegyzésnek meg kell neveznie a fogyasztót és a jelölt tesztet, konkrét indoklást kell adnia, valamint egy GitHub-issue-ra vagy pull requestre kell hivatkoznia. A hibás formátumú bejegyzések -zárt módon hibát okoznak. A kivételek nem fedhetnek el törölt jelölt tesztet vagy olyan diffet, amely `.skip`/`.todo` elemet ad hozzá; -az ellenőrzések gyengítése és az egyéb elfedések továbbra is a különálló, blokkoló +biztonságosan elutasításra kerülnek. A kivételek nem fedhetnek el törölt jelölt tesztet vagy olyan diffet, amely `.skip`/`.todo` elemet ad hozzá; +az ellenőrzések gyengítése és más elfedések továbbra is a függetlenül blokkoló `check:test-masking` kapu hatáskörébe tartoznak. ### Feladat: `lint` Minden, a `main` ágra irányuló PR esetén lefut. Hiba esetén blokkolja az egyesítést. -| Szkript (`npm run ...`) | Ellenőrzi | Blokkoló | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | A Node.js verziója a támogatott tartományon belül van | Igen | -| `check:cycles` | Körkörös importok — az összes `src/` + `open-sse/` modul | Igen | -| `check:route-validation:t06` | Minden útvonalon jelen vannak a Zod-sémák (6. szintű szabályzat) | Igen | -| `check:any-budget:t11` | Az `@ts-expect-error // any` előfordulásainak száma nem haladja meg a keretet (11. szintű catraca) | Igen | -| `check:provider-consistency` | A `providers.ts` minden szolgáltatójához tartozik megfelelő bejegyzés a `providerRegistry.ts` fájlban (és fordítva, az engedélyezési listán belül) | Igen | -| `check:model-lifecycle` | A három kézzel karbantartott útválasztási tábla konzisztens marad a repozitóriumban tárolt életciklus-pillanatképpel (#11503): a `FITNESS_TABLE` (`taskFitness.ts`) nem pontoz olyan kivezetett azonosítót, amelyhez a `REGISTRY` útvonalat tud rendelni; minden `BUILT_IN_ALIASES`-cél megtalálható a `REGISTRY`-ben, és hiányzik a kivezetett azonosítók pillanatképéből; minden, a `REGISTRY`-ben továbbra is szereplő kivezetett azonosító továbbításra kerül, vagy szerepel az `allowedRetiredInCatalog` listában; továbbá a `DEFAULT_DEGRADATION_MAP` egyetlen forrása vagy célja sem jelenik meg kivezetettként ebben a pillanatképben. Ez nem bizonyítja, hogy egy modellt jelenleg kiszolgál valamely élő felsőbb szintű szolgáltatás. Offline — összehasonlítást végez a `config/quality/model-lifecycle.json` fájllal, amely kézzel frissíthető az `npm run quality:refresh-model-lifecycle` paranccsal (hálózatot igényel; nincs bekötve a CI-be). Az `allowedRetiredInCatalog` egy fokozatos leépítést kikényszerítő mechanizmus: csak nyomonkövetési hibajeggyel együtt adj hozzá új bejegyzést. | Igen | -| `check:fetch-targets` | A kliensoldali `src/` minden `fetch("/api/...")` hívása egy valódi `route.ts` fájlra oldódik fel | Igen | -| `check:deps` | A repozitórium minden `package.json` fájljában található összes, `npm install` paranccsal telepíthető függőség szerepel a `dependency-allowlist.json` fájlban; az új, nem rögzített verziójú vagy néveltérítéses csomagok megjelölésre kerülnek | Igen | -| `audit:deps` | `npm audit` (gyökér + electron) — nincs magas/kritikus súlyosságú biztonsági figyelmeztetés (átfedésben van az osv `check:vuln-ratchet` ellenőrzésével; lásd: Racionalizálási hátralék) | Igen | -| `check:lockfile` | A `package-lock.json` integritása — https-nyilvántartás, integritási hashek, gazdagép-felülbírálások nélkül | Igen | -| `check:licenses` | SPDX-licenc engedélyezési lista az éles függőségekhez | Igen | -| `check:tracked-artifacts` | Nincsenek buildelési melléktermékek / verziókövetésbe bevitt `node_modules` szimbolikus linkek (a husky pre-commit során is lefut; a pre-push szándékosan kevés ellenőrzést végez — #6716) | Igen | -| `check:ai-attribution` | Nincs MI/bot `Co-Authored-By` zárósor vagy MI-generálásra utaló lábléc a PR commitjaiban, címében vagy törzsében — 16. szigorú szabály (a `quality.yml` gyors ellenőrzési ciklusában a PR→`release/**` esetén — beolvassa az esemény adatait, PR-en kívül nem végez műveletet —, valamint egy csak PR-ekre vonatkozó lépésként a `ci.yml` lintelésében a PR→`main` esetén; továbbá a husky `commit-msg` hookban; emberi társszerzők engedélyezettek; #14436) | -| `check:vitest-exclusions` | Minden Vitest-kizárás megnevez egy nyomon követési hibajegyet, és szerepel a `config/quality/vitest-exclusions.json` fájlban (#13204) | Igen | -| `check:file-size` | Egyetlen forrásfájl sem lépi túl a kiterjesztésenkénti korlátot (fokozatos szigorítás: a nagy, befagyasztott fájlok a `frozen` listában vannak) | Igen | -| `check:error-helper` | A végrehajtókban/kezelőkben található hibaválaszok a `buildErrorBody()` / `sanitizeErrorMessage()` függvényt használják (12. szigorú szabály) | Igen | -| `check:migration-numbering` | A migrációs SQL-fájlok sorszámozása folyamatos, nincsenek kihagyások vagy ismétlődések | Igen | -| `check:public-creds` | Nincsenek literális OAuth `client_id`/`client_secret` értékek vagy Firebase Web-kulcsok a `publicCreds.ts` fájlon kívül (11. szigorú szabály) | Igen | -| `check:db-rules` | Nincs nyers SQL a `src/lib/db/` modulokon kívül; nincsenek gyűjtőimportok a `localDb.ts` fájlból (2./5. szigorú szabály) | Igen | -| `check:known-symbols` | A diszpécsertáblákban regisztrált szolgáltató-végrehajtók, útválasztási stratégiák és fordítók megfelelnek a lemezen található fájloknak — nincsenek árva vagy deklarálatlan szimbólumok | Igen | -| `check:route-guard-membership` | Minden olyan útvonalat, amely gyermekfolyamatot indít, az `isLocalOnlyPath()` osztályoz (15./17. szigorú szabály) | Igen | -| `check:test-discovery` | A repó minden `*.test.ts` / `*.spec.ts` fájlját legalább egy tesztfuttató begyűjti (racsnis szabály: a `test-discovery-baseline.json` fájlban lévő árvalista csak csökkenhet) | Igen | -| `check:agent-skills-sync` | A generált agent-skills összetevők megfelelnek a forráskatalógusuknak (nincs eltérés) | -| `check:provider-asset-provenance` | A szolgáltatói logókhoz/összetevőkhöz rögzített származási bejegyzés tartozik | -| `lint:json` | A JSON-konfigurációs fájlok feldolgozhatók, és megfelelnek a repó lintelési szabályainak | -| `typecheck:core` | TypeScript-fordítás hibák nélkül (csak tájékoztató jellegű figyelmeztetések) | Igen | -| `typecheck:noimplicit:core` | Szigorú `noImplicitAny` — jövőorientált; számos már meglévő hívási helyen továbbra is szükség van annotációkra | **Tájékoztató** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | A `tsc` futtatása a `src/app/(dashboard)/**` körére korlátozva (#7033) — a `typecheck:core` gondosan összeállított, 27 fájlból álló engedélyezési listája egyetlen dashboard TSX-fájlt sem tartalmaz, és a `next build` sem végez rajtuk típusellenőrzést (`next.config.mjs` esetén az `ignoreBuildErrors: true` van beállítva), ezért az ottani árvaazonosító-regressziók (#6625/#6909) láthatatlanok voltak a CI számára. Az ellenőrzés egy befagyasztott, fájlonkénti és TS-hibakódonkénti darabszám-alapértékhez (`config/quality/dashboard-typecheck-baseline.json`) viszonyít (ugyanazzal az elavultság-ellenőrzési mintával, mint a `check:known-symbols`) — csak az alapértékben szereplő darabszámot meghaladó ÚJ hibák okozzák az ellenőrzési kapu sikertelenségét; ha egy már meglévő hibát kijavítottak, az alapérték az `--update` kapcsolóval csökkenthető. | Igen | +| Szkript (`npm run ...`) | Mit ellenőriz | Blokkoló | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | A Node.js verziója a támogatott tartományon belül van | Igen | +| `check:cycles` | Körkörös importok a teljes `src/` + `open-sse/` területén (AST-alapú, a tsconfig `paths` feloldásával). Önállóan futtatva tájékoztató jellegű, és felsorolja a ciklusokat. A `check:cycles:ratchet` (amelyet a CI futtat) blokkol, ha a szám meghaladja a `quality-baseline.json` fájlban szereplő `metrics.cycles` felső korlátot — ez jelenleg 14, `direction: down`, így csak csökkenhet (#15159 G-01/G-02) | Igen (ratchet) | +| `check:route-validation:t06` | Minden útvonalon megtalálhatók a Zod-sémák (6. szintű szabályzat) | Igen | +| `check:any-budget:t11` | Az `@ts-expect-error // any` előfordulásainak száma nem haladja meg a keretet (11. szintű catraca) | Igen | +| `check:provider-consistency` | A `providers.ts` minden szolgáltatójához tartozik megfelelő bejegyzés a `providerRegistry.ts` fájlban (és fordítva, az engedélyezési listán belül) | Igen | +| `check:model-lifecycle` | A három kézzel karbantartott útválasztási tábla konzisztens marad a verziókezelőben tárolt életciklus-pillanatképpel (#11503): a `FITNESS_TABLE` (`taskFitness.ts`) nem pontoz olyan kivezetett azonosítót, amelyet a `REGISTRY` képes útválasztani; minden `BUILT_IN_ALIASES`-cél megtalálható a `REGISTRY` jegyzékben, és hiányzik a kivezetett azonosítók pillanatképéből; minden, a `REGISTRY` jegyzékben még szereplő kivezetett azonosító továbbításra kerül, vagy szerepel az `allowedRetiredInCatalog` listában; továbbá a `DEFAULT_DEGRADATION_MAP` egyetlen forrása vagy célja sem szerepel kivezetettként ebben a pillanatképben. Ez nem bizonyítja, hogy egy modellt jelenleg kiszolgál egy élő külső szolgáltatás. Offline — az összehasonlítás a `config/quality/model-lifecycle.json` fájllal történik, amely kézzel frissíthető az `npm run quality:refresh-model-lifecycle` paranccsal (hálózatot igényel; nincs bekötve a CI-ba). Az `allowedRetiredInCatalog` egy fokozatos leépítést kikényszerítő mechanizmus: csak nyomonkövetési feladattal együtt adjon hozzá bejegyzést. | Igen | +| `check:fetch-targets` | Az ügyféloldali `src/` könyvtárban található minden `fetch("/api/...")` egy valódi `route.ts` fájlra oldódik fel | Igen | +| `check:deps` | A tároló minden `package.json` fájljában szereplő, `npm install` segítségével telepíthető összes függőség megtalálható a `dependency-allowlist.json` fájlban; az új, nem rögzített verziójú vagy elírásos csomagnévvel visszaélő csomagok megjelölésre kerülnek | Igen | +| `audit:deps` | `npm audit` (gyökér + electron) — nincs magas/kritikus súlyosságú biztonsági figyelmeztetés (átfedésben van az osv `check:vuln-ratchet` ellenőrzésével; lásd: Rationalizálási hátralék) | Igen | +| `check:lockfile` | A `package-lock.json` integritása — https-beállításjegyzék, integritási hashek, nincsenek gazdagép-felülbírálások | Igen | +| `check:licenses` | SPDX-licenc engedélyezési lista az éles környezet függőségeihez | Igen | +| `check:tracked-artifacts` | Nincsenek buildműtermékek / commitolt `node_modules` szimbolikus linkek (a husky pre-commit során is fut; a pre-push szándékosan könnyű — #6716) | Igen | +| `check:ai-attribution` | Nincs AI/bot `Co-Authored-By` előzetes vagy AI-generálásra utaló záradék a PR commitjaiban, címében vagy törzsében — 16. szigorú szabály (a `quality.yml` gyors ellenőrzési ciklusában a PR→`release/**` esetén — beolvassa az esemény adatait, PR-eken kívül nem végez műveletet —, valamint egy csak PR-ekre vonatkozó lépésként a `ci.yml` lintelésében a PR→`main` esetén; továbbá a husky `commit-msg` hookban; emberi társszerzők megengedettek; #14436) | +| `check:vitest-exclusions` | Minden Vitest-kizárás megnevez egy nyomon követési hibajegyet, és szerepel a `config/quality/vitest-exclusions.json` fájlban (#13204) | Igen | +| `check:file-size` | Egyetlen forrásfájl sem lépi túl a kiterjesztésenkénti korlátot (racsni: befagyasztott nagy fájlok a `frozen` listában) | Igen | +| `check:error-helper` | A végrehajtókban/kezelőkben található hibaválaszok a `buildErrorBody()` / `sanitizeErrorMessage()` függvényt használják (12. szigorú szabály) | Igen | +| `check:migration-numbering` | A migrációs SQL-fájlok sorszámozása folyamatos, nincsenek kihagyások vagy ismétlődések | Igen | +| `check:public-creds` | Nincsenek literálisan megadott OAuth `client_id`/`client_secret` értékek vagy Firebase Web-kulcsok a `publicCreds.ts` fájlon kívül (11. szigorú szabály) | Igen | +| `check:db-rules` | Nincs nyers SQL a `src/lib/db/` modulokon kívül; nincsenek gyűjtőimportok a `localDb.ts` fájlból (2./5. szigorú szabály) | Igen | +| `check:known-symbols` | A diszpécsertábláikban regisztrált szolgáltatói végrehajtók, útválasztási stratégiák és fordítók megfelelnek a lemezen található fájloknak — nincsenek árva vagy nem deklarált szimbólumok | Igen | +| `check:route-guard-membership` | Minden gyermekfolyamatot indító útvonalat besorol az `isLocalOnlyPath()` (15./17. szigorú szabály) | Igen | +| `check:test-discovery` | A repóban található minden `*.test.ts` / `*.spec.ts` fájlt legalább egy tesztfuttató begyűjt (kilincsmű: a `test-discovery-baseline.json` árvalistája csak rövidülhet) | Igen | +| `check:agent-skills-sync` | A generált agent-skills artefaktumok megfelelnek a forráskatalógusuknak (nincs eltérés) | +| `check:provider-asset-provenance` | A szolgáltatói logókhoz/erőforrásokhoz rögzített eredetbejegyzés tartozik | +| `lint:json` | A JSON-konfigurációs fájlok feldolgozhatók, és megfelelnek a repó lintelési szabályainak | +| `typecheck:core` | TypeScript-fordítás hibák nélkül (csak tájékoztató jellegű figyelmeztetések) | Igen | +| `typecheck:noimplicit:core` | Szigorú `noImplicitAny` — jövőorientált; számos már meglévő hívási helyhez továbbra is annotációk szükségesek | **Tájékoztató** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | A `tsc` hatóköre a `src/app/(dashboard)/**` útvonalra korlátozódik (#7033) — a `typecheck:core` gondosan összeállított, 27 fájlból álló engedélyezési listája egyetlen dashboard TSX-fájlt sem tartalmaz, és a `next build` sem végez rajtuk típusellenőrzést (a `next.config.mjs` fájlban az `ignoreBuildErrors: true` van beállítva), ezért az ottani gazdátlanazonosító-regressziók (#6625/#6909) láthatatlanok voltak a CI számára. Az ellenőrzés egy befagyasztott, fájlonkénti és TS-kódonkénti hibaszám-alapértékhez (`config/quality/dashboard-typecheck-baseline.json`, ugyanazzal az elavulás elleni kikényszerítési mintával, mint a `check:known-symbols`) viszonyít — csak az alapértékben rögzített darabszámot meghaladó ÚJ hibák miatt bukik el az ellenőrzési kapu; egy már meglévő hiba javításakor az alapérték az `--update` kapcsolóval csökkenthető. | Igen | ### Feladat: `quality-gate` -A `test-coverage` után fut. Sikertelenség esetén blokkolja az összevonást. +A `test-coverage` után fut. Hiba esetén blokkolja az összevonást. -| Szkript | Ellenőrzés | Blokkoló | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `quality:collect` | Létrehozza a `quality-metrics.json` fájlt (ESLint-figyelmeztetések száma, lefedettség az egyesített részjelentésből) | Igen (a ratchet előtt fut) | -| `quality:ratchet` | A `quality-baseline.json` egyetlen metrikája sem romlott (ESLint-figyelmeztetések ≤ alapérték; lefedettség ≥ alapérték) | Igen | -| `check:duplication` | A kódduplikáció (jscpd@4) nem haladja meg a `quality-baseline.json` alapértékét | Igen | -| `check:complexity` | A fájlszintű ciklomatikus komplexitás nem haladja meg a felső korlátot (alap ESLint `complexity` + `max-lines-per-function`) | Igen | -| `check:cognitive-complexity` | Kognitív komplexitási ratchet (`eslint-plugin-sonarjs`) — külön ESLint-futtatás; a CI mindkettőt egyesítve, egyetlen `check:complexity-ratchets` lépésként futtatja | Igen | -| `check:dead-code` | A nem használt exportok/fájlok ratchetje (knip) nem romlik az alapértékhez képest | Igen | -| `check:compression-budget` | Tömörítési benchmark kerete — a motoronkénti minimális tokenmegtakarítás nem romolhat | Igen | -| `check:type-coverage` | A típusokkal ellátott kód százalékos arányának ratchetje (`type-coverage`) nem romlik; nagyrészt kiváltja a `typecheck:noimplicit:core` ellenőrzést | Igen | -| `check:codeql-ratchet` | A nyitott CodeQL-riasztások száma nem nő (a `gh api` segítségével olvassa be; token nélkül szabályosan kihagyja) — a frissítési gyakoriságot és a kézi indítást lásd alább, a „CodeQL ratchet” szakaszban | Igen | +| Szkript | Ellenőrzés | Blokkoló | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `quality:collect` | Létrehozza a `quality-metrics.json` fájlt (ESLint-figyelmeztetések száma, lefedettség az egyes részek egyesített jelentéséből) | Igen (a ratchet előfeltétele) | +| `quality:ratchet` | A `quality-baseline.json` egyetlen metrikája sem romlott (ESLint-figyelmeztetések ≤ alapérték; lefedettség ≥ alapérték) | Igen | +| `check:duplication` | A kódduplikáció (jscpd@4) nem haladja meg a `quality-baseline.json` fájlban meghatározott alapértéket | Igen | +| `check:complexity` | A fájlszintű ciklomatikus komplexitás nem haladja meg a felső korlátot (alapvető ESLint `complexity` + `max-lines-per-function`) | Igen | +| `check:cognitive-complexity` | Kognitívkomplexitás-ratchet (`eslint-plugin-sonarjs`) — külön ESLint-futtatás; a CI mindkettőt összevonva, egyetlen `check:complexity-ratchets` lépésként futtatja | Igen | +| `check:dead-code` | A nem használt exportok/fájlok ratchetje (knip) nem romlik az alapértékhez képest | Igen | +| `check:compression-budget` | Tömörítési teljesítménymérés kerete — a motoronkénti tokenmegtakarítási minimumértékek nem romolhatnak | Igen | +| `check:type-coverage` | A típusozottsági százalék ratchetje (`type-coverage`) nem romlik; nagyrészt kiváltja a `typecheck:noimplicit:core` ellenőrzést | Igen | +| `check:codeql-ratchet` | A nyitott CodeQL-riasztások száma nem nő (beolvasás a `gh api` használatával; token nélkül szabályosan kihagyja) — frissítési gyakoriság és kézi indítás: lásd alább: „CodeQL-ratchet” | Igen | ### Feladat: `quality-extended` A teljes feladat tájékoztató jellegű (`continue-on-error: true`). Az npm-alapú ratchetek -ténylegesen lefutnak; a külső ellenőrzők a `gh release download` segítségével települnek, és automatikusan kihagyják magukat (0-s kilépési kóddal), -ha egy bináris továbbra sem érhető el. - -| Szkript | Ellenőrzés | Blokkoló | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | -| `check:circular-deps` | Nincsenek körkörös függőségek (dpdm) | **Tájékoztató jellegű** | -| `check:bundle-size` | A csomag mérete nem haladja meg a felső korlátot | **Tájékoztató jellegű** | -| `check:secrets` | Titkok keresése (gitleaks) — kihagyja, ha a bináris hiányzik | **Tájékoztató jellegű** | -| `check:vuln-ratchet` | A függőségek sebezhetőségei (osv-scanner) nem romlanak — kihagyja, ha a bináris hiányzik | **Tájékoztató jellegű** | -| `check:workflows` | Munkafolyamat-ellenőrzés (actionlint + zizmor); a hiányzó/hibás ellenőrzők, érvénytelen jelentések vagy hiányzó ratchet-alapérték INCOMPLETE eredménnyel meghiúsítják. Az érvényes találatok a kiválasztott szigorú/tájékoztató/ratchet szabályzatot követik | A végrehajtás kötelező; a zizmor ratchet blokkoló a CI-ben | -| `check:openapi-breaking` | A nyilvános API-szerződés (`openapi.yaml`) visszafelé nem kompatibilis változásai az alapághoz képest (oasdiff) — `openapiBreaking=N` értéket ad ki; kihagyja, ha az oasdiff hiányzik vagy az alapspecifikáció nem oldható fel | **Tájékoztató jellegű** | +ténylegesen lefutnak; a külső ellenőrzők telepítése a `gh release download` használatával +történik, és automatikusan kihagyják az ellenőrzést (exit 0), ha a bináris továbbra sem érhető el. + +| Szkript | Ellenőrzés | Blokkoló | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | +| `check:circular-deps` | Nincsenek körkörös függőségek (dpdm) | **Tájékoztató jellegű** | +| `check:bundle-size` | A csomag mérete nem haladja meg a felső korlátot | **Tájékoztató jellegű** | +| `check:secrets` | Titkok keresése (gitleaks) — kihagyja, ha a bináris nem érhető el | **Tájékoztató jellegű** | +| `check:vuln-ratchet` | A függőségek sebezhetőségei (osv-scanner) nem romlanak — kihagyja, ha a bináris nem érhető el | **Tájékoztató jellegű** | +| `check:workflows` | Munkafolyamatok lintelése (actionlint + zizmor); a hiányzó/hibás ellenőrzők, az érvénytelen jelentések vagy a hiányzó ratchet-alapérték INCOMPLETE állapottal hibát okoznak. Az érvényes találatokra a kiválasztott szigorú/tájékoztató/ratchet szabályzat vonatkozik | A végrehajtás kötelező; a zizmor-ratchet blokkol a CI-ben | +| `check:openapi-breaking` | A nyilvános API-szerződés (`openapi.yaml`) kompatibilitást megszakító módosításai az alapághoz képest (oasdiff) — `openapiBreaking=N` értéket állít elő; kihagyja, ha az oasdiff nem érhető el, vagy az alapspecifikáció nem oldható fel | **Tájékoztató jellegű** | ### Feladat: `docs-sync-strict` Minden, a `main` ágra irányuló PR esetén lefut. Hiba esetén blokkolja az egyesítést. -| Szkript | Ellenőrzi | Blokkoló | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| `check:docs-all` | Metakapu, amely egymás után futtatja az alábbi 6 alkaput | Igen | -| ↳ `check:docs-sync` | A CHANGELOG / OpenAPI / llm.txt verzióinak konzisztenciája | Igen | -| ↳ `check:docs-counts` | A prózában szereplő számok (szolgáltatók száma, migrációk száma stb.) a tényleges értékekhez tartozó szigorítási tartományon belül vannak | Igen | -| ↳ `check:env-doc-sync` | A `.env.example` minden környezeti változója dokumentálva van egy dokumentációs táblázatban, és fordítva | Igen | -| ↳ `check:deprecated-versions` | A dokumentáció nem tartalmaz elavult verziókarakterláncokat | Igen | -| ↳ `check:doc-links` | A dokumentáció belső markdown-hivatkozásai valódi fájlokra mutatnak (`[szöveg]`/`(útvonal)` formában) | Igen | -| ↳ `check:fabricated-docs` | A dokumentációban említett útvonalak, környezeti változók, CLI-parancsok, hooknevek és fájlútvonalak léteznek a kódbázisban. Szigorú kapu a `--strict` használatával; a kapcsoló nélkül csak enyhe hibát jelez. | Igen (CI-ben a `--strict` használatával) | -| `check:cli-i18n` | A CLI-parancsok karakterláncai minden i18n területi beállítási fájlban megtalálhatók | Igen | -| `check:openapi-coverage` | Az OpenAPI-specifikáció legalább a valós útvonalak szigorított alsó küszöbéig biztosít lefedettséget | Igen | -| `check:openapi-security-tiers` | Az `openapi.yaml` biztonsági szintjeinek annotációi összhangban vannak a `routeGuard.ts` besorolásaival | **Tájékoztató jellegű** | -| `check:openapi-routes` | Az `openapi.yaml` minden útvonala valódi `route.ts` fájlra oldható fel (hallucináció elleni védelem) | Igen | -| `check:docs-symbols` | A `docs/**/*.md` fájlok minden `/api/...` hivatkozása valódi `route.ts` fájlra oldható fel (hallucináció elleni védelem) | Igen | -| `i18n translation drift` | Lefordítatlan kulcsok az i18n területi beállítási fájlokban — csak figyelmeztetés | **Tájékoztató jellegű** | +| Szkript | Mit ellenőriz | Blokkoló | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| `check:docs-all` | Metakapu, amely egymás után futtatja az alábbi 6 alkaput | Igen | +| ↳ `check:docs-sync` | A CHANGELOG / OpenAPI / llm.txt verzióinak konzisztenciája | Igen | +| ↳ `check:docs-counts` | A prózában szereplő számok (szolgáltatók száma, migrációk száma stb.) a valós értékekhez tartozó szigorodó tartományon belül vannak | Igen | +| ↳ `check:env-doc-sync` | A `.env.example` minden környezeti változója dokumentálva van egy dokumentációs táblázatban, és fordítva | Igen | +| ↳ `check:deprecated-versions` | A dokumentáció nem tartalmaz elavult verziókarakterláncokat | Igen | +| ↳ `check:doc-links` | A dokumentáció belső markdown-hivatkozásai létező fájlokra mutatnak (`[szöveg]`/`(útvonal)` forma) | Igen | +| ↳ `check:fabricated-docs` | A dokumentációban hivatkozott útvonalak, környezeti változók, CLI-parancsok, hooknevek és fájlútvonalak léteznek a kódbázisban. Szigorú kapu a `--strict` kapcsolóval; nélküle csak figyelmeztető hibát jelez. | Igen (CI-ben a `--strict` révén) | +| `check:cli-i18n` | A CLI-parancsok karakterláncai minden i18n területi beállítás fájljában megtalálhatók | Igen | +| `check:openapi-coverage` | Az OpenAPI-specifikáció legalább a valós útvonalak számához igazított, fokozatosan emelt minimumot lefedi | Igen | +| `check:openapi-security-tiers` | Az `openapi.yaml` biztonsági szintre vonatkozó annotációi összhangban vannak a `routeGuard.ts` besorolásaival | **Tájékoztató jellegű** | +| `check:openapi-routes` | Az `openapi.yaml` minden útvonala egy valós `route.ts` fájlra oldódik fel (hallucináció elleni védelem) | Igen | +| `check:docs-symbols` | A `docs/**/*.md` fájlok minden `/api/...` hivatkozása egy valós `route.ts` fájlra oldódik fel (hallucináció elleni védelem) | Igen | +| `i18n translation drift` | Lefordítatlan kulcsok az i18n területi beállítások fájljaiban — csak figyelmeztetés | **Tájékoztató jellegű** | ### Feladat: `i18n-ui-coverage` -| Szkript | Ellenőrzi | Blokkoló | -| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `check-ui-keys-coverage` (beágyazott) | A felhasználói felület i18n-kulcsainak lefedettsége ≥ 65% | Igen | -| `check-ui-value-drift` (beágyazott) | Egy átírt angol **érték** nem hagy maga után elavult fordítást | Igen | -| `check-new-key-coverage` (beágyazott) | Egy **új** angol kulcs minden területi beállításhoz le van fordítva — a `__MISSING__:` jelölő elutasításra kerül | Igen | -| `check-translation-ratio` | A valódi fordítások területi beállításonkénti aránya (az angollal azonos / helyőrző / hiányzó levelek az engedélyezési listán kívül) nem haladhatja meg a `config/quality/i18n-translation-baseline.json` értékét és a ráhagyást | **Tájékoztató jellegű** | +| Szkript | Mit ellenőriz | Blokkoló | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `check-ui-keys-coverage` (inline) | A felhasználói felület i18n-kulcsainak lefedettsége ≥ 65% | Igen | +| `check-ui-value-drift` (inline) | Egy átírt angol **érték** nem hagy maga után elavult fordítást | Igen | +| `check-new-key-coverage` (inline) | Egy **új** angol kulcs minden területi beállításban le van fordítva — a `__MISSING__:` jelölő elutasításra kerül | Igen | +| `check-translation-ratio` | A tényleges fordítások területi beállításonkénti aránya (az angollal megegyező / helyőrző / hiányzó levelek az engedélyezési listán kívül) nem haladhatja meg a `config/quality/i18n-translation-baseline.json` értékét + a tűrést | **Tájékoztató jellegű** | -`fetch-depth: 0` szükséges — az értékeltérés-kapu az `en.json` fájlt a beolvasztási bázissal hasonlítja össze. +`fetch-depth: 0` szükséges — az értékváltozási kapu az `en.json` fájlt a beolvasztási bázissal hasonlítja össze. -#### `check-ui-value-drift` — elavult fordítások kapuja +#### `check-ui-value-drift` — elavult fordításokat kiszűrő kapu -Azt az egyetlen i18n-regressziót észleli, amelyet a többi kapu szerkezetéből adódóan nem képes felismerni: egy angol értéket -átírnak, miközben a _korábbi_ angol szövegből származó fordítások változatlanul maradnak, így -a nem angol nyelvű felhasználók továbbra is magabiztosan megfogalmazott, de immár helytelen szöveget olvasnak. +Azt az egy i18n-regressziót észleli, amelyet a többi kapu szerkezetileg nem képes felismerni: egy angol értéket +átírnak, de a _korábbi_ angol szövegből származó fordítások megmaradnak, így +a nem angol nyelvű felhasználók továbbra is magabiztosan megfogalmazott, de immár hibás szöveget olvasnak. -Ez valóban éles kiadásba került. Az `oauthModal.googleOAuthWarning` szövegét átírták, amikor bekerült az Antigravity -bejelentkezési segéd (#5203); **43 területi beállításból 39** megtartotta azt a szöveget, amely arra utasította az üzemeltetőket, hogy „másolják ki -a teljes URL-t, és illesszék be alább” — ennél a szolgáltatónál azonban ez a folyamat nem hajtható végre. A probléma +Ez valóban éles rendszerbe került. Az `oauthModal.googleOAuthWarning` át lett írva, amikor az Antigravity +bejelentkezési segéd bekerült (#5203); **43 területi beállításból 39** továbbra is azt közölte az üzemeltetőkkel, hogy „másolják ki a +teljes URL-t, és illesszék be alább” — ez a folyamat ennél a szolgáltatónál nem hajtható végre. A hiba #8463-ig észrevétlen maradt, mert: -- a `sync-ui-keys` csak a **hiányzó** kulcsokat tölti ki, az **elavultakat** soha; -- a `check-ui-keys-coverage` a kulcsok _jelenlétét_ számolja, így az elavult fordítás lefedettnek minősül; -- a `check-translation-drift` a `docs/i18n//**.md` dokumentációs tükröket követi nyomon — - a `src/i18n/messages/*.json` fájlokat soha nem olvassa. A 2026-09-es újraszinkronizálás óta blokkoló a `docs-sync-strict` feladatban: alapdokumentum szerkesztése → `npm run i18n:run -- --files=` (szakaszszintű, kis költségű). +- a `sync-ui-keys` csak a **hiányzó** kulcsokat pótolja, az **elavultakat** soha; +- a `check-ui-keys-coverage` a kulcsok _jelenlétét_ számolja, ezért az elavult fordítás is lefedettnek minősül; +- a `check-translation-drift` a `docs/i18n//**.md` dokumentációs tükröket követi — + a `src/i18n/messages/*.json` fájlokat soha nem olvassa. A 2026-09-es újraszinkronizálás óta blokkoló a `docs-sync-strict` feladatban: alapvető dokumentum szerkesztése → `npm run i18n:run -- --files=` (szakaszszintű, gyors). -**Diff-alapú, nem alapállapotra támaszkodó.** Az egyesítés alapjául szolgáló commit `en.json` fájlját hasonlítja össze a -munkafával; minden olyan kulcsnál, amelynek angol értéke megváltozott, elavultnak számít minden olyan területi beállítás, amely továbbra is -érintetlen fordítást tartalmaz. Ez szándékosan **befagyasztja a már meglévő adósságot** — egy diff -nem tudja megmutatni, hogy egy régóta létező fordítás melyik régi angol szövegből származik, ezért az ellenőrzés -csak azt vizsgálja, amit az aktuális módosítás érint. Az alternatíva (kulcsonkénti hash-alapállapot) -egy ~600 KB-os generált fájlt igényelne, amely háromszor akkora, mint a legnagyobb meglévő alapállapot, és minden i18n PR-nél módosulna. +**Diff-alapú, nem alapállapotra támaszkodik.** A merge-alapnál lévő `en.json` fájlt hasonlítja össze a +munkafával; minden olyan kulcs esetében, amelynek angol értéke megváltozott, elavultnak +minősül minden olyan területi beállítás, amely még érintetlen fordítást tartalmaz. Ez szándékosan +**befagyasztja a már meglévő adósságot** — egy diff nem képes megmutatni, hogy egy régóta meglévő +fordítás melyik korábbi angol szövegből származik, ezért az ellenőrzés csak azt vizsgálja, amit az +aktuális módosítás érint. Az alternatíva (egy kulcsonkénti hash-alapállapot) egy körülbelül +600 KB-os generált fájlba kerülne, amely háromszor akkora, mint a jelenlegi legnagyobb alapállapot, +és minden i18n PR-nél módosulna. Kétféleképpen teljesíthető: 1. frissítse az érintett fordításokat, vagy -2. állítsa őket `__MISSING__:` értékre — futásidőben ekkor a javított angol szöveg jelenik meg - (`src/i18n/request.ts::deepMergeFallback`, #7258), a kulcs pedig bekerül a fordítási sorba. +2. állítsa őket `__MISSING__:` értékre — ekkor futásidőben a javított angol + szöveg jelenik meg (`src/i18n/request.ts::deepMergeFallback`, #7258), a kulcs pedig bekerül a fordítási sorba. -Ha a karakterlánc **jelentése** változott, inkább **nevezze át a kulcsot**: egy új kulcs nem örökölhet -elavult fordítást. Ezt a mintát használta a #8463. +Ha a karakterlánc **jelentése** változott meg, inkább **nevezze át a kulcsot**: egy új kulcs nem +örökölhet elavult fordítást. A #8463 ezt a mintát alkalmazta. ```bash npm run i18n:check-value-drift # szigorú (ezt futtatja a CI) @@ -241,48 +240,48 @@ npm run i18n:check-value-drift:warn # csak jelentés BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -0-s kilépési kóddal és `SKIP reason=base-unresolved` üzenettel fejeződik be, ha az alapkatalógus nem olvasható (sekély -klónozás az alap-referencia nélkül), a `check-openapi-breaking` működését követve. +0-s kóddal és `SKIP reason=base-unresolved` üzenettel lép ki, ha az alapkatalógus nem olvasható +(sekély klón az alaphivatkozás nélkül), a `check-openapi-breaking` működését követve. ### Feladat: `i18n` Teljes i18n-ellenőrzési mátrix (területi beállításonként egy feladat). A teljes feladat tájékoztató jellegű. -| Szkript | Ellenőrzi | Blokkoló | -| ------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------- | -| `validate_translation.py quick` | A fordítás teljességét területi beállításonként | **Tájékoztató jellegű** (`continue-on-error: true` a teljes feladaton) | +| Szkript | Ellenőrzés | Blokkoló | +| ------------------------------- | ---------------------------------------------- | -------------------------------------------------------------- | +| `validate_translation.py quick` | A fordítás teljessége területi beállításonként | **Tájékoztató** (`continue-on-error: true` a teljes feladaton) | ### Feladat: `pr-test-policy` Csak pull requestek esetén fut. -| Szkript | Ellenőrzi | Blokkoló | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | -| `check:pr-test-policy` | A `src/`, `open-sse/`, `electron/` vagy `bin/` éles kódját módosító PR-eknek teszteket is tartalmazniuk vagy frissíteniük kell (8. szigorú szabály) | Igen | -| `check:test-masking` | A módosított tesztfájlok nem csökkentik az assertionök nettó számát, és nem adnak hozzá `assert.ok(true)` tautológiákat | Igen | -| `check:pr-evidence` | A PR leírása hivatkozik a módosításhoz tartozó teszt-/VPS-bizonyítékokra (a 18. szigorú szabály gépesítése a PR szövegének grep segítségével történő vizsgálatával — törékeny, lásd a Backlogot) | Igen | +| Szkript | Ellenőrzés | Blokkoló | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | A `src/`, `open-sse/`, `electron/` vagy `bin/` termelési kódját módosító PR-eknek teszteket is tartalmazniuk vagy frissíteniük kell (8. szigorú szabály) | Igen | +| `check:test-masking` | A módosított tesztfájlok nem csökkentik az assertionök nettó számát, és nem adnak hozzá `assert.ok(true)` tautológiákat | Igen | +| `check:pr-evidence` | A PR törzse tesztelési/VPS-bizonyítékot hivatkozik a módosításhoz (a 18. szigorú szabályt a PR szövegében végzett kereséssel automatizálja — törékeny, lásd: Backlog) | Igen | ### Feladat: `test-vitest` -A `build` után fut. Sikertelenség esetén blokkolja az egyesítést. +A `build` után fut. Hiba esetén blokkolja a merge-et. -| Tesztcsomag | Ellenőrzi | Blokkoló | -| ---------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP-kiszolgáló (110 eszköz), autoCombo, gyorsítótár — vitest futtató | Igen | -| `test:vitest:ui` | UI-komponenstesztek — vitest futtató | **Blokkoló** — a már meglévő hibák kifejezetten ki vannak zárva a `vitest.config.ts` fájlban; az új hibák sikertelenné teszik a feladatot | +| Tesztcsomag | Ellenőrzés | Blokkoló | +| ---------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP-kiszolgáló (110 eszköz), autoCombo, gyorsítótár — vitest futtató | Igen | +| `test:vitest:ui` | UI-komponenstesztek — vitest futtató | **Blokkoló** — a már meglévő hibák kifejezetten ki vannak zárva a `vitest.config.ts` fájlban; az új hibák meghiúsítják a feladatot | ### Éjszakai munkafolyamatok (ütemezett, tájékoztató jellegű) -Ezek cron-ütemezés szerint (és `workflow_dispatch` esetén) futnak, PR-eken soha. Mindegyik tájékoztató jellegű. +Ezek cron-ütemezés szerint (és `workflow_dispatch` útján) futnak, PR-ek esetén soha. Mindegyik tájékoztató jellegű. -| Munkafolyamat | Ellenőrzi | Blokkoló | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | -| `nightly-property` | fast-check tulajdonságtesztek véletlenszerű kezdőértékkel és magas futtatásszámmal | **Tájékoztató jellegű** | -| `nightly-resilience` | heapnövekedési ellenőrzés, kaotikus hibainjektálás, k6 terhelési/tartóssági teszt | **Tájékoztató jellegű** | -| `nightly-llm-security` | promptfoo injekcióvédelem (blokkolási mód) + garak próbák (szolgáltatói titok nélkül kihagyva) | **Tájékoztató jellegű** | -| `nightly-schemathesis` | OpenAPI-szerződés fuzz tesztelése (schemathesis) egy élő OmniRoute példánnyal a `docs/openapi.yaml` használatával — felszínre hozza a specifikáció megsértéseit / a nem kezelt 500-as hibákat (8. fázis B.4) | **Tájékoztató jellegű** | -| `nightly-mutation` | Stryker mutációtesztelési pontszám a gyors egységteszt-sávon — a túlélő mutánsok gyenge assertionöket jeleznek | **Tájékoztató jellegű** | -| `nightly-compat` | Node-motor kompatibilitási mátrixa a támogatott `engines.node` tartományokban | **Tájékoztató jellegű** | +| Munkafolyamat | Ellenőrzés | Blokkoló | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `nightly-property` | fast-check tulajdonságtesztek véletlenszerű kezdőértékkel és magas futtatásszámmal | **Tájékoztató** | +| `nightly-resilience` | heap-növekedési kapu, káoszalapú hibainjektálás, k6 terhelési/tartós terhelési teszt | **Tájékoztató** | +| `nightly-llm-security` | promptfoo injekció elleni védelem (blokkoló mód) + garak próbák (szolgáltatói titok nélkül kihagyva) | **Tájékoztató** | +| `nightly-schemathesis` | OpenAPI-szerződés fuzzing (schemathesis) egy élő OmniRoute példányon a `docs/openapi.yaml` használatával — felszínre hozza a specifikáció megsértéseit / a nem kezelt 500-as hibákat (8. fázis, B.4) | **Tájékoztató** | +| `nightly-mutation` | Stryker mutációtesztelési pontszám a gyors egységteszt-sávon — a túlélő mutánsok gyenge assertionöket jeleznek | **Tájékoztató** | +| `nightly-compat` | Node-motor kompatibilitási mátrixa a támogatott `engines.node` tartományokban | **Tájékoztató** | --- @@ -557,41 +556,41 @@ kapcsolódási hibát naplóz, és soha nem szakítja meg a munkameneteket. ## Racionalizálási hátralék (ROI-felülvizsgálat — 9. fázis, 3. hullám) -Ezt a leltárt 2026-06-17-én egyeztettük a `ci.yml` fájllal (az előző verzióból kimaradt az +Ezt a leltárt 2026-06-17-én összevetettük a `ci.yml` tartalmával (az előző verzióból kimaradt az `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Az egyeztetett készlet ROI-felülvizsgálata -az alábbi racionalizálási jelölteket azonosította. **Az összevonások mechanikus CI-módosítások; -az átállítások/eltávolítások az üzemeltető számára fenntartott szabályzati döntések.** Az alábbiak -közül még semmi sincs alkalmazva. +a következő racionalizálási jelölteket azonosította. **Az összevonások mechanikus CI-módosítások; +az átállítások/eltávolítások az üzemeltető számára fenntartott szabályzati döntések.** Az alábbiak közül +még semmi sem lett alkalmazva. -**A fentieken túl szintén nem dokumentált** (tanácsadó jellegű, alacsony jelértékű): a `docs-lint` feladat -(markdownlint + Vale, a teljes feladatnál `continue-on-error`), valamint az önálló ellenőrzési munkafolyamatok: +**Fent szintén nem dokumentált** (tanácsadó jellegű, alacsony jelértékű): a `docs-lint` feladat +(markdownlint + Vale, a teljes feladaton `continue-on-error`), valamint az önálló ellenőrző munkafolyamatok: `semgrep.yml` / `codeql.yml` / `scorecard.yml`. A `semgrepFindings: 0` szerepel a -`quality-baseline.json` fájlban, de nincs blokkoló racsnis ellenőrzéshez kapcsolva a `ci.yml` fájlban — a mérőszám +`quality-baseline.json` fájlban, de nincs blokkoló racsnihoz kötve a `ci.yml` fájlban — a metrika jelenleg árva. -### Összevonás / deduplikálás (mechanikus, alacsonyabb kockázat) +### Összevonás / deduplikáció (mechanikus, alacsonyabb kockázat) -Minden jelöltet ellenőriztünk az aktív kapuállapot alapján 2026-06-17-én (bízz, de ellenőrizd); -több „kézenfekvő” összevonásról is kiderült, hogy rejtett adósságot takar, ezért **nem** alkalmazható közvetlenül. +Minden jelöltet ellenőriztünk az éles kapuállapot alapján 2026-06-17-én (bízz, de ellenőrizd); +több „kézenfekvő” összevonásról kiderült, hogy rejtett adósságot takar, ezért **nem** alkalmazhatók közvetlenül. -- **A `check:docs-sync` kétszer fut** — önállóan a `lint` feladatban, majd ismét a `check:docs-all` részeként (`docs-sync-strict`), továbbá a husky pre-commit hookban. ✅ **KÉSZ** — az önálló `lint`-meghívás eltávolítva. -- **CVE-ellenőrzés** — ❌ **NEM vonható össze közvetlenül.** Az `audit:deps` bármely magas/kritikus CVE esetén azonnal hibát jelez; a `check:vuln-ratchet` (osv) csak az alapértékhez képesti _visszaesés_ esetén jelez hibát (jelenleg 1 MODERATE). Eltérő szemantika — az `audit:deps` eltávolításával elveszne az abszolút magas/kritikus kapu. Mindkettő megtartandó. -- **Ciklusészlelés** — ❌ **NEM vonható össze közvetlenül.** A `check:circular-deps` (dpdm) **91 ciklust** jelez (ezért tanácsadó jellegű); ezek előzetes megszüntetése nélkül nem tehető blokkolóvá, és szélesebb hatókörű, mint a hibamentes, gondosan összeállított `check:cycles`. A `check:cycles` maradjon blokkoló; a 91 dpdm-ciklus megszüntetése külön hátraléktétel. -- **Komplexitás** — ✅ **KÉSZ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): egyetlen ESLint-bejárás, `ruleId` szerinti számlálással, így a ciklomatikus+max-lines és a kognitív alapértékek függetlenek maradnak; az egyedi `check:complexity` / `check:cognitive-complexity` megmarad a helyi `--update` futtatásokhoz. -- **`/api` hallucináció elleni védelem** — ✅ **KÉSZ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): a `src/app/api` fájlrendszerének egyszeri leltározása, miközben az openapi-routes + docs-symbols továbbra is egymástól függetlenül jelent; az egyedi ellenőrzések megmaradnak a helyi futtatásokhoz. -- **A `check:node-runtime` 11 feladatban fut** — ⚠️ **alacsony ROI.** Mindegyik külön futtatókörnyezet, az ellenőrzés pedig <1 mp; a teljes megtakarítás ~10 mp lenne, miközben elveszne egy olcsó, feladatonkénti védőellenőrzés. Nem éri meg a felfordulást. +- **A `check:docs-sync` kétszer fut** — önállóan a `lint` feladatban, majd ismét a `check:docs-all` (`docs-sync-strict`) részeként és a husky pre-commit hookban. ✅ **KÉSZ** — az önálló `lint`-meghívás eltávolítva. +- **CVE-ellenőrzés** — ❌ **NEM vonható össze tisztán.** Az `audit:deps` minden magas/kritikus CVE esetén hibával leáll; a `check:vuln-ratchet` (osv) csak az alapértékhez viszonyított _regresszió_ esetén áll le hibával (jelenleg 1 MODERATE). Eltérő szemantika — az `audit:deps` eltávolításával elveszne az abszolút magas/kritikus kapu. Mindkettőt meg kell tartani. +- **Ciklusészlelés** — ✅ **KÉSZ** (#15159 G-01/G-02). A korábbi szöveg a `check:cycles` ellenőrzést „zöld, gondosan összeállított” kapunak nevezte, és azzal indokolta blokkolóként való megtartását, hogy a `check:circular-deps` (dpdm) 91 ciklust jelzett. Ez a zöld állapot **hamis zöld** volt: a `check:cycles` 5 alkönyvtárat (450 fájlt) vizsgált, csak a statikus `import|export … from` mintára illeszkedett, és minden `@/` és `@omniroute/open-sse/` specifikátort eldobott, így nem láthatta a repót uraló dinamikus importálási és aliasciklusokat. Javítva: a kapu most bejárja a `src` + `open-sse` könyvtárakat (5023 fájl), a TypeScript AST-ből gyűjti a specifikátorokat (így az `import("…")` számít, a típuspozícióban lévő `typeof import("…")` viszont nem), és feloldja a tsconfig `paths` beállításait. **14** ciklust talál, nem 0-t. Mivel 14 már meglévő ciklus nem javítható ki egy kapu-PR-ben, a `check:cycles` mostantól **racsni** (`--ratchet`, a felső korlát `metrics.cycles.value = 14` a `quality-baseline.json` fájlban, `direction: down`) — minden _regressziót_ blokkol, és az érték csak csökkenhet. A CI az `npm run check:cycles:ratchet` parancsot futtatja. A fokozatos leépítés az **A-01** részeként történik. A `check:circular-deps` (dpdm) tanácsadó jelleggel megmarad szélesebb körű másodvéleményként. +- **Komplexitás** — ✅ **KÉSZ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): egyetlen ESLint-bejárás, ruleId szerinti számlálással, így a ciklomatikus+max-lines és a kognitív alapértékek függetlenek maradnak; az egyedi `check:complexity` / `check:cognitive-complexity` ellenőrzések megmaradnak a helyi `--update` használatához. +- **`/api` hallucináció elleni védelem** — ✅ **KÉSZ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): a `src/app/api` egyetlen fájlrendszer-leltározása; az openapi-routes + docs-symbols továbbra is egymástól függetlenül jelent; az egyedi ellenőrzések megmaradnak helyi futtatáshoz. +- **A `check:node-runtime` 11 feladatban fut** — ⚠️ **alacsony ROI.** Mindegyik külön futtatókörnyezet, az ellenőrzés pedig <1 másodperc; a teljes megtakarítás ~10 másodperc lenne, miközben elveszne egy olcsó, feladatonkénti védőkorlát. Nem éri meg a vele járó változtatás. - **`typecheck:noimplicit:core` a CI lintelésében** — ✅ **eltávolítva a lint feladatból** (tanácsadó jellegű `continue-on-error` volt); a blokkoló típusfelületet a `typecheck:core` + `check:type-coverage` biztosítja. A helyi szkript megmaradt. ### Átállítás / döntés (üzemeltetői szabályzat) -- `check:openapi-security-tiers` (tanácsadó jellegű) — ❌ **NEM állítható át közvetlenül.** 0-s kilépési kóddal fejeződik be, de figyelmeztet, hogy a `LOCAL_ONLY_API_PREFIXES` alá tartozó több `traffic-inspector` útvonalról hiányzik az `x-loopback-only: true` annotáció. A kikényszerítéshez előbb hozzá kell adni ezeket az annotációkat az `openapi.yaml` fájlhoz. -- `typecheck:noimplicit:core` (tanácsadó jellegű) — nagyrészt lefedi a blokkoló `check:type-coverage` racsnis ellenőrzés. Állítsa át racsnis ellenőrzésre, vagy távolítsa el a redundáns második `tsc`-menetet. -- `test:vitest:ui` (most már **blokkoló**) — a már meglévő hibák explicit módon ki vannak zárva a `vitest.config.ts` fájlban, `// #8618` nyomonkövetési megjegyzésekkel; az új hibák meghiúsítják a feladatot. -- `check:secrets` (gitleaks, blokkoló racsnis ellenőrzés 3 dokumentált téves pozitívnál rögzítve) — vegye fel a 3 esetet az engedélyezési listára a 0 eléréséhez, vagy minősítse vissza tanácsadó jellegűvé. Átfedésben van a GitHub natív titokellenőrzésével és a `check:public-creds` ellenőrzéssel. -- `check:pr-evidence` (blokkoló, grep-pel vizsgálja a PR-törzs prózáját) — magas a téves pozitív eredmények kockázata; eltávolítása gyengíti a 18. szigorú szabály kikényszerítését, ezért ez valódi szabályzati döntés. -- `semgrep` (tanácsadó jellegű, önálló) — az OWASP-családok tekintetében átfedésben van a CodeQL-lel; kösse az alapértékét racsnis ellenőrzéshez, vagy távolítsa el. +- `check:openapi-security-tiers` (tanácsadó jellegű) — ❌ **NEM állítható át tisztán.** 0-s kilépési kódot ad, de figyelmeztet arra, hogy a `LOCAL_ONLY_API_PREFIXES` alá tartozó több `traffic-inspector` útvonalról hiányzik az `x-loopback-only: true` annotáció. A kikényszerítéséhez először hozzá kell adni ezeket az annotációkat az `openapi.yaml` fájlhoz. +- `typecheck:noimplicit:core` (tanácsadó jellegű) — nagyrészt lefedi a blokkoló `check:type-coverage` racsni. Állítsa át racsnivá, vagy távolítsa el a redundáns második `tsc` futtatást. +- `test:vitest:ui` (most **blokkoló**) — a már meglévő hibák kifejezetten ki vannak zárva a `vitest.config.ts` fájlban `// #8618` nyomonkövetési megjegyzésekkel; az új hibák meghiúsítják a feladatot. +- `check:secrets` (gitleaks, blokkoló racsni, 3 dokumentált téves pozitívnál befagyasztva) — tegye engedélyezési listára a 3 találatot a 0 eléréséhez, vagy minősítse vissza tanácsadó jellegűvé. Átfedésben van a GitHub natív titokellenőrzésével és a `check:public-creds` ellenőrzéssel. +- `check:pr-evidence` (blokkoló, grep segítségével vizsgálja a PR-törzs prózáját) — magas a téves pozitív eredmények kockázata; eltávolítása gyengítené a 18. szigorú szabály kikényszerítését, így ez valódi szabályzati döntés. +- `semgrep` (tanácsadó jellegű, önálló) — az OWASP-családok tekintetében átfedésben van a CodeQL-lel; kösse az alapértékét racsnihoz, vagy távolítsa el. --- diff --git a/docs/i18n/hu/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/hu/docs/compression/COMPRESSION_GUIDE.md index 8aaa2a8ce06a..603e88515e21 100644 --- a/docs/i18n/hu/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/hu/docs/compression/COMPRESSION_GUIDE.md @@ -453,8 +453,7 @@ kerül injektálásra az angol helyett. A `messages` mezővel rendelkező törzsön egy tartalom-átugrás (`shouldBypassCavemanOutputMode()` a `open-sse/services/compression/outputMode.ts` fájlban) ellenőrzi az utolsó három üzenetet, és kihagyja a stílusokat az egész fordulóra, ha azok megfelelnek a biztonsági, visszafordíthatatlan műveleti, -pontosítási vagy sorrendérzékeny kulcsszavainak. Az átugrás attól függetlenül fut, hogy a műszerfal -**Automatikus Tisztaság Átugrás** kapcsolója (`cavemanOutputMode.autoClarity`) mire van állítva. +pontosítási vagy sorrendérzékeny kulcsszavainak. Az átugrás akkor fut, amíg a műszerfal **Automatikus Tisztaság Átugrás** kapcsolója (`cavemanOutputMode.autoClarity`) be van kapcsolva, ami az alapértelmezés; kikapcsolt kapcsolónál a kiválasztott stílusok ezeken a fordulókon is érvényesülnek. Amikor az átugrás engedélyezi a fordulatot, a `placeSystemInstruction()` (ugyanaz a fájl), amely soha nem hoz létre új `messages[0]`-t, a blokkot az alábbiak közül az elsőbe helyezi, amit talál: diff --git a/docs/i18n/hu/docs/reference/ENVIRONMENT.md b/docs/i18n/hu/docs/reference/ENVIRONMENT.md index d9cc8fae4f01..350b85da2445 100644 --- a/docs/i18n/hu/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/hu/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Szükséges titkos értékek -Ezeket az első futtatás előtt **kötelező** beállítani. Nélkülük az alkalmazás vagy nem indul el, vagy nem biztonságos alapértelmezésekkel működik. - -| Változó | Kötelező | Alapértelmezett | Forrásfájl | Leírás | -| ---------------------------- | ---------------------------- | ----------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Igen** | _(nincs)_ | `src/lib/auth` | Aláírja és ellenőrzi az irányítópult összes munkamenet-cookie-ját (JWT). A létrehozásához használja az `openssl rand -base64 48` parancsot. | -| `API_KEY_SECRET` | **Igen** | _(nincs)_ | `src/lib/db/apiKeys.ts` | AES-titkosítási kulcs az SQLite-ban tárolt API-kulcsértékek titkosításához. A létrehozásához használja az `openssl rand -hex 32` parancsot. | -| `INITIAL_PASSWORD` | **Igen** | `CHANGEME` | Bootstrap szkript | Beállítja az irányítópult kezdeti rendszergazdai jelszavát (megegyezik a `.env.example` alapértelmezésével — szándékosan nyilvánvalóan nem biztonságos, hogy kikényszerítse a módosítását). **Az első használat előtt módosítsa.** Bejelentkezés után az Irányítópult → Beállítások → Biztonság menüpontban módosíthatja. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Igen** (éles környezetben) | _(nincs beállítva)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Az internal Codex Responses WebSocket-híd megosztott titkos értéke. Hitelesíti az Electron/böngésző WS-továbbítója és az OmniRoute közötti hídkéréseket. ⚠️ **Éles környezetben KÖTELEZŐ — ha nincs beállítva, a rendszer minden WS-hídkérést elutasít.** A létrehozásához használja az `openssl rand -base64 32` parancsot. | -| `OMNIROUTE_SW_BUILD_ID` | Nem | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicit service worker-gyorsítótár-frissítési azonosító a PWA-burokhoz (#11779); az első a feloldási láncban. | -| `SOURCE_VERSION` | Nem | _(nincs beállítva)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | A lánc második eleme — a PaaS buildrendszerek (például a Heroku-stílusú rendszerek) állítják be a telepített commit értékére. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nem | _(származtatott)_ | `src/shared/components/PwaRegister.tsx` | Buildelési időben elérhető nyilvános érték, amelyet a kliens a `/sw.js?v=…` regisztrálásához használ; a fenti két értékből, majd a git SHA-ból származik. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nem (automatikus) | _(indításonként automatikus)_ | `src/server/authz/policies/management.ts` | Folyamatonkénti titkos érték, amely igazolja, hogy a megbízható peer-IP-bélyeg az OmniRoute saját HTTP-kiszolgálójától (`scripts/dev/peer-stamp.mjs`) származik. Az authz middleware csak akkor bízik meg a kérés helyi eredetében (a LOCAL_ONLY útvonalak loopback/LAN-korlátozásakor), ha a bélyeg tartalmazza ezt a tokent. Minden indításkor automatikusan létrejön — hagyja beállítatlanul; csak olyan többfolyamatos konfigurációknál rögzítse, amelyeknek meg kell osztaniuk a bélyeget. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nem | _(nincs beállítva)_ | `scripts/dev/peer-stamp.mjs` | Azon fordított proxyk vesszővel elválasztott IP-címei vagy CIDR-tartományai, amelyek `X-Forwarded-For` / `X-Real-IP` értékeit az IP-engedélyezési/-tiltási lista megbízhatónak tekintheti, a mindig megbízhatónak számító loopback- és privát hálózati címeken, valamint Cloudflare-peremkiszolgálókon felül. Csak más nyilvános címen működő proxy esetén szükséges; egyébként a szűrő magának a proxynak a címét értékeli. | +A **Yes** (Igen) jelölésű, be nem állított titkos értékek az első futtatáskor automatikusan létrejönnek (`JWT_SECRET`, `API_KEY_SECRET`), és az adatkönyvtárban lévő `server.env` fájlba íródnak — akkor állítsa be őket explicit módon, ha az a könyvtár rövid életű. Kivétel az `OMNIROUTE_WS_BRIDGE_SECRET`: ez nem kerül a `server.env` fájlba — a kiszolgáló belépési pontjai minden indításkor egy rövid életű értéket állítanak elő, amelyet csak a folyamaton belüli híd használhat, ezért a külső hídkliensekhez explicit beállítás szükséges. + +| Változó | Kötelező | Alapértelmezett | Forrásfájl | Leírás | +| ---------------------------- | ---------------------------- | ----------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Igen** | _(nincs)_ | `src/lib/auth` | Aláírja és ellenőrzi az irányítópult összes munkamenet-cookie-ját (JWT). A létrehozásához használja az `openssl rand -base64 48` parancsot. | +| `API_KEY_SECRET` | **Igen** | _(nincs)_ | `src/lib/db/apiKeys.ts` | AES-titkosítási kulcs az SQLite-ban tárolt API-kulcsértékek titkosításához. A létrehozásához használja az `openssl rand -hex 32` parancsot. | +| `INITIAL_PASSWORD` | Nem | _(nincs beállítva)_ | `src/lib/auth/managementPassword.ts` | Amíg még nincs mentett jelszó, induláskor beállítja az irányítópult jelszavát; miután egy jelszó el lett mentve, a rendszer figyelmen kívül hagyja. Az npm- és a forrásból történő telepítések a `.env.example` fájlt `.env` néven másolják, így a nyilvánosan ismert `CHANGEME` helyőrzővel indulnak: cserélje le az első indítás előtt, vagy állítsa be az `INITIAL_PASSWORD=` (üres) értéket abban a `.env` fájlban, hogy a jelszó az első látogatáskor a beállítási varázslóban jöjjön létre, ahogy a beállítatlan érték (a Docker-image alapértelmezése) is teszi. A mentett jelszót az Irányítópult → Beállítások → Biztonság menüben módosíthatja, vagy futtassa az `omniroute reset-password` parancsot (forrásból történő telepítésnél: `node bin/reset-password.mjs`) úgy, hogy a `DATA_DIR` a kiszolgáló adatkönyvtárára mutat. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Igen** (éles környezetben) | _(nincs beállítva)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Az internal Codex Responses WebSocket-híd megosztott titkos értéke. Hitelesíti az Electron/böngésző WS-továbbítója és az OmniRoute közötti hídkéréseket. ⚠️ **Éles környezetben KÖTELEZŐ — ha nincs beállítva, a rendszer minden WS-hídkérést elutasít.** A létrehozásához használja az `openssl rand -base64 32` parancsot. | +| `OMNIROUTE_SW_BUILD_ID` | Nem | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicit service worker-gyorsítótár-frissítési azonosító a PWA-burokhoz (#11779); az első a feloldási láncban. | +| `SOURCE_VERSION` | Nem | _(nincs beállítva)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | A lánc második eleme — a PaaS buildrendszerek (például a Heroku-stílusú rendszerek) állítják be a telepített commit értékére. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nem | _(származtatott)_ | `src/shared/components/PwaRegister.tsx` | Buildelési időben elérhető nyilvános érték, amelyet a kliens a `/sw.js?v=…` regisztrálásához használ; a fenti két értékből, majd a git SHA-ból származik. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nem (automatikus) | _(indításonként automatikus)_ | `src/server/authz/policies/management.ts` | Folyamatonkénti titkos érték, amely igazolja, hogy a megbízható peer-IP-bélyeg az OmniRoute saját HTTP-kiszolgálójától (`scripts/dev/peer-stamp.mjs`) származik. Az authz middleware csak akkor bízik meg a kérés helyi eredetében (a LOCAL_ONLY útvonalak loopback/LAN-korlátozásakor), ha a bélyeg tartalmazza ezt a tokent. Minden indításkor automatikusan létrejön — hagyja beállítatlanul; csak olyan többfolyamatos konfigurációknál rögzítse, amelyeknek meg kell osztaniuk a bélyeget. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nem | _(nincs beállítva)_ | `scripts/dev/peer-stamp.mjs` | Azon fordított proxyk vesszővel elválasztott IP-címei vagy CIDR-tartományai, amelyek `X-Forwarded-For` / `X-Real-IP` értékeit az IP-engedélyezési/-tiltási lista megbízhatónak tekintheti, a mindig megbízhatónak számító loopback- és privát hálózati címeken, valamint Cloudflare-peremkiszolgálókon felül. Csak más nyilvános címen működő proxy esetén szükséges; egyébként a szűrő magának a proxynak a címét értékeli. | ### Létrehozási parancsok @@ -217,38 +217,38 @@ Az OmniRoute minden tartós adat tárolásához **SQLite**-ot használ (a `bette ## 4. Biztonság és hitelesítés -| Változó | Alapértelmezett | Forrásfájl | Leírás | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | A gép ujjlenyomatának létrehozásához a hardverazonosítókkal kombinált só. Az elkülönítés érdekében telepítésenként módosítsa. | -| `OMNIROUTE_CLI_SALT` | _(nincs beállítva = telepítésenként véletlenszerű, a `/cli-token-salt.json` fájlban megőrzött só)_ | `src/lib/machineToken.ts` | A helyi CLI-hitelesítési token származtatásához használt HMAC-só. Ezen érték beállítása elforgatja a gépen található összes CLI-tokent, és mindig elsőbbséget élvez a megőrzött sóval szemben. Lásd: `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Beállítja a `Secure` jelzőt a munkamenet-cookie-kon. HTTPS mögötti futtatáskor **`true` értékűnek kell lennie**. | -| `REQUIRE_API_KEY` | `false` | API-köztesszoftver | Ha `true`, minden `/v1/*` proxykérésnek tartalmaznia kell egy érvényes API-kulcsot. **Ez a jelző nem szabályozza a `GET /v1/models` hozzáférését**, amely ehelyett az irányítópult bejelentkezési beállítását (`requireAuthForModels`) követi — ezért a `/v1/models` végpontról érkező `401` válasz NEM jelenti azt, hogy a következtetés védett. Lásd: `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Lehetővé teszi a teljes API-kulcsértékek felfedését az irányítópult felhasználói felületén. Az irányítópult funkciójelzői között konfigurálható; megosztott példányokon biztonsági kockázatot jelent. | -| `NO_LOG_API_KEY_IDS` | _(üres)_ | `src/lib/compliance/index.ts` | A kérésnaplózást megkerülő API-kulcs-azonosítók vesszővel elválasztott listája (GDPR-megfelelőség). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nincs beállítva = korlátlan)_ | `src/shared/utils/apiKeyPolicy.ts` | Tartalék napi kérési keret, amely azon API-kulcsokra vonatkozik, amelyek `rate_limits` oszlopa null értékű. Ha nincs beállítva vagy üres: nincs implicit korlát (#2289, #11017). A `0` ugyanezt jelenti (korlátlan). A pozitív egész N engedélyezi az N/nap, 5N/hét és 20N/hónap korlátokat. A hibás formátumú, nem üres értékek esetén a rendszer visszatér a korábbi 1000/nap, 5000/hét és 20000/hónap időablakokhoz. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | A kérés törzsének legnagyobb engedélyezett mérete. Az ezt a korlátot meghaladó adattartalmakat elutasítja. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Az e küszöbértéket elérő vagy meghaladó tényleges kéréstörzsek a JSON feldolgozása előtt megszerzik az atomi, folyamaton belüli, nagy terhelésű befogadási bérletet (BYTE útvonal, beleértve a `POST /v1/responses` kérést). Ugyanaz az [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) szerinti, elegendő szabad kapacitás esetén alkalmazott kivétel érvényes, mint a struktúraigényes kéréseknél; továbbra is korlátozza az `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), hogy az [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne térjen vissza. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | A csevegési útvonal szigorú korlátja, amelyet a korlátozott beolvasás során beolvasott bájtok alapján érvényesít, beleértve a hiányzó, érvénytelen vagy megtévesztő `Content-Length` értékkel rendelkező kéréseket is; a túllépés `413` választ eredményez. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nincs beállítva — nincs kérésszámkorlát)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ez a régi, kérések SZÁMÁRA vonatkozó korlát mostantól csak akkor lép életbe, ha explicit módon be van állítva. Ha nincs beállítva (ez az alapértelmezés), a nagy erőforrás-igényű csevegési kérések befogadását ehelyett az `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` szabályozza — egy automatikusan származtatott BÁJTkeret, amelynek méretezése a folyamat tényleges memóriakorlátja alapján történik **egyetlen folyamatban** (egy V8-halom). Két, egymást átfedő, ~750k tokenes `/v1/responses` kérés megszakít ~12 GiB-os halmokat (#7849) — ez **memóriakeretre** vonatkozó figyelmeztetés, nem pedig a termék 2-es szigorú felső korlátja. Egy egészséges folyamat (a halom a tehermentesítési arány alatt van) TÖBB egyidejű, hosszú `/v1/responses` kérést is befogadhat az `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` révén. Több tucat hosszú SSE-kliens (40–50) esetén a korlátot a halom + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 határozza meg, nem a „legfeljebb 2”. Ennek vakon, „a gazdagép kihasználása” érdekében történő növelése újra előidézi a #7849 problémát. A **halmokat** **N független `DATA_DIR`-rel** többszörözze (#11024); soha ne használjon `replicas>1` értéket egyetlen SQLite-fájlhoz. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatikusan származtatott)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** az automatikusan származtatott beolvasási bájtkeret felülbírálása (a szigorúbb V8-/cgroup-memóriakorlát 25%-a, osztva a 8-szoros átmeneti felerősítéssel). A származtatott és az explicit értékek egyaránt a 8 MiB–2 GiB tartományra vannak korlátozva. Az effektív keretnél nagyobb törzs azonnal `413 body_exceeds_budget` hibával meghiúsul; az egyenként kiszolgálható törzsek közötti versengés továbbra is újrapróbálható `503` választ eredményez. 40–50 egyidejű, hosszú SSE-kliens esetén a korlátot ez a keret + a halom határozza meg, nem egy szigorú „legfeljebb 2” korlát. A finomhangolás előtt olvassa ki a `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` értékeket az `/api/monitoring/health` végponton. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Halomnyomás miatti tehermentesítési arány (`heapUsed / heap_size_limit`) a BÁJT- és STRUKTÚRA-alapú, nagy erőforrás-igényű kérések befogadásához (#10183, #10268, #10437). Az `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` értéket meghaladó egyidejű, nagy erőforrás-igényű kérés csak akkor kerül elutasításra az újrapróbálható `503` válasszal, ha a halom SZINTÉN eléri vagy meghaladja ezt az arányt; egészséges halom esetén az egészséges többletkapacitáson keresztül kerül befogadásra. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (alapértelmezés szerint `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Korlátozott többletkapacitás az egészséges halom gyors útvonalához (#10437), a STRUKTÚRA- és a BÁJT-alapú befogadásnál **egyaránt** (`admitChatRequest`, beleértve az `OMNIROUTE_CHAT_LARGE_BODY_BYTES` értéket elérő vagy meghaladó törzseket). E korlát nélkül minden elfoglalt, de egészséges halommal rendelkező kérés felső határ nélkül megkerülné a befogadási szabályozást. Amint ennyi egyidejű bérlet aktív az egészséges halom útvonalán keresztül, a további elfoglalt kérések UGYANARRA a korlátozott várakozási/tehermentesítési útvonalra kerülnek, amelyet a rendszer tényleges halomnyomás esetén használ. A `0` teljesen letiltja a megkerülést. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Az az üzenetszám, amely felett a csevegési kérés akkor is nagy erőforrás-igényűnek minősül, ha a törzse a bájtküszöb alatt van. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Az az eszközszám, amely felett a csevegési kérés akkor is nagy erőforrás-igényűnek minősül, ha a törzse a bájtküszöb alatt van. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervatív, a karakterlánc méretén alapuló tokenbecslés, amely alapján egy kérés nagy erőforrás-igényűnek minősül; ez a befogadási költség közelítő értéke, nem a szolgáltató számlázásához használt tokenizálás. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (letiltva) | `src/shared/middleware/chatBodyAdmission.ts` | Opcionálisan engedélyezhető csevegésielőzmény-korlát. Alapértelmezés szerint le van tiltva: az üzenetszám telepítési házirend, nem pedig a kérések univerzális tulajdonsága, és az itt alkalmazott korlátozás végleges `413` válasszal utasítaná el a beszélgetéseket, mielőtt a tömörítési folyamat kiszolgálhatóvá tehetné őket. A halom növekedését az `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` és a halomnyomás miatti tehermentesítés korlátozza. Állítson be pozitív értéket azokon a memóriakorlátos telepítéseken, amelyek szigorú felső korlátot igényelnek; a túllépés ekkor strukturált, tömörítést előíró `413` választ eredményez. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Szigorú korlát a nem adatfolyamként továbbított upstream-válaszokra, amelyek teljes egészében a memóriában pufferelődnek. E határ felett az upstream-olvasó leáll, és a kérés gyorsan meghiúsul ahelyett, hogy korlátlanul növekvő karakterláncot hozna létre a halom kimerüléséig. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | A felsőbb szintű válaszfejlécekből továbbított vezetékes bájtok maximális száma. A keret túllépésekor az alacsonyabb prioritású fejlécek (pl. egyéni `x-codex-*`, `x-oai-request-id`) kimaradnak, hogy a válasz a gyakori fordítottproxy-fejléckorlátokon belül maradjon. Állítsa magasabbra, ha több felsőbb szintű metaadatot szeretne továbbítani a nagyobb válaszfejléc-méret árán. | -| `CORS_ORIGIN` | _(nincs beállítva)_ | `src/server/cors/origins.ts` | Korábbi, egyetlen eredetet tartalmazó CORS-engedélyezési lista. Új telepítéseknél részesítse előnyben a `CORS_ALLOWED_ORIGINS` használatát. A CORS csak a különböző eredetű böngészős API-kliensekhez szükséges; a hitelesített irányítópult írási műveletei ehelyett azonos eredetű kéréseket és munkamenethez kötött CSRF-védelmet használnak. | -| `CORS_ALLOWED_ORIGINS` | _(nincs beállítva)_ | `src/server/cors/origins.ts` | Vesszővel elválasztott CORS-engedélyezési lista. Helyettesítő karakter csak akkor kerül elküldésre, ha a `CORS_ALLOW_ALL=true` kifejezetten be van állítva. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Csak fejlesztési célú vészkijárat, amellyel bármely böngészős `Origin` visszaküldhető. Megosztott vagy éles telepítéseken ne engedélyezze. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokkolja a privát/visszacsatolási/link-local IP-tartományokat célzó szolgáltatói hívásokat. Csak elkülönített tesztkörnyezetekben tiltsa le. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Engedélyezi a privát/helyi hálózatokra (localhost, 192.168.x.x, 10.x.x.x stb.) mutató szolgáltatói URL-eket. **Saját üzemeltetésű szolgáltatókhoz KÖTELEZŐ** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Ha `false`, az irányítópult elutasítja a helyi URL-ek ellenőrzését. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Engedélyezi a helyi/privát címeken (127.0.0.1, localhost, LAN, privát tartományok) található szolgáltatók hozzáadását/ellenőrzését — csak a szolgáltató-ellenőrzési útvonalra korlátozva. **Alapértelmezés: `true`** (helyi használat előnyben); állítsa `false` értékre a kizárólag nyilvános címeket engedélyező szigorú blokkolás kikényszerítéséhez. A felhőmetaadat-végpontok (169.254.169.254, metadata.google.internal) ettől függetlenül blokkolva maradnak. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Lehetővé teszi, hogy a `/v1/audio/*` útvonalak (átiratok, beszéd, fordítások) a localhoston kívül üzemeltetett, OpenAI-kompatibilis szolgáltatói csomópontot használjanak. Alapértelmezés szerint ki van kapcsolva — a hanganyag távoli gazdagépre történő irányítása megváltoztatja a kimenő forgalom identitását, ezért ehhez kifejezett üzemeltetői döntés szükséges. A visszacsatolási/privát csomópontok (localhost, 127.0.0.1, 172.16-31.x) mindig engedélyezettek, és ez a beállítás nem érinti őket. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Lehetővé teszi, hogy a `POST /v1/rerank` (valamint a memóriamotor visszacsatolási újrarangsorolási lépése) a localhoston kívül üzemeltetett, OpenAI-kompatibilis szolgáltatói csomópontot használjon — például egy TEI, Infinity, vLLM stb. rendszert futtató LAN-gépet vagy Tailscale-társat. Alapértelmezés szerint ki van kapcsolva — a távoli gazdagépre történő irányítás megváltoztatja a kimenő forgalom identitását, ezért ehhez kifejezett üzemeltetői döntés szükséges. A visszacsatolási csomópontok (localhost, 127.0.0.1, 172.16-31.x) mindig engedélyezettek, és ez a beállítás nem érinti őket. A távoli csomópontoknak a szolgáltatói kimenő URL-ekre vonatkozó szabályzatnak (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) is meg kell felelniük; a felhőmetaadat-gazdagépekre soha nem irányít forgalmat a rendszer. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ha az OIDC engedélyezve van, letiltja a jelszavas bejelentkezést, így a felhasználók csak az OIDC egyszeri bejelentkezésen keresztül hitelesíthetik magukat. Az egyszerű `OIDC_DISABLE_PASSWORD_LOGIN` álnév szintén elfogadott; az azonos kulcsú irányítópulti funkciójelző elsőbbséget élvez. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Az `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` egyszerű álneve (#10889). | +| Változó | Alapértelmezett | Forrásfájl | Leírás | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | A gép ujjlenyomatának létrehozásához a hardverazonosítókkal kombinált só. Az elkülönítés érdekében telepítésenként módosítsa. | +| `OMNIROUTE_CLI_SALT` | _(nincs beállítva = telepítésenként véletlenszerű, a `/cli-token-salt.json` fájlban megőrzött só)_ | `src/lib/machineToken.ts` | A helyi CLI-hitelesítési token származtatásához használt HMAC-só. Ezen érték beállítása elforgatja a gépen található összes CLI-tokent, és mindig elsőbbséget élvez a megőrzött sóval szemben. Lásd: `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Beállítja a `Secure` jelzőt a munkamenet-cookie-kon. HTTPS mögötti futtatáskor **`true` értékűnek kell lennie**. | +| `REQUIRE_API_KEY` | `false` | API-köztesszoftver | Ha `true`, minden `/v1/*` proxykérésnek tartalmaznia kell egy érvényes API-kulcsot. **Ez a jelző nem szabályozza a `GET /v1/models` hozzáférését**, amely ehelyett az irányítópult bejelentkezési beállítását (`requireAuthForModels`) követi — ezért a `/v1/models` végpontról érkező `401` válasz NEM jelenti azt, hogy a következtetés védett. Lásd: `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Lehetővé teszi a teljes API-kulcsértékek felfedését az irányítópult felhasználói felületén. Az irányítópult funkciójelzői között konfigurálható; megosztott példányokon biztonsági kockázatot jelent. | +| `NO_LOG_API_KEY_IDS` | _(üres)_ | `src/lib/compliance/index.ts` | A kérésnaplózást megkerülő API-kulcs-azonosítók vesszővel elválasztott listája (GDPR-megfelelőség). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nincs beállítva = korlátlan)_ | `src/shared/utils/apiKeyPolicy.ts` | Tartalék napi kérési keret, amely azon API-kulcsokra vonatkozik, amelyek `rate_limits` oszlopa null értékű. Ha nincs beállítva vagy üres: nincs implicit korlát (#2289, #11017). A `0` ugyanezt jelenti (korlátlan). A pozitív egész N engedélyezi az N/nap, 5N/hét és 20N/hónap korlátokat. A hibás formátumú, nem üres értékek esetén a rendszer visszatér a korábbi 1000/nap, 5000/hét és 20000/hónap időablakokhoz. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | A kérés törzsének legnagyobb engedélyezett mérete. Az ezt a korlátot meghaladó adattartalmakat elutasítja. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Az e küszöbértéket elérő vagy meghaladó tényleges kéréstörzsek a JSON feldolgozása előtt megszerzik az atomi, folyamaton belüli, nagy terhelésű befogadási bérletet (BYTE útvonal, beleértve a `POST /v1/responses` kérést). Ugyanaz az [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) szerinti, elegendő szabad kapacitás esetén alkalmazott kivétel érvényes, mint a struktúraigényes kéréseknél; továbbra is korlátozza az `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), hogy az [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne térjen vissza. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | A csevegési útvonal szigorú korlátja, amelyet a korlátozott beolvasás során beolvasott bájtok alapján érvényesít, beleértve a hiányzó, érvénytelen vagy megtévesztő `Content-Length` értékkel rendelkező kéréseket is; a túllépés `413` választ eredményez. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nincs beállítva — nincs kérésszámkorlát)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ez a régi, kérések SZÁMÁRA vonatkozó korlát mostantól csak akkor lép életbe, ha explicit módon be van állítva. Ha nincs beállítva (ez az alapértelmezés), a nagy erőforrás-igényű csevegési kérések befogadását ehelyett az `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` szabályozza — egy automatikusan származtatott BÁJTkeret, amelynek méretezése a folyamat tényleges memóriakorlátja alapján történik **egyetlen folyamatban** (egy V8-halom). Két, egymást átfedő, ~750k tokenes `/v1/responses` kérés megszakít ~12 GiB-os halmokat (#7849) — ez **memóriakeretre** vonatkozó figyelmeztetés, nem pedig a termék 2-es szigorú felső korlátja. Egy egészséges folyamat (a halom a tehermentesítési arány alatt van) TÖBB egyidejű, hosszú `/v1/responses` kérést is befogadhat az `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` révén. Több tucat hosszú SSE-kliens (40–50) esetén a korlátot a halom + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 határozza meg, nem a „legfeljebb 2”. Ennek vakon, „a gazdagép kihasználása” érdekében történő növelése újra előidézi a #7849 problémát. A **halmokat** **N független `DATA_DIR`-rel** többszörözze (#11024); soha ne használjon `replicas>1` értéket egyetlen SQLite-fájlhoz. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatikusan származtatott)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** az automatikusan származtatott beolvasási bájtkeret felülbírálása (a szigorúbb V8-/cgroup-memóriakorlát 25%-a, osztva a 8-szoros átmeneti felerősítéssel). A származtatott és az explicit értékek egyaránt a 8 MiB–2 GiB tartományra vannak korlátozva. Az effektív keretnél nagyobb törzs azonnal `413 body_exceeds_budget` hibával meghiúsul; az egyenként kiszolgálható törzsek közötti versengés továbbra is újrapróbálható `503` választ eredményez. 40–50 egyidejű, hosszú SSE-kliens esetén a korlátot ez a keret + a halom határozza meg, nem egy szigorú „legfeljebb 2” korlát. A finomhangolás előtt olvassa ki a `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` értékeket az `/api/monitoring/health` végponton. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Halomnyomás miatti tehermentesítési arány (`heapUsed / heap_size_limit`) a BÁJT- és STRUKTÚRA-alapú, nagy erőforrás-igényű kérések befogadásához (#10183, #10268, #10437). Az `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` értéket meghaladó egyidejű, nagy erőforrás-igényű kérés csak akkor kerül elutasításra az újrapróbálható `503` válasszal, ha a halom SZINTÉN eléri vagy meghaladja ezt az arányt; egészséges halom esetén az egészséges többletkapacitáson keresztül kerül befogadásra. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (alapértelmezés szerint `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Korlátozott többletkapacitás az egészséges halom gyors útvonalához (#10437), a STRUKTÚRA- és a BÁJT-alapú befogadásnál **egyaránt** (`admitChatRequest`, beleértve az `OMNIROUTE_CHAT_LARGE_BODY_BYTES` értéket elérő vagy meghaladó törzseket). E korlát nélkül minden elfoglalt, de egészséges halommal rendelkező kérés felső határ nélkül megkerülné a befogadási szabályozást. Amint ennyi egyidejű bérlet aktív az egészséges halom útvonalán keresztül, a további elfoglalt kérések UGYANARRA a korlátozott várakozási/tehermentesítési útvonalra kerülnek, amelyet a rendszer tényleges halomnyomás esetén használ. A `0` teljesen letiltja a megkerülést. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Az az üzenetszám, amely felett a csevegési kérés akkor is nagy erőforrás-igényűnek minősül, ha a törzse a bájtküszöb alatt van. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Az az eszközszám, amely felett a csevegési kérés akkor is nagy erőforrás-igényűnek minősül, ha a törzse a bájtküszöb alatt van. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervatív, a karakterlánc méretén alapuló tokenbecslés, amely alapján egy kérés nagy erőforrás-igényűnek minősül; ez a befogadási költség közelítő értéke, nem a szolgáltató számlázásához használt tokenizálás. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (letiltva) | `src/shared/middleware/chatBodyAdmission.ts` | Opcionálisan engedélyezhető csevegésielőzmény-korlát. Alapértelmezés szerint le van tiltva: az üzenetszám telepítési házirend, nem pedig a kérések univerzális tulajdonsága, és az itt alkalmazott korlátozás végleges `413` válasszal utasítaná el a beszélgetéseket, mielőtt a tömörítési folyamat kiszolgálhatóvá tehetné őket. A halom növekedését az `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` és a halomnyomás miatti tehermentesítés korlátozza. Állítson be pozitív értéket azokon a memóriakorlátos telepítéseken, amelyek szigorú felső korlátot igényelnek; a túllépés ekkor strukturált, tömörítést előíró `413` választ eredményez. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Szigorú korlát a nem adatfolyamként továbbított upstream-válaszokra, amelyek teljes egészében a memóriában pufferelődnek. E határ felett az upstream-olvasó leáll, és a kérés gyorsan meghiúsul ahelyett, hogy korlátlanul növekvő karakterláncot hozna létre a halom kimerüléséig. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | A felsőbb szintű válaszfejlécekből továbbított vezetékes bájtok maximális száma. A keret túllépésekor az alacsonyabb prioritású fejlécek (pl. egyéni `x-codex-*`, `x-oai-request-id`) kimaradnak, hogy a válasz a gyakori fordítottproxy-fejléckorlátokon belül maradjon. Állítsa magasabbra, ha több felsőbb szintű metaadatot szeretne továbbítani a nagyobb válaszfejléc-méret árán. | +| `CORS_ORIGIN` | _(nincs beállítva)_ | `src/server/cors/origins.ts` | Korábbi, egyetlen eredetet tartalmazó CORS-engedélyezési lista. Új telepítéseknél részesítse előnyben a `CORS_ALLOWED_ORIGINS` használatát. A CORS csak a különböző eredetű böngészős API-kliensekhez szükséges; a hitelesített irányítópult írási műveletei ehelyett azonos eredetű kéréseket és munkamenethez kötött CSRF-védelmet használnak. | +| `CORS_ALLOWED_ORIGINS` | _(nincs beállítva)_ | `src/server/cors/origins.ts` | Vesszővel elválasztott CORS-engedélyezési lista. Helyettesítő karakter csak akkor kerül elküldésre, ha a `CORS_ALLOW_ALL=true` kifejezetten be van állítva. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Csak fejlesztési célú vészkijárat, amellyel bármely böngészős `Origin` visszaküldhető. Megosztott vagy éles telepítéseken ne engedélyezze. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Örökölt álnév. A hozzá tartozó irányítópult-kapcsolót (adatbázis-felülírást) a rendszer e környezeti változó értéke előtt olvassa be; ha valamelyikben `false`, `0`, `no` vagy `off` szerepel, az ugyanúgy kikapcsolja a gazdagép-ellenőrzéseket, mint az `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` — még akkor is, ha annak a jelzőnek az irányítópult-kapcsolója kikapcsolva van mentve; minden más érték a védelem döntését a lenti két jelzőre hagyja. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Kikapcsolja a kimenő URL-védelem gazdagép-ellenőrzéseit — a felhőmetaadat-blokkolást is beleértve (a 169.254.0.0/16 egész tartománya, metadata.google.internal) — azokon a szolgáltatói útvonalakon, amelyek a védelmi módot követik: URL-ellenőrzés, modellfelderítés, szolgáltatói csomópontok alap-URL-jei (a távoli újrarangsorolási csomópontokkal együtt) és a proxy-fallback teszt, továbbá más, szolgáltatóközeli kimenő útvonalakon, például távoli kép-/médialekéréskor. Lehetővé teszi a privát webhookcélokat is, a felhőmetaadat/link-local végpontok kivételével, amelyek e jelző bekapcsolása mellett is blokkolva maradnak. A `BaseExecutor.execute()` útvonalán továbbított csevegési kérések megtartják felhőmetaadat-ellenőrzésüket. A helyi szolgáltatók az alapértelmezésekkel működnek: az `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (alapértelmezés: `true`) már engedélyezi a helyi és LAN URL-eket az ellenőrzési, felderítési és szolgáltatói csomóponti útvonalakon; a proxy-fallback teszt és a privát webhookcélok csak ezt a jelzőt nézik, és kikapcsolt állapotában blokkolják a helyi/LAN gazdagépeket. Felülírja az `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` értéket. Az irányítópult-kapcsolóból mentett érték elsőbbséget élvez e változóval szemben. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Engedélyezi a helyi/privát címeken (127.0.0.1, localhost, LAN, privát tartományok) található szolgáltatói URL-eket a szolgáltató-ellenőrzéshez, a modellfelderítéshez, a csevegéshez és a védelmi mód által szabályozott egyéb szolgáltatóközeli kimenő útvonalakhoz. **Alapértelmezés: `true`** (helyi használat előnyben): ekkor a védelem blokkolja a felhőmetaadat-végpontokat — a 169.254.0.0/16 egész tartományát és az ismert metaadat-gazdagépneveket (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Állítsa `false` értékre a kizárólag nyilvános módhoz, amely a privát és a visszacsatolási gazdagépeket is blokkolja. A védelem a gazdagépnevet vagy IP-literált írás szerint, minden DNS-feloldás előtt ellenőrzi; kizárólag nyilvános módban a safe-fetch csomagoló emellett feloldja a gazdagépnevet, és minden privát választ elutasít. Az `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` a saját sorában felsorolt útvonalakon kapcsolja ki ezeket az ellenőrzéseket. A beépített helyi szolgáltatók — például az LM Studio, az Ollama, a vLLM, a Llamafile és a Triton — kihagyják az összes gazdagép-ellenőrzést a kulcsellenőrzésnél és a csevegésnél. Az irányítópult-kapcsolóból mentett érték elsőbbséget élvez e változóval szemben. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Lehetővé teszi, hogy a `/v1/audio/*` útvonalak (átiratok, beszéd, fordítások) a localhoston kívül üzemeltetett, OpenAI-kompatibilis szolgáltatói csomópontot használjanak. Alapértelmezés szerint ki van kapcsolva — a hanganyag távoli gazdagépre történő irányítása megváltoztatja a kimenő forgalom identitását, ezért ehhez kifejezett üzemeltetői döntés szükséges. A visszacsatolási/privát csomópontok (localhost, 127.0.0.1, 172.16-31.x) mindig engedélyezettek, és ez a beállítás nem érinti őket. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Lehetővé teszi, hogy a `POST /v1/rerank` (valamint a memóriamotor visszacsatolási újrarangsorolási lépése) a localhoston kívül üzemeltetett, OpenAI-kompatibilis szolgáltatói csomópontot használjon — például egy TEI, Infinity, vLLM stb. rendszert futtató LAN-gépet vagy Tailscale-társat. Alapértelmezés szerint ki van kapcsolva — a távoli gazdagépre történő irányítás megváltoztatja a kimenő forgalom identitását, ezért ehhez kifejezett üzemeltetői döntés szükséges. A visszacsatolási csomópontok (localhost, 127.0.0.1, 172.16-31.x) mindig engedélyezettek, és ez a beállítás nem érinti őket. A távoli csomópont alap-URL-jének szintén meg kell felelnie a szolgáltatói kimenő URL-szabályzatnak (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ha az OIDC engedélyezve van, letiltja a jelszavas bejelentkezést, így a felhasználók csak az OIDC egyszeri bejelentkezésen keresztül hitelesíthetik magukat. Az egyszerű `OIDC_DISABLE_PASSWORD_LOGIN` álnév szintén elfogadott; az azonos kulcsú irányítópulti funkciójelző elsőbbséget élvez. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Az `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` egyszerű álneve (#10889). | ### Biztonsági megerősítési ellenőrzőlista diff --git a/docs/i18n/hy/CONTRIBUTING.md b/docs/i18n/hy/CONTRIBUTING.md index fe1b19028c46..92edaa343144 100644 --- a/docs/i18n/hy/CONTRIBUTING.md +++ b/docs/i18n/hy/CONTRIBUTING.md @@ -397,22 +397,22 @@ docs/ --- -## Pull Request-ի ստուգացանկ +## Pull Request-ի ստուգաթերթ -- [ ] Թեստերը հաջողությամբ անցնում են (`npm test`) -- [ ] Լինթինգն հաջողությամբ անցնում է (`npm run lint`) +- [ ] Թեստերն անցնում են (`npm test`) +- [ ] Լինտինգն անցնում է (`npm run lint`) - [ ] Կառուցումը հաջողվում է (`npm run build`) - [ ] Նոր հանրային ֆունկցիաների և ինտերֆեյսների համար ավելացվել են TypeScript տիպեր - [ ] Չկան կոդում ուղղակիորեն գրված գաղտնիքներ կամ պահուստային արժեքներ -- [ ] Վերին հոսքի հանրային հավատարմագրերը ներդրված են `resolvePublicCred()`-ի միջոցով (տե՛ս [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), և երբեք՝ որպես լիտերալներ -- [ ] Սխալի պատասխաններն անցնում են `buildErrorBody()` / `sanitizeErrorMessage()` ֆունկցիաներով՝ պատասխանների մարմիններում առանց չմշակված սթեքի հետագծերի (տե՛ս [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell հրամանները (`exec` / `spawn`) կատարման ժամանակի արժեքները փոխանցում են `env`-ի միջոցով, այլ ոչ թե տողային ինտերպոլացիայով -- [ ] Բոլոր մուտքային տվյալները վավերացվում են Zod սխեմաներով -- [ ] Օգտատիրոջը տեսանելի փոփոխությունների համար փոփոխությունների մատյանի **ֆրագմենտ** է ավելացվել `changelog.d/{features|fixes|maintenance}/-.md`-ում (տե՛ս [`changelog.d/README.md`](./changelog.d/README.md)) — **մի՛** խմբագրեք `CHANGELOG.md`-ն ուղղակիորեն․ ֆրագմենտները միավորվում են թողարկման ժամանակ և երբեք հակասություններ չեն առաջացնում PR-ների միջև +- [ ] Հանրային վերին հոսքի հավատարմագրերը ներդրված են `resolvePublicCred()`-ի միջոցով (տե՛ս [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), երբեք՝ որպես լիտերալներ +- [ ] Սխալների պատասխաններն անցնում են `buildErrorBody()` / `sanitizeErrorMessage()`-ի միջոցով. պատասխանների մարմիններում չկան չմշակված stack trace-եր (տե՛ս [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell հրամանները (`exec` / `spawn`) կատարման ժամանակի արժեքները փոխանցում են `env`-ի միջոցով, ոչ թե տողային ինտերպոլյացիայով +- [ ] Բոլոր մուտքային տվյալները վավերացված են Zod սխեմաներով +- [ ] Օգտատերերին տեսանելի փոփոխությունների համար `changelog.d/{features|fixes|maintenance}/-.md`-ում ավելացվել է փոփոխությունների մատյանի **հատված** (տե՛ս [`changelog.d/README.md`](./changelog.d/README.md)). մի՛ խմբագրեք `CHANGELOG.md`-ն անմիջապես. թողարկման ժամանակ հատվածները համախմբվում են և երբեք չեն հակասում տարբեր PR-ների միջև - [ ] Փաստաթղթերը թարմացվել են (եթե կիրառելի է) -- [ ] Չեն բացվել CodeQL / Secret-Scanning-ի նոր ահազանգեր, կամ յուրաքանչյուրն անտեսվել է տեխնիկական հիմնավորմամբ՝ համապատասխան `docs/security/` փաստաթղթին հղումով -- [ ] Դուստր գործընթացներ գործարկող երթուղիները (`/api/mcp/`, `/api/cli-tools/runtime/`) դասակարգված են որպես `isLocalOnlyPath()`՝ `src/server/authz/routeGuard.ts`-ում — տե՛ս [Խիստ կանոն #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Commit-ի հաղորդագրություններում չկան `Co-Authored-By` վերջնագրեր․ commit-ները պետք է ներկայացվեն բացառապես պահոցի սեփականատիրոջ Git ինքնության ներքո (Խիստ կանոն #16) +- [ ] CodeQL-ի / Secret-Scanning-ի նոր զգուշացումներ չեն բացվել, կամ յուրաքանչյուրն անտեսվել է՝ համապատասխան `docs/security/` փաստաթղթին հղում կատարող տեխնիկական հիմնավորմամբ +- [ ] Դուստր պրոցեսներ գործարկող երթուղիները (`/api/mcp/`, `/api/cli-tools/runtime/`) դասակարգված են որպես `isLocalOnlyPath()`՝ `src/server/authz/routeGuard.ts`-ում. տե՛ս [Խիստ կանոն #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Commit-ի հաղորդագրություններում չկան AI-ի/բոտի `Co-authored-by` վերջնատողեր (Խիստ կանոն #16). մարդկային համահեղինակները, որոնց աշխատանքը վերաօգտագործվել է, նշվում են ստանդարտ `Co-authored-by: Name ` վերջնատողերով --- diff --git a/docs/i18n/hy/docs/architecture/QUALITY_GATES.md b/docs/i18n/hy/docs/architecture/QUALITY_GATES.md index b47fd3eaf54f..fe63a8b1245c 100644 --- a/docs/i18n/hy/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/hy/docs/architecture/QUALITY_GATES.md @@ -15,214 +15,214 @@ --- -## Գեյթերի ցանկը և կատարման պրոֆիլները +## Դարպասների գույքացանկը և կատարման պրոֆիլները ### Թեկնածուի ընդունում CI և Quality Gates աշխատանքային հոսքերից յուրաքանչյուրն արձակում է կայուն վճիռ՝ `Gate / CI` և -`Gate / Quality`։ Դրանց տարբերակավորված ընդունման քաղաքականությունը թվարկում է վերին հոսքի յուրաքանչյուր աշխատանք՝ -որպես պարտադիր կամ խորհրդատվական։ Կիրառելի պարտադիր աշխատանքը պետք է հաջողվի․ բացակայող, +`Gate / Quality`։ Դրանց տարբերակավորված ընդունման քաղաքականությունը թվարկում է վերին հոսքի յուրաքանչյուր առաջադրանք՝ +որպես պարտադիր կամ խորհրդատվական։ Կիրառելի պարտադիր առաջադրանքը պետք է հաջողվի․ բացակայող, չեղարկված, բաց թողնված, սպասման մեջ գտնվող և անհայտ արդյունքները չեն կարող հաստատել PASS կարգավիճակը։ Վավեր -միայն փաստաթղթային կամ միայն կատալոգային դասակարգումը կարող է կոդի ուղին դարձնել անկիրառելի, -իսկ սևագիր PR-ը չի համարվում ընդունված թեկնածու։ `hotfix` պիտակը չի վերացնում ապացույցի պահանջը։ +միայն փաստաթղթերին կամ միայն կատալոգին վերաբերող դասակարգումը կարող է կոդի ուղին դարձնել ոչ կիրառելի․ +սևագիր PR-ը չի համարվում ընդունված թեկնածու։ `hotfix` պիտակը չի վերացնում ապացույցների պահանջը։ -Երկու աշխատանքային հոսքերն էլ ընդգրկում են PR-ները և main/release ճյուղեր կատարվող push-երը, ձեռքով գործարկումն ու +Երկու աշխատանքային հոսքերն էլ ընդգրկում են PR-ները և main/release ճյուղեր կատարվող push-երը, ձեռքով գործարկումը և merge-group իրադարձությունները։ Push-ը, dispatch-ը և merge-group-ը գործարկում են ամբողջական ընտրանին։ Fork-երը -և merge group-երը hosted runner-ներ են օգտագործում այն աշխատանքների համար, որոնք այլապես ընտրում են self-hosted -runner-ներ․ գործարկումից առաջ պետք է հաստատվի hosted միջավայրի բավարար հզորությունը։ +և merge group-երն օգտագործում են սպասարկվող runner-ներ այն առաջադրանքների համար, որոնք այլապես ընտրում են ինքնուրույն սպասարկվող +runner-ներ․ նախքան ներդրումը պետք է ստուգվի սպասարկվող runner-ների բավարար հզորությունը։ -Յուրաքանչյուր JSON ստացական նույնականացնում է checkout արված SHA-ն, աշխատանքային հոսքի գործարկումն ու փորձը։ -CLI-ն մերժում է checkout/event SHA-ների անհամապատասխանությունը։ Աշխատանքային հոսքի թեստերը քաղաքականության անդամակցությունը -կապում են վճիռ արձակող աշխատանքի `needs` ցանկին, որպեսզի նոր կամ հեռացված ուղին չկարողանա աննկատ անհետանալ։ -Ստացականներն ընդգրկում են իրենց իսկ աշխատանքային հոսքը, այլ ոչ թե հրապարակումը, տեղակայումը կամ -գոյություն ունեցող խորհրդատվական սկանավորիչի ներքին գործառույթները։ Ճյուղերի կանոններում երկու ստուգման անուններն էլ ակտիվացնելը -առանձին վարչական փոփոխություն է․ այս աշխատանքների ավելացումն ինքնին չի պաշտպանում ճյուղը։ +Յուրաքանչյուր JSON անդորրագիր նույնականացնում է checkout արված SHA-ն, աշխատանքային հոսքի գործարկումը և փորձը։ +CLI-ն մերժում է checkout/event SHA-ների անհամապատասխանությունը։ Աշխատանքային հոսքի թեստերը քաղաքականության անդամակցությունը կապում են +վճռի առաջադրանքի `needs` ցանկին, որպեսզի նոր կամ հեռացված ուղին չկարողանա աննկատ անհետանալ։ +Անդորրագրերն ընդգրկում են իրենց սեփական աշխատանքային հոսքը, այլ ոչ հրապարակումը, տեղակայումը կամ +գոյություն ունեցող խորհրդատվական սկաների ներքին աշխատանքը։ Ճյուղի կանոններում ստուգումների երկու անունների ակտիվացումը +առանձին վարչական փոփոխություն է․ այս առաջադրանքների ավելացումն ինքնին չի պաշտպանում ճյուղը։ -### Ստատիկ սկանավորման ցանկը +### Ստատիկ սկանավորման գույքացանկ -Տարբերակավորված npm alias-ների ցանկը և ստատիկ սկանավորման անդամակցությունը գտնվում են +Տարբերակավորված npm-alias գույքացանկը և ստատիկ սկանավորման անդամակցությունը գտնվում են `config/quality/gate-manifest.json`-ում։ Գործարկեք `npm run check:gate-manifest`՝ -սկրիպտների անուններն ու ճշգրիտ հրամանները `package.json`-ի համեմատ վավերացնելու համար․ ավելացումները, հեռացումները և -հրամանների շեղումները ձախողում են ինչպես տեղային hook-ը, այնպես էլ CI-ի փոփոխությունների դասակարգման աշխատանքները։ -Alias-ը աշխատանքային հոսքի աշխատանք, matrix-ի օրինակ կամ թեստային դեպք չէ․ այս քանակները -չպետք է ներկայացվեն որպես փոխարինելի։ - -Ընտրված alias-ներն առանց դրանք կատարելու դիտարկելու համար օգտագործեք `npm run quality:scan -- --list` կամ `npm run quality:scan:fast -- --list`։ -Runner-ը կանչում է -npm entrypoint-ը, ուստի դրա կատարման միջավայրը՝ ներառյալ Bun-ը, որտեղ այն կազմաձևված է, պահպանվում է։ +սկրիպտների անունները և ճշգրիտ հրամանները `package.json`-ի համեմատ վավերացնելու համար․ ավելացումները, հեռացումները և +հրամանների շեղումները ձախողում են և՛ տեղային hook-ը, և՛ CI-ում փոփոխությունների դասակարգման առաջադրանքները։ +Alias-ը աշխատանքային հոսքի առաջադրանք, matrix-ի օրինակ կամ թեստային դեպք չէ․ այս քանակները չպետք է +ներկայացվեն որպես փոխարինելի։ + +Օգտագործեք `npm run quality:scan -- --list` կամ `npm run quality:scan:fast -- --list` +ընտրված alias-ները առանց դրանք գործարկելու ստուգելու համար։ Runner-ը կանչում է +npm մուտքի կետը, ուստի դրա կատարման միջավայրը (ներառյալ Bun-ը, որտեղ այն կազմաձևված է) պահպանվում է։ Manifest-ը գրանցում է այդ պրոֆիլներից դուրս գտնվող alias-ները որպես առանձին կանչվող, իսկ -սպասարկման հրամաններն արգելված են միայն կարդալու համար նախատեսված սկանավորման պրոֆիլներում։ +սպասարկման հրամաններն արգելված են միայն ընթերցման համար նախատեսված սկանավորման պրոֆիլներում։ -Այս պրոֆիլներն ընդգրկում են միայն ստատիկ սկանավորումը։ Դրանք չեն հավաստագրում արտադրանքի թեստերը, +Այս պրոֆիլներն ընդգրկում են միայն ստատիկ սկանավորումը։ Դրանք չեն հավաստում արտադրանքի թեստերը, ծածկույթը, փաթեթավորումը, արտաքին ստուգումները կամ թեկնածուի թողարկման ամբողջական ընդունումը։ Աշխատանքային հոսքի ընդունումն օգտագործում է կապակցված `config/quality/admission-policy.json`-ը և `scripts/quality/admission-verdict.mjs`-ը։ Release-observer պրոֆիլները մնում են առանձին․ -դրանց կիրառելի ստուգումներն ու ստացականները դիտարկեք անկախ կերպով։ Ստորև բերված տեքստային -ցանկը տեղեկատու է, այլ ոչ թե ապացույց, որ գեյթն իրականում գործարկվել է։ +դրանց կիրառելի ստուգումներն ու անդորրագրերը ստուգեք անկախորեն։ Ստորև բերված տեքստային +գույքացանկը տեղեկանք է, այլ ոչ ապացույց, որ դարպասն իրականում գործարկվել է։ -Սկրիպտները գտնվում են `scripts/check/`-ում (քաղաքականության գեյթեր) և `scripts/quality/`-ում (ratchet շարժիչ)։ +Սկրիպտները գտնվում են `scripts/check/`-ի (քաղաքականության դարպասներ) և `scripts/quality/`-ի (ratchet շարժիչ) ներքո։ CI-ի ճշմարտության աղբյուրը `.github/workflows/ci.yml`-ն է։ -### Release PR-ի արագ ուղի (`quality.yml`) +### Թողարկման PR-ի արագ ուղի (`quality.yml`) -`.github/workflows/quality.yml`-ը լրացնում է CI-ն main/release PR-ների, պաշտպանված ճյուղեր կատարվող -push-երի, dispatch-ի և merge group-երի համար։ PR-ներն օգտագործում են ըստ ուղիների զտված արագ ստուգումներ։ Մշտապես +`.github/workflows/quality.yml`-ը լրացնում է CI-ն main/release PR-ների, պաշտպանված ճյուղերի +push-երի, dispatch-ի և merge group-երի համար։ PR-ներն օգտագործում են ուղիներով զտված արագ ստուգումներ։ Մշտապես անջատված կրկնօրինակ build-ը հեռացվել է․ իրական build/package/boot ստուգումները մնում են CI-ում։ -| Աշխատանք | Շրջանակ | Արգելափակող | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `Docs Gates (fast-path)` | Փաստաթղթային/կոդային PR-ներ․ API փաստաթղթերի հղումներ և docs-all | Այո | -| `Fast Quality Gates` | Կոդային PR-ներ․ ստատիկ ստուգումներ, typecheck, dashboard-ի typecheck, ազդեցության ենթարկված միավորային թեստեր | Այո | -| `Forgotten sibling tests` | Կոդային PR-ներ․ փոփոխված մոդուլներից հետագծում դեպի ստատիկ սպառողներ և թեկնածու հարակից թեստեր․ barrel և dynamic-import ուղիները հաղորդվում են որպես խորհրդատվական ախտորոշումներ՝ հղված allowlist բացառություններով | **Խորհրդատվական** | -| `Vitest (fast-path)` | Կոդային PR-ներ․ արագ vitest հավաքակազմ | Այո | -| `Unit Tests fast-path` | Կոդային PR-ներ․ 4-shard միավորային հավաքակազմ | Այո | -| `No new ESLint warnings` | Կոդային PR-ներ․ suppressions-ին իրազեկ lint պաշտպանիչ | Այո, ներառյալ fork-երը | -| `Merge integrity (changelog + generated skills)` | Ոչ սևագիր PR-ներ․ changelog-ի և գեներացված skill-երի համաժամացում | Այո, ներառյալ fork-երը | +| Առաջադրանք | Շրջանակ | Արգելափակող | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `Docs Gates (fast-path)` | Փաստաթղթերի/կոդի PR-ներ․ API փաստաթղթերի հղումներ և docs-all | Այո | +| `Fast Quality Gates` | Կոդի PR-ներ․ ստատիկ ստուգումներ, typecheck, dashboard-ի typecheck, ազդեցություն կրած միավորային թեստեր | Այո | +| `Forgotten sibling tests` | Կոդի PR-ներ․ փոփոխված մոդուլներից հետևում դեպի ստատիկ սպառողներ և հավանական sibling թեստեր․ barrel և dynamic-import ուղիները հաղորդվում են որպես խորհրդատվական ախտորոշումներ՝ նշված allowlist բացառություններով | **Խորհրդատվական** | +| `Vitest (fast-path)` | Կոդի PR-ներ․ արագ vitest հավաքակազմ | Այո | +| `Unit Tests fast-path` | Կոդի PR-ներ․ 4-shard միավորային հավաքակազմ | Այո | +| `No new ESLint warnings` | Կոդի PR-ներ․ suppression-ները հաշվի առնող lint պահակ | Այո, ներառյալ fork-երը | +| `Merge integrity (changelog + generated skills)` | Ոչ սևագիր PR-ներ․ changelog-ի և գեներացված skill-երի համաժամացում | Այո, ներառյալ fork-երը | -#### Մոռացված հարակից թեստերի հաշվետվություն +#### Մոռացված sibling թեստերի հաշվետվություն -`npm run check:forgotten-sibling-tests`-ը կրկին օգտագործում է թեստերի ազդեցության քարտեզի հիմքում ընկած import resolver-ը։ +`npm run check:forgotten-sibling-tests`-ը կրկին օգտագործում է թեստերի ազդեցության քարտեզի հիմքում գտնվող import resolver-ը։ Յուրաքանչյուր փոփոխված production մոդուլի համար այն հաղորդում է որոշակի -`փոփոխված մոդուլ/խորհրդանիշ -> ստատիկ սպառող -> թեկնածու հարակից թեստ` շղթաներ, երբ թեկնածու +`changed module/symbol -> static consumer -> candidate sibling test` շղթաներ, երբ թեկնածու թեստը բացակայում է pull-request-ի diff-ից։ Markdown ամփոփագիրը և JSON արդյունքը պահպանվում են որպես -`forgotten-sibling-tests` աշխատանքային հոսքի artifact՝ նախքան որևէ արգելափակող ներդրում calibration կատարելու համար։ +`forgotten-sibling-tests` աշխատանքային հոսքի artifact՝ նախքան որևէ արգելափակող ներդրում չափաբերման համար։ -Barrel վերաարտահանումները և դինամիկ ներմուծումները միայն լուծման ախտորոշումներ են․ դրանք երբեք արգելափակող խնդիր չեն ստեղծում։ Վերանայված բացառությունները գտնվում են +Barrel վերաարտահանումները և դինամիկ ներմուծումները միայն լուծման ախտորոշման համար են․ դրանք երբեք արգելափակող խախտում չեն ստեղծում։ +Վերանայված բացառությունները գտնվում են `config/quality/forgotten-sibling-allowlist.json`-ում։ Յուրաքանչյուր գրառում պետք է նշի սպառող և թեկնածու -թեստերը, ներկայացնի հստակ հիմնավորում և հղում պարունակի դեպի GitHub-ի խնդիր կամ pull request։ Սխալ ձևաչափված գրառումները -մերժվում են լռելյայն։ Բացառությունները չեն կարող անտեսել ջնջված թեկնածու թեստը կամ `.skip`/`.todo` ավելացնող տարբերությունը․ -assertion-ների թուլացումը և քողարկման այլ ձևերը շարունակում են վերահսկվել առանձին արգելափակող -`check:test-masking` դարպասի կողմից։ +թեստերը, ներկայացնի կոնկրետ հիմնավորում և հղում պարունակի GitHub issue-ի կամ pull request-ի։ Սխալ ձևաչափված գրառումների դեպքում ստուգումը +փակ սկզբունքով ձախողվում է։ Բացառությունները չեն կարող անտեսել ջնջված թեկնածու թեստը կամ `.skip`/`.todo` ավելացնող diff-ը․ +assertion-ի թուլացումը և քողարկման այլ ձևերը շարունակում են վերահսկվել անկախորեն արգելափակող +`check:test-masking` դարպասով։ ### Առաջադրանք՝ `lint` -Գործարկվում է դեպի `main` ուղղված յուրաքանչյուր PR-ի դեպքում։ Ձախողման դեպքում արգելափակում է միաձուլումը։ - -| Սկրիպտ (`npm run ...`) | Ստուգում է | Արգելափակող | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| `check:node-runtime` | Node.js-ի տարբերակը գտնվում է աջակցվող միջակայքում | Այո | -| `check:cycles` | Ցիկլային ներմուծումներ՝ բոլոր `src/` + `open-sse/` մոդուլներում | Այո | -| `check:route-validation:t06` | Zod սխեմաների առկայությունը բոլոր route-երում (6-րդ մակարդակի քաղաքականություն) | Այո | -| `check:any-budget:t11` | `@ts-expect-error // any`-ի քանակը չի գերազանցում սահմանաչափը (11-րդ մակարդակի catraca) | Այո | -| `check:provider-consistency` | `providers.ts`-ի յուրաքանչյուր մատակարար ունի համապատասխան գրառում `providerRegistry.ts`-ում (և հակառակը՝ թույլատրելի ցանկի սահմաններում) | Այո | -| `check:model-lifecycle` | Ձեռքով սպասարկվող երթուղավորման երեք աղյուսակները համահունչ են պահվում պահոցում գրանցված կենսացիկլի ակնթարթային պատկերի հետ (#11503). `FITNESS_TABLE`-ը (`taskFitness.ts`) չի գնահատում որևէ շահագործումից հանված id, որը `REGISTRY`-ն կարող է երթուղավորել, `BUILT_IN_ALIASES`-ի յուրաքանչյուր թիրախ առկա է `REGISTRY`-ում և բացակայում է շահագործումից հանված id-ների ակնթարթային պատկերից, `REGISTRY`-ում դեռևս առկա յուրաքանչյուր շահագործումից հանված id վերահղվում է կամ նշված է `allowedRetiredInCatalog`-ում, և `DEFAULT_DEGRADATION_MAP`-ի ոչ մի աղբյուր կամ թիրախ այդ ակնթարթային պատկերում նշված չէ որպես շահագործումից հանված։ Սա չի ապացուցում, որ մոդելը ներկայումս սպասարկվում է գործող վերին մակարդակի ծառայության կողմից։ Անցանց է՝ համեմատում է `config/quality/model-lifecycle.json`-ի հետ, որը ձեռքով թարմացվում է `npm run quality:refresh-model-lifecycle` հրամանով (պահանջում է ցանց, ներառված չէ CI-ում)։ `allowedRetiredInCatalog`-ը աստիճանական կրճատման արգելանիվ է. գրառում ավելացրեք միայն հետևման խնդրի առկայության դեպքում։ | Այո | -| `check:fetch-targets` | Հաճախորդի կողմի `src/`-ում յուրաքանչյուր `fetch("/api/...")` հանգուցալուծվում է իրական `route.ts`-ի | Այո | -| `check:deps` | Պահոցի յուրաքանչյուր `package.json`-ում առկա՝ `npm install`-ով տեղադրվող բոլոր կախվածությունները ներառված են `dependency-allowlist.json`-ում. նոր՝ չամրագրված տարբերակով կամ slopsquatting-ի ենթադրյալ փաթեթները նշագրվում են | Այո | -| `audit:deps` | `npm audit` (արմատային + electron) — բարձր/կրիտիկական մակարդակի ոչ մի զգուշացում (մասամբ համընկնում է osv-ի `check:vuln-ratchet`-ի հետ. տե՛ս Հիմնավորման կուտակված աշխատանքները) | Այո | -| `check:lockfile` | `package-lock.json`-ի ամբողջականություն՝ https ռեեստր, ամբողջականության հեշեր, հոսթի ոչ մի վերասահմանում | Այո | -| `check:licenses` | Արտադրական կախվածությունների SPDX լիցենզիաների թույլատրելի ցանկ | Այո | -| `check:tracked-artifacts` | Կառուցման արտեֆակտների / commit արված `node_modules` սիմվոլիկ հղումների բացակայություն (գործարկվում է նաև husky pre-commit-ում․ pre-push-ը միտումնավոր թեթև է — #6716) | Այո | -| `check:ai-attribution` | PR-ի commit-ներում, վերնագրում կամ բովանդակությունում AI/բոտի `Co-Authored-By` վերջնագրի կամ AI-ի միջոցով գեներացման նշման բացակայություն — Խիստ կանոն #16 (`quality.yml`-ի արագ ստուգումների ցիկլում՝ PR→`release/**`-ի համար — կարդում է իրադարձության payload-ը, PR-ներից դուրս ոչինչ չի անում — և `ci.yml`-ի lint-ում՝ միայն PR→`main`-ի համար նախատեսված քայլում․ նաև husky-ի `commit-msg` hook-ում․ մարդկանց՝ որպես համահեղինակներ նշելը թույլատրվում է․ #14436) | -| `check:vitest-exclusions` | Vitest-ի յուրաքանչյուր բացառություն նշում է հետևման issue և առկա է `config/quality/vitest-exclusions.json`-ում (#13204) | Այո | -| `check:file-size` | Ոչ մի սկզբնաղբյուրային ֆայլ չի գերազանցում իր ընդլայնման համար սահմանված առավելագույն չափը (ratchet՝ մեծ ֆայլերը ամրագրված են `frozen` ցանկում) | Այո | -| `check:error-helper` | Կատարիչներում/մշակիչներում սխալի պատասխաններն օգտագործում են `buildErrorBody()` / `sanitizeErrorMessage()` (Խիստ կանոն #12) | Այո | -| `check:migration-numbering` | Միգրացիոն SQL ֆայլերը համարակալված են հաջորդականորեն՝ առանց բացթողումների կամ կրկնությունների | Այո | -| `check:public-creds` | `publicCreds.ts`-ից դուրս չկան OAuth-ի բառացի `client_id`/`client_secret` կամ Firebase Web բանալիներ (Խիստ կանոն #11) | Այո | -| `check:db-rules` | `src/lib/db/` մոդուլներից դուրս չկան անմշակ SQL հարցումներ, իսկ `localDb.ts`-ից չկան միջնորդավորված ներմուծումներ (Խիստ կանոններ #2/#5) | Այո | -| `check:known-symbols` | Մատակարարների կատարիչները, երթուղավորման ռազմավարությունները և թարգմանիչները, որոնք գրանցված են իրենց դիսպետչերական աղյուսակներում, համապատասխանում են սկավառակի ֆայլերին՝ առանց չօգտագործվող կամ չհայտարարված սիմվոլների | Այո | -| `check:route-guard-membership` | Յուրաքանչյուր երթուղի, որը ստեղծում է ենթապրոցես, դասակարգվում է `isLocalOnlyPath()`-ի միջոցով (Խիստ կանոններ #15/#17) | Այո | -| `check:test-discovery` | Շտեմարանի յուրաքանչյուր `*.test.ts` / `*.spec.ts` ֆայլ հավաքվում է առնվազն մեկ թեստային գործարկիչի կողմից (արգելակ՝ `test-discovery-baseline.json`-ում որբ ֆայլերի ցանկը կարող է միայն կրճատվել) | Այո | -| `check:agent-skills-sync` | Գեներացված agent-skills արտեֆակտները համապատասխանում են իրենց սկզբնաղբյուր կատալոգին (շեղում չկա) | -| `check:provider-asset-provenance` | Պրովայդերների լոգոները/ռեսուրսներն ունեն գրանցված ծագման գրառում | -| `lint:json` | JSON կազմաձևման ֆայլերը վերլուծվում են և բավարարում են ռեպոզիտորիայի lint կանոնները | -| `typecheck:core` | TypeScript-ի կոմպիլյացիա՝ առանց սխալների (միայն խորհրդատվական զգուշացումներ) | Այո | -| `typecheck:noimplicit:core` | Խիստ `noImplicitAny` — ապագային միտված է. նախապես գոյություն ունեցող բազմաթիվ կանչման կետեր դեռևս անոտացիաների կարիք ունեն | **Խորհրդատվական** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`՝ սահմանափակված `src/app/(dashboard)/**`-ով (#7033) — `typecheck:core`-ի հատուկ ընտրված 27 ֆայլից բաղկացած թույլատրված ցանկը չի ներառում dashboard-ի որևէ TSX ֆայլ, իսկ `next build`-ը նույնպես երբեք չի կատարում դրանց տիպերի ստուգում (`next.config.mjs`-ում սահմանված է `ignoreBuildErrors: true`), ուստի այնտեղի որբ նույնացուցիչների հետընթացները (#6625/#6909) անտեսանելի էին CI-ի համար։ Տարբերությունները համեմատվում են ըստ ֆայլի/TS կոդի քանակի սառեցված ելակետային տվյալների հետ (`config/quality/dashboard-typecheck-baseline.json`, նույն հնացած տվյալների պարտադրման ձևանմուշը, ինչ `check:known-symbols`-ի դեպքում) — շեմի ձախողման են հանգեցնում միայն ելակետային քանակը գերազանցող ՆՈՐ սխալները. երբ նախապես գոյություն ունեցող սխալը շտկվում է, նվազեցրեք շեմը `--update`-ով։ | Այո | +Գործարկվում է դեպի `main` ուղղված յուրաքանչյուր PR-ի համար։ Ձախողման դեպքում արգելափակում է միավորումը։ + +| Սկրիպտ (`npm run ...`) | Ստուգում է | Արգելափակող | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `check:node-runtime` | Node.js-ի տարբերակը աջակցվող միջակայքում է | Այո | +| `check:cycles` | `src/` + `open-sse/` ամբողջ տարածքում շրջանաձև ներմուծումները (AST-ի վրա հիմնված, tsconfig-ի `paths`-ը լուծված է)։ Սովորական ռեժիմը խորհրդատվական է և ցուցադրում է ցիկլերը։ `check:cycles:ratchet`-ը (որը գործարկում է CI-ն) արգելափակում է, երբ քանակը գերազանցում է `quality-baseline.json`-ում սահմանված `metrics.cycles` վերին շեմը՝ ներկայումս 14, `direction: down`, ուստի այն կարող է միայն նվազել (#15159 G-01/G-02) | Այո (ratchet) | +| `check:route-validation:t06` | Zod սխեմաների առկայությունը բոլոր route-երում (Tier 6 քաղաքականություն) | Այո | +| `check:any-budget:t11` | `@ts-expect-error // any`-ների քանակը չի գերազանցում բյուջեն (Tier 11 catraca) | Այո | +| `check:provider-consistency` | `providers.ts`-ի յուրաքանչյուր մատակարար ունի համապատասխան գրառում `providerRegistry.ts`-ում (և հակառակը՝ թույլատրվածների ցանկի սահմաններում) | Այո | +| `check:model-lifecycle` | Ձեռքով սպասարկվող երթուղավորման երեք աղյուսակները մնում են համահունչ պահոցում ներառված կենսացիկլի ակնապատկերին (#11503). `FITNESS_TABLE`-ը (`taskFitness.ts`) չի գնահատում որևէ շահագործումից հանված id, որը `REGISTRY`-ն կարող է երթուղավորել, `BUILT_IN_ALIASES`-ի յուրաքանչյուր թիրախ առկա է `REGISTRY`-ում և բացակայում է շահագործումից հանված id-ների ակնապատկերից, `REGISTRY`-ում դեռ առկա յուրաքանչյուր շահագործումից հանված id վերահասցեավորվում է կամ նշված է `allowedRetiredInCatalog`-ում, իսկ `DEFAULT_DEGRADATION_MAP`-ի ոչ մի աղբյուր կամ թիրախ այդ ակնապատկերում նշված չէ որպես շահագործումից հանված։ Սա չի ապացուցում, որ մոդելը ներկայումս սպասարկվում է գործող վերին մակարդակի ծառայության կողմից։ Անցանց է՝ համեմատում է `config/quality/model-lifecycle.json`-ի հետ, որը ձեռքով թարմացվում է `npm run quality:refresh-model-lifecycle` հրամանով (պահանջում է ցանցային կապ և ինտեգրված չէ CI-ին)։ `allowedRetiredInCatalog`-ը աստիճանական կրճատման արգելանիվ է. գրառում ավելացրեք միայն հետևման խնդրի առկայության դեպքում։ | Այո | +| `check:fetch-targets` | Հաճախորդի կողմի `src/`-ում յուրաքանչյուր `fetch("/api/...")` հղվում է իրական `route.ts`-ի | Այո | +| `check:deps` | Պահոցի յուրաքանչյուր `package.json`-ում `npm install`-ով տեղադրվող բոլոր կախվածությունները ներառված են `dependency-allowlist.json`-ում. նոր՝ տարբերակով չամրագրված կամ slopsquatting-ի ենթարկված փաթեթները նշագրվում են | Այո | +| `audit:deps` | `npm audit` (արմատային + electron) — բարձր/կրիտիկական մակարդակի զգուշացումներ չկան (համընկնում է osv `check:vuln-ratchet`-ի հետ. տե՛ս Հիմնավորման չմշակված խնդիրների ցանկը) | Այո | +| `check:lockfile` | `package-lock.json`-ի ամբողջականություն՝ https ռեեստր, ամբողջականության հեշեր, առանց հոսթի վերասահմանումների | Այո | +| `check:licenses` | Արտադրական կախվածությունների SPDX լիցենզիաների թույլատրելի ցանկ | Այո | +| `check:tracked-artifacts` | Կառուցման արտեֆակտներ / commit արված `node_modules` խորհրդանշական հղումներ չկան (գործարկվում է նաև husky pre-commit-ում․ pre-push-ը միտումնավոր թեթև է — #6716) | Այո | +| `check:ai-attribution` | PR commit-ներում, վերնագրում կամ նկարագրության մեջ չկան AI/bot `Co-Authored-By` վերջնատող կամ AI-ով գեներացման ստորատող — Խիստ կանոն #16 (`quality.yml`-ի արագ ստուգումների ցիկլում՝ PR→`release/**`-ի համար — կարդում է իրադարձության payload-ը, PR-ներից դուրս ոչինչ չի անում — և `ci.yml`-ի lint-ում՝ միայն PR-ի համար նախատեսված քայլով, PR→`main`-ի դեպքում․ նաև husky-ի `commit-msg` hook-ում․ մարդկային համահեղինակները թույլատրված են․ #14436) | +| `check:vitest-exclusions` | Vitest-ի յուրաքանչյուր բացառություն նշում է հետևման issue և առկա է `config/quality/vitest-exclusions.json`-ում (#13204) | Այո | +| `check:file-size` | Ոչ մի սկզբնաղբյուր ֆայլ չի գերազանցում տվյալ ընդլայնման համար սահմանված առավելագույն չափը (ratchet․ սառեցված մեծ ֆայլերը՝ `frozen` ցանկում) | Այո | +| `check:error-helper` | Executor-ներում/handler-ներում սխալների պատասխաններն օգտագործում են `buildErrorBody()` / `sanitizeErrorMessage()` (Խիստ կանոն #12) | Այո | +| `check:migration-numbering` | Միգրացիոն SQL ֆայլերը համարակալված են հաջորդականորեն՝ առանց բացերի կամ կրկնությունների | Այո | +| `check:public-creds` | `publicCreds.ts`-ից դուրս չկան OAuth-ի բառացի `client_id`/`client_secret` կամ Firebase Web բանալիներ (Խիստ կանոն #11) | Այո | +| `check:db-rules` | `src/lib/db/` մոդուլներից դուրս չկա անմիջական SQL. `localDb.ts`-ից չկան barrel ներմուծումներ (Խիստ կանոններ #2/#5) | Այո | +| `check:known-symbols` | Մատակարարների կատարիչները, երթուղավորման ռազմավարությունները և թարգմանիչները, որոնք գրանցված են իրենց dispatch աղյուսակներում, համապատասխանում են սկավառակի ֆայլերին՝ չկան չկապակցված կամ չհայտարարված սիմվոլներ | Այո | +| `check:route-guard-membership` | Յուրաքանչյուր երթուղի, որը ստեղծում է ենթագործընթաց, դասակարգված է `isLocalOnlyPath()`-ի միջոցով (Խիստ կանոններ #15/#17) | Այո | +| `check:test-discovery` | Պահոցում գտնվող յուրաքանչյուր `*.test.ts` / `*.spec.ts` ֆայլ հավաքվում է առնվազն մեկ թեստային գործարկիչի կողմից (ratchet. `test-discovery-baseline.json`-ում չկապակցված ֆայլերի ցանկը կարող է միայն կրճատվել) | Այո | +| `check:agent-skills-sync` | Գեներացված agent-skills արտեֆակտները համապատասխանում են իրենց սկզբնաղբյուր կատալոգին (շեղումներ չկան) | +| `check:provider-asset-provenance` | Մատակարարների լոգոների/ռեսուրսների ծագման գրանցված գրառում առկա է | +| `lint:json` | JSON կազմաձևման ֆայլերը վերլուծվում են և համապատասխանում են ռեպոզիտորիայի lint կանոններին | +| `typecheck:core` | TypeScript-ի կոմպիլյացիա՝ առանց սխալների (միայն խորհրդատվական նախազգուշացումներ) | Այո | +| `typecheck:noimplicit:core` | Խիստ `noImplicitAny` — նախատեսված է ապագայի համար. նախկինում գոյություն ունեցող բազմաթիվ կանչի կետեր դեռևս անոտացիաների կարիք ունեն | **Խորհրդատվական** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, որի շրջանակը սահմանափակված է `src/app/(dashboard)/**`-ով (#7033) — `typecheck:core`-ի հատուկ ընտրված 27 ֆայլից բաղկացած թույլատրացուցակը չի ներառում dashboard-ի որևէ TSX ֆայլ, իսկ `next build`-ը նույնպես երբեք չի ստուգում դրա տիպերը (`next.config.mjs`-ում սահմանված է `ignoreBuildErrors: true`), ուստի այնտեղ առկա չկապակցված նույնացուցիչների հետընթացները (#6625/#6909) անտեսանելի էին CI-ի համար։ Տարբերությունները համեմատվում են ըստ ֆայլի և TS կոդի քանակների անփոփոխ բազային ցուցանիշի հետ (`config/quality/dashboard-typecheck-baseline.json`, նույն հնացածության վերահսկման ձևանմուշը, ինչ `check:known-symbols`-ի դեպքում) — միայն բազային քանակը գերազանցող ՆՈՐ սխալներն են ձախողում անցակետը. երբ նախկինում գոյություն ունեցող սխալը շտկվում է, բազային ցուցանիշը նվազեցրեք `--update`-ով։ | Այո | ### Առաջադրանք՝ `quality-gate` Գործարկվում է `test-coverage`-ից հետո։ Ձախողման դեպքում արգելափակում է միավորումը։ -| Սկրիպտ | Ստուգում է | Արգելափակող | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | -| `quality:collect` | Ստեղծում է `quality-metrics.json`-ը (ESLint-ի զգուշացումների քանակը, ծածկույթը՝ միավորված shard հաշվետվությունից) | Այո (ratchet-ից առաջ) | -| `quality:ratchet` | `quality-baseline.json`-ի յուրաքանչյուր չափանիշ չի վատթարացել (ESLint-ի զգուշացումներ ≤ ելակետային արժեքը, ծածկույթ ≥ ելակետային արժեքը) | Այո | -| `check:duplication` | Կոդի կրկնօրինակումը (jscpd@4) չի գերազանցում `quality-baseline.json`-ի ելակետային արժեքը | Այո | -| `check:complexity` | Ֆայլի մակարդակի ցիկլոմատիկ բարդությունը չի գերազանցում սահմանաչափը (հիմնական ESLint-ի `complexity` + `max-lines-per-function`) | Այո | -| `check:cognitive-complexity` | Կոգնիտիվ բարդության ratchet (`eslint-plugin-sonarjs`)՝ ESLint-ի առանձին անցում. CI-ն երկուսն էլ միավորված գործարկում է որպես մեկ `check:complexity-ratchets` քայլ | Այո | -| `check:dead-code` | Չօգտագործվող արտահանումների / ֆայլերի ratchet-ը (knip) չի վատթարանում ելակետային արժեքի համեմատ | Այո | -| `check:compression-budget` | Սեղմման հենանիշի բյուջեն՝ յուրաքանչյուր շարժիչի համար token-ների խնայողության նվազագույն շեմերը չպետք է վատթարանան | Այո | -| `check:type-coverage` | Տիպավորվածության տոկոսի ratchet-ը (`type-coverage`) չի վատթարանում. հիմնականում ներառում է `typecheck:noimplicit:core`-ի գործառույթները | Այո | -| `check:codeql-ratchet` | Բաց CodeQL ահազանգերի քանակը չի վատթարանում (ընթերցում է `gh api`-ի միջոցով. առանց token-ի՝ անվտանգ բաց է թողնում) — թարմացման հաճախականության և ձեռքով գործարկման համար տե՛ս ստորև «CodeQL ratchet»-ը | Այո | +| Սկրիպտ | Ստուգում է | Արգելակող | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | +| `quality:collect` | Ստեղծում է `quality-metrics.json`-ը (ESLint-ի զգուշացումների քանակը, միավորված հատվածային հաշվետվությունից ստացված ծածկույթը) | Այո (հետընթացի արգելքի նախորդ քայլն է) | +| `quality:ratchet` | `quality-baseline.json`-ի յուրաքանչյուր չափանիշ չի վատթարացել (ESLint-ի զգուշացումները ≤ ելակետային արժեքը, ծածկույթը ≥ ելակետային արժեքը) | Այո | +| `check:duplication` | Կոդի կրկնօրինակումը (jscpd@4) չի գերազանցում `quality-baseline.json`-ի ելակետային արժեքը | Այո | +| `check:complexity` | Ֆայլի մակարդակի ցիկլոմատիկ բարդությունը չի գերազանցում սահմանաչափը (հիմնական ESLint-ի `complexity` + `max-lines-per-function`) | Այո | +| `check:cognitive-complexity` | Ճանաչողական բարդության հետընթացի արգելք (`eslint-plugin-sonarjs`)՝ ESLint-ի առանձին անցում․ CI-ն երկուսն էլ միավորված գործարկում է որպես մեկ `check:complexity-ratchets` քայլ | Այո | +| `check:dead-code` | Չօգտագործվող արտահանումների / ֆայլերի հետընթացի արգելքը (knip) չի վատթարանում ելակետային արժեքի համեմատ | Այո | +| `check:compression-budget` | Սեղմման հենանիշային թեստի սահմանաչափ՝ յուրաքանչյուր շարժիչի թոքենների խնայողության նվազագույն շեմերը չպետք է վատթարանան | Այո | +| `check:type-coverage` | Տիպավորվածության տոկոսի հետընթացի արգելքը (`type-coverage`) չի վատթարանում․ հիմնականում ներառում է `typecheck:noimplicit:core`-ի գործառույթը | Այո | +| `check:codeql-ratchet` | CodeQL-ի բաց զգուշացումների քանակը չի վատթարանում (կարդում է `gh api`-ի միջոցով, առանց թոքենի բարեհաջող բաց է թողնում)․ թարմացման հաճախականության և ձեռքով գործարկման համար տե՛ս ստորև՝ «CodeQL-ի հետընթացի արգելք» | Այո | ### Առաջադրանք՝ `quality-extended` -Ամբողջ առաջադրանքը խորհրդատվական է (`continue-on-error: true`)։ npm-ի վրա հիմնված ratchet-ներն իրականում գործարկվում են, -իսկ արտաքին սկանավորիչները տեղադրվում են `gh release download`-ի միջոցով և ինքնուրույն բաց են թողնում ստուգումը (ելքի կոդ՝ 0), -եթե բինար ֆայլը դեռ բացակայում է։ +Ամբողջ առաջադրանքը խորհրդատվական է (`continue-on-error: true`)։ npm-ի վրա հիմնված հետընթացի արգելքներն իրականում գործարկվում են, +իսկ արտաքին սկանավորիչները տեղադրվում են `gh release download`-ի միջոցով և ինքնուրույն բաց են թողնում ստուգումը (ելքի կոդը՝ 0), +եթե երկուական ֆայլը դեռ բացակայում է։ -| Սկրիպտ | Ստուգում է | Արգելափակող | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| `check:circular-deps` | Շրջանաձև կախվածություններ չկան (dpdm) | **Խորհրդատվական** | -| `check:bundle-size` | Փաթեթի չափը չի գերազանցում սահմանաչափը | **Խորհրդատվական** | -| `check:secrets` | Գաղտնիքների սկանավորում (gitleaks)՝ բաց է թողնում, եթե բինար ֆայլը բացակայում է | **Խորհրդատվական** | -| `check:vuln-ratchet` | Կախվածությունների խոցելիությունները (osv-scanner) չեն վատթարանում՝ բաց է թողնում, եթե բինար ֆայլը բացակայում է | **Խորհրդատվական** | -| `check:workflows` | Workflow-ների lint ստուգում (actionlint + zizmor). բացակայող/չաշխատող սկանավորիչները, անվավեր հաշվետվությունները կամ բացակայող ratchet ելակետը հանգեցնում են INCOMPLETE ձախողման։ Վավեր արդյունքները հետևում են ընտրված strict/advisory/ratchet քաղաքականությանը | Կատարումը պարտադիր է. zizmor ratchet-ը CI-ում արգելափակող է | -| `check:openapi-breaking` | Հանրային API պայմանագրի (`openapi.yaml`) խախտող փոփոխությունները՝ համեմատած հիմնական ճյուղի հետ (oasdiff) — արտածում է `openapiBreaking=N`. բաց է թողնում, եթե oasdiff-ը բացակայում է կամ հիմնական սպեցիֆիկացիան հնարավոր չէ որոշել | **Խորհրդատվական** | +| Սկրիպտ | Ստուգում է | Արգելակող | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | +| `check:circular-deps` | Շրջանաձև կախվածությունների բացակայություն (dpdm) | **Խորհրդատվական** | +| `check:bundle-size` | Փաթեթի չափը չի գերազանցում սահմանաչափը | **Խորհրդատվական** | +| `check:secrets` | Գաղտնի տվյալների սկանավորում (gitleaks)՝ բաց է թողնվում, եթե երկուական ֆայլը բացակայում է | **Խորհրդատվական** | +| `check:vuln-ratchet` | Կախվածությունների խոցելիությունները (osv-scanner) չեն վատթարանում՝ բաց է թողնվում, եթե երկուական ֆայլը բացակայում է | **Խորհրդատվական** | +| `check:workflows` | Աշխատանքային հոսքերի լինտինգ (actionlint + zizmor)․ բացակայող/անսարք սկանավորիչները, անվավեր հաշվետվությունները կամ հետընթացի արգելքի բացակայող ելակետային արժեքը հանգեցնում են INCOMPLETE ձախողման։ Վավեր արդյունքները հետևում են ընտրված խիստ/խորհրդատվական/հետընթացի արգելքի քաղաքականությանը | Կատարումը պարտադիր է․ zizmor-ի հետընթացի արգելքը CI-ում արգելակող է | +| `check:openapi-breaking` | Հանրային API պայմանագրի (`openapi.yaml`) անհամատեղելի փոփոխություններ՝ համեմատած բազային ճյուղի հետ (oasdiff)․ ստեղծում է `openapiBreaking=N`․ բաց է թողնվում, եթե oasdiff-ը բացակայում է կամ բազային բնութագիրը հնարավոր չէ վերծանել | **Խորհրդատվական** | ### Առաջադրանք՝ `docs-sync-strict` -Գործարկվում է դեպի `main` ուղղված յուրաքանչյուր PR-ի համար։ Ձախողման դեպքում արգելափակում է միավորումը։ - -| Սկրիպտ | Ստուգում է | Արգելափակող | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| `check:docs-all` | Մետա-շեմ, որը ստորև նշված 6 ենթաշեմերը գործարկում է հաջորդաբար | Այո | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt տարբերակների համապատասխանությունը | Այո | -| ↳ `check:docs-counts` | Շարադրանքում նշված քանակները (մատակարարների քանակ, միգրացիաների քանակ և այլն) գտնվում են իրական քանակների սահմանած աստիճանական խստացման միջակայքում | Այո | -| ↳ `check:env-doc-sync` | `.env.example`-ի յուրաքանչյուր միջավայրի փոփոխական փաստագրված է փաստաթղթերի աղյուսակում, և հակառակը | Այո | -| ↳ `check:deprecated-versions` | Փաստաթղթերում հնացած տարբերակների տողեր չկան | Այո | -| ↳ `check:doc-links` | Փաստաթղթերի ներքին markdown հղումները մատնանշում են գոյություն ունեցող ֆայլեր (`[text]`/`(path)` ձևաչափով) | Այո | -| ↳ `check:fabricated-docs` | Փաստաթղթերում հիշատակված երթուղիները, միջավայրի փոփոխականները, CLI հրամանները, hook-երի անունները և ֆայլերի ուղիները գոյություն ունեն կոդային բազայում։ Խիստ շեմ՝ `--strict`-ի միջոցով, առանց դրոշակի՝ ոչ արգելափակող ձախողում։ | Այո (CI-ում `--strict`-ի միջոցով) | -| `check:cli-i18n` | CLI հրամանների տողերը առկա են i18n տեղայնացման բոլոր ֆայլերում | Այո | -| `check:openapi-coverage` | OpenAPI սպեցիֆիկացիան ընդգրկում է իրական երթուղիների՝ աստիճանաբար բարձրացվող նվազագույն շեմը | Այո | -| `check:openapi-security-tiers` | `openapi.yaml`-ի անվտանգության մակարդակների նշումները համապատասխանում են `routeGuard.ts`-ի դասակարգումներին | **Խորհրդատվական** | -| `check:openapi-routes` | `openapi.yaml`-ի յուրաքանչյուր ուղի համապատասխանում է իրական `route.ts`-ի (հորինվածքների կանխարգելում) | Այո | -| `check:docs-symbols` | `docs/**/*.md`-ում յուրաքանչյուր `/api/...` հղում համապատասխանում է իրական `route.ts`-ի (հորինվածքների կանխարգելում) | Այո | -| `i18n translation drift` | i18n տեղայնացման ֆայլերում չթարգմանված բանալիներ — միայն նախազգուշացում | **Խորհրդատվական** | +Գործարկվում է `main` ուղղված յուրաքանչյուր PR-ի համար։ Ձախողման դեպքում արգելափակում է միավորումը։ + +| Սկրիպտ | Ստուգում է | Արգելափակող | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| `check:docs-all` | Մետա-դարպաս, որը հաջորդաբար գործարկում է ստորև նշված 6 ենթադարպասները | Այո | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt տարբերակների համապատասխանությունը | Այո | +| ↳ `check:docs-counts` | Տեքստում նշված քանակները (մատակարարների քանակ, միգրացիաների քանակ և այլն) գտնվում են իրական քանակների համար սահմանված աստիճանական միջակայքում | Այո | +| ↳ `check:env-doc-sync` | `.env.example`-ի յուրաքանչյուր միջավայրի փոփոխական փաստաթղթավորված է փաստաթղթերի աղյուսակում, և հակառակը | Այո | +| ↳ `check:deprecated-versions` | Փաստաթղթերում չկան հնացած տարբերակների տողեր | Այո | +| ↳ `check:doc-links` | Փաստաթղթերի ներքին markdown հղումները տանում են դեպի գոյություն ունեցող ֆայլեր (`[տեքստ]`/`(path)` ձևաչափ) | Այո | +| ↳ `check:fabricated-docs` | Փաստաթղթերում նշված երթուղիները, միջավայրի փոփոխականները, CLI հրամանները, hook-երի անունները և ֆայլերի ուղիները գոյություն ունեն կոդային բազայում։ Խիստ դարպաս՝ `--strict`-ի միջոցով, իսկ առանց դրոշակի՝ ոչ խիստ ձախողում։ | Այո (CI-ում `--strict`-ի միջոցով) | +| `check:cli-i18n` | CLI հրամանների տողերը առկա են i18n տեղայնացման բոլոր ֆայլերում | Այո | +| `check:openapi-coverage` | OpenAPI սպեցիֆիկացիան ընդգրկում է իրական երթուղիների՝ առնվազն աստիճանաբար սահմանված նվազագույն քանակը | Այո | +| `check:openapi-security-tiers` | `openapi.yaml`-ի անվտանգության մակարդակների նշումները համապատասխանում են `routeGuard.ts`-ի դասակարգումներին | **Խորհրդատվական** | +| `check:openapi-routes` | `openapi.yaml`-ի յուրաքանչյուր ուղի համապատասխանում է իրական `route.ts`-ի (հորինվածքների կանխարգելում) | Այո | +| `check:docs-symbols` | `docs/**/*.md`-ում `/api/...`-ի յուրաքանչյուր հղում համապատասխանում է իրական `route.ts`-ի (հորինվածքների կանխարգելում) | Այո | +| `i18n translation drift` | i18n տեղայնացման ֆայլերում չթարգմանված բանալիներ՝ միայն նախազգուշացում | **Խորհրդատվական** | ### Առաջադրանք՝ `i18n-ui-coverage` -| Սկրիպտ | Ստուգում է | Արգելափակող | -| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `check-ui-keys-coverage` (ներկառուցված) | UI-ի i18n բանալիների ծածկույթը ≥ 65% է | Այո | -| `check-ui-value-drift` (ներկառուցված) | Վերաշարադրված անգլերեն **արժեքից** հետո հնացած թարգմանություն չի մնում | Այո | -| `check-new-key-coverage` (ներկառուցված) | Անգլերենի **նոր** բանալին թարգմանված է յուրաքանչյուր տեղայնացման մեջ. `__MISSING__:` նշիչը մերժվում է | Այո | -| `check-translation-ratio` | Յուրաքանչյուր տեղայնացման իրական թարգմանությունների հարաբերակցությունը (թույլատրելի ցանկից դուրս անգլերենին նույնական / տեղապահ / բացակայող տերևային արժեքներ) չպետք է գերազանցի `config/quality/i18n-translation-baseline.json`-ը + թույլատրելի շեղումը | **Խորհրդատվական** | +| Սկրիպտ | Ստուգում է | Արգելափակող | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `check-ui-keys-coverage` (ներդրված) | UI-ի i18n բանալիների ծածկույթը ≥ 65% է | Այո | +| `check-ui-value-drift` (ներդրված) | Վերաշարադրված անգլերեն **արժեքը** չի թողնում հնացած թարգմանություն | Այո | +| `check-new-key-coverage` (ներդրված) | Անգլերեն **նոր** բանալին թարգմանված է յուրաքանչյուր տեղայնացման համար. `__MISSING__:` նշիչը մերժվում է | Այո | +| `check-translation-ratio` | Յուրաքանչյուր տեղայնացման իրական թարգմանությունների հարաբերակցությունը (անգլերենին նույնական / տեղապահ / թույլատրված ցանկից դուրս բացակայող արժեքներ) չպետք է գերազանցի `config/quality/i18n-translation-baseline.json`-ը՝ գումարած թույլատրելի շեղումը | **Խորհրդատվական** | -Պահանջվում է `fetch-depth: 0` — արժեքների շեղման շեմը `en.json`-ը համեմատում է միաձուլման բազայի հետ։ +Պահանջում է `fetch-depth: 0` — արժեքների շեղման դարպասը համեմատում է `en.json`-ը միաձուլման բազայի հետ։ -#### `check-ui-value-drift` — հնացած թարգմանությունների շեմ +#### `check-ui-value-drift` — հնացած թարգմանությունների դարպաս -Հայտնաբերում է i18n-ի այն մեկ հետընթացը, որը մյուս շեմերը կառուցվածքային առումով չեն կարող տեսնել. անգլերեն արժեքը -վերաշարադրվում է, իսկ _նախորդ_ անգլերենից ստացված թարգմանությունները մնում են տեղում, ինչի հետևանքով -ոչ անգլախոս օգտատերերը շարունակում են կարդալ վստահ ձևակերպված, բայց արդեն սխալ տեքստը։ +Հայտնաբերում է i18n-ի այն մեկ հետընթացը, որը մյուս դարպասները կառուցվածքային առումով չեն կարող տեսնել. անգլերեն արժեքը +վերաշարադրվում է, իսկ _նախորդ_ անգլերենից ստացված թարգմանությունները մնում են, ինչի հետևանքով +ոչ անգլախոս օգտատերերը շարունակում են կարդալ վստահորեն ձևակերպված, սակայն այժմ սխալ տեքստ։ -Սա իրականում թողարկվել է։ `oauthModal.googleOAuthWarning`-ը վերաշարադրվել էր, երբ ավելացվեց Antigravity-ի -մուտքի օգնականը (#5203), սակայն **43 տեղայնացումներից 39-ում** մնացել էր օպերատորներին հուշող տեքստը՝ «պատճենել -ամբողջ URL-ը և տեղադրել այն ստորև», մինչդեռ այդ մատակարարի դեպքում այս հոսքը հնարավոր չէ ավարտել։ Այն -չէր նկատվել մինչև #8463-ը, քանի որ՝ +Սա իրականում հասել է թողարկման։ `oauthModal.googleOAuthWarning`-ը վերաշարադրվել էր, երբ ավելացվեց Antigravity +մուտքի օգնականը (#5203). **43 տեղայնացումներից 39-ը** պահպանել էին տեքստ, որն օպերատորներին ասում էր՝ «պատճենել +ամբողջական URL-ը և տեղադրել այն ստորև»՝ ընթացակարգ, որն այդ մատակարարի դեպքում չի կարող ավարտվել։ Այն +աննկատ էր մնացել մինչև #8463, քանի որ՝ -- `sync-ui-keys`-ը լրացնում է միայն **բացակայող** բանալիները, ոչ երբեք **հնացածները**, -- `check-ui-keys-coverage`-ը հաշվում է բանալու _առկայությունը_, ուստի հնացած թարգմանությունը համարվում է ծածկված, -- `check-translation-drift`-ը հետևում է `docs/i18n//**.md` փաստաթղթային հայելիներին՝ - այն երբեք չի կարդում `src/i18n/messages/*.json`։ Արգելափակող է `docs-sync-strict` առաջադրանքում՝ սկսած - 2026-09-ի վերասինքրոնացումից. խմբագրեք հիմնական փաստաթուղթը → `npm run i18n:run -- --files=` (բաժնի մակարդակով, քիչ ռեսուրսատար)։ +- `sync-ui-keys`-ը լրացնում է միայն **բացակայող** բանալիները, բայց երբեք՝ **հնացածները**. +- `check-ui-keys-coverage`-ը հաշվում է բանալու _առկայությունը_, ուստի հնացած թարգմանությունը համարվում է ծածկված. +- `check-translation-drift`-ը հետևում է `docs/i18n//**.md` փաստաթղթային հայելիներին — + այն երբեք չի կարդում `src/i18n/messages/*.json`։ 2026-09-ի վերասինքրոնացումից ի վեր արգելափակող է `docs-sync-strict` առաջադրանքում. խմբագրեք հիմնական փաստաթուղթ → `npm run i18n:run -- --files=` (բաժնի մակարդակով, քիչ ծախսատար)։ -**Տարբերությունները հաշվի առնող, ոչ թե բազային տարբերակի վրա հիմնված։** Այն միաձուլման բազայում գտնվող `en.json`-ը համեմատում է +**Տարբերությունները հաշվի առնող, ոչ թե բազային վիճակի վրա հիմնված։** Այն միաձուլման բազայում գտնվող `en.json`-ը համեմատում է աշխատանքային ծառի հետ․ յուրաքանչյուր բանալու համար, որի անգլերեն արժեքը փոխվել է, անփոփոխ -թարգմանություն դեռ պարունակող ցանկացած տեղայնացում համարվում է հնացած։ Սա միտումնավոր **սառեցնում է նախկինում կուտակված պարտքը**․ տարբերությունը -չի կարող բացահայտել, թե վաղուց գոյություն ունեցող թարգմանությունը որ հին անգլերեն տարբերակից է առաջացել, ուստի դարպասը գնահատում է -միայն այն, ինչին առնչվում է ընթացիկ փոփոխությունը։ Այլընտրանքը՝ ըստ բանալիների հեշերի բազային տարբերակը, կպահանջեր -մոտ 600 KB ծավալով գեներացված ֆայլ՝ 3 անգամ մեծ գոյություն ունեցող ամենախոշոր բազային տարբերակից, որը կփոփոխվեր յուրաքանչյուր i18n PR-ի ժամանակ։ +թարգմանություն դեռևս պարունակող ցանկացած տեղայնացում համարվում է հնացած։ Սա միտումնավոր **սառեցնում է նախկինում առկա պարտքը**․ տարբերությունը +չի կարող բացահայտել, թե վաղուց գոյություն ունեցող թարգմանությունը որ հին անգլերեն տարբերակից է առաջացել, ուստի արգելքը գնահատում է +միայն այն, ինչին վերաբերում է ընթացիկ փոփոխությունը։ Այլընտրանքը (յուրաքանչյուր բանալու համար հեշերի բազային վիճակ) կպահանջեր +մոտ 600 KB ծավալով գեներացված ֆայլ՝ ամենամեծ գոյություն ունեցող բազային ֆայլից 3× մեծ, որը կփոփոխվեր յուրաքանչյուր i18n PR-ի ժամանակ։ -Պահանջը բավարարելու երկու եղանակ կա․ +Այն բավարարելու երկու եղանակ կա․ 1. թարմացնել համապատասխան թարգմանությունները, կամ -2. դրանց արժեքը սահմանել որպես `__MISSING__:`․ այդ դեպքում կատարման միջավայրը վերադարձնում է շտկված անգլերենը - (`src/i18n/request.ts::deepMergeFallback`, #7258), իսկ բանալին ավելացվում է թարգմանության հերթին։ +2. սահմանել դրանք որպես `__MISSING__:<նոր անգլերեն տեքստ>`․ այդ դեպքում կատարման միջավայրը տրամադրում է ուղղված անգլերենը + (`src/i18n/request.ts::deepMergeFallback`, #7258), իսկ բանալին ավելացվում է թարգմանության հերթում։ Եթե փոխվել է տողի **իմաստը**, նախընտրելի է **վերանվանել բանալին**․ նոր բանալին չի կարող -ժառանգել հնացած թարգմանություն։ Այդ օրինաչափությունն է կիրառվել #8463-ում։ +ժառանգել հնացած թարգմանություն։ #8463-ում կիրառվել է հենց այդ ձևանմուշը։ ```bash npm run i18n:check-value-drift # խիստ ռեժիմ (այն, ինչ գործարկում է CI-ը) @@ -231,47 +231,47 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` Ավարտվում է 0 կոդով և `SKIP reason=base-unresolved` հաղորդագրությամբ, երբ բազային կատալոգը հնարավոր չէ կարդալ (մակերեսային -կլոն՝ առանց բազային հղման)՝ նմանակելով `check-openapi-breaking`-ի վարքագիծը։ +կլոն՝ առանց բազային հղման)՝ կրկնելով `check-openapi-breaking`-ի վարքագիծը։ ### Առաջադրանք՝ `i18n` -i18n-ի վավերացման ամբողջական մատրիցա (մեկ առաջադրանք՝ յուրաքանչյուր տեղայնացման համար)։ Ամբողջ առաջադրանքը խորհրդատվական է։ +i18n-ի ամբողջական վավերացման մատրիցա (մեկ առաջադրանք յուրաքանչյուր տեղայնացման համար)։ Ամբողջ առաջադրանքը խորհրդատվական է։ -| Սկրիպտ | Ինչն է վավերացնում | Արգելափակող | -| ------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------- | -| `validate_translation.py quick` | Թարգմանության ամբողջականությունը՝ ըստ տեղայնացման | **Խորհրդատվական** (`continue-on-error: true` ամբողջ առաջադրանքի համար) | +| Սկրիպտ | Ինչն է վավերացնում | Արգելափակող | +| ------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------- | +| `validate_translation.py quick` | Թարգմանության ամբողջականությունը՝ ըստ տեղայնացման | **Խորհրդատվական** (`continue-on-error: true`՝ ամբողջ առաջադրանքի համար) | ### Առաջադրանք՝ `pr-test-policy` Գործարկվում է միայն pull request-ների համար։ -| Սկրիպտ | Ինչն է վավերացնում | Արգելափակող | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` կամ `bin/` պանակներում արտադրական կոդը փոխող PR-ները պետք է ներառեն կամ թարմացնեն թեստեր (Խիստ կանոն #8) | Այո | -| `check:test-masking` | Փոփոխված թեստային ֆայլերը չեն նվազեցնում assert-ների զուտ քանակը և չեն ավելացնում `assert.ok(true)` տավտոլոգիաներ | Այո | -| `check:pr-evidence` | PR-ի նկարագրությունը նշում է փոփոխության համար թեստերի/VPS-ի ապացույցները (մեխանիկացնում է Խիստ կանոն #18-ը՝ PR-ի տեքստում որոնելով համապատասխան արտահայտություններ․ փխրուն է, տե՛ս Backlog-ը) | Այո | +| Սկրիպտ | Ինչն է վավերացնում | Արգելափակող | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check:pr-test-policy` | Այն PR-ները, որոնք փոխում են արտադրական կոդը `src/`, `open-sse/`, `electron/` կամ `bin/` ուղիներում, պետք է ներառեն կամ թարմացնեն թեստերը (Խիստ կանոն #8) | Այո | +| `check:test-masking` | Փոփոխված թեստային ֆայլերը չեն նվազեցնում assert-ների զուտ քանակը և չեն ավելացնում `assert.ok(true)` տավտոլոգիաներ | Այո | +| `check:pr-evidence` | PR-ի նկարագրությունը պարունակում է փոփոխության թեստային/VPS ապացույցներ (մեքենայացնում է Խիստ կանոն #18-ը՝ PR-ի տեքստում որոնելով․ փխրուն է, տե՛ս Հետագա աշխատանքների ցանկը) | Այո | ### Առաջադրանք՝ `test-vitest` Գործարկվում է `build`-ից հետո։ Ձախողման դեպքում արգելափակում է միաձուլումը։ -| Փաթեթ | Ինչն է վավերացնում | Արգելափակող | -| ---------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP սերվեր (110 գործիք), autoCombo, քեշ — vitest գործարկիչ | Այո | -| `test:vitest:ui` | UI բաղադրիչների թեստեր — vitest գործարկիչ | **Արգելափակող** — նախկինում գոյություն ունեցող ձախողումները բացահայտորեն բացառված են `vitest.config.ts`-ում․ նոր ձախողումները ձախողում են առաջադրանքը | +| Փաթեթ | Ինչն է վավերացնում | Արգելափակող | +| ---------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP սերվեր (110 գործիք), autoCombo, քեշ — vitest գործարկիչ | Այո | +| `test:vitest:ui` | UI բաղադրիչների թեստեր — vitest գործարկիչ | **Արգելափակող** — նախկինում առկա ձախողումները բացահայտորեն բացառված են `vitest.config.ts`-ում․ նոր ձախողումները ձախողում են առաջադրանքը | ### Գիշերային աշխատանքային հոսքեր (պլանավորված, խորհրդատվական) -Սրանք գործարկվում են cron ժամանակացույցով (և `workflow_dispatch`-ով), երբեք՝ PR-ների համար։ Բոլորը խորհրդատվական են։ +Դրանք գործարկվում են cron ժամանակացույցով (և `workflow_dispatch`-ով), երբեք՝ PR-ների համար։ Բոլորը խորհրդատվական են։ | Աշխատանքային հոսք | Ինչն է վավերացնում | Արգելափակող | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `nightly-property` | fast-check հատկությունների թեստեր՝ պատահական seed-ով և գործարկումների մեծ քանակով | **Խորհրդատվական** | -| `nightly-resilience` | heap-ի աճի դարպաս, խափանումների քաոսային ներարկում, k6 բեռնվածության/տևական ծանրաբեռնվածության թեստավորում | **Խորհրդատվական** | -| `nightly-llm-security` | promptfoo ներարկումների պաշտպանություն (արգելափակման ռեժիմ) + garak ստուգումներ (բաց են թողնվում առանց մատակարարի գաղտնիքի) | **Խորհրդատվական** | -| `nightly-schemathesis` | OpenAPI պայմանագրի ֆազինգ (schemathesis)՝ կենդանի OmniRoute-ի նկատմամբ, օգտագործելով `docs/openapi.yaml`-ը․ բացահայտում է սպեցիֆիկացիայի խախտումները / չմշակված 500 սխալները (Փուլ 8 B.4) | **Խորհրդատվական** | -| `nightly-mutation` | Stryker մուտացիոն թեստավորման գնահատականը արագ միավորային թեստերի ուղու համար․ գոյատևած մուտանտները բացահայտում են թույլ assert-ները | **Խորհրդատվական** | -| `nightly-compat` | Node շարժիչի համատեղելիության մատրիցան՝ աջակցվող `engines.node` միջակայքերում | **Խորհրդատվական** | +| `nightly-property` | fast-check հատկությունների թեստեր՝ պատահական սկզբնական արժեքով և գործարկումների մեծ քանակով | **Խորհրդատվական** | +| `nightly-resilience` | heap-ի աճի արգելք, խափանումների քաոսային ներարկում, k6 բեռնվածության/երկարատև ծանրաբեռնվածության թեստավորում | **Խորհրդատվական** | +| `nightly-llm-security` | promptfoo ներարկումների պաշտպանություն (արգելափակման ռեժիմ) + garak զոնդեր (բաց են թողնվում առանց մատակարարի գաղտնիքի) | **Խորհրդատվական** | +| `nightly-schemathesis` | OpenAPI պայմանագրի fuzzing (schemathesis)՝ գործող OmniRoute-ի նկատմամբ, օգտագործելով `docs/openapi.yaml`-ը․ բացահայտում է սպեցիֆիկացիայի խախտումները / չմշակված 500 սխալները (Փուլ 8 B.4) | **Խորհրդատվական** | +| `nightly-mutation` | Stryker մուտացիոն թեստավորման գնահատական՝ արագ մոդուլային ուղու համար․ գոյատևած մուտանտները բացահայտում են թույլ assert-ները | **Խորհրդատվական** | +| `nightly-compat` | Node շարժիչի համատեղելիության մատրիցա՝ աջակցվող `engines.node` միջակայքերի համար | **Խորհրդատվական** | --- @@ -534,43 +534,43 @@ CI ստուգիչներից բացի, OmniRoute-ը ներառում է **ըստ --- -## Ռացիոնալացման կուտակված աշխատանքներ (ROI-ի վերանայում — Փուլ 9 Ալիք 3) +## Ռացիոնալացման հետագա աշխատանքների ցանկ (ROI-ի վերանայում — Փուլ 9 Ալիք 3) -Այս գույքացանկը 2026-06-17-ին համադրվել է `ci.yml`-ի հետ (նախորդ տարբերակում բաց էին թողնվել +Այս գույքացանկը 2026-06-17-ին համադրվել է `ci.yml`-ի հետ (նախորդ տարբերակում բացակայում էին `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`)։ Համադրված հավաքածուի ROI-ի վերանայմամբ -բացահայտվել են ռացիոնալացման հետևյալ թեկնածուները։ **Միավորումները CI-ի մեխանիկական -փոփոխություններ են, իսկ փոխարկումները/հեռացումները՝ օպերատորին վերապահված քաղաքականության որոշումներ։** Ստորև -նշվածներից դեռ ոչինչ չի կիրառվել։ - -**Վերևում նաև փաստագրված չեն** (խորհրդատվական, թույլ ազդանշանով)՝ `docs-lint` առաջադրանքը -(markdownlint + Vale, ամբողջ առաջադրանքի համար՝ `continue-on-error`) և ինքնուրույն սկանավորման աշխատանքային հոսքերը՝ -`semgrep.yml` / `codeql.yml` / `scorecard.yml`։ `semgrepFindings: 0`-ն առկա է -`quality-baseline.json`-ում, սակայն կապված չէ `ci.yml`-ում արգելափակող ճշգրտիչի հետ․ չափորոշիչը -ներկայումս որբ է։ - -### Միավորում / կրկնությունների վերացում (մեխանիկական, ավելի ցածր ռիսկով) - -Յուրաքանչյուր թեկնածու 2026-06-17-ին վավերացվել է գործող անցակետի վիճակի նկատմամբ (վստահել, բայց ստուգել)․ -մի քանի «ակնհայտ» միավորումներ, պարզվեց, թաքցնում են տեխնիկական պարտք և **չեն** կարող կիրառվել որպես մաքուր ուղղակի փոխարինումներ։ - -- **`check:docs-sync`-ը գործարկվում է երկու անգամ** — առանձին՝ `lint` առաջադրանքում, և կրկին՝ `check:docs-all`-ի (`docs-sync-strict`) ու husky-ի pre-commit hook-ի ներսում։ ✅ **ԱՎԱՐՏՎԱԾ Է** — առանձին `lint` կանչը հեռացվել է։ -- **CVE սկանավորում** — ❌ **Մաքուր միավորում ՉԷ։** `audit:deps`-ը կոշտ ձախողվում է ցանկացած բարձր/կրիտիկական CVE-ի դեպքում, իսկ `check:vuln-ratchet`-ը (osv) ձախողվում է միայն ելակետի համեմատ _հետընթացի_ դեպքում (ներկայումս՝ 1 MODERATE)։ Իմաստաբանությունը տարբեր է․ `audit:deps`-ի հեռացումը կվերացնի բարձր/կրիտիկական մակարդակի բացարձակ անցակետը։ Պահել երկուսն էլ։ -- **Ցիկլերի հայտնաբերում** — ❌ **Մաքուր միավորում ՉԷ։** `check:circular-deps`-ը (dpdm) հաղորդում է **91 ցիկլի** մասին (հենց այդ պատճառով էլ այն խորհրդատվական է)․ այն չի կարող դառնալ արգելափակող՝ առանց դրանք նախապես վերացնելու, և դրա ընդգրկույթն ավելի լայն է, քան հաջող անցնող, նպատակային `check:cycles`-ինը։ `check:cycles`-ը պահել արգելափակող․ dpdm-ի 91 ցիկլերի վերացումն առանձին կուտակված աշխատանք է։ -- **Բարդություն** — ✅ **ԱՎԱՐՏՎԱԾ Է** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`)․ ESLint-ի մեկ անցում, հաշվարկը՝ ըստ ruleId-ի, որպեսզի ցիկլոմատիկ+max-lines և կոգնիտիվ ելակետերը մնան անկախ․ առանձին `check:complexity` / `check:cognitive-complexity` հրամանները պահպանվել են տեղային `--update`-ի համար։ -- **`/api` հակահալյուցինացիոն ստուգում** — ✅ **ԱՎԱՐՏՎԱԾ Է** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`)․ `src/app/api`-ի ֆայլային համակարգի մեկ գույքագրում, openapi-routes + docs-symbols-ը դեռ զեկուցում են առանձին․ առանձին հրամանները պահպանվել են տեղային գործարկումների համար։ -- **`check:node-runtime`-ը գործարկվում է 11 առաջադրանքում** — ⚠️ **ցածր ROI։** Յուրաքանչյուրն առանձին runner է, իսկ ստուգումը տևում է <1 վրկ․ ընդհանուր խնայողությունը մոտ ~10 վրկ է՝ յուրաքանչյուր առաջադրանքի համար էժան պաշտպանիչ ստուգումը կորցնելու դիմաց։ Չարժե նման փոփոխությունների իրարանցումը։ -- **`typecheck:noimplicit:core`-ը CI-ի lint-ում** — ✅ **հեռացվել է lint առաջադրանքից** (խորհրդատվական էր՝ `continue-on-error`)․ տիպերի արգելափակող մակերեսն ապահովում են `typecheck:core` + `check:type-coverage`։ Տեղային սկրիպտը պահպանվել է։ - -### Փոխարկել / որոշել (օպերատորի քաղաքականություն) - -- `check:openapi-security-tiers` (խորհրդատվական) — ❌ **Հնարավոր չէ մաքուր կերպով փոխարկել։** Այն ավարտվում է 0 կոդով, սակայն զգուշացնում է, որ `LOCAL_ONLY_API_PREFIXES`-ի տակ գտնվող մի քանի `traffic-inspector` երթուղիներում բացակայում է `x-loopback-only: true` նշագրումը։ Դրա պարտադրումը նախ պահանջում է այդ նշագրումներն ավելացնել `openapi.yaml`-ում։ -- `typecheck:noimplicit:core` (խորհրդատվական) — հիմնականում ներառված է արգելափակող `check:type-coverage` ճշգրտիչի մեջ։ Փոխարկել ճշգրտիչի կամ հեռացնել կրկնվող երկրորդ `tsc` անցումը։ -- `test:vitest:ui` (այժմ **արգելափակող**) — նախապես գոյություն ունեցող ձախողումները բացահայտորեն բացառված են `vitest.config.ts`-ում՝ `// #8618` հետագծման մեկնաբանություններով․ նոր ձախողումները ձախողում են առաջադրանքը։ -- `check:secrets` (gitleaks, արգելափակող ճշգրտիչ՝ սառեցված 3 փաստագրված կեղծ դրական արդյունքի վրա) — այդ 3-ը ներառել թույլատրելի ցանկում՝ 0-ի հասնելու համար, կամ իջեցնել խորհրդատվականի մակարդակի։ Համընկնում է GitHub-ի ներկառուցված գաղտնիքների սկանավորման + `check:public-creds`-ի հետ։ -- `check:pr-evidence` (արգելափակող, grep-ով որոնում է PR-ի մարմնի արձակ տեքստում) — կեղծ դրական արդյունքների բարձր ռիսկ․ հեռացնելու դեպքում թուլացնում է Խիստ կանոն #18-ի կիրառումը, ուստի սա իսկական քաղաքականության որոշում է։ -- `semgrep` (առանձին խորհրդատվական) — OWASP ընտանիքների մասով համընկնում է CodeQL-ի հետ․ դրա ելակետը կապել ճշգրտիչի հետ կամ հեռացնել։ +`check:codeql-ratchet`, `check:pr-evidence`)։ Համադրված բազմության ROI-ի վերանայումը +բացահայտեց ռացիոնալացման հետևյալ թեկնածուները։ **Միավորումները CI-ի մեխանիկական +փոփոխություններ են, իսկ ռեժիմի փոփոխությունները/հեռացումները քաղաքականության որոշումներ են, որոնք վերապահված են օպերատորին։** Ստորև նշվածից +դեռ ոչինչ չի կիրառվել։ + +**Վերևում նույնպես փաստաթղթավորված չեն** (խորհրդատվական, թույլ ազդանշան)՝ `docs-lint` առաջադրանքը +(markdownlint + Vale, ամբողջ առաջադրանքի համար՝ `continue-on-error`) և առանձին սկանավորիչների աշխատանքային հոսքերը՝ +`semgrep.yml` / `codeql.yml` / `scorecard.yml`։ `semgrepFindings: 0`-ը գտնվում է +`quality-baseline.json`-ում, սակայն կապված չէ `ci.yml`-ի արգելափակող սահմանափակիչի հետ. ներկայում այս չափանիշը +կապազուրկ է։ + +### Միավորում / կրկնությունների վերացում (մեխանիկական, ավելի ցածր ռիսկ) + +Յուրաքանչյուր թեկնածու 2026-06-17-ին ստուգվել է ակտիվ անցակետի վիճակի համեմատ (վստահել, բայց ստուգել). +մի քանի «ակնհայտ» միավորումներ, պարզվեց, թաքցնում են տեխնիկական պարտք և **չեն** կարող ուղղակիորեն փոխարինել առկան։ + +- **`check:docs-sync`-ը գործարկվում է երկու անգամ**՝ առանձին՝ `lint` առաջադրանքում, և կրկին՝ `check:docs-all`-ի (`docs-sync-strict`) ու husky pre-commit hook-ի ներսում։ ✅ **ԿԱՏԱՐՎԱԾ Է** — առանձին `lint` կանչը հեռացվել է։ +- **CVE սկանավորում** — ❌ **Մաքուր միավորում ՉԷ։** `audit:deps`-ը կոշտ ձախողվում է ցանկացած բարձր/կրիտիկական CVE-ի դեպքում, իսկ `check:vuln-ratchet`-ը (osv) ձախողվում է միայն ելակետային մակարդակի համեմատ _հետընթացի_ դեպքում (ներկայում՝ 1 MODERATE)։ Իմաստաբանությունը տարբեր է. `audit:deps`-ի հեռացումը կվերացներ բացարձակ բարձր/կրիտիկական անցակետը։ Պահել երկուսն էլ։ +- **Ցիկլերի հայտնաբերում** — ✅ **ԿԱՏԱՐՎԱԾ Է** (#15159 G-01/G-02)։ Այստեղ եղած հին տեքստը `check:cycles`-ն անվանում էր «կանաչ, խնամքով ընտրված» անցակետ և դրա արգելափակող լինելը հիմնավորում էր նրանով, որ `check:circular-deps`-ը (dpdm) հաղորդում էր 91 ցիկլի մասին։ Այդ կանաչ վիճակը **կեղծ կանաչ** էր. `check:cycles`-ը սկանավորում էր 5 ենթագրացուցակ (450 ֆայլ), համընկնում էր միայն ստատիկ `import|export … from`-ին և բաց էր թողնում բոլոր `@/` ու `@omniroute/open-sse/` նշիչները, ուստի չէր կարող տեսնել պահոցում գերակշռող դինամիկ import + alias ցիկլերը։ Ուղղված է. անցակետն այժմ շրջանցում է `src` + `open-sse`-ը (5023 ֆայլ), նշիչները հավաքում է TypeScript AST-ից (այդպիսով `import("…")`-ը հաշվվում է, իսկ տիպային դիրքում գտնվող `typeof import("…")`-ը՝ ոչ) և լուծում է tsconfig-ի `paths`-ը։ Այն գտնում է **14** ցիկլ, ոչ թե 0։ Քանի որ նախկինից առկա 14 ցիկլը հնարավոր չէ ուղղել անցակետի PR-ում, `check:cycles`-ն այժմ **սահմանափակիչ** է (`--ratchet`, վերին սահմանը՝ `metrics.cycles.value = 14`՝ `quality-baseline.json`-ում, `direction: down`). այն արգելափակում է ցանկացած _հետընթաց_, իսկ քանակը կարող է միայն նվազել։ CI-ն գործարկում է `npm run check:cycles:ratchet`։ Աստիճանական նվազեցումը կատարվում է **A-01**-ի հետ։ `check:circular-deps`-ը (dpdm) մնում է խորհրդատվական՝ որպես ավելի լայն երկրորդ կարծիք։ +- **Բարդություն** — ✅ **ԿԱՏԱՐՎԱԾ Է** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`)՝ ESLint-ի մեկ շրջանցում, հաշվարկ ըստ ruleId-ի, որպեսզի ցիկլոմատիկ+max-lines և կոգնիտիվ ելակետային մակարդակները մնան անկախ. առանձին `check:complexity` / `check:cognitive-complexity` հրամանները պահպանվում են տեղային `--update`-ի համար։ +- **`/api` հակահալյուցինացիոն ստուգում** — ✅ **ԿԱՏԱՐՎԱԾ Է** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`)՝ `src/app/api`-ի ֆայլային համակարգի մեկ գույքագրում, openapi-routes + docs-symbols-ը շարունակում են հաշվետվություն ներկայացնել անկախ. առանձին հրամանները պահպանվում են տեղային գործարկումների համար։ +- **`check:node-runtime`-ը գործարկվում է 11 առաջադրանքում** — ⚠️ **ցածր ROI։** Յուրաքանչյուրն առանձին runner է, իսկ ստուգումը տևում է <1վ. ընդհանուր խնայողությունը մոտավորապես 10վ է՝ էժան, յուրաքանչյուր առաջադրանքի համար առանձին պաշտպանությունը կորցնելու դիմաց։ Չարժե փոփոխությունների աղմուկը։ +- **`typecheck:noimplicit:core`-ը CI lint-ում** — ✅ **հեռացվել է lint առաջադրանքից** (խորհրդատվական էր՝ `continue-on-error`). արգելափակող տիպային մակերեսը `typecheck:core` + `check:type-coverage` է։ Տեղային սկրիպտը պահպանվել է։ + +### Փոխել ռեժիմը / որոշել (օպերատորի քաղաքականություն) + +- `check:openapi-security-tiers` (խորհրդատվական) — ❌ **Հնարավոր չէ մաքուր ձևով դարձնել արգելափակող։** Այն ավարտվում է 0 կոդով, սակայն զգուշացնում է, որ `LOCAL_ONLY_API_PREFIXES`-ի ներքո գտնվող մի քանի `traffic-inspector` երթուղիներ չունեն `x-loopback-only: true` նշագրումը։ Դրա պարտադրումը պահանջում է նախ այդ նշագրումներն ավելացնել `openapi.yaml`-ում։ +- `typecheck:noimplicit:core` (խորհրդատվական) — հիմնականում ներառված է արգելափակող `check:type-coverage` սահմանափակիչի մեջ։ Դարձնել սահմանափակիչ կամ հեռացնել կրկնվող երկրորդ `tsc` անցումը։ +- `test:vitest:ui` (այժմ **արգելափակող**) — նախկինից առկա ձախողումները բացահայտորեն բացառված են `vitest.config.ts`-ում՝ `// #8618` հետևման մեկնաբանություններով. նոր ձախողումները հանգեցնում են առաջադրանքի ձախողման։ +- `check:secrets` (gitleaks, արգելափակող սահմանափակիչ՝ սառեցված 3 փաստաթղթավորված կեղծ դրական արդյունքի վրա) — թույլատրելի ցանկում ներառել այդ 3-ը՝ 0-ի հասնելու համար, կամ իջեցնել մինչև խորհրդատվական։ Համընկնում է GitHub-ի ներկառուցված գաղտնիքների սկանավորման + `check:public-creds`-ի հետ։ +- `check:pr-evidence` (արգելափակող, grep-ով որոնում է PR-ի հիմնական տեքստում) — կեղծ դրական արդյունքների բարձր ռիսկ. հեռացնելու դեպքում թուլացնում է Hard Rule #18-ի պարտադրումը, ուստի սա քաղաքականության իրական որոշում է։ +- `semgrep` (խորհրդատվական առանձին աշխատանքային հոսք) — OWASP ընտանիքների մասով համընկնում է CodeQL-ի հետ. դրա ելակետային մակարդակը միացնել սահմանափակիչին կամ հեռացնել։ --- diff --git a/docs/i18n/hy/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/hy/docs/compression/COMPRESSION_GUIDE.md index 519af44123ae..b27f0f4ec632 100644 --- a/docs/i18n/hy/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/hy/docs/compression/COMPRESSION_GUIDE.md @@ -441,7 +441,7 @@ const { messages: aged, saved } = applyAging(messages, { `applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`) լուծում է ընտրությունը կատալոգի դեմ (անհայտ ID-ները և տեղայնացմանը չհամապատասխանող ոճերը բաց են թողնվում, երբեք սխալ չէ), միացնում է ընտրված հրահանգները կատալոգի կարգով, **մեկ անգամ** ավելացնում է սահմանների կետը և սկսում է բլոկը մեկ իդեմպոտենտության մարկերով (`[OmniRoute Output Styles]`), այնպես որ կրկնակի կիրառումը ոչինչ չի անում։ Երբ լուծված լեզուն (տես «Լեզվի ընտրություն» ստորև) ունի թարգմանություն, տեղայնացված հրահանգը ներարկվում է անգլերենի փոխարեն։ -Մարմնի վրա, որն ունի `messages`, բովանդակության շրջանցումը (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts`-ում) ստուգում է վերջին երեք հաղորդագրությունները և բաց է թողնում ոճերը ամբողջ շրջադարձի համար, երբ դրանք համապատասխանում են իր անվտանգության, անդառնալի գործողության, պարզաբանման կամ հերթականության նկատմամբ զգայուն հիմնաբառերին։ Շրջանցումն աշխատում է անկախ նրանից, թե ինչպես է սահմանված վահանակի **Auto-Clarity Bypass** անջատիչը (`cavemanOutputMode.autoClarity`)։ +Մարմնի վրա, որն ունի `messages`, բովանդակության շրջանցումը (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts`-ում) ստուգում է վերջին երեք հաղորդագրությունները և բաց է թողնում ոճերը ամբողջ շրջադարձի համար, երբ դրանք համապատասխանում են իր անվտանգության, անդառնալի գործողության, պարզաբանման կամ հերթականության նկատմամբ զգայուն հիմնաբառերին։ Շրջանցումն աշխատում է, երբ վահանակի **Auto-Clarity Bypass** անջատիչը (`cavemanOutputMode.autoClarity`) միացված է, ինչը լռելյայնն է. անջատիչն անջատված լինելիս ընտրված ոճերը կիրառվում են նաև այդ շրջադարձերի ժամանակ։ Երբ շրջանցումը թույլ է տալիս շրջադարձը, `placeSystemInstruction()` (նույն ֆայլը), որը երբեք նոր `messages[0]` չի ստեղծում, բլոկը տեղադրում է այս կետերից առաջինում, որը գտնում է. diff --git a/docs/i18n/hy/docs/reference/ENVIRONMENT.md b/docs/i18n/hy/docs/reference/ENVIRONMENT.md index b905a477f0e2..a607c297b929 100644 --- a/docs/i18n/hy/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/hy/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Պահանջվող գաղտնիքներ -Սրանք **պետք է** սահմանվեն առաջին գործարկումից առաջ։ Առանց դրանց հավելվածը կա՛մ կհրաժարվի մեկնարկել, կա՛մ կաշխատի ոչ անվտանգ լռելյայն արժեքներով։ - -| Փոփոխական | Պարտադիր | Լռելյայն | Սկզբնաղբյուր ֆայլ | Նկարագրություն | -| ---------------------------- | -------------------------------- | ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Այո** | _(չկա)_ | `src/lib/auth` | Ստորագրում/ստուգում է վահանակի աշխատաշրջանի բոլոր cookie-ները (JWT)։ Ստեղծեք `openssl rand -base64 48` հրամանով։ | -| `API_KEY_SECRET` | **Այո** | _(չկա)_ | `src/lib/db/apiKeys.ts` | AES գաղտնագրման բանալի՝ SQLite-ում պահվող API բանալիների արժեքների համար։ Ստեղծեք `openssl rand -hex 32` հրամանով։ | -| `INITIAL_PASSWORD` | **Այո** | `CHANGEME` | Նախնական կարգավորման սկրիպտ | Սահմանում է ադմինիստրատորի վահանակի սկզբնական գաղտնաբառը (համապատասխանում է `.env.example`-ի լռելյայն արժեքին. այն միտումնավոր ակնհայտորեն անապահով է պահվել՝ փոփոխությունը պարտադրելու համար)։ **Փոխեք առաջին օգտագործումից առաջ։** Մուտք գործելուց հետո փոխեք Dashboard → Settings → Security բաժնով։ | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Այո** (արտադրական միջավայրում) | _(սահմանված չէ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Ներքին Codex Responses WebSocket կամրջի ընդհանուր գաղտնիքը։ Նույնականացնում է Electron/դիտարկիչի WS վերահաղորդիչի և OmniRoute-ի միջև կամրջի հարցումները։ ⚠️ **ՊԱՐՏԱԴԻՐ Է արտադրական միջավայրում. երբ սահմանված չէ, WS կամրջի բոլոր հարցումները մերժվում են։** Ստեղծեք `openssl rand -base64 32` հրամանով։ | -| `OMNIROUTE_SW_BUILD_ID` | Ոչ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA թաղանթի համար ծառայողական աշխատողի քեշը չեղարկող բացահայտ նույնացուցիչ (#11779). լուծման շղթայում առաջինն է։ | -| `SOURCE_VERSION` | Ոչ | _(սահմանված չէ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Շղթայում երկրորդն է. սահմանվում է PaaS կառուցման համակարգերի կողմից (օրինակ՝ Heroku-ի ոճով)՝ որպես տեղակայված commit։ | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ոչ | _(ածանցյալ)_ | `src/shared/components/PwaRegister.tsx` | Կառուցման ժամանակ սահմանվող հանրային արժեք, որը հաճախորդն օգտագործում է `/sw.js?v=…`-ը գրանցելու համար. ածանցվում է վերը նշված երկուսից, ապա՝ git SHA-ից։ | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ոչ (ավտոմատ) | _(ավտոմատ՝ յուրաքանչյուր մեկնարկի ժամանակ)_ | `src/server/authz/policies/management.ts` | Յուրաքանչյուր գործընթացի համար առանձին գաղտնիք, որն ապացուցում է, որ վստահելի peer-IP դրոշմը ստացվել է OmniRoute-ի սեփական HTTP սերվերից (`scripts/dev/peer-stamp.mjs`)։ authz միջնաշերտը հարցման տեղայնությանը (LOCAL_ONLY երթուղիների loopback/LAN սահմանափակումներին) վստահում է միայն այն դեպքում, երբ դրոշմը պարունակում է այս տոկենը։ Յուրաքանչյուր մեկնարկի ժամանակ ավտոմատ ստեղծվում է. թողեք չսահմանված և ամրագրեք միայն բազմագործընթաց կարգավորումների համար, որոնք պետք է համատեղ օգտագործեն դրոշմը։ | -| `OMNIROUTE_TRUSTED_PROXIES` | Ոչ | _(սահմանված չէ)_ | `scripts/dev/peer-stamp.mjs` | Հակադարձ պրոքսիների՝ ստորակետերով բաժանված IP-ներ կամ CIDR տիրույթներ, որոնց `X-Forwarded-For` / `X-Real-IP` արժեքներին IP թույլատրման/արգելման ցանկը կարող է վստահել՝ ի լրումն loopback-ի, մասնավոր ցանցի հասցեների և Cloudflare-ի եզրային հանգույցների, որոնք միշտ վստահելի են։ Անհրաժեշտ է միայն որևէ այլ հանրային հասցեում գտնվող պրոքսիի համար. հակառակ դեպքում զտիչը գնահատում է հենց այդ պրոքսիի հասցեն։ | +**Այո** նշված, բայց սահմանված չեղած գաղտնիքները առաջին գործարկման ժամանակ ավտոմատ ստեղծվում են (`JWT_SECRET`, `API_KEY_SECRET`) և պահպանվում տվյալների պանակի `server.env` ֆայլում՝ սահմանեք դրանք հստակ, երբ այդ պանակը ժամանակավոր է։ Բացառությունն `OMNIROUTE_WS_BRIDGE_SECRET`-ն է. այն չի պահպանվում `server.env`-ում՝ սերվերի մուտքային կետերը յուրաքանչյուր մեկնարկի ժամանակ ստեղծում են ժամանակավոր արժեք, որը կարող է օգտագործել միայն գործընթացի ներսի կամուրջը, հետևաբար արտաքին կամրջի հաճախորդների համար այն պետք է սահմանվի հստակ։ + +| Փոփոխական | Պարտադիր | Լռելյայն | Սկզբնաղբյուր ֆայլ | Նկարագրություն | +| ---------------------------- | -------------------------------- | ------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Այո** | _(չկա)_ | `src/lib/auth` | Ստորագրում/ստուգում է վահանակի աշխատաշրջանի բոլոր cookie-ները (JWT)։ Ստեղծեք `openssl rand -base64 48` հրամանով։ | +| `API_KEY_SECRET` | **Այո** | _(չկա)_ | `src/lib/db/apiKeys.ts` | AES գաղտնագրման բանալի՝ SQLite-ում պահվող API բանալիների արժեքների համար։ Ստեղծեք `openssl rand -hex 32` հրամանով։ | +| `INITIAL_PASSWORD` | Ոչ | _(սահմանված չէ)_ | `src/lib/auth/managementPassword.ts` | Երբ դեռ ոչ մի գաղտնաբառ պահպանված չէ՝ սահմանում է վահանակի գաղտնաբառը մեկնարկի ժամանակ. երբ գաղտնաբառն արդեն պահպանված է, անտեսվում է։ npm-ով և սկզբնաղբյուրից տեղադրումները `.env.example`-ը պատճենում են `.env` անվան տակ, ուստի մեկնարկում են դրա հրապարակայնորեն հայտնի `CHANGEME` տեղապահով. փոխարինեք այն առաջին մեկնարկից առաջ կամ սահմանեք `INITIAL_PASSWORD=` (դատարկ) այդ `.env`-ում, որպեսզի գաղտնաբառը ստեղծվի առաջին այցելության ժամանակ կարգավորման օգնականում, ինչպես անում է չսահմանված արժեքը (Docker-ի պատկերի լռելյայնը)։ Պահպանված գաղտնաբառը փոխեք Dashboard → Settings → Security բաժնով կամ գործարկեք `omniroute reset-password` (սկզբնաղբյուրի աշխատապատճենում՝ `node bin/reset-password.mjs`)՝ `DATA_DIR`-ը սահմանելով սերվերի տվյալների պանակի վրա։ | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Այո** (արտադրական միջավայրում) | _(սահմանված չէ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Ներքին Codex Responses WebSocket կամրջի ընդհանուր գաղտնիքը։ Նույնականացնում է Electron/դիտարկիչի WS վերահաղորդիչի և OmniRoute-ի միջև կամրջի հարցումները։ ⚠️ **ՊԱՐՏԱԴԻՐ Է արտադրական միջավայրում. երբ սահմանված չէ, WS կամրջի բոլոր հարցումները մերժվում են։** Ստեղծեք `openssl rand -base64 32` հրամանով։ | +| `OMNIROUTE_SW_BUILD_ID` | Ոչ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA թաղանթի համար ծառայողական աշխատողի քեշը չեղարկող բացահայտ նույնացուցիչ (#11779). լուծման շղթայում առաջինն է։ | +| `SOURCE_VERSION` | Ոչ | _(սահմանված չէ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Շղթայում երկրորդն է. սահմանվում է PaaS կառուցման համակարգերի կողմից (օրինակ՝ Heroku-ի ոճով)՝ որպես տեղակայված commit։ | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ոչ | _(ածանցյալ)_ | `src/shared/components/PwaRegister.tsx` | Կառուցման ժամանակ սահմանվող հանրային արժեք, որը հաճախորդն օգտագործում է `/sw.js?v=…`-ը գրանցելու համար. ածանցվում է վերը նշված երկուսից, ապա՝ git SHA-ից։ | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ոչ (ավտոմատ) | _(ավտոմատ՝ յուրաքանչյուր մեկնարկի ժամանակ)_ | `src/server/authz/policies/management.ts` | Յուրաքանչյուր գործընթացի համար առանձին գաղտնիք, որն ապացուցում է, որ վստահելի peer-IP դրոշմը ստացվել է OmniRoute-ի սեփական HTTP սերվերից (`scripts/dev/peer-stamp.mjs`)։ authz միջնաշերտը հարցման տեղայնությանը (LOCAL_ONLY երթուղիների loopback/LAN սահմանափակումներին) վստահում է միայն այն դեպքում, երբ դրոշմը պարունակում է այս տոկենը։ Յուրաքանչյուր մեկնարկի ժամանակ ավտոմատ ստեղծվում է. թողեք չսահմանված և ամրագրեք միայն բազմագործընթաց կարգավորումների համար, որոնք պետք է համատեղ օգտագործեն դրոշմը։ | +| `OMNIROUTE_TRUSTED_PROXIES` | Ոչ | _(սահմանված չէ)_ | `scripts/dev/peer-stamp.mjs` | Հակադարձ պրոքսիների՝ ստորակետերով բաժանված IP-ներ կամ CIDR տիրույթներ, որոնց `X-Forwarded-For` / `X-Real-IP` արժեքներին IP թույլատրման/արգելման ցանկը կարող է վստահել՝ ի լրումն loopback-ի, մասնավոր ցանցի հասցեների և Cloudflare-ի եզրային հանգույցների, որոնք միշտ վստահելի են։ Անհրաժեշտ է միայն որևէ այլ հանրային հասցեում գտնվող պրոքսիի համար. հակառակ դեպքում զտիչը գնահատում է հենց այդ պրոքսիի հասցեն։ | ### Ստեղծման հրամաններ @@ -216,38 +216,38 @@ OmniRoute-ը բոլոր տվյալների պահպանման համար օգտ ## 4. Անվտանգություն և նույնականացում -| Փոփոխական | Լռելյայն արժեք | Աղբյուր ֆայլ | Նկարագրություն | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Մեքենայի թվային մատնահետքը ձևավորելու համար սարքաշարային նույնացուցիչների հետ համակցվող salt։ Մեկուսացման նպատակով փոխեք յուրաքանչյուր տեղակայման համար։ | -| `OMNIROUTE_CLI_SALT` | _(սահմանված չէ = յուրաքանչյուր տեղադրման համար պատահական salt, որը պահպանվում է `/cli-token-salt.json`-ում)_ | `src/lib/machineToken.ts` | Տեղային CLI նույնականացման թոքենը ստանալու համար օգտագործվող HMAC salt։ Այս արժեքը սահմանելը փոխարինում է մեքենայի բոլոր CLI թոքենները և միշտ գերակայում է պահպանված salt-ի նկատմամբ։ Տե՛ս `docs/security/CLI_TOKEN.md`։ | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Սեսիայի cookie-ների համար սահմանում է `Secure` դրոշը։ HTTPS-ի հետևում աշխատելիս **պետք է լինի `true`**։ | -| `REQUIRE_API_KEY` | `false` | API միջնաշերտ | Երբ `true` է, բոլոր `/v1/*` պրոքսի հարցումները պետք է ներառեն վավեր API բանալի։ **Այս դրոշը չի վերահսկում `GET /v1/models`-ը**, որն այդ փոխարեն հետևում է կառավարման վահանակի մուտքի քաղաքականությանը (`requireAuthForModels`), ուստի `/v1/models`-ից ստացված `401`-ը ՉԻ նշանակում, որ ինֆերենսը պաշտպանված է։ Տե՛ս `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695)։ | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Թույլ է տալիս կառավարման վահանակի միջերեսում ցուցադրել API բանալիների ամբողջական արժեքները։ Կարգավորվում է կառավարման վահանակի գործառույթների դրոշներից․ համօգտագործվող օրինակներում անվտանգության ռիսկ է ներկայացնում։ | -| `NO_LOG_API_KEY_IDS` | _(դատարկ)_ | `src/lib/compliance/index.ts` | Ստորակետերով առանձնացված API բանալիների ID-ներ, որոնց հարցումները չեն գրանցվում մատյանում (GDPR համապատասխանություն)։ | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(սահմանված չէ = անսահմանափակ)_ | `src/shared/utils/apiKeyPolicy.ts` | Օրական հարցումների պահուստային սահմանաչափ, որը կիրառվում է այն API բանալիների նկատմամբ, որոնց `rate_limits` սյունակը null է։ Սահմանված չէ կամ դատարկ է՝ անուղղակի սահմանափակում չկա (#2289, #11017)։ `0`-ն ունի նույն նշանակությունը (անսահմանափակ)։ Դրական ամբողջ N թիվը սահմանում է N/օր, 5N/շաբաթ, 20N/ամիս։ Սխալ ձևաչափով ոչ դատարկ արժեքների դեպքում կիրառվում են նախկին սահմանաչափերը՝ 1000/օր, 5000/շաբաթ, 20000/ամիս։ | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Հարցման մարմնի առավելագույն թույլատրելի չափը։ Այս սահմանաչափը գերազանցող օգտակար բեռները մերժվում են։ | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Այս շեմին հասնող կամ այն գերազանցող հարցման փաստացի մարմինները, նախքան JSON-ի վերլուծումը, ստանում են գործընթացի համար տեղային ծանրաբեռնված ընդունման ատոմային թույլտվություն (BYTE ուղի՝ ներառյալ `POST /v1/responses`)։ Կիրառվում է կառուցվածքով ծանր հարցումների համար նախատեսված նույն [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) առողջ ազատ հզորության բացառությունը, սակայն այն շարունակում է սահմանափակվել `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ով / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110)-ով, որպեսզի [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849)-ը չկրկնվի։ | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 ՄԲ) | `src/shared/middleware/chatBodyAdmission.ts` | Chat երթուղու կոշտ սահմանաչափ, որը կիրառվում է սահմանափակ ներմուծման ընթացքում կարդացված բայթերի նկատմամբ՝ ներառյալ բացակայող, անվավեր կամ խաբուսիկ `Content-Length` ունեցող հարցումները․ գերազանցման դեպքում վերադարձվում է `413`։ | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(սահմանված չէ՝ հարցումների քանակի սահմանաչափ չկա)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ի տարանցիկ տարածում:** հարցումների ՔԱՆԱԿԻ այս հնացած սահմանաչափն այժմ կիրառվում է միայն բացահայտ սահմանելու դեպքում։ Եթե սահմանված չէ (լռելյայն), ծանրակշիռ chat հարցումների ընդունումը փոխարենը վերահսկվում է `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ով՝ ԲԱՅԹԵՐԻ ավտոմատ հաշվարկվող բյուջեով, որի չափը որոշվում է **մեկ գործընթացում** (մեկ V8 heap) գործընթացի հիշողության իրական սահմանաչափից։ Երկու համընկնող, մոտ 750k-token ծավալով `/v1/responses` հարցումներ հանգեցնում են մոտ 12 ԳիԲ heap-երի վթարային ավարտի (#7849)․ սա **հիշողության բյուջեի** նախազգուշացում է, ոչ թե արտադրանքի՝ 2-ի կոշտ առավելագույն սահմանաչափ։ Առողջ գործընթացը (heap-ը՝ բեռնաթափման գործակցից ցածր) ԿԱՐՈՂ Է ընդունել ավելի շատ միաժամանակյա երկարատև `/v1/responses` հարցումներ՝ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`-ի միջոցով։ Տասնյակ երկարատև SSE հաճախորդների (40–50) սահմանը պայմանավորված է heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 համադրությամբ, ոչ թե «առավելագույնը 2»-ով։ Սա կուրորեն բարձրացնելը՝ «հոսթի ռեսուրսներն օգտագործելու» համար, կրկին առաջացնում է #7849-ը։ Բազմապատկեք **heap-երը** **N անկախ `DATA_DIR`-ներով** (#11024)․ երբեք մի օգտագործեք `replicas>1` մեկ SQLite ֆայլի համար։ | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ավտոմատ հաշվարկվող)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ի տարանցիկ տարածում:** ավտոմատ հաշվարկվող ներմուծման բայթային բյուջեի վերասահմանում (V8/cgroup հիշողության առավել խիստ սահմանաչափի 25%-ը՝ բաժանված անցողիկ 8x ուժեղացման վրա)։ Հաշվարկված և բացահայտ արժեքները սահմանափակվում են 8 ՄիԲ–2 ԳիԲ միջակայքով։ Արդյունավետ բյուջեից մեծ մարմինն անմիջապես մերժվում է `413 body_exceeds_budget`-ով․ առանձին-առանձին սպասարկելի մարմինների միջև ռեսուրսային մրցակցությունը շարունակում է վերադարձնել կրկին փորձելի `503`։ 40–50 միաժամանակյա երկարատև SSE հաճախորդների սահմանը պայմանավորված է այս բյուջեով + heap-ով, ոչ թե «առավելագույնը 2» կոշտ սահմանաչափով։ Կարգավորումից առաջ `/api/monitoring/health` հասցեում ստուգեք `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` արժեքները։ | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Heap-ի ճնշման դեպքում բեռնաթափման գործակիցը (`heapUsed / heap_size_limit`) ԲԱՅԹԱՅԻՆ և ԿԱՌՈՒՑՎԱԾՔԱՅԻՆ ծանրակշիռ հարցումների ընդունման համար (#10183, #10268, #10437)։ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ը գերազանցող միաժամանակյա ծանրակշիռ հարցումը կրկին փորձելի `503`-ով բեռնաթափվում է միայն այն դեպքում, երբ heap-ը ՆԱԵՎ այս գործակցին հավասար է կամ գերազանցում է այն․ առողջ heap-ի դեպքում հարցումն ընդունվում է առողջ վիճակի պահուստի միջոցով։ | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (լռելյայն՝ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Առողջ heap-ի արագ ուղու (#10437) սահմանափակ լրացուցիչ հզորություն՝ **թե՛** ԿԱՌՈՒՑՎԱԾՔԱՅԻՆ, **թե՛** ԲԱՅԹԱՅԻՆ (`admitChatRequest`, ներառյալ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` մարմինները) ընդունման համար։ Առանց այս սահմանափակման՝ զբաղված, սակայն առողջ heap ունեցող յուրաքանչյուր հարցում շրջանցում էր ընդունման մեխանիզմը՝ առանց վերին սահմանի։ Երբ առողջ heap-ի ուղով միաժամանակ ակտիվ վարձակալությունների քանակը հասնում է այս արժեքին, հետագա զբաղված հարցումներն անցնում են ՆՈՒՅՆ սահմանափակ սպասման/բեռնաթափման ուղով, որն օգտագործվում է heap-ի իրական ճնշման դեպքում։ `0`-ն ամբողջությամբ անջատում է շրջանցումը։ | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Հաղորդագրությունների քանակը, որի դեպքում chat հարցումը դասակարգվում է որպես ծանրակշիռ, նույնիսկ եթե դրա մարմինը բայթային շեմից փոքր է։ | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Գործիքների քանակը, որի դեպքում chat հարցումը դասակարգվում է որպես ծանրակշիռ, նույնիսկ եթե դրա մարմինը բայթային շեմից փոքր է։ | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Նշանաշարի չափի վրա հիմնված token-ների պահպանողական գնահատականը, որի դեպքում հարցումը դասակարգվում է որպես ծանրակշիռ․ սա ընդունման արժեքի մոտարկում է, ոչ թե մատակարարի վճարային tokenization-ը։ | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (անջատված) | `src/shared/middleware/chatBodyAdmission.ts` | Chat պատմության ընտրովի, բացահայտ միացվող սահմանաչափ։ Լռելյայն անջատված է․ հաղորդագրությունների քանակը տեղակայման քաղաքականություն է, ոչ թե հարցման համընդհանուր հատկություն, իսկ այստեղ սահմանափակելը մերժում է զրույցները վերջնական `413`-ով՝ նախքան սեղմման մշակաշարը կկարողանա դրանք սպասարկելի դարձնել։ Heap-ի աճը սահմանափակվում է `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ով և heap-ի ճնշման բեռնաթափմամբ։ Հիշողությամբ սահմանափակ տեղակայումներում, որոնց անհրաժեշտ է կոշտ վերին սահման, սահմանեք դրական արժեք․ գերազանցման դեպքում վերադարձվում է կառուցվածքային compact-required `413`։ | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 ՄԲ) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Հիշողության մեջ ամբողջությամբ բուֆերացվող՝ չհոսքային upstream պատասխանի կոշտ սահմանաչափը։ Այն գերազանցելու դեպքում upstream ընթերցիչը չեղարկվում է, և հարցումն արագորեն ձախողվում է՝ անսահմանափակ նշանաշարի աճը մինչև heap-ի սպառումը կանխելու համար։ | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Վերադաս պատասխանի վերնագրերից փոխանցվող բայթերի առավելագույն քանակը։ Երբ սահմանաչափը գերազանցվում է, ավելի ցածր առաջնահերթությամբ վերնագրերը (օրինակ՝ հատուկ `x-codex-*`, `x-oai-request-id`) հեռացվում են՝ հակադարձ պրոքսիների վերնագրերի տարածված սահմանաչափերի շրջանակում մնալու համար։ Սահմանեք ավելի բարձր արժեք՝ վերադասից ավելի շատ մետատվյալներ փոխանցելու համար՝ պատասխանի վերնագրերի ավելի մեծ չափի գնով։ | -| `CORS_ORIGIN` | _(սահմանված չէ)_ | `src/server/cors/origins.ts` | CORS-ի մեկ աղբյուրով հնացած թույլատրման ցանկ։ Նոր տեղակայումների համար նախընտրեք `CORS_ALLOWED_ORIGINS`-ը։ CORS-ը նախատեսված է միայն տարբեր աղբյուրներից աշխատող բրաուզերային API-հաճախորդների համար. նույնականացված կառավարման վահանակի գրանցումներն օգտագործում են նույն աղբյուրից հարցումներ և փոխարենը՝ աշխատաշրջանին կապված CSRF պաշտպանություն։ | -| `CORS_ALLOWED_ORIGINS` | _(սահմանված չէ)_ | `src/server/cors/origins.ts` | Ստորակետերով բաժանված CORS թույլատրման ցանկ։ Ընդհանուր նշան չի ուղարկվում, եթե `CORS_ALLOW_ALL=true`-ը հստակորեն կազմաձևված չէ։ | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Միայն մշակման համար նախատեսված շրջանցման միջոց՝ բրաուզերի ցանկացած `Origin` արտացոլելու համար։ Մի միացրեք համօգտագործվող կամ արտադրական տեղակայումներում։ | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Արգելափակել մասնավոր/հետադարձ կապի/link-local IP տիրույթներ թիրախավորող մատակարարի կանչերը։ Անջատեք միայն մեկուսացված փորձարկման միջավայրերում։ | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Թույլատրել մասնավոր/տեղական ցանցերին (localhost, 192.168.x.x, 10.x.x.x և այլն) մատնանշող մատակարարի URL-ներ։ **ՊԱՐՏԱԴԻՐ է ինքնուրույն հոսթավորված մատակարարների համար** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)։ Երբ `false` է, կառավարման վահանակը մերժում է տեղական URL-ների վավերացումը։ | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Թույլատրել տեղական/մասնավոր հասցեներում (127.0.0.1, localhost, LAN, մասնավոր տիրույթներ) մատակարարների ավելացումն ու վավերացումը՝ սահմանափակված մատակարարի վավերացման ուղով։ **Լռելյայն՝ `true`** (տեղականն առաջնահերթ է). սահմանեք `false`՝ միայն հանրային հասցեների խիստ արգելափակում կիրառելու համար։ Ամպային մետատվյալների վերջնակետերը (169.254.169.254, metadata.google.internal) մնում են արգելափակված՝ անկախ կարգավորումից։ (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Թույլատրել `/v1/audio/*` ուղիներին (վերծանումներ, խոսք, թարգմանություններ) օգտագործել localhost-ից դուրս հոսթավորված՝ OpenAI-ի հետ համատեղելի մատակարարի հանգույց։ Լռելյայն անջատված է. ձայնը հեռակա հոսթ ուղղորդելը փոխում է ելքային ինքնությունը և պետք է լինի օպերատորի հստակ որոշում։ Հետադարձ կապի/մասնավոր հանգույցները (localhost, 127.0.0.1, 172.16-31.x) միշտ թույլատրված են, և այս կարգավորումը դրանց վրա չի ազդում։ (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Թույլատրել `POST /v1/rerank`-ին (և հիշողության շարժիչի հետադարձ կապով վերադասավորման քայլին) օգտագործել localhost-ից դուրս հոսթավորված՝ OpenAI-ի հետ համատեղելի մատակարարի հանգույց՝ LAN սարք կամ Tailscale-ի գործընկեր, որտեղ աշխատում է TEI, Infinity, vLLM և այլն։ Լռելյայն անջատված է. հեռակա հոսթ ուղղորդելը փոխում է ելքային ինքնությունը և պետք է լինի օպերատորի հստակ որոշում։ Հետադարձ կապի հանգույցները (localhost, 127.0.0.1, 172.16-31.x) միշտ թույլատրված են, և այս կարգավորումը դրանց վրա չի ազդում։ Հեռակա հանգույցները պետք է նաև համապատասխանեն մատակարարի ելքային URL-ի քաղաքականությանը (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). ամպային մետատվյալների հոսթերին հարցումները երբեք չեն ուղղորդվում։ | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Երբ OIDC-ը միացված է, անջատել գաղտնաբառով մուտքը, որպեսզի օգտատերերը կարողանան նույնականացվել միայն OIDC միասնական մուտքի միջոցով։ Ընդունվում է նաև `OIDC_DISABLE_PASSWORD_LOGIN` պարզ կեղծանունը. նույն բանալին ունեցող կառավարման վահանակի գործառույթի դրոշը գերակա է։ (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-ի պարզ կեղծանունը (#10889)։ | +| Փոփոխական | Լռելյայն արժեք | Աղբյուր ֆայլ | Նկարագրություն | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Մեքենայի թվային մատնահետքը ձևավորելու համար սարքաշարային նույնացուցիչների հետ համակցվող salt։ Մեկուսացման նպատակով փոխեք յուրաքանչյուր տեղակայման համար։ | +| `OMNIROUTE_CLI_SALT` | _(սահմանված չէ = յուրաքանչյուր տեղադրման համար պատահական salt, որը պահպանվում է `/cli-token-salt.json`-ում)_ | `src/lib/machineToken.ts` | Տեղային CLI նույնականացման թոքենը ստանալու համար օգտագործվող HMAC salt։ Այս արժեքը սահմանելը փոխարինում է մեքենայի բոլոր CLI թոքենները և միշտ գերակայում է պահպանված salt-ի նկատմամբ։ Տե՛ս `docs/security/CLI_TOKEN.md`։ | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Սեսիայի cookie-ների համար սահմանում է `Secure` դրոշը։ HTTPS-ի հետևում աշխատելիս **պետք է լինի `true`**։ | +| `REQUIRE_API_KEY` | `false` | API միջնաշերտ | Երբ `true` է, բոլոր `/v1/*` պրոքսի հարցումները պետք է ներառեն վավեր API բանալի։ **Այս դրոշը չի վերահսկում `GET /v1/models`-ը**, որն այդ փոխարեն հետևում է կառավարման վահանակի մուտքի քաղաքականությանը (`requireAuthForModels`), ուստի `/v1/models`-ից ստացված `401`-ը ՉԻ նշանակում, որ ինֆերենսը պաշտպանված է։ Տե՛ս `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695)։ | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Թույլ է տալիս կառավարման վահանակի միջերեսում ցուցադրել API բանալիների ամբողջական արժեքները։ Կարգավորվում է կառավարման վահանակի գործառույթների դրոշներից․ համօգտագործվող օրինակներում անվտանգության ռիսկ է ներկայացնում։ | +| `NO_LOG_API_KEY_IDS` | _(դատարկ)_ | `src/lib/compliance/index.ts` | Ստորակետերով առանձնացված API բանալիների ID-ներ, որոնց հարցումները չեն գրանցվում մատյանում (GDPR համապատասխանություն)։ | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(սահմանված չէ = անսահմանափակ)_ | `src/shared/utils/apiKeyPolicy.ts` | Օրական հարցումների պահուստային սահմանաչափ, որը կիրառվում է այն API բանալիների նկատմամբ, որոնց `rate_limits` սյունակը null է։ Սահմանված չէ կամ դատարկ է՝ անուղղակի սահմանափակում չկա (#2289, #11017)։ `0`-ն ունի նույն նշանակությունը (անսահմանափակ)։ Դրական ամբողջ N թիվը սահմանում է N/օր, 5N/շաբաթ, 20N/ամիս։ Սխալ ձևաչափով ոչ դատարկ արժեքների դեպքում կիրառվում են նախկին սահմանաչափերը՝ 1000/օր, 5000/շաբաթ, 20000/ամիս։ | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Հարցման մարմնի առավելագույն թույլատրելի չափը։ Այս սահմանաչափը գերազանցող օգտակար բեռները մերժվում են։ | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Այս շեմին հասնող կամ այն գերազանցող հարցման փաստացի մարմինները, նախքան JSON-ի վերլուծումը, ստանում են գործընթացի համար տեղային ծանրաբեռնված ընդունման ատոմային թույլտվություն (BYTE ուղի՝ ներառյալ `POST /v1/responses`)։ Կիրառվում է կառուցվածքով ծանր հարցումների համար նախատեսված նույն [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) առողջ ազատ հզորության բացառությունը, սակայն այն շարունակում է սահմանափակվել `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ով / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110)-ով, որպեսզի [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849)-ը չկրկնվի։ | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 ՄԲ) | `src/shared/middleware/chatBodyAdmission.ts` | Chat երթուղու կոշտ սահմանաչափ, որը կիրառվում է սահմանափակ ներմուծման ընթացքում կարդացված բայթերի նկատմամբ՝ ներառյալ բացակայող, անվավեր կամ խաբուսիկ `Content-Length` ունեցող հարցումները․ գերազանցման դեպքում վերադարձվում է `413`։ | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(սահմանված չէ՝ հարցումների քանակի սահմանաչափ չկա)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ի տարանցիկ տարածում:** հարցումների ՔԱՆԱԿԻ այս հնացած սահմանաչափն այժմ կիրառվում է միայն բացահայտ սահմանելու դեպքում։ Եթե սահմանված չէ (լռելյայն), ծանրակշիռ chat հարցումների ընդունումը փոխարենը վերահսկվում է `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ով՝ ԲԱՅԹԵՐԻ ավտոմատ հաշվարկվող բյուջեով, որի չափը որոշվում է **մեկ գործընթացում** (մեկ V8 heap) գործընթացի հիշողության իրական սահմանաչափից։ Երկու համընկնող, մոտ 750k-token ծավալով `/v1/responses` հարցումներ հանգեցնում են մոտ 12 ԳիԲ heap-երի վթարային ավարտի (#7849)․ սա **հիշողության բյուջեի** նախազգուշացում է, ոչ թե արտադրանքի՝ 2-ի կոշտ առավելագույն սահմանաչափ։ Առողջ գործընթացը (heap-ը՝ բեռնաթափման գործակցից ցածր) ԿԱՐՈՂ Է ընդունել ավելի շատ միաժամանակյա երկարատև `/v1/responses` հարցումներ՝ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`-ի միջոցով։ Տասնյակ երկարատև SSE հաճախորդների (40–50) սահմանը պայմանավորված է heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 համադրությամբ, ոչ թե «առավելագույնը 2»-ով։ Սա կուրորեն բարձրացնելը՝ «հոսթի ռեսուրսներն օգտագործելու» համար, կրկին առաջացնում է #7849-ը։ Բազմապատկեք **heap-երը** **N անկախ `DATA_DIR`-ներով** (#11024)․ երբեք մի օգտագործեք `replicas>1` մեկ SQLite ֆայլի համար։ | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ավտոմատ հաշվարկվող)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ի տարանցիկ տարածում:** ավտոմատ հաշվարկվող ներմուծման բայթային բյուջեի վերասահմանում (V8/cgroup հիշողության առավել խիստ սահմանաչափի 25%-ը՝ բաժանված անցողիկ 8x ուժեղացման վրա)։ Հաշվարկված և բացահայտ արժեքները սահմանափակվում են 8 ՄիԲ–2 ԳիԲ միջակայքով։ Արդյունավետ բյուջեից մեծ մարմինն անմիջապես մերժվում է `413 body_exceeds_budget`-ով․ առանձին-առանձին սպասարկելի մարմինների միջև ռեսուրսային մրցակցությունը շարունակում է վերադարձնել կրկին փորձելի `503`։ 40–50 միաժամանակյա երկարատև SSE հաճախորդների սահմանը պայմանավորված է այս բյուջեով + heap-ով, ոչ թե «առավելագույնը 2» կոշտ սահմանաչափով։ Կարգավորումից առաջ `/api/monitoring/health` հասցեում ստուգեք `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` արժեքները։ | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Heap-ի ճնշման դեպքում բեռնաթափման գործակիցը (`heapUsed / heap_size_limit`) ԲԱՅԹԱՅԻՆ և ԿԱՌՈՒՑՎԱԾՔԱՅԻՆ ծանրակշիռ հարցումների ընդունման համար (#10183, #10268, #10437)։ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ը գերազանցող միաժամանակյա ծանրակշիռ հարցումը կրկին փորձելի `503`-ով բեռնաթափվում է միայն այն դեպքում, երբ heap-ը ՆԱԵՎ այս գործակցին հավասար է կամ գերազանցում է այն․ առողջ heap-ի դեպքում հարցումն ընդունվում է առողջ վիճակի պահուստի միջոցով։ | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (լռելյայն՝ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Առողջ heap-ի արագ ուղու (#10437) սահմանափակ լրացուցիչ հզորություն՝ **թե՛** ԿԱՌՈՒՑՎԱԾՔԱՅԻՆ, **թե՛** ԲԱՅԹԱՅԻՆ (`admitChatRequest`, ներառյալ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` մարմինները) ընդունման համար։ Առանց այս սահմանափակման՝ զբաղված, սակայն առողջ heap ունեցող յուրաքանչյուր հարցում շրջանցում էր ընդունման մեխանիզմը՝ առանց վերին սահմանի։ Երբ առողջ heap-ի ուղով միաժամանակ ակտիվ վարձակալությունների քանակը հասնում է այս արժեքին, հետագա զբաղված հարցումներն անցնում են ՆՈՒՅՆ սահմանափակ սպասման/բեռնաթափման ուղով, որն օգտագործվում է heap-ի իրական ճնշման դեպքում։ `0`-ն ամբողջությամբ անջատում է շրջանցումը։ | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Հաղորդագրությունների քանակը, որի դեպքում chat հարցումը դասակարգվում է որպես ծանրակշիռ, նույնիսկ եթե դրա մարմինը բայթային շեմից փոքր է։ | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Գործիքների քանակը, որի դեպքում chat հարցումը դասակարգվում է որպես ծանրակշիռ, նույնիսկ եթե դրա մարմինը բայթային շեմից փոքր է։ | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Նշանաշարի չափի վրա հիմնված token-ների պահպանողական գնահատականը, որի դեպքում հարցումը դասակարգվում է որպես ծանրակշիռ․ սա ընդունման արժեքի մոտարկում է, ոչ թե մատակարարի վճարային tokenization-ը։ | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (անջատված) | `src/shared/middleware/chatBodyAdmission.ts` | Chat պատմության ընտրովի, բացահայտ միացվող սահմանաչափ։ Լռելյայն անջատված է․ հաղորդագրությունների քանակը տեղակայման քաղաքականություն է, ոչ թե հարցման համընդհանուր հատկություն, իսկ այստեղ սահմանափակելը մերժում է զրույցները վերջնական `413`-ով՝ նախքան սեղմման մշակաշարը կկարողանա դրանք սպասարկելի դարձնել։ Heap-ի աճը սահմանափակվում է `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ով և heap-ի ճնշման բեռնաթափմամբ։ Հիշողությամբ սահմանափակ տեղակայումներում, որոնց անհրաժեշտ է կոշտ վերին սահման, սահմանեք դրական արժեք․ գերազանցման դեպքում վերադարձվում է կառուցվածքային compact-required `413`։ | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 ՄԲ) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Հիշողության մեջ ամբողջությամբ բուֆերացվող՝ չհոսքային upstream պատասխանի կոշտ սահմանաչափը։ Այն գերազանցելու դեպքում upstream ընթերցիչը չեղարկվում է, և հարցումն արագորեն ձախողվում է՝ անսահմանափակ նշանաշարի աճը մինչև heap-ի սպառումը կանխելու համար։ | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Վերադաս պատասխանի վերնագրերից փոխանցվող բայթերի առավելագույն քանակը։ Երբ սահմանաչափը գերազանցվում է, ավելի ցածր առաջնահերթությամբ վերնագրերը (օրինակ՝ հատուկ `x-codex-*`, `x-oai-request-id`) հեռացվում են՝ հակադարձ պրոքսիների վերնագրերի տարածված սահմանաչափերի շրջանակում մնալու համար։ Սահմանեք ավելի բարձր արժեք՝ վերադասից ավելի շատ մետատվյալներ փոխանցելու համար՝ պատասխանի վերնագրերի ավելի մեծ չափի գնով։ | +| `CORS_ORIGIN` | _(սահմանված չէ)_ | `src/server/cors/origins.ts` | CORS-ի մեկ աղբյուրով հնացած թույլատրման ցանկ։ Նոր տեղակայումների համար նախընտրեք `CORS_ALLOWED_ORIGINS`-ը։ CORS-ը նախատեսված է միայն տարբեր աղբյուրներից աշխատող բրաուզերային API-հաճախորդների համար. նույնականացված կառավարման վահանակի գրանցումներն օգտագործում են նույն աղբյուրից հարցումներ և փոխարենը՝ աշխատաշրջանին կապված CSRF պաշտպանություն։ | +| `CORS_ALLOWED_ORIGINS` | _(սահմանված չէ)_ | `src/server/cors/origins.ts` | Ստորակետերով բաժանված CORS թույլատրման ցանկ։ Ընդհանուր նշան չի ուղարկվում, եթե `CORS_ALLOW_ALL=true`-ը հստակորեն կազմաձևված չէ։ | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Միայն մշակման համար նախատեսված շրջանցման միջոց՝ բրաուզերի ցանկացած `Origin` արտացոլելու համար։ Մի միացրեք համօգտագործվող կամ արտադրական տեղակայումներում։ | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ժառանգված այլանուն է։ Դրա կառավարման վահանակի փոխարկիչը (DB-ի վերագրավորում) ընթերցվում է այս միջավայրային արժեքից առաջ. `false`, `0`, `no` կամ `off` արժեքը ցանկացած մեկում անջատում է հոսթի ստուգումները՝ այնպես, ինչպես դա անում է `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`-ն, նույնիսկ երբ այդ դրոշակի վահանակի փոխարկիչը պահպանված է անջատված. մյուս արժեքները պաշտպանությունը թողնում են ստորև նշված երկու դրոշակների հայեցողությանը։ | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Անջատում է ելքային URL-ի պաշտպանության հոսթի ստուգումները՝ ներառյալ ամպային մետատվյալների արգելափակումը (ամբողջ 169.254.0.0/16-ը, metadata.google.internal-ը)՝ այն մատակարարային ուղիներում, որոնք հետևում են պաշտպանության ռեժիմին՝ URL-ի վավերացում, մոդելների հայտնաբերում, մատակարարի հանգույցների հիմնական URL-ներ (ներառյալ հեռակա վերադասավորման հանգույցները) և proxy-fallback թեստը, ինչպես նաև այլ մատակարարին մոտ ելքային ուղիներում, օրինակ՝ հեռակա պատկերի/մեդիայի ստացումը։ Թույլատրում է նաև մասնավոր webhook թիրախները՝ բացի ամպային մետատվյալների/link-local վերջնակետերից, որոնք մնում են արգելափակված նույնիսկ այս դրոշակը միացված ժամանակ։ `BaseExecutor.execute()`-ով ուղարկվող չատի հարցումները պահպանում են իրենց ամպային մետատվյալների ստուգումը։ Տեղական մատակարարները գործում են լռելյայն արժեքներով. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS`-ը (լռելյայն՝ `true`) արդեն թույլատրում է տեղական և LAN URL-ները վավերացման, հայտնաբերման և մատակարարի հանգույցների ուղիներում. proxy-fallback թեստը և մասնավոր webhook թիրախները նայում են միայն այս դրոշակին ու արգելափակում են տեղական/LAN հոսթերը, քանի դեռ այն անջատված է։ Վերագրավում է `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` արժեքը։ Կառավարման վահանակի փոխարկիչից պահպանված արժեքն առաջնահերթություն ունի այս փոփոխականի նկատմամբ։ | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Թույլատրում է մատակարարի URL-ները տեղական/մասնավոր հասցեներում (127.0.0.1, localhost, LAN, մասնավոր տիրույթներ)՝ մատակարարի վավերացման, մոդելների հայտնաբերման, չատի և պաշտպանության ռեժիմի կառավարած մյուս մատակարարին մոտ ելքային ուղիների համար։ **Լռելյայն՝ `true`** (տեղականն առաջնահերթ է). այդ դեպքում պաշտպանությունն արգելափակում է ամպային մետատվյալների վերջնակետերը՝ ամբողջ 169.254.0.0/16-ը գումարած հայտնի մետատվյալային հոսթերի անունները (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254)։ Սահմանեք `false`՝ միայն հանրային ռեժիմի համար, որն արգելափակում է նաև մասնավոր և հետադարձ կապի հոսթերը։ Պաշտպանությունը ստուգում է հոսթի անունը կամ IP-ի բառացի արժեքն այնպես, ինչպես գրված է՝ ցանկացած DNS որոնումից առաջ. միայն հանրային ռեժիմում safe-fetch փաթեթավորիչը լրացուցիչ լուծում է հոսթի անունը և մերժում ցանկացած մասնավոր պատասխան։ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`-ն անջատում է այս ստուգումները իր տողում թվարկված ուղիներում։ Ներկառուցված տեղական մատակարարները, ինչպիսիք են LM Studio-ն, Ollama-ն, vLLM-ը, Llamafile-ը և Triton-ը, բաց են թողնում բոլոր հոսթի ստուգումները բանալիի վավերացման և չատի համար։ Կառավարման վահանակի փոխարկիչից պահպանված արժեքն առաջնահերթություն ունի այս փոփոխականի նկատմամբ։ (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Թույլատրել `/v1/audio/*` ուղիներին (վերծանումներ, խոսք, թարգմանություններ) օգտագործել localhost-ից դուրս հոսթավորված՝ OpenAI-ի հետ համատեղելի մատակարարի հանգույց։ Լռելյայն անջատված է. ձայնը հեռակա հոսթ ուղղորդելը փոխում է ելքային ինքնությունը և պետք է լինի օպերատորի հստակ որոշում։ Հետադարձ կապի/մասնավոր հանգույցները (localhost, 127.0.0.1, 172.16-31.x) միշտ թույլատրված են, և այս կարգավորումը դրանց վրա չի ազդում։ (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Թույլատրել `POST /v1/rerank`-ին (և հիշողության շարժիչի հետադարձ կապով վերադասավորման քայլին) օգտագործել localhost-ից դուրս հոսթավորված՝ OpenAI-ի հետ համատեղելի մատակարարի հանգույց՝ LAN սարք կամ Tailscale-ի գործընկեր, որտեղ աշխատում է TEI, Infinity, vLLM և այլն։ Լռելյայն անջատված է. հեռակա հոսթ ուղղորդելը փոխում է ելքային ինքնությունը և պետք է լինի օպերատորի հստակ որոշում։ Հետադարձ կապի հանգույցները (localhost, 127.0.0.1, 172.16-31.x) միշտ թույլատրված են, և այս կարգավորումը դրանց վրա չի ազդում։ Հեռակա հանգույցի հիմնական (base) URL-ն էլ պետք է անցնի մատակարարի ելքային URL-ի քաղաքականության ստուգումը (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)։ | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Երբ OIDC-ը միացված է, անջատել գաղտնաբառով մուտքը, որպեսզի օգտատերերը կարողանան նույնականացվել միայն OIDC միասնական մուտքի միջոցով։ Ընդունվում է նաև `OIDC_DISABLE_PASSWORD_LOGIN` պարզ կեղծանունը. նույն բանալին ունեցող կառավարման վահանակի գործառույթի դրոշը գերակա է։ (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-ի պարզ կեղծանունը (#10889)։ | ### Անվտանգության ամրապնդման ստուգացանկ diff --git a/docs/i18n/id/CONTRIBUTING.md b/docs/i18n/id/CONTRIBUTING.md index 68182d01d485..f21271275f01 100644 --- a/docs/i18n/id/CONTRIBUTING.md +++ b/docs/i18n/id/CONTRIBUTING.md @@ -405,16 +405,16 @@ Tulis pengujian unit di `tests/unit/` yang sekurang-kurangnya mencakup: - [ ] Linting lulus (`npm run lint`) - [ ] Build berhasil (`npm run build`) - [ ] Tipe TypeScript ditambahkan untuk fungsi dan antarmuka publik baru -- [ ] Tidak ada secret atau nilai fallback yang di-hardcode -- [ ] Kredensial upstream publik disematkan melalui `resolvePublicCred()` (lihat [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), jangan pernah sebagai literal -- [ ] Respons kesalahan diproses melalui `buildErrorBody()` / `sanitizeErrorMessage()` — tidak ada stack trace mentah dalam body respons (lihat [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Tidak ada rahasia atau nilai fallback yang di-hardcode +- [ ] Kredensial upstream publik disematkan melalui `resolvePublicCred()` (lihat [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), tidak pernah sebagai literal +- [ ] Respons kesalahan diproses melalui `buildErrorBody()` / `sanitizeErrorMessage()` — tidak ada stack trace mentah dalam isi respons (lihat [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Perintah shell (`exec` / `spawn`) meneruskan nilai runtime melalui `env`, bukan melalui interpolasi string - [ ] Semua input divalidasi dengan skema Zod -- [ ] **Fragmen** changelog ditambahkan di bawah `changelog.d/{features|fixes|maintenance}/-.md` untuk perubahan yang terlihat oleh pengguna (lihat [`changelog.d/README.md`](./changelog.d/README.md)) — **jangan** mengedit `CHANGELOG.md` secara langsung; fragmen digabungkan saat rilis dan tidak pernah menimbulkan konflik antar-PR +- [ ] **Fragmen** changelog ditambahkan di bawah `changelog.d/{features|fixes|maintenance}/-.md` untuk perubahan yang terlihat oleh pengguna (lihat [`changelog.d/README.md`](./changelog.d/README.md)) — **jangan** mengedit `CHANGELOG.md` secara langsung; fragmen diagregasikan saat rilis dan tidak pernah berkonflik antar-PR - [ ] Dokumentasi diperbarui (jika berlaku) -- [ ] Tidak ada peringatan CodeQL / Secret-Scanning baru yang dibuka, atau setiap peringatan telah ditutup dengan justifikasi teknis yang merujuk pada dokumen `docs/security/` yang relevan -- [ ] Route yang menjalankan proses anak (`/api/mcp/`, `/api/cli-tools/runtime/`) diklasifikasikan sebagai `isLocalOnlyPath()` di `src/server/authz/routeGuard.ts` — lihat [Aturan Ketat #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Tidak ada trailer `Co-Authored-By` dalam pesan commit — commit harus ditampilkan hanya di bawah identitas Git pemilik repositori (Aturan Ketat #16) +- [ ] Tidak ada peringatan CodeQL / Secret-Scanning baru yang dibuka, atau setiap peringatan ditolak dengan justifikasi teknis yang merujuk pada dokumen `docs/security/` yang relevan +- [ ] Rute yang menjalankan proses turunan (`/api/mcp/`, `/api/cli-tools/runtime/`) diklasifikasikan sebagai `isLocalOnlyPath()` dalam `src/server/authz/routeGuard.ts` — lihat [Aturan Ketat #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Tidak ada trailer `Co-authored-by` dari AI/bot dalam pesan commit (Aturan Ketat #16) — kolaborator manusia yang karyanya digunakan kembali diberi kredit dengan trailer standar `Co-authored-by: Name ` --- diff --git a/docs/i18n/id/docs/architecture/QUALITY_GATES.md b/docs/i18n/id/docs/architecture/QUALITY_GATES.md index dea9f728e324..0a031899d6ab 100644 --- a/docs/i18n/id/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/id/docs/architecture/QUALITY_GATES.md @@ -19,47 +19,48 @@ yang tidak bergantung pada alat untuk sistem yang sama, lihat ### Penerimaan kandidat -Workflow CI dan Quality Gates masing-masing menghasilkan putusan stabil: `Gate / CI` dan -`Gate / Quality`. Kebijakan penerimaan berversi miliknya mencantumkan setiap job hulu -sebagai wajib atau advisori. Job wajib yang berlaku harus berhasil: hasil yang hilang, +Workflow CI dan Quality Gates masing-masing menghasilkan putusan yang stabil: `Gate / CI` dan +`Gate / Quality`. Kebijakan penerimaan berversi milik keduanya mencantumkan setiap job upstream +sebagai wajib atau advisori. Job wajib yang berlaku harus berhasil: hasil yang tidak ada, dibatalkan, dilewati, tertunda, dan tidak diketahui tidak dapat menetapkan PASS. Klasifikasi -khusus dokumentasi atau khusus katalog yang valid dapat membuat lane kode tidak berlaku; +docs-only atau catalog-only yang valid dapat membuat lane kode tidak berlaku; PR draf bukan kandidat yang diterima. Label `hotfix` tidak menggugurkan persyaratan bukti. Kedua workflow mencakup PR dan push ke branch main/release, dispatch manual, serta -peristiwa merge-group. Push, dispatch, dan merge-group menjalankan pilihan lengkap. Fork -dan merge group menggunakan hosted runner untuk job yang biasanya memilih self-hosted -runner; kapasitas hosted yang memadai harus diverifikasi sebelum peluncuran. - -Setiap bukti JSON mengidentifikasi SHA yang di-checkout, workflow run, dan attempt. -CLI menolak ketidakcocokan SHA checkout/peristiwa. Pengujian workflow mengikat keanggotaan -kebijakan ke daftar `needs` milik job putusan sehingga lane baru atau yang dihapus tidak -dapat menghilang tanpa terdeteksi. Bukti tersebut mencakup workflow-nya sendiri, bukan -publikasi, deployment, atau bagian internal dari pemindai advisori yang sudah ada. -Mengaktifkan kedua nama pemeriksaan dalam aturan branch merupakan perubahan administratif -terpisah; menambahkan job ini tidak serta-merta melindungi branch. +event merge-group. Push, dispatch, dan merge-group menjalankan seluruh pilihan. Fork +dan merge group menggunakan runner terkelola untuk job yang jika tidak demikian akan +memilih runner self-hosted; kapasitas runner terkelola yang memadai harus diverifikasi +sebelum peluncuran. + +Setiap tanda terima JSON mengidentifikasi SHA yang di-checkout, workflow run, dan attempt. +CLI menolak ketidakcocokan SHA checkout/event. Pengujian workflow mengikat keanggotaan kebijakan +ke daftar `needs` milik job putusan sehingga lane baru atau yang dihapus tidak dapat menghilang +secara diam-diam. Tanda terima tersebut mencakup workflow-nya sendiri, bukan publikasi, +deployment, atau aspek internal dari scanner advisori yang sudah ada. Mengaktifkan kedua nama +check dalam aturan branch merupakan perubahan administratif terpisah; penambahan job ini +tidak secara otomatis melindungi branch. ### Inventaris pemindaian statis Inventaris npm-alias berversi dan keanggotaan pemindaian statis berada di `config/quality/gate-manifest.json`. Jalankan `npm run check:gate-manifest` untuk memvalidasi -nama skrip dan perintah persis terhadap `package.json`; penambahan, penghapusan, dan -perubahan perintah menyebabkan kegagalan pada hook lokal maupun job klasifikasi perubahan -di CI. Alias bukanlah job workflow, instance matriks, atau kasus pengujian: jumlah ini -tidak boleh disajikan sebagai hal yang dapat dipertukarkan. +nama skrip dan perintah yang tepat terhadap `package.json`; penambahan, penghapusan, dan +perubahan perintah akan menggagalkan hook lokal serta job klasifikasi perubahan di CI. +Alias bukanlah job workflow, instance matriks, atau test case: jumlah ini tidak boleh +disajikan seolah-olah dapat saling dipertukarkan. Gunakan `npm run quality:scan -- --list` atau `npm run quality:scan:fast -- --list` -untuk memeriksa alias yang dipilih tanpa mengeksekusinya. Runner memanggil entrypoint -npm, sehingga runtime-nya (termasuk Bun jika dikonfigurasi) tetap dipertahankan. -Manifes mencatat alias di luar profil tersebut sebagai dipanggil secara terpisah, dan -perintah pemeliharaan dilarang dalam profil pemindaian hanya-baca. +untuk memeriksa alias yang dipilih tanpa mengeksekusinya. Runner memanggil +entrypoint npm sehingga runtime-nya (termasuk Bun jika dikonfigurasi) tetap dipertahankan. +Manifest mencatat alias di luar profil tersebut sebagai alias yang dipanggil secara terpisah, +dan perintah pemeliharaan dilarang dalam profil pemindaian read-only. -Profil ini hanya mencakup pemindaian statis. Profil ini tidak menyertifikasi pengujian -produk, cakupan, pengemasan, pemeriksaan eksternal, atau penerimaan rilis lengkap kandidat. +Profil ini hanya mencakup pemindaian statis. Profil ini tidak menyertifikasi pengujian produk, +coverage, packaging, check eksternal, atau penerimaan rilis penuh suatu kandidat. Penerimaan workflow menggunakan `config/quality/admission-policy.json` yang ditautkan dan `scripts/quality/admission-verdict.mjs`. Profil release-observer tetap terpisah; -periksa pemeriksaan dan bukti yang berlaku secara independen. Inventaris naratif -di bawah ini merupakan referensi, bukan bukti bahwa gate benar-benar dijalankan. +periksa check dan tanda terima yang berlaku secara independen. Inventaris naratif +di bawah ini merupakan referensi, bukan bukti bahwa suatu gate benar-benar dijalankan. Skrip berada di bawah `scripts/check/` (gate kebijakan) dan `scripts/quality/` (mesin ratchet). Sumber kebenaran CI adalah `.github/workflows/ci.yml`. @@ -67,101 +68,101 @@ Sumber kebenaran CI adalah `.github/workflows/ci.yml`. ### Jalur cepat PR rilis (`quality.yml`) `.github/workflows/quality.yml` melengkapi CI pada PR main/release, push ke branch -terlindungi, dispatch, dan merge group. PR menggunakan pemeriksaan cepat yang difilter -berdasarkan path. Build duplikat yang dinonaktifkan secara permanen telah dihapus; -pemeriksaan build/package/boot yang sebenarnya tetap berada di CI. - -| Job | Cakupan | Memblokir | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `Docs Gates (fast-path)` | PR dokumentasi/kode; referensi dokumentasi API dan seluruh dokumentasi | Ya | -| `Fast Quality Gates` | PR kode; pemeriksaan statis, pemeriksaan tipe, pemeriksaan tipe dasbor, pengujian unit terdampak | Ya | -| `Forgotten sibling tests` | PR kode; modul yang diubah ditelusuri ke konsumen statis dan kandidat pengujian saudara; path barrel dan impor dinamis dilaporkan sebagai diagnostik advisori, dengan pengecualian allowlist yang dirujuk | **Advisori** | -| `Vitest (fast-path)` | PR kode; rangkaian vitest cepat | Ya | -| `Unit Tests fast-path` | PR kode; rangkaian unit 4-shard | Ya | -| `No new ESLint warnings` | PR kode; pelindung lint yang menyadari supresi | Ya, termasuk fork | -| `Merge integrity (changelog + generated skills)` | PR non-draf; sinkronisasi changelog dan skill yang dihasilkan | Ya, termasuk fork | - -#### Laporan pengujian saudara yang terlupakan +yang dilindungi, dispatch, dan merge group. PR menggunakan check cepat yang difilter berdasarkan +path. Build duplikat yang dinonaktifkan secara permanen telah dihapus; check build/package/boot +yang sebenarnya tetap berada di CI. + +| Job | Cakupan | Memblokir | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `Docs Gates (fast-path)` | PR docs/kode; referensi docs API dan docs-all | Ya | +| `Fast Quality Gates` | PR kode; check statis, typecheck, typecheck dashboard, unit test terdampak | Ya | +| `Forgotten sibling tests` | PR kode; modul yang diubah ditelusuri ke konsumen statis dan kandidat sibling test; path barrel dan dynamic-import dilaporkan sebagai diagnostik advisori, dengan pengecualian allowlist yang dirujuk | **Advisori** | +| `Vitest (fast-path)` | PR kode; rangkaian vitest cepat | Ya | +| `Unit Tests fast-path` | PR kode; rangkaian unit 4-shard | Ya | +| `No new ESLint warnings` | PR kode; guard lint yang mempertimbangkan suppression | Ya, termasuk fork | +| `Merge integrity (changelog + generated skills)` | PR non-draf; sinkronisasi changelog dan skill yang dihasilkan | Ya, termasuk fork | + +#### Laporan forgotten sibling tests `npm run check:forgotten-sibling-tests` menggunakan kembali resolver impor di balik peta dampak pengujian. Untuk setiap modul produksi yang diubah, perintah ini melaporkan rantai deterministik -`changed module/symbol -> static consumer -> candidate sibling test` ketika pengujian kandidat +`changed module/symbol -> static consumer -> candidate sibling test` ketika test kandidat tidak ada dalam diff pull request. Ringkasan Markdown dan hasil JSON disimpan sebagai -artefak workflow `forgotten-sibling-tests` untuk kalibrasi sebelum peluncuran apa pun yang memblokir. +artefak workflow `forgotten-sibling-tests` untuk kalibrasi sebelum peluncuran yang bersifat memblokir. Ekspor ulang barrel dan impor dinamis hanya merupakan diagnostik resolusi; keduanya tidak pernah menghasilkan temuan yang memblokir. Pengecualian yang telah ditinjau berada di -`config/quality/forgotten-sibling-allowlist.json`. Setiap entri harus mencantumkan konsumen dan pengujian +`config/quality/forgotten-sibling-allowlist.json`. Setiap entri harus menyebutkan pengonsumsi dan pengujian kandidat, memberikan alasan yang spesifik, serta menautkan issue atau pull request GitHub. Entri yang tidak valid -akan gagal secara tertutup. Pengecualian tidak dapat menyembunyikan pengujian kandidat yang dihapus atau diff yang menambahkan `.skip`/`.todo`; -pelemahan assertion dan penyamaran lainnya tetap menjadi tanggung jawab gate +akan ditolak secara default. Pengecualian tidak dapat menyembunyikan pengujian kandidat yang dihapus atau diff yang menambahkan `.skip`/`.todo`; +pelemahan asersi dan penyamaran lainnya tetap menjadi tanggung jawab gate `check:test-masking` yang memblokir secara independen. ### Job: `lint` Berjalan pada setiap PR ke `main`. Memblokir penggabungan jika gagal. -| Skrip (`npm run ...`) | Memvalidasi | Memblokir | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | -| `check:node-runtime` | Versi Node.js berada dalam rentang yang didukung | Ya | -| `check:cycles` | Impor melingkar — semua modul `src/` + `open-sse/` | Ya | -| `check:route-validation:t06` | Skema Zod tersedia pada semua rute (kebijakan Tier 6) | Ya | -| `check:any-budget:t11` | Jumlah `@ts-expect-error // any` tidak melebihi anggaran (catraca Tier 11) | Ya | -| `check:provider-consistency` | Setiap penyedia di `providers.ts` memiliki entri yang cocok di `providerRegistry.ts` (dan sebaliknya, dalam allowlist) | Ya | -| `check:model-lifecycle` | Ketiga tabel perutean yang dipelihara secara manual tetap konsisten dengan snapshot siklus hidup yang disimpan (#11503): `FITNESS_TABLE` (`taskFitness.ts`) tidak memberikan skor pada id yang sudah dihentikan dan masih dapat dirutekan oleh `REGISTRY`; setiap target `BUILT_IN_ALIASES` terdapat di `REGISTRY` dan tidak terdapat dalam snapshot id yang dihentikan; setiap id yang dihentikan dan masih berada di `REGISTRY` diteruskan atau tercantum dalam `allowedRetiredInCatalog`; dan tidak ada sumber atau target `DEFAULT_DEGRADATION_MAP` yang ditandai telah dihentikan dalam snapshot tersebut. Pemeriksaan ini tidak membuktikan bahwa suatu model saat ini dilayani oleh upstream aktif. Luring — membandingkan dengan `config/quality/model-lifecycle.json`, yang diperbarui secara manual menggunakan `npm run quality:refresh-model-lifecycle` (memerlukan jaringan; tidak diintegrasikan ke CI). `allowedRetiredInCatalog` adalah mekanisme ratchet pengurangan bertahap: tambahkan entri hanya jika disertai masalah pelacakan. | Ya | -| `check:fetch-targets` | Setiap `fetch("/api/...")` di `src/` sisi klien mengarah ke `route.ts` yang benar-benar ada | Ya | -| `check:deps` | Semua dependensi yang dapat dipasang dengan `npm install` dari setiap `package.json` dalam repositori tercantum di `dependency-allowlist.json`; paket baru yang versinya tidak dikunci atau berpotensi merupakan slopsquatting akan ditandai | Ya | -| `audit:deps` | `npm audit` (root + electron) — tidak ada advisori tingkat tinggi/kritis (tumpang tindih dengan `check:vuln-ratchet` osv; lihat Backlog Rasionalisasi) | Ya | -| `check:lockfile` | Integritas `package-lock.json` — registry https, hash integritas, tanpa penggantian host | Ya | -| `check:licenses` | Daftar izin lisensi SPDX untuk dependensi produksi | Ya | -| `check:tracked-artifacts` | Tidak ada artefak build / symlink `node_modules` yang di-commit (juga dijalankan dalam pre-commit husky; pre-push sengaja dibuat ringan — #6716) | Ya | -| `check:ai-attribution` | Tidak ada trailer `Co-Authored-By` AI/bot atau footer pembuatan oleh AI dalam commit, judul, atau isi PR — Aturan Tegas #16 (dalam perulangan pemeriksaan cepat `quality.yml` untuk PR→`release/**` — membaca payload peristiwa, tidak melakukan apa pun di luar PR — dan langkah khusus PR dalam lint `ci.yml` untuk PR→`main`; juga hook `commit-msg` husky; rekan penulis manusia diizinkan; #14436) | -| `check:vitest-exclusions` | Setiap pengecualian Vitest menyebutkan isu pelacakan dan tercantum dalam `config/quality/vitest-exclusions.json` (#13204) | Ya | -| `check:file-size` | Tidak ada file sumber yang melebihi batas per ekstensi (ratchet: file besar yang dibekukan dalam daftar `frozen`) | Ya | -| `check:error-helper` | Respons galat dalam eksekutor/handler menggunakan `buildErrorBody()` / `sanitizeErrorMessage()` (Aturan Tegas #12) | Ya | -| `check:migration-numbering` | File SQL migrasi diberi nomor secara berurutan, tanpa nomor yang terlewat atau duplikat | Ya | -| `check:public-creds` | Tidak ada OAuth `client_id`/`client_secret` literal atau kunci Firebase Web di luar `publicCreds.ts` (Aturan Ketat #11) | Ya | -| `check:db-rules` | Tidak ada SQL mentah di luar modul `src/lib/db/`; tidak ada impor barrel dari `localDb.ts` (Aturan Ketat #2/#5) | Ya | -| `check:known-symbols` | Eksekutor penyedia, strategi perutean, dan penerjemah yang terdaftar dalam tabel dispatch masing-masing cocok dengan file pada disk—tidak ada simbol yatim atau yang tidak dideklarasikan | Ya | -| `check:route-guard-membership` | Setiap rute yang membuat proses anak diklasifikasikan oleh `isLocalOnlyPath()` (Aturan Ketat #15/#17) | Ya | -| `check:test-discovery` | Setiap file `*.test.ts` / `*.spec.ts` dalam repo dikumpulkan oleh setidaknya satu test runner (ratchet: daftar yatim dalam `test-discovery-baseline.json` hanya dapat menyusut) | Ya | -| `check:agent-skills-sync` | Artefak agent-skills yang dihasilkan cocok dengan katalog sumbernya (tanpa penyimpangan) | -| `check:provider-asset-provenance` | Logo/aset penyedia memiliki entri asal-usul yang tercatat | -| `lint:json` | File konfigurasi JSON dapat diurai dan memenuhi aturan lint repositori | -| `typecheck:core` | Kompilasi TypeScript tanpa kesalahan (hanya peringatan informatif) | Ya | -| `typecheck:noimplicit:core` | `noImplicitAny` ketat — berorientasi ke masa depan; banyak lokasi pemanggilan yang sudah ada masih memerlukan anotasi | **Informatif** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` yang dibatasi ke `src/app/(dashboard)/**` (#7033) — daftar yang diizinkan berisi 27 file yang telah dikurasi milik `typecheck:core` tidak menyertakan TSX dasbor apa pun, dan `next build` juga tidak pernah memeriksa tipenya (`next.config.mjs` menetapkan `ignoreBuildErrors: true`), sehingga regresi pengidentifikasi yatim di sana (#6625/#6909) tidak terlihat oleh CI. Perubahan dibandingkan dengan tolok ukur jumlah per file/per kode TS yang dibekukan (`config/quality/dashboard-typecheck-baseline.json`, pola penerapan terhadap data usang yang sama seperti `check:known-symbols`) — hanya kesalahan BARU yang melebihi jumlah dalam tolok ukur yang menggagalkan gerbang; turunkan tolok ukur secara bertahap dengan `--update` ketika kesalahan yang sudah ada diperbaiki. | Ya | - -### Pekerjaan: `quality-gate` - -Berjalan setelah `test-coverage`. Memblokir penggabungan jika terjadi kegagalan. - -| Skrip | Memvalidasi | Memblokir | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `quality:collect` | Menghasilkan `quality-metrics.json` (jumlah peringatan ESLint, cakupan dari laporan shard gabungan) | Ya (sebelum ratchet) | -| `quality:ratchet` | Setiap metrik dalam `quality-baseline.json` tidak mengalami regresi (peringatan ESLint ≤ baseline; cakupan ≥ baseline) | Ya | -| `check:duplication` | Duplikasi kode (jscpd@4) tidak melebihi baseline dalam `quality-baseline.json` | Ya | -| `check:complexity` | Kompleksitas siklomatik tingkat file tidak melebihi batas (ESLint inti `complexity` + `max-lines-per-function`) | Ya | -| `check:cognitive-complexity` | Ratchet kompleksitas kognitif (`eslint-plugin-sonarjs`) — proses ESLint terpisah; CI menjalankan keduanya secara gabungan sebagai satu langkah `check:complexity-ratchets` | Ya | -| `check:dead-code` | Ratchet ekspor/file yang tidak digunakan (knip) tidak mengalami regresi dibandingkan baseline | Ya | -| `check:compression-budget` | Anggaran tolok ukur kompresi — batas minimum penghematan token per mesin tidak boleh mengalami regresi | Ya | -| `check:type-coverage` | Ratchet persentase kode bertipe (`type-coverage`) tidak mengalami regresi; sebagian besar mencakup `typecheck:noimplicit:core` | Ya | -| `check:codeql-ratchet` | Jumlah peringatan CodeQL terbuka tidak mengalami regresi (dibaca melalui `gh api`; dilewati secara wajar tanpa token) — frekuensi pembaruan dan pemicu manual: lihat "Ratchet CodeQL" di bawah | Ya | +| Skrip (`npm run ...`) | Memvalidasi | Memblokir | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| `check:node-runtime` | Versi Node.js berada dalam rentang yang didukung | Ya | +| `check:cycles` | Impor melingkar di seluruh `src/` + `open-sse/` (berbasis AST, `paths` tsconfig di-resolve). Bare = bersifat saran, menampilkan daftar siklus. `check:cycles:ratchet` (yang dijalankan CI) memblokir ketika jumlahnya melebihi batas atas `metrics.cycles` dalam `quality-baseline.json` — saat ini 14, `direction: down`, sehingga nilainya hanya dapat berkurang (#15159 G-01/G-02) | Ya (ratchet) | +| `check:route-validation:t06` | Skema Zod tersedia pada semua route (kebijakan Tier 6) | Ya | +| `check:any-budget:t11` | Jumlah `@ts-expect-error // any` tidak melebihi anggaran (catraca Tier 11) | Ya | +| `check:provider-consistency` | Setiap provider di `providers.ts` memiliki entri yang sesuai di `providerRegistry.ts` (dan sebaliknya, dalam allowlist) | Ya | +| `check:model-lifecycle` | Tiga tabel routing yang dikelola secara manual tetap konsisten dengan snapshot siklus hidup yang disimpan (#11503): `FITNESS_TABLE` (`taskFitness.ts`) tidak memberikan skor pada id yang sudah dihentikan dan masih dapat dirutekan oleh `REGISTRY`; setiap target `BUILT_IN_ALIASES` terdapat di `REGISTRY` dan tidak ada dalam snapshot id yang dihentikan; setiap id yang dihentikan dan masih ada di `REGISTRY` diteruskan atau tercantum dalam `allowedRetiredInCatalog`; dan tidak ada sumber atau target `DEFAULT_DEGRADATION_MAP` yang ditandai telah dihentikan dalam snapshot tersebut. Ini tidak membuktikan bahwa suatu model saat ini dilayani oleh upstream aktif. Offline — dibandingkan dengan `config/quality/model-lifecycle.json`, diperbarui secara manual menggunakan `npm run quality:refresh-model-lifecycle` (memerlukan jaringan; tidak diintegrasikan ke CI). `allowedRetiredInCatalog` adalah mekanisme ratchet burn-down: tambahkan entri hanya jika disertai issue pelacakan. | Ya | +| `check:fetch-targets` | Setiap `fetch("/api/...")` dalam `src/` sisi klien mengarah ke `route.ts` yang benar-benar ada | Ya | +| `check:deps` | Semua dependensi yang dapat diinstal dengan `npm install` di seluruh `package.json` dalam repositori tercantum di `dependency-allowlist.json`; paket baru yang tidak dikunci versinya atau berpotensi slopsquatting akan ditandai | Ya | +| `audit:deps` | `npm audit` (root + electron) — tidak ada advisory tingkat tinggi/kritis (tumpang tindih dengan osv `check:vuln-ratchet`; lihat Backlog Rasionalisasi) | Ya | +| `check:lockfile` | Integritas `package-lock.json` — registry https, hash integritas, tanpa penggantian host | Ya | +| `check:licenses` | Daftar izin lisensi SPDX untuk dependensi produksi | Ya | +| `check:tracked-artifacts` | Tidak ada artefak build / symlink `node_modules` yang di-commit (juga dijalankan dalam pre-commit husky; pre-push sengaja dibuat ringan — #6716) | Ya | +| `check:ai-attribution` | Tidak ada trailer `Co-Authored-By` AI/bot atau footer pembuatan oleh AI dalam commit, judul, atau isi PR — Aturan Ketat #16 (dalam loop gerbang cepat `quality.yml` untuk PR→`release/**` — membaca payload event, tidak melakukan apa pun di luar PR — dan langkah khusus PR dalam lint `ci.yml` untuk PR→`main`; juga hook `commit-msg` husky; rekan penulis manusia diperbolehkan; #14436) | +| `check:vitest-exclusions` | Setiap pengecualian Vitest mencantumkan issue pelacakan dan tercantum dalam `config/quality/vitest-exclusions.json` (#13204) | Ya | +| `check:file-size` | Tidak ada file sumber yang melebihi batas per ekstensi (ratchet: file besar yang dibekukan dalam daftar `frozen`) | Ya | +| `check:error-helper` | Respons error dalam eksekutor/handler menggunakan `buildErrorBody()` / `sanitizeErrorMessage()` (Aturan Ketat #12) | Ya | +| `check:migration-numbering` | File SQL migrasi diberi nomor secara berurutan, tanpa celah atau duplikat | Ya | +| `check:public-creds` | Tidak ada nilai literal OAuth `client_id`/`client_secret` atau kunci Firebase Web di luar `publicCreds.ts` (Aturan Ketat #11) | Ya | +| `check:db-rules` | Tidak ada SQL mentah di luar modul `src/lib/db/`; tidak ada impor barrel dari `localDb.ts` (Aturan Ketat #2/#5) | Ya | +| `check:known-symbols` | Eksekutor penyedia, strategi perutean, dan penerjemah yang terdaftar dalam tabel dispatch masing-masing cocok dengan file pada disk — tidak ada simbol yatim atau yang tidak dideklarasikan | Ya | +| `check:route-guard-membership` | Setiap rute yang menjalankan proses anak diklasifikasikan oleh `isLocalOnlyPath()` (Aturan Ketat #15/#17) | Ya | +| `check:test-discovery` | Setiap file `*.test.ts` / `*.spec.ts` dalam repositori dikumpulkan oleh setidaknya satu test runner (ratchet: daftar file yatim dalam `test-discovery-baseline.json` hanya dapat menyusut) | Ya | +| `check:agent-skills-sync` | Artefak agent-skills yang dihasilkan cocok dengan katalog sumbernya (tanpa penyimpangan) | +| `check:provider-asset-provenance` | Logo/aset penyedia memiliki entri asal-usul yang tercatat | +| `lint:json` | File konfigurasi JSON dapat diurai dan memenuhi aturan lint repositori | +| `typecheck:core` | Kompilasi TypeScript tanpa kesalahan (hanya peringatan advisori) | Ya | +| `typecheck:noimplicit:core` | `noImplicitAny` ketat — berorientasi ke depan; banyak lokasi pemanggilan yang sudah ada masih memerlukan anotasi | **Advisori** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` yang dicakup untuk `src/app/(dashboard)/**` (#7033) — daftar yang diizinkan dan dikurasi yang berisi 27 file milik `typecheck:core` tidak menyertakan TSX dasbor apa pun, dan `next build` juga tidak pernah melakukan pemeriksaan tipe terhadapnya (`next.config.mjs` menetapkan `ignoreBuildErrors: true`), sehingga regresi pengidentifikasi yatim di sana (#6625/#6909) tidak terlihat oleh CI. Membandingkan dengan baseline jumlah per file/per kode TS yang dibekukan (`config/quality/dashboard-typecheck-baseline.json`, pola penegakan kedaluwarsa yang sama seperti `check:known-symbols`) — hanya kesalahan BARU yang melampaui jumlah dalam baseline yang menggagalkan gate; turunkan baseline secara bertahap dengan `--update` ketika kesalahan yang sudah ada diperbaiki. | Ya | + +### Job: `quality-gate` + +Berjalan setelah `test-coverage`. Memblokir penggabungan jika gagal. + +| Skrip | Memvalidasi | Memblokir | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `quality:collect` | Menghasilkan `quality-metrics.json` (jumlah peringatan ESLint, cakupan dari laporan shard gabungan) | Ya (sebelum ratchet) | +| `quality:ratchet` | Setiap metrik dalam `quality-baseline.json` tidak mengalami regresi (peringatan ESLint ≤ baseline; cakupan ≥ baseline) | Ya | +| `check:duplication` | Duplikasi kode (jscpd@4) tidak melebihi baseline dalam `quality-baseline.json` | Ya | +| `check:complexity` | Kompleksitas siklomatik tingkat file tidak melebihi batas (aturan inti ESLint `complexity` + `max-lines-per-function`) | Ya | +| `check:cognitive-complexity` | Ratchet kompleksitas kognitif (`eslint-plugin-sonarjs`) — lintasan ESLint terpisah; CI menjalankan keduanya yang digabung sebagai satu langkah `check:complexity-ratchets` | Ya | +| `check:dead-code` | Ratchet ekspor/file yang tidak digunakan (knip) tidak mengalami regresi dibandingkan baseline | Ya | +| `check:compression-budget` | Anggaran benchmark kompresi — batas minimum penghematan token per mesin tidak boleh mengalami regresi | Ya | +| `check:type-coverage` | Ratchet persentase kode bertipe (`type-coverage`) tidak mengalami regresi; sebagian besar mencakup `typecheck:noimplicit:core` | Ya | +| `check:codeql-ratchet` | Jumlah peringatan CodeQL yang terbuka tidak mengalami regresi (dibaca melalui `gh api`; dilewati secara anggun tanpa token) — frekuensi pembaruan dan pemicu manual: lihat "Ratchet CodeQL" di bawah | Ya | ### Job: `quality-extended` -Seluruh job bersifat anjuran (`continue-on-error: true`). Ratchet berbasis npm benar-benar -dijalankan; pemindai eksternal diinstal melalui `gh release download` dan melewati pemeriksaan -secara mandiri (exit 0) ketika biner masih tidak tersedia. +Seluruh job bersifat sebagai saran (`continue-on-error: true`). Ratchet berbasis npm benar-benar +dijalankan; pemindai eksternal diinstal melalui `gh release download` dan melewati prosesnya sendiri (exit 0) +ketika biner masih tidak tersedia. -| Skrip | Memvalidasi | Memblokir | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | -| `check:circular-deps` | Tidak ada dependensi sirkular (dpdm) | **Anjuran** | -| `check:bundle-size` | Ukuran bundle tidak melebihi batas | **Anjuran** | -| `check:secrets` | Pemindaian rahasia (gitleaks) — dilewati jika biner tidak tersedia | **Anjuran** | -| `check:vuln-ratchet` | Kerentanan dependensi (osv-scanner) tidak mengalami regresi — dilewati jika biner tidak tersedia | **Anjuran** | -| `check:workflows` | Lint workflow (actionlint + zizmor); pemindai yang hilang/rusak, laporan yang tidak valid, atau baseline ratchet yang tidak tersedia akan gagal sebagai INCOMPLETE. Temuan yang valid mengikuti kebijakan ketat/anjuran/ratchet yang dipilih | Eksekusi wajib; ratchet zizmor memblokir di CI | -| `check:openapi-breaking` | Perubahan yang merusak kontrak API publik (`openapi.yaml`) dibandingkan dengan branch dasar (oasdiff) — menghasilkan `openapiBreaking=N`; dilewati jika oasdiff tidak tersedia atau spesifikasi dasar tidak dapat di-resolve | **Anjuran** | +| Skrip | Memvalidasi | Memblokir | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| `check:circular-deps` | Tidak ada dependensi melingkar (dpdm) | **Saran** | +| `check:bundle-size` | Ukuran bundle tidak melebihi batas | **Saran** | +| `check:secrets` | Pemindaian rahasia (gitleaks) — dilewati jika biner tidak tersedia | **Saran** | +| `check:vuln-ratchet` | Kerentanan dependensi (osv-scanner) tidak mengalami regresi — dilewati jika biner tidak tersedia | **Saran** | +| `check:workflows` | Lint alur kerja (actionlint + zizmor); pemindai yang tidak tersedia/rusak, laporan tidak valid, atau baseline ratchet yang tidak tersedia akan gagal sebagai INCOMPLETE. Temuan yang valid mengikuti kebijakan ketat/saran/ratchet yang dipilih | Eksekusi diwajibkan; ratchet zizmor memblokir di CI | +| `check:openapi-breaking` | Perubahan yang merusak kontrak API publik (`openapi.yaml`) dibandingkan cabang dasar (oasdiff) — menghasilkan `openapiBreaking=N`; dilewati jika oasdiff tidak tersedia atau spesifikasi dasar tidak dapat diresolusi | **Saran** | ### Job: `docs-sync-strict` @@ -169,67 +170,67 @@ Dijalankan pada setiap PR ke `main`. Memblokir penggabungan jika gagal. | Skrip | Memvalidasi | Memblokir | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| `check:docs-all` | Gerbang meta yang menjalankan 6 subgerbang di bawah ini secara berurutan | Ya | +| `check:docs-all` | Meta-gate yang menjalankan 6 sub-gate di bawah ini secara berurutan | Ya | | ↳ `check:docs-sync` | Konsistensi versi CHANGELOG / OpenAPI / llm.txt | Ya | | ↳ `check:docs-counts` | Jumlah dalam prosa (jumlah penyedia, jumlah migrasi, dll.) berada dalam rentang ratchet dari jumlah sebenarnya | Ya | -| ↳ `check:env-doc-sync` | Setiap variabel lingkungan dalam `.env.example` didokumentasikan dalam tabel dokumentasi, dan sebaliknya | Ya | +| ↳ `check:env-doc-sync` | Setiap env var dalam `.env.example` didokumentasikan dalam tabel dokumentasi, dan sebaliknya | Ya | | ↳ `check:deprecated-versions` | Tidak ada string versi yang sudah tidak digunakan lagi dalam dokumentasi | Ya | -| ↳ `check:doc-links` | Tautan markdown internal dalam dokumentasi mengarah ke file nyata (format `[text]`/`(path)`) | Ya | -| ↳ `check:fabricated-docs` | Rute, variabel lingkungan, perintah CLI, nama hook, dan jalur file yang disebutkan dalam dokumentasi ada di basis kode. Gerbang keras melalui `--strict`; gagal lunak tanpa flag. | Ya (melalui `--strict` di CI) | -| `check:cli-i18n` | String perintah CLI tersedia dalam semua file locale i18n | Ya | -| `check:openapi-coverage` | Spesifikasi OpenAPI mencakup setidaknya batas minimum yang diratchet dari rute nyata | Ya | +| ↳ `check:doc-links` | Tautan markdown internal dalam dokumentasi mengarah ke file yang benar-benar ada (format `[text]`/`(path)`) | Ya | +| ↳ `check:fabricated-docs` | Rute, env var, perintah CLI, nama hook, dan jalur file yang disebutkan dalam dokumentasi benar-benar ada dalam basis kode. Gate ketat melalui `--strict`; gagal lunak tanpa flag. | Ya (melalui `--strict` di CI) | +| `check:cli-i18n` | String perintah CLI tersedia di semua file locale i18n | Ya | +| `check:openapi-coverage` | Spesifikasi OpenAPI mencakup setidaknya batas minimum rute nyata yang telah di-ratchet | Ya | | `check:openapi-security-tiers` | Anotasi tingkat keamanan dalam `openapi.yaml` konsisten dengan klasifikasi `routeGuard.ts` | **Rekomendasi** | -| `check:openapi-routes` | Setiap path dalam `openapi.yaml` mengarah ke `route.ts` yang nyata (antihalusinasi) | Ya | -| `check:docs-symbols` | Setiap referensi `/api/...` dalam `docs/**/*.md` mengarah ke `route.ts` yang nyata (antihalusinasi) | Ya | -| `i18n translation drift` | Key yang belum diterjemahkan dalam file locale i18n — hanya peringatan | **Rekomendasi** | +| `check:openapi-routes` | Setiap path dalam `openapi.yaml` mengarah ke `route.ts` yang benar-benar ada (anti-halusinasi) | Ya | +| `check:docs-symbols` | Setiap referensi `/api/...` dalam `docs/**/*.md` mengarah ke `route.ts` yang benar-benar ada (anti-halusinasi) | Ya | +| `i18n translation drift` | Kunci yang belum diterjemahkan dalam file locale i18n — hanya peringatan | **Rekomendasi** | ### Job: `i18n-ui-coverage` | Skrip | Memvalidasi | Memblokir | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (inline) | Cakupan key i18n UI adalah ≥ 65% | Ya | -| `check-ui-value-drift` (inline) | **Nilai** bahasa Inggris yang ditulis ulang tidak menyisakan terjemahan usang | Ya | -| `check-new-key-coverage` (inline) | **Key** bahasa Inggris baru diterjemahkan dalam setiap locale — penanda `__MISSING__:` akan ditolak | Ya | +| `check-ui-keys-coverage` (inline) | Cakupan kunci i18n UI adalah ≥ 65% | Ya | +| `check-ui-value-drift` (inline) | **Nilai** bahasa Inggris yang ditulis ulang tidak meninggalkan terjemahan usang | Ya | +| `check-new-key-coverage` (inline) | Kunci bahasa Inggris **baru** diterjemahkan di setiap locale — penanda `__MISSING__:` ditolak | Ya | | `check-translation-ratio` | Rasio terjemahan nyata per locale (sama dengan bahasa Inggris / placeholder / entri yang hilang di luar daftar yang diizinkan) tidak boleh melebihi `config/quality/i18n-translation-baseline.json` + toleransi | **Rekomendasi** | -Memerlukan `fetch-depth: 0` — gerbang value-drift membandingkan perbedaan `en.json` terhadap basis penggabungan. +Memerlukan `fetch-depth: 0` — gate penyimpangan nilai membandingkan diff `en.json` terhadap basis penggabungan. -#### `check-ui-value-drift` — gerbang terjemahan usang +#### `check-ui-value-drift` — gate terjemahan usang -Menangkap satu regresi i18n yang secara struktural tidak dapat dideteksi oleh gerbang lainnya: sebuah nilai bahasa Inggris +Menangkap satu regresi i18n yang secara struktural tidak dapat dilihat oleh gate lain: sebuah nilai bahasa Inggris ditulis ulang dan terjemahan yang berasal dari bahasa Inggris _sebelumnya_ tetap tertinggal, sehingga -pengguna non-Inggris terus membaca teks yang terdengar meyakinkan, tetapi kini keliru. +pengguna non-Inggris terus membaca teks yang terdengar meyakinkan, tetapi kini salah. -Ini benar-benar pernah dirilis. `oauthModal.googleOAuthWarning` ditulis ulang ketika pembantu login -Antigravity diluncurkan (#5203); **39 dari 43 locale** tetap menyimpan teks yang menginstruksikan operator untuk "menyalin +Ini benar-benar pernah dirilis. `oauthModal.googleOAuthWarning` ditulis ulang ketika helper login Antigravity +ditambahkan (#5203); **39 dari 43 locale** mempertahankan teks yang memberi tahu operator untuk "menyalin URL lengkap dan menempelkannya di bawah" — alur yang tidak dapat diselesaikan untuk penyedia tersebut. Hal ini -tidak terdeteksi hingga #8463 karena: +tidak diketahui hingga #8463 karena: -- `sync-ui-keys` hanya mengisi ulang key yang **tidak ada**, tidak pernah key yang **usang**; -- `check-ui-keys-coverage` menghitung _keberadaan_ key, sehingga terjemahan usang dianggap tercakup; -- `check-translation-drift` melacak cerminan dokumentasi `docs/i18n//**.md` — +- `sync-ui-keys` hanya mengisi ulang kunci yang **tidak ada**, bukan yang **usang**; +- `check-ui-keys-coverage` menghitung _keberadaan_ kunci, sehingga terjemahan usang dianggap telah tercakup; +- `check-translation-drift` melacak mirror dokumentasi `docs/i18n//**.md` — skrip ini tidak pernah membaca `src/i18n/messages/*.json`. Memblokir dalam job `docs-sync-strict` sejak - sinkronisasi ulang 2026-09: edit dokumen inti → `npm run i18n:run -- --files=` (per bagian, ringan). + sinkronisasi ulang 2026-09: edit dokumen inti → `npm run i18n:run -- --files=` (tingkat bagian, ringan). -**Peka terhadap diff, bukan ditopang baseline.** Pemeriksaan ini membandingkan `en.json` pada basis penggabungan dengan -working tree; untuk setiap kunci yang nilai bahasa Inggrisnya berubah, locale yang masih memiliki -terjemahan yang belum diubah dianggap kedaluwarsa. Hal ini sengaja **membekukan utang yang sudah ada** — sebuah diff -tidak dapat mengungkapkan dari teks bahasa Inggris lama mana suatu terjemahan yang sudah lama ada berasal, sehingga pemeriksaan -hanya menilai apa yang disentuh oleh perubahan saat ini. Alternatifnya (baseline hash per kunci) akan membutuhkan +**Peka terhadap diff, bukan berbasis baseline.** Pemeriksaan ini membandingkan `en.json` pada basis penggabungan dengan +pohon kerja; untuk setiap kunci yang nilai bahasa Inggrisnya berubah, locale mana pun yang masih menyimpan +terjemahan yang belum disentuh dianggap usang. Ini sengaja **membekukan utang yang sudah ada** — sebuah diff +tidak dapat menunjukkan dari teks bahasa Inggris lama mana suatu terjemahan yang telah lama ada berasal, sehingga pemeriksaan +hanya menilai apa yang disentuh oleh perubahan saat ini. Alternatifnya (baseline hash per kunci) akan memerlukan file hasil generasi berukuran ~600 KB, 3× lebih besar daripada baseline terbesar yang ada, dan berubah pada setiap PR i18n. -Dua cara untuk memenuhinya: +Ada dua cara untuk memenuhinya: 1. perbarui terjemahan yang terdampak, atau -2. atur nilainya menjadi `__MISSING__:` — runtime kemudian menyajikan teks bahasa Inggris yang telah diperbaiki - (`src/i18n/request.ts::deepMergeFallback`, #7258), dan kunci tersebut masuk ke antrean penerjemahan. +2. atur nilainya menjadi `__MISSING__:` — runtime kemudian menyajikan teks bahasa Inggris yang telah dikoreksi + (`src/i18n/request.ts::deepMergeFallback`, #7258), dan kunci tersebut masuk antrean untuk diterjemahkan. -Jika **makna** string berubah, sebaiknya **ganti nama kuncinya**: kunci baru tidak dapat mewarisi -terjemahan yang kedaluwarsa. Itulah pola yang digunakan #8463. +Jika **makna** string berubah, sebaiknya **ganti nama kunci**: kunci baru tidak dapat mewarisi +terjemahan yang usang. Itulah pola yang digunakan oleh #8463. ```bash npm run i18n:check-value-drift # ketat (yang dijalankan CI) -npm run i18n:check-value-drift:warn # hanya laporan +npm run i18n:check-value-drift:warn # hanya melaporkan BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` @@ -240,7 +241,7 @@ tanpa ref dasar), mengikuti perilaku `check-openapi-breaking`. Matriks validasi i18n lengkap (satu job per locale). Seluruh job bersifat advisori. -| Skrip | Yang divalidasi | Pemblokiran | +| Skrip | Memvalidasi | Pemblokiran | | ------------------------------- | --------------------------------- | --------------------------------------------------------- | | `validate_translation.py quick` | Kelengkapan terjemahan per locale | **Advisori** (`continue-on-error: true` pada seluruh job) | @@ -248,32 +249,32 @@ Matriks validasi i18n lengkap (satu job per locale). Seluruh job bersifat adviso Hanya dijalankan pada pull request. -| Skrip | Yang divalidasi | Pemblokiran | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | PR yang mengubah kode produksi di `src/`, `open-sse/`, `electron/`, atau `bin/` harus menyertakan atau memperbarui pengujian (Aturan Keras #8) | Ya | -| `check:test-masking` | File pengujian yang diubah tidak mengurangi jumlah asersi bersih atau menambahkan tautologi `assert.ok(true)` | Ya | -| `check:pr-evidence` | Isi PR menyebutkan bukti pengujian/VPS untuk perubahan tersebut (mengotomatiskan Aturan Keras #18 dengan grep pada prosa PR — rapuh, lihat Backlog) | Ya | +| Skrip | Memvalidasi | Pemblokiran | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check:pr-test-policy` | PR yang mengubah kode produksi di `src/`, `open-sse/`, `electron/`, atau `bin/` harus menyertakan atau memperbarui pengujian (Aturan Keras #8) | Ya | +| `check:test-masking` | File pengujian yang diubah tidak mengurangi jumlah bersih assertion atau menambahkan tautologi `assert.ok(true)` | Ya | +| `check:pr-evidence` | Isi PR mencantumkan bukti pengujian/VPS untuk perubahan tersebut (mengotomatiskan Aturan Keras #18 dengan melakukan grep pada prosa PR — rapuh, lihat Backlog) | Ya | ### Job: `test-vitest` Dijalankan setelah `build`. Memblokir penggabungan jika gagal. -| Suite | Yang divalidasi | Pemblokiran | -| ---------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Server MCP (110 alat), autoCombo, cache — runner vitest | Ya | -| `test:vitest:ui` | Pengujian komponen UI — runner vitest | **Memblokir** — kegagalan yang sudah ada secara eksplisit dikecualikan dalam `vitest.config.ts`; kegagalan baru membuat job gagal | +| Suite | Memvalidasi | Pemblokiran | +| ---------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | Server MCP (110 alat), autoCombo, cache — runner vitest | Ya | +| `test:vitest:ui` | Pengujian komponen UI — runner vitest | **Memblokir** — kegagalan yang sudah ada secara eksplisit dikecualikan di `vitest.config.ts`; kegagalan baru membuat job gagal | ### Workflow nightly (terjadwal, advisori) Workflow ini dijalankan berdasarkan jadwal cron (dan `workflow_dispatch`), tidak pernah pada PR. Semuanya bersifat advisori. -| Workflow | Yang divalidasi | Pemblokiran | +| Workflow | Memvalidasi | Pemblokiran | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | | `nightly-property` | Pengujian properti fast-check dengan seed acak + jumlah eksekusi tinggi | **Advisori** | | `nightly-resilience` | Pemeriksaan pertumbuhan heap, injeksi kegagalan chaos, beban/soak k6 | **Advisori** | | `nightly-llm-security` | Perlindungan injeksi promptfoo (mode blokir) + probe garak (dilewati tanpa secret penyedia) | **Advisori** | | `nightly-schemathesis` | Fuzzing kontrak OpenAPI (schemathesis) terhadap OmniRoute aktif menggunakan `docs/openapi.yaml` — mengungkap pelanggaran spesifikasi / 500 yang tidak ditangani (Fase 8 B.4) | **Advisori** | -| `nightly-mutation` | Skor pengujian mutasi Stryker pada jalur unit cepat — mutan yang bertahan mengungkap asersi yang lemah | **Advisori** | +| `nightly-mutation` | Skor pengujian mutasi Stryker pada jalur unit cepat — mutan yang bertahan mengungkap assertion yang lemah | **Advisori** | | `nightly-compat` | Matriks kompatibilitas engine Node di seluruh rentang `engines.node` yang didukung | **Advisori** | --- @@ -539,41 +540,41 @@ kesalahan koneksi dan tidak pernah mengganggu sesi. ## Backlog Rasionalisasi (tinjauan ROI — Fase 9 Gelombang 3) -Inventaris ini direkonsiliasi terhadap `ci.yml` pada 2026-06-17 (versi sebelumnya tidak mencantumkan +Inventaris ini direkonsiliasi dengan `ci.yml` pada 2026-06-17 (versi sebelumnya menghilangkan `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Tinjauan ROI atas set yang telah direkonsiliasi +`check:codeql-ratchet`, `check:pr-evidence`). Tinjauan ROI terhadap kumpulan yang telah direkonsiliasi mengidentifikasi kandidat rasionalisasi berikut. **Penggabungan merupakan perubahan CI -mekanis; pengubahan/penghapusan merupakan keputusan kebijakan yang dicadangkan bagi operator.** Belum ada hal di bawah ini -yang diterapkan. +mekanis; pengubahan/penghapusan merupakan keputusan kebijakan yang diperuntukkan bagi operator.** Belum ada +hal di bawah ini yang diterapkan. **Juga belum didokumentasikan di atas** (bersifat saran, sinyal rendah): job `docs-lint` (markdownlint + Vale, seluruh job menggunakan `continue-on-error`) dan workflow pemindai mandiri -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` terdapat di -`quality-baseline.json`, tetapi tidak terhubung ke ratchet yang memblokir di `ci.yml` — metrik ini +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` terdapat dalam +`quality-baseline.json`, tetapi tidak terhubung ke ratchet pemblokir di `ci.yml` — metrik tersebut saat ini tidak terpakai. ### Gabungkan / deduplikasi (mekanis, risiko lebih rendah) -Setiap kandidat divalidasi terhadap status gate aktif pada 2026-06-17 (percaya, tetapi verifikasi); -beberapa penggabungan yang tampak "jelas" ternyata menyembunyikan utang dan **bukan** pengganti langsung yang bersih. +Setiap kandidat telah divalidasi terhadap status gate aktif pada 2026-06-17 (percaya tetapi tetap verifikasi); +beberapa penggabungan yang tampak "jelas" ternyata menyembunyikan utang dan **bukan** pengganti langsung yang aman. -- **`check:docs-sync` dijalankan dua kali** — secara mandiri di job `lint` dan sekali lagi di dalam `check:docs-all` (`docs-sync-strict`) serta hook pre-commit husky. ✅ **SELESAI** — pemanggilan mandiri di `lint` telah dihapus. -- **Pemindaian CVE** — ❌ **BUKAN penggabungan yang bersih.** `audit:deps` langsung gagal saat menemukan CVE tinggi/kritis apa pun; `check:vuln-ratchet` (osv) hanya gagal jika terjadi _regresi_ terhadap baseline (saat ini 1 MODERATE). Semantiknya berbeda — menghapus `audit:deps` akan menghilangkan gate absolut untuk tingkat tinggi/kritis. Pertahankan keduanya. -- **Deteksi siklus** — ❌ **BUKAN penggabungan yang bersih.** `check:circular-deps` (dpdm) melaporkan **91 siklus** (itulah sebabnya bersifat saran); pemeriksaan ini tidak dapat dipromosikan menjadi pemblokir tanpa menyelesaikan semuanya terlebih dahulu, dan cakupannya lebih luas daripada `check:cycles` terkurasi yang berstatus hijau. Pertahankan `check:cycles` sebagai pemblokir; penyelesaian 91 siklus dpdm merupakan backlog tersendiri. -- **Kompleksitas** — ✅ **SELESAI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): satu kali penelusuran ESLint, menghitung berdasarkan ruleId agar baseline siklomatik+max-lines dan kognitif tetap independen; masing-masing `check:complexity` / `check:cognitive-complexity` dipertahankan untuk `--update` lokal. +- **`check:docs-sync` berjalan dua kali** — secara mandiri dalam job `lint`, lalu kembali di dalam `check:docs-all` (`docs-sync-strict`) dan hook pre-commit husky. ✅ **SELESAI** — pemanggilan mandiri dalam `lint` telah dihapus. +- **Pemindaian CVE** — ❌ **BUKAN penggabungan yang aman.** `audit:deps` langsung gagal jika menemukan CVE high/critical; `check:vuln-ratchet` (osv) hanya gagal jika terjadi _regresi_ dibandingkan baseline (saat ini 1 MODERATE). Semantiknya berbeda — menghapus `audit:deps` akan menghilangkan gate absolut untuk tingkat high/critical. Pertahankan keduanya. +- **Deteksi siklus** — ✅ **SELESAI** (#15159 G-01/G-02). Teks lama di sini menyebut `check:cycles` sebagai gate "hijau dan terkurasi" serta membenarkan untuk mempertahankannya sebagai pemblokir karena `check:circular-deps` (dpdm) melaporkan 91 siklus. Status hijau tersebut merupakan **hijau palsu**: `check:cycles` memindai 5 subdirektori (450 file), hanya mencocokkan `import|export … from` statis, dan membuang setiap specifier `@/` serta `@omniroute/open-sse/`, sehingga tidak dapat mendeteksi siklus dynamic-import + alias yang mendominasi repo. Telah diperbaiki: gate tersebut kini menelusuri `src` + `open-sse` (5023 file), mengumpulkan specifier dari AST TypeScript (sehingga `import("…")` dihitung dan `typeof import("…")` pada posisi tipe tidak dihitung), serta menyelesaikan `paths` tsconfig. Gate tersebut menemukan **14** siklus, bukan 0. Karena 14 siklus yang sudah ada sebelumnya tidak dapat diperbaiki dalam PR gate, `check:cycles` kini menjadi **ratchet** (`--ratchet`, batas atas `metrics.cycles.value = 14` dalam `quality-baseline.json`, `direction: down`) — ratchet tersebut memblokir setiap _regresi_ dan jumlahnya hanya dapat turun. CI menjalankan `npm run check:cycles:ratchet`. Penurunan bertahap dilakukan bersama **A-01**. `check:circular-deps` (dpdm) tetap bersifat saran sebagai opini kedua yang lebih luas. +- **Kompleksitas** — ✅ **SELESAI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): satu penelusuran ESLint, menghitung berdasarkan ruleId agar baseline cyclomatic+max-lines dan cognitive tetap independen; masing-masing `check:complexity` / `check:cognitive-complexity` tetap tersedia untuk `--update` lokal. - **Anti-halusinasi `/api`** — ✅ **SELESAI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): satu inventaris FS untuk `src/app/api`, openapi-routes + docs-symbols tetap melaporkan secara independen; masing-masing tetap tersedia untuk eksekusi lokal. -- **`check:node-runtime` dijalankan di 11 job** — ⚠️ **ROI rendah.** Masing-masing menggunakan runner terpisah dan pemeriksaannya berlangsung <1 dtk; total penghematan ~10 dtk, dengan konsekuensi kehilangan pengaman per-job yang murah. Tidak sepadan dengan perubahan yang diperlukan. -- **`typecheck:noimplicit:core` pada lint CI** — ✅ **dihapus dari job lint** (sebelumnya bersifat saran dengan `continue-on-error`); permukaan tipe yang memblokir adalah `typecheck:core` + `check:type-coverage`. Skrip lokal dipertahankan. +- **`check:node-runtime` berjalan dalam 11 job** — ⚠️ **ROI rendah.** Masing-masing menggunakan runner terpisah dan pemeriksaannya berlangsung <1 detik; total penghematan ~10 detik, dengan konsekuensi kehilangan pengaman murah per job. Tidak sepadan dengan perubahan yang ditimbulkan. +- **`typecheck:noimplicit:core` pada lint CI** — ✅ **dihapus dari job lint** (sebelumnya bersifat saran dengan `continue-on-error`); permukaan tipe pemblokir adalah `typecheck:core` + `check:type-coverage`. Skrip lokal dipertahankan. ### Ubah / putuskan (kebijakan operator) -- `check:openapi-security-tiers` (bersifat saran) — ❌ **TIDAK dapat diubah secara langsung dengan aman.** Pemeriksaan ini keluar dengan kode 0, tetapi memperingatkan bahwa beberapa route `traffic-inspector` di bawah `LOCAL_ONLY_API_PREFIXES` tidak memiliki anotasi `x-loopback-only: true`. Penerapannya memerlukan penambahan anotasi tersebut ke `openapi.yaml` terlebih dahulu. -- `typecheck:noimplicit:core` (bersifat saran) — sebagian besar telah tercakup oleh ratchet `check:type-coverage` yang memblokir. Ubah menjadi ratchet atau hapus pass `tsc` kedua yang redundan. -- `test:vitest:ui` (sekarang **memblokir**) — kegagalan yang sudah ada secara eksplisit dikecualikan di `vitest.config.ts` dengan komentar pelacakan `// #8618`; kegagalan baru akan menggagalkan job. -- `check:secrets` (gitleaks, ratchet pemblokir yang dibekukan pada 3 positif palsu terdokumentasi) — masukkan ketiganya ke allowlist agar mencapai 0, atau turunkan menjadi bersifat saran. Tumpang tindih dengan pemindaian rahasia bawaan GitHub + `check:public-creds`. -- `check:pr-evidence` (memblokir, menggunakan grep pada prosa isi PR) — berisiko tinggi menghasilkan positif palsu; menghapusnya akan melemahkan penegakan Aturan Keras #18, sehingga ini benar-benar merupakan keputusan kebijakan. -- `semgrep` (pemindai mandiri yang bersifat saran) — tumpang tindih dengan CodeQL untuk kelompok OWASP; hubungkan baseline-nya ke ratchet atau hapus. +- `check:openapi-security-tiers` (bersifat saran) — ❌ **TIDAK dapat langsung diubah dengan aman.** Pemeriksaan ini keluar dengan kode 0, tetapi memperingatkan bahwa beberapa rute `traffic-inspector` di bawah `LOCAL_ONLY_API_PREFIXES` tidak memiliki anotasi `x-loopback-only: true`. Penerapannya sebagai kewajiban mengharuskan anotasi tersebut ditambahkan ke `openapi.yaml` terlebih dahulu. +- `typecheck:noimplicit:core` (bersifat saran) — sebagian besar telah tercakup oleh ratchet pemblokir `check:type-coverage`. Ubah menjadi ratchet atau hapus lintasan `tsc` kedua yang redundan. +- `test:vitest:ui` (sekarang **memblokir**) — kegagalan yang sudah ada sebelumnya secara eksplisit dikecualikan dalam `vitest.config.ts` dengan komentar pelacakan `// #8618`; kegagalan baru akan menggagalkan job. +- `check:secrets` (gitleaks, ratchet pemblokir yang dibekukan pada 3 false-positive terdokumentasi) — masukkan ketiganya ke allowlist agar mencapai 0, atau turunkan menjadi bersifat saran. Tumpang tindih dengan pemindaian secret bawaan GitHub + `check:public-creds`. +- `check:pr-evidence` (memblokir, melakukan grep terhadap prosa isi PR) — risiko false-positive tinggi; penghapusannya akan melemahkan penegakan Hard Rule #18, sehingga ini benar-benar merupakan keputusan kebijakan. +- `semgrep` (mandiri dan bersifat saran) — tumpang tindih dengan CodeQL untuk kelompok OWASP; hubungkan baseline-nya ke ratchet atau hapus. --- diff --git a/docs/i18n/id/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/id/docs/compression/COMPRESSION_GUIDE.md index c1c4fb7a7c20..714791874f94 100644 --- a/docs/i18n/id/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/id/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ disuntikkan alih-alih bahasa Inggris. Pada badan dengan `messages`, bypass konten (`shouldBypassCavemanOutputMode()` di `open-sse/services/compression/outputMode.ts`) memeriksa tiga pesan terakhir dan melewati gaya untuk seluruh giliran ketika pesan tersebut cocok dengan kata kunci keamanan, tindakan tidak dapat diubah, -klarifikasi, atau sensitif urutan. Bypass ini berjalan terlepas dari pengaturan toggle -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) di dasbor. +klarifikasi, atau sensitif urutan. Bypass ini berjalan selama toggle **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) di dasbor aktif, yang merupakan default; dengan toggle nonaktif, gaya yang dipilih juga berlaku pada giliran tersebut. Ketika bypass mengizinkan giliran, `placeSystemInstruction()` (file yang sama), yang tidak pernah membuat `messages[0]` baru, menempatkan blok di salah satu dari yang pertama ditemukan: diff --git a/docs/i18n/id/docs/reference/ENVIRONMENT.md b/docs/i18n/id/docs/reference/ENVIRONMENT.md index 27c062f90be9..cc07a46e0848 100644 --- a/docs/i18n/id/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/id/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Rahasia yang Wajib Diatur -Rahasia berikut **harus** diatur sebelum aplikasi dijalankan untuk pertama kalinya. Tanpanya, aplikasi akan menolak untuk dimulai atau beroperasi dengan nilai default yang tidak aman. - -| Variabel | Wajib | Default | File Sumber | Deskripsi | -| ---------------------------- | ----------------- | ------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ya** | _(tidak ada)_ | `src/lib/auth` | Menandatangani/memverifikasi semua cookie sesi dasbor (JWT). Buat dengan `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ya** | _(tidak ada)_ | `src/lib/db/apiKeys.ts` | Kunci enkripsi AES untuk nilai kunci API yang disimpan di SQLite. Buat dengan `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ya** | `CHANGEME` | Skrip bootstrap | Mengatur kata sandi awal dasbor admin (sesuai dengan default `.env.example` — sengaja dibuat jelas tidak aman untuk memaksa penggantian). **Ubah sebelum penggunaan pertama.** Setelah masuk, ubah melalui Dasbor → Pengaturan → Keamanan. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ya** (produksi) | _(tidak diatur)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Rahasia bersama untuk bridge WebSocket internal Codex Responses. Mengautentikasi permintaan bridge antara relay WS Electron/browser dan OmniRoute. ⚠️ **WAJIB dalam produksi — jika tidak diatur, semua permintaan bridge WS akan ditolak.** Buat dengan `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Tidak | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID eksplisit untuk membatalkan cache service worker pada shell PWA (#11779); berada di urutan pertama dalam rantai resolusi. | -| `SOURCE_VERSION` | Tidak | _(tidak diatur)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Urutan kedua dalam rantai — diatur oleh builder PaaS (misalnya bergaya Heroku) sebagai commit yang diterapkan. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Tidak | _(diturunkan)_ | `src/shared/components/PwaRegister.tsx` | Nilai publik waktu build yang digunakan klien untuk mendaftarkan `/sw.js?v=…`; diturunkan dari dua nilai di atas, lalu SHA git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Tidak (otomatis) | _(otomatis setiap boot)_ | `src/server/authz/policies/management.ts` | Rahasia per proses yang membuktikan bahwa stempel IP peer tepercaya berasal dari server HTTP milik OmniRoute sendiri (`scripts/dev/peer-stamp.mjs`). Middleware authz hanya memercayai lokalitas permintaan (pembatasan loopback/LAN untuk rute LOCAL_ONLY) jika stempel membawa token ini. Dibuat otomatis setiap boot — biarkan tidak diatur; tetapkan hanya untuk konfigurasi multiproses yang harus berbagi stempel tersebut. | -| `OMNIROUTE_TRUSTED_PROXIES` | Tidak | _(tidak diatur)_ | `scripts/dev/peer-stamp.mjs` | IP atau rentang CIDR proxy terbalik yang dipisahkan koma, yang nilai `X-Forwarded-For` / `X-Real-IP`-nya dapat dipercaya oleh daftar izin/penolakan IP, selain alamat loopback, alamat jaringan privat, dan edge Cloudflare yang selalu dipercaya. Hanya diperlukan untuk proxy pada alamat publik lain; jika tidak, alamat proxy itu sendiri yang akan dinilai oleh filter. | +Rahasia yang tidak diatur namun ditandai **Ya** akan dibuat otomatis saat dijalankan pertama kali (`JWT_SECRET`, `API_KEY_SECRET`) dan disimpan ke `server.env` di direktori data — atur secara eksplisit bila direktori tersebut bersifat sementara. Pengecualiannya adalah `OMNIROUTE_WS_BRIDGE_SECRET`: rahasia ini tidak disimpan ke `server.env` — titik masuk server mencetak nilai efemeral per-boot yang hanya dapat digunakan oleh bridge di dalam proses, sehingga klien bridge eksternal perlu mengaturnya secara eksplisit. + +| Variabel | Wajib | Default | File Sumber | Deskripsi | +| ---------------------------- | ----------------- | ------------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ya** | _(tidak ada)_ | `src/lib/auth` | Menandatangani/memverifikasi semua cookie sesi dasbor (JWT). Buat dengan `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ya** | _(tidak ada)_ | `src/lib/db/apiKeys.ts` | Kunci enkripsi AES untuk nilai kunci API yang disimpan di SQLite. Buat dengan `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Tidak | _(tidak diatur)_ | `src/lib/auth/managementPassword.ts` | Mengatur kata sandi dasbor saat startup selama belum ada kata sandi yang tersimpan; diabaikan begitu sebuah kata sandi tersimpan. Instalasi npm dan dari sumber menyalin `.env.example` menjadi `.env`, sehingga dimulai dengan placeholder publiknya yang dikenal, `CHANGEME`: ganti sebelum boot pertama, atau tetapkan `INITIAL_PASSWORD=` (kosong) di `.env` tersebut agar kata sandi dibuat di wizard penyiapan pada kunjungan pertama, sebagaimana yang dilakukan nilai yang tidak diatur (default image Docker). Ubah kata sandi yang tersimpan melalui Dasbor → Pengaturan → Keamanan, atau jalankan `omniroute reset-password` (`node bin/reset-password.mjs` pada checkout sumber) dengan `DATA_DIR` diatur ke direktori data server. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ya** (produksi) | _(tidak diatur)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Rahasia bersama untuk bridge WebSocket internal Codex Responses. Mengautentikasi permintaan bridge antara relay WS Electron/browser dan OmniRoute. ⚠️ **WAJIB dalam produksi — jika tidak diatur, semua permintaan bridge WS akan ditolak.** Buat dengan `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Tidak | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID eksplisit untuk membatalkan cache service worker pada shell PWA (#11779); berada di urutan pertama dalam rantai resolusi. | +| `SOURCE_VERSION` | Tidak | _(tidak diatur)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Urutan kedua dalam rantai — diatur oleh builder PaaS (misalnya bergaya Heroku) sebagai commit yang diterapkan. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Tidak | _(diturunkan)_ | `src/shared/components/PwaRegister.tsx` | Nilai publik waktu build yang digunakan klien untuk mendaftarkan `/sw.js?v=…`; diturunkan dari dua nilai di atas, lalu SHA git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Tidak (otomatis) | _(otomatis setiap boot)_ | `src/server/authz/policies/management.ts` | Rahasia per proses yang membuktikan bahwa stempel IP peer tepercaya berasal dari server HTTP milik OmniRoute sendiri (`scripts/dev/peer-stamp.mjs`). Middleware authz hanya memercayai lokalitas permintaan (pembatasan loopback/LAN untuk rute LOCAL_ONLY) jika stempel membawa token ini. Dibuat otomatis setiap boot — biarkan tidak diatur; tetapkan hanya untuk konfigurasi multiproses yang harus berbagi stempel tersebut. | +| `OMNIROUTE_TRUSTED_PROXIES` | Tidak | _(tidak diatur)_ | `scripts/dev/peer-stamp.mjs` | IP atau rentang CIDR proxy terbalik yang dipisahkan koma, yang nilai `X-Forwarded-For` / `X-Real-IP`-nya dapat dipercaya oleh daftar izin/penolakan IP, selain alamat loopback, alamat jaringan privat, dan edge Cloudflare yang selalu dipercaya. Hanya diperlukan untuk proxy pada alamat publik lain; jika tidak, alamat proxy itu sendiri yang akan dinilai oleh filter. | ### Perintah Pembuatan @@ -215,38 +215,38 @@ OmniRoute menggunakan **SQLite** (melalui `better-sqlite3`) untuk semua persiste ## 4. Keamanan & Autentikasi -| Variabel | Bawaan | File Sumber | Deskripsi | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt yang digabungkan dengan pengenal perangkat keras untuk membuat sidik jari mesin. Ubah untuk setiap deployment guna memastikan isolasi. | -| `OMNIROUTE_CLI_SALT` | _(tidak ditetapkan = salt acak per instalasi yang disimpan di `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC untuk memperoleh token autentikasi CLI lokal. Menetapkan nilai ini akan merotasi semua token CLI pada mesin dan selalu diprioritaskan daripada salt yang disimpan. Lihat `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Menetapkan flag `Secure` pada cookie sesi. **Harus bernilai `true`** saat dijalankan di belakang HTTPS. | -| `REQUIRE_API_KEY` | `false` | middleware API | Jika bernilai `true`, semua permintaan proksi `/v1/*` harus menyertakan kunci API yang valid. **Flag ini tidak membatasi `GET /v1/models`**, yang mengikuti postur login dasbor (`requireAuthForModels`) — sehingga respons `401` dari `/v1/models` TIDAK berarti inferensi telah dilindungi. Lihat `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Memungkinkan nilai lengkap kunci API ditampilkan di UI Dasbor. Dapat dikonfigurasi melalui Flag Fitur Dasbor; berisiko terhadap keamanan pada instans bersama. | -| `NO_LOG_API_KEY_IDS` | _(kosong)_ | `src/lib/compliance/index.ts` | ID kunci API yang dipisahkan koma dan tidak dicatat dalam log permintaan (kepatuhan GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(tidak ditetapkan = tanpa batas)_ | `src/shared/utils/apiKeyPolicy.ts` | Anggaran permintaan cadangan per hari yang diterapkan pada kunci API dengan kolom `rate_limits` bernilai null. Tidak ditetapkan atau kosong: tidak ada batas implisit (#2289, #11017). `0` memiliki arti yang sama (tanpa batas). Bilangan bulat positif N mengaktifkan N/hari, 5N/minggu, 20N/bulan. Nilai tidak kosong dengan format yang salah akan kembali menggunakan rentang lama, yaitu 1000/hari, 5000/minggu, dan 20000/bulan. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Ukuran maksimum body permintaan yang diizinkan. Menolak payload yang melebihi batas ini. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Body permintaan aktual yang mencapai atau melampaui ambang batas ini akan memperoleh lease penerimaan beban berat atomik yang bersifat lokal bagi proses sebelum parsing JSON (jalur BYTE, termasuk `POST /v1/responses`). Menggunakan mekanisme pelepasan ketika tersedia ruang kapasitas yang sehat dari [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) yang sama dengan permintaan berstruktur berat; tetap dibatasi oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) agar masalah [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) tidak muncul kembali. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Batas maksimum absolut rute chat yang diberlakukan terhadap byte yang dibaca selama penyerapan terbatas, termasuk permintaan dengan `Content-Length` yang tidak ada, tidak valid, atau tidak sesuai kenyataan; kelebihan akan menerima `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(tidak disetel — tanpa batas jumlah permintaan)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** batas JUMLAH permintaan lama ini sekarang hanya berlaku jika disetel secara eksplisit. Jika tidak disetel (default), penerimaan chat kelas berat akan dibatasi oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — anggaran BYTE yang diturunkan secara otomatis berdasarkan batas memori nyata proses dalam **satu proses** (satu heap V8). Dua `/v1/responses` dengan ~750 ribu token yang bertumpang tindih menyebabkan heap ~12 GiB berhenti (#7849) — ini adalah peringatan **anggaran memori**, bukan batas maksimum produk yang bersifat absolut sebesar 2. Proses yang sehat (heap di bawah rasio pelepasan) DAPAT menerima lebih banyak `/v1/responses` panjang secara bersamaan melalui `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Puluhan klien SSE berumur panjang (40–50) bergantung pada heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, bukan “maksimum 2”. Menaikkan nilai ini secara membabi buta untuk “menggunakan host” akan memunculkan kembali #7849. Gandakan **heap** dengan **N `DATA_DIR` independen** (#11024); jangan pernah menggunakan `replicas>1` pada satu file SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(diturunkan secara otomatis)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** penggantian untuk anggaran byte penyerapan yang diturunkan secara otomatis (25% dari batas memori V8/cgroup yang lebih ketat, dibagi dengan amplifikasi sementara 8x). Nilai turunan dan eksplisit dibatasi pada 8 MiB–2 GiB. Isi yang lebih besar daripada anggaran efektif akan langsung gagal dengan `413 body_exceeds_budget`; persaingan antara isi yang masing-masing dapat dilayani tetap dapat dicoba ulang dengan `503`. Kapasitas 40–50 klien SSE berumur panjang secara bersamaan bergantung pada anggaran ini + heap, bukan batas absolut “maksimum 2”. Baca `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` di `/api/monitoring/health` sebelum melakukan penyetelan. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Rasio pelepasan akibat tekanan heap (`heapUsed / heap_size_limit`) untuk penerimaan kelas berat BYTE dan STRUCTURE (#10183, #10268, #10437). Permintaan kelas berat konkuren yang melampaui `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` hanya dilepas dengan `503` yang dapat dicoba ulang ketika heap JUGA berada pada atau di atas rasio ini; pada heap yang sehat, permintaan tersebut diterima melalui ruang cadangan sehat. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (default `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Kapasitas tambahan terbatas untuk jalur cepat heap sehat (#10437) pada **keduanya**, STRUCTURE dan BYTE (`admitChatRequest`, termasuk isi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Tanpa batas ini, setiap permintaan pada kondisi sibuk namun dengan heap sehat akan melewati kontrol penerimaan tanpa batas maksimum. Setelah permintaan konkuren sebanyak ini memiliki lease aktif melalui jalur heap sehat, permintaan sibuk berikutnya akan diteruskan ke jalur tunggu terbatas/pelepasan yang SAMA dengan jalur yang digunakan ketika terjadi tekanan heap nyata. `0` menonaktifkan mekanisme bypass sepenuhnya. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Jumlah pesan yang mengklasifikasikan permintaan chat sebagai kelas berat meskipun ukuran isinya berada di bawah ambang byte. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Jumlah alat yang mengklasifikasikan permintaan chat sebagai kelas berat meskipun ukuran isinya berada di bawah ambang byte. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Perkiraan token konservatif berdasarkan ukuran string yang mengklasifikasikan permintaan sebagai kelas berat; ini merupakan proksi biaya penerimaan, bukan tokenisasi penagihan penyedia. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (dinonaktifkan) | `src/shared/middleware/chatBodyAdmission.ts` | Batas riwayat chat opsional yang harus diaktifkan secara eksplisit. Dinonaktifkan secara default: jumlah pesan adalah kebijakan deployment, bukan properti universal suatu permintaan, dan pembatasan di sini akan menolak percakapan dengan `413` terminal sebelum pipeline kompresi dapat membuatnya dapat dilayani. Pertumbuhan heap dibatasi oleh `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` dan pelepasan akibat tekanan heap. Tetapkan nilai positif pada deployment dengan memori terbatas yang memerlukan batas maksimum absolut; kelebihan kemudian akan menerima `413` terstruktur yang mengharuskan pemadatan. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Batas maksimum absolut untuk respons upstream non-streaming yang disangga sepenuhnya ke dalam memori. Setelah batas ini terlampaui, pembaca upstream akan dibatalkan dan permintaan akan gagal dengan cepat, alih-alih terus memperbesar string tanpa batas hingga heap habis. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Jumlah maksimum byte jaringan yang diteruskan dari header respons upstream. Ketika anggaran terlampaui, header berprioritas lebih rendah (misalnya, `x-codex-*`, `x-oai-request-id`) akan dihapus agar tetap berada dalam batas header reverse proxy yang umum. Tetapkan nilai yang lebih tinggi untuk meneruskan lebih banyak metadata upstream dengan konsekuensi ukuran header respons yang lebih besar. | -| `CORS_ORIGIN` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Daftar izin CORS satu origin versi lama. Utamakan `CORS_ALLOWED_ORIGINS` untuk deployment baru. CORS hanya ditujukan bagi klien API browser lintas origin; penulisan dashboard terautentikasi menggunakan permintaan dari origin yang sama beserta perlindungan CSRF yang terikat pada sesi. | -| `CORS_ALLOWED_ORIGINS` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Daftar izin CORS yang dipisahkan koma. Wildcard tidak dikirim kecuali `CORS_ALLOW_ALL=true` dikonfigurasi secara eksplisit. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Opsi darurat khusus pengembangan untuk menggemakan setiap `Origin` browser. Jangan aktifkan pada deployment bersama atau produksi. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokir panggilan penyedia yang menargetkan rentang IP privat/loopback/link-local. Nonaktifkan hanya di lingkungan pengujian yang terisolasi. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Izinkan URL penyedia yang mengarah ke jaringan privat/lokal (localhost, 192.168.x.x, 10.x.x.x, dll.). **DIPERLUKAN untuk penyedia yang di-host sendiri** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Ketika `false`, dashboard menolak validasi URL lokal. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Izinkan penambahan/validasi penyedia pada alamat lokal/privat (127.0.0.1, localhost, LAN, rentang privat) — dibatasi pada jalur validasi penyedia. **Default `true`** (mengutamakan lokal); tetapkan ke `false` untuk memberlakukan pemblokiran ketat khusus alamat publik. Endpoint metadata cloud (169.254.169.254, metadata.google.internal) tetap diblokir dalam kondisi apa pun. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Izinkan rute `/v1/audio/*` (transkripsi, ucapan, terjemahan) menggunakan node penyedia yang kompatibel dengan OpenAI dan di-host di luar localhost. Dinonaktifkan secara default — merutekan audio ke host jarak jauh mengubah identitas egress dan harus menjadi keputusan operator yang eksplisit. Node loopback/privat (localhost, 127.0.0.1, 172.16-31.x) selalu diizinkan dan tidak terpengaruh. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Izinkan `POST /v1/rerank` (serta langkah pemeringkatan ulang loopback milik mesin memori) menggunakan node penyedia yang kompatibel dengan OpenAI dan di-host di luar localhost — perangkat LAN atau peer Tailscale yang menjalankan TEI, Infinity, vLLM, dll. Dinonaktifkan secara default — merutekan ke host jarak jauh mengubah identitas egress dan harus menjadi keputusan operator yang eksplisit. Node loopback (localhost, 127.0.0.1, 172.16-31.x) selalu diizinkan dan tidak terpengaruh. Node jarak jauh juga harus lolos kebijakan URL outbound penyedia (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); host metadata cloud tidak pernah dijadikan tujuan perutean. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ketika OIDC diaktifkan, nonaktifkan login dengan kata sandi agar pengguna hanya dapat melakukan autentikasi melalui Single Sign-On OIDC. Alias dasar `OIDC_DISABLE_PASSWORD_LOGIN` juga diterima; Dashboard Feature Flag dengan kunci yang sama memiliki prioritas lebih tinggi. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias dasar dari `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variabel | Bawaan | File Sumber | Deskripsi | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt yang digabungkan dengan pengenal perangkat keras untuk membuat sidik jari mesin. Ubah untuk setiap deployment guna memastikan isolasi. | +| `OMNIROUTE_CLI_SALT` | _(tidak ditetapkan = salt acak per instalasi yang disimpan di `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC untuk memperoleh token autentikasi CLI lokal. Menetapkan nilai ini akan merotasi semua token CLI pada mesin dan selalu diprioritaskan daripada salt yang disimpan. Lihat `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Menetapkan flag `Secure` pada cookie sesi. **Harus bernilai `true`** saat dijalankan di belakang HTTPS. | +| `REQUIRE_API_KEY` | `false` | middleware API | Jika bernilai `true`, semua permintaan proksi `/v1/*` harus menyertakan kunci API yang valid. **Flag ini tidak membatasi `GET /v1/models`**, yang mengikuti postur login dasbor (`requireAuthForModels`) — sehingga respons `401` dari `/v1/models` TIDAK berarti inferensi telah dilindungi. Lihat `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Memungkinkan nilai lengkap kunci API ditampilkan di UI Dasbor. Dapat dikonfigurasi melalui Flag Fitur Dasbor; berisiko terhadap keamanan pada instans bersama. | +| `NO_LOG_API_KEY_IDS` | _(kosong)_ | `src/lib/compliance/index.ts` | ID kunci API yang dipisahkan koma dan tidak dicatat dalam log permintaan (kepatuhan GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(tidak ditetapkan = tanpa batas)_ | `src/shared/utils/apiKeyPolicy.ts` | Anggaran permintaan cadangan per hari yang diterapkan pada kunci API dengan kolom `rate_limits` bernilai null. Tidak ditetapkan atau kosong: tidak ada batas implisit (#2289, #11017). `0` memiliki arti yang sama (tanpa batas). Bilangan bulat positif N mengaktifkan N/hari, 5N/minggu, 20N/bulan. Nilai tidak kosong dengan format yang salah akan kembali menggunakan rentang lama, yaitu 1000/hari, 5000/minggu, dan 20000/bulan. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Ukuran maksimum body permintaan yang diizinkan. Menolak payload yang melebihi batas ini. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Body permintaan aktual yang mencapai atau melampaui ambang batas ini akan memperoleh lease penerimaan beban berat atomik yang bersifat lokal bagi proses sebelum parsing JSON (jalur BYTE, termasuk `POST /v1/responses`). Menggunakan mekanisme pelepasan ketika tersedia ruang kapasitas yang sehat dari [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) yang sama dengan permintaan berstruktur berat; tetap dibatasi oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) agar masalah [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) tidak muncul kembali. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Batas maksimum absolut rute chat yang diberlakukan terhadap byte yang dibaca selama penyerapan terbatas, termasuk permintaan dengan `Content-Length` yang tidak ada, tidak valid, atau tidak sesuai kenyataan; kelebihan akan menerima `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(tidak disetel — tanpa batas jumlah permintaan)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** batas JUMLAH permintaan lama ini sekarang hanya berlaku jika disetel secara eksplisit. Jika tidak disetel (default), penerimaan chat kelas berat akan dibatasi oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — anggaran BYTE yang diturunkan secara otomatis berdasarkan batas memori nyata proses dalam **satu proses** (satu heap V8). Dua `/v1/responses` dengan ~750 ribu token yang bertumpang tindih menyebabkan heap ~12 GiB berhenti (#7849) — ini adalah peringatan **anggaran memori**, bukan batas maksimum produk yang bersifat absolut sebesar 2. Proses yang sehat (heap di bawah rasio pelepasan) DAPAT menerima lebih banyak `/v1/responses` panjang secara bersamaan melalui `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Puluhan klien SSE berumur panjang (40–50) bergantung pada heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, bukan “maksimum 2”. Menaikkan nilai ini secara membabi buta untuk “menggunakan host” akan memunculkan kembali #7849. Gandakan **heap** dengan **N `DATA_DIR` independen** (#11024); jangan pernah menggunakan `replicas>1` pada satu file SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(diturunkan secara otomatis)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** penggantian untuk anggaran byte penyerapan yang diturunkan secara otomatis (25% dari batas memori V8/cgroup yang lebih ketat, dibagi dengan amplifikasi sementara 8x). Nilai turunan dan eksplisit dibatasi pada 8 MiB–2 GiB. Isi yang lebih besar daripada anggaran efektif akan langsung gagal dengan `413 body_exceeds_budget`; persaingan antara isi yang masing-masing dapat dilayani tetap dapat dicoba ulang dengan `503`. Kapasitas 40–50 klien SSE berumur panjang secara bersamaan bergantung pada anggaran ini + heap, bukan batas absolut “maksimum 2”. Baca `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` di `/api/monitoring/health` sebelum melakukan penyetelan. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Rasio pelepasan akibat tekanan heap (`heapUsed / heap_size_limit`) untuk penerimaan kelas berat BYTE dan STRUCTURE (#10183, #10268, #10437). Permintaan kelas berat konkuren yang melampaui `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` hanya dilepas dengan `503` yang dapat dicoba ulang ketika heap JUGA berada pada atau di atas rasio ini; pada heap yang sehat, permintaan tersebut diterima melalui ruang cadangan sehat. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (default `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Kapasitas tambahan terbatas untuk jalur cepat heap sehat (#10437) pada **keduanya**, STRUCTURE dan BYTE (`admitChatRequest`, termasuk isi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Tanpa batas ini, setiap permintaan pada kondisi sibuk namun dengan heap sehat akan melewati kontrol penerimaan tanpa batas maksimum. Setelah permintaan konkuren sebanyak ini memiliki lease aktif melalui jalur heap sehat, permintaan sibuk berikutnya akan diteruskan ke jalur tunggu terbatas/pelepasan yang SAMA dengan jalur yang digunakan ketika terjadi tekanan heap nyata. `0` menonaktifkan mekanisme bypass sepenuhnya. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Jumlah pesan yang mengklasifikasikan permintaan chat sebagai kelas berat meskipun ukuran isinya berada di bawah ambang byte. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Jumlah alat yang mengklasifikasikan permintaan chat sebagai kelas berat meskipun ukuran isinya berada di bawah ambang byte. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Perkiraan token konservatif berdasarkan ukuran string yang mengklasifikasikan permintaan sebagai kelas berat; ini merupakan proksi biaya penerimaan, bukan tokenisasi penagihan penyedia. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (dinonaktifkan) | `src/shared/middleware/chatBodyAdmission.ts` | Batas riwayat chat opsional yang harus diaktifkan secara eksplisit. Dinonaktifkan secara default: jumlah pesan adalah kebijakan deployment, bukan properti universal suatu permintaan, dan pembatasan di sini akan menolak percakapan dengan `413` terminal sebelum pipeline kompresi dapat membuatnya dapat dilayani. Pertumbuhan heap dibatasi oleh `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` dan pelepasan akibat tekanan heap. Tetapkan nilai positif pada deployment dengan memori terbatas yang memerlukan batas maksimum absolut; kelebihan kemudian akan menerima `413` terstruktur yang mengharuskan pemadatan. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Batas maksimum absolut untuk respons upstream non-streaming yang disangga sepenuhnya ke dalam memori. Setelah batas ini terlampaui, pembaca upstream akan dibatalkan dan permintaan akan gagal dengan cepat, alih-alih terus memperbesar string tanpa batas hingga heap habis. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Jumlah maksimum byte jaringan yang diteruskan dari header respons upstream. Ketika anggaran terlampaui, header berprioritas lebih rendah (misalnya, `x-codex-*`, `x-oai-request-id`) akan dihapus agar tetap berada dalam batas header reverse proxy yang umum. Tetapkan nilai yang lebih tinggi untuk meneruskan lebih banyak metadata upstream dengan konsekuensi ukuran header respons yang lebih besar. | +| `CORS_ORIGIN` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Daftar izin CORS satu origin versi lama. Utamakan `CORS_ALLOWED_ORIGINS` untuk deployment baru. CORS hanya ditujukan bagi klien API browser lintas origin; penulisan dashboard terautentikasi menggunakan permintaan dari origin yang sama beserta perlindungan CSRF yang terikat pada sesi. | +| `CORS_ALLOWED_ORIGINS` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Daftar izin CORS yang dipisahkan koma. Wildcard tidak dikirim kecuali `CORS_ALLOW_ALL=true` dikonfigurasi secara eksplisit. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Opsi darurat khusus pengembangan untuk menggemakan setiap `Origin` browser. Jangan aktifkan pada deployment bersama atau produksi. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias lawas (legacy). Sakelar dashboard-nya (berupa override DB) dibaca sebelum nilai environment ini; `false`, `0`, `no`, atau `off` pada salah satunya mematikan pemeriksaan host sebagaimana `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` melakukannya, bahkan ketika sakelar dashboard flag tersebut tersimpan dalam keadaan mati; nilai lainnya menyerahkan guard kepada dua flag di bawah ini. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Mematikan pemeriksaan host milik guard URL outbound, termasuk blokir metadata cloud (seluruh 169.254.0.0/16, metadata.google.internal), pada jalur penyedia yang mengikuti mode guard: validasi URL, penemuan model, URL dasar node penyedia (termasuk node rerank jarak jauh), dan uji proxy-fallback, di antara jalur outbound lain yang berdekatan dengan penyedia seperti pengambilan gambar/media jarak jauh. Flag ini juga mengizinkan target webhook privat, kecuali endpoint metadata cloud/link-local, yang tetap diblokir meskipun flag ini aktif. Permintaan chat yang dikirim melalui `BaseExecutor.execute()` tetap mempertahankan pemeriksaan metadata cloud-nya. Penyedia lokal bekerja dengan default: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (default `true`) sudah mengizinkan URL lokal dan LAN pada jalur validasi, penemuan, dan node penyedia; uji proxy-fallback dan target webhook privat hanya mengacu pada flag ini dan memblokir host lokal/LAN selama flag ini mati. Mengesampingkan `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Nilai yang disimpan dari sakelar dashboard lebih diutamakan daripada variabel ini. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Mengizinkan URL penyedia pada alamat lokal/privat (127.0.0.1, localhost, LAN, rentang privat) untuk validasi penyedia, penemuan model, chat, dan jalur outbound lain yang berdekatan dengan penyedia yang diatur oleh mode guard. **Default `true`** (mengutamakan lokal): guard kemudian memblokir endpoint metadata cloud — seluruh 169.254.0.0/16 ditambah nama host metadata yang dikenal (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Tetapkan `false` untuk mode khusus publik, yang juga memblokir host privat dan loopback. Guard memeriksa nama host atau literal IP apa adanya, sebelum resolusi DNS apa pun; dalam mode khusus publik, wrapper safe-fetch tambahan me-resolve nama host dan menolak jawaban apa pun yang privat. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` mematikan pemeriksaan ini pada jalur yang didaftar di barisnya. Penyedia lokal bawaan, seperti LM Studio, Ollama, vLLM, Llamafile, dan Triton, melewati semua pemeriksaan host untuk validasi kunci dan chat. Nilai yang disimpan dari sakelar dashboard lebih diutamakan daripada variabel ini. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Izinkan rute `/v1/audio/*` (transkripsi, ucapan, terjemahan) menggunakan node penyedia yang kompatibel dengan OpenAI dan di-host di luar localhost. Dinonaktifkan secara default — merutekan audio ke host jarak jauh mengubah identitas egress dan harus menjadi keputusan operator yang eksplisit. Node loopback/privat (localhost, 127.0.0.1, 172.16-31.x) selalu diizinkan dan tidak terpengaruh. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Izinkan `POST /v1/rerank` (serta langkah pemeringkatan ulang loopback milik mesin memori) menggunakan node penyedia yang kompatibel dengan OpenAI dan di-host di luar localhost — perangkat LAN atau peer Tailscale yang menjalankan TEI, Infinity, vLLM, dll. Dinonaktifkan secara default — merutekan ke host jarak jauh mengubah identitas egress dan harus menjadi keputusan operator yang eksplisit. Node loopback (localhost, 127.0.0.1, 172.16-31.x) selalu diizinkan dan tidak terpengaruh. URL dasar node jarak jauh juga harus lolos kebijakan URL outbound penyedia (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ketika OIDC diaktifkan, nonaktifkan login dengan kata sandi agar pengguna hanya dapat melakukan autentikasi melalui Single Sign-On OIDC. Alias dasar `OIDC_DISABLE_PASSWORD_LOGIN` juga diterima; Dashboard Feature Flag dengan kunci yang sama memiliki prioritas lebih tinggi. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias dasar dari `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Daftar Periksa Penguatan Keamanan diff --git a/docs/i18n/ig/CONTRIBUTING.md b/docs/i18n/ig/CONTRIBUTING.md index df500a9b8029..ba412c32ec58 100644 --- a/docs/i18n/ig/CONTRIBUTING.md +++ b/docs/i18n/ig/CONTRIBUTING.md @@ -399,22 +399,22 @@ Dee unit tests na `tests/unit/` nke ga-ekpuchi ma ọ dịkarịa ala: --- -## Ndepụta Nnyocha Pull Request +## Ndepụta Nlele Pull Request -- [ ] Tests gafere (`npm test`) +- [ ] Nnwale gafere (`npm test`) - [ ] Linting gafere (`npm run lint`) -- [ ] Build gara nke ọma (`npm run build`) -- [ ] Agbakwunyere ụdị TypeScript maka functions na interfaces ọhaneze ọhụrụ -- [ ] Enweghị secrets ma ọ bụ fallback values e dere ozugbo na koodu -- [ ] E tinyere public upstream credentials site na `resolvePublicCred()` (lee [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ọ bụghị dịka literals -- [ ] Error responses na-agafe na `buildErrorBody()` / `sanitizeErrorMessage()` — enweghị raw stack traces n'ime response bodies (lee [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell commands (`exec` / `spawn`) na-ebufe runtime values site na `env`, ọ bụghị site na string interpolation -- [ ] E ji Zod schemas nyochaa inputs niile -- [ ] Agbakwunyere **fragment** changelog n'okpuru `changelog.d/{features|fixes|maintenance}/-.md` maka mgbanwe ndị users ga-ahụ (lee [`changelog.d/README.md`](./changelog.d/README.md)) — **edezigharịla** `CHANGELOG.md` ozugbo; a na-achịkọta fragments n'oge release, ha anaghịkwa enwe conflict n'etiti PRs -- [ ] Emelitere documentation (ọ bụrụ na ọ dị mkpa) -- [ ] Enweghị alerts ọhụrụ nke CodeQL / Secret-Scanning e mepere, ma ọ bụ a kagburu nke ọ bụla site na technical justification nke na-ezo aka na doc kwesịrị ekwesị dị na `docs/security/` -- [ ] Routes ndị na-emepụta child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) ka e kewara dịka `isLocalOnlyPath()` na `src/server/authz/routeGuard.ts` — lee [Iwu Siri Ike #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Enweghị trailers `Co-Authored-By` n'ime commit messages — commits ga-apụta naanị n'okpuru Git identity nke onye nwe repository (Iwu Siri Ike #16) +- [ ] Nrụpụta gara nke ọma (`npm run build`) +- [ ] Agbakwunyere ụdị TypeScript maka ọrụ na interface ọhụụ ndị ọha na-eji +- [ ] Enweghị ihe nzuzo ma ọ bụ fallback values e dere kpọmkwem n'ime koodu +- [ ] E tinyere credentials ọha nke upstream site na `resolvePublicCred()` (lee [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), ọ bụghị dị ka literals +- [ ] Nzaghachi njehie na-agafe na `buildErrorBody()` / `sanitizeErrorMessage()` — enweghị raw stack traces n'ime response bodies (lee [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Iwu shell (`exec` / `spawn`) na-ebufe runtime values site na `env`, ọ bụghị site na string interpolation +- [ ] E jiri Zod schemas nyochaa inputs niile +- [ ] Agbakwunyere **fragment** nke changelog n'okpuru `changelog.d/{features|fixes|maintenance}/-.md` maka mgbanwe ndị ọrụ ga-ahụ (lee [`changelog.d/README.md`](./changelog.d/README.md)) — **edezila** `CHANGELOG.md` ozugbo; a na-achịkọta fragments n'oge release, ha anaghịkwa enwe esemokwu n'etiti PR dị iche iche +- [ ] Emelitere documentation (ma ọ bụrụ na ọ dị mkpa) +- [ ] Enweghị alerts ọhụrụ nke CodeQL / Secret-Scanning e mepere, ma ọ bụ a kagburu nke ọ bụla site na technical justification nke na-arụtụ aka na doc kwesịrị ekwesị dị na `docs/security/` +- [ ] E kọwara routes ndị na-amalite child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) dị ka `isLocalOnlyPath()` n'ime `src/server/authz/routeGuard.ts` — lee [Iwu Siri Ike #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Enweghị trailers AI/bot `Co-authored-by` n'ime commit messages (Iwu Siri Ike #16) — a na-enye ndị mmadụ rụkọrọ ọrụ, ndị e ji ọrụ ha mee ihe ọzọ, otuto site na trailers ọkọlọtọ `Co-authored-by: Name ` --- diff --git a/docs/i18n/ig/docs/architecture/QUALITY_GATES.md b/docs/i18n/ig/docs/architecture/QUALITY_GATES.md index f6f0bece8bb2..e95389496cd1 100644 --- a/docs/i18n/ig/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ig/docs/architecture/QUALITY_GATES.md @@ -17,262 +17,262 @@ nke otu sistemụ ahụ na-adabereghị na ngwá ọrụ ọ bụla, lee ## Ndepụta ọnụ ụzọ na profaịlụ mmezu -### Ịnabata onye a na-atụle - -Usoro ọrụ CI na Quality Gates na-ewepụta mkpebi kwụsiri ike nke ọ bụla: `Gate / CI` na -`Gate / Quality`. Iwu nnabata ha nwere ụdị e depụtara na-akọwa ọrụ niile dị n'elu -dị ka nke achọrọ ma ọ bụ nke ndụmọdụ. Ọrụ achọrọ nke metụtara ga-aga nke ọma: nsonaazụ -na-efu efu, nke a kagburu, nke a mafere, nke ka na-eche, na nke a na-amaghị enweghị ike igosi PASS. Nkewa ziri ezi -nke naanị docs ma ọ bụ naanị catalog nwere ike ime ka ụzọ code ghara ịdị mkpa; -a naghị anabata PR nke bụ draft dị ka onye a na-atụle. Label `hotfix` anaghị ewepụ mkpa ihe akaebe. - -Usoro ọrụ abụọ ahụ na-ekpuchi PR na push gaa na branch main/release, dispatch e ji aka mee na -event merge-group. Push, dispatch na merge-group na-eme nhọrọ zuru ezu. Fork -na merge group na-eji runner ndị a na-akwado n'ebe ọzọ maka ọrụ ndị gaara ahọrọ runner -nke a na-akwado n'onwe ya; a ga-enyocha na ikike runner ndị a na-akwado n'ebe ọzọ zuru oke tupu rollout. - -Receipt JSON ọ bụla na-akọwapụta SHA e checkout-rị, workflow run na attempt. -CLI na-ajụ ma SHA nke checkout na event ekwekọghị. Nnwale workflow na-ejikọta ịbụ onye otu policy -na ndepụta `needs` nke ọrụ verdict ka lane ọhụrụ ma ọ bụ nke ewepụrụ ghara ịla n'iyi n'amaghị ama. -Receipt ndị ahụ na-ekpuchi workflow nke ha, ọ bụghị publication, deployment, ma ọ bụ ihe ime -nke advisory scanner dị adị. Ime ka aha check abụọ ahụ rụọ ọrụ na branch rules bụ +### Nnabata onye a na-atụle + +Usoro ọrụ CI na Quality Gates ọ bụla na-ewepụta mkpebi kwụsiri ike: `Gate / CI` na +`Gate / Quality`. Iwu nnabata ha nwere ụdị mbipụta na-edepụta ọrụ upstream ọ bụla +dị ka nke a chọrọ ma ọ bụ nke ndụmọdụ. Ọrụ a chọrọ nke metụtara ọnọdụ ahụ ga-aga nke ọma: nsonaazụ +na-efu efu, a kagburu, a mafere, ka na-echere na nke amaghị enweghị ike igosi PASS. Nhazi ziri ezi +nke docs-only ma ọ bụ catalog-only nwere ike ime ka lane koodu ghara ịdị mkpa; +draft PR abụghị onye a na-anabata. Label `hotfix` anaghị ewepụ mkpa ihe akaebe. + +Usoro ọrụ abụọ ahụ na-ekpuchi PR na push gaa na ngalaba main/release, dispatch nke aka na +mmemme merge-group. Push, dispatch na merge-group na-eme nhọrọ zuru ezu. Fork +na merge group na-eji hosted runners maka ọrụ ndị gaara ahọrọ self-hosted +runners; a ga-enyocha na hosted capacity zuru oke tupu rollout. + +Receipt JSON ọ bụla na-akọwa SHA nke checkout, workflow run na attempt. +CLI na-ajụ ma SHA checkout/event ekwekọghị. Nnwale workflow na-ejikọta ndị otu policy +na ndepụta `needs` nke ọrụ mkpebi ka lane ọhụrụ ma ọ bụ nke ewepụrụ ghara ịpụ n'amaghị ama. +Receipt ndị ahụ na-ekpuchi workflow nke ha, ọ bụghị publication, deployment, ma ọ bụ ihe ndị dị n'ime +advisory scanner dị ugbu a. Ime ka aha check abụọ ahụ rụọ ọrụ na branch rules bụ mgbanwe nchịkwa dị iche; ịgbakwunye ọrụ ndị a anaghị echebe branch n'onwe ya. ### Ndepụta static scan -Ndepụta npm-alias nwere ụdị e depụtara na ịbụ onye otu static-scan dị na +Ndepụta npm-alias nwere ụdị mbipụta na ndị otu static-scan dị na `config/quality/gate-manifest.json`. Gbaa `npm run check:gate-manifest` iji nyochaa aha script na command ndị kpọmkwem megide `package.json`; mgbakwunye, mwepụ na -mgbanwe command na-eme ka ma hook mpaghara ma ọrụ change-classification dị na CI daa. -Alias abụghị workflow job, matrix instance ma ọ bụ test case: a gaghị egosi ọnụ ọgụgụ ndị a -dị ka ihe ndị nwere ike dochie ibe ha. +mgbanwe command na-eme ka ma local hook ma ọrụ nhazi mgbanwe dị na CI daa. +Alias abụghị ọrụ workflow, matrix instance ma ọ bụ test case: a gaghị +egosi ọnụ ọgụgụ ndị a dịka ihe ndị nwere ike dochie ibe ha. Jiri `npm run quality:scan -- --list` ma ọ bụ `npm run quality:scan:fast -- --list` -iji nyochaa alias ndị ahọrọghị n'emeghị ha. Runner na-akpọ +iji nyochaa alias ndị ahọpụtara n'emeghị ha. Runner na-akpọ npm entrypoint, ya mere a na-echekwa runtime ya (gụnyere Bun ebe ahaziri ya). -Manifest na-edekọ alias ndị na-adịghị na profaịlụ ndị ahụ dị ka ndị a na-akpọ iche, a machibidoro -maintenance command na profaịlụ scan nke bụ read-only. +Manifest na-edekọ alias ndị na-anọghị na profaịlụ ndị ahụ dịka ndị a na-akpọ iche, ma +a machibidoro maintenance commands na profaịlụ scan nke read-only. -Profaịlụ ndị a na-ekpuchi naanị static scan. Ha anaghị akwado product test, -coverage, packaging, external check ma ọ bụ nnabata release zuru ezu nke onye a na-atụle. +Profaịlụ ndị a na-ekpuchi naanị static scan. Ha anaghị akwado product tests, +coverage, packaging, external checks ma ọ bụ nnabata release zuru ezu nke onye a na-atụle. Nnabata workflow na-eji `config/quality/admission-policy.json` ejikọrọ na `scripts/quality/admission-verdict.mjs`. Profaịlụ release-observer ka dị iche; -nyochaa check na receipt ndị metụtara ha n'onwe ha. Ndepụta e dere n'okpuru -bụ ntụaka, ọ bụghị ihe akaebe na gate gara n'ezie. - -Script dị n'okpuru `scripts/check/` (policy gate) na `scripts/quality/` (ratchet engine). -Ebe eziokwu CI si abịa bụ `.github/workflows/ci.yml`. - -### Ụzọ ngwa ngwa nke Release PR (`quality.yml`) - -`.github/workflows/quality.yml` na-agbakwụnye CI maka PR main/release, push -gaa na protected-branch, dispatch na merge group. PR na-eji fast check a na-enyocha site na path. E wepụrụ duplicate build -a gbanyụrụ kpamkpam; ezigbo check build/package/boot ka dị na CI. - -| Ọrụ | Oke | Na-egbochi | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | PR docs/code; ntụaka API docs na docs-all | Ee | -| `Fast Quality Gates` | PR code; static check, typecheck, dashboard typecheck, unit test ndị mgbanwe metụtara | Ee | -| `Forgotten sibling tests` | PR code; module gbanwere ndị a soro ruo static consumer na candidate sibling test; a na-akọ path barrel na dynamic-import dị ka nchọpụta ndụmọdụ, tinyere allowlist exception ndị e zoro aka na ha | **Ndụmọdụ** | -| `Vitest (fast-path)` | PR code; vitest suite ngwa ngwa | Ee | -| `Unit Tests fast-path` | PR code; unit suite nwere shard 4 | Ee | -| `No new ESLint warnings` | PR code; lint guard maara suppression | Ee, gụnyere fork | -| `Merge integrity (changelog + generated skills)` | PR na-abụghị draft; changelog na mmekọrịta generated skill | Ee, gụnyere fork | - -#### Akụkọ forgotten sibling tests - -`npm run check:forgotten-sibling-tests` na-eji ọzọ import resolver dị n'azụ map test-impact. -Maka production module ọ bụla gbanwere, ọ na-akọ chain ndị a pụrụ ikwughachi n'otu usoro -`changed module/symbol -> static consumer -> candidate sibling test` mgbe candidate -test adịghị na diff pull-request. A na-edobe nchịkọta Markdown na nsonaazụ JSON dị ka -artifact workflow `forgotten-sibling-tests` maka calibration tupu rollout ọ bụla na-egbochi. - -Mbupụgharị site na barrel na mbubata dynamic bụ naanị maka nchọpụta nsogbu resolution; ha anaghị emepụta -nchọpụta na-egbochi ihe. Mwepu ndị enyochara dị na +nyochaa check na receipt ndị metụtara ha n'onwe ha. Ndepụta nkọwa +dị n'okpuru bụ ntụaka, ọ bụghị ihe akaebe na gate gbara ọsọ n'ezie. + +Script dị n'okpuru `scripts/check/` (policy gates) na `scripts/quality/` (ratchet engine). +Isi mmalite eziokwu nke CI bụ `.github/workflows/ci.yml`. + +### Ụzọ-ngwa-ngwa maka Release PR (`quality.yml`) + +`.github/workflows/quality.yml` na-emeju CI na main/release PR, push gaa na protected-branch, +dispatch na merge group. PR na-eji fast checks nke path-filtered. E wepụrụ duplicate build +nke agbanyụrụ kpamkpam; ezigbo check build/package/boot ka dị na CI. + +| Ọrụ | Oke | Na-egbochi | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `Docs Gates (fast-path)` | PR docs/koodu; ntụaka docs API na docs-all | Ee | +| `Fast Quality Gates` | PR koodu; static checks, typecheck, dashboard typecheck, unit tests ndị mgbanwe metụtara | Ee | +| `Forgotten sibling tests` | PR koodu; a na-esochi module gbanwere ruo static consumers na candidate sibling tests; a na-akọ ụzọ barrel na dynamic-import dịka advisory diagnostics, tinyere mwepu allowlist ndị e zoro aka na ha | **Ndụmọdụ** | +| `Vitest (fast-path)` | PR koodu; fast vitest suite | Ee | +| `Unit Tests fast-path` | PR koodu; unit suite nwere shard 4 | Ee | +| `No new ESLint warnings` | PR koodu; lint guard na-eburu suppressions n'uche | Ee, gụnyere fork | +| `Merge integrity (changelog + generated skills)` | PR ndị na-abụghị draft; mmekọrịta changelog na generated skill | Ee, gụnyere fork | + +#### Akụkọ Forgotten sibling tests + +`npm run check:forgotten-sibling-tests` na-eji ọzọ import resolver dị n'azụ test-impact map. +Maka production module ọ bụla gbanwere, ọ na-akọ usoro +`changed module/symbol -> static consumer -> candidate sibling test` ndị na-agbanweghi agbanwe mgbe candidate +test adịghị na diff nke pull-request. A na-edobe nchịkọta Markdown na nsonaazụ JSON dịka +workflow artifact `forgotten-sibling-tests` maka calibration tupu rollout ọ bụla na-egbochi ihe. + +Mbugharị-ọpụpụ barrel na mbubata dynamic bụ naanị nyocha mkpebi ụzọ; ha anaghị emepụta +nchọpụta na-egbochi ihe. Ewezuga ndị a nyochara dị na `config/quality/forgotten-sibling-allowlist.json`. Ntinye ọ bụla ga-akpọrịrị consumer na candidate -test aha, nye ezi ihe kpatara ya kpọmkwem, ma jikọọ GitHub issue ma ọ bụ pull request. Ntinye ndị ahazighị nke ọma na-eme ka -usoro mechie. Mwepu enweghị ike izochi candidate test ehichapụrụ ma ọ bụ diff nke na-agbakwunye `.skip`/`.todo`; -ime ka assertion ghara isi ike na ụdị izochi ndị ọzọ ka bụ ọrụ nke gate -`check:test-masking` nke na-egbochi ihe n'onwe ya. +test aha, nye ihe kpatara ya kpọmkwem, ma jikọọ na GitHub issue ma ọ bụ pull request. Ntinye ndị ahazighị +nke ọma na-ada n'ọnọdụ mechiri emechi. Ewezuga enweghị ike izochi candidate test ehichapụla ma ọ bụ diff na-agbakwunye `.skip`/`.todo`; +ime ka assertion ghara isi ike na ụdị nzuzo ndị ọzọ ka bụ ọrụ nke gate +`check:test-masking` nke na-egbochi n'onwe ya. ### Ọrụ: `lint` -Ọ na-agba na PR ọ bụla gaa na `main`. Ọ na-egbochi merge ma ọ bụrụ na ọ daa. - -| Script (`npm run ...`) | Ihe ọ na-enyocha | Na-egbochi | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:node-runtime` | Ụdị Node.js dị n'ime oke ndị a na-akwado | Ee | -| `check:cycles` | Mbubata ndị na-eme okirikiri — modul `src/` + `open-sse/` niile | Ee | -| `check:route-validation:t06` | Zod schemas dị na route niile (amụma Tier 6) | Ee | -| `check:any-budget:t11` | Ọnụọgụ `@ts-expect-error // any` anaghị agafe budget (catraca Tier 11) | Ee | -| `check:provider-consistency` | Provider ọ bụla dị na `providers.ts` nwere ndenye kwekọrọ na ya na `providerRegistry.ts` (otu ahụkwa n’aka nke ọzọ, n’ime allowlist) | Ee | -| `check:model-lifecycle` | Tebụl routing atọ a na-eji aka elekọta na-anọgide kwekọọ na lifecycle snapshot echekwara n’ime repo (#11503): `FITNESS_TABLE` (`taskFitness.ts`) anaghị enye retired id ọ bụla `REGISTRY` nwere ike iziga akara; target ọ bụla nke `BUILT_IN_ALIASES` dị na `REGISTRY` ma ọ dịghị na retired-id snapshot; retired id ọ bụla ka dị na `REGISTRY` ka a na-ebuga n’ebe ọzọ ma ọ bụ depụta ya na `allowedRetiredInCatalog`; source ma ọ bụ target ọ bụla nke `DEFAULT_DEGRADATION_MAP` adịghị ka nke lara ezumike nka na snapshot ahụ. Nke a anaghị egosi na upstream dị ndụ na-enye model ugbu a. Offline — ọ na-atụnyere ya na `config/quality/model-lifecycle.json`, nke a na-eji aka eme ka ọ dị ọhụrụ site na `npm run quality:refresh-model-lifecycle` (netwọk; ejikọghị ya na CI). `allowedRetiredInCatalog` bụ burn-down ratchet: tinye ndenye naanị ma ọ bụrụ na e nwere tracking issue. | Ee | -| `check:fetch-targets` | `fetch("/api/...")` ọ bụla dị na `src/` nke akụkụ client na-ezo aka na `route.ts` dị adị n’ezie | Ee | -| `check:deps` | Dep niile enwere ike iji `npm install` wụnye n’ofe `package.json` ọ bụla dị na repo nọ na `dependency-allowlist.json`; a na-akanye package ọhụrụ ndị a na-etinyeghị version kpọmkwem ma ọ bụ ndị e slopsquat | Ee | -| `audit:deps` | `npm audit` (root + electron) — enweghị ndụmọdụ nwere ogo high/critical (ọ na-adakọ n’akụkụ ụfọdụ na osv `check:vuln-ratchet`; lee Rationalization Backlog) | Ee | -| `check:lockfile` | Izu oke nke `package-lock.json` — https registry, integrity hashes, enweghị host overrides | Ee | -| `check:licenses` | Ndepụta ikike SPDX anabatara maka ndabere mmepụta | Ee | -| `check:tracked-artifacts` | Enweghị artefaktị nrụpụta / symlink `node_modules` e tinyere na commit (ọ na-agbakwa na husky pre-commit; pre-push bụ nke e kpachapụrụ anya mee ka ọ dị mfe — #6716) | Ee | -| `check:ai-attribution` | Enweghị trailer AI/bot `Co-Authored-By` ma ọ bụ footer nke na-egosi mmepụta AI na commit, aha, ma ọ bụ ahụ PR — Iwu Siri Ike #16 (n'ime loop fast-gates nke `quality.yml` maka PR→`release/**` — ọ na-agụ payload event, ọ naghị eme ihe ma ọ bụrụ na ọ bụghị PR — yana nzọụkwụ naanị maka PR na lint nke `ci.yml` maka PR→`main`; tinyekwara hook husky `commit-msg`; a na-anabata ndị mmadụ dịka ndị odee ibe; #14436) | -| `check:vitest-exclusions` | Mwepụ Vitest ọ bụla na-akpọ aha issue nsuso ma pụta na `config/quality/vitest-exclusions.json` (#13204) | Ee | -| `check:file-size` | Ọ dịghị faịlụ source gafere oke e debere maka extension ọ bụla (ratchet: faịlụ buru ibu a kpọchiri akpọchi dị na ndepụta `frozen`) | Ee | -| `check:error-helper` | Nzaghachi njehie dị na executors/handlers na-eji `buildErrorBody()` / `sanitizeErrorMessage()` (Iwu Siri Ike #12) | Ee | -| `check:migration-numbering` | A na-enye faịlụ SQL migration nọmba n’usoro, na-enweghị oghere ma ọ bụ nọmba ndị emegharịrị | Ee | -| `check:public-creds` | Enweghị OAuth `client_id`/`client_secret` ma ọ bụ igodo Firebase Web e dere ozugbo n’èzí `publicCreds.ts` (Iwu Siri Ike #11) | Ee | -| `check:db-rules` | Enweghị SQL raw n’èzí modulu `src/lib/db/`; enweghị barrel-imports sitere na `localDb.ts` (Iwu Siri Ike #2/#5) | Ee | -| `check:known-symbols` | Ndị mmejuputa provider, atụmatụ routing, na ndị ntụgharị edebanyere na dispatch tables ha dabara na faịlụ ndị dị na disk — enweghị symbols ndị na-enweghị njikọ ma ọ bụ ndị a na-ekwupụtaghị | Ee | -| `check:route-guard-membership` | Route ọ bụla na-ebido child process bụ nke `isLocalOnlyPath()` kewara n’ụdị kwesịrị ekwesị (Iwu Siri Ike #15/#17) | Ee | -| `check:test-discovery` | Faịlụ `*.test.ts` / `*.spec.ts` ọ bụla dị na repo bụ ma ọ dịkarịa ala otu test runner na-anakọta ya (ratchet: ndepụta faịlụ na-enweghị njikọ dị na `test-discovery-baseline.json` nwere ike naanị ibelata) | Ee | -| `check:agent-skills-sync` | Ihe arụpụtara maka agent-skills kwekọrọ na katalọgụ isi mmalite ha (enweghị mgbanwe na-ekwesịghị ekwesị) | -| `check:provider-asset-provenance` | Akara/logo na akụ ndị provider nwere ndekọ ebe ha si bịa | -| `lint:json` | Faịlụ nhazi JSON na-agụpụta nke ọma ma na-emezu iwu lint nke repo | -| `typecheck:core` | Nchịkọta TypeScript na-enweghị njehie (naanị ịdọ aka ná ntị ndụmọdụ) | Ee | -| `typecheck:noimplicit:core` | `noImplicitAny` siri ike — nke a lekwasịrị anya n'ọdịnihu; ọtụtụ ebe oku dịbu adị ka chọrọ nkọwa ụdị | **Ndụmọdụ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` nke akọwapụtara naanị maka `src/app/(dashboard)/**` (#7033) — ndepụta ikike faịlụ 27 ahọpụtara nke `typecheck:core` anaghị etinye dashboard TSX ọ bụla, `next build` anaghị enyocha ụdị ya (`next.config.mjs` na-edobe `ignoreBuildErrors: true`), ya mere regressions nke identifier na-enweghị njikọ nọ ebe ahụ (#6625/#6909) anaghị apụta ìhè nye CI. A na-atụnyere ya na baseline ọnụ ọgụgụ emechiri emechi maka faịlụ ọ bụla/koodu TS ọ bụla (`config/quality/dashboard-typecheck-baseline.json`, otu usoro mmanye maka ihe ochie dị ka `check:known-symbols`) — naanị njehie ỌHỤRỤ karịrị ọnụ ọgụgụ baseline ga-eme ka gate daa; jiri `--update` belata baseline mgbe edozi njehie dịbu adị. | Ee | +Ọ na-agba na PR ọ bụla gaa `main`. Ọ na-egbochi merge ma ọ bụrụ na ọ daa. + +| Skripti (`npm run ...`) | Ihe ọ na-enyocha | Na-egbochi | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------- | +| `check:node-runtime` | Ụdị Node.js dị n'ime oke a na-akwado | Ee | +| `check:cycles` | Mbubata okirikiri n'ime `src/` + `open-sse/` niile (dabere na AST, e doziela `paths` nke tsconfig). Bare = ndụmọdụ, ọ na-edepụta okirikiri ndị ahụ. `check:cycles:ratchet` (nke CI na-agba) na-egbochi mgbe ọnụọgụ ahụ gafere oke `metrics.cycles` dị na `quality-baseline.json` — ugbu a ọ bụ 14, `direction: down`, ya mere ọ nwere ike naanị ibelata (#15159 G-01/G-02) | Ee (ratchet) | +| `check:route-validation:t06` | Schema Zod dị na route niile (iwu Tier 6) | Ee | +| `check:any-budget:t11` | Ọnụọgụ `@ts-expect-error // any` anaghị agafe budget (catraca Tier 11) | Ee | +| `check:provider-consistency` | Provider ọ bụla dị na `providers.ts` nwere ntinye kwekọrọ na ya na `providerRegistry.ts` (nakwa n’aka nke ọzọ, n’ime allowlist) | Ee | +| `check:model-lifecycle` | Tebụl routing atọ a na-eji aka elekọta na-anọgide kwekọọ na lifecycle snapshot e tinyere na repository (#11503): `FITNESS_TABLE` (`taskFitness.ts`) anaghị enye retired id ọ bụla `REGISTRY` nwere ike iziga akara; target ọ bụla nke `BUILT_IN_ALIASES` dị na `REGISTRY` ma ọ dịghị na retired-id snapshot; retired id ọ bụla ka dị na `REGISTRY` ka a na-ebuga n’ebe ọzọ ma ọ bụ depụta ya na `allowedRetiredInCatalog`; source ma ọ bụ target ọ bụla nke `DEFAULT_DEGRADATION_MAP` adịghịkwa ka nke retired na snapshot ahụ. Nke a anaghị egosi na upstream dị ndụ na-enye model ugbu a. Offline — ọ na-atụnyere ya na `config/quality/model-lifecycle.json`, nke a na-eji aka eme ka ọ dị ọhụrụ site na `npm run quality:refresh-model-lifecycle` (network; ejikọghị ya na CI). `allowedRetiredInCatalog` bụ burn-down ratchet: tinye entry naanị ma ọ bụrụ na e nwere tracking issue. | Ee | +| `check:fetch-targets` | `fetch("/api/...")` ọ bụla dị na client-side `src/` na-ejikọ na `route.ts` dị adị | Ee | +| `check:deps` | Dep niile enwere ike iji `npm install` wụnye n’ofe `package.json` ọ bụla dị na repo nọ na `dependency-allowlist.json`; a na-akọ package ọhụrụ ndị a na-etinyeghị pin ma ọ bụ ndị e mere slopsquat | Ee | +| `audit:deps` | `npm audit` (root + electron) — enweghị advisory dị elu/critical (ọ na-ekpuchi otu akụkụ nke osv `check:vuln-ratchet`; lee Rationalization Backlog) | Ee | +| `check:lockfile` | Iguzosi ike n’ezi ihe nke `package-lock.json` — HTTPS registry, integrity hashes, enweghị host overrides | Ee | +| `check:licenses` | Ndepụta ikike SPDX akwadoro maka ndabere mmepụta | Ee | +| `check:tracked-artifacts` | Enweghị ihe nrụpụta / symlink `node_modules` etinyere na commit (ọ na-agbakwa na husky pre-commit; e mere pre-push ka ọ dị mfe n'ebumnuche — #6716) | Ee | +| `check:ai-attribution` | Enweghị trailer AI/bot `Co-Authored-By` ma ọ bụ ederede ngwụcha na-egosi mmepụta AI n'ime commit, aha, ma ọ bụ ọdịnaya PR — Iwu Siri Ike #16 (n'ime loop fast-gates nke `quality.yml` maka PR→`release/**` — na-agụ payload nke event, ọ naghị eme ihe ma ọ bụrụ na ọ bụghị PR — yana nzọụkwụ naanị maka PR n'ime lint nke `ci.yml` maka PR→`main`; nakwa hook husky `commit-msg`; a na-ekwe ka ndị mmadụ bụrụ ndị odekọrọ ọnụ; #14436) | +| `check:vitest-exclusions` | Mwepu Vitest ọ bụla na-akpọ aha issue nsuso ma pụta na `config/quality/vitest-exclusions.json` (#13204) | Ee | +| `check:file-size` | Ọ dịghị faịlụ source gafere oke edobere maka extension ọ bụla (ratchet: faịlụ buru ibu ndị akpọchiri n'ime ndepụta `frozen`) | Ee | +| `check:error-helper` | Nzaghachi njehie n'ime executors/handlers na-eji `buildErrorBody()` / `sanitizeErrorMessage()` (Iwu Siri Ike #12) | Ee | +| `check:migration-numbering` | Faịlụ SQL migration nwere nọmba na-aga n'usoro, enweghị oghere ma ọ bụ nọmba e megharịrị ugboro abụọ | Ee | +| `check:public-creds` | Enweghị OAuth `client_id`/`client_secret` e dere ozugbo ma ọ bụ igodo Firebase Web n'èzí `publicCreds.ts` (Iwu Siri Ike #11) | Ee | +| `check:db-rules` | Enweghị SQL raw n'èzí modul `src/lib/db/`; enweghị barrel-imports sitere na `localDb.ts` (Iwu Siri Ike #2/#5) | Ee | +| `check:known-symbols` | Ndị executor nke provider, usoro routing, na ndị translator edebanyere aha na dispatch tables ha kwekọrọ na faịlụ ndị dị na disk — enweghị symbols ndị na-enweghị njikọ ma ọ bụ ndị a na-ekwupụtaghị | Ee | +| `check:route-guard-membership` | Route ọ bụla na-amalite child process ka `isLocalOnlyPath()` na-ekewa n'ụdị kwesịrị ekwesị (Iwu Siri Ike #15/#17) | Ee | +| `check:test-discovery` | Faịlụ `*.test.ts` / `*.spec.ts` ọ bụla dị na repo ka ọ dịkarịa ala otu test runner na-achịkọta (ratchet: ndepụta orphan dị na `test-discovery-baseline.json` nwere ike naanị ibelata) | Ee | +| `check:agent-skills-sync` | Artifakt ndị agent-skills emepụtara dabara na katalọgụ isi mmalite ha (enweghị mgbanwe na-atụghị anya ya) | +| `check:provider-asset-provenance` | Akara na akụ ndị provider nwere ndekọ ebe ha si | +| `lint:json` | A na-agụ faịlụ nhazi JSON nke ọma ma ha na-emezu iwu lint nke repo | +| `typecheck:core` | Nchịkọta TypeScript na-enweghị njehie (naanị ịdọ aka ná ntị ndụmọdụ) | Ee | +| `typecheck:noimplicit:core` | `noImplicitAny` siri ike — maka ọdịnihu; ọtụtụ ebe oku ndị dịbu adị ka chọrọ nkọwa ụdị | **Ndụmọdụ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` nke ejedebere na `src/app/(dashboard)/**` (#7033) — ndepụta nnabata faịlụ 27 ahọpụtara nke `typecheck:core` anaghị etinye TSX dashboard ọ bụla, `next build` anaghịkwa enyocha ụdị ya (`next.config.mjs` na-ahazi `ignoreBuildErrors: true`), ya mere mmeghachi omume ọjọọ nke njirimara na-enweghị onye nwe ha n'ebe ahụ (#6625/#6909) anaghị apụta ìhè nye CI. A na-atụnyere ya na baseline ọnụọgụ maka faịlụ ọ bụla/koodu TS ọ bụla nke agaghị agbanwe (`config/quality/dashboard-typecheck-baseline.json`, otu usoro mmanye maka ihe ochie dịka `check:known-symbols`) — naanị njehie ỌHỤRỤ karịrị ọnụọgụ dị na baseline ga-eme ka gate daa; jiri `--update` belata ya mgbe e meziri njehie dịbu adị. | Ee | ### Ọrụ: `quality-gate` -Ọ na-agba mgbe `test-coverage` gasịrị. Ọ na-egbochi merge ma ọ bụrụ na ọ daa. +Ọ na-agba mgbe `test-coverage` gachara. Ọ na-egbochi ijikọta ma ọ bụrụ na ọ daa. -| Skripụ | Ihe ọ na-enyocha | Na-egbochi | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `quality:collect` | Na-emepụta `quality-metrics.json` (ọnụọgụ ịdọ aka ná ntị ESLint, mkpuchi sitere na akụkọ shard ndị ejikọtara ọnụ) | Ee (ọ na-ebu ratchet ụzọ) | -| `quality:ratchet` | Metric ọ bụla dị na `quality-baseline.json` alaghị azụ (ịdọ aka ná ntị ESLint ≤ ntọala; mkpuchi ≥ ntọala) | Ee | -| `check:duplication` | Mmegharị koodu ugboro ugboro (jscpd@4) anaghị agafe ntọala dị na `quality-baseline.json` | Ee | -| `check:complexity` | Mgbagwoju anya cyclomatic n'ọkwa faịlụ anaghị agafe oke ahụ (ESLint `complexity` bụ isi + `max-lines-per-function`) | Ee | -| `check:cognitive-complexity` | Ratchet mgbagwoju anya nke uche (`eslint-plugin-sonarjs`) — ngafe ESLint dị iche; CI na-agba ha abụọ ọnụ dịka otu nzọụkwụ `check:complexity-ratchets` | Ee | -| `check:dead-code` | Ratchet mbupụ / faịlụ ndị a na-ejighị (knip) anaghị ala azụ ma e jiri ya tụnyere ntọala | Ee | -| `check:compression-budget` | Oke benchmark mkpakọ — opekempe nchekwa token maka engine ọ bụla agaghị ala azụ | Ee | -| `check:type-coverage` | Ratchet pasent nke e nyere ụdị (`type-coverage`) anaghị ala azụ; ọ na-anọchi ọtụtụ ọrụ `typecheck:noimplicit:core` | Ee | -| `check:codeql-ratchet` | Ọnụọgụ ọkwa ịdọ aka ná ntị CodeQL mepere emepe anaghị ala azụ (na-agụ site na `gh api`; na-awụfe n'enweghị nsogbu ma ọ bụrụ na token adịghị) — maka oge mmelite na mkpalite aka: lee "CodeQL ratchet" n'okpuru | Ee | +| Skript | Ihe ọ na-enyocha | Na-egbochi | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `quality:collect` | Na-emepụta `quality-metrics.json` (ọnụọgụ ịdọ aka ná ntị ESLint, mkpuchi sitere na akụkọ shard ejikọtara ọnụ) | Ee (ọ na-ebute ratchet) | +| `quality:ratchet` | Metrik ọ bụla dị na `quality-baseline.json` alaghachighị azụ (ịdọ aka ná ntị ESLint ≤ ntọala; mkpuchi ≥ ntọala) | Ee | +| `check:duplication` | Mmegharị koodu ugboro ugboro (jscpd@4) anaghị agafe ntọala dị na `quality-baseline.json` | Ee | +| `check:complexity` | Mgbagwoju anya cyclomatic n'ọkwa faịlụ anaghị agafe oke ahụ (ESLint `complexity` nke isi + `max-lines-per-function`) | Ee | +| `check:cognitive-complexity` | Ratchet mgbagwoju anya nke echiche (`eslint-plugin-sonarjs`) — nnyefe ESLint dị iche; CI na-eme ha abụọ n'otu nzọụkwụ `check:complexity-ratchets` | Ee | +| `check:dead-code` | Ratchet mbupụ / faịlụ ndị a na-ejighị (knip) anaghị alaghachi azụ ma e jiri ya tụnyere ntọala | Ee | +| `check:compression-budget` | Mmefu benchmark mkpakọ — oke kacha nta nke nchekwa token maka engine ọ bụla agaghị alaghachi azụ | Ee | +| `check:type-coverage` | Ratchet pasentị e nyere ụdị (`type-coverage`) anaghị alaghachi azụ; ọ na-anọchi ihe ka ukwuu n'ime `typecheck:noimplicit:core` | Ee | +| `check:codeql-ratchet` | Ọnụọgụ ịdọ aka ná ntị CodeQL mepere emepe anaghị alaghachi azụ (na-agụ site na `gh api`; na-amapụ n'enweghị nsogbu ma token adịghị) — maka ugboro ume ọhụrụ na mkpali aka: lee "CodeQL ratchet" n'okpuru | Ee | ### Ọrụ: `quality-extended` -Ọrụ a dum bụ nke ndụmọdụ (`continue-on-error: true`). Ratchet ndị dabeere na npm na-agba -n'ezie; scanner ndị mpụga na-etinye site na `gh release download` ma na-awụfe onwe ha (exit 0) +Ọrụ a dum bụ naanị ndụmọdụ (`continue-on-error: true`). Ratchet ndị dabeere na npm +na-arụ ọrụ n'ezie; a na-etinye ndị nyocha mpụga site na `gh release download`, ha na-amapụ onwe ha (exit 0) mgbe binary ka na-adịghị. -| Skripụ | Ihe ọ na-enyocha | Na-egbochi | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | -| `check:circular-deps` | Enweghị dependency ndị na-agba okirikiri (dpdm) | **Ndụmọdụ** | -| `check:bundle-size` | Nha bundle anaghị agafe oke ahụ | **Ndụmọdụ** | -| `check:secrets` | Nnyocha ihe nzuzo (gitleaks) — na-awụfe ma ọ bụrụ na binary adịghị | **Ndụmọdụ** | -| `check:vuln-ratchet` | Ọghọm nchekwa dependency (osv-scanner) anaghị ala azụ — na-awụfe ma ọ bụrụ na binary adịghị | **Ndụmọdụ** | -| `check:workflows` | Nnyocha lint workflow (actionlint + zizmor); scanner na-efu/rụrụ arụ, akụkọ na-ezighi ezi ma ọ bụ ntọala ratchet na-efu na-eme ka ọ daa dịka INCOMPLETE. Nchọpụta ziri ezi na-agbaso iwu strict/advisory/ratchet ahọpụtara | A chọrọ ka ọ gbaa; ratchet zizmor na-egbochi na CI | -| `check:openapi-breaking` | Mgbanwe na-emebi nkwekọrịta API ọha (`openapi.yaml`) ma e jiri ya tụnyere alaka ntọala (oasdiff) — na-emepụta `openapiBreaking=N`; na-awụfe ma ọ bụrụ na oasdiff adịghị ma ọ bụ enweghị ike ịchọta nkọwa ntọala | **Ndụmọdụ** | +| Skript | Ihe ọ na-enyocha | Na-egbochi | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ | +| `check:circular-deps` | Enweghị dependency na-agba okirikiri (dpdm) | **Ndụmọdụ** | +| `check:bundle-size` | Ogo bundle anaghị agafe oke ahụ | **Ndụmọdụ** | +| `check:secrets` | Nnyocha ihe nzuzo (gitleaks) — na-amapụ ma binary adịghị | **Ndụmọdụ** | +| `check:vuln-ratchet` | Adịghị ike nchekwa nke dependency (osv-scanner) anaghị alaghachi azụ — na-amapụ ma binary adịghị | **Ndụmọdụ** | +| `check:workflows` | Nnyocha workflow (actionlint + zizmor); ndị nyocha na-adịghị/ndị mebiri emebi, akụkọ na-ezighi ezi ma ọ bụ enweghị ntọala ratchet na-eme ka ọ daa dịka INCOMPLETE. Nchọpụta ziri ezi na-agbaso iwu strict/advisory/ratchet ahọpụtara | A ghaghị ime ya; ratchet zizmor na-egbochi na CI | +| `check:openapi-breaking` | Mgbanwe ndị na-emebi nkwekọrịta API ọha (`openapi.yaml`) ma e jiri ya tụnyere alaka ntọala (oasdiff) — na-emepụta `openapiBreaking=N`; na-amapụ ma oasdiff adịghị ma ọ bụ enweghị ike ịchọta nkọwa ntọala | **Ndụmọdụ** | ### Ọrụ: `docs-sync-strict` -Ọ na-agba na PR ọ bụla gaa na `main`. Ọ na-egbochi njikọta ma ọ bụrụ na ọ daa. - -| Skript | Ihe ọ na-enyocha | Na-egbochi | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| `check:docs-all` | Ọnụ ụzọ nyocha ukwu nke na-agba ọnụ ụzọ nyocha nta 6 dị n'okpuru n'otu n'otu | Ee | -| ↳ `check:docs-sync` | Ndakọrịta ụdị nke CHANGELOG / OpenAPI / llm.txt | Ee | -| ↳ `check:docs-counts` | Ọnụọgụ dị n'ime ederede (ọnụọgụ ndị na-eweta ọrụ, ọnụọgụ mbugharị, wdg.) nọ n'ime oke ratchet nke ọnụọgụ ndị dị adị | Ee | -| ↳ `check:env-doc-sync` | E depụtara env var ọ bụla dị na `.env.example` n'ime tebụl docs, ma nke ọ bụla dị na tebụl ahụ dịkwa na `.env.example` | Ee | -| ↳ `check:deprecated-versions` | Enweghị eriri ụdị a kagburu akwado n'ime docs | Ee | -| ↳ `check:doc-links` | Njikọ markdown ime dị na docs na-eduga na faịlụ ndị dị adị (usoro `[text]`/`(path)`) | Ee | -| ↳ `check:fabricated-docs` | Routes, env vars, iwu CLI, aha hook, na ụzọ faịlụ ndị e zoro aka na ha n'ime docs dị n'ime codebase. Ọnụ ụzọ siri ike site na `--strict`; ọ na-enye naanị ịdọ aka ná ntị ma flag adịghị. | Ee (site na `--strict` na CI) | -| `check:cli-i18n` | Eriri iwu CLI dị n'ime faịlụ locale i18n niile | Ee | -| `check:openapi-coverage` | Nkọwapụta OpenAPI na-ekpuchi ma ọ dịkarịa ala oke ala e ji ratchet tọọ nke routes ndị dị adị | Ee | -| `check:openapi-security-tiers` | Nkọwa ọkwa nchekwa dị na `openapi.yaml` dabara na nhazi nke `routeGuard.ts` | **Ndụmọdụ** | -| `check:openapi-routes` | Path ọ bụla dị na `openapi.yaml` na-eduga na `route.ts` dị adị (mgbochi echiche ụgha) | Ee | -| `check:docs-symbols` | Ntụaka `/api/...` ọ bụla dị na `docs/**/*.md` na-eduga na `route.ts` dị adị (mgbochi echiche ụgha) | Ee | -| `i18n translation drift` | Keys ndị a na-atụgharịbeghị n'ime faịlụ locale i18n — naanị dọọ aka ná ntị | **Ndụmọdụ** | +Na-arụ na PR ọ bụla gaa `main`. Na-egbochi ijikọ ma ọ daa. + +| Skripti | Ihe ọ na-enyocha | Na-egbochi | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| `check:docs-all` | Ọnụ ụzọ nchịkọta nke na-eme obere ọnụ ụzọ 6 ndị dị n'okpuru n'otu n'otu | Ee | +| ↳ `check:docs-sync` | Nkwekọrịta ụdị nke CHANGELOG / OpenAPI / llm.txt | Ee | +| ↳ `check:docs-counts` | Ọnụọgụ ndị dị n'ihe edere (ọnụọgụ ndị na-eweta ọrụ, ọnụọgụ mbugharị, wdg.) dị n'ime oke ratchet nke ọnụọgụ ndị bụ eziokwu | Ee | +| ↳ `check:env-doc-sync` | A kọwara env var ọ bụla dị na `.env.example` n'ime tebụl dọkụmentị, otu ahụkwa n'akụkụ nke ọzọ | Ee | +| ↳ `check:deprecated-versions` | Enweghị eriri ụdị a kagburu akwado n'ime dọkụmentị | Ee | +| ↳ `check:doc-links` | Njikọ markdown ime n'ime dọkụmentị na-eduga na faịlụ ndị dị adị (ụdị `[text]`/`(path)`) | Ee | +| ↳ `check:fabricated-docs` | Routes, env vars, iwu CLI, aha hook, na ụzọ faịlụ ndị e zoro aka na ha n'ime dọkụmentị dị n'ime codebase. Ọnụ ụzọ siri ike site na `--strict`; ọ na-ada nwayọ ma ọkọlọtọ ahụ adịghị. | Ee (site na `--strict` na CI) | +| `check:cli-i18n` | Eriri iwu CLI dị n'ime faịlụ locale i18n niile | Ee | +| `check:openapi-coverage` | Nkọwapụta OpenAPI na-ekpuchi ma ọ dịkarịa ala oke ala e ji ratchet tụọ nke routes ndị dị adị | Ee | +| `check:openapi-security-tiers` | Nkọwa ọkwa nchekwa dị na `openapi.yaml` kwekọrọ na nhazi ọkwa dị na `routeGuard.ts` | **Ndụmọdụ** | +| `check:openapi-routes` | Path ọ bụla dị na `openapi.yaml` na-eduga na `route.ts` dị adị (mgbochi echiche ụgha) | Ee | +| `check:docs-symbols` | Ntugharị aka `/api/...` ọ bụla dị na `docs/**/*.md` na-eduga na `route.ts` dị adị (mgbochi echiche ụgha) | Ee | +| `i18n translation drift` | Key ndị a na-atụgharịghị asụsụ n'ime faịlụ locale i18n — naanị ịdọ aka ná ntị | **Ndụmọdụ** | ### Ọrụ: `i18n-ui-coverage` -| Skript | Ihe ọ na-enyocha | Na-egbochi | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | -| `check-ui-keys-coverage` (inline) | Mkpuchi key i18n nke UI bụ ≥ 65% | Ee | -| `check-ui-value-drift` (inline) | **Value** Bekee e degharịrị anaghị ahapụ ntụgharị ochie ọ bụla | Ee | -| `check-new-key-coverage` (inline) | A tụgharịrị key Bekee **ọhụrụ** n'ime locale ọ bụla — a na-ajụ akara `__MISSING__:` | Ee | -| `check-translation-ratio` | Oke ntụgharị n'ezie n'otu locale (ihe yiri Bekee kpọmkwem / placeholder / ihe na-efu ndị na-anọghị na allowlist) agaghị agafe `config/quality/i18n-translation-baseline.json` + ohere mgbakwunye | **Ndụmọdụ** | +| Skripti | Ihe ọ na-enyocha | Na-egbochi | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check-ui-keys-coverage` (inline) | Mkpuchi key i18n nke UI bụ ≥ 65% | Ee | +| `check-ui-value-drift` (inline) | Idegharị **value** Bekee anaghị ahapụ ntụgharị asụsụ ochie ọ bụla | Ee | +| `check-new-key-coverage` (inline) | A tụgharịrị key Bekee **ọhụrụ** n'ime locale ọ bụla — a na-ajụ akara `__MISSING__:` | Ee | +| `check-translation-ratio` | Oke ntụgharị asụsụ n'ezie maka locale ọ bụla (nke yiri Bekee / placeholder / ihe na-efu ndị na-anọghị na allowlist) agaghị agafe `config/quality/i18n-translation-baseline.json` + ohere mgbakwunye | **Ndụmọdụ** | -Ọ chọrọ `fetch-depth: 0` — ọnụ ụzọ value-drift na-atụnyere mgbanwe dị n'etiti `en.json` na merge base. +Ọ chọrọ `fetch-depth: 0` — ọnụ ụzọ value-drift na-atụnyere ọdịiche dị n'etiti `en.json` na merge base. -#### `check-ui-value-drift` — ọnụ ụzọ ntụgharị ochie +#### `check-ui-value-drift` — ọnụ ụzọ ntụgharị asụsụ ochie -Ọ na-achọpụta otu mmejọ i18n nke ọnụ ụzọ ndị ọzọ enweghị ike ịhụ site n'usoro ha: a na-edegharị value Bekee -ma ntụgharị ndị e si na Bekee _ochie_ mepụta ka na-adị, ya mere -ndị ọrụ na-abụghị ndị Bekee ka na-agụ ederede e dere n'olu nwere ntụkwasị obi, mana nke na-ezighi ezi ugbu a. +Ọ na-ejide otu ndaghachi azụ i18n nke ọnụ ụzọ ndị ọzọ enweghị ike ịhụ site n'ụdị nhazi: a na-edegharị value Bekee +ma ntụgharị asụsụ ndị sitere na Bekee _gara aga_ ka na-adị, nke mere na +ndị ọrụ na-abụghị ndị Bekee na-aga n'ihu na-agụ ozi e ji obi ike dee ma bụrụzi nke na-ezighị ezi. -Nke a abanyela n'ezie na mbipụta. E degharịrị `oauthModal.googleOAuthWarning` mgbe enyemaka nbanye Antigravity -batara (#5203); **39 n'ime locale 43** ka nwere ederede na-agwa ndị njikwa ka ha "detuo URL -zuru ezu ma mado ya n'okpuru" — usoro na-enweghị ike ịrụcha maka provider ahụ. A chọpụtaghị ya -ruo #8463 n'ihi na: +Nke a mere n'ezie mgbe ewepụtara ya. E degharịrị `oauthModal.googleOAuthWarning` mgbe onye enyemaka nbanye Antigravity +rutere (#5203); **39 n'ime locale 43** ka nwere ederede na-agwa ndị na-ahụ maka sistemụ ka ha “detuo +URL zuru ezu ma mado ya n'okpuru” — usoro nke na-enweghị ike ịrụcha maka onye na-eweta ọrụ ahụ. Ọ +ghị ihe a chọpụtara ruo #8463 n'ihi na: -- `sync-ui-keys` na-agbakwunye naanị keys ndị **na-adịghị**, ọ naghị agbakwunye ndị **ochie**; -- `check-ui-keys-coverage` na-agụ ọnụnọ key, ya mere a na-agụ ntụgharị ochie dịka nke e kpuchiri; -- `check-translation-drift` na-esochi oyiri akwụkwọ `docs/i18n//**.md` — - ọ naghị agụ `src/i18n/messages/*.json`. Ọ na-egbochi n'ime ọrụ `docs-sync-strict` kemgbe - re-sync nke 2026-09: dezie core doc → `npm run i18n:run -- --files=` (n'ọkwa section, dị ọnụ ala). +- `sync-ui-keys` na-agbakwunye naanị key ndị **na-adịghị**, ọ naghị agbakwunye ndị **merela ochie**; +- `check-ui-keys-coverage` na-agụ ọnụnọ key, ya mere a na-ewere ntụgharị asụsụ ochie dị ka nke ekpuchiri; +- `check-translation-drift` na-esochi dọkụmentị oyiri `docs/i18n//**.md` — + ọ naghị agụ `src/i18n/messages/*.json`. Ọ na-egbochi n'ọrụ `docs-sync-strict` kemgbe + mmekọrịta ọzọ nke 2026-09: dezie dọkụmentị isi → `npm run i18n:run -- --files=` (n'ogo ngalaba, dị ọnụ ala). -**Ọ na-aghọta diff, ọ bụghị nke baseline na-akwado.** Ọ na-atụnyere `en.json` dị na merge base na -working tree; maka key ọ bụla nke uru Bekee ya gbanwere, locale ọ bụla ka nwere +**Ọ na-elebara diff anya, ọ naghị adabere na baseline.** Ọ na-atụnyere `en.json` dị na merge base na +working tree; maka key ọ bụla nke value Bekee ya gbanwere, locale ọ bụla ka nwere ntụgharị a na-emetụghị aka abụrụla nke ochie. Nke a kpachapụrụ anya **na-eme ka ụgwọ dịbu adị ghara ịgbanwe** — diff -enweghị ike igosi ụdị Bekee ochie ntụgharị dịtere aka siri na ya pụta, ya mere gate ahụ na-enyocha -naanị ihe mgbanwe dị ugbu a metụrụ. Nhọrọ ọzọ (baseline hash maka key ọ bụla) ga-achọ -faịlụ emepụtara nke ruru ~600 KB, nke ji okpukpu 3 karịa baseline kachasị ukwuu dị ugbu a, ma na-agbanwe na PR i18n ọ bụla. +enweghị ike igosi ụdị Bekee ochie ntụgharị dịteworo aka si na ya pụta, ya mere gate ahụ na-enyocha +naanị ihe mgbanwe dị ugbu a metụtara. Nhọrọ ọzọ (baseline hash maka key ọ bụla) ga-achọ +faịlụ emepụtara nke ruru ~600 KB, okpukpu 3 nke baseline kachasị ukwuu dị ugbu a, ma na-agbanwe na PR i18n ọ bụla. E nwere ụzọ abụọ iji mezuo ya: 1. melite ntụgharị ndị emetụtara, ma ọ bụ -2. tọọ ha ka ha bụrụ `__MISSING__:` — runtime ga-enyezi Bekee e doziri - (`src/i18n/request.ts::deepMergeFallback`, #7258), key ahụ abanyezie n'ahịrị ntụgharị. +2. tọọ ha ka ha bụrụ `__MISSING__:` — mgbe ahụ runtime ga-enye Bekee e meziri + (`src/i18n/request.ts::deepMergeFallback`, #7258), key ahụ ga-abanyekwa n'ahịrị ntụgharị. -Ọ bụrụ na **pụtara** eriri ahụ gbanwere, ọ ka mma **ịgbanwe aha key ahụ**: key ọhụrụ agaghị eketa +Ọ bụrụ na **ihe eriri ahụ pụtara** gbanwere, họrọ **ịgbanwe aha key ahụ**: key ọhụrụ enweghị ike iketa ntụgharị ochie. Nke ahụ bụ usoro #8463 jiri. ```bash -npm run i18n:check-value-drift # siri ike (nke CI na-agba) +npm run i18n:check-value-drift # nke siri ike (nke CI na-agba) npm run i18n:check-value-drift:warn # naanị nye akụkọ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Ọ na-eji 0 pụta tinyere `SKIP reason=base-unresolved` mgbe enweghị ike ịgụ catalog dị na base (shallow +Ọ na-eji 0 pụọ yana `SKIP reason=base-unresolved` mgbe enweghị ike ịgụ catalog ntọala ahụ (shallow clone na-enweghị base ref), dịka `check-openapi-breaking`. ### Ọrụ: `i18n` -Matrix nyocha i18n zuru ezu (otu ọrụ maka locale ọ bụla). Ọrụ ahụ dum bụ ndụmọdụ. +Matrix nkwado i18n zuru ezu (otu ọrụ maka locale ọ bụla). Ọrụ ahụ dum bụ ndụmọdụ. -| Script | Ihe ọ na-enyocha | Mgbochi | -| ------------------------------- | ----------------------------------- | ----------------------------------------------------- | -| `validate_translation.py quick` | Izu oke ntụgharị maka locale ọ bụla | **Ndụmọdụ** (`continue-on-error: true` n'ọrụ ahụ dum) | +| Script | Ihe ọ na-enyocha | Ọ na-egbochi | +| ------------------------------- | --------------------------------- | ----------------------------------------------------- | +| `validate_translation.py quick` | Izuzu ntụgharị maka locale ọ bụla | **Ndụmọdụ** (`continue-on-error: true` n'ọrụ ahụ dum) | ### Ọrụ: `pr-test-policy` -Ọ na-agba naanị na pull requests. +Ọ na-agba naanị na pull request. -| Script | Ihe ọ na-enyocha | Mgbochi | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `check:pr-test-policy` | PR ndị gbanwere production code na `src/`, `open-sse/`, `electron/`, ma ọ bụ `bin/` ga-etinyerịrị ma ọ bụ melite tests (Hard Rule #8) | Ee | -| `check:test-masking` | Faịlụ test ndị gbanwere anaghị ebelata mkpokọta assert ma ọ bụ tinye tautology `assert.ok(true)` | Ee | -| `check:pr-evidence` | Ahụ PR na-akpọtụrụ ihe akaebe test/VPS maka mgbanwe ahụ (ọ na-eme Hard Rule #18 ka ọ rụọ ọrụ site n'iji grep nyochaa prose PR — adịghị ike, lee Backlog) | Ee | +| Script | Ihe ọ na-enyocha | Ọ na-egbochi | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `check:pr-test-policy` | PR ndị na-agbanwe production code na `src/`, `open-sse/`, `electron/`, ma ọ bụ `bin/` ga-etinyerịrị ma ọ bụ melite test (Iwu Siri Ike #8) | Ee | +| `check:test-masking` | Faịlụ test ndị gbanwere anaghị ebelata ngụkọta assert ma ọ bụ tinye tautology `assert.ok(true)` | Ee | +| `check:pr-evidence` | Ahụ PR na-ezo aka na ihe akaebe test/VPS maka mgbanwe ahụ (na-eme Iwu Siri Ike #18 ka igwe rụọ site n'ịchọgharị ederede PR — adịghị ike, lee Backlog) | Ee | ### Ọrụ: `test-vitest` Ọ na-agba mgbe `build` gachara. Ọ na-egbochi merge ma ọ bụrụ na ọ daa. -| Suite | Ihe ọ na-enyocha | Mgbochi | -| ---------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Sava MCP (ngwaọrụ 110), autoCombo, cache — vitest runner | Ee | -| `test:vitest:ui` | Tests nke component UI — vitest runner | **Na-egbochi** — e wepụrụ ọdịda ndị dịbu adị kpọmkwem na `vitest.config.ts`; ọdịda ọhụrụ na-eme ka ọrụ ahụ daa | +| Suite | Ihe ọ na-enyocha | Ọ na-egbochi | +| ---------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (ngwaọrụ 110), autoCombo, cache — vitest runner | Ee | +| `test:vitest:ui` | Test UI component — vitest runner | **Na-egbochi** — e wepụrụ ọdịda ndị dịbu adị n'ụzọ doro anya na `vitest.config.ts`; ọdịda ọhụrụ ga-eme ka ọrụ ahụ daa | -### Workflows abalị (ahaziri oge, ndụmọdụ) +### Workflow abalị (ahaziri oge, ndụmọdụ) -Ndị a na-agba dịka nhazi cron si dị (yana `workflow_dispatch`), ọ dịghị mgbe ha na-agba na PR. Ha niile bụ ndụmọdụ. +Ndị a na-agba dịka nhazi cron si dị (nakwa `workflow_dispatch`), ọ dịghị mgbe ha na-agba na PR. Ha niile bụ ndụmọdụ. -| Workflow | Ihe ọ na-enyocha | Mgbochi | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `nightly-property` | fast-check property tests nwere random seed + ọnụ ọgụgụ run dị elu | **Ndụmọdụ** | -| `nightly-resilience` | gate uto heap, chaos fault-injection, ibu/soak k6 | **Ndụmọdụ** | -| `nightly-llm-security` | nche injection promptfoo (block mode) + nyocha garak (a na-awụli ha ma ọ bụrụ na enweghị provider secret) | **Ndụmọdụ** | -| `nightly-schemathesis` | fuzzing contract OpenAPI (schemathesis) megide OmniRoute dị ndụ site n'iji `docs/openapi.yaml` — na-ekpughe mmebi spec / 500 ndị a na-ejighị aka (Fase 8 B.4) | **Ndụmọdụ** | -| `nightly-mutation` | akara mutation-testing Stryker n'elu fast unit lane — mutants ndị lanarịrị na-ekpughe assert ndị na-adịghị ike | **Ndụmọdụ** | -| `nightly-compat` | matrix ndakọrịta engine Node n'ofe oke `engines.node` ndị a na-akwado | **Ndụmọdụ** | +| Workflow | Ihe ọ na-enyocha | Ọ na-egbochi | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `nightly-property` | Test property fast-check nwere seed enweghị usoro + ọnụ ọgụgụ mgbagharị dị elu | **Ndụmọdụ** | +| `nightly-resilience` | gate uto heap, chaos fault-injection, ibu/soak k6 | **Ndụmọdụ** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + probe garak (a na-amafe ha ma ọ bụrụ na provider secret adịghị) | **Ndụmọdụ** | +| `nightly-schemathesis` | Fuzzing nkwekọrịta OpenAPI (schemathesis) megide OmniRoute dị ndụ site n'iji `docs/openapi.yaml` — na-ekpughe mmebi spec / 500 ndị a na-edozighị (Nkeji 8 B.4) | **Ndụmọdụ** | +| `nightly-mutation` | Akara mutation-testing Stryker n'elu fast unit lane — mutant ndị lanarịrị na-ekpughe assert ndị na-adịghị ike | **Ndụmọdụ** | +| `nightly-compat` | Matrix ndakọrịta engine Node gafee oke `engines.node` ndị a na-akwado | **Ndụmọdụ** | --- @@ -537,43 +537,43 @@ njehie njikọ, ọ naghịkwa emebi oge ọrụ. --- -## Ndepụta Ihe A Ga-eme Maka Mmekọrịta Uche (nyocha ROI — Agba 9 Ebili 3) +## Ndepụta Ọrụ Nchịkọta Echiche (nyocha ROI — Agba 9 Ebili Mmiri 3) -E jiri `ci.yml` tụnyere ma dozie ndepụta a na 2026-06-17 (ụdị nke gara aga hapụrụ +E mere ka ndepụta a kwekọọ na `ci.yml` na 2026-06-17 (ụdị gara aga ewepụghị `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Nyocha ROI e mere na nchịkọta ahụ e doziri +`check:codeql-ratchet`, `check:pr-evidence`). Nyocha ROI nke usoro ahụ e mere ka o kwekọọ chọpụtara ndị a dị ka ndị a ga-atụle maka ime ka usoro dị mfe. **Njikọ ndị ahụ bụ mgbanwe CI -nke usoro ọrụ; ịgbanwe/ịhapụ ha bụ mkpebi iwu e debeere onye na-ahụ maka usoro ahụ.** Ọ dịghị ihe dị n'okpuru -etinyegoro n'ọrụ. +a na-eme n'ụzọ akpaaka; ntụgharị/nhichapụ ndị ahụ bụ mkpebi iwu ọrụ edobere maka onye na-ahụ maka ya.** Ọ dịghị ihe dị n'okpuru ebe a +etinyela n'ọrụ ugbu a. **Ihe ọzọ a na-edeghị n'elu** (ndụmọdụ, mgbaàmà dị ala): ọrụ `docs-lint` -(markdownlint + Vale, ọrụ ahụ dum nwere `continue-on-error`) na usoro ọrụ nyocha kwụụrụ onwe ha +(markdownlint + Vale, ọrụ ahụ dum bụ `continue-on-error`) na usoro ọrụ nyocha kwụụrụ onwe ha `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` dị na -`quality-baseline.json` mana ejikọtaghị ya na ratchet na-egbochi ihe na `ci.yml` — metrik ahụ +`quality-baseline.json` mana ejikọghị ya na ratchet na-egbochi ihe na `ci.yml` — metrik ahụ enweghịzi ihe jikọrọ ya ugbu a. -### Jikọta / wepụ oyiri (usoro ọrụ nkịtị, ihe ize ndụ dị ala) +### Jikọta / wepụ nkwugharị (nke akpaaka, ihe ize ndụ dị ala) -A kwadoro onye ọ bụla a họpụtara site n'iji ọnọdụ gate dị ndụ na 2026-06-17 (tụkwasị obi-ma-nyochaa); -ọtụtụ njikọ ndị yiri ka ha “doro anya” mechara bụrụ ndị na-ezo ụgwọ ọrụ, ya mere ha **abụghị** ihe a ga-etinye ozugbo n'enweghị nsogbu. +A nyochara onye ọ bụla a tụpụtara megide ọnọdụ gate dị ndụ na 2026-06-17 (tụkwasị obi mana nyochaa); +ọtụtụ njikọ ndị yiri ihe "doro anya" mechara bụrụ na ha na-ezo ụgwọ teknụzụ ma ha **abụghị** ihe enwere ike dochie ozugbo n'enweghị nsogbu. -- **`check:docs-sync` na-agba ugboro abụọ** — ọ na-agba naanị ya n'ọrụ `lint`, ọzọkwa n'ime `check:docs-all` (`docs-sync-strict`) nakwa na husky pre-commit hook. ✅ **EMECHALA** — ewepụla ịkpọ ya naanị ya na `lint`. -- **Nnyocha CVE** — ❌ **Ọ BỤGHỊ njikọ dị ọcha.** `audit:deps` na-akwụsị ozugbo ma ọ bụrụ na e nwere CVE ọ bụla dị elu/dị oke egwu; `check:vuln-ratchet` (osv) na-akwụsị naanị mgbe e nwere _nlaghachi azụ_ ma e jiri ya tụnyere baseline (ugbu a 1 MODERATE). Ihe ha pụtara dị iche — iwepụ `audit:deps` ga-eme ka gate zuru oke maka ihe dị elu/dị oke egwu funahụ. Debe ha abụọ. -- **Nchọpụta cycle** — ❌ **Ọ BỤGHỊ njikọ dị ọcha.** `check:circular-deps` (dpdm) na-akọ **cycle 91** (ọ bụ ya mere o ji bụrụ naanị ndụmọdụ); enweghị ike ime ka ọ bụrụ ihe na-egbochi ruo mgbe e buru ụzọ dozie ha, ọ na-ekpuchikwa akụkụ sara mbara karịa `check:cycles` ahọpụtara nke ọma ma na-aga nke ọma. Debe `check:cycles` ka ọ bụrụ ihe na-egbochi; idozi cycle dpdm 91 ahụ bụ backlog nke ya. -- **Mgbagwoju anya** — ✅ **EMECHALA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): otu ngafe ESLint, na-agụ site na ruleId ka baseline cyclomatic+max-lines na cognitive wee nọgide na-anọrọ onwe ha; `check:complexity` / `check:cognitive-complexity` nke ọ bụla ka dị maka `--update` mpaghara. -- **Mgbochi echiche ụgha nke `/api`** — ✅ **EMECHALA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): otu ndepụta FS nke `src/app/api`, openapi-routes + docs-symbols ka na-enye akụkọ nke ha n'onwe ha; nke ọ bụla ka dị maka ịgba ọsọ mpaghara. -- **`check:node-runtime` na-agba n'ọrụ 11** — ⚠️ **ROI dị ala.** Nke ọ bụla bụ runner dị iche, nyocha ahụ na-ewekwa <1s; mkpokọta oge a ga-echekwa bụ ~10s, ma e jiri ya tụnyere ịtụfu nchebe dị ọnụ ala maka ọrụ ọ bụla. O kwesịghị ọgba aghara mgbanwe ahụ. -- **`typecheck:noimplicit:core` na CI lint** — ✅ **ewepụla ya n'ọrụ lint** (ọ bụbu ndụmọdụ nwere `continue-on-error`); elu type na-egbochi bụ `typecheck:core` + `check:type-coverage`. E debere script mpaghara. +- **`check:docs-sync` na-agba ugboro abụọ** — naanị ya na ọrụ `lint`, ma ọzọkwa n'ime `check:docs-all` (`docs-sync-strict`) na husky pre-commit hook. ✅ **EMECHAA** — ewepụla ọkpụkpọ ya kwụụrụ onwe ya na `lint`. +- **Nyocha CVE** — ❌ **Ọ bụghị njikọ dị ọcha.** `audit:deps` na-ada ozugbo ma ọ bụrụ na e nwere CVE ọ bụla dị elu/dị oke egwu; `check:vuln-ratchet` (osv) na-ada naanị mgbe e nwere _nlaghachi azụ_ ma e jiri ya tụnyere baseline (ugbu a 1 MODERATE). Ihe ha pụtara dị iche — iwepụ `audit:deps` ga-eme ka gate zuru oke maka ihe dị elu/dị oke egwu funahụ. Debe ha abụọ. +- **Nchọpụta okirikiri** — ✅ **EMECHAA** (#15159 G-01/G-02). Ederede ochie ebe a kpọrọ `check:cycles` gate "na-acha akwụkwọ ndụ, ahọpụtara nke ọma" ma kwado ka ọ nọgide na-egbochi n'ihi na `check:circular-deps` (dpdm) gosipụtara okirikiri 91. Agba akwụkwọ ndụ ahụ bụ **agba akwụkwọ ndụ ụgha**: `check:cycles` nyochara subdirectory 5 (faịlụ 450), dakọtara naanị static `import|export … from`, ma wepụ specifier `@/` na `@omniroute/open-sse/` niile, ya mere ọ pụghị ịhụ okirikiri dynamic-import + alias ndị jupụtara na repo ahụ. Edozila ya: gate ahụ na-agafe ugbu a na `src` + `open-sse` (faịlụ 5023), na-anakọta specifier site na TypeScript AST (ya mere `import("…")` na-agụta mana `typeof import("…")` nọ n'ọnọdụ type anaghị agụta), ma na-edozi tsconfig `paths`. Ọ na-achọta okirikiri **14**, ọ bụghị 0. Ebe ọ bụ na enweghị ike idozi okirikiri 14 dịbu adị n'ime gate PR, `check:cycles` abụrụla **ratchet** ugbu a (`--ratchet`, oke elu `metrics.cycles.value = 14` na `quality-baseline.json`, `direction: down`) — ọ na-egbochi _nlaghachi azụ_ ọ bụla, ọnụ ọgụgụ ahụ nwekwara ike naanị ibelata. CI na-agba `npm run check:cycles:ratchet`. Mbelata ya ga-eso **A-01** gaa n'ihu. `check:circular-deps` (dpdm) na-anọgide dịka ndụmọdụ nke na-enye echiche nke abụọ sara mbara. +- **Mgbagwoju anya** — ✅ **EMECHAA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): otu ngafe ESLint, na-agụta dịka ruleId ka baseline cyclomatic+max-lines na cognitive wee nọgide na-anọpụ iche; `check:complexity` / `check:cognitive-complexity` nke ọ bụla ka dị maka `--update` mpaghara. +- **Mgbochi echiche ụgha nke `/api`** — ✅ **EMECHAA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): otu ndepụta FS nke `src/app/api`; openapi-routes + docs-symbols ka na-enye akụkọ iche iche; nke ọ bụla ka dị maka ịgba ọsọ mpaghara. +- **`check:node-runtime` na-agba n'ọrụ 11** — ⚠️ **ROI dị ala.** Nke ọ bụla bụ runner dị iche, nyocha ahụ erughị 1s; mkpokọta oge echekwara bụ ihe dịka ~10s, mana nke ahụ ga-efu nchebe dị ọnụ ala n'ọrụ ọ bụla. O kwesịghị ọgba aghara mgbanwe ahụ. +- **`typecheck:noimplicit:core` na CI lint** — ✅ **ewepụla ya na ọrụ lint** (ọ bụbu ndụmọdụ `continue-on-error`); mpaghara type na-egbochi bụ `typecheck:core` + `check:type-coverage`. E debere script mpaghara. -### Gbanwee / kpebie (iwu onye na-ahụ maka usoro) +### Tụgharịa / kpebie (iwu ọrụ onye na-ahụ maka ya) -- `check:openapi-security-tiers` (ndụmọdụ) — ❌ **A PỤGHỊ ịgbanwe ya ozugbo n'enweghị nsogbu.** Ọ na-eji 0 pụọ mana ọ na-adọ aka ná ntị na ọtụtụ route `traffic-inspector` dị n'okpuru `LOCAL_ONLY_API_PREFIXES` enweghị nkọwa `x-loopback-only: true`. Ime ka ọ bụrụ iwu chọrọ ka e buru ụzọ tinye nkọwa ndị ahụ na `openapi.yaml`. -- `typecheck:noimplicit:core` (ndụmọdụ) — ratchet `check:type-coverage` na-egbochi ekpuchilarị ọtụtụ ọrụ ya. Gbanwee ya ka ọ bụrụ ratchet ma ọ bụ wepụ ngafe `tsc` nke abụọ nke na-eme otu ọrụ ahụ. -- `test:vitest:ui` (ugbu a ọ bụ **ihe na-egbochi**) — ewepụla ọdịda ndị dịbu adị kpọmkwem na `vitest.config.ts` site na nkọwa nsuso `// #8618`; ọdịda ọhụrụ ga-eme ka ọrụ ahụ daa. -- `check:secrets` (gitleaks, ratchet na-egbochi nke a kpọchiri na false-positive 3 edekọtara) — tinye 3 ahụ na allowlist iji ruo 0, ma ọ bụ wedata ya ka ọ bụrụ ndụmọdụ. Ọ na-ekpuchi ụfọdụ otu ebe GitHub native secret-scanning + `check:public-creds` na-ekpuchi. -- `check:pr-evidence` (na-egbochi, na-eji grep enyocha prose dị na PR-body) — ihe ize ndụ false-positive dị elu; iwepụ ya ga-eme ka mmanye Hard Rule #18 ghara isi ike, ya mere nke a bụ ezigbo mkpebi iwu. -- `semgrep` (ndụmọdụ kwụụrụ onwe ya) — ya na CodeQL na-ekpuchi otu ezinụlọ OWASP ụfọdụ; jikọta baseline ya na ratchet ma ọ bụ wepụ ya. +- `check:openapi-security-tiers` (ndụmọdụ) — ❌ **Enweghị ike ịtụgharị ya n'ụzọ dị ọcha.** Ọ na-eji 0 pụọ mana na-adọ aka ná ntị na ọtụtụ route `traffic-inspector` dị n'okpuru `LOCAL_ONLY_API_PREFIXES` enweghị annotation `x-loopback-only: true`. Ịmanye ya chọrọ ibu ụzọ tinye annotation ndị ahụ na `openapi.yaml`. +- `typecheck:noimplicit:core` (ndụmọdụ) — ratchet `check:type-coverage` na-egbochi ihe anọchiwo ọtụtụ ọrụ ya. Tụgharịa ya ka ọ bụrụ ratchet ma ọ bụ wepụ ngafe `tsc` nke abụọ na-eme otu ọrụ ọzọ. +- `test:vitest:ui` (ugbu a **na-egbochi**) — ewepụla ọdịda ndị dịbu adị kpọmkwem na `vitest.config.ts` site na comment nsuso `// #8618`; ọdịda ọhụrụ ga-eme ka ọrụ ahụ daa. +- `check:secrets` (gitleaks, ratchet na-egbochi nke akpọchiri na false-positive 3 edere n'akwụkwọ) — tinye atọ ahụ na allowlist ka ọ ruo 0, ma ọ bụ wedata ya ka ọ bụrụ ndụmọdụ. Ọ na-ekpuchi ụfọdụ ọrụ GitHub native secret-scanning + `check:public-creds` na-arụ. +- `check:pr-evidence` (na-egbochi, na-eji grep chọọ prose dị na PR-body) — ihe ize ndụ false-positive dị elu; iwepụ ya ga-eme ka mmanye Hard Rule #18 ghara ịdị ike, ya mere nke a bụ ezigbo mkpebi iwu ọrụ. +- `semgrep` (ndụmọdụ kwụụrụ onwe ya) — ya na CodeQL na-ekpuchi ụfọdụ otu OWASP; jikọta baseline ya na ratchet ma ọ bụ wepụ ya. --- diff --git a/docs/i18n/ig/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ig/docs/compression/COMPRESSION_GUIDE.md index fe005c060bb4..416f636718c6 100644 --- a/docs/i18n/ig/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ig/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ kama Bekee. N'ahụ nwere `messages`, nkwụsị ọdịnaya (`shouldBypassCavemanOutputMode()` na `open-sse/services/compression/outputMode.ts`) na-enyocha ozi atọ ikpeazụ ma na-awụfe ụdị maka ntụgharị ahụ dum mgbe ha dabara na nchekwa ya, omume na-enweghị ike ịgbanwe, -nkọwa, ma ọ bụ okwu nwere mmetụta n'usoro. Nkwụsị ahụ na-agba ọsọ n'agbanyeghị ihe dashboard's -**Auto-Clarity Bypass** toggle (`cavemanOutputMode.autoClarity`) edobere. +nkọwa, ma ọ bụ okwu nwere mmetụta n'usoro. Nkwụsị ahụ na-agba ọsọ mgbe **Auto-Clarity Bypass** toggle (`cavemanOutputMode.autoClarity`) dị na dashboard's na-arụ ọrụ, nke bụ ntọala mbụ; mgbe atụchiri ya, ụdị ndị a họpụtara na-emetụ na ntụgharị ndị ahụkwa. Mgbe nkwụsị ahụ kwere ka ntụgharị ahụ gafee, `placeSystemInstruction()` (otu faịlụ ahụ), nke na-adịghị emepụta `messages[0]` ọhụrụ, na-etinye ngọngọ ahụ n'ime nke mbụ n'ime ndị a ọ hụrụ: diff --git a/docs/i18n/ig/docs/reference/ENVIRONMENT.md b/docs/i18n/ig/docs/reference/ENVIRONMENT.md index 18c5b6c2caa8..de5582d8a8e7 100644 --- a/docs/i18n/ig/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ig/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Nzuzo Ndị Dị Mkpa -A **ga-etinyerịrị** ndị a tupu agba mbụ. Na-enweghị ha, ngwa ahụ agaghị ekwe ịmalite ma ọ bụ ọ ga-eji ntọala ndabara na-adịghị nchebe rụọ ọrụ. - -| Mgbanwe | Ọ Dị Mkpa | Ndabara | Faịlụ Isi Mmalite | Nkọwa | -| ---------------------------- | ----------------- | ------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Ee** | _(ọ dịghị)_ | `src/lib/auth` | Ọ na-abịanye aka ma na-enyocha kuki nnọkọ dashboard niile (JWT). Jiri `openssl rand -base64 48` mepụta ya. | -| `API_KEY_SECRET` | **Ee** | _(ọ dịghị)_ | `src/lib/db/apiKeys.ts` | Igodo nzuzo AES maka ụkpụrụ igodo API echekwara na SQLite. Jiri `openssl rand -hex 32` mepụta ya. | -| `INITIAL_PASSWORD` | **Ee** | `CHANGEME` | Skripụ bootstrap | Ọ na-edobe okwuntughe mbụ nke dashboard onye nchịkwa (ọ dabara na ndabara `.env.example` — e mere ka o doo anya na ọ naghị echebe iji manye mgbanwe). **Gbanwee ya tupu ojiji mbụ.** Mgbe ịbanyechara, gbanwee ya site na Dashboard → Settings → Security. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ee** (mmepụta) | _(edobeghị)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Nzuzo nkekọrịta maka njikọ WebSocket Codex Responses nke ime. Ọ na-enyocha arịrịọ njikọ dị n'etiti Electron/ihe nnyefe WS nke ihe nchọgharị na OmniRoute. ⚠️ **A CHỌRỌ YA na mmepụta — mgbe edobeghị ya, a na-ajụ arịrịọ njikọ WS niile.** Jiri `openssl rand -base64 32` mepụta ya. | -| `OMNIROUTE_SW_BUILD_ID` | Mba | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID doro anya maka imebi cache service-worker nke shei PWA (#11779); ọ bụ nke mbụ n'usoro mkpebi. | -| `SOURCE_VERSION` | Mba | _(edobeghị)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nke abụọ n'usoro ahụ — ndị na-ewu PaaS (dịka ụdị Heroku) na-edobe ya dịka commit ebugara. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Mba | _(e sitere na ya nweta)_ | `src/shared/components/PwaRegister.tsx` | Uru ọha nke oge owuwu nke klayenti ji edebanye `/sw.js?v=…`; a na-enweta ya site na abụọ dị n'elu, emesịa git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Mba (akpaghị aka) | _(akpaghị aka n'oge mmalite ọ bụla)_ | `src/server/authz/policies/management.ts` | Nzuzo nke usoro ọ bụla nke na-egosi na stampụ peer-IP a tụkwasịrị obi sitere na sava HTTP nke OmniRoute n'onwe ya (`scripts/dev/peer-stamp.mjs`). Middleware authz na-atụkwasị ọnọdụ mpaghara arịrịọ obi (mgbochi loopback/LAN nke ụzọ LOCAL_ONLY) naanị mgbe stampụ ahụ nwere token a. A na-emepụta ya akpaghị aka n'oge mmalite ọ bụla — hapụ ya ka edobeghị; naanị kpọgide ya maka nhazi nwere ọtụtụ usoro nke ga-ekekọrịrị stampụ ahụ. | -| `OMNIROUTE_TRUSTED_PROXIES` | Mba | _(edobeghị)_ | `scripts/dev/peer-stamp.mjs` | IP ma ọ bụ oke CIDR nke reverse proxies, nke kọma kewara, ndị ndepụta ikwe/ịjụ IP nwere ike ikwere `X-Forwarded-For` / `X-Real-IP` ha, gbakwunyere loopback, adreesị netwọkụ nkeonwe na nsọtụ Cloudflare, nke a na-atụkwasị obi mgbe niile. A chọrọ ya naanị maka proxy nọ na adreesị ọha ọ bụla ọzọ; ma ọ bụghị ya, adreesị proxy ahụ n'onwe ya bụ ihe nzacha ahụ na-enyocha. | +A na-emepụta akpaghị aka, na mgbalị mbụ, nzuzo ndị a na-edobeghị mana enyere ha akara **Ee** (`JWT_SECRET`, `API_KEY_SECRET`), ma na-eche ha na `server.env` dị na akwụkwọ ndekọ data — tinye ha n'ụzọ doro anya mgbe akwụkwọ ndekọ ahụ na-adịru naanị nwa oge. Ihe zọpụtara pụrụ iche bụ `OMNIROUTE_WS_BRIDGE_SECRET`: anaghị echekwa ya na `server.env` — ebe mmalite sava na-emepụta uru na-adị nwa oge n'oge mgbalite ọ bụla nke naanị njikọ dị n'ime usoro nwere ike iji, ya mere ndị ahịa njikọ mpụga chọrọ ka a edobe ya n'ụzọ doro anya. + +| Mgbanwe | Ọ Dị Mkpa | Ndabara | Faịlụ Isi Mmalite | Nkọwa | +| ---------------------------- | ----------------- | ------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Ee** | _(ọ dịghị)_ | `src/lib/auth` | Ọ na-abịanye aka ma na-enyocha kuki nnọkọ dashboard niile (JWT). Jiri `openssl rand -base64 48` mepụta ya. | +| `API_KEY_SECRET` | **Ee** | _(ọ dịghị)_ | `src/lib/db/apiKeys.ts` | Igodo nzuzo AES maka ụkpụrụ igodo API echekwara na SQLite. Jiri `openssl rand -hex 32` mepụta ya. | +| `INITIAL_PASSWORD` | Mba | _(edobeghị)_ | `src/lib/auth/managementPassword.ts` | Na-edobe okwuntughe dashboard n'oge mmalite mgbe edobeghị okwuntughe ọ bụla; a na-eleghara ya anya ozugbo echekwara okwuntughe. Ntinye npm na nke sitere na isi mmalite na-edepụta `.env.example` ka ọ bụrụ `.env`, ya mere ha na-amalite na ihe nnọchite anya ya ama ama n'ọha, `CHANGEME`: dochie ya tupu mmalite mbụ, ma ọ bụ tinye `INITIAL_PASSWORD=` (ihe efu) na `.env` ahụ iji mepụta okwuntughe na ọkachamara nhazi n'oge ọbịa mbụ, dịka uru edobeghị (ndabara ihe onyonyo Docker) si eme. Gbanwee okwuntughe echekwabara site na Dashboard → Settings → Security, ma ọ bụ gbaa `omniroute reset-password` (`node bin/reset-password.mjs` na edetu isi mmalite) na `DATA_DIR` edobere na akwụkwọ ndekọ data nke sava. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ee** (mmepụta) | _(edobeghị)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Nzuzo nkekọrịta maka njikọ WebSocket Codex Responses nke ime. Ọ na-enyocha arịrịọ njikọ dị n'etiti Electron/ihe nnyefe WS nke ihe nchọgharị na OmniRoute. ⚠️ **A CHỌRỌ YA na mmepụta — mgbe edobeghị ya, a na-ajụ arịrịọ njikọ WS niile.** Jiri `openssl rand -base64 32` mepụta ya. | +| `OMNIROUTE_SW_BUILD_ID` | Mba | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID doro anya maka imebi cache service-worker nke shei PWA (#11779); ọ bụ nke mbụ n'usoro mkpebi. | +| `SOURCE_VERSION` | Mba | _(edobeghị)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nke abụọ n'usoro ahụ — ndị na-ewu PaaS (dịka ụdị Heroku) na-edobe ya dịka commit ebugara. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Mba | _(e sitere na ya nweta)_ | `src/shared/components/PwaRegister.tsx` | Uru ọha nke oge owuwu nke klayenti ji edebanye `/sw.js?v=…`; a na-enweta ya site na abụọ dị n'elu, emesịa git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Mba (akpaghị aka) | _(akpaghị aka n'oge mmalite ọ bụla)_ | `src/server/authz/policies/management.ts` | Nzuzo nke usoro ọ bụla nke na-egosi na stampụ peer-IP a tụkwasịrị obi sitere na sava HTTP nke OmniRoute n'onwe ya (`scripts/dev/peer-stamp.mjs`). Middleware authz na-atụkwasị ọnọdụ mpaghara arịrịọ obi (mgbochi loopback/LAN nke ụzọ LOCAL_ONLY) naanị mgbe stampụ ahụ nwere token a. A na-emepụta ya akpaghị aka n'oge mmalite ọ bụla — hapụ ya ka edobeghị; naanị kpọgide ya maka nhazi nwere ọtụtụ usoro nke ga-ekekọrịrị stampụ ahụ. | +| `OMNIROUTE_TRUSTED_PROXIES` | Mba | _(edobeghị)_ | `scripts/dev/peer-stamp.mjs` | IP ma ọ bụ oke CIDR nke reverse proxies, nke kọma kewara, ndị ndepụta ikwe/ịjụ IP nwere ike ikwere `X-Forwarded-For` / `X-Real-IP` ha, gbakwunyere loopback, adreesị netwọkụ nkeonwe na nsọtụ Cloudflare, nke a na-atụkwasị obi mgbe niile. A chọrọ ya naanị maka proxy nọ na adreesị ọha ọ bụla ọzọ; ma ọ bụghị ya, adreesị proxy ahụ n'onwe ya bụ ihe nzacha ahụ na-enyocha. | ### Iwu Mepụta @@ -215,38 +215,38 @@ OmniRoute na-eji **SQLite** (site na `better-sqlite3`) maka nchekwa data niile n ## 4. Nchekwa & Nnyocha Njirimara -| Mgbanwe | Ndabara | Faịlụ Isi Mmalite | Nkọwa | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt a na-ejikọta na ihe njirimara ngwaike maka imepụta akara mkpịsị aka igwe. Gbanwee ya maka mbugharị ọ bụla iji hụ na e kewapụrụ ha. | -| `OMNIROUTE_CLI_SALT` | _(edoghị ya = salt a na-emepụta na mberede maka nrụnye ọ bụla ma chekwaa ya na `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC maka iwepụta token nyocha njirimara CLI mpaghara. Ịtọ uru a na-eme ka token CLI niile dị n'igwe ahụ gbanwee ma na-ebute ụzọ mgbe niile karịa salt echekwara. Lee `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Na-etinye ọkọlọtọ `Secure` na kuki nnọkọ. **Ọ ga-abụrịrị `true`** mgbe a na-arụ ọrụ n'azụ HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware API | Mgbe ọ bụ `true`, arịrịọ proxy `/v1/*` niile ga-etinyerịrị igodo API ziri ezi. **Ọkọlọtọ a anaghị egbochi `GET /v1/models`**, nke na-agbaso ọnọdụ nbanye dashboard (`requireAuthForModels`) kama — ya mere `401` sitere na `/v1/models` APỤTAGHỊ na e chekwara inference. Lee `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Na-enye ohere igosipụta uru igodo API zuru ezu na UI Dashboard. Enwere ike ịhazi ya site na Feature Flags nke Dashboard; ọ bụ ihe egwu nchekwa na instance ndị a na-ekekọrịta. | -| `NO_LOG_API_KEY_IDS` | _(efu)_ | `src/lib/compliance/index.ts` | ID igodo API ndị e ji kọma kewaa, bụ́ ndị anaghị eso ndekọ arịrịọ (nrube isi GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(edoghị ya = enweghị oke)_ | `src/shared/utils/apiKeyPolicy.ts` | Atụmatụ ndabere nke ọnụ ọgụgụ arịrịọ kwa ụbọchị a na-etinye na igodo API ndị kọlụm `rate_limits` ha bụ null. Ọ bụrụ na edoghị ya ma ọ bụ na ọ tọgbọ chakoo: enweghị oke ezoro ezo (#2289, #11017). `0` bụkwa otu ihe ahụ (enweghị oke). Integer dị mma N na-eme ka N/ụbọchị, 5N/izu, 20N/ọnwa rụọ ọrụ. Uru na-abụghị efu nke ahaziri n'ụzọ na-ezighi ezi na-alaghachi na oke ochie nke 1000/ụbọchị, 5000/izu, 20000/ọnwa. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Oke kachasị anabatara maka nha ahụ arịrịọ. Ọ na-ajụ payload ndị gafere oke a. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ahụ arịrịọ n'ezie ruru ma ọ bụ gafere oke a na-ewere mgbazinye nnabata heavyweight nke atomic ma bụrụkwa nke usoro mpaghara tupu parsing JSON (ụzọ BYTE, gụnyere `POST /v1/responses`). Otu ụzọ mgbapụ healthy-headroom [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ahụ dịka nke nhazi ya dị arọ; `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ka na-egbochi ya ka [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ghara ịlaghachi. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Oke kachasị siri ike nke ụzọ chat, nke a na-amanye megide bytes a gụrụ n'oge nnabata nwere oke, gụnyere arịrịọ ndị `Content-Length` ha na-efu, na-ezighi ezi, ma ọ bụ na-abụghị eziokwu; nke gafere oke na-enweta `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(edoghị — enweghị oke ọnụ ọgụgụ arịrịọ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** oke ọnụ ọgụgụ ARỊRỊỌ ochie a na-arụ ọrụ ugbu a naanị mgbe edobere ya kpọmkwem. Mgbe a hapụrụ ya n'enweghị ntọala (ndabara), a na-achịkwa nnabata chat dị arọ kama site na `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — mmefu BYTE a na-enweta na-akpaghị aka, nke a tụrụ dabere na oke ebe nchekwa n'ezie nke process n'ime **otu process** (otu V8 heap). Arịrịọ `/v1/responses` abụọ na-agafe ibe ha, nke ọ bụla nwere ihe dịka token 750k, na-eme ka heap dị ihe dịka 12 Gi daa (#7849) — nke a bụ ịdọ aka ná ntị banyere **mmefu ebe nchekwa**, ọ bụghị oke ngwaahịa siri ike nke 2. Process dị mma (heap dị n'okpuru shed ratio) NWERE IKE ịnabata ọtụtụ `/v1/responses` ogologo n'otu oge site na `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Ọtụtụ iri client SSE ogologo (40–50) bụ okwu metụtara heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ọ bụghị “oke 2”. Ibulite nke a n'amaghị ama iji “jiri host ahụ” ga-eweghachite #7849. Mụbaa **heaps** site na **`DATA_DIR` N nọọrọ onwe ha** (#11024); ejikwala `replicas>1` n'otu faịlụ SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(enwetara na-akpaghị aka)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ntọala nnọchi maka mmefu byte nnabata a na-enweta na-akpaghị aka (25% nke oke ebe nchekwa V8/cgroup nke ka nta, e kewara ya site na mmụba nwa oge 8x). A na-amachi uru ndị enwetara na ndị edobere kpọmkwem n'etiti 8 MiB–2 GiB. Body karịrị mmefu dị irè na-ada ozugbo site na `413 body_exceeds_budget`; mpi dị n'etiti body ndị enwere ike ijere nke ọ bụla ozi ka nwere ike ịnwalegharị site na `503`. Client SSE ogologo 40–50 na-arụ ọrụ n'otu oge bụ okwu metụtara mmefu a + heap, ọ bụghị “oke 2” siri ike. Gụọ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health` tupu imegharị ntọala. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Shed ratio nke nrụgide heap (`heapUsed / heap_size_limit`) maka nnabata BYTE na STRUCTURE dị arọ (#10183, #10268, #10437). A na-ajụ arịrịọ dị arọ ọzọ gafere `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` site na `503` enwere ike ịnwalegharị naanị mgbe heap DỊKWA n'ogo ratio a ma ọ bụ karịa; mgbe heap dị mma, a na-anabata ya kama site na healthy-headroom. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ndabara `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ikike mgbakwunye nwere oke maka ụzọ ngwa ngwa nke healthy-heap (#10437) na **ma** STRUCTURE **ma** BYTE (`admitChatRequest`, gụnyere body ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Na-enweghị oke a, arịrịọ ọ bụla ji ọrụ n'aka mana heap ya dị mma na-agafe nnabata n'enweghị oke kachasị. Ozugbo lease ndị na-arụ ọrụ n'otu oge site na ụzọ healthy-heap ruru ọnụ ọgụgụ a, arịrịọ ndị ọzọ ji ọrụ n'aka na-adaba n'otu ụzọ ichere-nwere-oke/shed ahụ a na-eji n'okpuru ezigbo nrụgide heap. `0` na-ewepụ ngafe a kpamkpam. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Ọnụ ọgụgụ ozi nke na-ekewa arịrịọ chat dịka nke dị arọ ọbụna mgbe body ya dị n'okpuru oke byte. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Ọnụ ọgụgụ tool nke na-ekewa arịrịọ chat dịka nke dị arọ ọbụna mgbe body ya dị n'okpuru oke byte. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Atụmatụ token dabere n'ogo string nke kpachapụrụ anya, nke na-ekewa arịrịọ dịka nke dị arọ; nke a bụ proxy maka ọnụ nnabata, ọ bụghị tokenization ịgba ụgwọ nke provider. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (agbanyụrụ) | `src/shared/middleware/chatBodyAdmission.ts` | Oke akụkọ chat nhọrọ nke a ga-eji naanị ma ọ bụrụ na agbanyere ya. Agbanyụrụ ya na ndabara: ọnụ ọgụgụ ozi bụ iwu deployment, ọ bụghị njirimara zuru ụwa ọnụ nke arịrịọ, na itinye oke ebe a na-ajụ mkparịta ụka site na `413` ikpeazụ tupu pipeline mkpakọ enwee ike ime ka e nwee ike ijere ha ozi. `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` na shed nke nrụgide heap na-amachi uto heap. Tọọ uru dị mma na deployment nwere oke ebe nchekwa nke chọrọ oke siri ike; ihe gafere oke ga-enweta `413` ahaziri nke na-egosi na achọrọ mkpakọ. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Oke siri ike maka nzaghachi upstream na-abụghị streaming nke a na-echekwa kpamkpam n'ime ebe nchekwa. Mgbe ọ gafere nke a, a na-akagbu upstream reader, arịrịọ ahụ adaakwa ngwa ngwa kama ịga n'ihu na-amụba string na-enweghị oke ruo mgbe heap gwụrụ. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Oke bytes wire a na-ebufe site na nkụnye isi nzaghachi nke upstream. Mgbe ọ gafere oke a, a na-ewepụ nkụnye isi ndị nwere mkpa dị ala (dịka, `x-codex-*`, `x-oai-request-id`) iji nọrọ n'ime oke nkụnye isi reverse-proxy ndị a na-ejikarị. Tọọ ya elu ka ebufekwuo metadata upstream, mana nke a ga-eme ka nha nkụnye isi nzaghachi bawanye. | -| `CORS_ORIGIN` | _(edoghị ya)_ | `src/server/cors/origins.ts` | Ndepụta ikike CORS nke otu origin ochie. Họrọ `CORS_ALLOWED_ORIGINS` maka nrụnye ọhụrụ. CORS bụ naanị maka ndị ahịa API ihe nchọgharị sitere na origin ọzọ; ide ihe na dashboard nke enyochala njirimara na-eji arịrịọ same-origin tinyere nchedo CSRF jikọrọ na session kama. | -| `CORS_ALLOWED_ORIGINS` | _(edoghị ya)_ | `src/server/cors/origins.ts` | Ndepụta ikike CORS nke rịkọm kewara. Agaghị eziga wildcard ọ bụla belụsọ ma ahaziri `CORS_ALLOW_ALL=true` n'ụzọ doro anya. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Ụzọ mgbapụ maka mmepe naanị, nke na-emegharị `Origin` ihe nchọgharị ọ bụla. Emela ka ọ rụọ ọrụ na nrụnye a na-ekekọrịta ma ọ bụ nke production. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Gbochie oku provider ndị na-ezubere oke IP private/loopback/link-local. Gbanyụọ ya naanị na env nnwale ndị e kewapụrụ iche. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Kwe ka URL provider na-atụ aka na netwọkụ private/local (localhost, 192.168.x.x, 10.x.x.x, wdg.). **A CHỌRỌ YA maka provider ndị mmadụ ji aka ha host** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Mgbe ọ bụ `false`, dashboard na-ajụ nkwado URL local. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Kwe ka a tinye/kwado provider nọ na adreesị local/private (127.0.0.1, localhost, LAN, oke private) — nke a metụtara naanị ụzọ nkwado provider. **Ndabara bụ `true`** (local-first); tọọ `false` iji manye mgbochi siri ike nke na-ekwe naanị adreesị public. A ka na-egbochi endpoint cloud-metadata (169.254.169.254, metadata.google.internal) n'agbanyeghị nhazi a. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Kwe ka route `/v1/audio/*` (ntụgharị ọdịyo gaa na ederede, okwu, ntụgharị asụsụ) jiri node provider dakọtara na OpenAI nke a na-host n'èzí localhost. A na-agbanyụ ya na ndabara — iziga ọdịyo na host remote na-agbanwe njirimara egress, ya mere ọ ga-abụrịrị mkpebi operator doro anya. A na-ekwe node loopback/private (localhost, 127.0.0.1, 172.16-31.x) mgbe niile, nhazi a anaghịkwa emetụta ha. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Kwe ka `POST /v1/rerank` (yana usoro rerank loopback nke engine ebe nchekwa) jiri node provider dakọtara na OpenAI nke a na-host n'èzí localhost — igbe LAN ma ọ bụ peer Tailscale na-agba TEI, Infinity, vLLM, wdg. A na-agbanyụ ya na ndabara — iziga ya na host remote na-agbanwe njirimara egress, ya mere ọ ga-abụrịrị mkpebi operator doro anya. A na-ekwe node loopback (localhost, 127.0.0.1, 172.16-31.x) mgbe niile, nhazi a anaghịkwa emetụta ha. Node remote ga-agafekwa iwu URL outbound nke provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); anaghị eziga arịrịọ na host cloud-metadata ma ọlị. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Mgbe enyere OIDC aka, gbanyụọ nbanye okwuntughe ka ndị ọrụ nwee ike nyochaa njirimara naanị site na OIDC Single Sign-On. A na-anabatakwa alias nkịtị `OIDC_DISABLE_PASSWORD_LOGIN`; Dashboard Feature Flag nwere otu key ahụ nwere mkpa karịa ya. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias nkịtị nke `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Mgbanwe | Ndabara | Faịlụ Isi Mmalite | Nkọwa | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt a na-ejikọta na ihe njirimara ngwaike maka imepụta akara mkpịsị aka igwe. Gbanwee ya maka mbugharị ọ bụla iji hụ na e kewapụrụ ha. | +| `OMNIROUTE_CLI_SALT` | _(edoghị ya = salt a na-emepụta na mberede maka nrụnye ọ bụla ma chekwaa ya na `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC maka iwepụta token nyocha njirimara CLI mpaghara. Ịtọ uru a na-eme ka token CLI niile dị n'igwe ahụ gbanwee ma na-ebute ụzọ mgbe niile karịa salt echekwara. Lee `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Na-etinye ọkọlọtọ `Secure` na kuki nnọkọ. **Ọ ga-abụrịrị `true`** mgbe a na-arụ ọrụ n'azụ HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware API | Mgbe ọ bụ `true`, arịrịọ proxy `/v1/*` niile ga-etinyerịrị igodo API ziri ezi. **Ọkọlọtọ a anaghị egbochi `GET /v1/models`**, nke na-agbaso ọnọdụ nbanye dashboard (`requireAuthForModels`) kama — ya mere `401` sitere na `/v1/models` APỤTAGHỊ na e chekwara inference. Lee `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Na-enye ohere igosipụta uru igodo API zuru ezu na UI Dashboard. Enwere ike ịhazi ya site na Feature Flags nke Dashboard; ọ bụ ihe egwu nchekwa na instance ndị a na-ekekọrịta. | +| `NO_LOG_API_KEY_IDS` | _(efu)_ | `src/lib/compliance/index.ts` | ID igodo API ndị e ji kọma kewaa, bụ́ ndị anaghị eso ndekọ arịrịọ (nrube isi GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(edoghị ya = enweghị oke)_ | `src/shared/utils/apiKeyPolicy.ts` | Atụmatụ ndabere nke ọnụ ọgụgụ arịrịọ kwa ụbọchị a na-etinye na igodo API ndị kọlụm `rate_limits` ha bụ null. Ọ bụrụ na edoghị ya ma ọ bụ na ọ tọgbọ chakoo: enweghị oke ezoro ezo (#2289, #11017). `0` bụkwa otu ihe ahụ (enweghị oke). Integer dị mma N na-eme ka N/ụbọchị, 5N/izu, 20N/ọnwa rụọ ọrụ. Uru na-abụghị efu nke ahaziri n'ụzọ na-ezighi ezi na-alaghachi na oke ochie nke 1000/ụbọchị, 5000/izu, 20000/ọnwa. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Oke kachasị anabatara maka nha ahụ arịrịọ. Ọ na-ajụ payload ndị gafere oke a. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ahụ arịrịọ n'ezie ruru ma ọ bụ gafere oke a na-ewere mgbazinye nnabata heavyweight nke atomic ma bụrụkwa nke usoro mpaghara tupu parsing JSON (ụzọ BYTE, gụnyere `POST /v1/responses`). Otu ụzọ mgbapụ healthy-headroom [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ahụ dịka nke nhazi ya dị arọ; `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ka na-egbochi ya ka [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ghara ịlaghachi. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Oke kachasị siri ike nke ụzọ chat, nke a na-amanye megide bytes a gụrụ n'oge nnabata nwere oke, gụnyere arịrịọ ndị `Content-Length` ha na-efu, na-ezighi ezi, ma ọ bụ na-abụghị eziokwu; nke gafere oke na-enweta `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(edoghị — enweghị oke ọnụ ọgụgụ arịrịọ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** oke ọnụ ọgụgụ ARỊRỊỌ ochie a na-arụ ọrụ ugbu a naanị mgbe edobere ya kpọmkwem. Mgbe a hapụrụ ya n'enweghị ntọala (ndabara), a na-achịkwa nnabata chat dị arọ kama site na `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — mmefu BYTE a na-enweta na-akpaghị aka, nke a tụrụ dabere na oke ebe nchekwa n'ezie nke process n'ime **otu process** (otu V8 heap). Arịrịọ `/v1/responses` abụọ na-agafe ibe ha, nke ọ bụla nwere ihe dịka token 750k, na-eme ka heap dị ihe dịka 12 Gi daa (#7849) — nke a bụ ịdọ aka ná ntị banyere **mmefu ebe nchekwa**, ọ bụghị oke ngwaahịa siri ike nke 2. Process dị mma (heap dị n'okpuru shed ratio) NWERE IKE ịnabata ọtụtụ `/v1/responses` ogologo n'otu oge site na `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Ọtụtụ iri client SSE ogologo (40–50) bụ okwu metụtara heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ọ bụghị “oke 2”. Ibulite nke a n'amaghị ama iji “jiri host ahụ” ga-eweghachite #7849. Mụbaa **heaps** site na **`DATA_DIR` N nọọrọ onwe ha** (#11024); ejikwala `replicas>1` n'otu faịlụ SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(enwetara na-akpaghị aka)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ntọala nnọchi maka mmefu byte nnabata a na-enweta na-akpaghị aka (25% nke oke ebe nchekwa V8/cgroup nke ka nta, e kewara ya site na mmụba nwa oge 8x). A na-amachi uru ndị enwetara na ndị edobere kpọmkwem n'etiti 8 MiB–2 GiB. Body karịrị mmefu dị irè na-ada ozugbo site na `413 body_exceeds_budget`; mpi dị n'etiti body ndị enwere ike ijere nke ọ bụla ozi ka nwere ike ịnwalegharị site na `503`. Client SSE ogologo 40–50 na-arụ ọrụ n'otu oge bụ okwu metụtara mmefu a + heap, ọ bụghị “oke 2” siri ike. Gụọ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health` tupu imegharị ntọala. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Shed ratio nke nrụgide heap (`heapUsed / heap_size_limit`) maka nnabata BYTE na STRUCTURE dị arọ (#10183, #10268, #10437). A na-ajụ arịrịọ dị arọ ọzọ gafere `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` site na `503` enwere ike ịnwalegharị naanị mgbe heap DỊKWA n'ogo ratio a ma ọ bụ karịa; mgbe heap dị mma, a na-anabata ya kama site na healthy-headroom. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ndabara `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ikike mgbakwunye nwere oke maka ụzọ ngwa ngwa nke healthy-heap (#10437) na **ma** STRUCTURE **ma** BYTE (`admitChatRequest`, gụnyere body ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Na-enweghị oke a, arịrịọ ọ bụla ji ọrụ n'aka mana heap ya dị mma na-agafe nnabata n'enweghị oke kachasị. Ozugbo lease ndị na-arụ ọrụ n'otu oge site na ụzọ healthy-heap ruru ọnụ ọgụgụ a, arịrịọ ndị ọzọ ji ọrụ n'aka na-adaba n'otu ụzọ ichere-nwere-oke/shed ahụ a na-eji n'okpuru ezigbo nrụgide heap. `0` na-ewepụ ngafe a kpamkpam. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Ọnụ ọgụgụ ozi nke na-ekewa arịrịọ chat dịka nke dị arọ ọbụna mgbe body ya dị n'okpuru oke byte. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Ọnụ ọgụgụ tool nke na-ekewa arịrịọ chat dịka nke dị arọ ọbụna mgbe body ya dị n'okpuru oke byte. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Atụmatụ token dabere n'ogo string nke kpachapụrụ anya, nke na-ekewa arịrịọ dịka nke dị arọ; nke a bụ proxy maka ọnụ nnabata, ọ bụghị tokenization ịgba ụgwọ nke provider. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (agbanyụrụ) | `src/shared/middleware/chatBodyAdmission.ts` | Oke akụkọ chat nhọrọ nke a ga-eji naanị ma ọ bụrụ na agbanyere ya. Agbanyụrụ ya na ndabara: ọnụ ọgụgụ ozi bụ iwu deployment, ọ bụghị njirimara zuru ụwa ọnụ nke arịrịọ, na itinye oke ebe a na-ajụ mkparịta ụka site na `413` ikpeazụ tupu pipeline mkpakọ enwee ike ime ka e nwee ike ijere ha ozi. `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` na shed nke nrụgide heap na-amachi uto heap. Tọọ uru dị mma na deployment nwere oke ebe nchekwa nke chọrọ oke siri ike; ihe gafere oke ga-enweta `413` ahaziri nke na-egosi na achọrọ mkpakọ. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Oke siri ike maka nzaghachi upstream na-abụghị streaming nke a na-echekwa kpamkpam n'ime ebe nchekwa. Mgbe ọ gafere nke a, a na-akagbu upstream reader, arịrịọ ahụ adaakwa ngwa ngwa kama ịga n'ihu na-amụba string na-enweghị oke ruo mgbe heap gwụrụ. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Oke bytes wire a na-ebufe site na nkụnye isi nzaghachi nke upstream. Mgbe ọ gafere oke a, a na-ewepụ nkụnye isi ndị nwere mkpa dị ala (dịka, `x-codex-*`, `x-oai-request-id`) iji nọrọ n'ime oke nkụnye isi reverse-proxy ndị a na-ejikarị. Tọọ ya elu ka ebufekwuo metadata upstream, mana nke a ga-eme ka nha nkụnye isi nzaghachi bawanye. | +| `CORS_ORIGIN` | _(edoghị ya)_ | `src/server/cors/origins.ts` | Ndepụta ikike CORS nke otu origin ochie. Họrọ `CORS_ALLOWED_ORIGINS` maka nrụnye ọhụrụ. CORS bụ naanị maka ndị ahịa API ihe nchọgharị sitere na origin ọzọ; ide ihe na dashboard nke enyochala njirimara na-eji arịrịọ same-origin tinyere nchedo CSRF jikọrọ na session kama. | +| `CORS_ALLOWED_ORIGINS` | _(edoghị ya)_ | `src/server/cors/origins.ts` | Ndepụta ikike CORS nke rịkọm kewara. Agaghị eziga wildcard ọ bụla belụsọ ma ahaziri `CORS_ALLOW_ALL=true` n'ụzọ doro anya. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Ụzọ mgbapụ maka mmepe naanị, nke na-emegharị `Origin` ihe nchọgharị ọ bụla. Emela ka ọ rụọ ọrụ na nrụnye a na-ekekọrịta ma ọ bụ nke production. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias ochie (legacy). A na-agụ mgbatịa dashboard ya (nsoghanye DB) tupu uru environment a; `false`, `0`, `no`, ma ọ bụ `off` na nke ọ bụla n'ime ha na-ebụchụpụ nyocha host otu ụzọ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` si eme, ọbụnadị mgbe echekwara mgbatịa dashboard nke flag ahụ n'ọnọdụ gbanyụrụ anwụ; uru ndị ọzọ na-ahapụ nchebe ahụ n'aka flag abụọ dị n'okpuru. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ọ na-ebụchụpụ nyocha host nke nchebe URL outbound, tinyere mgbochi cloud-metadata (niile nke 169.254.0.0/16, metadata.google.internal), n'ụzọ provider ndị na-eso ọnọdụ nchebe ahụ: nkwado URL, ịchọta ihe nlereanya, URL isi nke node provider (gụnyere node rerank remote), na nyocha proxy-fallback, n'etiti ụzọ outbound ndị ọzọ dị nso na provider dịka nweta ihe onyonyo/midia remote. Ọ na-ekwekwu ebumnuche webhook private, ma e wezụga endpoint cloud-metadata/link-local, ndị a ka na-egbochi ọbụnadị mgbe etinyere flag a. Arịrịọ mkparịta ụka a na-eziga site na `BaseExecutor.execute()` na-edobe nyocha cloud-metadata ha. Provider local na-arụ ọrụ na ndabara: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ndabara `true`) na-ekwetabụrụ URL local na LAN na ụzọ nkwado, ịchọta, na node provider; nyocha proxy-fallback na ebumnuche webhook private na-ele anya naanị flag a ma na-egbochi host local/LAN mgbe ọ nọ n'ọnọdụ gbanyụrụ. Ọ na-efe ntọala `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` isi. Uru echekwara site na mgbatịa dashboard karịrị mgbanwe a mkpa. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Kwe ka URL provider na adreesị local/private (127.0.0.1, localhost, LAN, oke private) maka nkwado provider, ịchọta ihe nlereanya, mkparịta ụka, na ụzọ outbound ndị ọzọ dị nso na provider nke ọnọdụ nchebe ahụ na-achị. **Ndabara bụ `true`** (local-first): nchebe ahụ na-egbochi endpoint cloud-metadata mgbe ahụ — niile nke 169.254.0.0/16 tinyere aha host metadata ama ama (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Tọọ `false` maka ọnọdụ naanị ọha, nke na-egbochikwa host private na loopback. Nchebe ahụ na-enyocha aha host ma ọ bụ ederede IP dịka e sere ya, tupu nyocha DNS ọ bụla; na ọnọdụ naanị ọha, ihe nchebe safe-fetch na-emepụta ọzọ aha host ahụ ma na-ajụ azịza ọ bụla bụ nkeonwe. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` na-ebụchụpụ nyocha ndị a n'ụzọ ndị edeturu na ahịrị ya. Provider local ndị e wuru n'ime, dịka LM Studio, Ollama, vLLM, Llamafile, na Triton, na-apụ na nyocha host niile maka nkwado key na mkparịta ụka. Uru echekwara site na mgbatịa dashboard karịrị mgbanwe a mkpa. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Kwe ka route `/v1/audio/*` (ntụgharị ọdịyo gaa na ederede, okwu, ntụgharị asụsụ) jiri node provider dakọtara na OpenAI nke a na-host n'èzí localhost. A na-agbanyụ ya na ndabara — iziga ọdịyo na host remote na-agbanwe njirimara egress, ya mere ọ ga-abụrịrị mkpebi operator doro anya. A na-ekwe node loopback/private (localhost, 127.0.0.1, 172.16-31.x) mgbe niile, nhazi a anaghịkwa emetụta ha. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Kwe ka `POST /v1/rerank` (yana usoro rerank loopback nke engine ebe nchekwa) jiri node provider dakọtara na OpenAI nke a na-host n'èzí localhost — igbe LAN ma ọ bụ peer Tailscale na-agba TEI, Infinity, vLLM, wdg. A na-agbanyụ ya na ndabara — iziga ya na host remote na-agbanwe njirimara egress, ya mere ọ ga-abụrịrị mkpebi operator doro anya. A na-ekwe node loopback (localhost, 127.0.0.1, 172.16-31.x) mgbe niile, nhazi a anaghịkwa emetụta ha. URL isi nke node remote ga-agafekwa iwu URL outbound nke provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Mgbe enyere OIDC aka, gbanyụọ nbanye okwuntughe ka ndị ọrụ nwee ike nyochaa njirimara naanị site na OIDC Single Sign-On. A na-anabatakwa alias nkịtị `OIDC_DISABLE_PASSWORD_LOGIN`; Dashboard Feature Flag nwere otu key ahụ nwere mkpa karịa ya. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias nkịtị nke `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Ndepụta Nlele Maka Ime Ka Nchedo Sie Ike diff --git a/docs/i18n/it/CONTRIBUTING.md b/docs/i18n/it/CONTRIBUTING.md index 2fa454244648..62ff673dc551 100644 --- a/docs/i18n/it/CONTRIBUTING.md +++ b/docs/i18n/it/CONTRIBUTING.md @@ -388,22 +388,22 @@ Scrivere test unitari in `tests/unit/` che coprano almeno: --- -## Checklist della Pull Request +## Checklist della pull request - [ ] I test vengono superati (`npm test`) - [ ] Il linting viene superato (`npm run lint`) -- [ ] La build viene completata correttamente (`npm run build`) +- [ ] La build viene completata con successo (`npm run build`) - [ ] Sono stati aggiunti i tipi TypeScript per le nuove funzioni e interfacce pubbliche - [ ] Nessun segreto o valore di fallback codificato direttamente -- [ ] Le credenziali upstream pubbliche sono incorporate tramite `resolvePublicCred()` (vedere [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), mai come valori letterali -- [ ] Le risposte di errore passano attraverso `buildErrorBody()` / `sanitizeErrorMessage()` — nessuna traccia dello stack non elaborata nei corpi delle risposte (vedere [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] I comandi shell (`exec` / `spawn`) passano i valori di runtime tramite `env`, non mediante interpolazione di stringhe -- [ ] Tutti gli input sono convalidati con schemi Zod +- [ ] Le credenziali pubbliche upstream sono incorporate tramite `resolvePublicCred()` (vedere [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), mai come valori letterali +- [ ] Le risposte di errore vengono gestite tramite `buildErrorBody()` / `sanitizeErrorMessage()` — nessuna traccia dello stack non elaborata nei corpi delle risposte (vedere [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] I comandi shell (`exec` / `spawn`) passano i valori di runtime tramite `env`, non tramite interpolazione di stringhe +- [ ] Tutti gli input vengono convalidati con schemi Zod - [ ] È stato aggiunto un **frammento** del changelog in `changelog.d/{features|fixes|maintenance}/-.md` per le modifiche visibili agli utenti (vedere [`changelog.d/README.md`](./changelog.d/README.md)) — **non** modificare direttamente `CHANGELOG.md`; i frammenti vengono aggregati al momento del rilascio e non generano mai conflitti tra le PR - [ ] La documentazione è stata aggiornata (se applicabile) -- [ ] Nessun nuovo avviso CodeQL / Secret-Scanning aperto, oppure ciascun avviso è stato ignorato con una giustificazione tecnica che fa riferimento al documento pertinente in `docs/security/` -- [ ] Le route che generano processi figli (`/api/mcp/`, `/api/cli-tools/runtime/`) sono classificate come `isLocalOnlyPath()` in `src/server/authz/routeGuard.ts` — vedere [Regola inderogabile n. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Nessun trailer `Co-Authored-By` nei messaggi di commit — i commit devono risultare esclusivamente associati all'identità Git del proprietario del repository (Regola inderogabile n. 16) +- [ ] Non sono stati generati nuovi avvisi CodeQL / Secret-Scanning oppure ciascuno è stato ignorato con una giustificazione tecnica che fa riferimento al documento pertinente in `docs/security/` +- [ ] Le route che generano processi figlio (`/api/mcp/`, `/api/cli-tools/runtime/`) sono classificate come `isLocalOnlyPath()` in `src/server/authz/routeGuard.ts` — vedere la [Regola tassativa n. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Nessun trailer `Co-authored-by` relativo ad AI/bot nei messaggi di commit (Regola tassativa n. 16) — i collaboratori umani il cui lavoro viene riutilizzato sono accreditati con trailer standard `Co-authored-by: Name ` --- diff --git a/docs/i18n/it/docs/architecture/QUALITY_GATES.md b/docs/i18n/it/docs/architecture/QUALITY_GATES.md index 1b7dacc0c208..77076db4f2cb 100644 --- a/docs/i18n/it/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/it/docs/architecture/QUALITY_GATES.md @@ -19,215 +19,217 @@ replicazione indipendente dagli strumenti dello stesso sistema, consulta il ### Ammissione dei candidati -I workflow CI e Quality Gates emettono ciascuno un verdetto stabile: `Gate / CI` e -`Gate / Quality`. La rispettiva policy di ammissione versionata elenca ogni job upstream -come obbligatorio o consultivo. Un job obbligatorio applicabile deve avere esito positivo: +I workflow CI e Quality Gates producono ciascuno un verdetto stabile: `Gate / CI` e +`Gate / Quality`. La relativa politica di ammissione versionata classifica ogni job +a monte come obbligatorio o consultivo. Un job obbligatorio applicabile deve riuscire: risultati mancanti, annullati, ignorati, in sospeso e sconosciuti non possono determinare -un esito PASS. Una classificazione valida come solo documentazione o solo catalogo può -rendere non applicabile una lane di codice; una PR in bozza non è un candidato accettato. -Un'etichetta `hotfix` non esonera dalla presentazione delle evidenze. +un esito PASS. Una classificazione valida come docs-only o catalog-only può rendere +non applicabile una lane di codice; una PR in bozza non è un candidato accettato. +Un'etichetta `hotfix` non esenta dall'obbligo di fornire evidenze. -Entrambi i workflow coprono le PR e i push sui branch main/release, l'avvio manuale e -gli eventi merge-group. Push, avvio manuale e merge-group eseguono la selezione completa. +Entrambi i workflow coprono le PR e i push verso i branch main/release, l'avvio manuale +e gli eventi merge-group. Push, avvio manuale e merge-group eseguono la selezione completa. I fork e i merge group utilizzano runner ospitati per i job che altrimenti selezionerebbero runner self-hosted; prima del rollout deve essere verificata una capacità ospitata sufficiente. Ogni ricevuta JSON identifica lo SHA sottoposto a checkout, l'esecuzione del workflow e il tentativo. -La CLI rifiuta una mancata corrispondenza tra lo SHA del checkout e quello dell'evento. I test del -workflow associano l'appartenenza alla policy all'elenco `needs` del job di verdetto, affinché una -lane nuova o rimossa non possa scomparire senza essere rilevata. Le ricevute coprono il workflow -di appartenenza, non la pubblicazione, il deployment o i meccanismi interni di uno scanner -consultivo esistente. L'attivazione di entrambi i nomi dei check nelle regole del branch è una -modifica amministrativa separata; l'aggiunta di questi job non protegge di per sé un branch. +La CLI rifiuta una mancata corrispondenza tra lo SHA del checkout e quello dell'evento. I test del workflow +vincolano l'appartenenza alla politica all'elenco `needs` del job del verdetto, in modo che una lane nuova +o rimossa non possa scomparire silenziosamente. Le ricevute coprono il workflow a cui appartengono, +non la pubblicazione, il deployment o il funzionamento interno di uno scanner consultivo esistente. +L'attivazione di entrambi i nomi dei check nelle regole dei branch è una modifica amministrativa +separata; l'aggiunta di questi job non protegge di per sé un branch. -### Inventario delle scansioni statiche +### Inventario della scansione statica L'inventario versionato degli alias npm e l'appartenenza alla scansione statica si trovano in -`config/quality/gate-manifest.json`. Eseguire `npm run check:gate-manifest` per convalidare +`config/quality/gate-manifest.json`. Esegui `npm run check:gate-manifest` per convalidare i nomi degli script e i comandi esatti rispetto a `package.json`; aggiunte, rimozioni e -variazioni dei comandi fanno fallire sia l'hook locale sia i job di classificazione delle -modifiche in CI. Un alias non è un job del workflow, un'istanza della matrice o un caso di test: -questi conteggi non devono essere presentati come intercambiabili. +variazioni dei comandi causano il fallimento sia dell'hook locale sia dei job di classificazione +delle modifiche nella CI. Un alias non è un job del workflow, un'istanza della matrice o un caso +di test: questi conteggi non devono essere presentati come intercambiabili. -Usare `npm run quality:scan -- --list` o `npm run quality:scan:fast -- --list` +Usa `npm run quality:scan -- --list` o `npm run quality:scan:fast -- --list` per esaminare gli alias selezionati senza eseguirli. Il runner richiama l'entrypoint -npm, preservandone quindi il runtime, incluso Bun dove configurato. +npm, preservandone così il runtime (incluso Bun, ove configurato). Il manifest registra gli alias esterni a tali profili come richiamati separatamente e -i comandi di manutenzione sono vietati nei profili di scansione di sola lettura. +i comandi di manutenzione sono vietati nei profili di scansione in sola lettura. -Questi profili coprono esclusivamente la scansione statica. Non certificano i test del prodotto, +Questi profili coprono soltanto la scansione statica. Non certificano i test del prodotto, la copertura, il packaging, i check esterni o l'accettazione completa di un candidato per il rilascio. -L'ammissione del workflow usa i file collegati `config/quality/admission-policy.json` e +L'ammissione del workflow utilizza i file collegati `config/quality/admission-policy.json` e `scripts/quality/admission-verdict.mjs`. I profili release-observer rimangono separati; -esaminare indipendentemente i relativi check e le ricevute applicabili. L'inventario -testuale riportato di seguito è un riferimento, non una prova dell'effettiva esecuzione di un gate. +esamina in modo indipendente i check e le ricevute applicabili. L'inventario +descrittivo riportato di seguito è un riferimento, non una prova dell'effettiva esecuzione di un gate. -Gli script si trovano in `scripts/check/` (gate di policy) e `scripts/quality/` (motore ratchet). -La fonte autorevole per la CI è `.github/workflows/ci.yml`. +Gli script si trovano in `scripts/check/` (gate delle politiche) e `scripts/quality/` (motore ratchet). +La fonte autorevole della CI è `.github/workflows/ci.yml`. ### Percorso rapido per le PR di rilascio (`quality.yml`) -`.github/workflows/quality.yml` integra la CI per le PR main/release, i push sui branch protetti, -l'avvio manuale e i merge group. Le PR utilizzano check rapidi filtrati in base ai percorsi. La build -duplicata, permanentemente disabilitata, è stata rimossa; i veri check di build/package/avvio -rimangono nella CI. - -| Job | Ambito | Bloccante | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `Docs Gates (fast-path)` | PR di documentazione/codice; riferimenti alla documentazione API e tutta la documentazione | Sì | -| `Fast Quality Gates` | PR di codice; check statici, typecheck, typecheck della dashboard, test unitari interessati | Sì | -| `Forgotten sibling tests` | PR di codice; moduli modificati tracciati fino ai consumer statici e ai test sibling candidati; i percorsi tramite barrel e import dinamici sono segnalati come diagnostica consultiva, con le eccezioni dell'allowlist di riferimento | **Consultivo** | -| `Vitest (fast-path)` | PR di codice; suite vitest rapida | Sì | -| `Unit Tests fast-path` | PR di codice; suite unitaria suddivisa in 4 shard | Sì | -| `No new ESLint warnings` | PR di codice; protezione lint consapevole delle soppressioni | Sì, inclusi i fork | -| `Merge integrity (changelog + generated skills)` | PR non in bozza; sincronizzazione del changelog e delle skill generate | Sì, inclusi i fork | +`.github/workflows/quality.yml` integra la CI per le PR main/release, i push verso branch +protetti, l'avvio manuale e i merge group. Le PR utilizzano controlli rapidi filtrati in base ai percorsi. +La build duplicata, disabilitata in modo permanente, è stata rimossa; i controlli effettivi di +build/package/avvio rimangono nella CI. + +| Job | Ambito | Bloccante | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `Docs Gates (fast-path)` | PR di documentazione/codice; riferimenti alla documentazione API e docs-all | Sì | +| `Fast Quality Gates` | PR di codice; controlli statici, typecheck, typecheck della dashboard, unit test interessati | Sì | +| `Forgotten sibling tests` | PR di codice; moduli modificati ricondotti ai consumer statici e ai test sibling candidati; i percorsi barrel e dynamic-import sono segnalati come diagnostica consultiva, con le eccezioni dell'allowlist indicate | **Consultivo** | +| `Vitest (fast-path)` | PR di codice; suite Vitest rapida | Sì | +| `Unit Tests fast-path` | PR di codice; suite di unit test suddivisa in 4 shard | Sì | +| `No new ESLint warnings` | PR di codice; protezione lint consapevole delle soppressioni | Sì, inclusi i fork | +| `Merge integrity (changelog + generated skills)` | PR non in bozza; sincronizzazione del changelog e delle skill generate | Sì, inclusi i fork | #### Report sui test sibling dimenticati -`npm run check:forgotten-sibling-tests` riutilizza il risolutore degli import alla base della mappa -dell'impatto dei test. Per ogni modulo di produzione modificato, segnala catene deterministiche -`changed module/symbol -> static consumer -> candidate sibling test` quando il test candidato -è assente dal diff della pull request. Il riepilogo Markdown e il risultato JSON vengono conservati -come artefatto del workflow `forgotten-sibling-tests` per la calibrazione prima di qualsiasi rollout -bloccante. +`npm run check:forgotten-sibling-tests` riutilizza il resolver delle importazioni alla base della mappa dell'impatto dei test. +Per ogni modulo di produzione modificato, segnala catene deterministiche +`modulo/simbolo modificato -> consumer statico -> test sibling candidato` quando il test candidato +non è presente nel diff della pull request. Il riepilogo Markdown e il risultato JSON vengono conservati come +artefatto del workflow `forgotten-sibling-tests` per la calibrazione prima di qualsiasi rollout bloccante. -Le riesportazioni barrel e le importazioni dinamiche sono esclusivamente diagnostiche di risoluzione; non generano mai un rilievo bloccante. Le eccezioni revisionate si trovano in +Le riesportazioni tramite barrel e le importazioni dinamiche sono solo diagnostiche di risoluzione; non generano mai un +rilievo bloccante. Le eccezioni esaminate si trovano in `config/quality/forgotten-sibling-allowlist.json`. Ogni voce deve indicare il consumer e il test candidato, fornire una motivazione specifica e includere un link a una issue o pull request di GitHub. Le voci non valide causano -il fallimento in modalità chiusa. Le eccezioni non possono ignorare un test candidato eliminato o una diff che aggiunge `.skip`/`.todo`; -l'indebolimento delle asserzioni e altre forme di mascheramento restano sotto la responsabilità del gate bloccante indipendente +un errore in modalità fail-closed. Le eccezioni non possono ignorare un test candidato eliminato o una diff che aggiunge `.skip`/`.todo`; +l'indebolimento delle asserzioni e altri meccanismi di mascheramento restano sotto la responsabilità del gate bloccante indipendente `check:test-masking`. ### Job: `lint` -Viene eseguito su ogni PR verso `main`. In caso di fallimento, blocca il merge. - -| Script (`npm run ...`) | Verifica | Bloccante | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | La versione di Node.js rientra nell'intervallo supportato | Sì | -| `check:cycles` | Importazioni circolari — tutti i moduli in `src/` + `open-sse/` | Sì | -| `check:route-validation:t06` | Presenza degli schemi Zod su tutte le route (policy Tier 6) | Sì | -| `check:any-budget:t11` | Il numero di `@ts-expect-error // any` non supera il budget (catraca Tier 11) | Sì | -| `check:provider-consistency` | Ogni provider in `providers.ts` ha una voce corrispondente in `providerRegistry.ts` (e viceversa, entro i limiti dell'allowlist) | Sì | -| `check:model-lifecycle` | Le tre tabelle di routing gestite manualmente rimangono coerenti con lo snapshot del ciclo di vita incluso nel repository (#11503): `FITNESS_TABLE` (`taskFitness.ts`) non assegna un punteggio ad alcun id ritirato che `REGISTRY` possa instradare; ogni destinazione di `BUILT_IN_ALIASES` è presente in `REGISTRY` e assente dallo snapshot degli id ritirati; ogni id ritirato ancora presente in `REGISTRY` viene inoltrato o elencato in `allowedRetiredInCatalog`; inoltre, nessuna origine o destinazione di `DEFAULT_DEGRADATION_MAP` risulta ritirata in tale snapshot. Questo non dimostra che un modello sia attualmente servito da un upstream attivo. Offline — esegue il confronto con `config/quality/model-lifecycle.json`, aggiornato manualmente tramite `npm run quality:refresh-model-lifecycle` (richiede la rete; non integrato nella CI). `allowedRetiredInCatalog` è un meccanismo progressivo di riduzione: aggiungere una voce solo se associata a un issue di tracciamento. | Sì | -| `check:fetch-targets` | Ogni `fetch("/api/...")` nel codice lato client in `src/` viene risolto in un `route.ts` reale | Sì | -| `check:deps` | Tutte le dipendenze installabili tramite `npm install` in ogni `package.json` del repository sono incluse in `dependency-allowlist.json`; i nuovi pacchetti senza versione fissata o vittime di slopsquatting vengono segnalati | Sì | -| `audit:deps` | `npm audit` (root + electron) — nessun avviso di gravità alta/critica (si sovrappone a osv `check:vuln-ratchet`; vedere il backlog di razionalizzazione) | Sì | -| `check:lockfile` | Integrità di `package-lock.json` — registro https, hash di integrità, nessuna sostituzione dell'host | Sì | -| `check:licenses` | Elenco consentito di licenze SPDX per le dipendenze di produzione | Sì | -| `check:tracked-artifacts` | Nessun artefatto di build / collegamento simbolico `node_modules` sottoposto a commit (eseguito anche nel pre-commit di husky; il pre-push è intenzionalmente leggero — #6716) | Sì | -| `check:ai-attribution` | Nessun trailer `Co-Authored-By` di IA/bot né piè di pagina relativo alla generazione tramite IA nei commit, nel titolo o nel corpo delle PR — Regola inderogabile #16 (nel ciclo di controlli rapidi di `quality.yml` per PR→`release/**` — legge il payload dell'evento e non esegue alcuna operazione al di fuori delle PR — e in un passaggio riservato alle PR nel lint di `ci.yml` per PR→`main`; inoltre nell'hook `commit-msg` di husky; coautori umani consentiti; #14436) | -| `check:vitest-exclusions` | Ogni esclusione di Vitest indica un issue di tracciamento ed è presente in `config/quality/vitest-exclusions.json` (#13204) | Sì | -| `check:file-size` | Nessun file sorgente supera il limite previsto per la relativa estensione (ratchet: file di grandi dimensioni bloccati nell'elenco `frozen`) | Sì | -| `check:error-helper` | Le risposte di errore negli esecutori/handler utilizzano `buildErrorBody()` / `sanitizeErrorMessage()` (Regola inderogabile #12) | Sì | -| `check:migration-numbering` | I file SQL di migrazione sono numerati in sequenza, senza lacune o duplicati | Sì | -| `check:public-creds` | Nessun valore letterale OAuth `client_id`/`client_secret` o chiave Firebase Web al di fuori di `publicCreds.ts` (Regola rigida n. 11) | Sì | -| `check:db-rules` | Nessun SQL grezzo al di fuori dei moduli `src/lib/db/`; nessuna importazione barrel da `localDb.ts` (Regole rigide n. 2/n. 5) | Sì | -| `check:known-symbols` | Gli esecutori dei provider, le strategie di routing e i traduttori registrati nelle rispettive tabelle di dispatch corrispondono ai file su disco, senza simboli orfani o non dichiarati | Sì | -| `check:route-guard-membership` | Ogni route che genera un processo figlio è classificata da `isLocalOnlyPath()` (Regole rigide n. 15/n. 17) | Sì | -| `check:test-discovery` | Ogni file `*.test.ts` / `*.spec.ts` nel repository viene raccolto da almeno un test runner (ratchet: l'elenco dei file orfani in `test-discovery-baseline.json` può solo ridursi) | Sì | -| `check:agent-skills-sync` | Gli artefatti agent-skills generati corrispondono al relativo catalogo sorgente (nessuna divergenza) | -| `check:provider-asset-provenance` | I loghi/gli asset dei provider dispongono di una voce di provenienza registrata | -| `lint:json` | I file di configurazione JSON vengono analizzati correttamente e soddisfano le regole di lint del repository | -| `typecheck:core` | Compilazione TypeScript senza errori (solo avvisi consultivi) | Sì | -| `typecheck:noimplicit:core` | `noImplicitAny` rigoroso — orientato al futuro; molti call site preesistenti richiedono ancora annotazioni | **Consultivo** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` limitato a `src/app/(dashboard)/**` (#7033) — l'allowlist selezionata di 27 file di `typecheck:core` non include alcun TSX della dashboard e nemmeno `next build` ne esegue il controllo dei tipi (`next.config.mjs` imposta `ignoreBuildErrors: true`), quindi le regressioni relative a identificatori orfani presenti in tale area (#6625/#6909) non erano visibili alla CI. Esegue il confronto con una baseline bloccata del conteggio per file/per codice TS (`config/quality/dashboard-typecheck-baseline.json`, seguendo lo stesso schema di controllo delle voci obsolete di `check:known-symbols`) — solo i NUOVI errori oltre il conteggio della baseline fanno fallire il controllo; ridurre progressivamente la baseline con `--update` quando viene corretto un errore preesistente. | Sì | +Viene eseguito su ogni PR verso `main`. In caso di errore, blocca il merge. + +| Script (`npm run ...`) | Verifica | Bloccante | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | +| `check:node-runtime` | La versione di Node.js rientra nell'intervallo supportato | Sì | +| `check:cycles` | Importazioni circolari nell'intero ambito di `src/` + `open-sse/` (basato su AST, con risoluzione dei `paths` di tsconfig). L'esecuzione semplice è consultiva ed elenca i cicli. `check:cycles:ratchet` (quello eseguito dalla CI) blocca quando il conteggio supera il limite massimo `metrics.cycles` in `quality-baseline.json` — attualmente 14, `direction: down`, quindi può solo diminuire (#15159 G-01/G-02) | Sì (ratchet) | +| `check:route-validation:t06` | Presenza degli schemi Zod su tutte le route (policy di livello 6) | Sì | +| `check:any-budget:t11` | Il conteggio di `@ts-expect-error // any` non supera il budget (ratchet di livello 11) | Sì | +| `check:provider-consistency` | Ogni provider in `providers.ts` ha una voce corrispondente in `providerRegistry.ts` (e viceversa, nei limiti dell'allowlist) | Sì | +| `check:model-lifecycle` | Le tre tabelle di routing gestite manualmente rimangono coerenti con lo snapshot del ciclo di vita incluso nel repository (#11503): `FITNESS_TABLE` (`taskFitness.ts`) non assegna un punteggio ad alcun id ritirato che `REGISTRY` possa instradare; ogni destinazione di `BUILT_IN_ALIASES` è presente in `REGISTRY` e assente dallo snapshot degli id ritirati; ogni id ritirato ancora presente in `REGISTRY` viene inoltrato oppure elencato in `allowedRetiredInCatalog`; inoltre, nessuna origine o destinazione di `DEFAULT_DEGRADATION_MAP` risulta ritirata in tale snapshot. Ciò non dimostra che un modello sia attualmente erogato da un upstream attivo. Offline — esegue il confronto con `config/quality/model-lifecycle.json`, aggiornato manualmente con `npm run quality:refresh-model-lifecycle` (rete; non integrato nella CI). `allowedRetiredInCatalog` è un meccanismo di riduzione progressiva: aggiungere una voce solo in presenza di un issue di tracciamento. | Sì | +| `check:fetch-targets` | Ogni `fetch("/api/...")` nel codice lato client in `src/` si risolve in un file `route.ts` reale | Sì | +| `check:deps` | Tutte le dipendenze installabili con `npm install` in ogni `package.json` del repository sono presenti in `dependency-allowlist.json`; i nuovi pacchetti senza versione bloccata o soggetti a slopsquatting vengono segnalati | Sì | +| `audit:deps` | `npm audit` (root + electron) — nessun avviso di gravità alta/critica (si sovrappone al controllo OSV `check:vuln-ratchet`; vedere il backlog di razionalizzazione) | Sì | +| `check:lockfile` | Integrità di `package-lock.json` — registro https, hash di integrità, nessuna sostituzione degli host | Sì | +| `check:licenses` | Elenco consentito di licenze SPDX per le dipendenze di produzione | Sì | +| `check:tracked-artifacts` | Nessun artefatto di build / link simbolico `node_modules` registrato nel repository (viene eseguito anche nel pre-commit di husky; il pre-push è intenzionalmente leggero — #6716) | Sì | +| `check:ai-attribution` | Nessun trailer `Co-Authored-By` di IA/bot o piè di pagina relativo alla generazione tramite IA nei commit, nel titolo o nel corpo delle PR — Regola inderogabile #16 (nel ciclo fast-gates di `quality.yml` per PR→`release/**` — legge il payload dell'evento, non esegue alcuna operazione al di fuori delle PR — e in un passaggio riservato alle PR nel lint di `ci.yml` per PR→`main`; anche nell'hook `commit-msg` di husky; sono consentiti i coautori umani; #14436) | +| `check:vitest-exclusions` | Ogni esclusione di Vitest indica un issue di tracciamento ed è presente in `config/quality/vitest-exclusions.json` (#13204) | Sì | +| `check:file-size` | Nessun file sorgente supera il limite previsto per la relativa estensione (ratchet: file di grandi dimensioni bloccati nell'elenco `frozen`) | Sì | +| `check:error-helper` | Le risposte di errore negli executor/handler utilizzano `buildErrorBody()` / `sanitizeErrorMessage()` (Regola inderogabile #12) | Sì | +| `check:migration-numbering` | I file SQL di migrazione sono numerati in sequenza, senza interruzioni o duplicati | Sì | +| `check:public-creds` | Nessun valore letterale OAuth `client_id`/`client_secret` o chiave Web Firebase al di fuori di `publicCreds.ts` (Regola rigida n. 11) | Sì | +| `check:db-rules` | Nessun SQL non elaborato al di fuori dei moduli `src/lib/db/`; nessuna importazione barrel da `localDb.ts` (Regole rigide n. 2/n. 5) | Sì | +| `check:known-symbols` | Gli esecutori dei provider, le strategie di routing e i traduttori registrati nelle rispettive tabelle di dispatch corrispondono ai file su disco, senza simboli orfani o non dichiarati | Sì | +| `check:route-guard-membership` | Ogni route che genera un processo figlio è classificata da `isLocalOnlyPath()` (Regole rigide n. 15/n. 17) | Sì | +| `check:test-discovery` | Ogni file `*.test.ts` / `*.spec.ts` nel repository viene rilevato da almeno un test runner (vincolo progressivo: l'elenco degli orfani in `test-discovery-baseline.json` può solo ridursi) | Sì | +| `check:agent-skills-sync` | Gli artefatti agent-skills generati corrispondono al relativo catalogo sorgente (nessuna divergenza) | +| `check:provider-asset-provenance` | I loghi/gli asset dei provider dispongono di una voce di provenienza registrata | +| `lint:json` | I file di configurazione JSON vengono analizzati correttamente e soddisfano le regole di lint del repository | +| `typecheck:core` | Compilazione TypeScript senza errori (solo avvisi consultivi) | Sì | +| `typecheck:noimplicit:core` | `noImplicitAny` rigoroso — orientato al futuro; molti siti di chiamata preesistenti richiedono ancora annotazioni | **Consultivo** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` limitato a `src/app/(dashboard)/**` (#7033) — l’elenco consentito selezionato di 27 file di `typecheck:core` non include alcun TSX della dashboard e nemmeno `next build` ne esegue il controllo dei tipi (`next.config.mjs` imposta `ignoreBuildErrors: true`), quindi le regressioni relative a identificatori orfani presenti lì (#6625/#6909) erano invisibili alla CI. Confronta i risultati con una baseline bloccata dei conteggi per file/per codice TS (`config/quality/dashboard-typecheck-baseline.json`, lo stesso schema di controllo dell’obsolescenza usato da `check:known-symbols`) — solo i NUOVI errori oltre il conteggio della baseline fanno fallire il gate; ridurre progressivamente la baseline con `--update` quando viene corretto un errore preesistente. | Sì | ### Job: `quality-gate` -Viene eseguito dopo `test-coverage`. Impedisce il merge in caso di errore. +Viene eseguito dopo `test-coverage`. Blocca il merge in caso di errore. -| Script | Verifica | Bloccante | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | Genera `quality-metrics.json` (numero di avvisi ESLint, copertura dal report combinato degli shard) | Sì (a monte del ratchet) | -| `quality:ratchet` | Nessuna metrica in `quality-baseline.json` è peggiorata (avvisi ESLint ≤ baseline; copertura ≥ baseline) | Sì | -| `check:duplication` | La duplicazione del codice (jscpd@4) non supera la baseline in `quality-baseline.json` | Sì | -| `check:complexity` | La complessità ciclomatica a livello di file non supera il limite (regole ESLint di base `complexity` + `max-lines-per-function`) | Sì | -| `check:cognitive-complexity` | Ratchet della complessità cognitiva (`eslint-plugin-sonarjs`) — passaggio ESLint separato; la CI esegue entrambi insieme nel singolo passaggio `check:complexity-ratchets` | Sì | -| `check:dead-code` | Il ratchet di esportazioni/file inutilizzati (knip) non peggiora rispetto alla baseline | Sì | -| `check:compression-budget` | Budget del benchmark di compressione — le soglie minime di risparmio di token per motore non devono peggiorare | Sì | -| `check:type-coverage` | Il ratchet della percentuale di codice tipizzato (`type-coverage`) non peggiora; sostituisce in gran parte `typecheck:noimplicit:core` | Sì | -| `check:codeql-ratchet` | Il numero di avvisi CodeQL aperti non peggiora (lettura tramite `gh api`; esclusione controllata senza token) — per la frequenza di aggiornamento e l'attivazione manuale, vedere "Ratchet CodeQL" sotto | Sì | +| Script | Verifica | Bloccante | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | Genera `quality-metrics.json` (numero di avvisi ESLint, copertura dal report aggregato degli shard) | Sì (a monte del ratchet) | +| `quality:ratchet` | Nessuna metrica in `quality-baseline.json` è peggiorata (avvisi ESLint ≤ baseline; copertura ≥ baseline) | Sì | +| `check:duplication` | La duplicazione del codice (jscpd@4) non supera la baseline in `quality-baseline.json` | Sì | +| `check:complexity` | La complessità ciclomatica a livello di file non supera il limite (`complexity` di ESLint core + `max-lines-per-function`) | Sì | +| `check:cognitive-complexity` | Ratchet della complessità cognitiva (`eslint-plugin-sonarjs`) — passaggio ESLint separato; la CI esegue entrambi accorpandoli nell'unico passaggio `check:complexity-ratchets` | Sì | +| `check:dead-code` | Il ratchet di esportazioni/file inutilizzati (knip) non peggiora rispetto alla baseline | Sì | +| `check:compression-budget` | Budget del benchmark di compressione — le soglie minime di risparmio dei token per motore non devono peggiorare | Sì | +| `check:type-coverage` | Il ratchet della percentuale di codice tipizzato (`type-coverage`) non peggiora; sostituisce in gran parte `typecheck:noimplicit:core` | Sì | +| `check:codeql-ratchet` | Il numero di avvisi CodeQL aperti non peggiora (lettura tramite `gh api`; ignorato senza errori in assenza di token) — per la frequenza di aggiornamento e l'attivazione manuale, vedere "Ratchet CodeQL" di seguito | Sì | ### Job: `quality-extended` L'intero job è consultivo (`continue-on-error: true`). I ratchet basati su npm vengono -eseguiti effettivamente; gli scanner esterni vengono installati tramite `gh release download` e si escludono automaticamente (exit 0) -quando un binario è ancora assente. - -| Script | Verifica | Bloccante | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | -| `check:circular-deps` | Nessuna dipendenza circolare (dpdm) | **Consultivo** | -| `check:bundle-size` | La dimensione del bundle non supera il limite | **Consultivo** | -| `check:secrets` | Scansione dei segreti (gitleaks) — viene ignorata se il binario è assente | **Consultivo** | -| `check:vuln-ratchet` | Le vulnerabilità delle dipendenze (osv-scanner) non peggiorano — viene ignorato se il binario è assente | **Consultivo** | -| `check:workflows` | Analisi dei workflow (actionlint + zizmor); scanner mancanti/non funzionanti, report non validi o baseline del ratchet mancante causano un errore INCOMPLETE. I risultati validi seguono la policy selezionata: rigorosa/consultiva/ratchet | Esecuzione obbligatoria; ratchet zizmor bloccante nella CI | -| `check:openapi-breaking` | Modifiche incompatibili al contratto dell'API pubblica (`openapi.yaml`) rispetto al branch di base (oasdiff) — genera `openapiBreaking=N`; viene ignorato se oasdiff è assente o la specifica di base non è risolvibile | **Consultivo** | +eseguiti effettivamente; gli scanner esterni vengono installati tramite `gh release download` +e si ignorano automaticamente (exit 0) se un eseguibile risulta ancora assente. + +| Script | Verifica | Bloccante | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| `check:circular-deps` | Nessuna dipendenza circolare (dpdm) | **Consultivo** | +| `check:bundle-size` | La dimensione del bundle non supera il limite | **Consultivo** | +| `check:secrets` | Scansione dei segreti (gitleaks) — ignorata se l'eseguibile è assente | **Consultivo** | +| `check:vuln-ratchet` | Le vulnerabilità delle dipendenze (osv-scanner) non peggiorano — ignorato se l'eseguibile è assente | **Consultivo** | +| `check:workflows` | Lint dei workflow (actionlint + zizmor); scanner mancanti/non funzionanti, report non validi o baseline del ratchet mancante causano un errore INCOMPLETE. I risultati validi seguono la policy rigorosa/consultiva/ratchet selezionata | Esecuzione obbligatoria; ratchet zizmor bloccante nella CI | +| `check:openapi-breaking` | Modifiche incompatibili al contratto dell'API pubblica (`openapi.yaml`) rispetto al branch di base (oasdiff) — genera `openapiBreaking=N`; ignorato se oasdiff è assente o se la specifica di base non può essere risolta | **Consultivo** | ### Job: `docs-sync-strict` Viene eseguito su ogni PR verso `main`. Blocca il merge in caso di errore. -| Script | Verifica | Bloccante | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| `check:docs-all` | Meta-gate che esegue sequenzialmente i 6 sotto-gate riportati di seguito | Sì | -| ↳ `check:docs-sync` | Coerenza delle versioni tra CHANGELOG / OpenAPI / llm.txt | Sì | -| ↳ `check:docs-counts` | I conteggi nel testo (numero di provider, numero di migrazioni, ecc.) rientrano nella finestra ratchet dei conteggi reali | Sì | -| ↳ `check:env-doc-sync` | Ogni variabile di ambiente in `.env.example` è documentata in una tabella della documentazione e viceversa | Sì | -| ↳ `check:deprecated-versions` | Nessuna stringa di versione deprecata nella documentazione | Sì | -| ↳ `check:doc-links` | I link markdown interni nella documentazione rimandano a file reali (formato `[testo]`/`(percorso)`) | Sì | -| ↳ `check:fabricated-docs` | Le route, le variabili di ambiente, i comandi CLI, i nomi degli hook e i percorsi dei file citati nella documentazione esistono nel codebase. Gate rigido tramite `--strict`; errore non bloccante senza il flag. | Sì (tramite `--strict` nella CI) | -| `check:cli-i18n` | Le stringhe dei comandi CLI sono presenti in tutti i file delle impostazioni locali i18n | Sì | -| `check:openapi-coverage` | La specifica OpenAPI copre almeno una soglia minima soggetta a ratchet delle route reali | Sì | -| `check:openapi-security-tiers` | Le annotazioni dei livelli di sicurezza in `openapi.yaml` sono coerenti con le classificazioni di `routeGuard.ts` | **Consultivo** | -| `check:openapi-routes` | Ogni percorso in `openapi.yaml` corrisponde a un file `route.ts` reale (anti-allucinazione) | Sì | -| `check:docs-symbols` | Ogni riferimento `/api/...` in `docs/**/*.md` corrisponde a un file `route.ts` reale (anti-allucinazione) | Sì | -| `i18n translation drift` | Chiavi non tradotte nei file delle impostazioni locali i18n — solo avviso | **Consultivo** | +| Script | Verifica | Bloccante | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| `check:docs-all` | Meta-gate che esegue in sequenza i 6 sotto-gate riportati di seguito | Sì | +| ↳ `check:docs-sync` | Coerenza delle versioni tra CHANGELOG / OpenAPI / llm.txt | Sì | +| ↳ `check:docs-counts` | I conteggi nel testo (numero di provider, numero di migrazioni, ecc.) rientrano nella finestra di ratchet dei conteggi reali | Sì | +| ↳ `check:env-doc-sync` | Ogni variabile d'ambiente in `.env.example` è documentata in una tabella della documentazione e viceversa | Sì | +| ↳ `check:deprecated-versions` | Nessuna stringa di versione deprecata nella documentazione | Sì | +| ↳ `check:doc-links` | I link Markdown interni nella documentazione si risolvono in file reali (formato `[testo]`/`(percorso)`) | Sì | +| ↳ `check:fabricated-docs` | Route, variabili d'ambiente, comandi CLI, nomi degli hook e percorsi di file citati nella documentazione esistono nel codebase. Gate rigido tramite `--strict`; errore non bloccante senza il flag. | Sì (tramite `--strict` nella CI) | +| `check:cli-i18n` | Le stringhe dei comandi CLI sono presenti in tutti i file delle impostazioni locali i18n | Sì | +| `check:openapi-coverage` | La specifica OpenAPI copre almeno una soglia minima, definita tramite ratchet, delle route reali | Sì | +| `check:openapi-security-tiers` | Le annotazioni dei livelli di sicurezza in `openapi.yaml` sono coerenti con le classificazioni di `routeGuard.ts` | **Consultivo** | +| `check:openapi-routes` | Ogni percorso in `openapi.yaml` si risolve in un `route.ts` reale (anti-allucinazione) | Sì | +| `check:docs-symbols` | Ogni riferimento `/api/...` in `docs/**/*.md` si risolve in un `route.ts` reale (anti-allucinazione) | Sì | +| `i18n translation drift` | Chiavi non tradotte nei file delle impostazioni locali i18n — solo avviso | **Consultivo** | ### Job: `i18n-ui-coverage` -| Script | Verifica | Bloccante | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (inline) | La copertura delle chiavi i18n dell'interfaccia utente è ≥ 65% | Sì | -| `check-ui-value-drift` (inline) | La riscrittura di un **valore** inglese non lascia traduzioni obsolete | Sì | -| `check-new-key-coverage` (inline) | Una **nuova** chiave inglese è tradotta in ogni impostazione locale — un marcatore `__MISSING__:` viene rifiutato | Sì | -| `check-translation-ratio` | Il rapporto di traduzioni reali per impostazione locale (valori identici all'inglese / segnaposto / elementi mancanti non inclusi nell'allowlist) non deve superare `config/quality/i18n-translation-baseline.json` + margine | **Consultivo** | +| Script | Verifica | Bloccante | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (inline) | La copertura delle chiavi i18n dell'interfaccia utente è ≥ 65% | Sì | +| `check-ui-value-drift` (inline) | La riscrittura di un **valore** inglese non lascia alcuna traduzione obsoleta | Sì | +| `check-new-key-coverage` (inline) | Una **nuova** chiave inglese è tradotta in ogni impostazione locale — un marcatore `__MISSING__:` viene rifiutato | Sì | +| `check-translation-ratio` | Il rapporto di traduzioni reali per impostazione locale (valori identici all'inglese / segnaposto / elementi mancanti non inclusi nell'elenco consentito) non deve superare `config/quality/i18n-translation-baseline.json` + margine | **Consultivo** | -Richiede `fetch-depth: 0` — il gate value-drift confronta `en.json` con la merge base. +Richiede `fetch-depth: 0` — il gate value-drift confronta `en.json` con la base di merge. #### `check-ui-value-drift` — gate per le traduzioni obsolete -Intercetta l'unica regressione i18n che gli altri gate non possono rilevare strutturalmente: un valore inglese +Rileva l'unica regressione i18n che gli altri gate non possono individuare strutturalmente: un valore inglese viene riscritto e le traduzioni derivate dall'inglese _precedente_ rimangono invariate, quindi -gli utenti non anglofoni continuano a leggere testi formulati in modo autorevole, ma ora errati. +gli utenti non anglofoni continuano a leggere un testo formulato con sicurezza, ma ormai errato. -Questo problema è stato effettivamente distribuito. `oauthModal.googleOAuthWarning` è stato riscritto quando è stato -introdotto l'helper di accesso Antigravity (#5203); **39 impostazioni locali su 43** hanno mantenuto il testo che indicava agli operatori di «copiare +È successo davvero in produzione. `oauthModal.googleOAuthWarning` è stato riscritto quando è stato +introdotto l'helper di accesso Antigravity (#5203); **39 delle 43 impostazioni locali** hanno mantenuto il testo che indicava agli operatori di «copiare l'URL completo e incollarlo qui sotto» — un flusso che non può essere completato per quel provider. Il problema è passato inosservato fino alla #8463 perché: -- `sync-ui-keys` aggiunge solo le chiavi **assenti**, mai quelle **obsolete**; -- `check-ui-keys-coverage` conteggia la _presenza_ delle chiavi, quindi una traduzione obsoleta viene considerata coperta; -- `check-translation-drift` tiene traccia delle copie speculari della documentazione `docs/i18n//**.md` — +- `sync-ui-keys` completa solo le chiavi **assenti**, mai quelle **obsolete**; +- `check-ui-keys-coverage` conta la _presenza_ delle chiavi, quindi una traduzione obsoleta viene considerata coperta; +- `check-translation-drift` monitora le copie speculari della documentazione in `docs/i18n//**.md` — non legge mai `src/i18n/messages/*.json`. Bloccante nel job `docs-sync-strict` dalla - risincronizzazione 2026-09: modifica un documento principale → `npm run i18n:run -- --files=` (a livello di sezione, economico). + risincronizzazione 2026-09: modifica un documento principale → `npm run i18n:run -- --files=` (a livello di sezione, poco costoso). -**Consapevole delle differenze, non basato su una baseline.** Confronta `en.json` alla base di merge con +**Basato sulle differenze, non su una baseline.** Confronta `en.json` alla base di merge con l'albero di lavoro; per ogni chiave il cui valore inglese è cambiato, qualsiasi lingua che -mantenga ancora una traduzione invariata è obsoleta. Questo deliberatamente **congela il debito preesistente**: -una differenza non può rivelare da quale vecchio testo inglese derivi una traduzione presente da tempo, -quindi il controllo valuta solo ciò che viene modificato dalla modifica corrente. L'alternativa (una baseline -di hash per chiave) richiederebbe un file generato di circa 600 KB, 3 volte più grande della maggiore -baseline esistente, modificato a ogni PR di i18n. +contenga ancora una traduzione non modificata è obsoleta. Questo deliberatamente **congela +il debito preesistente**: una differenza non può rivelare da quale vecchio testo inglese +derivi una traduzione esistente da tempo, quindi il controllo valuta solo ciò che viene +toccato dalla modifica corrente. L'alternativa (una baseline con hash per ogni chiave) +richiederebbe un file generato di ~600 KB, 3 volte più grande della maggiore baseline +esistente, con modifiche a ogni PR di internazionalizzazione. -Esistono due modi per soddisfare il controllo: +Due modi per soddisfare il controllo: 1. aggiornare le traduzioni interessate, oppure -2. impostarle su `__MISSING__:` — il runtime fornisce quindi il testo inglese corretto - (`src/i18n/request.ts::deepMergeFallback`, #7258) e la chiave viene accodata per la traduzione. +2. impostarle su `__MISSING__:` — il runtime fornirà quindi il testo inglese + corretto (`src/i18n/request.ts::deepMergeFallback`, #7258) e la chiave verrà accodata + per la traduzione. -Se è cambiato il **significato** della stringa, è preferibile **rinominare la chiave**: una nuova chiave -non può ereditare una traduzione obsoleta. Questo è il modello usato dalla #8463. +Se è cambiato il **significato** della stringa, è preferibile **rinominare la chiave**: una +nuova chiave non può ereditare una traduzione obsoleta. È il modello usato dalla #8463. ```bash npm run i18n:check-value-drift # rigoroso (quello eseguito dalla CI) @@ -235,47 +237,50 @@ npm run i18n:check-value-drift:warn # solo segnalazione BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Termina con codice 0 e `SKIP reason=base-unresolved` quando il catalogo di base non può essere letto -(clone superficiale senza il riferimento di base), analogamente a `check-openapi-breaking`. +Termina con codice 0 e `SKIP reason=base-unresolved` quando il catalogo di base non può +essere letto (clone superficiale senza il riferimento di base), analogamente a +`check-openapi-breaking`. -### Job: `i18n` +### Processo: `i18n` -Matrice completa di convalida i18n (un job per lingua). L'intero job è consultivo. +Matrice completa di convalida dell'internazionalizzazione (un processo per lingua). +L'intero processo è consultivo. -| Script | Convalida | Bloccante | -| ------------------------------- | --------------------------------------- | ---------------------------------------------------------- | -| `validate_translation.py quick` | Completezza della traduzione per lingua | **Consultivo** (`continue-on-error: true` sull'intero job) | +| Script | Convalida | Bloccante | +| ------------------------------- | --------------------------------------- | --------------------------------------------------------------- | +| `validate_translation.py quick` | Completezza della traduzione per lingua | **Consultivo** (`continue-on-error: true` sull'intero processo) | -### Job: `pr-test-policy` +### Processo: `pr-test-policy` Viene eseguito solo sulle pull request. | Script | Convalida | Bloccante | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | Le PR che modificano il codice di produzione in `src/`, `open-sse/`, `electron/` o `bin/` devono includere o aggiornare i test (Regola rigida #8) | Sì | +| `check:pr-test-policy` | Le PR che modificano il codice di produzione in `src/`, `open-sse/`, `electron/` o `bin/` devono includere o aggiornare i test (Regola rigida n. 8) | Sì | | `check:test-masking` | I file di test modificati non riducono il numero netto di asserzioni né aggiungono tautologie `assert.ok(true)` | Sì | -| `check:pr-evidence` | Il corpo della PR cita prove di test/VPS relative alla modifica (automatizza la Regola rigida #18 cercando nel testo della PR — fragile, vedere Arretrati) | Sì | +| `check:pr-evidence` | Il corpo della PR cita prove da test/VPS relative alla modifica (automatizza la Regola rigida n. 18 cercando nel testo della PR — fragile, vedere Backlog) | Sì | -### Job: `test-vitest` +### Processo: `test-vitest` -Viene eseguito dopo `build`. Blocca il merge in caso di errore. +Viene eseguito dopo `build`. In caso di errore, blocca il merge. -| Suite | Convalida | Bloccante | -| ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -| `test:vitest` | Server MCP (110 strumenti), autoCombo, cache — runner vitest | Sì | -| `test:vitest:ui` | Test dei componenti dell'interfaccia — runner vitest | **Bloccante** — gli errori preesistenti sono esplicitamente esclusi in `vitest.config.ts`; i nuovi errori fanno fallire il job | +| Suite | Convalida | Bloccante | +| ---------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Server MCP (110 strumenti), autoCombo, cache — runner vitest | Sì | +| `test:vitest:ui` | Test dei componenti UI — runner vitest | **Bloccante** — gli errori preesistenti sono esplicitamente esclusi in `vitest.config.ts`; i nuovi errori fanno fallire il processo | ### Workflow notturni (pianificati, consultivi) -Vengono eseguiti secondo una pianificazione cron (e tramite `workflow_dispatch`), mai sulle PR. Sono tutti consultivi. +Vengono eseguiti secondo una pianificazione cron (e tramite `workflow_dispatch`), mai +sulle PR. Sono tutti consultivi. | Workflow | Convalida | Bloccante | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | Test basati sulle proprietà con fast-check, un seed casuale e un numero elevato di esecuzioni | **Consultivo** | -| `nightly-resilience` | Controllo della crescita dell'heap, iniezione caotica di guasti, test di carico/soak con k6 | **Consultivo** | -| `nightly-llm-security` | Protezione dalle injection di promptfoo (modalità di blocco) + sonde garak (ignorate senza il segreto di un provider) | **Consultivo** | +| `nightly-property` | Test basati sulle proprietà con fast-check, con un seed casuale e un numero elevato di esecuzioni | **Consultivo** | +| `nightly-resilience` | Controllo della crescita dell'heap, iniezione caotica di errori, test di carico/prolungati con k6 | **Consultivo** | +| `nightly-llm-security` | Protezione dalle iniezioni con promptfoo (modalità blocco) + sonde garak (ignorate senza il segreto di un provider) | **Consultivo** | | `nightly-schemathesis` | Fuzzing del contratto OpenAPI (schemathesis) su un'istanza OmniRoute attiva usando `docs/openapi.yaml` — evidenzia violazioni della specifica/errori 500 non gestiti (Fase 8 B.4) | **Consultivo** | -| `nightly-mutation` | Punteggio dei test di mutazione Stryker sulla corsia rapida dei test unitari — i mutanti sopravvissuti evidenziano asserzioni deboli | **Consultivo** | +| `nightly-mutation` | Punteggio dei test di mutazione Stryker sulla corsia veloce dei test unitari — i mutanti sopravvissuti evidenziano asserzioni deboli | **Consultivo** | | `nightly-compat` | Matrice di compatibilità del motore Node per gli intervalli `engines.node` supportati | **Consultivo** | --- @@ -553,39 +558,39 @@ errore di connessione e non interrompe mai le sessioni. Questo inventario è stato riconciliato con `ci.yml` il 2026-06-17 (la versione precedente ometteva `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Una revisione del ROI dell'insieme riconciliato -ha identificato i seguenti candidati alla razionalizzazione. **Gli accorpamenti sono modifiche -meccaniche alla CI; le attivazioni/disattivazioni e le rimozioni sono decisioni di policy riservate -all'operatore.** Nulla di quanto segue è stato ancora applicato. +`check:codeql-ratchet`, `check:pr-evidence`). Una revisione ROI dell'insieme riconciliato +ha individuato i seguenti candidati alla razionalizzazione. **Le unificazioni sono modifiche +meccaniche alla CI; le conversioni/rimozioni sono decisioni di policy riservate all'operatore.** +Nulla di quanto segue è stato ancora applicato. -**Non documentati anche sopra** (consultivi, segnale debole): il job `docs-lint` -(markdownlint + Vale, intero job con `continue-on-error`) e i workflow autonomi degli scanner +**Inoltre non documentati sopra** (consultivi, segnale debole): il job `docs-lint` +(markdownlint + Vale, intero job con `continue-on-error`) e i workflow di scansione autonomi `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` è presente in `quality-baseline.json`, ma non è collegato a un ratchet bloccante in `ci.yml`: la metrica è attualmente orfana. -### Accorpamento / deduplicazione (meccanici, rischio inferiore) +### Unificazione / deduplicazione (meccanica, rischio inferiore) -Ogni candidato è stato convalidato rispetto allo stato effettivo dei gate il 2026-06-17 (fidarsi, -ma verificare); diversi accorpamenti "ovvi" si sono rivelati celare debito e **non** sono -sostituzioni dirette senza rischi. +Ogni candidato è stato validato rispetto allo stato effettivo dei gate il 2026-06-17 +(fidarsi, ma verificare); diverse unificazioni "ovvie" si sono rivelate nascondere debito +e **non** sono sostituzioni dirette prive di rischi. -- **`check:docs-sync` viene eseguito due volte** — autonomamente nel job `lint` e nuovamente all'interno di `check:docs-all` (`docs-sync-strict`) e dell'hook pre-commit di husky. ✅ **FATTO** — rimossa l'esecuzione autonoma da `lint`. -- **Scansione delle CVE** — ❌ **NON è un accorpamento diretto senza rischi.** `audit:deps` genera un errore bloccante per qualsiasi CVE di gravità alta/critica; `check:vuln-ratchet` (osv) fallisce soltanto in caso di _regressione_ rispetto alla baseline (attualmente 1 MODERATE). Semantiche diverse: rimuovere `audit:deps` eliminerebbe il gate assoluto per le vulnerabilità di gravità alta/critica. Mantenerli entrambi. -- **Rilevamento dei cicli** — ❌ **NON è un accorpamento diretto senza rischi.** `check:circular-deps` (dpdm) segnala **91 cicli** (per questo è consultivo); non può essere promosso a bloccante senza prima risolverli e ha un ambito più ampio rispetto a `check:cycles`, che è verde e mirato. Mantenere `check:cycles` come bloccante; la risoluzione dei 91 cicli dpdm costituisce un backlog separato. -- **Complessità** — ✅ **FATTO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): un'unica scansione ESLint, con conteggi per ruleId, in modo che le baseline di complessità ciclomatica+numero massimo di righe e di complessità cognitiva rimangano indipendenti; i singoli `check:complexity` / `check:cognitive-complexity` restano disponibili per l'uso locale con `--update`. -- **Anti-allucinazione di `/api`** — ✅ **FATTO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): un unico inventario del filesystem di `src/app/api`; openapi-routes + docs-symbols continuano a generare report indipendenti; i singoli controlli restano disponibili per le esecuzioni locali. -- **`check:node-runtime` viene eseguito in 11 job** — ⚠️ **ROI basso.** Ciascuno usa un runner separato e il controllo richiede <1s; risparmio totale di ~10s, a fronte della perdita di una protezione economica per ciascun job. Non vale la complessità della modifica. -- **`typecheck:noimplicit:core` nel lint della CI** — ✅ **rimosso dal job lint** (era consultivo con `continue-on-error`); la superficie dei tipi bloccante è `typecheck:core` + `check:type-coverage`. Script locale mantenuto. +- **`check:docs-sync` viene eseguito due volte** — autonomamente nel job `lint` e nuovamente all'interno di `check:docs-all` (`docs-sync-strict`) e dell'hook pre-commit di husky. ✅ **FATTO** — l'invocazione autonoma in `lint` è stata rimossa. +- **Scansione delle CVE** — ❌ **NON è un'unificazione pulita.** `audit:deps` fallisce in modo bloccante per qualsiasi CVE alta/critica; `check:vuln-ratchet` (osv) fallisce solo in caso di _regressione_ rispetto alla baseline (attualmente 1 MODERATE). Semantiche diverse: la rimozione di `audit:deps` eliminerebbe il gate assoluto per le vulnerabilità alte/critiche. Mantenerli entrambi. +- **Rilevamento dei cicli** — ✅ **FATTO** (#15159 G-01/G-02). Il testo precedente definiva `check:cycles` il gate "verde e curato" e giustificava il suo mantenimento come bloccante perché `check:circular-deps` (dpdm) segnalava 91 cicli. Quel verde era un **falso verde**: `check:cycles` analizzava 5 sottodirectory (450 file), rilevava solo `import|export … from` statici e scartava ogni specificatore `@/` e `@omniroute/open-sse/`, quindi non poteva rilevare i cicli con import dinamici + alias che dominavano il repository. Corretto: ora il gate percorre `src` + `open-sse` (5023 file), raccoglie gli specificatori dall'AST TypeScript (quindi `import("…")` conta, mentre `typeof import("…")` in posizione di tipo no) e risolve i `paths` di tsconfig. Trova **14** cicli, non 0. Poiché 14 cicli preesistenti non possono essere corretti in una PR dedicata al gate, `check:cycles` è ora un **ratchet** (`--ratchet`, soglia massima `metrics.cycles.value = 14` in `quality-baseline.json`, `direction: down`): blocca qualsiasi _regressione_ e il conteggio può solo diminuire. La CI esegue `npm run check:cycles:ratchet`. La riduzione progressiva procede insieme ad **A-01**. `check:circular-deps` (dpdm) rimane consultivo come seconda valutazione più ampia. +- **Complessità** — ✅ **FATTO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): un'unica scansione ESLint, con conteggi per ruleId affinché le baseline di complessità ciclomatica+max-lines e cognitiva restino indipendenti; i singoli `check:complexity` / `check:cognitive-complexity` rimangono disponibili per l'uso locale con `--update`. +- **Anti-allucinazione di `/api`** — ✅ **FATTO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): un unico inventario del file system per `src/app/api`; openapi-routes + docs-symbols continuano a produrre report indipendenti; i controlli individuali rimangono disponibili per le esecuzioni locali. +- **`check:node-runtime` viene eseguito in 11 job** — ⚠️ **ROI basso.** Ognuno è un runner separato e il controllo richiede <1s; il risparmio totale è di ~10s, a fronte della perdita di una protezione economica per ogni job. Non vale la pena affrontare le modifiche necessarie. +- **`typecheck:noimplicit:core` nel lint della CI** — ✅ **rimosso dal job lint** (era consultivo con `continue-on-error`); la superficie di tipi bloccante è `typecheck:core` + `check:type-coverage`. Script locale mantenuto. -### Attivare/disattivare / decidere (policy dell'operatore) +### Conversione / decisione (policy dell'operatore) -- `check:openapi-security-tiers` (consultivo) — ❌ **NON può essere reso bloccante senza interventi preliminari.** Termina con codice 0, ma avverte che diverse route di `traffic-inspector` sotto `LOCAL_ONLY_API_PREFIXES` non includono l'annotazione `x-loopback-only: true`. Per renderlo bloccante è necessario aggiungere prima tali annotazioni a `openapi.yaml`. -- `typecheck:noimplicit:core` (consultivo) — ampiamente sostituito dal ratchet bloccante `check:type-coverage`. Convertirlo in un ratchet oppure rimuovere il secondo passaggio `tsc` ridondante. -- `test:vitest:ui` (ora **bloccante**) — gli errori preesistenti sono esplicitamente esclusi in `vitest.config.ts` con commenti di tracciamento `// #8618`; i nuovi errori fanno fallire il job. -- `check:secrets` (gitleaks, ratchet bloccante congelato su 3 falsi positivi documentati) — aggiungere i 3 elementi all'allowlist per raggiungere 0, oppure declassarlo a consultivo. Si sovrappone alla scansione nativa dei segreti di GitHub + `check:public-creds`. -- `check:pr-evidence` (bloccante, esegue grep sul testo del corpo della PR) — alto rischio di falsi positivi; la rimozione indebolirebbe l'applicazione della Regola Rigida n. 18, quindi si tratta di un'autentica decisione di policy. -- `semgrep` (workflow autonomo consultivo) — si sovrappone a CodeQL per le categorie OWASP; collegare la sua baseline a un ratchet oppure rimuoverlo. +- `check:openapi-security-tiers` (consultivo) — ❌ **NON convertibile in modo pulito.** Termina con codice 0, ma avverte che diverse route `traffic-inspector` sotto `LOCAL_ONLY_API_PREFIXES` non hanno l'annotazione `x-loopback-only: true`. Per renderlo bloccante è necessario aggiungere prima tali annotazioni a `openapi.yaml`. +- `typecheck:noimplicit:core` (consultivo) — in gran parte sostituito dal ratchet bloccante `check:type-coverage`. Convertirlo in ratchet oppure rimuovere il secondo passaggio `tsc` ridondante. +- `test:vitest:ui` (ora **bloccante**) — gli errori preesistenti sono esclusi esplicitamente in `vitest.config.ts` con commenti di tracciamento `// #8618`; i nuovi errori fanno fallire il job. +- `check:secrets` (gitleaks, ratchet bloccante fissato a 3 falsi positivi documentati) — aggiungere i 3 alla allowlist per arrivare a 0, oppure declassarlo a consultivo. Si sovrappone alla scansione nativa dei segreti di GitHub + `check:public-creds`. +- `check:pr-evidence` (bloccante, esegue grep sul testo descrittivo del corpo della PR) — elevato rischio di falsi positivi; rimuoverlo indebolisce l'applicazione della Hard Rule #18, quindi si tratta di una vera decisione di policy. +- `semgrep` (autonomo consultivo) — si sovrappone a CodeQL per le famiglie OWASP; collegare la sua baseline a un ratchet oppure rimuoverlo. --- diff --git a/docs/i18n/it/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/it/docs/compression/COMPRESSION_GUIDE.md index d29dfdb7d195..8787feaae102 100644 --- a/docs/i18n/it/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/it/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ iniettata al posto dell'inglese. Su un corpo con `messages`, un bypass di contenuto (`shouldBypassCavemanOutputMode()` in `open-sse/services/compression/outputMode.ts`) controlla gli ultimi tre messaggi e salta gli stili per l'intera conversazione quando corrispondono alle sue parole chiave di sicurezza, -azione irreversibile, chiarimento o sensibilità all'ordine. Il bypass viene eseguito indipendentemente -dall'impostazione del toggle **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) della dashboard. +azione irreversibile, chiarimento o sensibilità all'ordine. Il bypass viene eseguito finché il toggle **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) della dashboard è attivo, che è l'impostazione predefinita; con il toggle disattivato, gli stili selezionati si applicano anche in quei turni. Quando il bypass consente il passaggio, `placeSystemInstruction()` (stesso file), che non crea mai un nuovo `messages[0]`, posiziona il blocco nel primo di questi che trova: diff --git a/docs/i18n/it/docs/reference/ENVIRONMENT.md b/docs/i18n/it/docs/reference/ENVIRONMENT.md index e55a3b672739..13d24ba70ea0 100644 --- a/docs/i18n/it/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/it/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Segreti obbligatori -Questi valori **devono** essere impostati prima della prima esecuzione. Senza di essi, l'applicazione si rifiuterà di avviarsi oppure utilizzerà impostazioni predefinite non sicure. - -| Variabile | Obbligatoria | Predefinito | File sorgente | Descrizione | -| ---------------------------- | ------------------- | --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Sì** | _(nessuno)_ | `src/lib/auth` | Firma/verifica tutti i cookie di sessione della dashboard (JWT). Generare con `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Sì** | _(nessuno)_ | `src/lib/db/apiKeys.ts` | Chiave di crittografia AES per i valori delle chiavi API archiviati in SQLite. Generare con `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Sì** | `CHANGEME` | Script di bootstrap | Imposta la password iniziale dell'amministratore della dashboard (corrisponde al valore predefinito di `.env.example`, mantenuto intenzionalmente non sicuro per imporne la modifica). **Modificare prima del primo utilizzo.** Dopo l'accesso, modificarla tramite Dashboard → Impostazioni → Sicurezza. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sì** (produzione) | _(non impostato)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Segreto condiviso per il bridge WebSocket interno di Codex Responses. Autentica le richieste del bridge tra il relay WS di Electron/browser e OmniRoute. ⚠️ **OBBLIGATORIO in produzione: quando non è impostato, tutte le richieste al bridge WS vengono rifiutate.** Generare con `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | No | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID esplicito per l'invalidazione della cache del service worker per la shell PWA (#11779); primo nella catena di risoluzione. | -| `SOURCE_VERSION` | No | _(non impostato)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Secondo nella catena: impostato dai sistemi di build PaaS (ad es. in stile Heroku) sul commit distribuito. | -| `NEXT_PUBLIC_SW_BUILD_ID` | No | _(derivato)_ | `src/shared/components/PwaRegister.tsx` | Valore pubblico definito in fase di build che il client utilizza per registrare `/sw.js?v=…`; derivato dai due valori precedenti e quindi dallo SHA git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | No (automatico) | _(automatico a ogni avvio)_ | `src/server/authz/policies/management.ts` | Segreto specifico del processo che dimostra che il contrassegno dell'IP peer attendibile proviene dal server HTTP di OmniRoute (`scripts/dev/peer-stamp.mjs`). Il middleware di autorizzazione considera attendibile la località della richiesta (restrizione loopback/LAN delle route LOCAL_ONLY) solo quando il contrassegno contiene questo token. Viene generato automaticamente a ogni avvio: lasciarlo non impostato; specificarlo solo per configurazioni multiprocesso che devono condividere il contrassegno. | -| `OMNIROUTE_TRUSTED_PROXIES` | No | _(non impostato)_ | `scripts/dev/peer-stamp.mjs` | IP o intervalli CIDR, separati da virgole, dei reverse proxy i cui valori `X-Forwarded-For` / `X-Real-IP` possono essere considerati attendibili dall'elenco di autorizzazione/blocco degli IP, oltre agli indirizzi loopback, agli indirizzi di rete privata e agli edge Cloudflare, che sono sempre considerati attendibili. Necessario solo per un proxy su qualsiasi altro indirizzo pubblico; in caso contrario, il filtro valuta l'indirizzo del proxy stesso. | +I segreti non impostati contrassegnati con **Sì** vengono generati automaticamente alla prima esecuzione (`JWT_SECRET`, `API_KEY_SECRET`) e salvati in `server.env` nella directory dei dati — impostarli esplicitamente quando tale directory è effimera. L'eccezione è `OMNIROUTE_WS_BRIDGE_SECRET`: non viene salvato in `server.env` — i punti di ingresso del server generano un valore effimero per ogni avvio utilizzabile solo dal bridge in-process, quindi i client bridge esterni devono impostarlo esplicitamente. + +| Variabile | Obbligatoria | Predefinito | File sorgente | Descrizione | +| ---------------------------- | ------------------- | --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Sì** | _(nessuno)_ | `src/lib/auth` | Firma/verifica tutti i cookie di sessione della dashboard (JWT). Generare con `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Sì** | _(nessuno)_ | `src/lib/db/apiKeys.ts` | Chiave di crittografia AES per i valori delle chiavi API archiviati in SQLite. Generare con `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | No | _(non impostato)_ | `src/lib/auth/managementPassword.ts` | Imposta la password della dashboard all'avvio finché non ne è stata salvata una; viene ignorata una volta salvata una password. Le installazioni npm e da sorgente copiano `.env.example` in `.env`, quindi partono con il suo segnaposto pubblicamente noto `CHANGEME`: sostituirlo prima del primo avvio, oppure impostare `INITIAL_PASSWORD=` (vuota) in quel `.env` per creare la password nella procedura guidata di configurazione alla prima visita, come fa un valore non impostato (il predefinito dell'immagine Docker). La password salvata si modifica in Dashboard → Impostazioni → Sicurezza, oppure eseguire `omniroute reset-password` (`node bin/reset-password.mjs` in un checkout da sorgente) con `DATA_DIR` impostata alla directory dei dati del server. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sì** (produzione) | _(non impostato)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Segreto condiviso per il bridge WebSocket interno di Codex Responses. Autentica le richieste del bridge tra il relay WS di Electron/browser e OmniRoute. ⚠️ **OBBLIGATORIO in produzione: quando non è impostato, tutte le richieste al bridge WS vengono rifiutate.** Generare con `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | No | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID esplicito per l'invalidazione della cache del service worker per la shell PWA (#11779); primo nella catena di risoluzione. | +| `SOURCE_VERSION` | No | _(non impostato)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Secondo nella catena: impostato dai sistemi di build PaaS (ad es. in stile Heroku) sul commit distribuito. | +| `NEXT_PUBLIC_SW_BUILD_ID` | No | _(derivato)_ | `src/shared/components/PwaRegister.tsx` | Valore pubblico definito in fase di build che il client utilizza per registrare `/sw.js?v=…`; derivato dai due valori precedenti e quindi dallo SHA git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | No (automatico) | _(automatico a ogni avvio)_ | `src/server/authz/policies/management.ts` | Segreto specifico del processo che dimostra che il contrassegno dell'IP peer attendibile proviene dal server HTTP di OmniRoute (`scripts/dev/peer-stamp.mjs`). Il middleware di autorizzazione considera attendibile la località della richiesta (restrizione loopback/LAN delle route LOCAL_ONLY) solo quando il contrassegno contiene questo token. Viene generato automaticamente a ogni avvio: lasciarlo non impostato; specificarlo solo per configurazioni multiprocesso che devono condividere il contrassegno. | +| `OMNIROUTE_TRUSTED_PROXIES` | No | _(non impostato)_ | `scripts/dev/peer-stamp.mjs` | IP o intervalli CIDR, separati da virgole, dei reverse proxy i cui valori `X-Forwarded-For` / `X-Real-IP` possono essere considerati attendibili dall'elenco di autorizzazione/blocco degli IP, oltre agli indirizzi loopback, agli indirizzi di rete privata e agli edge Cloudflare, che sono sempre considerati attendibili. Necessario solo per un proxy su qualsiasi altro indirizzo pubblico; in caso contrario, il filtro valuta l'indirizzo del proxy stesso. | ### Comandi di generazione @@ -215,38 +215,38 @@ OmniRoute utilizza **SQLite** (tramite `better-sqlite3`) per tutta la persistenz ## 4. Sicurezza e autenticazione -| Variabile | Valore predefinito | File sorgente | Descrizione | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinato con gli identificatori hardware per generare l'impronta digitale della macchina. Modificarlo per ogni distribuzione per garantire l'isolamento. | -| `OMNIROUTE_CLI_SALT` | _(non impostato = salt casuale per installazione, persistito in `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC per derivare il token di autenticazione locale della CLI. L'impostazione di questo valore ruota tutti i token CLI sulla macchina e ha sempre la precedenza sul salt persistito. Vedere `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Imposta il flag `Secure` sui cookie di sessione. **Deve essere `true`** durante l'esecuzione dietro HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware API | Quando è `true`, tutte le richieste proxy `/v1/*` devono includere una chiave API valida. **Questo flag non controlla `GET /v1/models`**, che segue invece la configurazione di accesso alla dashboard (`requireAuthForModels`): pertanto, una risposta `401` da `/v1/models` NON significa che l'inferenza sia protetta. Vedere `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Consente di mostrare i valori completi delle chiavi API nell'interfaccia utente della dashboard. Configurabile tramite i flag di funzionalità della dashboard; comporta un rischio per la sicurezza nelle istanze condivise. | -| `NO_LOG_API_KEY_IDS` | _(vuoto)_ | `src/lib/compliance/index.ts` | ID di chiavi API, separati da virgole, per i quali viene omessa la registrazione delle richieste (conformità al GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(non impostato = illimitato)_ | `src/shared/utils/apiKeyPolicy.ts` | Budget giornaliero di richieste di riserva applicato alle chiavi API la cui colonna `rate_limits` è null. Non impostato o vuoto: nessun limite implicito (#2289, #11017). `0` equivale a illimitato. Un intero positivo N abilita N richieste/giorno, 5N/settimana, 20N/mese. I valori non vuoti con formato non valido ripristinano le finestre precedenti di 1000/giorno, 5000/settimana e 20000/mese. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Dimensione massima consentita per il corpo della richiesta. Rifiuta i payload che superano questo limite. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | I corpi effettivi delle richieste pari o superiori a questa soglia acquisiscono il lease atomico locale al processo per l'ammissione dei carichi pesanti prima dell'analisi JSON (percorso BYTE, incluso `POST /v1/responses`). Stesso meccanismo di uscita in presenza di margine operativo sufficiente di [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) usato per le richieste strutturalmente pesanti; resta comunque limitato da `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), affinché [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) non si ripresenti. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite massimo rigido della route di chat, applicato ai byte letti durante l'acquisizione limitata, incluse le richieste con `Content-Length` mancante, non valido o non veritiero; il superamento genera `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(non impostato — nessun limite al numero di richieste)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** questo limite legacy al NUMERO di richieste si applica ora solo quando è impostato esplicitamente. Se non impostato (impostazione predefinita), l'ammissione delle richieste di chat pesanti è invece regolata da `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`, un budget in BYTE derivato automaticamente e dimensionato in base al limite di memoria reale del processo in **un solo processo** (un solo heap V8). Due richieste `/v1/responses` sovrapposte da circa 750.000 token causano l'interruzione di heap da circa 12 GiB (#7849): è un avviso relativo al **budget di memoria**, non un limite massimo rigido del prodotto pari a 2. Un processo in buono stato (heap al di sotto del rapporto di alleggerimento) PUÒ ammettere più richieste `/v1/responses` lunghe simultanee tramite `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Decine di client SSE di lunga durata (40–50) dipendono dall'heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, non da un «massimo di 2». Aumentare ciecamente questo valore per «utilizzare l'host» reintroduce #7849. Moltiplicare gli **heap** con **N `DATA_DIR` indipendenti** (#11024); non usare mai `replicas>1` su un singolo file SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivato automaticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** sostituzione del budget di byte di acquisizione derivato automaticamente (il 25% del limite di memoria V8/cgroup più restrittivo, diviso per un'amplificazione transitoria di 8x). I valori derivati ed espliciti sono limitati all'intervallo 8 MiB–2 GiB. Un corpo più grande del budget effettivo fallisce immediatamente con `413 body_exceeds_budget`; la contesa tra corpi gestibili singolarmente continua a generare un errore `503` ritentabile. La gestione simultanea di 40–50 client SSE di lunga durata dipende da questo budget + heap, non da un limite rigido «massimo di 2». Prima di modificare la configurazione, consultare `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` in `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Rapporto di alleggerimento dovuto alla pressione sull'heap (`heapUsed / heap_size_limit`) per l'ammissione pesante basata su BYTE e STRUTTURA (#10183, #10268, #10437). Una richiesta pesante simultanea oltre `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` viene respinta con il codice ritentabile `503` solo quando ANCHE l'heap si trova a questo rapporto o al di sopra di esso; con un heap in buono stato viene invece ammessa tramite la capacità aggiuntiva per stato sano. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valore predefinito `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacità aggiuntiva limitata per il percorso rapido con heap in buono stato (#10437), sia per STRUTTURA sia per BYTE (`admitChatRequest`, inclusi i corpi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Senza questo limite, ogni richiesta con il sistema occupato ma l'heap in buono stato ignorava l'ammissione senza alcun tetto massimo. Quando questo numero di lease simultanei è attivo tramite il percorso con heap in buono stato, le ulteriori richieste effettuate mentre il sistema è occupato passano allo STESSO percorso di attesa limitata/rifiuto usato in condizioni di effettiva pressione sull'heap. `0` disabilita completamente l'aggiramento. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Numero di messaggi che classifica una richiesta di chat come pesante anche quando il corpo è al di sotto della soglia in byte. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Numero di strumenti che classifica una richiesta di chat come pesante anche quando il corpo è al di sotto della soglia in byte. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Stima conservativa dei token basata sulla dimensione delle stringhe che classifica una richiesta come pesante; si tratta di un'approssimazione del costo di ammissione, non della tokenizzazione utilizzata dal provider per la fatturazione. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (disabilitato) | `src/shared/middleware/chatBodyAdmission.ts` | Limite facoltativo e attivabile per la cronologia della chat. Disabilitato per impostazione predefinita: il numero di messaggi è una policy di distribuzione, non una proprietà universale di una richiesta, e l'imposizione di un limite in questo punto rifiuta le conversazioni con un `413` terminale prima che la pipeline di compressione possa renderle gestibili. La crescita dell'heap è limitata da `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` e dal rifiuto dovuto alla pressione sull'heap. Impostare un valore positivo nelle distribuzioni con memoria limitata che necessitano di un tetto rigido; il superamento genera quindi un `413` strutturato che richiede la compattazione. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite massimo rigido per una risposta upstream non in streaming memorizzata interamente in memoria. Oltre questo limite, il lettore upstream viene annullato e la richiesta fallisce immediatamente, anziché far crescere senza limiti una stringa fino all'esaurimento dell'heap. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Numero massimo di byte trasmessi inoltrati dalle intestazioni della risposta upstream. Quando il limite viene superato, le intestazioni con priorità inferiore (ad esempio, le intestazioni personalizzate `x-codex-*`, `x-oai-request-id`) vengono eliminate per rimanere entro i comuni limiti delle intestazioni dei reverse proxy. Impostare un valore più alto per inoltrare più metadati upstream, a scapito di una maggiore dimensione delle intestazioni di risposta. | -| `CORS_ORIGIN` | _(non impostato)_ | `src/server/cors/origins.ts` | Elenco legacy di origini CORS consentite, limitato a una singola origine. Per le nuove distribuzioni, preferire `CORS_ALLOWED_ORIGINS`. CORS serve esclusivamente per i client API browser cross-origin; le scritture autenticate della dashboard utilizzano invece richieste same-origin insieme a una protezione CSRF associata alla sessione. | -| `CORS_ALLOWED_ORIGINS` | _(non impostato)_ | `src/server/cors/origins.ts` | Elenco separato da virgole delle origini CORS consentite. Non viene inviato alcun carattere jolly, a meno che `CORS_ALLOW_ALL=true` non sia configurato esplicitamente. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Opzione di emergenza, destinata esclusivamente allo sviluppo, per restituire qualsiasi `Origin` del browser. Non abilitarla nelle distribuzioni condivise o di produzione. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blocca le chiamate ai provider destinate a intervalli di indirizzi IP privati, loopback o link-local. Disabilitare solo in ambienti di test isolati. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Consente URL dei provider che puntano a reti private/locali (localhost, 192.168.x.x, 10.x.x.x, ecc.). **OBBLIGATORIO per i provider self-hosted** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Quando è impostato su `false`, la dashboard rifiuta la convalida degli URL locali. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Consente di aggiungere/convalidare provider su indirizzi locali/privati (127.0.0.1, localhost, LAN, intervalli privati) — limitatamente al percorso di convalida del provider. **Valore predefinito: `true`** (priorità locale); impostare su `false` per applicare un blocco rigoroso che consenta solo indirizzi pubblici. Gli endpoint dei metadati cloud (169.254.169.254, metadata.google.internal) rimangono bloccati in ogni caso. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Consente alle route `/v1/audio/*` (trascrizioni, sintesi vocale, traduzioni) di utilizzare un nodo provider compatibile con OpenAI ospitato al di fuori di localhost. Disabilitato per impostazione predefinita — l'instradamento dell'audio verso un host remoto modifica l'identità di uscita e deve essere una decisione esplicita dell'operatore. I nodi loopback/privati (localhost, 127.0.0.1, 172.16-31.x) sono sempre consentiti e non vengono interessati. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Consente a `POST /v1/rerank` (e al passaggio di reranking loopback del motore di memoria) di utilizzare un nodo provider compatibile con OpenAI ospitato al di fuori di localhost — un dispositivo nella LAN o un peer Tailscale che esegue TEI, Infinity, vLLM, ecc. Disabilitato per impostazione predefinita — l'instradamento verso un host remoto modifica l'identità di uscita e deve essere una decisione esplicita dell'operatore. I nodi loopback (localhost, 127.0.0.1, 172.16-31.x) sono sempre consentiti e non vengono interessati. I nodi remoti devono inoltre rispettare la policy degli URL in uscita del provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); gli host dei metadati cloud non vengono mai utilizzati per l'instradamento. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Quando OIDC è abilitato, disabilita l'accesso tramite password in modo che gli utenti possano autenticarsi esclusivamente tramite Single Sign-On OIDC. È accettato anche l'alias semplice `OIDC_DISABLE_PASSWORD_LOGIN`; il flag di funzionalità della dashboard con la stessa chiave ha la precedenza. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias semplice di `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variabile | Valore predefinito | File sorgente | Descrizione | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinato con gli identificatori hardware per generare l'impronta digitale della macchina. Modificarlo per ogni distribuzione per garantire l'isolamento. | +| `OMNIROUTE_CLI_SALT` | _(non impostato = salt casuale per installazione, persistito in `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC per derivare il token di autenticazione locale della CLI. L'impostazione di questo valore ruota tutti i token CLI sulla macchina e ha sempre la precedenza sul salt persistito. Vedere `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Imposta il flag `Secure` sui cookie di sessione. **Deve essere `true`** durante l'esecuzione dietro HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware API | Quando è `true`, tutte le richieste proxy `/v1/*` devono includere una chiave API valida. **Questo flag non controlla `GET /v1/models`**, che segue invece la configurazione di accesso alla dashboard (`requireAuthForModels`): pertanto, una risposta `401` da `/v1/models` NON significa che l'inferenza sia protetta. Vedere `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Consente di mostrare i valori completi delle chiavi API nell'interfaccia utente della dashboard. Configurabile tramite i flag di funzionalità della dashboard; comporta un rischio per la sicurezza nelle istanze condivise. | +| `NO_LOG_API_KEY_IDS` | _(vuoto)_ | `src/lib/compliance/index.ts` | ID di chiavi API, separati da virgole, per i quali viene omessa la registrazione delle richieste (conformità al GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(non impostato = illimitato)_ | `src/shared/utils/apiKeyPolicy.ts` | Budget giornaliero di richieste di riserva applicato alle chiavi API la cui colonna `rate_limits` è null. Non impostato o vuoto: nessun limite implicito (#2289, #11017). `0` equivale a illimitato. Un intero positivo N abilita N richieste/giorno, 5N/settimana, 20N/mese. I valori non vuoti con formato non valido ripristinano le finestre precedenti di 1000/giorno, 5000/settimana e 20000/mese. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Dimensione massima consentita per il corpo della richiesta. Rifiuta i payload che superano questo limite. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | I corpi effettivi delle richieste pari o superiori a questa soglia acquisiscono il lease atomico locale al processo per l'ammissione dei carichi pesanti prima dell'analisi JSON (percorso BYTE, incluso `POST /v1/responses`). Stesso meccanismo di uscita in presenza di margine operativo sufficiente di [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) usato per le richieste strutturalmente pesanti; resta comunque limitato da `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), affinché [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) non si ripresenti. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite massimo rigido della route di chat, applicato ai byte letti durante l'acquisizione limitata, incluse le richieste con `Content-Length` mancante, non valido o non veritiero; il superamento genera `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(non impostato — nessun limite al numero di richieste)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** questo limite legacy al NUMERO di richieste si applica ora solo quando è impostato esplicitamente. Se non impostato (impostazione predefinita), l'ammissione delle richieste di chat pesanti è invece regolata da `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`, un budget in BYTE derivato automaticamente e dimensionato in base al limite di memoria reale del processo in **un solo processo** (un solo heap V8). Due richieste `/v1/responses` sovrapposte da circa 750.000 token causano l'interruzione di heap da circa 12 GiB (#7849): è un avviso relativo al **budget di memoria**, non un limite massimo rigido del prodotto pari a 2. Un processo in buono stato (heap al di sotto del rapporto di alleggerimento) PUÒ ammettere più richieste `/v1/responses` lunghe simultanee tramite `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Decine di client SSE di lunga durata (40–50) dipendono dall'heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, non da un «massimo di 2». Aumentare ciecamente questo valore per «utilizzare l'host» reintroduce #7849. Moltiplicare gli **heap** con **N `DATA_DIR` indipendenti** (#11024); non usare mai `replicas>1` su un singolo file SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivato automaticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** sostituzione del budget di byte di acquisizione derivato automaticamente (il 25% del limite di memoria V8/cgroup più restrittivo, diviso per un'amplificazione transitoria di 8x). I valori derivati ed espliciti sono limitati all'intervallo 8 MiB–2 GiB. Un corpo più grande del budget effettivo fallisce immediatamente con `413 body_exceeds_budget`; la contesa tra corpi gestibili singolarmente continua a generare un errore `503` ritentabile. La gestione simultanea di 40–50 client SSE di lunga durata dipende da questo budget + heap, non da un limite rigido «massimo di 2». Prima di modificare la configurazione, consultare `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` in `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Rapporto di alleggerimento dovuto alla pressione sull'heap (`heapUsed / heap_size_limit`) per l'ammissione pesante basata su BYTE e STRUTTURA (#10183, #10268, #10437). Una richiesta pesante simultanea oltre `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` viene respinta con il codice ritentabile `503` solo quando ANCHE l'heap si trova a questo rapporto o al di sopra di esso; con un heap in buono stato viene invece ammessa tramite la capacità aggiuntiva per stato sano. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valore predefinito `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacità aggiuntiva limitata per il percorso rapido con heap in buono stato (#10437), sia per STRUTTURA sia per BYTE (`admitChatRequest`, inclusi i corpi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Senza questo limite, ogni richiesta con il sistema occupato ma l'heap in buono stato ignorava l'ammissione senza alcun tetto massimo. Quando questo numero di lease simultanei è attivo tramite il percorso con heap in buono stato, le ulteriori richieste effettuate mentre il sistema è occupato passano allo STESSO percorso di attesa limitata/rifiuto usato in condizioni di effettiva pressione sull'heap. `0` disabilita completamente l'aggiramento. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Numero di messaggi che classifica una richiesta di chat come pesante anche quando il corpo è al di sotto della soglia in byte. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Numero di strumenti che classifica una richiesta di chat come pesante anche quando il corpo è al di sotto della soglia in byte. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Stima conservativa dei token basata sulla dimensione delle stringhe che classifica una richiesta come pesante; si tratta di un'approssimazione del costo di ammissione, non della tokenizzazione utilizzata dal provider per la fatturazione. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (disabilitato) | `src/shared/middleware/chatBodyAdmission.ts` | Limite facoltativo e attivabile per la cronologia della chat. Disabilitato per impostazione predefinita: il numero di messaggi è una policy di distribuzione, non una proprietà universale di una richiesta, e l'imposizione di un limite in questo punto rifiuta le conversazioni con un `413` terminale prima che la pipeline di compressione possa renderle gestibili. La crescita dell'heap è limitata da `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` e dal rifiuto dovuto alla pressione sull'heap. Impostare un valore positivo nelle distribuzioni con memoria limitata che necessitano di un tetto rigido; il superamento genera quindi un `413` strutturato che richiede la compattazione. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite massimo rigido per una risposta upstream non in streaming memorizzata interamente in memoria. Oltre questo limite, il lettore upstream viene annullato e la richiesta fallisce immediatamente, anziché far crescere senza limiti una stringa fino all'esaurimento dell'heap. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Numero massimo di byte trasmessi inoltrati dalle intestazioni della risposta upstream. Quando il limite viene superato, le intestazioni con priorità inferiore (ad esempio, le intestazioni personalizzate `x-codex-*`, `x-oai-request-id`) vengono eliminate per rimanere entro i comuni limiti delle intestazioni dei reverse proxy. Impostare un valore più alto per inoltrare più metadati upstream, a scapito di una maggiore dimensione delle intestazioni di risposta. | +| `CORS_ORIGIN` | _(non impostato)_ | `src/server/cors/origins.ts` | Elenco legacy di origini CORS consentite, limitato a una singola origine. Per le nuove distribuzioni, preferire `CORS_ALLOWED_ORIGINS`. CORS serve esclusivamente per i client API browser cross-origin; le scritture autenticate della dashboard utilizzano invece richieste same-origin insieme a una protezione CSRF associata alla sessione. | +| `CORS_ALLOWED_ORIGINS` | _(non impostato)_ | `src/server/cors/origins.ts` | Elenco separato da virgole delle origini CORS consentite. Non viene inviato alcun carattere jolly, a meno che `CORS_ALLOW_ALL=true` non sia configurato esplicitamente. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Opzione di emergenza, destinata esclusivamente allo sviluppo, per restituire qualsiasi `Origin` del browser. Non abilitarla nelle distribuzioni condivise o di produzione. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias legacy. Il relativo interruttore della dashboard (un override nel DB) viene letto prima di questo valore di ambiente; `false`, `0`, `no` o `off` in uno dei due disattiva i controlli sull'host come fa `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, anche quando l'interruttore della dashboard di quel flag è salvato su off; gli altri valori lasciano la guardia ai due flag seguenti. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Disattiva i controlli sull'host della guardia degli URL in uscita, blocco dei metadati cloud incluso (tutto il blocco 169.254.0.0/16, metadata.google.internal), sui percorsi dei provider che seguono la modalità della guardia: convalida degli URL, scoperta dei modelli, URL di base dei nodi provider (inclusi i nodi rerank remoti) e test di proxy-fallback, tra gli altri percorsi in uscita adiacenti ai provider come il recupero remoto di immagini/media. Consente inoltre destinazioni webhook private, tranne gli endpoint cloud-metadata/link-local, che rimangono bloccati anche con questo flag attivo. Le richieste di chat inviate tramite `BaseExecutor.execute()` mantengono il loro controllo sui metadati cloud. I provider locali funzionano con le impostazioni predefinite: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (predefinito `true`) consente già URL locali e LAN sui percorsi di convalida, scoperta e nodi provider; il test di proxy-fallback e le destinazioni webhook private consultano solo questo flag e bloccano gli host locali/LAN mentre è disattivato. Prevale su `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Un valore salvato dall'interruttore della dashboard ha la precedenza su questa variabile. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Consente URL dei provider su indirizzi locali/privati (127.0.0.1, localhost, LAN, intervalli privati) per la convalida del provider, la scoperta dei modelli, la chat e gli altri percorsi in uscita adiacenti ai provider governati dalla modalità della guardia. **Valore predefinito: `true`** (priorità locale): la guardia blocca allora gli endpoint dei metadati cloud — tutto il blocco 169.254.0.0/16 più gli hostname dei metadati noti (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Impostare `false` per la modalità solo-pubblico, che blocca anche gli host privati e loopback. La guardia controlla l'hostname o il letterale IP così come scritto, prima di qualsiasi risoluzione DNS; in modalità solo-pubblico il wrapper safe-fetch risolve inoltre l'hostname e rifiuta qualsiasi risposta privata. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` disattiva questi controlli sui percorsi elencati nella sua riga. I provider locali integrati, come LM Studio, Ollama, vLLM, Llamafile e Triton, saltano tutti i controlli sull'host per la convalida delle chiavi e la chat. Un valore salvato dall'interruttore della dashboard ha la precedenza su questa variabile. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Consente alle route `/v1/audio/*` (trascrizioni, sintesi vocale, traduzioni) di utilizzare un nodo provider compatibile con OpenAI ospitato al di fuori di localhost. Disabilitato per impostazione predefinita — l'instradamento dell'audio verso un host remoto modifica l'identità di uscita e deve essere una decisione esplicita dell'operatore. I nodi loopback/privati (localhost, 127.0.0.1, 172.16-31.x) sono sempre consentiti e non vengono interessati. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Consente a `POST /v1/rerank` (e al passaggio di reranking loopback del motore di memoria) di utilizzare un nodo provider compatibile con OpenAI ospitato al di fuori di localhost — un dispositivo nella LAN o un peer Tailscale che esegue TEI, Infinity, vLLM, ecc. Disabilitato per impostazione predefinita — l'instradamento verso un host remoto modifica l'identità di uscita e deve essere una decisione esplicita dell'operatore. I nodi loopback (localhost, 127.0.0.1, 172.16-31.x) sono sempre consentiti e non vengono interessati. Anche l'URL di base di un nodo remoto deve rispettare la policy degli URL in uscita del provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Quando OIDC è abilitato, disabilita l'accesso tramite password in modo che gli utenti possano autenticarsi esclusivamente tramite Single Sign-On OIDC. È accettato anche l'alias semplice `OIDC_DISABLE_PASSWORD_LOGIN`; il flag di funzionalità della dashboard con la stessa chiave ha la precedenza. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias semplice di `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Checklist di hardening diff --git a/docs/i18n/ja/CONTRIBUTING.md b/docs/i18n/ja/CONTRIBUTING.md index f3f0d3f26a48..5bdc8aec9745 100644 --- a/docs/i18n/ja/CONTRIBUTING.md +++ b/docs/i18n/ja/CONTRIBUTING.md @@ -386,22 +386,22 @@ OAuth 認証情報を `src/lib/oauth/constants/oauth.ts` に、サービスを ` --- -## プルリクエストチェックリスト +## Pull Request チェックリスト - [ ] テストが成功する(`npm test`) - [ ] Lint が成功する(`npm run lint`) - [ ] ビルドが成功する(`npm run build`) - [ ] 新しい公開関数およびインターフェースに TypeScript の型が追加されている - [ ] ハードコードされたシークレットやフォールバック値がない -- [ ] 公開アップストリーム認証情報は、リテラルではなく `resolvePublicCred()` を介して埋め込まれている([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) を参照) -- [ ] エラーレスポンスは `buildErrorBody()` / `sanitizeErrorMessage()` を経由している — レスポンス本文に未加工のスタックトレースを含めない([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) を参照) -- [ ] シェルコマンド(`exec` / `spawn`)では、実行時の値を文字列補間ではなく `env` 経由で渡している +- [ ] 公開アップストリーム認証情報はリテラルではなく `resolvePublicCred()` を使用して埋め込まれている([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) を参照) +- [ ] エラーレスポンスは `buildErrorBody()` / `sanitizeErrorMessage()` を経由している — レスポンス本文に未加工のスタックトレースが含まれていない([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) を参照) +- [ ] シェルコマンド(`exec` / `spawn`)には、文字列補間ではなく `env` を介してランタイム値を渡している - [ ] すべての入力が Zod スキーマで検証されている -- [ ] ユーザー向けの変更について、`changelog.d/{features|fixes|maintenance}/-.md` 以下に変更履歴の**フラグメント**が追加されている([`changelog.d/README.md`](./changelog.d/README.md) を参照)— `CHANGELOG.md` を直接編集しては**ならない**。フラグメントはリリース時に集約され、PR 間で競合することはない +- [ ] ユーザー向けの変更について、`changelog.d/{features|fixes|maintenance}/-.md` 配下に Changelog の**フラグメント**が追加されている([`changelog.d/README.md`](./changelog.d/README.md) を参照)— `CHANGELOG.md` を直接編集してはならない。フラグメントはリリース時に集約され、PR 間で競合することはない - [ ] ドキュメントが更新されている(該当する場合) -- [ ] 新しい CodeQL / Secret-Scanning アラートが発生していない。または、各アラートが関連する `docs/security/` ドキュメントを参照した技術的根拠とともに却下されている -- [ ] 子プロセスを起動するルート(`/api/mcp/`、`/api/cli-tools/runtime/`)が `src/server/authz/routeGuard.ts` で `isLocalOnlyPath()` として分類されている — [ハードルール #15](docs/security/ROUTE_GUARD_TIERS.md) を参照 -- [ ] コミットメッセージに `Co-Authored-By` トレーラーがない — コミットはリポジトリ所有者の Git ID のみで作成されたものとして表示されなければならない(ハードルール #16) +- [ ] 新しい CodeQL / Secret-Scanning アラートが発生していない、または各アラートが関連する `docs/security/` ドキュメントを参照した技術的根拠とともに却下されている +- [ ] 子プロセスを起動するルート(`/api/mcp/`、`/api/cli-tools/runtime/`)が `src/server/authz/routeGuard.ts` 内で `isLocalOnlyPath()` として分類されている — [厳守ルール #15](docs/security/ROUTE_GUARD_TIERS.md) を参照 +- [ ] コミットメッセージに AI/bot の `Co-authored-by` トレーラーが含まれていない(厳守ルール #16)— 再利用した作業を行った人間の共同作業者は、標準の `Co-authored-by: Name ` トレーラーでクレジットされている --- diff --git a/docs/i18n/ja/docs/architecture/QUALITY_GATES.md b/docs/i18n/ja/docs/architecture/QUALITY_GATES.md index 685d2fde012a..9657b6c55cc7 100644 --- a/docs/i18n/ja/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ja/docs/architecture/QUALITY_GATES.md @@ -19,119 +19,123 @@ ### 候補の受け入れ -CI および Quality Gates ワークフローは、それぞれ安定した判定結果 `Gate / CI` と +CI ワークフローと Quality Gates ワークフローは、それぞれ安定した判定結果 `Gate / CI` および `Gate / Quality` を出力します。バージョン管理された受け入れポリシーでは、すべての上流ジョブを -必須またはアドバイザリーとして列挙します。適用対象の必須ジョブは成功しなければなりません。 -欠落、キャンセル、スキップ、保留中、不明の結果では PASS を確定できません。有効な -ドキュメントのみ、またはカタログのみという分類により、コードレーンを適用対象外にできますが、 -ドラフト PR は受け入れ可能な候補ではありません。`hotfix` ラベルによって証跡が免除されることはありません。 - -両方のワークフローは、PR、main/release ブランチへのプッシュ、手動ディスパッチ、 -およびマージグループイベントを対象とします。プッシュ、ディスパッチ、マージグループでは、 -すべての選択項目が実行されます。フォークおよびマージグループでは、通常であればセルフホステッド -ランナーを選択するジョブにもホステッドランナーを使用します。ロールアウト前に、十分な -ホステッド容量があることを確認する必要があります。 - -各 JSON レシートは、チェックアウトされた SHA、ワークフロー実行、および試行を識別します。 -CLI は、チェックアウト SHA とイベント SHA の不一致を拒否します。ワークフローテストでは、 -ポリシーのメンバーシップを判定ジョブの `needs` リストに紐付けるため、新規または削除された -レーンが気付かれないまま消えることはありません。レシートが対象とするのは自身のワークフローであり、 -公開、デプロイ、または既存のアドバイザリースキャナーの内部処理ではありません。ブランチルールで -両方のチェック名を有効化することは、別途必要な管理上の変更です。これらのジョブを追加するだけでは、 -ブランチは保護されません。 +必須またはアドバイザリとして列挙します。適用対象となる必須ジョブは成功しなければなりません。 +結果が欠落、キャンセル、スキップ、保留中、不明の場合、PASS を確定できません。有効な +docs-only または catalog-only 分類によりコードレーンが適用対象外になることはありますが、 +ドラフト PR は受け入れ対象の候補ではありません。`hotfix` ラベルによってエビデンス要件が +免除されることもありません。 + +両方のワークフローは、PR、main/release ブランチへの push、手動 dispatch、および +merge-group イベントを対象とします。push、dispatch、merge-group では完全な選択セットが +実行されます。fork と merge group では、本来 self-hosted runner を選択するジョブに +hosted runner を使用します。ロールアウト前に、十分な hosted capacity があることを +確認する必要があります。 + +各 JSON レシートは、checkout された SHA、workflow run、および attempt を識別します。 +CLI は checkout/event の SHA 不一致を拒否します。ワークフローテストでは、ポリシーの +メンバーシップを判定ジョブの `needs` リストに関連付けるため、新規または削除されたレーンが +暗黙のうちに消えることはありません。レシートが対象とするのは、それ自身のワークフローであり、 +publication、deployment、または既存のアドバイザリスキャナーの内部処理ではありません。 +ブランチルールで両方のチェック名を有効にすることは、別途必要な管理上の変更です。これらの +ジョブを追加するだけでは、ブランチは保護されません。 ### 静的スキャン一覧 バージョン管理された npm エイリアス一覧と静的スキャンのメンバーシップは、 `config/quality/gate-manifest.json` にあります。`package.json` に対してスクリプト名と -正確なコマンドを検証するには、`npm run check:gate-manifest` を実行してください。追加、削除、 -およびコマンドの乖離があると、ローカルフックと CI の変更分類ジョブの両方が失敗します。 +完全一致するコマンドを検証するには、`npm run check:gate-manifest` を実行します。 +追加、削除、コマンドの差異があると、ローカルフックと CI の変更分類ジョブの両方が失敗します。 エイリアスは、ワークフロージョブ、マトリックスインスタンス、テストケースのいずれでもありません。 -これらの件数を相互に置き換え可能なものとして提示してはなりません。 - -選択されたエイリアスを実行せずに確認するには、`npm run quality:scan -- --list` または -`npm run quality:scan:fast -- --list` を使用してください。ランナーは npm エントリポイントを -呼び出すため、設定されている場合の Bun を含め、そのランタイムが維持されます。マニフェストでは、 -これらのプロファイル外のエイリアスを個別実行として記録し、読み取り専用のスキャンプロファイルでは -メンテナンスコマンドを禁止しています。 - -これらのプロファイルが対象とするのは静的スキャンのみです。プロダクトテスト、カバレッジ、 -パッケージング、外部チェック、または候補のリリース受け入れ全体を保証するものではありません。 -ワークフローの受け入れでは、関連付けられた `config/quality/admission-policy.json` と -`scripts/quality/admission-verdict.mjs` を使用します。リリースオブザーバーのプロファイルは -引き続き別個のものです。適用対象のチェックとレシートは、それぞれ個別に確認してください。 -以下の文章による一覧は参考情報であり、ゲートが実際に実行されたことの証明ではありません。 - -スクリプトは `scripts/check/`(ポリシーゲート)と `scripts/quality/`(ラチェットエンジン)以下にあります。 +これらの件数を相互に置き換え可能なものとして示してはなりません。 + +選択されたエイリアスを実行せずに確認するには、 +`npm run quality:scan -- --list` または `npm run quality:scan:fast -- --list` を使用します。 +runner は npm entrypoint を呼び出すため、設定されている場合の Bun を含め、そのランタイムが +維持されます。manifest では、これらのプロファイル外のエイリアスを個別に呼び出されるものとして +記録します。また、読み取り専用のスキャンプロファイルでは、メンテナンスコマンドは禁止されます。 + +これらのプロファイルが対象とするのは静的スキャンのみです。product tests、coverage、 +packaging、external checks、または候補の完全な release acceptance を認定するものでは +ありません。ワークフローの受け入れでは、リンクされた +`config/quality/admission-policy.json` と +`scripts/quality/admission-verdict.mjs` を使用します。release-observer プロファイルは +引き続き分離されています。適用対象のチェックとレシートは個別に確認してください。以下の +文章による一覧は参照情報であり、ゲートが実際に実行されたことの証明ではありません。 + +スクリプトは `scripts/check/`(ポリシーゲート)と +`scripts/quality/`(ratchet engine)にあります。 CI の信頼できる唯一の情報源は `.github/workflows/ci.yml` です。 -### リリース PR の高速パス(`quality.yml`) - -`.github/workflows/quality.yml` は、main/release PR、保護対象ブランチへのプッシュ、 -ディスパッチ、マージグループにおいて CI を補完します。PR では、パスでフィルタリングされた -高速チェックを使用します。恒久的に無効化されていた重複ビルドは削除されました。 -実際のビルド、パッケージ、起動チェックは引き続き CI にあります。 - -| ジョブ | スコープ | ブロッキング | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `Docs Gates (fast-path)` | ドキュメント/コード PR。API ドキュメント参照とドキュメント全体 | はい | -| `Fast Quality Gates` | コード PR。静的チェック、型チェック、ダッシュボードの型チェック、影響を受けるユニットテスト | はい | -| `Forgotten sibling tests` | コード PR。変更されたモジュールから静的コンシューマーと候補となる兄弟テストを追跡。バレルおよび動的インポートのパスは、参照された許可リストの例外とともにアドバイザリー診断として報告されます | **アドバイザリー** | -| `Vitest (fast-path)` | コード PR。高速 Vitest スイート | はい | -| `Unit Tests fast-path` | コード PR。4 シャードのユニットスイート | はい | -| `No new ESLint warnings` | コード PR。抑制を考慮した lint ガード | はい(フォークを含む) | -| `Merge integrity (changelog + generated skills)` | ドラフトではない PR。変更履歴と生成されたスキルの同期 | はい(フォークを含む) | - -#### 取りこぼされた兄弟テストのレポート - -`npm run check:forgotten-sibling-tests` は、テスト影響マップの基盤となるインポートリゾルバーを再利用します。 -変更された各本番モジュールについて、候補テストがプルリクエストの差分に含まれていない場合に、 -決定論的な `changed module/symbol -> static consumer -> candidate sibling test` チェーンを報告します。 -Markdown サマリーと JSON 結果は、ブロッキングとしてロールアウトする前の調整用に、 -`forgotten-sibling-tests` ワークフローアーティファクトとして保持されます。 - -バレル再エクスポートと動的インポートは、解決診断のみを目的としており、 -ブロッキング対象の検出結果を生成することはありません。レビュー済みの例外は -`config/quality/forgotten-sibling-allowlist.json` で管理されます。各エントリには、コンシューマーと候補 -テストを明記し、具体的な理由を示したうえで、GitHub issue または pull request へのリンクを含める必要があります。不正な形式のエントリは -安全側に倒して拒否されます。例外によって、削除された候補テストや、`.skip`/`.todo` を追加する差分を抑制することはできません。 -アサーションの弱体化やその他のマスキングについては、独立してブロッキングする +### Release PR fast-path(`quality.yml`) + +`.github/workflows/quality.yml` は、main/release PR、保護されたブランチへの +push、dispatch、merge group において CI を補完します。PR では、パスでフィルタリングされた +高速チェックを使用します。恒久的に無効化されていた重複 build は削除されました。 +実際の build/package/boot チェックは引き続き CI に残ります。 + +| ジョブ | スコープ | ブロッキング | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `Docs Gates (fast-path)` | Docs/code PR、API docs refs、および docs-all | はい | +| `Fast Quality Gates` | Code PR、静的チェック、typecheck、dashboard typecheck、影響を受ける unit tests | はい | +| `Forgotten sibling tests` | Code PR、変更されたモジュールを static consumers と candidate sibling tests まで追跡。barrel と dynamic-import のパスは、参照された allowlist の例外とともにアドバイザリ診断として報告されます | **アドバイザリ** | +| `Vitest (fast-path)` | Code PR、高速 vitest suite | はい | +| `Unit Tests fast-path` | Code PR、4-shard unit suite | はい | +| `No new ESLint warnings` | Code PR、suppressions-aware lint guard | はい(fork を含む) | +| `Merge integrity (changelog + generated skills)` | ドラフトではない PR、changelog と generated skill の同期 | はい(fork を含む) | + +#### Forgotten sibling tests レポート + +`npm run check:forgotten-sibling-tests` は、test-impact map の基盤となる import resolver を +再利用します。変更された各 production module について、候補テストが pull-request diff に +含まれていない場合、決定論的な +`changed module/symbol -> static consumer -> candidate sibling test` チェーンを報告します。 +Markdown summary と JSON result は、ブロッキング方式でのロールアウト前にキャリブレーションを +行うため、`forgotten-sibling-tests` workflow artifact として保持されます。 + +バレル再エクスポートと動的インポートは、解決に関する診断のみを目的としており、 +ブロッキング判定を生成することはありません。レビュー済みの例外は +`config/quality/forgotten-sibling-allowlist.json` にあります。各エントリでは、コンシューマーと候補 +テストを指定し、具体的な根拠を示し、GitHub issue または pull request へのリンクを記載する必要があります。不正な形式のエントリは +安全側に倒して失敗します。例外によって、削除された候補テストや、`.skip`/`.todo` を追加する差分を抑制することはできません。 +アサーションの弱体化やその他のマスキングについては、独立してブロッキングを行う `check:test-masking` ゲートが引き続き担当します。 ### ジョブ: `lint` `main` に対するすべての PR で実行されます。失敗した場合はマージをブロックします。 -| スクリプト (`npm run ...`) | 検証内容 | ブロッキング | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -| `check:node-runtime` | Node.js のバージョンがサポート対象範囲内であること | はい | -| `check:cycles` | 循環インポート — すべての `src/` + `open-sse/` モジュール | はい | -| `check:route-validation:t06` | すべてのルートに Zod スキーマが存在すること(Tier 6 ポリシー) | はい | -| `check:any-budget:t11` | `@ts-expect-error // any` の数が上限を超えていないこと(Tier 11 ゲート) | はい | -| `check:provider-consistency` | `providers.ts` 内のすべてのプロバイダーに、`providerRegistry.ts` 内の対応するエントリが存在する(逆方向も同様。ただし許可リスト内に限る) | はい | -| `check:model-lifecycle` | 手動で保守されている3つのルーティングテーブルが、チェックイン済みのライフサイクルスナップショット(#11503)と整合していることを確認する:`FITNESS_TABLE`(`taskFitness.ts`)では、`REGISTRY` がルーティング可能な廃止済み id にスコアを付けないこと、すべての `BUILT_IN_ALIASES` のターゲットが `REGISTRY` に存在し、廃止済み id のスナップショットには存在しないこと、`REGISTRY` に残っているすべての廃止済み id が転送されるか `allowedRetiredInCatalog` に記載されていること、さらに `DEFAULT_DEGRADATION_MAP` のソースまたはターゲットがそのスナップショット内で廃止済みになっていないこと。これは、モデルが現在稼働中のアップストリームによって提供されていることを証明するものではない。オフライン — `config/quality/model-lifecycle.json` と比較する。このファイルは `npm run quality:refresh-model-lifecycle` を使用して手動で更新する(ネットワークを使用し、CI には組み込まれていない)。`allowedRetiredInCatalog` は段階的削減のためのラチェットであり、エントリを追加するのは追跡 issue がある場合に限る。 | はい | -| `check:fetch-targets` | クライアント側の `src/` にあるすべての `fetch("/api/...")` が、実在する `route.ts` に解決される | はい | -| `check:deps` | リポジトリ内のすべての `package.json` に含まれる、`npm install` でインストール可能な依存関係がすべて `dependency-allowlist.json` に登録されていることを確認し、新たなバージョン未固定パッケージやスロップスクワッティングされたパッケージを検出する | はい | -| `audit:deps` | `npm audit`(ルート + electron)— high/critical のアドバイザリがないこと(osv の `check:vuln-ratchet` と重複。Rationalization Backlog を参照) | はい | -| `check:lockfile` | `package-lock.json` の完全性 — https レジストリ、完全性ハッシュ、ホストのオーバーライドなし | はい | -| `check:licenses` | 本番依存関係の SPDX ライセンス許可リスト | はい | -| `check:tracked-artifacts` | ビルド成果物およびコミットされた `node_modules` シンボリックリンクがないこと(husky の pre-commit でも実行。pre-push は意図的に軽量化 — #6716) | はい | -| `check:ai-attribution` | PR のコミット、タイトル、本文に、AI/ボットによる `Co-Authored-By` トレーラーまたは AI 生成フッターがないこと — 厳格ルール #16(PR→`release/**` 向けの `quality.yml` 高速ゲートループ内。イベントペイロードを読み取り、PR 以外では何も行わない。また、PR→`main` 向けの `ci.yml` lint にある PR 専用ステップ、および husky の `commit-msg` フックでも実行。人間の共同作成者は許可。#14436) | -| `check:vitest-exclusions` | すべての Vitest 除外に追跡用 Issue が指定され、`config/quality/vitest-exclusions.json` に記載されていること(#13204) | はい | -| `check:file-size` | ソースファイルが拡張子ごとの上限を超えていないこと(ラチェット方式:サイズの大きい固定対象ファイルは `frozen` リストに記載) | はい | -| `check:error-helper` | executor/handler のエラーレスポンスで `buildErrorBody()` / `sanitizeErrorMessage()` を使用すること(厳格ルール #12) | はい | -| `check:migration-numbering` | Migration SQL ファイルは欠番や重複がなく、連番になっている | はい | -| `check:public-creds` | `publicCreds.ts` の外部に、OAuth の `client_id`/`client_secret` または Firebase Web キーのリテラルが存在しない(ハードルール #11) | はい | -| `check:db-rules` | `src/lib/db/` モジュールの外部に生の SQL が存在せず、`localDb.ts` からのバレルインポートもない(ハードルール #2/#5) | はい | -| `check:known-symbols` | ディスパッチテーブルに登録されたプロバイダーエグゼキューター、ルーティング戦略、トランスレーターがディスク上のファイルと一致しており、孤立したシンボルや未宣言のシンボルがない | はい | -| `check:route-guard-membership` | 子プロセスを生成するすべてのルートが `isLocalOnlyPath()` によって分類されている(ハードルール #15/#17) | はい | -| `check:test-discovery` | リポジトリ内のすべての `*.test.ts` / `*.spec.ts` ファイルが、少なくとも 1 つのテストランナーによって収集される(ラチェット:`test-discovery-baseline.json` 内の孤立ファイルリストは縮小のみ可能) | はい | -| `check:agent-skills-sync` | 生成された agent-skills アーティファクトがソースカタログと一致している(差分なし) | -| `check:provider-asset-provenance` | プロバイダーのロゴ/アセットに記録済みの出所エントリが付与されている | -| `lint:json` | JSON 設定ファイルが解析可能で、リポジトリの lint ルールを満たしている | -| `typecheck:core` | エラーなしで TypeScript をコンパイルする(警告のみ) | はい | -| `typecheck:noimplicit:core` | 厳格な `noImplicitAny` — 将来を見据えたチェック。既存の多数の呼び出し箇所には、依然として注釈が必要 | **勧告**(`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` を対象とする `tsc`(#7033)— `typecheck:core` で厳選された27ファイルの許可リストにはダッシュボードの TSX が一切含まれておらず、`next build` でも型チェックされません(`next.config.mjs` で `ignoreBuildErrors: true` が設定されているため)。そのため、そこで発生した未解決識別子のリグレッション(#6625/#6909)は CI で検出できませんでした。ファイルごと/TS コードごとの件数を固定したベースライン(`config/quality/dashboard-typecheck-baseline.json`、`check:known-symbols` と同じ陳腐化チェックパターン)と比較し、ベースラインの件数を超える新規エラーのみがゲートを失敗させます。既存エラーを修正した場合は、`--update` を使用してベースラインを段階的に引き下げます。 | はい | +| スクリプト (`npm run ...`) | 検証内容 | ブロッキング | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Node.js のバージョンがサポート対象範囲内であること | はい | +| `check:cycles` | `src/` + `open-sse/` 全体の循環インポート(AST ベース、tsconfig の `paths` を解決)。単独実行時は勧告のみで、循環を一覧表示します。`check:cycles:ratchet`(CI で実行されるもの)は、循環数が `quality-baseline.json` の `metrics.cycles` 上限を超えた場合にブロックします。現在は 14、`direction: down` であるため、減少のみが許可されます(#15159 G-01/G-02) | はい(ラチェット) | +| `check:route-validation:t06` | すべてのルートに Zod スキーマが存在すること(Tier 6 ポリシー) | はい | +| `check:any-budget:t11` | `@ts-expect-error // any` の件数が予算を超えていないこと(Tier 11 catraca) | はい | +| `check:provider-consistency` | `providers.ts` 内のすべてのプロバイダーに、`providerRegistry.ts` 内の対応するエントリが存在する(その逆も同様。ただし許可リスト内に限る) | はい | +| `check:model-lifecycle` | 手動で管理される3つのルーティングテーブルが、チェックイン済みのライフサイクルスナップショット(#11503)と整合していることを確認する:`FITNESS_TABLE`(`taskFitness.ts`)では、`REGISTRY` がルーティング可能な廃止済み id にスコアを付けないこと、すべての `BUILT_IN_ALIASES` のターゲットが `REGISTRY` に存在し、かつ廃止済み id のスナップショットに存在しないこと、`REGISTRY` に残っているすべての廃止済み id が転送されるか `allowedRetiredInCatalog` に記載されていること、さらに `DEFAULT_DEGRADATION_MAP` のソースまたはターゲットがそのスナップショットで廃止済みになっていないこと。これは、モデルが現在稼働中のアップストリームから提供されていることを証明するものではない。オフライン — `config/quality/model-lifecycle.json` と比較する。このファイルは `npm run quality:refresh-model-lifecycle`(ネットワークを使用。CI には組み込まれていない)で手動更新する。`allowedRetiredInCatalog` は段階的削減のためのラチェットであり、エントリは追跡用 issue がある場合にのみ追加する。 | はい | +| `check:fetch-targets` | クライアント側の `src/` 内にあるすべての `fetch("/api/...")` が、実在する `route.ts` に解決される | はい | +| `check:deps` | リポジトリ内のすべての `package.json` に含まれる、`npm install` でインストール可能な依存関係が `dependency-allowlist.json` に登録されていること。新規のバージョン未固定パッケージやスロップスクワッティングされたパッケージはフラグ付けされる | はい | +| `audit:deps` | `npm audit`(ルート + electron)— high/critical のアドバイザリがないこと(osv の `check:vuln-ratchet` と重複。Rationalization Backlog を参照) | はい | +| `check:lockfile` | `package-lock.json` の整合性 — https レジストリ、整合性ハッシュ、ホストのオーバーライドなし | はい | +| `check:licenses` | 本番環境の依存関係に対する SPDX ライセンス許可リスト | はい | +| `check:tracked-artifacts` | ビルド成果物およびコミットされた `node_modules` シンボリックリンクが存在しないこと(husky の pre-commit でも実行。pre-push は意図的に軽量化 — #6716) | はい | +| `check:ai-attribution` | PR のコミット、タイトル、本文に、AI/ボットによる `Co-Authored-By` トレーラーまたは AI 生成フッターが存在しないこと — 厳格ルール #16(PR→`release/**` 向けの `quality.yml` 高速ゲートループ内でイベントペイロードを読み取り、PR 以外では何も実行しない。また、PR→`main` 向けの `ci.yml` lint にある PR 専用ステップ、および husky の `commit-msg` フックでも実行。人間の共同作成者は許可。#14436) | +| `check:vitest-exclusions` | すべての Vitest 除外項目に追跡用 issue が記載され、`config/quality/vitest-exclusions.json` に含まれていること(#13204) | はい | +| `check:file-size` | ソースファイルが拡張子ごとの上限を超えていないこと(ラチェット方式:大容量ファイルは `frozen` リストで固定) | はい | +| `check:error-helper` | executor/handler のエラーレスポンスで `buildErrorBody()` / `sanitizeErrorMessage()` を使用していること(厳格ルール #12) | はい | +| `check:migration-numbering` | Migration SQL ファイルには欠番や重複がなく、連番になっている | はい | +| `check:public-creds` | `publicCreds.ts` 以外に、OAuth の `client_id`/`client_secret` または Firebase Web キーのリテラルが存在しない(厳格なルール #11) | はい | +| `check:db-rules` | `src/lib/db/` モジュール以外に生の SQL が存在せず、`localDb.ts` からのバレルインポートも存在しない(厳格なルール #2/#5) | はい | +| `check:known-symbols` | ディスパッチテーブルに登録されているプロバイダーエグゼキューター、ルーティング戦略、トランスレーターがディスク上のファイルと一致しており、孤立したシンボルや未宣言のシンボルが存在しない | はい | +| `check:route-guard-membership` | 子プロセスを起動するすべてのルートが `isLocalOnlyPath()` によって分類されている(厳格なルール #15/#17) | はい | +| `check:test-discovery` | リポジトリ内のすべての `*.test.ts` / `*.spec.ts` ファイルが、少なくとも 1 つのテストランナーによって収集される(ラチェット方式:`test-discovery-baseline.json` 内の孤立ファイル一覧は減少のみ許可) | はい | +| `check:agent-skills-sync` | 生成された agent-skills アーティファクトがソースカタログと一致している(ドリフトなし) | +| `check:provider-asset-provenance` | プロバイダーのロゴ/アセットに由来情報の記録が付与されている | +| `lint:json` | JSON 設定ファイルが正常に解析され、リポジトリの lint ルールを満たしている | +| `typecheck:core` | エラーなしで TypeScript をコンパイルできる(勧告的な警告のみ) | はい | +| `typecheck:noimplicit:core` | 厳格な `noImplicitAny` — 将来を見据えたチェック。既存の多くの呼び出し箇所には、まだ型注釈が必要 | **勧告的**(`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` を対象とする `tsc`(#7033)— `typecheck:core` で厳選された27ファイルの許可リストにはダッシュボードの TSX が一切含まれておらず、`next build` でも型チェックされません(`next.config.mjs` で `ignoreBuildErrors: true` が設定されているため)。そのため、そこで発生した孤立識別子のリグレッション(#6625/#6909)は CI で検出できませんでした。ファイルごと/TS コードごとの件数を固定したベースライン(`config/quality/dashboard-typecheck-baseline.json`、`check:known-symbols` と同じ古さを強制するパターン)との差分を比較します。ベースライン化された件数を超える新しいエラーのみがゲートを失敗させます。既存のエラーを修正した場合は、`--update` でベースラインを段階的に引き下げます。 | はい | ### ジョブ:`quality-gate` @@ -139,147 +143,136 @@ Markdown サマリーと JSON 結果は、ブロッキングとしてロール | スクリプト | 検証内容 | ブロッキング | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | `quality-metrics.json` を出力(ESLint の警告数、統合されたシャードレポートからのカバレッジ) | はい(ラチェットの前段) | +| `quality:collect` | `quality-metrics.json` を出力する(ESLint の警告数、マージされたシャードレポートからのカバレッジ) | はい(ラチェットの前段) | | `quality:ratchet` | `quality-baseline.json` 内の各メトリクスが悪化していないこと(ESLint の警告数 ≤ ベースライン、カバレッジ ≥ ベースライン) | はい | -| `check:duplication` | コード重複(jscpd@4)が `quality-baseline.json` 内のベースラインを超えていないこと | はい | +| `check:duplication` | コードの重複(jscpd@4)が `quality-baseline.json` のベースラインを超えていないこと | はい | | `check:complexity` | ファイル単位の循環的複雑度が上限を超えていないこと(コア ESLint の `complexity` + `max-lines-per-function`) | はい | -| `check:cognitive-complexity` | 認知的複雑度のラチェット(`eslint-plugin-sonarjs`)— 個別の ESLint パス。CI では両方を統合し、単一の `check:complexity-ratchets` ステップとして実行 | はい | -| `check:dead-code` | 未使用のエクスポート/ファイルのラチェット(knip)がベースラインと比較して悪化していないこと | はい | +| `check:cognitive-complexity` | 認知的複雑度のラチェット(`eslint-plugin-sonarjs`)— 個別の ESLint パス。CI では両方を統合し、単一の `check:complexity-ratchets` ステップとして実行する | はい | +| `check:dead-code` | 未使用のエクスポート/ファイルのラチェット(knip)がベースラインから悪化していないこと | はい | | `check:compression-budget` | 圧縮ベンチマークの予算 — エンジンごとのトークン削減率の下限が悪化してはならないこと | はい | -| `check:type-coverage` | 型付け率のラチェット(`type-coverage`)が悪化していないこと。`typecheck:noimplicit:core` をほぼ包含 | はい | +| `check:type-coverage` | 型付け率のラチェット(`type-coverage`)が悪化していないこと。`typecheck:noimplicit:core` をほぼ包含する | はい | | `check:codeql-ratchet` | 未解決の CodeQL アラート数が悪化していないこと(`gh api` 経由で読み取り。トークンがない場合は正常にスキップ)— 更新頻度と手動トリガーについては、以下の「CodeQL ラチェット」を参照 | はい | ### ジョブ: `quality-extended` -ジョブ全体は参考情報扱いです(`continue-on-error: true`)。npm ベースのラチェットは -実際に実行されます。外部スキャナーは `gh release download` を介してインストールされ、バイナリが -まだ存在しない場合は自動的にスキップします(終了コード 0)。 +ジョブ全体が参考情報扱いです(`continue-on-error: true`)。npm ベースのラチェットは +実際に実行されます。外部スキャナーは `gh release download` 経由でインストールされ、バイナリが +依然として存在しない場合は自動的にスキップします(終了コード 0)。 -| スクリプト | 検証内容 | ブロッキング | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | -| `check:circular-deps` | 循環依存がないこと(dpdm) | **参考情報** | -| `check:bundle-size` | バンドルサイズが上限を超えていないこと | **参考情報** | -| `check:secrets` | シークレットスキャン(gitleaks)— バイナリが存在しない場合はスキップ | **参考情報** | -| `check:vuln-ratchet` | 依存関係の脆弱性(osv-scanner)が悪化していないこと — バイナリが存在しない場合はスキップ | **参考情報** | -| `check:workflows` | ワークフローのリント(actionlint + zizmor)。スキャナーの欠落/破損、無効なレポート、またはラチェットのベースライン欠落は INCOMPLETE として失敗。妥当な検出結果には、選択した厳格/参考情報/ラチェットポリシーを適用 | 実行必須。CI では zizmor ラチェットがブロッキング | -| `check:openapi-breaking` | ベースブランチと比較した公開 API コントラクト(`openapi.yaml`)の破壊的変更(oasdiff)— `openapiBreaking=N` を出力。oasdiff が存在しないか、ベース仕様を解決できない場合はスキップ | **参考情報** | +| スクリプト | 検証内容 | ブロッキング | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| `check:circular-deps` | 循環依存がないこと(dpdm) | **参考情報** | +| `check:bundle-size` | バンドルサイズが上限を超えていないこと | **参考情報** | +| `check:secrets` | シークレットスキャン(gitleaks)— バイナリが存在しない場合はスキップ | **参考情報** | +| `check:vuln-ratchet` | 依存関係の脆弱性(osv-scanner)が悪化していないこと — バイナリが存在しない場合はスキップ | **参考情報** | +| `check:workflows` | ワークフローの lint(actionlint + zizmor)。スキャナーの欠落/破損、無効なレポート、またはラチェットのベースライン欠落は INCOMPLETE として失敗する。有効な検出結果には、選択された厳格/参考情報/ラチェットポリシーが適用される | 実行必須。CI では zizmor ラチェットがブロッキング | +| `check:openapi-breaking` | ベースブランチと比較した公開 API コントラクト(`openapi.yaml`)の破壊的変更(oasdiff)— `openapiBreaking=N` を出力する。oasdiff が存在しないか、ベース仕様を解決できない場合はスキップ | **参考情報** | ### ジョブ: `docs-sync-strict` `main` へのすべての PR で実行されます。失敗した場合はマージをブロックします。 -| スクリプト | 検証内容 | ブロッキング | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| `check:docs-all` | 以下の6つのサブゲートを順番に実行するメタゲート | はい | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt のバージョン整合性 | はい | -| ↳ `check:docs-counts` | 本文中の件数(プロバイダー数、マイグレーション数など)が、実際の件数に対するラチェット範囲内に収まっていること | はい | -| ↳ `check:env-doc-sync` | `.env.example` 内のすべての環境変数がドキュメントの表に記載され、その逆も成立すること | はい | -| ↳ `check:deprecated-versions` | ドキュメント内に非推奨のバージョン文字列が存在しないこと | はい | -| ↳ `check:doc-links` | ドキュメント内の内部 Markdown リンクが実在するファイルに解決されること(`[text]`/`(path)` 形式) | はい | -| ↳ `check:fabricated-docs` | ドキュメントに記載されたルート、環境変数、CLI コマンド、フック名、ファイルパスがコードベース内に存在すること。`--strict` ではハードゲート、フラグなしではソフトフェイル。 | はい(CI では `--strict` を使用) | -| `check:cli-i18n` | CLI コマンド文字列がすべての i18n ロケールファイルに存在すること | はい | -| `check:openapi-coverage` | OpenAPI 仕様が、実在するルート数に対してラチェットされた最低基準以上をカバーしていること | はい | -| `check:openapi-security-tiers` | `openapi.yaml` のセキュリティ階層アノテーションが `routeGuard.ts` の分類と一致すること | **参考情報** | -| `check:openapi-routes` | `openapi.yaml` 内のすべてのパスが実在する `route.ts` に解決されること(ハルシネーション防止) | はい | -| `check:docs-symbols` | `docs/**/*.md` 内のすべての `/api/...` 参照が実在する `route.ts` に解決されること(ハルシネーション防止) | はい | -| `i18n translation drift` | i18n ロケールファイル内の未翻訳キー — 警告のみ | **参考情報** | - -### ジョブ:`i18n-ui-coverage` - -| スクリプト | 検証内容 | ブロッキング | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `check-ui-keys-coverage` (inline) | UI i18n キーのカバレッジが 65% 以上であること | はい | -| `check-ui-value-drift` (inline) | 書き換えられた英語の**値**に対して、古い翻訳が残っていないこと | はい | -| `check-new-key-coverage` (inline) | **新しい**英語キーがすべてのロケールで翻訳されていること — `__MISSING__:` マーカーは拒否される | はい | -| `check-translation-ratio` | ロケールごとの実翻訳率(許可リスト外で英語と同一/プレースホルダー/未翻訳の項目)が `config/quality/i18n-translation-baseline.json` + 許容幅を超えないこと | **参考情報** | - -`fetch-depth: 0` が必要です — 値ドリフトゲートは、マージベースに対して `en.json` の差分を取得します。 +| スクリプト | 検証内容 | ブロッキング | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | +| `check:docs-all` | 以下の6つのサブゲートを順番に実行するメタゲート | はい | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt のバージョン整合性 | はい | +| ↳ `check:docs-counts` | 本文中の件数(プロバイダー数、マイグレーション数など)が、実際の件数に対するラチェット範囲内にあること | はい | +| ↳ `check:env-doc-sync` | `.env.example` 内のすべての環境変数がドキュメントの表に記載されており、その逆も満たされていること | はい | +| ↳ `check:deprecated-versions` | ドキュメントに非推奨のバージョン文字列が存在しないこと | はい | +| ↳ `check:doc-links` | ドキュメント内のMarkdown内部リンクが実在するファイルを参照していること(`[text]`/`(path)` 形式) | はい | +| ↳ `check:fabricated-docs` | ドキュメントに記載されたルート、環境変数、CLIコマンド、フック名、ファイルパスがコードベース内に存在すること。`--strict` ではハードゲート、フラグなしではソフトフェイル。 | はい(CIでは `--strict` を使用) | +| `check:cli-i18n` | CLIコマンド文字列がすべてのi18nロケールファイルに存在すること | はい | +| `check:openapi-coverage` | OpenAPI仕様が、実在するルート数について少なくともラチェット設定された下限以上をカバーしていること | はい | +| `check:openapi-security-tiers` | `openapi.yaml` のセキュリティ階層アノテーションが `routeGuard.ts` の分類と一致していること | **参考情報** | +| `check:openapi-routes` | `openapi.yaml` 内のすべてのパスが実在する `route.ts` に解決されること(ハルシネーション防止) | はい | +| `check:docs-symbols` | `docs/**/*.md` 内のすべての `/api/...` 参照が実在する `route.ts` に解決されること(ハルシネーション防止) | はい | +| `i18n translation drift` | i18nロケールファイル内の未翻訳キー — 警告のみ | **参考情報** | + +### ジョブ: `i18n-ui-coverage` + +| スクリプト | 検証内容 | ブロッキング | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `check-ui-keys-coverage` (inline) | UIのi18nキーのカバレッジが65%以上であること | はい | +| `check-ui-value-drift` (inline) | 書き換えられた英語の**値**に対して、古い翻訳が残っていないこと | はい | +| `check-new-key-coverage` (inline) | **新しい**英語キーがすべてのロケールで翻訳されていること — `__MISSING__:` マーカーは拒否される | はい | +| `check-translation-ratio` | ロケールごとの実翻訳率(許可リスト外で英語と同一/プレースホルダー/欠落しているリーフ)が `config/quality/i18n-translation-baseline.json` + 許容幅を超えてはならないこと | **参考情報** | + +`fetch-depth: 0` が必要です — 値ドリフトゲートは、マージベースとの差分として `en.json` を比較します。 #### `check-ui-value-drift` — 古い翻訳を検出するゲート -他のゲートでは構造上検出できない、ある種の i18n リグレッションを検出します。英語の値が -書き換えられたにもかかわらず、_以前の_英語を基にした翻訳が残り続け、その結果、 -英語以外のユーザーが、自信ありげに書かれた、現在では誤っている文言を読み続ける問題です。 +ほかのゲートでは構造上検出できない、ある種のi18nリグレッションを検出します。英語の値が +書き換えられたにもかかわらず、_以前の_英語から作られた翻訳が残り、 +英語以外のユーザーが、自信に満ちた表現で書かれた、現在では誤っている文言を読み続けてしまう問題です。 -これは実際にリリースされました。Antigravity ログインヘルパーが導入された際(#5203)に +これは実際にリリースされました。Antigravityログインヘルパーが導入された際(#5203)に `oauthModal.googleOAuthWarning` が書き換えられましたが、**43ロケール中39ロケール**では、 -運用者に「完全な URL をコピーして下に貼り付ける」よう案内する文言が残っていました。 -このプロバイダーでは、そのフローを完了できません。この問題は #8463 まで見過ごされていました。 -理由は次のとおりです。 - -- `sync-ui-keys` は**存在しない**キーのみを補完し、**古い**キーは更新しません。 -- `check-ui-keys-coverage` はキーの_存在_を数えるため、古い翻訳もカバー済みとして扱われます。 -- `check-translation-drift` は `docs/i18n//**.md` のドキュメントミラーを追跡します — - `src/i18n/messages/*.json` は一切読み込みません。2026-09 の再同期以降、ジョブ - `docs-sync-strict` でブロッキングされています。コアドキュメントを編集した場合は、 - `npm run i18n:run -- --files=` を実行してください(セクション単位で低コスト)。 - -**差分を認識し、ベースラインには依存しません。** マージベースの `en.json` と -作業ツリーを比較し、英語の値が変更された各キーについて、未変更の翻訳が残っている -ロケールを古いものと判定します。これは意図的に**既存の負債を固定**します。差分からは、 -長期間残っている翻訳がどの古い英語に由来するのか判別できないため、このゲートは -現在の変更が触れた箇所のみを判定します。代替案(キーごとのハッシュベースライン)では、 -既存で最大のベースラインの3倍にあたる、約600 KBの生成ファイルが必要になり、 -i18n関連のPRのたびに変更が発生します。 +オペレーターに「完全なURLをコピーして下に貼り付ける」よう案内するテキストが残っていました — +そのプロバイダーでは完了できないフローです。この問題は#8463まで見過ごされていました。その理由は次のとおりです。 + +- `sync-ui-keys` は**存在しない**キーのみを補完し、**古い**キーは決して補完しないため。 +- `check-ui-keys-coverage` はキーの_存在_を数えるため、古い翻訳もカバー済みとして計上されるため。 +- `check-translation-drift` は `docs/i18n//**.md` のドキュメントミラーを追跡し、 + `src/i18n/messages/*.json` は一切読み込まないため。2026-09の再同期以降、ジョブ `docs-sync-strict` でブロッキングされます。 + コアドキュメントを編集した場合は、`npm run i18n:run -- --files=` を実行してください(セクション単位で低コスト)。 + +**差分認識型であり、ベースライン依存ではありません。** マージベース時点の `en.json` と作業ツリーを比較し、英語の値が変更された各キーについて、未変更の翻訳が残っているロケールを古いものと判定します。これは意図的に**既存の負債を固定**します。差分からは、長期間存在する翻訳がどの古い英語に基づいているか判断できないため、このゲートでは現在の変更が触れた箇所のみを判定します。代替案(キーごとのハッシュベースライン)では、既存の最大ベースラインの3倍に相当する約600 KBの生成ファイルが必要となり、i18nのPRごとに変更が発生します。 これを満たす方法は2つあります。 1. 影響を受ける翻訳を更新する、または -2. `__MISSING__:` に設定する — その場合、ランタイムは修正済みの英語を返し - (`src/i18n/request.ts::deepMergeFallback`、#7258)、そのキーを翻訳待ちキューに追加します。 +2. `__MISSING__:` に設定する — するとランタイムは修正済みの英語を返し + (`src/i18n/request.ts::deepMergeFallback`、#7258)、そのキーは翻訳待ちキューに追加されます。 -文字列の**意味**が変わった場合は、**キー名の変更**を優先してください。新しいキーが -古い翻訳を継承することはありません。#8463ではこのパターンが使用されています。 +文字列の**意味**が変わった場合は、**キーの名前変更**を推奨します。新しいキーが古い翻訳を引き継ぐことはありません。#8463ではこのパターンが使用されています。 ```bash -npm run i18n:check-value-drift # 厳格モード(CIで実行されるもの) +npm run i18n:check-value-drift # 厳格(CIで実行されるもの) npm run i18n:check-value-drift:warn # レポートのみ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -ベースカタログを読み取れない場合(ベースrefのないシャロークローン)は、 -`check-openapi-breaking` と同様に、`SKIP reason=base-unresolved` を表示して終了コード0で -終了します。 +ベースカタログを読み取れない場合(ベースrefを含まないshallow clone)は、`check-openapi-breaking`と同様に、`SKIP reason=base-unresolved`を表示して終了コード0で終了します。 -### ジョブ: `i18n` +### ジョブ:`i18n` -完全なi18n検証マトリクス(ロケールごとに1ジョブ)。ジョブ全体が参考情報扱いです。 +完全なi18n検証マトリックス(ロケールごとに1ジョブ)。ジョブ全体がアドバイザリです。 -| スクリプト | 検証内容 | ブロッキング | -| ------------------------------- | -------------------------- | ----------------------------------------------------- | -| `validate_translation.py quick` | ロケールごとの翻訳の完全性 | **参考情報**(ジョブ全体で`continue-on-error: true`) | +| スクリプト | 検証内容 | ブロッキング | +| ------------------------------- | -------------------------- | --------------------------------------------------------- | +| `validate_translation.py quick` | ロケールごとの翻訳の完全性 | **アドバイザリ**(ジョブ全体に`continue-on-error: true`) | -### ジョブ: `pr-test-policy` +### ジョブ:`pr-test-policy` プルリクエストでのみ実行されます。 -| スクリプト | 検証内容 | ブロッキング | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `check:pr-test-policy` | `src/`、`open-sse/`、`electron/`、または`bin/`の本番コードを変更するPRには、テストの追加または更新が必要(ハードルール#8) | はい | -| `check:test-masking` | 変更されたテストファイルで、アサートの純数が減少しておらず、`assert.ok(true)`のような恒真式が追加されていないこと | はい | -| `check:pr-evidence` | PR本文に変更に関するテスト/VPSの証跡が記載されていること(PRの文章をgrepしてハードルール#18を機械化 — 壊れやすいためBacklogを参照) | はい | +| スクリプト | 検証内容 | ブロッキング | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `check:pr-test-policy` | `src/`、`open-sse/`、`electron/`、または`bin/`内の本番コードを変更するPRには、テストの追加または更新が必要(ハードルール#8) | はい | +| `check:test-masking` | 変更されたテストファイルで、アサーションの正味数が減少しておらず、`assert.ok(true)`のような恒真式が追加されていないこと | はい | +| `check:pr-evidence` | PR本文に変更に関するテスト/VPSの証拠が記載されていること(PR本文をgrepしてハードルール#18を自動化 — 脆弱。Backlogを参照) | はい | -### ジョブ: `test-vitest` +### ジョブ:`test-vitest` -`build`後に実行されます。失敗した場合はマージをブロックします。 +`build`の後に実行されます。失敗した場合はマージをブロックします。 -| スイート | 検証内容 | ブロッキング | -| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCPサーバー(110ツール)、autoCombo、キャッシュ — vitestランナー | はい | -| `test:vitest:ui` | UIコンポーネントテスト — vitestランナー | **ブロッキング** — 既存の失敗は`vitest.config.ts`で明示的に除外されており、新たな失敗が発生するとジョブが失敗します | +| スイート | 検証内容 | ブロッキング | +| ---------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCPサーバー(110ツール)、autoCombo、キャッシュ — vitestランナー | はい | +| `test:vitest:ui` | UIコンポーネントテスト — vitestランナー | **ブロッキング** — 既存の失敗は`vitest.config.ts`で明示的に除外されており、新しい失敗はジョブを失敗させます | -### 夜間ワークフロー(スケジュール実行、参考情報) +### ナイトリーワークフロー(スケジュール実行、アドバイザリ) -これらはcronスケジュール(および`workflow_dispatch`)で実行され、PRでは実行されません。すべて参考情報扱いです。 +これらはcronスケジュール(および`workflow_dispatch`)で実行され、PRでは実行されません。すべてアドバイザリです。 -| ワークフロー | 検証内容 | ブロッキング | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `nightly-property` | ランダムシードと多い実行回数を使用したfast-checkプロパティテスト | **参考情報** | -| `nightly-resilience` | ヒープ増加ゲート、カオスフォールトインジェクション、k6負荷/ソークテスト | **参考情報** | -| `nightly-llm-security` | promptfooインジェクションガード(ブロックモード)+ garakプローブ(プロバイダーシークレットがない場合はスキップ) | **参考情報** | -| `nightly-schemathesis` | `docs/openapi.yaml`を使用し、稼働中のOmniRouteに対してOpenAPIコントラクトファジング(schemathesis)を実行 — 仕様違反/未処理の500を明らかにする(フェーズ8 B.4) | **参考情報** | -| `nightly-mutation` | 高速ユニットレーンに対するStrykerミューテーションテストのスコア — 生き残ったミュータントにより弱いアサートを明らかにする | **参考情報** | -| `nightly-compat` | サポート対象の`engines.node`範囲全体にわたるNodeエンジン互換性マトリクス | **参考情報** | +| ワークフロー | 検証内容 | ブロッキング | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | +| `nightly-property` | ランダムシードと高い実行回数を使用するfast-checkプロパティテスト | **アドバイザリ** | +| `nightly-resilience` | ヒープ増加ゲート、カオス障害注入、k6負荷/ソークテスト | **アドバイザリ** | +| `nightly-llm-security` | promptfooインジェクションガード(ブロックモード)+ garakプローブ(プロバイダーシークレットがない場合はスキップ) | **アドバイザリ** | +| `nightly-schemathesis` | `docs/openapi.yaml`を使用して稼働中のOmniRouteに対して行うOpenAPIコントラクトファジング(schemathesis)— 仕様違反/未処理の500を顕在化(フェーズ8 B.4) | **アドバイザリ** | +| `nightly-mutation` | 高速ユニットレーンに対するStrykerミューテーションテストのスコア — 生き残ったミュータントによって弱いアサーションを顕在化 | **アドバイザリ** | +| `nightly-compat` | サポート対象の`engines.node`範囲全体にわたるNodeエンジン互換性マトリックス | **アドバイザリ** | --- @@ -536,41 +529,41 @@ CI ゲートに加えて、OmniRoute には**オプトイン**の `agent-lsp` ## 合理化バックログ(ROI レビュー — フェーズ 9 ウェーブ 3) -このインベントリは 2026-06-17 に `ci.yml` と照合済みです(以前のバージョンでは +このインベントリは 2026-06-17 に `ci.yml` と照合されました(以前のバージョンでは `audit:deps`、`check:tracked-artifacts`、`check:lockfile`、`check:licenses`、 `check:dead-code`、`check:cognitive-complexity`、`check:type-coverage`、 -`check:codeql-ratchet`、`check:pr-evidence` が抜けていました)。照合済みセットの ROI レビューにより、 -以下の合理化候補が特定されました。**マージは機械的な CI -変更です。切り替え/削除はオペレーターに委ねられたポリシー上の判断です。** 以下の内容は +`check:codeql-ratchet`、`check:pr-evidence` が欠落していました)。照合済みのセットに対する ROI レビューにより、 +以下の合理化候補が特定されました。**統合は機械的な CI +変更です。切り替え/削除はオペレーターに委ねられたポリシー判断です。** 以下の項目は まだ何も適用されていません。 -**上記に加えて未文書化の項目**(アドバイザリー、シグナルが弱い)として、`docs-lint` ジョブ -(markdownlint + Vale、ジョブ全体が `continue-on-error`)およびスタンドアロンのスキャナーワークフロー -`semgrep.yml` / `codeql.yml` / `scorecard.yml` があります。`semgrepFindings: 0` は -`quality-baseline.json` にありますが、`ci.yml` のブロッキング・ラチェットには接続されていません。このメトリクスは +**上記に記載されていないもの**(アドバイザリー、シグナルが弱い):`docs-lint` ジョブ +(markdownlint + Vale、ジョブ全体が `continue-on-error`)および独立したスキャナーワークフロー +`semgrep.yml` / `codeql.yml` / `scorecard.yml`。`semgrepFindings: 0` は +`quality-baseline.json` にありますが、`ci.yml` のブロッキングラチェットには接続されていません。このメトリクスは 現在孤立しています。 -### マージ/重複排除(機械的、低リスク) +### 統合/重複排除(機械的、低リスク) -各候補は 2026-06-17 時点の実際のゲート状態に対して検証済みです(信頼しつつ検証)。 -一見「明らか」なマージのいくつかは、実際には負債を隠していることが判明し、**そのまま置き換え可能ではありません**。 +各候補は 2026-06-17 時点の実際のゲート状態と照合して検証されました(信頼しつつ検証)。 +「明らか」に見えた複数の統合が、実際には負債を隠しており、**そのまま置き換えられるものではない**ことが判明しました。 -- **`check:docs-sync` が 2 回実行される** — `lint` ジョブ内で単独実行され、さらに `check:docs-all`(`docs-sync-strict`)内および husky の pre-commit フックでも実行されます。✅ **完了** — `lint` での単独呼び出しを削除しました。 -- **CVE スキャン** — ❌ **そのままマージ不可。** `audit:deps` は high/critical の CVE が 1 件でもあるとハードフェイルします。一方、`check:vuln-ratchet`(osv)はベースライン(現在は MODERATE が 1 件)に対する_リグレッション_がある場合にのみ失敗します。セマンティクスが異なるため、`audit:deps` を削除すると high/critical に対する絶対的なゲートが失われます。両方を維持してください。 -- **循環依存の検出** — ❌ **そのままマージ不可。** `check:circular-deps`(dpdm)は **91 件の循環**を報告します(そのためアドバイザリーになっています)。まずそれらを解消しない限りブロッキングに昇格できず、正常なキュレーション済み `check:cycles` よりも対象範囲が広くなっています。`check:cycles` はブロッキングのまま維持してください。91 件の dpdm 循環の解消は、独立したバックログ項目です。 -- **複雑度** — ✅ **完了**(`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`):ESLint の走査を 1 回に統合し、ruleId ごとにカウントすることで、循環的複雑度+最大行数と認知的複雑度のベースラインを独立させたままにしています。個別の `check:complexity` / `check:cognitive-complexity` は、ローカルでの `--update` 用に残しています。 -- **`/api` のハルシネーション防止** — ✅ **完了**(`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`):`src/app/api` の FS インベントリを 1 回に統合し、openapi-routes と docs-symbols は引き続き個別にレポートします。個別コマンドはローカル実行用に残しています。 -- **`check:node-runtime` が 11 個のジョブで実行される** — ⚠️ **ROI が低いです。** 各ジョブは別々のランナーで実行され、このチェックは 1 秒未満です。合計削減時間は約 10 秒ですが、安価なジョブ単位のガードを失うことになります。変更コストに見合いません。 -- **CI lint 上の `typecheck:noimplicit:core`** — ✅ **lint ジョブから削除済み**(以前はアドバイザリーの `continue-on-error`)。ブロッキングの型サーフェスは `typecheck:core` + `check:type-coverage` です。ローカルスクリプトは維持しています。 +- **`check:docs-sync` が 2 回実行される** — `lint` ジョブで単独実行され、さらに `check:docs-all`(`docs-sync-strict`)内と husky の pre-commit フックでも実行されます。✅ **完了** — 単独の `lint` 呼び出しを削除しました。 +- **CVE スキャン** — ❌ **そのまま統合可能ではありません。** `audit:deps` は high/critical の CVE が 1 件でもあればハードフェイルします。`check:vuln-ratchet`(osv)はベースライン(現在 1 MODERATE)に対する_悪化_の場合にのみ失敗します。セマンティクスが異なるため、`audit:deps` を削除すると high/critical に対する絶対的なゲートが失われます。両方を維持します。 +- **循環検出** — ✅ **完了**(#15159 G-01/G-02)。以前の記述では `check:cycles` を「グリーンで、厳選された」ゲートと呼び、`check:circular-deps`(dpdm)が 91 件の循環を報告していたことを理由に、ブロッキングのまま維持することを正当化していました。そのグリーンは**偽のグリーン**でした。`check:cycles` は 5 個のサブディレクトリ(450 ファイル)をスキャンし、静的な `import|export … from` のみにマッチし、すべての `@/` および `@omniroute/open-sse/` 指定子を除外していたため、リポジトリ内の大半を占める動的 import + エイリアスの循環を検出できませんでした。修正済みです。現在のゲートは `src` + `open-sse`(5023 ファイル)を走査し、TypeScript AST から指定子を収集し(そのため `import("…")` はカウントされ、型位置の `typeof import("…")` はカウントされません)、tsconfig の `paths` を解決します。検出される循環は 0 件ではなく **14** 件です。既存の 14 件の循環はゲート PR では修正できないため、`check:cycles` は現在**ラチェット**になっています(`--ratchet`、`quality-baseline.json` 内の上限は `metrics.cycles.value = 14`、`direction: down`)。あらゆる_悪化_をブロックし、件数は減少することしか許されません。CI は `npm run check:cycles:ratchet` を実行します。段階的な解消は **A-01** と併せて進めます。`check:circular-deps`(dpdm)は、より広範なセカンドオピニオンとしてアドバイザリーのまま維持します。 +- **複雑度** — ✅ **完了**(`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`):1 回の ESLint 走査で、ruleId ごとにカウントするため、循環的複雑度 + 最大行数と認知的複雑度のベースラインは独立したままです。個別の `check:complexity` / `check:cognitive-complexity` は、ローカルでの `--update` 用に維持します。 +- **`/api` の幻覚防止** — ✅ **完了**(`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`):`src/app/api` に対する 1 回の FS インベントリで、openapi-routes + docs-symbols は引き続き個別に報告します。個別のチェックはローカル実行用に維持します。 +- **`check:node-runtime` が 11 ジョブで実行される** — ⚠️ **ROI が低いです。** それぞれが別のランナーであり、チェックは 1 秒未満です。節約できる合計時間は約 10 秒ですが、その代わりに安価なジョブ単位のガードを失います。変更に伴う手間に見合いません。 +- **CI lint での `typecheck:noimplicit:core`** — ✅ **lint ジョブから削除済み**(以前はアドバイザリーの `continue-on-error`)。ブロッキング対象の型サーフェスは `typecheck:core` + `check:type-coverage` です。ローカルスクリプトは維持しています。 -### 切り替え/判断(オペレーターのポリシー) +### 切り替え/判断(オペレーターポリシー) -- `check:openapi-security-tiers`(アドバイザリー)— ❌ **そのまま切り替え不可。** 終了コードは 0 ですが、`LOCAL_ONLY_API_PREFIXES` 配下の複数の `traffic-inspector` ルートに `x-loopback-only: true` アノテーションがないと警告します。強制するには、まずそれらのアノテーションを `openapi.yaml` に追加する必要があります。 -- `typecheck:noimplicit:core`(アドバイザリー)— ブロッキングの `check:type-coverage` ラチェットによって大部分が包含されています。ラチェットに切り替えるか、重複する 2 回目の `tsc` パスを削除してください。 -- `test:vitest:ui`(現在は**ブロッキング**)— 既存の失敗は、`// #8618` の追跡コメント付きで `vitest.config.ts` から明示的に除外されています。新たな失敗が発生するとジョブは失敗します。 -- `check:secrets`(gitleaks、文書化済みの誤検知 3 件で固定されたブロッキング・ラチェット)— 3 件を許可リストに入れて 0 にするか、アドバイザリーに降格してください。GitHub ネイティブのシークレットスキャンおよび `check:public-creds` と重複しています。 -- `check:pr-evidence`(ブロッキング、PR 本文の文章を grep)— 誤検知のリスクが高い一方、削除するとハードルール #18 の強制力が弱まるため、これは純粋にポリシー上の判断です。 -- `semgrep`(アドバイザリーのスタンドアロン)— OWASP 系の検出で CodeQL と重複しています。ベースラインをラチェットに接続するか、削除してください。 +- `check:openapi-security-tiers`(アドバイザリー)— ❌ **そのまま切り替えることはできません。** 終了コードは 0 ですが、`LOCAL_ONLY_API_PREFIXES` 配下の複数の `traffic-inspector` ルートに `x-loopback-only: true` アノテーションがないことを警告します。強制するには、まずそれらのアノテーションを `openapi.yaml` に追加する必要があります。 +- `typecheck:noimplicit:core`(アドバイザリー)— 大部分がブロッキングの `check:type-coverage` ラチェットに包含されています。ラチェットに切り替えるか、重複する 2 回目の `tsc` パスを削除します。 +- `test:vitest:ui`(現在は**ブロッキング**)— 既存の失敗は、`// #8618` 追跡コメント付きで `vitest.config.ts` から明示的に除外されています。新たな失敗が発生するとジョブは失敗します。 +- `check:secrets`(gitleaks、文書化された 3 件の誤検知で固定されたブロッキングラチェット)— 3 件を許可リストに追加して 0 件にするか、アドバイザリーへ降格します。GitHub ネイティブの secret-scanning + `check:public-creds` と重複します。 +- `check:pr-evidence`(ブロッキング、PR 本文の文章を grep)— 誤検知のリスクが高いです。削除するとハードルール #18 の強制力が弱まるため、これは実質的なポリシー判断です。 +- `semgrep`(独立したアドバイザリー)— OWASP 系統について CodeQL と重複します。ベースラインをラチェットに接続するか、削除します。 --- diff --git a/docs/i18n/ja/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ja/docs/compression/COMPRESSION_GUIDE.md index caba996ebdfb..df5519274850 100644 --- a/docs/i18n/ja/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ja/docs/compression/COMPRESSION_GUIDE.md @@ -439,7 +439,7 @@ const { messages: aged, saved } = applyAging(messages, { `applyOutputStyles()` (`open-sse/services/compression/outputStyles/apply.ts`) は、カタログに対して選択を解決し(不明な `id` やロケールが一致しないスタイルは破棄され、エラーにはなりません)、選択された指示をカタログ順に連結し、境界句を**一度だけ**追加し、単一の冪等性マーカー (`[OmniRoute Output Styles]`) でブロックを開始します。これにより、再適用は何も行いません。解決された言語(下記の「言語選択」を参照)に翻訳がある場合、英語の代わりにローカライズされた指示が注入されます。 -`messages` を含むボディの場合、コンテンツバイパス (`open-sse/services/compression/outputMode.ts` 内の `shouldBypassCavemanOutputMode()`) は、最後の3つのメッセージをチェックし、それらがセキュリティ、不可逆的なアクション、明確化、または順序に敏感なキーワードと一致する場合、そのターン全体のスタイルをスキップします。このバイパスは、ダッシュボードの**自動明瞭化バイパス**トグル (`cavemanOutputMode.autoClarity`) の設定に関わらず実行されます。 +`messages` を含むボディの場合、コンテンツバイパス (`open-sse/services/compression/outputMode.ts` 内の `shouldBypassCavemanOutputMode()`) は、最後の3つのメッセージをチェックし、それらがセキュリティ、不可逆的なアクション、明確化、または順序に敏感なキーワードと一致する場合、そのターン全体のスタイルをスキップします。このバイパスは、ダッシュボードの**自動明瞭化バイパス**トグル (`cavemanOutputMode.autoClarity`) がオンの間実行されます。これは既定の設定です。トグルがオフのときは、選択したスタイルがそれらのターンでも適用されます。 バイパスがターンを通過させた場合、`messages[0]` を新しく作成することのない `placeSystemInstruction()` (同じファイル内) は、見つかった以下の最初の場所にブロックを配置します。 diff --git a/docs/i18n/ja/docs/reference/ENVIRONMENT.md b/docs/i18n/ja/docs/reference/ENVIRONMENT.md index 9b4e5ae24ff9..e4c254eae289 100644 --- a/docs/i18n/ja/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ja/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. 必須シークレット -これらは初回実行前に**必ず**設定する必要があります。設定されていない場合、アプリケーションは起動を拒否するか、安全でないデフォルト設定で動作します。 - -| 変数 | 必須 | デフォルト | ソースファイル | 説明 | -| ---------------------------- | -------------------- | ------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **はい** | _(なし)_ | `src/lib/auth` | すべてのダッシュボードセッションCookie(JWT)の署名と検証に使用します。`openssl rand -base64 48`で生成してください。 | -| `API_KEY_SECRET` | **はい** | _(なし)_ | `src/lib/db/apiKeys.ts` | SQLite内で保存されるAPIキー値を暗号化するためのAES暗号鍵です。`openssl rand -hex 32`で生成してください。 | -| `INITIAL_PASSWORD` | **はい** | `CHANGEME` | ブートストラップスクリプト | 初期管理者ダッシュボードパスワードを設定します(`.env.example`のデフォルトと一致します。変更を強制するため、意図的に安全でないことが明確な値になっています)。**初回使用前に変更してください。**ログイン後、ダッシュボード → 設定 → セキュリティから変更してください。 | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **はい**(本番環境) | _(未設定)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 内部Codex Responses WebSocketブリッジ用の共有シークレットです。Electron/ブラウザーのWSリレーとOmniRoute間のブリッジリクエストを認証します。⚠️ **本番環境では必須です。未設定の場合、すべてのWSブリッジリクエストが拒否されます。**`openssl rand -base64 32`で生成してください。 | -| `OMNIROUTE_SW_BUILD_ID` | いいえ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWAシェル(#11779)のキャッシュを確実に更新するための明示的なサービスワーカーのIDです。解決チェーンで最初に使用されます。 | -| `SOURCE_VERSION` | いいえ | _(未設定)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | チェーンの2番目に使用されます。デプロイされたコミットとしてPaaSビルダー(例:Heroku形式)によって設定されます。 | -| `NEXT_PUBLIC_SW_BUILD_ID` | いいえ | _(導出値)_ | `src/shared/components/PwaRegister.tsx` | クライアントが`/sw.js?v=…`を登録するために使用するビルド時の公開値です。上記2つの値、続いてgit SHAから導出されます。 | -| `OMNIROUTE_PEER_STAMP_TOKEN` | いいえ(自動) | _(起動ごとに自動)_ | `src/server/authz/policies/management.ts` | 信頼されたピアIPスタンプがOmniRoute自身のHTTPサーバー(`scripts/dev/peer-stamp.mjs`)から発行されたことを証明する、プロセス単位のシークレットです。authzミドルウェアは、スタンプにこのトークンが含まれている場合にのみ、リクエストのローカル性(LOCAL_ONLYルートのループバック/LANゲーティング)を信頼します。起動ごとに自動生成されるため、未設定のままにしてください。スタンプを共有する必要があるマルチプロセス構成の場合にのみ固定してください。 | -| `OMNIROUTE_TRUSTED_PROXIES` | いいえ | _(未設定)_ | `scripts/dev/peer-stamp.mjs` | IP許可/拒否リストが`X-Forwarded-For`/`X-Real-IP`を信頼できるリバースプロキシのIPまたはCIDR範囲を、カンマ区切りで指定します。ループバック、プライベートネットワークアドレス、およびCloudflareエッジは常に信頼されます。これら以外のパブリックアドレス上にあるプロキシの場合にのみ必要です。指定しない場合、フィルターはプロキシ自体のアドレスを判定対象とします。 | +**はい**とマークされた未設定のシークレットは、初回実行時に自動生成され(`JWT_SECRET`、`API_KEY_SECRET`)、データディレクトリ内の `server.env` に保存されます。そのディレクトリが一時的なものである場合は、明示的に設定してください。例外は `OMNIROUTE_WS_BRIDGE_SECRET` です。これは `server.env` には保存されません。サーバーのエントリポイントは起動ごとに、プロセス内のブリッジのみが使用できる一時的な値を発行するため、外部のブリッジクライアントでは明示的な設定が必要です。 + +| 変数 | 必須 | デフォルト | ソースファイル | 説明 | +| ---------------------------- | -------------------- | ------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **はい** | _(なし)_ | `src/lib/auth` | すべてのダッシュボードセッションCookie(JWT)の署名と検証に使用します。`openssl rand -base64 48`で生成してください。 | +| `API_KEY_SECRET` | **はい** | _(なし)_ | `src/lib/db/apiKeys.ts` | SQLite内で保存されるAPIキー値を暗号化するためのAES暗号鍵です。`openssl rand -hex 32`で生成してください。 | +| `INITIAL_PASSWORD` | いいえ | _(未設定)_ | `src/lib/auth/managementPassword.ts` | パスワードがまだ保存されていない間、起動時にダッシュボードのパスワードを設定します。パスワードが保存されると無視されます。npm インストールとソースインストールは `.env.example` を `.env` へコピーするため、公開されているプレースホルダー `CHANGEME` のまま起動します。初回起動前に置き換えるか、その `.env` で `INITIAL_PASSWORD=`(空)を設定して、未設定値(Docker イメージのデフォルト)と同様に、初回訪問時にセットアップウィザードでパスワードを作成してください。保存済みパスワードの変更は、ダッシュボード → 設定 → セキュリティから行うか、`DATA_DIR` をサーバーのデータディレクトリに設定して `omniroute reset-password`(ソースチェックアウトでは `node bin/reset-password.mjs`)を実行してください。 | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **はい**(本番環境) | _(未設定)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 内部Codex Responses WebSocketブリッジ用の共有シークレットです。Electron/ブラウザーのWSリレーとOmniRoute間のブリッジリクエストを認証します。⚠️ **本番環境では必須です。未設定の場合、すべてのWSブリッジリクエストが拒否されます。**`openssl rand -base64 32`で生成してください。 | +| `OMNIROUTE_SW_BUILD_ID` | いいえ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWAシェル(#11779)のキャッシュを確実に更新するための明示的なサービスワーカーのIDです。解決チェーンで最初に使用されます。 | +| `SOURCE_VERSION` | いいえ | _(未設定)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | チェーンの2番目に使用されます。デプロイされたコミットとしてPaaSビルダー(例:Heroku形式)によって設定されます。 | +| `NEXT_PUBLIC_SW_BUILD_ID` | いいえ | _(導出値)_ | `src/shared/components/PwaRegister.tsx` | クライアントが`/sw.js?v=…`を登録するために使用するビルド時の公開値です。上記2つの値、続いてgit SHAから導出されます。 | +| `OMNIROUTE_PEER_STAMP_TOKEN` | いいえ(自動) | _(起動ごとに自動)_ | `src/server/authz/policies/management.ts` | 信頼されたピアIPスタンプがOmniRoute自身のHTTPサーバー(`scripts/dev/peer-stamp.mjs`)から発行されたことを証明する、プロセス単位のシークレットです。authzミドルウェアは、スタンプにこのトークンが含まれている場合にのみ、リクエストのローカル性(LOCAL_ONLYルートのループバック/LANゲーティング)を信頼します。起動ごとに自動生成されるため、未設定のままにしてください。スタンプを共有する必要があるマルチプロセス構成の場合にのみ固定してください。 | +| `OMNIROUTE_TRUSTED_PROXIES` | いいえ | _(未設定)_ | `scripts/dev/peer-stamp.mjs` | IP許可/拒否リストが`X-Forwarded-For`/`X-Real-IP`を信頼できるリバースプロキシのIPまたはCIDR範囲を、カンマ区切りで指定します。ループバック、プライベートネットワークアドレス、およびCloudflareエッジは常に信頼されます。これら以外のパブリックアドレス上にあるプロキシの場合にのみ必要です。指定しない場合、フィルターはプロキシ自体のアドレスを判定対象とします。 | ### 生成コマンド @@ -215,38 +215,38 @@ OmniRoute は、すべての永続化に **SQLite**(`better-sqlite3` 経由) ## 4. セキュリティと認証 -| 変数 | デフォルト | ソースファイル | 説明 | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | マシンのフィンガープリント生成のためにハードウェア識別子と組み合わせるソルト。環境を分離するため、デプロイごとに変更してください。 | -| `OMNIROUTE_CLI_SALT` | _(未設定 = インストールごとにランダムなソルトを生成し、`/cli-token-salt.json` に永続化)_ | `src/lib/machineToken.ts` | ローカル CLI 認証トークンを導出するための HMAC ソルト。この値を設定すると、そのマシン上のすべての CLI トークンがローテーションされ、永続化されたソルトより常に優先されます。`docs/security/CLI_TOKEN.md` を参照してください。 | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | セッション Cookie に `Secure` フラグを設定します。HTTPS の背後で実行する場合は、**必ず `true` にする必要があります**。 | -| `REQUIRE_API_KEY` | `false` | API ミドルウェア | `true` の場合、すべての `/v1/*` プロキシリクエストに有効な API キーを含める必要があります。**このフラグは `GET /v1/models` へのアクセスを制御しません**。代わりに、`GET /v1/models` はダッシュボードのログイン設定(`requireAuthForModels`)に従います。そのため、`/v1/models` から `401` が返されても、推論が保護されていることを意味するわけではありません。`docs/security/INFERENCE_AUTH_POSTURE.md`(#13695)を参照してください。 | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ダッシュボード UI で API キーの完全な値を表示できるようにします。ダッシュボードの機能フラグから設定できますが、共有インスタンスではセキュリティリスクがあります。 | -| `NO_LOG_API_KEY_IDS` | _(空)_ | `src/lib/compliance/index.ts` | リクエストログへの記録を省略する API キー ID のカンマ区切りリスト(GDPR 準拠)。 | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(未設定 = 無制限)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` 列が null の API キーに適用される、1 日あたりのフォールバックリクエスト上限。未設定または空の場合、暗黙的な上限はありません(#2289、#11017)。`0` も同様に無制限です。正の整数 N を指定すると、N/日、5N/週、20N/月が有効になります。不正な空でない値の場合は、従来の 1000/日、5000/週、20000/月の期間別上限にフォールバックします。 | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 許可されるリクエスト本文の最大サイズ。この上限を超えるペイロードは拒否されます。 | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | このしきい値以上の実際のリクエスト本文は、JSON の解析前に、プロセスローカルなアトミック方式の高負荷処理用アドミッションリースを取得します(`POST /v1/responses` を含む BYTE パス)。構造的に高負荷な場合と同じ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) の健全なヘッドルームによるエスケープが適用されますが、[#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) の再発を防ぐため、引き続き `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) によって制限されます。 | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | 境界付き取り込み中に読み取られたバイト数に対して適用される、チャットルートのハード上限。`Content-Length` が欠落、不正、または偽装されているリクエストも対象となり、超過時には `413` が返されます。 | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(未設定 — リクエスト数の上限なし)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** このレガシーなリクエスト数上限は、明示的に設定された場合にのみ適用されるようになりました。未設定(デフォルト)の場合、重量級チャットの受け入れは代わりに `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` によって制御されます。これは、**1 プロセス**(1 つの V8 ヒープ)におけるプロセスの実際のメモリ上限から自動算出されるバイト単位の予算です。約 750k トークンの `/v1/responses` が 2 件重なると、約 12 GiB のヒープが異常終了します(#7849)。これは**メモリ予算**に関する警告であり、製品上のハード上限が 2 であることを意味しません。正常なプロセス(ヒープがシェッド比率未満)では、`OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` を介して、より多くの長時間 `/v1/responses` を同時に受け入れてもかまいません。長時間接続する SSE クライアントが数十(40~50)ある場合の制約は、ヒープ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 であり、「最大 2」ではありません。「ホストを使い切る」ためにこれをむやみに引き上げると、#7849 が再発します。**N 個の独立した `DATA_DIR`**(#11024)で**ヒープ**を増やしてください。1 つの SQLite ファイルに対して `replicas>1` を使用してはいけません。 | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(自動算出)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** 自動算出される取り込みバイト予算(V8 と cgroup のうち厳しい方のメモリ上限の 25% を、一時的な 8 倍の増幅係数で除算)のオーバーライドです。算出値と明示値はいずれも 8 MiB~2 GiB の範囲に制限されます。実効予算を超える本文は、直ちに `413 body_exceeds_budget` で失敗します。個別には処理可能な本文間の競合では、引き続き再試行可能な `503` が返されます。40~50 個の長時間 SSE クライアントに対する制約は、この予算 + ヒープであり、ハードな「最大 2」ではありません。調整前に、`/api/monitoring/health` で `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` を確認してください。 | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE および STRUCTURE の重量級受け入れに対する、ヒープ圧力のシェッド比率(`heapUsed / heap_size_limit`)です(#10183、#10268、#10437)。`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` を超える同時重量級リクエストが、再試行可能な `503` でシェッドされるのは、ヒープがこの比率以上でもある場合に限られます。ヒープが正常な場合は、healthy-headroom を介して受け入れられます。 | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (デフォルト `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE と BYTE の**両方**(`OMNIROUTE_CHAT_LARGE_BODY_BYTES` 以上の本文を含む `admitChatRequest`)に適用される、正常なヒープ向け高速パス(#10437)の制限付き追加容量です。この制限がなければ、ヒープが正常な場合、ビジー状態のすべてのリクエストが上限なしで受け入れ制御を迂回していました。この数の同時リースが正常ヒープパスを通じてアクティブになると、それ以降のビジー状態のリクエストは、実際のヒープ圧力下で使用されるものと同じ、境界付き待機/シェッドパスへフォールスルーします。`0` にするとバイパスが完全に無効になります。 | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | 本文がバイトしきい値未満でも、チャットリクエストを重量級として分類するメッセージ数です。 | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | 本文がバイトしきい値未満でも、チャットリクエストを重量級として分類するツール数です。 | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | リクエストを重量級として分類する、文字列サイズに基づく保守的なトークン推定値です。これは受け入れコストの代理指標であり、プロバイダーの課金用トークン化ではありません。 | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (無効) | `src/shared/middleware/chatBodyAdmission.ts` | オプトインで設定できる、チャット履歴の上限です。デフォルトでは無効です。メッセージ数はデプロイメントポリシーであり、リクエストに普遍的に備わる属性ではありません。また、ここで上限を設けると、圧縮パイプラインによって処理可能になる前に、会話が終端的な `413` で拒否されます。ヒープの増加は、`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` とヒープ圧力シェッドによって制限されます。ハード上限が必要なメモリ制約の厳しいデプロイメントでは、正の値を設定してください。超過時には、compact-required を示す構造化された `413` が返されます。 | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | メモリに完全にバッファリングされる、非ストリーミングのアップストリームレスポンスに対するハード上限です。これを超えるとアップストリームリーダーがキャンセルされ、ヒープが枯渇するまで文字列が無制限に増大する代わりに、リクエストが即座に失敗します。 | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | アップストリームのレスポンスヘッダーから転送する最大ワイヤーバイト数。上限を超えると、一般的なリバースプロキシのヘッダー制限内に収めるため、優先度の低いヘッダー(例: カスタム `x-codex-*`、`x-oai-request-id`)が削除されます。レスポンスヘッダーのサイズが大きくなる代わりに、より多くのアップストリームメタデータを転送するには、より大きな値を設定してください。 | -| `CORS_ORIGIN` | _(未設定)_ | `src/server/cors/origins.ts` | 従来の単一オリジン用CORS許可リスト。新規デプロイでは `CORS_ALLOWED_ORIGINS` を推奨します。CORSはクロスオリジンのブラウザAPIクライアント専用です。認証済みダッシュボードからの書き込みでは、代わりに同一オリジンリクエストとセッションに紐づいたCSRF保護を使用します。 | -| `CORS_ALLOWED_ORIGINS` | _(未設定)_ | `src/server/cors/origins.ts` | カンマ区切りのCORS許可リスト。`CORS_ALLOW_ALL=true` が明示的に設定されていない限り、ワイルドカードは送信されません。 | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 任意のブラウザ `Origin` をそのまま返す、開発環境専用の緊急回避設定です。共有環境または本番環境では有効にしないでください。 | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | プライベート/ループバック/リンクローカルIP範囲を対象とするプロバイダー呼び出しをブロックします。無効化するのは、隔離されたテスト環境のみにしてください。 | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | プライベート/ローカルネットワーク(localhost、192.168.x.x、10.x.x.xなど)を指すプロバイダーURLを許可します。**セルフホスト型プロバイダーには必須です**(LM Studio、Ollama、vLLM、Llamafile、Triton、SearXNG)。`false` の場合、ダッシュボードはローカルURLの検証を拒否します。 | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | ローカル/プライベートアドレス(127.0.0.1、localhost、LAN、プライベート範囲)上のプロバイダーの追加/検証を許可します。これはプロバイダー検証パスのみに適用されます。**デフォルトは `true`**(ローカル優先)。公開アドレス以外を厳格にブロックするには `false` に設定してください。クラウドメタデータエンドポイント(169.254.169.254、metadata.google.internal)は、この設定にかかわらずブロックされます。(#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ルート(文字起こし、音声、翻訳)が、localhost外でホストされているOpenAI互換プロバイダーノードを使用できるようにします。デフォルトでは無効です。音声をリモートホストにルーティングすると送信元IDが変わるため、オペレーターによる明示的な判断が必要です。ループバック/プライベートノード(localhost、127.0.0.1、172.16-31.x)は常に許可され、この設定の影響を受けません。(#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`(およびメモリエンジンのループバック再ランク処理)が、localhost外でホストされているOpenAI互換プロバイダーノード(TEI、Infinity、vLLMなどを実行するLAN上のマシンまたはTailscaleピア)を使用できるようにします。デフォルトでは無効です。リモートホストへのルーティングは送信元IDを変更するため、オペレーターによる明示的な判断が必要です。ループバックノード(localhost、127.0.0.1、172.16-31.x)は常に許可され、この設定の影響を受けません。リモートノードは、プロバイダーのアウトバウンドURLポリシー(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)にも合格する必要があります。クラウドメタデータホストには決してルーティングされません。 | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDCが有効な場合、パスワードログインを無効にし、ユーザーがOIDCシングルサインオン経由でのみ認証できるようにします。短縮エイリアス `OIDC_DISABLE_PASSWORD_LOGIN` も使用できます。同じキーを持つダッシュボードの機能フラグが優先されます。(#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` の短縮エイリアスです。(#10889) | +| 変数 | デフォルト | ソースファイル | 説明 | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | マシンのフィンガープリント生成のためにハードウェア識別子と組み合わせるソルト。環境を分離するため、デプロイごとに変更してください。 | +| `OMNIROUTE_CLI_SALT` | _(未設定 = インストールごとにランダムなソルトを生成し、`/cli-token-salt.json` に永続化)_ | `src/lib/machineToken.ts` | ローカル CLI 認証トークンを導出するための HMAC ソルト。この値を設定すると、そのマシン上のすべての CLI トークンがローテーションされ、永続化されたソルトより常に優先されます。`docs/security/CLI_TOKEN.md` を参照してください。 | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | セッション Cookie に `Secure` フラグを設定します。HTTPS の背後で実行する場合は、**必ず `true` にする必要があります**。 | +| `REQUIRE_API_KEY` | `false` | API ミドルウェア | `true` の場合、すべての `/v1/*` プロキシリクエストに有効な API キーを含める必要があります。**このフラグは `GET /v1/models` へのアクセスを制御しません**。代わりに、`GET /v1/models` はダッシュボードのログイン設定(`requireAuthForModels`)に従います。そのため、`/v1/models` から `401` が返されても、推論が保護されていることを意味するわけではありません。`docs/security/INFERENCE_AUTH_POSTURE.md`(#13695)を参照してください。 | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ダッシュボード UI で API キーの完全な値を表示できるようにします。ダッシュボードの機能フラグから設定できますが、共有インスタンスではセキュリティリスクがあります。 | +| `NO_LOG_API_KEY_IDS` | _(空)_ | `src/lib/compliance/index.ts` | リクエストログへの記録を省略する API キー ID のカンマ区切りリスト(GDPR 準拠)。 | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(未設定 = 無制限)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` 列が null の API キーに適用される、1 日あたりのフォールバックリクエスト上限。未設定または空の場合、暗黙的な上限はありません(#2289、#11017)。`0` も同様に無制限です。正の整数 N を指定すると、N/日、5N/週、20N/月が有効になります。不正な空でない値の場合は、従来の 1000/日、5000/週、20000/月の期間別上限にフォールバックします。 | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 許可されるリクエスト本文の最大サイズ。この上限を超えるペイロードは拒否されます。 | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | このしきい値以上の実際のリクエスト本文は、JSON の解析前に、プロセスローカルなアトミック方式の高負荷処理用アドミッションリースを取得します(`POST /v1/responses` を含む BYTE パス)。構造的に高負荷な場合と同じ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) の健全なヘッドルームによるエスケープが適用されますが、[#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) の再発を防ぐため、引き続き `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) によって制限されます。 | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | 境界付き取り込み中に読み取られたバイト数に対して適用される、チャットルートのハード上限。`Content-Length` が欠落、不正、または偽装されているリクエストも対象となり、超過時には `413` が返されます。 | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(未設定 — リクエスト数の上限なし)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** このレガシーなリクエスト数上限は、明示的に設定された場合にのみ適用されるようになりました。未設定(デフォルト)の場合、重量級チャットの受け入れは代わりに `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` によって制御されます。これは、**1 プロセス**(1 つの V8 ヒープ)におけるプロセスの実際のメモリ上限から自動算出されるバイト単位の予算です。約 750k トークンの `/v1/responses` が 2 件重なると、約 12 GiB のヒープが異常終了します(#7849)。これは**メモリ予算**に関する警告であり、製品上のハード上限が 2 であることを意味しません。正常なプロセス(ヒープがシェッド比率未満)では、`OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` を介して、より多くの長時間 `/v1/responses` を同時に受け入れてもかまいません。長時間接続する SSE クライアントが数十(40~50)ある場合の制約は、ヒープ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 であり、「最大 2」ではありません。「ホストを使い切る」ためにこれをむやみに引き上げると、#7849 が再発します。**N 個の独立した `DATA_DIR`**(#11024)で**ヒープ**を増やしてください。1 つの SQLite ファイルに対して `replicas>1` を使用してはいけません。 | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(自動算出)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** 自動算出される取り込みバイト予算(V8 と cgroup のうち厳しい方のメモリ上限の 25% を、一時的な 8 倍の増幅係数で除算)のオーバーライドです。算出値と明示値はいずれも 8 MiB~2 GiB の範囲に制限されます。実効予算を超える本文は、直ちに `413 body_exceeds_budget` で失敗します。個別には処理可能な本文間の競合では、引き続き再試行可能な `503` が返されます。40~50 個の長時間 SSE クライアントに対する制約は、この予算 + ヒープであり、ハードな「最大 2」ではありません。調整前に、`/api/monitoring/health` で `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` を確認してください。 | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE および STRUCTURE の重量級受け入れに対する、ヒープ圧力のシェッド比率(`heapUsed / heap_size_limit`)です(#10183、#10268、#10437)。`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` を超える同時重量級リクエストが、再試行可能な `503` でシェッドされるのは、ヒープがこの比率以上でもある場合に限られます。ヒープが正常な場合は、healthy-headroom を介して受け入れられます。 | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (デフォルト `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE と BYTE の**両方**(`OMNIROUTE_CHAT_LARGE_BODY_BYTES` 以上の本文を含む `admitChatRequest`)に適用される、正常なヒープ向け高速パス(#10437)の制限付き追加容量です。この制限がなければ、ヒープが正常な場合、ビジー状態のすべてのリクエストが上限なしで受け入れ制御を迂回していました。この数の同時リースが正常ヒープパスを通じてアクティブになると、それ以降のビジー状態のリクエストは、実際のヒープ圧力下で使用されるものと同じ、境界付き待機/シェッドパスへフォールスルーします。`0` にするとバイパスが完全に無効になります。 | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | 本文がバイトしきい値未満でも、チャットリクエストを重量級として分類するメッセージ数です。 | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | 本文がバイトしきい値未満でも、チャットリクエストを重量級として分類するツール数です。 | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | リクエストを重量級として分類する、文字列サイズに基づく保守的なトークン推定値です。これは受け入れコストの代理指標であり、プロバイダーの課金用トークン化ではありません。 | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (無効) | `src/shared/middleware/chatBodyAdmission.ts` | オプトインで設定できる、チャット履歴の上限です。デフォルトでは無効です。メッセージ数はデプロイメントポリシーであり、リクエストに普遍的に備わる属性ではありません。また、ここで上限を設けると、圧縮パイプラインによって処理可能になる前に、会話が終端的な `413` で拒否されます。ヒープの増加は、`OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` とヒープ圧力シェッドによって制限されます。ハード上限が必要なメモリ制約の厳しいデプロイメントでは、正の値を設定してください。超過時には、compact-required を示す構造化された `413` が返されます。 | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | メモリに完全にバッファリングされる、非ストリーミングのアップストリームレスポンスに対するハード上限です。これを超えるとアップストリームリーダーがキャンセルされ、ヒープが枯渇するまで文字列が無制限に増大する代わりに、リクエストが即座に失敗します。 | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | アップストリームのレスポンスヘッダーから転送する最大ワイヤーバイト数。上限を超えると、一般的なリバースプロキシのヘッダー制限内に収めるため、優先度の低いヘッダー(例: カスタム `x-codex-*`、`x-oai-request-id`)が削除されます。レスポンスヘッダーのサイズが大きくなる代わりに、より多くのアップストリームメタデータを転送するには、より大きな値を設定してください。 | +| `CORS_ORIGIN` | _(未設定)_ | `src/server/cors/origins.ts` | 従来の単一オリジン用CORS許可リスト。新規デプロイでは `CORS_ALLOWED_ORIGINS` を推奨します。CORSはクロスオリジンのブラウザAPIクライアント専用です。認証済みダッシュボードからの書き込みでは、代わりに同一オリジンリクエストとセッションに紐づいたCSRF保護を使用します。 | +| `CORS_ALLOWED_ORIGINS` | _(未設定)_ | `src/server/cors/origins.ts` | カンマ区切りのCORS許可リスト。`CORS_ALLOW_ALL=true` が明示的に設定されていない限り、ワイルドカードは送信されません。 | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 任意のブラウザ `Origin` をそのまま返す、開発環境専用の緊急回避設定です。共有環境または本番環境では有効にしないでください。 | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | レガシーエイリアスです。この変数のダッシュボードトグル(DBオーバーライド)は、この環境変数の値より先に読み取られます。いずれかに `false`、`0`、`no`、`off` があると、`OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` と同じやり方でホストチェックをオフにします(そのフラグのダッシュボードトグルがオフで保存されている場合も同様)。その他の値では、ガードの判断は下の2つのフラグに委ねられます。 | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | ガードモードに従うプロバイダーパス(URL検証、モデル検出、プロバイダーノードのベースURL(リモート再ランクノードを含む)、プロキシフォールバックテスト、その他リモート画像/メディア取得などのプロバイダー隣接のアウトバウンドパス)において、アウトバウンドURLガードのホストチェックを、クラウドメタデータブロック(169.254.0.0/16 のすべて、metadata.google.internal を含む)も含めてオフにします。クラウドメタデータ/リンクローカルのエンドポイントを除き、プライベートな Webhook 送信先も許可します。それらのエンドポイントは、このフラグが有効でもブロックされたままです。`BaseExecutor.execute()` 経由で送出されるチャットリクエストは、クラウドメタデータチェックを維持します。ローカルプロバイダーはデフォルト設定のままで動作します。`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS`(デフォルト `true`)が、検証・検出・プロバイダーノードの各パスでローカルおよびLAN URLをすでに許可しているためです。プロキシフォールバックテストとプライベート Webhook 送信先はこのフラグのみを参照し、オフの間はローカル/LANホストをブロックします。`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` を上書きします。ダッシュボードトグルから保存された値は、この変数より優先されます。 | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | プロバイダー検証、モデル検出、チャット、およびガードモードが管轄するその他のプロバイダー隣接アウトバウンドパスについて、ローカル/プライベートアドレス(127.0.0.1、localhost、LAN、プライベート範囲)上のプロバイダーURLを許可します。**デフォルトは `true`**(ローカル優先)。この場合ガードはクラウドメタデータエンドポイント(169.254.0.0/16 のすべてと、既知のメタデータホスト名(metadata.google.internal、metadata.goog、Alibaba/Azure/Oracle IMDS、fd00:ec2::254))をブロックします。公開のみモードにするには `false` を設定してください。このモードではプライベートホストとループバックホストもブロックされます。ガードは、DNSルックアップより前に、書かれたままのホスト名またはIPリテラルをチェックします。公開のみモードでは、safe-fetch ラッパーがさらにホスト名を名前解決し、プライベートな応答はすべて拒否します。`OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` は、その行に列挙されたパスでこれらのチェックをオフにします。LM Studio、Ollama、vLLM、Llamafile、Triton などの組み込みローカルプロバイダーは、キー検証とチャットでホストチェックをすべてスキップします。ダッシュボードトグルから保存された値は、この変数より優先されます。(#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ルート(文字起こし、音声、翻訳)が、localhost外でホストされているOpenAI互換プロバイダーノードを使用できるようにします。デフォルトでは無効です。音声をリモートホストにルーティングすると送信元IDが変わるため、オペレーターによる明示的な判断が必要です。ループバック/プライベートノード(localhost、127.0.0.1、172.16-31.x)は常に許可され、この設定の影響を受けません。(#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`(およびメモリエンジンのループバック再ランク処理)が、localhost外でホストされているOpenAI互換プロバイダーノード(TEI、Infinity、vLLMなどを実行するLAN上のマシンまたはTailscaleピア)を使用できるようにします。デフォルトでは無効です。リモートホストへのルーティングは送信元IDを変更するため、オペレーターによる明示的な判断が必要です。ループバックノード(localhost、127.0.0.1、172.16-31.x)は常に許可され、この設定の影響を受けません。リモートノードのベースURLも、プロバイダーのアウトバウンドURLポリシー(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)に合格する必要があります。 | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDCが有効な場合、パスワードログインを無効にし、ユーザーがOIDCシングルサインオン経由でのみ認証できるようにします。短縮エイリアス `OIDC_DISABLE_PASSWORD_LOGIN` も使用できます。同じキーを持つダッシュボードの機能フラグが優先されます。(#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` の短縮エイリアスです。(#10889) | ### セキュリティ強化チェックリスト diff --git a/docs/i18n/ka/CONTRIBUTING.md b/docs/i18n/ka/CONTRIBUTING.md index 953774c54f0b..7db424d06ddd 100644 --- a/docs/i18n/ka/CONTRIBUTING.md +++ b/docs/i18n/ka/CONTRIBUTING.md @@ -398,17 +398,17 @@ docs/ - [ ] ტესტები წარმატებით სრულდება (`npm test`) - [ ] ლინტინგი წარმატებით სრულდება (`npm run lint`) - [ ] აგება წარმატებით სრულდება (`npm run build`) -- [ ] ახალი საჯარო ფუნქციებისა და ინტერფეისებისთვის დამატებულია TypeScript-ის ტიპები -- [ ] არ არის მყარად გაწერილი საიდუმლოები ან სარეზერვო მნიშვნელობები -- [ ] ზედა დონის საჯარო ავტორიზაციის მონაცემები ჩაშენებულია `resolvePublicCred()`-ის მეშვეობით (იხილეთ [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) და არასოდეს — ლიტერალების სახით -- [ ] შეცდომის პასუხები მუშავდება `buildErrorBody()` / `sanitizeErrorMessage()`-ის მეშვეობით — პასუხის სხეულებში არ არის დაუმუშავებელი სტეკის კვალები (იხილეთ [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] ახალ საჯარო ფუნქციებსა და ინტერფეისებს დამატებული აქვს TypeScript-ის ტიპები +- [ ] არ არის მყარად გაწერილი საიდუმლო მონაცემები ან სარეზერვო მნიშვნელობები +- [ ] საჯარო upstream ავტორიზაციის მონაცემები ჩაშენებულია `resolvePublicCred()`-ის მეშვეობით (იხილეთ [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) და არასდროს — პირდაპირი ლიტერალების სახით +- [ ] შეცდომის პასუხები მუშავდება `buildErrorBody()` / `sanitizeErrorMessage()`-ის მეშვეობით — პასუხის სხეულებში არ არის დაუმუშავებელი stack trace-ები (იხილეთ [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] გარსის ბრძანებები (`exec` / `spawn`) შესრულების დროის მნიშვნელობებს გადასცემს `env`-ის მეშვეობით და არა სტრიქონული ინტერპოლაციით -- [ ] ყველა შეყვანილი მონაცემი შემოწმებულია Zod-ის სქემებით -- [ ] მომხმარებლისთვის ხილული ცვლილებებისთვის ცვლილებების ჟურნალის **ფრაგმენტი** დამატებულია `changelog.d/{features|fixes|maintenance}/-.md`-ში (იხილეთ [`changelog.d/README.md`](./changelog.d/README.md)) — **არ** დაარედაქტიროთ `CHANGELOG.md` პირდაპირ; ფრაგმენტები გაერთიანდება გამოშვებისას და Pull Request-ებს შორის კონფლიქტს არასოდეს წარმოქმნის +- [ ] ყველა შეყვანილი მონაცემი ვალიდირებულია Zod-ის სქემებით +- [ ] მომხმარებლისთვის ხილული ცვლილებებისთვის `changelog.d/{features|fixes|maintenance}/-.md`-ში დამატებულია ცვლილებების ჟურნალის **ფრაგმენტი** (იხილეთ [`changelog.d/README.md`](./changelog.d/README.md)) — **არ** დაარედაქტიროთ `CHANGELOG.md` პირდაპირ; ფრაგმენტები ერთიანდება რელიზის დროს და Pull Request-ებს შორის კონფლიქტს არასდროს იწვევს - [ ] დოკუმენტაცია განახლებულია (თუ საჭიროა) -- [ ] არ არის გახსნილი CodeQL-ის / Secret-Scanning-ის ახალი გაფრთხილებები, ან თითოეული მათგანი უარყოფილია ტექნიკური დასაბუთებით, რომელიც შესაბამის `docs/security/` დოკუმენტს მიუთითებს +- [ ] არ გახსნილა CodeQL-ის / Secret-Scanning-ის ახალი გაფრთხილებები, ან თითოეული მათგანი უარყოფილია ტექნიკური დასაბუთებით, შესაბამის `docs/security/` დოკუმენტზე მითითებით - [ ] მარშრუტები, რომლებიც შვილობილ პროცესებს უშვებს (`/api/mcp/`, `/api/cli-tools/runtime/`), კლასიფიცირებულია როგორც `isLocalOnlyPath()` ფაილში `src/server/authz/routeGuard.ts` — იხილეთ [მკაცრი წესი #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] commit-ის შეტყობინებებში არ არის `Co-Authored-By` ტრეილერები — commit-ები მხოლოდ რეპოზიტორიის მფლობელის Git-ის იდენტობის ქვეშ უნდა გამოჩნდეს (მკაცრი წესი #16) +- [ ] commit-ის შეტყობინებებში არ არის AI/bot-ის `Co-authored-by` ტრეილერები (მკაცრი წესი #16) — ადამიან თანამშრომლებს, რომელთა ნამუშევარიც ხელახლა გამოიყენება, მიეთითებათ ავტორობა სტანდარტული `Co-authored-by: Name ` ტრეილერებით --- diff --git a/docs/i18n/ka/docs/architecture/QUALITY_GATES.md b/docs/i18n/ka/docs/architecture/QUALITY_GATES.md index 50cea369a9dc..574f289c6d9b 100644 --- a/docs/i18n/ka/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ka/docs/architecture/QUALITY_GATES.md @@ -19,259 +19,251 @@ ### კანდიდატის დაშვება -CI და Quality Gates სამუშაო პროცესები თითოეული გასცემს სტაბილურ ვერდიქტს: `Gate / CI` და -`Gate / Quality`. მათი ვერსირებული დაშვების პოლიტიკა თითოეულ ზედა დონის დავალებას -სავალდებულოდ ან სარეკომენდაციოდ განსაზღვრავს. შესაბამისი სავალდებულო დავალება წარმატებით უნდა დასრულდეს: -არარსებული, გაუქმებული, გამოტოვებული, მოლოდინის რეჟიმში მყოფი და უცნობი შედეგები PASS სტატუსს ვერ ადასტურებს. მხოლოდ -დოკუმენტაციასთან ან მხოლოდ კატალოგთან დაკავშირებულმა მართებულმა კლასიფიკაციამ შესაძლოა კოდის ნაკადი შეუსაბამოდ აქციოს; -შავი PR მიღებულ კანდიდატად არ ითვლება. `hotfix` ჭდე მტკიცებულების მოთხოვნას არ აუქმებს. - -ორივე სამუშაო პროცესი მოიცავს PR-ებსა და main/release განშტოებებში გაგზავნილ ცვლილებებს, ხელით გაშვებასა და -merge-group მოვლენებს. Push, dispatch და merge-group სრულ შერჩევას უშვებს. ფორკები -და merge-group-ები ჰოსტირებულ რანერებს იყენებს იმ დავალებებისთვის, რომლებიც სხვა შემთხვევაში თვითჰოსტირებულ -რანერებს აირჩევდა; დანერგვამდე უნდა შემოწმდეს ჰოსტირებული სიმძლავრის საკმარისობა. - -თითოეული JSON ქვითარი განსაზღვრავს ამოღებულ SHA-ს, სამუშაო პროცესის გაშვებასა და მცდელობას. -CLI უარყოფს checkout/event SHA შეუსაბამობას. სამუშაო პროცესის ტესტები პოლიტიკის წევრობას -ვერდიქტის დავალების `needs` სიას უკავშირებს, რათა ახალი ან წაშლილი ნაკადი შეუმჩნევლად არ გაქრეს. -ქვითრები მოიცავს საკუთარ სამუშაო პროცესს და არა გამოქვეყნებას, განთავსებას ან არსებული -სარეკომენდაციო სკანერის შიდა მუშაობას. განშტოების წესებში ორივე შემოწმების სახელის გააქტიურება -ცალკე ადმინისტრაციული ცვლილებაა; ამ დავალებების დამატება თავისთავად არ იცავს განშტოებას. +CI-ისა და Quality Gates-ის სამუშაო პროცესები თითოეული გამოსცემს სტაბილურ ვერდიქტს: `Gate / CI` და +`Gate / Quality`. მათი ვერსირებული დაშვების პოლიტიკა ყველა ზედა დონის დავალებას +სავალდებულოდ ან სარეკომენდაციოდ განსაზღვრავს. შესაბამისი სავალდებულო დავალება წარმატებით უნდა დასრულდეს: არარსებული, +გაუქმებული, გამოტოვებული, მოლოდინის რეჟიმში მყოფი და უცნობი შედეგები PASS-ს ვერ დაადასტურებს. მხოლოდ +დოკუმენტაციად ან მხოლოდ კატალოგად მართებულმა კლასიფიკაციამ შესაძლოა კოდის შესრულების ზოლი შეუსაბამოდ აქციოს; +შავი PR მიღებულ კანდიდატად არ ითვლება. `hotfix` იარლიყი მტკიცებულების მოთხოვნას არ აუქმებს. + +ორივე სამუშაო პროცესი მოიცავს PR-ებსა და push-ებს main/release განშტოებებში, ხელით გაშვებასა და +merge-group მოვლენებს. Push, dispatch და merge-group სრულ არჩევანს ასრულებს. Fork-ები +და merge group-ები ჰოსტირებულ runner-ებს იყენებს იმ დავალებებისთვის, რომლებიც სხვა შემთხვევაში self-hosted +runner-ებს აირჩევდა; გაშლამდე საკმარისი ჰოსტირებული სიმძლავრე უნდა შემოწმდეს. + +თითოეული JSON ქვითარი განსაზღვრავს checkout-ით მიღებულ SHA-ს, სამუშაო პროცესის გაშვებასა და მცდელობას. +CLI უარყოფს checkout/event SHA-ს შეუსაბამობას. სამუშაო პროცესის ტესტები პოლიტიკის წევრობას +ვერდიქტის დავალების `needs` სიასთან აკავშირებს, რათა ახალი ან წაშლილი ზოლი შეუმჩნევლად არ გაქრეს. +ქვითრები მოიცავს საკუთარ სამუშაო პროცესს და არა გამოქვეყნებას, გაშლას ან არსებული +სარეკომენდაციო სკანერის შიდა ლოგიკას. განშტოების წესებში ორივე შემოწმების სახელის გააქტიურება +ცალკე ადმინისტრაციული ცვლილებაა; ამ დავალებების დამატება თავისთავად განშტოებას არ იცავს. ### სტატიკური სკანირების ინვენტარი ვერსირებული npm-ფსევდონიმების ინვენტარი და სტატიკური სკანირების წევრობა განთავსებულია -`config/quality/gate-manifest.json`-ში. სკრიპტების სახელებისა და ზუსტი ბრძანებების -`package.json`-თან შესადარებლად გაუშვით `npm run check:gate-manifest`; დამატებები, წაშლები და -ბრძანებების გადახრა წარუმატებელს ხდის როგორც ლოკალურ ჰუკს, ისე CI-ში ცვლილებების კლასიფიკაციის დავალებებს. +`config/quality/gate-manifest.json`-ში. გაუშვით `npm run check:gate-manifest`, რათა +სკრიპტების სახელები და ზუსტი ბრძანებები `package.json`-თან მიმართებით შეამოწმოთ; დამატებები, წაშლები და +ბრძანებების აცდენა ჩავარდნას იწვევს როგორც ლოკალურ hook-ში, ისე CI-ის ცვლილებების კლასიფიკაციის დავალებებში. ფსევდონიმი არ არის სამუშაო პროცესის დავალება, მატრიცის ეგზემპლარი ან სატესტო შემთხვევა: ეს რაოდენობები ურთიერთჩანაცვლებადად არ უნდა იყოს წარმოდგენილი. -არჩეული ფსევდონიმების შესრულების გარეშე შესამოწმებლად გამოიყენეთ `npm run quality:scan -- --list` ან `npm run quality:scan:fast -- --list`. -რანერი npm-ის შესვლის წერტილს იძახებს, ამიტომ მისი შესრულების გარემო (მათ შორის Bun, სადაც კონფიგურირებულია) შენარჩუნებულია. -მანიფესტი ამ პროფილების მიღმა არსებულ ფსევდონიმებს ცალკე გამოძახებულად აღრიცხავს, ხოლო +არჩეული ფსევდონიმების შესრულების გარეშე სანახავად გამოიყენეთ `npm run quality:scan -- --list` ან `npm run quality:scan:fast -- --list`. +runner-ი npm-ის შესვლის წერტილს იძახებს, ამიტომ მისი შესრულების გარემო (მათ შორის, სადაც კონფიგურირებულია, Bun) შენარჩუნებულია. +მანიფესტი ამ პროფილების გარეთ არსებულ ფსევდონიმებს ცალკე გამოძახებულად აღრიცხავს, ხოლო მომსახურების ბრძანებები მხოლოდ-წაკითხვის სკანირების პროფილებში აკრძალულია. ეს პროფილები მხოლოდ სტატიკურ სკანირებას მოიცავს. ისინი არ ადასტურებს პროდუქტის ტესტებს, -დაფარვას, პაკეტირებას, გარე შემოწმებებს ან კანდიდატის რელიზისთვის სრულ დაშვებას. +დაფარვას, შეფუთვას, გარე შემოწმებებს ან კანდიდატის სრულ შესაბამისობას რელიზის მოთხოვნებთან. სამუშაო პროცესში დაშვება იყენებს დაკავშირებულ `config/quality/admission-policy.json`-სა და -`scripts/quality/admission-verdict.mjs`-ს. Release-observer პროფილები განცალკევებული რჩება; +`scripts/quality/admission-verdict.mjs`-ს. Release-observer-ის პროფილები განცალკევებული რჩება; მათი შესაბამისი შემოწმებები და ქვითრები დამოუკიდებლად შეამოწმეთ. ქვემოთ მოცემული ტექსტური ინვენტარი საცნობაროა და არა იმის მტკიცებულება, რომ გეითი რეალურად გაეშვა. -სკრიპტები განთავსებულია `scripts/check/`-ში (პოლიტიკის გეითები) და `scripts/quality/`-ში (ratchet ძრავა). +სკრიპტები განთავსებულია `scripts/check/`-ში (პოლიტიკის გეითები) და `scripts/quality/`-ში (ratchet-ის ძრავა). CI-ის ჭეშმარიტების წყაროა `.github/workflows/ci.yml`. ### Release PR-ის სწრაფი გზა (`quality.yml`) -`.github/workflows/quality.yml` ავსებს CI-ს main/release PR-ებზე, დაცულ განშტოებებში -გაგზავნილ ცვლილებებზე, dispatch-სა და merge-group-ებზე. PR-ები იყენებს ბილიკებით გაფილტრულ სწრაფ შემოწმებებს. მუდმივად -გამორთული დუბლირებული აგება წაიშალა; რეალური აგების/პაკეტირების/ჩატვირთვის შემოწმებები CI-ში რჩება. +`.github/workflows/quality.yml` ავსებს CI-ს main/release PR-ებისთვის, დაცულ განშტოებებში +push-ებისთვის, dispatch-ისა და merge group-ებისთვის. PR-ები გზების მიხედვით გაფილტრულ სწრაფ შემოწმებებს იყენებს. მუდმივად +გამორთული დუბლირებული build წაიშალა; რეალური build/package/boot შემოწმებები CI-ში რჩება. -| დავალება | ფარგლები | ბლოკირებადი | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | -| `Docs Gates (fast-path)` | დოკუმენტაციის/კოდის PR-ები; API დოკუმენტაციის ბმულები და სრულად დოკუმენტაცია | დიახ | -| `Fast Quality Gates` | კოდის PR-ები; სტატიკური შემოწმებები, ტიპების შემოწმება, დაფის ტიპების შემოწმება, ზემოქმედებული მოდულური ტესტები | დიახ | -| `Forgotten sibling tests` | კოდის PR-ები; შეცვლილი მოდულების მიკვლევა სტატიკურ მომხმარებლებსა და შესაძლო მონათესავე ტესტებამდე; barrel და dynamic-import ბილიკები წარმოდგენილია სარეკომენდაციო დიაგნოსტიკის სახით, მითითებული დაშვების სიის გამონაკლისებით | **სარეკომენდაციო** | -| `Vitest (fast-path)` | კოდის PR-ები; სწრაფი vitest ნაკრები | დიახ | -| `Unit Tests fast-path` | კოდის PR-ები; 4-შარდიანი მოდულური ტესტების ნაკრები | დიახ | -| `No new ESLint warnings` | კოდის PR-ები; გამონაკლისების გათვალისწინების მქონე lint დამცავი | დიახ, ფორკების ჩათვლით | -| `Merge integrity (changelog + generated skills)` | არაშავი PR-ები; ცვლილებების ჟურნალისა და გენერირებული უნარების სინქრონიზაცია | დიახ, ფორკების ჩათვლით | +| დავალება | მოქმედების არეალი | დამბლოკავი | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `Docs Gates (fast-path)` | დოკუმენტაციის/კოდის PR-ები; API დოკუმენტაციის მითითებები და სრული დოკუმენტაცია | დიახ | +| `Fast Quality Gates` | კოდის PR-ები; სტატიკური შემოწმებები, ტიპების შემოწმება, dashboard-ის ტიპების შემოწმება, ზემოქმედების ქვეშ მყოფი მოდულური ტესტები | დიახ | +| `Forgotten sibling tests` | კოდის PR-ები; შეცვლილი მოდულების კვალის მიდევნება სტატიკურ მომხმარებლებსა და კანდიდატ მონათესავე ტესტებამდე; barrel-ისა და dynamic-import-ის გზები სარეკომენდაციო დიაგნოსტიკად ანგარიშდება, შესაბამისი allowlist გამონაკლისებით | **სარეკომენდაციო** | +| `Vitest (fast-path)` | კოდის PR-ები; სწრაფი vitest კომპლექტი | დიახ | +| `Unit Tests fast-path` | კოდის PR-ები; მოდულური ტესტების 4-shard-იანი კომპლექტი | დიახ | +| `No new ESLint warnings` | კოდის PR-ები; ჩახშობების გათვალისწინებით მოქმედი lint-ის დამცავი | დიახ, fork-ების ჩათვლით | +| `Merge integrity (changelog + generated skills)` | არაშავი PR-ები; changelog-ისა და გენერირებული უნარების სინქრონიზაცია | დიახ, fork-ების ჩათვლით | #### დავიწყებული მონათესავე ტესტების ანგარიში -`npm run check:forgotten-sibling-tests` ხელახლა იყენებს ტესტებზე ზემოქმედების რუკის საფუძვლად არსებულ იმპორტების გადამჭრელს. -თითოეული შეცვლილი საწარმოო მოდულისთვის ის დეტერმინისტულ -`changed module/symbol -> static consumer -> candidate sibling test` ჯაჭვებს აჩვენებს, როდესაც კანდიდატი -ტესტი pull-request-ის სხვაობაში არ არის. Markdown შეჯამება და JSON შედეგი ინახება, როგორც -`forgotten-sibling-tests` სამუშაო პროცესის არტეფაქტი, ნებისმიერი ბლოკირებადი დანერგვის წინ კალიბრაციისთვის. +`npm run check:forgotten-sibling-tests` ხელახლა იყენებს ტესტებზე ზემოქმედების რუკის საფუძვლად არსებულ import-ის resolver-ს. +ყოველი შეცვლილი საწარმოო მოდულისთვის ის დეტერმინისტულ +`changed module/symbol -> static consumer -> candidate sibling test` ჯაჭვებს აჩვენებს, როცა კანდიდატი +ტესტი pull-request-ის diff-ში არ არის. Markdown-ის შეჯამება და JSON შედეგი ინახება როგორც +`forgotten-sibling-tests` სამუშაო პროცესის არტეფაქტი, ნებისმიერი დამბლოკავი დანერგვის წინ კალიბრაციისთვის. -Barrel re-export-ები და დინამიკური import-ები მხოლოდ resolution-ის დიაგნოსტიკისთვის გამოიყენება; ისინი არასოდეს ქმნიან -დამბლოკავ დარღვევას. განხილული გამონაკლისები ინახება -`config/quality/forgotten-sibling-allowlist.json`-ში. თითოეულ ჩანაწერში მითითებული უნდა იყოს consumer და candidate -ტესტები, წარმოდგენილი უნდა იყოს კონკრეტული დასაბუთება და GitHub issue-ის ან pull request-ის ბმული. არასწორად ფორმირებული ჩანაწერები -უარყოფის პრინციპით მუშავდება. გამონაკლისები ვერ დაფარავს წაშლილ candidate ტესტს ან diff-ს, რომელიც ამატებს `.skip`/`.todo`-ს; -assertion-ის შესუსტება და სხვა სახის დაფარვა კვლავ დამოუკიდებლად დამბლოკავი -`check:test-masking` gate-ის პასუხისმგებლობაში რჩება. +Barrel-რეექსპორტები და დინამიკური იმპორტები მხოლოდ გარჩევადობის დიაგნოსტიკას ემსახურება; ისინი არასოდეს წარმოქმნის +დამბლოკავ მიგნებას. განხილული გამონაკლისები ინახება +`config/quality/forgotten-sibling-allowlist.json`-ში. თითოეულ ჩანაწერში მითითებული უნდა იყოს მომხმარებელი და კანდიდატი +ტესტი, მოცემული უნდა იყოს კონკრეტული დასაბუთება და GitHub-ის issue-ის ან pull request-ის ბმული. არასწორად შედგენილი ჩანაწერები +ნაგულისხმევად უარყოფილია. გამონაკლისები ვერ დაფარავს წაშლილ კანდიდატ ტესტს ან diff-ს, რომელიც ამატებს `.skip`/`.todo`-ს; +assertion-ის შესუსტება და სხვა სახის შენიღბვა კვლავ დამოუკიდებლად დამბლოკავი +`check:test-masking` კარიბჭის პასუხისმგებლობაში რჩება. ### დავალება: `lint` -ეშვება `main`-ზე მიმართულ ყოველ PR-ზე. წარუმატებლობის შემთხვევაში merge-ს ბლოკავს. - -| სკრიპტი (`npm run ...`) | რას ამოწმებს | დამბლოკავი | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | -| `check:node-runtime` | Node.js-ის ვერსია მხარდაჭერილ დიაპაზონშია | დიახ | -| `check:cycles` | ციკლური import-ები — ყველა `src/` + `open-sse/` მოდული | დიახ | -| `check:route-validation:t06` | Zod-ის სქემები წარმოდგენილია ყველა route-ზე (Tier 6-ის პოლიტიკა) | დიახ | -| `check:any-budget:t11` | `@ts-expect-error // any`-ის რაოდენობა არ აღემატება ბიუჯეტს (Tier 11 catraca) | დიახ | -| `check:provider-consistency` | `providers.ts`-ში თითოეულ პროვაიდერს აქვს შესაბამისი ჩანაწერი `providerRegistry.ts`-ში (და პირიქით, დაშვებული სიის ფარგლებში) | დიახ | -| `check:model-lifecycle` | ხელით მართული სამი მარშრუტიზაციის ცხრილი შესაბამისობაში რჩება რეპოზიტორიაში შენახულ სასიცოცხლო ციკლის სურათთან (#11503): `FITNESS_TABLE` (`taskFitness.ts`) არ ანიჭებს ქულას არცერთ გაუქმებულ id-ს, რომლის მარშრუტიზაციაც `REGISTRY`-ს შეუძლია; `BUILT_IN_ALIASES`-ის თითოეული სამიზნე წარმოდგენილია `REGISTRY`-ში და არ არის გაუქმებული id-ების სურათში; `REGISTRY`-ში ჯერ კიდევ არსებული თითოეული გაუქმებული id გადამისამართებულია ან ჩამოთვლილია `allowedRetiredInCatalog`-ში; და `DEFAULT_DEGRADATION_MAP`-ის არცერთი წყარო ან სამიზნე ამ სურათში გაუქმებულად არ არის მონიშნული. ეს არ ადასტურებს, რომ მოდელს ამჟამად მოქმედი ზედა დონის სერვისი ემსახურება. ოფლაინ რეჟიმში — ადარებს `config/quality/model-lifecycle.json`-ს, რომელიც ხელით ახლდება `npm run quality:refresh-model-lifecycle`-ის მეშვეობით (საჭიროებს ქსელს; CI-ში არ არის ჩართული). `allowedRetiredInCatalog` არის თანდათანობითი შემცირების მექანიზმი: ჩანაწერი დაამატეთ მხოლოდ თვალთვალის საკითხთან ერთად. | დიახ | -| `check:fetch-targets` | კლიენტის მხარის `src/`-ში არსებული თითოეული `fetch("/api/...")` რეალურ `route.ts`-ზე მიუთითებს | დიახ | -| `check:deps` | რეპოზიტორიის თითოეულ `package.json`-ში არსებული ყველა `npm install`-ით ინსტალირებადი დამოკიდებულება შეტანილია `dependency-allowlist.json`-ში; ახალი არაფიქსირებული ვერსიის ან slopsquatting-ის შედეგად წარმოქმნილი პაკეტები მოინიშნება | დიახ | -| `audit:deps` | `npm audit` (ძირითადი პროექტი + electron) — მაღალი/კრიტიკული დონის არცერთი გაფრთხილება (ნაწილობრივ ემთხვევა osv-ის `check:vuln-ratchet`-ს; იხილეთ რაციონალიზაციის ბექლოგი) | დიახ | -| `check:lockfile` | `package-lock.json`-ის მთლიანობა — https რეესტრი, მთლიანობის ჰეშები, ჰოსტის გადაფარვების გარეშე | დიახ | -| `check:licenses` | SPDX ლიცენზიების ნებადართული სია production dependencies-ისთვის | დიახ | -| `check:tracked-artifacts` | build artifacts / commit-ში დამატებული `node_modules` symlinks არ არის (ასევე ეშვება husky pre-commit-ში; pre-push განზრახ მსუბუქია — #6716) | დიახ | -| `check:ai-attribution` | PR commit-ებში, სათაურში ან ტექსტში არ არის AI/ბოტის `Co-Authored-By` trailer ან AI-ის მიერ გენერირების footer — მკაცრი წესი #16 (`quality.yml`-ის fast-gates ციკლში PR→`release/**`-ისთვის — კითხულობს event payload-ს, PR-ების გარეთ არაფერს აკეთებს — და მხოლოდ PR-ისთვის განკუთვნილი ნაბიჯი `ci.yml` lint-ში PR→`main`-ისთვის; ასევე husky `commit-msg` hook; ადამიანის თანაავტორები დაშვებულია; #14436) | -| `check:vitest-exclusions` | Vitest-ის თითოეული გამონაკლისი მიუთითებს tracking issue-ს და წარმოდგენილია `config/quality/vitest-exclusions.json`-ში (#13204) | დიახ | -| `check:file-size` | არცერთი source file არ აჭარბებს შესაბამისი გაფართოებისთვის დაწესებულ ზღვარს (ratchet: გაყინული დიდი ფაილები `frozen` სიაში) | დიახ | -| `check:error-helper` | executors/handlers-ში შეცდომის პასუხები იყენებს `buildErrorBody()` / `sanitizeErrorMessage()`-ს (მკაცრი წესი #12) | დიახ | -| `check:migration-numbering` | მიგრაციის SQL ფაილები დანომრილია თანმიმდევრულად, გამოტოვებებისა და დუბლიკატების გარეშე | დიახ | -| `check:public-creds` | `publicCreds.ts`-ის გარეთ არ არის OAuth-ის ლიტერალური `client_id`/`client_secret` ან Firebase Web გასაღებები (მკაცრი წესი #11) | დიახ | -| `check:db-rules` | `src/lib/db/` მოდულების გარეთ არ არის დაუმუშავებელი SQL; `localDb.ts`-იდან არ არის barrel-იმპორტები (მკაცრი წესები #2/#5) | დიახ | -| `check:known-symbols` | პროვაიდერის შემსრულებლები, მარშრუტიზაციის სტრატეგიები და მთარგმნელები, რომლებიც რეგისტრირებულია შესაბამის დისპეტჩერიზაციის ცხრილებში, ემთხვევა დისკზე არსებულ ფაილებს — არ არის ობოლი ან გამოუცხადებელი სიმბოლოები | დიახ | -| `check:route-guard-membership` | ყველა მარშრუტი, რომელიც შვილობილ პროცესს უშვებს, კლასიფიცირებულია `isLocalOnlyPath()`-ის მიერ (მკაცრი წესები #15/#17) | დიახ | -| `check:test-discovery` | რეპოზიტორიაში არსებული ყველა `*.test.ts` / `*.spec.ts` ფაილი შეგროვებულია სულ მცირე ერთი ტესტების გამშვების მიერ (ratchet: `test-discovery-baseline.json`-ში ობოლი ფაილების სია მხოლოდ შეიძლება შემცირდეს) | დიახ | -| `check:agent-skills-sync` | გენერირებული agent-skills არტეფაქტები ემთხვევა მათ წყარო კატალოგს (გადახრის გარეშე) | -| `check:provider-asset-provenance` | პროვაიდერის ლოგოებს/აქტივებს ახლავს წარმომავლობის რეგისტრირებული ჩანაწერი | -| `lint:json` | JSON კონფიგურაციის ფაილები იშლება შეცდომების გარეშე და აკმაყოფილებს რეპოზიტორიის ლინტინგის წესებს | -| `typecheck:core` | TypeScript-ის კომპილაცია შეცდომების გარეშე (მხოლოდ სარეკომენდაციო გაფრთხილებები) | დიახ | -| `typecheck:noimplicit:core` | მკაცრი `noImplicitAny` — სამომავლო მოთხოვნა; ბევრ მანამდე არსებულ გამოძახების ადგილს ჯერ კიდევ სჭირდება ანოტაციები | **სარეკომენდაციო** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, რომელიც შემოიფარგლება `src/app/(dashboard)/**`-ით (#7033) — `typecheck:core`-ის 27 ფაილისგან შემდგარი შერჩევითი დაშვების სია არ შეიცავს dashboard-ის არცერთ TSX ფაილს, ხოლო `next build` მას საერთოდ არ უტარებს ტიპების შემოწმებას (`next.config.mjs`-ში დაყენებულია `ignoreBuildErrors: true`), ამიტომ იქ არსებული უპატრონო იდენტიფიკატორების რეგრესიები (#6625/#6909) CI-სთვის შეუმჩნეველი იყო. განსხვავებები მოწმდება თითოეულ ფაილსა და TS კოდზე შეცდომების რაოდენობის გაყინულ საბაზისო მაჩვენებელთან (`config/quality/dashboard-typecheck-baseline.json`, მოძველების კონტროლის იგივე სქემით, რომელსაც `check:known-symbols` იყენებს) — შემოწმება ჩავარდება მხოლოდ მაშინ, როდესაც შეცდომების რაოდენობა საბაზისო მაჩვენებელს გადააჭარბებს; მანამდე არსებული შეცდომის გამოსწორებისას ზღვარი შეამცირეთ `--update`-ით. | დიახ | +ეშვება `main`-ში მიმართული ყოველი PR-ისთვის. წარუმატებლობის შემთხვევაში merge-ს ბლოკავს. + +| სკრიპტი (`npm run ...`) | რას ამოწმებს | დამბლოკავი | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:node-runtime` | Node.js-ის ვერსია მხარდაჭერილ დიაპაზონშია | დიახ | +| `check:cycles` | ციკლური იმპორტები მთლიან `src/` + `open-sse/`-ში (AST-ზე დაფუძნებული, tsconfig-ის `paths` გათვალისწინებულია). ცალკე გაშვება მხოლოდ სარეკომენდაციოა და ციკლებს ჩამოთვლის. `check:cycles:ratchet` (რომელსაც CI უშვებს) ბლოკავს, როდესაც რაოდენობა `quality-baseline.json`-ში განსაზღვრულ `metrics.cycles` ზღვარს აჭარბებს — ამჟამად 14, `direction: down`, ამიტომ ის მხოლოდ შეიძლება შემცირდეს (#15159 G-01/G-02) | დიახ (ratchet) | +| `check:route-validation:t06` | Zod-ის სქემები წარმოდგენილია ყველა route-ზე (Tier 6 პოლიტიკა) | დიახ | +| `check:any-budget:t11` | `@ts-expect-error // any`-ის რაოდენობა ბიუჯეტს არ აღემატება (Tier 11 catraca) | დიახ | +| `check:provider-consistency` | `providers.ts`-ში თითოეულ პროვაიდერს აქვს შესაბამისი ჩანაწერი `providerRegistry.ts`-ში (და პირიქით, დაშვებული სიის ფარგლებში) | დიახ | +| `check:model-lifecycle` | ხელით შენარჩუნებული მარშრუტიზაციის სამი ცხრილი შესაბამისობაში რჩება რეპოზიტორიაში დაფიქსირებულ სასიცოცხლო ციკლის სნეპშოტთან (#11503): `FITNESS_TABLE` (`taskFitness.ts`) არ ანიჭებს ქულას არცერთ გაუქმებულ id-ს, რომლის მარშრუტიზაციაც `REGISTRY`-ს შეუძლია; `BUILT_IN_ALIASES`-ის თითოეული სამიზნე წარმოდგენილია `REGISTRY`-ში და არ არის გაუქმებული id-ების სნეპშოტში; `REGISTRY`-ში ჯერ კიდევ არსებული თითოეული გაუქმებული id გადამისამართებულია ან შეტანილია `allowedRetiredInCatalog`-ში; და `DEFAULT_DEGRADATION_MAP`-ის არცერთი წყარო ან სამიზნე ამ სნეპშოტში გაუქმებულად არ არის მონიშნული. ეს არ ამტკიცებს, რომ მოდელს ამჟამად ცოცხალი ზედა დონის სერვისი ემსახურება. ოფლაინ — შედარება ხდება `config/quality/model-lifecycle.json`-თან, რომელიც ხელით ახლდება `npm run quality:refresh-model-lifecycle`-ის გამოყენებით (საჭიროებს ქსელს; CI-ში ინტეგრირებული არ არის). `allowedRetiredInCatalog` არის ეტაპობრივი შემცირების მექანიზმი: ჩანაწერი დაამატეთ მხოლოდ თვალყურის სადევნებელ საკითხთან ერთად. | დიახ | +| `check:fetch-targets` | კლიენტის მხარის `src/`-ში თითოეული `fetch("/api/...")` რეალურ `route.ts`-ზე მიუთითებს | დიახ | +| `check:deps` | რეპოზიტორიაში არსებულ თითოეულ `package.json`-ში ყველა `npm install`-ით დასაყენებელი დამოკიდებულება შეტანილია `dependency-allowlist.json`-ში; ახალი დაუფიქსირებელი ვერსიის ან slopsquatting-ის ნიშნების მქონე პაკეტები მონიშნულია | დიახ | +| `audit:deps` | `npm audit` (ძირეული პროექტი + electron) — მაღალი/კრიტიკული დონის არცერთი გაფრთხილება (ნაწილობრივ ფარავს osv-ის `check:vuln-ratchet`-ს; იხილეთ რაციონალიზაციის დაგროვილი სია) | დიახ | +| `check:lockfile` | `package-lock.json`-ის მთლიანობა — https რეესტრი, მთლიანობის ჰეშები, ჰოსტის ჩამნაცვლებლების გარეშე | დიახ | +| `check:licenses` | საწარმოო დამოკიდებულებებისთვის დაშვებული SPDX ლიცენზიების სია | დიახ | +| `check:tracked-artifacts` | არ არის აგების არტეფაქტები / რეპოზიტორიაში დაფიქსირებული `node_modules` სიმბოლური ბმულები (ასევე სრულდება husky-ის pre-commit ჰუკში; pre-push განზრახ მსუბუქია — #6716) | დიახ | +| `check:ai-attribution` | PR-ის კომიტებში, სათაურში ან აღწერაში არ არის AI/ბოტის `Co-Authored-By` ტრეილერი ან AI-გენერაციის ქვედა კოლონტიტული — მკაცრი წესი #16 (`quality.yml`-ის სწრაფი შემოწმებების ციკლში PR→`release/**`-ისთვის — კითხულობს მოვლენის მონაცემებს, ხოლო PR-ების გარეთ არაფერს აკეთებს — და მხოლოდ PR-ისთვის განკუთვნილ ნაბიჯში `ci.yml`-ის lint შემოწმებაში PR→`main`-ისთვის; ასევე husky-ის `commit-msg` ჰუკში; ადამიან თანაავტორები დაშვებულია; #14436) | +| `check:vitest-exclusions` | Vitest-ის თითოეული გამონაკლისი მიუთითებს თვალყურის სადევნებელ issue-ზე და მოცემულია `config/quality/vitest-exclusions.json`-ში (#13204) | დიახ | +| `check:file-size` | არცერთი საწყისი ფაილი არ აჭარბებს გაფართოების მიხედვით განსაზღვრულ ზღვარს (რეჩეტი: დიდი გაყინული ფაილები `frozen` სიაშია) | დიახ | +| `check:error-helper` | executors/handlers-ში შეცდომის პასუხები იყენებს `buildErrorBody()` / `sanitizeErrorMessage()`-ს (მკაცრი წესი #12) | დიახ | +| `check:migration-numbering` | მიგრაციის SQL ფაილები დანომრილია თანმიმდევრულად, გამოტოვებებისა და დუბლიკატების გარეშე | დიახ | +| `check:public-creds` | `publicCreds.ts`-ის გარეთ არ არის OAuth-ის პირდაპირ მითითებული `client_id`/`client_secret` ან Firebase Web-ის გასაღებები (მკაცრი წესი #11) | დიახ | +| `check:db-rules` | `src/lib/db/` მოდულების გარეთ არ არის დაუმუშავებელი SQL; `localDb.ts`-დან არ გამოიყენება barrel-იმპორტები (მკაცრი წესები #2/#5) | დიახ | +| `check:known-symbols` | პროვაიდერის შემსრულებლები, მარშრუტიზაციის სტრატეგიები და მთარგმნელები, რომლებიც რეგისტრირებულია შესაბამის დისპეტჩერიზაციის ცხრილებში, ემთხვევა დისკზე არსებულ ფაილებს — არ არის უპატრონო ან გამოუცხადებელი სიმბოლოები | დიახ | +| `check:route-guard-membership` | ყველა მარშრუტი, რომელიც შვილობილ პროცესს ქმნის, კლასიფიცირებულია `isLocalOnlyPath()`-ის მიერ (მკაცრი წესები #15/#17) | დიახ | +| `check:test-discovery` | რეპოზიტორიაში არსებული ყველა `*.test.ts` / `*.spec.ts` ფაილი გროვდება სულ მცირე ერთი ტესტების გამშვების მიერ (ratchet: `test-discovery-baseline.json`-ში არსებული უპატრონო ფაილების სია შეიძლება მხოლოდ შემცირდეს) | დიახ | +| `check:agent-skills-sync` | გენერირებული agent-skills არტეფაქტები შეესაბამება მათ საწყის კატალოგს (განსხვავებების გარეშე) | +| `check:provider-asset-provenance` | პროვაიდერის ლოგოებს/რესურსებს ახლავს ჩაწერილი წარმომავლობის ჩანაწერი | +| `lint:json` | JSON კონფიგურაციის ფაილები იშლება შეცდომის გარეშე და აკმაყოფილებს რეპოზიტორიის ლინტინგის წესებს | +| `typecheck:core` | TypeScript-ის კომპილაცია შეცდომების გარეშე (მხოლოდ სარეკომენდაციო გაფრთხილებები) | დიახ | +| `typecheck:noimplicit:core` | მკაცრი `noImplicitAny` — სამომავლო მოთხოვნა; ბევრ უკვე არსებულ გამოძახების ადგილს კვლავ სჭირდება ანოტაციები | **სარეკომენდაციო** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, რომლის არე შემოიფარგლება `src/app/(dashboard)/**`-ით (#7033) — `typecheck:core`-ის საგანგებოდ შერჩეული 27-ფაილიანი დასაშვები სია არ შეიცავს dashboard-ის არცერთ TSX ფაილს, ხოლო `next build` ასევე არასოდეს ახორციელებს მის ტიპების შემოწმებას (`next.config.mjs`-ში დაყენებულია `ignoreBuildErrors: true`), ამიტომ იქ არსებული მიუწვდომელი იდენტიფიკატორების რეგრესიები (#6625/#6909) CI-სთვის შეუმჩნეველი იყო. განსხვავებები მოწმდება თითოეული ფაილისა და თითოეული TS-კოდის რაოდენობის გაყინულ საბაზისო მაჩვენებელთან (`config/quality/dashboard-typecheck-baseline.json`, მოძველების კონტროლის იგივე სქემა, რაც `check:known-symbols`-ში) — ბარიერს ვერ გადალახავს მხოლოდ საბაზისო რაოდენობას ზემოთ არსებული ახალი შეცდომები; როდესაც უკვე არსებული შეცდომა გამოსწორდება, საბაზისო მაჩვენებელი შეამცირეთ `--update`-ით. | დიახ | ### დავალება: `quality-gate` -ეშვება `test-coverage`-ის შემდეგ. წარუმატებლობის შემთხვევაში ბლოკავს შერწყმას. +ეშვება `test-coverage`-ის შემდეგ. წარუმატებლობის შემთხვევაში შერწყმას ბლოკავს. | სკრიპტი | რას ამოწმებს | ბლოკირებადი | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `quality:collect` | ქმნის `quality-metrics.json`-ს (ESLint-ის გაფრთხილებების რაოდენობა, დაფარვა გაერთიანებული shard-ანგარიშიდან) | დიახ (ratchet-მდე) | -| `quality:ratchet` | `quality-baseline.json`-ში თითოეული მეტრიკა არ გაუარესებულა (ESLint-ის გაფრთხილებები ≤ baseline; დაფარვა ≥ baseline) | დიახ | -| `check:duplication` | კოდის დუბლირება (jscpd@4) არ აღემატება `quality-baseline.json`-ში მითითებულ baseline-ს | დიახ | -| `check:complexity` | ფაილის დონის ციკლომატური სირთულე არ აღემატება ზღვარს (ძირითადი ESLint `complexity` + `max-lines-per-function`) | დიახ | -| `check:cognitive-complexity` | კოგნიტიური სირთულის ratchet (`eslint-plugin-sonarjs`) — ESLint-ის ცალკე გაშვება; CI ორივეს აერთიანებს ერთ `check:complexity-ratchets` ნაბიჯად | დიახ | -| `check:dead-code` | გამოუყენებელი ექსპორტების / ფაილების ratchet (knip) baseline-თან შედარებით არ უარესდება | დიახ | -| `check:compression-budget` | შეკუმშვის საორიენტაციო ტესტის ბიუჯეტი — თითოეული ძრავისთვის ტოკენების დაზოგვის მინიმალური ზღვრები არ უნდა გაუარესდეს | დიახ | -| `check:type-coverage` | ტიპიზაციის პროცენტული მაჩვენებლის ratchet (`type-coverage`) არ უარესდება; დიდწილად მოიცავს `typecheck:noimplicit:core`-ს | დიახ | -| `check:codeql-ratchet` | ღია CodeQL გაფრთხილებების რაოდენობა არ უარესდება (კითხულობს `gh api`-ის მეშვეობით; ტოკენის გარეშე კორექტულად გამოტოვებს) — განახლების სიხშირე და ხელით გაშვება: იხილეთ „CodeQL ratchet“ ქვემოთ | დიახ | +| `quality:collect` | ქმნის `quality-metrics.json`-ს (ESLint-ის გაფრთხილებების რაოდენობა, გაერთიანებული შარდების ანგარიშიდან მიღებული დაფარვა) | დიახ (ratchet-მდე) | +| `quality:ratchet` | `quality-baseline.json`-ში თითოეული მეტრიკა არ გაუარესებულა (ESLint-ის გაფრთხილებები ≤ საბაზისო მაჩვენებელი; დაფარვა ≥ საბაზისო მაჩვენებელი) | დიახ | +| `check:duplication` | კოდის დუბლირება (jscpd@4) არ აჭარბებს `quality-baseline.json`-ში განსაზღვრულ საბაზისო მაჩვენებელს | დიახ | +| `check:complexity` | ფაილის დონის ციკლომატური სირთულე არ აჭარბებს ზღვარს (ძირითადი ESLint-ის `complexity` + `max-lines-per-function`) | დიახ | +| `check:cognitive-complexity` | კოგნიტიური სირთულის ratchet (`eslint-plugin-sonarjs`) — ESLint-ის ცალკე გაშვება; CI ორივეს აერთიანებს ერთ `check:complexity-ratchets` ეტაპად | დიახ | +| `check:dead-code` | გამოუყენებელი ექსპორტების / ფაილების ratchet (knip) არ უარესდება საბაზისო მაჩვენებელთან შედარებით | დიახ | +| `check:compression-budget` | შეკუმშვის საორიენტაციო ტესტის ბიუჯეტი — თითოეული ძრავისთვის ტოკენების დაზოგვის მინიმალური ზღვარი არ უნდა გაუარესდეს | დიახ | +| `check:type-coverage` | ტიპიზაციის პროცენტული მაჩვენებლის ratchet (`type-coverage`) არ უარესდება; მეტწილად მოიცავს `typecheck:noimplicit:core`-ს | დიახ | +| `check:codeql-ratchet` | ღია CodeQL გაფრთხილებების რაოდენობა არ უარესდება (იკითხება `gh api`-ის მეშვეობით; ტოკენის გარეშე კორექტულად გამოტოვდება) — განახლების სიხშირე და ხელით გაშვება: იხილეთ ქვემოთ „CodeQL ratchet“ | დიახ | ### დავალება: `quality-extended` -მთელი დავალება სარეკომენდაციო ხასიათისაა (`continue-on-error: true`). npm-ზე დაფუძნებული ratchet-ები -რეალურად სრულდება; გარე სკანერები ინსტალირდება `gh release download`-ის მეშვეობით და თავად გამოტოვებენ შემოწმებას (exit 0), -თუ ბინარული ფაილი კვლავ არ არის ხელმისაწვდომი. +მთელი დავალება სარეკომენდაციოა (`continue-on-error: true`). npm-ზე დაფუძნებული ratchet-ები +რეალურად სრულდება; გარე სკანერები ინსტალირდება `gh release download`-ის მეშვეობით და +თავად გამოტოვებენ შემოწმებას (exit 0), თუ ბინარული ფაილი კვლავ არ არსებობს. -| სკრიპტი | რას ამოწმებს | ბლოკირებადი | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| `check:circular-deps` | ციკლური დამოკიდებულებები არ არსებობს (dpdm) | **სარეკომენდაციო** | -| `check:bundle-size` | bundle-ის ზომა არ აღემატება ზღვარს | **სარეკომენდაციო** | -| `check:secrets` | საიდუმლო მონაცემების სკანირება (gitleaks) — გამოტოვებს, თუ ბინარული ფაილი არ არის ხელმისაწვდომი | **სარეკომენდაციო** | -| `check:vuln-ratchet` | დამოკიდებულებების მოწყვლადობები (osv-scanner) არ უარესდება — გამოტოვებს, თუ ბინარული ფაილი არ არის ხელმისაწვდომი | **სარეკომენდაციო** | -| `check:workflows` | workflow-ების lint-შემოწმება (actionlint + zizmor); არარსებული/გაუმართავი სკანერები, არასწორი ანგარიშები ან არარსებული ratchet baseline იწვევს INCOMPLETE სტატუსით ჩავარდნას. ვალიდური მიგნებები ემორჩილება არჩეულ მკაცრ/სარეკომენდაციო/ratchet პოლიტიკას | შესრულება სავალდებულოა; zizmor ratchet ბლოკირებადია CI-ში | -| `check:openapi-breaking` | საჯარო API კონტრაქტში (`openapi.yaml`) base branch-თან შედარებით შეუთავსებელი ცვლილებები (oasdiff) — ქმნის `openapiBreaking=N`-ს; გამოტოვებს, თუ oasdiff არ არის ხელმისაწვდომი ან საბაზისო სპეციფიკაციის დამუშავება შეუძლებელია | **სარეკომენდაციო** | +| სკრიპტი | რას ამოწმებს | ბლოკირებადი | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| `check:circular-deps` | ციკლური დამოკიდებულებები არ არსებობს (dpdm) | **სარეკომენდაციო** | +| `check:bundle-size` | პაკეტის ზომა არ აჭარბებს ზღვარს | **სარეკომენდაციო** | +| `check:secrets` | საიდუმლო მონაცემების სკანირება (gitleaks) — გამოტოვდება, თუ ბინარული ფაილი არ არსებობს | **სარეკომენდაციო** | +| `check:vuln-ratchet` | დამოკიდებულებების მოწყვლადობები (osv-scanner) არ უარესდება — გამოტოვდება, თუ ბინარული ფაილი არ არსებობს | **სარეკომენდაციო** | +| `check:workflows` | სამუშაო პროცესების ლინტინგი (actionlint + zizmor); არარსებული/გაუმართავი სკანერები, არასწორი ანგარიშები ან ratchet-ის არარსებული საბაზისო მაჩვენებელი იწვევს INCOMPLETE სტატუსით ჩავარდნას. ვალიდური მიგნებები მიჰყვება არჩეულ მკაცრ/სარეკომენდაციო/ratchet პოლიტიკას | შესრულება სავალდებულოა; zizmor-ის ratchet ბლოკირებადია CI-ში | +| `check:openapi-breaking` | საჯარო API კონტრაქტში (`openapi.yaml`) საბაზისო განშტოებასთან შედარებით შეუთავსებელი ცვლილებები (oasdiff) — ქმნის `openapiBreaking=N`-ს; გამოტოვდება, თუ oasdiff არ არსებობს ან საბაზისო სპეციფიკაციის დამუშავება შეუძლებელია | **სარეკომენდაციო** | ### დავალება: `docs-sync-strict` -სრულდება `main`-ისკენ მიმართულ ყველა PR-ზე. წარუმატებლობის შემთხვევაში merge-ს ბლოკავს. - -| სკრიპტი | რას ამოწმებს | ბლოკირებადი | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `check:docs-all` | მეტა-ბარიერი, რომელიც ქვემოთ მოცემულ 6 ქვე-ბარიერს თანმიმდევრულად უშვებს | დიახ | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ვერსიების თანხვედრას | დიახ | -| ↳ `check:docs-counts` | პროზაულ ტექსტში მოცემული რაოდენობები (პროვაიდერების რაოდენობა, მიგრაციების რაოდენობა და ა.შ.) რეალური რაოდენობების მკაცრ დიაპაზონშია | დიახ | -| ↳ `check:env-doc-sync` | `.env.example`-ში არსებული ყოველი გარემოს ცვლადი დოკუმენტაციის ცხრილშია აღწერილი და პირიქით | დიახ | -| ↳ `check:deprecated-versions` | დოკუმენტაციაში მოძველებული ვერსიების აღმნიშვნელი სტრიქონები არ არის | დიახ | -| ↳ `check:doc-links` | დოკუმენტაციაში არსებული შიდა markdown-ბმულები რეალურ ფაილებზე მიუთითებს (`[text]`/`(path)` ფორმა) | დიახ | -| ↳ `check:fabricated-docs` | დოკუმენტაციაში ნახსენები მარშრუტები, გარემოს ცვლადები, CLI ბრძანებები, ჰუკების სახელები და ფაილების ბილიკები კოდურ ბაზაში არსებობს. მკაცრი ბარიერი `--strict`-ის გამოყენებით; ამ ალმის გარეშე შეცდომა მხოლოდ გაფრთხილებაა. | დიახ (`--strict`-ით CI-ში) | -| `check:cli-i18n` | CLI ბრძანებების სტრიქონები ყველა i18n ლოკალის ფაილშია წარმოდგენილი | დიახ | -| `check:openapi-coverage` | OpenAPI სპეციფიკაცია რეალური მარშრუტების რაოდენობის სულ მცირე მკაცრად განსაზღვრულ ქვედა ზღვარს ფარავს | დიახ | -| `check:openapi-security-tiers` | `openapi.yaml`-ში უსაფრთხოების დონის ანოტაციები `routeGuard.ts`-ის კლასიფიკაციებთან თანხვედრაშია | **სარეკომენდაციო** | -| `check:openapi-routes` | `openapi.yaml`-ში მოცემული ყოველი ბილიკი რეალურ `route.ts`-ს შეესაბამება (ჰალუცინაციების საწინააღმდეგოდ) | დიახ | -| `check:docs-symbols` | `docs/**/*.md`-ში არსებული ყოველი `/api/...` მითითება რეალურ `route.ts`-ს შეესაბამება (ჰალუცინაციების საწინააღმდეგოდ) | დიახ | -| `i18n translation drift` | უთარგმნელი გასაღებები i18n ლოკალის ფაილებში — მხოლოდ გაფრთხილება | **სარეკომენდაციო** | +სრულდება `main`-ზე მიმართულ ყოველ PR-ზე. წარუმატებლობის შემთხვევაში შერწყმას ბლოკავს. + +| სკრიპტი | რას ამოწმებს | ბლოკირებადი | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- | +| `check:docs-all` | მეტა-ბარიერი, რომელიც ქვემოთ მოცემულ 6 ქვებარიერს თანმიმდევრულად უშვებს | დიახ | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ვერსიების თანხვედრა | დიახ | +| ↳ `check:docs-counts` | პროზაულ ტექსტში მოცემული რაოდენობები (პროვაიდერების რაოდენობა, მიგრაციების რაოდენობა და ა.შ.) რეალური რაოდენობების ratchet-ფანჯრის ფარგლებშია | დიახ | +| ↳ `check:env-doc-sync` | `.env.example`-ში არსებული ყველა გარემოს ცვლადი დოკუმენტაციის ცხრილშია აღწერილი და პირიქით | დიახ | +| ↳ `check:deprecated-versions` | დოკუმენტაციაში მოძველებული ვერსიების სტრიქონები არ არის | დიახ | +| ↳ `check:doc-links` | დოკუმენტაციაში არსებული შიდა markdown-ბმულები რეალურ ფაილებზე მიუთითებს (`[text]`/`(path)` ფორმა) | დიახ | +| ↳ `check:fabricated-docs` | დოკუმენტაციაში მითითებული მარშრუტები, გარემოს ცვლადები, CLI ბრძანებები, hook-ების სახელები და ფაილის გზები კოდურ ბაზაში არსებობს. მკაცრი ბარიერი `--strict`-ით; ამ ალმის გარეშე შეცდომა მხოლოდ რბილია. | დიახ (CI-ში `--strict`-ით) | +| `check:cli-i18n` | CLI ბრძანებების სტრიქონები ყველა i18n ლოკალის ფაილშია წარმოდგენილი | დიახ | +| `check:openapi-coverage` | OpenAPI სპეციფიკაცია მოიცავს რეალური მარშრუტების მინიმუმ ratchet-ით განსაზღვრულ ქვედა ზღვარს | დიახ | +| `check:openapi-security-tiers` | `openapi.yaml`-ში უსაფრთხოების დონის ანოტაციები შეესაბამება `routeGuard.ts`-ის კლასიფიკაციებს | **სარეკომენდაციო** | +| `check:openapi-routes` | `openapi.yaml`-ში არსებული ყველა გზა რეალურ `route.ts`-ს შეესაბამება (ჰალუცინაციის საწინააღმდეგო შემოწმება) | დიახ | +| `check:docs-symbols` | `docs/**/*.md`-ში არსებული ყველა `/api/...` მითითება რეალურ `route.ts`-ს შეესაბამება (ჰალუცინაციის საწინააღმდეგო შემოწმება) | დიახ | +| `i18n translation drift` | უთარგმნელი გასაღებები i18n ლოკალის ფაილებში — მხოლოდ გაფრთხილება | **სარეკომენდაციო** | ### დავალება: `i18n-ui-coverage` -| სკრიპტი | რას ამოწმებს | ბლოკირებადი | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `check-ui-keys-coverage` (inline) | UI-ის i18n გასაღებების დაფარვა ≥ 65%-ია | დიახ | -| `check-ui-value-drift` (inline) | ხელახლა დაწერილი ინგლისური **მნიშვნელობა** მოძველებულ თარგმანს არ ტოვებს | დიახ | -| `check-new-key-coverage` (inline) | **ახალი** ინგლისური გასაღები ყველა ლოკალში თარგმნილია — `__MISSING__:` მარკერი უარყოფილია | დიახ | -| `check-translation-ratio` | რეალური თარგმანების თანაფარდობა თითოეული ლოკალისთვის (ინგლისურის იდენტური / ჩანაცვლების ტექსტი / ნებადართული სიის მიღმა დაკარგული ფოთლები) არ უნდა აღემატებოდეს `config/quality/i18n-translation-baseline.json` + დასაშვებ გადახრას | **სარეკომენდაციო** | +| სკრიპტი | რას ამოწმებს | ბლოკირებადი | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `check-ui-keys-coverage` (inline) | UI i18n გასაღებების დაფარვა ≥ 65%-ია | დიახ | +| `check-ui-value-drift` (inline) | გადაწერილი ინგლისური **მნიშვნელობის** შემდეგ მოძველებული თარგმანი არ რჩება | დიახ | +| `check-new-key-coverage` (inline) | **ახალი** ინგლისური გასაღები ყველა ლოკალზეა თარგმნილი — `__MISSING__:` მარკერი უარყოფილია | დიახ | +| `check-translation-ratio` | თითოეული ლოკალის რეალური თარგმანის თანაფარდობა (ინგლისურის იდენტური / placeholder / გამოტოვებული ფოთლები დაშვებული სიის მიღმა) არ უნდა აღემატებოდეს `config/quality/i18n-translation-baseline.json`-ს + დასაშვებ გადახრას | **სარეკომენდაციო** | საჭიროა `fetch-depth: 0` — მნიშვნელობის აცდენის ბარიერი `en.json`-ს შერწყმის ბაზასთან ადარებს. #### `check-ui-value-drift` — მოძველებული თარგმანის ბარიერი -აღმოაჩენს იმ ერთ i18n რეგრესიას, რომლის დანახვაც სხვა ბარიერებს სტრუქტურულად არ შეუძლიათ: ინგლისური მნიშვნელობა -ხელახლა იწერება, ხოლო _წინა_ ინგლისური ტექსტიდან მიღებული თარგმანები უცვლელი რჩება, რის გამოც -არაინგლისურენოვანი მომხმარებლები კვლავ თავდაჯერებული ტონით დაწერილ, თუმცა უკვე მცდარ ტექსტს კითხულობენ. +აფიქსირებს იმ ერთადერთ i18n რეგრესიას, რომლის სტრუქტურულად დანახვაც სხვა ბარიერებს არ შეუძლიათ: ინგლისური მნიშვნელობა +გადაიწერება, ხოლო _წინა_ ინგლისურიდან მიღებული თარგმანები უცვლელი რჩება, რის გამოც +არაინგლისურენოვანი მომხმარებლები კვლავ დამაჯერებლად ფორმულირებულ, თუმცა უკვე არასწორ ტექსტს კითხულობენ. -ეს რეალურად გამოქვეყნებულ ვერსიაში მოხვდა. `oauthModal.googleOAuthWarning` ხელახლა დაიწერა, როდესაც Antigravity-ის +ეს რეალურ რელიზში მოხვდა. `oauthModal.googleOAuthWarning` გადაიწერა, როდესაც Antigravity-ის შესვლის დამხმარე დაემატა (#5203); **43 ლოკალიდან 39-ში** დარჩა ტექსტი, რომელიც ოპერატორებს ეუბნებოდა, რომ „სრული -URL დაეკოპირებინათ და ქვემოთ ჩაესვათ“ — პროცესი, რომელიც ამ პროვაიდერისთვის ვერ დასრულდება. ეს +URL დაეკოპირებინათ და ქვემოთ ჩაესვათ“ — პროცესი, რომლის დასრულებაც ამ პროვაიდერისთვის შეუძლებელია. პრობლემა #8463-მდე შეუმჩნეველი დარჩა, რადგან: -- `sync-ui-keys` მხოლოდ **არარსებულ** გასაღებებს ამატებს და არასოდეს ანახლებს **მოძველებულებს**; -- `check-ui-keys-coverage` გასაღების _არსებობას_ ითვლის, ამიტომ მოძველებული თარგმანი დაფარულად ითვლება; -- `check-translation-drift` თვალს ადევნებს `docs/i18n//**.md` დოკუმენტაციის ასლებს — - ის არასოდეს კითხულობს `src/i18n/messages/*.json`-ს. ბლოკირებადია დავალებაში `docs-sync-strict` 2026-09-ის - ხელახალი სინქრონიზაციიდან: შეცვალეთ ძირითადი დოკუმენტი → `npm run i18n:run -- --files=` (სექციის დონეზე, სწრაფი). +- `sync-ui-keys` მხოლოდ **არარსებულ** გასაღებებს ამატებს და არასოდეს აახლებს **მოძველებულებს**; +- `check-ui-keys-coverage` გასაღების _არსებობას_ ითვლის, ამიტომ მოძველებული თარგმანი დაფარულად მიიჩნევა; +- `check-translation-drift` აკვირდება `docs/i18n//**.md` დოკუმენტაციის სარკეებს — + ის არასოდეს კითხულობს `src/i18n/messages/*.json`-ს. 2026-09-ის ხელახალი სინქრონიზაციის შემდეგ ბლოკირებადია დავალებაში `docs-sync-strict`: ძირითადი დოკუმენტის რედაქტირება → `npm run i18n:run -- --files=` (სექციის დონეზე, იაფი). -**ცვლილებების სხვაობაზე ორიენტირებული და არა საბაზისო მონაცემებზე დაყრდნობილი.** ის შერწყმის საბაზისო წერტილში არსებულ `en.json`-ს -სამუშაო ხეს ადარებს; ყველა გასაღებისთვის, რომლის ინგლისური მნიშვნელობაც შეიცვალა, მოძველებულია ნებისმიერი ლოკალი, რომელიც ჯერ კიდევ -უცვლელ თარგმანს შეიცავს. ეს განზრახ **აფიქსირებს უკვე არსებულ დავალიანებას** — სხვაობა -ვერ გამოავლენს, თუ ძველი ინგლისური ტექსტის რომელი ვერსიიდან მომდინარეობს დიდი ხნის წინანდელი თარგმანი, ამიტომ შემოწმება აფასებს -მხოლოდ იმას, რასაც მიმდინარე ცვლილება ეხება. ალტერნატივა (თითოეული გასაღებისთვის ჰეშების საბაზისო ფაილი) მოითხოვდა -დაახლოებით 600 KB-იან გენერირებულ ფაილს, რაც უდიდეს არსებულ საბაზისო ფაილზე 3-ჯერ დიდია და ყოველ i18n PR-ზე შეიცვლებოდა. +**ითვალისწინებს diff-ს და არ ეყრდნობა საბაზისო ფაილს.** ის merge base-ში არსებულ `en.json`-ს სამუშაო ხეს ადარებს; თითოეული გასაღებისთვის, რომლის ინგლისური მნიშვნელობაც შეიცვალა, ნებისმიერი ლოკალი, რომელიც კვლავ უცვლელ თარგმანს შეიცავს, მოძველებულია. ეს განზრახ **აფიქსირებს უკვე არსებულ დავალიანებას** — diff ვერ გამოავლენს, თუ ძველი ინგლისური ტექსტის რომელი ვერსიიდან მომდინარეობს დიდი ხნის წინანდელი თარგმანი, ამიტომ შემოწმება აფასებს მხოლოდ იმას, რასაც მიმდინარე ცვლილება ეხება. ალტერნატივა (თითოეულ გასაღებზე hash-ის შემცველი საბაზისო ფაილი) მოითხოვდა დაახლოებით 600 KB-იან გენერირებულ ფაილს, რაც უდიდეს არსებულ საბაზისო ფაილზე 3-ჯერ დიდია და ყოველ i18n PR-ზე შეიცვლებოდა. -მისი დაკმაყოფილების ორი გზა არსებობს: +მის დასაკმაყოფილებლად ორი გზა არსებობს: 1. განაახლეთ შესაბამისი თარგმანები, ან -2. მიანიჭეთ მათ `__MISSING__:` — შესრულების გარემო შემდეგ გასცემს შესწორებულ ინგლისურ ტექსტს - (`src/i18n/request.ts::deepMergeFallback`, #7258), ხოლო გასაღები თარგმნის რიგში დაემატება. +2. მიანიჭეთ მათ `__MISSING__:` — ამის შემდეგ runtime გასცემს გასწორებულ ინგლისურ ტექსტს + (`src/i18n/request.ts::deepMergeFallback`, #7258), ხოლო გასაღები თარგმნის რიგში ჩადგება. -თუ სტრიქონის **მნიშვნელობა** შეიცვალა, უმჯობესია **გასაღებს სახელი შეუცვალოთ**: ახალი გასაღები -მოძველებულ თარგმანს ვერ დაიმკვიდრებს. ეს არის მიდგომა, რომელიც #8463-ში გამოიყენეს. +თუ სტრიქონის **მნიშვნელობა** შეიცვალა, უმჯობესია **გასაღებს სახელი შეუცვალოთ**: ახალი გასაღები მოძველებულ თარგმანს ვერ დაიმკვიდრებს. სწორედ ეს მიდგომა გამოიყენა #8463-მა. ```bash npm run i18n:check-value-drift # მკაცრი (რასაც CI უშვებს) -npm run i18n:check-value-drift:warn # მხოლოდ ანგარიშის მომზადება +npm run i18n:check-value-drift:warn # მხოლოდ ანგარიშის წარდგენა BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -ასრულებს მუშაობას 0 კოდით და `SKIP reason=base-unresolved` შეტყობინებით, როდესაც საბაზისო კატალოგის წაკითხვა შეუძლებელია (ზედაპირული -კლონი საბაზისო ref-ის გარეშე), `check-openapi-breaking`-ის ანალოგიურად. +ასრულებს მუშაობას 0 კოდით და `SKIP reason=base-unresolved` შეტყობინებით, როდესაც საბაზისო კატალოგის წაკითხვა შეუძლებელია (shallow clone საბაზისო ref-ის გარეშე), `check-openapi-breaking`-ის ანალოგიურად. ### დავალება: `i18n` i18n-ის სრული ვალიდაციის მატრიცა (თითო დავალება თითო ლოკალზე). მთელი დავალება სარეკომენდაციო ხასიათისაა. -| სკრიპტი | რას ამოწმებს | ბლოკირებადობა | -| ------------------------------- | -------------------------------------- | -------------------------------------------------------------- | -| `validate_translation.py quick` | თარგმანის სისრულე თითოეული ლოკალისთვის | **სარეკომენდაციო** (`continue-on-error: true` მთელ დავალებაზე) | +| სკრიპტი | რას ამოწმებს | ბლოკავს | +| ------------------------------- | --------------------------------------- | -------------------------------------------------------------- | +| `validate_translation.py quick` | თარგმანის სისრულეს თითოეული ლოკალისთვის | **სარეკომენდაციო** (`continue-on-error: true` მთელ დავალებაზე) | ### დავალება: `pr-test-policy` ეშვება მხოლოდ pull request-ებზე. -| სკრიპტი | რას ამოწმებს | ბლოკირებადობა | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `check:pr-test-policy` | PR-ები, რომლებიც ცვლიან საწარმოო კოდს `src/`, `open-sse/`, `electron/` ან `bin/`-ში, უნდა შეიცავდეს ან აახლებდეს ტესტებს (მკაცრი წესი #8) | დიახ | -| `check:test-masking` | შეცვლილი სატესტო ფაილები არ ამცირებს ასერტების წმინდა რაოდენობას და არ ამატებს `assert.ok(true)` ტავტოლოგიებს | დიახ | -| `check:pr-evidence` | PR-ის აღწერაში მითითებულია ცვლილების ტესტირების/VPS-ის მტკიცებულებები (მკაცრი წესის #18 ავტომატიზაცია PR-ის ტექსტში ძიებით — არასაიმედოა, იხილეთ Backlog) | დიახ | +| სკრიპტი | რას ამოწმებს | ბლოკავს | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `check:pr-test-policy` | PR-ები, რომლებიც ცვლიან საწარმოო კოდს `src/`, `open-sse/`, `electron/` ან `bin/`-ში, უნდა შეიცავდნენ ან აახლებდნენ ტესტებს (მკაცრი წესი #8) | დიახ | +| `check:test-masking` | შეცვლილი სატესტო ფაილები არ ამცირებენ assert-ების წმინდა რაოდენობას და არ ამატებენ `assert.ok(true)` ტავტოლოგიებს | დიახ | +| `check:pr-evidence` | PR-ის აღწერა მიუთითებს ცვლილების ტესტირების/VPS-ის მტკიცებულებაზე (მკაცრი წესის #18 ავტომატიზაცია PR-ის ტექსტში ძიებით — არასაიმედოა, იხილეთ Backlog) | დიახ | ### დავალება: `test-vitest` -ეშვება `build`-ის შემდეგ. წარუმატებლობის შემთხვევაში ბლოკავს შერწყმას. +ეშვება `build`-ის შემდეგ. წარუმატებლობის შემთხვევაში merge-ს ბლოკავს. -| ნაკრები | რას ამოწმებს | ბლოკირებადობა | -| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| `test:vitest` | MCP სერვერი (110 ხელსაწყო), autoCombo, ქეში — vitest-ის გამშვები | დიახ | -| `test:vitest:ui` | UI კომპონენტების ტესტები — vitest-ის გამშვები | **ბლოკირებადი** — უკვე არსებული წარუმატებლობები აშკარად გამორიცხულია `vitest.config.ts`-ში; ახალი წარუმატებლობები დავალებას ჩააგდებს | +| ნაკრები | რას ამოწმებს | ბლოკავს | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP სერვერს (110 ხელსაწყო), autoCombo-ს, cache-ს — vitest runner | დიახ | +| `test:vitest:ui` | UI კომპონენტების ტესტებს — vitest runner | **ბლოკავს** — უკვე არსებული წარუმატებელი ტესტები აშკარად გამორიცხულია `vitest.config.ts`-ში; ახალი წარუმატებლობები დავალებას ჩააგდებს | -### ღამის სამუშაო პროცესები (დაგეგმილი, სარეკომენდაციო) +### ღამის workflow-ები (განრიგით, სარეკომენდაციო) -ისინი ეშვება cron განრიგით (და `workflow_dispatch`-ით), მაგრამ არასდროს PR-ებზე. ყველა მათგანი სარეკომენდაციო ხასიათისაა. +ისინი cron-განრიგით (და `workflow_dispatch`-ით) ეშვება, PR-ებზე კი არასდროს. ყველა მათგანი სარეკომენდაციო ხასიათისაა. -| სამუშაო პროცესი | რას ამოწმებს | ბლოკირებადობა | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `nightly-property` | fast-check-ის თვისებებზე დაფუძნებული ტესტები შემთხვევითი საწყისი მნიშვნელობითა და გაშვებების დიდი რაოდენობით | **სარეკომენდაციო** | -| `nightly-resilience` | heap-ის ზრდის შემოწმება, ქაოსური გაუმართაობების ინექცია, k6 დატვირთვის/ხანგრძლივი დატვირთვის ტესტირება | **სარეკომენდაციო** | -| `nightly-llm-security` | promptfoo-ს ინექციისგან დაცვა (ბლოკირების რეჟიმი) + garak-ის შემოწმებები (გამოტოვებულია პროვაიდერის საიდუმლოს გარეშე) | **სარეკომენდაციო** | -| `nightly-schemathesis` | OpenAPI კონტრაქტის ფაზინგი (schemathesis) გაშვებულ OmniRoute-ზე `docs/openapi.yaml`-ის გამოყენებით — გამოავლენს სპეციფიკაციის დარღვევებს / დაუმუშავებელ 500-იან პასუხებს (ფაზა 8 B.4) | **სარეკომენდაციო** | -| `nightly-mutation` | Stryker-ის მუტაციური ტესტირების ქულა სწრაფი მოდულური ტესტების ნაკრებისთვის — გადარჩენილი მუტანტები სუსტ ასერტებს გამოავლენს | **სარეკომენდაციო** | -| `nightly-compat` | Node ძრავის თავსებადობის მატრიცა მხარდაჭერილი `engines.node` დიაპაზონებისთვის | **სარეკომენდაციო** | +| Workflow | რას ამოწმებს | ბლოკავს | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `nightly-property` | fast-check property ტესტებს შემთხვევითი seed-ითა და გაშვებების დიდი რაოდენობით | **სარეკომენდაციო** | +| `nightly-resilience` | heap-ის ზრდის შემოწმებას, chaos fault-injection-ს, k6 load/soak ტესტებს | **სარეკომენდაციო** | +| `nightly-llm-security` | promptfoo injection guard-ს (block რეჟიმი) + garak probes-ს (გამოტოვებულია provider-ის secret-ის გარეშე) | **სარეკომენდაციო** | +| `nightly-schemathesis` | OpenAPI კონტრაქტის fuzzing-ს (schemathesis) გაშვებული OmniRoute-ის წინააღმდეგ, `docs/openapi.yaml`-ის გამოყენებით — ავლენს სპეციფიკაციის დარღვევებს / დაუმუშავებელ 500-ებს (Fase 8 B.4) | **სარეკომენდაციო** | +| `nightly-mutation` | Stryker mutation-testing-ის ქულას სწრაფ unit lane-ზე — გადარჩენილი mutants სუსტ assert-ებს ავლენს | **სარეკომენდაციო** | +| `nightly-compat` | Node engine-ის თავსებადობის მატრიცას მხარდაჭერილ `engines.node` დიაპაზონებში | **სარეკომენდაციო** | --- @@ -534,43 +526,43 @@ CI-ის შემოწმებების გარდა, OmniRoute-ს --- -## რაციონალიზაციის ბექლოგი (ROI მიმოხილვა — ფაზა 9, ტალღა 3) +## რაციონალიზაციის ბექლოგი (ROI-ის მიმოხილვა — ფაზა 9, ტალღა 3) -ეს ინვენტარი 2026-06-17-ს შეჯერდა `ci.yml`-თან (წინა ვერსიაში გამოტოვებული იყო +ეს ინვენტარი შეჯერებულია `ci.yml`-თან 2026-06-17-ს (წინა ვერსიაში გამოტოვებული იყო `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). შეჯერებული ნაკრების ROI მიმოხილვამ +`check:codeql-ratchet`, `check:pr-evidence`). შეჯერებული ნაკრების ROI-ის მიმოხილვამ გამოავლინა რაციონალიზაციის შემდეგი კანდიდატები. **გაერთიანებები CI-ის მექანიკური -ცვლილებებია; გადართვა/ამოღება კი ოპერატორისთვის განკუთვნილი პოლიტიკის გადაწყვეტილებებია.** ქვემოთ -ჩამოთვლილიდან ჯერ არაფერი განხორციელებულა. +ცვლილებებია; გადართვა/ამოღება კი პოლიტიკის გადაწყვეტილებებია, რომლებიც ოპერატორს ეკუთვნის.** ქვემოთ +ჩამოთვლილთაგან ჯერ არაფერი გამოყენებულა. -**ზემოთ ასევე არ არის დოკუმენტირებული** (სარეკომენდაციო, დაბალი სიგნალი): `docs-lint` დავალება +**ზემოთ ასევე არ არის დოკუმენტირებული** (სარეკომენდაციო, დაბალი სიგნალის მქონე): `docs-lint` დავალება (markdownlint + Vale, მთელ დავალებაზე `continue-on-error`) და დამოუკიდებელი სკანერის სამუშაო პროცესები `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` არის -`quality-baseline.json`-ში, თუმცა `ci.yml`-ში ბლოკირების ტრეშოლდთან მიბმული არ არის — მეტრიკა +`quality-baseline.json`-ში, თუმცა `ci.yml`-ში ბლოკირების მექანიზმის მქონე რეჩეტთან დაკავშირებული არ არის — მეტრიკა ამჟამად ობლადაა დარჩენილი. ### გაერთიანება / დუბლირების მოცილება (მექანიკური, ნაკლები რისკი) -თითოეული კანდიდატი 2026-06-17-ს მოქმედი გეიტების მდგომარეობასთან მიმართებით გადამოწმდა (ენდე, მაგრამ გადაამოწმე); -რამდენიმე „აშკარა“ გაერთიანება, როგორც აღმოჩნდა, დაფარულ დავალიანებას შეიცავდა და **არ არის** პირდაპირი, უპრობლემო ჩანაცვლება. - -- **`check:docs-sync` ორჯერ სრულდება** — დამოუკიდებლად `lint` დავალებაში და ხელახლა `check:docs-all`-ის (`docs-sync-strict`) შიგნით, ასევე husky-ის pre-commit ჰუკში. ✅ **შესრულებულია** — დამოუკიდებელი `lint` გამოძახება ამოღებულია. -- **CVE სკანირება** — ❌ **სუფთა გაერთიანება არ არის.** `audit:deps` ნებისმიერ მაღალი/კრიტიკული სიმძიმის CVE-ზე მკაცრად მარცხდება; `check:vuln-ratchet` (osv) კი მხოლოდ საბაზისო მაჩვენებელთან შედარებით _გაუარესების_ შემთხვევაში მარცხდება (ამჟამად 1 MODERATE). სემანტიკა განსხვავებულია — `audit:deps`-ის ამოღებით მაღალი/კრიტიკული სიმძიმის აბსოლუტური გეიტი დაიკარგებოდა. ორივე დატოვეთ. -- **ციკლების აღმოჩენა** — ❌ **სუფთა გაერთიანება არ არის.** `check:circular-deps` (dpdm) აჩვენებს **91 ციკლს** (სწორედ ამიტომ არის სარეკომენდაციო); მათი წინასწარ აღმოფხვრის გარეშე მისი ბლოკირების რეჟიმში გადაყვანა შეუძლებელია და მას უფრო ფართო არეალი აქვს, ვიდრე წარმატებულ, კურირებულ `check:cycles`-ს. `check:cycles` დატოვეთ ბლოკირების რეჟიმში; dpdm-ის 91 ციკლის აღმოფხვრა ცალკე ბექლოგია. -- **სირთულე** — ✅ **შესრულებულია** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ESLint-ის ერთი გავლა, დათვლა ruleId-ის მიხედვით, რათა ციკლომატური სირთულისა და max-lines-ის საბაზისო მაჩვენებლები კოგნიტიური სირთულის საბაზისო მაჩვენებლებისგან დამოუკიდებელი დარჩეს; ცალკეული `check:complexity` / `check:cognitive-complexity` ლოკალური `--update`-ისთვის შენარჩუნებულია. -- **`/api`-ს ანტიჰალუცინაციური შემოწმება** — ✅ **შესრულებულია** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-ის FS ინვენტარიზაცია ერთხელ სრულდება, openapi-routes + docs-symbols კვლავ დამოუკიდებლად აგენერირებს ანგარიშებს; ცალკეული შემოწმებები ლოკალური გაშვებებისთვის შენარჩუნებულია. -- **`check:node-runtime` 11 დავალებაში სრულდება** — ⚠️ **დაბალი ROI.** თითოეული ცალკე რანერია და შემოწმებას <1წმ სჭირდება; ჯამური ეკონომია ~10წმ-ია, იაფი თითო-დავალებიანი დაცვის დაკარგვის ფასად. ცვლილებებით გამოწვეულ აურზაურად არ ღირს. -- **`typecheck:noimplicit:core` CI lint-ში** — ✅ **ამოღებულია lint დავალებიდან** (იყო სარეკომენდაციო `continue-on-error`); ტიპების ზედაპირის ბლოკირებას უზრუნველყოფს `typecheck:core` + `check:type-coverage`. ლოკალური სკრიპტი შენარჩუნებულია. - -### გადართვა / გადაწყვეტილება (ოპერატორის პოლიტიკა) - -- `check:openapi-security-tiers` (სარეკომენდაციო) — ❌ **პირდაპირ ვერ გადაირთვება.** ის 0 კოდით სრულდება, თუმცა აფრთხილებს, რომ `LOCAL_ONLY_API_PREFIXES`-ის ქვეშ მდებარე `traffic-inspector`-ის რამდენიმე მარშრუტს `x-loopback-only: true` ანოტაცია აკლია. მის სავალდებულოდ ქცევამდე ეს ანოტაციები ჯერ `openapi.yaml`-ს უნდა დაემატოს. -- `typecheck:noimplicit:core` (სარეკომენდაციო) — ძირითადად მოცულია ბლოკირების რეჟიმში მოქმედი `check:type-coverage` ტრეშოლდით. გადაიყვანეთ ტრეშოლდზე ან ამოიღეთ ზედმეტი მეორე `tsc` გავლა. -- `test:vitest:ui` (ახლა **ბლოკირების რეჟიმშია**) — წინასწარ არსებული ჩავარდნები ცალსახად გამორიცხულია `vitest.config.ts`-ში `// #8618` თვალთვალის კომენტარებით; ახალი ჩავარდნები დავალებას წარუმატებლად ასრულებს. -- `check:secrets` (gitleaks, ბლოკირების ტრეშოლდი, დაფიქსირებული 3 დოკუმენტირებული ცრუ დადებითი შედეგით) — დაამატეთ სამივე დაშვებულთა სიაში, რათა მაჩვენებელი 0 გახდეს, ან გადაიყვანეთ სარეკომენდაციო რეჟიმში. ნაწილობრივ ემთხვევა GitHub-ის ჩაშენებულ საიდუმლოებების სკანირებას + `check:public-creds`. -- `check:pr-evidence` (ბლოკირების რეჟიმში, grep-ით ამოწმებს PR-ის ტექსტურ აღწერას) — ცრუ დადებითი შედეგების მაღალი რისკი; ამოღების შემთხვევაში Hard Rule #18-ის აღსრულება სუსტდება, ამიტომ ეს ნამდვილად პოლიტიკის საკითხია. -- `semgrep` (დამოუკიდებელი, სარეკომენდაციო) — OWASP-ის კატეგორიებისთვის CodeQL-ს ნაწილობრივ ემთხვევა; მიაბით მისი საბაზისო მაჩვენებელი ტრეშოლდს ან ამოიღეთ. +თითოეული კანდიდატი შემოწმდა აქტიური გეიტების მდგომარეობასთან 2026-06-17-ს (ენდე, მაგრამ გადაამოწმე); +რამდენიმე „აშკარა“ გაერთიანება რეალურად დამალულ ტექნიკურ ვალს შეიცავდა და **არ არის** პირდაპირი უსაფრთხო ჩანაცვლება. + +- **`check:docs-sync` ორჯერ ეშვება** — ცალკე `lint` დავალებაში და კვლავ `check:docs-all`-ის (`docs-sync-strict`) შიგნით და husky-ის pre-commit ჰუკში. ✅ **შესრულებულია** — ცალკეული `lint` გამოძახება ამოღებულია. +- **CVE სკანირება** — ❌ **არ არის სუფთა გაერთიანება.** `audit:deps` იწვევს მკაცრ ჩავარდნას ნებისმიერ მაღალი/კრიტიკული სიმძიმის CVE-ზე; `check:vuln-ratchet` (osv) კი მხოლოდ საბაზისო დონესთან შედარებით _გაუარესების_ შემთხვევაში ვარდება (ამჟამად 1 MODERATE). სემანტიკა განსხვავებულია — `audit:deps`-ის ამოღებით დაიკარგებოდა მაღალი/კრიტიკული სიმძიმის აბსოლუტური გეიტი. ორივე უნდა დარჩეს. +- **ციკლების აღმოჩენა** — ✅ **შესრულებულია** (#15159 G-01/G-02). ძველ ტექსტში `check:cycles` აღწერილი იყო, როგორც „მწვანე, კურირებული“ გეიტი და მისი ბლოკირებად დატოვება იმით იყო გამართლებული, რომ `check:circular-deps` (dpdm) 91 ციკლს აფიქსირებდა. ეს მწვანე სტატუსი **ცრუ დადებითი შედეგი** იყო: `check:cycles` ასკანირებდა 5 ქვედირექტორიას (450 ფაილს), ამთხვევდა მხოლოდ სტატიკურ `import|export … from` კონსტრუქციებს და უგულებელყოფდა ყველა `@/` და `@omniroute/open-sse/` სპეციფიკატორს, ამიტომ ვერ ხედავდა დინამიკური იმპორტისა და ფსევდონიმების ციკლებს, რომლებიც რეპოზიტორიაში ჭარბობდა. გასწორებულია: ახლა გეიტი სრულად გადის `src` + `open-sse`-ზე (5023 ფაილი), სპეციფიკატორებს TypeScript AST-იდან აგროვებს (ამიტომ `import("…")` ითვლება, ხოლო ტიპის პოზიციაში არსებული `typeof import("…")` — არა) და ხსნის tsconfig-ის `paths`-ს. ის პოულობს **14** ციკლს და არა 0-ს. რადგან მანამდე არსებული 14 ციკლის გასწორება გეიტის PR-ში შეუძლებელია, `check:cycles` ახლა **რეჩეტია** (`--ratchet`, ზედა ზღვარი `metrics.cycles.value = 14` ფაილში `quality-baseline.json`, `direction: down`) — ის ბლოკავს ნებისმიერ _გაუარესებას_ და რაოდენობა მხოლოდ შემცირდება. CI უშვებს `npm run check:cycles:ratchet`-ს. ეტაპობრივი შემცირება მიმდინარეობს **A-01**-თან ერთად. `check:circular-deps` (dpdm) რჩება სარეკომენდაციო რეჟიმში, როგორც უფრო ფართო მეორე შეფასება. +- **სირთულე** — ✅ **შესრულებულია** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ESLint-ის ერთი გავლა, დათვლა ruleId-ის მიხედვით, რათა ციკლომატური სირთულის+max-lines-ისა და კოგნიტიური სირთულის საბაზისო მნიშვნელობები დამოუკიდებელი დარჩეს; ცალკეული `check:complexity` / `check:cognitive-complexity` შენარჩუნებულია ლოკალური `--update`-ისთვის. +- **`/api`-ის ანტიჰალუცინაციური შემოწმება** — ✅ **შესრულებულია** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-ის ფაილური სისტემის ერთი ინვენტარიზაცია, openapi-routes + docs-symbols კვლავ დამოუკიდებლად ქმნის ანგარიშებს; ცალკეული შემოწმებები შენარჩუნებულია ლოკალური გაშვებებისთვის. +- **`check:node-runtime` 11 დავალებაში ეშვება** — ⚠️ **დაბალი ROI.** თითოეული მათგანი ცალკე რანერია, ხოლო შემოწმებას <1 წმ სჭირდება; ჯამური დანაზოგი დაახლოებით 10 წმ-ია, სანაცვლოდ კი იკარგება იაფი, თითოეულ დავალებაზე მოქმედი დამცავი შემოწმება. ცვლილებით გამოწვეულ აურზაურად არ ღირს. +- **`typecheck:noimplicit:core` CI-ის lint-ზე** — ✅ **ამოღებულია lint დავალებიდან** (იყო სარეკომენდაციო `continue-on-error`); ტიპების ზედაპირის ბლოკირებადი შემოწმებაა `typecheck:core` + `check:type-coverage`. ლოკალური სკრიპტი შენარჩუნებულია. + +### გადართვა / გადაწყვეტილების მიღება (ოპერატორის პოლიტიკა) + +- `check:openapi-security-tiers` (სარეკომენდაციო) — ❌ **პირდაპირ გადართვა შეუძლებელია.** ის აბრუნებს 0-ს, თუმცა აფრთხილებს, რომ `LOCAL_ONLY_API_PREFIXES`-ის ქვეშ არსებული რამდენიმე `traffic-inspector` მარშრუტი არ შეიცავს `x-loopback-only: true` ანოტაციას. მის სავალდებულოდ ქცევამდე ეს ანოტაციები ჯერ `openapi.yaml`-ს უნდა დაემატოს. +- `typecheck:noimplicit:core` (სარეკომენდაციო) — ძირითადად ჩანაცვლებულია ბლოკირებადი `check:type-coverage` რეჩეტით. გადართეთ რეჩეტზე ან ამოიღეთ ზედმეტი მეორე `tsc` გავლა. +- `test:vitest:ui` (ახლა **ბლოკირებადია**) — მანამდე არსებული ჩავარდნები პირდაპირაა გამორიცხული `vitest.config.ts`-ში `// #8618` თვალთვალის კომენტარებით; ახალი ჩავარდნები დავალებას ჩააგდებს. +- `check:secrets` (gitleaks, ბლოკირებადი რეჩეტი, გაყინული 3 დოკუმენტირებულ ცრუ დადებით შედეგზე) — დაამატეთ ეს 3 დაშვებულთა სიაში, რათა მაჩვენებელი 0-მდე ჩამოვიდეს, ან გადაიყვანეთ სარეკომენდაციო რეჟიმში. ნაწილობრივ ემთხვევა GitHub-ის ჩაშენებულ საიდუმლოების სკანირებასა და `check:public-creds`-ს. +- `check:pr-evidence` (ბლოკირებადი, PR-ის ტექსტურ აღწერაში grep-ით ეძებს) — ცრუ დადებითი შედეგების მაღალი რისკი; მისი ამოღება ასუსტებს მკაცრი წესის #18 აღსრულებას, ამიტომ ეს ნამდვილად პოლიტიკის საკითხია. +- `semgrep` (სარეკომენდაციო დამოუკიდებელი შემოწმება) — OWASP-ის კატეგორიებისთვის ნაწილობრივ ემთხვევა CodeQL-ს; დაუკავშირეთ მისი საბაზისო მნიშვნელობა რეჩეტს ან ამოიღეთ. --- diff --git a/docs/i18n/ka/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ka/docs/compression/COMPRESSION_GUIDE.md index 39394f196043..40b3d296da7d 100644 --- a/docs/i18n/ka/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ka/docs/compression/COMPRESSION_GUIDE.md @@ -453,9 +453,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages`-ის მქონე body-ში, შიგთავსის გვერდის ავლა (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts`-ში) ბოლო სამ შეტყობინებას ამოწმებს და მთელი turn-ისთვის სტილებს გამოტოვებს, როცა ისინი უსაფრთხოების, შეუქცევადი მოქმედების, -დაზუსტების ან რიგითობაზე დამოკიდებულ საკვანძო სიტყვებს ემთხვევა. გვერდის ავლა მუშაობს -dashboard-ის **Auto-Clarity Bypass** გადამრთველის (`cavemanOutputMode.autoClarity`) მიმდინარე -პარამეტრის შესაბამისად. +დაზუსტების ან რიგითობაზე დამოკიდებულ საკვანძო სიტყვებს ემთხვევა. გვერდის ავლა მუშაობს, სანამ dashboard-ის **Auto-Clarity Bypass** გადამრთველი (`cavemanOutputMode.autoClarity`) ჩართულია, რაც ნაგულისხმევია; გადამრთველის გამორთვისას არჩეული სტილები ამ turn-ებზეც ვრცელდება. როცა გვერდის ავლა turn-ს ატარებს, `placeSystemInstruction()` (იმავე ფაილში), რომელიც ახალ `messages[0]`-ს არასდროს ქმნის, ბლოკს ათავსებს პირველივე ნაპოვნ ადგილას: diff --git a/docs/i18n/ka/docs/reference/ENVIRONMENT.md b/docs/i18n/ka/docs/reference/ENVIRONMENT.md index 24a08d638a00..b5de0a1cd9f5 100644 --- a/docs/i18n/ka/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ka/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. აუცილებელი საიდუმლოებები -ეს პარამეტრები პირველ გაშვებამდე **აუცილებლად** უნდა დაყენდეს. მათ გარეშე აპლიკაცია ან გაშვებაზე უარს იტყვის, ან არაუსაფრთხო ნაგულისხმევი პარამეტრებით იმუშავებს. - -| ცვლადი | აუცილებელია | ნაგულისხმევი მნიშვნელობა | წყაროს ფაილი | აღწერა | -| ---------------------------- | --------------------------- | --------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **დიახ** | _(არ არის)_ | `src/lib/auth` | ხელს აწერს/ამოწმებს მართვის პანელის სესიის ყველა cookie-ს (JWT). შექმენით `openssl rand -base64 48` ბრძანებით. | -| `API_KEY_SECRET` | **დიახ** | _(არ არის)_ | `src/lib/db/apiKeys.ts` | AES დაშიფვრის გასაღები SQLite-ში შენახული API გასაღებების მნიშვნელობებისთვის. შექმენით `openssl rand -hex 32` ბრძანებით. | -| `INITIAL_PASSWORD` | **დიახ** | `CHANGEME` | საწყისი ინიციალიზაციის სკრიპტი | ადგენს მართვის პანელის ადმინისტრატორის საწყის პაროლს (ემთხვევა `.env.example`-ის ნაგულისხმევ მნიშვნელობას — განზრახ აშკარად არაუსაფრთხოა, რათა მისი შეცვლა სავალდებულო იყოს). **შეცვალეთ პირველ გამოყენებამდე.** სისტემაში შესვლის შემდეგ შეცვალეთ Dashboard → Settings → Security-ის მეშვეობით. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **დიახ** (სამუშაო გარემოში) | _(დაუყენებელი)_ | `src/app/api/internal/codex-responses-ws/route.ts` | შიდა Codex Responses WebSocket ხიდის საერთო საიდუმლო. ადასტურებს ხიდის მოთხოვნებს Electron/ბრაუზერის WS რეტრანსლატორსა და OmniRoute-ს შორის. ⚠️ **სამუშაო გარემოში აუცილებელია — თუ დაყენებული არ არის, WS ხიდის ყველა მოთხოვნა უარყოფილი იქნება.** შექმენით `openssl rand -base64 32` ბრძანებით. | -| `OMNIROUTE_SW_BUILD_ID` | არა | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | სერვის-ვორკერის ქეშის იძულებითი განახლების აშკარად მითითებული იდენტიფიკატორი PWA გარსისთვის (#11779); გარჩევის ჯაჭვში პირველია. | -| `SOURCE_VERSION` | არა | _(დაუყენებელი)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ჯაჭვში მეორეა — განთავსებული commit-ის სახით დაყენებულია PaaS ამწყობების მიერ (მაგ., Heroku-ს ტიპის). | -| `NEXT_PUBLIC_SW_BUILD_ID` | არა | _(წარმოებული)_ | `src/shared/components/PwaRegister.tsx` | აგების დროს განსაზღვრული საჯარო მნიშვნელობა, რომელსაც კლიენტი `/sw.js?v=…`-ის რეგისტრაციისთვის იყენებს; მიიღება ზემოთ მოცემული ორი მნიშვნელობიდან, შემდეგ კი git SHA-დან. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | არა (ავტომატური) | _(ავტომატურად ყოველი გაშვებისას)_ | `src/server/authz/policies/management.ts` | თითოეული პროცესისთვის ცალკე საიდუმლო, რომელიც ადასტურებს, რომ სანდო თანასწორი კვანძის IP შტამპი OmniRoute-ის საკუთარი HTTP სერვერიდან (`scripts/dev/peer-stamp.mjs`) მოვიდა. ავტორიზაციის შუამავალი მოთხოვნის ლოკალურობას (LOCAL_ONLY მარშრუტების loopback/LAN შეზღუდვას) მხოლოდ მაშინ ენდობა, როდესაც შტამპი ამ ტოკენს შეიცავს. ყოველი გაშვებისას ავტომატურად იქმნება — დატოვეთ დაუყენებელი; ფიქსირებული მნიშვნელობა მხოლოდ მრავალპროცესიანი კონფიგურაციებისთვის მიუთითეთ, რომლებმაც შტამპი ერთობლივად უნდა გამოიყენონ. | -| `OMNIROUTE_TRUSTED_PROXIES` | არა | _(დაუყენებელი)_ | `scripts/dev/peer-stamp.mjs` | უკუპროქსი-სერვერების მძიმით გამოყოფილი IP-ები ან CIDR დიაპაზონები, რომელთა `X-Forwarded-For` / `X-Real-IP` მნიშვნელობებს IP-ების დაშვების/აკრძალვის სია შეიძლება ენდოს, გარდა loopback მისამართებისა, კერძო ქსელის მისამართებისა და Cloudflare-ის სასაზღვრო კვანძებისა, რომლებიც ყოველთვის სანდოდ მიიჩნევა. საჭიროა მხოლოდ ნებისმიერი სხვა საჯარო მისამართის მქონე პროქსი-სერვერისთვის; სხვა შემთხვევაში ფილტრი თავად ამ პროქსი-სერვერის მისამართს შეაფასებს. | +**დიახ**-ად მონიშნული, დაუყენებელი საიდუმლოები პირველ გაშვებაზე ავტომატურად იქმნება (`JWT_SECRET`, `API_KEY_SECRET`) და ინახება მონაცემთა საქაღალდეში არსებულ `server.env`-ში — დააყენეთ ისინი განზრახ, თუ ეს საქაღალდე დროებითია. გამონაკლისია `OMNIROUTE_WS_BRIDGE_SECRET`: ის `server.env`-ში არ ინახება — სერვერის შესასვლელი წერტილები თითოეულ გაშვებაზე ქმნის დროებით მნიშვნელობას, რომლის გამოყენებაც მხოლოდ პროცესში არსებული ხიდისთვის შეიძლება, ამიტომ გარე ხიდის კლიენტებს მისი ცალსახად დაყენება სჭირდებათ. + +| ცვლადი | აუცილებელია | ნაგულისხმევი მნიშვნელობა | წყაროს ფაილი | აღწერა | +| ---------------------------- | --------------------------- | --------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **დიახ** | _(არ არის)_ | `src/lib/auth` | ხელს აწერს/ამოწმებს მართვის პანელის სესიის ყველა cookie-ს (JWT). შექმენით `openssl rand -base64 48` ბრძანებით. | +| `API_KEY_SECRET` | **დიახ** | _(არ არის)_ | `src/lib/db/apiKeys.ts` | AES დაშიფვრის გასაღები SQLite-ში შენახული API გასაღებების მნიშვნელობებისთვის. შექმენით `openssl rand -hex 32` ბრძანებით. | +| `INITIAL_PASSWORD` | არა | _(დაუყენებელი)_ | `src/lib/auth/managementPassword.ts` | სანამ პაროლი ჯერ არ არის შენახული, გაშვებისას ადგენს მართვის პანელის პაროლს; პაროლის შენახვის შემდეგ იგნორირებულია. npm-ით და წყაროდან დაყენებული ვერსიები `.env.example`-ს `.env`-ად აკოპირებენ, ამიტომ იწყებენ მისი საჯაროდ ცნობილი ჩამნაცვლებლით `CHANGEME`: შეცვალეთ ის პირველ გაშვებამდე, ან დააყენეთ `INITIAL_PASSWORD=` (ცარიელი) ამ `.env`-ში, რათა პაროლი პირველი შემოსვლისას დაყენების ოსტატში შეიქმნას, ისე როგორც ამას დაუყენებელი მნიშვნელობა (Docker-ის ასლის ნაგულისხმევი) აკეთებს. შენახული პაროლის შესაცვლელად გამოიყენეთ Dashboard → Settings → Security, ან გაუშვით `omniroute reset-password` (`node bin/reset-password.mjs` წყაროს ასლში) `DATA_DIR`-ით, რომელიც მიუთითებს სერვერის მონაცემთა საქაღალდეს. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **დიახ** (სამუშაო გარემოში) | _(დაუყენებელი)_ | `src/app/api/internal/codex-responses-ws/route.ts` | შიდა Codex Responses WebSocket ხიდის საერთო საიდუმლო. ადასტურებს ხიდის მოთხოვნებს Electron/ბრაუზერის WS რეტრანსლატორსა და OmniRoute-ს შორის. ⚠️ **სამუშაო გარემოში აუცილებელია — თუ დაყენებული არ არის, WS ხიდის ყველა მოთხოვნა უარყოფილი იქნება.** შექმენით `openssl rand -base64 32` ბრძანებით. | +| `OMNIROUTE_SW_BUILD_ID` | არა | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | სერვის-ვორკერის ქეშის იძულებითი განახლების აშკარად მითითებული იდენტიფიკატორი PWA გარსისთვის (#11779); გარჩევის ჯაჭვში პირველია. | +| `SOURCE_VERSION` | არა | _(დაუყენებელი)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ჯაჭვში მეორეა — განთავსებული commit-ის სახით დაყენებულია PaaS ამწყობების მიერ (მაგ., Heroku-ს ტიპის). | +| `NEXT_PUBLIC_SW_BUILD_ID` | არა | _(წარმოებული)_ | `src/shared/components/PwaRegister.tsx` | აგების დროს განსაზღვრული საჯარო მნიშვნელობა, რომელსაც კლიენტი `/sw.js?v=…`-ის რეგისტრაციისთვის იყენებს; მიიღება ზემოთ მოცემული ორი მნიშვნელობიდან, შემდეგ კი git SHA-დან. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | არა (ავტომატური) | _(ავტომატურად ყოველი გაშვებისას)_ | `src/server/authz/policies/management.ts` | თითოეული პროცესისთვის ცალკე საიდუმლო, რომელიც ადასტურებს, რომ სანდო თანასწორი კვანძის IP შტამპი OmniRoute-ის საკუთარი HTTP სერვერიდან (`scripts/dev/peer-stamp.mjs`) მოვიდა. ავტორიზაციის შუამავალი მოთხოვნის ლოკალურობას (LOCAL_ONLY მარშრუტების loopback/LAN შეზღუდვას) მხოლოდ მაშინ ენდობა, როდესაც შტამპი ამ ტოკენს შეიცავს. ყოველი გაშვებისას ავტომატურად იქმნება — დატოვეთ დაუყენებელი; ფიქსირებული მნიშვნელობა მხოლოდ მრავალპროცესიანი კონფიგურაციებისთვის მიუთითეთ, რომლებმაც შტამპი ერთობლივად უნდა გამოიყენონ. | +| `OMNIROUTE_TRUSTED_PROXIES` | არა | _(დაუყენებელი)_ | `scripts/dev/peer-stamp.mjs` | უკუპროქსი-სერვერების მძიმით გამოყოფილი IP-ები ან CIDR დიაპაზონები, რომელთა `X-Forwarded-For` / `X-Real-IP` მნიშვნელობებს IP-ების დაშვების/აკრძალვის სია შეიძლება ენდოს, გარდა loopback მისამართებისა, კერძო ქსელის მისამართებისა და Cloudflare-ის სასაზღვრო კვანძებისა, რომლებიც ყოველთვის სანდოდ მიიჩნევა. საჭიროა მხოლოდ ნებისმიერი სხვა საჯარო მისამართის მქონე პროქსი-სერვერისთვის; სხვა შემთხვევაში ფილტრი თავად ამ პროქსი-სერვერის მისამართს შეაფასებს. | ### გენერირების ბრძანებები @@ -216,38 +216,38 @@ OmniRoute ყველა მუდმივი მონაცემისთ ## 4. უსაფრთხოება და ავთენტიფიკაცია -| ცვლადი | ნაგულისხმევი მნიშვნელობა | წყაროს ფაილი | აღწერა | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | მანქანის ციფრული ანაბეჭდის შესაქმნელად აპარატურულ იდენტიფიკატორებთან გაერთიანებული salt. იზოლაციისთვის შეცვალეთ თითოეული განთავსებისთვის. | -| `OMNIROUTE_CLI_SALT` | _(არ არის დაყენებული = თითოეული ინსტალაციისთვის შემთხვევითი salt, რომელიც ინახება მისამართზე `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt ლოკალური CLI ავთენტიფიკაციის ტოკენის წარმოსაქმნელად. ამ მნიშვნელობის დაყენება მანქანაზე არსებულ ყველა CLI ტოკენს განაახლებს და შენახულ salt-თან შედარებით ყოველთვის პრიორიტეტულია. იხილეთ `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | სესიის cookie-ებზე აყენებს `Secure` ალამს. HTTPS-ის უკან მუშაობისას მნიშვნელობა **აუცილებლად უნდა იყოს `true`**. | -| `REQUIRE_API_KEY` | `false` | API შუალედური პროგრამული უზრუნველყოფა | როდესაც არის `true`, ყველა `/v1/*` პროქსი-მოთხოვნა უნდა შეიცავდეს მოქმედ API გასაღებს. **ეს ალამი არ ზღუდავს `GET /v1/models`-ს**, რომელიც სანაცვლოდ მართვის პანელში შესვლის პოზიციას (`requireAuthForModels`) მიჰყვება — ამიტომ `/v1/models`-დან მიღებული `401` არ ნიშნავს, რომ ინფერენსი დაცულია. იხილეთ `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | მართვის პანელის ინტერფეისში API გასაღებების სრული მნიშვნელობების ჩვენების საშუალებას იძლევა. კონფიგურირდება მართვის პანელის ფუნქციური ალმებიდან; საზიარო ინსტანციებზე უსაფრთხოების რისკს წარმოადგენს. | -| `NO_LOG_API_KEY_IDS` | _(ცარიელი)_ | `src/lib/compliance/index.ts` | მძიმით გამოყოფილი API გასაღების ID-ები, რომლებისთვისაც მოთხოვნების ჟურნალში ჩაწერა გამოტოვებულია (GDPR-თან შესაბამისობა). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(არ არის დაყენებული = შეუზღუდავი)_ | `src/shared/utils/apiKeyPolicy.ts` | დღიური მოთხოვნების სარეზერვო ბიუჯეტი, რომელიც გამოიყენება API გასაღებებისთვის, რომელთა `rate_limits` სვეტი null-ია. დაუყენებელი ან ცარიელი: არაპირდაპირი ლიმიტი არ არსებობს (#2289, #11017). `0` იგივეა (შეუზღუდავი). დადებითი მთელი რიცხვი N რთავს N/დღე, 5N/კვირა, 20N/თვე ლიმიტებს. არასწორად ფორმატირებული არაცარიელი მნიშვნელობები ბრუნდება ძველ 1000/დღე, 5000/კვირა, 20000/თვე ფანჯრებზე. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | მოთხოვნის სხეულის მაქსიმალური დასაშვები ზომა. უარყოფს ამ ლიმიტზე დიდი ზომის მონაცემებს. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ამ ზღვრის ტოლი ან მასზე დიდი მოთხოვნის რეალური სხეულები JSON-ის დამუშავებამდე იღებენ ატომურ, პროცესის ფარგლებში ლოკალურ, მძიმე დატვირთვის დაშვების იჯარას (BYTE გზა, მათ შორის `POST /v1/responses`). სტრუქტურულად მძიმე მოთხოვნების მსგავსად გამოიყენება თავისუფალი რესურსების საკმარისი მარაგის იგივე [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) გამონაკლისი; ის კვლავ შეზღუდულია `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ით / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), რათა [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) არ განმეორდეს. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ჩატის მარშრუტის მკაცრი ზღვარი, რომელიც მოწმდება შეზღუდული მიღებისას წაკითხული ბაიტების მიხედვით, მათ შორის მოთხოვნებისთვის, რომლებშიც `Content-Length` არ არის მითითებული, არასწორია ან რეალობას არ შეესაბამება; ზღვრის გადაჭარბებისას ბრუნდება `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(დაყენებული არ არის — მოთხოვნების რაოდენობაზე ზღვარი არ მოქმედებს)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** მოთხოვნების რაოდენობაზე დაფუძნებული ეს მოძველებული ზღვარი ახლა მხოლოდ აშკარად დაყენებისას მოქმედებს. თუ ის დაყენებული არ არის (ნაგულისხმევი მდგომარეობა), მძიმე ჩატის მოთხოვნების დაშვება სანაცვლოდ კონტროლდება `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ით — ბაიტების ავტომატურად გამოთვლილი ბიუჯეტით, რომლის ზომაც განისაზღვრება პროცესის მეხსიერების რეალური ზღვრის მიხედვით **ერთ პროცესში** (ერთი V8 heap). ორი ერთმანეთზე გადაფარული, დაახლოებით 750k-ტოკენიანი `/v1/responses` მოთხოვნა დაახლოებით 12 GiB-ის heap-ებს ავარიულად ასრულებს (#7849) — ეს **მეხსიერების ბიუჯეტის** გაფრთხილებაა და არა პროდუქტის მკაცრი მაქსიმუმი, 2. გამართულმა პროცესმა (რომლის heap-იც განტვირთვის კოეფიციენტზე ქვემოთაა) შესაძლოა დაუშვას მეტი ერთდროული ხანგრძლივი `/v1/responses` მოთხოვნა `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`-ის მეშვეობით. ათეულობით ხანგრძლივი SSE კლიენტის (40–50) მხარდაჭერა დამოკიდებულია heap-ზე + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ზე / #10110-ზე და არ ნიშნავს „მაქსიმუმ 2“-ს. ამის დაუფიქრებლად გაზრდა „ჰოსტის გამოსაყენებლად“ ხელახლა წარმოშობს #7849-ს. გაამრავლეთ **heap-ები** **N დამოუკიდებელი `DATA_DIR`-ით** (#11024); არასოდეს გამოიყენოთ `replicas>1` ერთი SQLite ფაილისთვის. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ავტომატურად გამოთვლილი)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ავტომატურად გამოთვლილი მიღების ბაიტური ბიუჯეტის ჩანაცვლება (V8/cgroup მეხსიერების უფრო მკაცრი ზღვრის 25%, გაყოფილი გარდამავალი 8x გაძლიერების კოეფიციენტზე). როგორც გამოთვლილი, ისე აშკარად მითითებული მნიშვნელობები იზღუდება 8 MiB–2 GiB დიაპაზონით. ეფექტურ ბიუჯეტზე დიდი სხეული დაუყოვნებლივ სრულდება შეცდომით `413 body_exceeds_budget`; ცალ-ცალკე მომსახურებადი სხეულების შეჯიბრება კვლავ ხელახლა საცდელი `503`-ით სრულდება. 40–50 ერთდროული ხანგრძლივი SSE კლიენტის მხარდაჭერა დამოკიდებულია ამ ბიუჯეტზე + heap-ზე და არ წარმოადგენს მკაცრ „მაქსიმუმ 2“-ს. კონფიგურაციის შეცვლამდე `/api/monitoring/health`-ზე შეამოწმეთ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Heap-ზე ზეწოლის დროს განტვირთვის კოეფიციენტი (`heapUsed / heap_size_limit`) ბაიტური და სტრუქტურული მძიმე მოთხოვნების დაშვებისთვის (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ზე მეტი ერთდროული მძიმე მოთხოვნა ხელახლა საცდელი `503`-ით მხოლოდ მაშინ განიტვირთება, როდესაც heap ამ კოეფიციენტის ტოლი ან მასზე მაღალია; გამართული heap-ის შემთხვევაში მოთხოვნა healthy-headroom-ის მეშვეობით დაიშვება. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ნაგულისხმევად `1`) | `src/shared/middleware/chatBodyAdmission.ts` | შეზღუდული დამატებითი ტევადობა გამართული heap-ის სწრაფი გზისთვის (#10437), როგორც სტრუქტურული, ისე ბაიტური მოთხოვნებისთვის (`admitChatRequest`, მათ შორის სხეულებისთვის ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). ამ ზღვრის გარეშე ყოველი დატვირთული, მაგრამ გამართული heap-ის მქონე მოთხოვნა დაშვების კონტროლს ყოველგვარი ზღვრის გარეშე გვერდს უვლიდა. როდესაც healthy-heap გზით ამდენი ერთდროული lease გააქტიურდება, შემდგომი დატვირთული მოთხოვნები გადადის იმავე შეზღუდული ლოდინის/განტვირთვის გზაზე, რომელიც heap-ზე რეალური ზეწოლისას გამოიყენება. `0` გვერდის ავლას სრულად თიშავს. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | შეტყობინებების რაოდენობა, რომლის საფუძველზეც ჩატის მოთხოვნა მძიმედ კლასიფიცირდება მაშინაც კი, როდესაც მისი სხეული ბაიტურ ზღვარზე მცირეა. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ხელსაწყოების რაოდენობა, რომლის საფუძველზეც ჩატის მოთხოვნა მძიმედ კლასიფიცირდება მაშინაც კი, როდესაც მისი სხეული ბაიტურ ზღვარზე მცირეა. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | სტრიქონის ზომაზე დაფუძნებული ტოკენების კონსერვატიული შეფასება, რომლის მიხედვითაც მოთხოვნა მძიმედ კლასიფიცირდება; ეს დაშვების ღირებულების მიახლოებითი მაჩვენებელია და არა პროვაიდერის მიერ ანგარიშსწორებისთვის გამოყენებული ტოკენიზაცია. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (გამორთულია) | `src/shared/middleware/chatBodyAdmission.ts` | ჩატის ისტორიის არასავალდებულო, აშკარად ჩასართავი ზღვარი. ნაგულისხმევად გამორთულია: შეტყობინებების რაოდენობა განთავსების პოლიტიკაა და არა მოთხოვნის უნივერსალური თვისება, ხოლო აქ ზღვრის დაწესება საუბრებს საბოლოო `413`-ით უარყოფს, სანამ შეკუმშვის კონვეიერი მათ მომსახურებად გარდაქმნას შეძლებს. Heap-ის ზრდა იზღუდება `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ითა და heap-ზე ზეწოლისას განტვირთვით. მეხსიერებით შეზღუდულ განთავსებებში, რომლებსაც მკაცრი ზედა ზღვარი სჭირდებათ, მიუთითეთ დადებითი მნიშვნელობა; გადაჭარბებისას დაბრუნდება სტრუქტურირებული `413`, რომელიც შეკუმშვის აუცილებლობას მიუთითებს. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | მკაცრი ზღვარი ზემდგომი არანაკადური პასუხისთვის, რომელიც სრულად ბუფერიზდება მეხსიერებაში. ამ ზღვრის გადაჭარბებისას ზემდგომი წამკითხველი უქმდება და მოთხოვნა სწრაფად სრულდება შეცდომით, ნაცვლად იმისა, რომ შეუზღუდავი სტრიქონი გაიზარდოს heap-ის ამოწურვამდე. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ზედა დონის პასუხის სათაურებიდან გადმოცემული ბაიტების მაქსიმალური რაოდენობა. ბიუჯეტის გადაჭარბებისას ნაკლები პრიორიტეტის მქონე სათაურები (მაგ., მორგებული `x-codex-*`, `x-oai-request-id`) გამოიტოვება, რათა დაცული იყოს უკუპროქსების სათაურების გავრცელებული ლიმიტები. ზედა დონიდან მეტი მეტამონაცემის გადასაცემად დააყენეთ უფრო მაღალი მნიშვნელობა, თუმცა ეს პასუხის სათაურების ზომას გაზრდის. | -| `CORS_ORIGIN` | _(არ არის დაყენებული)_ | `src/server/cors/origins.ts` | მოძველებული, ერთი წყაროსთვის განკუთვნილი CORS-ის დაშვებული სია. ახალი განთავსებებისთვის უპირატესობა მიანიჭეთ `CORS_ALLOWED_ORIGINS`-ს. CORS განკუთვნილია მხოლოდ სხვა წყაროდან მომუშავე ბრაუზერული API-კლიენტებისთვის; ავთენტიფიცირებული მართვის პანელის ჩაწერის მოთხოვნები ამის ნაცვლად იყენებს იმავე წყაროს მოთხოვნებსა და სესიაზე მიბმულ CSRF-დაცვას. | -| `CORS_ALLOWED_ORIGINS` | _(არ არის დაყენებული)_ | `src/server/cors/origins.ts` | მძიმეებით გამოყოფილი CORS-ის დაშვებული სია. ვაილდქარდი არ გაიგზავნება, თუ `CORS_ALLOW_ALL=true` ცალსახად არ არის კონფიგურირებული. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | მხოლოდ დეველოპმენტისთვის განკუთვნილი საგანგებო მექანიზმი, რომელიც ბრაუზერის ნებისმიერ `Origin`-ს იმეორებს. არ ჩართოთ გაზიარებულ ან საწარმოო განთავსებებში. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | დაბლოკეთ პროვაიდერის გამოძახებები, რომლებიც მიმართულია კერძო/უკუმარყუჟის/link-local IP-დიაპაზონებზე. გამორთეთ მხოლოდ იზოლირებულ სატესტო გარემოებში. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | დაუშვით კერძო/ლოკალურ ქსელებზე მიმთითებელი პროვაიდერის URL-ები (localhost, 192.168.x.x, 10.x.x.x და სხვ.). **აუცილებელია თვითჰოსტირებული პროვაიდერებისთვის** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). როდესაც მნიშვნელობაა `false`, მართვის პანელი უარყოფს ლოკალური URL-ების ვალიდაციას. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | დაუშვით პროვაიდერების დამატება/ვალიდაცია ლოკალურ/კერძო მისამართებზე (127.0.0.1, localhost, LAN, კერძო დიაპაზონები) — მოქმედებს მხოლოდ პროვაიდერის ვალიდაციის გზაზე. **ნაგულისხმევი მნიშვნელობაა `true`** (უპირატესობა ლოკალურს); მკაცრი, მხოლოდ საჯარო მისამართების დაშვების რეჟიმის აღსასრულებლად დააყენეთ `false`. ღრუბლოვანი მეტამონაცემების საბოლოო წერტილები (169.254.169.254, metadata.google.internal) ნებისმიერ შემთხვევაში დაბლოკილი რჩება. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | მიეცით `/v1/audio/*` მარშრუტებს (ტრანსკრიფცია, მეტყველება, თარგმანი) localhost-ის გარეთ განთავსებული OpenAI-თან თავსებადი პროვაიდერის კვანძის გამოყენების უფლება. ნაგულისხმევად გამორთულია — აუდიოს დისტანციურ ჰოსტზე მარშრუტიზაცია ცვლის გამავალი ტრაფიკის იდენტობას და ოპერატორის ცალსახა გადაწყვეტილებას საჭიროებს. უკუმარყუჟის/კერძო კვანძები (localhost, 127.0.0.1, 172.16-31.x) ყოველთვის დაშვებულია და ეს პარამეტრი მათზე გავლენას არ ახდენს. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | მიეცით `POST /v1/rerank`-ს (და მეხსიერების ძრავის უკუმარყუჟის ხელახალი რანჟირების ეტაპს) localhost-ის გარეთ განთავსებული OpenAI-თან თავსებადი პროვაიდერის კვანძის გამოყენების უფლება — LAN-ის კომპიუტერის ან Tailscale-ის პირის, რომელზეც გაშვებულია TEI, Infinity, vLLM და სხვ. ნაგულისხმევად გამორთულია — დისტანციურ ჰოსტზე მარშრუტიზაცია ცვლის გამავალი ტრაფიკის იდენტობას და ოპერატორის ცალსახა გადაწყვეტილებას საჭიროებს. უკუმარყუჟის კვანძები (localhost, 127.0.0.1, 172.16-31.x) ყოველთვის დაშვებულია და ეს პარამეტრი მათზე გავლენას არ ახდენს. დისტანციურმა კვანძებმა ასევე უნდა გაიარონ პროვაიდერის გამავალი URL-ების პოლიტიკის შემოწმება (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); ღრუბლოვანი მეტამონაცემების ჰოსტებზე მარშრუტიზაცია არასოდეს ხდება. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | როდესაც OIDC ჩართულია, გამორთეთ პაროლით შესვლა, რათა მომხმარებლებმა ავთენტიფიკაცია მხოლოდ OIDC-ის ერთიანი შესვლის საშუალებით შეძლონ. ასევე მიიღება მარტივი ალიასი `OIDC_DISABLE_PASSWORD_LOGIN`; იმავე გასაღების მქონე მართვის პანელის ფუნქციურ ალამს უპირატესობა ენიჭება. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-ის მარტივი ალიასი (#10889). | +| ცვლადი | ნაგულისხმევი მნიშვნელობა | წყაროს ფაილი | აღწერა | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | მანქანის ციფრული ანაბეჭდის შესაქმნელად აპარატურულ იდენტიფიკატორებთან გაერთიანებული salt. იზოლაციისთვის შეცვალეთ თითოეული განთავსებისთვის. | +| `OMNIROUTE_CLI_SALT` | _(არ არის დაყენებული = თითოეული ინსტალაციისთვის შემთხვევითი salt, რომელიც ინახება მისამართზე `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt ლოკალური CLI ავთენტიფიკაციის ტოკენის წარმოსაქმნელად. ამ მნიშვნელობის დაყენება მანქანაზე არსებულ ყველა CLI ტოკენს განაახლებს და შენახულ salt-თან შედარებით ყოველთვის პრიორიტეტულია. იხილეთ `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | სესიის cookie-ებზე აყენებს `Secure` ალამს. HTTPS-ის უკან მუშაობისას მნიშვნელობა **აუცილებლად უნდა იყოს `true`**. | +| `REQUIRE_API_KEY` | `false` | API შუალედური პროგრამული უზრუნველყოფა | როდესაც არის `true`, ყველა `/v1/*` პროქსი-მოთხოვნა უნდა შეიცავდეს მოქმედ API გასაღებს. **ეს ალამი არ ზღუდავს `GET /v1/models`-ს**, რომელიც სანაცვლოდ მართვის პანელში შესვლის პოზიციას (`requireAuthForModels`) მიჰყვება — ამიტომ `/v1/models`-დან მიღებული `401` არ ნიშნავს, რომ ინფერენსი დაცულია. იხილეთ `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | მართვის პანელის ინტერფეისში API გასაღებების სრული მნიშვნელობების ჩვენების საშუალებას იძლევა. კონფიგურირდება მართვის პანელის ფუნქციური ალმებიდან; საზიარო ინსტანციებზე უსაფრთხოების რისკს წარმოადგენს. | +| `NO_LOG_API_KEY_IDS` | _(ცარიელი)_ | `src/lib/compliance/index.ts` | მძიმით გამოყოფილი API გასაღების ID-ები, რომლებისთვისაც მოთხოვნების ჟურნალში ჩაწერა გამოტოვებულია (GDPR-თან შესაბამისობა). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(არ არის დაყენებული = შეუზღუდავი)_ | `src/shared/utils/apiKeyPolicy.ts` | დღიური მოთხოვნების სარეზერვო ბიუჯეტი, რომელიც გამოიყენება API გასაღებებისთვის, რომელთა `rate_limits` სვეტი null-ია. დაუყენებელი ან ცარიელი: არაპირდაპირი ლიმიტი არ არსებობს (#2289, #11017). `0` იგივეა (შეუზღუდავი). დადებითი მთელი რიცხვი N რთავს N/დღე, 5N/კვირა, 20N/თვე ლიმიტებს. არასწორად ფორმატირებული არაცარიელი მნიშვნელობები ბრუნდება ძველ 1000/დღე, 5000/კვირა, 20000/თვე ფანჯრებზე. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | მოთხოვნის სხეულის მაქსიმალური დასაშვები ზომა. უარყოფს ამ ლიმიტზე დიდი ზომის მონაცემებს. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ამ ზღვრის ტოლი ან მასზე დიდი მოთხოვნის რეალური სხეულები JSON-ის დამუშავებამდე იღებენ ატომურ, პროცესის ფარგლებში ლოკალურ, მძიმე დატვირთვის დაშვების იჯარას (BYTE გზა, მათ შორის `POST /v1/responses`). სტრუქტურულად მძიმე მოთხოვნების მსგავსად გამოიყენება თავისუფალი რესურსების საკმარისი მარაგის იგივე [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) გამონაკლისი; ის კვლავ შეზღუდულია `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ით / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), რათა [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) არ განმეორდეს. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ჩატის მარშრუტის მკაცრი ზღვარი, რომელიც მოწმდება შეზღუდული მიღებისას წაკითხული ბაიტების მიხედვით, მათ შორის მოთხოვნებისთვის, რომლებშიც `Content-Length` არ არის მითითებული, არასწორია ან რეალობას არ შეესაბამება; ზღვრის გადაჭარბებისას ბრუნდება `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(დაყენებული არ არის — მოთხოვნების რაოდენობაზე ზღვარი არ მოქმედებს)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** მოთხოვნების რაოდენობაზე დაფუძნებული ეს მოძველებული ზღვარი ახლა მხოლოდ აშკარად დაყენებისას მოქმედებს. თუ ის დაყენებული არ არის (ნაგულისხმევი მდგომარეობა), მძიმე ჩატის მოთხოვნების დაშვება სანაცვლოდ კონტროლდება `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ით — ბაიტების ავტომატურად გამოთვლილი ბიუჯეტით, რომლის ზომაც განისაზღვრება პროცესის მეხსიერების რეალური ზღვრის მიხედვით **ერთ პროცესში** (ერთი V8 heap). ორი ერთმანეთზე გადაფარული, დაახლოებით 750k-ტოკენიანი `/v1/responses` მოთხოვნა დაახლოებით 12 GiB-ის heap-ებს ავარიულად ასრულებს (#7849) — ეს **მეხსიერების ბიუჯეტის** გაფრთხილებაა და არა პროდუქტის მკაცრი მაქსიმუმი, 2. გამართულმა პროცესმა (რომლის heap-იც განტვირთვის კოეფიციენტზე ქვემოთაა) შესაძლოა დაუშვას მეტი ერთდროული ხანგრძლივი `/v1/responses` მოთხოვნა `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`-ის მეშვეობით. ათეულობით ხანგრძლივი SSE კლიენტის (40–50) მხარდაჭერა დამოკიდებულია heap-ზე + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES`-ზე / #10110-ზე და არ ნიშნავს „მაქსიმუმ 2“-ს. ამის დაუფიქრებლად გაზრდა „ჰოსტის გამოსაყენებლად“ ხელახლა წარმოშობს #7849-ს. გაამრავლეთ **heap-ები** **N დამოუკიდებელი `DATA_DIR`-ით** (#11024); არასოდეს გამოიყენოთ `replicas>1` ერთი SQLite ფაილისთვის. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ავტომატურად გამოთვლილი)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ავტომატურად გამოთვლილი მიღების ბაიტური ბიუჯეტის ჩანაცვლება (V8/cgroup მეხსიერების უფრო მკაცრი ზღვრის 25%, გაყოფილი გარდამავალი 8x გაძლიერების კოეფიციენტზე). როგორც გამოთვლილი, ისე აშკარად მითითებული მნიშვნელობები იზღუდება 8 MiB–2 GiB დიაპაზონით. ეფექტურ ბიუჯეტზე დიდი სხეული დაუყოვნებლივ სრულდება შეცდომით `413 body_exceeds_budget`; ცალ-ცალკე მომსახურებადი სხეულების შეჯიბრება კვლავ ხელახლა საცდელი `503`-ით სრულდება. 40–50 ერთდროული ხანგრძლივი SSE კლიენტის მხარდაჭერა დამოკიდებულია ამ ბიუჯეტზე + heap-ზე და არ წარმოადგენს მკაცრ „მაქსიმუმ 2“-ს. კონფიგურაციის შეცვლამდე `/api/monitoring/health`-ზე შეამოწმეთ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Heap-ზე ზეწოლის დროს განტვირთვის კოეფიციენტი (`heapUsed / heap_size_limit`) ბაიტური და სტრუქტურული მძიმე მოთხოვნების დაშვებისთვის (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ზე მეტი ერთდროული მძიმე მოთხოვნა ხელახლა საცდელი `503`-ით მხოლოდ მაშინ განიტვირთება, როდესაც heap ამ კოეფიციენტის ტოლი ან მასზე მაღალია; გამართული heap-ის შემთხვევაში მოთხოვნა healthy-headroom-ის მეშვეობით დაიშვება. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ნაგულისხმევად `1`) | `src/shared/middleware/chatBodyAdmission.ts` | შეზღუდული დამატებითი ტევადობა გამართული heap-ის სწრაფი გზისთვის (#10437), როგორც სტრუქტურული, ისე ბაიტური მოთხოვნებისთვის (`admitChatRequest`, მათ შორის სხეულებისთვის ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). ამ ზღვრის გარეშე ყოველი დატვირთული, მაგრამ გამართული heap-ის მქონე მოთხოვნა დაშვების კონტროლს ყოველგვარი ზღვრის გარეშე გვერდს უვლიდა. როდესაც healthy-heap გზით ამდენი ერთდროული lease გააქტიურდება, შემდგომი დატვირთული მოთხოვნები გადადის იმავე შეზღუდული ლოდინის/განტვირთვის გზაზე, რომელიც heap-ზე რეალური ზეწოლისას გამოიყენება. `0` გვერდის ავლას სრულად თიშავს. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | შეტყობინებების რაოდენობა, რომლის საფუძველზეც ჩატის მოთხოვნა მძიმედ კლასიფიცირდება მაშინაც კი, როდესაც მისი სხეული ბაიტურ ზღვარზე მცირეა. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ხელსაწყოების რაოდენობა, რომლის საფუძველზეც ჩატის მოთხოვნა მძიმედ კლასიფიცირდება მაშინაც კი, როდესაც მისი სხეული ბაიტურ ზღვარზე მცირეა. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | სტრიქონის ზომაზე დაფუძნებული ტოკენების კონსერვატიული შეფასება, რომლის მიხედვითაც მოთხოვნა მძიმედ კლასიფიცირდება; ეს დაშვების ღირებულების მიახლოებითი მაჩვენებელია და არა პროვაიდერის მიერ ანგარიშსწორებისთვის გამოყენებული ტოკენიზაცია. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (გამორთულია) | `src/shared/middleware/chatBodyAdmission.ts` | ჩატის ისტორიის არასავალდებულო, აშკარად ჩასართავი ზღვარი. ნაგულისხმევად გამორთულია: შეტყობინებების რაოდენობა განთავსების პოლიტიკაა და არა მოთხოვნის უნივერსალური თვისება, ხოლო აქ ზღვრის დაწესება საუბრებს საბოლოო `413`-ით უარყოფს, სანამ შეკუმშვის კონვეიერი მათ მომსახურებად გარდაქმნას შეძლებს. Heap-ის ზრდა იზღუდება `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ითა და heap-ზე ზეწოლისას განტვირთვით. მეხსიერებით შეზღუდულ განთავსებებში, რომლებსაც მკაცრი ზედა ზღვარი სჭირდებათ, მიუთითეთ დადებითი მნიშვნელობა; გადაჭარბებისას დაბრუნდება სტრუქტურირებული `413`, რომელიც შეკუმშვის აუცილებლობას მიუთითებს. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | მკაცრი ზღვარი ზემდგომი არანაკადური პასუხისთვის, რომელიც სრულად ბუფერიზდება მეხსიერებაში. ამ ზღვრის გადაჭარბებისას ზემდგომი წამკითხველი უქმდება და მოთხოვნა სწრაფად სრულდება შეცდომით, ნაცვლად იმისა, რომ შეუზღუდავი სტრიქონი გაიზარდოს heap-ის ამოწურვამდე. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ზედა დონის პასუხის სათაურებიდან გადმოცემული ბაიტების მაქსიმალური რაოდენობა. ბიუჯეტის გადაჭარბებისას ნაკლები პრიორიტეტის მქონე სათაურები (მაგ., მორგებული `x-codex-*`, `x-oai-request-id`) გამოიტოვება, რათა დაცული იყოს უკუპროქსების სათაურების გავრცელებული ლიმიტები. ზედა დონიდან მეტი მეტამონაცემის გადასაცემად დააყენეთ უფრო მაღალი მნიშვნელობა, თუმცა ეს პასუხის სათაურების ზომას გაზრდის. | +| `CORS_ORIGIN` | _(არ არის დაყენებული)_ | `src/server/cors/origins.ts` | მოძველებული, ერთი წყაროსთვის განკუთვნილი CORS-ის დაშვებული სია. ახალი განთავსებებისთვის უპირატესობა მიანიჭეთ `CORS_ALLOWED_ORIGINS`-ს. CORS განკუთვნილია მხოლოდ სხვა წყაროდან მომუშავე ბრაუზერული API-კლიენტებისთვის; ავთენტიფიცირებული მართვის პანელის ჩაწერის მოთხოვნები ამის ნაცვლად იყენებს იმავე წყაროს მოთხოვნებსა და სესიაზე მიბმულ CSRF-დაცვას. | +| `CORS_ALLOWED_ORIGINS` | _(არ არის დაყენებული)_ | `src/server/cors/origins.ts` | მძიმეებით გამოყოფილი CORS-ის დაშვებული სია. ვაილდქარდი არ გაიგზავნება, თუ `CORS_ALLOW_ALL=true` ცალსახად არ არის კონფიგურირებული. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | მხოლოდ დეველოპმენტისთვის განკუთვნილი საგანგებო მექანიზმი, რომელიც ბრაუზერის ნებისმიერ `Origin`-ს იმეორებს. არ ჩართოთ გაზიარებულ ან საწარმოო განთავსებებში. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | მოძველებული ალიასი. მის მართვის პანელის გადამრთველს (მონაცემთა ბაზის გადაფარვას) ამ გარემოს ცვლადის მნიშვნელობამდე კითხულობენ; `false`, `0`, `no` ან `off` რომელიმე ერთშიც გამორთავს ჰოსტის შემოწმებებს ისე, როგორც ამას `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` აკეთებს, თუნდაც იმ ალის პანელის გადამრთველი გამორთულად არის შენახული; სხვა მნიშვნელობები დაცვას ქვემოთ მოყვანილ ორ ალამს უტოვებს. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | გამორთავს გამავალი URL-ების დამცავის ჰოსტის შემოწმებებს, ღრუბლოვანი მეტამონაცემების ბლოკის ჩათვლით (მთელი 169.254.0.0/16, metadata.google.internal), იმ პროვაიდერის გზებზე, რომლებიც დაცვის რეჟიმს მიჰყვებიან: URL-ების ვალიდაცია, მოდელების აღმოჩენა, პროვაიდერის კვანძების საბაზისო URL-ები (დისტანციური ხელახალი რანჟირების კვანძების ჩათვლით) და proxy-fallback ტესტი, ასევე სხვა პროვაიდერთან ახლოს მდგომ გამავალ გზებზე, როგორიცაა დისტანციური სურათის/მედიის მიღება. ასევე აძლევს უფლებას კერძო webhook-სამიზნეებს, გარდა ღრუბლოვანი მეტამონაცემების/link-local საბოლოო წერტილებისა, რომლებიც ამ ალის ჩართვის დროსაც დაბლოკილი რჩება. `BaseExecutor.execute()`-ის მეშვეობით გადაგზავნილი ჩატის მოთხოვნები ინარჩუნებენ ღრუბლოვანი მეტამონაცემების შემოწმებას. ლოკალური პროვაიდერები ნაგულისხმევი მნიშვნელობებით მუშაობენ: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ნაგულისხმევი `true`) უკვე აძლევს უფლებას ლოკალურ და LAN URL-ებს ვალიდაციის, აღმოჩენისა და პროვაიდერის კვანძების გზებზე; proxy-fallback ტესტი და კერძო webhook-სამიზნეები მხოლოდ ამ ალამს იყენებს და მისი გამორთვის დროს ბლოკავს ლოკალურ/LAN ჰოსტებს. გადაფარავს `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`-ს. მართვის პანელის გადამრთველიდან შენახული მნიშვნელობა ამ ცვლადზე უპირატესია. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | აძლევს უფლებას პროვაიდერის URL-ებს ლოკალურ/კერძო მისამართებზე (127.0.0.1, localhost, LAN, კერძო დიაპაზონები) პროვაიდერის ვალიდაციის, მოდელების აღმოჩენის, ჩატისა და დაცვის რეჟიმის მიერ მართვადი დანარჩენი პროვაიდერთან ახლოს მდგომი გამავალი გზებისთვის. **ნაგულისხმევი მნიშვნელობაა `true`** (უპირატესობა ლოკალურს): ამ დროს დამცავი ბლოკავს ღრუბლოვანი მეტამონაცემების საბოლოო წერტილებს — მთელ 169.254.0.0/16-ს პლუს ცნობილ მეტამონაცემთა ჰოსტის სახელებს (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). მხოლოდ საჯარო რეჟიმისთვის დააყენეთ `false`, რომელიც ბლოკავს კერძო და უკუმარყუჟის ჰოსტებსაც. დამცავი ამოწმებს ჰოსტის სახელს ან IP-ლიტერალს ისე, როგორც დაწერილია, ნებისმიერ DNS-ძიებამდე; მხოლოდ საჯარო რეჟიმში safe-fetch გარსი დამატებით ხსნის ჰოსტის სახელს და უარყოფს ნებისმიერ კერძო პასუხს. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` გამორთავს ამ შემოწმებებს მის სტრიქონში ჩამოთვლილ გზებზე. ჩაშენებული ლოკალური პროვაიდერები, როგორებიცაა LM Studio, Ollama, vLLM, Llamafile და Triton, გამოტოვებენ ჰოსტის ყველა შემოწმებას გასაღების ვალიდაციისა და ჩატისთვის. მართვის პანელის გადამრთველიდან შენახული მნიშვნელობა ამ ცვლადზე უპირატესია. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | მიეცით `/v1/audio/*` მარშრუტებს (ტრანსკრიფცია, მეტყველება, თარგმანი) localhost-ის გარეთ განთავსებული OpenAI-თან თავსებადი პროვაიდერის კვანძის გამოყენების უფლება. ნაგულისხმევად გამორთულია — აუდიოს დისტანციურ ჰოსტზე მარშრუტიზაცია ცვლის გამავალი ტრაფიკის იდენტობას და ოპერატორის ცალსახა გადაწყვეტილებას საჭიროებს. უკუმარყუჟის/კერძო კვანძები (localhost, 127.0.0.1, 172.16-31.x) ყოველთვის დაშვებულია და ეს პარამეტრი მათზე გავლენას არ ახდენს. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | მიეცით `POST /v1/rerank`-ს (და მეხსიერების ძრავის უკუმარყუჟის ხელახალი რანჟირების ეტაპს) localhost-ის გარეთ განთავსებული OpenAI-თან თავსებადი პროვაიდერის კვანძის გამოყენების უფლება — LAN-ის კომპიუტერის ან Tailscale-ის პირის, რომელზეც გაშვებულია TEI, Infinity, vLLM და სხვ. ნაგულისხმევად გამორთულია — დისტანციურ ჰოსტზე მარშრუტიზაცია ცვლის გამავალი ტრაფიკის იდენტობას და ოპერატორის ცალსახა გადაწყვეტილებას საჭიროებს. უკუმარყუჟის კვანძები (localhost, 127.0.0.1, 172.16-31.x) ყოველთვის დაშვებულია და ეს პარამეტრი მათზე გავლენას არ ახდენს. დისტანციური კვანძის საბაზისო URL-მაც უნდა გაიაროს პროვაიდერის გამავალი URL-ების პოლიტიკის შემოწმება (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | როდესაც OIDC ჩართულია, გამორთეთ პაროლით შესვლა, რათა მომხმარებლებმა ავთენტიფიკაცია მხოლოდ OIDC-ის ერთიანი შესვლის საშუალებით შეძლონ. ასევე მიიღება მარტივი ალიასი `OIDC_DISABLE_PASSWORD_LOGIN`; იმავე გასაღების მქონე მართვის პანელის ფუნქციურ ალამს უპირატესობა ენიჭება. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-ის მარტივი ალიასი (#10889). | ### გამყარების საკონტროლო სია diff --git a/docs/i18n/km/CONTRIBUTING.md b/docs/i18n/km/CONTRIBUTING.md index d499139b3f53..21d81ee9f9e8 100644 --- a/docs/i18n/km/CONTRIBUTING.md +++ b/docs/i18n/km/CONTRIBUTING.md @@ -401,20 +401,20 @@ docs/ ## បញ្ជីត្រួតពិនិត្យ Pull Request -- [ ] តេស្តឆ្លងកាត់ (`npm test`) -- [ ] ការត្រួតពិនិត្យ Lint ឆ្លងកាត់ (`npm run lint`) -- [ ] ការ build ជោគជ័យ (`npm run build`) -- [ ] បានបន្ថែម TypeScript types សម្រាប់ functions និង interfaces សាធារណៈថ្មី -- [ ] គ្មានព័ត៌មានសម្ងាត់ ឬតម្លៃ fallback ដែលបាន hardcode -- [ ] ព័ត៌មានសម្ងាត់សាធារណៈពីប្រភពខាងដើមត្រូវបានបង្កប់តាមរយៈ `resolvePublicCred()` (សូមមើល [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) មិនត្រូវបង្កប់ជា literals ឡើយ -- [ ] Error responses ឆ្លងកាត់ `buildErrorBody()` / `sanitizeErrorMessage()` — គ្មាន stack traces ដើមនៅក្នុង response bodies (សូមមើល [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell commands (`exec` / `spawn`) បញ្ជូនតម្លៃ runtime តាមរយៈ `env` មិនមែនតាមរយៈ string interpolation -- [ ] Inputs ទាំងអស់ត្រូវបានផ្ទៀងផ្ទាត់ដោយប្រើ Zod schemas -- [ ] បានបន្ថែម **fragment** នៃ changelog នៅក្រោម `changelog.d/{features|fixes|maintenance}/-.md` សម្រាប់ការផ្លាស់ប្តូរដែលអ្នកប្រើប្រាស់អាចមើលឃើញ (សូមមើល [`changelog.d/README.md`](./changelog.d/README.md)) — **កុំ** កែសម្រួល `CHANGELOG.md` ដោយផ្ទាល់; fragments ត្រូវបានប្រមូលបញ្ចូលគ្នានៅពេលចេញ release ហើយមិនដែលប៉ះទង្គិចគ្នារវាង PRs ឡើយ -- [ ] ឯកសារត្រូវបានធ្វើបច្ចុប្បន្នភាព (ប្រសិនបើអាចអនុវត្តបាន) -- [ ] មិនមាន CodeQL / Secret-Scanning alerts ថ្មីត្រូវបានបើក ឬ alerts នីមួយៗត្រូវបានបដិសេធដោយមានយុត្តិកម្មបច្ចេកទេសយោងទៅឯកសារ `docs/security/` ដែលពាក់ព័ន្ធ +- [ ] ការធ្វើតេស្តជោគជ័យ (`npm test`) +- [ ] ការត្រួតពិនិត្យ Lint ជោគជ័យ (`npm run lint`) +- [ ] ការ Build ជោគជ័យ (`npm run build`) +- [ ] បានបន្ថែម TypeScript types សម្រាប់ public functions និង interfaces ថ្មី +- [ ] មិនមាន secrets ឬ fallback values ដែលសរសេរបង្កប់ដោយផ្ទាល់ +- [ ] បានបង្កប់ public upstream credentials តាមរយៈ `resolvePublicCred()` (សូមមើល [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) ដោយមិនប្រើ literals ជាដាច់ខាត +- [ ] Error responses ត្រូវបានបញ្ជូនតាមរយៈ `buildErrorBody()` / `sanitizeErrorMessage()` — មិនមាន raw stack traces នៅក្នុង response bodies ទេ (សូមមើល [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell commands (`exec` / `spawn`) បញ្ជូន runtime values តាមរយៈ `env` មិនមែនតាមរយៈ string interpolation ទេ +- [ ] inputs ទាំងអស់ត្រូវបានផ្ទៀងផ្ទាត់ដោយប្រើ Zod schemas +- [ ] បានបន្ថែម **fragment** នៃ Changelog នៅក្រោម `changelog.d/{features|fixes|maintenance}/-.md` សម្រាប់ការផ្លាស់ប្តូរដែលប៉ះពាល់ដល់អ្នកប្រើប្រាស់ (សូមមើល [`changelog.d/README.md`](./changelog.d/README.md)) — **កុំ**កែសម្រួល `CHANGELOG.md` ដោយផ្ទាល់; fragments ត្រូវបានបូកបញ្ចូលគ្នានៅពេលចេញផ្សាយ ហើយមិនបង្កឱ្យមាន conflicts រវាង PRs ទេ +- [ ] បានធ្វើបច្ចុប្បន្នភាពឯកសារ (ប្រសិនបើអាចអនុវត្តបាន) +- [ ] មិនបានបង្កើត CodeQL / Secret-Scanning alerts ថ្មី ឬ alert នីមួយៗត្រូវបានបដិសេធដោយមានហេតុផលបច្ចេកទេសយោងទៅឯកសារ `docs/security/` ដែលពាក់ព័ន្ធ - [ ] Routes ដែលបង្កើត child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) ត្រូវបានចាត់ថ្នាក់ជា `isLocalOnlyPath()` នៅក្នុង `src/server/authz/routeGuard.ts` — សូមមើល [ច្បាប់តឹងរ៉ឹង #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] គ្មាន trailers `Co-Authored-By` នៅក្នុង commit messages — commits ត្រូវតែបង្ហាញតែក្រោម Git identity របស់ម្ចាស់ repository ប៉ុណ្ណោះ (ច្បាប់តឹងរ៉ឹង #16) +- [ ] មិនមាន AI/bot `Co-authored-by` trailers នៅក្នុង commit messages ទេ (ច្បាប់តឹងរ៉ឹង #16) — human collaborators ដែលស្នាដៃរបស់ពួកគេត្រូវបានប្រើឡើងវិញ ត្រូវបានផ្តល់កិត្តិយសដោយប្រើ trailers ស្តង់ដារ `Co-authored-by: Name ` --- diff --git a/docs/i18n/km/docs/architecture/QUALITY_GATES.md b/docs/i18n/km/docs/architecture/QUALITY_GATES.md index a7e1376b0104..a25089e669b5 100644 --- a/docs/i18n/km/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/km/docs/architecture/QUALITY_GATES.md @@ -15,215 +15,215 @@ --- -## បញ្ជីសារពើភណ្ឌ Gate និងប្រូហ្វាល់ប្រតិបត្តិការ +## បញ្ជីសារពើភណ្ឌ Gate និងប្រូហ្វាល់ការប្រតិបត្តិ -### ការអនុញ្ញាតបេក្ខភាព +### ការទទួលយកបេក្ខភាព -Workflow របស់ CI និង Quality Gates នីមួយៗបញ្ចេញលទ្ធផលវិនិច្ឆ័យដែលមានស្ថិរភាព៖ `Gate / CI` និង -`Gate / Quality`។ គោលការណ៍អនុញ្ញាតដែលមានកំណែរបស់វារាយបញ្ជីគ្រប់ job ខ្សែខាងលើ -ថាតម្រូវ ឬជាការណែនាំ។ job តម្រូវដែលអាចអនុវត្តបានត្រូវតែជោគជ័យ៖ លទ្ធផលដែល -បាត់ លុបចោល រំលង កំពុងរង់ចាំ និងមិនស្គាល់ មិនអាចបញ្ជាក់ PASS បានទេ។ ការចាត់ថ្នាក់ -docs-only ឬ catalog-only ដែលត្រឹមត្រូវ អាចធ្វើឱ្យ lane កូដមិនអាចអនុវត្តបាន; -draft PR មិនមែនជាបេក្ខភាពដែលត្រូវបានទទួលយកទេ។ label `hotfix` មិនលើកលែងភស្តុតាងឡើយ។ +Workflow CI និង Quality Gates នីមួយៗបញ្ចេញសេចក្តីសម្រេចដែលមានឈ្មោះថេរ៖ `Gate / CI` និង +`Gate / Quality`។ គោលការណ៍ទទួលយកដែលមានកំណែរបស់វារាយបញ្ជី job ខ្សែខាងលើនីមួយៗ +ថាជា job ចាំបាច់ ឬជាប្រឹក្សា។ job ចាំបាច់ដែលអនុវត្តបានត្រូវតែជោគជ័យ៖ លទ្ធផលដែលបាត់, +ត្រូវបានបោះបង់, ត្រូវបានរំលង, កំពុងរង់ចាំ និងមិនស្គាល់ មិនអាចបញ្ជាក់ PASS បានទេ។ ចំណាត់ថ្នាក់ +docs-only ឬ catalog-only ដែលត្រឹមត្រូវអាចធ្វើឱ្យ lane កូដមួយមិនអាចអនុវត្តបាន; +draft PR មិនមែនជាបេក្ខភាពដែលត្រូវបានទទួលយកទេ។ ស្លាក `hotfix` មិនលើកលែងតម្រូវការភស្តុតាងឡើយ។ -Workflow ទាំងពីរគ្របដណ្តប់ PR និង push ទៅកាន់ branch main/release, ការបញ្ជាដំណើរការដោយដៃ និង +Workflow ទាំងពីរគ្របដណ្ដប់លើ PR និង push ទៅកាន់ branch main/release, ការបញ្ជាដំណើរការដោយដៃ និង ព្រឹត្តិការណ៍ merge-group។ Push, dispatch និង merge-group ដំណើរការជម្រើសពេញលេញ។ Fork -និង merge group ប្រើ hosted runner សម្រាប់ job ដែលតាមធម្មតាជ្រើសរើស self-hosted -runner; ត្រូវតែផ្ទៀងផ្ទាត់ថាមានសមត្ថភាព hosted គ្រប់គ្រាន់មុនពេលដាក់ឱ្យប្រើប្រាស់។ +និង merge group ប្រើ hosted runner សម្រាប់ job ដែលបើមិនដូច្នោះទេនឹងជ្រើស self-hosted +runner; ត្រូវតែផ្ទៀងផ្ទាត់ថាមានសមត្ថភាព hosted គ្រប់គ្រាន់ មុនពេលដាក់ឱ្យប្រើប្រាស់។ -បង្កាន់ដៃ JSON នីមួយៗកំណត់អត្តសញ្ញាណ SHA ដែលបាន check out, workflow run និង attempt។ -CLI បដិសេធភាពមិនត្រូវគ្នារវាង checkout/event SHA។ ការធ្វើតេស្ត Workflow ភ្ជាប់សមាជិកភាពគោលការណ៍ -ទៅនឹងបញ្ជី `needs` របស់ verdict job ដើម្បីកុំឱ្យ lane ថ្មី ឬដែលបានដកចេញ បាត់ទៅដោយស្ងៀមស្ងាត់។ -បង្កាន់ដៃទាំងនេះគ្របដណ្តប់តែ workflow របស់វាផ្ទាល់ប៉ុណ្ណោះ មិនមែនការបោះពុម្ពផ្សាយ ការដាក់ឱ្យប្រើប្រាស់ ឬដំណើរការខាងក្នុង -របស់ advisory scanner ដែលមានស្រាប់ទេ។ ការធ្វើឱ្យ check name ទាំងពីរសកម្មនៅក្នុង branch rule គឺជា -ការផ្លាស់ប្តូរផ្នែករដ្ឋបាលដាច់ដោយឡែក; ការបន្ថែម job ទាំងនេះមិនការពារ branch ដោយខ្លួនវាឡើយ។ +បង្កាន់ដៃ JSON នីមួយៗកំណត់អត្តសញ្ញាណ SHA ដែលបាន check out, ការដំណើរការ workflow និង attempt។ +CLI បដិសេធភាពមិនត្រូវគ្នារវាង checkout/event SHA។ ការធ្វើតេស្ត workflow ភ្ជាប់សមាជិកភាពគោលការណ៍ +ទៅនឹងបញ្ជី `needs` របស់ verdict job ដើម្បីកុំឱ្យ lane ថ្មី ឬ lane ដែលបានដកចេញ បាត់ទៅដោយស្ងៀមស្ងាត់។ +បង្កាន់ដៃទាំងនេះគ្របដណ្ដប់លើ workflow របស់ខ្លួន មិនមែនលើការបោះពុម្ពផ្សាយ, ការដាក់ឱ្យប្រើប្រាស់ ឬផ្នែកខាងក្នុង +នៃ advisory scanner ដែលមានស្រាប់ទេ។ ការធ្វើឱ្យឈ្មោះ check ទាំងពីរសកម្មក្នុងច្បាប់ branch គឺជា +ការផ្លាស់ប្ដូរផ្នែករដ្ឋបាលដោយឡែកមួយ; ការបន្ថែម job ទាំងនេះមិនការពារ branch ដោយខ្លួនឯងទេ។ -### បញ្ជីសារពើភណ្ឌ static scan +### បញ្ជីសារពើភណ្ឌការស្កេនស្ថិតិ បញ្ជីសារពើភណ្ឌ npm-alias ដែលមានកំណែ និងសមាជិកភាព static-scan ស្ថិតនៅក្នុង `config/quality/gate-manifest.json`។ ដំណើរការ `npm run check:gate-manifest` ដើម្បីផ្ទៀងផ្ទាត់ -ឈ្មោះ script និង command ជាក់លាក់ទល់នឹង `package.json`; ការបន្ថែម ការដកចេញ និង -ការប្រែប្រួល command នឹងធ្វើឱ្យទាំង local hook និង job ចាត់ថ្នាក់ការផ្លាស់ប្តូរក្នុង CI បរាជ័យ។ -alias មិនមែនជា workflow job, matrix instance ឬ test case ទេ៖ មិនត្រូវបង្ហាញចំនួនទាំងនេះ +ឈ្មោះ script និង command ពិតប្រាកដធៀបនឹង `package.json`; ការបន្ថែម, ការដកចេញ និង +ការប្រែប្រួល command នឹងធ្វើឱ្យទាំង local hook និង job ចំណាត់ថ្នាក់ការផ្លាស់ប្ដូរក្នុង CI បរាជ័យ។ +alias មួយមិនមែនជា workflow job, matrix instance ឬ test case ទេ៖ មិនត្រូវបង្ហាញចំនួនទាំងនេះ ថាអាចប្រើជំនួសគ្នាបានឡើយ។ ប្រើ `npm run quality:scan -- --list` ឬ `npm run quality:scan:fast -- --list` -ដើម្បីពិនិត្យមើល alias ដែលបានជ្រើសរើសដោយមិនប្រតិបត្តិវា។ runner ហៅ -npm entrypoint ដូច្នេះ runtime របស់វា (រួមទាំង Bun នៅកន្លែងដែលបានកំណត់រចនាសម្ព័ន្ធ) ត្រូវបានរក្សាទុក។ -manifest កត់ត្រា alias ដែលនៅក្រៅប្រូហ្វាល់ទាំងនោះថាត្រូវបានហៅដោយឡែក ហើយ -command ថែទាំត្រូវបានហាមឃាត់ក្នុងប្រូហ្វាល់ scan ដែលបានតែអាន។ - -ប្រូហ្វាល់ទាំងនេះគ្របដណ្តប់តែ static scan ប៉ុណ្ណោះ។ វាមិនបញ្ជាក់ product test, -coverage, packaging, external check ឬការទទួលយកបេក្ខភាពពេញលេញសម្រាប់ release ឡើយ។ -ការអនុញ្ញាតរបស់ Workflow ប្រើ `config/quality/admission-policy.json` ដែលបានភ្ជាប់ និង +ដើម្បីត្រួតពិនិត្យ alias ដែលបានជ្រើស ដោយមិនប្រតិបត្តិពួកវា។ runner ហៅ npm entrypoint +ដូច្នេះ runtime របស់វា (រួមទាំង Bun នៅកន្លែងដែលបានកំណត់រចនាសម្ព័ន្ធ) ត្រូវបានរក្សាទុក។ +manifest កត់ត្រា alias នៅក្រៅប្រូហ្វាល់ទាំងនោះថាត្រូវបានហៅដោយឡែក ហើយ +maintenance command ត្រូវបានហាមឃាត់ក្នុងប្រូហ្វាល់ស្កេន read-only។ + +ប្រូហ្វាល់ទាំងនេះគ្របដណ្ដប់តែ static scan ប៉ុណ្ណោះ។ ពួកវាមិនបញ្ជាក់ជាផ្លូវការលើ product test, +coverage, packaging, external check ឬការទទួលយក release ពេញលេញរបស់បេក្ខភាពឡើយ។ +ការទទួលយក workflow ប្រើ `config/quality/admission-policy.json` ដែលបានភ្ជាប់ និង `scripts/quality/admission-verdict.mjs`។ ប្រូហ្វាល់ release-observer នៅតែដាច់ដោយឡែក; -ពិនិត្យ check និងបង្កាន់ដៃដែលអាចអនុវត្តបានរបស់វាដោយឡែក។ បញ្ជីសារពើភណ្ឌជាអត្ថបទ -ខាងក្រោមគឺជាឯកសារយោង មិនមែនជាភស្តុតាងថា gate បានដំណើរការពិតប្រាកដទេ។ +ត្រូវត្រួតពិនិត្យ check និងបង្កាន់ដៃដែលអនុវត្តបានរបស់ពួកវាដោយឯករាជ្យ។ បញ្ជីសារពើភណ្ឌជា +អត្ថបទខាងក្រោមគឺជាឯកសារយោង មិនមែនជាភស្តុតាងថា gate ពិតជាបានដំណើរការឡើយ។ Script ស្ថិតនៅក្រោម `scripts/check/` (policy gate) និង `scripts/quality/` (ratchet engine)។ -ប្រភពការពិតរបស់ CI គឺ `.github/workflows/ci.yml`។ +ប្រភពសេចក្តីពិតរបស់ CI គឺ `.github/workflows/ci.yml`។ -### Fast-path របស់ Release PR (`quality.yml`) +### ផ្លូវលឿនសម្រាប់ Release PR (`quality.yml`) -`.github/workflows/quality.yml` បំពេញបន្ថែម CI សម្រាប់ PR របស់ main/release, push ទៅ -protected branch, dispatch និង merge group។ PR ប្រើ fast check ដែលត្រងតាម path។ duplicate build -ដែលបានបិទជាអចិន្ត្រៃយ៍ត្រូវបានដកចេញ; check build/package/boot ពិតប្រាកដនៅតែមានក្នុង CI។ +`.github/workflows/quality.yml` បំពេញបន្ថែម CI លើ main/release PR, push ទៅកាន់ protected branch, +dispatch និង merge group។ PR ប្រើ fast check ដែលបានច្រោះតាម path។ duplicate build ដែលបាន +បិទជាអចិន្ត្រៃយ៍ត្រូវបានដកចេញ; build/package/boot check ពិតប្រាកដនៅតែមានក្នុង CI។ -| Job | វិសាលភាព | ការទប់ស្កាត់ | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `Docs Gates (fast-path)` | PR ឯកសារ/កូដ; API docs refs និង docs-all | បាទ/ចាស | -| `Fast Quality Gates` | PR កូដ; static check, typecheck, dashboard typecheck, impacted unit test | បាទ/ចាស | -| `Forgotten sibling tests` | PR កូដ; module ដែលបានផ្លាស់ប្តូរត្រូវបានតាមដានទៅ static consumer និង candidate sibling test; path របស់ barrel និង dynamic-import ត្រូវបានរាយការណ៍ជាការវិនិច្ឆ័យណែនាំ ជាមួយ exception ក្នុង allowlist ដែលយោងដល់ | **ការណែនាំ** | -| `Vitest (fast-path)` | PR កូដ; fast vitest suite | បាទ/ចាស | -| `Unit Tests fast-path` | PR កូដ; unit suite ចំនួន 4 shard | បាទ/ចាស | -| `No new ESLint warnings` | PR កូដ; lint guard ដែលគិតគូរពី suppression | បាទ/ចាស រួមទាំង fork | -| `Merge integrity (changelog + generated skills)` | PR ដែលមិនមែនជា draft; ការធ្វើសមកាលកម្ម changelog និង generated skill | បាទ/ចាស រួមទាំង fork | +| Job | វិសាលភាព | ការរារាំង | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | Docs/code PR; API docs refs និង docs-all | បាទ/ចាស | +| `Fast Quality Gates` | Code PR; static check, typecheck, dashboard typecheck, impacted unit test | បាទ/ចាស | +| `Forgotten sibling tests` | Code PR; module ដែលបានផ្លាស់ប្ដូរត្រូវបានតាមដានទៅកាន់ static consumer និង candidate sibling test; barrel និង dynamic-import path ត្រូវបានរាយការណ៍ជាការវិនិច្ឆ័យបែបប្រឹក្សា ដោយមានករណីលើកលែង allowlist ដែលបានយោង | **ប្រឹក្សា** | +| `Vitest (fast-path)` | Code PR; fast vitest suite | បាទ/ចាស | +| `Unit Tests fast-path` | Code PR; unit suite ចំនួន 4 shard | បាទ/ចាស | +| `No new ESLint warnings` | Code PR; lint guard ដែលគិតគូរពី suppression | បាទ/ចាស រួមទាំង fork | +| `Merge integrity (changelog + generated skills)` | Non-draft PR; changelog និង generated skill sync | បាទ/ចាស រួមទាំង fork | #### របាយការណ៍ Forgotten sibling tests `npm run check:forgotten-sibling-tests` ប្រើ import resolver ដែលនៅពីក្រោយ test-impact map ឡើងវិញ។ -សម្រាប់ production module នីមួយៗដែលបានផ្លាស់ប្តូរ វារាយការណ៍ខ្សែសង្វាក់កំណត់បានដដែល -`changed module/symbol -> static consumer -> candidate sibling test` នៅពេល candidate -test មិនមានក្នុង diff របស់ pull request។ សេចក្តីសង្ខេប Markdown និងលទ្ធផល JSON ត្រូវបានរក្សាទុកជា -workflow artifact `forgotten-sibling-tests` សម្រាប់ការក្រិតតម្រូវ មុនពេលដាក់ឱ្យប្រើប្រាស់ជាការទប់ស្កាត់ណាមួយ។ - -ការនាំចេញបន្តបែប Barrel និងការនាំចូលបែបថាមវន្ត គ្រាន់តែជាការវិនិច្ឆ័យអំពីការដោះស្រាយប៉ុណ្ណោះ; វាមិនដែលបង្កើត -លទ្ធផលរកឃើញដែលរារាំងឡើយ។ ករណីលើកលែងដែលបានពិនិត្យរួចមាននៅក្នុង -`config/quality/forgotten-sibling-allowlist.json`។ ធាតុនីមួយៗត្រូវតែបញ្ជាក់ឈ្មោះតេស្តប្រើប្រាស់ និងតេស្តបេក្ខជន -ផ្តល់ហេតុផលជាក់លាក់ និងភ្ជាប់ទៅកាន់ GitHub issue ឬ pull request។ ធាតុដែលមានទម្រង់មិនត្រឹមត្រូវនឹងបរាជ័យ -ក្នុងស្ថានភាពបិទ។ ករណីលើកលែងមិនអាចលាក់បាំងតេស្តបេក្ខជនដែលត្រូវបានលុប ឬ diff ដែលបន្ថែម `.skip`/`.todo` បានទេ; -ការធ្វើឱ្យ assertion ខ្សោយ និងការលាក់បាំងផ្សេងទៀត នៅតែស្ថិតក្រោមការគ្រប់គ្រងរបស់ច្រករារាំងឯករាជ្យ +សម្រាប់ production module នីមួយៗដែលបានផ្លាស់ប្ដូរ វារាយការណ៍ខ្សែសង្វាក់ +`changed module/symbol -> static consumer -> candidate sibling test` ដែលមានលក្ខណៈកំណត់បានជានិច្ច នៅពេល candidate +test មិនមានក្នុង diff របស់ pull-request។ សេចក្តីសង្ខេប Markdown និងលទ្ធផល JSON ត្រូវបានរក្សាទុកជា +workflow artifact `forgotten-sibling-tests` សម្រាប់ការក្រិតតម្រូវ មុនពេលដាក់ឱ្យប្រើប្រាស់ជាការរារាំងណាមួយ។ + +ការនាំចេញបន្តតាម barrel និងការនាំចូលបែបថាមវន្ត គ្រាន់តែជាការវិនិច្ឆ័យអំពីការដោះស្រាយប៉ុណ្ណោះ ពួកវាមិនដែលបង្កើត +ការរកឃើញដែលរារាំងឡើយ។ ករណីលើកលែងដែលបានពិនិត្យរួចមាននៅក្នុង +`config/quality/forgotten-sibling-allowlist.json`។ ធាតុនីមួយៗត្រូវតែបញ្ជាក់ឈ្មោះ consumer និង candidate +test ផ្តល់ហេតុផលជាក់លាក់ និងភ្ជាប់ទៅកាន់ GitHub issue ឬ pull request។ ធាតុដែលមានទម្រង់មិនត្រឹមត្រូវនឹងបរាជ័យ +តាមលំនាំសុវត្ថិភាព។ ករណីលើកលែងមិនអាចរារាំង candidate test ដែលបានលុប ឬ diff ដែលបន្ថែម `.skip`/`.todo` បានទេ; +ការធ្វើឱ្យ assertion ខ្សោយ និងការបិទបាំងផ្សេងទៀត នៅតែជាកម្មសិទ្ធិរបស់ច្រកត្រួតពិនិត្យដែលរារាំងដោយឯករាជ្យ `check:test-masking`។ -### កិច្ចការ: `lint` - -ដំណើរការលើរាល់ PR ទៅកាន់ `main`។ រារាំងការបញ្ចូលចូលគ្នា ប្រសិនបើបរាជ័យ។ - -| ស្គ្រីប (`npm run ...`) | ផ្ទៀងផ្ទាត់ | រារាំង | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | -| `check:node-runtime` | កំណែ Node.js ស្ថិតនៅក្នុងចន្លោះដែលបានគាំទ្រ | បាទ/ចាស | -| `check:cycles` | ការនាំចូលជារង្វង់ — ម៉ូឌុល `src/` + `open-sse/` ទាំងអស់ | បាទ/ចាស | -| `check:route-validation:t06` | មាន Zod schemas នៅលើ routes ទាំងអស់ (គោលការណ៍ Tier 6) | បាទ/ចាស | -| `check:any-budget:t11` | ចំនួន `@ts-expect-error // any` មិនលើសថវិកាកំណត់ (Tier 11 catraca) | បាទ/ចាស | -| `check:provider-consistency` | រាល់ provider នៅក្នុង `providers.ts` មានធាតុដែលត្រូវគ្នានៅក្នុង `providerRegistry.ts` (និងផ្ទុយមកវិញ សម្រាប់តែធាតុក្នុង allowlist) | បាទ/ចាស | -| `check:model-lifecycle` | តារាងកំណត់ផ្លូវដែលថែទាំដោយដៃទាំងបីនៅតែស្របគ្នាជាមួយ lifecycle snapshot ដែលបាន commit (#11503)៖ `FITNESS_TABLE` (`taskFitness.ts`) មិនដាក់ពិន្ទុឱ្យ id ដែលបានចូលនិវត្តន៍ណាមួយដែល `REGISTRY` អាចកំណត់ផ្លូវទៅកាន់បានទេ; រាល់គោលដៅរបស់ `BUILT_IN_ALIASES` មាននៅក្នុង `REGISTRY` និងមិនមាននៅក្នុង snapshot នៃ id ដែលបានចូលនិវត្តន៍; រាល់ id ដែលបានចូលនិវត្តន៍ ហើយនៅតែមានក្នុង `REGISTRY` ត្រូវបានបញ្ជូនបន្ត ឬរាយក្នុង `allowedRetiredInCatalog`; ហើយគ្មានប្រភព ឬគោលដៅណាមួយរបស់ `DEFAULT_DEGRADATION_MAP` ត្រូវបានសម្គាល់ថាបានចូលនិវត្តន៍នៅក្នុង snapshot នោះទេ។ វាមិនបញ្ជាក់ថា model មួយកំពុងត្រូវបានផ្តល់សេវាដោយ upstream ដែលកំពុងដំណើរការនោះទេ។ ដំណើរការក្រៅបណ្ដាញ — ប្រៀបធៀបជាមួយ `config/quality/model-lifecycle.json` ដែលធ្វើបច្ចុប្បន្នភាពដោយដៃតាមរយៈ `npm run quality:refresh-model-lifecycle` (ត្រូវការបណ្ដាញ; មិនបានភ្ជាប់ទៅក្នុង CI)។ `allowedRetiredInCatalog` គឺជាយន្តការរឹតបន្តឹងជាលំដាប់៖ បន្ថែមធាតុមួយបាន លុះត្រាតែមាន issue សម្រាប់តាមដាន។ | បាទ/ចាស | -| `check:fetch-targets` | រាល់ `fetch("/api/...")` នៅក្នុង `src/` ផ្នែក client អាចដោះស្រាយទៅកាន់ `route.ts` ដែលមានពិតប្រាកដ | បាទ/ចាស | -| `check:deps` | deps ទាំងអស់ដែលអាចដំឡើងដោយ `npm install` នៅទូទាំងរាល់ `package.json` ក្នុង repo មាននៅក្នុង `dependency-allowlist.json`; package ថ្មីដែលមិនបានចាក់សោ version ឬមានឈ្មោះស្រដៀងដើម្បីបោកបញ្ឆោត នឹងត្រូវបានសម្គាល់ | បាទ/ចាស | -| `audit:deps` | `npm audit` (root + electron) — គ្មានការជូនដំណឹងកម្រិតខ្ពស់/ធ្ងន់ធ្ងរ (ត្រួតគ្នាជាមួយ osv `check:vuln-ratchet`; សូមមើលបញ្ជីការងារ Rationalization Backlog) | បាទ/ចាស | -| `check:lockfile` | សុចរិតភាពរបស់ `package-lock.json` — https registry, integrity hashes និងគ្មានការកំណត់ host ជំនួស | បាទ/ចាស | -| `check:licenses` | បញ្ជីអនុញ្ញាតអាជ្ញាបណ្ណ SPDX សម្រាប់ dependency ក្នុង production | បាទ/ចាស | -| `check:tracked-artifacts` | មិនមាន artifact ពីការ build / symlink របស់ `node_modules` ដែលបាន commit ទេ (ដំណើរការផងដែរនៅក្នុង husky pre-commit; pre-push ត្រូវបានកំណត់ឱ្យស្រាលដោយចេតនា — #6716) | បាទ/ចាស | -| `check:ai-attribution` | មិនអនុញ្ញាត trailer `Co-Authored-By` របស់ AI/bot ឬ footer បញ្ជាក់ពីការបង្កើតដោយ AI នៅក្នុង commit ចំណងជើង ឬតួអត្ថបទរបស់ PR ទេ — ច្បាប់តឹងរ៉ឹង #16 (នៅក្នុងរង្វិលជុំ fast-gates របស់ `quality.yml` សម្រាប់ PR→`release/**` — អាន event payload និងមិនធ្វើអ្វីនៅក្រៅ PR — ព្រមទាំងជំហានសម្រាប់តែ PR នៅក្នុង lint របស់ `ci.yml` សម្រាប់ PR→`main`; ក៏មាន hook husky `commit-msg` ផងដែរ; អនុញ្ញាតសហអ្នកនិពន្ធជាមនុស្ស; #14436) | -| `check:vitest-exclusions` | ការដកចេញរបស់ Vitest នីមួយៗត្រូវបញ្ជាក់ tracking issue និងមាននៅក្នុង `config/quality/vitest-exclusions.json` (#13204) | បាទ/ចាស | -| `check:file-size` | គ្មានឯកសារ source ណាមួយលើសពីកម្រិតកំណត់តាម extension ទេ (ratchet៖ ឯកសារធំដែលត្រូវបានបង្កកនៅក្នុងបញ្ជី `frozen`) | បាទ/ចាស | -| `check:error-helper` | response កំហុសនៅក្នុង executor/handler ប្រើ `buildErrorBody()` / `sanitizeErrorMessage()` (ច្បាប់តឹងរ៉ឹង #12) | បាទ/ចាស | -| `check:migration-numbering` | ឯកសារ Migration SQL ត្រូវបានដាក់លេខតាមលំដាប់ ដោយគ្មានលេខរំលង ឬលេខស្ទួន | បាទ/ចាស | -| `check:public-creds` | មិនមានតម្លៃជាក់លាក់ OAuth `client_id`/`client_secret` ឬ Firebase Web keys នៅក្រៅ `publicCreds.ts` ទេ (វិធានតឹងរ៉ឹង #11) | បាទ/ចាស | -| `check:db-rules` | មិនមាន SQL ដើមនៅក្រៅម៉ូឌុល `src/lib/db/` ទេ; មិនមាន barrel-imports ពី `localDb.ts` ទេ (វិធានតឹងរ៉ឹង #2/#5) | បាទ/ចាស | -| `check:known-symbols` | Provider executors, routing strategies និង translators ដែលបានចុះឈ្មោះក្នុង dispatch tables របស់ពួកវា ត្រូវតែផ្គូផ្គងនឹងឯកសារនៅលើឌីស — មិនមាន symbols ដែលកំព្រា ឬមិនបានប្រកាសទេ | បាទ/ចាស | -| `check:route-guard-membership` | គ្រប់ route ដែលបង្កើត child process ត្រូវបានចាត់ថ្នាក់ដោយ `isLocalOnlyPath()` (វិធានតឹងរ៉ឹង #15/#17) | បាទ/ចាស | -| `check:test-discovery` | គ្រប់ឯកសារ `*.test.ts` / `*.spec.ts` ក្នុង repo ត្រូវបានប្រមូលដោយ test runner យ៉ាងហោចណាស់មួយ (ratchet៖ បញ្ជីឯកសារកំព្រាក្នុង `test-discovery-baseline.json` អាចតែថយចុះប៉ុណ្ណោះ) | បាទ/ចាស | -| `check:agent-skills-sync` | ផ្ទៀងផ្ទាត់ថា artifact ដែលបានបង្កើតសម្រាប់ agent-skills ត្រូវគ្នានឹងកាតាឡុកប្រភពរបស់វា (គ្មានភាពខុសគ្នា) | -| `check:provider-asset-provenance` | ឡូហ្គោ/asset របស់ provider មានធាតុកំណត់ត្រាប្រភពដើម | -| `lint:json` | ឯកសារកំណត់រចនាសម្ព័ន្ធ JSON អាច parse បាន និងអនុលោមតាមច្បាប់ lint របស់ repo | -| `typecheck:core` | ចងក្រង TypeScript ដោយគ្មានកំហុស (មានតែការព្រមានជាព័ត៌មានណែនាំប៉ុណ្ណោះ) | បាទ/ចាស | -| `typecheck:noimplicit:core` | `noImplicitAny` តឹងរ៉ឹង — សម្រាប់អនាគត; ទីតាំងហៅដែលមានស្រាប់ជាច្រើននៅតែត្រូវការការកំណត់ annotation | **ជាព័ត៌មានណែនាំ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` ដែលកំណត់វិសាលភាពត្រឹម `src/app/(dashboard)/**` (#7033) — បញ្ជីអនុញ្ញាត 27 ឯកសារដែលបានជ្រើសរើសរបស់ `typecheck:core` មិនរួមបញ្ចូល dashboard TSX ណាមួយទេ ហើយ `next build` ក៏មិនដែលពិនិត្យ type របស់វាដែរ (`next.config.mjs` កំណត់ `ignoreBuildErrors: true`) ដូច្នេះ regression នៃ identifier ដែលគ្មានប្រភពយោងនៅទីនោះ (#6625/#6909) មិនអាចត្រូវបាន CI រកឃើញ។ វាប្រៀបធៀបភាពខុសគ្នាជាមួយ baseline ចំនួនថេរតាមឯកសារ/កូដ TS (`config/quality/dashboard-typecheck-baseline.json`, ប្រើលំនាំអនុវត្តចំពោះភាពហួសសម័យដូចគ្នានឹង `check:known-symbols`) — មានតែកំហុសថ្មីលើសពីចំនួនក្នុង baseline ប៉ុណ្ណោះដែលធ្វើឱ្យ gate បរាជ័យ; បន្ថយ baseline ជាបន្តបន្ទាប់ដោយប្រើ `--update` នៅពេលកំហុសដែលមានស្រាប់ត្រូវបានកែ។ | បាទ/ចាស | - -### Job៖ `quality-gate` - -ដំណើរការបន្ទាប់ពី `test-coverage`។ រារាំងការបញ្ចូល merge នៅពេលបរាជ័យ។ - -| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `quality:collect` | បង្កើត `quality-metrics.json` (ចំនួនការព្រមាន ESLint និង coverage ពីរបាយការណ៍ shard ដែលបានបញ្ចូលគ្នា) | បាទ/ចាស (នៅមុន ratchet) | -| `quality:ratchet` | ម៉ែត្រនីមួយៗក្នុង `quality-baseline.json` មិនបានថយចុះ (ការព្រមាន ESLint ≤ baseline; coverage ≥ baseline) | បាទ/ចាស | -| `check:duplication` | ការស្ទួនកូដ (jscpd@4) មិនលើស baseline ក្នុង `quality-baseline.json` | បាទ/ចាស | -| `check:complexity` | ភាពស្មុគស្មាញ cyclomatic កម្រិតឯកសារមិនលើសដែនកំណត់ (ESLint `complexity` ស្នូល + `max-lines-per-function`) | បាទ/ចាស | -| `check:cognitive-complexity` | Ratchet នៃភាពស្មុគស្មាញផ្នែកការគិត (`eslint-plugin-sonarjs`) — ដំណើរការ ESLint ដាច់ដោយឡែក; CI ដំណើរការទាំងពីរដោយបញ្ចូលជាជំហាន `check:complexity-ratchets` តែមួយ | បាទ/ចាស | -| `check:dead-code` | Ratchet សម្រាប់ exports / ឯកសារដែលមិនបានប្រើ (knip) មិនថយចុះធៀបនឹង baseline | បាទ/ចាស | -| `check:compression-budget` | ថវិកា benchmark សម្រាប់ compression — កម្រិតអប្បបរមានៃការសន្សំ token តាម engine នីមួយៗមិនត្រូវថយចុះ | បាទ/ចាស | -| `check:type-coverage` | Ratchet ភាគរយដែលបានកំណត់ type (`type-coverage`) មិនថយចុះ; ជាទូទៅគ្របដណ្តប់ជំនួស `typecheck:noimplicit:core` | បាទ/ចាស | -| `check:codeql-ratchet` | ចំនួនការជូនដំណឹង CodeQL ដែលបើកមិនថយចុះ (អានតាមរយៈ `gh api`; រំលងដោយសុវត្ថិភាពនៅពេលគ្មាន token) — សម្រាប់វដ្ត refresh និងការបង្កឱ្យដំណើរការដោយដៃ៖ សូមមើល "CodeQL ratchet" ខាងក្រោម | បាទ/ចាស | - -### Job៖ `quality-extended` - -Job ទាំងមូលគឺសម្រាប់ផ្តល់យោបល់ (`continue-on-error: true`)។ Ratchet ដែលផ្អែកលើ npm ដំណើរការ -ជាក់ស្តែង; scanner ខាងក្រៅត្រូវបានដំឡើងតាមរយៈ `gh release download` ហើយរំលងដោយខ្លួនឯង (exit 0) -នៅពេល binary នៅតែមិនមាន។ - -| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -| `check:circular-deps` | មិនមាន dependency ជារង្វិល (dpdm) | **សម្រាប់ផ្តល់យោបល់** | -| `check:bundle-size` | ទំហំ bundle មិនលើសដែនកំណត់ | **សម្រាប់ផ្តល់យោបល់** | -| `check:secrets` | ការស្កេន secret (gitleaks) — រំលងប្រសិនបើគ្មាន binary | **សម្រាប់ផ្តល់យោបល់** | -| `check:vuln-ratchet` | ភាពងាយរងគ្រោះរបស់ dependency (osv-scanner) មិនថយចុះ — រំលងប្រសិនបើគ្មាន binary | **សម្រាប់ផ្តល់យោបល់** | -| `check:workflows` | ការត្រួតពិនិត្យ workflow (actionlint + zizmor); scanner ដែលបាត់/ខូច របាយការណ៍មិនត្រឹមត្រូវ ឬគ្មាន ratchet baseline នឹងបរាជ័យជា INCOMPLETE។ លទ្ធផលរកឃើញដែលមានសុពលភាពអនុវត្តតាមគោលការណ៍ strict/advisory/ratchet ដែលបានជ្រើសរើស | តម្រូវឱ្យដំណើរការ; zizmor ratchet រារាំងក្នុង CI | -| `check:openapi-breaking` | ការផ្លាស់ប្តូរដែលបំបែកភាពឆបគ្នានៃកិច្ចសន្យា API សាធារណៈ (`openapi.yaml`) ធៀបនឹង branch មូលដ្ឋាន (oasdiff) — បង្កើត `openapiBreaking=N`; រំលងប្រសិនបើគ្មាន oasdiff ឬមិនអាចដោះស្រាយ spec មូលដ្ឋានបាន | **សម្រាប់ផ្តល់យោបល់** | - -### Job៖ `docs-sync-strict` - -ដំណើរការលើរាល់ PR ទៅកាន់ `main`។ រារាំងការបញ្ចូលគ្នានៅពេលបរាជ័យ។ - -| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `check:docs-all` | ច្រកត្រួតពិនិត្យមេតាដែលដំណើរការច្រកត្រួតពិនិត្យរងទាំង 6 ខាងក្រោមតាមលំដាប់ | បាទ/ចាស | -| ↳ `check:docs-sync` | ភាពស៊ីសង្វាក់គ្នានៃកំណែ CHANGELOG / OpenAPI / llm.txt | បាទ/ចាស | -| ↳ `check:docs-counts` | ចំនួនដែលរៀបរាប់ក្នុងអត្ថបទ (ចំនួនអ្នកផ្តល់សេវា ចំនួនការធ្វើចំណាកស្រុក ជាដើម) ស្ថិតនៅក្នុងចន្លោះ ratchet នៃចំនួនពិត | បាទ/ចាស | -| ↳ `check:env-doc-sync` | អថេរបរិស្ថានទាំងអស់ក្នុង `.env.example` ត្រូវបានចងក្រងជាឯកសារក្នុងតារាងឯកសារណាមួយ ហើយផ្ទុយមកវិញ | បាទ/ចាស | -| ↳ `check:deprecated-versions` | មិនមានខ្សែអក្សរកំណែដែលលែងប្រើនៅក្នុងឯកសារ | បាទ/ចាស | -| ↳ `check:doc-links` | តំណ markdown ខាងក្នុងឯកសារអាចចូលទៅកាន់ឯកសារពិតបាន (ទម្រង់ `[text]`/`(path)`) | បាទ/ចាស | -| ↳ `check:fabricated-docs` | Route អថេរបរិស្ថាន ពាក្យបញ្ជា CLI ឈ្មោះ hook និងផ្លូវឯកសារដែលយោងក្នុងឯកសារ មាននៅក្នុងមូលដ្ឋានកូដ។ ជាច្រកត្រួតពិនិត្យរឹងតាមរយៈ `--strict`; បរាជ័យបែបទន់ដោយគ្មាន flag។ | បាទ/ចាស (តាមរយៈ `--strict` ក្នុង CI) | -| `check:cli-i18n` | ខ្សែអក្សរពាក្យបញ្ជា CLI មាននៅក្នុងឯកសារ locale i18n ទាំងអស់ | បាទ/ចាស | -| `check:openapi-coverage` | សេចក្តីបញ្ជាក់ OpenAPI គ្របដណ្តប់យ៉ាងហោចណាស់ដល់កម្រិតអប្បបរមាបែប ratchet នៃ route ពិត | បាទ/ចាស | -| `check:openapi-security-tiers` | ចំណារពន្យល់កម្រិតសុវត្ថិភាពក្នុង `openapi.yaml` ស្របគ្នាជាមួយចំណាត់ថ្នាក់ក្នុង `routeGuard.ts` | **ជាការណែនាំ** | -| `check:openapi-routes` | Path នីមួយៗក្នុង `openapi.yaml` អាចចូលទៅកាន់ `route.ts` ពិតបាន (ការពារការប្រឌិតព័ត៌មាន) | បាទ/ចាស | -| `check:docs-symbols` | រាល់ឯកសារយោង `/api/...` ក្នុង `docs/**/*.md` អាចចូលទៅកាន់ `route.ts` ពិតបាន (ការពារការប្រឌិតព័ត៌មាន) | បាទ/ចាស | -| `i18n translation drift` | Key ដែលមិនបានបកប្រែក្នុងឯកសារ locale i18n — គ្រាន់តែព្រមានប៉ុណ្ណោះ | **ជាការណែនាំ** | +### កិច្ចការ៖ `lint` + +ដំណើរការលើគ្រប់ PR ទៅកាន់ `main`។ រារាំងការបញ្ចូលចូលគ្នា ប្រសិនបើបរាជ័យ។ + +| ស្គ្រីប (`npm run ...`) | អ្វីដែលត្រូវផ្ទៀងផ្ទាត់ | ការរារាំង | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `check:node-runtime` | កំណែ Node.js ស្ថិតនៅក្នុងជួរដែលបានគាំទ្រ | បាទ/ចាស | +| `check:cycles` | ការនាំចូលជារង្វង់នៅទូទាំង `src/` + `open-sse/` (ផ្អែកលើ AST ហើយ `paths` របស់ tsconfig ត្រូវបានដោះស្រាយ)។ ការដំណើរការធម្មតា = ជាការណែនាំ និងបង្ហាញបញ្ជីរង្វង់។ `check:cycles:ratchet` (ដែល CI ដំណើរការ) នឹងរារាំង នៅពេលចំនួនលើសពីកម្រិតអតិបរមា `metrics.cycles` ក្នុង `quality-baseline.json` — បច្ចុប្បន្នគឺ 14, `direction: down` ដូច្នេះវាអាចតែថយចុះប៉ុណ្ណោះ (#15159 G-01/G-02) | បាទ/ចាស (ratchet) | +| `check:route-validation:t06` | មាន Zod schemas នៅលើ routes ទាំងអស់ (គោលការណ៍ Tier 6) | បាទ/ចាស | +| `check:any-budget:t11` | ចំនួន `@ts-expect-error // any` មិនលើសពីកម្រិតដែលបានកំណត់ (Tier 11 catraca) | បាទ/ចាស | +| `check:provider-consistency` | រាល់ provider នៅក្នុង `providers.ts` មាន entry ដែលត្រូវគ្នានៅក្នុង `providerRegistry.ts` (និងផ្ទុយមកវិញ សម្រាប់អ្វីដែលស្ថិតក្នុង allowlist) | បាទ | +| `check:model-lifecycle` | តារាង routing ចំនួនបីដែលថែទាំដោយដៃ នៅតែមានភាពស៊ីសង្វាក់គ្នាជាមួយ lifecycle snapshot ដែលបាន commit (#11503)៖ `FITNESS_TABLE` (`taskFitness.ts`) មិនដាក់ពិន្ទុឱ្យ retired id ណាមួយដែល `REGISTRY` អាច route បានទេ; រាល់ target របស់ `BUILT_IN_ALIASES` មាននៅក្នុង `REGISTRY` និងមិនមាននៅក្នុង retired-id snapshot; រាល់ retired id ដែលនៅតែមានក្នុង `REGISTRY` ត្រូវបាន forward ឬរាយក្នុង `allowedRetiredInCatalog`; ហើយគ្មាន source ឬ target របស់ `DEFAULT_DEGRADATION_MAP` ណាមួយបង្ហាញថា retired ក្នុង snapshot នោះទេ។ វាមិនបញ្ជាក់ថា model មួយបច្ចុប្បន្នត្រូវបានផ្តល់សេវាដោយ live upstream នោះទេ។ Offline — ប្រៀបធៀបជាមួយ `config/quality/model-lifecycle.json` ដែល refresh ដោយដៃតាមរយៈ `npm run quality:refresh-model-lifecycle` (ត្រូវការ network; មិនបានភ្ជាប់ទៅក្នុង CI)។ `allowedRetiredInCatalog` គឺជា burn-down ratchet៖ បន្ថែម entry តែជាមួយ tracking issue ប៉ុណ្ណោះ។ | បាទ | +| `check:fetch-targets` | រាល់ `fetch("/api/...")` នៅក្នុង `src/` ផ្នែក client-side resolve ទៅកាន់ `route.ts` ដែលមានពិតប្រាកដ | បាទ | +| `check:deps` | deps ទាំងអស់ដែលអាច `npm install` បាន នៅទូទាំងរាល់ `package.json` ក្នុង repo សុទ្ធតែមានក្នុង `dependency-allowlist.json`; package ថ្មីដែលមិនបាន pin ឬត្រូវបាន slopsquat នឹងត្រូវបានដាក់សញ្ញាសម្គាល់ | បាទ | +| `audit:deps` | `npm audit` (root + electron) — គ្មានការណែនាំសុវត្ថិភាពកម្រិត high/critical (ជាន់គ្នាជាមួយ osv `check:vuln-ratchet`; សូមមើល Rationalization Backlog) | បាទ | +| `check:lockfile` | integrity របស់ `package-lock.json` — https registry, integrity hashes, គ្មាន host overrides | បាទ | +| `check:licenses` | បញ្ជីអាជ្ញាបណ្ណ SPDX ដែលអនុញ្ញាតសម្រាប់ production dependencies | បាទ | +| `check:tracked-artifacts` | គ្មាន build artifacts / symlinks របស់ `node_modules` ដែលបាន commit (ក៏ដំណើរការនៅក្នុង husky pre-commit ផងដែរ; pre-push ត្រូវបានកំណត់ឱ្យស្រាលដោយចេតនា — #6716) | បាទ | +| `check:ai-attribution` | គ្មាន trailer `Co-Authored-By` របស់ AI/bot ឬ footer នៃការបង្កើតដោយ AI នៅក្នុង commits, ចំណងជើង ឬខ្លឹមសារ PR — ច្បាប់តឹងរ៉ឹង #16 (នៅក្នុង fast-gates loop របស់ `quality.yml` សម្រាប់ PR→`release/**` — អាន event payload, មិនធ្វើអ្វីទេនៅក្រៅ PR — និងជំហានសម្រាប់តែ PR នៅក្នុង lint របស់ `ci.yml` សម្រាប់ PR→`main`; ព្រមទាំង hook `commit-msg` របស់ husky ផងដែរ; អនុញ្ញាតឱ្យមានសហអ្នកនិពន្ធជាមនុស្ស; #14436) | +| `check:vitest-exclusions` | រាល់ការដកចេញពី Vitest ត្រូវបញ្ជាក់ tracking issue និងមាននៅក្នុង `config/quality/vitest-exclusions.json` (#13204) | បាទ | +| `check:file-size` | គ្មានឯកសារ source ណាមួយលើសពីកម្រិតអតិបរមាតាម extension (ratchet: ឯកសារធំដែលបានបង្កកនៅក្នុងបញ្ជី `frozen`) | បាទ | +| `check:error-helper` | Error responses នៅក្នុង executors/handlers ប្រើ `buildErrorBody()` / `sanitizeErrorMessage()` (ច្បាប់តឹងរ៉ឹង #12) | បាទ | +| `check:migration-numbering` | ឯកសារ Migration SQL ត្រូវបានដាក់លេខរៀងបន្តបន្ទាប់ ដោយគ្មានលេខខ្វះ ឬលេខស្ទួន | បាទ/ចាស | +| `check:public-creds` | មិនមានតម្លៃ OAuth `client_id`/`client_secret` ឬ Firebase Web keys ដែលសរសេរផ្ទាល់នៅខាងក្រៅ `publicCreds.ts` ទេ (វិធានតឹងរ៉ឹង #11) | បាទ/ចាស | +| `check:db-rules` | មិនមាន SQL ដើមនៅខាងក្រៅម៉ូឌុល `src/lib/db/` ទេ; មិនមាន barrel-imports ពី `localDb.ts` ទេ (វិធានតឹងរ៉ឹង #2/#5) | បាទ/ចាស | +| `check:known-symbols` | កម្មវិធីប្រតិបត្តិរបស់ Provider យុទ្ធសាស្ត្រកំណត់ផ្លូវ និងកម្មវិធីបកប្រែដែលបានចុះបញ្ជីក្នុងតារាង dispatch របស់ពួកវា ត្រូវគ្នានឹងឯកសារនៅលើថាស — មិនមាននិមិត្តសញ្ញាដែលគ្មានម្ចាស់ ឬមិនបានប្រកាសទេ | បាទ/ចាស | +| `check:route-guard-membership` | គ្រប់ route ដែលបង្កើត child process ត្រូវបានចាត់ថ្នាក់ដោយ `isLocalOnlyPath()` (វិធានតឹងរ៉ឹង #15/#17) | បាទ/ចាស | +| `check:test-discovery` | គ្រប់ឯកសារ `*.test.ts` / `*.spec.ts` ក្នុង repo ត្រូវបានប្រមូលដោយ test runner យ៉ាងហោចណាស់មួយ (ratchet៖ បញ្ជីឯកសារគ្មានម្ចាស់ក្នុង `test-discovery-baseline.json` អាចតែថយចុះប៉ុណ្ណោះ) | បាទ/ចាស | +| `check:agent-skills-sync` | វត្ថុសិប្បនិម្មិត agent-skills ដែលបានបង្កើតត្រូវគ្នានឹងកាតាឡុកប្រភពរបស់វា (គ្មានភាពខុសគ្នា) | +| `check:provider-asset-provenance` | ឡូហ្គោ/ទ្រព្យសកម្មរបស់អ្នកផ្តល់សេវាមានកំណត់ត្រាប្រភពដើម | +| `lint:json` | ឯកសារកំណត់រចនាសម្ព័ន្ធ JSON អាចញែកបាន និងអនុលោមតាមច្បាប់ lint របស់ repo | +| `typecheck:core` | ការចងក្រង TypeScript ដោយគ្មានកំហុស (មានតែការព្រមានជាលក្ខណៈណែនាំប៉ុណ្ណោះ) | បាទ/ចាស | +| `typecheck:noimplicit:core` | `noImplicitAny` ដ៏តឹងរ៉ឹង — ផ្តោតទៅអនាគត; ទីតាំងហៅដែលមានស្រាប់ជាច្រើននៅតែត្រូវការចំណារពន្យល់ប្រភេទ | **ជាលក្ខណៈណែនាំ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` ដែលកំណត់វិសាលភាពត្រឹម `src/app/(dashboard)/**` (#7033) — បញ្ជីអនុញ្ញាត 27 ឯកសារដែលបានជ្រើសរើសរបស់ `typecheck:core` មិនរួមបញ្ចូល dashboard TSX ណាមួយទេ ហើយ `next build` ក៏មិនដែលពិនិត្យប្រភេទរបស់វាដែរ (`next.config.mjs` កំណត់ `ignoreBuildErrors: true`) ដូច្នេះ ការថយចុះគុណភាពពាក់ព័ន្ធនឹង identifier ដែលកំព្រានៅទីនោះ (#6625/#6909) មិនអាចមើលឃើញដោយ CI បានទេ។ ប្រៀបធៀបភាពខុសគ្នាជាមួយ baseline ចំនួនកូដ TS ក្នុងមួយឯកសារដែលបានបង្កក (`config/quality/dashboard-typecheck-baseline.json`, លំនាំអនុវត្តភាពហួសសម័យដូចគ្នានឹង `check:known-symbols`) — មានតែកំហុសថ្មីដែលលើសពីចំនួនក្នុង baseline ប៉ុណ្ណោះដែលធ្វើឱ្យ gate បរាជ័យ; កាត់បន្ថយជាបន្តបន្ទាប់ដោយប្រើ `--update` នៅពេលកំហុសដែលមានស្រាប់ត្រូវបានជួសជុល។ | បាទ/ចាស | + +### កិច្ចការ៖ `quality-gate` + +ដំណើរការបន្ទាប់ពី `test-coverage`។ ទប់ស្កាត់ការបញ្ចូលបញ្ចូលគ្នា នៅពេលបរាជ័យ។ + +| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `quality:collect` | បង្កើត `quality-metrics.json` (ចំនួនការព្រមាន ESLint និង coverage ពីរបាយការណ៍ shard ដែលបានបញ្ចូលគ្នា) | បាទ/ចាស (ដំណើរការមុន ratchet) | +| `quality:ratchet` | ម៉ែត្រនីមួយៗក្នុង `quality-baseline.json` មិនបានថយចុះ (ការព្រមាន ESLint ≤ baseline; coverage ≥ baseline) | បាទ/ចាស | +| `check:duplication` | ការស្ទួនកូដ (jscpd@4) មិនលើស baseline ក្នុង `quality-baseline.json` | បាទ/ចាស | +| `check:complexity` | ភាពស្មុគស្មាញ cyclomatic កម្រិតឯកសារមិនលើសកម្រិតកំណត់ (ESLint `complexity` ស្នូល + `max-lines-per-function`) | បាទ/ចាស | +| `check:cognitive-complexity` | ratchet ភាពស្មុគស្មាញផ្នែកការគិត (`eslint-plugin-sonarjs`) — ការដំណើរការ ESLint ដាច់ដោយឡែក; CI ដំណើរការទាំងពីរដោយបញ្ចូលគ្នាជាជំហាន `check:complexity-ratchets` តែមួយ | បាទ/ចាស | +| `check:dead-code` | ratchet សម្រាប់ export / ឯកសារដែលមិនបានប្រើ (knip) មិនថយចុះធៀបនឹង baseline | បាទ/ចាស | +| `check:compression-budget` | ថវិកា benchmark នៃការបង្ហាប់ — កម្រិតអប្បបរមានៃការសន្សំ token សម្រាប់ engine នីមួយៗមិនត្រូវថយចុះ | បាទ/ចាស | +| `check:type-coverage` | ratchet ភាគរយដែលបានកំណត់ type (`type-coverage`) មិនថយចុះ; ជាទូទៅគ្របដណ្ដប់លើ `typecheck:noimplicit:core` | បាទ/ចាស | +| `check:codeql-ratchet` | ចំនួនការជូនដំណឹង CodeQL ដែលនៅបើកមិនថយក្រោយ (អានតាម `gh api`; រំលងដោយសុវត្ថិភាពពេលគ្មាន token) — សម្រាប់ចន្លោះពេល refresh និងការចាប់ផ្ដើមដោយដៃ៖ សូមមើល "CodeQL ratchet" ខាងក្រោម | បាទ/ចាស | + +### Job: `quality-extended` + +Job ទាំងមូលមានលក្ខណៈផ្ដល់យោបល់ (`continue-on-error: true`)។ ratchet ដែលផ្អែកលើ npm ដំណើរការ +ជាក់ស្ដែង; scanner ខាងក្រៅត្រូវបានដំឡើងតាម `gh release download` ហើយរំលងខ្លួនឯង (exit 0) +នៅពេលនៅតែគ្មាន binary។ + +| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `check:circular-deps` | មិនមាន dependency ជារង្វង់ (dpdm) | **ផ្ដល់យោបល់** | +| `check:bundle-size` | ទំហំ bundle មិនលើសកម្រិតកំណត់ | **ផ្ដល់យោបល់** | +| `check:secrets` | ស្កេនរក secret (gitleaks) — រំលងប្រសិនបើគ្មាន binary | **ផ្ដល់យោបល់** | +| `check:vuln-ratchet` | ភាពងាយរងគ្រោះនៃ dependency (osv-scanner) មិនថយក្រោយ — រំលងប្រសិនបើគ្មាន binary | **ផ្ដល់យោបល់** | +| `check:workflows` | ពិនិត្យ workflow (actionlint + zizmor); scanner ដែលបាត់/ខូច របាយការណ៍មិនត្រឹមត្រូវ ឬគ្មាន ratchet baseline នឹងបរាជ័យជា INCOMPLETE។ បញ្ហាដែលរកឃើញត្រឹមត្រូវអនុវត្តតាមគោលការណ៍ strict/advisory/ratchet ដែលបានជ្រើសរើស | តម្រូវឱ្យប្រតិបត្តិ; zizmor ratchet រារាំងក្នុង CI | +| `check:openapi-breaking` | ការផ្លាស់ប្ដូរដែលធ្វើឱ្យខូចភាពឆបគ្នានៃកិច្ចសន្យា API សាធារណៈ (`openapi.yaml`) ធៀបនឹង branch មូលដ្ឋាន (oasdiff) — បង្កើត `openapiBreaking=N`; រំលងប្រសិនបើគ្មាន oasdiff ឬមិនអាចដោះស្រាយ spec មូលដ្ឋានបាន | **ផ្ដល់យោបល់** | + +### Job: `docs-sync-strict` + +ដំណើរការលើរាល់ PR ទៅ `main`។ រារាំងការបញ្ចូលចូលគ្នាប្រសិនបើបរាជ័យ។ + +| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| `check:docs-all` | ច្រកមេតាដែលដំណើរការច្រករងទាំង 6 ខាងក្រោមតាមលំដាប់ | បាទ/ចាស | +| ↳ `check:docs-sync` | ភាពស៊ីសង្វាក់គ្នានៃកំណែ CHANGELOG / OpenAPI / llm.txt | បាទ/ចាស | +| ↳ `check:docs-counts` | ចំនួននៅក្នុងអត្ថបទ (ចំនួនអ្នកផ្តល់សេវា ចំនួនការធ្វើ migration ជាដើម) ស្ថិតនៅក្នុងចន្លោះ ratchet នៃចំនួនពិត | បាទ/ចាស | +| ↳ `check:env-doc-sync` | អថេរបរិស្ថាននីមួយៗក្នុង `.env.example` ត្រូវបានចងក្រងជាឯកសារនៅក្នុងតារាងឯកសារ ហើយផ្ទុយមកវិញ | បាទ/ចាស | +| ↳ `check:deprecated-versions` | មិនមានខ្សែអក្សរកំណែដែលបានឈប់គាំទ្រនៅក្នុងឯកសារ | បាទ/ចាស | +| ↳ `check:doc-links` | តំណ markdown ខាងក្នុងនៅក្នុងឯកសារអាចនាំទៅកាន់ឯកសារពិត (`[text]`/`(path)` form) | បាទ/ចាស | +| ↳ `check:fabricated-docs` | Routes អថេរបរិស្ថាន ពាក្យបញ្ជា CLI ឈ្មោះ hook និងផ្លូវឯកសារដែលបានយោងក្នុងឯកសារ មានពិតនៅក្នុង codebase។ ជាច្រកតឹងរ៉ឹងតាមរយៈ `--strict`; បរាជ័យបែបទន់បើគ្មាន flag។ | បាទ/ចាស (តាមរយៈ `--strict` ក្នុង CI) | +| `check:cli-i18n` | ខ្សែអក្សរពាក្យបញ្ជា CLI មាននៅក្នុងឯកសារ locale i18n ទាំងអស់ | បាទ/ចាស | +| `check:openapi-coverage` | សេចក្តីបញ្ជាក់ OpenAPI គ្របដណ្តប់យ៉ាងហោចណាស់ដល់កម្រិតអប្បបរមាបែប ratchet នៃ routes ពិត | បាទ/ចាស | +| `check:openapi-security-tiers` | សេចក្តីសម្គាល់កម្រិតសុវត្ថិភាពនៅក្នុង `openapi.yaml` ស៊ីសង្វាក់គ្នាជាមួយចំណាត់ថ្នាក់ក្នុង `routeGuard.ts` | **ជាការណែនាំ** | +| `check:openapi-routes` | គ្រប់ path នៅក្នុង `openapi.yaml` អាចនាំទៅកាន់ `route.ts` ពិត (ការពារការប្រឌិតព័ត៌មាន) | បាទ/ចាស | +| `check:docs-symbols` | គ្រប់ឯកសារយោង `/api/...` នៅក្នុង `docs/**/*.md` អាចនាំទៅកាន់ `route.ts` ពិត (ការពារការប្រឌិតព័ត៌មាន) | បាទ/ចាស | +| `i18n translation drift` | key ដែលមិនបានបកប្រែនៅក្នុងឯកសារ locale i18n — គ្រាន់តែព្រមានប៉ុណ្ណោះ | **ជាការណែនាំ** | ### កិច្ចការ៖ `i18n-ui-coverage` -| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | -| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (ក្នុងបន្ទាត់) | អត្រាគ្របដណ្តប់ key i18n របស់ UI គឺ ≥ 65% | បាទ/ចាស | -| `check-ui-value-drift` (ក្នុងបន្ទាត់) | **តម្លៃ**ជាភាសាអង់គ្លេសដែលត្រូវបានសរសេរឡើងវិញ មិនបន្សល់ទុកការបកប្រែចាស់ណាមួយទេ | បាទ/ចាស | -| `check-new-key-coverage` (ក្នុងបន្ទាត់) | Key ភាសាអង់គ្លេស**ថ្មី**ត្រូវបានបកប្រែក្នុងគ្រប់ locale — សញ្ញាសម្គាល់ `__MISSING__:` ត្រូវបានបដិសេធ | បាទ/ចាស | -| `check-translation-ratio` | អនុបាតការបកប្រែពិតក្នុង locale នីមួយៗ (តម្លៃដូចគ្នានឹងភាសាអង់គ្លេស / placeholder / ធាតុដែលបាត់នៅក្រៅបញ្ជីអនុញ្ញាត) មិនត្រូវលើសពី `config/quality/i18n-translation-baseline.json` + កម្រិតបន្ធូរបន្ថយឡើយ | **ជាការណែនាំ** | +| ស្គ្រីប | ផ្ទៀងផ្ទាត់ | រារាំង | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (ក្នុងបន្ទាត់) | ការគ្របដណ្តប់ key i18n របស់ UI គឺ ≥ 65% | បាទ/ចាស | +| `check-ui-value-drift` (ក្នុងបន្ទាត់) | **តម្លៃ** ភាសាអង់គ្លេសដែលបានសរសេរឡើងវិញ មិនបន្សល់ទុកការបកប្រែហួសសម័យឡើយ | បាទ/ចាស | +| `check-new-key-coverage` (ក្នុងបន្ទាត់) | key ភាសាអង់គ្លេស **ថ្មី** ត្រូវបានបកប្រែក្នុងគ្រប់ locale — marker `__MISSING__:` នឹងត្រូវបានបដិសេធ | បាទ/ចាស | +| `check-translation-ratio` | អនុបាតការបកប្រែពិតក្នុងមួយ locale (ស្លឹកដែលដូចគ្នានឹងភាសាអង់គ្លេស / placeholder / បាត់ នៅក្រៅ allowlist) មិនត្រូវលើសពី `config/quality/i18n-translation-baseline.json` + កម្រិតបន្ធូរ | **ជាការណែនាំ** | -តម្រូវឱ្យមាន `fetch-depth: 0` — ច្រកត្រួតពិនិត្យ value-drift ធ្វើ diff លើ `en.json` ធៀបនឹង merge base។ +ត្រូវការ `fetch-depth: 0` — ច្រក value-drift ធ្វើ diff លើ `en.json` ធៀបនឹង merge base។ -#### `check-ui-value-drift` — ច្រកត្រួតពិនិត្យការបកប្រែចាស់ +#### `check-ui-value-drift` — ច្រកការបកប្រែហួសសម័យ -ចាប់បាន regression i18n មួយប្រភេទដែលច្រកត្រួតពិនិត្យផ្សេងទៀតមិនអាចមើលឃើញតាមរចនាសម្ព័ន្ធបាន៖ តម្លៃជាភាសាអង់គ្លេស -ត្រូវបានសរសេរឡើងវិញ ខណៈការបកប្រែដែលផ្អែកលើភាសាអង់គ្លេស_ពីមុន_នៅតែមាន ដូច្នេះ -អ្នកប្រើប្រាស់ដែលមិនប្រើភាសាអង់គ្លេសនៅតែអានអត្ថបទដែលសរសេរយ៉ាងច្បាស់លាស់ ប៉ុន្តែឥឡូវនេះខុស។ +ចាប់បាន regression i18n មួយប្រភេទដែលច្រកផ្សេងៗមិនអាចមើលឃើញតាមរចនាសម្ព័ន្ធ៖ តម្លៃភាសាអង់គ្លេស +ត្រូវបានសរសេរឡើងវិញ ខណៈការបកប្រែដែលបានបង្កើតពីភាសាអង់គ្លេស _មុន_ នៅតែមាន ដូច្នេះ +អ្នកប្រើប្រាស់ដែលមិនប្រើភាសាអង់គ្លេសនៅតែអានអត្ថបទដែលមានទម្រង់ជឿជាក់ ប៉ុន្តែឥឡូវនេះខុស។ -បញ្ហានេះពិតជាត្រូវបានចេញផ្សាយមែន។ `oauthModal.googleOAuthWarning` ត្រូវបានសរសេរឡើងវិញ នៅពេលកម្មវិធីជំនួយចូលប្រើ Antigravity -ត្រូវបានដាក់ឱ្យប្រើ (#5203); **39 ក្នុងចំណោម 43 locale** នៅតែរក្សាអត្ថបទដែលប្រាប់អ្នកប្រតិបត្តិការឱ្យ "ចម្លង -URL ពេញលេញ ហើយបិទភ្ជាប់វានៅខាងក្រោម" — ជាលំហូរដែលមិនអាចបញ្ចប់បានសម្រាប់អ្នកផ្តល់សេវានោះ។ វា -មិនត្រូវបានរកឃើញរហូតដល់ #8463 ពីព្រោះ៖ +បញ្ហានេះពិតជាបានចេញផ្សាយទៅអ្នកប្រើប្រាស់។ `oauthModal.googleOAuthWarning` ត្រូវបានសរសេរឡើងវិញ នៅពេលកម្មវិធីជំនួយចូលប្រើ Antigravity +ត្រូវបានដាក់បញ្ចូល (#5203); **39 ក្នុងចំណោម 43 locales** នៅតែរក្សាអត្ថបទដែលប្រាប់អ្នកប្រតិបត្តិឱ្យ "ចម្លង +URL ពេញលេញ ហើយបិទភ្ជាប់វាខាងក្រោម" — ជាលំហូរដែលមិនអាចបញ្ចប់បានសម្រាប់អ្នកផ្តល់សេវានោះ។ វា +មិនត្រូវបានកត់សម្គាល់រហូតដល់ #8463 ដោយសារតែ៖ -- `sync-ui-keys` បំពេញបន្ថែមតែ key ដែល**មិនមាន**ប៉ុណ្ណោះ មិនមែន key ដែល**ចាស់**ទេ; -- `check-ui-keys-coverage` រាប់_វត្តមាន_របស់ key ដូច្នេះការបកប្រែចាស់ត្រូវបានចាត់ទុកថាបានគ្របដណ្តប់; -- `check-translation-drift` តាមដានឯកសារឆ្លុះបញ្ចាំងនៃឯកសារ `docs/i18n//**.md` — - វាមិនដែលអាន `src/i18n/messages/*.json` ទេ។ ជាការរារាំងក្នុងកិច្ចការ `docs-sync-strict` ចាប់តាំងពី - ការធ្វើសមកាលកម្មឡើងវិញ 2026-09៖ កែសម្រួលឯកសារស្នូល → `npm run i18n:run -- --files=` (តាមកម្រិតផ្នែក ចំណាយធនធានតិច)។ +- `sync-ui-keys` បំពេញតែ key ដែល **អវត្តមាន** ប៉ុណ្ណោះ មិនដែលបំពេញ key ដែល **ហួសសម័យ** ឡើយ; +- `check-ui-keys-coverage` រាប់ _វត្តមាន_ របស់ key ដូច្នេះការបកប្រែហួសសម័យត្រូវបានរាប់ថាបានគ្របដណ្តប់; +- `check-translation-drift` តាមដានឯកសារចម្លងនៃ documentation ក្នុង `docs/i18n//**.md` — + វាមិនដែលអាន `src/i18n/messages/*.json` ទេ។ បានរារាំងក្នុងកិច្ចការ `docs-sync-strict` ចាប់តាំងពី + ការធ្វើ re-sync ឆ្នាំ 2026-09៖ កែសម្រួលឯកសារស្នូល → `npm run i18n:run -- --files=` (កម្រិត section ចំណាយតិច)។ **យល់ដឹងអំពី diff មិនមែនផ្អែកលើ baseline ទេ។** វាប្រៀបធៀប `en.json` នៅ merge base ជាមួយនឹង -working tree; សម្រាប់ key នីមួយៗដែលតម្លៃភាសាអង់គ្លេសបានផ្លាស់ប្តូរ locale ណាមួយដែលនៅតែមាន -ការបកប្រែមិនទាន់បានកែប្រែ ត្រូវបានចាត់ទុកថាហួសសម័យ។ វាចេតនា **បង្កកបំណុលដែលមានស្រាប់** — diff -មិនអាចបង្ហាញថាការបកប្រែដែលមានជាយូរមកហើយនោះបានមកពីអត្ថបទភាសាអង់គ្លេសចាស់មួយណាទេ ដូច្នេះ gate វាយតម្លៃ +working tree; សម្រាប់ key នីមួយៗដែលតម្លៃជាភាសាអង់គ្លេសបានផ្លាស់ប្តូរ locale ណាមួយដែលនៅតែមាន +ការបកប្រែមិនបានកែប្រែ នឹងត្រូវចាត់ទុកថាហួសសុពលភាព។ វាមានចេតនា **បង្កកបំណុលដែលមានស្រាប់** — diff +មិនអាចបង្ហាញថាការបកប្រែដែលមានជាយូរមកហើយបានមកពីអត្ថបទភាសាអង់គ្លេសចាស់មួយណាទេ ដូច្នេះ gate វាយតម្លៃ តែអ្វីដែលការផ្លាស់ប្តូរបច្ចុប្បន្នប៉ះពាល់ប៉ុណ្ណោះ។ ជម្រើសផ្សេងទៀត (baseline hash សម្រាប់ key នីមួយៗ) នឹងត្រូវការ -ឯកសារដែលបានបង្កើតទំហំប្រហែល ~600 KB ដែលធំជាង baseline ដែលមានស្រាប់ធំបំផុត 3× ហើយប្រែប្រួលនៅរាល់ i18n PR។ +ឯកសារដែលបានបង្កើតទំហំប្រហែល 600 KB ដែលមានទំហំធំជាង baseline ធំបំផុតបច្ចុប្បន្ន 3 ដង ហើយផ្លាស់ប្តូររាល់ i18n PR។ មានពីរវិធីដើម្បីបំពេញលក្ខខណ្ឌនេះ៖ 1. ធ្វើបច្ចុប្បន្នភាពការបកប្រែដែលរងផលប៉ះពាល់ ឬ -2. កំណត់ពួកវាទៅជា `__MISSING__:` — បន្ទាប់មក runtime នឹងបង្ហាញអត្ថបទភាសាអង់គ្លេសដែលបានកែតម្រូវ - (`src/i18n/request.ts::deepMergeFallback`, #7258) ហើយដាក់ key ចូលជួររង់ចាំសម្រាប់ការបកប្រែ។ +2. កំណត់តម្លៃពួកវាទៅជា `__MISSING__:` — បន្ទាប់មក runtime នឹងផ្តល់អត្ថបទភាសាអង់គ្លេសដែលបានកែត្រឹមត្រូវ + (`src/i18n/request.ts::deepMergeFallback`, #7258) ហើយ key នោះនឹងចូលជួររង់ចាំសម្រាប់ការបកប្រែ។ ប្រសិនបើ **អត្ថន័យ** របស់ string បានផ្លាស់ប្តូរ គួរតែជ្រើសរើស **ប្តូរឈ្មោះ key**៖ key ថ្មីមិនអាចទទួលបន្ត -ការបកប្រែហួសសម័យបានទេ។ នេះជាគំរូដែល #8463 បានប្រើ។ +ការបកប្រែដែលហួសសុពលភាពបានទេ។ នេះជាគំរូដែល #8463 បានប្រើ។ ```bash npm run i18n:check-value-drift # តឹងរ៉ឹង (អ្វីដែល CI ដំណើរការ) @@ -231,48 +231,48 @@ npm run i18n:check-value-drift:warn # រាយការណ៍តែប៉ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -បញ្ចប់ដោយលេខកូដ 0 ជាមួយ `SKIP reason=base-unresolved` នៅពេលមិនអាចអាន catalog មូលដ្ឋានបាន (shallow -clone ដែលគ្មាន base ref) ដោយធ្វើតាមឥរិយាបថរបស់ `check-openapi-breaking`។ +ចាកចេញដោយលេខកូដ 0 ជាមួយ `SKIP reason=base-unresolved` នៅពេលមិនអាចអាន catalog មូលដ្ឋានបាន (shallow +clone ដែលគ្មាន base ref) ដោយឆ្លុះបញ្ចាំងឥរិយាបថរបស់ `check-openapi-breaking`។ ### Job៖ `i18n` -ម៉ាទ្រីសផ្ទៀងផ្ទាត់ i18n ពេញលេញ (មួយ job ក្នុងមួយ locale)។ job ទាំងមូលគ្រាន់តែជាការណែនាំ។ +ម៉ាទ្រីសត្រួតពិនិត្យ i18n ពេញលេញ (មួយ job សម្រាប់ locale នីមួយៗ)។ job ទាំងមូលគឺជាការណែនាំ។ -| Script | ផ្ទៀងផ្ទាត់ | ការរារាំង | -| ------------------------------- | --------------------------------------- | --------------------------------------------------------- | -| `validate_translation.py quick` | ភាពពេញលេញនៃការបកប្រែក្នុង locale នីមួយៗ | **ជាការណែនាំ** (`continue-on-error: true` លើ job ទាំងមូល) | +| Script | ផ្ទៀងផ្ទាត់ | ការរារាំង | +| ------------------------------- | ----------------------------------------- | --------------------------------------------------------- | +| `validate_translation.py quick` | ភាពពេញលេញនៃការបកប្រែសម្រាប់ locale នីមួយៗ | **ជាការណែនាំ** (`continue-on-error: true` លើ job ទាំងមូល) | ### Job៖ `pr-test-policy` ដំណើរការតែលើ pull request ប៉ុណ្ណោះ។ -| Script | ផ្ទៀងផ្ទាត់ | ការរារាំង | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | PR ដែលផ្លាស់ប្តូរ production code ក្នុង `src/`, `open-sse/`, `electron/`, ឬ `bin/` ត្រូវតែរួមបញ្ចូល ឬធ្វើបច្ចុប្បន្នភាព test (វិធានតឹងរ៉ឹង #8) | បាទ/ចាស | -| `check:test-masking` | ឯកសារ test ដែលបានផ្លាស់ប្តូរមិនកាត់បន្ថយចំនួន assert សរុបសុទ្ធ ឬបន្ថែម tautology `assert.ok(true)` ទេ | បាទ/ចាស | -| `check:pr-evidence` | ខ្លឹមសារ PR យោងភស្តុតាង test/VPS សម្រាប់ការផ្លាស់ប្តូរ (អនុវត្តវិធានតឹងរ៉ឹង #18 ដោយ grep អត្ថបទ PR — ងាយខូច សូមមើល Backlog) | បាទ/ចាស | +| Script | ផ្ទៀងផ្ទាត់ | ការរារាំង | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `check:pr-test-policy` | PR ដែលផ្លាស់ប្តូរ production code ក្នុង `src/`, `open-sse/`, `electron/` ឬ `bin/` ត្រូវតែបញ្ចូល ឬធ្វើបច្ចុប្បន្នភាព test (Hard Rule #8) | បាទ/ចាស | +| `check:test-masking` | ឯកសារ test ដែលបានផ្លាស់ប្តូរមិនបន្ថយចំនួន assert សុទ្ធ ឬបន្ថែម tautology `assert.ok(true)` ទេ | បាទ/ចាស | +| `check:pr-evidence` | ខ្លឹមសារ PR យោងភស្តុតាង test/VPS សម្រាប់ការផ្លាស់ប្តូរ (ធ្វើឱ្យ Hard Rule #18 ដំណើរការដោយ grep លើអត្ថបទ PR — ងាយខូច សូមមើល Backlog) | បាទ/ចាស | ### Job៖ `test-vitest` -ដំណើរការបន្ទាប់ពី `build`។ រារាំងការបញ្ចូល merge នៅពេលបរាជ័យ។ +ដំណើរការបន្ទាប់ពី `build`។ រារាំងការ merge នៅពេលបរាជ័យ។ -| Suite | ផ្ទៀងផ្ទាត់ | ការរារាំង | -| ---------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (ឧបករណ៍ 110), autoCombo, cache — vitest runner | បាទ/ចាស | -| `test:vitest:ui` | test សមាសភាគ UI — vitest runner | **រារាំង** — failure ដែលមានស្រាប់ត្រូវបានដកចេញយ៉ាងច្បាស់ក្នុង `vitest.config.ts`; failure ថ្មីធ្វើឱ្យ job បរាជ័យ | +| Suite | ផ្ទៀងផ្ទាត់ | ការរារាំង | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | បាទ/ចាស | +| `test:vitest:ui` | test សមាសភាគ UI — vitest runner | **រារាំង** — failure ដែលមានស្រាប់ត្រូវបានដកចេញយ៉ាងច្បាស់ក្នុង `vitest.config.ts`; failure ថ្មីនឹងធ្វើឱ្យ job បរាជ័យ | -### Workflow ប្រចាំយប់ (កំណត់ពេល និងជាការណែនាំ) +### Workflow ប្រចាំយប់ (បានកំណត់ពេល ជាការណែនាំ) -Workflow ទាំងនេះដំណើរការតាមកាលវិភាគ cron (និង `workflow_dispatch`) មិនដែលដំណើរការលើ PR ទេ។ ទាំងអស់គ្រាន់តែជាការណែនាំ។ +ទាំងនេះដំណើរការតាមកាលវិភាគ cron (និង `workflow_dispatch`) ហើយមិនដំណើរការលើ PR ទេ។ ទាំងអស់គឺជាការណែនាំ។ -| Workflow | ផ្ទៀងផ្ទាត់ | ការរារាំង | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | property test របស់ fast-check ជាមួយ random seed + ចំនួនដងដំណើរការខ្ពស់ | **ជាការណែនាំ** | -| `nightly-resilience` | gate សម្រាប់ការកើនឡើងនៃ heap, ការបញ្ចូល fault បែប chaos, load/soak របស់ k6 | **ជាការណែនាំ** | -| `nightly-llm-security` | ការការពារ injection របស់ promptfoo (block mode) + probe របស់ garak (រំលងប្រសិនបើគ្មាន provider secret) | **ជាការណែនាំ** | -| `nightly-schemathesis` | ការធ្វើ fuzzing លើ contract OpenAPI (schemathesis) ទល់នឹង OmniRoute ដែលកំពុងដំណើរការ ដោយប្រើ `docs/openapi.yaml` — បង្ហាញការបំពាន spec / 500 ដែលមិនបានដោះស្រាយ (ដំណាក់កាល 8 B.4) | **ជាការណែនាំ** | -| `nightly-mutation` | ពិន្ទុ mutation-testing របស់ Stryker លើ lane unit រហ័ស — mutant ដែលរស់រានបង្ហាញពី assert ខ្សោយ | **ជាការណែនាំ** | -| `nightly-compat` | ម៉ាទ្រីសភាពឆបគ្នារបស់ Node engine នៅទូទាំងចន្លោះ `engines.node` ដែលបានគាំទ្រ | **ជាការណែនាំ** | +| Workflow | ផ្ទៀងផ្ទាត់ | ការរារាំង | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | property test របស់ fast-check ជាមួយ random seed + ចំនួនដំណើរការខ្ពស់ | **ជាការណែនាំ** | +| `nightly-resilience` | gate កំណើន heap, ការបញ្ចូល fault បែប chaos, load/soak របស់ k6 | **ជាការណែនាំ** | +| `nightly-llm-security` | ឧបករណ៍ការពារ injection របស់ promptfoo (block mode) + probe របស់ garak (រំលងបើគ្មាន provider secret) | **ជាការណែនាំ** | +| `nightly-schemathesis` | ការ fuzz contract OpenAPI (schemathesis) ប្រឆាំងនឹង OmniRoute ដែលកំពុងដំណើរការ ដោយប្រើ `docs/openapi.yaml` — បង្ហាញការបំពាន spec / error 500 ដែលមិនបានដោះស្រាយ (Fase 8 B.4) | **ជាការណែនាំ** | +| `nightly-mutation` | ពិន្ទុ mutation testing របស់ Stryker លើ lane unit test លឿន — mutant ដែលនៅរស់បង្ហាញពី assert ខ្សោយ | **ជាការណែនាំ** | +| `nightly-compat` | ម៉ាទ្រីសភាពឆបគ្នារបស់ Node engine នៅទូទាំងចន្លោះ `engines.node` ដែលបានគាំទ្រ | **ជាការណែនាំ** | --- @@ -536,43 +536,43 @@ Job នេះមិនដែល push ទៅ `release/*` ទេ — មនុស --- -## បញ្ជីការងាររង់ចាំសម្រាប់ការរៀបចំសមហេតុផល (ការពិនិត្យ ROI — ដំណាក់កាល 9 រលក 3) +## បញ្ជីការងាររង់ចាំសម្រាប់ការរៀបចំឱ្យសមហេតុផល (ការពិនិត្យ ROI — ដំណាក់កាលទី 9 រលកទី 3) បញ្ជីសារពើភណ្ឌនេះត្រូវបានផ្ទៀងផ្ទាត់ជាមួយ `ci.yml` នៅថ្ងៃទី 2026-06-17 (កំណែមុនបានរំលង `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`)។ ការពិនិត្យ ROI លើសំណុំដែលបានផ្ទៀងផ្ទាត់ -បានកំណត់ឃើញបេក្ខភាពសម្រាប់ការរៀបចំសមហេតុផលដូចខាងក្រោម។ **ការបញ្ចូលគ្នាទាំងនេះគឺជា -ការផ្លាស់ប្តូរ CI បែបមេកានិច ចំណែកការប្តូរ/លុបចោលគឺជាការសម្រេចចិត្តផ្នែកគោលនយោបាយ -ដែលទុកជូនអ្នកប្រតិបត្តិ។** មិនទាន់មានអ្វីខាងក្រោមត្រូវបានអនុវត្តនៅឡើយទេ។ +បានកំណត់ឃើញបេក្ខភាពខាងក្រោមសម្រាប់ការរៀបចំឱ្យសមហេតុផល។ **ការបញ្ចូលគ្នាគឺជាការផ្លាស់ប្តូរ CI +បែបមេកានិច ចំណែកការប្តូរ/លុបគឺជាការសម្រេចចិត្តផ្នែកគោលនយោបាយដែលទុកឱ្យប្រតិបត្តិករ។** មិនទាន់មានអ្វី +ខាងក្រោមត្រូវបានអនុវត្តនៅឡើយទេ។ -**អ្វីដែលមិនបានចងក្រងជាឯកសារនៅខាងលើផងដែរ** (ជាការណែនាំ សញ្ញាខ្សោយ)៖ ការងារ `docs-lint` -(markdownlint + Vale ដោយការងារទាំងមូលប្រើ `continue-on-error`) និង workflow ស្កេនដាច់ដោយឡែក +**អ្វីដែលមិនបានចងក្រងជាឯកសារនៅខាងលើផងដែរ** (ជាការណែនាំ សញ្ញាខ្សោយ)៖ job `docs-lint` +(markdownlint + Vale, job ទាំងមូលប្រើ `continue-on-error`) និង workflow ស្កេនដាច់ដោយឡែក `semgrep.yml` / `codeql.yml` / `scorecard.yml`។ `semgrepFindings: 0` មាននៅក្នុង -`quality-baseline.json` ប៉ុន្តែមិនត្រូវបានភ្ជាប់ទៅនឹង ratchet ដែលទប់ស្កាត់នៅក្នុង `ci.yml` ទេ — ម៉ែត្រនេះ -បច្ចុប្បន្នគ្មានការតភ្ជាប់។ +`quality-baseline.json` ប៉ុន្តែមិនត្រូវបានភ្ជាប់ទៅ ratchet ដែលរារាំងនៅក្នុង `ci.yml` ទេ — metric នេះ +បច្ចុប្បន្ននៅឯកោ។ ### បញ្ចូលគ្នា / លុបភាពស្ទួន (បែបមេកានិច ហានិភ័យទាបជាង) -បេក្ខភាពនីមួយៗត្រូវបានផ្ទៀងផ្ទាត់ជាមួយស្ថានភាព gate ជាក់ស្តែងនៅថ្ងៃទី 2026-06-17 (ជឿ ប៉ុន្តែត្រូវផ្ទៀងផ្ទាត់); -ការបញ្ចូលគ្នាមួយចំនួនដែលមើលទៅ «ច្បាស់លាស់» បែរជាលាក់បំណុល ហើយ **មិនមែន** ជាជម្រើសជំនួសដោយផ្ទាល់ដែលស្អាតនោះទេ។ - -- **`check:docs-sync` ដំណើរការពីរដង** — ដាច់ដោយឡែកនៅក្នុងការងារ `lint` ហើយម្តងទៀតនៅខាងក្នុង `check:docs-all` (`docs-sync-strict`) និង husky pre-commit hook។ ✅ **រួចរាល់** — បានដកការហៅដំណើរការ `lint` ដាច់ដោយឡែកចេញ។ -- **ការស្កេន CVE** — ❌ **មិនមែនជាការបញ្ចូលគ្នាដែលស្អាតទេ។** `audit:deps` បរាជ័យភ្លាមៗនៅពេលមាន CVE កម្រិតខ្ពស់/ធ្ងន់ធ្ងរណាមួយ; `check:vuln-ratchet` (osv) បរាជ័យតែលើ _ការថយចុះ_ ធៀបនឹង baseline ប៉ុណ្ណោះ (បច្ចុប្បន្នមាន 1 MODERATE)។ អត្ថន័យខុសគ្នា — ការលុប `audit:deps` នឹងធ្វើឱ្យបាត់ gate ដាច់ខាតសម្រាប់កម្រិតខ្ពស់/ធ្ងន់ធ្ងរ។ រក្សាទុកទាំងពីរ។ -- **ការរកឃើញវដ្ត** — ❌ **មិនមែនជាការបញ្ចូលគ្នាដែលស្អាតទេ។** `check:circular-deps` (dpdm) រាយការណ៍ **91 វដ្ត** (នេះហើយជាមូលហេតុដែលវាគ្រាន់តែជាការណែនាំ); វាមិនអាចត្រូវបានដំឡើងទៅជាការទប់ស្កាត់បានទេ ដោយមិនដោះស្រាយវដ្តទាំងនោះជាមុន ហើយវាមានវិសាលភាពទូលំទូលាយជាង `check:cycles` ដែលបានជ្រើសរើសយ៉ាងច្បាស់លាស់ និងមានស្ថានភាពបៃតង។ រក្សា `check:cycles` ជាការទប់ស្កាត់; ការដោះស្រាយវដ្ត dpdm ទាំង 91 គឺជាបញ្ជីការងាររង់ចាំដាច់ដោយឡែក។ -- **ភាពស្មុគស្មាញ** — ✅ **រួចរាល់** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`)៖ ការស្កេន ESLint មួយដង រាប់តាម ruleId ដូច្នេះ baseline សម្រាប់ cyclomatic+max-lines និង cognitive នៅតែឯករាជ្យពីគ្នា; `check:complexity` / `check:cognitive-complexity` នីមួយៗនៅតែរក្សាទុកសម្រាប់ `--update` ក្នុងមូលដ្ឋាន។ -- **ការប្រឆាំងការប្រឌិតព័ត៌មានសម្រាប់ `/api`** — ✅ **រួចរាល់** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`)៖ បញ្ជីសារពើភណ្ឌ FS មួយនៃ `src/app/api` ខណៈ openapi-routes + docs-symbols នៅតែរាយការណ៍ដោយឯករាជ្យ; ការត្រួតពិនិត្យនីមួយៗនៅតែរក្សាទុកសម្រាប់ការដំណើរការក្នុងមូលដ្ឋាន។ -- **`check:node-runtime` ដំណើរការក្នុងការងារ 11** — ⚠️ **ROI ទាប។** ការងារនីមួយៗមាន runner ដាច់ដោយឡែក ហើយការត្រួតពិនិត្យចំណាយពេល <1s; ការសន្សំសរុបប្រហែល ~10s បើធៀបនឹងការបាត់បង់របាំងការពារថោកមួយសម្រាប់ការងារនីមួយៗ។ មិនសមនឹងការរំខានពីការផ្លាស់ប្តូរទេ។ -- **`typecheck:noimplicit:core` នៅលើ CI lint** — ✅ **បានដកចេញពីការងារ lint** (ពីមុនជាការណែនាំដោយប្រើ `continue-on-error`); ផ្ទៃប្រភេទដែលទប់ស្កាត់គឺ `typecheck:core` + `check:type-coverage`។ ស្គ្រីបក្នុងមូលដ្ឋានត្រូវបានរក្សាទុក។ - -### ប្តូរ / សម្រេចចិត្ត (គោលនយោបាយរបស់អ្នកប្រតិបត្តិ) - -- `check:openapi-security-tiers` (ជាការណែនាំ) — ❌ **មិនអាចប្តូរបានដោយស្អាតទេ។** វាបញ្ចប់ដោយលេខកូដ 0 ប៉ុន្តែព្រមានថា route `traffic-inspector` មួយចំនួននៅក្រោម `LOCAL_ONLY_API_PREFIXES` ខ្វះ annotation `x-loopback-only: true`។ ការអនុវត្តវាជាកាតព្វកិច្ច តម្រូវឱ្យបន្ថែម annotation ទាំងនោះទៅក្នុង `openapi.yaml` ជាមុន។ -- `typecheck:noimplicit:core` (ជាការណែនាំ) — ភាគច្រើនត្រូវបានគ្របដណ្តប់ដោយ ratchet `check:type-coverage` ដែលទប់ស្កាត់។ ប្តូរទៅជា ratchet ឬលុបការដំណើរការ `tsc` លើកទីពីរដែលស្ទួន។ -- `test:vitest:ui` (ឥឡូវ **ទប់ស្កាត់**) — ការបរាជ័យដែលមានស្រាប់ត្រូវបានដកចេញយ៉ាងច្បាស់នៅក្នុង `vitest.config.ts` ដោយមានមតិយោបល់តាមដាន `// #8618`; ការបរាជ័យថ្មីនឹងធ្វើឱ្យការងារបរាជ័យ។ -- `check:secrets` (gitleaks, ratchet ទប់ស្កាត់ដែលត្រូវបានកំណត់ថេរនៅលទ្ធផលវិជ្ជមានក្លែងក្លាយ 3 ដែលបានចងក្រងជាឯកសារ) — ដាក់ 3 នោះក្នុង allowlist ដើម្បីឱ្យដល់ 0 ឬបន្ថយទៅជាការណែនាំ។ វាត្រួតស៊ីគ្នាជាមួយការស្កេន secret ដើមរបស់ GitHub + `check:public-creds`។ -- `check:pr-evidence` (ទប់ស្កាត់ និងស្វែងរកអត្ថបទក្នុងតួ PR ដោយ grep) — មានហានិភ័យខ្ពស់នៃលទ្ធផលវិជ្ជមានក្លែងក្លាយ; ប្រសិនបើលុប វានឹងធ្វើឱ្យការអនុវត្ត Hard Rule #18 ចុះខ្សោយ ដូច្នេះនេះពិតជាការសម្រេចចិត្តផ្នែកគោលនយោបាយ។ -- `semgrep` (ដំណើរការដាច់ដោយឡែកជាការណែនាំ) — ត្រួតស៊ីគ្នាជាមួយ CodeQL សម្រាប់ក្រុម OWASP; ភ្ជាប់ baseline របស់វាទៅនឹង ratchet ឬលុបចោល។ +បេក្ខភាពនីមួយៗត្រូវបានផ្ទៀងផ្ទាត់ជាមួយស្ថានភាព gate ជាក់ស្តែងនៅថ្ងៃទី 2026-06-17 (ទុកចិត្ត ប៉ុន្តែត្រូវផ្ទៀងផ្ទាត់); +ការបញ្ចូលគ្នាដែលមើលទៅ "ជាក់ស្តែង" មួយចំនួន បែរជាលាក់បំណុល ហើយ **មិនមែន** ជាការជំនួសដោយផ្ទាល់ដែលគ្មានបញ្ហាទេ។ + +- **`check:docs-sync` ដំណើរការពីរដង** — ដាច់ដោយឡែកនៅក្នុង job `lint` និងម្តងទៀតនៅក្នុង `check:docs-all` (`docs-sync-strict`) ព្រមទាំង husky pre-commit hook។ ✅ **រួចរាល់** — invocation ដាច់ដោយឡែកក្នុង `lint` ត្រូវបានដកចេញ។ +- **ការស្កេន CVE** — ❌ **មិនមែនជាការបញ្ចូលគ្នាដែលគ្មានបញ្ហាទេ។** `audit:deps` បរាជ័យភ្លាមៗលើ CVE កម្រិត high/critical ណាមួយ; `check:vuln-ratchet` (osv) បរាជ័យតែលើ _ការធ្លាក់ចុះ_ ធៀបនឹង baseline ប៉ុណ្ណោះ (បច្ចុប្បន្នមាន 1 MODERATE)។ អត្ថន័យខុសគ្នា — ការលុប `audit:deps` នឹងធ្វើឱ្យបាត់បង់ gate ដាច់ខាតសម្រាប់កម្រិត high/critical។ រក្សាទុកទាំងពីរ។ +- **ការរកឃើញវដ្ត** — ✅ **រួចរាល់** (#15159 G-01/G-02)។ អត្ថបទចាស់នៅទីនេះបានហៅ `check:cycles` ថាជា gate "ពណ៌បៃតង ដែលបានជ្រើសរើសយ៉ាងប្រុងប្រយ័ត្ន" និងបានលើកហេតុផលឱ្យរក្សាវាជា blocking ព្រោះ `check:circular-deps` (dpdm) បានរាយការណ៍វដ្តចំនួន 91។ ស្ថានភាពពណ៌បៃតងនោះគឺជា **ភាពវិជ្ជមានក្លែងក្លាយ**៖ `check:cycles` បានស្កេនថតរងចំនួន 5 (450 files), ផ្គូផ្គងតែ `import|export … from` បែប static ហើយបានបោះបង់រាល់ specifier `@/` និង `@omniroute/open-sse/` ដូច្នេះវាមិនអាចមើលឃើញវដ្ត dynamic-import + alias ដែលមានច្រើនជាងគេក្នុង repo បានទេ។ បានកែរួច៖ ឥឡូវ gate ដើរឆ្លងកាត់ `src` + `open-sse` (5023 files), ប្រមូល specifier ពី TypeScript AST (ដូច្នេះ `import("…")` ត្រូវបានរាប់ ខណៈ `typeof import("…")` នៅទីតាំង type មិនត្រូវបានរាប់) ហើយ resolve `paths` របស់ tsconfig។ វារកឃើញវដ្តចំនួន **14** មិនមែន 0 ទេ។ ដោយសារវដ្តដែលមានស្រាប់ចំនួន 14 មិនអាចត្រូវបានជួសជុលនៅក្នុង gate PR បាន `check:cycles` ឥឡូវនេះគឺជា **ratchet** (`--ratchet`, ពិដាន `metrics.cycles.value = 14` ក្នុង `quality-baseline.json`, `direction: down`) — វារារាំង _ការធ្លាក់ចុះ_ ណាមួយ ហើយចំនួននេះអាចតែថយចុះប៉ុណ្ណោះ។ CI ដំណើរការ `npm run check:cycles:ratchet`។ ការកាត់បន្ថយជាបន្តបន្ទាប់ទៅជាមួយ **A-01**។ `check:circular-deps` (dpdm) នៅតែជាការណែនាំក្នុងនាមជាមតិទីពីរដែលទូលំទូលាយជាង។ +- **ភាពស្មុគស្មាញ** — ✅ **រួចរាល់** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`)៖ ការដើរឆ្លងកាត់ ESLint មួយដង រាប់តាម ruleId ដើម្បីឱ្យ baseline របស់ cyclomatic+max-lines និង cognitive នៅដាច់ពីគ្នា; `check:complexity` / `check:cognitive-complexity` រៀងៗខ្លួននៅតែរក្សាទុកសម្រាប់ `--update` នៅមូលដ្ឋាន។ +- **ការប្រឆាំងការបំភាន់សម្រាប់ `/api`** — ✅ **រួចរាល់** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`)៖ បញ្ជីសារពើភណ្ឌ FS មួយនៃ `src/app/api`, openapi-routes + docs-symbols នៅតែរាយការណ៍ដោយឯករាជ្យ; មុខងាររៀងៗខ្លួននៅតែរក្សាទុកសម្រាប់ការដំណើរការនៅមូលដ្ឋាន។ +- **`check:node-runtime` ដំណើរការនៅក្នុង 11 jobs** — ⚠️ **ROI ទាប។** Job នីមួយៗប្រើ runner ដាច់ដោយឡែក ហើយការត្រួតពិនិត្យចំណាយពេល <1s; ការសន្សំសរុប ~10s ប៉ុន្តែត្រូវបាត់បង់ guard ក្នុងមួយ job ដែលមានតម្លៃថោក។ មិនសមនឹងការរំខានដោយការផ្លាស់ប្តូរទេ។ +- **`typecheck:noimplicit:core` លើ CI lint** — ✅ **ត្រូវបានដកចេញពី job lint** (ពីមុនជាការណែនាំដោយប្រើ `continue-on-error`); ផ្ទៃ type ដែលជា blocking គឺ `typecheck:core` + `check:type-coverage`។ Script នៅមូលដ្ឋានត្រូវបានរក្សាទុក។ + +### ប្តូរ / សម្រេចចិត្ត (គោលនយោបាយរបស់ប្រតិបត្តិករ) + +- `check:openapi-security-tiers` (ជាការណែនាំ) — ❌ **មិនអាចប្តូរបានដោយគ្មានបញ្ហាទេ។** វាបញ្ចប់ដោយលេខកូដ 0 ប៉ុន្តែព្រមានថា route `traffic-inspector` មួយចំនួននៅក្រោម `LOCAL_ONLY_API_PREFIXES` ខ្វះ annotation `x-loopback-only: true`។ ការបង្ខំអនុវត្តវាតម្រូវឱ្យបន្ថែម annotation ទាំងនោះទៅក្នុង `openapi.yaml` ជាមុនសិន។ +- `typecheck:noimplicit:core` (ជាការណែនាំ) — ភាគច្រើនត្រូវបានគ្របដណ្តប់ដោយ ratchet `check:type-coverage` ដែលជា blocking រួចហើយ។ ប្តូរទៅជា ratchet ឬលុប pass `tsc` ទីពីរដែលស្ទួន។ +- `test:vitest:ui` (ឥឡូវជា **blocking**) — failure ដែលមានស្រាប់ត្រូវបានដកចេញយ៉ាងច្បាស់នៅក្នុង `vitest.config.ts` ជាមួយ comment តាមដាន `// #8618`; failure ថ្មីធ្វើឱ្យ job បរាជ័យ។ +- `check:secrets` (gitleaks, blocking ratchet ដែលបានកំណត់ថេរនៅលទ្ធផលវិជ្ជមានក្លែងក្លាយចំនួន 3 ដែលមានឯកសារពន្យល់) — ដាក់ 3 នោះក្នុង allowlist ដើម្បីឱ្យដល់ 0 ឬបន្ថយទៅជាការណែនាំ។ ត្រួតគ្នាជាមួយការស្កេន secret ដើមរបស់ GitHub + `check:public-creds`។ +- `check:pr-evidence` (blocking, ប្រើ greps លើអត្ថបទក្នុង PR body) — មានហានិភ័យ false-positive ខ្ពស់; ប្រសិនបើលុប វានឹងធ្វើឱ្យការអនុវត្ត Hard Rule #18 ចុះខ្សោយ ដូច្នេះនេះជាការសម្រេចចិត្តផ្នែកគោលនយោបាយពិតប្រាកដ។ +- `semgrep` (standalone ជាការណែនាំ) — ត្រួតគ្នាជាមួយ CodeQL សម្រាប់ក្រុម OWASP; ភ្ជាប់ baseline របស់វាទៅ ratchet ឬលុបចេញ។ --- diff --git a/docs/i18n/km/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/km/docs/compression/COMPRESSION_GUIDE.md index f57e442986bc..3781034299f1 100644 --- a/docs/i18n/km/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/km/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ error strings, URLs និង identifiers ឱ្យនៅដដែល។ លើ body ដែលមាន `messages`, content bypass (`shouldBypassCavemanOutputMode()` នៅក្នុង `open-sse/services/compression/outputMode.ts`) ពិនិត្យសារបីចុងក្រោយ ហើយរំលង រចនាប័ទ្មសម្រាប់ turn ទាំងមូល នៅពេលសារទាំងនោះត្រូវនឹង keywords ស្តីពីសុវត្ថិភាព, សកម្មភាពមិនអាចត្រឡប់វិញបាន, -ការបញ្ជាក់ឱ្យច្បាស់ ឬលំដាប់ដែលមានសារៈសំខាន់។ bypass ដំណើរការតាមការកំណត់ toggle -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) របស់ dashboard។ +ការបញ្ជាក់ឱ្យច្បាស់ ឬលំដាប់ដែលមានសារៈសំខាន់។ bypass ដំណើរការពេល toggle **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) របស់ dashboard បើក ដែលជាការកំណត់លំនាំដើម។ ពេល toggle បិទ រចនាប័ទ្មដែលបានជ្រើសរើសក៏អនុវត្តលើ turn ទាំងនោះដែរ។ នៅពេល bypass អនុញ្ញាតឱ្យ turn ឆ្លងកាត់ `placeSystemInstruction()` (file ដដែល) ដែល មិនដែលបង្កើត `messages[0]` ថ្មី ដាក់ប្លុកទៅក្នុងទីតាំងដំបូងដែលវារកឃើញក្នុងចំណោម៖ diff --git a/docs/i18n/km/docs/reference/ENVIRONMENT.md b/docs/i18n/km/docs/reference/ENVIRONMENT.md index 97c546c779fc..b315c2e46b0b 100644 --- a/docs/i18n/km/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/km/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. តម្លៃសម្ងាត់ដែលត្រូវការ -តម្លៃទាំងនេះ **ត្រូវតែ** កំណត់មុនពេលដំណើរការលើកដំបូង។ ប្រសិនបើគ្មានតម្លៃទាំងនេះទេ កម្មវិធីនឹងបដិសេធមិនចាប់ផ្ដើម ឬដំណើរការជាមួយការកំណត់លំនាំដើមដែលគ្មានសុវត្ថិភាព។ - -| អថេរ | តម្រូវការ | លំនាំដើម | ឯកសារប្រភព | សេចក្ដីពិពណ៌នា | -| ---------------------------- | -------------------------------- | --------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **បាទ/ចាស** | _(គ្មាន)_ | `src/lib/auth` | ចុះហត្ថលេខា/ផ្ទៀងផ្ទាត់ខូគីសម័យទាំងអស់របស់ផ្ទាំងគ្រប់គ្រង (JWT)។ បង្កើតដោយប្រើ `openssl rand -base64 48`។ | -| `API_KEY_SECRET` | **បាទ/ចាស** | _(គ្មាន)_ | `src/lib/db/apiKeys.ts` | សោអ៊ិនគ្រីប AES សម្រាប់តម្លៃ API key ដែលផ្ទុកក្នុង SQLite។ បង្កើតដោយប្រើ `openssl rand -hex 32`។ | -| `INITIAL_PASSWORD` | **បាទ/ចាស** | `CHANGEME` | ស្គ្រីបចាប់ផ្ដើមដំបូង | កំណត់ពាក្យសម្ងាត់ដំបូងសម្រាប់អ្នកគ្រប់គ្រងផ្ទាំងគ្រប់គ្រង (ត្រូវគ្នានឹងតម្លៃលំនាំដើមក្នុង `.env.example` — រក្សាទុកឱ្យឃើញច្បាស់ថាគ្មានសុវត្ថិភាព ដើម្បីបង្ខំឱ្យផ្លាស់ប្ដូរ)។ **ផ្លាស់ប្ដូរមុនពេលប្រើប្រាស់លើកដំបូង។** បន្ទាប់ពីចូលប្រើ សូមផ្លាស់ប្ដូរតាមរយៈ ផ្ទាំងគ្រប់គ្រង → ការកំណត់ → សុវត្ថិភាព។ | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **បាទ/ចាស** (សម្រាប់ production) | _(មិនបានកំណត់)_ | `src/app/api/internal/codex-responses-ws/route.ts` | តម្លៃសម្ងាត់រួមសម្រាប់ស្ពាន WebSocket ខាងក្នុងរបស់ Codex Responses។ ផ្ទៀងផ្ទាត់សំណើស្ពានរវាង WS relay របស់ Electron/browser និង OmniRoute។ ⚠️ **តម្រូវជាចាំបាច់នៅក្នុង production — នៅពេលមិនបានកំណត់ សំណើស្ពាន WS ទាំងអស់នឹងត្រូវបដិសេធ។** បង្កើតដោយប្រើ `openssl rand -base64 32`។ | -| `OMNIROUTE_SW_BUILD_ID` | ទេ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | លេខសម្គាល់សម្រាប់បំបាត់ cache របស់ service worker ដែលកំណត់ជាក់លាក់សម្រាប់ PWA shell (#11779); ជាតម្លៃទីមួយក្នុងលំដាប់ដោះស្រាយ។ | -| `SOURCE_VERSION` | ទេ | _(មិនបានកំណត់)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ជាតម្លៃទីពីរក្នុងលំដាប់ — កំណត់ដោយកម្មវិធីបង្កើត PaaS (ឧ. បែប Heroku) ជា commit ដែលបានដាក់ឱ្យដំណើរការ។ | -| `NEXT_PUBLIC_SW_BUILD_ID` | ទេ | _(បានមកពីការគណនា)_ | `src/shared/components/PwaRegister.tsx` | តម្លៃសាធារណៈនៅពេល build ដែល client ប្រើដើម្បីចុះឈ្មោះ `/sw.js?v=…`; បានមកពីតម្លៃទាំងពីរខាងលើ បន្ទាប់មក git SHA។ | -| `OMNIROUTE_PEER_STAMP_TOKEN` | ទេ (ស្វ័យប្រវត្តិ) | _(ស្វ័យប្រវត្តិរាល់ពេលចាប់ផ្ដើម)_ | `src/server/authz/policies/management.ts` | តម្លៃសម្ងាត់សម្រាប់ process នីមួយៗ ដែលបញ្ជាក់ថាត្រា peer-IP ដែលទុកចិត្តបានមកពី HTTP server ផ្ទាល់របស់ OmniRoute (`scripts/dev/peer-stamp.mjs`)។ middleware ផ្នែក authz ទុកចិត្តលើភាពជិតខាងរបស់សំណើ (ការកំណត់ loopback/LAN សម្រាប់ route ប្រភេទ LOCAL_ONLY) តែនៅពេលត្រានោះមាន token នេះប៉ុណ្ណោះ។ វាត្រូវបានបង្កើតដោយស្វ័យប្រវត្តិរាល់ពេលចាប់ផ្ដើម — សូមទុកវាមិនឱ្យកំណត់; កំណត់វាជាក់លាក់តែសម្រាប់ការរៀបចំ multi-process ដែលត្រូវចែករំលែកត្រាប៉ុណ្ណោះ។ | -| `OMNIROUTE_TRUSTED_PROXIES` | ទេ | _(មិនបានកំណត់)_ | `scripts/dev/peer-stamp.mjs` | IP ឬជួរ CIDR របស់ reverse proxy ដែលបំបែកដោយសញ្ញាក្បៀស ហើយដែលបញ្ជីអនុញ្ញាត/បដិសេធ IP អាចជឿលើ `X-Forwarded-For` / `X-Real-IP` របស់ពួកវា បន្ថែមពីលើ loopback, អាសយដ្ឋានបណ្ដាញឯកជន និង Cloudflare edge ដែលតែងតែត្រូវបានទុកចិត្ត។ ត្រូវការតែសម្រាប់ proxy នៅលើអាសយដ្ឋានសាធារណៈផ្សេងទៀតប៉ុណ្ណោះ; បើមិនដូច្នោះទេ អាសយដ្ឋានផ្ទាល់របស់ proxy នោះគឺជាអាសយដ្ឋានដែលតម្រងយកមកវាយតម្លៃ។ | +តម្លៃសម្ងាត់ដែលមិនទាន់កំណត់ ប៉ុន្តែត្រូវបានសម្គាល់ថា **បាទ/ចាស** នឹងត្រូវបង្កើតដោយស្វ័យប្រវត្តិនៅពេលដំណើរការលើកដំបូង (`JWT_SECRET`, `API_KEY_SECRET`) ហើយរក្សាទុកក្នុង `server.env` នៅក្នុងថតទិន្នន័យ — សូមកំណត់វាដោយច្បាស់លាស់នៅពេលថតនោះជាថតបណ្ដោះអាសន្ន។ ករណីលើកលែងគឺ `OMNIROUTE_WS_BRIDGE_SECRET`៖ វាមិនត្រូវបានរក្សាទុកក្នុង `server.env` ទេ — ចំណុចចាប់ផ្ដើមរបស់ម៉ាស៊ីនបម្រើបង្កើតតម្លៃបណ្ដោះអាសន្នសម្រាប់ពេលចាប់ផ្ដើមរាល់ដង ដែលមានតែស្ពានក្នុងដំណើរការប៉ុណ្ណោះអាចប្រើបាន ដូច្នេះម៉ាស៊ីនភាគីស្ពានពីខាងក្រៅត្រូវការកំណត់វាដោយច្បាស់លាស់។ + +| អថេរ | តម្រូវការ | លំនាំដើម | ឯកសារប្រភព | សេចក្ដីពិពណ៌នា | +| ---------------------------- | -------------------------------- | --------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **បាទ/ចាស** | _(គ្មាន)_ | `src/lib/auth` | ចុះហត្ថលេខា/ផ្ទៀងផ្ទាត់ខូគីសម័យទាំងអស់របស់ផ្ទាំងគ្រប់គ្រង (JWT)។ បង្កើតដោយប្រើ `openssl rand -base64 48`។ | +| `API_KEY_SECRET` | **បាទ/ចាស** | _(គ្មាន)_ | `src/lib/db/apiKeys.ts` | សោអ៊ិនគ្រីប AES សម្រាប់តម្លៃ API key ដែលផ្ទុកក្នុង SQLite។ បង្កើតដោយប្រើ `openssl rand -hex 32`។ | +| `INITIAL_PASSWORD` | ទេ | _(មិនបានកំណត់)_ | `src/lib/auth/managementPassword.ts` | កំណត់ពាក្យសម្ងាត់ផ្ទាំងគ្រប់គ្រងនៅពេលចាប់ផ្ដើម ខណៈពេលដែលមិនទាន់មានពាក្យសម្ងាត់ត្រូវបានរក្សាទុក; វានឹងមិនត្រូវអើពើនៅពេលពាក្យសម្ងាត់ត្រូវបានរក្សាទុករួចហើយ។ ការដំឡើងតាម npm និងពី source ចម្លង `.env.example` ទៅជា `.env` ដូច្នេះពួកវាចាប់ផ្ដើមជាមួយគោលកំណត់ជំនួសដែលគេស្គាល់ធំទូលាយរបស់វា `CHANGEME`៖ សូមផ្លាស់ប្ដូរវាមុនពេលចាប់ផ្ដើមលើកដំបូង ឬកំណត់ `INITIAL_PASSWORD=` (ទទេ) ក្នុង `.env` នោះ ដើម្បីបង្កើតពាក្យសម្ងាត់ក្នុង setup wizard នៅពេលចូលប្រើលើកដំបូង ដូចដែលតម្លៃមិនបានកំណត់ (លំនាំដើមរបស់រូបភាព Docker) ធ្វើដូច្នេះ។ ផ្លាស់ប្ដូរពាក្យសម្ងាត់ដែលបានរក្សាទុកតាមរយៈ ផ្ទាំងគ្រប់គ្រង → ការកំណត់ → សុវត្ថិភាព ឬរត់ `omniroute reset-password` (`node bin/reset-password.mjs` ក្នុង source checkout) ដោយកំណត់ `DATA_DIR` ទៅថតទិន្នន័យរបស់ម៉ាស៊ីនបម្រើ។ | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **បាទ/ចាស** (សម្រាប់ production) | _(មិនបានកំណត់)_ | `src/app/api/internal/codex-responses-ws/route.ts` | តម្លៃសម្ងាត់រួមសម្រាប់ស្ពាន WebSocket ខាងក្នុងរបស់ Codex Responses។ ផ្ទៀងផ្ទាត់សំណើស្ពានរវាង WS relay របស់ Electron/browser និង OmniRoute។ ⚠️ **តម្រូវជាចាំបាច់នៅក្នុង production — នៅពេលមិនបានកំណត់ សំណើស្ពាន WS ទាំងអស់នឹងត្រូវបដិសេធ។** បង្កើតដោយប្រើ `openssl rand -base64 32`។ | +| `OMNIROUTE_SW_BUILD_ID` | ទេ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | លេខសម្គាល់សម្រាប់បំបាត់ cache របស់ service worker ដែលកំណត់ជាក់លាក់សម្រាប់ PWA shell (#11779); ជាតម្លៃទីមួយក្នុងលំដាប់ដោះស្រាយ។ | +| `SOURCE_VERSION` | ទេ | _(មិនបានកំណត់)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ជាតម្លៃទីពីរក្នុងលំដាប់ — កំណត់ដោយកម្មវិធីបង្កើត PaaS (ឧ. បែប Heroku) ជា commit ដែលបានដាក់ឱ្យដំណើរការ។ | +| `NEXT_PUBLIC_SW_BUILD_ID` | ទេ | _(បានមកពីការគណនា)_ | `src/shared/components/PwaRegister.tsx` | តម្លៃសាធារណៈនៅពេល build ដែល client ប្រើដើម្បីចុះឈ្មោះ `/sw.js?v=…`; បានមកពីតម្លៃទាំងពីរខាងលើ បន្ទាប់មក git SHA។ | +| `OMNIROUTE_PEER_STAMP_TOKEN` | ទេ (ស្វ័យប្រវត្តិ) | _(ស្វ័យប្រវត្តិរាល់ពេលចាប់ផ្ដើម)_ | `src/server/authz/policies/management.ts` | តម្លៃសម្ងាត់សម្រាប់ process នីមួយៗ ដែលបញ្ជាក់ថាត្រា peer-IP ដែលទុកចិត្តបានមកពី HTTP server ផ្ទាល់របស់ OmniRoute (`scripts/dev/peer-stamp.mjs`)។ middleware ផ្នែក authz ទុកចិត្តលើភាពជិតខាងរបស់សំណើ (ការកំណត់ loopback/LAN សម្រាប់ route ប្រភេទ LOCAL_ONLY) តែនៅពេលត្រានោះមាន token នេះប៉ុណ្ណោះ។ វាត្រូវបានបង្កើតដោយស្វ័យប្រវត្តិរាល់ពេលចាប់ផ្ដើម — សូមទុកវាមិនឱ្យកំណត់; កំណត់វាជាក់លាក់តែសម្រាប់ការរៀបចំ multi-process ដែលត្រូវចែករំលែកត្រាប៉ុណ្ណោះ។ | +| `OMNIROUTE_TRUSTED_PROXIES` | ទេ | _(មិនបានកំណត់)_ | `scripts/dev/peer-stamp.mjs` | IP ឬជួរ CIDR របស់ reverse proxy ដែលបំបែកដោយសញ្ញាក្បៀស ហើយដែលបញ្ជីអនុញ្ញាត/បដិសេធ IP អាចជឿលើ `X-Forwarded-For` / `X-Real-IP` របស់ពួកវា បន្ថែមពីលើ loopback, អាសយដ្ឋានបណ្ដាញឯកជន និង Cloudflare edge ដែលតែងតែត្រូវបានទុកចិត្ត។ ត្រូវការតែសម្រាប់ proxy នៅលើអាសយដ្ឋានសាធារណៈផ្សេងទៀតប៉ុណ្ណោះ; បើមិនដូច្នោះទេ អាសយដ្ឋានផ្ទាល់របស់ proxy នោះគឺជាអាសយដ្ឋានដែលតម្រងយកមកវាយតម្លៃ។ | ### ពាក្យបញ្ជាសម្រាប់បង្កើត @@ -215,38 +215,38 @@ OmniRoute ប្រើ **SQLite** (តាមរយៈ `better-sqlite3`) សម ## 4. សុវត្ថិភាព និងការផ្ទៀងផ្ទាត់អត្តសញ្ញាណ -| អថេរ | តម្លៃលំនាំដើម | ឯកសារប្រភព | សេចក្ដីពិពណ៌នា | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | តម្លៃ salt ដែលត្រូវបានបញ្ចូលគ្នាជាមួយឧបករណ៍កំណត់អត្តសញ្ញាណផ្នែករឹង ដើម្បីបង្កើតស្នាមម្រាមដៃម៉ាស៊ីន។ ផ្លាស់ប្ដូរវាសម្រាប់ការដាក់ឱ្យប្រើប្រាស់នីមួយៗ ដើម្បីធានាភាពដាច់ដោយឡែក។ | -| `OMNIROUTE_CLI_SALT` | _(មិនបានកំណត់ = salt ចៃដន្យសម្រាប់ការដំឡើងនីមួយៗ ដែលរក្សាទុកនៅ `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt សម្រាប់បង្កើតតូខឹនផ្ទៀងផ្ទាត់អត្តសញ្ញាណ CLI មូលដ្ឋាន។ ការកំណត់តម្លៃនេះនឹងប្ដូរតូខឹន CLI ទាំងអស់នៅលើម៉ាស៊ីន ហើយតែងតែមានអាទិភាពលើ salt ដែលបានរក្សាទុក។ សូមមើល `docs/security/CLI_TOKEN.md`។ | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | កំណត់ទង់ `Secure` លើ cookie សម័យ។ **ត្រូវតែជា `true`** នៅពេលដំណើរការនៅពីក្រោយ HTTPS។ | -| `REQUIRE_API_KEY` | `false` | មីដលវែរ API | នៅពេលជា `true` សំណើប្រូកស៊ី `/v1/*` ទាំងអស់ត្រូវតែរួមបញ្ចូល API key ដែលមានសុពលភាព។ **ទង់នេះមិនគ្រប់គ្រងការចូលប្រើ `GET /v1/models` ទេ** ដោយវាអនុវត្តតាមស្ថានភាពនៃការចូល Dashboard (`requireAuthForModels`) ជំនួសវិញ — ដូច្នេះ ការទទួលបាន `401` ពី `/v1/models` មិនមានន័យថាការធ្វើ inference ត្រូវបានការពារនោះទេ។ សូមមើល `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695)។ | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | អនុញ្ញាតឱ្យបង្ហាញតម្លៃ API key ពេញលេញនៅក្នុង UI របស់ Dashboard។ អាចកំណត់រចនាសម្ព័ន្ធពី Feature Flags របស់ Dashboard; វាបង្កហានិភ័យសុវត្ថិភាពលើ instance ដែលប្រើរួមគ្នា។ | -| `NO_LOG_API_KEY_IDS` | _(ទទេ)_ | `src/lib/compliance/index.ts` | ID របស់ API key ដែលបំបែកដោយសញ្ញាក្បៀស និងមិនត្រូវបានកត់ត្រាសំណើ (សម្រាប់ការអនុលោមតាម GDPR)។ | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(មិនបានកំណត់ = គ្មានដែនកំណត់)_ | `src/shared/utils/apiKeyPolicy.ts` | ថវិកាសំណើប្រចាំថ្ងៃបម្រុង ដែលអនុវត្តចំពោះ API key ដែលជួរឈរ `rate_limits` របស់វាមានតម្លៃ null។ មិនបានកំណត់ ឬទទេ៖ គ្មានកម្រិតអតិបរមាដោយប្រយោល (#2289, #11017)។ `0` មានន័យដូចគ្នា (គ្មានដែនកំណត់)។ ចំនួនគត់វិជ្ជមាន N បើកឱ្យប្រើ N/ថ្ងៃ, 5N/សប្ដាហ៍, 20N/ខែ។ តម្លៃមិនទទេដែលមានទម្រង់មិនត្រឹមត្រូវ នឹងត្រឡប់ទៅប្រើចន្លោះពេលចាស់ 1000/ថ្ងៃ, 5000/សប្ដាហ៍, 20000/ខែ។ | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ទំហំ body អតិបរមារបស់សំណើដែលត្រូវបានអនុញ្ញាត។ បដិសេធ payload ដែលលើសពីដែនកំណត់នេះ។ | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | body ពិតប្រាកដរបស់សំណើដែលមានទំហំស្មើ ឬលើសពីកម្រិតនេះ នឹងទទួលយក lease សម្រាប់ការអនុញ្ញាតឱ្យដំណើរការងារធ្ងន់កម្រិត process-local បែបអាតូមិក មុនពេលញែក JSON (ផ្លូវ BYTE រួមទាំង `POST /v1/responses`)។ ប្រើយន្តការគេចចេញដោយផ្អែកលើទំហំទំនេរដែលមានសុខភាពល្អដូចគ្នានឹង [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) សម្រាប់រចនាសម្ព័ន្ធធ្ងន់; នៅតែត្រូវបានកំណត់ដោយ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ដើម្បីកុំឱ្យ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) កើតឡើងវិញ។ | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | កម្រិតអតិបរមាដាច់ខាតរបស់ផ្លូវជជែក ដែលត្រូវបានអនុវត្តចំពោះចំនួនបៃដែលបានអានកំឡុងពេលទទួលទិន្នន័យមានដែនកំណត់ រួមទាំងសំណើដែលបាត់ មិនត្រឹមត្រូវ ឬបន្លំ `Content-Length`; ទិន្នន័យលើសកំណត់នឹងទទួលបាន `413`។ | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(មិនបានកំណត់ — គ្មានកម្រិតចំនួនសំណើ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ឥឡូវនេះ កម្រិតចំនួនសំណើ (COUNT) ចាស់នេះនឹងមានប្រសិទ្ធភាពតែនៅពេលកំណត់ដោយច្បាស់លាស់ប៉ុណ្ណោះ។ នៅពេលមិនកំណត់ (លំនាំដើម) ការអនុញ្ញាតសំណើជជែកធ្ងន់ត្រូវបានគ្រប់គ្រងដោយ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ជំនួសវិញ — ថវិកាបៃ (BYTE) ដែលបានគណនាដោយស្វ័យប្រវត្តិតាមពិដានអង្គចងចាំពិតប្រាកដរបស់ដំណើរការ ក្នុង **ដំណើរការតែមួយ** (V8 heap មួយ)។ សំណើ `/v1/responses` ប្រហែល 750k-token ពីរដែលត្រួតគ្នា ធ្វើឱ្យ heap ប្រហែល 12 Gi ដួល (#7849) — នេះជាការព្រមានអំពី **ថវិកាអង្គចងចាំ** មិនមែនជាកម្រិតអតិបរមាដាច់ខាតរបស់ផលិតផលត្រឹម 2 ទេ។ ដំណើរការដែលមានសុខភាពល្អ (heap ទាបជាងអនុបាតបោះបង់) អាចអនុញ្ញាតឱ្យមាន `/v1/responses` វែងៗស្របពេលគ្នាបន្ថែមទៀតតាមរយៈ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`។ កម្មវិធីភ្ញៀវ SSE វែងៗរាប់សិប (40–50) អាស្រ័យលើ heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 មិនមែន “អតិបរមា 2” ទេ។ ការបង្កើនតម្លៃនេះដោយមិនពិចារណាដើម្បី “ប្រើម៉ាស៊ីនមេ” នឹងបង្កបញ្ហា #7849 ឡើងវិញ។ បង្កើនចំនួន **heap** ដោយប្រើ **`DATA_DIR` ឯករាជ្យ N** (#11024); កុំប្រើ `replicas>1` លើឯកសារ SQLite តែមួយ។ | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(គណនាដោយស្វ័យប្រវត្តិ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** តម្លៃកំណត់ជំនួសសម្រាប់ថវិកាបៃនៃការទទួលទិន្នន័យដែលបានគណនាដោយស្វ័យប្រវត្តិ (25% នៃពិដានអង្គចងចាំ V8/cgroup ដែលតឹងជាងគេ ចែកនឹងការពង្រីកបណ្ដោះអាសន្ន 8x)។ តម្លៃដែលបានគណនា និងកំណត់ជាក់លាក់ត្រូវបានកំណត់ក្នុងចន្លោះ 8 MiB–2 GiB។ តួសំណើដែលធំជាងថវិកាមានប្រសិទ្ធភាពនឹងបរាជ័យភ្លាមៗជាមួយ `413 body_exceeds_budget`; ការប្រជែងធនធានរវាងតួសំណើដែលនីមួយៗអាចបម្រើបាន នៅតែអាចសាកល្បងឡើងវិញដោយទទួល `503`។ កម្មវិធីភ្ញៀវ SSE វែងៗស្របពេលគ្នា 40–50 អាស្រ័យលើថវិកានេះ + heap មិនមែនជាកម្រិតដាច់ខាត “អតិបរមា 2” ទេ។ សូមអាន `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` នៅ `/api/monitoring/health` មុននឹងកែសម្រួល។ | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | អនុបាតបោះបង់ដោយសារសម្ពាធ heap (`heapUsed / heap_size_limit`) សម្រាប់ការអនុញ្ញាតសំណើធ្ងន់ប្រភេទ BYTE និង STRUCTURE (#10183, #10268, #10437)។ សំណើធ្ងន់ស្របពេលគ្នាដែលលើស `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ត្រូវបានបោះបង់ជាមួយ `503` ដែលអាចសាកល្បងឡើងវិញបាន លុះត្រាតែ heap ក៏ស្ថិតនៅ ឬលើសអនុបាតនេះដែរ; នៅពេល heap មានសុខភាពល្អ វាត្រូវបានអនុញ្ញាតតាមរយៈចន្លោះបន្ថែមសម្រាប់ស្ថានភាពមានសុខភាពល្អជំនួសវិញ។ | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (លំនាំដើម `1`) | `src/shared/middleware/chatBodyAdmission.ts` | សមត្ថភាពបន្ថែមមានដែនកំណត់សម្រាប់ផ្លូវលឿននៅពេល heap មានសុខភាពល្អ (#10437) សម្រាប់ **ទាំង** STRUCTURE និង BYTE (`admitChatRequest` រួមទាំងតួសំណើ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`)។ បើគ្មានដែនកំណត់នេះ រាល់សំណើរវល់ប៉ុន្តែ heap មានសុខភាពល្អនឹងរំលងការគ្រប់គ្រងការអនុញ្ញាតដោយគ្មានពិដាន។ នៅពេលសិទ្ធិជួលស្របពេលគ្នាចំនួននេះសកម្មតាមផ្លូវ heap មានសុខភាពល្អ សំណើរវល់បន្ថែមនឹងបន្តទៅផ្លូវរង់ចាំមានដែនកំណត់/បោះបង់ដូចគ្នា ដែលប្រើនៅក្រោមសម្ពាធ heap ពិតប្រាកដ។ `0` បិទការរំលងទាំងស្រុង។ | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ចំនួនសារដែលចាត់ថ្នាក់សំណើជជែកថាជាសំណើធ្ងន់ ទោះបីជាតួសំណើរបស់វាស្ថិតក្រោមកម្រិតបៃក៏ដោយ។ | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ចំនួនឧបករណ៍ដែលចាត់ថ្នាក់សំណើជជែកថាជាសំណើធ្ងន់ ទោះបីជាតួសំណើរបស់វាស្ថិតក្រោមកម្រិតបៃក៏ដោយ។ | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ការប៉ាន់ស្មាន token តាមទំហំខ្សែអក្សរបែបប្រុងប្រយ័ត្ន ដែលចាត់ថ្នាក់សំណើថាជាសំណើធ្ងន់; នេះជាតម្លៃតំណាងសម្រាប់ថ្លៃដើមនៃការអនុញ្ញាត មិនមែនជាការបែងចែក token សម្រាប់ការគិតថ្លៃរបស់អ្នកផ្តល់សេវាទេ។ | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (បានបិទ) | `src/shared/middleware/chatBodyAdmission.ts` | កម្រិតប្រវត្តិជជែកជាជម្រើសដែលត្រូវបើកប្រើដោយចេតនា។ បានបិទតាមលំនាំដើម៖ ចំនួនសារគឺជាគោលការណ៍នៃការដាក់ឱ្យដំណើរការ មិនមែនជាលក្ខណៈសកលរបស់សំណើទេ ហើយការកំណត់នៅទីនេះនឹងបដិសេធការសន្ទនាដោយ `413` ចុងក្រោយ មុនពេលខ្សែសង្វាក់បង្ហាប់អាចធ្វើឱ្យវាអាចបម្រើបាន។ កំណើន heap ត្រូវបានកំណត់ដោយ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` និងការបោះបង់ដោយសារសម្ពាធ heap។ កំណត់តម្លៃវិជ្ជមានសម្រាប់ការដាក់ឱ្យដំណើរការដែលមានអង្គចងចាំកម្រិតទាប និងត្រូវការពិដានដាច់ខាត; នៅពេលនោះ ទិន្នន័យលើសកំណត់នឹងទទួលបាន `413` មានរចនាសម្ព័ន្ធ ដែលទាមទារឱ្យបង្រួម។ | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | កម្រិតដាច់ខាតសម្រាប់ការឆ្លើយតបមិនស្ទ្រីមពីប្រភពខាងលើ ដែលត្រូវបានដាក់ក្នុងសតិបណ្ដោះអាសន្នទាំងស្រុង។ នៅពេលលើសកម្រិតនេះ កម្មវិធីអានពីប្រភពខាងលើត្រូវបានបោះបង់ ហើយសំណើនឹងបរាជ័យភ្លាមៗ ជំនួសឱ្យការពង្រីកខ្សែអក្សរដោយគ្មានដែនកំណត់រហូតដល់ heap អស់។ | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ចំនួនបៃអតិបរមានៅលើបណ្ដាញ ដែលត្រូវបញ្ជូនបន្តពីក្បាលតបរបស់ប្រភពខាងលើ។ នៅពេលលើសកម្រិតកំណត់ ក្បាលដែលមានអាទិភាពទាបជាង (ឧ. `x-codex-*`, `x-oai-request-id`) នឹងត្រូវបានដកចេញ ដើម្បីស្ថិតនៅក្នុងដែនកំណត់ទំហំក្បាលទូទៅរបស់ប្រូកស៊ីបញ្ច្រាស។ កំណត់ឱ្យខ្ពស់ជាងនេះ ដើម្បីបញ្ជូនទិន្នន័យមេតាពីប្រភពខាងលើបន្ថែមទៀត ដោយត្រូវប្តូរជាមួយទំហំក្បាលតបដែលធំជាងមុន។ | -| `CORS_ORIGIN` | _(មិនបានកំណត់)_ | `src/server/cors/origins.ts` | បញ្ជីអនុញ្ញាត CORS សម្រាប់ប្រភពតែមួយបែបចាស់។ សម្រាប់ការដាក់ឱ្យប្រើប្រាស់ថ្មី សូមប្រើ `CORS_ALLOWED_ORIGINS`។ CORS គឺសម្រាប់តែកម្មវិធី API ក្នុងកម្មវិធីរុករកដែលឆ្លងប្រភពប៉ុណ្ណោះ។ ប្រតិបត្តិការសរសេរដែលបានផ្ទៀងផ្ទាត់អត្តសញ្ញាណពីផ្ទាំងគ្រប់គ្រង ប្រើសំណើពីប្រភពដូចគ្នា រួមជាមួយការការពារ CSRF ដែលភ្ជាប់នឹងសម័យ ជំនួសវិញ។ | -| `CORS_ALLOWED_ORIGINS` | _(មិនបានកំណត់)_ | `src/server/cors/origins.ts` | បញ្ជីអនុញ្ញាត CORS ដែលបំបែកដោយសញ្ញាក្បៀស។ នឹងមិនផ្ញើតួអក្សរជំនួសទាំងអស់ទេ លុះត្រាតែបានកំណត់ `CORS_ALLOW_ALL=true` យ៉ាងជាក់លាក់។ | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ច្រកលើកលែងសម្រាប់តែការអភិវឌ្ឍ ដើម្បីតបត្រឡប់ `Origin` ណាមួយរបស់កម្មវិធីរុករក។ កុំបើកវានៅលើការដាក់ឱ្យប្រើប្រាស់រួម ឬក្នុងបរិស្ថានផលិតកម្ម។ | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | ទប់ស្កាត់ការហៅទៅកាន់អ្នកផ្តល់សេវាដែលកំណត់គោលដៅទៅជួរអាសយដ្ឋាន IP ឯកជន/loopback/link-local។ បិទវាតែក្នុងបរិស្ថានសាកល្បងដែលដាច់ដោយឡែកប៉ុណ្ណោះ។ | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | អនុញ្ញាត URL របស់អ្នកផ្តល់សេវាដែលចង្អុលទៅបណ្ដាញឯកជន/មូលដ្ឋាន (localhost, 192.168.x.x, 10.x.x.x ជាដើម)។ **តម្រូវឱ្យមានសម្រាប់អ្នកផ្តល់សេវាដែលបង្ហោះដោយខ្លួនឯង** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)។ នៅពេល `false` ផ្ទាំងគ្រប់គ្រងនឹងបដិសេធការផ្ទៀងផ្ទាត់ URL មូលដ្ឋាន។ | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | អនុញ្ញាតឱ្យបន្ថែម/ផ្ទៀងផ្ទាត់អ្នកផ្តល់សេវានៅលើអាសយដ្ឋានមូលដ្ឋាន/ឯកជន (127.0.0.1, localhost, LAN, ជួរឯកជន) — ដែលមានវិសាលភាពត្រឹមផ្លូវផ្ទៀងផ្ទាត់អ្នកផ្តល់សេវា។ **លំនាំដើម `true`** (ផ្តល់អាទិភាពដល់មូលដ្ឋាន); កំណត់ជា `false` ដើម្បីអនុវត្តការទប់ស្កាត់យ៉ាងតឹងរ៉ឹងសម្រាប់តែអាសយដ្ឋានសាធារណៈ។ ចំណុចបញ្ចប់ទិន្នន័យមេតារបស់ក្លោដ (169.254.169.254, metadata.google.internal) នៅតែត្រូវបានទប់ស្កាត់ ទោះជាកំណត់បែបណាក៏ដោយ។ (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | អនុញ្ញាតឱ្យផ្លូវ `/v1/audio/*` (ការចម្លងសំឡេងជាអក្សរ ការបង្កើតសំឡេង និងការបកប្រែ) ប្រើណូដអ្នកផ្តល់សេវាដែលឆបគ្នាជាមួយ OpenAI និងត្រូវបានបង្ហោះនៅខាងក្រៅ localhost។ តាមលំនាំដើម វាត្រូវបានបិទ — ការបញ្ជូនសំឡេងទៅម៉ាស៊ីនពីចម្ងាយនឹងផ្លាស់ប្ដូរអត្តសញ្ញាណចរាចរចេញ ហើយត្រូវតែជាការសម្រេចចិត្តយ៉ាងជាក់លាក់របស់ប្រតិបត្តិករ។ ណូដ loopback/ឯកជន (localhost, 127.0.0.1, 172.16-31.x) ត្រូវបានអនុញ្ញាតជានិច្ច និងមិនរងផលប៉ះពាល់ទេ។ (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | អនុញ្ញាតឱ្យ `POST /v1/rerank` (និងជំហានរៀបលំដាប់ឡើងវិញតាម loopback របស់ម៉ាស៊ីនមេម៉ូរី) ប្រើណូដអ្នកផ្តល់សេវាដែលឆបគ្នាជាមួយ OpenAI និងត្រូវបានបង្ហោះនៅខាងក្រៅ localhost — ដូចជាម៉ាស៊ីនក្នុង LAN ឬឧបករណ៍ដៃគូ Tailscale ដែលដំណើរការ TEI, Infinity, vLLM ជាដើម។ តាមលំនាំដើម វាត្រូវបានបិទ — ការបញ្ជូនទៅម៉ាស៊ីនពីចម្ងាយនឹងផ្លាស់ប្ដូរអត្តសញ្ញាណចរាចរចេញ ហើយត្រូវតែជាការសម្រេចចិត្តយ៉ាងជាក់លាក់របស់ប្រតិបត្តិករ។ ណូដ loopback (localhost, 127.0.0.1, 172.16-31.x) ត្រូវបានអនុញ្ញាតជានិច្ច និងមិនរងផលប៉ះពាល់ទេ។ ណូដពីចម្ងាយក៏ត្រូវតែឆ្លងកាត់គោលការណ៍ URL ចេញរបស់អ្នកផ្តល់សេវាផងដែរ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); ម៉ាស៊ីនទិន្នន័យមេតារបស់ក្លោដមិនត្រូវបានបញ្ជូនទៅជាដាច់ខាត។ | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | នៅពេលបើក OIDC សូមបិទការចូលដោយពាក្យសម្ងាត់ ដើម្បីឱ្យអ្នកប្រើអាចផ្ទៀងផ្ទាត់អត្តសញ្ញាណបានតែតាមរយៈការចូលតែមួយ OIDC ប៉ុណ្ណោះ។ ឈ្មោះកាត់ `OIDC_DISABLE_PASSWORD_LOGIN` ក៏ត្រូវបានទទួលយកផងដែរ; ទង់មុខងារនៅផ្ទាំងគ្រប់គ្រងដែលមានសោដូចគ្នា មានអាទិភាពជាង។ (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | ឈ្មោះកាត់របស់ `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889)។ | +| អថេរ | តម្លៃលំនាំដើម | ឯកសារប្រភព | សេចក្ដីពិពណ៌នា | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | តម្លៃ salt ដែលត្រូវបានបញ្ចូលគ្នាជាមួយឧបករណ៍កំណត់អត្តសញ្ញាណផ្នែករឹង ដើម្បីបង្កើតស្នាមម្រាមដៃម៉ាស៊ីន។ ផ្លាស់ប្ដូរវាសម្រាប់ការដាក់ឱ្យប្រើប្រាស់នីមួយៗ ដើម្បីធានាភាពដាច់ដោយឡែក។ | +| `OMNIROUTE_CLI_SALT` | _(មិនបានកំណត់ = salt ចៃដន្យសម្រាប់ការដំឡើងនីមួយៗ ដែលរក្សាទុកនៅ `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt សម្រាប់បង្កើតតូខឹនផ្ទៀងផ្ទាត់អត្តសញ្ញាណ CLI មូលដ្ឋាន។ ការកំណត់តម្លៃនេះនឹងប្ដូរតូខឹន CLI ទាំងអស់នៅលើម៉ាស៊ីន ហើយតែងតែមានអាទិភាពលើ salt ដែលបានរក្សាទុក។ សូមមើល `docs/security/CLI_TOKEN.md`។ | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | កំណត់ទង់ `Secure` លើ cookie សម័យ។ **ត្រូវតែជា `true`** នៅពេលដំណើរការនៅពីក្រោយ HTTPS។ | +| `REQUIRE_API_KEY` | `false` | មីដលវែរ API | នៅពេលជា `true` សំណើប្រូកស៊ី `/v1/*` ទាំងអស់ត្រូវតែរួមបញ្ចូល API key ដែលមានសុពលភាព។ **ទង់នេះមិនគ្រប់គ្រងការចូលប្រើ `GET /v1/models` ទេ** ដោយវាអនុវត្តតាមស្ថានភាពនៃការចូល Dashboard (`requireAuthForModels`) ជំនួសវិញ — ដូច្នេះ ការទទួលបាន `401` ពី `/v1/models` មិនមានន័យថាការធ្វើ inference ត្រូវបានការពារនោះទេ។ សូមមើល `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695)។ | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | អនុញ្ញាតឱ្យបង្ហាញតម្លៃ API key ពេញលេញនៅក្នុង UI របស់ Dashboard។ អាចកំណត់រចនាសម្ព័ន្ធពី Feature Flags របស់ Dashboard; វាបង្កហានិភ័យសុវត្ថិភាពលើ instance ដែលប្រើរួមគ្នា។ | +| `NO_LOG_API_KEY_IDS` | _(ទទេ)_ | `src/lib/compliance/index.ts` | ID របស់ API key ដែលបំបែកដោយសញ្ញាក្បៀស និងមិនត្រូវបានកត់ត្រាសំណើ (សម្រាប់ការអនុលោមតាម GDPR)។ | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(មិនបានកំណត់ = គ្មានដែនកំណត់)_ | `src/shared/utils/apiKeyPolicy.ts` | ថវិកាសំណើប្រចាំថ្ងៃបម្រុង ដែលអនុវត្តចំពោះ API key ដែលជួរឈរ `rate_limits` របស់វាមានតម្លៃ null។ មិនបានកំណត់ ឬទទេ៖ គ្មានកម្រិតអតិបរមាដោយប្រយោល (#2289, #11017)។ `0` មានន័យដូចគ្នា (គ្មានដែនកំណត់)។ ចំនួនគត់វិជ្ជមាន N បើកឱ្យប្រើ N/ថ្ងៃ, 5N/សប្ដាហ៍, 20N/ខែ។ តម្លៃមិនទទេដែលមានទម្រង់មិនត្រឹមត្រូវ នឹងត្រឡប់ទៅប្រើចន្លោះពេលចាស់ 1000/ថ្ងៃ, 5000/សប្ដាហ៍, 20000/ខែ។ | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ទំហំ body អតិបរមារបស់សំណើដែលត្រូវបានអនុញ្ញាត។ បដិសេធ payload ដែលលើសពីដែនកំណត់នេះ។ | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | body ពិតប្រាកដរបស់សំណើដែលមានទំហំស្មើ ឬលើសពីកម្រិតនេះ នឹងទទួលយក lease សម្រាប់ការអនុញ្ញាតឱ្យដំណើរការងារធ្ងន់កម្រិត process-local បែបអាតូមិក មុនពេលញែក JSON (ផ្លូវ BYTE រួមទាំង `POST /v1/responses`)។ ប្រើយន្តការគេចចេញដោយផ្អែកលើទំហំទំនេរដែលមានសុខភាពល្អដូចគ្នានឹង [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) សម្រាប់រចនាសម្ព័ន្ធធ្ងន់; នៅតែត្រូវបានកំណត់ដោយ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ដើម្បីកុំឱ្យ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) កើតឡើងវិញ។ | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | កម្រិតអតិបរមាដាច់ខាតរបស់ផ្លូវជជែក ដែលត្រូវបានអនុវត្តចំពោះចំនួនបៃដែលបានអានកំឡុងពេលទទួលទិន្នន័យមានដែនកំណត់ រួមទាំងសំណើដែលបាត់ មិនត្រឹមត្រូវ ឬបន្លំ `Content-Length`; ទិន្នន័យលើសកំណត់នឹងទទួលបាន `413`។ | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(មិនបានកំណត់ — គ្មានកម្រិតចំនួនសំណើ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ឥឡូវនេះ កម្រិតចំនួនសំណើ (COUNT) ចាស់នេះនឹងមានប្រសិទ្ធភាពតែនៅពេលកំណត់ដោយច្បាស់លាស់ប៉ុណ្ណោះ។ នៅពេលមិនកំណត់ (លំនាំដើម) ការអនុញ្ញាតសំណើជជែកធ្ងន់ត្រូវបានគ្រប់គ្រងដោយ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ជំនួសវិញ — ថវិកាបៃ (BYTE) ដែលបានគណនាដោយស្វ័យប្រវត្តិតាមពិដានអង្គចងចាំពិតប្រាកដរបស់ដំណើរការ ក្នុង **ដំណើរការតែមួយ** (V8 heap មួយ)។ សំណើ `/v1/responses` ប្រហែល 750k-token ពីរដែលត្រួតគ្នា ធ្វើឱ្យ heap ប្រហែល 12 Gi ដួល (#7849) — នេះជាការព្រមានអំពី **ថវិកាអង្គចងចាំ** មិនមែនជាកម្រិតអតិបរមាដាច់ខាតរបស់ផលិតផលត្រឹម 2 ទេ។ ដំណើរការដែលមានសុខភាពល្អ (heap ទាបជាងអនុបាតបោះបង់) អាចអនុញ្ញាតឱ្យមាន `/v1/responses` វែងៗស្របពេលគ្នាបន្ថែមទៀតតាមរយៈ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`។ កម្មវិធីភ្ញៀវ SSE វែងៗរាប់សិប (40–50) អាស្រ័យលើ heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 មិនមែន “អតិបរមា 2” ទេ។ ការបង្កើនតម្លៃនេះដោយមិនពិចារណាដើម្បី “ប្រើម៉ាស៊ីនមេ” នឹងបង្កបញ្ហា #7849 ឡើងវិញ។ បង្កើនចំនួន **heap** ដោយប្រើ **`DATA_DIR` ឯករាជ្យ N** (#11024); កុំប្រើ `replicas>1` លើឯកសារ SQLite តែមួយ។ | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(គណនាដោយស្វ័យប្រវត្តិ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** តម្លៃកំណត់ជំនួសសម្រាប់ថវិកាបៃនៃការទទួលទិន្នន័យដែលបានគណនាដោយស្វ័យប្រវត្តិ (25% នៃពិដានអង្គចងចាំ V8/cgroup ដែលតឹងជាងគេ ចែកនឹងការពង្រីកបណ្ដោះអាសន្ន 8x)។ តម្លៃដែលបានគណនា និងកំណត់ជាក់លាក់ត្រូវបានកំណត់ក្នុងចន្លោះ 8 MiB–2 GiB។ តួសំណើដែលធំជាងថវិកាមានប្រសិទ្ធភាពនឹងបរាជ័យភ្លាមៗជាមួយ `413 body_exceeds_budget`; ការប្រជែងធនធានរវាងតួសំណើដែលនីមួយៗអាចបម្រើបាន នៅតែអាចសាកល្បងឡើងវិញដោយទទួល `503`។ កម្មវិធីភ្ញៀវ SSE វែងៗស្របពេលគ្នា 40–50 អាស្រ័យលើថវិកានេះ + heap មិនមែនជាកម្រិតដាច់ខាត “អតិបរមា 2” ទេ។ សូមអាន `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` នៅ `/api/monitoring/health` មុននឹងកែសម្រួល។ | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | អនុបាតបោះបង់ដោយសារសម្ពាធ heap (`heapUsed / heap_size_limit`) សម្រាប់ការអនុញ្ញាតសំណើធ្ងន់ប្រភេទ BYTE និង STRUCTURE (#10183, #10268, #10437)។ សំណើធ្ងន់ស្របពេលគ្នាដែលលើស `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ត្រូវបានបោះបង់ជាមួយ `503` ដែលអាចសាកល្បងឡើងវិញបាន លុះត្រាតែ heap ក៏ស្ថិតនៅ ឬលើសអនុបាតនេះដែរ; នៅពេល heap មានសុខភាពល្អ វាត្រូវបានអនុញ្ញាតតាមរយៈចន្លោះបន្ថែមសម្រាប់ស្ថានភាពមានសុខភាពល្អជំនួសវិញ។ | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (លំនាំដើម `1`) | `src/shared/middleware/chatBodyAdmission.ts` | សមត្ថភាពបន្ថែមមានដែនកំណត់សម្រាប់ផ្លូវលឿននៅពេល heap មានសុខភាពល្អ (#10437) សម្រាប់ **ទាំង** STRUCTURE និង BYTE (`admitChatRequest` រួមទាំងតួសំណើ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`)។ បើគ្មានដែនកំណត់នេះ រាល់សំណើរវល់ប៉ុន្តែ heap មានសុខភាពល្អនឹងរំលងការគ្រប់គ្រងការអនុញ្ញាតដោយគ្មានពិដាន។ នៅពេលសិទ្ធិជួលស្របពេលគ្នាចំនួននេះសកម្មតាមផ្លូវ heap មានសុខភាពល្អ សំណើរវល់បន្ថែមនឹងបន្តទៅផ្លូវរង់ចាំមានដែនកំណត់/បោះបង់ដូចគ្នា ដែលប្រើនៅក្រោមសម្ពាធ heap ពិតប្រាកដ។ `0` បិទការរំលងទាំងស្រុង។ | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ចំនួនសារដែលចាត់ថ្នាក់សំណើជជែកថាជាសំណើធ្ងន់ ទោះបីជាតួសំណើរបស់វាស្ថិតក្រោមកម្រិតបៃក៏ដោយ។ | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ចំនួនឧបករណ៍ដែលចាត់ថ្នាក់សំណើជជែកថាជាសំណើធ្ងន់ ទោះបីជាតួសំណើរបស់វាស្ថិតក្រោមកម្រិតបៃក៏ដោយ។ | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ការប៉ាន់ស្មាន token តាមទំហំខ្សែអក្សរបែបប្រុងប្រយ័ត្ន ដែលចាត់ថ្នាក់សំណើថាជាសំណើធ្ងន់; នេះជាតម្លៃតំណាងសម្រាប់ថ្លៃដើមនៃការអនុញ្ញាត មិនមែនជាការបែងចែក token សម្រាប់ការគិតថ្លៃរបស់អ្នកផ្តល់សេវាទេ។ | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (បានបិទ) | `src/shared/middleware/chatBodyAdmission.ts` | កម្រិតប្រវត្តិជជែកជាជម្រើសដែលត្រូវបើកប្រើដោយចេតនា។ បានបិទតាមលំនាំដើម៖ ចំនួនសារគឺជាគោលការណ៍នៃការដាក់ឱ្យដំណើរការ មិនមែនជាលក្ខណៈសកលរបស់សំណើទេ ហើយការកំណត់នៅទីនេះនឹងបដិសេធការសន្ទនាដោយ `413` ចុងក្រោយ មុនពេលខ្សែសង្វាក់បង្ហាប់អាចធ្វើឱ្យវាអាចបម្រើបាន។ កំណើន heap ត្រូវបានកំណត់ដោយ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` និងការបោះបង់ដោយសារសម្ពាធ heap។ កំណត់តម្លៃវិជ្ជមានសម្រាប់ការដាក់ឱ្យដំណើរការដែលមានអង្គចងចាំកម្រិតទាប និងត្រូវការពិដានដាច់ខាត; នៅពេលនោះ ទិន្នន័យលើសកំណត់នឹងទទួលបាន `413` មានរចនាសម្ព័ន្ធ ដែលទាមទារឱ្យបង្រួម។ | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | កម្រិតដាច់ខាតសម្រាប់ការឆ្លើយតបមិនស្ទ្រីមពីប្រភពខាងលើ ដែលត្រូវបានដាក់ក្នុងសតិបណ្ដោះអាសន្នទាំងស្រុង។ នៅពេលលើសកម្រិតនេះ កម្មវិធីអានពីប្រភពខាងលើត្រូវបានបោះបង់ ហើយសំណើនឹងបរាជ័យភ្លាមៗ ជំនួសឱ្យការពង្រីកខ្សែអក្សរដោយគ្មានដែនកំណត់រហូតដល់ heap អស់។ | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ចំនួនបៃអតិបរមានៅលើបណ្ដាញ ដែលត្រូវបញ្ជូនបន្តពីក្បាលតបរបស់ប្រភពខាងលើ។ នៅពេលលើសកម្រិតកំណត់ ក្បាលដែលមានអាទិភាពទាបជាង (ឧ. `x-codex-*`, `x-oai-request-id`) នឹងត្រូវបានដកចេញ ដើម្បីស្ថិតនៅក្នុងដែនកំណត់ទំហំក្បាលទូទៅរបស់ប្រូកស៊ីបញ្ច្រាស។ កំណត់ឱ្យខ្ពស់ជាងនេះ ដើម្បីបញ្ជូនទិន្នន័យមេតាពីប្រភពខាងលើបន្ថែមទៀត ដោយត្រូវប្តូរជាមួយទំហំក្បាលតបដែលធំជាងមុន។ | +| `CORS_ORIGIN` | _(មិនបានកំណត់)_ | `src/server/cors/origins.ts` | បញ្ជីអនុញ្ញាត CORS សម្រាប់ប្រភពតែមួយបែបចាស់។ សម្រាប់ការដាក់ឱ្យប្រើប្រាស់ថ្មី សូមប្រើ `CORS_ALLOWED_ORIGINS`។ CORS គឺសម្រាប់តែកម្មវិធី API ក្នុងកម្មវិធីរុករកដែលឆ្លងប្រភពប៉ុណ្ណោះ។ ប្រតិបត្តិការសរសេរដែលបានផ្ទៀងផ្ទាត់អត្តសញ្ញាណពីផ្ទាំងគ្រប់គ្រង ប្រើសំណើពីប្រភពដូចគ្នា រួមជាមួយការការពារ CSRF ដែលភ្ជាប់នឹងសម័យ ជំនួសវិញ។ | +| `CORS_ALLOWED_ORIGINS` | _(មិនបានកំណត់)_ | `src/server/cors/origins.ts` | បញ្ជីអនុញ្ញាត CORS ដែលបំបែកដោយសញ្ញាក្បៀស។ នឹងមិនផ្ញើតួអក្សរជំនួសទាំងអស់ទេ លុះត្រាតែបានកំណត់ `CORS_ALLOW_ALL=true` យ៉ាងជាក់លាក់។ | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ច្រកលើកលែងសម្រាប់តែការអភិវឌ្ឍ ដើម្បីតបត្រឡប់ `Origin` ណាមួយរបស់កម្មវិធីរុករក។ កុំបើកវានៅលើការដាក់ឱ្យប្រើប្រាស់រួម ឬក្នុងបរិស្ថានផលិតកម្ម។ | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ឈ្មោះហៅក្រៅចាស់ (legacy)។ កុងតាក់ផ្ទាំងគ្រប់គ្រងរបស់វា (ជាការបដិសេធតម្លៃក្នុង DB) ត្រូវបានអានមុនតម្លៃ environment នេះ; `false`, `0`, `no` ឬ `off` នៅក្នុងមួយណាមួយ នឹងបិទការពិនិត្យម៉ាស៊ីនដូចដែល `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ធ្វើ សូម្បីតែពេលកុងតាក់ផ្ទាំងគ្រប់គ្រងរបស់ flag នោះត្រូវបានរក្សាទុកជាបិទក៏ដោយ; តម្លៃផ្សេងទៀតទុកឱ្យការការពារសម្រាប់ flag ពីរខាងក្រោម។ | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | បិទការពិនិត្យម៉ាស៊ីនរបស់ការការពារ URL ចេញ រួមទាំងការទប់ស្កាត់ទិន្នន័យមេតារបស់ក្លោដ (ទាំងមូលនៃ 169.254.0.0/16, metadata.google.internal) លើផ្លូវអ្នកផ្តល់សេវាដែលអនុវត្តតាមរបៀបការពារ៖ ការផ្ទៀងផ្ទាត់ URL, ការរកឃើញម៉ូដែល, URL មូលដ្ឋានរបស់ណូដអ្នកផ្តល់សេវា (រួមទាំងណូដ rerank ពីចម្ងាយ) និងការសាកល្បង proxy-fallback ក្នុងចំណោមផ្លូវចេញផ្សេងទៀតជិតអ្នកផ្តល់សេវា ដូចជាការទាញយករូបភាព/មេឌៀពីចម្ងាយ។ វាក៏អនុញ្ញាតគោលដៅ webhook ឯកជនផងដែរ លើកលែងតែចំណុចបញ្ចប់ទិន្នន័យមេតារបស់ក្លោដ/link-local ដែលនៅតែត្រូវបានទប់ស្កាត់ សូម្បីតែពេលបើក flag នេះក៏ដោយ។ សំណើសន្ទនាដែលបញ្ជូនតាមរយៈ `BaseExecutor.execute()` រក្សាការពិនិត្យទិន្នន័យមេតារបស់ក្លោដរបស់វា។ អ្នកផ្តល់សេវាមូលដ្ឋានដំណើរការបានជាមួយលំនាំដើម៖ `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (លំនាំដើម `true`) អនុញ្ញាត URL មូលដ្ឋាន និង LAN លើផ្លូវផ្ទៀងផ្ទាត់ រកឃើញ និងណូដអ្នកផ្តល់សេវារួចហើយ; ការសាកល្បង proxy-fallback និងគោលដៅ webhook ឯកជនពិនិត្យតែ flag នេះប៉ុណ្ណោះ ហើយទប់ស្កាត់ម៉ាស៊ីនមូលដ្ឋាន/LAN ខណៈពេលវាបិទ។ បដិសេធលើ `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`។ តម្លៃដែលរក្សាទុកពីកុងតាក់ផ្ទាំងគ្រប់គ្រង មានអាទិភាពលើតម្លៃអថេរនេះ។ | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | អនុញ្ញាត URL របស់អ្នកផ្តល់សេវានៅលើអាសយដ្ឋានមូលដ្ឋាន/ឯកជន (127.0.0.1, localhost, LAN, ជួរឯកជន) សម្រាប់ការផ្ទៀងផ្ទាត់អ្នកផ្តល់សេវា ការរកឃើញម៉ូដែល ការសន្ទនា និងផ្លូវចេញផ្សេងទៀតជិតអ្នកផ្តល់សេវាដែលរបៀបការពារគ្រប់គ្រង។ **លំនាំដើម `true`** (ផ្តល់អាទិភាពដល់មូលដ្ឋាន)៖ ការការពារនឹងទប់ស្កាត់ចំណុចបញ្ចប់ទិន្នន័យមេតារបស់ក្លោដ — ទាំងមូលនៃ 169.254.0.0/16 បូករួមជាមួយឈ្មោះម៉ាស៊ីនទិន្នន័យមេតាដែលស្គាល់ (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254)។ កំណត់ជា `false` សម្រាប់របៀបសាធារណៈប៉ុណ្ណោះ ដែលទប់ស្កាត់ទាំងម៉ាស៊ីនឯកជន និង loopback។ ការការពារពិនិត្យឈ្មោះម៉ាស៊ីន ឬ IP literal តាមដែលបានសរសេរ មុនការស្វែងរក DNS ណាមួយ; ក្នុងរបៀបសាធារណៈប៉ុណ្ណោះ wrapper របស់ safe-fetch រកឃើញឈ្មោះម៉ាស៊ីនបន្ថែមទៀត ហើយបដិសេធចម្លើយឯកជនណាមួយ។ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` បិទការពិនិត្យទាំងនេះលើផ្លូវដែលបានរាយក្នុងជួរដេករបស់វា។ អ្នកផ្តល់សេវាមូលដ្ឋានដែលបង្កប់មក ដូចជា LM Studio, Ollama, vLLM, Llamafile និង Triton រំលងការពិនិត្យម៉ាស៊ីនទាំងអស់សម្រាប់ការផ្ទៀងផ្ទាត់កូនសោ និងការសន្ទនា។ តម្លៃដែលរក្សាទុកពីកុងតាក់ផ្ទាំងគ្រប់គ្រង មានអាទិភាពលើតម្លៃអថេរនេះ។ (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | អនុញ្ញាតឱ្យផ្លូវ `/v1/audio/*` (ការចម្លងសំឡេងជាអក្សរ ការបង្កើតសំឡេង និងការបកប្រែ) ប្រើណូដអ្នកផ្តល់សេវាដែលឆបគ្នាជាមួយ OpenAI និងត្រូវបានបង្ហោះនៅខាងក្រៅ localhost។ តាមលំនាំដើម វាត្រូវបានបិទ — ការបញ្ជូនសំឡេងទៅម៉ាស៊ីនពីចម្ងាយនឹងផ្លាស់ប្ដូរអត្តសញ្ញាណចរាចរចេញ ហើយត្រូវតែជាការសម្រេចចិត្តយ៉ាងជាក់លាក់របស់ប្រតិបត្តិករ។ ណូដ loopback/ឯកជន (localhost, 127.0.0.1, 172.16-31.x) ត្រូវបានអនុញ្ញាតជានិច្ច និងមិនរងផលប៉ះពាល់ទេ។ (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | អនុញ្ញាតឱ្យ `POST /v1/rerank` (និងជំហានរៀបលំដាប់ឡើងវិញតាម loopback របស់ម៉ាស៊ីនមេម៉ូរី) ប្រើណូដអ្នកផ្តល់សេវាដែលឆបគ្នាជាមួយ OpenAI និងត្រូវបានបង្ហោះនៅខាងក្រៅ localhost — ដូចជាម៉ាស៊ីនក្នុង LAN ឬឧបករណ៍ដៃគូ Tailscale ដែលដំណើរការ TEI, Infinity, vLLM ជាដើម។ តាមលំនាំដើម វាត្រូវបានបិទ — ការបញ្ជូនទៅម៉ាស៊ីនពីចម្ងាយនឹងផ្លាស់ប្ដូរអត្តសញ្ញាណចរាចរចេញ ហើយត្រូវតែជាការសម្រេចចិត្តយ៉ាងជាក់លាក់របស់ប្រតិបត្តិករ។ ណូដ loopback (localhost, 127.0.0.1, 172.16-31.x) ត្រូវបានអនុញ្ញាតជានិច្ច និងមិនរងផលប៉ះពាល់ទេ។ URL មូលដ្ឋានរបស់ណូដពីចម្ងាយក៏ត្រូវតែឆ្លងកាត់គោលការណ៍ URL ចេញរបស់អ្នកផ្តល់សេវាផងដែរ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)។ | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | នៅពេលបើក OIDC សូមបិទការចូលដោយពាក្យសម្ងាត់ ដើម្បីឱ្យអ្នកប្រើអាចផ្ទៀងផ្ទាត់អត្តសញ្ញាណបានតែតាមរយៈការចូលតែមួយ OIDC ប៉ុណ្ណោះ។ ឈ្មោះកាត់ `OIDC_DISABLE_PASSWORD_LOGIN` ក៏ត្រូវបានទទួលយកផងដែរ; ទង់មុខងារនៅផ្ទាំងគ្រប់គ្រងដែលមានសោដូចគ្នា មានអាទិភាពជាង។ (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | ឈ្មោះកាត់របស់ `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889)។ | ### បញ្ជីត្រួតពិនិត្យការពង្រឹងសុវត្ថិភាព diff --git a/docs/i18n/kn/CONTRIBUTING.md b/docs/i18n/kn/CONTRIBUTING.md index dbda93e3e43e..7948751d39b8 100644 --- a/docs/i18n/kn/CONTRIBUTING.md +++ b/docs/i18n/kn/CONTRIBUTING.md @@ -398,22 +398,22 @@ model definitions ಅನ್ನು `open-sse/config/providerRegistry.ts` ನಲ --- -## Pull Request ಪರಿಶೀಲನಾಪಟ್ಟಿ - -- [ ] Tests ಯಶಸ್ವಿಯಾಗುತ್ತವೆ (`npm test`) -- [ ] Linting ಯಶಸ್ವಿಯಾಗುತ್ತದೆ (`npm run lint`) -- [ ] Build ಯಶಸ್ವಿಯಾಗುತ್ತದೆ (`npm run build`) -- [ ] ಹೊಸ public functions ಮತ್ತು interfaces ಗಾಗಿ TypeScript types ಸೇರಿಸಲಾಗಿದೆ -- [ ] hardcoded secrets ಅಥವಾ fallback values ಇಲ್ಲ -- [ ] Public upstream credentials ಅನ್ನು `resolvePublicCred()` ಮೂಲಕ embed ಮಾಡಲಾಗಿದೆ ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ನೋಡಿ), literals ಆಗಿ ಎಂದಿಗೂ ಅಲ್ಲ -- [ ] Error responses ಗಳು `buildErrorBody()` / `sanitizeErrorMessage()` ಮೂಲಕ ಹಾದುಹೋಗುತ್ತವೆ — response bodies ನಲ್ಲಿ ಕಚ್ಚಾ stack traces ಇಲ್ಲ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ನೋಡಿ) -- [ ] Shell commands (`exec` / `spawn`) runtime values ಅನ್ನು string interpolation ಮೂಲಕವಲ್ಲ, `env` ಮೂಲಕ ರವಾನಿಸುತ್ತವೆ -- [ ] ಎಲ್ಲಾ inputs ಅನ್ನು Zod schemas ಮೂಲಕ ಮೌಲ್ಯೀಕರಿಸಲಾಗಿದೆ -- [ ] ಬಳಕೆದಾರರಿಗೆ ಗೋಚರಿಸುವ ಬದಲಾವಣೆಗಳಿಗಾಗಿ `changelog.d/{features|fixes|maintenance}/-.md` ಅಡಿಯಲ್ಲಿ Changelog **fragment** ಸೇರಿಸಲಾಗಿದೆ ([`changelog.d/README.md`](./changelog.d/README.md) ನೋಡಿ) — `CHANGELOG.md` ಅನ್ನು ನೇರವಾಗಿ **ತಿದ್ದುಪಡಿ ಮಾಡಬೇಡಿ**; fragments ಅನ್ನು release ಸಮಯದಲ್ಲಿ ಒಟ್ಟುಗೂಡಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಅವು PR ಗಳ ನಡುವೆ ಎಂದಿಗೂ ಸಂಘರ್ಷಿಸುವುದಿಲ್ಲ -- [ ] Documentation ನವೀಕರಿಸಲಾಗಿದೆ (ಅನ್ವಯಿಸಿದರೆ) -- [ ] ಯಾವುದೇ ಹೊಸ CodeQL / Secret-Scanning alerts ತೆರೆಯಲಾಗಿಲ್ಲ, ಅಥವಾ ಪ್ರತಿಯೊಂದನ್ನೂ ಸಂಬಂಧಿತ `docs/security/` doc ಅನ್ನು ಉಲ್ಲೇಖಿಸುವ ತಾಂತ್ರಿಕ ಸಮರ್ಥನೆಯೊಂದಿಗೆ ವಜಾಗೊಳಿಸಲಾಗಿದೆ -- [ ] child processes ಅನ್ನು spawn ಮಾಡುವ routes (`/api/mcp/`, `/api/cli-tools/runtime/`) ಅನ್ನು `src/server/authz/routeGuard.ts` ನಲ್ಲಿ `isLocalOnlyPath()` ಎಂದು ವರ್ಗೀಕರಿಸಲಾಗಿದೆ — [ಕಠಿಣ ನಿಯಮ #15](docs/security/ROUTE_GUARD_TIERS.md) ನೋಡಿ -- [ ] commit messages ನಲ್ಲಿ `Co-Authored-By` trailers ಇಲ್ಲ — commits ಗಳು repository ಮಾಲೀಕರ Git identity ಅಡಿಯಲ್ಲಿ ಮಾತ್ರ ಕಾಣಿಸಬೇಕು (ಕಠಿಣ ನಿಯಮ #16) +## ಪುಲ್ ರಿಕ್ವೆಸ್ಟ್ ಪರಿಶೀಲನಾ ಪಟ್ಟಿ + +- [ ] ಪರೀಕ್ಷೆಗಳು ಯಶಸ್ವಿಯಾಗುತ್ತವೆ (`npm test`) +- [ ] ಲಿಂಟಿಂಗ್ ಯಶಸ್ವಿಯಾಗುತ್ತದೆ (`npm run lint`) +- [ ] ಬಿಲ್ಡ್ ಯಶಸ್ವಿಯಾಗುತ್ತದೆ (`npm run build`) +- [ ] ಹೊಸ ಸಾರ್ವಜನಿಕ ಫಂಕ್ಷನ್ಗಳು ಮತ್ತು ಇಂಟರ್ಫೇಸ್ಗಳಿಗೆ TypeScript ಟೈಪ್ಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ +- [ ] ಹಾರ್ಡ್ಕೋಡ್ ಮಾಡಿದ ರಹಸ್ಯಗಳು ಅಥವಾ ಪರ್ಯಾಯ ಮೌಲ್ಯಗಳಿಲ್ಲ +- [ ] ಸಾರ್ವಜನಿಕ ಅಪ್ಸ್ಟ್ರೀಮ್ ರುಜುವಾತುಗಳನ್ನು `resolvePublicCred()` ಮೂಲಕ ಎಂಬೆಡ್ ಮಾಡಲಾಗಿದೆ ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ನೋಡಿ), ಎಂದಿಗೂ ಲಿಟರಲ್ಗಳಾಗಿ ಅಲ್ಲ +- [ ] ದೋಷ ಪ್ರತಿಕ್ರಿಯೆಗಳನ್ನು `buildErrorBody()` / `sanitizeErrorMessage()` ಮೂಲಕ ರವಾನಿಸಲಾಗಿದೆ — ಪ್ರತಿಕ್ರಿಯೆ ಬಾಡಿಗಳಲ್ಲಿ ಕಚ್ಚಾ ಸ್ಟ್ಯಾಕ್ ಟ್ರೇಸ್ಗಳಿಲ್ಲ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ನೋಡಿ) +- [ ] ಶೆಲ್ ಕಮಾಂಡ್ಗಳು (`exec` / `spawn`) ರನ್ಟೈಮ್ ಮೌಲ್ಯಗಳನ್ನು ಸ್ಟ್ರಿಂಗ್ ಇಂಟರ್ಪೊಲೇಶನ್ ಮೂಲಕ ಅಲ್ಲ, `env` ಮೂಲಕ ರವಾನಿಸುತ್ತವೆ +- [ ] ಎಲ್ಲಾ ಇನ್ಪುಟ್ಗಳನ್ನು Zod ಸ್ಕೀಮಾಗಳೊಂದಿಗೆ ಮೌಲ್ಯೀಕರಿಸಲಾಗಿದೆ +- [ ] ಬಳಕೆದಾರರಿಗೆ ಗೋಚರಿಸುವ ಬದಲಾವಣೆಗಳಿಗಾಗಿ `changelog.d/{features|fixes|maintenance}/-.md` ಅಡಿಯಲ್ಲಿ ಚೇಂಜ್ಲಾಗ್ **ಫ್ರ್ಯಾಗ್ಮೆಂಟ್** ಸೇರಿಸಲಾಗಿದೆ ([`changelog.d/README.md`](./changelog.d/README.md) ನೋಡಿ) — `CHANGELOG.md` ಅನ್ನು ನೇರವಾಗಿ ಸಂಪಾದಿಸ**ಬೇಡಿ**; ಫ್ರ್ಯಾಗ್ಮೆಂಟ್ಗಳನ್ನು ಬಿಡುಗಡೆ ಸಮಯದಲ್ಲಿ ಒಟ್ಟುಗೂಡಿಸಲಾಗುತ್ತದೆ ಮತ್ತು PRಗಳ ನಡುವೆ ಎಂದಿಗೂ ಸಂಘರ್ಷಿಸುವುದಿಲ್ಲ +- [ ] ದಸ್ತಾವೇಜನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ (ಅನ್ವಯಿಸಿದರೆ) +- [ ] ಯಾವುದೇ ಹೊಸ CodeQL / Secret-Scanning ಎಚ್ಚರಿಕೆಗಳನ್ನು ತೆರೆಯಲಾಗಿಲ್ಲ, ಅಥವಾ ಪ್ರತಿಯೊಂದನ್ನೂ ಸಂಬಂಧಿತ `docs/security/` ದಸ್ತಾವೇಜನ್ನು ಉಲ್ಲೇಖಿಸುವ ತಾಂತ್ರಿಕ ಸಮರ್ಥನೆಯೊಂದಿಗೆ ವಜಾಗೊಳಿಸಲಾಗಿದೆ +- [ ] ಚೈಲ್ಡ್ ಪ್ರೊಸೆಸ್ಗಳನ್ನು ಸ್ಪಾನ್ ಮಾಡುವ ರೂಟ್ಗಳನ್ನು (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` ನಲ್ಲಿನ `isLocalOnlyPath()` ಆಗಿ ವರ್ಗೀಕರಿಸಲಾಗಿದೆ — [ಕಠಿಣ ನಿಯಮ #15](docs/security/ROUTE_GUARD_TIERS.md) ನೋಡಿ +- [ ] ಕಮಿಟ್ ಸಂದೇಶಗಳಲ್ಲಿ AI/ಬಾಟ್ `Co-authored-by` ಟ್ರೇಲರ್ಗಳಿಲ್ಲ (ಕಠಿಣ ನಿಯಮ #16) — ಮರುಬಳಕೆ ಮಾಡಲಾದ ಕೆಲಸದ ಮಾನವ ಸಹಯೋಗಿಗಳಿಗೆ ಪ್ರಮಾಣಿತ `Co-authored-by: Name ` ಟ್ರೇಲರ್ಗಳೊಂದಿಗೆ ಮನ್ನಣೆ ನೀಡಲಾಗಿದೆ --- diff --git a/docs/i18n/kn/docs/architecture/QUALITY_GATES.md b/docs/i18n/kn/docs/architecture/QUALITY_GATES.md index 969b176cb064..04c3cbf5c3ec 100644 --- a/docs/i18n/kn/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/kn/docs/architecture/QUALITY_GATES.md @@ -19,259 +19,259 @@ ### ಅಭ್ಯರ್ಥಿ ಪ್ರವೇಶ -CI ಮತ್ತು Quality Gates ವರ್ಕ್ಫ್ಲೋಗಳು ಪ್ರತಿಯೊಂದೂ ಸ್ಥಿರ ತೀರ್ಪನ್ನು ಹೊರಸೂಸುತ್ತವೆ: `Gate / CI` ಮತ್ತು -`Gate / Quality`. ಅವುಗಳ ಆವೃತ್ತಿಗೊಳಿಸಿದ ಪ್ರವೇಶ ನೀತಿಯು ಪ್ರತಿಯೊಂದು ಅಪ್ಸ್ಟ್ರೀಮ್ ಜಾಬ್ ಅನ್ನು -ಕಡ್ಡಾಯ ಅಥವಾ ಸಲಹಾತ್ಮಕ ಎಂದು ಪಟ್ಟಿಮಾಡುತ್ತದೆ. ಅನ್ವಯವಾಗುವ ಕಡ್ಡಾಯ ಜಾಬ್ ಯಶಸ್ವಿಯಾಗಬೇಕು: ಕಾಣೆಯಾದ, -ರದ್ದಾದ, ಬಿಟ್ಟುಬಿಡಲಾದ, ಬಾಕಿಯಿರುವ ಮತ್ತು ಅಜ್ಞಾತ ಫಲಿತಾಂಶಗಳು PASS ಅನ್ನು ಸ್ಥಾಪಿಸಲಾರವು. ಮಾನ್ಯವಾದ +CI ಮತ್ತು Quality Gates ವರ್ಕ್ಫ್ಲೋಗಳು ಪ್ರತಿಯೊಂದೂ ಸ್ಥಿರ ತೀರ್ಪನ್ನು ನೀಡುತ್ತವೆ: `Gate / CI` ಮತ್ತು +`Gate / Quality`. ಅವುಗಳ ಆವೃತ್ತಿಗೊಳಿಸಲಾದ ಪ್ರವೇಶ ನೀತಿಯು ಪ್ರತಿ ಅಪ್ಸ್ಟ್ರೀಮ್ ಜಾಬ್ ಅನ್ನು +ಕಡ್ಡಾಯ ಅಥವಾ ಸಲಹಾತ್ಮಕ ಎಂದು ಪಟ್ಟಿ ಮಾಡುತ್ತದೆ. ಅನ್ವಯವಾಗುವ ಕಡ್ಡಾಯ ಜಾಬ್ ಯಶಸ್ವಿಯಾಗಬೇಕು: ಕಾಣೆಯಾದ, +ರದ್ದಾದ, ಸ್ಕಿಪ್ ಆದ, ಬಾಕಿ ಇರುವ ಮತ್ತು ಅಜ್ಞಾತ ಫಲಿತಾಂಶಗಳು PASS ಅನ್ನು ಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಮಾನ್ಯವಾದ docs-only ಅಥವಾ catalog-only ವರ್ಗೀಕರಣವು ಕೋಡ್ ಲೇನ್ ಅನ್ನು ಅನ್ವಯವಾಗದಂತೆ ಮಾಡಬಹುದು; -ಕರಡು PR ಅಂಗೀಕೃತ ಅಭ್ಯರ್ಥಿಯಲ್ಲ. `hotfix` ಲೇಬಲ್ ಸಾಕ್ಷ್ಯದ ಅಗತ್ಯವನ್ನು ಮನ್ನಾ ಮಾಡುವುದಿಲ್ಲ. +ಡ್ರಾಫ್ಟ್ PR ಅನ್ನು ಅಂಗೀಕೃತ ಅಭ್ಯರ್ಥಿಯಾಗಿ ಪರಿಗಣಿಸಲಾಗುವುದಿಲ್ಲ. `hotfix` ಲೇಬಲ್ ಸಾಕ್ಷ್ಯದ ಅಗತ್ಯವನ್ನು ಮನ್ನಾ ಮಾಡುವುದಿಲ್ಲ. -ಎರಡೂ ವರ್ಕ್ಫ್ಲೋಗಳು PR-ಗಳು ಮತ್ತು main/release ಬ್ರಾಂಚ್ಗಳಿಗೆ ಪುಶ್ಗಳು, ಹಸ್ತಚಾಲಿತ ಡಿಸ್ಪ್ಯಾಚ್ ಹಾಗೂ -ಮರ್ಜ್-ಗ್ರೂಪ್ ಈವೆಂಟ್ಗಳನ್ನು ಒಳಗೊಳ್ಳುತ್ತವೆ. ಪುಶ್, ಡಿಸ್ಪ್ಯಾಚ್ ಮತ್ತು ಮರ್ಜ್-ಗ್ರೂಪ್ ಪೂರ್ಣ ಆಯ್ಕೆಯನ್ನು ಚಲಾಯಿಸುತ್ತವೆ. ಫೋರ್ಕ್ಗಳು -ಮತ್ತು ಮರ್ಜ್ ಗ್ರೂಪ್ಗಳು, ಸಾಮಾನ್ಯವಾಗಿ ಸ್ವಯಂ-ಹೋಸ್ಟ್ ಮಾಡಿದ ರನ್ನರ್ಗಳನ್ನು ಆಯ್ಕೆಮಾಡುವ ಜಾಬ್ಗಳಿಗೆ ಹೋಸ್ಟ್ ಮಾಡಿದ ರನ್ನರ್ಗಳನ್ನು -ಬಳಸುತ್ತವೆ; ರೋಲ್ಔಟ್ಗೆ ಮೊದಲು ಸಾಕಷ್ಟು ಹೋಸ್ಟ್ ಮಾಡಿದ ಸಾಮರ್ಥ್ಯವನ್ನು ಪರಿಶೀಲಿಸಬೇಕು. +ಎರಡೂ ವರ್ಕ್ಫ್ಲೋಗಳು PRಗಳು ಮತ್ತು main/release ಬ್ರಾಂಚ್ಗಳಿಗೆ ಪುಶ್ಗಳು, ಹಸ್ತಚಾಲಿತ dispatch ಹಾಗೂ +merge-group ಈವೆಂಟ್ಗಳನ್ನು ಒಳಗೊಳ್ಳುತ್ತವೆ. Push, dispatch ಮತ್ತು merge-group ಸಂಪೂರ್ಣ ಆಯ್ಕೆಯನ್ನು ಚಲಾಯಿಸುತ್ತವೆ. Forkಗಳು +ಮತ್ತು merge groupಗಳು, ಇಲ್ಲದಿದ್ದರೆ self-hosted ರನ್ನರ್ಗಳನ್ನು ಆಯ್ಕೆಮಾಡುವ ಜಾಬ್ಗಳಿಗೆ hosted ರನ್ನರ್ಗಳನ್ನು ಬಳಸುತ್ತವೆ; +ರೋಲ್ಔಟ್ಗೆ ಮೊದಲು ಸಾಕಷ್ಟು hosted ಸಾಮರ್ಥ್ಯವನ್ನು ಪರಿಶೀಲಿಸಬೇಕು. -ಪ್ರತಿ JSON ರಸೀದಿಯು ಚೆಕ್ಔಟ್ ಮಾಡಿದ SHA, ವರ್ಕ್ಫ್ಲೋ ರನ್ ಮತ್ತು ಪ್ರಯತ್ನವನ್ನು ಗುರುತಿಸುತ್ತದೆ. -ಚೆಕ್ಔಟ್/ಈವೆಂಟ್ SHA ಅಸಾಮ್ಯವನ್ನು CLI ತಿರಸ್ಕರಿಸುತ್ತದೆ. ವರ್ಕ್ಫ್ಲೋ ಪರೀಕ್ಷೆಗಳು ನೀತಿ ಸದಸ್ಯತ್ವವನ್ನು -ತೀರ್ಪು ಜಾಬ್ನ `needs` ಪಟ್ಟಿಗೆ ಬದ್ಧಗೊಳಿಸುತ್ತವೆ, ಆದ್ದರಿಂದ ಹೊಸ ಅಥವಾ ತೆಗೆದುಹಾಕಿದ ಲೇನ್ ಮೌನವಾಗಿ ಕಣ್ಮರೆಯಾಗಲು ಸಾಧ್ಯವಿಲ್ಲ. -ರಸೀದಿಗಳು ತಮ್ಮದೇ ವರ್ಕ್ಫ್ಲೋವನ್ನು ಒಳಗೊಳ್ಳುತ್ತವೆ, ಪ್ರಕಟಣೆ, ಡಿಪ್ಲಾಯ್ಮೆಂಟ್ ಅಥವಾ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ -ಸಲಹಾತ್ಮಕ ಸ್ಕ್ಯಾನರ್ನ ಆಂತರಿಕ ಅಂಶಗಳನ್ನಲ್ಲ. ಬ್ರಾಂಚ್ ನಿಯಮಗಳಲ್ಲಿ ಎರಡೂ ಚೆಕ್ ಹೆಸರುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದು -ಪ್ರತ್ಯೇಕ ಆಡಳಿತಾತ್ಮಕ ಬದಲಾವಣೆಯಾಗಿದೆ; ಈ ಜಾಬ್ಗಳನ್ನು ಸೇರಿಸುವುದರಿಂದ ಮಾತ್ರ ಬ್ರಾಂಚ್ ರಕ್ಷಿತವಾಗುವುದಿಲ್ಲ. +ಪ್ರತಿ JSON ರಸೀದಿಯು ಚೆಕ್ಔಟ್ ಮಾಡಲಾದ SHA, ವರ್ಕ್ಫ್ಲೋ ರನ್ ಮತ್ತು ಪ್ರಯತ್ನವನ್ನು ಗುರುತಿಸುತ್ತದೆ. +ಚೆಕ್ಔಟ್/event SHA ಅಸಂಗತತೆಯನ್ನು CLI ತಿರಸ್ಕರಿಸುತ್ತದೆ. ವರ್ಕ್ಫ್ಲೋ ಪರೀಕ್ಷೆಗಳು ನೀತಿ ಸದಸ್ಯತ್ವವನ್ನು +ತೀರ್ಪು ಜಾಬ್ನ `needs` ಪಟ್ಟಿಗೆ ಬಿಗಿದಿಡುತ್ತವೆ, ಹೀಗಾಗಿ ಹೊಸದಾಗಿ ಸೇರಿಸಲಾದ ಅಥವಾ ತೆಗೆದುಹಾಕಲಾದ ಲೇನ್ ಮೌನವಾಗಿ ಕಣ್ಮರೆಯಾಗಲು ಸಾಧ್ಯವಿಲ್ಲ. +ರಸೀದಿಗಳು ತಮ್ಮದೇ ವರ್ಕ್ಫ್ಲೋವನ್ನು ಒಳಗೊಳ್ಳುತ್ತವೆ; ಪ್ರಕಟಣೆ, ನಿಯೋಜನೆ ಅಥವಾ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ +ಸಲಹಾತ್ಮಕ ಸ್ಕ್ಯಾನರ್ನ ಆಂತರಿಕ ಪ್ರಕ್ರಿಯೆಗಳನ್ನು ಅಲ್ಲ. ಬ್ರಾಂಚ್ ನಿಯಮಗಳಲ್ಲಿ ಎರಡೂ ಚೆಕ್ ಹೆಸರುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದು +ಪ್ರತ್ಯೇಕ ಆಡಳಿತಾತ್ಮಕ ಬದಲಾವಣೆಯಾಗಿದೆ; ಈ ಜಾಬ್ಗಳನ್ನು ಸೇರಿಸುವುದರಿಂದ ಮಾತ್ರ ಬ್ರಾಂಚ್ ಸಂರಕ್ಷಿತವಾಗುವುದಿಲ್ಲ. ### ಸ್ಥಿರ ಸ್ಕ್ಯಾನ್ ಇನ್ವೆಂಟರಿ -ಆವೃತ್ತಿಗೊಳಿಸಿದ npm-ಅಲಿಯಾಸ್ ಇನ್ವೆಂಟರಿ ಮತ್ತು ಸ್ಥಿರ-ಸ್ಕ್ಯಾನ್ ಸದಸ್ಯತ್ವವು -`config/quality/gate-manifest.json` ನಲ್ಲಿ ಇವೆ. ಸ್ಕ್ರಿಪ್ಟ್ ಹೆಸರುಗಳು ಮತ್ತು ನಿಖರ ಕಮಾಂಡ್ಗಳನ್ನು -`package.json` ವಿರುದ್ಧ ಮೌಲ್ಯೀಕರಿಸಲು `npm run check:gate-manifest` ಅನ್ನು ಚಲಾಯಿಸಿ; ಸೇರ್ಪಡೆಗಳು, ತೆಗೆದುಹಾಕುವಿಕೆಗಳು ಮತ್ತು -ಕಮಾಂಡ್ ವ್ಯತ್ಯಾಸಗಳು ಸ್ಥಳೀಯ ಹುಕ್ ಹಾಗೂ CI-ಯಲ್ಲಿನ ಬದಲಾವಣೆ-ವರ್ಗೀಕರಣ ಜಾಬ್ಗಳೆರಡನ್ನೂ ವಿಫಲಗೊಳಿಸುತ್ತವೆ. -ಅಲಿಯಾಸ್ ಎಂಬುದು ವರ್ಕ್ಫ್ಲೋ ಜಾಬ್, ಮ್ಯಾಟ್ರಿಕ್ಸ್ ಇನ್ಸ್ಟಾನ್ಸ್ ಅಥವಾ ಟೆಸ್ಟ್ ಕೇಸ್ ಅಲ್ಲ: ಈ ಎಣಿಕೆಗಳನ್ನು -ಪರಸ್ಪರ ವಿನಿಮಯಸಾಧ್ಯವೆಂದು ಪ್ರಸ್ತುತಪಡಿಸಬಾರದು. +ಆವೃತ್ತಿಗೊಳಿಸಲಾದ npm-alias ಇನ್ವೆಂಟರಿ ಮತ್ತು ಸ್ಥಿರ-ಸ್ಕ್ಯಾನ್ ಸದಸ್ಯತ್ವವು +`config/quality/gate-manifest.json` ನಲ್ಲಿವೆ. ಸ್ಕ್ರಿಪ್ಟ್ ಹೆಸರುಗಳು ಮತ್ತು ನಿಖರ ಕಮಾಂಡ್ಗಳನ್ನು +`package.json` ಗೆ ಹೋಲಿಸಿ ಮೌಲ್ಯೀಕರಿಸಲು `npm run check:gate-manifest` ಅನ್ನು ಚಲಾಯಿಸಿ; ಸೇರ್ಪಡೆಗಳು, ತೆಗೆದುಹಾಕುವಿಕೆಗಳು ಮತ್ತು +ಕಮಾಂಡ್ ವ್ಯತ್ಯಾಸಗಳು ಸ್ಥಳೀಯ hook ಮತ್ತು CIಯಲ್ಲಿನ ಬದಲಾವಣೆ-ವರ್ಗೀಕರಣ ಜಾಬ್ಗಳೆರಡನ್ನೂ ವಿಫಲಗೊಳಿಸುತ್ತವೆ. +alias ಎಂಬುದು ವರ್ಕ್ಫ್ಲೋ ಜಾಬ್, matrix instance ಅಥವಾ test case ಅಲ್ಲ: ಈ ಎಣಿಕೆಗಳನ್ನು +ಪರಸ್ಪರ ಬದಲಾಯಿಸಬಹುದಾದವುಗಳಂತೆ ಪ್ರಸ್ತುತಪಡಿಸಬಾರದು. -ಆಯ್ಕೆಮಾಡಿದ ಅಲಿಯಾಸ್ಗಳನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸದೆ ಪರಿಶೀಲಿಸಲು `npm run quality:scan -- --list` ಅಥವಾ `npm run quality:scan:fast -- --list` -ಬಳಸಿ. ರನ್ನರ್ npm ಎಂಟ್ರಿಪಾಯಿಂಟ್ ಅನ್ನು ಆಹ್ವಾನಿಸುತ್ತದೆ, ಆದ್ದರಿಂದ ಅದರ ರನ್ಟೈಮ್ (ಕಾನ್ಫಿಗರ್ ಮಾಡಿರುವಲ್ಲಿ Bun ಸೇರಿದಂತೆ) ಉಳಿಯುತ್ತದೆ. -ಮ್ಯಾನಿಫೆಸ್ಟ್ ಆ ಪ್ರೊಫೈಲ್ಗಳ ಹೊರಗಿನ ಅಲಿಯಾಸ್ಗಳನ್ನು ಪ್ರತ್ಯೇಕವಾಗಿ ಆಹ್ವಾನಿಸಲಾದವುಗಳೆಂದು ದಾಖಲಿಸುತ್ತದೆ, ಮತ್ತು +ಆಯ್ಕೆಮಾಡಲಾದ aliasಗಳನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸದೆ ಪರಿಶೀಲಿಸಲು `npm run quality:scan -- --list` ಅಥವಾ `npm run quality:scan:fast -- --list` +ಬಳಸಿ. ರನ್ನರ್ npm entrypoint ಅನ್ನು ಆವಾಹಿಸುತ್ತದೆ, ಆದ್ದರಿಂದ ಅದರ runtime (ಕಾನ್ಫಿಗರ್ ಮಾಡಿರುವಲ್ಲಿ Bun ಸೇರಿದಂತೆ) ಉಳಿಯುತ್ತದೆ. +manifest ಆ ಪ್ರೊಫೈಲ್ಗಳ ಹೊರಗಿನ aliasಗಳನ್ನು ಪ್ರತ್ಯೇಕವಾಗಿ ಆವಾಹಿಸಲಾದವುಗಳೆಂದು ದಾಖಲಿಸುತ್ತದೆ ಮತ್ತು ಓದಲು-ಮಾತ್ರವಾದ ಸ್ಕ್ಯಾನ್ ಪ್ರೊಫೈಲ್ಗಳಲ್ಲಿ ನಿರ್ವಹಣಾ ಕಮಾಂಡ್ಗಳನ್ನು ನಿಷೇಧಿಸಲಾಗಿದೆ. -ಈ ಪ್ರೊಫೈಲ್ಗಳು ಸ್ಥಿರ ಸ್ಕ್ಯಾನ್ ಅನ್ನು ಮಾತ್ರ ಒಳಗೊಳ್ಳುತ್ತವೆ. ಅವು ಉತ್ಪನ್ನ ಪರೀಕ್ಷೆಗಳು, -ಕವರೇಜ್, ಪ್ಯಾಕೇಜಿಂಗ್, ಬಾಹ್ಯ ಚೆಕ್ಗಳು ಅಥವಾ ಅಭ್ಯರ್ಥಿಯ ಸಂಪೂರ್ಣ ಬಿಡುಗಡೆ ಅಂಗೀಕಾರವನ್ನು ಪ್ರಮಾಣೀಕರಿಸುವುದಿಲ್ಲ. +ಈ ಪ್ರೊಫೈಲ್ಗಳು ಸ್ಥಿರ ಸ್ಕ್ಯಾನ್ ಅನ್ನು ಮಾತ್ರ ಒಳಗೊಳ್ಳುತ್ತವೆ. ಇವು ಉತ್ಪನ್ನ ಪರೀಕ್ಷೆಗಳು, +ವ್ಯಾಪ್ತಿ, ಪ್ಯಾಕೇಜಿಂಗ್, ಬಾಹ್ಯ ಚೆಕ್ಗಳು ಅಥವಾ ಅಭ್ಯರ್ಥಿಯ ಸಂಪೂರ್ಣ ಬಿಡುಗಡೆ ಅಂಗೀಕಾರವನ್ನು ಪ್ರಮಾಣೀಕರಿಸುವುದಿಲ್ಲ. ವರ್ಕ್ಫ್ಲೋ ಪ್ರವೇಶವು ಲಿಂಕ್ ಮಾಡಲಾದ `config/quality/admission-policy.json` ಮತ್ತು -`scripts/quality/admission-verdict.mjs` ಅನ್ನು ಬಳಸುತ್ತದೆ. ಬಿಡುಗಡೆ-ವೀಕ್ಷಕ ಪ್ರೊಫೈಲ್ಗಳು ಪ್ರತ್ಯೇಕವಾಗಿಯೇ ಉಳಿಯುತ್ತವೆ; -ಅವುಗಳಿಗೆ ಅನ್ವಯವಾಗುವ ಚೆಕ್ಗಳು ಮತ್ತು ರಸೀದಿಗಳನ್ನು ಸ್ವತಂತ್ರವಾಗಿ ಪರಿಶೀಲಿಸಿ. ಕೆಳಗಿನ ಗದ್ಯರೂಪದ -ಇನ್ವೆಂಟರಿ ಒಂದು ಉಲ್ಲೇಖವೇ ಹೊರತು, ಗೇಟ್ ನಿಜವಾಗಿಯೂ ಚಲಾಯಿತೆಂಬುದಕ್ಕೆ ಪುರಾವೆಯಲ್ಲ. +`scripts/quality/admission-verdict.mjs` ಅನ್ನು ಬಳಸುತ್ತದೆ. Release-observer ಪ್ರೊಫೈಲ್ಗಳು ಪ್ರತ್ಯೇಕವಾಗಿಯೇ ಉಳಿಯುತ್ತವೆ; +ಅವುಗಳ ಅನ್ವಯವಾಗುವ ಚೆಕ್ಗಳು ಮತ್ತು ರಸೀದಿಗಳನ್ನು ಸ್ವತಂತ್ರವಾಗಿ ಪರಿಶೀಲಿಸಿ. ಕೆಳಗಿನ ಗದ್ಯರೂಪದ +ಇನ್ವೆಂಟರಿ ಒಂದು ಉಲ್ಲೇಖವಾಗಿದೆ; ಗೇಟ್ ನಿಜವಾಗಿಯೂ ಚಲಾಯಿಸಲ್ಪಟ್ಟಿದೆ ಎಂಬುದಕ್ಕೆ ಪುರಾವೆಯಲ್ಲ. -ಸ್ಕ್ರಿಪ್ಟ್ಗಳು `scripts/check/` (ನೀತಿ ಗೇಟ್ಗಳು) ಮತ್ತು `scripts/quality/` (ರ್ಯಾಚೆಟ್ ಎಂಜಿನ್) ಅಡಿಯಲ್ಲಿ ಇವೆ. -CI-ಯ ಸತ್ಯದ ಮೂಲವು `.github/workflows/ci.yml` ಆಗಿದೆ. +ಸ್ಕ್ರಿಪ್ಟ್ಗಳು `scripts/check/` (ನೀತಿ ಗೇಟ್ಗಳು) ಮತ್ತು `scripts/quality/` (ratchet ಎಂಜಿನ್) ಅಡಿಯಲ್ಲಿ ಇವೆ. +CIಗೆ ಮೂಲ ಸತ್ಯವು `.github/workflows/ci.yml` ಆಗಿದೆ. ### ಬಿಡುಗಡೆ PR ವೇಗದ-ಮಾರ್ಗ (`quality.yml`) -`.github/workflows/quality.yml` main/release PR-ಗಳು, ರಕ್ಷಿತ-ಬ್ರಾಂಚ್ -ಪುಶ್ಗಳು, ಡಿಸ್ಪ್ಯಾಚ್ ಮತ್ತು ಮರ್ಜ್ ಗ್ರೂಪ್ಗಳಲ್ಲಿ CI-ಗೆ ಪೂರಕವಾಗಿರುತ್ತದೆ. PR-ಗಳು ಪಥ-ಫಿಲ್ಟರ್ ಮಾಡಿದ ವೇಗದ ಚೆಕ್ಗಳನ್ನು ಬಳಸುತ್ತವೆ. ಶಾಶ್ವತವಾಗಿ -ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದ್ದ ನಕಲಿ ಬಿಲ್ಡ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ; ನೈಜ ಬಿಲ್ಡ್/ಪ್ಯಾಕೇಜ್/ಬೂಟ್ ಚೆಕ್ಗಳು CI-ಯಲ್ಲಿಯೇ ಉಳಿದಿವೆ. - -| ಜಾಬ್ | ವ್ಯಾಪ್ತಿ | ತಡೆಯುವಿಕೆ | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `Docs Gates (fast-path)` | ಡಾಕ್ಸ್/ಕೋಡ್ PR-ಗಳು; API ಡಾಕ್ಸ್ ಉಲ್ಲೇಖಗಳು ಮತ್ತು docs-all | ಹೌದು | -| `Fast Quality Gates` | ಕೋಡ್ PR-ಗಳು; ಸ್ಥಿರ ಚೆಕ್ಗಳು, ಟೈಪ್ಚೆಕ್, ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಟೈಪ್ಚೆಕ್, ಪರಿಣಾಮಕ್ಕೊಳಗಾದ ಯೂನಿಟ್ ಪರೀಕ್ಷೆಗಳು | ಹೌದು | -| `Forgotten sibling tests` | ಕೋಡ್ PR-ಗಳು; ಬದಲಾದ ಮಾಡ್ಯೂಲ್ಗಳಿಂದ ಸ್ಥಿರ ಬಳಕೆದಾರರು ಮತ್ತು ಸಂಭಾವ್ಯ ಸಹೋದರ ಪರೀಕ್ಷೆಗಳವರೆಗೆ ಪತ್ತೆಹಚ್ಚಲಾಗುತ್ತದೆ; ಬ್ಯಾರೆಲ್ ಮತ್ತು ಡೈನಾಮಿಕ್-ಇಂಪೋರ್ಟ್ ಪಥಗಳನ್ನು ಉಲ್ಲೇಖಿತ ಅಲೌಲೀಸ್ಟ್ ವಿನಾಯಿತಿಗಳೊಂದಿಗೆ ಸಲಹಾತ್ಮಕ ಡಯಾಗ್ನೋಸ್ಟಿಕ್ಗಳಾಗಿ ವರದಿ ಮಾಡಲಾಗುತ್ತದೆ | **ಸಲಹಾತ್ಮಕ** | -| `Vitest (fast-path)` | ಕೋಡ್ PR-ಗಳು; ವೇಗದ vitest ಸೂಟ್ | ಹೌದು | -| `Unit Tests fast-path` | ಕೋಡ್ PR-ಗಳು; 4-ಶಾರ್ಡ್ ಯೂನಿಟ್ ಸೂಟ್ | ಹೌದು | -| `No new ESLint warnings` | ಕೋಡ್ PR-ಗಳು; ಸಪ್ರೆಷನ್ಗಳನ್ನು ಪರಿಗಣಿಸುವ ಲಿಂಟ್ ಗಾರ್ಡ್ | ಹೌದು, ಫೋರ್ಕ್ಗಳು ಸೇರಿದಂತೆ | -| `Merge integrity (changelog + generated skills)` | ಕರಡು ಅಲ್ಲದ PR-ಗಳು; ಚೇಂಜ್ಲಾಗ್ ಮತ್ತು ರಚಿಸಲಾದ ಸ್ಕಿಲ್ ಸಿಂಕ್ | ಹೌದು, ಫೋರ್ಕ್ಗಳು ಸೇರಿದಂತೆ | - -#### ಮರೆತುಹೋದ ಸಹೋದರ ಪರೀಕ್ಷೆಗಳ ವರದಿ - -`npm run check:forgotten-sibling-tests` ಪರೀಕ್ಷಾ-ಪರಿಣಾಮ ನಕ್ಷೆಯ ಹಿಂದಿರುವ ಇಂಪೋರ್ಟ್ ರಿಸಾಲ್ವರ್ ಅನ್ನು ಮರುಬಳಕೆ ಮಾಡುತ್ತದೆ. -ಬದಲಾದ ಪ್ರತಿಯೊಂದು ಪ್ರೊಡಕ್ಷನ್ ಮಾಡ್ಯೂಲ್ಗಾಗಿ, ಸಂಭಾವ್ಯ -ಪರೀಕ್ಷೆಯು ಪುಲ್-ರಿಕ್ವೆಸ್ಟ್ ಡಿಫ್ನಲ್ಲಿ ಇಲ್ಲದಿದ್ದಾಗ ಇದು ನಿರ್ಣಾಯಕ `changed module/symbol -> static consumer -> candidate sibling test` ಸರಪಳಿಗಳನ್ನು ವರದಿ ಮಾಡುತ್ತದೆ. -ಯಾವುದೇ ತಡೆಯುವ ರೋಲ್ಔಟ್ಗೆ ಮುನ್ನ ಕ್ಯಾಲಿಬ್ರೇಶನ್ಗಾಗಿ Markdown ಸಾರಾಂಶ ಮತ್ತು JSON ಫಲಿತಾಂಶವನ್ನು -`forgotten-sibling-tests` ವರ್ಕ್ಫ್ಲೋ ಆರ್ಟಿಫ್ಯಾಕ್ಟ್ ಆಗಿ ಉಳಿಸಿಕೊಳ್ಳಲಾಗುತ್ತದೆ. - -ಬ್ಯಾರೆಲ್ ಮರು-ರಫ್ತುಗಳು ಮತ್ತು ಡೈನಾಮಿಕ್ ಆಮದುಗಳು ರೆಸಲ್ಯೂಷನ್ ಡಯಾಗ್ನೋಸ್ಟಿಕ್ಸ್ಗೆ ಮಾತ್ರ ಸೀಮಿತವಾಗಿವೆ; ಅವು ಎಂದಿಗೂ -ತಡೆಯುವ ಫಲಿತಾಂಶವನ್ನು ಸೃಷ್ಟಿಸುವುದಿಲ್ಲ. ಪರಿಶೀಲಿಸಲಾದ ವಿನಾಯಿತಿಗಳು -`config/quality/forgotten-sibling-allowlist.json` ನಲ್ಲಿ ಇರುತ್ತವೆ. ಪ್ರತಿ ನಮೂದಿನಲ್ಲಿ ಗ್ರಾಹಕ ಮತ್ತು ಅಭ್ಯರ್ಥಿ -ಪರೀಕ್ಷೆಯ ಹೆಸರನ್ನು ನಮೂದಿಸಬೇಕು, ನಿರ್ದಿಷ್ಟ ತಾರ್ಕಿಕ ಕಾರಣವನ್ನು ನೀಡಬೇಕು ಮತ್ತು GitHub issue ಅಥವಾ pull requestಗೆ ಲಿಂಕ್ ಮಾಡಬೇಕು. ದೋಷಪೂರಿತ ನಮೂದುಗಳು -ಮುಚ್ಚಿದ ಸ್ಥಿತಿಯಲ್ಲಿ ವಿಫಲಗೊಳ್ಳುತ್ತವೆ. ಅಳಿಸಲಾದ ಅಭ್ಯರ್ಥಿ ಪರೀಕ್ಷೆ ಅಥವಾ `.skip`/`.todo` ಸೇರಿಸುವ diff ಅನ್ನು ವಿನಾಯಿತಿಗಳು ನಿರ್ಬಂಧಿಸಲಾರವು; -assertion ದುರ್ಬಲಗೊಳಿಸುವಿಕೆ ಮತ್ತು ಇತರ ಮರೆಮಾಚುವಿಕೆಗಳು ಸ್ವತಂತ್ರವಾಗಿ ತಡೆಯುವ -`check:test-masking` ಗೇಟ್ನ ನಿಯಂತ್ರಣದಲ್ಲಿಯೇ ಇರುತ್ತವೆ. - -### ಜಾಬ್: `lint` - -`main` ಗೆ ಸಲ್ಲಿಸುವ ಪ್ರತಿಯೊಂದು PR ನಲ್ಲಿಯೂ ರನ್ ಆಗುತ್ತದೆ. ವಿಫಲವಾದರೆ ವಿಲೀನವನ್ನು ತಡೆಯುತ್ತದೆ. - -| ಸ್ಕ್ರಿಪ್ಟ್ (`npm run ...`) | ಪರಿಶೀಲಿಸುವುದು | ತಡೆಯುತ್ತದೆಯೇ | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| `check:node-runtime` | Node.js ಆವೃತ್ತಿಯು ಬೆಂಬಲಿತ ವ್ಯಾಪ್ತಿಯಲ್ಲಿದೆ | ಹೌದು | -| `check:cycles` | ವೃತ್ತಾಕಾರದ ಆಮದುಗಳು — ಎಲ್ಲಾ `src/` + `open-sse/` ಮಾಡ್ಯೂಲ್ಗಳು | ಹೌದು | -| `check:route-validation:t06` | ಎಲ್ಲಾ ರೂಟ್ಗಳಲ್ಲಿ Zod ಸ್ಕೀಮಾಗಳು ಇವೆ (Tier 6 ನೀತಿ) | ಹೌದು | -| `check:any-budget:t11` | `@ts-expect-error // any` ಎಣಿಕೆಯು ಬಜೆಟ್ ಅನ್ನು ಮೀರುವುದಿಲ್ಲ (Tier 11 catraca) | ಹೌದು | -| `check:provider-consistency` | `providers.ts`ನಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು providerಗೆ `providerRegistry.ts`ನಲ್ಲಿ ಹೊಂದಾಣಿಕೆಯ entry ಇದೆ (ಮತ್ತು allowlist ವ್ಯಾಪ್ತಿಯಲ್ಲಿ ಇದರ ವಿರುದ್ಧವೂ ಅನ್ವಯಿಸುತ್ತದೆ) | ಹೌದು | -| `check:model-lifecycle` | ಕೈಯಿಂದ ನಿರ್ವಹಿಸಲಾಗುವ ಮೂರು routing tableಗಳು checked-in lifecycle snapshot (#11503) ಜೊತೆಗೆ ಸ್ಥಿರವಾಗಿರುತ್ತವೆ: `FITNESS_TABLE` (`taskFitness.ts`) ನಲ್ಲಿ `REGISTRY` route ಮಾಡಬಹುದಾದ ಯಾವುದೇ retired idಗೆ score ನೀಡಲಾಗುವುದಿಲ್ಲ; ಪ್ರತಿಯೊಂದು `BUILT_IN_ALIASES` target `REGISTRY`ಯಲ್ಲಿ ಇರುತ್ತದೆ ಮತ್ತು retired-id snapshotನಲ್ಲಿ ಇರುವುದಿಲ್ಲ; `REGISTRY`ಯಲ್ಲಿ ಇನ್ನೂ ಇರುವ ಪ್ರತಿಯೊಂದು retired id ಅನ್ನು forward ಮಾಡಲಾಗುತ್ತದೆ ಅಥವಾ `allowedRetiredInCatalog`ನಲ್ಲಿ ಪಟ್ಟಿ ಮಾಡಲಾಗುತ್ತದೆ; ಮತ್ತು ಯಾವುದೇ `DEFAULT_DEGRADATION_MAP` source ಅಥವಾ target ಆ snapshotನಲ್ಲಿ retired ಆಗಿ ಕಾಣಿಸುವುದಿಲ್ಲ. ಪ್ರಸ್ತುತ live upstream ಮೂಲಕ model ಅನ್ನು ಒದಗಿಸಲಾಗುತ್ತಿದೆ ಎಂಬುದನ್ನು ಇದು ಸಾಬೀತುಪಡಿಸುವುದಿಲ್ಲ. Offline — `config/quality/model-lifecycle.json` ಜೊತೆ ಹೋಲಿಸುತ್ತದೆ; ಇದನ್ನು `npm run quality:refresh-model-lifecycle` ಬಳಸಿ ಕೈಯಿಂದ refresh ಮಾಡಲಾಗುತ್ತದೆ (network; CIಗೆ ಜೋಡಿಸಲಾಗಿಲ್ಲ). `allowedRetiredInCatalog` ಒಂದು burn-down ratchet ಆಗಿದೆ: tracking issue ಇದ್ದಾಗ ಮಾತ್ರ entry ಸೇರಿಸಿ. | ಹೌದು | -| `check:fetch-targets` | client-side `src/`ನಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು `fetch("/api/...")` ನೈಜ `route.ts`ಗೆ resolve ಆಗುತ್ತದೆ | ಹೌದು | -| `check:deps` | repoದಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು `package.json`ನಾದ್ಯಂತ ಇರುವ ಎಲ್ಲಾ `npm install` ಮಾಡಬಹುದಾದ depsಗಳು `dependency-allowlist.json`ನಲ್ಲಿ ಇರುತ್ತವೆ; ಹೊಸ unpinned ಅಥವಾ slopsquatted packageಗಳನ್ನು flag ಮಾಡಲಾಗುತ್ತದೆ | ಹೌದು | -| `audit:deps` | `npm audit` (root + electron) — ಯಾವುದೇ high/critical advisoryಗಳಿಲ್ಲ (osv `check:vuln-ratchet` ಜೊತೆ overlap ಆಗುತ್ತದೆ; Rationalization Backlog ನೋಡಿ) | ಹೌದು | -| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashಗಳು, host overrideಗಳಿಲ್ಲ | ಹೌದು | -| `check:licenses` | ಉತ್ಪಾದನಾ ಅವಲಂಬನೆಗಳಿಗಾಗಿ SPDX ಪರವಾನಗಿ ಅನುಮತಿಪಟ್ಟಿ | ಹೌದು | -| `check:tracked-artifacts` | ಯಾವುದೇ ಬಿಲ್ಡ್ ಆರ್ಟಿಫ್ಯಾಕ್ಟ್ಗಳು / ಕಮಿಟ್ ಮಾಡಲಾದ `node_modules` ಸಿಂಲಿಂಕ್ಗಳು ಇರಬಾರದು (husky pre-commitನಲ್ಲಿಯೂ ಚಲಿಸುತ್ತದೆ; pre-push ಅನ್ನು ಉದ್ದೇಶಪೂರ್ವಕವಾಗಿ ಹಗುರವಾಗಿಡಲಾಗಿದೆ — #6716) | ಹೌದು | -| `check:ai-attribution` | PR ಕಮಿಟ್ಗಳು, ಶೀರ್ಷಿಕೆ ಅಥವಾ ಪಠ್ಯದಲ್ಲಿ ಯಾವುದೇ AI/bot `Co-Authored-By` ಟ್ರೇಲರ್ ಅಥವಾ AI-ರಚನೆಯ ಫುಟರ್ ಇರಬಾರದು — ಕಠಿಣ ನಿಯಮ #16 (`quality.yml` ನಲ್ಲಿನ PR→`release/**` ಗಾಗಿನ fast-gates ಲೂಪ್ನಲ್ಲಿ — ಈವೆಂಟ್ ಪೇಲೋಡ್ ಅನ್ನು ಓದುತ್ತದೆ, PR ಅಲ್ಲದಿದ್ದಾಗ ಯಾವುದೇ ಕಾರ್ಯ ಮಾಡುವುದಿಲ್ಲ — ಮತ್ತು PR→`main` ಗಾಗಿ `ci.yml` lintನಲ್ಲಿನ PR-ಮಾತ್ರದ ಹಂತ; ಹಾಗೆಯೇ husky `commit-msg` ಹುಕ್; ಮಾನವ ಸಹ-ಲೇಖಕರಿಗೆ ಅನುಮತಿಯಿದೆ; #14436) | -| `check:vitest-exclusions` | ಪ್ರತಿಯೊಂದು Vitest ಹೊರತುಪಡಿಸುವಿಕೆಯೂ ಟ್ರ್ಯಾಕಿಂಗ್ ಸಮಸ್ಯೆಯನ್ನು ಹೆಸರಿಸಬೇಕು ಮತ್ತು `config/quality/vitest-exclusions.json` ನಲ್ಲಿ ಕಾಣಿಸಿಕೊಳ್ಳಬೇಕು (#13204) | ಹೌದು | -| `check:file-size` | ಯಾವುದೇ ಮೂಲ ಫೈಲ್ ಪ್ರತಿ-ವಿಸ್ತರಣೆಯ ಮಿತಿಯನ್ನು ಮೀರಬಾರದು (ರ್ಯಾಚೆಟ್: `frozen` ಪಟ್ಟಿಯಲ್ಲಿರುವ ಸ್ಥಿರಗೊಳಿಸಲಾದ ದೊಡ್ಡ ಫೈಲ್ಗಳು) | ಹೌದು | -| `check:error-helper` | executors/handlersನಲ್ಲಿನ ದೋಷ ಪ್ರತಿಕ್ರಿಯೆಗಳು `buildErrorBody()` / `sanitizeErrorMessage()` ಅನ್ನು ಬಳಸಬೇಕು (ಕಠಿಣ ನಿಯಮ #12) | ಹೌದು | -| `check:migration-numbering` | Migration SQL ಫೈಲ್ಗಳು ಯಾವುದೇ ಅಂತರಗಳು ಅಥವಾ ನಕಲುಗಳಿಲ್ಲದೆ ಅನುಕ್ರಮವಾಗಿ ಸಂಖ್ಯೀಕರಿಸಲ್ಪಟ್ಟಿವೆ | ಹೌದು | -| `check:public-creds` | `publicCreds.ts` ಹೊರಗೆ ಯಾವುದೇ ಅಕ್ಷರಶಃ OAuth `client_id`/`client_secret` ಅಥವಾ Firebase Web ಕೀಲಿಗಳಿಲ್ಲ (ಕಠಿಣ ನಿಯಮ #11) | ಹೌದು | -| `check:db-rules` | `src/lib/db/` ಮಾಡ್ಯೂಲ್ಗಳ ಹೊರಗೆ ಯಾವುದೇ ಕಚ್ಚಾ SQL ಇಲ್ಲ; `localDb.ts` ನಿಂದ ಯಾವುದೇ ಬ್ಯಾರೆಲ್-ಆಮದುಗಳಿಲ್ಲ (ಕಠಿಣ ನಿಯಮಗಳು #2/#5) | ಹೌದು | -| `check:known-symbols` | ಅವುಗಳ ಡಿಸ್ಪ್ಯಾಚ್ ಕೋಷ್ಟಕಗಳಲ್ಲಿ ನೋಂದಾಯಿಸಲಾದ ಪ್ರೊವೈಡರ್ ಎಕ್ಸಿಕ್ಯೂಟರ್ಗಳು, ರೂಟಿಂಗ್ ತಂತ್ರಗಳು ಮತ್ತು ಅನುವಾದಕಗಳು ಡಿಸ್ಕ್ನಲ್ಲಿರುವ ಫೈಲ್ಗಳಿಗೆ ಹೊಂದಿಕೆಯಾಗುತ್ತವೆ — ಯಾವುದೇ ಅನಾಥ ಅಥವಾ ಘೋಷಿಸದ ಸಂಕೇತಗಳಿಲ್ಲ | ಹೌದು | -| `check:route-guard-membership` | ಚೈಲ್ಡ್ ಪ್ರೊಸೆಸ್ ಅನ್ನು ರಚಿಸುವ ಪ್ರತಿಯೊಂದು ರೂಟ್ ಅನ್ನು `isLocalOnlyPath()` ಮೂಲಕ ವರ್ಗೀಕರಿಸಲಾಗಿದೆ (ಕಠಿಣ ನಿಯಮಗಳು #15/#17) | ಹೌದು | -| `check:test-discovery` | ರೆಪೊದಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು `*.test.ts` / `*.spec.ts` ಫೈಲ್ ಅನ್ನು ಕನಿಷ್ಠ ಒಂದು ಟೆಸ್ಟ್ ರನ್ನರ್ ಸಂಗ್ರಹಿಸುತ್ತದೆ (ರ್ಯಾಚೆಟ್: `test-discovery-baseline.json` ನಲ್ಲಿರುವ ಅನಾಥ ಪಟ್ಟಿ ಕೇವಲ ಕುಗ್ಗಬಹುದು) | ಹೌದು | -| `check:agent-skills-sync` | ರಚಿಸಲಾದ agent-skills ಆರ್ಟಿಫ್ಯಾಕ್ಟ್ಗಳು ಅವುಗಳ ಮೂಲ ಕ್ಯಾಟಲಾಗ್ಗೆ ಹೊಂದಿಕೆಯಾಗುತ್ತವೆ (ಯಾವುದೇ ವ್ಯತ್ಯಾಸವಿಲ್ಲ) | -| `check:provider-asset-provenance` | ಪೂರೈಕೆದಾರರ ಲೋಗೋಗಳು/ಸ್ವತ್ತುಗಳು ದಾಖಲಿಸಲಾದ ಮೂಲದಾಖಲೆ ನಮೂದನ್ನು ಹೊಂದಿವೆ | -| `lint:json` | JSON ಕಾನ್ಫಿಗರೇಶನ್ ಫೈಲ್ಗಳು ಪಾರ್ಸ್ ಆಗುತ್ತವೆ ಮತ್ತು ರೆಪೊ lint ನಿಯಮಗಳನ್ನು ಪೂರೈಸುತ್ತವೆ | -| `typecheck:core` | ದೋಷಗಳಿಲ್ಲದ TypeScript ಕಂಪೈಲೇಶನ್ (ಸಲಹಾತ್ಮಕ ಎಚ್ಚರಿಕೆಗಳು ಮಾತ್ರ) | ಹೌದು | -| `typecheck:noimplicit:core` | ಕಟ್ಟುನಿಟ್ಟಿನ `noImplicitAny` — ಭವಿಷ್ಯೋನ್ಮುಖ; ಮೊದಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಅನೇಕ ಕರೆ ಸ್ಥಳಗಳಿಗೆ ಇನ್ನೂ ಅನೋಟೇಶನ್ಗಳು ಅಗತ್ಯವಿವೆ | **ಸಲಹಾತ್ಮಕ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` ಗೆ ಸೀಮಿತವಾದ `tsc` (#7033) — `typecheck:core` ನ ಆಯ್ದ 27-ಫೈಲ್ ಅನುಮತಿಪಟ್ಟಿಯಲ್ಲಿ ಯಾವುದೇ ಡ್ಯಾಶ್ಬೋರ್ಡ್ TSX ಒಳಗೊಂಡಿಲ್ಲ ಮತ್ತು `next build` ಕೂಡ ಅದನ್ನು ಎಂದಿಗೂ ಟೈಪ್-ಚೆಕ್ ಮಾಡುವುದಿಲ್ಲ (`next.config.mjs` ನಲ್ಲಿ `ignoreBuildErrors: true` ಹೊಂದಿಸಲಾಗಿದೆ), ಆದ್ದರಿಂದ ಅಲ್ಲಿನ ಅನಾಥ-ಐಡೆಂಟಿಫೈಯರ್ ರಿಗ್ರೆಷನ್ಗಳು (#6625/#6909) CIಗೆ ಗೋಚರಿಸುತ್ತಿರಲಿಲ್ಲ. ಸ್ಥಿರಗೊಳಿಸಲಾದ ಪ್ರತಿ-ಫೈಲ್/ಪ್ರತಿ-TS-ಕೋಡ್ ಎಣಿಕೆಯ ಬೇಸ್ಲೈನ್ಗೆ (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ನಂತೆಯೇ ಅದೇ stale-enforcement ಮಾದರಿ) ಹೋಲಿಸಿ ವ್ಯತ್ಯಾಸಗಳನ್ನು ಪರಿಶೀಲಿಸುತ್ತದೆ — ಬೇಸ್ಲೈನ್ ಎಣಿಕೆಯನ್ನು ಮೀರಿದ ಹೊಸ ದೋಷಗಳು ಮಾತ್ರ ಗೇಟ್ ವಿಫಲಗೊಳ್ಳುವಂತೆ ಮಾಡುತ್ತವೆ; ಮೊದಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ದೋಷವನ್ನು ಸರಿಪಡಿಸಿದಾಗ `--update` ಮೂಲಕ ಮಿತಿಯನ್ನು ಕೆಳಗಿಳಿಸಿ. | ಹೌದು | - -### ಜಾಬ್: `quality-gate` - -`test-coverage` ನಂತರ ರನ್ ಆಗುತ್ತದೆ. ವಿಫಲವಾದರೆ ವಿಲೀನವನ್ನು ತಡೆಯುತ್ತದೆ. - -| ಸ್ಕ್ರಿಪ್ಟ್ | ಪರಿಶೀಲಿಸುವುದು | ತಡೆಯುವಿಕೆ | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `quality:collect` | `quality-metrics.json` ಅನ್ನು ಉತ್ಪಾದಿಸುತ್ತದೆ (ESLint ಎಚ್ಚರಿಕೆಗಳ ಸಂಖ್ಯೆ, ವಿಲೀನಗೊಳಿಸಿದ ಶಾರ್ಡ್ ವರದಿಯಿಂದ ಕವರೇಜ್) | ಹೌದು (ರ್ಯಾಚೆಟ್ಗಿಂತ ಮುಂಚಿತ) | -| `quality:ratchet` | `quality-baseline.json` ನಲ್ಲಿನ ಪ್ರತಿಯೊಂದು ಮೆಟ್ರಿಕ್ ಹಿಂಜರಿದಿಲ್ಲ (ESLint ಎಚ್ಚರಿಕೆಗಳು ≤ ಬೇಸ್ಲೈನ್; ಕವರೇಜ್ ≥ ಬೇಸ್ಲೈನ್) | ಹೌದು | -| `check:duplication` | ಕೋಡ್ ನಕಲೀಕರಣವು (jscpd@4) `quality-baseline.json` ನಲ್ಲಿನ ಬೇಸ್ಲೈನ್ ಅನ್ನು ಮೀರುವುದಿಲ್ಲ | ಹೌದು | -| `check:complexity` | ಫೈಲ್-ಮಟ್ಟದ ಸೈಕ್ಲೋಮ್ಯಾಟಿಕ್ ಸಂಕೀರ್ಣತೆಯು ಮಿತಿಯನ್ನು ಮೀರುವುದಿಲ್ಲ (ಕೋರ್ ESLint `complexity` + `max-lines-per-function`) | ಹೌದು | -| `check:cognitive-complexity` | ಅರಿವಿನ ಸಂಕೀರ್ಣತೆಯ ರ್ಯಾಚೆಟ್ (`eslint-plugin-sonarjs`) — ಪ್ರತ್ಯೇಕ ESLint ಪಾಸ್; CI ಎರಡನ್ನೂ ವಿಲೀನಗೊಳಿಸಿ ಏಕೈಕ `check:complexity-ratchets` ಹಂತವಾಗಿ ಚಲಾಯಿಸುತ್ತದೆ | ಹೌದು | -| `check:dead-code` | ಬಳಕೆಯಾಗದ ಎಕ್ಸ್ಪೋರ್ಟ್ಗಳು / ಫೈಲ್ಗಳ ರ್ಯಾಚೆಟ್ (knip) ಬೇಸ್ಲೈನ್ಗೆ ಹೋಲಿಸಿದಾಗ ಹಿಂಜರಿಯುವುದಿಲ್ಲ | ಹೌದು | -| `check:compression-budget` | ಸಂಕುಚನ ಬೆಂಚ್ಮಾರ್ಕ್ ಬಜೆಟ್ — ಪ್ರತಿ-ಎಂಜಿನ್ ಟೋಕನ್-ಉಳಿತಾಯದ ಕನಿಷ್ಠ ಮಿತಿಗಳು ಹಿಂಜರಿಯಬಾರದು | ಹೌದು | -| `check:type-coverage` | ಟೈಪ್ ಮಾಡಲಾದ ಶೇಕಡಾವಾರು ರ್ಯಾಚೆಟ್ (`type-coverage`) ಹಿಂಜರಿಯುವುದಿಲ್ಲ; ಬಹುಮಟ್ಟಿಗೆ `typecheck:noimplicit:core` ಅನ್ನು ಒಳಗೊಳ್ಳುತ್ತದೆ | ಹೌದು | -| `check:codeql-ratchet` | ತೆರೆದಿರುವ CodeQL ಎಚ್ಚರಿಕೆಗಳ ಸಂಖ್ಯೆ ಹಿಂಜರಿಯುವುದಿಲ್ಲ (`gh api` ಮೂಲಕ ಓದುತ್ತದೆ; ಟೋಕನ್ ಇಲ್ಲದಿದ್ದರೆ ಸೌಮ್ಯವಾಗಿ ಬಿಟ್ಟುಬಿಡುತ್ತದೆ) — ರಿಫ್ರೆಶ್ ಆವರ್ತನೆ ಮತ್ತು ಕೈಯಾರೆ ಪ್ರಚೋದಿಸುವಿಕೆಗಾಗಿ: ಕೆಳಗಿನ "CodeQL ರ್ಯಾಚೆಟ್" ನೋಡಿ | ಹೌದು | +`.github/workflows/quality.yml` main/release PRಗಳು, ಸಂರಕ್ಷಿತ-ಬ್ರಾಂಚ್ +ಪುಶ್ಗಳು, dispatch ಮತ್ತು merge groupಗಳಲ್ಲಿ CIಗೆ ಪೂರಕವಾಗಿದೆ. PRಗಳು ಪಥ-ಫಿಲ್ಟರ್ ಮಾಡಿದ ವೇಗದ ಚೆಕ್ಗಳನ್ನು ಬಳಸುತ್ತವೆ. ಶಾಶ್ವತವಾಗಿ +ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾದ ನಕಲಿ build ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ; ನೈಜ build/package/boot ಚೆಕ್ಗಳು CIಯಲ್ಲಿಯೇ ಉಳಿದಿವೆ. + +| ಜಾಬ್ | ವ್ಯಾಪ್ತಿ | ನಿರ್ಬಂಧಕ | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | Docs/code PRಗಳು; API docs refs ಮತ್ತು docs-all | ಹೌದು | +| `Fast Quality Gates` | Code PRಗಳು; static checks, typecheck, dashboard typecheck, ಪರಿಣಾಮಕ್ಕೊಳಗಾದ unit tests | ಹೌದು | +| `Forgotten sibling tests` | Code PRಗಳು; ಬದಲಾದ modules ಅನ್ನು static consumers ಮತ್ತು candidate sibling testsಗೆ ಟ್ರೇಸ್ ಮಾಡಲಾಗುತ್ತದೆ; barrel ಮತ್ತು dynamic-import paths ಅನ್ನು ಉಲ್ಲೇಖಿತ allowlist ವಿನಾಯಿತಿಗಳೊಂದಿಗೆ ಸಲಹಾತ್ಮಕ diagnostics ಆಗಿ ವರದಿ ಮಾಡಲಾಗುತ್ತದೆ | **ಸಲಹಾತ್ಮಕ** | +| `Vitest (fast-path)` | Code PRಗಳು; ವೇಗದ vitest suite | ಹೌದು | +| `Unit Tests fast-path` | Code PRಗಳು; 4-shard unit suite | ಹೌದು | +| `No new ESLint warnings` | Code PRಗಳು; suppressions-aware lint guard | ಹೌದು, forks ಸೇರಿದಂತೆ | +| `Merge integrity (changelog + generated skills)` | ಡ್ರಾಫ್ಟ್ ಅಲ್ಲದ PRಗಳು; changelog ಮತ್ತು generated skill sync | ಹೌದು, forks ಸೇರಿದಂತೆ | + +#### ಮರೆತುಹೋದ sibling tests ವರದಿ + +`npm run check:forgotten-sibling-tests` test-impact map ಹಿಂದಿರುವ import resolver ಅನ್ನು ಮರುಬಳಕೆ ಮಾಡುತ್ತದೆ. +ಬದಲಾದ ಪ್ರತಿಯೊಂದು production moduleಗೆ, ಅಭ್ಯರ್ಥಿ +test pull-request diffನಲ್ಲಿ ಇಲ್ಲದಿದ್ದಾಗ, ಇದು ನಿರ್ಣಾಯಕ +`changed module/symbol -> static consumer -> candidate sibling test` ಸರಪಳಿಗಳನ್ನು ವರದಿ ಮಾಡುತ್ತದೆ. ಯಾವುದೇ ನಿರ್ಬಂಧಕ ರೋಲ್ಔಟ್ಗೆ ಮುನ್ನ ಮಾಪನಾಂಕ ನಿರ್ಣಯಕ್ಕಾಗಿ +Markdown ಸಾರಾಂಶ ಮತ್ತು JSON ಫಲಿತಾಂಶವನ್ನು `forgotten-sibling-tests` ವರ್ಕ್ಫ್ಲೋ artifact ಆಗಿ ಉಳಿಸಿಕೊಳ್ಳಲಾಗುತ್ತದೆ. + +Barrel re-exports ಮತ್ತು dynamic imports ಕೇವಲ resolution diagnostics ಆಗಿವೆ; ಅವು ಎಂದಿಗೂ +ತಡೆಯುವ finding ಅನ್ನು ಸೃಷ್ಟಿಸುವುದಿಲ್ಲ. ಪರಿಶೀಲಿಸಲಾದ ವಿನಾಯಿತಿಗಳು +`config/quality/forgotten-sibling-allowlist.json` ನಲ್ಲಿ ಇರುತ್ತವೆ. ಪ್ರತಿಯೊಂದು ನಮೂದು consumer ಮತ್ತು candidate +test ಅನ್ನು ಹೆಸರಿಸಬೇಕು, ನಿರ್ದಿಷ್ಟ ತಾರ್ಕಿಕ ಕಾರಣವನ್ನು ನೀಡಬೇಕು ಮತ್ತು GitHub issue ಅಥವಾ pull requestಗೆ ಲಿಂಕ್ ಮಾಡಬೇಕು. ದೋಷಪೂರಿತ ನಮೂದುಗಳು +ಮುಚ್ಚಿದ ಸ್ಥಿತಿಯಲ್ಲಿ ವಿಫಲವಾಗುತ್ತವೆ. ವಿನಾಯಿತಿಗಳು ಅಳಿಸಲಾದ candidate test ಅಥವಾ `.skip`/`.todo` ಸೇರಿಸುವ diff ಅನ್ನು ನಿರ್ಲಕ್ಷಿಸುವಂತೆ ಮಾಡಲಾಗುವುದಿಲ್ಲ; +assertion ದುರ್ಬಲಗೊಳಿಸುವಿಕೆ ಮತ್ತು ಇತರ masking ಕ್ರಮಗಳು ಸ್ವತಂತ್ರವಾಗಿ ತಡೆಯುವ +`check:test-masking` gateನ ವ್ಯಾಪ್ತಿಯಲ್ಲಿಯೇ ಉಳಿಯುತ್ತವೆ. + +### Job: `lint` + +`main` ಗೆ ಸಲ್ಲಿಸುವ ಪ್ರತಿಯೊಂದು PRನಲ್ಲೂ ರನ್ ಆಗುತ್ತದೆ. ವಿಫಲವಾದರೆ merge ಅನ್ನು ತಡೆಯುತ್ತದೆ. + +| Script (`npm run ...`) | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುತ್ತದೆಯೇ | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| `check:node-runtime` | Node.js ಆವೃತ್ತಿಯು ಬೆಂಬಲಿತ ವ್ಯಾಪ್ತಿಯಲ್ಲಿದೆ | ಹೌದು | +| `check:cycles` | `src/` + `open-sse/` ಎಲ್ಲೆಡೆ ಇರುವ circular imports (AST-ಆಧಾರಿತ, tsconfig `paths` resolve ಮಾಡಲಾಗಿದೆ). Bare = ಸಲಹಾತ್ಮಕ, cycles ಅನ್ನು ಪಟ್ಟಿ ಮಾಡುತ್ತದೆ. `check:cycles:ratchet` (CI ರನ್ ಮಾಡುವುದು) ಎಣಿಕೆಯು `quality-baseline.json` ನಲ್ಲಿರುವ `metrics.cycles` ಮಿತಿಯನ್ನು ಮೀರಿದಾಗ ತಡೆಯುತ್ತದೆ — ಪ್ರಸ್ತುತ 14, `direction: down`, ಆದ್ದರಿಂದ ಅದು ಕಡಿಮೆಯಾಗಲು ಮಾತ್ರ ಸಾಧ್ಯ (#15159 G-01/G-02) | ಹೌದು (ratchet) | +| `check:route-validation:t06` | ಎಲ್ಲಾ routesನಲ್ಲಿ Zod schemas ಇವೆ (Tier 6 policy) | ಹೌದು | +| `check:any-budget:t11` | `@ts-expect-error // any` ಎಣಿಕೆಯು budget ಅನ್ನು ಮೀರುವುದಿಲ್ಲ (Tier 11 catraca) | ಹೌದು | +| `check:provider-consistency` | `providers.ts` ನಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು providerಗೆ `providerRegistry.ts` ನಲ್ಲಿ ಹೊಂದಾಣಿಕೆಯ entry ಇದೆ (ಮತ್ತು allowlist ವ್ಯಾಪ್ತಿಯಲ್ಲಿ ಇದರ ವಿರುದ್ಧವೂ ಅನ್ವಯಿಸುತ್ತದೆ) | ಹೌದು | +| `check:model-lifecycle` | ಕೈಯಾರೆ ನಿರ್ವಹಿಸಲಾದ ಮೂರು routing tableಗಳು checked-in lifecycle snapshot (#11503) ಜೊತೆಗೆ ಹೊಂದಾಣಿಕೆಯಿಂದಿರುತ್ತವೆ: `FITNESS_TABLE` (`taskFitness.ts`) ನಲ್ಲಿ `REGISTRY` route ಮಾಡಬಹುದಾದ ಯಾವುದೇ retired idಗೆ score ನೀಡಲಾಗುವುದಿಲ್ಲ; ಪ್ರತಿಯೊಂದು `BUILT_IN_ALIASES` target `REGISTRY` ನಲ್ಲಿ ಇರುತ್ತದೆ ಮತ್ತು retired-id snapshotನಲ್ಲಿ ಇರುವುದಿಲ್ಲ; `REGISTRY` ನಲ್ಲಿ ಇನ್ನೂ ಇರುವ ಪ್ರತಿಯೊಂದು retired id ಅನ್ನು forward ಮಾಡಲಾಗುತ್ತದೆ ಅಥವಾ `allowedRetiredInCatalog` ನಲ್ಲಿ ಪಟ್ಟಿ ಮಾಡಲಾಗುತ್ತದೆ; ಮತ್ತು ಯಾವುದೇ `DEFAULT_DEGRADATION_MAP` source ಅಥವಾ target ಆ snapshotನಲ್ಲಿ retired ಆಗಿರುವುದಿಲ್ಲ. model ಒಂದನ್ನು ಪ್ರಸ್ತುತ live upstream ಮೂಲಕ serve ಮಾಡಲಾಗುತ್ತಿದೆ ಎಂಬುದನ್ನು ಇದು ಸಾಬೀತುಪಡಿಸುವುದಿಲ್ಲ. Offline — `config/quality/model-lifecycle.json` ಜೊತೆಗೆ ಹೋಲಿಸುತ್ತದೆ; ಇದನ್ನು `npm run quality:refresh-model-lifecycle` ಮೂಲಕ ಕೈಯಾರೆ refresh ಮಾಡಲಾಗುತ್ತದೆ (network; CIಗೆ ಜೋಡಿಸಲಾಗಿಲ್ಲ). `allowedRetiredInCatalog` ಒಂದು burn-down ratchet ಆಗಿದೆ: tracking issue ಇದ್ದಾಗ ಮಾತ್ರ entry ಸೇರಿಸಿ. | ಹೌದು | +| `check:fetch-targets` | client-side `src/` ನಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು `fetch("/api/...")` ನೈಜ `route.ts` ಗೆ resolve ಆಗುತ್ತದೆ | ಹೌದು | +| `check:deps` | repoದಲ್ಲಿನ ಪ್ರತಿಯೊಂದು `package.json` ನಾದ್ಯಂತ ಇರುವ ಎಲ್ಲಾ `npm install` ಮಾಡಬಹುದಾದ depsಗಳು `dependency-allowlist.json` ನಲ್ಲಿ ಇವೆ; ಹೊಸ unpinned ಅಥವಾ slopsquatted packageಗಳನ್ನು flag ಮಾಡಲಾಗುತ್ತದೆ | ಹೌದು | +| `audit:deps` | `npm audit` (root + electron) — ಯಾವುದೇ high/critical advisoryಗಳಿಲ್ಲ (osv `check:vuln-ratchet` ಜೊತೆಗೆ overlap ಆಗುತ್ತದೆ; Rationalization Backlog ನೋಡಿ) | ಹೌದು | +| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashಗಳು, ಯಾವುದೇ host overrideಗಳಿಲ್ಲ | ಹೌದು | +| `check:licenses` | ಉತ್ಪಾದನಾ ಅವಲಂಬನೆಗಳಿಗಾಗಿ SPDX ಪರವಾನಗಿ ಅನುಮತಿ ಪಟ್ಟಿ | ಹೌದು | +| `check:tracked-artifacts` | ಯಾವುದೇ ಬಿಲ್ಡ್ ಆರ್ಟಿಫ್ಯಾಕ್ಟ್ಗಳು / ಕಮಿಟ್ ಮಾಡಿದ `node_modules` ಸಿಮ್ಲಿಂಕ್ಗಳು ಇರಬಾರದು (husky pre-commitನಲ್ಲಿಯೂ ರನ್ ಆಗುತ್ತದೆ; pre-push ಅನ್ನು ಉದ್ದೇಶಪೂರ್ವಕವಾಗಿ ಹಗುರವಾಗಿ ಇರಿಸಲಾಗಿದೆ — #6716) | ಹೌದು | +| `check:ai-attribution` | PR ಕಮಿಟ್ಗಳು, ಶೀರ್ಷಿಕೆ ಅಥವಾ ಮುಖ್ಯಪಠ್ಯದಲ್ಲಿ ಯಾವುದೇ AI/bot `Co-Authored-By` ಟ್ರೇಲರ್ ಅಥವಾ AI-ರಚನೆಯ ಅಡಿಟಿಪ್ಪಣಿ ಇರಬಾರದು — ಕಠಿಣ ನಿಯಮ #16 (`quality.yml`ನ PR→`release/**` ವೇಗದ-ಗೇಟ್ಗಳ ಲೂಪ್ನಲ್ಲಿ — ಈವೆಂಟ್ ಪೇಲೋಡ್ ಅನ್ನು ಓದುತ್ತದೆ, PRಗಳ ಹೊರಗೆ ಯಾವುದೇ ಕಾರ್ಯ ಮಾಡುವುದಿಲ್ಲ — ಮತ್ತು PR→`main`ಗಾಗಿ `ci.yml` ಲಿಂಟ್ನಲ್ಲಿರುವ PR-ಮಾತ್ರದ ಹಂತ; husky `commit-msg` ಹುಕ್ ಕೂಡ; ಮಾನವ ಸಹ-ಲೇಖಕರಿಗೆ ಅನುಮತಿ ಇದೆ; #14436) | +| `check:vitest-exclusions` | ಪ್ರತಿಯೊಂದು Vitest ಹೊರಗಿಡುವಿಕೆಯೂ ಟ್ರ್ಯಾಕಿಂಗ್ ಸಮಸ್ಯೆಯನ್ನು ಹೆಸರಿಸಬೇಕು ಮತ್ತು `config/quality/vitest-exclusions.json`ನಲ್ಲಿ ಕಾಣಿಸಬೇಕು (#13204) | ಹೌದು | +| `check:file-size` | ಯಾವುದೇ ಮೂಲ ಫೈಲ್ ಪ್ರತಿ-ವಿಸ್ತರಣೆಯ ಮಿತಿಯನ್ನು ಮೀರಬಾರದು (ರ್ಯಾಚೆಟ್: `frozen` ಪಟ್ಟಿಯಲ್ಲಿರುವ ದೊಡ್ಡ ಫೈಲ್ಗಳನ್ನು ಸ್ಥಿರಗೊಳಿಸಲಾಗಿದೆ) | ಹೌದು | +| `check:error-helper` | ಎಕ್ಸಿಕ್ಯೂಟರ್ಗಳು/ಹ್ಯಾಂಡ್ಲರ್ಗಳಲ್ಲಿನ ದೋಷ ಪ್ರತಿಕ್ರಿಯೆಗಳು `buildErrorBody()` / `sanitizeErrorMessage()` ಅನ್ನು ಬಳಸಬೇಕು (ಕಠಿಣ ನಿಯಮ #12) | ಹೌದು | +| `check:migration-numbering` | Migration SQL ಫೈಲ್ಗಳು ಯಾವುದೇ ಅಂತರಗಳು ಅಥವಾ ನಕಲುಗಳಿಲ್ಲದೆ ಅನುಕ್ರಮವಾಗಿ ಸಂಖ್ಯೆ ಹೊಂದಿವೆ | ಹೌದು | +| `check:public-creds` | `publicCreds.ts` ಹೊರಗೆ ಯಾವುದೇ ಅಕ್ಷರಶಃ OAuth `client_id`/`client_secret` ಅಥವಾ Firebase Web ಕೀಗಳು ಇಲ್ಲ (ಕಠಿಣ ನಿಯಮ #11) | ಹೌದು | +| `check:db-rules` | `src/lib/db/` ಮಾಡ್ಯೂಲ್ಗಳ ಹೊರಗೆ ಯಾವುದೇ ಕಚ್ಚಾ SQL ಇಲ್ಲ; `localDb.ts` ನಿಂದ ಯಾವುದೇ ಬ್ಯಾರೆಲ್-ಆಮದುಗಳಿಲ್ಲ (ಕಠಿಣ ನಿಯಮಗಳು #2/#5) | ಹೌದು | +| `check:known-symbols` | ತಮ್ಮ dispatch ಕೋಷ್ಟಕಗಳಲ್ಲಿ ನೋಂದಾಯಿಸಲಾದ provider executors, routing strategies ಮತ್ತು translators ಡಿಸ್ಕ್ನಲ್ಲಿರುವ ಫೈಲ್ಗಳಿಗೆ ಹೊಂದಿಕೆಯಾಗುತ್ತವೆ — ಯಾವುದೇ ಅನಾಥ ಅಥವಾ ಘೋಷಿಸದ symbols ಇಲ್ಲ | ಹೌದು | +| `check:route-guard-membership` | child process ಅನ್ನು ಪ್ರಾರಂಭಿಸುವ ಪ್ರತಿಯೊಂದು route ಅನ್ನು `isLocalOnlyPath()` ಮೂಲಕ ವರ್ಗೀಕರಿಸಲಾಗಿದೆ (ಕಠಿಣ ನಿಯಮಗಳು #15/#17) | ಹೌದು | +| `check:test-discovery` | repoದಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು `*.test.ts` / `*.spec.ts` ಫೈಲ್ ಅನ್ನು ಕನಿಷ್ಠ ಒಂದು test runner ಸಂಗ್ರಹಿಸುತ್ತದೆ (ratchet: `test-discovery-baseline.json` ನಲ್ಲಿರುವ ಅನಾಥ ಪಟ್ಟಿ ಕೇವಲ ಕುಗ್ಗಬಹುದು) | ಹೌದು | +| `check:agent-skills-sync` | ರಚಿಸಲಾದ agent-skills ಆರ್ಟಿಫ್ಯಾಕ್ಟ್ಗಳು ಅವುಗಳ ಮೂಲ ಕ್ಯಾಟಲಾಗ್ಗೆ ಹೊಂದಿಕೆಯಾಗುತ್ತವೆ (ಯಾವುದೇ ವ್ಯತ್ಯಾಸವಿಲ್ಲ) | +| `check:provider-asset-provenance` | ಪೂರೈಕೆದಾರರ ಲೋಗೋಗಳು/ಆಸ್ತಿಗಳು ದಾಖಲಿಸಲಾದ ಮೂಲದ ನಮೂದನ್ನು ಹೊಂದಿರುತ್ತವೆ | +| `lint:json` | JSON ಕಾನ್ಫಿಗರೇಶನ್ ಫೈಲ್ಗಳು ಪಾರ್ಸ್ ಆಗುತ್ತವೆ ಮತ್ತು repo lint ನಿಯಮಗಳನ್ನು ಪೂರೈಸುತ್ತವೆ | +| `typecheck:core` | ದೋಷಗಳಿಲ್ಲದೆ TypeScript ಕಂಪೈಲೇಶನ್ (ಸಲಹಾತ್ಮಕ ಎಚ್ಚರಿಕೆಗಳು ಮಾತ್ರ) | ಹೌದು | +| `typecheck:noimplicit:core` | ಕಟ್ಟುನಿಟ್ಟಾದ `noImplicitAny` — ಭವಿಷ್ಯೋನ್ಮುಖ; ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಅನೇಕ ಕರೆ ಸ್ಥಳಗಳಿಗೆ ಇನ್ನೂ ಅನೋಟೇಶನ್ಗಳು ಅಗತ್ಯವಿವೆ | **ಸಲಹಾತ್ಮಕ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` ಗೆ ಸೀಮಿತವಾದ `tsc` (#7033) — `typecheck:core` ನ ಆಯ್ದ 27-ಫೈಲ್ allowlist ಯಾವುದೇ dashboard TSX ಅನ್ನು ಒಳಗೊಂಡಿಲ್ಲ, ಮತ್ತು `next build` ಕೂಡ ಅದನ್ನು ಎಂದಿಗೂ type-check ಮಾಡುವುದಿಲ್ಲ (`next.config.mjs` ನಲ್ಲಿ `ignoreBuildErrors: true` ಅನ್ನು ಹೊಂದಿಸಲಾಗಿದೆ), ಆದ್ದರಿಂದ ಅಲ್ಲಿನ ಅನಾಥ-ಗುರುತಿಸುವಿಕೆ ರಿಗ್ರೆಷನ್ಗಳು (#6625/#6909) CIಗೆ ಗೋಚರಿಸುತ್ತಿರಲಿಲ್ಲ. ಸ್ಥಿರಗೊಳಿಸಲಾದ ಪ್ರತಿ-ಫೈಲ್/ಪ್ರತಿ-TS-code ಎಣಿಕೆಯ baseline (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ನಂತೆಯೇ ಅದೇ stale-enforcement ಮಾದರಿ) ವಿರುದ್ಧ diff ಮಾಡುತ್ತದೆ — baseline ಎಣಿಕೆಯನ್ನು ಮೀರಿದ ಹೊಸ ದೋಷಗಳು ಮಾತ್ರ gate ಅನ್ನು ವಿಫಲಗೊಳಿಸುತ್ತವೆ; ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ದೋಷವನ್ನು ಸರಿಪಡಿಸಿದಾಗ `--update` ಬಳಸಿ ratchet ಅನ್ನು ಕಡಿಮೆ ಮಾಡಿ. | ಹೌದು | + +### Job: `quality-gate` + +`test-coverage` ನಂತರ ಚಾಲನೆಯಾಗುತ್ತದೆ. ವಿಫಲವಾದರೆ merge ಅನ್ನು ತಡೆಯುತ್ತದೆ. + +| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `quality:collect` | `quality-metrics.json` ಅನ್ನು ರಚಿಸುತ್ತದೆ (ESLint ಎಚ್ಚರಿಕೆಗಳ ಸಂಖ್ಯೆ, ವಿಲೀನಗೊಳಿಸಿದ ಶಾರ್ಡ್ ವರದಿಯಿಂದ ಕವರೇಜ್) | ಹೌದು (ರ್ಯಾಚೆಟ್ಗೆ ಅಪ್ಸ್ಟ್ರೀಮ್) | +| `quality:ratchet` | `quality-baseline.json` ನಲ್ಲಿನ ಪ್ರತಿಯೊಂದು ಮೆಟ್ರಿಕ್ ಹಿನ್ನಡೆ ಕಂಡಿಲ್ಲ (ESLint ಎಚ್ಚರಿಕೆಗಳು ≤ ಬೇಸ್ಲೈನ್; ಕವರೇಜ್ ≥ ಬೇಸ್ಲೈನ್) | ಹೌದು | +| `check:duplication` | ಕೋಡ್ ನಕಲು (jscpd@4), `quality-baseline.json` ನಲ್ಲಿನ ಬೇಸ್ಲೈನ್ ಅನ್ನು ಮೀರುವುದಿಲ್ಲ | ಹೌದು | +| `check:complexity` | ಫೈಲ್-ಮಟ್ಟದ ಸೈಕ್ಲೊಮ್ಯಾಟಿಕ್ ಸಂಕೀರ್ಣತೆಯು ಮಿತಿಯನ್ನು ಮೀರುವುದಿಲ್ಲ (ಕೋರ್ ESLint `complexity` + `max-lines-per-function`) | ಹೌದು | +| `check:cognitive-complexity` | ಕಾಗ್ನಿಟಿವ್ ಸಂಕೀರ್ಣತೆ ರ್ಯಾಚೆಟ್ (`eslint-plugin-sonarjs`) — ಪ್ರತ್ಯೇಕ ESLint ಪಾಸ್; CI ಎರಡನ್ನೂ ವಿಲೀನಗೊಳಿಸಿ ಏಕೈಕ `check:complexity-ratchets` ಹಂತವಾಗಿ ಚಲಾಯಿಸುತ್ತದೆ | ಹೌದು | +| `check:dead-code` | ಬಳಕೆಯಾಗದ ಎಕ್ಸ್ಪೋರ್ಟ್ಗಳು / ಫೈಲ್ಗಳ ರ್ಯಾಚೆಟ್ (knip), ಬೇಸ್ಲೈನ್ಗೆ ಹೋಲಿಸಿದಾಗ ಹಿನ್ನಡೆ ಕಾಣುವುದಿಲ್ಲ | ಹೌದು | +| `check:compression-budget` | ಕಂಪ್ರೆಷನ್ ಬೆಂಚ್ಮಾರ್ಕ್ ಬಜೆಟ್ — ಪ್ರತಿ ಎಂಜಿನ್ನ ಟೋಕನ್-ಉಳಿತಾಯದ ಕನಿಷ್ಠ ಮಿತಿಗಳು ಹಿನ್ನಡೆ ಕಾಣಬಾರದು | ಹೌದು | +| `check:type-coverage` | ಟೈಪ್ ಮಾಡಲಾದ ಶೇಕಡಾವಾರು ರ್ಯಾಚೆಟ್ (`type-coverage`) ಹಿನ್ನಡೆ ಕಾಣುವುದಿಲ್ಲ; ಬಹುಮಟ್ಟಿಗೆ `typecheck:noimplicit:core` ಅನ್ನು ಒಳಗೊಳ್ಳುತ್ತದೆ | ಹೌದು | +| `check:codeql-ratchet` | ತೆರೆದಿರುವ CodeQL ಎಚ್ಚರಿಕೆಗಳ ಸಂಖ್ಯೆ ಹಿನ್ನಡೆ ಕಾಣುವುದಿಲ್ಲ (`gh api` ಮೂಲಕ ಓದುತ್ತದೆ; ಟೋಕನ್ ಇಲ್ಲದಿದ್ದರೆ ಸುರಕ್ಷಿತವಾಗಿ ಬಿಟ್ಟುಬಿಡುತ್ತದೆ) — ನವೀಕರಣ ಅವಧಿ ಮತ್ತು ಹಸ್ತಚಾಲಿತ ಟ್ರಿಗರ್ಗಾಗಿ: ಕೆಳಗಿನ "CodeQL ರ್ಯಾಚೆಟ್" ನೋಡಿ | ಹೌದು | ### ಜಾಬ್: `quality-extended` ಸಂಪೂರ್ಣ ಜಾಬ್ ಸಲಹಾತ್ಮಕವಾಗಿದೆ (`continue-on-error: true`). npm-ಆಧಾರಿತ ರ್ಯಾಚೆಟ್ಗಳು -ನೈಜವಾಗಿ ಚಲಿಸುತ್ತವೆ; ಬಾಹ್ಯ ಸ್ಕ್ಯಾನರ್ಗಳು `gh release download` ಮೂಲಕ ಸ್ಥಾಪಿಸಲ್ಪಡುತ್ತವೆ ಮತ್ತು -ಬೈನರಿ ಇನ್ನೂ ಲಭ್ಯವಿಲ್ಲದಿದ್ದಾಗ ಸ್ವತಃ ಬಿಟ್ಟುಬಿಡುತ್ತವೆ (exit 0). +ನೈಜವಾಗಿ ಚಲಿಸುತ್ತವೆ; ಬಾಹ್ಯ ಸ್ಕ್ಯಾನರ್ಗಳು `gh release download` ಮೂಲಕ ಸ್ಥಾಪನೆಯಾಗುತ್ತವೆ ಮತ್ತು +ಬೈನರಿ ಇನ್ನೂ ಲಭ್ಯವಿಲ್ಲದಿದ್ದಾಗ ಸ್ವಯಂ ಬಿಟ್ಟುಬಿಡುತ್ತವೆ (exit 0). -| ಸ್ಕ್ರಿಪ್ಟ್ | ಪರಿಶೀಲಿಸುವುದು | ತಡೆಯುವಿಕೆ | +| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| `check:circular-deps` | ವೃತ್ತಾಕಾರದ ಅವಲಂಬನೆಗಳಿಲ್ಲ (dpdm) | **ಸಲಹಾತ್ಮಕ** | +| `check:circular-deps` | ವೃತ್ತಾಕಾರದ ಡಿಪೆಂಡೆನ್ಸಿಗಳು ಇಲ್ಲ (dpdm) | **ಸಲಹಾತ್ಮಕ** | | `check:bundle-size` | ಬಂಡಲ್ ಗಾತ್ರವು ಮಿತಿಯನ್ನು ಮೀರುವುದಿಲ್ಲ | **ಸಲಹಾತ್ಮಕ** | | `check:secrets` | ರಹಸ್ಯಗಳ ಸ್ಕ್ಯಾನಿಂಗ್ (gitleaks) — ಬೈನರಿ ಇಲ್ಲದಿದ್ದರೆ ಬಿಟ್ಟುಬಿಡುತ್ತದೆ | **ಸಲಹಾತ್ಮಕ** | -| `check:vuln-ratchet` | ಅವಲಂಬನೆಗಳ ದುರ್ಬಲತೆಗಳು (osv-scanner) ಹಿಂಜರಿಯುವುದಿಲ್ಲ — ಬೈನರಿ ಇಲ್ಲದಿದ್ದರೆ ಬಿಟ್ಟುಬಿಡುತ್ತದೆ | **ಸಲಹಾತ್ಮಕ** | -| `check:workflows` | ವರ್ಕ್ಫ್ಲೋ ಲಿಂಟ್ (actionlint + zizmor); ಕಾಣೆಯಾದ/ದೋಷಪೂರಿತ ಸ್ಕ್ಯಾನರ್ಗಳು, ಅಮಾನ್ಯ ವರದಿಗಳು ಅಥವಾ ಕಾಣೆಯಾದ ರ್ಯಾಚೆಟ್ ಬೇಸ್ಲೈನ್ INCOMPLETE ಎಂದು ವಿಫಲಗೊಳ್ಳುತ್ತವೆ. ಮಾನ್ಯ ಕಂಡುಬಂದ ಅಂಶಗಳು ಆಯ್ಕೆಮಾಡಿದ ಕಟ್ಟುನಿಟ್ಟಿನ/ಸಲಹಾತ್ಮಕ/ರ್ಯಾಚೆಟ್ ನೀತಿಯನ್ನು ಅನುಸರಿಸುತ್ತವೆ | ಕಾರ್ಯಗತಗೊಳಿಸುವಿಕೆ ಅಗತ್ಯ; CI ನಲ್ಲಿ zizmor ರ್ಯಾಚೆಟ್ ತಡೆಯುತ್ತದೆ | -| `check:openapi-breaking` | ಬೇಸ್ ಬ್ರಾಂಚ್ಗೆ ಹೋಲಿಸಿದ ಸಾರ್ವಜನಿಕ API ಒಪ್ಪಂದದ (`openapi.yaml`) ಬ್ರೇಕಿಂಗ್ ಬದಲಾವಣೆಗಳು (oasdiff) — `openapiBreaking=N` ಅನ್ನು ಉತ್ಪಾದಿಸುತ್ತದೆ; oasdiff ಇಲ್ಲದಿದ್ದರೆ ಅಥವಾ ಬೇಸ್ ಸ್ಪೆಕ್ ಪರಿಹರಿಸಲಾಗದಿದ್ದರೆ ಬಿಟ್ಟುಬಿಡುತ್ತದೆ | **ಸಲಹಾತ್ಮಕ** | +| `check:vuln-ratchet` | ಡಿಪೆಂಡೆನ್ಸಿ ದೌರ್ಬಲ್ಯಗಳು (osv-scanner) ಹಿನ್ನಡೆ ಕಾಣುವುದಿಲ್ಲ — ಬೈನರಿ ಇಲ್ಲದಿದ್ದರೆ ಬಿಟ್ಟುಬಿಡುತ್ತದೆ | **ಸಲಹಾತ್ಮಕ** | +| `check:workflows` | ವರ್ಕ್ಫ್ಲೋ ಲಿಂಟ್ (actionlint + zizmor); ಕಾಣೆಯಾದ/ದೋಷಯುಕ್ತ ಸ್ಕ್ಯಾನರ್ಗಳು, ಅಮಾನ್ಯ ವರದಿಗಳು ಅಥವಾ ಕಾಣೆಯಾದ ರ್ಯಾಚೆಟ್ ಬೇಸ್ಲೈನ್ ಅನ್ನು INCOMPLETE ಎಂದು ವಿಫಲಗೊಳಿಸುತ್ತವೆ. ಮಾನ್ಯ ಕಂಡುಬಂದ ಅಂಶಗಳು ಆಯ್ದ ಕಟ್ಟುನಿಟ್ಟಿನ/ಸಲಹಾತ್ಮಕ/ರ್ಯಾಚೆಟ್ ನೀತಿಯನ್ನು ಅನುಸರಿಸುತ್ತವೆ | ಕಾರ್ಯಗತಗೊಳಿಸುವಿಕೆ ಅಗತ್ಯ; CI ನಲ್ಲಿ zizmor ರ್ಯಾಚೆಟ್ ತಡೆಯುತ್ತದೆ | +| `check:openapi-breaking` | ಮೂಲ ಬ್ರಾಂಚ್ಗೆ ಹೋಲಿಸಿದ ಸಾರ್ವಜನಿಕ API ಒಪ್ಪಂದದಲ್ಲಿನ (`openapi.yaml`) ಬ್ರೇಕಿಂಗ್ ಬದಲಾವಣೆಗಳು (oasdiff) — `openapiBreaking=N` ಅನ್ನು ಹೊರಸೂಸುತ್ತದೆ; oasdiff ಇಲ್ಲದಿದ್ದರೆ ಅಥವಾ ಮೂಲ ಸ್ಪೆಕ್ ಪರಿಹರಿಸಲಾಗದಿದ್ದರೆ ಬಿಟ್ಟುಬಿಡುತ್ತದೆ | **ಸಲಹಾತ್ಮಕ** | ### ಜಾಬ್: `docs-sync-strict` -`main` ಗೆ ಸಲ್ಲಿಸುವ ಪ್ರತಿಯೊಂದು PR ನಲ್ಲೂ ಚಲಿಸುತ್ತದೆ. ವಿಫಲವಾದರೆ ವಿಲೀನವನ್ನು ತಡೆಯುತ್ತದೆ. - -| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `check:docs-all` | ಕೆಳಗಿನ 6 ಉಪ-ಗೇಟ್ಗಳನ್ನು ಅನುಕ್ರಮವಾಗಿ ಚಲಾಯಿಸುವ ಮೆಟಾ-ಗೇಟ್ | ಹೌದು | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ಆವೃತ್ತಿಗಳ ಹೊಂದಾಣಿಕೆ | ಹೌದು | -| ↳ `check:docs-counts` | ಗದ್ಯದಲ್ಲಿರುವ ಎಣಿಕೆಗಳು (ಪ್ರೊವೈಡರ್ಗಳ ಸಂಖ್ಯೆ, ಮೈಗ್ರೇಶನ್ಗಳ ಸಂಖ್ಯೆ ಇತ್ಯಾದಿ) ನೈಜ ಎಣಿಕೆಗಳ ರ್ಯಾಚೆಟ್ ವಿಂಡೋ ವ್ಯಾಪ್ತಿಯಲ್ಲಿವೆ | ಹೌದು | -| ↳ `check:env-doc-sync` | `.env.example` ನಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು env ವೇರಿಯಬಲ್ ಅನ್ನು docs ಕೋಷ್ಟಕದಲ್ಲಿ ದಾಖಲಿಸಲಾಗಿದೆ ಮತ್ತು ಪ್ರತಿಕ್ರಮದಲ್ಲಿಯೂ ಹಾಗೆಯೇ ಇದೆ | ಹೌದು | -| ↳ `check:deprecated-versions` | docs ನಲ್ಲಿ ಬಳಕೆಯಿಂದ ತೆಗೆದುಹಾಕಲಾದ ಆವೃತ್ತಿ ಸ್ಟ್ರಿಂಗ್ಗಳಿಲ್ಲ | ಹೌದು | -| ↳ `check:doc-links` | docs ನಲ್ಲಿರುವ ಆಂತರಿಕ markdown ಲಿಂಕ್ಗಳು ನೈಜ ಫೈಲ್ಗಳಿಗೆ ಪರಿಹಾರಗೊಳ್ಳುತ್ತವೆ (`[text]`/`(path)` ರೂಪ) | ಹೌದು | -| ↳ `check:fabricated-docs` | docs ನಲ್ಲಿ ಉಲ್ಲೇಖಿಸಲಾದ routes, env ವೇರಿಯಬಲ್ಗಳು, CLI ಕಮಾಂಡ್ಗಳು, hook ಹೆಸರುಗಳು ಮತ್ತು file paths ಕೋಡ್ಬೇಸ್ನಲ್ಲಿ ಅಸ್ತಿತ್ವದಲ್ಲಿವೆ. `--strict` ಮೂಲಕ ಕಠಿಣ ಗೇಟ್; ಫ್ಲ್ಯಾಗ್ ಇಲ್ಲದಿದ್ದರೆ ಸಾಫ್ಟ್-ಫೇಲ್. | ಹೌದು (CI ನಲ್ಲಿ `--strict` ಮೂಲಕ) | -| `check:cli-i18n` | CLI ಕಮಾಂಡ್ ಸ್ಟ್ರಿಂಗ್ಗಳು ಎಲ್ಲಾ i18n ಲೊಕೇಲ್ ಫೈಲ್ಗಳಲ್ಲಿ ಇವೆ | ಹೌದು | -| `check:openapi-coverage` | OpenAPI ಸ್ಪೆಕ್ ನೈಜ routes ಗಳ ಕನಿಷ್ಠ ರ್ಯಾಚೆಟ್ ಮಾಡಿದ ಕೆಳಮಿತಿಯಷ್ಟಾದರೂ ಒಳಗೊಳ್ಳುತ್ತದೆ | ಹೌದು | -| `check:openapi-security-tiers` | `openapi.yaml` ನಲ್ಲಿನ ಭದ್ರತಾ ಹಂತದ annotations, `routeGuard.ts` ವರ್ಗೀಕರಣಗಳೊಂದಿಗೆ ಹೊಂದಾಣಿಕೆಯಾಗಿವೆ | **ಸಲಹಾತ್ಮಕ** | -| `check:openapi-routes` | `openapi.yaml` ನಲ್ಲಿನ ಪ್ರತಿಯೊಂದು path ನೈಜ `route.ts` ಗೆ ಪರಿಹಾರಗೊಳ್ಳುತ್ತದೆ (ಭ್ರಮೆ-ವಿರೋಧಿ) | ಹೌದು | -| `check:docs-symbols` | `docs/**/*.md` ನಲ್ಲಿನ ಪ್ರತಿಯೊಂದು `/api/...` ಉಲ್ಲೇಖವು ನೈಜ `route.ts` ಗೆ ಪರಿಹಾರಗೊಳ್ಳುತ್ತದೆ (ಭ್ರಮೆ-ವಿರೋಧಿ) | ಹೌದು | -| `i18n translation drift` | i18n ಲೊಕೇಲ್ ಫೈಲ್ಗಳಲ್ಲಿನ ಅನುವಾದಿಸದ keys — ಎಚ್ಚರಿಕೆ ಮಾತ್ರ | **ಸಲಹಾತ್ಮಕ** | +`main` ಗೆ ಬರುವ ಪ್ರತಿಯೊಂದು PR ನಲ್ಲೂ ಚಲಿಸುತ್ತದೆ. ವಿಫಲವಾದರೆ ವಿಲೀನವನ್ನು ತಡೆಯುತ್ತದೆ. + +| ಸ್ಕ್ರಿಪ್ಟ್ | ಪರಿಶೀಲಿಸುವುದು | ನಿರ್ಬಂಧಿಸುವುದೇ | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `check:docs-all` | ಕೆಳಗಿನ 6 ಉಪ-ಗೇಟ್ಗಳನ್ನು ಅನುಕ್ರಮವಾಗಿ ಚಲಾಯಿಸುವ ಮೆಟಾ-ಗೇಟ್ | ಹೌದು | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ಆವೃತ್ತಿಗಳ ಸ್ಥಿರತೆ | ಹೌದು | +| ↳ `check:docs-counts` | ಗದ್ಯದಲ್ಲಿನ ಎಣಿಕೆಗಳು (ಪ್ರೊವೈಡರ್ಗಳ ಸಂಖ್ಯೆ, ಮೈಗ್ರೇಶನ್ಗಳ ಸಂಖ್ಯೆ, ಇತ್ಯಾದಿ) ನೈಜ ಎಣಿಕೆಗಳ ರಾಚೆಟ್ ವ್ಯಾಪ್ತಿಯೊಳಗಿವೆ | ಹೌದು | +| ↳ `check:env-doc-sync` | `.env.example` ನಲ್ಲಿರುವ ಪ್ರತಿಯೊಂದು env var ಅನ್ನು docs ಕೋಷ್ಟಕದಲ್ಲಿ ದಾಖಲಿಸಲಾಗಿದೆ ಮತ್ತು ಅದರ ವಿರುದ್ಧವೂ ಹಾಗೆಯೇ ಇದೆ | ಹೌದು | +| ↳ `check:deprecated-versions` | docs ನಲ್ಲಿ ಬಳಕೆಯಿಂದ ತೆಗೆದುಹಾಕಲಾದ ಯಾವುದೇ ಆವೃತ್ತಿ ಸ್ಟ್ರಿಂಗ್ಗಳಿಲ್ಲ | ಹೌದು | +| ↳ `check:doc-links` | docs ನಲ್ಲಿರುವ ಆಂತರಿಕ markdown ಲಿಂಕ್ಗಳು ನೈಜ ಫೈಲ್ಗಳಿಗೆ ಪರಿಹರಿಸಲ್ಪಡುತ್ತವೆ (`[text]`/`(path)` ರೂಪ) | ಹೌದು | +| ↳ `check:fabricated-docs` | docs ನಲ್ಲಿ ಉಲ್ಲೇಖಿಸಲಾದ ರೂಟ್ಗಳು, env vars, CLI ಕಮಾಂಡ್ಗಳು, ಹೂಕ್ ಹೆಸರುಗಳು ಮತ್ತು ಫೈಲ್ ಪಥಗಳು ಕೋಡ್ಬೇಸ್ನಲ್ಲಿ ಅಸ್ತಿತ್ವದಲ್ಲಿವೆ. `--strict` ಮೂಲಕ ಹಾರ್ಡ್ ಗೇಟ್; ಫ್ಲ್ಯಾಗ್ ಇಲ್ಲದೆ ಸಾಫ್ಟ್-ಫೇಲ್. | ಹೌದು (CI ನಲ್ಲಿ `--strict` ಮೂಲಕ) | +| `check:cli-i18n` | CLI ಕಮಾಂಡ್ ಸ್ಟ್ರಿಂಗ್ಗಳು ಎಲ್ಲಾ i18n ಲೊಕೇಲ್ ಫೈಲ್ಗಳಲ್ಲಿ ಇವೆ | ಹೌದು | +| `check:openapi-coverage` | OpenAPI ಸ್ಪೆಕ್ ನೈಜ ರೂಟ್ಗಳ ಕನಿಷ್ಠ ರಾಚೆಟ್ ಮಾಡಿದ ಮಿತಿಯನ್ನಾದರೂ ಒಳಗೊಳ್ಳುತ್ತದೆ | ಹೌದು | +| `check:openapi-security-tiers` | `openapi.yaml` ನಲ್ಲಿನ ಭದ್ರತಾ ಹಂತದ ಟಿಪ್ಪಣಿಗಳು `routeGuard.ts` ವರ್ಗೀಕರಣಗಳೊಂದಿಗೆ ಸ್ಥಿರವಾಗಿವೆ | **ಸಲಹಾತ್ಮಕ** | +| `check:openapi-routes` | `openapi.yaml` ನಲ್ಲಿನ ಪ್ರತಿಯೊಂದು ಪಥವು ನೈಜ `route.ts` ಗೆ ಪರಿಹರಿಸಲ್ಪಡುತ್ತದೆ (ಕಲ್ಪಿತ ಮಾಹಿತಿಯ ತಡೆ) | ಹೌದು | +| `check:docs-symbols` | `docs/**/*.md` ನಲ್ಲಿನ ಪ್ರತಿಯೊಂದು `/api/...` ಉಲ್ಲೇಖವು ನೈಜ `route.ts` ಗೆ ಪರಿಹರಿಸಲ್ಪಡುತ್ತದೆ (ಕಲ್ಪಿತ ಮಾಹಿತಿಯ ತಡೆ) | ಹೌದು | +| `i18n translation drift` | i18n ಲೊಕೇಲ್ ಫೈಲ್ಗಳಲ್ಲಿನ ಅನುವಾದಿಸದ ಕೀಗಳು — ಎಚ್ಚರಿಕೆ ಮಾತ್ರ | **ಸಲಹಾತ್ಮಕ** | ### ಜಾಬ್: `i18n-ui-coverage` -| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | -| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `check-ui-keys-coverage` (ಇನ್ಲೈನ್) | UI i18n key ವ್ಯಾಪ್ತಿ ≥ 65% ಆಗಿದೆ | ಹೌದು | -| `check-ui-value-drift` (ಇನ್ಲೈನ್) | ಮರುಬರೆಯಲಾದ ಇಂಗ್ಲಿಷ್ **value** ಯಾವುದೇ ಹಳೆಯ ಅನುವಾದವನ್ನು ಉಳಿಸುವುದಿಲ್ಲ | ಹೌದು | -| `check-new-key-coverage` (ಇನ್ಲೈನ್) | ಒಂದು **ಹೊಸ** ಇಂಗ್ಲಿಷ್ key ಅನ್ನು ಪ್ರತಿಯೊಂದು ಲೊಕೇಲ್ನಲ್ಲೂ ಅನುವಾದಿಸಲಾಗಿದೆ — `__MISSING__:` ಮಾರ್ಕರ್ ಅನ್ನು ತಿರಸ್ಕರಿಸಲಾಗುತ್ತದೆ | ಹೌದು | -| `check-translation-ratio` | ಪ್ರತಿ ಲೊಕೇಲ್ನ ನೈಜ-ಅನುವಾದ ಅನುಪಾತವು (ಅನುಮತಿ ಪಟ್ಟಿಯ ಹೊರಗಿರುವ ಇಂಗ್ಲಿಷ್ಗೆ-ತದ್ರೂಪ / placeholder / ಕಾಣೆಯಾದ leaves) `config/quality/i18n-translation-baseline.json` + slack ಅನ್ನು ಮೀರಬಾರದು | **ಸಲಹಾತ್ಮಕ** | +| ಸ್ಕ್ರಿಪ್ಟ್ | ಪರಿಶೀಲಿಸುವುದು | ನಿರ್ಬಂಧಿಸುವುದೇ | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (inline) | UI i18n ಕೀ ವ್ಯಾಪ್ತಿ ≥ 65% ಆಗಿದೆ | ಹೌದು | +| `check-ui-value-drift` (inline) | ಮರುಬರೆಯಲಾದ ಇಂಗ್ಲಿಷ್ **ಮೌಲ್ಯವು** ಯಾವುದೇ ಹಳೆಯ ಅನುವಾದವನ್ನು ಉಳಿಸುವುದಿಲ್ಲ | ಹೌದು | +| `check-new-key-coverage` (inline) | ಒಂದು **ಹೊಸ** ಇಂಗ್ಲಿಷ್ ಕೀ ಪ್ರತಿ ಲೊಕೇಲ್ನಲ್ಲಿಯೂ ಅನುವಾದಗೊಂಡಿದೆ — `__MISSING__:` ಗುರುತನ್ನು ತಿರಸ್ಕರಿಸಲಾಗುತ್ತದೆ | ಹೌದು | +| `check-translation-ratio` | ಪ್ರತಿ ಲೊಕೇಲ್ನ ನೈಜ-ಅನುವಾದ ಅನುಪಾತವು (ಅನುಮತಿ ಪಟ್ಟಿಯ ಹೊರಗೆ ಇಂಗ್ಲಿಷ್ಗೆ ತದ್ರೂಪವಾದ / ಪ್ಲೇಸ್ಹೋಲ್ಡರ್ / ಕಾಣೆಯಾದ ಲೀಫ್ಗಳು) `config/quality/i18n-translation-baseline.json` + ಸಡಿಲಿಕೆಯ ಮಿತಿಯನ್ನು ಮೀರಬಾರದು | **ಸಲಹಾತ್ಮಕ** | -`fetch-depth: 0` ಅಗತ್ಯವಿದೆ — value-drift ಗೇಟ್, merge base ಗೆ ಹೋಲಿಸಿ `en.json` ನ diff ಪರಿಶೀಲಿಸುತ್ತದೆ. +`fetch-depth: 0` ಅಗತ್ಯವಿದೆ — ಮೌಲ್ಯ-ವ್ಯತ್ಯಾಸ ಗೇಟ್, ವಿಲೀನದ ಮೂಲದ ವಿರುದ್ಧ `en.json` ಅನ್ನು ಹೋಲಿಸುತ್ತದೆ. #### `check-ui-value-drift` — ಹಳೆಯ-ಅನುವಾದ ಗೇಟ್ -ಇತರ ಗೇಟ್ಗಳು ರಚನಾತ್ಮಕವಾಗಿ ಗುರುತಿಸಲಾಗದ ಏಕೈಕ i18n regression ಅನ್ನು ಇದು ಪತ್ತೆಹಚ್ಚುತ್ತದೆ: ಇಂಗ್ಲಿಷ್ value ಅನ್ನು -ಮರುಬರೆಯಲಾಗಿದ್ದರೂ, _ಹಿಂದಿನ_ ಇಂಗ್ಲಿಷ್ನಿಂದ ಪಡೆದ ಅನುವಾದಗಳು ಹಾಗೆಯೇ ಉಳಿಯುತ್ತವೆ; ಪರಿಣಾಮವಾಗಿ -ಇಂಗ್ಲಿಷ್ ಹೊರತಾದ ಭಾಷೆಗಳ ಬಳಕೆದಾರರು ಆತ್ಮವಿಶ್ವಾಸದಿಂದ ಬರೆದಿರುವ, ಆದರೆ ಈಗ ತಪ್ಪಾಗಿರುವ ಪಠ್ಯವನ್ನೇ ಓದುತ್ತಿರುತ್ತಾರೆ. +ಇತರ ಗೇಟ್ಗಳು ರಚನಾತ್ಮಕವಾಗಿ ಪತ್ತೆಹಚ್ಚಲಾಗದ ಏಕೈಕ i18n ರಿಗ್ರೆಷನ್ ಅನ್ನು ಇದು ಪತ್ತೆಹಚ್ಚುತ್ತದೆ: ಒಂದು ಇಂಗ್ಲಿಷ್ ಮೌಲ್ಯವನ್ನು +ಮರುಬರೆಯಲಾಗುತ್ತದೆ ಮತ್ತು _ಹಿಂದಿನ_ ಇಂಗ್ಲಿಷ್ನಿಂದ ಪಡೆದ ಅನುವಾದಗಳು ಹಾಗೆಯೇ ಉಳಿಯುತ್ತವೆ, ಆದ್ದರಿಂದ +ಇಂಗ್ಲಿಷ್ ಹೊರತಾದ ಭಾಷೆಗಳ ಬಳಕೆದಾರರು ಆತ್ಮವಿಶ್ವಾಸದಿಂದ ಬರೆಯಲಾದ, ಈಗ ತಪ್ಪಾಗಿರುವ ಪಠ್ಯವನ್ನೇ ಓದುತ್ತಿರುತ್ತಾರೆ. -ಇದು ನಿಜವಾಗಿಯೂ ಬಿಡುಗಡೆಯಾಗಿತ್ತು. Antigravity login helper ಸೇರಿಸಲಾದಾಗ (#5203) -`oauthModal.googleOAuthWarning` ಅನ್ನು ಮರುಬರೆಯಲಾಯಿತು; **43 ಲೊಕೇಲ್ಗಳಲ್ಲಿ 39** ಲೊಕೇಲ್ಗಳು ಆಪರೇಟರ್ಗಳಿಗೆ "ಸಂಪೂರ್ಣ -URL ಅನ್ನು ನಕಲಿಸಿ ಮತ್ತು ಅದನ್ನು ಕೆಳಗೆ ಅಂಟಿಸಿ" ಎಂದು ಹೇಳುವ ಪಠ್ಯವನ್ನೇ ಉಳಿಸಿಕೊಂಡಿದ್ದವು — ಆ provider ಗಾಗಿ ಪೂರ್ಣಗೊಳ್ಳಲು ಸಾಧ್ಯವಿಲ್ಲದ flow. ಈ ಕೆಳಗಿನ ಕಾರಣಗಳಿಂದ -#8463 ವರೆಗೆ ಇದು ಗಮನಕ್ಕೆ ಬರಲಿಲ್ಲ: +ಇದು ನಿಜವಾಗಿಯೂ ಬಿಡುಗಡೆಗೊಂಡಿತ್ತು. Antigravity ಲಾಗಿನ್ ಸಹಾಯಕವನ್ನು ಸೇರಿಸಿದಾಗ (#5203) +`oauthModal.googleOAuthWarning` ಅನ್ನು ಮರುಬರೆಯಲಾಗಿತ್ತು; **43 ಲೊಕೇಲ್ಗಳಲ್ಲಿ 39** ಲೊಕೇಲ್ಗಳು ಆಪರೇಟರ್ಗಳಿಗೆ "ಸಂಪೂರ್ಣ +URL ಅನ್ನು ನಕಲಿಸಿ ಮತ್ತು ಕೆಳಗೆ ಅಂಟಿಸಿ" ಎಂದು ಹೇಳುವ ಪಠ್ಯವನ್ನು ಉಳಿಸಿಕೊಂಡಿದ್ದವು — ಆ ಪ್ರೊವೈಡರ್ಗಾಗಿ ಪೂರ್ಣಗೊಳಿಸಲಾಗದ ಪ್ರಕ್ರಿಯೆ. ಈ ಕೆಳಗಿನ ಕಾರಣಗಳಿಂದ +#8463 ವರೆಗೆ ಇದು ಗಮನಕ್ಕೆ ಬಂದಿರಲಿಲ್ಲ: -- `sync-ui-keys` **ಇಲ್ಲದಿರುವ** keys ಅನ್ನು ಮಾತ್ರ backfill ಮಾಡುತ್ತದೆ, **ಹಳೆಯದಾದ** keys ಅನ್ನು ಎಂದಿಗೂ ಮಾಡುವುದಿಲ್ಲ; -- `check-ui-keys-coverage` key _ಇರುವಿಕೆಯನ್ನು_ ಎಣಿಸುತ್ತದೆ, ಆದ್ದರಿಂದ ಹಳೆಯ ಅನುವಾದವೂ ಒಳಗೊಂಡಿದೆ ಎಂದು ಪರಿಗಣಿಸಲ್ಪಡುತ್ತದೆ; -- `check-translation-drift` ಎಂಬುದು `docs/i18n//**.md` documentation mirrors ಅನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡುತ್ತದೆ — - ಅದು `src/i18n/messages/*.json` ಅನ್ನು ಎಂದಿಗೂ ಓದುವುದಿಲ್ಲ. 2026-09 ಮರು-ಸಿಂಕ್ನಿಂದ ಜಾಬ್ `docs-sync-strict` ನಲ್ಲಿ ತಡೆಯಲಾಗುತ್ತಿದೆ: - core doc ಅನ್ನು ಸಂಪಾದಿಸಿ → `npm run i18n:run -- --files=` (ವಿಭಾಗ-ಮಟ್ಟದ, ಕಡಿಮೆ ವೆಚ್ಚದ). +- `sync-ui-keys` **ಇಲ್ಲದಿರುವ** ಕೀಗಳನ್ನು ಮಾತ್ರ ಬ್ಯಾಕ್ಫಿಲ್ ಮಾಡುತ್ತದೆ, **ಹಳೆಯದಾದ** ಕೀಗಳನ್ನು ಎಂದಿಗೂ ಅಲ್ಲ; +- `check-ui-keys-coverage` ಕೀಗಳ _ಉಪಸ್ಥಿತಿಯನ್ನು_ ಎಣಿಸುತ್ತದೆ, ಆದ್ದರಿಂದ ಹಳೆಯ ಅನುವಾದವೂ ವ್ಯಾಪ್ತಿಗೆ ಒಳಪಟ್ಟಂತೆ ಪರಿಗಣಿಸಲ್ಪಡುತ್ತದೆ; +- `check-translation-drift` ಎಂಬುದು `docs/i18n//**.md` ದಸ್ತಾವೇಜೀಕರಣ ಮಿರರ್ಗಳನ್ನು ಟ್ರ್ಯಾಕ್ ಮಾಡುತ್ತದೆ — + ಅದು ಎಂದಿಗೂ `src/i18n/messages/*.json` ಅನ್ನು ಓದುವುದಿಲ್ಲ. 2026-09 ಮರು-ಸಿಂಕ್ನಿಂದ `docs-sync-strict` ಜಾಬ್ನಲ್ಲಿ ನಿರ್ಬಂಧಿಸಲಾಗುತ್ತಿದೆ: + ಪ್ರಮುಖ doc ಅನ್ನು ಸಂಪಾದಿಸಿ → `npm run i18n:run -- --files=` (ವಿಭಾಗ-ಮಟ್ಟದ, ಕಡಿಮೆ ವೆಚ್ಚದ). -**ಡಿಫ್-ಅರಿವುಳ್ಳದ್ದು, ಬೇಸ್ಲೈನ್-ಆಧಾರಿತವಲ್ಲ.** ಇದು ವಿಲೀನದ ಮೂಲದಲ್ಲಿರುವ `en.json` ಅನ್ನು -ವರ್ಕಿಂಗ್ ಟ್ರೀಯೊಂದಿಗೆ ಹೋಲಿಸುತ್ತದೆ; ಇಂಗ್ಲಿಷ್ ಮೌಲ್ಯ ಬದಲಾಗಿರುವ ಪ್ರತಿಯೊಂದು ಕೀಗಾಗಿ, ಇನ್ನೂ -ಬದಲಾಗದ ಅನುವಾದವನ್ನು ಹೊಂದಿರುವ ಯಾವುದೇ ಲೊಕೇಲ್ ಹಳತಾಗಿದೆ. ಇದು ಉದ್ದೇಶಪೂರ್ವಕವಾಗಿ **ಮೊದಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಬಾಕಿಯನ್ನು ಸ್ಥಗಿತಗೊಳಿಸುತ್ತದೆ** — ದೀರ್ಘಕಾಲದಿಂದ ಇರುವ ಅನುವಾದವು ಯಾವ ಹಳೆಯ ಇಂಗ್ಲಿಷ್ನಿಂದ ಬಂದಿದೆ ಎಂಬುದನ್ನು ಡಿಫ್ -ಬಹಿರಂಗಪಡಿಸಲಾರದು, ಆದ್ದರಿಂದ ಗೇಟ್ ಪ್ರಸ್ತುತ ಬದಲಾವಣೆಯು ಸ್ಪರ್ಶಿಸುವುದನ್ನು ಮಾತ್ರ ನಿರ್ಣಯಿಸುತ್ತದೆ. -ಪರ್ಯಾಯವು (ಪ್ರತಿ-ಕೀ ಹ್ಯಾಶ್ ಬೇಸ್ಲೈನ್) ಸುಮಾರು 600 KB ಗಾತ್ರದ ರಚಿಸಲಾದ ಫೈಲ್ ಅಗತ್ಯಪಡಿಸುತ್ತದೆ, -ಇದು ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಅತಿ ದೊಡ್ಡ ಬೇಸ್ಲೈನ್ಗಿಂತ 3× ದೊಡ್ಡದಾಗಿದ್ದು, ಪ್ರತಿ i18n PR ನಲ್ಲೂ ಬದಲಾವಣೆಗಳನ್ನು ಉಂಟುಮಾಡುತ್ತದೆ. +**ವ್ಯತ್ಯಾಸ-ಅರಿವಿನದು, ಬೇಸ್ಲೈನ್-ಆಧಾರಿತವಲ್ಲ.** ಇದು ವಿಲೀನ ಬೇಸ್ನಲ್ಲಿರುವ `en.json` ಅನ್ನು +ವರ್ಕಿಂಗ್ ಟ್ರೀಯೊಂದಿಗೆ ಹೋಲಿಸುತ್ತದೆ; ಇಂಗ್ಲಿಷ್ ಮೌಲ್ಯ ಬದಲಾದ ಪ್ರತಿಯೊಂದು ಕೀಗಾಗಿ, ಇನ್ನೂ +ಬದಲಾಯಿಸದ ಅನುವಾದವನ್ನು ಹೊಂದಿರುವ ಯಾವುದೇ ಲೊಕೇಲ್ ಹಳೆಯದಾಗಿದೆ. ಇದು ಉದ್ದೇಶಪೂರ್ವಕವಾಗಿ **ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಬಾಕಿಯನ್ನು ಸ್ಥಗಿತಗೊಳಿಸುತ್ತದೆ** — ದೀರ್ಘಕಾಲದಿಂದ ಇರುವ ಅನುವಾದವು ಯಾವ ಹಳೆಯ ಇಂಗ್ಲಿಷ್ನಿಂದ ಬಂದಿದೆ ಎಂಬುದನ್ನು +ವ್ಯತ್ಯಾಸವು ಬಹಿರಂಗಪಡಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ, ಆದ್ದರಿಂದ ಪ್ರಸ್ತುತ ಬದಲಾವಣೆಯು ಸ್ಪರ್ಶಿಸುವುದನ್ನು ಮಾತ್ರ ಗೇಟ್ ನಿರ್ಣಯಿಸುತ್ತದೆ. +ಪರ್ಯಾಯಕ್ಕೆ (ಪ್ರತಿ-ಕೀ ಹ್ಯಾಶ್ ಬೇಸ್ಲೈನ್) ~600 KB ರಚಿಸಲಾದ ಫೈಲ್ ಅಗತ್ಯವಿರುತ್ತದೆ, +ಇದು ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಅತಿದೊಡ್ಡ ಬೇಸ್ಲೈನ್ಗಿಂತ 3× ದೊಡ್ಡದು ಮತ್ತು ಪ್ರತಿಯೊಂದು i18n PRನಲ್ಲಿಯೂ ಬದಲಾವಣೆಗೆ ಒಳಗಾಗುತ್ತದೆ. ಇದನ್ನು ಪೂರೈಸಲು ಎರಡು ಮಾರ್ಗಗಳಿವೆ: 1. ಪರಿಣಾಮಕ್ಕೊಳಗಾದ ಅನುವಾದಗಳನ್ನು ನವೀಕರಿಸಿ, ಅಥವಾ -2. ಅವುಗಳನ್ನು `__MISSING__:` ಎಂದು ಹೊಂದಿಸಿ — ಆಗ ರನ್ಟೈಮ್ ಸರಿಪಡಿಸಿದ ಇಂಗ್ಲಿಷ್ ಅನ್ನು ಒದಗಿಸುತ್ತದೆ - (`src/i18n/request.ts::deepMergeFallback`, #7258) ಮತ್ತು ಕೀ ಅನುವಾದಕ್ಕಾಗಿ ಸರದಿಗೆ ಸೇರುತ್ತದೆ. +2. ಅವುಗಳನ್ನು `__MISSING__:` ಎಂದು ಹೊಂದಿಸಿ — ನಂತರ ರನ್ಟೈಮ್ ಸರಿಪಡಿಸಿದ ಇಂಗ್ಲಿಷ್ ಅನ್ನು ಒದಗಿಸುತ್ತದೆ + (`src/i18n/request.ts::deepMergeFallback`, #7258) ಮತ್ತು ಕೀ ಅನುವಾದಕ್ಕಾಗಿ ಸರತಿಗೆ ಸೇರುತ್ತದೆ. -ಸ್ಟ್ರಿಂಗ್ನ **ಅರ್ಥ** ಬದಲಾಗಿದ್ದರೆ, **ಕೀಗೆ ಮರುಹೆಸರಿಸುವುದಕ್ಕೆ** ಆದ್ಯತೆ ನೀಡಿ: ಹೊಸ ಕೀ -ಹಳತಾದ ಅನುವಾದವನ್ನು ಆನುವಂಶಿಕವಾಗಿ ಪಡೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ. #8463 ಬಳಸಿದ ಮಾದರಿ ಇದೇ. +ಸ್ಟ್ರಿಂಗ್ನ **ಅರ್ಥ** ಬದಲಾಗಿದ್ದರೆ, **ಕೀಗೆ ಮರುಹೆಸರಿಸುವುದಕ್ಕೆ** ಆದ್ಯತೆ ನೀಡಿ: ಹೊಸ ಕೀಗೆ +ಹಳೆಯ ಅನುವಾದವನ್ನು ಆನುವಂಶಿಕವಾಗಿ ಪಡೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ. #8463 ಬಳಸಿದ ಮಾದರಿ ಇದೇ. ```bash -npm run i18n:check-value-drift # ಕಟ್ಟುನಿಟ್ಟಿನದು (CI ಚಲಾಯಿಸುವುದು) +npm run i18n:check-value-drift # ಕಟ್ಟುನಿಟ್ಟಾದುದು (CI ಚಲಾಯಿಸುವುದು) npm run i18n:check-value-drift:warn # ವರದಿ ಮಾತ್ರ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -ಮೂಲ ಕ್ಯಾಟಲಾಗ್ ಅನ್ನು ಓದಲು ಸಾಧ್ಯವಾಗದಿದ್ದಾಗ (ಬೇಸ್ ref ಇಲ್ಲದ ಶಾಲೋ -ಕ್ಲೋನ್), `check-openapi-breaking` ಅನ್ನು ಅನುಸರಿಸುವಂತೆ, `SKIP reason=base-unresolved` ಜೊತೆಗೆ 0 ಅನ್ನು ನಿರ್ಗಮನ ಮೌಲ್ಯವಾಗಿ ನೀಡುತ್ತದೆ. +ಬೇಸ್ ಕ್ಯಾಟಲಾಗ್ ಅನ್ನು ಓದಲು ಸಾಧ್ಯವಾಗದಿದ್ದಾಗ (ಬೇಸ್ ref ಇಲ್ಲದ ಶಾಲೋ +ಕ್ಲೋನ್), `check-openapi-breaking` ಅನ್ನು ಅನುಸರಿಸುವಂತೆ `SKIP reason=base-unresolved` ಜೊತೆಗೆ 0 ಮೌಲ್ಯದಿಂದ ನಿರ್ಗಮಿಸುತ್ತದೆ. ### ಜಾಬ್: `i18n` -ಸಂಪೂರ್ಣ i18n ಮೌಲ್ಯೀಕರಣ ಮ್ಯಾಟ್ರಿಕ್ಸ್ (ಪ್ರತಿ ಲೊಕೇಲ್ಗೆ ಒಂದು ಜಾಬ್). ಇಡೀ ಜಾಬ್ ಸಲಹಾತ್ಮಕವಾಗಿದೆ. +ಸಂಪೂರ್ಣ i18n ಮೌಲ್ಯಮಾಪನ ಮ್ಯಾಟ್ರಿಕ್ಸ್ (ಪ್ರತಿ ಲೊಕೇಲ್ಗೆ ಒಂದು ಜಾಬ್). ಸಂಪೂರ್ಣ ಜಾಬ್ ಸಲಹಾತ್ಮಕವಾಗಿದೆ. -| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | -| ------------------------------- | ---------------------------- | ------------------------------------------------------ | -| `validate_translation.py quick` | ಪ್ರತಿ ಲೊಕೇಲ್ನ ಅನುವಾದ ಪೂರ್ಣತೆ | **ಸಲಹಾತ್ಮಕ** (ಇಡೀ ಜಾಬ್ನಲ್ಲಿ `continue-on-error: true`) | +| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ನಿರ್ಬಂಧಿಸುವಿಕೆ | +| ------------------------------- | ------------------------------- | ---------------------------------------------------------- | +| `validate_translation.py quick` | ಪ್ರತಿ ಲೊಕೇಲ್ನ ಅನುವಾದದ ಸಂಪೂರ್ಣತೆ | **ಸಲಹಾತ್ಮಕ** (ಸಂಪೂರ್ಣ ಜಾಬ್ನಲ್ಲಿ `continue-on-error: true`) | ### ಜಾಬ್: `pr-test-policy` -ಪುಲ್ ರಿಕ್ವೆಸ್ಟ್ಗಳಲ್ಲಿ ಮಾತ್ರ ಚಲಿಸುತ್ತದೆ. +ಪುಲ್ ರಿಕ್ವೆಸ್ಟ್ಗಳಲ್ಲಿ ಮಾತ್ರ ರನ್ ಆಗುತ್ತದೆ. -| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, ಅಥವಾ `bin/` ನಲ್ಲಿನ ಪ್ರೊಡಕ್ಷನ್ ಕೋಡ್ ಬದಲಾಯಿಸುವ PRಗಳು ಪರೀಕ್ಷೆಗಳನ್ನು ಸೇರಿಸಬೇಕು ಅಥವಾ ನವೀಕರಿಸಬೇಕು (ಕಠಿಣ ನಿಯಮ #8) | ಹೌದು | -| `check:test-masking` | ಬದಲಾದ ಪರೀಕ್ಷಾ ಫೈಲ್ಗಳು ಒಟ್ಟು assert ಸಂಖ್ಯೆಯನ್ನು ಕಡಿಮೆ ಮಾಡುವುದಿಲ್ಲ ಅಥವಾ `assert.ok(true)` ಪುನರುಕ್ತಿಗಳನ್ನು ಸೇರಿಸುವುದಿಲ್ಲ | ಹೌದು | -| `check:pr-evidence` | PR ವಿವರಣೆಯು ಬದಲಾವಣೆಗೆ ಸಂಬಂಧಿಸಿದ ಪರೀಕ್ಷೆ/VPS ಸಾಕ್ಷ್ಯವನ್ನು ಉಲ್ಲೇಖಿಸುತ್ತದೆ (PR ಗದ್ಯವನ್ನು grep ಮಾಡುವ ಮೂಲಕ ಕಠಿಣ ನಿಯಮ #18 ಅನ್ನು ಯಾಂತ್ರೀಕರಿಸುತ್ತದೆ — ದುರ್ಬಲವಾಗಿದೆ, Backlog ನೋಡಿ) | ಹೌದು | +| ಸ್ಕ್ರಿಪ್ಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ನಿರ್ಬಂಧಿಸುವಿಕೆ | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, ಅಥವಾ `bin/` ನಲ್ಲಿನ ಪ್ರೊಡಕ್ಷನ್ ಕೋಡ್ ಅನ್ನು ಬದಲಾಯಿಸುವ PRಗಳು ಪರೀಕ್ಷೆಗಳನ್ನು ಒಳಗೊಂಡಿರಬೇಕು ಅಥವಾ ನವೀಕರಿಸಬೇಕು (ಕಠಿಣ ನಿಯಮ #8) | ಹೌದು | +| `check:test-masking` | ಬದಲಾದ ಪರೀಕ್ಷಾ ಫೈಲ್ಗಳು ನಿವ್ವಳ ಅಸರ್ಶನ್ ಎಣಿಕೆಯನ್ನು ಕಡಿಮೆ ಮಾಡುವುದಿಲ್ಲ ಅಥವಾ `assert.ok(true)` ನಿರರ್ಥಕ ಸತ್ಯಗಳನ್ನು ಸೇರಿಸುವುದಿಲ್ಲ | ಹೌದು | +| `check:pr-evidence` | PR ವಿವರಣೆಯು ಬದಲಾವಣೆಗಾಗಿ ಪರೀಕ್ಷೆ/VPS ಸಾಕ್ಷ್ಯವನ್ನು ಉಲ್ಲೇಖಿಸುತ್ತದೆ (PR ಪಠ್ಯದಲ್ಲಿ grep ಮಾಡುವ ಮೂಲಕ ಕಠಿಣ ನಿಯಮ #18 ಅನ್ನು ಯಾಂತ್ರೀಕರಿಸುತ್ತದೆ — ದುರ್ಬಲವಾಗಿದೆ, ಬ್ಯಾಕ್ಲಾಗ್ ನೋಡಿ) | ಹೌದು | ### ಜಾಬ್: `test-vitest` -`build` ನಂತರ ಚಲಿಸುತ್ತದೆ. ವಿಫಲವಾದರೆ ವಿಲೀನವನ್ನು ತಡೆಯುತ್ತದೆ. +`build` ನಂತರ ರನ್ ಆಗುತ್ತದೆ. ವಿಫಲವಾದರೆ ವಿಲೀನವನ್ನು ನಿರ್ಬಂಧಿಸುತ್ತದೆ. -| ಸೂಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | -| ---------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP ಸರ್ವರ್ (110 ಟೂಲ್ಗಳು), autoCombo, ಕ್ಯಾಶ್ — vitest ರನ್ನರ್ | ಹೌದು | -| `test:vitest:ui` | UI ಕಾಂಪೊನೆಂಟ್ ಪರೀಕ್ಷೆಗಳು — vitest ರನ್ನರ್ | **ತಡೆಯುತ್ತದೆ** — ಮೊದಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ವಿಫಲತೆಗಳನ್ನು `vitest.config.ts` ನಲ್ಲಿ ಸ್ಪಷ್ಟವಾಗಿ ಹೊರಗಿಡಲಾಗಿದೆ; ಹೊಸ ವಿಫಲತೆಗಳು ಜಾಬ್ ಅನ್ನು ವಿಫಲಗೊಳಿಸುತ್ತವೆ | +| ಸೂಟ್ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ನಿರ್ಬಂಧಿಸುವಿಕೆ | +| ---------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP ಸರ್ವರ್ (110 ಉಪಕರಣಗಳು), autoCombo, ಕ್ಯಾಶ್ — vitest ರನ್ನರ್ | ಹೌದು | +| `test:vitest:ui` | UI ಕಾಂಪೊನೆಂಟ್ ಪರೀಕ್ಷೆಗಳು — vitest ರನ್ನರ್ | **ನಿರ್ಬಂಧಿಸುವುದು** — ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ವೈಫಲ್ಯಗಳನ್ನು `vitest.config.ts` ನಲ್ಲಿ ಸ್ಪಷ್ಟವಾಗಿ ಹೊರಗಿಡಲಾಗಿದೆ; ಹೊಸ ವೈಫಲ್ಯಗಳು ಜಾಬ್ ಅನ್ನು ವಿಫಲಗೊಳಿಸುತ್ತವೆ | -### ರಾತ್ರಿಯ ವರ್ಕ್ಫ್ಲೋಗಳು (ವೇಳಾಪಟ್ಟಿ ಪ್ರಕಾರ, ಸಲಹಾತ್ಮಕ) +### ರಾತ್ರಿಯ ವರ್ಕ್ಫ್ಲೋಗಳು (ವೇಳಾಪಟ್ಟಿಯಂತೆ, ಸಲಹಾತ್ಮಕ) -ಇವು cron ವೇಳಾಪಟ್ಟಿಯಲ್ಲಿ (ಮತ್ತು `workflow_dispatch` ಮೂಲಕ) ಚಲಿಸುತ್ತವೆ, PRಗಳಲ್ಲಿ ಎಂದಿಗೂ ಚಲಿಸುವುದಿಲ್ಲ. ಎಲ್ಲವೂ ಸಲಹಾತ್ಮಕವಾಗಿವೆ. +ಇವು cron ವೇಳಾಪಟ್ಟಿಯಂತೆ (ಮತ್ತು `workflow_dispatch` ಮೂಲಕ) ರನ್ ಆಗುತ್ತವೆ, PRಗಳಲ್ಲಿ ಎಂದಿಗೂ ರನ್ ಆಗುವುದಿಲ್ಲ. ಎಲ್ಲವೂ ಸಲಹಾತ್ಮಕವಾಗಿವೆ. -| ವರ್ಕ್ಫ್ಲೋ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ತಡೆಯುವಿಕೆ | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | -| `nightly-property` | ಯಾದೃಚ್ಛಿಕ seed + ಹೆಚ್ಚಿನ ರನ್ ಸಂಖ್ಯೆಯೊಂದಿಗೆ fast-check ಪ್ರಾಪರ್ಟಿ ಪರೀಕ್ಷೆಗಳು | **ಸಲಹಾತ್ಮಕ** | -| `nightly-resilience` | ಹೀಪ್-ಬೆಳವಣಿಗೆ ಗೇಟ್, chaos fault-injection, k6 ಲೋಡ್/soak ಪರೀಕ್ಷೆ | **ಸಲಹಾತ್ಮಕ** | -| `nightly-llm-security` | promptfoo ಇಂಜೆಕ್ಷನ್ ಗಾರ್ಡ್ (ಬ್ಲಾಕ್ ಮೋಡ್) + garak ಪ್ರೋಬ್ಗಳು (ಪ್ರೊವೈಡರ್ ಸೀಕ್ರೆಟ್ ಇಲ್ಲದಿದ್ದರೆ ಬಿಟ್ಟುಬಿಡಲಾಗುತ್ತದೆ) | **ಸಲಹಾತ್ಮಕ** | -| `nightly-schemathesis` | `docs/openapi.yaml` ಬಳಸಿ ಲೈವ್ OmniRoute ವಿರುದ್ಧ OpenAPI ಕಾಂಟ್ರಾಕ್ಟ್ fuzzing (schemathesis) — ಸ್ಪೆಕ್ ಉಲ್ಲಂಘನೆಗಳು / ನಿರ್ವಹಿಸದ 500ಗಳನ್ನು ಬಹಿರಂಗಪಡಿಸುತ್ತದೆ (ಹಂತ 8 B.4) | **ಸಲಹಾತ್ಮಕ** | -| `nightly-mutation` | ವೇಗದ ಯೂನಿಟ್ ಲೇನ್ನಾದ್ಯಂತ Stryker mutation-testing ಸ್ಕೋರ್ — ಉಳಿದುಕೊಳ್ಳುವ mutants ದುರ್ಬಲ assertಗಳನ್ನು ಬಹಿರಂಗಪಡಿಸುತ್ತವೆ | **ಸಲಹಾತ್ಮಕ** | -| `nightly-compat` | ಬೆಂಬಲಿತ `engines.node` ಶ್ರೇಣಿಗಳಾದ್ಯಂತ Node ಎಂಜಿನ್ ಹೊಂದಾಣಿಕೆ ಮ್ಯಾಟ್ರಿಕ್ಸ್ | **ಸಲಹಾತ್ಮಕ** | +| ವರ್ಕ್ಫ್ಲೋ | ಮೌಲ್ಯೀಕರಿಸುವುದು | ನಿರ್ಬಂಧಿಸುವಿಕೆ | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | ಯಾದೃಚ್ಛಿಕ ಸೀಡ್ + ಹೆಚ್ಚಿನ ರನ್ ಎಣಿಕೆಯೊಂದಿಗೆ fast-check ಪ್ರಾಪರ್ಟಿ ಪರೀಕ್ಷೆಗಳು | **ಸಲಹಾತ್ಮಕ** | +| `nightly-resilience` | ಹೀಪ್-ಬೆಳವಣಿಗೆ ಗೇಟ್, ಕೆಯಾಸ್ ದೋಷ-ಇಂಜೆಕ್ಷನ್, k6 ಲೋಡ್/ಸೋಕ್ | **ಸಲಹಾತ್ಮಕ** | +| `nightly-llm-security` | promptfoo ಇಂಜೆಕ್ಷನ್ ಗಾರ್ಡ್ (ಬ್ಲಾಕ್ ಮೋಡ್) + garak ಪ್ರೋಬ್ಗಳು (ಪ್ರೊವೈಡರ್ ಸೀಕ್ರೆಟ್ ಇಲ್ಲದಿದ್ದರೆ ಬಿಟ್ಟುಬಿಡಲಾಗುತ್ತದೆ) | **ಸಲಹಾತ್ಮಕ** | +| `nightly-schemathesis` | `docs/openapi.yaml` ಬಳಸಿಕೊಂಡು ಲೈವ್ OmniRoute ವಿರುದ್ಧ OpenAPI ಕಾಂಟ್ರಾಕ್ಟ್ ಫಝಿಂಗ್ (schemathesis) — ಸ್ಪೆಕ್ ಉಲ್ಲಂಘನೆಗಳು / ನಿರ್ವಹಿಸದ 500 ದೋಷಗಳನ್ನು ಬಹಿರಂಗಪಡಿಸುತ್ತದೆ (ಹಂತ 8 B.4) | **ಸಲಹಾತ್ಮಕ** | +| `nightly-mutation` | ವೇಗದ ಯೂನಿಟ್ ಲೇನ್ನಾದ್ಯಂತ Stryker ಮ್ಯೂಟೇಶನ್-ಪರೀಕ್ಷಾ ಸ್ಕೋರ್ — ಉಳಿದುಕೊಳ್ಳುವ ಮ್ಯೂಟಂಟ್ಗಳು ದುರ್ಬಲ ಅಸರ್ಶನ್ಗಳನ್ನು ಬಹಿರಂಗಪಡಿಸುತ್ತವೆ | **ಸಲಹಾತ್ಮಕ** | +| `nightly-compat` | ಬೆಂಬಲಿತ `engines.node` ಶ್ರೇಣಿಗಳಾದ್ಯಂತ Node ಎಂಜಿನ್ ಹೊಂದಾಣಿಕೆ ಮ್ಯಾಟ್ರಿಕ್ಸ್ | **ಸಲಹಾತ್ಮಕ** | --- @@ -534,43 +534,43 @@ CI ಗೇಟ್ಗಳ ಜೊತೆಗೆ, OmniRoute ಒಂದು **ಆಯ್ಕ --- -## ತರ್ಕಬದ್ಧೀಕರಣ ಬಾಕಿಪಟ್ಟಿ (ROI ಪರಿಶೀಲನೆ — ಹಂತ 9 ಅಲೆ 3) +## ತರ್ಕಬದ್ಧಗೊಳಿಸುವಿಕೆ ಬಾಕಿಪಟ್ಟಿ (ROI ಪರಿಶೀಲನೆ — ಹಂತ 9 ಅಲೆ 3) -ಈ ದಾಸ್ತಾನನ್ನು 2026-06-17 ರಂದು `ci.yml` ಗೆ ಹೋಲಿಸಿ ಸಮನ್ವಯಗೊಳಿಸಲಾಗಿದೆ (ಹಿಂದಿನ ಆವೃತ್ತಿಯಲ್ಲಿ +ಈ ಪಟ್ಟಿಯನ್ನು 2026-06-17 ರಂದು `ci.yml` ಜೊತೆಗೆ ಹೊಂದಾಣಿಕೆ ಮಾಡಲಾಗಿದೆ (ಹಿಂದಿನ ಆವೃತ್ತಿಯಲ್ಲಿ `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` ಬಿಟ್ಟುಹೋಗಿದ್ದವು). ಸಮನ್ವಯಗೊಳಿಸಿದ ಸಮೂಹದ ROI ಪರಿಶೀಲನೆಯು -ಕೆಳಗಿನ ತರ್ಕಬದ್ಧೀಕರಣ ಅಭ್ಯರ್ಥಿಗಳನ್ನು ಗುರುತಿಸಿದೆ. **ವಿಲೀನಗಳು ಯಾಂತ್ರಿಕ CI -ಬದಲಾವಣೆಗಳಾಗಿವೆ; ಬದಲಾವಣೆಗಳು/ತೆಗೆದುಹಾಕುವಿಕೆಗಳು ಆಪರೇಟರ್ಗೆ ಮೀಸಲಾದ ನೀತಿ ನಿರ್ಧಾರಗಳಾಗಿವೆ.** ಕೆಳಗಿನ ಯಾವುದನ್ನೂ +`check:codeql-ratchet`, `check:pr-evidence` ಬಿಟ್ಟುಹೋಗಿದ್ದವು). ಹೊಂದಾಣಿಕೆ ಮಾಡಿದ ಸಮೂಹದ ROI ಪರಿಶೀಲನೆಯು +ಕೆಳಗಿನ ತರ್ಕಬದ್ಧಗೊಳಿಸುವಿಕೆ ಅಭ್ಯರ್ಥಿಗಳನ್ನು ಗುರುತಿಸಿದೆ. **ವಿಲೀನಗಳು ಯಾಂತ್ರಿಕ CI +ಬದಲಾವಣೆಗಳಾಗಿವೆ; ತಿರುಗಿಸುವಿಕೆಗಳು/ತೆಗೆದುಹಾಕುವಿಕೆಗಳು ನಿರ್ವಾಹಕರಿಗೆ ಮೀಸಲಾದ ನೀತಿ ನಿರ್ಧಾರಗಳಾಗಿವೆ.** ಕೆಳಗಿನ ಯಾವುದನ್ನೂ ಇನ್ನೂ ಅನ್ವಯಿಸಲಾಗಿಲ್ಲ. -**ಮೇಲೆ ದಾಖಲಿಸದೆ ಉಳಿದಿರುವವುಗಳೂ ಇವೆ** (ಸಲಹಾತ್ಮಕ, ಕಡಿಮೆ ಸಂಕೇತ): `docs-lint` job -(markdownlint + Vale, ಸಂಪೂರ್ಣ job `continue-on-error`) ಮತ್ತು ಸ್ವತಂತ್ರ ಸ್ಕ್ಯಾನರ್ workflowಗಳಾದ +**ಮೇಲೆ ದಾಖಲಿಸದಿರುವವುಗಳೂ ಇವೆ** (ಸಲಹಾತ್ಮಕ, ಕಡಿಮೆ ಸಂಕೇತ): `docs-lint` ಜಾಬ್ +(markdownlint + Vale, ಸಂಪೂರ್ಣ ಜಾಬ್ಗೆ `continue-on-error`) ಮತ್ತು ಸ್ವತಂತ್ರ ಸ್ಕ್ಯಾನರ್ ವರ್ಕ್ಫ್ಲೋಗಳಾದ `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` ಎಂಬುದು -`quality-baseline.json` ನಲ್ಲಿದೆ, ಆದರೆ `ci.yml` ನಲ್ಲಿನ ತಡೆಗಟ್ಟುವ ratchetಗೆ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ — ಈ metric -ಪ್ರಸ್ತುತ ಅನಾಥವಾಗಿದೆ. +`quality-baseline.json` ನಲ್ಲಿದೆ, ಆದರೆ `ci.yml` ನಲ್ಲಿ ತಡೆಹಿಡಿಯುವ ರ್ಯಾಚೆಟ್ಗೆ ಜೋಡಿಸಲಾಗಿಲ್ಲ — ಈ ಮೆಟ್ರಿಕ್ ಪ್ರಸ್ತುತ +ಅನಾಥವಾಗಿದೆ. ### ವಿಲೀನ / ನಕಲು ನಿವಾರಣೆ (ಯಾಂತ್ರಿಕ, ಕಡಿಮೆ ಅಪಾಯ) -ಪ್ರತಿ ಅಭ್ಯರ್ಥಿಯನ್ನೂ 2026-06-17 ರಲ್ಲಿದ್ದ ಸಕ್ರಿಯ gate ಸ್ಥಿತಿಗೆ ಹೋಲಿಸಿ ಮೌಲ್ಯೀಕರಿಸಲಾಗಿದೆ (ನಂಬಿ, ಆದರೆ ಪರಿಶೀಲಿಸಿ); -ಹಲವು "ಸ್ಪಷ್ಟ" ವಿಲೀನಗಳು ಅಡಗಿದ ತಾಂತ್ರಿಕ ಸಾಲವನ್ನು ಮರೆಮಾಡುತ್ತವೆ ಎಂದು ಕಂಡುಬಂದಿದ್ದು, ಅವು **ನೇರವಾಗಿ ಬದಲಿಸಬಹುದಾದವುಗಳಲ್ಲ**. - -- **`check:docs-sync` ಎರಡು ಬಾರಿ ಚಲಿಸುತ್ತದೆ** — `lint` job ನಲ್ಲಿ ಸ್ವತಂತ್ರವಾಗಿ ಮತ್ತು `check:docs-all` (`docs-sync-strict`) ಒಳಗೆ ಹಾಗೂ husky pre-commit hookನಲ್ಲಿ ಮತ್ತೊಮ್ಮೆ. ✅ **ಪೂರ್ಣಗೊಂಡಿದೆ** — ಸ್ವತಂತ್ರ `lint` invocation ತೆಗೆದುಹಾಕಲಾಗಿದೆ. -- **CVE ಸ್ಕ್ಯಾನಿಂಗ್** — ❌ **ಸ್ವಚ್ಛವಾದ ವಿಲೀನವಲ್ಲ.** ಯಾವುದೇ high/critical CVE ಕಂಡುಬಂದರೆ `audit:deps` ಕಡ್ಡಾಯವಾಗಿ ವಿಫಲವಾಗುತ್ತದೆ; `check:vuln-ratchet` (osv) baselineಗೆ ಹೋಲಿಸಿದಾಗ _ಹಿಂಜರಿತ_ ಉಂಟಾದರೆ ಮಾತ್ರ ವಿಫಲವಾಗುತ್ತದೆ (ಪ್ರಸ್ತುತ 1 MODERATE). ಅರ್ಥವ್ಯಾಪ್ತಿಗಳು ಭಿನ್ನವಾಗಿವೆ — `audit:deps` ತೆಗೆದುಹಾಕಿದರೆ ನಿರಪೇಕ್ಷ high/critical gate ಕಳೆದುಹೋಗುತ್ತದೆ. ಎರಡನ್ನೂ ಉಳಿಸಿಕೊಳ್ಳಿ. -- **Cycle ಪತ್ತೆ** — ❌ **ಸ್ವಚ್ಛವಾದ ವಿಲೀನವಲ್ಲ.** `check:circular-deps` (dpdm) **91 cycles** ವರದಿ ಮಾಡುತ್ತದೆ (ಅದಕ್ಕಾಗಿಯೇ ಅದು ಸಲಹಾತ್ಮಕವಾಗಿದೆ); ಅವುಗಳನ್ನು ಮೊದಲು ಪರಿಹರಿಸದೆ ಅದನ್ನು ತಡೆಗಟ್ಟುವ ಹಂತಕ್ಕೆ ಏರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ, ಮತ್ತು green ಆಗಿರುವ, ಆಯ್ದ `check:cycles` ಗಿಂತ ಇದರ ವ್ಯಾಪ್ತಿ ವಿಶಾಲವಾಗಿದೆ. `check:cycles` ಅನ್ನು ತಡೆಗಟ್ಟುವಂತೆಯೇ ಉಳಿಸಿಕೊಳ್ಳಿ; 91 dpdm cyclesಗಳನ್ನು ಪರಿಹರಿಸುವುದು ಪ್ರತ್ಯೇಕ ಬಾಕಿಪಟ್ಟಿಯಾಗಿದೆ. -- **ಸಂಕೀರ್ಣತೆ** — ✅ **ಪೂರ್ಣಗೊಂಡಿದೆ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ಒಂದೇ ESLint walk, ruleId ಪ್ರಕಾರ ಎಣಿಕೆ ಮಾಡುವುದರಿಂದ cyclomatic+max-lines ಮತ್ತು cognitive baselineಗಳು ಸ್ವತಂತ್ರವಾಗಿ ಉಳಿಯುತ್ತವೆ; ಸ್ಥಳೀಯ `--update` ಗಾಗಿ ಪ್ರತ್ಯೇಕ `check:complexity` / `check:cognitive-complexity` ಉಳಿದಿವೆ. -- **`/api` anti-hallucination** — ✅ **ಪೂರ್ಣಗೊಂಡಿದೆ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ಯ ಒಂದೇ FS inventory, openapi-routes + docs-symbols ಇನ್ನೂ ಸ್ವತಂತ್ರವಾಗಿ ವರದಿ ಮಾಡುತ್ತವೆ; ಸ್ಥಳೀಯ runಗಳಿಗಾಗಿ ಪ್ರತ್ಯೇಕವಾದವುಗಳು ಉಳಿದಿವೆ. -- **`check:node-runtime` 11 jobಗಳಲ್ಲಿ ಚಲಿಸುತ್ತದೆ** — ⚠️ **ಕಡಿಮೆ ROI.** ಪ್ರತಿಯೊಂದೂ ಪ್ರತ್ಯೇಕ runner ಆಗಿದ್ದು check <1s ತೆಗೆದುಕೊಳ್ಳುತ್ತದೆ; ಅಗ್ಗದ ಪ್ರತಿ-job guard ಅನ್ನು ಕಳೆದುಕೊಳ್ಳುವುದಕ್ಕೆ ಪ್ರತಿಯಾಗಿ ಒಟ್ಟು ಉಳಿತಾಯ ~10s. ಈ ಬದಲಾವಣೆಯ ಗೊಂದಲಕ್ಕೆ ತಕ್ಕಷ್ಟು ಮೌಲ್ಯವಿಲ್ಲ. -- **CI lintನಲ್ಲಿನ `typecheck:noimplicit:core`** — ✅ **lint jobನಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆ** (ಇದು ಸಲಹಾತ್ಮಕ `continue-on-error` ಆಗಿತ್ತು); ತಡೆಗಟ್ಟುವ type surface ಎಂದರೆ `typecheck:core` + `check:type-coverage`. ಸ್ಥಳೀಯ script ಉಳಿಸಿಕೊಳ್ಳಲಾಗಿದೆ. - -### ಬದಲಿಸಿ / ನಿರ್ಧರಿಸಿ (ಆಪರೇಟರ್ ನೀತಿ) - -- `check:openapi-security-tiers` (ಸಲಹಾತ್ಮಕ) — ❌ **ನೇರವಾಗಿ ಬದಲಿಸಲಾಗುವುದಿಲ್ಲ.** ಇದು 0 ನೊಂದಿಗೆ ನಿರ್ಗಮಿಸುತ್ತದೆ, ಆದರೆ `LOCAL_ONLY_API_PREFIXES` ಅಡಿಯಲ್ಲಿರುವ ಹಲವಾರು `traffic-inspector` routeಗಳಲ್ಲಿ `x-loopback-only: true` annotation ಇಲ್ಲವೆಂದು ಎಚ್ಚರಿಸುತ್ತದೆ. ಅದನ್ನು ಜಾರಿಗೊಳಿಸಲು ಮೊದಲು ಆ annotationಗಳನ್ನು `openapi.yaml` ಗೆ ಸೇರಿಸಬೇಕು. -- `typecheck:noimplicit:core` (ಸಲಹಾತ್ಮಕ) — ತಡೆಗಟ್ಟುವ `check:type-coverage` ratchet ಇದನ್ನು ಬಹುಮಟ್ಟಿಗೆ ಒಳಗೊಳ್ಳುತ್ತದೆ. ಇದನ್ನು ratchetಗೆ ಬದಲಿಸಿ ಅಥವಾ ಅನಗತ್ಯವಾಗಿ ಮರುಕಳಿಸುವ ಎರಡನೇ `tsc` pass ಅನ್ನು ತೆಗೆದುಹಾಕಿ. -- `test:vitest:ui` (ಈಗ **ತಡೆಗಟ್ಟುವಂತಿದೆ**) — ಮೊದಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿದ್ದ ವೈಫಲ್ಯಗಳನ್ನು `vitest.config.ts` ನಲ್ಲಿ `// #8618` tracking commentಗಳೊಂದಿಗೆ ಸ್ಪಷ್ಟವಾಗಿ ಹೊರಗಿಡಲಾಗಿದೆ; ಹೊಸ ವೈಫಲ್ಯಗಳು job ಅನ್ನು ವಿಫಲಗೊಳಿಸುತ್ತವೆ. -- `check:secrets` (gitleaks, ದಾಖಲಿಸಲಾದ 3 false-positiveಗಳಲ್ಲಿ ಸ್ಥಿರಗೊಳಿಸಲಾದ ತಡೆಗಟ್ಟುವ ratchet) — 0 ತಲುಪಲು ಆ 3 ಅನ್ನು allowlistಗೆ ಸೇರಿಸಿ, ಅಥವಾ ಸಲಹಾತ್ಮಕ ಹಂತಕ್ಕೆ ಇಳಿಸಿ. ಇದು GitHub native secret-scanning + `check:public-creds` ಜೊತೆ ಅತಿಕ್ರಮಿಸುತ್ತದೆ. -- `check:pr-evidence` (ತಡೆಗಟ್ಟುವ, PR-body ಗದ್ಯವನ್ನು greps ಮಾಡುತ್ತದೆ) — false-positive ಅಪಾಯ ಹೆಚ್ಚು; ಇದನ್ನು ತೆಗೆದುಹಾಕಿದರೆ Hard Rule #18 ಜಾರಿ ದುರ್ಬಲಗೊಳ್ಳುತ್ತದೆ, ಆದ್ದರಿಂದ ಇದು ನಿಜವಾದ ನೀತಿ ನಿರ್ಧಾರವಾಗಿದೆ. -- `semgrep` (ಸ್ವತಂತ್ರ ಸಲಹಾತ್ಮಕ) — OWASP familyಗಳಿಗೆ ಸಂಬಂಧಿಸಿದಂತೆ CodeQL ಜೊತೆ ಅತಿಕ್ರಮಿಸುತ್ತದೆ; ಅದರ baseline ಅನ್ನು ratchetಗೆ ಸಂಪರ್ಕಿಸಿ ಅಥವಾ ತೆಗೆದುಹಾಕಿ. +ಪ್ರತಿ ಅಭ್ಯರ್ಥಿಯನ್ನೂ 2026-06-17 ರಂದು ಲೈವ್ ಗೇಟ್ ಸ್ಥಿತಿಯ ವಿರುದ್ಧ ಮೌಲ್ಯೀಕರಿಸಲಾಗಿದೆ (ನಂಬಿ, ಆದರೆ ಪರಿಶೀಲಿಸಿ); +ಹಲವಾರು "ಸ್ಪಷ್ಟ" ವಿಲೀನಗಳು ಸಾಲವನ್ನು ಮರೆಮಾಡಿರುವುದು ಕಂಡುಬಂದಿದ್ದು, ಅವು **ಸ್ವಚ್ಛ ಪರ್ಯಾಯಗಳಲ್ಲ**. + +- **`check:docs-sync` ಎರಡು ಬಾರಿ ರನ್ ಆಗುತ್ತದೆ** — `lint` ಜಾಬ್ನಲ್ಲಿ ಸ್ವತಂತ್ರವಾಗಿ ಮತ್ತು `check:docs-all` (`docs-sync-strict`) ಒಳಗೆ ಮತ್ತೊಮ್ಮೆ, ಹಾಗೆಯೇ husky pre-commit hookನಲ್ಲಿಯೂ. ✅ **ಪೂರ್ಣಗೊಂಡಿದೆ** — ಸ್ವತಂತ್ರ `lint` ಆಹ್ವಾನವನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ. +- **CVE ಸ್ಕ್ಯಾನಿಂಗ್** — ❌ **ಸ್ವಚ್ಛ ವಿಲೀನವಲ್ಲ.** ಯಾವುದೇ high/critical CVE ಕಂಡುಬಂದರೆ `audit:deps` ಕಟ್ಟುನಿಟ್ಟಾಗಿ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ; `check:vuln-ratchet` (osv) baselineಗೆ ಹೋಲಿಸಿದಾಗ _ಹಿಂಜರಿತ_ ಉಂಟಾದರೆ ಮಾತ್ರ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ (ಪ್ರಸ್ತುತ 1 MODERATE). ಅರ್ಥವ್ಯವಸ್ಥೆಗಳು ವಿಭಿನ್ನವಾಗಿವೆ — `audit:deps` ತೆಗೆದುಹಾಕಿದರೆ ಸಂಪೂರ್ಣ high/critical ಗೇಟ್ ಕಳೆದುಹೋಗುತ್ತದೆ. ಎರಡನ್ನೂ ಉಳಿಸಿ. +- **ಚಕ್ರ ಪತ್ತೆ** — ✅ **ಪೂರ್ಣಗೊಂಡಿದೆ** (#15159 G-01/G-02). ಇಲ್ಲಿನ ಹಳೆಯ ಪಠ್ಯವು `check:cycles` ಅನ್ನು "ಹಸಿರು, ಆಯ್ದ" ಗೇಟ್ ಎಂದು ಕರೆದಿತ್ತು ಮತ್ತು `check:circular-deps` (dpdm) 91 ಚಕ್ರಗಳನ್ನು ವರದಿ ಮಾಡಿದ್ದರಿಂದ ಅದನ್ನು ತಡೆಹಿಡಿಯುವ ಗೇಟ್ ಆಗಿ ಉಳಿಸುವುದನ್ನು ಸಮರ್ಥಿಸಿತ್ತು. ಆ ಹಸಿರು ಸ್ಥಿತಿ **ತಪ್ಪು ಹಸಿರು** ಆಗಿತ್ತು: `check:cycles` 5 ಉಪಡೈರೆಕ್ಟರಿಗಳನ್ನು (450 ಫೈಲ್ಗಳು) ಸ್ಕ್ಯಾನ್ ಮಾಡುತ್ತಿತ್ತು, ಸ್ಥಿರ `import|export … from` ಮಾತ್ರ ಹೊಂದಿಸುತ್ತಿತ್ತು ಮತ್ತು ಪ್ರತಿಯೊಂದು `@/` ಹಾಗೂ `@omniroute/open-sse/` ಸ್ಪೆಸಿಫೈಯರ್ ಅನ್ನು ಕೈಬಿಡುತ್ತಿತ್ತು; ಆದ್ದರಿಂದ ರೆಪೊದಲ್ಲಿ ಪ್ರಾಬಲ್ಯ ಹೊಂದಿದ್ದ dynamic-import + alias ಚಕ್ರಗಳನ್ನು ಅದು ನೋಡಲಾಗುತ್ತಿರಲಿಲ್ಲ. ಸರಿಪಡಿಸಲಾಗಿದೆ: ಗೇಟ್ ಈಗ `src` + `open-sse` (5023 ಫೈಲ್ಗಳು) ಅನ್ನು ಸಂಚರಿಸುತ್ತದೆ, TypeScript AST ನಿಂದ ಸ್ಪೆಸಿಫೈಯರ್ಗಳನ್ನು ಸಂಗ್ರಹಿಸುತ್ತದೆ (ಹೀಗಾಗಿ `import("…")` ಎಣಿಕೆಯಾಗುತ್ತದೆ ಮತ್ತು type-position `typeof import("…")` ಎಣಿಕೆಯಾಗುವುದಿಲ್ಲ), ಹಾಗೂ tsconfig `paths` ಅನ್ನು ಪರಿಹರಿಸುತ್ತದೆ. ಇದು 0 ಅಲ್ಲ, **14** ಚಕ್ರಗಳನ್ನು ಕಂಡುಹಿಡಿಯುತ್ತದೆ. ಗೇಟ್ PRನಲ್ಲಿ ಮೊದಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ 14 ಚಕ್ರಗಳನ್ನು ಸರಿಪಡಿಸಲಾಗದ ಕಾರಣ, `check:cycles` ಈಗ ಒಂದು **ರ್ಯಾಚೆಟ್** ಆಗಿದೆ (`--ratchet`, `quality-baseline.json` ನಲ್ಲಿ ಗರಿಷ್ಠ ಮಿತಿ `metrics.cycles.value = 14`, `direction: down`) — ಇದು ಯಾವುದೇ _ಹಿಂಜರಿತವನ್ನು_ ತಡೆಯುತ್ತದೆ ಮತ್ತು ಎಣಿಕೆ ಕೇವಲ ಕಡಿಮೆಯಾಗಬಹುದು. CI `npm run check:cycles:ratchet` ಅನ್ನು ರನ್ ಮಾಡುತ್ತದೆ. ಕ್ರಮೇಣ ಕಡಿತವು **A-01** ಜೊತೆಗೆ ಸಾಗುತ್ತದೆ. ವಿಶಾಲವಾದ ಎರಡನೇ ಅಭಿಪ್ರಾಯವಾಗಿ `check:circular-deps` (dpdm) ಸಲಹಾತ್ಮಕವಾಗಿಯೇ ಉಳಿಯುತ್ತದೆ. +- **ಸಂಕೀರ್ಣತೆ** — ✅ **ಪೂರ್ಣಗೊಂಡಿದೆ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ಒಂದು ESLint ಸಂಚಾರ, ruleId ಪ್ರಕಾರ ಎಣಿಸುತ್ತದೆ; ಆದ್ದರಿಂದ cyclomatic+max-lines ಮತ್ತು cognitive baselineಗಳು ಸ್ವತಂತ್ರವಾಗಿರುತ್ತವೆ; ಪ್ರತ್ಯೇಕ `check:complexity` / `check:cognitive-complexity` ಸ್ಥಳೀಯ `--update` ಗಾಗಿ ಉಳಿಯುತ್ತವೆ. +- **`/api` ಭ್ರಮೆ-ವಿರೋಧಿ ಪರಿಶೀಲನೆ** — ✅ **ಪೂರ್ಣಗೊಂಡಿದೆ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ಯ ಒಂದು FS ಪಟ್ಟಿ; openapi-routes + docs-symbols ಇನ್ನೂ ಸ್ವತಂತ್ರವಾಗಿ ವರದಿ ಮಾಡುತ್ತವೆ; ಪ್ರತ್ಯೇಕ ಪರಿಶೀಲನೆಗಳು ಸ್ಥಳೀಯ ರನ್ಗಳಿಗಾಗಿ ಉಳಿಯುತ್ತವೆ. +- **`check:node-runtime` 11 ಜಾಬ್ಗಳಲ್ಲಿ ರನ್ ಆಗುತ್ತದೆ** — ⚠️ **ಕಡಿಮೆ ROI.** ಪ್ರತಿಯೊಂದೂ ಪ್ರತ್ಯೇಕ ರನ್ನರ್ ಆಗಿದ್ದು, ಪರಿಶೀಲನೆಗೆ <1s ಬೇಕಾಗುತ್ತದೆ; ಅಗ್ಗದ ಪ್ರತಿ-ಜಾಬ್ ಸುರಕ್ಷತಾ ಪರಿಶೀಲನೆಯನ್ನು ಕಳೆದುಕೊಳ್ಳುವ ಬದಲಾಗಿ ಒಟ್ಟು ಉಳಿತಾಯ ~10s ಮಾತ್ರ. ಈ ಬದಲಾವಣೆಯ ಗೊಂದಲಕ್ಕೆ ತಕ್ಕ ಮೌಲ್ಯವಿಲ್ಲ. +- **CI lintನಲ್ಲಿ `typecheck:noimplicit:core`** — ✅ **lint ಜಾಬ್ನಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆ** (ಇದು ಸಲಹಾತ್ಮಕ `continue-on-error` ಆಗಿತ್ತು); ತಡೆಹಿಡಿಯುವ ಪ್ರಕಾರ ಮೇಲ್ಮೈ `typecheck:core` + `check:type-coverage` ಆಗಿದೆ. ಸ್ಥಳೀಯ ಸ್ಕ್ರಿಪ್ಟ್ ಉಳಿಸಲಾಗಿದೆ. + +### ತಿರುಗಿಸಿ / ನಿರ್ಧರಿಸಿ (ನಿರ್ವಾಹಕರ ನೀತಿ) + +- `check:openapi-security-tiers` (ಸಲಹಾತ್ಮಕ) — ❌ **ನೇರವಾಗಿ ತಿರುಗಿಸಲಾಗುವುದಿಲ್ಲ.** ಇದು 0 ನೊಂದಿಗೆ ನಿರ್ಗಮಿಸುತ್ತದೆ, ಆದರೆ `LOCAL_ONLY_API_PREFIXES` ಅಡಿಯಲ್ಲಿರುವ ಹಲವಾರು `traffic-inspector` ರೂಟ್ಗಳಲ್ಲಿ `x-loopback-only: true` annotation ಇಲ್ಲವೆಂದು ಎಚ್ಚರಿಸುತ್ತದೆ. ಇದನ್ನು ಜಾರಿಗೊಳಿಸುವ ಮೊದಲು ಆ annotationಗಳನ್ನು `openapi.yaml` ಗೆ ಸೇರಿಸುವುದು ಅಗತ್ಯ. +- `typecheck:noimplicit:core` (ಸಲಹಾತ್ಮಕ) — ತಡೆಹಿಡಿಯುವ `check:type-coverage` ರ್ಯಾಚೆಟ್ ಇದನ್ನು ಬಹುಮಟ್ಟಿಗೆ ಒಳಗೊಂಡಿದೆ. ಇದನ್ನು ರ್ಯಾಚೆಟ್ಗೆ ತಿರುಗಿಸಿ ಅಥವಾ ಅನಗತ್ಯವಾದ ಎರಡನೇ `tsc` ಪಾಸ್ ಅನ್ನು ತೆಗೆದುಹಾಕಿ. +- `test:vitest:ui` (ಈಗ **ತಡೆಹಿಡಿಯುವ**) — ಮೊದಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ವೈಫಲ್ಯಗಳನ್ನು `vitest.config.ts` ನಲ್ಲಿ `// #8618` ಟ್ರ್ಯಾಕಿಂಗ್ ಕಾಮೆಂಟ್ಗಳೊಂದಿಗೆ ಸ್ಪಷ್ಟವಾಗಿ ಹೊರಗಿಡಲಾಗಿದೆ; ಹೊಸ ವೈಫಲ್ಯಗಳು ಜಾಬ್ ಅನ್ನು ವಿಫಲಗೊಳಿಸುತ್ತವೆ. +- `check:secrets` (gitleaks, ದಾಖಲೀಕರಿಸಿದ 3 false-positiveಗಳಲ್ಲಿ ಸ್ಥಗಿತಗೊಳಿಸಿದ ತಡೆಹಿಡಿಯುವ ರ್ಯಾಚೆಟ್) — 0 ತಲುಪಲು ಆ 3 ಅನ್ನು allowlistಗೆ ಸೇರಿಸಿ, ಅಥವಾ ಸಲಹಾತ್ಮಕ ಮಟ್ಟಕ್ಕೆ ಇಳಿಸಿ. ಇದು GitHubನ ಮೂಲ secret-scanning + `check:public-creds` ಜೊತೆ ಅತಿಕ್ರಮಿಸುತ್ತದೆ. +- `check:pr-evidence` (ತಡೆಹಿಡಿಯುವ, PR-body ಗದ್ಯವನ್ನು grep ಮಾಡುತ್ತದೆ) — false-positive ಅಪಾಯ ಹೆಚ್ಚು; ತೆಗೆದುಹಾಕಿದರೆ Hard Rule #18 ಜಾರಿ ದುರ್ಬಲಗೊಳ್ಳುತ್ತದೆ, ಆದ್ದರಿಂದ ಇದು ನಿಜವಾದ ನೀತಿ ನಿರ್ಧಾರವಾಗಿದೆ. +- `semgrep` (ಸಲಹಾತ್ಮಕ ಸ್ವತಂತ್ರ ಪರಿಶೀಲನೆ) — OWASP ವರ್ಗಗಳಿಗೆ ಸಂಬಂಧಿಸಿ CodeQL ಜೊತೆ ಅತಿಕ್ರಮಿಸುತ್ತದೆ; ಅದರ baseline ಅನ್ನು ರ್ಯಾಚೆಟ್ಗೆ ಜೋಡಿಸಿ ಅಥವಾ ತೆಗೆದುಹಾಕಿ. --- diff --git a/docs/i18n/kn/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/kn/docs/compression/COMPRESSION_GUIDE.md index 9e96c3f15985..95c53426ac96 100644 --- a/docs/i18n/kn/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/kn/docs/compression/COMPRESSION_GUIDE.md @@ -452,8 +452,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages` ಹೊಂದಿರುವ bodyಯಲ್ಲಿ, ವಿಷಯ ಬೈಪಾಸ್ (`shouldBypassCavemanOutputMode()` ಅನ್ನು `open-sse/services/compression/outputMode.ts` ನಲ್ಲಿ) ಕೊನೆಯ ಮೂರು ಸಂದೇಶಗಳನ್ನು ಪರಿಶೀಲಿಸುತ್ತದೆ ಮತ್ತು ಅವು ಅದರ ಭದ್ರತೆ, ಹಿಂತಿರುಗಿಸಲಾಗದ-ಕ್ರಿಯೆ, ಸ್ಪಷ್ಟೀಕರಣ ಅಥವಾ ಕ್ರಮ-ಸೂಕ್ಷ್ಮ ಕೀವರ್ಡ್ಗಳಿಗೆ ಹೊಂದಿದಾಗ -ಇಡೀ ಟರ್ನ್ಗೆ ಶೈಲಿಗಳನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತದೆ. ಡ್ಯಾಶ್ಬೋರ್ಡ್ನ -**Auto-Clarity Bypass** ಟಾಗಲ್ (`cavemanOutputMode.autoClarity`) ಯಾವ ಸ್ಥಿತಿಗೆ ಹೊಂದಿಸಲ್ಪಟ್ಟಿದೆಯೋ ಅದಕ್ಕೆ ಅನುಗುಣವಾಗಿ ಬೈಪಾಸ್ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ. +ಇಡೀ ಟರ್ನ್ಗೆ ಶೈಲಿಗಳನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತದೆ. ಡ್ಯಾಶ್ಬೋರ್ಡ್ನ **Auto-Clarity Bypass** ಟಾಗಲ್ (`cavemanOutputMode.autoClarity`) ಆನ್ ಆಗಿರುವಾಗ ಬೈಪಾಸ್ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ, ಇದು ಪೂರ್ವನಿಯೋಜಿತವಾಗಿದೆ; ಟಾಗಲ್ ಆಫ್ ಆಗಿರುವಾಗ ಆಯ್ದ ಶೈಲಿಗಳು ಆ ಟರ್ನ್ಗಳಲ್ಲಿಯೂ ಅನ್ವಯಿಸುತ್ತವೆ. ಬೈಪಾಸ್ ಟರ್ನ್ ಅನ್ನು ಮುಂದುವರಿಯಲು ಬಿಟ್ಟಾಗ, `placeSystemInstruction()` (ಅದೇ ಫೈಲ್), ಇದು ಹೊಸ `messages[0]` ಅನ್ನು ಎಂದಿಗೂ ರಚಿಸುವುದಿಲ್ಲ, ಬ್ಲಾಕ್ ಅನ್ನು ಕೆಳಗಿನವುಗಳಲ್ಲಿ ಮೊದಲು ಸಿಗುವುದರಲ್ಲಿ ಇರಿಸುತ್ತದೆ: diff --git a/docs/i18n/kn/docs/reference/ENVIRONMENT.md b/docs/i18n/kn/docs/reference/ENVIRONMENT.md index 32d79a8fb01d..14229482e1ed 100644 --- a/docs/i18n/kn/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/kn/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. ಅಗತ್ಯವಿರುವ ರಹಸ್ಯಗಳು -ಇವುಗಳನ್ನು ಮೊದಲ ಬಾರಿ ರನ್ ಮಾಡುವ ಮೊದಲು **ಕಡ್ಡಾಯವಾಗಿ** ಹೊಂದಿಸಬೇಕು. ಇವುಗಳಿಲ್ಲದೆ, ಅಪ್ಲಿಕೇಶನ್ ಪ್ರಾರಂಭವಾಗಲು ನಿರಾಕರಿಸುತ್ತದೆ ಅಥವಾ ಅಸುರಕ್ಷಿತ ಡೀಫಾಲ್ಟ್ಗಳೊಂದಿಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ. - -| ವೇರಿಯಬಲ್ | ಅಗತ್ಯವಿದೆ | ಡೀಫಾಲ್ಟ್ | ಮೂಲ ಫೈಲ್ | ವಿವರಣೆ | -| ---------------------------- | -------------------------- | --------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **ಹೌದು** | _(ಯಾವುದೂ ಇಲ್ಲ)_ | `src/lib/auth` | ಎಲ್ಲಾ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಸೆಷನ್ ಕುಕಿಗಳಿಗೆ (JWT) ಸಹಿ ಮಾಡುತ್ತದೆ/ಪರಿಶೀಲಿಸುತ್ತದೆ. `openssl rand -base64 48` ಬಳಸಿ ರಚಿಸಿ. | -| `API_KEY_SECRET` | **ಹೌದು** | _(ಯಾವುದೂ ಇಲ್ಲ)_ | `src/lib/db/apiKeys.ts` | SQLite ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯ ಸ್ಥಿತಿಯಲ್ಲಿರುವ API ಕೀ ಮೌಲ್ಯಗಳಿಗಾಗಿ AES ಎನ್ಕ್ರಿಪ್ಶನ್ ಕೀ. `openssl rand -hex 32` ಬಳಸಿ ರಚಿಸಿ. | -| `INITIAL_PASSWORD` | **ಹೌದು** | `CHANGEME` | ಬೂಟ್ಸ್ಟ್ರ್ಯಾಪ್ ಸ್ಕ್ರಿಪ್ಟ್ | ಆರಂಭಿಕ ನಿರ್ವಾಹಕ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಹೊಂದಿಸುತ್ತದೆ (`.env.example` ಡೀಫಾಲ್ಟ್ಗೆ ಹೊಂದಿಕೆಯಾಗುತ್ತದೆ — ಬದಲಾಯಿಸುವುದನ್ನು ಕಡ್ಡಾಯಗೊಳಿಸಲು ಇದನ್ನು ಸ್ಪಷ್ಟವಾಗಿಯೇ ಅಸುರಕ್ಷಿತವಾಗಿ ಇರಿಸಲಾಗಿದೆ). **ಮೊದಲ ಬಳಕೆಗೆ ಮೊದಲು ಬದಲಾಯಿಸಿ.** ಲಾಗಿನ್ ಮಾಡಿದ ನಂತರ, ಡ್ಯಾಶ್ಬೋರ್ಡ್ → ಸೆಟ್ಟಿಂಗ್ಗಳು → ಭದ್ರತೆ ಮೂಲಕ ಬದಲಾಯಿಸಿ. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **ಹೌದು** (ಪ್ರೊಡಕ್ಷನ್ನಲ್ಲಿ) | _(ಹೊಂದಿಸಿಲ್ಲ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ಆಂತರಿಕ Codex Responses WebSocket ಬ್ರಿಡ್ಜ್ಗಾಗಿ ಹಂಚಿಕೆಯ ರಹಸ್ಯ. Electron/ಬ್ರೌಸರ್ WS ರಿಲೇ ಮತ್ತು OmniRoute ನಡುವಿನ ಬ್ರಿಡ್ಜ್ ವಿನಂತಿಗಳನ್ನು ದೃಢೀಕರಿಸುತ್ತದೆ. ⚠️ **ಪ್ರೊಡಕ್ಷನ್ನಲ್ಲಿ ಅಗತ್ಯವಿದೆ — ಇದನ್ನು ಹೊಂದಿಸದಿದ್ದಾಗ, ಎಲ್ಲಾ WS ಬ್ರಿಡ್ಜ್ ವಿನಂತಿಗಳನ್ನು ತಿರಸ್ಕರಿಸಲಾಗುತ್ತದೆ.** `openssl rand -base64 32` ಬಳಸಿ ರಚಿಸಿ. | -| `OMNIROUTE_SW_BUILD_ID` | ಇಲ್ಲ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ಶೆಲ್ಗಾಗಿ ಸ್ಪಷ್ಟವಾದ ಸರ್ವಿಸ್-ವರ್ಕರ್ ಕ್ಯಾಶ್-ಬಸ್ಟಿಂಗ್ ಐಡಿ (#11779); ಪರಿಹಾರ ಸರಣಿಯಲ್ಲಿ ಮೊದಲನೆಯದು. | -| `SOURCE_VERSION` | ಇಲ್ಲ | _(ಹೊಂದಿಸಿಲ್ಲ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ಸರಣಿಯಲ್ಲಿ ಎರಡನೆಯದು — ಡಿಪ್ಲಾಯ್ ಮಾಡಲಾದ ಕಮಿಟ್ ಆಗಿ PaaS ಬಿಲ್ಡರ್ಗಳು (ಉದಾ. Heroku ಶೈಲಿ) ಹೊಂದಿಸುತ್ತವೆ. | -| `NEXT_PUBLIC_SW_BUILD_ID` | ಇಲ್ಲ | _(ವ್ಯುತ್ಪನ್ನ)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` ಅನ್ನು ನೋಂದಾಯಿಸಲು ಕ್ಲೈಂಟ್ ಬಳಸುವ ಬಿಲ್ಡ್-ಟೈಮ್ ಸಾರ್ವಜನಿಕ ಮೌಲ್ಯ; ಮೇಲಿನ ಎರಡರಿಂದ, ನಂತರ git SHA ಯಿಂದ ವ್ಯುತ್ಪನ್ನವಾಗುತ್ತದೆ. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | ಇಲ್ಲ (ಸ್ವಯಂಚಾಲಿತ) | _(ಪ್ರತಿ ಬೂಟ್ಗೆ ಸ್ವಯಂಚಾಲಿತ)_ | `src/server/authz/policies/management.ts` | ವಿಶ್ವಾಸಾರ್ಹ ಪಿಯರ್-IP ಸ್ಟ್ಯಾಂಪ್ OmniRoute ನ ಸ್ವಂತ HTTP ಸರ್ವರ್ನಿಂದ (`scripts/dev/peer-stamp.mjs`) ಬಂದಿದೆ ಎಂಬುದನ್ನು ಸಾಬೀತುಪಡಿಸುವ ಪ್ರತಿ-ಪ್ರಕ್ರಿಯೆಯ ರಹಸ್ಯ. ಸ್ಟ್ಯಾಂಪ್ ಈ ಟೋಕನ್ ಹೊಂದಿರುವಾಗ ಮಾತ್ರ authz ಮಿಡಲ್ವೇರ್ ವಿನಂತಿಯ ಸ್ಥಳೀಯತೆಯನ್ನು (LOCAL_ONLY ರೂಟ್ಗಳ ಲೂಪ್ಬ್ಯಾಕ್/LAN ಗೇಟಿಂಗ್) ನಂಬುತ್ತದೆ. ಪ್ರತಿ ಬೂಟ್ನಲ್ಲಿ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ರಚಿಸಲಾಗುತ್ತದೆ — ಹೊಂದಿಸದೆ ಬಿಡಿ; ಸ್ಟ್ಯಾಂಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲೇಬೇಕಾದ ಬಹು-ಪ್ರಕ್ರಿಯೆ ಸೆಟಪ್ಗಳಿಗೆ ಮಾತ್ರ ಅದನ್ನು ಸ್ಥಿರಗೊಳಿಸಿ. | -| `OMNIROUTE_TRUSTED_PROXIES` | ಇಲ್ಲ | _(ಹೊಂದಿಸಿಲ್ಲ)_ | `scripts/dev/peer-stamp.mjs` | IP ಅನುಮತಿ/ನಿರಾಕರಣೆ ಪಟ್ಟಿಯು ನಂಬಬಹುದಾದ reverse proxy ಗಳ `X-Forwarded-For` / `X-Real-IP` ಗಾಗಿ ಅಲ್ಪವಿರಾಮದಿಂದ ಬೇರ್ಪಡಿಸಿದ IP ಗಳು ಅಥವಾ CIDR ಶ್ರೇಣಿಗಳು; ಯಾವಾಗಲೂ ವಿಶ್ವಾಸಾರ್ಹವಾಗಿರುವ ಲೂಪ್ಬ್ಯಾಕ್, ಖಾಸಗಿ-ನೆಟ್ವರ್ಕ್ ವಿಳಾಸಗಳು ಮತ್ತು Cloudflare ಎಡ್ಜ್ಗಳಿಗೆ ಹೆಚ್ಚುವರಿಯಾಗಿ ಇವು ಅನ್ವಯಿಸುತ್ತವೆ. ಬೇರೆ ಯಾವುದೇ ಸಾರ್ವಜನಿಕ ವಿಳಾಸದಲ್ಲಿರುವ proxy ಗೆ ಮಾತ್ರ ಇದು ಅಗತ್ಯ; ಇಲ್ಲದಿದ್ದರೆ ಫಿಲ್ಟರ್ ಮೌಲ್ಯಮಾಪನ ಮಾಡುವುದು ಆ proxy ಯ ಸ್ವಂತ ವಿಳಾಸವನ್ನೇ. | +**ಹೌದು** ಎಂದು ಗುರುತಿಸಲಾದ, ಆದರೆ ಹೊಂದಿಸದ ರಹಸ್ಯಗಳು ಮೊದಲ ರನ್ನಲ್ಲಿ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ರಚನೆಯಾಗುತ್ತವೆ (`JWT_SECRET`, `API_KEY_SECRET`) ಮತ್ತು ಡೇಟಾ ಡೈರೆಕ್ಟರಿಯಲ್ಲಿನ `server.env` ಗೆ ಉಳಿಸಲ್ಪಡುತ್ತವೆ — ಆ ಡೈರೆಕ್ಟರಿ ತಾತ್ಕಾಲಿಕವಾಗಿದ್ದರೆ ಅವುಗಳನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ಹೊಂದಿಸಿ. ಇದಕ್ಕೆ ಹೊರತಾದದ್ದು `OMNIROUTE_WS_BRIDGE_SECRET`: ಇದನ್ನು `server.env` ಗೆ ಉಳಿಸಲಾಗುವುದಿಲ್ಲ — ಸರ್ವರ್ ಪ್ರವೇಶ ಬಿಂದುಗಳು ಪ್ರತಿ ಬೂಟ್ಗೆ ಒಂದು ತಾತ್ಕಾಲಿಕ ಮೌಲ್ಯವನ್ನು ರಚಿಸುತ್ತವೆ, ಅದನ್ನು ಪ್ರಕ್ರಿಯೆಯೊಳಗಿನ ಬ್ರಿಡ್ಜ್ ಮಾತ್ರ ಬಳಸಬಲ್ಲದು, ಆದ್ದರಿಂದ ಬಾಹ್ಯ ಬ್ರಿಡ್ಜ್ ಕ್ಲೈಂಟ್ಗಳಿಗೆ ಇದನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ಹೊಂದಿಸುವುದು ಅಗತ್ಯ. + +| ವೇರಿಯಬಲ್ | ಅಗತ್ಯವಿದೆ | ಡೀಫಾಲ್ಟ್ | ಮೂಲ ಫೈಲ್ | ವಿವರಣೆ | +| ---------------------------- | -------------------------- | --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **ಹೌದು** | _(ಯಾವುದೂ ಇಲ್ಲ)_ | `src/lib/auth` | ಎಲ್ಲಾ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಸೆಷನ್ ಕುಕಿಗಳಿಗೆ (JWT) ಸಹಿ ಮಾಡುತ್ತದೆ/ಪರಿಶೀಲಿಸುತ್ತದೆ. `openssl rand -base64 48` ಬಳಸಿ ರಚಿಸಿ. | +| `API_KEY_SECRET` | **ಹೌದು** | _(ಯಾವುದೂ ಇಲ್ಲ)_ | `src/lib/db/apiKeys.ts` | SQLite ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯ ಸ್ಥಿತಿಯಲ್ಲಿರುವ API ಕೀ ಮೌಲ್ಯಗಳಿಗಾಗಿ AES ಎನ್ಕ್ರಿಪ್ಶನ್ ಕೀ. `openssl rand -hex 32` ಬಳಸಿ ರಚಿಸಿ. | +| `INITIAL_PASSWORD` | ಇಲ್ಲ | _(ಹೊಂದಿಸಿಲ್ಲ)_ | `src/lib/auth/managementPassword.ts` | ಇನ್ನೂ ಯಾವುದೇ ಪಾಸ್ವರ್ಡ್ ಉಳಿಸಲ್ಪಟ್ಟಿರದೆ ಇರುವಾಗ ಪ್ರಾರಂಭದ ಸಮಯದಲ್ಲಿ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಹೊಂದಿಸುತ್ತದೆ; ಪಾಸ್ವರ್ಡ್ ಉಳಿಸಿದ ನಂತರ ಇದನ್ನು ನಿರ್ಲಕ್ಷಿಸಲಾಗುತ್ತದೆ. npm ಮತ್ತು ಸೋರ್ಸ್ ಸ್ಥಾಪನೆಗಳು `.env.example` ಅನ್ನು `.env` ಗೆ ನಕಲಿಸುತ್ತವೆ, ಆದ್ದರಿಂದ ಅವು ಅದರ ಸಾರ್ವಜನಿಕವಾಗಿ ತಿಳಿದ ಪ್ಲೇಸ್ಹೋಲ್ಡರ್ `CHANGEME` ನೊಂದಿಗೆ ಪ್ರಾರಂಭವಾಗುತ್ತವೆ: ಮೊದಲ ಬೂಟ್ಗೆ ಮೊದಲು ಅದನ್ನು ಬದಲಾಯಿಸಿ, ಅಥವಾ ಆ `.env` ನಲ್ಲಿ `INITIAL_PASSWORD=` (ಖಾಲಿ) ಎಂದು ಹೊಂದಿಸಿ, ಹೊಂದಿಸದ ಮೌಲ್ಯ (ಡಾಕರ್ ಇಮೇಜ್ನ ಡೀಫಾಲ್ಟ್) ಮಾಡುವಂತೆಯೇ, ಮೊದಲ ಭೇಟಿಯಲ್ಲಿ ಸೆಟಪ್ ವಿಝಾರ್ಡ್ನಲ್ಲಿ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ರಚಿಸಿ. ಉಳಿಸಿದ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಡ್ಯಾಶ್ಬೋರ್ಡ್ → ಸೆಟ್ಟಿಂಗ್ಗಳು → ಭದ್ರತೆ ಮೂಲಕ ಬದಲಾಯಿಸಿ, ಅಥವಾ `DATA_DIR` ಅನ್ನು ಸರ್ವರ್ನ ಡೇಟಾ ಡೈರೆಕ್ಟರಿಗೆ ಹೊಂದಿಸಿಕೊಂಡು `omniroute reset-password` (`node bin/reset-password.mjs` ಸೋರ್ಸ್ ಚೆಕ್ಔಟ್ನಲ್ಲಿ) ಅನ್ನು ಚಲಾಯಿಸಿ. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **ಹೌದು** (ಪ್ರೊಡಕ್ಷನ್ನಲ್ಲಿ) | _(ಹೊಂದಿಸಿಲ್ಲ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ಆಂತರಿಕ Codex Responses WebSocket ಬ್ರಿಡ್ಜ್ಗಾಗಿ ಹಂಚಿಕೆಯ ರಹಸ್ಯ. Electron/ಬ್ರೌಸರ್ WS ರಿಲೇ ಮತ್ತು OmniRoute ನಡುವಿನ ಬ್ರಿಡ್ಜ್ ವಿನಂತಿಗಳನ್ನು ದೃಢೀಕರಿಸುತ್ತದೆ. ⚠️ **ಪ್ರೊಡಕ್ಷನ್ನಲ್ಲಿ ಅಗತ್ಯವಿದೆ — ಇದನ್ನು ಹೊಂದಿಸದಿದ್ದಾಗ, ಎಲ್ಲಾ WS ಬ್ರಿಡ್ಜ್ ವಿನಂತಿಗಳನ್ನು ತಿರಸ್ಕರಿಸಲಾಗುತ್ತದೆ.** `openssl rand -base64 32` ಬಳಸಿ ರಚಿಸಿ. | +| `OMNIROUTE_SW_BUILD_ID` | ಇಲ್ಲ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ಶೆಲ್ಗಾಗಿ ಸ್ಪಷ್ಟವಾದ ಸರ್ವಿಸ್-ವರ್ಕರ್ ಕ್ಯಾಶ್-ಬಸ್ಟಿಂಗ್ ಐಡಿ (#11779); ಪರಿಹಾರ ಸರಣಿಯಲ್ಲಿ ಮೊದಲನೆಯದು. | +| `SOURCE_VERSION` | ಇಲ್ಲ | _(ಹೊಂದಿಸಿಲ್ಲ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ಸರಣಿಯಲ್ಲಿ ಎರಡನೆಯದು — ಡಿಪ್ಲಾಯ್ ಮಾಡಲಾದ ಕಮಿಟ್ ಆಗಿ PaaS ಬಿಲ್ಡರ್ಗಳು (ಉದಾ. Heroku ಶೈಲಿ) ಹೊಂದಿಸುತ್ತವೆ. | +| `NEXT_PUBLIC_SW_BUILD_ID` | ಇಲ್ಲ | _(ವ್ಯುತ್ಪನ್ನ)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` ಅನ್ನು ನೋಂದಾಯಿಸಲು ಕ್ಲೈಂಟ್ ಬಳಸುವ ಬಿಲ್ಡ್-ಟೈಮ್ ಸಾರ್ವಜನಿಕ ಮೌಲ್ಯ; ಮೇಲಿನ ಎರಡರಿಂದ, ನಂತರ git SHA ಯಿಂದ ವ್ಯುತ್ಪನ್ನವಾಗುತ್ತದೆ. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | ಇಲ್ಲ (ಸ್ವಯಂಚಾಲಿತ) | _(ಪ್ರತಿ ಬೂಟ್ಗೆ ಸ್ವಯಂಚಾಲಿತ)_ | `src/server/authz/policies/management.ts` | ವಿಶ್ವಾಸಾರ್ಹ ಪಿಯರ್-IP ಸ್ಟ್ಯಾಂಪ್ OmniRoute ನ ಸ್ವಂತ HTTP ಸರ್ವರ್ನಿಂದ (`scripts/dev/peer-stamp.mjs`) ಬಂದಿದೆ ಎಂಬುದನ್ನು ಸಾಬೀತುಪಡಿಸುವ ಪ್ರತಿ-ಪ್ರಕ್ರಿಯೆಯ ರಹಸ್ಯ. ಸ್ಟ್ಯಾಂಪ್ ಈ ಟೋಕನ್ ಹೊಂದಿರುವಾಗ ಮಾತ್ರ authz ಮಿಡಲ್ವೇರ್ ವಿನಂತಿಯ ಸ್ಥಳೀಯತೆಯನ್ನು (LOCAL_ONLY ರೂಟ್ಗಳ ಲೂಪ್ಬ್ಯಾಕ್/LAN ಗೇಟಿಂಗ್) ನಂಬುತ್ತದೆ. ಪ್ರತಿ ಬೂಟ್ನಲ್ಲಿ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ರಚಿಸಲಾಗುತ್ತದೆ — ಹೊಂದಿಸದೆ ಬಿಡಿ; ಸ್ಟ್ಯಾಂಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಲೇಬೇಕಾದ ಬಹು-ಪ್ರಕ್ರಿಯೆ ಸೆಟಪ್ಗಳಿಗೆ ಮಾತ್ರ ಅದನ್ನು ಸ್ಥಿರಗೊಳಿಸಿ. | +| `OMNIROUTE_TRUSTED_PROXIES` | ಇಲ್ಲ | _(ಹೊಂದಿಸಿಲ್ಲ)_ | `scripts/dev/peer-stamp.mjs` | IP ಅನುಮತಿ/ನಿರಾಕರಣೆ ಪಟ್ಟಿಯು ನಂಬಬಹುದಾದ reverse proxy ಗಳ `X-Forwarded-For` / `X-Real-IP` ಗಾಗಿ ಅಲ್ಪವಿರಾಮದಿಂದ ಬೇರ್ಪಡಿಸಿದ IP ಗಳು ಅಥವಾ CIDR ಶ್ರೇಣಿಗಳು; ಯಾವಾಗಲೂ ವಿಶ್ವಾಸಾರ್ಹವಾಗಿರುವ ಲೂಪ್ಬ್ಯಾಕ್, ಖಾಸಗಿ-ನೆಟ್ವರ್ಕ್ ವಿಳಾಸಗಳು ಮತ್ತು Cloudflare ಎಡ್ಜ್ಗಳಿಗೆ ಹೆಚ್ಚುವರಿಯಾಗಿ ಇವು ಅನ್ವಯಿಸುತ್ತವೆ. ಬೇರೆ ಯಾವುದೇ ಸಾರ್ವಜನಿಕ ವಿಳಾಸದಲ್ಲಿರುವ proxy ಗೆ ಮಾತ್ರ ಇದು ಅಗತ್ಯ; ಇಲ್ಲದಿದ್ದರೆ ಫಿಲ್ಟರ್ ಮೌಲ್ಯಮಾಪನ ಮಾಡುವುದು ಆ proxy ಯ ಸ್ವಂತ ವಿಳಾಸವನ್ನೇ. | ### ರಚನಾ ಕಮಾಂಡ್ಗಳು @@ -217,38 +217,38 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ## 4. ಭದ್ರತೆ ಮತ್ತು ದೃಢೀಕರಣ -| ವೇರಿಯಬಲ್ | ಡೀಫಾಲ್ಟ್ | ಮೂಲ ಫೈಲ್ | ವಿವರಣೆ | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ಯಂತ್ರದ ಫಿಂಗರ್ಪ್ರಿಂಟಿಂಗ್ಗಾಗಿ ಹಾರ್ಡ್ವೇರ್ ಗುರುತಿಸುವಿಕೆಗಳೊಂದಿಗೆ ಸಂಯೋಜಿಸಲಾದ ಸಾಲ್ಟ್. ಪ್ರತ್ಯೇಕತೆಗಾಗಿ ಪ್ರತಿ ನಿಯೋಜನೆಯಲ್ಲಿಯೂ ಇದನ್ನು ಬದಲಾಯಿಸಿ. | -| `OMNIROUTE_CLI_SALT` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ = ಪ್ರತಿ ಸ್ಥಾಪನೆಗೆ ಯಾದೃಚ್ಛಿಕ ಸಾಲ್ಟ್, `/cli-token-salt.json` ನಲ್ಲಿ ಉಳಿಸಲಾಗುತ್ತದೆ)_ | `src/lib/machineToken.ts` | ಸ್ಥಳೀಯ CLI ದೃಢೀಕರಣ ಟೋಕನ್ ಪಡೆಯಲು ಬಳಸುವ HMAC ಸಾಲ್ಟ್. ಈ ಮೌಲ್ಯವನ್ನು ಹೊಂದಿಸುವುದರಿಂದ ಯಂತ್ರದಲ್ಲಿರುವ ಎಲ್ಲಾ CLI ಟೋಕನ್ಗಳು ರೊಟೇಟ್ ಆಗುತ್ತವೆ ಮತ್ತು ಉಳಿಸಲಾದ ಸಾಲ್ಟ್ಗಿಂತ ಇದು ಯಾವಾಗಲೂ ಆದ್ಯತೆ ಪಡೆಯುತ್ತದೆ. `docs/security/CLI_TOKEN.md` ನೋಡಿ. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ಸೆಷನ್ ಕುಕಿಗಳಲ್ಲಿ `Secure` ಫ್ಲ್ಯಾಗ್ ಅನ್ನು ಹೊಂದಿಸುತ್ತದೆ. HTTPS ಹಿಂದೆ ಚಾಲನೆಯಲ್ಲಿರುವಾಗ ಇದು **`true` ಆಗಿರಲೇಬೇಕು**. | -| `REQUIRE_API_KEY` | `false` | API ಮಿಡಲ್ವೇರ್ | `true` ಆಗಿರುವಾಗ, ಎಲ್ಲಾ `/v1/*` ಪ್ರಾಕ್ಸಿ ವಿನಂತಿಗಳು ಮಾನ್ಯ API ಕೀಲಿಯನ್ನು ಒಳಗೊಂಡಿರಬೇಕು. **ಈ ಫ್ಲ್ಯಾಗ್ `GET /v1/models` ಅನ್ನು ನಿಯಂತ್ರಿಸುವುದಿಲ್ಲ**, ಅದು ಬದಲಾಗಿ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಲಾಗಿನ್ ಸ್ಥಿತಿಯನ್ನು (`requireAuthForModels`) ಅನುಸರಿಸುತ್ತದೆ — ಆದ್ದರಿಂದ `/v1/models` ನಿಂದ ಬರುವ `401`, ಇನ್ಫರೆನ್ಸ್ ರಕ್ಷಿತವಾಗಿದೆ ಎಂದು ಅರ್ಥವಲ್ಲ. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ನೋಡಿ. | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ಡ್ಯಾಶ್ಬೋರ್ಡ್ UI ನಲ್ಲಿ ಸಂಪೂರ್ಣ API ಕೀ ಮೌಲ್ಯಗಳನ್ನು ಬಹಿರಂಗಪಡಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಡ್ಯಾಶ್ಬೋರ್ಡ್ ವೈಶಿಷ್ಟ್ಯ ಫ್ಲ್ಯಾಗ್ಗಳಿಂದ ಸಂರಚಿಸಬಹುದು; ಹಂಚಿಕೆಯ ಇನ್ಸ್ಟಾನ್ಸ್ಗಳಲ್ಲಿ ಇದು ಭದ್ರತಾ ಅಪಾಯವಾಗಿದೆ. | -| `NO_LOG_API_KEY_IDS` | _(ಖಾಲಿ)_ | `src/lib/compliance/index.ts` | ವಿನಂತಿ ಲಾಗಿಂಗ್ ಅನ್ನು ಬಿಟ್ಟುಬಿಡುವ, ಅಲ್ಪವಿರಾಮದಿಂದ ಬೇರ್ಪಡಿಸಿದ API ಕೀ IDಗಳು (GDPR ಅನುಸರಣೆಗಾಗಿ). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ = ಮಿತಿಯಿಲ್ಲ)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` ಕಾಲಮ್ null ಆಗಿರುವ API ಕೀಗಳಿಗೆ ಅನ್ವಯಿಸುವ, ಪ್ರತಿ ದಿನದ ಪರ್ಯಾಯ ವಿನಂತಿ ಬಜೆಟ್. ಹೊಂದಿಸದಿದ್ದರೆ ಅಥವಾ ಖಾಲಿಯಾಗಿದ್ದರೆ: ಯಾವುದೇ ಪರೋಕ್ಷ ಮಿತಿಯಿಲ್ಲ (#2289, #11017). `0` ಕೂಡ ಅದೇ ಅರ್ಥ ಹೊಂದಿದೆ (ಮಿತಿಯಿಲ್ಲ). ಧನ ಪೂರ್ಣಾಂಕ N, N/day, 5N/week, 20N/month ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ. ದೋಷಪೂರಿತ ಖಾಲಿಯಲ್ಲದ ಮೌಲ್ಯಗಳು ಹಳೆಯ 1000/day, 5000/week, 20000/month ವಿಂಡೋಗಳಿಗೆ ಪರ್ಯಾಯವಾಗಿ ಮರಳುತ್ತವೆ. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ಅನುಮತಿಸಲಾದ ಗರಿಷ್ಠ ವಿನಂತಿ ಬಾಡಿ ಗಾತ್ರ. ಈ ಮಿತಿಯನ್ನು ಮೀರಿದ ಪೇಲೋಡ್ಗಳನ್ನು ತಿರಸ್ಕರಿಸುತ್ತದೆ. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ಈ ಮಿತಿಯಲ್ಲಿ ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಗಾತ್ರದಲ್ಲಿರುವ ನೈಜ ವಿನಂತಿ ಬಾಡಿಗಳು JSON ಪಾರ್ಸಿಂಗ್ಗಿಂತ ಮೊದಲು ಅಟಾಮಿಕ್ ಪ್ರಕ್ರಿಯೆ-ಸ್ಥಳೀಯ ಹೆವಿವೇಟ್ ಅಡ್ಮಿಷನ್ ಲೀಸ್ ಅನ್ನು ಪಡೆಯುತ್ತವೆ (BYTE ಮಾರ್ಗ, `POST /v1/responses` ಸೇರಿದಂತೆ). ರಚನೆ-ಭಾರಿತ ವಿನಂತಿಗಳಿಗೆ ಇರುವ ಅದೇ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ಆರೋಗ್ಯಕರ-ಹೆಡ್ರೂಮ್ ವಿನಾಯಿತಿ ಅನ್ವಯಿಸುತ್ತದೆ; ಆದರೂ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ಮರುಕಳಿಸದಂತೆ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ಮೂಲಕ ಇದು ಇನ್ನೂ ಮಿತಿಗೊಳಿಸಲಾಗಿದೆ. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ಮಿತಿಗೊಳಿಸಿದ ಒಳಸೇರಿಸುವಿಕೆಯ ಸಮಯದಲ್ಲಿ ಓದಲಾದ ಬೈಟ್ಗಳ ಆಧಾರದ ಮೇಲೆ ಜಾರಿಗೊಳಿಸಲಾದ ಚಾಟ್-ರೂಟ್ನ ಕಟ್ಟುನಿಟ್ಟಿನ ಮಿತಿ; `Content-Length` ಇಲ್ಲದಿರುವ, ಅಮಾನ್ಯವಾಗಿರುವ ಅಥವಾ ತಪ್ಪಾಗಿರುವ ವಿನಂತಿಗಳೂ ಇದರಲ್ಲಿ ಸೇರಿವೆ; ಮಿತಿ ಮೀರಿದರೆ `413` ಸ್ವೀಕರಿಸುತ್ತದೆ. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ — ವಿನಂತಿ-ಎಣಿಕೆ ಮಿತಿಯಿಲ್ಲ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ಫ್ಯಾನ್ಔಟ್:** ಈ ಹಳೆಯ ವಿನಂತಿ-ಎಣಿಕೆ ಮಿತಿ ಈಗ ಸ್ಪಷ್ಟವಾಗಿ ಹೊಂದಿಸಿದಾಗ ಮಾತ್ರ ಅನ್ವಯಿಸುತ್ತದೆ. ಇದನ್ನು ಹೊಂದಿಸದೆ ಬಿಟ್ಟರೆ (ಡೀಫಾಲ್ಟ್), ಹೆವಿವೇಟ್ ಚಾಟ್ ಪ್ರವೇಶವನ್ನು ಬದಲಾಗಿ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ನಿಯಂತ್ರಿಸುತ್ತದೆ — **ಒಂದೇ ಪ್ರಕ್ರಿಯೆಯಲ್ಲಿ** (ಒಂದು V8 ಹೀಪ್) ಪ್ರಕ್ರಿಯೆಯ ನೈಜ ಮೆಮೊರಿ ಗರಿಷ್ಠ ಮಿತಿಯಿಂದ ಗಾತ್ರ ನಿರ್ಧರಿಸಲಾದ, ಸ್ವಯಂ-ವ್ಯುತ್ಪನ್ನವಾದ ಬೈಟ್ ಬಜೆಟ್. ಒಂದರ ಮೇಲೊಂದು ಬರುವ ~750k-ಟೋಕನ್ `/v1/responses` ವಿನಂತಿಗಳು ~12 GiB ಹೀಪ್ಗಳನ್ನು ಸ್ಥಗಿತಗೊಳಿಸುತ್ತವೆ (#7849) — ಇದು **ಮೆಮೊರಿ-ಬಜೆಟ್** ಎಚ್ಚರಿಕೆ, ಉತ್ಪನ್ನದ ಕಟ್ಟುನಿಟ್ಟಿನ ಗರಿಷ್ಠ ಮಿತಿ 2 ಎಂಬುದಲ್ಲ. ಆರೋಗ್ಯಕರ ಪ್ರಕ್ರಿಯೆಯು (ಶೆಡ್ ಅನುಪಾತಕ್ಕಿಂತ ಕೆಳಗಿನ ಹೀಪ್) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ಮೂಲಕ ಇನ್ನಷ್ಟು ಸಮಕಾಲೀನ ದೀರ್ಘ `/v1/responses` ವಿನಂತಿಗಳನ್ನು ಸ್ವೀಕರಿಸಬಹುದು. ಹತ್ತಾರು ದೀರ್ಘ SSE ಕ್ಲೈಂಟ್ಗಳು (40–50) ಎಂಬುದು ಹೀಪ್ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ಗೆ ಸಂಬಂಧಿಸಿದ್ದು, “ಗರಿಷ್ಠ 2” ಎಂದಲ್ಲ. “ಹೋಸ್ಟ್ ಅನ್ನು ಬಳಸಲು” ಇದನ್ನು ವಿವೇಚನೆಯಿಲ್ಲದೆ ಹೆಚ್ಚಿಸುವುದರಿಂದ #7849 ಮತ್ತೆ ಉಂಟಾಗುತ್ತದೆ. **N ಸ್ವತಂತ್ರ `DATA_DIR`ಗಳೊಂದಿಗೆ** **ಹೀಪ್ಗಳನ್ನು** ಗುಣಿಸಿ (#11024); ಒಂದೇ SQLite ಫೈಲ್ನಲ್ಲಿ ಎಂದಿಗೂ `replicas>1` ಬಳಸಬೇಡಿ. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ಸ್ವಯಂ-ವ್ಯುತ್ಪನ್ನ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ಫ್ಯಾನ್ಔಟ್:** ಸ್ವಯಂ-ವ್ಯುತ್ಪನ್ನವಾದ ಒಳಸೇರಿಸುವಿಕೆ ಬೈಟ್ ಬಜೆಟ್ಗೆ ಓವರ್ರೈಡ್ (ಹೆಚ್ಚು ನಿರ್ಬಂಧಿತವಾದ V8/cgroup ಮೆಮೊರಿ ಗರಿಷ್ಠ ಮಿತಿಯ 25% ಅನ್ನು 8x ತಾತ್ಕಾಲಿಕ ವರ್ಧನೆಯಿಂದ ಭಾಗಿಸಲಾಗಿದೆ). ವ್ಯುತ್ಪನ್ನ ಮತ್ತು ಸ್ಪಷ್ಟ ಮೌಲ್ಯಗಳನ್ನು 8 MiB–2 GiB ವ್ಯಾಪ್ತಿಗೆ ಸೀಮಿತಗೊಳಿಸಲಾಗುತ್ತದೆ. ಪರಿಣಾಮಕಾರಿ ಬಜೆಟ್ಗಿಂತ ದೊಡ್ಡದಾದ ಬಾಡಿ ತಕ್ಷಣವೇ `413 body_exceeds_budget` ದೋಷದೊಂದಿಗೆ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ; ಪ್ರತ್ಯೇಕವಾಗಿ ಸೇವೆ ಒದಗಿಸಬಹುದಾದ ಬಾಡಿಗಳ ನಡುವಿನ ಸಂಪನ್ಮೂಲ ಪೈಪೋಟಿಗೆ ಮರುಪ್ರಯತ್ನಿಸಬಹುದಾದ `503` ದೊರೆಯುತ್ತದೆ. 40–50 ಸಮಕಾಲೀನ ದೀರ್ಘ SSE ಕ್ಲೈಂಟ್ಗಳು ಈ ಬಜೆಟ್ + ಹೀಪ್ಗೆ ಸಂಬಂಧಿಸಿದ್ದು, ಕಟ್ಟುನಿಟ್ಟಿನ “ಗರಿಷ್ಠ 2” ಎಂದಲ್ಲ. ಟ್ಯೂನ್ ಮಾಡುವ ಮೊದಲು `/api/monitoring/health` ನಲ್ಲಿ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ಓದಿ. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE ಮತ್ತು STRUCTURE ಹೆವಿವೇಟ್ ಪ್ರವೇಶಕ್ಕಾಗಿ ಹೀಪ್-ಒತ್ತಡದ ಶೆಡ್ ಅನುಪಾತ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ಮೀರಿದ ಸಮಕಾಲೀನ ಹೆವಿವೇಟ್ ವಿನಂತಿಯನ್ನು ಹೀಪ್ ಕೂಡ ಈ ಅನುಪಾತದಲ್ಲಿದ್ದಾಗ ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಾಗಿದ್ದಾಗ ಮಾತ್ರ ಮರುಪ್ರಯತ್ನಿಸಬಹುದಾದ `503` ಮೂಲಕ ಶೆಡ್ ಮಾಡಲಾಗುತ್ತದೆ; ಆರೋಗ್ಯಕರ ಹೀಪ್ನಲ್ಲಿ ಅದನ್ನು ಬದಲಾಗಿ ಆರೋಗ್ಯಕರ-ಹೆಡ್ರೂಮ್ ಮೂಲಕ ಸ್ವೀಕರಿಸಲಾಗುತ್ತದೆ. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ಡೀಫಾಲ್ಟ್ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE ಮತ್ತು BYTE **ಎರಡರಲ್ಲಿಯೂ** (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ಗಿಂತ ದೊಡ್ಡದಾದ ಅಥವಾ ಸಮನಾದ ಬಾಡಿಗಳೂ ಸೇರಿದಂತೆ) ಆರೋಗ್ಯಕರ-ಹೀಪ್ ವೇಗದ ಪಥಕ್ಕೆ (#10437) ಮಿತಿಗೊಳಿಸಿದ ಹೆಚ್ಚುವರಿ ಸಾಮರ್ಥ್ಯ. ಈ ಮಿತಿಯಿಲ್ಲದೆ, ಕಾರ್ಯನಿರತವಾದರೂ ಆರೋಗ್ಯಕರ ಹೀಪ್ ಹೊಂದಿರುವ ಪ್ರತಿಯೊಂದು ವಿನಂತಿಯೂ ಯಾವುದೇ ಗರಿಷ್ಠ ಮಿತಿಯಿಲ್ಲದೆ ಪ್ರವೇಶ ನಿಯಂತ್ರಣವನ್ನು ಬೈಪಾಸ್ ಮಾಡುತ್ತಿತ್ತು. ಆರೋಗ್ಯಕರ-ಹೀಪ್ ಪಥದ ಮೂಲಕ ಇಷ್ಟು ಸಮಕಾಲೀನ ಲೀಸ್ಗಳು ಸಕ್ರಿಯವಾದ ಬಳಿಕ, ಮುಂದಿನ ಕಾರ್ಯನಿರತ ವಿನಂತಿಗಳು ನೈಜ ಹೀಪ್ ಒತ್ತಡದ ಅಡಿಯಲ್ಲಿ ಬಳಸುವ ಅದೇ ಮಿತಿಗೊಳಿಸಿದ-ಕಾಯುವಿಕೆ/ಶೆಡ್ ಪಥಕ್ಕೆ ಸಾಗುತ್ತವೆ. `0` ಬೈಪಾಸ್ ಅನ್ನು ಸಂಪೂರ್ಣವಾಗಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ಚಾಟ್ ವಿನಂತಿಯ ಬಾಡಿ ಬೈಟ್ ಮಿತಿಗಿಂತ ಕೆಳಗಿದ್ದರೂ ಅದನ್ನು ಹೆವಿವೇಟ್ ಎಂದು ವರ್ಗೀಕರಿಸುವ ಸಂದೇಶಗಳ ಎಣಿಕೆ. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ಚಾಟ್ ವಿನಂತಿಯ ಬಾಡಿ ಬೈಟ್ ಮಿತಿಗಿಂತ ಕೆಳಗಿದ್ದರೂ ಅದನ್ನು ಹೆವಿವೇಟ್ ಎಂದು ವರ್ಗೀಕರಿಸುವ ಟೂಲ್ಗಳ ಎಣಿಕೆ. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ವಿನಂತಿಯನ್ನು ಹೆವಿವೇಟ್ ಎಂದು ವರ್ಗೀಕರಿಸುವ ಸಂಪ್ರದಾಯಬದ್ಧ ಸ್ಟ್ರಿಂಗ್-ಗಾತ್ರದ ಟೋಕನ್ ಅಂದಾಜು; ಇದು ಪ್ರವೇಶ-ವೆಚ್ಚದ ಪ್ರಾಕ್ಸಿಯೇ ಹೊರತು ಪೂರೈಕೆದಾರರ ಬಿಲ್ಲಿಂಗ್ ಟೋಕನೀಕರಣವಲ್ಲ. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ) | `src/shared/middleware/chatBodyAdmission.ts` | ಐಚ್ಛಿಕವಾಗಿ ಸಕ್ರಿಯಗೊಳಿಸಬಹುದಾದ ಚಾಟ್ ಇತಿಹಾಸ ಮಿತಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ: ಸಂದೇಶಗಳ ಎಣಿಕೆಯು ನಿಯೋಜನಾ ನೀತಿಯೇ ಹೊರತು ವಿನಂತಿಯ ಸಾರ್ವತ್ರಿಕ ಗುಣವಲ್ಲ; ಇಲ್ಲಿ ಮಿತಿಗೊಳಿಸಿದರೆ, ಸಂಕುಚನ ಪೈಪ್ಲೈನ್ ಸಂಭಾಷಣೆಗಳನ್ನು ಸೇವಿಸಬಹುದಾಗಿಸುವ ಮೊದಲೇ ಅವುಗಳನ್ನು ಅಂತಿಮ `413` ಮೂಲಕ ತಿರಸ್ಕರಿಸಲಾಗುತ್ತದೆ. ಹೀಪ್ ಬೆಳವಣಿಗೆಯನ್ನು `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ಮತ್ತು ಹೀಪ್-ಒತ್ತಡದ ಶೆಡ್ ಮಿತಿಗೊಳಿಸುತ್ತವೆ. ಕಟ್ಟುನಿಟ್ಟಿನ ಗರಿಷ್ಠ ಮಿತಿ ಅಗತ್ಯವಿರುವ ಮೆಮೊರಿ-ನಿರ್ಬಂಧಿತ ನಿಯೋಜನೆಗಳಲ್ಲಿ ಧನಾತ್ಮಕ ಮೌಲ್ಯವನ್ನು ಹೊಂದಿಸಿ; ನಂತರ ಮಿತಿ ಮೀರಿದರೆ ರಚನಾತ್ಮಕ compact-required `413` ದೊರೆಯುತ್ತದೆ. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ಸಂಪೂರ್ಣವಾಗಿ ಮೆಮೊರಿಯಲ್ಲಿ ಬಫರ್ ಮಾಡಲಾದ ಸ್ಟ್ರೀಮಿಂಗ್-ಅಲ್ಲದ ಅಪ್ಸ್ಟ್ರೀಮ್ ಪ್ರತಿಕ್ರಿಯೆಯ ಕಟ್ಟುನಿಟ್ಟಿನ ಮಿತಿ. ಇದನ್ನು ಮೀರಿದಾಗ ಅಪ್ಸ್ಟ್ರೀಮ್ ರೀಡರ್ ಅನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಹೀಪ್ ಖಾಲಿಯಾಗುವವರೆಗೆ ಮಿತಿಯಿಲ್ಲದ ಸ್ಟ್ರಿಂಗ್ ಬೆಳೆಯಲು ಬಿಡುವ ಬದಲು ವಿನಂತಿಯು ತ್ವರಿತವಾಗಿ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ಅಪ್ಸ್ಟ್ರೀಮ್ ಪ್ರತಿಕ್ರಿಯೆ ಹೆಡರ್ಗಳಿಂದ ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗುವ ಗರಿಷ್ಠ ವೈರ್ ಬೈಟ್ಗಳು. ಬಜೆಟ್ ಮೀರಿದಾಗ, ಸಾಮಾನ್ಯ ರಿವರ್ಸ್-ಪ್ರಾಕ್ಸಿ ಹೆಡರ್ ಮಿತಿಗಳೊಳಗೆ ಉಳಿಯಲು ಕಡಿಮೆ ಆದ್ಯತೆಯ ಹೆಡರ್ಗಳನ್ನು (ಉದಾ., ಕಸ್ಟಮ್ `x-codex-*`, `x-oai-request-id`) ಕೈಬಿಡಲಾಗುತ್ತದೆ. ಪ್ರತಿಕ್ರಿಯೆ ಹೆಡರ್ ಗಾತ್ರ ಹೆಚ್ಚಾಗುವ ವೆಚ್ಚದಲ್ಲಿ ಹೆಚ್ಚಿನ ಅಪ್ಸ್ಟ್ರೀಮ್ ಮೆಟಾಡೇಟಾವನ್ನು ಫಾರ್ವರ್ಡ್ ಮಾಡಲು ಇದನ್ನು ಹೆಚ್ಚಿನ ಮೌಲ್ಯಕ್ಕೆ ಹೊಂದಿಸಿ. | -| `CORS_ORIGIN` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ)_ | `src/server/cors/origins.ts` | ಹಳೆಯ ಏಕ-ಮೂಲ CORS ಅನುಮತಿ ಪಟ್ಟಿ. ಹೊಸ ನಿಯೋಜನೆಗಳಿಗೆ `CORS_ALLOWED_ORIGINS` ಅನ್ನು ಆದ್ಯತೆ ನೀಡಿ. CORS ಎಂಬುದು ಕ್ರಾಸ್-ಒರಿಜಿನ್ ಬ್ರೌಸರ್ API ಕ್ಲೈಂಟ್ಗಳಿಗೆ ಮಾತ್ರ; ದೃಢೀಕೃತ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಬರವಣಿಗೆಗಳು ಅದಕ್ಕೆ ಬದಲಾಗಿ ಅದೇ ಮೂಲದ ವಿನಂತಿಗಳೊಂದಿಗೆ ಸೆಷನ್ಗೆ ಬದ್ಧವಾದ CSRF ರಕ್ಷಣೆಯನ್ನು ಬಳಸುತ್ತವೆ. | -| `CORS_ALLOWED_ORIGINS` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ)_ | `src/server/cors/origins.ts` | ಅಲ್ಪವಿರಾಮದಿಂದ ಬೇರ್ಪಡಿಸಿದ CORS ಅನುಮತಿ ಪಟ್ಟಿ. `CORS_ALLOW_ALL=true` ಅನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ಕಾನ್ಫಿಗರ್ ಮಾಡದ ಹೊರತು ಯಾವುದೇ ವೈಲ್ಡ್ಕಾರ್ಡ್ ಕಳುಹಿಸಲಾಗುವುದಿಲ್ಲ. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ಯಾವುದೇ ಬ್ರೌಸರ್ `Origin` ಅನ್ನು ಪ್ರತಿಧ್ವನಿಸಲು ಅಭಿವೃದ್ಧಿಗಾಗಿ ಮಾತ್ರ ಇರುವ ತಪ್ಪಿಸಿಕೊಳ್ಳುವ ಮಾರ್ಗ. ಹಂಚಿಕೆಯ ಅಥವಾ ಪ್ರೊಡಕ್ಷನ್ ನಿಯೋಜನೆಗಳಲ್ಲಿ ಇದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬೇಡಿ. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | ಖಾಸಗಿ/ಲೂಪ್ಬ್ಯಾಕ್/ಲಿಂಕ್-ಲೋಕಲ್ IP ವ್ಯಾಪ್ತಿಗಳನ್ನು ಗುರಿಯಾಗಿಸುವ ಪ್ರೊವೈಡರ್ ಕರೆಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿ. ಪ್ರತ್ಯೇಕಿಸಲಾದ ಪರೀಕ್ಷಾ ಪರಿಸರಗಳಲ್ಲಿ ಮಾತ್ರ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | ಖಾಸಗಿ/ಸ್ಥಳೀಯ ನೆಟ್ವರ್ಕ್ಗಳನ್ನು (localhost, 192.168.x.x, 10.x.x.x, ಇತ್ಯಾದಿ) ಸೂಚಿಸುವ ಪ್ರೊವೈಡರ್ URLಗಳನ್ನು ಅನುಮತಿಸಿ. **ಸ್ವಯಂ-ಹೋಸ್ಟ್ ಮಾಡಿದ ಪ್ರೊವೈಡರ್ಗಳಿಗೆ ಅಗತ್ಯವಿದೆ** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). `false` ಆಗಿರುವಾಗ, ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಸ್ಥಳೀಯ URLಗಳ ಮೌಲ್ಯೀಕರಣವನ್ನು ತಿರಸ್ಕರಿಸುತ್ತದೆ. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | ಸ್ಥಳೀಯ/ಖಾಸಗಿ ವಿಳಾಸಗಳಲ್ಲಿ (127.0.0.1, localhost, LAN, ಖಾಸಗಿ ವ್ಯಾಪ್ತಿಗಳು) ಪ್ರೊವೈಡರ್ಗಳನ್ನು ಸೇರಿಸಲು/ಮೌಲ್ಯೀಕರಿಸಲು ಅನುಮತಿಸಿ — ಇದು ಪ್ರೊವೈಡರ್ ಮೌಲ್ಯೀಕರಣ ಪಥಕ್ಕೆ ಸೀಮಿತವಾಗಿದೆ. **ಡೀಫಾಲ್ಟ್ `true`** (ಸ್ಥಳೀಯಕ್ಕೆ-ಮೊದಲ-ಆದ್ಯತೆ); ಸಾರ್ವಜನಿಕ ವಿಳಾಸಗಳಿಗೆ ಮಾತ್ರ ಸೀಮಿತವಾದ ಕಟ್ಟುನಿಟ್ಟಿನ ನಿರ್ಬಂಧವನ್ನು ಜಾರಿಗೊಳಿಸಲು `false` ಗೆ ಹೊಂದಿಸಿ. ಕ್ಲೌಡ್-ಮೆಟಾಡೇಟಾ ಎಂಡ್ಪಾಯಿಂಟ್ಗಳು (169.254.169.254, metadata.google.internal) ಯಾವುದೇ ಸಂದರ್ಭದಲ್ಲಿಯೂ ನಿರ್ಬಂಧಿತವಾಗಿಯೇ ಇರುತ್ತವೆ. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ಮಾರ್ಗಗಳು (ಲಿಪ್ಯಂತರಗಳು, ಮಾತು, ಅನುವಾದಗಳು) localhost ಹೊರಗೆ ಹೋಸ್ಟ್ ಮಾಡಲಾದ OpenAI-ಹೊಂದಾಣಿಕೆಯ ಪ್ರೊವೈಡರ್ ನೋಡ್ ಅನ್ನು ಬಳಸಲು ಅನುಮತಿಸಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ನಿಷ್ಕ್ರಿಯವಾಗಿದೆ — ಆಡಿಯೊವನ್ನು ರಿಮೋಟ್ ಹೋಸ್ಟ್ಗೆ ರೂಟ್ ಮಾಡುವುದರಿಂದ ಎಗ್ರೆಸ್ ಗುರುತು ಬದಲಾಗುತ್ತದೆ ಮತ್ತು ಇದು ಆಪರೇಟರ್ನ ಸ್ಪಷ್ಟ ನಿರ್ಧಾರವಾಗಿರಬೇಕು. ಲೂಪ್ಬ್ಯಾಕ್/ಖಾಸಗಿ ನೋಡ್ಗಳು (localhost, 127.0.0.1, 172.16-31.x) ಯಾವಾಗಲೂ ಅನುಮತಿಸಲ್ಪಡುತ್ತವೆ ಮತ್ತು ಪರಿಣಾಮಕ್ಕೊಳಗಾಗುವುದಿಲ್ಲ. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (ಮತ್ತು ಮೆಮೊರಿ ಎಂಜಿನ್ನ ಲೂಪ್ಬ್ಯಾಕ್ ಮರುಶ್ರೇಯಾಂಕ ಹಂತ) localhost ಹೊರಗೆ ಹೋಸ್ಟ್ ಮಾಡಲಾದ OpenAI-ಹೊಂದಾಣಿಕೆಯ ಪ್ರೊವೈಡರ್ ನೋಡ್ ಅನ್ನು ಬಳಸಲು ಅನುಮತಿಸಿ — ಅಂದರೆ TEI, Infinity, vLLM, ಇತ್ಯಾದಿಗಳನ್ನು ಚಲಾಯಿಸುವ LAN ಯಂತ್ರ ಅಥವಾ Tailscale ಪೀರ್. ಡೀಫಾಲ್ಟ್ ಆಗಿ ನಿಷ್ಕ್ರಿಯವಾಗಿದೆ — ರಿಮೋಟ್ ಹೋಸ್ಟ್ಗೆ ರೂಟ್ ಮಾಡುವುದರಿಂದ ಎಗ್ರೆಸ್ ಗುರುತು ಬದಲಾಗುತ್ತದೆ ಮತ್ತು ಇದು ಆಪರೇಟರ್ನ ಸ್ಪಷ್ಟ ನಿರ್ಧಾರವಾಗಿರಬೇಕು. ಲೂಪ್ಬ್ಯಾಕ್ ನೋಡ್ಗಳು (localhost, 127.0.0.1, 172.16-31.x) ಯಾವಾಗಲೂ ಅನುಮತಿಸಲ್ಪಡುತ್ತವೆ ಮತ್ತು ಪರಿಣಾಮಕ್ಕೊಳಗಾಗುವುದಿಲ್ಲ. ರಿಮೋಟ್ ನೋಡ್ಗಳು ಪ್ರೊವೈಡರ್ನ ಔಟ್ಬೌಂಡ್ URL ನೀತಿಯನ್ನೂ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ಪೂರೈಸಬೇಕು; ಕ್ಲೌಡ್-ಮೆಟಾಡೇಟಾ ಹೋಸ್ಟ್ಗಳಿಗೆ ಎಂದಿಗೂ ರೂಟ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ಸಕ್ರಿಯವಾಗಿರುವಾಗ, ಬಳಕೆದಾರರು OIDC ಏಕ ಸೈನ್-ಆನ್ ಮೂಲಕ ಮಾತ್ರ ದೃಢೀಕರಿಸಿಕೊಳ್ಳುವಂತೆ ಪಾಸ್ವರ್ಡ್ ಲಾಗಿನ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ. ಸರಳ ಅಲಿಯಾಸ್ `OIDC_DISABLE_PASSWORD_LOGIN` ಅನ್ನೂ ಸ್ವೀಕರಿಸಲಾಗುತ್ತದೆ; ಅದೇ ಕೀಲಿಯ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ವೈಶಿಷ್ಟ್ಯ ಫ್ಲ್ಯಾಗ್ಗೆ ಆದ್ಯತೆ ಇರುತ್ತದೆ. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ನ ಸರಳ ಅಲಿಯಾಸ್ (#10889). | +| ವೇರಿಯಬಲ್ | ಡೀಫಾಲ್ಟ್ | ಮೂಲ ಫೈಲ್ | ವಿವರಣೆ | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ಯಂತ್ರದ ಫಿಂಗರ್ಪ್ರಿಂಟಿಂಗ್ಗಾಗಿ ಹಾರ್ಡ್ವೇರ್ ಗುರುತಿಸುವಿಕೆಗಳೊಂದಿಗೆ ಸಂಯೋಜಿಸಲಾದ ಸಾಲ್ಟ್. ಪ್ರತ್ಯೇಕತೆಗಾಗಿ ಪ್ರತಿ ನಿಯೋಜನೆಯಲ್ಲಿಯೂ ಇದನ್ನು ಬದಲಾಯಿಸಿ. | +| `OMNIROUTE_CLI_SALT` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ = ಪ್ರತಿ ಸ್ಥಾಪನೆಗೆ ಯಾದೃಚ್ಛಿಕ ಸಾಲ್ಟ್, `/cli-token-salt.json` ನಲ್ಲಿ ಉಳಿಸಲಾಗುತ್ತದೆ)_ | `src/lib/machineToken.ts` | ಸ್ಥಳೀಯ CLI ದೃಢೀಕರಣ ಟೋಕನ್ ಪಡೆಯಲು ಬಳಸುವ HMAC ಸಾಲ್ಟ್. ಈ ಮೌಲ್ಯವನ್ನು ಹೊಂದಿಸುವುದರಿಂದ ಯಂತ್ರದಲ್ಲಿರುವ ಎಲ್ಲಾ CLI ಟೋಕನ್ಗಳು ರೊಟೇಟ್ ಆಗುತ್ತವೆ ಮತ್ತು ಉಳಿಸಲಾದ ಸಾಲ್ಟ್ಗಿಂತ ಇದು ಯಾವಾಗಲೂ ಆದ್ಯತೆ ಪಡೆಯುತ್ತದೆ. `docs/security/CLI_TOKEN.md` ನೋಡಿ. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ಸೆಷನ್ ಕುಕಿಗಳಲ್ಲಿ `Secure` ಫ್ಲ್ಯಾಗ್ ಅನ್ನು ಹೊಂದಿಸುತ್ತದೆ. HTTPS ಹಿಂದೆ ಚಾಲನೆಯಲ್ಲಿರುವಾಗ ಇದು **`true` ಆಗಿರಲೇಬೇಕು**. | +| `REQUIRE_API_KEY` | `false` | API ಮಿಡಲ್ವೇರ್ | `true` ಆಗಿರುವಾಗ, ಎಲ್ಲಾ `/v1/*` ಪ್ರಾಕ್ಸಿ ವಿನಂತಿಗಳು ಮಾನ್ಯ API ಕೀಲಿಯನ್ನು ಒಳಗೊಂಡಿರಬೇಕು. **ಈ ಫ್ಲ್ಯಾಗ್ `GET /v1/models` ಅನ್ನು ನಿಯಂತ್ರಿಸುವುದಿಲ್ಲ**, ಅದು ಬದಲಾಗಿ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಲಾಗಿನ್ ಸ್ಥಿತಿಯನ್ನು (`requireAuthForModels`) ಅನುಸರಿಸುತ್ತದೆ — ಆದ್ದರಿಂದ `/v1/models` ನಿಂದ ಬರುವ `401`, ಇನ್ಫರೆನ್ಸ್ ರಕ್ಷಿತವಾಗಿದೆ ಎಂದು ಅರ್ಥವಲ್ಲ. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ನೋಡಿ. | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ಡ್ಯಾಶ್ಬೋರ್ಡ್ UI ನಲ್ಲಿ ಸಂಪೂರ್ಣ API ಕೀ ಮೌಲ್ಯಗಳನ್ನು ಬಹಿರಂಗಪಡಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಡ್ಯಾಶ್ಬೋರ್ಡ್ ವೈಶಿಷ್ಟ್ಯ ಫ್ಲ್ಯಾಗ್ಗಳಿಂದ ಸಂರಚಿಸಬಹುದು; ಹಂಚಿಕೆಯ ಇನ್ಸ್ಟಾನ್ಸ್ಗಳಲ್ಲಿ ಇದು ಭದ್ರತಾ ಅಪಾಯವಾಗಿದೆ. | +| `NO_LOG_API_KEY_IDS` | _(ಖಾಲಿ)_ | `src/lib/compliance/index.ts` | ವಿನಂತಿ ಲಾಗಿಂಗ್ ಅನ್ನು ಬಿಟ್ಟುಬಿಡುವ, ಅಲ್ಪವಿರಾಮದಿಂದ ಬೇರ್ಪಡಿಸಿದ API ಕೀ IDಗಳು (GDPR ಅನುಸರಣೆಗಾಗಿ). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ = ಮಿತಿಯಿಲ್ಲ)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` ಕಾಲಮ್ null ಆಗಿರುವ API ಕೀಗಳಿಗೆ ಅನ್ವಯಿಸುವ, ಪ್ರತಿ ದಿನದ ಪರ್ಯಾಯ ವಿನಂತಿ ಬಜೆಟ್. ಹೊಂದಿಸದಿದ್ದರೆ ಅಥವಾ ಖಾಲಿಯಾಗಿದ್ದರೆ: ಯಾವುದೇ ಪರೋಕ್ಷ ಮಿತಿಯಿಲ್ಲ (#2289, #11017). `0` ಕೂಡ ಅದೇ ಅರ್ಥ ಹೊಂದಿದೆ (ಮಿತಿಯಿಲ್ಲ). ಧನ ಪೂರ್ಣಾಂಕ N, N/day, 5N/week, 20N/month ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ. ದೋಷಪೂರಿತ ಖಾಲಿಯಲ್ಲದ ಮೌಲ್ಯಗಳು ಹಳೆಯ 1000/day, 5000/week, 20000/month ವಿಂಡೋಗಳಿಗೆ ಪರ್ಯಾಯವಾಗಿ ಮರಳುತ್ತವೆ. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ಅನುಮತಿಸಲಾದ ಗರಿಷ್ಠ ವಿನಂತಿ ಬಾಡಿ ಗಾತ್ರ. ಈ ಮಿತಿಯನ್ನು ಮೀರಿದ ಪೇಲೋಡ್ಗಳನ್ನು ತಿರಸ್ಕರಿಸುತ್ತದೆ. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ಈ ಮಿತಿಯಲ್ಲಿ ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಗಾತ್ರದಲ್ಲಿರುವ ನೈಜ ವಿನಂತಿ ಬಾಡಿಗಳು JSON ಪಾರ್ಸಿಂಗ್ಗಿಂತ ಮೊದಲು ಅಟಾಮಿಕ್ ಪ್ರಕ್ರಿಯೆ-ಸ್ಥಳೀಯ ಹೆವಿವೇಟ್ ಅಡ್ಮಿಷನ್ ಲೀಸ್ ಅನ್ನು ಪಡೆಯುತ್ತವೆ (BYTE ಮಾರ್ಗ, `POST /v1/responses` ಸೇರಿದಂತೆ). ರಚನೆ-ಭಾರಿತ ವಿನಂತಿಗಳಿಗೆ ಇರುವ ಅದೇ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ಆರೋಗ್ಯಕರ-ಹೆಡ್ರೂಮ್ ವಿನಾಯಿತಿ ಅನ್ವಯಿಸುತ್ತದೆ; ಆದರೂ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ಮರುಕಳಿಸದಂತೆ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ಮೂಲಕ ಇದು ಇನ್ನೂ ಮಿತಿಗೊಳಿಸಲಾಗಿದೆ. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ಮಿತಿಗೊಳಿಸಿದ ಒಳಸೇರಿಸುವಿಕೆಯ ಸಮಯದಲ್ಲಿ ಓದಲಾದ ಬೈಟ್ಗಳ ಆಧಾರದ ಮೇಲೆ ಜಾರಿಗೊಳಿಸಲಾದ ಚಾಟ್-ರೂಟ್ನ ಕಟ್ಟುನಿಟ್ಟಿನ ಮಿತಿ; `Content-Length` ಇಲ್ಲದಿರುವ, ಅಮಾನ್ಯವಾಗಿರುವ ಅಥವಾ ತಪ್ಪಾಗಿರುವ ವಿನಂತಿಗಳೂ ಇದರಲ್ಲಿ ಸೇರಿವೆ; ಮಿತಿ ಮೀರಿದರೆ `413` ಸ್ವೀಕರಿಸುತ್ತದೆ. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ — ವಿನಂತಿ-ಎಣಿಕೆ ಮಿತಿಯಿಲ್ಲ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ಫ್ಯಾನ್ಔಟ್:** ಈ ಹಳೆಯ ವಿನಂತಿ-ಎಣಿಕೆ ಮಿತಿ ಈಗ ಸ್ಪಷ್ಟವಾಗಿ ಹೊಂದಿಸಿದಾಗ ಮಾತ್ರ ಅನ್ವಯಿಸುತ್ತದೆ. ಇದನ್ನು ಹೊಂದಿಸದೆ ಬಿಟ್ಟರೆ (ಡೀಫಾಲ್ಟ್), ಹೆವಿವೇಟ್ ಚಾಟ್ ಪ್ರವೇಶವನ್ನು ಬದಲಾಗಿ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ನಿಯಂತ್ರಿಸುತ್ತದೆ — **ಒಂದೇ ಪ್ರಕ್ರಿಯೆಯಲ್ಲಿ** (ಒಂದು V8 ಹೀಪ್) ಪ್ರಕ್ರಿಯೆಯ ನೈಜ ಮೆಮೊರಿ ಗರಿಷ್ಠ ಮಿತಿಯಿಂದ ಗಾತ್ರ ನಿರ್ಧರಿಸಲಾದ, ಸ್ವಯಂ-ವ್ಯುತ್ಪನ್ನವಾದ ಬೈಟ್ ಬಜೆಟ್. ಒಂದರ ಮೇಲೊಂದು ಬರುವ ~750k-ಟೋಕನ್ `/v1/responses` ವಿನಂತಿಗಳು ~12 GiB ಹೀಪ್ಗಳನ್ನು ಸ್ಥಗಿತಗೊಳಿಸುತ್ತವೆ (#7849) — ಇದು **ಮೆಮೊರಿ-ಬಜೆಟ್** ಎಚ್ಚರಿಕೆ, ಉತ್ಪನ್ನದ ಕಟ್ಟುನಿಟ್ಟಿನ ಗರಿಷ್ಠ ಮಿತಿ 2 ಎಂಬುದಲ್ಲ. ಆರೋಗ್ಯಕರ ಪ್ರಕ್ರಿಯೆಯು (ಶೆಡ್ ಅನುಪಾತಕ್ಕಿಂತ ಕೆಳಗಿನ ಹೀಪ್) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ಮೂಲಕ ಇನ್ನಷ್ಟು ಸಮಕಾಲೀನ ದೀರ್ಘ `/v1/responses` ವಿನಂತಿಗಳನ್ನು ಸ್ವೀಕರಿಸಬಹುದು. ಹತ್ತಾರು ದೀರ್ಘ SSE ಕ್ಲೈಂಟ್ಗಳು (40–50) ಎಂಬುದು ಹೀಪ್ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ಗೆ ಸಂಬಂಧಿಸಿದ್ದು, “ಗರಿಷ್ಠ 2” ಎಂದಲ್ಲ. “ಹೋಸ್ಟ್ ಅನ್ನು ಬಳಸಲು” ಇದನ್ನು ವಿವೇಚನೆಯಿಲ್ಲದೆ ಹೆಚ್ಚಿಸುವುದರಿಂದ #7849 ಮತ್ತೆ ಉಂಟಾಗುತ್ತದೆ. **N ಸ್ವತಂತ್ರ `DATA_DIR`ಗಳೊಂದಿಗೆ** **ಹೀಪ್ಗಳನ್ನು** ಗುಣಿಸಿ (#11024); ಒಂದೇ SQLite ಫೈಲ್ನಲ್ಲಿ ಎಂದಿಗೂ `replicas>1` ಬಳಸಬೇಡಿ. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ಸ್ವಯಂ-ವ್ಯುತ್ಪನ್ನ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ಫ್ಯಾನ್ಔಟ್:** ಸ್ವಯಂ-ವ್ಯುತ್ಪನ್ನವಾದ ಒಳಸೇರಿಸುವಿಕೆ ಬೈಟ್ ಬಜೆಟ್ಗೆ ಓವರ್ರೈಡ್ (ಹೆಚ್ಚು ನಿರ್ಬಂಧಿತವಾದ V8/cgroup ಮೆಮೊರಿ ಗರಿಷ್ಠ ಮಿತಿಯ 25% ಅನ್ನು 8x ತಾತ್ಕಾಲಿಕ ವರ್ಧನೆಯಿಂದ ಭಾಗಿಸಲಾಗಿದೆ). ವ್ಯುತ್ಪನ್ನ ಮತ್ತು ಸ್ಪಷ್ಟ ಮೌಲ್ಯಗಳನ್ನು 8 MiB–2 GiB ವ್ಯಾಪ್ತಿಗೆ ಸೀಮಿತಗೊಳಿಸಲಾಗುತ್ತದೆ. ಪರಿಣಾಮಕಾರಿ ಬಜೆಟ್ಗಿಂತ ದೊಡ್ಡದಾದ ಬಾಡಿ ತಕ್ಷಣವೇ `413 body_exceeds_budget` ದೋಷದೊಂದಿಗೆ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ; ಪ್ರತ್ಯೇಕವಾಗಿ ಸೇವೆ ಒದಗಿಸಬಹುದಾದ ಬಾಡಿಗಳ ನಡುವಿನ ಸಂಪನ್ಮೂಲ ಪೈಪೋಟಿಗೆ ಮರುಪ್ರಯತ್ನಿಸಬಹುದಾದ `503` ದೊರೆಯುತ್ತದೆ. 40–50 ಸಮಕಾಲೀನ ದೀರ್ಘ SSE ಕ್ಲೈಂಟ್ಗಳು ಈ ಬಜೆಟ್ + ಹೀಪ್ಗೆ ಸಂಬಂಧಿಸಿದ್ದು, ಕಟ್ಟುನಿಟ್ಟಿನ “ಗರಿಷ್ಠ 2” ಎಂದಲ್ಲ. ಟ್ಯೂನ್ ಮಾಡುವ ಮೊದಲು `/api/monitoring/health` ನಲ್ಲಿ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ಓದಿ. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE ಮತ್ತು STRUCTURE ಹೆವಿವೇಟ್ ಪ್ರವೇಶಕ್ಕಾಗಿ ಹೀಪ್-ಒತ್ತಡದ ಶೆಡ್ ಅನುಪಾತ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ಮೀರಿದ ಸಮಕಾಲೀನ ಹೆವಿವೇಟ್ ವಿನಂತಿಯನ್ನು ಹೀಪ್ ಕೂಡ ಈ ಅನುಪಾತದಲ್ಲಿದ್ದಾಗ ಅಥವಾ ಅದಕ್ಕಿಂತ ಹೆಚ್ಚಾಗಿದ್ದಾಗ ಮಾತ್ರ ಮರುಪ್ರಯತ್ನಿಸಬಹುದಾದ `503` ಮೂಲಕ ಶೆಡ್ ಮಾಡಲಾಗುತ್ತದೆ; ಆರೋಗ್ಯಕರ ಹೀಪ್ನಲ್ಲಿ ಅದನ್ನು ಬದಲಾಗಿ ಆರೋಗ್ಯಕರ-ಹೆಡ್ರೂಮ್ ಮೂಲಕ ಸ್ವೀಕರಿಸಲಾಗುತ್ತದೆ. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ಡೀಫಾಲ್ಟ್ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE ಮತ್ತು BYTE **ಎರಡರಲ್ಲಿಯೂ** (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ಗಿಂತ ದೊಡ್ಡದಾದ ಅಥವಾ ಸಮನಾದ ಬಾಡಿಗಳೂ ಸೇರಿದಂತೆ) ಆರೋಗ್ಯಕರ-ಹೀಪ್ ವೇಗದ ಪಥಕ್ಕೆ (#10437) ಮಿತಿಗೊಳಿಸಿದ ಹೆಚ್ಚುವರಿ ಸಾಮರ್ಥ್ಯ. ಈ ಮಿತಿಯಿಲ್ಲದೆ, ಕಾರ್ಯನಿರತವಾದರೂ ಆರೋಗ್ಯಕರ ಹೀಪ್ ಹೊಂದಿರುವ ಪ್ರತಿಯೊಂದು ವಿನಂತಿಯೂ ಯಾವುದೇ ಗರಿಷ್ಠ ಮಿತಿಯಿಲ್ಲದೆ ಪ್ರವೇಶ ನಿಯಂತ್ರಣವನ್ನು ಬೈಪಾಸ್ ಮಾಡುತ್ತಿತ್ತು. ಆರೋಗ್ಯಕರ-ಹೀಪ್ ಪಥದ ಮೂಲಕ ಇಷ್ಟು ಸಮಕಾಲೀನ ಲೀಸ್ಗಳು ಸಕ್ರಿಯವಾದ ಬಳಿಕ, ಮುಂದಿನ ಕಾರ್ಯನಿರತ ವಿನಂತಿಗಳು ನೈಜ ಹೀಪ್ ಒತ್ತಡದ ಅಡಿಯಲ್ಲಿ ಬಳಸುವ ಅದೇ ಮಿತಿಗೊಳಿಸಿದ-ಕಾಯುವಿಕೆ/ಶೆಡ್ ಪಥಕ್ಕೆ ಸಾಗುತ್ತವೆ. `0` ಬೈಪಾಸ್ ಅನ್ನು ಸಂಪೂರ್ಣವಾಗಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ಚಾಟ್ ವಿನಂತಿಯ ಬಾಡಿ ಬೈಟ್ ಮಿತಿಗಿಂತ ಕೆಳಗಿದ್ದರೂ ಅದನ್ನು ಹೆವಿವೇಟ್ ಎಂದು ವರ್ಗೀಕರಿಸುವ ಸಂದೇಶಗಳ ಎಣಿಕೆ. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ಚಾಟ್ ವಿನಂತಿಯ ಬಾಡಿ ಬೈಟ್ ಮಿತಿಗಿಂತ ಕೆಳಗಿದ್ದರೂ ಅದನ್ನು ಹೆವಿವೇಟ್ ಎಂದು ವರ್ಗೀಕರಿಸುವ ಟೂಲ್ಗಳ ಎಣಿಕೆ. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ವಿನಂತಿಯನ್ನು ಹೆವಿವೇಟ್ ಎಂದು ವರ್ಗೀಕರಿಸುವ ಸಂಪ್ರದಾಯಬದ್ಧ ಸ್ಟ್ರಿಂಗ್-ಗಾತ್ರದ ಟೋಕನ್ ಅಂದಾಜು; ಇದು ಪ್ರವೇಶ-ವೆಚ್ಚದ ಪ್ರಾಕ್ಸಿಯೇ ಹೊರತು ಪೂರೈಕೆದಾರರ ಬಿಲ್ಲಿಂಗ್ ಟೋಕನೀಕರಣವಲ್ಲ. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ) | `src/shared/middleware/chatBodyAdmission.ts` | ಐಚ್ಛಿಕವಾಗಿ ಸಕ್ರಿಯಗೊಳಿಸಬಹುದಾದ ಚಾಟ್ ಇತಿಹಾಸ ಮಿತಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ: ಸಂದೇಶಗಳ ಎಣಿಕೆಯು ನಿಯೋಜನಾ ನೀತಿಯೇ ಹೊರತು ವಿನಂತಿಯ ಸಾರ್ವತ್ರಿಕ ಗುಣವಲ್ಲ; ಇಲ್ಲಿ ಮಿತಿಗೊಳಿಸಿದರೆ, ಸಂಕುಚನ ಪೈಪ್ಲೈನ್ ಸಂಭಾಷಣೆಗಳನ್ನು ಸೇವಿಸಬಹುದಾಗಿಸುವ ಮೊದಲೇ ಅವುಗಳನ್ನು ಅಂತಿಮ `413` ಮೂಲಕ ತಿರಸ್ಕರಿಸಲಾಗುತ್ತದೆ. ಹೀಪ್ ಬೆಳವಣಿಗೆಯನ್ನು `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ಮತ್ತು ಹೀಪ್-ಒತ್ತಡದ ಶೆಡ್ ಮಿತಿಗೊಳಿಸುತ್ತವೆ. ಕಟ್ಟುನಿಟ್ಟಿನ ಗರಿಷ್ಠ ಮಿತಿ ಅಗತ್ಯವಿರುವ ಮೆಮೊರಿ-ನಿರ್ಬಂಧಿತ ನಿಯೋಜನೆಗಳಲ್ಲಿ ಧನಾತ್ಮಕ ಮೌಲ್ಯವನ್ನು ಹೊಂದಿಸಿ; ನಂತರ ಮಿತಿ ಮೀರಿದರೆ ರಚನಾತ್ಮಕ compact-required `413` ದೊರೆಯುತ್ತದೆ. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ಸಂಪೂರ್ಣವಾಗಿ ಮೆಮೊರಿಯಲ್ಲಿ ಬಫರ್ ಮಾಡಲಾದ ಸ್ಟ್ರೀಮಿಂಗ್-ಅಲ್ಲದ ಅಪ್ಸ್ಟ್ರೀಮ್ ಪ್ರತಿಕ್ರಿಯೆಯ ಕಟ್ಟುನಿಟ್ಟಿನ ಮಿತಿ. ಇದನ್ನು ಮೀರಿದಾಗ ಅಪ್ಸ್ಟ್ರೀಮ್ ರೀಡರ್ ಅನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗುತ್ತದೆ ಮತ್ತು ಹೀಪ್ ಖಾಲಿಯಾಗುವವರೆಗೆ ಮಿತಿಯಿಲ್ಲದ ಸ್ಟ್ರಿಂಗ್ ಬೆಳೆಯಲು ಬಿಡುವ ಬದಲು ವಿನಂತಿಯು ತ್ವರಿತವಾಗಿ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ಅಪ್ಸ್ಟ್ರೀಮ್ ಪ್ರತಿಕ್ರಿಯೆ ಹೆಡರ್ಗಳಿಂದ ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗುವ ಗರಿಷ್ಠ ವೈರ್ ಬೈಟ್ಗಳು. ಬಜೆಟ್ ಮೀರಿದಾಗ, ಸಾಮಾನ್ಯ ರಿವರ್ಸ್-ಪ್ರಾಕ್ಸಿ ಹೆಡರ್ ಮಿತಿಗಳೊಳಗೆ ಉಳಿಯಲು ಕಡಿಮೆ ಆದ್ಯತೆಯ ಹೆಡರ್ಗಳನ್ನು (ಉದಾ., ಕಸ್ಟಮ್ `x-codex-*`, `x-oai-request-id`) ಕೈಬಿಡಲಾಗುತ್ತದೆ. ಪ್ರತಿಕ್ರಿಯೆ ಹೆಡರ್ ಗಾತ್ರ ಹೆಚ್ಚಾಗುವ ವೆಚ್ಚದಲ್ಲಿ ಹೆಚ್ಚಿನ ಅಪ್ಸ್ಟ್ರೀಮ್ ಮೆಟಾಡೇಟಾವನ್ನು ಫಾರ್ವರ್ಡ್ ಮಾಡಲು ಇದನ್ನು ಹೆಚ್ಚಿನ ಮೌಲ್ಯಕ್ಕೆ ಹೊಂದಿಸಿ. | +| `CORS_ORIGIN` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ)_ | `src/server/cors/origins.ts` | ಹಳೆಯ ಏಕ-ಮೂಲ CORS ಅನುಮತಿ ಪಟ್ಟಿ. ಹೊಸ ನಿಯೋಜನೆಗಳಿಗೆ `CORS_ALLOWED_ORIGINS` ಅನ್ನು ಆದ್ಯತೆ ನೀಡಿ. CORS ಎಂಬುದು ಕ್ರಾಸ್-ಒರಿಜಿನ್ ಬ್ರೌಸರ್ API ಕ್ಲೈಂಟ್ಗಳಿಗೆ ಮಾತ್ರ; ದೃಢೀಕೃತ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಬರವಣಿಗೆಗಳು ಅದಕ್ಕೆ ಬದಲಾಗಿ ಅದೇ ಮೂಲದ ವಿನಂತಿಗಳೊಂದಿಗೆ ಸೆಷನ್ಗೆ ಬದ್ಧವಾದ CSRF ರಕ್ಷಣೆಯನ್ನು ಬಳಸುತ್ತವೆ. | +| `CORS_ALLOWED_ORIGINS` | _(ಹೊಂದಿಸಲಾಗಿಲ್ಲ)_ | `src/server/cors/origins.ts` | ಅಲ್ಪವಿರಾಮದಿಂದ ಬೇರ್ಪಡಿಸಿದ CORS ಅನುಮತಿ ಪಟ್ಟಿ. `CORS_ALLOW_ALL=true` ಅನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ಕಾನ್ಫಿಗರ್ ಮಾಡದ ಹೊರತು ಯಾವುದೇ ವೈಲ್ಡ್ಕಾರ್ಡ್ ಕಳುಹಿಸಲಾಗುವುದಿಲ್ಲ. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ಯಾವುದೇ ಬ್ರೌಸರ್ `Origin` ಅನ್ನು ಪ್ರತಿಧ್ವನಿಸಲು ಅಭಿವೃದ್ಧಿಗಾಗಿ ಮಾತ್ರ ಇರುವ ತಪ್ಪಿಸಿಕೊಳ್ಳುವ ಮಾರ್ಗ. ಹಂಚಿಕೆಯ ಅಥವಾ ಪ್ರೊಡಕ್ಷನ್ ನಿಯೋಜನೆಗಳಲ್ಲಿ ಇದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬೇಡಿ. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ಪರಂಪರೆಯ (legacy) ಅಲಿಯಾಸ್. ಇದರ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಟಾಗಲ್ ಅನ್ನು (DB ಓವರ್ರೈಡ್) ಈ environment ಮೌಲ್ಯಕ್ಕೂ ಮೊದಲು ಓದಲಾಗುತ್ತದೆ; ಯಾವುದಾದರೂ ಒಂದರಲ್ಲಿ `false`, `0`, `no` ಅಥವಾ `off` ಇದ್ದರೆ, `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ಮಾಡುವಂತೆಯೇ ಹೋಸ್ಟ್ ಪರಿಶೀಲನೆಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ, ಆ ಫ್ಲ್ಯಾಗ್ನ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಟಾಗಲ್ ಆಫ್ ಆಗಿ ಉಳಿಸಲ್ಪಟ್ಟಿದ್ದರೂ ಸಹ; ಇತರ ಮೌಲ್ಯಗಳು ಗಾರ್ಡ್ ನಿರ್ಧಾರವನ್ನು ಕೆಳಗಿನ ಎರಡು ಫ್ಲ್ಯಾಗ್ಗಳಿಗೆ ಬಿಡುತ್ತವೆ. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | ಗಾರ್ಡ್ ಮೋಡ್ ಅನ್ನು ಅನುಸರಿಸುವ ಪ್ರೊವೈಡರ್ ಪಥಗಳಲ್ಲಿ — URL ಮೌಲ್ಯೀಕರಣ, ಮಾಡೆಲ್ ಡಿಸ್ಕವರಿ, ಪ್ರೊವೈಡರ್-ನೋಡ್ ಬೇಸ್ URLಗಳು (ರಿಮೋಟ್ ಮರುಶ್ರೇಯಾಂಕ ನೋಡ್ಗಳನ್ನು ಒಳಗೊಂಡು) ಮತ್ತು ಪ್ರಾಕ್ಸಿ-ಫಾಲ್ಬ್ಯಾಕ್ ಪರೀಕ್ಷೆ, ಜೊತೆಗೆ ರಿಮೋಟ್ ಚಿತ್ರ/ಮಾಧ್ಯಮ ಪಡೆಯುವಿಕೆಯಂತಹ ಇತರ ಪ್ರೊವೈಡರ್-ಸಂಬಂಧಿತ ಔಟ್ಬೌಂಡ್ ಪಥಗಳಲ್ಲಿ — ಔಟ್ಬೌಂಡ್ URL ಗಾರ್ಡ್ನ ಹೋಸ್ಟ್ ಪರಿಶೀಲನೆಗಳನ್ನು, ಕ್ಲೌಡ್-ಮೆಟಾಡೇಟಾ ನಿರ್ಬಂಧವನ್ನು ಒಳಗೊಂಡು (ಸಂಪೂರ್ಣ 169.254.0.0/16, metadata.google.internal), ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ. ಇದು ಖಾಸಗಿ webhook ಗುರಿಗಳನ್ನೂ ಅನುಮತಿಸುತ್ತದೆ, ಆದರೆ ಕ್ಲೌಡ್-ಮೆಟಾಡೇಟಾ/ಲಿಂಕ್-ಲೋಕಲ್ ಎಂಡ್ಪಾಯಿಂಟ್ಗಳು ಹೊರತುಪಡಿಸಿ — ಅವು ಈ ಫ್ಲ್ಯಾಗ್ ಆನ್ ಇರುವಾಗಲೂ ನಿರ್ಬಂಧಿತವಾಗಿಯೇ ಇರುತ್ತವೆ. `BaseExecutor.execute()` ಮೂಲಕ ಕಳುಹಿಸಲಾದ ಚಾಟ್ ವಿನಂತಿಗಳು ತಮ್ಮ ಕ್ಲೌಡ್-ಮೆಟಾಡೇಟಾ ಪರಿಶೀಲನೆಯನ್ನು ಉಳಿಸಿಕೊಳ್ಳುತ್ತವೆ. ಸ್ಥಳೀಯ ಪ್ರೊವೈಡರ್ಗಳು ಡೀಫಾಲ್ಟ್ಗಳೊಂದಿಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ಡೀಫಾಲ್ಟ್ `true`) ಮೌಲ್ಯೀಕರಣ, ಡಿಸ್ಕವರಿ ಮತ್ತು ಪ್ರೊವೈಡರ್-ನೋಡ್ ಪಥಗಳಲ್ಲಿ ಸ್ಥಳೀಯ ಮತ್ತು LAN URLಗಳನ್ನು ಈಗಾಗಲೇ ಅನುಮತಿಸುತ್ತದೆ; ಪ್ರಾಕ್ಸಿ-ಫಾಲ್ಬ್ಯಾಕ್ ಪರೀಕ್ಷೆ ಮತ್ತು ಖಾಸಗಿ webhook ಗುರಿಗಳು ಈ ಫ್ಲ್ಯಾಗ್ ಅನ್ನು ಮಾತ್ರ ನೋಡುತ್ತವೆ ಮತ್ತು ಅದು ಆಫ್ ಇರುವವರೆಗೆ ಸ್ಥಳೀಯ/LAN ಹೋಸ್ಟ್ಗಳನ್ನು ನಿರ್ಬಂಧಿಸುತ್ತವೆ. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` ಅನ್ನು ಅತಿಕ್ರಮಿಸುತ್ತದೆ. ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಟಾಗಲ್ನಿಂದ ಉಳಿಸಲಾದ ಮೌಲ್ಯವು ಈ ವೇರಿಯೇಬಲ್ಗಿಂತ ಆದ್ಯತೆ ಪಡೆಯುತ್ತದೆ. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ಸ್ಥಳೀಯ/ಖಾಸಗಿ ವಿಳಾಸಗಳಲ್ಲಿ (127.0.0.1, localhost, LAN, ಖಾಸಗಿ ವ್ಯಾಪ್ತಿಗಳು) ಪ್ರೊವೈಡರ್ ಮೌಲ್ಯೀಕರಣ, ಮಾಡೆಲ್ ಡಿಸ್ಕವರಿ, ಚಾಟ್ ಮತ್ತು ಗಾರ್ಡ್ ಮೋಡ್ ನಿಯಂತ್ರಿಸುವ ಇತರ ಪ್ರೊವೈಡರ್-ಸಂಬಂಧಿತ ಔಟ್ಬೌಂಡ್ ಪಥಗಳಿಗಾಗಿ ಪ್ರೊವೈಡರ್ URLಗಳನ್ನು ಅನುಮತಿಸಿ. **ಡೀಫಾಲ್ಟ್ `true`** (ಸ್ಥಳೀಯಕ್ಕೆ-ಮೊದಲ-ಆದ್ಯತೆ): ಆಗ ಗಾರ್ಡ್ ಕ್ಲೌಡ್-ಮೆಟಾಡೇಟಾ ಎಂಡ್ಪಾಯಿಂಟ್ಗಳನ್ನು ನಿರ್ಬಂಧಿಸುತ್ತದೆ — ಸಂಪೂರ್ಣ 169.254.0.0/16 ಜೊತೆಗೆ ತಿಳಿದಿರುವ ಮೆಟಾಡೇಟಾ ಹೋಸ್ಟ್ ಹೆಸರುಗಳು (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). ಸಾರ್ವಜನಿಕ-ಮಾತ್ರ ಮೋಡ್ಗಾಗಿ `false` ಗೆ ಹೊಂದಿಸಿ, ಅದು ಖಾಸಗಿ ಮತ್ತು ಲೂಪ್ಬ್ಯಾಕ್ ಹೋಸ್ಟ್ಗಳನ್ನೂ ನಿರ್ಬಂಧಿಸುತ್ತದೆ. ಗಾರ್ಡ್ ಹೋಸ್ಟ್ ಹೆಸರನ್ನು ಅಥವಾ IP ಲಿಟರಲ್ ಅನ್ನು ಬರೆದಂತೆಯೇ, ಯಾವುದೇ DNS ಲುಕಪ್ಗೂ ಮೊದಲು ಪರಿಶೀಲಿಸುತ್ತದೆ; ಸಾರ್ವಜನಿಕ-ಮಾತ್ರ ಮೋಡ್ನಲ್ಲಿ safe-fetch ರ್ಯಾಪರ್ ಹೆಚ್ಚುವರಿಯಾಗಿ ಹೋಸ್ಟ್ ಹೆಸರನ್ನು ಪರಿಹರಿಸಿ ಯಾವುದೇ ಖಾಸಗಿ ಉತ್ತರವನ್ನು ನಿರಾಕರಿಸುತ್ತದೆ. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ತನ್ನ ಸಾಲಿನಲ್ಲಿ ಪಟ್ಟಿ ಮಾಡಿದ ಪಥಗಳಲ್ಲಿ ಈ ಪರಿಶೀಲನೆಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ. LM Studio, Ollama, vLLM, Llamafile ಮತ್ತು Triton ನಂತಹ ಒಳನಿರ್ಮಿತ ಸ್ಥಳೀಯ ಪ್ರೊವೈಡರ್ಗಳು ಕೀ ಮೌಲ್ಯೀಕರಣ ಮತ್ತು ಚಾಟ್ಗಾಗಿ ಎಲ್ಲಾ ಹೋಸ್ಟ್ ಪರಿಶೀಲನೆಗಳನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತವೆ. ಡ್ಯಾಶ್ಬೋರ್ಡ್ ಟಾಗಲ್ನಿಂದ ಉಳಿಸಲಾದ ಮೌಲ್ಯವು ಈ ವೇರಿಯೇಬಲ್ಗಿಂತ ಆದ್ಯತೆ ಪಡೆಯುತ್ತದೆ. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ಮಾರ್ಗಗಳು (ಲಿಪ್ಯಂತರಗಳು, ಮಾತು, ಅನುವಾದಗಳು) localhost ಹೊರಗೆ ಹೋಸ್ಟ್ ಮಾಡಲಾದ OpenAI-ಹೊಂದಾಣಿಕೆಯ ಪ್ರೊವೈಡರ್ ನೋಡ್ ಅನ್ನು ಬಳಸಲು ಅನುಮತಿಸಿ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ನಿಷ್ಕ್ರಿಯವಾಗಿದೆ — ಆಡಿಯೊವನ್ನು ರಿಮೋಟ್ ಹೋಸ್ಟ್ಗೆ ರೂಟ್ ಮಾಡುವುದರಿಂದ ಎಗ್ರೆಸ್ ಗುರುತು ಬದಲಾಗುತ್ತದೆ ಮತ್ತು ಇದು ಆಪರೇಟರ್ನ ಸ್ಪಷ್ಟ ನಿರ್ಧಾರವಾಗಿರಬೇಕು. ಲೂಪ್ಬ್ಯಾಕ್/ಖಾಸಗಿ ನೋಡ್ಗಳು (localhost, 127.0.0.1, 172.16-31.x) ಯಾವಾಗಲೂ ಅನುಮತಿಸಲ್ಪಡುತ್ತವೆ ಮತ್ತು ಪರಿಣಾಮಕ್ಕೊಳಗಾಗುವುದಿಲ್ಲ. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (ಮತ್ತು ಮೆಮೊರಿ ಎಂಜಿನ್ನ ಲೂಪ್ಬ್ಯಾಕ್ ಮರುಶ್ರೇಯಾಂಕ ಹಂತ) localhost ಹೊರಗೆ ಹೋಸ್ಟ್ ಮಾಡಲಾದ OpenAI-ಹೊಂದಾಣಿಕೆಯ ಪ್ರೊವೈಡರ್ ನೋಡ್ ಅನ್ನು ಬಳಸಲು ಅನುಮತಿಸಿ — ಅಂದರೆ TEI, Infinity, vLLM, ಇತ್ಯಾದಿಗಳನ್ನು ಚಲಾಯಿಸುವ LAN ಯಂತ್ರ ಅಥವಾ Tailscale ಪೀರ್. ಡೀಫಾಲ್ಟ್ ಆಗಿ ನಿಷ್ಕ್ರಿಯವಾಗಿದೆ — ರಿಮೋಟ್ ಹೋಸ್ಟ್ಗೆ ರೂಟ್ ಮಾಡುವುದರಿಂದ ಎಗ್ರೆಸ್ ಗುರುತು ಬದಲಾಗುತ್ತದೆ ಮತ್ತು ಇದು ಆಪರೇಟರ್ನ ಸ್ಪಷ್ಟ ನಿರ್ಧಾರವಾಗಿರಬೇಕು. ಲೂಪ್ಬ್ಯಾಕ್ ನೋಡ್ಗಳು (localhost, 127.0.0.1, 172.16-31.x) ಯಾವಾಗಲೂ ಅನುಮತಿಸಲ್ಪಡುತ್ತವೆ ಮತ್ತು ಪರಿಣಾಮಕ್ಕೊಳಗಾಗುವುದಿಲ್ಲ. ರಿಮೋಟ್ ನೋಡ್ನ ಬೇಸ್ URL ಕೂಡ ಪ್ರೊವೈಡರ್ನ ಔಟ್ಬೌಂಡ್ URL ನೀತಿಯನ್ನು (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ಪೂರೈಸಬೇಕು. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ಸಕ್ರಿಯವಾಗಿರುವಾಗ, ಬಳಕೆದಾರರು OIDC ಏಕ ಸೈನ್-ಆನ್ ಮೂಲಕ ಮಾತ್ರ ದೃಢೀಕರಿಸಿಕೊಳ್ಳುವಂತೆ ಪಾಸ್ವರ್ಡ್ ಲಾಗಿನ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ. ಸರಳ ಅಲಿಯಾಸ್ `OIDC_DISABLE_PASSWORD_LOGIN` ಅನ್ನೂ ಸ್ವೀಕರಿಸಲಾಗುತ್ತದೆ; ಅದೇ ಕೀಲಿಯ ಡ್ಯಾಶ್ಬೋರ್ಡ್ ವೈಶಿಷ್ಟ್ಯ ಫ್ಲ್ಯಾಗ್ಗೆ ಆದ್ಯತೆ ಇರುತ್ತದೆ. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ನ ಸರಳ ಅಲಿಯಾಸ್ (#10889). | ### ಬಲಪಡಿಸುವ ಪರಿಶೀಲನಾಪಟ್ಟಿ diff --git a/docs/i18n/ko/CONTRIBUTING.md b/docs/i18n/ko/CONTRIBUTING.md index 7e8a422a767e..5bbf64536bf4 100644 --- a/docs/i18n/ko/CONTRIBUTING.md +++ b/docs/i18n/ko/CONTRIBUTING.md @@ -386,22 +386,22 @@ handler/executor 내부에서 클라이언트에 전달되는 오류 메시지 --- -## 풀 리퀘스트 체크리스트 +## Pull 리퀘스트 체크리스트 - [ ] 테스트 통과 (`npm test`) -- [ ] 린트 검사 통과 (`npm run lint`) +- [ ] 린팅 통과 (`npm run lint`) - [ ] 빌드 성공 (`npm run build`) - [ ] 새로운 공개 함수 및 인터페이스에 TypeScript 타입 추가 - [ ] 하드코딩된 시크릿 또는 대체 값 없음 -- [ ] 공개 업스트림 자격 증명은 리터럴이 아닌 `resolvePublicCred()`를 통해 포함 ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) 참조) -- [ ] 오류 응답은 `buildErrorBody()` / `sanitizeErrorMessage()`를 통해 처리 — 응답 본문에 원시 스택 트레이스 포함 금지 ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) 참조) +- [ ] 공개 업스트림 자격 증명은 리터럴로 지정하지 않고 `resolvePublicCred()`를 통해 포함(참조: [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) +- [ ] 오류 응답은 `buildErrorBody()` / `sanitizeErrorMessage()`를 통해 처리 — 응답 본문에 원시 스택 트레이스 포함 금지(참조: [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] 셸 명령어(`exec` / `spawn`)는 문자열 보간이 아닌 `env`를 통해 런타임 값을 전달 - [ ] 모든 입력을 Zod 스키마로 검증 -- [ ] 사용자에게 영향을 미치는 변경 사항에 대해 `changelog.d/{features|fixes|maintenance}/-.md` 아래에 변경 로그 **조각** 추가 ([`changelog.d/README.md`](./changelog.d/README.md) 참조) — `CHANGELOG.md`를 직접 편집하지 **말 것**. 조각은 릴리스 시점에 통합되며 PR 간 충돌이 발생하지 않음 +- [ ] 사용자에게 영향을 주는 변경 사항의 경우 `changelog.d/{features|fixes|maintenance}/-.md` 아래에 변경 로그 **조각** 추가(참조: [`changelog.d/README.md`](./changelog.d/README.md)) — `CHANGELOG.md`를 직접 편집하지 말 것. 조각은 릴리스 시점에 통합되며 PR 간 충돌이 발생하지 않음 - [ ] 문서 업데이트(해당하는 경우) -- [ ] 새로운 CodeQL / Secret-Scanning 경고가 발생하지 않았거나, 각 경고를 관련 `docs/security/` 문서를 참조하는 기술적 근거와 함께 해제 -- [ ] 자식 프로세스를 생성하는 라우트(`/api/mcp/`, `/api/cli-tools/runtime/`)를 `src/server/authz/routeGuard.ts`에서 `isLocalOnlyPath()`로 분류 — [하드 규칙 #15](docs/security/ROUTE_GUARD_TIERS.md) 참조 -- [ ] 커밋 메시지에 `Co-Authored-By` 트레일러 포함 금지 — 커밋은 저장소 소유자의 Git ID로만 표시되어야 함(하드 규칙 #16) +- [ ] 새로운 CodeQL / Secret-Scanning 경고가 생성되지 않았거나, 각 경고를 관련 `docs/security/` 문서를 참조한 기술적 근거와 함께 해제 +- [ ] 하위 프로세스를 생성하는 라우트(`/api/mcp/`, `/api/cli-tools/runtime/`)를 `src/server/authz/routeGuard.ts`에서 `isLocalOnlyPath()`로 분류 — [엄격 규칙 #15](docs/security/ROUTE_GUARD_TIERS.md) 참조 +- [ ] 커밋 메시지에 AI/봇 `Co-authored-by` 트레일러 포함 금지(엄격 규칙 #16) — 작업이 재사용된 인간 협업자는 표준 `Co-authored-by: Name ` 트레일러로 크레딧 표기 --- diff --git a/docs/i18n/ko/docs/architecture/QUALITY_GATES.md b/docs/i18n/ko/docs/architecture/QUALITY_GATES.md index 7c2174218996..31c080a2389e 100644 --- a/docs/i18n/ko/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ko/docs/architecture/QUALITY_GATES.md @@ -19,81 +19,79 @@ ### 후보 승인 CI 및 Quality Gates 워크플로는 각각 안정적인 판정 결과인 `Gate / CI`와 -`Gate / Quality`를 출력합니다. 버전이 지정된 승인 정책은 모든 업스트림 작업을 -필수 또는 권고로 열거합니다. 적용 가능한 필수 작업은 성공해야 합니다. 누락, -취소, 건너뜀, 대기 중 및 알 수 없는 결과로는 PASS를 확정할 수 없습니다. 유효한 +`Gate / Quality`를 생성합니다. 버전이 관리되는 승인 정책은 모든 업스트림 작업을 +필수 또는 권고로 열거합니다. 적용 가능한 필수 작업은 성공해야 합니다. 누락됨, +취소됨, 건너뜀, 대기 중 및 알 수 없는 결과로는 PASS를 확정할 수 없습니다. 유효한 문서 전용 또는 카탈로그 전용 분류는 코드 레인을 적용 대상에서 제외할 수 있지만, -초안 PR은 승인된 후보가 아닙니다. `hotfix` 레이블은 증거 요건을 면제하지 않습니다. +초안 PR은 승인된 후보가 아닙니다. `hotfix` 레이블이 증거 요건을 면제하지는 않습니다. -두 워크플로 모두 PR, main/release 브랜치로의 푸시, 수동 디스패치 및 -병합 그룹 이벤트를 포함합니다. 푸시, 디스패치 및 병합 그룹은 전체 선택 항목을 -실행합니다. 포크와 병합 그룹은 원래 자체 호스팅 러너를 선택하는 작업에 대해 -호스팅 러너를 사용합니다. 따라서 출시 전에 충분한 호스팅 용량을 검증해야 합니다. +두 워크플로 모두 PR과 main/release 브랜치로의 푸시, 수동 디스패치 및 +병합 그룹 이벤트를 다룹니다. 푸시, 디스패치 및 병합 그룹은 전체 선택 항목을 +실행합니다. 포크와 병합 그룹은 원래 자체 호스팅 러너를 선택하는 작업에 +호스팅 러너를 사용합니다. 롤아웃 전에 충분한 호스팅 용량을 확인해야 합니다. 각 JSON 영수증은 체크아웃된 SHA, 워크플로 실행 및 시도를 식별합니다. -CLI는 체크아웃 SHA와 이벤트 SHA가 일치하지 않으면 거부합니다. 워크플로 테스트는 -정책 멤버십을 판정 작업의 `needs` 목록에 연결하므로 새 레인이 추가되거나 기존 레인이 -제거되어도 조용히 사라질 수 없습니다. 영수증은 자체 워크플로만 대상으로 하며, -게시, 배포 또는 기존 권고 스캐너의 내부 동작은 포함하지 않습니다. 브랜치 규칙에서 -두 검사 이름을 모두 활성화하는 것은 별도의 관리 변경 사항입니다. 이러한 작업을 -추가하는 것만으로는 브랜치가 보호되지 않습니다. +CLI는 체크아웃 SHA와 이벤트 SHA의 불일치를 거부합니다. 워크플로 테스트는 정책 +멤버십을 판정 작업의 `needs` 목록에 연결하므로 새로 추가되거나 제거된 레인이 +조용히 사라질 수 없습니다. 영수증은 자체 워크플로를 다루며, 게시, 배포 또는 +기존 권고 스캐너의 내부 동작은 다루지 않습니다. 브랜치 규칙에서 두 검사 이름을 +모두 활성화하는 것은 별도의 관리 변경 사항입니다. 이러한 작업을 추가하는 것만으로는 +브랜치가 보호되지 않습니다. ### 정적 스캔 인벤토리 -버전이 지정된 npm 별칭 인벤토리와 정적 스캔 멤버십은 +버전이 관리되는 npm 별칭 인벤토리와 정적 스캔 멤버십은 `config/quality/gate-manifest.json`에 있습니다. `package.json`을 기준으로 스크립트 이름과 정확한 명령을 검증하려면 `npm run check:gate-manifest`를 실행하세요. 추가, 제거 및 명령 드리프트가 발생하면 로컬 훅과 CI의 변경 분류 작업이 모두 실패합니다. 별칭은 워크플로 작업, 매트릭스 인스턴스 또는 테스트 케이스가 아닙니다. -따라서 이러한 개수를 서로 동일한 것으로 제시해서는 안 됩니다. +따라서 이러한 개수를 서로 호환되는 것처럼 제시해서는 안 됩니다. -선택된 별칭을 실행하지 않고 검사하려면 `npm run quality:scan -- --list` 또는 -`npm run quality:scan:fast -- --list`를 사용하세요. 러너는 npm 진입점을 호출하므로 -구성된 경우 Bun을 포함한 런타임이 유지됩니다. 매니페스트는 해당 프로필 외부의 -별칭을 별도 호출 항목으로 기록하며, 읽기 전용 스캔 프로필에서는 유지보수 명령이 -금지됩니다. +선택된 별칭을 실행하지 않고 확인하려면 `npm run quality:scan -- --list` 또는 +`npm run quality:scan:fast -- --list`를 사용하세요. 러너는 npm 진입점을 +호출하므로 구성된 경우 Bun을 포함한 런타임이 유지됩니다. 매니페스트는 해당 +프로필 외부의 별칭을 별도 호출 항목으로 기록하며, 읽기 전용 스캔 프로필에서는 +유지보수 명령이 금지됩니다. -이러한 프로필은 정적 스캔만 포함합니다. 제품 테스트, 커버리지, 패키징, 외부 검사 -또는 후보의 전체 릴리스 승인을 보증하지 않습니다. 워크플로 승인은 연결된 -`config/quality/admission-policy.json`과 +이러한 프로필은 정적 스캔만 다룹니다. 제품 테스트, 커버리지, 패키징, +외부 검사 또는 후보의 전체 릴리스 승인을 보증하지 않습니다. 워크플로 승인은 +연결된 `config/quality/admission-policy.json`과 `scripts/quality/admission-verdict.mjs`를 사용합니다. 릴리스 관찰자 프로필은 -별도로 유지됩니다. 해당 프로필에 적용 가능한 검사와 영수증을 독립적으로 -검토하세요. 아래의 서술형 인벤토리는 참조 자료일 뿐, 게이트가 실제로 실행되었다는 -증거는 아닙니다. +별도로 유지됩니다. 적용 가능한 검사와 영수증을 독립적으로 확인하세요. 아래의 +서술형 인벤토리는 참조 자료일 뿐, 게이트가 실제로 실행되었다는 증거는 아닙니다. -스크립트는 `scripts/check/`(정책 게이트)와 `scripts/quality/`(래칫 엔진) 아래에 -있습니다. CI의 단일 진실 공급원은 `.github/workflows/ci.yml`입니다. +스크립트는 `scripts/check/`(정책 게이트)와 `scripts/quality/`(래칫 엔진)에 있습니다. +CI의 단일 진실 공급원은 `.github/workflows/ci.yml`입니다. ### 릴리스 PR 빠른 경로(`quality.yml`) `.github/workflows/quality.yml`은 main/release PR, 보호된 브랜치로의 푸시, -디스패치 및 병합 그룹에서 CI를 보완합니다. PR은 경로 필터가 적용된 빠른 검사를 -사용합니다. 영구적으로 비활성화되어 있던 중복 빌드는 제거되었습니다. 실제 -빌드/패키지/부팅 검사는 CI에 그대로 남아 있습니다. - -| 작업 | 범위 | 차단 여부 | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `Docs Gates (fast-path)` | 문서/코드 PR, API 문서 참조 및 전체 문서 | 예 | -| `Fast Quality Gates` | 코드 PR, 정적 검사, 타입 검사, 대시보드 타입 검사, 영향받는 단위 테스트 | 예 | -| `Forgotten sibling tests` | 코드 PR, 변경된 모듈을 정적 소비자 및 후보 형제 테스트까지 추적, 배럴 및 동적 가져오기 경로는 참조된 허용 목록 예외와 함께 권고 진단으로 보고됨 | **권고** | -| `Vitest (fast-path)` | 코드 PR, 빠른 vitest 스위트 | 예 | -| `Unit Tests fast-path` | 코드 PR, 4-샤드 단위 테스트 스위트 | 예 | -| `No new ESLint warnings` | 코드 PR, 억제 설정을 인식하는 린트 가드 | 예, 포크 포함 | -| `Merge integrity (changelog + generated skills)` | 초안이 아닌 PR, 변경 로그 및 생성된 스킬 동기화 | 예, 포크 포함 | +디스패치 및 병합 그룹에서 CI를 보완합니다. PR은 경로 필터링된 빠른 검사를 사용합니다. +영구적으로 비활성화된 중복 빌드는 제거되었으며, 실제 빌드/패키지/부팅 검사는 CI에 남아 있습니다. + +| 작업 | 범위 | 차단 여부 | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `Docs Gates (fast-path)` | 문서/코드 PR, API 문서 참조 및 전체 문서 | 예 | +| `Fast Quality Gates` | 코드 PR, 정적 검사, 타입 검사, 대시보드 타입 검사, 영향받는 단위 테스트 | 예 | +| `Forgotten sibling tests` | 코드 PR, 변경된 모듈을 정적 소비자와 후보 형제 테스트까지 추적, 배럴 및 동적 임포트 경로는 참조된 허용 목록 예외와 함께 권고 진단으로 보고됨 | **권고** | +| `Vitest (fast-path)` | 코드 PR, 빠른 vitest 스위트 | 예 | +| `Unit Tests fast-path` | 코드 PR, 4샤드 단위 테스트 스위트 | 예 | +| `No new ESLint warnings` | 코드 PR, 억제 규칙을 인식하는 린트 가드 | 예, 포크 포함 | +| `Merge integrity (changelog + generated skills)` | 초안이 아닌 PR, 변경 로그 및 생성된 스킬 동기화 | 예, 포크 포함 | #### 누락된 형제 테스트 보고서 -`npm run check:forgotten-sibling-tests`는 테스트 영향도 맵에서 사용하는 가져오기 -해결기를 재사용합니다. 변경된 모든 프로덕션 모듈에 대해 후보 테스트가 풀 리퀘스트 -차이에 없으면 결정론적인 -`changed module/symbol -> static consumer -> candidate sibling test` 체인을 보고합니다. -차단 방식으로 출시하기 전 보정을 위해 Markdown 요약과 JSON 결과는 -`forgotten-sibling-tests` 워크플로 아티팩트로 보존됩니다. +`npm run check:forgotten-sibling-tests`는 테스트 영향도 맵의 기반이 되는 임포트 리졸버를 재사용합니다. +변경된 각 프로덕션 모듈에 대해 후보 테스트가 풀 리퀘스트 diff에 없으면 결정론적인 +`변경된 모듈/심볼 -> 정적 소비자 -> 후보 형제 테스트` 체인을 보고합니다. 차단 방식으로 +롤아웃하기 전 보정을 위해 Markdown 요약과 JSON 결과는 `forgotten-sibling-tests` +워크플로 아티팩트로 보존됩니다. -배럴 재내보내기와 동적 임포트는 해석 진단에만 사용되며, 차단 결과를 생성하지 않습니다. -검토된 예외는 `config/quality/forgotten-sibling-allowlist.json`에 있습니다. 각 항목에는 소비자와 후보 +배럴 재내보내기와 동적 가져오기는 오직 해석 진단에만 사용되며, 차단 +결과를 생성하지 않습니다. 검토된 예외는 +`config/quality/forgotten-sibling-allowlist.json`에 있습니다. 각 항목에는 소비자와 후보 테스트를 명시하고, 구체적인 근거를 제시하며, GitHub 이슈 또는 풀 리퀘스트를 연결해야 합니다. 형식이 잘못된 항목은 -차단됩니다. 예외는 삭제된 후보 테스트나 `.skip`/`.todo`를 추가하는 diff를 억제할 수 없습니다. +폐쇄형으로 실패합니다. 예외는 삭제된 후보 테스트나 `.skip`/`.todo`를 추가하는 diff를 억제할 수 없습니다. 어서션 약화 및 기타 마스킹은 독립적으로 차단하는 `check:test-masking` 게이트가 계속 담당합니다. @@ -101,133 +99,126 @@ CLI는 체크아웃 SHA와 이벤트 SHA가 일치하지 않으면 거부합니 `main`을 대상으로 하는 모든 PR에서 실행됩니다. 실패 시 병합을 차단합니다. -| 스크립트 (`npm run ...`) | 검증 항목 | 차단 여부 | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `check:node-runtime` | Node.js 버전이 지원 범위 내에 있는지 | 예 | -| `check:cycles` | 순환 임포트 — 모든 `src/` + `open-sse/` 모듈 | 예 | -| `check:route-validation:t06` | 모든 라우트에 Zod 스키마가 있는지(Tier 6 정책) | 예 | -| `check:any-budget:t11` | `@ts-expect-error // any` 개수가 예산을 초과하지 않는지(Tier 11 catraca) | 예 | -| `check:provider-consistency` | `providers.ts`의 모든 provider에는 `providerRegistry.ts`에 일치하는 항목이 있으며, 그 반대도 마찬가지입니다(allowlist 내에서) | 예 | -| `check:model-lifecycle` | 수동으로 유지 관리되는 세 개의 라우팅 테이블이 체크인된 수명 주기 스냅샷(#11503)과 일관성을 유지합니다. `FITNESS_TABLE`(`taskFitness.ts`)은 `REGISTRY`가 라우팅할 수 있는 폐기된 id에 점수를 부여하지 않고, 모든 `BUILT_IN_ALIASES` 대상은 `REGISTRY`에 존재하며 폐기된 id 스냅샷에는 없고, `REGISTRY`에 여전히 남아 있는 모든 폐기된 id는 전달되거나 `allowedRetiredInCatalog`에 나열되며, `DEFAULT_DEGRADATION_MAP`의 source 또는 target은 해당 스냅샷에서 폐기된 것으로 표시되지 않습니다. 이는 모델이 현재 실제 upstream에서 제공되고 있음을 입증하지는 않습니다. 오프라인 — `config/quality/model-lifecycle.json`과 비교하며, `npm run quality:refresh-model-lifecycle`을 사용하여 수동으로 새로 고칩니다(network 필요, CI에는 연결되지 않음). `allowedRetiredInCatalog`는 점진적 축소를 위한 ratchet입니다. 추적 issue가 있는 경우에만 항목을 추가하세요. | 예 | -| `check:fetch-targets` | 클라이언트 측 `src/`의 모든 `fetch("/api/...")`가 실제 `route.ts`로 확인됩니다 | 예 | -| `check:deps` | repo 내 모든 `package.json`에서 `npm install` 가능한 모든 deps가 `dependency-allowlist.json`에 포함되어 있으며, 새로 추가된 고정되지 않은 패키지 또는 slopsquatting 패키지는 표시됩니다 | 예 | -| `audit:deps` | `npm audit`(root + electron) — high/critical advisory가 없음(osv `check:vuln-ratchet`과 중복됨. Rationalization Backlog 참조) | 예 | -| `check:lockfile` | `package-lock.json` 무결성 — https registry, integrity hash, host override 없음 | 예 | -| `check:licenses` | 프로덕션 종속성에 대한 SPDX 라이선스 허용 목록 | 예 | -| `check:tracked-artifacts` | 빌드 아티팩트 / 커밋된 `node_modules` 심볼릭 링크 없음(husky pre-commit에서도 실행됨; pre-push는 의도적으로 가볍게 유지 — #6716) | 예 | -| `check:ai-attribution` | PR 커밋, 제목 또는 본문에 AI/봇 `Co-Authored-By` 트레일러나 AI 생성 푸터 없음 — 엄격한 규칙 #16(`quality.yml`의 PR→`release/**` 빠른 게이트 루프에서 실행 — 이벤트 페이로드를 읽으며 PR이 아니면 아무 작업도 하지 않음 — 및 `ci.yml` lint의 PR→`main`용 PR 전용 단계; husky `commit-msg` 훅에서도 실행; 사람 공동 작성자는 허용됨; #14436) | -| `check:vitest-exclusions` | 모든 Vitest 제외 항목은 추적 이슈를 명시하고 `config/quality/vitest-exclusions.json`에 있어야 함(#13204) | 예 | -| `check:file-size` | 소스 파일이 확장자별 상한을 초과하지 않음(래칫: 대용량 파일은 `frozen` 목록에 고정됨) | 예 | -| `check:error-helper` | 실행기/핸들러의 오류 응답은 `buildErrorBody()` / `sanitizeErrorMessage()`를 사용함(엄격한 규칙 #12) | 예 | -| `check:migration-numbering` | 마이그레이션 SQL 파일은 누락되거나 중복된 번호 없이 순차적으로 번호가 지정되어야 함 | 예 | -| `check:public-creds` | `publicCreds.ts` 외부에 리터럴 OAuth `client_id`/`client_secret` 또는 Firebase Web 키가 없어야 함(엄격 규칙 #11) | 예 | -| `check:db-rules` | `src/lib/db/` 모듈 외부에 원시 SQL이 없어야 하며, `localDb.ts`에서 배럴 임포트를 사용하지 않아야 함(엄격 규칙 #2/#5) | 예 | -| `check:known-symbols` | 디스패치 테이블에 등록된 제공자 실행기, 라우팅 전략 및 변환기가 디스크의 파일과 일치해야 하며, 고립되거나 선언되지 않은 심벌이 없어야 함 | 예 | -| `check:route-guard-membership` | 하위 프로세스를 생성하는 모든 라우트는 `isLocalOnlyPath()`로 분류되어야 함(엄격 규칙 #15/#17) | 예 | -| `check:test-discovery` | 저장소의 모든 `*.test.ts` / `*.spec.ts` 파일은 하나 이상의 테스트 러너에 의해 수집되어야 함(래칫: `test-discovery-baseline.json`의 고립 파일 목록은 줄어들기만 해야 함) | 예 | -| `check:agent-skills-sync` | 생성된 agent-skills 아티팩트가 소스 카탈로그와 일치함(드리프트 없음) | -| `check:provider-asset-provenance` | 제공자 로고/에셋에 기록된 출처 항목이 있음 | -| `lint:json` | JSON 구성 파일이 올바르게 파싱되고 저장소 린트 규칙을 충족함 | -| `typecheck:core` | 오류 없는 TypeScript 컴파일(권고 경고만 있음) | 예 | -| `typecheck:noimplicit:core` | 엄격한 `noImplicitAny` — 향후 적용을 위한 항목이며, 기존의 많은 호출 지점에는 여전히 타입 주석이 필요함 | **권고** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**`로 범위가 한정된 `tsc` (#7033) — `typecheck:core`의 선별된 27개 파일 허용 목록에는 대시보드 TSX가 전혀 포함되지 않으며, `next build` 역시 이를 타입 검사하지 않으므로(`next.config.mjs`에서 `ignoreBuildErrors: true` 설정), 해당 위치의 고립된 식별자 회귀(#6625/#6909)가 CI에서 감지되지 않았음. 고정된 파일별/TS 코드별 개수 기준선(`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols`와 동일한 오래된 항목 강제 적용 패턴)과 차이를 비교함 — 기준선의 개수를 초과하는 새로운 오류만 게이트를 실패하게 하며, 기존 오류를 수정한 경우 `--update`를 사용해 기준선을 낮출 수 있음. | 예 | +| 스크립트 (`npm run ...`) | 검증 항목 | 차단 여부 | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| `check:node-runtime` | Node.js 버전이 지원 범위 내에 있는지 확인 | 예 | +| `check:cycles` | `src/` + `open-sse/` 전체의 순환 가져오기를 검사합니다(AST 기반, tsconfig `paths` 해석). 기본 실행은 권고용이며 순환 목록을 표시합니다. `check:cycles:ratchet`(CI에서 실행되는 항목)은 순환 수가 `quality-baseline.json`의 `metrics.cycles` 상한을 초과하면 차단합니다. 현재 상한은 14이고 `direction: down`이므로 감소만 가능합니다(#15159 G-01/G-02) | 예(래칫) | +| `check:route-validation:t06` | 모든 라우트에 Zod 스키마가 있는지 확인(Tier 6 정책) | 예 | +| `check:any-budget:t11` | `@ts-expect-error // any` 개수가 예산을 초과하지 않는지 확인(Tier 11 catraca) | 예 | +| `check:provider-consistency` | `providers.ts`의 모든 프로바이더에는 `providerRegistry.ts`에 일치하는 항목이 있으며, 그 반대도 동일합니다(허용 목록 내에서). | 예 | +| `check:model-lifecycle` | 수동으로 유지 관리되는 세 개의 라우팅 테이블이 체크인된 수명 주기 스냅샷(#11503)과 일관성을 유지합니다. `FITNESS_TABLE`(`taskFitness.ts`)은 `REGISTRY`가 라우팅할 수 있는 사용 종료 ID에 점수를 부여하지 않으며, 모든 `BUILT_IN_ALIASES` 대상은 `REGISTRY`에 존재하고 사용 종료 ID 스냅샷에는 없습니다. `REGISTRY`에 아직 남아 있는 모든 사용 종료 ID는 포워딩되거나 `allowedRetiredInCatalog`에 나열되며, `DEFAULT_DEGRADATION_MAP`의 소스나 대상은 해당 스냅샷에서 사용 종료 상태로 표시되지 않습니다. 이는 모델이 현재 실제 업스트림에서 제공되고 있음을 입증하지는 않습니다. 오프라인 검사로, `config/quality/model-lifecycle.json`과 비교합니다. 이 파일은 `npm run quality:refresh-model-lifecycle`을 사용해 수동으로 갱신합니다(네트워크 필요, CI에는 연결되지 않음). `allowedRetiredInCatalog`는 단계적 축소를 위한 래칫입니다. 추적 이슈가 있는 경우에만 항목을 추가하십시오. | 예 | +| `check:fetch-targets` | 클라이언트 측 `src/`에 있는 모든 `fetch("/api/...")`가 실제 `route.ts`로 해석됩니다. | 예 | +| `check:deps` | 저장소의 모든 `package.json`에 있는 `npm install` 가능한 모든 종속성이 `dependency-allowlist.json`에 포함되어 있으며, 새로 추가된 버전이 고정되지 않은 패키지나 슬롭스쿼팅 패키지는 플래그로 표시됩니다. | 예 | +| `audit:deps` | `npm audit`(루트 + electron) — 높음/심각 수준의 보안 권고 없음(osv `check:vuln-ratchet`과 중복됨. Rationalization Backlog 참조) | 예 | +| `check:lockfile` | `package-lock.json` 무결성 — https 레지스트리, 무결성 해시, 호스트 재정의 없음 | 예 | +| `check:licenses` | 프로덕션 종속성에 대한 SPDX 라이선스 허용 목록 | 예 | +| `check:tracked-artifacts` | 빌드 아티팩트 / 커밋된 `node_modules` 심볼릭 링크 없음(husky pre-commit에서도 실행됨; pre-push는 의도적으로 가볍게 유지 — #6716) | 예 | +| `check:ai-attribution` | PR 커밋, 제목 또는 본문에 AI/봇 `Co-Authored-By` 트레일러나 AI 생성 푸터 없음 — 엄격한 규칙 #16(PR→`release/**`에 대한 `quality.yml` 빠른 게이트 루프에서 이벤트 페이로드를 읽으며, PR이 아닌 경우 아무 작업도 하지 않음 — 그리고 PR→`main`에 대한 `ci.yml` 린트의 PR 전용 단계; husky `commit-msg` 훅에서도 실행됨; 사람 공동 작성자는 허용됨; #14436) | +| `check:vitest-exclusions` | 모든 Vitest 제외 항목은 추적 이슈를 명시하고 `config/quality/vitest-exclusions.json`에 포함되어야 함(#13204) | 예 | +| `check:file-size` | 소스 파일이 확장자별 상한을 초과하지 않음(래칫: 대용량 파일은 `frozen` 목록에 고정) | 예 | +| `check:error-helper` | 실행기/핸들러의 오류 응답은 `buildErrorBody()` / `sanitizeErrorMessage()`를 사용함(엄격한 규칙 #12) | 예 | +| `check:migration-numbering` | 마이그레이션 SQL 파일은 누락되거나 중복된 번호 없이 순차적으로 번호가 지정되어야 함 | 예 | +| `check:public-creds` | `publicCreds.ts` 외부에 OAuth `client_id`/`client_secret` 또는 Firebase Web 키 리터럴이 없어야 함(강제 규칙 #11) | 예 | +| `check:db-rules` | `src/lib/db/` 모듈 외부에 원시 SQL이 없어야 하며, `localDb.ts`에서 배럴 임포트를 사용하지 않아야 함(강제 규칙 #2/#5) | 예 | +| `check:known-symbols` | 디스패치 테이블에 등록된 공급자 실행기, 라우팅 전략 및 변환기가 디스크의 파일과 일치해야 하며, 연결되지 않았거나 선언되지 않은 심볼이 없어야 함 | 예 | +| `check:route-guard-membership` | 자식 프로세스를 생성하는 모든 라우트는 `isLocalOnlyPath()`로 분류되어야 함(강제 규칙 #15/#17) | 예 | +| `check:test-discovery` | 저장소의 모든 `*.test.ts` / `*.spec.ts` 파일은 하나 이상의 테스트 러너에서 수집되어야 함(래칫: `test-discovery-baseline.json`의 고아 목록은 줄어들기만 할 수 있음) | 예 | +| `check:agent-skills-sync` | 생성된 agent-skills 아티팩트가 소스 카탈로그와 일치함(드리프트 없음) | +| `check:provider-asset-provenance` | 공급자 로고/에셋에 기록된 출처 항목이 있음 | +| `lint:json` | JSON 구성 파일이 정상적으로 파싱되고 저장소 린트 규칙을 충족함 | +| `typecheck:core` | 오류 없이 TypeScript 컴파일(권고 경고만 있음) | 예 | +| `typecheck:noimplicit:core` | 엄격한 `noImplicitAny` — 향후 적용을 위한 항목이며, 기존의 많은 호출 지점에는 여전히 어노테이션이 필요함 | **권고** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**`로 범위가 지정된 `tsc` (#7033) — `typecheck:core`의 엄선된 27개 파일 허용 목록에는 대시보드 TSX가 전혀 포함되지 않으며, `next build` 역시 이를 타입 검사하지 않으므로(`next.config.mjs`에서 `ignoreBuildErrors: true` 설정), 해당 영역의 고립된 식별자 회귀(#6625/#6909)가 CI에서 감지되지 않았음. 고정된 파일별/TS 코드별 개수 기준선(`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols`와 동일한 오래된 항목 강제 적용 패턴)과 비교함 — 기준선에 기록된 개수를 초과하는 새로운 오류만 게이트를 실패하게 하며, 기존 오류가 수정되면 `--update`를 사용하여 기준선을 낮출 수 있음. | 예 | ### 작업: `quality-gate` `test-coverage` 이후에 실행됩니다. 실패하면 병합을 차단합니다. -| 스크립트 | 검증 항목 | 차단 여부 | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `quality:collect` | `quality-metrics.json` 생성(ESLint 경고 수, 병합된 샤드 보고서의 커버리지) | 예(래칫의 선행 단계) | -| `quality:ratchet` | `quality-baseline.json`의 각 메트릭이 퇴보하지 않았는지 확인(ESLint 경고 ≤ 기준선, 커버리지 ≥ 기준선) | 예 | -| `check:duplication` | 코드 중복(jscpd@4)이 `quality-baseline.json`의 기준선을 초과하지 않는지 확인 | 예 | -| `check:complexity` | 파일 수준 순환 복잡도가 상한을 초과하지 않는지 확인(핵심 ESLint `complexity` + `max-lines-per-function`) | 예 | -| `check:cognitive-complexity` | 인지 복잡도 래칫(`eslint-plugin-sonarjs`) — 별도의 ESLint 패스이며, CI에서는 두 검사를 단일 `check:complexity-ratchets` 단계로 병합하여 실행 | 예 | -| `check:dead-code` | 사용되지 않는 내보내기/파일 래칫(knip)이 기준선보다 퇴보하지 않는지 확인 | 예 | -| `check:compression-budget` | 압축 벤치마크 예산 — 엔진별 토큰 절감률 하한이 퇴보하지 않아야 함 | 예 | -| `check:type-coverage` | 타입 지정 비율 래칫(`type-coverage`)이 퇴보하지 않는지 확인하며, `typecheck:noimplicit:core`를 대부분 포괄함 | 예 | -| `check:codeql-ratchet` | 미해결 CodeQL 경고 수가 증가하지 않는지 확인(`gh api`로 조회하며, 토큰이 없으면 정상적으로 건너뜀) — 갱신 주기 및 수동 트리거는 아래의 "CodeQL 래칫" 참조 | 예 | +| 스크립트 | 검증 항목 | 차단 여부 | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | +| `quality:collect` | `quality-metrics.json` 생성(ESLint 경고 수, 병합된 샤드 보고서의 커버리지) | 예(래칫의 선행 단계) | +| `quality:ratchet` | `quality-baseline.json`의 각 메트릭이 악화되지 않았는지 확인(ESLint 경고 ≤ 기준선, 커버리지 ≥ 기준선) | 예 | +| `check:duplication` | 코드 중복(jscpd@4)이 `quality-baseline.json`의 기준선을 초과하지 않는지 확인 | 예 | +| `check:complexity` | 파일 수준 순환 복잡도가 상한을 초과하지 않는지 확인(코어 ESLint `complexity` + `max-lines-per-function`) | 예 | +| `check:cognitive-complexity` | 인지 복잡도 래칫(`eslint-plugin-sonarjs`) — 별도의 ESLint 패스이며, CI에서는 두 패스를 단일 `check:complexity-ratchets` 단계로 병합하여 실행 | 예 | +| `check:dead-code` | 사용되지 않는 내보내기/파일 래칫(knip)이 기준선 대비 악화되지 않는지 확인 | 예 | +| `check:compression-budget` | 압축 벤치마크 예산 — 엔진별 토큰 절감률 하한이 악화되지 않아야 함 | 예 | +| `check:type-coverage` | 타입 지정 비율 래칫(`type-coverage`)이 악화되지 않는지 확인하며, 대부분 `typecheck:noimplicit:core`를 포괄 | 예 | +| `check:codeql-ratchet` | 열린 CodeQL 경고 수가 악화되지 않는지 확인(`gh api`를 통해 조회하며, 토큰이 없으면 정상적으로 건너뜀) — 갱신 주기 및 수동 트리거는 아래의 "CodeQL 래칫" 참조 | 예 | ### 작업: `quality-extended` 전체 작업은 권고용입니다(`continue-on-error: true`). npm 기반 래칫은 -실제로 실행됩니다. 외부 스캐너는 `gh release download`를 통해 설치되며, 바이너리가 -여전히 없으면 자체적으로 건너뜁니다(exit 0). - -| 스크립트 | 검증 항목 | 차단 여부 | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -| `check:circular-deps` | 순환 종속성이 없는지 확인(dpdm) | **권고** | -| `check:bundle-size` | 번들 크기가 상한을 초과하지 않는지 확인 | **권고** | -| `check:secrets` | 비밀 정보 스캔(gitleaks) — 바이너리가 없으면 건너뜀 | **권고** | -| `check:vuln-ratchet` | 종속성 취약점(osv-scanner)이 퇴보하지 않는지 확인 — 바이너리가 없으면 건너뜀 | **권고** | -| `check:workflows` | 워크플로 린트(actionlint + zizmor). 스캐너 누락/오작동, 유효하지 않은 보고서 또는 래칫 기준선 누락 시 INCOMPLETE로 실패. 유효한 발견 사항에는 선택한 엄격/권고/래칫 정책 적용 | 실행 필수, CI에서 zizmor 래칫은 차단됨 | -| `check:openapi-breaking` | 기본 브랜치 대비 공개 API 계약(`openapi.yaml`)의 호환성을 깨뜨리는 변경 사항(oasdiff) — `openapiBreaking=N` 생성. oasdiff가 없거나 기본 명세를 확인할 수 없으면 건너뜀 | **권고** | +실제로 실행되며, 외부 스캐너는 `gh release download`를 통해 설치되고 바이너리가 +여전히 없으면 자체적으로 건너뜁니다(종료 코드 0). + +| 스크립트 | 검증 항목 | 차단 여부 | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | +| `check:circular-deps` | 순환 종속성이 없는지 확인(dpdm) | **권고** | +| `check:bundle-size` | 번들 크기가 상한을 초과하지 않는지 확인 | **권고** | +| `check:secrets` | 비밀 정보 스캔(gitleaks) — 바이너리가 없으면 건너뜀 | **권고** | +| `check:vuln-ratchet` | 종속성 취약점(osv-scanner)이 악화되지 않는지 확인 — 바이너리가 없으면 건너뜀 | **권고** | +| `check:workflows` | 워크플로 린트(actionlint + zizmor). 스캐너가 없거나 손상된 경우, 보고서가 유효하지 않은 경우 또는 래칫 기준선이 없는 경우 INCOMPLETE로 실패합니다. 유효한 발견 항목에는 선택된 엄격/권고/래칫 정책이 적용됩니다 | 실행 필수, CI에서 zizmor 래칫은 차단 항목 | +| `check:openapi-breaking` | 기본 브랜치 대비 공개 API 계약(`openapi.yaml`)의 호환성을 깨뜨리는 변경(oasdiff) — `openapiBreaking=N` 생성. oasdiff가 없거나 기본 명세를 확인할 수 없으면 건너뜀 | **권고** | ### 작업: `docs-sync-strict` -`main`에 대한 모든 PR에서 실행됩니다. 실패하면 병합을 차단합니다. - -| 스크립트 | 검증 항목 | 차단 여부 | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- | -| `check:docs-all` | 아래 6개의 하위 게이트를 순차적으로 실행하는 메타 게이트 | 예 | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt 버전 일관성 | 예 | -| ↳ `check:docs-counts` | 문서 본문의 개수(프로바이더 수, 마이그레이션 수 등)가 실제 개수의 래칫 허용 범위 내에 있는지 확인 | 예 | -| ↳ `check:env-doc-sync` | `.env.example`의 모든 환경 변수가 문서 표에 기록되어 있고, 그 반대도 성립하는지 확인 | 예 | -| ↳ `check:deprecated-versions` | 문서에 지원 중단된 버전 문자열이 없는지 확인 | 예 | -| ↳ `check:doc-links` | 문서의 내부 마크다운 링크가 실제 파일로 연결되는지 확인(`[text]`/`(path)` 형식) | 예 | -| ↳ `check:fabricated-docs` | 문서에서 언급한 라우트, 환경 변수, CLI 명령, 훅 이름 및 파일 경로가 코드베이스에 존재하는지 확인. `--strict` 사용 시 하드 게이트이며, 플래그가 없으면 소프트 실패. | 예(CI에서 `--strict` 사용) | -| `check:cli-i18n` | CLI 명령 문자열이 모든 i18n 로케일 파일에 있는지 확인 | 예 | -| `check:openapi-coverage` | OpenAPI 명세가 실제 라우트 중 래칫 방식으로 설정된 최소 기준 이상을 포함하는지 확인 | 예 | -| `check:openapi-security-tiers` | `openapi.yaml`의 보안 등급 주석이 `routeGuard.ts` 분류와 일치하는지 확인 | **권고** | -| `check:openapi-routes` | `openapi.yaml`의 모든 경로가 실제 `route.ts`로 연결되는지 확인(환각 방지) | 예 | -| `check:docs-symbols` | `docs/**/*.md`의 모든 `/api/...` 참조가 실제 `route.ts`로 연결되는지 확인(환각 방지) | 예 | -| `i18n translation drift` | i18n 로케일 파일의 번역되지 않은 키 — 경고만 표시 | **권고** | +`main`에 대한 모든 PR에서 실행됩니다. 실패 시 병합을 차단합니다. + +| 스크립트 | 검증 내용 | 차단 여부 | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | 아래의 6개 하위 게이트를 순차적으로 실행하는 메타 게이트 | 예 | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt 버전 일관성 | 예 | +| ↳ `check:docs-counts` | 문서 본문의 수치(제공자 수, 마이그레이션 수 등)가 실제 수치의 래칫 범위 내에 있는지 확인 | 예 | +| ↳ `check:env-doc-sync` | `.env.example`의 모든 환경 변수가 문서 표에 기록되어 있고, 그 반대도 성립하는지 확인 | 예 | +| ↳ `check:deprecated-versions` | 문서에 지원 중단된 버전 문자열이 없는지 확인 | 예 | +| ↳ `check:doc-links` | 문서의 내부 마크다운 링크가 실제 파일로 연결되는지 확인(`[text]`/`(path)` 형식) | 예 | +| ↳ `check:fabricated-docs` | 문서에 언급된 라우트, 환경 변수, CLI 명령어, 훅 이름 및 파일 경로가 코드베이스에 존재하는지 확인. `--strict`를 사용하면 하드 게이트이며, 플래그가 없으면 소프트 실패. | 예(CI에서 `--strict` 사용) | +| `check:cli-i18n` | CLI 명령어 문자열이 모든 i18n 로케일 파일에 존재하는지 확인 | 예 | +| `check:openapi-coverage` | OpenAPI 명세가 래칫으로 설정된 실제 라우트의 최소 기준 이상을 포괄하는지 확인 | 예 | +| `check:openapi-security-tiers` | `openapi.yaml`의 보안 계층 어노테이션이 `routeGuard.ts` 분류와 일치하는지 확인 | **권고** | +| `check:openapi-routes` | `openapi.yaml`의 모든 경로가 실제 `route.ts`로 연결되는지 확인(환각 방지) | 예 | +| `check:docs-symbols` | `docs/**/*.md`의 모든 `/api/...` 참조가 실제 `route.ts`로 연결되는지 확인(환각 방지) | 예 | +| `i18n translation drift` | i18n 로케일 파일의 번역되지 않은 키 — 경고만 표시 | **권고** | ### 작업: `i18n-ui-coverage` -| 스크립트 | 검증 항목 | 차단 여부 | +| 스크립트 | 검증 내용 | 차단 여부 | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check-ui-keys-coverage` (인라인) | UI i18n 키 적용 범위가 65% 이상인지 확인 | 예 | -| `check-ui-value-drift` (인라인) | 다시 작성된 영어 **값**으로 인해 오래된 번역이 남아 있지 않은지 확인 | 예 | +| `check-ui-keys-coverage` (인라인) | UI i18n 키 커버리지가 65% 이상인지 확인 | 예 | +| `check-ui-value-drift` (인라인) | 재작성된 영어 **값**에 오래된 번역이 남아 있지 않은지 확인 | 예 | | `check-new-key-coverage` (인라인) | **새로운** 영어 키가 모든 로케일에서 번역되었는지 확인 — `__MISSING__:` 마커는 거부됨 | 예 | -| `check-translation-ratio` | 로케일별 실제 번역 비율(허용 목록에 포함되지 않은 영어와 동일한 값 / 플레이스홀더 / 누락된 리프)이 `config/quality/i18n-translation-baseline.json` + 여유치를 초과하지 않아야 함 | **권고** | +| `check-translation-ratio` | 로케일별 실제 번역 비율(허용 목록에 포함되지 않은 영어와 동일한 값 / 플레이스홀더 / 누락된 리프)이 `config/quality/i18n-translation-baseline.json` + 여유분을 초과하지 않아야 함 | **권고** | -`fetch-depth: 0`이 필요합니다 — 값 드리프트 게이트가 병합 기준점을 기준으로 `en.json`의 차이를 비교합니다. +`fetch-depth: 0`이 필요합니다. 값 드리프트 게이트는 병합 기준점을 기준으로 `en.json`의 차이를 비교합니다. #### `check-ui-value-drift` — 오래된 번역 게이트 다른 게이트가 구조적으로 감지할 수 없는 한 가지 i18n 회귀를 포착합니다. 영어 값이 -다시 작성되었지만 _이전_ 영어에서 파생된 번역은 그대로 남아 비영어권 사용자가 -확신에 찬 어조의, 이제는 잘못된 문구를 계속 읽게 되는 경우입니다. +재작성되었지만 _이전_ 영어에서 파생된 번역은 그대로 남아 있어, 영어가 아닌 언어의 +사용자가 확신에 찬 어조로 작성된, 이제는 잘못된 문구를 계속 읽게 되는 경우입니다. -이 문제는 실제로 배포되었습니다. Antigravity 로그인 도우미가 도입되었을 때(#5203) -`oauthModal.googleOAuthWarning`이 다시 작성되었지만, **43개 로케일 중 39개**에는 -운영자에게 "전체 URL을 복사하여 아래에 붙여넣으라"고 안내하는 문구가 그대로 남았습니다. -해당 프로바이더에서는 완료할 수 없는 흐름이었습니다. 이 문제는 다음 이유로 #8463까지 +이 문제는 실제로 배포되었습니다. Antigravity 로그인 도우미가 추가되었을 때(#5203) +`oauthModal.googleOAuthWarning`이 재작성되었지만, **43개 로케일 중 39개**에는 운영자에게 +"전체 URL을 복사하여 아래에 붙여 넣으라"고 안내하는 문구가 그대로 남아 있었습니다. +해당 제공자에서는 완료할 수 없는 흐름이었습니다. 이 문제는 다음 이유로 #8463까지 발견되지 않았습니다. -- `sync-ui-keys`는 **누락된** 키만 채우며, **오래된** 키는 절대 갱신하지 않습니다. -- `check-ui-keys-coverage`는 키의 _존재 여부_를 계산하므로, 오래된 번역도 적용된 것으로 집계됩니다. +- `sync-ui-keys`는 **없는** 키만 백필하며, **오래된** 키는 절대 처리하지 않습니다. +- `check-ui-keys-coverage`는 키의 _존재 여부_를 계산하므로, 오래된 번역도 커버된 것으로 간주됩니다. - `check-translation-drift`는 `docs/i18n//**.md` 문서 미러를 추적하며 — - `src/i18n/messages/*.json`은 절대 읽지 않습니다. 2026-09 재동기화 이후 작업 - `docs-sync-strict`에서 차단됩니다. 핵심 문서 편집 → `npm run i18n:run -- --files=` - (섹션 단위로 실행되며 비용이 적음). + `src/i18n/messages/*.json`은 전혀 읽지 않습니다. 2026-09 재동기화 이후 작업 + `docs-sync-strict`에서 차단됩니다. 핵심 문서를 편집하면 → `npm run i18n:run -- --files=`를 실행하세요(섹션 수준으로 처리되어 비용이 적음). -**diff를 인식하며, 기준선에 의존하지 않습니다.** 병합 베이스의 `en.json`과 -작업 트리를 비교합니다. 영어 값이 변경된 모든 키에 대해, 변경되지 않은 번역을 -여전히 유지하는 로케일은 오래된 것으로 간주됩니다. 이는 의도적으로 **기존 부채를 동결합니다**. -diff만으로는 오래된 번역이 어떤 이전 영어 문구에서 비롯되었는지 알 수 없으므로, 이 게이트는 -현재 변경 사항이 건드린 부분만 판단합니다. 대안인 키별 해시 기준선은 -약 600 KB의 생성 파일을 필요로 하며, 이는 현재 가장 큰 기준선의 3배이고 모든 i18n PR마다 변경됩니다. +**차이 인식 방식이며, 기준선에 의존하지 않습니다.** 병합 기준점의 `en.json`과 작업 트리를 비교합니다. 영어 값이 변경된 모든 키에 대해, 변경되지 않은 번역을 그대로 유지하는 로케일은 오래된 상태로 간주됩니다. 이는 의도적으로 **기존 부채를 동결**합니다. 차이만으로는 오래된 번역이 어떤 이전 영어 문구에서 비롯되었는지 알 수 없으므로, 이 게이트는 현재 변경 사항이 건드리는 부분만 판단합니다. 대안인 키별 해시 기준선은 약 600 KB의 생성 파일을 필요로 하며, 이는 기존 최대 기준선의 3배 크기이고 모든 i18n PR마다 변경됩니다. 이를 충족하는 방법은 두 가지입니다. 1. 영향을 받는 번역을 업데이트하거나 -2. 해당 값을 `__MISSING__:`로 설정합니다. 그러면 런타임이 수정된 영어를 제공하고 - (`src/i18n/request.ts::deepMergeFallback`, #7258) 해당 키는 번역 대기열에 추가됩니다. +2. 해당 값을 `__MISSING__:<새 영어 문구>`로 설정합니다. 그러면 런타임은 수정된 영어 문구를 제공하고 + (`src/i18n/request.ts::deepMergeFallback`, #7258), 해당 키는 번역 대기열에 추가됩니다. -문자열의 **의미**가 변경되었다면 **키 이름을 변경하는 것**이 좋습니다. 새 키는 -오래된 번역을 상속할 수 없습니다. #8463에서 사용한 패턴이 바로 이것입니다. +문자열의 **의미**가 변경되었다면 **키 이름을 변경**하는 것이 좋습니다. 새 키는 오래된 번역을 상속할 수 없습니다. #8463에서 이 패턴을 사용했습니다. ```bash npm run i18n:check-value-drift # 엄격 모드(CI에서 실행하는 방식) @@ -235,8 +226,7 @@ npm run i18n:check-value-drift:warn # 보고만 수행 BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -기본 카탈로그를 읽을 수 없는 경우(기본 ref가 없는 얕은 clone) -`check-openapi-breaking`과 동일하게 `SKIP reason=base-unresolved`를 출력하고 종료 코드 0으로 종료합니다. +기준 카탈로그를 읽을 수 없는 경우(기준 ref가 없는 얕은 클론), `check-openapi-breaking`과 동일하게 `SKIP reason=base-unresolved`를 출력하고 종료 코드 0으로 종료합니다. ### 작업: `i18n` @@ -244,39 +234,39 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift | 스크립트 | 검증 항목 | 차단 여부 | | ------------------------------- | -------------------- | ---------------------------------------------------- | -| `validate_translation.py quick` | 로케일별 번역 완성도 | **권고**(전체 작업에 `continue-on-error: true` 적용) | +| `validate_translation.py quick` | 로케일별 번역 완전성 | **권고**(전체 작업에 `continue-on-error: true` 적용) | ### 작업: `pr-test-policy` 풀 리퀘스트에서만 실행됩니다. -| 스크립트 | 검증 항목 | 차단 여부 | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` 또는 `bin/`의 프로덕션 코드를 변경하는 PR은 테스트를 포함하거나 업데이트해야 함(강경 규칙 #8) | 예 | -| `check:test-masking` | 변경된 테스트 파일이 순 어설션 수를 줄이거나 `assert.ok(true)` 항진식을 추가하지 않는지 확인 | 예 | -| `check:pr-evidence` | PR 본문에 변경 사항에 대한 테스트/VPS 증거가 명시되어 있는지 확인(PR 본문을 grep하여 강경 규칙 #18을 자동화함 — 취약하므로 Backlog 참조) | 예 | +| 스크립트 | 검증 항목 | 차단 여부 | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` 또는 `bin/`의 프로덕션 코드를 변경하는 PR은 테스트를 포함하거나 업데이트해야 함(강제 규칙 #8) | 예 | +| `check:test-masking` | 변경된 테스트 파일이 순 어설션 수를 줄이거나 `assert.ok(true)` 같은 항진 명제를 추가하지 않는지 확인 | 예 | +| `check:pr-evidence` | PR 본문에 변경 사항에 대한 테스트/VPS 근거가 명시되어 있는지 확인(PR 설명을 grep하여 강제 규칙 #18을 자동화 — 취약함, Backlog 참조) | 예 | ### 작업: `test-vitest` -`build` 후에 실행됩니다. 실패 시 병합을 차단합니다. +`build` 이후에 실행됩니다. 실패하면 병합을 차단합니다. -| 스위트 | 검증 항목 | 차단 여부 | -| ---------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP 서버(도구 110개), autoCombo, cache — vitest 러너 | 예 | -| `test:vitest:ui` | UI 컴포넌트 테스트 — vitest 러너 | **차단** — 기존 실패는 `vitest.config.ts`에서 명시적으로 제외되며, 새로운 실패가 발생하면 작업이 실패함 | +| 스위트 | 검증 항목 | 차단 여부 | +| ---------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP 서버(도구 110개), autoCombo, 캐시 — vitest 러너 | 예 | +| `test:vitest:ui` | UI 컴포넌트 테스트 — vitest 러너 | **차단** — 기존 실패 항목은 `vitest.config.ts`에서 명시적으로 제외되며, 새로운 실패는 작업을 실패 처리함 | ### 야간 워크플로(예약 실행, 권고) -cron 일정에 따라(그리고 `workflow_dispatch`를 통해) 실행되며, PR에서는 실행되지 않습니다. 모두 권고 사항입니다. +이 워크플로는 cron 일정에 따라 실행되며(`workflow_dispatch`로도 실행 가능), PR에서는 실행되지 않습니다. 모두 권고 사항입니다. | 워크플로 | 검증 항목 | 차단 여부 | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `nightly-property` | 무작위 seed와 높은 실행 횟수를 사용하는 fast-check 속성 테스트 | **권고** | -| `nightly-resilience` | heap 증가 게이트, chaos fault-injection, k6 부하/지속 테스트 | **권고** | -| `nightly-llm-security` | promptfoo injection guard(차단 모드) + garak probe(provider secret이 없으면 건너뜀) | **권고** | -| `nightly-schemathesis` | `docs/openapi.yaml`을 사용하여 실행 중인 OmniRoute를 대상으로 수행하는 OpenAPI 계약 퍼징(schemathesis) — 명세 위반/처리되지 않은 500 오류를 드러냄(단계 8 B.4) | **권고** | -| `nightly-mutation` | 빠른 단위 테스트 레인에 대한 Stryker mutation-testing 점수 — 살아남은 mutant는 취약한 어설션을 드러냄 | **권고** | -| `nightly-compat` | 지원되는 `engines.node` 범위 전반의 Node 엔진 호환성 매트릭스 | **권고** | +| `nightly-property` | 무작위 시드와 높은 실행 횟수를 사용하는 fast-check 속성 테스트 | **권고** | +| `nightly-resilience` | 힙 증가 게이트, 카오스 장애 주입, k6 부하/소크 테스트 | **권고** | +| `nightly-llm-security` | promptfoo 인젝션 가드(차단 모드) + garak 프로브(프로바이더 시크릿이 없으면 건너뜀) | **권고** | +| `nightly-schemathesis` | `docs/openapi.yaml`을 사용하여 실행 중인 OmniRoute를 대상으로 수행하는 OpenAPI 계약 퍼징(schemathesis) — 명세 위반/처리되지 않은 500 오류를 드러냄(Fase 8 B.4) | **권고** | +| `nightly-mutation` | 빠른 단위 테스트 레인에 대한 Stryker 변이 테스트 점수 — 살아남은 변이는 취약한 어설션을 드러냄 | **권고** | +| `nightly-compat` | 지원되는 `engines.node` 범위 전반에 걸친 Node 엔진 호환성 매트릭스 | **권고** | --- @@ -534,43 +524,43 @@ CI 게이트 외에도 OmniRoute는 **선택 사항**인 `agent-lsp` 스캐폴 --- -## 합리화 백로그 (ROI 검토 — 9단계 3차) +## 합리화 백로그(ROI 검토 — 9단계 3차) -이 인벤토리는 2026-06-17에 `ci.yml`을 기준으로 대조되었습니다(이전 버전에서는 +이 인벤토리는 2026-06-17에 `ci.yml`을 기준으로 대조되었습니다(이전 버전에는 `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`가 누락됨). 대조된 항목에 대한 ROI 검토를 통해 -다음과 같은 합리화 후보가 식별되었습니다. **병합은 기계적인 CI 변경이며, -전환/제거는 운영자에게 유보된 정책 결정입니다.** 아래 내용은 아직 +`check:codeql-ratchet`, `check:pr-evidence`가 누락되어 있었습니다). 대조된 항목에 대한 ROI 검토를 통해 +다음과 같은 합리화 후보를 식별했습니다. **병합은 기계적인 CI +변경이며, 전환/제거는 운영자가 결정해야 하는 정책 사항입니다.** 아래 항목은 아직 적용되지 않았습니다. -**위에서 문서화되지 않은 추가 항목**(권고 수준, 신호가 약함): `docs-lint` 작업 -(markdownlint + Vale, 전체 작업에 `continue-on-error` 적용) 및 독립형 스캐너 워크플로 +**위에 문서화되지 않은 추가 항목**(권고 수준, 낮은 신호): `docs-lint` 작업 +(markdownlint + Vale, 전체 작업에 `continue-on-error` 적용)과 독립형 스캐너 워크플로 `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0`은 -`quality-baseline.json`에 있지만 `ci.yml`의 차단형 래칫과 연결되어 있지 않습니다. 현재 이 메트릭은 +`quality-baseline.json`에 있지만 `ci.yml`의 차단형 래칫과 연결되어 있지 않습니다. 즉, 현재 이 메트릭은 고립된 상태입니다. -### 병합 / 중복 제거 (기계적, 위험도 낮음) +### 병합 / 중복 제거(기계적, 낮은 위험) 각 후보는 2026-06-17의 실제 게이트 상태를 기준으로 검증되었습니다(신뢰하되 검증); -몇몇 "명백한" 병합이 실제로는 부채를 숨기고 있는 것으로 드러났으며 **즉시 대체 가능한 깔끔한 변경이 아닙니다**. - -- **`check:docs-sync`가 두 번 실행됨** — `lint` 작업에서 독립적으로 실행되고, `check:docs-all` 내부(`docs-sync-strict`)와 husky pre-commit 훅에서 다시 실행됩니다. ✅ **완료** — 독립적인 `lint` 호출이 제거되었습니다. -- **CVE 스캔** — ❌ **깔끔하게 병합할 수 없음.** `audit:deps`는 high/critical CVE가 하나라도 있으면 즉시 실패합니다. `check:vuln-ratchet`(osv)은 기준선(현재 MODERATE 1건) 대비 _회귀_가 있을 때만 실패합니다. 의미 체계가 다르므로 `audit:deps`를 제거하면 절대적인 high/critical 게이트를 잃게 됩니다. 둘 다 유지합니다. -- **순환 종속성 탐지** — ❌ **깔끔하게 병합할 수 없음.** `check:circular-deps`(dpdm)는 **91개의 순환**을 보고합니다(이 때문에 권고 수준임). 이를 먼저 해결하지 않고는 차단형으로 승격할 수 없으며, 정상 상태인 선별된 `check:cycles`보다 범위가 더 넓습니다. `check:cycles`는 차단형으로 유지하고, dpdm 순환 91개 해결은 별도의 백로그로 관리합니다. -- **복잡도** — ✅ **완료** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ESLint를 한 번 순회하며 ruleId별로 집계하므로 cyclomatic+max-lines와 cognitive 기준선이 서로 독립적으로 유지됩니다. 개별 `check:complexity` / `check:cognitive-complexity`는 로컬 `--update`용으로 유지됩니다. -- **`/api` 환각 방지** — ✅ **완료** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`의 FS 인벤토리를 한 번만 생성하며, openapi-routes + docs-symbols는 계속 독립적으로 보고합니다. 개별 항목은 로컬 실행용으로 유지됩니다. -- **`check:node-runtime`이 11개 작업에서 실행됨** — ⚠️ **ROI 낮음.** 각각 별도의 러너이며 검사는 1초 미만입니다. 총 절감 시간은 약 10초지만, 작업별로 저렴한 보호 장치를 잃게 됩니다. 변경에 따른 혼란을 감수할 가치가 없습니다. -- **CI lint의 `typecheck:noimplicit:core`** — ✅ **lint 작업에서 제거됨**(기존에는 권고 수준의 `continue-on-error`였음). 차단형 타입 표면은 `typecheck:core` + `check:type-coverage`입니다. 로컬 스크립트는 유지되었습니다. - -### 전환 / 결정 (운영자 정책) - -- `check:openapi-security-tiers` (권고 수준) — ❌ **깔끔하게 전환할 수 없음.** 종료 코드는 0이지만 `LOCAL_ONLY_API_PREFIXES` 아래의 여러 `traffic-inspector` 라우트에 `x-loopback-only: true` 주석이 없다고 경고합니다. 이를 강제하려면 먼저 해당 주석을 `openapi.yaml`에 추가해야 합니다. -- `typecheck:noimplicit:core` (권고 수준) — 차단형 `check:type-coverage` 래칫에 대부분 포함됩니다. 래칫으로 전환하거나 중복되는 두 번째 `tsc` 패스를 제거합니다. -- `test:vitest:ui` (현재 **차단형**) — 기존 실패는 `vitest.config.ts`에서 `// #8618` 추적 주석과 함께 명시적으로 제외되어 있으며, 새로운 실패는 작업 실패로 처리됩니다. -- `check:secrets` (gitleaks, 문서화된 오탐 3건에 고정된 차단형 래칫) — 3건을 허용 목록에 추가하여 0으로 만들거나 권고 수준으로 강등합니다. GitHub 네이티브 secret-scanning + `check:public-creds`와 중복됩니다. -- `check:pr-evidence` (차단형, PR 본문을 grep으로 검사) — 오탐 위험이 높습니다. 제거하면 강제 규칙 #18의 집행력이 약화되므로 이는 실질적인 정책 결정 사항입니다. -- `semgrep` (권고 수준의 독립형 항목) — OWASP 계열에 대해 CodeQL과 중복됩니다. 기준선을 래칫에 연결하거나 제거합니다. +여러 "명백한" 병합이 실제로는 부채를 숨기고 있는 것으로 드러났으며 **즉시 대체할 수 있는 깔끔한 변경이 아닙니다**. + +- **`check:docs-sync`가 두 번 실행됨** — `lint` 작업에서 독립적으로 실행되고, `check:docs-all`(`docs-sync-strict`) 및 husky pre-commit 훅 내부에서 다시 실행됩니다. ✅ **완료** — 독립적인 `lint` 호출을 제거했습니다. +- **CVE 스캔** — ❌ **깔끔하게 병합할 수 없습니다.** `audit:deps`는 high/critical CVE가 하나라도 있으면 즉시 실패합니다. 반면 `check:vuln-ratchet`(osv)은 기준선(현재 MODERATE 1개) 대비 _회귀_가 있을 때만 실패합니다. 의미 체계가 다르므로 `audit:deps`를 제거하면 high/critical에 대한 절대적 게이트를 잃게 됩니다. 둘 다 유지합니다. +- **순환 감지** — ✅ **완료**(#15159 G-01/G-02). 이전 설명에서는 `check:cycles`를 "정상 상태인 선별된" 게이트로 부르고, `check:circular-deps`(dpdm)가 순환 91개를 보고했기 때문에 이를 차단형으로 유지해야 한다고 정당화했습니다. 그러나 그 정상 상태는 **거짓 정상**이었습니다. `check:cycles`는 5개 하위 디렉터리(파일 450개)를 스캔하고 정적 `import|export … from`만 일치시켰으며, 모든 `@/` 및 `@omniroute/open-sse/` 지정자를 누락했기 때문에 저장소의 대부분을 차지하는 동적 import + 별칭 순환을 감지할 수 없었습니다. 수정 후 게이트는 이제 `src` + `open-sse`(파일 5023개)를 순회하고 TypeScript AST에서 지정자를 수집하며(`import("…")`는 계산하고 타입 위치의 `typeof import("…")`는 계산하지 않음), tsconfig `paths`를 해석합니다. 발견되는 순환은 0개가 아니라 **14개**입니다. 기존 순환 14개는 게이트 PR에서 수정할 수 없으므로, `check:cycles`는 이제 **래칫**입니다(`--ratchet`, `quality-baseline.json`의 상한 `metrics.cycles.value = 14`, `direction: down`). 모든 _회귀_를 차단하며 개수는 감소만 가능합니다. CI는 `npm run check:cycles:ratchet`을 실행합니다. 점진적 감소 작업은 **A-01**과 함께 진행합니다. `check:circular-deps`(dpdm)는 더 광범위한 보조 검증 수단으로서 권고 수준으로 유지합니다. +- **복잡도** — ✅ **완료**(`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): 단일 ESLint 순회에서 ruleId별로 집계하므로 순환 복잡도+최대 줄 수와 인지 복잡도 기준선이 서로 독립적으로 유지됩니다. 개별 `check:complexity` / `check:cognitive-complexity`는 로컬 `--update`를 위해 유지합니다. +- **`/api` 환각 방지** — ✅ **완료**(`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`에 대한 단일 FS 인벤토리를 사용하며, openapi-routes + docs-symbols는 계속 독립적으로 보고합니다. 개별 항목은 로컬 실행을 위해 유지합니다. +- **`check:node-runtime`이 11개 작업에서 실행됨** — ⚠️ **낮은 ROI.** 각각 별도의 러너이며 검사는 1초 미만입니다. 총 절감 시간은 약 10초인 반면, 저비용 작업별 가드가 사라집니다. 변경에 따른 혼란을 감수할 가치가 없습니다. +- **CI lint의 `typecheck:noimplicit:core`** — ✅ **lint 작업에서 제거됨**(기존에는 `continue-on-error`가 적용된 권고 수준이었음). 차단형 타입 범위는 `typecheck:core` + `check:type-coverage`입니다. 로컬 스크립트는 유지했습니다. + +### 전환 / 결정(운영자 정책) + +- `check:openapi-security-tiers`(권고 수준) — ❌ **깔끔하게 전환할 수 없습니다.** 종료 코드는 0이지만, `LOCAL_ONLY_API_PREFIXES` 아래의 여러 `traffic-inspector` 라우트에 `x-loopback-only: true` 주석이 없다고 경고합니다. 이를 강제하려면 먼저 해당 주석을 `openapi.yaml`에 추가해야 합니다. +- `typecheck:noimplicit:core`(권고 수준) — 차단형 `check:type-coverage` 래칫이 대부분 대체합니다. 래칫으로 전환하거나 중복되는 두 번째 `tsc` 패스를 제거합니다. +- `test:vitest:ui`(현재 **차단형**) — 기존 실패는 `vitest.config.ts`에서 `// #8618` 추적 주석과 함께 명시적으로 제외되어 있습니다. 새로운 실패가 발생하면 작업이 실패합니다. +- `check:secrets`(gitleaks, 문서화된 오탐 3개에서 고정된 차단형 래칫) — 3개 항목을 허용 목록에 추가하여 0을 달성하거나 권고 수준으로 강등합니다. GitHub 네이티브 비밀 스캔 + `check:public-creds`와 중복됩니다. +- `check:pr-evidence`(차단형, PR 본문 산문을 grep으로 검색) — 오탐 위험이 높습니다. 제거하면 Hard Rule #18의 강제력이 약화되므로 이는 실질적인 정책 결정 사항입니다. +- `semgrep`(권고 수준의 독립형 작업) — OWASP 계열에 대해 CodeQL과 중복됩니다. 기준선을 래칫에 연결하거나 제거합니다. --- diff --git a/docs/i18n/ko/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ko/docs/compression/COMPRESSION_GUIDE.md index 8c8189775ac9..fec3420e1aae 100644 --- a/docs/i18n/ko/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ko/docs/compression/COMPRESSION_GUIDE.md @@ -436,7 +436,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages`가 있는 본문에서 콘텐츠 우회 (`open-sse/services/compression/outputMode.ts`의 `shouldBypassCavemanOutputMode()`)는 마지막 세 메시지를 확인하고, 해당 보안, 되돌릴 수 없는 작업, 명확화 또는 순서에 민감한 키워드와 일치할 경우 전체 턴에 대한 스타일을 건너뜁니다. -이 우회는 대시보드의 **자동 명확성 우회** 토글 (`cavemanOutputMode.autoClarity`) 설정과 관계없이 실행됩니다. +이 우회는 대시보드의 **자동 명확성 우회** 토글 (`cavemanOutputMode.autoClarity`) 이 켜져 있는 동안 실행되며, 이것이 기본값입니다. 토글이 꺼져 있으면 선택한 스타일이 해당 턴에도 적용됩니다. 우회가 턴을 통과시키면, `placeSystemInstruction()` (동일 파일)은 새 `messages[0]`를 절대 생성하지 않으며, 다음 중 첫 번째로 발견되는 위치에 블록을 배치합니다. diff --git a/docs/i18n/ko/docs/reference/ENVIRONMENT.md b/docs/i18n/ko/docs/reference/ENVIRONMENT.md index b5bd2ce765d8..402b98ce5838 100644 --- a/docs/i18n/ko/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ko/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. 필수 시크릿 -다음 값은 최초 실행 전에 **반드시** 설정해야 합니다. 설정하지 않으면 애플리케이션이 시작을 거부하거나 안전하지 않은 기본값으로 작동합니다. - -| 변수 | 필수 여부 | 기본값 | 소스 파일 | 설명 | -| ---------------------------- | ----------------- | ---------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **예** | _(없음)_ | `src/lib/auth` | 모든 대시보드 세션 쿠키(JWT)를 서명/검증합니다. `openssl rand -base64 48`로 생성하세요. | -| `API_KEY_SECRET` | **예** | _(없음)_ | `src/lib/db/apiKeys.ts` | SQLite에 저장되는 API 키 값을 보호하는 AES 암호화 키입니다. `openssl rand -hex 32`로 생성하세요. | -| `INITIAL_PASSWORD` | **예** | `CHANGEME` | 부트스트랩 스크립트 | 초기 관리자 대시보드 비밀번호를 설정합니다(`.env.example` 기본값과 일치하며, 변경을 유도하기 위해 의도적으로 안전하지 않은 값으로 유지됩니다). **처음 사용하기 전에 변경하세요.** 로그인 후 대시보드 → 설정 → 보안에서 변경할 수 있습니다. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **예** (프로덕션) | _(설정되지 않음)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 내부 Codex Responses WebSocket 브리지용 공유 시크릿입니다. Electron/브라우저 WS 릴레이와 OmniRoute 간의 브리지 요청을 인증합니다. ⚠️ **프로덕션에서는 필수입니다. 설정하지 않으면 모든 WS 브리지 요청이 거부됩니다.** `openssl rand -base64 32`로 생성하세요. | -| `OMNIROUTE_SW_BUILD_ID` | 아니요 | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA 셸(#11779)을 위한 명시적인 서비스 워커 캐시 무효화 ID이며, 확인 순서에서 첫 번째입니다. | -| `SOURCE_VERSION` | 아니요 | _(설정되지 않음)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | 확인 순서에서 두 번째이며, PaaS 빌더(예: Heroku 스타일)가 배포된 커밋 값으로 설정합니다. | -| `NEXT_PUBLIC_SW_BUILD_ID` | 아니요 | _(파생됨)_ | `src/shared/components/PwaRegister.tsx` | 클라이언트가 `/sw.js?v=…` 등록에 사용하는 빌드 타임 공개 값입니다. 위 두 값에서 파생되며, 두 값이 없으면 git SHA를 사용합니다. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | 아니요 (자동) | _(부팅마다 자동 생성)_ | `src/server/authz/policies/management.ts` | 신뢰할 수 있는 피어 IP 스탬프가 OmniRoute 자체 HTTP 서버(`scripts/dev/peer-stamp.mjs`)에서 생성되었음을 증명하는 프로세스별 시크릿입니다. authz 미들웨어는 스탬프에 이 토큰이 포함된 경우에만 요청의 로컬성(LOCAL_ONLY 경로의 루프백/LAN 제한)을 신뢰합니다. 부팅할 때마다 자동 생성되므로 설정하지 마세요. 스탬프를 공유해야 하는 다중 프로세스 구성에서만 값을 고정하세요. | -| `OMNIROUTE_TRUSTED_PROXIES` | 아니요 | _(설정되지 않음)_ | `scripts/dev/peer-stamp.mjs` | IP 허용/차단 목록이 해당 프록시의 `X-Forwarded-For` / `X-Real-IP`를 신뢰할 수 있도록 하는 역방향 프록시 IP 또는 CIDR 범위의 쉼표로 구분된 목록입니다. 항상 신뢰되는 루프백, 사설 네트워크 주소 및 Cloudflare 에지에 추가로 적용됩니다. 그 외의 공인 주소에서 프록시를 사용하는 경우에만 필요합니다. 그렇지 않으면 필터는 프록시 자체 주소를 기준으로 판단합니다. | +**예**로 표시되었지만 설정되지 않은 시크릿은 최초 실행 시 자동으로 생성되어(`JWT_SECRET`, `API_KEY_SECRET`) 데이터 디렉터리의 `server.env`에 저장됩니다. 해당 디렉터리가 휘발성인 경우에는 명시적으로 설정하세요. 예외는 `OMNIROUTE_WS_BRIDGE_SECRET`입니다. 이 값은 `server.env`에 저장되지 않습니다. 서버 진입점이 부팅할 때마다 프로세스 내부의 브리지만 사용할 수 있는 휘발성 값을 새로 발급하므로, 외부 브리지 클라이언트에는 명시적인 설정이 필요합니다. + +| 변수 | 필수 여부 | 기본값 | 소스 파일 | 설명 | +| ---------------------------- | ----------------- | ---------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **예** | _(없음)_ | `src/lib/auth` | 모든 대시보드 세션 쿠키(JWT)를 서명/검증합니다. `openssl rand -base64 48`로 생성하세요. | +| `API_KEY_SECRET` | **예** | _(없음)_ | `src/lib/db/apiKeys.ts` | SQLite에 저장되는 API 키 값을 보호하는 AES 암호화 키입니다. `openssl rand -hex 32`로 생성하세요. | +| `INITIAL_PASSWORD` | 아니요 | _(설정되지 않음)_ | `src/lib/auth/managementPassword.ts` | 저장된 비밀번호가 아직 없는 동안 시작 시 대시보드 비밀번호를 설정하며, 비밀번호가 저장되고 나면 무시됩니다. npm 설치와 소스 설치는 `.env.example`을 `.env`로 복사하므로 공개적으로 알려진 자리 표시자 `CHANGEME`와 함께 시작합니다. 최초 부팅 전에 교체하거나, 해당 `.env`에 `INITIAL_PASSWORD=`(빈 값)를 설정하여 설정되지 않은 값(Docker 이미지의 기본값)이 그러하듯 첫 방문 시 설정 마법사에서 비밀번호를 생성하게 하세요. 저장된 비밀번호는 대시보드 → 설정 → 보안에서 변경하거나, `DATA_DIR`을 서버의 데이터 디렉터리로 지정한 상태에서 `omniroute reset-password`(소스 체크아웃에서는 `node bin/reset-password.mjs`)을 실행하여 변경할 수 있습니다. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **예** (프로덕션) | _(설정되지 않음)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 내부 Codex Responses WebSocket 브리지용 공유 시크릿입니다. Electron/브라우저 WS 릴레이와 OmniRoute 간의 브리지 요청을 인증합니다. ⚠️ **프로덕션에서는 필수입니다. 설정하지 않으면 모든 WS 브리지 요청이 거부됩니다.** `openssl rand -base64 32`로 생성하세요. | +| `OMNIROUTE_SW_BUILD_ID` | 아니요 | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA 셸(#11779)을 위한 명시적인 서비스 워커 캐시 무효화 ID이며, 확인 순서에서 첫 번째입니다. | +| `SOURCE_VERSION` | 아니요 | _(설정되지 않음)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | 확인 순서에서 두 번째이며, PaaS 빌더(예: Heroku 스타일)가 배포된 커밋 값으로 설정합니다. | +| `NEXT_PUBLIC_SW_BUILD_ID` | 아니요 | _(파생됨)_ | `src/shared/components/PwaRegister.tsx` | 클라이언트가 `/sw.js?v=…` 등록에 사용하는 빌드 타임 공개 값입니다. 위 두 값에서 파생되며, 두 값이 없으면 git SHA를 사용합니다. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | 아니요 (자동) | _(부팅마다 자동 생성)_ | `src/server/authz/policies/management.ts` | 신뢰할 수 있는 피어 IP 스탬프가 OmniRoute 자체 HTTP 서버(`scripts/dev/peer-stamp.mjs`)에서 생성되었음을 증명하는 프로세스별 시크릿입니다. authz 미들웨어는 스탬프에 이 토큰이 포함된 경우에만 요청의 로컬성(LOCAL_ONLY 경로의 루프백/LAN 제한)을 신뢰합니다. 부팅할 때마다 자동 생성되므로 설정하지 마세요. 스탬프를 공유해야 하는 다중 프로세스 구성에서만 값을 고정하세요. | +| `OMNIROUTE_TRUSTED_PROXIES` | 아니요 | _(설정되지 않음)_ | `scripts/dev/peer-stamp.mjs` | IP 허용/차단 목록이 해당 프록시의 `X-Forwarded-For` / `X-Real-IP`를 신뢰할 수 있도록 하는 역방향 프록시 IP 또는 CIDR 범위의 쉼표로 구분된 목록입니다. 항상 신뢰되는 루프백, 사설 네트워크 주소 및 Cloudflare 에지에 추가로 적용됩니다. 그 외의 공인 주소에서 프록시를 사용하는 경우에만 필요합니다. 그렇지 않으면 필터는 프록시 자체 주소를 기준으로 판단합니다. | ### 생성 명령어 @@ -240,11 +240,11 @@ OmniRoute는 모든 영속성 처리에 **SQLite**(`better-sqlite3` 경유)를 | `CORS_ORIGIN` | _(설정되지 않음)_ | `src/server/cors/origins.ts` | 레거시 단일 오리진 CORS 허용 목록입니다. 새 배포에서는 `CORS_ALLOWED_ORIGINS`를 사용하는 것이 좋습니다. CORS는 교차 오리진 브라우저 API 클라이언트에만 사용됩니다. 인증된 대시보드 쓰기 작업은 대신 세션에 바인딩된 CSRF 보호와 함께 동일 오리진 요청을 사용합니다. | | `CORS_ALLOWED_ORIGINS` | _(설정되지 않음)_ | `src/server/cors/origins.ts` | 쉼표로 구분된 CORS 허용 목록입니다. `CORS_ALLOW_ALL=true`가 명시적으로 구성되지 않는 한 와일드카드는 전송되지 않습니다. | | `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 모든 브라우저 `Origin`을 그대로 반환하도록 하는 개발 전용 우회 옵션입니다. 공유 또는 프로덕션 배포에서는 활성화하지 마세요. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | 사설/루프백/링크 로컬 IP 범위를 대상으로 하는 공급자 호출을 차단합니다. 격리된 테스트 환경에서만 비활성화하세요. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | 사설/로컬 네트워크(localhost, 192.168.x.x, 10.x.x.x 등)를 가리키는 공급자 URL을 허용합니다. **자체 호스팅 공급자에 필수입니다**(LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). `false`이면 대시보드에서 로컬 URL 검증을 거부합니다. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | 공급자 검증 경로로 범위가 제한된 상태에서 로컬/사설 주소(127.0.0.1, localhost, LAN, 사설 범위)의 공급자를 추가하거나 검증할 수 있도록 허용합니다. **기본값은 `true`**(로컬 우선)이며, 공개 주소만 엄격하게 허용하려면 `false`로 설정하세요. 클라우드 메타데이터 엔드포인트(169.254.169.254, metadata.google.internal)는 설정과 관계없이 계속 차단됩니다. (#5066) | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | 레거시 별칭입니다. 이 변수의 대시보드 토글(DB 오버라이드)은 이 환경 변수 값보다 먼저 읽힙니다. 둘 중 하나에 `false`, `0`, `no`, `off`가 있으면 `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`가 그러하듯 호스트 검사를 끄며, 해당 플래그의 대시보드 토글이 꺼짐으로 저장되어 있어도 마찬가지입니다. 그 외의 값은 가드를 아래 두 플래그에 맡깁니다. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | 가드 모드를 따르는 공급자 경로, 즉 URL 검증, 모델 탐색, 공급자 노드 기본 URL(원격 재순위 지정 노드 포함), 프록시 폴백 테스트, 그리고 원격 이미지/미디어 가져오기 같은 그 밖의 공급자 인접 아웃바운드 경로에서 아웃바운드 URL 가드의 호스트 검사를 클라우드 메타데이터 차단(169.254.0.0/16 전체, metadata.google.internal 포함)과 함께 끕니다. 클라우드 메타데이터/링크 로컬 엔드포인트는 예외로, 이 플래그가 켜져 있어도 계속 차단되며, 그 외의 사설 웹훅 대상은 허용합니다. `BaseExecutor.execute()`를 통해 발송되는 채팅 요청은 클라우드 메타데이터 검사를 유지합니다. 로컬 공급자는 기본값으로 동작합니다. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS`(기본값 `true`)이 검증, 탐색, 공급자 노드 경로에서 로컬 및 LAN URL을 이미 허용하기 때문입니다. 프록시 폴백 테스트와 사설 웹훅 대상은 이 플래그만 참조하며, 꺼져 있는 동안에는 로컬/LAN 호스트를 차단합니다. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`를 재정의합니다. 대시보드 토글에서 저장된 값은 이 변수보다 우선합니다. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | 공급자 검증, 모델 탐색, 채팅, 그리고 가드 모드가 관할하는 그 밖의 공급자 인접 아웃바운드 경로에 대해 로컬/사설 주소(127.0.0.1, localhost, LAN, 사설 범위)의 공급자 URL을 허용합니다. **기본값은 `true`**(로컬 우선)이며, 이때 가드는 클라우드 메타데이터 엔드포인트(169.254.0.0/16 전체와 알려진 메타데이터 호스트 이름들: metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254)를 차단합니다. 공개 전용 모드로 설정하려면 `false`로 설정하세요. 이 모드는 사설 및 루프백 호스트도 차단합니다. 가드는 DNS 조회 전에 호스트 이름이나 IP 리터럴을 표기된 그대로 검사하며, 공개 전용 모드에서는 safe-fetch 래퍼가 추가로 호스트 이름을 확인(resolve)해 사설 응답은 모두 거부합니다. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`는 해당 행에 나열된 경로에서 이 검사들을 끕니다. LM Studio, Ollama, vLLM, Llamafile, Triton 같은 내장 로컬 공급자는 키 검증과 채팅에서 모든 호스트 검사를 건너뜁니다. 대시보드 토글에서 저장된 값은 이 변수보다 우선합니다. (#5066) | | `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` 경로(전사, 음성, 번역)가 localhost 외부에서 호스팅되는 OpenAI 호환 공급자 노드를 사용하도록 허용합니다. 기본적으로 비활성화되어 있습니다. 오디오를 원격 호스트로 라우팅하면 이그레스 ID가 변경되므로 운영자가 명시적으로 결정해야 합니다. 루프백/사설 노드(localhost, 127.0.0.1, 172.16-31.x)는 항상 허용되며 영향을 받지 않습니다. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`(및 메모리 엔진의 루프백 재순위 지정 단계)가 localhost 외부에서 호스팅되는 OpenAI 호환 공급자 노드(TEI, Infinity, vLLM 등을 실행하는 LAN 장비 또는 Tailscale 피어)를 사용하도록 허용합니다. 기본적으로 비활성화되어 있습니다. 원격 호스트로 라우팅하면 이그레스 ID가 변경되므로 운영자가 명시적으로 결정해야 합니다. 루프백 노드(localhost, 127.0.0.1, 172.16-31.x)는 항상 허용되며 영향을 받지 않습니다. 원격 노드는 공급자의 아웃바운드 URL 정책(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)도 통과해야 하며, 클라우드 메타데이터 호스트로는 절대 라우팅되지 않습니다. | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`(및 메모리 엔진의 루프백 재순위 지정 단계)가 localhost 외부에서 호스팅되는 OpenAI 호환 공급자 노드(TEI, Infinity, vLLM 등을 실행하는 LAN 장비 또는 Tailscale 피어)를 사용하도록 허용합니다. 기본적으로 비활성화되어 있습니다. 원격 호스트로 라우팅하면 이그레스 ID가 변경되므로 운영자가 명시적으로 결정해야 합니다. 루프백 노드(localhost, 127.0.0.1, 172.16-31.x)는 항상 허용되며 영향을 받지 않습니다. 원격 노드의 기본 URL 역시 공급자의 아웃바운드 URL 정책(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)을 통과해야 합니다. | | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC가 활성화된 경우 사용자가 OIDC Single Sign-On을 통해서만 인증할 수 있도록 비밀번호 로그인을 비활성화합니다. 단축 별칭 `OIDC_DISABLE_PASSWORD_LOGIN`도 허용되며, 같은 키를 사용하는 대시보드 기능 플래그가 우선합니다. (#10889) | | `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`의 단축 별칭입니다. (#10889) | diff --git a/docs/i18n/lt/CONTRIBUTING.md b/docs/i18n/lt/CONTRIBUTING.md index ccccf3fe7df5..e2967aa4145d 100644 --- a/docs/i18n/lt/CONTRIBUTING.md +++ b/docs/i18n/lt/CONTRIBUTING.md @@ -400,22 +400,22 @@ Parašykite vienetinius testus kataloge `tests/unit/`, kurie apimtų bent: --- -## Pull Request kontrolinis sąrašas +## „Pull Request“ kontrolinis sąrašas - [ ] Testai sėkmingi (`npm test`) -- [ ] Linting patikra sėkminga (`npm run lint`) +- [ ] Statinė analizė sėkminga (`npm run lint`) - [ ] Kompiliavimas sėkmingas (`npm run build`) -- [ ] Pridėti naujų viešųjų funkcijų ir sąsajų TypeScript tipai -- [ ] Nėra kode įrašytų paslapčių ar atsarginių reikšmių -- [ ] Vieši pirminio teikėjo prisijungimo duomenys įterpti naudojant `resolvePublicCred()` (žr. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), o ne kaip literalai -- [ ] Klaidų atsakymai apdorojami naudojant `buildErrorBody()` / `sanitizeErrorMessage()` — atsakymų turinyje nėra neapdorotų dėklo išklotinių (žr. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Apvalkalo komandos (`exec` / `spawn`) vykdymo meto reikšmes perduoda per `env`, o ne naudodamos eilučių interpoliaciją +- [ ] Naujoms viešosioms funkcijoms ir sąsajoms pridėti TypeScript tipai +- [ ] Nėra kode tiesiogiai įrašytų paslapčių ar atsarginių reikšmių +- [ ] Vieši išorinės sistemos prisijungimo duomenys įterpiami naudojant `resolvePublicCred()` (žr. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), niekada nenaudojant literalų +- [ ] Klaidų atsakymai apdorojami per `buildErrorBody()` / `sanitizeErrorMessage()` — atsakymų turinyje nėra neapdorotų dėklo trasų (žr. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Apvalkalo komandos (`exec` / `spawn`) vykdymo reikšmes perduoda per `env`, o ne naudodamos eilučių interpoliaciją - [ ] Visos įvestys patikrintos naudojant Zod schemas -- [ ] Naudotojams matomiems pakeitimams pridėtas pakeitimų žurnalo **fragmentas** kataloge `changelog.d/{features|fixes|maintenance}/-.md` (žr. [`changelog.d/README.md`](./changelog.d/README.md)) — **neredaguokite** `CHANGELOG.md` tiesiogiai; fragmentai sujungiami leidimo metu ir niekada nesukelia konfliktų tarp PR +- [ ] Naudotojams matomiems pakeitimams pridėtas pakeitimų žurnalo **fragmentas**, esantis `changelog.d/{features|fixes|maintenance}/-.md` (žr. [`changelog.d/README.md`](./changelog.d/README.md)) — **neredaguokite** `CHANGELOG.md` tiesiogiai; fragmentai sujungiami leidimo metu ir niekada nesukelia konfliktų tarp „Pull Request“ užklausų - [ ] Dokumentacija atnaujinta (jei taikoma) -- [ ] Neatsirado naujų CodeQL / Secret-Scanning įspėjimų arba kiekvienas iš jų atmestas pateikus techninį pagrindimą su nuoroda į atitinkamą `docs/security/` dokumentą -- [ ] Maršrutai, paleidžiantys antrinius procesus (`/api/mcp/`, `/api/cli-tools/runtime/`), faile `src/server/authz/routeGuard.ts` klasifikuojami kaip `isLocalOnlyPath()` — žr. [Griežtąją taisyklę Nr. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Įvykdymo pranešimuose nėra `Co-Authored-By` galinių eilučių — įvykdymai turi būti rodomi tik su saugyklos savininko Git tapatybe (Griežtoji taisyklė Nr. 16) +- [ ] Neatsirado naujų CodeQL / Secret-Scanning įspėjimų arba kiekvienas jų atmestas pateikus techninį pagrindimą su nuoroda į atitinkamą `docs/security/` dokumentą +- [ ] Maršrutai, paleidžiantys antrinius procesus (`/api/mcp/`, `/api/cli-tools/runtime/`), faile `src/server/authz/routeGuard.ts` klasifikuoti kaip `isLocalOnlyPath()` — žr. [griežtą taisyklę Nr. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Įvykdymo pranešimuose nėra DI / robotų `Co-authored-by` užbaigiamųjų eilučių (griežta taisyklė Nr. 16) — žmonės bendradarbiai, kurių darbas panaudojamas pakartotinai, nurodomi standartinėmis `Co-authored-by: Name ` užbaigiamosiomis eilutėmis --- diff --git a/docs/i18n/lt/docs/architecture/QUALITY_GATES.md b/docs/i18n/lt/docs/architecture/QUALITY_GATES.md index 873d550b4e3c..cfcb55fc496a 100644 --- a/docs/i18n/lt/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/lt/docs/architecture/QUALITY_GATES.md @@ -21,213 +21,215 @@ tos pačios sistemos atkūrimo planą rasite CI ir kokybės užkardų darbo eigos pateikia po stabilų verdiktą: `Gate / CI` ir `Gate / Quality`. Jų versijuojamoje priėmimo politikoje kiekviena ankstesnė užduotis -įvardijama kaip privaloma arba rekomendacinė. Taikoma privaloma užduotis turi būti sėkminga: -trūkstami, atšaukti, praleisti, laukiantys ir nežinomi rezultatai negali patvirtinti PASS. -Galiojanti tik dokumentacijos arba tik katalogo klasifikacija gali padaryti kodo vykdymo šaką -netaikomą; juodraštinis PR nelaikomas priimtu kandidatu. Žyma `hotfix` neatleidžia nuo -įrodymų pateikimo. - -Abi darbo eigos apima PR ir siuntimus į pagrindines / leidimo šakas, rankinį paleidimą ir -sujungimo grupių įvykius. Siuntimo, paleidimo ir sujungimo grupių atvejais vykdomas visas -rinkinys. Išsišakojusios saugyklos ir sujungimo grupės naudoja prieglobos vykdykles užduotims, -kurios kitais atvejais pasirinktų savarankiškai administruojamas vykdykles; prieš diegimą -reikia patikrinti, ar prieglobos pajėgumų pakanka. - -Kiekviename JSON kvite nurodomi paimtos versijos SHA, darbo eigos vykdymas ir bandymas. -CLI atmeta neatitiktį tarp paimtos versijos ir įvykio SHA. Darbo eigos testai susieja -politikos narystę su verdikto užduoties `needs` sąrašu, kad nauja arba pašalinta vykdymo šaka -negalėtų nepastebimai dingti. Kvitai apima tik savo darbo eigą, o ne publikavimą, diegimą ar -esamo rekomendacinio skaitytuvo vidinį veikimą. Abiejų patikros pavadinimų aktyvinimas šakų -taisyklėse yra atskiras administracinis pakeitimas; vien šių užduočių pridėjimas šakos -neapsaugo. - -### Statinės patikros inventorius - -Versijuojamas npm alternatyviųjų pavadinimų inventorius ir statinės patikros narystė saugomi -`config/quality/gate-manifest.json`. Paleiskite `npm run check:gate-manifest`, kad pagal -`package.json` patikrintumėte scenarijų pavadinimus ir tikslias komandas; papildymai, -pašalinimai ir komandų neatitikimai lemia tiek vietinio kabliuko, tiek pakeitimų -klasifikavimo užduočių CI aplinkoje nesėkmę. Alternatyvusis pavadinimas nėra darbo eigos -užduotis, matricos egzempliorius ar testo atvejis: šių skaičių negalima pateikti kaip -tarpusavyje pakeičiamų. - -Naudokite `npm run quality:scan -- --list` arba `npm run quality:scan:fast -- --list`, -kad peržiūrėtumėte pasirinktus alternatyviuosius pavadinimus jų nevykdydami. Vykdyklė -paleidžia npm įvesties tašką, todėl išlaikoma jo vykdymo aplinka (įskaitant Bun, kur jis -sukonfigūruotas). Apraše užregistruoti už šių profilių ribų esantys alternatyvieji -pavadinimai kaip paleidžiami atskirai, o priežiūros komandos draudžiamos tik skaitymui -skirtuose patikros profiliuose. - -Šie profiliai apima tik statinę patikrą. Jie nepatvirtina produkto testų, aprėpties, -pakavimo, išorinių patikrų ar viso kandidato priėmimo leidimui. Priėmimas darbo eigoje -naudoja susietus `config/quality/admission-policy.json` ir +įvardijama kaip privaloma arba rekomendacinė. Taikoma privaloma užduotis turi būti +sėkminga: trūkstami, atšaukti, praleisti, laukiantys ir nežinomi rezultatai negali +patvirtinti PASS. Tinkama tik dokumentų arba tik katalogo pakeitimų klasifikacija +gali padaryti kodo vykdymo šaką netaikomą; juodraštinė PR nelaikoma priimtu kandidatu. +Žyma `hotfix` neatleidžia nuo įrodymų pateikimo. + +Abi darbo eigos apima PR ir siuntimus į pagrindines / leidimo šakas, rankinį paleidimą +ir sujungimo grupės įvykius. Siuntimo, rankinio paleidimo ir sujungimo grupės atvejais +vykdomas visas rinkinys. Išsišakojusios saugyklos ir sujungimo grupės naudoja prieglobos +vykdykles užduotims, kurios kitu atveju pasirinktų savarankiškai valdomas vykdykles; +prieš diegimą būtina patikrinti, ar prieglobos pajėgumų pakanka. + +Kiekviename JSON kvite nurodomi paimtos versijos SHA, darbo eigos paleidimas ir bandymas. +CLI atmeta atvejį, kai paimtos versijos ir įvykio SHA nesutampa. Darbo eigos testai +susieja politikos narystę su verdikto užduoties `needs` sąrašu, kad nauja ar pašalinta +vykdymo šaka negalėtų nepastebimai dingti. Kvitai apima tik savo darbo eigą, o ne +publikavimą, diegimą ar esamo rekomendacinio skaitytuvo vidinę veiklą. Abiejų patikrų +pavadinimų aktyvinimas šakų taisyklėse yra atskiras administracinis pakeitimas; +vien šių užduočių pridėjimas šakos neapsaugo. + +### Statinės analizės inventorius + +Versijuojamas npm alternatyviųjų pavadinimų inventorius ir statinės analizės sudėtis +saugomi faile `config/quality/gate-manifest.json`. Paleiskite +`npm run check:gate-manifest`, kad patikrintumėte scenarijų pavadinimus ir tikslias +komandas pagal `package.json`; papildymai, pašalinimai ir komandų neatitikimai sukelia +klaidą tiek vietiniame kabliuke, tiek CI pakeitimų klasifikavimo užduotyse. +Alternatyvusis pavadinimas nėra darbo eigos užduotis, matricos egzempliorius ar testo +atvejis: šie skaičiai neturi būti pateikiami kaip lygiaverčiai. + +Naudokite `npm run quality:scan -- --list` arba +`npm run quality:scan:fast -- --list`, kad peržiūrėtumėte pasirinktus alternatyviuosius +pavadinimus jų nevykdydami. Vykdyklė iškviečia npm įėjimo tašką, todėl išsaugoma jo +vykdymo aplinka (įskaitant Bun, kur jis sukonfigūruotas). Apraše alternatyvieji +pavadinimai, nepriklausantys šiems profiliams, pažymėti kaip iškviečiami atskirai, +o priežiūros komandos draudžiamos tik skaitymui skirtuose analizės profiliuose. + +Šie profiliai apima tik statinę analizę. Jie nepatvirtina produkto testų, aprėpties, +pakavimo, išorinių patikrų ar visapusiško kandidato priėmimo leidimui. Darbo eigos +priėmimui naudojami susieti `config/quality/admission-policy.json` ir `scripts/quality/admission-verdict.mjs`. Leidimo stebėtojo profiliai lieka atskiri; -jų taikomas patikras ir kvitus tikrinkite nepriklausomai. Toliau pateiktas tekstinis -inventorius yra informacinis, o ne įrodymas, kad užkarda iš tikrųjų buvo paleista. +jų taikomas patikras ir kvitus tikrinkite nepriklausomai. Toliau pateikiamas tekstinis +inventorius yra informacinis ir neįrodo, kad užkarda iš tikrųjų buvo paleista. -Scenarijai yra kataloguose `scripts/check/` (politikos užkardos) ir `scripts/quality/` -(griežtinimo mechanizmas). CI tiesos šaltinis yra `.github/workflows/ci.yml`. +Scenarijai yra kataloguose `scripts/check/` (politikos užkardos) ir +`scripts/quality/` (kartelės kėlimo variklis). +Pagrindinis CI informacijos šaltinis yra `.github/workflows/ci.yml`. ### Spartusis leidimo PR kelias (`quality.yml`) -`.github/workflows/quality.yml` papildo CI pagrindinių / leidimo šakų PR, siuntimų į -apsaugotas šakas, rankinio paleidimo ir sujungimo grupių atvejais. PR naudoja pagal kelius -filtruojamas sparčiąsias patikras. Visam laikui išjungtas pasikartojantis kūrimas buvo -pašalintas; tikrosios kūrimo / pakavimo / paleidimo patikros lieka CI. - -| Užduotis | Aprėptis | Blokuojanti | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| `Docs Gates (fast-path)` | Dokumentacijos / kodo PR; API dokumentacijos nuorodos ir visa dokumentacija | Taip | -| `Fast Quality Gates` | Kodo PR; statinės patikros, tipų patikra, ataskaitų srities tipų patikra, paveikti vienetų testai | Taip | -| `Forgotten sibling tests` | Kodo PR; pakeisti moduliai atsekami iki statinių naudotojų ir galimų susijusių testų; grupuojančių modulių ir dinaminių importų keliai pateikiami kaip rekomendacinė diagnostika, atsižvelgiant į nurodytas leidžiamojo sąrašo išimtis | **Rekomendacinė** | -| `Vitest (fast-path)` | Kodo PR; spartusis vitest rinkinys | Taip | -| `Unit Tests fast-path` | Kodo PR; į 4 dalis suskaidytas vienetų testų rinkinys | Taip | -| `No new ESLint warnings` | Kodo PR; lint apsauga, atsižvelgianti į įspėjimų slopinimą | Taip, įskaitant išsišakojusias saugyklas | -| `Merge integrity (changelog + generated skills)` | Ne juodraštiniai PR; pakeitimų žurnalo ir sugeneruotų įgūdžių sinchronizavimas | Taip, įskaitant išsišakojusias saugyklas | +`.github/workflows/quality.yml` papildo CI pagrindinių / leidimo šakų PR, siuntimų +į apsaugotas šakas, rankinio paleidimo ir sujungimo grupių atvejais. PR naudojamos +pagal kelius filtruojamos sparčiosios patikros. Visam laikui išjungta besidubliuojanti +kompiliavimo užduotis pašalinta; tikrosios kompiliavimo / pakavimo / paleidimo patikros +lieka CI. + +| Užduotis | Aprėptis | Blokuojanti | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | +| `Docs Gates (fast-path)` | Dokumentų / kodo PR; API dokumentacijos nuorodos ir visi dokumentai | Taip | +| `Fast Quality Gates` | Kodo PR; statinės patikros, tipų patikra, valdymo skydelio tipų patikra, paveikti vienetų testai | Taip | +| `Forgotten sibling tests` | Kodo PR; pakeisti moduliai atsekami iki statinių naudotojų ir galimų susijusių testų; eksportavimo modulio ir dinaminio importavimo keliai pateikiami kaip rekomendacinė diagnostika, atsižvelgiant į nurodytas leidžiamojo sąrašo išimtis | **Rekomendacinė** | +| `Vitest (fast-path)` | Kodo PR; spartusis vitest rinkinys | Taip | +| `Unit Tests fast-path` | Kodo PR; į 4 dalis suskaidytas vienetų testų rinkinys | Taip | +| `No new ESLint warnings` | Kodo PR; į slopinimus atsižvelgianti lint apsauga | Taip, įskaitant išsišakojusias saugyklas | +| `Merge integrity (changelog + generated skills)` | Ne juodraštinės PR; pakeitimų žurnalo ir sugeneruotų įgūdžių sinchronizavimas | Taip, įskaitant išsišakojusias saugyklas | #### Pamirštų susijusių testų ataskaita -`npm run check:forgotten-sibling-tests` pakartotinai naudoja importų sprendiklį, kuriuo -grindžiamas testų poveikio žemėlapis. Kiekvienam pakeistam produkciniam moduliui jis pateikia -deterministines grandines -`pakeistas modulis / simbolis -> statinis naudotojas -> galimas susijęs testas`, kai galimo -testo nėra ištraukimo užklausos pakeitimų rinkinyje. Markdown santrauka ir JSON rezultatas -išsaugomi kaip `forgotten-sibling-tests` darbo eigos artefaktas, skirtas kalibravimui prieš -bet kokį blokuojančio režimo diegimą. - -Pakartotiniai eksportai per „barrel“ failus ir dinaminiai importai naudojami tik kaip raiškos diagnostika; jie niekada nesukuria -blokuojančio radinio. Peržiūrėtos išimtys pateikiamos faile -`config/quality/forgotten-sibling-allowlist.json`. Kiekviename įraše turi būti nurodytas vartotojo ir kandidato -testas, pateiktas konkretus pagrindimas ir nuoroda į GitHub problemą arba pakeitimų užklausą. Netinkamai suformuoti įrašai -atmetami. Išimtys negali paslėpti pašalinto kandidato testo arba skirtumo, kuriuo pridedama `.skip`/`.todo`; -teiginių susilpninimas ir kiti maskavimo būdai lieka atskirai blokuojančios -`check:test-masking` patikros atsakomybė. +`npm run check:forgotten-sibling-tests` pakartotinai naudoja importų sprendimo +mechanizmą, kuriuo grindžiamas testų poveikio žemėlapis. Kiekvienam pakeistam +produkcinio kodo moduliui jis pateikia deterministines +`pakeistas modulis / simbolis -> statinis naudotojas -> galimas susijęs testas` +grandines, kai galimo testo nėra įtrauktame pull-request skirtume. Markdown suvestinė +ir JSON rezultatas išsaugomi kaip darbo eigos artefaktas `forgotten-sibling-tests`, +kad prieš bet kokį blokuojantį diegimą būtų galima atlikti kalibravimą. + +„Barrel“ pakartotiniai eksportai ir dinaminiai importai naudojami tik skyrimo diagnostikai; jie niekada +nesukuria blokuojančio radinio. Peržiūrėtos išimtys pateikiamos faile +`config/quality/forgotten-sibling-allowlist.json`. Kiekviename įraše turi būti nurodytas vartotojas ir +galimas testas, pateiktas konkretus pagrindimas bei nuoroda į GitHub problemą arba ištraukimo užklausą. Netinkamai suformuoti įrašai +atmetami. Išimtys negali paslėpti ištrinto galimo testo arba skirtumo, kuriuo pridedama `.skip`/`.todo`; +teiginių susilpninimą ir kitus maskavimo atvejus toliau kontroliuoja nepriklausomai blokuojantis +`check:test-masking` kontrolės mechanizmas. ### Užduotis: `lint` -Vykdoma kiekvienai į `main` nukreiptai pakeitimų užklausai. Nesėkmės atveju blokuoja suliejimą. - -| Scenarijus (`npm run ...`) | Tikrina | Blokuoja | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Ar Node.js versija patenka į palaikomą diapazoną | Taip | -| `check:cycles` | Ciklinius importus — visuose `src/` + `open-sse/` moduliuose | Taip | -| `check:route-validation:t06` | Ar visuose maršrutuose yra Zod schemos (6 lygio politika) | Taip | -| `check:any-budget:t11` | Ar `@ts-expect-error // any` skaičius neviršija limito (11 lygio „catraca“) | Taip | -| `check:provider-consistency` | Kiekvienam `providers.ts` teikėjui yra atitinkamas įrašas faile `providerRegistry.ts` (ir atvirkščiai, kiek tai leidžia leidžiamųjų sąrašas) | Taip | -| `check:model-lifecycle` | Trys rankiniu būdu prižiūrimos maršrutizavimo lentelės išlieka suderintos su į saugyklą įtraukta gyvavimo ciklo momentine kopija (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nepriskiria balo jokiam nebenaudojamam identifikatoriui, kurį gali maršrutizuoti `REGISTRY`; kiekvienas `BUILT_IN_ALIASES` tikslas yra `REGISTRY` ir jo nėra nebenaudojamų identifikatorių momentinėje kopijoje; kiekvienas nebenaudojamas identifikatorius, vis dar esantis `REGISTRY`, yra peradresuojamas arba nurodytas `allowedRetiredInCatalog`; ir joks `DEFAULT_DEGRADATION_MAP` šaltinis ar tikslas toje momentinėje kopijoje nėra pažymėtas kaip nebenaudojamas. Tai neįrodo, kad modelį šiuo metu teikia veikianti išorinė sistema. Tikrinama neprisijungus — lyginama su `config/quality/model-lifecycle.json`, kuris rankiniu būdu atnaujinamas naudojant `npm run quality:refresh-model-lifecycle` (reikalingas tinklas; neintegruota į CI). `allowedRetiredInCatalog` yra palaipsnio mažinimo reketas: įrašą pridėkite tik turėdami stebėjimo užduotį. | Taip | -| `check:fetch-targets` | Kiekvienas `fetch("/api/...")` kliento pusės kataloge `src/` nurodo tikrą `route.ts` | Taip | -| `check:deps` | Visos naudojant `npm install` įdiegiamos priklausomybės iš visų saugyklos failų `package.json` yra faile `dependency-allowlist.json`; nauji neprisegti arba dėl rašybos klaidomis pagrįstų tiekimo grandinės atakų įtartini paketai pažymimi | Taip | -| `audit:deps` | `npm audit` (šakninis projektas ir electron) — nėra didelio ar kritinio lygio įspėjimų (iš dalies dubliuoja osv `check:vuln-ratchet`; žr. racionalizavimo darbų sąrašą) | Taip | -| `check:lockfile` | `package-lock.json` vientisumas — https registras, vientisumo maišos, nėra pagrindinio kompiuterio perrašymų | Taip | -| `check:licenses` | SPDX licencijų leidžiamų reikšmių sąrašas gamybinėms priklausomybėms | Taip | -| `check:tracked-artifacts` | Nėra kompiliavimo artefaktų / į versijų kontrolę įtrauktų `node_modules` simbolinių nuorodų (taip pat vykdoma „husky“ prieš patvirtinant pakeitimus; patikra prieš siunčiant pakeitimus sąmoningai minimali — #6716) | Taip | -| `check:ai-attribution` | PR patvirtinimuose, pavadinime ar apraše nėra DI / roboto `Co-Authored-By` prierašo ar DI generavimo poraštės — griežta taisyklė Nr. 16 (`quality.yml` greitųjų patikrų cikle, skirtame PR→`release/**` — nuskaito įvykio naudingąją apkrovą, o ne PR atveju nieko nedaro — ir tik PR skirtame `ci.yml` lint patikros žingsnyje, skirtame PR→`main`; taip pat „husky“ `commit-msg` kabliuke; bendraautoriai žmonės leidžiami; #14436) | -| `check:vitest-exclusions` | Kiekvienoje „Vitest“ išimtyje nurodoma sekimo užduotis ir ji pateikiama faile `config/quality/vitest-exclusions.json` (#13204) | Taip | -| `check:file-size` | Nė vienas šaltinio failas neviršija jo plėtiniui nustatytos ribos (fiksatorius: dideli failai, kurių dydis užfiksuotas `frozen` sąraše) | Taip | -| `check:error-helper` | Klaidų atsakuose vykdyklėse / apdorojimo priemonėse naudojamos `buildErrorBody()` / `sanitizeErrorMessage()` (griežta taisyklė Nr. 12) | Taip | -| `check:migration-numbering` | Migracijų SQL failai numeruojami nuosekliai, be tarpų ar pasikartojimų | Taip | -| `check:public-creds` | Už `publicCreds.ts` ribų nėra tiesiogiai įrašytų OAuth `client_id` / `client_secret` arba Firebase žiniatinklio raktų (griežta taisyklė Nr. 11) | Taip | -| `check:db-rules` | Už `src/lib/db/` modulių ribų nėra neapdoroto SQL; nėra apibendrinamųjų importų iš `localDb.ts` (griežtos taisyklės Nr. 2 / Nr. 5) | Taip | -| `check:known-symbols` | Teikėjų vykdytojai, maršruto parinkimo strategijos ir transformuotojai, užregistruoti jų nukreipimo lentelėse, atitinka diske esančius failus — nėra nesusietų ar nedeklaruotų simbolių | Taip | -| `check:route-guard-membership` | Kiekvienas maršrutas, paleidžiantis antrinį procesą, yra klasifikuojamas naudojant `isLocalOnlyPath()` (griežtos taisyklės Nr. 15 / Nr. 17) | Taip | -| `check:test-discovery` | Kiekvieną saugykloje esantį `*.test.ts` / `*.spec.ts` failą aptinka bent viena testų vykdymo priemonė (reketo principas: nesusietų failų sąrašas faile `test-discovery-baseline.json` gali tik trumpėti) | Taip | -| `check:agent-skills-sync` | Sugeneruoti agento įgūdžių artefaktai atitinka jų šaltinio katalogą (nėra neatitikimų) | -| `check:provider-asset-provenance` | Teikėjų logotipai / ištekliai turi užregistruotą kilmės įrašą | -| `lint:json` | JSON konfigūracijos failai sėkmingai analizuojami ir atitinka saugyklos lint taisykles | -| `typecheck:core` | TypeScript kompiliavimas be klaidų (tik patariamojo pobūdžio įspėjimai) | Taip | -| `typecheck:noimplicit:core` | Griežtas `noImplicitAny` — orientuotas į ateitį; daugeliui esamų iškvietimo vietų vis dar reikia anotacijų | **Patariamasis** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, apribotas iki `src/app/(dashboard)/**` (#7033) — `typecheck:core` kruopščiai atrinktas 27 failų leidžiamasis sąrašas neapima jokio dashboard TSX failo, o `next build` taip pat niekada neatlieka jų tipų patikros (`next.config.mjs` nustato `ignoreBuildErrors: true`), todėl ten atsiradusios nesusietų identifikatorių regresijos (#6625/#6909) CI likdavo nepastebėtos. Skirtumai lyginami su fiksuotu baziniu kiekvieno failo ir kiekvieno TS kodo klaidų skaičiumi (`config/quality/dashboard-typecheck-baseline.json`, toks pats pasenusių įrašų tikrinimo modelis kaip `check:known-symbols`) — patikra nepavyksta tik dėl NAUJŲ klaidų, viršijančių bazinį skaičių; ištaisius esamą klaidą, bazinį lygį sumažinkite naudodami `--update`. | Taip | +Vykdoma kiekvienai PR į `main`. Nesėkmės atveju sujungimas blokuojamas. + +| Scenarijus (`npm run ...`) | Tikrina | Blokuojantis | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Ar Node.js versija patenka į palaikomą diapazoną | Taip | +| `check:cycles` | Ciklinius importus visame `src/` + `open-sse/` (AST pagrindu, išsprendžiant tsconfig `paths`). Bazinis režimas = patariamasis, pateikia ciklų sąrašą. `check:cycles:ratchet` (vykdomas CI) blokuoja, kai skaičius viršija `quality-baseline.json` nustatytą `metrics.cycles` ribą — šiuo metu 14, `direction: down`, todėl jis gali tik mažėti (#15159 G-01/G-02) | Taip (reketo režimu) | +| `check:route-validation:t06` | Ar visiems maršrutams pateiktos Zod schemos (6 lygio politika) | Taip | +| `check:any-budget:t11` | Ar `@ts-expect-error // any` skaičius neviršija biudžeto (11 lygio reketo mechanizmas) | Taip | +| `check:provider-consistency` | Kiekvienas teikėjas faile `providers.ts` turi atitinkamą įrašą faile `providerRegistry.ts` (ir atvirkščiai, atsižvelgiant į leidžiamųjų sąrašą) | Taip | +| `check:model-lifecycle` | Trys rankiniu būdu prižiūrimos maršruto parinkimo lentelės išlieka suderintos su repozitorijoje saugoma gyvavimo ciklo momentine kopija (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nepriskiria balo jokiam nebenaudojamam id, kurį gali nukreipti `REGISTRY`; kiekvienas `BUILT_IN_ALIASES` tikslas yra `REGISTRY` ir jo nėra nebenaudojamų id momentinėje kopijoje; kiekvienas nebenaudojamas id, vis dar esantis `REGISTRY`, yra persiunčiamas arba įtrauktas į `allowedRetiredInCatalog`; joks `DEFAULT_DEGRADATION_MAP` šaltinis ar tikslas toje momentinėje kopijoje nėra pažymėtas kaip nebenaudojamas. Tai neįrodo, kad modelį šiuo metu aptarnauja veikiantis išorinis tiekėjas. Veikia neprisijungus — lygina su `config/quality/model-lifecycle.json`, kuris atnaujinamas rankiniu būdu naudojant `npm run quality:refresh-model-lifecycle` (reikalingas tinklas; neintegruota į CI). `allowedRetiredInCatalog` yra laipsniško mažinimo reketo mechanizmas: įrašą pridėkite tik kartu su sekimo užduotimi. | Taip | +| `check:fetch-targets` | Kiekvienas `fetch("/api/...")` kliento pusės kataloge `src/` nurodo į realų `route.ts` | Taip | +| `check:deps` | Visos naudojant `npm install` įdiegiamos priklausomybės iš kiekvieno repozitorijos failo `package.json` yra faile `dependency-allowlist.json`; nauji nefiksuotų versijų arba dėl rašybos klaidų galimai užgrobtų paketų atvejai pažymimi | Taip | +| `audit:deps` | `npm audit` (šakninis projektas + electron) — nėra aukšto ar kritinio lygio įspėjimų (iš dalies sutampa su osv `check:vuln-ratchet`; žr. racionalizavimo neatliktų darbų sąrašą) | Taip | +| `check:lockfile` | `package-lock.json` vientisumas — https registras, vientisumo maišos, nėra pagrindinio kompiuterio perrašymų | Taip | +| `check:licenses` | Gamybinių priklausomybių SPDX licencijų leidžiamasis sąrašas | Taip | +| `check:tracked-artifacts` | Nėra kompiliavimo artefaktų / į versijų valdymą įtrauktų `node_modules` simbolinių nuorodų (taip pat vykdoma per husky prieš patvirtinimą; prieš išsiuntimą atliekama patikra sąmoningai minimali — #6716) | Taip | +| `check:ai-attribution` | PR patvirtinimuose, pavadinime ar apraše nėra DI / roboto `Co-Authored-By` prierašo ar DI generavimo poraštės — griežta taisyklė Nr. 16 (`quality.yml` sparčiųjų patikrų cikle, skirtame PR→`release/**` — nuskaito įvykio naudingąją apkrovą, ne PR atveju neatlieka jokių veiksmų — ir tik PR skirtame `ci.yml` analizės žingsnyje, skirtame PR→`main`; taip pat husky `commit-msg` kabliuke; žmonės bendraautoriai leidžiami; #14436) | +| `check:vitest-exclusions` | Kiekvienoje Vitest išimtyje nurodyta sekimo užduotis ir ji pateikiama faile `config/quality/vitest-exclusions.json` (#13204) | Taip | +| `check:file-size` | Joks šaltinio failas neviršija atitinkamam plėtiniui nustatytos ribos (reketinis ribojimas: dideli failai užfiksuoti sąraše `frozen`) | Taip | +| `check:error-helper` | Klaidų atsakymuose vykdyklėse / doroklėse naudojama `buildErrorBody()` / `sanitizeErrorMessage()` (griežta taisyklė Nr. 12) | Taip | +| `check:migration-numbering` | Migracijos SQL failai sunumeruoti nuosekliai, be praleistų ar pasikartojančių numerių | Taip | +| `check:public-creds` | Už `publicCreds.ts` ribų nėra tiesiogiai įrašytų OAuth `client_id`/`client_secret` ar Firebase Web raktų (griežta taisyklė Nr. 11) | Taip | +| `check:db-rules` | Už `src/lib/db/` modulių ribų nėra neapdorotų SQL užklausų; nėra apibendrinamųjų importų iš `localDb.ts` (griežtos taisyklės Nr. 2/Nr. 5) | Taip | +| `check:known-symbols` | Teikėjų vykdytojai, maršruto parinkimo strategijos ir transformuotojai, užregistruoti jų išsiuntimo lentelėse, atitinka diske esančius failus — nėra nesusietų ar nedeklaruotų simbolių | Taip | +| `check:route-guard-membership` | Kiekvienas maršrutas, paleidžiantis antrinį procesą, yra klasifikuojamas naudojant `isLocalOnlyPath()` (griežtos taisyklės Nr. 15/Nr. 17) | Taip | +| `check:test-discovery` | Kiekvieną saugykloje esantį `*.test.ts` / `*.spec.ts` failą aptinka bent viena testų vykdymo priemonė (terkšlė: nesusietų failų sąrašas faile `test-discovery-baseline.json` gali tik trumpėti) | Taip | +| `check:agent-skills-sync` | Sugeneruoti agentų įgūdžių artefaktai atitinka jų šaltinio katalogą (nėra nukrypimų) | +| `check:provider-asset-provenance` | Teikėjų logotipai / ištekliai turi užregistruotą kilmės įrašą | +| `lint:json` | JSON konfigūracijos failai yra tinkamai analizuojami ir atitinka saugyklos lint taisykles | +| `typecheck:core` | TypeScript kompiliavimas be klaidų (tik patariamojo pobūdžio įspėjimai) | Taip | +| `typecheck:noimplicit:core` | Griežtas `noImplicitAny` — orientuotas į ateitį; daugeliui jau esamų iškvietimo vietų vis dar reikia anotacijų | **Patariamasis** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, apribotas iki `src/app/(dashboard)/**` (#7033) — `typecheck:core` kuruojamame 27 failų leidžiamųjų sąraše nėra jokio ataskaitų srities TSX failo, o `next build` taip pat niekada netikrina jo tipų (`next.config.mjs` nustato `ignoreBuildErrors: true`), todėl ten atsiradusios vienišų identifikatorių regresijos (#6625/#6909) CI sistemoje buvo nematomos. Skirtumai lyginami su fiksuotu kiekvieno failo ir kiekvieno TS kodo skaičiaus baziniu lygiu (`config/quality/dashboard-typecheck-baseline.json`, taikant tą patį pasenimo kontrolės modelį kaip `check:known-symbols`) — patikrą sustabdo tik NAUJOS klaidos, viršijančios bazinį skaičių; ištaisius jau egzistuojančią klaidą, bazinį lygį mažinkite naudodami `--update`. | Taip | ### Užduotis: `quality-gate` -Vykdoma po `test-coverage`. Nesėkmės atveju sujungimas blokuojamas. +Vykdoma po `test-coverage`. Nesėkmės atveju blokuoja suliejimą. -| Scenarijus | Tikrina | Blokuojantis | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `quality:collect` | Sugeneruoja `quality-metrics.json` (ESLint įspėjimų skaičius, aprėptis iš sujungtos segmentų ataskaitos) | Taip (prieš kartelės tikrinimą) | -| `quality:ratchet` | Nė viena `quality-baseline.json` metrika nepablogėjo (ESLint įspėjimų ≤ bazinė reikšmė; aprėptis ≥ bazinė reikšmė) | Taip | -| `check:duplication` | Kodo dubliavimas (jscpd@4) neviršija `quality-baseline.json` bazinės reikšmės | Taip | -| `check:complexity` | Failo lygmens ciklomatinis sudėtingumas neviršija ribos (pagrindinės ESLint taisyklės `complexity` + `max-lines-per-function`) | Taip | -| `check:cognitive-complexity` | Kognityvinio sudėtingumo kartelė (`eslint-plugin-sonarjs`) — atskiras ESLint vykdymas; CI abu patikrinimus vykdo kartu kaip vieną `check:complexity-ratchets` veiksmą | Taip | -| `check:dead-code` | Nenaudojamų eksportų / failų kartelė (knip) nepablogėjo, palyginti su bazine reikšme | Taip | -| `check:compression-budget` | Glaudinimo etalono biudžetas — kiekvieno variklio mažiausias sutaupytų leksemų kiekis neturi pablogėti | Taip | -| `check:type-coverage` | Tipizuotos dalies procentinės aprėpties kartelė (`type-coverage`) nepablogėjo; iš esmės pakeičia `typecheck:noimplicit:core` | Taip | -| `check:codeql-ratchet` | Atvirų CodeQL įspėjimų skaičius nepadidėjo (nuskaitoma per `gh api`; jei nėra prieigos rakto, patikra korektiškai praleidžiama) — atnaujinimo dažnį ir rankinį paleidimą žr. toliau „CodeQL kartelė“ | Taip | +| Scenarijus | Tikrina | Blokuojantis | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `quality:collect` | Sugeneruoja `quality-metrics.json` (ESLint įspėjimų skaičių, aprėptį iš sujungtos segmentų ataskaitos) | Taip (vykdomas prieš „ratchet“) | +| `quality:ratchet` | Nė viena `quality-baseline.json` metrika nepablogėjo (ESLint įspėjimų ≤ bazinė reikšmė; aprėptis ≥ bazinė reikšmė) | Taip | +| `check:duplication` | Kodo dubliavimas (jscpd@4) neviršija `quality-baseline.json` bazinės reikšmės | Taip | +| `check:complexity` | Failo lygmens ciklomatinis sudėtingumas neviršija ribos (bazinis ESLint `complexity` + `max-lines-per-function`) | Taip | +| `check:cognitive-complexity` | Kognityvinio sudėtingumo „ratchet“ (`eslint-plugin-sonarjs`) — atskiras ESLint vykdymas; CI abu vykdo sujungtus į vieną `check:complexity-ratchets` veiksmą | Taip | +| `check:dead-code` | Nenaudojamų eksportų / failų „ratchet“ (knip) nepablogėjo, palyginti su bazine reikšme | Taip | +| `check:compression-budget` | Glaudinimo našumo testo biudžetas — kiekvieno variklio minimali sutaupytų žetonų riba negali pablogėti | Taip | +| `check:type-coverage` | Tipais aprašytos dalies procentinis „ratchet“ (`type-coverage`) nepablogėjo; iš esmės apima `typecheck:noimplicit:core` | Taip | +| `check:codeql-ratchet` | Atvirų CodeQL įspėjimų skaičius nepablogėjo (nuskaitoma per `gh api`; jei nėra prieigos rakto, korektiškai praleidžiama) — atnaujinimo dažnį ir rankinį paleidimą žr. toliau „CodeQL ratchet“ | Taip | ### Užduotis: `quality-extended` -Visa užduotis yra rekomendacinė (`continue-on-error: true`). npm pagrįstos kartelės -vykdomos iš tikrųjų; išoriniai skaitytuvai įdiegiami naudojant `gh release download` -ir patys praleidžia patikrą (exit 0), jei vykdomojo failo vis dar nėra. +Visa užduotis yra rekomendacinė (`continue-on-error: true`). npm pagrįsti „ratchet“ +tikrinimai vykdomi iš tikrųjų; išoriniai skaitytuvai įdiegiami naudojant `gh release download` +ir patys praleidžia vykdymą (exit 0), jei dvejetainio failo vis dar nėra. -| Scenarijus | Tikrina | Blokuojantis | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- | -| `check:circular-deps` | Nėra ciklinių priklausomybių (dpdm) | **Rekomendacinis** | -| `check:bundle-size` | Paketo dydis neviršija ribos | **Rekomendacinis** | -| `check:secrets` | Paslapčių paieška (gitleaks) — praleidžiama, jei nėra vykdomojo failo | **Rekomendacinis** | -| `check:vuln-ratchet` | Priklausomybių pažeidžiamumai (osv-scanner) nepablogėjo — praleidžiama, jei nėra vykdomojo failo | **Rekomendacinis** | -| `check:workflows` | Darbo eigų analizė (actionlint + zizmor); nesami / neveikiantys skaitytuvai, netinkamos ataskaitos arba trūkstama kartelės bazinė reikšmė sukelia INCOMPLETE. Tinkami radiniai apdorojami pagal pasirinktą griežtą / rekomendacinę / kartelės politiką | Vykdymas privalomas; zizmor kartelė blokuoja CI | -| `check:openapi-breaking` | Viešojo API kontrakto (`openapi.yaml`) nesuderinami pakeitimai, palyginti su bazine šaka (oasdiff) — sugeneruoja `openapiBreaking=N`; praleidžiama, jei nėra oasdiff arba nepavyksta gauti bazinės specifikacijos | **Rekomendacinis** | +| Scenarijus | Tikrina | Blokuojantis | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| `check:circular-deps` | Nėra ciklinių priklausomybių (dpdm) | **Rekomendacinis** | +| `check:bundle-size` | Paketo dydis neviršija ribos | **Rekomendacinis** | +| `check:secrets` | Paslapčių paieška (gitleaks) — praleidžiama, jei nėra dvejetainio failo | **Rekomendacinis** | +| `check:vuln-ratchet` | Priklausomybių pažeidžiamumai (osv-scanner) nepablogėjo — praleidžiama, jei nėra dvejetainio failo | **Rekomendacinis** | +| `check:workflows` | Darbo eigų tikrinimas (actionlint + zizmor); jei skaitytuvų nėra arba jie neveikia, ataskaitos netinkamos ar nėra „ratchet“ bazinės reikšmės, tikrinimas baigiamas būsena INCOMPLETE. Tinkami radiniai apdorojami pagal pasirinktą griežtą / rekomendacinę / „ratchet“ politiką | Vykdymas privalomas; zizmor „ratchet“ blokuoja CI | +| `check:openapi-breaking` | Viešosios API sutarties (`openapi.yaml`) nesuderinami pakeitimai, palyginti su bazine šaka (oasdiff) — sugeneruoja `openapiBreaking=N`; praleidžiama, jei nėra oasdiff arba nepavyksta nustatyti bazinės specifikacijos | **Rekomendacinis** | ### Užduotis: `docs-sync-strict` -Vykdoma kiekvienai į `main` nukreiptai PR. Nesėkmės atveju blokuoja sujungimą. - -| Scenarijus | Tikrina | Blokuojantis | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | -| `check:docs-all` | Metatikra, nuosekliai paleidžianti 6 toliau nurodytas dalines tikras | Taip | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versijų nuoseklumą | Taip | -| ↳ `check:docs-counts` | Ar tekste pateikti skaičiai (teikėjų skaičius, migracijų skaičius ir kt.) patenka į realiems skaičiams taikomą kartelės intervalo ribą | Taip | -| ↳ `check:env-doc-sync` | Ar kiekvienas aplinkos kintamasis iš `.env.example` yra aprašytas dokumentacijos lentelėje ir atvirkščiai | Taip | -| ↳ `check:deprecated-versions` | Ar dokumentacijoje nėra nebenaudojamų versijų eilučių | Taip | -| ↳ `check:doc-links` | Ar vidinės „Markdown“ nuorodos dokumentacijoje nurodo realius failus (`[text]`/`(path)` forma) | Taip | -| ↳ `check:fabricated-docs` | Ar dokumentacijoje minimi maršrutai, aplinkos kintamieji, CLI komandos, kablių pavadinimai ir failų keliai egzistuoja kodų bazėje. Griežtoji patikra naudojant `--strict`; be žymos klaida nėra blokuojanti. | Taip (CI aplinkoje naudojant `--strict`) | -| `check:cli-i18n` | Ar CLI komandų eilutės yra visuose i18n lokalės failuose | Taip | -| `check:openapi-coverage` | Ar OpenAPI specifikacija apima bent pagal kartelę nustatytą minimalų realių maršrutų skaičių | Taip | -| `check:openapi-security-tiers` | Ar saugumo lygio anotacijos faile `openapi.yaml` atitinka `routeGuard.ts` klasifikacijas | **Rekomendacinis** | -| `check:openapi-routes` | Ar kiekvienas kelias faile `openapi.yaml` nurodo realų `route.ts` (apsauga nuo haliucinacijų) | Taip | -| `check:docs-symbols` | Ar kiekviena `/api/...` nuoroda failuose `docs/**/*.md` nurodo realų `route.ts` (apsauga nuo haliucinacijų) | Taip | -| `i18n translation drift` | Neišversti raktai i18n lokalės failuose — tik įspėjimas | **Rekomendacinis** | +Vykdoma kiekvienai į `main` šaką nukreiptai PR. Nesėkmės atveju sujungimas blokuojamas. + +| Scenarijus | Tikrina | Blokuojantis | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `check:docs-all` | Metapatikra, kuri nuosekliai paleidžia 6 toliau nurodytas dalines patikras | Taip | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versijų nuoseklumą | Taip | +| ↳ `check:docs-counts` | Ar tekste nurodyti skaičiai (teikėjų skaičius, migracijų skaičius ir kt.) patenka į realiems skaičiams taikomą laipsniškai griežtinamą intervalą | Taip | +| ↳ `check:env-doc-sync` | Ar kiekvienas `.env.example` aplinkos kintamasis dokumentuotas dokumentacijos lentelėje ir atvirkščiai | Taip | +| ↳ `check:deprecated-versions` | Ar dokumentacijoje nėra nebenaudojamų versijų eilučių | Taip | +| ↳ `check:doc-links` | Ar vidinės dokumentacijos markdown nuorodos nurodo esamus failus (`[tekstas]`/`(kelias)` forma) | Taip | +| ↳ `check:fabricated-docs` | Ar dokumentacijoje nurodyti maršrutai, aplinkos kintamieji, CLI komandos, kablių pavadinimai ir failų keliai egzistuoja kodų bazėje. Griežta patikra naudojant `--strict`; be šios žymos klaida neblokuoja. | Taip (CI naudojant `--strict`) | +| `check:cli-i18n` | Ar CLI komandų eilutės yra visuose i18n lokalės failuose | Taip | +| `check:openapi-coverage` | Ar OpenAPI specifikacija apima bent laipsniškai didinamą minimalią realių maršrutų dalį | Taip | +| `check:openapi-security-tiers` | Ar `openapi.yaml` saugumo lygių anotacijos atitinka `routeGuard.ts` klasifikacijas | **Rekomendacinis** | +| `check:openapi-routes` | Ar kiekvienas `openapi.yaml` kelias atitinka realų `route.ts` (apsauga nuo haliucinacijų) | Taip | +| `check:docs-symbols` | Ar kiekviena `/api/...` nuoroda faile `docs/**/*.md` atitinka realų `route.ts` (apsauga nuo haliucinacijų) | Taip | +| `i18n translation drift` | Neišversti raktai i18n lokalės failuose — tik įspėjimas | **Rekomendacinis** | ### Užduotis: `i18n-ui-coverage` -| Scenarijus | Tikrina | Blokuojantis | -| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `check-ui-keys-coverage` (įterptas) | Ar UI i18n raktų aprėptis yra ≥ 65 % | Taip | -| `check-ui-value-drift` (įterptas) | Ar perrašius anglišką **reikšmę** nelieka pasenusių vertimų | Taip | -| `check-new-key-coverage` (įterptas) | Ar **naujas** angliškas raktas išverstas kiekvienoje lokalėje — žymeklis `__MISSING__:` atmetamas | Taip | -| `check-translation-ratio` | Realių vertimų santykis kiekvienoje lokalėje (angliškai versijai identiški / vietaženklio / trūkstami įrašai, nepatenkantys į leidžiamųjų sąrašą) neturi viršyti `config/quality/i18n-translation-baseline.json` + leidžiamos paklaidos | **Rekomendacinis** | +| Scenarijus | Tikrina | Blokuojantis | +| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `check-ui-keys-coverage` (įterptas) | Ar UI i18n raktų aprėptis yra ≥ 65% | Taip | +| `check-ui-value-drift` (įterptas) | Ar perrašius anglišką **reikšmę** nelieka pasenusio vertimo | Taip | +| `check-new-key-coverage` (įterptas) | Ar **naujas** angliškas raktas išverstas kiekvienoje lokalėje — `__MISSING__:` žymuo neleidžiamas | Taip | +| `check-translation-ratio` | Tikrų vertimų dalis kiekvienoje lokalėje (už leidžiamųjų sąrašo ribų esantys identiški angliškiems / vietos rezervavimo / trūkstami lapai) negali viršyti `config/quality/i18n-translation-baseline.json` + leidžiamos paklaidos | **Rekomendacinis** | -Reikalingas `fetch-depth: 0` — reikšmių skirtumų patikra lygina `en.json` su sujungimo baze. +Reikalingas `fetch-depth: 0` — reikšmių nuokrypio patikra palygina `en.json` su sujungimo baze. #### `check-ui-value-drift` — pasenusių vertimų patikra -Aptinka vieną i18n regresijos atvejį, kurio kitos patikros dėl savo struktūros negali pastebėti: angliška reikšmė +Aptinka vieną i18n regresijos tipą, kurio kitos patikros dėl savo struktūros negali pastebėti: angliška reikšmė perrašoma, o pagal _ankstesnį_ anglišką tekstą sukurti vertimai lieka nepakeisti, todėl -ne anglakalbiai naudotojai ir toliau mato užtikrintai suformuluotą, bet jau klaidingą tekstą. +ne anglakalbiai naudotojai toliau skaito įtikinamai suformuluotą, bet dabar jau klaidingą tekstą. Tai iš tikrųjų pateko į leidimą. `oauthModal.googleOAuthWarning` buvo perrašytas, kai buvo -pridėtas „Antigravity“ prisijungimo pagalbininkas (#5203); **39 iš 43 lokalių** liko tekstas, nurodantis operatoriams „nukopijuoti -visą URL ir įklijuoti jį toliau“ — šio teikėjo atveju tokio proceso užbaigti neįmanoma. Tai liko +pridėtas Antigravity prisijungimo pagalbininkas (#5203); **39 iš 43 lokalių** liko tekstas, nurodantis operatoriams „nukopijuoti +visą URL ir įklijuoti jį toliau“ — tokiu būdu šio teikėjo srauto užbaigti neįmanoma. Tai liko nepastebėta iki #8463, nes: -- `sync-ui-keys` užpildo tik **nesamus** raktus, bet niekada neatnaujina **pasenusių**; -- `check-ui-keys-coverage` skaičiuoja rakto _buvimą_, todėl pasenęs vertimas laikomas padengtu; -- `check-translation-drift` stebi `docs/i18n//**.md` dokumentacijos kopijas — +- `sync-ui-keys` užpildo tik **trūkstamus** raktus, o ne **pasenusius**; +- `check-ui-keys-coverage` skaičiuoja raktų _buvimą_, todėl pasenęs vertimas laikomas padengtu; +- `check-translation-drift` stebi `docs/i18n//**.md` dokumentacijos atspindžius — jis niekada neskaito `src/i18n/messages/*.json`. Užduotyje `docs-sync-strict` ši patikra blokuoja nuo - 2026-09 pakartotinio sinchronizavimo: redaguojant pagrindinį dokumentą → `npm run i18n:run -- --files=` (skyriaus lygmens, nebrangu). + 2026-09 pakartotinio sinchronizavimo: redaguojant pagrindinį dokumentą → `npm run i18n:run -- --files=` (skilties lygmens, nebrangu). -**Atsižvelgia į skirtumus, o ne remiasi bazine būsena.** Sujungimo bazėje esantis `en.json` palyginamas su -darbo medžiu; kiekvienam raktui, kurio angliška reikšmė pasikeitė, bet kuri lokalė, kurioje vis dar yra -nepakeistas vertimas, laikoma pasenusia. Taip sąmoningai **įšaldoma anksčiau susikaupusi skola** — skirtumas -negali parodyti, iš kurios senos angliškos reikšmės atsirado ilgą laiką naudotas vertimas, todėl patikra vertina -tik tai, ką paliečia dabartinis pakeitimas. Alternatyva (kiekvieno rakto maišos bazinė būsena) pareikalautų -~600 KB sugeneruoto failo, 3 kartus didesnio už didžiausią esamą bazinės būsenos failą, kuris keistųsi su kiekvienu i18n PR. +**Atsižvelgia į skirtumus, o ne remiasi bazine versija.** Ji palygina `en.json` sujungimo bazėje su +darbine kopija; kiekvienam raktui, kurio angliška reikšmė pasikeitė, bet kurios lokalės nepakeistas +vertimas laikomas pasenusiu. Taip sąmoningai **įšaldoma anksčiau susikaupusi skola** — skirtumai +negali parodyti, iš kurios senos angliškos reikšmės atsirado ilgą laiką egzistuojantis vertimas, todėl patikra vertina +tik tai, ką paliečia dabartinis pakeitimas. Alternatyva (kiekvieno rakto kontrolinėmis sumomis paremta bazinė versija) pareikalautų +~600 KB sugeneruoto failo, 3× didesnio už didžiausią esamą bazinį failą, kuris keistųsi su kiekvienu i18n PR. -Patikrą galima įvykdyti dviem būdais: +Yra du būdai patikrą įvykdyti sėkmingai: 1. atnaujinti paveiktus vertimus arba -2. nustatyti jų reikšmę į `__MISSING__:` — tada vykdymo metu pateikiamas pataisytas angliškas tekstas +2. nustatyti jų reikšmę į `__MISSING__:` — vykdymo metu tuomet pateikiamas pataisytas angliškas tekstas (`src/i18n/request.ts::deepMergeFallback`, #7258), o raktas įtraukiamas į vertimo eilę. Jei pasikeitė eilutės **prasmė**, geriau **pervadinti raktą**: naujas raktas negali paveldėti @@ -235,52 +237,52 @@ pasenusio vertimo. Toks šablonas buvo panaudotas #8463. ```bash npm run i18n:check-value-drift # griežta patikra (vykdoma CI) -npm run i18n:check-value-drift:warn # tik pateikti ataskaitą +npm run i18n:check-value-drift:warn # tik ataskaita BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Baigiama su 0 ir `SKIP reason=base-unresolved`, kai bazinio katalogo negalima perskaityti (paviršinis -klonas be bazinės nuorodos), taip atkartojant `check-openapi-breaking`. +Baigiama su 0 ir `SKIP reason=base-unresolved`, kai bazinio katalogo nepavyksta perskaityti (negilus +klonas be bazinės nuorodos), analogiškai kaip `check-openapi-breaking`. ### Užduotis: `i18n` Visa i18n tikrinimo matrica (po vieną užduotį kiekvienai lokalei). Visa užduotis yra rekomendacinė. -| Scenarijus | Kas tikrinama | Blokuoja | -| ------------------------------- | ------------------------------------ | ------------------------------------------------------------- | -| `validate_translation.py quick` | Kiekvienos lokalės vertimų išsamumas | **Rekomendacinė** (`continue-on-error: true` visai užduočiai) | +| Scenarijus | Tikrina | Blokuoja | +| ------------------------------- | ----------------------------------- | ------------------------------------------------------------- | +| `validate_translation.py quick` | Kiekvienos lokalės vertimo išsamumą | **Rekomendacinė** (`continue-on-error: true` visai užduočiai) | ### Užduotis: `pr-test-policy` -Vykdoma tik sujungimo užklausoms. +Vykdoma tik gavimo užklausoms. -| Scenarijus | Kas tikrinama | Blokuoja | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | -| `check:pr-test-policy` | PR, keičiantys produkcinį kodą kataloguose `src/`, `open-sse/`, `electron/` arba `bin/`, turi įtraukti arba atnaujinti testus (griežta taisyklė Nr. 8) | Taip | -| `check:test-masking` | Pakeisti testų failai nesumažina bendro patikrų skaičiaus ir neprideda `assert.ok(true)` tautologijų | Taip | -| `check:pr-evidence` | PR apraše pateikiami pakeitimo testavimo / VPS įrodymai (automatizuoja griežtą taisyklę Nr. 18, ieškodamas PR tekste — nepatikima, žr. darbų sąrašą) | Taip | +| Scenarijus | Tikrina | Blokuoja | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | PR, keičiantys produkcinį kodą kataloguose `src/`, `open-sse/`, `electron/` arba `bin/`, privalo įtraukti arba atnaujinti testus (griežtoji taisyklė #8) | Taip | +| `check:test-masking` | Pakeisti testų failai nesumažina bendro teiginių skaičiaus ir neprideda `assert.ok(true)` tautologijų | Taip | +| `check:pr-evidence` | PR apraše pateikiami pakeitimo testavimo / VPS įrodymai (automatizuojama griežtoji taisyklė #18, ieškant PR tekste — nepatikima, žr. darbų sąrašą) | Taip | ### Užduotis: `test-vitest` Vykdoma po `build`. Nesėkmės atveju sujungimas blokuojamas. -| Rinkinys | Kas tikrinama | Blokuoja | -| ---------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP serveris (110 įrankių), autoCombo, podėlis — vitest vykdyklė | Taip | -| `test:vitest:ui` | UI komponentų testai — vitest vykdyklė | **Blokuoja** — anksčiau egzistavusios klaidos aiškiai išskirtos faile `vitest.config.ts`; naujos klaidos nutraukia užduotį | +| Rinkinys | Tikrina | Blokuoja | +| ---------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP serverį (110 įrankių), autoCombo, podėlį — vitest vykdyklė | Taip | +| `test:vitest:ui` | UI komponentų testus — vitest vykdyklė | **Blokuoja** — ankstesnės nesėkmės aiškiai neįtrauktos faile `vitest.config.ts`; dėl naujų nesėkmių užduotis nepavyksta | -### Naktinės darbo eigos (suplanuotos, rekomendacinės) +### Naktinės darbo sekos (suplanuotos, rekomendacinės) -Jos vykdomos pagal cron tvarkaraštį (ir naudojant `workflow_dispatch`), bet niekada nevykdomos PR. Visos jos yra rekomendacinės. +Jos vykdomos pagal cron tvarkaraštį (ir `workflow_dispatch`), bet niekada nevykdomos PR. Visos jos yra rekomendacinės. -| Darbo eiga | Kas tikrinama | Blokuoja | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `nightly-property` | fast-check savybių testai su atsitiktine pradine reikšme ir dideliu vykdymų skaičiumi | **Rekomendacinė** | -| `nightly-resilience` | atminties krūvos augimo patikra, chaoso trikčių įterpimas, k6 apkrovos / ilgalaikiai testai | **Rekomendacinė** | -| `nightly-llm-security` | promptfoo injekcijų apsauga (blokavimo režimas) ir garak zondai (praleidžiami, jei nėra teikėjo paslapties) | **Rekomendacinė** | -| `nightly-schemathesis` | OpenAPI sutarties neapibrėžtųjų įvesčių testavimas (schemathesis) veikiančioje OmniRoute naudojant `docs/openapi.yaml` — atskleidžia specifikacijos pažeidimus / neapdorotas 500 klaidas (8 etapas, B.4) | **Rekomendacinė** | -| `nightly-mutation` | Stryker mutacijų testavimo įvertis greitojoje vienetinių testų juostoje — išlikę mutantai atskleidžia silpnas patikras | **Rekomendacinė** | -| `nightly-compat` | Node variklio suderinamumo matrica palaikomuose `engines.node` diapazonuose | **Rekomendacinė** | +| Darbo seka | Tikrina | Blokuoja | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `nightly-property` | fast-check savybių testus su atsitiktine pradine reikšme ir dideliu vykdymų skaičiumi | **Rekomendacinė** | +| `nightly-resilience` | atminties krūvos augimo ribą, chaoso trikčių įterpimą, k6 apkrovos / ilgalaikės apkrovos testus | **Rekomendacinė** | +| `nightly-llm-security` | promptfoo injekcijų apsaugą (blokavimo režimu) ir garak patikras (praleidžiamos be teikėjo paslapties) | **Rekomendacinė** | +| `nightly-schemathesis` | OpenAPI sutarties neapibrėžtąjį testavimą (schemathesis) veikiančioje OmniRoute, naudojant `docs/openapi.yaml` — aptinka specifikacijos pažeidimus / neapdorotas 500 klaidas (8 etapas B.4) | **Rekomendacinė** | +| `nightly-mutation` | Stryker mutacinio testavimo įvertį sparčiojoje vienetinių testų juostoje — išlikę mutantai atskleidžia silpnus teiginius | **Rekomendacinė** | +| `nightly-compat` | Node variklio suderinamumo matricą visuose palaikomuose `engines.node` diapazonuose | **Rekomendacinė** | --- @@ -550,43 +552,43 @@ prisijungimo klaidą ir niekada nenutraukia seansų. --- -## Racionalizavimo darbų sąrašas (ROI peržiūra — 9 etapas, 3 banga) +## Racionalizavimo darbų sąrašas (ROI peržiūra — 9 fazė, 3 banga) -Šis inventorius buvo suderintas su `ci.yml` 2026-06-17 (ankstesnėje versijoje nebuvo +Šis sąrašas buvo suderintas su `ci.yml` 2026-06-17 (ankstesnėje versijoje nebuvo `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Suderinto rinkinio ROI peržiūra -nustatė toliau nurodytus racionalizavimo kandidatus. **Sujungimai yra mechaniniai CI -pakeitimai; perjungimai ir pašalinimai yra operatoriui palikti politikos sprendimai.** Nė vienas -toliau nurodytas pakeitimas dar nepritaikytas. +nustatė toliau pateiktus racionalizavimo kandidatus. **Sujungimai yra mechaniniai CI +pakeitimai; perjungimai ir pašalinimai yra operatoriui palikti politikos sprendimai.** Niekas iš toliau +pateiktų pakeitimų dar nepritaikyta. -**Taip pat pirmiau neaprašyta** (rekomendacinio pobūdžio, silpnas signalas): `docs-lint` užduotis -(markdownlint + Vale, visai užduočiai taikoma `continue-on-error`) ir atskiros skaitytuvų darbo eigos -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` yra faile -`quality-baseline.json`, tačiau nėra susietas su blokuojančiu reketiniu patikrinimu faile `ci.yml` — šiuo metu -metrika yra atsieta. +**Taip pat pirmiau nedokumentuota** (patariamojo pobūdžio, silpnas signalas): `docs-lint` užduotis +(markdownlint + Vale, visai užduočiai taikoma `continue-on-error`) ir atskiros skenerių darbo eigos +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` yra +`quality-baseline.json`, tačiau nėra prijungta prie blokuojančio reketo `ci.yml` faile — šiuo metu ši metrika +yra nesusieta. -### Sujungimas / dubliavimo šalinimas (mechaninis, mažesnė rizika) +### Sujungimas / dubliavimo pašalinimas (mechaniniai pakeitimai, mažesnė rizika) -Kiekvienas kandidatas buvo patikrintas pagal faktinę kontrolinių patikrų būseną 2026-06-17 (pasitikėk, bet tikrink); -paaiškėjo, kad keli „akivaizdūs“ sujungimai slepia techninę skolą ir **nėra** tinkami tiesiogiai pakeisti esamus sprendimus. +Kiekvienas kandidatas buvo patikrintas pagal faktinę vartų būseną 2026-06-17 (pasitikėk, bet patikrink); +paaiškėjo, kad keli „akivaizdūs“ sujungimai slepia techninę skolą ir **nėra** tiesiogiai pritaikomi. -- **`check:docs-sync` vykdomas du kartus** — atskirai `lint` užduotyje ir dar kartą `check:docs-all` (`docs-sync-strict`) viduje bei husky prieš patvirtinimą vykdomame kabliuke. ✅ **ATLIKTA** — atskiras iškvietimas iš `lint` pašalintas. -- **CVE nuskaitymas** — ❌ **NĖRA paprastas sujungimas.** `audit:deps` griežtai nesėkmingas aptikus bet kokį aukšto ar kritinio lygio CVE; `check:vuln-ratchet` (osv) nesėkmingas tik esant _regresijai_, palyginti su bazine reikšme (šiuo metu 1 MODERATE). Semantika skiriasi — pašalinus `audit:deps` būtų prarasta absoliuti aukšto ar kritinio lygio kontrolė. Palikti abu. -- **Ciklų aptikimas** — ❌ **NĖRA paprastas sujungimas.** `check:circular-deps` (dpdm) praneša apie **91 ciklą** (todėl jis yra rekomendacinio pobūdžio); jo negalima padaryti blokuojančio, kol šie ciklai nebus pašalinti, be to, jo aprėptis platesnė nei sėkmingai vykdomo, kruopščiai atrinkto `check:cycles`. Palikti `check:cycles` blokuojantį; 91 dpdm ciklo pašalinimas yra atskiras darbų sąrašo punktas. -- **Sudėtingumas** — ✅ **ATLIKTA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): vienas ESLint perėjimas, skaičiuojama pagal ruleId, todėl ciklomatinio sudėtingumo bei didžiausio eilučių skaičiaus ir kognityvinio sudėtingumo bazinės reikšmės išlieka nepriklausomos; atskiri `check:complexity` / `check:cognitive-complexity` palikti vietiniam `--update`. -- **`/api` apsauga nuo haliucinacijų** — ✅ **ATLIKTA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): viena `src/app/api` FS inventorizacija, openapi-routes + docs-symbols ir toliau teikia atskiras ataskaitas; atskiri patikrinimai palikti vietiniam vykdymui. -- **`check:node-runtime` vykdomas 11 užduočių** — ⚠️ **maža ROI.** Kiekviena užduotis vykdoma atskirame vykdyklės egzemplioriuje, o patikrinimas trunka <1 s; iš viso būtų sutaupyta ~10 s, tačiau būtų prarasta nebrangi kiekvienos užduoties apsauga. Neverta dėl to daryti pakeitimų. -- **`typecheck:noimplicit:core` CI lint užduotyje** — ✅ **pašalinta iš lint užduoties** (buvo rekomendacinio pobūdžio `continue-on-error`); blokuojamą tipų paviršių sudaro `typecheck:core` + `check:type-coverage`. Vietinis scenarijus paliktas. +- **`check:docs-sync` vykdoma du kartus** — atskirai `lint` užduotyje ir dar kartą `check:docs-all` (`docs-sync-strict`) viduje bei husky prieš patvirtinimą vykdomame kabliuke. ✅ **ATLIKTA** — atskiras iškvietimas `lint` užduotyje pašalintas. +- **CVE skenavimas** — ❌ **NETINKA tiesiogiai sujungti.** `audit:deps` griežtai nesėkmingai baigiasi aptikusi bet kokį aukšto / kritinio lygio CVE; `check:vuln-ratchet` (osv) nesėkmingai baigiasi tik esant _regresijai_, palyginti su bazine reikšme (šiuo metu 1 MODERATE). Semantika skiriasi — pašalinus `audit:deps` būtų prarasti absoliutūs aukšto / kritinio lygio vartai. Palikti abu. +- **Ciklų aptikimas** — ✅ **ATLIKTA** (#15159 G-01/G-02). Ankstesniame tekste `check:cycles` buvo vadinama „žaliais, kuruojamais“ vartais ir jos blokavimas buvo grindžiamas tuo, kad `check:circular-deps` (dpdm) pranešė apie 91 ciklą. Tas žalias rezultatas buvo **klaidingai teigiamas**: `check:cycles` skenavo 5 pakatalogius (450 failų), aptiko tik statinius `import|export … from` ir atmetė kiekvieną `@/` bei `@omniroute/open-sse/` specifikatorių, todėl negalėjo aptikti dinaminių importų ir pseudonimų ciklų, kurių saugykloje buvo daugiausia. Ištaisyta: dabar vartai pereina per `src` + `open-sse` (5023 failus), surenka specifikatorius iš TypeScript AST (todėl `import("…")` įskaičiuojamas, o tipo pozicijoje esantis `typeof import("…")` — ne) ir išsprendžia tsconfig `paths`. Aptinkama **14** ciklų, o ne 0. Kadangi 14 anksčiau egzistavusių ciklų negalima ištaisyti vartų PR, `check:cycles` dabar yra **reketas** (`--ratchet`, riba `metrics.cycles.value = 14` faile `quality-baseline.json`, `direction: down`) — jis blokuoja bet kokią _regresiją_, o skaičius gali tik mažėti. CI vykdo `npm run check:cycles:ratchet`. Mažinimo darbai vykdomi kartu su **A-01**. `check:circular-deps` (dpdm) lieka patariamojo pobūdžio kaip platesnė antroji nuomonė. +- **Sudėtingumas** — ✅ **ATLIKTA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): vienas ESLint perėjimas, skaičiuojama pagal ruleId, todėl ciklomatinio sudėtingumo ir maksimalaus eilučių skaičiaus bei kognityvinio sudėtingumo bazinės reikšmės lieka nepriklausomos; atskiros `check:complexity` / `check:cognitive-complexity` paliekamos vietiniam `--update`. +- **`/api` apsauga nuo haliucinacijų** — ✅ **ATLIKTA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): viena `src/app/api` FS inventorizacija, openapi-routes + docs-symbols vis dar teikia atskiras ataskaitas; atskiros patikros paliktos vietiniam vykdymui. +- **`check:node-runtime` vykdoma 11 užduočių** — ⚠️ **maža ROI.** Kiekvienai naudojamas atskiras vykdytojas, o patikra trunka <1 s; iš viso būtų sutaupyta ~10 s, tačiau būtų prarasta nebrangi kiekvienos užduoties apsauga. Neverta sukeliamo triukšmo. +- **`typecheck:noimplicit:core` CI lint užduotyje** — ✅ **pašalinta iš lint užduoties** (buvo patariamojo pobūdžio `continue-on-error`); blokuojamas tipų paviršius yra `typecheck:core` + `check:type-coverage`. Vietinis scenarijus paliktas. ### Perjungti / nuspręsti (operatoriaus politika) -- `check:openapi-security-tiers` (rekomendacinio pobūdžio) — ❌ **NEGALIMA paprastai perjungti.** Jis baigiamas su 0 kodu, tačiau įspėja, kad keliems `traffic-inspector` maršrutams, esantiems `LOCAL_ONLY_API_PREFIXES`, trūksta anotacijos `x-loopback-only: true`. Prieš įjungiant privalomą vykdymą pirmiausia reikia pridėti šias anotacijas į `openapi.yaml`. -- `typecheck:noimplicit:core` (rekomendacinio pobūdžio) — didžiąja dalimi pakeistas blokuojančiu reketiniu `check:type-coverage` patikrinimu. Paversti reketiniu patikrinimu arba pašalinti perteklinį antrą `tsc` perėjimą. -- `test:vitest:ui` (dabar **blokuojantis**) — ankstesnės klaidos aiškiai neįtrauktos faile `vitest.config.ts`, naudojant `// #8618` sekimo komentarus; dėl naujų klaidų užduotis tampa nesėkminga. -- `check:secrets` (gitleaks, blokuojantis reketinis patikrinimas, užfiksuotas ties 3 dokumentuotais klaidingai teigiamais rezultatais) — įtraukti tuos 3 atvejus į leidžiamųjų sąrašą, kad būtų pasiektas 0, arba pakeisti į rekomendacinio pobūdžio patikrinimą. Persidengia su GitHub savuoju paslapčių nuskaitymu + `check:public-creds`. -- `check:pr-evidence` (blokuojantis, ieško atitikmenų PR turinio tekste) — didelė klaidingai teigiamų rezultatų rizika; jį pašalinus susilpnėtų griežtos taisyklės Nr. 18 vykdymo užtikrinimas, todėl tai yra tikras politikos sprendimas. -- `semgrep` (atskiras rekomendacinio pobūdžio patikrinimas) — OWASP kategorijose persidengia su CodeQL; susieti jo bazinę reikšmę su reketiniu patikrinimu arba pašalinti. +- `check:openapi-security-tiers` (patariamojo pobūdžio) — ❌ **NETINKA tiesiogiai perjungti.** Ji baigiama su 0 kodu, tačiau įspėja, kad keliems `traffic-inspector` maršrutams, esantiems `LOCAL_ONLY_API_PREFIXES`, trūksta anotacijos `x-loopback-only: true`. Norint ją padaryti privalomą, pirmiausia reikia pridėti tas anotacijas į `openapi.yaml`. +- `typecheck:noimplicit:core` (patariamojo pobūdžio) — didžiąja dalimi pakeista blokuojančiu `check:type-coverage` reketu. Perjungti į reketą arba pašalinti perteklinį antrąjį `tsc` perėjimą. +- `test:vitest:ui` (dabar **blokuojanti**) — anksčiau egzistavusios triktys aiškiai neįtrauktos faile `vitest.config.ts` su `// #8618` sekimo komentarais; dėl naujų trikčių užduotis baigiama nesėkmingai. +- `check:secrets` (gitleaks, blokuojantis reketas, užfiksuotas ties 3 dokumentuotais klaidingai teigiamais rezultatais) — įtraukti šiuos 3 į leidžiamųjų sąrašą, kad būtų pasiektas 0, arba pažeminti iki patariamojo pobūdžio. Dubliuojasi su GitHub integruotu paslapčių skenavimu + `check:public-creds`. +- `check:pr-evidence` (blokuojanti, ieško atitikmenų PR aprašo tekste) — didelė klaidingai teigiamų rezultatų rizika; ją pašalinus susilpnėtų griežtosios taisyklės Nr. 18 vykdymas, todėl tai tikras politikos sprendimas. +- `semgrep` (atskira patariamojo pobūdžio patikra) — dubliuojasi su CodeQL OWASP šeimoms; prijungti jos bazinę reikšmę prie reketo arba pašalinti. --- diff --git a/docs/i18n/lt/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/lt/docs/compression/COMPRESSION_GUIDE.md index 386679089f2a..7faa1e77e143 100644 --- a/docs/i18n/lt/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/lt/docs/compression/COMPRESSION_GUIDE.md @@ -481,8 +481,7 @@ instrukcijos įterpiama lokalizuota instrukcija. Turinyje su `messages` turinio apėjimo funkcija (`shouldBypassCavemanOutputMode()`, esanti `open-sse/services/compression/outputMode.ts`) patikrina paskutinius tris pranešimus ir praleidžia stilių taikymą visam veiksmui, kai juose aptinkami saugumo, negrįžtamo veiksmo, -patikslinimo arba tvarkai jautrūs raktažodžiai. Apėjimo funkcija veikia pagal tai, kaip nustatytas -valdymo skydelio **Auto-Clarity Bypass** jungiklis (`cavemanOutputMode.autoClarity`). +patikslinimo arba tvarkai jautrūs raktažodžiai. Apėjimo funkcija veikia, kai valdymo skydelio **Auto-Clarity Bypass** jungiklis (`cavemanOutputMode.autoClarity`) įjungtas – tai numatytoji reikšmė; jungikliui išjungus, pasirinkti stiliai taikomi ir šiuose veiksmuose. Kai apėjimo funkcija leidžia apdoroti veiksmą, `placeSystemInstruction()` (tame pačiame faile), kuri niekada nesukuria naujo `messages[0]`, įterpia bloką pirmoje rastoje vietoje: diff --git a/docs/i18n/lt/docs/reference/ENVIRONMENT.md b/docs/i18n/lt/docs/reference/ENVIRONMENT.md index b2f12b2d06a7..7502878f1ff3 100644 --- a/docs/i18n/lt/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/lt/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Privalomos slaptosios reikšmės -Jos **turi** būti nustatytos prieš pirmąjį paleidimą. Be jų programa atsisakys paleisti arba veiks naudodama nesaugias numatytąsias reikšmes. - -| Kintamasis | Privalomas | Numatytoji reikšmė | Šaltinio failas | Aprašymas | -| ---------------------------- | ------------------- | ----------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Taip** | _(nėra)_ | `src/lib/auth` | Pasirašo ir tikrina visus valdymo skydelio seanso slapukus (JWT). Sugeneruokite naudodami `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Taip** | _(nėra)_ | `src/lib/db/apiKeys.ts` | AES šifravimo raktas, skirtas ramybės būsenoje esantiems API raktams šifruoti SQLite duomenų bazėje. Sugeneruokite naudodami `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Taip** | `CHANGEME` | Pradinės sąrankos scenarijus | Nustato pradinį administratoriaus valdymo skydelio slaptažodį (atitinka `.env.example` numatytąją reikšmę — ji palikta akivaizdžiai nesaugi, kad būtų privaloma ją pakeisti). **Pakeiskite prieš naudodami pirmą kartą.** Prisijungę pakeiskite per Valdymo skydelis → Nustatymai → Saugumas. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Taip** (gamyboje) | _(nenustatyta)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Bendroji slaptoji reikšmė, skirta vidiniam Codex Responses WebSocket tiltui. Autentifikuoja tilto užklausas tarp Electron / naršyklės WS perdavimo mechanizmo ir OmniRoute. ⚠️ **PRIVALOMA gamyboje — kai reikšmė nenustatyta, visos WS tilto užklausos atmetamos.** Sugeneruokite naudodami `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Aiškiai nurodytas PWA aplinkkalo tarnybinio darbuotojo podėlio atnaujinimo identifikatorius (#11779); pirmasis nustatymo grandinėje. | -| `SOURCE_VERSION` | Ne | _(nenustatyta)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Antrasis grandinėje — PaaS kūrimo sistemos (pvz., panašios į Heroku) nustato jį kaip įdiegtą commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(išvedama)_ | `src/shared/components/PwaRegister.tsx` | Kompiliavimo metu nustatoma vieša reikšmė, kurią klientas naudoja registruodamas `/sw.js?v=…`; ji išvedama iš dviejų pirmiau nurodytų reikšmių, o tada iš git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automatiškai) | _(automatiškai kiekvieno paleidimo metu)_ | `src/server/authz/policies/management.ts` | Kiekvieno proceso slaptoji reikšmė, įrodanti, kad patikimo lygiaverčio mazgo IP žyma gauta iš OmniRoute nuosavo HTTP serverio (`scripts/dev/peer-stamp.mjs`). Autorizavimo tarpinė programinė įranga pasitiki užklausos vietine kilme (LOCAL_ONLY maršrutų ribojimu iki loopback / LAN) tik tada, kai žymoje yra šis prieigos raktas. Jis automatiškai sugeneruojamas kiekvieno paleidimo metu — palikite nenustatytą; fiksuokite tik kelių procesų sąrankose, kuriose ši žyma turi būti bendra. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nenustatyta)_ | `scripts/dev/peer-stamp.mjs` | Kableliais atskirti atvirkštinių tarpinių serverių IP adresai arba CIDR diapazonai, kurių `X-Forwarded-For` / `X-Real-IP` reikšmėmis gali pasitikėti IP leidimų / draudimų sąrašas, papildomai prie loopback, privačių tinklų adresų ir Cloudflare kraštinių mazgų, kuriais pasitikima visada. Reikalinga tik tarpiniam serveriui, naudojančiam bet kurį kitą viešąjį adresą; kitu atveju filtras vertina paties tarpinio serverio adresą. | +Nenustatytos paslaptys, pažymėtos **Taip**, pirmojo paleidimo metu sugeneruojamos automatiškai (`JWT_SECRET`, `API_KEY_SECRET`) ir išsaugomos duomenų katalogo faile `server.env` — nustatykite jas aiškiai, kai tas katalogas yra laikinas. Išimtis — `OMNIROUTE_WS_BRIDGE_SECRET`: ji faile `server.env` neišsaugoma — serverio įėjimo taškai kiekvieno paleidimo metu sukuria laikiną reikšmę, kurią gali naudoti tik procese veikiantis tiltas, todėl išoriniams tilto klientams ją reikia nustatyti aiškiai. + +| Kintamasis | Privalomas | Numatytoji reikšmė | Šaltinio failas | Aprašymas | +| ---------------------------- | ------------------- | ----------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Taip** | _(nėra)_ | `src/lib/auth` | Pasirašo ir tikrina visus valdymo skydelio seanso slapukus (JWT). Sugeneruokite naudodami `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Taip** | _(nėra)_ | `src/lib/db/apiKeys.ts` | AES šifravimo raktas, skirtas ramybės būsenoje esantiems API raktams šifruoti SQLite duomenų bazėje. Sugeneruokite naudodami `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ne | _(nenustatyta)_ | `src/lib/auth/managementPassword.ts` | Kol nėra išsaugoto slaptažodžio, paleidimo metu nustato valdymo skydelio slaptažodį; kai slaptažodis išsaugotas, ši reikšmė nepaisoma. Įdiegimai per npm ir iš pirminio kodo nukopijuoja `.env.example` į `.env`, todėl prasideda su jo viešai žinomu pakaitalu `CHANGEME`: pakeiskite jį prieš pirmąjį paleidimą arba toje `.env` faile nustatykite `INITIAL_PASSWORD=` (tuščią), kad slaptažodis būtų sukurtas sąrankos vediklyje pirmojo apsilankymo metu, kaip tai daro nenustatyta reikšmė (Docker vaizdo numatytoji). Išsaugotą slaptažodį keiskite per Valdymo skydelis → Nustatymai → Saugumas arba paleiskite `omniroute reset-password` (`node bin/reset-password.mjs` pirminio kodo kopijoje), kai `DATA_DIR` nurodo serverio duomenų katalogą. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Taip** (gamyboje) | _(nenustatyta)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Bendroji slaptoji reikšmė, skirta vidiniam Codex Responses WebSocket tiltui. Autentifikuoja tilto užklausas tarp Electron / naršyklės WS perdavimo mechanizmo ir OmniRoute. ⚠️ **PRIVALOMA gamyboje — kai reikšmė nenustatyta, visos WS tilto užklausos atmetamos.** Sugeneruokite naudodami `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Aiškiai nurodytas PWA aplinkkalo tarnybinio darbuotojo podėlio atnaujinimo identifikatorius (#11779); pirmasis nustatymo grandinėje. | +| `SOURCE_VERSION` | Ne | _(nenustatyta)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Antrasis grandinėje — PaaS kūrimo sistemos (pvz., panašios į Heroku) nustato jį kaip įdiegtą commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(išvedama)_ | `src/shared/components/PwaRegister.tsx` | Kompiliavimo metu nustatoma vieša reikšmė, kurią klientas naudoja registruodamas `/sw.js?v=…`; ji išvedama iš dviejų pirmiau nurodytų reikšmių, o tada iš git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (automatiškai) | _(automatiškai kiekvieno paleidimo metu)_ | `src/server/authz/policies/management.ts` | Kiekvieno proceso slaptoji reikšmė, įrodanti, kad patikimo lygiaverčio mazgo IP žyma gauta iš OmniRoute nuosavo HTTP serverio (`scripts/dev/peer-stamp.mjs`). Autorizavimo tarpinė programinė įranga pasitiki užklausos vietine kilme (LOCAL_ONLY maršrutų ribojimu iki loopback / LAN) tik tada, kai žymoje yra šis prieigos raktas. Jis automatiškai sugeneruojamas kiekvieno paleidimo metu — palikite nenustatytą; fiksuokite tik kelių procesų sąrankose, kuriose ši žyma turi būti bendra. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(nenustatyta)_ | `scripts/dev/peer-stamp.mjs` | Kableliais atskirti atvirkštinių tarpinių serverių IP adresai arba CIDR diapazonai, kurių `X-Forwarded-For` / `X-Real-IP` reikšmėmis gali pasitikėti IP leidimų / draudimų sąrašas, papildomai prie loopback, privačių tinklų adresų ir Cloudflare kraštinių mazgų, kuriais pasitikima visada. Reikalinga tik tarpiniam serveriui, naudojančiam bet kurį kitą viešąjį adresą; kitu atveju filtras vertina paties tarpinio serverio adresą. | ### Generavimo komandos @@ -217,38 +217,38 @@ Visiems nuolatiniams duomenims saugoti „OmniRoute“ naudoja **SQLite** (per ` ## 4. Saugumas ir autentifikavimas -| Kintamasis | Numatytoji reikšmė | Šaltinio failas | Aprašymas | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Papildoma reikšmė, sujungiama su aparatinės įrangos identifikatoriais įrenginio kontroliniam atspaudui sudaryti. Kad diegimai būtų izoliuoti, kiekviename diegime ją pakeiskite. | -| `OMNIROUTE_CLI_SALT` | _(nenustatyta = atsitiktinė kiekvieno diegimo papildoma reikšmė, išsaugoma `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC papildoma reikšmė, naudojama vietiniam CLI autentifikavimo prieigos raktui išvesti. Nustačius šią reikšmę, pakeičiami visi įrenginio CLI prieigos raktai, o ji visada turi pirmenybę prieš išsaugotą papildomą reikšmę. Žr. `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Seanso slapukams nustato požymį `Secure`. Naudojant už HTTPS, **privalo būti `true`**. | -| `REQUIRE_API_KEY` | `false` | API tarpinė programinė įranga | Kai reikšmė yra `true`, visose `/v1/*` tarpinio serverio užklausose turi būti galiojantis API raktas. **Šis požymis neapriboja `GET /v1/models`**, nes šiai užklausai taikoma prietaisų skydelio prisijungimo nuostata (`requireAuthForModels`) — todėl `/v1/models` grąžintas `401` NEREIŠKIA, kad išvedimas yra apsaugotas. Žr. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Leidžia prietaisų skydelio naudotojo sąsajoje parodyti visas API raktų reikšmes. Konfigūruojama prietaisų skydelio funkcijų požymių skiltyje; bendrinamuose egzemplioriuose tai kelia saugumo riziką. | -| `NO_LOG_API_KEY_IDS` | _(tuščia)_ | `src/lib/compliance/index.ts` | Kableliais atskirti API raktų ID, kuriems netaikomas užklausų registravimas (BDAR atitiktis). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenustatyta = neribota)_ | `src/shared/utils/apiKeyPolicy.ts` | Atsarginis dienos užklausų limitas, taikomas API raktams, kurių stulpelio `rate_limits` reikšmė yra null. Nenustatyta arba tuščia reikšmė: nėra numanomo apribojimo (#2289, #11017). `0` reiškia tą patį (neribota). Teigiamas sveikasis skaičius N įgalina N per dieną, 5N per savaitę ir 20N per mėnesį. Netinkamai suformuotoms netuščioms reikšmėms taikomi ankstesni 1000 per dieną, 5000 per savaitę ir 20000 per mėnesį laikotarpiai. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Didžiausias leidžiamas užklausos turinio dydis. Šį limitą viršijantys duomenys atmetami. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiniams užklausų turiniams, kurių dydis siekia arba viršija šią ribą, prieš analizuojant JSON atominiu būdu suteikiama vietinio proceso daug išteklių naudojančio apdorojimo prieigos teisė (BYTE kelias, įskaitant `POST /v1/responses`). Taikomas tas pats [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) pakankamos laisvos talpos išimties mechanizmas kaip ir sudėtingos struktūros užklausoms; vis tiek ribojama naudojant `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), kad [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) problema nepasikartotų. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Griežtoji pokalbių maršruto riba, taikoma riboto nuskaitymo metu perskaitytiems baitams, įskaitant užklausas, kuriose `Content-Length` nėra, jis netinkamas arba klaidinantis; viršijus ribą grąžinamas `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nenustatyta — užklausų skaičiaus ribos nėra)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ši senesnė užklausų SKAIČIAUS riba dabar taikoma tik tada, kai yra aiškiai nustatyta. Jei ji nenustatyta (numatytoji būsena), didelės apkrovos pokalbių užklausų priėmimą riboja `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatiškai nustatomas BAITŲ biudžetas, apskaičiuotas pagal faktinę proceso atminties ribą **viename procese** (viename V8 kaupe). Dvi persidengiančios maždaug 750 tūkst. prieigos raktų `/v1/responses` užklausos nutraukia maždaug 12 GiB kaupų veikimą (#7849) — tai **atminties biudžeto** įspėjimas, o ne griežta produkto riba, lygi 2. Tvarkingai veikiančiame procese (kai kaupas nesiekia apkrovos mažinimo santykio) GALIMA priimti daugiau lygiagrečių ilgų `/v1/responses` užklausų naudojant `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dešimtys ilgai veikiančių SSE klientų (40–50) priklauso nuo kaupo ir `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, o ne nuo „daugiausia 2“. Aklai padidinus šią reikšmę, kad būtų „naudojamas pagrindinis kompiuteris“, vėl atsiranda #7849. Dauginkite **kaupus**, naudodami **N nepriklausomų `DATA_DIR`** (#11024); niekada nenaudokite `replicas>1` su vienu SQLite failu. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(nustatoma automatiškai)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automatiškai nustatomo gaunamų duomenų baitų biudžeto pakeitimas (25 % griežtesnės V8 / cgroup atminties ribos, padalytos iš 8 kartų laikinojo išdidėjimo). Išvestinės ir aiškiai nurodytos reikšmės apribojamos iki 8 MiB–2 GiB. Už efektyvųjį biudžetą didesniam turiniui iškart grąžinama `413 body_exceeds_budget`; konkurencija tarp atskirai aptarnaujamų turinių lieka pakartotinai bandytina `503` klaida. 40–50 lygiagrečių ilgai veikiančių SSE klientų priklauso nuo šio biudžeto ir kaupo, o ne nuo griežtos „daugiausia 2“ ribos. Prieš derindami perskaitykite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` adresu `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kaupo apkrovos mažinimo santykis (`heapUsed / heap_size_limit`), taikomas BAITŲ ir STRUKTŪROS požiūriu didelės apkrovos užklausų priėmimui (#10183, #10268, #10437). Lygiagreti didelės apkrovos užklausa, viršijanti `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`, atmetama su pakartotinai bandytina `503` klaida tik tada, kai kaupas TAIP PAT yra pasiekęs arba viršijęs šį santykį; kai kaupas veikia tvarkingai, užklausa priimama naudojant papildomą pajėgumą. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (numatytoji reikšmė `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ribotas papildomas pajėgumas tvarkingo kaupo sparčiajam keliui (#10437), taikomas **ir** STRUKTŪRAI, **ir** BAITAMS (`admitChatRequest`, įskaitant turinius ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Be šios ribos kiekviena užimto, bet tvarkingo kaupo užklausa apeidavo priėmimo valdymą be jokios viršutinės ribos. Kai šiuo tvarkingo kaupo keliu aktyvinamas nurodytas lygiagrečių nuomos įrašų skaičius, paskesnės užklausos pereina į TĄ PATĮ riboto laukimo / atmetimo kelią, kuris naudojamas esant realiai kaupo apkrovai. `0` visiškai išjungia apėjimą. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Pranešimų skaičius, pagal kurį pokalbio užklausa klasifikuojama kaip didelės apkrovos, net kai jos turinys nesiekia baitų ribos. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Įrankių skaičius, pagal kurį pokalbio užklausa klasifikuojama kaip didelės apkrovos, net kai jos turinys nesiekia baitų ribos. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatyvus prieigos raktų skaičiaus įvertis pagal eilutės dydį, pagal kurį užklausa klasifikuojama kaip didelės apkrovos; tai yra priėmimo sąnaudų aproksimacija, o ne teikėjo atsiskaitymams naudojamas prieigos raktų skaičiavimas. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (išjungta) | `src/shared/middleware/chatBodyAdmission.ts` | Pasirinktinė pokalbių istorijos riba. Pagal numatytąją nuostatą išjungta: pranešimų skaičius yra diegimo politika, o ne universali užklausos savybė, todėl čia taikoma riba atmestų pokalbius su galutine `413` klaida dar prieš glaudinimo konvejeriui paverčiant juos aptarnaujamais. Kaupo augimą riboja `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ir kaupo apkrovos mažinimas. Ribotos atminties diegimuose, kuriems reikia griežtos viršutinės ribos, nustatykite teigiamą reikšmę; ją viršijus grąžinama struktūrinė `413` klaida, nurodanti, kad būtinas glaudinimas. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Griežtoji nesrautinio aukštesniojo serverio atsako, visiškai buferizuojamo atmintyje, riba. Ją viršijus aukštesniojo serverio skaitytuvas atšaukiamas, o užklausa greitai nutraukiama, užuot leidus neribotai eilutei augti, kol išnaudojamas kaupas. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Didžiausias iš pirminio serverio atsakymo antraščių persiunčiamų baitų skaičius. Viršijus limitą, žemesnio prioriteto antraštės (pvz., pasirinktinės `x-codex-*`, `x-oai-request-id`) atmetamos, kad būtų laikomasi įprastų atvirkštinio tarpinio serverio antraščių apribojimų. Nustatykite didesnę reikšmę, kad būtų persiunčiama daugiau pirminio serverio metaduomenų, tačiau atsakymo antraštės bus didesnės. | -| `CORS_ORIGIN` | _(nenustatyta)_ | `src/server/cors/origins.ts` | Senasis vieno šaltinio CORS leidžiamųjų reikšmių sąrašas. Naujiems diegimams rekomenduojama naudoti `CORS_ALLOWED_ORIGINS`. CORS skirtas tik skirtingų šaltinių naršyklės API klientams; autentifikuoti valdymo skydelio rašymo veiksmai naudoja tos pačios kilmės užklausas ir su sesija susietą CSRF apsaugą. | -| `CORS_ALLOWED_ORIGINS` | _(nenustatyta)_ | `src/server/cors/origins.ts` | Kableliais atskirtas CORS leidžiamųjų reikšmių sąrašas. Pakaitos simbolis nesiunčiamas, nebent aiškiai sukonfigūruota `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Tik kūrimui skirta išimtinė priemonė, leidžianti atkartoti bet kurią naršyklės `Origin` reikšmę. Neįjunkite bendrinamuose ar produkciniuose diegimuose. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokuoti paslaugų teikėjų iškvietimus, nukreiptus į privačius, grįžtamojo ryšio ar vietinio ryšio IP diapazonus. Išjunkite tik izoliuotose testavimo aplinkose. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Leisti paslaugų teikėjų URL, nukreipiančius į privačius ar vietinius tinklus (localhost, 192.168.x.x, 10.x.x.x ir t. t.). **BŪTINA savarankiškai talpinamiems paslaugų teikėjams** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Kai nustatyta `false`, valdymo skydelis atmeta vietinių URL patikrą. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Leisti pridėti ir tikrinti paslaugų teikėjus vietiniais ar privačiais adresais (127.0.0.1, localhost, LAN, privatūs diapazonai) — taikoma tik paslaugų teikėjo tikrinimo keliui. **Numatytoji reikšmė `true`** (pirmenybė vietiniams); nustatykite `false`, kad būtų taikomas griežtas tik viešų adresų leidimas. Debesijos metaduomenų galiniai taškai (169.254.169.254, metadata.google.internal) lieka blokuojami neatsižvelgiant į šią nuostatą. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Leisti `/v1/audio/*` maršrutams (transkripcijai, kalbos sintezei, vertimams) naudoti su OpenAI suderinamą paslaugų teikėjo mazgą, talpinamą ne localhost. Pagal numatytąją nuostatą išjungta — garso duomenų nukreipimas į nuotolinį pagrindinį kompiuterį pakeičia išeinančio srauto tapatybę, todėl tai turi būti aiškus operatoriaus sprendimas. Grįžtamojo ryšio ir privatūs mazgai (localhost, 127.0.0.1, 172.16-31.x) visada leidžiami ir ši nuostata jiems įtakos neturi. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Leisti `POST /v1/rerank` (ir atminties modulio grįžtamojo ryšio perrikiavimo veiksmui) naudoti su OpenAI suderinamą paslaugų teikėjo mazgą, talpinamą ne localhost — LAN kompiuterį arba Tailscale lygiavertį mazgą, kuriame veikia TEI, Infinity, vLLM ir pan. Pagal numatytąją nuostatą išjungta — nukreipimas į nuotolinį pagrindinį kompiuterį pakeičia išeinančio srauto tapatybę, todėl tai turi būti aiškus operatoriaus sprendimas. Grįžtamojo ryšio mazgai (localhost, 127.0.0.1, 172.16-31.x) visada leidžiami ir ši nuostata jiems įtakos neturi. Nuotoliniai mazgai taip pat turi atitikti paslaugų teikėjo išeinančių URL politiką (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); srautas niekada nenukreipiamas į debesijos metaduomenų pagrindinius kompiuterius. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kai OIDC įjungtas, išjungti prisijungimą slaptažodžiu, kad naudotojai galėtų autentifikuotis tik per OIDC bendrąjį prisijungimą. Taip pat priimamas trumpasis pseudonimas `OIDC_DISABLE_PASSWORD_LOGIN`; pirmenybę turi valdymo skydelio funkcijos žymė tuo pačiu raktu. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Trumpasis `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` pseudonimas (#10889). | +| Kintamasis | Numatytoji reikšmė | Šaltinio failas | Aprašymas | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Papildoma reikšmė, sujungiama su aparatinės įrangos identifikatoriais įrenginio kontroliniam atspaudui sudaryti. Kad diegimai būtų izoliuoti, kiekviename diegime ją pakeiskite. | +| `OMNIROUTE_CLI_SALT` | _(nenustatyta = atsitiktinė kiekvieno diegimo papildoma reikšmė, išsaugoma `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC papildoma reikšmė, naudojama vietiniam CLI autentifikavimo prieigos raktui išvesti. Nustačius šią reikšmę, pakeičiami visi įrenginio CLI prieigos raktai, o ji visada turi pirmenybę prieš išsaugotą papildomą reikšmę. Žr. `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Seanso slapukams nustato požymį `Secure`. Naudojant už HTTPS, **privalo būti `true`**. | +| `REQUIRE_API_KEY` | `false` | API tarpinė programinė įranga | Kai reikšmė yra `true`, visose `/v1/*` tarpinio serverio užklausose turi būti galiojantis API raktas. **Šis požymis neapriboja `GET /v1/models`**, nes šiai užklausai taikoma prietaisų skydelio prisijungimo nuostata (`requireAuthForModels`) — todėl `/v1/models` grąžintas `401` NEREIŠKIA, kad išvedimas yra apsaugotas. Žr. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Leidžia prietaisų skydelio naudotojo sąsajoje parodyti visas API raktų reikšmes. Konfigūruojama prietaisų skydelio funkcijų požymių skiltyje; bendrinamuose egzemplioriuose tai kelia saugumo riziką. | +| `NO_LOG_API_KEY_IDS` | _(tuščia)_ | `src/lib/compliance/index.ts` | Kableliais atskirti API raktų ID, kuriems netaikomas užklausų registravimas (BDAR atitiktis). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenustatyta = neribota)_ | `src/shared/utils/apiKeyPolicy.ts` | Atsarginis dienos užklausų limitas, taikomas API raktams, kurių stulpelio `rate_limits` reikšmė yra null. Nenustatyta arba tuščia reikšmė: nėra numanomo apribojimo (#2289, #11017). `0` reiškia tą patį (neribota). Teigiamas sveikasis skaičius N įgalina N per dieną, 5N per savaitę ir 20N per mėnesį. Netinkamai suformuotoms netuščioms reikšmėms taikomi ankstesni 1000 per dieną, 5000 per savaitę ir 20000 per mėnesį laikotarpiai. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Didžiausias leidžiamas užklausos turinio dydis. Šį limitą viršijantys duomenys atmetami. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiniams užklausų turiniams, kurių dydis siekia arba viršija šią ribą, prieš analizuojant JSON atominiu būdu suteikiama vietinio proceso daug išteklių naudojančio apdorojimo prieigos teisė (BYTE kelias, įskaitant `POST /v1/responses`). Taikomas tas pats [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) pakankamos laisvos talpos išimties mechanizmas kaip ir sudėtingos struktūros užklausoms; vis tiek ribojama naudojant `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), kad [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) problema nepasikartotų. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Griežtoji pokalbių maršruto riba, taikoma riboto nuskaitymo metu perskaitytiems baitams, įskaitant užklausas, kuriose `Content-Length` nėra, jis netinkamas arba klaidinantis; viršijus ribą grąžinamas `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nenustatyta — užklausų skaičiaus ribos nėra)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ši senesnė užklausų SKAIČIAUS riba dabar taikoma tik tada, kai yra aiškiai nustatyta. Jei ji nenustatyta (numatytoji būsena), didelės apkrovos pokalbių užklausų priėmimą riboja `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatiškai nustatomas BAITŲ biudžetas, apskaičiuotas pagal faktinę proceso atminties ribą **viename procese** (viename V8 kaupe). Dvi persidengiančios maždaug 750 tūkst. prieigos raktų `/v1/responses` užklausos nutraukia maždaug 12 GiB kaupų veikimą (#7849) — tai **atminties biudžeto** įspėjimas, o ne griežta produkto riba, lygi 2. Tvarkingai veikiančiame procese (kai kaupas nesiekia apkrovos mažinimo santykio) GALIMA priimti daugiau lygiagrečių ilgų `/v1/responses` užklausų naudojant `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dešimtys ilgai veikiančių SSE klientų (40–50) priklauso nuo kaupo ir `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, o ne nuo „daugiausia 2“. Aklai padidinus šią reikšmę, kad būtų „naudojamas pagrindinis kompiuteris“, vėl atsiranda #7849. Dauginkite **kaupus**, naudodami **N nepriklausomų `DATA_DIR`** (#11024); niekada nenaudokite `replicas>1` su vienu SQLite failu. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(nustatoma automatiškai)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automatiškai nustatomo gaunamų duomenų baitų biudžeto pakeitimas (25 % griežtesnės V8 / cgroup atminties ribos, padalytos iš 8 kartų laikinojo išdidėjimo). Išvestinės ir aiškiai nurodytos reikšmės apribojamos iki 8 MiB–2 GiB. Už efektyvųjį biudžetą didesniam turiniui iškart grąžinama `413 body_exceeds_budget`; konkurencija tarp atskirai aptarnaujamų turinių lieka pakartotinai bandytina `503` klaida. 40–50 lygiagrečių ilgai veikiančių SSE klientų priklauso nuo šio biudžeto ir kaupo, o ne nuo griežtos „daugiausia 2“ ribos. Prieš derindami perskaitykite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` adresu `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kaupo apkrovos mažinimo santykis (`heapUsed / heap_size_limit`), taikomas BAITŲ ir STRUKTŪROS požiūriu didelės apkrovos užklausų priėmimui (#10183, #10268, #10437). Lygiagreti didelės apkrovos užklausa, viršijanti `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`, atmetama su pakartotinai bandytina `503` klaida tik tada, kai kaupas TAIP PAT yra pasiekęs arba viršijęs šį santykį; kai kaupas veikia tvarkingai, užklausa priimama naudojant papildomą pajėgumą. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (numatytoji reikšmė `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ribotas papildomas pajėgumas tvarkingo kaupo sparčiajam keliui (#10437), taikomas **ir** STRUKTŪRAI, **ir** BAITAMS (`admitChatRequest`, įskaitant turinius ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Be šios ribos kiekviena užimto, bet tvarkingo kaupo užklausa apeidavo priėmimo valdymą be jokios viršutinės ribos. Kai šiuo tvarkingo kaupo keliu aktyvinamas nurodytas lygiagrečių nuomos įrašų skaičius, paskesnės užklausos pereina į TĄ PATĮ riboto laukimo / atmetimo kelią, kuris naudojamas esant realiai kaupo apkrovai. `0` visiškai išjungia apėjimą. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Pranešimų skaičius, pagal kurį pokalbio užklausa klasifikuojama kaip didelės apkrovos, net kai jos turinys nesiekia baitų ribos. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Įrankių skaičius, pagal kurį pokalbio užklausa klasifikuojama kaip didelės apkrovos, net kai jos turinys nesiekia baitų ribos. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatyvus prieigos raktų skaičiaus įvertis pagal eilutės dydį, pagal kurį užklausa klasifikuojama kaip didelės apkrovos; tai yra priėmimo sąnaudų aproksimacija, o ne teikėjo atsiskaitymams naudojamas prieigos raktų skaičiavimas. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (išjungta) | `src/shared/middleware/chatBodyAdmission.ts` | Pasirinktinė pokalbių istorijos riba. Pagal numatytąją nuostatą išjungta: pranešimų skaičius yra diegimo politika, o ne universali užklausos savybė, todėl čia taikoma riba atmestų pokalbius su galutine `413` klaida dar prieš glaudinimo konvejeriui paverčiant juos aptarnaujamais. Kaupo augimą riboja `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ir kaupo apkrovos mažinimas. Ribotos atminties diegimuose, kuriems reikia griežtos viršutinės ribos, nustatykite teigiamą reikšmę; ją viršijus grąžinama struktūrinė `413` klaida, nurodanti, kad būtinas glaudinimas. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Griežtoji nesrautinio aukštesniojo serverio atsako, visiškai buferizuojamo atmintyje, riba. Ją viršijus aukštesniojo serverio skaitytuvas atšaukiamas, o užklausa greitai nutraukiama, užuot leidus neribotai eilutei augti, kol išnaudojamas kaupas. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Didžiausias iš pirminio serverio atsakymo antraščių persiunčiamų baitų skaičius. Viršijus limitą, žemesnio prioriteto antraštės (pvz., pasirinktinės `x-codex-*`, `x-oai-request-id`) atmetamos, kad būtų laikomasi įprastų atvirkštinio tarpinio serverio antraščių apribojimų. Nustatykite didesnę reikšmę, kad būtų persiunčiama daugiau pirminio serverio metaduomenų, tačiau atsakymo antraštės bus didesnės. | +| `CORS_ORIGIN` | _(nenustatyta)_ | `src/server/cors/origins.ts` | Senasis vieno šaltinio CORS leidžiamųjų reikšmių sąrašas. Naujiems diegimams rekomenduojama naudoti `CORS_ALLOWED_ORIGINS`. CORS skirtas tik skirtingų šaltinių naršyklės API klientams; autentifikuoti valdymo skydelio rašymo veiksmai naudoja tos pačios kilmės užklausas ir su sesija susietą CSRF apsaugą. | +| `CORS_ALLOWED_ORIGINS` | _(nenustatyta)_ | `src/server/cors/origins.ts` | Kableliais atskirtas CORS leidžiamųjų reikšmių sąrašas. Pakaitos simbolis nesiunčiamas, nebent aiškiai sukonfigūruota `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Tik kūrimui skirta išimtinė priemonė, leidžianti atkartoti bet kurią naršyklės `Origin` reikšmę. Neįjunkite bendrinamuose ar produkciniuose diegimuose. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Pasenęs alternatyvusis pavadinimas. Jo valdymo skydelio perjungiklis (duomenų bazės perrašymas) skaitomas dar prieš šią aplinkos kintamojo reikšmę; `false`, `0`, `no` ar `off` bet kuriame iš jų išjungia pagrindinio kompiuterio tikrinimus taip, kaip tai daro `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, net kai to požymio valdymo skydelio perjungiklis išsaugotas išjungtas; kitos reikšmės apsaugą palieka žemiau išvardytiems dviem požymiams. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Išjungia išeinančių URL apsaugos pagrindinio kompiuterio tikrinimus, įskaitant debesijos metaduomenų blokavimą (visą 169.254.0.0/16, metadata.google.internal), paslaugų teikėjų keliuose, kurie vadovaujasi apsaugos režimu: URL tikrinimas, modelių aptikimas, paslaugų teikėjų mazgų pagrindiniai URL (įskaitant nuotolinius perrikiavimo mazgus) ir proxy-fallback testas, taip pat kituose su paslaugų teikėjais susijusiuose išeinančiuose keliuose, pavyzdžiui, gaunant nutolusius vaizdus ar medijas. Taip pat leidžia privačius webhook adresatus, išskyrus debesijos metaduomenų ir vietinio ryšio galinius taškus, kurie lieka blokuojami net įjungus šį požymį. Pokalbio užklausos, siunčiamos per `BaseExecutor.execute()`, išlaiko savo debesijos metaduomenų tikrinimą. Vietiniai paslaugų teikėjai veikia su numatytosiomis reikšmėmis: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (numatytoji `true`) jau leidžia vietinius ir LAN URL tikrinimo, aptikimo ir paslaugų teikėjų mazgų keliuose; proxy-fallback testas ir privatūs webhook adresatai žiūri tik į šį požymį ir, kol jis išjungtas, blokuoja vietinius bei LAN pagrindinius kompiuterius. Nepaiso `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Iš valdymo skydelio perjungiklio išsaugota reikšmė turi pirmenybę prieš šį kintamąjį. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Leidžia paslaugų teikėjų URL vietiniais ar privačiais adresais (127.0.0.1, localhost, LAN, privatūs diapazonai) paslaugų teikėjų tikrinimui, modelių aptikimui, pokalbiams ir kitiems su paslaugų teikėjais susijusiems išeinantiems keliams, kuriuos valdo apsaugos režimas. **Numatytoji reikšmė `true`** (pirmenybė vietiniams): tuomet apsauga blokuoja debesijos metaduomenų galinius taškus — visą 169.254.0.0/16 ir žinomus metaduomenų pagrindinio kompiuterio vardus (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Nustatykite `false` tik viešam režimui, kuris blokuoja ir privačius, ir grįžtamojo ryšio pagrindinius kompiuterius. Apsauga tikrina pagrindinio kompiuterio vardą arba IP literalą tokį, koks užrašytas, dar prieš bet kokią DNS paiešką; tik viešame režime safe-fetch įvyniojimas papildomai išsprendžia pagrindinio kompiuterio vardą ir atmeta bet kokį privatų atsakymą. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` išjungia šiuos tikrinimus keliuose, išvardytuose jo eilutėje. Įtaisytieji vietiniai paslaugų teikėjai, tokie kaip LM Studio, Ollama, vLLM, Llamafile ir Triton, praleidžia visus pagrindinio kompiuterio tikrinimus raktų tikrinimui ir pokalbiams. Iš valdymo skydelio perjungiklio išsaugota reikšmė turi pirmenybę prieš šį kintamąjį. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Leisti `/v1/audio/*` maršrutams (transkripcijai, kalbos sintezei, vertimams) naudoti su OpenAI suderinamą paslaugų teikėjo mazgą, talpinamą ne localhost. Pagal numatytąją nuostatą išjungta — garso duomenų nukreipimas į nuotolinį pagrindinį kompiuterį pakeičia išeinančio srauto tapatybę, todėl tai turi būti aiškus operatoriaus sprendimas. Grįžtamojo ryšio ir privatūs mazgai (localhost, 127.0.0.1, 172.16-31.x) visada leidžiami ir ši nuostata jiems įtakos neturi. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Leisti `POST /v1/rerank` (ir atminties modulio grįžtamojo ryšio perrikiavimo veiksmui) naudoti su OpenAI suderinamą paslaugų teikėjo mazgą, talpinamą ne localhost — LAN kompiuterį arba Tailscale lygiavertį mazgą, kuriame veikia TEI, Infinity, vLLM ir pan. Pagal numatytąją nuostatą išjungta — nukreipimas į nuotolinį pagrindinį kompiuterį pakeičia išeinančio srauto tapatybę, todėl tai turi būti aiškus operatoriaus sprendimas. Grįžtamojo ryšio mazgai (localhost, 127.0.0.1, 172.16-31.x) visada leidžiami ir ši nuostata jiems įtakos neturi. Nuotolinio mazgo pagrindinis URL taip pat turi atitikti paslaugų teikėjo išeinančių URL politiką (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kai OIDC įjungtas, išjungti prisijungimą slaptažodžiu, kad naudotojai galėtų autentifikuotis tik per OIDC bendrąjį prisijungimą. Taip pat priimamas trumpasis pseudonimas `OIDC_DISABLE_PASSWORD_LOGIN`; pirmenybę turi valdymo skydelio funkcijos žymė tuo pačiu raktu. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Trumpasis `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` pseudonimas (#10889). | ### Sustiprinimo kontrolinis sąrašas diff --git a/docs/i18n/lv/CONTRIBUTING.md b/docs/i18n/lv/CONTRIBUTING.md index 93d02493d2a9..427199ec2a08 100644 --- a/docs/i18n/lv/CONTRIBUTING.md +++ b/docs/i18n/lv/CONTRIBUTING.md @@ -391,20 +391,20 @@ Uzrakstiet vienību testus direktorijā `tests/unit/`, kas aptver vismaz: ## Pull Request kontrolsaraksts -- [ ] Testi ir veiksmīgi (`npm test`) -- [ ] Lint pārbaude ir veiksmīga (`npm run lint`) -- [ ] Būvēšana ir veiksmīga (`npm run build`) -- [ ] Jaunām publiskajām funkcijām un saskarnēm ir pievienoti TypeScript tipi -- [ ] Nav cietkodētu noslēpumu vai rezerves vērtību -- [ ] Publiskie augšupējā nodrošinātāja akreditācijas dati ir iegulti, izmantojot `resolvePublicCred()` (skatiet [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nekad kā literāļi -- [ ] Kļūdu atbildes tiek novirzītas caur `buildErrorBody()` / `sanitizeErrorMessage()` — atbildes pamattekstā nav neapstrādātu steka izsekojumu (skatiet [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Čaulas komandas (`exec` / `spawn`) izpildlaika vērtības padod, izmantojot `env`, nevis virkņu interpolāciju -- [ ] Visas ievades tiek validētas ar Zod shēmām -- [ ] Lietotājiem redzamām izmaiņām ir pievienots izmaiņu žurnāla **fragment** fails `changelog.d/{features|fixes|maintenance}/-.md` (skatiet [`changelog.d/README.md`](./changelog.d/README.md)) — **ne**rediģējiet `CHANGELOG.md` tieši; fragmenti tiek apkopoti laidiena izveides laikā un nekad nerada konfliktus starp PR -- [ ] Dokumentācija ir atjaunināta (ja piemērojams) -- [ ] Nav atvērti jauni CodeQL / Secret-Scanning brīdinājumi, vai arī katrs no tiem ir noraidīts ar tehnisku pamatojumu, atsaucoties uz atbilstošo `docs/security/` dokumentu -- [ ] Maršruti, kas palaiž bērnprocesus (`/api/mcp/`, `/api/cli-tools/runtime/`), failā `src/server/authz/routeGuard.ts` ir klasificēti ar `isLocalOnlyPath()` — skatiet [Stingro noteikumu #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Komitziņojumos nav `Co-Authored-By` papildinājumu — komitiem ir jāparādās tikai repozitorija īpašnieka Git identitātes vārdā (Stingrais noteikums #16) +- [ ] Testi ir sekmīgi (`npm test`) +- [ ] Lintēšana ir sekmīga (`npm run lint`) +- [ ] Būvēšana ir sekmīga (`npm run build`) +- [ ] Jaunajām publiskajām funkcijām un saskarnēm ir pievienoti TypeScript tipi +- [ ] Nav nekodētu noslēpumu vai atkāpšanās vērtību +- [ ] Publiskie augšupstraumes akreditācijas dati ir iegulti, izmantojot `resolvePublicCred()` (skatiet [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), un nekad nav norādīti kā literāļi +- [ ] Kļūdu atbildes tiek apstrādātas, izmantojot `buildErrorBody()` / `sanitizeErrorMessage()` — atbilžu pamattekstā nav neapstrādātu steka izsekojumu (skatiet [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Čaulas komandas (`exec` / `spawn`) izpildlaika vērtības nodod, izmantojot `env`, nevis virkņu interpolāciju +- [ ] Visas ievades ir validētas, izmantojot Zod shēmas +- [ ] Lietotājam redzamajām izmaiņām sadaļā `changelog.d/{features|fixes|maintenance}/-.md` ir pievienots izmaiņu žurnāla **fragments** (skatiet [`changelog.d/README.md`](./changelog.d/README.md)) — **nerediģējiet** `CHANGELOG.md` tieši; fragmenti tiek apkopoti laidiena izveides laikā un nekad nerada konfliktus starp PR +- [ ] Dokumentācija ir atjaunināta (ja attiecināms) +- [ ] Nav izveidots neviens jauns CodeQL / Secret-Scanning brīdinājums, vai arī katrs brīdinājums ir noraidīts ar tehnisku pamatojumu, atsaucoties uz attiecīgo `docs/security/` dokumentu +- [ ] Maršruti, kas palaiž bērnprocesus (`/api/mcp/`, `/api/cli-tools/runtime/`), failā `src/server/authz/routeGuard.ts` ir klasificēti kā `isLocalOnlyPath()` — skatiet [Stingro noteikumu Nr. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Izpildes ziņojumos nav AI/robotu `Co-authored-by` nobeiguma rindu (Stingrais noteikums Nr. 16) — cilvēki, kuru darbs tiek atkārtoti izmantots, tiek norādīti ar standarta `Co-authored-by: Name ` nobeiguma rindām --- diff --git a/docs/i18n/lv/docs/architecture/QUALITY_GATES.md b/docs/i18n/lv/docs/architecture/QUALITY_GATES.md index 8f2ba6e5e44c..cd450480bde0 100644 --- a/docs/i18n/lv/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/lv/docs/architecture/QUALITY_GATES.md @@ -15,269 +15,271 @@ replikācijas plānu skatiet --- -## Vārtu inventarizācija un izpildes profili - -### Kandidātu pielaide - -CI un kvalitātes vārtu darbplūsmas katra izdod stabilu verdiktu: `Gate / CI` un -`Gate / Quality`. To versijotā pielaides politika uzskaita katru augšupējo uzdevumu -kā obligātu vai konsultatīvu. Piemērojamam obligātajam uzdevumam jābeidzas sekmīgi: -trūkstoši, atcelti, izlaisti, gaidoši un nezināmi rezultāti nevar noteikt PASS. Derīga -klasifikācija “tikai dokumentācija” vai “tikai katalogs” var padarīt koda joslu -nepiemērojamu; melnraksta PR netiek pieņemts kā kandidāts. `hotfix` etiķete neatceļ -pierādījumu prasības. - -Abas darbplūsmas aptver PR un izmaiņu nosūtīšanu uz galvenajiem/laidiena zariem, manuālu -palaišanu un apvienošanas grupu notikumus. Izmaiņu nosūtīšanas, manuālās palaišanas un -apvienošanas grupas gadījumā tiek izpildīta pilnā atlase. Atzarojumi un apvienošanas -grupas izmanto mitinātus izpildītājus uzdevumiem, kuri citādi izvēlētos pašmitinātus -izpildītājus; pirms ieviešanas jāpārbauda, vai mitinātā jauda ir pietiekama. - -Katrā JSON kvītī ir norādīts izrakstītais SHA, darbplūsmas izpilde un mēģinājums. -CLI noraida neatbilstību starp izrakstījuma un notikuma SHA. Darbplūsmas testi sasaista -politikas dalību ar verdikta uzdevuma `needs` sarakstu, lai jauna vai noņemta josla -nevarētu nemanāmi pazust. Kvītis aptver savu darbplūsmu, nevis publicēšanu, izvietošanu -vai esoša konsultatīva skenera iekšējo darbību. Abu pārbaužu nosaukumu aktivizēšana -zaru noteikumos ir atsevišķa administratīva izmaiņa; šo uzdevumu pievienošana pati par -sevi neaizsargā zaru. - -### Statiskās skenēšanas inventarizācija - -Versijotā npm aizstājvārdu inventarizācija un statiskās skenēšanas dalība atrodas +## Vārtu inventārs un izpildes profili + +### Kandidātu pielaišana + +CI un kvalitātes vārtu darbplūsmas katra izvada stabilu verdiktu: `Gate / CI` un +`Gate / Quality`. To versijotā pielaišanas politika uzskaita katru augšupējo darbu +kā obligātu vai konsultatīvu. Piemērojamam obligātajam darbam jābeidzas sekmīgi: +trūkstoši, atcelti, izlaisti, gaidoši un nezināmi rezultāti nevar apstiprināt PASS. +Derīga klasifikācija «tikai dokumentācija» vai «tikai katalogs» var padarīt koda +izpildes zaru nepiemērojamu; melnraksta PR nav pieņemts kandidāts. `hotfix` etiķete +neatceļ pierādījumu prasību. + +Abas darbplūsmas aptver PR un izmaiņu nosūtīšanu uz galvenajiem/laidiena zariem, +manuālu palaišanu un apvienošanas grupas notikumus. Izmaiņu nosūtīšana, manuāla +palaišana un apvienošanas grupa izpilda pilno atlasi. Atzarojumi un apvienošanas +grupas izmanto mitinātus izpildītājus darbiem, kuri citādi atlasītu pašmitinātus +izpildītājus; pirms ieviešanas jāpārbauda pietiekamas mitinātās jaudas pieejamība. + +Katra JSON kvīts identificē izrakstīto SHA, darbplūsmas izpildi un mēģinājumu. +CLI noraida neatbilstību starp izrakstījuma un notikuma SHA. Darbplūsmas testi +saista politikas dalību ar verdikta darba `needs` sarakstu, lai jauns vai noņemts +izpildes zars nevarētu nemanāmi pazust. Kvītis aptver savu darbplūsmu, nevis +publicēšanu, izvietošanu vai esoša konsultatīvā skenera iekšējo darbību. Abu +pārbaužu nosaukumu aktivizēšana zaru noteikumos ir atsevišķa administratīva +izmaiņa; šo darbu pievienošana pati par sevi neaizsargā zaru. + +### Statiskās skenēšanas inventārs + +Versijotais npm aizstājvārdu inventārs un statiskās skenēšanas dalība atrodas `config/quality/gate-manifest.json`. Palaidiet `npm run check:gate-manifest`, lai validētu skriptu nosaukumus un precīzās komandas pret `package.json`; papildinājumi, -noņemšanas un komandu novirzes izraisa kļūmi gan lokālajā āķī, gan izmaiņu klasifikācijas -uzdevumos CI. Aizstājvārds nav darbplūsmas uzdevums, matricas instance vai testa gadījums: -šos skaitus nedrīkst pasniegt kā savstarpēji aizstājamus. +noņemšanas un komandu novirzes izraisa kļūmi gan lokālajā āķī, gan izmaiņu +klasifikācijas darbos CI vidē. Aizstājvārds nav darbplūsmas darbs, matricas instance +vai testa gadījums: šos skaitus nedrīkst pasniegt kā savstarpēji aizstājamus. Izmantojiet `npm run quality:scan -- --list` vai `npm run quality:scan:fast -- --list`, -lai pārskatītu atlasītos aizstājvārdus, tos neizpildot. Izpildītājs izsauc npm -ieejas punktu, tāpēc tiek saglabāta tā izpildvide (tostarp Bun, kur tas ir konfigurēts). -Manifests reģistrē ārpus šiem profiliem esošos aizstājvārdus kā atsevišķi izsaucamus, -un tikai lasāmos skenēšanas profilos uzturēšanas komandas ir aizliegtas. - -Šie profili aptver tikai statisko skenēšanu. Tie neapliecina produkta testus, -pārklājumu, pakotņu veidošanu, ārējās pārbaudes vai kandidāta pilnīgu pieņemšanu laidienam. -Darbplūsmas pielaide izmanto saistīto `config/quality/admission-policy.json` un -`scripts/quality/admission-verdict.mjs`. Laidiena novērotāja profili paliek atsevišķi; -pārbaudiet to piemērojamās pārbaudes un kvītis neatkarīgi. Tālāk esošā teksta -inventarizācija ir uzziņas materiāls, nevis pierādījums, ka vārti patiešām tika izpildīti. - -Skripti atrodas zem `scripts/check/` (politikas vārti) un `scripts/quality/` (sprūdrata dzinis). -CI patiesības avots ir `.github/workflows/ci.yml`. +lai pārbaudītu atlasītos aizstājvārdus, tos neizpildot. Izpildītājs izsauc npm +ieejas punktu, tāpēc tiek saglabāta tā izpildlaika vide (tostarp Bun, kur tas +konfigurēts). Manifests reģistrē aizstājvārdus ārpus šiem profiliem kā atsevišķi +izsaucamus, un tikai lasāmas skenēšanas profilos uzturēšanas komandas ir aizliegtas. + +Šie profili aptver tikai statisko skenēšanu. Tie nesertificē produkta testus, +pārklājumu, pakošanu, ārējās pārbaudes vai kandidāta pilnīgu pieņemšanu laidienam. +Darbplūsmas pielaišana izmanto saistīto `config/quality/admission-policy.json` un +`scripts/quality/admission-verdict.mjs`. Laidiena novērotāja profili paliek +atsevišķi; pārbaudiet tiem piemērojamās pārbaudes un kvītis neatkarīgi. Tālāk +sniegtais tekstuālais inventārs ir uzziņai, nevis pierādījums tam, ka vārti +patiešām tika izpildīti. + +Skripti atrodas zem `scripts/check/` (politikas vārti) un `scripts/quality/` +(pakāpeniskās pastiprināšanas dzinis). CI patiesības avots ir +`.github/workflows/ci.yml`. ### Laidiena PR ātrais ceļš (`quality.yml`) -`.github/workflows/quality.yml` papildina CI galveno/laidiena zaru PR, izmaiņu nosūtīšanai -uz aizsargātiem zariem, manuālai palaišanai un apvienošanas grupām. PR izmanto pēc ceļiem -filtrētas ātrās pārbaudes. Pastāvīgi atspējotā dublētā būvēšana tika noņemta; īstās -būvēšanas/pakotņu veidošanas/palaišanas pārbaudes paliek CI. - -| Uzdevums | Tvērums | Bloķējošs | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `Docs Gates (fast-path)` | Dokumentācijas/koda PR; API dokumentācijas atsauces un visa dokumentācija | Jā | -| `Fast Quality Gates` | Koda PR; statiskās pārbaudes, tipu pārbaude, informācijas paneļa tipu pārbaude, ietekmētie vienību testi | Jā | -| `Forgotten sibling tests` | Koda PR; mainītie moduļi tiek izsekoti līdz statiskajiem patērētājiem un kandidātu radniecīgajiem testiem; barelu un dinamiskā importa ceļi tiek ziņoti kā konsultatīva diagnostika ar norādītiem atļauto izņēmumu saraksta izņēmumiem | **Konsultatīvs** | -| `Vitest (fast-path)` | Koda PR; ātrais vitest komplekts | Jā | -| `Unit Tests fast-path` | Koda PR; 4 daļās sadalīts vienību testu komplekts | Jā | -| `No new ESLint warnings` | Koda PR; lintēšanas aizsargs, kas ņem vērā apspiešanas | Jā, tostarp atzarojumiem | -| `Merge integrity (changelog + generated skills)` | PR, kas nav melnraksti; izmaiņu žurnāla un ģenerēto prasmju sinhronizācija | Jā, tostarp atzarojumiem | - -#### Ziņojums par aizmirstajiem radniecīgajiem testiem - -`npm run check:forgotten-sibling-tests` atkārtoti izmanto importa atrisinātāju, kas ir -testu ietekmes kartes pamatā. Katram mainītajam produkcijas modulim tas ziņo deterministiskas -`mainītais modulis/simbols -> statiskais patērētājs -> kandidāta radniecīgais tests` ķēdes, -ja kandidāta testa nav izmaiņu pieprasījuma atšķirībās. Markdown kopsavilkums un JSON -rezultāts tiek saglabāti kā `forgotten-sibling-tests` darbplūsmas artefakts kalibrēšanai -pirms jebkādas bloķējošas ieviešanas. - -Barrel atkārtotie eksporti un dinamiskie importi kalpo tikai izšķirtspējas diagnostikai; tie nekad nerada -bloķējošu atradni. Pārskatītie izņēmumi atrodas failā +`.github/workflows/quality.yml` papildina CI galveno/laidiena zaru PR, aizsargāto +zaru izmaiņu nosūtīšanā, manuālā palaišanā un apvienošanas grupās. PR izmanto pēc +ceļiem filtrētas ātrās pārbaudes. Pastāvīgi atspējotais dublētais būvējums tika +noņemts; īstās būvēšanas/pakošanas/palaišanas pārbaudes paliek CI vidē. + +| Darbs | Tvērums | Bloķējošs | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `Docs Gates (fast-path)` | Dokumentācijas/koda PR; API dokumentācijas atsauces un visa dokumentācija | Jā | +| `Fast Quality Gates` | Koda PR; statiskās pārbaudes, tipu pārbaude, informācijas paneļa tipu pārbaude, ietekmētie vienībtesti | Jā | +| `Forgotten sibling tests` | Koda PR; mainītie moduļi, kas izsekoti līdz statiskajiem patērētājiem un saistīto testu kandidātiem; mucas un dinamiskās importēšanas ceļi tiek ziņoti kā konsultatīva diagnostika ar atsauktiem atļauto izņēmumu saraksta izņēmumiem | **Konsultatīvs** | +| `Vitest (fast-path)` | Koda PR; ātrais vitest komplekts | Jā | +| `Unit Tests fast-path` | Koda PR; 4 daļās sadalīts vienībtestu komplekts | Jā | +| `No new ESLint warnings` | Koda PR; lintēšanas aizsargs, kas ņem vērā slāpēšanas | Jā, tostarp atzarojumiem | +| `Merge integrity (changelog + generated skills)` | PR, kas nav melnraksti; izmaiņu žurnāla un ģenerēto prasmju sinhronizācija | Jā, tostarp atzarojumiem | + +#### Aizmirsto saistīto testu pārskats + +`npm run check:forgotten-sibling-tests` atkārtoti izmanto importēšanas atrisinātāju, +kas ir testu ietekmes kartes pamatā. Katram mainītajam produkcijas modulim tas +ziņo deterministiskas `mainītais modulis/simbols -> statiskais patērētājs -> saistītā testa kandidāts` +ķēdes, ja kandidāta testa nav izmaiņu pieprasījuma atšķirībās. Markdown kopsavilkums +un JSON rezultāts tiek saglabāti kā `forgotten-sibling-tests` darbplūsmas artefakts +kalibrēšanai pirms jebkādas bloķējošas ieviešanas. + +Barrel tipa atkārtotie eksporti un dinamiskie importi ir tikai izšķirtspējas diagnostika; tie nekad nerada +bloķējošu konstatējumu. Pārskatītie izņēmumi atrodas failā `config/quality/forgotten-sibling-allowlist.json`. Katrā ierakstā jānorāda patērētājs un kandidāta -tests, jāsniedz konkrēts pamatojums un jāpievieno saite uz GitHub problēmu vai izmaiņu pieprasījumu. Nederīgi ieraksti tiek -noraidīti. Izņēmumi nevar ignorēt dzēstu kandidāta testu vai izmaiņu kopu, kas pievieno `.skip`/`.todo`; -apgalvojumu vājināšana un cita maskēšana joprojām ir neatkarīgi bloķējošā -`check:test-masking` vārtejas pārziņā. +tests, jāsniedz konkrēts pamatojums un jāpievieno saite uz GitHub problēmu vai izmaiņu pieprasījumu. Nederīgi ieraksti +drošības nolūkā tiek noraidīti. Izņēmumi nevar ignorēt dzēstu kandidāta testu vai izmaiņas, kas pievieno `.skip`/`.todo`; +apgalvojumu vājināšanu un citu maskēšanu joprojām pārvalda neatkarīgi bloķējošā +`check:test-masking` pārbaude. ### Uzdevums: `lint` Tiek izpildīts katram izmaiņu pieprasījumam uz `main`. Kļūmes gadījumā bloķē sapludināšanu. -| Skripts (`npm run ...`) | Pārbauda | Bloķējošs | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Node.js versija ir atbalstītajā diapazonā | Jā | -| `check:cycles` | Cikliskie importi — visi `src/` + `open-sse/` moduļi | Jā | -| `check:route-validation:t06` | Zod shēmas ir pieejamas visiem maršrutiem (6. līmeņa politika) | Jā | -| `check:any-budget:t11` | `@ts-expect-error // any` skaits nepārsniedz budžetu (11. līmeņa catraca) | Jā | -| `check:provider-consistency` | Katram pakalpojumu sniedzējam failā `providers.ts` ir atbilstošs ieraksts failā `providerRegistry.ts` (un otrādi, atļauto ierakstu saraksta ietvaros) | Jā | -| `check:model-lifecycle` | Trīs manuāli uzturētās maršrutēšanas tabulas saglabā savstarpēju atbilstību ar repozitorijā iekļauto dzīves cikla momentuzņēmumu (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nepiešķir vērtējumu nevienam izņemtam ID, kuru `REGISTRY` var maršrutēt; katrs `BUILT_IN_ALIASES` mērķis ir iekļauts `REGISTRY` un nav atrodams izņemto ID momentuzņēmumā; katrs izņemtais ID, kas joprojām atrodas `REGISTRY`, tiek pāradresēts vai ir norādīts sarakstā `allowedRetiredInCatalog`; un neviens `DEFAULT_DEGRADATION_MAP` avots vai mērķis šajā momentuzņēmumā nav atzīmēts kā izņemts. Tas nepierāda, ka modeli pašlaik nodrošina aktīvs augšupstraumes pakalpojums. Bezsaistes pārbaude — salīdzina ar `config/quality/model-lifecycle.json`, kas manuāli tiek atjaunināts ar `npm run quality:refresh-model-lifecycle` (nepieciešams tīkls; nav integrēts CI). `allowedRetiredInCatalog` ir pakāpeniskas samazināšanas sprūdrata mehānisms: pievienojiet ierakstu tikai kopā ar izsekošanas pieteikumu. | Jā | -| `check:fetch-targets` | Katrs `fetch("/api/...")` klienta puses direktorijā `src/` norāda uz reālu `route.ts` | Jā | -| `check:deps` | Visas ar `npm install` instalējamās atkarības visos repozitorija failos `package.json` ir iekļautas failā `dependency-allowlist.json`; jaunas atkarības bez fiksētas versijas vai ar nosaukumiem, kas var liecināt par slopsquatting uzbrukumu, tiek atzīmētas | Jā | -| `audit:deps` | `npm audit` (saknes projekts + Electron) — nav augstas/kritiskas bīstamības ieteikumu (daļēji pārklājas ar OSV `check:vuln-ratchet`; skatiet racionalizācijas neizdarīto darbu sarakstu) | Jā | -| `check:lockfile` | `package-lock.json` integritāte — HTTPS reģistrs, integritātes jaucējvērtības, nav resursdatoru pārrakstīšanas iestatījumu | Jā | -| `check:licenses` | SPDX licenču atļauto saraksts produkcijas atkarībām | Jā | -| `check:tracked-artifacts` | Nav būvējuma artefaktu / repozitorijā iekļautu `node_modules` simbolisko saišu (tiek izpildīts arī husky pirmskomita pārbaudē; pirms nosūtīšanas pārbaude apzināti ir viegla — #6716) | Jā | -| `check:ai-attribution` | PR komitos, virsrakstā vai aprakstā nav AI/bota `Co-Authored-By` noslēguma rindas vai AI ģenerēšanas kājenes — stingrais noteikums Nr. 16 (`quality.yml` ātro pārbaužu ciklā PR→`release/**` — nolasa notikuma datu saturu, ārpus PR neveic nekādas darbības — un tikai PR paredzētā `ci.yml` lintēšanas solī PR→`main`; kā arī husky `commit-msg` āķī; cilvēki līdzautori ir atļauti; #14436) | -| `check:vitest-exclusions` | Katram Vitest izņēmumam ir norādīts izsekošanas pieteikums, un tas ir iekļauts failā `config/quality/vitest-exclusions.json` (#13204) | Jā | -| `check:file-size` | Neviens pirmkoda fails nepārsniedz attiecīgajam paplašinājumam noteikto ierobežojumu (sprūdrata princips: lielie faili ir fiksēti sarakstā `frozen`) | Jā | -| `check:error-helper` | Kļūdu atbildēs izpildītājos/apdarinātājos tiek izmantots `buildErrorBody()` / `sanitizeErrorMessage()` (stingrais noteikums Nr. 12) | Jā | -| `check:migration-numbering` | Migrāciju SQL faili ir secīgi numurēti, bez iztrūkumiem vai dublikātiem | Jā | -| `check:public-creds` | Ārpus `publicCreds.ts` nav literālu OAuth `client_id`/`client_secret` vai Firebase tīmekļa atslēgu (stingrais noteikums #11) | Jā | -| `check:db-rules` | Ārpus `src/lib/db/` moduļiem nav neapstrādāta SQL; nav apkopojošo importu no `localDb.ts` (stingrie noteikumi #2/#5) | Jā | -| `check:known-symbols` | Pakalpojumu sniedzēju izpildītāji, maršrutēšanas stratēģijas un tulkotāji, kas reģistrēti to nosūtīšanas tabulās, atbilst diskā esošajiem failiem — nav bāreņu vai nedeklarētu simbolu | Jā | -| `check:route-guard-membership` | Katrs maršruts, kas palaiž bērnprocesu, ir klasificēts ar `isLocalOnlyPath()` (stingrie noteikumi #15/#17) | Jā | -| `check:test-discovery` | Katru repozitorijā esošo `*.test.ts` / `*.spec.ts` failu apkopo vismaz viens testu izpildītājs (sprūdratmehānisms: bāreņu saraksts failā `test-discovery-baseline.json` var tikai samazināties) | Jā | -| `check:agent-skills-sync` | Ģenerētie agentu prasmju artefakti atbilst to avota katalogam (nav noviržu) | -| `check:provider-asset-provenance` | Pakalpojumu sniedzēju logotipiem/resursiem ir reģistrēts izcelsmes ieraksts | -| `lint:json` | JSON konfigurācijas faili tiek parsēti un atbilst repozitorija lintēšanas noteikumiem | -| `typecheck:core` | TypeScript kompilācija bez kļūdām (tikai konsultatīvi brīdinājumi) | Jā | -| `typecheck:noimplicit:core` | Stingrs `noImplicitAny` — uz nākotni vērsta pārbaude; daudzām jau esošām izsaukuma vietām joprojām nepieciešamas anotācijas | **Konsultatīvs** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, kas attiecas tikai uz `src/app/(dashboard)/**` (#7033) — `typecheck:core` rūpīgi atlasītais 27 failu atļauju saraksts neietver nevienu informācijas paneļa TSX failu, un arī `next build` tiem nekad neveic tipu pārbaudi (`next.config.mjs` iestata `ignoreBuildErrors: true`), tādēļ nesaistītu identifikatoru regresijas tur (#6625/#6909) CI nebija pamanāmas. Atšķirības tiek noteiktas pret fiksētu katra faila un TS koda skaita bāzes līniju (`config/quality/dashboard-typecheck-baseline.json`, tāds pats novecojušu ierakstu kontroles modelis kā `check:known-symbols`) — pārbaude neizdodas tikai tad, ja ir JAUNAS kļūdas, kas pārsniedz bāzes līnijas skaitu; kad jau esoša kļūda ir novērsta, pazeminiet bāzes līmeni ar `--update`. | Jā | - -### Uzdevums: `quality-gate` - -Tiek izpildīts pēc `test-coverage`. Kļūmes gadījumā bloķē sapludināšanu. - -| Skripts | Pārbauda | Bloķējošs | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `quality:collect` | Izveido `quality-metrics.json` (ESLint brīdinājumu skaits, pārklājums no apvienotā segmentu pārskata) | Jā (pirms ratchet pārbaudes) | -| `quality:ratchet` | Neviena `quality-baseline.json` metrika nav pasliktinājusies (ESLint brīdinājumi ≤ bāzes līmenis; pārklājums ≥ bāzes līmenis) | Jā | -| `check:duplication` | Koda dublēšanās (jscpd@4) nepārsniedz `quality-baseline.json` noteikto bāzes līmeni | Jā | -| `check:complexity` | Faila līmeņa ciklomātiskā sarežģītība nepārsniedz ierobežojumu (pamata ESLint `complexity` + `max-lines-per-function`) | Jā | -| `check:cognitive-complexity` | Kognitīvās sarežģītības ratchet pārbaude (`eslint-plugin-sonarjs`) — atsevišķa ESLint izpilde; CI abas pārbaudes apvieno vienā `check:complexity-ratchets` solī | Jā | -| `check:dead-code` | Neizmantoto eksportu/failu ratchet pārbaude (knip) neuzrāda pasliktinājumu salīdzinājumā ar bāzes līmeni | Jā | -| `check:compression-budget` | Saspiešanas etalontesta budžets — minimālais katram dzinim noteiktais marķieru ietaupījums nedrīkst samazināties | Jā | -| `check:type-coverage` | Tipizācijas procentuālās daļas ratchet pārbaude (`type-coverage`) neuzrāda pasliktinājumu; lielā mērā aizstāj `typecheck:noimplicit:core` | Jā | -| `check:codeql-ratchet` | Atvērto CodeQL brīdinājumu skaits nepalielinās (nolasa, izmantojot `gh api`; korekti izlaiž bez pilnvaras) — atjaunošanas biežumu un manuālo palaišanu skatiet tālāk sadaļā "CodeQL ratchet" | Jā | +| Skripts (`npm run ...`) | Pārbauda | Bloķējošs | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Node.js versija ir atbalstītajā diapazonā | Jā | +| `check:cycles` | Cikliskos importus visā `src/` + `open-sse/` (balstīts uz AST, atrisināti tsconfig `paths`). Palaižot atsevišķi, pārbaude ir informatīva un uzskaita ciklus. `check:cycles:ratchet` (ko izpilda CI) bloķē, ja skaits pārsniedz `quality-baseline.json` definēto `metrics.cycles` robežvērtību — pašlaik 14, `direction: down`, tādēļ tā var tikai samazināties (#15159 G-01/G-02) | Jā (pakāpeniskā robežkontrole) | +| `check:route-validation:t06` | Zod shēmu esamību visos maršrutos (6. līmeņa politika) | Jā | +| `check:any-budget:t11` | `@ts-expect-error // any` skaits nepārsniedz budžetu (11. līmeņa sprūdrata kontrole) | Jā | +| `check:provider-consistency` | Katram pakalpojumu sniedzējam failā `providers.ts` ir atbilstošs ieraksts failā `providerRegistry.ts` (un otrādi — atļauto vienumu saraksta ietvaros) | Jā | +| `check:model-lifecycle` | Trīs manuāli uzturētās maršrutēšanas tabulas saglabā atbilstību repozitorijā iekļautajam dzīves cikla momentuzņēmumam (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nepiešķir vērtējumu nevienam ekspluatāciju beigušam id, kuru `REGISTRY` var maršrutēt; katrs `BUILT_IN_ALIASES` mērķis ir ietverts `REGISTRY` un nav atrodams ekspluatāciju beigušo id momentuzņēmumā; katrs ekspluatāciju beigušais id, kas joprojām atrodas `REGISTRY`, tiek pārsūtīts vai norādīts `allowedRetiredInCatalog`; un neviens `DEFAULT_DEGRADATION_MAP` avots vai mērķis šajā momentuzņēmumā nav atzīmēts kā ekspluatāciju beidzis. Tas nepierāda, ka modeli pašlaik apkalpo aktīvs augšupstraumes pakalpojums. Bezsaistes pārbaude — salīdzina ar `config/quality/model-lifecycle.json`, kas tiek manuāli atsvaidzināts ar `npm run quality:refresh-model-lifecycle` (nepieciešams tīkls; nav integrēts CI). `allowedRetiredInCatalog` ir pakāpeniskas samazināšanas sprūdrata mehānisms: pievienojiet ierakstu tikai kopā ar izsekošanas problēmu. | Jā | +| `check:fetch-targets` | Katrs `fetch("/api/...")` klienta puses direktorijā `src/` norāda uz reālu `route.ts` | Jā | +| `check:deps` | Visas ar `npm install` instalējamās atkarības visos repozitorija `package.json` failos ir iekļautas `dependency-allowlist.json`; jaunās atkarības bez fiksētas versijas vai ar iespējamu slopsquatting risku tiek atzīmētas | Jā | +| `audit:deps` | `npm audit` (saknes projekts + electron) — nav augsta/kritiska līmeņa drošības paziņojumu (daļēji pārklājas ar osv `check:vuln-ratchet`; skatiet Racionalizācijas neizdarīto darbu sarakstu) | Jā | +| `check:lockfile` | `package-lock.json` integritāte — https reģistrs, integritātes kontrolsummas, bez resursdatora pārrakstīšanas | Jā | +| `check:licenses` | SPDX licenču atļauto vērtību saraksts produkcijas atkarībām | Jā | +| `check:tracked-artifacts` | Nav būvējuma artefaktu / repozitorijā iekļautu `node_modules` simbolisko saišu (tiek palaista arī husky pirmsiesniegšanas pārbaudē; pirms nosūtīšanas pārbaude ir apzināti vienkārša — #6716) | Jā | +| `check:ai-attribution` | PR komitos, virsrakstā vai pamattekstā nav AI/bota `Co-Authored-By` noslēguma rindas vai AI ģenerēšanas kājenes — stingrais noteikums Nr. 16 (`quality.yml` ātro pārbaužu ciklā PR→`release/**` gadījumā — nolasa notikuma derīgo slodzi, bet ārpus PR neveic nekādas darbības — un tikai PR paredzētā `ci.yml` linting pārbaudes solī PR→`main` gadījumā; kā arī husky `commit-msg` āķī; cilvēki kā līdzautori ir atļauti; #14436) | +| `check:vitest-exclusions` | Katram Vitest izņēmumam ir norādīts izsekošanas pieteikums, un tas ir iekļauts `config/quality/vitest-exclusions.json` (#13204) | Jā | +| `check:file-size` | Neviens avota fails nepārsniedz attiecīgajam paplašinājumam noteikto ierobežojumu (pakāpeniskais ierobežojums: iesaldētie lielie faili ir `frozen` sarakstā) | Jā | +| `check:error-helper` | Kļūdu atbildēs izpildītājos/apstrādātājos tiek izmantots `buildErrorBody()` / `sanitizeErrorMessage()` (stingrais noteikums Nr. 12) | Jā | +| `check:migration-numbering` | Migrācijas SQL faili ir secīgi numurēti, bez iztrūkumiem vai dublikātiem | Jā | +| `check:public-creds` | Ārpus `publicCreds.ts` nav literālu OAuth `client_id`/`client_secret` vai Firebase tīmekļa atslēgu (stingrais noteikums Nr. 11) | Jā | +| `check:db-rules` | Ārpus `src/lib/db/` moduļiem nav neapstrādāta SQL; no `localDb.ts` netiek veikti apvienotie importi (stingrie noteikumi Nr. 2/Nr. 5) | Jā | +| `check:known-symbols` | Pakalpojumu sniedzēju izpildītāji, maršrutēšanas stratēģijas un tulkotāji, kas reģistrēti to dispečertabulās, atbilst diskā esošajiem failiem — nav bāreņsimbolu vai nedeklarētu simbolu | Jā | +| `check:route-guard-membership` | Katrs maršruts, kas palaiž bērnprocesu, ir klasificēts ar `isLocalOnlyPath()` (stingrie noteikumi Nr. 15/Nr. 17) | Jā | +| `check:test-discovery` | Katru repozitorijā esošo `*.test.ts` / `*.spec.ts` failu apkopo vismaz viens testu izpildītājs (sprūdrata princips: bāreņfailu saraksts failā `test-discovery-baseline.json` var tikai samazināties) | Jā | +| `check:agent-skills-sync` | Ģenerētie agentu prasmju artefakti atbilst to avota katalogam (nav noviržu) | +| `check:provider-asset-provenance` | Pakalpojumu sniedzēju logotipiem/resursiem ir reģistrēts izcelsmes ieraksts | +| `lint:json` | JSON konfigurācijas faili tiek parsēti un atbilst repozitorija lintēšanas noteikumiem | +| `typecheck:core` | TypeScript kompilācija bez kļūdām (tikai konsultatīvi brīdinājumi) | Jā | +| `typecheck:noimplicit:core` | Stingrais `noImplicitAny` — paredzēts turpmākai ieviešanai; daudzām jau esošām izsaukuma vietām joprojām ir nepieciešamas anotācijas | **Konsultatīvs** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, kas attiecas tikai uz `src/app/(dashboard)/**` (#7033) — `typecheck:core` atlasītais 27 failu atļauto failu saraksts neietver nevienu informācijas paneļa TSX failu, un `next build` arī nekad neveic tā tipu pārbaudi (`next.config.mjs` iestata `ignoreBuildErrors: true`), tādēļ nesaistītu identifikatoru regresijas šajos failos (#6625/#6909) CI sistēmai nebija pamanāmas. Atšķirības tiek salīdzinātas ar fiksētu bāzes līmeni, kurā norādīts skaits katram failam un katram TS kodam (`config/quality/dashboard-typecheck-baseline.json`, tāds pats novecojušu ierakstu kontroles modelis kā `check:known-symbols`) — pārbaude neizdodas tikai tad, ja rodas JAUNAS kļūdas, kas pārsniedz bāzes līmeņa skaitu; samaziniet bāzes līmeni ar `--update`, kad jau esoša kļūda ir novērsta. | Jā | + +### Darbs: `quality-gate` + +Tiek izpildīts pēc `test-coverage`. Kļūmes gadījumā bloķē apvienošanu. + +| Skripts | Validē | Bloķējošs | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | +| `quality:collect` | Izveido `quality-metrics.json` (ESLint brīdinājumu skaits, pārklājums no apvienotā segmentu pārskata) | Jā (pirms ratchet) | +| `quality:ratchet` | Neviena metrika failā `quality-baseline.json` nav pasliktinājusies (ESLint brīdinājumi ≤ bāzes līmenis; pārklājums ≥ bāzes līmenis) | Jā | +| `check:duplication` | Koda dublēšanās (jscpd@4) nepārsniedz bāzes līmeni failā `quality-baseline.json` | Jā | +| `check:complexity` | Faila līmeņa ciklomātiskā sarežģītība nepārsniedz ierobežojumu (pamata ESLint `complexity` + `max-lines-per-function`) | Jā | +| `check:cognitive-complexity` | Kognitīvās sarežģītības ratchet (`eslint-plugin-sonarjs`) — atsevišķa ESLint pārbaude; CI izpilda abas apvienoti vienā `check:complexity-ratchets` solī | Jā | +| `check:dead-code` | Neizmantoto eksportu/failu ratchet (knip) rezultāts nav pasliktinājies salīdzinājumā ar bāzes līmeni | Jā | +| `check:compression-budget` | Saspiešanas etalonmērījuma budžets — katram dzinim noteiktais minimālais marķieru ietaupījums nedrīkst samazināties | Jā | +| `check:type-coverage` | Tipizētā koda procentuālās daļas ratchet (`type-coverage`) rezultāts nav pasliktinājies; lielā mērā aizstāj `typecheck:noimplicit:core` | Jā | +| `check:codeql-ratchet` | Atvērto CodeQL brīdinājumu skaits nav palielinājies (nolasa, izmantojot `gh api`; bez pilnvaras marķiera pārbaude tiek korekti izlaista) — atsvaidzināšanas biežumu un manuālo palaišanu skatiet tālāk sadaļā "CodeQL ratchet" | Jā | ### Uzdevums: `quality-extended` -Viss uzdevums ir konsultatīvs (`continue-on-error: true`). Uz npm balstītās ratchet pārbaudes -tiek izpildītas pilnībā; ārējie skeneri tiek instalēti, izmantojot `gh release download`, un paši izlaiž pārbaudi (exit 0), -ja binārais fails joprojām nav pieejams. +Viss uzdevums ir konsultatīvs (`continue-on-error: true`). Uz npm balstītās ratchet pārbaudes tiek izpildītas +pilnvērtīgi; ārējie skeneri tiek instalēti ar `gh release download` un paši izlaiž pārbaudi (izejas kods 0), +ja binārā faila joprojām nav. -| Skripts | Pārbauda | Bloķējošs | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -| `check:circular-deps` | Nav ciklisku atkarību (dpdm) | **Konsultatīvs** | -| `check:bundle-size` | Pakotnes izmērs nepārsniedz ierobežojumu | **Konsultatīvs** | -| `check:secrets` | Noslēpumu skenēšana (gitleaks) — tiek izlaista, ja binārā faila nav | **Konsultatīvs** | -| `check:vuln-ratchet` | Atkarību ievainojamības (osv-scanner) nepasliktinās — tiek izlaista, ja binārā faila nav | **Konsultatīvs** | -| `check:workflows` | Darbplūsmu lint pārbaude (actionlint + zizmor); trūkstoši/bojāti skeneri, nederīgi pārskati vai trūkstošs ratchet bāzes līmenis izraisa INCOMPLETE kļūmi. Derīgi atradumi tiek apstrādāti atbilstoši izvēlētajai stingrajai/konsultatīvajai/ratchet politikai | Izpilde ir obligāta; zizmor ratchet pārbaude CI vidē ir bloķējoša | -| `check:openapi-breaking` | Publiskā API līguma (`openapi.yaml`) nesaderīgas izmaiņas salīdzinājumā ar bāzes zaru (oasdiff) — izvada `openapiBreaking=N`; tiek izlaista, ja oasdiff nav pieejams vai bāzes specifikāciju nevar atrisināt | **Konsultatīvs** | +| Skripts | Validē | Bloķējošs | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| `check:circular-deps` | Nav ciklisku atkarību (dpdm) | **Konsultatīvs** | +| `check:bundle-size` | Pakotnes izmērs nepārsniedz ierobežojumu | **Konsultatīvs** | +| `check:secrets` | Noslēpumu skenēšana (gitleaks) — tiek izlaista, ja binārā faila nav | **Konsultatīvs** | +| `check:vuln-ratchet` | Atkarību ievainojamību (osv-scanner) stāvoklis nav pasliktinājies — tiek izlaista, ja binārā faila nav | **Konsultatīvs** | +| `check:workflows` | Darbplūsmu lint pārbaude (actionlint + zizmor); trūkstoši vai bojāti skeneri, nederīgi pārskati vai trūkstošs ratchet bāzes līmenis izraisa INCOMPLETE kļūmi. Derīgi atradumi tiek apstrādāti saskaņā ar izvēlēto stingro/konsultatīvo/ratchet politiku | Izpilde obligāta; zizmor ratchet ir bloķējošs CI vidē | +| `check:openapi-breaking` | Publiskā API līguma (`openapi.yaml`) nesaderīgas izmaiņas salīdzinājumā ar bāzes zaru (oasdiff) — izvada `openapiBreaking=N`; tiek izlaista, ja oasdiff nav pieejams vai bāzes specifikāciju nevar atrisināt | **Konsultatīvs** | ### Uzdevums: `docs-sync-strict` Tiek izpildīts katram PR uz `main`. Kļūmes gadījumā bloķē sapludināšanu. -| Skripts | Pārbauda | Bloķējošs | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- | -| `check:docs-all` | Meta-pārbaude, kas secīgi izpilda 6 tālāk norādītās apakšpārbaudes | Jā | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versiju konsekvenci | Jā | -| ↳ `check:docs-counts` | Tekstā norādītie skaitļi (nodrošinātāju skaits, migrāciju skaits utt.) atrodas faktisko skaitļu pakāpeniski paaugstināmā sliekšņa diapazonā | Jā | -| ↳ `check:env-doc-sync` | Katrs `.env.example` vides mainīgais ir dokumentēts dokumentācijas tabulā un otrādi | Jā | -| ↳ `check:deprecated-versions` | Dokumentācijā nav novecojušu versiju virkņu | Jā | -| ↳ `check:doc-links` | Dokumentācijas iekšējās markdown saites norāda uz reāliem failiem (`[text]`/`(path)` formā) | Jā | -| ↳ `check:fabricated-docs` | Dokumentācijā minētie maršruti, vides mainīgie, CLI komandas, āķu nosaukumi un failu ceļi pastāv kodu bāzē. Stingra pārbaude ar `--strict`; bez karodziņa kļūme nav bloķējoša. | Jā (CI vidē ar `--strict`) | -| `check:cli-i18n` | CLI komandu virknes ir ietvertas visos i18n lokalizāciju failos | Jā | -| `check:openapi-coverage` | OpenAPI specifikācija aptver vismaz pakāpeniski paaugstināmu reālo maršrutu minimālo slieksni | Jā | -| `check:openapi-security-tiers` | Drošības līmeņu anotācijas failā `openapi.yaml` atbilst klasifikācijām failā `routeGuard.ts` | **Konsultatīvs** | -| `check:openapi-routes` | Katrs ceļš failā `openapi.yaml` atbilst reālam `route.ts` failam (pret halucinācijām) | Jā | -| `check:docs-symbols` | Katra `/api/...` atsauce failos `docs/**/*.md` atbilst reālam `route.ts` failam (pret halucinācijām) | Jā | -| `i18n translation drift` | Neiztulkotas atslēgas i18n lokalizāciju failos — tikai brīdinājums | **Konsultatīvs** | +| Skripts | Pārbauda | Bloķējošs | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | Meta-vārteja, kas secīgi izpilda 6 tālāk norādītās apakšvārtejas | Jā | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versiju konsekvenci | Jā | +| ↳ `check:docs-counts` | Vai tekstā norādītie skaitļi (nodrošinātāju skaits, migrāciju skaits utt.) ir faktisko skaitļu pakāpeniski noteiktajā diapazonā | Jā | +| ↳ `check:env-doc-sync` | Vai katrs vides mainīgais failā `.env.example` ir dokumentēts dokumentācijas tabulā un otrādi | Jā | +| ↳ `check:deprecated-versions` | Vai dokumentācijā nav novecojušu versiju virkņu | Jā | +| ↳ `check:doc-links` | Vai iekšējās markdown saites dokumentācijā norāda uz reāliem failiem (`[text]`/`(path)` formā) | Jā | +| ↳ `check:fabricated-docs` | Vai dokumentācijā minētie maršruti, vides mainīgie, CLI komandas, āķu nosaukumi un failu ceļi pastāv kodu bāzē. Stingrā vārteja ar `--strict`; bez karoga kļūme ir nebloķējoša. | Jā (CI vidē ar `--strict`) | +| `check:cli-i18n` | Vai CLI komandu virknes ir visos i18n lokalizācijas failos | Jā | +| `check:openapi-coverage` | Vai OpenAPI specifikācija aptver vismaz pakāpeniski noteikto reālo maršrutu minimumu | Jā | +| `check:openapi-security-tiers` | Vai drošības līmeņu anotācijas failā `openapi.yaml` atbilst `routeGuard.ts` klasifikācijām | **Ieteikuma rakstura** | +| `check:openapi-routes` | Vai katrs ceļš failā `openapi.yaml` atbilst reālam `route.ts` (pret halucinācijām) | Jā | +| `check:docs-symbols` | Vai katra `/api/...` atsauce failos `docs/**/*.md` atbilst reālam `route.ts` (pret halucinācijām) | Jā | +| `i18n translation drift` | Netulkotas atslēgas i18n lokalizācijas failos — tikai brīdinājums | **Ieteikuma rakstura** | ### Darbs: `i18n-ui-coverage` -| Skripts | Pārbauda | Bloķējošs | -| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (iekļauts) | UI i18n atslēgu pārklājums ir ≥ 65% | Jā | -| `check-ui-value-drift` (iekļauts) | Pārrakstīta angļu valodas **vērtība** neatstāj novecojušu tulkojumu | Jā | -| `check-new-key-coverage` (iekļauts) | **Jauna** angļu valodas atslēga ir iztulkota katrā lokalizācijā — `__MISSING__:` marķieris tiek noraidīts | Jā | -| `check-translation-ratio` | Reālo tulkojumu attiecība katrā lokalizācijā (ārpus atļauto vienumu saraksta esošas lapas, kas ir identiskas angļu valodai, satur vietturus vai nav iztulkotas) nedrīkst pārsniegt `config/quality/i18n-translation-baseline.json` + pielaidi | **Konsultatīvs** | +| Skripts | Pārbauda | Bloķējošs | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `check-ui-keys-coverage` (iekļauts) | Vai UI i18n atslēgu pārklājums ir ≥ 65% | Jā | +| `check-ui-value-drift` (iekļauts) | Vai pārrakstīta angļu valodas **vērtība** neatstāj novecojušu tulkojumu | Jā | +| `check-new-key-coverage` (iekļauts) | Vai **jauna** angļu valodas atslēga ir iztulkota katrā lokalizācijā — marķieris `__MISSING__:` tiek noraidīts | Jā | +| `check-translation-ratio` | Reālo tulkojumu attiecība katrā lokalizācijā (angļu valodai identiskas / vietturu / trūkstošas lapas ārpus atļauto elementu saraksta) nedrīkst pārsniegt `config/quality/i18n-translation-baseline.json` + pielaidi | **Ieteikuma rakstura** | -Nepieciešams `fetch-depth: 0` — vērtību novirzes pārbaude salīdzina `en.json` ar apvienošanas bāzi. +Nepieciešams `fetch-depth: 0` — vērtību novirzes vārteja salīdzina `en.json` ar sapludināšanas bāzi. -#### `check-ui-value-drift` — novecojušu tulkojumu pārbaude +#### `check-ui-value-drift` — novecojušu tulkojumu vārteja -Atklāj to vienu i18n regresijas veidu, kuru citas pārbaudes strukturāli nespēj konstatēt: angļu valodas vērtība -tiek pārrakstīta, bet no _iepriekšējā_ angļu teksta atvasinātie tulkojumi paliek nemainīti, tādēļ -lietotāji, kuri nelieto angļu valodu, turpina lasīt pārliecinoši formulētu, bet nu jau nepareizu tekstu. +Atklāj vienu i18n regresijas veidu, ko pārējās vārtejas strukturāli nespēj pamanīt: angļu valodas vērtība +tiek pārrakstīta, bet no _iepriekšējā_ angļu valodas teksta atvasinātie tulkojumi paliek, tāpēc +lietotāji, kuri nelieto angļu valodu, turpina lasīt pārliecinoši formulētu, taču tagad nepareizu tekstu. -Tas patiešām nonāca laidienā. `oauthModal.googleOAuthWarning` tika pārrakstīts, kad tika ieviests Antigravity -pieteikšanās palīgs (#5203); **39 no 43 lokalizācijām** saglabāja tekstu, kas operatoriem norādīja „nokopēt -pilno URL un ielīmēt to zemāk” — šim nodrošinātājam šādu plūsmu nav iespējams pabeigt. Tas +Tas tiešām nonāca laidienā. `oauthModal.googleOAuthWarning` tika pārrakstīts, kad tika ieviests Antigravity +pieteikšanās palīgs (#5203); **39 no 43 lokalizācijām** saglabāja tekstu, kas operatoriem lika „kopēt +pilno URL un ielīmēt to tālāk” — šī plūsma attiecīgajam nodrošinātājam nav izpildāma. Tas palika nepamanīts līdz #8463, jo: -- `sync-ui-keys` aizpilda tikai **neesošas** atslēgas, bet nekad neatjaunina **novecojušas**; -- `check-ui-keys-coverage` uzskaita atslēgas _esamību_, tādēļ novecojis tulkojums tiek uzskatīts par pārklātu; +- `sync-ui-keys` aizpilda tikai **trūkstošas** atslēgas, bet nekad neatjaunina **novecojušas**; +- `check-ui-keys-coverage` uzskaita atslēgas _esamību_, tāpēc novecojis tulkojums tiek uzskatīts par segtu; - `check-translation-drift` izseko `docs/i18n//**.md` dokumentācijas spoguļkopijas — - tas nekad nelasa `src/i18n/messages/*.json`. Darbā `docs-sync-strict` šī pārbaude ir bloķējoša kopš - 2026-09 atkārtotās sinhronizācijas: rediģējiet pamatdokumentu → `npm run i18n:run -- --files=` (sadaļas līmenī, ātri). + tas nekad nelasa `src/i18n/messages/*.json`. Bloķējošs darbā `docs-sync-strict` kopš + 2026-09 atkārtotās sinhronizācijas: rediģējiet pamatdokumentu → `npm run i18n:run -- --files=` (sadaļas līmenī, ar zemām izmaksām). -**Ņem vērā izmaiņu starpību, nevis balstās uz etalonu.** Tas salīdzina `en.json` sapludināšanas bāzes punktā ar -darba koku; katrai atslēgai, kuras angļu valodas vērtība ir mainījusies, ikviena lokalizācija, kurā joprojām ir -neskarts tulkojums, tiek uzskatīta par novecojušu. Tas apzināti **iesaldē iepriekš pastāvējušo parādu** — izmaiņu -starpība nevar atklāt, no kura vecā angļu teksta ir radies sen pastāvošs tulkojums, tāpēc pārbaude vērtē -tikai to, ko skar pašreizējās izmaiņas. Alternatīvai pieejai (katras atslēgas jaucējvērtību etalonam) būtu nepieciešams -~600 KB ģenerēts fails, kas ir 3× lielāks par lielāko esošo etalonu un tiktu mainīts katrā i18n PR. +**Ņem vērā izmaiņas, nevis balstās uz bāzes stāvokli.** Tas salīdzina `en.json` sapludināšanas bāzes punktā ar +darba koku; katrai atslēgai, kuras angļu valodas vērtība ir mainījusies, jebkura lokalizācija, kurā joprojām ir +neskarts tulkojums, ir novecojusi. Tas apzināti **iesaldē iepriekš pastāvošo parādu** — izmaiņu salīdzinājums +nevar atklāt, no kuras vecās angļu valodas versijas ir radies ilgstoši pastāvošs tulkojums, tāpēc pārbaude vērtē +tikai to, ko skar pašreizējās izmaiņas. Alternatīva (katras atslēgas jaucējvērtību bāzes fails) prasītu +~600 KB ģenerētu failu, kas ir 3× lielāks par lielāko esošo bāzes failu un tiktu mainīts katrā i18n PR. To var izpildīt divos veidos: 1. atjaunināt skartos tulkojumus vai -2. iestatīt tos uz `__MISSING__:` — izpildlaikā tad tiek parādīts izlabotais teksts angļu valodā +2. iestatīt tos uz `__MISSING__:` — izpildlaikā tad tiek nodrošināts izlabotais teksts angļu valodā (`src/i18n/request.ts::deepMergeFallback`, #7258), un atslēga tiek ievietota tulkošanas rindā. Ja ir mainījusies virknes **nozīme**, ieteicams **pārdēvēt atslēgu**: jauna atslēga nevar mantot -novecojušu tulkojumu. Šādu pieeju izmantoja #8463. +novecojušu tulkojumu. Šo pieeju izmantoja #8463. ```bash npm run i18n:check-value-drift # stingrā pārbaude (to izpilda CI) -npm run i18n:check-value-drift:warn # tikai ziņošana +npm run i18n:check-value-drift:warn # tikai ziņojums BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Beidz darbu ar kodu 0 un `SKIP reason=base-unresolved`, ja bāzes katalogu nevar nolasīt (sekls -klons bez bāzes atsauces), tāpat kā `check-openapi-breaking`. +Beidzas ar kodu 0 un `SKIP reason=base-unresolved`, ja bāzes katalogu nevar nolasīt (sekls +klons bez bāzes atsauces), atbilstoši `check-openapi-breaking` darbībai. ### Uzdevums: `i18n` Pilna i18n validācijas matrica (viens uzdevums katrai lokalizācijai). Viss uzdevums ir konsultatīvs. -| Skripts | Pārbauda | Bloķēšana | +| Skripts | Pārbauda | Bloķējošs | | ------------------------------- | ---------------------------------------- | ------------------------------------------------------------ | -| `validate_translation.py quick` | Tulkojuma pilnīgumu katrai lokalizācijai | **Konsultatīva** (`continue-on-error: true` visam uzdevumam) | +| `validate_translation.py quick` | Tulkojuma pilnīgumu katrai lokalizācijai | **Konsultatīvs** (`continue-on-error: true` visam uzdevumam) | ### Uzdevums: `pr-test-policy` Tiek izpildīts tikai izmaiņu pieprasījumiem. -| Skripts | Pārbauda | Bloķēšana | +| Skripts | Pārbauda | Bloķējošs | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | PR, kas maina produkcijas kodu mapēs `src/`, `open-sse/`, `electron/` vai `bin/`, ir jāiekļauj vai jāatjaunina testi (Stingrais noteikums #8) | Jā | -| `check:test-masking` | Mainītie testu faili nesamazina kopējo apgalvojumu skaitu un nepievieno `assert.ok(true)` tautoloģijas | Jā | -| `check:pr-evidence` | PR aprakstā ir minēti izmaiņu testēšanas/VPS pierādījumi (automatizē Stingro noteikumu #18, pārmeklējot PR tekstu — trausli, skatiet neizdarīto darbu sarakstu) | Jā | +| `check:pr-test-policy` | PR, kas maina produkcijas kodu mapēs `src/`, `open-sse/`, `electron/` vai `bin/`, ir jāiekļauj vai jāatjaunina testi (stingrais noteikums #8) | Jā | +| `check:test-masking` | Mainītie testu faili nesamazina kopējo neto apgalvojumu skaitu un nepievieno `assert.ok(true)` tautoloģijas | Jā | +| `check:pr-evidence` | PR aprakstā ir norādīti izmaiņu testēšanas/VPS pierādījumi (automatizē stingro noteikumu #18, meklējot PR tekstā — trausli, skatiet neizpildīto darbu sarakstu) | Jā | ### Uzdevums: `test-vitest` Tiek izpildīts pēc `build`. Kļūmes gadījumā bloķē sapludināšanu. -| Komplekts | Pārbauda | Bloķēšana | -| ---------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP serveri (110 rīki), autoCombo, kešatmiņu — vitest izpildītājs | Jā | -| `test:vitest:ui` | UI komponentu testus — vitest izpildītājs | **Bloķē** — iepriekš pastāvošās kļūmes ir nepārprotami izslēgtas failā `vitest.config.ts`; jaunas kļūmes izraisa uzdevuma kļūmi | +| Komplekts | Pārbauda | Bloķējošs | +| ---------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | MCP serveri (110 rīki), autoCombo, kešatmiņu — vitest izpildītājs | Jā | +| `test:vitest:ui` | UI komponentu testus — vitest izpildītājs | **Bloķējošs** — iepriekš pastāvošās kļūmes ir skaidri izslēgtas failā `vitest.config.ts`; jaunas kļūmes izraisa uzdevuma kļūmi | ### Nakts darbplūsmas (ieplānotas, konsultatīvas) Tās tiek izpildītas pēc cron grafika (un ar `workflow_dispatch`), bet nekad PR ietvaros. Visas ir konsultatīvas. -| Darbplūsma | Pārbauda | Bloķēšana | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | fast-check īpašību testus ar nejaušu sākumvērtību un lielu izpildes reižu skaitu | **Konsultatīva** | -| `nightly-resilience` | kaudzes pieauguma pārbaudi, haosa kļūmju ievadīšanu, k6 slodzes/ilgstošas slodzes testus | **Konsultatīva** | -| `nightly-llm-security` | promptfoo injekciju aizsardzību (bloķēšanas režīmā) + garak zondes (tiek izlaistas bez pakalpojuma sniedzēja noslēpuma) | **Konsultatīva** | -| `nightly-schemathesis` | OpenAPI līguma izpludināšanas testēšanu (schemathesis) pret aktīvu OmniRoute, izmantojot `docs/openapi.yaml` — atklāj specifikācijas pārkāpumus / neapstrādātas 500 kļūdas (8. fāze B.4) | **Konsultatīva** | -| `nightly-mutation` | Stryker mutāciju testēšanas rezultātu ātro vienībtestu posmā — izdzīvojušās mutācijas atklāj vājus apgalvojumus | **Konsultatīva** | -| `nightly-compat` | Node dzinēja saderības matricu visos atbalstītajos `engines.node` diapazonos | **Konsultatīva** | +| Darbplūsma | Pārbauda | Bloķējoša | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `nightly-property` | fast-check īpašību testus ar nejaušu sākumvērtību un lielu izpildes reižu skaitu | **Konsultatīva** | +| `nightly-resilience` | kaudzes pieauguma pārbaudi, haosa kļūmju injicēšanu, k6 slodzes/ilgstošas slodzes testus | **Konsultatīva** | +| `nightly-llm-security` | promptfoo injekciju aizsardzību (bloķēšanas režīmā) + garak pārbaudes (tiek izlaistas bez pakalpojuma sniedzēja noslēpuma) | **Konsultatīva** | +| `nightly-schemathesis` | OpenAPI līguma izpludināšanas testēšanu (schemathesis) pret aktīvu OmniRoute, izmantojot `docs/openapi.yaml` — atklāj specifikācijas pārkāpumus / neapstrādātas 500 kļūdas (8. posms B.4) | **Konsultatīva** | +| `nightly-mutation` | Stryker mutāciju testēšanas rezultātu ātrajā vienību testu joslā — izdzīvojušās mutācijas atklāj vājus apgalvojumus | **Konsultatīva** | +| `nightly-compat` | Node dzinēja saderības matricu atbalstītajos `engines.node` diapazonos | **Konsultatīva** | --- @@ -544,43 +546,43 @@ savienojuma kļūdu un nekad nepārtrauc sesijas. --- -## Racionalizācijas neizdarīto darbu saraksts (ROI pārskatīšana — 9. fāzes 3. vilnis) +## Racionalizācijas neizdarīto darbu saraksts (ROI pārskats — 9. fāze, 3. vilnis) -Šis uzskaitījums 2026-06-17 tika salīdzināts ar `ci.yml` (iepriekšējā versijā nebija iekļauti +Šis inventārs 2026-06-17 tika saskaņots ar `ci.yml` (iepriekšējā versijā bija izlaisti `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Saskaņotās kopas ROI pārskatīšanā -tika identificēti tālāk minētie racionalizācijas kandidāti. **Apvienošana ir mehāniska CI -izmaiņa; statusa maiņa/atmešana ir politikas lēmums, kas atstāts operatora ziņā.** Nekas no tālāk -minētā vēl nav ieviests. +`check:codeql-ratchet`, `check:pr-evidence`). Saskaņotās kopas ROI pārskatā +tika identificēti tālāk norādītie racionalizācijas kandidāti. **Apvienošanas ir mehāniskas CI +izmaiņas; pārslēgšanas/atmešanas ir politikas lēmumi, kas atstāti operatora ziņā.** Nekas no tālāk minētā +vēl nav piemērots. **Iepriekš nav dokumentēti arī** (konsultatīvi, zems signāla līmenis): `docs-lint` darbs -(markdownlint + Vale, visam darbam iestatīts `continue-on-error`) un savrupās skeneru darbplūsmas -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` ir norādīts -`quality-baseline.json`, taču `ci.yml` tas nav piesaistīts bloķējošam sliekšņa mehānismam — šis rādītājs -pašlaik ir bāreņrādītājs. - -### Apvienošana / dublikātu novēršana (mehāniska, zemāks risks) - -Katrs kandidāts 2026-06-17 tika pārbaudīts pret aktuālo kontroles statusu (uzticies, bet pārbaudi); -vairāki „acīmredzami” apvienošanas gadījumi izrādījās slēpj parādu un **nav** tieši aizstājēji. - -- **`check:docs-sync` tiek izpildīts divreiz** — atsevišķi `lint` darbā un vēlreiz `check:docs-all` (`docs-sync-strict`) ietvaros, kā arī husky pirmsiesniegšanas āķī. ✅ **PABEIGTS** — atsevišķā izsaukšana no `lint` ir noņemta. -- **CVE skenēšana** — ❌ **NAV vienkārši apvienojama.** `audit:deps` izraisa kļūmi jebkuras augstas/kritiskas CVE gadījumā; `check:vuln-ratchet` (osv) izraisa kļūmi tikai _regresijas_ gadījumā salīdzinājumā ar bāzes līmeni (pašlaik 1 MODERATE). Atšķirīga semantika — `audit:deps` atmešana likvidētu absolūto augsta/kritiska līmeņa kontroli. Saglabāt abus. -- **Ciklu noteikšana** — ❌ **NAV vienkārši apvienojama.** `check:circular-deps` (dpdm) ziņo par **91 ciklu** (tādēļ tā ir konsultatīva); to nevar padarīt bloķējošu, vispirms šos ciklus nenovēršot, un tās tvērums ir plašāks nekā sekmīgajai, rūpīgi atlasītajai `check:cycles`. Saglabāt `check:cycles` kā bloķējošu pārbaudi; 91 dpdm cikla novēršana ir atsevišķs neizdarīto darbu vienums. -- **Sarežģītība** — ✅ **PABEIGTS** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): viena ESLint caurskate, uzskaite pēc ruleId, lai ciklomātiskās sarežģītības+max-lines un kognitīvās sarežģītības bāzes līmeņi paliktu neatkarīgi; individuālās `check:complexity` / `check:cognitive-complexity` pārbaudes saglabātas lokālai `--update` lietošanai. -- **`/api` pret-halucināciju pārbaude** — ✅ **PABEIGTS** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): viena `src/app/api` failu sistēmas inventarizācija; openapi-routes + docs-symbols joprojām ziņo neatkarīgi; individuālās pārbaudes saglabātas lokālai izpildei. -- **`check:node-runtime` tiek izpildīts 11 darbos** — ⚠️ **zema ROI.** Katram ir atsevišķs izpildītājs, un pārbaude ilgst <1 s; kopējais ietaupījums būtu ~10 s, taču tiktu zaudēta vienkārša katra darba aizsardzība. Nav vērts radīt liekas izmaiņas. -- **`typecheck:noimplicit:core` CI lint pārbaudē** — ✅ **noņemts no lint darba** (bija konsultatīvs `continue-on-error`); bloķējošo tipu tvērumu nodrošina `typecheck:core` + `check:type-coverage`. Lokālais skripts saglabāts. - -### Statusa maiņa / lēmums (operatora politika) - -- `check:openapi-security-tiers` (konsultatīvs) — ❌ **NAV vienkārši padarāms par bloķējošu.** Tas beidz darbu ar kodu 0, taču brīdina, ka vairākiem `traffic-inspector` maršrutiem zem `LOCAL_ONLY_API_PREFIXES` trūkst anotācijas `x-loopback-only: true`. Lai to ieviestu kā obligātu pārbaudi, vispirms šīs anotācijas jāpievieno failam `openapi.yaml`. -- `typecheck:noimplicit:core` (konsultatīvs) — lielā mērā dublē bloķējošā `check:type-coverage` sliekšņa mehānisma funkcijas. Pārveidot par sliekšņa mehānismu vai atmest redundanto otro `tsc` izpildi. -- `test:vitest:ui` (tagad **bloķējošs**) — iepriekš pastāvošās kļūmes ir skaidri izslēgtas failā `vitest.config.ts`, izmantojot `// #8618` izsekošanas komentārus; jaunas kļūmes izraisa darba kļūmi. -- `check:secrets` (gitleaks, bloķējošs sliekšņa mehānisms, fiksēts pie 3 dokumentētiem kļūdaini pozitīviem rezultātiem) — iekļaut šos 3 atļauto vienumu sarakstā, lai sasniegtu 0, vai pazemināt līdz konsultatīvam statusam. Pārklājas ar GitHub iebūvēto noslēpumu skenēšanu + `check:public-creds`. -- `check:pr-evidence` (bloķējošs, meklē atbilstības PR apraksta tekstā) — augsts kļūdaini pozitīvu rezultātu risks; atmešana vājinātu stingrā noteikuma Nr. 18 izpildi, tādēļ tas ir īsts politikas lēmums. -- `semgrep` (konsultatīvs, savrupa darbplūsma) — OWASP kategorijās pārklājas ar CodeQL; piesaistīt tā bāzes līmeni sliekšņa mehānismam vai atmest. +(markdownlint + Vale, visam darbam iestatīts `continue-on-error`) un savrupās skenēšanas darbplūsmas +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` ir ietverts +`quality-baseline.json`, bet nav piesaistīts bloķējošam sprūdratam failā `ci.yml` — šī metrika +pašlaik ir bāreņmetrika. + +### Apvienošana / dublikātu novēršana (mehāniski, zemāks risks) + +Katrs kandidāts 2026-06-17 tika pārbaudīts pret aktuālo vārtu stāvokli (uzticies, bet pārbaudi); +vairākas „acīmredzamas” apvienošanas tomēr slēpa tehnisko parādu un **nav** tīri, tieši aizstājēji. + +- **`check:docs-sync` tiek palaists divreiz** — atsevišķi `lint` darbā un vēlreiz `check:docs-all` (`docs-sync-strict`) ietvaros un husky pirmskomita āķī. ✅ **PABEIGTS** — atsevišķā izsaukšana `lint` darbā ir noņemta. +- **CVE skenēšana** — ❌ **NAV tīri apvienojama.** `audit:deps` izraisa stingru kļūmi jebkuras augstas/kritiskas CVE gadījumā; `check:vuln-ratchet` (osv) izraisa kļūmi tikai _regresijas_ gadījumā salīdzinājumā ar bāzes līmeni (pašlaik 1 MODERATE). Atšķirīga semantika — atmetot `audit:deps`, tiktu zaudēti absolūtie augstas/kritiskas nozīmības vārti. Paturēt abus. +- **Ciklu noteikšana** — ✅ **PABEIGTS** (#15159 G-01/G-02). Iepriekšējā tekstā `check:cycles` bija nosaukts par „zaļajiem, rūpīgi atlasītajiem” vārtiem, un to bloķējošais statuss tika pamatots ar to, ka `check:circular-deps` (dpdm) ziņoja par 91 ciklu. Šis zaļais statuss bija **kļūdaini zaļš**: `check:cycles` skenēja 5 apakšdirektorijus (450 failus), atpazina tikai statiskos `import|export … from` un atmeta katru `@/` un `@omniroute/open-sse/` specifikatoru, tādēļ tas nevarēja konstatēt dinamiskās importēšanas un aizstājvārdu ciklus, kas dominēja repozitorijā. Labots: tagad vārti apstaigā `src` + `open-sse` (5023 failus), apkopo specifikatorus no TypeScript AST (tādēļ `import("…")` tiek ieskaitīts, bet tipa pozīcijas `typeof import("…")` — netiek) un atrisina tsconfig `paths`. Tas atrod **14** ciklus, nevis 0. Tā kā 14 iepriekš pastāvošus ciklus nevar novērst vārtu PR ietvaros, `check:cycles` tagad ir **sprūdrats** (`--ratchet`, griesti `metrics.cycles.value = 14` failā `quality-baseline.json`, `direction: down`) — tas bloķē jebkuru _regresiju_, un ciklu skaits var tikai samazināties. CI izpilda `npm run check:cycles:ratchet`. Pakāpeniskā samazināšana notiek kopā ar **A-01**. `check:circular-deps` (dpdm) paliek konsultatīvs kā plašāks otrais viedoklis. +- **Sarežģītība** — ✅ **PABEIGTS** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): viena ESLint apstaigāšana, uzskaite pēc ruleId, lai ciklomātiskās sarežģītības+max-lines un kognitīvās sarežģītības bāzes līmeņi paliktu neatkarīgi; individuālie `check:complexity` / `check:cognitive-complexity` paliek lokālai `--update` izmantošanai. +- **`/api` prethalucināciju pārbaude** — ✅ **PABEIGTS** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): viena `src/app/api` failu sistēmas inventarizācija; openapi-routes + docs-symbols joprojām sniedz pārskatus neatkarīgi; individuālās pārbaudes paliek lokālai izpildei. +- **`check:node-runtime` tiek palaists 11 darbos** — ⚠️ **zema ROI.** Katrs izmanto atsevišķu izpildītāju, un pārbaude ilgst <1 s; kopējais ietaupījums ir ~10 s, taču tiktu zaudēta lēta katra darba aizsargpārbaude. Nav vērts radīt izmaiņu troksni. +- **`typecheck:noimplicit:core` CI lintēšanas darbā** — ✅ **noņemts no lintēšanas darba** (bija konsultatīvs `continue-on-error`); bloķējošo tipu virsmu nodrošina `typecheck:core` + `check:type-coverage`. Lokālais skripts ir saglabāts. + +### Pārslēgt / izlemt (operatora politika) + +- `check:openapi-security-tiers` (konsultatīvs) — ❌ **NAV tīri pārslēdzams.** Tas iziet ar kodu 0, bet brīdina, ka vairākiem `traffic-inspector` maršrutiem zem `LOCAL_ONLY_API_PREFIXES` trūkst anotācijas `x-loopback-only: true`. Lai to padarītu obligātu, vispirms šīs anotācijas jāpievieno failam `openapi.yaml`. +- `typecheck:noimplicit:core` (konsultatīvs) — lielākoties to aizstāj bloķējošais `check:type-coverage` sprūdrats. Pārslēgt uz sprūdratu vai atmest lieko otro `tsc` izpildi. +- `test:vitest:ui` (tagad **bloķējošs**) — iepriekš pastāvošās kļūmes ir tieši izslēgtas failā `vitest.config.ts` ar `// #8618` izsekošanas komentāriem; jaunas kļūmes izraisa darba kļūmi. +- `check:secrets` (gitleaks, bloķējošs sprūdrats, iesaldēts pie 3 dokumentētiem kļūdaini pozitīviem rezultātiem) — pievienot šos 3 atļauto vienumu sarakstam, lai sasniegtu 0, vai pazemināt līdz konsultatīvam statusam. Pārklājas ar GitHub iebūvēto slepeno datu skenēšanu + `check:public-creds`. +- `check:pr-evidence` (bloķējošs, meklē atbilstības PR apraksta prozas tekstā) — augsts kļūdaini pozitīvu rezultātu risks; atmešana vājinātu Stingrā noteikuma Nr. 18 izpildi, tāpēc šis ir īsts politikas lēmums. +- `semgrep` (konsultatīva savrupā pārbaude) — OWASP kategorijās pārklājas ar CodeQL; piesaistīt tā bāzes līmeni sprūdratam vai atmest. --- diff --git a/docs/i18n/lv/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/lv/docs/compression/COMPRESSION_GUIDE.md index da26883f4f00..257dc0fd2abb 100644 --- a/docs/i18n/lv/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/lv/docs/compression/COMPRESSION_GUIDE.md @@ -466,9 +466,7 @@ vietā tiek ievietota lokalizētā instrukcija. Pamattekstā ar `messages` satura apiešana (`shouldBypassCavemanOutputMode()` failā `open-sse/services/compression/outputMode.ts`) pārbauda pēdējos trīs ziņojumus un izlaiž stilus visam gājienam, ja tie atbilst drošības, neatgriezeniskas darbības, -precizēšanas vai secības jutīguma atslēgvārdiem. Apiešana darbojas atbilstoši tam, kā -vadības panelī ir iestatīts **Auto-Clarity Bypass** pārslēgs -(`cavemanOutputMode.autoClarity`). +precizēšanas vai secības jutīguma atslēgvārdiem. Apiešana darbojas, kamēr vadības panelī **Auto-Clarity Bypass** pārslēgs (`cavemanOutputMode.autoClarity`) ir ieslēgts, kas ir noklusējuma iestatījums; ja pārslēgs ir izslēgts, izvēlētie stili tiek piemēroti arī šajos gājienos. Ja apiešana ļauj apstrādāt gājienu, `placeSystemInstruction()` (tajā pašā failā), kas nekad neveido jaunu `messages[0]`, ievieto bloku pirmajā atrastajā vietā: diff --git a/docs/i18n/lv/docs/reference/ENVIRONMENT.md b/docs/i18n/lv/docs/reference/ENVIRONMENT.md index b072b5c81d9a..188899b37ab8 100644 --- a/docs/i18n/lv/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/lv/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Obligātie noslēpumi -Tie **obligāti** jāiestata pirms pirmās palaišanas. Bez tiem lietojumprogramma vai nu atteiksies startēt, vai darbosies ar nedrošām noklusējuma vērtībām. - -| Mainīgais | Obligāts | Noklusējums | Avota fails | Apraksts | -| ---------------------------- | ------------------- | -------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Jā** | _(nav)_ | `src/lib/auth` | Paraksta/pārbauda visus informācijas paneļa sesijas sīkfailus (JWT). Ģenerējiet ar `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Jā** | _(nav)_ | `src/lib/db/apiKeys.ts` | AES šifrēšanas atslēga SQLite datubāzē glabātajām API atslēgu vērtībām. Ģenerējiet ar `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Jā** | `CHANGEME` | Sāknēšanas skripts | Iestata sākotnējo administratora informācijas paneļa paroli (atbilst `.env.example` noklusējuma vērtībai — tā apzināti atstāta acīmredzami nedroša, lai piespiestu to nomainīt). **Nomainiet pirms pirmās lietošanas.** Pēc pieteikšanās nomainiet to, izmantojot Informācijas panelis → Iestatījumi → Drošība. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Jā** (produkcijā) | _(nav iestatīts)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Koplietojams noslēpums iekšējam Codex Responses WebSocket tiltam. Autentificē tilta pieprasījumus starp Electron/pārlūkprogrammas WS releju un OmniRoute. ⚠️ **OBLIGĀTS produkcijā — ja tas nav iestatīts, visi WS tilta pieprasījumi tiek noraidīti.** Ģenerējiet ar `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nē | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nepārprotams servisa darbinieka kešatmiņas apiešanas identifikators PWA čaulai (#11779); pirmais noteikšanas ķēdē. | -| `SOURCE_VERSION` | Nē | _(nav iestatīts)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Otrais ķēdē — PaaS būvētāji (piemēram, Heroku tipa) to iestata uz izvietotā komita vērtību. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nē | _(atvasināts)_ | `src/shared/components/PwaRegister.tsx` | Būvēšanas laikā pieejamā publiskā vērtība, ko klients izmanto, lai reģistrētu `/sw.js?v=…`; tā tiek atvasināta no abām iepriekš minētajām vērtībām un pēc tam no git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nē (automātiski) | _(automātiski katrā palaišanas reizē)_ | `src/server/authz/policies/management.ts` | Katra procesa noslēpums, kas apliecina, ka uzticamais vienādranga IP zīmogs saņemts no paša OmniRoute HTTP servera (`scripts/dev/peer-stamp.mjs`). Autorizācijas starpprogrammatūra uzticas pieprasījuma lokālumam (LOCAL_ONLY maršrutu ierobežošana uz atgriezeniskās cilpas/LAN adresēm) tikai tad, ja zīmogs ietver šo pilnvaru. Tas tiek automātiski ģenerēts katrā palaišanas reizē — atstājiet neiestatītu; fiksējiet to tikai vairāku procesu konfigurācijām, kurām zīmogs ir jākoplieto. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nē | _(nav iestatīts)_ | `scripts/dev/peer-stamp.mjs` | Ar komatiem atdalītas reverso starpniekserveru IP adreses vai CIDR diapazoni, kuru `X-Forwarded-For` / `X-Real-IP` vērtībām IP atļaušanas/aizliegšanas saraksts drīkst uzticēties papildus atgriezeniskās cilpas un privātā tīkla adresēm, kā arī Cloudflare malām, kuras vienmēr tiek uzskatītas par uzticamām. Nepieciešams tikai starpniekserverim ar jebkuru citu publisko adresi; pretējā gadījumā filtrs izvērtē paša starpniekservera adresi. | +Nesetie noslēpumi ar atzīmi **Jā** pirmajā palaišanas reizē tiek automātiski ģenerēti (`JWT_SECRET`, `API_KEY_SECRET`) un saglabāti datu direktorijas failā `server.env` — iestatiet tos nepārprotami, ja šī direktorija ir īslaicīga. Izņēmums ir `OMNIROUTE_WS_BRIDGE_SECRET`: tas netiek saglabāts `server.env` — servera palaišanas ievades punkti katrā palaišanas reizē izveido īslaicīgu vērtību, kuru var izmantot tikai paša procesa iekšienē darbojošais tilts, tāpēc ārējiem tilta klientiem šī vērtība ir nepārprotami jāiestata. + +| Mainīgais | Obligāts | Noklusējums | Avota fails | Apraksts | +| ---------------------------- | ------------------- | -------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Jā** | _(nav)_ | `src/lib/auth` | Paraksta/pārbauda visus informācijas paneļa sesijas sīkfailus (JWT). Ģenerējiet ar `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Jā** | _(nav)_ | `src/lib/db/apiKeys.ts` | AES šifrēšanas atslēga SQLite datubāzē glabātajām API atslēgu vērtībām. Ģenerējiet ar `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nē | _(nav iestatīts)_ | `src/lib/auth/managementPassword.ts` | Iestata informācijas paneļa paroli palaišanas laikā, kamēr vēl nav saglabāta neviena parole; pēc paroles saglabāšanas to ignorē. npm instalācijas un instalācijas no pirmkoda kopē `.env.example` uz `.env`, tāpēc tās sākas ar tā publiski zināmo vietturi `CHANGEME`: nomainiet to pirms pirmās palaišanas vai iestatiet `INITIAL_PASSWORD=` (tukšu vērtību) šajā `.env`, lai paroli izveidotu pirmā apmeklējuma reizē sākotnējās iestatīšanas vednī — tāpat, kā to dara neiestatīta vērtība (Docker attēla noklusējums). Saglabāto paroli nomainiet sadaļā Informācijas panelis → Iestatījumi → Drošība vai izpildiet `omniroute reset-password` (`node bin/reset-password.mjs` pirmkoda izrakstā), iestatot `DATA_DIR` uz servera datu direktoriju. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Jā** (produkcijā) | _(nav iestatīts)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Koplietojams noslēpums iekšējam Codex Responses WebSocket tiltam. Autentificē tilta pieprasījumus starp Electron/pārlūkprogrammas WS releju un OmniRoute. ⚠️ **OBLIGĀTS produkcijā — ja tas nav iestatīts, visi WS tilta pieprasījumi tiek noraidīti.** Ģenerējiet ar `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nē | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nepārprotams servisa darbinieka kešatmiņas apiešanas identifikators PWA čaulai (#11779); pirmais noteikšanas ķēdē. | +| `SOURCE_VERSION` | Nē | _(nav iestatīts)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Otrais ķēdē — PaaS būvētāji (piemēram, Heroku tipa) to iestata uz izvietotā komita vērtību. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nē | _(atvasināts)_ | `src/shared/components/PwaRegister.tsx` | Būvēšanas laikā pieejamā publiskā vērtība, ko klients izmanto, lai reģistrētu `/sw.js?v=…`; tā tiek atvasināta no abām iepriekš minētajām vērtībām un pēc tam no git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nē (automātiski) | _(automātiski katrā palaišanas reizē)_ | `src/server/authz/policies/management.ts` | Katra procesa noslēpums, kas apliecina, ka uzticamais vienādranga IP zīmogs saņemts no paša OmniRoute HTTP servera (`scripts/dev/peer-stamp.mjs`). Autorizācijas starpprogrammatūra uzticas pieprasījuma lokālumam (LOCAL_ONLY maršrutu ierobežošana uz atgriezeniskās cilpas/LAN adresēm) tikai tad, ja zīmogs ietver šo pilnvaru. Tas tiek automātiski ģenerēts katrā palaišanas reizē — atstājiet neiestatītu; fiksējiet to tikai vairāku procesu konfigurācijām, kurām zīmogs ir jākoplieto. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nē | _(nav iestatīts)_ | `scripts/dev/peer-stamp.mjs` | Ar komatiem atdalītas reverso starpniekserveru IP adreses vai CIDR diapazoni, kuru `X-Forwarded-For` / `X-Real-IP` vērtībām IP atļaušanas/aizliegšanas saraksts drīkst uzticēties papildus atgriezeniskās cilpas un privātā tīkla adresēm, kā arī Cloudflare malām, kuras vienmēr tiek uzskatītas par uzticamām. Nepieciešams tikai starpniekserverim ar jebkuru citu publisko adresi; pretējā gadījumā filtrs izvērtē paša starpniekservera adresi. | ### Ģenerēšanas komandas @@ -217,38 +217,38 @@ OmniRoute visai pastāvīgajai datu glabāšanai izmanto **SQLite** (ar `better- ## 4. Drošība un autentifikācija -| Mainīgais | Noklusējuma vērtība | Avota fails | Apraksts | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sāls, kas tiek kombinēta ar aparatūras identifikatoriem, lai izveidotu ierīces digitālo nospiedumu. Izolācijas nolūkā mainiet to katrai izvietošanai. | -| `OMNIROUTE_CLI_SALT` | _(nav iestatīts = nejauša sāls katrai instalācijai, kas tiek saglabāta failā `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC sāls lokālā CLI autentifikācijas marķiera atvasināšanai. Šīs vērtības iestatīšana nomaina visus ierīces CLI marķierus un vienmēr ir prioritāra pār saglabāto sāli. Skatiet `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sesijas sīkfailiem iestata karogu `Secure`. Darbojoties aiz HTTPS, tam **jābūt iestatītam uz `true`**. | -| `REQUIRE_API_KEY` | `false` | API starpprogrammatūra | Ja iestatīts uz `true`, visos `/v1/*` starpniekservera pieprasījumos jāiekļauj derīga API atslēga. **Šis karogs neierobežo `GET /v1/models`**, jo tas tā vietā izmanto informācijas paneļa pieteikšanās režīmu (`requireAuthForModels`), tādēļ atbilde `401` no `/v1/models` NENOZĪMĒ, ka izsecināšana ir aizsargāta. Skatiet `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Ļauj informācijas paneļa lietotāja saskarnē atklāt pilnas API atslēgu vērtības. Konfigurējams informācijas paneļa funkciju karogu sadaļā; koplietotās instancēs tas rada drošības risku. | -| `NO_LOG_API_KEY_IDS` | _(tukšs)_ | `src/lib/compliance/index.ts` | Ar komatiem atdalīti API atslēgu ID, kuriem netiek veikta pieprasījumu reģistrēšana (VDAR atbilstības nodrošināšanai). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nav iestatīts = neierobežots)_ | `src/shared/utils/apiKeyPolicy.ts` | Rezerves dienas pieprasījumu limits, kas tiek piemērots API atslēgām, kuru kolonnas `rate_limits` vērtība ir null. Nav iestatīts vai ir tukšs: nav netieša ierobežojuma (#2289, #11017). `0` nozīmē to pašu (neierobežots). Pozitīvs vesels skaitlis N iespējo N dienā, 5N nedēļā un 20N mēnesī. Nepareizi formatētām vērtībām, kas nav tukšas, tiek izmantoti mantotie ierobežojumi: 1000 dienā, 5000 nedēļā un 20000 mēnesī. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimālais atļautais pieprasījuma pamatdaļas izmērs. Noraida lietderīgās slodzes, kas pārsniedz šo ierobežojumu. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiskās pieprasījumu pamatdaļas, kuru izmērs sasniedz vai pārsniedz šo slieksni, pirms JSON parsēšanas iegūst atomāru, lokālu procesam un resursietilpīgiem pieprasījumiem paredzētu uzņemšanas nomu (BYTE ceļš, tostarp `POST /v1/responses`). Tiek izmantots tas pats [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) veselīgas brīvās jaudas izņēmums kā strukturāli sarežģītiem pieprasījumiem; to joprojām ierobežo `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), lai [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) neatkārtotos. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Tērzēšanas maršruta stingrais ierobežojums, kas tiek piemērots ierobežotas ievades laikā nolasīto baitu skaitam, tostarp pieprasījumiem ar trūkstošu, nederīgu vai nepatiesu `Content-Length`; pārsniegšanas gadījumā tiek atgriezts `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nav iestatīts — nav pieprasījumu skaita ierobežojuma)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** šis mantotais pieprasījumu SKAITA ierobežojums tagad ir saistošs tikai tad, ja tas ir nepārprotami iestatīts. Ja tas nav iestatīts (noklusējums), resursietilpīgu tērzēšanas pieprasījumu uzņemšanu tā vietā ierobežo `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automātiski atvasināts BAITU budžets, kura lielumu nosaka procesa reālais atmiņas ierobežojums **vienā procesā** (vienā V8 kaudzē). Divi vienlaicīgi `/v1/responses` pieprasījumi ar ~750 tūkst. marķieru izraisa ~12 GiB kaudžu avāriju (#7849) — tas ir **atmiņas budžeta** brīdinājums, nevis stingrs produkta maksimums 2. Vesels process (kaudze zem slodzes nomešanas attiecības) VAR uzņemt vairāk vienlaicīgu ilgu `/v1/responses` pieprasījumu, izmantojot `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Desmitiem ilgu SSE klientu (40–50) nosaka kaudze + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nevis „maksimums 2”. Akla šīs vērtības palielināšana, lai „izmantotu resursdatoru”, no jauna izraisa #7849. Pavairojiet **kaudzes**, izmantojot **N neatkarīgus `DATA_DIR`** (#11024); nekad nelietojiet `replicas>1` vienam SQLite failam. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automātiski atvasināts)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automātiski atvasinātā ievades baitu budžeta pārrakstīšana (25% no stingrākā V8/cgroup atmiņas ierobežojuma, dalīti ar 8x pārejošās pastiprināšanas koeficientu). Atvasinātās un tieši norādītās vērtības tiek ierobežotas diapazonā no 8 MiB līdz 2 GiB. Ķermenis, kas pārsniedz efektīvo budžetu, nekavējoties saņem kļūdu `413 body_exceeds_budget`; konkurence starp ķermeņiem, kurus katru atsevišķi var apkalpot, joprojām izraisa atkārtojamu `503`. 40–50 vienlaicīgus ilgus SSE klientus nosaka šis budžets + kaudze, nevis stingrs „maksimums 2”. Pirms regulēšanas skatiet `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` adresē `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kaudzes spiediena slodzes nomešanas attiecība (`heapUsed / heap_size_limit`) BAITU un STRUKTŪRAS resursietilpīgu pieprasījumu uzņemšanai (#10183, #10268, #10437). Vienlaicīgs resursietilpīgs pieprasījums virs `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` tiek noraidīts ar atkārtojamu `503` tikai tad, ja kaudze ARĪ ir sasniegusi vai pārsniegusi šo attiecību; ja kaudze ir veselīga, pieprasījums tiek uzņemts, izmantojot veselīgās kaudzes rezerves kapacitāti. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (noklusējums `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ierobežota papildu kapacitāte veselīgas kaudzes ātrajam ceļam (#10437) gan STRUKTŪRAI, gan BAITIEM (`admitChatRequest`, tostarp ķermeņiem ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez šī ierobežojuma katrs aizņemts, bet ar veselīgu kaudzi saistīts pieprasījums apietu uzņemšanas kontroli bez jebkādas augšējās robežas. Kad veselīgas kaudzes ceļā ir aktīvs šāds vienlaicīgu nomu skaits, turpmākie pieprasījumi aizņemtības laikā pāriet uz TO PAŠU ierobežotās gaidīšanas/slodzes nomešanas ceļu, ko izmanto reāla kaudzes spiediena apstākļos. `0` pilnībā atspējo apiešanu. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Ziņojumu skaits, pie kura tērzēšanas pieprasījums tiek klasificēts kā resursietilpīgs pat tad, ja tā ķermenis nepārsniedz baitu slieksni. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Rīku skaits, pie kura tērzēšanas pieprasījums tiek klasificēts kā resursietilpīgs pat tad, ja tā ķermenis nepārsniedz baitu slieksni. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatīvs uz virknes lielumu balstīts marķieru aprēķins, pēc kura pieprasījums tiek klasificēts kā resursietilpīgs; tas ir uzņemšanas izmaksu aizstājējrādītājs, nevis pakalpojumu sniedzēja norēķinu marķierizācija. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (atspējots) | `src/shared/middleware/chatBodyAdmission.ts` | Neobligāts, apzināti iespējojams tērzēšanas vēstures ierobežojums. Pēc noklusējuma atspējots: ziņojumu skaits ir izvietošanas politika, nevis universāla pieprasījuma īpašība, un ierobežošana šeit noraida sarunas ar galīgu `413`, pirms saspiešanas konveijers var tās padarīt apkalpojamas. Kaudzes pieaugumu ierobežo `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` un kaudzes spiediena slodzes nomešana. Atmiņas ziņā ierobežotos izvietojumos, kuriem nepieciešama stingra augšējā robeža, iestatiet pozitīvu vērtību; pārsniegšanas gadījumā tiek atgriezts strukturēts `413`, kas norāda uz nepieciešamu saspiešanu. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Stingrs ierobežojums nestraumējošai augšupstraumes atbildei, kas tiek pilnībā buferēta atmiņā. To pārsniedzot, augšupstraumes lasītājs tiek atcelts un pieprasījums ātri beidzas ar kļūdu, nevis turpina audzēt neierobežotu virkni, līdz kaudze ir izsmelta. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimālais no augšupējās atbildes galvenēm pārsūtāmo datu apjoms baitos. Kad limits tiek pārsniegts, zemākas prioritātes galvenes (piem., pielāgotās `x-codex-*`, `x-oai-request-id`) tiek atmestas, lai nepārsniegtu izplatītos reverso starpniekserveru galveņu ierobežojumus. Iestatiet lielāku vērtību, lai pārsūtītu vairāk augšupējo metadatu, palielinot atbildes galveņu izmēru. | -| `CORS_ORIGIN` | _(nav iestatīts)_ | `src/server/cors/origins.ts` | Novecojis vienas izcelsmes CORS atļauto izcelsmju saraksts. Jauniem izvietojumiem ieteicams izmantot `CORS_ALLOWED_ORIGINS`. CORS ir paredzēts tikai citas izcelsmes pārlūkprogrammu API klientiem; autentificēti informācijas paneļa rakstīšanas pieprasījumi tā vietā izmanto tās pašas izcelsmes pieprasījumus kopā ar sesijai piesaistītu CSRF aizsardzību. | -| `CORS_ALLOWED_ORIGINS` | _(nav iestatīts)_ | `src/server/cors/origins.ts` | Ar komatiem atdalīts CORS atļauto izcelsmju saraksts. Aizstājējzīme netiek nosūtīta, ja vien nav nepārprotami konfigurēts `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Tikai izstrādei paredzēta atkāpšanās iespēja, kas ļauj atspoguļot jebkuru pārlūkprogrammas `Origin`. Neiespējojiet koplietojamos vai produkcijas izvietojumos. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Bloķē pakalpojumu sniedzēju izsaukumus, kuru mērķis ir privāto, atgriezeniskās cilpas vai lokālās saites IP adrešu diapazoni. Atspējojiet tikai izolētās testa vidēs. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Atļauj pakalpojumu sniedzēju URL, kas norāda uz privātiem/lokāliem tīkliem (localhost, 192.168.x.x, 10.x.x.x utt.). **OBLIGĀTI pašmitinātiem pakalpojumu sniedzējiem** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Ja vērtība ir `false`, informācijas panelis noraida lokālo URL validāciju. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Atļauj pievienot/validēt pakalpojumu sniedzējus lokālās/privātās adresēs (127.0.0.1, localhost, LAN, privātie diapazoni) — attiecas tikai uz pakalpojumu sniedzēja validācijas ceļu. **Noklusējums ir `true`** (prioritāte lokālajam); iestatiet `false`, lai ieviestu stingru bloķēšanu, atļaujot tikai publiskas adreses. Mākoņa metadatu galapunkti (169.254.169.254, metadata.google.internal) paliek bloķēti neatkarīgi no šī iestatījuma. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ļauj `/v1/audio/*` maršrutiem (transkripcijai, runai, tulkošanai) izmantot ar OpenAI saderīgu pakalpojumu sniedzēja mezglu, kas mitināts ārpus localhost. Pēc noklusējuma izslēgts — audio maršrutēšana uz attālu resursdatoru maina izejošās datplūsmas identitāti, tādēļ tam jābūt nepārprotamam operatora lēmumam. Atgriezeniskās cilpas/privātie mezgli (localhost, 127.0.0.1, 172.16-31.x) vienmēr ir atļauti, un šis iestatījums tos neietekmē. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ļauj `POST /v1/rerank` (un atmiņas dzinēja atgriezeniskās cilpas pārkārtošanas darbībai) izmantot ar OpenAI saderīgu pakalpojumu sniedzēja mezglu, kas mitināts ārpus localhost — LAN ierīci vai Tailscale vienranga mezglu, kurā darbojas TEI, Infinity, vLLM utt. Pēc noklusējuma izslēgts — maršrutēšana uz attālu resursdatoru maina izejošās datplūsmas identitāti, tādēļ tam jābūt nepārprotamam operatora lēmumam. Atgriezeniskās cilpas mezgli (localhost, 127.0.0.1, 172.16-31.x) vienmēr ir atļauti, un šis iestatījums tos neietekmē. Attālajiem mezgliem jāatbilst arī pakalpojumu sniedzēju izejošo URL politikai (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); datplūsma nekad netiek maršrutēta uz mākoņa metadatu resursdatoriem. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kad OIDC ir iespējots, atspējo pieteikšanos ar paroli, lai lietotāji varētu autentificēties tikai ar OIDC vienoto pierakstīšanos. Tiek pieņemts arī vienkāršais aizstājvārds `OIDC_DISABLE_PASSWORD_LOGIN`; informācijas paneļa funkcijas karogam ar tādu pašu atslēgu ir augstāka prioritāte. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Vienkāršais `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` aizstājvārds (#10889). | +| Mainīgais | Noklusējuma vērtība | Avota fails | Apraksts | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sāls, kas tiek kombinēta ar aparatūras identifikatoriem, lai izveidotu ierīces digitālo nospiedumu. Izolācijas nolūkā mainiet to katrai izvietošanai. | +| `OMNIROUTE_CLI_SALT` | _(nav iestatīts = nejauša sāls katrai instalācijai, kas tiek saglabāta failā `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC sāls lokālā CLI autentifikācijas marķiera atvasināšanai. Šīs vērtības iestatīšana nomaina visus ierīces CLI marķierus un vienmēr ir prioritāra pār saglabāto sāli. Skatiet `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sesijas sīkfailiem iestata karogu `Secure`. Darbojoties aiz HTTPS, tam **jābūt iestatītam uz `true`**. | +| `REQUIRE_API_KEY` | `false` | API starpprogrammatūra | Ja iestatīts uz `true`, visos `/v1/*` starpniekservera pieprasījumos jāiekļauj derīga API atslēga. **Šis karogs neierobežo `GET /v1/models`**, jo tas tā vietā izmanto informācijas paneļa pieteikšanās režīmu (`requireAuthForModels`), tādēļ atbilde `401` no `/v1/models` NENOZĪMĒ, ka izsecināšana ir aizsargāta. Skatiet `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Ļauj informācijas paneļa lietotāja saskarnē atklāt pilnas API atslēgu vērtības. Konfigurējams informācijas paneļa funkciju karogu sadaļā; koplietotās instancēs tas rada drošības risku. | +| `NO_LOG_API_KEY_IDS` | _(tukšs)_ | `src/lib/compliance/index.ts` | Ar komatiem atdalīti API atslēgu ID, kuriem netiek veikta pieprasījumu reģistrēšana (VDAR atbilstības nodrošināšanai). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nav iestatīts = neierobežots)_ | `src/shared/utils/apiKeyPolicy.ts` | Rezerves dienas pieprasījumu limits, kas tiek piemērots API atslēgām, kuru kolonnas `rate_limits` vērtība ir null. Nav iestatīts vai ir tukšs: nav netieša ierobežojuma (#2289, #11017). `0` nozīmē to pašu (neierobežots). Pozitīvs vesels skaitlis N iespējo N dienā, 5N nedēļā un 20N mēnesī. Nepareizi formatētām vērtībām, kas nav tukšas, tiek izmantoti mantotie ierobežojumi: 1000 dienā, 5000 nedēļā un 20000 mēnesī. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimālais atļautais pieprasījuma pamatdaļas izmērs. Noraida lietderīgās slodzes, kas pārsniedz šo ierobežojumu. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiskās pieprasījumu pamatdaļas, kuru izmērs sasniedz vai pārsniedz šo slieksni, pirms JSON parsēšanas iegūst atomāru, lokālu procesam un resursietilpīgiem pieprasījumiem paredzētu uzņemšanas nomu (BYTE ceļš, tostarp `POST /v1/responses`). Tiek izmantots tas pats [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) veselīgas brīvās jaudas izņēmums kā strukturāli sarežģītiem pieprasījumiem; to joprojām ierobežo `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), lai [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) neatkārtotos. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Tērzēšanas maršruta stingrais ierobežojums, kas tiek piemērots ierobežotas ievades laikā nolasīto baitu skaitam, tostarp pieprasījumiem ar trūkstošu, nederīgu vai nepatiesu `Content-Length`; pārsniegšanas gadījumā tiek atgriezts `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nav iestatīts — nav pieprasījumu skaita ierobežojuma)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** šis mantotais pieprasījumu SKAITA ierobežojums tagad ir saistošs tikai tad, ja tas ir nepārprotami iestatīts. Ja tas nav iestatīts (noklusējums), resursietilpīgu tērzēšanas pieprasījumu uzņemšanu tā vietā ierobežo `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automātiski atvasināts BAITU budžets, kura lielumu nosaka procesa reālais atmiņas ierobežojums **vienā procesā** (vienā V8 kaudzē). Divi vienlaicīgi `/v1/responses` pieprasījumi ar ~750 tūkst. marķieru izraisa ~12 GiB kaudžu avāriju (#7849) — tas ir **atmiņas budžeta** brīdinājums, nevis stingrs produkta maksimums 2. Vesels process (kaudze zem slodzes nomešanas attiecības) VAR uzņemt vairāk vienlaicīgu ilgu `/v1/responses` pieprasījumu, izmantojot `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Desmitiem ilgu SSE klientu (40–50) nosaka kaudze + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nevis „maksimums 2”. Akla šīs vērtības palielināšana, lai „izmantotu resursdatoru”, no jauna izraisa #7849. Pavairojiet **kaudzes**, izmantojot **N neatkarīgus `DATA_DIR`** (#11024); nekad nelietojiet `replicas>1` vienam SQLite failam. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automātiski atvasināts)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** automātiski atvasinātā ievades baitu budžeta pārrakstīšana (25% no stingrākā V8/cgroup atmiņas ierobežojuma, dalīti ar 8x pārejošās pastiprināšanas koeficientu). Atvasinātās un tieši norādītās vērtības tiek ierobežotas diapazonā no 8 MiB līdz 2 GiB. Ķermenis, kas pārsniedz efektīvo budžetu, nekavējoties saņem kļūdu `413 body_exceeds_budget`; konkurence starp ķermeņiem, kurus katru atsevišķi var apkalpot, joprojām izraisa atkārtojamu `503`. 40–50 vienlaicīgus ilgus SSE klientus nosaka šis budžets + kaudze, nevis stingrs „maksimums 2”. Pirms regulēšanas skatiet `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` adresē `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Kaudzes spiediena slodzes nomešanas attiecība (`heapUsed / heap_size_limit`) BAITU un STRUKTŪRAS resursietilpīgu pieprasījumu uzņemšanai (#10183, #10268, #10437). Vienlaicīgs resursietilpīgs pieprasījums virs `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` tiek noraidīts ar atkārtojamu `503` tikai tad, ja kaudze ARĪ ir sasniegusi vai pārsniegusi šo attiecību; ja kaudze ir veselīga, pieprasījums tiek uzņemts, izmantojot veselīgās kaudzes rezerves kapacitāti. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (noklusējums `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ierobežota papildu kapacitāte veselīgas kaudzes ātrajam ceļam (#10437) gan STRUKTŪRAI, gan BAITIEM (`admitChatRequest`, tostarp ķermeņiem ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez šī ierobežojuma katrs aizņemts, bet ar veselīgu kaudzi saistīts pieprasījums apietu uzņemšanas kontroli bez jebkādas augšējās robežas. Kad veselīgas kaudzes ceļā ir aktīvs šāds vienlaicīgu nomu skaits, turpmākie pieprasījumi aizņemtības laikā pāriet uz TO PAŠU ierobežotās gaidīšanas/slodzes nomešanas ceļu, ko izmanto reāla kaudzes spiediena apstākļos. `0` pilnībā atspējo apiešanu. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Ziņojumu skaits, pie kura tērzēšanas pieprasījums tiek klasificēts kā resursietilpīgs pat tad, ja tā ķermenis nepārsniedz baitu slieksni. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Rīku skaits, pie kura tērzēšanas pieprasījums tiek klasificēts kā resursietilpīgs pat tad, ja tā ķermenis nepārsniedz baitu slieksni. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservatīvs uz virknes lielumu balstīts marķieru aprēķins, pēc kura pieprasījums tiek klasificēts kā resursietilpīgs; tas ir uzņemšanas izmaksu aizstājējrādītājs, nevis pakalpojumu sniedzēja norēķinu marķierizācija. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (atspējots) | `src/shared/middleware/chatBodyAdmission.ts` | Neobligāts, apzināti iespējojams tērzēšanas vēstures ierobežojums. Pēc noklusējuma atspējots: ziņojumu skaits ir izvietošanas politika, nevis universāla pieprasījuma īpašība, un ierobežošana šeit noraida sarunas ar galīgu `413`, pirms saspiešanas konveijers var tās padarīt apkalpojamas. Kaudzes pieaugumu ierobežo `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` un kaudzes spiediena slodzes nomešana. Atmiņas ziņā ierobežotos izvietojumos, kuriem nepieciešama stingra augšējā robeža, iestatiet pozitīvu vērtību; pārsniegšanas gadījumā tiek atgriezts strukturēts `413`, kas norāda uz nepieciešamu saspiešanu. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Stingrs ierobežojums nestraumējošai augšupstraumes atbildei, kas tiek pilnībā buferēta atmiņā. To pārsniedzot, augšupstraumes lasītājs tiek atcelts un pieprasījums ātri beidzas ar kļūdu, nevis turpina audzēt neierobežotu virkni, līdz kaudze ir izsmelta. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimālais no augšupējās atbildes galvenēm pārsūtāmo datu apjoms baitos. Kad limits tiek pārsniegts, zemākas prioritātes galvenes (piem., pielāgotās `x-codex-*`, `x-oai-request-id`) tiek atmestas, lai nepārsniegtu izplatītos reverso starpniekserveru galveņu ierobežojumus. Iestatiet lielāku vērtību, lai pārsūtītu vairāk augšupējo metadatu, palielinot atbildes galveņu izmēru. | +| `CORS_ORIGIN` | _(nav iestatīts)_ | `src/server/cors/origins.ts` | Novecojis vienas izcelsmes CORS atļauto izcelsmju saraksts. Jauniem izvietojumiem ieteicams izmantot `CORS_ALLOWED_ORIGINS`. CORS ir paredzēts tikai citas izcelsmes pārlūkprogrammu API klientiem; autentificēti informācijas paneļa rakstīšanas pieprasījumi tā vietā izmanto tās pašas izcelsmes pieprasījumus kopā ar sesijai piesaistītu CSRF aizsardzību. | +| `CORS_ALLOWED_ORIGINS` | _(nav iestatīts)_ | `src/server/cors/origins.ts` | Ar komatiem atdalīts CORS atļauto izcelsmju saraksts. Aizstājējzīme netiek nosūtīta, ja vien nav nepārprotami konfigurēts `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Tikai izstrādei paredzēta atkāpšanās iespēja, kas ļauj atspoguļot jebkuru pārlūkprogrammas `Origin`. Neiespējojiet koplietojamos vai produkcijas izvietojumos. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Novecojis aizstājvārds. Tā pārslēgu informācijas panelī (DB pārrakstīšanas vērtību) nolasa pirms šīs vides vērtības; `false`, `0`, `no` vai `off` jebkurā no abiem izslēdz resursdatoru pārbaudes tāpat, kā to dara `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, pat ja šī karoga pārslēgs informācijas panelī ir saglabāts izslēgts; pārējās vērtības atstāj aizsargu divu zemāk esošo karogo ziņā. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Izslēdz izejošo URL aizsarga resursdatoru pārbaudes, tostarp mākoņa metadatu bloķēšanu (viss 169.254.0.0/16, metadata.google.internal), pakalpojumu sniedzēju ceļos, kas seko aizsarga režīmam: URL validācija, modeļu atklāšana, pakalpojumu sniedzēju mezglu bāzes URL (tostarp attālie pārkārtošanas mezgli) un starpniekservera atkāpšanās tests, kā arī citos ar pakalpojumu sniedzējiem saistītos izejošajos ceļos, piemēram, attālinātā attēlu/mediju iegūšanā. Tas atļauj arī privātus webhook mērķus, izņemot mākoņa metadatu/lokālās saites galapunktus, kas paliek bloķēti arī ar iespējotu karogu. Tērzēšanas pieprasījumiem, kas tiek nosūtīti caur `BaseExecutor.execute()`, saglabājas to mākoņa metadatu pārbaude. Vietējie pakalpojumu sniedzēji darbojas ar noklusējuma iestatījumiem: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (noklusējums `true`) jau atļauj vietējos un LAN URL validācijas, atklāšanas un pakalpojumu sniedzēju mezglu ceļos; starpniekservera atkāpšanās tests un privātie webhook mērķi izmanto tikai šo karogu un bloķē vietējos/LAN resursdatorus, kamēr tas ir izslēgts. Pārraksta `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. No informācijas paneļa pārslēga saglabātai vērtībai ir priekšroka pirms šī mainīgā. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Atļauj pakalpojumu sniedzēju URL lokālās/privātās adresēs (127.0.0.1, localhost, LAN, privātie diapazoni) pakalpojumu sniedzēju validācijai, modeļu atklāšanai, tērzēšanai un pārējiem ar pakalpojumu sniedzējiem saistītajiem izejošajiem ceļiem, ko pārvalda aizsarga režīms. **Noklusējums ir `true`** (prioritāte lokālajam): aizsargs tad bloķē mākoņa metadatu galapunktus — visu 169.254.0.0/16 plus zināmos metadatu resursdatoru nosaukumus (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Iestatiet `false` tikai publisko adrešu režīmam, kas bloķē arī privātos un atgriezeniskās cilpas resursdatorus. Aizsargs pārbauda resursdatora nosaukumu vai IP adresi burtiski, kā tas uzrakstīts, pirms jebkāda DNS vaicājuma; tikai publisko adrešu režīmā drošās iegūšanas (safe-fetch) apvalks papildus atrisina resursdatora nosaukumu un noraida jebkuru privātu atbildi. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` izslēdz šīs pārbaudes ceļos, ko uzskaita tā rinda. Iebūvētie vietējie pakalpojumu sniedzēji, piemēram, LM Studio, Ollama, vLLM, Llamafile un Triton, izlaiž visas resursdatoru pārbaudes atslēgu validācijai un tērzēšanai. No informācijas paneļa pārslēga saglabātai vērtībai ir priekšroka pirms šī mainīgā. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ļauj `/v1/audio/*` maršrutiem (transkripcijai, runai, tulkošanai) izmantot ar OpenAI saderīgu pakalpojumu sniedzēja mezglu, kas mitināts ārpus localhost. Pēc noklusējuma izslēgts — audio maršrutēšana uz attālu resursdatoru maina izejošās datplūsmas identitāti, tādēļ tam jābūt nepārprotamam operatora lēmumam. Atgriezeniskās cilpas/privātie mezgli (localhost, 127.0.0.1, 172.16-31.x) vienmēr ir atļauti, un šis iestatījums tos neietekmē. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ļauj `POST /v1/rerank` (un atmiņas dzinēja atgriezeniskās cilpas pārkārtošanas darbībai) izmantot ar OpenAI saderīgu pakalpojumu sniedzēja mezglu, kas mitināts ārpus localhost — LAN ierīci vai Tailscale vienranga mezglu, kurā darbojas TEI, Infinity, vLLM utt. Pēc noklusējuma izslēgts — maršrutēšana uz attālu resursdatoru maina izejošās datplūsmas identitāti, tādēļ tam jābūt nepārprotamam operatora lēmumam. Atgriezeniskās cilpas mezgli (localhost, 127.0.0.1, 172.16-31.x) vienmēr ir atļauti, un šis iestatījums tos neietekmē. Attālā mezgla bāzes URL arī jāiziet cauri pakalpojumu sniedzēju izejošo URL politikai (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kad OIDC ir iespējots, atspējo pieteikšanos ar paroli, lai lietotāji varētu autentificēties tikai ar OIDC vienoto pierakstīšanos. Tiek pieņemts arī vienkāršais aizstājvārds `OIDC_DISABLE_PASSWORD_LOGIN`; informācijas paneļa funkcijas karogam ar tādu pašu atslēgu ir augstāka prioritāte. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Vienkāršais `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` aizstājvārds (#10889). | ### Drošības pastiprināšanas kontrolsaraksts diff --git a/docs/i18n/ml/CONTRIBUTING.md b/docs/i18n/ml/CONTRIBUTING.md index 63053805f4fc..af40eea32b4e 100644 --- a/docs/i18n/ml/CONTRIBUTING.md +++ b/docs/i18n/ml/CONTRIBUTING.md @@ -394,22 +394,22 @@ Handler/executor-കൾക്കുള്ളിൽ, ക്ലയന്റില --- -## Pull Request ചെക്ക്ലിസ്റ്റ് +## പുൾ റിക്വസ്റ്റ് ചെക്ക്ലിസ്റ്റ് - [ ] ടെസ്റ്റുകൾ വിജയിക്കുന്നു (`npm test`) -- [ ] Linting വിജയിക്കുന്നു (`npm run lint`) -- [ ] Build വിജയിക്കുന്നു (`npm run build`) -- [ ] പുതിയ പൊതു function-ുകൾക്കും interface-ുകൾക്കും TypeScript type-ുകൾ ചേർത്തിട്ടുണ്ട് -- [ ] Hardcode ചെയ്ത secret-ുകളോ fallback മൂല്യങ്ങളോ ഇല്ല -- [ ] പൊതു അപ്സ്ട്രീം ക്രെഡൻഷ്യലുകൾ `resolvePublicCred()` വഴി ഉൾച്ചേർത്തിട്ടുണ്ട് ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) കാണുക), ഒരിക്കലും literal-ുകളായി ഉൾപ്പെടുത്തിയിട്ടില്ല -- [ ] പിശക് response-ുകൾ `buildErrorBody()` / `sanitizeErrorMessage()` വഴി കടന്നുപോകുന്നു — response body-കളിൽ അസംസ്കൃത stack trace-ുകൾ ഇല്ല ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) കാണുക) -- [ ] Shell command-ുകൾ (`exec` / `spawn`) runtime മൂല്യങ്ങൾ string interpolation വഴിയല്ല, `env` വഴിയാണ് കൈമാറുന്നത് -- [ ] എല്ലാ input-ുകളും Zod schema-കൾ ഉപയോഗിച്ച് സാധൂകരിച്ചിട്ടുണ്ട് -- [ ] ഉപയോക്താവിനെ ബാധിക്കുന്ന മാറ്റങ്ങൾക്ക് `changelog.d/{features|fixes|maintenance}/-.md`-ന് കീഴിൽ changelog **fragment** ചേർത്തിട്ടുണ്ട് ([`changelog.d/README.md`](./changelog.d/README.md) കാണുക) — `CHANGELOG.md` നേരിട്ട് തിരുത്തരുത്; release സമയത്ത് fragment-ുകൾ സമാഹരിക്കപ്പെടുന്നതിനാൽ PR-ുകൾക്കിടയിൽ ഒരിക്കലും conflict ഉണ്ടാകില്ല -- [ ] ഡോക്യുമെന്റേഷൻ പുതുക്കിയിട്ടുണ്ട് (ബാധകമെങ്കിൽ) -- [ ] പുതിയ CodeQL / Secret-Scanning alert-ുകൾ തുറന്നിട്ടില്ല, അല്ലെങ്കിൽ പ്രസക്തമായ `docs/security/` ഡോക്യുമെന്റ് പരാമർശിക്കുന്ന സാങ്കേതിക ന്യായീകരണത്തോടെ ഓരോന്നും നിരസിച്ചിട്ടുണ്ട് -- [ ] Child process-ുകൾ spawn ചെയ്യുന്ന route-ുകൾ (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts`-ൽ `isLocalOnlyPath()` ആയി വർഗ്ഗീകരിച്ചിട്ടുണ്ട് — [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md) കാണുക -- [ ] Commit സന്ദേശങ്ങളിൽ `Co-Authored-By` trailer-ുകൾ ഇല്ല — commit-ുകൾ repository ഉടമയുടെ Git identity-യിൽ മാത്രം ദൃശ്യമാകണം (Hard Rule #16) +- [ ] ലിന്റിംഗ് വിജയിക്കുന്നു (`npm run lint`) +- [ ] ബിൽഡ് വിജയിക്കുന്നു (`npm run build`) +- [ ] പുതിയ പൊതു ഫങ്ഷനുകൾക്കും ഇന്റർഫേസുകൾക്കും TypeScript ടൈപ്പുകൾ ചേർത്തിട്ടുണ്ട് +- [ ] ഹാർഡ്കോഡ് ചെയ്ത രഹസ്യങ്ങളോ ഫാൾബാക്ക് മൂല്യങ്ങളോ ഇല്ല +- [ ] പൊതു അപ്സ്ട്രീം ക്രെഡൻഷ്യലുകൾ ലിറ്ററലുകളായി ഒരിക്കലും ഉൾപ്പെടുത്താതെ `resolvePublicCred()` വഴി ഉൾപ്പെടുത്തിയിട്ടുണ്ട് ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) കാണുക) +- [ ] പിശക് പ്രതികരണങ്ങൾ `buildErrorBody()` / `sanitizeErrorMessage()` വഴി റൂട്ട് ചെയ്യുന്നു — പ്രതികരണ ബോഡികളിൽ അസംസ്കൃത സ്റ്റാക്ക് ട്രെയ്സുകൾ ഇല്ല ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) കാണുക) +- [ ] ഷെൽ കമാൻഡുകൾ (`exec` / `spawn`) റൺടൈം മൂല്യങ്ങൾ സ്ട്രിങ് ഇന്റർപൊളേഷൻ വഴിയല്ല, `env` വഴി കൈമാറുന്നു +- [ ] എല്ലാ ഇൻപുട്ടുകളും Zod സ്കീമകൾ ഉപയോഗിച്ച് സാധൂകരിച്ചിട്ടുണ്ട് +- [ ] ഉപയോക്താവിന് ദൃശ്യമാകുന്ന മാറ്റങ്ങൾക്കായി `changelog.d/{features|fixes|maintenance}/-.md` എന്നതിനു കീഴിൽ ചേഞ്ച്ലോഗ് **ഫ്രാഗ്മെന്റ്** ചേർത്തിട്ടുണ്ട് ([`changelog.d/README.md`](./changelog.d/README.md) കാണുക) — `CHANGELOG.md` നേരിട്ട് എഡിറ്റ് ചെയ്യരുത്; റിലീസ് സമയത്ത് ഫ്രാഗ്മെന്റുകൾ സമാഹരിക്കപ്പെടുന്നതിനാൽ PR-കൾ തമ്മിൽ ഒരിക്കലും വൈരുദ്ധ്യമുണ്ടാകില്ല +- [ ] ഡോക്യുമെന്റേഷൻ അപ്ഡേറ്റ് ചെയ്തിട്ടുണ്ട് (ബാധകമെങ്കിൽ) +- [ ] പുതിയ CodeQL / Secret-Scanning അലേർട്ടുകളൊന്നും സൃഷ്ടിച്ചിട്ടില്ല, അല്ലെങ്കിൽ പ്രസക്തമായ `docs/security/` ഡോക്യുമെന്റ് പരാമർശിക്കുന്ന സാങ്കേതിക ന്യായീകരണത്തോടെ ഓരോന്നും നിരസിച്ചിട്ടുണ്ട് +- [ ] ചൈൽഡ് പ്രോസസ്സുകൾ സൃഷ്ടിക്കുന്ന റൂട്ടുകൾ (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts`-ൽ `isLocalOnlyPath()` ആയി വർഗ്ഗീകരിച്ചിട്ടുണ്ട് — [കർശന നിയമം #15](docs/security/ROUTE_GUARD_TIERS.md) കാണുക +- [ ] കമ്മിറ്റ് സന്ദേശങ്ങളിൽ AI/ബോട്ട് `Co-authored-by` ട്രെയിലറുകളില്ല (കർശന നിയമം #16) — വീണ്ടും ഉപയോഗിച്ചിട്ടുള്ള പ്രവൃത്തിയുടെ ഉടമകളായ മനുഷ്യ സഹപ്രവർത്തകർക്ക് സാധാരണ `Co-authored-by: Name ` ട്രെയിലറുകൾ ഉപയോഗിച്ച് അംഗീകാരം നൽകിയിട്ടുണ്ട് --- diff --git a/docs/i18n/ml/docs/architecture/QUALITY_GATES.md b/docs/i18n/ml/docs/architecture/QUALITY_GATES.md index de3473e529aa..9c09de08c0ee 100644 --- a/docs/i18n/ml/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ml/docs/architecture/QUALITY_GATES.md @@ -17,198 +17,215 @@ ## ഗേറ്റ് ഇൻവെന്ററിയും നിർവഹണ പ്രൊഫൈലുകളും -### കാൻഡിഡേറ്റ് പ്രവേശനം +### സ്ഥാനാർഥി പ്രവേശനം -CI, Quality Gates വർക്ക്ഫ്ലോകൾ ഓരോന്നും സ്ഥിരമായൊരു വിധി പുറപ്പെടുവിക്കുന്നു: `Gate / CI`, `Gate / Quality`. -അവയുടെ പതിപ്പിട്ട പ്രവേശന നയം ഓരോ അപ്സ്ട്രീം ജോബിനെയും നിർബന്ധിതമോ ഉപദേശപരമോ ആയി പട്ടികപ്പെടുത്തുന്നു. -ബാധകമായ ഒരു നിർബന്ധിത ജോബ് വിജയിക്കണം: ലഭ്യമല്ലാത്തതോ റദ്ദാക്കിയതോ ഒഴിവാക്കിയതോ തീർപ്പാകാത്തതോ അജ്ഞാതമായതോ ആയ ഫലങ്ങൾക്ക് PASS സ്ഥിരീകരിക്കാനാവില്ല. സാധുവായ docs-only അല്ലെങ്കിൽ catalog-only വർഗ്ഗീകരണത്തിന് ഒരു കോഡ് ലെയ്ൻ ബാധകമല്ലാതാക്കാം; -ഒരു draft PR അംഗീകരിക്കപ്പെട്ട കാൻഡിഡേറ്റല്ല. `hotfix` ലേബൽ തെളിവിന്റെ ആവശ്യകത ഒഴിവാക്കുന്നില്ല. +CI, Quality Gates വർക്ക്ഫ്ലോകൾ ഓരോന്നും സ്ഥിരമായ ഒരു വിധി പുറപ്പെടുവിക്കുന്നു: `Gate / CI`, +`Gate / Quality`. അവയുടെ പതിപ്പിട്ട പ്രവേശന നയം, ഓരോ അപ്സ്ട്രീം ജോബിനെയും +നിർബന്ധിതമോ ഉപദേശപരമോ ആയി പട്ടികപ്പെടുത്തുന്നു. ബാധകമായ ഒരു നിർബന്ധിത ജോബ് വിജയിക്കണം: +ലഭ്യമല്ലാത്തത്, റദ്ദാക്കിയത്, ഒഴിവാക്കിയത്, തീർപ്പാകാത്തത്, അജ്ഞാതമായ ഫലങ്ങൾ എന്നിവയ്ക്ക് PASS +സ്ഥാപിക്കാനാവില്ല. സാധുവായ docs-only അല്ലെങ്കിൽ catalog-only വർഗ്ഗീകരണം ഒരു കോഡ് ലെയ്നെ +ബാധകമല്ലാതാക്കാം; ഒരു ഡ്രാഫ്റ്റ് PR സ്വീകാര്യമായ സ്ഥാനാർഥിയല്ല. `hotfix` ലേബൽ തെളിവിന്റെ +ആവശ്യകത ഒഴിവാക്കുന്നില്ല. രണ്ട് വർക്ക്ഫ്ലോകളും PR-കൾ, main/release ബ്രാഞ്ചുകളിലേക്കുള്ള പുഷുകൾ, മാനുവൽ ഡിസ്പാച്ച്, -merge-group ഇവന്റുകൾ എന്നിവ ഉൾക്കൊള്ളുന്നു. Push, dispatch, merge-group എന്നിവ പൂർണ്ണമായ തിരഞ്ഞെടുപ്പ് പ്രവർത്തിപ്പിക്കുന്നു. സാധാരണയായി self-hosted റണ്ണറുകൾ തിരഞ്ഞെടുക്കുന്ന ജോബുകൾക്ക് forks-ഉം merge groups-ഉം hosted റണ്ണറുകൾ ഉപയോഗിക്കുന്നു; റോളൗട്ടിന് മുമ്പ് മതിയായ hosted ശേഷി ഉറപ്പാക്കണം. - -ഓരോ JSON രസീതും ചെക്ക്ഔട്ട് ചെയ്ത SHA, വർക്ക്ഫ്ലോ റൺ, ശ്രമം എന്നിവ തിരിച്ചറിയുന്നു. -ചെക്ക്ഔട്ട്/event SHA പൊരുത്തക്കേട് CLI നിരസിക്കുന്നു. ഒരു പുതിയ ലെയ്നോ നീക്കംചെയ്ത ലെയ്നോ നിശ്ശബ്ദമായി അപ്രത്യക്ഷമാകാതിരിക്കാൻ വർക്ക്ഫ്ലോ ടെസ്റ്റുകൾ പോളിസി അംഗത്വത്തെ വിധി ജോബിന്റെ `needs` ലിസ്റ്റുമായി ബന്ധിപ്പിക്കുന്നു. -രസീതുകൾ അവയുടെ സ്വന്തം വർക്ക്ഫ്ലോയെ മാത്രമാണ് ഉൾക്കൊള്ളുന്നത്; പ്രസിദ്ധീകരണം, ഡിപ്ലോയ്മെന്റ്, നിലവിലുള്ള ഒരു ഉപദേശപരമായ സ്കാനറിന്റെ ആന്തരിക പ്രവർത്തനം എന്നിവയെ ഉൾക്കൊള്ളുന്നില്ല. ബ്രാഞ്ച് നിയമങ്ങളിൽ രണ്ട് ചെക്ക് നാമങ്ങളും സജീവമാക്കുന്നത് ഒരു പ്രത്യേക അഡ്മിനിസ്ട്രേറ്റീവ് മാറ്റമാണ്; ഈ ജോബുകൾ ചേർക്കുന്നതുകൊണ്ട് മാത്രം ഒരു ബ്രാഞ്ച് പരിരക്ഷിക്കപ്പെടുന്നില്ല. +merge-group ഇവന്റുകൾ എന്നിവ ഉൾക്കൊള്ളുന്നു. Push, dispatch, merge-group എന്നിവ പൂർണ്ണമായ +തിരഞ്ഞെടുപ്പ് പ്രവർത്തിപ്പിക്കുന്നു. സാധാരണയായി self-hosted റണ്ണറുകൾ തിരഞ്ഞെടുക്കുന്ന ജോബുകൾക്ക് +ഫോർക്കുകളും merge group-ുകളും hosted റണ്ണറുകൾ ഉപയോഗിക്കുന്നു; പുറത്തിറക്കുന്നതിന് മുമ്പ് +മതിയായ hosted ശേഷി പരിശോധിച്ചുറപ്പിക്കണം. + +ഓരോ JSON രസീതും ചെക്ക് ഔട്ട് ചെയ്ത SHA, വർക്ക്ഫ്ലോ റൺ, ശ്രമം എന്നിവ തിരിച്ചറിയുന്നു. +ചെക്ക്ഔട്ട്/event SHA പൊരുത്തക്കേട് CLI നിരസിക്കുന്നു. പുതിയതോ നീക്കംചെയ്തതോ ആയ ഒരു ലെയ്ൻ +നിശ്ശബ്ദമായി അപ്രത്യക്ഷമാകാതിരിക്കാൻ, വർക്ക്ഫ്ലോ ടെസ്റ്റുകൾ നയത്തിലെ അംഗത്വത്തെ വിധി ജോബിന്റെ +`needs` പട്ടികയുമായി ബന്ധിപ്പിക്കുന്നു. രസീതുകൾ അവയുടെ സ്വന്തം വർക്ക്ഫ്ലോയെ ഉൾക്കൊള്ളുന്നു; +പ്രസിദ്ധീകരണം, വിന്യാസം, അല്ലെങ്കിൽ നിലവിലുള്ള ഒരു ഉപദേശപരമായ സ്കാനറിന്റെ ആന്തരിക പ്രവർത്തനം +എന്നിവയെ അല്ല. ബ്രാഞ്ച് നിയമങ്ങളിൽ രണ്ട് ചെക്ക് പേരുകളും സജീവമാക്കുന്നത് വേറിട്ടൊരു +അഡ്മിനിസ്ട്രേറ്റീവ് മാറ്റമാണ്; ഈ ജോബുകൾ ചേർക്കുന്നതുകൊണ്ടുമാത്രം ഒരു ബ്രാഞ്ചിന് സംരക്ഷണം +ലഭിക്കില്ല. ### സ്റ്റാറ്റിക് സ്കാൻ ഇൻവെന്ററി -പതിപ്പിട്ട npm-alias ഇൻവെന്ററിയും static-scan അംഗത്വവും -`config/quality/gate-manifest.json`-ൽ സ്ഥിതിചെയ്യുന്നു. സ്ക്രിപ്റ്റ് നാമങ്ങളും കൃത്യമായ കമാൻഡുകളും `package.json`-നോട് ഒത്തുനോക്കി സാധൂകരിക്കാൻ `npm run check:gate-manifest` പ്രവർത്തിപ്പിക്കുക; ചേർക്കലുകൾ, നീക്കംചെയ്യലുകൾ, കമാൻഡ് വ്യതിയാനങ്ങൾ എന്നിവ ലോക്കൽ ഹുക്കിനെയും CI-യിലെ മാറ്റ-വർഗ്ഗീകരണ ജോബുകളെയും പരാജയപ്പെടുത്തും. -ഒരു alias എന്നത് ഒരു വർക്ക്ഫ്ലോ ജോബോ matrix instance-ഓ test case-ഓ അല്ല: ഈ എണ്ണങ്ങളെ പരസ്പരം മാറ്റിസ്ഥാപിക്കാവുന്നവയായി അവതരിപ്പിക്കരുത്. - -തിരഞ്ഞെടുത്ത aliases പ്രവർത്തിപ്പിക്കാതെ പരിശോധിക്കാൻ `npm run quality:scan -- --list` അല്ലെങ്കിൽ `npm run quality:scan:fast -- --list` ഉപയോഗിക്കുക. -റണ്ണർ npm entrypoint പ്രവർത്തിപ്പിക്കുന്നതിനാൽ അതിന്റെ runtime—കോൺഫിഗർ ചെയ്തിടത്ത് Bun ഉൾപ്പെടെ—നിലനിർത്തപ്പെടുന്നു. -ആ പ്രൊഫൈലുകൾക്ക് പുറത്തുള്ള aliases പ്രത്യേകം പ്രവർത്തിപ്പിക്കുന്നവയായി manifest രേഖപ്പെടുത്തുന്നു; read-only scan പ്രൊഫൈലുകളിൽ maintenance കമാൻഡുകൾ നിരോധിച്ചിരിക്കുന്നു. - -ഈ പ്രൊഫൈലുകൾ static scan മാത്രമാണ് ഉൾക്കൊള്ളുന്നത്. അവ product tests, -coverage, packaging, external checks, അല്ലെങ്കിൽ ഒരു കാൻഡിഡേറ്റിന്റെ സമ്പൂർണ്ണ release acceptance എന്നിവ സാക്ഷ്യപ്പെടുത്തുന്നില്ല. -വർക്ക്ഫ്ലോ പ്രവേശനം ലിങ്ക് ചെയ്ത `config/quality/admission-policy.json`, -`scripts/quality/admission-verdict.mjs` എന്നിവ ഉപയോഗിക്കുന്നു. Release-observer പ്രൊഫൈലുകൾ വേറിട്ടുതന്നെ തുടരുന്നു; -അവയുടെ ബാധകമായ ചെക്കുകളും രസീതുകളും സ്വതന്ത്രമായി പരിശോധിക്കുക. താഴെയുള്ള വിവരണാത്മക -ഇൻവെന്ററി ഒരു റഫറൻസാണ്, ഒരു ഗേറ്റ് യഥാർത്ഥത്തിൽ പ്രവർത്തിച്ചു എന്നതിനുള്ള തെളിവല്ല. - -സ്ക്രിപ്റ്റുകൾ `scripts/check/`-ലും (policy gates) `scripts/quality/`-ലും (ratchet engine) സ്ഥിതിചെയ്യുന്നു. -CI-യുടെ ആധികാരിക ഉറവിടം `.github/workflows/ci.yml` ആണ്. - -### Release PR ഫാസ്റ്റ്-പാത്ത് (`quality.yml`) - -`.github/workflows/quality.yml`, main/release PR-കൾ, protected-branch -pushes, dispatch, merge groups എന്നിവയിൽ CI-യെ പൂരകമാക്കുന്നു. PR-കൾ path-filtered fast checks ഉപയോഗിക്കുന്നു. സ്ഥിരമായി പ്രവർത്തനരഹിതമാക്കിയിരുന്ന duplicate build നീക്കംചെയ്തു; യഥാർത്ഥ build/package/boot ചെക്കുകൾ CI-യിൽ തുടരുന്നു. - -| ജോബ് | പരിധി | തടയുന്നത് | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -| `Docs Gates (fast-path)` | Docs/code PR-കൾ; API docs refs, docs-all | അതെ | -| `Fast Quality Gates` | Code PR-കൾ; static checks, typecheck, dashboard typecheck, ബാധിക്കപ്പെട്ട unit tests | അതെ | -| `Forgotten sibling tests` | Code PR-കൾ; മാറ്റിയ modules-ൽ നിന്ന് static consumers-ലേക്കും candidate sibling tests-ലേക്കും ട്രെയ്സ് ചെയ്യുന്നു; barrel, dynamic-import paths എന്നിവ referenced allowlist exceptions സഹിതം ഉപദേശപരമായ diagnostics ആയി റിപ്പോർട്ട് ചെയ്യുന്നു | **ഉപദേശപരം** | -| `Vitest (fast-path)` | Code PR-കൾ; fast vitest suite | അതെ | -| `Unit Tests fast-path` | Code PR-കൾ; 4-shard unit suite | അതെ | -| `No new ESLint warnings` | Code PR-കൾ; suppressions-aware lint guard | അതെ, forks ഉൾപ്പെടെ | -| `Merge integrity (changelog + generated skills)` | Non-draft PR-കൾ; changelog, generated skill sync | അതെ, forks ഉൾപ്പെടെ | - -#### മറന്നുപോയ sibling tests റിപ്പോർട്ട് - -`npm run check:forgotten-sibling-tests`, test-impact map-ന് പിന്നിലുള്ള import resolver പുനരുപയോഗിക്കുന്നു. -മാറ്റം വരുത്തിയ ഓരോ production module-നും, candidate -test pull-request diff-ൽ ഇല്ലെങ്കിൽ നിർണ്ണായകമായ -`changed module/symbol -> static consumer -> candidate sibling test` ശൃംഖലകൾ ഇത് റിപ്പോർട്ട് ചെയ്യുന്നു. ഏതെങ്കിലും blocking rollout-ന് മുമ്പുള്ള calibration-നായി Markdown സംഗ്രഹവും JSON ഫലവും -`forgotten-sibling-tests` വർക്ക്ഫ്ലോ artifact ആയി നിലനിർത്തുന്നു. +പതിപ്പിട്ട npm-alias ഇൻവെന്ററിയും സ്റ്റാറ്റിക്-സ്കാൻ അംഗത്വവും +`config/quality/gate-manifest.json`-ലാണ്. സ്ക്രിപ്റ്റ് പേരുകളും കൃത്യമായ കമാൻഡുകളും +`package.json`-നോട് ഒത്തുനോക്കി സാധൂകരിക്കാൻ `npm run check:gate-manifest` പ്രവർത്തിപ്പിക്കുക; +കൂട്ടിച്ചേർക്കലുകൾ, നീക്കംചെയ്യലുകൾ, കമാൻഡ് വ്യതിയാനം എന്നിവ ലോക്കൽ ഹുക്കിനെയും CI-യിലെ +മാറ്റ-വർഗ്ഗീകരണ ജോബുകളെയും പരാജയപ്പെടുത്തും. ഒരു alias എന്നത് വർക്ക്ഫ്ലോ ജോബോ, മാട്രിക്സ് +ഇൻസ്റ്റൻസോ, ടെസ്റ്റ് കേസോ അല്ല: ഈ എണ്ണങ്ങൾ പരസ്പരം മാറ്റിസ്ഥാപിക്കാവുന്നവയായി അവതരിപ്പിക്കരുത്. + +തിരഞ്ഞെടുത്ത alias-കൾ പ്രവർത്തിപ്പിക്കാതെ പരിശോധിക്കാൻ `npm run quality:scan -- --list` +അല്ലെങ്കിൽ `npm run quality:scan:fast -- --list` ഉപയോഗിക്കുക. റണ്ണർ npm എൻട്രിപോയിന്റ് +വിളിക്കുന്നതിനാൽ, അതിന്റെ റൺടൈം—കോൺഫിഗർ ചെയ്തിടത്ത് Bun ഉൾപ്പെടെ—നിലനിർത്തപ്പെടുന്നു. +ഈ പ്രൊഫൈലുകൾക്ക് പുറത്തുള്ള alias-കൾ പ്രത്യേകം അഭ്യർത്ഥിക്കപ്പെടുന്നവയായി മാനിഫെസ്റ്റ് +രേഖപ്പെടുത്തുന്നു; read-only സ്കാൻ പ്രൊഫൈലുകളിൽ പരിപാലന കമാൻഡുകൾ അനുവദനീയമല്ല. + +ഈ പ്രൊഫൈലുകൾ സ്റ്റാറ്റിക് സ്കാൻ മാത്രമാണ് ഉൾക്കൊള്ളുന്നത്. ഉൽപ്പന്ന ടെസ്റ്റുകൾ, കവറേജ്, +പാക്കേജിംഗ്, ബാഹ്യ പരിശോധനകൾ, അല്ലെങ്കിൽ ഒരു സ്ഥാനാർഥിയുടെ പൂർണ്ണ റിലീസ് സ്വീകാര്യത +എന്നിവയ്ക്ക് ഇവ സാക്ഷ്യപത്രം നൽകുന്നില്ല. വർക്ക്ഫ്ലോ പ്രവേശനം ബന്ധിപ്പിച്ചിരിക്കുന്ന +`config/quality/admission-policy.json`, `scripts/quality/admission-verdict.mjs` എന്നിവ +ഉപയോഗിക്കുന്നു. Release-observer പ്രൊഫൈലുകൾ വേറിട്ടുതന്നെ തുടരുന്നു; അവയ്ക്ക് ബാധകമായ +പരിശോധനകളും രസീതുകളും സ്വതന്ത്രമായി പരിശോധിക്കുക. ചുവടെയുള്ള ഗദ്യരൂപത്തിലുള്ള ഇൻവെന്ററി +ഒരു റഫറൻസാണ്, ഒരു ഗേറ്റ് യഥാർഥത്തിൽ പ്രവർത്തിച്ചു എന്നതിന്റെ തെളിവല്ല. + +സ്ക്രിപ്റ്റുകൾ `scripts/check/`-ലും (നയ ഗേറ്റുകൾ) `scripts/quality/`-ലും (റാച്ചറ്റ് എൻജിൻ) +സ്ഥിതിചെയ്യുന്നു. CI-യുടെ ആധികാരിക ഉറവിടം `.github/workflows/ci.yml` ആണ്. + +### റിലീസ് PR ഫാസ്റ്റ്-പാത്ത് (`quality.yml`) + +`.github/workflows/quality.yml`, main/release PR-കളിലും സംരക്ഷിത-ബ്രാഞ്ച് പുഷുകളിലും +dispatch, merge group എന്നിവയിലും CI-യെ പൂരകമാക്കുന്നു. PR-കൾ പാത്ത്-ഫിൽട്ടർ ചെയ്ത വേഗത്തിലുള്ള +പരിശോധനകൾ ഉപയോഗിക്കുന്നു. സ്ഥിരമായി പ്രവർത്തനരഹിതമാക്കിയിരുന്ന ഡ്യൂപ്ലിക്കേറ്റ് ബിൽഡ് +നീക്കംചെയ്തു; യഥാർഥ build/package/boot പരിശോധനകൾ CI-യിൽ തുടരുന്നു. + +| ജോബ് | വ്യാപ്തി | തടസ്സപ്പെടുത്തുന്നത് | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | Docs/code PR-കൾ; API docs റഫറൻസുകളും docs-all-ഉം | അതെ | +| `Fast Quality Gates` | Code PR-കൾ; സ്റ്റാറ്റിക് പരിശോധനകൾ, typecheck, dashboard typecheck, ബാധിക്കപ്പെട്ട unit ടെസ്റ്റുകൾ | അതെ | +| `Forgotten sibling tests` | Code PR-കൾ; മാറ്റംവന്ന മൊഡ്യൂളുകളിൽ നിന്ന് സ്റ്റാറ്റിക് ഉപഭോക്താക്കളിലേക്കും സാധ്യതയുള്ള sibling ടെസ്റ്റുകളിലേക്കും പിന്തുടരുന്നു; barrel, dynamic-import പാത്തുകൾ allowlist-ൽ പരാമർശിച്ചിട്ടുള്ള ഒഴിവാക്കലുകളോടെ ഉപദേശപരമായ ഡയഗ്നോസ്റ്റിക്കുകളായി റിപ്പോർട്ട് ചെയ്യുന്നു | **ഉപദേശപരം** | +| `Vitest (fast-path)` | Code PR-കൾ; വേഗത്തിലുള്ള vitest സ്യൂട്ട് | അതെ | +| `Unit Tests fast-path` | Code PR-കൾ; 4-shard unit സ്യൂട്ട് | അതെ | +| `No new ESLint warnings` | Code PR-കൾ; suppression-കളെ പരിഗണിക്കുന്ന lint ഗാർഡ് | അതെ, forks ഉൾപ്പെടെ | +| `Merge integrity (changelog + generated skills)` | ഡ്രാഫ്റ്റ് അല്ലാത്ത PR-കൾ; changelog, generated skill സമന്വയം | അതെ, forks ഉൾപ്പെടെ | + +#### മറന്നുപോയ sibling ടെസ്റ്റുകളുടെ റിപ്പോർട്ട് + +`npm run check:forgotten-sibling-tests`, test-impact മാപ്പിന് പിന്നിലുള്ള import resolver +വീണ്ടും ഉപയോഗിക്കുന്നു. മാറ്റംവന്ന ഓരോ production മൊഡ്യൂളിനും, സാധ്യതയുള്ള ടെസ്റ്റ് +pull-request diff-ൽ ഇല്ലാത്തപ്പോൾ, നിർണായകമായ +`changed module/symbol -> static consumer -> candidate sibling test` ശൃംഖലകൾ അത് റിപ്പോർട്ട് +ചെയ്യുന്നു. തടസ്സപ്പെടുത്തുന്ന രീതിയിലുള്ള ഏതെങ്കിലും rollout-നു മുമ്പുള്ള കാലിബ്രേഷനായി, +Markdown സംഗ്രഹവും JSON ഫലവും `forgotten-sibling-tests` വർക്ക്ഫ്ലോ ആർട്ടിഫാക്റ്റായി +നിലനിർത്തുന്നു. Barrel re-export-ുകളും dynamic import-ുകളും resolution diagnostics മാത്രമാണ്; അവ ഒരിക്കലും -blocking finding സൃഷ്ടിക്കില്ല. അവലോകനം ചെയ്ത exceptions -`config/quality/forgotten-sibling-allowlist.json`-ൽ നിലനിൽക്കുന്നു. ഓരോ entry-യും consumer-നെയും candidate -test-നെയും വ്യക്തമാക്കുകയും, നിർദ്ദിഷ്ടമായ ഒരു ന്യായീകരണം നൽകുകയും, ഒരു GitHub issue-ലേക്കോ pull request-ലേക്കോ link ചെയ്യുകയും വേണം. തെറ്റായ ഘടനയിലുള്ള entries -അനുവദിക്കപ്പെടില്ല. ഇല്ലാതാക്കിയ candidate test-നെയോ `.skip`/`.todo` ചേർക്കുന്ന diff-നെയോ exceptions-ന് ഒഴിവാക്കാനാവില്ല; -assertion ദുർബലപ്പെടുത്തലും മറ്റ് masking രീതികളും സ്വതന്ത്രമായി blocking ചെയ്യുന്ന -`check:test-masking` gate-ന്റെ നിയന്ത്രണത്തിൽ തുടരും. +തടസ്സപ്പെടുത്തുന്ന finding സൃഷ്ടിക്കില്ല. അവലോകനം ചെയ്ത exceptions +`config/quality/forgotten-sibling-allowlist.json`-ലാണ്. ഓരോ entry-യും consumer-ഉം candidate +test-ഉം വ്യക്തമാക്കുകയും, കൃത്യമായ കാരണം നൽകുകയും, ഒരു GitHub issue അല്ലെങ്കിൽ pull request-ലേക്ക് link ചെയ്യുകയും വേണം. തെറ്റായ രൂപത്തിലുള്ള entries fail +closed ചെയ്യും. ഇല്ലാതാക്കിയ candidate test-നെയോ `.skip`/`.todo` ചേർക്കുന്ന diff-നെയോ exceptions-ന് suppress ചെയ്യാനാവില്ല; +assertion ദുർബലപ്പെടുത്തലും മറ്റ് masking-ഉം സ്വതന്ത്രമായി merge തടയുന്ന +`check:test-masking` gate-ന്റെ പരിധിയിൽ തുടരും. ### Job: `lint` `main`-ലേക്കുള്ള ഓരോ PR-ലും പ്രവർത്തിക്കുന്നു. പരാജയപ്പെട്ടാൽ merge തടയുന്നു. -| Script (`npm run ...`) | പരിശോധിക്കുന്നത് | Blocking | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | Node.js version പിന്തുണയ്ക്കുന്ന പരിധിക്കുള്ളിലാണോ എന്ന് | അതെ | -| `check:cycles` | Circular import-ുകൾ — എല്ലാ `src/` + `open-sse/` module-ുകളും | അതെ | -| `check:route-validation:t06` | എല്ലാ route-ുകളിലും Zod schema-കൾ ഉണ്ടോ എന്ന് (Tier 6 policy) | അതെ | -| `check:any-budget:t11` | `@ts-expect-error // any`-യുടെ എണ്ണം budget കവിയുന്നില്ലെന്ന് (Tier 11 catraca) | അതെ | -| `check:provider-consistency` | `providers.ts`-ലെ ഓരോ provider-നും `providerRegistry.ts`-ൽ പൊരുത്തപ്പെടുന്ന ഒരു entry ഉണ്ട് (അതുപോലെ മറിച്ചും, allowlist-ന്റെ പരിധിക്കുള്ളിൽ) | ഉണ്ട് | -| `check:model-lifecycle` | കൈകൊണ്ട് പരിപാലിക്കുന്ന മൂന്ന് routing table-കൾ checked-in lifecycle snapshot-ുമായി (#11503) സ്ഥിരത പുലർത്തുന്നു: `REGISTRY`-ക്ക് route ചെയ്യാനാകുന്ന retired id ഒന്നിനും `FITNESS_TABLE` (`taskFitness.ts`) score നൽകുന്നില്ല; എല്ലാ `BUILT_IN_ALIASES` target-ുകളും `REGISTRY`-യിൽ ഉണ്ടായിരിക്കുകയും retired-id snapshot-ൽ ഇല്ലാതിരിക്കുകയും ചെയ്യുന്നു; ഇപ്പോഴും `REGISTRY`-യിലുള്ള എല്ലാ retired id-കളും forward ചെയ്യപ്പെട്ടവയോ `allowedRetiredInCatalog`-ൽ ചേർത്തവയോ ആണ്; കൂടാതെ `DEFAULT_DEGRADATION_MAP`-ലെ source-കളോ target-ുകളോ ആ snapshot-ൽ retired ആയി കാണപ്പെടുന്നില്ല. ഒരു model നിലവിൽ live upstream വഴി സേവിക്കപ്പെടുന്നു എന്ന് ഇത് തെളിയിക്കുന്നില്ല. Offline — `config/quality/model-lifecycle.json`-മായി താരതമ്യം ചെയ്യുന്നു; ഇത് `npm run quality:refresh-model-lifecycle` ഉപയോഗിച്ച് കൈകൊണ്ട് പുതുക്കുന്നു (network; CI-യുമായി ബന്ധിപ്പിച്ചിട്ടില്ല). `allowedRetiredInCatalog` ഒരു burn-down ratchet ആണ്: tracking issue ഉണ്ടെങ്കിൽ മാത്രം ഒരു entry ചേർക്കുക. | ഉണ്ട് | -| `check:fetch-targets` | client-side `src/`-ലെ ഓരോ `fetch("/api/...")`-ഉം യഥാർത്ഥ `route.ts`-ലേക്ക് resolve ചെയ്യുന്നു | ഉണ്ട് | -| `check:deps` | repo-യിലെ എല്ലാ `package.json`-കളിലുമുള്ള `npm install` ചെയ്യാവുന്ന എല്ലാ deps-ഉം `dependency-allowlist.json`-ൽ ഉണ്ട്; പുതുതായി ചേർത്ത unpinned അല്ലെങ്കിൽ slopsquatted package-ുകൾ flag ചെയ്യപ്പെടുന്നു | ഉണ്ട് | -| `audit:deps` | `npm audit` (root + electron) — high/critical advisory-കൾ ഇല്ല (osv `check:vuln-ratchet`-മായി overlap ചെയ്യുന്നു; Rationalization Backlog കാണുക) | ഉണ്ട് | -| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hash-ുകൾ, host override-ുകൾ ഇല്ല | ഉണ്ട് | -| `check:licenses` | പ്രൊഡക്ഷൻ ഡിപെൻഡൻസികൾക്കായുള്ള SPDX ലൈസൻസ് അനുവദനീയ പട്ടിക | അതെ | -| `check:tracked-artifacts` | ബിൽഡ് ആർട്ടിഫാക്റ്റുകളോ കമ്മിറ്റ് ചെയ്ത `node_modules` സിംലിങ്കുകളോ ഇല്ല (husky pre-commit-ലും പ്രവർത്തിക്കുന്നു; pre-push മനഃപൂർവം ലഘുവാക്കിയിരിക്കുന്നു — #6716) | അതെ | -| `check:ai-attribution` | PR കമ്മിറ്റുകളിലോ ശീർഷകത്തിലോ ബോഡിയിലോ AI/bot `Co-Authored-By` ട്രെയിലറോ AI-ജനറേഷൻ ഫൂട്ടറോ ഇല്ല — കർശന നിയമം #16 (`quality.yml`-ലെ PR→`release/**` fast-gates ലൂപ്പിൽ — ഇവന്റ് പേലോഡ് വായിക്കുന്നു, PR അല്ലാത്തവയിൽ ഒന്നും ചെയ്യുന്നില്ല — കൂടാതെ PR→`main`-നുള്ള `ci.yml` lint-ലെ PR-മാത്രം ഘട്ടത്തിലും; husky `commit-msg` ഹുക്കിലും; മനുഷ്യ സഹ-രചയിതാക്കളെ അനുവദിച്ചിരിക്കുന്നു; #14436) | -| `check:vitest-exclusions` | ഓരോ Vitest ഒഴിവാക്കലും ഒരു ട്രാക്കിംഗ് ഇഷ്യൂ വ്യക്തമാക്കുകയും `config/quality/vitest-exclusions.json`-ൽ ഉൾപ്പെട്ടിരിക്കുകയും വേണം (#13204) | അതെ | -| `check:file-size` | ഒരു സോഴ്സ് ഫയലും ഓരോ എക്സ്റ്റൻഷനുമുള്ള പരിധി കവിയരുത് (റാച്ചറ്റ്: ഫ്രീസ് ചെയ്ത വലിയ ഫയലുകൾ `frozen` പട്ടികയിൽ) | അതെ | -| `check:error-helper` | executors/handlers-ലെ പിശക് റെസ്പോൺസുകൾ `buildErrorBody()` / `sanitizeErrorMessage()` ഉപയോഗിക്കുന്നു (കർശന നിയമം #12) | അതെ | -| `check:migration-numbering` | Migration SQL ഫയലുകൾ ഇടവേളകളോ ആവർത്തനങ്ങളോ ഇല്ലാതെ ക്രമാനുഗതമായി നമ്പർ ചെയ്തിരിക്കുന്നു | അതെ | -| `check:public-creds` | `publicCreds.ts`-ന് പുറത്ത് ലിറ്ററൽ OAuth `client_id`/`client_secret` അല്ലെങ്കിൽ Firebase Web കീകൾ ഇല്ല (കർശന നിയമം #11) | അതെ | -| `check:db-rules` | `src/lib/db/` മൊഡ്യൂളുകൾക്ക് പുറത്ത് അസംസ്കൃത SQL ഇല്ല; `localDb.ts`-ൽ നിന്ന് ബാരൽ-ഇംപോർട്ടുകൾ ഇല്ല (കർശന നിയമങ്ങൾ #2/#5) | അതെ | -| `check:known-symbols` | അവയുടെ ഡിസ്പാച്ച് പട്ടികകളിൽ രജിസ്റ്റർ ചെയ്തിരിക്കുന്ന പ്രൊവൈഡർ എക്സിക്യൂട്ടറുകൾ, റൂട്ടിംഗ് തന്ത്രങ്ങൾ, ട്രാൻസ്ലേറ്ററുകൾ എന്നിവ ഡിസ്കിലെ ഫയലുകളുമായി പൊരുത്തപ്പെടുന്നു — അനാഥമായതോ പ്രഖ്യാപിക്കാത്തതോ ആയ സിംബലുകൾ ഇല്ല | അതെ | -| `check:route-guard-membership` | ചൈൽഡ് പ്രോസസ് ആരംഭിക്കുന്ന ഓരോ റൂട്ടും `isLocalOnlyPath()` ഉപയോഗിച്ച് വർഗ്ഗീകരിച്ചിരിക്കുന്നു (കർശന നിയമങ്ങൾ #15/#17) | അതെ | -| `check:test-discovery` | റിപ്പോസിറ്ററിയിലെ ഓരോ `*.test.ts` / `*.spec.ts` ഫയലും കുറഞ്ഞത് ഒരു ടെസ്റ്റ് റണ്ണറെങ്കിലും ശേഖരിക്കുന്നു (റാച്ചറ്റ്: `test-discovery-baseline.json`-ലെ അനാഥ ഫയലുകളുടെ പട്ടികയ്ക്ക് ചുരുങ്ങാൻ മാത്രമേ കഴിയൂ) | അതെ | +| Script (`npm run ...`) | പരിശോധിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Node.js പതിപ്പ് പിന്തുണയ്ക്കുന്ന പരിധിക്കുള്ളിലാണെന്ന് | അതെ | +| `check:cycles` | `src/` + `open-sse/` മുഴുവനിലുമുള്ള circular import-ുകൾ (AST-അധിഷ്ഠിതം, tsconfig `paths` resolve ചെയ്തവ). Bare = advisory, cycle-ുകൾ പട്ടികപ്പെടുത്തുന്നു. `check:cycles:ratchet` (CI പ്രവർത്തിപ്പിക്കുന്നത്) `quality-baseline.json`-ലെ `metrics.cycles` പരിധിയെ എണ്ണം കവിയുമ്പോൾ തടയുന്നു — നിലവിൽ 14, `direction: down`, അതിനാൽ അത് കുറയാൻ മാത്രമേ കഴിയൂ (#15159 G-01/G-02) | അതെ (ratchet) | +| `check:route-validation:t06` | എല്ലാ route-ുകളിലും Zod schema-കൾ ഉണ്ടെന്ന് (Tier 6 policy) | അതെ | +| `check:any-budget:t11` | `@ts-expect-error // any` എണ്ണം budget കവിയുന്നില്ലെന്ന് (Tier 11 catraca) | അതെ | +| `check:provider-consistency` | `providers.ts`-ലെ ഓരോ provider-നും `providerRegistry.ts`-ൽ പൊരുത്തപ്പെടുന്ന ഒരു entry ഉണ്ട് (അതുപോലെ തിരിച്ചും, allowlist-ന്റെ പരിധിക്കുള്ളിൽ) | അതെ | +| `check:model-lifecycle` | കൈകൊണ്ട് പരിപാലിക്കുന്ന മൂന്ന് routing table-കൾ checked-in lifecycle snapshot-മായി (#11503) സ്ഥിരത പുലർത്തുന്നു: `REGISTRY`-ക്ക് route ചെയ്യാൻ കഴിയുന്ന retired id ഒന്നിനും `FITNESS_TABLE` (`taskFitness.ts`) score നൽകുന്നില്ല; ഓരോ `BUILT_IN_ALIASES` target-ഉം `REGISTRY`-യിൽ ഉണ്ടായിരിക്കുകയും retired-id snapshot-ൽ ഇല്ലാതിരിക്കുകയും ചെയ്യുന്നു; `REGISTRY`-യിൽ ഇപ്പോഴും ഉള്ള ഓരോ retired id-യും forward ചെയ്യപ്പെട്ടതോ `allowedRetiredInCatalog`-ൽ ലിസ്റ്റ് ചെയ്തതോ ആണ്; കൂടാതെ `DEFAULT_DEGRADATION_MAP`-ലെ source-ഓ target-ഓ ആ snapshot-ൽ retired ആയി കാണപ്പെടുന്നില്ല. ഒരു model നിലവിൽ live upstream വഴി serve ചെയ്യപ്പെടുന്നു എന്ന് ഇത് തെളിയിക്കുന്നില്ല. Offline — `config/quality/model-lifecycle.json`-മായി താരതമ്യം ചെയ്യുന്നു; ഇത് `npm run quality:refresh-model-lifecycle` ഉപയോഗിച്ച് കൈകൊണ്ട് പുതുക്കുന്നു (network; CI-യുമായി ബന്ധിപ്പിച്ചിട്ടില്ല). `allowedRetiredInCatalog` ഒരു burn-down ratchet ആണ്: tracking issue ഉണ്ടെങ്കിൽ മാത്രം ഒരു entry ചേർക്കുക. | അതെ | +| `check:fetch-targets` | client-side `src/`-ലെ ഓരോ `fetch("/api/...")`-ഉം യഥാർത്ഥ `route.ts`-ലേക്ക് resolve ചെയ്യുന്നു | അതെ | +| `check:deps` | repo-യിലെ ഓരോ `package.json`-ലുമുള്ള `npm install` ചെയ്യാവുന്ന എല്ലാ deps-ഉം `dependency-allowlist.json`-ൽ ഉണ്ട്; pin ചെയ്യാത്തതോ slopsquat ചെയ്തതോ ആയ പുതിയ package-കൾ flag ചെയ്യപ്പെടുന്നു | അതെ | +| `audit:deps` | `npm audit` (root + electron) — high/critical advisory-കൾ ഒന്നുമില്ല (osv `check:vuln-ratchet`-മായി overlap ചെയ്യുന്നു; Rationalization Backlog കാണുക) | അതെ | +| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hash-കൾ, host override-കൾ ഇല്ല | അതെ | +| `check:licenses` | പ്രൊഡക്ഷൻ ഡിപെൻഡൻസികൾക്കായുള്ള SPDX ലൈസൻസ് അനുവദനീയ പട്ടിക | അതെ | +| `check:tracked-artifacts` | ബിൽഡ് ആർട്ടിഫാക്റ്റുകളോ കമ്മിറ്റ് ചെയ്ത `node_modules` സിംലിങ്കുകളോ ഇല്ല (`husky` pre-commit-ലും പ്രവർത്തിക്കുന്നു; pre-push മനഃപൂർവം ലഘുവാക്കിയിരിക്കുന്നു — #6716) | അതെ | +| `check:ai-attribution` | PR കമ്മിറ്റുകളിലോ ശീർഷകത്തിലോ ബോഡിയിലോ AI/bot `Co-Authored-By` ട്രെയിലറോ AI-ജനറേഷൻ ഫൂട്ടറോ ഇല്ല — കർശന നിയമം #16 (`quality.yml`-ലെ PR→`release/**` fast-gates ലൂപ്പിൽ — ഇവന്റ് പേലോഡ് വായിക്കുന്നു, PR അല്ലെങ്കിൽ ഒന്നും ചെയ്യുന്നില്ല — കൂടാതെ PR→`main` എന്നതിനായുള്ള `ci.yml` lint-ലെ PR-മാത്രമായ ഘട്ടത്തിലും; `husky` `commit-msg` ഹുക്കിലും; മനുഷ്യ സഹ-രചയിതാക്കളെ അനുവദിച്ചിരിക്കുന്നു; #14436) | +| `check:vitest-exclusions` | ഓരോ Vitest ഒഴിവാക്കലും ഒരു ട്രാക്കിംഗ് ഇഷ്യൂ വ്യക്തമാക്കുകയും `config/quality/vitest-exclusions.json`-ൽ ഉൾപ്പെടുകയും ചെയ്യുന്നു (#13204) | അതെ | +| `check:file-size` | ഒരു സോഴ്സ് ഫയലും ഓരോ എക്സ്റ്റൻഷനുമുള്ള പരമാവധി പരിധി കവിയുന്നില്ല (റാച്ചറ്റ്: `frozen` പട്ടികയിലെ സ്ഥിരപ്പെടുത്തിയ വലിയ ഫയലുകൾ) | അതെ | +| `check:error-helper` | executors/handlers-ലെ പിശക് പ്രതികരണങ്ങൾ `buildErrorBody()` / `sanitizeErrorMessage()` ഉപയോഗിക്കുന്നു (കർശന നിയമം #12) | അതെ | +| `check:migration-numbering` | Migration SQL ഫയലുകൾ വിടവുകളോ തനിപ്പകർപ്പുകളോ ഇല്ലാതെ ക്രമാനുഗതമായി നമ്പർ ചെയ്തിരിക്കുന്നു | അതെ | +| `check:public-creds` | `publicCreds.ts`-ന് പുറത്ത് അക്ഷരാർഥത്തിലുള്ള OAuth `client_id`/`client_secret` അല്ലെങ്കിൽ Firebase Web കീകൾ ഇല്ല (കർശന നിയമം #11) | അതെ | +| `check:db-rules` | `src/lib/db/` മൊഡ്യൂളുകൾക്ക് പുറത്ത് അസംസ്കൃത SQL ഇല്ല; `localDb.ts`-ൽ നിന്ന് barrel-import-കൾ ഇല്ല (കർശന നിയമങ്ങൾ #2/#5) | അതെ | +| `check:known-symbols` | അവയുടെ dispatch ടേബിളുകളിൽ രജിസ്റ്റർ ചെയ്തിരിക്കുന്ന provider executors, routing strategies, translators എന്നിവ ഡിസ്കിലുള്ള ഫയലുകളുമായി പൊരുത്തപ്പെടുന്നു — അനാഥമായതോ പ്രഖ്യാപിക്കാത്തതോ ആയ symbols ഇല്ല | അതെ | +| `check:route-guard-membership` | child process സൃഷ്ടിക്കുന്ന എല്ലാ route-കളും `isLocalOnlyPath()` പ്രകാരം വർഗ്ഗീകരിച്ചിരിക്കുന്നു (കർശന നിയമങ്ങൾ #15/#17) | അതെ | +| `check:test-discovery` | repo-യിലെ ഓരോ `*.test.ts` / `*.spec.ts` ഫയലും കുറഞ്ഞത് ഒരു test runner എങ്കിലും ശേഖരിക്കുന്നു (ratchet: `test-discovery-baseline.json`-ലെ orphan list ചുരുങ്ങാൻ മാത്രമേ കഴിയൂ) | അതെ | | `check:agent-skills-sync` | സൃഷ്ടിച്ച agent-skills ആർട്ടിഫാക്റ്റുകൾ അവയുടെ സോഴ്സ് കാറ്റലോഗുമായി പൊരുത്തപ്പെടുന്നു (വ്യതിയാനമില്ല) | -| `check:provider-asset-provenance` | പ്രൊവൈഡർ ലോഗോകൾക്കും/അസറ്റുകൾക്കും രേഖപ്പെടുത്തിയ ഉറവിട എൻട്രി ഉണ്ടായിരിക്കുന്നു | -| `lint:json` | JSON കോൺഫിഗറേഷൻ ഫയലുകൾ പാഴ്സ് ചെയ്യപ്പെടുകയും repo lint നിയമങ്ങൾ പാലിക്കുകയും ചെയ്യുന്നു | -| `typecheck:core` | പിശകുകളില്ലാതെയുള്ള TypeScript കംപൈലേഷൻ (ഉപദേശാത്മക മുന്നറിയിപ്പുകൾ മാത്രം) | അതെ | -| `typecheck:noimplicit:core` | കർശനമായ `noImplicitAny` — ഭാവിയിലേക്കുള്ളത്; മുമ്പേ നിലവിലുള്ള നിരവധി call site-കൾക്ക് ഇപ്പോഴും annotations ആവശ്യമാണ് | **ഉപദേശാത്മകം** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**`-ലേക്ക് പരിമിതപ്പെടുത്തിയ `tsc` (#7033) — `typecheck:core`-ന്റെ പ്രത്യേകം തിരഞ്ഞെടുത്ത 27-ഫയൽ allowlist-ൽ dashboard TSX ഒന്നും ഉൾപ്പെടുന്നില്ല, കൂടാതെ `next build` അതിനെ ഒരിക്കലും type-check ചെയ്യുന്നുമില്ല (`next.config.mjs`-ൽ `ignoreBuildErrors: true` സജ്ജീകരിച്ചിരിക്കുന്നു); അതിനാൽ അവിടെയുള്ള orphaned-identifier regressions (#6625/#6909) CI-ക്ക് അദൃശ്യമായിരുന്നു. മാറ്റങ്ങൾ ഫ്രീസ് ചെയ്ത ഓരോ-ഫയൽ/ഓരോ-TS-code എണ്ണത്തിന്റെ baseline-നോട് (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols`-ലേതുപോലുള്ള അതേ stale-enforcement രീതി) താരതമ്യം ചെയ്യുന്നു — baseline ചെയ്ത എണ്ണത്തിനപ്പുറമുള്ള പുതിയ പിശകുകൾ മാത്രമാണ് gate പരാജയപ്പെടുത്തുന്നത്; മുമ്പേ നിലവിലുണ്ടായിരുന്ന ഒരു പിശക് പരിഹരിക്കുമ്പോൾ `--update` ഉപയോഗിച്ച് baseline കുറയ്ക്കുക. | അതെ | +| `check:provider-asset-provenance` | Provider ലോഗോകൾക്കും അസറ്റുകൾക്കും രേഖപ്പെടുത്തിയ provenance എൻട്രിയുണ്ട് | +| `lint:json` | JSON കോൺഫിഗ് ഫയലുകൾ പാഴ്സ് ചെയ്യപ്പെടുകയും repo lint നിയമങ്ങൾ പാലിക്കുകയും ചെയ്യുന്നു | +| `typecheck:core` | പിശകുകളില്ലാതെയുള്ള TypeScript കമ്പൈലേഷൻ (ഉപദേശക മുന്നറിയിപ്പുകൾ മാത്രം) | ഉണ്ട് | +| `typecheck:noimplicit:core` | കർശനമായ `noImplicitAny` — ഭാവിയെ ലക്ഷ്യമിട്ടുള്ളത്; മുമ്പേ നിലവിലുള്ള നിരവധി call site-കൾക്ക് ഇപ്പോഴും annotations ആവശ്യമാണ് | **ഉപദേശകം** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**`-ലേക്ക് പരിമിതപ്പെടുത്തിയ `tsc` (#7033) — `typecheck:core`-ന്റെ ശ്രദ്ധാപൂർവം തിരഞ്ഞെടുത്ത 27-ഫയൽ allowlist-ൽ dashboard TSX ഒന്നും ഉൾപ്പെടുന്നില്ല; കൂടാതെ `next build` അതിനെ ഒരിക്കലും type-check ചെയ്യുന്നുമില്ല (`next.config.mjs`-ൽ `ignoreBuildErrors: true` സജ്ജീകരിച്ചിരിക്കുന്നു). അതിനാൽ അവിടെയുള്ള orphaned-identifier regressions (#6625/#6909) CI-ക്ക് ദൃശ്യമായിരുന്നില്ല. ഓരോ ഫയലിലെയും/ഓരോ TS കോഡിലെയും എണ്ണം അടിസ്ഥാനമാക്കിയുള്ള, മാറ്റമില്ലാതെ സൂക്ഷിച്ച baseline-നോട് (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols`-ലെ അതേ stale-enforcement രീതി) വ്യത്യാസങ്ങൾ താരതമ്യം ചെയ്യുന്നു — baseline എണ്ണത്തിന് അപ്പുറമുള്ള പുതിയ പിശകുകൾ മാത്രം gate പരാജയപ്പെടുത്തും; മുമ്പേ നിലവിലുള്ള ഒരു പിശക് പരിഹരിക്കുമ്പോൾ `--update` ഉപയോഗിച്ച് baseline ക്രമേണ കുറയ്ക്കുക. | ഉണ്ട് | ### Job: `quality-gate` `test-coverage`-ന് ശേഷം പ്രവർത്തിക്കുന്നു. പരാജയപ്പെട്ടാൽ merge തടയുന്നു. -| സ്ക്രിപ്റ്റ് | സാധൂകരിക്കുന്നത് | തടയുന്നത് | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | -| `quality:collect` | `quality-metrics.json` സൃഷ്ടിക്കുന്നു (ESLint മുന്നറിയിപ്പുകളുടെ എണ്ണം, ലയിപ്പിച്ച ഷാർഡ് റിപ്പോർട്ടിൽ നിന്നുള്ള കവറേജ്) | അതെ (റാച്ചറ്റിന് മുമ്പുള്ളത്) | -| `quality:ratchet` | `quality-baseline.json`-ലെ ഓരോ മെട്രിക്കും പിന്നോട്ട് പോയിട്ടില്ല (ESLint മുന്നറിയിപ്പുകൾ ≤ ബേസ്ലൈൻ; കവറേജ് ≥ ബേസ്ലൈൻ) | അതെ | -| `check:duplication` | കോഡ് ഡ്യൂപ്ലിക്കേഷൻ (jscpd@4) `quality-baseline.json`-ലെ ബേസ്ലൈൻ കവിയുന്നില്ല | അതെ | -| `check:complexity` | ഫയൽ-തല സൈക്ലോമാറ്റിക് സങ്കീർണ്ണത പരിധി കവിയുന്നില്ല (കോർ ESLint `complexity` + `max-lines-per-function`) | അതെ | -| `check:cognitive-complexity` | കോഗ്നിറ്റീവ് സങ്കീർണ്ണത റാച്ചറ്റ് (`eslint-plugin-sonarjs`) — പ്രത്യേക ESLint പാസ്; CI ഇവ രണ്ടും ലയിപ്പിച്ച് ഒറ്റ `check:complexity-ratchets` ഘട്ടമായി പ്രവർത്തിപ്പിക്കുന്നു | അതെ | -| `check:dead-code` | ഉപയോഗിക്കാത്ത എക്സ്പോർട്ടുകൾ / ഫയലുകൾക്കായുള്ള റാച്ചറ്റ് (knip) ബേസ്ലൈനുമായി താരതമ്യപ്പെടുത്തുമ്പോൾ പിന്നോട്ട് പോകുന്നില്ല | അതെ | -| `check:compression-budget` | കംപ്രഷൻ ബെഞ്ച്മാർക്ക് ബജറ്റ് — ഓരോ എൻജിനിലെയും ടോക്കൺ-സേവിംഗ്സ് കുറഞ്ഞ പരിധികൾ പിന്നോട്ട് പോകരുത് | അതെ | -| `check:type-coverage` | ടൈപ്പ് ചെയ്ത ശതമാനത്തിനുള്ള റാച്ചറ്റ് (`type-coverage`) പിന്നോട്ട് പോകുന്നില്ല; `typecheck:noimplicit:core`-നെ ഏറെക്കുറെ ഉൾക്കൊള്ളുന്നു | അതെ | -| `check:codeql-ratchet` | തുറന്ന CodeQL അലേർട്ടുകളുടെ എണ്ണം പിന്നോട്ട് പോകുന്നില്ല (`gh api` വഴി വായിക്കുന്നു; ടോക്കൺ ഇല്ലെങ്കിൽ പ്രശ്നമില്ലാതെ ഒഴിവാക്കുന്നു) — പുതുക്കൽ ഇടവേളയ്ക്കും മാനുവൽ ട്രിഗറിനും: താഴെയുള്ള "CodeQL റാച്ചറ്റ്" കാണുക | അതെ | - -### ജോബ്: `quality-extended` - -മുഴുവൻ ജോബും ഉപദേശാത്മകമാണ് (`continue-on-error: true`). npm അടിസ്ഥാനമാക്കിയുള്ള റാച്ചറ്റുകൾ -യഥാർത്ഥത്തിൽ പ്രവർത്തിക്കുന്നു; ബാഹ്യ സ്കാനറുകൾ `gh release download` വഴി ഇൻസ്റ്റാൾ ചെയ്യുകയും ഒരു -ബൈനറി ഇപ്പോഴും ലഭ്യമല്ലെങ്കിൽ സ്വയം ഒഴിവാകുകയും ചെയ്യുന്നു (exit 0). - -| സ്ക്രിപ്റ്റ് | സാധൂകരിക്കുന്നത് | തടയുന്നത് | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `check:circular-deps` | സർക്കുലർ ഡിപെൻഡൻസികളില്ല (dpdm) | **ഉപദേശാത്മകം** | -| `check:bundle-size` | ബണ്ടിൽ വലുപ്പം പരിധി കവിയുന്നില്ല | **ഉപദേശാത്മകം** | -| `check:secrets` | രഹസ്യങ്ങളുടെ സ്കാനിംഗ് (gitleaks) — ബൈനറി ഇല്ലെങ്കിൽ ഒഴിവാക്കുന്നു | **ഉപദേശാത്മകം** | -| `check:vuln-ratchet` | ഡിപെൻഡൻസി ദുർബലതകൾ (osv-scanner) പിന്നോട്ട് പോകുന്നില്ല — ബൈനറി ഇല്ലെങ്കിൽ ഒഴിവാക്കുന്നു | **ഉപദേശാത്മകം** | -| `check:workflows` | വർക്ക്ഫ്ലോ ലിന്റ് (actionlint + zizmor); സ്കാനറുകൾ ഇല്ലാത്തതോ തകരാറിലായതോ ആണെങ്കിൽ, റിപ്പോർട്ടുകൾ അസാധുവാണെങ്കിൽ, അല്ലെങ്കിൽ റാച്ചറ്റ് ബേസ്ലൈൻ ഇല്ലെങ്കിൽ INCOMPLETE ആയി പരാജയപ്പെടുന്നു. സാധുവായ കണ്ടെത്തലുകൾ തിരഞ്ഞെടുത്ത കർശന/ഉപദേശാത്മക/റാച്ചറ്റ് നയം പിന്തുടരുന്നു | എക്സിക്യൂഷൻ നിർബന്ധം; CI-യിൽ zizmor റാച്ചറ്റ് തടയും | -| `check:openapi-breaking` | ബേസ് ബ്രാഞ്ചുമായി താരതമ്യപ്പെടുത്തിയുള്ള പൊതു API കരാറിലെ (`openapi.yaml`) ബ്രേക്കിംഗ് മാറ്റങ്ങൾ (oasdiff) — `openapiBreaking=N` സൃഷ്ടിക്കുന്നു; oasdiff ഇല്ലെങ്കിൽ അല്ലെങ്കിൽ ബേസ് സ്പെക് പരിഹരിക്കാനാകുന്നില്ലെങ്കിൽ ഒഴിവാക്കുന്നു | **ഉപദേശാത്മകം** | - -### ജോബ്: `docs-sync-strict` - -`main`-ലേക്കുള്ള ഓരോ PR-ലും പ്രവർത്തിക്കുന്നു. പരാജയപ്പെട്ടാൽ ലയനം തടയുന്നു. - -| സ്ക്രിപ്റ്റ് | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `check:docs-all` | താഴെയുള്ള 6 ഉപ-ഗേറ്റുകൾ ക്രമാനുഗതമായി പ്രവർത്തിപ്പിക്കുന്ന മെറ്റാ-ഗേറ്റ് | അതെ | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt പതിപ്പുകളുടെ സ്ഥിരത | അതെ | -| ↳ `check:docs-counts` | ഗദ്യത്തിലെ എണ്ണങ്ങൾ (പ്രൊവൈഡർമാരുടെ എണ്ണം, മൈഗ്രേഷനുകളുടെ എണ്ണം തുടങ്ങിയവ) യഥാർഥ എണ്ണങ്ങളുടെ റാച്ചറ്റ് പരിധിക്കുള്ളിലാണെന്ന് | അതെ | -| ↳ `check:env-doc-sync` | `.env.example`-ലെ എല്ലാ പരിസ്ഥിതി വേരിയബിളുകളും ഡോക്സ് പട്ടികയിൽ രേഖപ്പെടുത്തിയിട്ടുണ്ടെന്നും തിരിച്ചും അങ്ങനെ തന്നെയാണെന്നും | അതെ | -| ↳ `check:deprecated-versions` | ഡോക്സിൽ ഒഴിവാക്കിയ പതിപ്പ് സ്ട്രിങ്ങുകളൊന്നുമില്ലെന്ന് | അതെ | -| ↳ `check:doc-links` | ഡോക്സിലെ ആന്തരിക markdown ലിങ്കുകൾ യഥാർഥ ഫയലുകളിലേക്ക് പരിഹരിക്കപ്പെടുന്നുവെന്ന് (`[text]`/`(path)` രൂപം) | അതെ | -| ↳ `check:fabricated-docs` | ഡോക്സിൽ ഉദ്ധരിച്ചിരിക്കുന്ന റൂട്ടുകൾ, പരിസ്ഥിതി വേരിയബിളുകൾ, CLI കമാൻഡുകൾ, ഹുക്ക് പേരുകൾ, ഫയൽ പാതകൾ എന്നിവ കോഡ്ബേസിൽ നിലവിലുണ്ടെന്ന്. `--strict` വഴിയുള്ള ഹാർഡ് ഗേറ്റ്; ഫ്ലാഗ് ഇല്ലെങ്കിൽ സോഫ്റ്റ്-ഫെയിൽ. | അതെ (CI-യിൽ `--strict` വഴി) | -| `check:cli-i18n` | CLI കമാൻഡ് സ്ട്രിങ്ങുകൾ എല്ലാ i18n ലോക്കേൽ ഫയലുകളിലും ഉണ്ടെന്ന് | അതെ | -| `check:openapi-coverage` | യഥാർഥ റൂട്ടുകളുടെ റാച്ചറ്റ് ചെയ്ത ഏറ്റവും കുറഞ്ഞ പരിധിയെങ്കിലും OpenAPI സ്പെക് ഉൾക്കൊള്ളുന്നുവെന്ന് | അതെ | -| `check:openapi-security-tiers` | `openapi.yaml`-ലെ സുരക്ഷാ ടയർ വ്യാഖ്യാനങ്ങൾ `routeGuard.ts`-ലെ വർഗ്ഗീകരണങ്ങളുമായി പൊരുത്തപ്പെടുന്നുവെന്ന് | **ഉപദേശാത്മകം** | -| `check:openapi-routes` | `openapi.yaml`-ലെ ഓരോ പാതയും യഥാർഥ `route.ts`-ലേക്ക് പരിഹരിക്കപ്പെടുന്നുവെന്ന് (കെട്ടിച്ചമയ്ക്കൽ തടയൽ) | അതെ | -| `check:docs-symbols` | `docs/**/*.md`-ലെ ഓരോ `/api/...` പരാമർശവും യഥാർഥ `route.ts`-ലേക്ക് പരിഹരിക്കപ്പെടുന്നുവെന്ന് (കെട്ടിച്ചമയ്ക്കൽ തടയൽ) | അതെ | -| `i18n translation drift` | i18n ലോക്കേൽ ഫയലുകളിലെ വിവർത്തനം ചെയ്യാത്ത കീകൾ — മുന്നറിയിപ്പ് മാത്രം | **ഉപദേശാത്മകം** | +| സ്ക്രിപ്റ്റ് | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `quality:collect` | `quality-metrics.json` സൃഷ്ടിക്കുന്നു (ESLint മുന്നറിയിപ്പുകളുടെ എണ്ണം, ലയിപ്പിച്ച shard റിപ്പോർട്ടിൽ നിന്നുള്ള coverage) | അതെ (ratchet-ന് മുമ്പുള്ളത്) | +| `quality:ratchet` | `quality-baseline.json`-ലെ ഓരോ metric-ഉം മോശമായിട്ടില്ല (ESLint മുന്നറിയിപ്പുകൾ ≤ baseline; coverage ≥ baseline) | അതെ | +| `check:duplication` | കോഡ് ആവർത്തനം (jscpd@4) `quality-baseline.json`-ലെ baseline കവിയുന്നില്ല | അതെ | +| `check:complexity` | ഫയൽ-തല cyclomatic complexity പരിധി കവിയുന്നില്ല (core ESLint `complexity` + `max-lines-per-function`) | അതെ | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — പ്രത്യേക ESLint pass; CI ഇവ രണ്ടും ലയിപ്പിച്ച് ഒരൊറ്റ `check:complexity-ratchets` ഘട്ടമായി പ്രവർത്തിപ്പിക്കുന്നു | അതെ | +| `check:dead-code` | ഉപയോഗിക്കാത്ത exports / files ratchet (knip) baseline-നെ അപേക്ഷിച്ച് മോശമാകുന്നില്ല | അതെ | +| `check:compression-budget` | Compression benchmark budget — ഓരോ engine-ന്റെയും token-savings കുറഞ്ഞ പരിധികൾ മോശമാകരുത് | അതെ | +| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) മോശമാകുന്നില്ല; `typecheck:noimplicit:core`-നെ വലിയ തോതിൽ ഉൾക്കൊള്ളുന്നു | അതെ | +| `check:codeql-ratchet` | തുറന്ന CodeQL alert-ുകളുടെ എണ്ണം മോശമാകുന്നില്ല (`gh api` വഴി വായിക്കുന്നു; token ഇല്ലെങ്കിൽ തടസ്സമില്ലാതെ skip ചെയ്യുന്നു) — പുതുക്കൽ ഇടവേളയ്ക്കും manual trigger-നും: താഴെയുള്ള "CodeQL ratchet" കാണുക | അതെ | + +### Job: `quality-extended` + +മുഴുവൻ job-ഉം ഉപദേശാത്മകമാണ് (`continue-on-error: true`). npm അടിസ്ഥാനമാക്കിയുള്ള ratchet-ുകൾ +യഥാർത്ഥത്തിൽ പ്രവർത്തിക്കുന്നു; ബാഹ്യ scanner-ുകൾ `gh release download` വഴി install ചെയ്യുകയും binary +ഇപ്പോഴും ലഭ്യമല്ലെങ്കിൽ സ്വയം skip ചെയ്യുകയും ചെയ്യുന്നു (exit 0). + +| സ്ക്രിപ്റ്റ് | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| `check:circular-deps` | Circular dependency-കൾ ഇല്ല (dpdm) | **ഉപദേശാത്മകം** | +| `check:bundle-size` | Bundle വലുപ്പം പരിധി കവിയുന്നില്ല | **ഉപദേശാത്മകം** | +| `check:secrets` | രഹസ്യവിവരങ്ങൾക്കായുള്ള scanning (gitleaks) — binary ഇല്ലെങ്കിൽ skip ചെയ്യുന്നു | **ഉപദേശാത്മകം** | +| `check:vuln-ratchet` | Dependency vulnerability-കൾ (osv-scanner) മോശമാകുന്നില്ല — binary ഇല്ലെങ്കിൽ skip ചെയ്യുന്നു | **ഉപദേശാത്മകം** | +| `check:workflows` | Workflow lint (actionlint + zizmor); ലഭ്യമല്ലാത്ത/തകരാറിലായ scanner-ുകൾ, അസാധുവായ report-ുകൾ, അല്ലെങ്കിൽ ലഭ്യമല്ലാത്ത ratchet baseline എന്നിവ INCOMPLETE ആയി പരാജയപ്പെടുന്നു. സാധുവായ കണ്ടെത്തലുകൾ തിരഞ്ഞെടുത്ത strict/advisory/ratchet നയം പിന്തുടരുന്നു | നിർവഹണം നിർബന്ധം; CI-യിൽ zizmor ratchet തടസ്സപ്പെടുത്തുന്നു | +| `check:openapi-breaking` | അടിസ്ഥാന branch-നെ അപേക്ഷിച്ച് public API contract-ലുള്ള (`openapi.yaml`) breaking change-ുകൾ (oasdiff) — `openapiBreaking=N` സൃഷ്ടിക്കുന്നു; oasdiff ഇല്ലെങ്കിലോ അടിസ്ഥാന spec പരിഹരിക്കാനാകുന്നില്ലെങ്കിലോ skip ചെയ്യുന്നു | **ഉപദേശാത്മകം** | + +### Job: `docs-sync-strict` + +`main`-ലേക്കുള്ള ഓരോ PR-ലും പ്രവർത്തിക്കുന്നു. പരാജയപ്പെട്ടാൽ merge തടയുന്നു. + +| സ്ക്രിപ്റ്റ് | പരിശോധിക്കുന്നത് | തടയുന്നത് | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `check:docs-all` | താഴെയുള്ള 6 ഉപ-ഗേറ്റുകൾ ക്രമാനുസൃതമായി പ്രവർത്തിപ്പിക്കുന്ന മെറ്റാ-ഗേറ്റ് | അതെ | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt പതിപ്പുകളുടെ സ്ഥിരത | അതെ | +| ↳ `check:docs-counts` | ഗദ്യത്തിലെ എണ്ണങ്ങൾ (പ്രൊവൈഡർ എണ്ണം, മൈഗ്രേഷൻ എണ്ണം മുതലായവ) യഥാർഥ എണ്ണങ്ങളുടെ റാച്ചറ്റ് പരിധിക്കുള്ളിലാണെന്ന് | അതെ | +| ↳ `check:env-doc-sync` | `.env.example`-ലെ എല്ലാ env var-കളും docs പട്ടികയിൽ രേഖപ്പെടുത്തിയിട്ടുണ്ടെന്നും തിരിച്ചും അങ്ങനെ തന്നെയാണെന്നും | അതെ | +| ↳ `check:deprecated-versions` | docs-ൽ ഒഴിവാക്കിയ പതിപ്പ് സ്ട്രിങ്ങുകൾ ഇല്ലെന്ന് | അതെ | +| ↳ `check:doc-links` | docs-ലെ ആന്തരിക markdown ലിങ്കുകൾ യഥാർഥ ഫയലുകളിലേക്ക് പരിഹരിക്കപ്പെടുന്നുവെന്ന് (`[text]`/`(path)` രൂപം) | അതെ | +| ↳ `check:fabricated-docs` | docs-ൽ പരാമർശിച്ച routes, env var-കൾ, CLI കമാൻഡുകൾ, hook പേരുകൾ, file path-കൾ എന്നിവ codebase-ൽ നിലവിലുണ്ടെന്ന്. `--strict` വഴി ഹാർഡ് ഗേറ്റ്; ഫ്ലാഗില്ലെങ്കിൽ സോഫ്റ്റ്-ഫെയിൽ. | അതെ (CI-യിൽ `--strict` വഴി) | +| `check:cli-i18n` | CLI കമാൻഡ് സ്ട്രിങ്ങുകൾ എല്ലാ i18n locale ഫയലുകളിലും ഉണ്ടെന്ന് | അതെ | +| `check:openapi-coverage` | OpenAPI spec യഥാർഥ routes-ന്റെ കുറഞ്ഞത് റാച്ചറ്റ് ചെയ്ത അടിസ്ഥാനപരിധിയെങ്കിലും ഉൾക്കൊള്ളുന്നുവെന്ന് | അതെ | +| `check:openapi-security-tiers` | `openapi.yaml`-ലെ സുരക്ഷാ ടയർ annotations `routeGuard.ts` വർഗ്ഗീകരണങ്ങളുമായി പൊരുത്തപ്പെടുന്നുവെന്ന് | **ഉപദേശാത്മകം** | +| `check:openapi-routes` | `openapi.yaml`-ലെ ഓരോ path-ഉം യഥാർഥ `route.ts`-ലേക്ക് പരിഹരിക്കപ്പെടുന്നുവെന്ന് (വ്യാജസൃഷ്ടി തടയൽ) | അതെ | +| `check:docs-symbols` | `docs/**/*.md`-ലെ ഓരോ `/api/...` പരാമർശവും യഥാർഥ `route.ts`-ലേക്ക് പരിഹരിക്കപ്പെടുന്നുവെന്ന് (വ്യാജസൃഷ്ടി തടയൽ) | അതെ | +| `i18n translation drift` | i18n locale ഫയലുകളിലെ പരിഭാഷപ്പെടുത്താത്ത keys — മുന്നറിയിപ്പ് മാത്രം | **ഉപദേശാത്മകം** | ### ജോലി: `i18n-ui-coverage` -| സ്ക്രിപ്റ്റ് | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | -| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `check-ui-keys-coverage` (ഇൻലൈൻ) | UI i18n കീ കവറേജ് ≥ 65% ആണെന്ന് | അതെ | -| `check-ui-value-drift` (ഇൻലൈൻ) | മാറ്റിയെഴുതിയ ഒരു ഇംഗ്ലീഷ് **മൂല്യം** പഴകിയ വിവർത്തനമൊന്നും അവശേഷിപ്പിക്കുന്നില്ലെന്ന് | അതെ | -| `check-new-key-coverage` (ഇൻലൈൻ) | ഒരു **പുതിയ** ഇംഗ്ലീഷ് കീ എല്ലാ ലോക്കേലുകളിലും വിവർത്തനം ചെയ്തിട്ടുണ്ടെന്ന് — `__MISSING__:` മാർക്കർ നിരസിക്കപ്പെടും | അതെ | -| `check-translation-ratio` | ഓരോ ലോക്കേലിലെയും യഥാർഥ വിവർത്തന അനുപാതം (അനുവദനീയ പട്ടികയ്ക്ക് പുറത്തുള്ള ഇംഗ്ലീഷിനോട് സമാനമായ / പ്ലേസ്ഹോൾഡർ / നഷ്ടപ്പെട്ട ഇലകൾ) `config/quality/i18n-translation-baseline.json` + ഇളവ് കവിയാൻ പാടില്ല | **ഉപദേശാത്മകം** | +| സ്ക്രിപ്റ്റ് | പരിശോധിക്കുന്നത് | തടയുന്നത് | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | +| `check-ui-keys-coverage` (inline) | UI i18n key കവറേജ് ≥ 65% ആണെന്ന് | അതെ | +| `check-ui-value-drift` (inline) | തിരുത്തിയെഴുതിയ ഒരു ഇംഗ്ലീഷ് **value** പഴകിയ പരിഭാഷയൊന്നും ശേഷിപ്പിക്കുന്നില്ലെന്ന് | അതെ | +| `check-new-key-coverage` (inline) | ഒരു **പുതിയ** ഇംഗ്ലീഷ് key എല്ലാ locale-ലും പരിഭാഷപ്പെടുത്തിയിട്ടുണ്ടെന്ന് — `__MISSING__:` marker നിരസിക്കപ്പെടും | അതെ | +| `check-translation-ratio` | ഓരോ locale-ലെയും യഥാർഥ-പരിഭാഷ അനുപാതം (allowlist-ന് പുറത്തുള്ള ഇംഗ്ലീഷിനോട് സമാനമായ / placeholder / നഷ്ടമായ leaves) `config/quality/i18n-translation-baseline.json` + slack കവിയരുത് | **ഉപദേശാത്മകം** | -`fetch-depth: 0` ആവശ്യമാണ് — മൂല്യ-ഡ്രിഫ്റ്റ് ഗേറ്റ് മെർജ് ബേസുമായി താരതമ്യം ചെയ്ത് `en.json`-ന്റെ വ്യത്യാസം പരിശോധിക്കുന്നു. +`fetch-depth: 0` ആവശ്യമാണ് — value-drift ഗേറ്റ് merge base-നെതിരെ `en.json` diff ചെയ്യുന്നു. -#### `check-ui-value-drift` — പഴകിയ വിവർത്തന ഗേറ്റ് +#### `check-ui-value-drift` — പഴകിയ-പരിഭാഷാ ഗേറ്റ് -മറ്റ് ഗേറ്റുകൾക്ക് ഘടനാപരമായി കണ്ടെത്താനാകാത്ത ഏക i18n റിഗ്രഷൻ ഇത് കണ്ടെത്തുന്നു: ഒരു ഇംഗ്ലീഷ് മൂല്യം -മാറ്റിയെഴുതിയിട്ടും _മുൻപത്തെ_ ഇംഗ്ലീഷിൽനിന്ന് സൃഷ്ടിച്ച വിവർത്തനങ്ങൾ അവശേഷിക്കുന്നതിനാൽ, -ഇംഗ്ലീഷ് ഇതര ഉപയോക്താക്കൾ ആത്മവിശ്വാസത്തോടെ എഴുതിയിട്ടുള്ള, എന്നാൽ ഇപ്പോൾ തെറ്റായ പകർപ്പ് തുടർന്നും വായിക്കുന്നു. +മറ്റ് ഗേറ്റുകൾക്ക് ഘടനാപരമായി കാണാൻ കഴിയാത്ത ഒരേയൊരു i18n regression ഇത് കണ്ടെത്തുന്നു: ഒരു ഇംഗ്ലീഷ് value +തിരുത്തിയെഴുതിയിട്ടും _മുമ്പത്തെ_ ഇംഗ്ലീഷ് value-യിൽ നിന്ന് ഉരുത്തിരിഞ്ഞ പരിഭാഷകൾ അവിടെത്തന്നെ ശേഷിക്കുന്നു; അതിനാൽ +ഇംഗ്ലീഷിതര ഉപയോക്താക്കൾ ആത്മവിശ്വാസത്തോടെ എഴുതപ്പെട്ടതെങ്കിലും ഇപ്പോൾ തെറ്റായ ഉള്ളടക്കം തുടർന്നും വായിക്കുന്നു. -ഇത് യഥാർഥത്തിൽ റിലീസ് ചെയ്തിരുന്നു. Antigravity -ലോഗിൻ സഹായി എത്തിയപ്പോൾ (#5203) `oauthModal.googleOAuthWarning` മാറ്റിയെഴുതപ്പെട്ടു; **43 ലോക്കേലുകളിൽ 39 എണ്ണവും** ഓപ്പറേറ്റർമാരോട് "പൂർണ്ണ URL പകർത്തി താഴെ ഒട്ടിക്കുക" എന്ന് പറയുന്ന വാചകം നിലനിർത്തി — ആ പ്രൊവൈഡറിന് പൂർത്തിയാക്കാനാകാത്ത ഒരു പ്രവാഹമാണത്. താഴെ പറയുന്ന കാരണങ്ങളാൽ #8463 വരെ -ഇത് ശ്രദ്ധിക്കപ്പെടാതെ പോയി: +ഇത് യഥാർഥത്തിൽ റിലീസ് ചെയ്യപ്പെട്ടു. Antigravity login helper ചേർത്തപ്പോൾ (#5203) +`oauthModal.googleOAuthWarning` തിരുത്തിയെഴുതപ്പെട്ടു; **43 locale-കളിൽ 39 എണ്ണത്തിലും** ഓപ്പറേറ്റർമാരോട് "പൂർണ്ണ URL +പകർത്തി താഴെ ഒട്ടിക്കുക" എന്ന് പറയുന്ന വാചകം നിലനിന്നു — ആ provider-നായി പൂർത്തിയാക്കാനാകാത്ത ഒരു flow. #8463 വരെ +ഇത് ശ്രദ്ധിക്കപ്പെടാതെ പോയതിന്റെ കാരണങ്ങൾ: -- `sync-ui-keys` **ഇല്ലാത്ത** കീകൾ മാത്രമേ ബാക്ക്ഫിൽ ചെയ്യൂ, **പഴകിയവ** ഒരിക്കലും ചെയ്യില്ല; -- `check-ui-keys-coverage` കീയുടെ _സാന്നിധ്യം_ കണക്കാക്കുന്നതിനാൽ പഴകിയ വിവർത്തനവും കവർ ചെയ്തതായി കണക്കാക്കപ്പെടുന്നു; -- `check-translation-drift` `docs/i18n//**.md` ഡോക്യുമെന്റേഷൻ മിററുകളെയാണ് പിന്തുടരുന്നത് — - അത് ഒരിക്കലും `src/i18n/messages/*.json` വായിക്കുന്നില്ല. 2026-09 പുനഃസമന്വയം മുതൽ `docs-sync-strict` ജോലിയിൽ തടസ്സപ്പെടുത്തുന്നു: ഒരു പ്രധാന ഡോക് തിരുത്തുക → `npm run i18n:run -- --files=` (വിഭാഗ-തലത്തിലുള്ളത്, ചെലവ് കുറഞ്ഞത്). +- `sync-ui-keys` **ഇല്ലാത്ത** keys മാത്രമേ backfill ചെയ്യൂ, **പഴകിയവ** ഒരിക്കലും ചെയ്യില്ല; +- `check-ui-keys-coverage` key-യുടെ _സാന്നിധ്യം_ എണ്ണുന്നതിനാൽ, പഴകിയ പരിഭാഷയും ഉൾപ്പെടുത്തിയതായി കണക്കാക്കപ്പെടുന്നു; +- `check-translation-drift` `docs/i18n//**.md` documentation mirrors ആണ് ട്രാക്ക് ചെയ്യുന്നത് — + അത് ഒരിക്കലും `src/i18n/messages/*.json` വായിക്കുന്നില്ല. 2026-09 re-sync മുതൽ `docs-sync-strict` ജോലിയിൽ തടയുന്നു: + ഒരു core doc തിരുത്തുക → `npm run i18n:run -- --files=` (section-level, ചെലവുകുറഞ്ഞത്). -**വ്യത്യാസം കണക്കിലെടുക്കുന്നു, അടിസ്ഥാനരേഖയെ ആശ്രയിക്കുന്നില്ല.** ഇത് merge base-ലെ `en.json`-നെ -working tree-യുമായി താരതമ്യം ചെയ്യുന്നു; ഇംഗ്ലീഷ് മൂല്യം മാറിയ ഓരോ key-ക്കും, മാറ്റമില്ലാത്ത -വിവർത്തനം ഇപ്പോഴും നിലനിർത്തുന്ന locale കാലഹരണപ്പെട്ടതാണ്. ഇത് മനഃപൂർവം **നിലവിലുള്ള കടബാധ്യതയെ അതേപടി നിർത്തുന്നു** — ദീർഘകാലമായി നിലനിൽക്കുന്ന ഒരു വിവർത്തനം ഏത് പഴയ ഇംഗ്ലീഷ് വാചകത്തിൽനിന്നാണ് വന്നതെന്ന് -ഒരു diff-ന് വെളിപ്പെടുത്താനാകില്ല, അതിനാൽ നിലവിലെ മാറ്റം സ്പർശിക്കുന്നവയെ മാത്രമാണ് gate വിലയിരുത്തുന്നത്. -മറ്റൊരു മാർഗമായ ഓരോ key-ക്കും പ്രത്യേകം hash baseline ഉപയോഗിക്കുന്നതിന്, നിലവിലുള്ള ഏറ്റവും വലിയ baseline-ന്റെ 3× വലുപ്പമുള്ള -~600 KB generated file ആവശ്യമായി വരും; ഓരോ i18n PR-ലും അത് മാറിക്കൊണ്ടിരിക്കുകയും ചെയ്യും. +**Diff-aware ആണ്, baseline-backed അല്ല.** ഇത് merge base-ലെ `en.json`-നെ working tree-യുമായി താരതമ്യം ചെയ്യുന്നു; ഇംഗ്ലീഷ് മൂല്യം മാറിയ ഓരോ key-ക്കും, മാറ്റമൊന്നും വരുത്താത്ത translation ഇപ്പോഴും കൈവശമുള്ള locale കാലഹരണപ്പെട്ടതായി കണക്കാക്കുന്നു. ഇത് മനഃപൂർവം **നിലവിലുള്ള കടബാധ്യതയെ അതേപടി നിർത്തുന്നു** — ദീർഘകാലമായി നിലനിൽക്കുന്ന ഒരു translation ഏത് പഴയ ഇംഗ്ലീഷ് മൂല്യത്തിൽനിന്ന് വന്നതാണെന്ന് diff-ന് വെളിപ്പെടുത്താനാകില്ല; അതിനാൽ നിലവിലെ മാറ്റം സ്പർശിക്കുന്നവയെ മാത്രമാണ് gate വിലയിരുത്തുന്നത്. ബദൽ മാർഗമായ (ഓരോ key-ക്കും hash baseline) ഏകദേശം 600 KB വലുപ്പമുള്ള generated file ആവശ്യപ്പെടും; ഇത് നിലവിലുള്ള ഏറ്റവും വലിയ baseline-ന്റെ 3 മടങ്ങാണ്, കൂടാതെ ഓരോ i18n PR-ലും അനാവശ്യ മാറ്റങ്ങൾ സൃഷ്ടിക്കും. ഇത് പാലിക്കാൻ രണ്ട് മാർഗങ്ങളുണ്ട്: -1. ബാധിക്കപ്പെട്ട വിവർത്തനങ്ങൾ പുതുക്കുക, അല്ലെങ്കിൽ -2. അവയെ `__MISSING__:` ആയി സജ്ജീകരിക്കുക — തുടർന്ന് runtime തിരുത്തിയ ഇംഗ്ലീഷ് നൽകും - (`src/i18n/request.ts::deepMergeFallback`, #7258), കൂടാതെ key വിവർത്തനത്തിനായി queue ചെയ്യപ്പെടും. +1. ബാധിക്കപ്പെട്ട translations പുതുക്കുക, അല്ലെങ്കിൽ +2. അവയെ `__MISSING__:` ആയി സജ്ജീകരിക്കുക — തുടർന്ന് runtime തിരുത്തിയ ഇംഗ്ലീഷ് നൽകുകയും + (`src/i18n/request.ts::deepMergeFallback`, #7258) ആ key translation-നുള്ള queue-യിൽ ചേർക്കുകയും ചെയ്യും. -സ്ട്രിങ്ങിന്റെ **അർത്ഥം** മാറിയിട്ടുണ്ടെങ്കിൽ, **key-യുടെ പേര് മാറ്റുന്നതാണ്** ഉചിതം: ഒരു പുതിയ key-ക്ക് -കാലഹരണപ്പെട്ട വിവർത്തനം പാരമ്പര്യമായി ലഭിക്കാനാവില്ല. #8463 ഉപയോഗിച്ച മാതൃക അതാണ്. +string-ന്റെ **അർത്ഥം** മാറിയിട്ടുണ്ടെങ്കിൽ, **key-യുടെ പേര് മാറ്റുന്നതിന്** മുൻഗണന നൽകുക: ഒരു പുതിയ key-ക്ക് കാലഹരണപ്പെട്ട translation പാരമ്പര്യമായി ലഭിക്കാനാവില്ല. #8463 ഉപയോഗിച്ച മാതൃക അതാണ്. ```bash npm run i18n:check-value-drift # കർശനം (CI പ്രവർത്തിപ്പിക്കുന്നത്) @@ -216,48 +233,47 @@ npm run i18n:check-value-drift:warn # റിപ്പോർട്ട് മ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -അടിസ്ഥാന catalog വായിക്കാനാകാത്തപ്പോൾ (base ref ഇല്ലാത്ത shallow -clone), `check-openapi-breaking`-നെ അനുകരിച്ച് `SKIP reason=base-unresolved` സഹിതം 0 എന്ന നിലയിൽ അവസാനിക്കുന്നു. +base catalog വായിക്കാനാകാത്തപ്പോൾ (base ref ഇല്ലാത്ത shallow clone), `check-openapi-breaking`-ന്റെ പ്രവർത്തനം അനുകരിച്ച് `SKIP reason=base-unresolved` സഹിതം 0 exit code നൽകുന്നു. ### Job: `i18n` -പൂർണ്ണ i18n validation matrix (ഓരോ locale-നും ഓരോ job). മുഴുവൻ job-ഉം ഉപദേശാത്മകമാണ്. +സമ്പൂർണ്ണ i18n validation matrix (ഓരോ locale-നും ഓരോ job). മുഴുവൻ job-വും advisory ആണ്. -| Script | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | -| ------------------------------- | --------------------------------- | ---------------------------------------------------------- | -| `validate_translation.py quick` | ഓരോ locale-ലെയും വിവർത്തന പൂർണ്ണത | **ഉപദേശാത്മകം** (മുഴുവൻ job-ലും `continue-on-error: true`) | +| Script | പരിശോധിക്കുന്നത് | Blocking | +| ------------------------------- | -------------------------------------------- | ------------------------------------------------------- | +| `validate_translation.py quick` | ഓരോ locale-ലെയും translation-ന്റെ സമ്പൂർണ്ണത | **Advisory** (മുഴുവൻ job-ലും `continue-on-error: true`) | ### Job: `pr-test-policy` pull request-ുകളിൽ മാത്രം പ്രവർത്തിക്കുന്നു. -| Script | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, അല്ലെങ്കിൽ `bin/` എന്നിവയിലെ production code മാറ്റുന്ന PR-ുകളിൽ tests ഉൾപ്പെടുത്തുകയോ പുതുക്കുകയോ വേണം (Hard Rule #8) | അതെ | -| `check:test-masking` | മാറ്റിയ test file-ുകൾ മൊത്തം assert എണ്ണം കുറയ്ക്കുകയോ `assert.ok(true)` tautology-കൾ ചേർക്കുകയോ ചെയ്യുന്നില്ല | അതെ | -| `check:pr-evidence` | മാറ്റത്തിനുള്ള test/VPS തെളിവ് PR body-യിൽ ഉദ്ധരിച്ചിരിക്കുന്നു (PR ഗദ്യം grep ചെയ്ത് Hard Rule #18 യാന്ത്രികമാക്കുന്നു — ദുർബലമാണ്, Backlog കാണുക) | അതെ | +| Script | പരിശോധിക്കുന്നത് | Blocking | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, അല്ലെങ്കിൽ `bin/` എന്നിവയിലെ production code മാറ്റുന്ന PR-ുകളിൽ tests ഉൾപ്പെടുത്തുകയോ പുതുക്കുകയോ വേണം (Hard Rule #8) | അതെ | +| `check:test-masking` | മാറ്റിയ test files ആകെ assert count കുറയ്ക്കുകയോ `assert.ok(true)` tautologies ചേർക്കുകയോ ചെയ്യുന്നില്ല | അതെ | +| `check:pr-evidence` | മാറ്റത്തിനുള്ള test/VPS തെളിവുകൾ PR body-ൽ ഉദ്ധരിക്കുന്നു (PR prose grep ചെയ്ത് Hard Rule #18 യാന്ത്രികമാക്കുന്നു — ഇത് ദുർബലമാണ്; Backlog കാണുക) | അതെ | ### Job: `test-vitest` `build`-ന് ശേഷം പ്രവർത്തിക്കുന്നു. പരാജയപ്പെട്ടാൽ merge തടയുന്നു. -| Suite | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | -| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | അതെ | -| `test:vitest:ui` | UI component tests — vitest runner | **തടസ്സപ്പെടുത്തുന്നു** — നിലവിലുള്ള പരാജയങ്ങൾ `vitest.config.ts`-ൽ വ്യക്തമായി ഒഴിവാക്കിയിരിക്കുന്നു; പുതിയ പരാജയങ്ങൾ job പരാജയപ്പെടുത്തും | +| Suite | പരിശോധിക്കുന്നത് | Blocking | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | അതെ | +| `test:vitest:ui` | UI component tests — vitest runner | **Blocking** — മുമ്പേ നിലവിലുള്ള failures `vitest.config.ts`-ൽ വ്യക്തമായി ഒഴിവാക്കിയിരിക്കുന്നു; പുതിയ failures job പരാജയപ്പെടുത്തും | -### രാത്രികാല workflows (ക്രമീകരിച്ചവ, ഉപദേശാത്മകം) +### Nightly workflows (scheduled, advisory) -ഇവ cron schedule-ൽ (കൂടാതെ `workflow_dispatch` വഴിയും) പ്രവർത്തിക്കുന്നു; PR-ുകളിൽ ഒരിക്കലും പ്രവർത്തിക്കില്ല. എല്ലാം ഉപദേശാത്മകമാണ്. +ഇവ cron schedule-ൽ (കൂടാതെ `workflow_dispatch` വഴിയും) പ്രവർത്തിക്കുന്നു; PR-ുകളിൽ ഒരിക്കലും പ്രവർത്തിക്കില്ല. എല്ലാം advisory ആണ്. -| Workflow | സാധൂകരിക്കുന്നത് | തടസ്സപ്പെടുത്തുന്നത് | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `nightly-property` | random seed + ഉയർന്ന run count ഉപയോഗിച്ചുള്ള fast-check property tests | **ഉപദേശാത്മകം** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **ഉപദേശാത്മകം** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret ഇല്ലെങ്കിൽ ഒഴിവാക്കപ്പെടും) | **ഉപദേശാത്മകം** | -| `nightly-schemathesis` | `docs/openapi.yaml` ഉപയോഗിച്ച് live OmniRoute-നെതിരെയുള്ള OpenAPI contract fuzzing (schemathesis) — spec ലംഘനങ്ങൾ / കൈകാര്യം ചെയ്യാത്ത 500-കൾ വെളിപ്പെടുത്തുന്നു (Fase 8 B.4) | **ഉപദേശാത്മകം** | -| `nightly-mutation` | fast unit lane-ലുടനീളമുള്ള Stryker mutation-testing score — അതിജീവിക്കുന്ന mutants ദുർബലമായ asserts വെളിപ്പെടുത്തുന്നു | **ഉപദേശാത്മകം** | -| `nightly-compat` | പിന്തുണയ്ക്കുന്ന `engines.node` ranges-ലുടനീളമുള്ള Node engine compatibility matrix | **ഉപദേശാത്മകം** | +| Workflow | പരിശോധിക്കുന്നത് | Blocking | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `nightly-property` | random seed-ഉം ഉയർന്ന run count-ഉം ഉപയോഗിച്ചുള്ള fast-check property tests | **Advisory** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Advisory** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret ഇല്ലെങ്കിൽ ഒഴിവാക്കപ്പെടും) | **Advisory** | +| `nightly-schemathesis` | `docs/openapi.yaml` ഉപയോഗിച്ച് live OmniRoute-നെതിരെയുള്ള OpenAPI contract fuzzing (schemathesis) — spec ലംഘനങ്ങൾ / കൈകാര്യം ചെയ്യാത്ത 500s വെളിപ്പെടുത്തുന്നു (Fase 8 B.4) | **Advisory** | +| `nightly-mutation` | വേഗത്തിലുള്ള unit lane-ലെ Stryker mutation-testing score — അതിജീവിക്കുന്ന mutants ദുർബലമായ asserts വെളിപ്പെടുത്തുന്നു | **Advisory** | +| `nightly-compat` | പിന്തുണയ്ക്കുന്ന `engines.node` ranges-ലുടനീളമുള്ള Node engine compatibility matrix | **Advisory** | --- @@ -513,41 +529,41 @@ CI ഗേറ്റുകൾക്ക് പുറമേ, OmniRoute ഒരു ** ## യുക്തിസഹമാക്കൽ ബാക്ക്ലോഗ് (ROI അവലോകനം — ഘട്ടം 9 തരംഗം 3) -ഈ ഇൻവെന്ററി 2026-06-17-ന് `ci.yml`-മായി ഒത്തുനോക്കി (മുൻ പതിപ്പിൽ +ഈ ഇൻവെന്ററി 2026-06-17-ന് `ci.yml`-മായി പൊരുത്തപ്പെടുത്തി (മുൻ പതിപ്പിൽ `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` എന്നിവ വിട്ടുപോയിരുന്നു). ഒത്തുനോക്കിയ സെറ്റിന്റെ ROI അവലോകനം -താഴെപ്പറയുന്ന യുക്തിസഹമാക്കൽ സ്ഥാനാർഥികളെ തിരിച്ചറിഞ്ഞു. **ലയിപ്പിക്കലുകൾ യാന്ത്രിക CI -മാറ്റങ്ങളാണ്; ഫ്ലിപ്പുകൾ/ഒഴിവാക്കലുകൾ ഓപ്പറേറ്റർക്കായി നീക്കിവെച്ചിരിക്കുന്ന നയപരമായ തീരുമാനങ്ങളാണ്.** താഴെയുള്ളതൊന്നും -ഇതുവരെ പ്രയോഗിച്ചിട്ടില്ല. +`check:codeql-ratchet`, `check:pr-evidence` എന്നിവ വിട്ടുപോയിരുന്നു). പൊരുത്തപ്പെടുത്തിയ സെറ്റിന്റെ ROI അവലോകനം +താഴെപ്പറയുന്ന യുക്തിസഹമാക്കൽ സ്ഥാനാർഥികളെ കണ്ടെത്തി. **ലയിപ്പിക്കലുകൾ യാന്ത്രിക CI +മാറ്റങ്ങളാണ്; ഫ്ലിപ്പുകൾ/ഒഴിവാക്കലുകൾ ഓപ്പറേറ്റർക്കായി നീക്കിവച്ചിരിക്കുന്ന നയപരമായ തീരുമാനങ്ങളാണ്.** താഴെപ്പറയുന്നവയിൽ +ഒന്നും ഇതുവരെ പ്രയോഗിച്ചിട്ടില്ല. **മുകളിൽ രേഖപ്പെടുത്താത്തവയും** (ഉപദേശാത്മകം, കുറഞ്ഞ സിഗ്നൽ): `docs-lint` ജോലി (markdownlint + Vale, മുഴുവൻ ജോലിക്കും `continue-on-error`) കൂടാതെ സ്വതന്ത്ര സ്കാനർ വർക്ക്ഫ്ലോകളായ -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` -`quality-baseline.json`-ൽ ഉണ്ടെങ്കിലും `ci.yml`-ലെ ഒരു ബ്ലോക്കിംഗ് റാച്ചറ്റുമായി ബന്ധിപ്പിച്ചിട്ടില്ല — ഈ മെട്രിക് +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `quality-baseline.json`-ൽ +`semgrepFindings: 0` ഉണ്ട്, എന്നാൽ `ci.yml`-ലെ ഒരു ബ്ലോക്കിങ് റാച്ചറ്റുമായി അത് ബന്ധിപ്പിച്ചിട്ടില്ല — ഈ മെട്രിക് നിലവിൽ അനാഥമാണ്. -### ലയിപ്പിക്കൽ / ഡീഡ്യൂപ്ലിക്കേഷൻ (യാന്ത്രികം, കുറഞ്ഞ അപകടസാധ്യത) +### ലയിപ്പിക്കൽ / ഡ്യൂപ്ലിക്കേഷൻ നീക്കംചെയ്യൽ (യാന്ത്രികം, കുറഞ്ഞ അപകടസാധ്യത) -ഓരോ സ്ഥാനാർഥിയും 2026-06-17-ലെ തത്സമയ ഗേറ്റ് നിലയ്ക്കെതിരെ സാധൂകരിച്ചു (വിശ്വസിക്കുക, എന്നാൽ പരിശോധിക്കുക); -"വ്യക്തമായത്" എന്ന് തോന്നിയ പല ലയിപ്പിക്കലുകളും കടം മറച്ചുവെച്ചിരുന്നതായി തെളിഞ്ഞു; അവ **ശുദ്ധമായ പകരക്കാരല്ല**. +ഓരോ സ്ഥാനാർഥിയെയും 2026-06-17-ലെ തത്സമയ ഗേറ്റ് നിലയ്ക്കെതിരെ സാധൂകരിച്ചു (വിശ്വസിക്കുക, എന്നാൽ പരിശോധിക്കുക); +"വ്യക്തമായ" പല ലയിപ്പിക്കലുകളും മറഞ്ഞിരിക്കുന്ന സാങ്കേതിക കടം ഒളിപ്പിക്കുന്നതായി കണ്ടെത്തി, അവ **ശുദ്ധമായ പകരക്കാരല്ല**. -- **`check:docs-sync` രണ്ടുതവണ പ്രവർത്തിക്കുന്നു** — `lint` ജോലിയിൽ സ്വതന്ത്രമായും `check:docs-all`-ന്റെ (`docs-sync-strict`) ഉള്ളിലും husky pre-commit ഹുക്കിലും വീണ്ടും. ✅ **പൂർത്തിയായി** — സ്വതന്ത്രമായ `lint` ഇൻവൊക്കേഷൻ നീക്കംചെയ്തു. -- **CVE സ്കാനിംഗ്** — ❌ **ശുദ്ധമായ ലയിപ്പിക്കൽ അല്ല.** ഏതെങ്കിലും ഉയർന്ന/ഗുരുതര CVE ഉണ്ടെങ്കിൽ `audit:deps` കർശനമായി പരാജയപ്പെടും; `check:vuln-ratchet` (osv) ബേസ്ലൈനുമായി താരതമ്യപ്പെടുത്തുമ്പോൾ ഒരു _പിന്നോട്ടുപോക്ക്_ ഉണ്ടായാൽ മാത്രമേ പരാജയപ്പെടൂ (നിലവിൽ 1 MODERATE). വ്യത്യസ്ത സെമാന്റിക്സ് — `audit:deps` ഒഴിവാക്കിയാൽ സമ്പൂർണ്ണമായ ഉയർന്ന/ഗുരുതര ഗേറ്റ് നഷ്ടപ്പെടും. രണ്ടും നിലനിർത്തുക. -- **സൈക്കിൾ കണ്ടെത്തൽ** — ❌ **ശുദ്ധമായ ലയിപ്പിക്കൽ അല്ല.** `check:circular-deps` (dpdm) **91 സൈക്കിളുകൾ** റിപ്പോർട്ട് ചെയ്യുന്നു (അതുകൊണ്ടാണ് ഇത് ഉപദേശാത്മകമായിരിക്കുന്നത്); അവ ആദ്യം പരിഹരിക്കാതെ ഇതിനെ ബ്ലോക്കിംഗിലേക്ക് ഉയർത്താനാവില്ല, കൂടാതെ ഗ്രീൻ നിലയിലുള്ള, ശ്രദ്ധാപൂർവം തിരഞ്ഞെടുത്ത `check:cycles`-നെക്കാൾ വിശാലമായ വ്യാപ്തിയും ഇതിനുണ്ട്. `check:cycles` ബ്ലോക്കിംഗായി നിലനിർത്തുക; 91 dpdm സൈക്കിളുകൾ പരിഹരിക്കുന്നത് വേറൊരു ബാക്ക്ലോഗാണ്. -- **സങ്കീർണ്ണത** — ✅ **പൂർത്തിയായി** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ഒരൊറ്റ ESLint വാക്ക്, ruleId അനുസരിച്ച് എണ്ണുന്നതിനാൽ cyclomatic+max-lines, cognitive ബേസ്ലൈനുകൾ സ്വതന്ത്രമായി തുടരുന്നു; പ്രാദേശിക `--update`-നായി വ്യക്തിഗത `check:complexity` / `check:cognitive-complexity` നിലനിർത്തുന്നു. -- **`/api` ആന്റി-ഹാലൂസിനേഷൻ** — ✅ **പൂർത്തിയായി** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-യുടെ ഒരൊറ്റ FS ഇൻവെന്ററി; openapi-routes + docs-symbols ഇപ്പോഴും സ്വതന്ത്രമായി റിപ്പോർട്ട് ചെയ്യുന്നു; പ്രാദേശിക റണ്ണുകൾക്കായി വ്യക്തിഗതവ നിലനിർത്തുന്നു. -- **`check:node-runtime` 11 ജോലികളിൽ പ്രവർത്തിക്കുന്നു** — ⚠️ **കുറഞ്ഞ ROI.** ഓരോന്നും വേറിട്ട റണ്ണറാണ്, ചെക്ക് <1s മാത്രമാണ്; വിലകുറഞ്ഞ ഓരോ-ജോലി ഗാർഡ് നഷ്ടപ്പെടുത്തുന്നതിന് പകരമായി ലഭിക്കുന്ന ആകെ ലാഭം ~10s മാത്രം. ഈ മാറ്റച്ചെലവിന് തക്കതല്ല. -- **CI lint-ലെ `typecheck:noimplicit:core`** — ✅ **lint ജോലിയിൽ നിന്ന് നീക്കംചെയ്തു** (ഉപദേശാത്മകമായ `continue-on-error` ആയിരുന്നു); ബ്ലോക്കിംഗ് ടൈപ്പ് ഉപരിതലം `typecheck:core` + `check:type-coverage` ആണ്. പ്രാദേശിക സ്ക്രിപ്റ്റ് നിലനിർത്തി. +- **`check:docs-sync` രണ്ടുതവണ പ്രവർത്തിക്കുന്നു** — `lint` ജോലിയിൽ സ്വതന്ത്രമായും `check:docs-all` (`docs-sync-strict`)-ന്റെ ഉള്ളിലും husky pre-commit ഹുക്കിലും വീണ്ടും. ✅ **പൂർത്തിയായി** — സ്വതന്ത്ര `lint` ഇൻവൊക്കേഷൻ നീക്കംചെയ്തു. +- **CVE സ്കാനിങ്** — ❌ **ശുദ്ധമായി ലയിപ്പിക്കാനാകില്ല.** ഏതെങ്കിലും high/critical CVE കണ്ടെത്തിയാൽ `audit:deps` കർശനമായി പരാജയപ്പെടുന്നു; `check:vuln-ratchet` (osv) ബേസ്ലൈനുമായി താരതമ്യപ്പെടുത്തിയുള്ള ഒരു _പിന്നോട്ടുപോക്ക്_ ഉണ്ടായാൽ മാത്രമാണ് പരാജയപ്പെടുന്നത് (നിലവിൽ 1 MODERATE). അർത്ഥശാസ്ത്രം വ്യത്യസ്തമാണ് — `audit:deps` ഒഴിവാക്കിയാൽ സമ്പൂർണ്ണ high/critical ഗേറ്റ് നഷ്ടപ്പെടും. രണ്ടും നിലനിർത്തുക. +- **സൈക്കിൾ കണ്ടെത്തൽ** — ✅ **പൂർത്തിയായി** (#15159 G-01/G-02). ഇവിടെയുള്ള പഴയ വാചകം `check:cycles`-നെ "ഹരിതമായ, ക്യൂറേറ്റ് ചെയ്ത" ഗേറ്റ് എന്ന് വിളിക്കുകയും `check:circular-deps` (dpdm) 91 സൈക്കിളുകൾ റിപ്പോർട്ട് ചെയ്തതിനാൽ അത് ബ്ലോക്കിങ്ങായി നിലനിർത്തുന്നതിനെ ന്യായീകരിക്കുകയും ചെയ്തിരുന്നു. ആ ഹരിതനില ഒരു **വ്യാജ ഹരിതനില** ആയിരുന്നു: `check:cycles` 5 ഉപഡയറക്ടറികൾ (450 ഫയലുകൾ) സ്കാൻ ചെയ്തു, സ്റ്റാറ്റിക് `import|export … from` മാത്രമാണ് പൊരുത്തപ്പെടുത്തിയത്, കൂടാതെ എല്ലാ `@/`, `@omniroute/open-sse/` സ്പെസിഫയറുകളും ഒഴിവാക്കി; അതിനാൽ റിപ്പോയിൽ ആധിപത്യം പുലർത്തിയ dynamic-import + alias സൈക്കിളുകൾ അതിന് കാണാനായില്ല. പരിഹരിച്ചു: ഗേറ്റ് ഇപ്പോൾ `src` + `open-sse` (5023 ഫയലുകൾ) പരിശോധിക്കുന്നു, TypeScript AST-യിൽ നിന്ന് സ്പെസിഫയറുകൾ ശേഖരിക്കുന്നു (അതിനാൽ `import("…")` കണക്കാക്കപ്പെടും, എന്നാൽ type-position `typeof import("…")` കണക്കാക്കപ്പെടില്ല), കൂടാതെ tsconfig `paths` റിസോൾവ് ചെയ്യുന്നു. ഇത് 0 അല്ല, **14** സൈക്കിളുകൾ കണ്ടെത്തുന്നു. നിലവിലുള്ള 14 സൈക്കിളുകൾ ഒരു ഗേറ്റ് PR-ൽ പരിഹരിക്കാനാകാത്തതിനാൽ, `check:cycles` ഇപ്പോൾ ഒരു **റാച്ചറ്റ്** ആണ് (`--ratchet`, `quality-baseline.json`-ൽ പരിധി `metrics.cycles.value = 14`, `direction: down`) — ഏതൊരു _പിന്നോട്ടുപോക്കും_ ഇത് തടയുന്നു, എണ്ണം കുറയുക മാത്രമേ ചെയ്യാനാകൂ. CI `npm run check:cycles:ratchet` പ്രവർത്തിപ്പിക്കുന്നു. കുറച്ചുകൊണ്ടുവരൽ **A-01**-നൊപ്പം നടക്കും. വിശാലമായ രണ്ടാമത്തെ അഭിപ്രായമായി `check:circular-deps` (dpdm) ഉപദേശാത്മകമായി തുടരുന്നു. +- **സങ്കീർണ്ണത** — ✅ **പൂർത്തിയായി** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ഒരൊറ്റ ESLint പരിശോധന, ruleId അനുസരിച്ച് എണ്ണുന്നതിനാൽ cyclomatic+max-lines, cognitive ബേസ്ലൈനുകൾ സ്വതന്ത്രമായി തുടരുന്നു; പ്രാദേശിക `--update`-നായി വ്യക്തിഗത `check:complexity` / `check:cognitive-complexity` നിലനിർത്തിയിട്ടുണ്ട്. +- **`/api` ആന്റി-ഹാലൂസിനേഷൻ** — ✅ **പൂർത്തിയായി** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-യുടെ ഒരൊറ്റ FS ഇൻവെന്ററി, openapi-routes + docs-symbols ഇപ്പോഴും സ്വതന്ത്രമായി റിപ്പോർട്ട് ചെയ്യുന്നു; പ്രാദേശിക റണ്ണുകൾക്കായി വ്യക്തിഗതവ നിലനിർത്തിയിട്ടുണ്ട്. +- **`check:node-runtime` 11 ജോലികളിൽ പ്രവർത്തിക്കുന്നു** — ⚠️ **കുറഞ്ഞ ROI.** ഓരോന്നും പ്രത്യേക റണ്ണറാണ്, പരിശോധന <1s ആണ്; വിലകുറഞ്ഞ ഓരോ-ജോലി സംരക്ഷണവും നഷ്ടപ്പെടുന്നതിന് പകരമായി മൊത്തം ലാഭം ~10s മാത്രം. മാറ്റത്തിന്റെ ബുദ്ധിമുട്ടിന് തക്ക മൂല്യമില്ല. +- **CI lint-ലെ `typecheck:noimplicit:core`** — ✅ **lint ജോലിയിൽ നിന്ന് നീക്കംചെയ്തു** (ഉപദേശാത്മകമായ `continue-on-error` ആയിരുന്നു); ബ്ലോക്കിങ് ടൈപ്പ് ഉപരിതലം `typecheck:core` + `check:type-coverage` ആണ്. പ്രാദേശിക സ്ക്രിപ്റ്റ് നിലനിർത്തി. ### ഫ്ലിപ്പ് / തീരുമാനം (ഓപ്പറേറ്റർ നയം) -- `check:openapi-security-tiers` (ഉപദേശാത്മകം) — ❌ **വൃത്തിയായി ഫ്ലിപ്പ് ചെയ്യാനാവില്ല.** ഇത് 0-യിൽ എക്സിറ്റ് ചെയ്യുന്നു, എന്നാൽ `LOCAL_ONLY_API_PREFIXES`-ന് കീഴിലുള്ള നിരവധി `traffic-inspector` റൂട്ടുകളിൽ `x-loopback-only: true` അനോട്ടേഷൻ ഇല്ലെന്ന് മുന്നറിയിപ്പ് നൽകുന്നു. ഇത് നിർബന്ധമാക്കുന്നതിന് ആദ്യം ആ അനോട്ടേഷനുകൾ `openapi.yaml`-ലേക്ക് ചേർക്കേണ്ടതുണ്ട്. -- `typecheck:noimplicit:core` (ഉപദേശാത്മകം) — ബ്ലോക്കിംഗ് `check:type-coverage` റാച്ചറ്റ് ഇതിനെ വലിയ തോതിൽ ഉൾക്കൊള്ളുന്നു. ഒരു റാച്ചറ്റിലേക്ക് ഫ്ലിപ്പ് ചെയ്യുക, അല്ലെങ്കിൽ ആവർത്തനപരമായ രണ്ടാമത്തെ `tsc` പാസ് ഒഴിവാക്കുക. -- `test:vitest:ui` (ഇപ്പോൾ **ബ്ലോക്കിംഗ്**) — മുമ്പേ ഉണ്ടായിരുന്ന പരാജയങ്ങൾ `vitest.config.ts`-ൽ `// #8618` ട്രാക്കിംഗ് കമന്റുകളോടെ വ്യക്തമായി ഒഴിവാക്കിയിട്ടുണ്ട്; പുതിയ പരാജയങ്ങൾ ജോലിയെ പരാജയപ്പെടുത്തും. -- `check:secrets` (gitleaks, രേഖപ്പെടുത്തിയ 3 തെറ്റായ പോസിറ്റീവുകളിൽ ഫ്രീസ് ചെയ്തിരിക്കുന്ന ബ്ലോക്കിംഗ് റാച്ചറ്റ്) — 0-ൽ എത്താൻ ആ 3 എണ്ണം allowlist ചെയ്യുക, അല്ലെങ്കിൽ ഉപദേശാത്മകമായി താഴ്ത്തുക. GitHub-ന്റെ നേറ്റീവ് secret-scanning + `check:public-creds` എന്നിവയുമായി ഓവർലാപ്പ് ചെയ്യുന്നു. -- `check:pr-evidence` (ബ്ലോക്കിംഗ്, PR-body ഗദ്യം grep ചെയ്യുന്നു) — തെറ്റായ പോസിറ്റീവുകൾക്ക് ഉയർന്ന സാധ്യത; ഒഴിവാക്കിയാൽ Hard Rule #18-ന്റെ നടപ്പാക്കൽ ദുർബലമാകും, അതിനാൽ ഇത് യഥാർഥ നയപരമായ തീരുമാനമാണ്. -- `semgrep` (സ്വതന്ത്രവും ഉപദേശാത്മകവും) — OWASP വിഭാഗങ്ങളിൽ CodeQL-മായി ഓവർലാപ്പ് ചെയ്യുന്നു; അതിന്റെ ബേസ്ലൈൻ ഒരു റാച്ചറ്റുമായി ബന്ധിപ്പിക്കുക, അല്ലെങ്കിൽ ഒഴിവാക്കുക. +- `check:openapi-security-tiers` (ഉപദേശാത്മകം) — ❌ **വൃത്തിയായി ഫ്ലിപ്പ് ചെയ്യാനാകില്ല.** ഇത് 0-ൽ എക്സിറ്റ് ചെയ്യുന്നുവെങ്കിലും `LOCAL_ONLY_API_PREFIXES`-ന്റെ കീഴിലുള്ള നിരവധി `traffic-inspector` റൂട്ടുകളിൽ `x-loopback-only: true` അനോട്ടേഷൻ ഇല്ലെന്ന് മുന്നറിയിപ്പ് നൽകുന്നു. ഇത് നിർബന്ധമാക്കുന്നതിന് ആദ്യം ആ അനോട്ടേഷനുകൾ `openapi.yaml`-ൽ ചേർക്കേണ്ടതുണ്ട്. +- `typecheck:noimplicit:core` (ഉപദേശാത്മകം) — ബ്ലോക്കിങ് `check:type-coverage` റാച്ചറ്റ് ഇതിനെ വലിയൊരളവിൽ ഉൾക്കൊള്ളുന്നു. ഒരു റാച്ചറ്റിലേക്ക് ഫ്ലിപ്പ് ചെയ്യുക, അല്ലെങ്കിൽ ആവർത്തനമായ രണ്ടാമത്തെ `tsc` പാസ് ഒഴിവാക്കുക. +- `test:vitest:ui` (ഇപ്പോൾ **ബ്ലോക്കിങ്**) — മുൻപേ നിലവിലുണ്ടായിരുന്ന പരാജയങ്ങൾ `vitest.config.ts`-ൽ `// #8618` ട്രാക്കിങ് കമന്റുകൾ ഉപയോഗിച്ച് വ്യക്തമായി ഒഴിവാക്കിയിട്ടുണ്ട്; പുതിയ പരാജയങ്ങൾ ജോലി പരാജയപ്പെടുത്തും. +- `check:secrets` (gitleaks, രേഖപ്പെടുത്തിയ 3 false-positive-ുകളിൽ ഫ്രീസ് ചെയ്ത ബ്ലോക്കിങ് റാച്ചറ്റ്) — 0-ൽ എത്താൻ ആ 3 എണ്ണം allowlist ചെയ്യുക, അല്ലെങ്കിൽ ഉപദേശാത്മകമായി തരംതാഴ്ത്തുക. GitHub-ന്റെ നേറ്റീവ് secret-scanning + `check:public-creds` എന്നിവയുമായി ഓവർലാപ്പ് ചെയ്യുന്നു. +- `check:pr-evidence` (ബ്ലോക്കിങ്, PR-body ഗദ്യം greps ചെയ്യുന്നു) — false-positive സാധ്യത ഉയർന്നതാണ്; ഒഴിവാക്കിയാൽ Hard Rule #18 നടപ്പാക്കൽ ദുർബലമാകും, അതിനാൽ ഇത് യഥാർത്ഥ നയപരമായ തീരുമാനമാണ്. +- `semgrep` (ഉപദേശാത്മക സ്വതന്ത്ര ഘടകം) — OWASP കുടുംബങ്ങളിൽ CodeQL-മായി ഓവർലാപ്പ് ചെയ്യുന്നു; അതിന്റെ ബേസ്ലൈൻ ഒരു റാച്ചറ്റുമായി ബന്ധിപ്പിക്കുക, അല്ലെങ്കിൽ ഒഴിവാക്കുക. --- diff --git a/docs/i18n/ml/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ml/docs/compression/COMPRESSION_GUIDE.md index d7dcb4d53f81..f3bc828c68b4 100644 --- a/docs/i18n/ml/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ml/docs/compression/COMPRESSION_GUIDE.md @@ -454,9 +454,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages` ഉള്ള ഒരു ബോഡിയിൽ, ഉള്ളടക്ക ബൈപാസ് (`open-sse/services/compression/outputMode.ts`-ലെ `shouldBypassCavemanOutputMode()`) അവസാനത്തെ മൂന്ന് സന്ദേശങ്ങൾ പരിശോധിക്കുകയും അവയിലെ സുരക്ഷ, തിരിച്ചെടുക്കാനാവാത്ത പ്രവർത്തനം, വ്യക്തത തേടൽ, അല്ലെങ്കിൽ ക്രമസൂക്ഷ്മതയുള്ള കീവേഡുകൾ -പൊരുത്തപ്പെട്ടാൽ ആ മുഴുവൻ ടേണിലും ശൈലികൾ ഒഴിവാക്കുകയും ചെയ്യുന്നു. ഡാഷ്ബോർഡിലെ -**Auto-Clarity Bypass** ടോഗിൾ (`cavemanOutputMode.autoClarity`) സജ്ജമാക്കിയിരിക്കുന്നത് -എന്താണോ അതനുസരിച്ചാണ് ബൈപാസ് പ്രവർത്തിക്കുന്നത്. +പൊരുത്തപ്പെട്ടാൽ ആ മുഴുവൻ ടേണിലും ശൈലികൾ ഒഴിവാക്കുകയും ചെയ്യുന്നു. ഡാഷ്ബോർഡിലെ **Auto-Clarity Bypass** ടോഗിൾ (`cavemanOutputMode.autoClarity`) ഓണായിരിക്കുമ്പോൾ ബൈപാസ് പ്രവർത്തിക്കുന്നു, ഇതാണ് സ്ഥിരസ്ഥിതി; ടോഗിൾ ഓഫ് ആകുമ്പോൾ തിരഞ്ഞെടുത്ത ശൈലികൾ ആ ടേണുകളിലും ബാധകമാകും. ബൈപാസ് ടേൺ കടന്നുപോകാൻ അനുവദിക്കുമ്പോൾ, ഒരിക്കലും പുതിയ `messages[0]` സൃഷ്ടിക്കാത്ത `placeSystemInstruction()` (അതേ ഫയൽ), കണ്ടെത്തുന്ന താഴെപ്പറയുന്ന ആദ്യ സ്ഥാനത്ത് ബ്ലോക്ക് diff --git a/docs/i18n/ml/docs/reference/ENVIRONMENT.md b/docs/i18n/ml/docs/reference/ENVIRONMENT.md index 42158de26e3a..ccaa4b0ae76a 100644 --- a/docs/i18n/ml/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ml/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. ആവശ്യമായ രഹസ്യങ്ങൾ -ആദ്യമായി പ്രവർത്തിപ്പിക്കുന്നതിന് മുമ്പ് ഇവ **നിർബന്ധമായും** സജ്ജീകരിക്കണം. ഇവ ഇല്ലെങ്കിൽ, ആപ്ലിക്കേഷൻ ആരംഭിക്കാൻ വിസമ്മതിക്കുകയോ സുരക്ഷിതമല്ലാത്ത ഡിഫോൾട്ട് ക്രമീകരണങ്ങളോടെ പ്രവർത്തിക്കുകയോ ചെയ്യും. - -| വേരിയബിൾ | ആവശ്യമാണോ | ഡിഫോൾട്ട് | സോഴ്സ് ഫയൽ | വിവരണം | -| ---------------------------- | --------------------- | ------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **അതെ** | _(ഒന്നുമില്ല)_ | `src/lib/auth` | എല്ലാ ഡാഷ്ബോർഡ് സെഷൻ കുക്കികളും (JWT) ഒപ്പിടുന്നതിനും പരിശോധിക്കുന്നതിനും ഉപയോഗിക്കുന്നു. `openssl rand -base64 48` ഉപയോഗിച്ച് സൃഷ്ടിക്കുക. | -| `API_KEY_SECRET` | **അതെ** | _(ഒന്നുമില്ല)_ | `src/lib/db/apiKeys.ts` | SQLite-ൽ സൂക്ഷിച്ചിരിക്കുന്ന API കീ മൂല്യങ്ങൾക്കായുള്ള AES എൻക്രിപ്ഷൻ കീ. `openssl rand -hex 32` ഉപയോഗിച്ച് സൃഷ്ടിക്കുക. | -| `INITIAL_PASSWORD` | **അതെ** | `CHANGEME` | ബൂട്ട്സ്ട്രാപ്പ് സ്ക്രിപ്റ്റ് | പ്രാരംഭ അഡ്മിൻ ഡാഷ്ബോർഡ് പാസ്വേഡ് സജ്ജീകരിക്കുന്നു (`.env.example`-ലെ ഡിഫോൾട്ടുമായി പൊരുത്തപ്പെടുന്നു — മാറ്റാൻ നിർബന്ധിക്കുന്നതിനായി ഇത് സുരക്ഷിതമല്ലെന്ന് വ്യക്തമായി കാണിക്കുന്ന രീതിയിൽ നിലനിർത്തിയിരിക്കുന്നു). **ആദ്യ ഉപയോഗത്തിന് മുമ്പ് മാറ്റുക.** ലോഗിൻ ചെയ്തശേഷം Dashboard → Settings → Security വഴി മാറ്റുക. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **അതെ** (പ്രൊഡക്ഷനിൽ) | _(സജ്ജീകരിച്ചിട്ടില്ല)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ആന്തരിക Codex Responses WebSocket ബ്രിഡ്ജിനായുള്ള പങ്കിട്ട രഹസ്യം. Electron/browser WS റിലേയ്ക്കും OmniRoute-നും ഇടയിലുള്ള ബ്രിഡ്ജ് അഭ്യർത്ഥനകളെ പ്രാമാണീകരിക്കുന്നു. ⚠️ **പ്രൊഡക്ഷനിൽ ഇത് നിർബന്ധമാണ് — സജ്ജീകരിച്ചിട്ടില്ലെങ്കിൽ എല്ലാ WS ബ്രിഡ്ജ് അഭ്യർത്ഥനകളും നിരസിക്കപ്പെടും.** `openssl rand -base64 32` ഉപയോഗിച്ച് സൃഷ്ടിക്കുക. | -| `OMNIROUTE_SW_BUILD_ID` | ഇല്ല | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ഷെല്ലിനായുള്ള (#11779) വ്യക്തമായി നിർവചിച്ച service-worker കാഷ്-ബസ്റ്റിംഗ് ഐഡി; നിർണ്ണയക്രമത്തിൽ ആദ്യത്തേത്. | -| `SOURCE_VERSION` | ഇല്ല | _(സജ്ജീകരിച്ചിട്ടില്ല)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | നിർണ്ണയക്രമത്തിൽ രണ്ടാമത്തേത് — വിന്യസിച്ച commit ആയി PaaS ബിൽഡറുകൾ (ഉദാ. Heroku-style) സജ്ജീകരിക്കുന്നു. | -| `NEXT_PUBLIC_SW_BUILD_ID` | ഇല്ല | _(ലഭ്യമാക്കിയത്)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` രജിസ്റ്റർ ചെയ്യാൻ ക്ലയന്റ് ഉപയോഗിക്കുന്ന ബിൽഡ്-ടൈം പബ്ലിക് മൂല്യം; മുകളിലുള്ള രണ്ടിൽനിന്നും തുടർന്ന് git SHA-യിൽനിന്നും ലഭ്യമാക്കുന്നു. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | ഇല്ല (സ്വയമേവ) | _(ഓരോ ബൂട്ടിലും സ്വയമേവ)_ | `src/server/authz/policies/management.ts` | വിശ്വസനീയമായ peer-IP സ്റ്റാമ്പ് OmniRoute-ന്റെ സ്വന്തം HTTP സെർവറിൽനിന്നാണ് (`scripts/dev/peer-stamp.mjs`) വന്നതെന്ന് തെളിയിക്കുന്ന ഓരോ പ്രോസസിനുമുള്ള രഹസ്യം. സ്റ്റാമ്പിൽ ഈ ടോക്കൺ ഉള്ളപ്പോൾ മാത്രമാണ് authz മിഡിൽവെയർ അഭ്യർത്ഥനയുടെ ലോക്കാലിറ്റിയെ (LOCAL_ONLY റൂട്ടുകളുടെ loopback/LAN ഗേറ്റിംഗ്) വിശ്വസിക്കുന്നത്. ഓരോ ബൂട്ടിലും സ്വയമേവ സൃഷ്ടിക്കപ്പെടുന്നു — സജ്ജീകരിക്കാതെ വിടുക; സ്റ്റാമ്പ് പങ്കിടേണ്ട മൾട്ടി-പ്രോസസ് സജ്ജീകരണങ്ങൾക്ക് മാത്രമേ ഇത് സ്ഥിരമായി നിർവചിക്കാവൂ. | -| `OMNIROUTE_TRUSTED_PROXIES` | ഇല്ല | _(സജ്ജീകരിച്ചിട്ടില്ല)_ | `scripts/dev/peer-stamp.mjs` | IP അനുവദിക്കൽ/നിരസിക്കൽ പട്ടികയ്ക്ക് വിശ്വസിക്കാവുന്ന `X-Forwarded-For` / `X-Real-IP` ഉള്ള റിവേഴ്സ് പ്രോക്സികളുടെ കോമ ഉപയോഗിച്ച് വേർതിരിച്ച IP-കൾ അല്ലെങ്കിൽ CIDR ശ്രേണികൾ. എപ്പോഴും വിശ്വസിക്കപ്പെടുന്ന loopback, സ്വകാര്യ നെറ്റ്വർക്ക് വിലാസങ്ങൾ, Cloudflare edge-കൾ എന്നിവയ്ക്ക് പുറമേയാണിത്. മറ്റേതെങ്കിലും പൊതു വിലാസത്തിലുള്ള പ്രോക്സിക്ക് മാത്രമേ ഇത് ആവശ്യമുള്ളൂ; അല്ലെങ്കിൽ ഫിൽട്ടർ വിലയിരുത്തുന്നത് ആ പ്രോക്സിയുടെ സ്വന്തം വിലാസമായിരിക്കും. | +**അതെ** എന്ന് അടയാളപ്പെടുത്തിയിരിക്കുന്ന, സജ്ജീകരിക്കാത്ത രഹസ്യങ്ങൾ ആദ്യ പ്രവർത്തനത്തിൽ സ്വയമേവ സൃഷ്ടിക്കപ്പെടുകയും (`JWT_SECRET`, `API_KEY_SECRET`) ഡാറ്റാ ഡയറക്ടറിയിലെ `server.env`-ൽ സൂക്ഷിക്കപ്പെടുകയും ചെയ്യും — ആ ഡയറക്ടറി താൽക്കാലികമാണെങ്കിൽ ഇവ വ്യക്തമായി സജ്ജീകരിക്കുക. ഇതിനൊരപവാദമാണ് `OMNIROUTE_WS_BRIDGE_SECRET`: ഇത് `server.env`-ൽ സൂക്ഷിക്കപ്പെടുന്നില്ല — സെർവർ എൻട്രിപോയിന്റുകൾ ഓരോ ബൂട്ടിലും പ്രോസസിനുള്ളിലെ ബ്രിഡ്ജിന് മാത്രം ഉപയോഗിക്കാനാവുന്ന ഒരു താൽക്കാലിക മൂല്യം സൃഷ്ടിക്കുന്നു, അതിനാൽ ബാഹ്യ ബ്രിഡ്ജ് ക്ലയന്റുകൾക്ക് ഇത് വ്യക്തമായി സജ്ജീകരിക്കേണ്ടതുണ്ട്. + +| വേരിയബിൾ | ആവശ്യമാണോ | ഡിഫോൾട്ട് | സോഴ്സ് ഫയൽ | വിവരണം | +| ---------------------------- | --------------------- | ------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **അതെ** | _(ഒന്നുമില്ല)_ | `src/lib/auth` | എല്ലാ ഡാഷ്ബോർഡ് സെഷൻ കുക്കികളും (JWT) ഒപ്പിടുന്നതിനും പരിശോധിക്കുന്നതിനും ഉപയോഗിക്കുന്നു. `openssl rand -base64 48` ഉപയോഗിച്ച് സൃഷ്ടിക്കുക. | +| `API_KEY_SECRET` | **അതെ** | _(ഒന്നുമില്ല)_ | `src/lib/db/apiKeys.ts` | SQLite-ൽ സൂക്ഷിച്ചിരിക്കുന്ന API കീ മൂല്യങ്ങൾക്കായുള്ള AES എൻക്രിപ്ഷൻ കീ. `openssl rand -hex 32` ഉപയോഗിച്ച് സൃഷ്ടിക്കുക. | +| `INITIAL_PASSWORD` | ഇല്ല | _(സജ്ജീകരിച്ചിട്ടില്ല)_ | `src/lib/auth/managementPassword.ts` | ഒരു പാസ്വേഡും സൂക്ഷിച്ചിട്ടില്ലാത്തപ്പോൾ സ്റ്റാർട്ടപ്പിൽ ഡാഷ്ബോർഡ് പാസ്വേഡ് സജ്ജീകരിക്കുന്നു; പാസ്വേഡ് സൂക്ഷിച്ചുകഴിഞ്ഞാൽ ഇത് അവഗണിക്കപ്പെടും. npm, സോഴ്സ് ഇൻസ്റ്റാളുകൾ `.env.example`-നെ `.env`-ലേക്ക് പകർത്തുന്നു, അതിനാൽ അവ അതിന്റെ പൊതുവായി അറിയപ്പെടുന്ന പ്ലേസ്ഹോൾഡർ `CHANGEME`-ഉമായാണ് ആരംഭിക്കുന്നത്: ആദ്യ ബൂട്ടിന് മുമ്പ് ഇത് മാറ്റുക, അല്ലെങ്കിൽ ആദ്യ സന്ദർശനത്തിൽ ഓൺബോർഡിംഗ് വിസാർഡിൽ പാസ്വേഡ് സൃഷ്ടിക്കാൻ ആ ഫയലിൽ `INITIAL_PASSWORD=` (ശൂന്യം) സജ്ജീകരിക്കുക — സജ്ജീകരിക്കാത്ത മൂല്യം (Docker ഇമേജിന്റെ ഡിഫോൾട്ട്) ചെയ്യുന്നതുപോലെ. സൂക്ഷിച്ച പാസ്വേഡ് Dashboard → Settings → Security-യിൽ മാറ്റുക, അല്ലെങ്കിൽ സെർവറിന്റെ ഡാറ്റാ ഡയറക്ടറിയിലേക്ക് `DATA_DIR` സജ്ജീകരിച്ച് `omniroute reset-password` (സോഴ്സ് ചെക്ക്ഔട്ടിൽ `node bin/reset-password.mjs`) പ്രവർത്തിപ്പിക്കുക. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **അതെ** (പ്രൊഡക്ഷനിൽ) | _(സജ്ജീകരിച്ചിട്ടില്ല)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ആന്തരിക Codex Responses WebSocket ബ്രിഡ്ജിനായുള്ള പങ്കിട്ട രഹസ്യം. Electron/browser WS റിലേയ്ക്കും OmniRoute-നും ഇടയിലുള്ള ബ്രിഡ്ജ് അഭ്യർത്ഥനകളെ പ്രാമാണീകരിക്കുന്നു. ⚠️ **പ്രൊഡക്ഷനിൽ ഇത് നിർബന്ധമാണ് — സജ്ജീകരിച്ചിട്ടില്ലെങ്കിൽ എല്ലാ WS ബ്രിഡ്ജ് അഭ്യർത്ഥനകളും നിരസിക്കപ്പെടും.** `openssl rand -base64 32` ഉപയോഗിച്ച് സൃഷ്ടിക്കുക. | +| `OMNIROUTE_SW_BUILD_ID` | ഇല്ല | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ഷെല്ലിനായുള്ള (#11779) വ്യക്തമായി നിർവചിച്ച service-worker കാഷ്-ബസ്റ്റിംഗ് ഐഡി; നിർണ്ണയക്രമത്തിൽ ആദ്യത്തേത്. | +| `SOURCE_VERSION` | ഇല്ല | _(സജ്ജീകരിച്ചിട്ടില്ല)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | നിർണ്ണയക്രമത്തിൽ രണ്ടാമത്തേത് — വിന്യസിച്ച commit ആയി PaaS ബിൽഡറുകൾ (ഉദാ. Heroku-style) സജ്ജീകരിക്കുന്നു. | +| `NEXT_PUBLIC_SW_BUILD_ID` | ഇല്ല | _(ലഭ്യമാക്കിയത്)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` രജിസ്റ്റർ ചെയ്യാൻ ക്ലയന്റ് ഉപയോഗിക്കുന്ന ബിൽഡ്-ടൈം പബ്ലിക് മൂല്യം; മുകളിലുള്ള രണ്ടിൽനിന്നും തുടർന്ന് git SHA-യിൽനിന്നും ലഭ്യമാക്കുന്നു. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | ഇല്ല (സ്വയമേവ) | _(ഓരോ ബൂട്ടിലും സ്വയമേവ)_ | `src/server/authz/policies/management.ts` | വിശ്വസനീയമായ peer-IP സ്റ്റാമ്പ് OmniRoute-ന്റെ സ്വന്തം HTTP സെർവറിൽനിന്നാണ് (`scripts/dev/peer-stamp.mjs`) വന്നതെന്ന് തെളിയിക്കുന്ന ഓരോ പ്രോസസിനുമുള്ള രഹസ്യം. സ്റ്റാമ്പിൽ ഈ ടോക്കൺ ഉള്ളപ്പോൾ മാത്രമാണ് authz മിഡിൽവെയർ അഭ്യർത്ഥനയുടെ ലോക്കാലിറ്റിയെ (LOCAL_ONLY റൂട്ടുകളുടെ loopback/LAN ഗേറ്റിംഗ്) വിശ്വസിക്കുന്നത്. ഓരോ ബൂട്ടിലും സ്വയമേവ സൃഷ്ടിക്കപ്പെടുന്നു — സജ്ജീകരിക്കാതെ വിടുക; സ്റ്റാമ്പ് പങ്കിടേണ്ട മൾട്ടി-പ്രോസസ് സജ്ജീകരണങ്ങൾക്ക് മാത്രമേ ഇത് സ്ഥിരമായി നിർവചിക്കാവൂ. | +| `OMNIROUTE_TRUSTED_PROXIES` | ഇല്ല | _(സജ്ജീകരിച്ചിട്ടില്ല)_ | `scripts/dev/peer-stamp.mjs` | IP അനുവദിക്കൽ/നിരസിക്കൽ പട്ടികയ്ക്ക് വിശ്വസിക്കാവുന്ന `X-Forwarded-For` / `X-Real-IP` ഉള്ള റിവേഴ്സ് പ്രോക്സികളുടെ കോമ ഉപയോഗിച്ച് വേർതിരിച്ച IP-കൾ അല്ലെങ്കിൽ CIDR ശ്രേണികൾ. എപ്പോഴും വിശ്വസിക്കപ്പെടുന്ന loopback, സ്വകാര്യ നെറ്റ്വർക്ക് വിലാസങ്ങൾ, Cloudflare edge-കൾ എന്നിവയ്ക്ക് പുറമേയാണിത്. മറ്റേതെങ്കിലും പൊതു വിലാസത്തിലുള്ള പ്രോക്സിക്ക് മാത്രമേ ഇത് ആവശ്യമുള്ളൂ; അല്ലെങ്കിൽ ഫിൽട്ടർ വിലയിരുത്തുന്നത് ആ പ്രോക്സിയുടെ സ്വന്തം വിലാസമായിരിക്കും. | ### സൃഷ്ടിക്കുന്നതിനുള്ള കമാൻഡുകൾ @@ -217,38 +217,38 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ## 4. സുരക്ഷയും പ്രാമാണീകരണവും -| വേരിയബിൾ | ഡിഫോൾട്ട് | സോഴ്സ് ഫയൽ | വിവരണം | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | മെഷീൻ ഫിംഗർപ്രിന്റിംഗിനായി ഹാർഡ്വെയർ ഐഡന്റിഫയറുകളുമായി സംയോജിപ്പിച്ച സാൾട്ട്. ഐസൊലേഷനായി ഓരോ ഡിപ്ലോയ്മെന്റിനും മാറ്റുക. | -| `OMNIROUTE_CLI_SALT` | _(സെറ്റ് ചെയ്യാത്തത് = `/cli-token-salt.json` എന്നതിൽ നിലനിർത്തുന്ന ഓരോ ഇൻസ്റ്റാളിനും റാൻഡം സാൾട്ട്)_ | `src/lib/machineToken.ts` | ലോക്കൽ CLI ഓത്ത് ടോക്കൺ ഉണ്ടാക്കുന്നതിനുള്ള HMAC സാൾട്ട്. ഈ മൂല്യം സജ്ജീകരിക്കുന്നത് മെഷീനിലെ എല്ലാ CLI ടോക്കണുകളും റൊട്ടേറ്റ് ചെയ്യുകയും നിലനിർത്തുന്ന സാൾട്ടിന് എപ്പോഴും മുൻഗണന നൽകുകയും ചെയ്യുന്നു. `docs/security/CLI_TOKEN.md` കാണുക. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | സെഷൻ കുക്കികളിൽ `Secure` ഫ്ലാഗ് സജ്ജീകരിക്കുന്നു. HTTPS-ന് പിന്നിൽ പ്രവർത്തിക്കുമ്പോൾ **`true` ആയിരിക്കണം**. | -| `REQUIRE_API_KEY` | `false` | API മിഡിൽവെയർ | `true` ആയിരിക്കുമ്പോൾ, എല്ലാ `/v1/*` പ്രോക്സി അഭ്യർത്ഥനകളിലും ഒരു സാധുവായ API കീ ഉൾപ്പെടുത്തണം. **ഈ ഫ്ലാഗ് `GET /v1/models` നെ തടയുന്നില്ല**, ഇത് ഡാഷ്ബോർഡ് ലോഗിൻ പോസ്ചർ (`requireAuthForModels`) പിന്തുടരുന്നു — അതിനാൽ `/v1/models` ൽ നിന്നുള്ള ഒരു `401` എന്നാൽ ഇൻഫറൻസ് സംരക്ഷിക്കപ്പെട്ടിരിക്കുന്നു എന്നല്ല അർത്ഥമാക്കുന്നത്. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) കാണുക. | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ഡാഷ്ബോർഡ് UI-ൽ പൂർണ്ണ API കീ മൂല്യങ്ങൾ വെളിപ്പെടുത്താൻ അനുവദിക്കുന്നു. ഡാഷ്ബോർഡ് ഫീച്ചർ ഫ്ലാഗുകളിൽ നിന്ന് കോൺഫിഗർ ചെയ്യാവുന്നതാണ്; പങ്കിട്ട ഇൻസ്റ്റൻസുകളിൽ സുരക്ഷാ അപകടസാധ്യതയുണ്ട്. | -| `NO_LOG_API_KEY_IDS` | _(ഒഴിഞ്ഞത്)_ | `src/lib/compliance/index.ts` | അഭ്യർത്ഥന ലോഗിംഗ് ഒഴിവാക്കുന്ന കോമയാൽ വേർതിരിച്ച API കീ ഐഡികൾ (GDPR പാലനം). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(സെറ്റ് ചെയ്യാത്തത് = പരിധിയില്ലാത്തത്)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` കോളം null ആയ API കീകൾക്ക് ബാധകമാകുന്ന പ്രതിദിന അഭ്യർത്ഥന ബഡ്ജറ്റ്. സെറ്റ് ചെയ്യാത്തതോ ഒഴിഞ്ഞതോ: വ്യക്തമായ പരിധിയില്ല (#2289, #11017). `0` ഉം സമാനമാണ് (പരിധിയില്ലാത്തത്). പോസിറ്റീവ് പൂർണ്ണസംഖ്യ N, N/ദിവസം, 5N/ആഴ്ച, 20N/മാസം എന്നിവ പ്രാപ്തമാക്കുന്നു. തെറ്റായ നോൺ-എംപ്റ്റി മൂല്യങ്ങൾ പഴയ 1000/ദിവസം, 5000/ആഴ്ച, 20000/മാസം വിൻഡോകളിലേക്ക് തിരികെ പോകുന്നു. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | അനുവദനീയമായ പരമാവധി അഭ്യർത്ഥന ബോഡി വലുപ്പം. ഈ പരിധി കവിയുന്ന പേലോഡുകൾ നിരസിക്കുന്നു. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ഈ പരിധിയിലോ അതിനു മുകളിലോ ഉള്ള യഥാർത്ഥ അഭ്യർത്ഥന ബോഡികൾ JSON പാഴ്സിംഗിന് മുമ്പ് അറ്റോമിക് പ്രോസസ്-ലോക്കൽ ഹെവിവെയ്റ്റ് അഡ്മിഷൻ ലീസിനെടുക്കുന്നു (BYTE പാത, `POST /v1/responses` ഉൾപ്പെടെ). ഘടനാപരമായി ഭാരമുള്ളതിന് സമാനമായ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ആരോഗ്യകരമായ ഹെഡ്റൂം എസ്കേപ്പ്; എന്നിരുന്നാലും `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) എന്നിവയാൽ പരിമിതപ്പെടുത്തിയിരിക്കുന്നു, അതിനാൽ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) തിരികെ വരുന്നില്ല. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | നഷ്ടമായതോ, അസാധുവായതോ, തെറ്റായതോ ആയ `Content-Length` ഉള്ള അഭ്യർത്ഥനകൾ ഉൾപ്പെടെ, ബൗണ്ടഡ് ഇൻജഷൻ സമയത്ത് വായിച്ച ബൈറ്റുകൾക്കെതിരെ നടപ്പിലാക്കുന്ന ചാറ്റ്-റൂട്ട് ഹാർഡ് ക്യാപ്; അധികമുള്ളവയ്ക്ക് `413` ലഭിക്കുന്നു. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(സെറ്റ് ചെയ്തിട്ടില്ല — അഭ്യർത്ഥന-എണ്ണം ക്യാപ് ഇല്ല)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ഈ ലെഗസി അഭ്യർത്ഥന-എണ്ണം ക്യാപ് ഇപ്പോൾ വ്യക്തമായി സജ്ജീകരിക്കുമ്പോൾ മാത്രമേ ബാധകമാകൂ. സജ്ജീകരിക്കാതെ വിടുമ്പോൾ (സ്ഥിരസ്ഥിതി), ഹെവിവെയ്റ്റ് ചാറ്റ് അഡ്മിഷൻ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` വഴി നിയന്ത്രിക്കപ്പെടുന്നു — ഇത് ഒരു പ്രോസസ്സിന്റെ യഥാർത്ഥ മെമ്മറി സീലിംഗിൽ നിന്ന് സ്വയമേവ ലഭിക്കുന്ന ഒരു ബൈറ്റ് ബഡ്ജറ്റാണ് (ഒരു V8 ഹീപ്പ്). ഏകദേശം 750k-ടോക്കൺ വരുന്ന രണ്ട് `/v1/responses` ഏകദേശം 12 Gi ഹീപ്പുകൾ റദ്ദാക്കുന്നു (#7849) — ഇത് ഒരു **മെമ്മറി-ബഡ്ജറ്റ്** മുന്നറിയിപ്പാണ്, അല്ലാതെ 2 എന്ന ഹാർഡ് പ്രൊഡക്റ്റ് മാക്സ് അല്ല. ആരോഗ്യകരമായ ഒരു പ്രോസസ്സ് (ഷെഡ് റേഷ്യോയ്ക്ക് താഴെയുള്ള ഹീപ്പ്) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` വഴി കൂടുതൽ ഒരേസമയം പ്രവർത്തിക്കുന്ന നീണ്ട `/v1/responses` അനുവദിച്ചേക്കാം. പത്ത് നീണ്ട SSE ക്ലയന്റുകൾ (40–50) ഹീപ്പ് + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ആണ്, അല്ലാതെ “പരമാവധി 2” അല്ല. ഇത് “ഹോസ്റ്റ് ഉപയോഗിക്കാൻ” എന്നതിലേക്ക് അന്ധമായി ഉയർത്തുന്നത് #7849 വീണ്ടും അവതരിപ്പിക്കുന്നു. **N സ്വതന്ത്ര `DATA_DIR`s** (#11024) ഉള്ള **ഹീപ്പുകൾ** ഗുണിക്കുക; ഒരു SQLite ഫയലിൽ ഒരിക്കലും `replicas>1` ചെയ്യരുത്. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(സ്വയമേവ ലഭിക്കുന്നത്)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** സ്വയമേവ ലഭിക്കുന്ന ഇൻജസ്റ്റ് ബൈറ്റ് ബഡ്ജറ്റിനായുള്ള ഓവർറൈഡ് (ഇറുകിയ V8/cgroup മെമ്മറി സീലിംഗിന്റെ 25% നെ 8x ട്രാൻസിയന്റ് ആംപ്ലിഫിക്കേഷൻ കൊണ്ട് ഹരിച്ചാൽ ലഭിക്കുന്നത്). ലഭിച്ചതും വ്യക്തമായതുമായ മൂല്യങ്ങൾ 8 MiB–2 GiB ആയി പരിമിതപ്പെടുത്തുന്നു. ഫലപ്രദമായ ബഡ്ജറ്റിനേക്കാൾ വലിയ ഒരു ബോഡിക്ക് `413 body_exceeds_budget` എന്ന പിശക് ഉടനടി ലഭിക്കുന്നു; വ്യക്തിഗതമായി സേവനം നൽകാൻ കഴിയുന്ന ബോഡികൾ തമ്മിലുള്ള മത്സരം `503` ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കാവുന്നതാണ്. 40–50 ഒരേസമയം പ്രവർത്തിക്കുന്ന നീണ്ട SSE ക്ലയന്റുകൾ ഈ ബഡ്ജറ്റും ഹീപ്പും ചേർന്നതാണ്, അല്ലാതെ ഒരു ഹാർഡ് “പരമാവധി 2” അല്ല. ട്യൂൺ ചെയ്യുന്നതിന് മുമ്പ് `/api/monitoring/health` എന്നതിൽ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` വായിക്കുക. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | ബൈറ്റ്, സ്ട്രക്ച്ചർ ഹെവിവെയ്റ്റ് അഡ്മിഷനായുള്ള ഹീപ്പ്-പ്രഷർ ഷെഡ് റേഷ്യോ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` കഴിഞ്ഞുള്ള ഒരു കൺകറന്റ് ഹെവിവെയ്റ്റ് അഭ്യർത്ഥനയ്ക്ക് ഹീപ്പ് ഈ അനുപാതത്തിൽ അല്ലെങ്കിൽ അതിന് മുകളിലാണെങ്കിൽ മാത്രമേ `503` എന്ന റീട്രൈ ചെയ്യാവുന്ന പിശക് ലഭിക്കൂ; ആരോഗ്യകരമായ ഒരു ഹീപ്പിൽ ഇത് ഹെൽത്തി-ഹെഡ്റൂം വഴി അനുവദിക്കപ്പെടുന്നു. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (സ്ഥിരസ്ഥിതി `1`) | `src/shared/middleware/chatBodyAdmission.ts` | സ്ട്രക്ച്ചർ, ബൈറ്റ് (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ന് തുല്യമോ അതിൽ കൂടുതലോ ഉള്ള ബോഡികൾ ഉൾപ്പെടെ) എന്നിവയിലെ ആരോഗ്യകരമായ-ഹീപ്പ് ഫാസ്റ്റ് പാത്തിനായുള്ള ബൗണ്ടഡ് അധിക ശേഷി (#10437). ഈ പരിധി ഇല്ലായിരുന്നെങ്കിൽ, തിരക്കുള്ളതും എന്നാൽ ആരോഗ്യകരവുമായ ഓരോ ഹീപ്പ് അഭ്യർത്ഥനയും പരിധിയില്ലാതെ അഡ്മിഷൻ ഒഴിവാക്കുമായിരുന്നു. ഈ എണ്ണം കൺകറന്റ് ലീസുകൾ ആരോഗ്യകരമായ-ഹീപ്പ് പാതയിലൂടെ സജീവമായാൽ, കൂടുതൽ തിരക്കുള്ള അഭ്യർത്ഥനകൾ യഥാർത്ഥ ഹീപ്പ് സമ്മർദ്ദത്തിൽ ഉപയോഗിക്കുന്ന അതേ ബൗണ്ടഡ്-വെയ്റ്റ്/ഷെഡ് പാതയിലേക്ക് മാറുന്നു. `0` ഈ ബൈപാസ് പൂർണ്ണമായും പ്രവർത്തനരഹിതമാക്കുന്നു. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ബോഡി ബൈറ്റ് പരിധിക്ക് താഴെയാണെങ്കിൽ പോലും ഒരു ചാറ്റ് അഭ്യർത്ഥനയെ ഹെവിവെയ്റ്റ് ആയി തരംതിരിക്കുന്ന സന്ദേശങ്ങളുടെ എണ്ണം. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ബോഡി ബൈറ്റ് പരിധിക്ക് താഴെയാണെങ്കിൽ പോലും ഒരു ചാറ്റ് അഭ്യർത്ഥനയെ ഹെവിവെയ്റ്റ് ആയി തരംതിരിക്കുന്ന ടൂളുകളുടെ എണ്ണം. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ഒരു അഭ്യർത്ഥനയെ ഹെവിവെയ്റ്റ് ആയി തരംതിരിക്കുന്ന കൺസർവേറ്റീവ് സ്ട്രിംഗ്-സൈസ് ടോക്കൺ എസ്റ്റിമേറ്റ്; ഇത് ഒരു അഡ്മിഷൻ-കോസ്റ്റ് പ്രോക്സിയാണ്, പ്രൊവൈഡർ ബില്ലിംഗ് ടോക്കണൈസേഷൻ അല്ല. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (പ്രവർത്തനരഹിതമാക്കി) | `src/shared/middleware/chatBodyAdmission.ts` | ഓപ്ഷണൽ ഓപ്റ്റ്-ഇൻ ചാറ്റ് ഹിസ്റ്ററി ക്യാപ്. സ്ഥിരസ്ഥിതിയായി പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു: ഒരു സന്ദേശങ്ങളുടെ എണ്ണം ഡിപ്ലോയ്മെന്റ് പോളിസിയാണ്, ഒരു അഭ്യർത്ഥനയുടെ സാർവത്രിക സ്വഭാവമല്ല, ഇവിടെ ക്യാപ് ചെയ്യുന്നത് കംപ്രഷൻ പൈപ്പ്ലൈൻ അവയെ സേവിക്കാൻ കഴിയുന്നതാക്കുന്നതിന് മുമ്പ് ഒരു ടെർമിനൽ `413` ഉപയോഗിച്ച് സംഭാഷണങ്ങളെ നിരസിക്കുന്നു. ഹീപ്പ് വളർച്ച `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`, ഹീപ്പ്-പ്രഷർ ഷെഡ് എന്നിവയാൽ പരിമിതപ്പെടുത്തിയിരിക്കുന്നു. ഹാർഡ് സീലിംഗ് ആവശ്യമുള്ള മെമ്മറി-പരിമിതമായ ഡിപ്ലോയ്മെന്റുകളിൽ ഒരു പോസിറ്റീവ് മൂല്യം സജ്ജീകരിക്കുക; അധികമുള്ളവയ്ക്ക് സ്ട്രക്ച്ചേർഡ് കോംപാക്റ്റ്-റിക്വയേർഡ് `413` ലഭിക്കുന്നു. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | മെമ്മറിയിലേക്ക് പൂർണ്ണമായി ബഫർ ചെയ്ത ഒരു നോൺ-സ്ട്രീമിംഗ് അപ്സ്ട്രീം പ്രതികരണത്തിനുള്ള ഹാർഡ് ക്യാപ്. ഇതിന് മുകളിലാണെങ്കിൽ അപ്സ്ട്രീം റീഡർ റദ്ദാക്കപ്പെടുകയും ഹീപ്പ് തീരുന്നത് വരെ പരിധിയില്ലാത്ത സ്ട്രിംഗ് വളർത്തുന്നതിന് പകരം അഭ്യർത്ഥന വേഗത്തിൽ പരാജയപ്പെടുകയും ചെയ്യുന്നു. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | അപ്സ്ട്രീം പ്രതികരണ ഹെഡറുകളിൽ നിന്ന് കൈമാറുന്ന പരമാവധി വയർ ബൈറ്റുകൾ. ബഡ്ജറ്റ് കവിഞ്ഞാൽ, സാധാരണ റിവേഴ്സ്-പ്രോക്സി ഹെഡർ പരിധിക്കുള്ളിൽ നിൽക്കുന്നതിനായി കുറഞ്ഞ മുൻഗണനയുള്ള ഹെഡറുകൾ (ഉദാഹരണത്തിന്, കസ്റ്റം `x-codex-*`, `x-oai-request-id`) ഒഴിവാക്കപ്പെടും. കൂടുതൽ അപ്സ്ട്രീം മെറ്റാഡാറ്റ കൈമാറുന്നതിനായി ഇത് ഉയർന്ന മൂല്യത്തിൽ സജ്ജീകരിക്കുക, ഇത് വലിയ പ്രതികരണ ഹെഡർ വലുപ്പത്തിന് കാരണമാകും. | -| `CORS_ORIGIN` | _(unset)_ | `src/server/cors/origins.ts` | പഴയ സിംഗിൾ-ഒറിജിൻ CORS അനുമതിപ്പട്ടിക. പുതിയ വിന്യാസങ്ങൾക്കായി `CORS_ALLOWED_ORIGINS` തിരഞ്ഞെടുക്കുക. CORS ക്രോസ്-ഒറിജിൻ ബ്രൗസർ API ക്ലയന്റുകൾക്ക് മാത്രമുള്ളതാണ്; പ്രാമാണീകരിച്ച ഡാഷ്ബോർഡ് എഴുത്തുകൾക്ക് സെഷൻ-ബൗണ്ട് CSRF സംരക്ഷണത്തോടുകൂടിയ സെയിം-ഒറിജിൻ അഭ്യർത്ഥനകളാണ് ഉപയോഗിക്കുന്നത്. | -| `CORS_ALLOWED_ORIGINS` | _(unset)_ | `src/server/cors/origins.ts` | കോമയാൽ വേർതിരിച്ച CORS അനുമതിപ്പട്ടിക. `CORS_ALLOW_ALL=true` എന്ന് വ്യക്തമായി കോൺഫിഗർ ചെയ്തിട്ടില്ലെങ്കിൽ വൈൽഡ്കാർഡ് അയക്കില്ല. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ഏതൊരു ബ്രൗസർ `Origin` നെയും പ്രതിധ്വനിപ്പിക്കാനുള്ള ഡെവലപ്മെന്റിന് മാത്രമുള്ള ഒരു എസ്കേപ്പ് ഹാച്ച്. പങ്കിട്ടതോ പ്രൊഡക്ഷൻ വിന്യാസങ്ങളിലോ ഇത് പ്രവർത്തനക്ഷമമാക്കരുത്. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | സ്വകാര്യ/ലൂപ്പ്ബാക്ക്/ലിങ്ക്-ലോക്കൽ IP ശ്രേണികളെ ലക്ഷ്യമിടുന്ന പ്രൊവൈഡർ കോളുകൾ തടയുക. ഒറ്റപ്പെട്ട ടെസ്റ്റ് എൻവയോൺമെന്റുകളിൽ മാത്രം പ്രവർത്തനരഹിതമാക്കുക. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | സ്വകാര്യ/പ്രാദേശിക നെറ്റ്വർക്കുകളിലേക്ക് (localhost, 192.168.x.x, 10.x.x.x, മുതലായവ) പോയിന്റ് ചെയ്യുന്ന പ്രൊവൈഡർ URL-കൾ അനുവദിക്കുക. **സ്വയം ഹോസ്റ്റ് ചെയ്യുന്ന പ്രൊവൈഡർമാർക്ക് ആവശ്യമാണ്** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). `false` ആയിരിക്കുമ്പോൾ, ഡാഷ്ബോർഡ് പ്രാദേശിക URL-കളുടെ സാധുത നിരസിക്കുന്നു. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | പ്രാദേശിക/സ്വകാര്യ വിലാസങ്ങളിൽ (127.0.0.1, localhost, LAN, സ്വകാര്യ ശ്രേണികൾ) പ്രൊവൈഡർമാരെ ചേർക്കാനും സാധൂകരിക്കാനും അനുവദിക്കുക — ഇത് പ്രൊവൈഡർ സാധൂകരണ പാതയിലേക്ക് പരിമിതപ്പെടുത്തിയിരിക്കുന്നു. **സ്ഥിരസ്ഥിതി `true`** (പ്രാദേശിക-ആദ്യം); കർശനമായ പൊതു-മാത്രം തടയൽ നടപ്പിലാക്കാൻ `false` എന്ന് സജ്ജീകരിക്കുക. ക്ലൗഡ്-മെറ്റാഡാറ്റ എൻഡ്പോയിന്റുകൾ (169.254.169.254, metadata.google.internal) എന്തായാലും തടയപ്പെടും. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` റൂട്ടുകൾക്ക് (ട്രാൻസ്ക്രിപ്ഷനുകൾ, സ്പീച്ച്, വിവർത്തനങ്ങൾ) ലോക്കൽഹോസ്റ്റിന് പുറത്ത് ഹോസ്റ്റ് ചെയ്തിട്ടുള്ള ഒരു OpenAI-അനുയോജ്യമായ പ്രൊവൈഡർ നോഡ് ഉപയോഗിക്കാൻ അനുവദിക്കുക. സ്ഥിരസ്ഥിതിയായി പ്രവർത്തനരഹിതമാണ് — ഓഡിയോ ഒരു റിമോട്ട് ഹോസ്റ്റിലേക്ക് റൂട്ട് ചെയ്യുന്നത് എഗ്രസ് ഐഡന്റിറ്റി മാറ്റുകയും ഒരു ഓപ്പറേറ്ററുടെ വ്യക്തമായ തീരുമാനമായിരിക്കുകയും വേണം. ലൂപ്പ്ബാക്ക്/സ്വകാര്യ നോഡുകൾ (localhost, 127.0.0.1, 172.16-31.x) എല്ലായ്പ്പോഴും അനുവദനീയവും ബാധിക്കപ്പെടാത്തതുമാണ്. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (കൂടാതെ മെമ്മറി എഞ്ചിന്റെ ലൂപ്പ്ബാക്ക് റീറാങ്ക് സ്റ്റെപ്പ്) ലോക്കൽഹോസ്റ്റിന് പുറത്ത് ഹോസ്റ്റ് ചെയ്തിട്ടുള്ള ഒരു OpenAI-അനുയോജ്യമായ പ്രൊവൈഡർ നോഡ് ഉപയോഗിക്കാൻ അനുവദിക്കുക — ഒരു LAN ബോക്സ് അല്ലെങ്കിൽ TEI, Infinity, vLLM മുതലായവ പ്രവർത്തിക്കുന്ന ഒരു Tailscale പിയർ. സ്ഥിരസ്ഥിതിയായി പ്രവർത്തനരഹിതമാണ് — ഒരു റിമോട്ട് ഹോസ്റ്റിലേക്ക് റൂട്ട് ചെയ്യുന്നത് എഗ്രസ് ഐഡന്റിറ്റി മാറ്റുകയും ഒരു ഓപ്പറേറ്ററുടെ വ്യക്തമായ തീരുമാനമായിരിക്കുകയും വേണം. ലൂപ്പ്ബാക്ക് നോഡുകൾ (localhost, 127.0.0.1, 172.16-31.x) എല്ലായ്പ്പോഴും അനുവദനീയവും ബാധിക്കപ്പെടാത്തതുമാണ്. റിമോട്ട് നോഡുകൾ പ്രൊവൈഡർ ഔട്ട്ബൗണ്ട് URL പോളിസി (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) പാസ്സാക്കണം; ക്ലൗഡ്-മെറ്റാഡാറ്റ ഹോസ്റ്റുകളിലേക്ക് ഒരിക്കലും റൂട്ട് ചെയ്യില്ല. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, പാസ്വേഡ് ലോഗിൻ പ്രവർത്തനരഹിതമാക്കുക, അങ്ങനെ ഉപയോക്താക്കൾക്ക് OIDC സിംഗിൾ സൈൻ-ഓൺ വഴി മാത്രമേ പ്രാമാണീകരിക്കാൻ കഴിയൂ. `OIDC_DISABLE_PASSWORD_LOGIN` എന്ന അപരനാമവും സ്വീകാര്യമാണ്; അതേ കീയിലുള്ള ഡാഷ്ബോർഡ് ഫീച്ചർ ഫ്ലാഗിനാണ് മുൻഗണന. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ന്റെ അപരനാമം (#10889). | +| വേരിയബിൾ | ഡിഫോൾട്ട് | സോഴ്സ് ഫയൽ | വിവരണം | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | മെഷീൻ ഫിംഗർപ്രിന്റിംഗിനായി ഹാർഡ്വെയർ ഐഡന്റിഫയറുകളുമായി സംയോജിപ്പിച്ച സാൾട്ട്. ഐസൊലേഷനായി ഓരോ ഡിപ്ലോയ്മെന്റിനും മാറ്റുക. | +| `OMNIROUTE_CLI_SALT` | _(സെറ്റ് ചെയ്യാത്തത് = `/cli-token-salt.json` എന്നതിൽ നിലനിർത്തുന്ന ഓരോ ഇൻസ്റ്റാളിനും റാൻഡം സാൾട്ട്)_ | `src/lib/machineToken.ts` | ലോക്കൽ CLI ഓത്ത് ടോക്കൺ ഉണ്ടാക്കുന്നതിനുള്ള HMAC സാൾട്ട്. ഈ മൂല്യം സജ്ജീകരിക്കുന്നത് മെഷീനിലെ എല്ലാ CLI ടോക്കണുകളും റൊട്ടേറ്റ് ചെയ്യുകയും നിലനിർത്തുന്ന സാൾട്ടിന് എപ്പോഴും മുൻഗണന നൽകുകയും ചെയ്യുന്നു. `docs/security/CLI_TOKEN.md` കാണുക. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | സെഷൻ കുക്കികളിൽ `Secure` ഫ്ലാഗ് സജ്ജീകരിക്കുന്നു. HTTPS-ന് പിന്നിൽ പ്രവർത്തിക്കുമ്പോൾ **`true` ആയിരിക്കണം**. | +| `REQUIRE_API_KEY` | `false` | API മിഡിൽവെയർ | `true` ആയിരിക്കുമ്പോൾ, എല്ലാ `/v1/*` പ്രോക്സി അഭ്യർത്ഥനകളിലും ഒരു സാധുവായ API കീ ഉൾപ്പെടുത്തണം. **ഈ ഫ്ലാഗ് `GET /v1/models` നെ തടയുന്നില്ല**, ഇത് ഡാഷ്ബോർഡ് ലോഗിൻ പോസ്ചർ (`requireAuthForModels`) പിന്തുടരുന്നു — അതിനാൽ `/v1/models` ൽ നിന്നുള്ള ഒരു `401` എന്നാൽ ഇൻഫറൻസ് സംരക്ഷിക്കപ്പെട്ടിരിക്കുന്നു എന്നല്ല അർത്ഥമാക്കുന്നത്. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) കാണുക. | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ഡാഷ്ബോർഡ് UI-ൽ പൂർണ്ണ API കീ മൂല്യങ്ങൾ വെളിപ്പെടുത്താൻ അനുവദിക്കുന്നു. ഡാഷ്ബോർഡ് ഫീച്ചർ ഫ്ലാഗുകളിൽ നിന്ന് കോൺഫിഗർ ചെയ്യാവുന്നതാണ്; പങ്കിട്ട ഇൻസ്റ്റൻസുകളിൽ സുരക്ഷാ അപകടസാധ്യതയുണ്ട്. | +| `NO_LOG_API_KEY_IDS` | _(ഒഴിഞ്ഞത്)_ | `src/lib/compliance/index.ts` | അഭ്യർത്ഥന ലോഗിംഗ് ഒഴിവാക്കുന്ന കോമയാൽ വേർതിരിച്ച API കീ ഐഡികൾ (GDPR പാലനം). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(സെറ്റ് ചെയ്യാത്തത് = പരിധിയില്ലാത്തത്)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` കോളം null ആയ API കീകൾക്ക് ബാധകമാകുന്ന പ്രതിദിന അഭ്യർത്ഥന ബഡ്ജറ്റ്. സെറ്റ് ചെയ്യാത്തതോ ഒഴിഞ്ഞതോ: വ്യക്തമായ പരിധിയില്ല (#2289, #11017). `0` ഉം സമാനമാണ് (പരിധിയില്ലാത്തത്). പോസിറ്റീവ് പൂർണ്ണസംഖ്യ N, N/ദിവസം, 5N/ആഴ്ച, 20N/മാസം എന്നിവ പ്രാപ്തമാക്കുന്നു. തെറ്റായ നോൺ-എംപ്റ്റി മൂല്യങ്ങൾ പഴയ 1000/ദിവസം, 5000/ആഴ്ച, 20000/മാസം വിൻഡോകളിലേക്ക് തിരികെ പോകുന്നു. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | അനുവദനീയമായ പരമാവധി അഭ്യർത്ഥന ബോഡി വലുപ്പം. ഈ പരിധി കവിയുന്ന പേലോഡുകൾ നിരസിക്കുന്നു. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ഈ പരിധിയിലോ അതിനു മുകളിലോ ഉള്ള യഥാർത്ഥ അഭ്യർത്ഥന ബോഡികൾ JSON പാഴ്സിംഗിന് മുമ്പ് അറ്റോമിക് പ്രോസസ്-ലോക്കൽ ഹെവിവെയ്റ്റ് അഡ്മിഷൻ ലീസിനെടുക്കുന്നു (BYTE പാത, `POST /v1/responses` ഉൾപ്പെടെ). ഘടനാപരമായി ഭാരമുള്ളതിന് സമാനമായ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ആരോഗ്യകരമായ ഹെഡ്റൂം എസ്കേപ്പ്; എന്നിരുന്നാലും `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) എന്നിവയാൽ പരിമിതപ്പെടുത്തിയിരിക്കുന്നു, അതിനാൽ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) തിരികെ വരുന്നില്ല. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | നഷ്ടമായതോ, അസാധുവായതോ, തെറ്റായതോ ആയ `Content-Length` ഉള്ള അഭ്യർത്ഥനകൾ ഉൾപ്പെടെ, ബൗണ്ടഡ് ഇൻജഷൻ സമയത്ത് വായിച്ച ബൈറ്റുകൾക്കെതിരെ നടപ്പിലാക്കുന്ന ചാറ്റ്-റൂട്ട് ഹാർഡ് ക്യാപ്; അധികമുള്ളവയ്ക്ക് `413` ലഭിക്കുന്നു. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(സെറ്റ് ചെയ്തിട്ടില്ല — അഭ്യർത്ഥന-എണ്ണം ക്യാപ് ഇല്ല)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ഈ ലെഗസി അഭ്യർത്ഥന-എണ്ണം ക്യാപ് ഇപ്പോൾ വ്യക്തമായി സജ്ജീകരിക്കുമ്പോൾ മാത്രമേ ബാധകമാകൂ. സജ്ജീകരിക്കാതെ വിടുമ്പോൾ (സ്ഥിരസ്ഥിതി), ഹെവിവെയ്റ്റ് ചാറ്റ് അഡ്മിഷൻ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` വഴി നിയന്ത്രിക്കപ്പെടുന്നു — ഇത് ഒരു പ്രോസസ്സിന്റെ യഥാർത്ഥ മെമ്മറി സീലിംഗിൽ നിന്ന് സ്വയമേവ ലഭിക്കുന്ന ഒരു ബൈറ്റ് ബഡ്ജറ്റാണ് (ഒരു V8 ഹീപ്പ്). ഏകദേശം 750k-ടോക്കൺ വരുന്ന രണ്ട് `/v1/responses` ഏകദേശം 12 Gi ഹീപ്പുകൾ റദ്ദാക്കുന്നു (#7849) — ഇത് ഒരു **മെമ്മറി-ബഡ്ജറ്റ്** മുന്നറിയിപ്പാണ്, അല്ലാതെ 2 എന്ന ഹാർഡ് പ്രൊഡക്റ്റ് മാക്സ് അല്ല. ആരോഗ്യകരമായ ഒരു പ്രോസസ്സ് (ഷെഡ് റേഷ്യോയ്ക്ക് താഴെയുള്ള ഹീപ്പ്) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` വഴി കൂടുതൽ ഒരേസമയം പ്രവർത്തിക്കുന്ന നീണ്ട `/v1/responses` അനുവദിച്ചേക്കാം. പത്ത് നീണ്ട SSE ക്ലയന്റുകൾ (40–50) ഹീപ്പ് + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ആണ്, അല്ലാതെ “പരമാവധി 2” അല്ല. ഇത് “ഹോസ്റ്റ് ഉപയോഗിക്കാൻ” എന്നതിലേക്ക് അന്ധമായി ഉയർത്തുന്നത് #7849 വീണ്ടും അവതരിപ്പിക്കുന്നു. **N സ്വതന്ത്ര `DATA_DIR`s** (#11024) ഉള്ള **ഹീപ്പുകൾ** ഗുണിക്കുക; ഒരു SQLite ഫയലിൽ ഒരിക്കലും `replicas>1` ചെയ്യരുത്. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(സ്വയമേവ ലഭിക്കുന്നത്)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** സ്വയമേവ ലഭിക്കുന്ന ഇൻജസ്റ്റ് ബൈറ്റ് ബഡ്ജറ്റിനായുള്ള ഓവർറൈഡ് (ഇറുകിയ V8/cgroup മെമ്മറി സീലിംഗിന്റെ 25% നെ 8x ട്രാൻസിയന്റ് ആംപ്ലിഫിക്കേഷൻ കൊണ്ട് ഹരിച്ചാൽ ലഭിക്കുന്നത്). ലഭിച്ചതും വ്യക്തമായതുമായ മൂല്യങ്ങൾ 8 MiB–2 GiB ആയി പരിമിതപ്പെടുത്തുന്നു. ഫലപ്രദമായ ബഡ്ജറ്റിനേക്കാൾ വലിയ ഒരു ബോഡിക്ക് `413 body_exceeds_budget` എന്ന പിശക് ഉടനടി ലഭിക്കുന്നു; വ്യക്തിഗതമായി സേവനം നൽകാൻ കഴിയുന്ന ബോഡികൾ തമ്മിലുള്ള മത്സരം `503` ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കാവുന്നതാണ്. 40–50 ഒരേസമയം പ്രവർത്തിക്കുന്ന നീണ്ട SSE ക്ലയന്റുകൾ ഈ ബഡ്ജറ്റും ഹീപ്പും ചേർന്നതാണ്, അല്ലാതെ ഒരു ഹാർഡ് “പരമാവധി 2” അല്ല. ട്യൂൺ ചെയ്യുന്നതിന് മുമ്പ് `/api/monitoring/health` എന്നതിൽ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` വായിക്കുക. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | ബൈറ്റ്, സ്ട്രക്ച്ചർ ഹെവിവെയ്റ്റ് അഡ്മിഷനായുള്ള ഹീപ്പ്-പ്രഷർ ഷെഡ് റേഷ്യോ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` കഴിഞ്ഞുള്ള ഒരു കൺകറന്റ് ഹെവിവെയ്റ്റ് അഭ്യർത്ഥനയ്ക്ക് ഹീപ്പ് ഈ അനുപാതത്തിൽ അല്ലെങ്കിൽ അതിന് മുകളിലാണെങ്കിൽ മാത്രമേ `503` എന്ന റീട്രൈ ചെയ്യാവുന്ന പിശക് ലഭിക്കൂ; ആരോഗ്യകരമായ ഒരു ഹീപ്പിൽ ഇത് ഹെൽത്തി-ഹെഡ്റൂം വഴി അനുവദിക്കപ്പെടുന്നു. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (സ്ഥിരസ്ഥിതി `1`) | `src/shared/middleware/chatBodyAdmission.ts` | സ്ട്രക്ച്ചർ, ബൈറ്റ് (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ന് തുല്യമോ അതിൽ കൂടുതലോ ഉള്ള ബോഡികൾ ഉൾപ്പെടെ) എന്നിവയിലെ ആരോഗ്യകരമായ-ഹീപ്പ് ഫാസ്റ്റ് പാത്തിനായുള്ള ബൗണ്ടഡ് അധിക ശേഷി (#10437). ഈ പരിധി ഇല്ലായിരുന്നെങ്കിൽ, തിരക്കുള്ളതും എന്നാൽ ആരോഗ്യകരവുമായ ഓരോ ഹീപ്പ് അഭ്യർത്ഥനയും പരിധിയില്ലാതെ അഡ്മിഷൻ ഒഴിവാക്കുമായിരുന്നു. ഈ എണ്ണം കൺകറന്റ് ലീസുകൾ ആരോഗ്യകരമായ-ഹീപ്പ് പാതയിലൂടെ സജീവമായാൽ, കൂടുതൽ തിരക്കുള്ള അഭ്യർത്ഥനകൾ യഥാർത്ഥ ഹീപ്പ് സമ്മർദ്ദത്തിൽ ഉപയോഗിക്കുന്ന അതേ ബൗണ്ടഡ്-വെയ്റ്റ്/ഷെഡ് പാതയിലേക്ക് മാറുന്നു. `0` ഈ ബൈപാസ് പൂർണ്ണമായും പ്രവർത്തനരഹിതമാക്കുന്നു. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ബോഡി ബൈറ്റ് പരിധിക്ക് താഴെയാണെങ്കിൽ പോലും ഒരു ചാറ്റ് അഭ്യർത്ഥനയെ ഹെവിവെയ്റ്റ് ആയി തരംതിരിക്കുന്ന സന്ദേശങ്ങളുടെ എണ്ണം. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ബോഡി ബൈറ്റ് പരിധിക്ക് താഴെയാണെങ്കിൽ പോലും ഒരു ചാറ്റ് അഭ്യർത്ഥനയെ ഹെവിവെയ്റ്റ് ആയി തരംതിരിക്കുന്ന ടൂളുകളുടെ എണ്ണം. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ഒരു അഭ്യർത്ഥനയെ ഹെവിവെയ്റ്റ് ആയി തരംതിരിക്കുന്ന കൺസർവേറ്റീവ് സ്ട്രിംഗ്-സൈസ് ടോക്കൺ എസ്റ്റിമേറ്റ്; ഇത് ഒരു അഡ്മിഷൻ-കോസ്റ്റ് പ്രോക്സിയാണ്, പ്രൊവൈഡർ ബില്ലിംഗ് ടോക്കണൈസേഷൻ അല്ല. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (പ്രവർത്തനരഹിതമാക്കി) | `src/shared/middleware/chatBodyAdmission.ts` | ഓപ്ഷണൽ ഓപ്റ്റ്-ഇൻ ചാറ്റ് ഹിസ്റ്ററി ക്യാപ്. സ്ഥിരസ്ഥിതിയായി പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു: ഒരു സന്ദേശങ്ങളുടെ എണ്ണം ഡിപ്ലോയ്മെന്റ് പോളിസിയാണ്, ഒരു അഭ്യർത്ഥനയുടെ സാർവത്രിക സ്വഭാവമല്ല, ഇവിടെ ക്യാപ് ചെയ്യുന്നത് കംപ്രഷൻ പൈപ്പ്ലൈൻ അവയെ സേവിക്കാൻ കഴിയുന്നതാക്കുന്നതിന് മുമ്പ് ഒരു ടെർമിനൽ `413` ഉപയോഗിച്ച് സംഭാഷണങ്ങളെ നിരസിക്കുന്നു. ഹീപ്പ് വളർച്ച `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`, ഹീപ്പ്-പ്രഷർ ഷെഡ് എന്നിവയാൽ പരിമിതപ്പെടുത്തിയിരിക്കുന്നു. ഹാർഡ് സീലിംഗ് ആവശ്യമുള്ള മെമ്മറി-പരിമിതമായ ഡിപ്ലോയ്മെന്റുകളിൽ ഒരു പോസിറ്റീവ് മൂല്യം സജ്ജീകരിക്കുക; അധികമുള്ളവയ്ക്ക് സ്ട്രക്ച്ചേർഡ് കോംപാക്റ്റ്-റിക്വയേർഡ് `413` ലഭിക്കുന്നു. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | മെമ്മറിയിലേക്ക് പൂർണ്ണമായി ബഫർ ചെയ്ത ഒരു നോൺ-സ്ട്രീമിംഗ് അപ്സ്ട്രീം പ്രതികരണത്തിനുള്ള ഹാർഡ് ക്യാപ്. ഇതിന് മുകളിലാണെങ്കിൽ അപ്സ്ട്രീം റീഡർ റദ്ദാക്കപ്പെടുകയും ഹീപ്പ് തീരുന്നത് വരെ പരിധിയില്ലാത്ത സ്ട്രിംഗ് വളർത്തുന്നതിന് പകരം അഭ്യർത്ഥന വേഗത്തിൽ പരാജയപ്പെടുകയും ചെയ്യുന്നു. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | അപ്സ്ട്രീം പ്രതികരണ ഹെഡറുകളിൽ നിന്ന് കൈമാറുന്ന പരമാവധി വയർ ബൈറ്റുകൾ. ബഡ്ജറ്റ് കവിഞ്ഞാൽ, സാധാരണ റിവേഴ്സ്-പ്രോക്സി ഹെഡർ പരിധിക്കുള്ളിൽ നിൽക്കുന്നതിനായി കുറഞ്ഞ മുൻഗണനയുള്ള ഹെഡറുകൾ (ഉദാഹരണത്തിന്, കസ്റ്റം `x-codex-*`, `x-oai-request-id`) ഒഴിവാക്കപ്പെടും. കൂടുതൽ അപ്സ്ട്രീം മെറ്റാഡാറ്റ കൈമാറുന്നതിനായി ഇത് ഉയർന്ന മൂല്യത്തിൽ സജ്ജീകരിക്കുക, ഇത് വലിയ പ്രതികരണ ഹെഡർ വലുപ്പത്തിന് കാരണമാകും. | +| `CORS_ORIGIN` | _(unset)_ | `src/server/cors/origins.ts` | പഴയ സിംഗിൾ-ഒറിജിൻ CORS അനുമതിപ്പട്ടിക. പുതിയ വിന്യാസങ്ങൾക്കായി `CORS_ALLOWED_ORIGINS` തിരഞ്ഞെടുക്കുക. CORS ക്രോസ്-ഒറിജിൻ ബ്രൗസർ API ക്ലയന്റുകൾക്ക് മാത്രമുള്ളതാണ്; പ്രാമാണീകരിച്ച ഡാഷ്ബോർഡ് എഴുത്തുകൾക്ക് സെഷൻ-ബൗണ്ട് CSRF സംരക്ഷണത്തോടുകൂടിയ സെയിം-ഒറിജിൻ അഭ്യർത്ഥനകളാണ് ഉപയോഗിക്കുന്നത്. | +| `CORS_ALLOWED_ORIGINS` | _(unset)_ | `src/server/cors/origins.ts` | കോമയാൽ വേർതിരിച്ച CORS അനുമതിപ്പട്ടിക. `CORS_ALLOW_ALL=true` എന്ന് വ്യക്തമായി കോൺഫിഗർ ചെയ്തിട്ടില്ലെങ്കിൽ വൈൽഡ്കാർഡ് അയക്കില്ല. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ഏതൊരു ബ്രൗസർ `Origin` നെയും പ്രതിധ്വനിപ്പിക്കാനുള്ള ഡെവലപ്മെന്റിന് മാത്രമുള്ള ഒരു എസ്കേപ്പ് ഹാച്ച്. പങ്കിട്ടതോ പ്രൊഡക്ഷൻ വിന്യാസങ്ങളിലോ ഇത് പ്രവർത്തനക്ഷമമാക്കരുത്. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | പഴയ അപരനാമം. ഇതിന്റെ ഡാഷ്ബോർഡ് ടോഗിൾ (ഒരു DB ഓവറൈഡ്) ഈ എൻവയോൺമെന്റ് മൂല്യത്തിന് മുമ്പ് വായിക്കപ്പെടുന്നു; ഇവയിലേതിലും `false`, `0`, `no` അല്ലെങ്കിൽ `off` ആണെങ്കിൽ, ആ ഫ്ലാഗിന്റെ ഡാഷ്ബോർഡ് ടോഗിൾ ഓഫായി സൂക്ഷിച്ചിരിക്കുമ്പോഴും പോലും, `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ചെയ്യുന്നതുപോലെ ഹോസ്റ്റ് പരിശോധനകൾ ഓഫാക്കുന്നു; മറ്റ് മൂല്യങ്ങൾ ഗാർഡിനെ താഴെയുള്ള രണ്ട് ഫ്ലാഗുകൾക്ക് വിട്ടുകൊടുക്കുന്നു. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | ഗാർഡ് മോഡ് പിന്തുടരുന്ന പ്രൊവൈഡർ പാതകളിൽ — URL സാധൂകരണം, മോഡൽ കണ്ടെത്തൽ, പ്രൊവൈഡർ-നോഡ് ബേസ് URL-കൾ (റിമോട്ട് റീറാങ്ക് നോഡുകൾ ഉൾപ്പെടെ), പ്രോക്സി-ഫാൾബാക്ക് ടെസ്റ്റ്, കൂടാതെ റിമോട്ട് ഇമേജ്/മീഡിയ ഫെച്ച് പോലുള്ള മറ്റ് പ്രൊവൈഡർ-സമീപ ഔട്ട്ബൗണ്ട് പാതകളിലും — ഔട്ട്ബൗണ്ട് URL ഗാർഡിന്റെ ഹോസ്റ്റ് പരിശോധനകൾ, ക്ലൗഡ്-മെറ്റാഡാറ്റ ബ്ലോക്ക് ഉൾപ്പെടെ (169.254.0.0/16 മുഴുവൻ, metadata.google.internal), ഓഫാക്കുന്നു. ക്ലൗഡ്-മെറ്റാഡാറ്റ/ലിങ്ക്-ലോക്കൽ എൻഡ്പോയിന്റുകൾ ഒഴികെ, സ്വകാര്യ വെബ്ഹുക്ക് ടാർഗെറ്റുകളെയും ഇത് അനുവദിക്കുന്നു — ഈ ഫ്ലാഗ് ഓണായിരിക്കുമ്പോഴും അവ തടയപ്പെട്ടിരിക്കും. `BaseExecutor.execute()` വഴി അയയ്ക്കുന്ന ചാറ്റ് അഭ്യർത്ഥനകളുടെ ക്ലൗഡ്-മെറ്റാഡാറ്റ പരിശോധന നിലനിർത്തുന്നു. ലോക്കൽ പ്രൊവൈഡർമാർ ഡിഫോൾട്ടുകളോടെ പ്രവർത്തിക്കുന്നു: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ഡിഫോൾട്ട് `true`) സാധൂകരണ, കണ്ടെത്തൽ, പ്രൊവൈഡർ-നോഡ് പാതകളിൽ ലോക്കൽ, LAN URL-കൾ ഇതിനകം അനുവദിക്കുന്നു; പ്രോക്സി-ഫാൾബാക്ക് ടെസ്റ്റും സ്വകാര്യ വെബ്ഹുക്ക് ടാർഗെറ്റുകളും ഈ ഫ്ലാഗിനെ മാത്രം പരിശോധിക്കുന്നു, ഇത് ഓഫായിരിക്കുമ്പോൾ ലോക്കൽ/LAN ഹോസ്റ്റുകളെ തടയുന്നു. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`-നെ ഓവറൈഡ് ചെയ്യുന്നു. ഡാഷ്ബോർഡ് ടോഗിളിൽ നിന്ന് സൂക്ഷിച്ച മൂല്യത്തിന് ഈ വേരിയബിളിനെക്കാൾ മുൻഗണനയുണ്ട്. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | പ്രൊവൈഡർ സാധൂകരണം, മോഡൽ കണ്ടെത്തൽ, ചാറ്റ്, ഗാർഡ് മോഡ് നിയന്ത്രിക്കുന്ന മറ്റ് പ്രൊവൈഡർ-സമീപ ഔട്ട്ബൗണ്ട് പാതകൾ എന്നിവയ്ക്കായി ലോക്കൽ/സ്വകാര്യ വിലാസങ്ങളിലുള്ള (127.0.0.1, localhost, LAN, സ്വകാര്യ ശ്രേണികൾ) പ്രൊവൈഡർ URL-കൾ അനുവദിക്കുന്നു. **ഡിഫോൾട്ട് `true`** (ലോക്കൽ-ആദ്യം): അപ്പോൾ ഗാർഡ് ക്ലൗഡ്-മെറ്റാഡാറ്റ എൻഡ്പോയിന്റുകളെ — 169.254.0.0/16 മുഴുവനും അറിയപ്പെടുന്ന മെറ്റാഡാറ്റ ഹോസ്റ്റ്നെയിമുകളും (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) — തടയുന്നു. സ്വകാര്യ, ലൂപ്പ്ബാക്ക് ഹോസ്റ്റുകളെയും തടയുന്ന പൊതു-മാത്രം മോഡിനായി `false` സജ്ജീകരിക്കുക. ഏത് DNS ലുക്കപ്പിനും മുമ്പ്, എഴുതിയിരിക്കുന്നതുപോലെ ഗാർഡ് ഹോസ്റ്റ്നെയിമോ IP ലിറ്ററലോ പരിശോധിക്കുന്നു; പൊതു-മാത്രം മോഡിൽ safe-fetch റാപ്പർ കൂടി ഹോസ്റ്റ്നെയിം റിസോൾവ് ചെയ്യുകയും സ്വകാര്യ ഉത്തരം ലഭിച്ചാൽ നിരസിക്കുകയും ചെയ്യുന്നു. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ആ വരിയിൽ ലിസ്റ്റ് ചെയ്ത പാതകളിൽ ഈ പരിശോധനകൾ ഓഫാക്കുന്നു. LM Studio, Ollama, vLLM, Llamafile, Triton പോലുള്ള ബിൽറ്റ്-ഇൻ ലോക്കൽ പ്രൊവൈഡർമാർ കീ സാധൂകരണത്തിനും ചാറ്റിനും എല്ലാ ഹോസ്റ്റ് പരിശോധനകളും ഒഴിവാക്കുന്നു. ഡാഷ്ബോർഡ് ടോഗിളിൽ നിന്ന് സൂക്ഷിച്ച മൂല്യത്തിന് ഈ വേരിയബിളിനെക്കാൾ മുൻഗണനയുണ്ട്. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` റൂട്ടുകൾക്ക് (ട്രാൻസ്ക്രിപ്ഷനുകൾ, സ്പീച്ച്, വിവർത്തനങ്ങൾ) ലോക്കൽഹോസ്റ്റിന് പുറത്ത് ഹോസ്റ്റ് ചെയ്തിട്ടുള്ള ഒരു OpenAI-അനുയോജ്യമായ പ്രൊവൈഡർ നോഡ് ഉപയോഗിക്കാൻ അനുവദിക്കുക. സ്ഥിരസ്ഥിതിയായി പ്രവർത്തനരഹിതമാണ് — ഓഡിയോ ഒരു റിമോട്ട് ഹോസ്റ്റിലേക്ക് റൂട്ട് ചെയ്യുന്നത് എഗ്രസ് ഐഡന്റിറ്റി മാറ്റുകയും ഒരു ഓപ്പറേറ്ററുടെ വ്യക്തമായ തീരുമാനമായിരിക്കുകയും വേണം. ലൂപ്പ്ബാക്ക്/സ്വകാര്യ നോഡുകൾ (localhost, 127.0.0.1, 172.16-31.x) എല്ലായ്പ്പോഴും അനുവദനീയവും ബാധിക്കപ്പെടാത്തതുമാണ്. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (കൂടാതെ മെമ്മറി എഞ്ചിന്റെ ലൂപ്പ്ബാക്ക് റീറാങ്ക് സ്റ്റെപ്പ്) ലോക്കൽഹോസ്റ്റിന് പുറത്ത് ഹോസ്റ്റ് ചെയ്തിട്ടുള്ള ഒരു OpenAI-അനുയോജ്യമായ പ്രൊവൈഡർ നോഡ് ഉപയോഗിക്കാൻ അനുവദിക്കുക — ഒരു LAN ബോക്സ് അല്ലെങ്കിൽ TEI, Infinity, vLLM മുതലായവ പ്രവർത്തിക്കുന്ന ഒരു Tailscale പിയർ. സ്ഥിരസ്ഥിതിയായി പ്രവർത്തനരഹിതമാണ് — ഒരു റിമോട്ട് ഹോസ്റ്റിലേക്ക് റൂട്ട് ചെയ്യുന്നത് എഗ്രസ് ഐഡന്റിറ്റി മാറ്റുകയും ഒരു ഓപ്പറേറ്ററുടെ വ്യക്തമായ തീരുമാനമായിരിക്കുകയും വേണം. ലൂപ്പ്ബാക്ക് നോഡുകൾ (localhost, 127.0.0.1, 172.16-31.x) എല്ലായ്പ്പോഴും അനുവദനീയവും ബാധിക്കപ്പെടാത്തതുമാണ്. ഒരു റിമോട്ട് നോഡിന്റെ ബേസ് URL-ും പ്രൊവൈഡർ ഔട്ട്ബൗണ്ട് URL പോളിസി (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) പാസ്സാക്കണം. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, പാസ്വേഡ് ലോഗിൻ പ്രവർത്തനരഹിതമാക്കുക, അങ്ങനെ ഉപയോക്താക്കൾക്ക് OIDC സിംഗിൾ സൈൻ-ഓൺ വഴി മാത്രമേ പ്രാമാണീകരിക്കാൻ കഴിയൂ. `OIDC_DISABLE_PASSWORD_LOGIN` എന്ന അപരനാമവും സ്വീകാര്യമാണ്; അതേ കീയിലുള്ള ഡാഷ്ബോർഡ് ഫീച്ചർ ഫ്ലാഗിനാണ് മുൻഗണന. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ന്റെ അപരനാമം (#10889). | ### സുരക്ഷാ പരിശോധനാ പട്ടിക diff --git a/docs/i18n/mr/CONTRIBUTING.md b/docs/i18n/mr/CONTRIBUTING.md index b3000894eca7..6fa11f4365d3 100644 --- a/docs/i18n/mr/CONTRIBUTING.md +++ b/docs/i18n/mr/CONTRIBUTING.md @@ -387,20 +387,20 @@ OAuth क्रेडेन्शियल्स `src/lib/oauth/constants/oauth. ## Pull Request तपासणी सूची -- [ ] चाचण्या उत्तीर्ण (`npm test`) -- [ ] लिंटिंग उत्तीर्ण (`npm run lint`) -- [ ] बिल्ड यशस्वी (`npm run build`) -- [ ] नवीन सार्वजनिक फंक्शन्स आणि इंटरफेसेससाठी TypeScript प्रकार जोडले +- [ ] चाचण्या यशस्वी होतात (`npm test`) +- [ ] लिंटिंग यशस्वी होते (`npm run lint`) +- [ ] बिल्ड यशस्वी होते (`npm run build`) +- [ ] नवीन सार्वजनिक फंक्शन्स आणि इंटरफेसेससाठी TypeScript प्रकार जोडले आहेत - [ ] कोणतीही हार्डकोड केलेली गुपिते किंवा फॉलबॅक मूल्ये नाहीत -- [ ] सार्वजनिक अपस्ट्रीम क्रेडेन्शियल्स `resolvePublicCred()` द्वारे एम्बेड केलेली आहेत ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) पहा), लिटरल्स म्हणून कधीही नाहीत -- [ ] त्रुटी प्रतिसाद `buildErrorBody()` / `sanitizeErrorMessage()` मधून पाठवले जातात — प्रतिसादांच्या बॉडीमध्ये कोणतेही रॉ स्टॅक ट्रेस नाहीत ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) पहा) +- [ ] सार्वजनिक अपस्ट्रीम क्रेडेन्शियल्स `resolvePublicCred()` द्वारे एम्बेड केली आहेत ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) पहा), लिटरल्स म्हणून कधीही नाही +- [ ] त्रुटी प्रतिसाद `buildErrorBody()` / `sanitizeErrorMessage()` द्वारे पाठवले जातात — प्रतिसादांच्या बॉडीमध्ये रॉ स्टॅक ट्रेसेस नाहीत ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) पहा) - [ ] शेल कमांड्स (`exec` / `spawn`) रनटाइम मूल्ये स्ट्रिंग इंटरपोलेशनद्वारे नव्हे, तर `env` द्वारे पाठवतात - [ ] सर्व इनपुट्स Zod स्कीमांद्वारे प्रमाणित केले आहेत -- [ ] वापरकर्त्यांना दिसणाऱ्या बदलांसाठी `changelog.d/{features|fixes|maintenance}/-.md` अंतर्गत चेंजलॉग **फ्रॅगमेंट** जोडला आहे ([`changelog.d/README.md`](./changelog.d/README.md) पहा) — `CHANGELOG.md` थेट संपादित करू **नका**; फ्रॅगमेंट्स रिलीजच्या वेळी एकत्रित केले जातात आणि PRs दरम्यान कधीही संघर्ष निर्माण करत नाहीत -- [ ] दस्तऐवजीकरण अद्ययावत केले (लागू असल्यास) +- [ ] वापरकर्त्यांना दिसणाऱ्या बदलांसाठी `changelog.d/{features|fixes|maintenance}/-.md` अंतर्गत चेंजलॉग **फ्रॅगमेंट** जोडला आहे ([`changelog.d/README.md`](./changelog.d/README.md) पहा) — `CHANGELOG.md` थेट संपादित करू **नका**; फ्रॅगमेंट्स रिलीजच्या वेळी एकत्रित केले जातात आणि PRs दरम्यान त्यांचा कधीही संघर्ष होत नाही +- [ ] दस्तऐवजीकरण अद्ययावत केले आहे (लागू असल्यास) - [ ] कोणतेही नवीन CodeQL / Secret-Scanning अलर्ट उघडलेले नाहीत किंवा प्रत्येक अलर्ट संबंधित `docs/security/` दस्तऐवजाचा संदर्भ देणाऱ्या तांत्रिक समर्थनासह डिसमिस केला आहे -- [ ] चाइल्ड प्रोसेसेस स्पॉन करणारे रूट्स (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` मधील `isLocalOnlyPath()` म्हणून वर्गीकृत केले आहेत — [कठोर नियम #15](docs/security/ROUTE_GUARD_TIERS.md) पहा -- [ ] कमिट संदेशांमध्ये कोणतेही `Co-Authored-By` ट्रेलर्स नाहीत — कमिट्स केवळ रिपॉझिटरी मालकाच्या Git ओळखीखाली दिसले पाहिजेत (कठोर नियम #16) +- [ ] चाइल्ड प्रोसेसेस सुरू करणारे रूट्स (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` मध्ये `isLocalOnlyPath()` म्हणून वर्गीकृत केले आहेत — [कठोर नियम #15](docs/security/ROUTE_GUARD_TIERS.md) पहा +- [ ] कमिट संदेशांमध्ये AI/bot `Co-authored-by` ट्रेलर्स नाहीत (कठोर नियम #16) — ज्यांचे काम पुन्हा वापरले आहे अशा मानवी सहकाऱ्यांना मानक `Co-authored-by: Name ` ट्रेलर्सद्वारे श्रेय दिले आहे --- diff --git a/docs/i18n/mr/docs/architecture/QUALITY_GATES.md b/docs/i18n/mr/docs/architecture/QUALITY_GATES.md index 9f4f1621ef3d..04148937ccf1 100644 --- a/docs/i18n/mr/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/mr/docs/architecture/QUALITY_GATES.md @@ -15,256 +15,257 @@ --- -## गेट इन्व्हेंटरी आणि अंमलबजावणी प्रोफाइल +## गेट इन्व्हेंटरी आणि अंमलबजावणी प्रोफाइल्स -### उमेदवार प्रवेश +### उमेदवार स्वीकृती CI आणि Quality Gates वर्कफ्लो प्रत्येकी एक स्थिर निकाल देतात: `Gate / CI` आणि -`Gate / Quality`. त्यांचे आवृत्तीकृत प्रवेश धोरण प्रत्येक अपस्ट्रीम जॉब आवश्यक किंवा सल्लागार म्हणून -सूचीबद्ध करते. लागू होणारा आवश्यक जॉब यशस्वी झालाच पाहिजे: अनुपस्थित, -रद्द केलेले, वगळलेले, प्रलंबित आणि अज्ञात निकाल PASS स्थापित करू शकत नाहीत. वैध -केवळ-दस्तऐवज किंवा केवळ-कॅटलॉग वर्गीकरणामुळे कोड लेन लागू न होणारी ठरू शकते; +`Gate / Quality`. त्यांचे आवृत्तीकृत स्वीकृती धोरण प्रत्येक अपस्ट्रीम जॉब आवश्यक +किंवा सल्लात्मक म्हणून सूचीबद्ध करते. लागू असलेला आवश्यक जॉब यशस्वी होणे आवश्यक आहे: +गहाळ, रद्द केलेले, वगळलेले, प्रलंबित आणि अज्ञात निकाल PASS सिद्ध करू शकत नाहीत. वैध +केवळ-दस्तऐवज किंवा केवळ-कॅटलॉग वर्गीकरणामुळे कोड लेन अनुपयुक्त ठरू शकते; मसुदा PR हा स्वीकारलेला उमेदवार नाही. `hotfix` लेबल पुराव्याची आवश्यकता रद्द करत नाही. -दोन्ही वर्कफ्लो PRs आणि main/release शाखांवरील pushes, manual dispatch आणि -merge-group events समाविष्ट करतात. Push, dispatch आणि merge-group पूर्ण निवड चालवतात. Forks -आणि merge groups अशा जॉबसाठी hosted runners वापरतात जे अन्यथा self-hosted -runners निवडतात; rollout पूर्वी पुरेशी hosted क्षमता सत्यापित केली पाहिजे. +दोन्ही वर्कफ्लो PRs आणि main/release शाखांवरील pushes, मॅन्युअल dispatch आणि +merge-group इव्हेंट्स समाविष्ट करतात. Push, dispatch आणि merge-group संपूर्ण निवड चालवतात. Forks +आणि merge groups अशा जॉबसाठी hosted runners वापरतात, जे अन्यथा self-hosted +runners निवडतात; रोलआउटपूर्वी पुरेशी hosted क्षमता सत्यापित करणे आवश्यक आहे. -प्रत्येक JSON पावती check out केलेला SHA, workflow run आणि attempt ओळखते. -CLI checkout/event SHA विसंगती नाकारते. Workflow tests धोरणातील सदस्यत्व -निकाल जॉबच्या `needs` सूचीशी बांधतात, जेणेकरून नवीन किंवा काढलेली लेन नकळत अदृश्य होऊ शकत नाही. -पावत्या त्यांच्या स्वतःच्या वर्कफ्लोला समाविष्ट करतात, प्रकाशन, deployment किंवा विद्यमान -सल्लागार scanner चे अंतर्गत कार्य नाही. Branch rules मध्ये दोन्ही check names सक्रिय करणे हा -स्वतंत्र प्रशासकीय बदल आहे; हे jobs जोडल्याने स्वतःहून branch संरक्षित होत नाही. +प्रत्येक JSON पावती checkout केलेला SHA, workflow run आणि attempt ओळखते. +CLI checkout/event SHA विसंगती नाकारते. Workflow चाचण्या धोरणातील सदस्यत्व +निकाल जॉबच्या `needs` सूचीशी बांधतात, ज्यामुळे एखादी नवीन किंवा काढून टाकलेली लेन लक्षात न येता अदृश्य होऊ शकत नाही. +पावत्या त्यांच्या स्वतःच्या workflow ला समाविष्ट करतात; publication, deployment किंवा +विद्यमान advisory scanner च्या अंतर्गत बाबींना नाही. Branch rules मध्ये दोन्ही check names सक्रिय करणे हा +स्वतंत्र प्रशासकीय बदल आहे; हे jobs जोडल्याने आपोआप branch संरक्षित होत नाही. -### स्थिर स्कॅन इन्व्हेंटरी +### स्टॅटिक स्कॅन इन्व्हेंटरी आवृत्तीकृत npm-alias इन्व्हेंटरी आणि static-scan सदस्यत्व `config/quality/gate-manifest.json` मध्ये आहेत. Script names आणि अचूक commands -`package.json` शी पडताळण्यासाठी `npm run check:gate-manifest` चालवा; भर, काढणे आणि -command drift मुळे local hook तसेच CI मधील change-classification jobs दोन्ही अयशस्वी होतात. -Alias हा workflow job, matrix instance किंवा test case नाही: या संख्या -परस्पर अदलाबदल करण्यायोग्य म्हणून सादर करू नयेत. - -निवडलेले aliases अंमलात न आणता तपासण्यासाठी `npm run quality:scan -- --list` किंवा `npm run quality:scan:fast -- --list` -वापरा. Runner npm entrypoint ला कॉल करतो, त्यामुळे त्याचे runtime (कॉन्फिगर केलेले असल्यास Bun सह) जतन केले जाते. -Manifest त्या profiles बाहेरील aliases स्वतंत्रपणे कॉल केलेले म्हणून नोंदवतो आणि +`package.json` शी पडताळण्यासाठी `npm run check:gate-manifest` चालवा; additions, removals आणि +command drift मुळे local hook आणि CI मधील change-classification jobs दोन्ही अयशस्वी होतात. +Alias म्हणजे workflow job, matrix instance किंवा test case नाही: या संख्या +परस्पर बदलून वापरता येण्यासारख्या म्हणून सादर करू नयेत. + +निवडलेले aliases कार्यान्वित न करता तपासण्यासाठी `npm run quality:scan -- --list` किंवा +`npm run quality:scan:fast -- --list` वापरा. +Runner npm entrypoint कार्यान्वित करतो, त्यामुळे त्याचे runtime (जिथे कॉन्फिगर केले आहे तिथे Bun सहित) जतन केले जाते. +Manifest त्या प्रोफाइल्सच्या बाहेरील aliases स्वतंत्रपणे कार्यान्वित केलेले म्हणून नोंदवतो आणि read-only scan profiles मध्ये maintenance commands प्रतिबंधित आहेत. -ही profiles केवळ static scan समाविष्ट करतात. ती product tests, -coverage, packaging, external checks किंवा उमेदवाराची पूर्ण release acceptance प्रमाणित करत नाहीत. +ही प्रोफाइल्स केवळ static scan समाविष्ट करतात. ती product tests, +coverage, packaging, external checks किंवा उमेदवाराची संपूर्ण release acceptance प्रमाणित करत नाहीत. Workflow admission लिंक केलेले `config/quality/admission-policy.json` आणि `scripts/quality/admission-verdict.mjs` वापरते. Release-observer profiles स्वतंत्र राहतात; -त्यांचे लागू होणारे checks आणि receipts स्वतंत्रपणे तपासा. खालील गद्य -इन्व्हेंटरी हा संदर्भ आहे, एखादा gate प्रत्यक्षात चालला याचा पुरावा नाही. +त्यांचे लागू checks आणि receipts स्वतंत्रपणे तपासा. खालील गद्य +इन्व्हेंटरी हा संदर्भ आहे; gate प्रत्यक्षात चालला याचा पुरावा नाही. Scripts `scripts/check/` (policy gates) आणि `scripts/quality/` (ratchet engine) अंतर्गत आहेत. -CI चा अधिकृत स्रोत `.github/workflows/ci.yml` आहे. +CI साठी सत्याचा अधिकृत स्रोत `.github/workflows/ci.yml` आहे. -### Release PR जलद मार्ग (`quality.yml`) +### Release PR जलद-मार्ग (`quality.yml`) `.github/workflows/quality.yml` हे main/release PRs, protected-branch -pushes, dispatch आणि merge groups वरील CI ला पूरक आहे. PRs path-filtered जलद checks वापरतात. कायमस्वरूपी -अक्षम केलेला duplicate build काढण्यात आला; वास्तविक build/package/boot checks CI मध्ये कायम आहेत. - -| जॉब | व्याप्ती | अवरोधक | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `Docs Gates (fast-path)` | Docs/code PRs; API docs refs आणि docs-all | होय | -| `Fast Quality Gates` | Code PRs; static checks, typecheck, dashboard typecheck, प्रभावित unit tests | होय | -| `Forgotten sibling tests` | Code PRs; बदललेल्या modules पासून static consumers आणि candidate sibling tests पर्यंत मागोवा; barrel आणि dynamic-import paths हे सल्लागार diagnostics म्हणून नोंदवले जातात, तसेच संदर्भित allowlist अपवादही नोंदवले जातात | **सल्लागार** | -| `Vitest (fast-path)` | Code PRs; जलद vitest suite | होय | -| `Unit Tests fast-path` | Code PRs; 4-shard unit suite | होय | -| `No new ESLint warnings` | Code PRs; suppressions-aware lint guard | होय, forks सह | -| `Merge integrity (changelog + generated skills)` | Non-draft PRs; changelog आणि generated skill sync | होय, forks सह | - -#### विस्मृतीत गेलेल्या sibling tests चा अहवाल - -`npm run check:forgotten-sibling-tests` test-impact map च्या मागील import resolver चा पुनर्वापर करते. +pushes, dispatch आणि merge groups वर CI ला पूरक ठरते. PRs path-filtered fast checks वापरतात. कायमस्वरूपी +अक्षम केलेला duplicate build काढून टाकण्यात आला; वास्तविक build/package/boot checks CI मध्ये कायम आहेत. + +| जॉब | व्याप्ती | अवरोधक | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `Docs Gates (fast-path)` | Docs/code PRs; API docs refs आणि docs-all | होय | +| `Fast Quality Gates` | Code PRs; static checks, typecheck, dashboard typecheck, impacted unit tests | होय | +| `Forgotten sibling tests` | Code PRs; बदललेले modules हे static consumers आणि candidate sibling tests पर्यंत ट्रेस केले जातात; barrel आणि dynamic-import paths सल्लात्मक diagnostics म्हणून नोंदवले जातात, तसेच संदर्भित allowlist exceptions | **सल्लात्मक** | +| `Vitest (fast-path)` | Code PRs; fast vitest suite | होय | +| `Unit Tests fast-path` | Code PRs; 4-shard unit suite | होय | +| `No new ESLint warnings` | Code PRs; suppressions-aware lint guard | होय, forks सहित | +| `Merge integrity (changelog + generated skills)` | मसुदा नसलेले PRs; changelog आणि generated skill sync | होय, forks सहित | + +#### विसरलेल्या sibling tests चा अहवाल + +`npm run check:forgotten-sibling-tests` test-impact map मागील import resolver पुन्हा वापरते. प्रत्येक बदललेल्या production module साठी, candidate -test pull-request diff मध्ये अनुपस्थित असताना ते निर्धारक -`changed module/symbol -> static consumer -> candidate sibling test` साखळ्या नोंदवते. कोणत्याही blocking rollout पूर्वी calibration साठी Markdown summary आणि JSON result +test हा pull-request diff मध्ये अनुपस्थित असताना ते निश्चित +`changed module/symbol -> static consumer -> candidate sibling test` साखळ्या नोंदवते. +कोणत्याही blocking rollout पूर्वी calibration करण्यासाठी Markdown summary आणि JSON result `forgotten-sibling-tests` workflow artifact म्हणून राखून ठेवले जातात. -Barrel पुनर्निर्यात आणि डायनॅमिक आयात केवळ निराकरण निदानासाठी आहेत; त्या कधीही -अवरोधक निष्कर्ष निर्माण करत नाहीत. पुनरावलोकन केलेले अपवाद +Barrel री-एक्सपोर्ट्स आणि डायनॅमिक इम्पोर्ट्स केवळ रिझोल्यूशन निदानांसाठी आहेत; ते कधीही +ब्लॉक करणारा निष्कर्ष निर्माण करत नाहीत. पुनरावलोकन केलेले अपवाद `config/quality/forgotten-sibling-allowlist.json` मध्ये असतात. प्रत्येक नोंदीमध्ये consumer आणि candidate -चाचणीचे नाव असणे, विशिष्ट कारण देणे आणि GitHub issue किंवा pull request ची लिंक देणे आवश्यक आहे. सदोष नोंदी -सुरक्षितपणे अयशस्वी होतात. अपवाद हटवलेली candidate चाचणी किंवा `.skip`/`.todo` जोडणारा diff दडपू शकत नाहीत; -assertion कमकुवत करणे आणि इतर masking स्वतंत्रपणे अवरोधक असलेल्या -`check:test-masking` gate च्याच अखत्यारीत राहतात. - -### Job: `lint` - -`main` वरील प्रत्येक PR साठी चालते. अयशस्वी झाल्यास merge अवरोधित करते. - -| Script (`npm run ...`) | काय प्रमाणित करते | अवरोधक | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | -| `check:node-runtime` | Node.js आवृत्ती समर्थित श्रेणीत आहे | होय | -| `check:cycles` | चक्रीय आयात — सर्व `src/` + `open-sse/` modules | होय | -| `check:route-validation:t06` | सर्व routes वर Zod schemas उपस्थित आहेत (Tier 6 धोरण) | होय | -| `check:any-budget:t11` | `@ts-expect-error // any` ची संख्या budget पेक्षा जास्त नाही (Tier 11 catraca) | होय | -| `check:provider-consistency` | `providers.ts` मधील प्रत्येक provider साठी `providerRegistry.ts` मध्ये जुळणारी नोंद आहे (आणि उलटही, allowlist च्या मर्यादेत) | होय | -| `check:model-lifecycle` | हाताने देखभाल केल्या जाणाऱ्या तीन routing tables, तपासून commit केलेल्या lifecycle snapshot (#11503) शी सुसंगत राहतात: `FITNESS_TABLE` (`taskFitness.ts`) अशा कोणत्याही retired id ला score देत नाही ज्याकडे `REGISTRY` route करू शकते; प्रत्येक `BUILT_IN_ALIASES` target हा `REGISTRY` मध्ये उपस्थित आणि retired-id snapshot मध्ये अनुपस्थित असतो; `REGISTRY` मध्ये अजूनही असलेला प्रत्येक retired id forward केलेला असतो किंवा `allowedRetiredInCatalog` मध्ये सूचीबद्ध असतो; आणि कोणताही `DEFAULT_DEGRADATION_MAP` source किंवा target त्या snapshot मध्ये retired म्हणून दिसत नाही. यावरून एखादे model सध्या live upstream द्वारे उपलब्ध करून दिले जात आहे, हे सिद्ध होत नाही. Offline — `config/quality/model-lifecycle.json` शी तुलना करते; ते `npm run quality:refresh-model-lifecycle` वापरून हाताने refresh केले जाते (network; CI मध्ये जोडलेले नाही). `allowedRetiredInCatalog` हा burn-down ratchet आहे: केवळ tracking issue असल्यासच नोंद जोडा. | होय | -| `check:fetch-targets` | client-side `src/` मधील प्रत्येक `fetch("/api/...")` प्रत्यक्ष `route.ts` कडे resolve होतो | होय | -| `check:deps` | repo मधील प्रत्येक `package.json` मधील `npm install` करता येणाऱ्या सर्व deps या `dependency-allowlist.json` मध्ये आहेत; नवीन unpinned किंवा slopsquatted packages flag केली जातात | होय | -| `audit:deps` | `npm audit` (root + electron) — कोणत्याही high/critical advisories नाहीत (osv `check:vuln-ratchet` शी overlap होते; Rationalization Backlog पाहा) | होय | -| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashes, कोणतेही host overrides नाहीत | होय | -| `check:licenses` | उत्पादन अवलंबनांसाठी SPDX परवाना अनुमतसूची | होय | -| `check:tracked-artifacts` | कोणतेही बिल्ड आर्टिफॅक्ट्स / कमिट केलेले `node_modules` सिमलिंक्स नाहीत (husky pre-commit मध्येही चालते; pre-push हेतुपुरस्सर हलके ठेवले आहे — #6716) | होय | -| `check:ai-attribution` | PR कमिट्स, शीर्षक किंवा मजकुरामध्ये कोणताही AI/bot `Co-Authored-By` ट्रेलर किंवा AI-निर्मिती फूटर नाही — कठोर नियम #16 (`quality.yml` मधील PR→`release/**` साठीच्या fast-gates लूपमध्ये — इव्हेंट पेलोड वाचते, PR नसल्यास काहीही करत नाही — आणि `ci.yml` lint मधील PR→`main` साठीच्या केवळ-PR पायरीमध्ये; तसेच husky `commit-msg` हुकमध्ये; मानवी सह-लेखकांना परवानगी आहे; #14436) | -| `check:vitest-exclusions` | प्रत्येक Vitest वगळण्यात ट्रॅकिंग इश्यू नमूद केलेला असतो आणि ते `config/quality/vitest-exclusions.json` मध्ये दिसते (#13204) | होय | -| `check:file-size` | कोणतीही स्रोत फाइल प्रति-विस्तार मर्यादा ओलांडत नाही (रॅचेट: `frozen` सूचीतील गोठवलेल्या मोठ्या फाइल्स) | होय | -| `check:error-helper` | एक्झिक्युटर्स/हँडलर्समधील त्रुटी प्रतिसाद `buildErrorBody()` / `sanitizeErrorMessage()` वापरतात (कठोर नियम #12) | होय | -| `check:migration-numbering` | Migration SQL फाइल्सना कोणत्याही क्रमांकातील उड्या किंवा पुनरावृत्ती नसलेले सलग क्रमांक दिलेले आहेत | होय | -| `check:public-creds` | `publicCreds.ts` च्या बाहेर अक्षरशः नमूद केलेले OAuth `client_id`/`client_secret` किंवा Firebase Web कीज नाहीत (कठोर नियम #11) | होय | -| `check:db-rules` | `src/lib/db/` मॉड्यूल्सच्या बाहेर थेट SQL नाही; `localDb.ts` मधून बॅरल-इम्पोर्ट्स नाहीत (कठोर नियम #2/#5) | होय | -| `check:known-symbols` | त्यांच्या डिस्पॅच टेबल्समध्ये नोंदणीकृत प्रोव्हायडर एक्झिक्युटर्स, राउटिंग धोरणे आणि ट्रान्सलेटर्स डिस्कवरील फाइल्सशी जुळतात — कोणतीही अनाथ किंवा अघोषित सिंबल्स नाहीत | होय | -| `check:route-guard-membership` | चाइल्ड प्रोसेस सुरू करणारा प्रत्येक रूट `isLocalOnlyPath()` द्वारे वर्गीकृत केला जातो (कठोर नियम #15/#17) | होय | -| `check:test-discovery` | रेपोमधील प्रत्येक `*.test.ts` / `*.spec.ts` फाइल किमान एका टेस्ट रनरद्वारे संकलित केली जाते (रॅचेट: `test-discovery-baseline.json` मधील अनाथ फाइल्सची यादी केवळ कमी होऊ शकते) | होय | -| `check:agent-skills-sync` | व्युत्पन्न agent-skills आर्टिफॅक्ट्स त्यांच्या स्रोत कॅटलॉगशी जुळतात (कोणताही फरक नाही) | -| `check:provider-asset-provenance` | Provider लोगो/अॅसेट्ससाठी नोंदवलेली उगम नोंद उपलब्ध आहे | -| `lint:json` | JSON कॉन्फिगरेशन फाइल्स पार्स होतात आणि repo च्या lint नियमांची पूर्तता करतात | -| `typecheck:core` | त्रुटींशिवाय TypeScript संकलन (केवळ सल्लात्मक इशारे) | होय | -| `typecheck:noimplicit:core` | कठोर `noImplicitAny` — भविष्याभिमुख; आधीपासून अस्तित्वात असलेल्या अनेक कॉल साइट्सना अजूनही अॅनोटेशन्सची आवश्यकता आहे | **सल्लात्मक** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` पुरते मर्यादित `tsc` (#7033) — `typecheck:core` च्या निवडक 27-फाइल allowlist मध्ये कोणतेही dashboard TSX समाविष्ट नाही आणि `next build` देखील त्याचे कधीही type-checking करत नाही (`next.config.mjs` मध्ये `ignoreBuildErrors: true` सेट केले आहे), त्यामुळे तेथील orphaned-identifier regressions (#6625/#6909) CI साठी अदृश्य होते. गोठवलेल्या प्रति-फाइल/प्रति-TS-code संख्या baseline (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` प्रमाणेच stale-enforcement नमुना) विरुद्ध फरक तपासले जातात — baseline संख्येपलीकडील केवळ नवीन त्रुटींमुळे gate अयशस्वी होते; आधीपासून अस्तित्वात असलेली त्रुटी दुरुस्त केल्यावर `--update` वापरून baseline क्रमशः कमी करा. | होय | - -### Job: `quality-gate` - -`test-coverage` नंतर चालते. अयशस्वी झाल्यास merge अवरोधित करते. - -| स्क्रिप्ट | पडताळणी | अवरोधक | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `quality:collect` | `quality-metrics.json` तयार करते (ESLint चेतावणी संख्या, विलीन केलेल्या शार्ड अहवालातील कव्हरेज) | होय (रॅचेटच्या अपस्ट्रीममध्ये) | -| `quality:ratchet` | `quality-baseline.json` मधील प्रत्येक मेट्रिकची अधोगती झालेली नाही (ESLint चेतावण्या ≤ बेसलाइन; कव्हरेज ≥ बेसलाइन) | होय | -| `check:duplication` | कोड डुप्लिकेशन (jscpd@4) `quality-baseline.json` मधील बेसलाइनपेक्षा जास्त नाही | होय | -| `check:complexity` | फाइल-स्तरीय सायक्लोमॅटिक जटिलता कमाल मर्यादेपेक्षा जास्त नाही (कोर ESLint `complexity` + `max-lines-per-function`) | होय | -| `check:cognitive-complexity` | कॉग्निटिव्ह जटिलता रॅचेट (`eslint-plugin-sonarjs`) — स्वतंत्र ESLint पास; CI दोन्ही एकत्रित करून एकाच `check:complexity-ratchets` टप्प्याच्या रूपात चालवते | होय | -| `check:dead-code` | न वापरलेल्या एक्सपोर्ट्स / फाइल्सचे रॅचेट (knip) बेसलाइनच्या तुलनेत अधोगती होऊ देत नाही | होय | -| `check:compression-budget` | कॉम्प्रेशन बेंचमार्क बजेट — प्रत्येक इंजिनसाठी टोकन-बचतीच्या किमान मर्यादांची अधोगती होता कामा नये | होय | -| `check:type-coverage` | टाइप केलेल्या टक्केवारीचे रॅचेट (`type-coverage`) अधोगती होऊ देत नाही; मोठ्या प्रमाणात `typecheck:noimplicit:core` समाविष्ट करते | होय | -| `check:codeql-ratchet` | खुल्या CodeQL अलर्टची संख्या वाढत नाही (`gh api` द्वारे वाचते; टोकनशिवाय सुलभपणे वगळते) — रीफ्रेश वारंवारता आणि मॅन्युअल ट्रिगर: खालील "CodeQL रॅचेट" पहा | होय | +test नमूद करणे, विशिष्ट कारण देणे आणि GitHub issue किंवा pull request ची लिंक देणे आवश्यक आहे. चुकीच्या स्वरूपातील नोंदी +fail closed होतात. अपवाद हटवलेली candidate test किंवा `.skip`/`.todo` जोडणारा diff दडपू शकत नाहीत; +assertion कमकुवत करणे आणि इतर masking यांची जबाबदारी स्वतंत्रपणे ब्लॉक करणाऱ्या +`check:test-masking` gate कडेच राहते. + +### जॉब: `lint` + +`main` वरील प्रत्येक PR साठी चालतो. अपयश आल्यास merge ब्लॉक करतो. + +| स्क्रिप्ट (`npm run ...`) | काय प्रमाणित करते | ब्लॉक करणारे | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | +| `check:node-runtime` | Node.js आवृत्ती समर्थित श्रेणीत आहे | होय | +| `check:cycles` | संपूर्ण `src/` + `open-sse/` मधील circular imports (AST-आधारित, tsconfig `paths` resolve केलेले). स्वतंत्रपणे चालवल्यास = सल्लात्मक, cycles ची सूची दाखवते. `check:cycles:ratchet` (जे CI चालवते) `quality-baseline.json` मधील `metrics.cycles` कमाल मर्यादेपेक्षा संख्या जास्त झाल्यास ब्लॉक करते — सध्या 14, `direction: down`, त्यामुळे ती केवळ कमी होऊ शकते (#15159 G-01/G-02) | होय (ratchet) | +| `check:route-validation:t06` | सर्व routes वर Zod schemas उपस्थित आहेत (Tier 6 धोरण) | होय | +| `check:any-budget:t11` | `@ts-expect-error // any` ची संख्या budget पेक्षा जास्त नाही (Tier 11 catraca) | होय | +| `check:provider-consistency` | `providers.ts` मधील प्रत्येक provider साठी `providerRegistry.ts` मध्ये जुळणारी नोंद आहे (आणि उलटही, allowlist च्या मर्यादेत) | होय | +| `check:model-lifecycle` | हाताने सांभाळले जाणारे तीन routing tables तपासून ठेवलेल्या lifecycle snapshot (#11503) शी सुसंगत राहतात: `FITNESS_TABLE` (`taskFitness.ts`) अशा कोणत्याही निवृत्त id ला score देत नाही ज्याकडे `REGISTRY` route करू शकते; प्रत्येक `BUILT_IN_ALIASES` target `REGISTRY` मध्ये उपस्थित आणि retired-id snapshot मध्ये अनुपस्थित आहे; `REGISTRY` मध्ये अजूनही असलेला प्रत्येक निवृत्त id पुढे पाठवला जातो किंवा `allowedRetiredInCatalog` मध्ये सूचीबद्ध केला जातो; आणि कोणताही `DEFAULT_DEGRADATION_MAP` source किंवा target त्या snapshot मध्ये निवृत्त म्हणून दिसत नाही. यावरून एखादे model सध्या थेट upstream द्वारे उपलब्ध केले जात आहे, हे सिद्ध होत नाही. Offline — `config/quality/model-lifecycle.json` शी तुलना करते; हे `npm run quality:refresh-model-lifecycle` वापरून हाताने refresh केले जाते (network; CI मध्ये जोडलेले नाही). `allowedRetiredInCatalog` हे burn-down ratchet आहे: tracking issue असल्यासच नोंद जोडा. | होय | +| `check:fetch-targets` | client-side `src/` मधील प्रत्येक `fetch("/api/...")` वास्तविक `route.ts` शी resolve होतो | होय | +| `check:deps` | repo मधील प्रत्येक `package.json` तील सर्व `npm install`-योग्य deps `dependency-allowlist.json` मध्ये आहेत; नवीन unpinned किंवा slopsquatted packages flag केले जातात | होय | +| `audit:deps` | `npm audit` (root + electron) — कोणतेही high/critical advisories नाहीत (osv `check:vuln-ratchet` शी overlap होते; Rationalization Backlog पहा) | होय | +| `check:lockfile` | `package-lock.json` ची अखंडता — https registry, integrity hashes, कोणतेही host overrides नाहीत | होय | +| `check:licenses` | उत्पादन अवलंबनांसाठी SPDX परवाना अनुमतसूची | होय | +| `check:tracked-artifacts` | कोणतीही बिल्ड आर्टिफॅक्ट्स / कमिट केलेल्या `node_modules` सिमलिंक्स नाहीत (husky pre-commit मध्येही चालते; pre-push हेतुपुरस्सर हलके ठेवले आहे — #6716) | होय | +| `check:ai-attribution` | PR कमिट्स, शीर्षक किंवा मजकुरात कोणताही AI/bot `Co-Authored-By` ट्रेलर किंवा AI-निर्मिती फूटर नाही — कठोर नियम #16 (`quality.yml` मधील PR→`release/**` साठीच्या fast-gates लूपमध्ये — इव्हेंट पेलोड वाचतो, PR नसल्यास काहीही करत नाही — आणि `ci.yml` lint मध्ये PR→`main` साठी केवळ-PR पायरी; तसेच husky `commit-msg` हुक; मानवी सह-लेखकांना परवानगी आहे; #14436) | +| `check:vitest-exclusions` | प्रत्येक Vitest वगळण्यात ट्रॅकिंग इश्यू नमूद केलेला असतो आणि ते `config/quality/vitest-exclusions.json` मध्ये दिसते (#13204) | होय | +| `check:file-size` | कोणतीही स्रोत फाइल प्रति-विस्तार मर्यादा ओलांडत नाही (रॅचेट: `frozen` सूचीतील गोठवलेल्या मोठ्या फाइल्स) | होय | +| `check:error-helper` | executors/handlers मधील त्रुटी प्रतिसाद `buildErrorBody()` / `sanitizeErrorMessage()` वापरतात (कठोर नियम #12) | होय | +| `check:migration-numbering` | Migration SQL फाइल्सना कोणतीही पोकळी किंवा डुप्लिकेट नसलेले सलग क्रमांक दिलेले आहेत | होय | +| `check:public-creds` | `publicCreds.ts` च्या बाहेर कोणतेही अक्षरशः OAuth `client_id`/`client_secret` किंवा Firebase Web कीज नाहीत (कठोर नियम #11) | होय | +| `check:db-rules` | `src/lib/db/` मॉड्यूल्सच्या बाहेर कोणतेही रॉ SQL नाही; `localDb.ts` मधून कोणतेही barrel-imports नाहीत (कठोर नियम #2/#5) | होय | +| `check:known-symbols` | त्यांच्या dispatch tables मध्ये नोंदणीकृत provider executors, routing strategies आणि translators हे डिस्कवरील फाइल्सशी जुळतात — कोणतेही अनाथ किंवा अघोषित symbols नाहीत | होय | +| `check:route-guard-membership` | child process सुरू करणारा प्रत्येक route `isLocalOnlyPath()` द्वारे वर्गीकृत केला जातो (कठोर नियम #15/#17) | होय | +| `check:test-discovery` | repo मधील प्रत्येक `*.test.ts` / `*.spec.ts` फाइल किमान एका test runner द्वारे संकलित केली जाते (ratchet: `test-discovery-baseline.json` मधील orphan list फक्त कमी होऊ शकते) | होय | +| `check:agent-skills-sync` | व्युत्पन्न agent-skills आर्टिफॅक्ट्स त्यांच्या स्रोत कॅटलॉगशी जुळतात (कोणताही फरक नाही) | +| `check:provider-asset-provenance` | प्रदाता लोगो/आर्टिफॅक्ट्ससाठी नोंदवलेली उगम नोंद उपलब्ध आहे | +| `lint:json` | JSON कॉन्फिगरेशन फाइल्स पार्स होतात आणि रेपोच्या लिंट नियमांची पूर्तता करतात | +| `typecheck:core` | त्रुटींविना TypeScript संकलन (केवळ सल्लात्मक इशारे) | होय | +| `typecheck:noimplicit:core` | कठोर `noImplicitAny` — भविष्योन्मुख; आधीपासून अस्तित्वात असलेल्या अनेक कॉल साइट्सना अजूनही ॲनोटेशन्सची आवश्यकता आहे | **सल्लात्मक** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` पुरते मर्यादित `tsc` (#7033) — `typecheck:core` च्या काळजीपूर्वक निवडलेल्या 27-फाइल अनुमतीसूचीत कोणतेही डॅशबोर्ड TSX समाविष्ट नाही आणि `next build` देखील त्याची कधीही प्रकार-तपासणी करत नाही (`next.config.mjs` मध्ये `ignoreBuildErrors: true` सेट केले आहे), त्यामुळे तेथील अनाथ-आयडेंटिफायर रिग्रेशन्स (#6625/#6909) CI ला दिसत नव्हते. गोठवलेल्या प्रति-फाइल/प्रति-TS-कोड संख्या बेसलाइनशी (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` प्रमाणेच जुन्या नोंदींच्या अंमलबजावणीचा नमुना) फरक तपासते — बेसलाइन संख्येपलीकडील केवळ नवीन त्रुटींमुळे गेट अयशस्वी होते; आधीपासून असलेली त्रुटी दुरुस्त केल्यावर `--update` वापरून बेसलाइन कमी करा. | होय | + +### जॉब: `quality-gate` + +`test-coverage` नंतर चालतो. अयशस्वी झाल्यास मर्ज अवरोधित करतो. + +| स्क्रिप्ट | काय प्रमाणित करते | अवरोधक | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `quality:collect` | `quality-metrics.json` तयार करते (ESLint चेतावण्यांची संख्या, विलीन केलेल्या शार्ड अहवालातील कव्हरेज) | होय (रॅचेटच्या अपस्ट्रीमला) | +| `quality:ratchet` | `quality-baseline.json` मधील प्रत्येक मेट्रिकची घसरण झालेली नाही (ESLint चेतावण्या ≤ बेसलाइन; कव्हरेज ≥ बेसलाइन) | होय | +| `check:duplication` | कोडची पुनरावृत्ती (jscpd@4) `quality-baseline.json` मधील बेसलाइनपेक्षा जास्त नाही | होय | +| `check:complexity` | फाइल-स्तरीय सायक्लोमॅटिक जटिलता कमाल मर्यादेपेक्षा जास्त नाही (मूलभूत ESLint `complexity` + `max-lines-per-function`) | होय | +| `check:cognitive-complexity` | संज्ञानात्मक जटिलता रॅचेट (`eslint-plugin-sonarjs`) — स्वतंत्र ESLint पास; CI हे दोन्ही एकत्रित करून एकच `check:complexity-ratchets` चरण म्हणून चालवते | होय | +| `check:dead-code` | न वापरलेल्या एक्सपोर्ट्स / फाइल्सच्या रॅचेटची (knip) बेसलाइनच्या तुलनेत घसरण होत नाही | होय | +| `check:compression-budget` | कॉम्प्रेशन बेंचमार्क बजेट — प्रत्येक इंजिनसाठी टोकन-बचतीच्या किमान मर्यादांची घसरण होता कामा नये | होय | +| `check:type-coverage` | टाइप केलेल्या कोडच्या टक्केवारीचे रॅचेट (`type-coverage`) घसरत नाही; हे बहुतांशी `typecheck:noimplicit:core` समाविष्ट करते | होय | +| `check:codeql-ratchet` | उघड्या CodeQL अलर्टची संख्या वाढत नाही (`gh api` द्वारे वाचते; टोकन नसल्यास सुरळीतपणे वगळते) — रीफ्रेश वारंवारता आणि मॅन्युअल ट्रिगर: खालील "CodeQL रॅचेट" पहा | होय | ### जॉब: `quality-extended` -संपूर्ण जॉब सल्लात्मक आहे (`continue-on-error: true`). npm-आधारित रॅचेट्स प्रत्यक्षात -चालतात; बाह्य स्कॅनर्स `gh release download` द्वारे इन्स्टॉल होतात आणि बायनरी अद्याप -अनुपस्थित असल्यास स्वतःहून वगळले जातात (exit 0). +संपूर्ण जॉब सल्लागार स्वरूपाचा आहे (`continue-on-error: true`). npm-आधारित रॅचेट्स +प्रत्यक्ष चालतात; बाह्य स्कॅनर्स `gh release download` द्वारे स्थापित होतात आणि बायनरी अद्याप +उपलब्ध नसल्यास स्वतःच वगळले जातात (exit 0). -| स्क्रिप्ट | पडताळणी | अवरोधक | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `check:circular-deps` | कोणतीही चक्रीय अवलंबित्वे नाहीत (dpdm) | **सल्लात्मक** | -| `check:bundle-size` | बंडलचा आकार कमाल मर्यादेपेक्षा जास्त नाही | **सल्लात्मक** | -| `check:secrets` | गुपितांचे स्कॅनिंग (gitleaks) — बायनरी अनुपस्थित असल्यास वगळते | **सल्लात्मक** | -| `check:vuln-ratchet` | अवलंबित्वांमधील असुरक्षितता (osv-scanner) वाढत नाही — बायनरी अनुपस्थित असल्यास वगळते | **सल्लात्मक** | -| `check:workflows` | वर्कफ्लो लिंट (actionlint + zizmor); स्कॅनर्स अनुपस्थित/बिघडलेले असणे, अवैध अहवाल किंवा रॅचेट बेसलाइन अनुपस्थित असणे यामुळे INCOMPLETE म्हणून अपयश येते. वैध निष्कर्ष निवडलेल्या strict/advisory/ratchet धोरणाचे पालन करतात | अंमलबजावणी आवश्यक; CI मध्ये zizmor रॅचेट अवरोधक आहे | -| `check:openapi-breaking` | बेस ब्रँचच्या तुलनेत सार्वजनिक API करारातील (`openapi.yaml`) ब्रेकिंग बदल (oasdiff) — `openapiBreaking=N` उत्सर्जित करते; oasdiff अनुपस्थित असल्यास किंवा बेस स्पेकचे निराकरण शक्य नसल्यास वगळते | **सल्लात्मक** | +| स्क्रिप्ट | काय प्रमाणित करते | अवरोधक स्थिती | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| `check:circular-deps` | कोणतीही चक्रीय अवलंबित्वे नाहीत (dpdm) | **सल्लागार** | +| `check:bundle-size` | बंडलचा आकार कमाल मर्यादेपेक्षा जास्त नाही | **सल्लागार** | +| `check:secrets` | गुपितांचे स्कॅनिंग (gitleaks) — बायनरी उपलब्ध नसल्यास वगळते | **सल्लागार** | +| `check:vuln-ratchet` | अवलंबित्वांमधील असुरक्षा (osv-scanner) वाढत नाहीत — बायनरी उपलब्ध नसल्यास वगळते | **सल्लागार** | +| `check:workflows` | वर्कफ्लो लिंट (actionlint + zizmor); स्कॅनर्स अनुपलब्ध/बिघडलेले असल्यास, अहवाल अवैध असल्यास किंवा रॅचेट बेसलाइन अनुपलब्ध असल्यास INCOMPLETE म्हणून अपयशी ठरते. वैध निष्कर्ष निवडलेल्या कठोर/सल्लागार/रॅचेट धोरणाचे पालन करतात | अंमलबजावणी आवश्यक; CI मध्ये zizmor रॅचेट अवरोधक | +| `check:openapi-breaking` | बेस ब्रँचच्या तुलनेत सार्वजनिक API करारामधील (`openapi.yaml`) ब्रेकिंग बदल (oasdiff) — `openapiBreaking=N` तयार करते; oasdiff उपलब्ध नसल्यास किंवा बेस स्पेसिफिकेशनचे निराकरण शक्य नसल्यास वगळते | **सल्लागार** | ### जॉब: `docs-sync-strict` -`main` कडे असलेल्या प्रत्येक PR वर चालतो. अपयश आल्यास मर्ज अवरोधित करतो. - -| स्क्रिप्ट | काय सत्यापित करते | अवरोधक | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| `check:docs-all` | खालील 6 उप-गेट्स अनुक्रमाने चालवणारा मेटा-गेट | होय | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt आवृत्ती सुसंगतता | होय | -| ↳ `check:docs-counts` | गद्यातील मोजण्या (प्रदाता संख्या, माइग्रेशन संख्या इ.) वास्तविक मोजण्यांच्या रॅचेट मर्यादेत आहेत | होय | -| ↳ `check:env-doc-sync` | `.env.example` मधील प्रत्येक env var दस्तऐवजांच्या तक्त्यात नोंदवलेला आहे आणि त्याउलटही | होय | -| ↳ `check:deprecated-versions` | दस्तऐवजांमध्ये कालबाह्य आवृत्ती स्ट्रिंग नाहीत | होय | -| ↳ `check:doc-links` | दस्तऐवजांमधील अंतर्गत markdown दुवे वास्तविक फाइल्सकडे निर्देशित होतात (`[text]`/`(path)` स्वरूप) | होय | -| ↳ `check:fabricated-docs` | दस्तऐवजांत नमूद केलेले routes, env vars, CLI commands, hook names आणि file paths कोडबेसमध्ये अस्तित्वात आहेत. `--strict` द्वारे कठोर गेट; फ्लॅगशिवाय सौम्य अपयश. | होय (CI मध्ये `--strict` द्वारे) | -| `check:cli-i18n` | CLI command strings सर्व i18n locale files मध्ये उपलब्ध आहेत | होय | -| `check:openapi-coverage` | OpenAPI spec मध्ये वास्तविक routes च्या किमान रॅचेट केलेल्या मर्यादेइतके कव्हरेज आहे | होय | -| `check:openapi-security-tiers` | `openapi.yaml` मधील सुरक्षा स्तर भाष्ये `routeGuard.ts` वर्गीकरणांशी सुसंगत आहेत | **सल्लात्मक** | -| `check:openapi-routes` | `openapi.yaml` मधील प्रत्येक path वास्तविक `route.ts` कडे निर्देशित होतो (कल्पित माहितीविरोधी) | होय | -| `check:docs-symbols` | `docs/**/*.md` मधील प्रत्येक `/api/...` संदर्भ वास्तविक `route.ts` कडे निर्देशित होतो (कल्पित माहितीविरोधी) | होय | -| `i18n translation drift` | i18n locale files मधील अनुवाद न केलेल्या keys — केवळ इशारा | **सल्लात्मक** | +`main` वरील प्रत्येक PR साठी चालते. अपयश आल्यास मर्ज अवरोधित करते. + +| स्क्रिप्ट | काय सत्यापित करते | अवरोधक | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| `check:docs-all` | खालील 6 उप-गेट्स अनुक्रमे चालवणारे मेटा-गेट | होय | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt आवृत्तीतील सुसंगतता | होय | +| ↳ `check:docs-counts` | गद्यातील संख्या (प्रदात्यांची संख्या, स्थलांतरांची संख्या इ.) वास्तविक संख्यांच्या रॅचेट मर्यादेत आहेत | होय | +| ↳ `check:env-doc-sync` | `.env.example` मधील प्रत्येक पर्यावरणीय चल दस्तऐवजांच्या तक्त्यात नोंदवलेला आहे आणि त्याउलटही | होय | +| ↳ `check:deprecated-versions` | दस्तऐवजांमध्ये कालबाह्य आवृत्ती स्ट्रिंग नाहीत | होय | +| ↳ `check:doc-links` | दस्तऐवजांमधील अंतर्गत markdown दुवे वास्तविक फाइल्सकडे निर्देशित होतात (`[text]`/`(path)` स्वरूप) | होय | +| ↳ `check:fabricated-docs` | दस्तऐवजांमध्ये उद्धृत केलेले रूट्स, पर्यावरणीय चल, CLI कमांड्स, हुकची नावे आणि फाइल पाथ्स कोडबेसमध्ये अस्तित्वात आहेत. `--strict` द्वारे कठोर गेट; फ्लॅगशिवाय सौम्य अपयश. | होय (CI मध्ये `--strict` द्वारे) | +| `check:cli-i18n` | CLI कमांड स्ट्रिंग्स सर्व i18n लोकेल फाइल्समध्ये उपस्थित आहेत | होय | +| `check:openapi-coverage` | OpenAPI तपशील वास्तविक रूट्सच्या किमान रॅचेट केलेल्या तळमर्यादेला व्यापतो | होय | +| `check:openapi-security-tiers` | `openapi.yaml` मधील सुरक्षा स्तर भाष्ये `routeGuard.ts` वर्गीकरणांशी सुसंगत आहेत | **सल्लात्मक** | +| `check:openapi-routes` | `openapi.yaml` मधील प्रत्येक पाथ वास्तविक `route.ts` कडे निर्देशित होतो (भ्रमनिर्मितीविरोधी) | होय | +| `check:docs-symbols` | `docs/**/*.md` मधील प्रत्येक `/api/...` संदर्भ वास्तविक `route.ts` कडे निर्देशित होतो (भ्रमनिर्मितीविरोधी) | होय | +| `i18n translation drift` | i18n लोकेल फाइल्समधील अनुवाद न केलेल्या की — केवळ इशारा | **सल्लात्मक** | ### जॉब: `i18n-ui-coverage` -| स्क्रिप्ट | काय सत्यापित करते | अवरोधक | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `check-ui-keys-coverage` (inline) | UI i18n key कव्हरेज ≥ 65% आहे | होय | -| `check-ui-value-drift` (inline) | पुन्हा लिहिलेल्या इंग्रजी **मूल्यामुळे** कोणताही जुना अनुवाद मागे राहत नाही | होय | -| `check-new-key-coverage` (inline) | एखादी **नवीन** इंग्रजी key प्रत्येक locale मध्ये अनुवादित केली आहे — `__MISSING__:` marker नाकारला जातो | होय | -| `check-translation-ratio` | प्रत्येक locale साठी वास्तविक-अनुवाद गुणोत्तर (allowlist बाहेरील इंग्रजीशी-समान / placeholder / missing leaves) `config/quality/i18n-translation-baseline.json` + slack पेक्षा अधिक नसावे | **सल्लात्मक** | +| स्क्रिप्ट | काय सत्यापित करते | अवरोधक | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `check-ui-keys-coverage` (इनलाइन) | UI i18n की कव्हरेज ≥ 65% आहे | होय | +| `check-ui-value-drift` (इनलाइन) | पुन्हा लिहिलेल्या इंग्रजी **मूल्यामुळे** कोणताही कालबाह्य अनुवाद मागे राहत नाही | होय | +| `check-new-key-coverage` (इनलाइन) | **नवीन** इंग्रजी की प्रत्येक लोकेलमध्ये अनुवादित केलेली आहे — `__MISSING__:` मार्कर नाकारला जातो | होय | +| `check-translation-ratio` | प्रत्येक लोकेलसाठी वास्तविक-अनुवाद गुणोत्तर (इंग्रजीशी समान / प्लेसहोल्डर / अनुमतीसूचीबाहेरील गहाळ लीफ) `config/quality/i18n-translation-baseline.json` + शिथिलता यापेक्षा अधिक नसावे | **सल्लात्मक** | -`fetch-depth: 0` आवश्यक आहे — value-drift gate merge base च्या तुलनेत `en.json` चा फरक तपासतो. +`fetch-depth: 0` आवश्यक आहे — मूल्य-ड्रिफ्ट गेट मर्ज बेसच्या तुलनेत `en.json` चा फरक तपासतो. -#### `check-ui-value-drift` — जुना-अनुवाद गेट +#### `check-ui-value-drift` — कालबाह्य-अनुवाद गेट -इतर गेट्सना रचनात्मकदृष्ट्या दिसू न शकणारी i18n मधील एक त्रुटी हे पकडते: इंग्रजी मूल्य -पुन्हा लिहिले जाते आणि _मागील_ इंग्रजीवरून तयार केलेले अनुवाद तसेच मागे राहतात, त्यामुळे -इंग्रजीतर वापरकर्ते आत्मविश्वासाने लिहिलेला, पण आता चुकीचा असलेला मजकूर वाचत राहतात. +इतर गेट्सना संरचनात्मकदृष्ट्या दिसू न शकणारी एक i18n प्रतिगती हे पकडते: एखादे इंग्रजी मूल्य +पुन्हा लिहिले जाते आणि _मागील_ इंग्रजीवर आधारित अनुवाद तसेच मागे राहतात, त्यामुळे +इंग्रजीतर वापरकर्ते आत्मविश्वासपूर्ण शैलीतील, पण आता चुकीचा असलेला मजकूर वाचत राहतात. -हे प्रत्यक्षात रिलीझ झाले होते. Antigravity login helper जोडला गेला तेव्हा (#5203) -`oauthModal.googleOAuthWarning` पुन्हा लिहिले गेले; **43 पैकी 39 locales** मध्ये operators ना -"संपूर्ण URL कॉपी करा आणि खाली पेस्ट करा" असे सांगणारा मजकूर कायम राहिला — त्या प्रदात्यासाठी -पूर्णच होऊ न शकणारी प्रक्रिया. #8463 पर्यंत ही बाब लक्षात आली नाही, कारण: +हे प्रत्यक्षात रिलीज झाले होते. Antigravity लॉगिन सहाय्यक जोडला गेला तेव्हा (#5203) +`oauthModal.googleOAuthWarning` पुन्हा लिहिले गेले; **43 पैकी 39 लोकेल्समध्ये** ऑपरेटर्सना "संपूर्ण +URL कॉपी करा आणि खाली पेस्ट करा" असे सांगणारा मजकूर कायम राहिला — अशी प्रक्रिया जी त्या प्रदात्यासाठी पूर्णच होऊ शकत नाही. #8463 पर्यंत +हे लक्षात आले नाही, कारण: -- `sync-ui-keys` केवळ **अनुपस्थित** keys भरून काढते, **जुन्या** keys कधीही नाही; -- `check-ui-keys-coverage` key ची _उपस्थिती_ मोजते, त्यामुळे जुना अनुवादही कव्हर झालेला म्हणून गणला जातो; -- `check-translation-drift` हे `docs/i18n//**.md` दस्तऐवजीकरण mirrors ट्रॅक करते — - ते `src/i18n/messages/*.json` कधीही वाचत नाही. 2026-09 re-sync पासून `docs-sync-strict` जॉबमध्ये अवरोधक: - मुख्य दस्तऐवज संपादित करा → `npm run i18n:run -- --files=` (विभाग-स्तरीय, कमी खर्चिक). +- `sync-ui-keys` केवळ **अनुपस्थित** की भरतो, **कालबाह्य** की कधीही भरत नाही; +- `check-ui-keys-coverage` कीची _उपस्थिती_ मोजतो, त्यामुळे कालबाह्य अनुवाद कव्हर झालेला म्हणून गणला जातो; +- `check-translation-drift` हे `docs/i18n//**.md` दस्तऐवजीकरण प्रतिबिंबांचा मागोवा घेते — + ते `src/i18n/messages/*.json` कधीही वाचत नाही. 2026-09 पुनः-सिंक्रोनायझेशनपासून `docs-sync-strict` जॉबमध्ये अवरोधक: मुख्य दस्तऐवज संपादित करा → `npm run i18n:run -- --files=` (विभाग-स्तरीय, कमी खर्चिक). -**फरक-जागरूक, आधाररेषेवर अवलंबून नाही.** हे merge base वरील `en.json` ची working tree शी तुलना करते; ज्या प्रत्येक key चे इंग्रजी मूल्य बदलले आहे, त्या key साठी अजूनही न बदललेले भाषांतर असलेले कोणतेही locale कालबाह्य मानले जाते. यामुळे जाणीवपूर्वक **आधीपासून अस्तित्वात असलेले तांत्रिक ऋण गोठवले जाते** — दीर्घकाळापासून असलेले भाषांतर कोणत्या जुन्या इंग्रजी मजकुरावरून आले आहे हे diff मधून समजू शकत नाही, त्यामुळे gate फक्त सध्याच्या बदलाने स्पर्श केलेल्या गोष्टींचेच मूल्यमापन करते. पर्यायी पद्धतीसाठी (प्रत्येक key साठी hash baseline) ~600 KB ची generated file लागेल, जी सध्या अस्तित्वात असलेल्या सर्वांत मोठ्या baseline च्या 3 पट आहे आणि प्रत्येक i18n PR वर बदलत राहील. +**फरक-जागरूक, आधाररेषेवर अवलंबून नाही.** हे मर्ज बेसवरील `en.json` ची वर्किंग ट्रीशी तुलना करते; ज्या प्रत्येक कीचे इंग्रजी मूल्य बदलले आहे, त्या कीसाठी अजूनही न बदललेले भाषांतर असलेले कोणतेही लोकेल कालबाह्य मानले जाते. हे जाणीवपूर्वक **आधीपासूनचे प्रलंबित काम गोठवते** — दीर्घकाळ अस्तित्वात असलेले भाषांतर कोणत्या जुन्या इंग्रजी मजकुरावरून आले हे फरकातून समजू शकत नाही, त्यामुळे गेट केवळ सध्याच्या बदलाने स्पर्श केलेल्या गोष्टींचे मूल्यमापन करते. पर्यायासाठी (प्रत्येक कीसाठी हॅश आधाररेषा) सुमारे 600 KB आकाराची जनरेट केलेली फाइल लागेल, जी विद्यमान सर्वांत मोठ्या आधाररेषेच्या 3× असेल आणि प्रत्येक i18n PR वेळी बदलेल. -याची पूर्तता करण्याचे दोन मार्ग: +ही अट पूर्ण करण्याचे दोन मार्ग: 1. प्रभावित भाषांतरे अद्ययावत करा, किंवा -2. ती `__MISSING__:` अशी सेट करा — त्यानंतर runtime सुधारित इंग्रजी मजकूर पुरवतो - (`src/i18n/request.ts::deepMergeFallback`, #7258) आणि ती key भाषांतरासाठी रांगेत जाते. +2. त्यांना `__MISSING__:` असे सेट करा — त्यानंतर रनटाइम सुधारित इंग्रजी मजकूर देते + (`src/i18n/request.ts::deepMergeFallback`, #7258) आणि ती की भाषांतरासाठी रांगेत जाते. -जर string चा **अर्थ** बदलला असेल, तर **key चे नाव बदलण्यास** प्राधान्य द्या: नवीन key ला कालबाह्य भाषांतर वारशाने मिळू शकत नाही. #8463 मध्ये हाच नमुना वापरला होता. +स्ट्रिंगचा **अर्थ** बदलला असल्यास, **कीचे नाव बदलण्याला** प्राधान्य द्या: नवीन कीला कालबाह्य भाषांतर वारशाने मिळू शकत नाही. #8463 मध्ये हाच पॅटर्न वापरला होता. ```bash -npm run i18n:check-value-drift # कठोर (CI जे चालवते) -npm run i18n:check-value-drift:warn # फक्त अहवाल +npm run i18n:check-value-drift # कठोर (CI जे चालवते ते) +npm run i18n:check-value-drift:warn # केवळ अहवाल BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -base catalog वाचता येत नसल्यास (base ref नसलेला shallow clone), `check-openapi-breaking` प्रमाणेच `SKIP reason=base-unresolved` सह 0 exit code देते. +बेस कॅटलॉग वाचता येत नसल्यास (`base ref` नसलेला shallow clone), `check-openapi-breaking` प्रमाणेच `SKIP reason=base-unresolved` सह 0 एक्झिट कोड देते. -### Job: `i18n` +### जॉब: `i18n` -संपूर्ण i18n प्रमाणीकरण matrix (प्रत्येक locale साठी एक job). संपूर्ण job सल्लात्मक आहे. +संपूर्ण i18n प्रमाणीकरण मॅट्रिक्स (प्रत्येक लोकेलसाठी एक जॉब). संपूर्ण जॉब सल्लात्मक आहे. -| Script | काय प्रमाणित करते | अवरोधक | -| ------------------------------- | --------------------------------------- | -------------------------------------------------------- | -| `validate_translation.py quick` | प्रत्येक locale साठी भाषांतराची पूर्णता | **सल्लात्मक** (संपूर्ण job वर `continue-on-error: true`) | +| स्क्रिप्ट | काय प्रमाणित करते | ब्लॉकिंग | +| ------------------------------- | ------------------------------------- | ------------------------------------------------------- | +| `validate_translation.py quick` | प्रत्येक लोकेलसाठी भाषांतराची पूर्णता | **सल्लात्मक** (संपूर्ण जॉबवर `continue-on-error: true`) | -### Job: `pr-test-policy` +### जॉब: `pr-test-policy` फक्त pull requests वर चालते. -| Script | काय प्रमाणित करते | अवरोधक | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, किंवा `bin/` मधील production code बदलणाऱ्या PRs मध्ये tests समाविष्ट किंवा अद्ययावत करणे आवश्यक आहे (Hard Rule #8) | होय | -| `check:test-masking` | बदललेल्या test files मुळे निव्वळ assert संख्या कमी होत नाही किंवा `assert.ok(true)` tautologies जोडल्या जात नाहीत | होय | -| `check:pr-evidence` | PR body मध्ये बदलासाठी test/VPS पुराव्याचा संदर्भ असतो (PR prose grep करून Hard Rule #18 स्वयंचलित करते — नाजूक आहे, Backlog पहा) | होय | +| स्क्रिप्ट | काय प्रमाणित करते | ब्लॉकिंग | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, किंवा `bin/` मधील प्रॉडक्शन कोड बदलणाऱ्या PRs मध्ये चाचण्यांचा समावेश किंवा अद्ययावत चाचण्या असणे आवश्यक आहे (कठोर नियम #8) | होय | +| `check:test-masking` | बदललेल्या चाचणी फाइल्स निव्वळ assert संख्या कमी करत नाहीत किंवा `assert.ok(true)` सारखी स्वयंसिद्ध विधाने जोडत नाहीत | होय | +| `check:pr-evidence` | PR च्या मजकुरात बदलासाठी चाचणी/VPS पुराव्याचा उल्लेख आहे (PR मधील गद्य मजकुरावर grep करून कठोर नियम #18 यांत्रिकरीत्या लागू करते — नाजूक; Backlog पहा) | होय | -### Job: `test-vitest` +### जॉब: `test-vitest` -`build` नंतर चालते. अपयश आल्यास merge अवरोधित करते. +`build` नंतर चालते. अपयशी झाल्यास मर्ज ब्लॉक करते. -| Suite | काय प्रमाणित करते | अवरोधक | -| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | होय | -| `test:vitest:ui` | UI component tests — vitest runner | **अवरोधक** — आधीपासूनची अपयशे `vitest.config.ts` मध्ये स्पष्टपणे वगळली आहेत; नवीन अपयशांमुळे job अयशस्वी होतो | +| संच | काय प्रमाणित करते | ब्लॉकिंग | +| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP सर्व्हर (110 टूल्स), autoCombo, cache — vitest रनर | होय | +| `test:vitest:ui` | UI कॉम्पोनंट चाचण्या — vitest रनर | **ब्लॉकिंग** — आधीपासूनची अपयशे `vitest.config.ts` मध्ये स्पष्टपणे वगळली आहेत; नवीन अपयशांमुळे जॉब अपयशी होतो | -### रात्रीचे workflows (नियोजित, सल्लात्मक) +### रात्रीच्या वर्कफ्लोज (शेड्यूल केलेले, सल्लात्मक) -हे cron schedule वर (आणि `workflow_dispatch` द्वारे) चालतात, PRs वर कधीही चालत नाहीत. सर्व सल्लात्मक आहेत. +हे cron शेड्यूलवर (आणि `workflow_dispatch` द्वारे) चालतात, PRs वर कधीही चालत नाहीत. हे सर्व सल्लात्मक आहेत. -| Workflow | काय प्रमाणित करते | अवरोधक | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `nightly-property` | random seed + उच्च run count सह fast-check property tests | **सल्लात्मक** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **सल्लात्मक** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret शिवाय वगळले जातात) | **सल्लात्मक** | -| `nightly-schemathesis` | `docs/openapi.yaml` वापरून live OmniRoute विरुद्ध OpenAPI contract fuzzing (schemathesis) — spec उल्लंघने / हाताळले न गेलेले 500s समोर आणते (Fase 8 B.4) | **सल्लात्मक** | -| `nightly-mutation` | fast unit lane वरील Stryker mutation-testing score — टिकून राहिलेले mutants कमकुवत asserts समोर आणतात | **सल्लात्मक** | -| `nightly-compat` | समर्थित `engines.node` ranges मधील Node engine compatibility matrix | **सल्लात्मक** | +| वर्कफ्लो | काय प्रमाणित करते | ब्लॉकिंग | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `nightly-property` | यादृच्छिक seed आणि मोठ्या run count सह fast-check property चाचण्या | **सल्लात्मक** | +| `nightly-resilience` | heap-growth गेट, chaos fault-injection, k6 load/soak | **सल्लात्मक** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret शिवाय वगळले जातात) | **सल्लात्मक** | +| `nightly-schemathesis` | `docs/openapi.yaml` वापरून लाइव्ह OmniRoute विरुद्ध OpenAPI contract fuzzing (schemathesis) — स्पेसिफिकेशनचे उल्लंघन / न हाताळलेल्या 500s उघड करते (Fase 8 B.4) | **सल्लात्मक** | +| `nightly-mutation` | जलद unit lane वरील Stryker mutation-testing स्कोअर — टिकून राहिलेले mutants कमकुवत asserts उघड करतात | **सल्लात्मक** | +| `nightly-compat` | समर्थित `engines.node` श्रेणींमधील Node engine सुसंगतता मॅट्रिक्स | **सल्लात्मक** | --- @@ -526,43 +527,43 @@ CI गेटच्या पलीकडे, OmniRoute मध्ये **ऐच --- -## सुसूत्रीकरण प्रलंबित कामे (ROI पुनरावलोकन — टप्पा 9 लाट 3) +## सुसूत्रीकरण अनुशेष (ROI पुनरावलोकन — टप्पा 9 लाट 3) -ही यादी 2026-06-17 रोजी `ci.yml` शी ताळमेळ करून अद्ययावत केली होती (आधीच्या आवृत्तीत +ही सूची 2026-06-17 रोजी `ci.yml` च्या तुलनेत समेट करण्यात आली होती (आधीच्या आवृत्तीमध्ये `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` वगळले गेले होते). ताळमेळ केलेल्या संचाच्या ROI पुनरावलोकनातून -खालील सुसूत्रीकरणाचे उमेदवार ओळखले गेले. **विलीनीकरणे ही यांत्रिक CI -बदल आहेत; स्थिती-बदल/वगळणे हे ऑपरेटरसाठी राखीव धोरणात्मक निर्णय आहेत.** खालीलपैकी काहीही +`check:codeql-ratchet`, `check:pr-evidence` वगळले गेले होते). समेट केलेल्या संचाच्या ROI पुनरावलोकनाने +खालील सुसूत्रीकरण उमेदवार ओळखले. **विलीनीकरणे ही यांत्रिक CI +बदल आहेत; फ्लिप/वगळण्याचे निर्णय हे ऑपरेटरसाठी राखीव धोरणात्मक निर्णय आहेत.** खालीलपैकी काहीही अद्याप लागू केलेले नाही. -**वरील भागात ज्यांचे दस्तऐवजीकरणही झालेले नाही** (सल्लात्मक, कमी संकेतक्षम): `docs-lint` जॉब -(markdownlint + Vale, संपूर्ण जॉबसाठी `continue-on-error`) आणि स्वतंत्र स्कॅनर वर्कफ्लो +**वर दस्तऐवजीकरण न केलेले आणखी घटक** (सल्लात्मक, कमी संकेत): `docs-lint` जॉब +(markdownlint + Vale, संपूर्ण जॉब `continue-on-error`) आणि स्वतंत्र स्कॅनर वर्कफ्लो `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` हे `quality-baseline.json` मध्ये आहे, परंतु `ci.yml` मधील ब्लॉकिंग रॅचेटशी जोडलेले नाही — हे मेट्रिक सध्या अनाथ आहे. -### विलीनीकरण / डुप्लिकेशन काढणे (यांत्रिक, कमी जोखीम) +### विलीनीकरण / डिड्युप्लिकेशन (यांत्रिक, कमी जोखीम) -प्रत्येक उमेदवाराची 2026-06-17 रोजी प्रत्यक्ष गेट स्थितीशी पडताळणी केली होती (विश्वास ठेवा, पण पडताळा); -अनेक "स्पष्ट" वाटणारी विलीनीकरणे प्रत्यक्षात प्रलंबित तांत्रिक कर्ज लपवत असल्याचे आढळले आणि ती **स्वच्छ पर्याय** नाहीत. +प्रत्येक उमेदवाराची 2026-06-17 रोजी प्रत्यक्ष गेट स्थितीच्या तुलनेत पडताळणी करण्यात आली (विश्वास ठेवा, पण पडताळा); +अनेक "सुस्पष्ट" विलीनीकरणांमध्ये तांत्रिक ऋण दडलेले असल्याचे आढळले आणि ती **स्वच्छ थेट पर्याय** नाहीत. -- **`check:docs-sync` दोनदा चालते** — `lint` जॉबमध्ये स्वतंत्रपणे आणि पुन्हा `check:docs-all` (`docs-sync-strict`) व husky pre-commit हुकमध्ये. ✅ **पूर्ण** — स्वतंत्र `lint` आवाहन काढले. -- **CVE स्कॅनिंग** — ❌ **स्वच्छ विलीनीकरण नाही.** कोणत्याही उच्च/गंभीर CVE वर `audit:deps` थेट अपयशी ठरते; `check:vuln-ratchet` (osv) फक्त आधाररेषेच्या तुलनेत _प्रतिगमन_ झाल्यास अपयशी ठरते (सध्या 1 MODERATE). अर्थविषयक वर्तन वेगळे आहे — `audit:deps` वगळल्यास निरपेक्ष उच्च/गंभीर गेट गमावले जाईल. दोन्ही ठेवा. -- **चक्रीय अवलंबित्व शोधणे** — ❌ **स्वच्छ विलीनीकरण नाही.** `check:circular-deps` (dpdm) **91 चक्रे** नोंदवते (म्हणूनच ते सल्लात्मक आहे); ती आधी सोडवल्याशिवाय त्याला ब्लॉकिंग दर्जा देता येणार नाही आणि त्याची व्याप्ती हिरव्या स्थितीतील, निवडक `check:cycles` पेक्षा व्यापक आहे. `check:cycles` ब्लॉकिंग ठेवा; 91 dpdm चक्रे सोडवणे हे स्वतंत्र प्रलंबित काम आहे. -- **जटिलता** — ✅ **पूर्ण** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): एक ESLint फेरी, `ruleId` नुसार मोजणी, त्यामुळे cyclomatic+max-lines आणि cognitive आधाररेषा स्वतंत्र राहतात; स्थानिक `--update` साठी स्वतंत्र `check:complexity` / `check:cognitive-complexity` कायम आहेत. -- **`/api` भ्रमविरोधी तपासणी** — ✅ **पूर्ण** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ची एक FS यादी, openapi-routes + docs-symbols अजूनही स्वतंत्रपणे अहवाल देतात; स्थानिक रनसाठी स्वतंत्र तपासण्या कायम आहेत. -- **`check:node-runtime` 11 जॉबमध्ये चालते** — ⚠️ **कमी ROI.** प्रत्येक स्वतंत्र रनर आहे आणि तपासणी <1s आहे; स्वस्त प्रति-जॉब संरक्षक गमावण्याच्या बदल्यात एकूण बचत ~10s. या उलथापालथीचे मूल्य नाही. -- **CI lint वरील `typecheck:noimplicit:core`** — ✅ **lint जॉबमधून काढले** (ते सल्लात्मक `continue-on-error` होते); ब्लॉकिंग प्रकार-पृष्ठभाग म्हणजे `typecheck:core` + `check:type-coverage`. स्थानिक स्क्रिप्ट कायम ठेवली. +- **`check:docs-sync` दोनदा चालते** — `lint` जॉबमध्ये स्वतंत्रपणे आणि पुन्हा `check:docs-all` (`docs-sync-strict`) तसेच husky pre-commit हुकमध्ये. ✅ **पूर्ण** — स्वतंत्र `lint` आवाहन काढून टाकले. +- **CVE स्कॅनिंग** — ❌ **स्वच्छ विलीनीकरण नाही.** कोणत्याही उच्च/गंभीर CVE वर `audit:deps` हार्ड-फेल होते; `check:vuln-ratchet` (osv) फक्त आधाररेषेच्या तुलनेत _प्रतिगमन_ झाल्यास अपयशी होते (सध्या 1 MODERATE). अर्थभेद आहेत — `audit:deps` वगळल्यास निरपेक्ष उच्च/गंभीर गेट नष्ट होईल. दोन्ही ठेवा. +- **चक्र शोध** — ✅ **पूर्ण** (#15159 G-01/G-02). येथील जुन्या मजकुराने `check:cycles` ला "हिरवा, निवडक" गेट म्हटले होते आणि `check:circular-deps` (dpdm) ने 91 चक्रे नोंदवल्यामुळे ते ब्लॉकिंग ठेवण्याचे समर्थन केले होते. तो हिरवा निकाल **खोटा हिरवा** होता: `check:cycles` ने 5 उपनिर्देशिका (450 फाइल्स) स्कॅन केल्या, फक्त स्थिर `import|export … from` जुळवले आणि प्रत्येक `@/` व `@omniroute/open-sse/` स्पेसिफायर वगळला, त्यामुळे रेपोमध्ये प्रबळ असलेली डायनॅमिक-import + alias चक्रे त्याला दिसू शकली नाहीत. दुरुस्त केले: गेट आता `src` + `open-sse` (5023 फाइल्स) मधून जाते, TypeScript AST मधून स्पेसिफायर संकलित करते (म्हणून `import("…")` मोजले जाते आणि type-position `typeof import("…")` मोजले जात नाही), तसेच tsconfig `paths` रिजॉल्व्ह करते. त्याला 0 नव्हे, तर **14** चक्रे आढळतात. आधीपासून अस्तित्वात असलेली 14 चक्रे गेट PR मध्ये दुरुस्त करता येत नसल्यामुळे, `check:cycles` आता **रॅचेट** आहे (`--ratchet`, `quality-baseline.json` मध्ये कमाल मर्यादा `metrics.cycles.value = 14`, `direction: down`) — ते कोणतेही _प्रतिगमन_ ब्लॉक करते आणि संख्या फक्त कमी होऊ शकते. CI मध्ये `npm run check:cycles:ratchet` चालते. कमी करण्याचे काम **A-01** सोबत केले जाईल. व्यापक दुसरे मत म्हणून `check:circular-deps` (dpdm) सल्लात्मक राहते. +- **गुंतागुंत** — ✅ **पूर्ण** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): एक ESLint वॉक, ruleId नुसार मोजणी, जेणेकरून cyclomatic+max-lines आणि cognitive आधाररेषा स्वतंत्र राहतील; स्थानिक `--update` साठी स्वतंत्र `check:complexity` / `check:cognitive-complexity` कायम राहतात. +- **`/api` भ्रम-विरोधी तपासणी** — ✅ **पूर्ण** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ची एक FS सूची, openapi-routes + docs-symbols अजूनही स्वतंत्रपणे अहवाल देतात; स्थानिक रनसाठी स्वतंत्र तपासण्या कायम राहतात. +- **`check:node-runtime` 11 जॉबमध्ये चालते** — ⚠️ **कमी ROI.** प्रत्येक स्वतंत्र रनर आहे आणि तपासणी <1s आहे; स्वस्त प्रति-जॉब संरक्षक गमावण्याच्या बदल्यात एकूण बचत ~10s. या उलथापालथीची किंमत योग्य नाही. +- **CI lint वरील `typecheck:noimplicit:core`** — ✅ **lint जॉबमधून काढले** (पूर्वी सल्लात्मक `continue-on-error` होते); ब्लॉकिंग प्रकार-पृष्ठभाग म्हणजे `typecheck:core` + `check:type-coverage`. स्थानिक स्क्रिप्ट कायम ठेवली. -### स्थिती बदला / निर्णय घ्या (ऑपरेटर धोरण) +### फ्लिप / निर्णय (ऑपरेटर धोरण) -- `check:openapi-security-tiers` (सल्लात्मक) — ❌ **स्वच्छपणे स्थिती बदलता येत नाही.** ते 0 सह बाहेर पडते, पण `LOCAL_ONLY_API_PREFIXES` अंतर्गत असलेल्या अनेक `traffic-inspector` रूट्समध्ये `x-loopback-only: true` भाष्य नसल्याची चेतावणी देते. त्याची अंमलबजावणी सक्तीची करण्यासाठी आधी `openapi.yaml` मध्ये ती भाष्ये जोडावी लागतील. -- `typecheck:noimplicit:core` (सल्लात्मक) — ब्लॉकिंग `check:type-coverage` रॅचेटने बहुतांशी समाविष्ट केलेले आहे. त्याला रॅचेटमध्ये बदला किंवा अनावश्यक दुसरी `tsc` फेरी वगळा. -- `test:vitest:ui` (आता **ब्लॉकिंग**) — आधीपासून अस्तित्वात असलेली अपयशे `vitest.config.ts` मध्ये `// #8618` ट्रॅकिंग टिप्पण्यांसह स्पष्टपणे वगळली आहेत; नवीन अपयशांमुळे जॉब अपयशी ठरतो. -- `check:secrets` (gitleaks, दस्तऐवजीकरण केलेल्या 3 चुकीच्या सकारात्मक निष्कर्षांवर गोठवलेले ब्लॉकिंग रॅचेट) — 0 पर्यंत पोहोचण्यासाठी त्या 3 नोंदी allowlist मध्ये जोडा किंवा सल्लात्मक दर्जावर आणा. GitHub चे मूळ secret-scanning + `check:public-creds` यांच्याशी आच्छादन आहे. -- `check:pr-evidence` (ब्लॉकिंग, PR-body मधील गद्य मजकूर grep करते) — चुकीच्या सकारात्मक निष्कर्षांचा उच्च धोका; वगळल्यास Hard Rule #18 ची अंमलबजावणी कमकुवत होते, त्यामुळे हा खरोखरच धोरणात्मक निर्णय आहे. -- `semgrep` (स्वतंत्र सल्लात्मक तपासणी) — OWASP कुटुंबांसाठी CodeQL शी आच्छादन आहे; त्याची आधाररेषा रॅचेटशी जोडा किंवा ते वगळा. +- `check:openapi-security-tiers` (सल्लात्मक) — ❌ **स्वच्छपणे फ्लिप करता येत नाही.** ते 0 सह बाहेर पडते, परंतु `LOCAL_ONLY_API_PREFIXES` अंतर्गत अनेक `traffic-inspector` रूट्समध्ये `x-loopback-only: true` भाष्य नसल्याची चेतावणी देते. त्याची अंमलबजावणी करण्यासाठी आधी `openapi.yaml` मध्ये ही भाष्ये जोडावी लागतील. +- `typecheck:noimplicit:core` (सल्लात्मक) — ब्लॉकिंग `check:type-coverage` रॅचेटमध्ये मोठ्या प्रमाणावर समाविष्ट आहे. रॅचेटमध्ये फ्लिप करा किंवा अनावश्यक दुसरा `tsc` पास वगळा. +- `test:vitest:ui` (आता **ब्लॉकिंग**) — आधीपासून अस्तित्वात असलेली अपयशे `vitest.config.ts` मध्ये `// #8618` ट्रॅकिंग टिप्पण्यांसह स्पष्टपणे वगळली आहेत; नवीन अपयशांमुळे जॉब अपयशी होतो. +- `check:secrets` (gitleaks, 3 दस्तऐवजीकृत चुकीच्या-सकारात्मक निकालांवर गोठवलेले ब्लॉकिंग रॅचेट) — 0 पर्यंत पोहोचण्यासाठी त्या 3 निकालांना allowlist मध्ये जोडा किंवा सल्लात्मक स्तरावर अवनत करा. GitHub चे मूळ secret-scanning + `check:public-creds` यांच्याशी आच्छादित होते. +- `check:pr-evidence` (ब्लॉकिंग, PR-body मधील गद्य मजकुरावर grep करते) — चुकीच्या-सकारात्मक निकालांचा उच्च धोका; वगळल्यास कठोर नियम #18 ची अंमलबजावणी कमकुवत होते, त्यामुळे हा खरोखरच धोरणात्मक निर्णय आहे. +- `semgrep` (स्वतंत्र सल्लात्मक) — OWASP कुटुंबांसाठी CodeQL शी आच्छादित होते; त्याची आधाररेषा रॅचेटशी जोडा किंवा वगळा. --- diff --git a/docs/i18n/mr/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/mr/docs/compression/COMPRESSION_GUIDE.md index 3fad052ef4e9..f43ef5a77007 100644 --- a/docs/i18n/mr/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/mr/docs/compression/COMPRESSION_GUIDE.md @@ -451,8 +451,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages` असलेल्या बॉडीवर, आशय बायपास (`shouldBypassCavemanOutputMode()` हा `open-sse/services/compression/outputMode.ts` मधील) शेवटचे तीन संदेश तपासतो आणि ते सुरक्षा, अपरिवर्तनीय कृती, स्पष्टीकरण किंवा क्रम-संवेदनशील कीवर्डशी जुळल्यास -संपूर्ण टर्नसाठी शैली वगळतो. डॅशबोर्डचे **Auto-Clarity Bypass** टॉगल -(`cavemanOutputMode.autoClarity`) जसे सेट केलेले असेल, त्यानुसार बायपास चालतो. +संपूर्ण टर्नसाठी शैली वगळतो. डॅशबोर्डचे **Auto-Clarity Bypass** टॉगल (`cavemanOutputMode.autoClarity`) चालू असताना बायपास चालतो, जो डीफॉल्ट आहे; टॉगल बंद असताना निवडलेल्या शैली त्या टर्नवरही लागू होतात. बायपासने टर्नला अनुमती दिल्यास, `placeSystemInstruction()` (त्याच फाइलमधील), जे कधीही नवीन `messages[0]` तयार करत नाही, त्याला सापडणाऱ्या खालीलपैकी पहिल्या ठिकाणी ब्लॉक ठेवते: diff --git a/docs/i18n/mr/docs/reference/ENVIRONMENT.md b/docs/i18n/mr/docs/reference/ENVIRONMENT.md index e40b606ea334..51e852c1381f 100644 --- a/docs/i18n/mr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/mr/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. आवश्यक गुपिते -ही गुपिते पहिल्यांदा चालवण्यापूर्वी सेट करणे **आवश्यक** आहे. त्यांच्याशिवाय अनुप्रयोग सुरू होण्यास नकार देईल किंवा असुरक्षित डीफॉल्ट मूल्यांसह कार्य करेल. - -| चल | आवश्यक | डीफॉल्ट | स्रोत फाइल | वर्णन | -| ---------------------------- | ------------------------ | ------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **होय** | _(काहीही नाही)_ | `src/lib/auth` | सर्व डॅशबोर्ड सत्र कुकीजवर (JWT) स्वाक्षरी करते/त्यांची पडताळणी करते. `openssl rand -base64 48` वापरून तयार करा. | -| `API_KEY_SECRET` | **होय** | _(काहीही नाही)_ | `src/lib/db/apiKeys.ts` | SQLite मध्ये संग्रहित API की मूल्यांसाठी AES एन्क्रिप्शन की. `openssl rand -hex 32` वापरून तयार करा. | -| `INITIAL_PASSWORD` | **होय** | `CHANGEME` | बूटस्ट्रॅप स्क्रिप्ट | प्रारंभिक प्रशासक डॅशबोर्ड पासवर्ड सेट करते (`.env.example` च्या डीफॉल्टशी जुळतो — बदल करणे भाग पडावे म्हणून जाणूनबुजून स्पष्टपणे असुरक्षित ठेवलेला आहे). **प्रथम वापरापूर्वी बदला.** लॉगिन केल्यानंतर, डॅशबोर्ड → सेटिंग्ज → सुरक्षा याद्वारे बदला. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **होय** (प्रॉडक्शनमध्ये) | _(सेट केलेले नाही)_ | `src/app/api/internal/codex-responses-ws/route.ts` | अंतर्गत Codex Responses WebSocket ब्रिजसाठी सामायिक गुपित. Electron/ब्राउझर WS रिले आणि OmniRoute यांच्यातील ब्रिज विनंत्यांचे प्रमाणीकरण करते. ⚠️ **प्रॉडक्शनमध्ये आवश्यक — ते सेट केलेले नसल्यास, सर्व WS ब्रिज विनंत्या नाकारल्या जातात.** `openssl rand -base64 32` वापरून तयार करा. | -| `OMNIROUTE_SW_BUILD_ID` | नाही | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA शेलसाठी स्पष्ट service-worker कॅश-बस्टिंग आयडी (#11779); निर्धारण साखळीमध्ये प्रथम. | -| `SOURCE_VERSION` | नाही | _(सेट केलेले नाही)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | साखळीतील दुसरे — PaaS बिल्डर्सद्वारे (उदा. Heroku-शैलीतील) डिप्लॉय केलेल्या कमिटनुसार सेट केले जाते. | -| `NEXT_PUBLIC_SW_BUILD_ID` | नाही | _(व्युत्पन्न)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` नोंदवण्यासाठी क्लायंट वापरत असलेले बिल्ड-वेळेतील सार्वजनिक मूल्य; वरील दोन मूल्यांपासून आणि त्यानंतर git SHA पासून व्युत्पन्न केले जाते. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | नाही (स्वयंचलित) | _(प्रत्येक बूटवेळी स्वयंचलित)_ | `src/server/authz/policies/management.ts` | विश्वासार्ह पीअर-IP स्टॅम्प OmniRoute च्या स्वतःच्या HTTP सर्व्हरकडून (`scripts/dev/peer-stamp.mjs`) आला आहे हे सिद्ध करणारे प्रत्येक प्रक्रियेसाठी स्वतंत्र गुपित. स्टॅम्पमध्ये हे टोकन असेल तरच authz मिडलवेअर विनंतीच्या स्थानिकतेवर (LOCAL_ONLY मार्गांचे लूपबॅक/LAN गेटिंग) विश्वास ठेवते. प्रत्येक बूटवेळी स्वयंचलितपणे तयार केले जाते — सेट न करता सोडा; स्टॅम्प सामायिक करणे आवश्यक असलेल्या बहु-प्रक्रिया सेटअपसाठीच ते निश्चित करा. | -| `OMNIROUTE_TRUSTED_PROXIES` | नाही | _(सेट केलेले नाही)_ | `scripts/dev/peer-stamp.mjs` | अशा रिव्हर्स प्रॉक्सींचे स्वल्पविरामाने विभक्त केलेले IP किंवा CIDR परिक्षेत्र, ज्यांच्या `X-Forwarded-For` / `X-Real-IP` वर IP अनुमती/नकार सूची विश्वास ठेवू शकते; हे नेहमी विश्वासार्ह असलेल्या लूपबॅक, खाजगी-नेटवर्क पत्ते आणि Cloudflare एजेसच्या व्यतिरिक्त आहेत. इतर कोणत्याही सार्वजनिक पत्त्यावरील प्रॉक्सीसाठीच याची आवश्यकता असते; अन्यथा फिल्टर त्या प्रॉक्सीच्या स्वतःच्या पत्त्याचे मूल्यमापन करतो. | +**होय** या खूणेसह नमूद केलेली, सेट न केलेली गुपिते पहिल्यांदा चालवताना स्वयंचलितपणे तयार होतात (`JWT_SECRET`, `API_KEY_SECRET`) आणि डेटा डिरेक्टरीतील `server.env` मध्ये संग्रहित केली जातात — ती डिरेक्टरी क्षणिक असल्यास ती स्पष्टपणे सेट करा. याला अपवाद आहे `OMNIROUTE_WS_BRIDGE_SECRET`: ते `server.env` मध्ये संग्रहित होत नाही — सर्व्हर प्रवेशबिंदू प्रत्येक बूटसाठी एक क्षणिक मूल्य तयार करतात, जे फक्त प्रक्रियेतील ब्रिज वापरू शकतो, म्हणून बाह्य ब्रिज क्लायंट्सना ते स्पष्टपणे सेट करावे लागते. + +| चल | आवश्यक | डीफॉल्ट | स्रोत फाइल | वर्णन | +| ---------------------------- | ------------------------ | ------------------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **होय** | _(काहीही नाही)_ | `src/lib/auth` | सर्व डॅशबोर्ड सत्र कुकीजवर (JWT) स्वाक्षरी करते/त्यांची पडताळणी करते. `openssl rand -base64 48` वापरून तयार करा. | +| `API_KEY_SECRET` | **होय** | _(काहीही नाही)_ | `src/lib/db/apiKeys.ts` | SQLite मध्ये संग्रहित API की मूल्यांसाठी AES एन्क्रिप्शन की. `openssl rand -hex 32` वापरून तयार करा. | +| `INITIAL_PASSWORD` | नाही | _(सेट केलेले नाही)_ | `src/lib/auth/managementPassword.ts` | जोपर्यंत कोणताही पासवर्ड संग्रहित नाही, तोपर्यंत सुरुवातीच्या वेळी डॅशबोर्ड पासवर्ड सेट करतो; पासवर्ड संग्रहित झाल्यानंतर तो दुर्लक्षिला जातो. npm आणि सोर्स इंस्टॉल `.env.example` ची `.env` मध्ये प्रत करतात, त्यामुळे त्यांची सुरुवात त्याच्या सार्वजनिकरित्या ज्ञात प्लेसहोल्डर `CHANGEME` सोबत होते: पहिल्या बूटपूर्वी तो बदला, किंवा पहिल्या भेटीच्या वेळी ऑनबोर्डिंग विझार्डमध्ये पासवर्ड तयार करण्यासाठी त्या `.env` मध्ये `INITIAL_PASSWORD=` (रिक्त) सेट करा — जसे सेट न केलेले मूल्य (Docker इमेजचे डीफॉल्ट) करते. संग्रहित पासवर्ड डॅशबोर्ड → सेटिंग्ज → सुरक्षा मध्ये बदला, किंवा सर्व्हरच्या डेटा डिरेक्टरीवर `DATA_DIR` सेट करून `omniroute reset-password` (सोर्स चेकआउटमध्ये `node bin/reset-password.mjs`) चालवा. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **होय** (प्रॉडक्शनमध्ये) | _(सेट केलेले नाही)_ | `src/app/api/internal/codex-responses-ws/route.ts` | अंतर्गत Codex Responses WebSocket ब्रिजसाठी सामायिक गुपित. Electron/ब्राउझर WS रिले आणि OmniRoute यांच्यातील ब्रिज विनंत्यांचे प्रमाणीकरण करते. ⚠️ **प्रॉडक्शनमध्ये आवश्यक — ते सेट केलेले नसल्यास, सर्व WS ब्रिज विनंत्या नाकारल्या जातात.** `openssl rand -base64 32` वापरून तयार करा. | +| `OMNIROUTE_SW_BUILD_ID` | नाही | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA शेलसाठी स्पष्ट service-worker कॅश-बस्टिंग आयडी (#11779); निर्धारण साखळीमध्ये प्रथम. | +| `SOURCE_VERSION` | नाही | _(सेट केलेले नाही)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | साखळीतील दुसरे — PaaS बिल्डर्सद्वारे (उदा. Heroku-शैलीतील) डिप्लॉय केलेल्या कमिटनुसार सेट केले जाते. | +| `NEXT_PUBLIC_SW_BUILD_ID` | नाही | _(व्युत्पन्न)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` नोंदवण्यासाठी क्लायंट वापरत असलेले बिल्ड-वेळेतील सार्वजनिक मूल्य; वरील दोन मूल्यांपासून आणि त्यानंतर git SHA पासून व्युत्पन्न केले जाते. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | नाही (स्वयंचलित) | _(प्रत्येक बूटवेळी स्वयंचलित)_ | `src/server/authz/policies/management.ts` | विश्वासार्ह पीअर-IP स्टॅम्प OmniRoute च्या स्वतःच्या HTTP सर्व्हरकडून (`scripts/dev/peer-stamp.mjs`) आला आहे हे सिद्ध करणारे प्रत्येक प्रक्रियेसाठी स्वतंत्र गुपित. स्टॅम्पमध्ये हे टोकन असेल तरच authz मिडलवेअर विनंतीच्या स्थानिकतेवर (LOCAL_ONLY मार्गांचे लूपबॅक/LAN गेटिंग) विश्वास ठेवते. प्रत्येक बूटवेळी स्वयंचलितपणे तयार केले जाते — सेट न करता सोडा; स्टॅम्प सामायिक करणे आवश्यक असलेल्या बहु-प्रक्रिया सेटअपसाठीच ते निश्चित करा. | +| `OMNIROUTE_TRUSTED_PROXIES` | नाही | _(सेट केलेले नाही)_ | `scripts/dev/peer-stamp.mjs` | अशा रिव्हर्स प्रॉक्सींचे स्वल्पविरामाने विभक्त केलेले IP किंवा CIDR परिक्षेत्र, ज्यांच्या `X-Forwarded-For` / `X-Real-IP` वर IP अनुमती/नकार सूची विश्वास ठेवू शकते; हे नेहमी विश्वासार्ह असलेल्या लूपबॅक, खाजगी-नेटवर्क पत्ते आणि Cloudflare एजेसच्या व्यतिरिक्त आहेत. इतर कोणत्याही सार्वजनिक पत्त्यावरील प्रॉक्सीसाठीच याची आवश्यकता असते; अन्यथा फिल्टर त्या प्रॉक्सीच्या स्वतःच्या पत्त्याचे मूल्यमापन करतो. | ### निर्मिती कमांड्स @@ -215,38 +215,38 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ## 4. सुरक्षा आणि प्रमाणीकरण -| चल | डीफॉल्ट | स्रोत फाइल | वर्णन | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | मशीनचे फिंगरप्रिंट तयार करण्यासाठी हार्डवेअर अभिज्ञापकांसह एकत्रित केलेले सॉल्ट. विलगीकरणासाठी प्रत्येक डिप्लॉयमेंटनुसार ते बदला. | -| `OMNIROUTE_CLI_SALT` | _(सेट केलेले नाही = प्रत्येक इंस्टॉलेशनसाठी यादृच्छिक सॉल्ट, `/cli-token-salt.json` येथे कायमस्वरूपी जतन केले जाते)_ | `src/lib/machineToken.ts` | स्थानिक CLI प्रमाणीकरण टोकन प्राप्त करण्यासाठी HMAC सॉल्ट. हे मूल्य सेट केल्याने मशीनवरील सर्व CLI टोकन बदलली जातात आणि कायमस्वरूपी जतन केलेल्या सॉल्टपेक्षा याला नेहमी प्राधान्य दिले जाते. `docs/security/CLI_TOKEN.md` पहा. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | सत्र कुकींवर `Secure` ध्वज सेट करते. HTTPS मागे चालवत असताना हे **`true` असणे आवश्यक आहे**. | -| `REQUIRE_API_KEY` | `false` | API मिडलवेअर | `true` असताना, सर्व `/v1/*` प्रॉक्सी विनंत्यांमध्ये वैध API की असणे आवश्यक आहे. **हा ध्वज `GET /v1/models` वर निर्बंध लागू करत नाही**; त्याऐवजी तो डॅशबोर्ड लॉगिन स्थितीचे (`requireAuthForModels`) अनुसरण करतो — त्यामुळे `/v1/models` कडून मिळालेल्या `401` चा अर्थ इन्फरन्स संरक्षित आहे असा होत नाही. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) पहा. | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | डॅशबोर्ड UI मध्ये API कीची संपूर्ण मूल्ये उघड करण्याची अनुमती देते. डॅशबोर्ड वैशिष्ट्य ध्वजांमधून कॉन्फिगर करता येते; सामायिक इंस्टन्सवर यामुळे सुरक्षा जोखीम निर्माण होते. | -| `NO_LOG_API_KEY_IDS` | _(रिक्त)_ | `src/lib/compliance/index.ts` | विनंती लॉगिंग वगळणाऱ्या API की ID ची स्वल्पविरामाने विभक्त केलेली सूची (GDPR अनुपालन). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(सेट केलेले नाही = अमर्यादित)_ | `src/shared/utils/apiKeyPolicy.ts` | ज्या API कींचा `rate_limits` स्तंभ null आहे, त्यांना लागू होणारी प्रतिदिन विनंती मर्यादा. सेट न केलेले किंवा रिक्त असल्यास: कोणतीही अप्रत्यक्ष मर्यादा नाही (#2289, #11017). `0` चा अर्थही तोच (अमर्यादित). धन पूर्णांक N मुळे N/दिवस, 5N/आठवडा, 20N/महिना सक्षम होते. चुकीच्या स्वरूपातील रिक्त नसलेली मूल्ये जुन्या 1000/दिवस, 5000/आठवडा, 20000/महिना मर्यादांकडे परत जातात. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | विनंतीच्या बॉडीचा अनुमत कमाल आकार. ही मर्यादा ओलांडणारे पेलोड नाकारते. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | या मर्यादेइतक्या किंवा त्यापेक्षा मोठ्या प्रत्यक्ष विनंती बॉडी JSON पार्सिंगपूर्वी अणूस्तरीय, प्रोसेस-लोकल हेवीवेट अॅडमिशन लीज घेतात (`POST /v1/responses` सह BYTE मार्ग). स्ट्रक्चर-हेवीप्रमाणेच [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) मधील निरोगी हेडरूम एस्केप लागू होते; तरीही ते `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) द्वारे मर्यादित राहते, जेणेकरून [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) पुन्हा उद्भवणार नाही. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | मर्यादित अंतर्ग्रहणादरम्यान वाचलेल्या बाइट्सवर लागू केलेली चॅट-रूटची कठोर कमाल मर्यादा; यात `Content-Length` गहाळ, अवैध किंवा दिशाभूल करणारे असलेल्या विनंत्यांचाही समावेश होतो; मर्यादा ओलांडल्यास `413` मिळतो. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(सेट केलेले नाही — विनंती-संख्येवर मर्यादा नाही)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ही जुनी विनंती-COUNT मर्यादा आता केवळ स्पष्टपणे सेट केल्यावरच लागू होते. ती सेट न केल्यास (डीफॉल्ट), हेवीवेट चॅट प्रवेश त्याऐवजी `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` द्वारे नियंत्रित केला जातो — **एका प्रक्रियेत** (एक V8 heap) प्रक्रियेच्या वास्तविक मेमरी मर्यादेवरून आकारमान आपोआप ठरवलेले BYTE बजेट. एकमेकांवर ओव्हरलॅप होणाऱ्या सुमारे 750k-token च्या दोन `/v1/responses` विनंत्या सुमारे 12 Gi heaps (#7849) बंद पाडतात — हा **मेमरी-बजेट** इशारा आहे, उत्पादनाची 2 ही कठोर कमाल मर्यादा नाही. निरोगी प्रक्रिया (shed ratio पेक्षा कमी heap) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` द्वारे अधिक समवर्ती दीर्घ `/v1/responses` स्वीकारू शकते. दीर्घकाळ चालणारे दहाच्या पटीतील SSE क्लायंट (40–50) हे heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 यांवर अवलंबून आहेत, “कमाल 2” वर नाहीत. “होस्टचा वापर करण्यासाठी” हे विचार न करता वाढवल्यास #7849 पुन्हा उद्भवतो. **N स्वतंत्र `DATA_DIR`s** (#11024) वापरून **heaps** गुणाकाराने वाढवा; एका SQLite फाइलवर कधीही `replicas>1` वापरू नका. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(आपोआप निर्धारित)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** आपोआप निर्धारित केलेल्या अंतर्ग्रहण बाइट बजेटसाठी ओव्हरराइड (अधिक कठोर V8/cgroup मेमरी मर्यादेच्या 25% भागिले 8x क्षणिक वृद्धी). निर्धारित आणि स्पष्ट मूल्ये 8 MiB–2 GiB या मर्यादेत बांधली जातात. प्रभावी बजेटपेक्षा मोठी body त्वरित `413 body_exceeds_budget` सह अयशस्वी होते; स्वतंत्रपणे सेवा देता येण्याजोग्या bodies मधील स्पर्धेसाठी पुन्हा प्रयत्न करता येणारा `503` मिळतो. 40–50 समवर्ती दीर्घ SSE क्लायंट हे या बजेटवर + heap वर अवलंबून आहेत, “कमाल 2” या कठोर मर्यादेवर नाहीत. ट्यूनिंग करण्यापूर्वी `/api/monitoring/health` येथे `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` वाचा. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE आणि STRUCTURE हेवीवेट प्रवेशासाठी (#10183, #10268, #10437) heap-pressure shed ratio (`heapUsed / heap_size_limit`). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` च्या पलीकडील समवर्ती हेवीवेट विनंतीला heap देखील या गुणोत्तराएवढा किंवा त्यापेक्षा जास्त असल्यावरच पुन्हा प्रयत्न करता येणाऱ्या `503` सह नाकारले जाते; निरोगी heap वर ती त्याऐवजी healthy-headroom द्वारे स्वीकारली जाते. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (डीफॉल्ट `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **दोन्ही** STRUCTURE आणि BYTE (`admitChatRequest`, ज्यात ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` bodies समाविष्ट आहेत) वरील healthy-heap जलद मार्गासाठी (#10437) मर्यादित अतिरिक्त क्षमता. या मर्यादेशिवाय, व्यस्त-पण-निरोगी-heap असलेली प्रत्येक विनंती कोणत्याही कमाल मर्यादेशिवाय प्रवेश नियंत्रण टाळत होती. healthy-heap मार्गाद्वारे इतक्या समवर्ती leases सक्रिय झाल्यावर, पुढील व्यस्त विनंत्या वास्तविक heap दबावाखाली वापरल्या जाणाऱ्या त्याच मर्यादित-प्रतीक्षा/नकार मार्गाकडे जातात. `0` हा बायपास पूर्णपणे अक्षम करतो. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | चॅट विनंतीची body बाइट मर्यादेपेक्षा कमी असली तरी तिला हेवीवेट म्हणून वर्गीकृत करणारी संदेश संख्या. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | चॅट विनंतीची body बाइट मर्यादेपेक्षा कमी असली तरी तिला हेवीवेट म्हणून वर्गीकृत करणारी टूल संख्या. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | विनंतीला हेवीवेट म्हणून वर्गीकृत करणारा मजकूर-आकाराचा सावध token अंदाज; हा प्रवेश-खर्चाचा प्रॉक्सी आहे, provider billing tokenization नाही. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (अक्षम) | `src/shared/middleware/chatBodyAdmission.ts` | पर्यायी opt-in चॅट इतिहास मर्यादा. डीफॉल्टनुसार अक्षम: संदेश संख्या हे deployment धोरण आहे, विनंतीचा सार्वत्रिक गुणधर्म नाही; आणि येथे मर्यादा लावल्यास compression pipeline संभाषणे सेवायोग्य बनवण्यापूर्वीच ती अंतिम `413` सह नाकारली जातात. Heap वाढ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` आणि heap-pressure shed द्वारे मर्यादित केली जाते. कठोर कमाल मर्यादा आवश्यक असलेल्या मेमरी-मर्यादित deployments वर धन मूल्य सेट करा; त्यानंतर मर्यादा ओलांडल्यास संरचित compact-required `413` मिळतो. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | मेमरीमध्ये पूर्णपणे बफर केलेल्या non-streaming upstream प्रतिसादासाठी कठोर कमाल मर्यादा. ही मर्यादा ओलांडल्यानंतर upstream reader रद्द केला जातो आणि heap संपेपर्यंत अमर्यादित string वाढू देण्याऐवजी विनंती त्वरित अयशस्वी होते. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | अपस्ट्रीम प्रतिसाद हेडर्समधून फॉरवर्ड केल्या जाणाऱ्या कमाल वायर बाइट्स. बजेट ओलांडल्यावर, सामान्य रिव्हर्स-प्रॉक्सी हेडर मर्यादांमध्ये राहण्यासाठी कमी प्राधान्याचे हेडर्स (उदा., सानुकूल `x-codex-*`, `x-oai-request-id`) वगळले जातात. प्रतिसाद हेडरचा आकार वाढण्याच्या बदल्यात अधिक अपस्ट्रीम मेटाडेटा फॉरवर्ड करण्यासाठी याचे मूल्य वाढवा. | -| `CORS_ORIGIN` | _(सेट केलेले नाही)_ | `src/server/cors/origins.ts` | लेगसी एकल-ओरिजिन CORS अनुमतीसूची. नवीन डिप्लॉयमेंटसाठी `CORS_ALLOWED_ORIGINS` वापरण्यास प्राधान्य द्या. CORS केवळ क्रॉस-ओरिजिन ब्राउझर API क्लायंटसाठी आहे; त्याऐवजी प्रमाणीकरण केलेल्या डॅशबोर्ड लेखन विनंत्या समान-ओरिजिन विनंत्या आणि सत्राशी बांधलेले CSRF संरक्षण वापरतात. | -| `CORS_ALLOWED_ORIGINS` | _(सेट केलेले नाही)_ | `src/server/cors/origins.ts` | स्वल्पविरामाने विभक्त केलेली CORS अनुमतीसूची. `CORS_ALLOW_ALL=true` स्पष्टपणे कॉन्फिगर केल्याशिवाय कोणतेही वाइल्डकार्ड पाठवले जात नाही. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | कोणताही ब्राउझर `Origin` प्रतिध्वनित करण्यासाठी केवळ विकासासाठी असलेला पर्यायी मार्ग. सामायिक किंवा प्रॉडक्शन डिप्लॉयमेंटवर हे सक्षम करू नका. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | खाजगी/लूपबॅक/लिंक-लोकल IP श्रेणींना लक्ष्य करणारे प्रोव्हायडर कॉल ब्लॉक करा. केवळ विलगीकृत चाचणी वातावरणांमध्ये अक्षम करा. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | खाजगी/स्थानिक नेटवर्ककडे निर्देश करणाऱ्या प्रोव्हायडर URLना परवानगी द्या (localhost, 192.168.x.x, 10.x.x.x, इ.). **स्वतः होस्ट केलेल्या प्रोव्हायडरसाठी आवश्यक** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). `false` असताना, डॅशबोर्ड स्थानिक URLचे प्रमाणीकरण नाकारतो. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | स्थानिक/खाजगी पत्त्यांवर (127.0.0.1, localhost, LAN, खाजगी श्रेणी) प्रोव्हायडर जोडण्यास/प्रमाणित करण्यास परवानगी द्या — हे प्रोव्हायडर प्रमाणीकरण मार्गापुरते मर्यादित आहे. **डीफॉल्ट `true`** (स्थानिक-प्रथम); केवळ सार्वजनिक पत्त्यांसाठी कठोर ब्लॉकिंग लागू करण्यासाठी `false` सेट करा. क्लाउड-मेटाडेटा एंडपॉइंट्स (169.254.169.254, metadata.google.internal) कोणत्याही परिस्थितीत ब्लॉकच राहतात. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` रूट्सना (लिप्यंतरण, स्पीच, भाषांतरे) localhost च्या बाहेर होस्ट केलेला OpenAI-सुसंगत प्रोव्हायडर नोड वापरू द्या. डीफॉल्टनुसार बंद — ऑडिओ दूरस्थ होस्टकडे रूट केल्याने इग्रेस ओळख बदलते आणि तो ऑपरेटरचा स्पष्ट निर्णय असणे आवश्यक आहे. लूपबॅक/खाजगी नोड्सना (localhost, 127.0.0.1, 172.16-31.x) नेहमी परवानगी असते आणि त्यांच्यावर परिणाम होत नाही. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` ला (आणि मेमरी इंजिनच्या लूपबॅक रीरँक टप्प्याला) localhost च्या बाहेर होस्ट केलेला OpenAI-सुसंगत प्रोव्हायडर नोड वापरू द्या — म्हणजे TEI, Infinity, vLLM इ. चालवणारा LAN बॉक्स किंवा Tailscale पीअर. डीफॉल्टनुसार बंद — दूरस्थ होस्टकडे रूट केल्याने इग्रेस ओळख बदलते आणि तो ऑपरेटरचा स्पष्ट निर्णय असणे आवश्यक आहे. लूपबॅक नोड्सना (localhost, 127.0.0.1, 172.16-31.x) नेहमी परवानगी असते आणि त्यांच्यावर परिणाम होत नाही. दूरस्थ नोड्सनी प्रोव्हायडर आउटबाउंड URL धोरणदेखील (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) पूर्ण केले पाहिजे; क्लाउड-मेटाडेटा होस्टकडे कधीही रूट केले जात नाही. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC सक्षम असताना, पासवर्ड लॉगिन अक्षम करा, जेणेकरून वापरकर्ते फक्त OIDC Single Sign-On द्वारे प्रमाणीकरण करू शकतील. साधे उपनाव `OIDC_DISABLE_PASSWORD_LOGIN` देखील स्वीकारले जाते; त्याच कीच्या Dashboard Feature Flag ला प्राधान्य दिले जाते. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` चे साधे उपनाव (#10889). | +| चल | डीफॉल्ट | स्रोत फाइल | वर्णन | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | मशीनचे फिंगरप्रिंट तयार करण्यासाठी हार्डवेअर अभिज्ञापकांसह एकत्रित केलेले सॉल्ट. विलगीकरणासाठी प्रत्येक डिप्लॉयमेंटनुसार ते बदला. | +| `OMNIROUTE_CLI_SALT` | _(सेट केलेले नाही = प्रत्येक इंस्टॉलेशनसाठी यादृच्छिक सॉल्ट, `/cli-token-salt.json` येथे कायमस्वरूपी जतन केले जाते)_ | `src/lib/machineToken.ts` | स्थानिक CLI प्रमाणीकरण टोकन प्राप्त करण्यासाठी HMAC सॉल्ट. हे मूल्य सेट केल्याने मशीनवरील सर्व CLI टोकन बदलली जातात आणि कायमस्वरूपी जतन केलेल्या सॉल्टपेक्षा याला नेहमी प्राधान्य दिले जाते. `docs/security/CLI_TOKEN.md` पहा. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | सत्र कुकींवर `Secure` ध्वज सेट करते. HTTPS मागे चालवत असताना हे **`true` असणे आवश्यक आहे**. | +| `REQUIRE_API_KEY` | `false` | API मिडलवेअर | `true` असताना, सर्व `/v1/*` प्रॉक्सी विनंत्यांमध्ये वैध API की असणे आवश्यक आहे. **हा ध्वज `GET /v1/models` वर निर्बंध लागू करत नाही**; त्याऐवजी तो डॅशबोर्ड लॉगिन स्थितीचे (`requireAuthForModels`) अनुसरण करतो — त्यामुळे `/v1/models` कडून मिळालेल्या `401` चा अर्थ इन्फरन्स संरक्षित आहे असा होत नाही. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) पहा. | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | डॅशबोर्ड UI मध्ये API कीची संपूर्ण मूल्ये उघड करण्याची अनुमती देते. डॅशबोर्ड वैशिष्ट्य ध्वजांमधून कॉन्फिगर करता येते; सामायिक इंस्टन्सवर यामुळे सुरक्षा जोखीम निर्माण होते. | +| `NO_LOG_API_KEY_IDS` | _(रिक्त)_ | `src/lib/compliance/index.ts` | विनंती लॉगिंग वगळणाऱ्या API की ID ची स्वल्पविरामाने विभक्त केलेली सूची (GDPR अनुपालन). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(सेट केलेले नाही = अमर्यादित)_ | `src/shared/utils/apiKeyPolicy.ts` | ज्या API कींचा `rate_limits` स्तंभ null आहे, त्यांना लागू होणारी प्रतिदिन विनंती मर्यादा. सेट न केलेले किंवा रिक्त असल्यास: कोणतीही अप्रत्यक्ष मर्यादा नाही (#2289, #11017). `0` चा अर्थही तोच (अमर्यादित). धन पूर्णांक N मुळे N/दिवस, 5N/आठवडा, 20N/महिना सक्षम होते. चुकीच्या स्वरूपातील रिक्त नसलेली मूल्ये जुन्या 1000/दिवस, 5000/आठवडा, 20000/महिना मर्यादांकडे परत जातात. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | विनंतीच्या बॉडीचा अनुमत कमाल आकार. ही मर्यादा ओलांडणारे पेलोड नाकारते. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | या मर्यादेइतक्या किंवा त्यापेक्षा मोठ्या प्रत्यक्ष विनंती बॉडी JSON पार्सिंगपूर्वी अणूस्तरीय, प्रोसेस-लोकल हेवीवेट अॅडमिशन लीज घेतात (`POST /v1/responses` सह BYTE मार्ग). स्ट्रक्चर-हेवीप्रमाणेच [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) मधील निरोगी हेडरूम एस्केप लागू होते; तरीही ते `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) द्वारे मर्यादित राहते, जेणेकरून [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) पुन्हा उद्भवणार नाही. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | मर्यादित अंतर्ग्रहणादरम्यान वाचलेल्या बाइट्सवर लागू केलेली चॅट-रूटची कठोर कमाल मर्यादा; यात `Content-Length` गहाळ, अवैध किंवा दिशाभूल करणारे असलेल्या विनंत्यांचाही समावेश होतो; मर्यादा ओलांडल्यास `413` मिळतो. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(सेट केलेले नाही — विनंती-संख्येवर मर्यादा नाही)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ही जुनी विनंती-COUNT मर्यादा आता केवळ स्पष्टपणे सेट केल्यावरच लागू होते. ती सेट न केल्यास (डीफॉल्ट), हेवीवेट चॅट प्रवेश त्याऐवजी `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` द्वारे नियंत्रित केला जातो — **एका प्रक्रियेत** (एक V8 heap) प्रक्रियेच्या वास्तविक मेमरी मर्यादेवरून आकारमान आपोआप ठरवलेले BYTE बजेट. एकमेकांवर ओव्हरलॅप होणाऱ्या सुमारे 750k-token च्या दोन `/v1/responses` विनंत्या सुमारे 12 Gi heaps (#7849) बंद पाडतात — हा **मेमरी-बजेट** इशारा आहे, उत्पादनाची 2 ही कठोर कमाल मर्यादा नाही. निरोगी प्रक्रिया (shed ratio पेक्षा कमी heap) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` द्वारे अधिक समवर्ती दीर्घ `/v1/responses` स्वीकारू शकते. दीर्घकाळ चालणारे दहाच्या पटीतील SSE क्लायंट (40–50) हे heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 यांवर अवलंबून आहेत, “कमाल 2” वर नाहीत. “होस्टचा वापर करण्यासाठी” हे विचार न करता वाढवल्यास #7849 पुन्हा उद्भवतो. **N स्वतंत्र `DATA_DIR`s** (#11024) वापरून **heaps** गुणाकाराने वाढवा; एका SQLite फाइलवर कधीही `replicas>1` वापरू नका. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(आपोआप निर्धारित)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** आपोआप निर्धारित केलेल्या अंतर्ग्रहण बाइट बजेटसाठी ओव्हरराइड (अधिक कठोर V8/cgroup मेमरी मर्यादेच्या 25% भागिले 8x क्षणिक वृद्धी). निर्धारित आणि स्पष्ट मूल्ये 8 MiB–2 GiB या मर्यादेत बांधली जातात. प्रभावी बजेटपेक्षा मोठी body त्वरित `413 body_exceeds_budget` सह अयशस्वी होते; स्वतंत्रपणे सेवा देता येण्याजोग्या bodies मधील स्पर्धेसाठी पुन्हा प्रयत्न करता येणारा `503` मिळतो. 40–50 समवर्ती दीर्घ SSE क्लायंट हे या बजेटवर + heap वर अवलंबून आहेत, “कमाल 2” या कठोर मर्यादेवर नाहीत. ट्यूनिंग करण्यापूर्वी `/api/monitoring/health` येथे `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` वाचा. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE आणि STRUCTURE हेवीवेट प्रवेशासाठी (#10183, #10268, #10437) heap-pressure shed ratio (`heapUsed / heap_size_limit`). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` च्या पलीकडील समवर्ती हेवीवेट विनंतीला heap देखील या गुणोत्तराएवढा किंवा त्यापेक्षा जास्त असल्यावरच पुन्हा प्रयत्न करता येणाऱ्या `503` सह नाकारले जाते; निरोगी heap वर ती त्याऐवजी healthy-headroom द्वारे स्वीकारली जाते. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (डीफॉल्ट `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **दोन्ही** STRUCTURE आणि BYTE (`admitChatRequest`, ज्यात ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` bodies समाविष्ट आहेत) वरील healthy-heap जलद मार्गासाठी (#10437) मर्यादित अतिरिक्त क्षमता. या मर्यादेशिवाय, व्यस्त-पण-निरोगी-heap असलेली प्रत्येक विनंती कोणत्याही कमाल मर्यादेशिवाय प्रवेश नियंत्रण टाळत होती. healthy-heap मार्गाद्वारे इतक्या समवर्ती leases सक्रिय झाल्यावर, पुढील व्यस्त विनंत्या वास्तविक heap दबावाखाली वापरल्या जाणाऱ्या त्याच मर्यादित-प्रतीक्षा/नकार मार्गाकडे जातात. `0` हा बायपास पूर्णपणे अक्षम करतो. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | चॅट विनंतीची body बाइट मर्यादेपेक्षा कमी असली तरी तिला हेवीवेट म्हणून वर्गीकृत करणारी संदेश संख्या. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | चॅट विनंतीची body बाइट मर्यादेपेक्षा कमी असली तरी तिला हेवीवेट म्हणून वर्गीकृत करणारी टूल संख्या. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | विनंतीला हेवीवेट म्हणून वर्गीकृत करणारा मजकूर-आकाराचा सावध token अंदाज; हा प्रवेश-खर्चाचा प्रॉक्सी आहे, provider billing tokenization नाही. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (अक्षम) | `src/shared/middleware/chatBodyAdmission.ts` | पर्यायी opt-in चॅट इतिहास मर्यादा. डीफॉल्टनुसार अक्षम: संदेश संख्या हे deployment धोरण आहे, विनंतीचा सार्वत्रिक गुणधर्म नाही; आणि येथे मर्यादा लावल्यास compression pipeline संभाषणे सेवायोग्य बनवण्यापूर्वीच ती अंतिम `413` सह नाकारली जातात. Heap वाढ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` आणि heap-pressure shed द्वारे मर्यादित केली जाते. कठोर कमाल मर्यादा आवश्यक असलेल्या मेमरी-मर्यादित deployments वर धन मूल्य सेट करा; त्यानंतर मर्यादा ओलांडल्यास संरचित compact-required `413` मिळतो. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | मेमरीमध्ये पूर्णपणे बफर केलेल्या non-streaming upstream प्रतिसादासाठी कठोर कमाल मर्यादा. ही मर्यादा ओलांडल्यानंतर upstream reader रद्द केला जातो आणि heap संपेपर्यंत अमर्यादित string वाढू देण्याऐवजी विनंती त्वरित अयशस्वी होते. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | अपस्ट्रीम प्रतिसाद हेडर्समधून फॉरवर्ड केल्या जाणाऱ्या कमाल वायर बाइट्स. बजेट ओलांडल्यावर, सामान्य रिव्हर्स-प्रॉक्सी हेडर मर्यादांमध्ये राहण्यासाठी कमी प्राधान्याचे हेडर्स (उदा., सानुकूल `x-codex-*`, `x-oai-request-id`) वगळले जातात. प्रतिसाद हेडरचा आकार वाढण्याच्या बदल्यात अधिक अपस्ट्रीम मेटाडेटा फॉरवर्ड करण्यासाठी याचे मूल्य वाढवा. | +| `CORS_ORIGIN` | _(सेट केलेले नाही)_ | `src/server/cors/origins.ts` | लेगसी एकल-ओरिजिन CORS अनुमतीसूची. नवीन डिप्लॉयमेंटसाठी `CORS_ALLOWED_ORIGINS` वापरण्यास प्राधान्य द्या. CORS केवळ क्रॉस-ओरिजिन ब्राउझर API क्लायंटसाठी आहे; त्याऐवजी प्रमाणीकरण केलेल्या डॅशबोर्ड लेखन विनंत्या समान-ओरिजिन विनंत्या आणि सत्राशी बांधलेले CSRF संरक्षण वापरतात. | +| `CORS_ALLOWED_ORIGINS` | _(सेट केलेले नाही)_ | `src/server/cors/origins.ts` | स्वल्पविरामाने विभक्त केलेली CORS अनुमतीसूची. `CORS_ALLOW_ALL=true` स्पष्टपणे कॉन्फिगर केल्याशिवाय कोणतेही वाइल्डकार्ड पाठवले जात नाही. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | कोणताही ब्राउझर `Origin` प्रतिध्वनित करण्यासाठी केवळ विकासासाठी असलेला पर्यायी मार्ग. सामायिक किंवा प्रॉडक्शन डिप्लॉयमेंटवर हे सक्षम करू नका. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | जुने अलायस (लेगसी). याचा डॅशबोर्ड टॉगल (DB ओव्हरराइड) या एन्व्हायर्नमेंट मूल्यापूर्वी वाचला जातो; दोनपैकी कोणत्याहीमध्ये `false`, `0`, `no` किंवा `off` असल्यास, त्या फ्लॅगचा डॅशबोर्ड टॉगल बंद सेट केलेला असला तरीही, ते होस्ट तपासण्या बंद करते — जसे `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` करते; इतर मूल्यांमध्ये गार्ड खालील दोन फ्लॅग्सकडे सोपवला जातो. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | गार्ड मोडचे पालन करणाऱ्या प्रोव्हायडर मार्गांवर — URL प्रमाणीकरण, मॉडेल शोध, प्रोव्हायडर-नोड बेस URL (दूरस्थ रीरँक नोड्ससह), प्रॉक्सी-फॉलबॅक चाचणी, तसेच दूरस्थ प्रतिमा/मीडिया फेच सारख्या इतर प्रोव्हायडर-संबंधित आउटबाउंड मार्गांवर — आउटबाउंड URL गार्डच्या होस्ट तपासण्या, क्लाउड-मेटाडेटा ब्लॉकसह (169.254.0.0/16 पूर्ण, metadata.google.internal), बंद करतो. क्लाउड-मेटाडेटा/लिंक-लोकल एंडपॉइंट्स वगळता, ते खाजगी वेबहुक लक्ष्यांनाही परवानगी देते — जे या फ्लॅग चालू असतानाही ब्लॉकच राहतात. `BaseExecutor.execute()` मार्फत पाठवलेल्या चॅट विनंत्यांची क्लाउड-मेटाडेटा तपासणी कायम राहते. स्थानिक प्रोव्हायडर डीफॉल्टसह कार्य करतात: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (डीफॉल्ट `true`) प्रमाणीकरण, शोध आणि प्रोव्हायडर-नोड मार्गांवर स्थानिक आणि LAN URL यांना आधीच परवानगी देतो; प्रॉक्सी-फॉलबॅक चाचणी आणि खाजगी वेबहुक लक्ष्ये फक्त या फ्लॅगचा सल्ला घेतात आणि तो बंद असताना स्थानिक/LAN होस्ट ब्लॉक करतात. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` ला ओव्हरराइड करतो. डॅशबोर्ड टॉगलमधून संग्रहित मूल्याला या चलापेक्षा प्राधान्य आहे. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | प्रोव्हायडर प्रमाणीकरण, मॉडेल शोध, चॅट आणि गार्ड मोड ज्या इतर प्रोव्हायडर-संबंधित आउटबाउंड मार्गांना नियंत्रित करतो त्यांसाठी स्थानिक/खाजगी पत्त्यांवरील (127.0.0.1, localhost, LAN, खाजगी श्रेणी) प्रोव्हायडर URL ला परवानगी देतो. **डीफॉल्ट `true`** (स्थानिक-प्रथम): तेव्हा गार्ड क्लाउड-मेटाडेटा एंडपॉइंट्सना — 169.254.0.0/16 पूर्ण तसेच ज्ञात मेटाडेटा होस्टनेम्स (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) — ब्लॉक करतो. फक्त-सार्वजनिक मोडसाठी `false` सेट करा, जो खाजगी आणि लूपबॅक होस्टनाही ब्लॉक करतो. कोणताही DNS लुकअप होण्यापूर्वी, गार्ड होस्टनेम किंवा IP लिटरल लिहिल्याप्रमाणे तपासतो; फक्त-सार्वजनिक मोडमध्ये safe-fetch रॅपर अतिरिक्त होस्टनेमचे निराकरण करतो आणि कोणतेही खाजगी उत्तर नाकारतो. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` त्याच्या ओळीत सूचीबद्ध मार्गांवर या तपासण्या बंद करतो. LM Studio, Ollama, vLLM, Llamafile आणि Triton सारखे अंतर्निहित स्थानिक प्रोव्हायडर की प्रमाणीकरण आणि चॅटसाठी सर्व होस्ट तपासण्या वगळतात. डॅशबोर्ड टॉगलमधून संग्रहित मूल्याला या चलापेक्षा प्राधान्य आहे. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` रूट्सना (लिप्यंतरण, स्पीच, भाषांतरे) localhost च्या बाहेर होस्ट केलेला OpenAI-सुसंगत प्रोव्हायडर नोड वापरू द्या. डीफॉल्टनुसार बंद — ऑडिओ दूरस्थ होस्टकडे रूट केल्याने इग्रेस ओळख बदलते आणि तो ऑपरेटरचा स्पष्ट निर्णय असणे आवश्यक आहे. लूपबॅक/खाजगी नोड्सना (localhost, 127.0.0.1, 172.16-31.x) नेहमी परवानगी असते आणि त्यांच्यावर परिणाम होत नाही. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` ला (आणि मेमरी इंजिनच्या लूपबॅक रीरँक टप्प्याला) localhost च्या बाहेर होस्ट केलेला OpenAI-सुसंगत प्रोव्हायडर नोड वापरू द्या — म्हणजे TEI, Infinity, vLLM इ. चालवणारा LAN बॉक्स किंवा Tailscale पीअर. डीफॉल्टनुसार बंद — दूरस्थ होस्टकडे रूट केल्याने इग्रेस ओळख बदलते आणि तो ऑपरेटरचा स्पष्ट निर्णय असणे आवश्यक आहे. लूपबॅक नोड्सना (localhost, 127.0.0.1, 172.16-31.x) नेहमी परवानगी असते आणि त्यांच्यावर परिणाम होत नाही. दूरस्थ नोडच्या बेस URL ने देखील प्रोव्हायडर आउटबाउंड URL धोरण (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) पूर्ण केले पाहिजे. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC सक्षम असताना, पासवर्ड लॉगिन अक्षम करा, जेणेकरून वापरकर्ते फक्त OIDC Single Sign-On द्वारे प्रमाणीकरण करू शकतील. साधे उपनाव `OIDC_DISABLE_PASSWORD_LOGIN` देखील स्वीकारले जाते; त्याच कीच्या Dashboard Feature Flag ला प्राधान्य दिले जाते. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` चे साधे उपनाव (#10889). | ### सुरक्षा सुदृढीकरण तपासणीसूची diff --git a/docs/i18n/ms/CONTRIBUTING.md b/docs/i18n/ms/CONTRIBUTING.md index 1bb3f9ad7e0f..e8e80e7f7129 100644 --- a/docs/i18n/ms/CONTRIBUTING.md +++ b/docs/i18n/ms/CONTRIBUTING.md @@ -389,19 +389,19 @@ Tulis ujian unit dalam `tests/unit/` yang sekurang-kurangnya merangkumi: ## Senarai Semak Pull Request - [ ] Ujian lulus (`npm test`) -- [ ] Pelintiran lulus (`npm run lint`) +- [ ] Pelintan lulus (`npm run lint`) - [ ] Binaan berjaya (`npm run build`) - [ ] Jenis TypeScript ditambahkan untuk fungsi dan antara muka awam baharu - [ ] Tiada rahsia atau nilai sandaran yang dikod keras -- [ ] Bukti kelayakan huluan awam dibenamkan melalui `resolvePublicCred()` (lihat [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), bukan sebagai nilai literal -- [ ] Respons ralat disalurkan melalui `buildErrorBody()` / `sanitizeErrorMessage()` — tiada surihan tindanan mentah dalam badan respons (lihat [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Kelayakan huluan awam dibenamkan melalui `resolvePublicCred()` (lihat [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), bukan sebagai literal +- [ ] Respons ralat disalurkan melalui `buildErrorBody()` / `sanitizeErrorMessage()` — tiada surih tindanan mentah dalam badan respons (lihat [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Perintah shell (`exec` / `spawn`) menghantar nilai masa jalan melalui `env`, bukan melalui interpolasi rentetan - [ ] Semua input disahkan dengan skema Zod -- [ ] **Fragmen** log perubahan ditambahkan di bawah `changelog.d/{features|fixes|maintenance}/-.md` untuk perubahan yang dapat dilihat oleh pengguna (lihat [`changelog.d/README.md`](./changelog.d/README.md)) — jangan edit `CHANGELOG.md` secara langsung; fragmen dihimpunkan semasa keluaran dan tidak akan bercanggah antara PR +- [ ] **Fragmen** log perubahan ditambahkan di bawah `changelog.d/{features|fixes|maintenance}/-.md` untuk perubahan yang memberi kesan kepada pengguna (lihat [`changelog.d/README.md`](./changelog.d/README.md)) — **jangan** edit `CHANGELOG.md` secara langsung; fragmen digabungkan semasa keluaran dan tidak pernah bercanggah antara PR - [ ] Dokumentasi dikemas kini (jika berkenaan) -- [ ] Tiada amaran CodeQL / Secret-Scanning baharu dibuka, atau setiap amaran ditolak dengan justifikasi teknikal yang merujuk dokumen `docs/security/` yang berkaitan -- [ ] Laluan yang melancarkan proses anak (`/api/mcp/`, `/api/cli-tools/runtime/`) diklasifikasikan sebagai `isLocalOnlyPath()` dalam `src/server/authz/routeGuard.ts` — lihat [Peraturan Tegas #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Tiada treler `Co-Authored-By` dalam mesej komit — komit mesti dipaparkan semata-mata di bawah identiti Git pemilik repositori (Peraturan Tegas #16) +- [ ] Tiada amaran CodeQL / Secret-Scanning baharu dibuka, atau setiap satunya ditolak dengan justifikasi teknikal yang merujuk dokumen `docs/security/` yang berkaitan +- [ ] Laluan yang mencetuskan proses anak (`/api/mcp/`, `/api/cli-tools/runtime/`) diklasifikasikan sebagai `isLocalOnlyPath()` dalam `src/server/authz/routeGuard.ts` — lihat [Peraturan Tegas #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Tiada treler `Co-authored-by` AI/bot dalam mesej komit (Peraturan Tegas #16) — kolaborator manusia yang hasil kerja mereka digunakan semula diberi penghargaan dengan treler standard `Co-authored-by: Name ` --- diff --git a/docs/i18n/ms/docs/architecture/QUALITY_GATES.md b/docs/i18n/ms/docs/architecture/QUALITY_GATES.md index 2e186f8a8be4..9143fb05ea07 100644 --- a/docs/i18n/ms/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ms/docs/architecture/QUALITY_GATES.md @@ -17,83 +17,81 @@ bebas alat bagi sistem yang sama, lihat ## Inventori get dan profil pelaksanaan -### Penerimaan calon +### Kemasukan calon Aliran kerja CI dan Quality Gates masing-masing menghasilkan keputusan yang stabil: `Gate / CI` dan -`Gate / Quality`. Dasar penerimaan berversi mereka menyenaraikan setiap tugas huluan -sebagai wajib atau nasihat. Tugas wajib yang berkenaan mesti berjaya: hasil yang tiada, +`Gate / Quality`. Dasar kemasukan berversi mereka menyenaraikan setiap kerja huluan +sebagai wajib atau nasihat. Kerja wajib yang berkenaan mesti berjaya: hasil yang tiada, dibatalkan, dilangkau, belum selesai dan tidak diketahui tidak boleh menetapkan PASS. Pengelasan docs-only atau catalog-only yang sah boleh menjadikan laluan kod tidak berkenaan; -PR draf bukan calon yang diterima. Label `hotfix` tidak mengetepikan bukti. +PR draf bukan calon yang diterima. Label `hotfix` tidak mengetepikan keperluan bukti. -Kedua-dua aliran kerja meliputi PR dan push ke cabang main/release, pencetusan manual dan -peristiwa merge-group. Push, dispatch dan merge-group menjalankan pemilihan penuh. Fork -dan merge group menggunakan pelaksana dihoskan untuk tugas yang sebaliknya memilih pelaksana -dihoskan sendiri; kapasiti hos yang mencukupi mesti disahkan sebelum pelaksanaan. +Kedua-dua aliran kerja meliputi PR dan tolakan ke cabang main/release, pelaksanaan manual dan +peristiwa kumpulan cantuman. Tolakan, pelaksanaan dan kumpulan cantuman menjalankan pemilihan penuh. Fork +dan kumpulan cantuman menggunakan pelaksana yang dihoskan untuk kerja yang sebaliknya memilih pelaksana +dihoskan sendiri; kapasiti pengehosan yang mencukupi mesti disahkan sebelum pelaksanaan secara meluas. -Setiap resit JSON mengenal pasti SHA yang di-checkout, pelaksanaan aliran kerja dan percubaan. +Setiap resit JSON mengenal pasti SHA yang telah di-checkout, pelaksanaan aliran kerja dan percubaan. CLI menolak ketidakpadanan SHA checkout/peristiwa. Ujian aliran kerja mengikat keahlian dasar -kepada senarai `needs` tugas keputusan supaya laluan baharu atau yang dialih keluar tidak boleh -hilang secara senyap. Resit tersebut meliputi aliran kerjanya sendiri, bukan penerbitan, -penggunaan, atau kandungan dalaman pengimbas nasihat yang sedia ada. Mengaktifkan kedua-dua -nama semakan dalam peraturan cabang ialah perubahan pentadbiran yang berasingan; penambahan -tugas ini tidak dengan sendirinya melindungi cabang. +kepada senarai `needs` bagi kerja keputusan supaya laluan baharu atau yang dialih keluar tidak boleh lenyap secara senyap. +Resit meliputi aliran kerja masing-masing, bukan penerbitan, penggunaan, atau aspek dalaman +pengimbas nasihat sedia ada. Mengaktifkan kedua-dua nama semakan dalam peraturan cabang ialah +perubahan pentadbiran yang berasingan; penambahan kerja ini tidak dengan sendirinya melindungi cabang. ### Inventori imbasan statik -Inventori npm-alias berversi dan keahlian imbasan statik disimpan dalam +Inventori alias npm berversi dan keahlian imbasan statik berada dalam `config/quality/gate-manifest.json`. Jalankan `npm run check:gate-manifest` untuk mengesahkan -nama skrip dan arahan tepat dengan `package.json`; penambahan, pengalihan keluar dan -perubahan arahan akan menggagalkan kedua-dua hook setempat dan tugas pengelasan perubahan dalam CI. -Alias bukan tugas aliran kerja, tika matriks atau kes ujian: kiraan ini tidak boleh +nama skrip dan arahan tepat terhadap `package.json`; penambahan, pengalihan keluar dan +perubahan arahan menyebabkan kegagalan pada cangkuk setempat serta kerja pengelasan perubahan dalam CI. +Alias bukan kerja aliran kerja, tika matriks atau kes ujian: kiraan ini tidak boleh dikemukakan sebagai boleh saling ditukar ganti. Gunakan `npm run quality:scan -- --list` atau `npm run quality:scan:fast -- --list` -untuk memeriksa alias yang dipilih tanpa melaksanakannya. Pelaksana memanggil titik masuk -npm, maka masa jalan alias tersebut (termasuk Bun apabila dikonfigurasikan) dikekalkan. +untuk memeriksa alias yang dipilih tanpa melaksanakannya. Pelaksana memanggil +titik masuk npm, maka masa jalanannya (termasuk Bun apabila dikonfigurasikan) dikekalkan. Manifes merekodkan alias di luar profil tersebut sebagai dipanggil secara berasingan, dan arahan penyelenggaraan dilarang dalam profil imbasan baca sahaja. Profil ini meliputi imbasan statik sahaja. Profil ini tidak memperakui ujian produk, liputan, pembungkusan, semakan luaran atau penerimaan keluaran penuh calon. -Penerimaan aliran kerja menggunakan `config/quality/admission-policy.json` yang dipautkan dan -`scripts/quality/admission-verdict.mjs`. Profil release-observer kekal berasingan; -periksa semakan dan resit yang berkenaan secara berasingan. Inventori -prosa di bawah ialah rujukan, bukan bukti bahawa get benar-benar dilaksanakan. +Kemasukan aliran kerja menggunakan `config/quality/admission-policy.json` yang dipautkan dan +`scripts/quality/admission-verdict.mjs`. Profil pemerhati keluaran kekal berasingan; +periksa semakan dan resit yang berkenaan secara bebas. Inventori prosa +di bawah ialah rujukan, bukan bukti bahawa get benar-benar telah dijalankan. Skrip berada di bawah `scripts/check/` (get dasar) dan `scripts/quality/` (enjin ratchet). Sumber kebenaran CI ialah `.github/workflows/ci.yml`. ### Laluan pantas PR keluaran (`quality.yml`) -`.github/workflows/quality.yml` melengkapi CI pada PR main/release, push cabang -terlindung, dispatch dan merge group. PR menggunakan semakan pantas yang ditapis mengikut laluan. -Binaan pendua yang dilumpuhkan secara kekal telah dialih keluar; semakan build/package/boot -sebenar kekal dalam CI. - -| Tugas | Skop | Menyekat | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `Docs Gates (fast-path)` | PR dokumentasi/kod; rujukan dokumentasi API dan docs-all | Ya | -| `Fast Quality Gates` | PR kod; semakan statik, typecheck, typecheck papan pemuka, ujian unit terjejas | Ya | -| `Forgotten sibling tests` | PR kod; modul diubah dijejaki kepada pengguna statik dan ujian setara calon; laluan barrel dan dynamic-import dilaporkan sebagai diagnostik nasihat, dengan pengecualian senarai izin yang dirujuk | **Nasihat** | -| `Vitest (fast-path)` | PR kod; suit vitest pantas | Ya | -| `Unit Tests fast-path` | PR kod; suit unit 4 serpihan | Ya | -| `No new ESLint warnings` | PR kod; pengadang lint yang mengambil kira penyekatan | Ya, termasuk fork | -| `Merge integrity (changelog + generated skills)` | PR bukan draf; penyegerakan log perubahan dan kemahiran yang dijana | Ya, termasuk fork | +`.github/workflows/quality.yml` melengkapi CI pada PR main/release, tolakan cabang +terlindung, pelaksanaan dan kumpulan cantuman. PR menggunakan semakan pantas yang ditapis mengikut laluan. Binaan pendua yang +dilumpuhkan secara kekal telah dialih keluar; semakan binaan/pakej/boot sebenar kekal dalam CI. + +| Kerja | Skop | Menyekat | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `Docs Gates (fast-path)` | PR dokumentasi/kod; rujukan dokumentasi API dan docs-all | Ya | +| `Fast Quality Gates` | PR kod; semakan statik, semakan jenis, semakan jenis papan pemuka, ujian unit terjejas | Ya | +| `Forgotten sibling tests` | PR kod; modul yang diubah dijejaki kepada pengguna statik dan ujian setara calon; laluan barrel dan import dinamik dilaporkan sebagai diagnostik nasihat, dengan pengecualian senarai dibenarkan yang dirujuk | **Nasihat** | +| `Vitest (fast-path)` | PR kod; suit vitest pantas | Ya | +| `Unit Tests fast-path` | PR kod; suit unit 4 syard | Ya | +| `No new ESLint warnings` | PR kod; pengadang lint yang mengambil kira penindasan | Ya, termasuk fork | +| `Merge integrity (changelog + generated skills)` | PR bukan draf; penyegerakan log perubahan dan kemahiran terjana | Ya, termasuk fork | #### Laporan ujian setara yang terlupa `npm run check:forgotten-sibling-tests` menggunakan semula penyelesai import di sebalik peta impak ujian. Bagi setiap modul pengeluaran yang diubah, ia melaporkan rantaian -`changed module/symbol -> static consumer -> candidate sibling test` yang berketentuan apabila ujian -calon tiada daripada diff pull-request. Ringkasan Markdown dan hasil JSON disimpan sebagai -artifak aliran kerja `forgotten-sibling-tests` untuk penentukuran sebelum sebarang pelaksanaan yang menyekat. +`modul/simbol yang diubah -> pengguna statik -> ujian setara calon` secara berketentuan apabila ujian +calon tiada dalam perbezaan permintaan tarik. Ringkasan Markdown dan hasil JSON disimpan sebagai +artifak aliran kerja `forgotten-sibling-tests` untuk penentukuran sebelum sebarang pelaksanaan secara meluas yang menyekat. Eksport semula barrel dan import dinamik hanyalah diagnostik resolusi; ia tidak pernah menghasilkan -penemuan yang menyekat. Pengecualian yang telah disemak berada dalam -`config/quality/forgotten-sibling-allowlist.json`. Setiap entri mesti menamakan pengguna dan ujian calon, -memberikan rasional khusus, serta memautkan isu atau pull request GitHub. Entri yang tidak sah akan gagal -secara tertutup. Pengecualian tidak boleh menyekat ujian calon yang telah dipadamkan atau diff yang menambahkan `.skip`/`.todo`; +dapatan yang menyekat. Pengecualian yang telah disemak disimpan dalam +`config/quality/forgotten-sibling-allowlist.json`. Setiap entri mesti menyatakan pengguna dan ujian +calon, memberikan rasional khusus, serta memautkan isu atau permintaan tarik GitHub. Entri yang tidak +terbentuk dengan betul akan gagal secara tertutup. Pengecualian tidak boleh menyekat ujian calon yang dipadam atau perbezaan yang menambahkan `.skip`/`.todo`; pelemahan penegasan dan penyamaran lain kekal di bawah tanggungjawab pagar `check:test-masking` yang menyekat secara bebas. @@ -101,130 +99,128 @@ pelemahan penegasan dan penyamaran lain kekal di bawah tanggungjawab pagar Dijalankan pada setiap PR ke `main`. Menyekat penggabungan jika gagal. -| Skrip (`npm run ...`) | Mengesahkan | Menyekat | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:node-runtime` | Versi Node.js berada dalam julat yang disokong | Ya | -| `check:cycles` | Import berkitar — semua modul `src/` + `open-sse/` | Ya | -| `check:route-validation:t06` | Skema Zod tersedia pada semua laluan (dasar Peringkat 6) | Ya | -| `check:any-budget:t11` | Bilangan `@ts-expect-error // any` tidak melebihi belanjawan (catraca Peringkat 11) | Ya | -| `check:provider-consistency` | Setiap penyedia dalam `providers.ts` mempunyai entri yang sepadan dalam `providerRegistry.ts` (dan sebaliknya, dalam senarai dibenarkan) | Ya | -| `check:model-lifecycle` | Tiga jadual penghalaan yang diselenggara secara manual kekal konsisten dengan petikan kitar hayat yang dimasukkan ke dalam repositori (#11503): `FITNESS_TABLE` (`taskFitness.ts`) tidak memberikan skor kepada mana-mana id yang telah ditamatkan tetapi masih boleh dihalakan oleh `REGISTRY`; setiap sasaran `BUILT_IN_ALIASES` terdapat dalam `REGISTRY` dan tiada dalam petikan id yang telah ditamatkan; setiap id yang telah ditamatkan tetapi masih berada dalam `REGISTRY` dimajukan atau disenaraikan dalam `allowedRetiredInCatalog`; dan tiada sumber atau sasaran `DEFAULT_DEGRADATION_MAP` ditandai sebagai telah ditamatkan dalam petikan tersebut. Ini tidak membuktikan bahawa sesuatu model sedang disediakan oleh perkhidmatan huluan yang aktif. Luar talian — dibandingkan dengan `config/quality/model-lifecycle.json`, yang disegarkan secara manual menggunakan `npm run quality:refresh-model-lifecycle` (rangkaian; tidak disepadukan ke dalam CI). `allowedRetiredInCatalog` ialah mekanisme sehala untuk mengurangkan item secara beransur-ansur: tambahkan entri hanya jika terdapat isu penjejakan. | Ya | -| `check:fetch-targets` | Setiap `fetch("/api/...")` dalam `src/` sebelah klien merujuk kepada `route.ts` yang sebenar | Ya | -| `check:deps` | Semua kebergantungan yang boleh dipasang dengan `npm install` merentas setiap `package.json` dalam repositori terdapat dalam `dependency-allowlist.json`; pakej baharu yang tidak dikunci versinya atau disyaki slopsquatting akan ditandai | Ya | -| `audit:deps` | `npm audit` (akar + electron) — tiada nasihat keselamatan bertahap tinggi/kritikal (bertindih dengan `check:vuln-ratchet` osv; lihat Tunggakan Rasionalisasi) | Ya | -| `check:lockfile` | Integriti `package-lock.json` — pendaftaran https, cincangan integriti, tiada penggantian hos | Ya | -| `check:licenses` | Senarai dibenarkan lesen SPDX untuk kebergantungan produksi | Ya | -| `check:tracked-artifacts` | Tiada artifak binaan / pautan simbolik `node_modules` yang dikomit (turut dijalankan dalam pra-komit husky; pra-tolak sengaja diringankan — #6716) | Ya | -| `check:ai-attribution` | Tiada treler `Co-Authored-By` AI/bot atau pengaki penjanaan AI dalam komit, tajuk atau kandungan PR — Peraturan Tegas #16 (dalam gelung gerbang pantas `quality.yml` untuk PR→`release/**` — membaca muatan peristiwa, tiada operasi di luar PR — dan langkah PR sahaja dalam lint `ci.yml` untuk PR→`main`; juga cangkuk `commit-msg` husky; pengarang bersama manusia dibenarkan; #14436) | -| `check:vitest-exclusions` | Setiap pengecualian Vitest menyatakan isu penjejakan dan muncul dalam `config/quality/vitest-exclusions.json` (#13204) | Ya | -| `check:file-size` | Tiada fail sumber melebihi had bagi setiap sambungan (roda sehala: fail besar dibekukan dalam senarai `frozen`) | Ya | -| `check:error-helper` | Respons ralat dalam pelaksana/pengendali menggunakan `buildErrorBody()` / `sanitizeErrorMessage()` (Peraturan Tegas #12) | Ya | -| `check:migration-numbering` | Fail SQL migrasi dinomborkan secara berurutan, tanpa jurang atau pendua | Ya | -| `check:public-creds` | Tiada `client_id`/`client_secret` OAuth literal atau kunci Web Firebase di luar `publicCreds.ts` (Peraturan Tegas #11) | Ya | -| `check:db-rules` | Tiada SQL mentah di luar modul `src/lib/db/`; tiada import barrel daripada `localDb.ts` (Peraturan Tegas #2/#5) | Ya | -| `check:known-symbols` | Pelaksana penyedia, strategi penghalaan dan penterjemah yang didaftarkan dalam jadual penghantaran masing-masing sepadan dengan fail pada cakera — tiada simbol yatim atau tidak diisytiharkan | Ya | -| `check:route-guard-membership` | Setiap laluan yang menghasilkan proses anak diklasifikasikan oleh `isLocalOnlyPath()` (Peraturan Tegas #15/#17) | Ya | -| `check:test-discovery` | Setiap fail `*.test.ts` / `*.spec.ts` dalam repositori dikumpulkan oleh sekurang-kurangnya satu pelaksana ujian (ratchet: senarai yatim dalam `test-discovery-baseline.json` hanya boleh mengecil) | Ya | -| `check:agent-skills-sync` | Artifak agent-skills yang dijana sepadan dengan katalog sumbernya (tiada penyimpangan) | -| `check:provider-asset-provenance` | Logo/aset penyedia mempunyai entri asal-usul yang direkodkan | -| `lint:json` | Fail konfigurasi JSON dapat dihuraikan dan mematuhi peraturan lint repositori | -| `typecheck:core` | Kompilasi TypeScript tanpa ralat (amaran nasihat sahaja) | Ya | -| `typecheck:noimplicit:core` | `noImplicitAny` yang ketat — untuk kegunaan masa hadapan; banyak tapak panggilan sedia ada masih memerlukan anotasi | **Nasihat** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` yang dikhususkan kepada `src/app/(dashboard)/**` (#7033) — senarai dibenarkan terpilih 27 fail bagi `typecheck:core` tidak menyertakan sebarang TSX papan pemuka, dan `next build` juga tidak pernah menyemak jenisnya (`next.config.mjs` menetapkan `ignoreBuildErrors: true`), maka regresi pengecam yatim di situ (#6625/#6909) tidak dapat dikesan oleh CI. Perbezaan dibandingkan dengan garis dasar kiraan beku bagi setiap fail/setiap kod TS (`config/quality/dashboard-typecheck-baseline.json`, corak penguatkuasaan lapuk yang sama seperti `check:known-symbols`) — hanya ralat BAHARU yang melebihi kiraan garis dasar akan menggagalkan gerbang; kurangkan secara berperingkat menggunakan `--update` apabila ralat sedia ada dibetulkan. | Ya | +| Skrip (`npm run ...`) | Mengesahkan | Menyekat | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Versi Node.js berada dalam julat yang disokong | Ya | +| `check:cycles` | Import kitaran merentas seluruh `src/` + `open-sse/` (berasaskan AST, `paths` tsconfig diselesaikan). Mod biasa = nasihat, menyenaraikan kitaran. `check:cycles:ratchet` (yang dijalankan oleh CI) menyekat apabila bilangannya melebihi had `metrics.cycles` dalam `quality-baseline.json` — pada masa ini 14, `direction: down`, jadi bilangannya hanya boleh menurun (#15159 G-01/G-02) | Ya (ratchet) | +| `check:route-validation:t06` | Skema Zod tersedia pada semua laluan (dasar Tahap 6) | Ya | +| `check:any-budget:t11` | Bilangan `@ts-expect-error // any` tidak melebihi bajet (catraca Tahap 11) | Ya | +| `check:provider-consistency` | Setiap penyedia dalam `providers.ts` mempunyai entri yang sepadan dalam `providerRegistry.ts` (dan sebaliknya, dalam senarai dibenarkan) | Ya | +| `check:model-lifecycle` | Tiga jadual penghalaan yang diselenggara secara manual kekal konsisten dengan petikan kitar hayat yang disimpan dalam repositori (#11503): `FITNESS_TABLE` (`taskFitness.ts`) tidak memberikan skor kepada mana-mana id yang telah ditamatkan tetapi masih boleh dihalakan oleh `REGISTRY`; setiap sasaran `BUILT_IN_ALIASES` terdapat dalam `REGISTRY` dan tiada dalam petikan id yang telah ditamatkan; setiap id yang telah ditamatkan tetapi masih berada dalam `REGISTRY` dimajukan atau disenaraikan dalam `allowedRetiredInCatalog`; dan tiada sumber atau sasaran `DEFAULT_DEGRADATION_MAP` ditandai sebagai telah ditamatkan dalam petikan tersebut. Ini tidak membuktikan bahawa sesuatu model sedang disediakan oleh huluan langsung. Luar talian — membandingkan dengan `config/quality/model-lifecycle.json`, yang disegarkan secara manual menggunakan `npm run quality:refresh-model-lifecycle` (rangkaian; tidak disepadukan ke dalam CI). `allowedRetiredInCatalog` ialah mekanisme sehala pengurangan berperingkat: tambahkan entri hanya bersama isu penjejakan. | Ya | +| `check:fetch-targets` | Setiap `fetch("/api/...")` dalam `src/` sisi klien dipadankan kepada `route.ts` sebenar | Ya | +| `check:deps` | Semua kebergantungan yang boleh dipasang dengan `npm install` merentas setiap `package.json` dalam repositori terdapat dalam `dependency-allowlist.json`; pakej baharu yang tidak dipasak versinya atau disyaki slopsquatting ditandai | Ya | +| `audit:deps` | `npm audit` (akar + electron) — tiada nasihat keselamatan bertahap tinggi/kritikal (bertindih dengan osv `check:vuln-ratchet`; lihat Tunggakan Rasionalisasi) | Ya | +| `check:lockfile` | Integriti `package-lock.json` — pendaftaran https, cincangan integriti, tiada penggantian hos | Ya | +| `check:licenses` | Senarai dibenarkan lesen SPDX untuk kebergantungan produksi | Ya | +| `check:tracked-artifacts` | Tiada artifak binaan / pautan simbolik `node_modules` yang dikomit (turut dijalankan dalam pra-komit husky; pra-tolak sengaja diringankan — #6716) | Ya | +| `check:ai-attribution` | Tiada treler `Co-Authored-By` AI/bot atau pengaki penjanaan AI dalam komit, tajuk atau kandungan PR — Peraturan Tegas #16 (dalam gelung gerbang pantas `quality.yml` untuk PR→`release/**` — membaca muatan peristiwa, tanpa operasi di luar PR — dan langkah khusus PR dalam semakan lint `ci.yml` untuk PR→`main`; turut merangkumi cangkuk `commit-msg` husky; pengarang bersama manusia dibenarkan; #14436) | +| `check:vitest-exclusions` | Setiap pengecualian Vitest menyatakan isu penjejakan dan muncul dalam `config/quality/vitest-exclusions.json` (#13204) | Ya | +| `check:file-size` | Tiada fail sumber melebihi had bagi setiap sambungan (ratchet: fail besar yang dibekukan dalam senarai `frozen`) | Ya | +| `check:error-helper` | Respons ralat dalam pelaksana/pengendali menggunakan `buildErrorBody()` / `sanitizeErrorMessage()` (Peraturan Tegas #12) | Ya | +| `check:migration-numbering` | Fail SQL migrasi dinomborkan secara berurutan, tanpa jurang atau pendua | Ya | +| `check:public-creds` | Tiada `client_id`/`client_secret` OAuth literal atau kunci Web Firebase di luar `publicCreds.ts` (Peraturan Tegas #11) | Ya | +| `check:db-rules` | Tiada SQL mentah di luar modul `src/lib/db/`; tiada import barrel daripada `localDb.ts` (Peraturan Tegas #2/#5) | Ya | +| `check:known-symbols` | Pelaksana penyedia, strategi penghalaan dan penterjemah yang didaftarkan dalam jadual penghantaran mereka sepadan dengan fail pada cakera — tiada simbol yatim atau tidak diisytiharkan | Ya | +| `check:route-guard-membership` | Setiap laluan yang mewujudkan proses anak diklasifikasikan oleh `isLocalOnlyPath()` (Peraturan Tegas #15/#17) | Ya | +| `check:test-discovery` | Setiap fail `*.test.ts` / `*.spec.ts` dalam repositori dikumpulkan oleh sekurang-kurangnya satu pelaksana ujian (ratchet: senarai yatim dalam `test-discovery-baseline.json` hanya boleh mengecil) | Ya | +| `check:agent-skills-sync` | Artifak agent-skills yang dijana sepadan dengan katalog sumbernya (tiada penyimpangan) | +| `check:provider-asset-provenance` | Logo/aset penyedia mempunyai entri asal-usul yang direkodkan | +| `lint:json` | Fail konfigurasi JSON boleh dihuraikan dan mematuhi peraturan lint repo | +| `typecheck:core` | Pengkompilan TypeScript tanpa ralat (amaran nasihat sahaja) | Ya | +| `typecheck:noimplicit:core` | `noImplicitAny` yang ketat — berpandangan ke hadapan; banyak tapak panggilan sedia ada masih memerlukan anotasi | **Nasihat** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` yang diskopkan kepada `src/app/(dashboard)/**` (#7033) — senarai benarkan 27 fail terpilih milik `typecheck:core` tidak menyertakan sebarang TSX papan pemuka, dan `next build` juga tidak pernah melakukan semakan jenis padanya (`next.config.mjs` menetapkan `ignoreBuildErrors: true`), maka regresi pengecam yatim di situ (#6625/#6909) tidak dapat dikesan oleh CI. Perbezaan dibandingkan dengan garis dasar kiraan beku mengikut fail/mengikut kod TS (`config/quality/dashboard-typecheck-baseline.json`, corak penguatkuasaan lapuk yang sama seperti `check:known-symbols`) — hanya ralat BAHARU yang melebihi kiraan garis dasar akan menggagalkan semakan; kurangkan secara berperingkat dengan `--update` apabila ralat sedia ada dibetulkan. | Ya | ### Tugas: `quality-gate` Dijalankan selepas `test-coverage`. Menyekat penggabungan jika gagal. -| Skrip | Mengesahkan | Menyekat | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | Menghasilkan `quality-metrics.json` (bilangan amaran ESLint, liputan daripada laporan serpihan yang digabungkan) | Ya (huluan bagi ratchet) | -| `quality:ratchet` | Setiap metrik dalam `quality-baseline.json` tidak merosot (amaran ESLint ≤ garis dasar; liputan ≥ garis dasar) | Ya | -| `check:duplication` | Penduaan kod (jscpd@4) tidak melebihi garis dasar dalam `quality-baseline.json` | Ya | -| `check:complexity` | Kerumitan siklomatik pada peringkat fail tidak melebihi had (ESLint teras `complexity` + `max-lines-per-function`) | Ya | -| `check:cognitive-complexity` | Ratchet kerumitan kognitif (`eslint-plugin-sonarjs`) — laluan ESLint berasingan; CI menjalankan kedua-duanya secara tergabung sebagai langkah tunggal `check:complexity-ratchets` | Ya | -| `check:dead-code` | Ratchet eksport / fail yang tidak digunakan (knip) tidak merosot berbanding garis dasar | Ya | -| `check:compression-budget` | Bajet penanda aras pemampatan — had minimum penjimatan token bagi setiap enjin tidak boleh merosot | Ya | -| `check:type-coverage` | Ratchet peratusan yang ditaip (`type-coverage`) tidak merosot; sebahagian besarnya merangkumi `typecheck:noimplicit:core` | Ya | -| `check:codeql-ratchet` | Bilangan amaran CodeQL terbuka tidak merosot (dibaca melalui `gh api`; dilangkau secara wajar tanpa token) — kekerapan muat semula dan pencetus manual: lihat "Ratchet CodeQL" di bawah | Ya | +| Skrip | Mengesahkan | Menyekat | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `quality:collect` | Menghasilkan `quality-metrics.json` (bilangan amaran ESLint, liputan daripada laporan serpihan yang digabungkan) | Ya (sebelum ratchet) | +| `quality:ratchet` | Setiap metrik dalam `quality-baseline.json` tidak merosot (amaran ESLint ≤ garis dasar; liputan ≥ garis dasar) | Ya | +| `check:duplication` | Penduaan kod (jscpd@4) tidak melebihi garis dasar dalam `quality-baseline.json` | Ya | +| `check:complexity` | Kerumitan siklomatik pada peringkat fail tidak melebihi had (ESLint teras `complexity` + `max-lines-per-function`) | Ya | +| `check:cognitive-complexity` | Ratchet kerumitan kognitif (`eslint-plugin-sonarjs`) — laluan ESLint berasingan; CI menjalankan kedua-duanya secara tergabung sebagai satu langkah `check:complexity-ratchets` | Ya | +| `check:dead-code` | Ratchet eksport / fail yang tidak digunakan (knip) tidak merosot berbanding garis dasar | Ya | +| `check:compression-budget` | Belanjawan penanda aras pemampatan — had minimum penjimatan token bagi setiap enjin tidak boleh merosot | Ya | +| `check:type-coverage` | Ratchet peratusan ditaip (`type-coverage`) tidak merosot; sebahagian besarnya merangkumi `typecheck:noimplicit:core` | Ya | +| `check:codeql-ratchet` | Bilangan amaran CodeQL terbuka tidak merosot (dibaca melalui `gh api`; dilangkau dengan selamat tanpa token) — kekerapan muat semula dan pencetus manual: lihat "Ratchet CodeQL" di bawah | Ya | ### Tugas: `quality-extended` -Keseluruhan tugas bersifat nasihat (`continue-on-error: true`). Ratchet berasaskan npm dijalankan -sepenuhnya; pengimbas luaran dipasang melalui `gh release download` dan melangkau sendiri (keluar 0) -apabila binari masih tiada. +Keseluruhan tugas bersifat nasihat (`continue-on-error: true`). Ratchet berasaskan npm benar-benar dijalankan; pengimbas luaran dipasang melalui `gh release download` dan melangkau sendiri (exit 0) apabila binari masih tiada. -| Skrip | Mengesahkan | Menyekat | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -| `check:circular-deps` | Tiada kebergantungan kitaran (dpdm) | **Nasihat** | -| `check:bundle-size` | Saiz berkas tidak melebihi had | **Nasihat** | -| `check:secrets` | Pengimbasan rahsia (gitleaks) — dilangkau jika binari tiada | **Nasihat** | -| `check:vuln-ratchet` | Kerentanan kebergantungan (osv-scanner) tidak merosot — dilangkau jika binari tiada | **Nasihat** | -| `check:workflows` | Lint aliran kerja (actionlint + zizmor); pengimbas yang tiada/rosak, laporan tidak sah atau garis dasar ratchet yang tiada gagal sebagai INCOMPLETE. Penemuan yang sah mengikut dasar tegas/nasihat/ratchet yang dipilih | Pelaksanaan diperlukan; ratchet zizmor menyekat dalam CI | -| `check:openapi-breaking` | Perubahan pemecah kepada kontrak API awam (`openapi.yaml`) berbanding cabang asas (oasdiff) — menghasilkan `openapiBreaking=N`; dilangkau jika oasdiff tiada atau spesifikasi asas tidak dapat diselesaikan | **Nasihat** | +| Skrip | Mengesahkan | Menyekat | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | +| `check:circular-deps` | Tiada kebergantungan kitaran (dpdm) | **Nasihat** | +| `check:bundle-size` | Saiz berkas tidak melebihi had | **Nasihat** | +| `check:secrets` | Pengimbasan rahsia (gitleaks) — dilangkau jika binari tiada | **Nasihat** | +| `check:vuln-ratchet` | Kerentanan kebergantungan (osv-scanner) tidak merosot — dilangkau jika binari tiada | **Nasihat** | +| `check:workflows` | Pelintan aliran kerja (actionlint + zizmor); pengimbas yang tiada/rosak, laporan tidak sah atau garis dasar ratchet yang tiada akan gagal sebagai INCOMPLETE. Dapatan sah mengikut dasar ketat/nasihat/ratchet yang dipilih | Pelaksanaan diperlukan; ratchet zizmor menyekat dalam CI | +| `check:openapi-breaking` | Perubahan yang memecahkan kontrak API awam (`openapi.yaml`) berbanding cabang asas (oasdiff) — menghasilkan `openapiBreaking=N`; dilangkau jika oasdiff tiada atau spesifikasi asas tidak dapat diselesaikan | **Nasihat** | ### Tugas: `docs-sync-strict` Dijalankan pada setiap PR ke `main`. Menyekat penggabungan jika gagal. -| Skrip | Mengesahkan | Menyekat | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| `check:docs-all` | Gerbang meta yang menjalankan 6 subgerbang di bawah secara berurutan | Ya | -| ↳ `check:docs-sync` | Konsistensi versi CHANGELOG / OpenAPI / llm.txt | Ya | -| ↳ `check:docs-counts` | Bilangan dalam prosa (bilangan penyedia, bilangan migrasi, dll.) berada dalam tetingkap ratchet bagi bilangan sebenar | Ya | -| ↳ `check:env-doc-sync` | Setiap pemboleh ubah persekitaran dalam `.env.example` didokumentasikan dalam jadual dokumentasi, dan begitu juga sebaliknya | Ya | -| ↳ `check:deprecated-versions` | Tiada rentetan versi yang telah ditamatkan dalam dokumentasi | Ya | -| ↳ `check:doc-links` | Pautan markdown dalaman dalam dokumentasi merujuk kepada fail sebenar (bentuk `[teks]`/`(laluan)`) | Ya | -| ↳ `check:fabricated-docs` | Laluan, pemboleh ubah persekitaran, perintah CLI, nama cangkuk dan laluan fail yang dirujuk dalam dokumentasi wujud dalam pangkalan kod. Gerbang keras melalui `--strict`; kegagalan lembut tanpa bendera. | Ya (melalui `--strict` dalam CI) | -| `check:cli-i18n` | Rentetan perintah CLI terdapat dalam semua fail pelokal i18n | Ya | -| `check:openapi-coverage` | Spesifikasi OpenAPI merangkumi sekurang-kurangnya had minimum laluan sebenar yang ditetapkan secara ratchet | Ya | -| `check:openapi-security-tiers` | Anotasi aras keselamatan dalam `openapi.yaml` selaras dengan pengelasan `routeGuard.ts` | **Nasihat** | -| `check:openapi-routes` | Setiap laluan dalam `openapi.yaml` merujuk kepada `route.ts` sebenar (antihalusinasi) | Ya | -| `check:docs-symbols` | Setiap rujukan `/api/...` dalam `docs/**/*.md` merujuk kepada `route.ts` sebenar (antihalusinasi) | Ya | -| `i18n translation drift` | Kekunci yang tidak diterjemahkan dalam fail pelokal i18n — amaran sahaja | **Nasihat** | +| Skrip | Mengesahkan | Menyekat | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| `check:docs-all` | Gerbang meta yang menjalankan 6 subgerbang di bawah secara berurutan | Ya | +| ↳ `check:docs-sync` | Ketekalan versi CHANGELOG / OpenAPI / llm.txt | Ya | +| ↳ `check:docs-counts` | Bilangan dalam prosa (bilangan penyedia, bilangan migrasi, dan sebagainya) berada dalam tetingkap ratchet bagi bilangan sebenar | Ya | +| ↳ `check:env-doc-sync` | Setiap pemboleh ubah persekitaran dalam `.env.example` didokumentasikan dalam jadual dokumentasi, dan sebaliknya | Ya | +| ↳ `check:deprecated-versions` | Tiada rentetan versi lapuk dalam dokumentasi | Ya | +| ↳ `check:doc-links` | Pautan markdown dalaman dalam dokumentasi menghala kepada fail sebenar (bentuk `[text]`/`(path)`) | Ya | +| ↳ `check:fabricated-docs` | Laluan, pemboleh ubah persekitaran, perintah CLI, nama hook dan laluan fail yang dirujuk dalam dokumentasi wujud dalam pangkalan kod. Gerbang keras melalui `--strict`; kegagalan lembut tanpa bendera. | Ya (melalui `--strict` dalam CI) | +| `check:cli-i18n` | Rentetan perintah CLI terdapat dalam semua fail lokal i18n | Ya | +| `check:openapi-coverage` | Spesifikasi OpenAPI merangkumi sekurang-kurangnya ambang minimum yang diratchet bagi laluan sebenar | Ya | +| `check:openapi-security-tiers` | Anotasi peringkat keselamatan dalam `openapi.yaml` selaras dengan pengelasan `routeGuard.ts` | **Nasihat** | +| `check:openapi-routes` | Setiap laluan dalam `openapi.yaml` menghala kepada `route.ts` sebenar (antihalusinasi) | Ya | +| `check:docs-symbols` | Setiap rujukan `/api/...` dalam `docs/**/*.md` menghala kepada `route.ts` sebenar (antihalusinasi) | Ya | +| `i18n translation drift` | Kekunci yang tidak diterjemahkan dalam fail lokal i18n — amaran sahaja | **Nasihat** | ### Tugas: `i18n-ui-coverage` -| Skrip | Mengesahkan | Menyekat | -| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check-ui-keys-coverage` (sebaris) | Liputan kekunci i18n UI ialah ≥ 65% | Ya | -| `check-ui-value-drift` (sebaris) | **Nilai** bahasa Inggeris yang ditulis semula tidak meninggalkan terjemahan lapuk | Ya | -| `check-new-key-coverage` (sebaris) | Kekunci bahasa Inggeris yang **baharu** diterjemahkan dalam setiap pelokal — penanda `__MISSING__:` ditolak | Ya | -| `check-translation-ratio` | Nisbah terjemahan sebenar bagi setiap pelokal (nilai yang sama dengan bahasa Inggeris / ruang letak / entri hilang di luar senarai dibenarkan) tidak boleh melebihi `config/quality/i18n-translation-baseline.json` + kelonggaran | **Nasihat** | +| Skrip | Mengesahkan | Menyekat | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check-ui-keys-coverage` (sebaris) | Liputan kekunci i18n UI ialah ≥ 65% | Ya | +| `check-ui-value-drift` (sebaris) | **Nilai** bahasa Inggeris yang ditulis semula tidak meninggalkan sebarang terjemahan lapuk | Ya | +| `check-new-key-coverage` (sebaris) | Kekunci bahasa Inggeris yang **baharu** diterjemahkan dalam setiap lokal — penanda `__MISSING__:` ditolak | Ya | +| `check-translation-ratio` | Nisbah terjemahan sebenar bagi setiap lokal (sama dengan bahasa Inggeris / ruang letak / entri hilang di luar senarai yang dibenarkan) tidak boleh melebihi `config/quality/i18n-translation-baseline.json` + kelonggaran | **Nasihat** | -Memerlukan `fetch-depth: 0` — gerbang hanyutan nilai membandingkan perbezaan `en.json` dengan asas cantuman. +Memerlukan `fetch-depth: 0` — gerbang sisihan nilai membandingkan perbezaan `en.json` dengan asas cantuman. #### `check-ui-value-drift` — gerbang terjemahan lapuk -Mengesan satu regresi i18n yang tidak dapat dikesan secara struktur oleh gerbang lain: nilai bahasa Inggeris -ditulis semula dan terjemahan yang diterbitkan daripada bahasa Inggeris _sebelumnya_ kekal, menyebabkan -pengguna bukan berbahasa Inggeris terus membaca teks yang ditulis dengan meyakinkan tetapi kini salah. +Mengesan satu regresi i18n yang secara struktur tidak dapat dilihat oleh gerbang lain: nilai bahasa Inggeris +ditulis semula dan terjemahan yang berasal daripada bahasa Inggeris _sebelumnya_ kekal, menyebabkan +pengguna bukan berbahasa Inggeris terus membaca teks yang kedengaran meyakinkan tetapi kini salah. -Perkara ini benar-benar telah dilancarkan. `oauthModal.googleOAuthWarning` ditulis semula apabila pembantu log masuk -Antigravity diperkenalkan (#5203); **39 daripada 43 pelokal** mengekalkan teks yang menyuruh pengendali untuk "menyalin -URL penuh dan menampalnya di bawah" — aliran yang tidak dapat diselesaikan bagi penyedia tersebut. Perkara ini +Ini benar-benar pernah dikeluarkan. `oauthModal.googleOAuthWarning` telah ditulis semula apabila pembantu log masuk +Antigravity diperkenalkan (#5203); **39 daripada 43 lokal** mengekalkan teks yang mengarahkan pengendali untuk "menyalin +URL penuh dan menampalnya di bawah" — aliran yang tidak dapat dilengkapkan untuk penyedia tersebut. Hal ini tidak disedari sehingga #8463 kerana: -- `sync-ui-keys` hanya mengisi semula kekunci yang **tiada**, bukan kekunci yang **lapuk**; -- `check-ui-keys-coverage` mengira _kewujudan_ kekunci, jadi terjemahan lapuk dikira sebagai diliputi; +- `sync-ui-keys` hanya mengisi kekunci yang **tiada**, bukan kekunci yang **lapuk**; +- `check-ui-keys-coverage` mengira _kewujudan_ kekunci, maka terjemahan lapuk dikira sebagai diliputi; - `check-translation-drift` menjejaki cerminan dokumentasi `docs/i18n//**.md` — ia tidak pernah membaca `src/i18n/messages/*.json`. Menyekat dalam tugas `docs-sync-strict` sejak - penyegerakan semula 2026-09: sunting dokumen teras → `npm run i18n:run -- --files=` (pada peringkat bahagian, ringan). + penyegerakan semula 2026-09: sunting dokumen teras → `npm run i18n:run -- --files=` (peringkat bahagian, ringan). -**Peka terhadap perbezaan, bukan disokong garis dasar.** Ia membandingkan `en.json` pada asas cantuman dengan -pepohon kerja; bagi setiap kekunci yang nilai bahasa Inggerisnya berubah, mana-mana locale yang masih mempunyai +**Peka perbezaan, bukan disokong garis dasar.** Ia membandingkan `en.json` pada pangkalan cantuman dengan +pepohon kerja; bagi setiap kekunci yang nilai bahasa Inggerisnya berubah, mana-mana penempatan yang masih mempunyai terjemahan yang tidak disentuh dianggap lapuk. Ini sengaja **membekukan hutang sedia ada** — perbezaan -tidak dapat mendedahkan teks bahasa Inggeris lama yang menjadi sumber sesuatu terjemahan lama, maka get hanya menilai -perkara yang disentuh oleh perubahan semasa. Alternatifnya (garis dasar hash bagi setiap kekunci) memerlukan -fail terjana ~600 KB, 3× lebih besar daripada garis dasar sedia ada yang terbesar, serta berubah pada setiap PR i18n. +tidak dapat menunjukkan versi bahasa Inggeris lama yang menjadi sumber sesuatu terjemahan lama, maka pagar hanya menilai +perkara yang disentuh oleh perubahan semasa. Alternatifnya (garis dasar cincangan bagi setiap kekunci) memerlukan +fail dijana berukuran ~600 KB, 3× lebih besar daripada garis dasar sedia ada yang terbesar, dan berubah pada setiap PR i18n. Dua cara untuk mematuhinya: 1. kemas kini terjemahan yang terjejas, atau -2. tetapkannya kepada `__MISSING__:` — masa jalan kemudiannya menyajikan teks bahasa Inggeris yang telah dibetulkan - (`src/i18n/request.ts::deepMergeFallback`, #7258) dan kekunci tersebut dimasukkan ke dalam baris gilir terjemahan. +2. tetapkannya kepada `__MISSING__:` — masa jalan kemudiannya menyajikan teks bahasa Inggeris yang dibetulkan + (`src/i18n/request.ts::deepMergeFallback`, #7258) dan kekunci tersebut dibariskan untuk diterjemahkan. -Jika **makna** rentetan berubah, utamakan **penamaan semula kekunci**: kekunci baharu tidak boleh mewarisi +Jika **makna** rentetan berubah, utamakan **menamakan semula kekunci**: kekunci baharu tidak boleh mewarisi terjemahan yang lapuk. Itulah corak yang digunakan oleh #8463. ```bash @@ -233,26 +229,26 @@ npm run i18n:check-value-drift:warn # laporan sahaja BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Keluar dengan kod 0 bersama `SKIP reason=base-unresolved` apabila katalog asas tidak dapat dibaca (klon cetek +Keluar dengan kod 0 berserta `SKIP reason=base-unresolved` apabila katalog asas tidak dapat dibaca (klon cetek tanpa rujukan asas), selaras dengan `check-openapi-breaking`. ### Tugas: `i18n` -Matriks pengesahan i18n penuh (satu tugas bagi setiap locale). Keseluruhan tugas bersifat nasihat. +Matriks pengesahan i18n penuh (satu tugas bagi setiap penempatan). Keseluruhan tugas bersifat nasihat. -| Skrip | Mengesahkan | Menyekat | -| ------------------------------- | ----------------------------------------- | ---------------------------------------------------------- | -| `validate_translation.py quick` | Kelengkapan terjemahan bagi setiap locale | **Nasihat** (`continue-on-error: true` pada seluruh tugas) | +| Skrip | Mengesahkan | Menyekat | +| ------------------------------- | --------------------------------------------- | -------------------------------------------------------------- | +| `validate_translation.py quick` | Kelengkapan terjemahan bagi setiap penempatan | **Nasihat** (`continue-on-error: true` pada keseluruhan tugas) | ### Tugas: `pr-test-policy` -Dijalankan pada pull request sahaja. +Dijalankan pada permintaan tarik sahaja. -| Skrip | Mengesahkan | Menyekat | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | PR yang mengubah kod pengeluaran dalam `src/`, `open-sse/`, `electron/`, atau `bin/` mesti menyertakan atau mengemas kini ujian (Peraturan Tegas #8) | Ya | -| `check:test-masking` | Fail ujian yang diubah tidak mengurangkan jumlah bersih penegasan atau menambah tautologi `assert.ok(true)` | Ya | -| `check:pr-evidence` | Kandungan PR memetik bukti ujian/VPS untuk perubahan tersebut (mengautomasi Peraturan Tegas #18 dengan menggrep prosa PR — rapuh, lihat Backlog) | Ya | +| Skrip | Mengesahkan | Menyekat | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | PR yang mengubah kod pengeluaran dalam `src/`, `open-sse/`, `electron/`, atau `bin/` mesti menyertakan atau mengemas kini ujian (Peraturan Tegas #8) | Ya | +| `check:test-masking` | Fail ujian yang diubah tidak mengurangkan bilangan bersih penegasan atau menambah tautologi `assert.ok(true)` | Ya | +| `check:pr-evidence` | Isi PR menyatakan bukti ujian/VPS bagi perubahan tersebut (mengautomatikkan Peraturan Tegas #18 dengan menggrep prosa PR — rapuh, lihat Senarai Tertunggak) | Ya | ### Tugas: `test-vitest` @@ -263,18 +259,18 @@ Dijalankan selepas `build`. Menyekat cantuman jika gagal. | `test:vitest` | Pelayan MCP (110 alat), autoCombo, cache — pelaksana vitest | Ya | | `test:vitest:ui` | Ujian komponen UI — pelaksana vitest | **Menyekat** — kegagalan sedia ada dikecualikan secara jelas dalam `vitest.config.ts`; kegagalan baharu menggagalkan tugas | -### Aliran kerja setiap malam (dijadualkan, bersifat nasihat) +### Aliran kerja setiap malam (berjadual, bersifat nasihat) -Ini dijalankan mengikut jadual cron (dan `workflow_dispatch`), tidak sekali-kali pada PR. Semuanya bersifat nasihat. +Ini dijalankan mengikut jadual cron (dan `workflow_dispatch`), tidak pernah pada PR. Semuanya bersifat nasihat. -| Aliran kerja | Mengesahkan | Menyekat | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `nightly-property` | Ujian sifat fast-check dengan benih rawak + bilangan larian yang tinggi | **Nasihat** | -| `nightly-resilience` | Get pertumbuhan heap, suntikan kerosakan huru-hara, beban/rendaman k6 | **Nasihat** | -| `nightly-llm-security` | Pengadang suntikan promptfoo (mod sekat) + prob garak (dilangkau tanpa rahsia penyedia) | **Nasihat** | -| `nightly-schemathesis` | Pengkaburan kontrak OpenAPI (schemathesis) terhadap OmniRoute langsung menggunakan `docs/openapi.yaml` — mendedahkan pelanggaran spesifikasi / ralat 500 yang tidak dikendalikan (Fasa 8 B.4) | **Nasihat** | -| `nightly-mutation` | Skor ujian mutasi Stryker pada laluan unit pantas — mutan yang terselamat mendedahkan penegasan yang lemah | **Nasihat** | -| `nightly-compat` | Matriks keserasian enjin Node merentas julat `engines.node` yang disokong | **Nasihat** | +| Aliran kerja | Mengesahkan | Menyekat | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `nightly-property` | ujian sifat fast-check dengan benih rawak + bilangan larian yang tinggi | **Nasihat** | +| `nightly-resilience` | pagar pertumbuhan timbunan, suntikan kerosakan huru-hara, beban/rendaman k6 | **Nasihat** | +| `nightly-llm-security` | pengadang suntikan promptfoo (mod sekat) + prob garak (dilangkau tanpa rahsia penyedia) | **Nasihat** | +| `nightly-schemathesis` | fuzzing kontrak OpenAPI (schemathesis) terhadap OmniRoute langsung menggunakan `docs/openapi.yaml` — menampilkan pelanggaran spesifikasi / ralat 500 yang tidak dikendalikan (Fasa 8 B.4) | **Nasihat** | +| `nightly-mutation` | skor ujian mutasi Stryker pada laluan unit pantas — mutan yang bertahan menampilkan penegasan yang lemah | **Nasihat** | +| `nightly-compat` | matriks keserasian enjin Node merentas julat `engines.node` yang disokong | **Nasihat** | --- @@ -540,41 +536,41 @@ ralat sambungan dan tidak pernah merosakkan sesi. ## Tunggakan Rasionalisasi (semakan ROI — Fasa 9 Gelombang 3) -Inventori ini telah diselaraskan dengan `ci.yml` pada 2026-06-17 (versi terdahulu tidak menyertakan +Inventori ini telah diselaraskan dengan `ci.yml` pada 2026-06-17 (versi sebelumnya tidak menyertakan `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Semakan ROI bagi set yang telah diselaraskan +`check:codeql-ratchet`, `check:pr-evidence`). Semakan ROI terhadap set yang telah diselaraskan mengenal pasti calon rasionalisasi berikut. **Penggabungan ialah perubahan CI yang -mekanikal; penukaran/pengguguran ialah keputusan dasar yang dikhaskan untuk pengendali.** Tiada apa-apa di bawah -yang telah dilaksanakan lagi. +mekanikal; penukaran/pengguguran ialah keputusan dasar yang dikhaskan untuk pengendali.** Tiada perkara di bawah +yang telah diterapkan lagi. -**Turut tidak didokumentasikan di atas** (bersifat nasihat, isyarat rendah): tugas `docs-lint` -(markdownlint + Vale, keseluruhan tugas menggunakan `continue-on-error`) dan aliran kerja pengimbas kendiri +**Turut tidak didokumenkan di atas** (nasihat sahaja, isyarat rendah): tugas `docs-lint` +(markdownlint + Vale, seluruh tugas menggunakan `continue-on-error`) dan aliran kerja pengimbas kendiri `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` terdapat dalam -`quality-baseline.json` tetapi tidak disambungkan kepada mekanisme sehala yang menyekat dalam `ci.yml` — metrik ini +`quality-baseline.json` tetapi tidak disambungkan kepada mekanisme ratchet yang menyekat dalam `ci.yml` — metrik ini kini terbiar. ### Gabung / nyahpendua (mekanikal, risiko lebih rendah) -Setiap calon telah disahkan berdasarkan keadaan gerbang semasa pada 2026-06-17 (percaya tetapi sahkan); +Setiap calon telah disahkan terhadap keadaan gate sebenar pada 2026-06-17 (percaya tetapi sahkan); beberapa penggabungan yang kelihatan "jelas" sebenarnya menyembunyikan hutang dan **bukan** penggantian terus yang bersih. -- **`check:docs-sync` dijalankan dua kali** — secara kendiri dalam tugas `lint` dan sekali lagi dalam `check:docs-all` (`docs-sync-strict`) serta cangkuk pra-komit husky. ✅ **SELESAI** — pemanggilan kendiri dalam `lint` telah dialih keluar. -- **Pengimbasan CVE** — ❌ **BUKAN penggabungan yang bersih.** `audit:deps` gagal keras bagi mana-mana CVE tinggi/kritikal; `check:vuln-ratchet` (osv) hanya gagal apabila berlaku _regresi_ berbanding garis dasar (kini 1 MODERATE). Semantiknya berbeza — menggugurkan `audit:deps` akan menghilangkan gerbang mutlak bagi tahap tinggi/kritikal. Kekalkan kedua-duanya. -- **Pengesanan kitaran** — ❌ **BUKAN penggabungan yang bersih.** `check:circular-deps` (dpdm) melaporkan **91 kitaran** (itulah sebabnya ia bersifat nasihat); ia tidak boleh dinaik taraf menjadi menyekat tanpa menyelesaikan kitaran tersebut terlebih dahulu, dan skopnya lebih luas daripada `check:cycles` terpilih yang berstatus hijau. Kekalkan `check:cycles` sebagai menyekat; penyelesaian 91 kitaran dpdm merupakan tunggakan yang berasingan. -- **Kerumitan** — ✅ **SELESAI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): satu lintasan ESLint, pengiraan mengikut ruleId supaya garis dasar siklomatik+max-lines dan kognitif kekal bebas; `check:complexity` / `check:cognitive-complexity` individu dikekalkan untuk `--update` setempat. -- **Antihalusinasi `/api`** — ✅ **SELESAI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): satu inventori FS bagi `src/app/api`, openapi-routes + docs-symbols masih melaporkan secara berasingan; semakan individu dikekalkan untuk pelaksanaan setempat. -- **`check:node-runtime` dijalankan dalam 11 tugas** — ⚠️ **ROI rendah.** Setiap satunya menggunakan pelaksana berasingan dan semakan mengambil masa <1s; jumlah penjimatan ~10s, dengan risiko kehilangan kawalan murah bagi setiap tugas. Tidak berbaloi dengan perubahan tersebut. +- **`check:docs-sync` dijalankan dua kali** — secara kendiri dalam tugas `lint` dan sekali lagi dalam `check:docs-all` (`docs-sync-strict`) serta hook pra-komit husky. ✅ **SELESAI** — panggilan kendiri dalam `lint` telah dialih keluar. +- **Pengimbasan CVE** — ❌ **BUKAN penggabungan yang bersih.** `audit:deps` gagal keras bagi sebarang CVE tinggi/kritikal; `check:vuln-ratchet` (osv) hanya gagal apabila terdapat _regresi_ berbanding garis dasar (kini 1 MODERATE). Semantiknya berbeza — menggugurkan `audit:deps` akan menghilangkan gate mutlak bagi tahap tinggi/kritikal. Kekalkan kedua-duanya. +- **Pengesanan kitaran** — ✅ **SELESAI** (#15159 G-01/G-02). Teks lama di sini menyifatkan `check:cycles` sebagai gate "hijau, disusun rapi" dan mewajarkan pengekalan status menyekatnya kerana `check:circular-deps` (dpdm) melaporkan 91 kitaran. Status hijau itu ialah **hijau palsu**: `check:cycles` mengimbas 5 subdirektori (450 fail), hanya memadankan `import|export … from` statik dan menggugurkan setiap penspesifikasi `@/` dan `@omniroute/open-sse/`, maka ia tidak dapat melihat kitaran import dinamik + alias yang mendominasi repo. Telah dibetulkan: gate kini menelusuri `src` + `open-sse` (5023 fail), mengumpulkan penspesifikasi daripada AST TypeScript (maka `import("…")` dikira manakala `typeof import("…")` pada kedudukan jenis tidak dikira), dan menyelesaikan `paths` tsconfig. Ia menemukan **14** kitaran, bukannya 0. Oleh sebab 14 kitaran sedia ada tidak dapat dibetulkan dalam PR gate, `check:cycles` kini merupakan **ratchet** (`--ratchet`, had maksimum `metrics.cycles.value = 14` dalam `quality-baseline.json`, `direction: down`) — ia menyekat sebarang _regresi_ dan bilangannya hanya boleh menurun. CI menjalankan `npm run check:cycles:ratchet`. Pengurangan beransur-ansur dilaksanakan bersama **A-01**. `check:circular-deps` (dpdm) kekal sebagai nasihat dan pandangan kedua yang lebih luas. +- **Kerumitan** — ✅ **SELESAI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): satu penelusuran ESLint, dikira mengikut ruleId supaya garis dasar siklomatik+max-lines dan kognitif kekal berasingan; `check:complexity` / `check:cognitive-complexity` individu dikekalkan untuk `--update` setempat. +- **Anti-halusinasi `/api`** — ✅ **SELESAI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): satu inventori FS bagi `src/app/api`, openapi-routes + docs-symbols masih melaporkan secara berasingan; pemeriksaan individu dikekalkan untuk pelaksanaan setempat. +- **`check:node-runtime` dijalankan dalam 11 tugas** — ⚠️ **ROI rendah.** Setiap satu menggunakan runner berasingan dan pemeriksaan mengambil masa <1s; jumlah penjimatan ~10s, tetapi pengadang murah bagi setiap tugas akan hilang. Tidak berbaloi dengan gangguan yang diperlukan. - **`typecheck:noimplicit:core` dalam lint CI** — ✅ **dialih keluar daripada tugas lint** (sebelum ini bersifat nasihat dengan `continue-on-error`); permukaan jenis yang menyekat ialah `typecheck:core` + `check:type-coverage`. Skrip setempat dikekalkan. ### Tukar / putuskan (dasar pengendali) -- `check:openapi-security-tiers` (bersifat nasihat) — ❌ **TIDAK boleh ditukar dengan bersih.** Ia keluar dengan kod 0 tetapi memberi amaran bahawa beberapa laluan `traffic-inspector` di bawah `LOCAL_ONLY_API_PREFIXES` tidak mempunyai anotasi `x-loopback-only: true`. Penguatkuasaannya memerlukan anotasi tersebut ditambahkan terlebih dahulu pada `openapi.yaml`. -- `typecheck:noimplicit:core` (bersifat nasihat) — sebahagian besarnya telah dirangkumi oleh mekanisme sehala `check:type-coverage` yang menyekat. Tukar kepada mekanisme sehala atau gugurkan lintasan `tsc` kedua yang berlebihan. -- `test:vitest:ui` (kini **menyekat**) — kegagalan sedia ada dikecualikan secara jelas dalam `vitest.config.ts` dengan komen penjejakan `// #8618`; kegagalan baharu akan menggagalkan tugas. -- `check:secrets` (gitleaks, mekanisme sehala menyekat yang dibekukan pada 3 positif palsu yang didokumentasikan) — tambahkan 3 kes tersebut pada senarai izin untuk mencapai 0, atau turunkan taraf kepada bersifat nasihat. Bertindih dengan pengimbasan rahsia natif GitHub + `check:public-creds`. -- `check:pr-evidence` (menyekat, mencari teks kandungan PR menggunakan grep) — risiko positif palsu yang tinggi; menggugurkannya akan melemahkan penguatkuasaan Peraturan Tegas #18, jadi ini sememangnya keputusan dasar. -- `semgrep` (kendiri, bersifat nasihat) — bertindih dengan CodeQL untuk keluarga OWASP; sambungkan garis dasarnya kepada mekanisme sehala atau gugurkannya. +- `check:openapi-security-tiers` (nasihat) — ❌ **TIDAK boleh ditukar dengan bersih.** Ia keluar dengan kod 0 tetapi memberi amaran bahawa beberapa laluan `traffic-inspector` di bawah `LOCAL_ONLY_API_PREFIXES` tidak mempunyai anotasi `x-loopback-only: true`. Penguatkuasaannya memerlukan anotasi tersebut ditambahkan pada `openapi.yaml` terlebih dahulu. +- `typecheck:noimplicit:core` (nasihat) — sebahagian besarnya telah dirangkumi oleh ratchet `check:type-coverage` yang menyekat. Tukar kepada ratchet atau gugurkan laluan `tsc` kedua yang berlebihan. +- `test:vitest:ui` (kini **menyekat**) — kegagalan sedia ada dikecualikan secara eksplisit dalam `vitest.config.ts` dengan komen penjejakan `// #8618`; kegagalan baharu menyebabkan tugas gagal. +- `check:secrets` (gitleaks, ratchet menyekat yang dibekukan pada 3 positif palsu yang didokumenkan) — masukkan ketiga-tiganya ke dalam senarai benarkan untuk mencapai 0, atau turunkan kepada status nasihat. Bertindih dengan pengimbasan rahsia asli GitHub + `check:public-creds`. +- `check:pr-evidence` (menyekat, menggunakan grep pada prosa badan PR) — risiko positif palsu yang tinggi; menggugurkannya akan melemahkan penguatkuasaan Hard Rule #18, jadi ini benar-benar keputusan dasar. +- `semgrep` (kendiri, bersifat nasihat) — bertindih dengan CodeQL untuk keluarga OWASP; sambungkan garis dasarnya kepada ratchet atau gugurkan. --- diff --git a/docs/i18n/ms/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ms/docs/compression/COMPRESSION_GUIDE.md index 71ac7fcb836e..9cfc27709a8f 100644 --- a/docs/i18n/ms/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ms/docs/compression/COMPRESSION_GUIDE.md @@ -451,8 +451,7 @@ disuntikkan dan bukannya bahasa Inggeris. Pada badan dengan `messages`, pintasan kandungan (`shouldBypassCavemanOutputMode()` dalam `open-sse/services/compression/outputMode.ts`) menyemak tiga mesej terakhir dan melangkau gaya untuk keseluruhan giliran apabila ia sepadan dengan keselamatan, tindakan tidak boleh diterbalikkan, -penjelasan, atau kata kunci sensitif urutan. Pintasan berjalan tidak kira apa yang ditetapkan oleh togol **Pintasan Kejelasan Auto** papan pemuka -(`cavemanOutputMode.autoClarity`). +penjelasan, atau kata kunci sensitif urutan. Pintasan berjalan selagi togol **Pintasan Kejelasan Auto** papan pemuka (`cavemanOutputMode.autoClarity`) dihidupkan, iaitu tetapan lalai; apabila togol dimatikan, gaya yang dipilih turut digunakan pada giliran tersebut. Apabila pintasan membenarkan giliran, `placeSystemInstruction()` (fail yang sama), yang tidak pernah mencipta `messages[0]` baharu, meletakkan blok dalam yang pertama daripada ini yang ditemuinya: diff --git a/docs/i18n/ms/docs/reference/ENVIRONMENT.md b/docs/i18n/ms/docs/reference/ENVIRONMENT.md index d254e53bc5aa..cde21130c90e 100644 --- a/docs/i18n/ms/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ms/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Rahsia yang Diperlukan -Ini **mesti** ditetapkan sebelum pelaksanaan pertama. Tanpanya, aplikasi sama ada akan enggan dimulakan atau beroperasi dengan tetapan lalai yang tidak selamat. - -| Pemboleh Ubah | Diperlukan | Lalai | Fail Sumber | Penerangan | -| ---------------------------- | -------------------- | ------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ya** | _(tiada)_ | `src/lib/auth` | Menandatangani/mengesahkan semua kuki sesi papan pemuka (JWT). Jana dengan `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ya** | _(tiada)_ | `src/lib/db/apiKeys.ts` | Kunci penyulitan AES untuk nilai kunci API yang disimpan dalam SQLite. Jana dengan `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ya** | `CHANGEME` | Skrip bootstrap | Menetapkan kata laluan awal papan pemuka pentadbir (sepadan dengan nilai lalai `.env.example` — sengaja dibiarkan jelas tidak selamat untuk mewajibkan perubahan). **Ubah sebelum penggunaan pertama.** Selepas log masuk, ubah melalui Papan Pemuka → Tetapan → Keselamatan. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ya** (pengeluaran) | _(tidak ditetapkan)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Rahsia dikongsi untuk jambatan WebSocket Codex Responses dalaman. Mengesahkan permintaan jambatan antara geganti WS Electron/pelayar dengan OmniRoute. ⚠️ **DIPERLUKAN dalam pengeluaran — apabila tidak ditetapkan, semua permintaan jambatan WS akan ditolak.** Jana dengan `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Tidak | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID pemecahan cache pekerja perkhidmatan secara eksplisit untuk cangkerang PWA (#11779); yang pertama dalam rantaian penyelesaian. | -| `SOURCE_VERSION` | Tidak | _(tidak ditetapkan)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Yang kedua dalam rantaian — ditetapkan oleh pembina PaaS (cth. gaya Heroku) sebagai komit yang digunakan. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Tidak | _(diterbitkan)_ | `src/shared/components/PwaRegister.tsx` | Nilai awam masa binaan yang digunakan oleh klien untuk mendaftarkan `/sw.js?v=…`; diterbitkan daripada dua nilai di atas, kemudian SHA git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Tidak (automatik) | _(automatik setiap but)_ | `src/server/authz/policies/management.ts` | Rahsia bagi setiap proses yang membuktikan cop IP rakan dipercayai berasal daripada pelayan HTTP OmniRoute sendiri (`scripts/dev/peer-stamp.mjs`). Perisian tengah authz hanya mempercayai kelokalan permintaan (pengehadan gelung balik/LAN bagi laluan LOCAL_ONLY) apabila cop tersebut mengandungi token ini. Dijana secara automatik pada setiap but — biarkan tidak ditetapkan; hanya tetapkan secara kekal untuk persediaan berbilang proses yang mesti berkongsi cop tersebut. | -| `OMNIROUTE_TRUSTED_PROXIES` | Tidak | _(tidak ditetapkan)_ | `scripts/dev/peer-stamp.mjs` | IP atau julat CIDR proksi songsang yang dipisahkan koma, yang `X-Forwarded-For` / `X-Real-IP`-nya boleh dipercayai oleh senarai benarkan/tolak IP, sebagai tambahan kepada alamat gelung balik, alamat rangkaian persendirian dan pinggir Cloudflare yang sentiasa dipercayai. Hanya diperlukan untuk proksi pada mana-mana alamat awam lain; jika tidak, alamat proksi itu sendiri akan dinilai oleh penapis. | +Rahsia yang tidak ditetapkan tetapi ditandakan **Ya** dijana secara automatik pada pelaksanaan pertama (`JWT_SECRET`, `API_KEY_SECRET`) dan disimpan ke `server.env` dalam direktori data — tetapkan secara eksplisit apabila direktori itu bersifat sementara. Pengecualiannya ialah `OMNIROUTE_WS_BRIDGE_SECRET`: ia tidak disimpan ke `server.env` — titik masuk pelayan mencipta nilai sementara bagi setiap but yang hanya boleh digunakan oleh jambatan dalam proses, maka klien jambatan luaran perlu menetapkannya secara eksplisit. + +| Pemboleh Ubah | Diperlukan | Lalai | Fail Sumber | Penerangan | +| ---------------------------- | -------------------- | ------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ya** | _(tiada)_ | `src/lib/auth` | Menandatangani/mengesahkan semua kuki sesi papan pemuka (JWT). Jana dengan `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ya** | _(tiada)_ | `src/lib/db/apiKeys.ts` | Kunci penyulitan AES untuk nilai kunci API yang disimpan dalam SQLite. Jana dengan `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Tidak | _(tidak ditetapkan)_ | `src/lib/auth/managementPassword.ts` | Menetapkan kata laluan papan pemuka semasa permulaan selagi tiada kata laluan disimpan; diabaikan setelah kata laluan disimpan. Pemasangan npm dan sumber menyalin `.env.example` ke `.env`, jadi ia bermula dengan pemegang tempatnya yang diketahui umum `CHANGEME`: gantikannya sebelum but pertama, atau tetapkan `INITIAL_PASSWORD=` (kosong) dalam `.env` tersebut untuk mencipta kata laluan dalam bestari persediaan pada lawatan pertama, seperti yang dilakukan oleh nilai yang tidak ditetapkan (lalai imej Docker). Ubah kata laluan yang disimpan dalam Papan Pemuka → Tetapan → Keselamatan, atau jalankan `omniroute reset-password` (`node bin/reset-password.mjs` dalam salinan sumber) dengan `DATA_DIR` ditetapkan ke direktori data pelayan. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ya** (pengeluaran) | _(tidak ditetapkan)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Rahsia dikongsi untuk jambatan WebSocket Codex Responses dalaman. Mengesahkan permintaan jambatan antara geganti WS Electron/pelayar dengan OmniRoute. ⚠️ **DIPERLUKAN dalam pengeluaran — apabila tidak ditetapkan, semua permintaan jambatan WS akan ditolak.** Jana dengan `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Tidak | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID pemecahan cache pekerja perkhidmatan secara eksplisit untuk cangkerang PWA (#11779); yang pertama dalam rantaian penyelesaian. | +| `SOURCE_VERSION` | Tidak | _(tidak ditetapkan)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Yang kedua dalam rantaian — ditetapkan oleh pembina PaaS (cth. gaya Heroku) sebagai komit yang digunakan. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Tidak | _(diterbitkan)_ | `src/shared/components/PwaRegister.tsx` | Nilai awam masa binaan yang digunakan oleh klien untuk mendaftarkan `/sw.js?v=…`; diterbitkan daripada dua nilai di atas, kemudian SHA git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Tidak (automatik) | _(automatik setiap but)_ | `src/server/authz/policies/management.ts` | Rahsia bagi setiap proses yang membuktikan cop IP rakan dipercayai berasal daripada pelayan HTTP OmniRoute sendiri (`scripts/dev/peer-stamp.mjs`). Perisian tengah authz hanya mempercayai kelokalan permintaan (pengehadan gelung balik/LAN bagi laluan LOCAL_ONLY) apabila cop tersebut mengandungi token ini. Dijana secara automatik pada setiap but — biarkan tidak ditetapkan; hanya tetapkan secara kekal untuk persediaan berbilang proses yang mesti berkongsi cop tersebut. | +| `OMNIROUTE_TRUSTED_PROXIES` | Tidak | _(tidak ditetapkan)_ | `scripts/dev/peer-stamp.mjs` | IP atau julat CIDR proksi songsang yang dipisahkan koma, yang `X-Forwarded-For` / `X-Real-IP`-nya boleh dipercayai oleh senarai benarkan/tolak IP, sebagai tambahan kepada alamat gelung balik, alamat rangkaian persendirian dan pinggir Cloudflare yang sentiasa dipercayai. Hanya diperlukan untuk proksi pada mana-mana alamat awam lain; jika tidak, alamat proksi itu sendiri akan dinilai oleh penapis. | ### Perintah Penjanaan @@ -216,38 +216,38 @@ OmniRoute menggunakan **SQLite** (melalui `better-sqlite3`) untuk semua pengekal ## 4. Keselamatan & Pengesahan -| Pemboleh Ubah | Lalai | Fail Sumber | Penerangan | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt yang digabungkan dengan pengecam perkakasan untuk penjanaan cap jari mesin. Ubah bagi setiap pelaksanaan untuk pengasingan. | -| `OMNIROUTE_CLI_SALT` | _(tidak ditetapkan = salt rawak bagi setiap pemasangan yang disimpan secara berterusan di `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC untuk menerbitkan token pengesahan CLI setempat. Menetapkan nilai ini akan menggilirkan semua token CLI pada mesin dan sentiasa mengatasi salt yang disimpan secara berterusan. Lihat `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Menetapkan bendera `Secure` pada kuki sesi. **Mestilah `true`** apabila dijalankan di belakang HTTPS. | -| `REQUIRE_API_KEY` | `false` | Perisian tengah API | Apabila `true`, semua permintaan proksi `/v1/*` mesti menyertakan kunci API yang sah. **Bendera ini tidak mengawal `GET /v1/models`**, yang sebaliknya mengikut keadaan log masuk papan pemuka (`requireAuthForModels`) — oleh itu, respons `401` daripada `/v1/models` TIDAK bermakna inferens dilindungi. Lihat `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Membenarkan nilai penuh kunci API didedahkan dalam UI Papan Pemuka. Boleh dikonfigurasikan melalui Bendera Ciri Papan Pemuka; merupakan risiko keselamatan pada tika yang dikongsi. | -| `NO_LOG_API_KEY_IDS` | _(kosong)_ | `src/lib/compliance/index.ts` | ID kunci API yang dipisahkan dengan koma dan memintas pengelogan permintaan (pematuhan GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(tidak ditetapkan = tanpa had)_ | `src/shared/utils/apiKeyPolicy.ts` | Belanjawan permintaan harian sandaran yang digunakan pada kunci API apabila lajur `rate_limits` bernilai null. Tidak ditetapkan atau kosong: tiada had tersirat (#2289, #11017). `0` adalah sama (tanpa had). Integer positif N mendayakan N/hari, 5N/minggu, 20N/bulan. Nilai tidak kosong yang tidak sah akan kembali kepada tetingkap legasi 1000/hari, 5000/minggu, 20000/bulan. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Saiz maksimum badan permintaan yang dibenarkan. Menolak muatan yang melebihi had ini. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Badan permintaan sebenar yang berada pada atau melebihi ambang ini mengambil pajakan kemasukan kelas berat setempat proses secara atomik sebelum penghuraian JSON (laluan BYTE, termasuk `POST /v1/responses`). Menggunakan pelepasan ruang lebihan sihat [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) yang sama seperti permintaan berstruktur berat; masih dihadkan oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) supaya [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) tidak berulang. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Had keras laluan sembang yang dikuatkuasakan terhadap bait yang dibaca semasa pengingesan terbatas, termasuk permintaan dengan `Content-Length` yang tiada, tidak sah atau tidak jujur; lebihan menerima `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(tidak ditetapkan — tiada had bilangan permintaan)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** had BILANGAN permintaan legasi ini kini hanya berkuat kuasa apabila ditetapkan secara eksplisit. Jika dibiarkan tidak ditetapkan (lalai), penerimaan sembang berwajaran berat sebaliknya dikawal oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — belanjawan BAIT terbitan automatik yang ditentukan berdasarkan had memori sebenar proses dalam **satu proses** (satu timbunan V8). Dua `/v1/responses` ~750k token yang bertindih menggugurkan timbunan ~12 GiB (#7849) — amaran **belanjawan memori**, bukan maksimum keras produk sebanyak 2. Proses yang sihat (timbunan di bawah nisbah pengurangan beban) BOLEH menerima lebih banyak `/v1/responses` panjang serentak melalui `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Puluhan klien SSE panjang (40–50) bergantung pada timbunan + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, bukannya “maksimum 2”. Menaikkan nilai ini secara membuta tuli untuk “menggunakan hos” akan menyebabkan #7849 berulang. Gandakan **timbunan** dengan **N `DATA_DIR` bebas** (#11024); jangan sekali-kali gunakan `replicas>1` pada satu fail SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(diterbitkan secara automatik)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nilai gantian untuk belanjawan bait pengingesan terbitan automatik (25% daripada had memori V8/cgroup yang lebih ketat dibahagikan dengan penguatan sementara 8x). Nilai terbitan dan eksplisit dihadkan kepada 8 MiB–2 GiB. Badan yang lebih besar daripada belanjawan berkesan gagal serta-merta dengan `413 body_exceeds_budget`; pertikaian antara badan yang masing-masing boleh dilayan kekal boleh dicuba semula dengan `503`. 40–50 klien SSE panjang serentak bergantung pada belanjawan ini + timbunan, bukannya “maksimum 2” yang keras. Baca `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` di `/api/monitoring/health` sebelum membuat penalaan. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Nisbah pengurangan beban akibat tekanan timbunan (`heapUsed / heap_size_limit`) untuk penerimaan berwajaran berat BYTE dan STRUCTURE (#10183, #10268, #10437). Permintaan berwajaran berat serentak yang melepasi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` hanya digugurkan dengan `503` yang boleh dicuba semula apabila timbunan JUGA berada pada atau melebihi nisbah ini; pada timbunan yang sihat, permintaan diterima melalui ruang lebihan sihat. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (lalai `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Kapasiti tambahan terbatas untuk laluan pantas timbunan sihat (#10437) pada **kedua-dua** STRUCTURE dan BYTE (`admitChatRequest`, termasuk badan ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Tanpa batas ini, setiap permintaan sibuk tetapi dengan timbunan sihat memintas penerimaan tanpa had maksimum. Sebaik sahaja sebanyak ini pajakan serentak aktif melalui laluan timbunan sihat, permintaan sibuk seterusnya akan beralih kepada laluan tunggu terbatas/pengurangan beban yang SAMA seperti yang digunakan ketika tekanan timbunan sebenar. `0` menyahdayakan pintasan sepenuhnya. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Bilangan mesej yang mengelaskan permintaan sembang sebagai berwajaran berat walaupun badannya berada di bawah ambang bait. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Bilangan alat yang mengelaskan permintaan sembang sebagai berwajaran berat walaupun badannya berada di bawah ambang bait. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Anggaran token berdasarkan saiz rentetan secara konservatif yang mengelaskan permintaan sebagai berwajaran berat; ini ialah proksi kos penerimaan, bukan pentokenan pengebilan penyedia. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (dinyahdayakan) | `src/shared/middleware/chatBodyAdmission.ts` | Had sejarah sembang ikut serta pilihan. Dinyahdayakan secara lalai: bilangan mesej ialah dasar penggunaan, bukannya sifat universal sesuatu permintaan, dan pengehadan di sini menolak perbualan dengan `413` muktamad sebelum saluran pemampatan boleh menjadikannya boleh dilayan. Pertumbuhan timbunan dibatasi oleh `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` dan pengurangan beban akibat tekanan timbunan. Tetapkan nilai positif pada penggunaan yang mempunyai kekangan memori dan memerlukan had maksimum keras; lebihan kemudiannya menerima `413` berstruktur yang memerlukan pemadatan. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Had keras untuk respons huluan bukan penstriman yang ditimbal sepenuhnya ke dalam memori. Apabila melepasi had ini, pembaca huluan dibatalkan dan permintaan gagal dengan segera, dan bukannya terus membesarkan rentetan tanpa had sehingga timbunan kehabisan ruang. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Bilangan maksimum bait pada talian yang dimajukan daripada pengepala respons huluan. Apabila belanjawan dilampaui, pengepala berkeutamaan lebih rendah (cth., `x-codex-*`, `x-oai-request-id`) akan digugurkan untuk kekal dalam had pengepala proksi songsang yang lazim. Tetapkan nilai lebih tinggi untuk memajukan lebih banyak metadata huluan dengan kos saiz pengepala respons yang lebih besar. | -| `CORS_ORIGIN` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Senarai kebenaran CORS asal tunggal legasi. Utamakan `CORS_ALLOWED_ORIGINS` untuk penggunaan baharu. CORS hanya untuk klien API pelayar rentas asal; operasi tulis papan pemuka yang disahkan menggunakan permintaan asal sama serta perlindungan CSRF terikat sesi. | -| `CORS_ALLOWED_ORIGINS` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Senarai kebenaran CORS yang dipisahkan koma. Tiada kad bebas dihantar melainkan `CORS_ALLOW_ALL=true` dikonfigurasikan secara eksplisit. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Jalan keluar untuk pembangunan sahaja bagi menggemakan sebarang `Origin` pelayar. Jangan dayakan pada penggunaan dikongsi atau pengeluaran. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Sekat panggilan penyedia yang menyasarkan julat IP peribadi/gelung balik/pautan setempat. Nyahdayakan hanya dalam persekitaran ujian terpencil. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Benarkan URL penyedia yang menghala ke rangkaian peribadi/setempat (localhost, 192.168.x.x, 10.x.x.x, dsb.). **DIPERLUKAN untuk penyedia dihos sendiri** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Apabila `false`, papan pemuka menolak pengesahan URL setempat. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Benarkan penambahan/pengesahan penyedia pada alamat setempat/peribadi (127.0.0.1, localhost, LAN, julat peribadi) — terhad kepada laluan pengesahan penyedia. **Lalai `true`** (mengutamakan setempat); tetapkan `false` untuk menguatkuasakan penyekatan ketat kepada alamat awam sahaja. Titik akhir metadata awan (169.254.169.254, metadata.google.internal) kekal disekat dalam semua keadaan. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Benarkan laluan `/v1/audio/*` (transkripsi, pertuturan, terjemahan) menggunakan nod penyedia serasi OpenAI yang dihoskan di luar localhost. Dimatikan secara lalai — penghalaan audio ke hos jauh mengubah identiti trafik keluar dan mesti merupakan keputusan eksplisit pengendali. Nod gelung balik/peribadi (localhost, 127.0.0.1, 172.16-31.x) sentiasa dibenarkan dan tidak terjejas. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Benarkan `POST /v1/rerank` (serta langkah penarafan semula gelung balik enjin memori) menggunakan nod penyedia serasi OpenAI yang dihoskan di luar localhost — mesin LAN atau rakan Tailscale yang menjalankan TEI, Infinity, vLLM, dsb. Dimatikan secara lalai — penghalaan ke hos jauh mengubah identiti trafik keluar dan mesti merupakan keputusan eksplisit pengendali. Nod gelung balik (localhost, 127.0.0.1, 172.16-31.x) sentiasa dibenarkan dan tidak terjejas. Nod jauh juga mesti melepasi dasar URL keluar penyedia (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); hos metadata awan tidak pernah dihalakan. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Apabila OIDC didayakan, nyahdayakan log masuk kata laluan supaya pengguna hanya boleh mengesahkan identiti melalui Daftar Masuk Tunggal OIDC. Alias ringkas `OIDC_DISABLE_PASSWORD_LOGIN` turut diterima; Bendera Ciri Papan Pemuka dengan kekunci yang sama mempunyai keutamaan. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias ringkas bagi `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Pemboleh Ubah | Lalai | Fail Sumber | Penerangan | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt yang digabungkan dengan pengecam perkakasan untuk penjanaan cap jari mesin. Ubah bagi setiap pelaksanaan untuk pengasingan. | +| `OMNIROUTE_CLI_SALT` | _(tidak ditetapkan = salt rawak bagi setiap pemasangan yang disimpan secara berterusan di `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC untuk menerbitkan token pengesahan CLI setempat. Menetapkan nilai ini akan menggilirkan semua token CLI pada mesin dan sentiasa mengatasi salt yang disimpan secara berterusan. Lihat `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Menetapkan bendera `Secure` pada kuki sesi. **Mestilah `true`** apabila dijalankan di belakang HTTPS. | +| `REQUIRE_API_KEY` | `false` | Perisian tengah API | Apabila `true`, semua permintaan proksi `/v1/*` mesti menyertakan kunci API yang sah. **Bendera ini tidak mengawal `GET /v1/models`**, yang sebaliknya mengikut keadaan log masuk papan pemuka (`requireAuthForModels`) — oleh itu, respons `401` daripada `/v1/models` TIDAK bermakna inferens dilindungi. Lihat `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Membenarkan nilai penuh kunci API didedahkan dalam UI Papan Pemuka. Boleh dikonfigurasikan melalui Bendera Ciri Papan Pemuka; merupakan risiko keselamatan pada tika yang dikongsi. | +| `NO_LOG_API_KEY_IDS` | _(kosong)_ | `src/lib/compliance/index.ts` | ID kunci API yang dipisahkan dengan koma dan memintas pengelogan permintaan (pematuhan GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(tidak ditetapkan = tanpa had)_ | `src/shared/utils/apiKeyPolicy.ts` | Belanjawan permintaan harian sandaran yang digunakan pada kunci API apabila lajur `rate_limits` bernilai null. Tidak ditetapkan atau kosong: tiada had tersirat (#2289, #11017). `0` adalah sama (tanpa had). Integer positif N mendayakan N/hari, 5N/minggu, 20N/bulan. Nilai tidak kosong yang tidak sah akan kembali kepada tetingkap legasi 1000/hari, 5000/minggu, 20000/bulan. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Saiz maksimum badan permintaan yang dibenarkan. Menolak muatan yang melebihi had ini. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Badan permintaan sebenar yang berada pada atau melebihi ambang ini mengambil pajakan kemasukan kelas berat setempat proses secara atomik sebelum penghuraian JSON (laluan BYTE, termasuk `POST /v1/responses`). Menggunakan pelepasan ruang lebihan sihat [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) yang sama seperti permintaan berstruktur berat; masih dihadkan oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) supaya [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) tidak berulang. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Had keras laluan sembang yang dikuatkuasakan terhadap bait yang dibaca semasa pengingesan terbatas, termasuk permintaan dengan `Content-Length` yang tiada, tidak sah atau tidak jujur; lebihan menerima `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(tidak ditetapkan — tiada had bilangan permintaan)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** had BILANGAN permintaan legasi ini kini hanya berkuat kuasa apabila ditetapkan secara eksplisit. Jika dibiarkan tidak ditetapkan (lalai), penerimaan sembang berwajaran berat sebaliknya dikawal oleh `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — belanjawan BAIT terbitan automatik yang ditentukan berdasarkan had memori sebenar proses dalam **satu proses** (satu timbunan V8). Dua `/v1/responses` ~750k token yang bertindih menggugurkan timbunan ~12 GiB (#7849) — amaran **belanjawan memori**, bukan maksimum keras produk sebanyak 2. Proses yang sihat (timbunan di bawah nisbah pengurangan beban) BOLEH menerima lebih banyak `/v1/responses` panjang serentak melalui `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Puluhan klien SSE panjang (40–50) bergantung pada timbunan + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, bukannya “maksimum 2”. Menaikkan nilai ini secara membuta tuli untuk “menggunakan hos” akan menyebabkan #7849 berulang. Gandakan **timbunan** dengan **N `DATA_DIR` bebas** (#11024); jangan sekali-kali gunakan `replicas>1` pada satu fail SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(diterbitkan secara automatik)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nilai gantian untuk belanjawan bait pengingesan terbitan automatik (25% daripada had memori V8/cgroup yang lebih ketat dibahagikan dengan penguatan sementara 8x). Nilai terbitan dan eksplisit dihadkan kepada 8 MiB–2 GiB. Badan yang lebih besar daripada belanjawan berkesan gagal serta-merta dengan `413 body_exceeds_budget`; pertikaian antara badan yang masing-masing boleh dilayan kekal boleh dicuba semula dengan `503`. 40–50 klien SSE panjang serentak bergantung pada belanjawan ini + timbunan, bukannya “maksimum 2” yang keras. Baca `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` di `/api/monitoring/health` sebelum membuat penalaan. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Nisbah pengurangan beban akibat tekanan timbunan (`heapUsed / heap_size_limit`) untuk penerimaan berwajaran berat BYTE dan STRUCTURE (#10183, #10268, #10437). Permintaan berwajaran berat serentak yang melepasi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` hanya digugurkan dengan `503` yang boleh dicuba semula apabila timbunan JUGA berada pada atau melebihi nisbah ini; pada timbunan yang sihat, permintaan diterima melalui ruang lebihan sihat. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (lalai `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Kapasiti tambahan terbatas untuk laluan pantas timbunan sihat (#10437) pada **kedua-dua** STRUCTURE dan BYTE (`admitChatRequest`, termasuk badan ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Tanpa batas ini, setiap permintaan sibuk tetapi dengan timbunan sihat memintas penerimaan tanpa had maksimum. Sebaik sahaja sebanyak ini pajakan serentak aktif melalui laluan timbunan sihat, permintaan sibuk seterusnya akan beralih kepada laluan tunggu terbatas/pengurangan beban yang SAMA seperti yang digunakan ketika tekanan timbunan sebenar. `0` menyahdayakan pintasan sepenuhnya. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Bilangan mesej yang mengelaskan permintaan sembang sebagai berwajaran berat walaupun badannya berada di bawah ambang bait. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Bilangan alat yang mengelaskan permintaan sembang sebagai berwajaran berat walaupun badannya berada di bawah ambang bait. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Anggaran token berdasarkan saiz rentetan secara konservatif yang mengelaskan permintaan sebagai berwajaran berat; ini ialah proksi kos penerimaan, bukan pentokenan pengebilan penyedia. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (dinyahdayakan) | `src/shared/middleware/chatBodyAdmission.ts` | Had sejarah sembang ikut serta pilihan. Dinyahdayakan secara lalai: bilangan mesej ialah dasar penggunaan, bukannya sifat universal sesuatu permintaan, dan pengehadan di sini menolak perbualan dengan `413` muktamad sebelum saluran pemampatan boleh menjadikannya boleh dilayan. Pertumbuhan timbunan dibatasi oleh `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` dan pengurangan beban akibat tekanan timbunan. Tetapkan nilai positif pada penggunaan yang mempunyai kekangan memori dan memerlukan had maksimum keras; lebihan kemudiannya menerima `413` berstruktur yang memerlukan pemadatan. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Had keras untuk respons huluan bukan penstriman yang ditimbal sepenuhnya ke dalam memori. Apabila melepasi had ini, pembaca huluan dibatalkan dan permintaan gagal dengan segera, dan bukannya terus membesarkan rentetan tanpa had sehingga timbunan kehabisan ruang. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Bilangan maksimum bait pada talian yang dimajukan daripada pengepala respons huluan. Apabila belanjawan dilampaui, pengepala berkeutamaan lebih rendah (cth., `x-codex-*`, `x-oai-request-id`) akan digugurkan untuk kekal dalam had pengepala proksi songsang yang lazim. Tetapkan nilai lebih tinggi untuk memajukan lebih banyak metadata huluan dengan kos saiz pengepala respons yang lebih besar. | +| `CORS_ORIGIN` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Senarai kebenaran CORS asal tunggal legasi. Utamakan `CORS_ALLOWED_ORIGINS` untuk penggunaan baharu. CORS hanya untuk klien API pelayar rentas asal; operasi tulis papan pemuka yang disahkan menggunakan permintaan asal sama serta perlindungan CSRF terikat sesi. | +| `CORS_ALLOWED_ORIGINS` | _(tidak ditetapkan)_ | `src/server/cors/origins.ts` | Senarai kebenaran CORS yang dipisahkan koma. Tiada kad bebas dihantar melainkan `CORS_ALLOW_ALL=true` dikonfigurasikan secara eksplisit. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Jalan keluar untuk pembangunan sahaja bagi menggemakan sebarang `Origin` pelayar. Jangan dayakan pada penggunaan dikongsi atau pengeluaran. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias warisan. Togol papan pemukanya (pengaturan pangkalan data) dibaca sebelum nilai persekitaran ini; `false`, `0`, `no` atau `off` pada mana-mana satu mematikan semakan hos seperti yang dilakukan oleh `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, walaupun togol papan pemuka bendera tersebut disimpan dalam keadaan mati; nilai lain menyerahkan pelindung kepada dua bendera di bawah. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Mematikan semakan hos pelindung URL keluar, termasuk sekatan metadata awan (keseluruhan 169.254.0.0/16, metadata.google.internal), pada laluan penyedia yang mengikut mod pelindung: pengesahan URL, penemuan model, URL dasar nod penyedia (termasuk nod penarafan semula jauh) dan ujian jatuh balik proksi, antara laluan keluar lain yang berkaitan penyedia seperti pengambilan imej/media jauh. Ia turut membenarkan sasaran webhook peribadi, kecuali titik akhir metadata awan/pautan setempat yang kekal disekat walaupun bendera ini hidup. Permintaan sembang yang dihantar melalui `BaseExecutor.execute()` mengekalkan semakan metadata awannya. Penyedia setempat berfungsi dengan lalai: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (lalai `true`) sudah membenarkan URL setempat dan LAN pada laluan pengesahan, penemuan dan nod penyedia; ujian jatuh balik proksi dan sasaran webhook peribadi hanya merujuk bendera ini dan menyekat hos setempat/LAN selagi ia mati. Mengatasi `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Nilai yang disimpan daripada togol papan pemuka mengatasi pemboleh ubah ini. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Benarkan URL penyedia pada alamat setempat/peribadi (127.0.0.1, localhost, LAN, julat peribadi) untuk pengesahan penyedia, penemuan model, sembang dan laluan keluar lain yang berkaitan penyedia yang ditadbir oleh mod pelindung. **Lalai `true`** (mengutamakan setempat): pelindung kemudian menyekat titik akhir metadata awan — keseluruhan 169.254.0.0/16 bersama nama hos metadata yang diketahui (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Tetapkan `false` untuk mod awam sahaja, yang turut menyekat hos peribadi dan gelung balik. Pelindung memeriksa nama hos atau literal IP seperti yang tertulis, sebelum sebarang carian DNS; dalam mod awam sahaja, pembungkus selamat-fetch (safe-fetch) turut menyelesaikan nama hos dan menolak sebarang jawapan peribadi. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` mematikan semakan ini pada laluan yang disenaraikan dalam barisnya. Penyedia setempat terbina dalam, seperti LM Studio, Ollama, vLLM, Llamafile dan Triton, melangkau semua semakan hos untuk pengesahan kunci dan sembang. Nilai yang disimpan daripada togol papan pemuka mengatasi pemboleh ubah ini. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Benarkan laluan `/v1/audio/*` (transkripsi, pertuturan, terjemahan) menggunakan nod penyedia serasi OpenAI yang dihoskan di luar localhost. Dimatikan secara lalai — penghalaan audio ke hos jauh mengubah identiti trafik keluar dan mesti merupakan keputusan eksplisit pengendali. Nod gelung balik/peribadi (localhost, 127.0.0.1, 172.16-31.x) sentiasa dibenarkan dan tidak terjejas. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Benarkan `POST /v1/rerank` (serta langkah penarafan semula gelung balik enjin memori) menggunakan nod penyedia serasi OpenAI yang dihoskan di luar localhost — mesin LAN atau rakan Tailscale yang menjalankan TEI, Infinity, vLLM, dsb. Dimatikan secara lalai — penghalaan ke hos jauh mengubah identiti trafik keluar dan mesti merupakan keputusan eksplisit pengendali. Nod gelung balik (localhost, 127.0.0.1, 172.16-31.x) sentiasa dibenarkan dan tidak terjejas. URL dasar nod jauh juga mesti melepasi dasar URL keluar penyedia (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Apabila OIDC didayakan, nyahdayakan log masuk kata laluan supaya pengguna hanya boleh mengesahkan identiti melalui Daftar Masuk Tunggal OIDC. Alias ringkas `OIDC_DISABLE_PASSWORD_LOGIN` turut diterima; Bendera Ciri Papan Pemuka dengan kekunci yang sama mempunyai keutamaan. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias ringkas bagi `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Senarai Semak Pengerasan diff --git a/docs/i18n/mt/CONTRIBUTING.md b/docs/i18n/mt/CONTRIBUTING.md index 101b3c8d04f7..eea669192335 100644 --- a/docs/i18n/mt/CONTRIBUTING.md +++ b/docs/i18n/mt/CONTRIBUTING.md @@ -390,22 +390,24 @@ Ikteb testijiet unitarji f'`tests/unit/` li jkopru mill-inqas: --- -## Kaxxa tal-Għarfien tal-Pull Request - -- [ ] Testijiet għaddew (`npm test`) -- [ ] Linting għaddew (`npm run lint`) -- [ ] Build rnexxielu (`npm run build`) -- [ ] It-Tipijiet TypeScript ġew miżjuda għal funzjonijiet u interfaces ġodda pubbliċi -- [ ] M'hemm xejn skrin fiżjati ta' sigrieti jew valuri fallback -- [ ] Kredenzjali upstream pubbliċi ġew imbodding permezz ta' `resolvePublicCred()` (ara [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), qatt bħala literals -- [ ] Ir-risponsi ta' żball jgħaddu minn `buildErrorBody()` / `sanitizeErrorMessage()` — l-ebda trace rwiexi fil-korp tal-Response (ara [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Il-kmandi shell (`exec` / `spawn`) jgħaddu valuri runtime permezz ta' `env`, mhux permezz ta' interpolarzjoni tal-stringa -- [ ] Kull input ġie validat permezz ta' skemi Zod -- [ ] **Fragament** tal-CHANGELOG ġie miżjud taħt `changelog.d/{features|fixes|maintenance}/-.md` għal bidliet li jaffettwaw l-utenti (ara [`changelog.d/README.md`](./changelog.d/README.md)) — tagħmilx direttament editjar `CHANGELOG.md`; il-fragments jiġbru fis-sak ħin tat-tħarir u qatt ma jikkuntrastaw bejn il-PRs -- [ ] Dukumentazzjoni aġġornata (jekk applicable) -- [ ] L-ebda ġdida CodeQL / Secret-Scanning alerts miftuħa, jew kollha riżolvuti b'ġustifikazzjoni teknika li tirreferi għad-dokument rilevanti ta' `docs/security/` -- [ ] Rotot li jħaddmu proċessi iben (`/api/mcp/`, `/api/cli-tools/runtime/`) huma klassifikati bħala `isLocalOnlyPath()` f'`src/server/authz/routeGuard.ts` — ara [Rekwiżiti Ħorox #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] M'hemm l-ebda `Co-Authored-By` trailers fil-messaġġi tal-kommit — il-kommits iridu jidhru biss taħt l-identità Git tal-sid tal-repositorju (Rekwiżit Ħarxa #16) +## Lista ta' Kontroll tal-Pull Request + +- [ ] It-testijiet jgħaddu (`npm test`) +- [ ] Il-linting jgħaddi (`npm run lint`) +- [ ] Il-build jirnexxi (`npm run build`) +- [ ] Żdiedu t-tipi TypeScript għal funzjonijiet u interfaces pubbliċi ġodda +- [ ] L-ebda sigriet jew valur ta' riżerva kkodifikat direttament +- [ ] Il-kredenzjali upstream pubbliċi huma inkorporati permezz ta' `resolvePublicCred()` (ara [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), u qatt bħala litterali +- [ ] Ir-risposti ta' żball jgħaddu minn `buildErrorBody()` / `sanitizeErrorMessage()` — l-ebda stack trace mhux ipproċessat fil-korpi tar-risposti (ara [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Il-kmandi tax-shell (`exec` / `spawn`) jgħaddu l-valuri waqt l-eżekuzzjoni permezz ta' `env`, mhux permezz ta' interpolazzjoni ta' strings +- [ ] L-inputs kollha huma vvalidati bi skemi Zod +- [ ] Żdied **framment** tal-changelog taħt `changelog.d/{features|fixes|maintenance}/-.md` għal bidliet viżibbli għall-utent (ara [`changelog.d/README.md`](./changelog.d/README.md)) — teditjax `CHANGELOG.md` direttament; il-frammenti jiġu aggregati waqt ir-rilaxx u qatt ma joħolqu kunflitti bejn il-PRs +- [ ] Id-dokumentazzjoni ġiet aġġornata (jekk applikabbli) +- [ ] Ma nfetħux twissijiet ġodda ta' CodeQL / Secret-Scanning, jew kull waħda ġiet miċħuda b'ġustifikazzjoni teknika li tirreferi għad-dokument rilevanti f'`docs/security/` +- [ ] Ir-rotot li jniedu proċessi sekondarji (`/api/mcp/`, `/api/cli-tools/runtime/`) huma kklassifikati bħala `isLocalOnlyPath()` f'`src/server/authz/routeGuard.ts` — ara [Regola Stretta #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] L-ebda trailer `Co-authored-by` ta' AI/bot fil-messaġġi tal-commit (Regola Stretta #16) — il-kollaboraturi umani li x-xogħol tagħhom jerġa' jintuża jingħataw kreditu bi trailers standard `Co-authored-by: Name ` + +--- ## Rilaxjar diff --git a/docs/i18n/mt/docs/architecture/QUALITY_GATES.md b/docs/i18n/mt/docs/architecture/QUALITY_GATES.md index d55ae96e06d6..38beace51f82 100644 --- a/docs/i18n/mt/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/mt/docs/architecture/QUALITY_GATES.md @@ -19,211 +19,211 @@ ta’ replikazzjoni indipendenti mill-għodda tal-istess sistema, ara l- ### Ammissjoni tal-kandidati -Il-workflows CI u Quality Gates kull wieħed joħroġ verdett stabbli: `Gate / CI` u -`Gate / Quality`. Il-politika tal-ammissjoni tagħhom, li għandha verżjoni, telenka kull job upstream -bħala obbligatorju jew konsultattiv. Job obbligatorju applikabbli jrid jirnexxi: riżultati -neqsin, ikkanċellati, maqbuża, pendenti u mhux magħrufa ma jistgħux jistabbilixxu PASS. Klassifikazzjoni valida -docs-only jew catalog-only tista’ tagħmel code lane mhux applikabbli; -draft PR mhuwiex kandidat aċċettat. Label `hotfix` ma jneħħix il-ħtieġa għall-evidenza. - -Iż-żewġ workflows ikopru PRs u pushes lejn branches main/release, dispatch manwali u -avvenimenti merge-group. Push, dispatch u merge-group jeżegwixxu l-għażla sħiħa. Forks -u merge groups jużaw hosted runners għal jobs li normalment jagħżlu self-hosted -runners; trid tiġi vverifikata kapaċità hosted suffiċjenti qabel ir-rollout. - -Kull irċevuta JSON tidentifika x-SHA checkout, il-workflow run u l-attempt. -Is-CLI jirrifjuta diskrepanza bejn ix-SHA taċ-checkout u dak tal-avveniment. It-testijiet tal-workflow jorbtu s-sħubija fil-politika -mal-lista `needs` tal-job tal-verdett, sabiex lane ġdida jew imneħħija ma tkunx tista’ tisparixxi mingħajr ma tinduna. -L-irċevuti jkopru l-workflow tagħhom stess, mhux il-pubblikazzjoni, id-deployment, jew il-funzjonament intern -ta’ scanner konsultattiv eżistenti. L-attivazzjoni taż-żewġ ismijiet tal-checks fir-regoli tal-branch hija -bidla amministrattiva separata; iż-żieda ta’ dawn il-jobs minnha nnifisha ma tipproteġix branch. - -### Inventarju tal-iskennjar statiku - -L-inventarju npm-alias bil-verżjoni u s-sħubija fl-iskennjar statiku jinsabu f’ +Il-workflows CI u Quality Gates kull wieħed jipproduċi verdett stabbli: `Gate / CI` u +`Gate / Quality`. Il-politika tal-ammissjoni tagħhom bil-verżjonijiet telenka kull job upstream +bħala meħtieġ jew konsultattiv. Job meħtieġ applikabbli għandu jirnexxi: riżultati neqsin, +ikkanċellati, maqbuża, pendenti u mhux magħrufa ma jistgħux jistabbilixxu PASS. Klassifikazzjoni valida +docs-only jew catalog-only tista’ tagħmel lane tal-kodiċi mhux applikabbli; +draft PR mhuwiex kandidat aċċettat. Label `hotfix` ma jneħħix il-ħtieġa tal-evidenza. + +Iż-żewġ workflows ikopru PRs u pushes lejn fergħat main/release, dispatch manwali u +avvenimenti ta’ merge-group. Push, dispatch u merge-group iħaddmu l-għażla sħiħa. Forks +u merge groups jużaw runners hosted għal jobs li altrimenti jagħżlu runners self-hosted; +għandha tiġi vverifikata kapaċità hosted suffiċjenti qabel ir-rollout. + +Kull irċevuta JSON tidentifika s-SHA checked-out, il-workflow run u l-attempt. +Is-CLI jirrifjuta nuqqas ta’ qbil bejn is-SHA taċ-checkout u dak tal-event. It-testijiet tal-workflow jorbtu s-sħubija fil-politika +mal-lista `needs` tal-job tal-verdett sabiex lane ġdida jew imneħħija ma tkunx tista’ tisparixxi mingħajr avviż. +L-irċevuti jkopru l-workflow tagħhom stess, mhux il-pubblikazzjoni, id-deployment, jew l-interni +ta’ scanner konsultattiv eżistenti. L-attivazzjoni taż-żewġ ismijiet taċ-checks fir-regoli tal-fergħat hija +bidla amministrattiva separata; iż-żieda ta’ dawn il-jobs ma tipproteġix fergħa minnha nnifisha. + +### Inventarju tal-iscan statiku + +L-inventarju tal-npm-aliases bil-verżjonijiet u s-sħubija fl-iscan statiku jinsabu f’ `config/quality/gate-manifest.json`. Ħaddem `npm run check:gate-manifest` biex tivvalida -l-ismijiet tal-iskripts u l-kmandi eżatti ma’ `package.json`; żidiet, tneħħijiet u -devjazzjonijiet fil-kmandi jikkawżaw falliment kemm fil-hook lokali kif ukoll fil-jobs tal-klassifikazzjoni tal-bidliet f’CI. -Alias mhuwiex workflow job, istanza ta’ matrix jew każ ta’ test: dawn l-għaddijiet -ma għandhomx jiġu ppreżentati bħala interkambjabbli. +l-ismijiet tal-iskripts u l-kmandi eżatti kontra `package.json`; żidiet, tneħħijiet u +devjazzjoni fil-kmandi jfallu kemm il-hook lokali kif ukoll il-jobs tal-klassifikazzjoni tal-bidliet f’CI. +Alias mhuwiex workflow job, matrix instance jew test case: dawn l-għaddijiet ma għandhomx +jiġu ppreżentati bħala interkambjabbli. Uża `npm run quality:scan -- --list` jew `npm run quality:scan:fast -- --list` biex tispezzjona l-aliases magħżula mingħajr ma teżegwihom. Ir-runner jinvoka l- -entrypoint ta’ npm, għalhekk ir-runtime tiegħu (inkluż Bun fejn ikkonfigurat) jinżamm. +npm entrypoint, għalhekk ir-runtime tiegħu (inkluż Bun fejn ikkonfigurat) jiġi ppreservat. Il-manifest jirreġistra aliases barra dawk il-profili bħala invokati separatament, u -kmandi ta’ manutenzjoni huma pprojbiti fi profili ta’ skennjar read-only. +kmandi ta’ manutenzjoni huma pprojbiti fi profili ta’ scan read-only. -Dawn il-profili jkopru biss l-iskennjar statiku. Ma jiċċertifikawx testijiet tal-prodott, -coverage, packaging, checks esterni jew l-aċċettazzjoni sħiħa ta’ kandidat għar-rilaxx. -L-ammissjoni tal-workflow tuża l-fajl marbut `config/quality/admission-policy.json` u -`scripts/quality/admission-verdict.mjs`. Il-profili release-observer jibqgħu separati; -spezzjona l-checks u l-irċevuti applikabbli tagħhom b’mod indipendenti. L-inventarju deskritt -hawn taħt huwa referenza, mhux prova li gate fil-fatt ġie eżegwit. +Dawn il-profili jkopru biss l-iscan statiku. Ma jiċċertifikawx it-testijiet tal-prodott, +il-coverage, il-packaging, checks esterni jew l-aċċettazzjoni sħiħa ta’ kandidat għar-release. +L-ammissjoni tal-workflow tuża l-`config/quality/admission-policy.json` marbut u +`scripts/quality/admission-verdict.mjs`. Il-profili tar-release-observer jibqgħu separati; +spezzjona ċ-checks u l-irċevuti applikabbli tagħhom b’mod indipendenti. L-inventarju deskrittiv +hawn taħt huwa referenza, mhux prova li gate fil-fatt tħaddem. L-iskripts jinsabu taħt `scripts/check/` (gates tal-politika) u `scripts/quality/` (magna ratchet). Is-sors awtorevoli tas-CI huwa `.github/workflows/ci.yml`. -### Mogħdija rapida għar-Release PR (`quality.yml`) +### Fast-path tal-PR tar-release (`quality.yml`) -`.github/workflows/quality.yml` jikkomplementa s-CI fuq PRs main/release, pushes lejn protected branches, +`.github/workflows/quality.yml` jikkomplementa s-CI fuq PRs main/release, pushes lejn fergħat protetti, dispatch u merge groups. Il-PRs jużaw fast checks iffiltrati skont il-path. Il-build duplikat -diżattivat b’mod permanenti tneħħa; il-checks reali tal-build/package/boot jibqgħu fis-CI. - -| Job | Ambitu | Imblukkar | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | -| `Docs Gates (fast-path)` | PRs tad-dokumentazzjoni/kodiċi; referenzi tad-dokumentazzjoni tal-API u docs-all | Iva | -| `Fast Quality Gates` | PRs tal-kodiċi; checks statiċi, typecheck, typecheck tad-dashboard, testijiet unit affettwati | Iva | -| `Forgotten sibling tests` | PRs tal-kodiċi; moduli mibdula traċċati sal-konsumaturi statiċi u testijiet sibling kandidati; paths ta’ barrel u dynamic-import jiġu rrappurtati bħala dijanjostika konsultattiva, b’eċċezzjonijiet referenzjati fl-allowlist | **Konsultattiv** | -| `Vitest (fast-path)` | PRs tal-kodiċi; suite rapida ta’ vitest | Iva | -| `Unit Tests fast-path` | PRs tal-kodiċi; suite ta’ unit tests b’4 shards | Iva | -| `No new ESLint warnings` | PRs tal-kodiċi; kontroll tal-lint konxju tas-suppressions | Iva, inklużi forks | -| `Merge integrity (changelog + generated skills)` | PRs li mhumiex draft; sinkronizzazzjoni taċ-changelog u tal-ħiliet iġġenerati | Iva, inklużi forks | - -#### Rapport dwar forgotten sibling tests - -`npm run check:forgotten-sibling-tests` jerġa’ juża l-import resolver wara l-mappa tal-impatt tat-testijiet. -Għal kull production module mibdul, jirrapporta ktajjen deterministiċi +diżattivat b’mod permanenti tneħħa; iċ-checks reali tal-build/package/boot jibqgħu fis-CI. + +| Job | Ambitu | Jibblokka | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `Docs Gates (fast-path)` | PRs tad-docs/kodiċi; refs tad-dokumentazzjoni tal-API u docs-all | Iva | +| `Fast Quality Gates` | PRs tal-kodiċi; checks statiċi, typecheck, typecheck tad-dashboard, unit tests affettwati | Iva | +| `Forgotten sibling tests` | PRs tal-kodiċi; moduli mibdula traċċati lejn konsumaturi statiċi u sibling tests kandidati; paths tal-barrel u dynamic-import jiġu rrappurtati bħala dijanjostika konsultattiva, b’eċċezzjonijiet referenzjati tal-allowlist | **Konsultattiv** | +| `Vitest (fast-path)` | PRs tal-kodiċi; suite vitest rapida | Iva | +| `Unit Tests fast-path` | PRs tal-kodiċi; suite ta’ unit tests b’4 shards | Iva | +| `No new ESLint warnings` | PRs tal-kodiċi; guard tal-lint konxju tas-suppressions | Iva, inklużi forks | +| `Merge integrity (changelog + generated skills)` | PRs mhux draft; sinkronizzazzjoni tal-changelog u tal-skills iġġenerati | Iva, inklużi forks | + +#### Rapport tat-testijiet sibling minsija + +`npm run check:forgotten-sibling-tests` jerġa’ juża l-import resolver wara t-test-impact map. +Għal kull modulu ta’ produzzjoni mibdul, jirrapporta ktajjen deterministiċi `changed module/symbol -> static consumer -> candidate sibling test` meta t-test kandidat -ikun nieqes mid-diff tal-pull request. Is-sommarju Markdown u r-riżultat JSON jinżammu bħala +ma jkunx fid-diff tal-pull request. Is-sommarju Markdown u r-riżultat JSON jinżammu bħala l-artifact tal-workflow `forgotten-sibling-tests` għall-kalibrazzjoni qabel kwalunkwe rollout li jimblokka. -Ir-re-esportazzjonijiet barrel u l-importazzjonijiet dinamiċi huma biss dijanjostiċi tar-riżoluzzjoni; qatt ma joħolqu +Ir-ri-esportazzjonijiet barrel u l-importazzjonijiet dinamiċi huma biss dijanjostiċi tar-riżoluzzjoni; qatt ma joħolqu sejba li timblokka. L-eċċezzjonijiet rieżaminati jinsabu f' -`config/quality/forgotten-sibling-allowlist.json`. Kull entrata trid issemmi t-test konsumatur u dak kandidat, -tagħti ġustifikazzjoni speċifika, u tinkludi link għal issue jew pull request ta' GitHub. Entrati fformattjati ħażin ifallu -b'mod restrittiv. L-eċċezzjonijiet ma jistgħux jinjoraw test kandidat imħassar jew diff li jżid `.skip`/`.todo`; -id-dgħufija tal-asserzjonijiet u forom oħra ta' masking jibqgħu r-responsabbiltà tal-gate indipendenti u imblokkanti +`config/quality/forgotten-sibling-allowlist.json`. Kull entrata trid issemmi l-konsumatur u t-test +kandidat, tagħti ġustifikazzjoni speċifika, u torbot ma' issue jew pull request ta' GitHub. Entrati fformattjati ħażin ifallu +b'mod magħluq. L-eċċezzjonijiet ma jistgħux jinjoraw test kandidat imħassar jew diff li jżid `.skip`/`.todo`; +id-dgħufija tal-asserzjonijiet u tipi oħra ta' maskra jibqgħu taħt ir-responsabbiltà tal-gate li jimblokka b'mod indipendenti `check:test-masking`. ### Job: `lint` Jitħaddem fuq kull PR lejn `main`. Jimblokka l-merge jekk ifalli. -| Script (`npm run ...`) | Jivvalida | Jimblokka | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Il-verżjoni ta' Node.js tinsab fil-medda appoġġjata | Iva | -| `check:cycles` | Importazzjonijiet ċirkolari — il-moduli kollha ta' `src/` + `open-sse/` | Iva | -| `check:route-validation:t06` | Schemas ta' Zod preżenti fuq ir-routes kollha (politika ta' Tier 6) | Iva | -| `check:any-budget:t11` | L-għadd ta' `@ts-expect-error // any` ma jaqbiżx il-baġit (catraca ta' Tier 11) | Iva | -| `check:provider-consistency` | Kull fornitur f’`providers.ts` għandu entrata korrispondenti f’`providerRegistry.ts` (u viċi versa, fi ħdan l-allowlist) | Iva | -| `check:model-lifecycle` | It-tliet tabelli tar-routing miżmuma manwalment jibqgħu konsistenti mal-istampa tal-lifecycle inkluża fir-repo (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ma tagħti punteġġ lil ebda id irtirat li `REGISTRY` jista’ jidderieġi; kull mira ta’ `BUILT_IN_ALIASES` tinsab f’`REGISTRY` u hija assenti mill-istampa tal-ids irtirati; kull id irtirat li għadu f’`REGISTRY` jiġi ridirett jew ikun elenkat f’`allowedRetiredInCatalog`; u ebda sors jew mira ta’ `DEFAULT_DEGRADATION_MAP` ma jidher bħala rtirat f’dik l-istampa. Dan ma jipprovax li mudell bħalissa qed jiġi pprovdut minn upstream attiv. Offline — iqabbel ma’ `config/quality/model-lifecycle.json`, aġġornat manwalment permezz ta’ `npm run quality:refresh-model-lifecycle` (jeħtieġ in-network; mhux integrat fis-CI). `allowedRetiredInCatalog` huwa ratchet ta’ tnaqqis gradwali: żid entrata biss flimkien ma’ issue ta’ traċċar. | Iva | -| `check:fetch-targets` | Kull `fetch("/api/...")` f’`src/` fuq in-naħa tal-klijent jirrisolvi għal `route.ts` reali | Iva | -| `check:deps` | Id-dipendenzi kollha li jistgħu jiġu installati b’`npm install` fil-`package.json` kollha fir-repo jinsabu f’`dependency-allowlist.json`; pakketti ġodda mhux ippinnjati jew slopsquatted jiġu mmarkati | Iva | -| `audit:deps` | `npm audit` (root + electron) — ebda avviż ta’ severità għolja/kritika (jikkoinċidi ma’ `check:vuln-ratchet` ta’ osv; ara l-Backlog tar-Razzjonalizzazzjoni) | Iva | -| `check:lockfile` | Integrità ta’ `package-lock.json` — reġistru https, hashes tal-integrità, ebda sovrascrittura tal-host | Iva | -| `check:licenses` | Lista ta’ permessi għal-liċenzji SPDX għad-dipendenzi tal-produzzjoni | Iva | -| `check:tracked-artifacts` | Ebda artifact tal-build / symlink ta’ `node_modules` ikkommitjat (jitħaddem ukoll fil-pre-commit ta’ husky; il-pre-push huwa intenzjonalment ħafif — #6716) | Iva | -| `check:ai-attribution` | Ebda trailer `Co-Authored-By` tal-AI/bot jew footer ta’ ġenerazzjoni bl-AI fil-commits, fit-titlu jew fil-kontenut tal-PR — Regola Stretta #16 (fil-loop ta’ kontrolli rapidi ta’ `quality.yml` għal PR→`release/**` — jaqra l-payload tal-event, ma jagħmel xejn barra mill-PRs — u pass għal PR biss fil-lint ta’ `ci.yml` għal PR→`main`; kif ukoll il-hook `commit-msg` ta’ husky; koawturi umani permessi; #14436) | -| `check:vitest-exclusions` | Kull esklużjoni ta’ Vitest issemmi issue ta’ traċċar u tidher f’`config/quality/vitest-exclusions.json` (#13204) | Iva | -| `check:file-size` | Ebda fajl tas-sors ma jaqbeż il-limitu għal kull estensjoni (ratchet: fajls kbar iffriżati fil-lista `frozen`) | Iva | -| `check:error-helper` | Ir-risposti ta’ żball fl-executors/handlers jużaw `buildErrorBody()` / `sanitizeErrorMessage()` (Regola Stretta #12) | Iva | -| `check:migration-numbering` | Il-fajls SQL tal-migrazzjoni huma nnumerati b’mod sekwenzjali, mingħajr lakuni jew duplikati | Iva | -| `check:public-creds` | L-ebda `client_id`/`client_secret` OAuth litterali jew ċwievet Firebase Web barra minn `publicCreds.ts` (Regola Stretta #11) | Iva | -| `check:db-rules` | L-ebda SQL mhux ipproċessat barra mill-moduli `src/lib/db/`; l-ebda importazzjoni aggregata minn `localDb.ts` (Regoli Stretti #2/#5) | Iva | -| `check:known-symbols` | L-eżekuturi tal-fornituri, l-istrateġiji tar-routing, u t-tradutturi rreġistrati fit-tabelli tad-dispaċċ tagħhom jaqblu mal-fajls fuq id-diska — l-ebda simbolu orfni jew mhux iddikjarat | Iva | -| `check:route-guard-membership` | Kull rotta li tniedi proċess sekondarju hija kklassifikata minn `isLocalOnlyPath()` (Regoli Stretti #15/#17) | Iva | -| `check:test-discovery` | Kull fajl `*.test.ts` / `*.spec.ts` fir-repo jinġabar minn tal-inqas eżekutur wieħed tat-testijiet (ratchet: il-lista tal-orfni f’`test-discovery-baseline.json` tista’ biss tiċkien) | Iva | -| `check:agent-skills-sync` | L-artifatti ġġenerati ta’ agent-skills jaqblu mal-katalgu tas-sors tagħhom (mingħajr devjazzjoni) | -| `check:provider-asset-provenance` | Il-logos/assi tal-fornituri għandhom entrata rreġistrata tal-provenjenza | -| `lint:json` | Il-fajls tal-konfigurazzjoni JSON jiġu pproċessati u jissodisfaw ir-regoli tal-lint tar-repożitorju | -| `typecheck:core` | Kompilazzjoni ta’ TypeScript mingħajr żbalji (twissijiet konsultattivi biss) | Iva | -| `typecheck:noimplicit:core` | `noImplicitAny` strett — maħsub għall-futur; ħafna siti ta’ sejħiet eżistenti għadhom jeħtieġu annotazzjonijiet | **Konsultattiv** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` limitat għal `src/app/(dashboard)/**` (#7033) — il-lista magħżula ta’ 27 fajl ta’ `typecheck:core` ma tinkludi ebda TSX tad-dashboard, u `next build` lanqas qatt ma jwettaq kontroll tat-tipi fuqu (`next.config.mjs` jistabbilixxi `ignoreBuildErrors: true`), għalhekk rigressjonijiet ta’ identifikaturi orfni hemmhekk (#6625/#6909) ma kinux viżibbli għas-CI. Isiru differenzi ma’ linja bażi ffriżata tal-għadd għal kull fajl/kodiċi TS (`config/quality/dashboard-typecheck-baseline.json`, bl-istess mudell ta’ infurzar kontra valuri skaduti bħal `check:known-symbols`) — żbalji ĠODDA biss lil hinn mill-għadd fil-linja bażi jfallu l-kontroll; naqqas il-linja bażi b’mod progressiv permezz ta’ `--update` meta jiġi rranġat żball eżistenti. | Iva | +| Script (`npm run ...`) | Jivvalida | Jimblokka | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Il-verżjoni ta' Node.js tinsab fil-medda appoġġjata | Iva | +| `check:cycles` | Importazzjonijiet ċirkolari madwar `src/` + `open-sse/` kollha (ibbażati fuq AST, bil-`paths` ta' tsconfig riżolti). Bare = konsultattiv, jelenka ċ-ċikli. `check:cycles:ratchet` (dak li jħaddem is-CI) jimblokka meta l-għadd jaqbeż il-limitu massimu `metrics.cycles` f'`quality-baseline.json` — bħalissa 14, `direction: down`, għalhekk jista' jonqos biss (#15159 G-01/G-02) | Iva (ratchet) | +| `check:route-validation:t06` | Schemas ta' Zod preżenti fuq ir-routes kollha (politika ta' Tier 6) | Iva | +| `check:any-budget:t11` | L-għadd ta' `@ts-expect-error // any` ma jaqbiżx il-baġit (ratchet ta' Tier 11) | Iva | +| `check:provider-consistency` | Kull fornitur f’`providers.ts` għandu entrata korrispondenti f’`providerRegistry.ts` (u viċi versa, fil-limiti tal-lista ta’ permessi) | Iva | +| `check:model-lifecycle` | It-tliet tabelli tar-routing miżmuma manwalment jibqgħu konsistenti mal-istampa tal-istat taċ-ċiklu tal-ħajja inkluża fir-repo (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ma tagħti punteġġ lil ebda id irtirat li `REGISTRY` jista’ jirrotja; kull destinazzjoni ta’ `BUILT_IN_ALIASES` tinsab f’`REGISTRY` u hija assenti mill-istampa tal-istat tal-ids irtirati; kull id irtirat li għadu f’`REGISTRY` jiġi ridirezzjonat jew elenkat f’`allowedRetiredInCatalog`; u ebda sors jew destinazzjoni ta’ `DEFAULT_DEGRADATION_MAP` ma jidher bħala rtirat f’dik l-istampa tal-istat. Dan ma jagħtix prova li mudell bħalissa qed jiġi servut minn upstream attiv. Offline — iqabbel ma’ `config/quality/model-lifecycle.json`, aġġornat manwalment permezz ta’ `npm run quality:refresh-model-lifecycle` (jeħtieġ in-network; mhux integrat fis-CI). `allowedRetiredInCatalog` huwa mekkaniżmu progressiv ta’ tnaqqis: żid entrata biss flimkien ma’ issue għat-traċċar. | Iva | +| `check:fetch-targets` | Kull `fetch("/api/...")` f’`src/` fuq in-naħa tal-klijent jirriżolvi għal `route.ts` reali | Iva | +| `check:deps` | Id-dipendenzi kollha li jistgħu jiġu installati b’`npm install` fil-fajls `package.json` kollha fir-repo jinsabu f’`dependency-allowlist.json`; pakketti ġodda mhux ippinjati jew slopsquatted jiġu mmarkati | Iva | +| `audit:deps` | `npm audit` (root + electron) — ebda avviż ta’ severità għolja/kritika (jikkoinċidi mal-osv `check:vuln-ratchet`; ara l-Lista ta’ Xogħol b'Lura għar-Razzjonalizzazzjoni) | Iva | +| `check:lockfile` | Integrità ta’ `package-lock.json` — reġistru https, hashes tal-integrità, ebda sostituzzjoni tal-host | Iva | +| `check:licenses` | Lista ta’ permessi għal-liċenzji SPDX għad-dipendenzi tal-produzzjoni | Iva | +| `check:tracked-artifacts` | L-ebda artefatt tal-build / symlink ta’ `node_modules` ikkommettut (jitħaddem ukoll fil-pre-commit ta’ husky; il-pre-push huwa intenzjonalment ħafif — #6716) | Iva | +| `check:ai-attribution` | L-ebda trailer `Co-Authored-By` ta’ AI/bot jew footer ta’ ġenerazzjoni bl-AI fil-commits, fit-titlu jew fil-korp tal-PR — Regola Stretta #16 (fil-loop ta’ kontrolli rapidi ta’ `quality.yml` għal PR→`release/**` — jaqra l-payload tal-event, ma jagħmel xejn barra mill-PRs — u pass għal PR biss fil-lint ta’ `ci.yml` għal PR→`main`; kif ukoll il-hook `commit-msg` ta’ husky; koawturi umani huma permessi; #14436) | +| `check:vitest-exclusions` | Kull esklużjoni ta’ Vitest issemmi issue ta’ traċċar u tidher f’`config/quality/vitest-exclusions.json` (#13204) | Iva | +| `check:file-size` | L-ebda fajl tas-sors ma jaqbeż il-limitu għal kull estensjoni (ratchet: fajls kbar iffriżati fil-lista `frozen`) | Iva | +| `check:error-helper` | Ir-risposti ta’ żball fl-executors/handlers jużaw `buildErrorBody()` / `sanitizeErrorMessage()` (Regola Stretta #12) | Iva | +| `check:migration-numbering` | Il-fajls SQL tal-migrazzjoni huma nnumerati b'mod sekwenzjali, mingħajr lakuni jew duplikati | Iva | +| `check:public-creds` | L-ebda `client_id`/`client_secret` OAuth litterali jew ċwievet tal-Web ta' Firebase barra minn `publicCreds.ts` (Regola Stretta #11) | Iva | +| `check:db-rules` | L-ebda SQL mhux ipproċessat barra mill-moduli `src/lib/db/`; l-ebda importazzjoni minn barrel minn `localDb.ts` (Regoli Stretti #2/#5) | Iva | +| `check:known-symbols` | L-eżekuturi tal-fornituri, l-istrateġiji tar-routing, u t-tradutturi rreġistrati fit-tabelli tad-dispaċċ tagħhom jaqblu mal-fajls fuq id-diska — l-ebda simbolu orfni jew mhux iddikjarat | Iva | +| `check:route-guard-membership` | Kull rotta li tniedi proċess subordinat hija kklassifikata minn `isLocalOnlyPath()` (Regoli Stretti #15/#17) | Iva | +| `check:test-discovery` | Kull fajl `*.test.ts` / `*.spec.ts` fir-repo jinġabar minn mill-inqas test runner wieħed (ratchet: il-lista ta' fajls orfni f'`test-discovery-baseline.json` tista' biss tiċkien) | Iva | +| `check:agent-skills-sync` | L-artifacts iġġenerati tal-ħiliet tal-aġent jaqblu mal-katalgu sors tagħhom (mingħajr devjazzjoni) | +| `check:provider-asset-provenance` | Il-logos/assi tal-fornituri għandhom entrata tal-provenjenza rreġistrata | +| `lint:json` | Il-fajls tal-konfigurazzjoni JSON jiġu analizzati u jissodisfaw ir-regoli tal-lint tar-repo | +| `typecheck:core` | Kompilazzjoni TypeScript mingħajr żbalji (twissijiet konsultattivi biss) | Iva | +| `typecheck:noimplicit:core` | `noImplicitAny` strett — immirat għall-futur; ħafna siti ta' sejħiet eżistenti għadhom jeħtieġu annotazzjonijiet | **Konsultattiv** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` limitat għal `src/app/(dashboard)/**` (#7033) — il-lista ta' permessi kkurata ta' 27 fajl ta' `typecheck:core` ma tinkludi ebda TSX tad-dashboard, u `next build` lanqas qatt ma jagħmel verifika tat-tipi tiegħu (`next.config.mjs` jistabbilixxi `ignoreBuildErrors: true`), għalhekk ir-rigressjonijiet ta' identifikaturi orfni hemmhekk (#6625/#6909) ma kinux viżibbli għas-CI. Jagħmel diff ma' linja bażi ffriżata tal-għadd għal kull fajl/għal kull kodiċi TS (`config/quality/dashboard-typecheck-baseline.json`, l-istess mudell ta' infurzar tal-iskadenza bħal `check:known-symbols`) — huma biss żbalji ĠODDA lil hinn mill-għadd fil-linja bażi li jfallu l-kontroll; naqqas progressivament b'`--update` meta jiġi rranġat żball eżistenti. | Iva | ### Kompitu: `quality-gate` -Jitħaddem wara `test-coverage`. Jibblokka l-inkorporazzjoni jekk ifalli. - -| Skript | Jivvalida | Imblokkanti | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `quality:collect` | Jiġġenera `quality-metrics.json` (għadd ta' twissijiet ta' ESLint, kopertura mir-rapport ikkombinat tax-shards) | Iva (qabel ir-ratchet) | -| `quality:ratchet` | Kull metrika f'`quality-baseline.json` ma marritx lura (twissijiet ta' ESLint ≤ il-linja bażi; kopertura ≥ il-linja bażi) | Iva | -| `check:duplication` | Id-duplikazzjoni tal-kodiċi (jscpd@4) ma taqbiżx il-linja bażi f'`quality-baseline.json` | Iva | -| `check:complexity` | Il-kumplessità ċiklomatika fil-livell tal-fajl ma taqbiżx il-limitu (ESLint ewlieni `complexity` + `max-lines-per-function`) | Iva | -| `check:cognitive-complexity` | Ratchet tal-kumplessità konjittiva (`eslint-plugin-sonarjs`) — pass separat ta' ESLint; is-CI jħaddem it-tnejn magħqudin bħala l-pass uniku `check:complexity-ratchets` | Iva | -| `check:dead-code` | Ir-ratchet ta' exports / fajls mhux użati (knip) ma jmurx lura meta mqabbel mal-linja bażi | Iva | -| `check:compression-budget` | Baġit tal-benchmark tal-kompressjoni — il-limiti minimi tal-iffrankar tat-tokens għal kull magna ma jistgħux imorru lura | Iva | -| `check:type-coverage` | Ir-ratchet tal-perċentwal ittajpjat (`type-coverage`) ma jmurx lura; fil-biċċa l-kbira jissostitwixxi `typecheck:noimplicit:core` | Iva | -| `check:codeql-ratchet` | L-għadd ta' twissijiet CodeQL miftuħa ma jmurx lura (jaqra permezz ta' `gh api`; jaqbeż mingħajr żball jekk ma jkunx hemm token) — għall-frekwenza tal-aġġornament u l-attivazzjoni manwali: ara "Ratchet ta' CodeQL" hawn taħt | Iva | - -### Job: `quality-extended` - -Il-job kollu huwa konsultattiv (`continue-on-error: true`). Ir-ratchets ibbażati fuq npm -jitħaddmu tassew; l-iskaners esterni jiġu installati permezz ta' `gh release download` u jaqbżu -awtomatikament (exit 0) meta binarju jkun għadu nieqes. - -| Skript | Jivvalida | Imblokkanti | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| `check:circular-deps` | Ebda dipendenzi ċirkolari (dpdm) | **Konsultattiv** | -| `check:bundle-size` | Id-daqs tal-bundle ma jaqbiżx il-limitu | **Konsultattiv** | -| `check:secrets` | Skennjar tas-sigrieti (gitleaks) — jaqbeż jekk il-binarju jkun nieqes | **Konsultattiv** | -| `check:vuln-ratchet` | Il-vulnerabbiltajiet tad-dipendenzi (osv-scanner) ma jmorrux lura — jaqbeż jekk il-binarju jkun nieqes | **Konsultattiv** | -| `check:workflows` | Lint tal-workflows (actionlint + zizmor); skaners neqsin/bil-ħsara, rapporti invalidi jew linja bażi tar-ratchet nieqsa jwasslu għal falliment bħala INCOMPLETE. Sejbiet validi jsegwu l-politika stretta/konsultattiva/tar-ratchet magħżula | L-eżekuzzjoni hija meħtieġa; ir-ratchet ta' zizmor huwa imblokkanti fis-CI | -| `check:openapi-breaking` | Bidliet inkompatibbli fil-kuntratt tal-API pubblika (`openapi.yaml`) meta mqabbel mal-fergħa bażi (oasdiff) — jiġġenera `openapiBreaking=N`; jaqbeż jekk oasdiff ikun nieqes jew l-ispeċifikazzjoni bażi ma tkunx tista' tiġi riżolta | **Konsultattiv** | - -### Job: `docs-sync-strict` +Jitħaddem wara `test-coverage`. Jimblokka l-amalgamazzjoni jekk ifalli. + +| Skript | Jivvalida | Jimblokka | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `quality:collect` | Jiġġenera `quality-metrics.json` (għadd ta’ twissijiet ESLint, kopertura mir-rapport ikkombinat tax-shards) | Iva (jiġi qabel ir-ratchet) | +| `quality:ratchet` | Kull metrika f’`quality-baseline.json` ma marritx lura (twissijiet ESLint ≤ il-linja bażi; kopertura ≥ il-linja bażi) | Iva | +| `check:duplication` | Id-duplikazzjoni tal-kodiċi (jscpd@4) ma taqbiżx il-linja bażi f’`quality-baseline.json` | Iva | +| `check:complexity` | Il-kumplessità ċiklomatika fil-livell tal-fajl ma taqbiżx il-limitu (ESLint ewlieni `complexity` + `max-lines-per-function`) | Iva | +| `check:cognitive-complexity` | Ratchet tal-kumplessità konjittiva (`eslint-plugin-sonarjs`) — pass separat ta’ ESLint; is-CI jħaddem it-tnejn ikkombinati bħala l-pass uniku `check:complexity-ratchets` | Iva | +| `check:dead-code` | Ir-ratchet tal-esportazzjonijiet / fajls mhux użati (knip) ma jmurx lura meta mqabbel mal-linja bażi | Iva | +| `check:compression-budget` | Baġit tal-parametru referenzjarju tal-kompressjoni — il-limiti minimi tal-iffrankar tat-tokens għal kull magna ma għandhomx imorru lura | Iva | +| `check:type-coverage` | Ir-ratchet tal-perċentwal ittajpjat (`type-coverage`) ma jmurx lura; fil-biċċa l-kbira jissostitwixxi `typecheck:noimplicit:core` | Iva | +| `check:codeql-ratchet` | L-għadd ta’ twissijiet CodeQL miftuħa ma jmurx lura (jaqra permezz ta’ `gh api`; jaqbeż b’mod sigur mingħajr token) — għaċ-ċiklu tal-aġġornament u l-iskattar manwali: ara "Ratchet ta’ CodeQL" hawn taħt | Iva | + +### Kompitu: `quality-extended` + +Il-kompitu kollu huwa konsultattiv (`continue-on-error: true`). Ir-ratchets ibbażati fuq npm jitħaddmu +tassew; l-iskaners esterni jiġu installati permezz ta’ `gh release download` u jaqbżu lilhom infushom (exit 0) +meta l-binarju jkun għadu nieqes. + +| Skript | Jivvalida | Jimblokka | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `check:circular-deps` | L-ebda dipendenza ċirkolari (dpdm) | **Konsultattiv** | +| `check:bundle-size` | Id-daqs tal-bundle ma jaqbiżx il-limitu | **Konsultattiv** | +| `check:secrets` | Skennjar ta’ sigrieti (gitleaks) — jaqbeż jekk il-binarju jkun nieqes | **Konsultattiv** | +| `check:vuln-ratchet` | Il-vulnerabbiltajiet tad-dipendenzi (osv-scanner) ma jmorrux lura — jaqbeż jekk il-binarju jkun nieqes | **Konsultattiv** | +| `check:workflows` | Verifika tal-workflows (actionlint + zizmor); skaners neqsin/bil-ħsara, rapporti invalidi jew linja bażi tar-ratchet nieqsa jfallu bħala INCOMPLETE. Sejbiet validi jsegwu l-politika magħżula stretta/konsultattiva/tar-ratchet | L-eżekuzzjoni hija meħtieġa; ir-ratchet ta’ zizmor jimblokka fis-CI | +| `check:openapi-breaking` | Bidliet inkompatibbli fil-kuntratt tal-API pubblika (`openapi.yaml`) meta mqabbel mal-fergħa bażi (oasdiff) — jiġġenera `openapiBreaking=N`; jaqbeż jekk oasdiff ikun nieqes jew l-ispeċifikazzjoni bażi ma tkunx tista’ tiġi riżolta | **Konsultattiv** | + +### Kompitu: `docs-sync-strict` Jitħaddem fuq kull PR lejn `main`. Jimblokka l-merge jekk ifalli. -| Skritt | Jivvalida | Jimblokka | +| Skript | Jivvalida | Imblokkanti | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -| `check:docs-all` | Meta-gate li tħaddem is-6 sub-gates ta' hawn taħt b'mod sekwenzjali | Iva | -| ↳ `check:docs-sync` | Konsistenza tal-verżjonijiet ta' CHANGELOG / OpenAPI / llm.txt | Iva | -| ↳ `check:docs-counts` | L-għadd imsemmi fit-test (għadd ta' fornituri, għadd ta' migrazzjonijiet, eċċ.) jinsab fit-tieqa ratchet tal-għadd reali | Iva | -| ↳ `check:env-doc-sync` | Kull varjabbli tal-ambjent f'`.env.example` hija ddokumentata f'tabella tad-dokumentazzjoni, u viċi versa | Iva | -| ↳ `check:deprecated-versions` | Ma hemm l-ebda string ta' verżjoni deprekata fid-dokumentazzjoni | Iva | +| `check:docs-all` | Meta-gate li jħaddem is-6 sub-gates ta’ hawn taħt b’mod sekwenzjali | Iva | +| ↳ `check:docs-sync` | Konsistenza tal-verżjonijiet bejn CHANGELOG / OpenAPI / llm.txt | Iva | +| ↳ `check:docs-counts` | L-għadd fil-proża (għadd ta’ fornituri, għadd ta’ migrazzjonijiet, eċċ.) jinsab fit-tieqa ratcheted tal-għadd reali | Iva | +| ↳ `check:env-doc-sync` | Kull varjabbli tal-ambjent f’`.env.example` huwa ddokumentat f’tabella tad-dokumentazzjoni, u viċi versa | Iva | +| ↳ `check:deprecated-versions` | Ebda sekwenza ta’ verżjoni deprekata fid-dokumentazzjoni | Iva | | ↳ `check:doc-links` | Il-links markdown interni fid-dokumentazzjoni jirriżolvu għal fajls reali (forma `[test]`/`(path)`) | Iva | -| ↳ `check:fabricated-docs` | Ir-rotot, il-varjabbli tal-ambjent, il-kmandi CLI, l-ismijiet tal-hooks, u l-mogħdijiet tal-fajls iċċitati fid-dokumentazzjoni jeżistu fil-codebase. Gate stretta permezz ta' `--strict`; falliment mhux imblokkanti mingħajr il-flag. | Iva (permezz ta' `--strict` fis-CI) | -| `check:cli-i18n` | L-strings tal-kmandi CLI huma preżenti fil-fajls kollha tal-lokalitajiet i18n | Iva | +| ↳ `check:fabricated-docs` | Ir-rotot, il-varjabbli tal-ambjent, il-kmandi CLI, l-ismijiet tal-hooks, u l-mogħdijiet tal-fajls imsemmija fid-dokumentazzjoni jeżistu fil-codebase. Gate strett permezz ta’ `--strict`; falliment mhux imblokkanti mingħajr il-flag. | Iva (permezz ta’ `--strict` fis-CI) | +| `check:cli-i18n` | Is-sekwenzi tal-kmandi CLI huma preżenti fil-fajls tal-lokalitajiet i18n kollha | Iva | | `check:openapi-coverage` | L-ispeċifikazzjoni OpenAPI tkopri mill-inqas limitu minimu ratcheted tar-rotot reali | Iva | -| `check:openapi-security-tiers` | L-annotazzjonijiet tal-livelli tas-sigurtà f'`openapi.yaml` huma konsistenti mal-klassifikazzjonijiet ta' `routeGuard.ts` | **Konsultattiv** | -| `check:openapi-routes` | Kull mogħdija f'`openapi.yaml` tirriżolvi għal `route.ts` reali (kontra l-alluċinazzjonijiet) | Iva | -| `check:docs-symbols` | Kull referenza għal `/api/...` f'`docs/**/*.md` tirriżolvi għal `route.ts` reali (kontra l-alluċinazzjonijiet) | Iva | +| `check:openapi-security-tiers` | L-annotazzjonijiet tal-livelli tas-sigurtà f’`openapi.yaml` huma konsistenti mal-klassifikazzjonijiet ta’ `routeGuard.ts` | **Konsultattiv** | +| `check:openapi-routes` | Kull mogħdija f’`openapi.yaml` tirriżolvi għal `route.ts` reali (kontra l-alluċinazzjonijiet) | Iva | +| `check:docs-symbols` | Kull referenza `/api/...` f’`docs/**/*.md` tirriżolvi għal `route.ts` reali (kontra l-alluċinazzjonijiet) | Iva | | `i18n translation drift` | Ċwievet mhux tradotti fil-fajls tal-lokalitajiet i18n — twissija biss | **Konsultattiv** | -### Xogħol: `i18n-ui-coverage` +### Kompitu: `i18n-ui-coverage` -| Skritt | Jivvalida | Jimblokka | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (inline) | Il-kopertura taċ-ċwievet i18n tal-UI hija ≥ 65% | Iva | -| `check-ui-value-drift` (inline) | **Valur** bl-Ingliż li jkun inkiteb mill-ġdid ma jħalli warajh l-ebda traduzzjoni skaduta | Iva | -| `check-new-key-coverage` (inline) | Ċavetta **ġdida** bl-Ingliż tkun tradotta f'kull lokalità — markatur `__MISSING__:` jiġi rrifjutat | Iva | -| `check-translation-ratio` | Il-proporzjon ta' traduzzjoni reali għal kull lokalità (valuri identiċi għall-Ingliż / placeholders / entrati neqsin barra mill-allowlist) ma jridx jaqbeż `config/quality/i18n-translation-baseline.json` + il-marġni | **Konsultattiv** | +| Skript | Jivvalida | Imblokkanti | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `check-ui-keys-coverage` (inline) | Il-kopertura taċ-ċwievet i18n tal-UI hija ≥ 65% | Iva | +| `check-ui-value-drift` (inline) | **Valur** bl-Ingliż li jkun inkiteb mill-ġdid ma jħalli l-ebda traduzzjoni skaduta warajh | Iva | +| `check-new-key-coverage` (inline) | Ċavetta **ġdida** bl-Ingliż hija tradotta f’kull lokalità — markatur `__MISSING__:` jiġi rrifjutat | Iva | +| `check-translation-ratio` | Il-proporzjon ta’ traduzzjonijiet reali għal kull lokalità (identiku għall-Ingliż / placeholder / entrati neqsin barra l-lista permessa) ma jridx jaqbeż `config/quality/i18n-translation-baseline.json` + marġni | **Konsultattiv** | -Jeħtieġ `fetch-depth: 0` — il-gate tal-value-drift tqabbel `en.json` mal-merge base. +Jeħtieġ `fetch-depth: 0` — il-gate tad-drift tal-valuri jqabbel `en.json` mal-bażi tal-merge. -#### `check-ui-value-drift` — gate kontra traduzzjonijiet skaduti +#### `check-ui-value-drift` — gate għat-traduzzjonijiet skaduti -Taqbad dik ir-rigressjoni i18n waħda li l-gates l-oħra strutturalment ma jistgħux jaraw: valur bl-Ingliż -jinkiteb mill-ġdid u t-traduzzjonijiet derivati mill-Ingliż _preċedenti_ jibqgħu hemm, biex b'hekk -utenti li ma jużawx l-Ingliż jibqgħu jaqraw test miktub b'kunfidenza iżda li issa huwa żbaljat. +Jaqbad l-unika rigressjoni i18n li l-gates l-oħra strutturalment ma jistgħux jaraw: valur bl-Ingliż +jinkiteb mill-ġdid u t-traduzzjonijiet derivati mill-Ingliż _preċedenti_ jibqgħu hemm, sabiex +utenti li ma jużawx l-Ingliż jibqgħu jaqraw test miktub b’ċertezza, iżda li issa huwa żbaljat. -Dan fil-fatt ġie rilaxxat. `oauthModal.googleOAuthWarning` inkiteb mill-ġdid meta żdied l-għajnun tal-login -Antigravity (#5203); **39 minn 43 lokalità** żammew test li jgħid lill-operaturi biex "jikkopjaw il-URL -sħiħ u jwaħħluh hawn taħt" — fluss li ma jistax jitlesta għal dak il-fornitur. Dan baqa' -ma ndunawx bih sa #8463 minħabba li: +Dan tassew wasal fil-verżjoni rilaxxata. `oauthModal.googleOAuthWarning` inkiteb mill-ġdid meta ġie introdott +l-assistent tal-login Antigravity (#5203); **39 minn 43 lokalità** żammew test li kien jgħid lill-operaturi biex “jikkopjaw +il-URL sħiħ u jwaħħluh hawn taħt” — fluss li ma jistax jitlesta għal dak il-fornitur. Dan baqa’ +ma ġiex innutat sa #8463 għaliex: -- `sync-ui-keys` jimla biss ċwievet li huma **assenti**, qatt dawk li huma **skaduti**; +- `sync-ui-keys` jimla biss ċwievet li huma **assenti**, u qatt dawk li huma **skaduti**; - `check-ui-keys-coverage` jgħodd il-_preżenza_ taċ-ċwievet, għalhekk traduzzjoni skaduta titqies bħala koperta; -- `check-translation-drift` isegwi l-kopji mera tad-dokumentazzjoni `docs/i18n//**.md` — - qatt ma jaqra `src/i18n/messages/*.json`. Ilu jimblokka fix-xogħol `docs-sync-strict` mir-risinkronizzazzjoni - 2026-09: editja dokument ewlieni → `npm run i18n:run -- --files=` (fil-livell tat-taqsima, irħis). +- `check-translation-drift` isegwi l-kopji paralleli tad-dokumentazzjoni `docs/i18n//**.md` — + qatt ma jaqra `src/i18n/messages/*.json`. Imblokkanti fil-kompitu `docs-sync-strict` mir-risinkronizzazzjoni + 2026-09 ’l hawn: editja dokument ewlieni → `npm run i18n:run -- --files=` (fil-livell tat-taqsima, irħis). -**Konxju tad-diff, mhux ibbażat fuq baseline.** Dan iqabbel `en.json` fil-merge base mas-siġra -tax-xogħol; għal kull key li l-valur tagħha bl-Ingliż inbidel, kwalunkwe locale li għadu fih -traduzzjoni mhux mittiefsa jitqies skadut. Dan intenzjonalment **jiffriża d-dejn eżistenti minn qabel** — diff -ma jistax juri minn liema verżjoni antika bl-Ingliż oriġinat traduzzjoni li ilha hemm, għalhekk il-kontroll jiġġudika -biss dak li tmiss il-bidla attwali. L-alternattiva (baseline ta’ hash għal kull key) tkun teħtieġ -fajl iġġenerat ta’ ~600 KB, 3× akbar mill-akbar baseline eżistenti, li jinbidel ma’ kull PR tal-i18n. +**Sensittiv għad-diff, mhux sostnut minn baseline.** Iqabbel `en.json` fil-bażi tal-merge mas- +siġra tax-xogħol; għal kull ċavetta li l-valur Ingliż tagħha nbidel, kwalunkwe locale li għadu +fih traduzzjoni mhux mittiefsa jkun skadut. Dan intenzjonalment **jiffriża d-dejn eżistenti minn qabel** — diff +ma jistax jiżvela minn liema Ingliż antik ġiet traduzzjoni li ilha teżisti, għalhekk il-kontroll jiġġudika +biss dak li tmiss il-bidla attwali. L-alternattiva (baseline ta' hash għal kull ċavetta) tkun teħtieġ +fajl iġġenerat ta' ~600 KB, 3× akbar mill-akbar baseline eżistenti, li jinbidel ma' kull PR tal-i18n. Hemm żewġ modi kif tissodisfah: 1. aġġorna t-traduzzjonijiet affettwati, jew -2. issettjahom għal `__MISSING__:` — waqt l-eżekuzzjoni mbagħad jiġi pprovdut l-Ingliż ikkoreġut - (`src/i18n/request.ts::deepMergeFallback`, #7258) u l-key tidħol fil-kju għat-traduzzjoni. +2. issettjahom għal `__MISSING__:` — waqt it-tħaddim imbagħad jiġi pprovdut l-Ingliż ikkoreġut + (`src/i18n/request.ts::deepMergeFallback`, #7258) u ċ-ċavetta tidħol fil-kju għat-traduzzjoni. -Jekk it-**tifsira** tas-string inbidlet, ippreferi li **tibdel l-isem tal-key**: key ġdida ma tistax tiret -traduzzjoni skaduta. Dan huwa l-mudell li ntuża minn #8463. +Jekk it-**tifsira** tas-sekwenza nbidlet, ippreferi li **tibdel isem iċ-ċavetta**: ċavetta ġdida ma tistax tiret +traduzzjoni skaduta. Dan huwa l-mudell li ntuża f'#8463. ```bash npm run i18n:check-value-drift # strett (dak li jħaddem is-CI) @@ -231,48 +231,48 @@ npm run i18n:check-value-drift:warn # rapport biss BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Joħroġ b’0 u `SKIP reason=base-unresolved` meta l-katalgu bażi ma jkunx jista’ jinqara (shallow -clone mingħajr il-base ref), bl-istess imġiba bħal `check-openapi-breaking`. +Joħroġ b'0 u `SKIP reason=base-unresolved` meta l-katalgu bażi ma jkunx jista' jinqara (klonu baxx +mingħajr ir-ref bażi), bl-istess imġiba bħal `check-openapi-breaking`. -### Job: `i18n` +### Kompitu: `i18n` -Matriċi sħiħa ta’ validazzjoni tal-i18n (job wieħed għal kull locale). Il-job kollu huwa konsultattiv. +Matriċi sħiħa ta' validazzjoni tal-i18n (kompitu wieħed għal kull locale). Il-kompitu kollu huwa konsultattiv. -| Script | Jivvalida | Imblukkar | -| ------------------------------- | -------------------------------------------- | ------------------------------------------------------------- | -| `validate_translation.py quick` | Kompletezza tat-traduzzjoni għal kull locale | **Konsultattiv** (`continue-on-error: true` fuq il-job kollu) | +| Skritt | Jivvalida | Imblukkar | +| ------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------- | +| `validate_translation.py quick` | Il-kompletezza tat-traduzzjoni għal kull locale | **Konsultattiv** (`continue-on-error: true` fuq il-kompitu kollu) | -### Job: `pr-test-policy` +### Kompitu: `pr-test-policy` Jitħaddem fuq pull requests biss. -| Script | Jivvalida | Imblukkar | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | PRs li jibdlu l-code tal-produzzjoni f’`src/`, `open-sse/`, `electron/`, jew `bin/` iridu jinkludu jew jaġġornaw it-tests (Regola Stretta #8) | Iva | -| `check:test-masking` | Il-fajls tat-test mibdula ma jnaqqsux l-għadd nett ta’ asserts u ma jżidux tautoloġiji `assert.ok(true)` | Iva | -| `check:pr-evidence` | Il-body tal-PR jiċċita evidenza minn tests/VPS għall-bidla (jawtomatizza r-Regola Stretta #18 billi jfittex fit-test tal-PR — fraġli, ara l-Backlog) | Iva | +| Skritt | Jivvalida | Imblukkar | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | +| `check:pr-test-policy` | PRs li jibdlu l-kodiċi tal-produzzjoni f'`src/`, `open-sse/`, `electron/`, jew `bin/` iridu jinkludu jew jaġġornaw it-testijiet (Regola Stretta #8) | Iva | +| `check:test-masking` | Il-fajls tat-test mibdula ma jnaqqsux l-għadd nett ta' asserzjonijiet u ma jżidux tautoloġiji `assert.ok(true)` | Iva | +| `check:pr-evidence` | Il-kontenut tal-PR jiċċita evidenza minn testijiet/VPS għall-bidla (jawtomatizza r-Regola Stretta #18 billi jfittex fit-test tal-PR — fraġli, ara l-Backlog) | Iva | -### Job: `test-vitest` +### Kompitu: `test-vitest` -Jitħaddem wara `build`. Jimblokka l-merge jekk ifalli. +Jitħaddem wara `build`. Jibblokka l-merge jekk ifalli. -| Suite | Jivvalida | Imblukkar | -| ---------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Server MCP (110 għodod), autoCombo, cache — runner ta’ vitest | Iva | -| `test:vitest:ui` | Tests tal-komponenti tal-UI — runner ta’ vitest | **Jimblokka** — il-fallimenti eżistenti minn qabel huma espliċitament esklużi f’`vitest.config.ts`; fallimenti ġodda jfallu l-job | +| Ġabra | Jivvalida | Imblukkar | +| ---------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Server MCP (110 għodod), autoCombo, cache — runner ta' vitest | Iva | +| `test:vitest:ui` | Testijiet tal-komponenti tal-UI — runner ta' vitest | **Jimblokka** — il-fallimenti eżistenti minn qabel huma esklużi b'mod espliċitu f'`vitest.config.ts`; fallimenti ġodda jġiegħlu lill-kompitu jfalli | -### Workflows ta’ kull lejl (skedati, konsultattivi) +### Flussi tax-xogħol ta' kull lejl (skedati, konsultattivi) Dawn jitħaddmu fuq skeda cron (u `workflow_dispatch`), qatt fuq PRs. Kollha huma konsultattivi. -| Workflow | Jivvalida | Imblukkar | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | Tests ibbażati fuq proprjetajiet ta’ fast-check b’seed każwali + għadd għoli ta’ runs | **Konsultattiv** | -| `nightly-resilience` | Kontroll tat-tkabbir tal-heap, injezzjoni ta’ ħsarat kaotiċi, load/soak b’k6 | **Konsultattiv** | -| `nightly-llm-security` | Protezzjoni kontra l-injezzjoni ta’ promptfoo (modalità ta’ mblukkar) + probes ta’ garak (jinqabżu mingħajr provider secret) | **Konsultattiv** | -| `nightly-schemathesis` | Fuzzing tal-kuntratt OpenAPI (schemathesis) kontra OmniRoute live permezz ta’ `docs/openapi.yaml` — jikxef ksur tal-ispeċifikazzjoni / 500s mhux immaniġġjati (Fażi 8 B.4) | **Konsultattiv** | -| `nightly-mutation` | Punteġġ tal-mutation testing ta’ Stryker fuq il-linja rapida tal-unit tests — mutants li jibqgħu ħajjin jikxfu asserts dgħajfa | **Konsultattiv** | -| `nightly-compat` | Matriċi tal-kompatibbiltà tal-engine ta’ Node fil-firxiet appoġġjati ta’ `engines.node` | **Konsultattiv** | +| Fluss tax-xogħol | Jivvalida | Imblukkar | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `nightly-property` | Testijiet tal-proprjetajiet ta' fast-check b'seed każwali + għadd għoli ta' eżekuzzjonijiet | **Konsultattiv** | +| `nightly-resilience` | Kontroll tat-tkabbir tal-heap, injezzjoni ta' ħsarat kaotiċi, tagħbija/test fit-tul b'k6 | **Konsultattiv** | +| `nightly-llm-security` | Protezzjoni ta' promptfoo kontra l-injezzjoni (modalità ta' mblukkar) + sondi ta' garak (jinqabżu mingħajr sigriet tal-fornitur) | **Konsultattiv** | +| `nightly-schemathesis` | Fuzzing tal-kuntratt OpenAPI (schemathesis) kontra OmniRoute attiv bl-użu ta' `docs/openapi.yaml` — jesponi ksur tal-ispeċifikazzjoni / żbalji 500 mhux immaniġġjati (Fażi 8 B.4) | **Konsultattiv** | +| `nightly-mutation` | Punteġġ tal-ittestjar b'mutazzjonijiet ta' Stryker fuq il-korsija rapida tat-testijiet tal-unità — mutanti li jibqgħu ħajjin jesponu asserzjonijiet dgħajfa | **Konsultattiv** | +| `nightly-compat` | Matriċi ta' kompatibbiltà tal-magna Node fil-firxiet appoġġjati ta' `engines.node` | **Konsultattiv** | --- @@ -539,43 +539,43 @@ ma jitgħabbiex awtomatikament (int tagħżel u tivverifika l-bridge MCP↔LSP); --- -## Lista Pendenti tar-Razzjonalizzazzjoni (rieżami tal-ROI — Fażi 9 Mewġa 3) +## Lista Pendenti ta’ Razzjonalizzazzjoni (rieżami tar-ROI — Fażi 9 Mewġa 3) -Dan l-inventarju ġie rrikonċiljat ma’ `ci.yml` fis-2026-06-17 (il-verżjoni preċedenti kienet ħalliet barra +Dan l-inventarju ġie rrikonċiljat ma’ `ci.yml` fis-2026-06-17 (il-verżjoni preċedenti ħalliet barra `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Rieżami tal-ROI tas-sett irrikonċiljat +`check:codeql-ratchet`, `check:pr-evidence`). Rieżami tar-ROI tas-sett irrikonċiljat identifika l-kandidati għar-razzjonalizzazzjoni li ġejjin. **L-għaqdiet huma bidliet mekkaniċi fis-CI; -il-qlib/tneħħijiet huma deċiżjonijiet ta’ politika riżervati għall-operatur.** Għadu ma ġie applikat -xejn minn dan ta’ hawn taħt. +il-qlib/tneħħija huma deċiżjonijiet ta’ politika riżervati għall-operatur.** Xejn minn dan ta’ hawn taħt +għadu ma ġie applikat. -**Mhux dokumentati hawn fuq ukoll** (konsultattivi, sinjal baxx): il-job `docs-lint` +**Ukoll mhux iddokumentati hawn fuq** (konsultattivi, sinjal dgħajjef): il-job `docs-lint` (markdownlint + Vale, il-job kollu b’`continue-on-error`) u l-workflows awtonomi tal-iskanners `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` jinsab f’ `quality-baseline.json` iżda mhuwiex marbut ma’ ratchet li jimblokka f’`ci.yml` — il-metrika -bħalissa hija iżolata. +bħalissa hija orfni. -### Għaqda / deduplikazzjoni (mekkaniċi, riskju aktar baxx) +### Għaqda / deduplikazzjoni (mekkanika, riskju aktar baxx) -Kull kandidat ġie vvalidat mal-istat attiv tal-gates fis-2026-06-17 (ivverifika anki jekk tafda); -diversi għaqdiet “ovvji” irriżultaw li jaħbu dejn u **mhumiex** sostituzzjonijiet diretti u nodfa. +Kull kandidat ġie vvalidat kontra l-istat attiv tal-kontrolli fis-2026-06-17 (afda-iżda-vverifika); +diversi għaqdiet “ovvji” irriżultaw li jaħbu dejn u **mhumiex** sostituzzjonijiet diretti nodfa. -- **`check:docs-sync` jitħaddem darbtejn** — waħdu fil-job `lint` u għal darb’oħra fi ħdan `check:docs-all` (`docs-sync-strict`) u l-hook pre-commit ta’ husky. ✅ **LEST** — l-invokazzjoni awtonoma f’`lint` tneħħiet. -- **Skennjar tas-CVE** — ❌ **MHIX għaqda nadifa.** `audit:deps` ifalli b’mod assolut għal kwalunkwe CVE ta’ severità għolja/kritika; `check:vuln-ratchet` (osv) ifalli biss fuq _rigressjoni_ meta mqabbel mal-baseline (bħalissa 1 MODERATE). Semantika differenti — it-tneħħija ta’ `audit:deps` telimina l-gate assolut għal severità għolja/kritika. Żomm it-tnejn. -- **Sejbien taċ-ċikli** — ❌ **MHIX għaqda nadifa.** `check:circular-deps` (dpdm) jirrapporta **91 ċiklu** (għalhekk huwa konsultattiv); ma jistax jiġi promoss għal wieħed li jimblokka qabel ma jiġu solvuti, u għandu kamp ta’ applikazzjoni usa’ minn dak ta’ `check:cycles`, li huwa aħdar u kkurat. Żomm `check:cycles` bħala wieħed li jimblokka; ir-riżoluzzjoni tal-91 ċiklu tad-dpdm hija punt separat fil-lista pendenti. -- **Kumplessità** — ✅ **LEST** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): passaġġ wieħed tal-ESLint, b’għadd skont ruleId sabiex il-baselines ta’ cyclomatic+max-lines u cognitive jibqgħu indipendenti; il-kontrolli individwali `check:complexity` / `check:cognitive-complexity` jibqgħu għal `--update` lokali. -- **Kontra l-alluċinazzjoni ta’ `/api`** — ✅ **LEST** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): inventarju wieħed tas-sistema tal-fajls għal `src/app/api`, filwaqt li openapi-routes + docs-symbols xorta jirrapportaw indipendentement; il-kontrolli individwali jibqgħu għall-eżekuzzjonijiet lokali. -- **`check:node-runtime` jitħaddem fi 11-il job** — ⚠️ **ROI baxx.** Kull wieħed juża runner separat u l-kontroll jieħu <1s; l-iffrankar totali huwa ta’ madwar 10s, bil-konsegwenza li tintilef protezzjoni rħisa għal kull job. Ma jiswiex it-tibdil. -- **`typecheck:noimplicit:core` fil-lint tas-CI** — ✅ **tneħħa mill-job lint** (kien konsultattiv b’`continue-on-error`); is-superfiċje tat-tipi li timblokka hija `typecheck:core` + `check:type-coverage`. L-iskript lokali nżamm. +- **`check:docs-sync` jitħaddem darbtejn** — b’mod awtonomu fil-job `lint` u għal darb’oħra ġewwa `check:docs-all` (`docs-sync-strict`) u l-hook pre-commit ta’ husky. ✅ **LEST** — l-invokazzjoni awtonoma f’`lint` tneħħiet. +- **Skennjar tas-CVE** — ❌ **MHUX għaqda nadifa.** `audit:deps` ifalli b’mod strett għal kwalunkwe CVE ta’ severità għolja/kritika; `check:vuln-ratchet` (osv) ifalli biss meta jkun hemm _rigressjoni_ meta mqabbel mal-linja bażi (bħalissa 1 MODERATE). Semantika differenti — it-tneħħija ta’ `audit:deps` telimina l-kontroll assolut għal severità għolja/kritika. Żomm it-tnejn. +- **Individwazzjoni taċ-ċikli** — ✅ **LEST** (#15159 G-01/G-02). It-test l-antik hawn sejjaħ lil `check:cycles` il-kontroll “aħdar u kkurat” u ġġustifika li jibqa’ jimblokka minħabba li `check:circular-deps` (dpdm) irrapporta 91 ċiklu. Dak l-aħdar kien **aħdar falz**: `check:cycles` skennja 5 sottodirettorji (450 fajl), qabbel biss `import|export … from` statiċi, u warrab kull speċifikatur `@/` u `@omniroute/open-sse/`, għalhekk ma setax jara ċ-ċikli ta’ importazzjoni dinamika + aliases li kienu jiddominaw ir-repo. Irranġat: il-kontroll issa jgħaddi minn `src` + `open-sse` (5023 fajl), jiġbor speċifikaturi mill-AST ta’ TypeScript (għalhekk `import("…")` jingħadd filwaqt li `typeof import("…")` f’pożizzjoni ta’ tip ma jingħaddx), u jirriżolvi `paths` ta’ tsconfig. Isib **14-il** ċiklu, mhux 0. Minħabba li 14-il ċiklu preeżistenti ma jistgħux jiġu rranġati fi PR ta’ kontroll, `check:cycles` issa huwa **ratchet** (`--ratchet`, limitu massimu `metrics.cycles.value = 14` f’`quality-baseline.json`, `direction: down`) — jimblokka kwalunkwe _rigressjoni_ u l-għadd jista’ jonqos biss. Is-CI jħaddem `npm run check:cycles:ratchet`. It-tnaqqis progressiv jimxi ma’ **A-01**. `check:circular-deps` (dpdm) jibqa’ konsultattiv bħala t-tieni opinjoni usa’. +- **Kumplessità** — ✅ **LEST** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): passaġġ wieħed ta’ ESLint, jgħodd skont ruleId sabiex il-linji bażi ta’ cyclomatic+max-lines u dawk konjittivi jibqgħu indipendenti; `check:complexity` / `check:cognitive-complexity` individwali jibqgħu għal `--update` lokali. +- **Kontra l-alluċinazzjoni ta’ `/api`** — ✅ **LEST** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): inventarju wieħed tas-sistema tal-fajls ta’ `src/app/api`, openapi-routes + docs-symbols xorta jirrapportaw b’mod indipendenti; il-kontrolli individwali jibqgħu għal eżekuzzjonijiet lokali. +- **`check:node-runtime` jitħaddem fi 11-il job** — ⚠️ **ROI baxx.** Kull wieħed juża runner separat u l-kontroll jieħu <1s; iffrankar totali ta’ ~10s, bir-riskju li jintilef kontroll irħis għal kull job. Ma jiswiex it-taqlib. +- **`typecheck:noimplicit:core` fil-lint tas-CI** — ✅ **tneħħa mill-job lint** (kien konsultattiv b’`continue-on-error`); is-superfiċje tal-kontroll tat-tipi li timblokka hija `typecheck:core` + `check:type-coverage`. L-iskript lokali nżamm. ### Aqleb / iddeċiedi (politika tal-operatur) -- `check:openapi-security-tiers` (konsultattiv) — ❌ **MA JISTAX JINQALEB B’MOD NADIF.** Joħroġ b’0 iżda jwissi li diversi routes ta’ `traffic-inspector` taħt `LOCAL_ONLY_API_PREFIXES` m’għandhomx l-annotazzjoni `x-loopback-only: true`. L-infurzar tiegħu jeħtieġ li l-ewwel jiżdiedu dawk l-annotazzjonijiet ma’ `openapi.yaml`. -- `typecheck:noimplicit:core` (konsultattiv) — fil-biċċa l-kbira huwa kopert mir-ratchet `check:type-coverage` li jimblokka. Aqilbu għal ratchet jew neħħi t-tieni passaġġ `tsc` żejjed. -- `test:vitest:ui` (issa **jimblokka**) — il-fallimenti li kienu jeżistu minn qabel huma esklużi espliċitament f’`vitest.config.ts` b’kummenti ta’ traċċar `// #8618`; fallimenti ġodda jfallu l-job. -- `check:secrets` (gitleaks, ratchet li jimblokka u ffriżat fuq 3 pożittivi foloz dokumentati) — żid it-3 mal-allowlist biex tilħaq 0, jew baxxih għal wieħed konsultattiv. Jikkoinċidi mal-iskannjar nattiv tas-segreti ta’ GitHub + `check:public-creds`. -- `check:pr-evidence` (jimblokka, ifittex b’grep fit-test tal-korp tal-PR) — riskju għoli ta’ pożittivi foloz; jekk jitneħħa jdgħajjef l-infurzar tar-Regola Stretta #18, għalhekk din hija deċiżjoni ta’ politika ġenwina. -- `semgrep` (awtonomu u konsultattiv) — jikkoinċidi ma’ CodeQL għall-familji tal-OWASP; qabbad il-baseline tiegħu ma’ ratchet jew neħħih. +- `check:openapi-security-tiers` (konsultattiv) — ❌ **MA JISTAX jinqaleb b’mod nadif.** Joħroġ b’0 iżda jwissi li diversi routes ta’ `traffic-inspector` taħt `LOCAL_ONLY_API_PREFIXES` m’għandhomx l-annotazzjoni `x-loopback-only: true`. Biex jiġi infurzat, l-ewwel iridu jiżdiedu dawk l-annotazzjonijiet ma’ `openapi.yaml`. +- `typecheck:noimplicit:core` (konsultattiv) — fil-biċċa l-kbira sostitwit mir-ratchet `check:type-coverage` li jimblokka. Aqilbu għal ratchet jew neħħi t-tieni passaġġ żejjed ta’ `tsc`. +- `test:vitest:ui` (issa **jimblokka**) — fallimenti preeżistenti huma espliċitament esklużi f’`vitest.config.ts` b’kummenti ta’ traċċar `// #8618`; fallimenti ġodda jfallu l-job. +- `check:secrets` (gitleaks, ratchet li jimblokka ffriżat fuq 3 pożittivi foloz iddokumentati) — żid it-3 mal-lista ta’ permessi biex tilħaq 0, jew iddegradah għal konsultattiv. Jikkoinċidi mal-iskannjar nattiv tas-sigrieti ta’ GitHub + `check:public-creds`. +- `check:pr-evidence` (jimblokka, ifittex fit-test tal-korp tal-PR) — riskju għoli ta’ pożittivi foloz; jekk jitneħħa jdgħajjef l-infurzar ta’ Hard Rule #18, għalhekk din hija deċiżjoni ġenwina ta’ politika. +- `semgrep` (awtonomu konsultattiv) — jikkoinċidi ma’ CodeQL għall-familji OWASP; qabbad il-linja bażi tiegħu ma’ ratchet jew neħħih. --- diff --git a/docs/i18n/mt/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/mt/docs/compression/COMPRESSION_GUIDE.md index c40247fde43a..dec4a4ed0633 100644 --- a/docs/i18n/mt/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/mt/docs/compression/COMPRESSION_GUIDE.md @@ -468,8 +468,7 @@ l-istruzzjoni lokalizzata minflok dik bl-Ingliż. F’body b’`messages`, bypass tal-kontenut (`shouldBypassCavemanOutputMode()` f’ `open-sse/services/compression/outputMode.ts`) jiċċekkja l-aħħar tliet messaġġi u jaqbeż l-istili għat-turn kollu meta dawn jaqblu mal-kliem ewlieni tiegħu dwar is-sigurtà, azzjonijiet irriversibbli, -kjarifika, jew sensittività għall-ordni. Il-bypass jaħdem skont kif ikun issettjat it-toggle -**Auto-Clarity Bypass** tad-dashboard (`cavemanOutputMode.autoClarity`). +kjarifika, jew sensittività għall-ordni. Il-bypass jaħdem waqt li t-toggle **Auto-Clarity Bypass** tad-dashboard (`cavemanOutputMode.autoClarity`) tkun mixgħula, li huwa l-impostazzjoni prefissata; bit-toggle mitfi, l-istili magħżula japplikaw ukoll f'dawk it-turni. Meta l-bypass iħalli t-turn jgħaddi, `placeSystemInstruction()` (fl-istess fajl), li qatt ma joħloq `messages[0]` ġdid, ipoġġi l-blokka fl-ewwel wieħed minn dawn li jsib: diff --git a/docs/i18n/mt/docs/reference/ENVIRONMENT.md b/docs/i18n/mt/docs/reference/ENVIRONMENT.md index c497074474ce..463fbf739437 100644 --- a/docs/i18n/mt/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/mt/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Sigrieti Meħtieġa -Dawn **iridu** jiġu ssettjati qabel l-ewwel tħaddim. Mingħajrhom, l-applikazzjoni jew tirrifjuta li tibda jew taħdem b’valuri awtomatiċi mhux siguri. - -| Varjabbli | Meħtieġa | Valur Awtomatiku | Fajl tas-Sors | Deskrizzjoni | -| ---------------------------- | ------------------------- | ---------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Iva** | _(xejn)_ | `src/lib/auth` | Jiffirma/jivverifika l-cookies kollha tas-sessjoni tad-dashboard (JWT). Iġġenerah b’`openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Iva** | _(xejn)_ | `src/lib/db/apiKeys.ts` | Ċavetta tal-kriptaġġ AES għall-valuri taċ-ċwievet API maħżuna f’SQLite. Iġġeneraha b’`openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Iva** | `CHANGEME` | Skript tal-bootstrap | Jissettja l-password inizjali tal-amministratur tad-dashboard (jaqbel mal-valur awtomatiku ta’ `.env.example` — inżamm ovvjament mhux sigur biex iġiegħlek tibdlu). **Ibdlu qabel l-ewwel użu.** Wara li tidħol, ibdlu permezz ta’ Dashboard → Settings → Security. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Iva** (fil-produzzjoni) | _(mhux issettjat)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sigriet kondiviż għall-bridge intern tal-WebSocket ta’ Codex Responses. Jawtentika t-talbiet tal-bridge bejn ir-relay WS ta’ Electron/browser u OmniRoute. ⚠️ **MEĦTIEĠ fil-produzzjoni — meta ma jkunx issettjat, it-talbiet kollha tal-bridge WS jiġu rrifjutati.** Iġġenerah b’`openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Le | _(SHA ta’ git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identifikatur espliċitu għall-invalidazzjoni tal-cache tas-service worker għall-qoxra PWA (#11779); l-ewwel fil-katina tar-riżoluzzjoni. | -| `SOURCE_VERSION` | Le | _(mhux issettjat)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | It-tieni fil-katina — jiġi ssettjat mill-bennejja PaaS (eż. stil Heroku) bħala l-commit ippubblikat. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Le | _(derivat)_ | `src/shared/components/PwaRegister.tsx` | Valur pubbliku ta’ waqt il-bini li l-klijent juża biex jirreġistra `/sw.js?v=…`; jiġi derivat mit-tnejn ta’ hawn fuq, imbagħad mix-SHA ta’ git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Le (awtomatiku) | _(awtomatiku ma’ kull boot)_ | `src/server/authz/policies/management.ts` | Sigriet għal kull proċess li jagħti prova li t-timbru fdat tal-IP tal-peer ġie mis-server HTTP ta’ OmniRoute stess (`scripts/dev/peer-stamp.mjs`). Il-middleware authz jafda l-lokalità tat-talba (restrizzjoni għal loopback/LAN tar-rotot LOCAL_ONLY) biss meta t-timbru jkun fih dan it-token. Jiġi ġġenerat awtomatikament ma’ kull boot — ħallih mhux issettjat; issettjah b’mod fiss biss għal konfigurazzjonijiet b’diversi proċessi li jridu jikkondividu t-timbru. | -| `OMNIROUTE_TRUSTED_PROXIES` | Le | _(mhux issettjat)_ | `scripts/dev/peer-stamp.mjs` | IPs jew meded CIDR separati b’virgoli ta’ reverse proxies li l-lista ta’ permess/projbizzjoni tal-IP tista’ tafda l-`X-Forwarded-For` / `X-Real-IP` tagħhom, flimkien mal-loopback, l-indirizzi tan-network privat u l-edges ta’ Cloudflare, li huma dejjem fdati. Meħtieġ biss għal proxy fuq kwalunkwe indirizz pubbliku ieħor; inkella jkun l-indirizz tal-proxy stess li jiġi evalwat mill-filtru. | +Sigrieti mhux issettjati mmarkati b’**Iva** jiġu ġġenerati awtomatikament fl-ewwel tħaddim (`JWT_SECRET`, `API_KEY_SECRET`) u jiġu salvati f’`server.env` fid-direttorju tad-dejta — issejjahom espliċitament meta dak id-direttorju huwa temporanju. L-eċċezzjoni hija `OMNIROUTE_WS_BRIDGE_SECRET`: mhux jiġi salvat f’`server.env` — l-entrypoints tas-server joħolqu valur temporanju għal kull boot li jista’ juża biss il-bridge in-process, għalhekk il-klijenti tal-bridge esterni jkollhom bżonn issejjhulu espliċitament. + +| Varjabbli | Meħtieġa | Valur Awtomatiku | Fajl tas-Sors | Deskrizzjoni | +| ---------------------------- | ------------------------- | ---------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Iva** | _(xejn)_ | `src/lib/auth` | Jiffirma/jivverifika l-cookies kollha tas-sessjoni tad-dashboard (JWT). Iġġenerah b’`openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Iva** | _(xejn)_ | `src/lib/db/apiKeys.ts` | Ċavetta tal-kriptaġġ AES għall-valuri taċ-ċwievet API maħżuna f’SQLite. Iġġeneraha b’`openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Le | _(mhux issettjat)_ | `src/lib/auth/managementPassword.ts` | Jissettja l-password tad-dashboard mat-tħaddim sakemm ma jkun hemm l-ebda password salvata; jiġi injorat ladarba password tkun salvata. L-installazzjoniet npm u dawk mis-sors jikkopjaw `.env.example` għal `.env`, għalhekk jibdew bil-placeholder pubblikament magħruf tagħha `CHANGEME`: ibdlu qabel l-ewwel boot, jew issettja `INITIAL_PASSWORD=` (vojt) f’dak il-`.env` biex toħloq il-password fil-wizard tal-onboarding fl-ewwel żjara, kif jagħmel valur mhux issettjat (id-default tal-imaġni Docker). Ibdel password salvata f’Dashboard → Settings → Security, jew esegwi `omniroute reset-password` (`node bin/reset-password.mjs` f’checkout tas-sors) b’`DATA_DIR` issettjat għad-direttorju tad-dejta tas-server. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Iva** (fil-produzzjoni) | _(mhux issettjat)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Sigriet kondiviż għall-bridge intern tal-WebSocket ta’ Codex Responses. Jawtentika t-talbiet tal-bridge bejn ir-relay WS ta’ Electron/browser u OmniRoute. ⚠️ **MEĦTIEĠ fil-produzzjoni — meta ma jkunx issettjat, it-talbiet kollha tal-bridge WS jiġu rrifjutati.** Iġġenerah b’`openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Le | _(SHA ta’ git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identifikatur espliċitu għall-invalidazzjoni tal-cache tas-service worker għall-qoxra PWA (#11779); l-ewwel fil-katina tar-riżoluzzjoni. | +| `SOURCE_VERSION` | Le | _(mhux issettjat)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | It-tieni fil-katina — jiġi ssettjat mill-bennejja PaaS (eż. stil Heroku) bħala l-commit ippubblikat. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Le | _(derivat)_ | `src/shared/components/PwaRegister.tsx` | Valur pubbliku ta’ waqt il-bini li l-klijent juża biex jirreġistra `/sw.js?v=…`; jiġi derivat mit-tnejn ta’ hawn fuq, imbagħad mix-SHA ta’ git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Le (awtomatiku) | _(awtomatiku ma’ kull boot)_ | `src/server/authz/policies/management.ts` | Sigriet għal kull proċess li jagħti prova li t-timbru fdat tal-IP tal-peer ġie mis-server HTTP ta’ OmniRoute stess (`scripts/dev/peer-stamp.mjs`). Il-middleware authz jafda l-lokalità tat-talba (restrizzjoni għal loopback/LAN tar-rotot LOCAL_ONLY) biss meta t-timbru jkun fih dan it-token. Jiġi ġġenerat awtomatikament ma’ kull boot — ħallih mhux issettjat; issettjah b’mod fiss biss għal konfigurazzjonijiet b’diversi proċessi li jridu jikkondividu t-timbru. | +| `OMNIROUTE_TRUSTED_PROXIES` | Le | _(mhux issettjat)_ | `scripts/dev/peer-stamp.mjs` | IPs jew meded CIDR separati b’virgoli ta’ reverse proxies li l-lista ta’ permess/projbizzjoni tal-IP tista’ tafda l-`X-Forwarded-For` / `X-Real-IP` tagħhom, flimkien mal-loopback, l-indirizzi tan-network privat u l-edges ta’ Cloudflare, li huma dejjem fdati. Meħtieġ biss għal proxy fuq kwalunkwe indirizz pubbliku ieħor; inkella jkun l-indirizz tal-proxy stess li jiġi evalwat mill-filtru. | ### Kmandi tal-Ġenerazzjoni @@ -216,38 +216,38 @@ OmniRoute juża **SQLite** (permezz ta’ `better-sqlite3`) għall-persistenza k ## 4. Sigurtà u Awtentikazzjoni -| Varjabbli | Valur Default | Fajl tas-Sors | Deskrizzjoni | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt ikkombinat ma’ identifikaturi tal-hardware għall-ħolqien tal-marki tas-swaba’ tal-magni. Ibdlu għal kull deployment biex tiżgura l-iżolament. | -| `OMNIROUTE_CLI_SALT` | _(mhux issettjat = salt każwali għal kull installazzjoni, ippersistit f’`/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC biex jiġi derivat it-token lokali tal-awtentikazzjoni tas-CLI. L-issettjar ta’ dan il-valur idawwar it-tokens kollha tas-CLI fuq il-magna u dejjem jieħu prijorità fuq is-salt ippersistit. Ara `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Jissettja l-flag `Secure` fuq il-cookies tas-sessjoni. **Irid ikun `true`** meta jitħaddem wara HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware tal-API | Meta jkun `true`, it-talbiet proxy kollha għal `/v1/*` iridu jinkludu API key valida. **Dan il-flag ma jikkontrollax `GET /v1/models`**, li minflok isegwi l-pożizzjoni tal-login tad-dashboard (`requireAuthForModels`) — għalhekk `401` minn `/v1/models` MA jfissirx li l-inferenza hija protetta. Ara `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Jippermetti li jintwerew il-valuri sħaħ tal-API keys fl-interfaċċa tad-Dashboard. Jista’ jiġi kkonfigurat mill-Feature Flags tad-Dashboard; joħloq riskju għas-sigurtà fuq istanzi kondiviżi. | -| `NO_LOG_API_KEY_IDS` | _(vojt)_ | `src/lib/compliance/index.ts` | IDs tal-API keys separati b’virgoli li jaqbżu l-illoggjar tat-talbiet (konformità mal-GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(mhux issettjat = bla limitu)_ | `src/shared/utils/apiKeyPolicy.ts` | Baġit ta’ riżerva ta’ talbiet kuljum applikat għall-API keys li l-kolonna `rate_limits` tagħhom hija null. Mhux issettjat jew vojt: ebda limitu impliċitu (#2289, #11017). `0` huwa l-istess (bla limitu). Numru sħiħ pożittiv N jippermetti N/kuljum, 5N/ġimgħa, 20N/xahar. Valuri mhux vojta iżda fformattjati ħażin jerġgħu jużaw it-twieqi preċedenti ta’ 1000/kuljum, 5000/ġimgħa, 20000/xahar. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Id-daqs massimu permess tal-body tat-talba. Jirrifjuta payloads li jaqbżu dan il-limitu. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Il-bodies effettivi tat-talbiet li jilħqu jew jaqbżu dan il-limitu jieħdu l-lease atomika u lokali għall-proċess għall-ammissjoni ta’ tagħbijiet tqal qabel il-parsing tal-JSON (mogħdija BYTE, inkluż `POST /v1/responses`). L-istess mekkaniżmu ta’ ħruġ meta jkun hemm marġni operattiv tajjeb ta’ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) bħal fil-każ ta’ struttura tqila; xorta limitat minn `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) sabiex [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ma jerġax iseħħ. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limitu massimu strett tar-rotta taċ-chat infurzat skont il-bytes moqrija matul l-inġestjoni limitata, inklużi talbiet b’`Content-Length` nieqes, invalidu jew mhux veritier; l-eċċess jirċievi `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(mhux issettjat — ebda limitu fuq l-għadd ta’ talbiet)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** dan il-limitu preċedenti fuq l-GĦADD ta’ talbiet issa japplika biss meta jiġi ssettjat espliċitament. Meta jitħalla mhux issettjat (l-imġiba predefinita), l-ammissjoni ta’ chats tqal minflok tkun ikkontrollata minn `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — baġit ta’ BYTES derivat awtomatikament u kkalkolat skont il-limitu reali tal-memorja tal-proċess fi **proċess wieħed** (heap V8 wieħed). Żewġ talbiet `/v1/responses` sovrapposti ta’ madwar 750k token iwaqqgħu heaps ta’ madwar 12 Gi (#7849) — twissija dwar **baġit tal-memorja**, mhux massimu strett tal-prodott ta’ 2. Proċess f’saħħtu (heap taħt il-proporzjon ta’ tnaqqis tat-tagħbija) JISTA’ jaċċetta aktar talbiet twal konkorrenti għal `/v1/responses` permezz ta’ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Għexieren ta’ klijenti SSE fit-tul (40–50) jiddependu mill-heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, mhux minn “massimu ta’ 2”. Jekk iżżidu bl-addoċċ biex “tużaw il-host”, terġgħu tintroduċu #7849. Immultiplikaw il-**heaps** b’**N `DATA_DIR`s indipendenti** (#11024); qatt `replicas>1` fuq fajl SQLite wieħed. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivat awtomatikament)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** valur alternattiv għall-baġit ta’ bytes tal-inġestjoni derivat awtomatikament (25% tal-iżgħar limitu tal-memorja bejn V8 u cgroup, diviż b’amplifikazzjoni tranżitorja ta’ 8x). Il-valuri derivati u espliċiti jiġu limitati għal 8 MiB–2 GiB. Korp akbar mill-baġit effettiv ifalli minnufih b’`413 body_exceeds_budget`; il-kontenzjoni bejn korpi li kull wieħed minnhom jista’ jiġi pproċessat tibqa’ tista’ terġa’ tiġi ppruvata b’`503`. 40–50 klijent SSE konkorrenti fit-tul jiddependu minn dan il-baġit + il-heap, mhux minn “massimu ta’ 2” strett. Aqraw `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` f’`/api/monitoring/health` qabel tirfinaw il-konfigurazzjoni. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Proporzjon ta’ tnaqqis tat-tagħbija minħabba pressjoni fuq il-heap (`heapUsed / heap_size_limit`) għall-ammissjoni tqila skont il-BYTES u l-ISTRUTTURA (#10183, #10268, #10437). Talba tqila konkorrenti li taqbeż `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` titnaqqsilha t-tagħbija bil-`503` li jista’ jerġa’ jiġi ppruvat biss meta l-heap ikun UKOLL f’dan il-proporzjon jew ogħla minnu; fuq heap f’saħħtu tiġi aċċettata minflok permezz tal-marġni ta’ kapaċità f’saħħtu. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valur predefinit `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Kapaċità addizzjonali limitata għall-perkors veloċi tal-heap f’saħħtu (#10437), kemm għall-ISTRUTTURA kif ukoll għall-BYTES (`admitChatRequest`, inklużi korpi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Mingħajr dan il-limitu, kull talba waqt okkupazzjoni iżda b’heap f’saħħtu kienet taqbeż l-ammissjoni mingħajr ebda limitu massimu. Ladarba jkun hemm dan l-għadd ta’ kirjiet konkorrenti attivi permezz tal-perkors tal-heap f’saħħtu, talbiet okkupati ulterjuri jgħaddu għall-ISTESS perkors ta’ stennija limitata/tnaqqis tat-tagħbija li jintuża taħt pressjoni reali fuq il-heap. `0` jiddiżattiva kompletament dan il-qbiż. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Għadd ta’ messaġġi li jikklassifika talba taċ-chat bħala tqila anki meta l-korp tagħha jkun taħt il-limitu tal-bytes. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Għadd ta’ għodod li jikklassifika talba taċ-chat bħala tqila anki meta l-korp tagħha jkun taħt il-limitu tal-bytes. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Stima konservattiva tat-token ibbażata fuq id-daqs tas-sekwenza li tikklassifika talba bħala tqila; din hija approssimazzjoni tal-kost tal-ammissjoni, mhux it-tokenizzazzjoni tal-fornitur għall-kontijiet. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (diżattivat) | `src/shared/middleware/chatBodyAdmission.ts` | Limitu fakultattiv u attivat b’mod espliċitu għall-istorja taċ-chat. Diżattivat awtomatikament: għadd ta’ messaġġi huwa politika tal-iskjerament, mhux proprjetà universali ta’ talba, u jekk jiġi impost limitu hawn, konverżazzjonijiet jiġu miċħuda b’`413` terminali qabel ma l-pipeline tal-kompressjoni jkun jista’ jagħmilhom proċessabbli. It-tkabbir tal-heap huwa limitat minn `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` u mit-tnaqqis tat-tagħbija minħabba pressjoni fuq il-heap. Issettjaw valur pożittiv fuq skjeramenti b’memorja limitata li jeħtieġu limitu strett; l-eċċess imbagħad jirċievi `413` strutturat li jindika li hija meħtieġa l-kompattazzjoni. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limitu strett għal risposta upstream mingħajr streaming li tinżamm kollha kemm hi fil-memorja. Lil hinn minn dan il-limitu, il-qarrej upstream jiġi kkanċellat u t-talba tfalli minnufih minflok tkompli tkabbar sekwenza mingħajr limitu sakemm il-heap jiġi eżawrit. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | L-għadd massimu ta’ bytes fuq il-wajer mgħoddija mill-headers tar-rispons upstream. Meta jinqabeż il-limitu, il-headers bi prijorità aktar baxxa (eż., `x-codex-*`, `x-oai-request-id`) jitneħħew biex jinżammu l-limiti komuni tal-headers ta’ reverse proxy. Issettjah għal valur ogħla biex tgħaddi aktar metadata upstream, iżda b’headers tar-rispons akbar. | -| `CORS_ORIGIN` | _(mhux issettjat)_ | `src/server/cors/origins.ts` | Lista permessa CORS legacy għal oriġini waħda. Għal deployments ġodda, ippreferi `CORS_ALLOWED_ORIGINS`. CORS jintuża biss għal klijenti tal-API tal-browser minn oriġini differenti; minflok, il-kitbiet awtentikati tad-dashboard jużaw talbiet mill-istess oriġini flimkien ma’ protezzjoni CSRF marbuta mas-sessjoni. | -| `CORS_ALLOWED_ORIGINS` | _(mhux issettjat)_ | `src/server/cors/origins.ts` | Lista permessa CORS separata b’virgoli. Ma jintbagħat l-ebda wildcard sakemm `CORS_ALLOW_ALL=true` ma jkunx ikkonfigurat b’mod espliċitu. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Soluzzjoni ta’ emerġenza għall-iżvilupp biss biex tirrepeti kwalunkwe `Origin` tal-browser. Tattivahiex fuq deployments kondiviżi jew tal-produzzjoni. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Imblokka sejħiet lill-fornituri mmirati lejn firxiet ta’ indirizzi IP privati/loopback/link-local. Iddiżattivah biss f’ambjenti tat-test iżolati. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Ippermetti URLs tal-fornituri li jippuntaw lejn networks privati/lokali (localhost, 192.168.x.x, 10.x.x.x, eċċ.). **MEĦTIEĠ għal fornituri self-hosted** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Meta jkun `false`, id-dashboard jirrifjuta l-validazzjoni ta’ URLs lokali. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Ippermetti ż-żieda/validazzjoni ta’ fornituri fuq indirizzi lokali/privati (127.0.0.1, localhost, LAN, firxiet privati) — limitat għall-mogħdija tal-validazzjoni tal-fornitur. **Default `true`** (lokali l-ewwel); issettjah għal `false` biex tinforza imblukkar strett ta’ indirizzi pubbliċi biss. L-endpoints tal-metadata tal-cloud (169.254.169.254, metadata.google.internal) jibqgħu mblukkati fi kwalunkwe każ. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ħalli r-rotot `/v1/audio/*` (traskrizzjonijiet, diskors, traduzzjonijiet) jużaw node ta’ fornitur kompatibbli ma’ OpenAI ospitat barra minn localhost. Diżattivat b’mod default — ir-routing tal-awdjo lejn host remot ibiddel l-identità tal-egress u jrid ikun deċiżjoni espliċita tal-operatur. In-nodes loopback/privati (localhost, 127.0.0.1, 172.16-31.x) huma dejjem permessi u mhux affettwati. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ħalli `POST /v1/rerank` (u l-pass ta’ rerank loopback tal-magna tal-memorja) juża node ta’ fornitur kompatibbli ma’ OpenAI ospitat barra minn localhost — kaxxa fuq il-LAN jew peer ta’ Tailscale li jħaddem TEI, Infinity, vLLM, eċċ. Diżattivat b’mod default — ir-routing lejn host remot ibiddel l-identità tal-egress u jrid ikun deċiżjoni espliċita tal-operatur. In-nodes loopback (localhost, 127.0.0.1, 172.16-31.x) huma dejjem permessi u mhux affettwati. In-nodes remoti jridu wkoll jgħaddu mill-politika tal-URL outbound tal-fornitur (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); qatt ma jsir routing lejn hosts tal-metadata tal-cloud. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Meta OIDC ikun attivat, iddiżattiva l-login bil-password sabiex l-utenti jkunu jistgħu jawtentikaw biss permezz ta’ Single Sign-On b’OIDC. L-alias sempliċi `OIDC_DISABLE_PASSWORD_LOGIN` huwa aċċettat ukoll; id-Dashboard Feature Flag bl-istess key jieħu preċedenza. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias sempliċi ta’ `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Varjabbli | Valur Default | Fajl tas-Sors | Deskrizzjoni | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt ikkombinat ma’ identifikaturi tal-hardware għall-ħolqien tal-marki tas-swaba’ tal-magni. Ibdlu għal kull deployment biex tiżgura l-iżolament. | +| `OMNIROUTE_CLI_SALT` | _(mhux issettjat = salt każwali għal kull installazzjoni, ippersistit f’`/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC biex jiġi derivat it-token lokali tal-awtentikazzjoni tas-CLI. L-issettjar ta’ dan il-valur idawwar it-tokens kollha tas-CLI fuq il-magna u dejjem jieħu prijorità fuq is-salt ippersistit. Ara `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Jissettja l-flag `Secure` fuq il-cookies tas-sessjoni. **Irid ikun `true`** meta jitħaddem wara HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware tal-API | Meta jkun `true`, it-talbiet proxy kollha għal `/v1/*` iridu jinkludu API key valida. **Dan il-flag ma jikkontrollax `GET /v1/models`**, li minflok isegwi l-pożizzjoni tal-login tad-dashboard (`requireAuthForModels`) — għalhekk `401` minn `/v1/models` MA jfissirx li l-inferenza hija protetta. Ara `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Jippermetti li jintwerew il-valuri sħaħ tal-API keys fl-interfaċċa tad-Dashboard. Jista’ jiġi kkonfigurat mill-Feature Flags tad-Dashboard; joħloq riskju għas-sigurtà fuq istanzi kondiviżi. | +| `NO_LOG_API_KEY_IDS` | _(vojt)_ | `src/lib/compliance/index.ts` | IDs tal-API keys separati b’virgoli li jaqbżu l-illoggjar tat-talbiet (konformità mal-GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(mhux issettjat = bla limitu)_ | `src/shared/utils/apiKeyPolicy.ts` | Baġit ta’ riżerva ta’ talbiet kuljum applikat għall-API keys li l-kolonna `rate_limits` tagħhom hija null. Mhux issettjat jew vojt: ebda limitu impliċitu (#2289, #11017). `0` huwa l-istess (bla limitu). Numru sħiħ pożittiv N jippermetti N/kuljum, 5N/ġimgħa, 20N/xahar. Valuri mhux vojta iżda fformattjati ħażin jerġgħu jużaw it-twieqi preċedenti ta’ 1000/kuljum, 5000/ġimgħa, 20000/xahar. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Id-daqs massimu permess tal-body tat-talba. Jirrifjuta payloads li jaqbżu dan il-limitu. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Il-bodies effettivi tat-talbiet li jilħqu jew jaqbżu dan il-limitu jieħdu l-lease atomika u lokali għall-proċess għall-ammissjoni ta’ tagħbijiet tqal qabel il-parsing tal-JSON (mogħdija BYTE, inkluż `POST /v1/responses`). L-istess mekkaniżmu ta’ ħruġ meta jkun hemm marġni operattiv tajjeb ta’ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) bħal fil-każ ta’ struttura tqila; xorta limitat minn `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) sabiex [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ma jerġax iseħħ. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limitu massimu strett tar-rotta taċ-chat infurzat skont il-bytes moqrija matul l-inġestjoni limitata, inklużi talbiet b’`Content-Length` nieqes, invalidu jew mhux veritier; l-eċċess jirċievi `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(mhux issettjat — ebda limitu fuq l-għadd ta’ talbiet)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** dan il-limitu preċedenti fuq l-GĦADD ta’ talbiet issa japplika biss meta jiġi ssettjat espliċitament. Meta jitħalla mhux issettjat (l-imġiba predefinita), l-ammissjoni ta’ chats tqal minflok tkun ikkontrollata minn `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — baġit ta’ BYTES derivat awtomatikament u kkalkolat skont il-limitu reali tal-memorja tal-proċess fi **proċess wieħed** (heap V8 wieħed). Żewġ talbiet `/v1/responses` sovrapposti ta’ madwar 750k token iwaqqgħu heaps ta’ madwar 12 Gi (#7849) — twissija dwar **baġit tal-memorja**, mhux massimu strett tal-prodott ta’ 2. Proċess f’saħħtu (heap taħt il-proporzjon ta’ tnaqqis tat-tagħbija) JISTA’ jaċċetta aktar talbiet twal konkorrenti għal `/v1/responses` permezz ta’ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Għexieren ta’ klijenti SSE fit-tul (40–50) jiddependu mill-heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, mhux minn “massimu ta’ 2”. Jekk iżżidu bl-addoċċ biex “tużaw il-host”, terġgħu tintroduċu #7849. Immultiplikaw il-**heaps** b’**N `DATA_DIR`s indipendenti** (#11024); qatt `replicas>1` fuq fajl SQLite wieħed. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivat awtomatikament)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** valur alternattiv għall-baġit ta’ bytes tal-inġestjoni derivat awtomatikament (25% tal-iżgħar limitu tal-memorja bejn V8 u cgroup, diviż b’amplifikazzjoni tranżitorja ta’ 8x). Il-valuri derivati u espliċiti jiġu limitati għal 8 MiB–2 GiB. Korp akbar mill-baġit effettiv ifalli minnufih b’`413 body_exceeds_budget`; il-kontenzjoni bejn korpi li kull wieħed minnhom jista’ jiġi pproċessat tibqa’ tista’ terġa’ tiġi ppruvata b’`503`. 40–50 klijent SSE konkorrenti fit-tul jiddependu minn dan il-baġit + il-heap, mhux minn “massimu ta’ 2” strett. Aqraw `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` f’`/api/monitoring/health` qabel tirfinaw il-konfigurazzjoni. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Proporzjon ta’ tnaqqis tat-tagħbija minħabba pressjoni fuq il-heap (`heapUsed / heap_size_limit`) għall-ammissjoni tqila skont il-BYTES u l-ISTRUTTURA (#10183, #10268, #10437). Talba tqila konkorrenti li taqbeż `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` titnaqqsilha t-tagħbija bil-`503` li jista’ jerġa’ jiġi ppruvat biss meta l-heap ikun UKOLL f’dan il-proporzjon jew ogħla minnu; fuq heap f’saħħtu tiġi aċċettata minflok permezz tal-marġni ta’ kapaċità f’saħħtu. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (valur predefinit `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Kapaċità addizzjonali limitata għall-perkors veloċi tal-heap f’saħħtu (#10437), kemm għall-ISTRUTTURA kif ukoll għall-BYTES (`admitChatRequest`, inklużi korpi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Mingħajr dan il-limitu, kull talba waqt okkupazzjoni iżda b’heap f’saħħtu kienet taqbeż l-ammissjoni mingħajr ebda limitu massimu. Ladarba jkun hemm dan l-għadd ta’ kirjiet konkorrenti attivi permezz tal-perkors tal-heap f’saħħtu, talbiet okkupati ulterjuri jgħaddu għall-ISTESS perkors ta’ stennija limitata/tnaqqis tat-tagħbija li jintuża taħt pressjoni reali fuq il-heap. `0` jiddiżattiva kompletament dan il-qbiż. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Għadd ta’ messaġġi li jikklassifika talba taċ-chat bħala tqila anki meta l-korp tagħha jkun taħt il-limitu tal-bytes. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Għadd ta’ għodod li jikklassifika talba taċ-chat bħala tqila anki meta l-korp tagħha jkun taħt il-limitu tal-bytes. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Stima konservattiva tat-token ibbażata fuq id-daqs tas-sekwenza li tikklassifika talba bħala tqila; din hija approssimazzjoni tal-kost tal-ammissjoni, mhux it-tokenizzazzjoni tal-fornitur għall-kontijiet. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (diżattivat) | `src/shared/middleware/chatBodyAdmission.ts` | Limitu fakultattiv u attivat b’mod espliċitu għall-istorja taċ-chat. Diżattivat awtomatikament: għadd ta’ messaġġi huwa politika tal-iskjerament, mhux proprjetà universali ta’ talba, u jekk jiġi impost limitu hawn, konverżazzjonijiet jiġu miċħuda b’`413` terminali qabel ma l-pipeline tal-kompressjoni jkun jista’ jagħmilhom proċessabbli. It-tkabbir tal-heap huwa limitat minn `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` u mit-tnaqqis tat-tagħbija minħabba pressjoni fuq il-heap. Issettjaw valur pożittiv fuq skjeramenti b’memorja limitata li jeħtieġu limitu strett; l-eċċess imbagħad jirċievi `413` strutturat li jindika li hija meħtieġa l-kompattazzjoni. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limitu strett għal risposta upstream mingħajr streaming li tinżamm kollha kemm hi fil-memorja. Lil hinn minn dan il-limitu, il-qarrej upstream jiġi kkanċellat u t-talba tfalli minnufih minflok tkompli tkabbar sekwenza mingħajr limitu sakemm il-heap jiġi eżawrit. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | L-għadd massimu ta’ bytes fuq il-wajer mgħoddija mill-headers tar-rispons upstream. Meta jinqabeż il-limitu, il-headers bi prijorità aktar baxxa (eż., `x-codex-*`, `x-oai-request-id`) jitneħħew biex jinżammu l-limiti komuni tal-headers ta’ reverse proxy. Issettjah għal valur ogħla biex tgħaddi aktar metadata upstream, iżda b’headers tar-rispons akbar. | +| `CORS_ORIGIN` | _(mhux issettjat)_ | `src/server/cors/origins.ts` | Lista permessa CORS legacy għal oriġini waħda. Għal deployments ġodda, ippreferi `CORS_ALLOWED_ORIGINS`. CORS jintuża biss għal klijenti tal-API tal-browser minn oriġini differenti; minflok, il-kitbiet awtentikati tad-dashboard jużaw talbiet mill-istess oriġini flimkien ma’ protezzjoni CSRF marbuta mas-sessjoni. | +| `CORS_ALLOWED_ORIGINS` | _(mhux issettjat)_ | `src/server/cors/origins.ts` | Lista permessa CORS separata b’virgoli. Ma jintbagħat l-ebda wildcard sakemm `CORS_ALLOW_ALL=true` ma jkunx ikkonfigurat b’mod espliċitu. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Soluzzjoni ta’ emerġenza għall-iżvilupp biss biex tirrepeti kwalunkwe `Origin` tal-browser. Tattivahiex fuq deployments kondiviżi jew tal-produzzjoni. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias antik (legacy). It-toggle tad-dashboard tiegħu (override fil-DB) jinqara qabel din il-valur tal-ambjent; `false`, `0`, `no` jew `off` f’waħda minnhom itfi l-kontrolli tal-host kif jagħmel `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, anke meta t-toggle tad-dashboard ta’ dak il-flag huwa salvat mitfi; valuri oħrajn jħallu l-gwardja lit-tnejn flags t’hawn isfel. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Itfi l-kontrolli tal-host tal-gwardja tal-URLs outbound, inkluż l-imblukkar tal-metadata tal-cloud (169.254.0.0/16 kollha, metadata.google.internal), fuq il-mogħdijiet tal-fornituri li jsegwu l-modalità tal-gwardja: validazzjoni tal-URL, skoperta tal-mudelli, URLs bażi tan-nodes tal-fornituri (inklużi nodes tar-rerank remoti) u t-test tal-proxy-fallback, fost mogħdijiet outbound oħra assoċjati mal-fornituri bħal ġbir ta’ stampi/medja remoti. Jippermetti wkoll miri ta’ webhooks privati, ħlief endpoints tal-metadata tal-cloud/link-local, li jibqgħu mblukkati anke ma’ dan il-flag attivat. It-talbiet ta’ chat mibgħuta permezz ta’ `BaseExecutor.execute()` jżommu l-kontroll tagħhom tal-metadata tal-cloud. Il-fornituri lokali jaħdmu bid-defaults: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (default `true`) diġà jippermetti URLs lokali u LAN fuq il-mogħdijiet tal-validazzjoni, l-iskoperta u n-nodes tal-fornituri; it-test tal-proxy-fallback u l-miri ta’ webhooks privati jikkonsultaw biss dan il-flag u jiblokkaw hosts lokali/LAN sakemm ikun mitfi. Jagħmel override għal `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Valur salvat mit-toggle tad-dashboard għandu preċedenza fuq din il-varjabbli. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ippermetti URLs tal-fornituri fuq indirizzi lokali/privati (127.0.0.1, localhost, LAN, firxiet privati) għal validazzjoni tal-fornitur, skoperta tal-mudelli, chat, u l-mogħdijiet outbound l-oħra assoċjati mal-fornituri li tmexxi l-modalità tal-gwardja. **Default `true`** (lokali l-ewwel): il-gwardja mbagħad tibblokka l-endpoints tal-metadata tal-cloud — 169.254.0.0/16 kollha flimkien mal-hostnames tal-metadata magħrufa (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Issettja `false` għal-modalità pubblika biss, li tibblokka wkoll hosts privati u loopback. Il-gwardja tikkontrolla l-hostname jew il-literal IP kif miktub, qabel kwalunkwe lookup DNS; f’ modalità pubblika biss, il-wrapper safe-fetch jirriżolvi wkoll l-hostname u jirrifjuta kwalunkwe risposta privata. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` itfi dawn il-kontrolli fuq il-mogħdijiet elenkati fir-ringiela tiegħu. Il-fornituri lokali built-in, bħal LM Studio, Ollama, vLLM, Llamafile u Triton, jaqbgħu l-kontrolli kollha tal-host għal validazzjoni taċ-ċwievet u chat. Valur salvat mit-toggle tad-dashboard għandu preċedenza fuq din il-varjabbli. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ħalli r-rotot `/v1/audio/*` (traskrizzjonijiet, diskors, traduzzjonijiet) jużaw node ta’ fornitur kompatibbli ma’ OpenAI ospitat barra minn localhost. Diżattivat b’mod default — ir-routing tal-awdjo lejn host remot ibiddel l-identità tal-egress u jrid ikun deċiżjoni espliċita tal-operatur. In-nodes loopback/privati (localhost, 127.0.0.1, 172.16-31.x) huma dejjem permessi u mhux affettwati. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ħalli `POST /v1/rerank` (u l-pass ta’ rerank loopback tal-magna tal-memorja) juża node ta’ fornitur kompatibbli ma’ OpenAI ospitat barra minn localhost — kaxxa fuq il-LAN jew peer ta’ Tailscale li jħaddem TEI, Infinity, vLLM, eċċ. Diżattivat b’mod default — ir-routing lejn host remot ibiddel l-identità tal-egress u jrid ikun deċiżjoni espliċita tal-operatur. In-nodes loopback (localhost, 127.0.0.1, 172.16-31.x) huma dejjem permessi u mhux affettwati. L-URL bażi ta’ node remot jrid wkoll jgħaddu mill-politika tal-URL outbound tal-fornitur (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Meta OIDC ikun attivat, iddiżattiva l-login bil-password sabiex l-utenti jkunu jistgħu jawtentikaw biss permezz ta’ Single Sign-On b’OIDC. L-alias sempliċi `OIDC_DISABLE_PASSWORD_LOGIN` huwa aċċettat ukoll; id-Dashboard Feature Flag bl-istess key jieħu preċedenza. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias sempliċi ta’ `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Lista ta’ Kontroll għat-Tisħiħ tas-Sigurtà diff --git a/docs/i18n/my/CONTRIBUTING.md b/docs/i18n/my/CONTRIBUTING.md index 59ea4143e425..2cc6ab67a873 100644 --- a/docs/i18n/my/CONTRIBUTING.md +++ b/docs/i18n/my/CONTRIBUTING.md @@ -402,20 +402,20 @@ Model definition များကို `open-sse/config/providerRegistry.ts` တ ## Pull Request စစ်ဆေးရန်စာရင်း -- [ ] Tests များ အောင်မြင်သည် (`npm test`) -- [ ] Linting အောင်မြင်သည် (`npm run lint`) -- [ ] Build အောင်မြင်သည် (`npm run build`) -- [ ] Public function နှင့် interface အသစ်များအတွက် TypeScript types များ ထည့်သွင်းထားသည် -- [ ] Hardcoded secrets သို့မဟုတ် fallback values များ မရှိပါ -- [ ] အများသုံး upstream credentials များကို literal များအဖြစ် လုံးဝမထည့်ဘဲ `resolvePublicCred()` မှတစ်ဆင့် ထည့်သွင်းထားသည် ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ကို ကြည့်ပါ) -- [ ] Error response များသည် `buildErrorBody()` / `sanitizeErrorMessage()` ကို ဖြတ်သန်းသည် — response body များထဲတွင် raw stack trace များ မပါဝင်ပါ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ကို ကြည့်ပါ) -- [ ] Shell commands (`exec` / `spawn`) များသည် runtime values များကို string interpolation ဖြင့် မဟုတ်ဘဲ `env` မှတစ်ဆင့် ပေးပို့သည် -- [ ] Input အားလုံးကို Zod schemas များဖြင့် အတည်ပြုစစ်ဆေးထားသည် -- [ ] အသုံးပြုသူနှင့် သက်ဆိုင်သော ပြောင်းလဲမှုများအတွက် Changelog **fragment** ကို `changelog.d/{features|fixes|maintenance}/-.md` အောက်တွင် ထည့်သွင်းထားသည် ([`changelog.d/README.md`](./changelog.d/README.md) ကို ကြည့်ပါ) — `CHANGELOG.md` ကို တိုက်ရိုက် **မပြင်ဆင်ပါနှင့်**။ Fragment များကို release လုပ်ချိန်တွင် စုစည်းပြီး PR များအကြား မည်သည့်အခါမျှ conflict မဖြစ်ပါ -- [ ] Documentation ကို အပ်ဒိတ်လုပ်ထားသည် (သက်ဆိုင်ပါက) -- [ ] CodeQL / Secret-Scanning alert အသစ်များ မဖွင့်ထားပါ၊ သို့မဟုတ် alert တစ်ခုချင်းစီကို သက်ဆိုင်ရာ `docs/security/` doc ကို ကိုးကားသည့် နည်းပညာဆိုင်ရာ အကြောင်းပြချက်ဖြင့် ပယ်ဖျက်ထားသည် -- [ ] Child process များကို စတင်သည့် routes (`/api/mcp/`, `/api/cli-tools/runtime/`) ကို `src/server/authz/routeGuard.ts` ရှိ `isLocalOnlyPath()` အဖြစ် အမျိုးအစားသတ်မှတ်ထားသည် — [တင်းကျပ်သော စည်းမျဉ်း #15](docs/security/ROUTE_GUARD_TIERS.md) ကို ကြည့်ပါ -- [ ] Commit message များတွင် `Co-Authored-By` trailers မပါဝင်ပါ — commit များသည် repository ပိုင်ရှင်၏ Git identity တစ်ခုတည်းအောက်တွင်သာ ပေါ်ရမည် (တင်းကျပ်သော စည်းမျဉ်း #16) +- [ ] စမ်းသပ်မှုများ အောင်မြင်သည် (`npm test`) +- [ ] Linting စစ်ဆေးမှုများ အောင်မြင်သည် (`npm run lint`) +- [ ] Build ပြုလုပ်မှု အောင်မြင်သည် (`npm run build`) +- [ ] အများသုံး function နှင့် interface အသစ်များအတွက် TypeScript type များ ထည့်သွင်းထားသည် +- [ ] Hardcode လုပ်ထားသော secret သို့မဟုတ် fallback value များ မရှိပါ +- [ ] အများသုံး upstream credential များကို literal များအဖြစ် မသုံးဘဲ `resolvePublicCred()` မှတစ်ဆင့် ထည့်သွင်းထားသည် ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ကို ကြည့်ပါ) +- [ ] Error response များကို `buildErrorBody()` / `sanitizeErrorMessage()` မှတစ်ဆင့် ဖြတ်သန်းစေထားပြီး response body များတွင် မူရင်း stack trace များ မပါဝင်ပါ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ကို ကြည့်ပါ) +- [ ] Shell command များ (`exec` / `spawn`) သည် runtime value များကို string interpolation မှတစ်ဆင့် မဟုတ်ဘဲ `env` မှတစ်ဆင့် ပေးပို့သည် +- [ ] Input အားလုံးကို Zod schema များဖြင့် စစ်ဆေးအတည်ပြုထားသည် +- [ ] အသုံးပြုသူအပေါ် သက်ရောက်သည့် ပြောင်းလဲမှုများအတွက် Changelog **fragment** ကို `changelog.d/{features|fixes|maintenance}/-.md` အောက်တွင် ထည့်သွင်းထားသည် ([`changelog.d/README.md`](./changelog.d/README.md) ကို ကြည့်ပါ) — `CHANGELOG.md` ကို တိုက်ရိုက် **မပြင်ဆင်ပါနှင့်**၊ fragment များကို release ပြုလုပ်ချိန်တွင် စုစည်းပြီး PR များအကြား conflict မဖြစ်စေပါ +- [ ] Documentation ကို လိုအပ်ပါက အပ်ဒိတ်လုပ်ထားသည် +- [ ] CodeQL / Secret-Scanning alert အသစ်များ မပေါ်လာပါ၊ သို့မဟုတ် alert တစ်ခုချင်းစီကို သက်ဆိုင်ရာ `docs/security/` doc အား ကိုးကားထားသည့် နည်းပညာဆိုင်ရာ အကြောင်းပြချက်ဖြင့် ပယ်ချထားသည် +- [ ] Child process များကို စတင်သည့် route များ (`/api/mcp/`, `/api/cli-tools/runtime/`) ကို `src/server/authz/routeGuard.ts` ရှိ `isLocalOnlyPath()` အဖြစ် သတ်မှတ်ထားသည် — [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md) ကို ကြည့်ပါ +- [ ] Commit message များတွင် AI/bot `Co-authored-by` trailer များ မပါဝင်ပါ (Hard Rule #16) — ပြန်လည်အသုံးပြုထားသော အလုပ်၏ လူသားပူးပေါင်းဆောင်ရွက်သူများကို စံသတ်မှတ်ထားသည့် `Co-authored-by: Name ` trailer များဖြင့် အသိအမှတ်ပြုထားသည် --- diff --git a/docs/i18n/my/docs/architecture/QUALITY_GATES.md b/docs/i18n/my/docs/architecture/QUALITY_GATES.md index ab0685f726c0..e58eaf4744a5 100644 --- a/docs/i18n/my/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/my/docs/architecture/QUALITY_GATES.md @@ -15,267 +15,263 @@ ratchet baseline သို့မဟုတ် pass/fail policy ကို အသ --- -## Gate စာရင်းနှင့် လုပ်ဆောင်မှုပရိုဖိုင်များ +## Gate စာရင်းနှင့် လုပ်ဆောင်မှု ပရိုဖိုင်များ ### Candidate လက်ခံခြင်း -CI နှင့် Quality Gates workflow တစ်ခုစီသည် တည်ငြိမ်သော ဆုံးဖြတ်ချက်တစ်ခုဖြစ်သည့် `Gate / CI` နှင့် -`Gate / Quality` ကို ထုတ်ပေးသည်။ ၎င်းတို့၏ version သတ်မှတ်ထားသော လက်ခံရေးမူဝါဒတွင် upstream job တိုင်းကို -မဖြစ်မနေလိုအပ်သော သို့မဟုတ် အကြံပြုသဘောသက်သက်ဖြစ်သော job အဖြစ် စာရင်းပြုစုထားသည်။ သက်ဆိုင်သော မဖြစ်မနေလိုအပ်သည့် job သည် အောင်မြင်ရမည်ဖြစ်ပြီး ပျောက်ဆုံးနေခြင်း၊ -ပယ်ဖျက်ထားခြင်း၊ ကျော်သွားခြင်း၊ စောင့်ဆိုင်းနေခြင်းနှင့် မသိရသေးသော ရလဒ်များဖြင့် PASS ကို သတ်မှတ်နိုင်ခြင်းမရှိပါ။ မှန်ကန်သော -docs-only သို့မဟုတ် catalog-only အမျိုးအစားခွဲခြားမှုသည် code lane တစ်ခုကို မသက်ဆိုင်စေနိုင်သည်။ -draft PR တစ်ခုကို လက်ခံထားသော candidate အဖြစ် မသတ်မှတ်ပါ။ `hotfix` label တစ်ခုက အထောက်အထားလိုအပ်ချက်ကို ပယ်ဖျက်မပေးပါ။ - -Workflow နှစ်ခုစလုံးသည် PR များ၊ main/release branch များသို့ push လုပ်ခြင်းများ၊ manual dispatch နှင့် -merge-group event များကို အကျုံးဝင်စေသည်။ Push၊ dispatch နှင့် merge-group တို့သည် ရွေးချယ်မှုအားလုံးကို အပြည့်အစုံ လုပ်ဆောင်သည်။ Fork များနှင့် -merge group များသည် ပုံမှန်အားဖြင့် self-hosted runner ကို ရွေးချယ်မည့် job များအတွက် hosted runner များကို အသုံးပြုသည်။ -ဖြန့်ချိအသုံးချခြင်းမပြုမီ hosted capacity လုံလောက်မှုရှိကြောင်း အတည်ပြုရမည်။ - -JSON receipt တစ်ခုစီသည် checkout လုပ်ထားသော SHA၊ workflow run နှင့် attempt ကို သတ်မှတ်ဖော်ပြသည်။ -CLI သည် checkout/event SHA မကိုက်ညီမှုကို လက်မခံပါ။ Workflow test များသည် policy membership ကို -verdict job ၏ `needs` စာရင်းနှင့် ချိတ်ဆက်ထားသောကြောင့် lane အသစ်တစ်ခု သို့မဟုတ် ဖယ်ရှားထားသော lane တစ်ခုသည် အသိပေးမှုမရှိဘဲ ပျောက်ကွယ်သွားနိုင်မည်မဟုတ်ပါ။ -Receipt များသည် မိမိတို့၏ workflow ကိုသာ အကျုံးဝင်ပြီး publication၊ deployment သို့မဟုတ် +CI နှင့် Quality Gates workflow တစ်ခုစီသည် တည်ငြိမ်သော ဆုံးဖြတ်ချက်တစ်ခုကို ထုတ်ပေးသည်- `Gate / CI` နှင့် +`Gate / Quality`။ ၎င်းတို့၏ version သတ်မှတ်ထားသော လက်ခံမှုမူဝါဒတွင် upstream job တစ်ခုစီကို +လိုအပ်သော သို့မဟုတ် အကြံပြုသဘောသာဖြစ်သော job အဖြစ် စာရင်းပြုထားသည်။ သက်ဆိုင်သည့် လိုအပ်သော job သည် အောင်မြင်ရမည်- ပျောက်ဆုံးနေခြင်း၊ +ပယ်ဖျက်ထားခြင်း၊ ကျော်သွားခြင်း၊ ဆိုင်းငံ့နေခြင်းနှင့် မသိရှိသော ရလဒ်များဖြင့် PASS ဖြစ်ကြောင်း အတည်မပြုနိုင်ပါ။ မှန်ကန်သော +docs-only သို့မဟုတ် catalog-only အမျိုးအစားခွဲခြားမှုကြောင့် code lane တစ်ခုကို သက်ဆိုင်မှုမရှိဟု သတ်မှတ်နိုင်သည်၊ +draft PR တစ်ခုကို လက်ခံထားသော candidate အဖြစ် မသတ်မှတ်ပါ။ `hotfix` label တစ်ခုကြောင့် အထောက်အထားလိုအပ်ချက်ကို ကင်းလွတ်ခွင့်မပြုပါ။ + +Workflow နှစ်ခုစလုံးသည် PR များ၊ main/release branch များသို့ push များ၊ manual dispatch နှင့် +merge-group event များကို အကျုံးဝင်သည်။ Push၊ dispatch နှင့် merge-group တို့သည် ရွေးချယ်မှုအပြည့်အစုံကို လုပ်ဆောင်သည်။ Fork များနှင့် +merge group များသည် ပုံမှန်အားဖြင့် self-hosted runner များကို ရွေးချယ်မည့် job များအတွက် hosted runner များကို အသုံးပြုသည်၊ +rollout မပြုလုပ်မီ လုံလောက်သော hosted capacity ရှိကြောင်း အတည်ပြုရမည်။ + +JSON receipt တစ်ခုစီသည် checkout ပြုလုပ်ထားသော SHA၊ workflow run နှင့် attempt ကို သတ်မှတ်ဖော်ပြသည်။ +CLI သည် checkout/event SHA မကိုက်ညီမှုကို ငြင်းပယ်သည်။ Workflow test များသည် policy membership ကို +verdict job ၏ `needs` စာရင်းနှင့် ချိတ်ဆက်ထားသဖြင့် အသစ်ထည့်ထားသော သို့မဟုတ် ဖယ်ရှားထားသော lane တစ်ခုသည် အသံတိတ် ပျောက်ကွယ်သွားနိုင်မည်မဟုတ်ပါ။ +Receipt များသည် ၎င်းတို့၏ကိုယ်ပိုင် workflow ကိုသာ အကျုံးဝင်ပြီး publication၊ deployment သို့မဟုတ် ရှိပြီးသား advisory scanner တစ်ခု၏ အတွင်းပိုင်းလုပ်ဆောင်ချက်များကို မအကျုံးဝင်ပါ။ Branch rule များတွင် check name နှစ်ခုစလုံးကို အသက်သွင်းခြင်းသည် -သီးခြား စီမံခန့်ခွဲရေးပြောင်းလဲမှုတစ်ခုဖြစ်ပြီး ဤ job များကို ထည့်သွင်းခြင်းတစ်ခုတည်းဖြင့် branch ကို ကာကွယ်ပေးမည်မဟုတ်ပါ။ +သီးခြား စီမံခန့်ခွဲရေးဆိုင်ရာ ပြောင်းလဲမှုတစ်ခုဖြစ်သည်၊ ဤ job များကို ထည့်သွင်းရုံဖြင့် branch တစ်ခုကို အလိုအလျောက် ကာကွယ်ပေးမည်မဟုတ်ပါ။ ### Static scan စာရင်း -Version သတ်မှတ်ထားသော npm-alias စာရင်းနှင့် static-scan membership တို့ကို -`config/quality/gate-manifest.json` တွင် ထားရှိသည်။ `package.json` နှင့် နှိုင်းယှဉ်၍ -script name များနှင့် command အတိအကျကို အတည်ပြုရန် `npm run check:gate-manifest` ကို လုပ်ဆောင်ပါ။ ထည့်သွင်းခြင်း၊ ဖယ်ရှားခြင်းနှင့် -command ပြောင်းလဲသွားခြင်းတို့ကြောင့် local hook နှင့် CI အတွင်းရှိ change-classification job နှစ်ခုစလုံး ကျရှုံးမည်။ -Alias တစ်ခုသည် workflow job၊ matrix instance သို့မဟုတ် test case မဟုတ်သောကြောင့် ဤအရေအတွက်များကို -တစ်ခုနှင့်တစ်ခု အပြန်အလှန်တူညီသည့်အရာများအဖြစ် မဖော်ပြရပါ။ - -ရွေးချယ်ထားသော alias များကို လုပ်ဆောင်ခြင်းမရှိဘဲ စစ်ဆေးကြည့်ရှုရန် `npm run quality:scan -- --list` သို့မဟုတ် -`npm run quality:scan:fast -- --list` ကို အသုံးပြုပါ။ Runner သည် -npm entrypoint ကို ခေါ်ယူအသုံးပြုသောကြောင့် configure လုပ်ထားသည့်နေရာများတွင် Bun အပါအဝင် ၎င်း၏ runtime ကို ဆက်လက်ထိန်းသိမ်းထားသည်။ -Manifest သည် ထို profile များ၏ ပြင်ပရှိ alias များကို သီးခြားခေါ်ယူလုပ်ဆောင်သည့်အရာများအဖြစ် မှတ်တမ်းတင်ထားပြီး +Version သတ်မှတ်ထားသော npm-alias စာရင်းနှင့် static-scan membership ကို +`config/quality/gate-manifest.json` တွင် ထားရှိသည်။ Script name များနှင့် command အတိအကျကို `package.json` နှင့် တိုက်ဆိုင်စစ်ဆေးရန် +`npm run check:gate-manifest` ကို လုပ်ဆောင်ပါ။ ထည့်သွင်းမှုများ၊ ဖယ်ရှားမှုများနှင့် +command ပြောင်းလဲကွဲလွဲမှုများသည် local hook နှင့် CI အတွင်းရှိ change-classification job များ နှစ်ခုစလုံးကို မအောင်မြင်စေပါ။ +Alias တစ်ခုသည် workflow job၊ matrix instance သို့မဟုတ် test case မဟုတ်ပါ- ဤအရေအတွက်များကို +တူညီဖလှယ်အသုံးပြုနိုင်သကဲ့သို့ မဖော်ပြရပါ။ + +ရွေးချယ်ထားသော alias များကို လုပ်ဆောင်ခြင်းမရှိဘဲ စစ်ဆေးကြည့်ရှုရန် `npm run quality:scan -- --list` သို့မဟုတ် `npm run quality:scan:fast -- --list` +ကို အသုံးပြုပါ။ Runner သည် +npm entrypoint ကို ခေါ်ယူသောကြောင့် configure လုပ်ထားသည့်နေရာတွင် Bun အပါအဝင် ၎င်း၏ runtime ကို ဆက်လက်ထိန်းသိမ်းထားသည်။ +Manifest သည် ဤ profile များ၏ ပြင်ပရှိ alias များကို သီးခြားခေါ်ယူလုပ်ဆောင်သည့်အရာများအဖြစ် မှတ်တမ်းတင်ထားပြီး read-only scan profile များတွင် maintenance command များကို တားမြစ်ထားသည်။ ဤ profile များသည် static scan ကိုသာ အကျုံးဝင်သည်။ ၎င်းတို့သည် product test များ၊ coverage၊ packaging၊ external check များ သို့မဟုတ် candidate တစ်ခု၏ release လက်ခံမှုအပြည့်အစုံကို အသိအမှတ်မပြုပါ။ Workflow လက်ခံမှုသည် ချိတ်ဆက်ထားသော `config/quality/admission-policy.json` နှင့် -`scripts/quality/admission-verdict.mjs` ကို အသုံးပြုသည်။ Release-observer profile များကို သီးခြားထားရှိဆဲဖြစ်ပြီး -၎င်းတို့နှင့် သက်ဆိုင်သော check များနှင့် receipt များကို သီးခြားစစ်ဆေးပါ။ အောက်ပါ စာသားဖြင့်ဖော်ပြထားသော -စာရင်းသည် ကိုးကားရန်သာဖြစ်ပြီး gate တစ်ခု အမှန်တကယ် လုပ်ဆောင်ခဲ့ကြောင်း သက်သေမပြနိုင်ပါ။ +`scripts/quality/admission-verdict.mjs` ကို အသုံးပြုသည်။ Release-observer profile များသည် သီးခြားဆက်ရှိနေသည်၊ +၎င်းတို့၏ သက်ဆိုင်သော check များနှင့် receipt များကို သီးခြားစစ်ဆေးပါ။ အောက်ပါ စာသားဖြင့်ဖော်ပြထားသော +စာရင်းသည် ကိုးကားရန်သာဖြစ်ပြီး gate တစ်ခု အမှန်တကယ် လုပ်ဆောင်ခဲ့ကြောင်း အထောက်အထားမဟုတ်ပါ။ Script များကို `scripts/check/` (policy gate များ) နှင့် `scripts/quality/` (ratchet engine) အောက်တွင် ထားရှိသည်။ -CI အတွက် တစ်ခုတည်းသော အမှန်တရားရင်းမြစ်မှာ `.github/workflows/ci.yml` ဖြစ်သည်။ +CI အတွက် တစ်ခုတည်းသော အမှန်တရားအရင်းအမြစ်မှာ `.github/workflows/ci.yml` ဖြစ်သည်။ ### Release PR fast-path (`quality.yml`) `.github/workflows/quality.yml` သည် main/release PR များ၊ protected-branch push များ၊ dispatch နှင့် merge group များတွင် CI ကို ဖြည့်စွက်ပေးသည်။ PR များသည် path-filtered fast check များကို အသုံးပြုသည်။ အမြဲတမ်း -ပိတ်ထားသည့် ထပ်နေသော build ကို ဖယ်ရှားပြီးဖြစ်သော်လည်း အမှန်တကယ် build/package/boot check များကို CI တွင် ဆက်လက်ထားရှိသည်။ - -| Job | အကျုံးဝင်မှု | ပိတ်ဆို့မှု | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `Docs Gates (fast-path)` | Docs/code PR များ၊ API docs ref များနှင့် docs-all | ဟုတ်သည် | -| `Fast Quality Gates` | Code PR များ၊ static check များ၊ typecheck၊ dashboard typecheck၊ သက်ရောက်မှုရှိသော unit test များ | ဟုတ်သည် | -| `Forgotten sibling tests` | Code PR များ၊ ပြောင်းလဲထားသော module များမှ static consumer များနှင့် ဖြစ်နိုင်ခြေရှိသော sibling test များအထိ ခြေရာခံခြင်း၊ barrel နှင့် dynamic-import path များကို ကိုးကားထားသော allowlist ခြွင်းချက်များနှင့်အတူ အကြံပြုသဘော diagnostic များအဖြစ် အစီရင်ခံသည် | **အကြံပြုသဘော** | -| `Vitest (fast-path)` | Code PR များ၊ လျင်မြန်သော vitest suite | ဟုတ်သည် | -| `Unit Tests fast-path` | Code PR များ၊ 4-shard unit suite | ဟုတ်သည် | -| `No new ESLint warnings` | Code PR များ၊ suppression များကို ထည့်သွင်းစဉ်းစားသော lint guard | ဟုတ်သည်၊ fork များအပါအဝင် | -| `Merge integrity (changelog + generated skills)` | Draft မဟုတ်သော PR များ၊ changelog နှင့် generated skill sync | ဟုတ်သည်၊ fork များအပါအဝင် | - -#### မေ့ကျန်ခဲ့သော sibling test အစီရင်ခံစာ - -`npm run check:forgotten-sibling-tests` သည် test-impact map ၏ နောက်ကွယ်ရှိ import resolver ကို ပြန်လည်အသုံးပြုသည်။ -ပြောင်းလဲထားသော production module တစ်ခုစီအတွက် ဖြစ်နိုင်ခြေရှိသော -test သည် pull-request diff တွင် မပါရှိသည့်အခါ တိကျစွာ ထပ်တူပြန်လည်ရရှိနိုင်သော `changed module/symbol -> static consumer -> candidate sibling test` chain များကို အစီရင်ခံသည်။ -Markdown အနှစ်ချုပ်နှင့် JSON ရလဒ်ကို blocking rollout တစ်စုံတစ်ရာမပြုမီ calibration ပြုလုပ်ရန် +ပိတ်ထားခဲ့သော ထပ်နေသည့် build ကို ဖယ်ရှားခဲ့ပြီး၊ အမှန်တကယ် build/package/boot check များသည် CI တွင် ဆက်ရှိနေသည်။ + +| Job | အကျုံးဝင်မှု | ပိတ်ဆို့မှု | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `Docs Gates (fast-path)` | Docs/code PR များ၊ API docs ref များနှင့် docs-all | ဟုတ်သည် | +| `Fast Quality Gates` | Code PR များ၊ static check များ၊ typecheck၊ dashboard typecheck၊ သက်ရောက်မှုရှိသော unit test များ | ဟုတ်သည် | +| `Forgotten sibling tests` | Code PR များ၊ ပြောင်းလဲထားသော module များကို static consumer များနှင့် candidate sibling test များအထိ ခြေရာခံခြင်း၊ barrel နှင့် dynamic-import path များကို ကိုးကားထားသော allowlist ခြွင်းချက်များနှင့်အတူ အကြံပြုသဘောဆိုင်ရာ diagnostic များအဖြစ် အစီရင်ခံသည် | **အကြံပြုသဘောသာ** | +| `Vitest (fast-path)` | Code PR များ၊ မြန်ဆန်သော vitest suite | ဟုတ်သည် | +| `Unit Tests fast-path` | Code PR များ၊ shard 4 ခုပါ unit suite | ဟုတ်သည် | +| `No new ESLint warnings` | Code PR များ၊ suppression များကို ထည့်သွင်းစဉ်းစားသော lint guard | ဟုတ်သည်၊ fork များအပါအဝင် | +| `Merge integrity (changelog + generated skills)` | Draft မဟုတ်သော PR များ၊ changelog နှင့် generated skill sync | ဟုတ်သည်၊ fork များအပါအဝင် | + +#### Forgotten sibling tests အစီရင်ခံစာ + +`npm run check:forgotten-sibling-tests` သည် test-impact map နောက်ကွယ်ရှိ import resolver ကို ပြန်လည်အသုံးပြုသည်။ +ပြောင်းလဲထားသော production module တစ်ခုစီအတွက် candidate +test သည် pull-request diff တွင် မပါရှိပါက သတ်မှတ်ချက်တူညီစွာ ထပ်မံရရှိနိုင်သော +`changed module/symbol -> static consumer -> candidate sibling test` chain များကို အစီရင်ခံသည်။ ပိတ်ဆို့သည့် rollout တစ်ခုခု မပြုလုပ်မီ ချိန်ညှိနိုင်ရန် Markdown summary နှင့် JSON result ကို `forgotten-sibling-tests` workflow artifact အဖြစ် ထိန်းသိမ်းထားသည်။ -Barrel ပြန်လည်တင်ပို့မှုများနှင့် dynamic import များသည် resolution စစ်ဆေးရေးအချက်အလက်များသာဖြစ်ပြီး၊ -ပိတ်ဆို့သော finding တစ်ခုကို မည်သည့်အခါမျှ မဖန်တီးပါ။ ပြန်လည်သုံးသပ်အတည်ပြုထားသော ခြွင်းချက်များကို +Barrel ပြန်လည် export လုပ်ခြင်းများနှင့် dynamic import များသည် resolution ဆိုင်ရာ ရောဂါရှာဖွေမှုများသာ ဖြစ်ပြီး၊ ပိတ်ဆို့သည့် finding တစ်ခုကို မည်သည့်အခါမျှ မဖန်တီးပါ။ +ပြန်လည်သုံးသပ်အတည်ပြုထားသော ခြွင်းချက်များကို `config/quality/forgotten-sibling-allowlist.json` တွင် ထားရှိသည်။ Entry တစ်ခုစီတွင် consumer နှင့် candidate -test ကို အမည်တပ်ဖော်ပြရမည်၊ တိကျသော အကြောင်းပြချက်ကို ပေးရမည်၊ ထို့ပြင် GitHub issue သို့မဟုတ် pull request တစ်ခုကို link ချိတ်ရမည်။ ပုံစံမမှန်သော entry များသည် -လုံခြုံရေးဦးစားပေးအဖြစ် ပိတ်ဆို့မည်။ ခြွင်းချက်များသည် ဖျက်ထားသော candidate test သို့မဟုတ် `.skip`/`.todo` ထည့်သွင်းထားသော diff ကို ဖုံးကွယ်မပေးနိုင်ပါ။ -assertion အားပျော့စေခြင်းနှင့် အခြားဖုံးကွယ်မှုများကို သီးခြားပိတ်ဆို့သည့် -`check:test-masking` gate က ဆက်လက်တာဝန်ယူသည်။ - -### Job: `lint` - -`main` သို့ PR တင်တိုင်း run သည်။ မအောင်မြင်ပါက merge လုပ်ခြင်းကို ပိတ်ဆို့သည်။ - -| Script (`npm run ...`) | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှုရှိ/မရှိ | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| `check:node-runtime` | Node.js version သည် ပံ့ပိုးထားသော range အတွင်း ရှိသည် | ရှိ | -| `check:cycles` | Circular import များ — `src/` + `open-sse/` module အားလုံး | ရှိ | -| `check:route-validation:t06` | route အားလုံးတွင် Zod schema များ ရှိသည် (အဆင့် 6 မူဝါဒ) | ရှိ | -| `check:any-budget:t11` | `@ts-expect-error // any` အရေအတွက်သည် သတ်မှတ်ထားသော budget ကို မကျော်လွန်ပါ (အဆင့် 11 catraca) | ရှိ | -| `check:provider-consistency` | `providers.ts` ရှိ provider တိုင်းတွင် `providerRegistry.ts` အတွင်း ကိုက်ညီသော entry တစ်ခုရှိပြီး အပြန်အလှန်အားဖြင့်လည်း အလားတူဖြစ်သည် (allowlist အတွင်း) | ဟုတ်သည် | -| `check:model-lifecycle` | ကိုယ်တိုင်ထိန်းသိမ်းထားသော routing table သုံးခုသည် repository တွင် ထည့်သွင်းထားသည့် lifecycle snapshot (#11503) နှင့် ကိုက်ညီမှုရှိနေသည်- `FITNESS_TABLE` (`taskFitness.ts`) သည် `REGISTRY` က route လုပ်နိုင်သော retired id မည်သည့်အရာကိုမျှ score မပေးပါ။ `BUILT_IN_ALIASES` target တိုင်းသည် `REGISTRY` တွင် ရှိပြီး retired-id snapshot တွင် မရှိပါ။ `REGISTRY` ထဲတွင် ဆက်လက်ရှိနေသည့် retired id တိုင်းကို forward လုပ်ထားခြင်း သို့မဟုတ် `allowedRetiredInCatalog` တွင် စာရင်းသွင်းထားခြင်း ရှိသည်။ ထို့ပြင် `DEFAULT_DEGRADATION_MAP` ၏ source သို့မဟုတ် target တစ်ခုမျှ ထို snapshot တွင် retired အဖြစ် မပေါ်ပါ။ ဤစစ်ဆေးမှုက model တစ်ခုကို လက်ရှိတွင် အသုံးပြုနိုင်သော upstream မှ အမှန်တကယ် ဝန်ဆောင်မှုပေးနေကြောင်း သက်သေမပြပါ။ Offline — `config/quality/model-lifecycle.json` နှင့် နှိုင်းယှဉ်ပြီး ၎င်းကို `npm run quality:refresh-model-lifecycle` ဖြင့် ကိုယ်တိုင် refresh လုပ်သည် (network လိုအပ်ပြီး CI တွင် ချိတ်ဆက်မထားပါ)။ `allowedRetiredInCatalog` သည် တဖြည်းဖြည်းလျှော့ချရန် ရည်ရွယ်သော ratchet တစ်ခုဖြစ်သည်- tracking issue ရှိမှသာ entry တစ်ခု ထည့်ပါ။ | ဟုတ်သည် | -| `check:fetch-targets` | client-side `src/` ရှိ `fetch("/api/...")` တိုင်းသည် အမှန်တကယ်ရှိသော `route.ts` သို့ resolve လုပ်သည် | ဟုတ်သည် | -| `check:deps` | repo အတွင်းရှိ `package.json` တိုင်းမှ `npm install` လုပ်နိုင်သော dependency အားလုံးသည် `dependency-allowlist.json` တွင် ရှိသည်။ pin မလုပ်ထားသော သို့မဟုတ် slopsquatted package အသစ်များကို flag လုပ်သည် | ဟုတ်သည် | -| `audit:deps` | `npm audit` (root + electron) — high/critical advisory မရှိပါ (osv `check:vuln-ratchet` နှင့် ထပ်နေသည်။ Rationalization Backlog ကို ကြည့်ပါ) | ဟုတ်သည် | -| `check:lockfile` | `package-lock.json` ၏ integrity — https registry၊ integrity hash များနှင့် host override မရှိခြင်း | ဟုတ်သည် | -| `check:licenses` | production dependencies များအတွက် SPDX license allowlist | ဟုတ်သည် | -| `check:tracked-artifacts` | build artifacts / commit လုပ်ထားသော `node_modules` symlinks မရှိရပါ (husky pre-commit တွင်လည်း run သည်; pre-push ကို ရည်ရွယ်ချက်ရှိရှိ ပေါ့ပါးအောင်ထားသည် — #6716) | ဟုတ်သည် | -| `check:ai-attribution` | PR commits၊ title သို့မဟုတ် body တွင် AI/bot `Co-Authored-By` trailer သို့မဟုတ် AI-generation footer မရှိရပါ — Hard Rule #16 (`quality.yml` ရှိ PR→`release/**` အတွက် fast-gates loop ထဲတွင် ပါဝင်သည် — event payload ကို ဖတ်ပြီး PR မဟုတ်ပါက မည်သည့်လုပ်ဆောင်ချက်မျှ မလုပ်ပါ — ထို့ပြင် `ci.yml` lint ရှိ PR→`main` အတွက် PR သီးသန့် step တစ်ခုလည်း ဖြစ်သည်; husky `commit-msg` hook လည်း ပါဝင်သည်; လူသား co-authors များကို ခွင့်ပြုသည်; #14436) | -| `check:vitest-exclusions` | Vitest exclusion တစ်ခုစီတွင် tracking issue တစ်ခုကို အမည်ဖော်ပြထားပြီး `config/quality/vitest-exclusions.json` တွင် ပါဝင်ရမည် (#13204) | ဟုတ်သည် | -| `check:file-size` | source file တစ်ခုမျှ extension အလိုက် သတ်မှတ်ထားသော အများဆုံးအရွယ်အစားကို မကျော်လွန်ရပါ (ratchet: အရွယ်ကြီး file များကို `frozen` စာရင်းတွင် ထိန်းချုပ်ထားသည်) | ဟုတ်သည် | -| `check:error-helper` | executors/handlers များရှိ error responses များသည် `buildErrorBody()` / `sanitizeErrorMessage()` ကို အသုံးပြုရမည် (Hard Rule #12) | ဟုတ်သည် | -| `check:migration-numbering` | Migration SQL ဖိုင်များကို ကွက်လပ် သို့မဟုတ် ထပ်နေမှုမရှိဘဲ အစဉ်လိုက် နံပါတ်တပ်ထားသည် | ဟုတ်သည် | -| `check:public-creds` | `publicCreds.ts` ပြင်ပတွင် OAuth `client_id`/`client_secret` သို့မဟုတ် Firebase Web key များကို တိုက်ရိုက်ထည့်သွင်းထားခြင်း မရှိပါ (တင်းကျပ်သော စည်းမျဉ်း #11) | ဟုတ်သည် | -| `check:db-rules` | `src/lib/db/` module များ၏ ပြင်ပတွင် raw SQL မရှိပါ၊ `localDb.ts` မှ barrel import များ မရှိပါ (တင်းကျပ်သော စည်းမျဉ်းများ #2/#5) | ဟုတ်သည် | -| `check:known-symbols` | ၎င်းတို့၏ dispatch table များတွင် မှတ်ပုံတင်ထားသော provider executor များ၊ routing strategy များနှင့် translator များသည် disk ပေါ်ရှိ ဖိုင်များနှင့် ကိုက်ညီသည် — သီးခြားကျန်နေသော သို့မဟုတ် ကြေညာမထားသော symbol များ မရှိပါ | ဟုတ်သည် | -| `check:route-guard-membership` | child process တစ်ခု စတင်သည့် route တိုင်းကို `isLocalOnlyPath()` ဖြင့် အမျိုးအစားခွဲထားသည် (တင်းကျပ်သော စည်းမျဉ်းများ #15/#17) | ဟုတ်သည် | -| `check:test-discovery` | repo ရှိ `*.test.ts` / `*.spec.ts` ဖိုင်တိုင်းကို အနည်းဆုံး test runner တစ်ခုက စုဆောင်းသည် (ratchet: `test-discovery-baseline.json` ရှိ orphan စာရင်းသည် လျော့နည်းသွားနိုင်ရုံသာရှိသည်) | ဟုတ်သည် | -| `check:agent-skills-sync` | ထုတ်လုပ်ထားသော agent-skills artifact များသည် ၎င်းတို့၏ မူရင်း catalog နှင့် ကိုက်ညီမှုရှိမရှိ စစ်ဆေးသည် (ကွဲလွဲမှုမရှိရ) | -| `check:provider-asset-provenance` | Provider logo/asset များတွင် မှတ်တမ်းတင်ထားသော provenance entry ရှိမရှိ စစ်ဆေးသည် | -| `lint:json` | JSON config file များကို parse လုပ်နိုင်ပြီး repo ၏ lint စည်းမျဉ်းများနှင့် ကိုက်ညီမှုရှိမရှိ စစ်ဆေးသည် | -| `typecheck:core` | Error မရှိဘဲ TypeScript compilation ပြုလုပ်သည် (အကြံပြုချက်ဆိုင်ရာ warning များသာ) | ရှိသည် | -| `typecheck:noimplicit:core` | တင်းကျပ်သော `noImplicitAny` — ရှေ့အနာဂတ်အတွက် ရည်ရွယ်ထားပြီး၊ ယခင်ကတည်းကရှိသော call site အများအပြားတွင် annotation များ ထည့်ရန် လိုအပ်နေသေးသည် | **အကြံပြုချက်သာ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` အတွက်သာ သတ်မှတ်ထားသော `tsc` (#7033) — `typecheck:core` ၏ စိစစ်ရွေးချယ်ထားသည့် file ၂၇ ခုပါ allowlist တွင် dashboard TSX တစ်ခုမျှ မပါဝင်သည့်အပြင် `next build` ကလည်း ၎င်းကို type-check လုံးဝ မလုပ်ပါ (`next.config.mjs` တွင် `ignoreBuildErrors: true` သတ်မှတ်ထားသည်)။ ထို့ကြောင့် ထိုနေရာရှိ orphaned-identifier regression များ (#6625/#6909) ကို CI က မမြင်နိုင်ခဲ့ပါ။ File တစ်ခုချင်းစီနှင့် TS-code တစ်ခုချင်းစီအလိုက် count များကို ပြင်ဆင်၍မရအောင် သတ်မှတ်ထားသည့် baseline (`config/quality/dashboard-typecheck-baseline.json`၊ `check:known-symbols` နှင့် တူညီသော stale-enforcement ပုံစံ) နှင့် diff လုပ်သည် — baseline သတ်မှတ်ထားသည့် count ထက်ကျော်လွန်သော error အသစ်များသာ gate ကို ကျရှုံးစေသည်။ ယခင်ကတည်းကရှိသော error တစ်ခုကို ပြင်ဆင်ပြီးသည့်အခါ `--update` ဖြင့် baseline ကို တဖြည်းဖြည်း လျှော့ချပါ။ | ရှိသည် | +test ကို အမည်ဖော်ပြရမည်၊ တိကျသော အကြောင်းပြချက်တစ်ခု ပေးရမည်ဖြစ်ပြီး GitHub issue သို့မဟုတ် pull request တစ်ခုကို link ချိတ်ရမည်။ ပုံစံမမှန်သော entry များသည် +အလိုအလျောက် ပိတ်ဆို့မည်။ +ခြွင်းချက်များသည် ဖျက်ထားသော candidate test သို့မဟုတ် `.skip`/`.todo` ကို ထည့်သွင်းထားသော diff တစ်ခုကို မဖုံးကွယ်နိုင်ပါ။ +assertion အားလျော့စေခြင်းနှင့် အခြား masking လုပ်ခြင်းများသည် သီးခြားပိတ်ဆို့သည့် +`check:test-masking` gate ၏ တာဝန်အဖြစ် ဆက်လက်တည်ရှိသည်။ + +### အလုပ်: `lint` + +`main` သို့ PR တင်သည့်အခါတိုင်း လုပ်ဆောင်သည်။ မအောင်မြင်ပါက merge လုပ်ခြင်းကို ပိတ်ဆို့သည်။ + +| Script (`npm run ...`) | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့ခြင်း | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `check:node-runtime` | Node.js version သည် ပံ့ပိုးထားသော အတိုင်းအတာအတွင်း ရှိခြင်း | ဟုတ်သည် | +| `check:cycles` | `src/` + `open-sse/` အားလုံးတစ်လျှောက်ရှိ circular import များ (AST အခြေပြု၍ tsconfig `paths` ကို ဖြေရှင်းထားသည်)။ ရိုးရိုးလုပ်ဆောင်မှု = အကြံပေးချက်သာဖြစ်ပြီး cycle များကို စာရင်းပြုစုသည်။ `check:cycles:ratchet` (CI က လုပ်ဆောင်သည့်အရာ) သည် အရေအတွက်က `quality-baseline.json` ရှိ `metrics.cycles` အများဆုံးသတ်မှတ်ချက်ကို ကျော်လွန်သည့်အခါ ပိတ်ဆို့သည် — လက်ရှိ 14၊ `direction: down` ဖြစ်သောကြောင့် လျော့ကျရုံသာ ရနိုင်သည် (#15159 G-01/G-02) | ဟုတ်သည် (ratchet) | +| `check:route-validation:t06` | route အားလုံးတွင် Zod schema များ ပါရှိခြင်း (Tier 6 မူဝါဒ) | ဟုတ်သည် | +| `check:any-budget:t11` | `@ts-expect-error // any` အရေအတွက်သည် သတ်မှတ်ထားသော budget ကို မကျော်လွန်ခြင်း (Tier 11 catraca) | ဟုတ်သည် | +| `check:provider-consistency` | `providers.ts` ရှိ provider တိုင်းတွင် `providerRegistry.ts` ၌ ကိုက်ညီသော entry တစ်ခုရှိသည် (အပြန်အလှန်အားဖြင့်လည်း allowlist အတွင်း ကိုက်ညီမှုရှိသည်) | ဟုတ်သည် | +| `check:model-lifecycle` | ကိုယ်တိုင်ထိန်းသိမ်းထားသော routing table သုံးခုသည် repository တွင် ထည့်သွင်းထားသော lifecycle snapshot (#11503) နှင့် တစ်သမတ်တည်းရှိနေသည်- `FITNESS_TABLE` (`taskFitness.ts`) သည် `REGISTRY` က route လုပ်နိုင်သည့် အနားပေးပြီးသော id တစ်ခုကိုမျှ score မပေးပါ။ `BUILT_IN_ALIASES` target တိုင်းသည် `REGISTRY` တွင် ရှိပြီး အနားပေးထားသော id snapshot တွင် မရှိပါ။ `REGISTRY` တွင် ကျန်ရှိနေသေးသော အနားပေးထားသည့် id တိုင်းကို forward လုပ်ထားခြင်း သို့မဟုတ် `allowedRetiredInCatalog` တွင် စာရင်းသွင်းထားခြင်း ရှိသည်။ ထို့အပြင် `DEFAULT_DEGRADATION_MAP` ၏ source သို့မဟုတ် target တစ်ခုမျှ အဆိုပါ snapshot တွင် အနားပေးထားသည်ဟု မပေါ်ပါ။ ဤစစ်ဆေးမှုသည် model တစ်ခုကို လက်ရှိအသုံးပြုနေသော live upstream က အမှန်တကယ် ဝန်ဆောင်မှုပေးနေကြောင်း သက်သေမပြပါ။ Offline ဖြစ်ပြီး `config/quality/model-lifecycle.json` နှင့် နှိုင်းယှဉ်သည်။ ၎င်းကို `npm run quality:refresh-model-lifecycle` ဖြင့် ကိုယ်တိုင် refresh လုပ်သည် (network လိုအပ်ပြီး CI နှင့် ချိတ်ဆက်မထားပါ)။ `allowedRetiredInCatalog` သည် တဖြည်းဖြည်းလျှော့ချရန် ရည်ရွယ်သည့် ratchet ဖြစ်သည်- tracking issue တစ်ခုရှိမှသာ entry တစ်ခုကို ထည့်ပါ။ | ဟုတ်သည် | +| `check:fetch-targets` | client-side `src/` ရှိ `fetch("/api/...")` တိုင်းသည် အမှန်တကယ်ရှိသော `route.ts` တစ်ခုသို့ resolve ဖြစ်သည် | ဟုတ်သည် | +| `check:deps` | repo ရှိ `package.json` တိုင်းမှ `npm install` ဖြင့် ထည့်သွင်းနိုင်သော dependency အားလုံးသည် `dependency-allowlist.json` တွင် ရှိသည်။ pin မလုပ်ထားသော သို့မဟုတ် slopsquatting ဖြစ်နိုင်သော package အသစ်များကို အမှတ်အသားပြုသည် | ဟုတ်သည် | +| `audit:deps` | `npm audit` (root + electron) — high/critical advisory မရှိပါ (osv `check:vuln-ratchet` နှင့် ထပ်နေသည်၊ Rationalization Backlog ကို ကြည့်ပါ) | ဟုတ်သည် | +| `check:lockfile` | `package-lock.json` ၏ integrity — https registry၊ integrity hash များ၊ host override မရှိခြင်း | ဟုတ်သည် | +| `check:licenses` | production dependencies များအတွက် SPDX license allowlist | ဟုတ်သည် | +| `check:tracked-artifacts` | build artifacts / commit လုပ်ထားသော `node_modules` symlinks မရှိရပါ (husky pre-commit တွင်လည်း လုပ်ဆောင်သည်။ pre-push ကို ရည်ရွယ်ချက်ရှိရှိ ပေါ့ပါးအောင်ထားသည် — #6716) | ဟုတ်သည် | +| `check:ai-attribution` | PR commits၊ title သို့မဟုတ် body တွင် AI/bot `Co-Authored-By` trailer သို့မဟုတ် AI-generation footer မရှိရပါ — Hard Rule #16 (`quality.yml` ရှိ PR→`release/**` အတွက် fast-gates loop တွင် ပါဝင်သည် — event payload ကို ဖတ်ပြီး PR မဟုတ်ပါက မည်သည့်လုပ်ဆောင်ချက်မျှ မလုပ်ပါ — ထို့အပြင် PR→`main` အတွက် `ci.yml` lint ရှိ PR-only step တွင်လည်း ပါဝင်သည်။ husky `commit-msg` hook လည်း ပါဝင်သည်။ လူသား co-authors များကို ခွင့်ပြုသည်။ #14436) | +| `check:vitest-exclusions` | Vitest exclusion တိုင်းတွင် tracking issue တစ်ခုကို အမည်ဖော်ပြထားပြီး `config/quality/vitest-exclusions.json` တွင် ပါဝင်ရမည် (#13204) | ဟုတ်သည် | +| `check:file-size` | source file တစ်ခုမျှ extension အလိုက် သတ်မှတ်ထားသော အများဆုံးအရွယ်အစားကို မကျော်လွန်ရပါ (ratchet: ကြီးမားသော frozen files များကို `frozen` list တွင် ထည့်ထားသည်) | ဟုတ်သည် | +| `check:error-helper` | executors/handlers ရှိ error responses များတွင် `buildErrorBody()` / `sanitizeErrorMessage()` ကို အသုံးပြုရမည် (Hard Rule #12) | ဟုတ်သည် | +| `check:migration-numbering` | Migration SQL ဖိုင်များကို ကွက်လပ်များ သို့မဟုတ် ထပ်နေမှုများမရှိဘဲ အစဉ်လိုက် နံပါတ်တပ်ထားသည် | ဟုတ်သည် | +| `check:public-creds` | `publicCreds.ts` ပြင်ပတွင် OAuth `client_id`/`client_secret` သို့မဟုတ် Firebase Web key များကို တိုက်ရိုက်ရေးသားထားခြင်းမရှိပါ (တင်းကျပ်သော စည်းမျဉ်း #11) | ဟုတ်သည် | +| `check:db-rules` | `src/lib/db/` module များပြင်ပတွင် raw SQL မရှိပါ။ `localDb.ts` မှ barrel-import များလည်း မရှိပါ (တင်းကျပ်သော စည်းမျဉ်းများ #2/#5) | ဟုတ်သည် | +| `check:known-symbols` | ၎င်းတို့၏ dispatch table များတွင် မှတ်ပုံတင်ထားသော provider executor များ၊ routing strategy များနှင့် translator များသည် disk ပေါ်ရှိ ဖိုင်များနှင့် ကိုက်ညီသည် — ချိတ်ဆက်မထားသော သို့မဟုတ် ကြေညာမထားသော symbol များ မရှိပါ | ဟုတ်သည် | +| `check:route-guard-membership` | child process တစ်ခုကို စတင်သည့် route တိုင်းကို `isLocalOnlyPath()` ဖြင့် အမျိုးအစားခွဲခြားထားသည် (တင်းကျပ်သော စည်းမျဉ်းများ #15/#17) | ဟုတ်သည် | +| `check:test-discovery` | repo အတွင်းရှိ `*.test.ts` / `*.spec.ts` ဖိုင်တိုင်းကို အနည်းဆုံး test runner တစ်ခုက စုဆောင်းသည် (ratchet: `test-discovery-baseline.json` ရှိ orphan စာရင်းသည် လျော့နည်းရုံသာ ဖြစ်နိုင်သည်) | ဟုတ်သည် | +| `check:agent-skills-sync` | ထုတ်လုပ်ထားသော agent-skills artifact များသည် ၎င်းတို့၏ မူရင်း catalog နှင့် ကိုက်ညီကြောင်း စစ်ဆေးသည် (ကွာဟချက်မရှိစေရ) | +| `check:provider-asset-provenance` | Provider logo/asset များတွင် မှတ်တမ်းတင်ထားသော မူရင်းဇာစ်မြစ် entry တစ်ခု ပါရှိကြောင်း စစ်ဆေးသည် | +| `lint:json` | JSON config ဖိုင်များကို parse လုပ်နိုင်ပြီး repo ၏ lint စည်းမျဉ်းများနှင့် ကိုက်ညီကြောင်း စစ်ဆေးသည် | +| `typecheck:core` | Error မရှိဘဲ TypeScript ကို compile လုပ်သည် (အကြံပြုချက်ဆိုင်ရာ warning များသာ) | ဟုတ်သည် | +| `typecheck:noimplicit:core` | တင်းကျပ်သော `noImplicitAny` — အနာဂတ်အတွက် ရည်ရွယ်ထားပြီး ယခင်ကတည်းက ရှိနေသော call site များစွာတွင် annotation များ ထည့်သွင်းရန် လိုအပ်နေသေးသည် | **အကြံပြုချက်သာ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` အတွက်သာ အကျုံးဝင်သော `tsc` (#7033) — `typecheck:core` ၏ ရွေးချယ်ထားသည့် 27-file allowlist တွင် dashboard TSX တစ်ခုမျှ မပါဝင်သကဲ့သို့ `next build` ကလည်း ၎င်းကို မည်သည့်အခါမျှ type-check မလုပ်ပါ (`next.config.mjs` တွင် `ignoreBuildErrors: true` ဟု သတ်မှတ်ထားသည်)။ ထို့ကြောင့် ထိုနေရာရှိ သီးခြားကျန်ရစ်နေသော identifier regression များ (#6625/#6909) ကို CI က မတွေ့နိုင်ခဲ့ပါ။ မပြောင်းလဲစေရန် သတ်မှတ်ထားသော file တစ်ခုချင်းစီ/TS-code တစ်ခုချင်းစီ၏ အရေအတွက် baseline (`config/quality/dashboard-typecheck-baseline.json`၊ `check:known-symbols` နှင့် တူညီသော stale-enforcement ပုံစံ) နှင့် diff ပြုလုပ်သည် — baseline သတ်မှတ်ထားသော အရေအတွက်ထက် ကျော်လွန်သည့် error အသစ်များသာ gate ကို မအောင်မြင်စေသည်။ ယခင်ရှိပြီးသား error တစ်ခုကို ပြင်ဆင်ပြီးသောအခါ `--update` ဖြင့် baseline ကို တဖြည်းဖြည်း လျှော့ချပါ။ | ဟုတ်သည် | ### Job: `quality-gate` -`test-coverage` ပြီးနောက် လုပ်ဆောင်သည်။ မအောင်မြင်ပါက merge လုပ်ခြင်းကို ပိတ်ဆို့သည်။ - -| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| `quality:collect` | `quality-metrics.json` ကို ထုတ်ပေးသည် (ESLint သတိပေးချက်အရေအတွက်၊ ပေါင်းစည်းထားသော shard အစီရင်ခံစာမှ coverage) | ဟုတ်သည် (ratchet မတိုင်မီ အဆင့်) | -| `quality:ratchet` | `quality-baseline.json` ရှိ metric တစ်ခုစီသည် ယခင်ထက် ဆုတ်ယုတ်မသွားကြောင်း စစ်ဆေးသည် (ESLint သတိပေးချက်များ ≤ baseline; coverage ≥ baseline) | ဟုတ်သည် | -| `check:duplication` | Code duplication (jscpd@4) သည် `quality-baseline.json` ရှိ baseline ကို မကျော်လွန်ကြောင်း စစ်ဆေးသည် | ဟုတ်သည် | -| `check:complexity` | ဖိုင်အဆင့် cyclomatic complexity သည် သတ်မှတ်ထားသော အများဆုံးပမာဏကို မကျော်လွန်ကြောင်း စစ်ဆေးသည် (core ESLint `complexity` + `max-lines-per-function`) | ဟုတ်သည် | -| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — သီးခြား ESLint စစ်ဆေးမှုဖြစ်ပြီး CI တွင် နှစ်ခုလုံးကို `check:complexity-ratchets` အဆင့်တစ်ခုတည်းအဖြစ် ပေါင်းစည်း၍ လုပ်ဆောင်သည် | ဟုတ်သည် | -| `check:dead-code` | အသုံးမပြုထားသော exports / ဖိုင်များအတွက် ratchet (knip) သည် baseline နှင့် နှိုင်းယှဉ်ရာတွင် ဆုတ်ယုတ်မသွားကြောင်း စစ်ဆေးသည် | ဟုတ်သည် | -| `check:compression-budget` | Compression benchmark budget — engine တစ်ခုချင်းစီ၏ token-savings အနိမ့်ဆုံးသတ်မှတ်ချက်များ ဆုတ်ယုတ်မသွားရပါ | ဟုတ်သည် | -| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) သည် ဆုတ်ယုတ်မသွားရပါ။ ၎င်းသည် `typecheck:noimplicit:core` ကို အဓိကအားဖြင့် အစားထိုးလွှမ်းခြုံထားသည် | ဟုတ်သည် | -| `check:codeql-ratchet` | ဖွင့်ထားသော CodeQL alert အရေအတွက် မတိုးလာကြောင်း စစ်ဆေးသည် (`gh api` မှတစ်ဆင့် ဖတ်ယူသည်။ token မရှိပါက အမှားမဖြစ်စေဘဲ ကျော်သွားသည်) — ပြန်လည်စစ်ဆေးသည့် အကြိမ်နှုန်းနှင့် ကိုယ်တိုင်စတင်ခြင်းအတွက် အောက်ပါ "CodeQL ratchet" ကို ကြည့်ပါ | ဟုတ်သည် | - -### လုပ်ငန်း: `quality-extended` - -လုပ်ငန်းတစ်ခုလုံးသည် အကြံပြုသဘောသာ ဖြစ်သည် (`continue-on-error: true`)။ npm အခြေပြု ratchet များကို -အမှန်တကယ် လုပ်ဆောင်သည်။ ပြင်ပ scanner များကို `gh release download` မှတစ်ဆင့် ထည့်သွင်းပြီး binary -မရှိသေးပါက မိမိဘာသာ ကျော်သွားသည် (exit 0)။ - -| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| `check:circular-deps` | Circular dependency မရှိကြောင်း စစ်ဆေးသည် (dpdm) | **အကြံပြုသဘောသာ** | -| `check:bundle-size` | Bundle size သည် သတ်မှတ်ထားသော အများဆုံးပမာဏကို မကျော်လွန်ကြောင်း စစ်ဆေးသည် | **အကြံပြုသဘောသာ** | -| `check:secrets` | Secret scanning (gitleaks) — binary မရှိပါက ကျော်သွားသည် | **အကြံပြုသဘောသာ** | -| `check:vuln-ratchet` | Dependency vulnerability များ (osv-scanner) ဆုတ်ယုတ်မသွားကြောင်း စစ်ဆေးသည် — binary မရှိပါက ကျော်သွားသည် | **အကြံပြုသဘောသာ** | -| `check:workflows` | Workflow lint (actionlint + zizmor)။ scanner ပျောက်ဆုံးခြင်း/ပျက်ယွင်းခြင်း၊ အစီရင်ခံစာ မမှန်ကန်ခြင်း သို့မဟုတ် ratchet baseline ပျောက်ဆုံးခြင်းတို့ကို INCOMPLETE အဖြစ် သတ်မှတ်၍ မအောင်မြင်စေသည်။ တွေ့ရှိချက်အမှန်များသည် ရွေးချယ်ထားသော strict/advisory/ratchet မူဝါဒအတိုင်း လိုက်နာသည် | လုပ်ဆောင်ရန် လိုအပ်သည်။ CI တွင် zizmor ratchet က ပိတ်ဆို့သည် | -| `check:openapi-breaking` | အများသုံး API contract (`openapi.yaml`) တွင် base branch နှင့် နှိုင်းယှဉ်သည့် breaking change များ (oasdiff) — `openapiBreaking=N` ကို ထုတ်ပေးသည်။ oasdiff မရှိပါက သို့မဟုတ် base spec ကို ဖြေရှင်းမရပါက ကျော်သွားသည် | **အကြံပြုသဘောသာ** | - -### လုပ်ငန်း: `docs-sync-strict` - -`main` သို့ ပေးပို့သည့် PR တိုင်းတွင် လုပ်ဆောင်သည်။ မအောင်မြင်ပါက merge လုပ်ခြင်းကို ပိတ်ဆို့သည်။ - -| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | -| `check:docs-all` | အောက်ပါ sub-gate ၆ ခုကို အစဉ်လိုက် လုပ်ဆောင်သည့် meta-gate | ဟုတ်သည် | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ဗားရှင်းများ၏ တစ်သမတ်တည်းဖြစ်မှု | ဟုတ်သည် | -| ↳ `check:docs-counts` | စာသားဖော်ပြချက်များရှိ အရေအတွက်များ (provider အရေအတွက်၊ migration အရေအတွက် စသည်) သည် အမှန်တကယ်အရေအတွက်များ၏ ratchet window အတွင်း ရှိနေခြင်း | ဟုတ်သည် | -| ↳ `check:env-doc-sync` | `.env.example` ရှိ env var တိုင်းကို docs ဇယားတစ်ခုတွင် မှတ်တမ်းတင်ထားပြီး၊ docs ဇယားရှိ env var တိုင်းလည်း `.env.example` တွင် ရှိနေခြင်း | ဟုတ်သည် | -| ↳ `check:deprecated-versions` | docs များတွင် ရပ်ဆိုင်းထားသော ဗားရှင်း string များ မရှိခြင်း | ဟုတ်သည် | -| ↳ `check:doc-links` | docs အတွင်းရှိ internal markdown link များက အမှန်တကယ်ရှိသော ဖိုင်များသို့ ညွှန်းဆိုနိုင်ခြင်း (`[text]`/`(path)` ပုံစံ) | ဟုတ်သည် | -| ↳ `check:fabricated-docs` | docs တွင် ရည်ညွှန်းထားသော route များ၊ env var များ၊ CLI command များ၊ hook အမည်များနှင့် file path များသည် codebase ထဲတွင် အမှန်တကယ်ရှိခြင်း။ `--strict` ဖြင့် hard gate ဖြစ်ပြီး flag မပါလျှင် soft-fail ဖြစ်သည်။ | ဟုတ်သည် (CI တွင် `--strict` မှတစ်ဆင့်) | -| `check:cli-i18n` | CLI command string များသည် i18n locale ဖိုင်အားလုံးတွင် ရှိနေခြင်း | ဟုတ်သည် | -| `check:openapi-coverage` | OpenAPI spec သည် အမှန်တကယ်ရှိသော route များ၏ ratchet လုပ်ထားသည့် အနည်းဆုံးသတ်မှတ်ချက်ကို ပြည့်မီစွာ လွှမ်းခြုံထားခြင်း | ဟုတ်သည် | -| `check:openapi-security-tiers` | `openapi.yaml` ရှိ security tier annotation များသည် `routeGuard.ts` ရှိ classification များနှင့် တစ်သမတ်တည်းဖြစ်ခြင်း | **အကြံပြုချက်သာ** | -| `check:openapi-routes` | `openapi.yaml` ရှိ path တိုင်းသည် အမှန်တကယ်ရှိသော `route.ts` တစ်ခုသို့ ညွှန်းဆိုနိုင်ခြင်း (မရှိသောအရာ ဖန်တီးဖော်ပြမှုကို တားဆီးခြင်း) | ဟုတ်သည် | -| `check:docs-symbols` | `docs/**/*.md` ရှိ `/api/...` ရည်ညွှန်းချက်တိုင်းသည် အမှန်တကယ်ရှိသော `route.ts` တစ်ခုသို့ ညွှန်းဆိုနိုင်ခြင်း (မရှိသောအရာ ဖန်တီးဖော်ပြမှုကို တားဆီးခြင်း) | ဟုတ်သည် | -| `i18n translation drift` | i18n locale ဖိုင်များရှိ ဘာသာမပြန်ရသေးသော key များ — သတိပေးချက်သာ | **အကြံပြုချက်သာ** | +`test-coverage` ပြီးနောက် လုပ်ဆောင်သည်။ မအောင်မြင်ပါက merge ပြုလုပ်ခြင်းကို ပိတ်ဆို့သည်။ + +| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `quality:collect` | `quality-metrics.json` ကို ထုတ်ပေးသည် (ESLint သတိပေးချက်အရေအတွက်၊ ပေါင်းစည်းထားသော shard အစီရင်ခံစာမှ coverage) | ဟုတ်သည် (ratchet မတိုင်မီ) | +| `quality:ratchet` | `quality-baseline.json` ရှိ metric တစ်ခုချင်းစီသည် နောက်ပြန်ဆုတ်မသွားရပါ (ESLint သတိပေးချက်များ ≤ baseline; coverage ≥ baseline) | ဟုတ်သည် | +| `check:duplication` | Code duplication (jscpd@4) သည် `quality-baseline.json` ရှိ baseline ထက် မကျော်လွန်ရပါ | ဟုတ်သည် | +| `check:complexity` | File အဆင့် cyclomatic complexity သည် သတ်မှတ်ထားသော အများဆုံးပမာဏကို မကျော်လွန်ရပါ (core ESLint `complexity` + `max-lines-per-function`) | ဟုတ်သည် | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — သီးခြား ESLint pass; CI သည် နှစ်ခုလုံးကို `check:complexity-ratchets` အဆင့်တစ်ခုတည်းအဖြစ် ပေါင်းစည်း၍ run သည် | ဟုတ်သည် | +| `check:dead-code` | အသုံးမပြုသော exports / files ratchet (knip) သည် baseline နှင့် နှိုင်းယှဉ်လျှင် နောက်ပြန်ဆုတ်မသွားရပါ | ဟုတ်သည် | +| `check:compression-budget` | Compression benchmark budget — engine တစ်ခုချင်းစီ၏ token-savings အနိမ့်ဆုံးသတ်မှတ်ချက်များ နောက်ပြန်ဆုတ်မသွားရပါ | ဟုတ်သည် | +| `check:type-coverage` | ရာခိုင်နှုန်းအလိုက် typed ratchet (`type-coverage`) သည် နောက်ပြန်ဆုတ်မသွားရပါ; `typecheck:noimplicit:core` ကို အများစု လွှမ်းခြုံအစားထိုးထားသည် | ဟုတ်သည် | +| `check:codeql-ratchet` | ဖွင့်ထားသော CodeQL alert အရေအတွက်သည် နောက်ပြန်ဆုတ်မသွားရပါ (`gh api` မှတစ်ဆင့် ဖတ်သည်; token မရှိပါက အမှားမဖြစ်ဘဲ ကျော်သွားသည်) — refresh အကြိမ်ရေနှင့် ကိုယ်တိုင်စတင်ခြင်းအတွက် အောက်ရှိ "CodeQL ratchet" ကို ကြည့်ပါ | ဟုတ်သည် | + +### Job: `quality-extended` + +Job တစ်ခုလုံးသည် အကြံပြုချက်သဘောသာ ဖြစ်သည် (`continue-on-error: true`)။ npm အခြေပြု ratchets များကို +အမှန်တကယ် run သည်; ပြင်ပ scanners များကို `gh release download` မှတစ်ဆင့် install လုပ်ပြီး binary မရှိသေးပါက +ကိုယ်တိုင်ကျော်သွားသည် (exit 0)။ + +| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| `check:circular-deps` | Circular dependencies မရှိရပါ (dpdm) | **အကြံပြုချက်သဘော** | +| `check:bundle-size` | Bundle size သည် သတ်မှတ်ထားသော အများဆုံးပမာဏကို မကျော်လွန်ရပါ | **အကြံပြုချက်သဘော** | +| `check:secrets` | လျှို့ဝှက်ချက်များကို scan လုပ်ခြင်း (gitleaks) — binary မရှိပါက ကျော်သွားသည် | **အကြံပြုချက်သဘော** | +| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) များသည် နောက်ပြန်ဆုတ်မသွားရပါ — binary မရှိပါက ကျော်သွားသည် | **အကြံပြုချက်သဘော** | +| `check:workflows` | Workflow lint (actionlint + zizmor); scanners မရှိခြင်း/ပျက်နေခြင်း၊ မမှန်ကန်သော reports သို့မဟုတ် ratchet baseline မရှိခြင်းတို့ကြောင့် INCOMPLETE အဖြစ် ကျရှုံးသည်။ မှန်ကန်သော တွေ့ရှိချက်များသည် ရွေးချယ်ထားသည့် strict/advisory/ratchet policy ကို လိုက်နာသည် | လုပ်ဆောင်ရန် လိုအပ်သည်; CI တွင် zizmor ratchet က ပိတ်ဆို့သည် | +| `check:openapi-breaking` | Base branch (oasdiff) နှင့် နှိုင်းယှဉ်ရာတွင် public API contract (`openapi.yaml`) ၏ breaking changes — `openapiBreaking=N` ကို ထုတ်ပေးသည်; oasdiff မရှိပါက သို့မဟုတ် base spec ကို ဖြေရှင်းမရပါက ကျော်သွားသည် | **အကြံပြုချက်သဘော** | + +### Job: `docs-sync-strict` + +`main` သို့ PR တိုင်းတွင် run သည်။ မအောင်မြင်ပါက merge လုပ်ခြင်းကို ပိတ်ဆို့သည်။ + +| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | +| `check:docs-all` | အောက်ပါ sub-gate 6 ခုကို အစဉ်လိုက် လုပ်ဆောင်သည့် meta-gate | ရှိ | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt version များ တစ်သမတ်တည်းရှိမှု | ရှိ | +| ↳ `check:docs-counts` | စာသားအတွင်းရှိ အရေအတွက်များ (provider အရေအတွက်၊ migration အရေအတွက် စသည်) သည် အမှန်တကယ်အရေအတွက်များ၏ ratchet window အတွင်း ရှိနေမှု | ရှိ | +| ↳ `check:env-doc-sync` | `.env.example` အတွင်းရှိ env var တိုင်းကို docs ဇယားတစ်ခုတွင် မှတ်တမ်းတင်ထားပြီး၊ အပြန်အလှန်အားဖြင့် docs ဇယားရှိ env var တိုင်းသည်လည်း `.env.example` တွင် ရှိနေမှု | ရှိ | +| ↳ `check:deprecated-versions` | docs များတွင် အသုံးမပြုတော့သည့် version string များ မရှိမှု | ရှိ | +| ↳ `check:doc-links` | docs အတွင်းရှိ internal markdown link များသည် အမှန်တကယ်ရှိသော file များသို့ ညွှန်းဆိုမှု (`[text]`/`(path)` ပုံစံ) | ရှိ | +| ↳ `check:fabricated-docs` | docs များတွင် ကိုးကားထားသော route များ၊ env var များ၊ CLI command များ၊ hook name များနှင့် file path များသည် codebase ထဲတွင် အမှန်တကယ် ရှိမှု။ `--strict` ဖြင့် hard gate ဖြစ်ပြီး flag မပါလျှင် soft-fail ဖြစ်သည်။ | ရှိ (CI တွင် `--strict` မှတစ်ဆင့်) | +| `check:cli-i18n` | CLI command string များသည် i18n locale file အားလုံးတွင် ပါရှိမှု | ရှိ | +| `check:openapi-coverage` | OpenAPI spec သည် အမှန်တကယ် route များ၏ ratchet သတ်မှတ်ထားသော အနည်းဆုံးအရေအတွက်ကို လွှမ်းခြုံမှု | ရှိ | +| `check:openapi-security-tiers` | `openapi.yaml` အတွင်းရှိ security tier annotation များသည် `routeGuard.ts` classification များနှင့် တစ်သမတ်တည်းရှိမှု | **အကြံပြုချက်သာ** | +| `check:openapi-routes` | `openapi.yaml` အတွင်းရှိ path တိုင်းသည် အမှန်တကယ်ရှိသော `route.ts` တစ်ခုသို့ ညွှန်းဆိုမှု (မမှန်ကန်သော ဖန်တီးဖော်ပြမှုကို ကာကွယ်ခြင်း) | ရှိ | +| `check:docs-symbols` | `docs/**/*.md` အတွင်းရှိ `/api/...` ကိုးကားချက်တိုင်းသည် အမှန်တကယ်ရှိသော `route.ts` တစ်ခုသို့ ညွှန်းဆိုမှု (မမှန်ကန်သော ဖန်တီးဖော်ပြမှုကို ကာကွယ်ခြင်း) | ရှိ | +| `i18n translation drift` | i18n locale file များအတွင်း ဘာသာမပြန်ရသေးသော key များ — သတိပေးချက်သာ | **အကြံပြုချက်သာ** | ### Job: `i18n-ui-coverage` -| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `check-ui-keys-coverage` (inline) | UI i18n key လွှမ်းခြုံမှုသည် ≥ 65% ဖြစ်ခြင်း | ဟုတ်သည် | -| `check-ui-value-drift` (inline) | ပြန်လည်ရေးသားထားသော အင်္ဂလိပ် **တန်ဖိုး** တစ်ခုကြောင့် ခေတ်မမီတော့သော ဘာသာပြန်ချက် တစ်ခုမျှ ကျန်မနေခြင်း | ဟုတ်သည် | -| `check-new-key-coverage` (inline) | **အသစ်** ထည့်ထားသော အင်္ဂလိပ် key ကို locale တိုင်းတွင် ဘာသာပြန်ထားခြင်း — `__MISSING__:` marker ကို လက်မခံပါ | ဟုတ်သည် | -| `check-translation-ratio` | locale တစ်ခုချင်းစီ၏ ဘာသာပြန်ချက်အစစ်အမှန် အချိုး (allowlist ပြင်ပရှိ အင်္ဂလိပ်နှင့် တူညီနေသော / placeholder ဖြစ်သော / ပျောက်ဆုံးနေသော leaf များ) သည် `config/quality/i18n-translation-baseline.json` + slack ကို မကျော်လွန်ရပါ | **အကြံပြုချက်သာ** | +| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `check-ui-keys-coverage` (inline) | UI i18n key လွှမ်းခြုံမှုသည် ≥ 65% ဖြစ်မှု | ရှိ | +| `check-ui-value-drift` (inline) | ပြန်လည်ရေးသားထားသော English **value** ကြောင့် ခေတ်မမီတော့သည့် ဘာသာပြန်ဆိုချက်တစ်ခုမျှ ကျန်မနေမှု | ရှိ | +| `check-new-key-coverage` (inline) | **အသစ်** ထည့်ထားသော English key ကို locale တိုင်းတွင် ဘာသာပြန်ထားမှု — `__MISSING__:` marker ကို လက်မခံပါ | ရှိ | +| `check-translation-ratio` | locale တစ်ခုစီ၏ အမှန်တကယ်ဘာသာပြန်ဆိုမှုအချိုး (allowlist ပြင်ပရှိ English နှင့်တူညီသော / placeholder ဖြစ်သော / ပျောက်ဆုံးနေသော leaf များ) သည် `config/quality/i18n-translation-baseline.json` + slack ထက် မကျော်လွန်ရ | **အကြံပြုချက်သာ** | -`fetch-depth: 0` လိုအပ်သည် — value-drift gate သည် merge base နှင့် `en.json` ကို diff ပြုလုပ်သည်။ +`fetch-depth: 0` လိုအပ်သည် — value-drift gate သည် `en.json` ကို merge base နှင့် diff ပြုလုပ်သည်။ -#### `check-ui-value-drift` — ခေတ်မမီတော့သော ဘာသာပြန်ချက် gate +#### `check-ui-value-drift` — ခေတ်မမီသော ဘာသာပြန်ဆိုချက် gate -အခြား gate များက ဖွဲ့စည်းပုံအရ မမြင်နိုင်သော i18n regression တစ်မျိုးကို ဖမ်းဆီးသည်− အင်္ဂလိပ်တန်ဖိုးတစ်ခုကို -ပြန်လည်ရေးသားလိုက်သော်လည်း _ယခင်_ အင်္ဂလိပ်မှ ဆင်းသက်လာသော ဘာသာပြန်ချက်များက ကျန်နေသဖြင့် -အင်္ဂလိပ်မဟုတ်သော ဘာသာစကားအသုံးပြုသူများသည် ယုံကြည်ချက်ရှိရှိ ရေးသားထားသော်လည်း ယခုအခါ မှားယွင်းနေသော စာသားကို ဆက်လက်ဖတ်နေရသည်။ +အခြား gate များက ဖွဲ့စည်းပုံအရ မတွေ့နိုင်သည့် i18n regression တစ်မျိုးကို ဖမ်းယူပေးသည်။ English value တစ်ခုကို +ပြန်လည်ရေးသားလိုက်သော်လည်း _ယခင်_ English မှ ဆင်းသက်လာသည့် ဘာသာပြန်ဆိုချက်များက ကျန်ရှိနေသောကြောင့် +English မဟုတ်သော ဘာသာစကားအသုံးပြုသူများသည် ယုံကြည်စိတ်ချရသည့် လေသံဖြင့် ရေးထားသော်လည်း ယခုအခါ မှားယွင်းနေသော စာသားကို ဆက်လက်ဖတ်ရှုနေရသည်။ -ဤပြဿနာသည် အမှန်တကယ် ထုတ်လုပ်ဖြန့်ချိသွားခဲ့သည်။ Antigravity -login helper ထည့်သွင်းစဉ် (#5203) `oauthModal.googleOAuthWarning` ကို ပြန်လည်ရေးသားခဲ့သော်လည်း၊ **locale ၄၃ ခုအနက် ၃၉ ခုတွင်** operator များအား "URL အပြည့်အစုံကို copy လုပ်ပြီး အောက်တွင် paste လုပ်ပါ" ဟု ပြောထားသည့် စာသား ဆက်လက်ကျန်ရှိခဲ့သည် — ထို provider အတွက် ပြီးမြောက်အောင် မလုပ်ဆောင်နိုင်သော flow ဖြစ်သည်။ အောက်ပါအကြောင်းရင်းများကြောင့် -#8463 အထိ သတိမပြုမိခဲ့ပါ− +ဤပြဿနာသည် လက်တွေ့ release တွင် အမှန်တကယ် ဖြစ်ပွားခဲ့သည်။ Antigravity +login helper ကို ထည့်သွင်းစဉ် (#5203) `oauthModal.googleOAuthWarning` ကို ပြန်လည်ရေးသားခဲ့သော်လည်း၊ **locale 43 ခုအနက် 39 ခုတွင်** operator များအား "URL အပြည့်အစုံကို copy လုပ်ပြီး အောက်တွင် paste လုပ်ပါ" ဟု ညွှန်ကြားသော စာသားကို ဆက်လက်ထားရှိခဲ့သည် — ထို provider အတွက် ပြီးမြောက်အောင် မလုပ်ဆောင်နိုင်သည့် flow တစ်ခုဖြစ်သည်။ အောက်ပါအကြောင်းရင်းများကြောင့် +#8463 အထိ သတိမပြုမိခဲ့ပါ။ -- `sync-ui-keys` သည် **မရှိသေးသော** key များကိုသာ backfill လုပ်ပြီး **ခေတ်မမီတော့သော** key များကို မလုပ်ပါ; -- `check-ui-keys-coverage` သည် key _ရှိနေမှု_ ကို ရေတွက်သောကြောင့် ခေတ်မမီတော့သော ဘာသာပြန်ချက်ကို လွှမ်းခြုံပြီးဟု သတ်မှတ်သည်; +- `sync-ui-keys` သည် **မရှိသေးသော** key များကိုသာ ဖြည့်စွက်ပေးပြီး **ခေတ်မမီတော့သော** key များကို မည်သည့်အခါမျှ မပြင်ဆင်ပါ။ +- `check-ui-keys-coverage` သည် key _ရှိနေမှု_ ကို ရေတွက်သောကြောင့် ခေတ်မမီသော ဘာသာပြန်ဆိုချက်တစ်ခုကိုလည်း လွှမ်းခြုံထားသည်ဟု သတ်မှတ်သည်။ - `check-translation-drift` သည် `docs/i18n//**.md` documentation mirror များကို ခြေရာခံသည် — - `src/i18n/messages/*.json` ကို လုံးဝမဖတ်ပါ။ 2026-09 re-sync ကတည်းက job `docs-sync-strict` တွင် ပိတ်ဆို့ထားသည်− core doc တစ်ခုကို ပြင်ဆင်ပါ → `npm run i18n:run -- --files=` (section-level ဖြစ်၍ ကုန်ကျစရိတ်နည်းသည်)။ + `src/i18n/messages/*.json` ကို မည်သည့်အခါမျှ မဖတ်ပါ။ 2026-09 re-sync မှစ၍ job `docs-sync-strict` တွင် ပိတ်ဆို့ထားသည်။ core doc တစ်ခုကို ပြင်ဆင်ပါက → `npm run i18n:run -- --files=` (section အဆင့်၊ ကုန်ကျစရိတ်နည်း) ကို လုပ်ဆောင်ပါ။ -**Diff ကို အခြေခံသိရှိပြီး baseline ကို မမှီခိုပါ။** ၎င်းသည် merge base ရှိ `en.json` ကို -working tree နှင့် နှိုင်းယှဉ်သည်။ အင်္ဂလိပ်တန်ဖိုး ပြောင်းလဲသွားသော key တစ်ခုစီအတွက် မူလ -ဘာသာပြန်ဆိုချက်အတိုင်း မပြောင်းလဲဘဲ ရှိနေသေးသော locale တိုင်းကို ခေတ်မမီတော့ဟု သတ်မှတ်သည်။ -ဤနည်းဖြင့် **ယခင်ကတည်းက ရှိနေသော ကြွေးကျန်ကို တမင်တကာ ထိန်းထားသည်** — ကာလကြာရှည် -တည်ရှိနေသော ဘာသာပြန်ဆိုချက်တစ်ခုသည် မည်သည့် အင်္ဂလိပ်စာသားဟောင်းမှ ဆင်းသက်လာကြောင်းကို -diff ဖြင့် မဖော်ထုတ်နိုင်သောကြောင့် gate သည် လက်ရှိပြောင်းလဲမှု ထိတွေ့သည့်အရာများကိုသာ -စစ်ဆေးဆုံးဖြတ်သည်။ အခြားရွေးချယ်စရာဖြစ်သည့် key တစ်ခုချင်းစီအလိုက် hash baseline သည် -အကြီးဆုံး လက်ရှိ baseline ထက် 3× ပိုကြီးသော ~600 KB generated file တစ်ခု လိုအပ်မည်ဖြစ်ပြီး -i18n PR တိုင်းတွင် အပြောင်းအလဲများ ဆက်တိုက်ဖြစ်ပေါ်စေမည်။ +**Diff ကို အခြေခံသိရှိပြီး baseline ကို အားမကိုးပါ။** ၎င်းသည် merge base ရှိ `en.json` ကို +working tree နှင့် နှိုင်းယှဉ်သည်။ အင်္ဂလိပ်တန်ဖိုး ပြောင်းလဲသွားသော key တစ်ခုစီအတွက် မပြောင်းလဲရသေးသော +ဘာသာပြန်တန်ဖိုးကို ဆက်လက်ကိုင်ထားသည့် locale မှန်သမျှသည် ခေတ်မမီတော့ဟု သတ်မှတ်သည်။ ဤနည်းဖြင့် **ယခင်ကတည်းက ရှိနေသော ကြွေးကျန်များကို ရည်ရွယ်ချက်ရှိရှိ ရပ်တန့်ထားသည်** — ကာလရှည်ကြာ ရှိနေသော ဘာသာပြန်ချက်တစ်ခုသည် မည်သည့် အင်္ဂလိပ်စာသားအဟောင်းမှ ဆင်းသက်လာသည်ကို diff တစ်ခုဖြင့် မဖော်ထုတ်နိုင်သောကြောင့် gate သည် +လက်ရှိပြောင်းလဲမှု ထိတွေ့သည့်အရာများကိုသာ အကဲဖြတ်သည်။ အခြားနည်းလမ်းဖြစ်သော key တစ်ခုချင်းစီအလိုက် hash baseline သည် +ထုတ်လုပ်ထားသော ~600 KB ဖိုင်တစ်ခု လိုအပ်မည်ဖြစ်ပြီး လက်ရှိအကြီးဆုံး baseline ထက် 3× ကြီးကာ i18n PR တစ်ခုစီတိုင်းတွင် +မကြာခဏ ပြောင်းလဲမှုများ ဖြစ်ပေါ်စေမည်ဖြစ်သည်။ -၎င်းကို ကျော်ဖြတ်ရန် နည်းလမ်းနှစ်ခုရှိသည်- +၎င်းကို ဖြည့်ဆည်းရန် နည်းလမ်းနှစ်ခုရှိသည်- -1. သက်ရောက်မှုရှိသော ဘာသာပြန်ဆိုချက်များကို update လုပ်ပါ၊ သို့မဟုတ် -2. ၎င်းတို့ကို `__MISSING__:` ဟု သတ်မှတ်ပါ — ထို့နောက် runtime သည် ပြင်ဆင်ပြီးသော - အင်္ဂလိပ်စာသားကို ပေးမည်ဖြစ်ပြီး (`src/i18n/request.ts::deepMergeFallback`, #7258) ထို key ကို - ဘာသာပြန်ရန် queue ထဲသို့ ထည့်မည်။ +1. သက်ရောက်မှုရှိသော ဘာသာပြန်ချက်များကို အပ်ဒိတ်လုပ်ပါ၊ သို့မဟုတ် +2. ၎င်းတို့ကို `__MISSING__:` ဟု သတ်မှတ်ပါ — ထို့နောက် runtime သည် ပြင်ဆင်ထားသော အင်္ဂလိပ်စာသားကို ပေးမည် + (`src/i18n/request.ts::deepMergeFallback`, #7258) ဖြစ်ပြီး ယင်း key ကို ဘာသာပြန်ရန် တန်းစီထားမည်ဖြစ်သည်။ -စာသား၏ **အဓိပ္ပာယ်** ပြောင်းလဲသွားပါက **key ကို အမည်ပြောင်းခြင်း** ကို ဦးစားပေးပါ။ key အသစ်သည် -ခေတ်မမီတော့သော ဘာသာပြန်ဆိုချက်ကို အမွေဆက်ခံနိုင်မည်မဟုတ်ပါ။ #8463 တွင် ဤပုံစံကို အသုံးပြုခဲ့သည်။ +စာသား၏ **အဓိပ္ပာယ်** ပြောင်းလဲသွားပါက **key ကို အမည်ပြောင်းခြင်း** ကို ဦးစားပေးပါ။ key အသစ်တစ်ခုသည် +ခေတ်မမီသော ဘာသာပြန်ချက်ကို ဆက်ခံ၍မရပါ။ ဤသည်မှာ #8463 အသုံးပြုခဲ့သော ပုံစံဖြစ်သည်။ ```bash -npm run i18n:check-value-drift # တင်းကျပ်သော စစ်ဆေးမှု (CI က လုပ်ဆောင်သည့်အရာ) +npm run i18n:check-value-drift # တင်းကျပ်သောမုဒ် (CI အသုံးပြုသည့်အရာ) npm run i18n:check-value-drift:warn # အစီရင်ခံရုံသာ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -အခြေခံ catalog ကို မဖတ်နိုင်သည့်အခါ (base ref မပါသော shallow clone) `SKIP reason=base-unresolved` -နှင့်အတူ 0 ဖြင့် ထွက်ပြီး `check-openapi-breaking` ၏ လုပ်ဆောင်ပုံကို အတုယူထားသည်။ +အခြေခံ catalog ကို မဖတ်နိုင်သည့်အခါ (base ref မပါသော shallow +clone) `check-openapi-breaking` ၏ လုပ်ဆောင်ပုံအတိုင်း `SKIP reason=base-unresolved` ဖြင့် exit code 0 ထုတ်ပေးသည်။ ### Job: `i18n` -i18n validation matrix အပြည့်အစုံ (locale တစ်ခုလျှင် job တစ်ခု)။ Job တစ်ခုလုံးသည် အကြံပြုချက်သဘောသာဖြစ်သည်။ +အပြည့်အစုံသော i18n စစ်ဆေးအတည်ပြုမှု matrix (locale တစ်ခုလျှင် job တစ်ခု)။ Job တစ်ခုလုံးသည် အကြံပြုချက်သဘောသာ ဖြစ်သည်။ -| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| ------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------- | -| `validate_translation.py quick` | locale တစ်ခုချင်းစီ၏ ဘာသာပြန်ဆိုမှု ပြည့်စုံခြင်း | **အကြံပြုချက်သဘောသာ** (job တစ်ခုလုံးတွင် `continue-on-error: true`) | +| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | Merge ကို ပိတ်ဆို့မှု | +| ------------------------------- | --------------------------------------------- | ------------------------------------------------------------------- | +| `validate_translation.py quick` | locale တစ်ခုချင်းစီ၏ ဘာသာပြန်ချက် ပြည့်စုံမှု | **အကြံပြုချက်သဘောသာ** (job တစ်ခုလုံးတွင် `continue-on-error: true`) | ### Job: `pr-test-policy` Pull request များတွင်သာ လုပ်ဆောင်သည်။ -| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` သို့မဟုတ် `bin/` ရှိ production code ကို ပြောင်းလဲသော PR များတွင် test များ ထည့်သွင်းခြင်း သို့မဟုတ် update လုပ်ခြင်း ရှိရမည် (Hard Rule #8) | ရှိသည် | -| `check:test-masking` | ပြောင်းလဲထားသော test file များသည် net assert အရေအတွက်ကို မလျှော့ချရသကဲ့သို့ `assert.ok(true)` tautology များကိုလည်း မထည့်ရ | ရှိသည် | -| `check:pr-evidence` | PR body တွင် ပြောင်းလဲမှုအတွက် test/VPS အထောက်အထားကို ရည်ညွှန်းထားရမည် (PR စာသားကို grep လုပ်ခြင်းဖြင့် Hard Rule #18 ကို အလိုအလျောက် အသက်သွင်းထားသည် — ပျက်လွယ်သောကြောင့် Backlog ကို ကြည့်ပါ) | ရှိသည် | +| Script | စစ်ဆေးအတည်ပြုသည့်အရာ | Merge ကို ပိတ်ဆို့မှု | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` သို့မဟုတ် `bin/` ရှိ production code ကို ပြောင်းလဲသည့် PR များတွင် test များကို ထည့်သွင်းရန် သို့မဟုတ် အပ်ဒိတ်လုပ်ရန် လိုအပ်သည် (Hard Rule #8) | ဟုတ်သည် | +| `check:test-masking` | ပြောင်းလဲထားသော test ဖိုင်များသည် စုစုပေါင်း assert အရေအတွက်ကို မလျှော့စေရသည့်အပြင် `assert.ok(true)` ကဲ့သို့ အမြဲမှန်နေသော စစ်ဆေးချက်များကိုလည်း မထည့်ရ | ဟုတ်သည် | +| `check:pr-evidence` | PR body တွင် ပြောင်းလဲမှုအတွက် test/VPS အထောက်အထားကို ကိုးကားထားသည် (PR စာသားကို grep လုပ်ခြင်းဖြင့် Hard Rule #18 ကို စက်ဖြင့် အကောင်အထည်ဖော်သည် — ခိုင်မာမှုနည်းသောကြောင့် Backlog ကို ကြည့်ပါ) | ဟုတ်သည် | ### Job: `test-vitest` `build` ပြီးနောက် လုပ်ဆောင်သည်။ မအောင်မြင်ပါက merge ကို ပိတ်ဆို့သည်။ -| Suite | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| ---------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (tool 110 ခု), autoCombo, cache — vitest runner | ရှိသည် | -| `test:vitest:ui` | UI component test များ — vitest runner | **ပိတ်ဆို့သည်** — ယခင်ကတည်းက ရှိနေသော failure များကို `vitest.config.ts` တွင် အတိအလင်း ဖယ်ထုတ်ထားပြီး failure အသစ်များသည် job ကို ကျရှုံးစေသည် | +| Suite | စစ်ဆေးအတည်ပြုသည့်အရာ | Merge ကို ပိတ်ဆို့မှု | +| ---------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (tool 110 ခု), autoCombo, cache — vitest runner | ဟုတ်သည် | +| `test:vitest:ui` | UI component test များ — vitest runner | **ပိတ်ဆို့သည်** — ယခင်ကတည်းက ရှိနေသော မအောင်မြင်မှုများကို `vitest.config.ts` တွင် အတိအလင်း ဖယ်ထုတ်ထားပြီး မအောင်မြင်မှုအသစ်များသည် job ကို မအောင်မြင်စေသည် | -### ညစဉ် workflow များ (အချိန်ဇယားအလိုက်၊ အကြံပြုချက်သဘောသာ) +### ညစဉ် workflow များ (အချိန်ဇယားအတိုင်း၊ အကြံပြုချက်သဘောသာ) -၎င်းတို့သည် cron အချိန်ဇယားအတိုင်း (နှင့် `workflow_dispatch` ဖြင့်) လုပ်ဆောင်ပြီး PR များတွင် -မည်သည့်အခါမျှ မလုပ်ဆောင်ပါ။ အားလုံးသည် အကြံပြုချက်သဘောသာဖြစ်သည်။ +၎င်းတို့သည် cron အချိန်ဇယားအတိုင်း (နှင့် `workflow_dispatch` ဖြင့်) လုပ်ဆောင်ပြီး PR များတွင် မည်သည့်အခါမျှ မလုပ်ဆောင်ပါ။ အားလုံးသည် အကြံပြုချက်သဘောသာ ဖြစ်သည်။ -| Workflow | စစ်ဆေးအတည်ပြုသည့်အရာ | ပိတ်ဆို့မှု | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `nightly-property` | random seed နှင့် မြင့်မားသော run count ကို အသုံးပြုသည့် fast-check property test များ | **အကြံပြုချက်သဘောသာ** | -| `nightly-resilience` | heap-growth gate၊ chaos fault-injection၊ k6 load/soak | **အကြံပြုချက်သဘောသာ** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probe များ (provider secret မရှိပါက ကျော်သွားသည်) | **အကြံပြုချက်သဘောသာ** | -| `nightly-schemathesis` | `docs/openapi.yaml` ကို အသုံးပြု၍ live OmniRoute ပေါ်တွင် OpenAPI contract fuzzing (schemathesis) — spec ချိုးဖောက်မှုများ / ကိုင်တွယ်မထားသော 500 များကို ဖော်ထုတ်ပေးသည် (အဆင့် 8 B.4) | **အကြံပြုချက်သဘောသာ** | -| `nightly-mutation` | မြန်ဆန်သော unit lane ပေါ်ရှိ Stryker mutation-testing score — ဆက်လက်ရှင်သန်နေသော mutant များက အားနည်းသည့် assert များကို ဖော်ထုတ်ပေးသည် | **အကြံပြုချက်သဘောသာ** | -| `nightly-compat` | ပံ့ပိုးထားသော `engines.node` range များတစ်လျှောက် Node engine compatibility matrix | **အကြံပြုချက်သဘောသာ** | +| Workflow | စစ်ဆေးအတည်ပြုသည့်အရာ | Merge ကို ပိတ်ဆို့မှု | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `nightly-property` | ကျပန်း seed နှင့် များပြားသော run အရေအတွက်ကို အသုံးပြုသည့် fast-check property test များ | **အကြံပြုချက်သဘောသာ** | +| `nightly-resilience` | heap တိုးပွားမှု gate၊ chaos fault-injection၊ k6 load/soak | **အကြံပြုချက်သဘောသာ** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probe များ (provider secret မရှိပါက ကျော်သွားသည်) | **အကြံပြုချက်သဘောသာ** | +| `nightly-schemathesis` | `docs/openapi.yaml` ကို အသုံးပြု၍ လည်ပတ်နေသော OmniRoute ကို ဆန့်ကျင်စမ်းသပ်သည့် OpenAPI contract fuzzing (schemathesis) — spec ချိုးဖောက်မှုများ / မကိုင်တွယ်ထားသော 500 များကို ဖော်ထုတ်သည် (Fase 8 B.4) | **အကြံပြုချက်သဘောသာ** | +| `nightly-mutation` | မြန်ဆန်သော unit lane ပေါ်ရှိ Stryker mutation-testing ရမှတ် — ရှင်သန်ကျန်ရစ်သော mutant များက အားနည်းသည့် assert များကို ဖော်ထုတ်သည် | **အကြံပြုချက်သဘောသာ** | +| `nightly-compat` | ပံ့ပိုးထားသော `engines.node` range များတစ်လျှောက် Node engine လိုက်ဖက်ညီမှု matrix | **အကြံပြုချက်သဘောသာ** | --- @@ -534,47 +530,43 @@ diagnostic များကို ဖြေရှင်းနိုင်မည --- -## ကျိုးကြောင်းဆီလျော်အောင် ပြန်လည်စီစဉ်ရန် ကျန်ရှိစာရင်း (ROI ပြန်လည်သုံးသပ်မှု — အဆင့် 9 လှိုင်း 3) +## ကျိုးကြောင်းဆီလျော်အောင် ပြင်ဆင်ရန် ကျန်ရှိစာရင်း (ROI ပြန်လည်သုံးသပ်မှု — အဆင့် 9 လှိုင်း 3) -ဤစာရင်းကို 2026-06-17 ရက်နေ့တွင် `ci.yml` နှင့် တိုက်ဆိုင်စစ်ဆေးထားသည် (ယခင်ဗားရှင်းတွင် +ဤစာရင်းကို 2026-06-17 ရက်နေ့တွင် `ci.yml` နှင့် တိုက်ဆိုင်စစ်ဆေးပြီးဖြစ်သည် (ယခင်ဗားရှင်းတွင် `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` တို့ မပါရှိခဲ့ပါ)။ တိုက်ဆိုင်စစ်ဆေးထားသော အစုကို -ROI ပြန်လည်သုံးသပ်ရာမှ အောက်ပါ ကျိုးကြောင်းဆီလျော်အောင် ပြန်လည်စီစဉ်နိုင်မည့် အရာများကို -ဖော်ထုတ်ခဲ့သည်။ **ပေါင်းစည်းမှုများသည် စက်ပိုင်းဆိုင်ရာ CI အပြောင်းအလဲများဖြစ်ပြီး၊ -ပြောင်းလဲဖွင့်ခြင်း/ဖယ်ရှားခြင်းများသည် အော်ပရေတာအတွက် သီးသန့်ထားသော မူဝါဒဆုံးဖြတ်ချက်များ -ဖြစ်သည်။** အောက်ပါအရာများအနက် မည်သည့်အရာကိုမျှ အသုံးမချရသေးပါ။ - -**အထက်တွင် မှတ်တမ်းမတင်ရသေးသောအရာများလည်း ရှိသည်** (အကြံပြုအဆင့်၊ အချက်ပြမှုနည်း): -`docs-lint` job (markdownlint + Vale၊ job တစ်ခုလုံး `continue-on-error`) နှင့် သီးခြား scanner -workflow များဖြစ်သော `semgrep.yml` / `codeql.yml` / `scorecard.yml` တို့ဖြစ်သည်။ -`semgrepFindings: 0` သည် `quality-baseline.json` ထဲတွင် ရှိသော်လည်း `ci.yml` ထဲရှိ -ပိတ်ဆို့တားဆီးသော ratchet တစ်ခုနှင့် ချိတ်ဆက်ထားခြင်းမရှိပါ — လက်ရှိတွင် ဤ metric သည် -သီးခြားပြတ်တောက်နေသည်။ - -### ပေါင်းစည်းခြင်း / ထပ်နေမှုဖယ်ရှားခြင်း (စက်ပိုင်းဆိုင်ရာ၊ အန္တရာယ်ပိုနည်း) - -အဆိုပြုထားသည့် အရာတစ်ခုစီကို 2026-06-17 ရက်နေ့ရှိ တကယ့် gate အခြေအနေနှင့် တိုက်ဆိုင်၍ -အတည်ပြုထားသည် (ယုံကြည်သော်လည်း ပြန်လည်စစ်ဆေးခြင်း)။ “သိသာထင်ရှား” သော ပေါင်းစည်းမှု -အများအပြားသည် အမှန်တကယ်တွင် ကြွေးကျန်များကို ဖုံးကွယ်ထားကြောင်း တွေ့ရှိရသဖြင့် -**တိုက်ရိုက်အစားထိုးအသုံးပြုနိုင်သော သန့်ရှင်းသည့် ပေါင်းစည်းမှုများ မဟုတ်ပါ**။ - -- **`check:docs-sync` ကို နှစ်ကြိမ် လုပ်ဆောင်နေသည်** — `lint` job ထဲတွင် သီးခြားလုပ်ဆောင်ပြီး `check:docs-all` (`docs-sync-strict`) နှင့် husky pre-commit hook ထဲတွင် ထပ်မံလုပ်ဆောင်သည်။ ✅ **ပြီးစီးပြီ** — သီးခြား `lint` invocation ကို ဖယ်ရှားပြီးဖြစ်သည်။ -- **CVE စကင်ဖတ်ခြင်း** — ❌ **သန့်ရှင်းစွာ ပေါင်းစည်း၍ မရပါ။** `audit:deps` သည် high/critical CVE တစ်ခုခုရှိလျှင် ချက်ချင်း မအောင်မြင်စေသည်။ `check:vuln-ratchet` (osv) သည် baseline နှင့် နှိုင်းယှဉ်၍ _ပိုမိုဆိုးရွားလာမှု_ ရှိမှသာ မအောင်မြင်စေသည် (လက်ရှိတွင် 1 MODERATE)။ အဓိပ္ပာယ်သတ်မှတ်ချက်များ မတူညီပါ — `audit:deps` ကို ဖယ်ရှားပါက absolute high/critical gate ကို ဆုံးရှုံးမည်ဖြစ်သည်။ နှစ်ခုလုံးကို ဆက်လက်ထားရှိပါ။ -- **Cycle ရှာဖွေခြင်း** — ❌ **သန့်ရှင်းစွာ ပေါင်းစည်း၍ မရပါ။** `check:circular-deps` (dpdm) က **cycle 91 ခု** ကို အစီရင်ခံထားသည် (ထို့ကြောင့်ပင် advisory အဖြစ်ထားရှိခြင်းဖြစ်သည်)။ ၎င်းတို့ကို ဦးစွာမဖြေရှင်းဘဲ blocking အဖြစ် မြှင့်တင်၍မရသည့်အပြင် green အခြေအနေရှိပြီး ရွေးချယ်စီစဉ်ထားသော `check:cycles` ထက် scope ပိုကျယ်သည်။ `check:cycles` ကို blocking အဖြစ် ဆက်လက်ထားရှိပါ။ dpdm cycle 91 ခုကို ဖြေရှင်းခြင်းသည် သီးခြား backlog ဖြစ်သည်။ -- **ရှုပ်ထွေးမှု** — ✅ **ပြီးစီးပြီ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`)။ ESLint walk တစ်ကြိမ်တည်းဖြင့် ruleId အလိုက် ရေတွက်သဖြင့် cyclomatic+max-lines နှင့် cognitive baseline များကို သီးခြားဆက်လက်ထိန်းသိမ်းထားသည်။ တစ်ခုချင်းစီဖြစ်သော `check:complexity` / `check:cognitive-complexity` ကို local `--update` အတွက် ဆက်လက်ထားရှိသည်။ -- **`/api` anti-hallucination** — ✅ **ပြီးစီးပြီ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`)။ `src/app/api` ကို FS inventory တစ်ကြိမ်တည်း ပြုလုပ်ပြီး openapi-routes + docs-symbols တို့က သီးခြားစီ ဆက်လက်အစီရင်ခံသည်။ တစ်ခုချင်းစီကို local run များအတွက် ဆက်လက်ထားရှိသည်။ -- **`check:node-runtime` ကို job 11 ခုတွင် လုပ်ဆောင်နေသည်** — ⚠️ **ROI နည်းသည်။** တစ်ခုစီသည် သီးခြား runner ဖြစ်ပြီး check သည် <1s သာ ကြာသည်။ စုစုပေါင်းချွေတာနိုင်သည့်အချိန်မှာ ~10s ဖြစ်ကာ job တစ်ခုချင်းစီအတွက် စရိတ်နည်းသော guard ကို ဆုံးရှုံးရမည့်အခြေအနေနှင့် နှိုင်းယှဉ်လျှင် ပြောင်းလဲရသည့် အားထုတ်မှုနှင့် မထိုက်တန်ပါ။ -- **CI lint ပေါ်ရှိ `typecheck:noimplicit:core`** — ✅ **lint job မှ ဖယ်ရှားပြီးဖြစ်သည်** (ယခင်က advisory `continue-on-error` ဖြစ်ခဲ့သည်)။ blocking type surface မှာ `typecheck:core` + `check:type-coverage` ဖြစ်သည်။ Local script ကို ဆက်လက်ထားရှိသည်။ - -### ပြောင်းလဲဖွင့်ရန် / ဆုံးဖြတ်ရန် (အော်ပရေတာ မူဝါဒ) - -- `check:openapi-security-tiers` (advisory) — ❌ **သန့်ရှင်းစွာ ပြောင်းလဲဖွင့်၍ မရပါ။** ၎င်းသည် 0 ဖြင့် ထွက်သော်လည်း `LOCAL_ONLY_API_PREFIXES` အောက်ရှိ `traffic-inspector` route အများအပြားတွင် `x-loopback-only: true` annotation မရှိကြောင်း သတိပေးသည်။ ၎င်းကို အတင်းအကျပ်လိုက်နာစေရန် ဦးစွာ အဆိုပါ annotation များကို `openapi.yaml` ထဲသို့ ထည့်ရမည်။ -- `typecheck:noimplicit:core` (advisory) — blocking ဖြစ်သော `check:type-coverage` ratchet က အများစုကို လွှမ်းခြုံထားပြီးဖြစ်သည်။ ratchet အဖြစ် ပြောင်းလဲဖွင့်ပါ သို့မဟုတ် ထပ်နေသော ဒုတိယ `tsc` pass ကို ဖယ်ရှားပါ။ -- `test:vitest:ui` (ယခု **blocking**) — ယခင်ကတည်းကရှိသော failure များကို `vitest.config.ts` ထဲတွင် `// #8618` tracking comment များဖြင့် အတိအလင်း ဖယ်ထုတ်ထားသည်။ failure အသစ်များက job ကို မအောင်မြင်စေမည်။ -- `check:secrets` (gitleaks၊ မှတ်တမ်းတင်ထားသော false-positive 3 ခုတွင် ရပ်တန့်ထားသည့် blocking ratchet) — 0 သို့ရောက်ရှိရန် 3 ခုကို allowlist ထဲ ထည့်ပါ သို့မဟုတ် advisory အဆင့်သို့ လျှော့ချပါ။ GitHub native secret-scanning + `check:public-creds` နှင့် ထပ်နေသည်။ -- `check:pr-evidence` (blocking၊ PR-body စာသားကို greps လုပ်သည်) — false-positive ဖြစ်နိုင်ခြေ မြင့်မားသည်။ ဖယ်ရှားပါက Hard Rule #18 ကို အတင်းအကျပ်လိုက်နာစေမှု အားနည်းသွားမည်ဖြစ်သောကြောင့် ၎င်းသည် အမှန်တကယ် မူဝါဒဆိုင်ရာ ဆုံးဖြတ်ချက်ဖြစ်သည်။ -- `semgrep` (advisory standalone) — OWASP family များအတွက် CodeQL နှင့် ထပ်နေသည်။ ၎င်း၏ baseline ကို ratchet တစ်ခုနှင့် ချိတ်ဆက်ပါ သို့မဟုတ် ဖယ်ရှားပါ။ +`check:codeql-ratchet`, `check:pr-evidence` တို့ မပါရှိခဲ့ပါ)။ တိုက်ဆိုင်စစ်ဆေးထားသော အစုကို ROI ပြန်လည်သုံးသပ်ရာမှ +အောက်ပါ ကျိုးကြောင်းဆီလျော်အောင် ပြင်ဆင်နိုင်မည့်အရာများကို ဖော်ထုတ်နိုင်ခဲ့သည်။ **ပေါင်းစည်းမှုများသည် အလိုအလျောက်လုပ်ဆောင်နိုင်သော CI +အပြောင်းအလဲများဖြစ်ပြီး၊ အခြေအနေပြောင်းခြင်း/ဖယ်ရှားခြင်းများသည် အော်ပရေတာအတွက် သီးသန့်ထားရှိသော မူဝါဒဆိုင်ရာ ဆုံးဖြတ်ချက်များဖြစ်သည်။** အောက်ပါအရာများအနက် +မည်သည့်အရာကိုမျှ မကျင့်သုံးရသေးပါ။ + +**အထက်တွင် မှတ်တမ်းမတင်ထားသေးသည့်အရာများလည်း ရှိသည်** (အကြံပြုအဆင့်၊ အချက်ပြအားနည်းသည်) — `docs-lint` အလုပ် +(markdownlint + Vale၊ အလုပ်တစ်ခုလုံး `continue-on-error`) နှင့် သီးခြား scanner workflow များဖြစ်သော +`semgrep.yml` / `codeql.yml` / `scorecard.yml`။ `semgrepFindings: 0` သည် +`quality-baseline.json` တွင် ပါရှိသော်လည်း `ci.yml` အတွင်းရှိ ပိတ်ဆို့သည့် ratchet နှင့် ချိတ်ဆက်ထားခြင်းမရှိပါ — ထို့ကြောင့် ဤမက်ထရစ်သည် +လက်ရှိတွင် ချိတ်ဆက်မှုမရှိဘဲ သီးခြားဖြစ်နေသည်။ + +### ပေါင်းစည်းခြင်း / ထပ်နေမှုဖယ်ရှားခြင်း (အလိုအလျောက်လုပ်ဆောင်နိုင်ပြီး အန္တရာယ်နည်းသည်) + +ပြင်ဆင်နိုင်မည့်အရာတစ်ခုစီကို 2026-06-17 ရက်နေ့ရှိ အသုံးပြုနေသော gate အခြေအနေနှင့် တိုက်ဆိုင်အတည်ပြုထားသည် (ယုံကြည်သော်လည်း စစ်ဆေးအတည်ပြုပါ)။ +“သိသာထင်ရှားသည့်” ပေါင်းစည်းမှုအချို့သည် နောက်ကွယ်ရှိ နည်းပညာကြွေးမြီကို ဖုံးကွယ်ထားကြောင်း တွေ့ရှိရသဖြင့် **တိုက်ရိုက်အစားထိုးအသုံးပြုနိုင်ခြင်း မရှိပါ**။ + +- **`check:docs-sync` ကို နှစ်ကြိမ် လုပ်ဆောင်နေသည်** — `lint` အလုပ်အတွင်း သီးခြားလုပ်ဆောင်ပြီး `check:docs-all` (`docs-sync-strict`) အတွင်းနှင့် husky pre-commit hook အတွင်းတွင်လည်း ထပ်မံလုပ်ဆောင်သည်။ ✅ **ပြီးစီးပြီ** — သီးခြား `lint` ခေါ်ယူမှုကို ဖယ်ရှားပြီးဖြစ်သည်။ +- **CVE စကင်ဖတ်ခြင်း** — ❌ **ပေါင်းစည်းရန် အဆင်သင့်မဖြစ်သေးပါ။** `audit:deps` သည် high/critical CVE တစ်ခုခုရှိပါက ချက်ချင်း မအောင်မြင်စေသည်။ `check:vuln-ratchet` (osv) ကမူ baseline (လက်ရှိတွင် 1 MODERATE) နှင့် နှိုင်းယှဉ်ရာ၌ _ဆုတ်ယုတ်မှု_ ရှိမှသာ မအောင်မြင်စေသည်။ အဓိပ္ပာယ်ဖွင့်ဆိုမှု မတူညီပါ — `audit:deps` ကို ဖယ်ရှားပါက တိကျပြတ်သားသော high/critical gate ကို ဆုံးရှုံးသွားမည်ဖြစ်သည်။ နှစ်ခုစလုံးကို ဆက်လက်ထားရှိပါ။ +- **Cycle ရှာဖွေခြင်း** — ✅ **ပြီးစီးပြီ** (#15159 G-01/G-02)။ ဤနေရာရှိ စာသားဟောင်းတွင် `check:cycles` ကို “အောင်မြင်နေသော၊ ရွေးချယ်စီစဉ်ထားသည့်” gate ဟု ခေါ်ဆိုခဲ့ပြီး `check:circular-deps` (dpdm) က cycle 91 ခုကို အစီရင်ခံထားသဖြင့် ၎င်းကို ပိတ်ဆို့သည့် gate အဖြစ် ဆက်ထားရန် ကျိုးကြောင်းပြခဲ့သည်။ ထိုအောင်မြင်မှုသည် **မှားယွင်းသော အောင်မြင်မှု** ဖြစ်သည် — `check:cycles` သည် subdirectory 5 ခု (ဖိုင် 450 ခု) ကိုသာ စကင်ဖတ်ခဲ့ပြီး static `import|export … from` ကိုသာ ကိုက်ညီစစ်ဆေးကာ `@/` နှင့် `@omniroute/open-sse/` specifier အားလုံးကို ဖယ်ရှားခဲ့သောကြောင့် repo အတွင်း အများစုဖြစ်နေသည့် dynamic-import + alias cycle များကို မမြင်နိုင်ခဲ့ပါ။ ပြင်ဆင်ပြီးနောက် gate သည် ယခု `src` + `open-sse` (ဖိုင် 5023 ခု) ကို ဖြတ်သန်းစစ်ဆေးပြီး TypeScript AST မှ specifier များကို စုဆောင်းသည် (ထို့ကြောင့် `import("…")` ကို ရေတွက်ပြီး type-position `typeof import("…")` ကို မရေတွက်ပါ)၊ ထို့ပြင် tsconfig `paths` ကိုလည်း ဖြေရှင်းပေးသည်။ ၎င်းသည် 0 မဟုတ်ဘဲ cycle **14** ခုကို ရှာတွေ့သည်။ ယခင်ကတည်းကရှိနေသော cycle 14 ခုကို gate PR တစ်ခုတည်းအတွင်း မပြင်ဆင်နိုင်သောကြောင့် `check:cycles` သည် ယခု **ratchet** (`--ratchet`, `quality-baseline.json` အတွင်း ceiling `metrics.cycles.value = 14`, `direction: down`) ဖြစ်လာသည် — မည်သည့် _ဆုတ်ယုတ်မှု_ ကိုမဆို ပိတ်ဆို့ပြီး အရေအတွက်သည် လျော့ကျရန်သာ ခွင့်ပြုသည်။ CI သည် `npm run check:cycles:ratchet` ကို လုပ်ဆောင်သည်။ တဖြည်းဖြည်းလျှော့ချမှုကို **A-01** နှင့်အတူ ဆောင်ရွက်မည်။ `check:circular-deps` (dpdm) ကို ပိုမိုကျယ်ပြန့်သော ဒုတိယအမြင်အဖြစ် အကြံပြုအဆင့်တွင် ဆက်ထားမည်။ +- **ရှုပ်ထွေးမှု** — ✅ **ပြီးစီးပြီ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`) — ESLint ဖြတ်သန်းစစ်ဆေးမှုတစ်ကြိမ်တည်းဖြင့် ruleId အလိုက် ရေတွက်သဖြင့် cyclomatic+max-lines နှင့် cognitive baseline များ သီးခြားတည်ရှိနေသည်။ တစ်ခုချင်းစီဖြစ်သော `check:complexity` / `check:cognitive-complexity` ကို local `--update` အတွက် ဆက်ထားသည်။ +- **`/api` anti-hallucination** — ✅ **ပြီးစီးပြီ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`) — `src/app/api` ၏ FS inventory တစ်ကြိမ်တည်းဖြင့် openapi-routes + docs-symbols တို့က သီးခြားစီ အစီရင်ခံနေဆဲဖြစ်သည်။ တစ်ခုချင်းစီကို local run များအတွက် ဆက်ထားသည်။ +- **`check:node-runtime` ကို အလုပ် 11 ခုတွင် လုပ်ဆောင်နေသည်** — ⚠️ **ROI နည်းသည်။** တစ်ခုစီသည် သီးခြား runner ဖြစ်ပြီး စစ်ဆေးမှုသည် 1s အောက်သာ ကြာသည်။ စုစုပေါင်း ချွေတာနိုင်သည့်အချိန်မှာ ~10s ဖြစ်သော်လည်း အလုပ်တစ်ခုချင်းစီအတွက် စရိတ်နည်းသော guard ကို ဆုံးရှုံးရမည်ဖြစ်သည်။ ပြောင်းလဲရသည့် ဝန်နှင့် မတန်ပါ။ +- **CI lint ပေါ်ရှိ `typecheck:noimplicit:core`** — ✅ **lint အလုပ်မှ ဖယ်ရှားပြီးဖြစ်သည်** (ယခင်က အကြံပြုအဆင့် `continue-on-error` ဖြစ်သည်)။ ပိတ်ဆို့သည့် type surface မှာ `typecheck:core` + `check:type-coverage` ဖြစ်သည်။ Local script ကို ဆက်လက်ထားရှိသည်။ + +### အခြေအနေပြောင်းခြင်း / ဆုံးဖြတ်ခြင်း (အော်ပရေတာ မူဝါဒ) + +- `check:openapi-security-tiers` (အကြံပြုအဆင့်) — ❌ **ပိတ်ဆို့သည့်အခြေအနေသို့ တိုက်ရိုက်ပြောင်း၍ မရသေးပါ။** ၎င်းသည် 0 ဖြင့် ထွက်သော်လည်း `LOCAL_ONLY_API_PREFIXES` အောက်ရှိ `traffic-inspector` route အချို့တွင် `x-loopback-only: true` annotation မရှိကြောင်း သတိပေးသည်။ ၎င်းကို မဖြစ်မနေလိုက်နာစေရန် ဦးစွာ ထို annotation များကို `openapi.yaml` ထဲသို့ ထည့်ရမည်။ +- `typecheck:noimplicit:core` (အကြံပြုအဆင့်) — ပိတ်ဆို့သည့် `check:type-coverage` ratchet က အများအားဖြင့် အစားထိုးလွှမ်းခြုံထားသည်။ Ratchet အဖြစ် ပြောင်းပါ သို့မဟုတ် ထပ်နေသော ဒုတိယ `tsc` ဖြတ်သန်းမှုကို ဖယ်ရှားပါ။ +- `test:vitest:ui` (ယခု **ပိတ်ဆို့နေသည်**) — ယခင်ကတည်းကရှိနေသော မအောင်မြင်မှုများကို `vitest.config.ts` တွင် `// #8618` ခြေရာခံမှတ်ချက်များဖြင့် အတိအလင်း ဖယ်ထုတ်ထားသည်။ မအောင်မြင်မှုအသစ်များက အလုပ်ကို မအောင်မြင်စေသည်။ +- `check:secrets` (gitleaks၊ မှတ်တမ်းတင်ထားသော false-positive 3 ခုတွင် ရပ်တန့်ထားသည့် ပိတ်ဆို့သော ratchet) — 0 သို့ရောက်ရန် ထို 3 ခုကို allowlist ထဲ ထည့်ပါ၊ သို့မဟုတ် အကြံပြုအဆင့်သို့ လျှော့ချပါ။ GitHub ၏ မူလ secret-scanning + `check:public-creds` နှင့် ထပ်နေသည်။ +- `check:pr-evidence` (ပိတ်ဆို့ထားပြီး PR-body စကားပြေစာသားကို grep လုပ်သည်) — false-positive ဖြစ်နိုင်ခြေ မြင့်မားသည်။ ဖယ်ရှားပါက Hard Rule #18 ကို အကောင်အထည်ဖော်မှု အားနည်းသွားမည်ဖြစ်သောကြောင့် ၎င်းသည် အမှန်တကယ် မူဝါဒဆိုင်ရာ ဆုံးဖြတ်ချက်ဖြစ်သည်။ +- `semgrep` (အကြံပြုအဆင့် သီးခြား workflow) — OWASP အမျိုးအစားများအတွက် CodeQL နှင့် ထပ်နေသည်။ ၎င်း၏ baseline ကို ratchet တစ်ခုနှင့် ချိတ်ဆက်ပါ၊ သို့မဟုတ် ဖယ်ရှားပါ။ --- diff --git a/docs/i18n/my/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/my/docs/compression/COMPRESSION_GUIDE.md index 31671d20cbeb..501fc5c3c6fc 100644 --- a/docs/i18n/my/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/my/docs/compression/COMPRESSION_GUIDE.md @@ -452,8 +452,7 @@ marker တစ်ခုတည်း (`[OmniRoute Output Styles]`) ဖြင့် `messages` ပါသော body တစ်ခုတွင် content bypass (`shouldBypassCavemanOutputMode()` ကို `open-sse/services/compression/outputMode.ts` တွင် ကြည့်ပါ) သည် နောက်ဆုံး message သုံးခုကို စစ်ဆေးပြီး ၎င်းတို့သည် လုံခြုံရေး၊ ပြန်ပြင်မရသော လုပ်ဆောင်ချက်၊ ရှင်းလင်းချက်တောင်းခံမှု သို့မဟုတ် အစီအစဉ်အပေါ်မူတည်သော keyword များနှင့် ကိုက်ညီပါက -အဆိုပါ turn တစ်ခုလုံးအတွက် စတိုင်များကို ကျော်သွားသည်။ Dashboard ၏ -**Auto-Clarity Bypass** toggle (`cavemanOutputMode.autoClarity`) ကို မည်သို့သတ်မှတ်ထားသည်ဖြစ်စေ bypass က လည်ပတ်သည်။ +အဆိုပါ turn တစ်ခုလုံးအတွက် စတိုင်များကို ကျော်သွားသည်။ Dashboard ၏ **Auto-Clarity Bypass** toggle (`cavemanOutputMode.autoClarity`) ဖွင့်ထားသရွေ့ bypass က လည်ပတ်သည်၊ ဤသည်မှာ ပုံသေနည်းဖြစ်သည်။ toggle ပိတ်ထားပါက ရွေးချယ်ထားသော စတိုင်များသည် ထို turn များတွင်လည်း အသုံးချပါသည်။ bypass က turn ကို ဖြတ်သန်းခွင့်ပြုသောအခါ `placeSystemInstruction()` (ဖိုင်တူတူတွင်ရှိသည်) သည် `messages[0]` အသစ်ကို ဘယ်တော့မှ မဖန်တီးဘဲ block ကို အောက်ပါအစီအစဉ်အတိုင်း ပထမဆုံးတွေ့ရှိသည့်နေရာတွင် ထည့်သည်- diff --git a/docs/i18n/my/docs/reference/ENVIRONMENT.md b/docs/i18n/my/docs/reference/ENVIRONMENT.md index 1eaeb0191604..d603345d1b2d 100644 --- a/docs/i18n/my/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/my/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. လိုအပ်သော လျှို့ဝှက်တန်ဖိုးများ -ပထမဆုံးအကြိမ် မလည်ပတ်မီ ဤတန်ဖိုးများကို **မဖြစ်မနေ** သတ်မှတ်ထားရမည်။ ၎င်းတို့မရှိပါက အပလီကေးရှင်းသည် စတင်ရန် ငြင်းပယ်မည် သို့မဟုတ် မလုံခြုံသော မူလတန်ဖိုးများဖြင့် လည်ပတ်မည်ဖြစ်သည်။ - -| Variable | လိုအပ်မှု | မူလတန်ဖိုး | အရင်းအမြစ်ဖိုင် | ဖော်ပြချက် | -| ---------------------------- | -------------------------- | ----------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **လိုအပ်သည်** | _(မရှိပါ)_ | `src/lib/auth` | Dashboard session cookie (JWT) အားလုံးကို လက်မှတ်ထိုးခြင်း/အတည်ပြုခြင်း ပြုလုပ်သည်။ `openssl rand -base64 48` ဖြင့် ထုတ်လုပ်ပါ။ | -| `API_KEY_SECRET` | **လိုအပ်သည်** | _(မရှိပါ)_ | `src/lib/db/apiKeys.ts` | SQLite တွင် သိမ်းဆည်းထားသော API key တန်ဖိုးများအတွက် AES ကုဒ်ဝှက်ရေး key ဖြစ်သည်။ `openssl rand -hex 32` ဖြင့် ထုတ်လုပ်ပါ။ | -| `INITIAL_PASSWORD` | **လိုအပ်သည်** | `CHANGEME` | Bootstrap script | ကနဦး admin dashboard စကားဝှက်ကို သတ်မှတ်သည် (`.env.example` ၏ မူလတန်ဖိုးနှင့် ကိုက်ညီသည် — ပြောင်းလဲရန် မဖြစ်မနေလိုအပ်ကြောင်း သိသာစေရန် ရည်ရွယ်ချက်ရှိရှိ မလုံခြုံအောင် ထားရှိထားသည်)။ **ပထမဆုံးအသုံးမပြုမီ ပြောင်းလဲပါ။** Login ဝင်ပြီးနောက် Dashboard → Settings → Security မှတစ်ဆင့် ပြောင်းလဲပါ။ | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **လိုအပ်သည်** (production) | _(မသတ်မှတ်ထားပါ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | အတွင်းပိုင်း Codex Responses WebSocket bridge အတွက် မျှဝေသုံးစွဲသော လျှို့ဝှက်တန်ဖိုးဖြစ်သည်။ Electron/browser WS relay နှင့် OmniRoute အကြား bridge request များကို အထောက်အထားစစ်မှန်ကြောင်း စစ်ဆေးပေးသည်။ ⚠️ **Production တွင် မဖြစ်မနေလိုအပ်သည် — မသတ်မှတ်ထားပါက WS bridge request အားလုံးကို ငြင်းပယ်မည်။** `openssl rand -base64 32` ဖြင့် ထုတ်လုပ်ပါ။ | -| `OMNIROUTE_SW_BUILD_ID` | မလိုအပ်ပါ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA shell (#11779) အတွက် service-worker cache ကို မဖြစ်မနေ အသစ်ပြန်လည်ရယူစေမည့် သီးခြား id ဖြစ်ပြီး resolution chain တွင် ပထမဆုံးဖြစ်သည်။ | -| `SOURCE_VERSION` | မလိုအပ်ပါ | _(မသတ်မှတ်ထားပါ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Chain ၏ ဒုတိယတန်ဖိုးဖြစ်ပြီး deploy လုပ်ထားသော commit အဖြစ် PaaS builder များ (ဥပမာ Heroku ပုံစံ) က သတ်မှတ်သည်။ | -| `NEXT_PUBLIC_SW_BUILD_ID` | မလိုအပ်ပါ | _(ဆင့်ပွားရရှိသည်)_ | `src/shared/components/PwaRegister.tsx` | Client က `/sw.js?v=…` ကို register လုပ်ရာတွင် အသုံးပြုသည့် build-time public တန်ဖိုးဖြစ်သည်။ အထက်ပါနှစ်ခုမှ ဆင့်ပွားရရှိပြီး ထို့နောက် git SHA ကို အသုံးပြုသည်။ | -| `OMNIROUTE_PEER_STAMP_TOKEN` | မလိုအပ်ပါ (အလိုအလျောက်) | _(boot တစ်ကြိမ်စီတွင် အလိုအလျောက်)_ | `src/server/authz/policies/management.ts` | ယုံကြည်စိတ်ချရသော peer-IP stamp သည် OmniRoute ၏ ကိုယ်ပိုင် HTTP server (`scripts/dev/peer-stamp.mjs`) မှ လာကြောင်း သက်သေပြသည့် process တစ်ခုချင်းစီအလိုက် လျှို့ဝှက်တန်ဖိုးဖြစ်သည်။ Stamp တွင် ဤ token ပါရှိမှသာ authz middleware သည် request ၏ local ဖြစ်မှုကို ယုံကြည်သည် (LOCAL_ONLY route များ၏ loopback/LAN ကန့်သတ်မှု)။ Boot တစ်ကြိမ်စီတွင် အလိုအလျောက် ထုတ်လုပ်ပေးသောကြောင့် မသတ်မှတ်ဘဲထားပါ။ Stamp ကို မျှဝေရန်လိုအပ်သည့် multi-process setup များအတွက်သာ တန်ဖိုးကို ပုံသေသတ်မှတ်ပါ။ | -| `OMNIROUTE_TRUSTED_PROXIES` | မလိုအပ်ပါ | _(မသတ်မှတ်ထားပါ)_ | `scripts/dev/peer-stamp.mjs` | IP ခွင့်ပြု/ငြင်းပယ်စာရင်းက ယုံကြည်နိုင်သော `X-Forwarded-For` / `X-Real-IP` ပါရှိသည့် reverse proxy များ၏ ကော်မာဖြင့် ခွဲထားသော IP များ သို့မဟုတ် CIDR range များဖြစ်သည်။ အမြဲယုံကြည်ထားသည့် loopback၊ private-network address များနှင့် Cloudflare edge များအပြင် ထပ်မံသတ်မှတ်ခြင်းဖြစ်သည်။ အခြား public address တစ်ခုခုရှိ proxy အတွက်သာ လိုအပ်သည်။ မဟုတ်ပါက filter က ထို proxy ၏ ကိုယ်ပိုင် address ကိုသာ စစ်ဆေးဆုံးဖြတ်မည်။ | +**လိုအပ်သည်** ဟု မှတ်သားထားပြီး မသတ်မှတ်ရသေးသော လျှို့ဝှက်တန်ဖိုးများ (`JWT_SECRET`, `API_KEY_SECRET`) ကို ပထမဆုံးအကြိမ် လည်ပတ်သည့်အခါ အလိုအလျောက် ထုတ်ပေးပြီး data directory ရှိ `server.env` တွင် သိမ်းဆည်းသည် — ထို directory သည် ယာယီဖြစ်နေပါက ၎င်းတို့ကို အတိအလင်း သတ်မှတ်ပါ။ ခြားနားချက်မှာ `OMNIROUTE_WS_BRIDGE_SECRET` ဖြစ်သည် — ၎င်းကို `server.env` တွင် မသိမ်းဆည်းပါ — server entrypoint များသည် boot တစ်ကြိမ်စီအတွက် process အတွင်းရှိ bridge ကသာ အသုံးပြုနိုင်သည့် ယာယီတန်ဖိုးတစ်ခု ထုတ်ပေးသောကြောင့် ပြင်ပ bridge client များအတွက် ၎င်းကို အတိအလင်း သတ်မှတ်ပေးရမည်။ + +| Variable | လိုအပ်မှု | မူလတန်ဖိုး | အရင်းအမြစ်ဖိုင် | ဖော်ပြချက် | +| ---------------------------- | -------------------------- | ----------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **လိုအပ်သည်** | _(မရှိပါ)_ | `src/lib/auth` | Dashboard session cookie (JWT) အားလုံးကို လက်မှတ်ထိုးခြင်း/အတည်ပြုခြင်း ပြုလုပ်သည်။ `openssl rand -base64 48` ဖြင့် ထုတ်လုပ်ပါ။ | +| `API_KEY_SECRET` | **လိုအပ်သည်** | _(မရှိပါ)_ | `src/lib/db/apiKeys.ts` | SQLite တွင် သိမ်းဆည်းထားသော API key တန်ဖိုးများအတွက် AES ကုဒ်ဝှက်ရေး key ဖြစ်သည်။ `openssl rand -hex 32` ဖြင့် ထုတ်လုပ်ပါ။ | +| `INITIAL_PASSWORD` | မလိုအပ်ပါ | _(မသတ်မှတ်ထားပါ)_ | `src/lib/auth/managementPassword.ts` | မည်သည့်စကားဝှက်မျှ သိမ်းဆည်းထားခြင်း မရှိသေးသည့်အခါ startup တွင် dashboard စကားဝှက်ကို သတ်မှတ်ပေးပြီး စကားဝှက် သိမ်းဆည်းပြီးသည်နှင့် ပယ်ဖျောက်ခြင်း ခံရသည်။ npm နှင့် source install များသည် `.env.example` ကို `.env` သို့ ကူးယူသောကြောင့် ၎င်းတို့သည် အများသိထားသော placeholder `CHANGEME` ဖြင့်သာ စတင်ကြသည် — ပထမဆုံး boot မပြုလုပ်မီ ပြောင်းလဲပါ၊ သို့မဟုတ် ပထမအကြိမ် ဝင်ရောက်သည့်အခါ onboarding wizard တွင် စကားဝှက် ဖန်တီးနိုင်ရန် ထို `.env` တွင် `INITIAL_PASSWORD=` (အလွတ်) ဟု သတ်မှတ်ပါ — မသတ်မှတ်ထားသောတန်ဖိုး (Docker image ၏ မူလတန်ဖိုး) ပြုလုပ်သကဲ့သို့ပင်။ သိမ်းဆည်းထားသော စကားဝှက်ကို Dashboard → Settings → Security တွင် ပြောင်းလဲပါ၊ သို့မဟုတ် `DATA_DIR` ကို server ၏ data directory သို့ သတ်မှတ်ကာ `omniroute reset-password` (source checkout တစ်ခုတွင် `node bin/reset-password.mjs`) ကို လည်ပတ်ပါ။ | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **လိုအပ်သည်** (production) | _(မသတ်မှတ်ထားပါ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | အတွင်းပိုင်း Codex Responses WebSocket bridge အတွက် မျှဝေသုံးစွဲသော လျှို့ဝှက်တန်ဖိုးဖြစ်သည်။ Electron/browser WS relay နှင့် OmniRoute အကြား bridge request များကို အထောက်အထားစစ်မှန်ကြောင်း စစ်ဆေးပေးသည်။ ⚠️ **Production တွင် မဖြစ်မနေလိုအပ်သည် — မသတ်မှတ်ထားပါက WS bridge request အားလုံးကို ငြင်းပယ်မည်။** `openssl rand -base64 32` ဖြင့် ထုတ်လုပ်ပါ။ | +| `OMNIROUTE_SW_BUILD_ID` | မလိုအပ်ပါ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA shell (#11779) အတွက် service-worker cache ကို မဖြစ်မနေ အသစ်ပြန်လည်ရယူစေမည့် သီးခြား id ဖြစ်ပြီး resolution chain တွင် ပထမဆုံးဖြစ်သည်။ | +| `SOURCE_VERSION` | မလိုအပ်ပါ | _(မသတ်မှတ်ထားပါ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Chain ၏ ဒုတိယတန်ဖိုးဖြစ်ပြီး deploy လုပ်ထားသော commit အဖြစ် PaaS builder များ (ဥပမာ Heroku ပုံစံ) က သတ်မှတ်သည်။ | +| `NEXT_PUBLIC_SW_BUILD_ID` | မလိုအပ်ပါ | _(ဆင့်ပွားရရှိသည်)_ | `src/shared/components/PwaRegister.tsx` | Client က `/sw.js?v=…` ကို register လုပ်ရာတွင် အသုံးပြုသည့် build-time public တန်ဖိုးဖြစ်သည်။ အထက်ပါနှစ်ခုမှ ဆင့်ပွားရရှိပြီး ထို့နောက် git SHA ကို အသုံးပြုသည်။ | +| `OMNIROUTE_PEER_STAMP_TOKEN` | မလိုအပ်ပါ (အလိုအလျောက်) | _(boot တစ်ကြိမ်စီတွင် အလိုအလျောက်)_ | `src/server/authz/policies/management.ts` | ယုံကြည်စိတ်ချရသော peer-IP stamp သည် OmniRoute ၏ ကိုယ်ပိုင် HTTP server (`scripts/dev/peer-stamp.mjs`) မှ လာကြောင်း သက်သေပြသည့် process တစ်ခုချင်းစီအလိုက် လျှို့ဝှက်တန်ဖိုးဖြစ်သည်။ Stamp တွင် ဤ token ပါရှိမှသာ authz middleware သည် request ၏ local ဖြစ်မှုကို ယုံကြည်သည် (LOCAL_ONLY route များ၏ loopback/LAN ကန့်သတ်မှု)။ Boot တစ်ကြိမ်စီတွင် အလိုအလျောက် ထုတ်လုပ်ပေးသောကြောင့် မသတ်မှတ်ဘဲထားပါ။ Stamp ကို မျှဝေရန်လိုအပ်သည့် multi-process setup များအတွက်သာ တန်ဖိုးကို ပုံသေသတ်မှတ်ပါ။ | +| `OMNIROUTE_TRUSTED_PROXIES` | မလိုအပ်ပါ | _(မသတ်မှတ်ထားပါ)_ | `scripts/dev/peer-stamp.mjs` | IP ခွင့်ပြု/ငြင်းပယ်စာရင်းက ယုံကြည်နိုင်သော `X-Forwarded-For` / `X-Real-IP` ပါရှိသည့် reverse proxy များ၏ ကော်မာဖြင့် ခွဲထားသော IP များ သို့မဟုတ် CIDR range များဖြစ်သည်။ အမြဲယုံကြည်ထားသည့် loopback၊ private-network address များနှင့် Cloudflare edge များအပြင် ထပ်မံသတ်မှတ်ခြင်းဖြစ်သည်။ အခြား public address တစ်ခုခုရှိ proxy အတွက်သာ လိုအပ်သည်။ မဟုတ်ပါက filter က ထို proxy ၏ ကိုယ်ပိုင် address ကိုသာ စစ်ဆေးဆုံးဖြတ်မည်။ | ### ထုတ်လုပ်ရန် Command များ @@ -215,38 +215,38 @@ OmniRoute သည် အမြဲတမ်းဒေတာသိမ်းဆည် ## 4. လုံခြုံရေးနှင့် စစ်မှန်ကြောင်းအတည်ပြုခြင်း -| Variable | Default | Source File | Description | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | စက်၏ လက်ဗွေဖော်ထုတ်ခြင်းအတွက် hardware identifier များနှင့် ပေါင်းစပ်အသုံးပြုသည့် salt ဖြစ်သည်။ သီးခြားခွဲထားနိုင်ရန် deployment တစ်ခုစီအလိုက် ပြောင်းလဲပါ။ | -| `OMNIROUTE_CLI_SALT` | _(မသတ်မှတ်ထားပါ = installation တစ်ခုစီအလိုက် ကျပန်း salt ကို `/cli-token-salt.json` တွင် သိမ်းဆည်းထားသည်)_ | `src/lib/machineToken.ts` | local CLI auth token ကို ဆင်းသက်ထုတ်လုပ်ရန် အသုံးပြုသည့် HMAC salt ဖြစ်သည်။ ဤတန်ဖိုးကို သတ်မှတ်ခြင်းဖြင့် စက်ပေါ်ရှိ CLI token အားလုံးကို ပြောင်းလဲအသစ်ထုတ်ပေးပြီး သိမ်းဆည်းထားသော salt ထက် အမြဲတမ်း ဦးစားပေးအသုံးပြုသည်။ `docs/security/CLI_TOKEN.md` ကို ကြည့်ပါ။ | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | session cookie များတွင် `Secure` flag ကို သတ်မှတ်သည်။ HTTPS နောက်ကွယ်တွင် လုပ်ဆောင်သည့်အခါ **`true` ဖြစ်ရမည်**။ | -| `REQUIRE_API_KEY` | `false` | API middleware | `true` ဖြစ်သည့်အခါ `/v1/*` proxy request အားလုံးတွင် မှန်ကန်သော API key တစ်ခု ပါဝင်ရမည်။ **ဤ flag သည် `GET /v1/models` ကို ထိန်းချုပ်မထားပါ**။ ယင်းအစား ၎င်းသည် dashboard login posture (`requireAuthForModels`) ကို လိုက်နာသည် — ထို့ကြောင့် `/v1/models` မှ `401` ပြန်လာခြင်းသည် inference ကို ကာကွယ်ထားသည်ဟု မဆိုလိုပါ။ `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ကို ကြည့်ပါ။ | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Dashboard UI တွင် API key တန်ဖိုးအပြည့်အစုံကို ဖော်ပြခွင့်ပြုသည်။ Dashboard Feature Flags မှ ပြင်ဆင်သတ်မှတ်နိုင်ပြီး မျှဝေအသုံးပြုသည့် instance များတွင် လုံခြုံရေးအန္တရာယ်ရှိသည်။ | -| `NO_LOG_API_KEY_IDS` | _(ဗလာ)_ | `src/lib/compliance/index.ts` | request logging ကို ကျော်လွှားမည့် API key ID များကို ကော်မာဖြင့် ခွဲခြားဖော်ပြထားသော စာရင်းဖြစ်သည် (GDPR လိုက်နာမှုအတွက်)။ | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(မသတ်မှတ်ထားပါ = အကန့်အသတ်မရှိ)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` column သည် null ဖြစ်နေသော API key များတွင် အသုံးပြုမည့် တစ်ရက်စာ request ပမာဏ၏ အရန်သတ်မှတ်ချက်ဖြစ်သည်။ မသတ်မှတ်ထားခြင်း သို့မဟုတ် ဗလာဖြစ်ခြင်းသည် သွယ်ဝိုက်သတ်မှတ်ထားသော အကန့်အသတ်မရှိဟု ဆိုလိုသည် (#2289, #11017)။ `0` သည်လည်း အတူတူပင်ဖြစ်ပြီး အကန့်အသတ်မရှိပါ။ အပေါင်းကိန်း N သည် N/day၊ 5N/week၊ 20N/month ကို ဖွင့်ပေးသည်။ ပုံစံမမှန်သော ဗလာမဟုတ်သည့် တန်ဖိုးများအတွက် ယခင် 1000/day၊ 5000/week၊ 20000/month အချိန်ကာလကန့်သတ်ချက်များကို အရန်အဖြစ် အသုံးပြုသည်။ | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ခွင့်ပြုထားသော request body ၏ အများဆုံးအရွယ်အစားဖြစ်သည်။ ဤကန့်သတ်ချက်ထက် ကျော်လွန်သော payload များကို ငြင်းပယ်သည်။ | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ဤသတ်မှတ်ချက်နှင့် ညီမျှသော သို့မဟုတ် ကျော်လွန်သော အမှန်တကယ် request body များသည် JSON parsing မပြုလုပ်မီ atomic process-local heavyweight admission lease ကို ရယူသည် (BYTE လမ်းကြောင်း၊ `POST /v1/responses` အပါအဝင်)။ structure-heavy ကဲ့သို့ပင် [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ၏ healthy-headroom escape ကို အသုံးပြုသည်။ ထို့အပြင် [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ပြန်မဖြစ်စေရန် `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ဖြင့် ဆက်လက်ကန့်သတ်ထားသည်။ | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ကန့်သတ်ထားသော လက်ခံဖတ်ရှုမှုအတွင်း ဖတ်ယူသည့် byte များအပေါ် မူတည်၍ သတ်မှတ်ထားသော chat route ၏ အမြင့်ဆုံးကန့်သတ်ချက်ဖြစ်ပြီး `Content-Length` မပါရှိသော၊ မမှန်ကန်သော သို့မဟုတ် အမှန်မဖော်ပြသော request များလည်း အကျုံးဝင်သည်။ ကန့်သတ်ချက်ကျော်လွန်ပါက `413` ရရှိမည်။ | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(မသတ်မှတ်ထား — request အရေအတွက် ကန့်သတ်ချက်မရှိ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ဤအမွေဆက်ခံထားသော request အရေအတွက် ကန့်သတ်ချက်သည် တိတိကျကျ သတ်မှတ်ထားမှသာ သက်ရောက်သည်။ မသတ်မှတ်ဘဲထားပါက (မူလတန်ဖိုး) heavyweight chat လက်ခံမှုကို `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` က ထိန်းချုပ်သည် — **process တစ်ခုအတွင်း** (V8 heap တစ်ခု) process ၏ အမှန်တကယ် memory အမြင့်ဆုံးကန့်သတ်ချက်မှ အလိုအလျောက် တွက်ချက်ထားသော BYTE budget ဖြစ်သည်။ တစ်ချိန်တည်းထပ်နေသည့် token ~750k ပါ `/v1/responses` နှစ်ခုသည် ~12 Gi heap များကို ရပ်တန့်စေသည် (#7849) — ၎င်းသည် **memory-budget** သတိပေးချက်ဖြစ်ပြီး product ၏ အတိအကျ အများဆုံးတန်ဖိုး 2 ဟု ဆိုလိုခြင်းမဟုတ်ပါ။ ကောင်းမွန်သော process တစ်ခု (heap သည် shed ratio အောက်တွင်ရှိခြင်း) သည် `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` မှတစ်ဆင့် တစ်ပြိုင်နက် long `/v1/responses` များကို ပိုမိုလက်ခံနိုင်သည်။ long SSE client ဆယ်ဂဏန်း (40–50) ကို လက်ခံနိုင်မှုသည် heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ပေါ် မူတည်ပြီး “အများဆုံး 2” ဟု မဆိုလိုပါ။ “host ကို အပြည့်အသုံးပြုရန်” ဟူ၍ ဤတန်ဖိုးကို မစဉ်းစားဘဲ မြှင့်ခြင်းသည် #7849 ကို ပြန်ဖြစ်စေသည်။ **N ခုသော သီးခြား `DATA_DIR` များ** (#11024) ဖြင့် **heap** အရေအတွက်ကို တိုးပါ။ SQLite file တစ်ခုပေါ်တွင် `replicas>1` ကို လုံးဝ မသုံးပါနှင့်။ | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(အလိုအလျောက် တွက်ချက်ထားသည်)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** အလိုအလျောက် တွက်ချက်ထားသော ingest byte budget ကို အစားထိုးသတ်မှတ်ရန်ဖြစ်သည် (ပိုမိုတင်းကျပ်သော V8/cgroup memory အမြင့်ဆုံးကန့်သတ်ချက်၏ 25% ကို ယာယီချဲ့ထွင်မှု 8x ဖြင့် စားထားသည်)။ တွက်ချက်ထားသောတန်ဖိုးနှင့် တိတိကျကျသတ်မှတ်ထားသောတန်ဖိုး နှစ်မျိုးစလုံးကို 8 MiB–2 GiB အတွင်း ကန့်သတ်ထားသည်။ ထိရောက်သော budget ထက် body ပိုကြီးပါက `413 body_exceeds_budget` ဖြင့် ချက်ချင်းပျက်ကွက်မည်။ တစ်ခုချင်းစီအနေဖြင့် ဝန်ဆောင်မှုပေးနိုင်သော body များအကြား အရင်းအမြစ်လုယူမှုဖြစ်ပါက ပြန်လည်ကြိုးစားနိုင်သော `503` အဖြစ် ဆက်လက်ရှိမည်။ တစ်ပြိုင်နက် long SSE client 40–50 ခုကို လက်ခံနိုင်မှုသည် ဤ budget + heap ပေါ် မူတည်ပြီး “အများဆုံး 2” ဟူသော အတိအကျကန့်သတ်ချက် မဟုတ်ပါ။ ချိန်ညှိခြင်းမပြုမီ `/api/monitoring/health` ရှိ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ကို ဖတ်ရှုပါ။ | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE နှင့် STRUCTURE heavyweight လက်ခံမှုအတွက် heap-pressure shed ratio (`heapUsed / heap_size_limit`) ဖြစ်သည် (#10183, #10268, #10437)။ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ကို ကျော်လွန်သော တစ်ပြိုင်နက် heavyweight request ကို heap သည်လည်း ဤ ratio သို့မဟုတ် ထိုထက်မြင့်နေမှသာ ပြန်လည်ကြိုးစားနိုင်သော `503` ဖြင့် ဖယ်ရှားမည်။ ကောင်းမွန်သော heap တွင်မူ healthy-headroom မှတစ်ဆင့် လက်ခံမည်။ | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (မူလတန်ဖိုး `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE နှင့် BYTE **နှစ်မျိုးစလုံး** အတွက် ကောင်းမွန်သော heap fast path (#10437) ၏ ကန့်သတ်ထားသည့် အပိုစွမ်းရည်ဖြစ်သည် (`admitChatRequest`၊ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` နှင့်အထက် body များအပါအဝင်)။ ဤကန့်သတ်ချက်မရှိပါက အလုပ်များနေသော်လည်း heap ကောင်းမွန်သည့် request တိုင်းသည် အမြင့်ဆုံးကန့်သတ်ချက်မရှိဘဲ လက်ခံမှုထိန်းချုပ်ရေးကို ကျော်သွားမည်။ healthy-heap path မှတစ်ဆင့် ဤအရေအတွက်အထိ တစ်ပြိုင်နက် lease များ အသက်ဝင်လာပြီးနောက် နောက်ထပ် အလုပ်များနေသော request များသည် အမှန်တကယ် heap pressure အောက်တွင် အသုံးပြုသည့် တူညီသော ကန့်သတ်စောင့်ဆိုင်းမှု/shed path သို့ ရောက်သွားမည်။ `0` သည် bypass ကို လုံးဝပိတ်သည်။ | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | body သည် byte အနိမ့်ဆုံးသတ်မှတ်ချက်အောက်တွင် ရှိနေသော်လည်း chat request ကို heavyweight အဖြစ် သတ်မှတ်သည့် message အရေအတွက်ဖြစ်သည်။ | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | body သည် byte အနိမ့်ဆုံးသတ်မှတ်ချက်အောက်တွင် ရှိနေသော်လည်း chat request ကို heavyweight အဖြစ် သတ်မှတ်သည့် tool အရေအတွက်ဖြစ်သည်။ | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | request တစ်ခုကို heavyweight အဖြစ် သတ်မှတ်သည့် ကြိုတင်ကာကွယ်မှုမြင့်သော string အရွယ်အစားအခြေပြု token ခန့်မှန်းချက်ဖြစ်သည်။ ၎င်းသည် လက်ခံမှုကုန်ကျစရိတ်အတွက် ကိုယ်စားပြုတန်ဖိုးသာဖြစ်ပြီး provider ၏ billing tokenization မဟုတ်ပါ။ | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ပိတ်ထားသည်) | `src/shared/middleware/chatBodyAdmission.ts` | ဆန္ဒရှိမှ ဖွင့်သုံးနိုင်သော chat history ကန့်သတ်ချက်ဖြစ်သည်။ မူလအားဖြင့် ပိတ်ထားသည်။ message အရေအတွက်သည် deployment policy ဖြစ်ပြီး request တစ်ခု၏ နေရာတိုင်းနှင့်သက်ဆိုင်သော ပင်ကိုဂုဏ်သတ္တိ မဟုတ်သည့်အပြင် ဤနေရာတွင် ကန့်သတ်ခြင်းက compression pipeline မှ ဝန်ဆောင်မှုပေးနိုင်အောင် မလုပ်ဆောင်မီ conversation များကို အပြီးသတ် `413` ဖြင့် ငြင်းပယ်စေသည်။ Heap ကြီးထွားမှုကို `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` နှင့် heap-pressure shed တို့ဖြင့် ကန့်သတ်ထားသည်။ အတိအကျ အမြင့်ဆုံးကန့်သတ်ချက်လိုအပ်သော memory နည်းပါးသည့် deployment များတွင် အပေါင်းတန်ဖိုးတစ်ခု သတ်မှတ်ပါ။ ထိုအခါ ကန့်သတ်ချက်ကျော်လွန်မှုသည် ဖွဲ့စည်းပုံပါ compact-required `413` ကို ရရှိမည်။ | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | memory ထဲသို့ အပြည့်အဝ buffer လုပ်ထားသော non-streaming upstream response အတွက် အတိအကျ အမြင့်ဆုံးကန့်သတ်ချက်ဖြစ်သည်။ ၎င်းကို ကျော်လွန်ပါက upstream reader ကို ပယ်ဖျက်ပြီး heap ကုန်ဆုံးသည်အထိ အကန့်အသတ်မရှိ string ကြီးထွားနေမည့်အစား request ကို ချက်ချင်း ပျက်ကွက်စေသည်။ | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | အထက်ပိုင်းတုံ့ပြန်မှု header များမှ ထပ်ဆင့်ပို့သည့် wire byte အများဆုံးပမာဏ။ သတ်မှတ်ပမာဏကို ကျော်လွန်သောအခါ အသုံးများသည့် reverse-proxy header ကန့်သတ်ချက်များအတွင်း ရှိနေစေရန် ဦးစားပေးမှုနိမ့်သည့် header များ (ဥပမာ၊ စိတ်ကြိုက် `x-codex-*`, `x-oai-request-id`) ကို ဖယ်ရှားသည်။ တုံ့ပြန်မှု header အရွယ်အစား ပိုကြီးလာခြင်းကို လက်ခံပြီး အထက်ပိုင်း metadata များကို ပိုမိုထပ်ဆင့်ပို့လိုပါက ပိုမြင့်သည့်တန်ဖိုး သတ်မှတ်ပါ။ | -| `CORS_ORIGIN` | _(မသတ်မှတ်ထား)_ | `src/server/cors/origins.ts` | ယခင်စနစ်သုံး မူလရင်းမြစ်တစ်ခုတည်းအတွက် CORS ခွင့်ပြုစာရင်း။ ဖြန့်ကျက်မှုအသစ်များအတွက် `CORS_ALLOWED_ORIGINS` ကို ဦးစားပေးပါ။ CORS သည် မတူညီသောမူလရင်းမြစ်မှ browser API client များအတွက်သာ ဖြစ်သည်။ အထောက်အထားစစ်ဆေးထားသော dashboard ရေးသားမှုများသည် ယင်းအစား မူလရင်းမြစ်တူ request များနှင့် session နှင့် ချိတ်ဆက်ထားသည့် CSRF ကာကွယ်မှုကို အသုံးပြုသည်။ | -| `CORS_ALLOWED_ORIGINS` | _(မသတ်မှတ်ထား)_ | `src/server/cors/origins.ts` | ကော်မာဖြင့် ခွဲထားသော CORS ခွင့်ပြုစာရင်း။ `CORS_ALLOW_ALL=true` ကို အတိအလင်း စီစဉ်သတ်မှတ်မထားပါက wildcard ကို ပို့မည်မဟုတ်ပါ။ | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | မည်သည့် browser `Origin` ကိုမဆို ပြန်လည်တုံ့ပြန်ရန် development အတွက်သာ အသုံးပြုသည့် အရေးပေါ်ရှောင်လမ်း။ မျှဝေသုံးစွဲသော သို့မဟုတ် production ဖြန့်ကျက်မှုများတွင် မဖွင့်ပါနှင့်။ | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | private/loopback/link-local IP အပိုင်းအခြားများကို ပစ်မှတ်ထားသည့် provider call များကို ပိတ်ဆို့ပါ။ သီးခြားခွဲထားသော test environment များတွင်သာ ပိတ်ပါ။ | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | private/local network များ (localhost, 192.168.x.x, 10.x.x.x စသည်) သို့ ညွှန်ပြသည့် provider URL များကို ခွင့်ပြုပါ။ **ကိုယ်တိုင် host လုပ်ထားသော provider များအတွက် မဖြစ်မနေလိုအပ်သည်** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)။ `false` ဖြစ်သည့်အခါ dashboard သည် local URL များကို အတည်ပြုစစ်ဆေးခြင်းအား ငြင်းပယ်သည်။ | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | local/private လိပ်စာများ (127.0.0.1, localhost, LAN, private အပိုင်းအခြားများ) ပေါ်ရှိ provider များကို ထည့်သွင်းခြင်း/အတည်ပြုစစ်ဆေးခြင်းအား ခွင့်ပြုပါ — provider အတည်ပြုစစ်ဆေးမှုလမ်းကြောင်းအတွက်သာ သက်ရောက်သည်။ **မူလတန်ဖိုးမှာ `true` ဖြစ်သည်** (local-first)။ public လိပ်စာများကိုသာ တင်းကျပ်စွာ ခွင့်ပြုရန် `false` သတ်မှတ်ပါ။ Cloud-metadata endpoint များ (169.254.169.254, metadata.google.internal) ကို မည်သည့်အခြေအနေတွင်မဆို ဆက်လက်ပိတ်ဆို့ထားသည်။ (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` route များ (စာသားကူးပြောင်းမှုများ၊ အသံပြောဆိုမှု၊ ဘာသာပြန်ဆိုမှုများ) ကို localhost ပြင်ပတွင် host လုပ်ထားသည့် OpenAI-compatible provider node တစ်ခု အသုံးပြုခွင့်ပေးပါ။ မူလအားဖြင့် ပိတ်ထားသည် — audio ကို remote host တစ်ခုသို့ လမ်းကြောင်းပေးခြင်းသည် egress identity ကို ပြောင်းလဲစေသောကြောင့် operator က အတိအလင်း ဆုံးဖြတ်ရမည်။ Loopback/private node များ (localhost, 127.0.0.1, 172.16-31.x) ကို အမြဲခွင့်ပြုထားပြီး သက်ရောက်မှုမရှိပါ။ (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (နှင့် memory engine ၏ loopback rerank အဆင့်) ကို localhost ပြင်ပတွင် host လုပ်ထားသည့် OpenAI-compatible provider node — TEI, Infinity, vLLM စသည်တို့ အသုံးပြုနေသော LAN စက် သို့မဟုတ် Tailscale peer — အား အသုံးပြုခွင့်ပေးပါ။ မူလအားဖြင့် ပိတ်ထားသည် — remote host တစ်ခုသို့ လမ်းကြောင်းပေးခြင်းသည် egress identity ကို ပြောင်းလဲစေသောကြောင့် operator က အတိအလင်း ဆုံးဖြတ်ရမည်။ Loopback node များ (localhost, 127.0.0.1, 172.16-31.x) ကို အမြဲခွင့်ပြုထားပြီး သက်ရောက်မှုမရှိပါ။ Remote node များသည် provider outbound URL မူဝါဒ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ကိုလည်း ကျော်ဖြတ်ရမည်။ cloud-metadata host များသို့ မည်သည့်အခါမျှ လမ်းကြောင်းမပေးပါ။ | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ကို ဖွင့်ထားသည့်အခါ အသုံးပြုသူများက OIDC Single Sign-On မှတစ်ဆင့်သာ အထောက်အထားစစ်ဆေးနိုင်စေရန် စကားဝှက်ဖြင့် ဝင်ရောက်ခြင်းကို ပိတ်ပါ။ အတိုကောက် alias `OIDC_DISABLE_PASSWORD_LOGIN` ကိုလည်း လက်ခံသည်။ တူညီသော key ရှိ Dashboard Feature Flag က ဦးစားပေးသည်။ (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ၏ အတိုကောက် alias ဖြစ်သည် (#10889)။ | +| Variable | Default | Source File | Description | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | စက်၏ လက်ဗွေဖော်ထုတ်ခြင်းအတွက် hardware identifier များနှင့် ပေါင်းစပ်အသုံးပြုသည့် salt ဖြစ်သည်။ သီးခြားခွဲထားနိုင်ရန် deployment တစ်ခုစီအလိုက် ပြောင်းလဲပါ။ | +| `OMNIROUTE_CLI_SALT` | _(မသတ်မှတ်ထားပါ = installation တစ်ခုစီအလိုက် ကျပန်း salt ကို `/cli-token-salt.json` တွင် သိမ်းဆည်းထားသည်)_ | `src/lib/machineToken.ts` | local CLI auth token ကို ဆင်းသက်ထုတ်လုပ်ရန် အသုံးပြုသည့် HMAC salt ဖြစ်သည်။ ဤတန်ဖိုးကို သတ်မှတ်ခြင်းဖြင့် စက်ပေါ်ရှိ CLI token အားလုံးကို ပြောင်းလဲအသစ်ထုတ်ပေးပြီး သိမ်းဆည်းထားသော salt ထက် အမြဲတမ်း ဦးစားပေးအသုံးပြုသည်။ `docs/security/CLI_TOKEN.md` ကို ကြည့်ပါ။ | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | session cookie များတွင် `Secure` flag ကို သတ်မှတ်သည်။ HTTPS နောက်ကွယ်တွင် လုပ်ဆောင်သည့်အခါ **`true` ဖြစ်ရမည်**။ | +| `REQUIRE_API_KEY` | `false` | API middleware | `true` ဖြစ်သည့်အခါ `/v1/*` proxy request အားလုံးတွင် မှန်ကန်သော API key တစ်ခု ပါဝင်ရမည်။ **ဤ flag သည် `GET /v1/models` ကို ထိန်းချုပ်မထားပါ**။ ယင်းအစား ၎င်းသည် dashboard login posture (`requireAuthForModels`) ကို လိုက်နာသည် — ထို့ကြောင့် `/v1/models` မှ `401` ပြန်လာခြင်းသည် inference ကို ကာကွယ်ထားသည်ဟု မဆိုလိုပါ။ `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ကို ကြည့်ပါ။ | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Dashboard UI တွင် API key တန်ဖိုးအပြည့်အစုံကို ဖော်ပြခွင့်ပြုသည်။ Dashboard Feature Flags မှ ပြင်ဆင်သတ်မှတ်နိုင်ပြီး မျှဝေအသုံးပြုသည့် instance များတွင် လုံခြုံရေးအန္တရာယ်ရှိသည်။ | +| `NO_LOG_API_KEY_IDS` | _(ဗလာ)_ | `src/lib/compliance/index.ts` | request logging ကို ကျော်လွှားမည့် API key ID များကို ကော်မာဖြင့် ခွဲခြားဖော်ပြထားသော စာရင်းဖြစ်သည် (GDPR လိုက်နာမှုအတွက်)။ | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(မသတ်မှတ်ထားပါ = အကန့်အသတ်မရှိ)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` column သည် null ဖြစ်နေသော API key များတွင် အသုံးပြုမည့် တစ်ရက်စာ request ပမာဏ၏ အရန်သတ်မှတ်ချက်ဖြစ်သည်။ မသတ်မှတ်ထားခြင်း သို့မဟုတ် ဗလာဖြစ်ခြင်းသည် သွယ်ဝိုက်သတ်မှတ်ထားသော အကန့်အသတ်မရှိဟု ဆိုလိုသည် (#2289, #11017)။ `0` သည်လည်း အတူတူပင်ဖြစ်ပြီး အကန့်အသတ်မရှိပါ။ အပေါင်းကိန်း N သည် N/day၊ 5N/week၊ 20N/month ကို ဖွင့်ပေးသည်။ ပုံစံမမှန်သော ဗလာမဟုတ်သည့် တန်ဖိုးများအတွက် ယခင် 1000/day၊ 5000/week၊ 20000/month အချိန်ကာလကန့်သတ်ချက်များကို အရန်အဖြစ် အသုံးပြုသည်။ | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ခွင့်ပြုထားသော request body ၏ အများဆုံးအရွယ်အစားဖြစ်သည်။ ဤကန့်သတ်ချက်ထက် ကျော်လွန်သော payload များကို ငြင်းပယ်သည်။ | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ဤသတ်မှတ်ချက်နှင့် ညီမျှသော သို့မဟုတ် ကျော်လွန်သော အမှန်တကယ် request body များသည် JSON parsing မပြုလုပ်မီ atomic process-local heavyweight admission lease ကို ရယူသည် (BYTE လမ်းကြောင်း၊ `POST /v1/responses` အပါအဝင်)။ structure-heavy ကဲ့သို့ပင် [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ၏ healthy-headroom escape ကို အသုံးပြုသည်။ ထို့အပြင် [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ပြန်မဖြစ်စေရန် `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ဖြင့် ဆက်လက်ကန့်သတ်ထားသည်။ | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ကန့်သတ်ထားသော လက်ခံဖတ်ရှုမှုအတွင်း ဖတ်ယူသည့် byte များအပေါ် မူတည်၍ သတ်မှတ်ထားသော chat route ၏ အမြင့်ဆုံးကန့်သတ်ချက်ဖြစ်ပြီး `Content-Length` မပါရှိသော၊ မမှန်ကန်သော သို့မဟုတ် အမှန်မဖော်ပြသော request များလည်း အကျုံးဝင်သည်။ ကန့်သတ်ချက်ကျော်လွန်ပါက `413` ရရှိမည်။ | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(မသတ်မှတ်ထား — request အရေအတွက် ကန့်သတ်ချက်မရှိ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ဤအမွေဆက်ခံထားသော request အရေအတွက် ကန့်သတ်ချက်သည် တိတိကျကျ သတ်မှတ်ထားမှသာ သက်ရောက်သည်။ မသတ်မှတ်ဘဲထားပါက (မူလတန်ဖိုး) heavyweight chat လက်ခံမှုကို `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` က ထိန်းချုပ်သည် — **process တစ်ခုအတွင်း** (V8 heap တစ်ခု) process ၏ အမှန်တကယ် memory အမြင့်ဆုံးကန့်သတ်ချက်မှ အလိုအလျောက် တွက်ချက်ထားသော BYTE budget ဖြစ်သည်။ တစ်ချိန်တည်းထပ်နေသည့် token ~750k ပါ `/v1/responses` နှစ်ခုသည် ~12 Gi heap များကို ရပ်တန့်စေသည် (#7849) — ၎င်းသည် **memory-budget** သတိပေးချက်ဖြစ်ပြီး product ၏ အတိအကျ အများဆုံးတန်ဖိုး 2 ဟု ဆိုလိုခြင်းမဟုတ်ပါ။ ကောင်းမွန်သော process တစ်ခု (heap သည် shed ratio အောက်တွင်ရှိခြင်း) သည် `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` မှတစ်ဆင့် တစ်ပြိုင်နက် long `/v1/responses` များကို ပိုမိုလက်ခံနိုင်သည်။ long SSE client ဆယ်ဂဏန်း (40–50) ကို လက်ခံနိုင်မှုသည် heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ပေါ် မူတည်ပြီး “အများဆုံး 2” ဟု မဆိုလိုပါ။ “host ကို အပြည့်အသုံးပြုရန်” ဟူ၍ ဤတန်ဖိုးကို မစဉ်းစားဘဲ မြှင့်ခြင်းသည် #7849 ကို ပြန်ဖြစ်စေသည်။ **N ခုသော သီးခြား `DATA_DIR` များ** (#11024) ဖြင့် **heap** အရေအတွက်ကို တိုးပါ။ SQLite file တစ်ခုပေါ်တွင် `replicas>1` ကို လုံးဝ မသုံးပါနှင့်။ | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(အလိုအလျောက် တွက်ချက်ထားသည်)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** အလိုအလျောက် တွက်ချက်ထားသော ingest byte budget ကို အစားထိုးသတ်မှတ်ရန်ဖြစ်သည် (ပိုမိုတင်းကျပ်သော V8/cgroup memory အမြင့်ဆုံးကန့်သတ်ချက်၏ 25% ကို ယာယီချဲ့ထွင်မှု 8x ဖြင့် စားထားသည်)။ တွက်ချက်ထားသောတန်ဖိုးနှင့် တိတိကျကျသတ်မှတ်ထားသောတန်ဖိုး နှစ်မျိုးစလုံးကို 8 MiB–2 GiB အတွင်း ကန့်သတ်ထားသည်။ ထိရောက်သော budget ထက် body ပိုကြီးပါက `413 body_exceeds_budget` ဖြင့် ချက်ချင်းပျက်ကွက်မည်။ တစ်ခုချင်းစီအနေဖြင့် ဝန်ဆောင်မှုပေးနိုင်သော body များအကြား အရင်းအမြစ်လုယူမှုဖြစ်ပါက ပြန်လည်ကြိုးစားနိုင်သော `503` အဖြစ် ဆက်လက်ရှိမည်။ တစ်ပြိုင်နက် long SSE client 40–50 ခုကို လက်ခံနိုင်မှုသည် ဤ budget + heap ပေါ် မူတည်ပြီး “အများဆုံး 2” ဟူသော အတိအကျကန့်သတ်ချက် မဟုတ်ပါ။ ချိန်ညှိခြင်းမပြုမီ `/api/monitoring/health` ရှိ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ကို ဖတ်ရှုပါ။ | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE နှင့် STRUCTURE heavyweight လက်ခံမှုအတွက် heap-pressure shed ratio (`heapUsed / heap_size_limit`) ဖြစ်သည် (#10183, #10268, #10437)။ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ကို ကျော်လွန်သော တစ်ပြိုင်နက် heavyweight request ကို heap သည်လည်း ဤ ratio သို့မဟုတ် ထိုထက်မြင့်နေမှသာ ပြန်လည်ကြိုးစားနိုင်သော `503` ဖြင့် ဖယ်ရှားမည်။ ကောင်းမွန်သော heap တွင်မူ healthy-headroom မှတစ်ဆင့် လက်ခံမည်။ | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (မူလတန်ဖိုး `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE နှင့် BYTE **နှစ်မျိုးစလုံး** အတွက် ကောင်းမွန်သော heap fast path (#10437) ၏ ကန့်သတ်ထားသည့် အပိုစွမ်းရည်ဖြစ်သည် (`admitChatRequest`၊ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` နှင့်အထက် body များအပါအဝင်)။ ဤကန့်သတ်ချက်မရှိပါက အလုပ်များနေသော်လည်း heap ကောင်းမွန်သည့် request တိုင်းသည် အမြင့်ဆုံးကန့်သတ်ချက်မရှိဘဲ လက်ခံမှုထိန်းချုပ်ရေးကို ကျော်သွားမည်။ healthy-heap path မှတစ်ဆင့် ဤအရေအတွက်အထိ တစ်ပြိုင်နက် lease များ အသက်ဝင်လာပြီးနောက် နောက်ထပ် အလုပ်များနေသော request များသည် အမှန်တကယ် heap pressure အောက်တွင် အသုံးပြုသည့် တူညီသော ကန့်သတ်စောင့်ဆိုင်းမှု/shed path သို့ ရောက်သွားမည်။ `0` သည် bypass ကို လုံးဝပိတ်သည်။ | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | body သည် byte အနိမ့်ဆုံးသတ်မှတ်ချက်အောက်တွင် ရှိနေသော်လည်း chat request ကို heavyweight အဖြစ် သတ်မှတ်သည့် message အရေအတွက်ဖြစ်သည်။ | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | body သည် byte အနိမ့်ဆုံးသတ်မှတ်ချက်အောက်တွင် ရှိနေသော်လည်း chat request ကို heavyweight အဖြစ် သတ်မှတ်သည့် tool အရေအတွက်ဖြစ်သည်။ | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | request တစ်ခုကို heavyweight အဖြစ် သတ်မှတ်သည့် ကြိုတင်ကာကွယ်မှုမြင့်သော string အရွယ်အစားအခြေပြု token ခန့်မှန်းချက်ဖြစ်သည်။ ၎င်းသည် လက်ခံမှုကုန်ကျစရိတ်အတွက် ကိုယ်စားပြုတန်ဖိုးသာဖြစ်ပြီး provider ၏ billing tokenization မဟုတ်ပါ။ | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ပိတ်ထားသည်) | `src/shared/middleware/chatBodyAdmission.ts` | ဆန္ဒရှိမှ ဖွင့်သုံးနိုင်သော chat history ကန့်သတ်ချက်ဖြစ်သည်။ မူလအားဖြင့် ပိတ်ထားသည်။ message အရေအတွက်သည် deployment policy ဖြစ်ပြီး request တစ်ခု၏ နေရာတိုင်းနှင့်သက်ဆိုင်သော ပင်ကိုဂုဏ်သတ္တိ မဟုတ်သည့်အပြင် ဤနေရာတွင် ကန့်သတ်ခြင်းက compression pipeline မှ ဝန်ဆောင်မှုပေးနိုင်အောင် မလုပ်ဆောင်မီ conversation များကို အပြီးသတ် `413` ဖြင့် ငြင်းပယ်စေသည်။ Heap ကြီးထွားမှုကို `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` နှင့် heap-pressure shed တို့ဖြင့် ကန့်သတ်ထားသည်။ အတိအကျ အမြင့်ဆုံးကန့်သတ်ချက်လိုအပ်သော memory နည်းပါးသည့် deployment များတွင် အပေါင်းတန်ဖိုးတစ်ခု သတ်မှတ်ပါ။ ထိုအခါ ကန့်သတ်ချက်ကျော်လွန်မှုသည် ဖွဲ့စည်းပုံပါ compact-required `413` ကို ရရှိမည်။ | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | memory ထဲသို့ အပြည့်အဝ buffer လုပ်ထားသော non-streaming upstream response အတွက် အတိအကျ အမြင့်ဆုံးကန့်သတ်ချက်ဖြစ်သည်။ ၎င်းကို ကျော်လွန်ပါက upstream reader ကို ပယ်ဖျက်ပြီး heap ကုန်ဆုံးသည်အထိ အကန့်အသတ်မရှိ string ကြီးထွားနေမည့်အစား request ကို ချက်ချင်း ပျက်ကွက်စေသည်။ | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | အထက်ပိုင်းတုံ့ပြန်မှု header များမှ ထပ်ဆင့်ပို့သည့် wire byte အများဆုံးပမာဏ။ သတ်မှတ်ပမာဏကို ကျော်လွန်သောအခါ အသုံးများသည့် reverse-proxy header ကန့်သတ်ချက်များအတွင်း ရှိနေစေရန် ဦးစားပေးမှုနိမ့်သည့် header များ (ဥပမာ၊ စိတ်ကြိုက် `x-codex-*`, `x-oai-request-id`) ကို ဖယ်ရှားသည်။ တုံ့ပြန်မှု header အရွယ်အစား ပိုကြီးလာခြင်းကို လက်ခံပြီး အထက်ပိုင်း metadata များကို ပိုမိုထပ်ဆင့်ပို့လိုပါက ပိုမြင့်သည့်တန်ဖိုး သတ်မှတ်ပါ။ | +| `CORS_ORIGIN` | _(မသတ်မှတ်ထား)_ | `src/server/cors/origins.ts` | ယခင်စနစ်သုံး မူလရင်းမြစ်တစ်ခုတည်းအတွက် CORS ခွင့်ပြုစာရင်း။ ဖြန့်ကျက်မှုအသစ်များအတွက် `CORS_ALLOWED_ORIGINS` ကို ဦးစားပေးပါ။ CORS သည် မတူညီသောမူလရင်းမြစ်မှ browser API client များအတွက်သာ ဖြစ်သည်။ အထောက်အထားစစ်ဆေးထားသော dashboard ရေးသားမှုများသည် ယင်းအစား မူလရင်းမြစ်တူ request များနှင့် session နှင့် ချိတ်ဆက်ထားသည့် CSRF ကာကွယ်မှုကို အသုံးပြုသည်။ | +| `CORS_ALLOWED_ORIGINS` | _(မသတ်မှတ်ထား)_ | `src/server/cors/origins.ts` | ကော်မာဖြင့် ခွဲထားသော CORS ခွင့်ပြုစာရင်း။ `CORS_ALLOW_ALL=true` ကို အတိအလင်း စီစဉ်သတ်မှတ်မထားပါက wildcard ကို ပို့မည်မဟုတ်ပါ။ | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | မည်သည့် browser `Origin` ကိုမဆို ပြန်လည်တုံ့ပြန်ရန် development အတွက်သာ အသုံးပြုသည့် အရေးပေါ်ရှောင်လမ်း။ မျှဝေသုံးစွဲသော သို့မဟုတ် production ဖြန့်ကျက်မှုများတွင် မဖွင့်ပါနှင့်။ | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Legacy alias ဖြစ်သည်။ ၎င်း၏ dashboard toggle (DB override) ကို ဤ environment တန်ဖိုးထက် အရင် ဖတ်ယူသည် — မည်တစ်ခုတွင်မဆို `false`, `0`, `no` သို့မဟုတ် `off` ဖြစ်နေပါက ၎င်း flag ၏ dashboard toggle ကို ပိတ်သိမ်းထားသည် ဖြစ်စေ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ကဲ့သို့ပင် host စစ်ဆေးမှုများကို ရပ်တန့်စေသည် — အခြားတန်ဖိုးများအတွက် guard ကို အောက်ခြေမှ flag နှစ်ခုက ဆုံးဖြတ်စေသည်။ | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Guard mode လိုက်နေသော provider လမ်းကြောင်းများ — URL အတည်ပြုခြင်း၊ model ရှာဖွေခြင်း၊ provider node base URL များ (remote rerank node များအပါအဝင်)၊ proxy-fallback စမ်းသပ်ခြင်းနှင့် remote image/media ရယူခြင်းကဲ့သို့သော provider နှင့်ဆက်စပ်သည့် အခြား outbound လမ်းကြောင်းများ — ပေါ်ရှိ outbound URL guard ၏ host စစ်ဆေးမှုများကို cloud-metadata ပိတ်ဆို့မှု (169.254.0.0/16 အားလုံး၊ metadata.google.internal) အပါအဝင် ရပ်တန့်စေသည်။ Cloud-metadata/link-local endpoint များမှအပ ၎င်းသည် private webhook ပစ်မှတ်များကိုပါ ခွင့်ပြုသည် — ၎င်းတို့ကိုမူ ဤ flag ဖွင့်ထားသည့်အခါတွင်ပင် ဆက်လက် ပိတ်ဆို့ထားသည်။ `BaseExecutor.execute()` ဖြင့် ပေးပို့သော chat request များ၏ cloud-metadata စစ်ဆေးမှုကို ထိန်းသိမ်းထားသည်။ Local provider များသည် မူလတန်ဖိုးများဖြင့် အလုပ်လုပ်သည် — `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (မူလတန်ဖိုး `true`) သည် အတည်ပြုခြင်း၊ ရှာဖွေခြင်းနှင့် provider node လမ်းကြောင်းများပေါ်တွင် local နှင့် LAN URL များကို ရှိပြီးသား ခွင့်ပြုထားသည် — proxy-fallback စမ်းသပ်ချက်နှင့် private webhook ပစ်မှတ်များကမူ ဤ flag ကိုသာ မေးမြန်းပြီး ၎င်း ပိတ်နေသရွေ့ local/LAN host များကို ပိတ်ဆို့ထားသည်။ `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` ကို override လုပ်သည်။ Dashboard toggle မှ သိမ်းဆည်းထားသော တန်ဖိုးသည် ဤ variable ထက် ဦးစားပေးခြင်း ခံရသည်။ | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Provider အတည်ပြုခြင်း၊ model ရှာဖွေခြင်း၊ chat နှင့် guard mode က ထိန်းချုပ်သော provider နှင့်ဆက်စပ်သည့် အခြား outbound လမ်းကြောင်းများအတွက် local/private လိပ်စာများ (127.0.0.1, localhost, LAN, private အပိုင်းအခြားများ) ပေါ်ရှိ provider URL များကို ခွင့်ပြုသည်။ **မူလတန်ဖိုးမှာ `true` ဖြစ်သည်** (local-first) — ၎င်းအခါ guard သည် cloud-metadata endpoint များ (169.254.0.0/16 အားလုံးနှင့် သိထားသော metadata hostname များ — metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) ကို ပိတ်ဆို့သည်။ Private နှင့် loopback host များကိုပါ ပိတ်ဆို့မည့် public-only mode အတွက် `false` သတ်မှတ်ပါ။ Guard သည် DNS lookup မပြုလုပ်မီ hostname သို့မဟုတ် IP literal ကို ရေးထားသည့်အတိုင်း စစ်ဆေးသည် — public-only mode တွင် safe-fetch wrapper က hostname ကို ထပ်မံ resolve ပြီး private အဖြေ ရရှိပါက ငြင်းပယ်သည်။ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` သည် ၎င်းအတန်းတွင် ဖော်ပြထားသော လမ်းကြောင်းများပေါ်ရှိ ဤစစ်ဆေးမှုများကို ရပ်တန့်စေသည်။ LM Studio, Ollama, vLLM, Llamafile နှင့် Triton ကဲ့သို့သော built-in local provider များသည် key အတည်ပြုခြင်းနှင့် chat အတွက် host စစ်ဆေးမှု အားလုံးကို ကျော်လွှားသည်။ Dashboard toggle မှ သိမ်းဆည်းထားသော တန်ဖိုးသည် ဤ variable ထက် ဦးစားပေးခြင်း ခံရသည်။ (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` route များ (စာသားကူးပြောင်းမှုများ၊ အသံပြောဆိုမှု၊ ဘာသာပြန်ဆိုမှုများ) ကို localhost ပြင်ပတွင် host လုပ်ထားသည့် OpenAI-compatible provider node တစ်ခု အသုံးပြုခွင့်ပေးပါ။ မူလအားဖြင့် ပိတ်ထားသည် — audio ကို remote host တစ်ခုသို့ လမ်းကြောင်းပေးခြင်းသည် egress identity ကို ပြောင်းလဲစေသောကြောင့် operator က အတိအလင်း ဆုံးဖြတ်ရမည်။ Loopback/private node များ (localhost, 127.0.0.1, 172.16-31.x) ကို အမြဲခွင့်ပြုထားပြီး သက်ရောက်မှုမရှိပါ။ (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (နှင့် memory engine ၏ loopback rerank အဆင့်) ကို localhost ပြင်ပတွင် host လုပ်ထားသည့် OpenAI-compatible provider node — TEI, Infinity, vLLM စသည်တို့ အသုံးပြုနေသော LAN စက် သို့မဟုတ် Tailscale peer — အား အသုံးပြုခွင့်ပေးပါ။ မူလအားဖြင့် ပိတ်ထားသည် — remote host တစ်ခုသို့ လမ်းကြောင်းပေးခြင်းသည် egress identity ကို ပြောင်းလဲစေသောကြောင့် operator က အတိအလင်း ဆုံးဖြတ်ရမည်။ Loopback node များ (localhost, 127.0.0.1, 172.16-31.x) ကို အမြဲခွင့်ပြုထားပြီး သက်ရောက်မှုမရှိပါ။ Remote node ၏ base URL သည်လည်း provider outbound URL မူဝါဒ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ကို ကျော်ဖြတ်ရမည်။ | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ကို ဖွင့်ထားသည့်အခါ အသုံးပြုသူများက OIDC Single Sign-On မှတစ်ဆင့်သာ အထောက်အထားစစ်ဆေးနိုင်စေရန် စကားဝှက်ဖြင့် ဝင်ရောက်ခြင်းကို ပိတ်ပါ။ အတိုကောက် alias `OIDC_DISABLE_PASSWORD_LOGIN` ကိုလည်း လက်ခံသည်။ တူညီသော key ရှိ Dashboard Feature Flag က ဦးစားပေးသည်။ (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ၏ အတိုကောက် alias ဖြစ်သည် (#10889)။ | ### လုံခြုံရေးမြှင့်တင်မှု စစ်ဆေးရန်စာရင်း diff --git a/docs/i18n/ne/CONTRIBUTING.md b/docs/i18n/ne/CONTRIBUTING.md index b9f2afa66bc9..57117a8de9b9 100644 --- a/docs/i18n/ne/CONTRIBUTING.md +++ b/docs/i18n/ne/CONTRIBUTING.md @@ -400,22 +400,22 @@ handlers/executors भित्र, client सम्म पुग्ने error --- -## Pull Request Checklist - -- [ ] Tests सफल हुन्छन् (`npm test`) -- [ ] Linting सफल हुन्छ (`npm run lint`) -- [ ] Build सफल हुन्छ (`npm run build`) -- [ ] नयाँ public functions र interfaces का लागि TypeScript types थपिएका छन् -- [ ] कुनै hardcoded secrets वा fallback values छैनन् -- [ ] सार्वजनिक upstream credentials लाई `resolvePublicCred()` मार्फत embed गरिएको छ ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) हेर्नुहोस्), literals का रूपमा कहिल्यै होइन -- [ ] Error responses लाई `buildErrorBody()` / `sanitizeErrorMessage()` मार्फत route गरिएको छ — response bodies मा raw stack traces छैनन् ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) हेर्नुहोस्) -- [ ] Shell commands (`exec` / `spawn`) ले runtime values लाई string interpolation मार्फत होइन, `env` मार्फत पठाउँछन् -- [ ] सबै inputs लाई Zod schemas प्रयोग गरेर validate गरिएको छ -- [ ] User-facing changes का लागि `changelog.d/{features|fixes|maintenance}/-.md` अन्तर्गत changelog **fragment** थपिएको छ ([`changelog.d/README.md`](./changelog.d/README.md) हेर्नुहोस्) — `CHANGELOG.md` लाई सीधै edit **नगर्नुहोस्**; fragments लाई release को समयमा aggregate गरिन्छ र PRs बीच कहिल्यै conflict हुँदैन -- [ ] Documentation अद्यावधिक गरिएको छ (लागू भएमा) -- [ ] कुनै नयाँ CodeQL / Secret-Scanning alerts खोलिएका छैनन्, वा प्रत्येकलाई सम्बन्धित `docs/security/` doc उल्लेख गर्ने technical justification सहित dismiss गरिएको छ -- [ ] Child processes spawn गर्ने routes (`/api/mcp/`, `/api/cli-tools/runtime/`) लाई `src/server/authz/routeGuard.ts` मा `isLocalOnlyPath()` का रूपमा classify गरिएको छ — [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md) हेर्नुहोस् -- [ ] Commit messages मा `Co-Authored-By` trailers छैनन् — commits केवल repository owner को Git identity अन्तर्गत देखिनुपर्छ (Hard Rule #16) +## पुल अनुरोध जाँचसूची + +- [ ] परीक्षणहरू सफल हुन्छन् (`npm test`) +- [ ] लिन्टिङ सफल हुन्छ (`npm run lint`) +- [ ] बिल्ड सफल हुन्छ (`npm run build`) +- [ ] नयाँ सार्वजनिक फङ्सन र इन्टरफेसहरूका लागि TypeScript प्रकारहरू थपिएका छन् +- [ ] हार्डकोड गरिएका गोप्य मान वा वैकल्पिक मानहरू छैनन् +- [ ] सार्वजनिक अपस्ट्रिम प्रमाणहरू `resolvePublicCred()` मार्फत समावेश गरिएका छन् ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) हेर्नुहोस्), लिटरलका रूपमा कहिल्यै होइन +- [ ] त्रुटि प्रतिक्रियाहरू `buildErrorBody()` / `sanitizeErrorMessage()` मार्फत पठाइन्छन् — प्रतिक्रिया बडीहरूमा कच्चा स्ट्याक ट्रेसहरू छैनन् ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) हेर्नुहोस्) +- [ ] शेल कमान्डहरू (`exec` / `spawn`) ले रनटाइम मानहरू स्ट्रिङ इन्टरपोलेसनमार्फत नभई `env` मार्फत पठाउँछन् +- [ ] सबै इनपुटहरू Zod स्किमाहरूद्वारा प्रमाणीकरण गरिएका छन् +- [ ] प्रयोगकर्ताले देख्ने परिवर्तनहरूका लागि `changelog.d/{features|fixes|maintenance}/-.md` अन्तर्गत चेन्जलग **फ्र्याग्मेन्ट** थपिएको छ ([`changelog.d/README.md`](./changelog.d/README.md) हेर्नुहोस्) — `CHANGELOG.md` लाई सीधै सम्पादन **नगर्नुहोस्**; फ्र्याग्मेन्टहरू रिलिजको समयमा एकत्रित गरिन्छन् र PR हरूबीच कहिल्यै द्वन्द्व हुँदैन +- [ ] दस्तावेज अद्यावधिक गरिएको छ (लागू भएमा) +- [ ] कुनै नयाँ CodeQL / Secret-Scanning अलर्ट खोलिएको छैन, वा प्रत्येकलाई सम्बन्धित `docs/security/` दस्तावेज सन्दर्भ गर्ने प्राविधिक औचित्यसहित खारेज गरिएको छ +- [ ] चाइल्ड प्रोसेस सुरु गर्ने रुटहरू (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` मा `isLocalOnlyPath()` का रूपमा वर्गीकृत गरिएका छन् — [कडा नियम #15](docs/security/ROUTE_GUARD_TIERS.md) हेर्नुहोस् +- [ ] कमिट सन्देशहरूमा AI/bot `Co-authored-by` ट्रेलरहरू छैनन् (कडा नियम #16) — पुनः प्रयोग गरिएको कामका मानव सहकर्मीहरूलाई मानक `Co-authored-by: Name ` ट्रेलरहरूमार्फत श्रेय दिइएको छ --- diff --git a/docs/i18n/ne/docs/architecture/QUALITY_GATES.md b/docs/i18n/ne/docs/architecture/QUALITY_GATES.md index 3db83b3759d8..03cac28ffacd 100644 --- a/docs/i18n/ne/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ne/docs/architecture/QUALITY_GATES.md @@ -19,212 +19,210 @@ ratchet baseline वा pass/fail नीति प्रयोग गर्छ ### उम्मेदवार स्वीकृति -CI र Quality Gates कार्यप्रवाहहरूले प्रत्येकले स्थिर निर्णय उत्सर्जन गर्छन्: `Gate / CI` र -`Gate / Quality`। तिनको संस्करणबद्ध स्वीकृति नीतिले प्रत्येक अपस्ट्रिम कार्यलाई -आवश्यक वा परामर्शात्मकका रूपमा सूचीकृत गर्छ। लागू हुने आवश्यक कार्य सफल हुनैपर्छ: हराएका, -रद्द गरिएका, छोडिएका, विचाराधीन र अज्ञात नतिजाहरूले PASS स्थापित गर्न सक्दैनन्। मान्य -docs-only वा catalog-only वर्गीकरणले कोड लेनलाई अनुपयुक्त बनाउन सक्छ; +CI र Quality Gates कार्यप्रवाहहरू प्रत्येकले स्थिर निर्णय उत्सर्जन गर्छन्: `Gate / CI` र +`Gate / Quality`। तिनको संस्करणीकृत स्वीकृति नीतिले प्रत्येक अपस्ट्रिम जबलाई +आवश्यक वा परामर्शात्मकका रूपमा सूचीबद्ध गर्छ। लागू हुने आवश्यक जब सफल हुनैपर्छ: हराएका, +रद्द गरिएका, स्किप गरिएका, विचाराधीन र अज्ञात परिणामहरूले PASS स्थापित गर्न सक्दैनन्। वैध +docs-only वा catalog-only वर्गीकरणले कोड लेनलाई लागू नहुने बनाउन सक्छ; ड्राफ्ट PR स्वीकृत उम्मेदवार होइन। `hotfix` लेबलले प्रमाणको आवश्यकता हटाउँदैन। -दुवै कार्यप्रवाहले PR हरू र main/release शाखाहरूमा हुने पुशहरू, म्यानुअल डिस्प्याच र -मर्ज-समूह घटनाहरू समेट्छन्। पुश, डिस्प्याच र मर्ज-समूहले पूर्ण छनोट चलाउँछन्। फोर्कहरू -र मर्ज समूहहरूले अन्यथा self-hosted रनरहरू चयन गर्ने कार्यहरूका लागि hosted रनरहरू -प्रयोग गर्छन्; रोलआउटअघि पर्याप्त hosted क्षमता प्रमाणित गर्नुपर्छ। +दुवै कार्यप्रवाहले PR हरू र main/release शाखाहरूमा हुने पुश, म्यानुअल डिस्प्याच र +merge-group इभेन्टहरू समेट्छन्। पुश, डिस्प्याच र merge-group ले पूर्ण चयन चलाउँछन्। फोर्कहरू +र merge group हरूले अन्यथा self-hosted रनर चयन गर्ने जबहरूका लागि hosted रनर प्रयोग गर्छन्; +रोलआउटअघि पर्याप्त hosted क्षमता प्रमाणित गरिनुपर्छ। प्रत्येक JSON रसिदले चेकआउट गरिएको SHA, कार्यप्रवाह रन र प्रयास पहिचान गर्छ। -CLI ले checkout/event SHA बेमेल अस्वीकार गर्छ। कार्यप्रवाह परीक्षणहरूले नीति सदस्यतालाई -निर्णय कार्यको `needs` सूचीसँग बाँध्छन्, ताकि नयाँ वा हटाइएको लेन चुपचाप हराउन नसकोस्। -रसिदहरूले आफ्नै कार्यप्रवाह समेट्छन्, प्रकाशन, परिनियोजन वा विद्यमान परामर्शात्मक -स्क्यानरका आन्तरिक पक्षहरू होइनन्। शाखा नियमहरूमा दुवै जाँच नाम सक्रिय गर्नु छुट्टै -प्रशासनिक परिवर्तन हो; यी कार्यहरू थप्दैमा शाखा आफैं सुरक्षित हुँदैन। +CLI ले checkout/event SHA बेमेललाई अस्वीकार गर्छ। कार्यप्रवाह परीक्षणहरूले नीति सदस्यतालाई +निर्णय जबको `needs` सूचीसँग बाँध्छन्, ताकि नयाँ वा हटाइएको लेन चुपचाप हराउन नसकोस्। +रसिदहरूले आफ्नै कार्यप्रवाह समेट्छन्, प्रकाशन, डिप्लोयमेन्ट वा विद्यमान परामर्शात्मक +स्क्यानरका आन्तरिक पक्षहरू होइनन्। शाखा नियमहरूमा दुवै चेक नाम सक्रिय गर्नु छुट्टै +प्रशासनिक परिवर्तन हो; यी जबहरू थप्दैमा शाखा आफैँ सुरक्षित हुँदैन। ### स्थिर स्क्यान सूची -संस्करणबद्ध npm-alias सूची र static-scan सदस्यता -`config/quality/gate-manifest.json` मा रहन्छन्। स्क्रिप्ट नामहरू र ठ्याक्कै मिल्ने आदेशहरू -`package.json` सँग प्रमाणीकरण गर्न `npm run check:gate-manifest` चलाउनुहोस्; थप, हटाइ र -आदेशमा आएको विचलनले स्थानीय हुक र CI का परिवर्तन-वर्गीकरण कार्यहरू दुवैलाई असफल बनाउँछ। -एउटा alias कार्यप्रवाह कार्य, matrix instance वा परीक्षण केस होइन: यी गणनाहरूलाई +संस्करणीकृत npm-alias सूची र स्थिर-स्क्यान सदस्यता +`config/quality/gate-manifest.json` मा छन्। स्क्रिप्ट नामहरू र ठ्याक्कै मिल्ने कमाण्डहरू +`package.json` विरुद्ध प्रमाणीकरण गर्न `npm run check:gate-manifest` चलाउनुहोस्; थपाइ, हटाइ र +कमाण्ड विचलनले स्थानीय हुक र CI का परिवर्तन-वर्गीकरण जबहरू दुवैलाई असफल बनाउँछन्। +एउटा alias कार्यप्रवाह जब, matrix instance वा test case होइन: यी गणनाहरूलाई परस्पर साट्न मिल्ने रूपमा प्रस्तुत गर्नु हुँदैन। -चयन गरिएका aliases लाई कार्यान्वयन नगरी निरीक्षण गर्न -`npm run quality:scan -- --list` वा `npm run quality:scan:fast -- --list` प्रयोग गर्नुहोस्। -रनरले npm entrypoint आह्वान गर्छ, त्यसैले यसको runtime (कन्फिगर गरिएको ठाउँमा Bun सहित) -संरक्षित रहन्छ। manifest ले ती प्रोफाइलबाहिरका aliases लाई छुट्टै आह्वान गरिने रूपमा -अभिलेख गर्छ, र read-only scan प्रोफाइलहरूमा मर्मतसम्भार आदेशहरू निषेधित छन्। - -यी प्रोफाइलहरूले static scan मात्र समेट्छन्। तिनले उत्पादन परीक्षण, coverage, packaging, -बाह्य जाँचहरू वा उम्मेदवारको पूर्ण release acceptance प्रमाणित गर्दैनन्। -कार्यप्रवाह स्वीकृतिले लिङ्क गरिएको `config/quality/admission-policy.json` र -`scripts/quality/admission-verdict.mjs` प्रयोग गर्छ। Release-observer प्रोफाइलहरू अलग नै रहन्छन्; -तिनका लागू हुने जाँचहरू र रसिदहरू स्वतन्त्र रूपमा निरीक्षण गर्नुहोस्। तलको गद्य +चयनित alias हरू कार्यान्वयन नगरी निरीक्षण गर्न `npm run quality:scan -- --list` वा +`npm run quality:scan:fast -- --list` प्रयोग गर्नुहोस्। रनरले +npm entrypoint आह्वान गर्छ, त्यसैले यसको runtime (कन्फिगर गरिएको ठाउँमा Bun सहित) सुरक्षित रहन्छ। +manifest ले ती प्रोफाइलहरू बाहिरका alias हरूलाई छुट्टै आह्वान गरिने रूपमा अभिलेख गर्छ, र +read-only scan profile हरूमा maintenance command हरू निषेधित छन्। + +यी प्रोफाइलहरूले स्थिर स्क्यान मात्र समेट्छन्। तिनले product test, +coverage, packaging, external check वा उम्मेदवारको पूर्ण release acceptance प्रमाणित गर्दैनन्। +कार्यप्रवाह स्वीकृतिले लिङ्क गरिएका `config/quality/admission-policy.json` र +`scripts/quality/admission-verdict.mjs` प्रयोग गर्छ। Release-observer profile हरू अलग रहन्छन्; +तिनका लागू हुने चेक र रसिदहरू स्वतन्त्र रूपमा निरीक्षण गर्नुहोस्। तलको गद्य सूची सन्दर्भ हो, गेट वास्तवमै चलेको प्रमाण होइन। -स्क्रिप्टहरू `scripts/check/` (नीति गेटहरू) र `scripts/quality/` (ratchet engine) अन्तर्गत रहन्छन्। +स्क्रिप्टहरू `scripts/check/` (नीति गेटहरू) र `scripts/quality/` (ratchet engine) अन्तर्गत छन्। CI को आधिकारिक स्रोत `.github/workflows/ci.yml` हो। -### Release PR द्रुत-पथ (`quality.yml`) +### रिलीज PR द्रुत-पथ (`quality.yml`) -`.github/workflows/quality.yml` ले main/release PR हरू, सुरक्षित-शाखा पुशहरू, -डिस्प्याच र मर्ज समूहहरूमा CI लाई पूरक बनाउँछ। PR हरूले path-filtered द्रुत जाँचहरू प्रयोग गर्छन्। -स्थायी रूपमा निष्क्रिय गरिएको दोहोरो build हटाइएको छ; वास्तविक build/package/boot जाँचहरू CI मै रहन्छन्। +`.github/workflows/quality.yml` ले main/release PR हरू, protected-branch +पुशहरू, डिस्प्याच र merge group हरूमा CI लाई पूरक बनाउँछ। PR हरूले path-filtered द्रुत चेकहरू प्रयोग गर्छन्। स्थायी रूपमा +निष्क्रिय पारिएको दोहोरो build हटाइएको छ; वास्तविक build/package/boot चेकहरू CI मै रहन्छन्। -| कार्य | कार्यक्षेत्र | अवरोधक | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | Docs/code PR हरू; API docs refs र docs-all | हो | -| `Fast Quality Gates` | Code PR हरू; static checks, typecheck, dashboard typecheck, प्रभावित unit tests | हो | -| `Forgotten sibling tests` | Code PR हरू; परिवर्तन गरिएका modules लाई static consumers र सम्भावित sibling tests सम्म ट्रेस गरिन्छ; barrel र dynamic-import paths लाई सन्दर्भित allowlist exceptions सहित परामर्शात्मक निदानका रूपमा रिपोर्ट गरिन्छ | **परामर्शात्मक** | -| `Vitest (fast-path)` | Code PR हरू; द्रुत vitest suite | हो | -| `Unit Tests fast-path` | Code PR हरू; 4-shard unit suite | हो | -| `No new ESLint warnings` | Code PR हरू; suppressions-aware lint guard | हो, forks सहित | -| `Merge integrity (changelog + generated skills)` | Non-draft PR हरू; changelog र generated skill sync | हो, forks सहित | +| जब | दायरा | अवरोधक | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `Docs Gates (fast-path)` | Docs/code PR हरू; API docs refs र docs-all | हो | +| `Fast Quality Gates` | Code PR हरू; स्थिर चेकहरू, typecheck, dashboard typecheck, प्रभावित unit test हरू | हो | +| `Forgotten sibling tests` | Code PR हरू; परिवर्तन गरिएका मोड्युलहरूबाट static consumer र सम्भावित sibling test हरूसम्म ट्रेस गरिन्छ; barrel र dynamic-import path हरूलाई सन्दर्भित allowlist अपवादहरूसहित परामर्शात्मक diagnostic का रूपमा रिपोर्ट गरिन्छ | **परामर्शात्मक** | +| `Vitest (fast-path)` | Code PR हरू; द्रुत vitest suite | हो | +| `Unit Tests fast-path` | Code PR हरू; 4-shard unit suite | हो | +| `No new ESLint warnings` | Code PR हरू; suppression-aware lint guard | हो, fork हरू सहित | +| `Merge integrity (changelog + generated skills)` | Non-draft PR हरू; changelog र generated skill sync | हो, fork हरू सहित | -#### बिर्सिएका sibling tests को रिपोर्ट +#### बिर्सिएका sibling test हरूको रिपोर्ट `npm run check:forgotten-sibling-tests` ले test-impact map पछाडिको import resolver पुनः प्रयोग गर्छ। -परिवर्तन गरिएको प्रत्येक production module का लागि, सम्भावित -test pull-request diff मा अनुपस्थित हुँदा यसले निर्धारणात्मक +प्रत्येक परिवर्तन गरिएको production module का लागि, pull-request diff मा candidate +test नभएको अवस्थामा यसले निर्धारणात्मक `changed module/symbol -> static consumer -> candidate sibling test` शृङ्खलाहरू रिपोर्ट गर्छ। -कुनै पनि अवरोधक rollout अघि calibration का लागि Markdown सारांश र JSON नतिजालाई -`forgotten-sibling-tests` कार्यप्रवाह artifact का रूपमा सुरक्षित राखिन्छ। +Markdown सारांश र JSON परिणाम कुनै पनि blocking rollout अघि calibration का लागि +`forgotten-sibling-tests` workflow artifact का रूपमा सुरक्षित राखिन्छन्। -Barrel पुनः-निर्यातहरू र गतिशील आयातहरू केवल रिजोल्युसन निदान हुन्; तिनले कहिल्यै -अवरोधक निष्कर्ष सिर्जना गर्दैनन्। समीक्षा गरिएका अपवादहरू -`config/quality/forgotten-sibling-allowlist.json` मा रहन्छन्। प्रत्येक प्रविष्टिले उपभोक्ता र उम्मेदवार -परीक्षणको नाम उल्लेख गर्नुपर्छ, विशिष्ट औचित्य दिनुपर्छ, र GitHub issue वा pull request को लिङ्क दिनुपर्छ। त्रुटिपूर्ण प्रविष्टिहरू -बन्द अवस्थामै असफल हुन्छन्। अपवादहरूले मेटाइएको उम्मेदवार परीक्षण वा `.skip`/`.todo` थप्ने diff लाई दबाउन सक्दैनन्; -assertion कमजोर पार्ने र अन्य masking स्वतन्त्र रूपमा अवरोध गर्ने -`check:test-masking` gate कै जिम्मेवारीमा रहन्छन्। +Barrel पुनः-निर्यातहरू र dynamic imports समाधानसम्बन्धी निदान मात्र हुन्; तिनले कहिल्यै +अवरोधक finding सिर्जना गर्दैनन्। समीक्षा गरिएका अपवादहरू +`config/quality/forgotten-sibling-allowlist.json` मा रहन्छन्। प्रत्येक प्रविष्टिले consumer र candidate +test को नाम उल्लेख गर्नुपर्छ, विशिष्ट औचित्य दिनुपर्छ, र GitHub issue वा pull request मा लिङ्क गर्नुपर्छ। विकृत प्रविष्टिहरू +बन्द अवस्थामा असफल हुन्छन्। अपवादहरूले मेटाइएको candidate test वा `.skip`/`.todo` थप्ने diff लाई दबाउन सक्दैनन्; +assertion कमजोर पार्ने र अन्य masking कार्यहरू स्वतन्त्र रूपमा अवरोध गर्ने +`check:test-masking` gate कै अधीनमा रहन्छन्। ### Job: `lint` -`main` तर्फको प्रत्येक PR मा चल्छ। असफल भएमा merge रोक्छ। - -| Script (`npm run ...`) | प्रमाणीकरण गर्छ | अवरोधक | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | -| `check:node-runtime` | Node.js को संस्करण समर्थित दायराभित्र छ | हो | -| `check:cycles` | चक्रीय आयातहरू — सबै `src/` + `open-sse/` मोड्युलहरू | हो | -| `check:route-validation:t06` | सबै routes मा Zod schemas उपस्थित छन् (Tier 6 नीति) | हो | -| `check:any-budget:t11` | `@ts-expect-error // any` को सङ्ख्याले बजेट नाघ्दैन (Tier 11 catraca) | हो | -| `check:provider-consistency` | `providers.ts` मा भएका प्रत्येक provider सँग `providerRegistry.ts` मा मिल्दो entry छ (र allowlist भित्र यसको उल्टो पनि सत्य छ) | हो | -| `check:model-lifecycle` | हातैले मर्मत गरिएका तीनवटा routing table हरू checked-in lifecycle snapshot (#11503) सँग एकरूप रहन्छन्: `FITNESS_TABLE` (`taskFitness.ts`) ले `REGISTRY` ले route गर्न सक्ने कुनै पनि retired id लाई score गर्दैन; प्रत्येक `BUILT_IN_ALIASES` target `REGISTRY` मा उपस्थित र retired-id snapshot मा अनुपस्थित छ; `REGISTRY` मा अझै रहेको प्रत्येक retired id लाई forward गरिएको छ वा `allowedRetiredInCatalog` मा सूचीबद्ध गरिएको छ; र कुनै पनि `DEFAULT_DEGRADATION_MAP` source वा target उक्त snapshot मा retired देखिँदैन। यसले कुनै model हाल live upstream द्वारा उपलब्ध गराइएको छ भन्ने प्रमाणित गर्दैन। Offline — `config/quality/model-lifecycle.json` सँग तुलना गर्छ, जसलाई `npm run quality:refresh-model-lifecycle` (network; CI मा जडान गरिएको छैन) मार्फत हातैले refresh गरिन्छ। `allowedRetiredInCatalog` एउटा burn-down ratchet हो: tracking issue भएको अवस्थामा मात्र entry थप्नुहोस्। | हो | -| `check:fetch-targets` | client-side `src/` मा भएका प्रत्येक `fetch("/api/...")` वास्तविक `route.ts` मा resolve हुन्छन् | हो | -| `check:deps` | repo का प्रत्येक `package.json` मा रहेका सबै `npm install` गर्न मिल्ने deps `dependency-allowlist.json` मा छन्; नयाँ unpinned वा slopsquatted packages लाई flag गरिन्छ | हो | -| `audit:deps` | `npm audit` (root + electron) — कुनै high/critical advisories छैनन् (osv `check:vuln-ratchet` सँग overlap हुन्छ; Rationalization Backlog हेर्नुहोस्) | हो | -| `check:lockfile` | `package-lock.json` को integrity — https registry, integrity hashes, कुनै host overrides छैनन् | हो | -| `check:licenses` | उत्पादन निर्भरताहरूका लागि SPDX इजाजतपत्र अनुमति-सूची | हो | -| `check:tracked-artifacts` | कुनै बिल्ड आर्टिफ्याक्ट / कमिट गरिएका `node_modules` सिमलिङ्कहरू छैनन् (husky pre-commit मा पनि चल्छ; pre-push जानाजानी हल्का राखिएको छ — #6716) | हो | -| `check:ai-attribution` | PR कमिटहरू, शीर्षक वा मुख्य भागमा कुनै AI/bot `Co-Authored-By` ट्रेलर वा AI-जेनेरेसन फुटर छैन — कठोर नियम #16 (`quality.yml` को PR→`release/**` का लागि fast-gates लूपमा — इभेन्ट पेलोड पढ्छ, PR बाहिर कुनै कार्य गर्दैन — र `ci.yml` lint मा PR→`main` का लागि PR-मात्र चरण; साथै husky `commit-msg` हुक; मानव सह-लेखकहरूलाई अनुमति छ; #14436) | -| `check:vitest-exclusions` | प्रत्येक Vitest बहिष्करणले ट्र्याकिङ इश्यू उल्लेख गर्छ र `config/quality/vitest-exclusions.json` मा देखिन्छ (#13204) | हो | -| `check:file-size` | कुनै पनि स्रोत फाइलले प्रति-एक्सटेन्सन सीमा नाघ्दैन (र्याचेट: `frozen` सूचीमा स्थिर गरिएका ठूला फाइलहरू) | हो | -| `check:error-helper` | executors/handlers का त्रुटि प्रतिक्रियाहरूले `buildErrorBody()` / `sanitizeErrorMessage()` प्रयोग गर्छन् (कठोर नियम #12) | हो | -| `check:migration-numbering` | Migration SQL फाइलहरू कुनै खाली क्रम वा दोहोरो नम्बरबिना क्रमिक रूपमा नम्बर गरिएका छन् | हो | -| `check:public-creds` | `publicCreds.ts` बाहिर कुनै पनि शाब्दिक OAuth `client_id`/`client_secret` वा Firebase Web कुञ्जीहरू छैनन् (कडा नियम #11) | हो | -| `check:db-rules` | `src/lib/db/` मोड्युलहरू बाहिर कुनै कच्चा SQL छैन; `localDb.ts` बाट कुनै barrel-imports छैनन् (कडा नियमहरू #2/#5) | हो | -| `check:known-symbols` | आफ्ना dispatch tables मा दर्ता भएका provider executors, routing strategies, र translators डिस्कमा भएका फाइलहरूसँग मेल खान्छन् — कुनै अनाथ वा अघोषित symbols छैनन् | हो | -| `check:route-guard-membership` | child process सुरु गर्ने प्रत्येक route लाई `isLocalOnlyPath()` द्वारा वर्गीकृत गरिएको छ (कडा नियमहरू #15/#17) | हो | -| `check:test-discovery` | repo मा भएका प्रत्येक `*.test.ts` / `*.spec.ts` फाइल कम्तीमा एउटा test runner द्वारा सङ्कलित हुन्छ (`test-discovery-baseline.json` मा रहेको orphan list केवल घट्न सक्छ) | हो | -| `check:agent-skills-sync` | उत्पन्न गरिएका agent-skills आर्टिफ्याक्टहरू तिनको स्रोत क्याटलगसँग मेल खान्छन् (कुनै विचलन छैन) | -| `check:provider-asset-provenance` | प्रदायकका लोगो/एसेटहरूमा अभिलेख गरिएको उत्पत्ति प्रविष्टि हुन्छ | -| `lint:json` | JSON कन्फिग फाइलहरू पार्स हुन्छन् र repo का lint नियमहरू पूरा गर्छन् | -| `typecheck:core` | त्रुटिबिनाको TypeScript कम्पाइलेसन (सल्लाहकारी चेतावनीहरू मात्र) | हो | -| `typecheck:noimplicit:core` | कडा `noImplicitAny` — भविष्य-उन्मुख; पहिलेबाट रहेका धेरै call site हरूलाई अझै annotation आवश्यक छ | **सल्लाहकारी** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` मा सीमित `tsc` (#7033) — `typecheck:core` को सावधानीपूर्वक चयन गरिएको 27-फाइल allowlist मा कुनै पनि dashboard TSX समावेश छैन, र `next build` ले पनि यसलाई कहिल्यै type-check गर्दैन (`next.config.mjs` ले `ignoreBuildErrors: true` सेट गर्छ), त्यसैले त्यहाँका orphaned-identifier regression हरू (#6625/#6909) CI का लागि अदृश्य थिए। स्थिर गरिएको प्रति-फाइल/प्रति-TS-code सङ्ख्या baseline (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` कै जस्तो stale-enforcement ढाँचा) सँग diff गरिन्छ — baseline गरिएको सङ्ख्याभन्दा बढीका नयाँ त्रुटिहरूले मात्र gate असफल गराउँछन्; पहिलेबाट रहेको त्रुटि समाधान भएपछि `--update` प्रयोग गरेर सीमा क्रमशः घटाउनुहोस्। | हो | +`main` तर्फको प्रत्येक PR मा चल्छ। असफल भएमा merge अवरुद्ध गर्छ। + +| Script (`npm run ...`) | प्रमाणीकरण गर्ने कुरा | अवरोधक | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | Node.js को संस्करण समर्थित दायराभित्र छ | हो | +| `check:cycles` | सम्पूर्ण `src/` + `open-sse/` मा रहेका circular imports (AST-आधारित, tsconfig का `paths` समाधान गरिएका)। Bare = परामर्शात्मक, cycles को सूची देखाउँछ। `check:cycles:ratchet` (CI ले चलाउने) ले गणना `quality-baseline.json` मा रहेको `metrics.cycles` को अधिकतम सीमाभन्दा बढी हुँदा अवरुद्ध गर्छ — हाल 14, `direction: down`, त्यसैले यो घट्न मात्र सक्छ (#15159 G-01/G-02) | हो (ratchet) | +| `check:route-validation:t06` | सबै routes मा Zod schemas उपस्थित छन् (Tier 6 नीति) | हो | +| `check:any-budget:t11` | `@ts-expect-error // any` को गणना budget भन्दा बढी हुँदैन (Tier 11 catraca) | हो | +| `check:provider-consistency` | `providers.ts` मा रहेको प्रत्येक प्रदायकको `providerRegistry.ts` मा मिल्दो प्रविष्टि छ (र यसको उल्टो पनि, अनुमति सूचीभित्र) | हो | +| `check:model-lifecycle` | हातैले मर्मत गरिने तीनवटा राउटिङ तालिकाहरू चेक-इन गरिएको जीवनचक्र स्न्यापसट (#11503) सँग सुसंगत रहन्छन्: `FITNESS_TABLE` (`taskFitness.ts`) ले `REGISTRY` ले राउट गर्न सक्ने कुनै पनि अवकाशप्राप्त id लाई स्कोर गर्दैन; प्रत्येक `BUILT_IN_ALIASES` लक्ष्य `REGISTRY` मा उपस्थित र अवकाशप्राप्त-id स्न्यापसटमा अनुपस्थित हुन्छ; `REGISTRY` मा अझै रहेका प्रत्येक अवकाशप्राप्त id लाई फर्वार्ड गरिएको वा `allowedRetiredInCatalog` मा सूचीबद्ध गरिएको हुन्छ; र कुनै पनि `DEFAULT_DEGRADATION_MAP` स्रोत वा लक्ष्य उक्त स्न्यापसटमा अवकाशप्राप्त देखिँदैन। यसले कुनै मोडेल हाल प्रत्यक्ष अपस्ट्रिमद्वारा उपलब्ध गराइएको छ भन्ने प्रमाणित गर्दैन। अफलाइन — `config/quality/model-lifecycle.json` सँग तुलना गर्छ, जसलाई `npm run quality:refresh-model-lifecycle` (नेटवर्क; CI मा जडान नगरिएको) मार्फत हातैले ताजा गरिन्छ। `allowedRetiredInCatalog` क्रमशः घटाउँदै लैजाने र्याचेट हो: ट्र्याकिङ इश्युसहित मात्र प्रविष्टि थप्नुहोस्। | हो | +| `check:fetch-targets` | क्लाइन्ट-साइड `src/` मा रहेको प्रत्येक `fetch("/api/...")` वास्तविक `route.ts` मा रिजोल्भ हुन्छ | हो | +| `check:deps` | रिपोजिटरीको प्रत्येक `package.json` मा रहेका सबै `npm install` गर्न मिल्ने निर्भरताहरू `dependency-allowlist.json` मा छन्; नयाँ पिन नगरिएका वा स्लपस्क्वाट गरिएका प्याकेजहरू चिन्ह लगाइन्छन् | हो | +| `audit:deps` | `npm audit` (रुट + electron) — कुनै उच्च/गम्भीर एडभाइजरी छैनन् (osv `check:vuln-ratchet` सँग ओभरल्याप हुन्छ; युक्तिसङ्गतीकरण ब्याकलग हेर्नुहोस्) | हो | +| `check:lockfile` | `package-lock.json` को अखण्डता — https रजिस्ट्री, अखण्डता ह्यासहरू, कुनै होस्ट ओभरराइड छैन | हो | +| `check:licenses` | उत्पादन निर्भरताहरूका लागि SPDX इजाजतपत्र अनुमति-सूची | हो | +| `check:tracked-artifacts` | कुनै बिल्ड आर्टिफ्याक्ट / कमिट गरिएका `node_modules` सिमलिङ्कहरू छैनन् (husky pre-commit मा पनि चल्छ; pre-push लाई जानाजानी हल्का राखिएको छ — #6716) | हो | +| `check:ai-attribution` | PR कमिटहरू, शीर्षक वा मुख्य सामग्रीमा कुनै AI/bot `Co-Authored-By` ट्रेलर वा AI-उत्पादन फुटर छैन — कडा नियम #16 (`quality.yml` को PR→`release/**` का लागि fast-gates लूपमा — इभेन्ट पेलोड पढ्छ, PR बाहिर कुनै कार्य गर्दैन — र `ci.yml` lint मा PR→`main` का लागि PR-मात्र चरण; साथै husky `commit-msg` हुक; मानव सह-लेखकहरूलाई अनुमति छ; #14436) | +| `check:vitest-exclusions` | प्रत्येक Vitest बहिष्करणले ट्र्याकिङ समस्या उल्लेख गर्छ र `config/quality/vitest-exclusions.json` मा देखा पर्छ (#13204) | हो | +| `check:file-size` | कुनै पनि स्रोत फाइलले प्रत्येक एक्सटेन्सनको सीमा नाघ्दैन (र्याचेट: `frozen` सूचीमा स्थिर गरिएका ठूला फाइलहरू) | हो | +| `check:error-helper` | executors/handlers का त्रुटि प्रतिक्रियाहरूले `buildErrorBody()` / `sanitizeErrorMessage()` प्रयोग गर्छन् (कडा नियम #12) | हो | +| `check:migration-numbering` | Migration SQL फाइलहरू क्रमिक रूपमा नम्बर गरिएका छन्, कुनै खाली क्रम वा दोहोरिएका नम्बरहरू छैनन् | हो | +| `check:public-creds` | `publicCreds.ts` बाहिर कुनै पनि प्रत्यक्ष OAuth `client_id`/`client_secret` वा Firebase Web कुञ्जीहरू छैनन् (कडा नियम #11) | हो | +| `check:db-rules` | `src/lib/db/` मोड्युलहरू बाहिर कुनै पनि कच्चा SQL छैन; `localDb.ts` बाट कुनै पनि ब्यारेल-इम्पोर्ट छैन (कडा नियमहरू #2/#5) | हो | +| `check:known-symbols` | आफ्ना डिस्प्याच तालिकाहरूमा दर्ता गरिएका प्रदायक एक्जिक्युटरहरू, राउटिङ रणनीतिहरू र अनुवादकहरू डिस्कमा रहेका फाइलहरूसँग मेल खान्छन् — कुनै पनि अनाथ वा अघोषित सिम्बलहरू छैनन् | हो | +| `check:route-guard-membership` | चाइल्ड प्रोसेस सुरु गर्ने प्रत्येक रुटलाई `isLocalOnlyPath()` द्वारा वर्गीकृत गरिएको छ (कडा नियमहरू #15/#17) | हो | +| `check:test-discovery` | रिपोजिटरीमा रहेको प्रत्येक `*.test.ts` / `*.spec.ts` फाइल कम्तीमा एउटा टेस्ट रनरद्वारा सङ्कलन गरिन्छ (र्याचेट: `test-discovery-baseline.json` मा रहेको अनाथ सूची घट्न मात्र सक्छ) | हो | +| `check:agent-skills-sync` | उत्पन्न गरिएका agent-skills आर्टिफ्याक्टहरू तिनको स्रोत क्याटलगसँग मेल खान्छन् (कुनै विचलन छैन) | +| `check:provider-asset-provenance` | प्रदायकका लोगो/एसेटहरूमा अभिलेख गरिएको उत्पत्ति प्रविष्टि हुन्छ | +| `lint:json` | JSON कन्फिग फाइलहरू पार्स हुन्छन् र repo का lint नियमहरू पूरा गर्छन् | +| `typecheck:core` | त्रुटिविनाको TypeScript कम्पाइलेसन (सल्लाहात्मक चेतावनीहरू मात्र) | हो | +| `typecheck:noimplicit:core` | कडा `noImplicitAny` — भविष्य-उन्मुख; पहिलेबाट रहेका धेरै call site हरूलाई अझै annotation आवश्यक छ | **सल्लाहात्मक** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` मा सीमित `tsc` (#7033) — `typecheck:core` को चयनित 27-फाइल allowlist मा कुनै पनि dashboard TSX समावेश छैन, र `next build` ले पनि यसलाई कहिल्यै type-check गर्दैन (`next.config.mjs` ले `ignoreBuildErrors: true` सेट गर्छ), त्यसैले त्यहाँका orphaned-identifier regression हरू (#6625/#6909) CI का लागि अदृश्य थिए। स्थिर गरिएको प्रति-फाइल/प्रति-TS-code सङ्ख्या baseline (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` कै जस्तो stale-enforcement ढाँचा) सँग diff गरिन्छ — baseline मा रहेको सङ्ख्याभन्दा बढीका नयाँ त्रुटिहरूले मात्र gate असफल गराउँछन्; पहिलेबाट रहेको त्रुटि समाधान हुँदा `--update` प्रयोग गरेर क्रमशः घटाउनुहोस्। | हो | ### Job: `quality-gate` `test-coverage` पछि चल्छ। असफल भएमा merge रोक्छ। -| स्क्रिप्ट | प्रमाणीकरण गर्छ | अवरोधक | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | `quality-metrics.json` उत्सर्जन गर्छ (ESLint चेतावनी सङ्ख्या, मर्ज गरिएको shard रिपोर्टबाट coverage) | हो (ratchet भन्दा अगाडि) | -| `quality:ratchet` | `quality-baseline.json` को प्रत्येक metric पछाडि परेको छैन (ESLint चेतावनीहरू ≤ baseline; coverage ≥ baseline) | हो | -| `check:duplication` | कोड दोहोरोपन (jscpd@4) ले `quality-baseline.json` को baseline नाघ्दैन | हो | -| `check:complexity` | फाइल-स्तरीय cyclomatic complexity ले सीमा नाघ्दैन (मुख्य ESLint `complexity` + `max-lines-per-function`) | हो | -| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — छुट्टै ESLint pass; CI ले दुवैलाई एउटै `check:complexity-ratchets` चरणका रूपमा मर्ज गरेर चलाउँछ | हो | -| `check:dead-code` | प्रयोग नगरिएका exports / files को ratchet (knip) baseline को तुलनामा पछाडि पर्दैन | हो | -| `check:compression-budget` | Compression benchmark budget — प्रति-engine token-savings का न्यूनतम सीमाहरू पछाडि पर्नु हुँदैन | हो | -| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) पछाडि पर्दैन; यसले धेरै हदसम्म `typecheck:noimplicit:core` लाई समेट्छ | हो | -| `check:codeql-ratchet` | खुला CodeQL alert सङ्ख्या पछाडि पर्दैन (`gh api` मार्फत पढ्छ; token बिना सुचारु रूपमा skip गर्छ) — refresh cadence र manual trigger का लागि: तलको "CodeQL ratchet" हेर्नुहोस् | हो | - -### कार्य: `quality-extended` - -सम्पूर्ण कार्य परामर्शात्मक हो (`continue-on-error: true`)। npm-आधारित ratchets वास्तविक रूपमा -चल्छन्; बाह्य scanners हरू `gh release download` मार्फत स्थापना हुन्छन् र binary अझै अनुपस्थित हुँदा +| स्क्रिप्ट | प्रमाणीकरण गर्छ | अवरोधक | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `quality:collect` | `quality-metrics.json` उत्सर्जन गर्छ (ESLint चेतावनी सङ्ख्या, मर्ज गरिएको shard रिपोर्टबाट coverage) | हो (ratchet भन्दा अघिल्लो) | +| `quality:ratchet` | `quality-baseline.json` मा भएको प्रत्येक metric खस्किएको छैन (ESLint चेतावनीहरू ≤ baseline; coverage ≥ baseline) | हो | +| `check:duplication` | कोड दोहोरोपन (jscpd@4) ले `quality-baseline.json` को baseline नाघ्दैन | हो | +| `check:complexity` | फाइल-स्तरीय cyclomatic complexity ले सीमा नाघ्दैन (core ESLint `complexity` + `max-lines-per-function`) | हो | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — छुट्टै ESLint pass; CI ले दुवैलाई एकल `check:complexity-ratchets` चरणका रूपमा मर्ज गरेर चलाउँछ | हो | +| `check:dead-code` | प्रयोग नगरिएका exports / files ratchet (knip) baseline को तुलनामा खस्किँदैन | हो | +| `check:compression-budget` | Compression benchmark budget — प्रति-engine token-savings का न्यूनतम सीमाहरू खस्किनु हुँदैन | हो | +| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) खस्किँदैन; यसले धेरै हदसम्म `typecheck:noimplicit:core` लाई समेट्छ | हो | +| `check:codeql-ratchet` | खुला CodeQL alert सङ्ख्या खस्किँदैन (`gh api` मार्फत पढ्छ; token नभए सहज रूपमा skip गर्छ) — refresh cadence र manual trigger: तलको "CodeQL ratchet" हेर्नुहोस् | हो | + +### Job: `quality-extended` + +सम्पूर्ण job सल्लाहात्मक हो (`continue-on-error: true`)। npm-आधारित ratchets वास्तवमै +चल्छन्; बाह्य scanners `gh release download` मार्फत install हुन्छन् र binary अझै अनुपस्थित हुँदा आफैँ skip हुन्छन् (exit 0)। -| स्क्रिप्ट | प्रमाणीकरण गर्छ | अवरोधक | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| `check:circular-deps` | कुनै circular dependencies छैनन् (dpdm) | **परामर्शात्मक** | -| `check:bundle-size` | Bundle size ले सीमा नाघ्दैन | **परामर्शात्मक** | -| `check:secrets` | Secret scanning (gitleaks) — binary अनुपस्थित भए skip गर्छ | **परामर्शात्मक** | -| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) पछाडि पर्दैनन् — binary अनुपस्थित भए skip गर्छ | **परामर्शात्मक** | -| `check:workflows` | Workflow lint (actionlint + zizmor); अनुपस्थित/बिग्रिएका scanners, अमान्य reports वा अनुपस्थित ratchet baseline भए INCOMPLETE का रूपमा असफल हुन्छ। मान्य findings ले चयन गरिएको strict/advisory/ratchet policy पालना गर्छन् | कार्यान्वयन आवश्यक; CI मा zizmor ratchet अवरोधक | -| `check:openapi-breaking` | आधार शाखाको तुलनामा सार्वजनिक API contract (`openapi.yaml`) मा breaking changes (oasdiff) — `openapiBreaking=N` उत्सर्जन गर्छ; oasdiff अनुपस्थित भए वा base spec समाधान गर्न नसकिए skip गर्छ | **परामर्शात्मक** | - -### कार्य: `docs-sync-strict` - -`main` तर्फको प्रत्येक PR मा चल्छ। असफल भए merge हुनबाट रोक्छ। - -| स्क्रिप्ट | प्रमाणीकरण गर्ने विषय | अवरोधक | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `check:docs-all` | तलका ६ वटा उप-गेटलाई क्रमिक रूपमा चलाउने मेटा-गेट | हो | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt संस्करणको एकरूपता | हो | -| ↳ `check:docs-counts` | गद्यमा भएका सङ्ख्याहरू (प्रदायक सङ्ख्या, माइग्रेसन सङ्ख्या आदि) वास्तविक सङ्ख्याको र्याचेट सीमाभित्र छन् | हो | -| ↳ `check:env-doc-sync` | `.env.example` मा भएको प्रत्येक env var कागजातको तालिकामा अभिलेखित छ, र यसको उल्टो पनि सत्य छ | हो | -| ↳ `check:deprecated-versions` | कागजातमा बहिष्कृत संस्करण स्ट्रिङहरू छैनन् | हो | -| ↳ `check:doc-links` | कागजातमा भएका आन्तरिक markdown लिङ्कहरू वास्तविक फाइलहरूमा समाधान हुन्छन् (`[text]`/`(path)` ढाँचा) | हो | -| ↳ `check:fabricated-docs` | कागजातमा उल्लेख गरिएका रुट, env vars, CLI कमान्ड, hook नाम र फाइल पाथहरू कोडबेसमा अवस्थित छन्। `--strict` मार्फत कडा गेट; फ्ल्यागबिना सफ्ट-फेल। | हो (CI मा `--strict` मार्फत) | -| `check:cli-i18n` | CLI कमान्ड स्ट्रिङहरू सबै i18n locale फाइलहरूमा उपस्थित छन् | हो | -| `check:openapi-coverage` | OpenAPI spec ले वास्तविक रुटहरूको कम्तीमा र्याचेट गरिएको न्यूनतम सीमा समेट्छ | हो | -| `check:openapi-security-tiers` | `openapi.yaml` मा भएका सुरक्षा तहका एनोटेसनहरू `routeGuard.ts` का वर्गीकरणहरूसँग एकरूप छन् | **सल्लाहमूलक** | -| `check:openapi-routes` | `openapi.yaml` को प्रत्येक पाथ वास्तविक `route.ts` मा समाधान हुन्छ (भ्रमजन्य विवरण-विरोधी) | हो | -| `check:docs-symbols` | `docs/**/*.md` मा भएको प्रत्येक `/api/...` सन्दर्भ वास्तविक `route.ts` मा समाधान हुन्छ (भ्रमजन्य विवरण-विरोधी) | हो | -| `i18n translation drift` | i18n locale फाइलहरूमा अनुवाद नगरिएका key हरू — चेतावनी मात्र | **सल्लाहमूलक** | +| स्क्रिप्ट | प्रमाणीकरण गर्छ | अवरोधक | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| `check:circular-deps` | कुनै circular dependencies छैनन् (dpdm) | **सल्लाहात्मक** | +| `check:bundle-size` | Bundle size ले सीमा नाघ्दैन | **सल्लाहात्मक** | +| `check:secrets` | Secret scanning (gitleaks) — binary अनुपस्थित भए skip गर्छ | **सल्लाहात्मक** | +| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) खस्किँदैनन् — binary अनुपस्थित भए skip गर्छ | **सल्लाहात्मक** | +| `check:workflows` | Workflow lint (actionlint + zizmor); हराएका/बिग्रिएका scanners, अमान्य reports वा हराएको ratchet baseline भए INCOMPLETE का रूपमा असफल हुन्छ। मान्य findings ले चयन गरिएको strict/advisory/ratchet policy पालना गर्छन् | कार्यान्वयन आवश्यक; CI मा zizmor ratchet अवरोधक | +| `check:openapi-breaking` | आधार branch को तुलनामा सार्वजनिक API contract (`openapi.yaml`) मा भएका breaking changes (oasdiff) — `openapiBreaking=N` उत्सर्जन गर्छ; oasdiff अनुपस्थित भए वा आधार spec resolve गर्न नसकिए skip गर्छ | **सल्लाहात्मक** | + +### Job: `docs-sync-strict` + +`main` तर्फको प्रत्येक PR मा चल्छ। असफल भए merge रोक्छ। + +| स्क्रिप्ट | प्रमाणीकरण | अवरोधक | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `check:docs-all` | तलका ६ उप-गेटहरू क्रमिक रूपमा चलाउने मेटा-गेट | हो | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt संस्करणको एकरूपता | हो | +| ↳ `check:docs-counts` | गद्यमा भएका सङ्ख्याहरू (प्रदायक सङ्ख्या, माइग्रेसन सङ्ख्या आदि) वास्तविक सङ्ख्याहरूको र्याचेट विन्डोभित्र छन् | हो | +| ↳ `check:env-doc-sync` | `.env.example` मा भएको प्रत्येक env var कागजातको तालिकामा अभिलेखित छ, र यसको उल्टो पनि सत्य छ | हो | +| ↳ `check:deprecated-versions` | कागजातमा कुनै बहिष्कृत संस्करण स्ट्रिङ छैन | हो | +| ↳ `check:doc-links` | कागजातभित्रका आन्तरिक markdown लिङ्कहरू वास्तविक फाइलहरूमा समाधान हुन्छन् (`[text]`/`(path)` ढाँचा) | हो | +| ↳ `check:fabricated-docs` | कागजातमा उल्लेख गरिएका रुट, env vars, CLI आदेश, हुक नाम र फाइल पथहरू कोडबेसमा अवस्थित छन्। `--strict` मार्फत हार्ड गेट; फ्ल्यागबिना सफ्ट-फेल। | हो (CI मा `--strict` मार्फत) | +| `check:cli-i18n` | CLI आदेशका स्ट्रिङहरू सबै i18n लोकेल फाइलमा उपस्थित छन् | हो | +| `check:openapi-coverage` | OpenAPI स्पेकले वास्तविक रुटहरूको कम्तीमा र्याचेट गरिएको न्यूनतम सीमा समेट्छ | हो | +| `check:openapi-security-tiers` | `openapi.yaml` मा भएका सुरक्षा तहका एनोटेसनहरू `routeGuard.ts` का वर्गीकरणहरूसँग एकरूप छन् | **परामर्शात्मक** | +| `check:openapi-routes` | `openapi.yaml` को प्रत्येक पथ वास्तविक `route.ts` मा समाधान हुन्छ (भ्रमित सिर्जना-विरोधी) | हो | +| `check:docs-symbols` | `docs/**/*.md` मा भएको प्रत्येक `/api/...` सन्दर्भ वास्तविक `route.ts` मा समाधान हुन्छ (भ्रमित सिर्जना-विरोधी) | हो | +| `i18n translation drift` | i18n लोकेल फाइलहरूमा अनुवाद नगरिएका कुञ्जीहरू — चेतावनी मात्र | **परामर्शात्मक** | ### कार्य: `i18n-ui-coverage` -| स्क्रिप्ट | प्रमाणीकरण गर्ने विषय | अवरोधक | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (इनलाइन) | UI i18n key कभरेज ≥ 65% छ | हो | -| `check-ui-value-drift` (इनलाइन) | पुनर्लेखन गरिएको अङ्ग्रेजी **मान** ले कुनै पुरानो अनुवाद बाँकी राख्दैन | हो | -| `check-new-key-coverage` (इनलाइन) | **नयाँ** अङ्ग्रेजी key प्रत्येक locale मा अनुवाद गरिएको छ — `__MISSING__:` मार्कर अस्वीकार गरिन्छ | हो | -| `check-translation-ratio` | प्रति locale वास्तविक-अनुवाद अनुपात (allowlist बाहिरका अङ्ग्रेजीसँग समान / placeholder / हराएका leaf हरू) `config/quality/i18n-translation-baseline.json` + slack भन्दा बढी हुनु हुँदैन | **सल्लाहमूलक** | +| स्क्रिप्ट | प्रमाणीकरण | अवरोधक | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `check-ui-keys-coverage` (इनलाइन) | UI i18n कुञ्जी कभरेज ≥ 65% छ | हो | +| `check-ui-value-drift` (इनलाइन) | पुनर्लेखन गरिएको अङ्ग्रेजी **मान** ले कुनै पुरानो अनुवाद बाँकी राख्दैन | हो | +| `check-new-key-coverage` (इनलाइन) | एउटा **नयाँ** अङ्ग्रेजी कुञ्जी प्रत्येक लोकेलमा अनुवाद गरिएको छ — `__MISSING__:` मार्कर अस्वीकार गरिन्छ | हो | +| `check-translation-ratio` | प्रत्येक लोकेलको वास्तविक-अनुवाद अनुपात (अनुमति सूचीबाहिरका अङ्ग्रेजीसँग समान / प्लेसहोल्डर / छुटेका पातहरू) `config/quality/i18n-translation-baseline.json` + छुटभन्दा बढी हुनु हुँदैन | **परामर्शात्मक** | -`fetch-depth: 0` आवश्यक पर्छ — value-drift गेटले `en.json` लाई merge base सँग diff गर्छ। +`fetch-depth: 0` आवश्यक पर्छ — मान-ड्रिफ्ट गेटले मर्ज बेससँग `en.json` को diff गर्छ। #### `check-ui-value-drift` — पुरानो-अनुवाद गेट -अन्य गेटहरूले संरचनात्मक रूपमा देख्न नसक्ने एउटा i18n regression यसले समात्छ: अङ्ग्रेजी मान -पुनर्लेखन गरिन्छ र _अघिल्लो_ अङ्ग्रेजीबाट व्युत्पन्न अनुवादहरू यथावत् रहन्छन्, जसले गर्दा -गैर-अङ्ग्रेजी प्रयोगकर्ताहरूले आत्मविश्वासपूर्ण शैलीमा लेखिएको तर अहिले गलत भइसकेको पाठ पढिरहन्छन्। +यसले एउटा यस्तो i18n रिग्रेसन समात्छ जुन अन्य गेटहरूले संरचनात्मक रूपमा देख्न सक्दैनन्: कुनै अङ्ग्रेजी मान +पुनर्लेखन गरिन्छ र _अघिल्लो_ अङ्ग्रेजीबाट व्युत्पन्न अनुवादहरू यथावत् रहन्छन्, जसका कारण +गैर-अङ्ग्रेजी प्रयोगकर्ताहरूले आत्मविश्वासपूर्ण भाषामा लेखिएको तर अहिले गलत प्रतिलिपि पढिरहन्छन्। -यो समस्या वास्तवमै जारी गरिएको थियो। Antigravity लगइन helper थपिँदा (#5203) -`oauthModal.googleOAuthWarning` पुनर्लेखन गरिएको थियो; **४३ मध्ये ३९ locale** मा अपरेटरहरूलाई -"पूरा URL प्रतिलिपि गरेर तल टाँस्नुहोस्" भन्ने पाठ कायम रह्यो — त्यो प्रदायकका लागि पूरा हुनै नसक्ने प्रक्रिया। -यो #8463 सम्म ध्यानमा आएन, किनभने: +यो समस्या वास्तवमै रिलिज भएको थियो। Antigravity लगइन सहायक आएपछि (#5203) +`oauthModal.googleOAuthWarning` पुनर्लेखन गरिएको थियो; **४३ मध्ये ३९ लोकेलहरूमा** सञ्चालकहरूलाई "पूरै URL प्रतिलिपि गरेर तल टाँस्नुहोस्" भन्ने पाठ यथावत् रह्यो — त्यो प्रदायकका लागि पूरा हुनै नसक्ने प्रवाह। यो +#8463 सम्म ध्यानमा आएन, किनभने: -- `sync-ui-keys` ले **अनुपस्थित** key हरू मात्र backfill गर्छ, **पुराना** key हरू कहिल्यै गर्दैन; -- `check-ui-keys-coverage` ले key को _उपस्थिति_ गणना गर्छ, त्यसैले पुरानो अनुवाद पनि समेटिएको मानिन्छ; -- `check-translation-drift` ले `docs/i18n//**.md` का कागजात mirror हरू ट्र्याक गर्छ — - यसले `src/i18n/messages/*.json` कहिल्यै पढ्दैन। 2026-09 को पुनः-सिङ्कदेखि `docs-sync-strict` कार्यमा अवरोधक: - मुख्य कागजात सम्पादन गर्नुहोस् → `npm run i18n:run -- --files=` (खण्ड-स्तरीय, कम खर्चिलो)। +- `sync-ui-keys` ले केवल **अनुपस्थित** कुञ्जीहरू ब्याकफिल गर्छ, **पुराना** भएकाहरूलाई कहिल्यै गर्दैन; +- `check-ui-keys-coverage` ले कुञ्जीको _उपस्थिति_ गन्छ, त्यसैले पुरानो अनुवाद पनि समेटिएको मानिन्छ; +- `check-translation-drift` ले `docs/i18n//**.md` का कागजात मिररहरू ट्र्याक गर्छ — + यसले `src/i18n/messages/*.json` कहिल्यै पढ्दैन। 2026-09 पुनः-सिङ्कदेखि कार्य `docs-sync-strict` मा अवरोधक: मुख्य कागजात सम्पादन गर्नुहोस् → `npm run i18n:run -- --files=` (खण्ड-स्तरीय, सस्तो)। -**Diff-सचेत, आधाररेखामा निर्भर नरहेको।** यसले merge base मा रहेको `en.json` लाई +**Diff-aware हो, baseline-backed होइन।** यसले merge base मा रहेको `en.json` लाई working tree सँग तुलना गर्छ; अंग्रेजी मान परिवर्तन भएको प्रत्येक key का लागि, अझै पनि -नछोइएको अनुवाद राख्ने कुनै पनि locale पुरानो मानिन्छ। यसले जानाजानी **पहिलेदेखिको ऋणलाई स्थिर राख्छ** — -लामो समयदेखि रहेको अनुवाद कुन पुरानो अंग्रेजीबाट आएको हो भनेर diff ले देखाउन सक्दैन, त्यसैले gate ले -हालको परिवर्तनले छोएका कुराहरूलाई मात्र जाँच्छ। यसको विकल्प (प्रति-key hash आधाररेखा) का लागि -करिब 600 KB को generated file चाहिन्थ्यो, जुन सबैभन्दा ठूलो विद्यमान आधाररेखाभन्दा 3× ठूलो हो र प्रत्येक i18n PR मा परिवर्तन भइरहन्थ्यो। +नछोइएको अनुवाद भएको कुनै पनि locale बासी मानिन्छ। यसले जानाजानी **पहिलेदेखिको ऋणलाई स्थिर राख्छ** — diff ले +लामो समयदेखि रहेको अनुवाद कुन पुरानो अंग्रेजीबाट आएको हो भनेर देखाउन सक्दैन, त्यसैले gate ले +हालको परिवर्तनले छोएका कुराहरूलाई मात्र मूल्याङ्कन गर्छ। यसको विकल्प (प्रत्येक key का लागि hash baseline) का लागि +करिब 600 KB को generated file आवश्यक पर्थ्यो, जुन सबैभन्दा ठूलो विद्यमान baseline भन्दा 3× ठूलो हो र प्रत्येक i18n PR मा फेरबदल हुन्थ्यो। यसलाई पूरा गर्ने दुई तरिका छन्: 1. प्रभावित अनुवादहरू अद्यावधिक गर्नुहोस्, वा -2. तिनलाई `__MISSING__:` मा सेट गर्नुहोस् — त्यसपछि runtime ले सच्याइएको अंग्रेजी - (`src/i18n/request.ts::deepMergeFallback`, #7258) प्रदान गर्छ र उक्त key अनुवादका लागि पङ्क्तिबद्ध हुन्छ। +2. तिनलाई `__MISSING__:` मा सेट गर्नुहोस् — त्यसपछि runtime ले सच्याइएको अंग्रेजी उपलब्ध गराउँछ + (`src/i18n/request.ts::deepMergeFallback`, #7258) र key अनुवादका लागि लामबद्ध हुन्छ। -यदि string को **अर्थ** परिवर्तन भएको हो भने, **key को नाम परिवर्तन गर्नुलाई** प्राथमिकता दिनुहोस्: नयाँ key ले -पुरानो अनुवाद उत्तराधिकारमा पाउन सक्दैन। #8463 ले प्रयोग गरेको ढाँचा यही हो। +यदि string को **अर्थ** परिवर्तन भएको छ भने, **key को नाम परिवर्तन गर्नुलाई** प्राथमिकता दिनुहोस्: नयाँ key ले +बासी अनुवाद उत्तराधिकारमा लिन सक्दैन। #8463 ले यही ढाँचा प्रयोग गरेको थियो। ```bash npm run i18n:check-value-drift # कडा (CI ले चलाउने) @@ -232,48 +230,48 @@ npm run i18n:check-value-drift:warn # रिपोर्ट मात्र BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -आधार catalog पढ्न नसकिँदा (base ref बिनाको shallow -clone), `check-openapi-breaking` को व्यवहारअनुरूप `SKIP reason=base-unresolved` सहित 0 मा exit हुन्छ। +base catalog पढ्न नसकिँदा (base ref बिनाको shallow +clone), `check-openapi-breaking` कै व्यवहारअनुसार `SKIP reason=base-unresolved` सहित 0 मा निस्कन्छ। ### Job: `i18n` -पूर्ण i18n प्रमाणीकरण matrix (प्रति locale एउटा job)। सम्पूर्ण job परामर्शात्मक हो। +पूर्ण i18n प्रमाणीकरण matrix (प्रत्येक locale का लागि एउटा job)। सम्पूर्ण job परामर्शात्मक हो। -| Script | प्रमाणीकरण गर्ने कुरा | अवरोध गर्ने | -| ------------------------------- | ----------------------------- | ------------------------------------------------------------ | -| `validate_translation.py quick` | प्रति locale अनुवादको पूर्णता | **परामर्शात्मक** (सम्पूर्ण job मा `continue-on-error: true`) | +| Script | प्रमाणीकरण गर्ने कुरा | अवरोधक | +| ------------------------------- | --------------------------------- | ------------------------------------------------------------ | +| `validate_translation.py quick` | प्रत्येक locale को अनुवाद पूर्णता | **परामर्शात्मक** (सम्पूर्ण job मा `continue-on-error: true`) | ### Job: `pr-test-policy` pull request हरूमा मात्र चल्छ। -| Script | प्रमाणीकरण गर्ने कुरा | अवरोध गर्ने | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, वा `bin/` मा production code परिवर्तन गर्ने PR हरूले test समावेश वा अद्यावधिक गर्नुपर्छ (Hard Rule #8) | हो | -| `check:test-masking` | परिवर्तन गरिएका test file हरूले कुल assert सङ्ख्या घटाउँदैनन् वा `assert.ok(true)` जस्ता स्वतःसिद्ध कथन थप्दैनन् | हो | -| `check:pr-evidence` | PR body ले परिवर्तनका लागि test/VPS प्रमाण उल्लेख गर्छ (PR prose लाई grep गरेर Hard Rule #18 यान्त्रिकीकरण गर्छ — नाजुक, Backlog हेर्नुहोस्) | हो | +| Script | प्रमाणीकरण गर्ने कुरा | अवरोधक | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, वा `bin/` मा production code परिवर्तन गर्ने PR हरूले test समावेश वा अद्यावधिक गर्नुपर्छ (कडा नियम #8) | हो | +| `check:test-masking` | परिवर्तन गरिएका test file हरूले कुल assert सङ्ख्या घटाउँदैनन् वा `assert.ok(true)` जस्ता tautology थप्दैनन् | हो | +| `check:pr-evidence` | PR body ले परिवर्तनका लागि test/VPS प्रमाण उद्धृत गर्छ (PR prose लाई grep गरेर कडा नियम #18 लाई यान्त्रिक बनाउँछ — कमजोर छ, Backlog हेर्नुहोस्) | हो | ### Job: `test-vitest` `build` पछि चल्छ। असफल भएमा merge रोक्छ। -| Suite | प्रमाणीकरण गर्ने कुरा | अवरोध गर्ने | -| ---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | हो | -| `test:vitest:ui` | UI component test हरू — vitest runner | **अवरोध गर्ने** — पहिलेदेखिका failure हरू `vitest.config.ts` मा स्पष्ट रूपमा बहिष्कृत छन्; नयाँ failure ले job असफल गराउँछन् | +| Suite | प्रमाणीकरण गर्ने कुरा | अवरोधक | +| ---------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | हो | +| `test:vitest:ui` | UI component test हरू — vitest runner | **अवरोधक** — पहिलेदेखिका failure हरूलाई `vitest.config.ts` मा स्पष्ट रूपमा बाहिर राखिएको छ; नयाँ failure ले job असफल बनाउँछन् | ### रात्रिकालीन workflow हरू (तालिकाबद्ध, परामर्शात्मक) -यी cron schedule मा (र `workflow_dispatch` मार्फत) चल्छन्, PR हरूमा कहिल्यै चल्दैनन्। सबै परामर्शात्मक हुन्। +यी cron तालिकामा (र `workflow_dispatch` मार्फत) चल्छन्, PR हरूमा कहिल्यै चल्दैनन्। सबै परामर्शात्मक हुन्। -| Workflow | प्रमाणीकरण गर्ने कुरा | अवरोध गर्ने | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | random seed + उच्च run count सहितका fast-check property test हरू | **परामर्शात्मक** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **परामर्शात्मक** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probe हरू (provider secret नभएमा छोडिन्छ) | **परामर्शात्मक** | -| `nightly-schemathesis` | `docs/openapi.yaml` प्रयोग गरी live OmniRoute विरुद्ध OpenAPI contract fuzzing (schemathesis) — spec उल्लङ्घन / सम्हाल्न नसकिएका 500 हरू उजागर गर्छ (चरण 8 B.4) | **परामर्शात्मक** | -| `nightly-mutation` | द्रुत unit lane मा Stryker mutation-testing score — जीवित रहने mutant हरूले कमजोर assert उजागर गर्छन् | **परामर्शात्मक** | -| `nightly-compat` | समर्थित `engines.node` range हरूमा Node engine compatibility matrix | **परामर्शात्मक** | +| Workflow | प्रमाणीकरण गर्ने कुरा | अवरोधक | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | +| `nightly-property` | random seed + उच्च run count सहितका fast-check property test हरू | **परामर्शात्मक** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **परामर्शात्मक** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probe हरू (provider secret नभएमा छोडिन्छ) | **परामर्शात्मक** | +| `nightly-schemathesis` | `docs/openapi.yaml` प्रयोग गरी live OmniRoute विरुद्ध OpenAPI contract fuzzing (schemathesis) — spec उल्लङ्घन / ह्यान्डल नगरिएका 500 हरू देखाउँछ (चरण 8 B.4) | **परामर्शात्मक** | +| `nightly-mutation` | द्रुत unit lane मा Stryker mutation-testing score — बाँचेका mutant हरूले कमजोर assert देखाउँछन् | **परामर्शात्मक** | +| `nightly-compat` | समर्थित `engines.node` range हरूमा Node engine compatibility matrix | **परामर्शात्मक** | --- @@ -535,43 +533,44 @@ CI गेटहरूभन्दा बाहिर, OmniRoute ले **ऐच --- -## युक्तिसंगतीकरण ब्याकलग (ROI समीक्षा — चरण 9 लहर 3) +## युक्तिसङ्गतीकरण ब्याकलग (ROI समीक्षा — चरण 9 लहर 3) यो सूचीलाई 2026-06-17 मा `ci.yml` सँग मिलान गरिएको थियो (अघिल्लो संस्करणमा `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` छुटेका थिए)। मिलान गरिएको सेटको ROI समीक्षाले -निम्न युक्तिसंगतीकरणका उम्मेदवारहरू पहिचान गर्यो। **मर्जहरू यान्त्रिक CI -परिवर्तनहरू हुन्; फ्लिप/हटाउने निर्णयहरू अपरेटरका लागि आरक्षित नीतिगत निर्णय हुन्।** तलका कुनै पनि कुरा -अहिलेसम्म लागू गरिएको छैन। +`check:codeql-ratchet`, `check:pr-evidence` छुटेका थिए)। मिलान गरिएको सेटको ROI +समीक्षाले निम्न युक्तिसङ्गतीकरणका उम्मेदवारहरू पहिचान गर्यो। **मर्जहरू यान्त्रिक CI +परिवर्तनहरू हुन्; फ्लिप/हटाउने कार्यहरू अपरेटरका लागि आरक्षित नीतिगत निर्णयहरू हुन्।** +तलका कुनै पनि कुरा अहिलेसम्म लागू गरिएको छैन। -**माथि कागजातबद्ध नगरिएका थप विषयहरू** (परामर्शात्मक, न्यून सङ्केत): `docs-lint` job -(markdownlint + Vale, सम्पूर्ण job मा `continue-on-error`) र छुट्टाछुट्टै scanner workflow हरू +**माथि दस्तावेजीकरण नगरिएका थप कुराहरू** (परामर्शात्मक, न्यून सङ्केत): `docs-lint` जब +(markdownlint + Vale, सम्पूर्ण जबमा `continue-on-error`) र छुट्टाछुट्टै स्क्यानर वर्कफ्लोहरू `semgrep.yml` / `codeql.yml` / `scorecard.yml`। `semgrepFindings: 0` -`quality-baseline.json` मा छ तर `ci.yml` मा अवरोधकारी ratchet सँग जोडिएको छैन — यो metric +`quality-baseline.json` मा छ तर `ci.yml` मा कुनै अवरोधक र्याचेटसँग जोडिएको छैन — यो मेट्रिक हाल अनाथ छ। -### मर्ज / डिडुप्लिकेसन (यान्त्रिक, कम जोखिम) +### मर्ज / दोहोरोपन हटाउने (यान्त्रिक, कम जोखिम) -प्रत्येक उम्मेदवारलाई 2026-06-17 को प्रत्यक्ष gate अवस्थाविरुद्ध मान्य गरिएको थियो (विश्वास गर तर प्रमाणीकरण पनि गर); -धेरै "स्पष्ट" मर्जहरूले वास्तवमा प्राविधिक ऋण लुकाएका रहेछन् र ती **सिधै प्रतिस्थापन गर्न मिल्ने** छैनन्। +प्रत्येक उम्मेदवारलाई 2026-06-17 को प्रत्यक्ष गेट अवस्थासँग प्रमाणीकरण गरिएको थियो +(विश्वास गर, तर पुष्टि पनि गर); धेरै "स्पष्ट" देखिने मर्जहरूले ऋण लुकाएका रहेछन् र ती +**सफा रूपमा सीधै प्रतिस्थापन गर्न मिल्ने** छैनन्। -- **`check:docs-sync` दुई पटक चल्छ** — `lint` job मा छुट्टै र फेरि `check:docs-all` (`docs-sync-strict`) भित्र तथा husky pre-commit hook मा। ✅ **सम्पन्न** — छुट्टै `lint` invocation हटाइयो। -- **CVE scanning** — ❌ **सिधै मर्ज गर्न मिल्दैन।** `audit:deps` कुनै पनि उच्च/गम्भीर CVE मा कडाइका साथ विफल हुन्छ; `check:vuln-ratchet` (osv) baseline को तुलनामा _प्रतिगमन_ हुँदा मात्र विफल हुन्छ (हाल 1 MODERATE)। अर्थगत व्यवहार फरक छन् — `audit:deps` हटाउँदा निरपेक्ष उच्च/गम्भीर gate हराउँछ। दुवै राख्नुहोस्। -- **Cycle detection** — ❌ **सिधै मर्ज गर्न मिल्दैन।** `check:circular-deps` (dpdm) ले **91 cycles** रिपोर्ट गर्छ (त्यसैले यो परामर्शात्मक छ); पहिले तिनलाई समाधान नगरी यसलाई अवरोधकारी बनाउन सकिँदैन, र यसको दायरा हरियो, चयनित `check:cycles` भन्दा फराकिलो छ। `check:cycles` लाई अवरोधकारी नै राख्नुहोस्; 91 वटा dpdm cycle समाधान गर्नु यसको आफ्नै backlog हो। -- **Complexity** — ✅ **सम्पन्न** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): एउटा ESLint walk, ruleId अनुसार गणना गरिएकाले cyclomatic+max-lines र cognitive baseline हरू स्वतन्त्र रहन्छन्; स्थानीय `--update` का लागि छुट्टाछुट्टै `check:complexity` / `check:cognitive-complexity` कायम छन्। -- **`/api` anti-hallucination** — ✅ **सम्पन्न** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` को एउटै FS inventory, openapi-routes + docs-symbols ले अझै स्वतन्त्र रूपमा रिपोर्ट गर्छन्; स्थानीय run का लागि छुट्टाछुट्टै जाँचहरू कायम छन्। -- **`check:node-runtime` 11 वटा job मा चल्छ** — ⚠️ **न्यून ROI।** प्रत्येक छुट्टै runner हो र जाँच <1s लाग्छ; सस्तो per-job guard गुमाउने तुलनामा कुल बचत ~10s मात्र हुन्छ। परिवर्तनको झन्झट लायक छैन। -- **CI lint मा `typecheck:noimplicit:core`** — ✅ **lint job बाट हटाइयो** (परामर्शात्मक `continue-on-error` थियो); अवरोधकारी type surface `typecheck:core` + `check:type-coverage` हो। स्थानीय script कायम राखियो। +- **`check:docs-sync` दुई पटक चल्छ** — `lint` जबमा छुट्टै र फेरि `check:docs-all` (`docs-sync-strict`) तथा husky pre-commit हुकभित्र। ✅ **सम्पन्न** — छुट्टै `lint` आह्वान हटाइयो। +- **CVE स्क्यानिङ** — ❌ **सफा मर्ज होइन।** `audit:deps` ले कुनै पनि उच्च/गम्भीर CVE मा कडा रूपमा असफल गराउँछ; `check:vuln-ratchet` (osv) भने आधाररेखाको तुलनामा _पश्चगमन_ भएमा मात्र असफल हुन्छ (हाल 1 MODERATE)। अर्थविज्ञान फरक छन् — `audit:deps` हटाउँदा निरपेक्ष उच्च/गम्भीर गेट हराउनेछ। दुवै राख्नुहोस्। +- **चक्र पत्ता लगाउने** — ✅ **सम्पन्न** (#15159 G-01/G-02)। यहाँको पुरानो पाठले `check:cycles` लाई "हरियो, छनोट गरिएको" गेट भनेको थियो र `check:circular-deps` (dpdm) ले 91 चक्र रिपोर्ट गरेको कारण यसलाई अवरोधक राख्नुपर्ने औचित्य दिएको थियो। त्यो हरियो अवस्था **झूटो हरियो** थियो: `check:cycles` ले 5 उपनिर्देशिका (450 फाइल) स्क्यान गर्थ्यो, स्थिर `import|export … from` सँग मात्र मिलान गर्थ्यो, र प्रत्येक `@/` तथा `@omniroute/open-sse/` स्पेसिफायर हटाउँथ्यो, त्यसैले यसले रिपोजिटरीमा हाबी भएका dynamic-import + alias चक्रहरू देख्न सक्दैनथ्यो। सुधार गरियो: गेटले अब `src` + `open-sse` (5023 फाइल) मा खोज्छ, TypeScript AST बाट स्पेसिफायरहरू सङ्कलन गर्छ (त्यसैले `import("…")` गणना हुन्छ र type-position `typeof import("…")` हुँदैन), र tsconfig `paths` समाधान गर्छ। यसले 0 होइन, **14** चक्र फेला पार्छ। पहिलेदेखि रहेका 14 चक्रलाई गेट PR मा सुधार्न नसकिने भएकाले, `check:cycles` अब **र्याचेट** हो (`--ratchet`, `quality-baseline.json` मा सीमा `metrics.cycles.value = 14`, `direction: down`) — यसले कुनै पनि _पश्चगमन_ रोक्छ र सङ्ख्या घट्न मात्र सक्छ। CI ले `npm run check:cycles:ratchet` चलाउँछ। क्रमिक न्यूनीकरण **A-01** सँगै अघि बढ्छ। `check:circular-deps` (dpdm) व्यापक दोस्रो रायका रूपमा परामर्शात्मक नै रहन्छ। +- **जटिलता** — ✅ **सम्पन्न** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): एउटै ESLint भ्रमण, ruleId अनुसार गणना गरिन्छ ताकि cyclomatic+max-lines र cognitive आधाररेखाहरू स्वतन्त्र रहून्; छुट्टाछुट्टै `check:complexity` / `check:cognitive-complexity` स्थानीय `--update` का लागि कायम छन्। +- **`/api` भ्रम-निरोध** — ✅ **सम्पन्न** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` को एउटै FS सूचीकरण, openapi-routes + docs-symbols ले अझै पनि स्वतन्त्र रूपमा रिपोर्ट गर्छन्; छुट्टाछुट्टै जाँचहरू स्थानीय रनका लागि कायम छन्। +- **`check:node-runtime` 11 जबमा चल्छ** — ⚠️ **न्यून ROI।** प्रत्येक छुट्टै रनर हो र जाँच <1s को छ; सस्तो प्रति-जब सुरक्षा गुमाउनुको सट्टा कुल बचत ~10s मात्र हुन्छ। यस्तो फेरबदल गर्न लायक छैन। +- **CI lint मा `typecheck:noimplicit:core`** — ✅ **lint जबबाट हटाइयो** (परामर्शात्मक `continue-on-error` थियो); अवरोधक type surface `typecheck:core` + `check:type-coverage` हो। स्थानीय स्क्रिप्ट कायम राखिएको छ। ### फ्लिप / निर्णय (अपरेटर नीति) -- `check:openapi-security-tiers` (परामर्शात्मक) — ❌ **सफा रूपमा फ्लिप गर्न मिल्दैन।** यो 0 सहित बाहिरिन्छ तर `LOCAL_ONLY_API_PREFIXES` अन्तर्गतका धेरै `traffic-inspector` route मा `x-loopback-only: true` annotation नभएको चेतावनी दिन्छ। यसलाई लागू गर्न पहिले `openapi.yaml` मा ती annotation थप्नुपर्छ। -- `typecheck:noimplicit:core` (परामर्शात्मक) — अवरोधकारी `check:type-coverage` ratchet ले यसलाई धेरै हदसम्म समेट्छ। यसलाई ratchet मा फ्लिप गर्नुहोस् वा दोहोरिएको दोस्रो `tsc` pass हटाउनुहोस्। -- `test:vitest:ui` (अहिले **अवरोधकारी**) — पहिलेबाट रहेका विफलताहरू `vitest.config.ts` मा `// #8618` tracking comment सहित स्पष्ट रूपमा बहिष्कृत गरिएका छन्; नयाँ विफलताले job विफल बनाउँछ। -- `check:secrets` (gitleaks, कागजातबद्ध 3 वटा false-positive मा स्थिर गरिएको अवरोधकारी ratchet) — 0 मा पुग्न ती 3 वटालाई allowlist गर्नुहोस्, वा परामर्शात्मकमा घटाउनुहोस्। यो GitHub को native secret-scanning + `check:public-creds` सँग दोहोरिन्छ। -- `check:pr-evidence` (अवरोधकारी, PR-body को गद्यमा grep गर्छ) — false-positive को उच्च जोखिम; हटाउँदा कडा नियम #18 को कार्यान्वयन कमजोर हुन्छ, त्यसैले यो वास्तविक नीतिगत निर्णय हो। -- `semgrep` (परामर्शात्मक standalone) — OWASP family हरूका लागि CodeQL सँग दोहोरिन्छ; यसको baseline लाई ratchet सँग जोड्नुहोस् वा हटाउनुहोस्। +- `check:openapi-security-tiers` (परामर्शात्मक) — ❌ **सफा रूपमा फ्लिप गर्न मिल्दैन।** यो 0 सहित बाहिरिन्छ तर `LOCAL_ONLY_API_PREFIXES` अन्तर्गतका धेरै `traffic-inspector` रुटहरूमा `x-loopback-only: true` एनोटेसन नभएको चेतावनी दिन्छ। यसलाई लागू गराउन पहिले `openapi.yaml` मा ती एनोटेसनहरू थप्नुपर्छ। +- `typecheck:noimplicit:core` (परामर्शात्मक) — अवरोधक `check:type-coverage` र्याचेटले यसको अधिकांश भाग समेट्छ। यसलाई र्याचेटमा फ्लिप गर्नुहोस् वा अनावश्यक दोस्रो `tsc` पास हटाउनुहोस्। +- `test:vitest:ui` (अहिले **अवरोधक**) — पहिलेदेखिका असफलताहरूलाई `vitest.config.ts` मा `// #8618` ट्र्याकिङ टिप्पणीहरूसहित स्पष्ट रूपमा बहिष्कार गरिएको छ; नयाँ असफलताले जब असफल गराउँछन्। +- `check:secrets` (gitleaks, दस्तावेजीकृत 3 false-positive मा स्थिर गरिएको अवरोधक र्याचेट) — 0 मा पुग्न ती 3 वटालाई allowlist गर्नुहोस्, वा परामर्शात्मकमा घटुवा गर्नुहोस्। GitHub को नेटिभ secret-scanning + `check:public-creds` सँग दोहोरिन्छ। +- `check:pr-evidence` (अवरोधक, PR-body गद्यमा grep गर्छ) — false-positive को उच्च जोखिम; हटाइएमा Hard Rule #18 को कार्यान्वयन कमजोर हुन्छ, त्यसैले यो वास्तविक नीतिगत निर्णय हो। +- `semgrep` (परामर्शात्मक छुट्टै वर्कफ्लो) — OWASP परिवारहरूका लागि CodeQL सँग दोहोरिन्छ; यसको आधाररेखालाई र्याचेटसँग जोड्नुहोस् वा हटाउनुहोस्। --- diff --git a/docs/i18n/ne/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ne/docs/compression/COMPRESSION_GUIDE.md index b2d765d4f581..4db6588dbd21 100644 --- a/docs/i18n/ne/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ne/docs/compression/COMPRESSION_GUIDE.md @@ -466,8 +466,7 @@ input लाई कम्प्रेस गर्नुको सट्टा, `messages` भएको बडीमा, सामग्री बाइपास (`shouldBypassCavemanOutputMode()` in `open-sse/services/compression/outputMode.ts`) ले अन्तिम तीन सन्देशहरू जाँच गर्छ र तिनले सुरक्षा, अपरिवर्तनीय-कार्य, स्पष्टीकरण वा क्रम-संवेदनशील कुञ्जीशब्दहरू मिलाएमा -पूरै टर्नका लागि शैलीहरू छोड्छ। बाइपास ड्यासबोर्डको -**Auto-Clarity Bypass** टगल (`cavemanOutputMode.autoClarity`) मा सेट गरिएको मानअनुसार चल्छ। +पूरै टर्नका लागि शैलीहरू छोड्छ। बाइपास ड्यासबोर्डको **Auto-Clarity Bypass** टगल (`cavemanOutputMode.autoClarity`) खुला हुन्जेल चल्छ, जुन पूर्वनिर्धारित हो; टगल बन्द हुँदा छानिएका शैलीहरू ती टर्नमा पनि लागू हुन्छन्। बाइपासले टर्नलाई अगाडि बढ्न दिँदा, `placeSystemInstruction()` (उही फाइल), जसले कहिल्यै नयाँ `messages[0]` सिर्जना गर्दैन, ब्लकलाई यीमध्ये पहिलो फेला परेको स्थानमा राख्छ: diff --git a/docs/i18n/ne/docs/reference/ENVIRONMENT.md b/docs/i18n/ne/docs/reference/ENVIRONMENT.md index b28f59d96c31..c112303cc522 100644 --- a/docs/i18n/ne/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ne/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. आवश्यक गोप्य मानहरू -यी मानहरू पहिलोपटक चलाउनुअघि **अनिवार्य रूपमा** सेट गरिनुपर्छ। यिनको अभावमा, अनुप्रयोगले सुरु हुन अस्वीकार गर्नेछ वा असुरक्षित पूर्वनिर्धारित मानहरूसहित सञ्चालन हुनेछ। - -| चर | आवश्यक | पूर्वनिर्धारित | स्रोत फाइल | विवरण | -| ---------------------------- | ------------------ | --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **हो** | _(कुनै छैन)_ | `src/lib/auth` | सबै ड्यासबोर्ड सत्र कुकीहरू (JWT) मा हस्ताक्षर गर्छ/प्रमाणित गर्छ। `openssl rand -base64 48` प्रयोग गरेर सिर्जना गर्नुहोस्। | -| `API_KEY_SECRET` | **हो** | _(कुनै छैन)_ | `src/lib/db/apiKeys.ts` | SQLite मा भण्डारण गरिएका API कुञ्जी मानहरूका लागि AES इन्क्रिप्सन कुञ्जी। `openssl rand -hex 32` प्रयोग गरेर सिर्जना गर्नुहोस्। | -| `INITIAL_PASSWORD` | **हो** | `CHANGEME` | बुटस्ट्र्याप स्क्रिप्ट | प्रारम्भिक प्रशासकीय ड्यासबोर्ड पासवर्ड सेट गर्छ (`.env.example` को पूर्वनिर्धारित मानसँग मेल खान्छ — परिवर्तन गर्न बाध्य पार्न जानाजानी असुरक्षित राखिएको हो)। **पहिलो प्रयोगअघि परिवर्तन गर्नुहोस्।** लगइन गरेपछि Dashboard → Settings → Security मार्फत परिवर्तन गर्नुहोस्। | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **हो** (उत्पादनमा) | _(सेट नगरिएको)_ | `src/app/api/internal/codex-responses-ws/route.ts` | आन्तरिक Codex Responses WebSocket ब्रिजका लागि साझा गोप्य मान। Electron/browser WS रिले र OmniRoute बीचका ब्रिज अनुरोधहरू प्रमाणित गर्छ। ⚠️ **उत्पादनमा आवश्यक — यो सेट नगरिएको अवस्थामा सबै WS ब्रिज अनुरोधहरू अस्वीकार गरिन्छन्।** `openssl rand -base64 32` प्रयोग गरेर सिर्जना गर्नुहोस्। | -| `OMNIROUTE_SW_BUILD_ID` | होइन | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA शेल (#11779) का लागि स्पष्ट service-worker क्यास-बस्टिङ id; रिजोल्युसन शृङ्खलामा पहिलो। | -| `SOURCE_VERSION` | होइन | _(सेट नगरिएको)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | शृङ्खलामा दोस्रो — PaaS बिल्डरहरू (जस्तै Heroku-शैली) द्वारा डिप्लोय गरिएको कमिटको रूपमा सेट गरिन्छ। | -| `NEXT_PUBLIC_SW_BUILD_ID` | होइन | _(व्युत्पन्न)_ | `src/shared/components/PwaRegister.tsx` | क्लाइन्टले `/sw.js?v=…` दर्ता गर्न प्रयोग गर्ने बिल्ड-समयको सार्वजनिक मान; माथिका दुई मानबाट र त्यसपछि git SHA बाट व्युत्पन्न गरिन्छ। | -| `OMNIROUTE_PEER_STAMP_TOKEN` | होइन (स्वचालित) | _(प्रत्येक बुटमा स्वचालित)_ | `src/server/authz/policies/management.ts` | विश्वसनीय peer-IP स्ट्याम्प OmniRoute कै HTTP सर्भर (`scripts/dev/peer-stamp.mjs`) बाट आएको हो भनी प्रमाणित गर्ने प्रत्येक प्रक्रियाको गोप्य मान। स्ट्याम्पमा यो टोकन भएको अवस्थामा मात्र authz मिडलवेयरले अनुरोधको स्थानीयतालाई (LOCAL_ONLY रुटहरूको loopback/LAN गेटिङ) विश्वास गर्छ। प्रत्येक बुटमा स्वचालित रूपमा सिर्जना हुन्छ — सेट नगरी छोड्नुहोस्; स्ट्याम्प साझा गर्नुपर्ने बहु-प्रक्रिया सेटअपहरूका लागि मात्र यसलाई निश्चित गर्नुहोस्। | -| `OMNIROUTE_TRUSTED_PROXIES` | होइन | _(सेट नगरिएको)_ | `scripts/dev/peer-stamp.mjs` | रिभर्स प्रोक्सीहरूका अल्पविरामले छुट्याइएका IP वा CIDR दायराहरू, जसका `X-Forwarded-For` / `X-Real-IP` लाई IP अनुमति/अस्वीकृति सूचीले विश्वास गर्न सक्छ; loopback, निजी-नेटवर्क ठेगानाहरू र Cloudflare edge हरू सधैँ विश्वसनीय हुन्छन्। अन्य कुनै सार्वजनिक ठेगानामा रहेको प्रोक्सीका लागि मात्र आवश्यक हुन्छ; अन्यथा फिल्टरले उक्त प्रोक्सीको आफ्नै ठेगानाको मूल्याङ्कन गर्छ। | +**हो** भनी चिनो लगाइएका तर सेट नगरिएका गोप्य मानहरू पहिलो पटक चलाउँदा स्वचालित रूपमा सिर्जना गरिन्छन् (`JWT_SECRET`, `API_KEY_SECRET`) र डेटा डाइरेक्टरीमा रहेको `server.env` मा भण्डारण गरिन्छ — त्यो डाइरेक्टरी अस्थायी भएमा यिनलाई स्पष्ट रूपमा सेट गर्नुहोस्। यसको अपवाद `OMNIROUTE_WS_BRIDGE_SECRET` हो: यो `server.env` मा भण्डारण गरिँदैन — सर्भर एन्ट्रिपोइन्टहरूले हरेक बुटमा प्रक्रियाभित्रको ब्रिजले मात्र प्रयोग गर्न सक्ने अस्थायी मान सिर्जना गर्छन्, त्यसैले बाह्य ब्रिज क्लाइन्टहरूले यसलाई स्पष्ट रूपमा सेट गर्नुपर्छ। + +| चर | आवश्यक | पूर्वनिर्धारित | स्रोत फाइल | विवरण | +| ---------------------------- | ------------------ | --------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **हो** | _(कुनै छैन)_ | `src/lib/auth` | सबै ड्यासबोर्ड सत्र कुकीहरू (JWT) मा हस्ताक्षर गर्छ/प्रमाणित गर्छ। `openssl rand -base64 48` प्रयोग गरेर सिर्जना गर्नुहोस्। | +| `API_KEY_SECRET` | **हो** | _(कुनै छैन)_ | `src/lib/db/apiKeys.ts` | SQLite मा भण्डारण गरिएका API कुञ्जी मानहरूका लागि AES इन्क्रिप्सन कुञ्जी। `openssl rand -hex 32` प्रयोग गरेर सिर्जना गर्नुहोस्। | +| `INITIAL_PASSWORD` | होइन | _(सेट नगरिएको)_ | `src/lib/auth/managementPassword.ts` | कुनै पासवर्ड भण्डारण नभएसम्म सुरुवातको बेला ड्यासबोर्ड पासवर्ड सेट गर्छ; पासवर्ड भण्डारण भएपछि यसलाई उपेक्षा गरिन्छ। npm र स्रोत स्थापनाहरूले `.env.example` लाई `.env` मा प्रतिलिपि गर्छन्, त्यसैले तिनीहरू यसको सार्वजनिक रूपमा ज्ञात प्लेसहोल्डर `CHANGEME` सँगै सुरु हुन्छन्: पहिलो बुटअघि यसलाई प्रतिस्थापन गर्नुहोस्, वा पहिलो भ्रमणमा अनबोर्डिङ विजार्डमा पासवर्ड सिर्जना गर्न त्यही `.env` मा `INITIAL_PASSWORD=` (खाली) सेट गर्नुहोस् — जसरी सेट नगरिएको मान (Docker इमेजको पूर्वनिर्धारित मान) ले गर्छ। भण्डारित पासवर्ड Dashboard → Settings → Security मा परिवर्तन गर्नुहोस्, वा सर्भरको डेटा डाइरेक्टरीमा `DATA_DIR` सेट गरेर `omniroute reset-password` (स्रोत चेकआउटमा `node bin/reset-password.mjs`) चलाउनुहोस्। | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **हो** (उत्पादनमा) | _(सेट नगरिएको)_ | `src/app/api/internal/codex-responses-ws/route.ts` | आन्तरिक Codex Responses WebSocket ब्रिजका लागि साझा गोप्य मान। Electron/browser WS रिले र OmniRoute बीचका ब्रिज अनुरोधहरू प्रमाणित गर्छ। ⚠️ **उत्पादनमा आवश्यक — यो सेट नगरिएको अवस्थामा सबै WS ब्रिज अनुरोधहरू अस्वीकार गरिन्छन्।** `openssl rand -base64 32` प्रयोग गरेर सिर्जना गर्नुहोस्। | +| `OMNIROUTE_SW_BUILD_ID` | होइन | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA शेल (#11779) का लागि स्पष्ट service-worker क्यास-बस्टिङ id; रिजोल्युसन शृङ्खलामा पहिलो। | +| `SOURCE_VERSION` | होइन | _(सेट नगरिएको)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | शृङ्खलामा दोस्रो — PaaS बिल्डरहरू (जस्तै Heroku-शैली) द्वारा डिप्लोय गरिएको कमिटको रूपमा सेट गरिन्छ। | +| `NEXT_PUBLIC_SW_BUILD_ID` | होइन | _(व्युत्पन्न)_ | `src/shared/components/PwaRegister.tsx` | क्लाइन्टले `/sw.js?v=…` दर्ता गर्न प्रयोग गर्ने बिल्ड-समयको सार्वजनिक मान; माथिका दुई मानबाट र त्यसपछि git SHA बाट व्युत्पन्न गरिन्छ। | +| `OMNIROUTE_PEER_STAMP_TOKEN` | होइन (स्वचालित) | _(प्रत्येक बुटमा स्वचालित)_ | `src/server/authz/policies/management.ts` | विश्वसनीय peer-IP स्ट्याम्प OmniRoute कै HTTP सर्भर (`scripts/dev/peer-stamp.mjs`) बाट आएको हो भनी प्रमाणित गर्ने प्रत्येक प्रक्रियाको गोप्य मान। स्ट्याम्पमा यो टोकन भएको अवस्थामा मात्र authz मिडलवेयरले अनुरोधको स्थानीयतालाई (LOCAL_ONLY रुटहरूको loopback/LAN गेटिङ) विश्वास गर्छ। प्रत्येक बुटमा स्वचालित रूपमा सिर्जना हुन्छ — सेट नगरी छोड्नुहोस्; स्ट्याम्प साझा गर्नुपर्ने बहु-प्रक्रिया सेटअपहरूका लागि मात्र यसलाई निश्चित गर्नुहोस्। | +| `OMNIROUTE_TRUSTED_PROXIES` | होइन | _(सेट नगरिएको)_ | `scripts/dev/peer-stamp.mjs` | रिभर्स प्रोक्सीहरूका अल्पविरामले छुट्याइएका IP वा CIDR दायराहरू, जसका `X-Forwarded-For` / `X-Real-IP` लाई IP अनुमति/अस्वीकृति सूचीले विश्वास गर्न सक्छ; loopback, निजी-नेटवर्क ठेगानाहरू र Cloudflare edge हरू सधैँ विश्वसनीय हुन्छन्। अन्य कुनै सार्वजनिक ठेगानामा रहेको प्रोक्सीका लागि मात्र आवश्यक हुन्छ; अन्यथा फिल्टरले उक्त प्रोक्सीको आफ्नै ठेगानाको मूल्याङ्कन गर्छ। | ### सिर्जना गर्ने कमान्डहरू @@ -215,38 +215,38 @@ OmniRoute ले सबै स्थायी भण्डारणका ला ## 4. सुरक्षा र प्रमाणीकरण -| भेरिएबल | पूर्वनिर्धारित | स्रोत फाइल | विवरण | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | मेसिन फिङ्गरप्रिन्टिङका लागि हार्डवेयर पहिचानकर्ताहरूसँग संयोजन गरिने salt। पृथकीकरणका लागि प्रत्येक डिप्लोयमेन्टमा परिवर्तन गर्नुहोस्। | -| `OMNIROUTE_CLI_SALT` | _(सेट नगरिएको = प्रत्येक इन्स्टलेसनका लागि अनियमित salt, `/cli-token-salt.json` मा सुरक्षित)_ | `src/lib/machineToken.ts` | स्थानीय CLI प्रमाणीकरण टोकन निकाल्न प्रयोग गरिने HMAC salt। यो मान सेट गर्दा मेसिनका सबै CLI टोकनहरू रोटेट हुन्छन् र यसले सुरक्षित गरिएको salt भन्दा सधैँ प्राथमिकता पाउँछ। `docs/security/CLI_TOKEN.md` हेर्नुहोस्। | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | सत्र कुकीहरूमा `Secure` फ्ल्याग सेट गर्छ। HTTPS पछाडि चलाउँदा यो **`true` हुनैपर्छ**। | -| `REQUIRE_API_KEY` | `false` | API मिडलवेयर | `true` हुँदा, सबै `/v1/*` प्रोक्सी अनुरोधहरूमा मान्य API कुञ्जी समावेश हुनुपर्छ। **यो फ्ल्यागले `GET /v1/models` लाई नियन्त्रण गर्दैन**, बरु यसले ड्यासबोर्ड लगइन अवस्था (`requireAuthForModels`) पालना गर्छ — त्यसैले `/v1/models` बाट आएको `401` ले इन्फरेन्स सुरक्षित छ भन्ने अर्थ दिँदैन। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) हेर्नुहोस्। | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ड्यासबोर्ड UI मा API कुञ्जीका पूर्ण मानहरू देखाउन अनुमति दिन्छ। ड्यासबोर्डका फिचर फ्ल्यागहरूबाट कन्फिगर गर्न सकिन्छ; साझा इन्स्ट्यान्सहरूमा सुरक्षा जोखिम हुन्छ। | -| `NO_LOG_API_KEY_IDS` | _(खाली)_ | `src/lib/compliance/index.ts` | अनुरोध लगिङलाई बाइपास गर्ने अल्पविरामद्वारा छुट्याइएका API कुञ्जी ID हरू (GDPR अनुपालन)। | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(सेट नगरिएको = असीमित)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` स्तम्भ null भएका API कुञ्जीहरूमा लागू हुने प्रतिदिनको वैकल्पिक अनुरोध बजेट। सेट नगरिएको वा खाली भएमा: कुनै अन्तर्निहित सीमा हुँदैन (#2289, #11017)। `0` पनि उही हो (असीमित)। धनात्मक पूर्णाङ्क N ले N/दिन, 5N/हप्ता, 20N/महिना सक्षम गर्छ। त्रुटिपूर्ण गैर-खाली मानहरू पुरानो 1000/दिन, 5000/हप्ता, 20000/महिना सीमामा फर्कन्छन्। | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | अनुमति दिइएको अनुरोध बडीको अधिकतम आकार। यो सीमा नाघ्ने पेलोडहरू अस्वीकार गर्छ। | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | यस थ्रेसहोल्ड बराबर वा माथिका वास्तविक अनुरोध बडीहरूले JSON पार्स गर्नुअघि एटोमिक प्रक्रिया-स्थानीय हेभीवेट एडमिसन लिज लिन्छन् (BYTE पथ, `POST /v1/responses` सहित)। संरचना-भारी अवस्थाको जस्तै [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) स्वस्थ-हेडरुम एस्केप; अझै पनि `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) द्वारा सीमित छ, जसले गर्दा [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) पुनः देखा पर्दैन। | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | सीमित इन्जेसनका क्रममा पढिएका बाइटहरूका आधारमा लागू गरिने च्याट-रुटको कठोर सीमा, जसमा `Content-Length` नभएका, अमान्य भएका वा गलत रूपमा प्रस्तुत गरिएका अनुरोधहरू पनि समावेश हुन्छन्; सीमा नाघेमा `413` प्राप्त हुन्छ। | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(सेट नगरिएको — अनुरोध-सङ्ख्या सीमा छैन)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** यो पुरानो अनुरोध-सङ्ख्या सीमा अब स्पष्ट रूपमा सेट गरिएको अवस्थामा मात्र लागू हुन्छ। सेट नगरी छोड्दा (पूर्वनिर्धारित), भारी च्याट एडमिसनलाई यसको सट्टा `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ले नियन्त्रण गर्छ — **एउटै प्रक्रिया** (एउटै V8 heap) मा प्रक्रियाको वास्तविक मेमोरी सीमाबाट आकार निर्धारण गरिएको स्वतः-व्युत्पन्न बाइट बजेट। करिब 750k-token का दुईवटा एकअर्कासँग खप्टिएका `/v1/responses` ले करिब 12 GiB heap हरू अबोर्ट गर्छन् (#7849) — यो **मेमोरी-बजेट** चेतावनी हो, 2 को कठोर उत्पादन अधिकतम होइन। स्वस्थ प्रक्रिया (shed ratio भन्दा तलको heap) ले `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` मार्फत थप समवर्ती लामो `/v1/responses` लाई एडमिट गर्न सक्छ। दर्जनौँ लामो SSE क्लाइन्टहरू (40–50) heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 सँग सम्बन्धित छन्, “अधिकतम 2” होइन। “होस्ट प्रयोग गर्न” यसलाई अन्धाधुन्ध बढाउँदा #7849 पुनः देखा पर्छ। **N स्वतन्त्र `DATA_DIR` हरू** (#11024) सँग **heap हरू** गुणन गर्नुहोस्; एउटै SQLite फाइलमा कहिल्यै `replicas>1` नराख्नुहोस्। | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(स्वतः-व्युत्पन्न)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** स्वतः-व्युत्पन्न इन्जेस्ट बाइट बजेटका लागि ओभरराइड (अझ कडा V8/cgroup मेमोरी सीमाको 25% लाई 8x अस्थायी एम्प्लिफिकेसनले भाग गरिएको)। व्युत्पन्न र स्पष्ट दुवै मानहरू 8 MiB–2 GiB मा क्ल्याम्प हुन्छन्। प्रभावकारी बजेटभन्दा ठूलो body तुरुन्तै `413 body_exceeds_budget` सहित असफल हुन्छ; व्यक्तिगत रूपमा सेवा दिन सकिने body हरूबीचको कन्टेन्सन भने पुनः प्रयास गर्न मिल्ने `503` नै रहन्छ। 40–50 समवर्ती लामो SSE क्लाइन्टहरू यो बजेट + heap सँग सम्बन्धित छन्, कठोर “अधिकतम 2” होइन। ट्युन गर्नुअघि `/api/monitoring/health` मा `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` पढ्नुहोस्। | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | बाइट र स्ट्रक्चरका भारी एडमिसनका लागि heap-pressure shed ratio (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` नाघेको समवर्ती भारी अनुरोधलाई heap पनि यो ratio मा वा त्यसभन्दा माथि हुँदा मात्र पुनः प्रयास गर्न मिल्ने `503` सहित shed गरिन्छ; स्वस्थ heap मा यसलाई healthy-headroom मार्फत एडमिट गरिन्छ। | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (पूर्वनिर्धारित `1`) | `src/shared/middleware/chatBodyAdmission.ts` | स्ट्रक्चर र बाइट दुवैमा (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` भन्दा ठूला वा बराबर body हरूसहित) स्वस्थ-heap फास्ट पाथ (#10437) का लागि सीमित अतिरिक्त क्षमता। यो सीमा नभएमा, प्रत्येक व्यस्त-तर-स्वस्थ-heap अनुरोधले कुनै उच्चतम सीमाविनै एडमिसन बाइपास गर्थ्यो। healthy-heap पाथमार्फत यति धेरै समवर्ती lease हरू सक्रिय भएपछि, थप व्यस्त अनुरोधहरू वास्तविक heap pressure मा प्रयोग हुने उही सीमित-प्रतीक्षा/shed पाथमा पुग्छन्। `0` ले बाइपास पूर्ण रूपमा निष्क्रिय गर्छ। | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | body बाइट थ्रेसहोल्डभन्दा तल हुँदा पनि च्याट अनुरोधलाई भारी भनेर वर्गीकृत गर्ने सन्देश सङ्ख्या। | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | body बाइट थ्रेसहोल्डभन्दा तल हुँदा पनि च्याट अनुरोधलाई भारी भनेर वर्गीकृत गर्ने tool सङ्ख्या। | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | अनुरोधलाई भारी भनेर वर्गीकृत गर्ने कन्जरभेटिभ string-size token अनुमान; यो एडमिसन लागतको प्रोक्सी हो, प्रदायकको बिलिङ tokenization होइन। | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (निष्क्रिय) | `src/shared/middleware/chatBodyAdmission.ts` | वैकल्पिक रूपमा अप्ट-इन गर्न सकिने च्याट इतिहास सीमा। पूर्वनिर्धारित रूपमा निष्क्रिय: सन्देश सङ्ख्या डिप्लोयमेन्ट नीति हो, अनुरोधको सार्वभौमिक गुण होइन, र यहाँ सीमा लगाउँदा compression pipeline ले तिनलाई सेवा दिन सक्ने बनाउनुअघि नै कुराकानीहरूलाई अन्तिम `413` सहित अस्वीकार गर्छ। Heap वृद्धि `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` र heap-pressure shed द्वारा सीमित हुन्छ। कठोर उच्चतम सीमा आवश्यक पर्ने मेमोरी-सीमित डिप्लोयमेन्टहरूमा धनात्मक मान सेट गर्नुहोस्; त्यसपछि सीमा नाघेमा संरचित compact-required `413` प्राप्त हुन्छ। | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | मेमोरीमा पूर्ण रूपमा बफर गरिने non-streaming upstream response का लागि कठोर सीमा। यो सीमा नाघेपछि upstream reader रद्द गरिन्छ र heap समाप्त नहुन्जेल असीमित string बढाइरहनुको सट्टा अनुरोध तुरुन्तै असफल हुन्छ। | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | अपस्ट्रिम प्रतिक्रिया हेडरहरूबाट फर्वार्ड गरिने अधिकतम वायर बाइटहरू। बजेट नाघेमा, सामान्य रिभर्स-प्रोक्सी हेडर सीमाभित्र रहन कम प्राथमिकताका हेडरहरू (जस्तै, अनुकूलित `x-codex-*`, `x-oai-request-id`) हटाइन्छन्। ठूलो प्रतिक्रिया हेडर आकारको मूल्यमा थप अपस्ट्रिम मेटाडेटा फर्वार्ड गर्न यसलाई उच्च मानमा सेट गर्नुहोस्। | -| `CORS_ORIGIN` | _(सेट नगरिएको)_ | `src/server/cors/origins.ts` | लिगेसी एकल-ओरिजिन CORS अनुमति सूची। नयाँ डिप्लोयमेन्टहरूका लागि `CORS_ALLOWED_ORIGINS` लाई प्राथमिकता दिनुहोस्। CORS क्रस-ओरिजिन ब्राउजर API क्लाइन्टहरूका लागि मात्र हो; प्रमाणीकरण गरिएका ड्यासबोर्ड लेखनहरूले यसको सट्टा समान-ओरिजिन अनुरोधहरूका साथै सत्रसँग बाँधिएको CSRF सुरक्षा प्रयोग गर्छन्। | -| `CORS_ALLOWED_ORIGINS` | _(सेट नगरिएको)_ | `src/server/cors/origins.ts` | अल्पविरामद्वारा छुट्याइएको CORS अनुमति सूची। `CORS_ALLOW_ALL=true` स्पष्ट रूपमा कन्फिगर नगरिएसम्म कुनै वाइल्डकार्ड पठाइँदैन। | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | कुनै पनि ब्राउजर `Origin` प्रतिध्वनित गर्न विकासका लागि मात्र प्रयोग गरिने वैकल्पिक उपाय। साझा वा उत्पादन डिप्लोयमेन्टहरूमा सक्षम नगर्नुहोस्। | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | निजी/लुपब्याक/लिङ्क-लोकल IP दायराहरूलाई लक्षित गर्ने प्रदायक कलहरू रोक्नुहोस्। पृथक परीक्षण वातावरणहरूमा मात्र असक्षम गर्नुहोस्। | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | निजी/स्थानीय नेटवर्कहरू (localhost, 192.168.x.x, 10.x.x.x, आदि) तर्फ सङ्केत गर्ने प्रदायक URL हरूलाई अनुमति दिनुहोस्। **आफैं होस्ट गरिएका प्रदायकहरूका लागि आवश्यक** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)। `false` हुँदा, ड्यासबोर्डले स्थानीय URL हरूको प्रमाणीकरण अस्वीकार गर्छ। | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | स्थानीय/निजी ठेगानाहरूमा (127.0.0.1, localhost, LAN, निजी दायराहरू) प्रदायकहरू थप्न/प्रमाणीकरण गर्न अनुमति दिनुहोस् — प्रदायक प्रमाणीकरण मार्गमा सीमित। **पूर्वनिर्धारित `true`** (स्थानीय-प्रथम); सार्वजनिक ठेगानाहरूलाई मात्र अनुमति दिने कडा रोक लागू गर्न `false` सेट गर्नुहोस्। क्लाउड-मेटाडेटा एन्डपोइन्टहरू (169.254.169.254, metadata.google.internal) भने जुनसुकै अवस्थामा पनि अवरुद्ध रहन्छन्। (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` रुटहरूलाई (ट्रान्सक्रिप्सन, स्पिच, अनुवाद) localhost बाहिर होस्ट गरिएको OpenAI-सङ्गत प्रदायक नोड प्रयोग गर्न दिनुहोस्। पूर्वनिर्धारित रूपमा बन्द — अडियोलाई रिमोट होस्टमा रुट गर्दा निर्गमन पहिचान परिवर्तन हुन्छ र यो अपरेटरको स्पष्ट निर्णय हुनुपर्छ। लुपब्याक/निजी नोडहरू (localhost, 127.0.0.1, 172.16-31.x) सधैं अनुमतिप्राप्त हुन्छन् र अप्रभावित रहन्छन्। (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (र मेमोरी इन्जिनको लुपब्याक रिर्याङ्क चरण) लाई localhost बाहिर होस्ट गरिएको OpenAI-सङ्गत प्रदायक नोड — TEI, Infinity, vLLM आदि चलिरहेको LAN बक्स वा Tailscale पियर — प्रयोग गर्न दिनुहोस्। पूर्वनिर्धारित रूपमा बन्द — रिमोट होस्टमा रुट गर्दा निर्गमन पहिचान परिवर्तन हुन्छ र यो अपरेटरको स्पष्ट निर्णय हुनुपर्छ। लुपब्याक नोडहरू (localhost, 127.0.0.1, 172.16-31.x) सधैं अनुमतिप्राप्त हुन्छन् र अप्रभावित रहन्छन्। रिमोट नोडहरूले प्रदायकको आउटबाउन्ड URL नीति (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) पनि पालना गर्नुपर्छ; क्लाउड-मेटाडेटा होस्टहरूमा कहिल्यै रुट गरिँदैन। | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC सक्षम हुँदा, पासवर्ड लगइन असक्षम गर्नुहोस् ताकि प्रयोगकर्ताहरूले OIDC Single Sign-On मार्फत मात्र प्रमाणीकरण गर्न सकून्। छोटो उपनाम `OIDC_DISABLE_PASSWORD_LOGIN` पनि स्वीकार गरिन्छ; उही कुञ्जी भएको ड्यासबोर्ड सुविधा फ्ल्यागले प्राथमिकता पाउँछ। (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` को छोटो उपनाम (#10889)। | +| भेरिएबल | पूर्वनिर्धारित | स्रोत फाइल | विवरण | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | मेसिन फिङ्गरप्रिन्टिङका लागि हार्डवेयर पहिचानकर्ताहरूसँग संयोजन गरिने salt। पृथकीकरणका लागि प्रत्येक डिप्लोयमेन्टमा परिवर्तन गर्नुहोस्। | +| `OMNIROUTE_CLI_SALT` | _(सेट नगरिएको = प्रत्येक इन्स्टलेसनका लागि अनियमित salt, `/cli-token-salt.json` मा सुरक्षित)_ | `src/lib/machineToken.ts` | स्थानीय CLI प्रमाणीकरण टोकन निकाल्न प्रयोग गरिने HMAC salt। यो मान सेट गर्दा मेसिनका सबै CLI टोकनहरू रोटेट हुन्छन् र यसले सुरक्षित गरिएको salt भन्दा सधैँ प्राथमिकता पाउँछ। `docs/security/CLI_TOKEN.md` हेर्नुहोस्। | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | सत्र कुकीहरूमा `Secure` फ्ल्याग सेट गर्छ। HTTPS पछाडि चलाउँदा यो **`true` हुनैपर्छ**। | +| `REQUIRE_API_KEY` | `false` | API मिडलवेयर | `true` हुँदा, सबै `/v1/*` प्रोक्सी अनुरोधहरूमा मान्य API कुञ्जी समावेश हुनुपर्छ। **यो फ्ल्यागले `GET /v1/models` लाई नियन्त्रण गर्दैन**, बरु यसले ड्यासबोर्ड लगइन अवस्था (`requireAuthForModels`) पालना गर्छ — त्यसैले `/v1/models` बाट आएको `401` ले इन्फरेन्स सुरक्षित छ भन्ने अर्थ दिँदैन। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) हेर्नुहोस्। | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ड्यासबोर्ड UI मा API कुञ्जीका पूर्ण मानहरू देखाउन अनुमति दिन्छ। ड्यासबोर्डका फिचर फ्ल्यागहरूबाट कन्फिगर गर्न सकिन्छ; साझा इन्स्ट्यान्सहरूमा सुरक्षा जोखिम हुन्छ। | +| `NO_LOG_API_KEY_IDS` | _(खाली)_ | `src/lib/compliance/index.ts` | अनुरोध लगिङलाई बाइपास गर्ने अल्पविरामद्वारा छुट्याइएका API कुञ्जी ID हरू (GDPR अनुपालन)। | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(सेट नगरिएको = असीमित)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` स्तम्भ null भएका API कुञ्जीहरूमा लागू हुने प्रतिदिनको वैकल्पिक अनुरोध बजेट। सेट नगरिएको वा खाली भएमा: कुनै अन्तर्निहित सीमा हुँदैन (#2289, #11017)। `0` पनि उही हो (असीमित)। धनात्मक पूर्णाङ्क N ले N/दिन, 5N/हप्ता, 20N/महिना सक्षम गर्छ। त्रुटिपूर्ण गैर-खाली मानहरू पुरानो 1000/दिन, 5000/हप्ता, 20000/महिना सीमामा फर्कन्छन्। | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | अनुमति दिइएको अनुरोध बडीको अधिकतम आकार। यो सीमा नाघ्ने पेलोडहरू अस्वीकार गर्छ। | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | यस थ्रेसहोल्ड बराबर वा माथिका वास्तविक अनुरोध बडीहरूले JSON पार्स गर्नुअघि एटोमिक प्रक्रिया-स्थानीय हेभीवेट एडमिसन लिज लिन्छन् (BYTE पथ, `POST /v1/responses` सहित)। संरचना-भारी अवस्थाको जस्तै [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) स्वस्थ-हेडरुम एस्केप; अझै पनि `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) द्वारा सीमित छ, जसले गर्दा [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) पुनः देखा पर्दैन। | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | सीमित इन्जेसनका क्रममा पढिएका बाइटहरूका आधारमा लागू गरिने च्याट-रुटको कठोर सीमा, जसमा `Content-Length` नभएका, अमान्य भएका वा गलत रूपमा प्रस्तुत गरिएका अनुरोधहरू पनि समावेश हुन्छन्; सीमा नाघेमा `413` प्राप्त हुन्छ। | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(सेट नगरिएको — अनुरोध-सङ्ख्या सीमा छैन)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** यो पुरानो अनुरोध-सङ्ख्या सीमा अब स्पष्ट रूपमा सेट गरिएको अवस्थामा मात्र लागू हुन्छ। सेट नगरी छोड्दा (पूर्वनिर्धारित), भारी च्याट एडमिसनलाई यसको सट्टा `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ले नियन्त्रण गर्छ — **एउटै प्रक्रिया** (एउटै V8 heap) मा प्रक्रियाको वास्तविक मेमोरी सीमाबाट आकार निर्धारण गरिएको स्वतः-व्युत्पन्न बाइट बजेट। करिब 750k-token का दुईवटा एकअर्कासँग खप्टिएका `/v1/responses` ले करिब 12 GiB heap हरू अबोर्ट गर्छन् (#7849) — यो **मेमोरी-बजेट** चेतावनी हो, 2 को कठोर उत्पादन अधिकतम होइन। स्वस्थ प्रक्रिया (shed ratio भन्दा तलको heap) ले `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` मार्फत थप समवर्ती लामो `/v1/responses` लाई एडमिट गर्न सक्छ। दर्जनौँ लामो SSE क्लाइन्टहरू (40–50) heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 सँग सम्बन्धित छन्, “अधिकतम 2” होइन। “होस्ट प्रयोग गर्न” यसलाई अन्धाधुन्ध बढाउँदा #7849 पुनः देखा पर्छ। **N स्वतन्त्र `DATA_DIR` हरू** (#11024) सँग **heap हरू** गुणन गर्नुहोस्; एउटै SQLite फाइलमा कहिल्यै `replicas>1` नराख्नुहोस्। | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(स्वतः-व्युत्पन्न)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** स्वतः-व्युत्पन्न इन्जेस्ट बाइट बजेटका लागि ओभरराइड (अझ कडा V8/cgroup मेमोरी सीमाको 25% लाई 8x अस्थायी एम्प्लिफिकेसनले भाग गरिएको)। व्युत्पन्न र स्पष्ट दुवै मानहरू 8 MiB–2 GiB मा क्ल्याम्प हुन्छन्। प्रभावकारी बजेटभन्दा ठूलो body तुरुन्तै `413 body_exceeds_budget` सहित असफल हुन्छ; व्यक्तिगत रूपमा सेवा दिन सकिने body हरूबीचको कन्टेन्सन भने पुनः प्रयास गर्न मिल्ने `503` नै रहन्छ। 40–50 समवर्ती लामो SSE क्लाइन्टहरू यो बजेट + heap सँग सम्बन्धित छन्, कठोर “अधिकतम 2” होइन। ट्युन गर्नुअघि `/api/monitoring/health` मा `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` पढ्नुहोस्। | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | बाइट र स्ट्रक्चरका भारी एडमिसनका लागि heap-pressure shed ratio (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` नाघेको समवर्ती भारी अनुरोधलाई heap पनि यो ratio मा वा त्यसभन्दा माथि हुँदा मात्र पुनः प्रयास गर्न मिल्ने `503` सहित shed गरिन्छ; स्वस्थ heap मा यसलाई healthy-headroom मार्फत एडमिट गरिन्छ। | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (पूर्वनिर्धारित `1`) | `src/shared/middleware/chatBodyAdmission.ts` | स्ट्रक्चर र बाइट दुवैमा (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` भन्दा ठूला वा बराबर body हरूसहित) स्वस्थ-heap फास्ट पाथ (#10437) का लागि सीमित अतिरिक्त क्षमता। यो सीमा नभएमा, प्रत्येक व्यस्त-तर-स्वस्थ-heap अनुरोधले कुनै उच्चतम सीमाविनै एडमिसन बाइपास गर्थ्यो। healthy-heap पाथमार्फत यति धेरै समवर्ती lease हरू सक्रिय भएपछि, थप व्यस्त अनुरोधहरू वास्तविक heap pressure मा प्रयोग हुने उही सीमित-प्रतीक्षा/shed पाथमा पुग्छन्। `0` ले बाइपास पूर्ण रूपमा निष्क्रिय गर्छ। | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | body बाइट थ्रेसहोल्डभन्दा तल हुँदा पनि च्याट अनुरोधलाई भारी भनेर वर्गीकृत गर्ने सन्देश सङ्ख्या। | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | body बाइट थ्रेसहोल्डभन्दा तल हुँदा पनि च्याट अनुरोधलाई भारी भनेर वर्गीकृत गर्ने tool सङ्ख्या। | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | अनुरोधलाई भारी भनेर वर्गीकृत गर्ने कन्जरभेटिभ string-size token अनुमान; यो एडमिसन लागतको प्रोक्सी हो, प्रदायकको बिलिङ tokenization होइन। | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (निष्क्रिय) | `src/shared/middleware/chatBodyAdmission.ts` | वैकल्पिक रूपमा अप्ट-इन गर्न सकिने च्याट इतिहास सीमा। पूर्वनिर्धारित रूपमा निष्क्रिय: सन्देश सङ्ख्या डिप्लोयमेन्ट नीति हो, अनुरोधको सार्वभौमिक गुण होइन, र यहाँ सीमा लगाउँदा compression pipeline ले तिनलाई सेवा दिन सक्ने बनाउनुअघि नै कुराकानीहरूलाई अन्तिम `413` सहित अस्वीकार गर्छ। Heap वृद्धि `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` र heap-pressure shed द्वारा सीमित हुन्छ। कठोर उच्चतम सीमा आवश्यक पर्ने मेमोरी-सीमित डिप्लोयमेन्टहरूमा धनात्मक मान सेट गर्नुहोस्; त्यसपछि सीमा नाघेमा संरचित compact-required `413` प्राप्त हुन्छ। | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | मेमोरीमा पूर्ण रूपमा बफर गरिने non-streaming upstream response का लागि कठोर सीमा। यो सीमा नाघेपछि upstream reader रद्द गरिन्छ र heap समाप्त नहुन्जेल असीमित string बढाइरहनुको सट्टा अनुरोध तुरुन्तै असफल हुन्छ। | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | अपस्ट्रिम प्रतिक्रिया हेडरहरूबाट फर्वार्ड गरिने अधिकतम वायर बाइटहरू। बजेट नाघेमा, सामान्य रिभर्स-प्रोक्सी हेडर सीमाभित्र रहन कम प्राथमिकताका हेडरहरू (जस्तै, अनुकूलित `x-codex-*`, `x-oai-request-id`) हटाइन्छन्। ठूलो प्रतिक्रिया हेडर आकारको मूल्यमा थप अपस्ट्रिम मेटाडेटा फर्वार्ड गर्न यसलाई उच्च मानमा सेट गर्नुहोस्। | +| `CORS_ORIGIN` | _(सेट नगरिएको)_ | `src/server/cors/origins.ts` | लिगेसी एकल-ओरिजिन CORS अनुमति सूची। नयाँ डिप्लोयमेन्टहरूका लागि `CORS_ALLOWED_ORIGINS` लाई प्राथमिकता दिनुहोस्। CORS क्रस-ओरिजिन ब्राउजर API क्लाइन्टहरूका लागि मात्र हो; प्रमाणीकरण गरिएका ड्यासबोर्ड लेखनहरूले यसको सट्टा समान-ओरिजिन अनुरोधहरूका साथै सत्रसँग बाँधिएको CSRF सुरक्षा प्रयोग गर्छन्। | +| `CORS_ALLOWED_ORIGINS` | _(सेट नगरिएको)_ | `src/server/cors/origins.ts` | अल्पविरामद्वारा छुट्याइएको CORS अनुमति सूची। `CORS_ALLOW_ALL=true` स्पष्ट रूपमा कन्फिगर नगरिएसम्म कुनै वाइल्डकार्ड पठाइँदैन। | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | कुनै पनि ब्राउजर `Origin` प्रतिध्वनित गर्न विकासका लागि मात्र प्रयोग गरिने वैकल्पिक उपाय। साझा वा उत्पादन डिप्लोयमेन्टहरूमा सक्षम नगर्नुहोस्। | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | लेगेसी उपनाम। यसको ड्यासबोर्ड टगल (DB ओभरराइड) यो वातावरण मानभन्दा अघि पढिन्छ; दुईमध्ये कुनैमा पनि `false`, `0`, `no` वा `off` भएमा — त्यो फ्ल्यागको ड्यासबोर्ड टगल बन्द रूपमा भण्डारण भए पनि — होस्ट जाँचहरू `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ले गरेझैं बन्द गर्छ; अन्य मानहरूमा गार्ड तलका दुई फ्ल्यागहरूको भरोसामा छोडिन्छ। | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | गार्ड मोडले पालना गर्ने प्रदायक मार्गहरूमा — URL प्रमाणीकरण, मोडेल खोज, प्रदायक-नोड आधार URL (रिमोट रिर्याङ्क नोडहरू सहित), प्रोक्सी-फलब्याक परीक्षण र रिमोट तस्बिर/मिडिया फेच जस्ता अन्य प्रदायक-नजिकका आउटबाउन्ड मार्गहरू — आउटबाउन्ड URL गार्डका होस्ट जाँचहरू, क्लाउड-मेटाडेटा रोकथाम सहित (सम्पूर्ण 169.254.0.0/16, metadata.google.internal), बन्द गर्छ। क्लाउड-मेटाडेटा/लिङ्क-लोकल एन्डपोइन्टहरू बाहेक, यसले निजी वेबहुक लक्ष्यहरूलाई पनि अनुमति दिन्छ — जुन यो फ्ल्याग सक्रिय भए पनि अवरुद्ध नै रहन्छन्। `BaseExecutor.execute()` मार्फत पठाइएका कुराकानी अनुरोधहरूको क्लाउड-मेटाडेटा जाँच कायम रहन्छ। स्थानीय प्रदायकहरू पूर्वनिर्धारित मानहरूसँगै काम गर्छन्: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (पूर्वनिर्धारित `true`) ले प्रमाणीकरण, खोज र प्रदायक-नोड मार्गहरूमा स्थानीय र LAN URL हरू पहिले नै अनुमति दिन्छ; प्रोक्सी-फलब्याक परीक्षण र निजी वेबहुक लक्ष्यहरूले भने यही फ्ल्यागलाई मात्र सोध्छन् र यो बन्द रहेसम्म स्थानीय/LAN होस्टहरू रोक्छन्। `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` लाई ओभरराइड गर्छ। ड्यासबोर्ड टगलबाट भण्डारण गरिएको मानले यो चरलाई उछिन्छ। | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | प्रदायक प्रमाणीकरण, मोडेल खोज, कुराकानी र गार्ड मोडले नियन्त्रण गर्ने अन्य प्रदायक-नजिकका आउटबाउन्ड मार्गहरूका लागि स्थानीय/निजी ठेगानाहरूमा (127.0.0.1, localhost, LAN, निजी दायराहरू) प्रदायक URL हरूलाई अनुमति दिन्छ। **पूर्वनिर्धारित `true`** (स्थानीय-प्रथम): त्यस अवस्थामा गार्डले क्लाउड-मेटाडेटा एन्डपोइन्टहरू — सम्पूर्ण 169.254.0.0/16 र ज्ञात मेटाडेटा होस्टनामहरू (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) — रोक्छ। निजी र लुपब्याक होस्टहरूलाई पनि रोक्ने सार्वजनिक-मात्र मोडका लागि `false` सेट गर्नुहोस्। गार्डले कुनै पनि DNS खोजी हुनुअघि होस्टनाम वा IP लिटरललाई लेखिएझैं जाँच गर्छ; सार्वजनिक-मात्र मोडमा safe-fetch र्‍यापरले थप रूपमा होस्टनाम रिजोल्भ गर्छ र कुनै पनि निजी उत्तर आए अस्वीकार गर्छ। `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ले यसको पङ्क्तिमा सूचीकृत मार्गहरूमा यी जाँचहरू बन्द गर्छ। LM Studio, Ollama, vLLM, Llamafile र Triton जस्ता निर्मित स्थानीय प्रदायकहरूले कुञ्जी प्रमाणीकरण र कुराकानीका लागि सबै होस्ट जाँचहरू छल्छन्। ड्यासबोर्ड टगलबाट भण्डारण गरिएको मानले यो चरलाई उछिन्छ। (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` रुटहरूलाई (ट्रान्सक्रिप्सन, स्पिच, अनुवाद) localhost बाहिर होस्ट गरिएको OpenAI-सङ्गत प्रदायक नोड प्रयोग गर्न दिनुहोस्। पूर्वनिर्धारित रूपमा बन्द — अडियोलाई रिमोट होस्टमा रुट गर्दा निर्गमन पहिचान परिवर्तन हुन्छ र यो अपरेटरको स्पष्ट निर्णय हुनुपर्छ। लुपब्याक/निजी नोडहरू (localhost, 127.0.0.1, 172.16-31.x) सधैं अनुमतिप्राप्त हुन्छन् र अप्रभावित रहन्छन्। (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (र मेमोरी इन्जिनको लुपब्याक रिर्याङ्क चरण) लाई localhost बाहिर होस्ट गरिएको OpenAI-सङ्गत प्रदायक नोड — TEI, Infinity, vLLM आदि चलिरहेको LAN बक्स वा Tailscale पियर — प्रयोग गर्न दिनुहोस्। पूर्वनिर्धारित रूपमा बन्द — रिमोट होस्टमा रुट गर्दा निर्गमन पहिचान परिवर्तन हुन्छ र यो अपरेटरको स्पष्ट निर्णय हुनुपर्छ। लुपब्याक नोडहरू (localhost, 127.0.0.1, 172.16-31.x) सधैं अनुमतिप्राप्त हुन्छन् र अप्रभावित रहन्छन्। रिमोट नोडको आधार URL ले पनि प्रदायकको आउटबाउन्ड URL नीति (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) पालना गर्नुपर्छ। | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC सक्षम हुँदा, पासवर्ड लगइन असक्षम गर्नुहोस् ताकि प्रयोगकर्ताहरूले OIDC Single Sign-On मार्फत मात्र प्रमाणीकरण गर्न सकून्। छोटो उपनाम `OIDC_DISABLE_PASSWORD_LOGIN` पनि स्वीकार गरिन्छ; उही कुञ्जी भएको ड्यासबोर्ड सुविधा फ्ल्यागले प्राथमिकता पाउँछ। (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` को छोटो उपनाम (#10889)। | ### सुरक्षा सुदृढीकरण जाँचसूची diff --git a/docs/i18n/nl/CONTRIBUTING.md b/docs/i18n/nl/CONTRIBUTING.md index 70a931b6949f..a947dc06ac85 100644 --- a/docs/i18n/nl/CONTRIBUTING.md +++ b/docs/i18n/nl/CONTRIBUTING.md @@ -394,15 +394,15 @@ Schrijf unittests in `tests/unit/` die minimaal het volgende afdekken: - [ ] Build slaagt (`npm run build`) - [ ] TypeScript-typen toegevoegd voor nieuwe openbare functies en interfaces - [ ] Geen hardgecodeerde geheimen of terugvalwaarden -- [ ] Openbare upstream-referenties ingesloten via `resolvePublicCred()` (zie [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nooit als literals +- [ ] Openbare upstream-inloggegevens ingesloten via `resolvePublicCred()` (zie [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nooit als letterlijke waarden - [ ] Foutreacties worden verwerkt via `buildErrorBody()` / `sanitizeErrorMessage()` — geen onbewerkte stacktraces in responsbody's (zie [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shellopdrachten (`exec` / `spawn`) geven runtimewaarden door via `env`, niet via tekenreeksinterpolatie +- [ ] Shell-opdrachten (`exec` / `spawn`) geven runtimewaarden door via `env`, niet via tekenreeksinterpolatie - [ ] Alle invoer gevalideerd met Zod-schema's -- [ ] Changelog**fragment** toegevoegd onder `changelog.d/{features|fixes|maintenance}/-.md` voor gebruikersgerichte wijzigingen (zie [`changelog.d/README.md`](./changelog.d/README.md)) — bewerk `CHANGELOG.md` **niet** rechtstreeks; fragmenten worden bij een release samengevoegd en veroorzaken nooit conflicten tussen pull requests +- [ ] **Fragment** voor de changelog toegevoegd onder `changelog.d/{features|fixes|maintenance}/-.md` voor gebruikersgerichte wijzigingen (zie [`changelog.d/README.md`](./changelog.d/README.md)) — bewerk `CHANGELOG.md` **niet** rechtstreeks; fragmenten worden bij een release samengevoegd en veroorzaken nooit conflicten tussen pull requests - [ ] Documentatie bijgewerkt (indien van toepassing) -- [ ] Geen nieuwe CodeQL- / Secret-Scanning-waarschuwingen geopend, of elke waarschuwing is afgewezen met een technische onderbouwing die verwijst naar het relevante document in `docs/security/` -- [ ] Routes die onderliggende processen starten (`/api/mcp/`, `/api/cli-tools/runtime/`) geclassificeerd als `isLocalOnlyPath()` in `src/server/authz/routeGuard.ts` — zie [Harde regel #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Geen `Co-Authored-By`-trailers in commitberichten — commits mogen uitsluitend onder de Git-identiteit van de eigenaar van de repository verschijnen (Harde regel #16) +- [ ] Geen nieuwe CodeQL- of Secret-Scanning-meldingen geopend, of elke melding afgewezen met een technische onderbouwing die verwijst naar het relevante document in `docs/security/` +- [ ] Routes die onderliggende processen starten (`/api/mcp/`, `/api/cli-tools/runtime/`) geclassificeerd als `isLocalOnlyPath()` in `src/server/authz/routeGuard.ts` — zie [harde regel #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Geen AI-/bottrailers met `Co-authored-by` in commitberichten (harde regel #16) — menselijke medewerkers van wie werk wordt hergebruikt, worden vermeld met standaardtrailers in de vorm `Co-authored-by: Name ` --- diff --git a/docs/i18n/nl/docs/architecture/QUALITY_GATES.md b/docs/i18n/nl/docs/architecture/QUALITY_GATES.md index 62862aa02332..8dde4fa56d63 100644 --- a/docs/i18n/nl/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/nl/docs/architecture/QUALITY_GATES.md @@ -19,210 +19,214 @@ replicatieplanning van hetzelfde systeem het ### Toelating van kandidaten -De workflows CI en Quality Gates produceren elk een stabiel oordeel: `Gate / CI` en -`Gate / Quality`. Hun versiegebonden toelatingsbeleid somt elke upstream-job op -als vereist of adviserend. Een toepasselijke vereiste job moet slagen: ontbrekende, -geannuleerde, overgeslagen, in behandeling zijnde en onbekende resultaten kunnen geen PASS vaststellen. Een geldige -classificatie als alleen documentatie of alleen catalogus kan een code-lane niet-toepasselijk maken; -een concept-PR is geen geaccepteerde kandidaat. Een `hotfix`-label stelt niet vrij van bewijs. - -Beide workflows dekken PR's en pushes naar main-/release-branches, handmatige dispatches en -merge-group-events. Pushes, dispatches en merge groups voeren de volledige selectie uit. Forks -en merge groups gebruiken gehoste runners voor jobs die anders zelfgehoste -runners selecteren; vóór de uitrol moet voldoende gehoste capaciteit worden geverifieerd. +De workflows CI en Quality Gates geven elk een stabiel oordeel af: `Gate / CI` en +`Gate / Quality`. Hun geversioneerde toelatingsbeleid vermeldt elke upstream-job +als verplicht of adviserend. Een toepasselijke verplichte job moet slagen: ontbrekende, +geannuleerde, overgeslagen, wachtende en onbekende resultaten kunnen geen PASS aantonen. +Een geldige classificatie als alleen-documentatie of alleen-catalogus kan een code-lane +niet-toepasselijk maken; een concept-PR is geen geaccepteerde kandidaat. Een `hotfix`-label +stelt een kandidaat niet vrij van het leveren van bewijs. + +Beide workflows ondersteunen PR's en pushes naar main/release-branches, handmatige dispatches +en merge-group-events. Bij push, dispatch en merge-group wordt de volledige selectie uitgevoerd. +Forks en merge-groepen gebruiken hosted runners voor jobs die anders self-hosted runners +selecteren; vóór de uitrol moet worden geverifieerd dat er voldoende hosted capaciteit is. Elk JSON-ontvangstbewijs identificeert de uitgecheckte SHA, workflow-run en poging. -De CLI weigert een mismatch tussen de checkout-SHA en de event-SHA. Workflowtests koppelen beleidslidmaatschap -aan de `needs`-lijst van de beoordelingsjob, zodat een nieuwe of verwijderde lane niet ongemerkt kan verdwijnen. -De ontvangstbewijzen dekken hun eigen workflow, niet publicatie, implementatie of de interne werking -van een bestaande adviserende scanner. Het activeren van beide controlenamen in branchregels is een -afzonderlijke administratieve wijziging; het toevoegen van deze jobs beschermt op zichzelf geen branch. +De CLI weigert een verschil tussen de SHA van de checkout en die van het event. Workflowtests +koppelen beleidslidmaatschap aan de `needs`-lijst van de beoordelingsjob, zodat een nieuwe of +verwijderde lane niet ongemerkt kan verdwijnen. De ontvangstbewijzen dekken hun eigen workflow, +niet publicatie, implementatie of de interne werking van een bestaande adviserende scanner. +Het activeren van beide controlenamen in branchregels is een afzonderlijke administratieve +wijziging; het toevoegen van deze jobs beschermt op zichzelf geen branch. ### Inventaris van statische scans -De versiegebonden inventaris van npm-aliassen en het lidmaatschap van statische scans bevinden zich in +De geversioneerde inventaris van npm-aliassen en het lidmaatschap van statische scans staan in `config/quality/gate-manifest.json`. Voer `npm run check:gate-manifest` uit om -scriptnamen en exacte commando's te valideren aan de hand van `package.json`; toevoegingen, verwijderingen en -afwijkingen in commando's laten zowel de lokale hook als de wijzigingsclassificatiejobs in CI mislukken. -Een alias is geen workflowjob, matrixinstantie of testcase: deze aantallen mogen -niet als onderling uitwisselbaar worden gepresenteerd. +scriptnamen en exacte opdrachten te valideren aan de hand van `package.json`; toevoegingen, +verwijderingen en opdrachtafwijkingen laten zowel de lokale hook als de +wijzigingsclassificatiejobs in CI mislukken. Een alias is geen workflowjob, matrixinstantie of +testcase: deze aantallen mogen niet als onderling uitwisselbaar worden gepresenteerd. Gebruik `npm run quality:scan -- --list` of `npm run quality:scan:fast -- --list` -om de geselecteerde aliassen te inspecteren zonder ze uit te voeren. De runner roept het +om de geselecteerde aliassen te bekijken zonder ze uit te voeren. De runner roept het npm-entrypoint aan, zodat de runtime ervan (inclusief Bun waar geconfigureerd) behouden blijft. -Het manifest registreert aliassen buiten die profielen als afzonderlijk aangeroepen, en -onderhoudscommando's zijn verboden in alleen-lezen-scanprofielen. +Het manifest registreert aliassen buiten deze profielen als afzonderlijk aangeroepen, en +onderhoudsopdrachten zijn verboden in alleen-lezen-scanprofielen. -Deze profielen dekken alleen de statische scan. Ze certificeren geen producttests, +Deze profielen dekken uitsluitend de statische scan. Ze certificeren geen producttests, dekking, packaging, externe controles of de volledige releaseacceptatie van een kandidaat. Workflowtoelating gebruikt het gekoppelde `config/quality/admission-policy.json` en `scripts/quality/admission-verdict.mjs`. Release-observer-profielen blijven afzonderlijk; -inspecteer hun toepasselijke controles en ontvangstbewijzen onafhankelijk. De tekstuele -inventaris hieronder is een referentie, geen bewijs dat een gate daadwerkelijk is uitgevoerd. +controleer hun toepasselijke controles en ontvangstbewijzen onafhankelijk. De tekstuele +inventaris hieronder is een naslagwerk, geen bewijs dat een gate daadwerkelijk is uitgevoerd. Scripts bevinden zich onder `scripts/check/` (beleidsgates) en `scripts/quality/` (ratchet-engine). De bron van waarheid voor CI is `.github/workflows/ci.yml`. ### Snel pad voor release-PR's (`quality.yml`) -`.github/workflows/quality.yml` vult CI aan voor PR's naar main-/release-branches, pushes naar beveiligde branches, -dispatches en merge groups. PR's gebruiken snelle controles met padfilters. De permanent -uitgeschakelde dubbele build is verwijderd; de echte build-/package-/opstartcontroles blijven in CI. - -| Job | Bereik | Blokkerend | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -| `Docs Gates (fast-path)` | Documentatie-/code-PR's; API-documentatiereferenties en alle documentatie | Ja | -| `Fast Quality Gates` | Code-PR's; statische controles, typecontrole, typecontrole van het dashboard, beïnvloede unittests | Ja | -| `Forgotten sibling tests` | Code-PR's; gewijzigde modules getraceerd naar statische gebruikers en kandidaat-siblingtests; barrel- en dynamic-import-paden worden gerapporteerd als adviserende diagnostiek, met uitzonderingen op basis van een allowlist waarnaar wordt verwezen | **Adviserend** | -| `Vitest (fast-path)` | Code-PR's; snelle Vitest-suite | Ja | -| `Unit Tests fast-path` | Code-PR's; unittest-suite met 4 shards | Ja | -| `No new ESLint warnings` | Code-PR's; onderdrukkingsbewuste lintbewaking | Ja, inclusief forks | -| `Merge integrity (changelog + generated skills)` | Niet-concept-PR's; synchronisatie van de changelog en gegenereerde skills | Ja, inclusief forks | +`.github/workflows/quality.yml` vult CI aan voor PR's naar main/release, pushes naar beveiligde +branches, dispatches en merge-groepen. PR's gebruiken snelle, op paden gefilterde controles. De +permanent uitgeschakelde dubbele build is verwijderd; de echte controles voor build, packaging +en opstarten blijven in CI. + +| Job | Bereik | Blokkerend | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `Docs Gates (fast-path)` | Documentatie-/code-PR's; API-documentatiereferenties en docs-all | Ja | +| `Fast Quality Gates` | Code-PR's; statische controles, typecheck, dashboard-typecheck, beïnvloede unittests | Ja | +| `Forgotten sibling tests` | Code-PR's; gewijzigde modules getraceerd naar statische consumers en kandidaat-siblingtests; barrel- en dynamic-import-paden worden gerapporteerd als adviserende diagnostiek, met allowlist-uitzonderingen waarnaar wordt verwezen | **Adviserend** | +| `Vitest (fast-path)` | Code-PR's; snelle vitest-suite | Ja | +| `Unit Tests fast-path` | Code-PR's; unitsuite met 4 shards | Ja | +| `No new ESLint warnings` | Code-PR's; onderdrukkingsbewuste lint-bewaking | Ja, inclusief forks | +| `Merge integrity (changelog + generated skills)` | Niet-concept-PR's; synchronisatie van changelog en gegenereerde skills | Ja, inclusief forks | #### Rapport over vergeten siblingtests `npm run check:forgotten-sibling-tests` hergebruikt de importresolver achter de test-impactmap. -Voor elke gewijzigde productiemodule rapporteert het deterministische -`gewijzigde module/symbool -> statische gebruiker -> kandidaat-siblingtest`-ketens wanneer de kandidaat- -test ontbreekt in de diff van de pull request. De Markdown-samenvatting en het JSON-resultaat worden bewaard als -het workflowartefact `forgotten-sibling-tests` voor kalibratie vóór een eventuele blokkerende uitrol. +Voor elke gewijzigde productiemodule rapporteert het deterministische ketens van +`gewijzigde module/symbool -> statische consumer -> kandidaat-siblingtest` wanneer de kandidaat- +test ontbreekt in de diff van de pull request. De Markdown-samenvatting en het JSON-resultaat +worden bewaard als het workflowartefact `forgotten-sibling-tests` voor kalibratie voordat een +blokkerende uitrol plaatsvindt. Barrel-re-exports en dynamische imports dienen uitsluitend als resolutiediagnostiek; ze leiden nooit tot een blokkerende bevinding. Beoordeelde uitzonderingen staan in -`config/quality/forgotten-sibling-allowlist.json`. Elke vermelding moet de consumerende test en de kandidaat-test -benoemen, een specifieke motivering geven en naar een GitHub-issue of pull request verwijzen. Ongeldige vermeldingen worden -standaard geweigerd. Uitzonderingen kunnen een verwijderde kandidaat-test of een diff die `.skip`/`.todo` toevoegt niet onderdrukken; -het verzwakken van assertions en andere vormen van maskering blijven de verantwoordelijkheid van de onafhankelijk blokkerende +`config/quality/forgotten-sibling-allowlist.json`. Elke vermelding moet de consumer en kandidaat- +test benoemen, een specifieke onderbouwing geven en naar een GitHub-issue of pull request linken. Ongeldige vermeldingen worden +standaard geweigerd. Uitzonderingen kunnen een verwijderde kandidaattest of een diff die `.skip`/`.todo` toevoegt niet onderdrukken; +het afzwakken van assertions en andere maskering vallen onder de onafhankelijk blokkerende `check:test-masking`-gate. ### Job: `lint` Wordt uitgevoerd bij elke PR naar `main`. Blokkeert samenvoegen bij een fout. -| Script (`npm run ...`) | Valideert | Blokkerend | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | De Node.js-versie valt binnen het ondersteunde bereik | Ja | -| `check:cycles` | Circulaire imports — alle modules in `src/` + `open-sse/` | Ja | -| `check:route-validation:t06` | Zod-schema's aanwezig op alle routes (Tier 6-beleid) | Ja | -| `check:any-budget:t11` | Het aantal `@ts-expect-error // any` overschrijdt het budget niet (Tier 11-catraca) | Ja | -| `check:provider-consistency` | Elke provider in `providers.ts` heeft een overeenkomstige vermelding in `providerRegistry.ts` (en omgekeerd, binnen de allowlist) | Ja | -| `check:model-lifecycle` | De drie handmatig onderhouden routeringstabellen blijven consistent met de ingecheckte levenscyclussnapshot (#11503): `FITNESS_TABLE` (`taskFitness.ts`) kent geen score toe aan een uitgefaseerde id die door `REGISTRY` kan worden gerouteerd; elk doel van `BUILT_IN_ALIASES` is aanwezig in `REGISTRY` en afwezig in de snapshot met uitgefaseerde id's; elke uitgefaseerde id die nog in `REGISTRY` staat, wordt doorgestuurd of vermeld in `allowedRetiredInCatalog`; en geen enkele bron of doel van `DEFAULT_DEGRADATION_MAP` staat in die snapshot als uitgefaseerd. Dit bewijst niet dat een model momenteel door een actieve upstream wordt aangeboden. Offline — vergelijkt met `config/quality/model-lifecycle.json`, handmatig vernieuwd met `npm run quality:refresh-model-lifecycle` (netwerk; niet in CI geïntegreerd). `allowedRetiredInCatalog` is een afbouwmechanisme: voeg alleen een vermelding toe als er een tracking-issue is. | Ja | -| `check:fetch-targets` | Elke `fetch("/api/...")` in `src/` aan de clientzijde wordt herleid tot een bestaand `route.ts` | Ja | -| `check:deps` | Alle via `npm install` installeerbare afhankelijkheden in elk `package.json` in de repository staan in `dependency-allowlist.json`; nieuwe niet-vastgezette pakketten of pakketten met een naam die lijkt op die van een bestaand pakket worden gemarkeerd | Ja | -| `audit:deps` | `npm audit` (root + electron) — geen waarschuwingen met hoge/kritieke ernst (overlapt met osv `check:vuln-ratchet`; zie Rationalisatiebacklog) | Ja | -| `check:lockfile` | Integriteit van `package-lock.json` — https-register, integriteitshashes, geen hostoverschrijvingen | Ja | -| `check:licenses` | SPDX-licentietoegestane lijst voor productieafhankelijkheden | Ja | -| `check:tracked-artifacts` | Geen buildartefacten / vastgelegde `node_modules`-symbolische koppelingen (wordt ook uitgevoerd in de husky-pre-commithaak; pre-push is bewust lichtgewicht — #6716) | Ja | -| `check:ai-attribution` | Geen AI/bot-`Co-Authored-By`-trailer of AI-generatievoettekst in PR-commits, titel of hoofdtekst — Harde regel #16 (in de snelle-controlelus van `quality.yml` voor PR→`release/**` — leest de eventpayload, doet niets buiten PR's — en een stap die alleen voor PR's geldt in de linttaak van `ci.yml` voor PR→`main`; ook de husky-`commit-msg`-haak; menselijke co-auteurs toegestaan; #14436) | -| `check:vitest-exclusions` | Elke Vitest-uitsluiting vermeldt een trackingissue en komt voor in `config/quality/vitest-exclusions.json` (#13204) | Ja | -| `check:file-size` | Geen bronbestand overschrijdt de limiet per extensie (ratelmechanisme: bevroren grote bestanden in de `frozen`-lijst) | Ja | -| `check:error-helper` | Foutreacties in executors/handlers gebruiken `buildErrorBody()` / `sanitizeErrorMessage()` (Harde regel #12) | Ja | -| `check:migration-numbering` | Migratie-SQL-bestanden zijn opeenvolgend genummerd, zonder hiaten of duplicaten | Ja | -| `check:public-creds` | Geen letterlijke OAuth-`client_id`/`client_secret`-waarden of Firebase Web-sleutels buiten `publicCreds.ts` (Harde regel #11) | Ja | -| `check:db-rules` | Geen onbewerkte SQL buiten modules in `src/lib/db/`; geen barrel-imports vanuit `localDb.ts` (Harde regels #2/#5) | Ja | -| `check:known-symbols` | Provider-executors, routeringsstrategieën en vertalers die in hun dispatch-tabellen zijn geregistreerd, komen overeen met de bestanden op schijf — geen verweesde of niet-gedeclareerde symbolen | Ja | -| `check:route-guard-membership` | Elke route die een onderliggend proces start, is geclassificeerd door `isLocalOnlyPath()` (Harde regels #15/#17) | Ja | -| `check:test-discovery` | Elk `*.test.ts`- / `*.spec.ts`-bestand in de repo wordt door ten minste één testrunner verzameld (ratchet: de lijst met verweesde bestanden in `test-discovery-baseline.json` kan alleen maar krimpen) | Ja | -| `check:agent-skills-sync` | Gegenereerde agent-skills-artefacten komen overeen met hun broncatalogus (geen afwijkingen) | -| `check:provider-asset-provenance` | Logo's/assets van providers hebben een vastgelegde herkomstregistratie | -| `lint:json` | JSON-configuratiebestanden kunnen worden geparseerd en voldoen aan de lintregels van de repo | -| `typecheck:core` | TypeScript-compilatie zonder fouten (alleen adviserende waarschuwingen) | Ja | -| `typecheck:noimplicit:core` | Strikte `noImplicitAny` — toekomstgericht; veel bestaande aanroeplocaties moeten nog van annotaties worden voorzien | **Adviserend** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` beperkt tot `src/app/(dashboard)/**` (#7033) — de samengestelde allowlist van 27 bestanden van `typecheck:core` bevat geen enkele dashboard-TSX en `next build` voert er evenmin typecontrole op uit (`next.config.mjs` stelt `ignoreBuildErrors: true` in), waardoor regressies met verweesde identifiers daar (#6625/#6909) onzichtbaar waren voor CI. Vergelijkt met een bevroren basislijn voor aantallen per bestand/per TS-code (`config/quality/dashboard-typecheck-baseline.json`, hetzelfde patroon voor afdwinging van veroudering als `check:known-symbols`) — alleen NIEUWE fouten boven op het aantal in de basislijn laten de controle mislukken; stel de basislijn naar beneden bij met `--update` wanneer een bestaande fout is opgelost. | Ja | - -### Taak: `quality-gate` - -Wordt uitgevoerd na `test-coverage`. Blokkeert het samenvoegen bij een fout. - -| Script | Valideert | Blokkerend | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `quality:collect` | Genereert `quality-metrics.json` (aantal ESLint-waarschuwingen, dekking uit samengevoegd shardrapport) | Ja (voorafgaand aan ratchet) | -| `quality:ratchet` | Geen enkele metriek in `quality-baseline.json` is achteruitgegaan (ESLint-waarschuwingen ≤ baseline; dekking ≥ baseline) | Ja | -| `check:duplication` | Codeduplicatie (jscpd@4) overschrijdt de baseline in `quality-baseline.json` niet | Ja | -| `check:complexity` | Cyclomatische complexiteit op bestandsniveau overschrijdt de limiet niet (ESLint-kernregels `complexity` + `max-lines-per-function`) | Ja | -| `check:cognitive-complexity` | Ratchet voor cognitieve complexiteit (`eslint-plugin-sonarjs`) — afzonderlijke ESLint-run; CI voert beide samengevoegd uit als de ene stap `check:complexity-ratchets` | Ja | -| `check:dead-code` | Ratchet voor ongebruikte exports/bestanden (knip) gaat niet achteruit ten opzichte van de baseline | Ja | -| `check:compression-budget` | Budget voor compressiebenchmark — minimale tokenbesparing per engine mag niet achteruitgaan | Ja | -| `check:type-coverage` | Ratchet voor percentage getypeerde code (`type-coverage`) gaat niet achteruit; vervangt grotendeels `typecheck:noimplicit:core` | Ja | -| `check:codeql-ratchet` | Het aantal openstaande CodeQL-meldingen gaat niet achteruit (uitgelezen via `gh api`; gecontroleerd overslaan zonder token) — vernieuwingsfrequentie en handmatige trigger: zie "CodeQL-ratchet" hieronder | Ja | - -### Taak: `quality-extended` - -De volledige taak is adviserend (`continue-on-error: true`). De op npm gebaseerde ratchets worden +| Script (`npm run ...`) | Valideert | Blokkerend | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | De Node.js-versie valt binnen het ondersteunde bereik | Ja | +| `check:cycles` | Cyclische imports in heel `src/` + `open-sse/` (AST-gebaseerd, tsconfig-`paths` opgelost). Zonder extra opties = adviserend, toont de cycli. `check:cycles:ratchet` (wat CI uitvoert) blokkeert wanneer het aantal het `metrics.cycles`-plafond in `quality-baseline.json` overschrijdt — momenteel 14, `direction: down`, dus dit kan alleen dalen (#15159 G-01/G-02) | Ja (ratchet) | +| `check:route-validation:t06` | Zod-schema's aanwezig op alle routes (Tier 6-beleid) | Ja | +| `check:any-budget:t11` | Het aantal `@ts-expect-error // any` overschrijdt het budget niet (Tier 11-ratel) | Ja | +| `check:provider-consistency` | Elke provider in `providers.ts` heeft een overeenkomstige vermelding in `providerRegistry.ts` (en omgekeerd, binnen de allowlist) | Ja | +| `check:model-lifecycle` | De drie handmatig onderhouden routeringstabellen blijven consistent met de ingecheckte levenscyclussnapshot (#11503): `FITNESS_TABLE` (`taskFitness.ts`) kent geen score toe aan een uitgefaseerde id die door `REGISTRY` kan worden gerouteerd; elk doel van `BUILT_IN_ALIASES` is aanwezig in `REGISTRY` en afwezig in de snapshot met uitgefaseerde id's; elke uitgefaseerde id die nog in `REGISTRY` staat, wordt doorgestuurd of vermeld in `allowedRetiredInCatalog`; en geen enkele bron of bestemming van `DEFAULT_DEGRADATION_MAP` wordt in die snapshot als uitgefaseerd aangemerkt. Dit bewijst niet dat een model momenteel door een actieve upstream wordt aangeboden. Offline — vergelijkt met `config/quality/model-lifecycle.json`, handmatig bijgewerkt met `npm run quality:refresh-model-lifecycle` (netwerk; niet geïntegreerd in CI). `allowedRetiredInCatalog` is een afbouwratel: voeg alleen een vermelding toe met een tracking-issue. | Ja | +| `check:fetch-targets` | Elke `fetch("/api/...")` in de client-side `src/` verwijst naar een echte `route.ts` | Ja | +| `check:deps` | Alle via `npm install` installeerbare afhankelijkheden in elke `package.json` in de repo staan in `dependency-allowlist.json`; nieuwe niet-vastgezette of slopsquatted pakketten worden gemarkeerd | Ja | +| `audit:deps` | `npm audit` (root + electron) — geen waarschuwingen met hoge/kritieke ernst (overlapt met OSV-`check:vuln-ratchet`; zie Rationalisatiebacklog) | Ja | +| `check:lockfile` | Integriteit van `package-lock.json` — https-register, integriteitshashes, geen hostoverschrijvingen | Ja | +| `check:licenses` | SPDX-licentie-toestaanlijst voor productieafhankelijkheden | Ja | +| `check:tracked-artifacts` | Geen buildartefacten / vastgelegde `node_modules`-symbolische koppelingen (wordt ook uitgevoerd in husky pre-commit; pre-push is opzettelijk lichtgewicht — #6716) | Ja | +| `check:ai-attribution` | Geen AI-/bot-`Co-Authored-By`-trailer of door AI gegenereerde voettekst in PR-commits, -titel of -tekst — Harde regel #16 (in de snelle-controlelus van `quality.yml` voor PR→`release/**` — leest de eventpayload, no-op buiten PR's — en een uitsluitend voor PR's bestemde stap in de linttaak van `ci.yml` voor PR→`main`; tevens de husky-`commit-msg`-hook; menselijke co-auteurs zijn toegestaan; #14436) | +| `check:vitest-exclusions` | Elke Vitest-uitsluiting vermeldt een trackingissue en komt voor in `config/quality/vitest-exclusions.json` (#13204) | Ja | +| `check:file-size` | Geen bronbestand overschrijdt de limiet per extensie (ratchet: bevroren grote bestanden in de lijst `frozen`) | Ja | +| `check:error-helper` | Foutresponsen in uitvoerders/handlers gebruiken `buildErrorBody()` / `sanitizeErrorMessage()` (Harde regel #12) | Ja | +| `check:migration-numbering` | Migratie-SQL-bestanden zijn opeenvolgend genummerd, zonder hiaten of duplicaten | Ja | +| `check:public-creds` | Geen letterlijke OAuth-`client_id`/`client_secret` of Firebase-websleutels buiten `publicCreds.ts` (Harde regel #11) | Ja | +| `check:db-rules` | Geen ruwe SQL buiten modules in `src/lib/db/`; geen barrel-imports uit `localDb.ts` (Harde regels #2/#5) | Ja | +| `check:known-symbols` | Provider-executors, routeringsstrategieën en translators die in hun dispatch-tabellen zijn geregistreerd, komen overeen met de bestanden op schijf — geen verweesde of niet-gedeclareerde symbolen | Ja | +| `check:route-guard-membership` | Elke route die een onderliggend proces start, is geclassificeerd door `isLocalOnlyPath()` (Harde regels #15/#17) | Ja | +| `check:test-discovery` | Elk `*.test.ts`- / `*.spec.ts`-bestand in de repo wordt door ten minste één test-runner verzameld (ratchet: de lijst met verweesde bestanden in `test-discovery-baseline.json` kan alleen kleiner worden) | Ja | +| `check:agent-skills-sync` | Gegenereerde agent-skills-artefacten komen overeen met hun broncatalogus (geen afwijkingen) | +| `check:provider-asset-provenance` | Providerlogo's/-assets hebben een vastgelegde herkomstvermelding | +| `lint:json` | JSON-configuratiebestanden worden correct geparseerd en voldoen aan de lintregels van de repo | +| `typecheck:core` | TypeScript-compilatie zonder fouten (alleen adviserende waarschuwingen) | Ja | +| `typecheck:noimplicit:core` | Strikte `noImplicitAny` — toekomstgericht; veel reeds bestaande aanroepen hebben nog annotaties nodig | **Adviserend** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` beperkt tot `src/app/(dashboard)/**` (#7033) — de samengestelde allowlist van 27 bestanden van `typecheck:core` bevat geen dashboard-TSX, en `next build` voert hiervoor evenmin typecontrole uit (`next.config.mjs` stelt `ignoreBuildErrors: true` in), waardoor regressies met verweesde identifiers daar (#6625/#6909) onzichtbaar waren voor CI. Verschillen worden vergeleken met een bevroren baseline van aantallen per bestand/per TS-code (`config/quality/dashboard-typecheck-baseline.json`, hetzelfde patroon voor afdwinging bij veroudering als `check:known-symbols`) — alleen NIEUWE fouten boven het aantal in de baseline laten de gate mislukken; verlaag de baseline stapsgewijs met `--update` wanneer een reeds bestaande fout is opgelost. | Ja | + +### Job: `quality-gate` + +Wordt uitgevoerd na `test-coverage`. Blokkeert samenvoegen bij mislukking. + +| Script | Valideert | Blokkerend | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `quality:collect` | Genereert `quality-metrics.json` (aantal ESLint-waarschuwingen, dekking uit samengevoegd shardrapport) | Ja (voorafgaand aan ratchet) | +| `quality:ratchet` | Geen enkele metriek in `quality-baseline.json`` is verslechterd (ESLint-waarschuwingen ≤ baseline; dekking ≥ baseline) | Ja | +| `check:duplication` | Codeduplicatie (jscpd@4) overschrijdt de baseline in `quality-baseline.json` niet | Ja | +| `check:complexity` | Cyclomatische complexiteit op bestandsniveau overschrijdt de limiet niet (ESLint-core `complexity` + `max-lines-per-function`) | Ja | +| `check:cognitive-complexity` | Ratchet voor cognitieve complexiteit (`eslint-plugin-sonarjs`) — afzonderlijke ESLint-uitvoering; CI voert beide samengevoegd uit als de enkele stap `check:complexity-ratchets` | Ja | +| `check:dead-code` | Ratchet voor ongebruikte exports/bestanden (knip) verslechtert niet ten opzichte van de baseline | Ja | +| `check:compression-budget` | Budget voor compressiebenchmarks — minimale tokenbesparingen per engine mogen niet verslechteren | Ja | +| `check:type-coverage` | Ratchet voor het percentage getypeerde code (`type-coverage`) verslechtert niet; omvat grotendeels `typecheck:noimplicit:core` | Ja | +| `check:codeql-ratchet` | Aantal openstaande CodeQL-waarschuwingen verslechtert niet (uitgelezen via `gh api`; wordt zonder token probleemloos overgeslagen) — voor vernieuwingsfrequentie en handmatige trigger: zie 'CodeQL-ratchet' hieronder | Ja | + +### Job: `quality-extended` + +De volledige job is adviserend (`continue-on-error: true`). De op npm gebaseerde ratchets worden daadwerkelijk uitgevoerd; de externe scanners worden geïnstalleerd via `gh release download` en slaan zichzelf over (exit 0) wanneer een binair bestand nog steeds ontbreekt. -| Script | Valideert | Blokkerend | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `check:circular-deps` | Geen circulaire afhankelijkheden (dpdm) | **Adviserend** | -| `check:bundle-size` | Bundelgrootte overschrijdt de limiet niet | **Adviserend** | -| `check:secrets` | Scannen op geheimen (gitleaks) — wordt overgeslagen als het binaire bestand ontbreekt | **Adviserend** | -| `check:vuln-ratchet` | Kwetsbaarheden in afhankelijkheden (osv-scanner) gaan niet achteruit — wordt overgeslagen als het binaire bestand ontbreekt | **Adviserend** | -| `check:workflows` | Workflowlinting (actionlint + zizmor); ontbrekende/defecte scanners, ongeldige rapporten of een ontbrekende ratchetbaseline leiden tot INCOMPLETE. Geldige bevindingen volgen het geselecteerde strikte/adviserende/ratchetbeleid | Uitvoering vereist; zizmor-ratchet blokkerend in CI | -| `check:openapi-breaking` | Brekende wijzigingen in het openbare API-contract (`openapi.yaml`) ten opzichte van de basisbranch (oasdiff) — genereert `openapiBreaking=N`; wordt overgeslagen als oasdiff ontbreekt of de basisspecificatie niet kan worden omgezet | **Adviserend** | - -### Taak: `docs-sync-strict` - -Wordt uitgevoerd bij elke PR naar `main`. Blokkeert samenvoegen bij een fout. - -| Script | Valideert | Blokkerend | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `check:docs-all` | Metacontrole die de 6 onderstaande deelcontroles achtereenvolgens uitvoert | Ja | -| ↳ `check:docs-sync` | Versieconsistentie van CHANGELOG / OpenAPI / llm.txt | Ja | -| ↳ `check:docs-counts` | Aantallen in prozatekst (aantal providers, aantal migraties enz.) vallen binnen het ratchetvenster van de werkelijke aantallen | Ja | -| ↳ `check:env-doc-sync` | Elke omgevingsvariabele in `.env.example` is gedocumenteerd in een documentatietabel, en omgekeerd | Ja | -| ↳ `check:deprecated-versions` | Geen verouderde versietekenreeksen in de documentatie | Ja | -| ↳ `check:doc-links` | Interne markdownlinks in de documentatie verwijzen naar bestaande bestanden (`[text]`/`(path)`-vorm) | Ja | -| ↳ `check:fabricated-docs` | Routes, omgevingsvariabelen, CLI-opdrachten, hooknamen en bestandspaden die in de documentatie worden genoemd, bestaan in de codebase. Harde controle via `--strict`; zachte fout zonder vlag. | Ja (via `--strict` in CI) | -| `check:cli-i18n` | CLI-opdrachttekenreeksen zijn aanwezig in alle i18n-localebestanden | Ja | -| `check:openapi-coverage` | De OpenAPI-specificatie dekt ten minste een met een ratchet vastgelegde ondergrens van de werkelijke routes | Ja | -| `check:openapi-security-tiers` | Beveiligingsniveauannotaties in `openapi.yaml` komen overeen met de classificaties in `routeGuard.ts` | **Adviserend** | -| `check:openapi-routes` | Elk pad in `openapi.yaml` verwijst naar een bestaande `route.ts` (anti-hallucinatie) | Ja | -| `check:docs-symbols` | Elke `/api/...`-verwijzing in `docs/**/*.md` verwijst naar een bestaande `route.ts` (anti-hallucinatie) | Ja | -| `i18n translation drift` | Onvertaalde sleutels in i18n-localebestanden — alleen waarschuwen | **Adviserend** | +| Script | Valideert | Blokkerend | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `check:circular-deps` | Geen circulaire afhankelijkheden (dpdm) | **Adviserend** | +| `check:bundle-size` | Bundelgrootte overschrijdt de limiet niet | **Adviserend** | +| `check:secrets` | Scannen op geheimen (gitleaks) — wordt overgeslagen als het binaire bestand ontbreekt | **Adviserend** | +| `check:vuln-ratchet` | Kwetsbaarheden in afhankelijkheden (osv-scanner) verslechteren niet — wordt overgeslagen als het binaire bestand ontbreekt | **Adviserend** | +| `check:workflows` | Workflow-linting (actionlint + zizmor); ontbrekende/defecte scanners, ongeldige rapporten of een ontbrekende ratchetbaseline leiden tot INCOMPLETE. Geldige bevindingen volgen het geselecteerde strikte/adviserende/ratchetbeleid | Uitvoering vereist; zizmor-ratchet blokkeert in CI | +| `check:openapi-breaking` | Incompatibele wijzigingen in het openbare API-contract (`openapi.yaml`) ten opzichte van de basisbranch (oasdiff) — genereert `openapiBreaking=N`; wordt overgeslagen als oasdiff ontbreekt of de basisspecificatie niet kan worden verwerkt | **Adviserend** | + +### Job: `docs-sync-strict` + +Wordt uitgevoerd bij elke PR naar `main`. Blokkeert samenvoegen bij fouten. + +| Script | Valideert | Blokkerend | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `check:docs-all` | Metapoort die de 6 onderstaande subpoorten achtereenvolgens uitvoert | Ja | +| ↳ `check:docs-sync` | Versieconsistentie tussen CHANGELOG / OpenAPI / llm.txt | Ja | +| ↳ `check:docs-counts` | Aantallen in lopende tekst (aantal providers, aantal migraties enz.) vallen binnen het aanscherpingsvenster van de werkelijke aantallen | Ja | +| ↳ `check:env-doc-sync` | Elke omgevingsvariabele in `.env.example` is gedocumenteerd in een documentatietabel en omgekeerd | Ja | +| ↳ `check:deprecated-versions` | Geen verouderde versietekenreeksen in de documentatie | Ja | +| ↳ `check:doc-links` | Interne markdown-links in de documentatie verwijzen naar bestaande bestanden (`[tekst]`/`(pad)`-vorm) | Ja | +| ↳ `check:fabricated-docs` | Routes, omgevingsvariabelen, CLI-opdrachten, hooknamen en bestandspaden die in de documentatie worden genoemd, bestaan in de codebase. Harde poort via `--strict`; zonder vlag slechts een waarschuwing. | Ja (via `--strict` in CI) | +| `check:cli-i18n` | CLI-opdrachttekenreeksen zijn aanwezig in alle i18n-localebestanden | Ja | +| `check:openapi-coverage` | De OpenAPI-specificatie dekt ten minste een stapsgewijs aangescherpte ondergrens van de werkelijke routes | Ja | +| `check:openapi-security-tiers` | Beveiligingsniveau-annotaties in `openapi.yaml` zijn consistent met de classificaties in `routeGuard.ts` | **Adviserend** | +| `check:openapi-routes` | Elk pad in `openapi.yaml` verwijst naar een bestaande `route.ts` (anti-hallucinatie) | Ja | +| `check:docs-symbols` | Elke `/api/...`-verwijzing in `docs/**/*.md` verwijst naar een bestaande `route.ts` (anti-hallucinatie) | Ja | +| `i18n translation drift` | Onvertaalde sleutels in i18n-localebestanden — alleen waarschuwen | **Adviserend** | ### Taak: `i18n-ui-coverage` -| Script | Valideert | Blokkerend | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (inline) | De dekking van UI-i18n-sleutels is ≥ 65% | Ja | -| `check-ui-value-drift` (inline) | Een herschreven Engelse **waarde** laat geen verouderde vertaling achter | Ja | -| `check-new-key-coverage` (inline) | Een **nieuwe** Engelse sleutel is in elke locale vertaald — een `__MISSING__:`-markering wordt afgewezen | Ja | -| `check-translation-ratio` | De verhouding van echte vertalingen per locale (bladen die identiek zijn aan het Engels / placeholders bevatten / ontbreken en buiten de toelatingslijst vallen) mag `config/quality/i18n-translation-baseline.json` + marge niet overschrijden | **Adviserend** | +| Script | Valideert | Blokkerend | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (inline) | De dekking van i18n-sleutels in de UI is ≥ 65% | Ja | +| `check-ui-value-drift` (inline) | Een herschreven Engelse **waarde** laat geen verouderde vertaling achter | Ja | +| `check-new-key-coverage` (inline) | Een **nieuwe** Engelse sleutel is in elke locale vertaald — een `__MISSING__:`-markering wordt afgewezen | Ja | +| `check-translation-ratio` | De verhouding echte vertalingen per locale (bladen die identiek zijn aan het Engels, placeholders bevatten of ontbreken en niet op de uitzonderingslijst staan) mag `config/quality/i18n-translation-baseline.json` + marge niet overschrijden | **Adviserend** | -Vereist `fetch-depth: 0` — de waardeafwijkingscontrole vergelijkt `en.json` met de merge-base. +Vereist `fetch-depth: 0` — de waardeafwijkingspoort vergelijkt `en.json` met de samenvoegbasis. -#### `check-ui-value-drift` — controle op verouderde vertalingen +#### `check-ui-value-drift` — poort voor verouderde vertalingen -Detecteert de ene i18n-regressie die de andere controles structureel niet kunnen zien: een Engelse waarde -wordt herschreven en de vertalingen die van het _vorige_ Engels zijn afgeleid, blijven staan, waardoor -niet-Engelstalige gebruikers zelfverzekerd geformuleerde, maar inmiddels onjuiste tekst blijven lezen. +Detecteert de ene i18n-regressie die de andere poorten structureel niet kunnen zien: een Engelse waarde +wordt herschreven terwijl de vertalingen die van de _vorige_ Engelse tekst zijn afgeleid, behouden blijven, waardoor +niet-Engelstalige gebruikers overtuigend geformuleerde, maar inmiddels onjuiste tekst blijven lezen. -Dit is daadwerkelijk in productie terechtgekomen. `oauthModal.googleOAuthWarning` werd herschreven toen de Antigravity- -aanmeldhulp werd toegevoegd (#5203); **39 van de 43 locales** behielden tekst die operators vertelde om "de +Dit is daadwerkelijk in productie uitgebracht. `oauthModal.googleOAuthWarning` werd herschreven toen de Antigravity- +inloghelper werd toegevoegd (#5203); **39 van de 43 locales** behielden tekst die operators opdroeg om "de volledige URL te kopiëren en hieronder te plakken" — een proces dat voor die provider niet kan worden voltooid. Dit bleef onopgemerkt tot #8463, omdat: -- `sync-ui-keys` alleen sleutels aanvult die **afwezig** zijn, nooit sleutels die **verouderd** zijn; +- `sync-ui-keys` alleen sleutels aanvult die **ontbreken**, nooit sleutels die **verouderd** zijn; - `check-ui-keys-coverage` de _aanwezigheid_ van sleutels telt, waardoor een verouderde vertaling als gedekt wordt beschouwd; -- `check-translation-drift` de documentatiespiegels in `docs/i18n//**.md` volgt — +- `check-translation-drift` de documentatiespiegels in `docs/i18n//**.md` bijhoudt — deze controle leest nooit `src/i18n/messages/*.json`. Blokkerend in taak `docs-sync-strict` sinds de - hersynchronisatie van 2026-09: bewerk een kerndocument → `npm run i18n:run -- --files=` (op sectieniveau, goedkoop). + hersynchronisatie van 2026-09: bewerk een kerndocument → `npm run i18n:run -- --files=` (per sectie, snel). **Diff-bewust, niet ondersteund door een baseline.** Dit vergelijkt `en.json` op de merge-base met de -werkboom; voor elke sleutel waarvan de Engelse waarde is gewijzigd, is elke locale die nog -een ongewijzigde vertaling bevat verouderd. Dit **bevriest bewust bestaande achterstand** — een diff -kan niet onthullen van welke oude Engelse tekst een al lang bestaande vertaling afkomstig is, dus de controle beoordeelt -alleen wat door de huidige wijziging wordt geraakt. Het alternatief (een baseline met een hash per sleutel) zou +working tree; voor elke sleutel waarvan de Engelse waarde is gewijzigd, is elke locale die nog een +ongewijzigde vertaling bevat verouderd. Hiermee wordt bewust **bestaande achterstand bevroren** — een diff +kan niet aantonen van welke oude Engelse tekst een reeds lang bestaande vertaling afkomstig is, dus de gate beoordeelt +alleen wat door de huidige wijziging wordt geraakt. Het alternatief (een hashbaseline per sleutel) zou een gegenereerd bestand van ~600 KB vereisen, 3× zo groot als de grootste bestaande baseline, dat bij elke i18n-PR wijzigt. -Er zijn twee manieren om eraan te voldoen: +Er zijn twee manieren om hieraan te voldoen: 1. werk de betreffende vertalingen bij, of -2. stel ze in op `__MISSING__:` — de runtime levert dan de gecorrigeerde Engelse tekst - (`src/i18n/request.ts::deepMergeFallback`, #7258) en zet de sleutel in de wachtrij voor vertaling. +2. stel ze in op `__MISSING__:` — de runtime levert dan de gecorrigeerde Engelse tekst + (`src/i18n/request.ts::deepMergeFallback`, #7258) en de sleutel wordt in de wachtrij voor vertaling geplaatst. -Als de **betekenis** van de tekenreeks is gewijzigd, geef dan de voorkeur aan **het hernoemen van de sleutel**: een nieuwe sleutel kan geen +Als de **betekenis** van de tekenreeks is gewijzigd, geef dan bij voorkeur **de sleutel een nieuwe naam**: een nieuwe sleutel kan geen verouderde vertaling overnemen. Dat is het patroon dat in #8463 is gebruikt. ```bash @@ -231,48 +235,48 @@ npm run i18n:check-value-drift:warn # alleen rapporteren BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Eindigt met code 0 en `SKIP reason=base-unresolved` wanneer de basiscatalogus niet kan worden gelezen (ondiepe -clone zonder de basisreferentie), overeenkomstig `check-openapi-breaking`. +Sluit af met 0 en `SKIP reason=base-unresolved` wanneer de basiscatalogus niet kan worden gelezen (ondiepe +clone zonder de base-ref), naar analogie van `check-openapi-breaking`. -### Taak: `i18n` +### Job: `i18n` -Volledige i18n-validatiematrix (één taak per locale). De volledige taak is adviserend. +Volledige i18n-validatiematrix (één job per locale). De volledige job is adviserend. -| Script | Valideert | Blokkerend | -| ------------------------------- | --------------------------------------- | ----------------------------------------------------------------- | -| `validate_translation.py quick` | Volledigheid van vertalingen per locale | **Adviserend** (`continue-on-error: true` voor de volledige taak) | +| Script | Valideert | Blokkerend | +| ------------------------------- | --------------------------------------- | ---------------------------------------------------------------- | +| `validate_translation.py quick` | Volledigheid van vertalingen per locale | **Adviserend** (`continue-on-error: true` voor de volledige job) | -### Taak: `pr-test-policy` +### Job: `pr-test-policy` Wordt alleen uitgevoerd voor pull requests. -| Script | Valideert | Blokkerend | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check:pr-test-policy` | PR's die productiecode in `src/`, `open-sse/`, `electron/` of `bin/` wijzigen, moeten tests toevoegen of bijwerken (Harde regel #8) | Ja | -| `check:test-masking` | Gewijzigde testbestanden verlagen het nettoaantal asserts niet en voegen geen `assert.ok(true)`-tautologieën toe | Ja | -| `check:pr-evidence` | De PR-beschrijving vermeldt bewijs van tests/VPS voor de wijziging (automatiseert Harde regel #18 door PR-tekst te doorzoeken — kwetsbaar, zie Backlog) | Ja | +| Script | Valideert | Blokkerend | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | +| `check:pr-test-policy` | PR's die productiecode wijzigen in `src/`, `open-sse/`, `electron/` of `bin/`, moeten tests toevoegen of bijwerken (harde regel #8) | Ja | +| `check:test-masking` | Gewijzigde testbestanden verlagen het nettoaantal assertions niet en voegen geen `assert.ok(true)`-tautologieën toe | Ja | +| `check:pr-evidence` | De PR-beschrijving vermeldt test-/VPS-bewijs voor de wijziging (automatiseert harde regel #18 door de PR-tekst te doorzoeken — kwetsbaar, zie Backlog) | Ja | -### Taak: `test-vitest` +### Job: `test-vitest` -Wordt uitgevoerd na `build`. Blokkeert samenvoegen bij fouten. +Wordt uitgevoerd na `build`. Blokkeert samenvoegen bij een fout. -| Suite | Valideert | Blokkerend | -| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP-server (110 tools), autoCombo, cache — vitest-runner | Ja | -| `test:vitest:ui` | UI-componenttests — vitest-runner | **Blokkerend** — reeds bestaande fouten zijn expliciet uitgesloten in `vitest.config.ts`; nieuwe fouten laten de taak mislukken | +| Suite | Valideert | Blokkerend | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | MCP-server (110 tools), autoCombo, cache — vitest-runner | Ja | +| `test:vitest:ui` | UI-componenttests — vitest-runner | **Blokkerend** — reeds bestaande fouten zijn expliciet uitgesloten in `vitest.config.ts`; nieuwe fouten laten de job mislukken | ### Nachtelijke workflows (gepland, adviserend) Deze worden volgens een cron-schema uitgevoerd (en via `workflow_dispatch`), nooit voor PR's. Ze zijn allemaal adviserend. -| Workflow | Valideert | Blokkerend | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | fast-check-eigenschapstests met een willekeurige seed en een hoog aantal uitvoeringen | **Adviserend** | -| `nightly-resilience` | controle op heapgroei, chaos-foutinjectie, k6-belastings-/duurtest | **Adviserend** | -| `nightly-llm-security` | promptfoo-injectiebeveiliging (blokkeermodus) + garak-probes (overgeslagen zonder providergeheim) | **Adviserend** | -| `nightly-schemathesis` | OpenAPI-contractfuzzing (schemathesis) tegen een actieve OmniRoute met `docs/openapi.yaml` — brengt specificatieschendingen / niet-afgehandelde 500-fouten aan het licht (Fase 8 B.4) | **Adviserend** | -| `nightly-mutation` | Stryker-mutatie-testscore voor de snelle unit-lane — overlevende mutanten brengen zwakke asserts aan het licht | **Adviserend** | -| `nightly-compat` | Compatibiliteitsmatrix voor de Node-engine voor de ondersteunde `engines.node`-bereiken | **Adviserend** | +| Workflow | Valideert | Blokkerend | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | fast-check-propertytests met een willekeurige seed en een hoog aantal runs | **Adviserend** | +| `nightly-resilience` | gate voor heapgroei, chaos-foutinjectie, k6-load-/soaktests | **Adviserend** | +| `nightly-llm-security` | promptfoo-injectiebeveiliging (blokkeermodus) + garak-probes (overgeslagen zonder providersecret) | **Adviserend** | +| `nightly-schemathesis` | OpenAPI-contractfuzzing (schemathesis) tegen een live OmniRoute met `docs/openapi.yaml` — brengt specificatieschendingen/onafgehandelde 500-fouten aan het licht (fase 8 B.4) | **Adviserend** | +| `nightly-mutation` | Stryker-mutatietestscore voor de snelle unit-lane — overlevende mutanten leggen zwakke assertions bloot | **Adviserend** | +| `nightly-compat` | Compatibiliteitsmatrix voor de Node-engine over de ondersteunde `engines.node`-bereiken | **Adviserend** | --- @@ -543,37 +547,37 @@ Deze inventaris is op 2026-06-17 afgestemd op `ci.yml` (in de vorige versie ontb `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Een ROI-beoordeling van de afgestemde set -bracht de volgende kandidaten voor rationalisatie aan het licht. **De samenvoegingen zijn mechanische CI- -wijzigingen; de omzettingen/verwijderingen zijn beleidsbeslissingen die aan de operator zijn voorbehouden.** Niets hieronder +heeft de volgende kandidaten voor rationalisatie opgeleverd. **De samenvoegingen zijn mechanische CI- +wijzigingen; de omschakelingen/verwijderingen zijn beleidsbeslissingen die aan de operator zijn voorbehouden.** Niets hieronder is al toegepast. -**Hierboven ook niet gedocumenteerd** (adviserend, zwak signaal): de `docs-lint`-job -(markdownlint + Vale, de volledige job met `continue-on-error`) en de zelfstandige scannerworkflows +**Hierboven eveneens niet gedocumenteerd** (adviserend, weinig signaal): de `docs-lint`-job +(markdownlint + Vale, volledige job met `continue-on-error`) en de zelfstandige scannerworkflows `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` staat in `quality-baseline.json`, maar is niet gekoppeld aan een blokkerende ratchet in `ci.yml` — de metriek is momenteel verweesd. ### Samenvoegen / dedupliceren (mechanisch, lager risico) -Elke kandidaat is op 2026-06-17 gevalideerd tegen de actuele status van de gates (vertrouw, maar verifieer); -verschillende „voor de hand liggende” samenvoegingen bleken verborgen technische schuld te bevatten en zijn **geen** directe vervangingen. +Elke kandidaat is op 2026-06-17 gevalideerd aan de hand van de actuele gatestatus (vertrouwen-maar-verifiëren); +verscheidene „voor de hand liggende” samenvoegingen bleken verborgen achterstand te bevatten en zijn **geen** probleemloze vervangingen. - **`check:docs-sync` wordt tweemaal uitgevoerd** — zelfstandig in de `lint`-job en opnieuw binnen `check:docs-all` (`docs-sync-strict`) en de husky-pre-commithook. ✅ **VOLTOOID** — zelfstandige aanroep in `lint` verwijderd. -- **CVE-scanning** — ❌ **GEEN zuivere samenvoeging.** `audit:deps` faalt hard bij elke CVE met hoge/kritieke ernst; `check:vuln-ratchet` (osv) faalt alleen bij een _regressie_ ten opzichte van de baseline (momenteel 1 MODERATE). Verschillende semantiek — door `audit:deps` te verwijderen zou de absolute gate voor hoge/kritieke ernst verloren gaan. Behoud beide. -- **Cyclusdetectie** — ❌ **GEEN zuivere samenvoeging.** `check:circular-deps` (dpdm) rapporteert **91 cycli** (daarom is deze adviserend); deze kan niet blokkerend worden gemaakt zonder ze eerst op te lossen en heeft een breder bereik dan de groene, zorgvuldig samengestelde `check:cycles`. Behoud `check:cycles` als blokkerend; het oplossen van de 91 dpdm-cycli vormt een afzonderlijke backlog. -- **Complexiteit** — ✅ **VOLTOOID** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): één ESLint-scan, telt per ruleId zodat de baselines voor cyclomatische complexiteit + maximaal aantal regels en cognitieve complexiteit onafhankelijk blijven; afzonderlijke `check:complexity` / `check:cognitive-complexity` blijven behouden voor lokale `--update`. -- **`/api`-antihallucinatie** — ✅ **VOLTOOID** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): één bestandssysteeminventarisatie van `src/app/api`; openapi-routes + docs-symbols blijven onafhankelijk rapporteren; afzonderlijke controles blijven behouden voor lokale uitvoeringen. -- **`check:node-runtime` wordt in 11 jobs uitgevoerd** — ⚠️ **lage ROI.** Elke job gebruikt een afzonderlijke runner en de controle duurt <1 s; totale besparing ~10 s, tegenover het verlies van een goedkope beveiliging per job. De verstoring niet waard. -- **`typecheck:noimplicit:core` bij CI-linting** — ✅ **verwijderd uit de lint-job** (was adviserend met `continue-on-error`); het blokkerende type-oppervlak bestaat uit `typecheck:core` + `check:type-coverage`. Lokaal script behouden. - -### Omzetten / beslissen (operatorbeleid) - -- `check:openapi-security-tiers` (adviserend) — ❌ **NIET zonder meer om te zetten.** Deze eindigt met 0, maar waarschuwt dat meerdere `traffic-inspector`-routes onder `LOCAL_ONLY_API_PREFIXES` de annotatie `x-loopback-only: true` missen. Handhaving vereist dat die annotaties eerst aan `openapi.yaml` worden toegevoegd. -- `typecheck:noimplicit:core` (adviserend) — grotendeels afgedekt door de blokkerende `check:type-coverage`-ratchet. Zet deze om naar een ratchet of verwijder de redundante tweede `tsc`-doorgang. -- `test:vitest:ui` (nu **blokkerend**) — reeds bestaande fouten zijn expliciet uitgesloten in `vitest.config.ts` met `// #8618`-trackingopmerkingen; nieuwe fouten laten de job mislukken. -- `check:secrets` (gitleaks, blokkerende ratchet bevroren op 3 gedocumenteerde fout-positieven) — voeg de 3 toe aan de allowlist om 0 te bereiken, of degradeer deze naar adviserend. Overlapt met de ingebouwde secret-scanning van GitHub + `check:public-creds`. -- `check:pr-evidence` (blokkerend, doorzoekt de prozatekst van PR-beschrijvingen) — hoog risico op fout-positieven; verwijdering verzwakt de handhaving van Hard Rule #18, dus dit is een echte beleidsbeslissing. -- `semgrep` (zelfstandig, adviserend) — overlapt met CodeQL voor de OWASP-categorieën; koppel de baseline aan een ratchet of verwijder deze. +- **CVE-scanning** — ❌ **GEEN probleemloze samenvoeging.** `audit:deps` faalt hard bij elke hoge/kritieke CVE; `check:vuln-ratchet` (osv) faalt alleen bij een _regressie_ ten opzichte van de baseline (momenteel 1 MODERATE). Verschillende semantieken — door `audit:deps` te verwijderen zou de absolute gate voor hoge/kritieke kwetsbaarheden verloren gaan. Beide behouden. +- **Cyclusdetectie** — ✅ **VOLTOOID** (#15159 G-01/G-02). De oude tekst noemde `check:cycles` de „groene, gecureerde” gate en rechtvaardigde het blokkerend houden ervan omdat `check:circular-deps` (dpdm) 91 cycli rapporteerde. Dat groen was **vals groen**: `check:cycles` scande 5 submappen (450 bestanden), herkende alleen statische `import|export … from` en negeerde elke `@/`- en `@omniroute/open-sse/`-specifier, waardoor de door dynamische imports en aliassen gedomineerde cycli in de repo niet zichtbaar waren. Opgelost: de gate doorloopt nu `src` + `open-sse` (5023 bestanden), verzamelt specifiers uit de TypeScript-AST (waardoor `import("…")` meetelt en `typeof import("…")` in een typepositie niet) en verwerkt de `paths` uit tsconfig. De gate vindt **14** cycli, niet 0. Omdat 14 bestaande cycli niet in een gate-PR kunnen worden opgelost, is `check:cycles` nu een **ratchet** (`--ratchet`, plafond `metrics.cycles.value = 14` in `quality-baseline.json`, `direction: down`) — deze blokkeert elke _regressie_ en het aantal kan alleen dalen. CI voert `npm run check:cycles:ratchet` uit. De afbouw loopt mee met **A-01**. `check:circular-deps` (dpdm) blijft adviserend als bredere second opinion. +- **Complexiteit** — ✅ **VOLTOOID** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): één ESLint-doorloop, tellingen per ruleId zodat de cyclomatische+max-lines- en cognitieve baselines onafhankelijk blijven; de afzonderlijke `check:complexity` / `check:cognitive-complexity` blijven beschikbaar voor lokaal gebruik met `--update`. +- **`/api`-anti-hallucinatie** — ✅ **VOLTOOID** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): één FS-inventarisatie van `src/app/api`; openapi-routes + docs-symbols blijven onafhankelijk rapporteren; de afzonderlijke controles blijven beschikbaar voor lokale uitvoeringen. +- **`check:node-runtime` wordt in 11 jobs uitgevoerd** — ⚠️ **lage ROI.** Elke job gebruikt een afzonderlijke runner en de controle duurt <1s; totale besparing ~10s, ten koste van een goedkope beveiliging per job. De verstoring niet waard. +- **`typecheck:noimplicit:core` in CI-lint** — ✅ **verwijderd uit de lint-job** (was adviserend met `continue-on-error`); het blokkerende typeoppervlak bestaat uit `typecheck:core` + `check:type-coverage`. Lokaal script behouden. + +### Omschakelen / beslissen (operatorbeleid) + +- `check:openapi-security-tiers` (adviserend) — ❌ **NIET probleemloos omschakelbaar.** Deze eindigt met 0, maar waarschuwt dat meerdere `traffic-inspector`-routes onder `LOCAL_ONLY_API_PREFIXES` de annotatie `x-loopback-only: true` missen. Afdwinging vereist dat die annotaties eerst aan `openapi.yaml` worden toegevoegd. +- `typecheck:noimplicit:core` (adviserend) — grotendeels afgedekt door de blokkerende `check:type-coverage`-ratchet. Schakel om naar een ratchet of verwijder de redundante tweede `tsc`-doorloop. +- `test:vitest:ui` (nu **blokkerend**) — bestaande fouten zijn expliciet uitgesloten in `vitest.config.ts` met `// #8618`-trackingopmerkingen; nieuwe fouten laten de job mislukken. +- `check:secrets` (gitleaks, blokkerende ratchet vastgezet op 3 gedocumenteerde foutpositieven) — zet de 3 op de allowlist om 0 te bereiken, of degradeer de controle tot adviserend. Overlapt met de ingebouwde secret-scanning van GitHub + `check:public-creds`. +- `check:pr-evidence` (blokkerend, doorzoekt de prozatekst van de PR-body met grep) — hoog risico op foutpositieven; verwijdering verzwakt de handhaving van Hard Rule #18, dus dit is een echte beleidsbeslissing. +- `semgrep` (zelfstandig adviserend) — overlapt met CodeQL voor de OWASP-categorieën; koppel de baseline aan een ratchet of verwijder de controle. --- diff --git a/docs/i18n/nl/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/nl/docs/compression/COMPRESSION_GUIDE.md index 92c75455ad90..75a9f4281ab1 100644 --- a/docs/i18n/nl/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/nl/docs/compression/COMPRESSION_GUIDE.md @@ -470,8 +470,7 @@ geïnjecteerd in plaats van de Engelse. Bij een body met `messages` controleert een inhoudsbypass (`shouldBypassCavemanOutputMode()` in `open-sse/services/compression/outputMode.ts`) de laatste drie berichten en slaat de stijlen voor de hele beurt over wanneer deze overeenkomen met trefwoorden voor beveiliging, -onomkeerbare acties, verduidelijking of volgordegevoeligheid. De bypass wordt uitgevoerd volgens de instelling van -de schakelaar **Automatische duidelijkheidsbypass** (`cavemanOutputMode.autoClarity`) in het dashboard. +onomkeerbare acties, verduidelijking of volgordegevoeligheid. De bypass wordt uitgevoerd zolang de schakelaar **Automatische duidelijkheidsbypass** (`cavemanOutputMode.autoClarity`) in het dashboard aan staat, wat de standaard is; met de schakelaar uit gelden de gekozen stijlen ook voor die beurten. Wanneer de bypass de beurt doorlaat, plaatst `placeSystemInstruction()` (hetzelfde bestand), dat nooit een nieuwe `messages[0]` aanmaakt, het blok op de eerste van deze gevonden locaties: diff --git a/docs/i18n/nl/docs/reference/ENVIRONMENT.md b/docs/i18n/nl/docs/reference/ENVIRONMENT.md index 009ed5a9a4f2..888b7eea630a 100644 --- a/docs/i18n/nl/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/nl/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Vereiste geheimen -Deze **moeten** vóór de eerste uitvoering worden ingesteld. Zonder deze geheimen weigert de applicatie te starten of gebruikt deze onveilige standaardwaarden. - -| Variabele | Vereist | Standaardwaarde | Bronbestand | Beschrijving | -| ---------------------------- | ------------------ | ------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ja** | _(geen)_ | `src/lib/auth` | Ondertekent/verifieert alle sessiecookies van het dashboard (JWT). Genereer met `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ja** | _(geen)_ | `src/lib/db/apiKeys.ts` | AES-versleutelingssleutel voor versleutelde opslag van API-sleutelwaarden in SQLite. Genereer met `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ja** | `CHANGEME` | Bootstrap-script | Stelt het initiële beheerderswachtwoord voor het dashboard in (komt overeen met de standaardwaarde in `.env.example` — bewust duidelijk onveilig gehouden om een wijziging af te dwingen). **Wijzig dit vóór het eerste gebruik.** Wijzig het na het aanmelden via Dashboard → Instellingen → Beveiliging. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (productie) | _(niet ingesteld)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Gedeeld geheim voor de interne WebSocket-bridge van Codex Responses. Verifieert bridgeverzoeken tussen de WS-relay van Electron/de browser en OmniRoute. ⚠️ **VEREIST in productie — wanneer dit niet is ingesteld, worden alle WS-bridgeverzoeken geweigerd.** Genereer met `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nee | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Expliciete cache-busting-id van de serviceworker voor de PWA-shell (#11779); de eerste in de resolutieketen. | -| `SOURCE_VERSION` | Nee | _(niet ingesteld)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | De tweede in de keten — ingesteld door PaaS-buildsystemen (bijvoorbeeld in Heroku-stijl) als de geïmplementeerde commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nee | _(afgeleid)_ | `src/shared/components/PwaRegister.tsx` | Openbare waarde tijdens het bouwen die de client gebruikt om `/sw.js?v=…` te registreren; afgeleid van de twee bovenstaande waarden en vervolgens van de git-SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nee (automatisch) | _(automatisch per start)_ | `src/server/authz/policies/management.ts` | Geheim per proces dat bewijst dat het vertrouwde peer-IP-stempel afkomstig is van de eigen HTTP-server van OmniRoute (`scripts/dev/peer-stamp.mjs`). De authz-middleware vertrouwt alleen op de lokale oorsprong van het verzoek (loopback-/LAN-beperking van LOCAL_ONLY-routes) wanneer het stempel dit token bevat. Wordt bij elke start automatisch gegenereerd — laat dit niet ingesteld; stel het alleen vast in configuraties met meerdere processen die het stempel moeten delen. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nee | _(niet ingesteld)_ | `scripts/dev/peer-stamp.mjs` | Door komma's gescheiden IP-adressen of CIDR-bereiken van reverse proxy's waarvan de IP-toestaan/weigeren-lijst `X-Forwarded-For` / `X-Real-IP` mag vertrouwen, naast loopback, adressen van privénetwerken en Cloudflare-edges, die altijd worden vertrouwd. Alleen nodig voor een proxy op een ander openbaar adres; anders beoordeelt het filter het eigen adres van die proxy. | +Niet-ingestelde geheimen die zijn gemarkeerd met **Ja** worden bij de eerste uitvoering automatisch gegenereerd (`JWT_SECRET`, `API_KEY_SECRET`) en bewaard in `server.env` in de datamap — stel ze expliciet in wanneer die map tijdelijk is. De uitzondering is `OMNIROUTE_WS_BRIDGE_SECRET`: dit wordt niet bewaard in `server.env` — de server-entrypoints genereren bij elke boot een tijdelijke waarde die alleen door de in-process bridge kan worden gebruikt, dus externe bridge-clients moeten dit expliciet instellen. + +| Variabele | Vereist | Standaardwaarde | Bronbestand | Beschrijving | +| ---------------------------- | ------------------ | ------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ja** | _(geen)_ | `src/lib/auth` | Ondertekent/verifieert alle sessiecookies van het dashboard (JWT). Genereer met `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ja** | _(geen)_ | `src/lib/db/apiKeys.ts` | AES-versleutelingssleutel voor versleutelde opslag van API-sleutelwaarden in SQLite. Genereer met `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nee | _(niet ingesteld)_ | `src/lib/auth/managementPassword.ts` | Stelt het dashboardwachtwoord in bij het opstarten zolang er nog geen wachtwoord is opgeslagen; wordt genegeerd zodra een wachtwoord is opgeslagen. npm- en broninstallaties kopiëren `.env.example` naar `.env`, dus ze starten met de publiek bekende placeholder `CHANGEME` daaruit: vervang deze vóór de eerste boot, of stel `INITIAL_PASSWORD=` (leeg) in dat `.env`-bestand om het wachtwoord te creëren in de onboardingwizard bij het eerste bezoek, zoals een niet-ingestelde waarde (de standaard van het Docker-image) doet. Wijzig een opgeslagen wachtwoord via Dashboard → Instellingen → Beveiliging, of voer `omniroute reset-password` uit (`node bin/reset-password.mjs` in een broncheckout) met `DATA_DIR` ingesteld op de datamap van de server. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (productie) | _(niet ingesteld)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Gedeeld geheim voor de interne WebSocket-bridge van Codex Responses. Verifieert bridgeverzoeken tussen de WS-relay van Electron/de browser en OmniRoute. ⚠️ **VEREIST in productie — wanneer dit niet is ingesteld, worden alle WS-bridgeverzoeken geweigerd.** Genereer met `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nee | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Expliciete cache-busting-id van de serviceworker voor de PWA-shell (#11779); de eerste in de resolutieketen. | +| `SOURCE_VERSION` | Nee | _(niet ingesteld)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | De tweede in de keten — ingesteld door PaaS-buildsystemen (bijvoorbeeld in Heroku-stijl) als de geïmplementeerde commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nee | _(afgeleid)_ | `src/shared/components/PwaRegister.tsx` | Openbare waarde tijdens het bouwen die de client gebruikt om `/sw.js?v=…` te registreren; afgeleid van de twee bovenstaande waarden en vervolgens van de git-SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nee (automatisch) | _(automatisch per start)_ | `src/server/authz/policies/management.ts` | Geheim per proces dat bewijst dat het vertrouwde peer-IP-stempel afkomstig is van de eigen HTTP-server van OmniRoute (`scripts/dev/peer-stamp.mjs`). De authz-middleware vertrouwt alleen op de lokale oorsprong van het verzoek (loopback-/LAN-beperking van LOCAL_ONLY-routes) wanneer het stempel dit token bevat. Wordt bij elke start automatisch gegenereerd — laat dit niet ingesteld; stel het alleen vast in configuraties met meerdere processen die het stempel moeten delen. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nee | _(niet ingesteld)_ | `scripts/dev/peer-stamp.mjs` | Door komma's gescheiden IP-adressen of CIDR-bereiken van reverse proxy's waarvan de IP-toestaan/weigeren-lijst `X-Forwarded-For` / `X-Real-IP` mag vertrouwen, naast loopback, adressen van privénetwerken en Cloudflare-edges, die altijd worden vertrouwd. Alleen nodig voor een proxy op een ander openbaar adres; anders beoordeelt het filter het eigen adres van die proxy. | ### Generatieopdrachten @@ -217,38 +217,38 @@ OmniRoute gebruikt **SQLite** (via `better-sqlite3`) voor alle persistente opsla ## 4. Beveiliging & authenticatie -| Variabele | Standaardwaarde | Bronbestand | Beschrijving | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt die wordt gecombineerd met hardware-id's om een machinevingerafdruk te maken. Wijzig deze per implementatie voor isolatie. | -| `OMNIROUTE_CLI_SALT` | _(niet ingesteld = willekeurige salt per installatie, opgeslagen in `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt voor het afleiden van het lokale CLI-authenticatietoken. Door deze waarde in te stellen, worden alle CLI-tokens op de machine vernieuwd en krijgt deze altijd voorrang op de opgeslagen salt. Zie `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Stelt de vlag `Secure` in voor sessiecookies. **Moet `true` zijn** wanneer de toepassing achter HTTPS draait. | -| `REQUIRE_API_KEY` | `false` | API-middleware | Wanneer deze waarde `true` is, moeten alle `/v1/*`-proxyaanvragen een geldige API-sleutel bevatten. **Deze vlag schermt `GET /v1/models` niet af**; dit endpoint volgt in plaats daarvan de aanmeldingsconfiguratie van het dashboard (`requireAuthForModels`) — een `401` van `/v1/models` betekent dus NIET dat inferentie is beveiligd. Zie `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Maakt het mogelijk om volledige API-sleutelwaarden in de dashboardinterface weer te geven. Configureerbaar via de functievlaggen van het dashboard; vormt een beveiligingsrisico op gedeelde instanties. | -| `NO_LOG_API_KEY_IDS` | _(leeg)_ | `src/lib/compliance/index.ts` | Door komma's gescheiden API-sleutel-id's waarvoor aanvraaglogboekregistratie wordt overgeslagen (AVG-naleving). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(niet ingesteld = onbeperkt)_ | `src/shared/utils/apiKeyPolicy.ts` | Alternatief dagelijks aanvraagbudget dat wordt toegepast op API-sleutels waarvan de kolom `rate_limits` null is. Niet ingesteld of leeg: geen impliciete limiet (#2289, #11017). `0` betekent hetzelfde (onbeperkt). Een positief geheel getal N maakt N/dag, 5N/week en 20N/maand mogelijk. Bij ongeldige niet-lege waarden wordt teruggevallen op de verouderde vensters van 1000/dag, 5000/week en 20000/maand. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximaal toegestane grootte van de aanvraagbody. Payloads die deze limiet overschrijden, worden geweigerd. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Werkelijke aanvraagbody's die gelijk zijn aan of groter zijn dan deze drempelwaarde, verkrijgen vóór het parsen van JSON de atomaire, proceslokale toelatingslease voor zware aanvragen (BYTE-pad, inclusief `POST /v1/responses`). Dezelfde [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-uitweg bij voldoende gezonde reservecapaciteit als voor structuurzware aanvragen; nog steeds begrensd door `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), zodat [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) niet terugkeert. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Harde limiet voor chatroutes, afgedwongen op basis van het aantal bytes dat tijdens begrensde inlezing wordt gelezen, inclusief verzoeken met een ontbrekende, ongeldige of misleidende `Content-Length`; overschrijding resulteert in `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(niet ingesteld — geen limiet voor het aantal verzoeken)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** deze verouderde limiet voor het AANTAL verzoeken geldt nu alleen wanneer deze expliciet is ingesteld. Als deze niet is ingesteld (de standaardinstelling), wordt toelating van zware chatverzoeken in plaats daarvan beperkt door `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — een automatisch afgeleid BYTE-budget dat is afgestemd op de werkelijke geheugenlimiet van het proces in **één proces** (één V8-heap). Twee overlappende `/v1/responses`-verzoeken van ~750k tokens laten heaps van ~12 GiB vastlopen (#7849) — een waarschuwing over het **geheugenbudget**, geen harde productlimiet van 2. Een gezond proces (heap onder de afstootratio) MAG meer gelijktijdige lange `/v1/responses` toelaten via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Tientallen langdurige SSE-clients (40–50) worden bepaald door de heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, niet door „maximaal 2”. Dit blindelings verhogen om „de host te benutten” introduceert #7849 opnieuw. Vermenigvuldig **heaps** met **N onafhankelijke `DATA_DIR`'s** (#11024); gebruik nooit `replicas>1` met één SQLite-bestand. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatisch afgeleid)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** overschrijving voor het automatisch afgeleide bytebudget voor inlezing (25% van de strengste V8-/cgroup-geheugenlimiet, gedeeld door 8× tijdelijke amplificatie). Afgeleide en expliciete waarden worden begrensd tot 8 MiB–2 GiB. Een body die groter is dan het effectieve budget mislukt onmiddellijk met `413 body_exceeds_budget`; competitie tussen afzonderlijk verwerkbare bodies blijft opnieuw probeerbaar met `503`. 40–50 gelijktijdige langdurige SSE-clients worden bepaald door dit budget + de heap, niet door een harde limiet van „maximaal 2”. Lees `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` op `/api/monitoring/health` voordat u de configuratie afstemt. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Afstootratio voor heapdruk (`heapUsed / heap_size_limit`) bij toelating van zware BYTE- en STRUCTURE-verzoeken (#10183, #10268, #10437). Een gelijktijdig zwaar verzoek boven `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` wordt alleen afgestoten met de opnieuw probeerbare `503` wanneer de heap OOK op of boven deze ratio zit; bij een gezonde heap wordt het in plaats daarvan toegelaten via de gezonde-reservecapaciteit. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standaard `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begrensde extra capaciteit voor het snelle pad bij een gezonde heap (#10437), voor **zowel** STRUCTURE als BYTE (`admitChatRequest`, inclusief bodies ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Zonder deze begrenzing omzeilde elk verzoek bij drukte maar een gezonde heap de toelatingscontrole zonder bovengrens. Zodra dit aantal gelijktijdige leases actief is via het pad voor een gezonde heap, vallen verdere verzoeken bij drukte terug op HETZELFDE pad voor begrensd wachten/afstoten dat bij werkelijke heapdruk wordt gebruikt. `0` schakelt de omzeiling volledig uit. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Aantal berichten waarbij een chatverzoek als zwaar wordt geclassificeerd, zelfs wanneer de body onder de bytedrempel blijft. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Aantal tools waarbij een chatverzoek als zwaar wordt geclassificeerd, zelfs wanneer de body onder de bytedrempel blijft. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Conservatieve tokenschatting op basis van de tekenreeksgrootte waarmee een verzoek als zwaar wordt geclassificeerd; dit is een proxy voor toelatingskosten, geen tokenisering voor facturering door de provider. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (uitgeschakeld) | `src/shared/middleware/chatBodyAdmission.ts` | Optionele, expliciet in te schakelen limiet voor de chatgeschiedenis. Standaard uitgeschakeld: een berichtenaantal is implementatiebeleid, geen universele eigenschap van een verzoek, en een limiet hier wijst gesprekken definitief af met `413` voordat de compressiepijplijn ze verwerkbaar kan maken. Heapgroei wordt begrensd door `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` en het afstoten bij heapdruk. Stel een positieve waarde in voor implementaties met beperkt geheugen die een harde bovengrens nodig hebben; overschrijding resulteert dan in een gestructureerde `413` die aangeeft dat verkleining vereist is. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Harde limiet voor een niet-streamende upstreamrespons die volledig in het geheugen wordt gebufferd. Bij overschrijding wordt de upstreamlezer geannuleerd en mislukt het verzoek direct, in plaats van een onbeperkte tekenreeks te laten groeien totdat de heap is uitgeput. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximaal aantal wire-bytes dat vanuit upstream-responsheaders wordt doorgestuurd. Wanneer het budget wordt overschreden, worden headers met een lagere prioriteit (bijv. aangepaste `x-codex-*`, `x-oai-request-id`) verwijderd om binnen gangbare headerlimieten van reverse proxy's te blijven. Stel dit hoger in om meer upstream-metadata door te sturen, ten koste van grotere responsheaders. | -| `CORS_ORIGIN` | _(niet ingesteld)_ | `src/server/cors/origins.ts` | Verouderde CORS-toelatingslijst voor één origin. Gebruik bij nieuwe implementaties bij voorkeur `CORS_ALLOWED_ORIGINS`. CORS is alleen bedoeld voor browser-API-clients van een andere origin; geauthenticeerde schrijfbewerkingen in het dashboard gebruiken in plaats daarvan verzoeken van dezelfde origin plus sessiegebonden CSRF-bescherming. | -| `CORS_ALLOWED_ORIGINS` | _(niet ingesteld)_ | `src/server/cors/origins.ts` | Door komma's gescheiden CORS-toelatingslijst. Er wordt geen wildcard verzonden, tenzij `CORS_ALLOW_ALL=true` expliciet is geconfigureerd. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Alleen voor ontwikkeling bestemde uitweg om elke browser-`Origin` te retourneren. Schakel dit niet in bij gedeelde of productie-implementaties. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokkeer provideraanroepen die zijn gericht op privé-, loopback- of link-local-IP-bereiken. Schakel dit alleen uit in geïsoleerde testomgevingen. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Sta provider-URL's toe die verwijzen naar privé- of lokale netwerken (localhost, 192.168.x.x, 10.x.x.x, enz.). **VEREIST voor zelfgehoste providers** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Wanneer dit `false` is, weigert het dashboard de validatie van lokale URL's. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Sta het toevoegen/valideren van providers op lokale/privéadressen toe (127.0.0.1, localhost, LAN, privébereiken) — beperkt tot het providervalidatiepad. **Standaard `true`** (local-first); stel dit in op `false` om strikte blokkering tot uitsluitend openbare adressen af te dwingen. Cloudmetadata-eindpunten (169.254.169.254, metadata.google.internal) blijven hoe dan ook geblokkeerd. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Laat de `/v1/audio/*`-routes (transcripties, spraak, vertalingen) een OpenAI-compatibele providernode gebruiken die buiten localhost wordt gehost. Standaard uitgeschakeld — het routeren van audio naar een externe host wijzigt de egress-identiteit en moet een expliciete beslissing van de beheerder zijn. Loopback-/privénodes (localhost, 127.0.0.1, 172.16-31.x) zijn altijd toegestaan en worden hierdoor niet beïnvloed. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Laat `POST /v1/rerank` (en de loopback-rerankstap van de geheugenengine) een OpenAI-compatibele providernode gebruiken die buiten localhost wordt gehost — een machine op het LAN of een Tailscale-peer waarop TEI, Infinity, vLLM enz. draait. Standaard uitgeschakeld — routering naar een externe host wijzigt de egress-identiteit en moet een expliciete beslissing van de beheerder zijn. Loopback-nodes (localhost, 127.0.0.1, 172.16-31.x) zijn altijd toegestaan en worden hierdoor niet beïnvloed. Externe nodes moeten ook voldoen aan het beleid voor uitgaande provider-URL's (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); er wordt nooit naar cloudmetadata-hosts gerouteerd. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Wanneer OIDC is ingeschakeld, wordt aanmelden met een wachtwoord uitgeschakeld, zodat gebruikers zich alleen via OIDC Single Sign-On kunnen authenticeren. De kale alias `OIDC_DISABLE_PASSWORD_LOGIN` wordt ook geaccepteerd; de dashboardfunctievlag met dezelfde sleutel heeft voorrang. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kale alias van `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variabele | Standaardwaarde | Bronbestand | Beschrijving | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt die wordt gecombineerd met hardware-id's om een machinevingerafdruk te maken. Wijzig deze per implementatie voor isolatie. | +| `OMNIROUTE_CLI_SALT` | _(niet ingesteld = willekeurige salt per installatie, opgeslagen in `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt voor het afleiden van het lokale CLI-authenticatietoken. Door deze waarde in te stellen, worden alle CLI-tokens op de machine vernieuwd en krijgt deze altijd voorrang op de opgeslagen salt. Zie `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Stelt de vlag `Secure` in voor sessiecookies. **Moet `true` zijn** wanneer de toepassing achter HTTPS draait. | +| `REQUIRE_API_KEY` | `false` | API-middleware | Wanneer deze waarde `true` is, moeten alle `/v1/*`-proxyaanvragen een geldige API-sleutel bevatten. **Deze vlag schermt `GET /v1/models` niet af**; dit endpoint volgt in plaats daarvan de aanmeldingsconfiguratie van het dashboard (`requireAuthForModels`) — een `401` van `/v1/models` betekent dus NIET dat inferentie is beveiligd. Zie `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Maakt het mogelijk om volledige API-sleutelwaarden in de dashboardinterface weer te geven. Configureerbaar via de functievlaggen van het dashboard; vormt een beveiligingsrisico op gedeelde instanties. | +| `NO_LOG_API_KEY_IDS` | _(leeg)_ | `src/lib/compliance/index.ts` | Door komma's gescheiden API-sleutel-id's waarvoor aanvraaglogboekregistratie wordt overgeslagen (AVG-naleving). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(niet ingesteld = onbeperkt)_ | `src/shared/utils/apiKeyPolicy.ts` | Alternatief dagelijks aanvraagbudget dat wordt toegepast op API-sleutels waarvan de kolom `rate_limits` null is. Niet ingesteld of leeg: geen impliciete limiet (#2289, #11017). `0` betekent hetzelfde (onbeperkt). Een positief geheel getal N maakt N/dag, 5N/week en 20N/maand mogelijk. Bij ongeldige niet-lege waarden wordt teruggevallen op de verouderde vensters van 1000/dag, 5000/week en 20000/maand. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximaal toegestane grootte van de aanvraagbody. Payloads die deze limiet overschrijden, worden geweigerd. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Werkelijke aanvraagbody's die gelijk zijn aan of groter zijn dan deze drempelwaarde, verkrijgen vóór het parsen van JSON de atomaire, proceslokale toelatingslease voor zware aanvragen (BYTE-pad, inclusief `POST /v1/responses`). Dezelfde [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-uitweg bij voldoende gezonde reservecapaciteit als voor structuurzware aanvragen; nog steeds begrensd door `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), zodat [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) niet terugkeert. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Harde limiet voor chatroutes, afgedwongen op basis van het aantal bytes dat tijdens begrensde inlezing wordt gelezen, inclusief verzoeken met een ontbrekende, ongeldige of misleidende `Content-Length`; overschrijding resulteert in `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(niet ingesteld — geen limiet voor het aantal verzoeken)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** deze verouderde limiet voor het AANTAL verzoeken geldt nu alleen wanneer deze expliciet is ingesteld. Als deze niet is ingesteld (de standaardinstelling), wordt toelating van zware chatverzoeken in plaats daarvan beperkt door `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — een automatisch afgeleid BYTE-budget dat is afgestemd op de werkelijke geheugenlimiet van het proces in **één proces** (één V8-heap). Twee overlappende `/v1/responses`-verzoeken van ~750k tokens laten heaps van ~12 GiB vastlopen (#7849) — een waarschuwing over het **geheugenbudget**, geen harde productlimiet van 2. Een gezond proces (heap onder de afstootratio) MAG meer gelijktijdige lange `/v1/responses` toelaten via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Tientallen langdurige SSE-clients (40–50) worden bepaald door de heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, niet door „maximaal 2”. Dit blindelings verhogen om „de host te benutten” introduceert #7849 opnieuw. Vermenigvuldig **heaps** met **N onafhankelijke `DATA_DIR`'s** (#11024); gebruik nooit `replicas>1` met één SQLite-bestand. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatisch afgeleid)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** overschrijving voor het automatisch afgeleide bytebudget voor inlezing (25% van de strengste V8-/cgroup-geheugenlimiet, gedeeld door 8× tijdelijke amplificatie). Afgeleide en expliciete waarden worden begrensd tot 8 MiB–2 GiB. Een body die groter is dan het effectieve budget mislukt onmiddellijk met `413 body_exceeds_budget`; competitie tussen afzonderlijk verwerkbare bodies blijft opnieuw probeerbaar met `503`. 40–50 gelijktijdige langdurige SSE-clients worden bepaald door dit budget + de heap, niet door een harde limiet van „maximaal 2”. Lees `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` op `/api/monitoring/health` voordat u de configuratie afstemt. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Afstootratio voor heapdruk (`heapUsed / heap_size_limit`) bij toelating van zware BYTE- en STRUCTURE-verzoeken (#10183, #10268, #10437). Een gelijktijdig zwaar verzoek boven `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` wordt alleen afgestoten met de opnieuw probeerbare `503` wanneer de heap OOK op of boven deze ratio zit; bij een gezonde heap wordt het in plaats daarvan toegelaten via de gezonde-reservecapaciteit. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standaard `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begrensde extra capaciteit voor het snelle pad bij een gezonde heap (#10437), voor **zowel** STRUCTURE als BYTE (`admitChatRequest`, inclusief bodies ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Zonder deze begrenzing omzeilde elk verzoek bij drukte maar een gezonde heap de toelatingscontrole zonder bovengrens. Zodra dit aantal gelijktijdige leases actief is via het pad voor een gezonde heap, vallen verdere verzoeken bij drukte terug op HETZELFDE pad voor begrensd wachten/afstoten dat bij werkelijke heapdruk wordt gebruikt. `0` schakelt de omzeiling volledig uit. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Aantal berichten waarbij een chatverzoek als zwaar wordt geclassificeerd, zelfs wanneer de body onder de bytedrempel blijft. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Aantal tools waarbij een chatverzoek als zwaar wordt geclassificeerd, zelfs wanneer de body onder de bytedrempel blijft. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Conservatieve tokenschatting op basis van de tekenreeksgrootte waarmee een verzoek als zwaar wordt geclassificeerd; dit is een proxy voor toelatingskosten, geen tokenisering voor facturering door de provider. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (uitgeschakeld) | `src/shared/middleware/chatBodyAdmission.ts` | Optionele, expliciet in te schakelen limiet voor de chatgeschiedenis. Standaard uitgeschakeld: een berichtenaantal is implementatiebeleid, geen universele eigenschap van een verzoek, en een limiet hier wijst gesprekken definitief af met `413` voordat de compressiepijplijn ze verwerkbaar kan maken. Heapgroei wordt begrensd door `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` en het afstoten bij heapdruk. Stel een positieve waarde in voor implementaties met beperkt geheugen die een harde bovengrens nodig hebben; overschrijding resulteert dan in een gestructureerde `413` die aangeeft dat verkleining vereist is. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Harde limiet voor een niet-streamende upstreamrespons die volledig in het geheugen wordt gebufferd. Bij overschrijding wordt de upstreamlezer geannuleerd en mislukt het verzoek direct, in plaats van een onbeperkte tekenreeks te laten groeien totdat de heap is uitgeput. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximaal aantal wire-bytes dat vanuit upstream-responsheaders wordt doorgestuurd. Wanneer het budget wordt overschreden, worden headers met een lagere prioriteit (bijv. aangepaste `x-codex-*`, `x-oai-request-id`) verwijderd om binnen gangbare headerlimieten van reverse proxy's te blijven. Stel dit hoger in om meer upstream-metadata door te sturen, ten koste van grotere responsheaders. | +| `CORS_ORIGIN` | _(niet ingesteld)_ | `src/server/cors/origins.ts` | Verouderde CORS-toelatingslijst voor één origin. Gebruik bij nieuwe implementaties bij voorkeur `CORS_ALLOWED_ORIGINS`. CORS is alleen bedoeld voor browser-API-clients van een andere origin; geauthenticeerde schrijfbewerkingen in het dashboard gebruiken in plaats daarvan verzoeken van dezelfde origin plus sessiegebonden CSRF-bescherming. | +| `CORS_ALLOWED_ORIGINS` | _(niet ingesteld)_ | `src/server/cors/origins.ts` | Door komma's gescheiden CORS-toelatingslijst. Er wordt geen wildcard verzonden, tenzij `CORS_ALLOW_ALL=true` expliciet is geconfigureerd. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Alleen voor ontwikkeling bestemde uitweg om elke browser-`Origin` te retourneren. Schakel dit niet in bij gedeelde of productie-implementaties. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Verouderde alias. De bijbehorende dashboardschakelaar (een DB-override) wordt vóór deze omgevingswaarde gelezen; `false`, `0`, `no` of `off` in een van beide schakelt de hostcontroles uit, zoals `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` doet, zelfs wanneer de dashboardschakelaar van die vlag als uitgeschakeld is opgeslagen; andere waarden laten de guard over aan de twee vlaggen hieronder. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Schakelt de hostcontroles van de uitgaande URL-guard uit, inclusief de cloudmetadatablokkade (het volledige 169.254.0.0/16, metadata.google.internal), op de providerpaden die de guard-modus volgen: URL-validatie, modeldetectie, basis-URL's van providernodes (inclusief externe rerank-nodes) en de proxy-fallbacktest, naast andere providergerelateerde uitgaande paden zoals het ophalen van externe afbeeldingen/media. Het staat ook privé-webhookdoelen toe, behalve cloudmetadata-/link-local-eindpunten, die geblokkeerd blijven zelfs wanneer deze vlag aan staat. Chatverzoeken die via `BaseExecutor.execute()` worden verzonden behouden hun cloudmetadata-controle. Lokale providers werken met de standaardwaarden: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (standaard `true`) staat lokale en LAN-URL's op de validatie-, detectie- en providernodepaden al toe; de proxy-fallbacktest en privé-webhookdoelen raadplegen alleen deze vlag en blokkeren lokale/LAN-hosts zolang deze uit staat. Overschrijft `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Een vanuit de dashboardschakelaar opgeslagen waarde heeft voorrang op deze variabele. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Sta provider-URL's op lokale/privéadressen (127.0.0.1, localhost, LAN, privébereiken) toe voor providervalidatie, modeldetectie, chat en de andere providergerelateerde uitgaande paden die de guard-modus beheerst. **Standaard `true`** (local-first): de guard blokkeert dan cloudmetadata-eindpunten — het volledige 169.254.0.0/16 plus de bekende metadata-hostnamen (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Stel `false` in voor de modus alléén-openbaar, die ook privé- en loopback-hosts blokkeert. De guard controleert de hostnaam of het IP-letteraal zoals geschreven, vóór enige DNS-lookup; in de modus alléén-openbaar lost de safe-fetch-wrapper de hostnaam bovendien op en weigert elk privé-antwoord. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` schakelt deze controles uit op de paden die in de rij van die vlag staan. Ingebouwde lokale providers, zoals LM Studio, Ollama, vLLM, Llamafile en Triton, slaan alle hostcontroles over voor sleutelvalidatie en chat. Een vanuit de dashboardschakelaar opgeslagen waarde heeft voorrang op deze variabele. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Laat de `/v1/audio/*`-routes (transcripties, spraak, vertalingen) een OpenAI-compatibele providernode gebruiken die buiten localhost wordt gehost. Standaard uitgeschakeld — het routeren van audio naar een externe host wijzigt de egress-identiteit en moet een expliciete beslissing van de beheerder zijn. Loopback-/privénodes (localhost, 127.0.0.1, 172.16-31.x) zijn altijd toegestaan en worden hierdoor niet beïnvloed. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Laat `POST /v1/rerank` (en de loopback-rerankstap van de geheugenengine) een OpenAI-compatibele providernode gebruiken die buiten localhost wordt gehost — een machine op het LAN of een Tailscale-peer waarop TEI, Infinity, vLLM enz. draait. Standaard uitgeschakeld — routering naar een externe host wijzigt de egress-identiteit en moet een expliciete beslissing van de beheerder zijn. Loopback-nodes (localhost, 127.0.0.1, 172.16-31.x) zijn altijd toegestaan en worden hierdoor niet beïnvloed. De basis-URL van een externe node moet ook voldoen aan het beleid voor uitgaande provider-URL's (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Wanneer OIDC is ingeschakeld, wordt aanmelden met een wachtwoord uitgeschakeld, zodat gebruikers zich alleen via OIDC Single Sign-On kunnen authenticeren. De kale alias `OIDC_DISABLE_PASSWORD_LOGIN` wordt ook geaccepteerd; de dashboardfunctievlag met dezelfde sleutel heeft voorrang. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kale alias van `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Checklist voor beveiligingsversterking diff --git a/docs/i18n/no/CONTRIBUTING.md b/docs/i18n/no/CONTRIBUTING.md index acb7c3f1d5d5..5d64ec9a1f3b 100644 --- a/docs/i18n/no/CONTRIBUTING.md +++ b/docs/i18n/no/CONTRIBUTING.md @@ -391,18 +391,18 @@ Skriv enhetstester i `tests/unit/` som minst dekker: - [ ] Tester består (`npm test`) - [ ] Linting består (`npm run lint`) -- [ ] Bygging lykkes (`npm run build`) +- [ ] Bygging fullføres (`npm run build`) - [ ] TypeScript-typer er lagt til for nye offentlige funksjoner og grensesnitt - [ ] Ingen hardkodede hemmeligheter eller reserveverdier -- [ ] Offentlig tilgjengelig oppstrømslegitimasjon er innebygd via `resolvePublicCred()` (se [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), aldri som literalverdier -- [ ] Feilsvar går gjennom `buildErrorBody()` / `sanitizeErrorMessage()` — ingen rå stakkspor i svarinnholdet (se [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Offentlig tilgjengelig påloggingsinformasjon for oppstrømstjenester bygges inn via `resolvePublicCred()` (se [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), aldri som litteraler +- [ ] Feilsvar går gjennom `buildErrorBody()` / `sanitizeErrorMessage()` — ingen rå stakksporinger i svarinnhold (se [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Skallkommandoer (`exec` / `spawn`) sender kjøretidsverdier via `env`, ikke via strenginterpolasjon - [ ] Alle inndata valideres med Zod-skjemaer -- [ ] Et **fragment** til endringsloggen er lagt til under `changelog.d/{features|fixes|maintenance}/-.md` for endringer som er synlige for brukerne (se [`changelog.d/README.md`](./changelog.d/README.md)) — **ikke** rediger `CHANGELOG.md` direkte; fragmentene samles ved utgivelse og skaper aldri konflikter mellom pull requests +- [ ] En **fragmentfil** for endringsloggen er lagt til under `changelog.d/{features|fixes|maintenance}/-.md` for brukersynlige endringer (se [`changelog.d/README.md`](./changelog.d/README.md)) — rediger **ikke** `CHANGELOG.md` direkte; fragmentene samles ved utgivelse og skaper aldri konflikter mellom pull request-er - [ ] Dokumentasjonen er oppdatert (hvis aktuelt) -- [ ] Ingen nye CodeQL-varsler eller varsler fra Secret-Scanning er åpnet, eller hvert varsel er avvist med en teknisk begrunnelse som henviser til det relevante dokumentet under `docs/security/` +- [ ] Ingen nye CodeQL-/Secret-Scanning-varsler er opprettet, eller hvert varsel er avvist med en teknisk begrunnelse som henviser til det relevante dokumentet under `docs/security/` - [ ] Ruter som starter underprosesser (`/api/mcp/`, `/api/cli-tools/runtime/`), er klassifisert som `isLocalOnlyPath()` i `src/server/authz/routeGuard.ts` — se [ufravikelig regel nr. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Ingen `Co-Authored-By`-trailere i commit-meldinger — commits må utelukkende vises under repository-eierens Git-identitet (ufravikelig regel nr. 16) +- [ ] Ingen AI-/bot-`Co-authored-by`-trailere i commit-meldinger (ufravikelig regel nr. 16) — menneskelige samarbeidspartnere hvis arbeid gjenbrukes, krediteres med standardtrailere av typen `Co-authored-by: Name ` --- diff --git a/docs/i18n/no/docs/architecture/QUALITY_GATES.md b/docs/i18n/no/docs/architecture/QUALITY_GATES.md index 29f38bb6ed0c..aed0cf3d29ee 100644 --- a/docs/i18n/no/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/no/docs/architecture/QUALITY_GATES.md @@ -15,37 +15,37 @@ replikeringsplanen for det samme systemet, se --- -## Portoversikt og kjøringsprofiler +## Portvaktoversikt og kjøringsprofiler ### Kandidatgodkjenning -Arbeidsflytene for CI og kvalitetsporter avgir hver sin stabile avgjørelse: `Gate / CI` og +Arbeidsflytene for CI og kvalitetsporter avgir hver sin stabile konklusjon: `Gate / CI` og `Gate / Quality`. Den versjonerte godkjenningspolicyen deres angir hver oppstrømsjobb -som obligatorisk eller rådgivende. En gjeldende obligatorisk jobb må lykkes: manglende, +som obligatorisk eller veiledende. En aktuell obligatorisk jobb må lykkes: manglende, avbrutte, hoppede over, ventende og ukjente resultater kan ikke fastslå PASS. En gyldig -klassifisering som bare gjelder dokumentasjon eller katalogen, kan gjøre et kodespor irrelevant; -en PR merket som utkast er ikke en godkjent kandidat. En `hotfix`-etikett fritar ikke fra dokumentasjonskrav. +klassifisering som kun dokumentasjon eller kun katalog kan gjøre et kodespor uaktuelt; +en PR som er et utkast, er ikke en godkjent kandidat. En `hotfix`-etikett fritar ikke fra dokumentasjonskrav. -Begge arbeidsflytene dekker PR-er og push til main/release-grener, manuell oppstart og -merge-group-hendelser. Push, dispatch og merge-group kjører hele utvalget. Forker +Begge arbeidsflytene dekker PR-er og push til main-/release-grener, manuell kjøring og +merge-group-hendelser. Push, manuell kjøring og merge-group kjører hele utvalget. Forker og merge groups bruker driftede kjørere for jobber som ellers velger selvdriftede kjørere; tilstrekkelig driftet kapasitet må verifiseres før utrulling. -Hver JSON-kvittering identifiserer SHA-en som er sjekket ut, arbeidsflytkjøringen og forsøket. -CLI-verktøyet avviser avvik mellom SHA-en for checkout og hendelsen. Arbeidsflyttester knytter policymedlemskap -til avgjørelsesjobbens `needs`-liste, slik at et nytt eller fjernet spor ikke kan forsvinne ubemerket. -Kvitteringene dekker sin egen arbeidsflyt, ikke publisering, distribusjon eller det interne -i en eksisterende rådgivende skanner. Aktivering av begge kontrollnavnene i grenreglene er en -separat administrativ endring; å legge til disse jobbene beskytter ikke i seg selv en gren. +Hver JSON-kvittering identifiserer utsjekket SHA, arbeidsflytkjøring og forsøk. +CLI-en avviser manglende samsvar mellom SHA for utsjekking og hendelse. Arbeidsflyttester knytter policymedlemskap +til konklusjonsjobbens `needs`-liste, slik at et nytt eller fjernet spor ikke kan forsvinne ubemerket. +Kvitteringene dekker deres egen arbeidsflyt, ikke publisering, utrulling eller den interne virkemåten +til en eksisterende veiledende skanner. Aktivering av begge kontrollnavnene i grenreglene er en +separat administrativ endring; det å legge til disse jobbene beskytter ikke i seg selv en gren. ### Oversikt over statisk skanning -Den versjonerte oversikten over npm-aliaser og medlemskapet i statisk skanning finnes i +Den versjonerte oversikten over npm-aliaser og medlemskapet i den statiske skanningen ligger i `config/quality/gate-manifest.json`. Kjør `npm run check:gate-manifest` for å validere skriptnavn og eksakte kommandoer mot `package.json`; tillegg, fjerninger og -kommandoavvik gjør at både den lokale hooken og jobbene for endringsklassifisering i CI feiler. -Et alias er ikke en arbeidsflytjobb, matriseinstans eller testtilfelle: disse antallene må -ikke fremstilles som om de er innbyrdes utskiftbare. +kommandoavvik fører til feil både i den lokale hooken og jobbene for endringsklassifisering i CI. +Et alias er ikke en arbeidsflytjobb, matriseinstans eller testsak: disse antallene må +ikke fremstilles som om de er utskiftbare. Bruk `npm run quality:scan -- --list` eller `npm run quality:scan:fast -- --list` for å inspisere de valgte aliasene uten å kjøre dem. Kjøreren starter @@ -54,176 +54,177 @@ Manifestet registrerer aliaser utenfor disse profilene som separat startet, og vedlikeholdskommandoer er forbudt i skrivebeskyttede skanneprofiler. Disse profilene dekker bare den statiske skanningen. De sertifiserer ikke produkttester, -dekning, pakking, eksterne kontroller eller en kandidats fullstendige utgivelsesgodkjenning. -Godkjenning i arbeidsflyten bruker den tilknyttede `config/quality/admission-policy.json` og -`scripts/quality/admission-verdict.mjs`. Profiler for utgivelsesobservatører forblir separate; -inspiser de gjeldende kontrollene og kvitteringene deres uavhengig. Prosaoversikten +dekning, pakking, eksterne kontroller eller en kandidats fullstendige release-godkjenning. +Arbeidsflytens godkjenning bruker den tilknyttede `config/quality/admission-policy.json` og +`scripts/quality/admission-verdict.mjs`. Profiler for release-observatører forblir separate; +inspiser de aktuelle kontrollene og kvitteringene deres uavhengig. Prosaoversikten nedenfor er en referanse, ikke et bevis på at en port faktisk ble kjørt. -Skript finnes under `scripts/check/` (policyporter) og `scripts/quality/` (ratchet-motor). -Kilden til sannhet for CI er `.github/workflows/ci.yml`. +Skript ligger under `scripts/check/` (policyporter) og `scripts/quality/` (ratchet-motor). +CI-kilden til sannhet er `.github/workflows/ci.yml`. -### Hurtigspor for utgivelses-PR (`quality.yml`) +### Hurtigspor for release-PR-er (`quality.yml`) -`.github/workflows/quality.yml` utfyller CI for PR-er mot main/release, push til beskyttede grener, -dispatch og merge groups. PR-er bruker hurtigkontroller filtrert etter filsti. Den permanent -deaktiverte dupliserte bygningen ble fjernet; de reelle kontrollene for bygging/pakking/oppstart forblir i CI. +`.github/workflows/quality.yml` supplerer CI for PR-er mot main-/release-grener, push til beskyttede grener, +manuell kjøring og merge groups. PR-er bruker hurtigkontroller filtrert etter sti. Den permanent +deaktiverte duplikatbyggingen ble fjernet; de virkelige kontrollene for bygging/pakking/oppstart forblir i CI. -| Job | Omfang | Blokkerende | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | -| `Docs Gates (fast-path)` | Dokumentasjons-/kode-PR-er; referanser i API-dokumentasjon og all dokumentasjon | Ja | -| `Fast Quality Gates` | Kode-PR-er; statiske kontroller, typekontroll, typekontroll av kontrollpanel, berørte enhetstester | Ja | -| `Forgotten sibling tests` | Kode-PR-er; endrede moduler spores til statiske konsumenter og aktuelle søstertester; barrel- og dynamic-import-stier rapporteres som rådgivende diagnostikk, med refererte unntak i tillatelseslisten | **Rådgivende** | -| `Vitest (fast-path)` | Kode-PR-er; rask Vitest-testsamling | Ja | -| `Unit Tests fast-path` | Kode-PR-er; enhetstestsamling med 4 shards | Ja | -| `No new ESLint warnings` | Kode-PR-er; undertrykkelsesbevisst lint-vakt | Ja, inkludert forker | -| `Merge integrity (changelog + generated skills)` | PR-er som ikke er utkast; synkronisering av endringslogg og genererte ferdigheter | Ja, inkludert forker | +| Jobb | Omfang | Blokkerende | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | Dokumentasjons-/kode-PR-er; referanser i API-dokumentasjon og all dokumentasjon | Ja | +| `Fast Quality Gates` | Kode-PR-er; statiske kontroller, typekontroll, typekontroll av dashbord, berørte enhetstester | Ja | +| `Forgotten sibling tests` | Kode-PR-er; endrede moduler spores til statiske konsumenter og aktuelle søstertester; barrel- og dynamiske importstier rapporteres som veiledende diagnostikk, med refererte unntak i tillatelseslisten | **Veiledende** | +| `Vitest (fast-path)` | Kode-PR-er; rask Vitest-testpakke | Ja | +| `Unit Tests fast-path` | Kode-PR-er; enhetstestpakke med 4 sharder | Ja | +| `No new ESLint warnings` | Kode-PR-er; undertrykkelsesbevisst lint-vakt | Ja, inkludert forker | +| `Merge integrity (changelog + generated skills)` | PR-er som ikke er utkast; synkronisering av endringslogg og genererte ferdigheter | Ja, inkludert forker | #### Rapport om glemte søstertester -`npm run check:forgotten-sibling-tests` gjenbruker importløseren bak kartet over testpåvirkning. -For hver endrede produksjonsmodul rapporterer det deterministiske kjeder av typen -`changed module/symbol -> static consumer -> candidate sibling test` når den aktuelle -testen mangler i diffen for pull requesten. Markdown-sammendraget og JSON-resultatet beholdes som +`npm run check:forgotten-sibling-tests` gjenbruker importresolveren bak kartet over testpåvirkning. +For hver endrede produksjonsmodul rapporterer den deterministiske +`changed module/symbol -> static consumer -> candidate sibling test`-kjeder når den aktuelle +testen ikke finnes i pull-request-diffen. Markdown-sammendraget og JSON-resultatet beholdes som arbeidsflytartefaktet `forgotten-sibling-tests` for kalibrering før eventuell blokkerende utrulling. -Barrel-reeksporter og dynamiske importer er kun oppløsningsdiagnostikk; de oppretter aldri et +Barrel-gjeneksporter og dynamiske importer er kun oppløsningsdiagnostikk; de oppretter aldri et blokkerende funn. Gjennomgåtte unntak ligger i `config/quality/forgotten-sibling-allowlist.json`. Hver oppføring må angi konsumenten og den aktuelle -testen, gi en spesifikk begrunnelse og lenke til en GitHub-sak eller pull request. Feilformaterte oppføringer -avvises. Unntak kan ikke skjule en slettet aktuell test eller en diff som legger til `.skip`/`.todo`; -svekkelse av asserter og annen maskering håndteres fortsatt av den uavhengig blokkerende +testen, oppgi en spesifikk begrunnelse og lenke til en GitHub-sak eller pull request. Feilformaterte oppføringer +avvises. Unntak kan ikke undertrykke en slettet aktuell test eller en diff som legger til `.skip`/`.todo`; +svekkelse av assertions og annen maskering håndteres fortsatt av den uavhengig blokkerende `check:test-masking`-porten. ### Jobb: `lint` -Kjøres på hver PR til `main`. Blokkerer sammenslåing ved feil. - -| Skript (`npm run ...`) | Validerer | Blokkerende | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | Node.js-versjonen er innenfor det støttede området | Ja | -| `check:cycles` | Sirkulære importer — alle moduler i `src/` + `open-sse/` | Ja | -| `check:route-validation:t06` | Zod-skjemaer finnes på alle ruter (retningslinjer for nivå 6) | Ja | -| `check:any-budget:t11` | Antallet `@ts-expect-error // any` overskrider ikke budsjettet (nivå 11-sperre) | Ja | -| `check:provider-consistency` | Hver leverandør i `providers.ts` har en tilsvarende oppføring i `providerRegistry.ts` (og omvendt, innenfor tillatelseslisten) | Ja | -| `check:model-lifecycle` | De tre manuelt vedlikeholdte rutingtabellene forblir konsistente med det innsjekkede livssyklusøyeblikksbildet (#11503): `FITNESS_TABLE` (`taskFitness.ts`) gir ingen utfaset ID en poengsum som `REGISTRY` kan rute; hvert mål i `BUILT_IN_ALIASES` finnes i `REGISTRY` og er fraværende i øyeblikksbildet over utfasede ID-er; hver utfaset ID som fortsatt finnes i `REGISTRY`, videresendes eller er oppført i `allowedRetiredInCatalog`; og ingen kilde eller mål i `DEFAULT_DEGRADATION_MAP` er markert som utfaset i dette øyeblikksbildet. Dette beviser ikke at en modell for øyeblikket betjenes av en aktiv oppstrømstjeneste. Frakoblet — sammenligner med `config/quality/model-lifecycle.json`, som oppdateres manuelt med `npm run quality:refresh-model-lifecycle` (krever nettverk; ikke koblet til CI). `allowedRetiredInCatalog` er en nedtrappingssperre: legg bare til en oppføring med en sporingssak. | Ja | -| `check:fetch-targets` | Hver `fetch("/api/...")` i `src/` på klientsiden peker til en faktisk `route.ts` | Ja | -| `check:deps` | Alle avhengigheter som kan installeres med `npm install`, på tvers av alle `package.json` i repositoriet, finnes i `dependency-allowlist.json`; nye pakker uten fastlåst versjon eller pakker utsatt for slopsquatting flagges | Ja | -| `audit:deps` | `npm audit` (rot + electron) — ingen varsler med høy eller kritisk alvorlighetsgrad (overlapper med osv `check:vuln-ratchet`; se rasjonaliseringslisten) | Ja | -| `check:lockfile` | Integriteten til `package-lock.json` — https-register, integritetshasher, ingen overstyringer av vert | Ja | -| `check:licenses` | Tillatt SPDX-lisensliste for produksjonsavhengigheter | Ja | -| `check:tracked-artifacts` | Ingen byggartefakter / innsendte `node_modules`-symbolske lenker (kjøres også i husky pre-commit; pre-push er med hensikt lettvektig — #6716) | Ja | -| `check:ai-attribution` | Ingen AI-/bot-generert `Co-Authored-By`-ettertekst eller AI-genereringsbunntekst i PR-committer, tittel eller brødtekst — hard regel #16 (i hurtigkontrollsløyfen i `quality.yml` for PR→`release/**` — leser hendelsesnyttelasten, gjør ingenting utenfor PR-er — og et trinn kun for PR-er i `ci.yml`-linting for PR→`main`; også husky-kroken `commit-msg`; menneskelige medforfattere er tillatt; #14436) | -| `check:vitest-exclusions` | Hvert Vitest-unntak angir en sporingssak og finnes i `config/quality/vitest-exclusions.json` (#13204) | Ja | -| `check:file-size` | Ingen kildefil overskrider grensen per filtype (skralle: fryste store filer i `frozen`-listen) | Ja | -| `check:error-helper` | Feilsvar i eksekverere/behandlere bruker `buildErrorBody()` / `sanitizeErrorMessage()` (hard regel #12) | Ja | -| `check:migration-numbering` | SQL-filer for migrering er nummerert sekvensielt, uten hull eller duplikater | Ja | -| `check:public-creds` | Ingen bokstavelige OAuth-verdier for `client_id`/`client_secret` eller Firebase Web-nøkler utenfor `publicCreds.ts` (ufravikelig regel nr. 11) | Ja | -| `check:db-rules` | Ingen rå SQL utenfor moduler i `src/lib/db/`; ingen samleimporter fra `localDb.ts` (ufravikelige regler nr. 2/5) | Ja | -| `check:known-symbols` | Leverandøreksekverere, rutingsstrategier og oversettere som er registrert i sine rutingtabeller, samsvarer med filene på disken — ingen foreldreløse eller udeklarerte symboler | Ja | -| `check:route-guard-membership` | Hver rute som starter en underprosess, er klassifisert av `isLocalOnlyPath()` (ufravikelige regler nr. 15/17) | Ja | -| `check:test-discovery` | Hver `*.test.ts`- / `*.spec.ts`-fil i repositoriet samles inn av minst én testkjører (sperrehake: listen over foreldreløse filer i `test-discovery-baseline.json` kan bare krympe) | Ja | -| `check:agent-skills-sync` | Genererte agent-skills-artefakter samsvarer med kildekatalogen (ingen avvik) | -| `check:provider-asset-provenance` | Leverandørlogoer/-ressurser har en registrert oppførsel om opphav | -| `lint:json` | JSON-konfigurasjonsfiler kan tolkes og oppfyller repoets lint-regler | -| `typecheck:core` | TypeScript-kompilering uten feil (kun rådgivende advarsler) | Ja | -| `typecheck:noimplicit:core` | Streng `noImplicitAny` — fremoverskuende; mange eksisterende kallsteder trenger fortsatt annotasjoner | **Rådgivende** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` avgrenset til `src/app/(dashboard)/**` (#7033) — den kuraterte tillatelseslisten med 27 filer i `typecheck:core` inkluderer ingen dashboard-TSX-filer, og `next build` typekontrollerer dem heller aldri (`next.config.mjs` angir `ignoreBuildErrors: true`), så regresjoner med foreldreløse identifikatorer der (#6625/#6909) var usynlige for CI. Sammenligner med en frosset grunnlinje for antall per fil/per TS-kode (`config/quality/dashboard-typecheck-baseline.json`, samme håndhevingsmønster for foreldede oppføringer som `check:known-symbols`) — bare NYE feil utover antallet i grunnlinjen gjør at kontrollen mislykkes; reduser grunnlinjen trinnvis med `--update` når en eksisterende feil er rettet. | Ja | +Kjøres for hver PR til `main`. Blokkerer sammenslåing ved feil. + +| Skript (`npm run ...`) | Validerer | Blokkerende | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | Node.js-versjonen er innenfor det støttede intervallet | Ja | +| `check:cycles` | Sirkulære importer på tvers av hele `src/` + `open-sse/` (AST-basert, `paths` fra tsconfig løses). Uten argumenter = rådgivende, viser syklusene. `check:cycles:ratchet` (det CI kjører) blokkerer når antallet overstiger `metrics.cycles`-grensen i `quality-baseline.json` — for øyeblikket 14, `direction: down`, slik at den bare kan reduseres (#15159 G-01/G-02) | Ja (ratchet) | +| `check:route-validation:t06` | Zod-skjemaer finnes på alle ruter (nivå 6-policy) | Ja | +| `check:any-budget:t11` | Antallet `@ts-expect-error // any` overstiger ikke budsjettet (nivå 11-catraca) | Ja | +| `check:provider-consistency` | Hver leverandør i `providers.ts` har en tilsvarende oppføring i `providerRegistry.ts` (og omvendt, innenfor tillatelseslisten) | Ja | +| `check:model-lifecycle` | De tre manuelt vedlikeholdte rutingtabellene forblir konsistente med det innsjekkede livssyklusøyeblikksbildet (#11503): `FITNESS_TABLE` (`taskFitness.ts`) vurderer ingen utfaset id som `REGISTRY` kan rute; hvert mål i `BUILT_IN_ALIASES` finnes i `REGISTRY` og er fraværende i øyeblikksbildet over utfasede id-er; hver utfaset id som fortsatt finnes i `REGISTRY`, videresendes eller er oppført i `allowedRetiredInCatalog`; og ingen kilde eller noe mål i `DEFAULT_DEGRADATION_MAP` vises som utfaset i dette øyeblikksbildet. Dette beviser ikke at en modell for øyeblikket betjenes av en aktiv oppstrømstjeneste. Frakoblet — sammenligner mot `config/quality/model-lifecycle.json`, som oppdateres manuelt med `npm run quality:refresh-model-lifecycle` (nettverk; ikke koblet til CI). `allowedRetiredInCatalog` er en gradvis innstrammingsmekanisme: legg bare til en oppføring med en sporingssak. | Ja | +| `check:fetch-targets` | Hver `fetch("/api/...")` i klientsidens `src/` peker til en faktisk `route.ts` | Ja | +| `check:deps` | Alle avhengigheter som kan installeres med `npm install`, på tvers av hver `package.json` i repositoriet, finnes i `dependency-allowlist.json`; nye ikke-fastlåste pakker eller pakker med navn som kan forveksles med andre, flagges | Ja | +| `audit:deps` | `npm audit` (rot + electron) — ingen varsler med høy/kritisk alvorlighetsgrad (overlapper med osv `check:vuln-ratchet`; se rasjonaliseringsetterslepet) | Ja | +| `check:lockfile` | Integriteten til `package-lock.json` — https-register, integritetshasher, ingen overstyringer av vert | Ja | +| `check:licenses` | SPDX-lisenstillatelsesliste for produksjonsavhengigheter | Ja | +| `check:tracked-artifacts` | Ingen byggartefakter / innsendte `node_modules`-symbolske lenker (kjøres også i husky pre-commit; pre-push er med hensikt lettvektig — #6716) | Ja | +| `check:ai-attribution` | Ingen AI-/bot-generert `Co-Authored-By`-ettertekst eller AI-genereringsbunntekst i PR-commits, tittel eller brødtekst — absolutt regel #16 (i `quality.yml`-løkken med hurtigkontroller for PR→`release/**` — leser hendelsesnyttelasten, gjør ingenting utenfor PR-er — og et trinn kun for PR-er i `ci.yml`-lintingen for PR→`main`; også husky-`commit-msg`-hooken; menneskelige medforfattere er tillatt; #14436) | +| `check:vitest-exclusions` | Hvert Vitest-unntak navngir en sporingssak og finnes i `config/quality/vitest-exclusions.json` (#13204) | Ja | +| `check:file-size` | Ingen kildefil overskrider grensen per filtype (skralle: fryste store filer i `frozen`-listen) | Ja | +| `check:error-helper` | Feilsvar i eksekverere/behandlere bruker `buildErrorBody()` / `sanitizeErrorMessage()` (absolutt regel #12) | Ja | +| `check:migration-numbering` | SQL-migreringsfiler er nummerert sekvensielt, uten hull eller duplikater | Ja | +| `check:public-creds` | Ingen bokstavelige OAuth-verdier for `client_id`/`client_secret` eller Firebase Web-nøkler utenfor `publicCreds.ts` (ufravikelig regel nr. 11) | Ja | +| `check:db-rules` | Ingen rå SQL utenfor modulene i `src/lib/db/`; ingen barrel-importer fra `localDb.ts` (ufravikelige regler nr. 2/5) | Ja | +| `check:known-symbols` | Leverandørutførere, rutingsstrategier og oversettere som er registrert i sine dispatch-tabeller, samsvarer med filene på disken – ingen foreldreløse eller udeklarerte symboler | Ja | +| `check:route-guard-membership` | Hver rute som starter en underprosess, er klassifisert av `isLocalOnlyPath()` (ufravikelige regler nr. 15/17) | Ja | +| `check:test-discovery` | Hver `*.test.ts`- / `*.spec.ts`-fil i repoet samles inn av minst én testkjører (ratchet: listen over foreldreløse filer i `test-discovery-baseline.json` kan bare krympe) | Ja | +| `check:agent-skills-sync` | Genererte agent-skills-artefakter samsvarer med kildekatalogen (ingen avvik) | +| `check:provider-asset-provenance` | Leverandørlogoer/-ressurser har en registrert oppførsel om opphav | +| `lint:json` | JSON-konfigurasjonsfiler kan analyseres og oppfyller repoets lint-regler | +| `typecheck:core` | TypeScript-kompilering uten feil (kun veiledende advarsler) | Ja | +| `typecheck:noimplicit:core` | Streng `noImplicitAny` — fremoverskuende; mange eksisterende kallesteder trenger fortsatt annotasjoner | **Veiledende** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` avgrenset til `src/app/(dashboard)/**` (#7033) — den kuraterte tillatelseslisten med 27 filer i `typecheck:core` inkluderer ingen TSX-filer fra kontrollpanelet, og `next build` typesjekker dem heller aldri (`next.config.mjs` angir `ignoreBuildErrors: true`), så regresjoner med foreldreløse identifikatorer der (#6625/#6909) var usynlige for CI. Sammenligner med en fryst grunnlinje for antall per fil/per TS-kode (`config/quality/dashboard-typecheck-baseline.json`, samme mønster for håndheving av foreldede oppføringer som `check:known-symbols`) — bare NYE feil utover grunnlinjeantallet gjør at kontrollen mislykkes; reduser grunnlinjen med `--update` når en eksisterende feil er rettet. | Ja | ### Jobb: `quality-gate` -Kjører etter `test-coverage`. Blokkerer sammenslåing ved feil. +Kjøres etter `test-coverage`. Blokkerer sammenslåing ved feil. -| Skript | Validerer | Blokkerende | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `quality:collect` | Genererer `quality-metrics.json` (antall ESLint-advarsler, dekning fra sammenslått delrapport) | Ja (oppstrøms for terskelkontrollen) | -| `quality:ratchet` | Ingen måleverdi i `quality-baseline.json` har blitt dårligere (ESLint-advarsler ≤ referanseverdi; dekning ≥ referanseverdi) | Ja | -| `check:duplication` | Kodeduplisering (jscpd@4) overskrider ikke referanseverdien i `quality-baseline.json` | Ja | -| `check:complexity` | Syklomatisk kompleksitet på filnivå overskrider ikke grensen (ESLint-kjernereglene `complexity` + `max-lines-per-function`) | Ja | -| `check:cognitive-complexity` | Terskelkontroll for kognitiv kompleksitet (`eslint-plugin-sonarjs`) — separat ESLint-kjøring; CI kjører begge samlet som ett enkelt `check:complexity-ratchets`-trinn | Ja | -| `check:dead-code` | Terskelkontroll for ubrukte eksporter/filer (knip) blir ikke dårligere enn referanseverdien | Ja | -| `check:compression-budget` | Budsjett for komprimeringsreferansemåling — minimumskravene til tokenbesparelse per motor må ikke bli dårligere | Ja | -| `check:type-coverage` | Terskelkontrollen for prosentandel typet kode (`type-coverage`) blir ikke dårligere; erstatter i stor grad `typecheck:noimplicit:core` | Ja | -| `check:codeql-ratchet` | Antallet åpne CodeQL-varsler blir ikke dårligere (leses via `gh api`; hopper kontrollert over uten token) — oppdateringsfrekvens og manuell utløsning: se «CodeQL-terskelkontroll» nedenfor | Ja | +| Skript | Validerer | Blokkerende | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `quality:collect` | Genererer `quality-metrics.json` (antall ESLint-advarsler, dekning fra sammenslått shard-rapport) | Ja (før ratchet-kontrollen) | +| `quality:ratchet` | Ingen metrikker i `quality-baseline.json` har gått tilbake (ESLint-advarsler ≤ referanseverdien; dekning ≥ referanseverdien) | Ja | +| `check:duplication` | Kodeduplisering (jscpd@4) overskrider ikke referanseverdien i `quality-baseline.json` | Ja | +| `check:complexity` | Syklomatisk kompleksitet på filnivå overskrider ikke grensen (ESLint-kjernereglene `complexity` + `max-lines-per-function`) | Ja | +| `check:cognitive-complexity` | Ratchet-kontroll for kognitiv kompleksitet (`eslint-plugin-sonarjs`) — separat ESLint-kjøring; CI kjører begge samlet som ett enkelt `check:complexity-ratchets`-trinn | Ja | +| `check:dead-code` | Ratchet-kontroll for ubrukte eksporter/filer (knip) har ikke gått tilbake sammenlignet med referanseverdien | Ja | +| `check:compression-budget` | Budsjett for komprimeringsmåling — minimumskravene til tokenbesparelse per motor må ikke gå tilbake | Ja | +| `check:type-coverage` | Ratchet-kontrollen for prosentvis typet kode (`type-coverage`) har ikke gått tilbake; erstatter i stor grad `typecheck:noimplicit:core` | Ja | +| `check:codeql-ratchet` | Antallet åpne CodeQL-varsler har ikke gått tilbake (leses via `gh api`; hoppes over uten feil hvis token mangler) — oppdateringsfrekvens og manuell utløsning: se «CodeQL ratchet» nedenfor | Ja | ### Jobb: `quality-extended` -Hele jobben er rådgivende (`continue-on-error: true`). De npm-baserte terskelkontrollene kjøres -reelt; de eksterne skannerne installeres via `gh release download` og hopper over seg selv (avslutningskode 0) +Hele jobben er rådgivende (`continue-on-error: true`). De npm-baserte ratchet-kontrollene kjøres på +ordentlig; de eksterne skannerne installeres via `gh release download` og hopper over seg selv (exit 0) når en binærfil fortsatt mangler. -| Skript | Validerer | Blokkerende | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| `check:circular-deps` | Ingen sirkulære avhengigheter (dpdm) | **Rådgivende** | -| `check:bundle-size` | Pakkestørrelsen overskrider ikke grensen | **Rådgivende** | -| `check:secrets` | Skanning etter hemmeligheter (gitleaks) — hoppes over hvis binærfilen mangler | **Rådgivende** | -| `check:vuln-ratchet` | Sårbarheter i avhengigheter (osv-scanner) blir ikke verre — hoppes over hvis binærfilen mangler | **Rådgivende** | -| `check:workflows` | Linting av arbeidsflyter (actionlint + zizmor); manglende/ødelagte skannere, ugyldige rapporter eller manglende referanseverdi for terskelkontrollen gir statusen INCOMPLETE. Gyldige funn følger den valgte strenge, rådgivende eller terskelbaserte policyen | Kjøring kreves; zizmor-terskelkontrollen er blokkerende i CI | -| `check:openapi-breaking` | Inkompatible endringer i den offentlige API-kontrakten (`openapi.yaml`) sammenlignet med basisgrenen (oasdiff) — genererer `openapiBreaking=N`; hoppes over hvis oasdiff mangler eller basisspesifikasjonen ikke kan løses | **Rådgivende** | +| Skript | Validerer | Blokkerende | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:circular-deps` | Ingen sirkulære avhengigheter (dpdm) | **Rådgivende** | +| `check:bundle-size` | Pakkestørrelsen overskrider ikke grensen | **Rådgivende** | +| `check:secrets` | Skanning etter hemmeligheter (gitleaks) — hoppes over hvis binærfilen mangler | **Rådgivende** | +| `check:vuln-ratchet` | Sårbarheter i avhengigheter (osv-scanner) går ikke tilbake — hoppes over hvis binærfilen mangler | **Rådgivende** | +| `check:workflows` | Linting av arbeidsflyter (actionlint + zizmor); manglende/defekte skannere, ugyldige rapporter eller manglende ratchet-referanseverdi gir feilen INCOMPLETE. Gyldige funn følger den valgte strenge/rådgivende/ratchet-policyen | Kjøring påkrevd; zizmor-ratchet blokkerer i CI | +| `check:openapi-breaking` | Bakoverinkompatible endringer i den offentlige API-kontrakten (`openapi.yaml`) sammenlignet med basisgrenen (oasdiff) — genererer `openapiBreaking=N`; hoppes over hvis oasdiff mangler eller basisspesifikasjonen ikke kan løses | **Rådgivende** | ### Jobb: `docs-sync-strict` Kjøres for hver PR til `main`. Blokkerer sammenslåing ved feil. -| Skript | Validerer | Blokkerende | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `check:docs-all` | Metakontroll som kjører de seks delkontrollene nedenfor sekvensielt | Ja | -| ↳ `check:docs-sync` | Versjonssamsvar mellom CHANGELOG / OpenAPI / llm.txt | Ja | -| ↳ `check:docs-counts` | Antall i brødteksten (antall leverandører, antall migreringer osv.) er innenfor sperrevinduet til de faktiske antallene | Ja | -| ↳ `check:env-doc-sync` | Hver miljøvariabel i `.env.example` er dokumentert i en dokumentasjonstabell, og omvendt | Ja | -| ↳ `check:deprecated-versions` | Ingen avviklede versjonsstrenger i dokumentasjonen | Ja | -| ↳ `check:doc-links` | Interne Markdown-lenker i dokumentasjonen peker til faktiske filer (`[text]`/`(path)`-format) | Ja | -| ↳ `check:fabricated-docs` | Ruter, miljøvariabler, CLI-kommandoer, hook-navn og filstier som er omtalt i dokumentasjonen, finnes i kodebasen. Hard kontroll via `--strict`; myk feil uten flagget. | Ja (via `--strict` i CI) | -| `check:cli-i18n` | CLI-kommandostrenger finnes i alle i18n-språkfiler | Ja | -| `check:openapi-coverage` | OpenAPI-spesifikasjonen dekker minst en gradvis økende nedre grense for faktiske ruter | Ja | -| `check:openapi-security-tiers` | Sikkerhetsnivåmerknader i `openapi.yaml` samsvarer med klassifiseringene i `routeGuard.ts` | **Veiledende** | -| `check:openapi-routes` | Hver sti i `openapi.yaml` peker til en faktisk `route.ts` (motvirker hallusinasjoner) | Ja | -| `check:docs-symbols` | Hver `/api/...`-referanse i `docs/**/*.md` peker til en faktisk `route.ts` (motvirker hallusinasjoner) | Ja | -| `i18n translation drift` | Uoversatte nøkler i i18n-språkfiler — kun advarsel | **Veiledende** | +| Skript | Validerer | Blokkerende | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `check:docs-all` | Metakontroll som kjører de seks delkontrollene nedenfor sekvensielt | Ja | +| ↳ `check:docs-sync` | Versjonssamsvar mellom CHANGELOG / OpenAPI / llm.txt | Ja | +| ↳ `check:docs-counts` | Antall i prosa (antall leverandører, antall migreringer osv.) er innenfor sperrevinduet til de faktiske antallene | Ja | +| ↳ `check:env-doc-sync` | Hver miljøvariabel i `.env.example` er dokumentert i en dokumentasjonstabell, og omvendt | Ja | +| ↳ `check:deprecated-versions` | Ingen utdaterte versjonsstrenger i dokumentasjonen | Ja | +| ↳ `check:doc-links` | Interne markdown-lenker i dokumentasjonen peker til faktiske filer (`[tekst]`/`(sti)`-format) | Ja | +| ↳ `check:fabricated-docs` | Ruter, miljøvariabler, CLI-kommandoer, hook-navn og filstier som er nevnt i dokumentasjonen, finnes i kodebasen. Hard kontroll via `--strict`; myk feil uten flagget. | Ja (via `--strict` i CI) | +| `check:cli-i18n` | CLI-kommandostrenger finnes i alle i18n-lokalitetsfiler | Ja | +| `check:openapi-coverage` | OpenAPI-spesifikasjonen dekker minst en gradvis økende nedre grense for faktiske ruter | Ja | +| `check:openapi-security-tiers` | Sikkerhetsnivåannotasjoner i `openapi.yaml` samsvarer med klassifiseringene i `routeGuard.ts` | **Rådgivende** | +| `check:openapi-routes` | Hver sti i `openapi.yaml` peker til en faktisk `route.ts` (motvirker hallusinasjoner) | Ja | +| `check:docs-symbols` | Hver `/api/...`-referanse i `docs/**/*.md` peker til en faktisk `route.ts` (motvirker hallusinasjoner) | Ja | +| `i18n translation drift` | Uoversatte nøkler i i18n-lokalitetsfiler — kun advarsel | **Rådgivende** | ### Jobb: `i18n-ui-coverage` -| Skript | Validerer | Blokkerende | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | -| `check-ui-keys-coverage` (internt) | Dekningen av i18n-nøkler i brukergrensesnittet er ≥ 65 % | Ja | -| `check-ui-value-drift` (internt) | En omskrevet engelsk **verdi** etterlater ingen foreldet oversettelse | Ja | -| `check-new-key-coverage` (internt) | En **ny** engelsk nøkkel er oversatt i alle språk — en `__MISSING__:`-markør avvises | Ja | -| `check-translation-ratio` | Andelen reelle oversettelser per språk (blader utenfor tillatelseslisten som er identiske med engelsk / plassholdere / mangler) må ikke overstige `config/quality/i18n-translation-baseline.json` + slingringsmonn | **Veiledende** | +| Skript | Validerer | Blokkerende | +| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (internt) | Dekningen av UI i18n-nøkler er ≥ 65 % | Ja | +| `check-ui-value-drift` (internt) | En omskrevet engelsk **verdi** etterlater ingen utdatert oversettelse | Ja | +| `check-new-key-coverage` (internt) | En **ny** engelsk nøkkel er oversatt i alle lokaliteter — en `__MISSING__:`-markør avvises | Ja | +| `check-translation-ratio` | Andelen reelle oversettelser per lokalitet (blader utenfor tillatelseslisten som er identiske med engelsk / plassholdere / mangler) må ikke overstige `config/quality/i18n-translation-baseline.json` + slingringsmonn | **Rådgivende** | -Krever `fetch-depth: 0` — kontrollen for verdiavvik sammenligner `en.json` med sammenslåingsbasen. +Krever `fetch-depth: 0` — kontrollen for verdiavvik sammenligner `en.json` med flettingsbasen. -#### `check-ui-value-drift` — kontroll for foreldede oversettelser +#### `check-ui-value-drift` — kontroll for utdaterte oversettelser -Fanger opp den ene i18n-regresjonen som de andre kontrollene strukturelt sett ikke kan oppdage: En engelsk verdi -skrives om, mens oversettelsene som er basert på den _forrige_ engelske teksten, blir stående, slik at -ikke-engelskspråklige brukere fortsetter å lese en selvsikkert formulert tekst som nå er feil. +Fanger opp den ene i18n-regresjonen som de andre kontrollene strukturelt ikke kan oppdage: En engelsk verdi +skrives om, mens oversettelsene som er avledet fra den _forrige_ engelske teksten, blir stående, slik at +ikke-engelske brukere fortsetter å lese selvsikkert formulert, men nå feilaktig tekst. -Dette ble faktisk levert. `oauthModal.googleOAuthWarning` ble skrevet om da innloggingshjelperen for Antigravity -ble lagt til (#5203); **39 av 43 språk** beholdt tekst som ba operatører om å «kopiere hele -URL-en og lime den inn nedenfor» — en flyt som ikke kan fullføres for denne leverandøren. Det gikk -ubemerket hen frem til #8463 fordi: +Dette skjedde faktisk i produksjon. `oauthModal.googleOAuthWarning` ble skrevet om da Antigravity- +innloggingshjelperen ble innført (#5203); **39 av 43 lokaliteter** beholdt tekst som ba operatører om å «kopiere +hele URL-en og lime den inn nedenfor» — en flyt som ikke kan fullføres for denne leverandøren. Det +ble ikke oppdaget før #8463 fordi: -- `sync-ui-keys` fyller bare inn nøkler som **mangler**, aldri nøkler som er **foreldet**; -- `check-ui-keys-coverage` teller om nøkkelen er _til stede_, så en foreldet oversettelse regnes som dekket; -- `check-translation-drift` sporer dokumentasjonsspeilene `docs/i18n//**.md` — +- `sync-ui-keys` fyller bare inn nøkler som **mangler**, aldri nøkler som er **utdaterte**; +- `check-ui-keys-coverage` teller _tilstedeværelsen_ av nøkler, så en utdatert oversettelse regnes som dekket; +- `check-translation-drift` sporer dokumentasjonsspeilene i `docs/i18n//**.md` — den leser aldri `src/i18n/messages/*.json`. Blokkerende i jobben `docs-sync-strict` siden - 2026-09-resynkroniseringen: rediger et kjernedokument → `npm run i18n:run -- --files=` (på seksjonsnivå, rimelig). + resynkroniseringen i 2026-09: rediger et kjernedokument → `npm run i18n:run -- --files=` (på seksjonsnivå, rimelig). **Diff-bevisst, ikke støttet av en grunnlinje.** Den sammenligner `en.json` ved flettebasen med -arbeidstreet. For hver nøkkel der den engelske verdien er endret, regnes alle språkversjoner som -fortsatt har en urørt oversettelse, som utdaterte. Dette **fryser bevisst eksisterende gjeld** — en diff -kan ikke avsløre hvilken gammel engelsk tekst en langvarig oversettelse kom fra, så kontrollen vurderer -bare det den gjeldende endringen berører. Alternativet (en hash-grunnlinje per nøkkel) ville kreve -en generert fil på ~600 KB, 3× større enn den største eksisterende grunnlinjen, med endringer i hver i18n-PR. +arbeidstreet. For hver nøkkel der den engelske verdien er endret, er alle språkversjoner som +fortsatt har en urørt oversettelse, utdaterte. Dette **fryser eksisterende gjeld** med hensikt +— en diff kan ikke avdekke hvilken gammel engelsk tekst en langvarig oversettelse kom fra, +så kontrollen vurderer bare det den gjeldende endringen berører. Alternativet (en grunnlinje +med hash per nøkkel) ville kreve en generert fil på ~600 KB, 3× større enn den største +eksisterende grunnlinjen, med endringer for hver i18n-PR. Det finnes to måter å oppfylle kravet på: 1. oppdater de berørte oversettelsene, eller -2. sett dem til `__MISSING__:` — ved kjøring leveres da den korrigerte engelske teksten - (`src/i18n/request.ts::deepMergeFallback`, #7258), og nøkkelen legges i oversettelseskøen. +2. sett dem til `__MISSING__:` — ved kjøring vises da den korrigerte engelske + teksten (`src/i18n/request.ts::deepMergeFallback`, #7258), og nøkkelen legges i kø for oversettelse. -Hvis strengens **betydning** er endret, bør du helst **gi nøkkelen et nytt navn**: En ny nøkkel kan ikke arve -en utdatert oversettelse. Det er mønsteret som ble brukt i #8463. +Hvis strengens **betydning** er endret, bør du **gi nøkkelen et nytt navn**: En ny nøkkel kan +ikke arve en utdatert oversettelse. Det var mønsteret som ble brukt i #8463. ```bash npm run i18n:check-value-drift # streng (det CI kjører) @@ -231,48 +232,48 @@ npm run i18n:check-value-drift:warn # bare rapportering BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Avsluttes med kode 0 og `SKIP reason=base-unresolved` når basiskatalogen ikke kan leses (grunn -kloning uten basereferansen), i tråd med `check-openapi-breaking`. +Avslutter med 0 og `SKIP reason=base-unresolved` når basiskatalogen ikke kan leses (grunn +kloning uten basereferansen), på samme måte som `check-openapi-breaking`. ### Jobb: `i18n` Full valideringsmatrise for i18n (én jobb per språkversjon). Hele jobben er rådgivende. -| Skript | Validerer | Blokkerende | -| ------------------------------- | ------------------------------------ | ---------------------------------------------------------- | -| `validate_translation.py quick` | Fullstendighet for hver språkversjon | **Rådgivende** (`continue-on-error: true` for hele jobben) | +| Skript | Validerer | Blokkerende | +| ------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | +| `validate_translation.py quick` | Oversettelsesfullstendighet per språkversjon | **Rådgivende** (`continue-on-error: true` for hele jobben) | ### Jobb: `pr-test-policy` Kjøres bare for pull requests. -| Skript | Validerer | Blokkerende | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | PR-er som endrer produksjonskode i `src/`, `open-sse/`, `electron/` eller `bin/`, må inkludere eller oppdatere tester (fast regel nr. 8) | Ja | -| `check:test-masking` | Endrede testfiler reduserer ikke nettoantallet av assert-uttrykk eller legger til `assert.ok(true)`-tautologier | Ja | -| `check:pr-evidence` | PR-beskrivelsen viser til test-/VPS-dokumentasjon for endringen (automatiserer fast regel nr. 18 ved å søke i PR-teksten — skjørt, se restlisten) | Ja | +| Skript | Validerer | Blokkerende | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `check:pr-test-policy` | PR-er som endrer produksjonskode i `src/`, `open-sse/`, `electron/` eller `bin/`, må inkludere eller oppdatere tester (Hard regel #8) | Ja | +| `check:test-masking` | Endrede testfiler reduserer ikke nettoantallet av assert-uttrykk eller legger til `assert.ok(true)`-tautologier | Ja | +| `check:pr-evidence` | PR-teksten viser til test-/VPS-dokumentasjon for endringen (mekaniserer Hard regel #18 ved å søke gjennom PR-teksten — skjørt, se Backlog) | Ja | ### Jobb: `test-vitest` Kjøres etter `build`. Blokkerer fletting ved feil. -| Testpakke | Validerer | Blokkerende | -| ---------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP-server (110 verktøy), autoCombo, hurtigbuffer — vitest-kjører | Ja | -| `test:vitest:ui` | Tester av UI-komponenter — vitest-kjører | **Blokkerende** — eksisterende feil er uttrykkelig ekskludert i `vitest.config.ts`; nye feil fører til at jobben mislykkes | +| Testpakke | Validerer | Blokkerende | +| ---------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | MCP-server (110 verktøy), autoCombo, hurtigbuffer — vitest-testkjører | Ja | +| `test:vitest:ui` | Tester av UI-komponenter — vitest-testkjører | **Blokkerende** — eksisterende feil er uttrykkelig ekskludert i `vitest.config.ts`; nye feil gjør at jobben feiler | ### Nattlige arbeidsflyter (planlagte, rådgivende) -Disse kjøres etter en cron-plan (og med `workflow_dispatch`), aldri for PR-er. Alle er rådgivende. +Disse kjører etter en cron-plan (og `workflow_dispatch`), aldri for PR-er. Alle er rådgivende. | Arbeidsflyt | Validerer | Blokkerende | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | -| `nightly-property` | fast-check-egenskapstester med et tilfeldig starttall og et høyt antall kjøringer | **Rådgivende** | -| `nightly-resilience` | kontroll av heap-vekst, kaotisk feilinjeksjon, k6-belastnings-/utholdenhetstest | **Rådgivende** | +| `nightly-property` | fast-check-egenskapstester med et tilfeldig seed og et høyt antall kjøringer | **Rådgivende** | +| `nightly-resilience` | kontroll av heap-vekst, kaosbasert feilinjeksjon, k6-belastnings-/utholdenhetstest | **Rådgivende** | | `nightly-llm-security` | promptfoo-injeksjonsvern (blokkeringsmodus) + garak-prober (hoppes over uten en leverandørhemmelighet) | **Rådgivende** | -| `nightly-schemathesis` | fuzzing av OpenAPI-kontrakten (schemathesis) mot en kjørende OmniRoute ved bruk av `docs/openapi.yaml` — avdekker spesifikasjonsbrudd / uhåndterte 500-feil (Fase 8 B.4) | **Rådgivende** | -| `nightly-mutation` | Stryker-poengsum for mutasjonstesting i den raske enhetstestbanen — overlevende mutanter avdekker svake assert-uttrykk | **Rådgivende** | -| `nightly-compat` | Kompatibilitetsmatrise for Node-motoren på tvers av de støttede `engines.node`-intervallene | **Rådgivende** | +| `nightly-schemathesis` | OpenAPI-kontraktsfuzzing (schemathesis) mot en kjørende OmniRoute ved bruk av `docs/openapi.yaml` — avdekker brudd på spesifikasjonen / uhåndterte 500-feil (Fase 8 B.4) | **Rådgivende** | +| `nightly-mutation` | Stryker-mutasjonstestingspoeng for det raske enhetstestsporet — overlevende mutanter avdekker svake assert-uttrykk | **Rådgivende** | +| `nightly-compat` | kompatibilitetsmatrise for Node-motoren på tvers av de støttede `engines.node`-intervallene | **Rådgivende** | --- @@ -535,43 +536,44 @@ tilkoblingsfeil og avbryter aldri økter. --- -## Rasjonaliseringsrestanse (ROI-gjennomgang — fase 9 bølge 3) +## Rasjonaliseringsrestanse (ROI-gjennomgang — Fase 9 Onda 3) Denne oversikten ble avstemt mot `ci.yml` 2026-06-17 (den forrige versjonen utelot `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). En ROI-gjennomgang av det avstemte settet identifiserte følgende kandidater for rasjonalisering. **Sammenslåingene er mekaniske -CI-endringer; omgjøringene/fjerningene er policybeslutninger som er forbeholdt operatøren.** Ingenting nedenfor -er tatt i bruk ennå. +CI-endringer; endringene/fjerningene er policybeslutninger som er forbeholdt operatøren.** +Ingenting nedenfor er tatt i bruk ennå. -**Også udokumentert ovenfor** (rådgivende, svakt signal): `docs-lint`-jobben -(markdownlint + Vale, hele jobben har `continue-on-error`) og de frittstående skannerarbeidsflytene -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` finnes i -`quality-baseline.json`, men er ikke koblet til en blokkerende ratchet i `ci.yml` — måleverdien er -for øyeblikket foreldreløs. +**Også udokumentert ovenfor** (rådgivende, lav signalverdi): `docs-lint`-jobben +(markdownlint + Vale, hele jobben har `continue-on-error`) og de frittstående +skannerarbeidsflytene `semgrep.yml` / `codeql.yml` / `scorecard.yml`. +`semgrepFindings: 0` finnes i `quality-baseline.json`, men er ikke koblet til en +blokkerende skrallemekanisme i `ci.yml` — måleverdien er for øyeblikket foreldreløs. ### Slå sammen / dedupliser (mekanisk, lavere risiko) -Hver kandidat ble validert mot den aktive gate-tilstanden 2026-06-17 (stol på, men verifiser); -flere «åpenbare» sammenslåinger viste seg å skjule teknisk gjeld og er **ikke** problemfrie direkteerstatninger. +Hver kandidat ble validert mot den aktive porttilstanden 2026-06-17 +(stol på, men verifiser); flere «åpenbare» sammenslåinger viste seg å skjule gjeld og er +**ikke** rene direkteerstatninger. -- **`check:docs-sync` kjøres to ganger** — frittstående i `lint`-jobben og på nytt i `check:docs-all` (`docs-sync-strict`) og husky pre-commit-hooken. ✅ **FERDIG** — den frittstående `lint`-kjøringen er fjernet. -- **CVE-skanning** — ❌ **IKKE en problemfri sammenslåing.** `audit:deps` feiler kategorisk ved enhver CVE med høy/kritisk alvorlighetsgrad; `check:vuln-ratchet` (osv) feiler bare ved en _regresjon_ mot referanseverdien (for øyeblikket 1 MODERATE). Ulik semantikk — fjerning av `audit:deps` ville fjerne den absolutte gaten for høy/kritisk alvorlighetsgrad. Behold begge. -- **Syklusdeteksjon** — ❌ **IKKE en problemfri sammenslåing.** `check:circular-deps` (dpdm) rapporterer **91 sykluser** (det er derfor den er rådgivende); den kan ikke gjøres blokkerende før de er løst, og den har et bredere omfang enn den grønne, kuraterte `check:cycles`. Behold `check:cycles` som blokkerende; håndtering av de 91 dpdm-syklusene er en egen restanse. -- **Kompleksitet** — ✅ **FERDIG** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): én ESLint-gjennomgang, teller etter ruleId slik at referanseverdiene for syklomatisk kompleksitet+max-lines og kognitiv kompleksitet forblir uavhengige; de individuelle `check:complexity` / `check:cognitive-complexity` beholdes for lokal `--update`. -- **`/api`-antihallusinasjon** — ✅ **FERDIG** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): én FS-oversikt over `src/app/api`, openapi-routes + docs-symbols rapporterer fortsatt uavhengig; de individuelle kontrollene beholdes for lokale kjøringer. -- **`check:node-runtime` kjøres i 11 jobber** — ⚠️ **lav ROI.** Hver av dem bruker en separat runner, og kontrollen tar <1s; total besparelse er ~10s, mot å miste en rimelig beskyttelse per jobb. Ikke verdt omarbeidingen. -- **`typecheck:noimplicit:core` i CI-lint** — ✅ **fjernet fra lint-jobben** (var rådgivende med `continue-on-error`); den blokkerende typeoverflaten er `typecheck:core` + `check:type-coverage`. Lokalt skript er beholdt. +- **`check:docs-sync` kjøres to ganger** — frittstående i `lint`-jobben og på nytt i `check:docs-all` (`docs-sync-strict`) samt i husky sin pre-commit-hook. ✅ **FERDIG** — den frittstående `lint`-kjøringen er fjernet. +- **CVE-skanning** — ❌ **IKKE en ren sammenslåing.** `audit:deps` feiler ubetinget ved enhver CVE med høy/kritisk alvorlighetsgrad; `check:vuln-ratchet` (osv) feiler bare ved en _regresjon_ sammenlignet med grunnlinjen (for øyeblikket 1 MODERATE). Ulik semantikk — fjerning av `audit:deps` ville medføre tap av den absolutte porten for høy/kritisk alvorlighetsgrad. Behold begge. +- **Syklusdeteksjon** — ✅ **FERDIG** (#15159 G-01/G-02). Den gamle teksten her omtalte `check:cycles` som «den grønne, kuraterte» porten og begrunnet at den skulle forbli blokkerende fordi `check:circular-deps` (dpdm) rapporterte 91 sykluser. Det grønne resultatet var en **falsk grønnstatus**: `check:cycles` skannet 5 underkataloger (450 filer), samsvarte bare med statisk `import|export … from` og forkastet hver `@/`- og `@omniroute/open-sse/`-spesifikator, slik at den ikke kunne se syklusene med dynamisk import og aliaser som dominerte repoet. Rettet: Porten går nå gjennom `src` + `open-sse` (5023 filer), samler spesifikatorer fra TypeScript-AST-en (slik at `import("…")` telles, mens `typeof import("…")` i typeposisjon ikke gjør det) og løser opp tsconfig-`paths`. Den finner **14** sykluser, ikke 0. Fordi 14 eksisterende sykluser ikke kan rettes i en port-PR, er `check:cycles` nå en **skrallemekanisme** (`--ratchet`, tak `metrics.cycles.value = 14` i `quality-baseline.json`, `direction: down`) — den blokkerer enhver _regresjon_, og antallet kan bare synke. CI kjører `npm run check:cycles:ratchet`. Nedbyggingen følger **A-01**. `check:circular-deps` (dpdm) forblir rådgivende som en bredere tilleggsvurdering. +- **Kompleksitet** — ✅ **FERDIG** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): én ESLint-gjennomgang, telling etter ruleId slik at grunnlinjene for syklomatisk kompleksitet + maks antall linjer og kognitiv kompleksitet forblir uavhengige; de individuelle `check:complexity` / `check:cognitive-complexity` beholdes for lokal `--update`. +- **Antihallusinasjon for `/api`** — ✅ **FERDIG** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): én FS-oversikt over `src/app/api`; openapi-routes + docs-symbols rapporterer fortsatt uavhengig; de individuelle kontrollene beholdes for lokale kjøringer. +- **`check:node-runtime` kjøres i 11 jobber** — ⚠️ **lav ROI.** Hver kjører på en separat runner, og kontrollen tar <1s; total besparelse er ~10s, mot tapet av en rimelig kontroll per jobb. Ikke verdt endringsstøyen. +- **`typecheck:noimplicit:core` i CI-lint** — ✅ **fjernet fra lint-jobben** (var rådgivende med `continue-on-error`); den blokkerende typeoverflaten er `typecheck:core` + `check:type-coverage`. Det lokale skriptet er beholdt. ### Endre / avgjør (operatørpolicy) -- `check:openapi-security-tiers` (rådgivende) — ❌ **KAN IKKE problemfritt gjøres blokkerende.** Den avslutter med 0, men advarer om at flere `traffic-inspector`-ruter under `LOCAL_ONLY_API_PREFIXES` mangler annotasjonen `x-loopback-only: true`. Håndheving krever at disse annotasjonene først legges til i `openapi.yaml`. -- `typecheck:noimplicit:core` (rådgivende) — i stor grad innlemmet i den blokkerende `check:type-coverage`-ratcheten. Gjør den om til en ratchet, eller fjern den overflødige andre `tsc`-gjennomgangen. -- `test:vitest:ui` (nå **blokkerende**) — eksisterende feil er uttrykkelig ekskludert i `vitest.config.ts` med sporingskommentarene `// #8618`; nye feil gjør at jobben feiler. -- `check:secrets` (gitleaks, blokkerende ratchet fryst ved 3 dokumenterte falske positiver) — legg de 3 til i tillatelseslisten for å nå 0, eller nedgrader den til rådgivende. Overlapper med GitHubs innebygde hemmelighetsskanning + `check:public-creds`. -- `check:pr-evidence` (blokkerende, søker med grep i prosateksten i PR-beskrivelsen) — høy risiko for falske positiver; fjerning svekker håndhevingen av Hard Rule #18, så dette er en reell policyavgjørelse. -- `semgrep` (rådgivende, frittstående) — overlapper med CodeQL for OWASP-familiene; koble referanseverdien til en ratchet, eller fjern den. +- `check:openapi-security-tiers` (rådgivende) — ❌ **KAN IKKE endres uten videre.** Den avsluttes med 0, men advarer om at flere `traffic-inspector`-ruter under `LOCAL_ONLY_API_PREFIXES` mangler annotasjonen `x-loopback-only: true`. Håndheving krever at disse annotasjonene først legges til i `openapi.yaml`. +- `typecheck:noimplicit:core` (rådgivende) — i stor grad erstattet av den blokkerende `check:type-coverage`-skrallemekanismen. Endre den til en skrallemekanisme, eller fjern den overflødige andre `tsc`-kjøringen. +- `test:vitest:ui` (nå **blokkerende**) — eksisterende feil er eksplisitt ekskludert i `vitest.config.ts` med `// #8618`-sporingskommentarer; nye feil får jobben til å feile. +- `check:secrets` (gitleaks, blokkerende skrallemekanisme fryst ved 3 dokumenterte falske positiver) — tillat de 3 gjennom en allowlist for å nå 0, eller nedgrader til rådgivende. Overlapper med GitHubs innebygde hemmelighetsskanning + `check:public-creds`. +- `check:pr-evidence` (blokkerende, søker med grep i PR-beskrivelsens prosa) — høy risiko for falske positiver; fjerning svekker håndhevingen av Hard Rule #18, så dette er en reell policyavgjørelse. +- `semgrep` (rådgivende, frittstående) — overlapper CodeQL for OWASP-familiene; koble grunnlinjen til en skrallemekanisme, eller fjern den. --- diff --git a/docs/i18n/no/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/no/docs/compression/COMPRESSION_GUIDE.md index a10a84241a56..dcb904d7dbed 100644 --- a/docs/i18n/no/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/no/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ inn i stedet for den engelske. I en body med `messages` kontrollerer en innholdsforbikobling (`shouldBypassCavemanOutputMode()` i `open-sse/services/compression/outputMode.ts`) de tre siste meldingene og hopper over stilene for hele runden når de samsvarer med nøkkelord for sikkerhet, irreversible handlinger, -avklaringer eller rekkefølgefølsomhet. Forbikoblingen følger innstillingen til kontrollpanelets -**Automatisk klarhetsforbikobling**-bryter (`cavemanOutputMode.autoClarity`). +avklaringer eller rekkefølgefølsomhet. Forbikoblingen kjører mens kontrollpanelets **Automatisk klarhetsforbikobling**-bryter (`cavemanOutputMode.autoClarity`) er på, noe som er standard; med bryteren av gjelder de valgte stilene også i disse rundene. Når forbikoblingen slipper runden gjennom, plasserer `placeSystemInstruction()` (samme fil), som aldri oppretter en ny `messages[0]`, blokken på det første stedet nedenfor som finnes: diff --git a/docs/i18n/no/docs/reference/ENVIRONMENT.md b/docs/i18n/no/docs/reference/ENVIRONMENT.md index 5a12340d3b2e..963ee2bec495 100644 --- a/docs/i18n/no/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/no/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Påkrevde hemmeligheter -Disse **må** angis før første kjøring. Uten dem vil applikasjonen enten nekte å starte eller kjøre med usikre standardverdier. - -| Variabel | Påkrevd | Standardverdi | Kildefil | Beskrivelse | -| ---------------------------- | ------------------- | --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ja** | _(ingen)_ | `src/lib/auth` | Signerer/verifiserer alle øktinformasjonskapsler (JWT) for kontrollpanelet. Generer med `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ja** | _(ingen)_ | `src/lib/db/apiKeys.ts` | AES-krypteringsnøkkel for lagrede API-nøkkelverdier i SQLite. Generer med `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ja** | `CHANGEME` | Oppstartsskript | Angir det opprinnelige administratorpassordet for kontrollpanelet (samsvarer med standardverdien i `.env.example` – beholdt åpenbart usikkert for å fremtvinge en endring). **Endre før første gangs bruk.** Etter innlogging endrer du det via Kontrollpanel → Innstillinger → Sikkerhet. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (produksjon) | _(ikke angitt)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Delt hemmelighet for den interne WebSocket-broen for Codex Responses. Autentiserer broforespørsler mellom WS-releet i Electron/nettleseren og OmniRoute. ⚠️ **PÅKREVD i produksjon – når den ikke er angitt, avvises alle WS-broforespørsler.** Generer med `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nei | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplisitt ID for ugyldiggjøring av tjenestearbeiderens hurtigbuffer for PWA-skallet (#11779); først i oppløsningskjeden. | -| `SOURCE_VERSION` | Nei | _(ikke angitt)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nummer to i kjeden – angis av PaaS-byggere (f.eks. Heroku-lignende) som den distribuerte commiten. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nei | _(avledet)_ | `src/shared/components/PwaRegister.tsx` | Offentlig verdi ved byggetid som klienten bruker til å registrere `/sw.js?v=…`; avledes fra de to ovenfor og deretter git-SHA-en. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nei (automatisk) | _(automatisk per oppstart)_ | `src/server/authz/policies/management.ts` | Hemmelighet per prosess som beviser at det klarerte motpart-IP-stempelet kom fra OmniRoutes egen HTTP-server (`scripts/dev/peer-stamp.mjs`). Autorisasjonsmellomvaren stoler bare på forespørselens lokalitet (loopback/LAN-begrensning av LOCAL_ONLY-ruter) når stempelet inneholder dette tokenet. Genereres automatisk ved hver oppstart – la den være udefinert; angi den bare fast for flerprosessoppsett som må dele stempelet. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nei | _(ikke angitt)_ | `scripts/dev/peer-stamp.mjs` | Kommaseparerte IP-adresser eller CIDR-områder for reverse proxy-servere hvis `X-Forwarded-For` / `X-Real-IP` kan godtas av listen over tillatte/avviste IP-adresser, i tillegg til loopback, adresser i private nettverk og Cloudflare-kanter, som alltid er klarert. Trengs bare for en proxy på en annen offentlig adresse; ellers er det proxyens egen adresse som vurderes av filteret. | +Hemmeligheter som ikke er angitt, men er merket med **Ja**, genereres automatisk ved første kjøring (`JWT_SECRET`, `API_KEY_SECRET`) og lagres i `server.env` i datamappen — angi dem eksplisitt når den mappen er kortvarig. Unntaket er `OMNIROUTE_WS_BRIDGE_SECRET`: den lagres ikke i `server.env` — serverinngangspunktene genererer en kortvarig verdi per oppstart som bare broen i prosessen kan bruke, så eksterne broklienter må angi den eksplisitt. + +| Variabel | Påkrevd | Standardverdi | Kildefil | Beskrivelse | +| ---------------------------- | ------------------- | --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ja** | _(ingen)_ | `src/lib/auth` | Signerer/verifiserer alle øktinformasjonskapsler (JWT) for kontrollpanelet. Generer med `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ja** | _(ingen)_ | `src/lib/db/apiKeys.ts` | AES-krypteringsnøkkel for lagrede API-nøkkelverdier i SQLite. Generer med `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nei | _(ikke angitt)_ | `src/lib/auth/managementPassword.ts` | Angir passordet for kontrollpanelet ved oppstart så lenge ingen passord er lagret; ignoreres når et passord er lagret. npm- og kildeinstallasjoner kopierer `.env.example` til `.env`, så de starter med dens offentlig kjente plassholder `CHANGEME`: bytt den ut før første oppstart, eller angi `INITIAL_PASSWORD=` (tom) i den `.env`-filen for å opprette passordet i onboarding-veiviseren ved første besøk, slik en ikke-angitt verdi (standarden i Docker-avbildningen) gjør. Endre et lagret passord via Kontrollpanel → Innstillinger → Sikkerhet, eller kjør `omniroute reset-password` (`node bin/reset-password.mjs` i en kildeutsjekk) med `DATA_DIR` satt til serverens datamappe. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (produksjon) | _(ikke angitt)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Delt hemmelighet for den interne WebSocket-broen for Codex Responses. Autentiserer broforespørsler mellom WS-releet i Electron/nettleseren og OmniRoute. ⚠️ **PÅKREVD i produksjon – når den ikke er angitt, avvises alle WS-broforespørsler.** Generer med `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nei | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Eksplisitt ID for ugyldiggjøring av tjenestearbeiderens hurtigbuffer for PWA-skallet (#11779); først i oppløsningskjeden. | +| `SOURCE_VERSION` | Nei | _(ikke angitt)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Nummer to i kjeden – angis av PaaS-byggere (f.eks. Heroku-lignende) som den distribuerte commiten. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nei | _(avledet)_ | `src/shared/components/PwaRegister.tsx` | Offentlig verdi ved byggetid som klienten bruker til å registrere `/sw.js?v=…`; avledes fra de to ovenfor og deretter git-SHA-en. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nei (automatisk) | _(automatisk per oppstart)_ | `src/server/authz/policies/management.ts` | Hemmelighet per prosess som beviser at det klarerte motpart-IP-stempelet kom fra OmniRoutes egen HTTP-server (`scripts/dev/peer-stamp.mjs`). Autorisasjonsmellomvaren stoler bare på forespørselens lokalitet (loopback/LAN-begrensning av LOCAL_ONLY-ruter) når stempelet inneholder dette tokenet. Genereres automatisk ved hver oppstart – la den være udefinert; angi den bare fast for flerprosessoppsett som må dele stempelet. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nei | _(ikke angitt)_ | `scripts/dev/peer-stamp.mjs` | Kommaseparerte IP-adresser eller CIDR-områder for reverse proxy-servere hvis `X-Forwarded-For` / `X-Real-IP` kan godtas av listen over tillatte/avviste IP-adresser, i tillegg til loopback, adresser i private nettverk og Cloudflare-kanter, som alltid er klarert. Trengs bare for en proxy på en annen offentlig adresse; ellers er det proxyens egen adresse som vurderes av filteret. | ### Genereringskommandoer @@ -216,38 +216,38 @@ OmniRoute bruker **SQLite** (via `better-sqlite3`) for all vedvarende lagring. D ## 4. Sikkerhet og autentisering -| Variabel | Standardverdi | Kildefil | Beskrivelse | -| ------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt som kombineres med maskinvareidentifikatorer for å lage et maskinfingeravtrykk. Endre dette for hver distribusjon for å sikre isolasjon. | -| `OMNIROUTE_CLI_SALT` | _(ikke angitt = tilfeldig salt per installasjon, lagret i `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt for utledning av det lokale CLI-autentiseringstokenet. Hvis denne verdien angis, roteres alle CLI-tokener på maskinen, og verdien har alltid prioritet over det lagrede saltet. Se `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Angir `Secure`-flagget på øktinformasjonskapsler. **Må være `true`** ved kjøring bak HTTPS. | -| `REQUIRE_API_KEY` | `false` | API-mellomvare | Når denne er `true`, må alle proxyforespørsler til `/v1/*` inneholde en gyldig API-nøkkel. **Dette flagget styrer ikke tilgangen til `GET /v1/models`**, som i stedet følger innloggingsoppsettet for kontrollpanelet (`requireAuthForModels`) — derfor betyr en `401` fra `/v1/models` IKKE at inferens er beskyttet. Se `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Tillater visning av fullstendige API-nøkkelverdier i kontrollpanelets brukergrensesnitt. Kan konfigureres via funksjonsflagg i kontrollpanelet; utgjør en sikkerhetsrisiko på delte instanser. | -| `NO_LOG_API_KEY_IDS` | _(tom)_ | `src/lib/compliance/index.ts` | Kommaseparerte API-nøkkel-ID-er som unntas fra forespørselslogging (GDPR-samsvar). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ikke angitt = ubegrenset)_ | `src/shared/utils/apiKeyPolicy.ts` | Reservebudsjett for forespørsler per dag som brukes på API-nøkler der kolonnen `rate_limits` er null. Ikke angitt eller tom: ingen implisitt grense (#2289, #11017). `0` betyr det samme (ubegrenset). Et positivt heltall N aktiverer N/dag, 5N/uke, 20N/måned. Ugyldige verdier som ikke er tomme, bruker de tidligere grensene på 1000/dag, 5000/uke og 20000/måned. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimal tillatt størrelse på forespørselskroppen. Avviser nyttelaster som overskrider denne grensen. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiske forespørselskropper på eller over denne terskelen innhenter den atomiske, prosesslokale opptaksleien for tunge forespørsler før JSON-tolking (BYTE-banen, inkludert `POST /v1/responses`). Samme mekanisme i [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) for å slippe gjennom ved sunn ledig kapasitet som for strukturtunge forespørsler; fortsatt begrenset av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), slik at [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ikke kommer tilbake. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Absolutt grense for chatruter, håndhevet mot antall byte lest under begrenset innlesing, inkludert forespørsler med manglende, ugyldig eller misvisende `Content-Length`; overskridelse gir `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ikke angitt — ingen grense for antall forespørsler)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** Denne eldre grensen for ANTALL forespørsler gjelder nå bare når den er eksplisitt angitt. Når den ikke er angitt (standarden), styres tilgang for tunge chatforespørsler i stedet av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — et automatisk utledet BYTE-budsjett dimensjonert ut fra prosessens reelle minnegrense i **én prosess** (én V8-heap). To overlappende `/v1/responses`-forespørsler med ~750k tokener får ~12 GiB-heaper til å krasje (#7849) — en advarsel om **minnebudsjett**, ikke en absolutt produktgrense på 2. En frisk prosess (heap under avlastningsforholdet) KAN tillate flere samtidige, lange `/v1/responses` via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Titalls lange SSE-klienter (40–50) avhenger av heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ikke «maks. 2». Å ukritisk øke denne for å «bruke verten» gjeninnfører #7849. Multipliser **heaper** med **N uavhengige `DATA_DIR`-er** (#11024); bruk aldri `replicas>1` på én SQLite-fil. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatisk utledet)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** Overstyring for det automatisk utledede bytebudsjettet for innlesing (25 % av den strengeste minnegrensen for V8/cgroup, delt på 8x midlertidig forsterkning). Utledede og eksplisitte verdier begrenses til 8 MiB–2 GiB. En meldingskropp som er større enn det effektive budsjettet, avvises umiddelbart med `413 body_exceeds_budget`; konkurranse mellom meldingskropper som hver for seg kan behandles, gir fortsatt en `503` som kan prøves på nytt. 40–50 samtidige, langvarige SSE-klienter avhenger av dette budsjettet + heap, ikke en absolutt grense på «maks. 2». Les `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` på `/api/monitoring/health` før justering. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Avlastningsforhold ved heap-press (`heapUsed / heap_size_limit`) for tung tilgang av typen BYTE og STRUCTURE (#10183, #10268, #10437). En samtidig tung forespørsel utover `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` avvises bare med `503`, som kan prøves på nytt, når heapen OGSÅ er på eller over dette forholdet; på en frisk heap tillates den i stedet via reservekapasiteten for frisk heap. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standard `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begrenset ekstrakapasitet for hurtigbanen ved frisk heap (#10437) for **både** STRUCTURE og BYTE (`admitChatRequest`, inkludert meldingskropper ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Uten denne grensen omgikk alle forespørsler ved høy belastning, men frisk heap, tilgangskontrollen uten noen øvre grense. Når så mange samtidige leieavtaler er aktive gjennom banen for frisk heap, går ytterligere forespørsler ved høy belastning videre til den SAMME banen for begrenset venting/avlastning som brukes ved reelt heap-press. `0` deaktiverer omgåelsen fullstendig. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Antall meldinger som klassifiserer en chatforespørsel som tung, selv når meldingskroppen er under bytegrensen. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Antall verktøy som klassifiserer en chatforespørsel som tung, selv når meldingskroppen er under bytegrensen. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservativt tokenestimat basert på strengstørrelse som klassifiserer en forespørsel som tung; dette er en tilnærming for tilgangskostnad, ikke leverandørens tokenisering for fakturering. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (deaktivert) | `src/shared/middleware/chatBodyAdmission.ts` | Valgfri, eksplisitt aktivert grense for chatlogg. Deaktivert som standard: Et antall meldinger er en distribusjonspolicy, ikke en universell egenskap ved en forespørsel, og en grense her avviser samtaler permanent med `413` før komprimeringsforløpet kan gjøre dem behandlingsbare. Heap-vekst begrenses av `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` og avlastning ved heap-press. Angi en positiv verdi i minnebegrensede distribusjoner som trenger en absolutt grense; overskridelse gir da en strukturert `413` som angir at komprimering kreves. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Absolutt grense for en ikke-strømmet oppstrømsrespons som bufres fullstendig i minnet. Ved overskridelse avbrytes oppstrømsleseren, og forespørselen feiler raskt i stedet for å bygge opp en ubegrenset streng til heapen er uttømt. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalt antall byte på ledningen som videresendes fra oppstrøms responshoder. Når budsjettet overskrides, forkastes hoder med lavere prioritet (f.eks. egendefinerte `x-codex-*`, `x-oai-request-id`) for å holde seg innenfor vanlige grenser for responshoder i reverse proxy-er. Angi en høyere verdi for å videresende mer oppstrømsmetadata, på bekostning av større responshoder. | -| `CORS_ORIGIN` | _(ikke angitt)_ | `src/server/cors/origins.ts` | Eldre CORS-tillatelsesliste for én opprinnelse. Foretrekk `CORS_ALLOWED_ORIGINS` for nye distribusjoner. CORS er bare for nettleserbaserte API-klienter på tvers av opprinnelser; autentiserte skriveoperasjoner i kontrollpanelet bruker i stedet forespørsler med samme opprinnelse samt øktbundet CSRF-beskyttelse. | -| `CORS_ALLOWED_ORIGINS` | _(ikke angitt)_ | `src/server/cors/origins.ts` | Kommaseparert CORS-tillatelsesliste. Ingen jokerverdi sendes med mindre `CORS_ALLOW_ALL=true` er eksplisitt konfigurert. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Utvei kun for utvikling, som gjengir enhver `Origin` fra nettleseren. Ikke aktiver dette i delte miljøer eller produksjonsmiljøer. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokker leverandørkall som er rettet mot private IP-områder, loopback-områder eller link-local-områder. Deaktiver bare i isolerte testmiljøer. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Tillat leverandør-URL-er som peker til private/lokale nettverk (localhost, 192.168.x.x, 10.x.x.x osv.). **KREVES for egenhostede leverandører** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Når verdien er `false`, avviser kontrollpanelet validering av lokale URL-er. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Tillat å legge til/validere leverandører på lokale/private adresser (127.0.0.1, localhost, LAN, private områder) — begrenset til valideringsflyten for leverandører. **Standardverdi `true`** (lokalt først); angi `false` for å håndheve streng blokkering som bare tillater offentlige adresser. Endepunkter for skymetadata (169.254.169.254, metadata.google.internal) forblir blokkert uansett. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | La rutene `/v1/audio/*` (transkripsjoner, tale, oversettelser) bruke en OpenAI-kompatibel leverandørnode som driftes utenfor localhost. Deaktivert som standard — ruting av lyd til en ekstern vert endrer utgående identitet og må være en eksplisitt beslutning fra operatøren. Loopback-/private noder (localhost, 127.0.0.1, 172.16-31.x) er alltid tillatt og upåvirket. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | La `POST /v1/rerank` (og minnemotorens loopback-trinn for omrangering) bruke en OpenAI-kompatibel leverandørnode som driftes utenfor localhost — en maskin på LAN-et eller en Tailscale-node som kjører TEI, Infinity, vLLM osv. Deaktivert som standard — ruting til en ekstern vert endrer utgående identitet og må være en eksplisitt beslutning fra operatøren. Loopback-noder (localhost, 127.0.0.1, 172.16-31.x) er alltid tillatt og upåvirket. Eksterne noder må også bestå policyen for leverandørers utgående URL-er (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); verter for skymetadata rutes aldri til. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Når OIDC er aktivert, deaktiveres passordpålogging slik at brukere bare kan autentisere seg via OIDC-basert enkel pålogging. Det korte aliaset `OIDC_DISABLE_PASSWORD_LOGIN` godtas også; funksjonsflagget i kontrollpanelet med samme nøkkel har forrang. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kort alias for `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variabel | Standardverdi | Kildefil | Beskrivelse | +| ------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt som kombineres med maskinvareidentifikatorer for å lage et maskinfingeravtrykk. Endre dette for hver distribusjon for å sikre isolasjon. | +| `OMNIROUTE_CLI_SALT` | _(ikke angitt = tilfeldig salt per installasjon, lagret i `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt for utledning av det lokale CLI-autentiseringstokenet. Hvis denne verdien angis, roteres alle CLI-tokener på maskinen, og verdien har alltid prioritet over det lagrede saltet. Se `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Angir `Secure`-flagget på øktinformasjonskapsler. **Må være `true`** ved kjøring bak HTTPS. | +| `REQUIRE_API_KEY` | `false` | API-mellomvare | Når denne er `true`, må alle proxyforespørsler til `/v1/*` inneholde en gyldig API-nøkkel. **Dette flagget styrer ikke tilgangen til `GET /v1/models`**, som i stedet følger innloggingsoppsettet for kontrollpanelet (`requireAuthForModels`) — derfor betyr en `401` fra `/v1/models` IKKE at inferens er beskyttet. Se `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Tillater visning av fullstendige API-nøkkelverdier i kontrollpanelets brukergrensesnitt. Kan konfigureres via funksjonsflagg i kontrollpanelet; utgjør en sikkerhetsrisiko på delte instanser. | +| `NO_LOG_API_KEY_IDS` | _(tom)_ | `src/lib/compliance/index.ts` | Kommaseparerte API-nøkkel-ID-er som unntas fra forespørselslogging (GDPR-samsvar). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ikke angitt = ubegrenset)_ | `src/shared/utils/apiKeyPolicy.ts` | Reservebudsjett for forespørsler per dag som brukes på API-nøkler der kolonnen `rate_limits` er null. Ikke angitt eller tom: ingen implisitt grense (#2289, #11017). `0` betyr det samme (ubegrenset). Et positivt heltall N aktiverer N/dag, 5N/uke, 20N/måned. Ugyldige verdier som ikke er tomme, bruker de tidligere grensene på 1000/dag, 5000/uke og 20000/måned. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksimal tillatt størrelse på forespørselskroppen. Avviser nyttelaster som overskrider denne grensen. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiske forespørselskropper på eller over denne terskelen innhenter den atomiske, prosesslokale opptaksleien for tunge forespørsler før JSON-tolking (BYTE-banen, inkludert `POST /v1/responses`). Samme mekanisme i [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) for å slippe gjennom ved sunn ledig kapasitet som for strukturtunge forespørsler; fortsatt begrenset av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), slik at [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ikke kommer tilbake. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Absolutt grense for chatruter, håndhevet mot antall byte lest under begrenset innlesing, inkludert forespørsler med manglende, ugyldig eller misvisende `Content-Length`; overskridelse gir `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ikke angitt — ingen grense for antall forespørsler)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** Denne eldre grensen for ANTALL forespørsler gjelder nå bare når den er eksplisitt angitt. Når den ikke er angitt (standarden), styres tilgang for tunge chatforespørsler i stedet av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — et automatisk utledet BYTE-budsjett dimensjonert ut fra prosessens reelle minnegrense i **én prosess** (én V8-heap). To overlappende `/v1/responses`-forespørsler med ~750k tokener får ~12 GiB-heaper til å krasje (#7849) — en advarsel om **minnebudsjett**, ikke en absolutt produktgrense på 2. En frisk prosess (heap under avlastningsforholdet) KAN tillate flere samtidige, lange `/v1/responses` via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Titalls lange SSE-klienter (40–50) avhenger av heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ikke «maks. 2». Å ukritisk øke denne for å «bruke verten» gjeninnfører #7849. Multipliser **heaper** med **N uavhengige `DATA_DIR`-er** (#11024); bruk aldri `replicas>1` på én SQLite-fil. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatisk utledet)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** Overstyring for det automatisk utledede bytebudsjettet for innlesing (25 % av den strengeste minnegrensen for V8/cgroup, delt på 8x midlertidig forsterkning). Utledede og eksplisitte verdier begrenses til 8 MiB–2 GiB. En meldingskropp som er større enn det effektive budsjettet, avvises umiddelbart med `413 body_exceeds_budget`; konkurranse mellom meldingskropper som hver for seg kan behandles, gir fortsatt en `503` som kan prøves på nytt. 40–50 samtidige, langvarige SSE-klienter avhenger av dette budsjettet + heap, ikke en absolutt grense på «maks. 2». Les `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` på `/api/monitoring/health` før justering. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Avlastningsforhold ved heap-press (`heapUsed / heap_size_limit`) for tung tilgang av typen BYTE og STRUCTURE (#10183, #10268, #10437). En samtidig tung forespørsel utover `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` avvises bare med `503`, som kan prøves på nytt, når heapen OGSÅ er på eller over dette forholdet; på en frisk heap tillates den i stedet via reservekapasiteten for frisk heap. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standard `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begrenset ekstrakapasitet for hurtigbanen ved frisk heap (#10437) for **både** STRUCTURE og BYTE (`admitChatRequest`, inkludert meldingskropper ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Uten denne grensen omgikk alle forespørsler ved høy belastning, men frisk heap, tilgangskontrollen uten noen øvre grense. Når så mange samtidige leieavtaler er aktive gjennom banen for frisk heap, går ytterligere forespørsler ved høy belastning videre til den SAMME banen for begrenset venting/avlastning som brukes ved reelt heap-press. `0` deaktiverer omgåelsen fullstendig. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Antall meldinger som klassifiserer en chatforespørsel som tung, selv når meldingskroppen er under bytegrensen. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Antall verktøy som klassifiserer en chatforespørsel som tung, selv når meldingskroppen er under bytegrensen. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservativt tokenestimat basert på strengstørrelse som klassifiserer en forespørsel som tung; dette er en tilnærming for tilgangskostnad, ikke leverandørens tokenisering for fakturering. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (deaktivert) | `src/shared/middleware/chatBodyAdmission.ts` | Valgfri, eksplisitt aktivert grense for chatlogg. Deaktivert som standard: Et antall meldinger er en distribusjonspolicy, ikke en universell egenskap ved en forespørsel, og en grense her avviser samtaler permanent med `413` før komprimeringsforløpet kan gjøre dem behandlingsbare. Heap-vekst begrenses av `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` og avlastning ved heap-press. Angi en positiv verdi i minnebegrensede distribusjoner som trenger en absolutt grense; overskridelse gir da en strukturert `413` som angir at komprimering kreves. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Absolutt grense for en ikke-strømmet oppstrømsrespons som bufres fullstendig i minnet. Ved overskridelse avbrytes oppstrømsleseren, og forespørselen feiler raskt i stedet for å bygge opp en ubegrenset streng til heapen er uttømt. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalt antall byte på ledningen som videresendes fra oppstrøms responshoder. Når budsjettet overskrides, forkastes hoder med lavere prioritet (f.eks. egendefinerte `x-codex-*`, `x-oai-request-id`) for å holde seg innenfor vanlige grenser for responshoder i reverse proxy-er. Angi en høyere verdi for å videresende mer oppstrømsmetadata, på bekostning av større responshoder. | +| `CORS_ORIGIN` | _(ikke angitt)_ | `src/server/cors/origins.ts` | Eldre CORS-tillatelsesliste for én opprinnelse. Foretrekk `CORS_ALLOWED_ORIGINS` for nye distribusjoner. CORS er bare for nettleserbaserte API-klienter på tvers av opprinnelser; autentiserte skriveoperasjoner i kontrollpanelet bruker i stedet forespørsler med samme opprinnelse samt øktbundet CSRF-beskyttelse. | +| `CORS_ALLOWED_ORIGINS` | _(ikke angitt)_ | `src/server/cors/origins.ts` | Kommaseparert CORS-tillatelsesliste. Ingen jokerverdi sendes med mindre `CORS_ALLOW_ALL=true` er eksplisitt konfigurert. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Utvei kun for utvikling, som gjengir enhver `Origin` fra nettleseren. Ikke aktiver dette i delte miljøer eller produksjonsmiljøer. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Gammelt alias. Bryteren i kontrollpanelet (en databaseoverskrivning) leses før denne miljøverdien; `false`, `0`, `no` eller `off` i en av dem slår av vertskontrollene slik `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` gjør, selv når kontrollpanelbryteren for det flagget er lagret som av; andre verdier overlater vakten til de to flaggene nedenfor. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Slår av vakten for utgående URL-ers vertskontroller, inkludert blokkeringen av skymetadata (hele 169.254.0.0/16, metadata.google.internal), på leverandørstiene som følger vaktmodusen: URL-validering, modelloppdagelse, basis-URL-er for leverandørnoder (inkludert eksterne omrangeringsnoder) og proxy-fallback-testen, samt andre utgående leverandørnære stier som henting av eksterne bilder/medier. Den tillater også private webhook-mål, bortsett fra endepunkter for skymetadata/link-local, som forblir blokkert selv når flagget er på. Chatforespørsler som sendes via `BaseExecutor.execute()`, beholder sin skymetadata-kontroll. Lokale leverandører fungerer med standardverdiene: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (standard `true`) tillater allerede lokale og LAN-URL-er på validerings-, oppdagelses- og leverandørnodestiene; proxy-fallback-testen og private webhook-mål konsulterer bare dette flagget og blokkerer lokale/LAN-verter så lenge det er av. Overstyrer `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. En verdi lagret fra kontrollpanelbryteren har forrang fremfor denne variabelen. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Tillater leverandør-URL-er på lokale/private adresser (127.0.0.1, localhost, LAN, private områder) for leverandørvalidering, modelloppdagelse, chat og de andre utgående leverandørnære stiene som vaktmodusen styrer. **Standardverdi `true`** (lokalt først): vakten blokkerer da endepunkter for skymetadata — hele 169.254.0.0/16 pluss de kjente metadata-vertsnavnene (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Angi `false` for kun-offentlig-modus, som også blokkerer private og loopback-verter. Vakten kontrollerer vertsnavnet eller IP-literalen slik det er skrevet, før ethvert DNS-oppslag; i kun-offentlig-modus løser safe-fetch-wrapperen i tillegg opp vertsnavnet og avviser alle private svar. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` slår av disse kontrollene på stiene som raden til flagget lister. Innebygde lokale leverandører, som LM Studio, Ollama, vLLM, Llamafile og Triton, hopper over alle vertskontroller for nøkkelvalidering og chat. En verdi lagret fra kontrollpanelbryteren har forrang fremfor denne variabelen. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | La rutene `/v1/audio/*` (transkripsjoner, tale, oversettelser) bruke en OpenAI-kompatibel leverandørnode som driftes utenfor localhost. Deaktivert som standard — ruting av lyd til en ekstern vert endrer utgående identitet og må være en eksplisitt beslutning fra operatøren. Loopback-/private noder (localhost, 127.0.0.1, 172.16-31.x) er alltid tillatt og upåvirket. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | La `POST /v1/rerank` (og minnemotorens loopback-trinn for omrangering) bruke en OpenAI-kompatibel leverandørnode som driftes utenfor localhost — en maskin på LAN-et eller en Tailscale-node som kjører TEI, Infinity, vLLM osv. Deaktivert som standard — ruting til en ekstern vert endrer utgående identitet og må være en eksplisitt beslutning fra operatøren. Loopback-noder (localhost, 127.0.0.1, 172.16-31.x) er alltid tillatt og upåvirket. Basis-URL-en til en ekstern node må også bestå policyen for leverandørers utgående URL-er (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Når OIDC er aktivert, deaktiveres passordpålogging slik at brukere bare kan autentisere seg via OIDC-basert enkel pålogging. Det korte aliaset `OIDC_DISABLE_PASSWORD_LOGIN` godtas også; funksjonsflagget i kontrollpanelet med samme nøkkel har forrang. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kort alias for `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Sjekkliste for sikkerhetsherding diff --git a/docs/i18n/or/CONTRIBUTING.md b/docs/i18n/or/CONTRIBUTING.md index 9405c7c248fe..f1967573043b 100644 --- a/docs/i18n/or/CONTRIBUTING.md +++ b/docs/i18n/or/CONTRIBUTING.md @@ -397,22 +397,22 @@ docs/ --- -## ପୁଲ୍ ରିକ୍ୱେଷ୍ଟ୍ ଯାଞ୍ଚତାଲିକା - -- [ ] ପରୀକ୍ଷାଗୁଡ଼ିକ ସଫଳ ହୁଏ (`npm test`) -- [ ] ଲିଣ୍ଟିଂ ସଫଳ ହୁଏ (`npm run lint`) -- [ ] ବିଲ୍ଡ୍ ସଫଳ ହୁଏ (`npm run build`) -- [ ] ନୂତନ ସାର୍ବଜନୀନ ଫଙ୍କସନ୍ ଏବଂ ଇଣ୍ଟରଫେସ୍ଗୁଡ଼ିକ ପାଇଁ TypeScript ପ୍ରକାର ଯୋଡ଼ାଯାଇଛି -- [ ] କୌଣସି ହାର୍ଡକୋଡ୍ କରାଯାଇଥିବା ଗୁପ୍ତ ତଥ୍ୟ କିମ୍ବା ଫଲ୍ବ୍ୟାକ୍ ମୂଲ୍ୟ ନାହିଁ -- [ ] ସାର୍ବଜନୀନ ଅପ୍ଷ୍ଟ୍ରିମ୍ ପରିଚୟପତ୍ରଗୁଡ଼ିକ `resolvePublicCred()` ମାଧ୍ୟମରେ ଏମ୍ବେଡ୍ କରାଯାଇଛି ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ଦେଖନ୍ତୁ), କେବେବି ଲିଟେରାଲ୍ ଭାବେ ନୁହେଁ -- [ ] ତ୍ରୁଟି ପ୍ରତିକ୍ରିୟାଗୁଡ଼ିକ `buildErrorBody()` / `sanitizeErrorMessage()` ମାଧ୍ୟମରେ ଯାଏ — ପ୍ରତିକ୍ରିୟା ବଡିଗୁଡ଼ିକରେ କୌଣସି କଞ୍ଚା ଷ୍ଟାକ୍ ଟ୍ରେସ୍ ନାହିଁ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ଦେଖନ୍ତୁ) -- [ ] ଶେଲ୍ କମାଣ୍ଡ୍ଗୁଡ଼ିକ (`exec` / `spawn`) ଷ୍ଟ୍ରିଙ୍ଗ୍ ଇଣ୍ଟରପୋଲେସନ୍ ମାଧ୍ୟମରେ ନୁହେଁ, `env` ମାଧ୍ୟମରେ ରନ୍ଟାଇମ୍ ମୂଲ୍ୟଗୁଡ଼ିକ ପାସ୍ କରେ -- [ ] ସମସ୍ତ ଇନ୍ପୁଟ୍ Zod ସ୍କିମାଗୁଡ଼ିକ ସହିତ ବୈଧତା ଯାଞ୍ଚ କରାଯାଇଛି -- [ ] ବ୍ୟବହାରକାରୀଙ୍କୁ ପ୍ରଭାବିତ କରୁଥିବା ପରିବର୍ତ୍ତନ ପାଇଁ `changelog.d/{features|fixes|maintenance}/-.md` ଅଧୀନରେ Changelog **ଖଣ୍ଡ** ଯୋଡ଼ାଯାଇଛି ([`changelog.d/README.md`](./changelog.d/README.md) ଦେଖନ୍ତୁ) — `CHANGELOG.md`କୁ ସିଧାସଳଖ ସମ୍ପାଦନା **କରନ୍ତୁ ନାହିଁ**; ଖଣ୍ଡଗୁଡ଼ିକ ରିଲିଜ୍ ସମୟରେ ଏକତ୍ରିତ କରାଯାଏ ଏବଂ PRଗୁଡ଼ିକ ମଧ୍ୟରେ କେବେବି ବିବାଦ ସୃଷ୍ଟି କରେ ନାହିଁ -- [ ] ଡକ୍ୟୁମେଣ୍ଟେସନ୍ ଅଦ୍ୟତନ କରାଯାଇଛି (ଯଦି ପ୍ରଯୁଜ୍ୟ) +## ପୁଲ୍ ରିକ୍ୱେଷ୍ଟ ଯାଞ୍ଚତାଲିକା + +- [ ] ପରୀକ୍ଷଣଗୁଡ଼ିକ ସଫଳ ହୋଇଛି (`npm test`) +- [ ] ଲିଣ୍ଟିଂ ସଫଳ ହୋଇଛି (`npm run lint`) +- [ ] ବିଲ୍ଡ ସଫଳ ହୋଇଛି (`npm run build`) +- [ ] ନୂତନ ସାର୍ବଜନୀନ ଫଙ୍କସନ୍ ଏବଂ ଇଣ୍ଟରଫେସ୍ଗୁଡ଼ିକ ପାଇଁ TypeScript ଟାଇପ୍ ଯୋଡ଼ାଯାଇଛି +- [ ] କୌଣସି ହାର୍ଡକୋଡ୍ କରାଯାଇଥିବା ସିକ୍ରେଟ୍ କିମ୍ବା ଫଲ୍ବ୍ୟାକ୍ ମୂଲ୍ୟ ନାହିଁ +- [ ] ସାର୍ବଜନୀନ ଅପ୍ଷ୍ଟ୍ରିମ୍ କ୍ରେଡେନ୍ସିଆଲ୍ଗୁଡ଼ିକୁ `resolvePublicCred()` ମାଧ୍ୟମରେ ଏମ୍ବେଡ୍ କରାଯାଇଛି ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ଦେଖନ୍ତୁ), କେବେବି ଲିଟେରାଲ୍ ଭାବରେ ନୁହେଁ +- [ ] ତ୍ରୁଟି ପ୍ରତିକ୍ରିୟାଗୁଡ଼ିକ `buildErrorBody()` / `sanitizeErrorMessage()` ମାଧ୍ୟମରେ ରୁଟ୍ ହୋଇଛି — ପ୍ରତିକ୍ରିୟା ବଡିରେ କୌଣସି ଅପରିବର୍ତ୍ତିତ ଷ୍ଟାକ୍ ଟ୍ରେସ୍ ନାହିଁ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ଦେଖନ୍ତୁ) +- [ ] ସେଲ୍ କମାଣ୍ଡଗୁଡ଼ିକ (`exec` / `spawn`) ଷ୍ଟ୍ରିଙ୍ଗ୍ ଇଣ୍ଟରପୋଲେସନ୍ ମାଧ୍ୟମରେ ନୁହେଁ, `env` ମାଧ୍ୟମରେ ରନ୍ଟାଇମ୍ ମୂଲ୍ୟ ପାସ୍ କରନ୍ତି +- [ ] ସମସ୍ତ ଇନ୍ପୁଟ୍କୁ Zod ସ୍କିମା ସହିତ ବୈଧୀକରଣ କରାଯାଇଛି +- [ ] ବ୍ୟବହାରକାରୀଙ୍କୁ ପ୍ରଭାବିତ କରୁଥିବା ପରିବର୍ତ୍ତନଗୁଡ଼ିକ ପାଇଁ `changelog.d/{features|fixes|maintenance}/-.md` ଅଧୀନରେ ଚେଞ୍ଜଲଗ୍ **ଫ୍ରାଗ୍ମେଣ୍ଟ୍** ଯୋଡ଼ାଯାଇଛି ([`changelog.d/README.md`](./changelog.d/README.md) ଦେଖନ୍ତୁ) — `CHANGELOG.md`କୁ ସିଧାସଳଖ ସମ୍ପାଦନା କରନ୍ତୁ **ନାହିଁ**; ରିଲିଜ୍ ସମୟରେ ଫ୍ରାଗ୍ମେଣ୍ଟ୍ଗୁଡ଼ିକୁ ଏକତ୍ର କରାଯାଏ ଏବଂ PRଗୁଡ଼ିକ ମଧ୍ୟରେ କେବେବି ଦ୍ୱନ୍ଦ୍ୱ ସୃଷ୍ଟି ହୁଏ ନାହିଁ +- [ ] ଡକ୍ୟୁମେଣ୍ଟେସନ୍ ଅପଡେଟ୍ କରାଯାଇଛି (ଯଦି ପ୍ରଯୁଜ୍ୟ) - [ ] କୌଣସି ନୂତନ CodeQL / Secret-Scanning ସତର୍କତା ଖୋଲାଯାଇନାହିଁ, କିମ୍ବା ପ୍ରତ୍ୟେକଟିକୁ ସମ୍ପର୍କିତ `docs/security/` ଡକ୍ୟୁମେଣ୍ଟ୍ ଉଲ୍ଲେଖ କରୁଥିବା ବୈଷୟିକ ଯଥାର୍ଥତା ସହିତ ଖାରଜ କରାଯାଇଛି -- [ ] ଚାଇଲ୍ଡ୍ ପ୍ରୋସେସ୍ ଆରମ୍ଭ କରୁଥିବା ରୁଟ୍ଗୁଡ଼ିକ (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts`ରେ `isLocalOnlyPath()` ଭାବେ ଶ୍ରେଣୀଭୁକ୍ତ କରାଯାଇଛି — [କଠୋର ନିୟମ #15](docs/security/ROUTE_GUARD_TIERS.md) ଦେଖନ୍ତୁ -- [ ] କମିଟ୍ ସନ୍ଦେଶଗୁଡ଼ିକରେ କୌଣସି `Co-Authored-By` ଟ୍ରେଲର୍ ନାହିଁ — କମିଟ୍ଗୁଡ଼ିକ କେବଳ ରିପୋଜିଟୋରି ମାଲିକଙ୍କ Git ପରିଚୟ ଅଧୀନରେ ଦେଖାଯିବା ଆବଶ୍ୟକ (କଠୋର ନିୟମ #16) +- [ ] ଚାଇଲ୍ଡ ପ୍ରୋସେସ୍ ସ୍ପନ୍ କରୁଥିବା ରୁଟ୍ଗୁଡ଼ିକ (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts`ରେ `isLocalOnlyPath()` ଭାବରେ ବର୍ଗୀକୃତ ହୋଇଛି — [କଠୋର ନିୟମ #15](docs/security/ROUTE_GUARD_TIERS.md) ଦେଖନ୍ତୁ +- [ ] କମିଟ୍ ବାର୍ତ୍ତାଗୁଡ଼ିକରେ କୌଣସି AI/ବଟ୍ `Co-authored-by` ଟ୍ରେଲର୍ ନାହିଁ (କଠୋର ନିୟମ #16) — ଯେଉଁ ମାନବ ସହଯୋଗୀଙ୍କ କାର୍ଯ୍ୟ ପୁନଃବ୍ୟବହୃତ ହୋଇଛି, ସେମାନଙ୍କୁ ମାନକ `Co-authored-by: Name ` ଟ୍ରେଲର୍ ସହିତ ଶ୍ରେୟ ଦିଆଯାଇଛି --- diff --git a/docs/i18n/or/docs/architecture/QUALITY_GATES.md b/docs/i18n/or/docs/architecture/QUALITY_GATES.md index 56e3a7b0d832..12e9f60ed290 100644 --- a/docs/i18n/or/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/or/docs/architecture/QUALITY_GATES.md @@ -15,254 +15,260 @@ --- -## ଗେଟ୍ ଇନଭେଣ୍ଟୋରୀ ଏବଂ ନିଷ୍ପାଦନ ପ୍ରୋଫାଇଲ୍ +## ଗେଟ୍ ଇନଭେଣ୍ଟରି ଏବଂ ଏକ୍ସିକ୍ୟୁସନ୍ ପ୍ରୋଫାଇଲ୍ -### ପ୍ରାର୍ଥୀ ଗ୍ରହଣ +### ପ୍ରାର୍ଥୀ ଅନୁମୋଦନ -CI ଏବଂ Quality Gates ୱାର୍କଫ୍ଲୋ ପ୍ରତ୍ୟେକ ଏକ ସ୍ଥିର ରାୟ ପ୍ରଦାନ କରେ: `Gate / CI` ଏବଂ -`Gate / Quality`। ସେମାନଙ୍କର ସଂସ୍କରଣଭିତ୍ତିକ ଗ୍ରହଣ ନୀତି ପ୍ରତ୍ୟେକ ଅପ୍ଷ୍ଟ୍ରିମ୍ ଜବ୍କୁ -ଆବଶ୍ୟକ କିମ୍ବା ପରାମର୍ଶମୂଳକ ଭାବେ ତାଲିକାଭୁକ୍ତ କରେ। ଏକ ପ୍ରଯୋଜ୍ୟ ଆବଶ୍ୟକ ଜବ୍ ନିଶ୍ଚିତ ଭାବେ ସଫଳ ହେବା ଉଚିତ: ଅନୁପସ୍ଥିତ, -ବାତିଲ ହୋଇଥିବା, ଏଡ଼ାଇ ଦିଆଯାଇଥିବା, ବିଚାରାଧୀନ ଏବଂ ଅଜଣା ଫଳାଫଳ PASS ପ୍ରତିଷ୍ଠା କରିପାରିବ ନାହିଁ। ଏକ ବୈଧ -କେବଳ-ଡକ୍ସ କିମ୍ବା କେବଳ-କ୍ୟାଟାଲଗ୍ ବର୍ଗୀକରଣ କୌଣସି କୋଡ୍ ଲେନ୍କୁ ଅପ୍ରଯୋଜ୍ୟ କରିପାରେ; +CI ଏବଂ Quality Gates ୱର୍କଫ୍ଲୋଗୁଡ଼ିକ ପ୍ରତ୍ୟେକ ଏକ ସ୍ଥିର ନିଷ୍ପତ୍ତି ପ୍ରଦାନ କରେ: `Gate / CI` ଏବଂ +`Gate / Quality`। ସେମାନଙ୍କର ସଂସ୍କରଣଭିତ୍ତିକ ଅନୁମୋଦନ ନୀତି ପ୍ରତ୍ୟେକ ଅପ୍ଷ୍ଟ୍ରିମ୍ ଜବ୍କୁ +ଆବଶ୍ୟକ କିମ୍ବା ପରାମର୍ଶମୂଳକ ଭାବେ ତାଲିକାଭୁକ୍ତ କରେ। ଏକ ପ୍ରଯୁଜ୍ୟ ଆବଶ୍ୟକ ଜବ୍ ନିଶ୍ଚିତ ଭାବେ ସଫଳ ହେବା ଆବଶ୍ୟକ: ଅନୁପସ୍ଥିତ, +ବାତିଲ ହୋଇଥିବା, ଏଡ଼ାଇ ଦିଆଯାଇଥିବା, ବିଚାରାଧୀନ ଏବଂ ଅଜଣା ଫଳାଫଳ PASS ସ୍ଥାପନ କରିପାରିବ ନାହିଁ। ଏକ ବୈଧ +କେବଳ-ଡକ୍ସ କିମ୍ବା କେବଳ-କ୍ୟାଟାଲଗ୍ ବର୍ଗୀକରଣ ଏକ କୋଡ୍ ଲେନ୍କୁ ଅପ୍ରଯୁଜ୍ୟ କରିପାରେ; ଏକ ଡ୍ରାଫ୍ଟ PR ଗ୍ରହଣୀୟ ପ୍ରାର୍ଥୀ ନୁହେଁ। ଏକ `hotfix` ଲେବଲ୍ ପ୍ରମାଣର ଆବଶ୍ୟକତାକୁ ଛାଡ଼ କରେ ନାହିଁ। -ଉଭୟ ୱାର୍କଫ୍ଲୋ PR ଏବଂ main/release ବ୍ରାଞ୍ଚଗୁଡ଼ିକୁ ପୁଶ୍, ମାନୁଆଲ୍ ଡିସ୍ପ୍ୟାଚ୍ ଏବଂ -ମର୍ଜ୍-ଗ୍ରୁପ୍ ଇଭେଣ୍ଟଗୁଡ଼ିକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ। ପୁଶ୍, ଡିସ୍ପ୍ୟାଚ୍ ଏବଂ ମର୍ଜ୍-ଗ୍ରୁପ୍ ସମ୍ପୂର୍ଣ୍ଣ ଚୟନକୁ ଚଲାଏ। ଫୋର୍କ୍ଗୁଡ଼ିକ -ଏବଂ ମର୍ଜ୍ ଗ୍ରୁପ୍ଗୁଡ଼ିକ ସେହି ଜବ୍ଗୁଡ଼ିକ ପାଇଁ ହୋଷ୍ଟେଡ୍ ରନର୍ ବ୍ୟବହାର କରନ୍ତି, ଯେଉଁଗୁଡ଼ିକ ଅନ୍ୟଥା ସେଲ୍ଫ-ହୋଷ୍ଟେଡ୍ -ରନର୍ ଚୟନ କରିଥାନ୍ତେ; ରୋଲ୍ଆଉଟ୍ ପୂର୍ବରୁ ପର୍ଯ୍ୟାପ୍ତ ହୋଷ୍ଟେଡ୍ କ୍ଷମତା ଯାଞ୍ଚ କରିବା ଆବଶ୍ୟକ। +ଉଭୟ ୱର୍କଫ୍ଲୋ PR ଏବଂ main/release ବ୍ରାଞ୍ଚଗୁଡ଼ିକୁ ପୁସ୍, ମାନୁଆଲ୍ ଡିସ୍ପାଚ୍ ଏବଂ +ମର୍ଜ୍-ଗ୍ରୁପ୍ ଇଭେଣ୍ଟଗୁଡ଼ିକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ। ପୁସ୍, ଡିସ୍ପାଚ୍ ଏବଂ ମର୍ଜ୍-ଗ୍ରୁପ୍ ସମ୍ପୂର୍ଣ୍ଣ ଚୟନ ଚଲାଏ। ଫୋର୍କ୍ +ଏବଂ ମର୍ଜ୍ ଗ୍ରୁପ୍ଗୁଡ଼ିକ ସେହି ଜବ୍ଗୁଡ଼ିକ ପାଇଁ ହୋଷ୍ଟେଡ୍ ରନର୍ ବ୍ୟବହାର କରନ୍ତି, ଯେଉଁଗୁଡ଼ିକ ଅନ୍ୟଥା ସେଲ୍ଫ୍-ହୋଷ୍ଟେଡ୍ +ରନର୍ ଚୟନ କରିଥାନ୍ତେ; ରୋଲ୍ଆଉଟ୍ ପୂର୍ବରୁ ପର୍ଯ୍ୟାପ୍ତ ହୋଷ୍ଟେଡ୍ କ୍ଷମତା ଯାଞ୍ଚ କରାଯିବା ଆବଶ୍ୟକ। -ପ୍ରତ୍ୟେକ JSON ରସିଦ ଚେକ୍ଆଉଟ୍ ହୋଇଥିବା SHA, ୱାର୍କଫ୍ଲୋ ରନ୍ ଏବଂ ପ୍ରୟାସକୁ ଚିହ୍ନଟ କରେ। -CLI ଏକ ଚେକ୍ଆଉଟ୍/ଇଭେଣ୍ଟ SHA ଅମେଳକୁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରେ। ୱାର୍କଫ୍ଲୋ ପରୀକ୍ଷାଗୁଡ଼ିକ ନୀତି ସଦସ୍ୟତାକୁ -ରାୟ ଜବ୍ର `needs` ତାଲିକା ସହିତ ବାନ୍ଧିଥାଏ, ଯାହାଦ୍ୱାରା ଏକ ନୂତନ କିମ୍ବା ଅପସାରିତ ଲେନ୍ ନିରବରେ ଅଦୃଶ୍ୟ ହୋଇପାରିବ ନାହିଁ। -ରସିଦଗୁଡ଼ିକ ସେମାନଙ୍କ ନିଜସ୍ୱ ୱାର୍କଫ୍ଲୋକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ, ପ୍ରକାଶନ, ଡିପ୍ଲୟମେଣ୍ଟ କିମ୍ବା -କୌଣସି ବିଦ୍ୟମାନ ପରାମର୍ଶମୂଳକ ସ୍କାନର୍ର ଆଭ୍ୟନ୍ତରୀଣ କାର୍ଯ୍ୟକୁ ନୁହେଁ। ବ୍ରାଞ୍ଚ ନିୟମଗୁଡ଼ିକରେ ଉଭୟ ଚେକ୍ ନାମକୁ ସକ୍ରିୟ କରିବା -ଏକ ପୃଥକ ପ୍ରଶାସନିକ ପରିବର୍ତ୍ତନ; ଏହି ଜବ୍ଗୁଡ଼ିକୁ ଯୋଡ଼ିବା ନିଜେ କୌଣସି ବ୍ରାଞ୍ଚକୁ ସୁରକ୍ଷିତ କରେ ନାହିଁ। +ପ୍ରତ୍ୟେକ JSON ରସିଦ ଚେକ୍ଆଉଟ୍ ହୋଇଥିବା SHA, ୱର୍କଫ୍ଲୋ ରନ୍ ଏବଂ ପ୍ରୟାସକୁ ଚିହ୍ନଟ କରେ। +CLI ଏକ ଚେକ୍ଆଉଟ୍/ଇଭେଣ୍ଟ SHA ଅମେଳକୁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରେ। ୱର୍କଫ୍ଲୋ ପରୀକ୍ଷାଗୁଡ଼ିକ ନୀତିର ସଦସ୍ୟତାକୁ +ନିଷ୍ପତ୍ତି ଜବ୍ର `needs` ତାଲିକା ସହିତ ବାନ୍ଧି ରଖେ, ଯାହାଦ୍ୱାରା ଏକ ନୂଆ କିମ୍ବା ଅପସାରିତ ଲେନ୍ ନିରବରେ ଅଦୃଶ୍ୟ ହୋଇପାରିବ ନାହିଁ। +ରସିଦଗୁଡ଼ିକ ସେମାନଙ୍କର ନିଜ ୱର୍କଫ୍ଲୋକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ, ପ୍ରକାଶନ, ଡିପ୍ଲୟମେଣ୍ଟ କିମ୍ବା +ଏକ ବିଦ୍ୟମାନ ପରାମର୍ଶମୂଳକ ସ୍କାନର୍ର ଆଭ୍ୟନ୍ତରୀଣ ଅଂଶକୁ ନୁହେଁ। ବ୍ରାଞ୍ଚ ନିୟମରେ ଉଭୟ ଚେକ୍ ନାମକୁ ସକ୍ରିୟ କରିବା +ଏକ ପୃଥକ ପ୍ରଶାସନିକ ପରିବର୍ତ୍ତନ; ଏହି ଜବ୍ଗୁଡ଼ିକୁ ଯୋଡ଼ିବା ନିଜେ ନିଜେ ଏକ ବ୍ରାଞ୍ଚକୁ ସୁରକ୍ଷିତ କରେ ନାହିଁ। -### ଷ୍ଟାଟିକ୍ ସ୍କାନ୍ ଇନଭେଣ୍ଟୋରୀ +### ଷ୍ଟାଟିକ୍ ସ୍କାନ୍ ଇନଭେଣ୍ଟରି -ସଂସ୍କରଣଭିତ୍ତିକ npm-ଆଲିଆସ୍ ଇନଭେଣ୍ଟୋରୀ ଏବଂ ଷ୍ଟାଟିକ୍-ସ୍କାନ୍ ସଦସ୍ୟତା -`config/quality/gate-manifest.json`ରେ ରହିଛି। `package.json` ସହିତ ସ୍କ୍ରିପ୍ଟ ନାମ ଏବଂ ସଠିକ୍ କମାଣ୍ଡଗୁଡ଼ିକୁ -ଯାଞ୍ଚ କରିବା ପାଇଁ `npm run check:gate-manifest` ଚଲାନ୍ତୁ; ଯୋଗ, ଅପସାରଣ ଏବଂ -କମାଣ୍ଡ ବିଚ୍ୟୁତି ଉଭୟ ସ୍ଥାନୀୟ ହୁକ୍ ଏବଂ CIର ପରିବର୍ତ୍ତନ-ବର୍ଗୀକରଣ ଜବ୍ଗୁଡ଼ିକୁ ବିଫଳ କରେ। -ଏକ ଆଲିଆସ୍ କୌଣସି ୱାର୍କଫ୍ଲୋ ଜବ୍, ମ୍ୟାଟ୍ରିକ୍ସ ଇନ୍ଷ୍ଟାନ୍ସ କିମ୍ବା ପରୀକ୍ଷଣ କେସ୍ ନୁହେଁ: ଏହି ଗଣନାଗୁଡ଼ିକୁ -ପରସ୍ପର ବଦଳଯୋଗ୍ୟ ଭାବେ ଉପସ୍ଥାପନ କରାଯିବା ଉଚିତ ନୁହେଁ। +ସଂସ୍କରଣଭିତ୍ତିକ npm-ଆଲିଆସ୍ ଇନଭେଣ୍ଟରି ଏବଂ ଷ୍ଟାଟିକ୍-ସ୍କାନ୍ ସଦସ୍ୟତା +`config/quality/gate-manifest.json`ରେ ରହିଛି। ସ୍କ୍ରିପ୍ଟ ନାମ ଏବଂ ସଠିକ୍ କମାଣ୍ଡଗୁଡ଼ିକୁ +`package.json` ସହିତ ଯାଞ୍ଚ କରିବା ପାଇଁ `npm run check:gate-manifest` ଚଲାନ୍ତୁ; ଯୋଗ, ଅପସାରଣ ଏବଂ +କମାଣ୍ଡର ବିଚ୍ୟୁତି ଉଭୟ ଲୋକାଲ୍ ହୁକ୍ ଏବଂ CIର ପରିବର୍ତ୍ତନ-ବର୍ଗୀକରଣ ଜବ୍ଗୁଡ଼ିକୁ ବିଫଳ କରେ। +ଏକ ଆଲିଆସ୍ କୌଣସି ୱର୍କଫ୍ଲୋ ଜବ୍, ମ୍ୟାଟ୍ରିକ୍ସ ଇନ୍ଷ୍ଟାନ୍ସ କିମ୍ବା ପରୀକ୍ଷଣ କେସ୍ ନୁହେଁ: ଏହି ଗଣନାଗୁଡ଼ିକୁ +ପରସ୍ପର ବିନିମୟଯୋଗ୍ୟ ଭାବେ ଉପସ୍ଥାପନ କରାଯିବା ଉଚିତ ନୁହେଁ। -ଚୟନିତ ଆଲିଆସ୍ଗୁଡ଼ିକୁ ନିଷ୍ପାଦନ ନକରି ନିରୀକ୍ଷଣ କରିବା ପାଇଁ `npm run quality:scan -- --list` କିମ୍ବା `npm run quality:scan:fast -- --list` -ବ୍ୟବହାର କରନ୍ତୁ। ରନର୍ npm ଏଣ୍ଟ୍ରିପଏଣ୍ଟକୁ ଆହ୍ୱାନ କରେ, ତେଣୁ ଏହାର ରନ୍ଟାଇମ୍ (ଯେଉଁଠାରେ ବିନ୍ୟାସିତ ଅଛି ସେଠାରେ Bun ସମେତ) ସଂରକ୍ଷିତ ରହେ। -ମ୍ୟାନିଫେଷ୍ଟ ସେହି ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକ ବାହାରର ଆଲିଆସ୍ଗୁଡ଼ିକୁ ପୃଥକ ଭାବେ ଆହ୍ୱାନ କରାଯାଇଥିବା ଭାବେ ରେକର୍ଡ କରେ, ଏବଂ -ରିଡ୍-ଅନ୍ଲି ସ୍କାନ୍ ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକରେ ରକ୍ଷଣାବେକ୍ଷଣ କମାଣ୍ଡ ନିଷିଦ୍ଧ। +ଚୟନିତ ଆଲିଆସ୍ଗୁଡ଼ିକୁ ନିଷ୍ପାଦନ ନ କରି ନିରୀକ୍ଷଣ କରିବା ପାଇଁ `npm run quality:scan -- --list` କିମ୍ବା `npm run quality:scan:fast -- --list` +ବ୍ୟବହାର କରନ୍ତୁ। ରନର୍ npm ଏଣ୍ଟ୍ରିପଏଣ୍ଟକୁ ଆହ୍ୱାନ କରେ, ତେଣୁ ଏହାର ରନ୍ଟାଇମ୍ (ଯେଉଁଠାରେ ବିନ୍ୟାସିତ, ସେଠାରେ Bun ସମେତ) ସଂରକ୍ଷିତ ରହେ। +ମ୍ୟାନିଫେଷ୍ଟ ସେହି ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକର ବାହାରେ ଥିବା ଆଲିଆସ୍ଗୁଡ଼ିକୁ ପୃଥକ ଭାବେ ଆହ୍ୱାନ କରାଯାଇଥିବା ଭାବେ ରେକର୍ଡ କରେ, ଏବଂ +କେବଳ-ପଠନ ସ୍କାନ୍ ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକରେ ରକ୍ଷଣାବେକ୍ଷଣ କମାଣ୍ଡ ନିଷିଦ୍ଧ। -ଏହି ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକ କେବଳ ଷ୍ଟାଟିକ୍ ସ୍କାନ୍କୁ ଅନ୍ତର୍ଭୁକ୍ତ କରନ୍ତି। ସେଗୁଡ଼ିକ ପ୍ରଡକ୍ଟ ପରୀକ୍ଷା, -କଭରେଜ୍, ପ୍ୟାକେଜିଂ, ବାହ୍ୟ ଚେକ୍ କିମ୍ବା କୌଣସି ପ୍ରାର୍ଥୀର ସମ୍ପୂର୍ଣ୍ଣ ରିଲିଜ୍ ଗ୍ରହଣକୁ ପ୍ରମାଣିତ କରନ୍ତି ନାହିଁ। -ୱାର୍କଫ୍ଲୋ ଗ୍ରହଣ ଲିଙ୍କ୍ ହୋଇଥିବା `config/quality/admission-policy.json` ଏବଂ -`scripts/quality/admission-verdict.mjs` ବ୍ୟବହାର କରେ। ରିଲିଜ୍-ଅବଜର୍ଭର୍ ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକ ପୃଥକ ରହେ; -ସେମାନଙ୍କର ପ୍ରଯୋଜ୍ୟ ଚେକ୍ ଏବଂ ରସିଦଗୁଡ଼ିକୁ ସ୍ୱାଧୀନ ଭାବେ ନିରୀକ୍ଷଣ କରନ୍ତୁ। ନିମ୍ନରେ ଥିବା ଗଦ୍ୟ -ଇନଭେଣ୍ଟୋରୀ ଏକ ସନ୍ଦର୍ଭ, କୌଣସି ଗେଟ୍ ପ୍ରକୃତରେ ଚାଲିଥିବାର ପ୍ରମାଣ ନୁହେଁ। +ଏହି ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକ କେବଳ ଷ୍ଟାଟିକ୍ ସ୍କାନ୍କୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ। ସେଗୁଡ଼ିକ ପ୍ରଡକ୍ଟ ପରୀକ୍ଷା, +କଭରେଜ୍, ପ୍ୟାକେଜିଂ, ବାହ୍ୟ ଚେକ୍ କିମ୍ବା ଏକ ପ୍ରାର୍ଥୀର ସମ୍ପୂର୍ଣ୍ଣ ରିଲିଜ୍ ଗ୍ରହଣୀୟତାକୁ ପ୍ରମାଣିତ କରେ ନାହିଁ। +ୱର୍କଫ୍ଲୋ ଅନୁମୋଦନ ଲିଙ୍କ୍ ହୋଇଥିବା `config/quality/admission-policy.json` ଏବଂ +`scripts/quality/admission-verdict.mjs` ବ୍ୟବହାର କରେ। ରିଲିଜ୍-ପର୍ଯ୍ୟବେକ୍ଷକ ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକ ପୃଥକ ରହେ; +ସେମାନଙ୍କର ପ୍ରଯୁଜ୍ୟ ଚେକ୍ ଏବଂ ରସିଦଗୁଡ଼ିକୁ ସ୍ୱାଧୀନ ଭାବେ ନିରୀକ୍ଷଣ କରନ୍ତୁ। ନିମ୍ନରେ ଥିବା ଗଦ୍ୟ +ଇନଭେଣ୍ଟରି ଏକ ସନ୍ଦର୍ଭ, କୌଣସି ଗେଟ୍ ପ୍ରକୃତରେ ଚାଲିଥିବାର ପ୍ରମାଣ ନୁହେଁ। -ସ୍କ୍ରିପ୍ଟଗୁଡ଼ିକ `scripts/check/` (ନୀତି ଗେଟ୍ଗୁଡ଼ିକ) ଏବଂ `scripts/quality/` (ରାଚେଟ୍ ଇଞ୍ଜିନ୍) ଅଧୀନରେ ରହିଛି। -CIର ପ୍ରାମାଣିକ ଉତ୍ସ ହେଉଛି `.github/workflows/ci.yml`। +ସ୍କ୍ରିପ୍ଟଗୁଡ଼ିକ `scripts/check/` (ନୀତି ଗେଟ୍) ଏବଂ `scripts/quality/` (ରାଚେଟ୍ ଇଞ୍ଜିନ୍) ଅଧୀନରେ ରହିଛି। +CIର ପ୍ରମାଣିକ ଉତ୍ସ ହେଉଛି `.github/workflows/ci.yml`। -### ରିଲିଜ୍ PR ଫାଷ୍ଟ-ପାଥ୍ (`quality.yml`) +### ରିଲିଜ୍ PR ଦ୍ରୁତ ପଥ (`quality.yml`) -`.github/workflows/quality.yml` main/release PRଗୁଡ଼ିକ, ସୁରକ୍ଷିତ-ବ୍ରାଞ୍ଚ -ପୁଶ୍, ଡିସ୍ପ୍ୟାଚ୍ ଏବଂ ମର୍ଜ୍ ଗ୍ରୁପ୍ଗୁଡ଼ିକରେ CIକୁ ପରିପୂରକ କରେ। PRଗୁଡ଼ିକ ପାଥ୍-ଫିଲ୍ଟର୍ ହୋଇଥିବା ଦ୍ରୁତ ଚେକ୍ ବ୍ୟବହାର କରନ୍ତି। ସ୍ଥାୟୀ ଭାବେ -ଅକ୍ଷମ କରାଯାଇଥିବା ଡୁପ୍ଲିକେଟ୍ ବିଲ୍ଡକୁ ଅପସାରଣ କରାଯାଇଛି; ପ୍ରକୃତ ବିଲ୍ଡ/ପ୍ୟାକେଜ୍/ବୁଟ୍ ଚେକ୍ଗୁଡ଼ିକ CIରେ ରହିଛି। +`.github/workflows/quality.yml` main/release PR, ସୁରକ୍ଷିତ-ବ୍ରାଞ୍ଚ +ପୁସ୍, ଡିସ୍ପାଚ୍ ଏବଂ ମର୍ଜ୍ ଗ୍ରୁପ୍ଗୁଡ଼ିକରେ CIକୁ ପରିପୂରକ କରେ। PRଗୁଡ଼ିକ ପଥ-ଫିଲ୍ଟର୍ ହୋଇଥିବା ଦ୍ରୁତ ଚେକ୍ ବ୍ୟବହାର କରନ୍ତି। ସ୍ଥାୟୀ ଭାବେ +ଅକ୍ଷମ ହୋଇଥିବା ଡୁପ୍ଲିକେଟ୍ ବିଲ୍ଡକୁ ଅପସାରଣ କରାଯାଇଛି; ପ୍ରକୃତ ବିଲ୍ଡ/ପ୍ୟାକେଜ୍/ବୁଟ୍ ଚେକ୍ଗୁଡ଼ିକ CIରେ ରହିଛି। | ଜବ୍ | ପରିସର | ବ୍ଲକିଂ | | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `Docs Gates (fast-path)` | ଡକ୍ସ/କୋଡ୍ PRଗୁଡ଼ିକ; API ଡକ୍ସ ରେଫ୍ ଏବଂ docs-all | ହଁ | -| `Fast Quality Gates` | କୋଡ୍ PRଗୁଡ଼ିକ; ଷ୍ଟାଟିକ୍ ଚେକ୍, ଟାଇପ୍ଚେକ୍, ଡ୍ୟାସ୍ବୋର୍ଡ ଟାଇପ୍ଚେକ୍, ପ୍ରଭାବିତ ୟୁନିଟ୍ ପରୀକ୍ଷା | ହଁ | -| `Forgotten sibling tests` | କୋଡ୍ PRଗୁଡ଼ିକ; ପରିବର୍ତ୍ତିତ ମଡ୍ୟୁଲ୍ଗୁଡ଼ିକୁ ଷ୍ଟାଟିକ୍ ଉପଭୋକ୍ତା ଏବଂ ପ୍ରାର୍ଥୀ ସିବ୍ଲିଂ ପରୀକ୍ଷା ପର୍ଯ୍ୟନ୍ତ ଟ୍ରେସ୍ କରାଯାଏ; ବ୍ୟାରେଲ୍ ଏବଂ ଡାଇନାମିକ୍-ଇମ୍ପୋର୍ଟ ପାଥ୍ଗୁଡ଼ିକୁ ସନ୍ଦର୍ଭିତ ଅଲାଉଲିଷ୍ଟ ବ୍ୟତିକ୍ରମ ସହିତ ପରାମର୍ଶମୂଳକ ନିଦାନ ଭାବେ ରିପୋର୍ଟ କରାଯାଏ | **ପରାମର୍ଶମୂଳକ** | -| `Vitest (fast-path)` | କୋଡ୍ PRଗୁଡ଼ିକ; ଦ୍ରୁତ vitest ସୁଇଟ୍ | ହଁ | -| `Unit Tests fast-path` | କୋଡ୍ PRଗୁଡ଼ିକ; 4-ଶାର୍ଡ ୟୁନିଟ୍ ସୁଇଟ୍ | ହଁ | -| `No new ESLint warnings` | କୋଡ୍ PRଗୁଡ଼ିକ; ସପ୍ରେସନ୍-ସଚେତନ ଲିଣ୍ଟ୍ ଗାର୍ଡ | ହଁ, ଫୋର୍କ୍ ସମେତ | -| `Merge integrity (changelog + generated skills)` | ଅଣ-ଡ୍ରାଫ୍ଟ PRଗୁଡ଼ିକ; ଚେଞ୍ଜଲଗ୍ ଏବଂ ଜେନେରେଟେଡ୍ ସ୍କିଲ୍ ସିଙ୍କ୍ | ହଁ, ଫୋର୍କ୍ ସମେତ | +| `Docs Gates (fast-path)` | ଡକ୍ସ/କୋଡ୍ PR; API ଡକ୍ସ ରେଫରେନ୍ସ ଏବଂ docs-all | ହଁ | +| `Fast Quality Gates` | କୋଡ୍ PR; ଷ୍ଟାଟିକ୍ ଚେକ୍, ଟାଇପ୍ଚେକ୍, ଡ୍ୟାସ୍ବୋର୍ଡ ଟାଇପ୍ଚେକ୍, ପ୍ରଭାବିତ ୟୁନିଟ୍ ପରୀକ୍ଷା | ହଁ | +| `Forgotten sibling tests` | କୋଡ୍ PR; ପରିବର୍ତ୍ତିତ ମଡ୍ୟୁଲ୍ଗୁଡ଼ିକୁ ଷ୍ଟାଟିକ୍ ଉପଭୋକ୍ତା ଏବଂ ପ୍ରାର୍ଥୀ ସିବ୍ଲିଂ ପରୀକ୍ଷା ପର୍ଯ୍ୟନ୍ତ ଟ୍ରେସ୍ କରାଯାଏ; ବ୍ୟାରେଲ୍ ଏବଂ ଡାଇନାମିକ୍-ଇମ୍ପୋର୍ଟ ପଥଗୁଡ଼ିକୁ ସନ୍ଦର୍ଭିତ ଅଲାଉଲିଷ୍ଟ ବ୍ୟତିକ୍ରମ ସହିତ ପରାମର୍ଶମୂଳକ ଡାଇଗ୍ନୋଷ୍ଟିକ୍ ଭାବେ ରିପୋର୍ଟ କରାଯାଏ | **ପରାମର୍ଶମୂଳକ** | +| `Vitest (fast-path)` | କୋଡ୍ PR; ଦ୍ରୁତ vitest ସୁଇଟ୍ | ହଁ | +| `Unit Tests fast-path` | କୋଡ୍ PR; 4-ଶାର୍ଡ ୟୁନିଟ୍ ସୁଇଟ୍ | ହଁ | +| `No new ESLint warnings` | କୋଡ୍ PR; ସପ୍ରେସନ୍-ସଚେତନ lint ଗାର୍ଡ | ହଁ, ଫୋର୍କ୍ ସମେତ | +| `Merge integrity (changelog + generated skills)` | ଅଣ-ଡ୍ରାଫ୍ଟ PR; ଚେଞ୍ଜଲଗ୍ ଏବଂ ଜେନେରେଟେଡ୍ ସ୍କିଲ୍ ସିଙ୍କ୍ | ହଁ, ଫୋର୍କ୍ ସମେତ | #### ଭୁଲିଯାଇଥିବା ସିବ୍ଲିଂ ପରୀକ୍ଷା ରିପୋର୍ଟ `npm run check:forgotten-sibling-tests` ପରୀକ୍ଷା-ପ୍ରଭାବ ମ୍ୟାପ୍ ପଛରେ ଥିବା ଇମ୍ପୋର୍ଟ ରିଜଲ୍ଭର୍କୁ ପୁନଃବ୍ୟବହାର କରେ। -ପ୍ରତ୍ୟେକ ପରିବର୍ତ୍ତିତ ପ୍ରଡକ୍ସନ୍ ମଡ୍ୟୁଲ୍ ପାଇଁ, ଯେତେବେଳେ ପ୍ରାର୍ଥୀ -ପରୀକ୍ଷାଟି ପୁଲ୍-ରିକ୍ୱେଷ୍ଟ ଡିଫ୍ରେ ଅନୁପସ୍ଥିତ ଥାଏ, ଏହା ନିର୍ଦ୍ଧାରିତ -`changed module/symbol -> static consumer -> candidate sibling test` ଶୃଙ୍ଖଳଗୁଡ଼ିକୁ ରିପୋର୍ଟ କରେ। କୌଣସି ବ୍ଲକିଂ ରୋଲ୍ଆଉଟ୍ ପୂର୍ବରୁ କ୍ୟାଲିବ୍ରେସନ୍ ପାଇଁ Markdown ସାରାଂଶ ଏବଂ JSON ଫଳାଫଳକୁ -`forgotten-sibling-tests` ୱାର୍କଫ୍ଲୋ ଆର୍ଟିଫ୍ୟାକ୍ଟ ଭାବେ ରଖାଯାଏ। - -Barrel re-export ଏବଂ dynamic import କେବଳ resolution diagnostic ଅଟେ; ସେଗୁଡ଼ିକ କେବେ ମଧ୍ୟ -blocking finding ସୃଷ୍ଟି କରେ ନାହିଁ। ସମୀକ୍ଷା କରାଯାଇଥିବା exceptionଗୁଡ଼ିକ -`config/quality/forgotten-sibling-allowlist.json`ରେ ରହିଥାଏ। ପ୍ରତ୍ୟେକ entryରେ consumer ଏବଂ candidate -testର ନାମ, ଏକ ନିର୍ଦ୍ଦିଷ୍ଟ ଯୁକ୍ତି, ଏବଂ GitHub issue କିମ୍ବା pull requestର link ରହିବା ଆବଶ୍ୟକ। ବିକୃତ entryଗୁଡ଼ିକ -fail closed ହୁଏ। Exceptionଗୁଡ଼ିକ ଏକ ବିଲୋପ ହୋଇଥିବା candidate test କିମ୍ବା `.skip`/`.todo` ଯୋଡ଼ୁଥିବା diffକୁ suppress କରିପାରିବ ନାହିଁ; -assertion ଦୁର୍ବଳୀକରଣ ଏବଂ ଅନ୍ୟ masking ସ୍ୱାଧୀନ ଭାବେ blocking କରୁଥିବା -`check:test-masking` gateର ଅଧୀନରେ ରହେ। - -### Job: `lint` - -`main` ପାଇଁ ପ୍ରତ୍ୟେକ PRରେ ଚାଲେ। ବିଫଳ ହେଲେ mergeକୁ block କରେ। - -| Script (`npm run ...`) | ଯାହାକୁ validate କରେ | Blocking | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | Node.js version ସମର୍ଥିତ range ମଧ୍ୟରେ ଅଛି | ହଁ | -| `check:cycles` | Circular import — ସମସ୍ତ `src/` + `open-sse/` module | ହଁ | -| `check:route-validation:t06` | ସମସ୍ତ routeରେ Zod schema ଉପସ୍ଥିତ ଅଛି (Tier 6 policy) | ହଁ | -| `check:any-budget:t11` | `@ts-expect-error // any` ସଂଖ୍ୟା budget ଅତିକ୍ରମ କରେ ନାହିଁ (Tier 11 catraca) | ହଁ | -| `check:provider-consistency` | `providers.ts`ର ପ୍ରତ୍ୟେକ ପ୍ରଦାନକାରୀ ପାଇଁ `providerRegistry.ts`ରେ ଏକ ମେଳ ଖାଉଥିବା ଏଣ୍ଟ୍ରି ଅଛି (ଏବଂ ଏହାର ବିପରୀତଟି ମଧ୍ୟ, ଅନୁମତି-ତାଲିକା ମଧ୍ୟରେ) | ହଁ | -| `check:model-lifecycle` | ହାତରେ ରକ୍ଷଣାବେକ୍ଷଣ କରାଯାଉଥିବା ତିନୋଟି ରାଉଟିଂ ଟେବୁଲ୍ ଚେକ୍-ଇନ୍ ହୋଇଥିବା ଜୀବନଚକ୍ର ସ୍ନାପସଟ୍ (#11503) ସହ ସୁସଙ୍ଗତ ରହେ: `FITNESS_TABLE` (`taskFitness.ts`) ଏପରି କୌଣସି ଅବସରପ୍ରାପ୍ତ idକୁ ସ୍କୋର୍ କରେ ନାହିଁ ଯାହାକୁ `REGISTRY` ରାଉଟ୍ କରିପାରେ; ପ୍ରତ୍ୟେକ `BUILT_IN_ALIASES` ଲକ୍ଷ୍ୟ `REGISTRY`ରେ ଉପସ୍ଥିତ ଏବଂ ଅବସରପ୍ରାପ୍ତ-id ସ୍ନାପସଟ୍ରେ ଅନୁପସ୍ଥିତ; `REGISTRY`ରେ ଏବେ ମଧ୍ୟ ଥିବା ପ୍ରତ୍ୟେକ ଅବସରପ୍ରାପ୍ତ idକୁ ଫରୱାର୍ଡ କରାଯାଇଛି କିମ୍ବା `allowedRetiredInCatalog`ରେ ତାଲିକାଭୁକ୍ତ କରାଯାଇଛି; ଏବଂ ସେହି ସ୍ନାପସଟ୍ରେ କୌଣସି `DEFAULT_DEGRADATION_MAP` ଉତ୍ସ କିମ୍ବା ଲକ୍ଷ୍ୟ ଅବସରପ୍ରାପ୍ତ ଭାବେ ଦେଖାଯାଏ ନାହିଁ। ଏହା ପ୍ରମାଣ କରେ ନାହିଁ ଯେ କୌଣସି ମଡେଲ୍ ବର୍ତ୍ତମାନ ଏକ ସକ୍ରିୟ ଅପ୍ଷ୍ଟ୍ରିମ୍ ଦ୍ୱାରା ପରିବେଷିତ ହେଉଛି। ଅଫ୍ଲାଇନ୍ — `config/quality/model-lifecycle.json` ସହ ତୁଳନା କରେ, ଯାହାକୁ `npm run quality:refresh-model-lifecycle` ବ୍ୟବହାର କରି ହାତରେ ସତେଜ କରାଯାଏ (ନେଟୱର୍କ; CI ସହ ସଂଯୁକ୍ତ ନୁହେଁ)। `allowedRetiredInCatalog` ହେଉଛି ଏକ କ୍ରମଶଃ-ହ୍ରାସ ରାଚେଟ୍: କେବଳ ଏକ ଟ୍ରାକିଂ ଇସ୍ୟୁ ସହିତ ଏଣ୍ଟ୍ରି ଯୋଡ଼ନ୍ତୁ। | ହଁ | -| `check:fetch-targets` | କ୍ଲାଏଣ୍ଟ-ପାର୍ଶ୍ୱ `src/`ର ପ୍ରତ୍ୟେକ `fetch("/api/...")` ଏକ ପ୍ରକୃତ `route.ts`କୁ ସମାଧାନ ହୁଏ | ହଁ | -| `check:deps` | ରେପୋର ପ୍ରତ୍ୟେକ `package.json`ରେ ଥିବା ସମସ୍ତ `npm install`-ଯୋଗ୍ୟ ନିର୍ଭରଶୀଳତା `dependency-allowlist.json`ରେ ଅଛି; ନୂଆ ଅପିନ୍ ହୋଇଥିବା କିମ୍ବା ସ୍ଲପସ୍କ୍ୱାଟ୍ ହୋଇଥିବା ପ୍ୟାକେଜ୍ଗୁଡ଼ିକୁ ଚିହ୍ନିତ କରାଯାଏ | ହଁ | -| `audit:deps` | `npm audit` (ରୁଟ୍ + electron) — କୌଣସି ଉଚ୍ଚ/ଗୁରୁତର ପରାମର୍ଶ ନାହିଁ (osv `check:vuln-ratchet` ସହ ଓଭରଲାପ୍ କରେ; ଯୁକ୍ତିସଙ୍ଗତକରଣ ବ୍ୟାକଲଗ୍ ଦେଖନ୍ତୁ) | ହଁ | -| `check:lockfile` | `package-lock.json`ର ଅଖଣ୍ଡତା — https ରେଜିଷ୍ଟ୍ରି, ଅଖଣ୍ଡତା ହ୍ୟାସ୍, କୌଣସି ହୋଷ୍ଟ ଓଭରରାଇଡ୍ ନାହିଁ | ହଁ | -| `check:licenses` | ପ୍ରଡକ୍ସନ୍ ଡିପେଣ୍ଡେନ୍ସିଗୁଡ଼ିକ ପାଇଁ SPDX ଲାଇସେନ୍ସ ଅନୁମୋଦିତ ତାଲିକା | ହଁ | -| `check:tracked-artifacts` | କୌଣସି ବିଲ୍ଡ ଆର୍ଟିଫ୍ୟାକ୍ଟ / କମିଟ୍ ହୋଇଥିବା `node_modules` ସିମ୍ଲିଙ୍କ୍ ନାହିଁ (husky pre-commitରେ ମଧ୍ୟ ଚାଲେ; pre-pushକୁ ଉଦ୍ଦେଶ୍ୟମୂଳକ ଭାବେ ହାଲୁକା ରଖାଯାଇଛି — #6716) | ହଁ | -| `check:ai-attribution` | PR କମିଟ୍, ଶୀର୍ଷକ କିମ୍ବା ବଡିରେ କୌଣସି AI/ବଟ୍ `Co-Authored-By` ଟ୍ରେଲର୍ କିମ୍ବା AI-ଜେନେରେସନ୍ ଫୁଟର୍ ନାହିଁ — କଠୋର ନିୟମ #16 (`quality.yml`ର PR→`release/**` ପାଇଁ fast-gates ଲୁପ୍ରେ — ଇଭେଣ୍ଟ ପେଲୋଡ୍ ପଢ଼େ, PR ନଥିଲେ କୌଣସି କାର୍ଯ୍ୟ କରେନାହିଁ — ଏବଂ `ci.yml` lintରେ PR→`main` ପାଇଁ ଏକ କେବଳ-PR ଷ୍ଟେପ୍; husky `commit-msg` ହୁକ୍ରେ ମଧ୍ୟ; ମାନବ ସହ-ଲେଖକଙ୍କୁ ଅନୁମତି ଅଛି; #14436) | -| `check:vitest-exclusions` | ପ୍ରତ୍ୟେକ Vitest ବାଦକରଣ ଏକ ଟ୍ରାକିଂ ଇସ୍ୟୁକୁ ଉଲ୍ଲେଖ କରେ ଏବଂ `config/quality/vitest-exclusions.json`ରେ ଉପସ୍ଥିତ ଥାଏ (#13204) | ହଁ | -| `check:file-size` | କୌଣସି ସୋର୍ସ ଫାଇଲ୍ ପ୍ରତି-ଏକ୍ସଟେନ୍ସନ୍ ସୀମା ଅତିକ୍ରମ କରେନାହିଁ (ର୍ୟାଚେଟ୍: `frozen` ତାଲିକାରେ ଫ୍ରିଜ୍ କରାଯାଇଥିବା ବଡ଼ ଫାଇଲ୍ଗୁଡ଼ିକ) | ହଁ | -| `check:error-helper` | ଏକ୍ସିକ୍ୟୁଟର୍/ହ୍ୟାଣ୍ଡଲର୍ଗୁଡ଼ିକରେ ତ୍ରୁଟି ପ୍ରତିକ୍ରିୟା `buildErrorBody()` / `sanitizeErrorMessage()` ବ୍ୟବହାର କରେ (କଠୋର ନିୟମ #12) | ହଁ | -| `check:migration-numbering` | Migration SQL ଫାଇଲ୍ଗୁଡ଼ିକ କୌଣସି ଅନୁକ୍ରମ ଅନ୍ତର କିମ୍ବା ପୁନରାବୃତ୍ତି ବିନା କ୍ରମାନୁସାରେ ସଂଖ୍ୟାୟିତ ହୋଇଛି | ହଁ | -| `check:public-creds` | `publicCreds.ts` ବାହାରେ କୌଣସି ସ୍ପଷ୍ଟ OAuth `client_id`/`client_secret` କିମ୍ବା Firebase Web କୀ ନାହିଁ (କଠୋର ନିୟମ #11) | ହଁ | -| `check:db-rules` | `src/lib/db/` ମଡ୍ୟୁଲ୍ଗୁଡ଼ିକ ବାହାରେ କୌଣସି ର’ SQL ନାହିଁ; `localDb.ts` ରୁ କୌଣସି barrel-import ନାହିଁ (କଠୋର ନିୟମ #2/#5) | ହଁ | -| `check:known-symbols` | ସେମାନଙ୍କ dispatch ଟେବୁଲ୍ରେ ପଞ୍ଜୀକୃତ provider executor, routing strategy ଏବଂ translatorଗୁଡ଼ିକ ଡିସ୍କରେ ଥିବା ଫାଇଲ୍ଗୁଡ଼ିକ ସହ ମେଳ ଖାଆନ୍ତି — କୌଣସି ଅନାଥ କିମ୍ବା ଅଘୋଷିତ symbol ନାହିଁ | ହଁ | -| `check:route-guard-membership` | child process ସୃଷ୍ଟି କରୁଥିବା ପ୍ରତ୍ୟେକ routeକୁ `isLocalOnlyPath()` ଦ୍ୱାରା ଶ୍ରେଣୀଭୁକ୍ତ କରାଯାଇଛି (କଠୋର ନିୟମ #15/#17) | ହଁ | -| `check:test-discovery` | repoରେ ଥିବା ପ୍ରତ୍ୟେକ `*.test.ts` / `*.spec.ts` ଫାଇଲ୍ ଅତି କମରେ ଗୋଟିଏ test runner ଦ୍ୱାରା ସଂଗୃହୀତ ହୁଏ (ratchet: `test-discovery-baseline.json`ରେ ଥିବା ଅନାଥ ତାଲିକା କେବଳ ସଙ୍କୁଚିତ ହୋଇପାରିବ) | ହଁ | -| `check:agent-skills-sync` | ସୃଷ୍ଟି କରାଯାଇଥିବା agent-skills ଆର୍ଟିଫ୍ୟାକ୍ଟଗୁଡ଼ିକ ସେମାନଙ୍କର ଉତ୍ସ କ୍ୟାଟାଲଗ୍ ସହିତ ମେଳ ଖାଉଛି (କୌଣସି ବିଚ୍ୟୁତି ନାହିଁ) | -| `check:provider-asset-provenance` | ପ୍ରଦାତାଙ୍କ ଲୋଗୋ/ଆସେଟ୍ଗୁଡ଼ିକରେ ଏକ ରେକର୍ଡ କରାଯାଇଥିବା ଉତ୍ପତ୍ତି ଏଣ୍ଟ୍ରି ରହିଛି | -| `lint:json` | JSON କନଫିଗ୍ ଫାଇଲ୍ଗୁଡ଼ିକ ପାର୍ସ ହୁଏ ଏବଂ ରିପୋଜିଟୋରିର ଲିଣ୍ଟ ନିୟମଗୁଡ଼ିକ ପୂରଣ କରେ | -| `typecheck:core` | ତ୍ରୁଟି ବିନା TypeScript କମ୍ପାଇଲେସନ୍ (କେବଳ ପରାମର୍ଶମୂଳକ ଚେତାବନୀ) | ହଁ | -| `typecheck:noimplicit:core` | କଠୋର `noImplicitAny` — ଭବିଷ୍ୟତମୁଖୀ; ପୂର୍ବରୁ ଥିବା ଅନେକ କଲ୍ ସାଇଟ୍ରେ ଏବେ ମଧ୍ୟ ଆନୋଟେସନ୍ ଆବଶ୍ୟକ | **ପରାମର୍ଶମୂଳକ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, `src/app/(dashboard)/**` ମଧ୍ୟରେ ସୀମିତ (#7033) — `typecheck:core`ର ସୁନିର୍ବାଚିତ 27-ଫାଇଲ୍ ଅନୁମୋଦିତ ତାଲିକାରେ କୌଣସି ଡ୍ୟାସ୍ବୋର୍ଡ TSX ଅନ୍ତର୍ଭୁକ୍ତ ନାହିଁ, ଏବଂ `next build` ମଧ୍ୟ ଏହାକୁ କେବେ ଟାଇପ୍-ଚେକ୍ କରେ ନାହିଁ (`next.config.mjs`ରେ `ignoreBuildErrors: true` ସେଟ୍ କରାଯାଇଛି), ତେଣୁ ସେଠାରେ ଥିବା ଅନାଥ-ପରିଚାୟକ ରିଗ୍ରେସନ୍ଗୁଡ଼ିକ (#6625/#6909) CI ପାଇଁ ଅଦୃଶ୍ୟ ଥିଲା। ଏକ ସ୍ଥିରୀକୃତ ପ୍ରତି-ଫାଇଲ୍/ପ୍ରତି-TS-କୋଡ୍ ଗଣନା ବେସ୍ଲାଇନ୍ (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ପରି ସମାନ ଅଚଳତା-ପ୍ରବର୍ତ୍ତନ ପ୍ରଣାଳୀ) ସହିତ ତଫାତ୍ ତୁଳନା କରେ — ବେସ୍ଲାଇନ୍ ଗଣନାଠାରୁ ଅଧିକ କେବଳ ନୂତନ ତ୍ରୁଟିଗୁଡ଼ିକ ଗେଟ୍କୁ ବିଫଳ କରେ; ପୂର୍ବରୁ ଥିବା ତ୍ରୁଟି ସମାଧାନ ହେଲେ `--update` ସହିତ ବେସ୍ଲାଇନ୍ କମାନ୍ତୁ। | ହଁ | +ପ୍ରତ୍ୟେକ ପରିବର୍ତ୍ତିତ ପ୍ରଡକ୍ସନ୍ ମଡ୍ୟୁଲ୍ ପାଇଁ, ପ୍ରାର୍ଥୀ +ପରୀକ୍ଷାଟି ପୁଲ୍-ରିକ୍ୱେଷ୍ଟ ଡିଫ୍ରେ ଅନୁପସ୍ଥିତ ଥିବାବେଳେ, ଏହା ନିର୍ଦ୍ଧାରିତ +`changed module/symbol -> static consumer -> candidate sibling test` ଶୃଙ୍ଖଳଗୁଡ଼ିକୁ ରିପୋର୍ଟ କରେ। ଯେକୌଣସି ବ୍ଲକିଂ ରୋଲ୍ଆଉଟ୍ ପୂର୍ବରୁ କ୍ୟାଲିବ୍ରେସନ୍ ପାଇଁ +Markdown ସାରାଂଶ ଏବଂ JSON ଫଳାଫଳକୁ `forgotten-sibling-tests` ୱର୍କଫ୍ଲୋ ଆର୍ଟିଫ୍ୟାକ୍ଟ ଭାବେ ସଂରକ୍ଷିତ ରଖାଯାଏ। + +ବ୍ୟାରେଲ୍ ପୁନଃ-ରପ୍ତାନି ଏବଂ ଡାଇନାମିକ୍ ଇମ୍ପୋର୍ଟଗୁଡ଼ିକ କେବଳ ରିଜୋଲ୍ୟୁସନ୍ ଡାଇଗ୍ନୋଷ୍ଟିକ୍; ସେଗୁଡ଼ିକ କେବେ ମଧ୍ୟ +ବ୍ଲକିଂ ଫାଇଣ୍ଡିଂ ସୃଷ୍ଟି କରନ୍ତି ନାହିଁ। ସମୀକ୍ଷା କରାଯାଇଥିବା ବ୍ୟତିକ୍ରମଗୁଡ଼ିକ +`config/quality/forgotten-sibling-allowlist.json`ରେ ରହେ। ପ୍ରତ୍ୟେକ ଏଣ୍ଟ୍ରିରେ ଉପଭୋକ୍ତା ଏବଂ ପ୍ରାର୍ଥୀ +ଟେଷ୍ଟର ନାମ, ଏକ ନିର୍ଦ୍ଦିଷ୍ଟ ଯୁକ୍ତି, ଏବଂ ଏକ GitHub ଇସ୍ୟୁ କିମ୍ବା ପୁଲ୍ ରିକ୍ୱେଷ୍ଟର ଲିଙ୍କ ଦେବା ଆବଶ୍ୟକ। ତ୍ରୁଟିପୂର୍ଣ୍ଣ ଏଣ୍ଟ୍ରିଗୁଡ଼ିକ +ବନ୍ଦ ଅବସ୍ଥାରେ ବିଫଳ ହୁଏ। ବ୍ୟତିକ୍ରମଗୁଡ଼ିକ ଏକ ବିଲୋପିତ ପ୍ରାର୍ଥୀ ଟେଷ୍ଟ କିମ୍ବା `.skip`/`.todo` ଯୋଡ଼ୁଥିବା ଏକ diffକୁ ଦମନ କରିପାରିବ ନାହିଁ; +ଆସର୍ସନ୍ ଦୁର୍ବଳୀକରଣ ଏବଂ ଅନ୍ୟ ମାସ୍କିଂ ସ୍ୱାଧୀନ ଭାବେ ବ୍ଲକ୍ କରୁଥିବା +`check:test-masking` ଗେଟ୍ର ଅଧୀନରେ ରହେ। + +### ଜବ୍: `lint` + +`main` ପ୍ରତି ଥିବା ପ୍ରତ୍ୟେକ PRରେ ଚାଲେ। ବିଫଳ ହେଲେ ମର୍ଜ୍କୁ ବ୍ଲକ୍ କରେ। + +| ସ୍କ୍ରିପ୍ଟ (`npm run ...`) | ଯାହାକୁ ବୈଧ କରେ | ବ୍ଲକିଂ | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- | +| `check:node-runtime` | Node.js ସଂସ୍କରଣ ସମର୍ଥିତ ପରିସର ମଧ୍ୟରେ ଅଛି | ହଁ | +| `check:cycles` | ସମଗ୍ର `src/` + `open-sse/`ରେ ସର୍କୁଲାର୍ ଇମ୍ପୋର୍ଟଗୁଡ଼ିକ (AST-ଆଧାରିତ, tsconfig `paths` ରିଜଲ୍ଭ୍ କରାଯାଇଛି)। Bare = ପରାମର୍ଶମୂଳକ, ସାଇକେଲ୍ଗୁଡ଼ିକୁ ତାଲିକାଭୁକ୍ତ କରେ। `check:cycles:ratchet` (ଯାହାକୁ CI ଚଲାଏ) `quality-baseline.json`ରେ ଥିବା `metrics.cycles` ସର୍ବୋଚ୍ଚ ସୀମାଠାରୁ ଗଣନା ଅଧିକ ହେଲେ ବ୍ଲକ୍ କରେ — ବର୍ତ୍ତମାନ 14, `direction: down`, ତେଣୁ ଏହା କେବଳ କମିପାରିବ (#15159 G-01/G-02) | ହଁ (ରାଚେଟ୍) | +| `check:route-validation:t06` | ସମସ୍ତ ରୁଟ୍ରେ Zod ସ୍କିମା ଉପସ୍ଥିତ ଅଛି (ଟିୟର୍ 6 ନୀତି) | ହଁ | +| `check:any-budget:t11` | `@ts-expect-error // any` ଗଣନା ବଜେଟ୍ ଅତିକ୍ରମ କରେ ନାହିଁ (ଟିୟର୍ 11 catraca) | ହଁ | +| `check:provider-consistency` | `providers.ts` ର ପ୍ରତ୍ୟେକ provider ପାଇଁ `providerRegistry.ts` ରେ ଏକ ମେଳ ଖାଉଥିବା entry ଅଛି (ଏବଂ ଏହାର ବିପରୀତଟି ମଧ୍ୟ, allowlist ମଧ୍ୟରେ) | ହଁ | +| `check:model-lifecycle` | ହାତରେ ରକ୍ଷଣାବେକ୍ଷଣ କରାଯାଉଥିବା ତିନୋଟି routing table, checked-in lifecycle snapshot (#11503) ସହ ସୁସଙ୍ଗତ ରହେ: `FITNESS_TABLE` (`taskFitness.ts`) ଏପରି କୌଣସି ଅବସରପ୍ରାପ୍ତ id କୁ score କରେ ନାହିଁ ଯାହାକୁ `REGISTRY` route କରିପାରେ; ପ୍ରତ୍ୟେକ `BUILT_IN_ALIASES` target `REGISTRY` ରେ ଉପସ୍ଥିତ ଏବଂ ଅବସରପ୍ରାପ୍ତ-id snapshot ରେ ଅନୁପସ୍ଥିତ; `REGISTRY` ରେ ଏପର୍ଯ୍ୟନ୍ତ ଥିବା ପ୍ରତ୍ୟେକ ଅବସରପ୍ରାପ୍ତ id କୁ forward କରାଯାଇଛି କିମ୍ବା `allowedRetiredInCatalog` ରେ ତାଲିକାଭୁକ୍ତ କରାଯାଇଛି; ଏବଂ କୌଣସି `DEFAULT_DEGRADATION_MAP` source କିମ୍ବା target ସେହି snapshot ରେ ଅବସରପ୍ରାପ୍ତ ଭାବେ ଦେଖାଯାଏ ନାହିଁ। ଏହା କୌଣସି model ବର୍ତ୍ତମାନ ଏକ ସକ୍ରିୟ upstream ଦ୍ୱାରା ପରିବେଶିତ ହେଉଛି ବୋଲି ପ୍ରମାଣ କରେ ନାହିଁ। Offline — `config/quality/model-lifecycle.json` ସହ ତୁଳନା କରେ, ଯାହାକୁ `npm run quality:refresh-model-lifecycle` (network; CI ସହ ସଂଯୁକ୍ତ ନୁହେଁ) ଦ୍ୱାରା ହାତରେ refresh କରାଯାଏ। `allowedRetiredInCatalog` ଏକ burn-down ratchet: କେବଳ ଏକ tracking issue ସହିତ ଏକ entry ଯୋଡ଼ନ୍ତୁ। | ହଁ | +| `check:fetch-targets` | client-side `src/` ରେ ଥିବା ପ୍ରତ୍ୟେକ `fetch("/api/...")` ଏକ ପ୍ରକୃତ `route.ts` କୁ resolve ହୁଏ | ହଁ | +| `check:deps` | repo ର ପ୍ରତ୍ୟେକ `package.json` ରେ ଥିବା ସମସ୍ତ `npm install`-ଯୋଗ୍ୟ dep `dependency-allowlist.json` ରେ ଅଛି; ନୂତନ unpinned କିମ୍ବା slopsquatted package ଗୁଡ଼ିକୁ flag କରାଯାଏ | ହଁ | +| `audit:deps` | `npm audit` (root + electron) — କୌଣସି high/critical advisory ନାହିଁ (osv `check:vuln-ratchet` ସହ overlap କରେ; Rationalization Backlog ଦେଖନ୍ତୁ) | ହଁ | +| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hash, କୌଣସି host override ନାହିଁ | ହଁ | +| `check:licenses` | ପ୍ରଡକ୍ସନ୍ ନିର୍ଭରତାଗୁଡ଼ିକ ପାଇଁ SPDX ଲାଇସେନ୍ସ ଅନୁମୋଦିତ ତାଲିକା | ହଁ | +| `check:tracked-artifacts` | କୌଣସି ବିଲ୍ଡ ଆର୍ଟିଫ୍ୟାକ୍ଟ / କମିଟ୍ କରାଯାଇଥିବା `node_modules` ସିମ୍ଲିଙ୍କ୍ ନାହିଁ (husky pre-commitରେ ମଧ୍ୟ ଚାଲେ; pre-pushକୁ ଉଦ୍ଦେଶ୍ୟମୂଳକ ଭାବେ ହାଲୁକା ରଖାଯାଇଛି — #6716) | ହଁ | +| `check:ai-attribution` | PR କମିଟ୍, ଶୀର୍ଷକ କିମ୍ବା ବିବରଣୀରେ କୌଣସି AI/bot `Co-Authored-By` ଟ୍ରେଲର୍ କିମ୍ବା AI-ଜେନେରେସନ୍ ଫୁଟର୍ ନାହିଁ — କଠୋର ନିୟମ #16 (`quality.yml`ର PR→`release/**` ପାଇଁ fast-gates ଲୁପ୍ରେ — ଇଭେଣ୍ଟ ପେଲୋଡ୍ ପଢ଼େ, PR ବ୍ୟତୀତ ଅନ୍ୟ ସ୍ଥାନରେ କୌଣସି କାର୍ଯ୍ୟ କରେ ନାହିଁ — ଏବଂ `ci.yml` lintରେ PR→`main` ପାଇଁ କେବଳ-PR ପଦକ୍ଷେପ; husky `commit-msg` ହୁକ୍ରେ ମଧ୍ୟ; ମାନବ ସହ-ଲେଖକମାନେ ଅନୁମୋଦିତ; #14436) | +| `check:vitest-exclusions` | ପ୍ରତ୍ୟେକ Vitest ବହିଷ୍କରଣ ଏକ ଟ୍ରାକିଂ ଇସ୍ୟୁ ଉଲ୍ଲେଖ କରେ ଏବଂ `config/quality/vitest-exclusions.json`ରେ ଦେଖାଯାଏ (#13204) | ହଁ | +| `check:file-size` | କୌଣସି ସୋର୍ସ ଫାଇଲ୍ ପ୍ରତି-ଏକ୍ସଟେନ୍ସନ୍ ସୀମା ଅତିକ୍ରମ କରେ ନାହିଁ (ରାଚେଟ୍: `frozen` ତାଲିକାରେ ଫ୍ରିଜ୍ କରାଯାଇଥିବା ବଡ଼ ଫାଇଲ୍ଗୁଡ଼ିକ) | ହଁ | +| `check:error-helper` | ଏକ୍ସିକ୍ୟୁଟର୍/ହ୍ୟାଣ୍ଡଲର୍ଗୁଡ଼ିକରେ ତ୍ରୁଟି ପ୍ରତିକ୍ରିୟା `buildErrorBody()` / `sanitizeErrorMessage()` ବ୍ୟବହାର କରେ (କଠୋର ନିୟମ #12) | ହଁ | +| `check:migration-numbering` | Migration SQL ଫାଇଲଗୁଡ଼ିକ କୌଣସି କ୍ରମିକ ସଂଖ୍ୟାର ଅଭାବ କିମ୍ବା ପୁନରାବୃତ୍ତି ବିନା କ୍ରମାନୁସାରେ ସଂଖ୍ୟାଙ୍କିତ ହୋଇଛି | ହଁ | +| `check:public-creds` | `publicCreds.ts` ବାହାରେ କୌଣସି ସ୍ପଷ୍ଟ OAuth `client_id`/`client_secret` କିମ୍ବା Firebase Web କୀ ନାହିଁ (କଠୋର ନିୟମ #11) | ହଁ | +| `check:db-rules` | `src/lib/db/` ମଡ୍ୟୁଲଗୁଡ଼ିକ ବାହାରେ କୌଣସି କଞ୍ଚା SQL ନାହିଁ; `localDb.ts` ରୁ କୌଣସି ବ୍ୟାରେଲ୍ ଇମ୍ପୋର୍ଟ ନାହିଁ (କଠୋର ନିୟମ #2/#5) | ହଁ | +| `check:known-symbols` | ସେମାନଙ୍କ ଡିସ୍ପାଚ୍ ଟେବୁଲରେ ପଞ୍ଜୀକୃତ ପ୍ରଦାତା ନିଷ୍ପାଦକ, ରାଉଟିଂ କୌଶଳ ଏବଂ ଅନୁବାଦକଗୁଡ଼ିକ ଡିସ୍କରେ ଥିବା ଫାଇଲଗୁଡ଼ିକ ସହ ମେଳ ଖାଆନ୍ତି — କୌଣସି ଅନାଥ କିମ୍ବା ଅଘୋଷିତ ସଙ୍କେତ ନାହିଁ | ହଁ | +| `check:route-guard-membership` | ଚାଇଲ୍ଡ ପ୍ରୋସେସ୍ ଆରମ୍ଭ କରୁଥିବା ପ୍ରତ୍ୟେକ ରୁଟ୍ `isLocalOnlyPath()` ଦ୍ୱାରା ବର୍ଗୀକୃତ ହୋଇଛି (କଠୋର ନିୟମ #15/#17) | ହଁ | +| `check:test-discovery` | ରିପୋରେ ଥିବା ପ୍ରତ୍ୟେକ `*.test.ts` / `*.spec.ts` ଫାଇଲ୍ ଅତି କମରେ ଗୋଟିଏ ଟେଷ୍ଟ ରନର୍ ଦ୍ୱାରା ସଂଗୃହୀତ ହୁଏ (ରାଚେଟ୍: `test-discovery-baseline.json` ରେ ଥିବା ଅନାଥ ତାଲିକା କେବଳ ସଙ୍କୁଚିତ ହୋଇପାରିବ) | ହଁ | +| `check:agent-skills-sync` | ସୃଷ୍ଟି ହୋଇଥିବା agent-skills ଆର୍ଟିଫ୍ୟାକ୍ଟଗୁଡ଼ିକ ସେମାନଙ୍କର ଉତ୍ସ କ୍ୟାଟାଲଗ୍ ସହ ମେଳ ଖାଉଛି (କୌଣସି ବିଚ୍ୟୁତି ନାହିଁ) | +| `check:provider-asset-provenance` | ପ୍ରଦାତାଙ୍କ ଲୋଗୋ/ଆସେଟ୍ଗୁଡ଼ିକର ଏକ ରେକର୍ଡ କରାଯାଇଥିବା ଉତ୍ପତ୍ତି-ସୂଚନା ଏଣ୍ଟ୍ରି ରହିଛି | +| `lint:json` | JSON କନଫିଗ୍ ଫାଇଲ୍ଗୁଡ଼ିକ ପାର୍ସ ହୁଏ ଏବଂ ରିପୋଜିଟୋରିର ଲିଣ୍ଟ ନିୟମଗୁଡ଼ିକ ପୂରଣ କରେ | +| `typecheck:core` | ତ୍ରୁଟି ବିନା TypeScript କମ୍ପାଇଲେସନ୍ (କେବଳ ପରାମର୍ଶମୂଳକ ଚେତାବନୀ) | ହଁ | +| `typecheck:noimplicit:core` | କଠୋର `noImplicitAny` — ଭବିଷ୍ୟତମୁଖୀ; ପୂର୍ବରୁ ଥିବା ଅନେକ କଲ୍ ସାଇଟ୍ରେ ଏବେ ମଧ୍ୟ ଆନୋଟେସନ୍ ଆବଶ୍ୟକ | **ପରାମର୍ଶମୂଳକ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` ପର୍ଯ୍ୟନ୍ତ ସୀମିତ `tsc` (#7033) — `typecheck:core`ର ସୁନିର୍ଦ୍ଧାରିତ 27-ଫାଇଲ୍ ଅନୁମତି-ତାଲିକାରେ କୌଣସି ଡ୍ୟାସ୍ବୋର୍ଡ TSX ଅନ୍ତର୍ଭୁକ୍ତ ନାହିଁ, ଏବଂ `next build` ମଧ୍ୟ ଏହାର ପ୍ରକାର ଯାଞ୍ଚ କରେ ନାହିଁ (`next.config.mjs`ରେ `ignoreBuildErrors: true` ସେଟ୍ କରାଯାଇଛି), ତେଣୁ ସେଠାରେ ଥିବା ଅନାଥ-ଆଇଡେଣ୍ଟିଫାୟର୍ ରିଗ୍ରେସନ୍ଗୁଡ଼ିକ (#6625/#6909) CI ପାଇଁ ଅଦୃଶ୍ୟ ଥିଲା। ଏକ ସ୍ଥିର କରାଯାଇଥିବା ପ୍ରତି-ଫାଇଲ୍/ପ୍ରତି-TS-କୋଡ୍ ଗଣନା ବେସ୍ଲାଇନ୍ (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ପରି ସମାନ ପୁରୁଣା-ସ୍ଥିତି ପ୍ରବର୍ତ୍ତନ ଢାଞ୍ଚା) ସହ ପାର୍ଥକ୍ୟ ଯାଞ୍ଚ କରେ — ବେସ୍ଲାଇନ୍ ଗଣନାଠାରୁ ଅଧିକ କେବଳ ନୂତନ ତ୍ରୁଟିଗୁଡ଼ିକ ଗେଟ୍କୁ ବିଫଳ କରେ; ପୂର୍ବରୁ ଥିବା କୌଣସି ତ୍ରୁଟି ସମାଧାନ ହେଲେ `--update` ସହିତ ବେସ୍ଲାଇନ୍କୁ କ୍ରମେ କମାନ୍ତୁ। | ହଁ | ### ଜବ୍: `quality-gate` `test-coverage` ପରେ ଚାଲେ। ବିଫଳ ହେଲେ ମର୍ଜ୍କୁ ଅବରୋଧ କରେ। -| ସ୍କ୍ରିପ୍ଟ | ଯାହାକୁ ବୈଧ କରେ | ଅବରୋଧକ | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | -| `quality:collect` | `quality-metrics.json` ନିର୍ଗତ କରେ (ESLint ଚେତାବନୀ ସଂଖ୍ୟା, ମିଶ୍ରିତ shard ରିପୋର୍ଟରୁ coverage) | ହଁ (ratchetର upstream) | -| `quality:ratchet` | `quality-baseline.json`ର ପ୍ରତ୍ୟେକ metricର ଅବନତି ହୋଇନାହିଁ (ESLint ଚେତାବନୀ ≤ baseline; coverage ≥ baseline) | ହଁ | -| `check:duplication` | କୋଡ୍ ନକଲ (jscpd@4) `quality-baseline.json`ର baselineକୁ ଅତିକ୍ରମ କରେ ନାହିଁ | ହଁ | -| `check:complexity` | ଫାଇଲ୍-ସ୍ତରୀୟ cyclomatic complexity ନିର୍ଦ୍ଧାରିତ ସୀମାକୁ ଅତିକ୍ରମ କରେ ନାହିଁ (ମୂଳ ESLint `complexity` + `max-lines-per-function`) | ହଁ | -| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — ପୃଥକ ESLint pass; CI ଉଭୟକୁ ଏକକ `check:complexity-ratchets` ପଦକ୍ଷେପ ଭାବେ ମିଶାଇ ଚଲାଏ | ହଁ | -| `check:dead-code` | ଅବ୍ୟବହୃତ exports / files ratchet (knip)ର baseline ତୁଳନାରେ ଅବନତି ହୁଏ ନାହିଁ | ହଁ | -| `check:compression-budget` | Compression benchmark budget — ପ୍ରତ୍ୟେକ engineର token-savings floorsର ଅବନତି ହେବା ଉଚିତ ନୁହେଁ | ହଁ | -| `check:type-coverage` | Percent-typed ratchet (`type-coverage`)ର ଅବନତି ହୁଏ ନାହିଁ; ଏହା ମୁଖ୍ୟତଃ `typecheck:noimplicit:core`କୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ | ହଁ | -| `check:codeql-ratchet` | ଖୋଲା CodeQL alert ସଂଖ୍ୟାର ଅବନତି ହୁଏ ନାହିଁ (`gh api` ମାଧ୍ୟମରେ ପଢ଼େ; token ବିନା ସୁରକ୍ଷିତ ଭାବେ skip କରେ) — refresh cadence ଏବଂ manual trigger: ତଳେ "CodeQL ratchet" ଦେଖନ୍ତୁ | ହଁ | - -### Job: `quality-extended` - -ସମ୍ପୂର୍ଣ୍ଣ jobଟି ପରାମର୍ଶମୂଳକ (`continue-on-error: true`)। npm-ଆଧାରିତ ratchets ବାସ୍ତବରେ -ଚାଲେ; external scannersଗୁଡ଼ିକ `gh release download` ମାଧ୍ୟମରେ install ହୁଅନ୍ତି ଏବଂ କୌଣସି binary ଏପର୍ଯ୍ୟନ୍ତ ଅନୁପସ୍ଥିତ -ଥିଲେ ନିଜେ skip କରନ୍ତି (exit 0)। - -| ସ୍କ୍ରିପ୍ଟ | ଯାହାକୁ ବୈଧ କରେ | ଅବରୋଧକ | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:circular-deps` | କୌଣସି circular dependencies ନାହିଁ (dpdm) | **ପରାମର୍ଶମୂଳକ** | -| `check:bundle-size` | Bundle size ନିର୍ଦ୍ଧାରିତ ସୀମାକୁ ଅତିକ୍ରମ କରେ ନାହିଁ | **ପରାମର୍ଶମୂଳକ** | -| `check:secrets` | Secret scanning (gitleaks) — binary ଅନୁପସ୍ଥିତ ଥିଲେ skip କରେ | **ପରାମର୍ଶମୂଳକ** | -| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner)ର ଅବନତି ହୁଏ ନାହିଁ — binary ଅନୁପସ୍ଥିତ ଥିଲେ skip କରେ | **ପରାମର୍ଶମୂଳକ** | -| `check:workflows` | Workflow lint (actionlint + zizmor); ଅନୁପସ୍ଥିତ/ତ୍ରୁଟିପୂର୍ଣ୍ଣ scanners, ଅବୈଧ reports କିମ୍ବା ଅନୁପସ୍ଥିତ ratchet baseline, INCOMPLETE ଭାବେ ବିଫଳ ହୁଏ। ବୈଧ findings ଚୟନିତ strict/advisory/ratchet policy ଅନୁସରଣ କରେ | Execution ଆବଶ୍ୟକ; CIରେ zizmor ratchet ଅବରୋଧକ | -| `check:openapi-breaking` | ସାର୍ବଜନୀନ API contract (`openapi.yaml`)ରେ base branch ତୁଳନାରେ breaking changes (oasdiff) — `openapiBreaking=N` ନିର୍ଗତ କରେ; oasdiff ଅନୁପସ୍ଥିତ କିମ୍ବା base spec ସମାଧାନଯୋଗ୍ୟ ନହେଲେ skip କରେ | **ପରାମର୍ଶମୂଳକ** | - -### Job: `docs-sync-strict` - -`main`କୁ ଯାଉଥିବା ପ୍ରତ୍ୟେକ PRରେ ଚାଲେ। ବିଫଳ ହେଲେ mergeକୁ ଅବରୋଧ କରେ। - -| ସ୍କ୍ରିପ୍ଟ | ଯାଞ୍ଚ କରେ | ଅବରୋଧକ | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| `check:docs-all` | ନିମ୍ନର 6ଟି ଉପ-ଗେଟ୍କୁ କ୍ରମାନୁସାରେ ଚଲାଉଥିବା ମେଟା-ଗେଟ୍ | ହଁ | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ସଂସ୍କରଣ ସୁସଙ୍ଗତତା | ହଁ | -| ↳ `check:docs-counts` | ଗଦ୍ୟରେ ଥିବା ସଂଖ୍ୟାଗୁଡ଼ିକ (ପ୍ରଦାନକାରୀ ସଂଖ୍ୟା, ମାଇଗ୍ରେସନ୍ ସଂଖ୍ୟା ଇତ୍ୟାଦି) ପ୍ରକୃତ ସଂଖ୍ୟାର ରାଚେଟ୍ ସୀମା ମଧ୍ୟରେ ଅଛି | ହଁ | -| ↳ `check:env-doc-sync` | `.env.example`ର ପ୍ରତ୍ୟେକ ପରିବେଶ ଭେରିଏବଲ୍ ଏକ ଡକ୍ୟୁମେଣ୍ଟେସନ୍ ଟେବୁଲ୍ରେ ଲିପିବଦ୍ଧ ଅଛି, ଏବଂ ତାହାର ବିପରୀତଟି ମଧ୍ୟ ସତ୍ୟ | ହଁ | -| ↳ `check:deprecated-versions` | ଡକ୍ୟୁମେଣ୍ଟଗୁଡ଼ିକରେ କୌଣସି ଅବମୂଲ୍ୟାୟିତ ସଂସ୍କରଣ ଷ୍ଟ୍ରିଙ୍ଗ୍ ନାହିଁ | ହଁ | -| ↳ `check:doc-links` | ଡକ୍ୟୁମେଣ୍ଟଗୁଡ଼ିକର ଆଭ୍ୟନ୍ତରୀଣ ମାର୍କଡାଉନ୍ ଲିଙ୍କ୍ଗୁଡ଼ିକ ପ୍ରକୃତ ଫାଇଲ୍କୁ ରିଜଲ୍ଭ ହୁଏ (`[text]`/`(path)` ରୂପ) | ହଁ | -| ↳ `check:fabricated-docs` | ଡକ୍ୟୁମେଣ୍ଟରେ ଉଲ୍ଲେଖିତ ରୁଟ୍, ପରିବେଶ ଭେରିଏବଲ୍, CLI କମାଣ୍ଡ, ହୁକ୍ ନାମ ଏବଂ ଫାଇଲ୍ ପଥଗୁଡ଼ିକ କୋଡ୍ବେସ୍ରେ ଅଛି। `--strict` ମାଧ୍ୟମରେ କଠୋର ଗେଟ୍; ଫ୍ଲାଗ୍ ବିନା ସଫ୍ଟ-ଫେଲ୍। | ହଁ (CIରେ `--strict` ମାଧ୍ୟମରେ) | -| `check:cli-i18n` | CLI କମାଣ୍ଡ ଷ୍ଟ୍ରିଙ୍ଗ୍ଗୁଡ଼ିକ ସମସ୍ତ i18n ଲୋକେଲ୍ ଫାଇଲ୍ରେ ଉପସ୍ଥିତ | ହଁ | -| `check:openapi-coverage` | OpenAPI ସ୍ପେକ୍ ପ୍ରକୃତ ରୁଟ୍ଗୁଡ଼ିକର ଅତି କମରେ ଏକ ରାଚେଟ୍-ନିର୍ଦ୍ଧାରିତ ସର୍ବନିମ୍ନ ସୀମାକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ | ହଁ | -| `check:openapi-security-tiers` | `openapi.yaml`ରେ ଥିବା ସୁରକ୍ଷା ସ୍ତର ଆନୋଟେସନ୍ଗୁଡ଼ିକ `routeGuard.ts` ବର୍ଗୀକରଣ ସହିତ ସୁସଙ୍ଗତ | **ପରାମର୍ଶମୂଳକ** | -| `check:openapi-routes` | `openapi.yaml`ର ପ୍ରତ୍ୟେକ ପଥ ଏକ ପ୍ରକୃତ `route.ts`କୁ ରିଜଲ୍ଭ ହୁଏ (କଳ୍ପନା-ବିରୋଧୀ) | ହଁ | -| `check:docs-symbols` | `docs/**/*.md`ର ପ୍ରତ୍ୟେକ `/api/...` ସନ୍ଦର୍ଭ ଏକ ପ୍ରକୃତ `route.ts`କୁ ରିଜଲ୍ଭ ହୁଏ (କଳ୍ପନା-ବିରୋଧୀ) | ହଁ | -| `i18n translation drift` | i18n ଲୋକେଲ୍ ଫାଇଲ୍ଗୁଡ଼ିକରେ ଅନୁବାଦ ହୋଇନଥିବା କୀ — କେବଳ ଚେତାବନୀ | **ପରାମର୍ଶମୂଳକ** | - -### କାର୍ଯ୍ୟ: `i18n-ui-coverage` - -| ସ୍କ୍ରିପ୍ଟ | ଯାଞ୍ଚ କରେ | ଅବରୋଧକ | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | -| `check-ui-keys-coverage` (ଇନ୍ଲାଇନ୍) | UI i18n କୀ କଭରେଜ୍ ≥ 65% | ହଁ | -| `check-ui-value-drift` (ଇନ୍ଲାଇନ୍) | ପୁନର୍ଲିଖିତ ଇଂରାଜୀ **ମୂଲ୍ୟ** ପଛରେ କୌଣସି ଅଚଳ ଅନୁବାଦ ଛାଡ଼େ ନାହିଁ | ହଁ | -| `check-new-key-coverage` (ଇନ୍ଲାଇନ୍) | ଏକ **ନୂତନ** ଇଂରାଜୀ କୀ ପ୍ରତ୍ୟେକ ଲୋକେଲ୍ରେ ଅନୁବାଦିତ — ଏକ `__MISSING__:` ମାର୍କର୍ ପ୍ରତ୍ୟାଖ୍ୟାନ କରାଯାଏ | ହଁ | -| `check-translation-ratio` | ପ୍ରତି ଲୋକେଲ୍ର ପ୍ରକୃତ-ଅନୁବାଦ ଅନୁପାତ (ଅନୁମୋଦିତ ତାଲିକା ବାହାରେ ଇଂରାଜୀ ସହ ସମାନ / ପ୍ଲେସହୋଲ୍ଡର୍ / ଅନୁପସ୍ଥିତ ଲିଫ୍) `config/quality/i18n-translation-baseline.json` + ଅତିରିକ୍ତ ସୀମାକୁ ଅତିକ୍ରମ କରିବା ଉଚିତ୍ ନୁହେଁ | **ପରାମର୍ଶମୂଳକ** | - -`fetch-depth: 0` ଆବଶ୍ୟକ — ମୂଲ୍ୟ-ଡ୍ରିଫ୍ଟ ଗେଟ୍ ମର୍ଜ୍ ବେସ୍ ସହିତ `en.json`ର ଡିଫ୍ କରେ। - -#### `check-ui-value-drift` — ଅଚଳ-ଅନୁବାଦ ଗେଟ୍ - -ଏହା ସେହି ଗୋଟିଏ i18n ରିଗ୍ରେସନ୍କୁ ଧରେ ଯାହାକୁ ଅନ୍ୟ ଗେଟ୍ଗୁଡ଼ିକ ଗଠନାତ୍ମକ ଭାବେ ଦେଖିପାରନ୍ତି ନାହିଁ: ଏକ ଇଂରାଜୀ ମୂଲ୍ୟ -ପୁନର୍ଲିଖିତ ହୁଏ ଏବଂ _ପୂର୍ବବର୍ତ୍ତୀ_ ଇଂରାଜୀରୁ ଉତ୍ପନ୍ନ ଅନୁବାଦଗୁଡ଼ିକ ପଛରେ ରହିଯାଏ, ତେଣୁ -ଅଣ-ଇଂରାଜୀ ବ୍ୟବହାରକାରୀମାନେ ଆତ୍ମବିଶ୍ୱାସୀ ଭାଷାରେ ଲିଖିତ, କିନ୍ତୁ ବର୍ତ୍ତମାନ ଭୁଲ୍ ଥିବା ପାଠ୍ୟ ପଢ଼ିବା ଜାରି ରଖନ୍ତି। - -ଏହା ପ୍ରକୃତରେ ରିଲିଜ୍ ହୋଇଥିଲା। Antigravity ଲଗ୍ଇନ୍ ସହାୟକ ଆସିବା ସମୟରେ (#5203) -`oauthModal.googleOAuthWarning` ପୁନର୍ଲିଖିତ ହୋଇଥିଲା; **43ଟି ଲୋକେଲ୍ରୁ 39ଟି**ରେ ଅପରେଟର୍ମାନଙ୍କୁ "ସମ୍ପୂର୍ଣ୍ଣ URL କପି କରି ତଳେ ପେଷ୍ଟ କରନ୍ତୁ" ବୋଲି କହୁଥିବା ପାଠ୍ୟ ରହିଯାଇଥିଲା — ଏକ ଏପରି ପ୍ରବାହ ଯାହା ସେହି ପ୍ରଦାନକାରୀ ପାଇଁ ସମ୍ପୂର୍ଣ୍ଣ ହୋଇପାରେ ନାହିଁ। ଏହା -#8463 ପର୍ଯ୍ୟନ୍ତ ଅଲକ୍ଷିତ ରହିଲା, କାରଣ: - -- `sync-ui-keys` କେବଳ **ଅନୁପସ୍ଥିତ** କୀଗୁଡ଼ିକୁ ବ୍ୟାକ୍ଫିଲ୍ କରେ, **ଅଚଳ** କୀଗୁଡ଼ିକୁ କେବେ ନୁହେଁ; -- `check-ui-keys-coverage` କୀର _ଉପସ୍ଥିତି_ ଗଣନା କରେ, ତେଣୁ ଏକ ଅଚଳ ଅନୁବାଦକୁ କଭର୍ ହୋଇଛି ବୋଲି ଗଣାଯାଏ; -- `check-translation-drift` `docs/i18n//**.md` ଡକ୍ୟୁମେଣ୍ଟେସନ୍ ପ୍ରତିରୂପଗୁଡ଼ିକୁ ଟ୍ରାକ୍ କରେ — - ଏହା କେବେବି `src/i18n/messages/*.json` ପଢ଼େ ନାହିଁ। 2026-09 ପୁନଃ-ସିଙ୍କ୍ ପରଠାରୁ `docs-sync-strict` କାର୍ଯ୍ୟରେ ଅବରୋଧକ: ଏକ ମୁଖ୍ୟ ଡକ୍ୟୁମେଣ୍ଟ ସମ୍ପାଦନା କରନ୍ତୁ → `npm run i18n:run -- --files=` (ବିଭାଗ-ସ୍ତରୀୟ, କମ୍ ଖର୍ଚ୍ଚସାଧ୍ୟ)। - -**Diff-ସଚେତନ, baseline-ସମର୍ଥିତ ନୁହେଁ।** ଏହା merge base-ର `en.json`କୁ working tree ସହିତ ତୁଳନା କରେ; ଯେଉଁ ପ୍ରତ୍ୟେକ key-ର ଇଂରାଜୀ value ପରିବର୍ତ୍ତିତ ହୋଇଛି, ସେଥିପାଇଁ ଏପର୍ଯ୍ୟନ୍ତ ଅପରିବର୍ତ୍ତିତ translation ଧାରଣ କରିଥିବା ଯେକୌଣସି locale ପୁରୁଣା ହୋଇଯାଇଛି। ଏହା ଉଦ୍ଦେଶ୍ୟମୂଳକ ଭାବେ **ପୂର୍ବରୁ ଥିବା ବକେୟା କାମକୁ ସ୍ଥିର କରିଦିଏ** — ଦୀର୍ଘଦିନ ଧରି ଥିବା କୌଣସି translation କେଉଁ ପୁରୁଣା ଇଂରାଜୀରୁ ଆସିଛି, ଏକ diff ତାହା ପ୍ରକାଶ କରିପାରେ ନାହିଁ; ତେଣୁ gate କେବଳ ବର୍ତ୍ତମାନର ପରିବର୍ତ୍ତନ ଯାହାକୁ ସ୍ପର୍ଶ କରେ, ତାହାର ହିଁ ବିଚାର କରେ। ବିକଳ୍ପଟି (ପ୍ରତି-key hash baseline) ପାଇଁ ଏକ ~600 KB generated file ଆବଶ୍ୟକ ହେବ, ଯାହା ସବୁଠାରୁ ବଡ଼ ବର୍ତ୍ତମାନର baseline-ର 3×, ଏବଂ ପ୍ରତ୍ୟେକ i18n PR-ରେ ବାରମ୍ବାର ବଦଳିବ। +| ସ୍କ୍ରିପ୍ଟ | ଯାଞ୍ଚ କରେ | ଅବରୋଧକ | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `quality:collect` | `quality-metrics.json` ନିର୍ଗତ କରେ (ESLint ଚେତାବନୀ ସଂଖ୍ୟା, ମିଶ୍ରିତ shard ରିପୋର୍ଟରୁ coverage) | ହଁ (ratchet-ର upstream) | +| `quality:ratchet` | `quality-baseline.json`-ର ପ୍ରତ୍ୟେକ metric-ର ଅବନତି ହୋଇନାହିଁ (ESLint ଚେତାବନୀ ≤ baseline; coverage ≥ baseline) | ହଁ | +| `check:duplication` | କୋଡ୍ ନକଲ (jscpd@4) `quality-baseline.json`-ର baseline ଅତିକ୍ରମ କରେ ନାହିଁ | ହଁ | +| `check:complexity` | ଫାଇଲ୍-ସ୍ତରୀୟ cyclomatic complexity ସର୍ବାଧିକ ସୀମା ଅତିକ୍ରମ କରେ ନାହିଁ (ମୂଳ ESLint `complexity` + `max-lines-per-function`) | ହଁ | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — ପୃଥକ ESLint pass; CI ଉଭୟକୁ ଏକକ `check:complexity-ratchets` ପଦକ୍ଷେପ ଭାବେ ମିଶ୍ରିତ ଭାବରେ ଚଲାଏ | ହଁ | +| `check:dead-code` | ଅବ୍ୟବହୃତ exports / files ratchet (knip) baseline ତୁଳନାରେ ଅବନତି କରେ ନାହିଁ | ହଁ | +| `check:compression-budget` | Compression benchmark budget — ପ୍ରତି-engine token-savings-ର ନିମ୍ନସୀମାର ଅବନତି ହେବା ଉଚିତ୍ ନୁହେଁ | ହଁ | +| `check:type-coverage` | ଶତକଡ଼ା-typed ratchet (`type-coverage`) ଅବନତି କରେ ନାହିଁ; ବହୁମାତ୍ରାରେ `typecheck:noimplicit:core`-କୁ ଅନ୍ତର୍ଭୁକ୍ତ କରେ | ହଁ | +| `check:codeql-ratchet` | ଖୋଲା CodeQL alert ସଂଖ୍ୟାର ଅବନତି ହୁଏ ନାହିଁ (`gh api` ମାଧ୍ୟମରେ ପଢ଼େ; token ବିନା ସୁରକ୍ଷିତ ଭାବେ ଏଡ଼ାଇଯାଏ) — refresh cadence ଏବଂ manual trigger: ନିମ୍ନରେ "CodeQL ratchet" ଦେଖନ୍ତୁ | ହଁ | + +### କାର୍ଯ୍ୟ: `quality-extended` + +ସମ୍ପୂର୍ଣ୍ଣ କାର୍ଯ୍ୟଟି ପରାମର୍ଶମୂଳକ (`continue-on-error: true`)। npm-ଆଧାରିତ ratchet-ଗୁଡ଼ିକ +ପ୍ରକୃତରେ ଚାଲେ; ବାହ୍ୟ scanner-ଗୁଡ଼ିକ `gh release download` ମାଧ୍ୟମରେ install ହୁଅନ୍ତି ଏବଂ +binary ତଥାପି ଅନୁପସ୍ଥିତ ଥିଲେ ସ୍ୱୟଂ-ଏଡ଼ାଇଯାଆନ୍ତି (exit 0)। + +| ସ୍କ୍ରିପ୍ଟ | ଯାଞ୍ଚ କରେ | ଅବରୋଧକ | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `check:circular-deps` | କୌଣସି circular dependency ନାହିଁ (dpdm) | **ପରାମର୍ଶମୂଳକ** | +| `check:bundle-size` | Bundle size ସର୍ବାଧିକ ସୀମା ଅତିକ୍ରମ କରେ ନାହିଁ | **ପରାମର୍ଶମୂଳକ** | +| `check:secrets` | Secret scanning (gitleaks) — binary ଅନୁପସ୍ଥିତ ଥିଲେ ଏଡ଼ାଇଯାଏ | **ପରାମର୍ଶମୂଳକ** | +| `check:vuln-ratchet` | Dependency vulnerability-ଗୁଡ଼ିକର (osv-scanner) ଅବନତି ହୁଏ ନାହିଁ — binary ଅନୁପସ୍ଥିତ ଥିଲେ ଏଡ଼ାଇଯାଏ | **ପରାମର୍ଶମୂଳକ** | +| `check:workflows` | Workflow lint (actionlint + zizmor); scanner ଅନୁପସ୍ଥିତ/ତ୍ରୁଟିପୂର୍ଣ୍ଣ ହେଲେ, report ଅବୈଧ ହେଲେ କିମ୍ବା ratchet baseline ଅନୁପସ୍ଥିତ ଥିଲେ INCOMPLETE ଭାବେ ବିଫଳ ହୁଏ। ବୈଧ finding-ଗୁଡ଼ିକ ଚୟନିତ strict/advisory/ratchet policy ଅନୁସରଣ କରେ | Execution ଆବଶ୍ୟକ; CI-ରେ zizmor ratchet ଅବରୋଧକ | +| `check:openapi-breaking` | ସାର୍ବଜନୀନ API contract (`openapi.yaml`)-ରେ base branch ତୁଳନାରେ breaking change (oasdiff) — `openapiBreaking=N` ନିର୍ଗତ କରେ; oasdiff ଅନୁପସ୍ଥିତ ହେଲେ କିମ୍ବା base spec resolve ହୋଇନପାରିଲେ ଏଡ଼ାଇଯାଏ | **ପରାମର୍ଶମୂଳକ** | + +### କାର୍ଯ୍ୟ: `docs-sync-strict` + +`main`-କୁ ଯାଉଥିବା ପ୍ରତ୍ୟେକ PR-ରେ ଚାଲେ। ବିଫଳ ହେଲେ merge-କୁ ଅବରୋଧ କରେ। + +| ସ୍କ୍ରିପ୍ଟ | ଯାହାକୁ ବୈଧ କରେ | ଅବରୋଧକ | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `check:docs-all` | ନିମ୍ନରେ ଥିବା 6ଟି ଉପ-ଗେଟ୍କୁ କ୍ରମାନୁସାରେ ଚଲାଉଥିବା ମେଟା-ଗେଟ୍ | ହଁ | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ସଂସ୍କରଣ ସାମଞ୍ଜସ୍ୟ | ହଁ | +| ↳ `check:docs-counts` | ଗଦ୍ୟରେ ଥିବା ଗଣନାଗୁଡ଼ିକ (ପ୍ରଦାତା ସଂଖ୍ୟା, ମାଇଗ୍ରେସନ୍ ସଂଖ୍ୟା ଇତ୍ୟାଦି) ବାସ୍ତବ ଗଣନାର ରାଚେଟ୍ ୱିଣ୍ଡୋ ମଧ୍ୟରେ ଅଛି କି ନାହିଁ | ହଁ | +| ↳ `check:env-doc-sync` | `.env.example`ର ପ୍ରତ୍ୟେକ env var ଏକ ଡକ୍ସ ଟେବୁଲ୍ରେ ଦଲିଲଭୁକ୍ତ ହୋଇଛି କି ନାହିଁ, ଏବଂ ଏହାର ବିପରୀତଟି ମଧ୍ୟ | ହଁ | +| ↳ `check:deprecated-versions` | ଡକ୍ସରେ କୌଣସି ଅପ୍ରଚଳିତ ସଂସ୍କରଣ ଷ୍ଟ୍ରିଙ୍ଗ୍ ନାହିଁ କି ନାହିଁ | ହଁ | +| ↳ `check:doc-links` | ଡକ୍ସର ଆଭ୍ୟନ୍ତରୀଣ markdown ଲିଙ୍କ୍ଗୁଡ଼ିକ ବାସ୍ତବ ଫାଇଲ୍କୁ ରିଜଲ୍ଭ ହୁଏ କି ନାହିଁ (`[text]`/`(path)` ଫର୍ମ) | ହଁ | +| ↳ `check:fabricated-docs` | ଡକ୍ସରେ ଉଲ୍ଲେଖିତ ରୁଟ୍, env var, CLI କମାଣ୍ଡ୍, ହୁକ୍ ନାମ ଏବଂ ଫାଇଲ୍ ପଥ କୋଡ୍ବେସ୍ରେ ଅଛି କି ନାହିଁ। `--strict` ମାଧ୍ୟମରେ ହାର୍ଡ ଗେଟ୍; ଫ୍ଲାଗ୍ ବିନା ସଫ୍ଟ-ଫେଲ୍। | ହଁ (CIରେ `--strict` ମାଧ୍ୟମରେ) | +| `check:cli-i18n` | CLI କମାଣ୍ଡ୍ ଷ୍ଟ୍ରିଙ୍ଗ୍ଗୁଡ଼ିକ ସମସ୍ତ i18n ଲୋକେଲ୍ ଫାଇଲ୍ରେ ଅଛି କି ନାହିଁ | ହଁ | +| `check:openapi-coverage` | OpenAPI ସ୍ପେକ୍ ବାସ୍ତବ ରୁଟ୍ଗୁଡ଼ିକର ଅତିକମରେ ଏକ ରାଚେଟ୍ କରାଯାଇଥିବା ନ୍ୟୂନତମ ସୀମାକୁ ଆବୃତ କରେ କି ନାହିଁ | ହଁ | +| `check:openapi-security-tiers` | `openapi.yaml`ର ସୁରକ୍ଷା ସ୍ତର ଆନୋଟେସନ୍ଗୁଡ଼ିକ `routeGuard.ts`ର ବର୍ଗୀକରଣ ସହ ସାମଞ୍ଜସ୍ୟପୂର୍ଣ୍ଣ କି ନାହିଁ | **ପରାମର୍ଶମୂଳକ** | +| `check:openapi-routes` | `openapi.yaml`ର ପ୍ରତ୍ୟେକ ପଥ ବାସ୍ତବ `route.ts`କୁ ରିଜଲ୍ଭ ହୁଏ କି ନାହିଁ (କଳ୍ପିତ ତଥ୍ୟ-ବିରୋଧୀ) | ହଁ | +| `check:docs-symbols` | `docs/**/*.md`ର ପ୍ରତ୍ୟେକ `/api/...` ରେଫରେନ୍ସ ବାସ୍ତବ `route.ts`କୁ ରିଜଲ୍ଭ ହୁଏ କି ନାହିଁ (କଳ୍ପିତ ତଥ୍ୟ-ବିରୋଧୀ) | ହଁ | +| `i18n translation drift` | i18n ଲୋକେଲ୍ ଫାଇଲ୍ଗୁଡ଼ିକରେ ଅନୁବାଦ ହୋଇନଥିବା କୀ — କେବଳ ଚେତାବନୀ | **ପରାମର୍ଶମୂଳକ** | + +### ଜବ୍: `i18n-ui-coverage` + +| ସ୍କ୍ରିପ୍ଟ | ଯାହାକୁ ବୈଧ କରେ | ଅବରୋଧକ | +| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (ଇନ୍ଲାଇନ୍) | UI i18n କୀ କଭରେଜ୍ ≥ 65% କି ନାହିଁ | ହଁ | +| `check-ui-value-drift` (ଇନ୍ଲାଇନ୍) | ପୁନଃଲିଖିତ ଇଂରାଜୀ **ମୂଲ୍ୟ** ପଛରେ କୌଣସି ପୁରୁଣା ଅନୁବାଦ ଛାଡ଼େ ନାହିଁ କି ନାହିଁ | ହଁ | +| `check-new-key-coverage` (ଇନ୍ଲାଇନ୍) | ଏକ **ନୂଆ** ଇଂରାଜୀ କୀ ପ୍ରତ୍ୟେକ ଲୋକେଲ୍ରେ ଅନୁବାଦିତ ହୋଇଛି କି ନାହିଁ — ଏକ `__MISSING__:` ମାର୍କର୍ ପ୍ରତ୍ୟାଖ୍ୟାନ କରାଯାଏ | ହଁ | +| `check-translation-ratio` | ପ୍ରତ୍ୟେକ ଲୋକେଲ୍ ପାଇଁ ବାସ୍ତବ-ଅନୁବାଦ ଅନୁପାତ (ଅନୁମୋଦିତ ତାଲିକା ବାହାରେ ଇଂରାଜୀ ସହ ସମାନ / ପ୍ଲେସ୍ହୋଲ୍ଡର୍ / ଅନୁପସ୍ଥିତ ଲିଫ୍) `config/quality/i18n-translation-baseline.json` + ଅତିରିକ୍ତ ସୀମାକୁ ଅତିକ୍ରମ କରିବା ଉଚିତ୍ ନୁହେଁ | **ପରାମର୍ଶମୂଳକ** | + +`fetch-depth: 0` ଆବଶ୍ୟକ — value-drift ଗେଟ୍ ମର୍ଜ୍ ବେସ୍ ସହିତ `en.json`ର ପାର୍ଥକ୍ୟ ଯାଞ୍ଚ କରେ। + +#### `check-ui-value-drift` — ପୁରୁଣା-ଅନୁବାଦ ଗେଟ୍ + +ଏହା ସେହି ଏକମାତ୍ର i18n ରିଗ୍ରେସନ୍କୁ ଧରେ ଯାହାକୁ ଅନ୍ୟ ଗେଟ୍ଗୁଡ଼ିକ ଗଠନାତ୍ମକ ଭାବେ ଦେଖିପାରନ୍ତି ନାହିଁ: ଏକ ଇଂରାଜୀ ମୂଲ୍ୟ +ପୁନଃଲିଖିତ ହୁଏ ଏବଂ _ପୂର୍ବବର୍ତ୍ତୀ_ ଇଂରାଜୀରୁ ତିଆରି ହୋଇଥିବା ଅନୁବାଦଗୁଡ଼ିକ ପଛରେ ରହିଯାଏ, ଫଳରେ +ଅଣ-ଇଂରାଜୀ ବ୍ୟବହାରକାରୀମାନେ ଆତ୍ମବିଶ୍ୱାସପୂର୍ଣ୍ଣ ଶୈଳୀରେ ଲିଖିତ, କିନ୍ତୁ ବର୍ତ୍ତମାନ ଭୁଲ୍ ଥିବା ପାଠ୍ୟ ପଢ଼ିଚାଲନ୍ତି। + +ଏହା ପ୍ରକୃତରେ ରିଲିଜ୍ ହୋଇଥିଲା। Antigravity +ଲଗଇନ୍ ହେଲ୍ପର୍ ଆସିବାବେଳେ (#5203) `oauthModal.googleOAuthWarning` ପୁନଃଲିଖିତ ହୋଇଥିଲା; **43ଟି ଲୋକେଲ୍ରୁ 39ଟିରେ** ଅପରେଟର୍ମାନଙ୍କୁ "ସମ୍ପୂର୍ଣ୍ଣ URL କପି କରି ତଳେ ପେଷ୍ଟ କରନ୍ତୁ" ବୋଲି କହୁଥିବା ପାଠ୍ୟ ରହିଯାଇଥିଲା — ଏକ ଏପରି ପ୍ରବାହ ଯାହା ସେହି ପ୍ରଦାତା ପାଇଁ ସମ୍ପୂର୍ଣ୍ଣ ହୋଇପାରେ ନାହିଁ। ଏହି କାରଣଗୁଡ଼ିକ ଯୋଗୁଁ #8463 ପର୍ଯ୍ୟନ୍ତ ଏହା ଧରାପଡ଼ିନଥିଲା: + +- `sync-ui-keys` କେବଳ **ଅନୁପସ୍ଥିତ** କୀଗୁଡ଼ିକୁ ବ୍ୟାକ୍ଫିଲ୍ କରେ, **ପୁରୁଣା** କୀଗୁଡ଼ିକୁ କେବେ ନୁହେଁ; +- `check-ui-keys-coverage` କୀର _ଉପସ୍ଥିତି_ ଗଣନା କରେ, ତେଣୁ ଏକ ପୁରୁଣା ଅନୁବାଦ କଭର୍ ହୋଇଥିବା ଭାବେ ସ୍କୋର୍ କରେ; +- `check-translation-drift` `docs/i18n//**.md` ଡକ୍ୟୁମେଣ୍ଟେସନ୍ ମିରର୍ଗୁଡ଼ିକୁ ଟ୍ରାକ୍ କରେ — + ଏହା କେବେବି `src/i18n/messages/*.json` ପଢ଼େ ନାହିଁ। 2026-09 ପୁନଃ-ସିଙ୍କ୍ ପରଠାରୁ ଜବ୍ `docs-sync-strict`ରେ ଅବରୋଧକ: ଏକ ମୁଖ୍ୟ ଡକ୍ ସମ୍ପାଦନା କରନ୍ତୁ → `npm run i18n:run -- --files=` (ବିଭାଗ-ସ୍ତରୀୟ, କମ୍ ଖର୍ଚ୍ଚସାଧ୍ୟ)। + +**ଡିଫ୍-ସଚେତନ, ବେସ୍ଲାଇନ୍-ସମର୍ଥିତ ନୁହେଁ।** ଏହା ମର୍ଜ୍ ବେସ୍ରେ ଥିବା `en.json`କୁ +ୱର୍କିଂ ଟ୍ରି ସହିତ ତୁଳନା କରେ; ଯେଉଁ ପ୍ରତ୍ୟେକ କୀର ଇଂରାଜୀ ମୂଲ୍ୟ ପରିବର୍ତ୍ତିତ ହୋଇଛି, ସେଥିପାଇଁ ଏବେ ମଧ୍ୟ +ଅପରିବର୍ତ୍ତିତ ଅନୁବାଦ ଧାରଣ କରିଥିବା ଯେକୌଣସି ଲୋକେଲ୍ ପୁରୁଣା ହୋଇଯାଇଛି। ଏହା ଉଦ୍ଦେଶ୍ୟମୂଳକ ଭାବେ **ପୂର୍ବରୁ ଥିବା ଋଣକୁ ସ୍ଥିର ରଖେ** — ଦୀର୍ଘ ସମୟ ଧରି ଥିବା ଏକ ଅନୁବାଦ କେଉଁ ପୁରୁଣା ଇଂରାଜୀରୁ ଆସିଥିଲା, ତାହା ଏକ ଡିଫ୍ +ପ୍ରକାଶ କରିପାରିବ ନାହିଁ, ତେଣୁ ଗେଟ୍ କେବଳ ବର୍ତ୍ତମାନର ପରିବର୍ତ୍ତନ ଯାହାକୁ ସ୍ପର୍ଶ କରେ ତାହାକୁ ବିଚାର କରେ। +ବିକଳ୍ପଟି (ପ୍ରତି-କୀ ହ୍ୟାଶ୍ ବେସ୍ଲାଇନ୍) ପାଇଁ ପ୍ରାୟ 600 KBର ଏକ ଜେନେରେଟ୍ ହୋଇଥିବା ଫାଇଲ୍ ଆବଶ୍ୟକ ହେବ, +ଯାହା ସବୁଠାରୁ ବଡ଼ ବର୍ତ୍ତମାନର ବେସ୍ଲାଇନ୍ର 3×, ଏବଂ ପ୍ରତ୍ୟେକ i18n PRରେ ପରିବର୍ତ୍ତିତ ହେବ। ଏହାକୁ ସନ୍ତୁଷ୍ଟ କରିବାର ଦୁଇଟି ଉପାୟ: -1. ପ୍ରଭାବିତ translationଗୁଡ଼ିକୁ update କରନ୍ତୁ, କିମ୍ବା -2. ସେଗୁଡ଼ିକୁ `__MISSING__:` ଭାବେ set କରନ୍ତୁ — ତା’ପରେ runtime ସଂଶୋଧିତ ଇଂରାଜୀ ପରିବେଷଣ କରେ - (`src/i18n/request.ts::deepMergeFallback`, #7258) ଏବଂ keyଟି translation ପାଇଁ queueରେ ଯୋଡ଼ାଯାଏ। +1. ପ୍ରଭାବିତ ଅନୁବାଦଗୁଡ଼ିକୁ ଅପଡେଟ୍ କରନ୍ତୁ, କିମ୍ବା +2. ସେଗୁଡ଼ିକୁ `__MISSING__:` ଭାବେ ସେଟ୍ କରନ୍ତୁ — ତାହାପରେ ରନ୍ଟାଇମ୍ ସଂଶୋଧିତ ଇଂରାଜୀ ପ୍ରଦାନ କରେ + (`src/i18n/request.ts::deepMergeFallback`, #7258) ଏବଂ କୀଟି ଅନୁବାଦ ପାଇଁ ଧାଡ଼ିରେ ଯୋଡ଼ାଯାଏ। -ଯଦି stringଟିର **ଅର୍ଥ** ପରିବର୍ତ୍ତିତ ହୋଇଛି, ତେବେ **keyଟିକୁ rename କରିବାକୁ** ପ୍ରାଧାନ୍ୟ ଦିଅନ୍ତୁ: ଏକ ନୂଆ key ପୁରୁଣା translation ଉତ୍ତରାଧିକାରରେ ପାଇପାରିବ ନାହିଁ। #8463ରେ ଏହି pattern ବ୍ୟବହୃତ ହୋଇଥିଲା। +ଯଦି ଷ୍ଟ୍ରିଙ୍ଗ୍ର **ଅର୍ଥ** ପରିବର୍ତ୍ତିତ ହୋଇଛି, ତେବେ **କୀର ନାମ ପରିବର୍ତ୍ତନ କରିବାକୁ** ପ୍ରାଧାନ୍ୟ ଦିଅନ୍ତୁ: ଏକ ନୂଆ କୀ +ପୁରୁଣା ଅନୁବାଦ ଉତ୍ତରାଧିକାରରେ ପାଇପାରିବ ନାହିଁ। #8463ରେ ଏହି ପ୍ୟାଟର୍ନ ବ୍ୟବହାର କରାଯାଇଥିଲା। ```bash npm run i18n:check-value-drift # କଠୋର (CI ଯାହା ଚଲାଏ) -npm run i18n:check-value-drift:warn # କେବଳ report +npm run i18n:check-value-drift:warn # କେବଳ ରିପୋର୍ଟ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -base catalog ପଢ଼ାଯାଇ ନପାରିଲେ (base ref ବିନା shallow clone), `check-openapi-breaking`କୁ ଅନୁସରଣ କରି `SKIP reason=base-unresolved` ସହିତ 0ରେ exit କରେ। +ବେସ୍ କ୍ୟାଟାଲଗ୍ ପଢ଼ାଯାଇ ପାରିନଥିଲେ (ବେସ୍ ref ବିନା ଶ୍ୟାଲୋ +କ୍ଲୋନ୍), `check-openapi-breaking`କୁ ଅନୁସରଣ କରି, `SKIP reason=base-unresolved` ସହିତ 0ରେ ପ୍ରସ୍ଥାନ କରେ। -### Job: `i18n` +### ଜବ୍: `i18n` -ସମ୍ପୂର୍ଣ୍ଣ i18n validation matrix (ପ୍ରତ୍ୟେକ locale ପାଇଁ ଗୋଟିଏ job)। ସମଗ୍ର jobଟି ପରାମର୍ଶମୂଳକ। +ସମ୍ପୂର୍ଣ୍ଣ i18n ବୈଧତା ଯାଞ୍ଚ ମ୍ୟାଟ୍ରିକ୍ସ (ପ୍ରତ୍ୟେକ ଲୋକେଲ୍ ପାଇଁ ଗୋଟିଏ ଜବ୍)। ସମଗ୍ର ଜବ୍ଟି ପରାମର୍ଶମୂଳକ। -| Script | କ’ଣ validate କରେ | ବାଧ୍ୟକାରୀ | +| ସ୍କ୍ରିପ୍ଟ | ଯାହାକୁ ବୈଧ କରେ | ବ୍ଲକିଂ | | ------------------------------- | ----------------------------------------- | ------------------------------------------------------- | -| `validate_translation.py quick` | ପ୍ରତ୍ୟେକ localeର translation ସମ୍ପୂର୍ଣ୍ଣତା | **ପରାମର୍ଶମୂଳକ** (ସମଗ୍ର jobରେ `continue-on-error: true`) | +| `validate_translation.py quick` | ପ୍ରତ୍ୟେକ ଲୋକେଲ୍ ପାଇଁ ଅନୁବାଦର ସମ୍ପୂର୍ଣ୍ଣତା | **ପରାମର୍ଶମୂଳକ** (ସମଗ୍ର ଜବ୍ରେ `continue-on-error: true`) | -### Job: `pr-test-policy` +### ଜବ୍: `pr-test-policy` -କେବଳ pull requestଗୁଡ଼ିକରେ ଚାଲେ। +କେବଳ ପୁଲ୍ ରିକ୍ୱେଷ୍ଟଗୁଡ଼ିକରେ ଚାଲେ। -| Script | କ’ଣ validate କରେ | ବାଧ୍ୟକାରୀ | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, କିମ୍ବା `bin/`ର production code ପରିବର୍ତ୍ତନ କରୁଥିବା PRଗୁଡ଼ିକ test ଅନ୍ତର୍ଭୁକ୍ତ କିମ୍ବା update କରିବା ଆବଶ୍ୟକ (କଠୋର ନିୟମ #8) | ହଁ | -| `check:test-masking` | ପରିବର୍ତ୍ତିତ test fileଗୁଡ଼ିକ ମୋଟ assert ସଂଖ୍ୟା କମାନ୍ତି ନାହିଁ କିମ୍ବା `assert.ok(true)` tautology ଯୋଡ଼ନ୍ତି ନାହିଁ | ହଁ | -| `check:pr-evidence` | PR bodyରେ ପରିବର୍ତ୍ତନ ପାଇଁ test/VPS ପ୍ରମାଣ ଉଲ୍ଲେଖ ହୋଇଛି (PR ଗଦ୍ୟକୁ grep କରି କଠୋର ନିୟମ #18କୁ ସ୍ୱୟଂଚାଳିତ କରେ — ଭଙ୍ଗୁର, Backlog ଦେଖନ୍ତୁ) | ହଁ | +| ସ୍କ୍ରିପ୍ଟ | ଯାହାକୁ ବୈଧ କରେ | ବ୍ଲକିଂ | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, କିମ୍ବା `bin/`ରେ ପ୍ରଡକ୍ସନ୍ କୋଡ୍ ପରିବର୍ତ୍ତନ କରୁଥିବା PRଗୁଡ଼ିକ ନିଶ୍ଚିତ ଭାବେ ଟେଷ୍ଟ ସାମିଲ କିମ୍ବା ଅପଡେଟ୍ କରିବା ଆବଶ୍ୟକ (କଠୋର ନିୟମ #8) | ହଁ | +| `check:test-masking` | ପରିବର୍ତ୍ତିତ ଟେଷ୍ଟ ଫାଇଲ୍ଗୁଡ଼ିକ ସମୁଦାୟ ଆସର୍ଟ ଗଣନା କମାନ୍ତି ନାହିଁ କିମ୍ବା `assert.ok(true)` ଟଟୋଲୋଜି ଯୋଡ଼ନ୍ତି ନାହିଁ | ହଁ | +| `check:pr-evidence` | PR ବଡିରେ ପରିବର୍ତ୍ତନ ପାଇଁ ଟେଷ୍ଟ/VPS ପ୍ରମାଣ ଉଲ୍ଲେଖ ଅଛି (PR ଗଦ୍ୟକୁ grep କରି କଠୋର ନିୟମ #18କୁ ସ୍ୱୟଂଚାଳିତ କରେ — ଭଙ୍ଗୁର, Backlog ଦେଖନ୍ତୁ) | ହଁ | -### Job: `test-vitest` +### ଜବ୍: `test-vitest` -`build` ପରେ ଚାଲେ। ବିଫଳ ହେଲେ mergeକୁ ଅବରୋଧ କରେ। +`build` ପରେ ଚାଲେ। ବିଫଳ ହେଲେ ମର୍ଜ୍କୁ ଅବରୋଧ କରେ। -| Suite | କ’ଣ validate କରେ | ବାଧ୍ୟକାରୀ | -| ---------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110ଟି tool), autoCombo, cache — vitest runner | ହଁ | -| `test:vitest:ui` | UI component test — vitest runner | **ବାଧ୍ୟକାରୀ** — ପୂର୍ବରୁ ଥିବା failureଗୁଡ଼ିକୁ `vitest.config.ts`ରେ ସ୍ପଷ୍ଟ ଭାବେ ବାଦ ଦିଆଯାଇଛି; ନୂଆ failure jobଟିକୁ ବିଫଳ କରେ | +| ସୁଇଟ୍ | ଯାହାକୁ ବୈଧ କରେ | ବ୍ଲକିଂ | +| ---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP ସର୍ଭର୍ (110ଟି ଟୁଲ୍), autoCombo, କ୍ୟାଶ୍ — vitest ରନର୍ | ହଁ | +| `test:vitest:ui` | UI କମ୍ପୋନେଣ୍ଟ ଟେଷ୍ଟ — vitest ରନର୍ | **ବ୍ଲକିଂ** — ପୂର୍ବରୁ ଥିବା ବିଫଳତାଗୁଡ଼ିକୁ `vitest.config.ts`ରେ ସ୍ପଷ୍ଟ ଭାବେ ବାଦ ଦିଆଯାଇଛି; ନୂଆ ବିଫଳତା ଜବ୍କୁ ବିଫଳ କରେ | -### ରାତ୍ରିକାଳୀନ workflow (ନିର୍ଦ୍ଧାରିତ, ପରାମର୍ଶମୂଳକ) +### ରାତ୍ରିକାଳୀନ ୱର୍କଫ୍ଲୋଗୁଡ଼ିକ (ନିର୍ଦ୍ଧାରିତ, ପରାମର୍ଶମୂଳକ) -ଏଗୁଡ଼ିକ cron scheduleରେ (ଏବଂ `workflow_dispatch`ରେ) ଚାଲେ, PRରେ କେବେ ମଧ୍ୟ ନୁହେଁ। ସମସ୍ତେ ପରାମର୍ଶମୂଳକ। +ଏଗୁଡ଼ିକ ଏକ cron ସୂଚୀରେ (ଏବଂ `workflow_dispatch`ରେ) ଚାଲେ, PRଗୁଡ଼ିକରେ କେବେ ମଧ୍ୟ ନୁହେଁ। ସମସ୍ତେ ପରାମର୍ଶମୂଳକ। -| Workflow | କ’ଣ validate କରେ | ବାଧ୍ୟକାରୀ | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | random seed + ଉଚ୍ଚ run count ସହିତ fast-check property test | **ପରାମର୍ଶମୂଳକ** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **ପରାମର୍ଶମୂଳକ** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probe (provider secret ବିନା skip କରାଯାଏ) | **ପରାମର୍ଶମୂଳକ** | -| `nightly-schemathesis` | `docs/openapi.yaml` ବ୍ୟବହାର କରି ଏକ live OmniRoute ବିରୁଦ୍ଧରେ OpenAPI contract fuzzing (schemathesis) — spec ଉଲ୍ଲଂଘନ / unhandled 500କୁ ପ୍ରକାଶ କରେ (ପର୍ଯ୍ୟାୟ 8 B.4) | **ପରାମର୍ଶମୂଳକ** | -| `nightly-mutation` | fast unit lane ଉପରେ Stryker mutation-testing score — ବଞ୍ଚିରହିଥିବା mutantଗୁଡ଼ିକ ଦୁର୍ବଳ assertକୁ ପ୍ରକାଶ କରନ୍ତି | **ପରାମର୍ଶମୂଳକ** | -| `nightly-compat` | ସମର୍ଥିତ `engines.node` rangeଗୁଡ଼ିକରେ Node engine compatibility matrix | **ପରାମର୍ଶମୂଳକ** | +| ୱର୍କଫ୍ଲୋ | ଯାହାକୁ ବୈଧ କରେ | ବ୍ଲକିଂ | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `nightly-property` | ଏକ ଯାଦୃଚ୍ଛିକ ସିଡ୍ + ଅଧିକ ରନ୍ ଗଣନା ସହିତ fast-check ପ୍ରୋପର୍ଟି ଟେଷ୍ଟ | **ପରାମର୍ଶମୂଳକ** | +| `nightly-resilience` | ହିପ୍-ବୃଦ୍ଧି ଗେଟ୍, କ୍ୟାଓସ୍ ଫଲ୍ଟ-ଇଞ୍ଜେକ୍ସନ୍, k6 ଲୋଡ୍/ସୋକ୍ | **ପରାମର୍ଶମୂଳକ** | +| `nightly-llm-security` | promptfoo ଇଞ୍ଜେକ୍ସନ୍ ଗାର୍ଡ୍ (ବ୍ଲକ୍ ମୋଡ୍) + garak ପ୍ରୋବ୍ଗୁଡ଼ିକ (ପ୍ରଦାତା ସିକ୍ରେଟ୍ ବିନା ଏଡ଼ାଇ ଦିଆଯାଏ) | **ପରାମର୍ଶମୂଳକ** | +| `nightly-schemathesis` | `docs/openapi.yaml` ବ୍ୟବହାର କରି ଏକ ସକ୍ରିୟ OmniRoute ବିରୁଦ୍ଧରେ OpenAPI କଣ୍ଟ୍ରାକ୍ଟ ଫଜିଂ (schemathesis) — ସ୍ପେକ୍ ଉଲ୍ଲଂଘନ / ଅଣନିୟନ୍ତ୍ରିତ 500ଗୁଡ଼ିକୁ ପ୍ରକାଶ କରେ (Fase 8 B.4) | **ପରାମର୍ଶମୂଳକ** | +| `nightly-mutation` | ଦ୍ରୁତ ୟୁନିଟ୍ ଲେନ୍ରେ Stryker ମ୍ୟୁଟେସନ୍-ଟେଷ୍ଟିଂ ସ୍କୋର୍ — ବଞ୍ଚି ରହିଥିବା ମ୍ୟୁଟାଣ୍ଟଗୁଡ଼ିକ ଦୁର୍ବଳ ଆସର୍ଟଗୁଡ଼ିକୁ ପ୍ରକାଶ କରେ | **ପରାମର୍ଶମୂଳକ** | +| `nightly-compat` | ସମର୍ଥିତ `engines.node` ରେଞ୍ଜଗୁଡ଼ିକରେ Node ଇଞ୍ଜିନ୍ ସୁସଙ୍ଗତତା ମ୍ୟାଟ୍ରିକ୍ସ | **ପରାମର୍ଶମୂଳକ** | --- @@ -523,41 +529,41 @@ CI ଗେଟ୍ଗୁଡ଼ିକ ବ୍ୟତୀତ, OmniRoute ଏକ **ଇଚ ## ଯୁକ୍ତିସଙ୍ଗତକରଣ ବ୍ୟାକଲଗ୍ (ROI ସମୀକ୍ଷା — ପର୍ଯ୍ୟାୟ 9 ତରଙ୍ଗ 3) -ଏହି ଇନଭେଣ୍ଟରିକୁ 2026-06-17 ରେ `ci.yml` ସହିତ ମେଳ କରାଯାଇଥିଲା (ପୂର୍ବ ସଂସ୍କରଣରେ +ଏହି ଇନ୍ଭେଣ୍ଟରୀକୁ 2026-06-17 ରେ `ci.yml` ସହିତ ମେଳ କରାଯାଇଥିଲା (ପୂର୍ବ ସଂସ୍କରଣରେ `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence` ବାଦ ପଡ଼ିଥିଲା)। ମେଳ କରାଯାଇଥିବା ସେଟ୍ର ଏକ ROI ସମୀକ୍ଷା ନିମ୍ନଲିଖିତ ଯୁକ୍ତିସଙ୍ଗତକରଣ ପ୍ରାର୍ଥୀଗୁଡ଼ିକୁ ଚିହ୍ନଟ କରିଛି। **ମର୍ଜଗୁଡ଼ିକ ଯାନ୍ତ୍ରିକ CI -ପରିବର୍ତ୍ତନ; ଫ୍ଲିପ୍/ଡ୍ରପ୍ଗୁଡ଼ିକ ଅପରେଟରଙ୍କ ପାଇଁ ସଂରକ୍ଷିତ ନୀତିଗତ ନିଷ୍ପତ୍ତି।** ନିମ୍ନରେ ଥିବା କିଛି ମଧ୍ୟ +ପରିବର୍ତ୍ତନ; ଫ୍ଲିପ୍/ବାଦ ଦେବା ହେଉଛି ଅପରେଟରଙ୍କ ପାଇଁ ସଂରକ୍ଷିତ ନୀତିଗତ ନିଷ୍ପତ୍ତି।** ନିମ୍ନରେ ଥିବା କୌଣସି ବିଷୟ ଏପର୍ଯ୍ୟନ୍ତ ପ୍ରୟୋଗ କରାଯାଇନାହିଁ। -**ଉପରେ ଏହା ମଧ୍ୟ ଦସ୍ତାବିଜଭୁକ୍ତ ହୋଇନାହିଁ** (ପରାମର୍ଶମୂଳକ, ଦୁର୍ବଳ ସଙ୍କେତ): `docs-lint` ଜବ୍ +**ଉପରେ ଆହୁରି ଅଲିଖିତ ଥିବା ବିଷୟ** (ପରାମର୍ଶମୂଳକ, ନିମ୍ନ ସଙ୍କେତ): `docs-lint` ଜବ୍ (markdownlint + Vale, ସମଗ୍ର ଜବ୍ରେ `continue-on-error`) ଏବଂ ସ୍ୱତନ୍ତ୍ର ସ୍କାନର୍ ୱର୍କଫ୍ଲୋଗୁଡ଼ିକ `semgrep.yml` / `codeql.yml` / `scorecard.yml`। `semgrepFindings: 0` -`quality-baseline.json` ଭିତରେ ଅଛି, କିନ୍ତୁ `ci.yml` ରେ ଏହାକୁ କୌଣସି ବ୍ଲକିଂ ରାଚେଟ୍ ସହ ଯୋଡ଼ାଯାଇନାହିଁ — ମେଟ୍ରିକ୍ଟି +`quality-baseline.json` ରେ ଅଛି, କିନ୍ତୁ `ci.yml` ରେ ଏହାକୁ କୌଣସି ବ୍ଲକିଂ ରାଚେଟ୍ ସହିତ ସଂଯୋଗ କରାଯାଇନାହିଁ — ମେଟ୍ରିକ୍ଟି ବର୍ତ୍ତମାନ ଅନାଥ ଅବସ୍ଥାରେ ଅଛି। -### ମର୍ଜ୍ / ଡିଡୁପ୍ଲିକେସନ୍ (ଯାନ୍ତ୍ରିକ, କମ୍ ବିପଦ) +### ମର୍ଜ / ଡିଡ୍ୟୁପ୍ (ଯାନ୍ତ୍ରିକ, କମ୍ ବିପଦ) -ପ୍ରତ୍ୟେକ ପ୍ରାର୍ଥୀକୁ 2026-06-17 ର ଲାଇଭ୍ ଗେଟ୍ ସ୍ଥିତି ସହିତ ଯାଞ୍ଚ କରାଯାଇଥିଲା (ବିଶ୍ୱାସ କରନ୍ତୁ, କିନ୍ତୁ ଯାଞ୍ଚ ମଧ୍ୟ କରନ୍ତୁ); -ଅନେକ "ସ୍ପଷ୍ଟ" ମର୍ଜ୍ ପ୍ରକୃତରେ ଲୁକ୍କାୟିତ ଋଣ ଲୁଚାଇଥିବା ଜଣାପଡ଼ିଲା ଏବଂ ସେଗୁଡ଼ିକ **ସିଧାସଳଖ ପ୍ରତିସ୍ଥାପନଯୋଗ୍ୟ ନୁହେଁ**। +ପ୍ରତ୍ୟେକ ପ୍ରାର୍ଥୀକୁ 2026-06-17 ରେ ସକ୍ରିୟ ଗେଟ୍ ସ୍ଥିତି ସହିତ ଯାଞ୍ଚ କରାଯାଇଥିଲା (ବିଶ୍ୱାସ କରନ୍ତୁ, କିନ୍ତୁ ଯାଞ୍ଚ ମଧ୍ୟ କରନ୍ତୁ); +ଅନେକ "ସ୍ପଷ୍ଟ" ମର୍ଜ ଋଣ ଲୁଚାଇ ରଖିଥିବା ଜଣାପଡ଼ିଲା ଏବଂ ସେଗୁଡ଼ିକ **ସିଧାସଳଖ ପ୍ରତିସ୍ଥାପନଯୋଗ୍ୟ ନୁହେଁ**। -- **`check:docs-sync` ଦୁଇଥର ଚାଲେ** — `lint` ଜବ୍ରେ ସ୍ୱତନ୍ତ୍ର ଭାବରେ ଏବଂ ପୁନର୍ବାର `check:docs-all` (`docs-sync-strict`) ଓ husky pre-commit hook ଭିତରେ। ✅ **ସମ୍ପୂର୍ଣ୍ଣ** — ସ୍ୱତନ୍ତ୍ର `lint` ଆହ୍ୱାନ ଅପସାରିତ। -- **CVE ସ୍କାନିଂ** — ❌ **ଏହା ଏକ ସହଜ ମର୍ଜ୍ ନୁହେଁ।** `audit:deps` ଯେକୌଣସି high/critical CVE ରେ ହାର୍ଡ-ଫେଲ୍ କରେ; `check:vuln-ratchet` (osv) କେବଳ ବେସଲାଇନ୍ ତୁଳନାରେ ଏକ _ରିଗ୍ରେସନ୍_ ହେଲେ ବିଫଳ ହୁଏ (ବର୍ତ୍ତମାନ 1 MODERATE)। ଅର୍ଥବିଧି ଭିନ୍ନ — `audit:deps` ବାଦ ଦେଲେ ପରମ high/critical ଗେଟ୍ ହରାଇଯିବ। ଉଭୟକୁ ରଖନ୍ତୁ। -- **ସାଇକଲ୍ ଚିହ୍ନଟ** — ❌ **ଏହା ଏକ ସହଜ ମର୍ଜ୍ ନୁହେଁ।** `check:circular-deps` (dpdm) **91ଟି ସାଇକଲ୍** ରିପୋର୍ଟ କରେ (ସେଥିପାଇଁ ଏହା ପରାମର୍ଶମୂଳକ); ପ୍ରଥମେ ସେଗୁଡ଼ିକର ସମାଧାନ ନକରି ଏହାକୁ ବ୍ଲକିଂକୁ ଉନ୍ନୀତ କରାଯାଇପାରିବ ନାହିଁ, ଏବଂ ସବୁଜ, ସୁପରିଚାଳିତ `check:cycles` ତୁଳନାରେ ଏହାର ପରିସର ଅଧିକ ବ୍ୟାପକ। `check:cycles` କୁ ବ୍ଲକିଂ ଭାବେ ରଖନ୍ତୁ; 91ଟି dpdm ସାଇକଲ୍ର ସମାଧାନ ଏକ ପୃଥକ ବ୍ୟାକଲଗ୍। -- **ଜଟିଳତା** — ✅ **ସମ୍ପୂର୍ଣ୍ଣ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ଗୋଟିଏ ESLint ୱାକ୍, ruleId ଅନୁସାରେ ଗଣନା କରେ ଯାହାଦ୍ୱାରା cyclomatic+max-lines ଏବଂ cognitive ବେସଲାଇନ୍ଗୁଡ଼ିକ ସ୍ୱାଧୀନ ରହେ; ବ୍ୟକ୍ତିଗତ `check:complexity` / `check:cognitive-complexity` ସ୍ଥାନୀୟ `--update` ପାଇଁ ରହିଛି। -- **`/api` ଆଣ୍ଟି-ହାଲୁସିନେସନ୍** — ✅ **ସମ୍ପୂର୍ଣ୍ଣ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ର ଗୋଟିଏ FS ଇନଭେଣ୍ଟରି, openapi-routes + docs-symbols ଏବେ ମଧ୍ୟ ସ୍ୱାଧୀନ ଭାବେ ରିପୋର୍ଟ କରେ; ବ୍ୟକ୍ତିଗତଗୁଡ଼ିକ ସ୍ଥାନୀୟ ରନ୍ ପାଇଁ ରହିଛି। -- **`check:node-runtime` 11ଟି ଜବ୍ରେ ଚାଲେ** — ⚠️ **କମ୍ ROI।** ପ୍ରତ୍ୟେକଟି ଏକ ପୃଥକ ରନର୍ ଏବଂ ଯାଞ୍ଚଟି <1s; ମୋଟ ସଞ୍ଚୟ ~10s, କିନ୍ତୁ ଏହା ବଦଳରେ ଏକ ସସ୍ତା ପ୍ରତି-ଜବ୍ ସୁରକ୍ଷା ହରାଇବ। ପରିବର୍ତ୍ତନଜନିତ ଅସ୍ଥିରତା ପାଇଁ ଏହା ଯୋଗ୍ୟ ନୁହେଁ। -- **CI lint ରେ `typecheck:noimplicit:core`** — ✅ **lint ଜବ୍ରୁ ଅପସାରିତ** (ଏହା ପରାମର୍ଶମୂଳକ `continue-on-error` ଥିଲା); ବ୍ଲକିଂ ଟାଇପ୍ ସର୍ଫେସ୍ ହେଉଛି `typecheck:core` + `check:type-coverage`। ସ୍ଥାନୀୟ ସ୍କ୍ରିପ୍ଟ ରଖାଯାଇଛି। +- **`check:docs-sync` ଦୁଇଥର ଚାଲେ** — `lint` ଜବ୍ରେ ସ୍ୱତନ୍ତ୍ର ଭାବେ ଏବଂ ପୁଣି `check:docs-all` (`docs-sync-strict`) ଭିତରେ ଓ husky pre-commit hook ରେ। ✅ **ସମ୍ପୂର୍ଣ୍ଣ** — ସ୍ୱତନ୍ତ୍ର `lint` ଆହ୍ୱାନ ହଟାଯାଇଛି। +- **CVE ସ୍କାନିଂ** — ❌ **ଏକ ସ୍ୱଚ୍ଛ ମର୍ଜ ନୁହେଁ।** ଯେକୌଣସି high/critical CVE ଥିଲେ `audit:deps` ନିଶ୍ଚିତ ଭାବେ ବିଫଳ ହୁଏ; `check:vuln-ratchet` (osv) କେବଳ ବେସ୍ଲାଇନ୍ ତୁଳନାରେ ଏକ _ଅବନତି_ ହେଲେ ବିଫଳ ହୁଏ (ବର୍ତ୍ତମାନ 1 MODERATE)। ଅର୍ଥବିଜ୍ଞାନ ଭିନ୍ନ — `audit:deps` ବାଦ ଦେଲେ ସମ୍ପୂର୍ଣ୍ଣ high/critical ଗେଟ୍ ହରାଇଯିବ। ଉଭୟକୁ ରଖନ୍ତୁ। +- **ଚକ୍ର ଚିହ୍ନଟ** — ✅ **ସମ୍ପୂର୍ଣ୍ଣ** (#15159 G-01/G-02)। ଏଠାରେ ଥିବା ପୁରୁଣା ପାଠ୍ୟ `check:cycles` କୁ "ସବୁଜ, ସୁବ୍ୟବସ୍ଥିତ" ଗେଟ୍ ବୋଲି କହିଥିଲା ଏବଂ `check:circular-deps` (dpdm) 91ଟି ଚକ୍ର ରିପୋର୍ଟ କରିଥିବାରୁ ଏହାକୁ ବ୍ଲକିଂ ଭାବେ ରଖିବା ଯୁକ୍ତିସଙ୍ଗତ ବୋଲି ଦର୍ଶାଇଥିଲା। ସେହି ସବୁଜ ସ୍ଥିତି ଏକ **ଭୁଲ ସବୁଜ** ଥିଲା: `check:cycles` 5ଟି ସବ୍ଡିରେକ୍ଟୋରୀ (450ଟି ଫାଇଲ୍) ସ୍କାନ୍ କରୁଥିଲା, କେବଳ ସ୍ଥିର `import|export … from` ସହିତ ମେଳ କରୁଥିଲା, ଏବଂ ପ୍ରତ୍ୟେକ `@/` ଓ `@omniroute/open-sse/` ସ୍ପେସିଫାୟର୍କୁ ବାଦ ଦେଉଥିଲା, ତେଣୁ ରିପୋଜିଟୋରୀରେ ପ୍ରାଧାନ୍ୟ ରଖିଥିବା ଡାଇନାମିକ୍-import + alias ଚକ୍ରଗୁଡ଼ିକୁ ଏହା ଦେଖିପାରୁ ନଥିଲା। ସମାଧାନ କରାଯାଇଛି: ଗେଟ୍ଟି ବର୍ତ୍ତମାନ `src` + `open-sse` (5023ଟି ଫାଇଲ୍) ପରିକ୍ରମା କରେ, TypeScript AST ରୁ ସ୍ପେସିଫାୟର୍ଗୁଡ଼ିକ ସଂଗ୍ରହ କରେ (ଫଳରେ `import("…")` ଗଣାଯାଏ ଏବଂ type-position `typeof import("…")` ଗଣାଯାଏ ନାହିଁ), ଏବଂ tsconfig `paths` ସମାଧାନ କରେ। ଏହା 0 ନୁହେଁ, **14**ଟି ଚକ୍ର ଚିହ୍ନଟ କରେ। ଏକ ଗେଟ୍ PR ରେ ପୂର୍ବରୁ ଥିବା 14ଟି ଚକ୍ର ସମାଧାନ କରାଯାଇପାରିବ ନାହିଁ ବୋଲି, `check:cycles` ବର୍ତ୍ତମାନ ଏକ **ରାଚେଟ୍** (`--ratchet`, `quality-baseline.json` ରେ ସୀମା `metrics.cycles.value = 14`, `direction: down`) — ଏହା ଯେକୌଣସି _ଅବନତି_କୁ ଅବରୋଧ କରେ ଏବଂ ଗଣନା କେବଳ କମିପାରେ। CI `npm run check:cycles:ratchet` ଚଲାଏ। କ୍ରମଶଃ ହ୍ରାସ **A-01** ସହିତ ଚାଲିବ। ବ୍ୟାପକ ଦ୍ୱିତୀୟ ମତାମତ ଭାବେ `check:circular-deps` (dpdm) ପରାମର୍ଶମୂଳକ ରହିବ। +- **ଜଟିଳତା** — ✅ **ସମ୍ପୂର୍ଣ୍ଣ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ଗୋଟିଏ ESLint ପରିକ୍ରମା, ruleId ଅନୁଯାୟୀ ଗଣନା କରେ ଯାହାଦ୍ୱାରା cyclomatic+max-lines ଏବଂ cognitive ବେସ୍ଲାଇନ୍ଗୁଡ଼ିକ ସ୍ୱାଧୀନ ରହେ; ସ୍ଥାନୀୟ `--update` ପାଇଁ ବ୍ୟକ୍ତିଗତ `check:complexity` / `check:cognitive-complexity` ରହିବ। +- **`/api` ଭ୍ରମବିରୋଧୀ ବ୍ୟବସ୍ଥା** — ✅ **ସମ୍ପୂର୍ଣ୍ଣ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ର ଗୋଟିଏ FS ଇନ୍ଭେଣ୍ଟରୀ, openapi-routes + docs-symbols ଏବେ ମଧ୍ୟ ସ୍ୱାଧୀନ ଭାବେ ରିପୋର୍ଟ କରେ; ସ୍ଥାନୀୟ ଚାଳନା ପାଇଁ ବ୍ୟକ୍ତିଗତଗୁଡ଼ିକ ରହିବ। +- **`check:node-runtime` 11ଟି ଜବ୍ରେ ଚାଲେ** — ⚠️ **କମ୍ ROI।** ପ୍ରତ୍ୟେକଟି ଏକ ପୃଥକ ରନର୍ ଏବଂ ଯାଞ୍ଚଟି <1s; ମୋଟ ସଞ୍ଚୟ ~10s, କିନ୍ତୁ ଏହା ବଦଳରେ ପ୍ରତି-ଜବ୍ର ଏକ ଶସ୍ତା ସୁରକ୍ଷା ବ୍ୟବସ୍ଥା ହରାଇବାକୁ ପଡ଼ିବ। ଏହି ପରିବର୍ତ୍ତନଜନିତ ଅସ୍ଥିରତା ଯୋଗ୍ୟ ନୁହେଁ। +- **CI lint ରେ `typecheck:noimplicit:core`** — ✅ **lint ଜବ୍ରୁ ହଟାଯାଇଛି** (ପୂର୍ବରୁ ପରାମର୍ଶମୂଳକ `continue-on-error` ଥିଲା); ବ୍ଲକିଂ ଟାଇପ୍ ସର୍ଫେସ୍ ହେଉଛି `typecheck:core` + `check:type-coverage`। ସ୍ଥାନୀୟ ସ୍କ୍ରିପ୍ଟ ରଖାଯାଇଛି। -### ଫ୍ଲିପ୍ / ନିଷ୍ପତ୍ତି (ଅପରେଟର ନୀତି) +### ଫ୍ଲିପ୍ / ନିଷ୍ପତ୍ତି ନିଅନ୍ତୁ (ଅପରେଟର୍ ନୀତି) -- `check:openapi-security-tiers` (ପରାମର୍ଶମୂଳକ) — ❌ **ସହଜରେ ଫ୍ଲିପ୍ କରାଯାଇପାରିବ ନାହିଁ।** ଏହା 0 ସହିତ ପ୍ରସ୍ଥାନ କରେ, କିନ୍ତୁ ସତର୍କ କରେ ଯେ `LOCAL_ONLY_API_PREFIXES` ଅଧୀନରେ ଥିବା ଅନେକ `traffic-inspector` ରୁଟ୍ରେ `x-loopback-only: true` ଟିପ୍ପଣୀ ନାହିଁ। ଏହାକୁ ବାଧ୍ୟତାମୂଳକ କରିବା ପାଇଁ ପ୍ରଥମେ `openapi.yaml` ରେ ସେହି ଟିପ୍ପଣୀଗୁଡ଼ିକ ଯୋଡ଼ିବା ଆବଶ୍ୟକ। -- `typecheck:noimplicit:core` (ପରାମର୍ଶମୂଳକ) — ବ୍ଲକିଂ `check:type-coverage` ରାଚେଟ୍ ଦ୍ୱାରା ମୁଖ୍ୟତଃ ଅନ୍ତର୍ଭୁକ୍ତ। ଏହାକୁ ଏକ ରାଚେଟ୍କୁ ଫ୍ଲିପ୍ କରନ୍ତୁ କିମ୍ବା ଅନାବଶ୍ୟକ ଦ୍ୱିତୀୟ `tsc` ପାସ୍କୁ ବାଦ ଦିଅନ୍ତୁ। +- `check:openapi-security-tiers` (ପରାମର୍ଶମୂଳକ) — ❌ **ସ୍ୱଚ୍ଛ ଭାବେ ଫ୍ଲିପ୍ କରାଯାଇପାରିବ ନାହିଁ।** ଏହା 0 ସହିତ ପ୍ରସ୍ଥାନ କରେ, କିନ୍ତୁ ସତର୍କ କରେ ଯେ `LOCAL_ONLY_API_PREFIXES` ଅଧୀନରେ ଥିବା ଅନେକ `traffic-inspector` ରୁଟ୍ରେ `x-loopback-only: true` ଟୀକା ନାହିଁ। ଏହାକୁ ବାଧ୍ୟତାମୂଳକ କରିବା ପାଇଁ ପ୍ରଥମେ `openapi.yaml` ରେ ସେହି ଟୀକାଗୁଡ଼ିକ ଯୋଡ଼ିବା ଆବଶ୍ୟକ। +- `typecheck:noimplicit:core` (ପରାମର୍ଶମୂଳକ) — ବ୍ଲକିଂ `check:type-coverage` ରାଚେଟ୍ ଦ୍ୱାରା ମୁଖ୍ୟତଃ ଅନ୍ତର୍ଭୁକ୍ତ। ଏହାକୁ ରାଚେଟ୍ରେ ଫ୍ଲିପ୍ କରନ୍ତୁ କିମ୍ବା ଅନାବଶ୍ୟକ ଦ୍ୱିତୀୟ `tsc` ପାସ୍କୁ ବାଦ ଦିଅନ୍ତୁ। - `test:vitest:ui` (ବର୍ତ୍ତମାନ **ବ୍ଲକିଂ**) — ପୂର୍ବରୁ ଥିବା ବିଫଳତାଗୁଡ଼ିକୁ `vitest.config.ts` ରେ `// #8618` ଟ୍ରାକିଂ ମନ୍ତବ୍ୟ ସହିତ ସ୍ପଷ୍ଟ ଭାବେ ବାଦ ଦିଆଯାଇଛି; ନୂତନ ବିଫଳତା ଜବ୍କୁ ବିଫଳ କରେ। -- `check:secrets` (gitleaks, 3ଟି ଦସ୍ତାବିଜଭୁକ୍ତ ମିଥ୍ୟା-ପଜିଟିଭ୍ରେ ଫ୍ରିଜ୍ ହୋଇଥିବା ବ୍ଲକିଂ ରାଚେଟ୍) — 0 ରେ ପହଞ୍ଚିବା ପାଇଁ ସେହି 3ଟିକୁ allowlist କରନ୍ତୁ, କିମ୍ବା ପରାମର୍ଶମୂଳକ ସ୍ତରକୁ ଅବନମିତ କରନ୍ତୁ। GitHub ର ନେଟିଭ୍ secret-scanning + `check:public-creds` ସହିତ ଏହା ଆଂଶିକ ଭାବେ ଓଭରଲାପ୍ କରେ। -- `check:pr-evidence` (ବ୍ଲକିଂ, PR-body ଗଦ୍ୟରେ grep କରେ) — ମିଥ୍ୟା-ପଜିଟିଭ୍ର ଉଚ୍ଚ ବିପଦ; ଏହାକୁ ବାଦ ଦେଲେ Hard Rule #18 ର ପ୍ରବର୍ତ୍ତନ ଦୁର୍ବଳ ହୁଏ, ତେଣୁ ଏହା ଏକ ପ୍ରକୃତ ନୀତିଗତ ନିଷ୍ପତ୍ତି। -- `semgrep` (ପରାମର୍ଶମୂଳକ ସ୍ୱତନ୍ତ୍ର) — OWASP ପରିବାରଗୁଡ଼ିକ ପାଇଁ CodeQL ସହିତ ଓଭରଲାପ୍ କରେ; ଏହାର ବେସଲାଇନ୍କୁ ଏକ ରାଚେଟ୍ ସହ ଯୋଡ଼ନ୍ତୁ କିମ୍ବା ବାଦ ଦିଅନ୍ତୁ। +- `check:secrets` (gitleaks, 3ଟି ଡକ୍ୟୁମେଣ୍ଟେଡ୍ false-positive ରେ ସ୍ଥିରୀକୃତ ବ୍ଲକିଂ ରାଚେଟ୍) — 0 ରେ ପହଞ୍ଚିବା ପାଇଁ ସେହି 3ଟିକୁ allowlist କରନ୍ତୁ, କିମ୍ବା ପରାମର୍ଶମୂଳକ ସ୍ତରକୁ ଅବନମିତ କରନ୍ତୁ। GitHub ର ନେଟିଭ୍ secret-scanning + `check:public-creds` ସହିତ ଏହାର ଆଂଶିକ ପୁନରାବୃତ୍ତି ଅଛି। +- `check:pr-evidence` (ବ୍ଲକିଂ, PR-body ଗଦ୍ୟକୁ grep କରେ) — false-positive ହେବାର ଉଚ୍ଚ ବିପଦ; ବାଦ ଦିଆଗଲେ Hard Rule #18 ର ପ୍ରବର୍ତ୍ତନ ଦୁର୍ବଳ ହୁଏ, ତେଣୁ ଏହା ଏକ ପ୍ରକୃତ ନୀତିଗତ ନିଷ୍ପତ୍ତି। +- `semgrep` (ପରାମର୍ଶମୂଳକ ସ୍ୱତନ୍ତ୍ର) — OWASP ପରିବାରଗୁଡ଼ିକ ପାଇଁ CodeQL ସହିତ ପୁନରାବୃତ୍ତି ଅଛି; ଏହାର ବେସ୍ଲାଇନ୍କୁ ଏକ ରାଚେଟ୍ ସହିତ ସଂଯୋଗ କରନ୍ତୁ କିମ୍ବା ବାଦ ଦିଅନ୍ତୁ। --- diff --git a/docs/i18n/or/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/or/docs/compression/COMPRESSION_GUIDE.md index 9ca779968cb9..b7c01d70cee9 100644 --- a/docs/i18n/or/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/or/docs/compression/COMPRESSION_GUIDE.md @@ -464,8 +464,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages` ଥିବା bodyରେ, ଏକ ବିଷୟବସ୍ତୁ ବାଇପାସ୍ (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts`ରେ) ଶେଷ ତିନୋଟି ମେସେଜ୍ ଯାଞ୍ଚ କରେ ଏବଂ ସେଗୁଡ଼ିକ ଏହାର ସୁରକ୍ଷା, ଅପରିବର୍ତ୍ତନୀୟ-କାର୍ଯ୍ୟ, ସ୍ପଷ୍ଟୀକରଣ କିମ୍ବା କ୍ରମ-ସମ୍ବେଦନଶୀଳ କୀୱାର୍ଡ ସହିତ ମେଳ ଖାଇଲେ -ସମଗ୍ର ଟର୍ନ୍ ପାଇଁ ଶୈଳୀଗୁଡ଼ିକୁ ଏଡ଼ାଇ ଦିଏ। ବାଇପାସ୍ ଡ୍ୟାଶବୋର୍ଡର -**Auto-Clarity Bypass** ଟଗଲ୍ (`cavemanOutputMode.autoClarity`) ଯେଉଁ ସେଟିଂରେ ରହିଛି, ସେହି ଅନୁସାରେ ଚାଲେ। +ସମଗ୍ର ଟର୍ନ୍ ପାଇଁ ଶୈଳୀଗୁଡ଼ିକୁ ଏଡ଼ାଇ ଦିଏ। ବାଇପାସ୍ ଡ୍ୟାଶବୋର୍ଡର **Auto-Clarity Bypass** ଟଗଲ୍ (`cavemanOutputMode.autoClarity`) ଚାଲୁ ଥିବା ପର୍ଯ୍ୟନ୍ତ ଚାଲେ, ଯାହା ପୂର୍ବନିର୍ଧାରିତ; ଟଗଲ୍ ବନ୍ଦ ଥିଲେ ବଛାଯାଇଥିବା ଶୈଳୀଗୁଡ଼ିକ ସେହି ଟର୍ନରେ ମଧ୍ୟ ପ୍ରଯୁଜ୍ୟ ହୁଏ। ବାଇପାସ୍ ଟର୍ନ୍କୁ ଅନୁମତି ଦେଲେ, `placeSystemInstruction()` (ସେହି ଫାଇଲ୍ରେ), ଯାହା କେବେ ମଧ୍ୟ ନୂଆ `messages[0]` ସୃଷ୍ଟି କରେନାହିଁ, ବ୍ଲକ୍କୁ ନିମ୍ନଲିଖିତ ମଧ୍ୟରୁ ପ୍ରଥମେ ମିଳିଥିବା ସ୍ଥାନରେ ରଖେ: diff --git a/docs/i18n/or/docs/reference/ENVIRONMENT.md b/docs/i18n/or/docs/reference/ENVIRONMENT.md index c872cf576b58..8715fb6ed63b 100644 --- a/docs/i18n/or/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/or/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. ଆବଶ୍ୟକ ଗୁପ୍ତ ମୂଲ୍ୟଗୁଡ଼ିକ -ପ୍ରଥମ ଥର ଚଲାଇବା ପୂର୍ବରୁ ଏଗୁଡ଼ିକୁ **ନିଶ୍ଚିତ ଭାବରେ** ସେଟ୍ କରିବା ଆବଶ୍ୟକ। ଏଗୁଡ଼ିକ ବିନା, ଆପ୍ଲିକେସନ୍ ଆରମ୍ଭ ହେବାକୁ ମନା କରିବ କିମ୍ବା ଅସୁରକ୍ଷିତ ଡିଫଲ୍ଟ ସହିତ କାର୍ଯ୍ୟ କରିବ। - -| ଭେରିଏବଲ୍ | ଆବଶ୍ୟକ | ଡିଫଲ୍ଟ | ସୋର୍ସ ଫାଇଲ୍ | ବର୍ଣ୍ଣନା | -| ---------------------------- | ------------------ | ------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **ହଁ** | _(କିଛି ନାହିଁ)_ | `src/lib/auth` | ସମସ୍ତ ଡ୍ୟାସ୍ବୋର୍ଡ ସେସନ୍ କୁକି (JWT)କୁ ସାଇନ୍/ଯାଞ୍ଚ କରେ। `openssl rand -base64 48` ସହିତ ଜେନେରେଟ୍ କରନ୍ତୁ। | -| `API_KEY_SECRET` | **ହଁ** | _(କିଛି ନାହିଁ)_ | `src/lib/db/apiKeys.ts` | SQLiteରେ ସଂରକ୍ଷିତ API କୀ ମୂଲ୍ୟଗୁଡ଼ିକ ପାଇଁ AES ଏନ୍କ୍ରିପ୍ସନ୍ କୀ। `openssl rand -hex 32` ସହିତ ଜେନେରେଟ୍ କରନ୍ତୁ। | -| `INITIAL_PASSWORD` | **ହଁ** | `CHANGEME` | ବୁଟ୍ଷ୍ଟ୍ରାପ୍ ସ୍କ୍ରିପ୍ଟ | ପ୍ରାରମ୍ଭିକ ଆଡ୍ମିନ୍ ଡ୍ୟାସ୍ବୋର୍ଡ ପାସ୍ୱାର୍ଡ ସେଟ୍ କରେ (`.env.example` ଡିଫଲ୍ଟ ସହିତ ମେଳ ଖାଏ — ପରିବର୍ତ୍ତନ କରିବାକୁ ବାଧ୍ୟ କରିବା ପାଇଁ ଏହାକୁ ସ୍ପଷ୍ଟ ଭାବରେ ଅସୁରକ୍ଷିତ ରଖାଯାଇଛି)। **ପ୍ରଥମ ବ୍ୟବହାର ପୂର୍ବରୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ।** ଲଗ୍ଇନ୍ ପରେ, ଡ୍ୟାସ୍ବୋର୍ଡ → ସେଟିଂସ୍ → ସୁରକ୍ଷା ମାଧ୍ୟମରେ ପରିବର୍ତ୍ତନ କରନ୍ତୁ। | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **ହଁ** (ପ୍ରଡକ୍ସନ୍) | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ଆଭ୍ୟନ୍ତରୀଣ Codex Responses WebSocket ବ୍ରିଜ୍ ପାଇଁ ସାଧାରଣ ଗୁପ୍ତ ମୂଲ୍ୟ। Electron/ବ୍ରାଉଜର୍ WS ରିଲେ ଏବଂ OmniRoute ମଧ୍ୟରେ ବ୍ରିଜ୍ ଅନୁରୋଧଗୁଡ଼ିକୁ ପ୍ରମାଣିତ କରେ। ⚠️ **ପ୍ରଡକ୍ସନ୍ରେ ଆବଶ୍ୟକ — ଏହା ସେଟ୍ ହୋଇନଥିଲେ, ସମସ୍ତ WS ବ୍ରିଜ୍ ଅନୁରୋଧ ପ୍ରତ୍ୟାଖ୍ୟାନ କରାଯାଏ।** `openssl rand -base64 32` ସହିତ ଜେନେରେଟ୍ କରନ୍ତୁ। | -| `OMNIROUTE_SW_BUILD_ID` | ନା | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ଶେଲ୍ (#11779) ପାଇଁ ସ୍ପଷ୍ଟ ସର୍ଭିସ୍-ୱର୍କର୍ କ୍ୟାଶ୍-ବଷ୍ଟିଂ ID; ରିଜୋଲ୍ୟୁସନ୍ ଶୃଙ୍ଖଳାରେ ପ୍ରଥମ। | -| `SOURCE_VERSION` | ନା | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ଶୃଙ୍ଖଳାରେ ଦ୍ୱିତୀୟ — ଡିପ୍ଲୟ କରାଯାଇଥିବା କମିଟ୍ ଭାବରେ PaaS ବିଲ୍ଡର୍ଗୁଡ଼ିକ (ଯଥା Heroku-ଶୈଳୀ) ଦ୍ୱାରା ସେଟ୍ କରାଯାଏ। | -| `NEXT_PUBLIC_SW_BUILD_ID` | ନା | _(ବ୍ୟୁତ୍ପନ୍ନ)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` ରେଜିଷ୍ଟର୍ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟ ବ୍ୟବହାର କରୁଥିବା ବିଲ୍ଡ-ସମୟର ସାର୍ବଜନୀନ ମୂଲ୍ୟ; ଉପରୋକ୍ତ ଦୁଇଟିରୁ ଏବଂ ତା’ପରେ git SHAରୁ ବ୍ୟୁତ୍ପନ୍ନ ହୁଏ। | -| `OMNIROUTE_PEER_STAMP_TOKEN` | ନା (ସ୍ୱୟଂଚାଳିତ) | _(ପ୍ରତ୍ୟେକ ବୁଟ୍ରେ ସ୍ୱୟଂଚାଳିତ)_ | `src/server/authz/policies/management.ts` | ବିଶ୍ୱସ୍ତ peer-IP ଷ୍ଟାମ୍ପ OmniRouteର ନିଜସ୍ୱ HTTP ସର୍ଭର୍ (`scripts/dev/peer-stamp.mjs`)ରୁ ଆସିଥିବା ପ୍ରମାଣ କରୁଥିବା ପ୍ରତି-ପ୍ରକ୍ରିୟା ଗୁପ୍ତ ମୂଲ୍ୟ। ଷ୍ଟାମ୍ପରେ ଏହି ଟୋକନ୍ ଥିଲେ ମାତ୍ର authz ମିଡଲ୍ୱେର୍ ଅନୁରୋଧର ସ୍ଥାନୀୟତାକୁ (LOCAL_ONLY ରୁଟ୍ଗୁଡ଼ିକର loopback/LAN ଗେଟିଂ) ବିଶ୍ୱାସ କରେ। ପ୍ରତ୍ୟେକ ବୁଟ୍ରେ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଜେନେରେଟ୍ ହୁଏ — ଏହାକୁ ସେଟ୍ ନକରି ଛାଡ଼ନ୍ତୁ; କେବଳ ଷ୍ଟାମ୍ପ ସେୟାର୍ କରିବା ଆବଶ୍ୟକ ଥିବା ମଲ୍ଟି-ପ୍ରୋସେସ୍ ସେଟ୍ଅପ୍ ପାଇଁ ଏହାକୁ ସ୍ଥିର କରନ୍ତୁ। | -| `OMNIROUTE_TRUSTED_PROXIES` | ନା | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `scripts/dev/peer-stamp.mjs` | ଯେଉଁ ରିଭର୍ସ ପ୍ରକ୍ସିଗୁଡ଼ିକର `X-Forwarded-For` / `X-Real-IP`କୁ IP ଅନୁମତି/ପ୍ରତ୍ୟାଖ୍ୟାନ ତାଲିକା ବିଶ୍ୱାସ କରିପାରିବ, ସେଗୁଡ଼ିକର କମା-ଦ୍ୱାରା ପୃଥକ କରାଯାଇଥିବା IP କିମ୍ବା CIDR ପରିସର; ଏହା loopback, ବ୍ୟକ୍ତିଗତ-ନେଟ୍ୱର୍କ ଠିକଣା ଏବଂ Cloudflare ଏଜ୍ଗୁଡ଼ିକ ସହିତ ଅତିରିକ୍ତ ଭାବେ ପ୍ରୟୋଗ ହୁଏ, ଯେଉଁଗୁଡ଼ିକୁ ସର୍ବଦା ବିଶ୍ୱାସ କରାଯାଏ। ଅନ୍ୟ କୌଣସି ସାର୍ବଜନୀନ ଠିକଣାରେ ଥିବା ପ୍ରକ୍ସି ପାଇଁ ମାତ୍ର ଏହା ଆବଶ୍ୟକ; ନଚେତ୍ ଫିଲ୍ଟର୍ ସେହି ପ୍ରକ୍ସିର ନିଜସ୍ୱ ଠିକଣାକୁ ଯାଞ୍ଚ କରେ। | +**ହଁ** ଚିହ୍ନଟ ହୋଇଥିବା କିନ୍ତୁ ସେଟ୍ ହୋଇନଥିବା ଗୁପ୍ତ ମୂଲ୍ୟଗୁଡ଼ିକ (`JWT_SECRET`, `API_KEY_SECRET`) ପ୍ରଥମ ଚଲାଇବା ସମୟରେ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଜେନେରେଟ୍ ହୁଏ ଏବଂ ଡାଟା ଡିରେକ୍ଟୋରୀର `server.env`ରେ ସଂରକ୍ଷିତ ହୁଏ — ସେହି ଡିରେକ୍ଟୋରୀ ଅସ୍ଥାୟୀ ହୋଇଥିଲେ ସେଗୁଡ଼ିକୁ ସ୍ପଷ୍ଟ ଭାବରେ ସେଟ୍ କରନ୍ତୁ। ଏହାର ଅପବାଦ `OMNIROUTE_WS_BRIDGE_SECRET`: ଏହା `server.env`ରେ ସଂରକ୍ଷିତ ହୁଏ ନାହିଁ — ସର୍ଭର ଏଣ୍ଟ୍ରିପଏଣ୍ଟଗୁଡ଼ିକ ପ୍ରତ୍ୟେକ ବୁଟ୍ ପାଇଁ ଏକ ଅସ୍ଥାୟୀ ମୂଲ୍ୟ ସୃଷ୍ଟି କରନ୍ତି ଯାହା କେବଳ ପ୍ରକ୍ରିୟା-ଭିତରିକ ବ୍ରିଜ୍ ବ୍ୟବହାର କରିପାରେ, ତେଣୁ ବାହ୍ୟ ବ୍ରିଜ୍ କ୍ଲାଏଣ୍ଟଗୁଡ଼ିକ ଏହାକୁ ସ୍ପଷ୍ଟ ଭାବରେ ସେଟ୍ କରିବା ଆବଶ୍ୟକ। + +| ଭେରିଏବଲ୍ | ଆବଶ୍ୟକ | ଡିଫଲ୍ଟ | ସୋର୍ସ ଫାଇଲ୍ | ବର୍ଣ୍ଣନା | +| ---------------------------- | ------------------ | ------------------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **ହଁ** | _(କିଛି ନାହିଁ)_ | `src/lib/auth` | ସମସ୍ତ ଡ୍ୟାସ୍ବୋର୍ଡ ସେସନ୍ କୁକି (JWT)କୁ ସାଇନ୍/ଯାଞ୍ଚ କରେ। `openssl rand -base64 48` ସହିତ ଜେନେରେଟ୍ କରନ୍ତୁ। | +| `API_KEY_SECRET` | **ହଁ** | _(କିଛି ନାହିଁ)_ | `src/lib/db/apiKeys.ts` | SQLiteରେ ସଂରକ୍ଷିତ API କୀ ମୂଲ୍ୟଗୁଡ଼ିକ ପାଇଁ AES ଏନ୍କ୍ରିପ୍ସନ୍ କୀ। `openssl rand -hex 32` ସହିତ ଜେନେରେଟ୍ କରନ୍ତୁ। | +| `INITIAL_PASSWORD` | ନା | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `src/lib/auth/managementPassword.ts` | କୌଣସି ପାସୱାର୍ଡ ସଂରକ୍ଷିତ ହୋଇନଥିବା ପର୍ଯ୍ୟନ୍ତ ଷ୍ଟାର୍ଟଅପ୍ ସମୟରେ ଡ୍ୟାସ୍ବୋର୍ଡ ପାସୱାର୍ଡ ସେଟ୍ କରେ; ପାସୱାର୍ଡ ସଂରକ୍ଷିତ ହେବା ପରେ ଏହା ଉପେକ୍ଷିତ ହୁଏ। npm ଏବଂ ସୋର୍ସ ଇନଷ୍ଟଲ୍ `.env.example`କୁ `.env`କୁ କପି କରନ୍ତି, ତେଣୁ ସେମାନେ ଏହାର ସର୍ବଜନୀନ ଭାବେ ଜଣାଶୁଣା ପ୍ଲେସହୋଲ୍ଡର `CHANGEME` ସହିତ ଆରମ୍ଭ ହୁଅନ୍ତି: ପ୍ରଥମ ବୁଟ୍ ପୂର୍ବରୁ ଏହାକୁ ବଦଳାନ୍ତୁ, କିମ୍ବା ପ୍ରଥମ ଭ୍ରମଣ ସମୟରେ ଅନବୋର୍ଡିଂ ୱିଜାର୍ଡରେ ପାସୱାର୍ଡ ସୃଷ୍ଟି କରିବା ପାଇଁ ସେହି `.env`ରେ `INITIAL_PASSWORD=` (ଖାଲି) ସେଟ୍ କରନ୍ତୁ — ଯେପରି ଏକ ଅନସେଟ୍ ମୂଲ୍ୟ (Docker ଇମେଜର ଡିଫଲ୍ଟ) କରେ। ସଂରକ୍ଷିତ ପାସୱାର୍ଡକୁ ଡ୍ୟାସ୍ବୋର୍ଡ → ସେଟିଂସ୍ → ସୁରକ୍ଷାରେ ବଦଳାନ୍ତୁ, କିମ୍ବା `DATA_DIR`କୁ ସର୍ଭରର ଡାଟା ଡିରେକ୍ଟୋରୀକୁ ସେଟ୍ କରି `omniroute reset-password` (ସୋର୍ସ ଚେକଆଉଟରେ `node bin/reset-password.mjs`) ଚଲାନ୍ତୁ। | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **ହଁ** (ପ୍ରଡକ୍ସନ୍) | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ଆଭ୍ୟନ୍ତରୀଣ Codex Responses WebSocket ବ୍ରିଜ୍ ପାଇଁ ସାଧାରଣ ଗୁପ୍ତ ମୂଲ୍ୟ। Electron/ବ୍ରାଉଜର୍ WS ରିଲେ ଏବଂ OmniRoute ମଧ୍ୟରେ ବ୍ରିଜ୍ ଅନୁରୋଧଗୁଡ଼ିକୁ ପ୍ରମାଣିତ କରେ। ⚠️ **ପ୍ରଡକ୍ସନ୍ରେ ଆବଶ୍ୟକ — ଏହା ସେଟ୍ ହୋଇନଥିଲେ, ସମସ୍ତ WS ବ୍ରିଜ୍ ଅନୁରୋଧ ପ୍ରତ୍ୟାଖ୍ୟାନ କରାଯାଏ।** `openssl rand -base64 32` ସହିତ ଜେନେରେଟ୍ କରନ୍ତୁ। | +| `OMNIROUTE_SW_BUILD_ID` | ନା | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ଶେଲ୍ (#11779) ପାଇଁ ସ୍ପଷ୍ଟ ସର୍ଭିସ୍-ୱର୍କର୍ କ୍ୟାଶ୍-ବଷ୍ଟିଂ ID; ରିଜୋଲ୍ୟୁସନ୍ ଶୃଙ୍ଖଳାରେ ପ୍ରଥମ। | +| `SOURCE_VERSION` | ନା | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ଶୃଙ୍ଖଳାରେ ଦ୍ୱିତୀୟ — ଡିପ୍ଲୟ କରାଯାଇଥିବା କମିଟ୍ ଭାବରେ PaaS ବିଲ୍ଡର୍ଗୁଡ଼ିକ (ଯଥା Heroku-ଶୈଳୀ) ଦ୍ୱାରା ସେଟ୍ କରାଯାଏ। | +| `NEXT_PUBLIC_SW_BUILD_ID` | ନା | _(ବ୍ୟୁତ୍ପନ୍ନ)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` ରେଜିଷ୍ଟର୍ କରିବା ପାଇଁ କ୍ଲାଏଣ୍ଟ ବ୍ୟବହାର କରୁଥିବା ବିଲ୍ଡ-ସମୟର ସାର୍ବଜନୀନ ମୂଲ୍ୟ; ଉପରୋକ୍ତ ଦୁଇଟିରୁ ଏବଂ ତା’ପରେ git SHAରୁ ବ୍ୟୁତ୍ପନ୍ନ ହୁଏ। | +| `OMNIROUTE_PEER_STAMP_TOKEN` | ନା (ସ୍ୱୟଂଚାଳିତ) | _(ପ୍ରତ୍ୟେକ ବୁଟ୍ରେ ସ୍ୱୟଂଚାଳିତ)_ | `src/server/authz/policies/management.ts` | ବିଶ୍ୱସ୍ତ peer-IP ଷ୍ଟାମ୍ପ OmniRouteର ନିଜସ୍ୱ HTTP ସର୍ଭର୍ (`scripts/dev/peer-stamp.mjs`)ରୁ ଆସିଥିବା ପ୍ରମାଣ କରୁଥିବା ପ୍ରତି-ପ୍ରକ୍ରିୟା ଗୁପ୍ତ ମୂଲ୍ୟ। ଷ୍ଟାମ୍ପରେ ଏହି ଟୋକନ୍ ଥିଲେ ମାତ୍ର authz ମିଡଲ୍ୱେର୍ ଅନୁରୋଧର ସ୍ଥାନୀୟତାକୁ (LOCAL_ONLY ରୁଟ୍ଗୁଡ଼ିକର loopback/LAN ଗେଟିଂ) ବିଶ୍ୱାସ କରେ। ପ୍ରତ୍ୟେକ ବୁଟ୍ରେ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଜେନେରେଟ୍ ହୁଏ — ଏହାକୁ ସେଟ୍ ନକରି ଛାଡ଼ନ୍ତୁ; କେବଳ ଷ୍ଟାମ୍ପ ସେୟାର୍ କରିବା ଆବଶ୍ୟକ ଥିବା ମଲ୍ଟି-ପ୍ରୋସେସ୍ ସେଟ୍ଅପ୍ ପାଇଁ ଏହାକୁ ସ୍ଥିର କରନ୍ତୁ। | +| `OMNIROUTE_TRUSTED_PROXIES` | ନା | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `scripts/dev/peer-stamp.mjs` | ଯେଉଁ ରିଭର୍ସ ପ୍ରକ୍ସିଗୁଡ଼ିକର `X-Forwarded-For` / `X-Real-IP`କୁ IP ଅନୁମତି/ପ୍ରତ୍ୟାଖ୍ୟାନ ତାଲିକା ବିଶ୍ୱାସ କରିପାରିବ, ସେଗୁଡ଼ିକର କମା-ଦ୍ୱାରା ପୃଥକ କରାଯାଇଥିବା IP କିମ୍ବା CIDR ପରିସର; ଏହା loopback, ବ୍ୟକ୍ତିଗତ-ନେଟ୍ୱର୍କ ଠିକଣା ଏବଂ Cloudflare ଏଜ୍ଗୁଡ଼ିକ ସହିତ ଅତିରିକ୍ତ ଭାବେ ପ୍ରୟୋଗ ହୁଏ, ଯେଉଁଗୁଡ଼ିକୁ ସର୍ବଦା ବିଶ୍ୱାସ କରାଯାଏ। ଅନ୍ୟ କୌଣସି ସାର୍ବଜନୀନ ଠିକଣାରେ ଥିବା ପ୍ରକ୍ସି ପାଇଁ ମାତ୍ର ଏହା ଆବଶ୍ୟକ; ନଚେତ୍ ଫିଲ୍ଟର୍ ସେହି ପ୍ରକ୍ସିର ନିଜସ୍ୱ ଠିକଣାକୁ ଯାଞ୍ଚ କରେ। | ### ଜେନେରେସନ୍ କମାଣ୍ଡଗୁଡ଼ିକ @@ -215,38 +215,38 @@ OmniRoute ସମସ୍ତ ତଥ୍ୟ ସ୍ଥାୟୀ ଭାବେ ସଂର ## 4. ସୁରକ୍ଷା ଓ ପ୍ରମାଣୀକରଣ -| ଭେରିଏବଲ୍ | ଡିଫଲ୍ଟ | ଉତ୍ସ ଫାଇଲ୍ | ବର୍ଣ୍ଣନା | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ମେସିନ୍ ଫିଙ୍ଗରପ୍ରିଣ୍ଟିଂ ପାଇଁ ହାର୍ଡୱେର୍ ପରିଚାୟକ ସହିତ ସଂଯୋଜିତ ସଲ୍ଟ। ପୃଥକୀକରଣ ପାଇଁ ପ୍ରତ୍ୟେକ ଡିପ୍ଲୟମେଣ୍ଟରେ ଏହାକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ। | -| `OMNIROUTE_CLI_SALT` | _(ସେଟ୍ ହୋଇନାହିଁ = ପ୍ରତ୍ୟେକ ଇନ୍ଷ୍ଟଲେସନ୍ରେ ଯାଦୃଚ୍ଛିକ ସଲ୍ଟ, ଯାହା `/cli-token-salt.json`ରେ ସ୍ଥାୟୀ ଭାବେ ସଂରକ୍ଷିତ)_ | `src/lib/machineToken.ts` | ସ୍ଥାନୀୟ CLI ପ୍ରମାଣୀକରଣ ଟୋକେନ୍ ଉତ୍ପନ୍ନ କରିବା ପାଇଁ HMAC ସଲ୍ଟ। ଏହି ମୂଲ୍ୟ ସେଟ୍ କଲେ ମେସିନ୍ର ସମସ୍ତ CLI ଟୋକେନ୍ ପରିବର୍ତ୍ତିତ ହୁଏ ଏବଂ ଏହା ସର୍ବଦା ସ୍ଥାୟୀ ଭାବେ ସଂରକ୍ଷିତ ସଲ୍ଟ ଅପେକ୍ଷା ପ୍ରାଥମିକତା ପାଏ। `docs/security/CLI_TOKEN.md` ଦେଖନ୍ତୁ। | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ସେସନ୍ କୁକିଗୁଡ଼ିକରେ `Secure` ଫ୍ଲାଗ୍ ସେଟ୍ କରେ। HTTPS ପଛରେ ଚାଲୁଥିବାବେଳେ ଏହା **ନିଶ୍ଚିତ ଭାବେ `true` ହେବା ଆବଶ୍ୟକ**। | -| `REQUIRE_API_KEY` | `false` | API ମିଡଲୱେୟାର୍ | `true` ଥିବାବେଳେ, ସମସ୍ତ `/v1/*` ପ୍ରକ୍ସି ଅନୁରୋଧରେ ଏକ ବୈଧ API କୀ ଅନ୍ତର୍ଭୁକ୍ତ ହେବା ଆବଶ୍ୟକ। **ଏହି ଫ୍ଲାଗ୍ `GET /v1/models`କୁ ନିୟନ୍ତ୍ରଣ କରେ ନାହିଁ**, ଏହା ପରିବର୍ତ୍ତେ ଡ୍ୟାସବୋର୍ଡ ଲଗଇନ୍ ସ୍ଥିତି (`requireAuthForModels`) ଅନୁସରଣ କରେ — ତେଣୁ `/v1/models`ରୁ ମିଳୁଥିବା `401`ର ଅର୍ଥ ଇନ୍ଫେରେନ୍ସ ସୁରକ୍ଷିତ ଅଛି ବୋଲି **ନୁହେଁ**। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ଦେଖନ୍ତୁ। | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ଡ୍ୟାସବୋର୍ଡ UIରେ ସମ୍ପୂର୍ଣ୍ଣ API କୀ ମୂଲ୍ୟ ପ୍ରକାଶ କରିବାକୁ ଅନୁମତି ଦିଏ। ଡ୍ୟାସବୋର୍ଡ ଫିଚର୍ ଫ୍ଲାଗ୍ରୁ ବିନ୍ୟାସଯୋଗ୍ୟ; ଅଂଶୀଦାର ଇନ୍ଷ୍ଟାନ୍ସଗୁଡ଼ିକରେ ଏହା ଏକ ସୁରକ୍ଷା ବିପଦ। | -| `NO_LOG_API_KEY_IDS` | _(ଖାଲି)_ | `src/lib/compliance/index.ts` | ଅନୁରୋଧ ଲଗିଂକୁ ଏଡ଼ାଇଯାଉଥିବା କମା-ପୃଥକ API କୀ IDଗୁଡ଼ିକ (GDPR ଅନୁପାଳନ)। | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ସେଟ୍ ହୋଇନାହିଁ = ଅସୀମିତ)_ | `src/shared/utils/apiKeyPolicy.ts` | ଯେଉଁ API କୀଗୁଡ଼ିକର `rate_limits` ସ୍ତମ୍ଭ null ଅଟେ, ସେଗୁଡ଼ିକ ପାଇଁ ପ୍ରୟୋଗ ହେଉଥିବା ପ୍ରତିଦିନର ଫଲ୍ବ୍ୟାକ୍ ଅନୁରୋଧ ବଜେଟ୍। ସେଟ୍ ହୋଇନଥିଲେ କିମ୍ବା ଖାଲି ଥିଲେ: କୌଣସି ପରୋକ୍ଷ ସୀମା ନାହିଁ (#2289, #11017)। `0` ମଧ୍ୟ ସମାନ (ଅସୀମିତ)। ଧନାତ୍ମକ ପୂର୍ଣ୍ଣସଂଖ୍ୟା N, N/ଦିନ, 5N/ସପ୍ତାହ ଏବଂ 20N/ମାସ ସକ୍ଷମ କରେ। ତ୍ରୁଟିପୂର୍ଣ୍ଣ ଅଣ-ଖାଲି ମୂଲ୍ୟଗୁଡ଼ିକ ପୁରୁଣା 1000/ଦିନ, 5000/ସପ୍ତାହ ଏବଂ 20000/ମାସ ୱିଣ୍ଡୋକୁ ଫଲ୍ବ୍ୟାକ୍ କରେ। | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ସର୍ବାଧିକ ଅନୁମୋଦିତ ଅନୁରୋଧ ବଡି ଆକାର। ଏହି ସୀମା ଅତିକ୍ରମ କରୁଥିବା ପେଲୋଡ୍ଗୁଡ଼ିକୁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରେ। | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ଏହି ଥ୍ରେଶହୋଲ୍ଡରେ କିମ୍ବା ତାଠାରୁ ଅଧିକ ଥିବା ପ୍ରକୃତ ଅନୁରୋଧ ବଡିଗୁଡ଼ିକ JSON ପାର୍ସିଂ ପୂର୍ବରୁ ଆଟମିକ୍ ପ୍ରୋସେସ୍-ଲୋକାଲ୍ ହେଭିୱେଟ୍ ଆଡମିଶନ୍ ଲିଜ୍ ଗ୍ରହଣ କରେ (`POST /v1/responses` ସମେତ BYTE ପଥ)। ସଂରଚନା-ଭାରୀ ପଥ ପରି ସମାନ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ସୁସ୍ଥ-ହେଡ୍ରୁମ୍ ଏସ୍କେପ୍; ତଥାପି `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ଦ୍ୱାରା ସୀମିତ, ଯାହାଫଳରେ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ପୁନରାବୃତ୍ତି ହୁଏ ନାହିଁ। | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ସୀମିତ ଇନ୍ଜେସ୍ଟ ସମୟରେ ପଢ଼ାଯାଇଥିବା ବାଇଟ୍ଗୁଡ଼ିକ ଉପରେ ପ୍ରୟୋଗ କରାଯାଉଥିବା ଚାଟ୍-ରୁଟ୍ର କଠୋର ସୀମା; ଏଥିରେ ଅନୁପସ୍ଥିତ, ଅବୈଧ କିମ୍ବା ମିଥ୍ୟା `Content-Length` ଥିବା ଅନୁରୋଧଗୁଡ଼ିକ ମଧ୍ୟ ଅନ୍ତର୍ଭୁକ୍ତ; ସୀମା ଅତିକ୍ରମ କଲେ `413` ମିଳେ। | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ସେଟ୍ ହୋଇନାହିଁ — କୌଣସି ଅନୁରୋଧ-ସଂଖ୍ୟା ସୀମା ନାହିଁ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ଏହି ପୁରୁଣା ଅନୁରୋଧ-ସଂଖ୍ୟା ସୀମାଟି ବର୍ତ୍ତମାନ କେବଳ ସ୍ପଷ୍ଟ ଭାବେ ସେଟ୍ କରାଗଲେ ପ୍ରଯୁଜ୍ୟ ହୁଏ। ଏହାକୁ ସେଟ୍ ନ କରି ଛାଡ଼ିଲେ (ଡିଫଲ୍ଟ), ହେଭିୱେଟ୍ ଚାଟ୍ ଆଡ୍ମିଶନ୍ ପରିବର୍ତ୍ତେ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ଦ୍ୱାରା ନିୟନ୍ତ୍ରିତ ହୁଏ — **ଗୋଟିଏ ପ୍ରୋସେସ୍**ରେ (ଗୋଟିଏ V8 ହିପ୍) ପ୍ରୋସେସ୍ର ବାସ୍ତବ ମେମୋରି ସୀମାରୁ ଆକାର ନିର୍ଦ୍ଧାରିତ ଏକ ସ୍ୱୟଂ-ବ୍ୟୁତ୍ପନ୍ନ ବାଇଟ୍ ବଜେଟ୍। ପରସ୍ପର ସହ ଓଭରଲ୍ୟାପ୍ ହେଉଥିବା ଦୁଇଟି ~750k-token `/v1/responses` ପ୍ରାୟ 12 Gi ହିପ୍କୁ ବିଫଳ କରେ (#7849) — ଏହା ଏକ **ମେମୋରି-ବଜେଟ୍** ଚେତାବନୀ, 2ର କୌଣସି କଠୋର ପ୍ରଡକ୍ଟ ସର୍ବାଧିକ ସୀମା ନୁହେଁ। ଏକ ସୁସ୍ଥ ପ୍ରୋସେସ୍ (ହିପ୍ ଶେଡ୍ ଅନୁପାତରୁ କମ୍ ଥିଲେ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ମାଧ୍ୟମରେ ଅଧିକ ସମସାମୟିକ ଦୀର୍ଘ `/v1/responses` ଗ୍ରହଣ କରିପାରେ। ଦଶ ଦଶଟି ଦୀର୍ଘ SSE କ୍ଲାଏଣ୍ଟ (40–50) ହେଉଛି ହିପ୍ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110ର ବିଷୟ, “ସର୍ବାଧିକ 2” ନୁହେଁ। “ହୋଷ୍ଟ ବ୍ୟବହାର କରିବା” ପାଇଁ ଏହାକୁ ବିଚାରହୀନ ଭାବେ ବଢ଼ାଇବା #7849କୁ ପୁନଃ ଆଣେ। **Nଟି ସ୍ୱାଧୀନ `DATA_DIR`** (#11024) ସହ **ହିପ୍ଗୁଡ଼ିକ** ବଢ଼ାନ୍ତୁ; ଗୋଟିଏ SQLite ଫାଇଲ୍ରେ କଦାପି `replicas>1` ବ୍ୟବହାର କରନ୍ତୁ ନାହିଁ। | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ସ୍ୱୟଂ-ବ୍ୟୁତ୍ପନ୍ନ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ସ୍ୱୟଂ-ବ୍ୟୁତ୍ପନ୍ନ ଇନ୍ଜେସ୍ଟ ବାଇଟ୍ ବଜେଟ୍ର ଓଭରରାଇଡ୍ (ଅଧିକ କଠୋର V8/cgroup ମେମୋରି ସୀମାର 25%, 8x କ୍ଷଣସ୍ଥାୟୀ ବୃଦ୍ଧି ଦ୍ୱାରା ବିଭକ୍ତ)। ବ୍ୟୁତ୍ପନ୍ନ ଏବଂ ସ୍ପଷ୍ଟ ମୂଲ୍ୟଗୁଡ଼ିକ 8 MiB–2 GiB ମଧ୍ୟରେ ସୀମିତ ରହେ। କାର୍ଯ୍ୟକାରୀ ବଜେଟ୍ଠାରୁ ବଡ଼ ବଡି ତୁରନ୍ତ `413 body_exceeds_budget` ସହ ବିଫଳ ହୁଏ; ପୃଥକ ଭାବେ ସେବାଯୋଗ୍ୟ ବଡିଗୁଡ଼ିକ ମଧ୍ୟରେ ପ୍ରତିଦ୍ୱନ୍ଦ୍ୱିତା ପୁନଃଚେଷ୍ଟାଯୋଗ୍ୟ `503` ଭାବେ ରହେ। 40–50ଟି ସମସାମୟିକ ଦୀର୍ଘ SSE କ୍ଲାଏଣ୍ଟ ଏହି ବଜେଟ୍ + ହିପ୍ର ବିଷୟ, କୌଣସି କଠୋର “ସର୍ବାଧିକ 2” ନୁହେଁ। ଟ୍ୟୁନ୍ କରିବା ପୂର୍ବରୁ `/api/monitoring/health`ରେ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ପଢ଼ନ୍ତୁ। | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE ଏବଂ STRUCTURE ହେଭିୱେଟ୍ ଆଡ୍ମିଶନ୍ ପାଇଁ ହିପ୍-ଚାପ ଶେଡ୍ ଅନୁପାତ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ଅତିକ୍ରମ କରୁଥିବା ଏକ ସମସାମୟିକ ହେଭିୱେଟ୍ ଅନୁରୋଧକୁ କେବଳ ହିପ୍ଟି ମଧ୍ୟ ଏହି ଅନୁପାତରେ କିମ୍ବା ତାହାଠାରୁ ଅଧିକ ଥିଲେ ପୁନଃଚେଷ୍ଟାଯୋଗ୍ୟ `503` ସହ ଶେଡ୍ କରାଯାଏ; ଏକ ସୁସ୍ଥ ହିପ୍ରେ ଏହାକୁ healthy-headroom ମାଧ୍ୟମରେ ଗ୍ରହଣ କରାଯାଏ। | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ଡିଫଲ୍ଟ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **ଉଭୟ** STRUCTURE ଏବଂ BYTE (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ≥ ବଡି ସମେତ) ଉପରେ healthy-heap ଫାଷ୍ଟ ପାଥ୍ (#10437) ପାଇଁ ସୀମିତ ଅତିରିକ୍ତ କ୍ଷମତା। ଏହି ସୀମା ବିନା, ପ୍ରତ୍ୟେକ ବ୍ୟସ୍ତ-କିନ୍ତୁ-ସୁସ୍ଥ-ହିପ୍ ଅନୁରୋଧ କୌଣସି ସର୍ବୋଚ୍ଚ ସୀମା ବିନା ଆଡ୍ମିଶନ୍କୁ ବାଇପାସ୍ କରୁଥିଲା। healthy-heap ପାଥ୍ ମାଧ୍ୟମରେ ଏତିକି ସମସାମୟିକ ଲିଜ୍ ସକ୍ରିୟ ହେବା ପରେ, ପରବର୍ତ୍ତୀ ବ୍ୟସ୍ତ ଅନୁରୋଧଗୁଡ଼ିକ ବାସ୍ତବ ହିପ୍ ଚାପରେ ବ୍ୟବହୃତ ସେହି ଏକା ସୀମିତ-ପ୍ରତୀକ୍ଷା/ଶେଡ୍ ପାଥ୍କୁ ଯାଆନ୍ତି। `0` ବାଇପାସ୍କୁ ସମ୍ପୂର୍ଣ୍ଣ ଭାବେ ଅକ୍ଷମ କରେ। | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ବଡି ବାଇଟ୍ ସୀମାଠାରୁ କମ୍ ଥିଲେ ମଧ୍ୟ ଏକ ଚାଟ୍ ଅନୁରୋଧକୁ ହେଭିୱେଟ୍ ଭାବେ ବର୍ଗୀକୃତ କରୁଥିବା ସନ୍ଦେଶ ସଂଖ୍ୟା। | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ବଡି ବାଇଟ୍ ସୀମାଠାରୁ କମ୍ ଥିଲେ ମଧ୍ୟ ଏକ ଚାଟ୍ ଅନୁରୋଧକୁ ହେଭିୱେଟ୍ ଭାବେ ବର୍ଗୀକୃତ କରୁଥିବା ଟୁଲ୍ ସଂଖ୍ୟା। | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ଏକ ଅନୁରୋଧକୁ ହେଭିୱେଟ୍ ଭାବେ ବର୍ଗୀକୃତ କରୁଥିବା ରକ୍ଷଣଶୀଳ ଷ୍ଟ୍ରିଙ୍ଗ୍-ଆକାର ଟୋକନ୍ ଅନୁମାନ; ଏହା ଏକ ଆଡ୍ମିଶନ୍-ଖର୍ଚ୍ଚ ପ୍ରକ୍ସି, ପ୍ରଦାନକାରୀଙ୍କ ବିଲିଂ ଟୋକେନାଇଜେସନ୍ ନୁହେଁ। | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ଅକ୍ଷମ) | `src/shared/middleware/chatBodyAdmission.ts` | ଇଚ୍ଛାଧୀନ ଅପ୍ଟ-ଇନ୍ ଚାଟ୍ ଇତିହାସ ସୀମା। ଡିଫଲ୍ଟ ଭାବେ ଅକ୍ଷମ: ସନ୍ଦେଶ ସଂଖ୍ୟା ହେଉଛି ଡିପ୍ଲୟମେଣ୍ଟ ନୀତି, କୌଣସି ଅନୁରୋଧର ସାର୍ବଜନୀନ ଗୁଣ ନୁହେଁ; ଏବଂ ଏଠାରେ ସୀମା ଲଗାଇବା ଦ୍ୱାରା କମ୍ପ୍ରେସନ୍ ପାଇପ୍ଲାଇନ୍ ସେଗୁଡ଼ିକୁ ସେବାଯୋଗ୍ୟ କରିବା ପୂର୍ବରୁ ଟର୍ମିନାଲ୍ `413` ସହ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ପ୍ରତ୍ୟାଖ୍ୟାନ ହୁଏ। ହିପ୍ ବୃଦ୍ଧି `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ଏବଂ ହିପ୍-ଚାପ ଶେଡ୍ ଦ୍ୱାରା ସୀମିତ। କଠୋର ସର୍ବୋଚ୍ଚ ସୀମା ଆବଶ୍ୟକ କରୁଥିବା ମେମୋରି-ସୀମିତ ଡିପ୍ଲୟମେଣ୍ଟଗୁଡ଼ିକରେ ଏକ ଧନାତ୍ମକ ମୂଲ୍ୟ ସେଟ୍ କରନ୍ତୁ; ସୀମା ଅତିକ୍ରମ କଲେ ସଂରଚିତ compact-required `413` ମିଳେ। | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ମେମୋରିରେ ସମ୍ପୂର୍ଣ୍ଣ ଭାବେ ବଫର୍ ହୋଇଥିବା ଏକ ନନ୍-ଷ୍ଟ୍ରିମିଂ ଅପ୍ଷ୍ଟ୍ରିମ୍ ପ୍ରତିକ୍ରିୟା ପାଇଁ କଠୋର ସୀମା। ଏହା ଅତିକ୍ରମ କଲେ ଅପ୍ଷ୍ଟ୍ରିମ୍ ରିଡର୍ ବାତିଲ୍ ହୁଏ ଏବଂ ହିପ୍ ଶେଷ ହେବା ପର୍ଯ୍ୟନ୍ତ ଏକ ଅସୀମିତ ଷ୍ଟ୍ରିଙ୍ଗ୍ ବୃଦ୍ଧି କରିବା ପରିବର୍ତ୍ତେ ଅନୁରୋଧଟି ତୁରନ୍ତ ବିଫଳ ହୁଏ। | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ଅପ୍ଷ୍ଟ୍ରିମ୍ ପ୍ରତିକ୍ରିୟା ହେଡର୍ଗୁଡ଼ିକରୁ ଫରୱାର୍ଡ କରାଯାଉଥିବା ସର୍ବାଧିକ ୱାୟାର୍ ବାଇଟ୍। ବଜେଟ୍ ଅତିକ୍ରମ ହେଲେ, ସାଧାରଣ ରିଭର୍ସ-ପ୍ରକ୍ସି ହେଡର୍ ସୀମା ମଧ୍ୟରେ ରହିବା ପାଇଁ ନିମ୍ନ-ପ୍ରାଥମିକତା ହେଡର୍ଗୁଡ଼ିକୁ (ଯଥା, କଷ୍ଟମ୍ `x-codex-*`, `x-oai-request-id`) ବାଦ ଦିଆଯାଏ। ବଡ଼ ପ୍ରତିକ୍ରିୟା ହେଡର୍ ଆକାର ବଦଳରେ ଅଧିକ ଅପ୍ଷ୍ଟ୍ରିମ୍ ମେଟାଡାଟା ଫରୱାର୍ଡ କରିବାକୁ ଏହାକୁ ଅଧିକ ମୂଲ୍ୟରେ ସେଟ୍ କରନ୍ତୁ। | -| `CORS_ORIGIN` | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `src/server/cors/origins.ts` | ପୁରୁଣା ଏକକ-ଅରିଜିନ୍ CORS ଅନୁମୋଦନ ତାଲିକା। ନୂଆ ଡିପ୍ଲୟମେଣ୍ଟଗୁଡ଼ିକ ପାଇଁ `CORS_ALLOWED_ORIGINS`କୁ ପ୍ରାଧାନ୍ୟ ଦିଅନ୍ତୁ। CORS କେବଳ କ୍ରସ୍-ଅରିଜିନ୍ ବ୍ରାଉଜର୍ API କ୍ଲାଏଣ୍ଟଗୁଡ଼ିକ ପାଇଁ; ଅଧିକୃତ ଡ୍ୟାସ୍ବୋର୍ଡ ଲେଖନଗୁଡ଼ିକ ଏହା ବଦଳରେ ସମାନ-ଅରିଜିନ୍ ଅନୁରୋଧ ସହିତ ସେସନ୍-ବାଉଣ୍ଡ CSRF ସୁରକ୍ଷା ବ୍ୟବହାର କରେ। | -| `CORS_ALLOWED_ORIGINS` | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `src/server/cors/origins.ts` | କମା ଦ୍ୱାରା ପୃଥକ କରାଯାଇଥିବା CORS ଅନୁମୋଦନ ତାଲିକା। `CORS_ALLOW_ALL=true`କୁ ସ୍ପଷ୍ଟ ଭାବେ ବିନ୍ୟାସ ନ କରାଯିବା ପର୍ଯ୍ୟନ୍ତ କୌଣସି ୱାଇଲ୍ଡକାର୍ଡ ପଠାଯାଏ ନାହିଁ। | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ଯେକୌଣସି ବ୍ରାଉଜର୍ `Origin`କୁ ପ୍ରତିଧ୍ୱନିତ କରିବା ପାଇଁ କେବଳ ଡେଭଲପମେଣ୍ଟ ନିମନ୍ତେ ଉଦ୍ଦିଷ୍ଟ ଏକ ଏସ୍କେପ୍ ବ୍ୟବସ୍ଥା। ଅଂଶୀଦାର କିମ୍ବା ପ୍ରଡକ୍ସନ୍ ଡିପ୍ଲୟମେଣ୍ଟରେ ଏହାକୁ ସକ୍ଷମ କରନ୍ତୁ ନାହିଁ। | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | ପ୍ରାଇଭେଟ୍/ଲୁପ୍ବ୍ୟାକ୍/ଲିଙ୍କ୍-ଲୋକାଲ୍ IP ରେଞ୍ଜକୁ ଲକ୍ଷ୍ୟ କରୁଥିବା ପ୍ରଦାନକାରୀ କଲ୍ଗୁଡ଼ିକୁ ଅବରୋଧ କରନ୍ତୁ। କେବଳ ପୃଥକୀକୃତ ପରୀକ୍ଷଣ ପରିବେଶରେ ଅକ୍ଷମ କରନ୍ତୁ। | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | ପ୍ରାଇଭେଟ୍/ଲୋକାଲ୍ ନେଟୱର୍କଗୁଡ଼ିକୁ (localhost, 192.168.x.x, 10.x.x.x, ଇତ୍ୟାଦି) ସୂଚାଉଥିବା ପ୍ରଦାନକାରୀ URLଗୁଡ଼ିକୁ ଅନୁମତି ଦିଅନ୍ତୁ। **ସ୍ୱୟଂ-ହୋଷ୍ଟ କରାଯାଇଥିବା ପ୍ରଦାନକାରୀମାନଙ୍କ ପାଇଁ ଆବଶ୍ୟକ** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)। `false` ଥିବାବେଳେ, ଡ୍ୟାସ୍ବୋର୍ଡ ସ୍ଥାନୀୟ URLଗୁଡ଼ିକର ବୈଧତା ଯାଞ୍ଚକୁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରେ। | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | ପ୍ରଦାନକାରୀ ବୈଧତା ଯାଞ୍ଚ ପଥରେ ସୀମିତ ରହି, ସ୍ଥାନୀୟ/ପ୍ରାଇଭେଟ୍ ଠିକଣାଗୁଡ଼ିକରେ (127.0.0.1, localhost, LAN, ପ୍ରାଇଭେଟ୍ ରେଞ୍ଜ) ପ୍ରଦାନକାରୀ ଯୋଡ଼ିବା/ବୈଧତା ଯାଞ୍ଚ କରିବାକୁ ଅନୁମତି ଦିଅନ୍ତୁ। **ଡିଫଲ୍ଟ `true`** (ଲୋକାଲ୍-ପ୍ରଥମ); କେବଳ ପବ୍ଲିକ୍ ଠିକଣାକୁ ଅନୁମତି ଦେଉଥିବା କଠୋର ଅବରୋଧ ଲାଗୁ କରିବାକୁ `false` ସେଟ୍ କରନ୍ତୁ। ଏହା ସତ୍ତ୍ୱେ କ୍ଲାଉଡ୍-ମେଟାଡାଟା ଏଣ୍ଡପଏଣ୍ଟଗୁଡ଼ିକ (169.254.169.254, metadata.google.internal) ଅବରୋଧିତ ରହେ। (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ରୁଟ୍ଗୁଡ଼ିକୁ (ଟ୍ରାନ୍ସକ୍ରିପ୍ସନ୍, ସ୍ପିଚ୍, ଟ୍ରାନ୍ସଲେସନ୍) localhost ବାହାରେ ହୋଷ୍ଟ କରାଯାଇଥିବା OpenAI-ସୁସଙ୍ଗତ ପ୍ରଦାନକାରୀ ନୋଡ୍ ବ୍ୟବହାର କରିବାକୁ ଦିଅନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ — ଏକ ରିମୋଟ୍ ହୋଷ୍ଟକୁ ଅଡିଓ ରୁଟ୍ କରିବା ଇଗ୍ରେସ୍ ପରିଚୟ ପରିବର୍ତ୍ତନ କରେ ଏବଂ ଏହା ଏକ ସ୍ପଷ୍ଟ ଅପରେଟର୍ ନିଷ୍ପତ୍ତି ହେବା ଆବଶ୍ୟକ। ଲୁପ୍ବ୍ୟାକ୍/ପ୍ରାଇଭେଟ୍ ନୋଡ୍ଗୁଡ଼ିକ (localhost, 127.0.0.1, 172.16-31.x) ସର୍ବଦା ଅନୁମୋଦିତ ଏବଂ ଅପ୍ରଭାବିତ। (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`କୁ (ଏବଂ ମେମୋରି ଇଞ୍ଜିନ୍ର ଲୁପ୍ବ୍ୟାକ୍ ପୁନଃ-ରାଙ୍କିଂ ପଦକ୍ଷେପକୁ) localhost ବାହାରେ ହୋଷ୍ଟ କରାଯାଇଥିବା OpenAI-ସୁସଙ୍ଗତ ପ୍ରଦାନକାରୀ ନୋଡ୍—ଯଥା TEI, Infinity, vLLM ଇତ୍ୟାଦି ଚଳାଉଥିବା ଏକ LAN ବକ୍ସ କିମ୍ବା Tailscale ପିୟର୍—ବ୍ୟବହାର କରିବାକୁ ଦିଅନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ — ଏକ ରିମୋଟ୍ ହୋଷ୍ଟକୁ ରୁଟ୍ କରିବା ଇଗ୍ରେସ୍ ପରିଚୟ ପରିବର୍ତ୍ତନ କରେ ଏବଂ ଏହା ଏକ ସ୍ପଷ୍ଟ ଅପରେଟର୍ ନିଷ୍ପତ୍ତି ହେବା ଆବଶ୍ୟକ। ଲୁପ୍ବ୍ୟାକ୍ ନୋଡ୍ଗୁଡ଼ିକ (localhost, 127.0.0.1, 172.16-31.x) ସର୍ବଦା ଅନୁମୋଦିତ ଏବଂ ଅପ୍ରଭାବିତ। ରିମୋଟ୍ ନୋଡ୍ଗୁଡ଼ିକୁ ପ୍ରଦାନକାରୀଙ୍କ ଆଉଟ୍ବାଉଣ୍ଡ URL ନୀତି (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ମଧ୍ୟ ପାର୍ କରିବାକୁ ପଡ଼ିବ; କ୍ଲାଉଡ୍-ମେଟାଡାଟା ହୋଷ୍ଟଗୁଡ଼ିକୁ କେବେ ରୁଟ୍ କରାଯାଏ ନାହିଁ। | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ସକ୍ଷମ ଥିବାବେଳେ, ପାସୱାର୍ଡ ଲଗ୍ଇନ୍ ଅକ୍ଷମ କରନ୍ତୁ, ଯାହାଦ୍ୱାରା ବ୍ୟବହାରକାରୀମାନେ କେବଳ OIDC Single Sign-On ମାଧ୍ୟମରେ ପ୍ରମାଣୀକରଣ କରିପାରିବେ। ସରଳ ଉପନାମ `OIDC_DISABLE_PASSWORD_LOGIN` ମଧ୍ୟ ଗ୍ରହଣୀୟ; ସମାନ କି ଥିବା Dashboard Feature Flagକୁ ପ୍ରାଥମିକତା ଦିଆଯାଏ। (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`ର ସରଳ ଉପନାମ (#10889)। | +| ଭେରିଏବଲ୍ | ଡିଫଲ୍ଟ | ଉତ୍ସ ଫାଇଲ୍ | ବର୍ଣ୍ଣନା | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ମେସିନ୍ ଫିଙ୍ଗରପ୍ରିଣ୍ଟିଂ ପାଇଁ ହାର୍ଡୱେର୍ ପରିଚାୟକ ସହିତ ସଂଯୋଜିତ ସଲ୍ଟ। ପୃଥକୀକରଣ ପାଇଁ ପ୍ରତ୍ୟେକ ଡିପ୍ଲୟମେଣ୍ଟରେ ଏହାକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ। | +| `OMNIROUTE_CLI_SALT` | _(ସେଟ୍ ହୋଇନାହିଁ = ପ୍ରତ୍ୟେକ ଇନ୍ଷ୍ଟଲେସନ୍ରେ ଯାଦୃଚ୍ଛିକ ସଲ୍ଟ, ଯାହା `/cli-token-salt.json`ରେ ସ୍ଥାୟୀ ଭାବେ ସଂରକ୍ଷିତ)_ | `src/lib/machineToken.ts` | ସ୍ଥାନୀୟ CLI ପ୍ରମାଣୀକରଣ ଟୋକେନ୍ ଉତ୍ପନ୍ନ କରିବା ପାଇଁ HMAC ସଲ୍ଟ। ଏହି ମୂଲ୍ୟ ସେଟ୍ କଲେ ମେସିନ୍ର ସମସ୍ତ CLI ଟୋକେନ୍ ପରିବର୍ତ୍ତିତ ହୁଏ ଏବଂ ଏହା ସର୍ବଦା ସ୍ଥାୟୀ ଭାବେ ସଂରକ୍ଷିତ ସଲ୍ଟ ଅପେକ୍ଷା ପ୍ରାଥମିକତା ପାଏ। `docs/security/CLI_TOKEN.md` ଦେଖନ୍ତୁ। | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ସେସନ୍ କୁକିଗୁଡ଼ିକରେ `Secure` ଫ୍ଲାଗ୍ ସେଟ୍ କରେ। HTTPS ପଛରେ ଚାଲୁଥିବାବେଳେ ଏହା **ନିଶ୍ଚିତ ଭାବେ `true` ହେବା ଆବଶ୍ୟକ**। | +| `REQUIRE_API_KEY` | `false` | API ମିଡଲୱେୟାର୍ | `true` ଥିବାବେଳେ, ସମସ୍ତ `/v1/*` ପ୍ରକ୍ସି ଅନୁରୋଧରେ ଏକ ବୈଧ API କୀ ଅନ୍ତର୍ଭୁକ୍ତ ହେବା ଆବଶ୍ୟକ। **ଏହି ଫ୍ଲାଗ୍ `GET /v1/models`କୁ ନିୟନ୍ତ୍ରଣ କରେ ନାହିଁ**, ଏହା ପରିବର୍ତ୍ତେ ଡ୍ୟାସବୋର୍ଡ ଲଗଇନ୍ ସ୍ଥିତି (`requireAuthForModels`) ଅନୁସରଣ କରେ — ତେଣୁ `/v1/models`ରୁ ମିଳୁଥିବା `401`ର ଅର୍ଥ ଇନ୍ଫେରେନ୍ସ ସୁରକ୍ଷିତ ଅଛି ବୋଲି **ନୁହେଁ**। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ଦେଖନ୍ତୁ। | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ଡ୍ୟାସବୋର୍ଡ UIରେ ସମ୍ପୂର୍ଣ୍ଣ API କୀ ମୂଲ୍ୟ ପ୍ରକାଶ କରିବାକୁ ଅନୁମତି ଦିଏ। ଡ୍ୟାସବୋର୍ଡ ଫିଚର୍ ଫ୍ଲାଗ୍ରୁ ବିନ୍ୟାସଯୋଗ୍ୟ; ଅଂଶୀଦାର ଇନ୍ଷ୍ଟାନ୍ସଗୁଡ଼ିକରେ ଏହା ଏକ ସୁରକ୍ଷା ବିପଦ। | +| `NO_LOG_API_KEY_IDS` | _(ଖାଲି)_ | `src/lib/compliance/index.ts` | ଅନୁରୋଧ ଲଗିଂକୁ ଏଡ଼ାଇଯାଉଥିବା କମା-ପୃଥକ API କୀ IDଗୁଡ଼ିକ (GDPR ଅନୁପାଳନ)। | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ସେଟ୍ ହୋଇନାହିଁ = ଅସୀମିତ)_ | `src/shared/utils/apiKeyPolicy.ts` | ଯେଉଁ API କୀଗୁଡ଼ିକର `rate_limits` ସ୍ତମ୍ଭ null ଅଟେ, ସେଗୁଡ଼ିକ ପାଇଁ ପ୍ରୟୋଗ ହେଉଥିବା ପ୍ରତିଦିନର ଫଲ୍ବ୍ୟାକ୍ ଅନୁରୋଧ ବଜେଟ୍। ସେଟ୍ ହୋଇନଥିଲେ କିମ୍ବା ଖାଲି ଥିଲେ: କୌଣସି ପରୋକ୍ଷ ସୀମା ନାହିଁ (#2289, #11017)। `0` ମଧ୍ୟ ସମାନ (ଅସୀମିତ)। ଧନାତ୍ମକ ପୂର୍ଣ୍ଣସଂଖ୍ୟା N, N/ଦିନ, 5N/ସପ୍ତାହ ଏବଂ 20N/ମାସ ସକ୍ଷମ କରେ। ତ୍ରୁଟିପୂର୍ଣ୍ଣ ଅଣ-ଖାଲି ମୂଲ୍ୟଗୁଡ଼ିକ ପୁରୁଣା 1000/ଦିନ, 5000/ସପ୍ତାହ ଏବଂ 20000/ମାସ ୱିଣ୍ଡୋକୁ ଫଲ୍ବ୍ୟାକ୍ କରେ। | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ସର୍ବାଧିକ ଅନୁମୋଦିତ ଅନୁରୋଧ ବଡି ଆକାର। ଏହି ସୀମା ଅତିକ୍ରମ କରୁଥିବା ପେଲୋଡ୍ଗୁଡ଼ିକୁ ପ୍ରତ୍ୟାଖ୍ୟାନ କରେ। | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ଏହି ଥ୍ରେଶହୋଲ୍ଡରେ କିମ୍ବା ତାଠାରୁ ଅଧିକ ଥିବା ପ୍ରକୃତ ଅନୁରୋଧ ବଡିଗୁଡ଼ିକ JSON ପାର୍ସିଂ ପୂର୍ବରୁ ଆଟମିକ୍ ପ୍ରୋସେସ୍-ଲୋକାଲ୍ ହେଭିୱେଟ୍ ଆଡମିଶନ୍ ଲିଜ୍ ଗ୍ରହଣ କରେ (`POST /v1/responses` ସମେତ BYTE ପଥ)। ସଂରଚନା-ଭାରୀ ପଥ ପରି ସମାନ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ସୁସ୍ଥ-ହେଡ୍ରୁମ୍ ଏସ୍କେପ୍; ତଥାପି `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ଦ୍ୱାରା ସୀମିତ, ଯାହାଫଳରେ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ପୁନରାବୃତ୍ତି ହୁଏ ନାହିଁ। | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ସୀମିତ ଇନ୍ଜେସ୍ଟ ସମୟରେ ପଢ଼ାଯାଇଥିବା ବାଇଟ୍ଗୁଡ଼ିକ ଉପରେ ପ୍ରୟୋଗ କରାଯାଉଥିବା ଚାଟ୍-ରୁଟ୍ର କଠୋର ସୀମା; ଏଥିରେ ଅନୁପସ୍ଥିତ, ଅବୈଧ କିମ୍ବା ମିଥ୍ୟା `Content-Length` ଥିବା ଅନୁରୋଧଗୁଡ଼ିକ ମଧ୍ୟ ଅନ୍ତର୍ଭୁକ୍ତ; ସୀମା ଅତିକ୍ରମ କଲେ `413` ମିଳେ। | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ସେଟ୍ ହୋଇନାହିଁ — କୌଣସି ଅନୁରୋଧ-ସଂଖ୍ୟା ସୀମା ନାହିଁ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ଏହି ପୁରୁଣା ଅନୁରୋଧ-ସଂଖ୍ୟା ସୀମାଟି ବର୍ତ୍ତମାନ କେବଳ ସ୍ପଷ୍ଟ ଭାବେ ସେଟ୍ କରାଗଲେ ପ୍ରଯୁଜ୍ୟ ହୁଏ। ଏହାକୁ ସେଟ୍ ନ କରି ଛାଡ଼ିଲେ (ଡିଫଲ୍ଟ), ହେଭିୱେଟ୍ ଚାଟ୍ ଆଡ୍ମିଶନ୍ ପରିବର୍ତ୍ତେ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ଦ୍ୱାରା ନିୟନ୍ତ୍ରିତ ହୁଏ — **ଗୋଟିଏ ପ୍ରୋସେସ୍**ରେ (ଗୋଟିଏ V8 ହିପ୍) ପ୍ରୋସେସ୍ର ବାସ୍ତବ ମେମୋରି ସୀମାରୁ ଆକାର ନିର୍ଦ୍ଧାରିତ ଏକ ସ୍ୱୟଂ-ବ୍ୟୁତ୍ପନ୍ନ ବାଇଟ୍ ବଜେଟ୍। ପରସ୍ପର ସହ ଓଭରଲ୍ୟାପ୍ ହେଉଥିବା ଦୁଇଟି ~750k-token `/v1/responses` ପ୍ରାୟ 12 Gi ହିପ୍କୁ ବିଫଳ କରେ (#7849) — ଏହା ଏକ **ମେମୋରି-ବଜେଟ୍** ଚେତାବନୀ, 2ର କୌଣସି କଠୋର ପ୍ରଡକ୍ଟ ସର୍ବାଧିକ ସୀମା ନୁହେଁ। ଏକ ସୁସ୍ଥ ପ୍ରୋସେସ୍ (ହିପ୍ ଶେଡ୍ ଅନୁପାତରୁ କମ୍ ଥିଲେ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ମାଧ୍ୟମରେ ଅଧିକ ସମସାମୟିକ ଦୀର୍ଘ `/v1/responses` ଗ୍ରହଣ କରିପାରେ। ଦଶ ଦଶଟି ଦୀର୍ଘ SSE କ୍ଲାଏଣ୍ଟ (40–50) ହେଉଛି ହିପ୍ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110ର ବିଷୟ, “ସର୍ବାଧିକ 2” ନୁହେଁ। “ହୋଷ୍ଟ ବ୍ୟବହାର କରିବା” ପାଇଁ ଏହାକୁ ବିଚାରହୀନ ଭାବେ ବଢ଼ାଇବା #7849କୁ ପୁନଃ ଆଣେ। **Nଟି ସ୍ୱାଧୀନ `DATA_DIR`** (#11024) ସହ **ହିପ୍ଗୁଡ଼ିକ** ବଢ଼ାନ୍ତୁ; ଗୋଟିଏ SQLite ଫାଇଲ୍ରେ କଦାପି `replicas>1` ବ୍ୟବହାର କରନ୍ତୁ ନାହିଁ। | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ସ୍ୱୟଂ-ବ୍ୟୁତ୍ପନ୍ନ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ସ୍ୱୟଂ-ବ୍ୟୁତ୍ପନ୍ନ ଇନ୍ଜେସ୍ଟ ବାଇଟ୍ ବଜେଟ୍ର ଓଭରରାଇଡ୍ (ଅଧିକ କଠୋର V8/cgroup ମେମୋରି ସୀମାର 25%, 8x କ୍ଷଣସ୍ଥାୟୀ ବୃଦ୍ଧି ଦ୍ୱାରା ବିଭକ୍ତ)। ବ୍ୟୁତ୍ପନ୍ନ ଏବଂ ସ୍ପଷ୍ଟ ମୂଲ୍ୟଗୁଡ଼ିକ 8 MiB–2 GiB ମଧ୍ୟରେ ସୀମିତ ରହେ। କାର୍ଯ୍ୟକାରୀ ବଜେଟ୍ଠାରୁ ବଡ଼ ବଡି ତୁରନ୍ତ `413 body_exceeds_budget` ସହ ବିଫଳ ହୁଏ; ପୃଥକ ଭାବେ ସେବାଯୋଗ୍ୟ ବଡିଗୁଡ଼ିକ ମଧ୍ୟରେ ପ୍ରତିଦ୍ୱନ୍ଦ୍ୱିତା ପୁନଃଚେଷ୍ଟାଯୋଗ୍ୟ `503` ଭାବେ ରହେ। 40–50ଟି ସମସାମୟିକ ଦୀର୍ଘ SSE କ୍ଲାଏଣ୍ଟ ଏହି ବଜେଟ୍ + ହିପ୍ର ବିଷୟ, କୌଣସି କଠୋର “ସର୍ବାଧିକ 2” ନୁହେଁ। ଟ୍ୟୁନ୍ କରିବା ପୂର୍ବରୁ `/api/monitoring/health`ରେ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ପଢ଼ନ୍ତୁ। | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE ଏବଂ STRUCTURE ହେଭିୱେଟ୍ ଆଡ୍ମିଶନ୍ ପାଇଁ ହିପ୍-ଚାପ ଶେଡ୍ ଅନୁପାତ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ଅତିକ୍ରମ କରୁଥିବା ଏକ ସମସାମୟିକ ହେଭିୱେଟ୍ ଅନୁରୋଧକୁ କେବଳ ହିପ୍ଟି ମଧ୍ୟ ଏହି ଅନୁପାତରେ କିମ୍ବା ତାହାଠାରୁ ଅଧିକ ଥିଲେ ପୁନଃଚେଷ୍ଟାଯୋଗ୍ୟ `503` ସହ ଶେଡ୍ କରାଯାଏ; ଏକ ସୁସ୍ଥ ହିପ୍ରେ ଏହାକୁ healthy-headroom ମାଧ୍ୟମରେ ଗ୍ରହଣ କରାଯାଏ। | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ଡିଫଲ୍ଟ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **ଉଭୟ** STRUCTURE ଏବଂ BYTE (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ≥ ବଡି ସମେତ) ଉପରେ healthy-heap ଫାଷ୍ଟ ପାଥ୍ (#10437) ପାଇଁ ସୀମିତ ଅତିରିକ୍ତ କ୍ଷମତା। ଏହି ସୀମା ବିନା, ପ୍ରତ୍ୟେକ ବ୍ୟସ୍ତ-କିନ୍ତୁ-ସୁସ୍ଥ-ହିପ୍ ଅନୁରୋଧ କୌଣସି ସର୍ବୋଚ୍ଚ ସୀମା ବିନା ଆଡ୍ମିଶନ୍କୁ ବାଇପାସ୍ କରୁଥିଲା। healthy-heap ପାଥ୍ ମାଧ୍ୟମରେ ଏତିକି ସମସାମୟିକ ଲିଜ୍ ସକ୍ରିୟ ହେବା ପରେ, ପରବର୍ତ୍ତୀ ବ୍ୟସ୍ତ ଅନୁରୋଧଗୁଡ଼ିକ ବାସ୍ତବ ହିପ୍ ଚାପରେ ବ୍ୟବହୃତ ସେହି ଏକା ସୀମିତ-ପ୍ରତୀକ୍ଷା/ଶେଡ୍ ପାଥ୍କୁ ଯାଆନ୍ତି। `0` ବାଇପାସ୍କୁ ସମ୍ପୂର୍ଣ୍ଣ ଭାବେ ଅକ୍ଷମ କରେ। | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ବଡି ବାଇଟ୍ ସୀମାଠାରୁ କମ୍ ଥିଲେ ମଧ୍ୟ ଏକ ଚାଟ୍ ଅନୁରୋଧକୁ ହେଭିୱେଟ୍ ଭାବେ ବର୍ଗୀକୃତ କରୁଥିବା ସନ୍ଦେଶ ସଂଖ୍ୟା। | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ବଡି ବାଇଟ୍ ସୀମାଠାରୁ କମ୍ ଥିଲେ ମଧ୍ୟ ଏକ ଚାଟ୍ ଅନୁରୋଧକୁ ହେଭିୱେଟ୍ ଭାବେ ବର୍ଗୀକୃତ କରୁଥିବା ଟୁଲ୍ ସଂଖ୍ୟା। | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ଏକ ଅନୁରୋଧକୁ ହେଭିୱେଟ୍ ଭାବେ ବର୍ଗୀକୃତ କରୁଥିବା ରକ୍ଷଣଶୀଳ ଷ୍ଟ୍ରିଙ୍ଗ୍-ଆକାର ଟୋକନ୍ ଅନୁମାନ; ଏହା ଏକ ଆଡ୍ମିଶନ୍-ଖର୍ଚ୍ଚ ପ୍ରକ୍ସି, ପ୍ରଦାନକାରୀଙ୍କ ବିଲିଂ ଟୋକେନାଇଜେସନ୍ ନୁହେଁ। | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ଅକ୍ଷମ) | `src/shared/middleware/chatBodyAdmission.ts` | ଇଚ୍ଛାଧୀନ ଅପ୍ଟ-ଇନ୍ ଚାଟ୍ ଇତିହାସ ସୀମା। ଡିଫଲ୍ଟ ଭାବେ ଅକ୍ଷମ: ସନ୍ଦେଶ ସଂଖ୍ୟା ହେଉଛି ଡିପ୍ଲୟମେଣ୍ଟ ନୀତି, କୌଣସି ଅନୁରୋଧର ସାର୍ବଜନୀନ ଗୁଣ ନୁହେଁ; ଏବଂ ଏଠାରେ ସୀମା ଲଗାଇବା ଦ୍ୱାରା କମ୍ପ୍ରେସନ୍ ପାଇପ୍ଲାଇନ୍ ସେଗୁଡ଼ିକୁ ସେବାଯୋଗ୍ୟ କରିବା ପୂର୍ବରୁ ଟର୍ମିନାଲ୍ `413` ସହ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ପ୍ରତ୍ୟାଖ୍ୟାନ ହୁଏ। ହିପ୍ ବୃଦ୍ଧି `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ଏବଂ ହିପ୍-ଚାପ ଶେଡ୍ ଦ୍ୱାରା ସୀମିତ। କଠୋର ସର୍ବୋଚ୍ଚ ସୀମା ଆବଶ୍ୟକ କରୁଥିବା ମେମୋରି-ସୀମିତ ଡିପ୍ଲୟମେଣ୍ଟଗୁଡ଼ିକରେ ଏକ ଧନାତ୍ମକ ମୂଲ୍ୟ ସେଟ୍ କରନ୍ତୁ; ସୀମା ଅତିକ୍ରମ କଲେ ସଂରଚିତ compact-required `413` ମିଳେ। | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ମେମୋରିରେ ସମ୍ପୂର୍ଣ୍ଣ ଭାବେ ବଫର୍ ହୋଇଥିବା ଏକ ନନ୍-ଷ୍ଟ୍ରିମିଂ ଅପ୍ଷ୍ଟ୍ରିମ୍ ପ୍ରତିକ୍ରିୟା ପାଇଁ କଠୋର ସୀମା। ଏହା ଅତିକ୍ରମ କଲେ ଅପ୍ଷ୍ଟ୍ରିମ୍ ରିଡର୍ ବାତିଲ୍ ହୁଏ ଏବଂ ହିପ୍ ଶେଷ ହେବା ପର୍ଯ୍ୟନ୍ତ ଏକ ଅସୀମିତ ଷ୍ଟ୍ରିଙ୍ଗ୍ ବୃଦ୍ଧି କରିବା ପରିବର୍ତ୍ତେ ଅନୁରୋଧଟି ତୁରନ୍ତ ବିଫଳ ହୁଏ। | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ଅପ୍ଷ୍ଟ୍ରିମ୍ ପ୍ରତିକ୍ରିୟା ହେଡର୍ଗୁଡ଼ିକରୁ ଫରୱାର୍ଡ କରାଯାଉଥିବା ସର୍ବାଧିକ ୱାୟାର୍ ବାଇଟ୍। ବଜେଟ୍ ଅତିକ୍ରମ ହେଲେ, ସାଧାରଣ ରିଭର୍ସ-ପ୍ରକ୍ସି ହେଡର୍ ସୀମା ମଧ୍ୟରେ ରହିବା ପାଇଁ ନିମ୍ନ-ପ୍ରାଥମିକତା ହେଡର୍ଗୁଡ଼ିକୁ (ଯଥା, କଷ୍ଟମ୍ `x-codex-*`, `x-oai-request-id`) ବାଦ ଦିଆଯାଏ। ବଡ଼ ପ୍ରତିକ୍ରିୟା ହେଡର୍ ଆକାର ବଦଳରେ ଅଧିକ ଅପ୍ଷ୍ଟ୍ରିମ୍ ମେଟାଡାଟା ଫରୱାର୍ଡ କରିବାକୁ ଏହାକୁ ଅଧିକ ମୂଲ୍ୟରେ ସେଟ୍ କରନ୍ତୁ। | +| `CORS_ORIGIN` | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `src/server/cors/origins.ts` | ପୁରୁଣା ଏକକ-ଅରିଜିନ୍ CORS ଅନୁମୋଦନ ତାଲିକା। ନୂଆ ଡିପ୍ଲୟମେଣ୍ଟଗୁଡ଼ିକ ପାଇଁ `CORS_ALLOWED_ORIGINS`କୁ ପ୍ରାଧାନ୍ୟ ଦିଅନ୍ତୁ। CORS କେବଳ କ୍ରସ୍-ଅରିଜିନ୍ ବ୍ରାଉଜର୍ API କ୍ଲାଏଣ୍ଟଗୁଡ଼ିକ ପାଇଁ; ଅଧିକୃତ ଡ୍ୟାସ୍ବୋର୍ଡ ଲେଖନଗୁଡ଼ିକ ଏହା ବଦଳରେ ସମାନ-ଅରିଜିନ୍ ଅନୁରୋଧ ସହିତ ସେସନ୍-ବାଉଣ୍ଡ CSRF ସୁରକ୍ଷା ବ୍ୟବହାର କରେ। | +| `CORS_ALLOWED_ORIGINS` | _(ସେଟ୍ ହୋଇନାହିଁ)_ | `src/server/cors/origins.ts` | କମା ଦ୍ୱାରା ପୃଥକ କରାଯାଇଥିବା CORS ଅନୁମୋଦନ ତାଲିକା। `CORS_ALLOW_ALL=true`କୁ ସ୍ପଷ୍ଟ ଭାବେ ବିନ୍ୟାସ ନ କରାଯିବା ପର୍ଯ୍ୟନ୍ତ କୌଣସି ୱାଇଲ୍ଡକାର୍ଡ ପଠାଯାଏ ନାହିଁ। | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ଯେକୌଣସି ବ୍ରାଉଜର୍ `Origin`କୁ ପ୍ରତିଧ୍ୱନିତ କରିବା ପାଇଁ କେବଳ ଡେଭଲପମେଣ୍ଟ ନିମନ୍ତେ ଉଦ୍ଦିଷ୍ଟ ଏକ ଏସ୍କେପ୍ ବ୍ୟବସ୍ଥା। ଅଂଶୀଦାର କିମ୍ବା ପ୍ରଡକ୍ସନ୍ ଡିପ୍ଲୟମେଣ୍ଟରେ ଏହାକୁ ସକ୍ଷମ କରନ୍ତୁ ନାହିଁ। | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ପୁରୁଣା ଉପନାମ। ଏହାର ଡ୍ୟାସ୍ବୋର୍ଡ ଟୋଗଲ୍ (ଏକ DB ଓଭରରାଇଡ୍) ଏହି ଏନଭାଇରନମେଣ୍ଟ ମୂଲ୍ୟ ପୂର୍ବରୁ ପଢ଼ାଯାଏ; ଯେକୌଣସି ଗୋଟିଏରେ `false`, `0`, `no` କିମ୍ବା `off` ଥିଲେ — ସେହି ଫ୍ଲାଗର ଡ୍ୟାସ୍ବୋର୍ଡ ଟୋଗଲ୍ ବନ୍ଦ ଭାବେ ସଂରକ୍ଷିତ ଥିଲେ ମଧ୍ୟ — ହୋଷ୍ଟ ଯାଞ୍ଚଗୁଡ଼ିକୁ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ପରି ବନ୍ଦ କରିଦିଏ; ଅନ୍ୟ ମୂଲ୍ୟଗୁଡ଼ିକ ଗାର୍ଡକୁ ତଳେ ଥିବା ଦୁଇ ଫ୍ଲାଗର ହାତରେ ଛାଡ଼ିଦିଏ। | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | ଗାର୍ଡ ମୋଡ୍ ଅନୁସରଣ କରୁଥିବା ପ୍ରଦାନକାରୀ ପଥଗୁଡ଼ିକରେ — URL ବୈଧତା ଯାଞ୍ଚ, ମଡେଲ୍ ଆବିଷ୍କାର, ପ୍ରଦାନକାରୀ-ନୋଡ୍ ବେସ୍ URL (ରିମୋଟ୍ ପୁନଃ-ରାଙ୍କିଂ ନୋଡ୍ ସହିତ), ପ୍ରକ୍ସି-ଫଲବ୍ୟାକ୍ ପରୀକ୍ଷଣ ଏବଂ ରିମୋଟ୍ ଇମେଜ୍/ମିଡିଆ ଫେଚ୍ ଭଳି ଅନ୍ୟ ପ୍ରଦାନକାରୀ-ସମ୍ପର୍କିତ ଆଉଟ୍ବାଉଣ୍ଡ ପଥଗୁଡ଼ିକରେ — ଆଉଟ୍ବାଉଣ୍ଡ URL ଗାର୍ଡର ହୋଷ୍ଟ ଯାଞ୍ଚଗୁଡ଼ିକୁ, କ୍ଲାଉଡ୍-ମେଟାଡାଟା ଅବରୋଧ ସହିତ (ସମ୍ପୂର୍ଣ୍ଣ 169.254.0.0/16, metadata.google.internal), ବନ୍ଦ କରିଦିଏ। କ୍ଲାଉଡ୍-ମେଟାଡାଟା/ଲିଙ୍କ୍-ଲୋକାଲ୍ ଏଣ୍ଡପଏଣ୍ଟ ବାଦ୍, ଏହା ପ୍ରାଇଭେଟ୍ webhook ଲକ୍ଷ୍ୟସ୍ଥଳକୁ ମଧ୍ୟ ଅନୁମତି ଦିଏ — ଯାହା ଏହି ଫ୍ଲାଗ୍ ଚାଲୁ ଥିଲେ ମଧ୍ୟ ଅବରୋଧିତ ରହେ। `BaseExecutor.execute()` ମାଧ୍ୟମରେ ପଠାଯାଉଥିବା ଚାଟ୍ ଅନୁରୋଧଗୁଡ଼ିକର କ୍ଲାଉଡ୍-ମେଟାଡାଟା ଯାଞ୍ଚ ବଜାୟ ରହେ। ଲୋକାଲ୍ ପ୍ରଦାନକାରୀଗୁଡ଼ିକ ଡିଫଲ୍ଟ ସହିତ କାର୍ଯ୍ୟ କରନ୍ତି: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ଡିଫଲ୍ଟ `true`) ବୈଧତା, ଆବିଷ୍କାର ଏବଂ ପ୍ରଦାନକାରୀ-ନୋଡ୍ ପଥରେ ଲୋକାଲ୍ ଏବଂ LAN URL ପୂର୍ବରୁ ଅନୁମତି ଦେଇଛି; ପ୍ରକ୍ସି-ଫଲବ୍ୟାକ୍ ପରୀକ୍ଷଣ ଏବଂ ପ୍ରାଇଭେଟ୍ webhook ଲକ୍ଷ୍ୟସ୍ଥଳ କେବଳ ଏହି ଫ୍ଲାଗକୁ ଦେଖନ୍ତି ଏବଂ ଏହା ବନ୍ଦ ଥିବା ପର୍ଯ୍ୟନ୍ତ ଲୋକାଲ୍/LAN ହୋଷ୍ଟକୁ ଅବରୋଧ କରନ୍ତି। `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`କୁ ଓଭରରାଇଡ୍ କରେ। ଡ୍ୟାସ୍ବୋର୍ଡ ଟୋଗଲରୁ ସଂରକ୍ଷିତ ମୂଲ୍ୟ ଏହି ଭେରିଏବଲ୍ ଅପେକ୍ଷା ଅଗ୍ରାଧିକାର ପାଏ। | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ପ୍ରଦାନକାରୀ ବୈଧତା ଯାଞ୍ଚ, ମଡେଲ୍ ଆବିଷ୍କାର, ଚାଟ୍ ଏବଂ ଗାର୍ଡ ମୋଡ୍ ନିୟନ୍ତ୍ରଣ କରୁଥିବା ଅନ୍ୟ ପ୍ରଦାନକାରୀ-ସମ୍ପର୍କିତ ଆଉଟ୍ବାଉଣ୍ଡ ପଥଗୁଡ଼ିକ ପାଇଁ ଲୋକାଲ୍/ପ୍ରାଇଭେଟ୍ ଠିକଣାରେ (127.0.0.1, localhost, LAN, ପ୍ରାଇଭେଟ୍ ରେଞ୍ଜ) ଥିବା ପ୍ରଦାନକାରୀ URLଗୁଡ଼ିକୁ ଅନୁମତି ଦିଏ। **ଡିଫଲ୍ଟ `true`** (ଲୋକାଲ୍-ପ୍ରଥମ): ସେତେବେଳେ ଗାର୍ଡ କ୍ଲାଉଡ୍-ମେଟାଡାଟା ଏଣ୍ଡପଏଣ୍ଟଗୁଡ଼ିକୁ — ସମ୍ପୂର୍ଣ୍ଣ 169.254.0.0/16 ସହିତ ଜଣାଶୁଣା ମେଟାଡାଟା ହୋଷ୍ଟନାମ (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) — ଅବରୋଧ କରେ। ପ୍ରାଇଭେଟ୍ ଏବଂ ଲୁପ୍ବ୍ୟାକ୍ ହୋଷ୍ଟକୁ ମଧ୍ୟ ଅବରୋଧ କରୁଥିବା କେବଳ-ପବ୍ଲିକ୍ ମୋଡ୍ ପାଇଁ `false` ସେଟ୍ କରନ୍ତୁ। ଯେକୌଣସି DNS ଲୁକଅପ୍ ପୂର୍ବରୁ ଗାର୍ଡ ହୋଷ୍ଟନାମ କିମ୍ବା IP ଲିଟରାଲକୁ ଲେଖାଯିବା ପରି ଯାଞ୍ଚ କରେ; କେବଳ-ପବ୍ଲିକ୍ ମୋଡରେ safe-fetch ର୍ୟାପର ଅତିରିକ୍ତ ଭାବେ ହୋଷ୍ଟନାମକୁ ରିଜଲ୍ଭ କରେ ଏବଂ ଯେକୌଣସି ପ୍ରାଇଭେଟ୍ ଉତ୍ତର ପାଇଲେ ପ୍ରତ୍ୟାଖ୍ୟାନ କରେ। `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ଏହାର ଧାଡ଼ିରେ ତାଲିକାଭୁକ୍ତ ପଥଗୁଡ଼ିକରେ ଏହି ଯାଞ୍ଚଗୁଡ଼ିକୁ ବନ୍ଦ କରିଦିଏ। LM Studio, Ollama, vLLM, Llamafile ଏବଂ Triton ଭଳି ବିଲ୍ଟ-ଇନ୍ ଲୋକାଲ୍ ପ୍ରଦାନକାରୀଗୁଡ଼ିକ କୀ ବୈଧତା ଯାଞ୍ଚ ଏବଂ ଚାଟ୍ ପାଇଁ ସମସ୍ତ ହୋଷ୍ଟ ଯାଞ୍ଚ ଛାଡ଼ିଦିଅନ୍ତି। ଡ୍ୟାସ୍ବୋର୍ଡ ଟୋଗଲରୁ ସଂରକ୍ଷିତ ମୂଲ୍ୟ ଏହି ଭେରିଏବଲ୍ ଅପେକ୍ଷା ଅଗ୍ରାଧିକାର ପାଏ। (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ରୁଟ୍ଗୁଡ଼ିକୁ (ଟ୍ରାନ୍ସକ୍ରିପ୍ସନ୍, ସ୍ପିଚ୍, ଟ୍ରାନ୍ସଲେସନ୍) localhost ବାହାରେ ହୋଷ୍ଟ କରାଯାଇଥିବା OpenAI-ସୁସଙ୍ଗତ ପ୍ରଦାନକାରୀ ନୋଡ୍ ବ୍ୟବହାର କରିବାକୁ ଦିଅନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ — ଏକ ରିମୋଟ୍ ହୋଷ୍ଟକୁ ଅଡିଓ ରୁଟ୍ କରିବା ଇଗ୍ରେସ୍ ପରିଚୟ ପରିବର୍ତ୍ତନ କରେ ଏବଂ ଏହା ଏକ ସ୍ପଷ୍ଟ ଅପରେଟର୍ ନିଷ୍ପତ୍ତି ହେବା ଆବଶ୍ୟକ। ଲୁପ୍ବ୍ୟାକ୍/ପ୍ରାଇଭେଟ୍ ନୋଡ୍ଗୁଡ଼ିକ (localhost, 127.0.0.1, 172.16-31.x) ସର୍ବଦା ଅନୁମୋଦିତ ଏବଂ ଅପ୍ରଭାବିତ। (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank`କୁ (ଏବଂ ମେମୋରି ଇଞ୍ଜିନ୍ର ଲୁପ୍ବ୍ୟାକ୍ ପୁନଃ-ରାଙ୍କିଂ ପଦକ୍ଷେପକୁ) localhost ବାହାରେ ହୋଷ୍ଟ କରାଯାଇଥିବା OpenAI-ସୁସଙ୍ଗତ ପ୍ରଦାନକାରୀ ନୋଡ୍—ଯଥା TEI, Infinity, vLLM ଇତ୍ୟାଦି ଚଳାଉଥିବା ଏକ LAN ବକ୍ସ କିମ୍ବା Tailscale ପିୟର୍—ବ୍ୟବହାର କରିବାକୁ ଦିଅନ୍ତୁ। ଡିଫଲ୍ଟ ଭାବେ ବନ୍ଦ — ଏକ ରିମୋଟ୍ ହୋଷ୍ଟକୁ ରୁଟ୍ କରିବା ଇଗ୍ରେସ୍ ପରିଚୟ ପରିବର୍ତ୍ତନ କରେ ଏବଂ ଏହା ଏକ ସ୍ପଷ୍ଟ ଅପରେଟର୍ ନିଷ୍ପତ୍ତି ହେବା ଆବଶ୍ୟକ। ଲୁପ୍ବ୍ୟାକ୍ ନୋଡ୍ଗୁଡ଼ିକ (localhost, 127.0.0.1, 172.16-31.x) ସର୍ବଦା ଅନୁମୋଦିତ ଏବଂ ଅପ୍ରଭାବିତ। ଏକ ରିମୋଟ୍ ନୋଡର ବେସ୍ URL ମଧ୍ୟ ପ୍ରଦାନକାରୀଙ୍କ ଆଉଟ୍ବାଉଣ୍ଡ URL ନୀତି (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ପାର୍ କରିବାକୁ ପଡ଼ିବ। | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ସକ୍ଷମ ଥିବାବେଳେ, ପାସୱାର୍ଡ ଲଗ୍ଇନ୍ ଅକ୍ଷମ କରନ୍ତୁ, ଯାହାଦ୍ୱାରା ବ୍ୟବହାରକାରୀମାନେ କେବଳ OIDC Single Sign-On ମାଧ୍ୟମରେ ପ୍ରମାଣୀକରଣ କରିପାରିବେ। ସରଳ ଉପନାମ `OIDC_DISABLE_PASSWORD_LOGIN` ମଧ୍ୟ ଗ୍ରହଣୀୟ; ସମାନ କି ଥିବା Dashboard Feature Flagକୁ ପ୍ରାଥମିକତା ଦିଆଯାଏ। (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`ର ସରଳ ଉପନାମ (#10889)। | ### ସୁରକ୍ଷା ସୁଦୃଢ଼ୀକରଣ ଯାଞ୍ଚତାଲିକା diff --git a/docs/i18n/pa/CONTRIBUTING.md b/docs/i18n/pa/CONTRIBUTING.md index ff65e320f219..23856cd3f3bf 100644 --- a/docs/i18n/pa/CONTRIBUTING.md +++ b/docs/i18n/pa/CONTRIBUTING.md @@ -392,22 +392,22 @@ docs/ --- -## ਪੁੱਲ ਰਿਕਵੈਸਟ ਚੈੱਕਲਿਸਟ +## ਪੁੱਲ ਰਿਕਵੇਸਟ ਚੈੱਕਲਿਸਟ - [ ] ਟੈਸਟ ਪਾਸ ਹੁੰਦੇ ਹਨ (`npm test`) - [ ] ਲਿੰਟਿੰਗ ਪਾਸ ਹੁੰਦੀ ਹੈ (`npm run lint`) - [ ] ਬਿਲਡ ਸਫਲ ਹੁੰਦਾ ਹੈ (`npm run build`) -- [ ] ਨਵੇਂ ਜਨਤਕ ਫੰਕਸ਼ਨਾਂ ਅਤੇ ਇੰਟਰਫੇਸਾਂ ਲਈ TypeScript ਟਾਈਪ ਸ਼ਾਮਲ ਕੀਤੇ ਗਏ ਹਨ -- [ ] ਕੋਈ ਹਾਰਡਕੋਡ ਕੀਤਾ ਸੀਕ੍ਰੇਟ ਜਾਂ ਫਾਲਬੈਕ ਮੁੱਲ ਨਹੀਂ ਹੈ -- [ ] ਜਨਤਕ ਅੱਪਸਟ੍ਰੀਮ ਕ੍ਰੈਡੈਂਸ਼ੀਅਲ `resolvePublicCred()` ਰਾਹੀਂ ਏਮਬੈੱਡ ਕੀਤੇ ਗਏ ਹਨ ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ਵੇਖੋ), ਕਦੇ ਵੀ ਲਿਟਰਲ ਵਜੋਂ ਨਹੀਂ -- [ ] ਤਰੁੱਟੀ ਜਵਾਬ `buildErrorBody()` / `sanitizeErrorMessage()` ਰਾਹੀਂ ਜਾਂਦੇ ਹਨ — ਜਵਾਬ ਬਾਡੀਆਂ ਵਿੱਚ ਕੋਈ ਕੱਚਾ ਸਟੈਕ ਟਰੇਸ ਨਹੀਂ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ਵੇਖੋ) -- [ ] ਸ਼ੈੱਲ ਕਮਾਂਡਾਂ (`exec` / `spawn`) ਰਨਟਾਈਮ ਮੁੱਲਾਂ ਨੂੰ ਸਟਰਿੰਗ ਇੰਟਰਪੋਲੇਸ਼ਨ ਰਾਹੀਂ ਨਹੀਂ, ਸਗੋਂ `env` ਰਾਹੀਂ ਪਾਸ ਕਰਦੀਆਂ ਹਨ +- [ ] ਨਵੇਂ ਪਬਲਿਕ ਫੰਕਸ਼ਨਾਂ ਅਤੇ ਇੰਟਰਫੇਸਾਂ ਲਈ TypeScript ਟਾਈਪਾਂ ਜੋੜੀਆਂ ਗਈਆਂ ਹਨ +- [ ] ਕੋਈ ਹਾਰਡਕੋਡ ਕੀਤੇ ਸੀਕ੍ਰੇਟ ਜਾਂ ਫਾਲਬੈਕ ਮੁੱਲ ਨਹੀਂ ਹਨ +- [ ] ਪਬਲਿਕ ਅੱਪਸਟ੍ਰੀਮ ਕ੍ਰੈਡੈਂਸ਼ਲ `resolvePublicCred()` ਰਾਹੀਂ ਐਂਬੈੱਡ ਕੀਤੇ ਗਏ ਹਨ ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ਵੇਖੋ), ਕਦੇ ਵੀ ਲਿਟਰਲ ਵਜੋਂ ਨਹੀਂ +- [ ] ਗਲਤੀ ਜਵਾਬ `buildErrorBody()` / `sanitizeErrorMessage()` ਰਾਹੀਂ ਭੇਜੇ ਜਾਂਦੇ ਹਨ — ਜਵਾਬ ਬਾਡੀਆਂ ਵਿੱਚ ਕੋਈ ਕੱਚਾ ਸਟੈਕ ਟ੍ਰੇਸ ਨਹੀਂ ਹੈ ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ਵੇਖੋ) +- [ ] ਸ਼ੈੱਲ ਕਮਾਂਡਾਂ (`exec` / `spawn`) ਰਨਟਾਈਮ ਮੁੱਲ ਸਟ੍ਰਿੰਗ ਇੰਟਰਪੋਲੇਸ਼ਨ ਰਾਹੀਂ ਨਹੀਂ, ਸਗੋਂ `env` ਰਾਹੀਂ ਪਾਸ ਕਰਦੀਆਂ ਹਨ - [ ] ਸਾਰੇ ਇਨਪੁੱਟ Zod ਸਕੀਮਿਆਂ ਨਾਲ ਪ੍ਰਮਾਣਿਤ ਕੀਤੇ ਗਏ ਹਨ -- [ ] ਉਪਭੋਗਤਾ-ਸਾਹਮਣੇ ਵਾਲੀਆਂ ਤਬਦੀਲੀਆਂ ਲਈ `changelog.d/{features|fixes|maintenance}/-.md` ਅਧੀਨ ਚੇਂਜਲੌਗ **ਫ੍ਰੈਗਮੈਂਟ** ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ ([`changelog.d/README.md`](./changelog.d/README.md) ਵੇਖੋ) — `CHANGELOG.md` ਨੂੰ ਸਿੱਧਾ ਸੰਪਾਦਿਤ **ਨਾ** ਕਰੋ; ਫ੍ਰੈਗਮੈਂਟ ਰਿਲੀਜ਼ ਵੇਲੇ ਇਕੱਠੇ ਕੀਤੇ ਜਾਂਦੇ ਹਨ ਅਤੇ PRs ਵਿਚਕਾਰ ਕਦੇ ਟਕਰਾਅ ਨਹੀਂ ਕਰਦੇ +- [ ] ਵਰਤੋਂਕਾਰਾਂ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕਰਨ ਵਾਲੀਆਂ ਤਬਦੀਲੀਆਂ ਲਈ `changelog.d/{features|fixes|maintenance}/-.md` ਅਧੀਨ ਚੇਂਜਲੌਗ **ਫ੍ਰੈਗਮੈਂਟ** ਜੋੜਿਆ ਗਿਆ ਹੈ ([`changelog.d/README.md`](./changelog.d/README.md) ਵੇਖੋ) — `CHANGELOG.md` ਨੂੰ ਸਿੱਧੇ ਤੌਰ 'ਤੇ ਸੰਪਾਦਿਤ **ਨਾ** ਕਰੋ; ਫ੍ਰੈਗਮੈਂਟ ਰਿਲੀਜ਼ ਸਮੇਂ ਇਕੱਠੇ ਕੀਤੇ ਜਾਂਦੇ ਹਨ ਅਤੇ PRs ਵਿਚਕਾਰ ਕਦੇ ਟਕਰਾਅ ਨਹੀਂ ਕਰਦੇ - [ ] ਦਸਤਾਵੇਜ਼ ਅੱਪਡੇਟ ਕੀਤੇ ਗਏ ਹਨ (ਜੇ ਲਾਗੂ ਹੋਵੇ) -- [ ] ਕੋਈ ਨਵੀਂ CodeQL / Secret-Scanning ਚੇਤਾਵਨੀ ਨਹੀਂ ਖੋਲ੍ਹੀ ਗਈ, ਜਾਂ ਹਰੇਕ ਨੂੰ ਸੰਬੰਧਿਤ `docs/security/` ਦਸਤਾਵੇਜ਼ ਦਾ ਹਵਾਲਾ ਦਿੰਦੇ ਤਕਨੀਕੀ ਜਾਇਜ਼ੇ ਨਾਲ ਖਾਰਜ ਕੀਤਾ ਗਿਆ ਹੈ -- [ ] ਚਾਈਲਡ ਪ੍ਰੋਸੈਸ ਸ਼ੁਰੂ ਕਰਨ ਵਾਲੇ ਰੂਟ (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` ਵਿੱਚ `isLocalOnlyPath()` ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਹਨ — [ਸਖ਼ਤ ਨਿਯਮ #15](docs/security/ROUTE_GUARD_TIERS.md) ਵੇਖੋ -- [ ] ਕਮਿਟ ਸੁਨੇਹਿਆਂ ਵਿੱਚ ਕੋਈ `Co-Authored-By` ਟ੍ਰੇਲਰ ਨਹੀਂ — ਕਮਿਟ ਸਿਰਫ਼ ਰਿਪੋਜ਼ਟਰੀ ਮਾਲਕ ਦੀ Git ਪਛਾਣ ਹੇਠ ਦਿਖਾਈ ਦੇਣੇ ਚਾਹੀਦੇ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #16) +- [ ] ਕੋਈ ਨਵੀਂ CodeQL / Secret-Scanning ਚੇਤਾਵਨੀ ਨਹੀਂ ਖੋਲ੍ਹੀ ਗਈ, ਜਾਂ ਹਰ ਇੱਕ ਨੂੰ ਸੰਬੰਧਿਤ `docs/security/` ਦਸਤਾਵੇਜ਼ ਦਾ ਹਵਾਲਾ ਦਿੰਦੇ ਤਕਨੀਕੀ ਤਰਕ ਨਾਲ ਖਾਰਜ ਕੀਤਾ ਗਿਆ ਹੈ +- [ ] ਚਾਈਲਡ ਪ੍ਰੋਸੈਸ ਸ਼ੁਰੂ ਕਰਨ ਵਾਲੇ ਰੂਟ (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` ਵਿੱਚ `isLocalOnlyPath()` ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਕੀਤੇ ਗਏ ਹਨ — [ਸਖ਼ਤ ਨਿਯਮ #15](docs/security/ROUTE_GUARD_TIERS.md) ਵੇਖੋ +- [ ] ਕਮਿਟ ਸੁਨੇਹਿਆਂ ਵਿੱਚ ਕੋਈ AI/ਬੌਟ `Co-authored-by` ਟ੍ਰੇਲਰ ਨਹੀਂ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #16) — ਜਿਨ੍ਹਾਂ ਮਨੁੱਖੀ ਸਹਿਯੋਗੀਆਂ ਦੇ ਕੰਮ ਦੀ ਮੁੜ ਵਰਤੋਂ ਕੀਤੀ ਗਈ ਹੈ, ਉਨ੍ਹਾਂ ਨੂੰ ਮਿਆਰੀ `Co-authored-by: Name ` ਟ੍ਰੇਲਰਾਂ ਨਾਲ ਸਿਹਰਾ ਦਿੱਤਾ ਗਿਆ ਹੈ --- diff --git a/docs/i18n/pa/docs/architecture/QUALITY_GATES.md b/docs/i18n/pa/docs/architecture/QUALITY_GATES.md index 1072775968b3..71bc0a9dc04c 100644 --- a/docs/i18n/pa/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/pa/docs/architecture/QUALITY_GATES.md @@ -17,213 +17,207 @@ ## ਗੇਟ ਇਨਵੈਂਟਰੀ ਅਤੇ ਐਗਜ਼ਿਕਿਊਸ਼ਨ ਪ੍ਰੋਫ਼ਾਈਲ -### ਉਮੀਦਵਾਰ ਦਾਖਲਾ +### ਉਮੀਦਵਾਰ ਦੀ ਸਵੀਕ੍ਰਿਤੀ CI ਅਤੇ Quality Gates ਵਰਕਫ਼ਲੋ ਹਰ ਇੱਕ ਸਥਿਰ ਫ਼ੈਸਲਾ ਜਾਰੀ ਕਰਦੇ ਹਨ: `Gate / CI` ਅਤੇ -`Gate / Quality`। ਉਨ੍ਹਾਂ ਦੀ ਵਰਜ਼ਨ-ਨਿਯੰਤਰਿਤ ਦਾਖਲਾ ਨੀਤੀ ਹਰ ਅੱਪਸਟ੍ਰੀਮ ਜੌਬ ਨੂੰ -ਲਾਜ਼ਮੀ ਜਾਂ ਸਲਾਹਕਾਰੀ ਵਜੋਂ ਸੂਚੀਬੱਧ ਕਰਦੀ ਹੈ। ਲਾਗੂ ਹੋਣ ਵਾਲੀ ਲਾਜ਼ਮੀ ਜੌਬ ਦਾ ਸਫਲ ਹੋਣਾ -ਜ਼ਰੂਰੀ ਹੈ: ਗੁੰਮ, ਰੱਦ, ਛੱਡੇ ਗਏ, ਬਕਾਇਆ ਅਤੇ ਅਣਜਾਣ ਨਤੀਜੇ PASS ਸਥਾਪਤ ਨਹੀਂ ਕਰ ਸਕਦੇ। -ਇੱਕ ਵੈਧ ਸਿਰਫ਼-ਡੌਕਸ ਜਾਂ ਸਿਰਫ਼-ਕੈਟਾਲੌਗ ਵਰਗੀਕਰਨ ਕੋਡ ਲੇਨ ਨੂੰ ਗੈਰ-ਲਾਗੂ ਬਣਾ ਸਕਦਾ ਹੈ; -ਡਰਾਫਟ PR ਨੂੰ ਸਵੀਕਾਰਿਆ ਉਮੀਦਵਾਰ ਨਹੀਂ ਮੰਨਿਆ ਜਾਂਦਾ। `hotfix` ਲੇਬਲ ਸਬੂਤ ਦੀ ਲੋੜ ਨੂੰ -ਮੁਆਫ਼ ਨਹੀਂ ਕਰਦਾ। - -ਦੋਵੇਂ ਵਰਕਫ਼ਲੋ PRs ਅਤੇ main/release ਬ੍ਰਾਂਚਾਂ ਵੱਲ ਪੁਸ਼, ਮੈਨੁਅਲ ਡਿਸਪੈਚ ਅਤੇ -ਮਰਜ-ਗਰੁੱਪ ਇਵੈਂਟਾਂ ਨੂੰ ਕਵਰ ਕਰਦੇ ਹਨ। ਪੁਸ਼, ਡਿਸਪੈਚ ਅਤੇ ਮਰਜ-ਗਰੁੱਪ ਪੂਰੀ ਚੋਣ ਚਲਾਉਂਦੇ ਹਨ। -ਫੋਰਕ ਅਤੇ ਮਰਜ ਗਰੁੱਪ ਉਹਨਾਂ ਜੌਬਾਂ ਲਈ ਹੋਸਟਡ ਰਨਰ ਵਰਤਦੇ ਹਨ ਜੋ ਨਹੀਂ ਤਾਂ ਸੈਲਫ਼-ਹੋਸਟਡ -ਰਨਰ ਚੁਣਦੀਆਂ; ਰੋਲਆਉਟ ਤੋਂ ਪਹਿਲਾਂ ਲੋੜੀਂਦੀ ਹੋਸਟਡ ਸਮਰੱਥਾ ਦੀ ਪੁਸ਼ਟੀ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ। - -ਹਰ JSON ਰਸੀਦ ਚੈੱਕ-ਆਉਟ ਕੀਤੇ SHA, ਵਰਕਫ਼ਲੋ ਰਨ ਅਤੇ ਕੋਸ਼ਿਸ਼ ਦੀ ਪਛਾਣ ਕਰਦੀ ਹੈ। -CLI ਚੈੱਕਆਉਟ/ਇਵੈਂਟ SHA ਬੇਮੇਲ ਨੂੰ ਰੱਦ ਕਰਦਾ ਹੈ। ਵਰਕਫ਼ਲੋ ਟੈਸਟ ਨੀਤੀ ਮੈਂਬਰਸ਼ਿਪ ਨੂੰ -ਫ਼ੈਸਲਾ ਜੌਬ ਦੀ `needs` ਸੂਚੀ ਨਾਲ ਬੰਨ੍ਹਦੇ ਹਨ, ਤਾਂ ਜੋ ਕੋਈ ਨਵੀਂ ਜਾਂ ਹਟਾਈ ਗਈ ਲੇਨ -ਚੁੱਪਚਾਪ ਗਾਇਬ ਨਾ ਹੋ ਸਕੇ। ਰਸੀਦਾਂ ਆਪਣੇ ਵਰਕਫ਼ਲੋ ਨੂੰ ਕਵਰ ਕਰਦੀਆਂ ਹਨ, ਪ੍ਰਕਾਸ਼ਨ, -ਡਿਪਲੌਇਮੈਂਟ ਜਾਂ ਕਿਸੇ ਮੌਜੂਦਾ ਸਲਾਹਕਾਰੀ ਸਕੈਨਰ ਦੇ ਅੰਦਰੂਨੀ ਹਿੱਸਿਆਂ ਨੂੰ ਨਹੀਂ। ਬ੍ਰਾਂਚ -ਨਿਯਮਾਂ ਵਿੱਚ ਦੋਵੇਂ ਚੈੱਕ ਨਾਮ ਸਰਗਰਮ ਕਰਨਾ ਇੱਕ ਵੱਖਰਾ ਪ੍ਰਸ਼ਾਸਕੀ ਬਦਲਾਅ ਹੈ; ਇਹ ਜੌਬਾਂ -ਜੋੜਨਾ ਆਪਣੇ ਆਪ ਕਿਸੇ ਬ੍ਰਾਂਚ ਦੀ ਸੁਰੱਖਿਆ ਨਹੀਂ ਕਰਦਾ। +`Gate / Quality`। ਉਨ੍ਹਾਂ ਦੀ ਵਰਜਨਬੱਧ ਸਵੀਕ੍ਰਿਤੀ ਨੀਤੀ ਹਰੇਕ ਅੱਪਸਟ੍ਰੀਮ ਜੌਬ ਨੂੰ +ਲਾਜ਼ਮੀ ਜਾਂ ਸਲਾਹਕਾਰੀ ਵਜੋਂ ਸੂਚੀਬੱਧ ਕਰਦੀ ਹੈ। ਲਾਗੂ ਹੋਣ ਵਾਲਾ ਲਾਜ਼ਮੀ ਜੌਬ ਸਫਲ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ: +ਗੈਰਹਾਜ਼ਰ, ਰੱਦ, ਛੱਡੇ ਗਏ, ਬਕਾਇਆ ਅਤੇ ਅਣਜਾਣ ਨਤੀਜੇ PASS ਸਥਾਪਤ ਨਹੀਂ ਕਰ ਸਕਦੇ। ਇੱਕ ਵੈਧ +ਕੇਵਲ-ਦਸਤਾਵੇਜ਼ ਜਾਂ ਕੇਵਲ-ਕੈਟਾਲੌਗ ਵਰਗੀਕਰਨ ਕਿਸੇ ਕੋਡ ਲੇਨ ਨੂੰ ਗੈਰ-ਲਾਗੂ ਬਣਾ ਸਕਦਾ ਹੈ; +ਡਰਾਫਟ PR ਸਵੀਕਾਰਿਆ ਉਮੀਦਵਾਰ ਨਹੀਂ ਹੈ। `hotfix` ਲੇਬਲ ਸਬੂਤ ਦੀ ਲੋੜ ਨੂੰ ਮੁਆਫ਼ ਨਹੀਂ ਕਰਦਾ। + +ਦੋਵੇਂ ਵਰਕਫ਼ਲੋ PRs ਅਤੇ main/release ਬ੍ਰਾਂਚਾਂ ਵੱਲ ਪੁਸ਼, ਮੈਨੂਅਲ ਡਿਸਪੈਚ ਅਤੇ +merge-group ਇਵੈਂਟਾਂ ਨੂੰ ਕਵਰ ਕਰਦੇ ਹਨ। Push, dispatch ਅਤੇ merge-group ਪੂਰੀ ਚੋਣ ਚਲਾਉਂਦੇ ਹਨ। Forks +ਅਤੇ merge groups ਉਹਨਾਂ ਜੌਬਾਂ ਲਈ hosted runners ਵਰਤਦੇ ਹਨ ਜੋ ਹੋਰਥਾਂ self-hosted +runners ਚੁਣਦੇ ਹਨ; ਰੋਲਆਉਟ ਤੋਂ ਪਹਿਲਾਂ hosted ਸਮਰੱਥਾ ਦੀ ਯਥੇਸ਼ਟਤਾ ਦੀ ਪੁਸ਼ਟੀ ਕੀਤੀ ਜਾਣੀ ਚਾਹੀਦੀ ਹੈ। + +ਹਰੇਕ JSON ਰਸੀਦ ਚੈੱਕ-ਆਉਟ ਕੀਤੇ SHA, ਵਰਕਫ਼ਲੋ ਰਨ ਅਤੇ ਕੋਸ਼ਿਸ਼ ਦੀ ਪਛਾਣ ਕਰਦੀ ਹੈ। +CLI, checkout/event SHA ਦੇ ਬੇਮੇਲ ਨੂੰ ਅਸਵੀਕਾਰ ਕਰਦਾ ਹੈ। ਵਰਕਫ਼ਲੋ ਟੈਸਟ ਨੀਤੀ ਮੈਂਬਰਸ਼ਿਪ ਨੂੰ +ਫ਼ੈਸਲਾ ਜੌਬ ਦੀ `needs` ਸੂਚੀ ਨਾਲ ਜੋੜਦੇ ਹਨ, ਤਾਂ ਜੋ ਕੋਈ ਨਵੀਂ ਜਾਂ ਹਟਾਈ ਗਈ ਲੇਨ ਚੁੱਪਚਾਪ ਗਾਇਬ ਨਾ ਹੋ ਸਕੇ। +ਰਸੀਦਾਂ ਆਪਣੇ ਵਰਕਫ਼ਲੋ ਨੂੰ ਕਵਰ ਕਰਦੀਆਂ ਹਨ, ਪ੍ਰਕਾਸ਼ਨ, ਡਿਪਲੌਇਮੈਂਟ ਜਾਂ ਕਿਸੇ ਮੌਜੂਦਾ +ਸਲਾਹਕਾਰੀ ਸਕੈਨਰ ਦੇ ਅੰਦਰੂਨੀ ਹਿੱਸਿਆਂ ਨੂੰ ਨਹੀਂ। ਬ੍ਰਾਂਚ ਨਿਯਮਾਂ ਵਿੱਚ ਦੋਵੇਂ ਚੈੱਕ ਨਾਮ ਸਰਗਰਮ ਕਰਨਾ ਇੱਕ +ਵੱਖਰਾ ਪ੍ਰਸ਼ਾਸਕੀ ਬਦਲਾਅ ਹੈ; ਇਹ ਜੌਬ ਸ਼ਾਮਲ ਕਰਨ ਨਾਲ ਆਪਣੇ ਆਪ ਕੋਈ ਬ੍ਰਾਂਚ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਹੁੰਦੀ। ### ਸਟੈਟਿਕ ਸਕੈਨ ਇਨਵੈਂਟਰੀ -ਵਰਜ਼ਨ-ਨਿਯੰਤਰਿਤ npm-ਐਲਿਅਸ ਇਨਵੈਂਟਰੀ ਅਤੇ ਸਟੈਟਿਕ-ਸਕੈਨ ਮੈਂਬਰਸ਼ਿਪ -`config/quality/gate-manifest.json` ਵਿੱਚ ਮੌਜੂਦ ਹਨ। ਸਕ੍ਰਿਪਟ ਨਾਮਾਂ ਅਤੇ ਸਟੀਕ ਕਮਾਂਡਾਂ -ਨੂੰ `package.json` ਨਾਲ ਮਿਲਾ ਕੇ ਪ੍ਰਮਾਣਿਤ ਕਰਨ ਲਈ `npm run check:gate-manifest` ਚਲਾਓ; -ਜੋੜ, ਹਟਾਉਣ ਅਤੇ ਕਮਾਂਡ ਵਿੱਚ ਤਬਦੀਲੀ ਸਥਾਨਕ ਹੁੱਕ ਅਤੇ CI ਵਿੱਚ ਬਦਲਾਅ-ਵਰਗੀਕਰਨ ਜੌਬਾਂ -ਦੋਵਾਂ ਨੂੰ ਅਸਫਲ ਕਰ ਦਿੰਦੇ ਹਨ। ਇੱਕ ਐਲਿਅਸ ਕੋਈ ਵਰਕਫ਼ਲੋ ਜੌਬ, ਮੈਟ੍ਰਿਕਸ ਇੰਸਟੈਂਸ ਜਾਂ -ਟੈਸਟ ਕੇਸ ਨਹੀਂ ਹੁੰਦਾ: ਇਨ੍ਹਾਂ ਗਿਣਤੀਆਂ ਨੂੰ ਆਪਸ ਵਿੱਚ ਬਦਲਣਯੋਗ ਵਜੋਂ ਪੇਸ਼ ਨਹੀਂ ਕੀਤਾ -ਜਾਣਾ ਚਾਹੀਦਾ। - -ਚੁਣੇ ਹੋਏ ਐਲਿਅਸਾਂ ਨੂੰ ਚਲਾਏ ਬਿਨਾਂ ਦੇਖਣ ਲਈ `npm run quality:scan -- --list` ਜਾਂ -`npm run quality:scan:fast -- --list` ਵਰਤੋ। ਰਨਰ npm ਐਂਟਰੀਪੌਇੰਟ ਨੂੰ ਇਨਵੋਕ ਕਰਦਾ -ਹੈ, ਇਸ ਲਈ ਇਸ ਦਾ ਰਨਟਾਈਮ (ਜਿੱਥੇ ਸੰਰਚਿਤ ਹੋਵੇ ਉੱਥੇ Bun ਸਮੇਤ) ਬਰਕਰਾਰ ਰਹਿੰਦਾ ਹੈ। -ਮੈਨੀਫੈਸਟ ਉਨ੍ਹਾਂ ਪ੍ਰੋਫ਼ਾਈਲਾਂ ਤੋਂ ਬਾਹਰਲੇ ਐਲਿਅਸਾਂ ਨੂੰ ਵੱਖਰੇ ਤੌਰ 'ਤੇ ਇਨਵੋਕ ਕੀਤੇ ਵਜੋਂ -ਦਰਜ ਕਰਦਾ ਹੈ, ਅਤੇ ਰੀਡ-ਓਨਲੀ ਸਕੈਨ ਪ੍ਰੋਫ਼ਾਈਲਾਂ ਵਿੱਚ ਰੱਖ-ਰਖਾਅ ਕਮਾਂਡਾਂ ਦੀ ਮਨਾਹੀ ਹੈ। - -ਇਹ ਪ੍ਰੋਫ਼ਾਈਲ ਸਿਰਫ਼ ਸਟੈਟਿਕ ਸਕੈਨ ਨੂੰ ਕਵਰ ਕਰਦੇ ਹਨ। ਇਹ ਪ੍ਰੋਡਕਟ ਟੈਸਟਾਂ, ਕਵਰੇਜ, -ਪੈਕੇਜਿੰਗ, ਬਾਹਰੀ ਚੈੱਕਾਂ ਜਾਂ ਕਿਸੇ ਉਮੀਦਵਾਰ ਦੀ ਪੂਰੀ ਰਿਲੀਜ਼ ਸਵੀਕ੍ਰਿਤੀ ਨੂੰ ਪ੍ਰਮਾਣਿਤ -ਨਹੀਂ ਕਰਦੇ। ਵਰਕਫ਼ਲੋ ਦਾਖਲਾ ਲਿੰਕ ਕੀਤੇ `config/quality/admission-policy.json` ਅਤੇ -`scripts/quality/admission-verdict.mjs` ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਰਿਲੀਜ਼-ਆਬਜ਼ਰਵਰ ਪ੍ਰੋਫ਼ਾਈਲ -ਵੱਖਰੇ ਰਹਿੰਦੇ ਹਨ; ਉਨ੍ਹਾਂ ਦੇ ਲਾਗੂ ਚੈੱਕਾਂ ਅਤੇ ਰਸੀਦਾਂ ਦੀ ਸੁਤੰਤਰ ਤੌਰ 'ਤੇ ਜਾਂਚ ਕਰੋ। -ਹੇਠਾਂ ਦਿੱਤੀ ਵਾਰਤਕ ਇਨਵੈਂਟਰੀ ਇੱਕ ਹਵਾਲਾ ਹੈ, ਇਸ ਗੱਲ ਦਾ ਸਬੂਤ ਨਹੀਂ ਕਿ ਗੇਟ ਅਸਲ ਵਿੱਚ -ਚੱਲਿਆ ਸੀ। - -ਸਕ੍ਰਿਪਟਾਂ `scripts/check/` (ਨੀਤੀ ਗੇਟ) ਅਤੇ `scripts/quality/` (ਰੈਚਿਟ ਇੰਜਣ) ਦੇ -ਅਧੀਨ ਮੌਜੂਦ ਹਨ। CI ਲਈ ਸੱਚਾਈ ਦਾ ਸਰੋਤ `.github/workflows/ci.yml` ਹੈ। - -### ਰਿਲੀਜ਼ PR ਫਾਸਟ-ਪਾਥ (`quality.yml`) - -`.github/workflows/quality.yml` main/release PRs, ਸੁਰੱਖਿਅਤ-ਬ੍ਰਾਂਚ ਪੁਸ਼ਾਂ, ਡਿਸਪੈਚ -ਅਤੇ ਮਰਜ ਗਰੁੱਪਾਂ 'ਤੇ CI ਦੀ ਪੂਰਤੀ ਕਰਦਾ ਹੈ। PRs ਪਾਥ-ਫਿਲਟਰ ਕੀਤੇ ਤੇਜ਼ ਚੈੱਕ ਵਰਤਦੇ ਹਨ। -ਸਥਾਈ ਤੌਰ 'ਤੇ ਅਸਮਰੱਥ ਕੀਤੀ ਡੁਪਲੀਕੇਟ ਬਿਲਡ ਹਟਾ ਦਿੱਤੀ ਗਈ ਸੀ; ਅਸਲ -ਬਿਲਡ/ਪੈਕੇਜ/ਬੂਟ ਚੈੱਕ CI ਵਿੱਚ ਬਰਕਰਾਰ ਹਨ। - -| ਜੌਬ | ਦਾਇਰਾ | ਬਲੌਕਿੰਗ | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | ਡੌਕਸ/ਕੋਡ PRs; API ਡੌਕਸ ਹਵਾਲੇ ਅਤੇ docs-all | ਹਾਂ | -| `Fast Quality Gates` | ਕੋਡ PRs; ਸਟੈਟਿਕ ਚੈੱਕ, ਟਾਈਪਚੈੱਕ, ਡੈਸ਼ਬੋਰਡ ਟਾਈਪਚੈੱਕ, ਪ੍ਰਭਾਵਿਤ ਯੂਨਿਟ ਟੈਸਟ | ਹਾਂ | -| `Forgotten sibling tests` | ਕੋਡ PRs; ਬਦਲੇ ਮੌਡਿਊਲਾਂ ਤੋਂ ਸਟੈਟਿਕ ਉਪਭੋਗਤਾਵਾਂ ਅਤੇ ਸੰਭਾਵੀ ਸਿਬਲਿੰਗ ਟੈਸਟਾਂ ਤੱਕ ਟਰੇਸ; ਬੈਰਲ ਅਤੇ ਡਾਇਨਾਮਿਕ-ਇੰਪੋਰਟ ਪਾਥਾਂ ਨੂੰ ਸਲਾਹਕਾਰੀ ਡਾਇਗਨੌਸਟਿਕਸ ਵਜੋਂ ਰਿਪੋਰਟ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਹਵਾਲਾ ਦਿੱਤੇ ਅਲਾਊਲਿਸਟ ਅਪਵਾਦਾਂ ਸਮੇਤ | **ਸਲਾਹਕਾਰੀ** | -| `Vitest (fast-path)` | ਕੋਡ PRs; ਤੇਜ਼ vitest ਸੂਟ | ਹਾਂ | -| `Unit Tests fast-path` | ਕੋਡ PRs; 4-ਸ਼ਾਰਡ ਯੂਨਿਟ ਸੂਟ | ਹਾਂ | -| `No new ESLint warnings` | ਕੋਡ PRs; ਸਪ੍ਰੈਸ਼ਨ-ਅਵੇਅਰ ਲਿੰਟ ਗਾਰਡ | ਹਾਂ, ਫੋਰਕਾਂ ਸਮੇਤ | -| `Merge integrity (changelog + generated skills)` | ਗੈਰ-ਡਰਾਫਟ PRs; ਚੇਂਜਲੌਗ ਅਤੇ ਜਨਰੇਟ ਕੀਤੇ ਸਕਿੱਲਾਂ ਦਾ ਸਿੰਕ | ਹਾਂ, ਫੋਰਕਾਂ ਸਮੇਤ | - -#### ਭੁੱਲੇ ਹੋਏ ਸਿਬਲਿੰਗ ਟੈਸਟਾਂ ਦੀ ਰਿਪੋਰਟ - -`npm run check:forgotten-sibling-tests` ਟੈਸਟ-ਇੰਪੈਕਟ ਮੈਪ ਦੇ ਪਿੱਛੇ ਵਰਤੇ ਜਾਣ ਵਾਲੇ -ਇੰਪੋਰਟ ਰਿਜ਼ੌਲਵਰ ਨੂੰ ਮੁੜ ਵਰਤਦਾ ਹੈ। ਹਰੇਕ ਬਦਲੇ ਹੋਏ ਪ੍ਰੋਡਕਸ਼ਨ ਮੌਡਿਊਲ ਲਈ, ਜਦੋਂ -ਸੰਭਾਵੀ ਟੈਸਟ ਪੁੱਲ-ਰਿਕਵੈਸਟ ਡਿਫ਼ ਵਿੱਚ ਮੌਜੂਦ ਨਹੀਂ ਹੁੰਦਾ, ਇਹ ਨਿਰਧਾਰਤ -`changed module/symbol -> static consumer -> candidate sibling test` ਚੇਨਾਂ ਦੀ -ਰਿਪੋਰਟ ਕਰਦਾ ਹੈ। ਕਿਸੇ ਵੀ ਬਲੌਕਿੰਗ ਰੋਲਆਉਟ ਤੋਂ ਪਹਿਲਾਂ ਕੈਲੀਬ੍ਰੇਸ਼ਨ ਲਈ Markdown ਸੰਖੇਪ -ਅਤੇ JSON ਨਤੀਜੇ ਨੂੰ `forgotten-sibling-tests` ਵਰਕਫ਼ਲੋ ਆਰਟੀਫੈਕਟ ਵਜੋਂ ਸੰਭਾਲਿਆ ਜਾਂਦਾ ਹੈ। - -Barrel re-exports ਅਤੇ dynamic imports ਕੇਵਲ resolution diagnostics ਹਨ; ਇਹ ਕਦੇ ਵੀ ਕੋਈ -ਰੋਕਣ ਵਾਲੀ finding ਨਹੀਂ ਬਣਾਉਂਦੇ। ਸਮੀਖਿਆ ਕੀਤੇ exceptions -`config/quality/forgotten-sibling-allowlist.json` ਵਿੱਚ ਮੌਜੂਦ ਹਨ। ਹਰੇਕ entry ਵਿੱਚ consumer ਅਤੇ candidate -test ਦਾ ਨਾਮ, ਇੱਕ ਵਿਸ਼ੇਸ਼ ਤਰਕ, ਅਤੇ ਕਿਸੇ GitHub issue ਜਾਂ pull request ਦਾ link ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ। ਗਲਤ ਢੰਗ ਨਾਲ ਬਣੀਆਂ entries -ਬੰਦ-ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਅਸਫਲ ਹੁੰਦੀਆਂ ਹਨ। Exceptions ਕਿਸੇ ਮਿਟਾਏ ਗਏ candidate test ਜਾਂ `.skip`/`.todo` ਜੋੜਨ ਵਾਲੇ diff ਨੂੰ suppress ਨਹੀਂ ਕਰ ਸਕਦੇ; -assertion ਨੂੰ ਕਮਜ਼ੋਰ ਕਰਨਾ ਅਤੇ ਹੋਰ masking ਸੁਤੰਤਰ ਤੌਰ 'ਤੇ ਰੋਕ ਲਗਾਉਣ ਵਾਲੇ -`check:test-masking` gate ਦੇ ਅਧੀਨ ਰਹਿੰਦੇ ਹਨ। +ਵਰਜਨਬੱਧ npm-alias ਇਨਵੈਂਟਰੀ ਅਤੇ static-scan ਮੈਂਬਰਸ਼ਿਪ +`config/quality/gate-manifest.json` ਵਿੱਚ ਮੌਜੂਦ ਹਨ। ਸਕ੍ਰਿਪਟ ਨਾਮਾਂ ਅਤੇ ਸਟੀਕ ਕਮਾਂਡਾਂ ਨੂੰ +`package.json` ਨਾਲ ਮਿਲਾ ਕੇ ਪ੍ਰਮਾਣਿਤ ਕਰਨ ਲਈ `npm run check:gate-manifest` ਚਲਾਓ; ਜੋੜ, ਹਟਾਉਣ ਅਤੇ +ਕਮਾਂਡ ਡ੍ਰਿਫ਼ਟ ਦੋਵਾਂ—ਲੋਕਲ ਹੁੱਕ ਅਤੇ CI ਵਿੱਚ ਬਦਲਾਅ-ਵਰਗੀਕਰਨ ਜੌਬਾਂ—ਨੂੰ ਅਸਫਲ ਕਰ ਦਿੰਦੇ ਹਨ। +ਕੋਈ alias, workflow job, matrix instance ਜਾਂ test case ਨਹੀਂ ਹੁੰਦਾ: ਇਹ ਗਿਣਤੀਆਂ +ਆਪਸ ਵਿੱਚ ਬਦਲਣਯੋਗ ਵਜੋਂ ਪੇਸ਼ ਨਹੀਂ ਕੀਤੀਆਂ ਜਾਣੀਆਂ ਚਾਹੀਦੀਆਂ। + +ਚੁਣੇ ਹੋਏ aliases ਨੂੰ ਚਲਾਏ ਬਿਨਾਂ ਦੇਖਣ ਲਈ `npm run quality:scan -- --list` ਜਾਂ +`npm run quality:scan:fast -- --list` ਵਰਤੋ। Runner, npm entrypoint ਨੂੰ ਚਲਾਉਂਦਾ ਹੈ, +ਇਸ ਲਈ ਇਸ ਦਾ runtime—ਸੰਰਚਿਤ ਹੋਣ ਦੀ ਸਥਿਤੀ ਵਿੱਚ Bun ਸਮੇਤ—ਬਰਕਰਾਰ ਰਹਿੰਦਾ ਹੈ। +Manifest ਉਹਨਾਂ ਪ੍ਰੋਫ਼ਾਈਲਾਂ ਤੋਂ ਬਾਹਰਲੇ aliases ਨੂੰ ਵੱਖਰੇ ਤੌਰ 'ਤੇ ਚਲਾਏ ਗਏ ਵਜੋਂ ਦਰਜ ਕਰਦਾ ਹੈ, ਅਤੇ +read-only scan ਪ੍ਰੋਫ਼ਾਈਲਾਂ ਵਿੱਚ maintenance commands ਵਰਜਿਤ ਹਨ। + +ਇਹ ਪ੍ਰੋਫ਼ਾਈਲ ਕੇਵਲ static scan ਨੂੰ ਕਵਰ ਕਰਦੇ ਹਨ। ਇਹ product tests, +coverage, packaging, external checks ਜਾਂ ਕਿਸੇ ਉਮੀਦਵਾਰ ਦੀ ਪੂਰੀ release acceptance ਨੂੰ ਪ੍ਰਮਾਣਿਤ ਨਹੀਂ ਕਰਦੇ। +Workflow admission, ਲਿੰਕ ਕੀਤੇ `config/quality/admission-policy.json` ਅਤੇ +`scripts/quality/admission-verdict.mjs` ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ। Release-observer ਪ੍ਰੋਫ਼ਾਈਲ ਵੱਖਰੇ ਰਹਿੰਦੇ ਹਨ; +ਉਨ੍ਹਾਂ ਦੇ ਲਾਗੂ ਚੈੱਕਾਂ ਅਤੇ ਰਸੀਦਾਂ ਦੀ ਸੁਤੰਤਰ ਤੌਰ 'ਤੇ ਜਾਂਚ ਕਰੋ। ਹੇਠਾਂ ਦਿੱਤੀ ਵਿਆਖਿਆਤਮਕ +ਇਨਵੈਂਟਰੀ ਇੱਕ ਹਵਾਲਾ ਹੈ, ਇਸ ਗੱਲ ਦਾ ਸਬੂਤ ਨਹੀਂ ਕਿ ਕੋਈ ਗੇਟ ਵਾਸਤਵ ਵਿੱਚ ਚੱਲਿਆ ਸੀ। + +ਸਕ੍ਰਿਪਟਾਂ `scripts/check/` (ਨੀਤੀ ਗੇਟ) ਅਤੇ `scripts/quality/` (ratchet engine) ਦੇ ਅਧੀਨ ਮੌਜੂਦ ਹਨ। +CI ਲਈ ਸੱਚਾਈ ਦਾ ਸਰੋਤ `.github/workflows/ci.yml` ਹੈ। + +### Release PR ਫਾਸਟ-ਪਾਥ (`quality.yml`) + +`.github/workflows/quality.yml`, main/release PRs, protected-branch +pushes, dispatch ਅਤੇ merge groups ਉੱਤੇ CI ਦੀ ਪੂਰਤੀ ਕਰਦਾ ਹੈ। PRs path-filtered ਤੇਜ਼ ਚੈੱਕ ਵਰਤਦੇ ਹਨ। ਸਥਾਈ ਤੌਰ 'ਤੇ +ਅਸਮਰੱਥ ਕੀਤਾ ਡੁਪਲੀਕੇਟ build ਹਟਾ ਦਿੱਤਾ ਗਿਆ ਸੀ; ਅਸਲ build/package/boot ਚੈੱਕ CI ਵਿੱਚ ਮੌਜੂਦ ਰਹਿੰਦੇ ਹਨ। + +| ਜੌਬ | ਦਾਇਰਾ | ਬਲੌਕ ਕਰਨ ਵਾਲਾ | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `Docs Gates (fast-path)` | Docs/code PRs; API docs refs ਅਤੇ docs-all | ਹਾਂ | +| `Fast Quality Gates` | Code PRs; static checks, typecheck, dashboard typecheck, ਪ੍ਰਭਾਵਿਤ unit tests | ਹਾਂ | +| `Forgotten sibling tests` | Code PRs; ਬਦਲੇ ਹੋਏ modules ਤੋਂ static consumers ਅਤੇ candidate sibling tests ਤੱਕ ਟ੍ਰੇਸ; barrel ਅਤੇ dynamic-import paths ਨੂੰ allowlist ਦੇ ਹਵਾਲੇ ਵਾਲੇ ਅਪਵਾਦਾਂ ਸਮੇਤ ਸਲਾਹਕਾਰੀ diagnostics ਵਜੋਂ ਰਿਪੋਰਟ ਕੀਤਾ ਜਾਂਦਾ ਹੈ | **ਸਲਾਹਕਾਰੀ** | +| `Vitest (fast-path)` | Code PRs; ਤੇਜ਼ vitest suite | ਹਾਂ | +| `Unit Tests fast-path` | Code PRs; 4-shard unit suite | ਹਾਂ | +| `No new ESLint warnings` | Code PRs; suppressions-aware lint guard | ਹਾਂ, forks ਸਮੇਤ | +| `Merge integrity (changelog + generated skills)` | Non-draft PRs; changelog ਅਤੇ generated skill sync | ਹਾਂ, forks ਸਮੇਤ | + +#### ਭੁੱਲੇ ਹੋਏ sibling tests ਦੀ ਰਿਪੋਰਟ + +`npm run check:forgotten-sibling-tests`, test-impact map ਦੇ ਪਿੱਛੇ ਮੌਜੂਦ import resolver ਨੂੰ ਮੁੜ ਵਰਤਦਾ ਹੈ। +ਹਰੇਕ ਬਦਲੇ ਹੋਏ production module ਲਈ, ਜਦੋਂ candidate +test, pull-request diff ਵਿੱਚ ਗੈਰਹਾਜ਼ਰ ਹੋਵੇ, ਇਹ ਨਿਰਧਾਰਿਤ +`changed module/symbol -> static consumer -> candidate sibling test` ਲੜੀਆਂ ਰਿਪੋਰਟ ਕਰਦਾ ਹੈ। ਕਿਸੇ ਵੀ blocking rollout ਤੋਂ ਪਹਿਲਾਂ calibration ਲਈ +Markdown ਸਾਰਾਂਸ਼ ਅਤੇ JSON ਨਤੀਜੇ ਨੂੰ `forgotten-sibling-tests` workflow artifact ਵਜੋਂ ਸੰਭਾਲਿਆ ਜਾਂਦਾ ਹੈ। + +Barrel ਰੀ-ਐਕਸਪੋਰਟ ਅਤੇ ਡਾਇਨਾਮਿਕ ਇੰਪੋਰਟ ਸਿਰਫ਼ resolution diagnostics ਹਨ; ਇਹ ਕਦੇ ਵੀ +ਕੋਈ blocking finding ਨਹੀਂ ਬਣਾਉਂਦੇ। ਸਮੀਖਿਆ ਕੀਤੀਆਂ exceptions +`config/quality/forgotten-sibling-allowlist.json` ਵਿੱਚ ਮੌਜੂਦ ਹਨ। ਹਰ entry ਵਿੱਚ consumer ਅਤੇ candidate +ਟੈਸਟ ਦਾ ਨਾਮ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ, ਇੱਕ ਖਾਸ ਤਰਕ ਦਿੱਤਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ, ਅਤੇ GitHub issue ਜਾਂ pull request ਦਾ ਲਿੰਕ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। ਗਲਤ-ਰੂਪ ਵਾਲੀਆਂ entries +fail closed ਹੁੰਦੀਆਂ ਹਨ। Exceptions ਮਿਟਾਏ ਗਏ candidate ਟੈਸਟ ਜਾਂ `.skip`/`.todo` ਜੋੜਨ ਵਾਲੇ diff ਨੂੰ suppress ਨਹੀਂ ਕਰ ਸਕਦੀਆਂ; +assertion weakening ਅਤੇ ਹੋਰ masking ਦੀ ਜ਼ਿੰਮੇਵਾਰੀ ਸੁਤੰਤਰ ਤੌਰ 'ਤੇ blocking +`check:test-masking` gate ਕੋਲ ਰਹਿੰਦੀ ਹੈ। ### Job: `lint` -`main` ਲਈ ਹਰ PR ਉੱਤੇ ਚੱਲਦਾ ਹੈ। ਅਸਫਲਤਾ ਹੋਣ 'ਤੇ merge ਨੂੰ ਰੋਕਦਾ ਹੈ। - -| Script (`npm run ...`) | ਪ੍ਰਮਾਣਿਤ ਕਰਦਾ ਹੈ | ਰੋਕਣ ਵਾਲਾ | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| `check:node-runtime` | Node.js ਦਾ version ਸਮਰਥਿਤ range ਦੇ ਅੰਦਰ ਹੈ | ਹਾਂ | -| `check:cycles` | Circular imports — ਸਾਰੇ `src/` + `open-sse/` modules | ਹਾਂ | -| `check:route-validation:t06` | ਸਾਰੇ routes ਉੱਤੇ Zod schemas ਮੌਜੂਦ ਹਨ (Tier 6 policy) | ਹਾਂ | -| `check:any-budget:t11` | `@ts-expect-error // any` ਦੀ ਗਿਣਤੀ budget ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ (Tier 11 catraca) | ਹਾਂ | -| `check:provider-consistency` | `providers.ts` ਵਿੱਚ ਹਰ provider ਲਈ `providerRegistry.ts` ਵਿੱਚ ਇੱਕ ਮੇਲ ਖਾਂਦੀ ਐਂਟਰੀ ਮੌਜੂਦ ਹੈ (ਅਤੇ ਇਸਦੇ ਉਲਟ ਵੀ, allowlist ਦੇ ਅੰਦਰ) | ਹਾਂ | -| `check:model-lifecycle` | ਹੱਥੀਂ ਸੰਭਾਲੀਆਂ ਜਾਣ ਵਾਲੀਆਂ ਤਿੰਨ routing tables, checked-in lifecycle snapshot (#11503) ਨਾਲ ਇਕਸਾਰ ਰਹਿੰਦੀਆਂ ਹਨ: `FITNESS_TABLE` (`taskFitness.ts`) ਕਿਸੇ ਵੀ ਅਜਿਹੀ retired id ਨੂੰ ਸਕੋਰ ਨਹੀਂ ਕਰਦੀ ਜਿਸ ਨੂੰ `REGISTRY` route ਕਰ ਸਕਦੀ ਹੈ; ਹਰ `BUILT_IN_ALIASES` target `REGISTRY` ਵਿੱਚ ਮੌਜੂਦ ਹੈ ਅਤੇ retired-id snapshot ਵਿੱਚ ਗੈਰਹਾਜ਼ਰ ਹੈ; `REGISTRY` ਵਿੱਚ ਅਜੇ ਵੀ ਮੌਜੂਦ ਹਰ retired id ਨੂੰ forward ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ `allowedRetiredInCatalog` ਵਿੱਚ ਸੂਚੀਬੱਧ ਕੀਤਾ ਗਿਆ ਹੈ; ਅਤੇ ਕੋਈ ਵੀ `DEFAULT_DEGRADATION_MAP` source ਜਾਂ target ਉਸ snapshot ਵਿੱਚ retired ਵਜੋਂ ਨਹੀਂ ਦਿਸਦਾ। ਇਹ ਸਾਬਤ ਨਹੀਂ ਕਰਦਾ ਕਿ ਕੋਈ model ਇਸ ਵੇਲੇ live upstream ਵੱਲੋਂ serve ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ। Offline — `config/quality/model-lifecycle.json` ਨਾਲ ਤੁਲਨਾ ਕਰਦਾ ਹੈ, ਜਿਸਨੂੰ `npm run quality:refresh-model-lifecycle` ਨਾਲ ਹੱਥੀਂ refresh ਕੀਤਾ ਜਾਂਦਾ ਹੈ (network; CI ਨਾਲ ਜੋੜਿਆ ਨਹੀਂ ਗਿਆ)। `allowedRetiredInCatalog` ਇੱਕ burn-down ratchet ਹੈ: ਕੋਈ ਐਂਟਰੀ ਸਿਰਫ਼ tracking issue ਦੇ ਨਾਲ ਹੀ ਸ਼ਾਮਲ ਕਰੋ। | ਹਾਂ | -| `check:fetch-targets` | client-side `src/` ਵਿੱਚ ਹਰ `fetch("/api/...")` ਇੱਕ ਅਸਲ `route.ts` ਵੱਲ resolve ਹੁੰਦਾ ਹੈ | ਹਾਂ | -| `check:deps` | repo ਵਿੱਚ ਹਰ `package.json` ਦੇ ਸਾਰੇ `npm install`-ਯੋਗ deps `dependency-allowlist.json` ਵਿੱਚ ਹਨ; ਨਵੇਂ unpinned ਜਾਂ slopsquatted packages ਨੂੰ flag ਕੀਤਾ ਜਾਂਦਾ ਹੈ | ਹਾਂ | -| `audit:deps` | `npm audit` (root + electron) — ਕੋਈ high/critical advisory ਨਹੀਂ (`check:vuln-ratchet` osv ਨਾਲ overlap ਕਰਦਾ ਹੈ; Rationalization Backlog ਵੇਖੋ) | ਹਾਂ | -| `check:lockfile` | `package-lock.json` ਦੀ integrity — https registry, integrity hashes, ਕੋਈ host override ਨਹੀਂ | ਹਾਂ | -| `check:licenses` | ਉਤਪਾਦਨ ਨਿਰਭਰਤਾਵਾਂ ਲਈ SPDX ਲਾਇਸੈਂਸ ਮਨਜ਼ੂਰਸ਼ੁਦਾ-ਸੂਚੀ | ਹਾਂ | -| `check:tracked-artifacts` | ਕੋਈ ਬਿਲਡ ਆਰਟੀਫੈਕਟ / ਕਮਿਟ ਕੀਤੇ `node_modules` ਸਿੰਬੋਲਿਕ ਲਿੰਕ ਨਹੀਂ (husky pre-commit ਵਿੱਚ ਵੀ ਚੱਲਦਾ ਹੈ; pre-push ਜਾਣਬੁੱਝ ਕੇ ਹਲਕਾ ਰੱਖਿਆ ਗਿਆ ਹੈ — #6716) | ਹਾਂ | -| `check:ai-attribution` | PR ਕਮਿਟਾਂ, ਸਿਰਲੇਖ ਜਾਂ ਮੁੱਖ ਸਮੱਗਰੀ ਵਿੱਚ ਕੋਈ AI/bot `Co-Authored-By` ਟ੍ਰੇਲਰ ਜਾਂ AI-ਜਨਰੇਸ਼ਨ ਫੁੱਟਰ ਨਹੀਂ — ਸਖ਼ਤ ਨਿਯਮ #16 (`quality.yml` ਦੇ PR→`release/**` ਲਈ fast-gates ਲੂਪ ਵਿੱਚ — ਇਵੈਂਟ ਪੇਲੋਡ ਪੜ੍ਹਦਾ ਹੈ, PR ਨਾ ਹੋਣ 'ਤੇ ਕੁਝ ਨਹੀਂ ਕਰਦਾ — ਅਤੇ `ci.yml` lint ਵਿੱਚ PR→`main` ਲਈ ਕੇਵਲ-PR ਪੜਾਅ; ਨਾਲ ਹੀ husky `commit-msg` ਹੁੱਕ; ਮਨੁੱਖੀ ਸਹਿ-ਲੇਖਕ ਮਨਜ਼ੂਰ ਹਨ; #14436) | -| `check:vitest-exclusions` | ਹਰ Vitest ਅਪਵਾਦ ਵਿੱਚ ਇੱਕ ਟ੍ਰੈਕਿੰਗ ਇਸ਼ੂ ਦਾ ਨਾਮ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਉਹ `config/quality/vitest-exclusions.json` ਵਿੱਚ ਮੌਜੂਦ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ (#13204) | ਹਾਂ | -| `check:file-size` | ਕੋਈ ਵੀ ਸਰੋਤ ਫ਼ਾਈਲ ਪ੍ਰਤੀ-ਐਕਸਟੈਂਸ਼ਨ ਸੀਮਾ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੁੰਦੀ (ਰੈਚਟ: `frozen` ਸੂਚੀ ਵਿੱਚ ਫ੍ਰੀਜ਼ ਕੀਤੀਆਂ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ) | ਹਾਂ | -| `check:error-helper` | ਐਗਜ਼ਿਕਿਊਟਰਾਂ/ਹੈਂਡਲਰਾਂ ਵਿੱਚ ਤਰੁੱਟੀ ਜਵਾਬ `buildErrorBody()` / `sanitizeErrorMessage()` ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #12) | ਹਾਂ | -| `check:migration-numbering` | Migration SQL ਫਾਈਲਾਂ ਨੂੰ ਬਿਨਾਂ ਕਿਸੇ ਖਾਲੀ ਥਾਂ ਜਾਂ ਡੁਪਲੀਕੇਟ ਦੇ ਕ੍ਰਮਵਾਰ ਨੰਬਰ ਦਿੱਤੇ ਗਏ ਹਨ | ਹਾਂ | -| `check:public-creds` | `publicCreds.ts` ਤੋਂ ਬਾਹਰ ਕੋਈ ਵੀ ਸਿੱਧਾ ਲਿਖਿਆ OAuth `client_id`/`client_secret` ਜਾਂ Firebase Web ਕੁੰਜੀਆਂ ਨਹੀਂ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #11) | ਹਾਂ | -| `check:db-rules` | `src/lib/db/` ਮੋਡੀਊਲਾਂ ਤੋਂ ਬਾਹਰ ਕੋਈ ਕੱਚਾ SQL ਨਹੀਂ ਹੈ; `localDb.ts` ਤੋਂ ਕੋਈ barrel-imports ਨਹੀਂ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #2/#5) | ਹਾਂ | -| `check:known-symbols` | ਆਪਣੀਆਂ dispatch ਟੇਬਲਾਂ ਵਿੱਚ ਰਜਿਸਟਰ ਕੀਤੇ provider executors, routing strategies ਅਤੇ translators ਡਿਸਕ ਉੱਤੇ ਮੌਜੂਦ ਫਾਈਲਾਂ ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਹਨ—ਕੋਈ ਅਨਾਥ ਜਾਂ ਅਣ-ਐਲਾਨੇ symbols ਨਹੀਂ ਹਨ | ਹਾਂ | -| `check:route-guard-membership` | child process ਸ਼ੁਰੂ ਕਰਨ ਵਾਲਾ ਹਰੇਕ route, `isLocalOnlyPath()` ਦੁਆਰਾ ਵਰਗੀਕ੍ਰਿਤ ਹੈ (ਸਖ਼ਤ ਨਿਯਮ #15/#17) | ਹਾਂ | -| `check:test-discovery` | ਰਿਪੋ ਵਿੱਚ ਹਰੇਕ `*.test.ts` / `*.spec.ts` ਫਾਈਲ ਨੂੰ ਘੱਟੋ-ਘੱਟ ਇੱਕ test runner ਦੁਆਰਾ ਇਕੱਠਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ (ratchet: `test-discovery-baseline.json` ਵਿੱਚ orphan ਸੂਚੀ ਸਿਰਫ਼ ਛੋਟੀ ਹੋ ਸਕਦੀ ਹੈ) | ਹਾਂ | -| `check:agent-skills-sync` | ਬਣਾਏ ਗਏ agent-skills ਆਰਟੀਫੈਕਟ ਆਪਣੇ ਸਰੋਤ ਕੈਟਾਲਾਗ ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਹਨ (ਕੋਈ ਅੰਤਰ ਨਹੀਂ) | -| `check:provider-asset-provenance` | ਪ੍ਰਦਾਤਾ ਦੇ ਲੋਗੋ/ਐਸੈੱਟਾਂ ਲਈ ਦਰਜ ਕੀਤਾ ਗਿਆ ਮੂਲ-ਸਰੋਤ ਰਿਕਾਰਡ ਮੌਜੂਦ ਹੈ | -| `lint:json` | JSON ਕਨਫਿਗ ਫਾਈਲਾਂ ਸਹੀ ਢੰਗ ਨਾਲ ਪਾਰਸ ਹੁੰਦੀਆਂ ਹਨ ਅਤੇ ਰਿਪੋ ਦੇ lint ਨਿਯਮਾਂ ਦੀ ਪਾਲਣਾ ਕਰਦੀਆਂ ਹਨ | -| `typecheck:core` | ਬਿਨਾਂ ਤਰੁੱਟੀਆਂ ਦੇ TypeScript ਕੰਪਾਇਲੇਸ਼ਨ (ਕੇਵਲ ਸਲਾਹਕਾਰੀ ਚੇਤਾਵਨੀਆਂ) | ਹਾਂ | -| `typecheck:noimplicit:core` | ਸਖ਼ਤ `noImplicitAny` — ਭਵਿੱਖ-ਕੇਂਦਰਿਤ; ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਕਈ ਕਾਲ ਸਾਈਟਾਂ ਨੂੰ ਹਾਲੇ ਵੀ ਐਨੋਟੇਸ਼ਨਾਂ ਦੀ ਲੋੜ ਹੈ | **ਸਲਾਹਕਾਰੀ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` ਨੂੰ `src/app/(dashboard)/**` ਤੱਕ ਸੀਮਿਤ ਕੀਤਾ ਗਿਆ ਹੈ (#7033) — `typecheck:core` ਦੀ ਸੋਚ-ਸਮਝ ਕੇ ਚੁਣੀ 27-ਫਾਈਲਾਂ ਵਾਲੀ allowlist ਵਿੱਚ ਕੋਈ ਵੀ dashboard TSX ਸ਼ਾਮਲ ਨਹੀਂ ਹੈ, ਅਤੇ `next build` ਵੀ ਕਦੇ ਇਸ ਦੀ type-checking ਨਹੀਂ ਕਰਦਾ (`next.config.mjs` ਵਿੱਚ `ignoreBuildErrors: true` ਸੈੱਟ ਹੈ), ਇਸ ਲਈ ਉੱਥੇ orphaned-identifier ਰਿਗ੍ਰੈਸ਼ਨ (#6625/#6909) CI ਲਈ ਅਦਿੱਖ ਸਨ। ਇੱਕ ਸਥਿਰ ਕੀਤੀ ਪ੍ਰਤੀ-ਫਾਈਲ/ਪ੍ਰਤੀ-TS-code ਗਿਣਤੀ ਬੇਸਲਾਈਨ (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ਵਰਗਾ ਹੀ stale-enforcement ਪੈਟਰਨ) ਦੇ ਮੁਕਾਬਲੇ ਅੰਤਰ ਜਾਂਚੇ ਜਾਂਦੇ ਹਨ — ਬੇਸਲਾਈਨ ਕੀਤੀ ਗਿਣਤੀ ਤੋਂ ਵੱਧ ਕੇਵਲ ਨਵੀਆਂ ਤਰੁੱਟੀਆਂ ਹੀ ਗੇਟ ਨੂੰ ਅਸਫਲ ਕਰਦੀਆਂ ਹਨ; ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਤਰੁੱਟੀ ਠੀਕ ਹੋਣ 'ਤੇ `--update` ਨਾਲ ਬੇਸਲਾਈਨ ਘਟਾਓ। | ਹਾਂ | - -### ਜੌਬ: `quality-gate` +`main` ਲਈ ਹਰ PR ਉੱਤੇ ਚੱਲਦਾ ਹੈ। ਅਸਫਲ ਹੋਣ 'ਤੇ merge ਨੂੰ ਰੋਕਦਾ ਹੈ। -`test-coverage` ਤੋਂ ਬਾਅਦ ਚੱਲਦੀ ਹੈ। ਅਸਫਲਤਾ ਹੋਣ 'ਤੇ ਮਰਜ ਨੂੰ ਰੋਕਦੀ ਹੈ। - -| ਸਕ੍ਰਿਪਟ | ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਰੋਕਣ ਵਾਲੀ | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | `quality-metrics.json` ਬਣਾਉਂਦੀ ਹੈ (ESLint ਚੇਤਾਵਨੀਆਂ ਦੀ ਗਿਣਤੀ, ਮਿਲਾਈ ਗਈ shard ਰਿਪੋਰਟ ਤੋਂ coverage) | ਹਾਂ (ratchet ਤੋਂ ਪਹਿਲਾਂ) | -| `quality:ratchet` | `quality-baseline.json` ਵਿੱਚ ਹਰ metric ਪਿੱਛੇ ਨਹੀਂ ਗਈ ਹੈ (ESLint ਚੇਤਾਵਨੀਆਂ ≤ baseline; coverage ≥ baseline) | ਹਾਂ | -| `check:duplication` | ਕੋਡ ਦੀ duplication (jscpd@4), `quality-baseline.json` ਵਿੱਚ baseline ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ | ਹਾਂ | -| `check:complexity` | ਫ਼ਾਈਲ-ਪੱਧਰੀ cyclomatic complexity ਨਿਰਧਾਰਤ ਸੀਮਾ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ (ਮੁੱਖ ESLint `complexity` + `max-lines-per-function`) | ਹਾਂ | -| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — ਵੱਖਰਾ ESLint ਪਾਸ; CI ਦੋਵਾਂ ਨੂੰ ਇਕੱਲੇ `check:complexity-ratchets` ਪੜਾਅ ਵਜੋਂ ਮਿਲਾ ਕੇ ਚਲਾਉਂਦਾ ਹੈ | ਹਾਂ | -| `check:dead-code` | ਨਾ ਵਰਤੇ ਗਏ exports / ਫ਼ਾਈਲਾਂ ਦਾ ratchet (knip), baseline ਦੇ ਮੁਕਾਬਲੇ ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦਾ | ਹਾਂ | -| `check:compression-budget` | Compression benchmark ਬਜਟ — ਹਰ engine ਲਈ token-savings ਦੀਆਂ ਘੱਟੋ-ਘੱਟ ਹੱਦਾਂ ਪਿੱਛੇ ਨਹੀਂ ਜਾਣੀਆਂ ਚਾਹੀਦੀਆਂ | ਹਾਂ | -| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦਾ; ਵੱਡੇ ਪੱਧਰ 'ਤੇ `typecheck:noimplicit:core` ਨੂੰ ਆਪਣੇ ਅਧੀਨ ਲੈਂਦਾ ਹੈ | ਹਾਂ | -| `check:codeql-ratchet` | ਖੁੱਲ੍ਹੀਆਂ CodeQL ਚੇਤਾਵਨੀਆਂ ਦੀ ਗਿਣਤੀ ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦੀ (`gh api` ਰਾਹੀਂ ਪੜ੍ਹਦਾ ਹੈ; token ਤੋਂ ਬਿਨਾਂ ਸੁਚੱਜੇ ਢੰਗ ਨਾਲ ਛੱਡ ਦਿੰਦਾ ਹੈ) — refresh cadence ਅਤੇ manual trigger: ਹੇਠਾਂ "CodeQL ratchet" ਵੇਖੋ | ਹਾਂ | - -### Job: `quality-extended` - -ਪੂਰਾ job ਸਲਾਹਕਾਰੀ ਹੈ (`continue-on-error: true`)। npm-ਅਧਾਰਿਤ ratchets ਅਸਲ ਵਿੱਚ -ਚੱਲਦੇ ਹਨ; ਬਾਹਰੀ scanners `gh release download` ਰਾਹੀਂ install ਹੁੰਦੇ ਹਨ ਅਤੇ ਜਦੋਂ -binary ਹਾਲੇ ਵੀ ਮੌਜੂਦ ਨਾ ਹੋਵੇ ਤਾਂ ਆਪਣੇ ਆਪ ਛੱਡ ਦਿੰਦੇ ਹਨ (exit 0)। - -| ਸਕ੍ਰਿਪਟ | ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਰੋਕਣ ਵਾਲੀ | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | -| `check:circular-deps` | ਕੋਈ circular dependencies ਨਹੀਂ ਹਨ (dpdm) | **ਸਲਾਹਕਾਰੀ** | -| `check:bundle-size` | Bundle ਦਾ ਆਕਾਰ ਨਿਰਧਾਰਤ ਸੀਮਾ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ | **ਸਲਾਹਕਾਰੀ** | -| `check:secrets` | Secret scanning (gitleaks) — binary ਮੌਜੂਦ ਨਾ ਹੋਣ 'ਤੇ ਛੱਡ ਦਿੰਦੀ ਹੈ | **ਸਲਾਹਕਾਰੀ** | -| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦੀਆਂ — binary ਮੌਜੂਦ ਨਾ ਹੋਣ 'ਤੇ ਛੱਡ ਦਿੰਦੀ ਹੈ | **ਸਲਾਹਕਾਰੀ** | -| `check:workflows` | Workflow lint (actionlint + zizmor); ਗੁੰਮ/ਖ਼ਰਾਬ scanners, ਅਵੈਧ reports ਜਾਂ ਗੁੰਮ ratchet baseline ਨੂੰ INCOMPLETE ਵਜੋਂ ਅਸਫਲ ਕਰਦੇ ਹਨ। ਵੈਧ findings ਚੁਣੀ ਗਈ strict/advisory/ratchet policy ਦੀ ਪਾਲਣਾ ਕਰਦੀਆਂ ਹਨ | ਕਾਰਵਾਈ ਲਾਜ਼ਮੀ; CI ਵਿੱਚ zizmor ratchet ਰੋਕਣ ਵਾਲਾ ਹੈ | -| `check:openapi-breaking` | ਮੂਲ branch ਦੇ ਮੁਕਾਬਲੇ ਜਨਤਕ API contract (`openapi.yaml`) ਵਿੱਚ breaking changes (oasdiff) — `openapiBreaking=N` ਜਾਰੀ ਕਰਦਾ ਹੈ; oasdiff ਮੌਜੂਦ ਨਾ ਹੋਣ ਜਾਂ ਮੂਲ spec resolve ਨਾ ਹੋਣ 'ਤੇ ਛੱਡ ਦਿੰਦਾ ਹੈ | **ਸਲਾਹਕਾਰੀ** | - -### Job: `docs-sync-strict` +| Script (`npm run ...`) | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦਾ ਹੈ | Blocking | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| `check:node-runtime` | Node.js version ਸਮਰਥਿਤ range ਦੇ ਅੰਦਰ ਹੈ | ਹਾਂ | +| `check:cycles` | ਪੂਰੇ `src/` + `open-sse/` ਵਿੱਚ circular imports (AST-ਅਧਾਰਿਤ, tsconfig `paths` resolve ਕੀਤੇ ਹੋਏ)। Bare = advisory, cycles ਦੀ ਸੂਚੀ ਦਿੰਦਾ ਹੈ। `check:cycles:ratchet` (ਜੋ CI ਚਲਾਉਂਦਾ ਹੈ) ਉਸ ਵੇਲੇ block ਕਰਦਾ ਹੈ ਜਦੋਂ ਗਿਣਤੀ `quality-baseline.json` ਵਿੱਚ `metrics.cycles` ceiling ਤੋਂ ਵੱਧ ਜਾਂਦੀ ਹੈ — ਇਸ ਵੇਲੇ 14, `direction: down`, ਇਸ ਲਈ ਇਹ ਸਿਰਫ਼ ਘਟ ਸਕਦੀ ਹੈ (#15159 G-01/G-02) | ਹਾਂ (ratchet) | +| `check:route-validation:t06` | ਸਾਰੇ routes ਉੱਤੇ Zod schemas ਮੌਜੂਦ ਹਨ (Tier 6 policy) | ਹਾਂ | +| `check:any-budget:t11` | `@ts-expect-error // any` ਦੀ ਗਿਣਤੀ budget ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ (Tier 11 catraca) | ਹਾਂ | +| `check:provider-consistency` | `providers.ts` ਵਿੱਚ ਹਰ ਪ੍ਰੋਵਾਈਡਰ ਦੀ `providerRegistry.ts` ਵਿੱਚ ਇੱਕ ਮੇਲ ਖਾਂਦੀ ਐਂਟਰੀ ਹੈ (ਅਤੇ allowlist ਦੇ ਅੰਦਰ ਇਸਦੇ ਉਲਟ ਵੀ) | ਹਾਂ | +| `check:model-lifecycle` | ਹੱਥੀਂ ਸੰਭਾਲੀਆਂ ਜਾਣ ਵਾਲੀਆਂ ਤਿੰਨ ਰਾਊਟਿੰਗ ਟੇਬਲਾਂ ਚੈੱਕ-ਇਨ ਕੀਤੇ ਲਾਈਫਸਾਈਕਲ ਸਨੈਪਸ਼ਾਟ (#11503) ਨਾਲ ਇਕਸਾਰ ਰਹਿੰਦੀਆਂ ਹਨ: `FITNESS_TABLE` (`taskFitness.ts`) ਕਿਸੇ ਅਜਿਹੀ ਰਿਟਾਇਰਡ id ਨੂੰ ਸਕੋਰ ਨਹੀਂ ਕਰਦੀ ਜਿਸਨੂੰ `REGISTRY` ਰਾਊਟ ਕਰ ਸਕਦੀ ਹੈ; ਹਰ `BUILT_IN_ALIASES` ਟਾਰਗੇਟ `REGISTRY` ਵਿੱਚ ਮੌਜੂਦ ਅਤੇ ਰਿਟਾਇਰਡ-id ਸਨੈਪਸ਼ਾਟ ਤੋਂ ਗੈਰਹਾਜ਼ਰ ਹੈ; `REGISTRY` ਵਿੱਚ ਅਜੇ ਵੀ ਮੌਜੂਦ ਹਰ ਰਿਟਾਇਰਡ id ਨੂੰ ਫਾਰਵਰਡ ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ `allowedRetiredInCatalog` ਵਿੱਚ ਸੂਚੀਬੱਧ ਕੀਤਾ ਗਿਆ ਹੈ; ਅਤੇ ਕੋਈ ਵੀ `DEFAULT_DEGRADATION_MAP` ਸਰੋਤ ਜਾਂ ਟਾਰਗੇਟ ਉਸ ਸਨੈਪਸ਼ਾਟ ਵਿੱਚ ਰਿਟਾਇਰਡ ਵਜੋਂ ਨਹੀਂ ਦਿਸਦਾ। ਇਹ ਸਾਬਤ ਨਹੀਂ ਕਰਦਾ ਕਿ ਕੋਈ ਮਾਡਲ ਇਸ ਵੇਲੇ ਕਿਸੇ ਲਾਈਵ ਅੱਪਸਟ੍ਰੀਮ ਵੱਲੋਂ ਸਰਵ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ। ਆਫਲਾਈਨ — `config/quality/model-lifecycle.json` ਨਾਲ ਤੁਲਨਾ ਕਰਦਾ ਹੈ, ਜਿਸਨੂੰ `npm run quality:refresh-model-lifecycle` (ਨੈੱਟਵਰਕ; CI ਨਾਲ ਜੋੜਿਆ ਨਹੀਂ ਗਿਆ) ਰਾਹੀਂ ਹੱਥੀਂ ਰਿਫ੍ਰੈਸ਼ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। `allowedRetiredInCatalog` ਇੱਕ ਬਰਨ-ਡਾਊਨ ਰੈਚਟ ਹੈ: ਕੋਈ ਐਂਟਰੀ ਸਿਰਫ਼ ਟ੍ਰੈਕਿੰਗ ਇਸ਼ੂ ਦੇ ਨਾਲ ਹੀ ਸ਼ਾਮਲ ਕਰੋ। | ਹਾਂ | +| `check:fetch-targets` | ਕਲਾਇੰਟ-ਸਾਈਡ `src/` ਵਿੱਚ ਹਰ `fetch("/api/...")` ਇੱਕ ਅਸਲ `route.ts` ਤੱਕ ਰਿਜ਼ਾਲਵ ਹੁੰਦਾ ਹੈ | ਹਾਂ | +| `check:deps` | ਰਿਪੋ ਵਿੱਚ ਹਰ `package.json` ਦੀਆਂ ਸਾਰੀਆਂ `npm install`-ਯੋਗ ਡਿਪੈਂਡੈਂਸੀਆਂ `dependency-allowlist.json` ਵਿੱਚ ਹਨ; ਨਵੇਂ ਅਨਪਿੰਨ ਜਾਂ slopsquatted ਪੈਕੇਜ ਫਲੈਗ ਕੀਤੇ ਜਾਂਦੇ ਹਨ | ਹਾਂ | +| `audit:deps` | `npm audit` (ਰੂਟ + electron) — ਕੋਈ ਉੱਚ/ਨਾਜ਼ੁਕ ਐਡਵਾਈਜ਼ਰੀ ਨਹੀਂ (osv `check:vuln-ratchet` ਨਾਲ ਓਵਰਲੈਪ ਕਰਦਾ ਹੈ; Rationalization Backlog ਵੇਖੋ) | ਹਾਂ | +| `check:lockfile` | `package-lock.json` ਦੀ ਅਖੰਡਤਾ — https ਰਜਿਸਟਰੀ, ਅਖੰਡਤਾ ਹੈਸ਼, ਕੋਈ ਹੋਸਟ ਓਵਰਰਾਈਡ ਨਹੀਂ | ਹਾਂ | +| `check:licenses` | ਪ੍ਰੋਡਕਸ਼ਨ ਡਿਪੈਂਡੈਂਸੀਜ਼ ਲਈ SPDX ਲਾਇਸੈਂਸ ਅਨੁਮਤੀ-ਸੂਚੀ | ਹਾਂ | +| `check:tracked-artifacts` | ਕੋਈ ਬਿਲਡ ਆਰਟੀਫੈਕਟ / ਕਮਿਟ ਕੀਤੇ `node_modules` ਸਿਮਲਿੰਕ ਨਹੀਂ (husky pre-commit ਵਿੱਚ ਵੀ ਚੱਲਦਾ ਹੈ; pre-push ਨੂੰ ਜਾਣ-ਬੁੱਝ ਕੇ ਹਲਕਾ ਰੱਖਿਆ ਗਿਆ ਹੈ — #6716) | ਹਾਂ | +| `check:ai-attribution` | PR ਕਮਿਟਾਂ, ਸਿਰਲੇਖ ਜਾਂ ਮੁੱਖ ਭਾਗ ਵਿੱਚ ਕੋਈ AI/bot `Co-Authored-By` ਟ੍ਰੇਲਰ ਜਾਂ AI-ਜਨਰੇਸ਼ਨ ਫੁੱਟਰ ਨਹੀਂ — ਸਖ਼ਤ ਨਿਯਮ #16 (`quality.yml` ਦੇ PR→`release/**` ਲਈ fast-gates ਲੂਪ ਵਿੱਚ — ਇਵੈਂਟ ਪੇਲੋਡ ਪੜ੍ਹਦਾ ਹੈ, PR ਨਾ ਹੋਣ 'ਤੇ ਕੋਈ ਕਾਰਵਾਈ ਨਹੀਂ ਕਰਦਾ — ਅਤੇ PR→`main` ਲਈ `ci.yml` lint ਵਿੱਚ ਸਿਰਫ਼-PR ਪੜਾਅ; husky `commit-msg` ਹੁੱਕ ਵੀ; ਮਨੁੱਖੀ ਸਹਿ-ਲੇਖਕਾਂ ਦੀ ਆਗਿਆ ਹੈ; #14436) | +| `check:vitest-exclusions` | ਹਰ Vitest ਅਪਵਾਦ ਇੱਕ ਟ੍ਰੈਕਿੰਗ ਇਸ਼ੂ ਦਾ ਨਾਮ ਦਿੰਦਾ ਹੈ ਅਤੇ `config/quality/vitest-exclusions.json` ਵਿੱਚ ਮੌਜੂਦ ਹੁੰਦਾ ਹੈ (#13204) | ਹਾਂ | +| `check:file-size` | ਕੋਈ ਵੀ ਸਰੋਤ ਫ਼ਾਈਲ ਪ੍ਰਤੀ-ਐਕਸਟੈਂਸ਼ਨ ਸੀਮਾ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੁੰਦੀ (ਰੈਚਟ: `frozen` ਸੂਚੀ ਵਿੱਚ ਫ੍ਰੀਜ਼ ਕੀਤੀਆਂ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ) | ਹਾਂ | +| `check:error-helper` | executors/handlers ਵਿੱਚ ਗਲਤੀ ਜਵਾਬ `buildErrorBody()` / `sanitizeErrorMessage()` ਵਰਤਦੇ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #12) | ਹਾਂ | +| `check:migration-numbering` | Migration SQL ਫਾਈਲਾਂ ਨੂੰ ਬਿਨਾਂ ਕਿਸੇ ਖਾਲੀ ਥਾਂ ਜਾਂ ਦੁਹਰਾਅ ਦੇ ਲੜੀਵਾਰ ਨੰਬਰ ਦਿੱਤੇ ਗਏ ਹਨ | ਹਾਂ | +| `check:public-creds` | `publicCreds.ts` ਤੋਂ ਬਾਹਰ ਕੋਈ ਵੀ ਸ਼ਾਬਦਿਕ OAuth `client_id`/`client_secret` ਜਾਂ Firebase Web ਕੁੰਜੀਆਂ ਨਹੀਂ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #11) | ਹਾਂ | +| `check:db-rules` | `src/lib/db/` ਮੋਡੀਊਲਾਂ ਤੋਂ ਬਾਹਰ ਕੋਈ ਕੱਚਾ SQL ਨਹੀਂ; `localDb.ts` ਤੋਂ ਕੋਈ ਬੈਰਲ-ਇੰਪੋਰਟ ਨਹੀਂ (ਸਖ਼ਤ ਨਿਯਮ #2/#5) | ਹਾਂ | +| `check:known-symbols` | ਆਪਣੀਆਂ ਡਿਸਪੈਚ ਟੇਬਲਾਂ ਵਿੱਚ ਰਜਿਸਟਰ ਕੀਤੇ ਪ੍ਰੋਵਾਈਡਰ ਐਗਜ਼ੀਕਿਊਟਰ, ਰਾਊਟਿੰਗ ਰਣਨੀਤੀਆਂ ਅਤੇ ਅਨੁਵਾਦਕ ਡਿਸਕ ਉੱਤੇ ਮੌਜੂਦ ਫਾਈਲਾਂ ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਹਨ — ਕੋਈ ਅਨਾਥ ਜਾਂ ਅਘੋਸ਼ਿਤ ਸਿੰਬਲ ਨਹੀਂ | ਹਾਂ | +| `check:route-guard-membership` | ਚਾਈਲਡ ਪ੍ਰੋਸੈੱਸ ਸ਼ੁਰੂ ਕਰਨ ਵਾਲੇ ਹਰ ਰੂਟ ਨੂੰ `isLocalOnlyPath()` ਦੁਆਰਾ ਵਰਗੀਕ੍ਰਿਤ ਕੀਤਾ ਗਿਆ ਹੈ (ਸਖ਼ਤ ਨਿਯਮ #15/#17) | ਹਾਂ | +| `check:test-discovery` | ਰੈਪੋ ਵਿੱਚ ਹਰ `*.test.ts` / `*.spec.ts` ਫਾਈਲ ਨੂੰ ਘੱਟੋ-ਘੱਟ ਇੱਕ ਟੈਸਟ ਰਨਰ ਦੁਆਰਾ ਇਕੱਠਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ (ਰੈਚਟ: `test-discovery-baseline.json` ਵਿੱਚ ਅਨਾਥ ਸੂਚੀ ਸਿਰਫ਼ ਘੱਟ ਸਕਦੀ ਹੈ) | ਹਾਂ | +| `check:agent-skills-sync` | ਬਣਾਏ ਗਏ agent-skills ਆਰਟੀਫੈਕਟ ਆਪਣੇ ਸਰੋਤ ਕੈਟਾਲਾਗ ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਹਨ (ਕੋਈ ਅੰਤਰ ਨਹੀਂ) | +| `check:provider-asset-provenance` | ਪ੍ਰਦਾਤਾ ਦੇ ਲੋਗੋ/ਐਸੈੱਟਾਂ ਨਾਲ ਦਰਜ ਕੀਤਾ ਗਿਆ ਸਰੋਤ-ਵੇਰਵਾ ਜੁੜਿਆ ਹੁੰਦਾ ਹੈ | +| `lint:json` | JSON ਸੰਰਚਨਾ ਫ਼ਾਈਲਾਂ ਪਾਰਸ ਹੁੰਦੀਆਂ ਹਨ ਅਤੇ ਰਿਪੋਜ਼ਟਰੀ ਦੇ lint ਨਿਯਮਾਂ ਨੂੰ ਪੂਰਾ ਕਰਦੀਆਂ ਹਨ | +| `typecheck:core` | ਬਿਨਾਂ ਗਲਤੀਆਂ ਦੇ TypeScript ਕੰਪਾਇਲੇਸ਼ਨ (ਸਿਰਫ਼ ਸਲਾਹਕਾਰੀ ਚੇਤਾਵਨੀਆਂ) | ਹਾਂ | +| `typecheck:noimplicit:core` | ਸਖ਼ਤ `noImplicitAny` — ਭਵਿੱਖ-ਕੇਂਦਰਿਤ; ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਕਈ ਕਾਲ ਸਾਈਟਾਂ ਨੂੰ ਹਾਲੇ ਵੀ ਐਨੋਟੇਸ਼ਨਾਂ ਦੀ ਲੋੜ ਹੈ | **ਸਲਾਹਕਾਰੀ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` ਦਾ ਦਾਇਰਾ `src/app/(dashboard)/**` ਤੱਕ ਸੀਮਿਤ ਹੈ (#7033) — `typecheck:core` ਦੀ ਚੁਣੀ ਹੋਈ 27-ਫ਼ਾਈਲਾਂ ਦੀ ਮਨਜ਼ੂਰ-ਸੂਚੀ ਵਿੱਚ ਕੋਈ ਵੀ ਡੈਸ਼ਬੋਰਡ TSX ਸ਼ਾਮਲ ਨਹੀਂ ਹੈ, ਅਤੇ `next build` ਵੀ ਕਦੇ ਇਸ ਦੀ ਟਾਈਪ-ਜਾਂਚ ਨਹੀਂ ਕਰਦਾ (`next.config.mjs` ਵਿੱਚ `ignoreBuildErrors: true` ਸੈੱਟ ਹੈ), ਇਸ ਲਈ ਉੱਥੇ orphaned-identifier ਰਿਗ੍ਰੈਸ਼ਨ (#6625/#6909) CI ਨੂੰ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੇ ਸਨ। ਇੱਕ ਸਥਿਰ ਕੀਤੀ ਪ੍ਰਤੀ-ਫ਼ਾਈਲ/ਪ੍ਰਤੀ-TS-ਕੋਡ ਗਿਣਤੀ ਬੇਸਲਾਈਨ (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ਵਾਂਗ ਹੀ stale-enforcement ਪੈਟਰਨ) ਨਾਲ ਅੰਤਰਾਂ ਦੀ ਤੁਲਨਾ ਕੀਤੀ ਜਾਂਦੀ ਹੈ — ਬੇਸਲਾਈਨ ਕੀਤੀ ਗਿਣਤੀ ਤੋਂ ਵੱਧ ਸਿਰਫ਼ ਨਵੀਆਂ ਗਲਤੀਆਂ ਹੀ ਗੇਟ ਨੂੰ ਅਸਫਲ ਕਰਦੀਆਂ ਹਨ; ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਗਲਤੀ ਠੀਕ ਹੋਣ 'ਤੇ `--update` ਨਾਲ ਬੇਸਲਾਈਨ ਘਟਾਓ। | ਹਾਂ | -`main` ਲਈ ਹਰ PR ਉੱਤੇ ਚੱਲਦਾ ਹੈ। ਅਸਫਲ ਹੋਣ 'ਤੇ merge ਨੂੰ ਰੋਕਦਾ ਹੈ। +### ਜੌਬ: `quality-gate` -| ਸਕ੍ਰਿਪਟ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਬਲੌਕਿੰਗ | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `check:docs-all` | ਮੈਟਾ-ਗੇਟ ਜੋ ਹੇਠਾਂ ਦਿੱਤੇ 6 ਸਬ-ਗੇਟਾਂ ਨੂੰ ਕ੍ਰਮਵਾਰ ਚਲਾਉਂਦਾ ਹੈ | ਹਾਂ | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ਵਰਜਨ ਇਕਸਾਰਤਾ | ਹਾਂ | -| ↳ `check:docs-counts` | ਗੱਦ ਵਿੱਚ ਦਿੱਤੀਆਂ ਗਿਣਤੀਆਂ (ਪ੍ਰਦਾਤਾ ਗਿਣਤੀ, ਮਾਈਗ੍ਰੇਸ਼ਨ ਗਿਣਤੀ, ਆਦਿ) ਅਸਲ ਗਿਣਤੀਆਂ ਦੀ ਰੈਚਿਟ ਵਿੰਡੋ ਦੇ ਅੰਦਰ ਹਨ | ਹਾਂ | -| ↳ `check:env-doc-sync` | `.env.example` ਵਿੱਚ ਹਰ env var ਕਿਸੇ ਦਸਤਾਵੇਜ਼ੀ ਸਾਰਣੀ ਵਿੱਚ ਦਰਜ ਹੈ, ਅਤੇ ਇਸਦੇ ਉਲਟ ਵੀ | ਹਾਂ | -| ↳ `check:deprecated-versions` | ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਕੋਈ ਬਰਖ਼ਾਸਤ ਵਰਜਨ ਸਟਰਿੰਗ ਨਹੀਂ ਹੈ | ਹਾਂ | -| ↳ `check:doc-links` | ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਅੰਦਰੂਨੀ markdown ਲਿੰਕ ਅਸਲ ਫ਼ਾਈਲਾਂ ਵੱਲ ਜਾਂਦੇ ਹਨ (`[text]`/`(path)` ਰੂਪ) | ਹਾਂ | -| ↳ `check:fabricated-docs` | ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਦਰਸਾਏ ਰੂਟ, env vars, CLI ਕਮਾਂਡਾਂ, ਹੁੱਕ ਨਾਮ ਅਤੇ ਫ਼ਾਈਲ ਪਾਥ ਕੋਡਬੇਸ ਵਿੱਚ ਮੌਜੂਦ ਹਨ। `--strict` ਰਾਹੀਂ ਹਾਰਡ ਗੇਟ; ਫਲੈਗ ਤੋਂ ਬਿਨਾਂ ਸੌਫਟ-ਫੇਲ। | ਹਾਂ (CI ਵਿੱਚ `--strict` ਰਾਹੀਂ) | -| `check:cli-i18n` | CLI ਕਮਾਂਡ ਸਟਰਿੰਗਾਂ ਸਾਰੀਆਂ i18n ਲੋਕੇਲ ਫ਼ਾਈਲਾਂ ਵਿੱਚ ਮੌਜੂਦ ਹਨ | ਹਾਂ | -| `check:openapi-coverage` | OpenAPI ਸਪੈਕ ਅਸਲ ਰੂਟਾਂ ਦੀ ਘੱਟੋ-ਘੱਟ ਰੈਚਿਟ ਕੀਤੀ ਹੱਦ ਨੂੰ ਕਵਰ ਕਰਦਾ ਹੈ | ਹਾਂ | -| `check:openapi-security-tiers` | `openapi.yaml` ਵਿੱਚ ਸੁਰੱਖਿਆ ਟੀਅਰ ਐਨੋਟੇਸ਼ਨ `routeGuard.ts` ਵਰਗੀਕਰਨਾਂ ਨਾਲ ਇਕਸਾਰ ਹਨ | **ਸਲਾਹਕਾਰੀ** | -| `check:openapi-routes` | `openapi.yaml` ਵਿੱਚ ਹਰ ਪਾਥ ਕਿਸੇ ਅਸਲ `route.ts` ਵੱਲ ਜਾਂਦਾ ਹੈ (ਭਰਮ-ਰੋਕੂ) | ਹਾਂ | -| `check:docs-symbols` | `docs/**/*.md` ਵਿੱਚ ਹਰ `/api/...` ਹਵਾਲਾ ਕਿਸੇ ਅਸਲ `route.ts` ਵੱਲ ਜਾਂਦਾ ਹੈ (ਭਰਮ-ਰੋਕੂ) | ਹਾਂ | -| `i18n translation drift` | i18n ਲੋਕੇਲ ਫ਼ਾਈਲਾਂ ਵਿੱਚ ਅਨੁਵਾਦ ਨਾ ਕੀਤੀਆਂ ਕੁੰਜੀਆਂ — ਸਿਰਫ਼ ਚੇਤਾਵਨੀ | **ਸਲਾਹਕਾਰੀ** | +`test-coverage` ਤੋਂ ਬਾਅਦ ਚੱਲਦੀ ਹੈ। ਅਸਫਲ ਹੋਣ 'ਤੇ ਮਰਜ ਨੂੰ ਰੋਕਦੀ ਹੈ। + +| ਸਕ੍ਰਿਪਟ | ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਰੋਕਣ ਵਾਲੀ | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | +| `quality:collect` | `quality-metrics.json` ਉਤਪੰਨ ਕਰਦੀ ਹੈ (ESLint ਚੇਤਾਵਨੀਆਂ ਦੀ ਗਿਣਤੀ, ਮਿਲਾਈ ਗਈ shard ਰਿਪੋਰਟ ਤੋਂ coverage) | ਹਾਂ (ratchet ਤੋਂ ਪਹਿਲਾਂ) | +| `quality:ratchet` | `quality-baseline.json` ਵਿੱਚ ਹਰੇਕ metric ਪਿੱਛੇ ਨਹੀਂ ਗਈ ਹੈ (ESLint ਚੇਤਾਵਨੀਆਂ ≤ baseline; coverage ≥ baseline) | ਹਾਂ | +| `check:duplication` | Code duplication (jscpd@4), `quality-baseline.json` ਵਿੱਚ baseline ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ | ਹਾਂ | +| `check:complexity` | ਫ਼ਾਈਲ-ਪੱਧਰੀ cyclomatic complexity ਨਿਰਧਾਰਤ ਸੀਮਾ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ (ਮੁੱਖ ESLint `complexity` + `max-lines-per-function`) | ਹਾਂ | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — ਵੱਖਰਾ ESLint pass; CI ਦੋਵਾਂ ਨੂੰ ਇੱਕੋ `check:complexity-ratchets` ਪੜਾਅ ਵਜੋਂ ਮਿਲਾ ਕੇ ਚਲਾਉਂਦਾ ਹੈ | ਹਾਂ | +| `check:dead-code` | ਨਾ ਵਰਤੇ ਗਏ exports / files ਦਾ ratchet (knip), baseline ਦੇ ਮੁਕਾਬਲੇ ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦਾ | ਹਾਂ | +| `check:compression-budget` | Compression benchmark budget — ਪ੍ਰਤੀ-engine token-savings ਦੀਆਂ ਘੱਟੋ-ਘੱਟ ਹੱਦਾਂ ਪਿੱਛੇ ਨਹੀਂ ਜਾਣੀਆਂ ਚਾਹੀਦੀਆਂ | ਹਾਂ | +| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦਾ; ਵੱਡੇ ਪੱਧਰ 'ਤੇ `typecheck:noimplicit:core` ਨੂੰ ਸਮੇਟਦਾ ਹੈ | ਹਾਂ | +| `check:codeql-ratchet` | ਖੁੱਲ੍ਹੀਆਂ CodeQL ਚੇਤਾਵਨੀਆਂ ਦੀ ਗਿਣਤੀ ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦੀ (`gh api` ਰਾਹੀਂ ਪੜ੍ਹਦਾ ਹੈ; token ਤੋਂ ਬਿਨਾਂ ਸੁਚੱਜੇ ਢੰਗ ਨਾਲ ਛੱਡ ਦਿੰਦਾ ਹੈ) — refresh cadence ਅਤੇ ਹੱਥੀਂ trigger ਲਈ: ਹੇਠਾਂ "CodeQL ratchet" ਵੇਖੋ | ਹਾਂ | + +### ਜੌਬ: `quality-extended` + +ਪੂਰੀ ਜੌਬ ਸਲਾਹਕਾਰੀ ਹੈ (`continue-on-error: true`)। npm-ਅਧਾਰਿਤ ratchets ਅਸਲ ਵਿੱਚ +ਚੱਲਦੇ ਹਨ; ਬਾਹਰੀ scanners `gh release download` ਰਾਹੀਂ install ਹੁੰਦੇ ਹਨ ਅਤੇ ਜਦੋਂ ਕੋਈ +binary ਅਜੇ ਵੀ ਮੌਜੂਦ ਨਾ ਹੋਵੇ ਤਾਂ ਆਪਣੇ ਆਪ ਛੱਡ ਦਿੰਦੇ ਹਨ (exit 0)। + +| ਸਕ੍ਰਿਪਟ | ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਰੋਕਣ ਵਾਲੀ | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `check:circular-deps` | ਕੋਈ circular dependencies ਨਹੀਂ (dpdm) | **ਸਲਾਹਕਾਰੀ** | +| `check:bundle-size` | Bundle size ਨਿਰਧਾਰਤ ਸੀਮਾ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੈ | **ਸਲਾਹਕਾਰੀ** | +| `check:secrets` | Secret scanning (gitleaks) — binary ਮੌਜੂਦ ਨਾ ਹੋਣ 'ਤੇ ਛੱਡ ਦਿੰਦੀ ਹੈ | **ਸਲਾਹਕਾਰੀ** | +| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) ਪਿੱਛੇ ਨਹੀਂ ਜਾਂਦੀਆਂ — binary ਮੌਜੂਦ ਨਾ ਹੋਣ 'ਤੇ ਛੱਡ ਦਿੰਦੀ ਹੈ | **ਸਲਾਹਕਾਰੀ** | +| `check:workflows` | Workflow lint (actionlint + zizmor); ਗੁੰਮ/ਖ਼ਰਾਬ scanners, ਅਵੈਧ reports ਜਾਂ ਗੁੰਮ ratchet baseline ਨੂੰ INCOMPLETE ਮੰਨ ਕੇ ਅਸਫਲ ਕਰਦੇ ਹਨ। ਵੈਧ findings ਚੁਣੀ ਗਈ strict/advisory/ratchet policy ਦੀ ਪਾਲਣਾ ਕਰਦੀਆਂ ਹਨ | ਚਲਾਉਣਾ ਲਾਜ਼ਮੀ; CI ਵਿੱਚ zizmor ratchet ਰੋਕਣ ਵਾਲਾ ਹੈ | +| `check:openapi-breaking` | ਮੁੱਢਲੀ branch ਦੇ ਮੁਕਾਬਲੇ ਜਨਤਕ API contract (`openapi.yaml`) ਵਿੱਚ breaking changes (oasdiff) — `openapiBreaking=N` ਉਤਪੰਨ ਕਰਦੀ ਹੈ; oasdiff ਮੌਜੂਦ ਨਾ ਹੋਣ ਜਾਂ ਮੁੱਢਲੀ spec ਹੱਲ ਨਾ ਹੋ ਸਕਣ 'ਤੇ ਛੱਡ ਦਿੰਦੀ ਹੈ | **ਸਲਾਹਕਾਰੀ** | + +### ਜੌਬ: `docs-sync-strict` + +`main` ਲਈ ਹਰੇਕ PR ਉੱਤੇ ਚੱਲਦੀ ਹੈ। ਅਸਫਲ ਹੋਣ 'ਤੇ merge ਨੂੰ ਰੋਕਦੀ ਹੈ। + +| ਸਕ੍ਰਿਪਟ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਰੋਕਣ ਵਾਲੀ | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `check:docs-all` | ਮੈਟਾ-ਗੇਟ ਜੋ ਹੇਠਾਂ ਦਿੱਤੇ 6 ਉਪ-ਗੇਟਾਂ ਨੂੰ ਕ੍ਰਮਵਾਰ ਚਲਾਉਂਦਾ ਹੈ | ਹਾਂ | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt ਸੰਸਕਰਣ ਇਕਸਾਰਤਾ | ਹਾਂ | +| ↳ `check:docs-counts` | ਗੱਥ ਵਿੱਚ ਦਿੱਤੀਆਂ ਗਿਣਤੀਆਂ (ਪ੍ਰਦਾਤਾ ਗਿਣਤੀ, ਮਾਈਗ੍ਰੇਸ਼ਨ ਗਿਣਤੀ ਆਦਿ) ਅਸਲ ਗਿਣਤੀਆਂ ਦੀ ਰੈਚਟ ਸੀਮਾ ਦੇ ਅੰਦਰ ਹਨ | ਹਾਂ | +| ↳ `check:env-doc-sync` | `.env.example` ਵਿੱਚ ਮੌਜੂਦ ਹਰ env var ਨੂੰ ਦਸਤਾਵੇਜ਼ਾਂ ਦੀ ਇੱਕ ਸਾਰਣੀ ਵਿੱਚ ਦਰਜ ਕੀਤਾ ਗਿਆ ਹੈ, ਅਤੇ ਇਸਦੇ ਉਲਟ ਵੀ | ਹਾਂ | +| ↳ `check:deprecated-versions` | ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਕੋਈ ਵੀ ਬਰਖ਼ਾਸਤ ਸੰਸਕਰਣ ਸਤਰ ਨਹੀਂ ਹੈ | ਹਾਂ | +| ↳ `check:doc-links` | ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਅੰਦਰੂਨੀ markdown ਲਿੰਕ ਅਸਲ ਫ਼ਾਈਲਾਂ ਵੱਲ ਜਾਂਦੇ ਹਨ (`[text]`/`(path)` ਰੂਪ) | ਹਾਂ | +| ↳ `check:fabricated-docs` | ਦਸਤਾਵੇਜ਼ਾਂ ਵਿੱਚ ਦਰਸਾਏ ਗਏ ਰੂਟ, env vars, CLI ਕਮਾਂਡਾਂ, ਹੁੱਕ ਨਾਮ ਅਤੇ ਫ਼ਾਈਲ ਪਾਥ ਕੋਡਬੇਸ ਵਿੱਚ ਮੌਜੂਦ ਹਨ। `--strict` ਰਾਹੀਂ ਹਾਰਡ ਗੇਟ; ਫ਼ਲੈਗ ਤੋਂ ਬਿਨਾਂ ਸੌਫ਼ਟ-ਫੇਲ। | ਹਾਂ (CI ਵਿੱਚ `--strict` ਰਾਹੀਂ) | +| `check:cli-i18n` | CLI ਕਮਾਂਡ ਸਤਰਾਂ ਸਾਰੀਆਂ i18n ਲੋਕੇਲ ਫ਼ਾਈਲਾਂ ਵਿੱਚ ਮੌਜੂਦ ਹਨ | ਹਾਂ | +| `check:openapi-coverage` | OpenAPI ਵਿਸ਼ੇਸ਼ਤਾ ਅਸਲ ਰੂਟਾਂ ਦੀ ਘੱਟੋ-ਘੱਟ ਰੈਚਟ ਕੀਤੀ ਹੇਠਲੀ ਸੀਮਾ ਨੂੰ ਕਵਰ ਕਰਦੀ ਹੈ | ਹਾਂ | +| `check:openapi-security-tiers` | `openapi.yaml` ਵਿੱਚ ਸੁਰੱਖਿਆ ਟੀਅਰ ਐਨੋਟੇਸ਼ਨ `routeGuard.ts` ਵਰਗੀਕਰਨਾਂ ਨਾਲ ਇਕਸਾਰ ਹਨ | **ਸਲਾਹਕਾਰੀ** | +| `check:openapi-routes` | `openapi.yaml` ਵਿੱਚ ਹਰ ਪਾਥ ਇੱਕ ਅਸਲ `route.ts` ਵੱਲ ਜਾਂਦਾ ਹੈ (ਭਰਮ-ਰੋਕੂ) | ਹਾਂ | +| `check:docs-symbols` | `docs/**/*.md` ਵਿੱਚ ਹਰ `/api/...` ਹਵਾਲਾ ਇੱਕ ਅਸਲ `route.ts` ਵੱਲ ਜਾਂਦਾ ਹੈ (ਭਰਮ-ਰੋਕੂ) | ਹਾਂ | +| `i18n translation drift` | i18n ਲੋਕੇਲ ਫ਼ਾਈਲਾਂ ਵਿੱਚ ਅਨੁਵਾਦ ਨਾ ਕੀਤੀਆਂ ਕੁੰਜੀਆਂ — ਸਿਰਫ਼ ਚੇਤਾਵਨੀ | **ਸਲਾਹਕਾਰੀ** | ### ਜੌਬ: `i18n-ui-coverage` -| ਸਕ੍ਰਿਪਟ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਬਲੌਕਿੰਗ | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | -| `check-ui-keys-coverage` (ਇਨਲਾਈਨ) | UI i18n ਕੁੰਜੀ ਕਵਰੇਜ ≥ 65% ਹੈ | ਹਾਂ | -| `check-ui-value-drift` (ਇਨਲਾਈਨ) | ਮੁੜ ਲਿਖਿਆ ਗਿਆ ਅੰਗਰੇਜ਼ੀ **ਮੁੱਲ** ਕੋਈ ਪੁਰਾਣਾ ਅਨੁਵਾਦ ਪਿੱਛੇ ਨਹੀਂ ਛੱਡਦਾ | ਹਾਂ | -| `check-new-key-coverage` (ਇਨਲਾਈਨ) | ਇੱਕ **ਨਵੀਂ** ਅੰਗਰੇਜ਼ੀ ਕੁੰਜੀ ਹਰ ਲੋਕੇਲ ਵਿੱਚ ਅਨੁਵਾਦ ਕੀਤੀ ਗਈ ਹੈ — ਇੱਕ `__MISSING__:` ਮਾਰਕਰ ਅਸਵੀਕਾਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ | ਹਾਂ | -| `check-translation-ratio` | ਪ੍ਰਤੀ ਲੋਕੇਲ ਅਸਲ-ਅਨੁਵਾਦ ਅਨੁਪਾਤ (ਅੰਗਰੇਜ਼ੀ ਦੇ ਸਮਾਨ / ਪਲੇਸਹੋਲਡਰ / ਮਨਜ਼ੂਰਸ਼ੁਦਾ ਸੂਚੀ ਤੋਂ ਬਾਹਰ ਗੁੰਮ ਐਂਟਰੀਆਂ) `config/quality/i18n-translation-baseline.json` + ਛੋਟ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ | **ਸਲਾਹਕਾਰੀ** | +| ਸਕ੍ਰਿਪਟ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਰੋਕਣ ਵਾਲੀ | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `check-ui-keys-coverage` (ਇਨਲਾਈਨ) | UI i18n ਕੁੰਜੀ ਕਵਰੇਜ ≥ 65% ਹੈ | ਹਾਂ | +| `check-ui-value-drift` (ਇਨਲਾਈਨ) | ਮੁੜ ਲਿਖੇ ਗਏ ਅੰਗਰੇਜ਼ੀ **ਮੁੱਲ** ਤੋਂ ਬਾਅਦ ਕੋਈ ਪੁਰਾਣਾ ਅਨੁਵਾਦ ਬਾਕੀ ਨਹੀਂ ਰਹਿੰਦਾ | ਹਾਂ | +| `check-new-key-coverage` (ਇਨਲਾਈਨ) | ਇੱਕ **ਨਵੀਂ** ਅੰਗਰੇਜ਼ੀ ਕੁੰਜੀ ਦਾ ਹਰ ਲੋਕੇਲ ਵਿੱਚ ਅਨੁਵਾਦ ਕੀਤਾ ਗਿਆ ਹੈ — ਇੱਕ `__MISSING__:` ਮਾਰਕਰ ਅਸਵੀਕਾਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ | ਹਾਂ | +| `check-translation-ratio` | ਹਰ ਲੋਕੇਲ ਲਈ ਅਸਲ-ਅਨੁਵਾਦ ਅਨੁਪਾਤ (ਅੰਗਰੇਜ਼ੀ ਨਾਲ ਇੱਕੋ ਜਿਹੇ / ਪਲੇਸਹੋਲਡਰ / ਮਨਜ਼ੂਰ-ਸੂਚੀ ਤੋਂ ਬਾਹਰ ਗੁੰਮ ਪੱਤੇ) `config/quality/i18n-translation-baseline.json` + ਢਿੱਲ ਤੋਂ ਵੱਧ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ | **ਸਲਾਹਕਾਰੀ** | -`fetch-depth: 0` ਦੀ ਲੋੜ ਹੈ — ਵੈਲਿਊ-ਡ੍ਰਿਫਟ ਗੇਟ ਮਰਜ ਬੇਸ ਦੇ ਮੁਕਾਬਲੇ `en.json` ਦਾ diff ਕਰਦਾ ਹੈ। +`fetch-depth: 0` ਦੀ ਲੋੜ ਹੈ — ਮੁੱਲ-ਡ੍ਰਿਫਟ ਗੇਟ `en.json` ਦੀ merge base ਨਾਲ ਤੁਲਨਾ ਕਰਦਾ ਹੈ। #### `check-ui-value-drift` — ਪੁਰਾਣੇ-ਅਨੁਵਾਦ ਦਾ ਗੇਟ -ਇਹ ਉਸ ਇੱਕ i18n ਰਿਗ੍ਰੈਸ਼ਨ ਨੂੰ ਫੜਦਾ ਹੈ ਜਿਸਨੂੰ ਹੋਰ ਗੇਟ ਸੰਰਚਨਾਤਮਕ ਤੌਰ 'ਤੇ ਨਹੀਂ ਦੇਖ ਸਕਦੇ: ਇੱਕ ਅੰਗਰੇਜ਼ੀ ਮੁੱਲ -ਮੁੜ ਲਿਖਿਆ ਜਾਂਦਾ ਹੈ ਅਤੇ _ਪਿਛਲੇ_ ਅੰਗਰੇਜ਼ੀ ਮੁੱਲ ਤੋਂ ਬਣੇ ਅਨੁਵਾਦ ਉੱਥੇ ਹੀ ਰਹਿ ਜਾਂਦੇ ਹਨ, ਜਿਸ ਕਾਰਨ -ਗੈਰ-ਅੰਗਰੇਜ਼ੀ ਵਰਤੋਂਕਾਰ ਭਰੋਸੇ ਨਾਲ ਲਿਖੀ ਹੋਈ, ਪਰ ਹੁਣ ਗਲਤ ਕਾਪੀ ਪੜ੍ਹਦੇ ਰਹਿੰਦੇ ਹਨ। +ਇਹ ਉਸ ਇੱਕ i18n ਰਿਗ੍ਰੈਸ਼ਨ ਨੂੰ ਫੜਦਾ ਹੈ ਜਿਸਨੂੰ ਹੋਰ ਗੇਟ ਸੰਰਚਨਾਤਮਕ ਤੌਰ 'ਤੇ ਨਹੀਂ ਦੇਖ ਸਕਦੇ: ਕਿਸੇ ਅੰਗਰੇਜ਼ੀ ਮੁੱਲ ਨੂੰ +ਮੁੜ ਲਿਖਿਆ ਜਾਂਦਾ ਹੈ ਅਤੇ _ਪਿਛਲੀ_ ਅੰਗਰੇਜ਼ੀ ਤੋਂ ਬਣੇ ਅਨੁਵਾਦ ਪਿੱਛੇ ਹੀ ਰਹਿ ਜਾਂਦੇ ਹਨ, ਇਸ ਲਈ +ਗ਼ੈਰ-ਅੰਗਰੇਜ਼ੀ ਵਰਤੋਂਕਾਰ ਭਰੋਸੇ ਨਾਲ ਲਿਖੀ, ਪਰ ਹੁਣ ਗ਼ਲਤ, ਸਮੱਗਰੀ ਪੜ੍ਹਦੇ ਰਹਿੰਦੇ ਹਨ। -ਇਹ ਅਸਲ ਵਿੱਚ ਰਿਲੀਜ਼ ਹੋਇਆ ਸੀ। ਜਦੋਂ Antigravity ਲੌਗਇਨ ਸਹਾਇਕ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ (#5203), ਤਾਂ `oauthModal.googleOAuthWarning` -ਨੂੰ ਮੁੜ ਲਿਖਿਆ ਗਿਆ ਸੀ; **43 ਵਿੱਚੋਂ 39 ਲੋਕੇਲਾਂ** ਵਿੱਚ ਆਪਰੇਟਰਾਂ ਨੂੰ "ਪੂਰਾ URL ਕਾਪੀ ਕਰੋ -ਅਤੇ ਇਸਨੂੰ ਹੇਠਾਂ ਪੇਸਟ ਕਰੋ" ਦੱਸਣ ਵਾਲਾ ਟੈਕਸਟ ਹੀ ਰਿਹਾ — ਇੱਕ ਅਜਿਹਾ ਪ੍ਰਵਾਹ ਜੋ ਉਸ ਪ੍ਰਦਾਤਾ ਲਈ ਪੂਰਾ ਨਹੀਂ ਹੋ ਸਕਦਾ। ਇਸਦਾ -#8463 ਤੱਕ ਪਤਾ ਨਹੀਂ ਲੱਗਿਆ ਕਿਉਂਕਿ: +ਇਹ ਅਸਲ ਵਿੱਚ ਰਿਲੀਜ਼ ਹੋਇਆ ਸੀ। ਜਦੋਂ Antigravity +ਲੌਗਇਨ ਸਹਾਇਕ ਸ਼ਾਮਲ ਹੋਇਆ (#5203), ਤਾਂ `oauthModal.googleOAuthWarning` ਨੂੰ ਮੁੜ ਲਿਖਿਆ ਗਿਆ ਸੀ; **43 ਵਿੱਚੋਂ 39 ਲੋਕੇਲਾਂ** ਵਿੱਚ ਉਹੀ ਲਿਖਤ ਰਹੀ ਜੋ ਓਪਰੇਟਰਾਂ ਨੂੰ "ਪੂਰਾ +URL ਕਾਪੀ ਕਰਕੇ ਹੇਠਾਂ ਪੇਸਟ ਕਰੋ" ਕਹਿੰਦੀ ਸੀ — ਇੱਕ ਅਜਿਹਾ ਪ੍ਰਵਾਹ ਜੋ ਉਸ ਪ੍ਰਦਾਤਾ ਲਈ ਪੂਰਾ ਨਹੀਂ ਹੋ ਸਕਦਾ। ਇਹ +#8463 ਤੱਕ ਅਣਦੇਖਿਆ ਰਿਹਾ ਕਿਉਂਕਿ: -- `sync-ui-keys` ਸਿਰਫ਼ ਉਹਨਾਂ ਕੁੰਜੀਆਂ ਨੂੰ ਬੈਕਫ਼ਿਲ ਕਰਦਾ ਹੈ ਜੋ **ਗੈਰਹਾਜ਼ਰ** ਹਨ, ਉਹਨਾਂ ਨੂੰ ਕਦੇ ਨਹੀਂ ਜੋ **ਪੁਰਾਣੀਆਂ** ਹਨ; -- `check-ui-keys-coverage` ਕੁੰਜੀ ਦੀ _ਮੌਜੂਦਗੀ_ ਗਿਣਦਾ ਹੈ, ਇਸ ਲਈ ਪੁਰਾਣਾ ਅਨੁਵਾਦ ਕਵਰ ਕੀਤਾ ਹੋਇਆ ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ; +- `sync-ui-keys` ਸਿਰਫ਼ ਉਹਨਾਂ ਕੁੰਜੀਆਂ ਨੂੰ ਬੈਕਫ਼ਿਲ ਕਰਦਾ ਹੈ ਜੋ **ਗੈਰ-ਮੌਜੂਦ** ਹਨ, **ਪੁਰਾਣੀਆਂ** ਕੁੰਜੀਆਂ ਨੂੰ ਕਦੇ ਨਹੀਂ; +- `check-ui-keys-coverage` ਕੁੰਜੀ ਦੀ _ਮੌਜੂਦਗੀ_ ਗਿਣਦਾ ਹੈ, ਇਸ ਲਈ ਇੱਕ ਪੁਰਾਣਾ ਅਨੁਵਾਦ ਵੀ ਕਵਰ ਕੀਤਾ ਹੋਇਆ ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ; - `check-translation-drift` `docs/i18n//**.md` ਦਸਤਾਵੇਜ਼ੀ ਮਿਰਰਾਂ ਨੂੰ ਟ੍ਰੈਕ ਕਰਦਾ ਹੈ — - ਇਹ ਕਦੇ ਵੀ `src/i18n/messages/*.json` ਨਹੀਂ ਪੜ੍ਹਦਾ। 2026-09 ਮੁੜ-ਸਿੰਕ ਤੋਂ ਬਾਅਦ ਜੌਬ `docs-sync-strict` ਵਿੱਚ ਬਲੌਕਿੰਗ: - ਇੱਕ ਮੁੱਖ ਦਸਤਾਵੇਜ਼ ਸੋਧੋ → `npm run i18n:run -- --files=` (ਸੈਕਸ਼ਨ-ਪੱਧਰੀ, ਘੱਟ ਖ਼ਰਚੀਲਾ)। + ਇਹ ਕਦੇ ਵੀ `src/i18n/messages/*.json` ਨਹੀਂ ਪੜ੍ਹਦਾ। 2026-09 ਦੀ ਮੁੜ-ਸਿੰਕ ਤੋਂ ਬਾਅਦ ਜੌਬ `docs-sync-strict` ਵਿੱਚ ਰੋਕਣ ਵਾਲਾ: + ਮੁੱਖ ਦਸਤਾਵੇਜ਼ ਸੋਧੋ → `npm run i18n:run -- --files=` (ਸੈਕਸ਼ਨ-ਪੱਧਰੀ, ਘੱਟ-ਖ਼ਰਚੀਲਾ)। -**ਡਿਫ਼-ਸਚੇਤ, ਬੇਸਲਾਈਨ-ਸਮਰਥਿਤ ਨਹੀਂ।** ਇਹ ਮਰਜ ਬੇਸ ਉੱਤੇ ਮੌਜੂਦ `en.json` ਦੀ ਵਰਕਿੰਗ ਟ੍ਰੀ ਨਾਲ ਤੁਲਨਾ ਕਰਦਾ ਹੈ; ਹਰ ਉਸ ਕੁੰਜੀ ਲਈ ਜਿਸਦਾ ਅੰਗਰੇਜ਼ੀ ਮੁੱਲ ਬਦਲਿਆ ਹੈ, ਕੋਈ ਵੀ ਲੋਕੇਲ ਜਿਸ ਵਿੱਚ ਅਜੇ ਵੀ ਨਾ-ਬਦਲਿਆ ਅਨੁਵਾਦ ਹੈ, ਪੁਰਾਣਾ ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ। ਇਹ ਜਾਣ-ਬੁੱਝ ਕੇ **ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਕਰਜ਼ੇ ਨੂੰ ਫ੍ਰੀਜ਼ ਕਰਦਾ ਹੈ** — ਕੋਈ ਡਿਫ਼ ਇਹ ਨਹੀਂ ਦੱਸ ਸਕਦਾ ਕਿ ਲੰਬੇ ਸਮੇਂ ਤੋਂ ਮੌਜੂਦ ਅਨੁਵਾਦ ਕਿਸ ਪੁਰਾਣੇ ਅੰਗਰੇਜ਼ੀ ਪਾਠ ਤੋਂ ਆਇਆ ਸੀ, ਇਸ ਲਈ ਗੇਟ ਸਿਰਫ਼ ਉਸੇ ਨੂੰ ਜਾਂਚਦਾ ਹੈ ਜਿਸਨੂੰ ਮੌਜੂਦਾ ਤਬਦੀਲੀ ਛੂੰਹਦੀ ਹੈ। ਵਿਕਲਪ (ਹਰ ਕੁੰਜੀ ਲਈ ਹੈਸ਼ ਬੇਸਲਾਈਨ) ਲਈ ਲਗਭਗ 600 KB ਦੀ ਜਨਰੇਟ ਕੀਤੀ ਫ਼ਾਈਲ ਦੀ ਲੋੜ ਪਵੇਗੀ, ਜੋ ਮੌਜੂਦਾ ਸਭ ਤੋਂ ਵੱਡੀ ਬੇਸਲਾਈਨ ਤੋਂ 3× ਵੱਡੀ ਹੋਵੇਗੀ ਅਤੇ ਹਰ i18n PR ਉੱਤੇ ਬਦਲੇਗੀ। +**Diff-ਅਵੇਅਰ, ਬੇਸਲਾਈਨ-ਬੈਕਡ ਨਹੀਂ।** ਇਹ ਮਰਜ ਬੇਸ ਉੱਤੇ ਮੌਜੂਦ `en.json` ਦੀ ਵਰਕਿੰਗ ਟ੍ਰੀ ਨਾਲ ਤੁਲਨਾ ਕਰਦਾ ਹੈ; ਹਰ ਉਸ ਕੁੰਜੀ ਲਈ ਜਿਸਦਾ ਅੰਗਰੇਜ਼ੀ ਮੁੱਲ ਬਦਲਿਆ ਹੈ, ਕੋਈ ਵੀ ਲੋਕੇਲ ਜਿਸ ਵਿੱਚ ਹਾਲੇ ਵੀ ਨਾ-ਛੋਹਿਆ ਅਨੁਵਾਦ ਮੌਜੂਦ ਹੈ, ਪੁਰਾਣਾ ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ। ਇਹ ਜਾਣ-ਬੁੱਝ ਕੇ **ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਕਰਜ਼ੇ ਨੂੰ ਫ੍ਰੀਜ਼ ਕਰਦਾ ਹੈ** — ਕੋਈ diff ਇਹ ਨਹੀਂ ਦੱਸ ਸਕਦਾ ਕਿ ਲੰਬੇ ਸਮੇਂ ਤੋਂ ਮੌਜੂਦ ਅਨੁਵਾਦ ਕਿਸ ਪੁਰਾਣੇ ਅੰਗਰੇਜ਼ੀ ਪਾਠ ਤੋਂ ਆਇਆ ਸੀ, ਇਸ ਲਈ ਗੇਟ ਸਿਰਫ਼ ਉਸੇ ਨੂੰ ਪਰਖਦਾ ਹੈ ਜਿਸਨੂੰ ਮੌਜੂਦਾ ਤਬਦੀਲੀ ਛੂਹਦੀ ਹੈ। ਵਿਕਲਪ (ਹਰ ਕੁੰਜੀ ਲਈ ਹੈਸ਼ ਬੇਸਲਾਈਨ) ਲਈ ~600 KB ਦੀ ਜਨਰੇਟ ਕੀਤੀ ਫ਼ਾਈਲ ਦੀ ਲੋੜ ਪਵੇਗੀ, ਜੋ ਸਭ ਤੋਂ ਵੱਡੀ ਮੌਜੂਦਾ ਬੇਸਲਾਈਨ ਤੋਂ 3× ਵੱਡੀ ਹੋਵੇਗੀ ਅਤੇ ਹਰ i18n PR ਉੱਤੇ ਬਦਲੇਗੀ। ਇਸਨੂੰ ਸੰਤੁਸ਼ਟ ਕਰਨ ਦੇ ਦੋ ਤਰੀਕੇ ਹਨ: 1. ਪ੍ਰਭਾਵਿਤ ਅਨੁਵਾਦਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰੋ, ਜਾਂ -2. ਉਨ੍ਹਾਂ ਨੂੰ `__MISSING__:` ਉੱਤੇ ਸੈੱਟ ਕਰੋ — ਫਿਰ ਰਨਟਾਈਮ ਸੁਧਾਰਿਆ ਹੋਇਆ ਅੰਗਰੇਜ਼ੀ ਪਾਠ ਦਿੰਦਾ ਹੈ - (`src/i18n/request.ts::deepMergeFallback`, #7258) ਅਤੇ ਕੁੰਜੀ ਅਨੁਵਾਦ ਲਈ ਕਤਾਰ ਵਿੱਚ ਚਲੀ ਜਾਂਦੀ ਹੈ। +2. ਉਹਨਾਂ ਨੂੰ `__MISSING__:` ਉੱਤੇ ਸੈੱਟ ਕਰੋ — ਫਿਰ ਰਨਟਾਈਮ ਸੋਧਿਆ ਹੋਇਆ ਅੰਗਰੇਜ਼ੀ ਪਾਠ ਸਰਵ ਕਰਦਾ ਹੈ + (`src/i18n/request.ts::deepMergeFallback`, #7258) ਅਤੇ ਕੁੰਜੀ ਅਨੁਵਾਦ ਲਈ ਕਤਾਰਬੱਧ ਹੋ ਜਾਂਦੀ ਹੈ। -ਜੇ ਸਤਰ ਦਾ **ਅਰਥ** ਬਦਲ ਗਿਆ ਹੈ, ਤਾਂ **ਕੁੰਜੀ ਦਾ ਨਾਮ ਬਦਲਣ** ਨੂੰ ਤਰਜੀਹ ਦਿਓ: ਨਵੀਂ ਕੁੰਜੀ ਕਿਸੇ ਪੁਰਾਣੇ ਅਨੁਵਾਦ ਨੂੰ ਵਿਰਾਸਤ ਵਿੱਚ ਨਹੀਂ ਲੈ ਸਕਦੀ। #8463 ਵਿੱਚ ਇਹੀ ਪੈਟਰਨ ਵਰਤਿਆ ਗਿਆ ਸੀ। +ਜੇ ਸਤਰ ਦਾ **ਅਰਥ** ਬਦਲਿਆ ਹੈ, ਤਾਂ **ਕੁੰਜੀ ਦਾ ਨਾਮ ਬਦਲਣ** ਨੂੰ ਤਰਜੀਹ ਦਿਓ: ਨਵੀਂ ਕੁੰਜੀ ਕਿਸੇ ਪੁਰਾਣੇ ਅਨੁਵਾਦ ਨੂੰ ਵਿਰਾਸਤ ਵਿੱਚ ਨਹੀਂ ਲੈ ਸਕਦੀ। #8463 ਵਿੱਚ ਇਹੀ ਪੈਟਰਨ ਵਰਤਿਆ ਗਿਆ ਸੀ। ```bash npm run i18n:check-value-drift # ਸਖ਼ਤ (ਜੋ CI ਚਲਾਉਂਦਾ ਹੈ) @@ -231,47 +225,47 @@ npm run i18n:check-value-drift:warn # ਸਿਰਫ਼ ਰਿਪੋਰਟ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -ਜਦੋਂ ਬੇਸ ਕੈਟਾਲਾਗ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ (ਬੇਸ ਰੈਫ਼ ਤੋਂ ਬਿਨਾਂ ਸ਼ੈਲੋ ਕਲੋਨ), ਤਾਂ `SKIP reason=base-unresolved` ਨਾਲ 0 ਉੱਤੇ ਐਗਜ਼ਿਟ ਕਰਦਾ ਹੈ, ਜੋ `check-openapi-breaking` ਦੇ ਵਿਹਾਰ ਨੂੰ ਦਰਸਾਉਂਦਾ ਹੈ। +ਜਦੋਂ ਬੇਸ ਕੈਟਾਲਾਗ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ (ਬੇਸ ref ਤੋਂ ਬਿਨਾਂ shallow clone), ਤਾਂ `check-openapi-breaking` ਦੇ ਵਿਹਾਰ ਦੀ ਨਕਲ ਕਰਦਿਆਂ ਇਹ `SKIP reason=base-unresolved` ਨਾਲ 0 ਐਗਜ਼ਿਟ ਕਰਦਾ ਹੈ। ### ਜੌਬ: `i18n` -ਪੂਰਾ i18n ਪ੍ਰਮਾਣਿਕਤਾ ਮੈਟ੍ਰਿਕਸ (ਹਰ ਲੋਕੇਲ ਲਈ ਇੱਕ ਜੌਬ)। ਪੂਰੀ ਜੌਬ ਸਲਾਹਕਾਰੀ ਹੈ। +ਪੂਰਾ i18n ਵੈਲੀਡੇਸ਼ਨ ਮੈਟ੍ਰਿਕਸ (ਹਰ ਲੋਕੇਲ ਲਈ ਇੱਕ ਜੌਬ)। ਪੂਰੀ ਜੌਬ ਸਲਾਹਕਾਰੀ ਹੈ। -| ਸਕ੍ਰਿਪਟ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਬਲੌਕਿੰਗ | +| ਸਕ੍ਰਿਪਟ | ਕੀ ਵੈਲੀਡੇਟ ਕਰਦੀ ਹੈ | ਬਲੌਕਿੰਗ | | ------------------------------- | ---------------------------- | ------------------------------------------------------ | | `validate_translation.py quick` | ਹਰ ਲੋਕੇਲ ਲਈ ਅਨੁਵਾਦ ਦੀ ਪੂਰਨਤਾ | **ਸਲਾਹਕਾਰੀ** (ਪੂਰੀ ਜੌਬ ਉੱਤੇ `continue-on-error: true`) | ### ਜੌਬ: `pr-test-policy` -ਸਿਰਫ਼ ਪੁੱਲ ਰਿਕਵੈਸਟਾਂ ਉੱਤੇ ਚੱਲਦੀ ਹੈ। +ਸਿਰਫ਼ ਪੁੱਲ ਰਿਕਵੇਸਟਾਂ ਉੱਤੇ ਚੱਲਦੀ ਹੈ। -| ਸਕ੍ਰਿਪਟ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦੀ ਹੈ | ਬਲੌਕਿੰਗ | +| ਸਕ੍ਰਿਪਟ | ਕੀ ਵੈਲੀਡੇਟ ਕਰਦੀ ਹੈ | ਬਲੌਕਿੰਗ | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, ਜਾਂ `bin/` ਵਿੱਚ ਪ੍ਰੋਡਕਸ਼ਨ ਕੋਡ ਬਦਲਣ ਵਾਲੀਆਂ PRs ਵਿੱਚ ਟੈਸਟ ਸ਼ਾਮਲ ਜਾਂ ਅੱਪਡੇਟ ਹੋਣੇ ਲਾਜ਼ਮੀ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #8) | ਹਾਂ | -| `check:test-masking` | ਬਦਲੀਆਂ ਹੋਈਆਂ ਟੈਸਟ ਫ਼ਾਈਲਾਂ ਕੁੱਲ assert ਗਿਣਤੀ ਨੂੰ ਘਟਾਉਂਦੀਆਂ ਜਾਂ `assert.ok(true)` ਵਰਗੀਆਂ ਸਵੈ-ਸਪਸ਼ਟ ਸੱਚਾਈਆਂ ਨਹੀਂ ਜੋੜਦੀਆਂ | ਹਾਂ | -| `check:pr-evidence` | PR ਦਾ ਮੁੱਖ ਭਾਗ ਤਬਦੀਲੀ ਲਈ ਟੈਸਟ/VPS ਸਬੂਤ ਦਾ ਹਵਾਲਾ ਦਿੰਦਾ ਹੈ (PR ਦੇ ਗੱਦ ਨੂੰ grep ਕਰਕੇ ਸਖ਼ਤ ਨਿਯਮ #18 ਨੂੰ ਮਸ਼ੀਨੀ ਬਣਾਉਂਦਾ ਹੈ — ਨਾਜ਼ੁਕ, Backlog ਵੇਖੋ) | ਹਾਂ | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, ਜਾਂ `bin/` ਵਿੱਚ ਪ੍ਰੋਡਕਸ਼ਨ ਕੋਡ ਬਦਲਣ ਵਾਲੀਆਂ PRs ਵਿੱਚ ਟੈਸਟ ਸ਼ਾਮਲ ਜਾਂ ਅੱਪਡੇਟ ਕੀਤੇ ਹੋਣੇ ਲਾਜ਼ਮੀ ਹਨ (ਸਖ਼ਤ ਨਿਯਮ #8) | ਹਾਂ | +| `check:test-masking` | ਬਦਲੀਆਂ ਹੋਈਆਂ ਟੈਸਟ ਫ਼ਾਈਲਾਂ ਕੁੱਲ assert ਗਿਣਤੀ ਨੂੰ ਘਟਾਉਂਦੀਆਂ ਜਾਂ `assert.ok(true)` ਵਰਗੀਆਂ ਸਵੈ-ਸਿੱਧੀਆਂ ਸ਼ਰਤਾਂ ਨਹੀਂ ਜੋੜਦੀਆਂ | ਹਾਂ | +| `check:pr-evidence` | PR ਬਾਡੀ ਵਿੱਚ ਤਬਦੀਲੀ ਲਈ ਟੈਸਟ/VPS ਸਬੂਤ ਦਾ ਹਵਾਲਾ ਹੋਵੇ (PR ਲਿਖਤ ਨੂੰ grep ਕਰਕੇ ਸਖ਼ਤ ਨਿਯਮ #18 ਨੂੰ ਮਸ਼ੀਨੀ ਰੂਪ ਦਿੰਦਾ ਹੈ — ਨਾਜ਼ੁਕ ਹੈ, Backlog ਵੇਖੋ) | ਹਾਂ | ### ਜੌਬ: `test-vitest` -`build` ਤੋਂ ਬਾਅਦ ਚੱਲਦੀ ਹੈ। ਅਸਫਲ ਹੋਣ ਉੱਤੇ ਮਰਜ ਨੂੰ ਰੋਕਦੀ ਹੈ। +`build` ਤੋਂ ਬਾਅਦ ਚੱਲਦੀ ਹੈ। ਅਸਫਲ ਹੋਣ ਉੱਤੇ ਮਰਜ ਨੂੰ ਬਲੌਕ ਕਰਦੀ ਹੈ। -| ਸੂਟ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦਾ ਹੈ | ਬਲੌਕਿੰਗ | -| ---------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP ਸਰਵਰ (110 ਟੂਲ), autoCombo, ਕੈਸ਼ — vitest ਰਨਰ | ਹਾਂ | -| `test:vitest:ui` | UI ਕੰਪੋਨੈਂਟ ਟੈਸਟ — vitest ਰਨਰ | **ਬਲੌਕਿੰਗ** — ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਅਸਫਲਤਾਵਾਂ ਨੂੰ `vitest.config.ts` ਵਿੱਚ ਸਪਸ਼ਟ ਤੌਰ ਉੱਤੇ ਬਾਹਰ ਰੱਖਿਆ ਗਿਆ ਹੈ; ਨਵੀਆਂ ਅਸਫਲਤਾਵਾਂ ਜੌਬ ਨੂੰ ਅਸਫਲ ਕਰ ਦਿੰਦੀਆਂ ਹਨ | +| ਸੂਟ | ਕੀ ਵੈਲੀਡੇਟ ਕਰਦਾ ਹੈ | ਬਲੌਕਿੰਗ | +| ---------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP ਸਰਵਰ (110 ਟੂਲ), autoCombo, ਕੈਸ਼ — vitest ਰਨਰ | ਹਾਂ | +| `test:vitest:ui` | UI ਕੰਪੋਨੈਂਟ ਟੈਸਟ — vitest ਰਨਰ | **ਬਲੌਕਿੰਗ** — ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਅਸਫਲਤਾਵਾਂ ਨੂੰ `vitest.config.ts` ਵਿੱਚ ਸਪਸ਼ਟ ਤੌਰ ਉੱਤੇ ਬਾਹਰ ਰੱਖਿਆ ਗਿਆ ਹੈ; ਨਵੀਆਂ ਅਸਫਲਤਾਵਾਂ ਜੌਬ ਨੂੰ ਅਸਫਲ ਕਰਦੀਆਂ ਹਨ | -### ਰਾਤਰੀ ਵਰਕਫ਼ਲੋ (ਨਿਯਤ, ਸਲਾਹਕਾਰੀ) +### ਰਾਤਰੀ ਵਰਕਫ਼ਲੋ (ਨਿਰਧਾਰਤ, ਸਲਾਹਕਾਰੀ) ਇਹ cron ਸ਼ਡਿਊਲ ਉੱਤੇ (ਅਤੇ `workflow_dispatch` ਰਾਹੀਂ) ਚੱਲਦੇ ਹਨ, PRs ਉੱਤੇ ਕਦੇ ਨਹੀਂ। ਸਾਰੇ ਸਲਾਹਕਾਰੀ ਹਨ। -| ਵਰਕਫ਼ਲੋ | ਕੀ ਪ੍ਰਮਾਣਿਤ ਕਰਦਾ ਹੈ | ਬਲੌਕਿੰਗ | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `nightly-property` | ਇੱਕ ਰੈਂਡਮ ਸੀਡ ਅਤੇ ਵੱਡੀ ਰਨ ਗਿਣਤੀ ਨਾਲ fast-check ਪ੍ਰਾਪਰਟੀ ਟੈਸਟ | **ਸਲਾਹਕਾਰੀ** | -| `nightly-resilience` | ਹੀਪ-ਵਾਧਾ ਗੇਟ, ਕਾਓਸ ਫ਼ਾਲਟ-ਇੰਜੈਕਸ਼ਨ, k6 ਲੋਡ/ਸੋਕ | **ਸਲਾਹਕਾਰੀ** | -| `nightly-llm-security` | promptfoo ਇੰਜੈਕਸ਼ਨ ਗਾਰਡ (ਬਲੌਕ ਮੋਡ) + garak ਪ੍ਰੋਬ (ਪ੍ਰੋਵਾਈਡਰ ਸੀਕ੍ਰੇਟ ਤੋਂ ਬਿਨਾਂ ਛੱਡੇ ਜਾਂਦੇ ਹਨ) | **ਸਲਾਹਕਾਰੀ** | -| `nightly-schemathesis` | `docs/openapi.yaml` ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹੋਏ ਲਾਈਵ OmniRoute ਵਿਰੁੱਧ OpenAPI ਕਾਂਟ੍ਰੈਕਟ ਫ਼ਜ਼ਿੰਗ (schemathesis) — ਸਪੈਕ ਉਲੰਘਣਾਵਾਂ / ਨਾ-ਸੰਭਾਲੀਆਂ 500 ਗਲਤੀਆਂ ਨੂੰ ਸਾਹਮਣੇ ਲਿਆਉਂਦੀ ਹੈ (ਪੜਾਅ 8 B.4) | **ਸਲਾਹਕਾਰੀ** | -| `nightly-mutation` | ਤੇਜ਼ ਯੂਨਿਟ ਲੇਨ ਉੱਤੇ Stryker ਮਿਊਟੇਸ਼ਨ-ਟੈਸਟਿੰਗ ਸਕੋਰ — ਬਚੇ ਹੋਏ ਮਿਊਟੈਂਟ ਕਮਜ਼ੋਰ asserts ਨੂੰ ਸਾਹਮਣੇ ਲਿਆਉਂਦੇ ਹਨ | **ਸਲਾਹਕਾਰੀ** | -| `nightly-compat` | ਸਮਰਥਿਤ `engines.node` ਰੇਂਜਾਂ ਵਿੱਚ Node ਇੰਜਣ ਅਨੁਕੂਲਤਾ ਮੈਟ੍ਰਿਕਸ | **ਸਲਾਹਕਾਰੀ** | +| ਵਰਕਫ਼ਲੋ | ਕੀ ਵੈਲੀਡੇਟ ਕਰਦਾ ਹੈ | ਬਲੌਕਿੰਗ | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `nightly-property` | ਬੇਤਰਤੀਬ seed + ਉੱਚ run count ਨਾਲ fast-check ਪ੍ਰਾਪਰਟੀ ਟੈਸਟ | **ਸਲਾਹਕਾਰੀ** | +| `nightly-resilience` | heap-growth ਗੇਟ, chaos fault-injection, k6 load/soak | **ਸਲਾਹਕਾਰੀ** | +| `nightly-llm-security` | promptfoo injection ਗਾਰਡ (block ਮੋਡ) + garak probes (ਪ੍ਰੋਵਾਈਡਰ ਸੀਕ੍ਰੇਟ ਤੋਂ ਬਿਨਾਂ ਛੱਡੇ ਜਾਂਦੇ ਹਨ) | **ਸਲਾਹਕਾਰੀ** | +| `nightly-schemathesis` | `docs/openapi.yaml` ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਲਾਈਵ OmniRoute ਦੇ ਵਿਰੁੱਧ OpenAPI ਕੰਟ੍ਰੈਕਟ ਫਜ਼ਿੰਗ (schemathesis) — ਸਪੈੱਕ ਉਲੰਘਣਾਵਾਂ / ਨਾ-ਸੰਭਾਲੀਆਂ 500 ਗਲਤੀਆਂ ਸਾਹਮਣੇ ਲਿਆਉਂਦੀ ਹੈ (Fase 8 B.4) | **ਸਲਾਹਕਾਰੀ** | +| `nightly-mutation` | ਤੇਜ਼ ਯੂਨਿਟ ਲੇਨ ਉੱਤੇ Stryker ਮਿਊਟੇਸ਼ਨ-ਟੈਸਟਿੰਗ ਸਕੋਰ — ਬਚੇ ਰਹਿਣ ਵਾਲੇ mutants ਕਮਜ਼ੋਰ asserts ਨੂੰ ਸਾਹਮਣੇ ਲਿਆਉਂਦੇ ਹਨ | **ਸਲਾਹਕਾਰੀ** | +| `nightly-compat` | ਸਮਰਥਿਤ `engines.node` ਰੇਂਜਾਂ ਵਿੱਚ Node ਇੰਜਣ ਅਨੁਕੂਲਤਾ ਮੈਟ੍ਰਿਕਸ | **ਸਲਾਹਕਾਰੀ** | --- @@ -535,43 +529,43 @@ CI ਗੇਟਾਂ ਤੋਂ ਇਲਾਵਾ, OmniRoute ਇੱਕ **ਚੋਣਵ --- -## ਤਰਕਸੰਗਤੀਕਰਨ ਬੈਕਲੌਗ (ROI ਸਮੀਖਿਆ — ਪੜਾਅ 9 ਲਹਿਰ 3) +## ਤਰਕਸੰਗਤੀਕਰਨ ਬੈਕਲਾਗ (ROI ਸਮੀਖਿਆ — ਪੜਾਅ 9 ਲਹਿਰ 3) -ਇਸ ਸੂਚੀ ਦਾ 2026-06-17 ਨੂੰ `ci.yml` ਨਾਲ ਮਿਲਾਣ ਕੀਤਾ ਗਿਆ ਸੀ (ਪਿਛਲੇ ਸੰਸਕਰਣ ਵਿੱਚ +ਇਸ ਸੂਚੀ ਦਾ 2026-06-17 ਨੂੰ `ci.yml` ਨਾਲ ਮਿਲਾਨ ਕੀਤਾ ਗਿਆ ਸੀ (ਪਿਛਲੇ ਸੰਸਕਰਣ ਵਿੱਚ `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` ਸ਼ਾਮਲ ਨਹੀਂ ਸਨ)। ਮਿਲਾਣ ਕੀਤੇ ਸੈੱਟ ਦੀ ROI ਸਮੀਖਿਆ ਨੇ -ਹੇਠਾਂ ਦਿੱਤੇ ਤਰਕਸੰਗਤੀਕਰਨ ਉਮੀਦਵਾਰਾਂ ਦੀ ਪਛਾਣ ਕੀਤੀ। **ਮਰਜ ਮਕੈਨੀਕਲ CI -ਤਬਦੀਲੀਆਂ ਹਨ; ਫਲਿੱਪ/ਹਟਾਉਣ ਨੀਤੀਗਤ ਫ਼ੈਸਲੇ ਹਨ ਜੋ ਆਪਰੇਟਰ ਲਈ ਰਾਖਵੇਂ ਹਨ।** ਹੇਠਾਂ ਦਿੱਤੀ ਕੋਈ ਵੀ ਚੀਜ਼ -ਅਜੇ ਲਾਗੂ ਨਹੀਂ ਕੀਤੀ ਗਈ। +`check:codeql-ratchet`, `check:pr-evidence` ਸ਼ਾਮਲ ਨਹੀਂ ਸਨ)। ਮਿਲਾਨ ਕੀਤੇ ਸੈੱਟ ਦੀ ROI ਸਮੀਖਿਆ +ਨੇ ਹੇਠ ਲਿਖੇ ਤਰਕਸੰਗਤੀਕਰਨ ਉਮੀਦਵਾਰਾਂ ਦੀ ਪਛਾਣ ਕੀਤੀ। **ਮਰਜ ਮਕੈਨੀਕਲ CI +ਬਦਲਾਅ ਹਨ; ਫਲਿਪ/ਡ੍ਰਾਪ ਨੀਤੀਗਤ ਫੈਸਲੇ ਹਨ ਜੋ ਓਪਰੇਟਰ ਲਈ ਰਾਖਵੇਂ ਹਨ।** ਹੇਠਾਂ ਦਿੱਤਾ ਕੁਝ ਵੀ +ਹਾਲੇ ਲਾਗੂ ਨਹੀਂ ਕੀਤਾ ਗਿਆ। **ਉੱਪਰ ਇਹ ਵੀ ਦਸਤਾਵੇਜ਼ਬੱਧ ਨਹੀਂ ਹੈ** (ਸਲਾਹਕਾਰੀ, ਘੱਟ ਸੰਕੇਤ): `docs-lint` ਜੌਬ (markdownlint + Vale, ਪੂਰੀ ਜੌਬ `continue-on-error`) ਅਤੇ ਵੱਖਰੇ ਸਕੈਨਰ ਵਰਕਫ਼ਲੋ `semgrep.yml` / `codeql.yml` / `scorecard.yml`। `semgrepFindings: 0` -`quality-baseline.json` ਵਿੱਚ ਹੈ, ਪਰ `ci.yml` ਵਿੱਚ ਕਿਸੇ ਬਲੌਕਿੰਗ ਰੈਚਟ ਨਾਲ ਨਹੀਂ ਜੋੜਿਆ ਗਿਆ — ਇਹ ਮੈਟ੍ਰਿਕ +`quality-baseline.json` ਵਿੱਚ ਹੈ ਪਰ `ci.yml` ਵਿੱਚ ਕਿਸੇ ਬਲੌਕਿੰਗ ਰੈਚਿਟ ਨਾਲ ਜੋੜਿਆ ਨਹੀਂ ਗਿਆ — ਇਹ ਮੈਟ੍ਰਿਕ ਇਸ ਵੇਲੇ ਅਨਾਥ ਹੈ। -### ਮਰਜ / ਡੀਡੁਪਲੀਕੇਸ਼ਨ (ਮਕੈਨੀਕਲ, ਘੱਟ ਜੋਖਮ) +### ਮਰਜ / ਡੀਡੁਪ (ਮਕੈਨੀਕਲ, ਘੱਟ ਜੋਖਮ) -ਹਰੇਕ ਉਮੀਦਵਾਰ ਦੀ 2026-06-17 ਨੂੰ ਲਾਈਵ ਗੇਟ ਸਥਿਤੀ ਦੇ ਮੁਕਾਬਲੇ ਪੁਸ਼ਟੀ ਕੀਤੀ ਗਈ ਸੀ (ਭਰੋਸਾ ਕਰੋ, ਪਰ ਤਸਦੀਕ ਵੀ ਕਰੋ); -ਕਈ "ਸਪਸ਼ਟ" ਮਰਜਾਂ ਦੇ ਪਿੱਛੇ ਅਸਲ ਵਿੱਚ ਕਰਜ਼ ਲੁਕਿਆ ਹੋਇਆ ਸੀ ਅਤੇ ਉਹ **ਸਿੱਧੇ ਬਦਲ ਵਜੋਂ ਸਾਫ਼ ਨਹੀਂ** ਹਨ। +ਹਰ ਉਮੀਦਵਾਰ ਨੂੰ 2026-06-17 ਨੂੰ ਲਾਈਵ ਗੇਟ ਸਥਿਤੀ ਦੇ ਮੁਕਾਬਲੇ ਪ੍ਰਮਾਣਿਤ ਕੀਤਾ ਗਿਆ ਸੀ (ਭਰੋਸਾ ਕਰੋ, ਪਰ ਤਸਦੀਕ ਵੀ ਕਰੋ); +ਕਈ "ਸਪਸ਼ਟ" ਮਰਜਾਂ ਦੇ ਪਿੱਛੇ ਕਰਜ਼ਾ ਲੁਕਿਆ ਹੋਇਆ ਨਿਕਲਿਆ ਅਤੇ ਉਹ **ਸਿੱਧੇ ਤੌਰ 'ਤੇ ਵਰਤਣਯੋਗ ਬਦਲ ਨਹੀਂ** ਹਨ। -- **`check:docs-sync` ਦੋ ਵਾਰ ਚੱਲਦਾ ਹੈ** — `lint` ਜੌਬ ਵਿੱਚ ਵੱਖਰੇ ਤੌਰ 'ਤੇ ਅਤੇ ਦੁਬਾਰਾ `check:docs-all` (`docs-sync-strict`) ਅਤੇ husky pre-commit ਹੁੱਕ ਦੇ ਅੰਦਰ। ✅ **ਮੁਕੰਮਲ** — ਵੱਖਰਾ `lint` ਸੱਦਾ ਹਟਾ ਦਿੱਤਾ ਗਿਆ। -- **CVE ਸਕੈਨਿੰਗ** — ❌ **ਸਾਫ਼ ਮਰਜ ਨਹੀਂ ਹੈ।** `audit:deps` ਕਿਸੇ ਵੀ high/critical CVE ਉੱਤੇ ਪੱਕੇ ਤੌਰ 'ਤੇ ਫੇਲ੍ਹ ਹੁੰਦਾ ਹੈ; `check:vuln-ratchet` (osv) ਸਿਰਫ਼ ਬੇਸਲਾਈਨ ਦੇ ਮੁਕਾਬਲੇ ਕਿਸੇ _ਰਿਗ੍ਰੈਸ਼ਨ_ ਉੱਤੇ ਫੇਲ੍ਹ ਹੁੰਦਾ ਹੈ (ਇਸ ਵੇਲੇ 1 MODERATE)। ਅਰਥ-ਵਿਹਾਰ ਵੱਖਰੇ ਹਨ — `audit:deps` ਨੂੰ ਹਟਾਉਣ ਨਾਲ ਪੂਰਨ high/critical ਗੇਟ ਗੁਆਚ ਜਾਵੇਗਾ। ਦੋਵੇਂ ਰੱਖੋ। -- **ਸਾਈਕਲ ਪਛਾਣ** — ❌ **ਸਾਫ਼ ਮਰਜ ਨਹੀਂ ਹੈ।** `check:circular-deps` (dpdm) **91 ਸਾਈਕਲਾਂ** ਦੀ ਰਿਪੋਰਟ ਕਰਦਾ ਹੈ (ਇਸੇ ਲਈ ਇਹ ਸਲਾਹਕਾਰੀ ਹੈ); ਪਹਿਲਾਂ ਉਨ੍ਹਾਂ ਨੂੰ ਹੱਲ ਕੀਤੇ ਬਿਨਾਂ ਇਸਨੂੰ ਬਲੌਕਿੰਗ ਨਹੀਂ ਬਣਾਇਆ ਜਾ ਸਕਦਾ, ਅਤੇ ਇਸਦਾ ਦਾਇਰਾ ਹਰੇ, ਚੁਣੇ ਹੋਏ `check:cycles` ਨਾਲੋਂ ਵੱਡਾ ਹੈ। `check:cycles` ਨੂੰ ਬਲੌਕਿੰਗ ਰੱਖੋ; 91 dpdm ਸਾਈਕਲਾਂ ਨੂੰ ਹੱਲ ਕਰਨਾ ਆਪਣਾ ਵੱਖਰਾ ਬੈਕਲੌਗ ਹੈ। -- **ਜਟਿਲਤਾ** — ✅ **ਮੁਕੰਮਲ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ਇੱਕ ESLint ਵਾਕ, ruleId ਮੁਤਾਬਕ ਗਿਣਤੀ ਕਰਦਾ ਹੈ ਤਾਂ ਜੋ cyclomatic+max-lines ਅਤੇ cognitive ਬੇਸਲਾਈਨਾਂ ਸੁਤੰਤਰ ਰਹਿਣ; ਵੱਖਰੇ `check:complexity` / `check:cognitive-complexity` ਸਥਾਨਕ `--update` ਲਈ ਕਾਇਮ ਹਨ। -- **`/api` ਭਰਮ-ਰੋਕਥਾਮ** — ✅ **ਮੁਕੰਮਲ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ਦੀ ਇੱਕ FS ਇਨਵੈਂਟਰੀ, openapi-routes + docs-symbols ਅਜੇ ਵੀ ਸੁਤੰਤਰ ਤੌਰ 'ਤੇ ਰਿਪੋਰਟ ਕਰਦੇ ਹਨ; ਵੱਖਰੇ ਚੈੱਕ ਸਥਾਨਕ ਰਨਾਂ ਲਈ ਕਾਇਮ ਹਨ। -- **`check:node-runtime` 11 ਜੌਬਾਂ ਵਿੱਚ ਚੱਲਦਾ ਹੈ** — ⚠️ **ਘੱਟ ROI।** ਹਰੇਕ ਇੱਕ ਵੱਖਰਾ ਰਨਰ ਹੈ ਅਤੇ ਚੈੱਕ <1s ਹੈ; ਕੁੱਲ ਬਚਤ ~10s ਹੈ, ਜਿਸਦੇ ਮੁਕਾਬਲੇ ਹਰ ਜੌਬ ਲਈ ਇੱਕ ਸਸਤਾ ਗਾਰਡ ਗੁਆਉਣਾ ਪਵੇਗਾ। ਤਬਦੀਲੀ ਦੀ ਝੰਝਟ ਦੇ ਯੋਗ ਨਹੀਂ। -- **CI lint ਉੱਤੇ `typecheck:noimplicit:core`** — ✅ **lint ਜੌਬ ਤੋਂ ਹਟਾਇਆ ਗਿਆ** (ਇਹ ਸਲਾਹਕਾਰੀ `continue-on-error` ਸੀ); ਬਲੌਕਿੰਗ ਟਾਈਪ ਸਤਹ `typecheck:core` + `check:type-coverage` ਹੈ। ਸਥਾਨਕ ਸਕ੍ਰਿਪਟ ਕਾਇਮ ਰੱਖੀ ਗਈ ਹੈ। +- **`check:docs-sync` ਦੋ ਵਾਰ ਚੱਲਦਾ ਹੈ** — `lint` ਜੌਬ ਵਿੱਚ ਵੱਖਰੇ ਤੌਰ 'ਤੇ ਅਤੇ ਮੁੜ `check:docs-all` (`docs-sync-strict`) ਅਤੇ husky pre-commit ਹੁੱਕ ਦੇ ਅੰਦਰ। ✅ **ਪੂਰਾ ਹੋਇਆ** — ਵੱਖਰਾ `lint` ਇਨਵੋਕੇਸ਼ਨ ਹਟਾਇਆ ਗਿਆ। +- **CVE ਸਕੈਨਿੰਗ** — ❌ **ਸਾਫ਼-ਸੁਥਰਾ ਮਰਜ ਨਹੀਂ ਹੈ।** `audit:deps` ਕਿਸੇ ਵੀ high/critical CVE 'ਤੇ ਹਾਰਡ-ਫੇਲ ਹੁੰਦਾ ਹੈ; `check:vuln-ratchet` (osv) ਸਿਰਫ਼ ਬੇਸਲਾਈਨ (ਇਸ ਵੇਲੇ 1 MODERATE) ਦੇ ਮੁਕਾਬਲੇ ਕਿਸੇ _ਰਿਗ੍ਰੈਸ਼ਨ_ 'ਤੇ ਫੇਲ ਹੁੰਦਾ ਹੈ। ਅਰਥ-ਵਿਹਾਰ ਵੱਖਰੇ ਹਨ — `audit:deps` ਨੂੰ ਹਟਾਉਣ ਨਾਲ ਪੂਰਨ high/critical ਗੇਟ ਖਤਮ ਹੋ ਜਾਵੇਗਾ। ਦੋਵੇਂ ਰੱਖੋ। +- **ਚੱਕਰ ਖੋਜ** — ✅ **ਪੂਰਾ ਹੋਇਆ** (#15159 G-01/G-02)। ਇੱਥੇ ਪੁਰਾਣੇ ਟੈਕਸਟ ਨੇ `check:cycles` ਨੂੰ "ਹਰਾ, ਸੰਭਾਲ ਕੇ ਚੁਣਿਆ" ਗੇਟ ਕਿਹਾ ਸੀ ਅਤੇ ਇਸਨੂੰ ਬਲੌਕਿੰਗ ਰੱਖਣ ਨੂੰ ਇਸ ਆਧਾਰ 'ਤੇ ਜਾਇਜ਼ ਠਹਿਰਾਇਆ ਸੀ ਕਿ `check:circular-deps` (dpdm) ਨੇ 91 ਚੱਕਰ ਰਿਪੋਰਟ ਕੀਤੇ ਸਨ। ਉਹ ਹਰਾ ਦਰਜਾ ਇੱਕ **ਗਲਤ ਹਰਾ ਸੰਕੇਤ** ਸੀ: `check:cycles` ਨੇ 5 ਸਬਡਾਇਰੈਕਟਰੀਆਂ (450 ਫ਼ਾਈਲਾਂ) ਸਕੈਨ ਕੀਤੀਆਂ, ਸਿਰਫ਼ ਸਥਿਰ `import|export … from` ਨਾਲ ਮੇਲ ਕੀਤਾ, ਅਤੇ ਹਰੇਕ `@/` ਅਤੇ `@omniroute/open-sse/` ਸਪੈਸੀਫਾਇਰ ਨੂੰ ਛੱਡ ਦਿੱਤਾ, ਇਸ ਲਈ ਇਹ ਉਹ dynamic-import + alias ਚੱਕਰ ਨਹੀਂ ਦੇਖ ਸਕਿਆ ਜੋ ਰਿਪੋ ਵਿੱਚ ਹਾਵੀ ਸਨ। ਠੀਕ ਕੀਤਾ ਗਿਆ: ਗੇਟ ਹੁਣ `src` + `open-sse` (5023 ਫ਼ਾਈਲਾਂ) ਵਿੱਚੋਂ ਲੰਘਦਾ ਹੈ, TypeScript AST ਤੋਂ ਸਪੈਸੀਫਾਇਰ ਇਕੱਠੇ ਕਰਦਾ ਹੈ (ਇਸ ਲਈ `import("…")` ਗਿਣਿਆ ਜਾਂਦਾ ਹੈ ਅਤੇ type-position `typeof import("…")` ਨਹੀਂ), ਅਤੇ tsconfig `paths` ਨੂੰ ਰਿਜ਼ੌਲਵ ਕਰਦਾ ਹੈ। ਇਹ 0 ਨਹੀਂ, ਸਗੋਂ **14** ਚੱਕਰ ਲੱਭਦਾ ਹੈ। ਕਿਉਂਕਿ ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ 14 ਚੱਕਰਾਂ ਨੂੰ ਗੇਟ PR ਵਿੱਚ ਠੀਕ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ, `check:cycles` ਹੁਣ ਇੱਕ **ਰੈਚਿਟ** ਹੈ (`--ratchet`, `quality-baseline.json` ਵਿੱਚ ਸੀਮਾ `metrics.cycles.value = 14`, `direction: down`) — ਇਹ ਕਿਸੇ ਵੀ _ਰਿਗ੍ਰੈਸ਼ਨ_ ਨੂੰ ਬਲੌਕ ਕਰਦਾ ਹੈ ਅਤੇ ਗਿਣਤੀ ਸਿਰਫ਼ ਘਟ ਸਕਦੀ ਹੈ। CI `npm run check:cycles:ratchet` ਚਲਾਉਂਦਾ ਹੈ। ਘਟਾਓ ਦਾ ਕੰਮ **A-01** ਦੇ ਨਾਲ ਚੱਲੇਗਾ। `check:circular-deps` (dpdm) ਵਿਆਪਕ ਦੂਜੀ ਰਾਏ ਵਜੋਂ ਸਲਾਹਕਾਰੀ ਰਹਿੰਦਾ ਹੈ। +- **ਜਟਿਲਤਾ** — ✅ **ਪੂਰਾ ਹੋਇਆ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ਇੱਕ ESLint ਵਾਕ, `ruleId` ਮੁਤਾਬਕ ਗਿਣਤੀ ਤਾਂ ਜੋ cyclomatic+max-lines ਅਤੇ cognitive ਬੇਸਲਾਈਨਾਂ ਸੁਤੰਤਰ ਰਹਿਣ; ਵੱਖਰੇ `check:complexity` / `check:cognitive-complexity` ਲੋਕਲ `--update` ਲਈ ਬਰਕਰਾਰ ਹਨ। +- **`/api` ਐਂਟੀ-ਹੈਲੂਸੀਨੇਸ਼ਨ** — ✅ **ਪੂਰਾ ਹੋਇਆ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` ਦੀ ਇੱਕ FS ਇਨਵੈਂਟਰੀ, openapi-routes + docs-symbols ਹਾਲੇ ਵੀ ਸੁਤੰਤਰ ਤੌਰ 'ਤੇ ਰਿਪੋਰਟ ਕਰਦੇ ਹਨ; ਵੱਖਰੇ ਚੈਕ ਲੋਕਲ ਰਨਾਂ ਲਈ ਬਰਕਰਾਰ ਹਨ। +- **`check:node-runtime` 11 ਜੌਬਾਂ ਵਿੱਚ ਚੱਲਦਾ ਹੈ** — ⚠️ **ਘੱਟ ROI।** ਹਰ ਇੱਕ ਵੱਖਰਾ ਰਨਰ ਹੈ ਅਤੇ ਚੈਕ <1s ਲੈਂਦਾ ਹੈ; ਇੱਕ ਸਸਤੇ ਪ੍ਰਤੀ-ਜੌਬ ਗਾਰਡ ਨੂੰ ਗੁਆਉਣ ਦੇ ਮੁਕਾਬਲੇ ਕੁੱਲ ਬਚਤ ~10s ਹੈ। ਇੰਨੀ ਉਥਲ-ਪੁਥਲ ਦੇ ਯੋਗ ਨਹੀਂ। +- **CI lint 'ਤੇ `typecheck:noimplicit:core`** — ✅ **lint ਜੌਬ ਤੋਂ ਹਟਾਇਆ ਗਿਆ** (ਇਹ ਸਲਾਹਕਾਰੀ `continue-on-error` ਸੀ); ਬਲੌਕਿੰਗ type ਸਤਹ `typecheck:core` + `check:type-coverage` ਹੈ। ਲੋਕਲ ਸਕ੍ਰਿਪਟ ਬਰਕਰਾਰ ਹੈ। -### ਫਲਿੱਪ / ਫ਼ੈਸਲਾ ਕਰੋ (ਆਪਰੇਟਰ ਨੀਤੀ) +### ਫਲਿਪ / ਫੈਸਲਾ ਕਰੋ (ਓਪਰੇਟਰ ਨੀਤੀ) -- `check:openapi-security-tiers` (ਸਲਾਹਕਾਰੀ) — ❌ **ਸਾਫ਼ ਤੌਰ 'ਤੇ ਫਲਿੱਪ ਕਰਨਯੋਗ ਨਹੀਂ।** ਇਹ 0 ਨਾਲ ਬੰਦ ਹੁੰਦਾ ਹੈ, ਪਰ ਚੇਤਾਵਨੀ ਦਿੰਦਾ ਹੈ ਕਿ `LOCAL_ONLY_API_PREFIXES` ਹੇਠ ਕਈ `traffic-inspector` ਰੂਟਾਂ ਵਿੱਚ `x-loopback-only: true` ਐਨੋਟੇਸ਼ਨ ਮੌਜੂਦ ਨਹੀਂ ਹੈ। ਇਸਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ ਪਹਿਲਾਂ ਉਹ ਐਨੋਟੇਸ਼ਨ `openapi.yaml` ਵਿੱਚ ਸ਼ਾਮਲ ਕਰਨੇ ਲਾਜ਼ਮੀ ਹਨ। -- `typecheck:noimplicit:core` (ਸਲਾਹਕਾਰੀ) — ਬਲੌਕਿੰਗ `check:type-coverage` ਰੈਚਟ ਵੱਲੋਂ ਕਾਫ਼ੀ ਹੱਦ ਤੱਕ ਸਮੇਟਿਆ ਗਿਆ ਹੈ। ਇਸਨੂੰ ਰੈਚਟ ਵਿੱਚ ਫਲਿੱਪ ਕਰੋ ਜਾਂ ਫ਼ਾਲਤੂ ਦੂਜਾ `tsc` ਪਾਸ ਹਟਾਓ। -- `test:vitest:ui` (ਹੁਣ **ਬਲੌਕਿੰਗ**) — ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਅਸਫਲਤਾਵਾਂ ਨੂੰ `vitest.config.ts` ਵਿੱਚ `// #8618` ਟ੍ਰੈਕਿੰਗ ਟਿੱਪਣੀਆਂ ਨਾਲ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਬਾਹਰ ਰੱਖਿਆ ਗਿਆ ਹੈ; ਨਵੀਆਂ ਅਸਫਲਤਾਵਾਂ ਜੌਬ ਨੂੰ ਫੇਲ੍ਹ ਕਰਦੀਆਂ ਹਨ। -- `check:secrets` (gitleaks, 3 ਦਸਤਾਵੇਜ਼ਬੱਧ ਗਲਤ-ਸਕਾਰਾਤਮਕਾਂ ਉੱਤੇ ਜਮਾਇਆ ਹੋਇਆ ਬਲੌਕਿੰਗ ਰੈਚਟ) — 0 ਤੱਕ ਪਹੁੰਚਣ ਲਈ ਉਨ੍ਹਾਂ 3 ਨੂੰ allowlist ਕਰੋ, ਜਾਂ ਇਸਨੂੰ ਸਲਾਹਕਾਰੀ ਦਰਜੇ ਤੱਕ ਘਟਾਓ। GitHub ਦੀ ਮੂਲ secret-scanning + `check:public-creds` ਨਾਲ ਓਵਰਲੈਪ ਕਰਦਾ ਹੈ। -- `check:pr-evidence` (ਬਲੌਕਿੰਗ, PR-body ਗੱਦ ਨੂੰ greps ਕਰਦਾ ਹੈ) — ਗਲਤ-ਸਕਾਰਾਤਮਕ ਦਾ ਉੱਚ ਜੋਖਮ; ਹਟਾਉਣ ਨਾਲ Hard Rule #18 ਦੀ ਲਾਗੂਅਤ ਕਮਜ਼ੋਰ ਹੁੰਦੀ ਹੈ, ਇਸ ਲਈ ਇਹ ਸੱਚਮੁੱਚ ਇੱਕ ਨੀਤੀਗਤ ਫ਼ੈਸਲਾ ਹੈ। -- `semgrep` (ਵੱਖਰਾ ਸਲਾਹਕਾਰੀ) — OWASP ਪਰਿਵਾਰਾਂ ਲਈ CodeQL ਨਾਲ ਓਵਰਲੈਪ ਕਰਦਾ ਹੈ; ਇਸਦੀ ਬੇਸਲਾਈਨ ਨੂੰ ਕਿਸੇ ਰੈਚਟ ਨਾਲ ਜੋੜੋ ਜਾਂ ਹਟਾਓ। +- `check:openapi-security-tiers` (ਸਲਾਹਕਾਰੀ) — ❌ **ਸਾਫ਼ ਤਰੀਕੇ ਨਾਲ ਫਲਿਪ ਕਰਨਯੋਗ ਨਹੀਂ।** ਇਹ 0 ਨਾਲ ਬਾਹਰ ਨਿਕਲਦਾ ਹੈ ਪਰ ਚੇਤਾਵਨੀ ਦਿੰਦਾ ਹੈ ਕਿ `LOCAL_ONLY_API_PREFIXES` ਹੇਠ ਕਈ `traffic-inspector` ਰੂਟਾਂ ਵਿੱਚ `x-loopback-only: true` ਐਨੋਟੇਸ਼ਨ ਮੌਜੂਦ ਨਹੀਂ ਹੈ। ਇਸਨੂੰ ਲਾਗੂ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਉਹ ਐਨੋਟੇਸ਼ਨਾਂ `openapi.yaml` ਵਿੱਚ ਜੋੜਨੀਆਂ ਪੈਣਗੀਆਂ। +- `typecheck:noimplicit:core` (ਸਲਾਹਕਾਰੀ) — ਬਲੌਕਿੰਗ `check:type-coverage` ਰੈਚਿਟ ਵੱਲੋਂ ਵੱਡੇ ਪੱਧਰ 'ਤੇ ਕਵਰ ਕੀਤਾ ਗਿਆ ਹੈ। ਇਸਨੂੰ ਰੈਚਿਟ ਵਿੱਚ ਫਲਿਪ ਕਰੋ ਜਾਂ ਫ਼ਾਲਤੂ ਦੂਜਾ `tsc` ਪਾਸ ਹਟਾਓ। +- `test:vitest:ui` (ਹੁਣ **ਬਲੌਕਿੰਗ**) — ਪਹਿਲਾਂ ਤੋਂ ਮੌਜੂਦ ਅਸਫਲਤਾਵਾਂ ਨੂੰ `vitest.config.ts` ਵਿੱਚ `// #8618` ਟ੍ਰੈਕਿੰਗ ਟਿੱਪਣੀਆਂ ਨਾਲ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਬਾਹਰ ਰੱਖਿਆ ਗਿਆ ਹੈ; ਨਵੀਆਂ ਅਸਫਲਤਾਵਾਂ ਜੌਬ ਨੂੰ ਫੇਲ ਕਰਦੀਆਂ ਹਨ। +- `check:secrets` (gitleaks, 3 ਦਸਤਾਵੇਜ਼ਬੱਧ false-positives 'ਤੇ ਜੰਮਿਆ ਬਲੌਕਿੰਗ ਰੈਚਿਟ) — 0 ਤੱਕ ਪਹੁੰਚਣ ਲਈ ਉਹਨਾਂ 3 ਨੂੰ allowlist ਕਰੋ, ਜਾਂ ਸਲਾਹਕਾਰੀ ਦਰਜੇ 'ਤੇ ਘਟਾਓ। ਇਹ GitHub ਦੀ ਮੂਲ secret-scanning + `check:public-creds` ਨਾਲ ਓਵਰਲੈਪ ਕਰਦਾ ਹੈ। +- `check:pr-evidence` (ਬਲੌਕਿੰਗ, PR-body ਗੱਥ ਨੂੰ grep ਕਰਦਾ ਹੈ) — false-positive ਦਾ ਉੱਚ ਜੋਖਮ; ਹਟਾਉਣ 'ਤੇ Hard Rule #18 ਦੀ ਲਾਗੂਕਰਨ ਸਮਰੱਥਾ ਕਮਜ਼ੋਰ ਹੁੰਦੀ ਹੈ, ਇਸ ਲਈ ਇਹ ਇੱਕ ਅਸਲ ਨੀਤੀਗਤ ਫੈਸਲਾ ਹੈ। +- `semgrep` (ਵੱਖਰਾ ਸਲਾਹਕਾਰੀ) — OWASP ਪਰਿਵਾਰਾਂ ਲਈ CodeQL ਨਾਲ ਓਵਰਲੈਪ ਕਰਦਾ ਹੈ; ਇਸਦੀ ਬੇਸਲਾਈਨ ਨੂੰ ਕਿਸੇ ਰੈਚਿਟ ਨਾਲ ਜੋੜੋ ਜਾਂ ਹਟਾਓ। --- diff --git a/docs/i18n/pa/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/pa/docs/compression/COMPRESSION_GUIDE.md index c80bb39f28c8..f32c4250d601 100644 --- a/docs/i18n/pa/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/pa/docs/compression/COMPRESSION_GUIDE.md @@ -436,8 +436,7 @@ Caveman ਆਉਟਪੁੱਟ ਮੋਡ **ਆਪਟ-ਇਨ** ਹੈ — ਇਸ `messages` ਵਾਲੀ body ਉੱਤੇ, ਇੱਕ ਸਮੱਗਰੀ ਬਾਈਪਾਸ (`shouldBypassCavemanOutputMode()` ਜੋ `open-sse/services/compression/outputMode.ts` ਵਿੱਚ ਹੈ) ਆਖ਼ਰੀ ਤਿੰਨ ਸੁਨੇਹਿਆਂ ਦੀ ਜਾਂਚ ਕਰਦਾ ਹੈ ਅਤੇ ਜੇ ਉਹ ਇਸਦੇ ਸੁਰੱਖਿਆ, ਅਪਰਿਵਰਤਨੀ-ਕਾਰਵਾਈ, ਸਪਸ਼ਟੀਕਰਨ ਜਾਂ ਕ੍ਰਮ-ਸੰਵੇਦਨਸ਼ੀਲ ਕੀਵਰਡਾਂ ਨਾਲ ਮੇਲ ਖਾਂਦੇ ਹਨ, -ਤਾਂ ਪੂਰੇ turn ਲਈ ਸ਼ੈਲੀਆਂ ਛੱਡ ਦਿੰਦਾ ਹੈ। ਬਾਈਪਾਸ ਡੈਸ਼ਬੋਰਡ ਦੇ -**Auto-Clarity Bypass** ਟੌਗਲ (`cavemanOutputMode.autoClarity`) ਦੀ ਸੈਟਿੰਗ ਅਨੁਸਾਰ ਚੱਲਦਾ ਹੈ। +ਤਾਂ ਪੂਰੇ turn ਲਈ ਸ਼ੈਲੀਆਂ ਛੱਡ ਦਿੰਦਾ ਹੈ। ਜਦ ਤੱਕ ਡੈਸ਼ਬੋਰਡ ਦਾ **Auto-Clarity Bypass** ਟੌਗਲ (`cavemanOutputMode.autoClarity`) ਚਾਲੂ ਹੈ, ਬਾਈਪਾਸ ਚੱਲਦਾ ਹੈ, ਜੋ ਮੂਲ ਸੈਟਿੰਗ ਹੈ; ਟੌਗਲ ਬੰਦ ਹੋਣ 'ਤੇ ਚੁਣੀਆਂ ਸ਼ੈਲੀਆਂ ਉਨ੍ਹਾਂ ਟਰਨਾਂ 'ਤੇ ਵੀ ਲਾਗੂ ਹੁੰਦੀਆਂ ਹਨ। ਜਦੋਂ ਬਾਈਪਾਸ turn ਨੂੰ ਲੰਘਣ ਦਿੰਦਾ ਹੈ, `placeSystemInstruction()` (ਉਸੇ ਫ਼ਾਈਲ ਵਿੱਚ), ਜੋ ਕਦੇ ਵੀ ਨਵਾਂ `messages[0]` ਨਹੀਂ ਬਣਾਉਂਦਾ, ਬਲਾਕ ਨੂੰ ਹੇਠ ਲਿਖਿਆਂ ਵਿੱਚੋਂ ਸਭ ਤੋਂ ਪਹਿਲਾਂ ਮਿਲਣ ਵਾਲੀ ਥਾਂ ਰੱਖਦਾ ਹੈ: diff --git a/docs/i18n/pa/docs/reference/ENVIRONMENT.md b/docs/i18n/pa/docs/reference/ENVIRONMENT.md index a1e0f7212029..b4b036b7a629 100644 --- a/docs/i18n/pa/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/pa/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. ਲੋੜੀਂਦੇ ਗੁਪਤ ਮੁੱਲ -ਇਹ ਪਹਿਲੀ ਵਾਰ ਚਲਾਉਣ ਤੋਂ ਪਹਿਲਾਂ **ਲਾਜ਼ਮੀ** ਸੈੱਟ ਕੀਤੇ ਜਾਣੇ ਚਾਹੀਦੇ ਹਨ। ਇਨ੍ਹਾਂ ਤੋਂ ਬਿਨਾਂ, ਐਪਲੀਕੇਸ਼ਨ ਜਾਂ ਤਾਂ ਸ਼ੁਰੂ ਹੋਣ ਤੋਂ ਇਨਕਾਰ ਕਰੇਗੀ ਜਾਂ ਅਸੁਰੱਖਿਅਤ ਡਿਫੌਲਟਾਂ ਨਾਲ ਕੰਮ ਕਰੇਗੀ। - -| ਵੇਰੀਏਬਲ | ਲੋੜੀਂਦਾ | ਡਿਫੌਲਟ | ਸਰੋਤ ਫ਼ਾਈਲ | ਵੇਰਵਾ | -| ---------------------------- | ------------------- | ------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **ਹਾਂ** | _(ਕੋਈ ਨਹੀਂ)_ | `src/lib/auth` | ਸਾਰੀਆਂ ਡੈਸ਼ਬੋਰਡ ਸੈਸ਼ਨ ਕੁਕੀਜ਼ (JWT) ਨੂੰ ਸਾਈਨ/ਤਸਦੀਕ ਕਰਦਾ ਹੈ। `openssl rand -base64 48` ਨਾਲ ਬਣਾਓ। | -| `API_KEY_SECRET` | **ਹਾਂ** | _(ਕੋਈ ਨਹੀਂ)_ | `src/lib/db/apiKeys.ts` | SQLite ਵਿੱਚ ਸਟੋਰ ਕੀਤੇ API ਕੁੰਜੀ ਮੁੱਲਾਂ ਲਈ AES ਇਨਕ੍ਰਿਪਸ਼ਨ ਕੁੰਜੀ। `openssl rand -hex 32` ਨਾਲ ਬਣਾਓ। | -| `INITIAL_PASSWORD` | **ਹਾਂ** | `CHANGEME` | ਬੂਟਸਟਰੈਪ ਸਕ੍ਰਿਪਟ | ਸ਼ੁਰੂਆਤੀ ਐਡਮਿਨ ਡੈਸ਼ਬੋਰਡ ਪਾਸਵਰਡ ਸੈੱਟ ਕਰਦਾ ਹੈ (`.env.example` ਦੇ ਡਿਫੌਲਟ ਨਾਲ ਮੇਲ ਖਾਂਦਾ ਹੈ — ਬਦਲਾਅ ਲਈ ਮਜਬੂਰ ਕਰਨ ਵਾਸਤੇ ਇਸਨੂੰ ਜਾਣ-ਬੁੱਝ ਕੇ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਅਸੁਰੱਖਿਅਤ ਰੱਖਿਆ ਗਿਆ ਹੈ)। **ਪਹਿਲੀ ਵਰਤੋਂ ਤੋਂ ਪਹਿਲਾਂ ਬਦਲੋ।** ਲੌਗਇਨ ਕਰਨ ਤੋਂ ਬਾਅਦ, Dashboard → Settings → Security ਰਾਹੀਂ ਬਦਲੋ। | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **ਹਾਂ** (ਪ੍ਰੋਡਕਸ਼ਨ) | _(ਸੈੱਟ ਨਹੀਂ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ਅੰਦਰੂਨੀ Codex Responses WebSocket ਬ੍ਰਿਜ ਲਈ ਸਾਂਝਾ ਗੁਪਤ ਮੁੱਲ। Electron/ਬ੍ਰਾਊਜ਼ਰ WS ਰੀਲੇਅ ਅਤੇ OmniRoute ਵਿਚਕਾਰ ਬ੍ਰਿਜ ਬੇਨਤੀਆਂ ਨੂੰ ਪ੍ਰਮਾਣਿਤ ਕਰਦਾ ਹੈ। ⚠️ **ਪ੍ਰੋਡਕਸ਼ਨ ਵਿੱਚ ਲਾਜ਼ਮੀ — ਸੈੱਟ ਨਾ ਹੋਣ 'ਤੇ ਸਾਰੀਆਂ WS ਬ੍ਰਿਜ ਬੇਨਤੀਆਂ ਰੱਦ ਕਰ ਦਿੱਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।** `openssl rand -base64 32` ਨਾਲ ਬਣਾਓ। | -| `OMNIROUTE_SW_BUILD_ID` | ਨਹੀਂ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ਸ਼ੈੱਲ (#11779) ਲਈ ਸਪਸ਼ਟ ਸਰਵਿਸ-ਵਰਕਰ ਕੈਸ਼-ਬਸਟਿੰਗ id; ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਲੜੀ ਵਿੱਚ ਪਹਿਲਾਂ। | -| `SOURCE_VERSION` | ਨਹੀਂ | _(ਸੈੱਟ ਨਹੀਂ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ਲੜੀ ਵਿੱਚ ਦੂਜਾ — PaaS ਬਿਲਡਰਾਂ (ਉਦਾਹਰਨ ਲਈ Heroku-ਸ਼ੈਲੀ) ਵੱਲੋਂ ਡਿਪਲੌਇ ਕੀਤੇ ਕਮਿਟ ਵਜੋਂ ਸੈੱਟ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। | -| `NEXT_PUBLIC_SW_BUILD_ID` | ਨਹੀਂ | _(ਪ੍ਰਾਪਤ ਕੀਤਾ)_ | `src/shared/components/PwaRegister.tsx` | ਬਿਲਡ-ਟਾਈਮ ਜਨਤਕ ਮੁੱਲ, ਜਿਸਦੀ ਵਰਤੋਂ ਕਲਾਇੰਟ `/sw.js?v=…` ਨੂੰ ਰਜਿਸਟਰ ਕਰਨ ਲਈ ਕਰਦਾ ਹੈ; ਉਪਰੋਕਤ ਦੋਵਾਂ ਤੋਂ ਅਤੇ ਫਿਰ git SHA ਤੋਂ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। | -| `OMNIROUTE_PEER_STAMP_TOKEN` | ਨਹੀਂ (ਆਟੋ) | _(ਹਰ ਬੂਟ 'ਤੇ ਆਟੋ)_ | `src/server/authz/policies/management.ts` | ਹਰ-ਪ੍ਰਕਿਰਿਆ ਗੁਪਤ ਮੁੱਲ, ਜੋ ਸਾਬਤ ਕਰਦਾ ਹੈ ਕਿ ਭਰੋਸੇਯੋਗ ਪੀਅਰ-IP ਸਟੈਂਪ OmniRoute ਦੇ ਆਪਣੇ HTTP ਸਰਵਰ (`scripts/dev/peer-stamp.mjs`) ਤੋਂ ਆਇਆ ਹੈ। authz ਮਿਡਲਵੇਅਰ ਬੇਨਤੀ ਦੀ ਸਥਾਨਕਤਾ (LOCAL_ONLY ਰੂਟਾਂ ਦੀ ਲੂਪਬੈਕ/LAN ਗੇਟਿੰਗ) 'ਤੇ ਕੇਵਲ ਉਦੋਂ ਹੀ ਭਰੋਸਾ ਕਰਦਾ ਹੈ ਜਦੋਂ ਸਟੈਂਪ ਵਿੱਚ ਇਹ ਟੋਕਨ ਹੋਵੇ। ਹਰ ਬੂਟ 'ਤੇ ਆਪਣੇ ਆਪ ਬਣਦਾ ਹੈ — ਇਸਨੂੰ ਸੈੱਟ ਨਾ ਕਰੋ; ਸਿਰਫ਼ ਉਹਨਾਂ ਬਹੁ-ਪ੍ਰਕਿਰਿਆ ਸੈੱਟਅੱਪਾਂ ਲਈ ਇਸਨੂੰ ਨਿਸ਼ਚਿਤ ਕਰੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਸਟੈਂਪ ਸਾਂਝਾ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ। | -| `OMNIROUTE_TRUSTED_PROXIES` | ਨਹੀਂ | _(ਸੈੱਟ ਨਹੀਂ)_ | `scripts/dev/peer-stamp.mjs` | ਰਿਵਰਸ ਪ੍ਰੌਕਸੀਆਂ ਦੇ ਕੌਮਾ ਨਾਲ ਵੱਖ ਕੀਤੇ IP ਜਾਂ CIDR ਰੇਂਜ, ਜਿਨ੍ਹਾਂ ਦੇ `X-Forwarded-For` / `X-Real-IP` 'ਤੇ IP ਮਨਜ਼ੂਰ/ਇਨਕਾਰ ਸੂਚੀ ਭਰੋਸਾ ਕਰ ਸਕਦੀ ਹੈ; ਇਹ ਲੂਪਬੈਕ, ਨਿੱਜੀ-ਨੈੱਟਵਰਕ ਪਤਿਆਂ ਅਤੇ Cloudflare ਐਜਾਂ ਤੋਂ ਇਲਾਵਾ ਹਨ, ਜਿਨ੍ਹਾਂ 'ਤੇ ਹਮੇਸ਼ਾ ਭਰੋਸਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਸਿਰਫ਼ ਕਿਸੇ ਹੋਰ ਜਨਤਕ ਪਤੇ ਵਾਲੀ ਪ੍ਰੌਕਸੀ ਲਈ ਲੋੜੀਂਦਾ ਹੈ; ਨਹੀਂ ਤਾਂ ਫਿਲਟਰ ਪ੍ਰੌਕਸੀ ਦੇ ਆਪਣੇ ਪਤੇ ਦਾ ਮੁਲਾਂਕਣ ਕਰਦਾ ਹੈ। | +**ਹਾਂ** ਨਾਲ ਅੰਕਿਤ ਪਰ ਸੈੱਟ ਨਾ ਕੀਤੇ ਗਏ ਗੁਪਤ ਮੁੱਲ (`JWT_SECRET`, `API_KEY_SECRET`) ਪਹਿਲੀ ਵਾਰ ਚਲਾਉਣ 'ਤੇ ਆਪਣੇ-ਆਪ ਬਣਾਏ ਜਾਂਦੇ ਹਨ ਅਤੇ ਡਾਟਾ ਡਾਇਰੈਕਟਰੀ ਵਿੱਚ `server.env` ਵਿੱਚ ਸਟੋਰ ਹੁੰਦੇ ਹਨ — ਜਦੋਂ ਉਹ ਡਾਇਰੈਕਟਰੀ ਅਸਥਾਈ ਹੋਵੇ ਤਾਂ ਇਹਨਾਂ ਨੂੰ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਸੈੱਟ ਕਰੋ। ਇਸ ਦਾ ਅਪਵਾਦ `OMNIROUTE_WS_BRIDGE_SECRET` ਹੈ: ਇਹ `server.env` ਵਿੱਚ ਸਟੋਰ ਨਹੀਂ ਹੁੰਦਾ — ਸਰਵਰ ਐਂਟਰੀਪੁਆਇੰਟ ਹਰੇਕ ਬੂਟ ਲਈ ਇੱਕ ਅਸਥਾਈ ਮੁੱਲ ਬਣਾਉਂਦੇ ਹਨ ਜਿਸਨੂੰ ਸਿਰਫ਼ ਪ੍ਰੋਸੈਸ-ਅੰਦਰੂਨੀ ਬ੍ਰਿਜ ਹੀ ਵਰਤ ਸਕਦਾ ਹੈ, ਇਸ ਲਈ ਬਾਹਰੀ ਬ੍ਰਿਜ ਕਲਾਇੰਟਾਂ ਨੂੰ ਇਹਨੂੰ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਸੈੱਟ ਕਰਨਾ ਪੈਂਦਾ ਹੈ। + +| ਵੇਰੀਏਬਲ | ਲੋੜੀਂਦਾ | ਡਿਫੌਲਟ | ਸਰੋਤ ਫ਼ਾਈਲ | ਵੇਰਵਾ | +| ---------------------------- | ------------------- | ------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **ਹਾਂ** | _(ਕੋਈ ਨਹੀਂ)_ | `src/lib/auth` | ਸਾਰੀਆਂ ਡੈਸ਼ਬੋਰਡ ਸੈਸ਼ਨ ਕੁਕੀਜ਼ (JWT) ਨੂੰ ਸਾਈਨ/ਤਸਦੀਕ ਕਰਦਾ ਹੈ। `openssl rand -base64 48` ਨਾਲ ਬਣਾਓ। | +| `API_KEY_SECRET` | **ਹਾਂ** | _(ਕੋਈ ਨਹੀਂ)_ | `src/lib/db/apiKeys.ts` | SQLite ਵਿੱਚ ਸਟੋਰ ਕੀਤੇ API ਕੁੰਜੀ ਮੁੱਲਾਂ ਲਈ AES ਇਨਕ੍ਰਿਪਸ਼ਨ ਕੁੰਜੀ। `openssl rand -hex 32` ਨਾਲ ਬਣਾਓ। | +| `INITIAL_PASSWORD` | ਨਹੀਂ | _(ਸੈੱਟ ਨਹੀਂ)_ | `src/lib/auth/managementPassword.ts` | ਜਦੋਂ ਤੱਕ ਕੋਈ ਪਾਸਵਰਡ ਸਟੋਰ ਨਹੀਂ ਹੁੰਦਾ, ਸ਼ੁਰੂਆਤ ਸਮੇਂ ਡੈਸ਼ਬੋਰਡ ਪਾਸਵਰਡ ਸੈੱਟ ਕਰਦਾ ਹੈ; ਪਾਸਵਰਡ ਸਟੋਰ ਹੋਣ ਤੋਂ ਬਾਅਦ ਇਸਨੂੰ ਅਣਡਿੱਠਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। npm ਅਤੇ ਸਰੋਤ ਇੰਸਟਾਲ `.env.example` ਨੂੰ `.env` ਵਿੱਚ ਕਾਪੀ ਕਰਦੇ ਹਨ, ਇਸ ਲਈ ਉਹ ਇਸਦੇ ਜਨਤਕ ਤੌਰ 'ਤੇ ਜਾਣੇ-ਪਛਾਣੇ ਪਲੇਸਹੋਲਡਰ `CHANGEME` ਨਾਲ ਸ਼ੁਰੂ ਹੁੰਦੇ ਹਨ: ਪਹਿਲੇ ਬੂਟ ਤੋਂ ਪਹਿਲਾਂ ਇਸਨੂੰ ਬਦਲੋ, ਜਾਂ ਪਹਿਲੀ ਫੇਰੀ 'ਤੇ ਆਨਬੋਰਡਿੰਗ ਵਿਜ਼ਾਰਡ ਵਿੱਚ ਪਾਸਵਰਡ ਬਣਾਉਣ ਲਈ ਉਸ `.env` ਵਿੱਚ `INITIAL_PASSWORD=` (ਖ਼ਾਲੀ) ਸੈੱਟ ਕਰੋ — ਜਿਵੇਂ ਇੱਕ ਅਣ-ਸੈੱਟ ਮੁੱਲ (Docker ਈਮੇਜ ਦਾ ਡਿਫੌਲਟ) ਕਰਦਾ ਹੈ। ਸਟੋਰ ਕੀਤਾ ਪਾਸਵਰਡ Dashboard → Settings → Security ਵਿੱਚ ਬਦਲੋ, ਜਾਂ `DATA_DIR` ਨੂੰ ਸਰਵਰ ਦੀ ਡਾਟਾ ਡਾਇਰੈਕਟਰੀ 'ਤੇ ਸੈੱਟ ਕਰ ਕੇ `omniroute reset-password` (ਸਰੋਤ ਚੈੱਕਆਊਟ ਵਿੱਚ `node bin/reset-password.mjs`) ਚਲਾਓ। | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **ਹਾਂ** (ਪ੍ਰੋਡਕਸ਼ਨ) | _(ਸੈੱਟ ਨਹੀਂ)_ | `src/app/api/internal/codex-responses-ws/route.ts` | ਅੰਦਰੂਨੀ Codex Responses WebSocket ਬ੍ਰਿਜ ਲਈ ਸਾਂਝਾ ਗੁਪਤ ਮੁੱਲ। Electron/ਬ੍ਰਾਊਜ਼ਰ WS ਰੀਲੇਅ ਅਤੇ OmniRoute ਵਿਚਕਾਰ ਬ੍ਰਿਜ ਬੇਨਤੀਆਂ ਨੂੰ ਪ੍ਰਮਾਣਿਤ ਕਰਦਾ ਹੈ। ⚠️ **ਪ੍ਰੋਡਕਸ਼ਨ ਵਿੱਚ ਲਾਜ਼ਮੀ — ਸੈੱਟ ਨਾ ਹੋਣ 'ਤੇ ਸਾਰੀਆਂ WS ਬ੍ਰਿਜ ਬੇਨਤੀਆਂ ਰੱਦ ਕਰ ਦਿੱਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।** `openssl rand -base64 32` ਨਾਲ ਬਣਾਓ। | +| `OMNIROUTE_SW_BUILD_ID` | ਨਹੀਂ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ਸ਼ੈੱਲ (#11779) ਲਈ ਸਪਸ਼ਟ ਸਰਵਿਸ-ਵਰਕਰ ਕੈਸ਼-ਬਸਟਿੰਗ id; ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਲੜੀ ਵਿੱਚ ਪਹਿਲਾਂ। | +| `SOURCE_VERSION` | ਨਹੀਂ | _(ਸੈੱਟ ਨਹੀਂ)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ਲੜੀ ਵਿੱਚ ਦੂਜਾ — PaaS ਬਿਲਡਰਾਂ (ਉਦਾਹਰਨ ਲਈ Heroku-ਸ਼ੈਲੀ) ਵੱਲੋਂ ਡਿਪਲੌਇ ਕੀਤੇ ਕਮਿਟ ਵਜੋਂ ਸੈੱਟ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। | +| `NEXT_PUBLIC_SW_BUILD_ID` | ਨਹੀਂ | _(ਪ੍ਰਾਪਤ ਕੀਤਾ)_ | `src/shared/components/PwaRegister.tsx` | ਬਿਲਡ-ਟਾਈਮ ਜਨਤਕ ਮੁੱਲ, ਜਿਸਦੀ ਵਰਤੋਂ ਕਲਾਇੰਟ `/sw.js?v=…` ਨੂੰ ਰਜਿਸਟਰ ਕਰਨ ਲਈ ਕਰਦਾ ਹੈ; ਉਪਰੋਕਤ ਦੋਵਾਂ ਤੋਂ ਅਤੇ ਫਿਰ git SHA ਤੋਂ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। | +| `OMNIROUTE_PEER_STAMP_TOKEN` | ਨਹੀਂ (ਆਟੋ) | _(ਹਰ ਬੂਟ 'ਤੇ ਆਟੋ)_ | `src/server/authz/policies/management.ts` | ਹਰ-ਪ੍ਰਕਿਰਿਆ ਗੁਪਤ ਮੁੱਲ, ਜੋ ਸਾਬਤ ਕਰਦਾ ਹੈ ਕਿ ਭਰੋਸੇਯੋਗ ਪੀਅਰ-IP ਸਟੈਂਪ OmniRoute ਦੇ ਆਪਣੇ HTTP ਸਰਵਰ (`scripts/dev/peer-stamp.mjs`) ਤੋਂ ਆਇਆ ਹੈ। authz ਮਿਡਲਵੇਅਰ ਬੇਨਤੀ ਦੀ ਸਥਾਨਕਤਾ (LOCAL_ONLY ਰੂਟਾਂ ਦੀ ਲੂਪਬੈਕ/LAN ਗੇਟਿੰਗ) 'ਤੇ ਕੇਵਲ ਉਦੋਂ ਹੀ ਭਰੋਸਾ ਕਰਦਾ ਹੈ ਜਦੋਂ ਸਟੈਂਪ ਵਿੱਚ ਇਹ ਟੋਕਨ ਹੋਵੇ। ਹਰ ਬੂਟ 'ਤੇ ਆਪਣੇ ਆਪ ਬਣਦਾ ਹੈ — ਇਸਨੂੰ ਸੈੱਟ ਨਾ ਕਰੋ; ਸਿਰਫ਼ ਉਹਨਾਂ ਬਹੁ-ਪ੍ਰਕਿਰਿਆ ਸੈੱਟਅੱਪਾਂ ਲਈ ਇਸਨੂੰ ਨਿਸ਼ਚਿਤ ਕਰੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਸਟੈਂਪ ਸਾਂਝਾ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ। | +| `OMNIROUTE_TRUSTED_PROXIES` | ਨਹੀਂ | _(ਸੈੱਟ ਨਹੀਂ)_ | `scripts/dev/peer-stamp.mjs` | ਰਿਵਰਸ ਪ੍ਰੌਕਸੀਆਂ ਦੇ ਕੌਮਾ ਨਾਲ ਵੱਖ ਕੀਤੇ IP ਜਾਂ CIDR ਰੇਂਜ, ਜਿਨ੍ਹਾਂ ਦੇ `X-Forwarded-For` / `X-Real-IP` 'ਤੇ IP ਮਨਜ਼ੂਰ/ਇਨਕਾਰ ਸੂਚੀ ਭਰੋਸਾ ਕਰ ਸਕਦੀ ਹੈ; ਇਹ ਲੂਪਬੈਕ, ਨਿੱਜੀ-ਨੈੱਟਵਰਕ ਪਤਿਆਂ ਅਤੇ Cloudflare ਐਜਾਂ ਤੋਂ ਇਲਾਵਾ ਹਨ, ਜਿਨ੍ਹਾਂ 'ਤੇ ਹਮੇਸ਼ਾ ਭਰੋਸਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਸਿਰਫ਼ ਕਿਸੇ ਹੋਰ ਜਨਤਕ ਪਤੇ ਵਾਲੀ ਪ੍ਰੌਕਸੀ ਲਈ ਲੋੜੀਂਦਾ ਹੈ; ਨਹੀਂ ਤਾਂ ਫਿਲਟਰ ਪ੍ਰੌਕਸੀ ਦੇ ਆਪਣੇ ਪਤੇ ਦਾ ਮੁਲਾਂਕਣ ਕਰਦਾ ਹੈ। | ### ਬਣਾਉਣ ਲਈ ਕਮਾਂਡਾਂ @@ -217,38 +217,38 @@ OmniRoute ਸਾਰੀ ਸਥਾਈ ਸਟੋਰੇਜ ਲਈ **SQLite** (`bette ## 4. ਸੁਰੱਖਿਆ ਅਤੇ ਪ੍ਰਮਾਣੀਕਰਨ -| ਵੇਰੀਏਬਲ | ਡਿਫੌਲਟ | ਸਰੋਤ ਫ਼ਾਈਲ | ਵੇਰਵਾ | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ਮਸ਼ੀਨ ਫਿੰਗਰਪ੍ਰਿੰਟਿੰਗ ਲਈ ਹਾਰਡਵੇਅਰ ਪਛਾਣਕਰਤਾਵਾਂ ਨਾਲ ਜੋੜਿਆ ਜਾਣ ਵਾਲਾ ਸਾਲਟ। ਅਲੱਗਾਵ ਲਈ ਹਰ ਡਿਪਲੌਇਮੈਂਟ ਮੁਤਾਬਕ ਇਸਨੂੰ ਬਦਲੋ। | -| `OMNIROUTE_CLI_SALT` | _(ਅਣਸੈੱਟ = ਹਰ ਇੰਸਟਾਲ ਲਈ ਬੇਤਰਤੀਬ ਸਾਲਟ, ਜੋ `/cli-token-salt.json` 'ਤੇ ਸਥਾਈ ਰੂਪ ਵਿੱਚ ਸੰਭਾਲਿਆ ਜਾਂਦਾ ਹੈ)_ | `src/lib/machineToken.ts` | ਸਥਾਨਕ CLI ਪ੍ਰਮਾਣੀਕਰਨ ਟੋਕਨ ਬਣਾਉਣ ਲਈ HMAC ਸਾਲਟ। ਇਹ ਮੁੱਲ ਸੈੱਟ ਕਰਨ ਨਾਲ ਮਸ਼ੀਨ ਉੱਤੇ ਸਾਰੇ CLI ਟੋਕਨ ਰੋਟੇਟ ਹੋ ਜਾਂਦੇ ਹਨ ਅਤੇ ਇਸਨੂੰ ਹਮੇਸ਼ਾ ਸਥਾਈ ਤੌਰ 'ਤੇ ਸੰਭਾਲੇ ਸਾਲਟ ਉੱਤੇ ਤਰਜੀਹ ਮਿਲਦੀ ਹੈ। `docs/security/CLI_TOKEN.md` ਵੇਖੋ। | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ਸੈਸ਼ਨ ਕੁਕੀਜ਼ ਉੱਤੇ `Secure` ਫਲੈਗ ਸੈੱਟ ਕਰਦਾ ਹੈ। HTTPS ਦੇ ਪਿੱਛੇ ਚਲਾਉਂਦੇ ਸਮੇਂ ਇਹ **ਲਾਜ਼ਮੀ ਤੌਰ 'ਤੇ `true` ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ**। | -| `REQUIRE_API_KEY` | `false` | API ਮਿਡਲਵੇਅਰ | ਜਦੋਂ `true` ਹੋਵੇ, ਤਾਂ ਸਾਰੀਆਂ `/v1/*` ਪ੍ਰੌਕਸੀ ਬੇਨਤੀਆਂ ਵਿੱਚ ਇੱਕ ਵੈਧ API ਕੁੰਜੀ ਸ਼ਾਮਲ ਹੋਣੀ ਲਾਜ਼ਮੀ ਹੈ। **ਇਹ ਫਲੈਗ `GET /v1/models` ਨੂੰ ਨਿਯੰਤਰਿਤ ਨਹੀਂ ਕਰਦਾ**, ਜੋ ਇਸਦੀ ਬਜਾਏ ਡੈਸ਼ਬੋਰਡ ਲੌਗਇਨ ਸਥਿਤੀ (`requireAuthForModels`) ਦੀ ਪਾਲਣਾ ਕਰਦਾ ਹੈ — ਇਸ ਲਈ `/v1/models` ਤੋਂ ਮਿਲਣ ਵਾਲੇ `401` ਦਾ ਇਹ ਅਰਥ ਨਹੀਂ ਕਿ ਇਨਫਰੈਂਸ ਸੁਰੱਖਿਅਤ ਹੈ। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ਵੇਖੋ। | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ਡੈਸ਼ਬੋਰਡ UI ਵਿੱਚ API ਕੁੰਜੀ ਦੇ ਪੂਰੇ ਮੁੱਲ ਦਿਖਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਡੈਸ਼ਬੋਰਡ ਫੀਚਰ ਫਲੈਗਾਂ ਤੋਂ ਸੰਰਚਨਾਯੋਗ; ਸਾਂਝੀਆਂ ਇੰਸਟੈਂਸਾਂ ਉੱਤੇ ਸੁਰੱਖਿਆ ਜੋਖਮ। | -| `NO_LOG_API_KEY_IDS` | _(ਖਾਲੀ)_ | `src/lib/compliance/index.ts` | ਕਾਮਿਆਂ ਨਾਲ ਵੱਖ ਕੀਤੀਆਂ API ਕੁੰਜੀ ID, ਜੋ ਬੇਨਤੀ ਲੌਗਿੰਗ ਨੂੰ ਬਾਈਪਾਸ ਕਰਦੀਆਂ ਹਨ (GDPR ਅਨੁਕੂਲਤਾ)। | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ਅਣਸੈੱਟ = ਅਸੀਮਤ)_ | `src/shared/utils/apiKeyPolicy.ts` | ਉਹਨਾਂ API ਕੁੰਜੀਆਂ ਲਈ ਲਾਗੂ ਹੋਣ ਵਾਲਾ ਫਾਲਬੈਕ ਪ੍ਰਤੀ-ਦਿਨ ਬੇਨਤੀ ਬਜਟ, ਜਿਨ੍ਹਾਂ ਦਾ `rate_limits` ਕਾਲਮ null ਹੈ। ਅਣਸੈੱਟ ਜਾਂ ਖਾਲੀ: ਕੋਈ ਅਪਰਤੱਖ ਸੀਮਾ ਨਹੀਂ (#2289, #11017)। `0` ਵੀ ਇਸੇ ਦੇ ਬਰਾਬਰ ਹੈ (ਅਸੀਮਤ)। ਧਨਾਤਮਕ ਪੂਰਨ ਅੰਕ N, N/ਦਿਨ, 5N/ਹਫ਼ਤਾ, 20N/ਮਹੀਨਾ ਸਮਰੱਥ ਕਰਦਾ ਹੈ। ਗਲਤ-ਰੂਪ ਵਾਲੇ ਗੈਰ-ਖਾਲੀ ਮੁੱਲ ਪੁਰਾਣੀਆਂ 1000/ਦਿਨ, 5000/ਹਫ਼ਤਾ, 20000/ਮਹੀਨਾ ਵਿੰਡੋਜ਼ ਉੱਤੇ ਵਾਪਸ ਚਲੇ ਜਾਂਦੇ ਹਨ। | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ਬੇਨਤੀ ਬਾਡੀ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਮਨਜ਼ੂਰਸ਼ੁਦਾ ਆਕਾਰ। ਇਸ ਸੀਮਾ ਤੋਂ ਵੱਧ ਪੇਲੋਡਾਂ ਨੂੰ ਰੱਦ ਕਰਦਾ ਹੈ। | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ਇਸ ਥ੍ਰੈਸ਼ਹੋਲਡ ਦੇ ਬਰਾਬਰ ਜਾਂ ਇਸ ਤੋਂ ਵੱਧ ਆਕਾਰ ਵਾਲੀਆਂ ਅਸਲ ਬੇਨਤੀ ਬਾਡੀਆਂ JSON ਪਾਰਸਿੰਗ ਤੋਂ ਪਹਿਲਾਂ ਐਟਾਮਿਕ ਪ੍ਰਕਿਰਿਆ-ਸਥਾਨਕ ਹੈਵੀਵੇਟ ਦਾਖਲਾ ਲੀਜ਼ ਲੈਂਦੀਆਂ ਹਨ (BYTE ਪਾਥ, ਜਿਸ ਵਿੱਚ `POST /v1/responses` ਵੀ ਸ਼ਾਮਲ ਹੈ)। ਉਹੀ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ਸਿਹਤਮੰਦ-ਹੈੱਡਰੂਮ ਬਚਾਅ, ਜੋ ਸੰਰਚਨਾ-ਭਾਰੀ ਬੇਨਤੀਆਂ ਲਈ ਹੈ; ਫਿਰ ਵੀ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ਦੁਆਰਾ ਸੀਮਿਤ ਹੈ, ਤਾਂ ਜੋ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ਮੁੜ ਨਾ ਆਵੇ। | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ਸੀਮਿਤ ਇੰਜੈਸ਼ਨ ਦੌਰਾਨ ਪੜ੍ਹੇ ਗਏ ਬਾਈਟਾਂ ਦੇ ਆਧਾਰ 'ਤੇ ਲਾਗੂ ਕੀਤੀ ਜਾਣ ਵਾਲੀ ਚੈਟ-ਰੂਟ ਦੀ ਸਖ਼ਤ ਸੀਮਾ, ਜਿਸ ਵਿੱਚ ਗੁੰਮ, ਅਵੈਧ ਜਾਂ ਗੁਮਰਾਹਕੁੰਨ `Content-Length` ਵਾਲੀਆਂ ਬੇਨਤੀਆਂ ਵੀ ਸ਼ਾਮਲ ਹਨ; ਸੀਮਾ ਤੋਂ ਵੱਧ ਹੋਣ 'ਤੇ `413` ਮਿਲਦਾ ਹੈ। | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ਅਣਸੈੱਟ — ਬੇਨਤੀ-ਗਿਣਤੀ ਦੀ ਕੋਈ ਸੀਮਾ ਨਹੀਂ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ਫੈਨਆਉਟ:** ਇਹ ਪੁਰਾਣੀ ਬੇਨਤੀ-ਗਿਣਤੀ ਸੀਮਾ ਹੁਣ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਲਾਗੂ ਹੁੰਦੀ ਹੈ ਜਦੋਂ ਇਸਨੂੰ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ ਹੋਵੇ। ਇਸਨੂੰ ਅਣਸੈੱਟ ਛੱਡਣ 'ਤੇ (ਡਿਫਾਲਟ), ਹੈਵੀਵੇਟ ਚੈਟ ਦਾਖਲਾ ਇਸ ਦੀ ਬਜਾਏ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ਦੁਆਰਾ ਨਿਯੰਤਰਿਤ ਹੁੰਦਾ ਹੈ — **ਇੱਕ ਪ੍ਰੋਸੈਸ** (ਇੱਕ V8 ਹੀਪ) ਵਿੱਚ ਪ੍ਰੋਸੈਸ ਦੀ ਅਸਲ ਮੈਮੋਰੀ ਸੀਮਾ ਤੋਂ ਆਟੋਮੈਟਿਕ ਤੌਰ 'ਤੇ ਨਿਰਧਾਰਤ ਬਾਈਟ ਬਜਟ। ਇੱਕੋ ਸਮੇਂ ਚੱਲ ਰਹੀਆਂ ਲਗਭਗ 750k-ਟੋਕਨ ਵਾਲੀਆਂ ਦੋ `/v1/responses` ਬੇਨਤੀਆਂ ਲਗਭਗ 12 GiB ਹੀਪਾਂ ਨੂੰ ਅਬੋਰਟ ਕਰ ਦਿੰਦੀਆਂ ਹਨ (#7849) — ਇਹ ਇੱਕ **ਮੈਮੋਰੀ-ਬਜਟ** ਚੇਤਾਵਨੀ ਹੈ, 2 ਦੀ ਸਖ਼ਤ ਉਤਪਾਦ ਸੀਮਾ ਨਹੀਂ। ਇੱਕ ਸਿਹਤਮੰਦ ਪ੍ਰੋਸੈਸ (ਸ਼ੈਡ ਅਨੁਪਾਤ ਤੋਂ ਹੇਠਾਂ ਹੀਪ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ਰਾਹੀਂ ਹੋਰ ਸਮਕਾਲੀ ਲੰਬੀਆਂ `/v1/responses` ਬੇਨਤੀਆਂ ਨੂੰ ਦਾਖਲ ਕਰ ਸਕਦਾ ਹੈ। ਦਰਜਨਾਂ ਲੰਬੇ SSE ਕਲਾਇੰਟ (40–50) ਹੀਪ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ਦਾ ਮਾਮਲਾ ਹੈ, “ਵੱਧ ਤੋਂ ਵੱਧ 2” ਦਾ ਨਹੀਂ। “ਹੋਸਟ ਦੀ ਵਰਤੋਂ ਕਰਨ” ਲਈ ਇਸਨੂੰ ਅੰਨ੍ਹੇਵਾਹ ਵਧਾਉਣਾ #7849 ਨੂੰ ਮੁੜ ਲਿਆਉਂਦਾ ਹੈ। **N ਸੁਤੰਤਰ `DATA_DIR`s** (#11024) ਨਾਲ **ਹੀਪਾਂ** ਨੂੰ ਗੁਣਾ ਕਰੋ; ਇੱਕ SQLite ਫ਼ਾਈਲ ਉੱਤੇ ਕਦੇ ਵੀ `replicas>1` ਨਾ ਰੱਖੋ। | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ਆਟੋਮੈਟਿਕ ਤੌਰ 'ਤੇ ਨਿਰਧਾਰਤ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ਫੈਨਆਉਟ:** ਆਟੋਮੈਟਿਕ ਤੌਰ 'ਤੇ ਨਿਰਧਾਰਤ ਇੰਜੈਸਟ ਬਾਈਟ ਬਜਟ ਲਈ ਓਵਰਰਾਈਡ (ਵਧੇਰੇ ਸਖ਼ਤ V8/cgroup ਮੈਮੋਰੀ ਸੀਮਾ ਦਾ 25%, ਜਿਸਨੂੰ 8x ਅਸਥਾਈ ਐਂਪਲੀਫਿਕੇਸ਼ਨ ਨਾਲ ਵੰਡਿਆ ਜਾਂਦਾ ਹੈ)। ਨਿਰਧਾਰਤ ਅਤੇ ਸਪਸ਼ਟ ਮੁੱਲ 8 MiB–2 GiB ਦੀ ਸੀਮਾ ਵਿੱਚ ਕਲੈਂਪ ਹੁੰਦੇ ਹਨ। ਪ੍ਰਭਾਵੀ ਬਜਟ ਤੋਂ ਵੱਡੀ ਬਾਡੀ ਤੁਰੰਤ `413 body_exceeds_budget` ਨਾਲ ਅਸਫਲ ਹੁੰਦੀ ਹੈ; ਵੱਖ-ਵੱਖ ਤੌਰ 'ਤੇ ਸੰਭਾਲੀਆਂ ਜਾ ਸਕਣ ਵਾਲੀਆਂ ਬਾਡੀਆਂ ਵਿਚਕਾਰ ਟਕਰਾਅ ਲਈ ਮੁੜ ਕੋਸ਼ਿਸ਼ਯੋਗ `503` ਮਿਲਦਾ ਹੈ। 40–50 ਸਮਕਾਲੀ ਲੰਬੇ SSE ਕਲਾਇੰਟ ਇਸ ਬਜਟ + ਹੀਪ ਦਾ ਮਾਮਲਾ ਹੈ, ਸਖ਼ਤ “ਵੱਧ ਤੋਂ ਵੱਧ 2” ਦਾ ਨਹੀਂ। ਟਿਊਨ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ `/api/monitoring/health` ਉੱਤੇ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ਪੜ੍ਹੋ। | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | ਬਾਈਟ ਅਤੇ ਸਟਰੱਕਚਰ ਹੈਵੀਵੇਟ ਦਾਖਲੇ ਲਈ ਹੀਪ-ਦਬਾਅ ਸ਼ੈਡ ਅਨੁਪਾਤ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ਤੋਂ ਵੱਧ ਦੀ ਇੱਕ ਸਮਕਾਲੀ ਹੈਵੀਵੇਟ ਬੇਨਤੀ ਨੂੰ ਮੁੜ ਕੋਸ਼ਿਸ਼ਯੋਗ `503` ਨਾਲ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਸ਼ੈਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਜਦੋਂ ਹੀਪ ਵੀ ਇਸ ਅਨੁਪਾਤ 'ਤੇ ਜਾਂ ਇਸ ਤੋਂ ਉੱਪਰ ਹੋਵੇ; ਸਿਹਤਮੰਦ ਹੀਪ ਉੱਤੇ ਇਸਨੂੰ ਇਸ ਦੀ ਬਜਾਏ ਸਿਹਤਮੰਦ-ਹੈੱਡਰੂਮ ਰਾਹੀਂ ਦਾਖਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ਡਿਫਾਲਟ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **ਦੋਵਾਂ** ਸਟਰੱਕਚਰ ਅਤੇ ਬਾਈਟ (`admitChatRequest`, ਜਿਸ ਵਿੱਚ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ਬਾਡੀਆਂ ਸ਼ਾਮਲ ਹਨ) ਉੱਤੇ ਸਿਹਤਮੰਦ-ਹੀਪ ਫਾਸਟ ਪਾਥ (#10437) ਲਈ ਸੀਮਿਤ ਵਾਧੂ ਸਮਰੱਥਾ। ਇਸ ਸੀਮਾ ਤੋਂ ਬਿਨਾਂ, ਹਰ ਵਿਅਸਤ-ਪਰ-ਸਿਹਤਮੰਦ-ਹੀਪ ਬੇਨਤੀ ਬਿਨਾਂ ਕਿਸੇ ਉੱਪਰੀ ਹੱਦ ਦੇ ਦਾਖਲਾ ਨਿਯੰਤਰਣ ਨੂੰ ਬਾਈਪਾਸ ਕਰਦੀ ਸੀ। ਸਿਹਤਮੰਦ-ਹੀਪ ਪਾਥ ਰਾਹੀਂ ਇੰਨੇ ਸਮਕਾਲੀ ਲੀਜ਼ ਸਰਗਰਮ ਹੋਣ ਤੋਂ ਬਾਅਦ, ਅਗਲੀਆਂ ਵਿਅਸਤ ਬੇਨਤੀਆਂ ਉਸੇ ਸੀਮਿਤ-ਉਡੀਕ/ਸ਼ੈਡ ਪਾਥ ਵਿੱਚ ਚਲੀਆਂ ਜਾਂਦੀਆਂ ਹਨ ਜੋ ਅਸਲ ਹੀਪ ਦਬਾਅ ਹੇਠ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ। `0` ਬਾਈਪਾਸ ਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਅਸਮਰੱਥ ਕਰਦਾ ਹੈ। | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ਸੁਨੇਹਿਆਂ ਦੀ ਉਹ ਗਿਣਤੀ ਜੋ ਚੈਟ ਬੇਨਤੀ ਨੂੰ ਹੈਵੀਵੇਟ ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਕਰਦੀ ਹੈ, ਭਾਵੇਂ ਇਸਦੀ ਬਾਡੀ ਬਾਈਟ ਥ੍ਰੈਸ਼ਹੋਲਡ ਤੋਂ ਹੇਠਾਂ ਹੋਵੇ। | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ਟੂਲਾਂ ਦੀ ਉਹ ਗਿਣਤੀ ਜੋ ਚੈਟ ਬੇਨਤੀ ਨੂੰ ਹੈਵੀਵੇਟ ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਕਰਦੀ ਹੈ, ਭਾਵੇਂ ਇਸਦੀ ਬਾਡੀ ਬਾਈਟ ਥ੍ਰੈਸ਼ਹੋਲਡ ਤੋਂ ਹੇਠਾਂ ਹੋਵੇ। | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ਰੂੜ੍ਹੀਵਾਦੀ ਸਟ੍ਰਿੰਗ-ਆਕਾਰ ਟੋਕਨ ਅਨੁਮਾਨ ਜੋ ਬੇਨਤੀ ਨੂੰ ਹੈਵੀਵੇਟ ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਕਰਦਾ ਹੈ; ਇਹ ਦਾਖਲਾ-ਲਾਗਤ ਪ੍ਰਾਕਸੀ ਹੈ, ਪ੍ਰਦਾਤਾ ਦੀ ਬਿਲਿੰਗ ਟੋਕਨਾਈਜ਼ੇਸ਼ਨ ਨਹੀਂ। | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ਅਸਮਰੱਥ) | `src/shared/middleware/chatBodyAdmission.ts` | ਵਿਕਲਪਿਕ ਆਪਟ-ਇਨ ਚੈਟ ਇਤਿਹਾਸ ਸੀਮਾ। ਡਿਫਾਲਟ ਰੂਪ ਵਿੱਚ ਅਸਮਰੱਥ: ਸੁਨੇਹਿਆਂ ਦੀ ਗਿਣਤੀ ਡਿਪਲੌਇਮੈਂਟ ਨੀਤੀ ਹੈ, ਬੇਨਤੀ ਦੀ ਸਰਬਵਿਆਪੀ ਵਿਸ਼ੇਸ਼ਤਾ ਨਹੀਂ, ਅਤੇ ਇੱਥੇ ਸੀਮਾ ਲਗਾਉਣ ਨਾਲ ਗੱਲਬਾਤਾਂ ਨੂੰ ਇੱਕ ਅੰਤਿਮ `413` ਨਾਲ ਕੰਪ੍ਰੈਸ਼ਨ ਪਾਈਪਲਾਈਨ ਦੁਆਰਾ ਸੇਵਾਯੋਗ ਬਣਾਏ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਹੀ ਰੱਦ ਕਰ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ। ਹੀਪ ਵਾਧੇ ਨੂੰ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ਅਤੇ ਹੀਪ-ਦਬਾਅ ਸ਼ੈਡ ਦੁਆਰਾ ਸੀਮਿਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਮੈਮੋਰੀ-ਸੀਮਿਤ ਡਿਪਲੌਇਮੈਂਟਾਂ ਉੱਤੇ ਇੱਕ ਧਨਾਤਮਕ ਮੁੱਲ ਸੈੱਟ ਕਰੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਸਖ਼ਤ ਉੱਪਰੀ ਹੱਦ ਦੀ ਲੋੜ ਹੈ; ਫਿਰ ਵਾਧੂ ਬੇਨਤੀਆਂ ਨੂੰ ਸੰਰਚਿਤ ਕੰਪੈਕਟ-ਲੋੜੀਂਦਾ `413` ਮਿਲਦਾ ਹੈ। | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ਪੂਰੀ ਤਰ੍ਹਾਂ ਮੈਮੋਰੀ ਵਿੱਚ ਬਫ਼ਰ ਕੀਤੇ ਗਏ ਗੈਰ-ਸਟ੍ਰੀਮਿੰਗ ਅੱਪਸਟ੍ਰੀਮ ਜਵਾਬ ਲਈ ਸਖ਼ਤ ਸੀਮਾ। ਇਸ ਤੋਂ ਅੱਗੇ ਅੱਪਸਟ੍ਰੀਮ ਰੀਡਰ ਰੱਦ ਕਰ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਹੀਪ ਖ਼ਤਮ ਹੋਣ ਤੱਕ ਬੇਹੱਦ ਸਟ੍ਰਿੰਗ ਵਧਾਉਣ ਦੀ ਬਜਾਏ ਬੇਨਤੀ ਤੁਰੰਤ ਅਸਫਲ ਹੋ ਜਾਂਦੀ ਹੈ। | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ਅੱਪਸਟ੍ਰੀਮ ਜਵਾਬ ਹੈਡਰਾਂ ਤੋਂ ਫਾਰਵਰਡ ਕੀਤੇ ਜਾਣ ਵਾਲੇ ਵੱਧ ਤੋਂ ਵੱਧ ਵਾਇਰ ਬਾਈਟ। ਬਜਟ ਤੋਂ ਵੱਧ ਹੋਣ 'ਤੇ, ਆਮ ਰਿਵਰਸ-ਪ੍ਰੌਕਸੀ ਹੈਡਰ ਸੀਮਾਵਾਂ ਅੰਦਰ ਰਹਿਣ ਲਈ ਘੱਟ-ਤਰਜੀਹ ਵਾਲੇ ਹੈਡਰ (ਜਿਵੇਂ ਕਸਟਮ `x-codex-*`, `x-oai-request-id`) ਹਟਾ ਦਿੱਤੇ ਜਾਂਦੇ ਹਨ। ਵਧੇਰੇ ਅੱਪਸਟ੍ਰੀਮ ਮੈਟਾਡੇਟਾ ਫਾਰਵਰਡ ਕਰਨ ਲਈ ਇਸਨੂੰ ਵੱਧ ਸੈੱਟ ਕਰੋ, ਪਰ ਇਸ ਨਾਲ ਜਵਾਬ ਹੈਡਰ ਦਾ ਆਕਾਰ ਵੱਡਾ ਹੋਵੇਗਾ। | -| `CORS_ORIGIN` | _(ਸੈੱਟ ਨਹੀਂ)_ | `src/server/cors/origins.ts` | ਪੁਰਾਤਨ ਸਿੰਗਲ-ਓਰਿਜਿਨ CORS ਮਨਜ਼ੂਰ-ਸੂਚੀ। ਨਵੀਆਂ ਡਿਪਲੌਇਮੈਂਟਾਂ ਲਈ `CORS_ALLOWED_ORIGINS` ਨੂੰ ਤਰਜੀਹ ਦਿਓ। CORS ਸਿਰਫ਼ ਕਰਾਸ-ਓਰਿਜਿਨ ਬ੍ਰਾਊਜ਼ਰ API ਕਲਾਇੰਟਾਂ ਲਈ ਹੈ; ਪ੍ਰਮਾਣਿਤ ਡੈਸ਼ਬੋਰਡ ਲਿਖਤਾਂ ਇਸਦੀ ਬਜਾਏ ਇੱਕੋ-ਓਰਿਜਿਨ ਬੇਨਤੀਆਂ ਦੇ ਨਾਲ ਸੈਸ਼ਨ-ਬੱਧ CSRF ਸੁਰੱਖਿਆ ਵਰਤਦੀਆਂ ਹਨ। | -| `CORS_ALLOWED_ORIGINS` | _(ਸੈੱਟ ਨਹੀਂ)_ | `src/server/cors/origins.ts` | ਕੌਮੇ ਨਾਲ ਵੱਖ ਕੀਤੀ CORS ਮਨਜ਼ੂਰ-ਸੂਚੀ। ਜਦੋਂ ਤੱਕ `CORS_ALLOW_ALL=true` ਨੂੰ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਸੰਰਚਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ, ਕੋਈ ਵਾਈਲਡਕਾਰਡ ਨਹੀਂ ਭੇਜਿਆ ਜਾਂਦਾ। | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ਕਿਸੇ ਵੀ ਬ੍ਰਾਊਜ਼ਰ `Origin` ਨੂੰ ਵਾਪਸ ਭੇਜਣ ਲਈ ਸਿਰਫ਼ ਵਿਕਾਸ ਵਾਸਤੇ ਬਚਾਅ-ਰਾਹ। ਸਾਂਝੀਆਂ ਜਾਂ ਪ੍ਰੋਡਕਸ਼ਨ ਡਿਪਲੌਇਮੈਂਟਾਂ 'ਤੇ ਇਸਨੂੰ ਸਮਰੱਥ ਨਾ ਕਰੋ। | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | ਪ੍ਰਾਈਵੇਟ/ਲੂਪਬੈਕ/ਲਿੰਕ-ਲੋਕਲ IP ਰੇਂਜਾਂ ਨੂੰ ਨਿਸ਼ਾਨਾ ਬਣਾਉਣ ਵਾਲੀਆਂ ਪ੍ਰੋਵਾਈਡਰ ਕਾਲਾਂ ਨੂੰ ਬਲੌਕ ਕਰੋ। ਇਸਨੂੰ ਸਿਰਫ਼ ਅਲੱਗ-ਥਲੱਗ ਟੈਸਟ ਵਾਤਾਵਰਣਾਂ ਵਿੱਚ ਅਸਮਰੱਥ ਕਰੋ। | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | ਪ੍ਰਾਈਵੇਟ/ਸਥਾਨਕ ਨੈੱਟਵਰਕਾਂ (localhost, 192.168.x.x, 10.x.x.x, ਆਦਿ) ਵੱਲ ਇਸ਼ਾਰਾ ਕਰਨ ਵਾਲੇ ਪ੍ਰੋਵਾਈਡਰ URL ਦੀ ਆਗਿਆ ਦਿਓ। **ਸਵੈ-ਹੋਸਟ ਕੀਤੇ ਪ੍ਰੋਵਾਈਡਰਾਂ ਲਈ ਲਾਜ਼ਮੀ** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG)। ਜਦੋਂ `false` ਹੋਵੇ, ਡੈਸ਼ਬੋਰਡ ਸਥਾਨਕ URL ਦੀ ਪ੍ਰਮਾਣਿਕਤਾ ਜਾਂਚ ਨੂੰ ਰੱਦ ਕਰਦਾ ਹੈ। | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | ਸਥਾਨਕ/ਪ੍ਰਾਈਵੇਟ ਪਤਿਆਂ (127.0.0.1, localhost, LAN, ਪ੍ਰਾਈਵੇਟ ਰੇਂਜਾਂ) ਉੱਤੇ ਪ੍ਰੋਵਾਈਡਰ ਜੋੜਨ/ਪ੍ਰਮਾਣਿਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ — ਇਹ ਪ੍ਰੋਵਾਈਡਰ ਪ੍ਰਮਾਣਿਕਤਾ ਮਾਰਗ ਤੱਕ ਸੀਮਿਤ ਹੈ। **ਡਿਫੌਲਟ `true`** (ਸਥਾਨਕ-ਪਹਿਲਾਂ); ਸਿਰਫ਼ ਜਨਤਕ ਪਤਿਆਂ ਲਈ ਸਖ਼ਤ ਬਲੌਕਿੰਗ ਲਾਗੂ ਕਰਨ ਵਾਸਤੇ `false` ਸੈੱਟ ਕਰੋ। ਕਲਾਉਡ-ਮੈਟਾਡੇਟਾ ਐਂਡਪੁਆਇੰਟ (169.254.169.254, metadata.google.internal) ਹਰ ਹਾਲਤ ਵਿੱਚ ਬਲੌਕ ਰਹਿੰਦੇ ਹਨ। (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ਰੂਟਾਂ (ਟ੍ਰਾਂਸਕ੍ਰਿਪਸ਼ਨ, ਸਪੀਚ, ਅਨੁਵਾਦ) ਨੂੰ localhost ਤੋਂ ਬਾਹਰ ਹੋਸਟ ਕੀਤਾ OpenAI-ਅਨੁਕੂਲ ਪ੍ਰੋਵਾਈਡਰ ਨੋਡ ਵਰਤਣ ਦਿਓ। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ — ਆਡੀਓ ਨੂੰ ਰਿਮੋਟ ਹੋਸਟ ਵੱਲ ਰੂਟ ਕਰਨ ਨਾਲ ਈਗ੍ਰੈੱਸ ਪਛਾਣ ਬਦਲ ਜਾਂਦੀ ਹੈ ਅਤੇ ਇਹ ਆਪਰੇਟਰ ਦਾ ਸਪਸ਼ਟ ਫ਼ੈਸਲਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। ਲੂਪਬੈਕ/ਪ੍ਰਾਈਵੇਟ ਨੋਡ (localhost, 127.0.0.1, 172.16-31.x) ਹਮੇਸ਼ਾ ਮਨਜ਼ੂਰ ਹਨ ਅਤੇ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦੇ। (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (ਅਤੇ ਮੈਮਰੀ ਇੰਜਣ ਦੇ ਲੂਪਬੈਕ ਰੀਰੈਂਕ ਪੜਾਅ) ਨੂੰ localhost ਤੋਂ ਬਾਹਰ ਹੋਸਟ ਕੀਤਾ OpenAI-ਅਨੁਕੂਲ ਪ੍ਰੋਵਾਈਡਰ ਨੋਡ ਵਰਤਣ ਦਿਓ — TEI, Infinity, vLLM ਆਦਿ ਚਲਾ ਰਿਹਾ ਕੋਈ LAN ਬਾਕਸ ਜਾਂ Tailscale ਪੀਅਰ। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ — ਰਿਮੋਟ ਹੋਸਟ ਵੱਲ ਰੂਟ ਕਰਨ ਨਾਲ ਈਗ੍ਰੈੱਸ ਪਛਾਣ ਬਦਲ ਜਾਂਦੀ ਹੈ ਅਤੇ ਇਹ ਆਪਰੇਟਰ ਦਾ ਸਪਸ਼ਟ ਫ਼ੈਸਲਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। ਲੂਪਬੈਕ ਨੋਡ (localhost, 127.0.0.1, 172.16-31.x) ਹਮੇਸ਼ਾ ਮਨਜ਼ੂਰ ਹਨ ਅਤੇ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦੇ। ਰਿਮੋਟ ਨੋਡਾਂ ਨੂੰ ਪ੍ਰੋਵਾਈਡਰ ਦੀ ਆਊਟਬਾਊਂਡ URL ਨੀਤੀ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ਵੀ ਪਾਸ ਕਰਨੀ ਲਾਜ਼ਮੀ ਹੈ; ਕਲਾਉਡ-ਮੈਟਾਡੇਟਾ ਹੋਸਟਾਂ ਵੱਲ ਕਦੇ ਰੂਟ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ। | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | ਜਦੋਂ OIDC ਸਮਰੱਥ ਹੋਵੇ, ਪਾਸਵਰਡ ਲੌਗਇਨ ਨੂੰ ਅਸਮਰੱਥ ਕਰੋ ਤਾਂ ਜੋ ਵਰਤੋਂਕਾਰ ਸਿਰਫ਼ OIDC ਸਿੰਗਲ ਸਾਈਨ-ਆਨ ਰਾਹੀਂ ਪ੍ਰਮਾਣਿਤ ਹੋ ਸਕਣ। ਸਧਾਰਨ ਉਪਨਾਮ `OIDC_DISABLE_PASSWORD_LOGIN` ਵੀ ਸਵੀਕਾਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ; ਉਸੇ ਕੁੰਜੀ ਵਾਲੇ ਡੈਸ਼ਬੋਰਡ ਫੀਚਰ ਫਲੈਗ ਨੂੰ ਪਹਿਲ ਮਿਲਦੀ ਹੈ। (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ਦਾ ਸਧਾਰਨ ਉਪਨਾਮ (#10889)। | +| ਵੇਰੀਏਬਲ | ਡਿਫੌਲਟ | ਸਰੋਤ ਫ਼ਾਈਲ | ਵੇਰਵਾ | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ਮਸ਼ੀਨ ਫਿੰਗਰਪ੍ਰਿੰਟਿੰਗ ਲਈ ਹਾਰਡਵੇਅਰ ਪਛਾਣਕਰਤਾਵਾਂ ਨਾਲ ਜੋੜਿਆ ਜਾਣ ਵਾਲਾ ਸਾਲਟ। ਅਲੱਗਾਵ ਲਈ ਹਰ ਡਿਪਲੌਇਮੈਂਟ ਮੁਤਾਬਕ ਇਸਨੂੰ ਬਦਲੋ। | +| `OMNIROUTE_CLI_SALT` | _(ਅਣਸੈੱਟ = ਹਰ ਇੰਸਟਾਲ ਲਈ ਬੇਤਰਤੀਬ ਸਾਲਟ, ਜੋ `/cli-token-salt.json` 'ਤੇ ਸਥਾਈ ਰੂਪ ਵਿੱਚ ਸੰਭਾਲਿਆ ਜਾਂਦਾ ਹੈ)_ | `src/lib/machineToken.ts` | ਸਥਾਨਕ CLI ਪ੍ਰਮਾਣੀਕਰਨ ਟੋਕਨ ਬਣਾਉਣ ਲਈ HMAC ਸਾਲਟ। ਇਹ ਮੁੱਲ ਸੈੱਟ ਕਰਨ ਨਾਲ ਮਸ਼ੀਨ ਉੱਤੇ ਸਾਰੇ CLI ਟੋਕਨ ਰੋਟੇਟ ਹੋ ਜਾਂਦੇ ਹਨ ਅਤੇ ਇਸਨੂੰ ਹਮੇਸ਼ਾ ਸਥਾਈ ਤੌਰ 'ਤੇ ਸੰਭਾਲੇ ਸਾਲਟ ਉੱਤੇ ਤਰਜੀਹ ਮਿਲਦੀ ਹੈ। `docs/security/CLI_TOKEN.md` ਵੇਖੋ। | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ਸੈਸ਼ਨ ਕੁਕੀਜ਼ ਉੱਤੇ `Secure` ਫਲੈਗ ਸੈੱਟ ਕਰਦਾ ਹੈ। HTTPS ਦੇ ਪਿੱਛੇ ਚਲਾਉਂਦੇ ਸਮੇਂ ਇਹ **ਲਾਜ਼ਮੀ ਤੌਰ 'ਤੇ `true` ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ**। | +| `REQUIRE_API_KEY` | `false` | API ਮਿਡਲਵੇਅਰ | ਜਦੋਂ `true` ਹੋਵੇ, ਤਾਂ ਸਾਰੀਆਂ `/v1/*` ਪ੍ਰੌਕਸੀ ਬੇਨਤੀਆਂ ਵਿੱਚ ਇੱਕ ਵੈਧ API ਕੁੰਜੀ ਸ਼ਾਮਲ ਹੋਣੀ ਲਾਜ਼ਮੀ ਹੈ। **ਇਹ ਫਲੈਗ `GET /v1/models` ਨੂੰ ਨਿਯੰਤਰਿਤ ਨਹੀਂ ਕਰਦਾ**, ਜੋ ਇਸਦੀ ਬਜਾਏ ਡੈਸ਼ਬੋਰਡ ਲੌਗਇਨ ਸਥਿਤੀ (`requireAuthForModels`) ਦੀ ਪਾਲਣਾ ਕਰਦਾ ਹੈ — ਇਸ ਲਈ `/v1/models` ਤੋਂ ਮਿਲਣ ਵਾਲੇ `401` ਦਾ ਇਹ ਅਰਥ ਨਹੀਂ ਕਿ ਇਨਫਰੈਂਸ ਸੁਰੱਖਿਅਤ ਹੈ। `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) ਵੇਖੋ। | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | ਡੈਸ਼ਬੋਰਡ UI ਵਿੱਚ API ਕੁੰਜੀ ਦੇ ਪੂਰੇ ਮੁੱਲ ਦਿਖਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਡੈਸ਼ਬੋਰਡ ਫੀਚਰ ਫਲੈਗਾਂ ਤੋਂ ਸੰਰਚਨਾਯੋਗ; ਸਾਂਝੀਆਂ ਇੰਸਟੈਂਸਾਂ ਉੱਤੇ ਸੁਰੱਖਿਆ ਜੋਖਮ। | +| `NO_LOG_API_KEY_IDS` | _(ਖਾਲੀ)_ | `src/lib/compliance/index.ts` | ਕਾਮਿਆਂ ਨਾਲ ਵੱਖ ਕੀਤੀਆਂ API ਕੁੰਜੀ ID, ਜੋ ਬੇਨਤੀ ਲੌਗਿੰਗ ਨੂੰ ਬਾਈਪਾਸ ਕਰਦੀਆਂ ਹਨ (GDPR ਅਨੁਕੂਲਤਾ)। | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ਅਣਸੈੱਟ = ਅਸੀਮਤ)_ | `src/shared/utils/apiKeyPolicy.ts` | ਉਹਨਾਂ API ਕੁੰਜੀਆਂ ਲਈ ਲਾਗੂ ਹੋਣ ਵਾਲਾ ਫਾਲਬੈਕ ਪ੍ਰਤੀ-ਦਿਨ ਬੇਨਤੀ ਬਜਟ, ਜਿਨ੍ਹਾਂ ਦਾ `rate_limits` ਕਾਲਮ null ਹੈ। ਅਣਸੈੱਟ ਜਾਂ ਖਾਲੀ: ਕੋਈ ਅਪਰਤੱਖ ਸੀਮਾ ਨਹੀਂ (#2289, #11017)। `0` ਵੀ ਇਸੇ ਦੇ ਬਰਾਬਰ ਹੈ (ਅਸੀਮਤ)। ਧਨਾਤਮਕ ਪੂਰਨ ਅੰਕ N, N/ਦਿਨ, 5N/ਹਫ਼ਤਾ, 20N/ਮਹੀਨਾ ਸਮਰੱਥ ਕਰਦਾ ਹੈ। ਗਲਤ-ਰੂਪ ਵਾਲੇ ਗੈਰ-ਖਾਲੀ ਮੁੱਲ ਪੁਰਾਣੀਆਂ 1000/ਦਿਨ, 5000/ਹਫ਼ਤਾ, 20000/ਮਹੀਨਾ ਵਿੰਡੋਜ਼ ਉੱਤੇ ਵਾਪਸ ਚਲੇ ਜਾਂਦੇ ਹਨ। | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ਬੇਨਤੀ ਬਾਡੀ ਦਾ ਵੱਧ ਤੋਂ ਵੱਧ ਮਨਜ਼ੂਰਸ਼ੁਦਾ ਆਕਾਰ। ਇਸ ਸੀਮਾ ਤੋਂ ਵੱਧ ਪੇਲੋਡਾਂ ਨੂੰ ਰੱਦ ਕਰਦਾ ਹੈ। | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ਇਸ ਥ੍ਰੈਸ਼ਹੋਲਡ ਦੇ ਬਰਾਬਰ ਜਾਂ ਇਸ ਤੋਂ ਵੱਧ ਆਕਾਰ ਵਾਲੀਆਂ ਅਸਲ ਬੇਨਤੀ ਬਾਡੀਆਂ JSON ਪਾਰਸਿੰਗ ਤੋਂ ਪਹਿਲਾਂ ਐਟਾਮਿਕ ਪ੍ਰਕਿਰਿਆ-ਸਥਾਨਕ ਹੈਵੀਵੇਟ ਦਾਖਲਾ ਲੀਜ਼ ਲੈਂਦੀਆਂ ਹਨ (BYTE ਪਾਥ, ਜਿਸ ਵਿੱਚ `POST /v1/responses` ਵੀ ਸ਼ਾਮਲ ਹੈ)। ਉਹੀ [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ਸਿਹਤਮੰਦ-ਹੈੱਡਰੂਮ ਬਚਾਅ, ਜੋ ਸੰਰਚਨਾ-ਭਾਰੀ ਬੇਨਤੀਆਂ ਲਈ ਹੈ; ਫਿਰ ਵੀ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ਦੁਆਰਾ ਸੀਮਿਤ ਹੈ, ਤਾਂ ਜੋ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ਮੁੜ ਨਾ ਆਵੇ। | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ਸੀਮਿਤ ਇੰਜੈਸ਼ਨ ਦੌਰਾਨ ਪੜ੍ਹੇ ਗਏ ਬਾਈਟਾਂ ਦੇ ਆਧਾਰ 'ਤੇ ਲਾਗੂ ਕੀਤੀ ਜਾਣ ਵਾਲੀ ਚੈਟ-ਰੂਟ ਦੀ ਸਖ਼ਤ ਸੀਮਾ, ਜਿਸ ਵਿੱਚ ਗੁੰਮ, ਅਵੈਧ ਜਾਂ ਗੁਮਰਾਹਕੁੰਨ `Content-Length` ਵਾਲੀਆਂ ਬੇਨਤੀਆਂ ਵੀ ਸ਼ਾਮਲ ਹਨ; ਸੀਮਾ ਤੋਂ ਵੱਧ ਹੋਣ 'ਤੇ `413` ਮਿਲਦਾ ਹੈ। | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ਅਣਸੈੱਟ — ਬੇਨਤੀ-ਗਿਣਤੀ ਦੀ ਕੋਈ ਸੀਮਾ ਨਹੀਂ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ਫੈਨਆਉਟ:** ਇਹ ਪੁਰਾਣੀ ਬੇਨਤੀ-ਗਿਣਤੀ ਸੀਮਾ ਹੁਣ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਲਾਗੂ ਹੁੰਦੀ ਹੈ ਜਦੋਂ ਇਸਨੂੰ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ ਹੋਵੇ। ਇਸਨੂੰ ਅਣਸੈੱਟ ਛੱਡਣ 'ਤੇ (ਡਿਫਾਲਟ), ਹੈਵੀਵੇਟ ਚੈਟ ਦਾਖਲਾ ਇਸ ਦੀ ਬਜਾਏ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ਦੁਆਰਾ ਨਿਯੰਤਰਿਤ ਹੁੰਦਾ ਹੈ — **ਇੱਕ ਪ੍ਰੋਸੈਸ** (ਇੱਕ V8 ਹੀਪ) ਵਿੱਚ ਪ੍ਰੋਸੈਸ ਦੀ ਅਸਲ ਮੈਮੋਰੀ ਸੀਮਾ ਤੋਂ ਆਟੋਮੈਟਿਕ ਤੌਰ 'ਤੇ ਨਿਰਧਾਰਤ ਬਾਈਟ ਬਜਟ। ਇੱਕੋ ਸਮੇਂ ਚੱਲ ਰਹੀਆਂ ਲਗਭਗ 750k-ਟੋਕਨ ਵਾਲੀਆਂ ਦੋ `/v1/responses` ਬੇਨਤੀਆਂ ਲਗਭਗ 12 GiB ਹੀਪਾਂ ਨੂੰ ਅਬੋਰਟ ਕਰ ਦਿੰਦੀਆਂ ਹਨ (#7849) — ਇਹ ਇੱਕ **ਮੈਮੋਰੀ-ਬਜਟ** ਚੇਤਾਵਨੀ ਹੈ, 2 ਦੀ ਸਖ਼ਤ ਉਤਪਾਦ ਸੀਮਾ ਨਹੀਂ। ਇੱਕ ਸਿਹਤਮੰਦ ਪ੍ਰੋਸੈਸ (ਸ਼ੈਡ ਅਨੁਪਾਤ ਤੋਂ ਹੇਠਾਂ ਹੀਪ) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ਰਾਹੀਂ ਹੋਰ ਸਮਕਾਲੀ ਲੰਬੀਆਂ `/v1/responses` ਬੇਨਤੀਆਂ ਨੂੰ ਦਾਖਲ ਕਰ ਸਕਦਾ ਹੈ। ਦਰਜਨਾਂ ਲੰਬੇ SSE ਕਲਾਇੰਟ (40–50) ਹੀਪ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ਦਾ ਮਾਮਲਾ ਹੈ, “ਵੱਧ ਤੋਂ ਵੱਧ 2” ਦਾ ਨਹੀਂ। “ਹੋਸਟ ਦੀ ਵਰਤੋਂ ਕਰਨ” ਲਈ ਇਸਨੂੰ ਅੰਨ੍ਹੇਵਾਹ ਵਧਾਉਣਾ #7849 ਨੂੰ ਮੁੜ ਲਿਆਉਂਦਾ ਹੈ। **N ਸੁਤੰਤਰ `DATA_DIR`s** (#11024) ਨਾਲ **ਹੀਪਾਂ** ਨੂੰ ਗੁਣਾ ਕਰੋ; ਇੱਕ SQLite ਫ਼ਾਈਲ ਉੱਤੇ ਕਦੇ ਵੀ `replicas>1` ਨਾ ਰੱਖੋ। | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ਆਟੋਮੈਟਿਕ ਤੌਰ 'ਤੇ ਨਿਰਧਾਰਤ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ਫੈਨਆਉਟ:** ਆਟੋਮੈਟਿਕ ਤੌਰ 'ਤੇ ਨਿਰਧਾਰਤ ਇੰਜੈਸਟ ਬਾਈਟ ਬਜਟ ਲਈ ਓਵਰਰਾਈਡ (ਵਧੇਰੇ ਸਖ਼ਤ V8/cgroup ਮੈਮੋਰੀ ਸੀਮਾ ਦਾ 25%, ਜਿਸਨੂੰ 8x ਅਸਥਾਈ ਐਂਪਲੀਫਿਕੇਸ਼ਨ ਨਾਲ ਵੰਡਿਆ ਜਾਂਦਾ ਹੈ)। ਨਿਰਧਾਰਤ ਅਤੇ ਸਪਸ਼ਟ ਮੁੱਲ 8 MiB–2 GiB ਦੀ ਸੀਮਾ ਵਿੱਚ ਕਲੈਂਪ ਹੁੰਦੇ ਹਨ। ਪ੍ਰਭਾਵੀ ਬਜਟ ਤੋਂ ਵੱਡੀ ਬਾਡੀ ਤੁਰੰਤ `413 body_exceeds_budget` ਨਾਲ ਅਸਫਲ ਹੁੰਦੀ ਹੈ; ਵੱਖ-ਵੱਖ ਤੌਰ 'ਤੇ ਸੰਭਾਲੀਆਂ ਜਾ ਸਕਣ ਵਾਲੀਆਂ ਬਾਡੀਆਂ ਵਿਚਕਾਰ ਟਕਰਾਅ ਲਈ ਮੁੜ ਕੋਸ਼ਿਸ਼ਯੋਗ `503` ਮਿਲਦਾ ਹੈ। 40–50 ਸਮਕਾਲੀ ਲੰਬੇ SSE ਕਲਾਇੰਟ ਇਸ ਬਜਟ + ਹੀਪ ਦਾ ਮਾਮਲਾ ਹੈ, ਸਖ਼ਤ “ਵੱਧ ਤੋਂ ਵੱਧ 2” ਦਾ ਨਹੀਂ। ਟਿਊਨ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ `/api/monitoring/health` ਉੱਤੇ `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ਪੜ੍ਹੋ। | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | ਬਾਈਟ ਅਤੇ ਸਟਰੱਕਚਰ ਹੈਵੀਵੇਟ ਦਾਖਲੇ ਲਈ ਹੀਪ-ਦਬਾਅ ਸ਼ੈਡ ਅਨੁਪਾਤ (`heapUsed / heap_size_limit`) (#10183, #10268, #10437)। `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ਤੋਂ ਵੱਧ ਦੀ ਇੱਕ ਸਮਕਾਲੀ ਹੈਵੀਵੇਟ ਬੇਨਤੀ ਨੂੰ ਮੁੜ ਕੋਸ਼ਿਸ਼ਯੋਗ `503` ਨਾਲ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਸ਼ੈਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਜਦੋਂ ਹੀਪ ਵੀ ਇਸ ਅਨੁਪਾਤ 'ਤੇ ਜਾਂ ਇਸ ਤੋਂ ਉੱਪਰ ਹੋਵੇ; ਸਿਹਤਮੰਦ ਹੀਪ ਉੱਤੇ ਇਸਨੂੰ ਇਸ ਦੀ ਬਜਾਏ ਸਿਹਤਮੰਦ-ਹੈੱਡਰੂਮ ਰਾਹੀਂ ਦਾਖਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ਡਿਫਾਲਟ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **ਦੋਵਾਂ** ਸਟਰੱਕਚਰ ਅਤੇ ਬਾਈਟ (`admitChatRequest`, ਜਿਸ ਵਿੱਚ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ਬਾਡੀਆਂ ਸ਼ਾਮਲ ਹਨ) ਉੱਤੇ ਸਿਹਤਮੰਦ-ਹੀਪ ਫਾਸਟ ਪਾਥ (#10437) ਲਈ ਸੀਮਿਤ ਵਾਧੂ ਸਮਰੱਥਾ। ਇਸ ਸੀਮਾ ਤੋਂ ਬਿਨਾਂ, ਹਰ ਵਿਅਸਤ-ਪਰ-ਸਿਹਤਮੰਦ-ਹੀਪ ਬੇਨਤੀ ਬਿਨਾਂ ਕਿਸੇ ਉੱਪਰੀ ਹੱਦ ਦੇ ਦਾਖਲਾ ਨਿਯੰਤਰਣ ਨੂੰ ਬਾਈਪਾਸ ਕਰਦੀ ਸੀ। ਸਿਹਤਮੰਦ-ਹੀਪ ਪਾਥ ਰਾਹੀਂ ਇੰਨੇ ਸਮਕਾਲੀ ਲੀਜ਼ ਸਰਗਰਮ ਹੋਣ ਤੋਂ ਬਾਅਦ, ਅਗਲੀਆਂ ਵਿਅਸਤ ਬੇਨਤੀਆਂ ਉਸੇ ਸੀਮਿਤ-ਉਡੀਕ/ਸ਼ੈਡ ਪਾਥ ਵਿੱਚ ਚਲੀਆਂ ਜਾਂਦੀਆਂ ਹਨ ਜੋ ਅਸਲ ਹੀਪ ਦਬਾਅ ਹੇਠ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ। `0` ਬਾਈਪਾਸ ਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਅਸਮਰੱਥ ਕਰਦਾ ਹੈ। | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ਸੁਨੇਹਿਆਂ ਦੀ ਉਹ ਗਿਣਤੀ ਜੋ ਚੈਟ ਬੇਨਤੀ ਨੂੰ ਹੈਵੀਵੇਟ ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਕਰਦੀ ਹੈ, ਭਾਵੇਂ ਇਸਦੀ ਬਾਡੀ ਬਾਈਟ ਥ੍ਰੈਸ਼ਹੋਲਡ ਤੋਂ ਹੇਠਾਂ ਹੋਵੇ। | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ਟੂਲਾਂ ਦੀ ਉਹ ਗਿਣਤੀ ਜੋ ਚੈਟ ਬੇਨਤੀ ਨੂੰ ਹੈਵੀਵੇਟ ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਕਰਦੀ ਹੈ, ਭਾਵੇਂ ਇਸਦੀ ਬਾਡੀ ਬਾਈਟ ਥ੍ਰੈਸ਼ਹੋਲਡ ਤੋਂ ਹੇਠਾਂ ਹੋਵੇ। | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ਰੂੜ੍ਹੀਵਾਦੀ ਸਟ੍ਰਿੰਗ-ਆਕਾਰ ਟੋਕਨ ਅਨੁਮਾਨ ਜੋ ਬੇਨਤੀ ਨੂੰ ਹੈਵੀਵੇਟ ਵਜੋਂ ਵਰਗੀਕ੍ਰਿਤ ਕਰਦਾ ਹੈ; ਇਹ ਦਾਖਲਾ-ਲਾਗਤ ਪ੍ਰਾਕਸੀ ਹੈ, ਪ੍ਰਦਾਤਾ ਦੀ ਬਿਲਿੰਗ ਟੋਕਨਾਈਜ਼ੇਸ਼ਨ ਨਹੀਂ। | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ਅਸਮਰੱਥ) | `src/shared/middleware/chatBodyAdmission.ts` | ਵਿਕਲਪਿਕ ਆਪਟ-ਇਨ ਚੈਟ ਇਤਿਹਾਸ ਸੀਮਾ। ਡਿਫਾਲਟ ਰੂਪ ਵਿੱਚ ਅਸਮਰੱਥ: ਸੁਨੇਹਿਆਂ ਦੀ ਗਿਣਤੀ ਡਿਪਲੌਇਮੈਂਟ ਨੀਤੀ ਹੈ, ਬੇਨਤੀ ਦੀ ਸਰਬਵਿਆਪੀ ਵਿਸ਼ੇਸ਼ਤਾ ਨਹੀਂ, ਅਤੇ ਇੱਥੇ ਸੀਮਾ ਲਗਾਉਣ ਨਾਲ ਗੱਲਬਾਤਾਂ ਨੂੰ ਇੱਕ ਅੰਤਿਮ `413` ਨਾਲ ਕੰਪ੍ਰੈਸ਼ਨ ਪਾਈਪਲਾਈਨ ਦੁਆਰਾ ਸੇਵਾਯੋਗ ਬਣਾਏ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਹੀ ਰੱਦ ਕਰ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ। ਹੀਪ ਵਾਧੇ ਨੂੰ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ਅਤੇ ਹੀਪ-ਦਬਾਅ ਸ਼ੈਡ ਦੁਆਰਾ ਸੀਮਿਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਮੈਮੋਰੀ-ਸੀਮਿਤ ਡਿਪਲੌਇਮੈਂਟਾਂ ਉੱਤੇ ਇੱਕ ਧਨਾਤਮਕ ਮੁੱਲ ਸੈੱਟ ਕਰੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਸਖ਼ਤ ਉੱਪਰੀ ਹੱਦ ਦੀ ਲੋੜ ਹੈ; ਫਿਰ ਵਾਧੂ ਬੇਨਤੀਆਂ ਨੂੰ ਸੰਰਚਿਤ ਕੰਪੈਕਟ-ਲੋੜੀਂਦਾ `413` ਮਿਲਦਾ ਹੈ। | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ਪੂਰੀ ਤਰ੍ਹਾਂ ਮੈਮੋਰੀ ਵਿੱਚ ਬਫ਼ਰ ਕੀਤੇ ਗਏ ਗੈਰ-ਸਟ੍ਰੀਮਿੰਗ ਅੱਪਸਟ੍ਰੀਮ ਜਵਾਬ ਲਈ ਸਖ਼ਤ ਸੀਮਾ। ਇਸ ਤੋਂ ਅੱਗੇ ਅੱਪਸਟ੍ਰੀਮ ਰੀਡਰ ਰੱਦ ਕਰ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਹੀਪ ਖ਼ਤਮ ਹੋਣ ਤੱਕ ਬੇਹੱਦ ਸਟ੍ਰਿੰਗ ਵਧਾਉਣ ਦੀ ਬਜਾਏ ਬੇਨਤੀ ਤੁਰੰਤ ਅਸਫਲ ਹੋ ਜਾਂਦੀ ਹੈ। | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ਅੱਪਸਟ੍ਰੀਮ ਜਵਾਬ ਹੈਡਰਾਂ ਤੋਂ ਫਾਰਵਰਡ ਕੀਤੇ ਜਾਣ ਵਾਲੇ ਵੱਧ ਤੋਂ ਵੱਧ ਵਾਇਰ ਬਾਈਟ। ਬਜਟ ਤੋਂ ਵੱਧ ਹੋਣ 'ਤੇ, ਆਮ ਰਿਵਰਸ-ਪ੍ਰੌਕਸੀ ਹੈਡਰ ਸੀਮਾਵਾਂ ਅੰਦਰ ਰਹਿਣ ਲਈ ਘੱਟ-ਤਰਜੀਹ ਵਾਲੇ ਹੈਡਰ (ਜਿਵੇਂ ਕਸਟਮ `x-codex-*`, `x-oai-request-id`) ਹਟਾ ਦਿੱਤੇ ਜਾਂਦੇ ਹਨ। ਵਧੇਰੇ ਅੱਪਸਟ੍ਰੀਮ ਮੈਟਾਡੇਟਾ ਫਾਰਵਰਡ ਕਰਨ ਲਈ ਇਸਨੂੰ ਵੱਧ ਸੈੱਟ ਕਰੋ, ਪਰ ਇਸ ਨਾਲ ਜਵਾਬ ਹੈਡਰ ਦਾ ਆਕਾਰ ਵੱਡਾ ਹੋਵੇਗਾ। | +| `CORS_ORIGIN` | _(ਸੈੱਟ ਨਹੀਂ)_ | `src/server/cors/origins.ts` | ਪੁਰਾਤਨ ਸਿੰਗਲ-ਓਰਿਜਿਨ CORS ਮਨਜ਼ੂਰ-ਸੂਚੀ। ਨਵੀਆਂ ਡਿਪਲੌਇਮੈਂਟਾਂ ਲਈ `CORS_ALLOWED_ORIGINS` ਨੂੰ ਤਰਜੀਹ ਦਿਓ। CORS ਸਿਰਫ਼ ਕਰਾਸ-ਓਰਿਜਿਨ ਬ੍ਰਾਊਜ਼ਰ API ਕਲਾਇੰਟਾਂ ਲਈ ਹੈ; ਪ੍ਰਮਾਣਿਤ ਡੈਸ਼ਬੋਰਡ ਲਿਖਤਾਂ ਇਸਦੀ ਬਜਾਏ ਇੱਕੋ-ਓਰਿਜਿਨ ਬੇਨਤੀਆਂ ਦੇ ਨਾਲ ਸੈਸ਼ਨ-ਬੱਧ CSRF ਸੁਰੱਖਿਆ ਵਰਤਦੀਆਂ ਹਨ। | +| `CORS_ALLOWED_ORIGINS` | _(ਸੈੱਟ ਨਹੀਂ)_ | `src/server/cors/origins.ts` | ਕੌਮੇ ਨਾਲ ਵੱਖ ਕੀਤੀ CORS ਮਨਜ਼ੂਰ-ਸੂਚੀ। ਜਦੋਂ ਤੱਕ `CORS_ALLOW_ALL=true` ਨੂੰ ਸਪਸ਼ਟ ਤੌਰ 'ਤੇ ਸੰਰਚਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾਂਦਾ, ਕੋਈ ਵਾਈਲਡਕਾਰਡ ਨਹੀਂ ਭੇਜਿਆ ਜਾਂਦਾ। | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ਕਿਸੇ ਵੀ ਬ੍ਰਾਊਜ਼ਰ `Origin` ਨੂੰ ਵਾਪਸ ਭੇਜਣ ਲਈ ਸਿਰਫ਼ ਵਿਕਾਸ ਵਾਸਤੇ ਬਚਾਅ-ਰਾਹ। ਸਾਂਝੀਆਂ ਜਾਂ ਪ੍ਰੋਡਕਸ਼ਨ ਡਿਪਲੌਇਮੈਂਟਾਂ 'ਤੇ ਇਸਨੂੰ ਸਮਰੱਥ ਨਾ ਕਰੋ। | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ਪੁਰਾਣਾ ਉਪਨਾਮ। ਇਸਦਾ ਡੈਸ਼ਬੋਰਡ ਟੌਗਲ (ਇੱਕ DB ਓਵਰਰਾਈਡ) ਇਸ ਵਾਤਾਵਰਣ ਮੁੱਲ ਤੋਂ ਪਹਿਲਾਂ ਪੜ੍ਹਿਆ ਜਾਂਦਾ ਹੈ; ਦੋਵਾਂ ਵਿੱਚੋਂ ਕਿਸੇ ਵਿੱਚ ਵੀ `false`, `0`, `no` ਜਾਂ `off` ਹੋਣ 'ਤੇ — ਭਾਵੇਂ ਉਸ ਫਲੈਗ ਦਾ ਡੈਸ਼ਬੋਰਡ ਟੌਗਲ ਬੰਦ ਸੰਭਾਲਿਆ ਗਿਆ ਹੋਵੇ — ਹੋਸਟ ਜਾਂਚਾਂ ਨੂੰ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ਵਾਂਗ ਬੰਦ ਕਰ ਦਿੰਦਾ ਹੈ; ਬਾਕੀ ਮੁੱਲ ਗਾਰਡ ਨੂੰ ਹੇਠਾਂ ਦਿੱਤੇ ਦੋ ਫਲੈਗਾਂ ਦੇ ਹਵਾਲੇ ਛੱਡ ਦਿੰਦੇ ਹਨ। | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | ਗਾਰਡ ਮੋਡ ਦੀ ਪਾਲਣਾ ਕਰਨ ਵਾਲੇ ਪ੍ਰੋਵਾਈਡਰ ਮਾਰਗਾਂ 'ਤੇ — URL ਪ੍ਰਮਾਣਿਕਤਾ, ਮਾਡਲ ਖੋਜ, ਪ੍ਰੋਵਾਈਡਰ-ਨੋਡ ਬੇਸ URL (ਰਿਮੋਟ ਰੀਰੈਂਕ ਨੋਡਾਂ ਸਮੇਤ), ਪ੍ਰੌਕਸੀ-ਫਾਲਬੈਕ ਟੈਸਟ ਅਤੇ ਰਿਮੋਟ ਤਸਵੀਰ/ਮੀਡੀਆ ਫੈੱਚ ਵਰਗੇ ਹੋਰ ਪ੍ਰੋਵਾਈਡਰ-ਨਾਲ ਜੁੜੇ ਆਊਟਬਾਊਂਡ ਮਾਰਗਾਂ 'ਤੇ — ਆਊਟਬਾਊਂਡ URL ਗਾਰਡ ਦੀਆਂ ਹੋਸਟ ਜਾਂਚਾਂ ਨੂੰ, ਕਲਾਉਡ-ਮੈਟਾਡੇਟਾ ਬਲੌਕ ਸਮੇਤ (ਪੂਰਾ 169.254.0.0/16, metadata.google.internal), ਬੰਦ ਕਰ ਦਿੰਦਾ ਹੈ। ਇਹ ਪ੍ਰਾਈਵੇਟ webhook ਟਿਕਾਣਿਆਂ ਨੂੰ ਵੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ, ਸਿਵਾਏ ਕਲਾਉਡ-ਮੈਟਾਡੇਟਾ/ਲਿੰਕ-ਲੋਕਲ ਐਂਡਪੁਆਇੰਟਾਂ ਦੇ, ਜੋ ਇਹ ਫਲੈਗ ਚਾਲੂ ਹੋਣ 'ਤੇ ਵੀ ਬਲੌਕ ਰਹਿੰਦੇ ਹਨ। `BaseExecutor.execute()` ਰਾਹੀਂ ਭੇਜੀਆਂ ਗਈਆਂ ਚੈਟ ਬੇਨਤੀਆਂ ਦੀ ਕਲਾਉਡ-ਮੈਟਾਡੇਟਾ ਜਾਂਚ ਬਣੀ ਰਹਿੰਦੀ ਹੈ। ਸਥਾਨਕ ਪ੍ਰੋਵਾਈਡਰ ਡਿਫੌਲਟਾਂ ਨਾਲ ਕੰਮ ਕਰਦੇ ਹਨ: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ਡਿਫੌਲਟ `true`) ਪ੍ਰਮਾਣਿਕਤਾ, ਖੋਜ ਅਤੇ ਪ੍ਰੋਵਾਈਡਰ-ਨੋਡ ਮਾਰਗਾਂ 'ਤੇ ਸਥਾਨਕ ਅਤੇ LAN URL ਪਹਿਲਾਂ ਹੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ; ਪ੍ਰੌਕਸੀ-ਫਾਲਬੈਕ ਟੈਸਟ ਅਤੇ ਪ੍ਰਾਈਵੇਟ webhook ਟਿਕਾਣੇ ਸਿਰਫ਼ ਇਸੇ ਫਲੈਗ ਨੂੰ ਦੇਖਦੇ ਹਨ ਅਤੇ ਇਹ ਬੰਦ ਹੋਣ ਤੱਕ ਸਥਾਨਕ/LAN ਹੋਸਟਾਂ ਨੂੰ ਬਲੌਕ ਕਰਦੇ ਹਨ। `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` ਨੂੰ ਓਵਰਰਾਈਡ ਕਰਦਾ ਹੈ। ਡੈਸ਼ਬੋਰਡ ਟੌਗਲ ਤੋਂ ਸੰਭਾਲਿਆ ਗਿਆ ਮੁੱਲ ਇਸ ਵੇਰੀਏਬਲ ਤੋਂ ਪਹਿਲਾਂ ਆਉਂਦਾ ਹੈ। | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ਪ੍ਰੋਵਾਈਡਰ ਪ੍ਰਮਾਣਿਕਤਾ, ਮਾਡਲ ਖੋਜ, ਚੈਟ ਅਤੇ ਗਾਰਡ ਮੋਡ ਵੱਲੋਂ ਨਿਯੰਤਰਿਤ ਹੋਰ ਪ੍ਰੋਵਾਈਡਰ-ਨਾਲ ਜੁੜੇ ਆਊਟਬਾਊਂਡ ਮਾਰਗਾਂ ਲਈ ਸਥਾਨਕ/ਪ੍ਰਾਈਵੇਟ ਪਤਿਆਂ (127.0.0.1, localhost, LAN, ਪ੍ਰਾਈਵੇਟ ਰੇਂਜਾਂ) 'ਤੇ ਪ੍ਰੋਵਾਈਡਰ URL ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। **ਡਿਫੌਲਟ `true`** (ਸਥਾਨਕ-ਪਹਿਲਾਂ): ਤਾਂ ਗਾਰਡ ਕਲਾਉਡ-ਮੈਟਾਡੇਟਾ ਐਂਡਪੁਆਇੰਟਾਂ ਨੂੰ — ਪੂਰਾ 169.254.0.0/16 ਅਤੇ ਜਾਣੇ-ਪਛਾਣੇ ਮੈਟਾਡੇਟਾ ਹੋਸਟਨਾਮ (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) — ਬਲੌਕ ਕਰਦਾ ਹੈ। ਸਿਰਫ਼-ਜਨਤਕ ਮੋਡ ਲਈ `false` ਸੈੱਟ ਕਰੋ, ਜੋ ਪ੍ਰਾਈਵੇਟ ਅਤੇ ਲੂਪਬੈਕ ਹੋਸਟਾਂ ਨੂੰ ਵੀ ਬਲੌਕ ਕਰਦਾ ਹੈ। ਗਾਰਡ ਕਿਸੇ ਵੀ DNS ਲੁੱਕਅੱਪ ਤੋਂ ਪਹਿਲਾਂ ਹੋਸਟਨਾਮ ਜਾਂ IP ਲਿਟਰਲ ਨੂੰ ਲਿਖੇ ਹੀ ਰੂਪ ਵਿੱਚ ਜਾਂਚਦਾ ਹੈ; ਸਿਰਫ਼-ਜਨਤਕ ਮੋਡ ਵਿੱਚ safe-fetch ਰੈਪਰ ਹੋਰਾਂ ਤੋਂ ਹੋਸਟਨਾਮ ਨੂੰ ਰੈਜ਼ੌਲਵ ਕਰਦਾ ਹੈ ਅਤੇ ਕਿਸੇ ਵੀ ਪ੍ਰਾਈਵੇਟ ਜਵਾਬ ਨੂੰ ਠੁਕਰਾ ਦਿੰਦਾ ਹੈ। `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ਇਸਦੀ ਕਤਾਰ ਵਿੱਚ ਸੂਚੀਬੱਧ ਮਾਰਗਾਂ 'ਤੇ ਇਹ ਜਾਂਚਾਂ ਬੰਦ ਕਰ ਦਿੰਦਾ ਹੈ। LM Studio, Ollama, vLLM, Llamafile ਅਤੇ Triton ਵਰਗੇ ਬਿਲਟ-ਇਨ ਸਥਾਨਕ ਪ੍ਰੋਵਾਈਡਰ ਕੁੰਜੀ ਪ੍ਰਮਾਣਿਕਤਾ ਅਤੇ ਚੈਟ ਲਈ ਸਾਰੀਆਂ ਹੋਸਟ ਜਾਂਚਾਂ ਛੱਡ ਦਿੰਦੇ ਹਨ। ਡੈਸ਼ਬੋਰਡ ਟੌਗਲ ਤੋਂ ਸੰਭਾਲਿਆ ਗਿਆ ਮੁੱਲ ਇਸ ਵੇਰੀਏਬਲ ਤੋਂ ਪਹਿਲਾਂ ਆਉਂਦਾ ਹੈ। (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` ਰੂਟਾਂ (ਟ੍ਰਾਂਸਕ੍ਰਿਪਸ਼ਨ, ਸਪੀਚ, ਅਨੁਵਾਦ) ਨੂੰ localhost ਤੋਂ ਬਾਹਰ ਹੋਸਟ ਕੀਤਾ OpenAI-ਅਨੁਕੂਲ ਪ੍ਰੋਵਾਈਡਰ ਨੋਡ ਵਰਤਣ ਦਿਓ। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ — ਆਡੀਓ ਨੂੰ ਰਿਮੋਟ ਹੋਸਟ ਵੱਲ ਰੂਟ ਕਰਨ ਨਾਲ ਈਗ੍ਰੈੱਸ ਪਛਾਣ ਬਦਲ ਜਾਂਦੀ ਹੈ ਅਤੇ ਇਹ ਆਪਰੇਟਰ ਦਾ ਸਪਸ਼ਟ ਫ਼ੈਸਲਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। ਲੂਪਬੈਕ/ਪ੍ਰਾਈਵੇਟ ਨੋਡ (localhost, 127.0.0.1, 172.16-31.x) ਹਮੇਸ਼ਾ ਮਨਜ਼ੂਰ ਹਨ ਅਤੇ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦੇ। (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (ਅਤੇ ਮੈਮਰੀ ਇੰਜਣ ਦੇ ਲੂਪਬੈਕ ਰੀਰੈਂਕ ਪੜਾਅ) ਨੂੰ localhost ਤੋਂ ਬਾਹਰ ਹੋਸਟ ਕੀਤਾ OpenAI-ਅਨੁਕੂਲ ਪ੍ਰੋਵਾਈਡਰ ਨੋਡ ਵਰਤਣ ਦਿਓ — TEI, Infinity, vLLM ਆਦਿ ਚਲਾ ਰਿਹਾ ਕੋਈ LAN ਬਾਕਸ ਜਾਂ Tailscale ਪੀਅਰ। ਡਿਫੌਲਟ ਤੌਰ 'ਤੇ ਬੰਦ — ਰਿਮੋਟ ਹੋਸਟ ਵੱਲ ਰੂਟ ਕਰਨ ਨਾਲ ਈਗ੍ਰੈੱਸ ਪਛਾਣ ਬਦਲ ਜਾਂਦੀ ਹੈ ਅਤੇ ਇਹ ਆਪਰੇਟਰ ਦਾ ਸਪਸ਼ਟ ਫ਼ੈਸਲਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। ਲੂਪਬੈਕ ਨੋਡ (localhost, 127.0.0.1, 172.16-31.x) ਹਮੇਸ਼ਾ ਮਨਜ਼ੂਰ ਹਨ ਅਤੇ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦੇ। ਰਿਮੋਟ ਨੋਡ ਦੇ ਬੇਸ URL ਨੂੰ ਵੀ ਪ੍ਰੋਵਾਈਡਰ ਦੀ ਆਊਟਬਾਊਂਡ URL ਨੀਤੀ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ਪਾਸ ਕਰਨੀ ਲਾਜ਼ਮੀ ਹੈ। | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | ਜਦੋਂ OIDC ਸਮਰੱਥ ਹੋਵੇ, ਪਾਸਵਰਡ ਲੌਗਇਨ ਨੂੰ ਅਸਮਰੱਥ ਕਰੋ ਤਾਂ ਜੋ ਵਰਤੋਂਕਾਰ ਸਿਰਫ਼ OIDC ਸਿੰਗਲ ਸਾਈਨ-ਆਨ ਰਾਹੀਂ ਪ੍ਰਮਾਣਿਤ ਹੋ ਸਕਣ। ਸਧਾਰਨ ਉਪਨਾਮ `OIDC_DISABLE_PASSWORD_LOGIN` ਵੀ ਸਵੀਕਾਰ ਕੀਤਾ ਜਾਂਦਾ ਹੈ; ਉਸੇ ਕੁੰਜੀ ਵਾਲੇ ਡੈਸ਼ਬੋਰਡ ਫੀਚਰ ਫਲੈਗ ਨੂੰ ਪਹਿਲ ਮਿਲਦੀ ਹੈ। (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` ਦਾ ਸਧਾਰਨ ਉਪਨਾਮ (#10889)। | ### ਸੁਰੱਖਿਆ ਮਜ਼ਬੂਤੀ ਜਾਂਚ-ਸੂਚੀ diff --git a/docs/i18n/phi/CONTRIBUTING.md b/docs/i18n/phi/CONTRIBUTING.md index d48e38619637..01af88ee295e 100644 --- a/docs/i18n/phi/CONTRIBUTING.md +++ b/docs/i18n/phi/CONTRIBUTING.md @@ -392,17 +392,17 @@ Sumulat ng mga unit test sa `tests/unit/` na sumasaklaw, bilang minimum, sa: - [ ] Pumapasa ang mga test (`npm test`) - [ ] Pumapasa ang linting (`npm run lint`) - [ ] Matagumpay ang build (`npm run build`) -- [ ] Naidagdag ang mga TypeScript type para sa mga bagong pampublikong function at interface -- [ ] Walang mga naka-hardcode na secret o fallback value -- [ ] Naka-embed ang mga pampublikong upstream credential sa pamamagitan ng `resolvePublicCred()` (tingnan ang [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), at hindi kailanman bilang mga literal -- [ ] Dumadaan ang mga tugon sa error sa `buildErrorBody()` / `sanitizeErrorMessage()` — walang mga raw stack trace sa mga response body (tingnan ang [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Ipinapasa ng mga shell command (`exec` / `spawn`) ang mga runtime value sa pamamagitan ng `env`, hindi sa pamamagitan ng string interpolation -- [ ] Na-validate ang lahat ng input gamit ang mga Zod schema -- [ ] Nagdagdag ng **fragment** ng changelog sa ilalim ng `changelog.d/{features|fixes|maintenance}/-.md` para sa mga pagbabagong nakikita ng user (tingnan ang [`changelog.d/README.md`](./changelog.d/README.md)) — **huwag** direktang i-edit ang `CHANGELOG.md`; pinagsasama-sama ang mga fragment sa oras ng release at hindi kailanman nagkakaroon ng conflict sa pagitan ng mga PR +- [ ] Nagdagdag ng mga TypeScript type para sa mga bagong pampublikong function at interface +- [ ] Walang naka-hardcode na mga secret o fallback value +- [ ] Ang mga pampublikong upstream credential ay naka-embed sa pamamagitan ng `resolvePublicCred()` (tingnan ang [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), at hindi kailanman bilang mga literal +- [ ] Ang mga error response ay dumaraan sa `buildErrorBody()` / `sanitizeErrorMessage()` — walang mga raw stack trace sa mga response body (tingnan ang [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Ang mga shell command (`exec` / `spawn`) ay nagpapasa ng mga runtime value sa pamamagitan ng `env`, hindi sa pamamagitan ng string interpolation +- [ ] Napatunayan ang bisa ng lahat ng input gamit ang mga Zod schema +- [ ] Nagdagdag ng **fragment** ng changelog sa ilalim ng `changelog.d/{features|fixes|maintenance}/-.md` para sa mga pagbabagong nakikita ng user (tingnan ang [`changelog.d/README.md`](./changelog.d/README.md)) — **huwag** direktang i-edit ang `CHANGELOG.md`; pinagsasama-sama ang mga fragment sa oras ng release at hindi kailanman nagkakasalungatan sa pagitan ng mga PR - [ ] Na-update ang dokumentasyon (kung naaangkop) -- [ ] Walang bagong nabuksang alerto mula sa CodeQL / Secret-Scanning, o na-dismiss ang bawat isa nang may teknikal na katwirang tumutukoy sa nauugnay na dokumento sa `docs/security/` +- [ ] Walang bagong nabuksang alerto mula sa CodeQL / Secret-Scanning, o ang bawat isa ay na-dismiss nang may teknikal na katwiran na tumutukoy sa nauugnay na dokumento sa `docs/security/` - [ ] Ang mga route na naglulunsad ng mga child process (`/api/mcp/`, `/api/cli-tools/runtime/`) ay inuri bilang `isLocalOnlyPath()` sa `src/server/authz/routeGuard.ts` — tingnan ang [Mahigpit na Panuntunan #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Walang mga trailer na `Co-Authored-By` sa mga commit message — dapat lumabas ang mga commit sa ilalim lamang ng Git identity ng may-ari ng repository (Mahigpit na Panuntunan #16) +- [ ] Walang mga AI/bot na `Co-authored-by` trailer sa mga commit message (Mahigpit na Panuntunan #16) — ang mga taong collaborator na muling ginamit ang gawa ay kinikilala gamit ang karaniwang `Co-authored-by: Name ` na mga trailer --- diff --git a/docs/i18n/phi/docs/architecture/QUALITY_GATES.md b/docs/i18n/phi/docs/architecture/QUALITY_GATES.md index 948731566eb6..7426a3b938d8 100644 --- a/docs/i18n/phi/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/phi/docs/architecture/QUALITY_GATES.md @@ -15,228 +15,231 @@ plano ng replikasyon ng parehong sistema, tingnan ang --- -## Imbentaryo ng mga gate at profile ng pagpapatupad +## Imbentaryo ng gate at mga profile ng pagpapatupad ### Pagtanggap ng kandidato -Ang mga workflow ng CI at Quality Gates ay parehong naglalabas ng matatag na hatol: `Gate / CI` at -`Gate / Quality`. Inililista ng naka-bersyong patakaran sa pagtanggap ng mga ito ang bawat upstream na job -bilang kinakailangan o advisory. Dapat magtagumpay ang isang naaangkop na kinakailangang job: ang mga resultang -nawawala, kinansela, nilaktawan, nakabinbin, at hindi alam ay hindi makapagtatatag ng PASS. Maaaring gawing -hindi naaangkop ang isang code lane ng wastong klasipikasyong docs-only o catalog-only; -ang isang draft na PR ay hindi tinatanggap na kandidato. Hindi inaalis ng label na `hotfix` ang pangangailangan sa ebidensya. +Ang mga workflow ng CI at Quality Gates ay parehong naglalabas ng matatag na pasya: `Gate / CI` at +`Gate / Quality`. Inililista ng kanilang may-bersyong patakaran sa pagtanggap ang bawat upstream job +bilang kinakailangan o advisory. Dapat magtagumpay ang isang naaangkop na kinakailangang job: ang mga +resultang nawawala, kinansela, nilaktawan, nakabinbin, at hindi alam ay hindi makapagtatatag ng PASS. Maaaring +gawing hindi naaangkop ang isang code lane ng wastong klasipikasyong docs-only o catalog-only; +ang draft na PR ay hindi tinatanggap na kandidato. Hindi inaalis ng label na `hotfix` ang pangangailangan sa ebidensya. -Saklaw ng parehong workflow ang mga PR at push sa mga main/release branch, manual dispatch, at -merge-group event. Isinasagawa ng push, dispatch, at merge-group ang buong seleksyon. Gumagamit ang mga fork -at merge group ng mga hosted runner para sa mga job na karaniwang pumipili ng mga self-hosted +Saklaw ng parehong workflow ang mga PR at push sa mga main/release branch, manu-manong dispatch, at +mga merge-group event. Isinasagawa ng push, dispatch, at merge-group ang buong seleksyon. Gumagamit ang mga fork +at merge group ng mga hosted runner para sa mga job na kung hindi ay pipili ng mga self-hosted runner; dapat beripikahin ang sapat na hosted capacity bago ang rollout. Tinutukoy ng bawat JSON receipt ang na-checkout na SHA, workflow run, at attempt. -Tinatanggihan ng CLI ang hindi pagtutugma ng checkout/event SHA. Itinatali ng mga workflow test ang pagiging miyembro sa patakaran -sa listahan ng `needs` ng verdict job upang hindi palihim na mawala ang bago o inalis na lane. +Tinatanggihan ng CLI ang hindi pagtutugma ng checkout/event SHA. Iniuugnay ng mga workflow test ang pagiging miyembro ng patakaran +sa listahan ng `needs` ng verdict job upang hindi tahimik na mawala ang isang bago o inalis na lane. Saklaw ng mga receipt ang sarili nilang workflow, hindi ang publication, deployment, o mga internal -ng isang umiiral na advisory scanner. Ang pag-activate sa parehong pangalan ng check sa mga panuntunan ng branch ay isang -hiwalay na administratibong pagbabago; ang pagdaragdag sa mga job na ito ay hindi kusang nagpoprotekta sa isang branch. +na bahagi ng isang umiiral na advisory scanner. Ang pag-activate sa parehong pangalan ng check sa mga branch rule ay isang +hiwalay na pagbabagong administratibo; hindi awtomatikong napoprotektahan ang isang branch sa pagdaragdag lamang ng mga job na ito. ### Imbentaryo ng static scan -Ang naka-bersyong imbentaryo ng npm-alias at pagiging miyembro sa static-scan ay nasa -`config/quality/gate-manifest.json`. Patakbuhin ang `npm run check:gate-manifest` upang patotohanan +Makikita sa `config/quality/gate-manifest.json` ang may-bersyong imbentaryo ng npm-alias at ang pagiging miyembro ng static-scan. +Patakbuhin ang `npm run check:gate-manifest` upang patunayan ang mga pangalan ng script at eksaktong command laban sa `package.json`; kapag may idinagdag, inalis, o -nagbago ang command, mabibigo ang parehong local hook at mga change-classification job sa CI. +nagbago sa command, mabibigo ang lokal na hook at ang mga change-classification job sa CI. Ang alias ay hindi workflow job, matrix instance, o test case: hindi dapat ipakita ang mga bilang na ito na para bang mapagpapalit ang mga ito. Gamitin ang `npm run quality:scan -- --list` o `npm run quality:scan:fast -- --list` -upang siyasatin ang mga napiling alias nang hindi isinasagawa ang mga ito. Ginagamit ng runner ang -npm entrypoint, kaya napapanatili ang runtime nito (kabilang ang Bun kung naka-configure). -Itinatala ng manifest ang mga alias sa labas ng mga profile na iyon bilang hiwalay na isinasagawa, at +upang suriin ang mga napiling alias nang hindi isinasagawa ang mga ito. Tinatawag ng runner ang +npm entrypoint, kaya napapanatili ang runtime nito (kabilang ang Bun kung saan naka-configure). +Itinatala ng manifest bilang hiwalay na tinatawag ang mga alias na wala sa mga profile na iyon, at ipinagbabawal ang mga maintenance command sa mga read-only na profile ng scan. -Static scan lamang ang saklaw ng mga profile na ito. Hindi nito sinesertipikahan ang mga product test, +Ang static scan lamang ang saklaw ng mga profile na ito. Hindi nila sinesertipika ang mga product test, coverage, packaging, external check, o buong pagtanggap sa release ng isang kandidato. -Ginagamit ng pagtanggap sa workflow ang naka-link na `config/quality/admission-policy.json` at -`scripts/quality/admission-verdict.mjs`. Nananatiling hiwalay ang mga profile ng release-observer; -siyasatin nang hiwalay ang mga naaangkop na check at receipt ng mga ito. Ang tekstuwal na -imbentaryo sa ibaba ay sanggunian, hindi patunay na talagang naisagawa ang isang gate. +Ginagamit ng workflow admission ang naka-link na `config/quality/admission-policy.json` at +`scripts/quality/admission-verdict.mjs`. Nananatiling hiwalay ang mga release-observer profile; +suriin nang hiwalay ang kanilang mga naaangkop na check at receipt. Ang tekstuwal na +imbentaryo sa ibaba ay sanggunian, hindi patunay na aktuwal na tumakbo ang isang gate. Matatagpuan ang mga script sa ilalim ng `scripts/check/` (mga policy gate) at `scripts/quality/` (ratchet engine). Ang pinagmumulan ng katotohanan para sa CI ay `.github/workflows/ci.yml`. -### Mabilis na landas ng Release PR (`quality.yml`) +### Mabilis na landas ng release PR (`quality.yml`) -Kinukumpleto ng `.github/workflows/quality.yml` ang CI sa mga main/release PR, push sa protected branch, -dispatch, at merge group. Gumagamit ang mga PR ng mga mabilis na check na sinala ayon sa path. Inalis ang permanenteng -naka-disable na duplicate build; nananatili sa CI ang mga tunay na check para sa build/package/boot. +Dinadagdagan ng `.github/workflows/quality.yml` ang CI sa mga main/release PR, push sa protected branch, +dispatch, at merge group. Gumagamit ang mga PR ng mga path-filtered na mabilis na check. Inalis ang permanenteng +naka-disable na dobleng build; nananatili sa CI ang mga tunay na check para sa build/package/boot. -| Job | Saklaw | Nakakaharang | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | -| `Docs Gates (fast-path)` | Mga PR ng docs/code; mga reference ng API docs at docs-all | Oo | -| `Fast Quality Gates` | Mga PR ng code; mga static check, typecheck, dashboard typecheck, at mga apektadong unit test | Oo | -| `Forgotten sibling tests` | Mga PR ng code; tinutunton ang mga nabagong module patungo sa mga static consumer at kandidatong sibling test; iniuulat bilang mga advisory diagnostic ang mga barrel at dynamic-import path, kasama ang mga naka-reference na pagbubukod sa allowlist | **Advisory** | -| `Vitest (fast-path)` | Mga PR ng code; mabilis na vitest suite | Oo | -| `Unit Tests fast-path` | Mga PR ng code; 4-shard na unit suite | Oo | -| `No new ESLint warnings` | Mga PR ng code; suppressions-aware na lint guard | Oo, kabilang ang mga fork | -| `Merge integrity (changelog + generated skills)` | Mga hindi draft na PR; changelog at pag-sync ng mga nabuong skill | Oo, kabilang ang mga fork | +| Job | Saklaw | Humaharang | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | +| `Docs Gates (fast-path)` | Mga docs/code PR; mga sanggunian sa API docs at docs-all | Oo | +| `Fast Quality Gates` | Mga code PR; mga static check, typecheck, dashboard typecheck, at mga apektadong unit test | Oo | +| `Forgotten sibling tests` | Mga code PR; sinusundan ang mga binagong module patungo sa mga static consumer at kandidatong sibling test; iniuulat ang mga barrel at dynamic-import path bilang mga advisory diagnostic, kasama ang mga binanggit na allowlist exception | **Advisory** | +| `Vitest (fast-path)` | Mga code PR; mabilis na vitest suite | Oo | +| `Unit Tests fast-path` | Mga code PR; 4-shard na unit suite | Oo | +| `No new ESLint warnings` | Mga code PR; suppression-aware na lint guard | Oo, kabilang ang mga fork | +| `Merge integrity (changelog + generated skills)` | Mga hindi draft na PR; pag-sync ng changelog at mga nabuong skill | Oo, kabilang ang mga fork | #### Ulat ng mga nakaligtaang sibling test -Muling ginagamit ng `npm run check:forgotten-sibling-tests` ang import resolver sa likod ng mapa ng epekto sa test. -Para sa bawat nabagong production module, nag-uulat ito ng mga deterministikong chain na -`changed module/symbol -> static consumer -> candidate sibling test` kapag wala ang kandidatong -test sa diff ng pull request. Pinananatili ang buod na Markdown at resultang JSON bilang -workflow artifact na `forgotten-sibling-tests` para sa calibration bago ang anumang blocking rollout. +Muling ginagamit ng `npm run check:forgotten-sibling-tests` ang import resolver sa likod ng test-impact map. +Para sa bawat binagong production module, nag-uulat ito ng mga deterministikong +`changed module/symbol -> static consumer -> candidate sibling test` chain kapag wala ang kandidatong +test sa diff ng pull request. Pinananatili ang Markdown summary at JSON result bilang +workflow artifact na `forgotten-sibling-tests` para sa pagkakalibrate bago ang anumang blocking rollout. Ang mga barrel re-export at dynamic import ay para lamang sa mga diagnostic ng resolution; hindi kailanman lumilikha ang mga ito ng -finding na nakaka-block. Ang mga nasuring exception ay nasa +finding na nakakaharang. Ang mga nasuring exception ay nasa `config/quality/forgotten-sibling-allowlist.json`. Dapat tukuyin ng bawat entry ang consumer at candidate -test, magbigay ng partikular na katwiran, at mag-link sa isang GitHub issue o pull request. Kapag malformed ang mga entry, -awtomatikong bina-block ang mga ito. Hindi maaaring i-suppress ng mga exception ang isang tinanggal na candidate test o isang diff na nagdaragdag ng `.skip`/`.todo`; -ang pagpapahina ng assertion at iba pang masking ay nananatiling saklaw ng hiwalay na blocking -gate na `check:test-masking`. +test, magbigay ng partikular na katwiran, at mag-link sa isang GitHub issue o pull request. Ang mga entry na mali ang pagkakabuo ay +awtomatikong ibinabagsak. Hindi maaaring pigilan ng mga exception ang isang na-delete na candidate test o isang diff na nagdaragdag ng `.skip`/`.todo`; +ang pagpapahina ng assertion at iba pang pagtatakip ay nananatiling saklaw ng hiwalay na nakakaharang na +`check:test-masking` gate. ### Job: `lint` -Tumatakbo sa bawat PR patungo sa `main`. Bina-block ang merge kapag nabigo. - -| Script (`npm run ...`) | Bine-validate | Nagba-block | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| `check:node-runtime` | Ang bersyon ng Node.js ay nasa loob ng sinusuportahang saklaw | Oo | -| `check:cycles` | Mga circular import — lahat ng module sa `src/` + `open-sse/` | Oo | -| `check:route-validation:t06` | May mga Zod schema sa lahat ng route (patakaran ng Tier 6) | Oo | -| `check:any-budget:t11` | Hindi lumalampas sa budget ang bilang ng `@ts-expect-error // any` (Tier 11 catraca) | Oo | -| `check:provider-consistency` | Ang bawat provider sa `providers.ts` ay may katugmang entry sa `providerRegistry.ts` (at kabaliktaran, sa loob ng allowlist) | Oo | -| `check:model-lifecycle` | Nananatiling magkakatugma ang tatlong mano-manong pinapanatiling routing table sa naka-check-in na lifecycle snapshot (#11503): walang retired id na binibigyan ng score ng `FITNESS_TABLE` (`taskFitness.ts`) na maaaring i-route ng `REGISTRY`; naroroon sa `REGISTRY` at wala sa retired-id snapshot ang bawat target ng `BUILT_IN_ALIASES`; ang bawat retired id na nasa `REGISTRY` pa rin ay ipinapasa o nakalista sa `allowedRetiredInCatalog`; at walang source o target ng `DEFAULT_DEGRADATION_MAP` na lumalabas bilang retired sa snapshot na iyon. Hindi nito pinatutunayan na kasalukuyang sineserbisyuhan ng live upstream ang isang modelo. Offline — inihahambing sa `config/quality/model-lifecycle.json`, na mano-manong nire-refresh gamit ang `npm run quality:refresh-model-lifecycle` (network; hindi naka-wire sa CI). Ang `allowedRetiredInCatalog` ay isang burn-down ratchet: magdagdag lamang ng entry na may tracking issue. | Oo | -| `check:fetch-targets` | Ang bawat `fetch("/api/...")` sa client-side na `src/` ay tumutukoy sa isang totoong `route.ts` | Oo | -| `check:deps` | Ang lahat ng dependency na maaaring i-`npm install` mula sa bawat `package.json` sa repo ay nasa `dependency-allowlist.json`; bina-flag ang mga bagong package na hindi naka-pin o slopsquatted | Oo | -| `audit:deps` | `npm audit` (root + electron) — walang high/critical na advisory (nag-o-overlap sa osv `check:vuln-ratchet`; tingnan ang Rationalization Backlog) | Oo | -| `check:lockfile` | Integridad ng `package-lock.json` — https registry, mga integrity hash, walang host override | Oo | -| `check:licenses` | Allowlist ng lisensyang SPDX para sa mga dependency sa production | Oo | -| `check:tracked-artifacts` | Walang mga artifact ng build / naka-commit na mga symlink ng `node_modules` (tumatakbo rin sa husky pre-commit; sadyang magaan ang pre-push — #6716) | Oo | -| `check:ai-attribution` | Walang trailer na `Co-Authored-By` mula sa AI/bot o footer ng pagbuo ng AI sa mga commit, pamagat, o katawan ng PR — Mahigpit na Panuntunan #16 (sa fast-gates loop ng `quality.yml` para sa PR→`release/**` — binabasa ang event payload, walang ginagawa sa labas ng mga PR — at isang hakbang na para lamang sa PR sa lint ng `ci.yml` para sa PR→`main`; pati na rin ang husky hook na `commit-msg`; pinapayagan ang mga taong co-author; #14436) | -| `check:vitest-exclusions` | Ang bawat pagbubukod sa Vitest ay tumutukoy sa isang tracking issue at lumilitaw sa `config/quality/vitest-exclusions.json` (#13204) | Oo | -| `check:file-size` | Walang source file na lumalampas sa limitasyon para sa bawat extension (ratchet: ang malalaking file na hindi na maaaring lumaki ay nasa listahang `frozen`) | Oo | -| `check:error-helper` | Ginagamit ng mga tugon sa error sa mga executor/handler ang `buildErrorBody()` / `sanitizeErrorMessage()` (Mahigpit na Panuntunan #12) | Oo | -| `check:migration-numbering` | Ang mga Migration SQL file ay sunod-sunod ang pagnunumero, walang puwang o duplikado | Oo | -| `check:public-creds` | Walang literal na OAuth `client_id`/`client_secret` o mga Firebase Web key sa labas ng `publicCreds.ts` (Mahigpit na Panuntunan #11) | Oo | -| `check:db-rules` | Walang raw SQL sa labas ng mga module ng `src/lib/db/`; walang mga barrel import mula sa `localDb.ts` (Mahihigpit na Panuntunan #2/#5) | Oo | -| `check:known-symbols` | Ang mga provider executor, routing strategy, at translator na nakarehistro sa kani-kanilang mga dispatch table ay tumutugma sa mga file sa disk—walang mga ulila o hindi idineklarang simbolo | Oo | -| `check:route-guard-membership` | Ang bawat route na lumilikha ng child process ay inuuri ng `isLocalOnlyPath()` (Mahihigpit na Panuntunan #15/#17) | Oo | -| `check:test-discovery` | Ang bawat `*.test.ts` / `*.spec.ts` file sa repo ay kinokolekta ng kahit isang test runner (ratchet: maaari lamang lumiit ang listahan ng mga ulila sa `test-discovery-baseline.json`) | Oo | -| `check:agent-skills-sync` | Tumutugma ang mga nabuong agent-skills artifact sa kanilang source catalog (walang drift) | -| `check:provider-asset-provenance` | May nakatalang provenance entry ang mga logo/asset ng provider | -| `lint:json` | Napi-parse ang mga JSON config file at sumusunod ang mga ito sa mga lint rule ng repo | -| `typecheck:core` | Pag-compile ng TypeScript nang walang mga error (mga advisory warning lamang) | Oo | -| `typecheck:noimplicit:core` | Mahigpit na `noImplicitAny` — nakatuon sa hinaharap; kailangan pa rin ng mga annotation ang maraming dati nang call site | **Advisory** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` na nakatuon sa `src/app/(dashboard)/**` (#7033) — hindi kasama sa piniling 27-file allowlist ng `typecheck:core` ang anumang dashboard TSX, at hindi rin ito kailanman tine-type-check ng `next build` (itinatakda ng `next.config.mjs` ang `ignoreBuildErrors: true`), kaya hindi nakikita ng CI ang mga orphaned-identifier regression doon (#6625/#6909). Inihahambing sa isang naka-freeze na baseline ng bilang sa bawat file/bawat TS code (`config/quality/dashboard-typecheck-baseline.json`, kaparehong pattern ng stale-enforcement gaya ng `check:known-symbols`) — tanging mga BAGONG error na lampas sa naka-baseline na bilang ang magpapabagsak sa gate; unti-unting pababain gamit ang `--update` kapag naayos ang isang dati nang error. | Oo | +Tumatakbo sa bawat PR patungong `main`. Hinaharang ang merge kapag nabigo. + +| Script (`npm run ...`) | Bine-validate | Nakakaharang | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | +| `check:node-runtime` | Ang bersyon ng Node.js ay nasa loob ng suportadong saklaw | Oo | +| `check:cycles` | Mga circular import sa buong `src/` + `open-sse/` (nakabatay sa AST, ni-resolve ang `paths` ng tsconfig). Bare = advisory, inililista ang mga cycle. Hinaharang ng `check:cycles:ratchet` (ang pinapatakbo ng CI) kapag lumampas ang bilang sa limitasyong `metrics.cycles` sa `quality-baseline.json` — kasalukuyang 14, `direction: down`, kaya maaari lamang itong bumaba (#15159 G-01/G-02) | Oo (ratchet) | +| `check:route-validation:t06` | May mga Zod schema sa lahat ng route (patakaran ng Tier 6) | Oo | +| `check:any-budget:t11` | Ang bilang ng `@ts-expect-error // any` ay hindi lumalampas sa budget (Tier 11 catraca) | Oo | +| `check:provider-consistency` | Ang bawat provider sa `providers.ts` ay may katugmang entry sa `providerRegistry.ts` (at vice-versa, sa loob ng allowlist) | Oo | +| `check:model-lifecycle` | Ang tatlong routing table na mano-manong pinananatili ay nananatiling tugma sa naka-check-in na lifecycle snapshot (#11503): walang retired id na maaaring i-route ng `REGISTRY` ang binibigyan ng score ng `FITNESS_TABLE` (`taskFitness.ts`); ang bawat target ng `BUILT_IN_ALIASES` ay nasa `REGISTRY` at wala sa retired-id snapshot; ang bawat retired id na nasa `REGISTRY` pa rin ay naka-forward o nakalista sa `allowedRetiredInCatalog`; at walang source o target ng `DEFAULT_DEGRADATION_MAP` ang lumilitaw na retired sa snapshot na iyon. Hindi nito pinatutunayan na ang isang modelo ay kasalukuyang sineserbisyuhan ng isang live upstream. Offline — inihahambing sa `config/quality/model-lifecycle.json`, na mano-manong nire-refresh gamit ang `npm run quality:refresh-model-lifecycle` (network; hindi naka-wire sa CI). Ang `allowedRetiredInCatalog` ay isang burn-down ratchet: magdagdag lamang ng entry kung may tracking issue. | Oo | +| `check:fetch-targets` | Ang bawat `fetch("/api/...")` sa client-side na `src/` ay tumutukoy sa isang tunay na `route.ts` | Oo | +| `check:deps` | Ang lahat ng dependency na maaaring i-`npm install` mula sa bawat `package.json` sa repo ay nasa `dependency-allowlist.json`; mina-flag ang mga bagong package na hindi naka-pin o slopsquatted | Oo | +| `audit:deps` | `npm audit` (root + electron) — walang high/critical na advisory (nag-o-overlap sa osv `check:vuln-ratchet`; tingnan ang Backlog ng Rasyonalisasyon) | Oo | +| `check:lockfile` | Integridad ng `package-lock.json` — https registry, mga integrity hash, walang host override | Oo | +| `check:licenses` | Allowlist ng lisensyang SPDX para sa mga dependency sa production | Oo | +| `check:tracked-artifacts` | Walang mga artifact ng build / naka-commit na mga symlink ng `node_modules` (tumatakbo rin sa husky pre-commit; sadyang magaan ang pre-push — #6716) | Oo | +| `check:ai-attribution` | Walang trailer na AI/bot `Co-Authored-By` o footer ng pagbuo ng AI sa mga commit, pamagat, o katawan ng PR — Mahigpit na Panuntunan #16 (nasa fast-gates loop ng `quality.yml` para sa PR→`release/**` — binabasa ang event payload, walang ginagawa kapag hindi PR — at isang hakbang na para lamang sa PR sa lint ng `ci.yml` para sa PR→`main`; pati na rin ang husky hook na `commit-msg`; pinapayagan ang mga taong kapwa may-akda; #14436) | +| `check:vitest-exclusions` | Ang bawat pagbubukod sa Vitest ay nagbabanggit ng tracking issue at lumilitaw sa `config/quality/vitest-exclusions.json` (#13204) | Oo | +| `check:file-size` | Walang source file na lalampas sa limitasyon para sa bawat extension (ratchet: mga naka-freeze na malaking file sa listahang `frozen`) | Oo | +| `check:error-helper` | Gumagamit ang mga tugon sa error sa mga executor/handler ng `buildErrorBody()` / `sanitizeErrorMessage()` (Mahigpit na Panuntunan #12) | Oo | +| `check:migration-numbering` | Ang mga Migration SQL file ay sunod-sunod ang pagkakanumero, walang puwang o duplikado | Oo | +| `check:public-creds` | Walang literal na OAuth `client_id`/`client_secret` o mga Firebase Web key sa labas ng `publicCreds.ts` (Mahigpit na Panuntunan #11) | Oo | +| `check:db-rules` | Walang raw SQL sa labas ng mga module ng `src/lib/db/`; walang mga barrel import mula sa `localDb.ts` (Mahihigpit na Panuntunan #2/#5) | Oo | +| `check:known-symbols` | Ang mga provider executor, routing strategy, at translator na nakarehistro sa kani-kanilang dispatch table ay tumutugma sa mga file sa disk—walang nakahiwalay o hindi nakadeklarang mga symbol | Oo | +| `check:route-guard-membership` | Ang bawat route na lumilikha ng child process ay inuuri ng `isLocalOnlyPath()` (Mahihigpit na Panuntunan #15/#17) | Oo | +| `check:test-discovery` | Ang bawat `*.test.ts` / `*.spec.ts` file sa repo ay kinokolekta ng kahit isang test runner (ratchet: maaari lamang umikli ang listahan ng mga nakahiwalay na file sa `test-discovery-baseline.json`) | Oo | +| `check:agent-skills-sync` | Tumutugma ang mga nabuong artifact ng agent-skills sa kanilang source catalog (walang drift) | +| `check:provider-asset-provenance` | May nakatalang provenance entry ang mga logo/asset ng provider | +| `lint:json` | Nape-parse ang mga JSON config file at sumusunod ang mga ito sa mga panuntunan sa lint ng repo | +| `typecheck:core` | Pag-compile ng TypeScript nang walang mga error (mga advisory warning lamang) | Oo | +| `typecheck:noimplicit:core` | Mahigpit na `noImplicitAny` — nakatuon sa hinaharap; marami pa ring dati nang call site na nangangailangan ng mga annotation | **Advisory** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` na limitado sa `src/app/(dashboard)/**` (#7033) — hindi kasama sa piniling 27-file allowlist ng `typecheck:core` ang anumang dashboard TSX, at hindi rin ito kailanman tine-type-check ng `next build` (`next.config.mjs` ay nagtatakda ng `ignoreBuildErrors: true`), kaya hindi nakikita ng CI ang mga regression ng orphaned identifier doon (#6625/#6909). Inihahambing ang mga diff sa isang naka-freeze na baseline ng bilang ayon sa file/TS-code (`config/quality/dashboard-typecheck-baseline.json`, kaparehong pattern ng stale enforcement gaya ng `check:known-symbols`) — mga BAGONG error lamang na lampas sa naka-baseline na bilang ang nagpapabigo sa gate; unti-unting ibaba gamit ang `--update` kapag naayos ang dati nang error. | Oo | ### Job: `quality-gate` -Tumatakbo pagkatapos ng `test-coverage`. Hinaharangan ang pag-merge kapag nabigo. - -| Script | Bine-validate | Pagharang | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | -| `quality:collect` | Gumagawa ng `quality-metrics.json` (bilang ng mga babala ng ESLint, coverage mula sa pinagsamang ulat ng shard) | Oo (bago ang ratchet) | -| `quality:ratchet` | Hindi umatras ang bawat metric sa `quality-baseline.json` (mga babala ng ESLint ≤ baseline; coverage ≥ baseline) | Oo | -| `check:duplication` | Hindi lumalampas ang pagdodoble ng code (jscpd@4) sa baseline sa `quality-baseline.json` | Oo | -| `check:complexity` | Hindi lumalampas sa cap ang cyclomatic complexity sa antas ng file (pangunahing ESLint `complexity` + `max-lines-per-function`) | Oo | -| `check:cognitive-complexity` | Ratchet ng cognitive complexity (`eslint-plugin-sonarjs`) — hiwalay na ESLint pass; parehong pinapatakbo ng CI bilang iisang hakbang na `check:complexity-ratchets` | Oo | -| `check:dead-code` | Hindi lumalala kumpara sa baseline ang ratchet ng mga hindi ginagamit na export / file (knip) | Oo | -| `check:compression-budget` | Budget ng compression benchmark — hindi dapat bumaba ang pinakamababang matitipid na token para sa bawat engine | Oo | -| `check:type-coverage` | Hindi bumababa ang percent-typed ratchet (`type-coverage`); higit na sinasaklaw nito ang `typecheck:noimplicit:core` | Oo | -| `check:codeql-ratchet` | Hindi dumarami ang mga bukas na alerto ng CodeQL (nagbabasa sa pamamagitan ng `gh api`; maayos na nilalaktawan kapag walang token) — iskedyul ng pag-refresh at manu-manong pag-trigger: tingnan ang "CodeQL ratchet" sa ibaba | Oo | - -### Job: `quality-extended` - -Ang buong job ay advisory (`continue-on-error: true`). Aktuwal na pinapatakbo ang mga ratchet na nakabatay sa npm; ini-install ang mga panlabas na scanner sa pamamagitan ng `gh release download` at kusang nilalaktawan (exit 0) kapag wala pa rin ang binary. - -| Script | Bine-validate | Pagharang | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| `check:circular-deps` | Walang mga circular dependency (dpdm) | **Advisory** | -| `check:bundle-size` | Hindi lumalampas sa cap ang laki ng bundle | **Advisory** | -| `check:secrets` | Pag-scan ng mga lihim (gitleaks) — nilalaktawan kung wala ang binary | **Advisory** | -| `check:vuln-ratchet` | Hindi lumalala ang mga kahinaan ng dependency (osv-scanner) — nilalaktawan kung wala ang binary | **Advisory** | -| `check:workflows` | Pag-lint ng workflow (actionlint + zizmor); ang nawawala/sirang mga scanner, di-wastong ulat, o nawawalang baseline ng ratchet ay nabibigo bilang INCOMPLETE. Ang mga wastong natuklasan ay sumusunod sa napiling patakarang strict/advisory/ratchet | Kailangang maisagawa; humaharang sa CI ang zizmor ratchet | -| `check:openapi-breaking` | Mga breaking change sa pampublikong kontrata ng API (`openapi.yaml`) kumpara sa base branch (oasdiff) — gumagawa ng `openapiBreaking=N`; nilalaktawan kung wala ang oasdiff o hindi maresolba ang base spec | **Advisory** | - -### Job: `docs-sync-strict` - -Pinapatakbo sa bawat PR patungo sa `main`. Hinaharang ang pag-merge kapag nabigo. - -| Script | Bine-validate | Humaharang | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:docs-all` | Meta-gate na sunod-sunod na nagpapatakbo sa 6 na sub-gate sa ibaba | Oo | -| ↳ `check:docs-sync` | Pagkakatugma ng bersyon ng CHANGELOG / OpenAPI / llm.txt | Oo | -| ↳ `check:docs-counts` | Ang mga bilang sa teksto (bilang ng provider, bilang ng migration, atbp.) ay nasa loob ng ratchet window ng mga aktuwal na bilang | Oo | -| ↳ `check:env-doc-sync` | Ang bawat env var sa `.env.example` ay nakadokumento sa isang talahanayan ng docs, at gayundin sa kabaligtaran | Oo | -| ↳ `check:deprecated-versions` | Walang mga string ng hindi na suportadong bersyon sa docs | Oo | -| ↳ `check:doc-links` | Ang mga panloob na markdown link sa docs ay tumutukoy sa mga aktuwal na file (`[text]`/`(path)` na anyo) | Oo | -| ↳ `check:fabricated-docs` | Umiiral sa codebase ang mga route, env var, CLI command, pangalan ng hook, at file path na binanggit sa docs. Mahigpit na gate sa pamamagitan ng `--strict`; soft-fail kapag walang flag. | Oo (sa pamamagitan ng `--strict` sa CI) | -| `check:cli-i18n` | Makikita ang mga string ng CLI command sa lahat ng i18n locale file | Oo | -| `check:openapi-coverage` | Sinasaklaw ng OpenAPI spec ang hindi bababa sa isang ratcheted floor ng mga aktuwal na route | Oo | -| `check:openapi-security-tiers` | Ang mga anotasyon ng security tier sa `openapi.yaml` ay tugma sa mga klasipikasyon ng `routeGuard.ts` | **Payo lamang** | -| `check:openapi-routes` | Ang bawat path sa `openapi.yaml` ay tumutukoy sa isang aktuwal na `route.ts` (kontra-hallucination) | Oo | -| `check:docs-symbols` | Ang bawat `/api/...` na sanggunian sa `docs/**/*.md` ay tumutukoy sa isang aktuwal na `route.ts` (kontra-hallucination) | Oo | -| `i18n translation drift` | Mga hindi naisaling key sa mga i18n locale file — babala lamang | **Payo lamang** | +Tumatakbo pagkatapos ng `test-coverage`. Hinaharangan ang merge kapag nabigo. + +| Script | Bineberipika | Humaharang | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `quality:collect` | Gumagawa ng `quality-metrics.json` (bilang ng babala ng ESLint, coverage mula sa pinagsamang ulat ng shard) | Oo (nauuna sa ratchet) | +| `quality:ratchet` | Hindi bumaba ang bawat metric sa `quality-baseline.json` (mga babala ng ESLint ≤ baseline; coverage ≥ baseline) | Oo | +| `check:duplication` | Hindi lumalampas ang pagdodoble ng code (jscpd@4) sa baseline sa `quality-baseline.json` | Oo | +| `check:complexity` | Hindi lumalampas sa limitasyon ang cyclomatic complexity sa antas ng file (pangunahing ESLint `complexity` + `max-lines-per-function`) | Oo | +| `check:cognitive-complexity` | Ratchet ng cognitive complexity (`eslint-plugin-sonarjs`) — hiwalay na pagpapatakbo ng ESLint; parehong pinapatakbo ng CI nang pinagsama bilang iisang hakbang na `check:complexity-ratchets` | Oo | +| `check:dead-code` | Hindi bumaba kumpara sa baseline ang ratchet ng mga hindi ginagamit na export / file (knip) | Oo | +| `check:compression-budget` | Badyet para sa benchmark ng compression — hindi dapat bumaba ang pinakamababang token savings ng bawat engine | Oo | +| `check:type-coverage` | Hindi bumaba ang ratchet ng porsiyento ng may type (`type-coverage`); karaniwang sinasaklaw nito ang `typecheck:noimplicit:core` | Oo | +| `check:codeql-ratchet` | Hindi dumami ang mga bukas na alerto ng CodeQL (binabasa sa pamamagitan ng `gh api`; maayos na nilalaktawan kapag walang token) — para sa dalas ng pag-refresh at manu-manong pag-trigger: tingnan ang "CodeQL ratchet" sa ibaba | Oo | + +### Trabaho: `quality-extended` + +Ang buong trabaho ay nagbibigay-payo lamang (`continue-on-error: true`). Aktuwal na +tumatakbo ang mga ratchet na nakabatay sa npm; nag-i-install ang mga panlabas na scanner +sa pamamagitan ng `gh release download` at kusang lumalaktaw (exit 0) kapag wala pa rin +ang binary. + +| Script | Bineberipika | Humaharang | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| `check:circular-deps` | Walang mga paikot na dependency (dpdm) | **Nagbibigay-payo** | +| `check:bundle-size` | Hindi lumalampas sa limitasyon ang laki ng bundle | **Nagbibigay-payo** | +| `check:secrets` | Pag-scan ng mga lihim (gitleaks) — nilalaktawan kung wala ang binary | **Nagbibigay-payo** | +| `check:vuln-ratchet` | Hindi dumami ang mga kahinaan sa dependency (osv-scanner) — nilalaktawan kung wala ang binary | **Nagbibigay-payo** | +| `check:workflows` | Pag-lint ng workflow (actionlint + zizmor); ang mga nawawala/sirang scanner, di-wastong ulat, o nawawalang ratchet baseline ay nabibigo bilang INCOMPLETE. Ang mga wastong natuklasan ay sumusunod sa napiling patakarang strict/advisory/ratchet | Kinakailangan ang pagpapatupad; humaharang sa CI ang zizmor ratchet | +| `check:openapi-breaking` | Mga breaking change sa pampublikong kontrata ng API (`openapi.yaml`) kumpara sa base branch (oasdiff) — gumagawa ng `openapiBreaking=N`; nilalaktawan kung wala ang oasdiff o hindi malutas ang base spec | **Nagbibigay-payo** | + +### Trabaho: `docs-sync-strict` + +Tumatakbo sa bawat PR patungo sa `main`. Hinaharang ang pag-merge kapag nabigo. + +| Script | Bineberipika | Humahadlang | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:docs-all` | Meta-gate na sunud-sunod na nagpapatakbo sa 6 na sub-gate sa ibaba | Oo | +| ↳ `check:docs-sync` | Pagkakatugma ng bersyon ng CHANGELOG / OpenAPI / llm.txt | Oo | +| ↳ `check:docs-counts` | Ang mga bilang sa teksto (bilang ng provider, bilang ng migration, atbp.) ay nasa loob ng ratchet window ng mga aktuwal na bilang | Oo | +| ↳ `check:env-doc-sync` | Nakadokumento sa isang talahanayan ng docs ang bawat env var sa `.env.example`, at gayundin sa kabilang direksyon | Oo | +| ↳ `check:deprecated-versions` | Walang mga string ng hindi na suportadong bersyon sa docs | Oo | +| ↳ `check:doc-links` | Ang mga internal na markdown link sa docs ay tumutukoy sa mga aktuwal na file (`[text]`/`(path)` na anyo) | Oo | +| ↳ `check:fabricated-docs` | Umiiral sa codebase ang mga route, env var, CLI command, pangalan ng hook, at path ng file na binanggit sa docs. Mahigpit na gate sa pamamagitan ng `--strict`; soft-fail kapag walang flag. | Oo (sa pamamagitan ng `--strict` sa CI) | +| `check:cli-i18n` | Makikita ang mga string ng CLI command sa lahat ng i18n locale file | Oo | +| `check:openapi-coverage` | Saklaw ng OpenAPI spec ang hindi bababa sa ratcheted floor ng mga aktuwal na route | Oo | +| `check:openapi-security-tiers` | Ang mga anotasyon ng security tier sa `openapi.yaml` ay naaayon sa mga klasipikasyon ng `routeGuard.ts` | **Payo lamang** | +| `check:openapi-routes` | Ang bawat path sa `openapi.yaml` ay tumutukoy sa isang aktuwal na `route.ts` (kontra-halusinasyon) | Oo | +| `check:docs-symbols` | Ang bawat sangguniang `/api/...` sa `docs/**/*.md` ay tumutukoy sa isang aktuwal na `route.ts` (kontra-halusinasyon) | Oo | +| `i18n translation drift` | Mga hindi naisaling key sa mga i18n locale file — babala lamang | **Payo lamang** | ### Job: `i18n-ui-coverage` -| Script | Bine-validate | Humaharang | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (inline) | Ang saklaw ng UI i18n key ay ≥ 65% | Oo | -| `check-ui-value-drift` (inline) | Kapag muling isinulat ang isang English na **value**, walang naiiwang lipas na salin | Oo | -| `check-new-key-coverage` (inline) | Ang isang **bagong** English key ay naisalin sa bawat locale — tatanggihan ang isang `__MISSING__:` marker | Oo | -| `check-translation-ratio` | Ang proporsyon ng aktuwal na salin bawat locale (mga leaf na kapareho ng English / placeholder / nawawala sa labas ng allowlist) ay hindi dapat lumampas sa `config/quality/i18n-translation-baseline.json` + slack | **Payo lamang** | +| Script | Bineberipika | Humahadlang | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (inline) | Ang saklaw ng UI i18n key ay ≥ 65% | Oo | +| `check-ui-value-drift` (inline) | Kapag muling isinulat ang isang English na **value**, walang naiiwang lipas na salin | Oo | +| `check-new-key-coverage` (inline) | Ang isang **bagong** English key ay naisalin sa bawat locale — tinatanggihan ang marker na `__MISSING__:` | Oo | +| `check-translation-ratio` | Ang ratio ng aktuwal na salin sa bawat locale (mga value na kapareho ng English / placeholder / nawawalang leaf sa labas ng allowlist) ay hindi dapat lumampas sa `config/quality/i18n-translation-baseline.json` + slack | **Payo lamang** | -Nangangailangan ng `fetch-depth: 0` — dini-diff ng value-drift gate ang `en.json` laban sa merge base. +Kailangan ng `fetch-depth: 0` — dini-diff ng value-drift gate ang `en.json` laban sa merge base. #### `check-ui-value-drift` — gate para sa lipas na salin -Nahuhuli nito ang isang i18n regression na hindi nakikita ng ibang mga gate sa estruktural na paraan: muling -isinusulat ang isang English value at naiiwan ang mga saling hinango mula sa _nakaraang_ English, kaya -patuloy na nakakabasa ang mga user na hindi gumagamit ng English ng tekstong may tiyak na pananalita ngunit mali na ngayon. +Nahuhuli nito ang isang i18n regression na hindi nakikita ng iba pang gate sa estruktural na paraan: muling +isinulat ang isang English na value at naiwan ang mga saling hinango sa _nakaraang_ English, kaya +patuloy na nagbabasa ang mga hindi English na user ng tekstong may kumpiyansang pananalita ngunit mali na ngayon. -Aktuwal itong na-release. Muling isinulat ang `oauthModal.googleOAuthWarning` nang idagdag ang Antigravity +Talagang nailabas ito. Muling isinulat ang `oauthModal.googleOAuthWarning` nang idagdag ang Antigravity login helper (#5203); **39 sa 43 locale** ang nanatiling may tekstong nagsasabi sa mga operator na "kopyahin ang buong URL at i-paste ito sa ibaba" — isang flow na hindi makukumpleto para sa provider na iyon. Hindi ito napansin hanggang #8463 dahil: -- nagba-backfill lamang ang `sync-ui-keys` ng mga key na **wala**, at hindi kailanman ng mga **lipas**; -- binibilang ng `check-ui-keys-coverage` ang _presensya_ ng key, kaya itinuturing na saklaw ang isang lipas na salin; -- sinusubaybayan ng `check-translation-drift` ang mga mirror ng dokumentasyong `docs/i18n//**.md` — - hindi nito kailanman binabasa ang `src/i18n/messages/*.json`. Humaharang sa job na `docs-sync-strict` mula noong - 2026-09 re-sync: mag-edit ng isang pangunahing doc → `npm run i18n:run -- --files=` (antas ng seksyon, mura). +- Nagba-backfill lamang ang `sync-ui-keys` ng mga key na **wala**, hindi kailanman ng mga **lipas**; +- Binibilang ng `check-ui-keys-coverage` ang _pagkakaroon_ ng key, kaya itinuturing na sakop ang isang lipas na salin; +- Sinusubaybayan ng `check-translation-drift` ang mga mirror ng dokumentasyong `docs/i18n//**.md` — + hindi nito kailanman binabasa ang `src/i18n/messages/*.json`. Humahadlang sa job na `docs-sync-strict` mula noong + 2026-09 na muling pag-sync: mag-edit ng pangunahing doc → `npm run i18n:run -- --files=` (antas-seksyon, mabilis). -**May kamalayan sa diff, hindi nakabatay sa baseline.** Inihahambing nito ang `en.json` sa merge base laban sa -working tree; para sa bawat key na nagbago ang English value, lipas na ang anumang locale na mayroon pa ring -hindi nabagong salin. Sinasadya nitong **i-freeze ang dati nang pagkakautang** — hindi maihahayag ng isang diff -kung sa aling lumang English nagmula ang isang matagal nang salin, kaya ang gate ay humuhusga +**Nakabatay sa diff, hindi sa baseline.** Inihahambing nito ang `en.json` sa merge base laban sa +working tree; para sa bawat key na nagbago ang English na value, stale ang anumang locale na mayroon +pa ring hindi nabagong salin. Sadya nitong **pinananatili ang dati nang utang** — hindi maipapakita ng diff +kung sa aling lumang English nagmula ang isang matagal nang salin, kaya ang gate ay humahatol lamang sa mga naaapektuhan ng kasalukuyang pagbabago. Ang alternatibo (isang per-key hash baseline) ay mangangailangan -ng ~600 KB na generated file, 3× ng pinakamalaking kasalukuyang baseline, na mababago sa bawat i18n PR. +ng ~600 KB na generated file, 3× ng pinakamalaking kasalukuyang baseline, na magbabago sa bawat i18n PR. Dalawang paraan upang matugunan ito: 1. i-update ang mga apektadong salin, o -2. itakda ang mga ito sa `__MISSING__:` — ihahatid pagkatapos ng runtime ang itinamang English - (`src/i18n/request.ts::deepMergeFallback`, #7258) at ipipila ang key para sa pagsasalin. +2. itakda ang mga ito sa `__MISSING__:` — pagkatapos ay ihahatid ng runtime ang itinamang English + (`src/i18n/request.ts::deepMergeFallback`, #7258) at ilalagay sa pila ang key para sa pagsasalin. Kung nagbago ang **kahulugan** ng string, mas mainam na **palitan ang pangalan ng key**: hindi maaaring magmana -ng lipas na salin ang isang bagong key. Iyan ang pattern na ginamit ng #8463. +ng stale na salin ang isang bagong key. Iyon ang pattern na ginamit ng #8463. ```bash -npm run i18n:check-value-drift # mahigpit (ang pinapatakbo ng CI) +npm run i18n:check-value-drift # mahigpit (ito ang pinapatakbo ng CI) npm run i18n:check-value-drift:warn # ulat lamang BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Nagtatapos nang may 0 at `SKIP reason=base-unresolved` kapag hindi mabasa ang base catalog (shallow -clone na walang base ref), gaya ng `check-openapi-breaking`. +Nag-e-exit nang 0 na may `SKIP reason=base-unresolved` kapag hindi mabasa ang base catalog (shallow +clone na walang base ref), katulad ng `check-openapi-breaking`. ### Job: `i18n` Kumpletong i18n validation matrix (isang job bawat locale). Advisory ang buong job. -| Script | Bine-validate | Pagba-block | +| Script | Bine-validate | Pag-block | | ------------------------------- | ----------------------------------- | ----------------------------------------------------- | | `validate_translation.py quick` | Pagkakumpleto ng salin bawat locale | **Advisory** (`continue-on-error: true` sa buong job) | @@ -244,33 +247,33 @@ Kumpletong i18n validation matrix (isang job bawat locale). Advisory ang buong j Tumatakbo lamang sa mga pull request. -| Script | Bine-validate | Pagba-block | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | Ang mga PR na nagbabago ng production code sa `src/`, `open-sse/`, `electron/`, o `bin/` ay dapat magsama o mag-update ng mga test (Hard Rule #8) | Oo | -| `check:test-masking` | Hindi binabawasan ng mga binagong test file ang net assert count o nagdaragdag ng mga tautology na `assert.ok(true)` | Oo | -| `check:pr-evidence` | Binabanggit ng PR body ang ebidensya ng test/VPS para sa pagbabago (isinasagawa ang Hard Rule #18 sa pamamagitan ng pag-grep sa prose ng PR — marupok, tingnan ang Backlog) | Oo | +| Script | Bine-validate | Pag-block | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `check:pr-test-policy` | Ang mga PR na nagbabago ng production code sa `src/`, `open-sse/`, `electron/`, o `bin/` ay dapat magsama o mag-update ng mga test (Mahigpit na Panuntunan #8) | Oo | +| `check:test-masking` | Hindi binabawasan ng mga binagong test file ang net assert count o nagdaragdag ng mga tautology na `assert.ok(true)` | Oo | +| `check:pr-evidence` | Nagbabanggit ang PR body ng test/VPS evidence para sa pagbabago (ginagawang mekanikal ang Mahigpit na Panuntunan #18 sa pamamagitan ng pag-grep sa prose ng PR — marupok, tingnan ang Backlog) | Oo | ### Job: `test-vitest` -Tumatakbo pagkatapos ng `build`. Hinaharangan ang pag-merge kapag nabigo. +Tumatakbo pagkatapos ng `build`. Hinaharangan ang merge kapag nabigo. -| Suite | Bine-validate | Pagba-block | -| ---------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 tool), autoCombo, cache — vitest runner | Oo | -| `test:vitest:ui` | Mga test ng UI component — vitest runner | **Blocking** — tahasang ibinukod sa `vitest.config.ts` ang dati nang mga kabiguan; pinabibigo ng mga bagong kabiguan ang job | +| Suite | Bine-validate | Pag-block | +| ---------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 tool), autoCombo, cache — vitest runner | Oo | +| `test:vitest:ui` | Mga UI component test — vitest runner | **Nagba-block** — tahasang hindi isinasama sa `vitest.config.ts` ang mga dati nang failure; pinababagsak ng mga bagong failure ang job | ### Mga nightly workflow (naka-iskedyul, advisory) -Tumatakbo ang mga ito ayon sa cron schedule (at `workflow_dispatch`), at hindi kailanman sa mga PR. Advisory ang lahat ng ito. +Tumatakbo ang mga ito ayon sa cron schedule (at `workflow_dispatch`), hindi kailanman sa mga PR. Advisory ang lahat. -| Workflow | Bine-validate | Pagba-block | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `nightly-property` | Mga fast-check property test na may random seed + mataas na run count | **Advisory** | -| `nightly-resilience` | Heap-growth gate, chaos fault-injection, k6 load/soak | **Advisory** | -| `nightly-llm-security` | Promptfoo injection guard (block mode) + garak probe (nilalaktawan kapag walang provider secret) | **Advisory** | -| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) laban sa live na OmniRoute gamit ang `docs/openapi.yaml` — inilalantad ang mga paglabag sa spec / hindi nahawakang 500 (Fase 8 B.4) | **Advisory** | -| `nightly-mutation` | Stryker mutation-testing score sa mabilis na unit lane — inilalantad ng mga surviving mutant ang mahihinang assert | **Advisory** | -| `nightly-compat` | Node engine compatibility matrix sa lahat ng sinusuportahang saklaw ng `engines.node` | **Advisory** | +| Workflow | Bine-validate | Pag-block | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `nightly-property` | Mga fast-check property test na may random seed + mataas na run count | **Advisory** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Advisory** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + mga garak probe (nilalaktawan kapag walang provider secret) | **Advisory** | +| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) laban sa live na OmniRoute gamit ang `docs/openapi.yaml` — inilalantad ang mga paglabag sa spec / mga hindi nahawakang 500 (Yugto 8 B.4) | **Advisory** | +| `nightly-mutation` | Stryker mutation-testing score sa mabilis na unit lane — inilalantad ng mga nakaligtas na mutant ang mahihinang assert | **Advisory** | +| `nightly-compat` | Node engine compatibility matrix sa lahat ng sinusuportahang range ng `engines.node` | **Advisory** | --- @@ -535,43 +538,43 @@ connection error at hindi kailanman nakasisira ng mga session. --- -## Backlog ng Rasyonalisasyon (pagsusuri ng ROI — Yugto 9 Alon 3) +## Backlog ng Rasyonalisasyon (pagsusuri ng ROI — Fase 9 Onda 3) -Itinugma ang imbentaryong ito sa `ci.yml` noong 2026-06-17 (hindi naisama sa naunang bersyon ang +Itinugma ang imbentaryong ito sa `ci.yml` noong 2026-06-17 (hindi naisama sa nakaraang bersyon ang `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Natukoy sa pagsusuri ng ROI ng pinagkatugmang hanay -ang mga sumusunod na kandidato para sa rasyonalisasyon. **Ang mga pagsasanib ay mekanikal na -pagbabago sa CI; ang mga paglipat/pag-aalis ay mga desisyon sa patakaran na nakalaan sa operator.** Wala pa -sa mga nasa ibaba ang inilalapat. +`check:codeql-ratchet`, `check:pr-evidence`). Natukoy sa pagsusuri ng ROI sa itinugmang hanay +ang mga sumusunod na kandidato para sa rasyonalisasyon. **Ang mga pagsasanib ay mga mekanikal na +pagbabago sa CI; ang mga paglipat/pag-aalis ay mga desisyon sa patakaran na nakalaan para sa operator.** Wala pa +sa mga nasa ibaba ang nailalapat. **Hindi rin nakadokumento sa itaas** (advisory, mahinang signal): ang `docs-lint` job -(markdownlint + Vale, ang buong job ay `continue-on-error`) at ang mga standalone na scanner workflow na +(markdownlint + Vale, buong job ay `continue-on-error`) at ang mga nakahiwalay na scanner workflow na `semgrep.yml` / `codeql.yml` / `scorecard.yml`. Nasa -`quality-baseline.json` ang `semgrepFindings: 0` ngunit hindi ito nakakabit sa isang blocking ratchet sa `ci.yml` — kasalukuyang -ulila ang metric na ito. - -### Pagsasama / pag-aalis ng duplikasyon (mekanikal, mas mababang panganib) - -Sinuri ang bawat kandidato laban sa aktuwal na estado ng gate noong 2026-06-17 (magtiwala ngunit tiyakin); -lumabas na may itinatagong utang ang ilang "halatang" pagsasama at **hindi** maaaring direktang ipalit nang maayos. - -- **Dalawang beses tumatakbo ang `check:docs-sync`** — standalone sa `lint` job at muli sa loob ng `check:docs-all` (`docs-sync-strict`) at sa husky pre-commit hook. ✅ **TAPOS NA** — inalis ang standalone na invocation sa `lint`. -- **Pag-scan ng CVE** — ❌ **HINDI maayos na pagsasama.** Agad na nabibigo ang `audit:deps` sa anumang high/critical na CVE; nabibigo lamang ang `check:vuln-ratchet` (osv) kapag may _paglala_ kumpara sa baseline (kasalukuyang 1 MODERATE). Magkaiba ang semantics — mawawala ang absolute high/critical gate kapag inalis ang `audit:deps`. Panatilihin ang dalawa. -- **Pagtukoy ng cycle** — ❌ **HINDI maayos na pagsasama.** Nag-uulat ang `check:circular-deps` (dpdm) ng **91 cycle** (ito ang dahilan kung bakit advisory ito); hindi ito maaaring gawing blocking nang hindi muna nireresolba ang mga iyon, at mas malawak ang saklaw nito kaysa sa maayos at piniling `check:cycles`. Panatilihing blocking ang `check:cycles`; hiwalay na backlog ang paglutas sa 91 dpdm cycle. -- **Complexity** — ✅ **TAPOS NA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): isang ESLint walk, binibilang ayon sa ruleId upang manatiling magkahiwalay ang cyclomatic+max-lines at cognitive baseline; nananatili ang indibidwal na `check:complexity` / `check:cognitive-complexity` para sa lokal na `--update`. -- **Anti-hallucination ng `/api`** — ✅ **TAPOS NA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): isang FS inventory ng `src/app/api`, patuloy na magkahiwalay na nag-uulat ang openapi-routes + docs-symbols; nananatili ang mga indibidwal na check para sa mga lokal na pagpapatakbo. -- **Tumatakbo ang `check:node-runtime` sa 11 job** — ⚠️ **mababang ROI.** Bawat isa ay hiwalay na runner at <1s ang check; kabuuang matitipid ay ~10s, kapalit ng pagkawala ng isang murang guard sa bawat job. Hindi sulit ang pagbabago. -- **`typecheck:noimplicit:core` sa CI lint** — ✅ **inalis sa lint job** (dati itong advisory na `continue-on-error`); ang blocking na type surface ay `typecheck:core` + `check:type-coverage`. Pinanatili ang lokal na script. - -### Ilipat / pagpasyahan (patakaran ng operator) - -- `check:openapi-security-tiers` (advisory) — ❌ **HINDI maaaring ilipat nang maayos.** Nag-e-exit ito nang 0 ngunit nagbababala na walang annotation na `x-loopback-only: true` ang ilang `traffic-inspector` route sa ilalim ng `LOCAL_ONLY_API_PREFIXES`. Kailangang idagdag muna ang mga annotation na iyon sa `openapi.yaml` bago ito ipatupad. -- `typecheck:noimplicit:core` (advisory) — malaking bahagi nito ay nasasaklaw na ng blocking na `check:type-coverage` ratchet. Ilipat sa isang ratchet o alisin ang redundant na ikalawang `tsc` pass. -- `test:vitest:ui` (ngayon ay **blocking**) — tahasang hindi isinasama sa `vitest.config.ts` ang mga dati nang failure gamit ang mga tracking comment na `// #8618`; magpapabigo sa job ang mga bagong failure. -- `check:secrets` (gitleaks, blocking ratchet na nakapirmi sa 3 dokumentadong false-positive) — ilagay sa allowlist ang 3 upang maging 0, o ibaba sa advisory. Nag-o-overlap sa native secret-scanning ng GitHub + `check:public-creds`. -- `check:pr-evidence` (blocking, nagge-grep ng prose sa PR body) — mataas ang panganib ng false-positive; hihina ang pagpapatupad ng Hard Rule #18 kapag inalis, kaya tunay itong desisyon sa patakaran. -- `semgrep` (advisory standalone) — nag-o-overlap sa CodeQL para sa mga OWASP family; ikabit ang baseline nito sa isang ratchet o alisin. +`quality-baseline.json` ang `semgrepFindings: 0` ngunit hindi ito nakakabit sa isang blocking ratchet sa `ci.yml` — ang metric ay +kasalukuyang walang kaugnay na pagpapatupad. + +### Pagsasanib / pag-aalis ng duplikasyon (mekanikal, mas mababang panganib) + +Bawat kandidato ay pinatunayan laban sa aktuwal na estado ng gate noong 2026-06-17 (magtiwala ngunit magsuri); +lumabas na may nakatagong debt ang ilang "halatang" pagsasanib at ang mga ito ay **hindi** malinis na pamalit. + +- **Dalawang beses tumatakbo ang `check:docs-sync`** — nakahiwalay sa `lint` job at muli sa loob ng `check:docs-all` (`docs-sync-strict`) at sa husky pre-commit hook. ✅ **TAPOS NA** — inalis ang nakahiwalay na `lint` invocation. +- **Pag-scan ng CVE** — ❌ **HINDI malinis na pagsasanib.** Agad na nabibigo ang `audit:deps` sa anumang high/critical na CVE; nabibigo lamang ang `check:vuln-ratchet` (osv) kapag may _regression_ kumpara sa baseline (kasalukuyang 1 MODERATE). Magkaiba ang semantics — mawawala ang absolute high/critical gate kapag inalis ang `audit:deps`. Panatilihin ang dalawa. +- **Pagtukoy ng cycle** — ✅ **TAPOS NA** (#15159 G-01/G-02). Tinawag ng lumang teksto rito ang `check:cycles` na "ang green, curated" gate at binigyang-katwiran ang pagpapanatili rito bilang blocking dahil nag-ulat ang `check:circular-deps` (dpdm) ng 91 cycle. Ang pagiging green na iyon ay isang **false green**: nag-scan ang `check:cycles` ng 5 subdirectory (450 file), itinugma lamang ang static na `import|export … from`, at inalis ang bawat `@/` at `@omniroute/open-sse/` specifier, kaya hindi nito makita ang mga dynamic-import + alias cycle na nangingibabaw sa repo. Naayos na: nilalakbay na ngayon ng gate ang `src` + `open-sse` (5023 file), kinokolekta ang mga specifier mula sa TypeScript AST (kaya binibilang ang `import("…")` ngunit hindi ang type-position na `typeof import("…")`), at nire-resolve ang `paths` ng tsconfig. Nakakahanap ito ng **14** na cycle, hindi 0. Dahil hindi maaayos ang 14 na dati nang cycle sa isang gate PR, isa na ngayong **ratchet** ang `check:cycles` (`--ratchet`, ceiling na `metrics.cycles.value = 14` sa `quality-baseline.json`, `direction: down`) — bina-block nito ang anumang _regression_ at maaari lamang bumaba ang bilang. Pinapatakbo ng CI ang `npm run check:cycles:ratchet`. Kasabay ng **A-01** ang burn-down. Nananatiling advisory ang `check:circular-deps` (dpdm) bilang mas malawak na pangalawang opinyon. +- **Complexity** — ✅ **TAPOS NA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): isang ESLint walk, nagbibilang ayon sa ruleId upang manatiling magkahiwalay ang cyclomatic+max-lines at cognitive baseline; nananatili ang indibidwal na `check:complexity` / `check:cognitive-complexity` para sa lokal na `--update`. +- **`/api` anti-hallucination** — ✅ **TAPOS NA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): isang FS inventory ng `src/app/api`, patuloy na magkahiwalay ang pag-uulat ng openapi-routes + docs-symbols; nananatili ang mga indibidwal na check para sa mga lokal na pagpapatakbo. +- **Tumatakbo ang `check:node-runtime` sa 11 job** — ⚠️ **mababang ROI.** Hiwalay na runner ang bawat isa at <1s ang check; kabuuang matitipid ay ~10s, kapalit ng pagkawala ng murang guard sa bawat job. Hindi sulit ang pagbabago. +- **`typecheck:noimplicit:core` sa CI lint** — ✅ **inalis sa lint job** (dating advisory na `continue-on-error`); ang blocking type surface ay `typecheck:core` + `check:type-coverage`. Pinanatili ang lokal na script. + +### Ilipat / pagpasiyahan (patakaran ng operator) + +- `check:openapi-security-tiers` (advisory) — ❌ **HINDI malinis na maililipat.** Nag-e-exit ito nang 0 ngunit nagbababala na walang `x-loopback-only: true` annotation ang ilang `traffic-inspector` route sa ilalim ng `LOCAL_ONLY_API_PREFIXES`. Kailangang idagdag muna ang mga annotation na iyon sa `openapi.yaml` bago ito ipatupad. +- `typecheck:noimplicit:core` (advisory) — halos saklaw na ng blocking na `check:type-coverage` ratchet. Ilipat sa isang ratchet o alisin ang kalabisan na pangalawang `tsc` pass. +- `test:vitest:ui` (ngayon ay **blocking**) — tahasang ibinukod sa `vitest.config.ts` ang mga dati nang failure gamit ang `// #8618` tracking comment; pinababagsak ng mga bagong failure ang job. +- `check:secrets` (gitleaks, blocking ratchet na naka-freeze sa 3 nakadokumentong false-positive) — i-allowlist ang 3 upang umabot sa 0, o ibaba sa advisory. Nag-o-overlap sa native secret-scanning ng GitHub + `check:public-creds`. +- `check:pr-evidence` (blocking, nagge-grep ng prose sa PR body) — mataas ang panganib ng false-positive; pinahihina ang pagpapatupad ng Hard Rule #18 kapag inalis, kaya isa itong tunay na desisyon sa patakaran. +- `semgrep` (nakahiwalay na advisory) — nag-o-overlap sa CodeQL para sa mga OWASP family; ikabit ang baseline nito sa isang ratchet o alisin. --- diff --git a/docs/i18n/phi/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/phi/docs/compression/COMPRESSION_GUIDE.md index df1819e57daf..8819d7c26488 100644 --- a/docs/i18n/phi/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/phi/docs/compression/COMPRESSION_GUIDE.md @@ -468,8 +468,7 @@ tagubilin ang ini-inject sa halip na Ingles. Sa body na may `messages`, sinusuri ng content bypass (`shouldBypassCavemanOutputMode()` sa `open-sse/services/compression/outputMode.ts`) ang huling tatlong mensahe at nilalaktawan ang mga estilo para sa buong turn kapag tumugma ang mga ito sa mga keyword nito para sa seguridad, -hindi mababawi na aksyon, paglilinaw, o pagkakasunod-sunod. Tumatakbo ang bypass ayon sa anumang -setting ng **Auto-Clarity Bypass** toggle (`cavemanOutputMode.autoClarity`) sa dashboard. +hindi mababawi na aksyon, paglilinaw, o pagkakasunod-sunod. Tumatakbo ang bypass habang naka-on ang **Auto-Clarity Bypass** toggle (`cavemanOutputMode.autoClarity`) sa dashboard, na siyang default; kapag naka-off ang toggle, ang mga napiling estilo ay may bisa rin sa mga turn na iyon. Kapag pinahintulutan ng bypass ang turn, inilalagay ng `placeSystemInstruction()` (parehong file), na hindi kailanman gumagawa ng bagong `messages[0]`, ang block sa una nitong makita sa mga sumusunod: diff --git a/docs/i18n/phi/docs/reference/ENVIRONMENT.md b/docs/i18n/phi/docs/reference/ENVIRONMENT.md index fb8257d8470d..1647adaae036 100644 --- a/docs/i18n/phi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/phi/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Mga Kinakailangang Secret -Ang mga ito ay **dapat** itakda bago ang unang pagpapatakbo. Kung wala ang mga ito, tatanggi ang application na magsimula o gagana ito gamit ang mga hindi ligtas na default. - -| Variable | Kinakailangan | Default | Source File | Paglalarawan | -| ---------------------------- | ------------------- | ---------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Oo** | _(wala)_ | `src/lib/auth` | Lumalagda/nagbe-verify sa lahat ng dashboard session cookie (JWT). Bumuo gamit ang `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Oo** | _(wala)_ | `src/lib/db/apiKeys.ts` | AES encryption key para sa mga value ng API key na nakaimbak sa SQLite. Bumuo gamit ang `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Oo** | `CHANGEME` | Bootstrap script | Itinatakda ang paunang password ng admin dashboard (tumutugma sa default ng `.env.example` — sadyang pinanatiling malinaw na hindi ligtas upang piliting baguhin ito). **Baguhin bago ang unang paggamit.** Pagkatapos mag-login, baguhin sa Dashboard → Settings → Security. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Oo** (production) | _(hindi nakatakda)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Shared secret para sa internal na Codex Responses WebSocket bridge. Ina-authenticate nito ang mga bridge request sa pagitan ng Electron/browser WS relay at OmniRoute. ⚠️ **KINAKAILANGAN sa production — kapag hindi nakatakda, tinatanggihan ang lahat ng WS bridge request.** Bumuo gamit ang `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Hindi | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Tahasang service-worker cache-busting id para sa PWA shell (#11779); una sa resolution chain. | -| `SOURCE_VERSION` | Hindi | _(hindi nakatakda)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Pangalawa sa chain — itinatakda ng mga PaaS builder (hal. Heroku-style) bilang na-deploy na commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Hindi | _(hinango)_ | `src/shared/components/PwaRegister.tsx` | Public value sa build time na ginagamit ng client upang i-register ang `/sw.js?v=…`; hinango mula sa dalawang nasa itaas, pagkatapos ay sa git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Hindi (awtomatiko) | _(awtomatiko sa bawat boot)_ | `src/server/authz/policies/management.ts` | Secret sa bawat process na nagpapatunay na nagmula ang pinagkakatiwalaang peer-IP stamp sa sariling HTTP server ng OmniRoute (`scripts/dev/peer-stamp.mjs`). Pinagkakatiwalaan lamang ng authz middleware ang locality ng request (loopback/LAN gating ng mga LOCAL_ONLY route) kapag taglay ng stamp ang token na ito. Awtomatikong binubuo sa bawat boot — iwang hindi nakatakda; i-pin lamang ito para sa mga multi-process setup na kailangang magbahagi ng stamp. | -| `OMNIROUTE_TRUSTED_PROXIES` | Hindi | _(hindi nakatakda)_ | `scripts/dev/peer-stamp.mjs` | Mga IP o CIDR range ng reverse proxy na pinaghihiwalay ng kuwit at maaaring pagkatiwalaan ng IP allow/deny list para sa `X-Forwarded-For` / `X-Real-IP`, bukod pa sa loopback, mga private-network address, at Cloudflare edge, na palaging pinagkakatiwalaan. Kailangan lamang para sa proxy na nasa iba pang public address; kung hindi, ang sariling address ng proxy ang sinusuri ng filter. | +Ang mga hindi nakatakda na secret na minarkahang **Oo** ay awtomatikong nabubuo sa unang pagpapatakbo (`JWT_SECRET`, `API_KEY_SECRET`) at iniipon sa `server.env` sa data directory — itakda ang mga ito nang tahasan kapag ephemeral ang directory na iyon. Ang eksepsyon ay ang `OMNIROUTE_WS_BRIDGE_SECRET`: hindi ito iniipon sa `server.env` — gumagawa ang mga server entrypoint ng ephemeral na value kada boot na magagamit lamang ng in-process bridge, kaya kailangang itakda ito nang tahasan ng mga external bridge client. + +| Variable | Kinakailangan | Default | Source File | Paglalarawan | +| ---------------------------- | ------------------- | ---------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Oo** | _(wala)_ | `src/lib/auth` | Lumalagda/nagbe-verify sa lahat ng dashboard session cookie (JWT). Bumuo gamit ang `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Oo** | _(wala)_ | `src/lib/db/apiKeys.ts` | AES encryption key para sa mga value ng API key na nakaimbak sa SQLite. Bumuo gamit ang `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Hindi | _(hindi nakatakda)_ | `src/lib/auth/managementPassword.ts` | Itinatakda ang password ng dashboard sa pag-startup habang wala pang naka-save; hindi na ito pinapansin kapag may naka-save nang password. Kinokopya ng mga npm at source install ang `.env.example` patungo sa `.env`, kaya nagsisimula ang mga ito gamit ang pampublikong kilalang placeholder nito na `CHANGEME`: palitan ito bago ang unang boot, o itakda ang `INITIAL_PASSWORD=` (walang laman) sa `.env` na iyon upang likhain ang password sa onboarding wizard sa unang pagbisita, gaya ng ginagawa ng hindi nakatakda na value (ang default ng Docker image). Baguhin ang naka-save na password sa Dashboard → Settings → Security, o patakbuhin ang `omniroute reset-password` (`node bin/reset-password.mjs` sa source checkout) gamit ang `DATA_DIR` na nakatakda sa data directory ng server. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Oo** (production) | _(hindi nakatakda)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Shared secret para sa internal na Codex Responses WebSocket bridge. Ina-authenticate nito ang mga bridge request sa pagitan ng Electron/browser WS relay at OmniRoute. ⚠️ **KINAKAILANGAN sa production — kapag hindi nakatakda, tinatanggihan ang lahat ng WS bridge request.** Bumuo gamit ang `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Hindi | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Tahasang service-worker cache-busting id para sa PWA shell (#11779); una sa resolution chain. | +| `SOURCE_VERSION` | Hindi | _(hindi nakatakda)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Pangalawa sa chain — itinatakda ng mga PaaS builder (hal. Heroku-style) bilang na-deploy na commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Hindi | _(hinango)_ | `src/shared/components/PwaRegister.tsx` | Public value sa build time na ginagamit ng client upang i-register ang `/sw.js?v=…`; hinango mula sa dalawang nasa itaas, pagkatapos ay sa git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Hindi (awtomatiko) | _(awtomatiko sa bawat boot)_ | `src/server/authz/policies/management.ts` | Secret sa bawat process na nagpapatunay na nagmula ang pinagkakatiwalaang peer-IP stamp sa sariling HTTP server ng OmniRoute (`scripts/dev/peer-stamp.mjs`). Pinagkakatiwalaan lamang ng authz middleware ang locality ng request (loopback/LAN gating ng mga LOCAL_ONLY route) kapag taglay ng stamp ang token na ito. Awtomatikong binubuo sa bawat boot — iwang hindi nakatakda; i-pin lamang ito para sa mga multi-process setup na kailangang magbahagi ng stamp. | +| `OMNIROUTE_TRUSTED_PROXIES` | Hindi | _(hindi nakatakda)_ | `scripts/dev/peer-stamp.mjs` | Mga IP o CIDR range ng reverse proxy na pinaghihiwalay ng kuwit at maaaring pagkatiwalaan ng IP allow/deny list para sa `X-Forwarded-For` / `X-Real-IP`, bukod pa sa loopback, mga private-network address, at Cloudflare edge, na palaging pinagkakatiwalaan. Kailangan lamang para sa proxy na nasa iba pang public address; kung hindi, ang sariling address ng proxy ang sinusuri ng filter. | ### Mga Command sa Pagbuo @@ -217,38 +217,38 @@ Gumagamit ang OmniRoute ng **SQLite** (sa pamamagitan ng `better-sqlite3`) para ## 4. Seguridad at Pagpapatunay -| Variable | Default | Source File | Paglalarawan | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt na isinasama sa mga identifier ng hardware para sa fingerprinting ng makina. Baguhin sa bawat deployment para sa isolation. | -| `OMNIROUTE_CLI_SALT` | _(hindi nakatakda = random na salt sa bawat pag-install na pinapanatili sa `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt para sa pagbuo ng lokal na CLI auth token. Kapag itinakda ang value na ito, iniikot nito ang lahat ng CLI token sa makina at palaging inuuna kaysa sa pinanatiling salt. Tingnan ang `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Itinatakda ang flag na `Secure` sa mga session cookie. **Dapat ay `true`** kapag tumatakbo sa likod ng HTTPS. | -| `REQUIRE_API_KEY` | `false` | API middleware | Kapag `true`, dapat magsama ang lahat ng `/v1/*` proxy request ng wastong API key. **Hindi kinokontrol ng flag na ito ang `GET /v1/models`**, na sa halip ay sumusunod sa postura ng pag-login sa dashboard (`requireAuthForModels`) — kaya ang `401` mula sa `/v1/models` ay HINDI nangangahulugang protektado ang inference. Tingnan ang `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Nagbibigay-daan na maipakita ang buong value ng API key sa Dashboard UI. Maaaring i-configure mula sa Dashboard Feature Flags; panganib sa seguridad sa mga shared instance. | -| `NO_LOG_API_KEY_IDS` | _(walang laman)_ | `src/lib/compliance/index.ts` | Mga API key ID na pinaghihiwalay ng kuwit na lumalaktaw sa pag-log ng request (pagsunod sa GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(hindi nakatakda = walang limitasyon)_ | `src/shared/utils/apiKeyPolicy.ts` | Fallback na pang-araw-araw na badyet ng request na inilalapat sa mga API key na null ang column na `rate_limits`. Hindi nakatakda o walang laman: walang implicit na limitasyon (#2289, #11017). Pareho rin ang `0` (walang limitasyon). Ang positibong integer na N ay nagbibigay-daan sa N/araw, 5N/linggo, 20N/buwan. Ang mga maling-format na value na hindi walang laman ay bumabalik sa legacy na mga window na 1000/araw, 5000/linggo, 20000/buwan. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Pinakamalaking pinapayagang laki ng request body. Tinatanggihan ang mga payload na lumalampas sa limitasyong ito. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ang mga aktuwal na request body na nasa o lampas sa threshold na ito ay kumukuha ng atomic na process-local heavyweight admission lease bago ang pag-parse ng JSON (BYTE path, kabilang ang `POST /v1/responses`). Parehong healthy-headroom escape ng [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) tulad ng structure-heavy; nililimitahan pa rin ng `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) upang hindi na bumalik ang [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Mahigpit na limitasyon ng chat route na ipinapatupad batay sa mga byte na nabasa habang may hangganan ang ingestion, kabilang ang mga request na walang, may invalid, o may mapanlinlang na `Content-Length`; makakatanggap ng `413` ang lalampas. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(hindi nakatakda — walang limitasyon sa bilang ng request)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** umiiral lamang ang legacy na limitasyong ito sa BILANG ng request kapag tahasang itinakda. Kapag hindi nakatakda (ang default), ang admission ng heavyweight chat ay sa halip kinokontrol ng `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — isang awtomatikong hinangong BYTE budget na tinukoy ayon sa aktuwal na limitasyon ng memory ng proseso sa **isang proseso** (isang V8 heap). Dalawang magkakasabay na `/v1/responses` na may ~750k token ang nagpapabagsak sa ~12 GiB na mga heap (#7849) — isang babala sa **memory budget**, hindi isang mahigpit na maximum ng produkto na 2. MAAARING tumanggap ang isang maayos na proseso (heap na mas mababa sa shed ratio) ng mas marami pang magkakasabay na mahahabang `/v1/responses` sa pamamagitan ng `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Ang sampu-sampung mahahabang SSE client (40–50) ay nakadepende sa heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, hindi “maximum na 2”. Ang basta-bastang pagtataas nito upang “gamitin ang host” ay muling magdudulot ng #7849. Paramihin ang mga **heap** gamit ang **N magkakahiwalay na `DATA_DIR`** (#11024); huwag kailanman gumamit ng `replicas>1` sa iisang SQLite file. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(awtomatikong hinango)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** override para sa awtomatikong hinangong ingest byte budget (25% ng mas mahigpit na limitasyon ng memory ng V8/cgroup na hinati sa 8x transient amplification). Ang mga hinango at tahasang itinakdang value ay nililimitahan sa 8 MiB–2 GiB. Agad na mabibigo ang body na mas malaki kaysa sa epektibong budget gamit ang `413 body_exceeds_budget`; ang agawan sa pagitan ng mga body na maaari namang isa-isang pagsilbihan ay mananatiling maaaring subukang muli gamit ang `503`. Ang 40–50 magkakasabay na mahahabang SSE client ay nakadepende sa budget na ito + heap, hindi sa isang mahigpit na “maximum na 2”. Basahin ang `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` sa `/api/monitoring/health` bago mag-tune. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Heap-pressure shed ratio (`heapUsed / heap_size_limit`) para sa heavyweight admission na BYTE at STRUCTURE (#10183, #10268, #10437). Ang isang magkakasabay na heavyweight request na lumampas sa `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ay ise-shed lamang gamit ang maaaring subukang muling `503` kapag ang heap ay NASA ratio na ito o mas mataas din; sa isang maayos na heap, tatanggapin ito sa halip sa pamamagitan ng healthy-headroom. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (default na `1`) | `src/shared/middleware/chatBodyAdmission.ts` | May hangganang karagdagang kapasidad para sa fast path ng maayos na heap (#10437) sa **parehong** STRUCTURE at BYTE (`admitChatRequest`, kabilang ang mga body na ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Kung wala ang hangganang ito, nilalampasan ng bawat abala ngunit may maayos na heap na request ang admission nang walang maximum. Kapag ganito karaming magkakasabay na lease ang aktibo sa pamamagitan ng healthy-heap path, ang mga susunod na abalang request ay dadaan sa PAREHONG bounded-wait/shed path na ginagamit kapag may aktuwal na heap pressure. Ganap na dini-disable ng `0` ang bypass. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Bilang ng mensahe na nag-uuri sa isang chat request bilang heavyweight kahit mas mababa sa byte threshold ang body nito. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Bilang ng tool na nag-uuri sa isang chat request bilang heavyweight kahit mas mababa sa byte threshold ang body nito. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konserbatibong pagtatantiya ng token batay sa laki ng string na nag-uuri sa isang request bilang heavyweight; isa itong proxy ng admission cost, hindi ang tokenization sa pagsingil ng provider. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (naka-disable) | `src/shared/middleware/chatBodyAdmission.ts` | Opsyonal at tahasang pinapaganang limitasyon sa history ng chat. Naka-disable bilang default: patakaran sa deployment ang bilang ng mensahe, hindi pangkalahatang katangian ng isang request, at ang paglalagay ng limitasyon dito ay tumatanggi sa mga pag-uusap gamit ang terminal na `413` bago pa mapagsilbi ang mga ito ng compression pipeline. Nililimitahan ang paglaki ng heap ng `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` at ng heap-pressure shed. Magtakda ng positibong value sa mga deployment na limitado ang memory at nangangailangan ng mahigpit na maximum; makakatanggap ang lalampas ng structured compact-required na `413`. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Mahigpit na limitasyon para sa isang non-streaming upstream response na ganap na bina-buffer sa memory. Kapag lumampas dito, kakanselahin ang upstream reader at mabilis na mabibigo ang request sa halip na magpalaki ng walang-hanggang string hanggang sa maubos ang heap. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Pinakamataas na bilang ng wire bytes na ipinapasa mula sa mga upstream response header. Kapag lumampas sa badyet, inaalis ang mga header na mas mababa ang priyoridad (hal., custom na `x-codex-*`, `x-oai-request-id`) upang manatili sa loob ng karaniwang limitasyon ng header ng reverse proxy. Magtakda ng mas mataas na halaga upang makapagpasa ng mas maraming upstream metadata kapalit ng mas malaking sukat ng response header. | -| `CORS_ORIGIN` | _(hindi nakatakda)_ | `src/server/cors/origins.ts` | Legacy na allowlist ng CORS para sa iisang origin. Gamitin ang `CORS_ALLOWED_ORIGINS` para sa mga bagong deployment. Ang CORS ay para lamang sa mga cross-origin na browser API client; sa halip, gumagamit ang mga authenticated na pagsusulat sa dashboard ng mga same-origin request kasama ang proteksyon laban sa CSRF na nakatali sa session. | -| `CORS_ALLOWED_ORIGINS` | _(hindi nakatakda)_ | `src/server/cors/origins.ts` | Allowlist ng CORS na pinaghihiwalay ng kuwit. Walang ipinapadalang wildcard maliban kung tahasang kino-configure ang `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Pang-development lamang na pansamantalang paraan upang i-echo ang anumang `Origin` ng browser. Huwag itong paganahin sa mga shared o production deployment. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | I-block ang mga tawag sa provider na nagta-target sa mga private/loopback/link-local na saklaw ng IP. I-disable lamang sa mga nakahiwalay na test environment. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Payagan ang mga URL ng provider na tumuturo sa mga private/local network (localhost, 192.168.x.x, 10.x.x.x, atbp.). **KINAKAILANGAN para sa mga self-hosted na provider** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Kapag `false`, tinatanggihan ng dashboard ang validation ng mga local na URL. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Payagan ang pagdaragdag/pag-validate ng mga provider sa mga local/private address (127.0.0.1, localhost, LAN, mga private range) — saklaw lamang sa provider validation path. **Default ay `true`** (local-first); itakda sa `false` upang magpatupad ng mahigpit na pag-block sa lahat maliban sa mga public address. Mananatiling naka-block ang mga cloud-metadata endpoint (169.254.169.254, metadata.google.internal) anuman ang setting. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Payagan ang mga route na `/v1/audio/*` (mga transcription, speech, translation) na gumamit ng OpenAI-compatible na provider node na naka-host sa labas ng localhost. Naka-off bilang default — binabago ng pag-route ng audio sa isang remote host ang egress identity at dapat itong maging tahasang desisyon ng operator. Palaging pinapayagan at hindi naaapektuhan ang mga loopback/private node (localhost, 127.0.0.1, 172.16-31.x). (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Payagan ang `POST /v1/rerank` (at ang loopback rerank step ng memory engine) na gumamit ng OpenAI-compatible na provider node na naka-host sa labas ng localhost — isang LAN box o Tailscale peer na nagpapatakbo ng TEI, Infinity, vLLM, atbp. Naka-off bilang default — binabago ng pag-route sa isang remote host ang egress identity at dapat itong maging tahasang desisyon ng operator. Palaging pinapayagan at hindi naaapektuhan ang mga loopback node (localhost, 127.0.0.1, 172.16-31.x). Dapat ding pumasa ang mga remote node sa outbound URL policy ng provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); hindi kailanman niruruta ang mga cloud-metadata host. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kapag naka-enable ang OIDC, i-disable ang password login upang makapag-authenticate lamang ang mga user sa pamamagitan ng OIDC Single Sign-On. Tinatanggap din ang simpleng alias na `OIDC_DISABLE_PASSWORD_LOGIN`; inuuna ang Dashboard Feature Flag na may parehong key. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Simpleng alias ng `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variable | Default | Source File | Paglalarawan | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt na isinasama sa mga identifier ng hardware para sa fingerprinting ng makina. Baguhin sa bawat deployment para sa isolation. | +| `OMNIROUTE_CLI_SALT` | _(hindi nakatakda = random na salt sa bawat pag-install na pinapanatili sa `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC salt para sa pagbuo ng lokal na CLI auth token. Kapag itinakda ang value na ito, iniikot nito ang lahat ng CLI token sa makina at palaging inuuna kaysa sa pinanatiling salt. Tingnan ang `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Itinatakda ang flag na `Secure` sa mga session cookie. **Dapat ay `true`** kapag tumatakbo sa likod ng HTTPS. | +| `REQUIRE_API_KEY` | `false` | API middleware | Kapag `true`, dapat magsama ang lahat ng `/v1/*` proxy request ng wastong API key. **Hindi kinokontrol ng flag na ito ang `GET /v1/models`**, na sa halip ay sumusunod sa postura ng pag-login sa dashboard (`requireAuthForModels`) — kaya ang `401` mula sa `/v1/models` ay HINDI nangangahulugang protektado ang inference. Tingnan ang `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Nagbibigay-daan na maipakita ang buong value ng API key sa Dashboard UI. Maaaring i-configure mula sa Dashboard Feature Flags; panganib sa seguridad sa mga shared instance. | +| `NO_LOG_API_KEY_IDS` | _(walang laman)_ | `src/lib/compliance/index.ts` | Mga API key ID na pinaghihiwalay ng kuwit na lumalaktaw sa pag-log ng request (pagsunod sa GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(hindi nakatakda = walang limitasyon)_ | `src/shared/utils/apiKeyPolicy.ts` | Fallback na pang-araw-araw na badyet ng request na inilalapat sa mga API key na null ang column na `rate_limits`. Hindi nakatakda o walang laman: walang implicit na limitasyon (#2289, #11017). Pareho rin ang `0` (walang limitasyon). Ang positibong integer na N ay nagbibigay-daan sa N/araw, 5N/linggo, 20N/buwan. Ang mga maling-format na value na hindi walang laman ay bumabalik sa legacy na mga window na 1000/araw, 5000/linggo, 20000/buwan. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Pinakamalaking pinapayagang laki ng request body. Tinatanggihan ang mga payload na lumalampas sa limitasyong ito. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ang mga aktuwal na request body na nasa o lampas sa threshold na ito ay kumukuha ng atomic na process-local heavyweight admission lease bago ang pag-parse ng JSON (BYTE path, kabilang ang `POST /v1/responses`). Parehong healthy-headroom escape ng [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) tulad ng structure-heavy; nililimitahan pa rin ng `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) upang hindi na bumalik ang [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849). | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Mahigpit na limitasyon ng chat route na ipinapatupad batay sa mga byte na nabasa habang may hangganan ang ingestion, kabilang ang mga request na walang, may invalid, o may mapanlinlang na `Content-Length`; makakatanggap ng `413` ang lalampas. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(hindi nakatakda — walang limitasyon sa bilang ng request)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** umiiral lamang ang legacy na limitasyong ito sa BILANG ng request kapag tahasang itinakda. Kapag hindi nakatakda (ang default), ang admission ng heavyweight chat ay sa halip kinokontrol ng `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — isang awtomatikong hinangong BYTE budget na tinukoy ayon sa aktuwal na limitasyon ng memory ng proseso sa **isang proseso** (isang V8 heap). Dalawang magkakasabay na `/v1/responses` na may ~750k token ang nagpapabagsak sa ~12 GiB na mga heap (#7849) — isang babala sa **memory budget**, hindi isang mahigpit na maximum ng produkto na 2. MAAARING tumanggap ang isang maayos na proseso (heap na mas mababa sa shed ratio) ng mas marami pang magkakasabay na mahahabang `/v1/responses` sa pamamagitan ng `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Ang sampu-sampung mahahabang SSE client (40–50) ay nakadepende sa heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, hindi “maximum na 2”. Ang basta-bastang pagtataas nito upang “gamitin ang host” ay muling magdudulot ng #7849. Paramihin ang mga **heap** gamit ang **N magkakahiwalay na `DATA_DIR`** (#11024); huwag kailanman gumamit ng `replicas>1` sa iisang SQLite file. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(awtomatikong hinango)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** override para sa awtomatikong hinangong ingest byte budget (25% ng mas mahigpit na limitasyon ng memory ng V8/cgroup na hinati sa 8x transient amplification). Ang mga hinango at tahasang itinakdang value ay nililimitahan sa 8 MiB–2 GiB. Agad na mabibigo ang body na mas malaki kaysa sa epektibong budget gamit ang `413 body_exceeds_budget`; ang agawan sa pagitan ng mga body na maaari namang isa-isang pagsilbihan ay mananatiling maaaring subukang muli gamit ang `503`. Ang 40–50 magkakasabay na mahahabang SSE client ay nakadepende sa budget na ito + heap, hindi sa isang mahigpit na “maximum na 2”. Basahin ang `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` sa `/api/monitoring/health` bago mag-tune. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Heap-pressure shed ratio (`heapUsed / heap_size_limit`) para sa heavyweight admission na BYTE at STRUCTURE (#10183, #10268, #10437). Ang isang magkakasabay na heavyweight request na lumampas sa `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ay ise-shed lamang gamit ang maaaring subukang muling `503` kapag ang heap ay NASA ratio na ito o mas mataas din; sa isang maayos na heap, tatanggapin ito sa halip sa pamamagitan ng healthy-headroom. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (default na `1`) | `src/shared/middleware/chatBodyAdmission.ts` | May hangganang karagdagang kapasidad para sa fast path ng maayos na heap (#10437) sa **parehong** STRUCTURE at BYTE (`admitChatRequest`, kabilang ang mga body na ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Kung wala ang hangganang ito, nilalampasan ng bawat abala ngunit may maayos na heap na request ang admission nang walang maximum. Kapag ganito karaming magkakasabay na lease ang aktibo sa pamamagitan ng healthy-heap path, ang mga susunod na abalang request ay dadaan sa PAREHONG bounded-wait/shed path na ginagamit kapag may aktuwal na heap pressure. Ganap na dini-disable ng `0` ang bypass. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Bilang ng mensahe na nag-uuri sa isang chat request bilang heavyweight kahit mas mababa sa byte threshold ang body nito. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Bilang ng tool na nag-uuri sa isang chat request bilang heavyweight kahit mas mababa sa byte threshold ang body nito. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konserbatibong pagtatantiya ng token batay sa laki ng string na nag-uuri sa isang request bilang heavyweight; isa itong proxy ng admission cost, hindi ang tokenization sa pagsingil ng provider. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (naka-disable) | `src/shared/middleware/chatBodyAdmission.ts` | Opsyonal at tahasang pinapaganang limitasyon sa history ng chat. Naka-disable bilang default: patakaran sa deployment ang bilang ng mensahe, hindi pangkalahatang katangian ng isang request, at ang paglalagay ng limitasyon dito ay tumatanggi sa mga pag-uusap gamit ang terminal na `413` bago pa mapagsilbi ang mga ito ng compression pipeline. Nililimitahan ang paglaki ng heap ng `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` at ng heap-pressure shed. Magtakda ng positibong value sa mga deployment na limitado ang memory at nangangailangan ng mahigpit na maximum; makakatanggap ang lalampas ng structured compact-required na `413`. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Mahigpit na limitasyon para sa isang non-streaming upstream response na ganap na bina-buffer sa memory. Kapag lumampas dito, kakanselahin ang upstream reader at mabilis na mabibigo ang request sa halip na magpalaki ng walang-hanggang string hanggang sa maubos ang heap. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Pinakamataas na bilang ng wire bytes na ipinapasa mula sa mga upstream response header. Kapag lumampas sa badyet, inaalis ang mga header na mas mababa ang priyoridad (hal., custom na `x-codex-*`, `x-oai-request-id`) upang manatili sa loob ng karaniwang limitasyon ng header ng reverse proxy. Magtakda ng mas mataas na halaga upang makapagpasa ng mas maraming upstream metadata kapalit ng mas malaking sukat ng response header. | +| `CORS_ORIGIN` | _(hindi nakatakda)_ | `src/server/cors/origins.ts` | Legacy na allowlist ng CORS para sa iisang origin. Gamitin ang `CORS_ALLOWED_ORIGINS` para sa mga bagong deployment. Ang CORS ay para lamang sa mga cross-origin na browser API client; sa halip, gumagamit ang mga authenticated na pagsusulat sa dashboard ng mga same-origin request kasama ang proteksyon laban sa CSRF na nakatali sa session. | +| `CORS_ALLOWED_ORIGINS` | _(hindi nakatakda)_ | `src/server/cors/origins.ts` | Allowlist ng CORS na pinaghihiwalay ng kuwit. Walang ipinapadalang wildcard maliban kung tahasang kino-configure ang `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Pang-development lamang na pansamantalang paraan upang i-echo ang anumang `Origin` ng browser. Huwag itong paganahin sa mga shared o production deployment. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Legacy na alias. Binabasa ang dashboard toggle nito (isang DB override) bago ang environment value na ito; ang `false`, `0`, `no`, o `off` sa alinman sa dalawa ay pumapatay sa mga host check sa paraang ginagawa ng `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, kahit naka-save bilang off ang dashboard toggle ng flag na iyon; sa ibang value, ang dalawang flag sa ibaba ang nagpapasya ng guard. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Pinapatay ang mga host check ng outbound URL guard, kabilang ang pag-block sa cloud-metadata (buong 169.254.0.0/16, metadata.google.internal), sa mga provider path na sumusunod sa guard mode: URL validation, model discovery, mga base URL ng provider node (kabilang ang mga remote rerank node), at ang proxy-fallback test, bukod sa iba pang provider-adjacent na outbound path tulad ng remote image/media fetch. Pinapayagan din nito ang mga private webhook target, maliban sa mga cloud-metadata/link-local endpoint, na mananatiling naka-block kahit naka-on ang flag na ito. Pinapanatili ng mga chat request na ipinapadala sa pamamagitan ng `BaseExecutor.execute()` ang cloud-metadata check nila. Gumagana ang mga local provider gamit ang mga default: pinapayagan na ng `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (default na `true`) ang mga local at LAN na URL sa mga validation, discovery, at provider-node path; ang proxy-fallback test at mga private webhook target ay tanging flag na ito lamang ang kinukonsulta at humaharang sa mga local/LAN host habang naka-off ito. Sinasapawan nito ang `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Ang value na naka-save mula sa dashboard toggle ay inuuna sa variable na ito. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Payagan ang mga URL ng provider sa mga local/private address (127.0.0.1, localhost, LAN, mga private range) para sa provider validation, model discovery, chat, at ang iba pang provider-adjacent na outbound path na pinamamahalaan ng guard mode. **Default ay `true`** (local-first): humaharang noon ang guard sa mga cloud-metadata endpoint — buong 169.254.0.0/16 kasama ang mga kilalang metadata hostname (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Itakda sa `false` para sa public-only mode, na humaharang din sa mga private at loopback host. Sinusuri ng guard ang hostname o IP literal ayon sa pagkakasulat nito, bago ang anumang DNS lookup; sa public-only mode, nagre-resolve pa ang safe-fetch wrapper ng hostname at tinatanggihan ang anumang private na sagot. Pinapatay ng `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ang mga check na ito sa mga path na nakalista sa row nito. Ang mga built-in na local provider, tulad ng LM Studio, Ollama, vLLM, Llamafile, at Triton, ay lumalaktaw sa lahat ng host check para sa key validation at chat. Ang value na naka-save mula sa dashboard toggle ay inuuna sa variable na ito. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Payagan ang mga route na `/v1/audio/*` (mga transcription, speech, translation) na gumamit ng OpenAI-compatible na provider node na naka-host sa labas ng localhost. Naka-off bilang default — binabago ng pag-route ng audio sa isang remote host ang egress identity at dapat itong maging tahasang desisyon ng operator. Palaging pinapayagan at hindi naaapektuhan ang mga loopback/private node (localhost, 127.0.0.1, 172.16-31.x). (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Payagan ang `POST /v1/rerank` (at ang loopback rerank step ng memory engine) na gumamit ng OpenAI-compatible na provider node na naka-host sa labas ng localhost — isang LAN box o Tailscale peer na nagpapatakbo ng TEI, Infinity, vLLM, atbp. Naka-off bilang default — binabago ng pag-route sa isang remote host ang egress identity at dapat itong maging tahasang desisyon ng operator. Palaging pinapayagan at hindi naaapektuhan ang mga loopback node (localhost, 127.0.0.1, 172.16-31.x). Dapat ding pumasa ang base URL ng remote node sa outbound URL policy ng provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kapag naka-enable ang OIDC, i-disable ang password login upang makapag-authenticate lamang ang mga user sa pamamagitan ng OIDC Single Sign-On. Tinatanggap din ang simpleng alias na `OIDC_DISABLE_PASSWORD_LOGIN`; inuuna ang Dashboard Feature Flag na may parehong key. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Simpleng alias ng `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Checklist ng Pagpapatibay ng Seguridad diff --git a/docs/i18n/pl/CONTRIBUTING.md b/docs/i18n/pl/CONTRIBUTING.md index b13c8ed78dbe..212719ee0bf6 100644 --- a/docs/i18n/pl/CONTRIBUTING.md +++ b/docs/i18n/pl/CONTRIBUTING.md @@ -399,22 +399,22 @@ Napisz testy jednostkowe w `tests/unit/`, obejmujące co najmniej: --- -## Lista kontrolna pull requestu +## Lista kontrolna Pull Requesta -- [ ] Testy przechodzą (`npm test`) -- [ ] Lintowanie przechodzi (`npm run lint`) +- [ ] Testy przechodzą pomyślnie (`npm test`) +- [ ] Lintowanie przechodzi pomyślnie (`npm run lint`) - [ ] Kompilacja kończy się powodzeniem (`npm run build`) - [ ] Dodano typy TypeScript dla nowych publicznych funkcji i interfejsów - [ ] Brak zakodowanych na stałe sekretów lub wartości zapasowych -- [ ] Publiczne dane uwierzytelniające dostawcy nadrzędnego osadzono za pomocą `resolvePublicCred()` (zobacz [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nigdy jako literały -- [ ] Odpowiedzi o błędach przechodzą przez `buildErrorBody()` / `sanitizeErrorMessage()` — brak nieprzetworzonych śladów stosu w treściach odpowiedzi (zobacz [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Polecenia powłoki (`exec` / `spawn`) przekazują wartości środowiska wykonawczego przez `env`, a nie przez interpolację ciągów znaków +- [ ] Publiczne dane uwierzytelniające usług nadrzędnych osadzono za pomocą `resolvePublicCred()` (zobacz [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nigdy jako literały +- [ ] Odpowiedzi błędów są przetwarzane przez `buildErrorBody()` / `sanitizeErrorMessage()` — brak surowych śladów stosu w treści odpowiedzi (zobacz [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Polecenia powłoki (`exec` / `spawn`) przekazują wartości czasu wykonywania przez `env`, a nie przez interpolację ciągów znaków - [ ] Wszystkie dane wejściowe są walidowane za pomocą schematów Zod -- [ ] Dodano **fragment** dziennika zmian w `changelog.d/{features|fixes|maintenance}/-.md` dla zmian widocznych dla użytkownika (zobacz [`changelog.d/README.md`](./changelog.d/README.md)) — **nie** edytuj bezpośrednio `CHANGELOG.md`; fragmenty są agregowane podczas wydania i nigdy nie powodują konfliktów między pull requestami -- [ ] Zaktualizowano dokumentację (jeśli dotyczy) -- [ ] Nie utworzono żadnych nowych alertów CodeQL / Secret-Scanning albo każdy z nich odrzucono z uzasadnieniem technicznym odwołującym się do odpowiedniego dokumentu w `docs/security/` -- [ ] Trasy uruchamiające procesy potomne (`/api/mcp/`, `/api/cli-tools/runtime/`) sklasyfikowano jako `isLocalOnlyPath()` w `src/server/authz/routeGuard.ts` — zobacz [Twarda reguła #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Brak wpisów końcowych `Co-Authored-By` w komunikatach commitów — commity muszą być przypisane wyłącznie do tożsamości Git właściciela repozytorium (Twarda reguła #16) +- [ ] Dla zmian widocznych dla użytkowników dodano **fragment** dziennika zmian w `changelog.d/{features|fixes|maintenance}/-.md` (zobacz [`changelog.d/README.md`](./changelog.d/README.md)) — **nie** edytuj bezpośrednio pliku `CHANGELOG.md`; fragmenty są agregowane podczas wydania i nigdy nie powodują konfliktów między Pull Requestami +- [ ] Dokumentacja została zaktualizowana (jeśli dotyczy) +- [ ] Nie utworzono nowych alertów CodeQL / Secret-Scanning lub każdy z nich został odrzucony z technicznym uzasadnieniem odwołującym się do odpowiedniego dokumentu w `docs/security/` +- [ ] Trasy uruchamiające procesy podrzędne (`/api/mcp/`, `/api/cli-tools/runtime/`) sklasyfikowano jako `isLocalOnlyPath()` w `src/server/authz/routeGuard.ts` — zobacz [Twarda reguła nr 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Brak stopek `Co-authored-by` dotyczących AI/botów w komunikatach commitów (Twarda reguła nr 16) — współpracownicy będący ludźmi, których praca została ponownie wykorzystana, są wymieniani za pomocą standardowych stopek `Co-authored-by: Name ` --- diff --git a/docs/i18n/pl/docs/architecture/QUALITY_GATES.md b/docs/i18n/pl/docs/architecture/QUALITY_GATES.md index 8f616859e34f..f35f721183b5 100644 --- a/docs/i18n/pl/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/pl/docs/architecture/QUALITY_GATES.md @@ -16,231 +16,240 @@ plan odtworzenia tego samego systemu zawiera --- -## Inwentarz bramek i profile wykonywania +## Wykaz bramek i profile wykonywania ### Dopuszczanie kandydatów -Przepływy pracy CI i Quality Gates generują po jednym stabilnym werdykcie: `Gate / CI` oraz -`Gate / Quality`. Ich wersjonowana polityka dopuszczania określa każde nadrzędne zadanie -jako wymagane lub doradcze. Każde mające zastosowanie zadanie wymagane musi zakończyć się powodzeniem: -brakujące, anulowane, pominięte, oczekujące i nieznane wyniki nie mogą ustanowić wyniku PASS. -Prawidłowa klasyfikacja docs-only lub catalog-only może sprawić, że ścieżka kodu nie będzie miała zastosowania; -wersja robocza PR nie jest akceptowanym kandydatem. Etykieta `hotfix` nie znosi wymogu przedstawienia dowodów. - -Oba przepływy pracy obejmują PR-y i wypchnięcia do gałęzi main/release, ręczne uruchomienia oraz -zdarzenia grup scalania. Wypchnięcia, ręczne uruchomienia i grupy scalania wykonują pełny zestaw. -Forki i grupy scalania używają hostowanych runnerów dla zadań, które w przeciwnym razie wybrałyby -runnery self-hosted; przed wdrożeniem należy zweryfikować wystarczającą pojemność infrastruktury hostowanej. - -Każde potwierdzenie JSON identyfikuje SHA pobranego kodu, uruchomienie przepływu pracy i próbę. -CLI odrzuca niezgodność SHA między pobranym kodem a zdarzeniem. Testy przepływu pracy wiążą członkostwo w polityce -z listą `needs` zadania werdyktu, dzięki czemu nowa lub usunięta ścieżka nie może zniknąć po cichu. -Potwierdzenia obejmują własny przepływ pracy, a nie publikację, wdrożenie ani wewnętrzne działanie -istniejącego skanera doradczego. Aktywacja obu nazw kontroli w regułach gałęzi jest -osobną zmianą administracyjną; samo dodanie tych zadań nie chroni gałęzi. - -### Inwentarz skanowania statycznego - -Wersjonowany inwentarz aliasów npm i członkostwo w skanowaniu statycznym znajdują się w +Przepływy pracy CI i Quality Gates emitują stabilne werdykty: `Gate / CI` oraz +`Gate / Quality`. Ich wersjonowana polityka dopuszczania określa każde zadanie nadrzędne +jako wymagane lub doradcze. Każde mające zastosowanie zadanie wymagane musi zakończyć się +powodzeniem: brakujące, anulowane, pominięte, oczekujące i nieznane wyniki nie mogą stanowić +podstawy do uznania statusu PASS. Prawidłowa klasyfikacja wyłącznie jako docs-only lub +catalog-only może sprawić, że ścieżka kodu nie będzie miała zastosowania; +wersja robocza PR nie jest akceptowanym kandydatem. Etykieta `hotfix` nie zwalnia +z obowiązku przedstawienia dowodów. + +Oba przepływy pracy obejmują PR-y i wypchnięcia do gałęzi main/release, uruchomienia +ręczne oraz zdarzenia grupy scalania. Wypchnięcia, uruchomienia ręczne i grupy scalania +wykonują pełny zestaw. Forki i grupy scalania używają hostowanych runnerów dla zadań, +które w innych przypadkach wybierają samodzielnie hostowane runnery; przed wdrożeniem +należy zweryfikować wystarczającą pojemność środowiska hostowanego. + +Każde pokwitowanie JSON identyfikuje pobrany SHA, uruchomienie przepływu pracy i próbę. +CLI odrzuca niezgodność SHA między pobranym kodem a zdarzeniem. Testy przepływu pracy +wiążą przynależność do polityki z listą `needs` zadania werdyktu, dzięki czemu nowa lub +usunięta ścieżka nie może niezauważenie zniknąć. Pokwitowania obejmują własny przepływ +pracy, a nie publikację, wdrożenie ani wewnętrzne działanie istniejącego skanera +doradczego. Aktywowanie obu nazw kontroli w regułach gałęzi jest osobną zmianą +administracyjną; samo dodanie tych zadań nie chroni gałęzi. + +### Wykaz skanowania statycznego + +Wersjonowany wykaz aliasów npm i przynależność do skanowania statycznego znajdują się w `config/quality/gate-manifest.json`. Uruchom `npm run check:gate-manifest`, aby zweryfikować -nazwy skryptów i dokładne polecenia względem `package.json`; dodania, usunięcia i -zmiany poleceń powodują niepowodzenie zarówno lokalnego hooka, jak i zadań klasyfikacji zmian w CI. -Alias nie jest zadaniem przepływu pracy, instancją macierzy ani przypadkiem testowym: tych wartości -nie wolno przedstawiać jako zamiennych. +nazwy skryptów i dokładne polecenia względem `package.json`; dodania, usunięcia i zmiany +poleceń powodują niepowodzenie zarówno lokalnego hooka, jak i zadań klasyfikacji zmian +w CI. Alias nie jest zadaniem przepływu pracy, instancją macierzy ani przypadkiem +testowym: tych liczebności nie wolno przedstawiać jako równoważnych. Użyj `npm run quality:scan -- --list` lub `npm run quality:scan:fast -- --list`, -aby sprawdzić wybrane aliasy bez ich wykonywania. Runner wywołuje -punkt wejścia npm, więc jego środowisko uruchomieniowe (w tym Bun, jeśli skonfigurowano) zostaje zachowane. -Manifest rejestruje aliasy spoza tych profili jako wywoływane osobno, a -polecenia konserwacyjne są zabronione w profilach skanowania tylko do odczytu. +aby sprawdzić wybrane aliasy bez ich wykonywania. Runner wywołuje punkt wejścia npm, +więc jego środowisko uruchomieniowe (w tym Bun, jeśli jest skonfigurowany) zostaje +zachowane. Manifest rejestruje aliasy spoza tych profili jako wywoływane oddzielnie, +a polecenia konserwacyjne są zabronione w profilach skanowania tylko do odczytu. Profile te obejmują wyłącznie skanowanie statyczne. Nie poświadczają testów produktu, -pokrycia, pakowania, kontroli zewnętrznych ani pełnego dopuszczenia kandydata do wydania. -Dopuszczanie w przepływie pracy korzysta z powiązanych plików `config/quality/admission-policy.json` oraz -`scripts/quality/admission-verdict.mjs`. Profile obserwatora wydań pozostają oddzielne; -ich mające zastosowanie kontrole i potwierdzenia należy sprawdzać niezależnie. Poniższy -inwentarz opisowy stanowi materiał referencyjny, a nie dowód faktycznego uruchomienia bramki. - -Skrypty znajdują się w `scripts/check/` (bramki polityk) i `scripts/quality/` (silnik mechanizmu zapadkowego). -Źródłem prawdy dla CI jest `.github/workflows/ci.yml`. - -### Szybka ścieżka PR-a wydania (`quality.yml`) - -`.github/workflows/quality.yml` uzupełnia CI dla PR-ów do gałęzi main/release, wypchnięć -do chronionych gałęzi, ręcznych uruchomień i grup scalania. PR-y korzystają z szybkich kontroli -filtrowanych według ścieżek. Trwale wyłączony zduplikowany build został usunięty; -rzeczywiste kontrole buildu, pakowania i uruchamiania pozostają w CI. - -| Zadanie | Zakres | Blokujące | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `Docs Gates (fast-path)` | PR-y dokumentacji/kodu; odwołania do dokumentacji API i docs-all | Tak | -| `Fast Quality Gates` | PR-y kodu; kontrole statyczne, typecheck, typecheck dashboardu, testy jednostkowe objęte zmianami | Tak | -| `Forgotten sibling tests` | PR-y kodu; śledzenie zmienionych modułów do konsumentów statycznych i potencjalnych testów równoległych; ścieżki barrel i dynamic-import są zgłaszane jako diagnostyka doradcza wraz z przywołanymi wyjątkami z listy dozwolonych | **Doradcze** | -| `Vitest (fast-path)` | PR-y kodu; szybki zestaw vitest | Tak | -| `Unit Tests fast-path` | PR-y kodu; 4-częściowy zestaw testów jednostkowych | Tak | -| `No new ESLint warnings` | PR-y kodu; kontrola lintowania uwzględniająca wyciszenia | Tak, w tym dla forków | -| `Merge integrity (changelog + generated skills)` | PR-y niebędące wersjami roboczymi; synchronizacja changelogu i wygenerowanych umiejętności | Tak, w tym dla forków | - -#### Raport zapomnianych testów równoległych - -`npm run check:forgotten-sibling-tests` ponownie wykorzystuje mechanizm rozpoznawania importów, na którym opiera się mapa wpływu testów. -Dla każdego zmienionego modułu produkcyjnego raportuje deterministyczne łańcuchy -`zmieniony moduł/symbol -> konsument statyczny -> potencjalny test równoległy`, gdy potencjalny -test nie występuje w różnicach pull requestu. Podsumowanie Markdown i wynik JSON są zachowywane jako -artefakt przepływu pracy `forgotten-sibling-tests` na potrzeby kalibracji przed ewentualnym wdrożeniem trybu blokującego. - -Reeksporty zbiorcze i importy dynamiczne służą wyłącznie do diagnostyki rozwiązywania zależności; nigdy nie powodują -wykrycia problemu blokującego. Zatwierdzone wyjątki znajdują się w pliku +pokrycia, pakowania, kontroli zewnętrznych ani pełnej akceptacji kandydata do wydania. +Dopuszczanie przez przepływ pracy korzysta z powiązanych plików +`config/quality/admission-policy.json` oraz +`scripts/quality/admission-verdict.mjs`. Profile obserwatora wydania pozostają +oddzielne; ich właściwe kontrole i pokwitowania należy sprawdzać niezależnie. Poniższy +wykaz opisowy stanowi materiał referencyjny, a nie dowód, że bramka faktycznie została +uruchomiona. + +Skrypty znajdują się w `scripts/check/` (bramki polityki) i `scripts/quality/` (silnik +mechanizmu zapadkowego). Źródłem prawdy dla CI jest `.github/workflows/ci.yml`. + +### Szybka ścieżka PR-ów wydania (`quality.yml`) + +`.github/workflows/quality.yml` uzupełnia CI dla PR-ów do gałęzi main/release, +wypchnięć do chronionych gałęzi, uruchomień ręcznych i grup scalania. PR-y używają +szybkich kontroli filtrowanych według ścieżek. Trwale wyłączony, zduplikowany proces +kompilacji został usunięty; rzeczywiste kontrole kompilacji, pakowania i uruchamiania +pozostają w CI. + +| Zadanie | Zakres | Blokujące | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `Docs Gates (fast-path)` | PR-y dokumentacji/kodu; odwołania do dokumentacji API i cała dokumentacja | Tak | +| `Fast Quality Gates` | PR-y kodu; kontrole statyczne, kontrola typów, kontrola typów panelu, testy jednostkowe objęte zmianami | Tak | +| `Forgotten sibling tests` | PR-y kodu; śledzenie zmienionych modułów do statycznych konsumentów i potencjalnych testów siostrzanych; ścieżki przez moduły zbiorcze i importy dynamiczne są raportowane jako diagnostyka doradcza, z odwołaniami do wyjątków na liście dozwolonych | **Doradcze** | +| `Vitest (fast-path)` | PR-y kodu; szybki zestaw vitest | Tak | +| `Unit Tests fast-path` | PR-y kodu; 4-częściowy zestaw testów jednostkowych | Tak | +| `No new ESLint warnings` | PR-y kodu; kontrola lintowania uwzględniająca wyciszenia | Tak, w tym dla forków | +| `Merge integrity (changelog + generated skills)` | PR-y inne niż wersje robocze; synchronizacja dziennika zmian i wygenerowanych umiejętności | Tak, w tym dla forków | + +#### Raport zapomnianych testów siostrzanych + +`npm run check:forgotten-sibling-tests` ponownie wykorzystuje mechanizm rozwiązywania +importów będący podstawą mapy wpływu testów. Dla każdego zmienionego modułu produkcyjnego +raportuje deterministyczne łańcuchy +`zmieniony moduł/symbol -> statyczny konsument -> potencjalny test siostrzany`, gdy +potencjalnego testu nie ma w różnicy zmian pull requestu. Podsumowanie Markdown i wynik +JSON są zachowywane jako artefakt przepływu pracy `forgotten-sibling-tests` na potrzeby +kalibracji przed wdrożeniem trybu blokującego. + +Re-eksporty zbiorcze i importy dynamiczne służą wyłącznie do diagnostyki rozwiązywania zależności; nigdy nie tworzą +ustalenia blokującego. Zweryfikowane wyjątki znajdują się w `config/quality/forgotten-sibling-allowlist.json`. Każdy wpis musi wskazywać test używający i test -kandydujący, zawierać konkretne uzasadnienie oraz łącze do zgłoszenia lub pull requestu w serwisie GitHub. Nieprawidłowo sformułowane wpisy są -domyślnie odrzucane. Wyjątki nie mogą ukrywać usuniętego testu kandydującego ani różnicy dodającej `.skip`/`.todo`; +kandydujący, podawać konkretne uzasadnienie oraz zawierać link do zgłoszenia GitHub lub pull requestu. Nieprawidłowo sformatowane wpisy są +domyślnie odrzucane. Wyjątki nie mogą ukrywać usuniętego testu kandydującego ani diffu dodającego `.skip`/`.todo`; osłabianie asercji i inne formy maskowania pozostają objęte niezależnie blokującą bramką `check:test-masking`. ### Zadanie: `lint` -Uruchamiane dla każdego PR-a do `main`. W przypadku niepowodzenia blokuje scalanie. - -| Skrypt (`npm run ...`) | Sprawdza | Blokuje | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| `check:node-runtime` | Czy wersja Node.js mieści się w obsługiwanym zakresie | Tak | -| `check:cycles` | Importy cykliczne — wszystkie moduły w `src/` i `open-sse/` | Tak | -| `check:route-validation:t06` | Obecność schematów Zod we wszystkich trasach (zasady poziomu 6) | Tak | -| `check:any-budget:t11` | Czy liczba wystąpień `@ts-expect-error // any` nie przekracza limitu (bramka poziomu 11) | Tak | -| `check:provider-consistency` | Każdy dostawca w `providers.ts` ma odpowiadający mu wpis w `providerRegistry.ts` (i odwrotnie, w ramach listy dozwolonych) | Tak | -| `check:model-lifecycle` | Trzy ręcznie utrzymywane tabele routingu pozostają spójne z zapisaną w repozytorium migawką cyklu życia (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nie przyznaje punktów żadnemu wycofanemu identyfikatorowi, do którego `REGISTRY` może kierować; każdy cel `BUILT_IN_ALIASES` znajduje się w `REGISTRY` i nie występuje w migawce wycofanych identyfikatorów; każdy wycofany identyfikator nadal obecny w `REGISTRY` jest przekierowywany lub wymieniony w `allowedRetiredInCatalog`; a żadne źródło ani cel `DEFAULT_DEGRADATION_MAP` nie jest oznaczone jako wycofane w tej migawce. Nie dowodzi to, że model jest obecnie obsługiwany przez działającą usługę nadrzędną. W trybie offline — porównuje z `config/quality/model-lifecycle.json`, odświeżanym ręcznie za pomocą `npm run quality:refresh-model-lifecycle` (wymaga sieci; nie jest podłączone do CI). `allowedRetiredInCatalog` jest mechanizmem stopniowego ograniczania: wpis należy dodawać wyłącznie wraz ze zgłoszeniem śledzącym. | Tak | -| `check:fetch-targets` | Każde `fetch("/api/...")` w klienckim kodzie `src/` wskazuje na rzeczywisty plik `route.ts` | Tak | -| `check:deps` | Wszystkie zależności możliwe do zainstalowania za pomocą `npm install` ze wszystkich plików `package.json` w repozytorium znajdują się w `dependency-allowlist.json`; nowe nieprzypięte pakiety lub pakiety wykorzystujące literówki w nazwach są oznaczane | Tak | -| `audit:deps` | `npm audit` (katalog główny + electron) — brak ostrzeżeń o wysokim/krytycznym poziomie istotności (częściowo pokrywa się z `check:vuln-ratchet` systemu osv; zobacz Rejestr zadań racjonalizacyjnych) | Tak | -| `check:lockfile` | Integralność pliku `package-lock.json` — rejestr https, skróty integralności, brak nadpisań hosta | Tak | -| `check:licenses` | Lista dozwolonych licencji SPDX dla zależności produkcyjnych | Tak | -| `check:tracked-artifacts` | Brak artefaktów kompilacji / zatwierdzonych dowiązań symbolicznych `node_modules` (uruchamiane również przez husky przed zatwierdzeniem; kontrola przed wypchnięciem jest celowo uproszczona — #6716) | Tak | -| `check:ai-attribution` | Brak stopki `Co-Authored-By` wskazującej na AI/bota ani stopki dotyczącej generowania przez AI w commitach, tytule lub treści PR — reguła bezwzględna nr 16 (w pętli szybkich kontroli `quality.yml` dla PR→`release/**` — odczytuje dane zdarzenia, nie wykonuje żadnej operacji poza PR — oraz w kroku wykonywanym wyłącznie dla PR w kontroli lintowania `ci.yml` dla PR→`main`; także w hooku husky `commit-msg`; współautorzy będący ludźmi są dozwoleni; #14436) | -| `check:vitest-exclusions` | Każde wykluczenie Vitest wskazuje zgłoszenie śledzące i znajduje się w `config/quality/vitest-exclusions.json` (#13204) | Tak | -| `check:file-size` | Żaden plik źródłowy nie przekracza limitu określonego dla danego rozszerzenia (mechanizm zapadkowy: duże pliki zamrożone na liście `frozen`) | Tak | -| `check:error-helper` | Odpowiedzi o błędach w executorach/handlerach używają `buildErrorBody()` / `sanitizeErrorMessage()` (reguła bezwzględna nr 12) | Tak | -| `check:migration-numbering` | Pliki migracji SQL są numerowane sekwencyjnie, bez luk ani duplikatów | Tak | -| `check:public-creds` | Brak jawnie zapisanych wartości OAuth `client_id`/`client_secret` lub kluczy Firebase Web poza plikiem `publicCreds.ts` (Twarda reguła nr 11) | Tak | -| `check:db-rules` | Brak surowego SQL poza modułami `src/lib/db/`; brak importów zbiorczych z `localDb.ts` (Twarde reguły nr 2 i 5) | Tak | -| `check:known-symbols` | Wykonawcy dostawców, strategie routingu i translatory zarejestrowane w odpowiednich tabelach wysyłania są zgodne z plikami na dysku — bez osieroconych ani niezadeklarowanych symboli | Tak | -| `check:route-guard-membership` | Każda trasa uruchamiająca proces potomny jest sklasyfikowana przez `isLocalOnlyPath()` (Twarde reguły nr 15 i 17) | Tak | -| `check:test-discovery` | Każdy plik `*.test.ts` / `*.spec.ts` w repozytorium jest wykrywany przez co najmniej jeden mechanizm uruchamiania testów (mechanizm zapadkowy: lista osieroconych plików w `test-discovery-baseline.json` może się tylko skracać) | Tak | -| `check:agent-skills-sync` | Wygenerowane artefakty agent-skills są zgodne z katalogiem źródłowym (brak rozbieżności) | -| `check:provider-asset-provenance` | Logotypy/zasoby dostawców mają zarejestrowany wpis dotyczący pochodzenia | -| `lint:json` | Pliki konfiguracyjne JSON są poprawnie parsowane i spełniają reguły lintowania repozytorium | -| `typecheck:core` | Kompilacja TypeScript bez błędów (tylko ostrzeżenia doradcze) | Tak | -| `typecheck:noimplicit:core` | Rygorystyczne `noImplicitAny` — rozwiązanie przyszłościowe; wiele istniejących miejsc wywołań nadal wymaga adnotacji | **Doradcze** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` ograniczone do `src/app/(dashboard)/**` (#7033) — opracowana lista dozwolonych 27 plików w `typecheck:core` nie obejmuje żadnych plików TSX panelu, a `next build` również nigdy nie sprawdza ich typów (`next.config.mjs` ustawia `ignoreBuildErrors: true`), przez co regresje związane z osieroconymi identyfikatorami (#6625/#6909) pozostawały niewidoczne dla CI. Różnice są porównywane z zamrożoną wartością bazową liczby błędów dla poszczególnych plików i kodów TS (`config/quality/dashboard-typecheck-baseline.json`, ten sam wzorzec wymuszania aktualności co w `check:known-symbols`) — tylko NOWE błędy wykraczające poza liczbę bazową powodują niepowodzenie bramki; po naprawieniu istniejącego wcześniej błędu należy obniżyć wartość bazową za pomocą `--update`. | Tak | +Uruchamiane dla każdego PR do `main`. W przypadku niepowodzenia blokuje scalenie. + +| Skrypt (`npm run ...`) | Sprawdza | Blokujący | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- | +| `check:node-runtime` | Wersja Node.js mieści się w obsługiwanym zakresie | Tak | +| `check:cycles` | Importy cykliczne w całych katalogach `src/` + `open-sse/` (na podstawie AST, z rozwiązanymi `paths` z tsconfig). Tryb podstawowy = informacyjny, wyświetla cykle. `check:cycles:ratchet` (uruchamiany przez CI) blokuje, gdy liczba przekracza limit `metrics.cycles` w `quality-baseline.json` — obecnie 14, `direction: down`, więc może wyłącznie maleć (#15159 G-01/G-02) | Tak (mechanizm zapadkowy) | +| `check:route-validation:t06` | Obecność schematów Zod we wszystkich trasach (zasady poziomu 6) | Tak | +| `check:any-budget:t11` | Liczba wystąpień `@ts-expect-error // any` nie przekracza budżetu (mechanizm zapadkowy poziomu 11) | Tak | +| `check:provider-consistency` | Każdy dostawca w `providers.ts` ma odpowiadający mu wpis w `providerRegistry.ts` (i odwrotnie, w obrębie listy dozwolonych) | Tak | +| `check:model-lifecycle` | Trzy ręcznie utrzymywane tabele routingu pozostają spójne z przechowywaną w repozytorium migawką cyklu życia (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nie przyznaje punktów żadnemu wycofanemu identyfikatorowi, do którego `REGISTRY` może kierować; każdy cel `BUILT_IN_ALIASES` znajduje się w `REGISTRY` i nie występuje w migawce wycofanych identyfikatorów; każdy wycofany identyfikator nadal obecny w `REGISTRY` jest przekierowywany lub wymieniony w `allowedRetiredInCatalog`; a żadne źródło ani cel `DEFAULT_DEGRADATION_MAP` nie jest oznaczone jako wycofane w tej migawce. Nie dowodzi to, że model jest obecnie obsługiwany przez działającą usługę nadrzędną. Tryb offline — porównuje z `config/quality/model-lifecycle.json`, odświeżanym ręcznie za pomocą `npm run quality:refresh-model-lifecycle` (wymaga sieci; nie jest podłączone do CI). `allowedRetiredInCatalog` to zapadka służąca do stopniowego zmniejszania liczby wpisów: wpis należy dodawać wyłącznie wraz ze zgłoszeniem śledzącym. | Tak | +| `check:fetch-targets` | Każde `fetch("/api/...")` w kodzie klienckim w `src/` wskazuje na rzeczywisty plik `route.ts` | Tak | +| `check:deps` | Wszystkie zależności możliwe do zainstalowania za pomocą `npm install` ze wszystkich plików `package.json` w repozytorium znajdują się w `dependency-allowlist.json`; nowe pakiety bez przypiętej wersji lub podszywające się pod inne są oznaczane | Tak | +| `audit:deps` | `npm audit` (katalog główny + Electron) — brak ostrzeżeń o wysokiej/krytycznej wadze (częściowo pokrywa się z OSV `check:vuln-ratchet`; zobacz Rejestr zadań racjonalizacyjnych) | Tak | +| `check:lockfile` | Integralność `package-lock.json` — rejestr HTTPS, skróty integralności, brak nadpisań hosta | Tak | +| `check:licenses` | Lista dozwolonych licencji SPDX dla zależności produkcyjnych | Tak | +| `check:tracked-artifacts` | Brak artefaktów kompilacji / zatwierdzonych dowiązań symbolicznych `node_modules` (uruchamiane również w husky pre-commit; pre-push jest celowo uproszczony — #6716) | Tak | +| `check:ai-attribution` | Brak stopki AI/bota `Co-Authored-By` ani stopki informującej o wygenerowaniu przez AI w commitach, tytule lub treści PR — Reguła bezwzględna #16 (w pętli szybkich kontroli `quality.yml` dla PR→`release/**` — odczytuje dane zdarzenia, nie wykonuje żadnych działań poza PR — oraz jako krok wykonywany tylko dla PR w zadaniu lint pliku `ci.yml` dla PR→`main`; także hook husky `commit-msg`; współautorzy będący ludźmi są dozwoleni; #14436) | +| `check:vitest-exclusions` | Każde wykluczenie Vitest wskazuje zgłoszenie śledzące i znajduje się w `config/quality/vitest-exclusions.json` (#13204) | Tak | +| `check:file-size` | Żaden plik źródłowy nie przekracza limitu dla danego rozszerzenia (mechanizm zapadkowy: duże pliki zamrożone na liście `frozen`) | Tak | +| `check:error-helper` | Odpowiedzi błędów w executorach/handlerach używają `buildErrorBody()` / `sanitizeErrorMessage()` (Reguła bezwzględna #12) | Tak | +| `check:migration-numbering` | Pliki migracji SQL są numerowane sekwencyjnie, bez luk ani duplikatów | Tak | +| `check:public-creds` | Brak wpisanych na stałe wartości OAuth `client_id`/`client_secret` ani kluczy Firebase Web poza `publicCreds.ts` (Twarda reguła nr 11) | Tak | +| `check:db-rules` | Brak surowego kodu SQL poza modułami `src/lib/db/`; brak importów zbiorczych z `localDb.ts` (Twarde reguły nr 2 i 5) | Tak | +| `check:known-symbols` | Wykonawcy dostawców, strategie routingu i translatory zarejestrowane w ich tabelach dyspozytorskich odpowiadają plikom na dysku — brak osieroconych lub niezadeklarowanych symboli | Tak | +| `check:route-guard-membership` | Każda trasa uruchamiająca proces potomny jest sklasyfikowana przez `isLocalOnlyPath()` (Twarde reguły nr 15 i 17) | Tak | +| `check:test-discovery` | Każdy plik `*.test.ts` / `*.spec.ts` w repozytorium jest wykrywany przez co najmniej jeden program uruchamiający testy (mechanizm zapadkowy: lista osieroconych plików w `test-discovery-baseline.json` może się tylko skracać) | Tak | +| `check:agent-skills-sync` | Wygenerowane artefakty umiejętności agentów są zgodne z katalogiem źródłowym (brak rozbieżności) | +| `check:provider-asset-provenance` | Logo/zasoby dostawców mają zarejestrowany wpis dotyczący pochodzenia | +| `lint:json` | Pliki konfiguracyjne JSON są poprawnie przetwarzane i spełniają reguły lintowania repozytorium | +| `typecheck:core` | Kompilacja TypeScript bez błędów (wyłącznie ostrzeżenia informacyjne) | Tak | +| `typecheck:noimplicit:core` | Ścisłe `noImplicitAny` — rozwiązanie przyszłościowe; wiele istniejących miejsc wywołań nadal wymaga adnotacji | **Informacyjne** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` ograniczone do `src/app/(dashboard)/**` (#7033) — starannie dobrana lista 27 plików dozwolonych w `typecheck:core` nie obejmuje żadnych plików TSX panelu, a `next build` również nigdy nie sprawdza ich typów (`next.config.mjs` ustawia `ignoreBuildErrors: true`), dlatego regresje związane z osieroconymi identyfikatorami w tym miejscu (#6625/#6909) były niewidoczne dla CI. Różnice są porównywane z zamrożoną wartością bazową liczby błędów według pliku i kodu TS (`config/quality/dashboard-typecheck-baseline.json`, ten sam wzorzec wymuszania aktualności co w `check:known-symbols`) — tylko NOWE błędy przekraczające bazową liczbę powodują niepowodzenie kontroli; gdy istniejący wcześniej błąd zostanie naprawiony, obniż wartość bazową za pomocą `--update`. | Tak | ### Zadanie: `quality-gate` Uruchamiane po `test-coverage`. W przypadku niepowodzenia blokuje scalenie. -| Skrypt | Sprawdza | Blokujący | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `quality:collect` | Generuje `quality-metrics.json` (liczba ostrzeżeń ESLint, pokrycie z połączonego raportu fragmentów) | Tak (przed mechanizmem zapadki) | -| `quality:ratchet` | Żadna metryka w `quality-baseline.json` nie uległa pogorszeniu (ostrzeżenia ESLint ≤ poziom bazowy; pokrycie ≥ poziom bazowy) | Tak | -| `check:duplication` | Duplikacja kodu (jscpd@4) nie przekracza poziomu bazowego z `quality-baseline.json` | Tak | -| `check:complexity` | Złożoność cyklomatyczna na poziomie pliku nie przekracza limitu (reguły ESLint `complexity` + `max-lines-per-function`) | Tak | -| `check:cognitive-complexity` | Zapadka złożoności poznawczej (`eslint-plugin-sonarjs`) — oddzielny przebieg ESLint; CI uruchamia oba razem jako pojedynczy krok `check:complexity-ratchets` | Tak | -| `check:dead-code` | Zapadka nieużywanych eksportów / plików (knip) nie pogarsza się względem poziomu bazowego | Tak | -| `check:compression-budget` | Budżet testu wydajności kompresji — minimalne progi oszczędności tokenów dla poszczególnych silników nie mogą ulec pogorszeniu | Tak | -| `check:type-coverage` | Zapadka procentowego udziału typowanego kodu (`type-coverage`) nie pogarsza się; w dużej mierze zastępuje `typecheck:noimplicit:core` | Tak | -| `check:codeql-ratchet` | Liczba otwartych alertów CodeQL nie wzrasta (odczyt przez `gh api`; bezpieczne pominięcie bez tokenu) — częstotliwość odświeżania i ręczne uruchamianie: patrz „Zapadka CodeQL” poniżej | Tak | +| Skrypt | Sprawdza | Blokujący | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `quality:collect` | Generuje `quality-metrics.json` (liczba ostrzeżeń ESLint, pokrycie ze scalonego raportu fragmentów) | Tak (przed mechanizmem ratchet) | +| `quality:ratchet` | Żadna metryka w `quality-baseline.json` nie uległa pogorszeniu (ostrzeżenia ESLint ≤ wartość bazowa; pokrycie ≥ wartość bazowa) | Tak | +| `check:duplication` | Duplikacja kodu (jscpd@4) nie przekracza wartości bazowej w `quality-baseline.json` | Tak | +| `check:complexity` | Złożoność cyklomatyczna na poziomie pliku nie przekracza limitu (reguły podstawowe ESLint `complexity` + `max-lines-per-function`) | Tak | +| `check:cognitive-complexity` | Mechanizm ratchet dla złożoności poznawczej (`eslint-plugin-sonarjs`) — osobne uruchomienie ESLint; CI uruchamia oba w ramach jednego kroku `check:complexity-ratchets` | Tak | +| `check:dead-code` | Mechanizm ratchet dla nieużywanych eksportów/plików (knip) nie wykazuje regresji względem wartości bazowej | Tak | +| `check:compression-budget` | Budżet testu porównawczego kompresji — minimalne poziomy oszczędności tokenów dla poszczególnych silników nie mogą ulec pogorszeniu | Tak | +| `check:type-coverage` | Mechanizm ratchet dla odsetka kodu z typami (`type-coverage`) nie wykazuje regresji; w dużej mierze zastępuje `typecheck:noimplicit:core` | Tak | +| `check:codeql-ratchet` | Liczba otwartych alertów CodeQL nie wzrasta (odczyt przez `gh api`; pominięcie bez błędu przy braku tokenu) — częstotliwość odświeżania i ręczne uruchamianie: patrz „CodeQL ratchet” poniżej | Tak | ### Zadanie: `quality-extended` -Całe zadanie ma charakter doradczy (`continue-on-error: true`). Zapadki oparte na npm są -uruchamiane faktycznie; zewnętrzne skanery są instalowane za pomocą `gh release download` -i samoczynnie pomijają wykonanie (kod wyjścia 0), gdy plik binarny nadal jest niedostępny. +Całe zadanie ma charakter doradczy (`continue-on-error: true`). Mechanizmy ratchet oparte na npm są +faktycznie uruchamiane; zewnętrzne skanery są instalowane za pomocą `gh release download` i samoczynnie pomijają wykonanie (kod wyjścia 0), +jeśli plik binarny nadal nie jest dostępny. -| Skrypt | Sprawdza | Blokujący | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- | -| `check:circular-deps` | Brak cyklicznych zależności (dpdm) | **Doradczy** | -| `check:bundle-size` | Rozmiar pakietu nie przekracza limitu | **Doradczy** | -| `check:secrets` | Skanowanie pod kątem sekretów (gitleaks) — pomijane, jeśli brak pliku binarnego | **Doradczy** | -| `check:vuln-ratchet` | Podatności zależności (osv-scanner) nie ulegają pogorszeniu — pomijane, jeśli brak pliku binarnego | **Doradczy** | -| `check:workflows` | Lintowanie przepływów pracy (actionlint + zizmor); brakujące/uszkodzone skanery, nieprawidłowe raporty lub brak bazowego poziomu zapadki powodują status INCOMPLETE. Prawidłowe znaleziska podlegają wybranej polityce ścisłej/doradczej/zapadki | Wykonanie wymagane; zapadka zizmor blokuje w CI | -| `check:openapi-breaking` | Niezgodne wstecznie zmiany publicznego kontraktu API (`openapi.yaml`) względem gałęzi bazowej (oasdiff) — generuje `openapiBreaking=N`; pomijane, jeśli brak oasdiff lub nie można rozpoznać specyfikacji bazowej | **Doradczy** | +| Skrypt | Sprawdza | Blokujący | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| `check:circular-deps` | Brak cyklicznych zależności (dpdm) | **Doradczy** | +| `check:bundle-size` | Rozmiar pakietu nie przekracza limitu | **Doradczy** | +| `check:secrets` | Skanowanie pod kątem sekretów (gitleaks) — pomijane w przypadku braku pliku binarnego | **Doradczy** | +| `check:vuln-ratchet` | Podatności zależności (osv-scanner) nie wykazują regresji — pomijane w przypadku braku pliku binarnego | **Doradczy** | +| `check:workflows` | Lintowanie przepływów pracy (actionlint + zizmor); brakujące/uszkodzone skanery, nieprawidłowe raporty lub brak bazowej wartości ratchet powodują stan INCOMPLETE. Prawidłowe wykrycia podlegają wybranej polityce ścisłej/doradczej/ratchet | Wykonanie wymagane; ratchet zizmor blokuje w CI | +| `check:openapi-breaking` | Zmiany niezgodne wstecznie w publicznym kontrakcie API (`openapi.yaml`) względem gałęzi bazowej (oasdiff) — generuje `openapiBreaking=N`; pomijane, jeśli brakuje oasdiff lub nie można rozpoznać specyfikacji bazowej | **Doradczy** | ### Zadanie: `docs-sync-strict` -Uruchamiane dla każdego PR do `main`. W przypadku niepowodzenia blokuje scalenie. - -| Skrypt | Sprawdza | Blokujący | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `check:docs-all` | Metabrama uruchamiająca kolejno 6 poniższych bram podrzędnych | Tak | -| ↳ `check:docs-sync` | Spójność wersji w CHANGELOG / OpenAPI / llm.txt | Tak | -| ↳ `check:docs-counts` | Liczby w treści (liczba dostawców, liczba migracji itp.) mieszczą się w przedziale zapadkowym względem rzeczywistych wartości | Tak | -| ↳ `check:env-doc-sync` | Każda zmienna środowiskowa z `.env.example` jest udokumentowana w tabeli dokumentacji i odwrotnie | Tak | -| ↳ `check:deprecated-versions` | Brak ciągów oznaczających przestarzałe wersje w dokumentacji | Tak | -| ↳ `check:doc-links` | Wewnętrzne linki markdown w dokumentacji wskazują na istniejące pliki (format `[tekst]`/`(ścieżka)`) | Tak | -| ↳ `check:fabricated-docs` | Trasy, zmienne środowiskowe, polecenia CLI, nazwy hooków i ścieżki plików przywołane w dokumentacji istnieją w bazie kodu. Brama twarda z `--strict`; bez tej flagi zgłasza błąd miękki. | Tak (przez `--strict` w CI) | -| `check:cli-i18n` | Ciągi poleceń CLI są obecne we wszystkich plikach ustawień regionalnych i18n | Tak | -| `check:openapi-coverage` | Specyfikacja OpenAPI obejmuje co najmniej ustalony zapadkowo minimalny odsetek rzeczywistych tras | Tak | -| `check:openapi-security-tiers` | Adnotacje poziomów bezpieczeństwa w `openapi.yaml` są spójne z klasyfikacjami w `routeGuard.ts` | **Doradczy** | -| `check:openapi-routes` | Każda ścieżka w `openapi.yaml` wskazuje na rzeczywisty plik `route.ts` (ochrona przed halucynacjami) | Tak | -| `check:docs-symbols` | Każde odwołanie `/api/...` w `docs/**/*.md` wskazuje na rzeczywisty plik `route.ts` (ochrona przed halucynacjami) | Tak | -| `i18n translation drift` | Nieprzetłumaczone klucze w plikach ustawień regionalnych i18n — tylko ostrzeżenie | **Doradczy** | +Uruchamiane dla każdego PR do `main`. W przypadku niepowodzenia blokuje scalanie. + +| Skrypt | Sprawdza | Blokujący | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `check:docs-all` | Metabramka, która kolejno uruchamia 6 poniższych podbramek | Tak | +| ↳ `check:docs-sync` | Spójność wersji w CHANGELOG / OpenAPI / llm.txt | Tak | +| ↳ `check:docs-counts` | Liczby w tekście (liczba dostawców, liczba migracji itp.) mieszczą się w przedziale wyznaczonym względem rzeczywistych wartości | Tak | +| ↳ `check:env-doc-sync` | Każda zmienna środowiskowa z `.env.example` jest udokumentowana w tabeli dokumentacji i odwrotnie | Tak | +| ↳ `check:deprecated-versions` | W dokumentacji nie występują ciągi znaków z wycofanymi wersjami | Tak | +| ↳ `check:doc-links` | Wewnętrzne linki markdown w dokumentacji wskazują na istniejące pliki (format `[tekst]`/`(ścieżka)`) | Tak | +| ↳ `check:fabricated-docs` | Trasy, zmienne środowiskowe, polecenia CLI, nazwy hooków i ścieżki plików przywołane w dokumentacji istnieją w bazie kodu. Bramka twarda z `--strict`; bez tej flagi błąd nie blokuje. | Tak (przez `--strict` w CI) | +| `check:cli-i18n` | Ciągi znaków poleceń CLI są obecne we wszystkich plikach lokalizacji i18n | Tak | +| `check:openapi-coverage` | Specyfikacja OpenAPI obejmuje co najmniej progową, stopniowo zwiększaną liczbę rzeczywistych tras | Tak | +| `check:openapi-security-tiers` | Adnotacje poziomów zabezpieczeń w `openapi.yaml` są zgodne z klasyfikacjami w `routeGuard.ts` | **Doradczy** | +| `check:openapi-routes` | Każda ścieżka w `openapi.yaml` odpowiada rzeczywistemu plikowi `route.ts` (zapobieganie halucynacjom) | Tak | +| `check:docs-symbols` | Każde odwołanie `/api/...` w `docs/**/*.md` odpowiada rzeczywistemu plikowi `route.ts` (zapobieganie halucynacjom) | Tak | +| `i18n translation drift` | Nieprzetłumaczone klucze w plikach lokalizacji i18n — tylko ostrzeżenie | **Doradczy** | ### Zadanie: `i18n-ui-coverage` -| Skrypt | Sprawdza | Blokujący | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `check-ui-keys-coverage` (inline) | Pokrycie kluczy i18n interfejsu użytkownika wynosi ≥ 65% | Tak | -| `check-ui-value-drift` (inline) | Zmiana angielskiej **wartości** nie pozostawia nieaktualnego tłumaczenia | Tak | -| `check-new-key-coverage` (inline) | **Nowy** angielski klucz jest przetłumaczony dla każdego ustawienia regionalnego — znacznik `__MISSING__:` jest odrzucany | Tak | -| `check-translation-ratio` | Współczynnik rzeczywistych tłumaczeń dla każdego ustawienia regionalnego (liście identyczne z angielskimi / zastępcze / brakujące, spoza listy dozwolonych wyjątków) nie może przekraczać `config/quality/i18n-translation-baseline.json` + margines | **Doradczy** | +| Skrypt | Sprawdza | Blokujący | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `check-ui-keys-coverage` (inline) | Pokrycie kluczy i18n interfejsu użytkownika wynosi ≥ 65% | Tak | +| `check-ui-value-drift` (inline) | Zmieniona angielska **wartość** nie pozostawia nieaktualnego tłumaczenia | Tak | +| `check-new-key-coverage` (inline) | **Nowy** angielski klucz jest przetłumaczony w każdej lokalizacji — znacznik `__MISSING__:` jest odrzucany | Tak | +| `check-translation-ratio` | Współczynnik rzeczywistych tłumaczeń dla każdej lokalizacji (wartości identyczne z angielskimi / symbole zastępcze / brakujące elementy spoza listy dozwolonych) nie może przekraczać `config/quality/i18n-translation-baseline.json` + marginesu | **Doradczy** | -Wymaga `fetch-depth: 0` — brama wykrywająca rozbieżności wartości porównuje `en.json` z bazą scalania. +Wymaga `fetch-depth: 0` — bramka wykrywająca rozbieżności wartości porównuje `en.json` z bazą scalania. -#### `check-ui-value-drift` — brama nieaktualnych tłumaczeń +#### `check-ui-value-drift` — bramka nieaktualnych tłumaczeń -Wykrywa ten jeden rodzaj regresji i18n, którego pozostałe bramy nie są w stanie rozpoznać strukturalnie: angielska wartość -zostaje zmieniona, a tłumaczenia utworzone na podstawie _poprzedniej_ wersji angielskiej pozostają bez zmian, przez co -użytkownicy nieanglojęzyczni nadal czytają sformułowaną w sposób budzący zaufanie, lecz obecnie błędną treść. +Wykrywa jedną regresję i18n, której pozostałe bramki z przyczyn strukturalnych nie są w stanie zauważyć: angielska wartość +zostaje zmieniona, a tłumaczenia utworzone na podstawie _poprzedniej_ angielskiej wersji pozostają niezmienione, przez co +użytkownicy nieanglojęzyczni nadal czytają sformułowany z pełnym przekonaniem, ale już nieprawidłowy tekst. -Taki błąd rzeczywiście trafił do wydania. Wartość `oauthModal.googleOAuthWarning` została zmieniona po dodaniu -pomocniczego mechanizmu logowania Antigravity (#5203); w **39 z 43 ustawień regionalnych** pozostał tekst nakazujący operatorom „skopiować -pełny adres URL i wkleić go poniżej” — jest to procedura, której nie da się ukończyć dla tego dostawcy. Problem -pozostał niezauważony aż do #8463, ponieważ: +Taka sytuacja rzeczywiście trafiła do wydania. `oauthModal.googleOAuthWarning` zmieniono po dodaniu pomocnika logowania +Antigravity (#5203); **39 z 43 lokalizacji** nadal zawierało tekst nakazujący operatorom „skopiować pełny adres URL +i wkleić go poniżej” — w przypadku tego dostawcy taki proces nie może się zakończyć. Problem pozostał +niezauważony aż do #8463, ponieważ: -- `sync-ui-keys` uzupełnia wyłącznie klucze, których **brakuje**, a nigdy te, które są **nieaktualne**; -- `check-ui-keys-coverage` zlicza _obecność_ kluczy, dlatego nieaktualne tłumaczenie jest uznawane za pokryte; +- `sync-ui-keys` uzupełnia tylko klucze, których **brakuje**, nigdy te, które są **nieaktualne**; +- `check-ui-keys-coverage` zlicza _obecność_ kluczy, więc nieaktualne tłumaczenie jest uznawane za pokryte; - `check-translation-drift` śledzi kopie dokumentacji `docs/i18n//**.md` — nigdy nie odczytuje `src/i18n/messages/*.json`. Blokuje w zadaniu `docs-sync-strict` od ponownej synchronizacji - 2026-09: po edycji głównego dokumentu uruchom `npm run i18n:run -- --files=` (na poziomie sekcji, niewielki koszt). + 2026-09: edytuj główny dokument → `npm run i18n:run -- --files=` (na poziomie sekcji, niewielki koszt). -**Uwzględnia różnice, nie opiera się na bazie odniesienia.** Porównuje plik `en.json` w punkcie wspólnego przodka z +**Uwzględnia różnice, nie opiera się na pliku bazowym.** Porównuje `en.json` w punkcie bazowym scalania z drzewem roboczym; dla każdego klucza, którego angielska wartość uległa zmianie, każde tłumaczenie nadal zawierające -niezmienioną wersję jest nieaktualne. Celowo **zamraża to istniejący dług** — różnica -nie pozwala ustalić, z której starej angielskiej wersji pochodzi istniejące od dawna tłumaczenie, dlatego mechanizm kontrolny ocenia -wyłącznie to, czego dotyczy bieżąca zmiana. Alternatywa (baza skrótów dla poszczególnych kluczy) wymagałaby -wygenerowanego pliku o rozmiarze ~600 KB, 3× większego niż największa istniejąca baza odniesienia, zmieniającego się przy każdym PR dotyczącym i18n. +niezmienioną wersję jest nieaktualne. To celowo **zamraża istniejący dług** — różnica +nie pozwala ustalić, z której starej angielskiej wersji pochodzi istniejące od dawna tłumaczenie, dlatego bramka ocenia +wyłącznie to, czego dotyczy bieżąca zmiana. Alternatywa (bazowy skrót dla każdego klucza) wymagałaby +wygenerowanego pliku o rozmiarze około 600 KB, 3× większego niż największy istniejący plik bazowy, zmieniającego się przy każdym PR dotyczącym i18n. -Można spełnić ten wymóg na dwa sposoby: +Można spełnić ten warunek na dwa sposoby: 1. zaktualizować tłumaczenia, których dotyczy zmiana, albo -2. ustawić ich wartość na `__MISSING__:` — środowisko uruchomieniowe zwróci wtedy poprawiony tekst angielski - (`src/i18n/request.ts::deepMergeFallback`, #7258), a klucz trafi do kolejki tłumaczeń. +2. ustawić ich wartość na `__MISSING__:` — środowisko uruchomieniowe wyświetli wtedy poprawiony tekst angielski + (`src/i18n/request.ts::deepMergeFallback`, #7258), a klucz zostanie dodany do kolejki tłumaczeń. -Jeśli zmieniło się **znaczenie** ciągu, zaleca się **zmianę nazwy klucza**: nowy klucz nie może odziedziczyć -nieaktualnego tłumaczenia. Ten wzorzec zastosowano w #8463. +Jeśli zmieniło się **znaczenie** ciągu, najlepiej **zmienić nazwę klucza**: nowy klucz nie może odziedziczyć +nieaktualnego tłumaczenia. Taki wzorzec zastosowano w #8463. ```bash -npm run i18n:check-value-drift # tryb rygorystyczny (uruchamiany przez CI) +npm run i18n:check-value-drift # tryb ścisły (uruchamiany przez CI) npm run i18n:check-value-drift:warn # tylko raport BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` Kończy działanie z kodem 0 i komunikatem `SKIP reason=base-unresolved`, gdy nie można odczytać katalogu bazowego (płytki -klon bez bazowego odwołania), analogicznie do `check-openapi-breaking`. +klon bez referencji bazowej), analogicznie do `check-openapi-breaking`. ### Zadanie: `i18n` -Pełna macierz walidacji i18n (jedno zadanie na ustawienia regionalne). Całe zadanie ma charakter informacyjny. +Pełna macierz walidacji i18n (jedno zadanie na język). Całe zadanie ma charakter informacyjny. -| Skrypt | Sprawdza | Blokowanie | +| Skrypt | Weryfikuje | Blokowanie | | ------------------------------- | ------------------------------------------ | --------------------------------------------------------------- | | `validate_translation.py quick` | Kompletność tłumaczenia dla każdego języka | **Informacyjne** (`continue-on-error: true` dla całego zadania) | @@ -248,32 +257,32 @@ Pełna macierz walidacji i18n (jedno zadanie na ustawienia regionalne). Całe za Uruchamiane wyłącznie dla pull requestów. -| Skrypt | Sprawdza | Blokowanie | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | -| `check:pr-test-policy` | PR-y zmieniające kod produkcyjny w `src/`, `open-sse/`, `electron/` lub `bin/` muszą zawierać lub aktualizować testy (Twarda reguła #8) | Tak | -| `check:test-masking` | Zmienione pliki testowe nie zmniejszają łącznej liczby asercji ani nie dodają tautologii `assert.ok(true)` | Tak | -| `check:pr-evidence` | Treść PR-a przytacza wyniki testów/VPS dotyczące zmiany (automatyzuje Twardą regułę #18 przez przeszukiwanie opisu PR-a — rozwiązanie kruche, patrz Backlog) | Tak | +| Skrypt | Weryfikuje | Blokowanie | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check:pr-test-policy` | PR-y zmieniające kod produkcyjny w `src/`, `open-sse/`, `electron/` lub `bin/` muszą zawierać nowe lub zaktualizowane testy (Twarda reguła #8) | Tak | +| `check:test-masking` | Zmienione pliki testowe nie zmniejszają łącznej liczby asercji ani nie dodają tautologii `assert.ok(true)` | Tak | +| `check:pr-evidence` | Opis PR-a przytacza dowody z testów/VPS dotyczące zmiany (automatyzuje Twardą regułę #18 przez przeszukiwanie treści PR-a — rozwiązanie kruche, patrz Zaległości) | Tak | ### Zadanie: `test-vitest` -Uruchamiane po `build`. W przypadku niepowodzenia blokuje scalenie. +Uruchamiane po `build`. Niepowodzenie blokuje scalenie. -| Zestaw | Sprawdza | Blokowanie | -| ---------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Serwer MCP (110 narzędzi), autoCombo, pamięć podręczna — runner vitest | Tak | -| `test:vitest:ui` | Testy komponentów interfejsu użytkownika — runner vitest | **Blokujące** — istniejące wcześniej niepowodzenia są jawnie wykluczone w `vitest.config.ts`; nowe niepowodzenia przerywają zadanie | +| Zestaw | Weryfikuje | Blokowanie | +| ---------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Serwer MCP (110 narzędzi), autoCombo, pamięć podręczna — runner vitest | Tak | +| `test:vitest:ui` | Testy komponentów interfejsu użytkownika — runner vitest | **Blokujące** — istniejące wcześniej błędy są jawnie wykluczone w `vitest.config.ts`; nowe błędy powodują niepowodzenie zadania | ### Nocne przepływy pracy (zaplanowane, informacyjne) Są uruchamiane zgodnie z harmonogramem cron (oraz przez `workflow_dispatch`), nigdy dla PR-ów. Wszystkie mają charakter informacyjny. -| Przepływ pracy | Sprawdza | Blokowanie | +| Przepływ pracy | Weryfikuje | Blokowanie | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | Testy właściwości fast-check z losowym ziarnem i dużą liczbą przebiegów | **Informacyjne** | -| `nightly-resilience` | Kontrola wzrostu sterty, wstrzykiwanie błędów metodą chaos engineering, testy obciążeniowe/długotrwałe k6 | **Informacyjne** | -| `nightly-llm-security` | Ochrona promptfoo przed wstrzyknięciami (tryb blokowania) + sondy garak (pomijane bez sekretu dostawcy) | **Informacyjne** | -| `nightly-schemathesis` | Fuzzing kontraktu OpenAPI (schemathesis) względem działającej instancji OmniRoute z użyciem `docs/openapi.yaml` — ujawnia naruszenia specyfikacji / nieobsłużone błędy 500 (Etap 8 B.4) | **Informacyjne** | -| `nightly-mutation` | Wynik testów mutacyjnych Stryker dla szybkiej ścieżki testów jednostkowych — przetrwałe mutanty ujawniają słabe asercje | **Informacyjne** | +| `nightly-property` | Testy właściwości fast-check z losowym ziarnem i dużą liczbą uruchomień | **Informacyjne** | +| `nightly-resilience` | Bramka wzrostu sterty, chaotyczne wstrzykiwanie błędów, testy obciążeniowe/długotrwałe k6 | **Informacyjne** | +| `nightly-llm-security` | Ochrona przed atakami typu prompt injection w promptfoo (tryb blokowania) i sondy garak (pomijane bez sekretu dostawcy) | **Informacyjne** | +| `nightly-schemathesis` | Fuzzing kontraktu OpenAPI (schemathesis) względem działającej instancji OmniRoute z użyciem `docs/openapi.yaml` — ujawnia naruszenia specyfikacji / nieobsłużone błędy 500 (Faza 8 B.4) | **Informacyjne** | +| `nightly-mutation` | Wynik testów mutacyjnych Stryker dla szybkiej ścieżki testów jednostkowych — mutanty, które przetrwały, ujawniają słabe asercje | **Informacyjne** | | `nightly-compat` | Macierz zgodności silnika Node dla obsługiwanych zakresów `engines.node` | **Informacyjne** | --- @@ -539,43 +548,43 @@ błąd połączenia i nigdy nie zakłóca sesji. --- -## Rejestr racjonalizacji (przegląd ROI — Etap 9, Fala 3) +## Rejestr racjonalizacji (przegląd ROI — etap 9, fala 3) -Ten wykaz uzgodniono z `ci.yml` w dniu 2026-06-17 (w poprzedniej wersji pominięto +Ten wykaz uzgodniono z `ci.yml` 2026-06-17 (w poprzedniej wersji pominięto `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Przegląd ROI uzgodnionego zestawu wskazał następujących kandydatów do racjonalizacji. **Scalenia są mechanicznymi zmianami -CI; przełączenia/usunięcia są decyzjami dotyczącymi zasad, zastrzeżonymi dla operatora.** Żadna +CI; przełączenia/usunięcia to decyzje dotyczące zasad, zastrzeżone dla operatora.** Żadna z poniższych zmian nie została jeszcze zastosowana. -**Pominięte również powyżej** (doradcze, słaby sygnał): zadanie `docs-lint` -(markdownlint + Vale, całe zadanie z `continue-on-error`) oraz samodzielne przepływy pracy skanerów +**Powyżej nie udokumentowano również** (tryb doradczy, słaby sygnał): zadania `docs-lint` +(markdownlint + Vale, całe zadanie z `continue-on-error`) oraz samodzielnych przepływów pracy skanerów `semgrep.yml` / `codeql.yml` / `scorecard.yml`. Wartość `semgrepFindings: 0` znajduje się w -`quality-baseline.json`, ale nie jest powiązana z blokującym mechanizmem zapadkowym w `ci.yml` — ta metryka +`quality-baseline.json`, ale nie jest podłączona do blokującej zapadki w `ci.yml` — ta metryka jest obecnie osierocona. ### Scalanie / deduplikacja (mechaniczne, niższe ryzyko) -Każdego kandydata zweryfikowano względem bieżącego stanu bramek w dniu 2026-06-17 (ufaj, ale sprawdzaj); -okazało się, że kilka „oczywistych” scaleń ukrywa dług i **nie** nadaje się do bezpośredniego zastosowania. +Każdego kandydata zweryfikowano względem bieżącego stanu bramek 2026-06-17 (ufaj, ale sprawdzaj); +okazało się, że kilka „oczywistych” scaleń ukrywa dług i **nie** stanowi bezproblemowych zamienników. -- **`check:docs-sync` uruchamia się dwukrotnie** — samodzielnie w zadaniu `lint` oraz ponownie wewnątrz `check:docs-all` (`docs-sync-strict`) i haka husky pre-commit. ✅ **GOTOWE** — usunięto samodzielne wywołanie w `lint`. -- **Skanowanie CVE** — ❌ **NIE jest to proste scalenie.** `audit:deps` kończy się twardym błędem przy każdej luce CVE o wysokiej/krytycznej wadze; `check:vuln-ratchet` (osv) kończy się błędem tylko w przypadku _regresji_ względem poziomu bazowego (obecnie 1 MODERATE). Odmienna semantyka — usunięcie `audit:deps` oznaczałoby utratę bezwzględnej bramki dla luk o wysokiej/krytycznej wadze. Zachować oba. -- **Wykrywanie cykli** — ❌ **NIE jest to proste scalenie.** `check:circular-deps` (dpdm) zgłasza **91 cykli** (dlatego ma charakter doradczy); nie można nadać mu statusu blokującego bez ich uprzedniego rozwiązania, a jego zakres jest szerszy niż w przypadku zielonego, starannie dobranego `check:cycles`. Zachować `check:cycles` jako blokujące; rozwiązanie 91 cykli dpdm stanowi osobną pozycję rejestru. -- **Złożoność** — ✅ **GOTOWE** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jedno przejście ESLint, zliczanie według ruleId, dzięki czemu bazowe wartości złożoności cyklomatycznej+max-lines oraz złożoności poznawczej pozostają niezależne; poszczególne `check:complexity` / `check:cognitive-complexity` pozostają dostępne do lokalnego użycia z `--update`. -- **Ochrona `/api` przed halucynacjami** — ✅ **GOTOWE** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jedna inwentaryzacja FS katalogu `src/app/api`, openapi-routes + docs-symbols nadal raportują niezależnie; poszczególne polecenia pozostają dostępne do lokalnych uruchomień. -- **`check:node-runtime` uruchamia się w 11 zadaniach** — ⚠️ **niski ROI.** Każde działa na osobnym runnerze, a kontrola trwa <1s; łączna oszczędność wyniosłaby ~10s kosztem utraty niedrogiej ochrony dla każdego zadania. Nie warto powodować zamieszania. -- **`typecheck:noimplicit:core` w lintowaniu CI** — ✅ **usunięto z zadania lint** (miało charakter doradczy z `continue-on-error`); blokujący zakres kontroli typów zapewniają `typecheck:core` + `check:type-coverage`. Zachowano skrypt lokalny. +- **`check:docs-sync` uruchamia się dwukrotnie** — samodzielnie w zadaniu `lint` oraz ponownie wewnątrz `check:docs-all` (`docs-sync-strict`) i w hooku pre-commit husky. ✅ **GOTOWE** — samodzielne wywołanie w `lint` zostało usunięte. +- **Skanowanie CVE** — ❌ **NIE jest to bezproblemowe scalenie.** `audit:deps` kończy się twardym błędem przy dowolnej luce CVE o wysokiej/krytycznej wadze; `check:vuln-ratchet` (osv) kończy się błędem tylko przy _regresji_ względem wartości bazowej (obecnie 1 MODERATE). Inna semantyka — usunięcie `audit:deps` oznaczałoby utratę bezwzględnej bramki dla luk o wysokiej/krytycznej wadze. Należy zachować oba. +- **Wykrywanie cykli** — ✅ **GOTOWE** (#15159 G-01/G-02). Poprzedni tekst określał `check:cycles` jako „zieloną, starannie dobraną” bramkę i uzasadniał utrzymanie jej w trybie blokującym tym, że `check:circular-deps` (dpdm) zgłaszało 91 cykli. Ten zielony wynik był **fałszywie pozytywny**: `check:cycles` skanował 5 podkatalogów (450 plików), dopasowywał tylko statyczne `import|export … from` i odrzucał każdy specyfikator `@/` oraz `@omniroute/open-sse/`, przez co nie mógł wykryć cykli dynamicznego importu i aliasów, które dominowały w repozytorium. Naprawiono: bramka przeszukuje teraz `src` + `open-sse` (5023 pliki), zbiera specyfikatory z AST TypeScriptu (dzięki czemu `import("…")` jest uwzględniany, a `typeof import("…")` w pozycji typu — nie) i rozwiązuje `paths` z tsconfig. Wykrywa **14** cykli, a nie 0. Ponieważ 14 istniejących wcześniej cykli nie można naprawić w PR-ze dotyczącym bramki, `check:cycles` działa teraz jako **zapadka** (`--ratchet`, limit `metrics.cycles.value = 14` w `quality-baseline.json`, `direction: down`) — blokuje każdą _regresję_, a liczba może wyłącznie maleć. CI uruchamia `npm run check:cycles:ratchet`. Redukcja długu odbywa się w ramach **A-01**. `check:circular-deps` (dpdm) pozostaje doradczą, szerszą drugą opinią. +- **Złożoność** — ✅ **GOTOWE** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jedno przejście ESLint, zliczanie według ruleId, dzięki czemu wartości bazowe dla złożoności cyklomatycznej + maksymalnej liczby wierszy oraz złożoności poznawczej pozostają niezależne; poszczególne `check:complexity` / `check:cognitive-complexity` pozostają dostępne dla lokalnego `--update`. +- **Ochrona `/api` przed halucynacjami** — ✅ **GOTOWE** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jeden wykaz FS katalogu `src/app/api`; openapi-routes + docs-symbols nadal raportują niezależnie; poszczególne polecenia pozostają dostępne do uruchamiania lokalnego. +- **`check:node-runtime` uruchamia się w 11 zadaniach** — ⚠️ **niski ROI.** Każde korzysta z osobnego runnera, a sprawdzenie trwa <1 s; łączna oszczędność wyniosłaby ~10 s, kosztem utraty taniego zabezpieczenia dla każdego zadania. Nie warto wprowadzać takiej zmiany. +- **`typecheck:noimplicit:core` w zadaniu lint CI** — ✅ **usunięto z zadania lint** (wcześniej działało doradczo z `continue-on-error`); blokującą powierzchnię typów zapewniają `typecheck:core` + `check:type-coverage`. Skrypt lokalny zachowano. ### Przełączenie / decyzja (zasady operatora) -- `check:openapi-security-tiers` (doradcze) — ❌ **NIE można go bezpośrednio przełączyć.** Kończy się kodem 0, ale ostrzega, że kilka tras `traffic-inspector` w ramach `LOCAL_ONLY_API_PREFIXES` nie ma adnotacji `x-loopback-only: true`. Wymuszenie tej kontroli wymaga najpierw dodania tych adnotacji do `openapi.yaml`. -- `typecheck:noimplicit:core` (doradcze) — w dużej mierze zastąpione przez blokujący mechanizm zapadkowy `check:type-coverage`. Przełączyć na mechanizm zapadkowy albo usunąć redundantne drugie przejście `tsc`. -- `test:vitest:ui` (obecnie **blokujące**) — wcześniej istniejące błędy są jawnie wykluczone w `vitest.config.ts` za pomocą komentarzy śledzących `// #8618`; nowe błędy powodują niepowodzenie zadania. -- `check:secrets` (gitleaks, blokujący mechanizm zapadkowy zamrożony na poziomie 3 udokumentowanych wyników fałszywie dodatnich) — dodać te 3 przypadki do listy dozwolonych, aby osiągnąć 0, albo obniżyć status do doradczego. Pokrywa się z natywnym skanowaniem sekretów GitHub + `check:public-creds`. -- `check:pr-evidence` (blokujące, przeszukuje tekst opisu PR) — wysokie ryzyko wyników fałszywie dodatnich; usunięcie osłabi egzekwowanie Twardej Reguły nr 18, więc jest to rzeczywista decyzja dotycząca zasad. -- `semgrep` (samodzielne, doradcze) — pokrywa się z CodeQL w zakresie kategorii OWASP; powiązać jego poziom bazowy z mechanizmem zapadkowym albo usunąć. +- `check:openapi-security-tiers` (doradcze) — ❌ **NIE można go bezproblemowo przełączyć.** Kończy działanie z kodem 0, ale ostrzega, że w kilku trasach `traffic-inspector` w ramach `LOCAL_ONLY_API_PREFIXES` brakuje adnotacji `x-loopback-only: true`. Wymuszenie tej kontroli wymaga najpierw dodania tych adnotacji do `openapi.yaml`. +- `typecheck:noimplicit:core` (doradcze) — w dużej mierze zastąpione przez blokującą zapadkę `check:type-coverage`. Należy przełączyć je na zapadkę albo usunąć nadmiarowe drugie przejście `tsc`. +- `test:vitest:ui` (obecnie **blokujące**) — istniejące wcześniej błędy są jawnie wykluczone w `vitest.config.ts` za pomocą komentarzy śledzących `// #8618`; nowe błędy powodują niepowodzenie zadania. +- `check:secrets` (gitleaks, blokująca zapadka zamrożona na 3 udokumentowanych wynikach fałszywie pozytywnych) — należy dodać te 3 przypadki do listy dozwolonych, aby osiągnąć 0, albo zdegradować kontrolę do trybu doradczego. Nakłada się na natywne skanowanie sekretów GitHub + `check:public-creds`. +- `check:pr-evidence` (blokujące, przeszukuje tekst opisu PR) — wysokie ryzyko wyników fałszywie pozytywnych; usunięcie osłabi egzekwowanie Hard Rule #18, więc jest to rzeczywista decyzja dotycząca zasad. +- `semgrep` (samodzielne, doradcze) — nakłada się na CodeQL w zakresie rodzin OWASP; należy podłączyć jego wartość bazową do zapadki albo go usunąć. --- diff --git a/docs/i18n/pl/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/pl/docs/compression/COMPRESSION_GUIDE.md index 86ee364277c7..b33c906cad7c 100644 --- a/docs/i18n/pl/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/pl/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ wstrzykiwana jest instrukcja zlokalizowana. W treści zawierającej `messages` mechanizm pomijania na podstawie zawartości (`shouldBypassCavemanOutputMode()` w `open-sse/services/compression/outputMode.ts`) sprawdza trzy ostatnie wiadomości i pomija style dla całej tury, gdy pasują one do słów kluczowych związanych z bezpieczeństwem, nieodwracalnymi działaniami, -wyjaśnieniami lub wrażliwością na kolejność. Mechanizm pomijania działa zgodnie z ustawieniem przełącznika -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) w panelu. +wyjaśnieniami lub wrażliwością na kolejność. Mechanizm pomijania działa, dopóki przełącznik **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) w panelu jest włączony, co jest ustawieniem domyślnym; przy wyłączonym przełączniku wybrane style stosuje się także w tych turach. Gdy mechanizm pomijania przepuszcza turę, `placeSystemInstruction()` (ten sam plik), który nigdy nie tworzy nowego `messages[0]`, umieszcza blok w pierwszym znalezionym miejscu: diff --git a/docs/i18n/pl/docs/reference/ENVIRONMENT.md b/docs/i18n/pl/docs/reference/ENVIRONMENT.md index 79e1d2a4aaaf..c9a9c581323e 100644 --- a/docs/i18n/pl/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/pl/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Wymagane sekrety -Te wartości **muszą** zostać ustawione przed pierwszym uruchomieniem. Bez nich aplikacja odmówi uruchomienia albo będzie działać z niezabezpieczonymi wartościami domyślnymi. - -| Zmienna | Wymagana | Wartość domyślna | Plik źródłowy | Opis | -| ---------------------------- | ------------------- | ------------------------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Tak** | _(brak)_ | `src/lib/auth` | Podpisuje/weryfikuje wszystkie pliki cookie sesji panelu (JWT). Wygeneruj za pomocą `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Tak** | _(brak)_ | `src/lib/db/apiKeys.ts` | Klucz szyfrowania AES dla wartości kluczy API przechowywanych w SQLite. Wygeneruj za pomocą `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Tak** | `CHANGEME` | Skrypt inicjalizacyjny | Ustawia początkowe hasło administratora panelu (zgodne z wartością domyślną w `.env.example` — pozostawiono je celowo jako jawnie niezabezpieczone, aby wymusić zmianę). **Zmień je przed pierwszym użyciem.** Po zalogowaniu zmień je przez Panel → Ustawienia → Bezpieczeństwo. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Tak** (produkcja) | _(nieustawiona)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Wspólny sekret wewnętrznego mostu WebSocket dla Codex Responses. Uwierzytelnia żądania mostu między przekaźnikiem WS środowiska Electron/przeglądarki a OmniRoute. ⚠️ **WYMAGANY w środowisku produkcyjnym — jeśli nie jest ustawiony, wszystkie żądania mostu WS są odrzucane.** Wygeneruj za pomocą `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nie | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Jawny identyfikator unieważniania pamięci podręcznej service workera dla powłoki PWA (#11779); pierwszy w kolejności rozstrzygania. | -| `SOURCE_VERSION` | Nie | _(nieustawiona)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi w kolejności — ustawiany przez systemy budowania PaaS (np. w stylu Heroku) na wdrożony commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nie | _(wyprowadzana)_ | `src/shared/components/PwaRegister.tsx` | Publiczna wartość czasu kompilacji używana przez klienta do rejestrowania `/sw.js?v=…`; wyprowadzana z dwóch powyższych wartości, a następnie z SHA git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nie (automatycznie) | _(automatycznie przy każdym uruchomieniu)_ | `src/server/authz/policies/management.ts` | Sekret przypisany do procesu, który potwierdza, że zaufany znacznik adresu IP peera pochodzi z własnego serwera HTTP OmniRoute (`scripts/dev/peer-stamp.mjs`). Oprogramowanie pośredniczące authz uznaje lokalność żądania (ograniczanie tras LOCAL_ONLY do pętli zwrotnej/sieci LAN) tylko wtedy, gdy znacznik zawiera ten token. Generowany automatycznie przy każdym uruchomieniu — pozostaw nieustawiony; przypisz stałą wartość tylko w konfiguracjach wieloprocesowych, które muszą współdzielić znacznik. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nie | _(nieustawiona)_ | `scripts/dev/peer-stamp.mjs` | Rozdzielona przecinkami lista adresów IP lub zakresów CIDR serwerów reverse proxy, których nagłówkom `X-Forwarded-For` / `X-Real-IP` może ufać lista dozwolonych/zabronionych adresów IP, oprócz adresów pętli zwrotnej, sieci prywatnych i węzłów brzegowych Cloudflare, które są zawsze zaufane. Wymagane tylko dla serwera proxy działającego pod dowolnym innym adresem publicznym; w przeciwnym razie filtr ocenia własny adres tego serwera proxy. | +Nieustawione sekrety oznaczone jako **Tak** są generowane automatycznie przy pierwszym uruchomieniu (`JWT_SECRET`, `API_KEY_SECRET`) i zapisywane w pliku `server.env` w katalogu danych — ustaw je jawnie, gdy ten katalog jest efemeryczny. Wyjątkiem jest `OMNIROUTE_WS_BRIDGE_SECRET`: nie jest on zapisywany w `server.env` — punkty wejściowe serwera generują przy każdym uruchomieniu efemeryczną wartość, której użyć może wyłącznie most działający w procesie, dlatego zewnętrzni klienci mostu muszą ustawić go jawnie. + +| Zmienna | Wymagana | Wartość domyślna | Plik źródłowy | Opis | +| ---------------------------- | ------------------- | ------------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Tak** | _(brak)_ | `src/lib/auth` | Podpisuje/weryfikuje wszystkie pliki cookie sesji panelu (JWT). Wygeneruj za pomocą `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Tak** | _(brak)_ | `src/lib/db/apiKeys.ts` | Klucz szyfrowania AES dla wartości kluczy API przechowywanych w SQLite. Wygeneruj za pomocą `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nie | _(nieustawiona)_ | `src/lib/auth/managementPassword.ts` | Ustawia hasło panelu podczas uruchamiania, dopóki żadne nie jest zapisane; jest ignorowane, gdy hasło jest już zapisane. Instalacje npm i ze źródeł kopiują `.env.example` do `.env`, więc zaczynają z jego publicznie znaną wartością zastępczą `CHANGEME`: zastąp ją przed pierwszym uruchomieniem albo ustaw `INITIAL_PASSWORD=` (puste) w tym pliku `.env`, aby utworzyć hasło w kreatorze konfiguracji początkowej przy pierwszej wizycie — tak samo działa wartość nieustawiona (domyślna w obrazie Dockera). Zapisane hasło zmień w Panel → Ustawienia → Bezpieczeństwo albo uruchom `omniroute reset-password` (`node bin/reset-password.mjs` w kopii ze źródeł) z `DATA_DIR` ustawionym na katalog danych serwera. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Tak** (produkcja) | _(nieustawiona)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Wspólny sekret wewnętrznego mostu WebSocket dla Codex Responses. Uwierzytelnia żądania mostu między przekaźnikiem WS środowiska Electron/przeglądarki a OmniRoute. ⚠️ **WYMAGANY w środowisku produkcyjnym — jeśli nie jest ustawiony, wszystkie żądania mostu WS są odrzucane.** Wygeneruj za pomocą `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nie | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Jawny identyfikator unieważniania pamięci podręcznej service workera dla powłoki PWA (#11779); pierwszy w kolejności rozstrzygania. | +| `SOURCE_VERSION` | Nie | _(nieustawiona)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi w kolejności — ustawiany przez systemy budowania PaaS (np. w stylu Heroku) na wdrożony commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nie | _(wyprowadzana)_ | `src/shared/components/PwaRegister.tsx` | Publiczna wartość czasu kompilacji używana przez klienta do rejestrowania `/sw.js?v=…`; wyprowadzana z dwóch powyższych wartości, a następnie z SHA git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nie (automatycznie) | _(automatycznie przy każdym uruchomieniu)_ | `src/server/authz/policies/management.ts` | Sekret przypisany do procesu, który potwierdza, że zaufany znacznik adresu IP peera pochodzi z własnego serwera HTTP OmniRoute (`scripts/dev/peer-stamp.mjs`). Oprogramowanie pośredniczące authz uznaje lokalność żądania (ograniczanie tras LOCAL_ONLY do pętli zwrotnej/sieci LAN) tylko wtedy, gdy znacznik zawiera ten token. Generowany automatycznie przy każdym uruchomieniu — pozostaw nieustawiony; przypisz stałą wartość tylko w konfiguracjach wieloprocesowych, które muszą współdzielić znacznik. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nie | _(nieustawiona)_ | `scripts/dev/peer-stamp.mjs` | Rozdzielona przecinkami lista adresów IP lub zakresów CIDR serwerów reverse proxy, których nagłówkom `X-Forwarded-For` / `X-Real-IP` może ufać lista dozwolonych/zabronionych adresów IP, oprócz adresów pętli zwrotnej, sieci prywatnych i węzłów brzegowych Cloudflare, które są zawsze zaufane. Wymagane tylko dla serwera proxy działającego pod dowolnym innym adresem publicznym; w przeciwnym razie filtr ocenia własny adres tego serwera proxy. | ### Polecenia generowania @@ -218,38 +218,38 @@ OmniRoute używa **SQLite** (za pośrednictwem `better-sqlite3`) do trwałego pr ## 4. Bezpieczeństwo i uwierzytelnianie -| Zmienna | Wartość domyślna | Plik źródłowy | Opis | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sól łączona z identyfikatorami sprzętowymi w celu utworzenia odcisku palca maszyny. Należy ją zmienić dla każdego wdrożenia, aby zapewnić izolację. | -| `OMNIROUTE_CLI_SALT` | _(nieustawiona = losowa sól dla każdej instalacji, przechowywana w `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sól HMAC służąca do wyprowadzania lokalnego tokenu uwierzytelniającego CLI. Ustawienie tej wartości powoduje rotację wszystkich tokenów CLI na maszynie i zawsze ma pierwszeństwo przed przechowywaną solą. Zobacz `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Ustawia flagę `Secure` dla plików cookie sesji. Podczas działania za HTTPS wartość **musi wynosić `true`**. | -| `REQUIRE_API_KEY` | `false` | Oprogramowanie pośredniczące API | Gdy ma wartość `true`, wszystkie żądania proxy `/v1/*` muszą zawierać prawidłowy klucz API. **Ta flaga nie kontroluje dostępu do `GET /v1/models`**, który zamiast tego podlega konfiguracji logowania do panelu (`requireAuthForModels`) — dlatego odpowiedź `401` z `/v1/models` NIE oznacza, że wnioskowanie jest chronione. Zobacz `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Umożliwia ujawnianie pełnych wartości kluczy API w interfejsie panelu. Konfigurowalne z poziomu flag funkcji panelu; stanowi zagrożenie bezpieczeństwa we współdzielonych instancjach. | -| `NO_LOG_API_KEY_IDS` | _(pusta)_ | `src/lib/compliance/index.ts` | Rozdzielone przecinkami identyfikatory kluczy API, dla których pomijane jest rejestrowanie żądań (zgodność z RODO). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nieustawiona = bez limitu)_ | `src/shared/utils/apiKeyPolicy.ts` | Zapasowy dzienny budżet żądań stosowany do kluczy API, których kolumna `rate_limits` ma wartość null. Wartość nieustawiona lub pusta: brak domyślnego limitu (#2289, #11017). `0` oznacza to samo (bez limitu). Dodatnia liczba całkowita N włącza limity N/dzień, 5N/tydzień i 20N/miesiąc. Nieprawidłowe, niepuste wartości powodują użycie starszych limitów: 1000/dzień, 5000/tydzień i 20000/miesiąc. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksymalny dozwolony rozmiar treści żądania. Odrzuca ładunki przekraczające ten limit. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Rzeczywiste treści żądań o rozmiarze równym temu progowi lub większym uzyskują atomową, lokalną dla procesu ciężką dzierżawę dopuszczenia przed analizą JSON (ścieżka BYTE, w tym `POST /v1/responses`). Obowiązuje ten sam mechanizm awaryjny przy zdrowym zapasie zasobów z [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), co w przypadku obciążeń o złożonej strukturze; nadal podlega ograniczeniu `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), aby problem [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) nie powrócił. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Twardy limit dla tras czatu, egzekwowany względem bajtów odczytanych podczas ograniczonego pobierania, w tym dla żądań z brakującym, nieprawidłowym lub nierzetelnym nagłówkiem `Content-Length`; przekroczenie limitu skutkuje odpowiedzią `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nieustawione — brak limitu liczby żądań)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ten starszy limit LICZBY żądań obowiązuje teraz tylko po jawnym ustawieniu. Gdy pozostaje nieustawiony (domyślnie), przyjmowanie obciążających żądań czatu jest zamiast tego kontrolowane przez `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatycznie wyznaczany budżet BAJTÓW, którego rozmiar wynika z rzeczywistego limitu pamięci procesu w **jednym procesie** (jedna sterta V8). Dwa nakładające się żądania `/v1/responses` po około 750 tys. tokenów powodują przerwanie stert o rozmiarze około 12 GiB (#7849) — jest to ostrzeżenie dotyczące **budżetu pamięci**, a nie sztywny maksymalny limit produktu wynoszący 2. Proces w dobrym stanie (sterta poniżej współczynnika odrzucania) MOŻE przyjmować więcej równoczesnych długich żądań `/v1/responses` za pośrednictwem `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dziesiątki długotrwałych klientów SSE (40–50) zależą od sterty + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, a nie od „maksymalnie 2”. Bezrefleksyjne zwiększanie tej wartości, aby „wykorzystać hosta”, ponownie prowadzi do #7849. Zwiększaj liczbę **stert** za pomocą **N niezależnych `DATA_DIR`** (#11024); nigdy nie używaj `replicas>1` dla jednego pliku SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(wyznaczane automatycznie)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nadpisanie automatycznie wyznaczanego budżetu bajtów pobierania (25% niższego z limitów pamięci V8/cgroup, podzielone przez 8-krotne przejściowe zwiększenie użycia pamięci). Wartości wyznaczone i jawne są ograniczane do zakresu 8 MiB–2 GiB. Treść większa niż efektywny budżet natychmiast kończy się błędem `413 body_exceeds_budget`; rywalizacja między treściami, które pojedynczo mogą zostać obsłużone, nadal skutkuje ponawialnym błędem `503`. 40–50 równoczesnych długotrwałych klientów SSE zależy od tego budżetu + sterty, a nie od sztywnego limitu „maksymalnie 2”. Przed dostrajaniem odczytaj `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` pod adresem `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Współczynnik odrzucania przy presji na stertę (`heapUsed / heap_size_limit`) dla przyjmowania obciążających żądań BAJTOWYCH i STRUKTURALNYCH (#10183, #10268, #10437). Równoczesne obciążające żądanie przekraczające `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` jest odrzucane z ponawialnym błędem `503` tylko wtedy, gdy sterta jest RÓWNIEŻ na poziomie tego współczynnika lub powyżej niego; przy zdrowej stercie jest przyjmowane za pośrednictwem dodatkowej pojemności dla zdrowej sterty. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (domyślnie `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ograniczona dodatkowa pojemność szybkiej ścieżki dla zdrowej sterty (#10437), zarówno dla żądań STRUKTURALNYCH, jak i BAJTOWYCH (`admitChatRequest`, w tym treści ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez tego ograniczenia każde żądanie przy dużym obciążeniu, ale zdrowej stercie, omijałoby kontrolę przyjmowania bez żadnego limitu. Gdy taką liczbę równoczesnych dzierżaw aktywowano za pośrednictwem ścieżki zdrowej sterty, kolejne żądania przy dużym obciążeniu przechodzą do TEJ SAMEJ ścieżki ograniczonego oczekiwania/odrzucania, która jest używana przy rzeczywistej presji na stertę. `0` całkowicie wyłącza to obejście. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Liczba wiadomości, która klasyfikuje żądanie czatu jako obciążające, nawet gdy jego treść nie przekracza progu bajtowego. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Liczba narzędzi, która klasyfikuje żądanie czatu jako obciążające, nawet gdy jego treść nie przekracza progu bajtowego. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Zachowawcze oszacowanie liczby tokenów na podstawie rozmiaru ciągów znaków, które klasyfikuje żądanie jako obciążające; jest to przybliżenie kosztu przyjęcia, a nie tokenizacja używana przez dostawcę do rozliczeń. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (wyłączone) | `src/shared/middleware/chatBodyAdmission.ts` | Opcjonalny, włączany jawnie limit historii czatu. Domyślnie wyłączony: liczba wiadomości jest zasadą wdrożenia, a nie uniwersalną właściwością żądania, a ograniczanie jej w tym miejscu odrzuca rozmowy końcowym błędem `413`, zanim potok kompresji zdoła umożliwić ich obsłużenie. Wzrost sterty jest ograniczany przez `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` oraz odrzucanie przy presji na stertę. W przypadku wdrożeń z ograniczoną pamięcią, które wymagają sztywnego limitu, ustaw wartość dodatnią; przekroczenie limitu skutkuje wtedy ustrukturyzowaną odpowiedzią `413` informującą o wymaganej kompresji. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Twardy limit niestreamowanej odpowiedzi z systemu nadrzędnego, która jest w całości buforowana w pamięci. Po jego przekroczeniu odczyt z systemu nadrzędnego jest anulowany, a żądanie szybko kończy się niepowodzeniem, zamiast powodować wzrost nieograniczonego ciągu znaków aż do wyczerpania sterty. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksymalna liczba bajtów przesyłanych z nagłówków odpowiedzi serwera nadrzędnego. Po przekroczeniu limitu nagłówki o niższym priorytecie (np. niestandardowe `x-codex-*`, `x-oai-request-id`) są pomijane, aby zmieścić się w typowych limitach nagłówków odwrotnego serwera proxy. Ustaw wyższą wartość, aby przekazywać więcej metadanych serwera nadrzędnego kosztem większego rozmiaru nagłówków odpowiedzi. | -| `CORS_ORIGIN` | _(nieustawione)_ | `src/server/cors/origins.ts` | Starsza lista dozwolonych pojedynczych źródeł CORS. W nowych wdrożeniach preferuj `CORS_ALLOWED_ORIGINS`. CORS jest przeznaczony wyłącznie dla przeglądarkowych klientów API wysyłających żądania między źródłami; uwierzytelnione operacje zapisu w panelu używają zamiast tego żądań z tego samego źródła oraz ochrony CSRF powiązanej z sesją. | -| `CORS_ALLOWED_ORIGINS` | _(nieustawione)_ | `src/server/cors/origins.ts` | Rozdzielana przecinkami lista dozwolonych źródeł CORS. Symbol wieloznaczny nie jest wysyłany, chyba że jawnie skonfigurowano `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mechanizm awaryjny przeznaczony wyłącznie do programowania, który odsyła dowolną wartość `Origin` przeglądarki. Nie włączaj go we współdzielonych ani produkcyjnych wdrożeniach. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokuje wywołania dostawców kierowane do prywatnych, zwrotnych lub lokalnych dla łącza zakresów adresów IP. Wyłączaj wyłącznie w izolowanych środowiskach testowych. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Zezwala na adresy URL dostawców wskazujące sieci prywatne/lokalne (localhost, 192.168.x.x, 10.x.x.x itd.). **WYMAGANE w przypadku dostawców hostowanych samodzielnie** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Gdy ustawiono `false`, panel odrzuca walidację lokalnych adresów URL. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Zezwala na dodawanie/walidowanie dostawców pod adresami lokalnymi/prywatnymi (127.0.0.1, localhost, LAN, zakresy prywatne) — wyłącznie w ścieżce walidacji dostawcy. **Wartość domyślna to `true`** (priorytet dla środowiska lokalnego); ustaw `false`, aby wymusić ścisłe blokowanie wszystkich adresów poza publicznymi. Punkty końcowe metadanych chmurowych (169.254.169.254, metadata.google.internal) pozostają zablokowane niezależnie od tego ustawienia. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Zezwala trasom `/v1/audio/*` (transkrypcje, synteza mowy, tłumaczenia) na używanie węzła dostawcy zgodnego z OpenAI, hostowanego poza localhost. Domyślnie wyłączone — kierowanie dźwięku do zdalnego hosta zmienia tożsamość ruchu wychodzącego i musi być świadomą decyzją operatora. Węzły zwrotne/prywatne (localhost, 127.0.0.1, 172.16-31.x) są zawsze dozwolone i to ustawienie na nie nie wpływa. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Zezwala `POST /v1/rerank` (oraz zwrotnemu etapowi ponownego szeregowania silnika pamięci) na używanie węzła dostawcy zgodnego z OpenAI, hostowanego poza localhost — urządzenia w sieci LAN lub partnera Tailscale z uruchomionym TEI, Infinity, vLLM itd. Domyślnie wyłączone — kierowanie ruchu do zdalnego hosta zmienia tożsamość ruchu wychodzącego i musi być świadomą decyzją operatora. Węzły zwrotne (localhost, 127.0.0.1, 172.16-31.x) są zawsze dozwolone i to ustawienie na nie nie wpływa. Zdalne węzły muszą również spełniać zasady dotyczące wychodzących adresów URL dostawców (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); ruch nigdy nie jest kierowany do hostów metadanych chmurowych. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Gdy OIDC jest włączone, wyłącza logowanie hasłem, aby użytkownicy mogli uwierzytelniać się wyłącznie za pomocą logowania jednokrotnego OIDC. Akceptowany jest również prosty alias `OIDC_DISABLE_PASSWORD_LOGIN`; flaga funkcji panelu o tym samym kluczu ma pierwszeństwo. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Prosty alias zmiennej `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Zmienna | Wartość domyślna | Plik źródłowy | Opis | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sól łączona z identyfikatorami sprzętowymi w celu utworzenia odcisku palca maszyny. Należy ją zmienić dla każdego wdrożenia, aby zapewnić izolację. | +| `OMNIROUTE_CLI_SALT` | _(nieustawiona = losowa sól dla każdej instalacji, przechowywana w `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sól HMAC służąca do wyprowadzania lokalnego tokenu uwierzytelniającego CLI. Ustawienie tej wartości powoduje rotację wszystkich tokenów CLI na maszynie i zawsze ma pierwszeństwo przed przechowywaną solą. Zobacz `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Ustawia flagę `Secure` dla plików cookie sesji. Podczas działania za HTTPS wartość **musi wynosić `true`**. | +| `REQUIRE_API_KEY` | `false` | Oprogramowanie pośredniczące API | Gdy ma wartość `true`, wszystkie żądania proxy `/v1/*` muszą zawierać prawidłowy klucz API. **Ta flaga nie kontroluje dostępu do `GET /v1/models`**, który zamiast tego podlega konfiguracji logowania do panelu (`requireAuthForModels`) — dlatego odpowiedź `401` z `/v1/models` NIE oznacza, że wnioskowanie jest chronione. Zobacz `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Umożliwia ujawnianie pełnych wartości kluczy API w interfejsie panelu. Konfigurowalne z poziomu flag funkcji panelu; stanowi zagrożenie bezpieczeństwa we współdzielonych instancjach. | +| `NO_LOG_API_KEY_IDS` | _(pusta)_ | `src/lib/compliance/index.ts` | Rozdzielone przecinkami identyfikatory kluczy API, dla których pomijane jest rejestrowanie żądań (zgodność z RODO). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nieustawiona = bez limitu)_ | `src/shared/utils/apiKeyPolicy.ts` | Zapasowy dzienny budżet żądań stosowany do kluczy API, których kolumna `rate_limits` ma wartość null. Wartość nieustawiona lub pusta: brak domyślnego limitu (#2289, #11017). `0` oznacza to samo (bez limitu). Dodatnia liczba całkowita N włącza limity N/dzień, 5N/tydzień i 20N/miesiąc. Nieprawidłowe, niepuste wartości powodują użycie starszych limitów: 1000/dzień, 5000/tydzień i 20000/miesiąc. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maksymalny dozwolony rozmiar treści żądania. Odrzuca ładunki przekraczające ten limit. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Rzeczywiste treści żądań o rozmiarze równym temu progowi lub większym uzyskują atomową, lokalną dla procesu ciężką dzierżawę dopuszczenia przed analizą JSON (ścieżka BYTE, w tym `POST /v1/responses`). Obowiązuje ten sam mechanizm awaryjny przy zdrowym zapasie zasobów z [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), co w przypadku obciążeń o złożonej strukturze; nadal podlega ograniczeniu `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), aby problem [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) nie powrócił. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Twardy limit dla tras czatu, egzekwowany względem bajtów odczytanych podczas ograniczonego pobierania, w tym dla żądań z brakującym, nieprawidłowym lub nierzetelnym nagłówkiem `Content-Length`; przekroczenie limitu skutkuje odpowiedzią `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nieustawione — brak limitu liczby żądań)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ten starszy limit LICZBY żądań obowiązuje teraz tylko po jawnym ustawieniu. Gdy pozostaje nieustawiony (domyślnie), przyjmowanie obciążających żądań czatu jest zamiast tego kontrolowane przez `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automatycznie wyznaczany budżet BAJTÓW, którego rozmiar wynika z rzeczywistego limitu pamięci procesu w **jednym procesie** (jedna sterta V8). Dwa nakładające się żądania `/v1/responses` po około 750 tys. tokenów powodują przerwanie stert o rozmiarze około 12 GiB (#7849) — jest to ostrzeżenie dotyczące **budżetu pamięci**, a nie sztywny maksymalny limit produktu wynoszący 2. Proces w dobrym stanie (sterta poniżej współczynnika odrzucania) MOŻE przyjmować więcej równoczesnych długich żądań `/v1/responses` za pośrednictwem `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dziesiątki długotrwałych klientów SSE (40–50) zależą od sterty + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, a nie od „maksymalnie 2”. Bezrefleksyjne zwiększanie tej wartości, aby „wykorzystać hosta”, ponownie prowadzi do #7849. Zwiększaj liczbę **stert** za pomocą **N niezależnych `DATA_DIR`** (#11024); nigdy nie używaj `replicas>1` dla jednego pliku SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(wyznaczane automatycznie)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** nadpisanie automatycznie wyznaczanego budżetu bajtów pobierania (25% niższego z limitów pamięci V8/cgroup, podzielone przez 8-krotne przejściowe zwiększenie użycia pamięci). Wartości wyznaczone i jawne są ograniczane do zakresu 8 MiB–2 GiB. Treść większa niż efektywny budżet natychmiast kończy się błędem `413 body_exceeds_budget`; rywalizacja między treściami, które pojedynczo mogą zostać obsłużone, nadal skutkuje ponawialnym błędem `503`. 40–50 równoczesnych długotrwałych klientów SSE zależy od tego budżetu + sterty, a nie od sztywnego limitu „maksymalnie 2”. Przed dostrajaniem odczytaj `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` pod adresem `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Współczynnik odrzucania przy presji na stertę (`heapUsed / heap_size_limit`) dla przyjmowania obciążających żądań BAJTOWYCH i STRUKTURALNYCH (#10183, #10268, #10437). Równoczesne obciążające żądanie przekraczające `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` jest odrzucane z ponawialnym błędem `503` tylko wtedy, gdy sterta jest RÓWNIEŻ na poziomie tego współczynnika lub powyżej niego; przy zdrowej stercie jest przyjmowane za pośrednictwem dodatkowej pojemności dla zdrowej sterty. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (domyślnie `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ograniczona dodatkowa pojemność szybkiej ścieżki dla zdrowej sterty (#10437), zarówno dla żądań STRUKTURALNYCH, jak i BAJTOWYCH (`admitChatRequest`, w tym treści ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez tego ograniczenia każde żądanie przy dużym obciążeniu, ale zdrowej stercie, omijałoby kontrolę przyjmowania bez żadnego limitu. Gdy taką liczbę równoczesnych dzierżaw aktywowano za pośrednictwem ścieżki zdrowej sterty, kolejne żądania przy dużym obciążeniu przechodzą do TEJ SAMEJ ścieżki ograniczonego oczekiwania/odrzucania, która jest używana przy rzeczywistej presji na stertę. `0` całkowicie wyłącza to obejście. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Liczba wiadomości, która klasyfikuje żądanie czatu jako obciążające, nawet gdy jego treść nie przekracza progu bajtowego. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Liczba narzędzi, która klasyfikuje żądanie czatu jako obciążające, nawet gdy jego treść nie przekracza progu bajtowego. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Zachowawcze oszacowanie liczby tokenów na podstawie rozmiaru ciągów znaków, które klasyfikuje żądanie jako obciążające; jest to przybliżenie kosztu przyjęcia, a nie tokenizacja używana przez dostawcę do rozliczeń. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (wyłączone) | `src/shared/middleware/chatBodyAdmission.ts` | Opcjonalny, włączany jawnie limit historii czatu. Domyślnie wyłączony: liczba wiadomości jest zasadą wdrożenia, a nie uniwersalną właściwością żądania, a ograniczanie jej w tym miejscu odrzuca rozmowy końcowym błędem `413`, zanim potok kompresji zdoła umożliwić ich obsłużenie. Wzrost sterty jest ograniczany przez `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` oraz odrzucanie przy presji na stertę. W przypadku wdrożeń z ograniczoną pamięcią, które wymagają sztywnego limitu, ustaw wartość dodatnią; przekroczenie limitu skutkuje wtedy ustrukturyzowaną odpowiedzią `413` informującą o wymaganej kompresji. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Twardy limit niestreamowanej odpowiedzi z systemu nadrzędnego, która jest w całości buforowana w pamięci. Po jego przekroczeniu odczyt z systemu nadrzędnego jest anulowany, a żądanie szybko kończy się niepowodzeniem, zamiast powodować wzrost nieograniczonego ciągu znaków aż do wyczerpania sterty. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksymalna liczba bajtów przesyłanych z nagłówków odpowiedzi serwera nadrzędnego. Po przekroczeniu limitu nagłówki o niższym priorytecie (np. niestandardowe `x-codex-*`, `x-oai-request-id`) są pomijane, aby zmieścić się w typowych limitach nagłówków odwrotnego serwera proxy. Ustaw wyższą wartość, aby przekazywać więcej metadanych serwera nadrzędnego kosztem większego rozmiaru nagłówków odpowiedzi. | +| `CORS_ORIGIN` | _(nieustawione)_ | `src/server/cors/origins.ts` | Starsza lista dozwolonych pojedynczych źródeł CORS. W nowych wdrożeniach preferuj `CORS_ALLOWED_ORIGINS`. CORS jest przeznaczony wyłącznie dla przeglądarkowych klientów API wysyłających żądania między źródłami; uwierzytelnione operacje zapisu w panelu używają zamiast tego żądań z tego samego źródła oraz ochrony CSRF powiązanej z sesją. | +| `CORS_ALLOWED_ORIGINS` | _(nieustawione)_ | `src/server/cors/origins.ts` | Rozdzielana przecinkami lista dozwolonych źródeł CORS. Symbol wieloznaczny nie jest wysyłany, chyba że jawnie skonfigurowano `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mechanizm awaryjny przeznaczony wyłącznie do programowania, który odsyła dowolną wartość `Origin` przeglądarki. Nie włączaj go we współdzielonych ani produkcyjnych wdrożeniach. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Starszy alias. Przełącznik w panelu (nadpisanie w bazie danych) jest odczytywany przed wartością tej zmiennej środowiskowej; wartość `false`, `0`, `no` lub `off` w dowolnym z nich wyłącza sprawdzanie hostów tak, jak robi to `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, nawet gdy przełącznik panelu tej flagi jest zapisany jako wyłączony; przy pozostałych wartościach o działaniu mechanizmu ochronnego decydują dwie flagi poniżej. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Wyłącza sprawdzanie hostów przez mechanizm ochronny wychodzących adresów URL, w tym blokadę metadanych chmury (cały zakres 169.254.0.0/16, metadata.google.internal), na ścieżkach dostawców podlegających trybowi ochrony: walidacja adresów URL, wykrywanie modeli, bazowe adresy URL węzłów dostawców (w tym zdalne węzły ponownego szeregowania) oraz test proxy-fallback, a także na innych wychodzących ścieżkach powiązanych z dostawcami, takich jak zdalne pobieranie obrazów/mediów. Zezwala również na prywatne adresy docelowe webhooków, z wyjątkiem punktów końcowych metadanych chmury/link-local, które pozostają zablokowane nawet przy włączonej fladze. Żądania czatu wysyłane przez `BaseExecutor.execute()` zachowują swoje sprawdzanie metadanych chmury. Dostawcy lokalni działają z ustawieniami domyślnymi: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (domyślnie `true`) już zezwala na lokalne adresy URL i adresy LAN na ścieżkach walidacji, wykrywania i węzłów dostawców; test proxy-fallback i prywatne cele webhooków uwzględniają wyłącznie tę flagę i blokują hosty lokalne/LAN, dopóki jest wyłączona. Nadpisuje `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Wartość zapisana z przełącznika panelu ma pierwszeństwo przed tą zmienną. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Zezwala na adresy URL dostawców pod adresami lokalnymi/prywatnymi (127.0.0.1, localhost, LAN, zakresy prywatne) dla walidacji dostawców, wykrywania modeli, czatu i pozostałych wychodzących ścieżek powiązanych z dostawcami, którymi zarządza tryb ochrony. **Wartość domyślna to `true`** (priorytet dla środowiska lokalnego): mechanizm ochronny blokuje wtedy punkty końcowe metadanych chmury — cały zakres 169.254.0.0/16 oraz znane nazwy hostów metadanych (metadata.google.internal, metadata.goog, IMDS Alibaba/Azure/Oracle, fd00:ec2::254). Ustaw `false` dla trybu wyłącznie publicznego, który blokuje także hosty prywatne i zwrotne. Mechanizm sprawdza nazwę hosta lub literał IP dokładnie tak, jak został zapisany, przed jakimkolwiek odpytaniem DNS; w trybie wyłącznie publicznym otoka bezpiecznego pobierania dodatkowo rozwija nazwę hosta i odrzuca każdą prywatną odpowiedź. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` wyłącza te sprawdzenia na ścieżkach wymienionych w wierszu tej flagi. Wbudowani dostawcy lokalni, tacy jak LM Studio, Ollama, vLLM, Llamafile i Triton, pomijają wszystkie sprawdzania hostów przy walidacji kluczy i czacie. Wartość zapisana z przełącznika panelu ma pierwszeństwo przed tą zmienną. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Zezwala trasom `/v1/audio/*` (transkrypcje, synteza mowy, tłumaczenia) na używanie węzła dostawcy zgodnego z OpenAI, hostowanego poza localhost. Domyślnie wyłączone — kierowanie dźwięku do zdalnego hosta zmienia tożsamość ruchu wychodzącego i musi być świadomą decyzją operatora. Węzły zwrotne/prywatne (localhost, 127.0.0.1, 172.16-31.x) są zawsze dozwolone i to ustawienie na nie nie wpływa. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Zezwala `POST /v1/rerank` (oraz zwrotnemu etapowi ponownego szeregowania silnika pamięci) na używanie węzła dostawcy zgodnego z OpenAI, hostowanego poza localhost — urządzenia w sieci LAN lub partnera Tailscale z uruchomionym TEI, Infinity, vLLM itd. Domyślnie wyłączone — kierowanie ruchu do zdalnego hosta zmienia tożsamość ruchu wychodzącego i musi być świadomą decyzją operatora. Węzły zwrotne (localhost, 127.0.0.1, 172.16-31.x) są zawsze dozwolone i to ustawienie na nie nie wpływa. Bazowy adres URL zdalnego węzła musi również spełniać zasady dotyczące wychodzących adresów URL dostawców (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Gdy OIDC jest włączone, wyłącza logowanie hasłem, aby użytkownicy mogli uwierzytelniać się wyłącznie za pomocą logowania jednokrotnego OIDC. Akceptowany jest również prosty alias `OIDC_DISABLE_PASSWORD_LOGIN`; flaga funkcji panelu o tym samym kluczu ma pierwszeństwo. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Prosty alias zmiennej `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Lista kontrolna zabezpieczeń diff --git a/docs/i18n/pt-BR/CONTRIBUTING.md b/docs/i18n/pt-BR/CONTRIBUTING.md index 2c37bbfdf4bb..2295348707f7 100644 --- a/docs/i18n/pt-BR/CONTRIBUTING.md +++ b/docs/i18n/pt-BR/CONTRIBUTING.md @@ -399,7 +399,7 @@ Escreva testes unitários em `tests/unit/` que cubram, no mínimo: --- -## Checklist da Pull Request +## Checklist do Pull Request - [ ] Os testes passam (`npm test`) - [ ] O lint passa (`npm run lint`) @@ -407,14 +407,14 @@ Escreva testes unitários em `tests/unit/` que cubram, no mínimo: - [ ] Tipos TypeScript adicionados para novas funções e interfaces públicas - [ ] Nenhum segredo ou valor de fallback codificado diretamente - [ ] Credenciais públicas upstream incorporadas por meio de `resolvePublicCred()` (consulte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nunca como literais -- [ ] As respostas de erro passam por `buildErrorBody()` / `sanitizeErrorMessage()` — nenhum stack trace bruto nos corpos das respostas (consulte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Respostas de erro passam por `buildErrorBody()` / `sanitizeErrorMessage()` — nenhum stack trace bruto nos corpos das respostas (consulte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Comandos de shell (`exec` / `spawn`) passam valores de runtime por meio de `env`, não por interpolação de strings - [ ] Todas as entradas são validadas com esquemas Zod - [ ] **Fragmento** do changelog adicionado em `changelog.d/{features|fixes|maintenance}/-.md` para alterações visíveis aos usuários (consulte [`changelog.d/README.md`](./changelog.d/README.md)) — **não** edite `CHANGELOG.md` diretamente; os fragmentos são agregados no momento do lançamento e nunca geram conflitos entre PRs - [ ] Documentação atualizada (se aplicável) -- [ ] Nenhum novo alerta do CodeQL / Secret-Scanning aberto, ou cada um foi descartado com uma justificativa técnica que faça referência ao documento relevante em `docs/security/` +- [ ] Nenhum novo alerta do CodeQL / Secret-Scanning foi aberto, ou cada alerta foi descartado com uma justificativa técnica que faça referência ao documento relevante em `docs/security/` - [ ] Rotas que iniciam processos filhos (`/api/mcp/`, `/api/cli-tools/runtime/`) classificadas como `isLocalOnlyPath()` em `src/server/authz/routeGuard.ts` — consulte a [Regra Rígida nº 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Nenhum trailer `Co-Authored-By` nas mensagens de commit — os commits devem aparecer exclusivamente sob a identidade Git do proprietário do repositório (Regra Rígida nº 16) +- [ ] Nenhum trailer `Co-authored-by` de IA/bot nas mensagens de commit (Regra Rígida nº 16) — colaboradores humanos cujo trabalho for reutilizado recebem crédito com trailers padrão `Co-authored-by: Name ` --- diff --git a/docs/i18n/pt-BR/docs/architecture/QUALITY_GATES.md b/docs/i18n/pt-BR/docs/architecture/QUALITY_GATES.md index 35a0c8333ce3..464a90e944fa 100644 --- a/docs/i18n/pt-BR/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/pt-BR/docs/architecture/QUALITY_GATES.md @@ -20,173 +20,166 @@ replicação independente de ferramentas do mesmo sistema, consulte o ### Admissão de candidatos Os workflows de CI e Quality Gates emitem, cada um, um veredito estável: `Gate / CI` e -`Gate / Quality`. A política de admissão versionada deles enumera cada job upstream -como obrigatório ou consultivo. Um job obrigatório aplicável deve ser bem-sucedido: -resultados ausentes, cancelados, ignorados, pendentes e desconhecidos não podem -estabelecer PASS. Uma classificação válida como somente documentação ou somente -catálogo pode tornar uma lane de código não aplicável; um PR em rascunho não é um -candidato aceito. Um rótulo `hotfix` não dispensa evidências. - -Ambos os workflows abrangem PRs e pushes para branches main/release, acionamento -manual e eventos de merge group. Push, acionamento e merge group executam a seleção -completa. Forks e merge groups usam runners hospedados para jobs que, de outra forma, -selecionariam runners auto-hospedados; deve-se verificar se há capacidade hospedada -suficiente antes da implantação. - -Cada comprovante JSON identifica o SHA do checkout, a execução do workflow e a tentativa. -A CLI rejeita uma incompatibilidade entre o SHA do checkout e o do evento. Os testes -de workflow vinculam a associação à política à lista `needs` do job de veredito, para -que uma lane nova ou removida não desapareça silenciosamente. Os comprovantes abrangem -seu próprio workflow, não a publicação, a implantação nem os detalhes internos de um -scanner consultivo existente. Ativar ambos os nomes de verificação nas regras da branch -é uma alteração administrativa separada; adicionar esses jobs não protege uma branch -por si só. - -### Inventário de varredura estática - -O inventário versionado de aliases npm e a associação à varredura estática ficam em +`Gate / Quality`. Sua política de admissão versionada enumera cada job upstream +como obrigatório ou consultivo. Um job obrigatório aplicável deve ser bem-sucedido: resultados +ausentes, cancelados, ignorados, pendentes e desconhecidos não podem estabelecer PASS. Uma +classificação válida de somente documentação ou somente catálogo pode tornar uma lane de código não aplicável; +um PR em rascunho não é um candidato aceito. Um label `hotfix` não dispensa evidências. + +Ambos os workflows abrangem PRs e pushes para branches main/release, acionamento manual e +eventos de merge group. Push, dispatch e merge group executam a seleção completa. Forks +e merge groups usam runners hospedados para jobs que, de outra forma, selecionariam runners +auto-hospedados; deve-se verificar se há capacidade hospedada suficiente antes da implantação. + +Cada recibo JSON identifica o SHA do checkout, a execução e a tentativa do workflow. +A CLI rejeita uma divergência entre o SHA do checkout e o do evento. Os testes do workflow vinculam a participação +na política à lista `needs` do job de veredito, para que uma lane nova ou removida não possa desaparecer silenciosamente. +Os recibos abrangem seu próprio workflow, não a publicação, a implantação nem os detalhes internos +de um scanner consultivo existente. Ativar ambos os nomes de checks nas regras de branch é uma +alteração administrativa separada; adicionar esses jobs não protege, por si só, uma branch. + +### Inventário da varredura estática + +O inventário versionado de aliases do npm e a composição da varredura estática ficam em `config/quality/gate-manifest.json`. Execute `npm run check:gate-manifest` para validar -os nomes dos scripts e os comandos exatos em relação ao `package.json`; adições, remoções -e divergências de comandos fazem falhar tanto o hook local quanto os jobs de classificação -de alterações na CI. Um alias não é um job de workflow, uma instância de matriz nem um -caso de teste: essas contagens não devem ser apresentadas como equivalentes. +os nomes dos scripts e os comandos exatos em relação ao `package.json`; adições, remoções e +alterações de comandos fazem falhar tanto o hook local quanto os jobs de classificação de alterações na CI. +Um alias não é um job de workflow, uma instância de matriz nem um caso de teste: essas contagens não devem +ser apresentadas como equivalentes. Use `npm run quality:scan -- --list` ou `npm run quality:scan:fast -- --list` para inspecionar os aliases selecionados sem executá-los. O runner invoca o -entrypoint npm, portanto seu runtime (incluindo Bun, quando configurado) é preservado. -O manifesto registra os aliases externos a esses perfis como invocados separadamente, -e comandos de manutenção são proibidos em perfis de varredura somente leitura. +ponto de entrada do npm, portanto seu runtime (incluindo Bun, quando configurado) é preservado. +O manifesto registra aliases fora desses perfis como invocados separadamente, e +comandos de manutenção são proibidos em perfis de varredura somente leitura. Esses perfis abrangem apenas a varredura estática. Eles não certificam testes do produto, -cobertura, empacotamento, verificações externas nem a aceitação completa de um candidato -para release. A admissão do workflow usa o `config/quality/admission-policy.json` -vinculado e `scripts/quality/admission-verdict.mjs`. Os perfis de observação de release -permanecem separados; inspecione suas verificações e seus comprovantes aplicáveis de -forma independente. O inventário descritivo abaixo é uma referência, não uma prova de -que um gate foi realmente executado. +cobertura, empacotamento, verificações externas nem a aceitação completa de um candidato para release. +A admissão do workflow usa `config/quality/admission-policy.json` e +`scripts/quality/admission-verdict.mjs` vinculados. Os perfis do observador de release permanecem separados; +inspecione suas verificações e seus recibos aplicáveis de forma independente. O inventário +descritivo abaixo é uma referência, não uma prova de que um gate foi realmente executado. Os scripts ficam em `scripts/check/` (gates de política) e `scripts/quality/` (mecanismo de ratchet). A fonte da verdade da CI é `.github/workflows/ci.yml`. -### Caminho rápido de PR de release (`quality.yml`) +### Caminho rápido para PRs de release (`quality.yml`) -`.github/workflows/quality.yml` complementa a CI em PRs de main/release, pushes para -branches protegidas, acionamentos e merge groups. Os PRs usam verificações rápidas -filtradas por caminho. O build duplicado, permanentemente desabilitado, foi removido; -as verificações reais de build/empacotamento/inicialização permanecem na CI. +`.github/workflows/quality.yml` complementa a CI em PRs de main/release, pushes para branches +protegidas, dispatch e merge groups. Os PRs usam verificações rápidas filtradas por caminho. O build +duplicado permanentemente desabilitado foi removido; as verificações reais de build/package/boot permanecem na CI. -| Job | Escopo | Bloqueante | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | -| `Docs Gates (fast-path)` | PRs de documentação/código; referências de documentação da API e docs-all | Sim | -| `Fast Quality Gates` | PRs de código; verificações estáticas, verificação de tipos, verificação de tipos do dashboard, testes unitários impactados | Sim | -| `Forgotten sibling tests` | PRs de código; módulos alterados rastreados até consumidores estáticos e testes irmãos candidatos; caminhos de barrel e importação dinâmica são relatados como diagnósticos consultivos, com exceções referenciadas na lista de permissões | **Consultivo** | -| `Vitest (fast-path)` | PRs de código; suíte rápida do vitest | Sim | -| `Unit Tests fast-path` | PRs de código; suíte unitária de 4 shards | Sim | -| `No new ESLint warnings` | PRs de código; proteção de lint ciente de supressões | Sim, incluindo forks | -| `Merge integrity (changelog + generated skills)` | PRs que não sejam rascunhos; sincronização do changelog e das skills geradas | Sim, incluindo forks | +| Job | Escopo | Bloqueante | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | PRs de documentação/código; referências da documentação da API e toda a documentação | Sim | +| `Fast Quality Gates` | PRs de código; verificações estáticas, typecheck, typecheck do dashboard e testes unitários impactados | Sim | +| `Forgotten sibling tests` | PRs de código; módulos alterados rastreados até consumidores estáticos e possíveis testes irmãos; caminhos por barrel e importação dinâmica são relatados como diagnósticos consultivos, com exceções referenciadas na allowlist | **Consultivo** | +| `Vitest (fast-path)` | PRs de código; suíte rápida do vitest | Sim | +| `Unit Tests fast-path` | PRs de código; suíte de testes unitários com 4 shards | Sim | +| `No new ESLint warnings` | PRs de código; proteção de lint com reconhecimento de supressões | Sim, incluindo forks | +| `Merge integrity (changelog + generated skills)` | PRs que não sejam rascunhos; sincronização do changelog e das skills geradas | Sim, incluindo forks | #### Relatório de testes irmãos esquecidos -`npm run check:forgotten-sibling-tests` reutiliza o resolvedor de imports por trás do -mapa de impacto de testes. Para cada módulo de produção alterado, ele relata cadeias -determinísticas `módulo/símbolo alterado -> consumidor estático -> teste irmão candidato` -quando o teste candidato está ausente do diff da pull request. O resumo em Markdown e -o resultado JSON são mantidos como o artefato de workflow `forgotten-sibling-tests` -para calibração antes de qualquer implantação bloqueante. +`npm run check:forgotten-sibling-tests` reutiliza o resolvedor de imports por trás do mapa de impacto dos testes. +Para cada módulo de produção alterado, ele relata cadeias determinísticas +`módulo/símbolo alterado -> consumidor estático -> possível teste irmão` quando o teste +em questão está ausente do diff do pull request. O resumo em Markdown e o resultado em JSON são mantidos como +o artefato de workflow `forgotten-sibling-tests` para calibração antes de qualquer implantação bloqueante. Reexportações de barrel e importações dinâmicas servem apenas como diagnósticos de resolução; elas nunca criam uma constatação bloqueante. As exceções revisadas ficam em `config/quality/forgotten-sibling-allowlist.json`. Cada entrada deve nomear o consumidor e o teste candidato, fornecer uma justificativa específica e incluir um link para uma issue ou um pull request do GitHub. Entradas malformadas -falham de forma fechada. As exceções não podem suprimir um teste candidato excluído nem um diff que adicione `.skip`/`.todo`; -o enfraquecimento de asserções e outros tipos de mascaramento continuam sob responsabilidade do gate +falham de forma restritiva. As exceções não podem suprimir um teste candidato excluído nem um diff que adicione `.skip`/`.todo`; +o enfraquecimento de asserções e outras formas de mascaramento continuam sob responsabilidade do gate `check:test-masking`, que é bloqueante de forma independente. ### Job: `lint` -É executado em todo PR para `main`. Bloqueia o merge em caso de falha. - -| Script (`npm run ...`) | Valida | Bloqueante | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | A versão do Node.js está dentro do intervalo compatível | Sim | -| `check:cycles` | Importações circulares — todos os módulos de `src/` + `open-sse/` | Sim | -| `check:route-validation:t06` | Schemas Zod presentes em todas as rotas (política do Tier 6) | Sim | -| `check:any-budget:t11` | A contagem de `@ts-expect-error // any` não excede o orçamento (catraca do Tier 11) | Sim | -| `check:provider-consistency` | Cada provedor em `providers.ts` possui uma entrada correspondente em `providerRegistry.ts` (e vice-versa, dentro da lista de permissões) | Sim | -| `check:model-lifecycle` | As três tabelas de roteamento mantidas manualmente permanecem consistentes com o snapshot de ciclo de vida versionado (#11503): `FITNESS_TABLE` (`taskFitness.ts`) não pontua nenhum id descontinuado que `REGISTRY` possa rotear; cada destino de `BUILT_IN_ALIASES` está presente em `REGISTRY` e ausente do snapshot de ids descontinuados; cada id descontinuado ainda presente em `REGISTRY` é encaminhado ou listado em `allowedRetiredInCatalog`; e nenhuma origem ou destino de `DEFAULT_DEGRADATION_MAP` aparece como descontinuado nesse snapshot. Isso não comprova que um modelo esteja sendo fornecido atualmente por um upstream ativo. Offline — compara com `config/quality/model-lifecycle.json`, atualizado manualmente com `npm run quality:refresh-model-lifecycle` (rede; não integrado à CI). `allowedRetiredInCatalog` é uma catraca de redução: adicione uma entrada somente com uma issue de acompanhamento. | Sim | -| `check:fetch-targets` | Cada `fetch("/api/...")` no `src/` do lado do cliente é resolvido para um `route.ts` real | Sim | -| `check:deps` | Todas as dependências instaláveis com `npm install` em cada `package.json` do repositório estão em `dependency-allowlist.json`; pacotes novos sem versão fixada ou com risco de slopsquatting são sinalizados | Sim | -| `audit:deps` | `npm audit` (raiz + electron) — nenhum alerta de gravidade alta/crítica (sobrepõe-se ao `check:vuln-ratchet` do osv; consulte a lista de pendências de racionalização) | Sim | -| `check:lockfile` | Integridade do `package-lock.json` — registro https, hashes de integridade, nenhuma substituição de host | Sim | -| `check:licenses` | Lista de permissões de licenças SPDX para dependências de produção | Sim | -| `check:tracked-artifacts` | Nenhum artefato de build / link simbólico de `node_modules` versionado (também é executado no pre-commit do husky; o pre-push é intencionalmente leve — #6716) | Sim | -| `check:ai-attribution` | Nenhum trailer `Co-Authored-By` de IA/bot ou rodapé de geração por IA nos commits, título ou corpo do PR — Regra Rígida nº 16 (no loop de verificações rápidas de `quality.yml` para PR→`release/**` — lê o payload do evento e não faz nada fora de PRs — e uma etapa exclusiva de PR no lint de `ci.yml` para PR→`main`; também no hook `commit-msg` do husky; coautores humanos são permitidos; #14436) | -| `check:vitest-exclusions` | Toda exclusão do Vitest menciona uma issue de acompanhamento e aparece em `config/quality/vitest-exclusions.json` (#13204) | Sim | -| `check:file-size` | Nenhum arquivo-fonte excede o limite por extensão (catraca: arquivos grandes congelados na lista `frozen`) | Sim | -| `check:error-helper` | As respostas de erro em executores/handlers usam `buildErrorBody()` / `sanitizeErrorMessage()` (Regra Rígida nº 12) | Sim | -| `check:migration-numbering` | Os arquivos SQL de migração são numerados sequencialmente, sem lacunas ou duplicatas | Sim | -| `check:public-creds` | Nenhum `client_id`/`client_secret` OAuth literal ou chave Web do Firebase fora de `publicCreds.ts` (Regra Rígida nº 11) | Sim | -| `check:db-rules` | Nenhum SQL bruto fora dos módulos de `src/lib/db/`; nenhuma importação agregadora de `localDb.ts` (Regras Rígidas nº 2/nº 5) | Sim | -| `check:known-symbols` | Os executores de provedores, as estratégias de roteamento e os tradutores registrados em suas tabelas de despacho correspondem aos arquivos no disco — sem símbolos órfãos ou não declarados | Sim | -| `check:route-guard-membership` | Toda rota que inicia um processo filho é classificada por `isLocalOnlyPath()` (Regras Rígidas nº 15/nº 17) | Sim | -| `check:test-discovery` | Todo arquivo `*.test.ts` / `*.spec.ts` no repositório é coletado por pelo menos um executor de testes (ratchet: a lista de órfãos em `test-discovery-baseline.json` só pode diminuir) | Sim | -| `check:agent-skills-sync` | Os artefatos gerados de agent-skills correspondem ao catálogo de origem (sem divergências) | -| `check:provider-asset-provenance` | Os logotipos/ativos de provedores possuem uma entrada de procedência registrada | -| `lint:json` | Os arquivos de configuração JSON são analisados corretamente e atendem às regras de lint do repositório | -| `typecheck:core` | Compilação do TypeScript sem erros (somente avisos consultivos) | Sim | -| `typecheck:noimplicit:core` | `noImplicitAny` estrito — voltado para o futuro; muitos locais de chamada preexistentes ainda precisam de anotações | **Consultivo** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` limitado a `src/app/(dashboard)/**` (#7033) — a lista de permissões selecionada de 27 arquivos de `typecheck:core` não inclui nenhum TSX do dashboard, e `next build` também nunca faz a verificação de tipos dele (`next.config.mjs` define `ignoreBuildErrors: true`), portanto, regressões de identificadores órfãos nessa área (#6625/#6909) não eram detectadas pela CI. Compara com uma linha de base congelada da contagem por arquivo/por código TS (`config/quality/dashboard-typecheck-baseline.json`, o mesmo padrão de imposição de obsolescência de `check:known-symbols`) — somente erros NOVOS além da contagem registrada na linha de base fazem a verificação falhar; reduza gradualmente com `--update` quando um erro preexistente for corrigido. | Sim | +É executado em cada PR para `main`. Bloqueia o merge em caso de falha. + +| Script (`npm run ...`) | Valida | Bloqueante | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | A versão do Node.js está dentro do intervalo compatível | Sim | +| `check:cycles` | Importações circulares em todo o `src/` + `open-sse/` (baseado em AST, com `paths` do tsconfig resolvidos). A execução isolada é informativa e lista os ciclos. `check:cycles:ratchet` (o que a CI executa) bloqueia quando a contagem excede o limite máximo de `metrics.cycles` em `quality-baseline.json` — atualmente 14, `direction: down`, portanto só pode diminuir (#15159 G-01/G-02) | Sim (catraca) | +| `check:route-validation:t06` | Presença de schemas Zod em todas as rotas (política do Tier 6) | Sim | +| `check:any-budget:t11` | A contagem de `@ts-expect-error // any` não excede o orçamento (catraca do Tier 11) | Sim | +| `check:provider-consistency` | Cada provedor em `providers.ts` tem uma entrada correspondente em `providerRegistry.ts` (e vice-versa, dentro da lista de permissões) | Sim | +| `check:model-lifecycle` | As três tabelas de roteamento mantidas manualmente permanecem consistentes com o snapshot de ciclo de vida versionado (#11503): `FITNESS_TABLE` (`taskFitness.ts`) não pontua nenhum id descontinuado que `REGISTRY` possa rotear; cada destino de `BUILT_IN_ALIASES` está presente em `REGISTRY` e ausente do snapshot de ids descontinuados; cada id descontinuado ainda presente em `REGISTRY` é encaminhado ou listado em `allowedRetiredInCatalog`; e nenhuma origem ou destino de `DEFAULT_DEGRADATION_MAP` aparece como descontinuado nesse snapshot. Isso não comprova que um modelo esteja sendo disponibilizado atualmente por um upstream ativo. Offline — compara com `config/quality/model-lifecycle.json`, atualizado manualmente com `npm run quality:refresh-model-lifecycle` (rede; não integrado ao CI). `allowedRetiredInCatalog` é uma catraca de redução progressiva: adicione uma entrada somente com uma issue de acompanhamento. | Sim | +| `check:fetch-targets` | Cada `fetch("/api/...")` no `src/` do lado do cliente corresponde a um `route.ts` real | Sim | +| `check:deps` | Todas as dependências instaláveis com `npm install` em cada `package.json` do repositório estão em `dependency-allowlist.json`; pacotes novos sem versão fixada ou com nomes semelhantes a pacotes legítimos são sinalizados | Sim | +| `audit:deps` | `npm audit` (raiz + electron) — nenhum aviso de severidade alta/crítica (sobrepõe-se ao `check:vuln-ratchet` do osv; consulte o Backlog de Racionalização) | Sim | +| `check:lockfile` | Integridade de `package-lock.json` — registro https, hashes de integridade, nenhuma substituição de host | Sim | +| `check:licenses` | Lista de permissões de licenças SPDX para dependências de produção | Sim | +| `check:tracked-artifacts` | Nenhum artefato de build / link simbólico de `node_modules` versionado (também é executado no pre-commit do husky; o pre-push é intencionalmente leve — #6716) | Sim | +| `check:ai-attribution` | Nenhum trailer `Co-Authored-By` de IA/bot nem rodapé de geração por IA nos commits, no título ou no corpo do PR — Regra Rígida nº 16 (no loop de verificações rápidas de `quality.yml` para PR→`release/**` — lê o payload do evento e não realiza nenhuma operação fora de PRs — e em uma etapa exclusiva para PRs no lint de `ci.yml` para PR→`main`; também no hook `commit-msg` do husky; coautores humanos são permitidos; #14436) | +| `check:vitest-exclusions` | Toda exclusão do Vitest menciona uma issue de rastreamento e aparece em `config/quality/vitest-exclusions.json` (#13204) | Sim | +| `check:file-size` | Nenhum arquivo-fonte excede o limite por extensão (catraca: arquivos grandes congelados na lista `frozen`) | Sim | +| `check:error-helper` | As respostas de erro em executores/handlers usam `buildErrorBody()` / `sanitizeErrorMessage()` (Regra Rígida nº 12) | Sim | +| `check:migration-numbering` | Os arquivos SQL de migração são numerados sequencialmente, sem lacunas ou duplicatas | Sim | +| `check:public-creds` | Nenhum `client_id`/`client_secret` OAuth literal ou chave Web do Firebase fora de `publicCreds.ts` (Regra Rígida nº 11) | Sim | +| `check:db-rules` | Nenhum SQL bruto fora dos módulos de `src/lib/db/`; nenhuma importação de barril de `localDb.ts` (Regras Rígidas nº 2/nº 5) | Sim | +| `check:known-symbols` | Os executores de provedores, as estratégias de roteamento e os tradutores registrados em suas tabelas de despacho correspondem aos arquivos no disco — sem símbolos órfãos ou não declarados | Sim | +| `check:route-guard-membership` | Toda rota que inicia um processo filho é classificada por `isLocalOnlyPath()` (Regras Rígidas nº 15/nº 17) | Sim | +| `check:test-discovery` | Todo arquivo `*.test.ts` / `*.spec.ts` no repositório é coletado por pelo menos um executor de testes (ratchet: a lista de órfãos em `test-discovery-baseline.json` só pode diminuir) | Sim | +| `check:agent-skills-sync` | Os artefatos gerados de agent-skills correspondem ao catálogo de origem (sem divergências) | +| `check:provider-asset-provenance` | Os logotipos/ativos dos provedores possuem uma entrada de proveniência registrada | +| `lint:json` | Os arquivos de configuração JSON são analisados e atendem às regras de lint do repositório | +| `typecheck:core` | Compilação TypeScript sem erros (apenas avisos consultivos) | Sim | +| `typecheck:noimplicit:core` | `noImplicitAny` estrito — voltado para o futuro; muitos locais de chamada preexistentes ainda precisam de anotações | **Consultivo** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` restrito a `src/app/(dashboard)/**` (#7033) — a lista de permissões selecionada de 27 arquivos de `typecheck:core` não inclui nenhum TSX do dashboard, e `next build` também nunca faz a verificação de tipos deles (`next.config.mjs` define `ignoreBuildErrors: true`), portanto, regressões de identificadores órfãos nessa área (#6625/#6909) não eram detectadas pela CI. Compara com uma linha de base congelada de contagem por arquivo/por código TS (`config/quality/dashboard-typecheck-baseline.json`, o mesmo padrão de imposição contra desatualização usado por `check:known-symbols`) — somente erros NOVOS além da contagem da linha de base fazem o gate falhar; reduza-a progressivamente com `--update` quando um erro preexistente for corrigido. | Sim | ### Job: `quality-gate` -É executado após `test-coverage`. Bloqueia o merge em caso de falha. +É executado após `test-coverage`. Bloqueia a mesclagem em caso de falha. -| Script | Valida | Bloqueante | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `quality:collect` | Gera `quality-metrics.json` (contagem de avisos do ESLint, cobertura do relatório consolidado dos shards) | Sim (precede o ratchet) | -| `quality:ratchet` | Cada métrica em `quality-baseline.json` não regrediu (avisos do ESLint ≤ linha de base; cobertura ≥ linha de base) | Sim | -| `check:duplication` | A duplicação de código (jscpd@4) não excede a linha de base em `quality-baseline.json` | Sim | -| `check:complexity` | A complexidade ciclomática no nível do arquivo não excede o limite (regras `complexity` + `max-lines-per-function` do ESLint principal) | Sim | -| `check:cognitive-complexity` | Ratchet de complexidade cognitiva (`eslint-plugin-sonarjs`) — execução separada do ESLint; a CI executa ambos combinados na única etapa `check:complexity-ratchets` | Sim | -| `check:dead-code` | O ratchet de exportações/arquivos não utilizados (knip) não regride em relação à linha de base | Sim | -| `check:compression-budget` | Orçamento do benchmark de compactação — os limites mínimos de economia de tokens por mecanismo não devem regredir | Sim | -| `check:type-coverage` | O ratchet do percentual tipado (`type-coverage`) não regride; substitui em grande parte `typecheck:noimplicit:core` | Sim | -| `check:codeql-ratchet` | A contagem de alertas abertos do CodeQL não regride (lê via `gh api`; ignora normalmente sem token) — frequência de atualização e acionamento manual: veja "Ratchet do CodeQL" abaixo | Sim | +| Script | Valida | Bloqueante | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `quality:collect` | Gera `quality-metrics.json` (contagem de avisos do ESLint, cobertura proveniente do relatório consolidado dos shards) | Sim (precede o ratchet) | +| `quality:ratchet` | Cada métrica em `quality-baseline.json` não regrediu (avisos do ESLint ≤ baseline; cobertura ≥ baseline) | Sim | +| `check:duplication` | A duplicação de código (jscpd@4) não excede o baseline em `quality-baseline.json` | Sim | +| `check:complexity` | A complexidade ciclomática no nível de arquivo não excede o limite (regras `complexity` + `max-lines-per-function` do ESLint core) | Sim | +| `check:cognitive-complexity` | Ratchet de complexidade cognitiva (`eslint-plugin-sonarjs`) — execução separada do ESLint; a CI executa ambos de forma combinada na única etapa `check:complexity-ratchets` | Sim | +| `check:dead-code` | O ratchet de exports/arquivos não utilizados (knip) não regride em relação ao baseline | Sim | +| `check:compression-budget` | Orçamento do benchmark de compactação — os pisos de economia de tokens por engine não podem regredir | Sim | +| `check:type-coverage` | O ratchet de percentual tipado (`type-coverage`) não regride; substitui em grande parte `typecheck:noimplicit:core` | Sim | +| `check:codeql-ratchet` | A contagem de alertas abertos do CodeQL não regride (consulta via `gh api`; ignora de forma segura sem token) — frequência de atualização e acionamento manual: consulte "Ratchet do CodeQL" abaixo | Sim | ### Job: `quality-extended` -Todo o job é consultivo (`continue-on-error: true`). Os ratchets baseados em npm são executados -de fato; os scanners externos são instalados via `gh release download` e se ignoram automaticamente (exit 0) +O job inteiro é consultivo (`continue-on-error: true`). Os ratchets baseados em npm são executados +de fato; os scanners externos são instalados via `gh release download` e se autoignoram (exit 0) quando um binário ainda está ausente. -| Script | Valida | Bloqueante | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -| `check:circular-deps` | Nenhuma dependência circular (dpdm) | **Consultivo** | -| `check:bundle-size` | O tamanho do bundle não excede o limite | **Consultivo** | -| `check:secrets` | Varredura de segredos (gitleaks) — ignorada se o binário estiver ausente | **Consultivo** | -| `check:vuln-ratchet` | As vulnerabilidades de dependências (osv-scanner) não regridem — ignorado se o binário estiver ausente | **Consultivo** | -| `check:workflows` | Lint de workflows (actionlint + zizmor); scanners ausentes/com falha, relatórios inválidos ou linha de base do ratchet ausente falham como INCOMPLETE. Achados válidos seguem a política estrita/consultiva/de ratchet selecionada | Execução obrigatória; ratchet do zizmor bloqueante na CI | -| `check:openapi-breaking` | Alterações incompatíveis no contrato da API pública (`openapi.yaml`) em relação à branch base (oasdiff) — gera `openapiBreaking=N`; ignorado se o oasdiff estiver ausente ou se a especificação base não puder ser resolvida | **Consultivo** | +| Script | Valida | Bloqueante | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | +| `check:circular-deps` | Nenhuma dependência circular (dpdm) | **Consultivo** | +| `check:bundle-size` | O tamanho do bundle não excede o limite | **Consultivo** | +| `check:secrets` | Varredura de segredos (gitleaks) — ignora se o binário estiver ausente | **Consultivo** | +| `check:vuln-ratchet` | As vulnerabilidades de dependências (osv-scanner) não regridem — ignora se o binário estiver ausente | **Consultivo** | +| `check:workflows` | Lint de workflows (actionlint + zizmor); scanners ausentes/com falha, relatórios inválidos ou ausência do baseline do ratchet resultam em INCOMPLETE. Achados válidos seguem a política strict/advisory/ratchet selecionada | Execução obrigatória; ratchet do zizmor bloqueante na CI | +| `check:openapi-breaking` | Alterações incompatíveis no contrato da API pública (`openapi.yaml`) em relação à branch base (oasdiff) — gera `openapiBreaking=N`; ignora se o oasdiff estiver ausente ou se a especificação base não puder ser resolvida | **Consultivo** | ### Job: `docs-sync-strict` É executado em cada PR para `main`. Bloqueia o merge em caso de falha. -| Script | Valida | Bloqueante | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `check:docs-all` | Metagate que executa sequencialmente os 6 subgates abaixo | Sim | -| ↳ `check:docs-sync` | Consistência de versões entre CHANGELOG / OpenAPI / llm.txt | Sim | -| ↳ `check:docs-counts` | As contagens no texto (quantidade de provedores, quantidade de migrações etc.) estão dentro da janela progressiva das contagens reais | Sim | -| ↳ `check:env-doc-sync` | Cada variável de ambiente em `.env.example` está documentada em uma tabela da documentação e vice-versa | Sim | -| ↳ `check:deprecated-versions` | Não há strings de versões obsoletas na documentação | Sim | -| ↳ `check:doc-links` | Os links markdown internos na documentação apontam para arquivos reais (formato `[texto]`/`(caminho)`) | Sim | -| ↳ `check:fabricated-docs` | Rotas, variáveis de ambiente, comandos da CLI, nomes de hooks e caminhos de arquivos citados na documentação existem na base de código. Gate rígido via `--strict`; falha não bloqueante sem a flag. | Sim (via `--strict` na CI) | -| `check:cli-i18n` | As strings de comandos da CLI estão presentes em todos os arquivos de localidades i18n | Sim | -| `check:openapi-coverage` | A especificação OpenAPI cobre pelo menos um limite mínimo progressivo de rotas reais | Sim | -| `check:openapi-security-tiers` | As anotações de níveis de segurança em `openapi.yaml` são consistentes com as classificações de `routeGuard.ts` | **Consultivo** | -| `check:openapi-routes` | Cada caminho em `openapi.yaml` corresponde a um `route.ts` real (antialucinação) | Sim | -| `check:docs-symbols` | Cada referência a `/api/...` em `docs/**/*.md` corresponde a um `route.ts` real (antialucinação) | Sim | -| `i18n translation drift` | Chaves não traduzidas nos arquivos de localidades i18n — apenas alerta | **Consultivo** | +| Script | Valida | Bloqueante | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | Metaverificação que executa sequencialmente as 6 subverificações abaixo | Sim | +| ↳ `check:docs-sync` | Consistência de versões entre CHANGELOG / OpenAPI / llm.txt | Sim | +| ↳ `check:docs-counts` | As contagens no texto (quantidade de provedores, quantidade de migrações etc.) estão dentro da janela progressiva das contagens reais | Sim | +| ↳ `check:env-doc-sync` | Cada variável de ambiente em `.env.example` está documentada em uma tabela da documentação e vice-versa | Sim | +| ↳ `check:deprecated-versions` | Não há strings de versões obsoletas na documentação | Sim | +| ↳ `check:doc-links` | Links markdown internos na documentação apontam para arquivos reais (formato `[texto]`/`(caminho)`) | Sim | +| ↳ `check:fabricated-docs` | Rotas, variáveis de ambiente, comandos da CLI, nomes de hooks e caminhos de arquivos citados na documentação existem na base de código. Verificação rígida via `--strict`; falha não bloqueante sem a flag. | Sim (via `--strict` na CI) | +| `check:cli-i18n` | As strings de comandos da CLI estão presentes em todos os arquivos de localidades i18n | Sim | +| `check:openapi-coverage` | A especificação OpenAPI cobre pelo menos um limite mínimo progressivo das rotas reais | Sim | +| `check:openapi-security-tiers` | As anotações de níveis de segurança em `openapi.yaml` são consistentes com as classificações de `routeGuard.ts` | **Consultivo** | +| `check:openapi-routes` | Cada caminho em `openapi.yaml` corresponde a um `route.ts` real (antialucinação) | Sim | +| `check:docs-symbols` | Cada referência a `/api/...` em `docs/**/*.md` corresponde a um `route.ts` real (antialucinação) | Sim | +| `i18n translation drift` | Chaves não traduzidas nos arquivos de localidades i18n — apenas aviso | **Consultivo** | ### Job: `i18n-ui-coverage` @@ -195,90 +188,92 @@ quando um binário ainda está ausente. | `check-ui-keys-coverage` (inline) | A cobertura de chaves i18n da UI é ≥ 65% | Sim | | `check-ui-value-drift` (inline) | Um **valor** em inglês reescrito não deixa para trás nenhuma tradução desatualizada | Sim | | `check-new-key-coverage` (inline) | Uma **nova** chave em inglês está traduzida em todas as localidades — um marcador `__MISSING__:` é rejeitado | Sim | -| `check-translation-ratio` | A proporção de traduções reais por localidade (entradas idênticas ao inglês / placeholders / ausentes fora da lista de permissões) não deve exceder `config/quality/i18n-translation-baseline.json` + margem | **Consultivo** | +| `check-translation-ratio` | A proporção de traduções reais por localidade (valores idênticos ao inglês / placeholders / ausências fora da lista de permissões) não deve exceder `config/quality/i18n-translation-baseline.json` + margem | **Consultivo** | -Requer `fetch-depth: 0` — o gate de divergência de valores compara `en.json` com a base do merge. +Requer `fetch-depth: 0` — a verificação de divergência de valores compara `en.json` com a base de mesclagem. -#### `check-ui-value-drift` — gate de traduções desatualizadas +#### `check-ui-value-drift` — verificação de traduções desatualizadas -Detecta a única regressão de i18n que os outros gates não conseguem identificar estruturalmente: um valor em inglês +Detecta a única regressão de i18n que as outras verificações não conseguem identificar estruturalmente: um valor em inglês é reescrito e as traduções derivadas do inglês _anterior_ permanecem, fazendo com que -usuários que não falam inglês continuem lendo um texto assertivo, mas agora incorreto. +usuários que não falam inglês continuem lendo um texto formulado de maneira assertiva, mas agora incorreto. -Isso realmente chegou à produção. `oauthModal.googleOAuthWarning` foi reescrito quando o assistente de login -do Antigravity foi lançado (#5203); **39 de 43 localidades** mantiveram o texto instruindo os operadores a "copiar a +Isso realmente chegou à produção. `oauthModal.googleOAuthWarning` foi reescrito quando o auxiliar de login +Antigravity foi introduzido (#5203); **39 de 43 localidades** mantiveram o texto instruindo os operadores a "copiar a URL completa e colá-la abaixo" — um fluxo que não pode ser concluído para esse provedor. Isso passou despercebido até #8463 porque: -- `sync-ui-keys` apenas preenche chaves que estão **ausentes**, nunca as que estão **desatualizadas**; -- `check-ui-keys-coverage` contabiliza a _presença_ das chaves, portanto uma tradução desatualizada é considerada coberta; +- `sync-ui-keys` só preenche chaves que estão **ausentes**, nunca as que estão **desatualizadas**; +- `check-ui-keys-coverage` contabiliza a _presença_ da chave, portanto uma tradução desatualizada conta como coberta; - `check-translation-drift` monitora os espelhos da documentação em `docs/i18n//**.md` — ele nunca lê `src/i18n/messages/*.json`. Bloqueante no job `docs-sync-strict` desde a - ressincronização de 2026-09: edite um documento principal → `npm run i18n:run -- --files=` (por seção, econômico). + ressincronização de 2026-09: edite um documento principal → `npm run i18n:run -- --files=` (por seção, de baixo custo). -**Ciente do diff, sem depender de uma baseline.** Compara o `en.json` na base do merge com a -árvore de trabalho; para cada chave cujo valor em inglês foi alterado, qualquer localidade que ainda contenha uma -tradução intacta está desatualizada. Isso deliberadamente **congela a dívida preexistente** — um diff -não pode revelar de qual texto antigo em inglês veio uma tradução existente há muito tempo; portanto, a validação considera -apenas o que a alteração atual afeta. A alternativa (uma baseline de hashes por chave) exigiria -um arquivo gerado de aproximadamente 600 KB, 3× maior que a maior baseline existente, com alterações em todo PR de i18n. +**Ciente de diff, sem respaldo de baseline.** Ele compara o `en.json` na base do merge com a +árvore de trabalho; para cada chave cujo valor em inglês foi alterado, qualquer localidade +que ainda contenha uma tradução não modificada está desatualizada. Isso deliberadamente +**congela a dívida preexistente** — um diff não consegue revelar de qual versão antiga em +inglês veio uma tradução de longa data, portanto o gate avalia apenas o que a alteração +atual afeta. A alternativa (uma baseline de hash por chave) custaria um arquivo gerado de +~600 KB, 3× maior que a maior baseline existente, com alterações em todo PR de i18n. -Há duas maneiras de satisfazer a validação: +Há duas maneiras de satisfazê-lo: 1. atualizar as traduções afetadas; ou -2. defini-las como `__MISSING__:` — em seguida, o runtime fornece o texto corrigido em inglês - (`src/i18n/request.ts::deepMergeFallback`, #7258), e a chave entra na fila de tradução. +2. defini-las como `__MISSING__:` — o runtime então fornece o texto + corrigido em inglês (`src/i18n/request.ts::deepMergeFallback`, #7258), e a chave entra + na fila para tradução. -Se o **significado** da string tiver mudado, prefira **renomear a chave**: uma nova chave não pode herdar -uma tradução desatualizada. Esse foi o padrão usado em #8463. +Se o **significado** da string mudou, prefira **renomear a chave**: uma nova chave não pode +herdar uma tradução desatualizada. Esse foi o padrão usado no #8463. ```bash npm run i18n:check-value-drift # estrito (o que a CI executa) -npm run i18n:check-value-drift:warn # apenas relata +npm run i18n:check-value-drift:warn # apenas gera relatório BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Encerra com código 0 e `SKIP reason=base-unresolved` quando o catálogo-base não pode ser lido (clone -raso sem a ref da base), seguindo o comportamento de `check-openapi-breaking`. +Encerra com código 0 e `SKIP reason=base-unresolved` quando o catálogo-base não pode ser +lido (clone superficial sem a referência-base), espelhando `check-openapi-breaking`. ### Job: `i18n` Matriz completa de validação de i18n (um job por localidade). O job inteiro é consultivo. -| Script | Valida | Bloqueante | -| ------------------------------- | ------------------------------------- | --------------------------------------------------------- | -| `validate_translation.py quick` | Completude da tradução por localidade | **Consultivo** (`continue-on-error: true` no job inteiro) | +| Script | Valida | Bloqueante | +| ------------------------------- | ------------------------------------- | ------------------------------------------------------ | +| `validate_translation.py quick` | Completude da tradução por localidade | **Consultivo** (`continue-on-error: true` no job todo) | ### Job: `pr-test-policy` -Executado somente em pull requests. +Executado apenas em pull requests. -| Script | Valida | Bloqueante | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check:pr-test-policy` | PRs que alteram código de produção em `src/`, `open-sse/`, `electron/` ou `bin/` devem incluir ou atualizar testes (Regra Rígida nº 8) | Sim | -| `check:test-masking` | Arquivos de teste alterados não reduzem a contagem líquida de asserções nem adicionam tautologias `assert.ok(true)` | Sim | -| `check:pr-evidence` | O corpo do PR cita evidências de testes/VPS para a alteração (automatiza a Regra Rígida nº 18 procurando padrões no texto do PR — frágil; consulte o Backlog) | Sim | +| Script | Valida | Bloqueante | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check:pr-test-policy` | PRs que alteram código de produção em `src/`, `open-sse/`, `electron/` ou `bin/` devem incluir ou atualizar testes (Regra Rígida nº 8) | Sim | +| `check:test-masking` | Arquivos de teste alterados não reduzem a contagem líquida de asserções nem adicionam tautologias `assert.ok(true)` | Sim | +| `check:pr-evidence` | O corpo do PR cita evidências de teste/VPS para a alteração (automatiza a Regra Rígida nº 18 examinando o texto do PR — frágil, consulte as Pendências) | Sim | ### Job: `test-vitest` Executado após `build`. Bloqueia o merge em caso de falha. -| Suíte | Valida | Bloqueante | -| ---------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Servidor MCP (110 ferramentas), autoCombo, cache — executor vitest | Sim | -| `test:vitest:ui` | Testes de componentes da UI — executor vitest | **Bloqueante** — falhas preexistentes são explicitamente excluídas em `vitest.config.ts`; novas falhas fazem o job falhar | +| Suíte | Valida | Bloqueante | +| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Servidor MCP (110 ferramentas), autoCombo, cache — runner vitest | Sim | +| `test:vitest:ui` | Testes de componentes de UI — runner vitest | **Bloqueante** — falhas preexistentes são explicitamente excluídas em `vitest.config.ts`; novas falhas fazem o job falhar | ### Workflows noturnos (agendados, consultivos) -São executados por agendamento cron (e `workflow_dispatch`), nunca em PRs. Todos são consultivos. +São executados em um cronograma cron (e por `workflow_dispatch`), nunca em PRs. Todos são consultivos. | Workflow | Valida | Bloqueante | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | | `nightly-property` | Testes baseados em propriedades com fast-check, usando uma seed aleatória e uma alta contagem de execuções | **Consultivo** | -| `nightly-resilience` | Validação de crescimento do heap, injeção caótica de falhas, testes de carga/prolongados com k6 | **Consultivo** | +| `nightly-resilience` | Gate de crescimento de heap, injeção de falhas caóticas, testes de carga/soak com k6 | **Consultivo** | | `nightly-llm-security` | Proteção contra injeção do promptfoo (modo de bloqueio) + sondas do garak (ignoradas sem um segredo de provedor) | **Consultivo** | -| `nightly-schemathesis` | Fuzzing de contrato OpenAPI (schemathesis) contra uma instância ativa do OmniRoute usando `docs/openapi.yaml` — revela violações da especificação/erros 500 não tratados (Fase 8 B.4) | **Consultivo** | -| `nightly-mutation` | Pontuação de testes de mutação do Stryker sobre a via rápida de testes unitários — mutantes sobreviventes revelam asserções fracas | **Consultivo** | +| `nightly-schemathesis` | Fuzzing do contrato OpenAPI (schemathesis) contra uma instância ativa do OmniRoute usando `docs/openapi.yaml` — revela violações da especificação/erros 500 não tratados (Fase 8 B.4) | **Consultivo** | +| `nightly-mutation` | Pontuação de testes de mutação do Stryker na faixa rápida de testes unitários — mutantes sobreviventes revelam asserções fracas | **Consultivo** | | `nightly-compat` | Matriz de compatibilidade do mecanismo Node entre os intervalos compatíveis de `engines.node` | **Consultivo** | --- @@ -556,31 +551,31 @@ operador.** Nada abaixo foi aplicado ainda. **Também não documentados acima** (consultivos, baixo sinal): o job `docs-lint` (markdownlint + Vale, com `continue-on-error` no job inteiro) e os workflows independentes de análise `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` está em -`quality-baseline.json`, mas não está conectado a uma catraca bloqueante em `ci.yml` — a +`quality-baseline.json`, mas não está conectado a um ratchet bloqueante em `ci.yml` — a métrica está atualmente órfã. -### Consolidação / desduplicação (mecânica, menor risco) +### Consolidação / deduplicação (mecânica, menor risco) Cada candidato foi validado em relação ao estado real dos gates em 2026-06-17 (confiar, mas verificar); várias consolidações "óbvias" acabaram ocultando dívida e -**não** são substituições diretas seguras. +**não** são substituições diretas e seguras. -- **`check:docs-sync` é executado duas vezes** — de forma independente no job `lint` e novamente dentro de `check:docs-all` (`docs-sync-strict`) e no hook de pre-commit do husky. ✅ **CONCLUÍDO** — a invocação independente em `lint` foi removida. -- **Análise de CVEs** — ❌ **NÃO é uma consolidação segura.** `audit:deps` falha de forma bloqueante diante de qualquer CVE de severidade alta/crítica; `check:vuln-ratchet` (osv) só falha em caso de _regressão_ em relação à baseline (atualmente 1 MODERATE). Semânticas diferentes — remover `audit:deps` eliminaria o gate absoluto para severidades alta/crítica. Manter ambos. -- **Detecção de ciclos** — ❌ **NÃO é uma consolidação segura.** `check:circular-deps` (dpdm) relata **91 ciclos** (por isso é consultivo); ele não pode ser promovido a bloqueante sem primeiro resolvê-los e tem um escopo mais amplo do que o `check:cycles`, que está verde e é criteriosamente selecionado. Manter `check:cycles` como bloqueante; resolver os 91 ciclos do dpdm constitui um backlog próprio. -- **Complexidade** — ✅ **CONCLUÍDO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): uma única varredura do ESLint, com contagens por ruleId para que as baselines de complexidade ciclomática+máximo de linhas e de complexidade cognitiva permaneçam independentes; os comandos individuais `check:complexity` / `check:cognitive-complexity` permanecem disponíveis para `--update` local. -- **Antialucinação de `/api`** — ✅ **CONCLUÍDO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): um único inventário do sistema de arquivos de `src/app/api`; openapi-routes + docs-symbols continuam relatando resultados de forma independente; os comandos individuais permanecem disponíveis para execuções locais. -- **`check:node-runtime` é executado em 11 jobs** — ⚠️ **baixo ROI.** Cada um está em um runner separado e a verificação leva <1s; economia total de ~10s, ao custo de perder uma proteção barata por job. Não vale a perturbação. -- **`typecheck:noimplicit:core` no lint da CI** — ✅ **removido do job lint** (era consultivo com `continue-on-error`); a superfície de tipos bloqueante é `typecheck:core` + `check:type-coverage`. Script local mantido. +- **`check:docs-sync` é executado duas vezes** — de forma independente no job `lint` e novamente dentro de `check:docs-all` (`docs-sync-strict`) e do hook de pre-commit do husky. ✅ **CONCLUÍDO** — a execução independente em `lint` foi removida. +- **Varredura de CVEs** — ❌ **NÃO é uma consolidação segura.** `audit:deps` falha obrigatoriamente diante de qualquer CVE de severidade alta/crítica; `check:vuln-ratchet` (osv) só falha em uma _regressão_ em relação à baseline (atualmente 1 MODERATE). Semânticas diferentes — remover `audit:deps` eliminaria o gate absoluto para severidades alta/crítica. Manter ambos. +- **Detecção de ciclos** — ✅ **CONCLUÍDO** (#15159 G-01/G-02). O texto anterior chamava `check:cycles` de gate "verde e curado" e justificava mantê-lo bloqueante porque `check:circular-deps` (dpdm) relatava 91 ciclos. Esse resultado verde era um **falso positivo de sucesso**: `check:cycles` analisava 5 subdiretórios (450 arquivos), identificava somente `import|export … from` estáticos e descartava todos os especificadores `@/` e `@omniroute/open-sse/`, portanto não conseguia enxergar os ciclos de importação dinâmica + alias que predominavam no repositório. Corrigido: agora o gate percorre `src` + `open-sse` (5023 arquivos), coleta especificadores da AST do TypeScript (assim, `import("…")` conta e `typeof import("…")` em posição de tipo não conta) e resolve `paths` do tsconfig. Ele encontra **14** ciclos, não 0. Como 14 ciclos preexistentes não podem ser corrigidos em um PR de gate, `check:cycles` agora é um **ratchet** (`--ratchet`, teto `metrics.cycles.value = 14` em `quality-baseline.json`, `direction: down`) — ele bloqueia qualquer _regressão_ e a contagem só pode diminuir. A CI executa `npm run check:cycles:ratchet`. A redução gradual acompanha a **A-01**. `check:circular-deps` (dpdm) permanece consultivo como uma segunda opinião mais abrangente. +- **Complexidade** — ✅ **CONCLUÍDO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): uma única varredura do ESLint, com contagens por ruleId para que as baselines ciclomática+max-lines e cognitiva permaneçam independentes; `check:complexity` / `check:cognitive-complexity` individuais permanecem para uso local com `--update`. +- **Antialucinação de `/api`** — ✅ **CONCLUÍDO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): um único inventário do sistema de arquivos de `src/app/api`; openapi-routes + docs-symbols ainda geram relatórios independentes; os comandos individuais permanecem para execuções locais. +- **`check:node-runtime` é executado em 11 jobs** — ⚠️ **baixo ROI.** Cada um usa um runner separado e a verificação leva <1s; economia total de ~10s, em troca da perda de uma proteção barata por job. Não vale o retrabalho. +- **`typecheck:noimplicit:core` no lint da CI** — ✅ **removido do job lint** (era consultivo com `continue-on-error`); a superfície de tipos bloqueante é `typecheck:core` + `check:type-coverage`. O script local foi mantido. ### Alterar status / decidir (política do operador) -- `check:openapi-security-tiers` (consultivo) — ❌ **NÃO pode se tornar bloqueante diretamente.** Ele encerra com código 0, mas alerta que várias rotas de `traffic-inspector` sob `LOCAL_ONLY_API_PREFIXES` não têm a anotação `x-loopback-only: true`. Aplicá-lo de forma bloqueante exige primeiro adicionar essas anotações a `openapi.yaml`. -- `typecheck:noimplicit:core` (consultivo) — amplamente contemplado pela catraca bloqueante `check:type-coverage`. Converter em catraca ou remover a segunda passagem redundante do `tsc`. -- `test:vitest:ui` (agora **bloqueante**) — falhas preexistentes são explicitamente excluídas em `vitest.config.ts` com comentários de acompanhamento `// #8618`; novas falhas fazem o job falhar. -- `check:secrets` (gitleaks, catraca bloqueante congelada em 3 falsos positivos documentados) — adicionar os 3 à lista de permissões para chegar a 0 ou rebaixar para consultivo. Sobrepõe-se à análise nativa de segredos do GitHub + `check:public-creds`. -- `check:pr-evidence` (bloqueante, procura padrões no texto descritivo do corpo do PR) — alto risco de falsos positivos; sua remoção enfraquece a aplicação da Hard Rule #18, portanto esta é uma verdadeira decisão de política. -- `semgrep` (workflow independente consultivo) — sobrepõe-se ao CodeQL para as famílias do OWASP; conectar sua baseline a uma catraca ou remover. +- `check:openapi-security-tiers` (consultivo) — ❌ **NÃO pode ser tornado bloqueante de forma segura.** Ele encerra com código 0, mas alerta que várias rotas de `traffic-inspector` sob `LOCAL_ONLY_API_PREFIXES` não possuem a anotação `x-loopback-only: true`. Torná-lo obrigatório exige primeiro adicionar essas anotações a `openapi.yaml`. +- `typecheck:noimplicit:core` (consultivo) — amplamente substituído pelo ratchet bloqueante `check:type-coverage`. Transformar em ratchet ou remover a segunda execução redundante de `tsc`. +- `test:vitest:ui` (agora **bloqueante**) — falhas preexistentes são explicitamente excluídas em `vitest.config.ts` com comentários de rastreamento `// #8618`; novas falhas fazem o job falhar. +- `check:secrets` (gitleaks, ratchet bloqueante congelado em 3 falsos positivos documentados) — adicionar os 3 à lista de permissões para chegar a 0 ou rebaixar para consultivo. Sobrepõe-se à verificação nativa de segredos do GitHub + `check:public-creds`. +- `check:pr-evidence` (bloqueante, usa grep no texto do corpo do PR) — alto risco de falsos positivos; removê-lo enfraquece a aplicação da Hard Rule #18, portanto esta é uma decisão de política legítima. +- `semgrep` (workflow independente consultivo) — sobrepõe-se ao CodeQL para as famílias OWASP; conectar sua baseline a um ratchet ou remover. --- diff --git a/docs/i18n/pt-BR/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/pt-BR/docs/compression/COMPRESSION_GUIDE.md index 5ccc072003d8..68b7be299fe0 100644 --- a/docs/i18n/pt-BR/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/pt-BR/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ injetada em vez da versão em inglês. Em um corpo com `messages`, um desvio de conteúdo (`shouldBypassCavemanOutputMode()` em `open-sse/services/compression/outputMode.ts`) verifica as três últimas mensagens e ignora os estilos durante todo o turno quando elas correspondem às palavras-chave de segurança, ação irreversível, -esclarecimento ou sensíveis à ordem. O desvio é executado conforme a configuração do botão -**Desvio de Clareza Automática** (`cavemanOutputMode.autoClarity`) no painel. +esclarecimento ou sensíveis à ordem. O desvio é executado enquanto o botão **Desvio de Clareza Automática** (`cavemanOutputMode.autoClarity`) no painel estiver ativado, que é o padrão; com o botão desativado, os estilos selecionados também se aplicam nesses turnos. Quando o desvio permite que o turno prossiga, `placeSystemInstruction()` (no mesmo arquivo), que nunca cria um novo `messages[0]`, coloca o bloco no primeiro destes locais que encontrar: diff --git a/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md b/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md index 8ce6688d30f0..eb7bf1ee2684 100644 --- a/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/pt-BR/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Segredos obrigatórios -Estes **devem** ser definidos antes da primeira execução. Sem eles, a aplicação se recusará a iniciar ou operará com padrões inseguros. - -| Variável | Obrigatório | Padrão | Arquivo de origem | Descrição | -| ---------------------------- | ------------------ | ----------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Sim** | _(nenhum)_ | `src/lib/auth` | Assina/verifica todos os cookies de sessão do painel (JWT). Gere com `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Sim** | _(nenhum)_ | `src/lib/db/apiKeys.ts` | Chave de criptografia AES para valores de chaves de API armazenados no SQLite. Gere com `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Sim** | `CHANGEME` | Script de inicialização | Define a senha inicial do painel de administração (corresponde ao padrão de `.env.example` — mantido propositalmente inseguro para forçar uma alteração). **Altere antes do primeiro uso.** Após fazer login, altere em Painel → Configurações → Segurança. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sim** (produção) | _(não definido)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Segredo compartilhado para a ponte WebSocket interna do Codex Responses. Autentica solicitações da ponte entre o relay de WS do Electron/navegador e o OmniRoute. ⚠️ **OBRIGATÓRIO em produção — quando não definido, todas as solicitações da ponte de WS são rejeitadas.** Gere com `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Não | _(SHA do git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID explícito para invalidação do cache do service worker do shell da PWA (#11779); primeiro na cadeia de resolução. | -| `SOURCE_VERSION` | Não | _(não definido)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Segundo na cadeia — definido por ferramentas de build de PaaS (por exemplo, no estilo Heroku) como o commit implantado. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Não | _(derivado)_ | `src/shared/components/PwaRegister.tsx` | Valor público definido durante o build que o cliente usa para registrar `/sw.js?v=…`; derivado dos dois valores acima e, em seguida, do SHA do git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Não (automático) | _(automático a cada inicialização)_ | `src/server/authz/policies/management.ts` | Segredo por processo que comprova que o carimbo de IP do par confiável veio do próprio servidor HTTP do OmniRoute (`scripts/dev/peer-stamp.mjs`). O middleware de autorização confia na localidade da solicitação (restrição de loopback/LAN para rotas LOCAL_ONLY) somente quando o carimbo contém esse token. Gerado automaticamente a cada inicialização — deixe-o não definido; fixe-o apenas em configurações multiprocesso que precisem compartilhar o carimbo. | -| `OMNIROUTE_TRUSTED_PROXIES` | Não | _(não definido)_ | `scripts/dev/peer-stamp.mjs` | IPs ou intervalos CIDR separados por vírgulas de proxies reversos cujos `X-Forwarded-For` / `X-Real-IP` podem ser considerados confiáveis pela lista de permissão/bloqueio de IPs, além de endereços de loopback, de rede privada e de borda da Cloudflare, que são sempre confiáveis. Necessário apenas para um proxy em qualquer outro endereço público; caso contrário, o endereço do próprio proxy será avaliado pelo filtro. | +Segredos não definidos marcados como **Sim** são gerados automaticamente na primeira execução (`JWT_SECRET`, `API_KEY_SECRET`) e persistidos em `server.env` no diretório de dados — defina-os explicitamente quando esse diretório for efêmero. A exceção é `OMNIROUTE_WS_BRIDGE_SECRET`: ele não é persistido em `server.env` — os pontos de entrada do servidor geram um valor efêmero por boot que somente a ponte dentro do processo pode usar, por isso clientes externos da ponte precisam defini-lo explicitamente. + +| Variável | Obrigatório | Padrão | Arquivo de origem | Descrição | +| ---------------------------- | ------------------ | ----------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Sim** | _(nenhum)_ | `src/lib/auth` | Assina/verifica todos os cookies de sessão do painel (JWT). Gere com `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Sim** | _(nenhum)_ | `src/lib/db/apiKeys.ts` | Chave de criptografia AES para valores de chaves de API armazenados no SQLite. Gere com `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Não | _(não definido)_ | `src/lib/auth/managementPassword.ts` | Define a senha do painel na inicialização enquanto nenhuma estiver salva; é ignorada assim que houver uma senha salva. Instalações via npm e a partir do código-fonte copiam `.env.example` para `.env`, então começam com o espaço reservado publicamente conhecido dele, `CHANGEME`: substitua-o antes do primeiro boot, ou defina `INITIAL_PASSWORD=` (vazio) nesse `.env` para criar a senha no assistente de configuração inicial na primeira visita, como um valor não definido (o padrão da imagem Docker) faz. Altere uma senha salva em Painel → Configurações → Segurança, ou execute `omniroute reset-password` (`node bin/reset-password.mjs` em um checkout do código-fonte) com `DATA_DIR` definido para o diretório de dados do servidor. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sim** (produção) | _(não definido)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Segredo compartilhado para a ponte WebSocket interna do Codex Responses. Autentica solicitações da ponte entre o relay de WS do Electron/navegador e o OmniRoute. ⚠️ **OBRIGATÓRIO em produção — quando não definido, todas as solicitações da ponte de WS são rejeitadas.** Gere com `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Não | _(SHA do git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID explícito para invalidação do cache do service worker do shell da PWA (#11779); primeiro na cadeia de resolução. | +| `SOURCE_VERSION` | Não | _(não definido)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Segundo na cadeia — definido por ferramentas de build de PaaS (por exemplo, no estilo Heroku) como o commit implantado. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Não | _(derivado)_ | `src/shared/components/PwaRegister.tsx` | Valor público definido durante o build que o cliente usa para registrar `/sw.js?v=…`; derivado dos dois valores acima e, em seguida, do SHA do git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Não (automático) | _(automático a cada inicialização)_ | `src/server/authz/policies/management.ts` | Segredo por processo que comprova que o carimbo de IP do par confiável veio do próprio servidor HTTP do OmniRoute (`scripts/dev/peer-stamp.mjs`). O middleware de autorização confia na localidade da solicitação (restrição de loopback/LAN para rotas LOCAL_ONLY) somente quando o carimbo contém esse token. Gerado automaticamente a cada inicialização — deixe-o não definido; fixe-o apenas em configurações multiprocesso que precisem compartilhar o carimbo. | +| `OMNIROUTE_TRUSTED_PROXIES` | Não | _(não definido)_ | `scripts/dev/peer-stamp.mjs` | IPs ou intervalos CIDR separados por vírgulas de proxies reversos cujos `X-Forwarded-For` / `X-Real-IP` podem ser considerados confiáveis pela lista de permissão/bloqueio de IPs, além de endereços de loopback, de rede privada e de borda da Cloudflare, que são sempre confiáveis. Necessário apenas para um proxy em qualquer outro endereço público; caso contrário, o endereço do próprio proxy será avaliado pelo filtro. | ### Comandos de geração @@ -216,38 +216,38 @@ O OmniRoute usa **SQLite** (via `better-sqlite3`) para toda a persistência. Ess ## 4. Segurança e autenticação -| Variável | Padrão | Arquivo de origem | Descrição | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinado com identificadores de hardware para gerar a impressão digital da máquina. Altere-o em cada implantação para garantir o isolamento. | -| `OMNIROUTE_CLI_SALT` | _(não definido = salt aleatório por instalação, persistido em `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC usado para derivar o token de autenticação local da CLI. Definir esse valor rotaciona todos os tokens da CLI na máquina e sempre tem prioridade sobre o salt persistido. Consulte `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Define o atributo `Secure` nos cookies de sessão. **Deve ser `true`** ao executar por trás de HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware da API | Quando `true`, todas as solicitações de proxy para `/v1/*` devem incluir uma chave de API válida. **Esse sinalizador não controla o acesso a `GET /v1/models`**, que segue a configuração de login do painel (`requireAuthForModels`) — portanto, um `401` de `/v1/models` NÃO significa que a inferência está protegida. Consulte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite revelar os valores completos das chaves de API na interface do painel. Configurável nos sinalizadores de recursos do painel; representa um risco de segurança em instâncias compartilhadas. | -| `NO_LOG_API_KEY_IDS` | _(vazio)_ | `src/lib/compliance/index.ts` | IDs de chaves de API separados por vírgulas que ignoram o registro de solicitações (conformidade com o GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(não definido = ilimitado)_ | `src/shared/utils/apiKeyPolicy.ts` | Orçamento diário alternativo de solicitações aplicado às chaves de API cuja coluna `rate_limits` seja nula. Não definido ou vazio: sem limite implícito (#2289, #11017). `0` tem o mesmo efeito (ilimitado). Um inteiro positivo N habilita N/dia, 5N/semana e 20N/mês. Valores não vazios malformados utilizam os limites legados de 1000/dia, 5000/semana e 20000/mês. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Tamanho máximo permitido para o corpo da solicitação. Rejeita cargas que excedam esse limite. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Corpos reais de solicitações com tamanho igual ou superior a esse limite adquirem a concessão atômica local ao processo para admissão de carga pesada antes da análise do JSON (caminho BYTE, incluindo `POST /v1/responses`). Usa a mesma saída com margem operacional saudável de [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) aplicada a cargas estruturalmente pesadas; ainda limitada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), para que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) não volte a ocorrer. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite rígido das rotas de chat aplicado aos bytes lidos durante a ingestão limitada, incluindo solicitações com `Content-Length` ausente, inválido ou desonesto; o excesso recebe `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(não definido — sem limite de quantidade de solicitações)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** este limite legado de QUANTIDADE de solicitações agora só é aplicado quando definido explicitamente. Quando não definido (o padrão), a admissão de chats pesados é controlada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — um orçamento de BYTES derivado automaticamente, dimensionado a partir do limite real de memória do processo em **um processo** (um heap V8). Duas solicitações `/v1/responses` sobrepostas com ~750 mil tokens abortam heaps de ~12 GiB (#7849) — um alerta de **orçamento de memória**, não um máximo rígido de produto igual a 2. Um processo saudável (heap abaixo da proporção de descarte) PODE admitir mais solicitações `/v1/responses` longas simultâneas por meio de `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dezenas de clientes SSE de longa duração (40–50) são uma questão de heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, não de “máximo de 2”. Aumentar isso às cegas para “usar o host” reintroduz o problema #7849. Multiplique os **heaps** com **N `DATA_DIR`s independentes** (#11024); nunca use `replicas>1` com um único arquivo SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivado automaticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** substituição do orçamento de bytes de ingestão derivado automaticamente (25% do limite mais restritivo de memória do V8/cgroup, dividido pela amplificação transitória de 8x). Valores derivados e explícitos são limitados ao intervalo de 8 MiB–2 GiB. Um corpo maior que o orçamento efetivo falha imediatamente com `413 body_exceeds_budget`; a contenção entre corpos que podem ser processados individualmente continua podendo ser repetida com `503`. De 40 a 50 clientes SSE longos simultâneos são uma questão desse orçamento + heap, não de um “máximo de 2” rígido. Consulte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` em `/api/monitoring/health` antes de fazer ajustes. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Proporção de descarte por pressão do heap (`heapUsed / heap_size_limit`) para admissão pesada por BYTES e ESTRUTURA (#10183, #10268, #10437). Uma solicitação pesada simultânea que ultrapasse `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` só é descartada com o `503` repetível quando o heap TAMBÉM está nessa proporção ou acima dela; em um heap saudável, ela é admitida por meio da folga saudável. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (padrão `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacidade adicional limitada para o caminho rápido de heap saudável (#10437), tanto para ESTRUTURA quanto para BYTES (`admitChatRequest`, incluindo corpos ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sem esse limite, toda solicitação com o sistema ocupado, mas com heap saudável, ignorava a admissão sem qualquer teto. Quando essa quantidade de concessões simultâneas está ativa pelo caminho de heap saudável, outras solicitações com o sistema ocupado passam para o MESMO caminho de espera limitada/descarte usado sob pressão real do heap. `0` desabilita completamente esse desvio. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Quantidade de mensagens que classifica uma solicitação de chat como pesada, mesmo quando seu corpo está abaixo do limite de bytes. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Quantidade de ferramentas que classifica uma solicitação de chat como pesada, mesmo quando seu corpo está abaixo do limite de bytes. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimativa conservadora de tokens com base no tamanho das strings que classifica uma solicitação como pesada; trata-se de uma aproximação do custo de admissão, não da tokenização de cobrança do provedor. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (desabilitado) | `src/shared/middleware/chatBodyAdmission.ts` | Limite opcional e explícito do histórico de chat. Desabilitado por padrão: a quantidade de mensagens é uma política de implantação, não uma propriedade universal de uma solicitação, e aplicar um limite aqui rejeita conversas com um `413` terminal antes que o pipeline de compactação possa torná-las processáveis. O crescimento do heap é limitado por `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` e pelo descarte por pressão do heap. Defina um valor positivo em implantações com restrição de memória que precisem de um teto rígido; o excesso então recebe um `413` estruturado indicando que a compactação é necessária. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite rígido para uma resposta sem streaming do upstream armazenada integralmente em buffer na memória. Ao ultrapassá-lo, o leitor do upstream é cancelado e a solicitação falha rapidamente, em vez de expandir uma string sem limite até esgotar o heap. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Máximo de bytes transmitidos encaminhados dos cabeçalhos de resposta do upstream. Quando o limite é excedido, os cabeçalhos de menor prioridade (por exemplo, `x-codex-*`, `x-oai-request-id`) são descartados para permanecer dentro dos limites comuns de cabeçalhos de proxies reversos. Defina um valor maior para encaminhar mais metadados do upstream, ao custo de aumentar o tamanho dos cabeçalhos de resposta. | -| `CORS_ORIGIN` | _(não definido)_ | `src/server/cors/origins.ts` | Lista legada de origens permitidas pelo CORS, com uma única origem. Prefira `CORS_ALLOWED_ORIGINS` para novas implantações. O CORS destina-se apenas a clientes de API de navegador entre origens; as gravações autenticadas do painel usam solicitações da mesma origem, além de proteção CSRF vinculada à sessão. | -| `CORS_ALLOWED_ORIGINS` | _(não definido)_ | `src/server/cors/origins.ts` | Lista de origens permitidas pelo CORS, separadas por vírgulas. Nenhum curinga é enviado, a menos que `CORS_ALLOW_ALL=true` seja configurado explicitamente. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanismo de escape exclusivo para desenvolvimento que ecoa qualquer `Origin` do navegador. Não habilite em implantações compartilhadas ou de produção. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Bloqueia chamadas de provedores direcionadas a intervalos de IP privados, de loopback ou link-local. Desabilite somente em ambientes de teste isolados. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Permite URLs de provedores que apontem para redes privadas/locais (localhost, 192.168.x.x, 10.x.x.x etc.). **OBRIGATÓRIO para provedores auto-hospedados** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Quando definido como `false`, o painel rejeita a validação de URLs locais. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Permite adicionar/validar provedores em endereços locais/privados (127.0.0.1, localhost, LAN, intervalos privados) — restrito ao caminho de validação do provedor. **Padrão `true`** (local primeiro); defina como `false` para impor o bloqueio estrito, permitindo apenas endereços públicos. Endpoints de metadados de nuvem (169.254.169.254, metadata.google.internal) permanecem bloqueados independentemente dessa configuração. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permite que as rotas `/v1/audio/*` (transcrições, fala, traduções) usem um nó de provedor compatível com OpenAI hospedado fora do localhost. Desativado por padrão — rotear áudio para um host remoto altera a identidade de saída e deve ser uma decisão explícita do operador. Nós de loopback/privados (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permite que `POST /v1/rerank` (e a etapa de reranqueamento por loopback do mecanismo de memória) use um nó de provedor compatível com OpenAI hospedado fora do localhost — uma máquina na LAN ou um par do Tailscale executando TEI, Infinity, vLLM etc. Desativado por padrão — rotear para um host remoto altera a identidade de saída e deve ser uma decisão explícita do operador. Nós de loopback (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. Os nós remotos também devem ser aprovados pela política de URLs de saída do provedor (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); hosts de metadados de nuvem nunca são usados para roteamento. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Quando o OIDC está habilitado, desabilita o login por senha para que os usuários possam se autenticar somente por meio do Single Sign-On via OIDC. O alias simples `OIDC_DISABLE_PASSWORD_LOGIN` também é aceito; o sinalizador de recurso do painel com a mesma chave tem precedência. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simples de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variável | Padrão | Arquivo de origem | Descrição | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinado com identificadores de hardware para gerar a impressão digital da máquina. Altere-o em cada implantação para garantir o isolamento. | +| `OMNIROUTE_CLI_SALT` | _(não definido = salt aleatório por instalação, persistido em `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC usado para derivar o token de autenticação local da CLI. Definir esse valor rotaciona todos os tokens da CLI na máquina e sempre tem prioridade sobre o salt persistido. Consulte `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Define o atributo `Secure` nos cookies de sessão. **Deve ser `true`** ao executar por trás de HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware da API | Quando `true`, todas as solicitações de proxy para `/v1/*` devem incluir uma chave de API válida. **Esse sinalizador não controla o acesso a `GET /v1/models`**, que segue a configuração de login do painel (`requireAuthForModels`) — portanto, um `401` de `/v1/models` NÃO significa que a inferência está protegida. Consulte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite revelar os valores completos das chaves de API na interface do painel. Configurável nos sinalizadores de recursos do painel; representa um risco de segurança em instâncias compartilhadas. | +| `NO_LOG_API_KEY_IDS` | _(vazio)_ | `src/lib/compliance/index.ts` | IDs de chaves de API separados por vírgulas que ignoram o registro de solicitações (conformidade com o GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(não definido = ilimitado)_ | `src/shared/utils/apiKeyPolicy.ts` | Orçamento diário alternativo de solicitações aplicado às chaves de API cuja coluna `rate_limits` seja nula. Não definido ou vazio: sem limite implícito (#2289, #11017). `0` tem o mesmo efeito (ilimitado). Um inteiro positivo N habilita N/dia, 5N/semana e 20N/mês. Valores não vazios malformados utilizam os limites legados de 1000/dia, 5000/semana e 20000/mês. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Tamanho máximo permitido para o corpo da solicitação. Rejeita cargas que excedam esse limite. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Corpos reais de solicitações com tamanho igual ou superior a esse limite adquirem a concessão atômica local ao processo para admissão de carga pesada antes da análise do JSON (caminho BYTE, incluindo `POST /v1/responses`). Usa a mesma saída com margem operacional saudável de [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) aplicada a cargas estruturalmente pesadas; ainda limitada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), para que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) não volte a ocorrer. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite rígido das rotas de chat aplicado aos bytes lidos durante a ingestão limitada, incluindo solicitações com `Content-Length` ausente, inválido ou desonesto; o excesso recebe `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(não definido — sem limite de quantidade de solicitações)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** este limite legado de QUANTIDADE de solicitações agora só é aplicado quando definido explicitamente. Quando não definido (o padrão), a admissão de chats pesados é controlada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — um orçamento de BYTES derivado automaticamente, dimensionado a partir do limite real de memória do processo em **um processo** (um heap V8). Duas solicitações `/v1/responses` sobrepostas com ~750 mil tokens abortam heaps de ~12 GiB (#7849) — um alerta de **orçamento de memória**, não um máximo rígido de produto igual a 2. Um processo saudável (heap abaixo da proporção de descarte) PODE admitir mais solicitações `/v1/responses` longas simultâneas por meio de `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dezenas de clientes SSE de longa duração (40–50) são uma questão de heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, não de “máximo de 2”. Aumentar isso às cegas para “usar o host” reintroduz o problema #7849. Multiplique os **heaps** com **N `DATA_DIR`s independentes** (#11024); nunca use `replicas>1` com um único arquivo SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivado automaticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** substituição do orçamento de bytes de ingestão derivado automaticamente (25% do limite mais restritivo de memória do V8/cgroup, dividido pela amplificação transitória de 8x). Valores derivados e explícitos são limitados ao intervalo de 8 MiB–2 GiB. Um corpo maior que o orçamento efetivo falha imediatamente com `413 body_exceeds_budget`; a contenção entre corpos que podem ser processados individualmente continua podendo ser repetida com `503`. De 40 a 50 clientes SSE longos simultâneos são uma questão desse orçamento + heap, não de um “máximo de 2” rígido. Consulte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` em `/api/monitoring/health` antes de fazer ajustes. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Proporção de descarte por pressão do heap (`heapUsed / heap_size_limit`) para admissão pesada por BYTES e ESTRUTURA (#10183, #10268, #10437). Uma solicitação pesada simultânea que ultrapasse `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` só é descartada com o `503` repetível quando o heap TAMBÉM está nessa proporção ou acima dela; em um heap saudável, ela é admitida por meio da folga saudável. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (padrão `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacidade adicional limitada para o caminho rápido de heap saudável (#10437), tanto para ESTRUTURA quanto para BYTES (`admitChatRequest`, incluindo corpos ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sem esse limite, toda solicitação com o sistema ocupado, mas com heap saudável, ignorava a admissão sem qualquer teto. Quando essa quantidade de concessões simultâneas está ativa pelo caminho de heap saudável, outras solicitações com o sistema ocupado passam para o MESMO caminho de espera limitada/descarte usado sob pressão real do heap. `0` desabilita completamente esse desvio. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Quantidade de mensagens que classifica uma solicitação de chat como pesada, mesmo quando seu corpo está abaixo do limite de bytes. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Quantidade de ferramentas que classifica uma solicitação de chat como pesada, mesmo quando seu corpo está abaixo do limite de bytes. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimativa conservadora de tokens com base no tamanho das strings que classifica uma solicitação como pesada; trata-se de uma aproximação do custo de admissão, não da tokenização de cobrança do provedor. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (desabilitado) | `src/shared/middleware/chatBodyAdmission.ts` | Limite opcional e explícito do histórico de chat. Desabilitado por padrão: a quantidade de mensagens é uma política de implantação, não uma propriedade universal de uma solicitação, e aplicar um limite aqui rejeita conversas com um `413` terminal antes que o pipeline de compactação possa torná-las processáveis. O crescimento do heap é limitado por `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` e pelo descarte por pressão do heap. Defina um valor positivo em implantações com restrição de memória que precisem de um teto rígido; o excesso então recebe um `413` estruturado indicando que a compactação é necessária. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite rígido para uma resposta sem streaming do upstream armazenada integralmente em buffer na memória. Ao ultrapassá-lo, o leitor do upstream é cancelado e a solicitação falha rapidamente, em vez de expandir uma string sem limite até esgotar o heap. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Máximo de bytes transmitidos encaminhados dos cabeçalhos de resposta do upstream. Quando o limite é excedido, os cabeçalhos de menor prioridade (por exemplo, `x-codex-*`, `x-oai-request-id`) são descartados para permanecer dentro dos limites comuns de cabeçalhos de proxies reversos. Defina um valor maior para encaminhar mais metadados do upstream, ao custo de aumentar o tamanho dos cabeçalhos de resposta. | +| `CORS_ORIGIN` | _(não definido)_ | `src/server/cors/origins.ts` | Lista legada de origens permitidas pelo CORS, com uma única origem. Prefira `CORS_ALLOWED_ORIGINS` para novas implantações. O CORS destina-se apenas a clientes de API de navegador entre origens; as gravações autenticadas do painel usam solicitações da mesma origem, além de proteção CSRF vinculada à sessão. | +| `CORS_ALLOWED_ORIGINS` | _(não definido)_ | `src/server/cors/origins.ts` | Lista de origens permitidas pelo CORS, separadas por vírgulas. Nenhum curinga é enviado, a menos que `CORS_ALLOW_ALL=true` seja configurado explicitamente. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanismo de escape exclusivo para desenvolvimento que ecoa qualquer `Origin` do navegador. Não habilite em implantações compartilhadas ou de produção. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias legado. Seu interruptor no painel (uma substituição no banco de dados) é lido antes desta variável de ambiente; `false`, `0`, `no` ou `off` em qualquer um deles desativa as verificações de host da mesma forma que `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, mesmo quando o interruptor do painel dessa flag está salvo como desligado; outros valores deixam a proteção a cargo das duas flags abaixo. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Desativa as verificações de host do mecanismo de proteção de URLs de saída, incluindo o bloqueio de metadados de nuvem (todo o 169.254.0.0/16, metadata.google.internal), nos caminhos de provedor que seguem o modo de proteção: validação de URLs, descoberta de modelos, URLs base dos nós de provedor (incluindo nós remotos de reranqueamento) e o teste de proxy-fallback, entre outros caminhos de saída adjacentes ao provedor, como busca remota de imagens/mídia. Também permite destinos de webhook privados, exceto endpoints de metadados de nuvem/link-local, que permanecem bloqueados mesmo com esta flag ativada. Solicitações de chat despachadas por `BaseExecutor.execute()` mantêm sua verificação de metadados de nuvem. Provedores locais funcionam com os padrões: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (padrão `true`) já permite URLs locais e de LAN nos caminhos de validação, descoberta e nós de provedor; o teste de proxy-fallback e os destinos de webhook privados consultam apenas esta flag e bloqueiam hosts locais/LAN enquanto ela estiver desligada. Sobrepõe `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Um valor salvo pelo interruptor do painel tem precedência sobre esta variável. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Permite URLs de provedores em endereços locais/privados (127.0.0.1, localhost, LAN, intervalos privados) para validação de provedores, descoberta de modelos, chat e os demais caminhos de saída adjacentes ao provedor que o modo de proteção rege. **Padrão `true`** (local primeiro): a proteção então bloqueia endpoints de metadados de nuvem — todo o 169.254.0.0/16 mais os nomes de host de metadados conhecidos (metadata.google.internal, metadata.goog, IMDS de Alibaba/Azure/Oracle, fd00:ec2::254). Defina `false` para o modo somente público, que bloqueia também hosts privados e de loopback. A proteção verifica o nome do host ou o literal de IP conforme escrito, antes de qualquer consulta DNS; no modo somente público, o wrapper de busca segura ainda resolve o nome do host e recusa qualquer resposta privada. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` desativa essas verificações nos caminhos listados na linha dessa flag. Provedores locais integrados, como LM Studio, Ollama, vLLM, Llamafile e Triton, ignoram todas as verificações de host na validação de chaves e no chat. Um valor salvo pelo interruptor do painel tem precedência sobre esta variável. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permite que as rotas `/v1/audio/*` (transcrições, fala, traduções) usem um nó de provedor compatível com OpenAI hospedado fora do localhost. Desativado por padrão — rotear áudio para um host remoto altera a identidade de saída e deve ser uma decisão explícita do operador. Nós de loopback/privados (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permite que `POST /v1/rerank` (e a etapa de reranqueamento por loopback do mecanismo de memória) use um nó de provedor compatível com OpenAI hospedado fora do localhost — uma máquina na LAN ou um par do Tailscale executando TEI, Infinity, vLLM etc. Desativado por padrão — rotear para um host remoto altera a identidade de saída e deve ser uma decisão explícita do operador. Nós de loopback (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. A URL base de um nó remoto também deve ser aprovada pela política de URLs de saída do provedor (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Quando o OIDC está habilitado, desabilita o login por senha para que os usuários possam se autenticar somente por meio do Single Sign-On via OIDC. O alias simples `OIDC_DISABLE_PASSWORD_LOGIN` também é aceito; o sinalizador de recurso do painel com a mesma chave tem precedência. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simples de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Lista de verificação de proteção diff --git a/docs/i18n/pt/CONTRIBUTING.md b/docs/i18n/pt/CONTRIBUTING.md index a9677ebe1620..c24b27988d07 100644 --- a/docs/i18n/pt/CONTRIBUTING.md +++ b/docs/i18n/pt/CONTRIBUTING.md @@ -390,19 +390,19 @@ Escreva testes unitários em `tests/unit/` que abranjam, no mínimo: ## Lista de verificação do Pull Request - [ ] Os testes passam (`npm test`) -- [ ] A análise de lint passa (`npm run lint`) -- [ ] A compilação é concluída com sucesso (`npm run build`) -- [ ] Tipos TypeScript adicionados para novas funções e interfaces públicas -- [ ] Sem segredos codificados diretamente ou valores de recurso -- [ ] Credenciais públicas de upstream incorporadas através de `resolvePublicCred()` (consulte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nunca como literais -- [ ] As respostas de erro são encaminhadas através de `buildErrorBody()` / `sanitizeErrorMessage()` — sem stack traces em bruto nos corpos das respostas (consulte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Os comandos shell (`exec` / `spawn`) passam valores de runtime através de `env`, não através de interpolação de strings +- [ ] A verificação de lint passa (`npm run lint`) +- [ ] A compilação é concluída com êxito (`npm run build`) +- [ ] Foram adicionados tipos TypeScript para novas funções e interfaces públicas +- [ ] Não existem segredos ou valores de recurso codificados diretamente +- [ ] As credenciais públicas de upstream são incorporadas através de `resolvePublicCred()` (consulte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nunca como literais +- [ ] As respostas de erro são encaminhadas através de `buildErrorBody()` / `sanitizeErrorMessage()` — sem rastreios de pilha em bruto nos corpos das respostas (consulte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Os comandos de shell (`exec` / `spawn`) transmitem valores de tempo de execução através de `env`, não através de interpolação de strings - [ ] Todas as entradas são validadas com esquemas Zod -- [ ] **Fragmento** de changelog adicionado em `changelog.d/{features|fixes|maintenance}/-.md` para alterações visíveis ao utilizador (consulte [`changelog.d/README.md`](./changelog.d/README.md)) — **não** edite `CHANGELOG.md` diretamente; os fragmentos são agregados no momento do lançamento e nunca entram em conflito entre PRs -- [ ] Documentação atualizada (se aplicável) -- [ ] Nenhum novo alerta do CodeQL / Secret-Scanning aberto, ou cada alerta foi dispensado com uma justificação técnica que referencia o documento relevante em `docs/security/` -- [ ] Rotas que iniciam processos-filho (`/api/mcp/`, `/api/cli-tools/runtime/`) classificadas como `isLocalOnlyPath()` em `src/server/authz/routeGuard.ts` — consulte a [Regra Rígida n.º 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Sem trailers `Co-Authored-By` nas mensagens de commit — os commits devem aparecer exclusivamente sob a identidade Git do proprietário do repositório (Regra Rígida n.º 16) +- [ ] Foi adicionado um **fragmento** de registo de alterações em `changelog.d/{features|fixes|maintenance}/-.md` para alterações visíveis para o utilizador (consulte [`changelog.d/README.md`](./changelog.d/README.md)) — **não** edite `CHANGELOG.md` diretamente; os fragmentos são agregados aquando do lançamento e nunca entram em conflito entre PRs +- [ ] A documentação foi atualizada (se aplicável) +- [ ] Não foram abertos novos alertas do CodeQL / Secret-Scanning, ou cada um foi dispensado com uma justificação técnica que referencia o documento relevante em `docs/security/` +- [ ] As rotas que iniciam processos filhos (`/api/mcp/`, `/api/cli-tools/runtime/`) estão classificadas como `isLocalOnlyPath()` em `src/server/authz/routeGuard.ts` — consulte a [Regra Rígida n.º 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Não existem trailers `Co-authored-by` de IA/bots nas mensagens de commit (Regra Rígida n.º 16) — os colaboradores humanos cujo trabalho é reutilizado são creditados com trailers padrão `Co-authored-by: Name ` --- diff --git a/docs/i18n/pt/docs/architecture/QUALITY_GATES.md b/docs/i18n/pt/docs/architecture/QUALITY_GATES.md index 28403759dc2f..51eb1043fbda 100644 --- a/docs/i18n/pt/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/pt/docs/architecture/QUALITY_GATES.md @@ -20,210 +20,214 @@ replicação independente de ferramentas do mesmo sistema, consulte o ### Admissão de candidatos Os workflows CI e Quality Gates emitem, cada um, um veredicto estável: `Gate / CI` e -`Gate / Quality`. A respetiva política de admissão com controlo de versões enumera todos os jobs a montante -como obrigatórios ou consultivos. Um job obrigatório aplicável tem de ser concluído com êxito: resultados -em falta, cancelados, ignorados, pendentes e desconhecidos não permitem estabelecer PASS. Uma classificação -válida como apenas documentação ou apenas catálogo pode tornar uma via de código não aplicável; -um PR de rascunho não é um candidato aceite. Uma etiqueta `hotfix` não dispensa a apresentação de provas. - -Ambos os workflows abrangem PRs e pushes para branches main/release, execução manual e -eventos de grupos de merge. Pushes, execuções manuais e grupos de merge executam a seleção completa. Forks -e grupos de merge utilizam runners alojados para jobs que, de outra forma, selecionariam runners autoalojados; -é necessário verificar se existe capacidade alojada suficiente antes da implementação. - -Cada recibo JSON identifica o SHA obtido no checkout, a execução do workflow e a tentativa. -A CLI rejeita uma divergência entre o SHA do checkout e o do evento. Os testes do workflow associam a pertença à política -à lista `needs` do job de veredicto, para que uma via nova ou removida não possa desaparecer silenciosamente. -Os recibos abrangem o respetivo workflow, não a publicação, a implementação nem os componentes internos -de um scanner consultivo existente. A ativação de ambos os nomes de verificação nas regras de branches é uma -alteração administrativa separada; a adição destes jobs não protege, por si só, uma branch. +`Gate / Quality`. A respetiva política de admissão versionada enumera todos os jobs +a montante como obrigatórios ou consultivos. Um job obrigatório aplicável tem de ser +bem-sucedido: resultados em falta, cancelados, ignorados, pendentes e desconhecidos +não permitem estabelecer PASS. Uma classificação válida apenas de documentação ou +apenas de catálogo pode tornar uma lane de código não aplicável; um PR em rascunho +não é um candidato aceite. Uma etiqueta `hotfix` não dispensa evidências. + +Ambos os workflows abrangem PRs e pushes para branches main/release, acionamento manual e +eventos de merge group. Push, dispatch e merge group executam a seleção completa. Os forks +e merge groups utilizam runners alojados para jobs que, de outro modo, selecionariam runners +autoalojados; é necessário verificar se existe capacidade alojada suficiente antes da implementação. + +Cada recibo JSON identifica o SHA que foi objeto de checkout, a execução do workflow e a tentativa. +A CLI rejeita uma divergência entre o SHA do checkout e o SHA do evento. Os testes do workflow +associam a pertença à política à lista `needs` do job de veredicto, para que uma lane nova ou +removida não possa desaparecer silenciosamente. Os recibos abrangem o próprio workflow, não a +publicação, a implementação nem o funcionamento interno de um scanner consultivo existente. A +ativação de ambos os nomes de checks nas regras de branch é uma alteração administrativa separada; +a adição destes jobs não protege, por si só, uma branch. ### Inventário de análise estática -O inventário de aliases npm com controlo de versões e a pertença à análise estática encontram-se em +O inventário versionado de aliases npm e a composição da análise estática encontram-se em `config/quality/gate-manifest.json`. Execute `npm run check:gate-manifest` para validar -os nomes dos scripts e os comandos exatos face a `package.json`; adições, remoções e -alterações de comandos fazem falhar tanto o hook local como os jobs de classificação de alterações no CI. -Um alias não é um job de workflow, uma instância de matriz nem um caso de teste: estas contagens não devem -ser apresentadas como equivalentes. +os nomes dos scripts e os comandos exatos em relação a `package.json`; adições, remoções e +alterações aos comandos fazem falhar tanto o hook local como os jobs de classificação de +alterações no CI. Um alias não é um job de workflow, uma instância de matriz nem um caso de teste: +estas contagens não devem ser apresentadas como equivalentes. Utilize `npm run quality:scan -- --list` ou `npm run quality:scan:fast -- --list` para inspecionar os aliases selecionados sem os executar. O runner invoca o -ponto de entrada npm, pelo que o respetivo ambiente de execução (incluindo Bun, quando configurado) é preservado. -O manifesto regista os aliases fora destes perfis como invocados separadamente, e -os comandos de manutenção são proibidos em perfis de análise só de leitura. +ponto de entrada npm, pelo que o respetivo runtime (incluindo Bun, quando configurado) é preservado. +O manifesto regista os aliases fora desses perfis como invocados separadamente, e +os comandos de manutenção são proibidos em perfis de análise apenas de leitura. Estes perfis abrangem apenas a análise estática. Não certificam testes do produto, -cobertura, empacotamento, verificações externas nem a aceitação integral de uma versão candidata. -A admissão pelo workflow utiliza os ficheiros associados `config/quality/admission-policy.json` e -`scripts/quality/admission-verdict.mjs`. Os perfis de observação de versões permanecem separados; -inspecione de forma independente as respetivas verificações e recibos aplicáveis. O inventário -descritivo abaixo é uma referência, não uma prova de que um gate foi efetivamente executado. +cobertura, empacotamento, checks externos nem a aceitação completa de lançamento de um candidato. +A admissão do workflow utiliza `config/quality/admission-policy.json` e +`scripts/quality/admission-verdict.mjs` associados. Os perfis release-observer permanecem separados; +inspecione os respetivos checks e recibos aplicáveis de forma independente. O inventário +descritivo abaixo é uma referência, não uma prova de que um gate tenha sido efetivamente executado. -Os scripts encontram-se em `scripts/check/` (gates de políticas) e `scripts/quality/` (motor de ratchet). -A fonte fidedigna do CI é `.github/workflows/ci.yml`. +Os scripts encontram-se em `scripts/check/` (gates de política) e `scripts/quality/` (motor de ratchet). +A fonte de verdade do CI é `.github/workflows/ci.yml`. -### Via rápida de PRs de versão (`quality.yml`) +### Via rápida de PRs de lançamento (`quality.yml`) `.github/workflows/quality.yml` complementa o CI em PRs de main/release, pushes para branches -protegidas, execuções manuais e grupos de merge. Os PRs utilizam verificações rápidas filtradas por caminhos. A compilação -duplicada permanentemente desativada foi removida; as verificações reais de compilação/empacotamento/arranque permanecem no CI. - -| Job | Âmbito | Bloqueante | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | -| `Docs Gates (fast-path)` | PRs de documentação/código; referências da documentação da API e toda a documentação | Sim | -| `Fast Quality Gates` | PRs de código; verificações estáticas, verificação de tipos, verificação de tipos do dashboard, testes unitários afetados | Sim | -| `Forgotten sibling tests` | PRs de código; módulos alterados rastreados até consumidores estáticos e testes irmãos candidatos; caminhos de barrels e imports dinâmicos são reportados como diagnósticos consultivos, com exceções referenciadas na lista de permissões | **Consultivo** | -| `Vitest (fast-path)` | PRs de código; conjunto rápido de testes vitest | Sim | -| `Unit Tests fast-path` | PRs de código; conjunto de testes unitários com 4 shards | Sim | -| `No new ESLint warnings` | PRs de código; proteção de lint com conhecimento de supressões | Sim, incluindo forks | -| `Merge integrity (changelog + generated skills)` | PRs que não sejam rascunhos; sincronização do changelog e das competências geradas | Sim, incluindo forks | +protegidas, dispatch e merge groups. Os PRs utilizam checks rápidos filtrados por caminhos. A compilação +duplicada permanentemente desativada foi removida; os checks reais de compilação/empacotamento/arranque permanecem no CI. + +| Job | Âmbito | Bloqueante | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | PRs de documentação/código; referências da documentação da API e docs-all | Sim | +| `Fast Quality Gates` | PRs de código; checks estáticos, verificação de tipos, verificação de tipos do dashboard, testes unitários afetados | Sim | +| `Forgotten sibling tests` | PRs de código; módulos alterados rastreados até consumidores estáticos e testes irmãos candidatos; caminhos de barrels e imports dinâmicos são comunicados como diagnósticos consultivos, com exceções referenciadas da lista de permissões | **Consultivo** | +| `Vitest (fast-path)` | PRs de código; conjunto rápido do vitest | Sim | +| `Unit Tests fast-path` | PRs de código; conjunto de testes unitários com 4 shards | Sim | +| `No new ESLint warnings` | PRs de código; proteção de lint ciente de supressões | Sim, incluindo forks | +| `Merge integrity (changelog + generated skills)` | PRs que não sejam rascunhos; sincronização do changelog e das skills geradas | Sim, incluindo forks | #### Relatório de testes irmãos esquecidos `npm run check:forgotten-sibling-tests` reutiliza o resolvedor de imports subjacente ao mapa de impacto dos testes. -Para cada módulo de produção alterado, reporta cadeias determinísticas -`módulo/símbolo alterado -> consumidor estático -> teste irmão candidato` quando o teste -candidato não está presente no diff do pull request. O resumo em Markdown e o resultado JSON são mantidos como +Para cada módulo de produção alterado, comunica cadeias determinísticas +`módulo/símbolo alterado -> consumidor estático -> teste irmão candidato` quando o teste candidato +não está presente no diff do pull request. O resumo Markdown e o resultado JSON são mantidos como o artefacto de workflow `forgotten-sibling-tests` para calibração antes de qualquer implementação bloqueante. -As reexportações de barrel e as importações dinâmicas servem apenas como diagnósticos de resolução; nunca criam uma -ocorrência bloqueante. As exceções revistas encontram-se em +As reexportações barrel e as importações dinâmicas servem apenas para diagnóstico de resolução; nunca criam uma +ocorrência bloqueadora. As exceções revistas encontram-se em `config/quality/forgotten-sibling-allowlist.json`. Cada entrada tem de indicar o consumidor e o teste -candidato, apresentar uma justificação específica e incluir uma ligação para uma issue ou pull request do GitHub. As entradas malformadas -falham de forma fechada. As exceções não podem suprimir um teste candidato eliminado nem um diff que adicione `.skip`/`.todo`; -o enfraquecimento de asserções e outras formas de ocultação continuam a ser da responsabilidade do gate bloqueante independente +candidato, apresentar uma justificação específica e incluir uma ligação para um issue ou pull request do GitHub. As entradas malformadas +falham de forma restritiva. As exceções não podem suprimir um teste candidato eliminado nem um diff que adicione `.skip`/`.todo`; +o enfraquecimento de asserções e outras formas de dissimulação continuam a ser responsabilidade da barreira bloqueadora independente `check:test-masking`. -### Tarefa: `lint` - -É executada em todos os PR para `main`. Bloqueia a integração em caso de falha. - -| Script (`npm run ...`) | Valida | Bloqueante | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | A versão do Node.js encontra-se dentro do intervalo suportado | Sim | -| `check:cycles` | Importações circulares — todos os módulos de `src/` + `open-sse/` | Sim | -| `check:route-validation:t06` | Esquemas Zod presentes em todas as rotas (política do Nível 6) | Sim | -| `check:any-budget:t11` | A contagem de `@ts-expect-error // any` não excede o limite (torniquete do Nível 11) | Sim | -| `check:provider-consistency` | Cada fornecedor em `providers.ts` tem uma entrada correspondente em `providerRegistry.ts` (e vice-versa, dentro da lista de permissões) | Sim | -| `check:model-lifecycle` | As três tabelas de encaminhamento mantidas manualmente permanecem consistentes com o instantâneo do ciclo de vida incluído no repositório (#11503): `FITNESS_TABLE` (`taskFitness.ts`) não atribui pontuação a nenhum id descontinuado que `REGISTRY` possa encaminhar; todos os destinos de `BUILT_IN_ALIASES` estão presentes em `REGISTRY` e ausentes do instantâneo de ids descontinuados; todos os ids descontinuados ainda presentes em `REGISTRY` são reencaminhados ou indicados em `allowedRetiredInCatalog`; e nenhuma origem ou destino de `DEFAULT_DEGRADATION_MAP` aparece como descontinuado nesse instantâneo. Isto não comprova que um modelo esteja atualmente a ser servido por um upstream ativo. Offline — compara com `config/quality/model-lifecycle.json`, atualizado manualmente com `npm run quality:refresh-model-lifecycle` (rede; não integrado no CI). `allowedRetiredInCatalog` é um mecanismo de redução progressiva: adicione uma entrada apenas com uma issue de acompanhamento. | Sim | -| `check:fetch-targets` | Cada `fetch("/api/...")` no `src/` do lado do cliente corresponde a um `route.ts` real | Sim | -| `check:deps` | Todas as dependências instaláveis com `npm install` em todos os ficheiros `package.json` do repositório constam de `dependency-allowlist.json`; pacotes novos sem versão fixa ou resultantes de slopsquatting são sinalizados | Sim | -| `audit:deps` | `npm audit` (raiz + electron) — sem avisos de gravidade alta/crítica (sobrepõe-se ao `check:vuln-ratchet` do osv; consulte a Lista de Tarefas de Racionalização) | Sim | -| `check:lockfile` | Integridade de `package-lock.json` — registo https, hashes de integridade, sem substituições de anfitrião | Sim | -| `check:licenses` | Lista de permissões de licenças SPDX para dependências de produção | Sim | -| `check:tracked-artifacts` | Sem artefactos de compilação / ligações simbólicas de `node_modules` com controlo de versões (também é executado no pre-commit do husky; o pre-push é intencionalmente leve — #6716) | Sim | -| `check:ai-attribution` | Sem rodapé `Co-Authored-By` de IA/bot nem rodapé de geração por IA nos commits, título ou corpo do PR — Regra Rígida #16 (no ciclo de verificações rápidas de `quality.yml` para PR→`release/**` — lê o payload do evento, não executa qualquer operação fora de PRs — e num passo exclusivo de PRs no lint de `ci.yml` para PR→`main`; também no hook `commit-msg` do husky; coautores humanos permitidos; #14436) | -| `check:vitest-exclusions` | Cada exclusão do Vitest indica um issue de acompanhamento e consta em `config/quality/vitest-exclusions.json` (#13204) | Sim | -| `check:file-size` | Nenhum ficheiro de código-fonte excede o limite por extensão (ratchet: ficheiros grandes congelados na lista `frozen`) | Sim | -| `check:error-helper` | As respostas de erro em executores/handlers utilizam `buildErrorBody()` / `sanitizeErrorMessage()` (Regra Rígida #12) | Sim | -| `check:migration-numbering` | Os ficheiros SQL de migração são numerados sequencialmente, sem lacunas nem duplicados | Sim | -| `check:public-creds` | Não existem valores literais de OAuth `client_id`/`client_secret` nem chaves Web do Firebase fora de `publicCreds.ts` (Regra Rígida n.º 11) | Sim | -| `check:db-rules` | Não existe SQL em bruto fora dos módulos de `src/lib/db/`; não existem importações agregadas de `localDb.ts` (Regras Rígidas n.º 2/n.º 5) | Sim | -| `check:known-symbols` | Os executores de fornecedores, as estratégias de encaminhamento e os tradutores registados nas respetivas tabelas de expedição correspondem aos ficheiros no disco — sem símbolos órfãos ou não declarados | Sim | -| `check:route-guard-membership` | Todas as rotas que iniciam um processo subordinado são classificadas por `isLocalOnlyPath()` (Regras Rígidas n.º 15/n.º 17) | Sim | -| `check:test-discovery` | Todos os ficheiros `*.test.ts` / `*.spec.ts` no repositório são recolhidos por, pelo menos, um executor de testes (mecanismo de retenção: a lista de órfãos em `test-discovery-baseline.json` apenas pode diminuir) | Sim | -| `check:agent-skills-sync` | Os artefactos agent-skills gerados correspondem ao respetivo catálogo de origem (sem divergências) | -| `check:provider-asset-provenance` | Os logótipos/recursos dos fornecedores têm uma entrada de proveniência registada | -| `lint:json` | Os ficheiros de configuração JSON são analisados corretamente e cumprem as regras de lint do repositório | -| `typecheck:core` | Compilação TypeScript sem erros (apenas avisos informativos) | Sim | -| `typecheck:noimplicit:core` | `noImplicitAny` estrito — orientado para o futuro; muitos locais de chamada preexistentes ainda necessitam de anotações | **Informativo** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` limitado a `src/app/(dashboard)/**` (#7033) — a lista de permissões selecionada de 27 ficheiros de `typecheck:core` não inclui qualquer TSX do dashboard, e `next build` também nunca efetua a verificação de tipos (`next.config.mjs` define `ignoreBuildErrors: true`), pelo que as regressões de identificadores órfãos aí existentes (#6625/#6909) eram invisíveis para o CI. Compara com uma linha de base fixa de contagens por ficheiro/por código TS (`config/quality/dashboard-typecheck-baseline.json`, seguindo o mesmo padrão de imposição de obsolescência de `check:known-symbols`) — apenas NOVOS erros além da contagem da linha de base fazem com que a verificação falhe; reduza progressivamente com `--update` quando um erro preexistente for corrigido. | Sim | +### Job: `lint` + +É executado em cada PR para `main`. Bloqueia a integração em caso de falha. + +| Script (`npm run ...`) | Valida | Bloqueador | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | A versão do Node.js encontra-se dentro do intervalo suportado | Sim | +| `check:cycles` | Importações circulares em todo o `src/` + `open-sse/` (baseado em AST, com `paths` do tsconfig resolvidos). A execução simples é informativa e lista os ciclos. `check:cycles:ratchet` (que é executado pelo CI) bloqueia quando a contagem excede o limite máximo de `metrics.cycles` em `quality-baseline.json` — atualmente 14, `direction: down`, pelo que só pode diminuir (#15159 G-01/G-02) | Sim (catraca) | +| `check:route-validation:t06` | Presença de esquemas Zod em todas as rotas (política de Nível 6) | Sim | +| `check:any-budget:t11` | A contagem de `@ts-expect-error // any` não excede o limite (catraca de Nível 11) | Sim | +| `check:provider-consistency` | Cada fornecedor em `providers.ts` tem uma entrada correspondente em `providerRegistry.ts` (e vice-versa, dentro da lista de permissões) | Sim | +| `check:model-lifecycle` | As três tabelas de encaminhamento mantidas manualmente permanecem consistentes com o instantâneo do ciclo de vida incluído no repositório (#11503): `FITNESS_TABLE` (`taskFitness.ts`) não pontua nenhum id descontinuado que `REGISTRY` consiga encaminhar; todos os destinos de `BUILT_IN_ALIASES` estão presentes em `REGISTRY` e ausentes do instantâneo de ids descontinuados; todos os ids descontinuados ainda presentes em `REGISTRY` são reencaminhados ou indicados em `allowedRetiredInCatalog`; e nenhuma origem ou destino de `DEFAULT_DEGRADATION_MAP` aparece como descontinuado nesse instantâneo. Isto não prova que um modelo esteja atualmente a ser disponibilizado por um serviço a montante ativo. Offline — compara com `config/quality/model-lifecycle.json`, atualizado manualmente com `npm run quality:refresh-model-lifecycle` (rede; não integrado no CI). `allowedRetiredInCatalog` é um mecanismo de redução progressiva: adicione uma entrada apenas com uma questão de acompanhamento. | Sim | +| `check:fetch-targets` | Cada `fetch("/api/...")` no diretório `src/` do lado do cliente corresponde a um `route.ts` real | Sim | +| `check:deps` | Todas as dependências instaláveis com `npm install`, em todos os ficheiros `package.json` do repositório, estão em `dependency-allowlist.json`; pacotes novos sem versão fixa ou com nomes semelhantes a outros pacotes são assinalados | Sim | +| `audit:deps` | `npm audit` (raiz + electron) — sem avisos de gravidade alta/crítica (sobrepõe-se ao `check:vuln-ratchet` do osv; consulte a Lista de Pendências de Racionalização) | Sim | +| `check:lockfile` | Integridade de `package-lock.json` — registo https, hashes de integridade, sem substituições de anfitrião | Sim | +| `check:licenses` | Lista de permissões de licenças SPDX para dependências de produção | Sim | +| `check:tracked-artifacts` | Sem artefactos de compilação / ligações simbólicas de `node_modules` submetidas (também é executado no pre-commit do husky; o pre-push é intencionalmente leve — #6716) | Sim | +| `check:ai-attribution` | Sem rodapé `Co-Authored-By` de IA/bot nem rodapé de geração por IA nos commits, no título ou no corpo do PR — Regra Rígida n.º 16 (no ciclo de verificações rápidas de `quality.yml` para PR→`release/**` — lê o payload do evento, não efetua nenhuma operação fora de PRs — e num passo exclusivo de PRs na análise estática de `ci.yml` para PR→`main`; também no hook `commit-msg` do husky; são permitidos coautores humanos; #14436) | +| `check:vitest-exclusions` | Cada exclusão do Vitest identifica um issue de acompanhamento e consta de `config/quality/vitest-exclusions.json` (#13204) | Sim | +| `check:file-size` | Nenhum ficheiro de código-fonte excede o limite por extensão (ratchet: ficheiros grandes congelados na lista `frozen`) | Sim | +| `check:error-helper` | As respostas de erro em executores/handlers utilizam `buildErrorBody()` / `sanitizeErrorMessage()` (Regra Rígida n.º 12) | Sim | +| `check:migration-numbering` | Os ficheiros SQL de migração são numerados sequencialmente, sem lacunas nem duplicados | Sim | +| `check:public-creds` | Não existem valores literais de OAuth `client_id`/`client_secret` nem chaves Web do Firebase fora de `publicCreds.ts` (Regra Rígida #11) | Sim | +| `check:db-rules` | Não existe SQL em bruto fora dos módulos de `src/lib/db/`; não existem importações de agregação a partir de `localDb.ts` (Regras Rígidas #2/#5) | Sim | +| `check:known-symbols` | Os executores de fornecedores, as estratégias de encaminhamento e os tradutores registados nas respetivas tabelas de despacho correspondem aos ficheiros no disco — sem símbolos órfãos ou não declarados | Sim | +| `check:route-guard-membership` | Todas as rotas que iniciam um processo subordinado estão classificadas por `isLocalOnlyPath()` (Regras Rígidas #15/#17) | Sim | +| `check:test-discovery` | Todos os ficheiros `*.test.ts` / `*.spec.ts` no repositório são recolhidos por, pelo menos, um executor de testes (catraca: a lista de órfãos em `test-discovery-baseline.json` apenas pode diminuir) | Sim | +| `check:agent-skills-sync` | Os artefactos agent-skills gerados correspondem ao respetivo catálogo de origem (sem divergências) | +| `check:provider-asset-provenance` | Os logótipos/recursos dos fornecedores têm uma entrada de proveniência registada | +| `lint:json` | Os ficheiros de configuração JSON são analisados corretamente e cumprem as regras de lint do repositório | +| `typecheck:core` | Compilação TypeScript sem erros (apenas avisos informativos) | Sim | +| `typecheck:noimplicit:core` | `noImplicitAny` estrito — orientado para o futuro; muitos locais de chamada preexistentes ainda necessitam de anotações | **Informativo** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` limitado a `src/app/(dashboard)/**` (#7033) — a lista restrita de 27 ficheiros permitidos de `typecheck:core` não inclui qualquer TSX do dashboard, e `next build` também nunca efetua a verificação de tipos desse código (`next.config.mjs` define `ignoreBuildErrors: true`), pelo que as regressões de identificadores órfãos nessa área (#6625/#6909) eram invisíveis para a CI. Compara com uma linha de base fixa de contagens por ficheiro/por código TS (`config/quality/dashboard-typecheck-baseline.json`, seguindo o mesmo padrão de imposição de dados obsoletos de `check:known-symbols`) — apenas erros NOVOS que excedam a contagem da linha de base fazem falhar a validação; reduza-a progressivamente com `--update` quando um erro preexistente for corrigido. | Sim | ### Tarefa: `quality-gate` É executada após `test-coverage`. Bloqueia a integração em caso de falha. -| Script | Valida | Bloqueante | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `quality:collect` | Gera `quality-metrics.json` (número de avisos do ESLint, cobertura do relatório combinado dos fragmentos) | Sim (a montante do ratchet) | -| `quality:ratchet` | Nenhuma métrica em `quality-baseline.json` regrediu (avisos do ESLint ≤ referência; cobertura ≥ referência) | Sim | -| `check:duplication` | A duplicação de código (jscpd@4) não excede a referência em `quality-baseline.json` | Sim | -| `check:complexity` | A complexidade ciclomática ao nível do ficheiro não excede o limite (`complexity` principal do ESLint + `max-lines-per-function`) | Sim | -| `check:cognitive-complexity` | Ratchet de complexidade cognitiva (`eslint-plugin-sonarjs`) — execução separada do ESLint; a CI executa ambos combinados no passo único `check:complexity-ratchets` | Sim | -| `check:dead-code` | O ratchet de exportações/ficheiros não utilizados (knip) não regride relativamente à referência | Sim | -| `check:compression-budget` | Orçamento do benchmark de compressão — os valores mínimos de poupança de tokens por motor não podem regredir | Sim | -| `check:type-coverage` | O ratchet da percentagem de código tipado (`type-coverage`) não regride; substitui em grande medida `typecheck:noimplicit:core` | Sim | -| `check:codeql-ratchet` | O número de alertas CodeQL abertos não regride (lido através de `gh api`; ignorado de forma segura sem token) — frequência de atualização e acionamento manual: consulte «Ratchet do CodeQL» abaixo | Sim | - -### Tarefa: `quality-extended` - -Toda a tarefa é consultiva (`continue-on-error: true`). Os ratchets baseados em npm são executados -efetivamente; os analisadores externos são instalados através de `gh release download` e ignoram-se automaticamente (código de saída 0) +| Script | Valida | Bloqueante | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `quality:collect` | Gera `quality-metrics.json` (número de avisos do ESLint, cobertura do relatório combinado dos fragmentos) | Sim (a montante do ratchet) | +| `quality:ratchet` | Nenhuma métrica em `quality-baseline.json` regrediu (avisos do ESLint ≤ linha de base; cobertura ≥ linha de base) | Sim | +| `check:duplication` | A duplicação de código (jscpd@4) não excede a linha de base em `quality-baseline.json` | Sim | +| `check:complexity` | A complexidade ciclomática ao nível do ficheiro não excede o limite (`complexity` do ESLint base + `max-lines-per-function`) | Sim | +| `check:cognitive-complexity` | Ratchet de complexidade cognitiva (`eslint-plugin-sonarjs`) — execução separada do ESLint; a CI executa ambos em conjunto como o único passo `check:complexity-ratchets` | Sim | +| `check:dead-code` | O ratchet de exportações/ficheiros não utilizados (knip) não regride face à linha de base | Sim | +| `check:compression-budget` | Orçamento do benchmark de compressão — os limites mínimos de poupança de tokens por motor não podem regredir | Sim | +| `check:type-coverage` | O ratchet da percentagem tipificada (`type-coverage`) não regride; substitui, em grande medida, `typecheck:noimplicit:core` | Sim | +| `check:codeql-ratchet` | O número de alertas CodeQL abertos não regride (lê através de `gh api`; omissão controlada sem token) — para a cadência de atualização e o acionamento manual: consulte "Ratchet do CodeQL" abaixo | Sim | + +### Job: `quality-extended` + +Todo o job é consultivo (`continue-on-error: true`). Os ratchets baseados em npm são executados +efetivamente; os analisadores externos são instalados através de `gh release download` e omitem-se automaticamente (exit 0) quando um binário continua ausente. -| Script | Valida | Bloqueante | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -| `check:circular-deps` | Ausência de dependências circulares (dpdm) | **Consultivo** | -| `check:bundle-size` | O tamanho do bundle não excede o limite | **Consultivo** | -| `check:secrets` | Análise de segredos (gitleaks) — ignorada se o binário estiver ausente | **Consultivo** | -| `check:vuln-ratchet` | As vulnerabilidades das dependências (osv-scanner) não regridem — ignorado se o binário estiver ausente | **Consultivo** | -| `check:workflows` | Validação de workflows (actionlint + zizmor); analisadores ausentes/com falhas, relatórios inválidos ou uma referência de ratchet ausente resultam em INCOMPLETE. As conclusões válidas seguem a política rigorosa/consultiva/de ratchet selecionada | Execução obrigatória; ratchet do zizmor bloqueante na CI | -| `check:openapi-breaking` | Alterações incompatíveis ao contrato da API pública (`openapi.yaml`) relativamente ao ramo base (oasdiff) — gera `openapiBreaking=N`; ignorado se o oasdiff estiver ausente ou se não for possível resolver a especificação base | **Consultivo** | - -### Tarefa: `docs-sync-strict` - -É executada em todos os PR para `main`. Bloqueia a integração em caso de falha. - -| Script | Valida | Bloqueante | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| `check:docs-all` | Metaporta que executa sequencialmente as 6 subportas abaixo | Sim | -| ↳ `check:docs-sync` | Consistência das versões de CHANGELOG / OpenAPI / llm.txt | Sim | -| ↳ `check:docs-counts` | As contagens no texto (número de fornecedores, número de migrações, etc.) estão dentro da janela de progressão das contagens reais | Sim | -| ↳ `check:env-doc-sync` | Todas as variáveis de ambiente em `.env.example` estão documentadas numa tabela da documentação e vice-versa | Sim | -| ↳ `check:deprecated-versions` | Não existem cadeias de versões obsoletas na documentação | Sim | -| ↳ `check:doc-links` | As ligações internas de markdown na documentação remetem para ficheiros reais (formato `[text]`/`(path)`) | Sim | -| ↳ `check:fabricated-docs` | As rotas, variáveis de ambiente, comandos da CLI, nomes de hooks e caminhos de ficheiros citados na documentação existem na base de código. Porta rígida através de `--strict`; falha não bloqueante sem a opção. | Sim (através de `--strict` no CI) | -| `check:cli-i18n` | As cadeias dos comandos da CLI estão presentes em todos os ficheiros de idiomas i18n | Sim | -| `check:openapi-coverage` | A especificação OpenAPI abrange, no mínimo, um limite mínimo progressivo das rotas reais | Sim | -| `check:openapi-security-tiers` | As anotações dos níveis de segurança em `openapi.yaml` são consistentes com as classificações de `routeGuard.ts` | **Consultivo** | -| `check:openapi-routes` | Todos os caminhos em `openapi.yaml` correspondem a um `route.ts` real (antialucinação) | Sim | -| `check:docs-symbols` | Todas as referências `/api/...` em `docs/**/*.md` correspondem a um `route.ts` real (antialucinação) | Sim | -| `i18n translation drift` | Chaves não traduzidas nos ficheiros de idiomas i18n — apenas aviso | **Consultivo** | +| Script | Valida | Bloqueante | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | +| `check:circular-deps` | Ausência de dependências circulares (dpdm) | **Consultivo** | +| `check:bundle-size` | O tamanho do bundle não excede o limite | **Consultivo** | +| `check:secrets` | Análise de segredos (gitleaks) — omitida se o binário estiver ausente | **Consultivo** | +| `check:vuln-ratchet` | As vulnerabilidades das dependências (osv-scanner) não regridem — omitida se o binário estiver ausente | **Consultivo** | +| `check:workflows` | Lint de workflows (actionlint + zizmor); analisadores ausentes/com falhas, relatórios inválidos ou uma linha de base do ratchet em falta resultam em INCOMPLETE. As deteções válidas seguem a política estrita/consultiva/de ratchet selecionada | Execução obrigatória; ratchet do zizmor bloqueante na CI | +| `check:openapi-breaking` | Alterações incompatíveis ao contrato da API pública (`openapi.yaml`) face ao branch de base (oasdiff) — gera `openapiBreaking=N`; omitida se o oasdiff estiver ausente ou se a especificação de base não puder ser resolvida | **Consultivo** | + +### Job: `docs-sync-strict` + +É executado em todos os PR para `main`. Bloqueia a integração em caso de falha. + +| Script | Valida | Bloqueante | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| `check:docs-all` | Metaverificação que executa sequencialmente as 6 subverificações abaixo | Sim | +| ↳ `check:docs-sync` | Consistência das versões entre CHANGELOG / OpenAPI / llm.txt | Sim | +| ↳ `check:docs-counts` | As contagens no texto (número de fornecedores, número de migrações, etc.) encontram-se dentro do intervalo de progressão das contagens reais | Sim | +| ↳ `check:env-doc-sync` | Todas as variáveis de ambiente em `.env.example` estão documentadas numa tabela da documentação e vice-versa | Sim | +| ↳ `check:deprecated-versions` | Não existem cadeias de versões obsoletas na documentação | Sim | +| ↳ `check:doc-links` | As ligações markdown internas na documentação correspondem a ficheiros reais (formato `[texto]`/`(caminho)`) | Sim | +| ↳ `check:fabricated-docs` | As rotas, variáveis de ambiente, comandos da CLI, nomes de hooks e caminhos de ficheiros mencionados na documentação existem na base de código. Verificação estrita através de `--strict`; falha não bloqueante sem a opção. | Sim (através de `--strict` no CI) | +| `check:cli-i18n` | As cadeias dos comandos da CLI estão presentes em todos os ficheiros de idiomas i18n | Sim | +| `check:openapi-coverage` | A especificação OpenAPI cobre, pelo menos, um limiar progressivo de rotas reais | Sim | +| `check:openapi-security-tiers` | As anotações dos níveis de segurança em `openapi.yaml` são consistentes com as classificações em `routeGuard.ts` | **Consultivo** | +| `check:openapi-routes` | Cada caminho em `openapi.yaml` corresponde a um `route.ts` real (antialucinação) | Sim | +| `check:docs-symbols` | Cada referência `/api/...` em `docs/**/*.md` corresponde a um `route.ts` real (antialucinação) | Sim | +| `i18n translation drift` | Chaves não traduzidas nos ficheiros de idiomas i18n — apenas aviso | **Consultivo** | ### Tarefa: `i18n-ui-coverage` -| Script | Valida | Bloqueante | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (inline) | A cobertura das chaves de i18n da IU é ≥ 65% | Sim | -| `check-ui-value-drift` (inline) | Um **valor** em inglês reescrito não deixa para trás nenhuma tradução desatualizada | Sim | -| `check-new-key-coverage` (inline) | Uma **nova** chave em inglês está traduzida em todos os idiomas — um marcador `__MISSING__:` é rejeitado | Sim | -| `check-translation-ratio` | O rácio de traduções reais por idioma (valores idênticos ao inglês / marcadores de posição / elementos em falta fora da lista de permissões) não pode exceder `config/quality/i18n-translation-baseline.json` + margem | **Consultivo** | +| Script | Valida | Bloqueante | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (inline) | A cobertura das chaves i18n da IU é ≥ 65% | Sim | +| `check-ui-value-drift` (inline) | Um **valor** em inglês reescrito não deixa para trás nenhuma tradução desatualizada | Sim | +| `check-new-key-coverage` (inline) | Uma **nova** chave em inglês está traduzida em todos os idiomas — um marcador `__MISSING__:` é rejeitado | Sim | +| `check-translation-ratio` | O rácio de traduções reais por idioma (elementos idênticos ao inglês / marcadores de posição / em falta fora da lista de permissões) não pode exceder `config/quality/i18n-translation-baseline.json` + margem | **Consultivo** | -Requer `fetch-depth: 0` — a porta de divergência de valores compara `en.json` com a base de integração. +Requer `fetch-depth: 0` — a verificação de divergência de valores compara `en.json` com a base de integração. -#### `check-ui-value-drift` — porta de traduções desatualizadas +#### `check-ui-value-drift` — verificação de traduções desatualizadas -Deteta a única regressão de i18n que as outras portas, estruturalmente, não conseguem identificar: um valor em inglês -é reescrito e as traduções derivadas do inglês _anterior_ permanecem, fazendo com que -os utilizadores que não falam inglês continuem a ler um texto formulado de forma convincente, mas agora incorreto. +Deteta a única regressão de i18n que as outras verificações não conseguem identificar +estruturalmente: um valor em inglês é reescrito e as traduções derivadas do inglês +_anterior_ permanecem, pelo que os utilizadores que não usam inglês continuam a ler um +texto apresentado com confiança, mas agora incorreto. -Isto chegou efetivamente à produção. `oauthModal.googleOAuthWarning` foi reescrito quando o auxiliar de início de sessão -do Antigravity foi introduzido (#5203); **39 de 43 idiomas** mantiveram o texto que instruía os operadores a «copiar o -URL completo e colá-lo abaixo» — um fluxo que não pode ser concluído para esse fornecedor. O problema -passou despercebido até ao #8463 porque: +Isto chegou efetivamente à produção. `oauthModal.googleOAuthWarning` foi reescrito quando +foi introduzido o auxiliar de início de sessão Antigravity (#5203); **39 de 43 idiomas** +mantiveram o texto que instruía os operadores a «copiar o URL completo e colá-lo abaixo» — +um fluxo que não pode ser concluído para esse fornecedor. O problema passou despercebido +até ao #8463 porque: - `sync-ui-keys` apenas preenche chaves que estão **ausentes**, nunca as que estão **desatualizadas**; -- `check-ui-keys-coverage` contabiliza a _presença_ das chaves, pelo que uma tradução desatualizada conta como abrangida; +- `check-ui-keys-coverage` contabiliza a _presença_ das chaves, pelo que uma tradução desatualizada conta como coberta; - `check-translation-drift` monitoriza os espelhos da documentação em `docs/i18n//**.md` — nunca lê `src/i18n/messages/*.json`. Bloqueante na tarefa `docs-sync-strict` desde a - ressincronização de 2026-09: editar um documento principal → `npm run i18n:run -- --files=` (ao nível da secção, pouco dispendioso). + ressincronização de 2026-09: editar um documento principal → `npm run i18n:run -- --files=` (ao nível da secção, rápido). -**Tem em conta o diff, não se baseia numa baseline.** Compara o `en.json` na base de integração com a +**Sensível ao diff, sem depender de uma baseline.** Compara o `en.json` na base de integração com a árvore de trabalho; para cada chave cujo valor em inglês tenha sido alterado, qualquer locale que ainda contenha uma -tradução inalterada está desatualizado. Isto **congela deliberadamente a dívida preexistente** — um diff -não permite determinar de que versão antiga do inglês veio uma tradução existente há muito tempo, pelo que o controlo avalia +tradução não alterada está desatualizado. Isto **congela deliberadamente a dívida preexistente** — um diff +não consegue revelar de que versão antiga do inglês surgiu uma tradução existente há muito tempo, pelo que a validação avalia apenas aquilo em que a alteração atual toca. A alternativa (uma baseline de hashes por chave) implicaria um ficheiro gerado de ~600 KB, 3× maior do que a maior baseline existente, alterado em cada PR de i18n. -Há duas formas de satisfazer este controlo: +Há duas formas de cumprir a validação: 1. atualizar as traduções afetadas, ou -2. defini-las como `__MISSING__:` — em seguida, o runtime apresenta o inglês corrigido - (`src/i18n/request.ts::deepMergeFallback`, #7258) e a chave fica em fila para tradução. +2. defini-las como `__MISSING__:` — em tempo de execução, é então apresentado o inglês corrigido + (`src/i18n/request.ts::deepMergeFallback`, #7258) e a chave entra na fila para tradução. -Se o **significado** da cadeia tiver mudado, é preferível **mudar o nome da chave**: uma chave nova não pode herdar -uma tradução desatualizada. Esse foi o padrão utilizado em #8463. +Se o **significado** da string tiver mudado, dê preferência a **renomear a chave**: uma chave nova não pode herdar +uma tradução desatualizada. Foi esse o padrão utilizado em #8463. ```bash npm run i18n:check-value-drift # estrito (o que a CI executa) @@ -231,48 +235,48 @@ npm run i18n:check-value-drift:warn # apenas relatório BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Termina com o código 0 e `SKIP reason=base-unresolved` quando não é possível ler o catálogo base (clone -superficial sem a referência base), refletindo o comportamento de `check-openapi-breaking`. +Termina com o código 0 e `SKIP reason=base-unresolved` quando o catálogo de base não pode ser lido (clone +superficial sem a ref de base), em linha com `check-openapi-breaking`. -### Job: `i18n` +### Tarefa: `i18n` -Matriz completa de validação de i18n (um job por locale). O job inteiro é informativo. +Matriz completa de validação de i18n (uma tarefa por locale). Toda a tarefa é informativa. -| Script | Valida | Bloqueio | -| ------------------------------- | --------------------------------- | --------------------------------------------------------- | -| `validate_translation.py quick` | Completude da tradução por locale | **Informativo** (`continue-on-error: true` em todo o job) | +| Script | Valida | Bloqueante | +| ------------------------------- | --------------------------------- | ------------------------------------------------------------ | +| `validate_translation.py quick` | Completude da tradução por locale | **Informativo** (`continue-on-error: true` em toda a tarefa) | -### Job: `pr-test-policy` +### Tarefa: `pr-test-policy` -É executado apenas em pull requests. +Executada apenas em pull requests. -| Script | Valida | Bloqueio | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | Os PRs que alteram código de produção em `src/`, `open-sse/`, `electron/` ou `bin/` têm de incluir ou atualizar testes (Regra Rígida #8) | Sim | -| `check:test-masking` | Os ficheiros de teste alterados não reduzem a contagem líquida de asserções nem adicionam tautologias `assert.ok(true)` | Sim | -| `check:pr-evidence` | O corpo do PR apresenta provas de testes/VPS para a alteração (mecaniza a Regra Rígida #18 procurando padrões no texto do PR — frágil, ver Backlog) | Sim | +| Script | Valida | Bloqueante | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | +| `check:pr-test-policy` | Os PRs que alteram código de produção em `src/`, `open-sse/`, `electron/` ou `bin/` têm de incluir ou atualizar testes (Regra Rígida #8) | Sim | +| `check:test-masking` | Os ficheiros de teste alterados não reduzem a contagem líquida de asserções nem adicionam tautologias `assert.ok(true)` | Sim | +| `check:pr-evidence` | O corpo do PR menciona evidências de testes/VPS para a alteração (automatiza a Regra Rígida #18 pesquisando o texto do PR — frágil, ver Backlog) | Sim | -### Job: `test-vitest` +### Tarefa: `test-vitest` -É executado após `build`. Bloqueia a integração em caso de falha. +Executada após `build`. Bloqueia a integração em caso de falha. -| Suite | Valida | Bloqueio | -| ---------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Servidor MCP (110 ferramentas), autoCombo, cache — runner vitest | Sim | -| `test:vitest:ui` | Testes de componentes da IU — runner vitest | **Bloqueante** — as falhas preexistentes são explicitamente excluídas em `vitest.config.ts`; novas falhas fazem o job falhar | +| Suite | Valida | Bloqueante | +| ---------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Servidor MCP (110 ferramentas), autoCombo, cache — executor vitest | Sim | +| `test:vitest:ui` | Testes de componentes da IU — executor vitest | **Bloqueante** — as falhas preexistentes estão explicitamente excluídas em `vitest.config.ts`; novas falhas fazem a tarefa falhar | ### Workflows noturnos (agendados, informativos) -São executados segundo um agendamento cron (e através de `workflow_dispatch`), nunca em PRs. Todos são informativos. +São executados de acordo com um agendamento cron (e `workflow_dispatch`), nunca em PRs. Todos são informativos. -| Workflow | Valida | Bloqueio | +| Workflow | Valida | Bloqueante | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | Testes baseados em propriedades com fast-check, utilizando uma seed aleatória e um número elevado de execuções | **Informativo** | -| `nightly-resilience` | Controlo do crescimento da heap, injeção caótica de falhas, testes de carga/soak com k6 | **Informativo** | -| `nightly-llm-security` | Proteção contra injeção do promptfoo (modo de bloqueio) + sondas garak (ignoradas sem um segredo de fornecedor) | **Informativo** | +| `nightly-property` | Testes baseados em propriedades com fast-check, uma semente aleatória e um número elevado de execuções | **Informativo** | +| `nightly-resilience` | Validação do crescimento do heap, injeção caótica de falhas, testes de carga/prolongados com k6 | **Informativo** | +| `nightly-llm-security` | Proteção contra injeção com promptfoo (modo de bloqueio) + sondas garak (ignoradas sem um segredo de fornecedor) | **Informativo** | | `nightly-schemathesis` | Fuzzing do contrato OpenAPI (schemathesis) contra uma instância OmniRoute ativa utilizando `docs/openapi.yaml` — revela violações da especificação/erros 500 não tratados (Fase 8 B.4) | **Informativo** | -| `nightly-mutation` | Pontuação dos testes de mutação Stryker na via rápida de testes unitários — mutantes sobreviventes revelam asserções fracas | **Informativo** | -| `nightly-compat` | Matriz de compatibilidade do motor Node para os intervalos suportados de `engines.node` | **Informativo** | +| `nightly-mutation` | Pontuação dos testes de mutação Stryker na via rápida de testes unitários — os mutantes sobreviventes revelam asserções fracas | **Informativo** | +| `nightly-compat` | Matriz de compatibilidade do motor Node em todos os intervalos `engines.node` suportados | **Informativo** | --- @@ -536,43 +540,44 @@ erro de ligação e nunca interrompe as sessões. --- -## Lista de Pendentes de Racionalização (revisão de ROI — Fase 9 Onda 3) +## Backlog de racionalização (revisão de ROI — Fase 9 Onda 3) Este inventário foi reconciliado com `ci.yml` em 2026-06-17 (a versão anterior omitia `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Uma revisão de ROI do conjunto reconciliado -identificou os seguintes candidatos a racionalização. **As fusões são alterações mecânicas -de CI; as inversões/remoções são decisões de política reservadas ao operador.** Nada do que -se segue foi ainda aplicado. - -**Também não documentados acima** (consultivos, baixo sinal): a tarefa `docs-lint` -(markdownlint + Vale, tarefa completa com `continue-on-error`) e os fluxos de trabalho de análise autónomos -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` está em -`quality-baseline.json`, mas não está ligado a um mecanismo de bloqueio progressivo em `ci.yml` — a métrica +identificou os seguintes candidatos a racionalização. **As consolidações são alterações +mecânicas de CI; as ativações/desativações e remoções são decisões de política reservadas +ao operador.** Nada do que se segue foi aplicado. + +**Também não documentados acima** (consultivos, sinal fraco): a tarefa `docs-lint` +(markdownlint + Vale, com `continue-on-error` em toda a tarefa) e os workflows autónomos de +análise `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` consta de +`quality-baseline.json`, mas não está ligado a um ratchet bloqueante em `ci.yml` — a métrica está atualmente órfã. -### Fusão / desduplicação (mecânica, menor risco) +### Consolidação / desduplicação (mecânica, menor risco) -Cada candidato foi validado relativamente ao estado real dos controlos em 2026-06-17 (confiar, mas verificar); -várias fusões "óbvias" revelaram ocultar dívida e **não** são substituições diretas sem riscos. +Cada candidato foi validado face ao estado efetivo dos gates em 2026-06-17 (confiar, mas +verificar); várias consolidações "óbvias" acabaram por ocultar dívida e **não** podem ser +aplicadas diretamente sem problemas. -- **`check:docs-sync` é executado duas vezes** — de forma autónoma na tarefa `lint` e novamente dentro de `check:docs-all` (`docs-sync-strict`) e do hook de pre-commit do husky. ✅ **CONCLUÍDO** — invocação autónoma em `lint` removida. -- **Análise de CVE** — ❌ **NÃO é uma fusão simples.** `audit:deps` falha de forma definitiva perante qualquer CVE de gravidade alta/crítica; `check:vuln-ratchet` (osv) apenas falha perante uma _regressão_ relativamente à referência (atualmente 1 MODERATE). Semânticas diferentes — remover `audit:deps` eliminaria o controlo absoluto de gravidade alta/crítica. Manter ambos. -- **Deteção de ciclos** — ❌ **NÃO é uma fusão simples.** `check:circular-deps` (dpdm) comunica **91 ciclos** (razão pela qual é consultivo); não pode ser promovido a bloqueante sem os resolver primeiro e tem um âmbito mais abrangente do que o `check:cycles`, que está verde e foi cuidadosamente delimitado. Manter `check:cycles` como bloqueante; resolver os 91 ciclos do dpdm constitui uma tarefa pendente própria. -- **Complexidade** — ✅ **CONCLUÍDO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): uma passagem do ESLint, com contagens por ruleId, para que os valores de referência de complexidade ciclomática+máximo de linhas e de complexidade cognitiva permaneçam independentes; os comandos individuais `check:complexity` / `check:cognitive-complexity` permanecem disponíveis para `--update` local. -- **Anti-alucinação de `/api`** — ✅ **CONCLUÍDO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): um inventário do sistema de ficheiros de `src/app/api`; openapi-routes + docs-symbols continuam a produzir relatórios independentes; os comandos individuais permanecem disponíveis para execuções locais. -- **`check:node-runtime` é executado em 11 tarefas** — ⚠️ **ROI baixo.** Cada uma utiliza um executor separado e a verificação demora <1s; poupança total de ~10s, em troca da perda de uma salvaguarda económica por tarefa. Não justifica a perturbação. -- **`typecheck:noimplicit:core` no lint de CI** — ✅ **removido da tarefa lint** (era consultivo com `continue-on-error`); a superfície de tipos bloqueante é `typecheck:core` + `check:type-coverage`. Script local mantido. +- **`check:docs-sync` é executado duas vezes** — de forma autónoma na tarefa `lint` e novamente dentro de `check:docs-all` (`docs-sync-strict`) e do hook de pre-commit do husky. ✅ **CONCLUÍDO** — removida a invocação autónoma em `lint`. +- **Análise de CVE** — ❌ **NÃO é uma consolidação direta.** `audit:deps` falha de forma bloqueante perante qualquer CVE alta/crítica; `check:vuln-ratchet` (osv) só falha perante uma _regressão_ relativamente à baseline (atualmente 1 MODERATE). Semânticas diferentes — remover `audit:deps` eliminaria o gate absoluto para vulnerabilidades altas/críticas. Manter ambos. +- **Deteção de ciclos** — ✅ **CONCLUÍDO** (#15159 G-01/G-02). O texto anterior descrevia `check:cycles` como o gate "verde e curado" e justificava mantê-lo bloqueante porque `check:circular-deps` (dpdm) comunicava 91 ciclos. Esse resultado verde era um **falso positivo de sucesso**: `check:cycles` analisava 5 subdiretórios (450 ficheiros), apenas correspondia a `import|export … from` estáticos e descartava todos os especificadores `@/` e `@omniroute/open-sse/`, pelo que não conseguia detetar os ciclos de importação dinâmica + aliases que predominavam no repositório. Corrigido: o gate percorre agora `src` + `open-sse` (5023 ficheiros), recolhe especificadores da AST do TypeScript (pelo que `import("…")` conta e `typeof import("…")` em posição de tipo não conta) e resolve `paths` do tsconfig. Deteta **14** ciclos, não 0. Uma vez que 14 ciclos preexistentes não podem ser corrigidos num PR de gate, `check:cycles` é agora um **ratchet** (`--ratchet`, limite máximo `metrics.cycles.value = 14` em `quality-baseline.json`, `direction: down`) — bloqueia qualquer _regressão_ e a contagem só pode diminuir. O CI executa `npm run check:cycles:ratchet`. A redução progressiva será efetuada com **A-01**. `check:circular-deps` (dpdm) permanece consultivo como segunda opinião mais abrangente. +- **Complexidade** — ✅ **CONCLUÍDO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): uma passagem do ESLint, com contagens por ruleId para que as baselines de complexidade ciclomática+max-lines e cognitiva permaneçam independentes; `check:complexity` / `check:cognitive-complexity` individuais são mantidos para utilização local com `--update`. +- **Anti-alucinação de `/api`** — ✅ **CONCLUÍDO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): um único inventário do sistema de ficheiros de `src/app/api`; openapi-routes + docs-symbols continuam a produzir relatórios independentes; as verificações individuais são mantidas para execuções locais. +- **`check:node-runtime` é executado em 11 tarefas** — ⚠️ **ROI baixo.** Cada tarefa utiliza um runner separado e a verificação demora <1s; poupança total de ~10s, à custa da perda de uma salvaguarda económica por tarefa. Não justifica a perturbação. +- **`typecheck:noimplicit:core` no lint do CI** — ✅ **removido da tarefa lint** (era consultivo com `continue-on-error`); a superfície de tipos bloqueante é `typecheck:core` + `check:type-coverage`. Script local mantido. -### Inverter / decidir (política do operador) +### Ativar/desativar / decidir (política do operador) -- `check:openapi-security-tiers` (consultivo) — ❌ **NÃO pode ser simplesmente tornado bloqueante.** Termina com 0, mas avisa que várias rotas de `traffic-inspector` sob `LOCAL_ONLY_API_PREFIXES` não têm a anotação `x-loopback-only: true`. A sua aplicação obrigatória requer primeiro a adição dessas anotações a `openapi.yaml`. -- `typecheck:noimplicit:core` (consultivo) — amplamente substituído pelo mecanismo progressivo bloqueante `check:type-coverage`. Converter num mecanismo progressivo ou remover a segunda passagem redundante de `tsc`. -- `test:vitest:ui` (agora **bloqueante**) — as falhas preexistentes estão explicitamente excluídas em `vitest.config.ts` através de comentários de acompanhamento `// #8618`; novas falhas fazem a tarefa falhar. -- `check:secrets` (gitleaks, mecanismo progressivo bloqueante fixado em 3 falsos positivos documentados) — adicionar os 3 à lista de permissões para chegar a 0 ou despromover para consultivo. Sobrepõe-se à análise de segredos nativa do GitHub + `check:public-creds`. -- `check:pr-evidence` (bloqueante, procura padrões no texto do corpo do PR) — elevado risco de falsos positivos; a remoção enfraquece a aplicação da Regra Rígida #18, pelo que esta é uma verdadeira decisão de política. -- `semgrep` (autónomo consultivo) — sobrepõe-se ao CodeQL para as famílias OWASP; ligar o respetivo valor de referência a um mecanismo progressivo ou remover. +- `check:openapi-security-tiers` (consultivo) — ❌ **NÃO pode ser tornado bloqueante diretamente.** Termina com 0, mas avisa que várias rotas `traffic-inspector` sob `LOCAL_ONLY_API_PREFIXES` não possuem a anotação `x-loopback-only: true`. Torná-lo bloqueante exige primeiro adicionar essas anotações a `openapi.yaml`. +- `typecheck:noimplicit:core` (consultivo) — amplamente substituído pelo ratchet bloqueante `check:type-coverage`. Convertê-lo num ratchet ou remover a segunda passagem redundante do `tsc`. +- `test:vitest:ui` (agora **bloqueante**) — as falhas preexistentes são explicitamente excluídas em `vitest.config.ts` com comentários de acompanhamento `// #8618`; novas falhas fazem a tarefa falhar. +- `check:secrets` (gitleaks, ratchet bloqueante fixado em 3 falsos positivos documentados) — adicionar os 3 à lista de permissões para chegar a 0, ou despromover para consultivo. Sobrepõe-se à análise de segredos nativa do GitHub + `check:public-creds`. +- `check:pr-evidence` (bloqueante, procura padrões no texto do corpo do PR) — risco elevado de falsos positivos; a sua remoção enfraquece a aplicação da Regra Rígida #18, pelo que esta é uma verdadeira decisão de política. +- `semgrep` (consultivo autónomo) — sobrepõe-se ao CodeQL nas famílias OWASP; ligar a respetiva baseline a um ratchet ou remover. --- diff --git a/docs/i18n/pt/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/pt/docs/compression/COMPRESSION_GUIDE.md index fe9df580b84b..742605b02f96 100644 --- a/docs/i18n/pt/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/pt/docs/compression/COMPRESSION_GUIDE.md @@ -452,8 +452,7 @@ em vez da inglesa. Num corpo com `messages`, uma exclusão baseada no conteúdo (`shouldBypassCavemanOutputMode()` em `open-sse/services/compression/outputMode.ts`) verifica as últimas três mensagens e ignora os estilos durante todo o turno quando correspondem às suas palavras-chave de segurança, ação irreversível, -clarificação ou sensíveis à ordem. A exclusão é executada de acordo com a definição do botão -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) do painel. +clarificação ou sensíveis à ordem. A exclusão é executada enquanto o botão **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) do painel estiver ativado, que é a predefinição; com o botão desativado, os estilos selecionados também se aplicam nesses turnos. Quando a exclusão permite a passagem do turno, `placeSystemInstruction()` (no mesmo ficheiro), que nunca cria um novo `messages[0]`, coloca o bloco no primeiro dos seguintes locais que encontrar: diff --git a/docs/i18n/pt/docs/reference/ENVIRONMENT.md b/docs/i18n/pt/docs/reference/ENVIRONMENT.md index bda68790ef27..88c3f4b4c38e 100644 --- a/docs/i18n/pt/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/pt/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Segredos Obrigatórios -Estes **têm** de ser definidos antes da primeira execução. Sem eles, a aplicação recusará iniciar ou funcionará com predefinições inseguras. - -| Variável | Obrigatório | Predefinição | Ficheiro de Origem | Descrição | -| ---------------------------- | ------------------ | ------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Sim** | _(nenhuma)_ | `src/lib/auth` | Assina/verifica todos os cookies de sessão do painel (JWT). Gere com `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Sim** | _(nenhuma)_ | `src/lib/db/apiKeys.ts` | Chave de encriptação AES para os valores das chaves de API armazenados no SQLite. Gere com `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Sim** | `CHANGEME` | Script de inicialização | Define a palavra-passe inicial de administrador do painel (corresponde à predefinição de `.env.example` — mantida propositadamente insegura para forçar uma alteração). **Altere-a antes da primeira utilização.** Após iniciar sessão, altere-a através de Painel → Definições → Segurança. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sim** (produção) | _(não definido)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Segredo partilhado para a ponte WebSocket interna de Respostas Codex. Autentica os pedidos da ponte entre o retransmissor WS do Electron/navegador e o OmniRoute. ⚠️ **OBRIGATÓRIO em produção — quando não está definido, todos os pedidos à ponte WS são rejeitados.** Gere com `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Não | _(SHA do git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID explícito para invalidação da cache do service worker da shell da PWA (#11779); primeiro na cadeia de resolução. | -| `SOURCE_VERSION` | Não | _(não definido)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Segundo na cadeia — definido pelos sistemas de compilação PaaS (por exemplo, ao estilo do Heroku) como o commit implementado. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Não | _(derivado)_ | `src/shared/components/PwaRegister.tsx` | Valor público definido durante a compilação que o cliente utiliza para registar `/sw.js?v=…`; derivado dos dois anteriores e, em seguida, do SHA do git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Não (automático) | _(automático em cada arranque)_ | `src/server/authz/policies/management.ts` | Segredo por processo que comprova que a marca do IP do par fidedigno provém do próprio servidor HTTP do OmniRoute (`scripts/dev/peer-stamp.mjs`). O middleware de autorização só confia na localidade do pedido (restrição a loopback/LAN das rotas LOCAL_ONLY) quando a marca contém este token. Gerado automaticamente em cada arranque — deixe-o por definir; fixe-o apenas em configurações com vários processos que tenham de partilhar a marca. | -| `OMNIROUTE_TRUSTED_PROXIES` | Não | _(não definido)_ | `scripts/dev/peer-stamp.mjs` | IPs ou intervalos CIDR, separados por vírgulas, de proxies inversos cujos cabeçalhos `X-Forwarded-For` / `X-Real-IP` podem ser considerados fidedignos pela lista de permissões/bloqueios de IP, além dos endereços de loopback, dos endereços de redes privadas e dos nós periféricos da Cloudflare, que são sempre considerados fidedignos. Necessário apenas para um proxy em qualquer outro endereço público; caso contrário, o filtro avalia o endereço do próprio proxy. | +Os segredos não definidos marcados como **Sim** são gerados automaticamente na primeira execução (`JWT_SECRET`, `API_KEY_SECRET`) e guardados em `server.env` no diretório de dados — defina-os explicitamente quando esse diretório for efémero. A exceção é `OMNIROUTE_WS_BRIDGE_SECRET`: não é guardado em `server.env` — os pontos de entrada do servidor cunham um valor efémero por arranque que apenas a ponte no próprio processo pode utilizar, pelo que os clientes externos da ponte precisam de o definir explicitamente. + +| Variável | Obrigatório | Predefinição | Ficheiro de Origem | Descrição | +| ---------------------------- | ------------------ | ------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Sim** | _(nenhuma)_ | `src/lib/auth` | Assina/verifica todos os cookies de sessão do painel (JWT). Gere com `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Sim** | _(nenhuma)_ | `src/lib/db/apiKeys.ts` | Chave de encriptação AES para os valores das chaves de API armazenados no SQLite. Gere com `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Não | _(não definido)_ | `src/lib/auth/managementPassword.ts` | Define a palavra-passe do painel no arranque enquanto nenhuma estiver guardada; é ignorado assim que exista uma palavra-passe guardada. As instalações npm e a partir do código-fonte copiam `.env.example` para `.env`, pelo que arrancam com o seu marcador de posição publicamente conhecido `CHANGEME`: substitua-o antes do primeiro arranque, ou defina `INITIAL_PASSWORD=` (vazio) nesse `.env` para criar a palavra-passe no assistente de configuração inicial na primeira visita, tal como faz um valor não definido (a predefinição da imagem Docker). Altere uma palavra-passe guardada em Painel → Definições → Segurança, ou execute `omniroute reset-password` (`node bin/reset-password.mjs` num checkout do código-fonte) com `DATA_DIR` definido para o diretório de dados do servidor. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Sim** (produção) | _(não definido)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Segredo partilhado para a ponte WebSocket interna de Respostas Codex. Autentica os pedidos da ponte entre o retransmissor WS do Electron/navegador e o OmniRoute. ⚠️ **OBRIGATÓRIO em produção — quando não está definido, todos os pedidos à ponte WS são rejeitados.** Gere com `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Não | _(SHA do git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID explícito para invalidação da cache do service worker da shell da PWA (#11779); primeiro na cadeia de resolução. | +| `SOURCE_VERSION` | Não | _(não definido)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Segundo na cadeia — definido pelos sistemas de compilação PaaS (por exemplo, ao estilo do Heroku) como o commit implementado. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Não | _(derivado)_ | `src/shared/components/PwaRegister.tsx` | Valor público definido durante a compilação que o cliente utiliza para registar `/sw.js?v=…`; derivado dos dois anteriores e, em seguida, do SHA do git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Não (automático) | _(automático em cada arranque)_ | `src/server/authz/policies/management.ts` | Segredo por processo que comprova que a marca do IP do par fidedigno provém do próprio servidor HTTP do OmniRoute (`scripts/dev/peer-stamp.mjs`). O middleware de autorização só confia na localidade do pedido (restrição a loopback/LAN das rotas LOCAL_ONLY) quando a marca contém este token. Gerado automaticamente em cada arranque — deixe-o por definir; fixe-o apenas em configurações com vários processos que tenham de partilhar a marca. | +| `OMNIROUTE_TRUSTED_PROXIES` | Não | _(não definido)_ | `scripts/dev/peer-stamp.mjs` | IPs ou intervalos CIDR, separados por vírgulas, de proxies inversos cujos cabeçalhos `X-Forwarded-For` / `X-Real-IP` podem ser considerados fidedignos pela lista de permissões/bloqueios de IP, além dos endereços de loopback, dos endereços de redes privadas e dos nós periféricos da Cloudflare, que são sempre considerados fidedignos. Necessário apenas para um proxy em qualquer outro endereço público; caso contrário, o filtro avalia o endereço do próprio proxy. | ### Comandos de Geração @@ -216,38 +216,38 @@ O OmniRoute utiliza **SQLite** (através de `better-sqlite3`) para toda a persis ## 4. Segurança e Autenticação -| Variável | Predefinição | Ficheiro de Origem | Descrição | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinado com identificadores de hardware para criar a impressão digital da máquina. Altere-o em cada implementação para garantir o isolamento. | -| `OMNIROUTE_CLI_SALT` | _(não definido = salt aleatório por instalação, guardado em `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC para derivar o token de autenticação da CLI local. Definir este valor faz a rotação de todos os tokens da CLI na máquina e tem sempre prioridade sobre o salt guardado. Consulte `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Define o sinalizador `Secure` nos cookies de sessão. **Tem de ser `true`** quando executado atrás de HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware da API | Quando definido como `true`, todos os pedidos de proxy para `/v1/*` têm de incluir uma chave de API válida. **Este sinalizador não controla o acesso a `GET /v1/models`**, que segue, em vez disso, a postura de início de sessão do painel (`requireAuthForModels`) — por isso, uma resposta `401` de `/v1/models` NÃO significa que a inferência esteja protegida. Consulte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite revelar os valores completos das chaves de API na interface do Painel. Configurável através dos Sinalizadores de Funcionalidades do Painel; representa um risco de segurança em instâncias partilhadas. | -| `NO_LOG_API_KEY_IDS` | _(vazio)_ | `src/lib/compliance/index.ts` | IDs de chaves de API, separados por vírgulas, que ignoram o registo de pedidos (conformidade com o RGPD). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(não definido = ilimitado)_ | `src/shared/utils/apiKeyPolicy.ts` | Orçamento diário de pedidos de contingência aplicado às chaves de API cuja coluna `rate_limits` seja nula. Não definido ou vazio: sem limite implícito (#2289, #11017). `0` tem o mesmo significado (ilimitado). Um número inteiro positivo N ativa N/dia, 5N/semana e 20N/mês. Valores não vazios malformados revertem para os limites legados de 1000/dia, 5000/semana e 20000/mês. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Tamanho máximo permitido para o corpo do pedido. Rejeita cargas úteis que excedam este limite. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Os corpos reais dos pedidos com tamanho igual ou superior a este limiar obtêm a concessão atómica de admissão local ao processo para cargas pesadas antes da análise do JSON (caminho BYTE, incluindo `POST /v1/responses`). Aplica-se a mesma exceção de margem saudável de [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) usada para estruturas pesadas; continua limitada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), para que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) não volte a ocorrer. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite rígido da rota de chat aplicado aos bytes lidos durante a ingestão limitada, incluindo pedidos com `Content-Length` ausente, inválido ou desonesto; o excesso recebe `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(não definido — sem limite de número de pedidos)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** este limite legado de NÚMERO de pedidos aplica-se agora apenas quando explicitamente definido. Quando não definido (a predefinição), a admissão de pedidos de chat pesados é, em vez disso, controlada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — um orçamento de BYTES derivado automaticamente, dimensionado a partir do limite real de memória do processo em **um processo** (uma heap V8). Dois pedidos `/v1/responses` sobrepostos com ~750 mil tokens abortam heaps de ~12 GiB (#7849) — um aviso de **orçamento de memória**, não um máximo rígido de produto de 2. Um processo saudável (heap abaixo do rácio de descarte) PODE admitir mais pedidos `/v1/responses` longos em simultâneo através de `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dezenas de clientes SSE de longa duração (40–50) dependem da heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, não de um «máximo de 2». Aumentar este valor indiscriminadamente para «usar o anfitrião» reintroduz o #7849. Multiplique as **heaps** com **N `DATA_DIR`s independentes** (#11024); nunca use `replicas>1` num único ficheiro SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivado automaticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** substituição do orçamento de bytes de ingestão derivado automaticamente (25% do limite mais restritivo de memória V8/cgroup, dividido por uma amplificação transitória de 8x). Os valores derivados e explícitos são limitados ao intervalo de 8 MiB–2 GiB. Um corpo maior do que o orçamento efetivo falha imediatamente com `413 body_exceeds_budget`; a contenção entre corpos que poderiam ser processados individualmente continua a permitir nova tentativa com `503`. A existência de 40–50 clientes SSE longos em simultâneo depende deste orçamento + heap, não de um «máximo de 2» rígido. Consulte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` em `/api/monitoring/health` antes de ajustar. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Rácio de descarte por pressão da heap (`heapUsed / heap_size_limit`) para a admissão pesada por BYTES e ESTRUTURA (#10183, #10268, #10437). Um pedido pesado em simultâneo que ultrapasse `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` só é descartado com o `503` que permite nova tentativa quando a heap TAMBÉM se encontra neste rácio ou acima dele; numa heap saudável, é admitido através da margem de capacidade saudável. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (predefinição `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacidade adicional limitada para o caminho rápido de heap saudável (#10437), tanto para ESTRUTURA como para BYTES (`admitChatRequest`, incluindo corpos ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sem este limite, todos os pedidos com a heap ocupada, mas saudável, contornavam a admissão sem qualquer teto. Assim que este número de concessões simultâneas estiver ativo através do caminho de heap saudável, os pedidos adicionais com o sistema ocupado passam para o MESMO caminho de espera limitada/descarte utilizado sob pressão real da heap. `0` desativa completamente este desvio. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Número de mensagens que classifica um pedido de chat como pesado, mesmo quando o respetivo corpo está abaixo do limiar de bytes. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Número de ferramentas que classifica um pedido de chat como pesado, mesmo quando o respetivo corpo está abaixo do limiar de bytes. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimativa conservadora de tokens com base no tamanho das cadeias de caracteres, que classifica um pedido como pesado; trata-se de uma aproximação do custo de admissão, não da tokenização de faturação do fornecedor. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (desativado) | `src/shared/middleware/chatBodyAdmission.ts` | Limite opcional e explícito para o histórico de chat. Desativado por predefinição: o número de mensagens é uma política de implementação, não uma propriedade universal de um pedido, e aplicar aqui um limite rejeita conversas com um `413` terminal antes de o pipeline de compressão as poder tornar processáveis. O crescimento da heap é limitado por `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` e pelo descarte por pressão da heap. Defina um valor positivo em implementações com memória limitada que necessitem de um teto rígido; o excesso recebe então um `413` estruturado a indicar que é necessária compactação. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite rígido para uma resposta upstream sem streaming, integralmente armazenada em memória. Acima deste limite, o leitor upstream é cancelado e o pedido falha rapidamente, em vez de aumentar uma cadeia de caracteres sem limites até esgotar a heap. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Número máximo de bytes transmitidos reencaminhados a partir dos cabeçalhos da resposta a montante. Quando o limite é excedido, os cabeçalhos de menor prioridade (por exemplo, `x-codex-*`, `x-oai-request-id`) são removidos para respeitar os limites comuns dos cabeçalhos de proxies inversos. Defina um valor superior para reencaminhar mais metadados a montante, à custa de um tamanho maior dos cabeçalhos da resposta. | -| `CORS_ORIGIN` | _(não definido)_ | `src/server/cors/origins.ts` | Lista legada de origens únicas permitidas pelo CORS. Dê preferência a `CORS_ALLOWED_ORIGINS` em novas implementações. O CORS destina-se apenas a clientes de API de navegador entre origens; as escritas autenticadas no painel utilizam pedidos da mesma origem, juntamente com proteção CSRF associada à sessão. | -| `CORS_ALLOWED_ORIGINS` | _(não definido)_ | `src/server/cors/origins.ts` | Lista de origens permitidas pelo CORS, separadas por vírgulas. Não é enviado nenhum carácter universal, a menos que `CORS_ALLOW_ALL=true` seja configurado explicitamente. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanismo de exceção exclusivo para desenvolvimento que permite devolver qualquer `Origin` do navegador. Não o ative em implementações partilhadas ou de produção. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Bloqueie chamadas a fornecedores direcionadas para intervalos de IP privados, de loopback ou link-local. Desative apenas em ambientes de teste isolados. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Permita URLs de fornecedores que apontem para redes privadas/locais (localhost, 192.168.x.x, 10.x.x.x, etc.). **OBRIGATÓRIO para fornecedores alojados localmente** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Quando definido como `false`, o painel rejeita a validação de URLs locais. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Permita adicionar/validar fornecedores em endereços locais/privados (127.0.0.1, localhost, LAN, intervalos privados) — limitado ao fluxo de validação do fornecedor. **Predefinição: `true`** (prioridade ao local); defina como `false` para impor um bloqueio estrito apenas a endereços públicos. Os endpoints de metadados da nuvem (169.254.169.254, metadata.google.internal) permanecem bloqueados em qualquer circunstância. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permita que as rotas `/v1/audio/*` (transcrições, voz, traduções) utilizem um nó de fornecedor compatível com OpenAI alojado fora de localhost. Desativado por predefinição — encaminhar áudio para um anfitrião remoto altera a identidade de saída e tem de ser uma decisão explícita do operador. Os nós de loopback/privados (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permita que `POST /v1/rerank` (e o passo de reclassificação por loopback do motor de memória) utilize um nó de fornecedor compatível com OpenAI alojado fora de localhost — um equipamento na LAN ou um par Tailscale que execute TEI, Infinity, vLLM, etc. Desativado por predefinição — encaminhar para um anfitrião remoto altera a identidade de saída e tem de ser uma decisão explícita do operador. Os nós de loopback (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. Os nós remotos também têm de cumprir a política de URLs de saída do fornecedor (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); os anfitriões de metadados da nuvem nunca recebem tráfego encaminhado. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Quando o OIDC está ativado, desative o início de sessão por palavra-passe para que os utilizadores apenas se possam autenticar através do início de sessão único OIDC. O alias simples `OIDC_DISABLE_PASSWORD_LOGIN` também é aceite; o sinalizador de funcionalidade do painel com a mesma chave tem precedência. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simples de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variável | Predefinição | Ficheiro de Origem | Descrição | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinado com identificadores de hardware para criar a impressão digital da máquina. Altere-o em cada implementação para garantir o isolamento. | +| `OMNIROUTE_CLI_SALT` | _(não definido = salt aleatório por instalação, guardado em `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC para derivar o token de autenticação da CLI local. Definir este valor faz a rotação de todos os tokens da CLI na máquina e tem sempre prioridade sobre o salt guardado. Consulte `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Define o sinalizador `Secure` nos cookies de sessão. **Tem de ser `true`** quando executado atrás de HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware da API | Quando definido como `true`, todos os pedidos de proxy para `/v1/*` têm de incluir uma chave de API válida. **Este sinalizador não controla o acesso a `GET /v1/models`**, que segue, em vez disso, a postura de início de sessão do painel (`requireAuthForModels`) — por isso, uma resposta `401` de `/v1/models` NÃO significa que a inferência esteja protegida. Consulte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite revelar os valores completos das chaves de API na interface do Painel. Configurável através dos Sinalizadores de Funcionalidades do Painel; representa um risco de segurança em instâncias partilhadas. | +| `NO_LOG_API_KEY_IDS` | _(vazio)_ | `src/lib/compliance/index.ts` | IDs de chaves de API, separados por vírgulas, que ignoram o registo de pedidos (conformidade com o RGPD). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(não definido = ilimitado)_ | `src/shared/utils/apiKeyPolicy.ts` | Orçamento diário de pedidos de contingência aplicado às chaves de API cuja coluna `rate_limits` seja nula. Não definido ou vazio: sem limite implícito (#2289, #11017). `0` tem o mesmo significado (ilimitado). Um número inteiro positivo N ativa N/dia, 5N/semana e 20N/mês. Valores não vazios malformados revertem para os limites legados de 1000/dia, 5000/semana e 20000/mês. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Tamanho máximo permitido para o corpo do pedido. Rejeita cargas úteis que excedam este limite. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Os corpos reais dos pedidos com tamanho igual ou superior a este limiar obtêm a concessão atómica de admissão local ao processo para cargas pesadas antes da análise do JSON (caminho BYTE, incluindo `POST /v1/responses`). Aplica-se a mesma exceção de margem saudável de [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) usada para estruturas pesadas; continua limitada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), para que [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) não volte a ocorrer. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limite rígido da rota de chat aplicado aos bytes lidos durante a ingestão limitada, incluindo pedidos com `Content-Length` ausente, inválido ou desonesto; o excesso recebe `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(não definido — sem limite de número de pedidos)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** este limite legado de NÚMERO de pedidos aplica-se agora apenas quando explicitamente definido. Quando não definido (a predefinição), a admissão de pedidos de chat pesados é, em vez disso, controlada por `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — um orçamento de BYTES derivado automaticamente, dimensionado a partir do limite real de memória do processo em **um processo** (uma heap V8). Dois pedidos `/v1/responses` sobrepostos com ~750 mil tokens abortam heaps de ~12 GiB (#7849) — um aviso de **orçamento de memória**, não um máximo rígido de produto de 2. Um processo saudável (heap abaixo do rácio de descarte) PODE admitir mais pedidos `/v1/responses` longos em simultâneo através de `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Dezenas de clientes SSE de longa duração (40–50) dependem da heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, não de um «máximo de 2». Aumentar este valor indiscriminadamente para «usar o anfitrião» reintroduz o #7849. Multiplique as **heaps** com **N `DATA_DIR`s independentes** (#11024); nunca use `replicas>1` num único ficheiro SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivado automaticamente)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** substituição do orçamento de bytes de ingestão derivado automaticamente (25% do limite mais restritivo de memória V8/cgroup, dividido por uma amplificação transitória de 8x). Os valores derivados e explícitos são limitados ao intervalo de 8 MiB–2 GiB. Um corpo maior do que o orçamento efetivo falha imediatamente com `413 body_exceeds_budget`; a contenção entre corpos que poderiam ser processados individualmente continua a permitir nova tentativa com `503`. A existência de 40–50 clientes SSE longos em simultâneo depende deste orçamento + heap, não de um «máximo de 2» rígido. Consulte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` em `/api/monitoring/health` antes de ajustar. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Rácio de descarte por pressão da heap (`heapUsed / heap_size_limit`) para a admissão pesada por BYTES e ESTRUTURA (#10183, #10268, #10437). Um pedido pesado em simultâneo que ultrapasse `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` só é descartado com o `503` que permite nova tentativa quando a heap TAMBÉM se encontra neste rácio ou acima dele; numa heap saudável, é admitido através da margem de capacidade saudável. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (predefinição `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacidade adicional limitada para o caminho rápido de heap saudável (#10437), tanto para ESTRUTURA como para BYTES (`admitChatRequest`, incluindo corpos ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Sem este limite, todos os pedidos com a heap ocupada, mas saudável, contornavam a admissão sem qualquer teto. Assim que este número de concessões simultâneas estiver ativo através do caminho de heap saudável, os pedidos adicionais com o sistema ocupado passam para o MESMO caminho de espera limitada/descarte utilizado sob pressão real da heap. `0` desativa completamente este desvio. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Número de mensagens que classifica um pedido de chat como pesado, mesmo quando o respetivo corpo está abaixo do limiar de bytes. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Número de ferramentas que classifica um pedido de chat como pesado, mesmo quando o respetivo corpo está abaixo do limiar de bytes. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimativa conservadora de tokens com base no tamanho das cadeias de caracteres, que classifica um pedido como pesado; trata-se de uma aproximação do custo de admissão, não da tokenização de faturação do fornecedor. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (desativado) | `src/shared/middleware/chatBodyAdmission.ts` | Limite opcional e explícito para o histórico de chat. Desativado por predefinição: o número de mensagens é uma política de implementação, não uma propriedade universal de um pedido, e aplicar aqui um limite rejeita conversas com um `413` terminal antes de o pipeline de compressão as poder tornar processáveis. O crescimento da heap é limitado por `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` e pelo descarte por pressão da heap. Defina um valor positivo em implementações com memória limitada que necessitem de um teto rígido; o excesso recebe então um `413` estruturado a indicar que é necessária compactação. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limite rígido para uma resposta upstream sem streaming, integralmente armazenada em memória. Acima deste limite, o leitor upstream é cancelado e o pedido falha rapidamente, em vez de aumentar uma cadeia de caracteres sem limites até esgotar a heap. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Número máximo de bytes transmitidos reencaminhados a partir dos cabeçalhos da resposta a montante. Quando o limite é excedido, os cabeçalhos de menor prioridade (por exemplo, `x-codex-*`, `x-oai-request-id`) são removidos para respeitar os limites comuns dos cabeçalhos de proxies inversos. Defina um valor superior para reencaminhar mais metadados a montante, à custa de um tamanho maior dos cabeçalhos da resposta. | +| `CORS_ORIGIN` | _(não definido)_ | `src/server/cors/origins.ts` | Lista legada de origens únicas permitidas pelo CORS. Dê preferência a `CORS_ALLOWED_ORIGINS` em novas implementações. O CORS destina-se apenas a clientes de API de navegador entre origens; as escritas autenticadas no painel utilizam pedidos da mesma origem, juntamente com proteção CSRF associada à sessão. | +| `CORS_ALLOWED_ORIGINS` | _(não definido)_ | `src/server/cors/origins.ts` | Lista de origens permitidas pelo CORS, separadas por vírgulas. Não é enviado nenhum carácter universal, a menos que `CORS_ALLOW_ALL=true` seja configurado explicitamente. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanismo de exceção exclusivo para desenvolvimento que permite devolver qualquer `Origin` do navegador. Não o ative em implementações partilhadas ou de produção. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias antigo. O seu interruptor no painel (uma substituição na base de dados) é lido antes desta variável de ambiente; `false`, `0`, `no` ou `off` em qualquer um deles desliga as verificações de anfitrião da mesma forma que `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, mesmo quando o interruptor no painel dessa flag está guardado como desligado; outros valores deixam a proteção a cargo das duas flags abaixo. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Desliga as verificações de anfitrião do mecanismo de proteção de URLs de saída, incluindo o bloqueio de metadados da nuvem (todo o 169.254.0.0/16, metadata.google.internal), nos caminhos de fornecedor que seguem o modo de proteção: validação de URLs, descoberta de modelos, URLs base dos nós de fornecedor (incluindo nós remotos de reclassificação) e o teste de proxy-fallback, entre outros caminhos de saída adjacentes ao fornecedor, como a obtenção remota de imagens/multimédia. Também permite destinos de webhook privados, exceto endpoints de metadados da nuvem/link-local, que permanecem bloqueados mesmo com esta flag ativada. Os pedidos de conversação despachados através de `BaseExecutor.execute()` mantêm a sua verificação de metadados da nuvem. Os fornecedores locais funcionam com as predefinições: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (predefinição `true`) já permite URLs locais e da LAN nos caminhos de validação, descoberta e nós de fornecedor; o teste de proxy-fallback e os destinos de webhook privados consultam apenas esta flag e bloqueiam anfitriões locais/LAN enquanto estiver desligada. Sobrepõe-se a `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Um valor guardado a partir do interruptor no painel tem precedência sobre esta variável. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Permita URLs de fornecedor em endereços locais/privados (127.0.0.1, localhost, LAN, intervalos privados) para validação de fornecedores, descoberta de modelos, conversação e os restantes caminhos de saída adjacentes ao fornecedor geridos pelo modo de proteção. **Predefinição: `true`** (prioridade ao local): a proteção bloqueia então os endpoints de metadados da nuvem — todo o 169.254.0.0/16 mais os nomes de anfitrião de metadados conhecidos (metadata.google.internal, metadata.goog, IMDS da Alibaba/Azure/Oracle, fd00:ec2::254). Defina `false` para o modo apenas público, que bloqueia também anfitriões privados e de loopback. A proteção verifica o nome de anfitrião ou o literal IP tal como escrito, antes de qualquer consulta DNS; no modo apenas público, o wrapper de obtenção segura resolve adicionalmente o nome de anfitrião e recusa qualquer resposta privada. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` desliga estas verificações nos caminhos listados na linha dessa flag. Os fornecedores locais integrados, como LM Studio, Ollama, vLLM, Llamafile e Triton, ignoram todas as verificações de anfitrião na validação de chaves e na conversação. Um valor guardado a partir do interruptor no painel tem precedência sobre esta variável. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permita que as rotas `/v1/audio/*` (transcrições, voz, traduções) utilizem um nó de fornecedor compatível com OpenAI alojado fora de localhost. Desativado por predefinição — encaminhar áudio para um anfitrião remoto altera a identidade de saída e tem de ser uma decisão explícita do operador. Os nós de loopback/privados (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permita que `POST /v1/rerank` (e o passo de reclassificação por loopback do motor de memória) utilize um nó de fornecedor compatível com OpenAI alojado fora de localhost — um equipamento na LAN ou um par Tailscale que execute TEI, Infinity, vLLM, etc. Desativado por predefinição — encaminhar para um anfitrião remoto altera a identidade de saída e tem de ser uma decisão explícita do operador. Os nós de loopback (localhost, 127.0.0.1, 172.16-31.x) são sempre permitidos e não são afetados. O URL base de um nó remoto também tem de cumprir a política de URLs de saída do fornecedor (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Quando o OIDC está ativado, desative o início de sessão por palavra-passe para que os utilizadores apenas se possam autenticar através do início de sessão único OIDC. O alias simples `OIDC_DISABLE_PASSWORD_LOGIN` também é aceite; o sinalizador de funcionalidade do painel com a mesma chave tem precedência. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simples de `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Lista de verificação de reforço da segurança diff --git a/docs/i18n/ro/CONTRIBUTING.md b/docs/i18n/ro/CONTRIBUTING.md index 3d56a5344725..70139ecaebe5 100644 --- a/docs/i18n/ro/CONTRIBUTING.md +++ b/docs/i18n/ro/CONTRIBUTING.md @@ -391,18 +391,18 @@ Scrieți teste unitare în `tests/unit/` care să acopere cel puțin: - [ ] Testele trec (`npm test`) - [ ] Verificarea lint trece (`npm run lint`) -- [ ] Compilarea reușește (`npm run build`) +- [ ] Build-ul reușește (`npm run build`) - [ ] Au fost adăugate tipuri TypeScript pentru noile funcții și interfețe publice - [ ] Nu există secrete sau valori de rezervă codificate direct -- [ ] Credențialele publice upstream sunt încorporate prin `resolvePublicCred()` (consultați [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), niciodată ca valori literale +- [ ] Credențialele publice upstream sunt încorporate prin `resolvePublicCred()` (consultați [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), niciodată ca literali - [ ] Răspunsurile de eroare sunt procesate prin `buildErrorBody()` / `sanitizeErrorMessage()` — fără urme de stivă neprelucrate în corpurile răspunsurilor (consultați [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Comenzile shell (`exec` / `spawn`) transmit valorile din timpul execuției prin `env`, nu prin interpolarea șirurilor - [ ] Toate datele de intrare sunt validate cu scheme Zod -- [ ] A fost adăugat un **fragment** de jurnal al modificărilor în `changelog.d/{features|fixes|maintenance}/-.md` pentru modificările vizibile utilizatorilor (consultați [`changelog.d/README.md`](./changelog.d/README.md)) — **nu** editați direct `CHANGELOG.md`; fragmentele sunt agregate la momentul lansării și nu intră niciodată în conflict între PR-uri +- [ ] A fost adăugat un **fragment** de jurnal de modificări în `changelog.d/{features|fixes|maintenance}/-.md` pentru modificările vizibile utilizatorilor (consultați [`changelog.d/README.md`](./changelog.d/README.md)) — **nu** editați direct `CHANGELOG.md`; fragmentele sunt agregate la momentul lansării și nu intră niciodată în conflict între PR-uri - [ ] Documentația a fost actualizată (dacă este cazul) -- [ ] Nu au fost deschise alerte CodeQL / Secret-Scanning noi sau fiecare a fost respinsă cu o justificare tehnică ce face referire la documentul relevant din `docs/security/` -- [ ] Rutele care lansează procese copil (`/api/mcp/`, `/api/cli-tools/runtime/`) sunt clasificate ca `isLocalOnlyPath()` în `src/server/authz/routeGuard.ts` — consultați [Regula strictă nr. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Nu există trailere `Co-Authored-By` în mesajele commit-urilor — commit-urile trebuie să apară exclusiv sub identitatea Git a proprietarului depozitului (Regula strictă nr. 16) +- [ ] Nu au fost deschise alerte CodeQL / Secret-Scanning noi sau fiecare dintre acestea a fost respinsă cu o justificare tehnică ce face referire la documentul relevant din `docs/security/` +- [ ] Rutele care generează procese copil (`/api/mcp/`, `/api/cli-tools/runtime/`) sunt clasificate drept `isLocalOnlyPath()` în `src/server/authz/routeGuard.ts` — consultați [Regula strictă nr. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Mesajele de commit nu conțin trailere AI/bot `Co-authored-by` (Regula strictă nr. 16) — colaboratorii umani a căror muncă este reutilizată sunt creditați prin trailere standard `Co-authored-by: Name ` --- diff --git a/docs/i18n/ro/docs/architecture/QUALITY_GATES.md b/docs/i18n/ro/docs/architecture/QUALITY_GATES.md index 9834a73ae903..6878f1625929 100644 --- a/docs/i18n/ro/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ro/docs/architecture/QUALITY_GATES.md @@ -15,225 +15,214 @@ pentru reproducerea aceluiași sistem, consultați --- -## Inventarul porților și profilurile de execuție +## Inventarul gate-urilor și profilurile de execuție ### Admiterea candidaților -Fluxurile de lucru CI și Quality Gates emit fiecare un verdict stabil: `Gate / CI` și +Fluxurile de lucru CI și Quality Gates emit fiecare câte un verdict stabil: `Gate / CI` și `Gate / Quality`. Politica lor de admitere versionată enumeră fiecare job din amonte -ca fiind obligatoriu sau consultativ. Un job obligatoriu aplicabil trebuie să reușească: -rezultatele lipsă, anulate, omise, în așteptare și necunoscute nu pot stabili un rezultat -PASS. O clasificare validă exclusiv pentru documentație sau exclusiv pentru catalog poate -face ca o pistă de cod să nu fie aplicabilă; un PR în stadiu de ciornă nu este un candidat -acceptat. O etichetă `hotfix` nu elimină cerința privind dovezile. - -Ambele fluxuri de lucru acoperă PR-urile și operațiunile push către ramurile main/release, -declanșarea manuală și evenimentele merge-group. Operațiunile push, declanșările și -grupurile de îmbinare execută selecția completă. Fork-urile și grupurile de îmbinare -utilizează runner-e găzduite pentru joburile care, în alte condiții, selectează runner-e -auto-găzduite; înainte de lansare trebuie verificată existența unei capacități găzduite -suficiente. - -Fiecare dovadă JSON identifică SHA-ul checkout-ului, execuția fluxului de lucru și -încercarea. CLI-ul respinge o neconcordanță între SHA-ul checkout-ului și cel al -evenimentului. Testele fluxului de lucru leagă apartenența la politică de lista `needs` -a jobului de verdict, astfel încât o pistă nouă sau eliminată să nu poată dispărea fără -a fi observată. Dovezile acoperă propriul flux de lucru, nu publicarea, implementarea -sau mecanismele interne ale unui scaner consultativ existent. Activarea ambelor nume -de verificare în regulile ramurii reprezintă o modificare administrativă separată; -adăugarea acestor joburi nu protejează în sine o ramură. +drept obligatoriu sau consultativ. Un job obligatoriu aplicabil trebuie să reușească: rezultatele +lipsă, anulate, omise, în așteptare și necunoscute nu pot stabili PASS. O clasificare validă +doar pentru documentație sau doar pentru catalog poate face ca o pistă de cod să fie neaplicabilă; +un PR în stare draft nu este un candidat acceptat. O etichetă `hotfix` nu elimină obligația dovezilor. + +Ambele fluxuri de lucru acoperă PR-urile și push-urile către ramurile main/release, declanșarea manuală și +evenimentele merge-group. Push-urile, declanșările și evenimentele merge-group rulează selecția completă. Fork-urile +și grupurile de îmbinare folosesc runneri găzduiți pentru joburile care altfel ar selecta runneri auto-găzduiți; +înainte de lansare trebuie verificată existența unei capacități găzduite suficiente. + +Fiecare chitanță JSON identifică SHA-ul checkout-ului, rularea fluxului de lucru și încercarea. +CLI-ul respinge o neconcordanță între SHA-ul checkout-ului și cel al evenimentului. Testele fluxului de lucru leagă apartenența la politică +de lista `needs` a jobului de verdict, astfel încât o pistă nouă sau eliminată să nu poată dispărea neobservată. +Chitanțele acoperă propriul flux de lucru, nu publicarea, implementarea sau mecanismele interne +ale unui scaner consultativ existent. Activarea ambelor nume de verificare în regulile ramurii reprezintă o +modificare administrativă separată; adăugarea acestor joburi nu protejează în sine o ramură. ### Inventarul scanării statice -Inventarul versionat de aliasuri npm și apartenența la scanarea statică se află în +Inventarul versionat al aliasurilor npm și apartenența la scanarea statică se află în `config/quality/gate-manifest.json`. Rulați `npm run check:gate-manifest` pentru a valida -numele scripturilor și comenzile exacte în raport cu `package.json`; adăugările, -eliminările și modificările comenzilor duc la eșuarea atât a hook-ului local, cât și a -joburilor de clasificare a modificărilor din CI. Un alias nu este un job al fluxului de -lucru, o instanță a matricei sau un caz de testare: aceste numărători nu trebuie +numele scripturilor și comenzile exacte în raport cu `package.json`; adăugările, eliminările și +abaterile comenzilor duc la eșecul atât al hook-ului local, cât și al joburilor de clasificare a modificărilor din CI. +Un alias nu este un job de flux de lucru, o instanță de matrice sau un caz de testare: aceste numărători nu trebuie prezentate ca fiind interschimbabile. Utilizați `npm run quality:scan -- --list` sau `npm run quality:scan:fast -- --list` -pentru a inspecta aliasurile selectate fără a le executa. Runner-ul invocă punctul de -intrare npm, astfel încât mediul său de execuție (inclusiv Bun, acolo unde este -configurat) este păstrat. Manifestul înregistrează aliasurile din afara acestor profiluri -ca fiind invocate separat, iar comenzile de mentenanță sunt interzise în profilurile de -scanare doar în citire. - -Aceste profiluri acoperă numai scanarea statică. Ele nu certifică testele produsului, -acoperirea, împachetarea, verificările externe sau acceptarea completă a unui candidat -pentru lansare. Admiterea în fluxul de lucru utilizează fișierele asociate -`config/quality/admission-policy.json` și `scripts/quality/admission-verdict.mjs`. -Profilurile release-observer rămân separate; inspectați independent verificările -aplicabile și dovezile acestora. Inventarul descriptiv de mai jos este o referință, -nu o dovadă că o poartă a fost executată efectiv. - -Scripturile se află în `scripts/check/` (porți pentru politici) și `scripts/quality/` -(motor de limitare progresivă). Sursa de adevăr pentru CI este `.github/workflows/ci.yml`. +pentru a inspecta aliasurile selectate fără a le executa. Runnerul invocă punctul de intrare +npm, astfel încât runtime-ul acestuia (inclusiv Bun, acolo unde este configurat) este păstrat. +Manifestul înregistrează aliasurile din afara acestor profiluri ca fiind invocate separat, iar +comenzile de întreținere sunt interzise în profilurile de scanare doar în citire. + +Aceste profiluri acoperă doar scanarea statică. Ele nu certifică testele produsului, +acoperirea, împachetarea, verificările externe sau acceptarea completă pentru lansare a unui candidat. +Admiterea fluxului de lucru utilizează fișierele asociate `config/quality/admission-policy.json` și +`scripts/quality/admission-verdict.mjs`. Profilurile release-observer rămân separate; +inspectați independent verificările și chitanțele aplicabile acestora. Inventarul +descriptiv de mai jos este o referință, nu o dovadă că un gate a fost executat efectiv. + +Scripturile se află în `scripts/check/` (gate-uri de politică) și `scripts/quality/` (motorul ratchet). +Sursa de adevăr pentru CI este `.github/workflows/ci.yml`. ### Calea rapidă pentru PR-uri de lansare (`quality.yml`) -`.github/workflows/quality.yml` completează CI pentru PR-urile către main/release, -operațiunile push către ramurile protejate, declanșările și grupurile de îmbinare. -PR-urile utilizează verificări rapide filtrate în funcție de căi. Compilarea duplicată -dezactivată permanent a fost eliminată; verificările reale de compilare, împachetare și -pornire rămân în CI. - -| Job | Domeniu de aplicare | Blocant | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `Docs Gates (fast-path)` | PR-uri de documentație/cod; referințe pentru documentația API și întreaga documentație | Da | -| `Fast Quality Gates` | PR-uri de cod; verificări statice, verificarea tipurilor, verificarea tipurilor pentru dashboard, teste unitare afectate | Da | -| `Forgotten sibling tests` | PR-uri de cod; modulele modificate sunt urmărite până la consumatorii statici și testele candidate înrudite; căile prin barrel-uri și importuri dinamice sunt raportate ca diagnostice consultative, cu excepțiile aferente din lista de permisiuni | **Consultativ** | -| `Vitest (fast-path)` | PR-uri de cod; suita rapidă vitest | Da | -| `Unit Tests fast-path` | PR-uri de cod; suită de teste unitare împărțită în 4 shard-uri | Da | -| `No new ESLint warnings` | PR-uri de cod; protecție lint care ține cont de suprimări | Da, inclusiv fork-uri | -| `Merge integrity (changelog + generated skills)` | PR-uri care nu sunt ciorne; sincronizarea jurnalului de modificări și a skill-urilor generate | Da, inclusiv fork-uri | - -#### Raport privind testele înrudite uitate - -`npm run check:forgotten-sibling-tests` reutilizează resolver-ul de importuri din spatele hărții -impactului asupra testelor. Pentru fiecare modul de producție modificat, acesta raportează -lanțuri deterministe `modul/simbol modificat -> consumator static -> test candidat înrudit` -atunci când testul candidat lipsește din diff-ul pull request-ului. Rezumatul Markdown și -rezultatul JSON sunt păstrate drept artefactul de flux de lucru `forgotten-sibling-tests` -pentru calibrare înainte de orice lansare cu caracter blocant. - -Re-exporturile de tip barrel și importurile dinamice sunt doar diagnostice de rezoluție; acestea nu creează niciodată o +`.github/workflows/quality.yml` completează CI pentru PR-urile către main/release, push-urile către ramuri +protejate, declanșările și grupurile de îmbinare. PR-urile utilizează verificări rapide filtrate după căi. Build-ul +duplicat dezactivat permanent a fost eliminat; verificările reale de build/împachetare/pornire rămân în CI. + +| Job | Domeniu de aplicare | Blocant | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | +| `Docs Gates (fast-path)` | PR-uri de documentație/cod; referințe pentru documentația API și întreaga documentație | Da | +| `Fast Quality Gates` | PR-uri de cod; verificări statice, verificarea tipurilor, verificarea tipurilor pentru dashboard, teste unitare afectate | Da | +| `Forgotten sibling tests` | PR-uri de cod; modulele modificate sunt urmărite până la consumatorii statici și testele sibling candidate; căile prin barrel și importurile dinamice sunt raportate ca diagnostice consultative, cu excepțiile aferente din lista de permisiuni | **Consultativ** | +| `Vitest (fast-path)` | PR-uri de cod; suita rapidă vitest | Da | +| `Unit Tests fast-path` | PR-uri de cod; suită de teste unitare în 4 shard-uri | Da | +| `No new ESLint warnings` | PR-uri de cod; protecție lint care ține cont de suprimări | Da, inclusiv fork-uri | +| `Merge integrity (changelog + generated skills)` | PR-uri care nu sunt draft; sincronizarea jurnalului de modificări și a skill-urilor generate | Da, inclusiv fork-uri | + +#### Raportul privind testele sibling omise + +`npm run check:forgotten-sibling-tests` reutilizează resolverul de importuri folosit de harta impactului testelor. +Pentru fiecare modul de producție modificat, acesta raportează lanțuri deterministe +`modul/simbol modificat -> consumator static -> test sibling candidat` atunci când testul +candidat lipsește din diff-ul pull request-ului. Rezumatul Markdown și rezultatul JSON sunt păstrate ca +artefact al fluxului de lucru `forgotten-sibling-tests`, pentru calibrare înainte de orice lansare cu caracter blocant. + +Reexporturile de tip barrel și importurile dinamice sunt doar diagnostice de rezoluție; acestea nu creează niciodată o constatare blocantă. Excepțiile revizuite se află în -`config/quality/forgotten-sibling-allowlist.json`. Fiecare intrare trebuie să specifice testul consumator și testul candidat, -să ofere o justificare concretă și să includă un link către un issue sau pull request GitHub. Intrările incorecte determină -respingerea. Excepțiile nu pot suprima un test candidat șters sau un diff care adaugă `.skip`/`.todo`; -slăbirea aserțiunilor și alte forme de mascare rămân în responsabilitatea verificării blocante independente +`config/quality/forgotten-sibling-allowlist.json`. Fiecare intrare trebuie să specifice consumatorul și testul +candidat, să ofere o justificare specifică și să includă un link către un issue sau un pull request GitHub. Intrările incorecte +eșuează în mod închis. Excepțiile nu pot suprima un test candidat șters sau un diff care adaugă `.skip`/`.todo`; +slăbirea aserțiunilor și alte forme de mascare rămân în responsabilitatea porții blocante independente `check:test-masking`. ### Job: `lint` -Rulează pentru fiecare PR către `main`. Blochează îmbinarea în caz de eșec. - -| Script (`npm run ...`) | Validează | Blocant | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | Versiunea Node.js se află în intervalul acceptat | Da | -| `check:cycles` | Importuri circulare — toate modulele din `src/` + `open-sse/` | Da | -| `check:route-validation:t06` | Scheme Zod prezente pe toate rutele (politica Nivelului 6) | Da | -| `check:any-budget:t11` | Numărul de `@ts-expect-error // any` nu depășește bugetul (catraca Nivelului 11) | Da | -| `check:provider-consistency` | Fiecare furnizor din `providers.ts` are o intrare corespunzătoare în `providerRegistry.ts` (și invers, în cadrul listei de permisiuni) | Da | -| `check:model-lifecycle` | Cele trei tabele de rutare întreținute manual rămân consecvente cu instantaneul ciclului de viață inclus în depozit (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nu acordă niciun scor vreunui id retras pe care `REGISTRY` îl poate ruta; fiecare țintă din `BUILT_IN_ALIASES` este prezentă în `REGISTRY` și absentă din instantaneul id-urilor retrase; fiecare id retras încă prezent în `REGISTRY` este redirecționat sau enumerat în `allowedRetiredInCatalog`; și nicio sursă sau țintă din `DEFAULT_DEGRADATION_MAP` nu apare ca retrasă în acel instantaneu. Aceasta nu demonstrează că un model este deservit în prezent de un serviciu upstream activ. Offline — compară cu `config/quality/model-lifecycle.json`, reîmprospătat manual cu `npm run quality:refresh-model-lifecycle` (necesită rețea; nu este integrat în CI). `allowedRetiredInCatalog` este un mecanism incremental de reducere: adăugați o intrare numai împreună cu o problemă de urmărire. | Da | -| `check:fetch-targets` | Fiecare `fetch("/api/...")` din `src/` de partea clientului se rezolvă la un fișier `route.ts` real | Da | -| `check:deps` | Toate dependențele care pot fi instalate cu `npm install` din fiecare `package.json` din depozit se află în `dependency-allowlist.json`; pachetele noi fără versiune fixată sau care pot reprezenta tentative de slopsquatting sunt semnalate | Da | -| `audit:deps` | `npm audit` (rădăcină + electron) — fără avertismente cu severitate ridicată/critică (se suprapune cu `check:vuln-ratchet` din osv; consultați Registrul restant de raționalizare) | Da | -| `check:lockfile` | Integritatea `package-lock.json` — registru https, hash-uri de integritate, fără suprascrieri ale gazdei | Da | -| `check:licenses` | Lista de permisiuni pentru licențe SPDX aferentă dependențelor de producție | Da | -| `check:tracked-artifacts` | Fără artefacte de build / linkuri simbolice `node_modules` incluse în commituri (rulează și în etapa husky pre-commit; pre-push este intenționat minimal — #6716) | Da | -| `check:ai-attribution` | Fără trailer `Co-Authored-By` atribuit AI-ului/unui bot sau subsol privind generarea cu AI în commiturile, titlul sau corpul PR-ului — Regula strictă #16 (în bucla de verificări rapide `quality.yml` pentru PR→`release/**` — citește payloadul evenimentului, nu efectuează nicio operație în afara PR-urilor — și într-un pas exclusiv pentru PR-uri în verificarea lint din `ci.yml` pentru PR→`main`; de asemenea, în hook-ul husky `commit-msg`; coautorii umani sunt permiși; #14436) | -| `check:vitest-exclusions` | Fiecare excludere Vitest menționează un issue de urmărire și apare în `config/quality/vitest-exclusions.json` (#13204) | Da | -| `check:file-size` | Niciun fișier sursă nu depășește limita specifică extensiei (ratchet: fișierele mari înghețate se află în lista `frozen`) | Da | -| `check:error-helper` | Răspunsurile de eroare din executori/handlere utilizează `buildErrorBody()` / `sanitizeErrorMessage()` (Regula strictă #12) | Da | -| `check:migration-numbering` | Fișierele SQL de migrare sunt numerotate secvențial, fără goluri sau duplicate | Da | -| `check:public-creds` | Nu există valori OAuth literale pentru `client_id`/`client_secret` sau chei Firebase Web în afara `publicCreds.ts` (Regula strictă nr. 11) | Da | -| `check:db-rules` | Nu există SQL brut în afara modulelor `src/lib/db/`; nu există importuri de tip barrel din `localDb.ts` (Regulile stricte nr. 2/nr. 5) | Da | -| `check:known-symbols` | Executorii furnizorilor, strategiile de rutare și translatoarele înregistrate în tabelele lor de dispecerizare corespund fișierelor de pe disc — fără simboluri orfane sau nedeclarate | Da | -| `check:route-guard-membership` | Fiecare rută care lansează un proces copil este clasificată de `isLocalOnlyPath()` (Regulile stricte nr. 15/nr. 17) | Da | -| `check:test-discovery` | Fiecare fișier `*.test.ts` / `*.spec.ts` din depozit este colectat de cel puțin un executor de teste (ratchet: lista fișierelor orfane din `test-discovery-baseline.json` poate doar să se micșoreze) | Da | -| `check:agent-skills-sync` | Artefactele generate pentru agent-skills corespund catalogului sursă (fără divergențe) | -| `check:provider-asset-provenance` | Siglele/resursele furnizorilor au o înregistrare de proveniență | -| `lint:json` | Fișierele de configurare JSON sunt analizate cu succes și respectă regulile de lint ale depozitului | -| `typecheck:core` | Compilare TypeScript fără erori (doar avertismente consultative) | Da | -| `typecheck:noimplicit:core` | Verificare strictă `noImplicitAny` — orientată spre viitor; multe locuri de apel preexistente necesită încă adnotări | **Consultativ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` limitat la `src/app/(dashboard)/**` (#7033) — lista atent selectată de 27 de fișiere a `typecheck:core` nu include niciun fișier TSX din dashboard, iar `next build` nu efectuează niciodată verificarea tipurilor pentru acestea (`next.config.mjs` setează `ignoreBuildErrors: true`), astfel încât regresiile privind identificatorii orfani de acolo (#6625/#6909) erau invizibile pentru CI. Compară cu o bază de referință înghețată a numărului de erori pentru fiecare fișier/cod TS (`config/quality/dashboard-typecheck-baseline.json`, același model de impunere pentru intrările învechite ca la `check:known-symbols`) — numai erorile NOI care depășesc numărul din baza de referință fac ca verificarea să eșueze; reduceți treptat baza cu `--update` atunci când este remediată o eroare preexistentă. | Da | - -### Job: `quality-gate` +Rulează la fiecare PR către `main`. Blochează îmbinarea în caz de eșec. + +| Script (`npm run ...`) | Validează | Blocant | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | Versiunea Node.js se află în intervalul acceptat | Da | +| `check:cycles` | Importurile circulare din întregul `src/` + `open-sse/` (bazat pe AST, cu `paths` din tsconfig rezolvate). Rularea simplă = informativă, listează ciclurile. `check:cycles:ratchet` (ceea ce rulează CI) blochează atunci când numărul depășește pragul `metrics.cycles` din `quality-baseline.json` — în prezent 14, `direction: down`, deci poate doar să scadă (#15159 G-01/G-02) | Da (catracă) | +| `check:route-validation:t06` | Scheme Zod prezente pe toate rutele (politica Nivelului 6) | Da | +| `check:any-budget:t11` | Numărul de `@ts-expect-error // any` nu depășește bugetul (catracă de Nivel 11) | Da | +| `check:provider-consistency` | Fiecare furnizor din `providers.ts` are o intrare corespunzătoare în `providerRegistry.ts` (și invers, în limitele listei de permisiuni) | Da | +| `check:model-lifecycle` | Cele trei tabele de rutare întreținute manual rămân consecvente cu instantaneul ciclului de viață inclus în depozit (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nu acordă un scor niciunui id retras pe care `REGISTRY` îl poate ruta; fiecare destinație `BUILT_IN_ALIASES` este prezentă în `REGISTRY` și absentă din instantaneul id-urilor retrase; fiecare id retras încă prezent în `REGISTRY` este redirecționat sau enumerat în `allowedRetiredInCatalog`; și nicio sursă sau destinație din `DEFAULT_DEGRADATION_MAP` nu apare ca fiind retrasă în instantaneul respectiv. Acest lucru nu dovedește că un model este furnizat în prezent de un serviciu upstream activ. Offline — compară cu `config/quality/model-lifecycle.json`, actualizat manual prin `npm run quality:refresh-model-lifecycle` (necesită rețea; nu este integrat în CI). `allowedRetiredInCatalog` este un mecanism cu limitare progresivă: adăugați o intrare numai împreună cu o problemă de urmărire. | Da | +| `check:fetch-targets` | Fiecare `fetch("/api/...")` din `src/` pe partea de client se rezolvă către un `route.ts` real | Da | +| `check:deps` | Toate dependențele instalabile prin `npm install` din fiecare `package.json` al depozitului se află în `dependency-allowlist.json`; pachetele noi fără versiune fixată sau care imită numele altor pachete sunt semnalate | Da | +| `audit:deps` | `npm audit` (rădăcină + electron) — fără avertismente de severitate ridicată/critică (se suprapune cu `check:vuln-ratchet` din osv; consultați restanțele de raționalizare) | Da | +| `check:lockfile` | Integritatea fișierului `package-lock.json` — registru https, hash-uri de integritate, fără înlocuiri ale gazdei | Da | +| `check:licenses` | Listă de permisiuni pentru licențele SPDX ale dependențelor de producție | Da | +| `check:tracked-artifacts` | Fără artefacte de compilare / linkuri simbolice `node_modules` incluse în commituri (rulează și în pre-commit-ul husky; pre-push este intenționat simplificat — #6716) | Da | +| `check:ai-attribution` | Fără trailer AI/bot `Co-Authored-By` sau subsol privind generarea prin AI în commiturile, titlul sau corpul PR-ului — Regula strictă #16 (în bucla de verificări rapide din `quality.yml` pentru PR→`release/**` — citește conținutul evenimentului, nu efectuează nicio operație în afara PR-urilor — și ca pas exclusiv pentru PR-uri în verificarea lint din `ci.yml` pentru PR→`main`; de asemenea, hook-ul husky `commit-msg`; coautorii umani sunt permiși; #14436) | +| `check:vitest-exclusions` | Fiecare excludere Vitest indică o problemă de urmărire și apare în `config/quality/vitest-exclusions.json` (#13204) | Da | +| `check:file-size` | Niciun fișier sursă nu depășește limita specifică extensiei (mecanism cu prag progresiv: fișierele mari blocate sunt în lista `frozen`) | Da | +| `check:error-helper` | Răspunsurile de eroare din executori/gestionare folosesc `buildErrorBody()` / `sanitizeErrorMessage()` (Regula strictă #12) | Da | +| `check:migration-numbering` | Fișierele SQL de migrare sunt numerotate secvențial, fără întreruperi sau duplicate | Da | +| `check:public-creds` | Nu există valori literale OAuth `client_id`/`client_secret` sau chei Firebase Web în afara `publicCreds.ts` (Regula strictă nr. 11) | Da | +| `check:db-rules` | Nu există SQL brut în afara modulelor `src/lib/db/`; nu există importuri de tip barrel din `localDb.ts` (Regulile stricte nr. 2/nr. 5) | Da | +| `check:known-symbols` | Executorii furnizorilor, strategiile de rutare și translatoarele înregistrate în tabelele lor de expediere corespund fișierelor de pe disc — fără simboluri orfane sau nedeclarate | Da | +| `check:route-guard-membership` | Fiecare rută care generează un proces copil este clasificată de `isLocalOnlyPath()` (Regulile stricte nr. 15/nr. 17) | Da | +| `check:test-discovery` | Fiecare fișier `*.test.ts` / `*.spec.ts` din depozit este colectat de cel puțin un executor de teste (ratchet: lista de fișiere orfane din `test-discovery-baseline.json` poate doar să se restrângă) | Da | +| `check:agent-skills-sync` | Artefactele agent-skills generate corespund catalogului lor sursă (fără divergențe) | +| `check:provider-asset-provenance` | Siglele/activele furnizorilor au o înregistrare de proveniență | +| `lint:json` | Fișierele de configurare JSON sunt analizate cu succes și respectă regulile de linting ale depozitului | +| `typecheck:core` | Compilare TypeScript fără erori (doar avertismente consultative) | Da | +| `typecheck:noimplicit:core` | `noImplicitAny` strict — orientat spre viitor; multe locuri de apelare preexistente necesită încă adnotări | **Consultativ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` limitat la `src/app/(dashboard)/**` (#7033) — lista atent selectată de 27 de fișiere a `typecheck:core` nu include niciun fișier TSX al tabloului de bord, iar `next build` nu efectuează niciodată verificarea tipurilor pentru acesta (`next.config.mjs` setează `ignoreBuildErrors: true`), astfel încât regresiile privind identificatorii orfani de acolo (#6625/#6909) erau invizibile pentru CI. Compară cu o bază de referință înghețată a numărului de erori per fișier/per cod TS (`config/quality/dashboard-typecheck-baseline.json`, același model de aplicare pentru elemente învechite ca `check:known-symbols`) — doar erorile NOI care depășesc numărul din baza de referință determină eșecul verificării; reduceți treptat baza cu `--update` atunci când este remediată o eroare preexistentă. | Da | + +### Sarcină: `quality-gate` Rulează după `test-coverage`. Blochează îmbinarea în caz de eșec. -| Script | Validează | Blocant | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | -| `quality:collect` | Generează `quality-metrics.json` (numărul de avertismente ESLint, acoperirea din raportul combinat al fragmentelor) | Da (în amonte față de ratchet) | -| `quality:ratchet` | Nicio metrică din `quality-baseline.json` nu a regresat (avertismente ESLint ≤ valoarea de referință; acoperire ≥ valoarea de referință) | Da | -| `check:duplication` | Duplicarea codului (jscpd@4) nu depășește valoarea de referință din `quality-baseline.json` | Da | -| `check:complexity` | Complexitatea ciclomatică la nivel de fișier nu depășește limita (regulile ESLint de bază `complexity` + `max-lines-per-function`) | Da | -| `check:cognitive-complexity` | Pragul progresiv pentru complexitatea cognitivă (`eslint-plugin-sonarjs`) — rulare ESLint separată; CI le rulează pe ambele, combinate în pasul unic `check:complexity-ratchets` | Da | -| `check:dead-code` | Pragul progresiv pentru exporturile/fișierele neutilizate (knip) nu regresează față de valoarea de referință | Da | -| `check:compression-budget` | Bugetul pentru testul de performanță al compresiei — pragurile minime de economisire a tokenurilor pentru fiecare motor nu trebuie să regreseze | Da | -| `check:type-coverage` | Pragul progresiv al procentului de cod tipizat (`type-coverage`) nu regresează; în mare parte înlocuiește `typecheck:noimplicit:core` | Da | -| `check:codeql-ratchet` | Numărul alertelor CodeQL deschise nu regresează (citit prin `gh api`; omitere controlată fără token) — pentru frecvența actualizării și declanșarea manuală: consultați „Pragul progresiv CodeQL” de mai jos | Da | +| Script | Validează | Blocant | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | +| `quality:collect` | Generează `quality-metrics.json` (numărul de avertismente ESLint, acoperirea din raportul combinat al shard-urilor) | Da (în amonte față de mecanismul ratchet) | +| `quality:ratchet` | Fiecare metrică din `quality-baseline.json` nu a regresat (avertismente ESLint ≤ valoarea de referință; acoperire ≥ valoarea de referință) | Da | +| `check:duplication` | Duplicarea codului (jscpd@4) nu depășește valoarea de referință din `quality-baseline.json` | Da | +| `check:complexity` | Complexitatea ciclomatică la nivel de fișier nu depășește limita (regulile ESLint de bază `complexity` + `max-lines-per-function`) | Da | +| `check:cognitive-complexity` | Mecanism ratchet pentru complexitatea cognitivă (`eslint-plugin-sonarjs`) — rulare ESLint separată; CI le execută pe ambele, reunite într-un singur pas `check:complexity-ratchets` | Da | +| `check:dead-code` | Mecanismul ratchet pentru exporturile/fișierele neutilizate (knip) nu regresează față de valoarea de referință | Da | +| `check:compression-budget` | Bugetul benchmarkului de compresie — pragurile minime ale economiilor de tokenuri pentru fiecare motor nu trebuie să regreseze | Da | +| `check:type-coverage` | Mecanismul ratchet pentru procentul de cod tipizat (`type-coverage`) nu regresează; în mare parte, îl înlocuiește pe `typecheck:noimplicit:core` | Da | +| `check:codeql-ratchet` | Numărul alertelor CodeQL deschise nu regresează (citire prin `gh api`; omitere controlată fără token) — pentru frecvența actualizării și declanșarea manuală: consultați „CodeQL ratchet” mai jos | Da | ### Job: `quality-extended` -Întregul job are caracter consultativ (`continue-on-error: true`). Pragurile progresive bazate pe npm rulează -efectiv; scanerele externe se instalează prin `gh release download` și se omit automat (cod de ieșire 0) -atunci când un binar este încă absent. +Întregul job are caracter consultativ (`continue-on-error: true`). Mecanismele ratchet bazate pe npm rulează +efectiv; scanerele externe se instalează prin `gh release download` și se omit automat (exit 0) +atunci când un binar continuă să lipsească. -| Script | Validează | Caracter blocant | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -| `check:circular-deps` | Nu există dependențe circulare (dpdm) | **Consultativ** | -| `check:bundle-size` | Dimensiunea pachetului nu depășește limita | **Consultativ** | -| `check:secrets` | Scanarea secretelor (gitleaks) — este omisă dacă binarul lipsește | **Consultativ** | -| `check:vuln-ratchet` | Vulnerabilitățile dependențelor (osv-scanner) nu regresează — verificarea este omisă dacă binarul lipsește | **Consultativ** | -| `check:workflows` | Verificarea lint a fluxurilor de lucru (actionlint + zizmor); scanerele lipsă/defecte, rapoartele nevalide sau absența valorii de referință pentru pragul progresiv produc starea INCOMPLET. Constatările valide urmează politica strictă/consultativă/cu prag progresiv selectată | Execuție obligatorie; pragul progresiv zizmor este blocant în CI | -| `check:openapi-breaking` | Modificări incompatibile ale contractului API public (`openapi.yaml`) față de ramura de bază (oasdiff) — generează `openapiBreaking=N`; verificarea este omisă dacă oasdiff lipsește sau specificația de bază nu poate fi rezolvată | **Consultativ** | +| Script | Validează | Blocant | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| `check:circular-deps` | Nu există dependențe circulare (dpdm) | **Consultativ** | +| `check:bundle-size` | Dimensiunea bundle-ului nu depășește limita | **Consultativ** | +| `check:secrets` | Scanarea secretelor (gitleaks) — se omite dacă binarul lipsește | **Consultativ** | +| `check:vuln-ratchet` | Vulnerabilitățile dependențelor (osv-scanner) nu regresează — se omite dacă binarul lipsește | **Consultativ** | +| `check:workflows` | Lint pentru workflow-uri (actionlint + zizmor); scanerele lipsă/defecte, rapoartele nevalide sau absența valorii de referință pentru mecanismul ratchet eșuează cu INCOMPLETE. Constatările valide urmează politica strictă/consultativă/ratchet selectată | Execuție obligatorie; mecanismul ratchet zizmor este blocant în CI | +| `check:openapi-breaking` | Modificări incompatibile ale contractului API public (`openapi.yaml`) față de ramura de bază (oasdiff) — generează `openapiBreaking=N`; se omite dacă oasdiff lipsește sau specificația de bază nu poate fi rezolvată | **Consultativ** | ### Job: `docs-sync-strict` Rulează pentru fiecare PR către `main`. Blochează îmbinarea în caz de eșec. -| Script | Validează | Blocant | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `check:docs-all` | Metapoartă care rulează secvențial cele 6 subporți de mai jos | Da | -| ↳ `check:docs-sync` | Consecvența versiunilor din CHANGELOG / OpenAPI / llm.txt | Da | -| ↳ `check:docs-counts` | Valorile numerice din text (numărul de furnizori, numărul de migrări etc.) se încadrează în intervalul permis față de valorile reale | Da | -| ↳ `check:env-doc-sync` | Fiecare variabilă de mediu din `.env.example` este documentată într-un tabel din documentație și viceversa | Da | -| ↳ `check:deprecated-versions` | Nu există șiruri de versiune depreciate în documentație | Da | -| ↳ `check:doc-links` | Linkurile markdown interne din documentație trimit la fișiere reale (formatul `[text]`/`(path)`) | Da | -| ↳ `check:fabricated-docs` | Rutele, variabilele de mediu, comenzile CLI, numele hook-urilor și căile de fișiere menționate în documentație există în baza de cod. Poartă strictă prin `--strict`; eșec neblocant fără acest indicator. | Da (prin `--strict` în CI) | -| `check:cli-i18n` | Șirurile comenzilor CLI sunt prezente în toate fișierele de localizare i18n | Da | -| `check:openapi-coverage` | Specificația OpenAPI acoperă cel puțin un prag ajustat progresiv al rutelor reale | Da | -| `check:openapi-security-tiers` | Adnotările nivelurilor de securitate din `openapi.yaml` sunt consecvente cu clasificările din `routeGuard.ts` | **Consultativ** | -| `check:openapi-routes` | Fiecare cale din `openapi.yaml` corespunde unui fișier `route.ts` real (prevenirea halucinațiilor) | Da | -| `check:docs-symbols` | Fiecare referință `/api/...` din `docs/**/*.md` corespunde unui fișier `route.ts` real (prevenirea halucinațiilor) | Da | -| `i18n translation drift` | Chei netraduse în fișierele de localizare i18n — doar avertisment | **Consultativ** | +| Script | Validează | Blocant | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | Meta-poartă care rulează secvențial cele 6 sub-porți de mai jos | Da | +| ↳ `check:docs-sync` | Consecvența versiunilor din CHANGELOG / OpenAPI / llm.txt | Da | +| ↳ `check:docs-counts` | Valorile numerice din text (numărul de furnizori, numărul de migrări etc.) se încadrează în intervalul de ajustare progresivă al valorilor reale | Da | +| ↳ `check:env-doc-sync` | Fiecare variabilă de mediu din `.env.example` este documentată într-un tabel din documentație și invers | Da | +| ↳ `check:deprecated-versions` | Nu există șiruri de versiune perimate în documentație | Da | +| ↳ `check:doc-links` | Linkurile markdown interne din documentație indică fișiere reale (forma `[text]`/`(path)`) | Da | +| ↳ `check:fabricated-docs` | Rutele, variabilele de mediu, comenzile CLI, numele hook-urilor și căile de fișiere menționate în documentație există în baza de cod. Poartă strictă prin `--strict`; eșec neblocant fără opțiune. | Da (prin `--strict` în CI) | +| `check:cli-i18n` | Șirurile comenzilor CLI sunt prezente în toate fișierele de localizare i18n | Da | +| `check:openapi-coverage` | Specificația OpenAPI acoperă cel puțin un prag ajustat progresiv al rutelor reale | Da | +| `check:openapi-security-tiers` | Adnotările nivelurilor de securitate din `openapi.yaml` sunt consecvente cu clasificările din `routeGuard.ts` | **Consultativ** | +| `check:openapi-routes` | Fiecare cale din `openapi.yaml` corespunde unui fișier `route.ts` real (anti-halucinație) | Da | +| `check:docs-symbols` | Fiecare referință `/api/...` din `docs/**/*.md` corespunde unui fișier `route.ts` real (anti-halucinație) | Da | +| `i18n translation drift` | Chei netraduse în fișierele de localizare i18n — doar avertizare | **Consultativ** | ### Job: `i18n-ui-coverage` -| Script | Validează | Blocant | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (inline) | Acoperirea cheilor i18n din interfața cu utilizatorul este ≥ 65% | Da | -| `check-ui-value-drift` (inline) | O **valoare** în limba engleză care a fost rescrisă nu lasă în urmă nicio traducere învechită | Da | -| `check-new-key-coverage` (inline) | O cheie **nouă** în limba engleză este tradusă în fiecare localizare — un marcaj `__MISSING__:` este respins | Da | -| `check-translation-ratio` | Raportul traducerilor reale pentru fiecare localizare (valori identice cu cele în engleză / substituenți / elemente lipsă din afara listei de excluderi) nu trebuie să depășească `config/quality/i18n-translation-baseline.json` + marja permisă | **Consultativ** | +| Script | Validează | Blocant | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (inline) | Acoperirea cheilor i18n din interfața cu utilizatorul este ≥ 65% | Da | +| `check-ui-value-drift` (inline) | O **valoare** în engleză rescrisă nu lasă în urmă nicio traducere învechită | Da | +| `check-new-key-coverage` (inline) | O cheie **nouă** în engleză este tradusă în fiecare localizare — un marcaj `__MISSING__:` este respins | Da | +| `check-translation-ratio` | Raportul traducerilor reale pentru fiecare localizare (valori identice cu engleza / substituenți / elemente lipsă din afara listei de permisiuni) nu trebuie să depășească `config/quality/i18n-translation-baseline.json` + marja | **Consultativ** | -Necesită `fetch-depth: 0` — poarta pentru detectarea valorilor învechite compară diferențele dintre `en.json` și baza îmbinării. +Necesită `fetch-depth: 0` — poarta pentru detectarea valorilor învechite compară `en.json` cu baza de îmbinare. #### `check-ui-value-drift` — poartă pentru traduceri învechite -Detectează singura regresie i18n pe care celelalte porți nu o pot identifica structural: o valoare în limba engleză -este rescrisă, iar traducerile derivate din textul englezesc _anterior_ rămân neschimbate, astfel încât -utilizatorii care nu folosesc limba engleză continuă să citească un text formulat cu încredere, dar care acum este greșit. +Detectează acea regresie i18n pe care celelalte porți nu o pot identifica structural: o valoare în engleză +este rescrisă, iar traducerile derivate din textul în engleză _anterior_ rămân neschimbate, astfel încât +utilizatorii care nu folosesc limba engleză continuă să citească un text formulat cu încredere, dar acum incorect. -Acest lucru a ajuns efectiv în producție. `oauthModal.googleOAuthWarning` a fost rescris când a fost introdus -asistentul de autentificare Antigravity (#5203); **39 din 43 de localizări** au păstrat textul care le spunea operatorilor să „copieze -adresa URL completă și să o lipească mai jos” — un flux care nu poate fi finalizat pentru furnizorul respectiv. Problema a rămas -neobservată până la #8463 deoarece: +Acest lucru s-a întâmplat efectiv în producție. `oauthModal.googleOAuthWarning` a fost rescris când a fost +introdus utilitarul de autentificare Antigravity (#5203); **39 dintre cele 43 de localizări** au păstrat textul +care le spunea operatorilor să „copieze URL-ul complet și să îl lipească mai jos” — un flux care nu poate fi +finalizat pentru furnizorul respectiv. Problema a trecut neobservată până la #8463 deoarece: -- `sync-ui-keys` completează doar cheile care sunt **absente**, niciodată pe cele care sunt **învechite**; +- `sync-ui-keys` completează doar cheile care sunt **absente**, niciodată pe cele **învechite**; - `check-ui-keys-coverage` numără _prezența_ cheilor, astfel încât o traducere învechită este considerată acoperită; - `check-translation-drift` urmărește copiile documentației din `docs/i18n//**.md` — - nu citește niciodată `src/i18n/messages/*.json`. Este blocant în jobul `docs-sync-strict` începând cu - resincronizarea din 2026-09: editați un document principal → `npm run i18n:run -- --files=` (la nivel de secțiune, cu cost redus). + nu citește niciodată `src/i18n/messages/*.json`. Blocant în jobul `docs-sync-strict` începând cu + resincronizarea din 2026-09: modificați un document de bază → `npm run i18n:run -- --files=` (la nivel de secțiune, cu cost redus). -**Conștient de diff, nu bazat pe un baseline.** Compară `en.json` de la baza îmbinării cu -arborele de lucru; pentru fiecare cheie a cărei valoare în engleză s-a modificat, orice localizare -care încă păstrează o traducere neatinsă este învechită. Aceasta **îngheață intenționat datoria preexistentă** — un diff -nu poate dezvălui din ce versiune veche în engleză provine o traducere existentă de mult timp, astfel încât verificarea evaluează -doar ceea ce este afectat de modificarea curentă. Alternativa (un baseline cu hash pentru fiecare cheie) ar necesita -un fișier generat de ~600 KB, de 3× mai mare decât cel mai mare baseline existent, modificat la fiecare PR i18n. +**Conștient de diff, fără suportul unei baze de referință.** Compară `en.json` de la baza îmbinării cu +arborele de lucru; pentru fiecare cheie a cărei valoare în engleză s-a modificat, orice localizare care încă păstrează o +traducere nemodificată este învechită. Acest lucru **îngheață în mod deliberat datoria preexistentă** — un diff +nu poate dezvălui din ce versiune veche în engleză provine o traducere existentă de mult timp, astfel că verificarea evaluează +doar ceea ce atinge modificarea curentă. Alternativa (o bază de referință cu hash pentru fiecare cheie) ar necesita +un fișier generat de aproximativ 600 KB, de 3 ori mai mare decât cea mai mare bază de referință existentă, care s-ar modifica la fiecare PR de i18n. -Există două modalități de a trece verificarea: +Există două modalități de a îndeplini cerința: 1. actualizați traducerile afectate sau -2. setați-le la `__MISSING__:` — în acest caz, runtime-ul furnizează textul corectat în engleză - (`src/i18n/request.ts::deepMergeFallback`, #7258), iar cheia este adăugată în coada pentru traducere. +2. setați-le la `__MISSING__:` — la rulare va fi furnizat apoi textul corectat în engleză + (`src/i18n/request.ts::deepMergeFallback`, #7258), iar cheia va fi pusă în coada pentru traducere. -Dacă s-a schimbat **sensul** șirului, este preferabil să **redenumiți cheia**: o cheie nouă nu poate moșteni +Dacă **sensul** șirului s-a schimbat, preferați **redenumirea cheii**: o cheie nouă nu poate moșteni o traducere învechită. Acesta este modelul utilizat de #8463. ```bash @@ -242,12 +231,12 @@ npm run i18n:check-value-drift:warn # doar raportare BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Se încheie cu 0 și `SKIP reason=base-unresolved` atunci când catalogul de bază nu poate fi citit (clonă -superficială fără referința de bază), reflectând comportamentul `check-openapi-breaking`. +Se încheie cu codul 0 și `SKIP reason=base-unresolved` atunci când catalogul de bază nu poate fi citit (clonă +superficială fără referința de bază), similar cu `check-openapi-breaking`. ### Job: `i18n` -Matrice completă de validare i18n (câte un job pentru fiecare localizare). Întregul job este consultativ. +Matrice completă de validare i18n (un job pentru fiecare localizare). Întregul job are caracter consultativ. | Script | Validează | Blocant | | ------------------------------- | ---------------------------------------- | --------------------------------------------------------------- | @@ -255,7 +244,7 @@ Matrice completă de validare i18n (câte un job pentru fiecare localizare). În ### Job: `pr-test-policy` -Rulează numai pentru cererile de tip pull. +Rulează numai pentru pull request-uri. | Script | Validează | Blocant | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | @@ -267,23 +256,23 @@ Rulează numai pentru cererile de tip pull. Rulează după `build`. Blochează îmbinarea în caz de eșec. -| Suită | Validează | Blocant | -| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `test:vitest` | Serverul MCP (110 instrumente), autoCombo, cache — runner vitest | Da | -| `test:vitest:ui` | Testele componentelor UI — runner vitest | **Blocant** — eșecurile preexistente sunt excluse explicit în `vitest.config.ts`; eșecurile noi determină eșecul jobului | +| Suită | Validează | Blocant | +| ---------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Serverul MCP (110 instrumente), autoCombo, cache — executor vitest | Da | +| `test:vitest:ui` | Testele componentelor UI — executor vitest | **Blocant** — eșecurile preexistente sunt excluse explicit în `vitest.config.ts`; eșecurile noi provoacă eșecul jobului | ### Fluxuri de lucru nocturne (programate, consultative) -Acestea rulează conform unei programări cron (și prin `workflow_dispatch`), niciodată pentru PR-uri. Toate sunt consultative. +Acestea rulează conform unei programări cron (și prin `workflow_dispatch`), niciodată pentru PR-uri. Toate au caracter consultativ. -| Flux de lucru | Validează | Blocant | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | teste bazate pe proprietăți fast-check, cu seed aleatoriu și un număr mare de rulări | **Consultativ** | -| `nightly-resilience` | verificarea creșterii heap-ului, injectarea haotică a defecțiunilor, teste k6 de încărcare/anduranță | **Consultativ** | -| `nightly-llm-security` | protecție promptfoo împotriva injecțiilor (mod de blocare) + sonde garak (omise în lipsa unui secret pentru furnizor) | **Consultativ** | -| `nightly-schemathesis` | fuzzing al contractului OpenAPI (schemathesis) pe o instanță OmniRoute activă, utilizând `docs/openapi.yaml` — evidențiază încălcări ale specificației / erori 500 negestionate (Faza 8 B.4) | **Consultativ** | -| `nightly-mutation` | scorul testării prin mutații Stryker pentru culoarul rapid de teste unitare — mutanții supraviețuitori evidențiază aserțiuni slabe | **Consultativ** | -| `nightly-compat` | matricea de compatibilitate a motorului Node pentru intervalele `engines.node` acceptate | **Consultativ** | +| Flux de lucru | Validează | Blocant | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `nightly-property` | Teste bazate pe proprietăți fast-check, cu seed aleatoriu și număr mare de rulări | **Consultativ** | +| `nightly-resilience` | Verificarea creșterii heap-ului, injectarea haotică a defecțiunilor, teste k6 de încărcare/anduranță | **Consultativ** | +| `nightly-llm-security` | Protecție promptfoo împotriva injecțiilor (mod de blocare) + sonde garak (omise în lipsa unui secret pentru furnizor) | **Consultativ** | +| `nightly-schemathesis` | Fuzzing al contractului OpenAPI (schemathesis) pe o instanță OmniRoute activă, folosind `docs/openapi.yaml` — evidențiază încălcările specificației / erorile 500 negestionate (Faza 8 B.4) | **Consultativ** | +| `nightly-mutation` | Scorul testării prin mutații Stryker pentru fluxul rapid de teste unitare — mutanții supraviețuitori evidențiază aserțiunile slabe | **Consultativ** | +| `nightly-compat` | Matricea de compatibilitate a motorului Node pentru intervalele `engines.node` acceptate | **Consultativ** | --- @@ -547,44 +536,43 @@ eroare de conexiune și nu întrerupe niciodată sesiunile. --- -## Restanțe de raționalizare (revizuire ROI — Faza 9 Valul 3) +## Restanțe de raționalizare (analiză ROI — Faza 9 Valul 3) Acest inventar a fost reconciliat cu `ci.yml` la 2026-06-17 (versiunea anterioară omitea `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). O revizuire ROI a setului reconciliat +`check:codeql-ratchet`, `check:pr-evidence`). O analiză ROI a setului reconciliat a identificat următorii candidați pentru raționalizare. **Comasările sunt modificări CI -mecanice; activările/dezactivările sunt decizii de politică rezervate operatorului.** Nimic din cele de mai jos +mecanice; schimbările de stare/eliminările sunt decizii de politică rezervate operatorului.** Nimic din cele de mai jos nu este aplicat încă. **De asemenea, nedocumentate mai sus** (consultative, semnal slab): jobul `docs-lint` -(markdownlint + Vale, întregul job cu `continue-on-error`) și fluxurile de lucru autonome pentru scanare +(markdownlint + Vale, întregul job având `continue-on-error`) și fluxurile de lucru autonome de scanare `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` se află în -`quality-baseline.json`, dar nu este conectat la un mecanism de blocare bazat pe prag în `ci.yml` — metrica este +`quality-baseline.json`, dar nu este conectat la un prag incremental blocant în `ci.yml` — metrica este în prezent orfană. -### Comasare / deduplicare (mecanică, risc mai scăzut) +### Comasare / deduplicare (mecanică, risc mai redus) -Fiecare candidat a fost validat în raport cu starea efectivă a barierelor la 2026-06-17 (încredere, dar cu verificare); -mai multe comasări „evidente” s-au dovedit a ascunde datorie tehnică și **nu** pot fi aplicate -direct fără probleme. +Fiecare candidat a fost validat în raport cu starea efectivă a mecanismelor de control la 2026-06-17 (încredere, dar cu verificare); +mai multe comasări „evidente” s-au dovedit a ascunde datorie și **nu** pot fi aplicate direct fără probleme. -- **`check:docs-sync` rulează de două ori** — separat în jobul `lint` și din nou în `check:docs-all` (`docs-sync-strict`) și în hook-ul husky pre-commit. ✅ **FINALIZAT** — invocarea separată din `lint` a fost eliminată. -- **Scanarea CVE** — ❌ **NU este o comasare simplă.** `audit:deps` eșuează ferm la orice CVE cu severitate ridicată/critică; `check:vuln-ratchet` (osv) eșuează doar la o _regresie_ față de valoarea de referință (în prezent 1 MODERATE). Semantică diferită — eliminarea `audit:deps` ar elimina bariera absolută pentru severități ridicate/critice. Păstrați-le pe ambele. -- **Detectarea ciclurilor** — ❌ **NU este o comasare simplă.** `check:circular-deps` (dpdm) raportează **91 de cicluri** (acesta este motivul pentru care are caracter consultativ); nu poate fi promovat la statut de blocare fără a le rezolva mai întâi și are un domeniu de aplicare mai larg decât `check:cycles`, care este verde și atent configurat. Păstrați `check:cycles` ca mecanism de blocare; rezolvarea celor 91 de cicluri dpdm constituie o restanță separată. -- **Complexitate** — ✅ **FINALIZAT** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): o singură parcurgere ESLint, cu numărare după ruleId, astfel încât valorile de referință pentru complexitatea ciclomatică+numărul maxim de linii și complexitatea cognitivă să rămână independente; comenzile individuale `check:complexity` / `check:cognitive-complexity` sunt păstrate pentru rulări locale cu `--update`. -- **Prevenirea halucinațiilor pentru `/api`** — ✅ **FINALIZAT** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): un singur inventar FS pentru `src/app/api`, openapi-routes + docs-symbols continuă să raporteze independent; comenzile individuale sunt păstrate pentru rulări locale. -- **`check:node-runtime` rulează în 11 joburi** — ⚠️ **ROI scăzut.** Fiecare folosește un runner separat, iar verificarea durează <1s; economia totală este de ~10s, cu prețul pierderii unei protecții ieftine pentru fiecare job. Nu merită schimbările aferente. -- **`typecheck:noimplicit:core` în lint-ul CI** — ✅ **eliminat din jobul lint** (avea caracter consultativ, cu `continue-on-error`); suprafața de tipuri blocantă este `typecheck:core` + `check:type-coverage`. Scriptul local a fost păstrat. +- **`check:docs-sync` rulează de două ori** — autonom în jobul `lint` și din nou în `check:docs-all` (`docs-sync-strict`) și în hook-ul husky de pre-commit. ✅ **FINALIZAT** — invocarea autonomă din `lint` a fost eliminată. +- **Scanarea CVE** — ❌ **NU este o comasare fără probleme.** `audit:deps` eșuează categoric pentru orice CVE de severitate ridicată/critică; `check:vuln-ratchet` (osv) eșuează numai la o _regresie_ față de valoarea de referință (în prezent 1 MODERATE). Semantică diferită — eliminarea `audit:deps` ar înlătura pragul absolut pentru severitate ridicată/critică. Păstrați-le pe ambele. +- **Detectarea ciclurilor** — ✅ **FINALIZAT** (#15159 G-01/G-02). Textul anterior numea `check:cycles` mecanismul de control „verde, selecționat” și justifica păstrarea sa ca blocant deoarece `check:circular-deps` (dpdm) raporta 91 de cicluri. Acel rezultat verde era un **fals pozitiv verde**: `check:cycles` scana 5 subdirectoare (450 de fișiere), identifica numai construcțiile statice `import|export … from` și elimina fiecare specificator `@/` și `@omniroute/open-sse/`, astfel încât nu putea detecta ciclurile cu import dinamic + alias care predominau în depozit. Remediere: mecanismul de control parcurge acum `src` + `open-sse` (5023 de fișiere), colectează specificatorii din AST-ul TypeScript (astfel încât `import("…")` este luat în calcul, iar `typeof import("…")` din poziții de tip nu este) și rezolvă `paths` din tsconfig. Găsește **14** cicluri, nu 0. Deoarece 14 cicluri preexistente nu pot fi remediate într-un PR dedicat mecanismului de control, `check:cycles` este acum un **prag incremental** (`--ratchet`, plafon `metrics.cycles.value = 14` în `quality-baseline.json`, `direction: down`) — blochează orice _regresie_, iar numărul poate doar să scadă. CI rulează `npm run check:cycles:ratchet`. Reducerea treptată este gestionată împreună cu **A-01**. `check:circular-deps` (dpdm) rămâne consultativ, ca a doua opinie mai cuprinzătoare. +- **Complexitate** — ✅ **FINALIZAT** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): o singură parcurgere ESLint, cu numărare după ruleId, astfel încât valorile de referință pentru complexitatea ciclomatică+numărul maxim de linii și cele cognitive să rămână independente; comenzile individuale `check:complexity` / `check:cognitive-complexity` rămân disponibile pentru rularea locală cu `--update`. +- **Anti-halucinații pentru `/api`** — ✅ **FINALIZAT** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): un singur inventar FS pentru `src/app/api`; openapi-routes + docs-symbols continuă să raporteze independent; comenzile individuale rămân disponibile pentru rulări locale. +- **`check:node-runtime` rulează în 11 joburi** — ⚠️ **ROI scăzut.** Fiecare folosește un runner separat, iar verificarea durează <1s; economia totală este de ~10s, în schimbul pierderii unei verificări ieftine pentru fiecare job. Nu merită perturbarea. +- **`typecheck:noimplicit:core` în lint-ul CI** — ✅ **eliminat din jobul lint** (era consultativ, cu `continue-on-error`); suprafața de tipuri blocantă este `typecheck:core` + `check:type-coverage`. Scriptul local a fost păstrat. -### Activare / decizie (politica operatorului) +### Schimbare de stare / decizie (politica operatorului) -- `check:openapi-security-tiers` (consultativ) — ❌ **NU poate fi activat fără modificări prealabile.** Se încheie cu codul 0, dar avertizează că mai multe rute `traffic-inspector` din `LOCAL_ONLY_API_PREFIXES` nu au adnotarea `x-loopback-only: true`. Impunerea verificării necesită mai întâi adăugarea acestor adnotări în `openapi.yaml`. -- `typecheck:noimplicit:core` (consultativ) — este în mare parte acoperit de mecanismul blocant bazat pe prag `check:type-coverage`. Transformați-l într-un mecanism bazat pe prag sau eliminați a doua trecere `tsc`, care este redundantă. -- `test:vitest:ui` (acum **blocant**) — erorile preexistente sunt excluse explicit în `vitest.config.ts` prin comentarii de urmărire `// #8618`; erorile noi fac jobul să eșueze. -- `check:secrets` (gitleaks, mecanism blocant bazat pe prag, fixat la 3 rezultate fals pozitive documentate) — adăugați-le pe cele 3 în lista de permisiuni pentru a ajunge la 0 sau retrogradați verificarea la statut consultativ. Se suprapune cu scanarea nativă GitHub a secretelor + `check:public-creds`. -- `check:pr-evidence` (blocant, caută expresii în textul corpului PR-ului) — risc ridicat de rezultate fals pozitive; eliminarea sa slăbește aplicarea Regulii stricte #18, așadar aceasta este o decizie autentică de politică. -- `semgrep` (flux autonom consultativ) — se suprapune cu CodeQL pentru familiile OWASP; conectați valoarea sa de referință la un mecanism bazat pe prag sau eliminați-l. +- `check:openapi-security-tiers` (consultativ) — ❌ **NU poate deveni blocant fără modificări prealabile.** Se încheie cu codul 0, dar avertizează că mai multe rute `traffic-inspector` din `LOCAL_ONLY_API_PREFIXES` nu au adnotarea `x-loopback-only: true`. Aplicarea strictă necesită mai întâi adăugarea acestor adnotări în `openapi.yaml`. +- `typecheck:noimplicit:core` (consultativ) — în mare parte înlocuit de pragul incremental blocant `check:type-coverage`. Transformați-l într-un prag incremental sau eliminați a doua rulare redundantă a `tsc`. +- `test:vitest:ui` (acum **blocant**) — eșecurile preexistente sunt excluse explicit în `vitest.config.ts` prin comentarii de urmărire `// #8618`; eșecurile noi duc la eșuarea jobului. +- `check:secrets` (gitleaks, prag incremental blocant înghețat la 3 rezultate fals pozitive documentate) — adăugați-le pe cele 3 în lista de permisiuni pentru a ajunge la 0 sau retrogradați verificarea la statut consultativ. Se suprapune cu scanarea nativă a secretelor oferită de GitHub + `check:public-creds`. +- `check:pr-evidence` (blocant, caută prin grep în proza din corpul PR-ului) — risc ridicat de rezultate fals pozitive; eliminarea sa slăbește aplicarea Regulii stricte #18, deci aceasta este o veritabilă decizie de politică. +- `semgrep` (autonom, consultativ) — se suprapune cu CodeQL pentru familiile OWASP; conectați valoarea sa de referință la un prag incremental sau eliminați-l. --- diff --git a/docs/i18n/ro/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ro/docs/compression/COMPRESSION_GUIDE.md index 157343287488..103c3434a529 100644 --- a/docs/i18n/ro/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ro/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ adaugă clauza privind limitele **o singură dată** și începe blocul cu un si Pentru un corp cu `messages`, ocolirea bazată pe conținut (`shouldBypassCavemanOutputMode()` din `open-sse/services/compression/outputMode.ts`) verifică ultimele trei mesaje și omite stilurile pentru întreaga interacțiune atunci când acestea corespund cuvintelor-cheie privind securitatea, acțiunile ireversibile, -clarificarea sau ordinea operațiilor. Ocolirea rulează conform valorii comutatorului -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) din panoul de control. +clarificarea sau ordinea operațiilor. Ocolirea rulează cât timp comutatorul **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) din panoul de control este activat, ceea ce reprezintă valoarea implicită; cu comutatorul dezactivat, stilurile selectate se aplică și în aceste interacțiuni. Când ocolirea permite procesarea interacțiunii, `placeSystemInstruction()` (același fișier), care nu creează niciodată un nou `messages[0]`, plasează blocul în primul dintre următoarele locuri pe care îl găsește: diff --git a/docs/i18n/ro/docs/reference/ENVIRONMENT.md b/docs/i18n/ro/docs/reference/ENVIRONMENT.md index 8a3793046372..e3b2bd784a21 100644 --- a/docs/i18n/ro/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ro/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Secrete obligatorii -Acestea **trebuie** configurate înainte de prima rulare. Fără ele, aplicația fie va refuza să pornească, fie va funcționa cu valori implicite nesigure. - -| Variabilă | Obligatorie | Valoare implicită | Fișier sursă | Descriere | -| ---------------------------- | --------------------- | ------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Da** | _(niciuna)_ | `src/lib/auth` | Semnează/verifică toate cookie-urile de sesiune ale panoului de control (JWT). Generați cu `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Da** | _(niciuna)_ | `src/lib/db/apiKeys.ts` | Cheie de criptare AES pentru valorile cheilor API stocate în SQLite. Generați cu `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Da** | `CHANGEME` | Script de inițializare | Setează parola inițială de administrator pentru panoul de control (corespunde valorii implicite din `.env.example` — este păstrată în mod evident nesigură pentru a impune schimbarea ei). **Schimbați-o înainte de prima utilizare.** După autentificare, schimbați-o prin Panou de control → Setări → Securitate. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (în producție) | _(neconfigurată)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secret partajat pentru puntea WebSocket internă Codex Responses. Autentifică solicitările punții dintre releul WS Electron/browser și OmniRoute. ⚠️ **OBLIGATORIU în producție — când nu este configurat, toate solicitările către puntea WS sunt respinse.** Generați cu `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nu | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identificator explicit pentru invalidarea cache-ului service worker-ului pentru shell-ul PWA (#11779); primul în lanțul de rezoluție. | -| `SOURCE_VERSION` | Nu | _(neconfigurată)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Al doilea în lanț — configurat de sistemele de compilare PaaS (de exemplu, în stil Heroku) drept commit-ul implementat. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nu | _(derivată)_ | `src/shared/components/PwaRegister.tsx` | Valoare publică din timpul compilării, utilizată de client pentru a înregistra `/sw.js?v=…`; derivată din cele două valori de mai sus, apoi din SHA-ul git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nu (automat) | _(automat la fiecare pornire)_ | `src/server/authz/policies/management.ts` | Secret per proces care dovedește că marcajul IP de peer de încredere provine de la propriul server HTTP al OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware-ul authz consideră de încredere caracterul local al solicitării (restricționarea rutelor LOCAL_ONLY la loopback/LAN) numai când marcajul conține acest token. Este generat automat la fiecare pornire — lăsați-l neconfigurat; fixați-l doar pentru configurațiile cu mai multe procese care trebuie să partajeze marcajul. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nu | _(neconfigurată)_ | `scripts/dev/peer-stamp.mjs` | Adrese IP sau intervale CIDR separate prin virgulă ale proxy-urilor inverse ale căror valori `X-Forwarded-For` / `X-Real-IP` pot fi considerate de încredere de lista de permisiuni/interdicții IP, pe lângă adresele loopback, adresele din rețele private și nodurile periferice Cloudflare, care sunt întotdeauna de încredere. Este necesar doar pentru un proxy aflat la orice altă adresă publică; în caz contrar, filtrul evaluează adresa proprie a proxy-ului. | +Secretele neconfigurate marcate cu **Da** sunt generate automat la prima rulare (`JWT_SECRET`, `API_KEY_SECRET`) și persistate în `server.env` din directorul de date — configurați-le explicit atunci când acel director este efemer. Excepția este `OMNIROUTE_WS_BRIDGE_SECRET`: nu este persistat în `server.env` — punctele de intrare ale serverului generează la fiecare boot o valoare efemeră pe care o poate folosi doar puntea din proces, de aceea clienții externi ai punții trebuie să o configureze explicit. + +| Variabilă | Obligatorie | Valoare implicită | Fișier sursă | Descriere | +| ---------------------------- | --------------------- | ------------------------------ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Da** | _(niciuna)_ | `src/lib/auth` | Semnează/verifică toate cookie-urile de sesiune ale panoului de control (JWT). Generați cu `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Da** | _(niciuna)_ | `src/lib/db/apiKeys.ts` | Cheie de criptare AES pentru valorile cheilor API stocate în SQLite. Generați cu `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nu | _(neconfigurată)_ | `src/lib/auth/managementPassword.ts` | Setează parola panoului de control la pornire, cât timp niciuna nu este salvată; este ignorată odată ce există o parolă salvată. Instalările npm și din sursă copiază `.env.example` în `.env`, deci pornesc cu substituentul său public cunoscut `CHANGEME`: înlocuiți-l înainte de primul boot sau setați `INITIAL_PASSWORD=` (gol) în acel `.env` pentru a crea parola în asistentul de configurare inițială la prima vizită, așa cum face o valoare neconfigurată (implicită în imaginea Docker). Schimbați o parolă salvată în Panou de control → Setări → Securitate sau rulați `omniroute reset-password` (`node bin/reset-password.mjs` într-un checkout din sursă) cu `DATA_DIR` configurat către directorul de date al serverului. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (în producție) | _(neconfigurată)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secret partajat pentru puntea WebSocket internă Codex Responses. Autentifică solicitările punții dintre releul WS Electron/browser și OmniRoute. ⚠️ **OBLIGATORIU în producție — când nu este configurat, toate solicitările către puntea WS sunt respinse.** Generați cu `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nu | _(SHA git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Identificator explicit pentru invalidarea cache-ului service worker-ului pentru shell-ul PWA (#11779); primul în lanțul de rezoluție. | +| `SOURCE_VERSION` | Nu | _(neconfigurată)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Al doilea în lanț — configurat de sistemele de compilare PaaS (de exemplu, în stil Heroku) drept commit-ul implementat. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nu | _(derivată)_ | `src/shared/components/PwaRegister.tsx` | Valoare publică din timpul compilării, utilizată de client pentru a înregistra `/sw.js?v=…`; derivată din cele două valori de mai sus, apoi din SHA-ul git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nu (automat) | _(automat la fiecare pornire)_ | `src/server/authz/policies/management.ts` | Secret per proces care dovedește că marcajul IP de peer de încredere provine de la propriul server HTTP al OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware-ul authz consideră de încredere caracterul local al solicitării (restricționarea rutelor LOCAL_ONLY la loopback/LAN) numai când marcajul conține acest token. Este generat automat la fiecare pornire — lăsați-l neconfigurat; fixați-l doar pentru configurațiile cu mai multe procese care trebuie să partajeze marcajul. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nu | _(neconfigurată)_ | `scripts/dev/peer-stamp.mjs` | Adrese IP sau intervale CIDR separate prin virgulă ale proxy-urilor inverse ale căror valori `X-Forwarded-For` / `X-Real-IP` pot fi considerate de încredere de lista de permisiuni/interdicții IP, pe lângă adresele loopback, adresele din rețele private și nodurile periferice Cloudflare, care sunt întotdeauna de încredere. Este necesar doar pentru un proxy aflat la orice altă adresă publică; în caz contrar, filtrul evaluează adresa proprie a proxy-ului. | ### Comenzi de generare @@ -215,38 +215,38 @@ OmniRoute utilizează **SQLite** (prin `better-sqlite3`) pentru întreaga persis ## 4. Securitate și autentificare -| Variabilă | Valoare implicită | Fișier sursă | Descriere | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinat cu identificatorii hardware pentru amprentarea mașinii. Schimbați-l pentru fiecare implementare în vederea izolării. | -| `OMNIROUTE_CLI_SALT` | _(nesetat = salt aleatoriu pentru fiecare instalare, păstrat în `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC pentru derivarea tokenului local de autentificare CLI. Setarea acestei valori rotește toate tokenurile CLI de pe mașină și are întotdeauna prioritate față de saltul păstrat. Consultați `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Setează atributul `Secure` pentru cookie-urile de sesiune. **Trebuie să fie `true`** când aplicația rulează în spatele HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware API | Când este `true`, toate cererile proxy `/v1/*` trebuie să includă o cheie API validă. **Acest indicator nu controlează accesul la `GET /v1/models`**, care urmează în schimb configurația de autentificare a panoului de control (`requireAuthForModels`) — prin urmare, un răspuns `401` de la `/v1/models` NU înseamnă că inferența este protejată. Consultați `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite afișarea valorilor complete ale cheilor API în interfața panoului de control. Poate fi configurat din opțiunile funcționale ale panoului de control; reprezintă un risc de securitate pentru instanțele partajate. | -| `NO_LOG_API_KEY_IDS` | _(gol)_ | `src/lib/compliance/index.ts` | ID-uri de chei API, separate prin virgule, care omit jurnalizarea cererilor (conformitate GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nesetat = nelimitat)_ | `src/shared/utils/apiKeyPolicy.ts` | Buget zilnic implicit de cereri, aplicat cheilor API a căror coloană `rate_limits` este null. Nesetat sau gol: nicio limită implicită (#2289, #11017). `0` are același efect (nelimitat). Un număr întreg pozitiv N activează N/zi, 5N/săptămână, 20N/lună. Valorile nevalide care nu sunt goale revin la limitele vechi de 1000/zi, 5000/săptămână, 20000/lună. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Dimensiunea maximă permisă pentru corpul cererii. Respinge sarcinile utile care depășesc această limită. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Corpurile efective ale cererilor care ating sau depășesc acest prag obțin, înainte de analizarea JSON, permisul atomic local procesului pentru admiterea sarcinilor grele (calea BYTE, inclusiv `POST /v1/responses`). Aceeași evadare bazată pe marja de capacitate disponibilă din [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ca în cazul structurilor complexe; rămâne limitată de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), astfel încât problema [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) să nu reapară. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limită strictă pentru rutele de chat, aplicată octeților citiți în timpul ingestiei delimitate, inclusiv pentru cererile cu `Content-Length` lipsă, nevalid sau incorect; depășirea limitei primește `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nesetat — fără limită privind numărul de cereri)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** această limită veche privind NUMĂRUL de cereri se aplică acum numai când este setată explicit. Dacă rămâne nesetată (valoarea implicită), admiterea cererilor de chat cu sarcină mare este controlată în schimb de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — un buget de OCTEȚI derivat automat, dimensionat în funcție de limita reală de memorie a procesului în **un singur proces** (un singur heap V8). Două cereri `/v1/responses` suprapuse, de aproximativ 750k tokenuri, provoacă abandonarea heap-urilor de aproximativ 12 GiB (#7849) — un avertisment privind **bugetul de memorie**, nu o limită strictă de produs de 2. Un proces sănătos (heap sub raportul de degrevare) POATE admite mai multe cereri `/v1/responses` lungi concurente prin `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Zeci de clienți SSE cu durată lungă (40–50) depind de heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nu de „maximum 2”. Creșterea nejustificată a acestei valori pentru a „utiliza gazda” reintroduce #7849. Multiplicați **heap-urile** folosind **N directoare `DATA_DIR` independente** (#11024); nu utilizați niciodată `replicas>1` pentru același fișier SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivat automat)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** suprascriere pentru bugetul de octeți de ingestie derivat automat (25% din cea mai restrictivă limită de memorie V8/cgroup, împărțit la amplificarea tranzitorie de 8x). Valorile derivate și explicite sunt limitate la intervalul 8 MiB–2 GiB. Un corp mai mare decât bugetul efectiv eșuează imediat cu `413 body_exceeds_budget`; concurența dintre corpuri care pot fi procesate individual rămâne reîncercabilă cu `503`. 40–50 de clienți SSE concurenți cu durată lungă depind de acest buget + heap, nu de o limită strictă de „maximum 2”. Consultați `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` la `/api/monitoring/health` înainte de ajustare. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Raportul de degrevare la presiunea heap-ului (`heapUsed / heap_size_limit`) pentru admiterea cererilor cu sarcină mare de tip BYTE și STRUCTURE (#10183, #10268, #10437). O cerere concurentă cu sarcină mare care depășește `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` este respinsă cu codul reîncercabil `503` numai când heap-ul se află DE ASEMENEA la acest raport sau peste el; dacă heap-ul este sănătos, cererea este admisă prin marja de capacitate pentru stare sănătoasă. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (implicit `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacitate suplimentară delimitată pentru calea rapidă aferentă unui heap sănătos (#10437), atât pentru STRUCTURE, cât și pentru BYTE (`admitChatRequest`, inclusiv corpuri ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Fără această limită, fiecare cerere dintr-un sistem ocupat, dar cu heap sănătos, ocolea mecanismul de admitere fără nicio limită superioară. După ce acest număr de permise concurente devin active prin calea pentru heap sănătos, cererile ocupate suplimentare ajung pe ACEEAȘI cale delimitată de așteptare/degrevare utilizată în condiții de presiune reală asupra heap-ului. `0` dezactivează complet ocolirea. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Numărul de mesaje care clasifică o cerere de chat ca având sarcină mare, chiar dacă corpul acesteia se află sub pragul de octeți. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Numărul de instrumente care clasifică o cerere de chat ca având sarcină mare, chiar dacă corpul acesteia se află sub pragul de octeți. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimare conservatoare a numărului de tokenuri pe baza dimensiunii șirurilor, care clasifică o cerere ca având sarcină mare; aceasta este o aproximare a costului de admitere, nu tokenizarea utilizată de furnizor pentru facturare. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (dezactivat) | `src/shared/middleware/chatBodyAdmission.ts` | Limită opțională, activată explicit, pentru istoricul conversației. Dezactivată implicit: numărul de mesaje reprezintă o politică de implementare, nu o proprietate universală a unei cereri, iar limitarea aici respinge conversațiile cu un răspuns terminal `413` înainte ca fluxul de compresie să le poată face procesabile. Creșterea heap-ului este limitată de `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` și de degrevarea la presiunea heap-ului. Setați o valoare pozitivă în implementările cu memorie limitată care necesită o limită strictă; depășirea acesteia primește apoi răspunsul structurat `413`, care indică necesitatea compactării. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limită strictă pentru un răspuns upstream fără streaming, stocat integral în memoria tampon. La depășirea acesteia, cititorul upstream este anulat, iar cererea eșuează rapid, în loc să mărească un șir fără limită până la epuizarea heap-ului. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Numărul maxim de octeți transmiși prin rețea din anteturile răspunsului din amonte. Când bugetul este depășit, anteturile cu prioritate mai mică (de ex., cele personalizate `x-codex-*`, `x-oai-request-id`) sunt eliminate pentru a rămâne în limitele uzuale ale anteturilor proxy-urilor inverse. Setați o valoare mai mare pentru a transmite mai multe metadate din amonte, cu prețul unor anteturi de răspuns mai mari. | -| `CORS_ORIGIN` | _(nesetat)_ | `src/server/cors/origins.ts` | Listă CORS învechită de origini permise, pentru o singură origine. Pentru implementările noi, preferați `CORS_ALLOWED_ORIGINS`. CORS este destinat exclusiv clienților API din browser care folosesc origini diferite; operațiunile de scriere autentificate din panoul de control folosesc în schimb cereri de aceeași origine și protecție CSRF asociată sesiunii. | -| `CORS_ALLOWED_ORIGINS` | _(nesetat)_ | `src/server/cors/origins.ts` | Listă CORS de origini permise, separată prin virgule. Nu este trimis niciun metacaracter decât dacă `CORS_ALLOW_ALL=true` este configurat explicit. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanism de evitare destinat exclusiv dezvoltării, care permite reflectarea oricărei valori `Origin` din browser. Nu îl activați în implementări partajate sau de producție. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blochează apelurile către furnizori care vizează intervale de adrese IP private, loopback sau link-local. Dezactivați numai în medii de testare izolate. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Permite URL-uri de furnizori care indică spre rețele private/locale (localhost, 192.168.x.x, 10.x.x.x etc.). **OBLIGATORIU pentru furnizorii găzduiți local** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Când valoarea este `false`, panoul de control respinge validarea URL-urilor locale. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Permite adăugarea/validarea furnizorilor la adrese locale/private (127.0.0.1, localhost, LAN, intervale private) — limitată la calea de validare a furnizorului. **Valoarea implicită este `true`** (prioritate locală); setați la `false` pentru a impune blocarea strictă a tuturor adreselor care nu sunt publice. Punctele finale pentru metadatele cloud (169.254.169.254, metadata.google.internal) rămân blocate indiferent de această setare. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permite rutelor `/v1/audio/*` (transcrieri, vorbire, traduceri) să utilizeze un nod de furnizor compatibil cu OpenAI, găzduit în afara localhost. Dezactivat implicit — rutarea conținutului audio către o gazdă la distanță schimbă identitatea traficului de ieșire și trebuie să fie o decizie explicită a operatorului. Nodurile loopback/private (localhost, 127.0.0.1, 172.16-31.x) sunt întotdeauna permise și nu sunt afectate. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permite ca `POST /v1/rerank` (și pasul loopback de reclasificare al motorului de memorie) să utilizeze un nod de furnizor compatibil cu OpenAI, găzduit în afara localhost — un sistem din LAN sau un dispozitiv Tailscale peer care rulează TEI, Infinity, vLLM etc. Dezactivat implicit — rutarea către o gazdă la distanță schimbă identitatea traficului de ieșire și trebuie să fie o decizie explicită a operatorului. Nodurile loopback (localhost, 127.0.0.1, 172.16-31.x) sunt întotdeauna permise și nu sunt afectate. Nodurile la distanță trebuie, de asemenea, să respecte politica privind URL-urile de ieșire ale furnizorului (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); traficul nu este direcționat niciodată către gazdele pentru metadate cloud. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Când OIDC este activat, dezactivează autentificarea prin parolă, astfel încât utilizatorii să se poată autentifica numai prin conectare unică OIDC. Este acceptat și aliasul simplu `OIDC_DISABLE_PASSWORD_LOGIN`; indicatorul de funcționalitate din panoul de control cu aceeași cheie are prioritate. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simplu pentru `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variabilă | Valoare implicită | Fișier sursă | Descriere | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combinat cu identificatorii hardware pentru amprentarea mașinii. Schimbați-l pentru fiecare implementare în vederea izolării. | +| `OMNIROUTE_CLI_SALT` | _(nesetat = salt aleatoriu pentru fiecare instalare, păstrat în `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC pentru derivarea tokenului local de autentificare CLI. Setarea acestei valori rotește toate tokenurile CLI de pe mașină și are întotdeauna prioritate față de saltul păstrat. Consultați `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Setează atributul `Secure` pentru cookie-urile de sesiune. **Trebuie să fie `true`** când aplicația rulează în spatele HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware API | Când este `true`, toate cererile proxy `/v1/*` trebuie să includă o cheie API validă. **Acest indicator nu controlează accesul la `GET /v1/models`**, care urmează în schimb configurația de autentificare a panoului de control (`requireAuthForModels`) — prin urmare, un răspuns `401` de la `/v1/models` NU înseamnă că inferența este protejată. Consultați `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Permite afișarea valorilor complete ale cheilor API în interfața panoului de control. Poate fi configurat din opțiunile funcționale ale panoului de control; reprezintă un risc de securitate pentru instanțele partajate. | +| `NO_LOG_API_KEY_IDS` | _(gol)_ | `src/lib/compliance/index.ts` | ID-uri de chei API, separate prin virgule, care omit jurnalizarea cererilor (conformitate GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nesetat = nelimitat)_ | `src/shared/utils/apiKeyPolicy.ts` | Buget zilnic implicit de cereri, aplicat cheilor API a căror coloană `rate_limits` este null. Nesetat sau gol: nicio limită implicită (#2289, #11017). `0` are același efect (nelimitat). Un număr întreg pozitiv N activează N/zi, 5N/săptămână, 20N/lună. Valorile nevalide care nu sunt goale revin la limitele vechi de 1000/zi, 5000/săptămână, 20000/lună. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Dimensiunea maximă permisă pentru corpul cererii. Respinge sarcinile utile care depășesc această limită. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Corpurile efective ale cererilor care ating sau depășesc acest prag obțin, înainte de analizarea JSON, permisul atomic local procesului pentru admiterea sarcinilor grele (calea BYTE, inclusiv `POST /v1/responses`). Aceeași evadare bazată pe marja de capacitate disponibilă din [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ca în cazul structurilor complexe; rămâne limitată de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), astfel încât problema [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) să nu reapară. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Limită strictă pentru rutele de chat, aplicată octeților citiți în timpul ingestiei delimitate, inclusiv pentru cererile cu `Content-Length` lipsă, nevalid sau incorect; depășirea limitei primește `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nesetat — fără limită privind numărul de cereri)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** această limită veche privind NUMĂRUL de cereri se aplică acum numai când este setată explicit. Dacă rămâne nesetată (valoarea implicită), admiterea cererilor de chat cu sarcină mare este controlată în schimb de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — un buget de OCTEȚI derivat automat, dimensionat în funcție de limita reală de memorie a procesului în **un singur proces** (un singur heap V8). Două cereri `/v1/responses` suprapuse, de aproximativ 750k tokenuri, provoacă abandonarea heap-urilor de aproximativ 12 GiB (#7849) — un avertisment privind **bugetul de memorie**, nu o limită strictă de produs de 2. Un proces sănătos (heap sub raportul de degrevare) POATE admite mai multe cereri `/v1/responses` lungi concurente prin `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Zeci de clienți SSE cu durată lungă (40–50) depind de heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nu de „maximum 2”. Creșterea nejustificată a acestei valori pentru a „utiliza gazda” reintroduce #7849. Multiplicați **heap-urile** folosind **N directoare `DATA_DIR` independente** (#11024); nu utilizați niciodată `replicas>1` pentru același fișier SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(derivat automat)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** suprascriere pentru bugetul de octeți de ingestie derivat automat (25% din cea mai restrictivă limită de memorie V8/cgroup, împărțit la amplificarea tranzitorie de 8x). Valorile derivate și explicite sunt limitate la intervalul 8 MiB–2 GiB. Un corp mai mare decât bugetul efectiv eșuează imediat cu `413 body_exceeds_budget`; concurența dintre corpuri care pot fi procesate individual rămâne reîncercabilă cu `503`. 40–50 de clienți SSE concurenți cu durată lungă depind de acest buget + heap, nu de o limită strictă de „maximum 2”. Consultați `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` la `/api/monitoring/health` înainte de ajustare. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Raportul de degrevare la presiunea heap-ului (`heapUsed / heap_size_limit`) pentru admiterea cererilor cu sarcină mare de tip BYTE și STRUCTURE (#10183, #10268, #10437). O cerere concurentă cu sarcină mare care depășește `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` este respinsă cu codul reîncercabil `503` numai când heap-ul se află DE ASEMENEA la acest raport sau peste el; dacă heap-ul este sănătos, cererea este admisă prin marja de capacitate pentru stare sănătoasă. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (implicit `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Capacitate suplimentară delimitată pentru calea rapidă aferentă unui heap sănătos (#10437), atât pentru STRUCTURE, cât și pentru BYTE (`admitChatRequest`, inclusiv corpuri ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Fără această limită, fiecare cerere dintr-un sistem ocupat, dar cu heap sănătos, ocolea mecanismul de admitere fără nicio limită superioară. După ce acest număr de permise concurente devin active prin calea pentru heap sănătos, cererile ocupate suplimentare ajung pe ACEEAȘI cale delimitată de așteptare/degrevare utilizată în condiții de presiune reală asupra heap-ului. `0` dezactivează complet ocolirea. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Numărul de mesaje care clasifică o cerere de chat ca având sarcină mare, chiar dacă corpul acesteia se află sub pragul de octeți. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Numărul de instrumente care clasifică o cerere de chat ca având sarcină mare, chiar dacă corpul acesteia se află sub pragul de octeți. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Estimare conservatoare a numărului de tokenuri pe baza dimensiunii șirurilor, care clasifică o cerere ca având sarcină mare; aceasta este o aproximare a costului de admitere, nu tokenizarea utilizată de furnizor pentru facturare. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (dezactivat) | `src/shared/middleware/chatBodyAdmission.ts` | Limită opțională, activată explicit, pentru istoricul conversației. Dezactivată implicit: numărul de mesaje reprezintă o politică de implementare, nu o proprietate universală a unei cereri, iar limitarea aici respinge conversațiile cu un răspuns terminal `413` înainte ca fluxul de compresie să le poată face procesabile. Creșterea heap-ului este limitată de `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` și de degrevarea la presiunea heap-ului. Setați o valoare pozitivă în implementările cu memorie limitată care necesită o limită strictă; depășirea acesteia primește apoi răspunsul structurat `413`, care indică necesitatea compactării. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Limită strictă pentru un răspuns upstream fără streaming, stocat integral în memoria tampon. La depășirea acesteia, cititorul upstream este anulat, iar cererea eșuează rapid, în loc să mărească un șir fără limită până la epuizarea heap-ului. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Numărul maxim de octeți transmiși prin rețea din anteturile răspunsului din amonte. Când bugetul este depășit, anteturile cu prioritate mai mică (de ex., cele personalizate `x-codex-*`, `x-oai-request-id`) sunt eliminate pentru a rămâne în limitele uzuale ale anteturilor proxy-urilor inverse. Setați o valoare mai mare pentru a transmite mai multe metadate din amonte, cu prețul unor anteturi de răspuns mai mari. | +| `CORS_ORIGIN` | _(nesetat)_ | `src/server/cors/origins.ts` | Listă CORS învechită de origini permise, pentru o singură origine. Pentru implementările noi, preferați `CORS_ALLOWED_ORIGINS`. CORS este destinat exclusiv clienților API din browser care folosesc origini diferite; operațiunile de scriere autentificate din panoul de control folosesc în schimb cereri de aceeași origine și protecție CSRF asociată sesiunii. | +| `CORS_ALLOWED_ORIGINS` | _(nesetat)_ | `src/server/cors/origins.ts` | Listă CORS de origini permise, separată prin virgule. Nu este trimis niciun metacaracter decât dacă `CORS_ALLOW_ALL=true` este configurat explicit. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Mecanism de evitare destinat exclusiv dezvoltării, care permite reflectarea oricărei valori `Origin` din browser. Nu îl activați în implementări partajate sau de producție. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Alias moștenit. Comutatorul său din panoul de control (o suprascriere în baza de date) este citit înaintea valorii acestei variabile de mediu; `false`, `0`, `no` sau `off` în oricare dintre ele dezactivează verificările gazdelor la fel cum o face `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, chiar și atunci când comutatorul din panou al acelui indicator este salvat pe dezactivat; celelalte valori lasă mecanismul de protecție în seama celor doi indicatori de mai jos. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Dezactivează verificările gazdelor mecanismului de protecție a URL-urilor de ieșire, inclusiv blocarea metadatelor cloud (tot intervalul 169.254.0.0/16, metadata.google.internal), pe căile furnizorilor care urmează modul de protecție: validarea URL-urilor, descoperirea modelelor, URL-urile de bază ale nodurilor de furnizor (inclusiv nodurile la distanță de reclasificare) și testul de proxy-fallback, printre alte căi de ieșire adiacente furnizorilor, precum preluarea la distanță de imagini/media. Permite, de asemenea, țintele private pentru webhook-uri, cu excepția punctelor finale cloud-metadata/link-local, care rămân blocate chiar și cu acest indicator activat. Solicitările de conversație trimise prin `BaseExecutor.execute()` își păstrează verificarea metadatelor cloud. Furnizorii locali funcționează cu valorile implicite: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (implicit `true`) permite deja URL-uri locale și de LAN pe căile de validare, descoperire și noduri de furnizori; testul de proxy-fallback și țintele private de webhook-uri țin cont doar de acest indicator și blochează gazdele locale/LAN cât timp acesta este dezactivat. Suprascrie `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. O valoare salvată din comutatorul panoului de control are prioritate față de această variabilă. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Permite URL-uri de furnizori la adrese locale/private (127.0.0.1, localhost, LAN, intervale private) pentru validarea furnizorilor, descoperirea modelelor, conversație și celelalte căi de ieșire adiacente furnizorilor guvernate de modul de protecție. **Valoarea implicită este `true`** (prioritate locală): mecanismul de protecție blochează atunci punctele finale de metadate cloud — tot intervalul 169.254.0.0/16 plus numele de gazdă de metadate cunoscute (metadata.google.internal, metadata.goog, IMDS Alibaba/Azure/Oracle, fd00:ec2::254). Setați `false` pentru modul doar-public, care blochează și gazdele private și loopback. Mecanismul verifică numele gazdei sau literalul IP așa cum este scris, înainte de orice căutare DNS; în modul doar-public, învelișul de preluare sigură rezolvă suplimentar numele gazdei și respinge orice răspuns privat. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` dezactivează aceste verificări pe căile enumerate în rândul acelui indicator. Furnizorii locali integrați, precum LM Studio, Ollama, vLLM, Llamafile și Triton, sar toate verificările gazdelor pentru validarea cheilor și conversație. O valoare salvată din comutatorul panoului de control are prioritate față de această variabilă. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Permite rutelor `/v1/audio/*` (transcrieri, vorbire, traduceri) să utilizeze un nod de furnizor compatibil cu OpenAI, găzduit în afara localhost. Dezactivat implicit — rutarea conținutului audio către o gazdă la distanță schimbă identitatea traficului de ieșire și trebuie să fie o decizie explicită a operatorului. Nodurile loopback/private (localhost, 127.0.0.1, 172.16-31.x) sunt întotdeauna permise și nu sunt afectate. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Permite ca `POST /v1/rerank` (și pasul loopback de reclasificare al motorului de memorie) să utilizeze un nod de furnizor compatibil cu OpenAI, găzduit în afara localhost — un sistem din LAN sau un dispozitiv Tailscale peer care rulează TEI, Infinity, vLLM etc. Dezactivat implicit — rutarea către o gazdă la distanță schimbă identitatea traficului de ieșire și trebuie să fie o decizie explicită a operatorului. Nodurile loopback (localhost, 127.0.0.1, 172.16-31.x) sunt întotdeauna permise și nu sunt afectate. URL-ul de bază al unui nod la distanță trebuie, de asemenea, să respecte politica privind URL-urile de ieșire ale furnizorului (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Când OIDC este activat, dezactivează autentificarea prin parolă, astfel încât utilizatorii să se poată autentifica numai prin conectare unică OIDC. Este acceptat și aliasul simplu `OIDC_DISABLE_PASSWORD_LOGIN`; indicatorul de funcționalitate din panoul de control cu aceeași cheie are prioritate. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias simplu pentru `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Listă de verificare pentru consolidarea securității diff --git a/docs/i18n/ru/CONTRIBUTING.md b/docs/i18n/ru/CONTRIBUTING.md index 740b38324548..55c7c57a9478 100644 --- a/docs/i18n/ru/CONTRIBUTING.md +++ b/docs/i18n/ru/CONTRIBUTING.md @@ -390,19 +390,19 @@ docs/ # Documentation ## Контрольный список Pull Request - [ ] Тесты проходят (`npm test`) -- [ ] Линтинг проходит (`npm run lint`) -- [ ] Сборка выполняется успешно (`npm run build`) +- [ ] Проверка линтером проходит (`npm run lint`) +- [ ] Сборка завершается успешно (`npm run build`) - [ ] Для новых публичных функций и интерфейсов добавлены типы TypeScript - [ ] Нет жёстко заданных секретов или резервных значений -- [ ] Публичные учётные данные вышестоящих сервисов встраиваются через `resolvePublicCred()` (см. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), но никогда не задаются литералами -- [ ] Ответы с ошибками проходят через `buildErrorBody()` / `sanitizeErrorMessage()` — никаких необработанных трассировок стека в телах ответов (см. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Команды оболочки (`exec` / `spawn`) передают значения времени выполнения через `env`, а не посредством строковой интерполяции +- [ ] Публичные учётные данные вышестоящих сервисов встраиваются через `resolvePublicCred()` (см. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), а не задаются литералами +- [ ] Ответы с ошибками проходят через `buildErrorBody()` / `sanitizeErrorMessage()` — необработанные трассировки стека не включаются в тела ответов (см. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Команды оболочки (`exec` / `spawn`) передают значения времени выполнения через `env`, а не посредством интерполяции строк - [ ] Все входные данные проверяются с помощью схем Zod -- [ ] Для изменений, заметных пользователям, добавлен **фрагмент** журнала изменений в `changelog.d/{features|fixes|maintenance}/-.md` (см. [`changelog.d/README.md`](./changelog.d/README.md)) — **не** редактируйте `CHANGELOG.md` напрямую; фрагменты объединяются во время выпуска и никогда не создают конфликтов между PR +- [ ] Для изменений, видимых пользователям, добавлен **фрагмент** журнала изменений в `changelog.d/{features|fixes|maintenance}/-.md` (см. [`changelog.d/README.md`](./changelog.d/README.md)) — **не** редактируйте `CHANGELOG.md` напрямую; фрагменты объединяются при выпуске релиза и никогда не вызывают конфликтов между PR - [ ] Документация обновлена (если применимо) - [ ] Не создано новых предупреждений CodeQL / Secret-Scanning либо каждое из них отклонено с техническим обоснованием и ссылкой на соответствующий документ в `docs/security/` -- [ ] Маршруты, запускающие дочерние процессы (`/api/mcp/`, `/api/cli-tools/runtime/`), классифицированы как `isLocalOnlyPath()` в `src/server/authz/routeGuard.ts` — см. [Жёсткое правило №15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] В сообщениях коммитов отсутствуют завершающие строки `Co-Authored-By` — коммиты должны отображаться исключительно под Git-идентичностью владельца репозитория (Жёсткое правило №16) +- [ ] Маршруты, запускающие дочерние процессы (`/api/mcp/`, `/api/cli-tools/runtime/`), классифицированы как `isLocalOnlyPath()` в `src/server/authz/routeGuard.ts` — см. [жёсткое правило № 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] В сообщениях коммитов отсутствуют трейлеры `Co-authored-by` для ИИ/ботов (жёсткое правило № 16) — люди, чья работа была повторно использована, указываются как соавторы с помощью стандартных трейлеров `Co-authored-by: Name ` --- diff --git a/docs/i18n/ru/docs/architecture/QUALITY_GATES.md b/docs/i18n/ru/docs/architecture/QUALITY_GATES.md index 90fa60e02e80..87a6fe08df88 100644 --- a/docs/i18n/ru/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ru/docs/architecture/QUALITY_GATES.md @@ -15,221 +15,221 @@ --- -## Инвентаризация проверок и профили выполнения +## Инвентаризация гейтов и профили выполнения -### Допуск кандидата +### Допуск кандидатов Рабочие процессы CI и Quality Gates формируют стабильные вердикты: `Gate / CI` и `Gate / Quality`. Их версионируемая политика допуска определяет каждую вышестоящую задачу как обязательную или рекомендательную. Применимая обязательная задача должна завершиться -успешно: отсутствующий, отменённый, пропущенный, ожидающий выполнения или неизвестный -результат не может подтвердить PASS. Корректная классификация «только документация» или -«только каталог» может сделать ветку проверки кода неприменимой; черновик PR не считается -принятым кандидатом. Метка `hotfix` не отменяет требования к подтверждающим данным. - -Оба рабочих процесса охватывают PR и отправку изменений в ветки main/release, ручной запуск и -события групп слияния. При отправке изменений, ручном запуске и запуске для группы слияния -выполняется полный набор проверок. Для форков и групп слияния используются размещённые -раннеры в задачах, которые в иных случаях выбирают собственные раннеры; перед развёртыванием -необходимо убедиться в достаточной доступной мощности размещённых раннеров. - -Каждая JSON-квитанция идентифицирует извлечённый SHA, запуск рабочего процесса и попытку. -CLI отклоняет несовпадение SHA извлечённой версии и события. Тесты рабочего процесса -связывают состав политики со списком `needs` задачи вердикта, чтобы новая или удалённая -ветка проверки не могла незаметно исчезнуть. Квитанции охватывают только соответствующий -рабочий процесс, но не публикацию, развёртывание или внутреннюю работу существующего -рекомендательного сканера. Активация обоих имён проверок в правилах ветки является -отдельным административным изменением; добавление этих задач само по себе не защищает ветку. +успешно: отсутствующие, отменённые, пропущенные, ожидающие выполнения и неизвестные результаты +не могут установить статус PASS. Корректная классификация «только документация» или «только каталог» +может сделать ветку проверки кода неприменимой; черновик PR не считается допущенным кандидатом. +Метка `hotfix` не отменяет требование о наличии подтверждений. + +Оба рабочих процесса охватывают PR и отправки изменений в ветки main/release, ручной запуск и +события групп слияния. При отправке изменений, ручном запуске и запуске для группы слияния выполняется +полный набор проверок. Для форков и групп слияния используются размещённые раннеры в задачах, которые +в иных случаях выбирают самостоятельно размещённые раннеры; перед развёртыванием необходимо проверить +наличие достаточных ресурсов размещённых раннеров. + +Каждая JSON-квитанция идентифицирует SHA извлечённой версии, запуск рабочего процесса и попытку. +CLI отклоняет несовпадение SHA извлечённой версии и события. Тесты рабочего процесса связывают +членство в политике со списком `needs` задачи вердикта, чтобы новая или удалённая ветка проверки +не могла незаметно исчезнуть. Квитанции охватывают собственный рабочий процесс, но не публикацию, +развёртывание или внутреннюю реализацию существующего рекомендательного сканера. Активация обоих +названий проверок в правилах ветвей является отдельным административным изменением; добавление этих +задач само по себе не защищает ветку. ### Инвентаризация статического сканирования -Версионируемая инвентаризация псевдонимов npm и состав статического сканирования находятся в -`config/quality/gate-manifest.json`. Выполните `npm run check:gate-manifest`, чтобы проверить -имена скриптов и точные команды по `package.json`; добавления, удаления и изменения команд +Версионируемая инвентаризация npm-псевдонимов и состав статического сканирования находятся в +`config/quality/gate-manifest.json`. Запустите `npm run check:gate-manifest`, чтобы проверить +названия скриптов и точные команды по `package.json`; добавления, удаления и расхождения команд приводят к сбою как локального хука, так и задач классификации изменений в CI. Псевдоним не является задачей рабочего процесса, экземпляром матрицы или тестовым случаем: -числа для этих сущностей нельзя представлять как взаимозаменяемые. +количество этих сущностей нельзя представлять как взаимозаменяемое. Используйте `npm run quality:scan -- --list` или `npm run quality:scan:fast -- --list`, чтобы просмотреть выбранные псевдонимы без их выполнения. Раннер вызывает точку входа -npm, поэтому используемая ею среда выполнения (включая Bun, где он настроен) сохраняется. -Манифест отмечает псевдонимы вне этих профилей как вызываемые отдельно, а команды -обслуживания запрещены в профилях сканирования, доступных только для чтения. +npm, поэтому среда её выполнения (включая Bun, где он настроен) сохраняется. +Манифест регистрирует псевдонимы за пределами этих профилей как вызываемые отдельно, а +команды обслуживания запрещены в профилях сканирования, предназначенных только для чтения. -Эти профили охватывают только статическое сканирование. Они не подтверждают тесты продукта, -покрытие, упаковку, внешние проверки или полный допуск кандидата к выпуску. +Эти профили охватывают только статическое сканирование. Они не подтверждают прохождение тестов +продукта, проверку покрытия, упаковку, внешние проверки или полный допуск кандидата к выпуску. Для допуска в рабочем процессе используются связанные `config/quality/admission-policy.json` и `scripts/quality/admission-verdict.mjs`. Профили наблюдения за выпуском остаются отдельными; -проверяйте их применимые проверки и квитанции независимо. Приведённая ниже текстовая -инвентаризация является справочной и не доказывает, что проверка действительно выполнялась. +проверяйте применимые к ним проверки и квитанции независимо. Приведённая ниже текстовая +инвентаризация является справочной и не доказывает, что гейт действительно выполнялся. -Скрипты находятся в `scripts/check/` (проверки политик) и `scripts/quality/` (механизм ратчета). -Единым источником истины для CI является `.github/workflows/ci.yml`. +Скрипты находятся в `scripts/check/` (гейты политики) и `scripts/quality/` (механизм храповика). +Источник истины для CI — `.github/workflows/ci.yml`. ### Ускоренный путь для PR выпуска (`quality.yml`) -`.github/workflows/quality.yml` дополняет CI для PR в ветки main/release, отправки изменений +`.github/workflows/quality.yml` дополняет CI для PR в ветки main/release, отправок изменений в защищённые ветки, ручного запуска и групп слияния. Для PR используются быстрые проверки -с фильтрацией по путям. Постоянно отключённая дублирующая сборка удалена; фактические -проверки сборки, упаковки и запуска остаются в CI. - -| Задача | Область действия | Блокирующая | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `Docs Gates (fast-path)` | PR документации/кода; ссылки документации API и вся документация | Да | -| `Fast Quality Gates` | PR кода; статические проверки, проверка типов, проверка типов панели мониторинга, затронутые модульные тесты | Да | -| `Forgotten sibling tests` | PR кода; изменённые модули сопоставляются со статическими потребителями и потенциальными соседними тестами; пути через индексные модули и динамические импорты выводятся как рекомендательная диагностика с указанными исключениями из списка разрешений | **Рекомендательная** | -| `Vitest (fast-path)` | PR кода; быстрый набор тестов vitest | Да | -| `Unit Tests fast-path` | PR кода; набор модульных тестов в 4 сегментах | Да | -| `No new ESLint warnings` | PR кода; проверка линтинга с учётом подавлений | Да, включая форки | -| `Merge integrity (changelog + generated skills)` | Нечерновые PR; синхронизация журнала изменений и сгенерированных навыков | Да, включая форки | +с фильтрацией по путям. Постоянно отключённая дублирующая сборка была удалена; реальные проверки +сборки, упаковки и загрузки остаются в CI. + +| Задача | Область | Блокирующая | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | PR документации/кода; ссылки на документацию API и docs-all | Да | +| `Fast Quality Gates` | PR кода; статические проверки, проверка типов, проверка типов панели мониторинга, затронутые модульные тесты | Да | +| `Forgotten sibling tests` | PR кода; изменённые модули отслеживаются до статических потребителей и потенциальных соседних тестов; пути через barrel-файлы и динамический импорт выводятся как рекомендательная диагностика с указанными исключениями из списка разрешений | **Рекомендательная** | +| `Vitest (fast-path)` | PR кода; быстрый набор тестов vitest | Да | +| `Unit Tests fast-path` | PR кода; набор модульных тестов в 4 шардах | Да | +| `No new ESLint warnings` | PR кода; проверка линтера с учётом подавлений | Да, включая форки | +| `Merge integrity (changelog + generated skills)` | Нечерновые PR; синхронизация журнала изменений и сгенерированных навыков | Да, включая форки | #### Отчёт о забытых соседних тестах `npm run check:forgotten-sibling-tests` повторно использует механизм разрешения импортов, -лежащий в основе карты влияния на тесты. Для каждого изменённого производственного модуля -он выводит детерминированные цепочки -`изменённый модуль/символ -> статический потребитель -> потенциальный соседний тест`, если -потенциальный тест отсутствует в разнице изменений pull request. Сводка Markdown и результат -JSON сохраняются как артефакт рабочего процесса `forgotten-sibling-tests` для калибровки -перед возможным переводом проверки в блокирующий режим. - -Повторные экспорты через barrel-файлы и динамические импорты используются только для диагностики разрешения зависимостей; они никогда не создают -блокирующее нарушение. Проверенные исключения находятся в +лежащий в основе карты влияния на тесты. Для каждого изменённого производственного модуля он +выводит детерминированные цепочки +`изменённый модуль/символ -> статический потребитель -> потенциальный соседний тест`, если потенциальный +тест отсутствует в различиях pull request. Сводка Markdown и результат JSON сохраняются как +артефакт рабочего процесса `forgotten-sibling-tests` для калибровки перед возможным переходом +к блокирующему режиму. + +Повторные экспорты через barrel-файлы и динамические импорты используются только для диагностики разрешения; они никогда не создают +блокирующую проблему. Проверенные исключения находятся в `config/quality/forgotten-sibling-allowlist.json`. Каждая запись должна указывать потребителя и тест-кандидат, -содержать конкретное обоснование и ссылку на задачу GitHub или pull request. При некорректных записях -проверка завершается с ошибкой. Исключения не могут скрыть удалённый тест-кандидат или изменение, добавляющее `.skip`/`.todo`; -ослабление утверждений и другие способы маскировки по-прежнему контролируются независимо блокирующим -шлюзом `check:test-masking`. - -### Задание: `lint` - -Запускается для каждого PR в `main`. При ошибке блокирует слияние. - -| Скрипт (`npm run ...`) | Проверяет | Блокирует | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| `check:node-runtime` | Версия Node.js находится в поддерживаемом диапазоне | Да | -| `check:cycles` | Циклические импорты во всех модулях `src/` и `open-sse/` | Да | -| `check:route-validation:t06` | Наличие схем Zod во всех маршрутах (политика уровня 6) | Да | -| `check:any-budget:t11` | Количество `@ts-expect-error // any` не превышает установленный лимит (турникет уровня 11) | Да | -| `check:provider-consistency` | Каждому провайдеру в `providers.ts` соответствует запись в `providerRegistry.ts` (и наоборот, в пределах списка разрешённых) | Да | -| `check:model-lifecycle` | Три вручную поддерживаемые таблицы маршрутизации остаются согласованными с зафиксированным в репозитории снимком жизненного цикла (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не присваивает оценку ни одному выведенному из эксплуатации идентификатору, который может маршрутизировать `REGISTRY`; каждая цель `BUILT_IN_ALIASES` присутствует в `REGISTRY` и отсутствует в снимке выведенных из эксплуатации идентификаторов; каждый выведенный из эксплуатации идентификатор, всё ещё присутствующий в `REGISTRY`, перенаправляется или указан в `allowedRetiredInCatalog`; и ни один источник или цель `DEFAULT_DEGRADATION_MAP` не отмечены в этом снимке как выведенные из эксплуатации. Это не доказывает, что модель в настоящее время обслуживается работающим вышестоящим сервисом. Проверка выполняется офлайн — путём сравнения с `config/quality/model-lifecycle.json`, который обновляется вручную с помощью `npm run quality:refresh-model-lifecycle` (требуется сеть; не подключено к CI). `allowedRetiredInCatalog` — это храповик постепенного сокращения: добавляйте запись только при наличии задачи для отслеживания. | Да | -| `check:fetch-targets` | Каждый вызов `fetch("/api/...")` в клиентской части `src/` разрешается в реальный `route.ts` | Да | -| `check:deps` | Все зависимости, устанавливаемые через `npm install`, во всех файлах `package.json` репозитория присутствуют в `dependency-allowlist.json`; новые незакреплённые или подменённые через слопсквоттинг пакеты помечаются | Да | -| `audit:deps` | `npm audit` (корень + electron) — отсутствие предупреждений высокого/критического уровня (частично дублирует osv `check:vuln-ratchet`; см. журнал рационализации) | Да | -| `check:lockfile` | Целостность `package-lock.json` — реестр https, хеши целостности, отсутствие переопределений хоста | Да | -| `check:licenses` | Список разрешённых лицензий SPDX для production-зависимостей | Да | -| `check:tracked-artifacts` | Отсутствие артефактов сборки / закоммиченных символических ссылок `node_modules` (также запускается в husky pre-commit; pre-push намеренно облегчён — #6716) | Да | -| `check:ai-attribution` | Отсутствие трейлера AI/бота `Co-Authored-By` или примечания о генерации с помощью AI в коммитах, заголовке или описании PR — жёсткое правило №16 (в цикле быстрых проверок `quality.yml` для PR→`release/**` — считывает данные события, ничего не делает вне PR — и отдельный шаг только для PR в lint-проверке `ci.yml` для PR→`main`; также хук husky `commit-msg`; соавторы-люди разрешены; #14436) | -| `check:vitest-exclusions` | Каждое исключение Vitest содержит ссылку на отслеживаемую задачу и присутствует в `config/quality/vitest-exclusions.json` (#13204) | Да | -| `check:file-size` | Ни один исходный файл не превышает ограничение для соответствующего расширения (храповик: зафиксированные крупные файлы в списке `frozen`) | Да | -| `check:error-helper` | Ответы с ошибками в исполнителях/обработчиках используют `buildErrorBody()` / `sanitizeErrorMessage()` (жёсткое правило №12) | Да | -| `check:migration-numbering` | SQL-файлы миграций пронумерованы последовательно, без пропусков и дубликатов | Да | -| `check:public-creds` | За пределами `publicCreds.ts` отсутствуют литеральные значения OAuth `client_id`/`client_secret` и веб-ключи Firebase (жёсткое правило №11) | Да | -| `check:db-rules` | За пределами модулей `src/lib/db/` отсутствует необработанный SQL; отсутствуют импорты из barrel-файла `localDb.ts` (жёсткие правила №2/№5) | Да | -| `check:known-symbols` | Исполнители провайдеров, стратегии маршрутизации и трансляторы, зарегистрированные в соответствующих таблицах диспетчеризации, совпадают с файлами на диске — отсутствуют потерянные или необъявленные символы | Да | -| `check:route-guard-membership` | Каждый маршрут, порождающий дочерний процесс, классифицируется функцией `isLocalOnlyPath()` (жёсткие правила №15/№17) | Да | -| `check:test-discovery` | Каждый файл `*.test.ts` / `*.spec.ts` в репозитории обнаруживается хотя бы одним средством запуска тестов (храповый механизм: список необнаруженных файлов в `test-discovery-baseline.json` может только сокращаться) | Да | -| `check:agent-skills-sync` | Сгенерированные артефакты agent-skills соответствуют исходному каталогу (без расхождений) | -| `check:provider-asset-provenance` | Для логотипов/ресурсов провайдеров указана информация о происхождении | -| `lint:json` | Файлы конфигурации JSON корректно разбираются и соответствуют правилам линтинга репозитория | -| `typecheck:core` | Компиляция TypeScript без ошибок (только рекомендательные предупреждения) | Да | -| `typecheck:noimplicit:core` | Строгий режим `noImplicitAny` — рассчитан на будущее; многие существующие места вызова всё ещё требуют аннотаций | **Рекомендательно** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, ограниченный областью `src/app/(dashboard)/**` (#7033), — поддерживаемый вручную список разрешённых 27 файлов в `typecheck:core` не включает ни одного TSX-файла панели мониторинга, а `next build` также никогда не выполняет для них проверку типов (`next.config.mjs` задаёт `ignoreBuildErrors: true`), поэтому регрессии с потерянными идентификаторами в этих файлах (#6625/#6909) были невидимы для CI. Выполняется сравнение с зафиксированным базовым уровнем количества ошибок для каждого файла и каждого кода TS (`config/quality/dashboard-typecheck-baseline.json`, тот же шаблон контроля устаревания, что и в `check:known-symbols`): проверка завершается неудачно только при появлении НОВЫХ ошибок сверх количества, зафиксированного в базовом уровне; уменьшайте базовый уровень с помощью `--update` после исправления существующей ошибки. | Да | +содержать конкретное обоснование и ссылку на issue или pull request в GitHub. Некорректные записи приводят +к запрету по умолчанию. Исключения не могут скрыть удалённый тест-кандидат или diff, добавляющий `.skip`/`.todo`; +ослабление утверждений и другие способы маскировки остаются в ведении независимо блокирующего +гейта `check:test-masking`. + +### Задача: `lint` + +Запускается для каждого PR в `main`. При сбое блокирует слияние. + +| Скрипт (`npm run ...`) | Что проверяет | Блокирует | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| `check:node-runtime` | Версия Node.js находится в поддерживаемом диапазоне | Да | +| `check:cycles` | Циклические импорты во всех `src/` + `open-sse/` (на основе AST, с разрешением `paths` из tsconfig). Без аргументов работает в рекомендательном режиме и выводит список циклов. `check:cycles:ratchet` (запускаемый в CI) блокирует, когда их количество превышает порог `metrics.cycles` в `quality-baseline.json` — сейчас 14, `direction: down`, поэтому оно может только уменьшаться (#15159 G-01/G-02) | Да (ratchet) | +| `check:route-validation:t06` | Наличие схем Zod на всех маршрутах (политика Tier 6) | Да | +| `check:any-budget:t11` | Количество `@ts-expect-error // any` не превышает бюджет (catraca Tier 11) | Да | +| `check:provider-consistency` | Каждый провайдер в `providers.ts` имеет соответствующую запись в `providerRegistry.ts` (и наоборот, в пределах списка разрешений) | Да | +| `check:model-lifecycle` | Три поддерживаемые вручную таблицы маршрутизации остаются согласованными с зафиксированным в репозитории снимком жизненного цикла (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не оценивает ни один выведенный из эксплуатации идентификатор, который может маршрутизировать `REGISTRY`; каждая цель `BUILT_IN_ALIASES` присутствует в `REGISTRY` и отсутствует в снимке выведенных из эксплуатации идентификаторов; каждый выведенный из эксплуатации идентификатор, всё ещё находящийся в `REGISTRY`, перенаправляется или указан в `allowedRetiredInCatalog`; и ни один источник или цель `DEFAULT_DEGRADATION_MAP` не помечены в этом снимке как выведенные из эксплуатации. Это не доказывает, что модель в настоящее время обслуживается работающим вышестоящим сервисом. Проверка выполняется автономно — данные сравниваются с `config/quality/model-lifecycle.json`, который обновляется вручную с помощью `npm run quality:refresh-model-lifecycle` (требуется сеть; не подключено к CI). `allowedRetiredInCatalog` — это механизм постепенного сокращения: добавляйте запись только при наличии задачи для отслеживания. | Да | +| `check:fetch-targets` | Каждый вызов `fetch("/api/...")` в клиентском коде `src/` разрешается в реально существующий `route.ts` | Да | +| `check:deps` | Все зависимости, устанавливаемые с помощью `npm install`, во всех файлах `package.json` репозитория присутствуют в `dependency-allowlist.json`; новые зависимости без зафиксированных версий или пакеты, подозреваемые в слопсквоттинге, помечаются | Да | +| `audit:deps` | `npm audit` (корень + electron) — нет предупреждений высокой/критической степени опасности (пересекается с osv `check:vuln-ratchet`; см. журнал отложенной рационализации) | Да | +| `check:lockfile` | Целостность `package-lock.json` — реестр https, хеши целостности, отсутствие переопределений хостов | Да | +| `check:licenses` | Список разрешённых лицензий SPDX для production-зависимостей | Да | +| `check:tracked-artifacts` | Отсутствие артефактов сборки / закоммиченных символических ссылок `node_modules` (также запускается в husky pre-commit; pre-push намеренно облегчён — #6716) | Да | +| `check:ai-attribution` | Отсутствие трейлера AI/бота `Co-Authored-By` или примечания об AI-генерации в коммитах, заголовке или описании PR — строгое правило №16 (в цикле быстрых проверок `quality.yml` для PR→`release/**` — считывает данные события и ничего не делает вне PR — и отдельный шаг только для PR в lint-задаче `ci.yml` для PR→`main`; также хук husky `commit-msg`; соавторы-люди разрешены; #14436) | +| `check:vitest-exclusions` | Каждое исключение Vitest содержит ссылку на отслеживаемую задачу и присутствует в `config/quality/vitest-exclusions.json` (#13204) | Да | +| `check:file-size` | Ни один исходный файл не превышает ограничение для своего расширения (храповик: зафиксированные крупные файлы в списке `frozen`) | Да | +| `check:error-helper` | Ответы с ошибками в исполнителях/обработчиках используют `buildErrorBody()` / `sanitizeErrorMessage()` (строгое правило №12) | Да | +| `check:migration-numbering` | SQL-файлы миграций имеют последовательную нумерацию, без пропусков и дубликатов | Да | +| `check:public-creds` | Нет литеральных значений OAuth `client_id`/`client_secret` или веб-ключей Firebase за пределами `publicCreds.ts` (жёсткое правило №11) | Да | +| `check:db-rules` | Нет необработанного SQL за пределами модулей `src/lib/db/`; нет импорта через агрегирующий модуль из `localDb.ts` (жёсткие правила №2/№5) | Да | +| `check:known-symbols` | Исполнители провайдеров, стратегии маршрутизации и трансляторы, зарегистрированные в соответствующих таблицах диспетчеризации, соответствуют файлам на диске — без потерянных или необъявленных символов | Да | +| `check:route-guard-membership` | Каждый маршрут, запускающий дочерний процесс, классифицируется функцией `isLocalOnlyPath()` (жёсткие правила №15/№17) | Да | +| `check:test-discovery` | Каждый файл `*.test.ts` / `*.spec.ts` в репозитории обнаруживается хотя бы одним средством запуска тестов (храповый механизм: список потерянных тестов в `test-discovery-baseline.json` может только сокращаться) | Да | +| `check:agent-skills-sync` | Сгенерированные артефакты agent-skills соответствуют исходному каталогу (без расхождений) | +| `check:provider-asset-provenance` | Для логотипов/ресурсов провайдеров указаны сведения об их происхождении | +| `lint:json` | Файлы конфигурации JSON успешно разбираются и соответствуют правилам линтинга репозитория | +| `typecheck:core` | Компиляция TypeScript без ошибок (только рекомендательные предупреждения) | Да | +| `typecheck:noimplicit:core` | Строгий режим `noImplicitAny` — задел на будущее; многие существующие места вызова всё ещё требуют аннотаций | **Рекомендательный** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, ограниченный областью `src/app/(dashboard)/**` (#7033), — подготовленный список разрешённых 27 файлов в `typecheck:core` не включает ни одного TSX-файла панели мониторинга, а `next build` также никогда не проверяет их типы (`next.config.mjs` задаёт `ignoreBuildErrors: true`), поэтому регрессии с потерянными идентификаторами в этих файлах (#6625/#6909) оставались незаметными для CI. Результаты сравниваются с зафиксированным базовым количеством ошибок для каждого файла и кода TS (`config/quality/dashboard-typecheck-baseline.json`, по той же схеме контроля устаревания, что и `check:known-symbols`): проверка завершается неудачей только при появлении НОВЫХ ошибок сверх базового количества; уменьшайте базовый уровень с помощью `--update` после исправления существующей ошибки. | Да | ### Задание: `quality-gate` -Запускается после `test-coverage`. При ошибке блокирует слияние. +Запускается после `test-coverage`. При сбое блокирует слияние. -| Скрипт | Проверяет | Блокирующий | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | -| `quality:collect` | Создаёт `quality-metrics.json` (количество предупреждений ESLint, покрытие из объединённого отчёта по шардам) | Да (предшествует ratchet-проверке) | -| `quality:ratchet` | Ни одна метрика в `quality-baseline.json` не ухудшилась (предупреждения ESLint ≤ базового уровня; покрытие ≥ базового уровня) | Да | -| `check:duplication` | Дублирование кода (jscpd@4) не превышает базовый уровень в `quality-baseline.json` | Да | -| `check:complexity` | Цикломатическая сложность на уровне файла не превышает предельного значения (правила `complexity` + `max-lines-per-function` из ядра ESLint) | Да | -| `check:cognitive-complexity` | Ratchet-проверка когнитивной сложности (`eslint-plugin-sonarjs`) — отдельный проход ESLint; в CI оба запуска объединены в один этап `check:complexity-ratchets` | Да | -| `check:dead-code` | Ratchet-проверка неиспользуемых экспортов / файлов (knip) не ухудшилась относительно базового уровня | Да | -| `check:compression-budget` | Бюджет бенчмарка сжатия — минимальные пороги экономии токенов для каждого движка не должны ухудшаться | Да | -| `check:type-coverage` | Ratchet-проверка процента типизированного кода (`type-coverage`) не ухудшилась; в значительной степени заменяет `typecheck:noimplicit:core` | Да | -| `check:codeql-ratchet` | Количество открытых предупреждений CodeQL не увеличилось (получает данные через `gh api`; корректно пропускается без токена) — частоту обновления и ручной запуск см. ниже, в разделе «CodeQL ratchet» | Да | +| Скрипт | Что проверяет | Блокирующий | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `quality:collect` | Создаёт `quality-metrics.json` (количество предупреждений ESLint, покрытие из объединённого отчёта по шардам) | Да (предшествует ratchet) | +| `quality:ratchet` | Ни одна метрика в `quality-baseline.json` не ухудшилась (предупреждения ESLint ≤ базового уровня; покрытие ≥ базового уровня) | Да | +| `check:duplication` | Дублирование кода (jscpd@4) не превышает базовый уровень из `quality-baseline.json` | Да | +| `check:complexity` | Цикломатическая сложность на уровне файла не превышает предельное значение (правила ядра ESLint `complexity` + `max-lines-per-function`) | Да | +| `check:cognitive-complexity` | Ratchet когнитивной сложности (`eslint-plugin-sonarjs`) — отдельный проход ESLint; в CI оба запуска объединены в единый шаг `check:complexity-ratchets` | Да | +| `check:dead-code` | Ratchet неиспользуемых экспортов / файлов (knip) не ухудшился относительно базового уровня | Да | +| `check:compression-budget` | Бюджет бенчмарка сжатия — минимальные пороги экономии токенов для каждого движка не должны ухудшаться | Да | +| `check:type-coverage` | Ratchet процента типизированного кода (`type-coverage`) не ухудшился; в основном заменяет `typecheck:noimplicit:core` | Да | +| `check:codeql-ratchet` | Количество открытых оповещений CodeQL не увеличилось (получает данные через `gh api`; корректно пропускается без токена) — периодичность обновления и ручной запуск: см. «Ratchet CodeQL» ниже | Да | ### Задание: `quality-extended` -Всё задание носит рекомендательный характер (`continue-on-error: true`). Ratchet-проверки на основе npm выполняются -по-настоящему; внешние сканеры устанавливаются через `gh release download` и самостоятельно пропускают -проверку (код выхода 0), если исполняемый файл по-прежнему отсутствует. +Всё задание носит рекомендательный характер (`continue-on-error: true`). Ratchet-проверки на базе npm выполняются +полноценно; внешние сканеры устанавливаются через `gh release download` и самостоятельно пропускают проверку (exit 0), +если исполняемый файл всё ещё отсутствует. -| Скрипт | Проверяет | Блокирующий | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| `check:circular-deps` | Отсутствие циклических зависимостей (dpdm) | **Рекомендательный** | -| `check:bundle-size` | Размер бандла не превышает предельного значения | **Рекомендательный** | -| `check:secrets` | Сканирование секретов (gitleaks) — пропускается, если исполняемый файл отсутствует | **Рекомендательный** | -| `check:vuln-ratchet` | Уязвимости зависимостей (osv-scanner) не увеличились — пропускается, если исполняемый файл отсутствует | **Рекомендательный** | -| `check:workflows` | Линтинг рабочих процессов (actionlint + zizmor); отсутствующие/неработающие сканеры, недействительные отчёты или отсутствующий базовый уровень ratchet-проверки приводят к ошибке INCOMPLETE. Обнаруженные проблемы обрабатываются согласно выбранной строгой/рекомендательной/ratchet-политике | Выполнение обязательно; ratchet-проверка zizmor блокирует CI | -| `check:openapi-breaking` | Критические изменения публичного контракта API (`openapi.yaml`) относительно базовой ветки (oasdiff) — создаёт `openapiBreaking=N`; пропускается, если oasdiff отсутствует или базовую спецификацию невозможно разрешить | **Рекомендательный** | +| Скрипт | Что проверяет | Блокирующий | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| `check:circular-deps` | Отсутствие циклических зависимостей (dpdm) | **Рекомендательный** | +| `check:bundle-size` | Размер бандла не превышает предельное значение | **Рекомендательный** | +| `check:secrets` | Сканирование секретов (gitleaks) — пропускается, если исполняемый файл отсутствует | **Рекомендательный** | +| `check:vuln-ratchet` | Уязвимости зависимостей (osv-scanner) не увеличились — пропускается, если исполняемый файл отсутствует | **Рекомендательный** | +| `check:workflows` | Линтинг рабочих процессов (actionlint + zizmor); отсутствие/сбой сканеров, недопустимые отчёты или отсутствие базового уровня ratchet приводят к статусу INCOMPLETE. Допустимые находки обрабатываются согласно выбранной строгой/рекомендательной/ratchet-политике | Выполнение обязательно; ratchet zizmor блокирует в CI | +| `check:openapi-breaking` | Критические изменения публичного контракта API (`openapi.yaml`) относительно базовой ветки (oasdiff) — создаёт `openapiBreaking=N`; пропускается, если oasdiff отсутствует или базовую спецификацию невозможно разрешить | **Рекомендательный** | ### Задание: `docs-sync-strict` Запускается для каждого PR в `main`. При ошибке блокирует слияние. -| Скрипт | Что проверяет | Блокирующий | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `check:docs-all` | Мета-проверка, которая последовательно запускает 6 подпроверок ниже | Да | -| ↳ `check:docs-sync` | Согласованность версий в CHANGELOG / OpenAPI / llm.txt | Да | -| ↳ `check:docs-counts` | Числа в тексте (количество провайдеров, миграций и т. д.) находятся в пределах окна храповика относительно фактических значений | Да | -| ↳ `check:env-doc-sync` | Каждая переменная окружения из `.env.example` задокументирована в таблице документации, и наоборот | Да | -| ↳ `check:deprecated-versions` | В документации отсутствуют строки с устаревшими версиями | Да | -| ↳ `check:doc-links` | Внутренние markdown-ссылки в документации указывают на существующие файлы (формат `[текст]`/`(путь)`) | Да | -| ↳ `check:fabricated-docs` | Маршруты, переменные окружения, команды CLI, имена хуков и пути к файлам, упомянутые в документации, существуют в кодовой базе. Жёсткая проверка с `--strict`; без флага ошибка не блокирует. | Да (через `--strict` в CI) | -| `check:cli-i18n` | Строки команд CLI присутствуют во всех файлах локалей i18n | Да | -| `check:openapi-coverage` | Спецификация OpenAPI покрывает как минимум пороговое количество реальных маршрутов, заданное храповиком | Да | -| `check:openapi-security-tiers` | Аннотации уровней безопасности в `openapi.yaml` согласованы с классификациями в `routeGuard.ts` | **Рекомендательный** | -| `check:openapi-routes` | Каждый путь в `openapi.yaml` соответствует реальному `route.ts` (защита от галлюцинаций) | Да | -| `check:docs-symbols` | Каждая ссылка `/api/...` в `docs/**/*.md` соответствует реальному `route.ts` (защита от галлюцинаций) | Да | -| `i18n translation drift` | Непереведённые ключи в файлах локалей i18n — только предупреждение | **Рекомендательный** | +| Скрипт | Что проверяет | Блокирующий | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `check:docs-all` | Мета-проверка, последовательно запускающая 6 проверок ниже | Да | +| ↳ `check:docs-sync` | Согласованность версий в CHANGELOG / OpenAPI / llm.txt | Да | +| ↳ `check:docs-counts` | Числовые значения в тексте (количество провайдеров, миграций и т. д.) находятся в пределах допустимого окна относительно фактических значений | Да | +| ↳ `check:env-doc-sync` | Каждая переменная окружения из `.env.example` задокументирована в таблице документации, и наоборот | Да | +| ↳ `check:deprecated-versions` | В документации отсутствуют строки с устаревшими версиями | Да | +| ↳ `check:doc-links` | Внутренние markdown-ссылки в документации указывают на существующие файлы (формат `[текст]`/`(путь)`) | Да | +| ↳ `check:fabricated-docs` | Упомянутые в документации маршруты, переменные окружения, команды CLI, имена хуков и пути к файлам существуют в кодовой базе. Строгая проверка с `--strict`; без флага ошибка не блокирует. | Да (с `--strict` в CI) | +| `check:cli-i18n` | Строки команд CLI присутствуют во всех файлах локалей i18n | Да | +| `check:openapi-coverage` | Спецификация OpenAPI покрывает не меньше установленного порогового количества реальных маршрутов | Да | +| `check:openapi-security-tiers` | Аннотации уровней безопасности в `openapi.yaml` согласованы с классификациями в `routeGuard.ts` | **Рекомендательный** | +| `check:openapi-routes` | Каждый путь в `openapi.yaml` соответствует реальному `route.ts` (защита от галлюцинаций) | Да | +| `check:docs-symbols` | Каждая ссылка `/api/...` в `docs/**/*.md` соответствует реальному `route.ts` (защита от галлюцинаций) | Да | +| `i18n translation drift` | Непереведённые ключи в файлах локалей i18n — только предупреждение | **Рекомендательный** | ### Задание: `i18n-ui-coverage` -| Скрипт | Что проверяет | Блокирующий | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | -| `check-ui-keys-coverage` (inline) | Покрытие ключей i18n пользовательского интерфейса составляет ≥ 65% | Да | -| `check-ui-value-drift` (inline) | После изменения английского **значения** не остаётся устаревших переводов | Да | -| `check-new-key-coverage` (inline) | **Новый** английский ключ переведён во всех локалях — маркер `__MISSING__:` недопустим | Да | -| `check-translation-ratio` | Доля фактических переводов для каждой локали (совпадающие с английским / шаблонные / отсутствующие конечные узлы вне списка разрешений) не должна превышать `config/quality/i18n-translation-baseline.json` + допустимый запас | **Рекомендательный** | +| Скрипт | Что проверяет | Блокирующий | +| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `check-ui-keys-coverage` (встроенный) | Покрытие ключей i18n интерфейса составляет ≥ 65% | Да | +| `check-ui-value-drift` (встроенный) | После изменения английского **значения** не остаётся устаревших переводов | Да | +| `check-new-key-coverage` (встроенный) | **Новый** английский ключ переведён во всех локалях — маркер `__MISSING__:` недопустим | Да | +| `check-translation-ratio` | Доля реальных переводов для каждой локали (значения, идентичные английским, заполнители или отсутствующие элементы вне списка разрешённых исключений) не должна превышать `config/quality/i18n-translation-baseline.json` + допустимый запас | **Рекомендательный** | -Требуется `fetch-depth: 0` — проверка расхождений значений сравнивает `en.json` с базой слияния. +Требуется `fetch-depth: 0` — проверка изменения значений сравнивает `en.json` с базой слияния. #### `check-ui-value-drift` — проверка устаревших переводов -Выявляет единственную регрессию i18n, которую другие проверки структурно не способны обнаружить: английское значение -переписывается, а переводы, созданные на основе _предыдущего_ английского текста, остаются без изменений, поэтому -неанглоязычные пользователи продолжают читать уверенно сформулированный, но уже неверный текст. - -Это действительно попало в релиз. `oauthModal.googleOAuthWarning` был переписан при добавлении помощника входа -Antigravity (#5203); в **39 из 43 локалей** остался текст, предписывающий операторам «скопировать -полный URL-адрес и вставить его ниже» — сценарий, который невозможно завершить для этого провайдера. Это -оставалось незамеченным до #8463, потому что: - -- `sync-ui-keys` добавляет только **отсутствующие** ключи и никогда не обновляет **устаревшие**; -- `check-ui-keys-coverage` учитывает _наличие_ ключа, поэтому устаревший перевод считается покрытым; -- `check-translation-drift` отслеживает зеркала документации `docs/i18n//**.md` — - он никогда не читает `src/i18n/messages/*.json`. Блокирует в задании `docs-sync-strict` после - повторной синхронизации 2026-09: изменили основной документ → `npm run i18n:run -- --files=` (на уровне раздела, с небольшими затратами). - -**Учитывает diff, а не опирается на baseline.** Он сравнивает `en.json` в merge base -с рабочим деревом; для каждого ключа, английское значение которого изменилось, локаль, -всё ещё содержащая нетронутый перевод, считается устаревшей. Такой подход намеренно -**замораживает существующий технический долг** — по diff невозможно определить, из какой -старой английской строки был получен давно существующий перевод, поэтому проверка оценивает -только то, что затрагивает текущее изменение. Альтернатива (baseline с хешем для каждого ключа) -потребовала бы генерируемого файла размером около 600 КБ, в 3 раза больше крупнейшего -существующего baseline, который изменялся бы при каждом i18n PR. +Выявляет единственную регрессию i18n, которую остальные проверки структурно не способны обнаружить: английское значение +переписывается, а переводы, созданные на основе _предыдущего_ английского текста, остаются неизменными, из-за чего +пользователи с неанглийской локалью продолжают читать уверенно сформулированный, но уже неверный текст. + +Такая проблема действительно попала в релиз. `oauthModal.googleOAuthWarning` был переписан при добавлении вспомогательного инструмента +входа Antigravity (#5203); в **39 из 43 локалей** сохранился текст, предлагавший операторам «скопировать +полный URL-адрес и вставить его ниже», — при таком сценарии вход через этого провайдера невозможно завершить. Проблема +оставалась незамеченной до #8463, поскольку: + +- `sync-ui-keys` добавляет только **отсутствующие** ключи, но никогда не обновляет **устаревшие**; +- `check-ui-keys-coverage` учитывает наличие ключа, поэтому устаревший перевод считается покрытым; +- `check-translation-drift` отслеживает зеркальные копии документации `docs/i18n//**.md` — + файлы `src/i18n/messages/*.json` он никогда не читает. Блокирует в задании `docs-sync-strict` после + повторной синхронизации 2026-09: измените основной документ → `npm run i18n:run -- --files=` (на уровне раздела, быстро). + +**Учитывает различия, а не опирается на базовый файл.** Он сравнивает `en.json` в точке слияния с +рабочим деревом; для каждого ключа, английское значение которого изменилось, любой языковой файл, +где перевод остался нетронутым, считается устаревшим. Это намеренно **фиксирует уже существующий +технический долг** — по различиям невозможно определить, из какой старой английской строки был +получен давно существующий перевод, поэтому проверка оценивает только то, чего касается текущее +изменение. Альтернатива (базовый файл с хешем для каждого ключа) потребовала бы сгенерированного +файла размером около 600 КБ — втрое больше крупнейшего существующего базового файла — и создавала +бы изменения при каждом PR, связанном с i18n. Есть два способа пройти проверку: 1. обновить затронутые переводы или -2. задать им значение `__MISSING__:<новый английский текст>` — тогда во время выполнения - будет возвращаться исправленный английский текст - (`src/i18n/request.ts::deepMergeFallback`, #7258), а ключ будет поставлен в очередь на перевод. +2. задать для них значение `__MISSING__:` — тогда во время выполнения будет + использоваться исправленный английский текст (`src/i18n/request.ts::deepMergeFallback`, #7258), + а ключ будет поставлен в очередь на перевод. Если изменился **смысл** строки, предпочтительно **переименовать ключ**: новый ключ не может унаследовать устаревший перевод. Именно этот подход использовался в #8463. @@ -240,51 +240,49 @@ npm run i18n:check-value-drift:warn # только отчёт BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Завершается с кодом 0 и сообщением `SKIP reason=base-unresolved`, если базовый каталог -невозможно прочитать (поверхностный клон без базового ref), аналогично -`check-openapi-breaking`. +Завершается с кодом 0 и сообщением `SKIP reason=base-unresolved`, если базовый каталог невозможно +прочитать (поверхностный клон без базовой ссылки), аналогично `check-openapi-breaking`. ### Задание: `i18n` -Полная матрица проверок i18n (по одному заданию на локаль). Всё задание носит +Полная матрица проверок i18n (одно задание для каждого языкового файла). Всё задание носит рекомендательный характер. -| Скрипт | Что проверяет | Блокирует | -| ------------------------------- | ---------------------------------- | ----------------------------------------------------- | -| `validate_translation.py quick` | Полноту перевода для каждой локали | **Нет** (`continue-on-error: true` для всего задания) | +| Скрипт | Что проверяет | Блокировка | +| ------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------- | +| `validate_translation.py quick` | Полноту перевода для каждого языкового файла | **Рекомендательная проверка** (`continue-on-error: true` для всего задания) | ### Задание: `pr-test-policy` Выполняется только для pull request. -| Скрипт | Что проверяет | Блокирует | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | -| `check:pr-test-policy` | PR, изменяющие рабочий код в `src/`, `open-sse/`, `electron/` или `bin/`, должны включать или обновлять тесты (жёсткое правило № 8) | Да | -| `check:test-masking` | Изменённые файлы тестов не уменьшают общее количество assert и не добавляют тавтологии `assert.ok(true)` | Да | -| `check:pr-evidence` | Текст PR содержит подтверждения тестирования/VPS для изменения (механизация жёсткого правила № 18 через grep текста PR — ненадёжно, см. Backlog) | Да | +| Скрипт | Что проверяет | Блокировка | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check:pr-test-policy` | PR, изменяющие рабочий код в `src/`, `open-sse/`, `electron/` или `bin/`, должны добавлять или обновлять тесты (жёсткое правило №8) | Да | +| `check:test-masking` | Изменённые тестовые файлы не уменьшают итоговое количество проверок и не добавляют тавтологии `assert.ok(true)` | Да | +| `check:pr-evidence` | Тело PR содержит сведения о тестировании/VPS для изменения (автоматизирует жёсткое правило №18 через поиск в тексте PR — ненадёжно, см. бэклог) | Да | ### Задание: `test-vitest` Выполняется после `build`. При сбое блокирует слияние. -| Набор тестов | Что проверяет | Блокирует | -| ---------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP-сервер (110 инструментов), autoCombo, кеш — vitest | Да | -| `test:vitest:ui` | Тесты компонентов UI — vitest | **Да** — существующие ранее сбои явно исключены в `vitest.config.ts`; новые сбои приводят к провалу задания | +| Набор тестов | Что проверяет | Блокировка | +| ---------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Сервер MCP (110 инструментов), autoCombo, кеш — Vitest | Да | +| `test:vitest:ui` | Тесты компонентов UI — Vitest | **Блокирующая проверка** — существовавшие ранее сбои явно исключены в `vitest.config.ts`; новые сбои приводят к падению задания | -### Ночные workflow (по расписанию, рекомендательные) +### Ночные рабочие процессы (по расписанию, рекомендательные) -Они запускаются по расписанию cron (и через `workflow_dispatch`), но никогда не запускаются -для PR. Все они носят рекомендательный характер. +Они запускаются по расписанию cron (а также через `workflow_dispatch`) и никогда не выполняются для PR. Все они носят рекомендательный характер. -| Workflow | Что проверяет | Блокирует | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `nightly-property` | property-тесты fast-check со случайным seed и большим количеством запусков | **Нет** | -| `nightly-resilience` | порог роста heap, внедрение хаотических сбоев, нагрузочное/длительное тестирование k6 | **Нет** | -| `nightly-llm-security` | защиту promptfoo от инъекций (режим блокировки) и проверки garak (пропускаются без секрета провайдера) | **Нет** | -| `nightly-schemathesis` | фаззинг контракта OpenAPI (schemathesis) для запущенного OmniRoute с использованием `docs/openapi.yaml` — выявляет нарушения спецификации / необработанные ошибки 500 (Fase 8 B.4) | **Нет** | -| `nightly-mutation` | оценку мутационного тестирования Stryker для быстрого набора модульных тестов — выжившие мутанты выявляют слабые assert | **Нет** | -| `nightly-compat` | матрицу совместимости движка Node для поддерживаемых диапазонов `engines.node` | **Нет** | +| Рабочий процесс | Что проверяет | Блокировка | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `nightly-property` | Тесты свойств fast-check со случайным начальным значением и большим числом запусков | **Рекомендательная проверка** | +| `nightly-resilience` | Ограничение роста кучи, внедрение хаотических сбоев, нагрузочное/длительное тестирование k6 | **Рекомендательная проверка** | +| `nightly-llm-security` | Защиту promptfoo от инъекций (режим блокировки) и проверки garak (пропускаются при отсутствии секрета провайдера) | **Рекомендательная проверка** | +| `nightly-schemathesis` | Фаззинг контракта OpenAPI (schemathesis) на работающем OmniRoute с использованием `docs/openapi.yaml` — выявляет нарушения спецификации / необработанные ошибки 500 (этап 8 B.4) | **Рекомендательная проверка** | +| `nightly-mutation` | Показатель мутационного тестирования Stryker для быстрого набора модульных тестов — выжившие мутанты выявляют слабые проверки | **Рекомендательная проверка** | +| `nightly-compat` | Матрицу совместимости движка Node для поддерживаемых диапазонов `engines.node` | **Рекомендательная проверка** | --- @@ -522,42 +520,41 @@ CodeQL: По умолчанию → Расширенная. Не добавля ## Бэклог рационализации (оценка ROI — фаза 9, волна 3) -Этот перечень был сверен с `ci.yml` 2026-06-17 (в предыдущей версии были пропущены +Эта инвентаризация была сверена с `ci.yml` 2026-06-17 (в предыдущей версии были пропущены `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Оценка ROI сверенного набора выявила следующие кандидатуры на рационализацию. **Объединения представляют собой механические -изменения CI; включение обязательного режима и удаление — это решения в области политик, остающиеся за оператором.** Ничто из перечисленного ниже +изменения CI; переключение режимов и удаление — это решения по политикам, остающиеся за оператором.** Ничто из перечисленного ниже пока не применено. -**Также не задокументированы выше** (рекомендательные проверки с низкой информативностью): задание `docs-lint` -(markdownlint + Vale, для всего задания задано `continue-on-error`) и автономные рабочие процессы сканирования -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. Значение `semgrepFindings: 0` присутствует в -`quality-baseline.json`, но не подключено к блокирующему храповому механизму в `ci.yml` — в настоящее -время эта метрика изолирована. - -### Объединение / устранение дублирования (механические изменения, более низкий риск) - -Каждая кандидатура была проверена на соответствие фактическому состоянию шлюзов 2026-06-17 (доверяй, но проверяй); -несколько «очевидных» объединений, как выяснилось, скрывают технический долг и **не** являются чистыми -прямыми заменами. - -- **`check:docs-sync` запускается дважды** — отдельно в задании `lint`, а затем ещё раз внутри `check:docs-all` (`docs-sync-strict`) и в pre-commit-хуке husky. ✅ **ВЫПОЛНЕНО** — отдельный вызов из `lint` удалён. -- **Сканирование CVE** — ❌ **НЕ является чистым объединением.** `audit:deps` завершается с ошибкой при любой CVE высокой/критической серьёзности; `check:vuln-ratchet` (osv) завершается с ошибкой только при _регрессии_ относительно базового уровня (сейчас 1 MODERATE). Семантика различается — удаление `audit:deps` привело бы к потере абсолютного шлюза для высокой/критической серьёзности. Сохранить оба. -- **Обнаружение циклов** — ❌ **НЕ является чистым объединением.** `check:circular-deps` (dpdm) сообщает о **91 цикле** (поэтому эта проверка и является рекомендательной); её нельзя сделать блокирующей, не устранив сначала эти циклы, а область её действия шире, чем у успешно проходящей курируемой проверки `check:cycles`. Оставить `check:cycles` блокирующей; устранение 91 цикла dpdm — отдельная задача в бэклоге. -- **Сложность** — ✅ **ВЫПОЛНЕНО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): один проход ESLint, подсчёт по ruleId, поэтому базовые уровни цикломатической сложности+max-lines и когнитивной сложности остаются независимыми; отдельные `check:complexity` / `check:cognitive-complexity` сохранены для локального запуска с `--update`. -- **Защита от галлюцинаций `/api`** — ✅ **ВЫПОЛНЕНО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): одна инвентаризация `src/app/api` через ФС, openapi-routes + docs-symbols по-прежнему формируют отчёты независимо; отдельные проверки сохранены для локальных запусков. -- **`check:node-runtime` запускается в 11 заданиях** — ⚠️ **низкий ROI.** Каждое из них выполняется на отдельном раннере, а проверка занимает <1 с; общая экономия составит ~10 с ценой потери дешёвой проверки в каждом задании. Изменения того не стоят. -- **`typecheck:noimplicit:core` в CI lint** — ✅ **удалено из задания lint** (проверка была рекомендательной с `continue-on-error`); блокирующая проверка поверхности типов обеспечивается `typecheck:core` + `check:type-coverage`. Локальный скрипт сохранён. - -### Включить обязательный режим / принять решение (политика оператора) - -- `check:openapi-security-tiers` (рекомендательная) — ❌ **нельзя просто сделать блокирующей.** Она завершается с кодом 0, но предупреждает, что у нескольких маршрутов `traffic-inspector` в `LOCAL_ONLY_API_PREFIXES` отсутствует аннотация `x-loopback-only: true`. Для её обязательного применения необходимо сначала добавить эти аннотации в `openapi.yaml`. -- `typecheck:noimplicit:core` (рекомендательная) — в значительной степени перекрывается блокирующим храповым механизмом `check:type-coverage`. Преобразовать в храповую проверку или удалить избыточный второй проход `tsc`. -- `test:vitest:ui` (теперь **блокирующая**) — существовавшие ранее сбои явно исключены в `vitest.config.ts` с отслеживающими комментариями `// #8618`; новые сбои приводят к ошибке задания. -- `check:secrets` (gitleaks, блокирующий храповой механизм, зафиксированный на уровне 3 задокументированных ложноположительных результатов) — добавить эти 3 результата в список разрешённых, чтобы достичь 0, либо перевести проверку в рекомендательный режим. Перекрывается встроенным сканированием секретов GitHub + `check:public-creds`. -- `check:pr-evidence` (блокирующая, выполняет grep по тексту тела PR) — высокий риск ложноположительных результатов; её удаление ослабит контроль соблюдения жёсткого правила №18, поэтому это действительно вопрос политики. -- `semgrep` (автономная рекомендательная проверка) — пересекается с CodeQL по семействам OWASP; подключить её базовый уровень к храповому механизму или удалить. +**Также не задокументированы выше** (рекомендательные, низкий уровень сигнала): задание `docs-lint` +(markdownlint + Vale, всё задание с `continue-on-error`) и автономные рабочие процессы сканирования +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` присутствует в +`quality-baseline.json`, но не подключён к блокирующему храповому механизму в `ci.yml` — сейчас эта метрика +изолирована. + +### Объединение / устранение дублирования (механические изменения, меньший риск) + +Каждая кандидатура была проверена по фактическому состоянию гейтов на 2026-06-17 (доверяй, но проверяй); +оказалось, что несколько «очевидных» объединений скрывают технический долг и **не** являются беспроблемной заменой. + +- **`check:docs-sync` запускается дважды** — отдельно в задании `lint`, а затем ещё раз внутри `check:docs-all` (`docs-sync-strict`) и в pre-commit-хуке husky. ✅ **ГОТОВО** — отдельный вызов в `lint` удалён. +- **Сканирование CVE** — ❌ **НЕ является беспроблемным объединением.** `audit:deps` завершается с ошибкой при любой CVE уровня high/critical; `check:vuln-ratchet` (osv) завершается с ошибкой только при _регрессии_ относительно базового уровня (сейчас 1 MODERATE). Семантика различается — удаление `audit:deps` приведёт к потере абсолютного гейта для уровней high/critical. Сохранить оба. +- **Обнаружение циклов** — ✅ **ГОТОВО** (#15159 G-01/G-02). В старом тексте `check:cycles` назывался «зелёным, тщательно настроенным» гейтом, а его блокирующий статус обосновывался тем, что `check:circular-deps` (dpdm) обнаруживал 91 цикл. Этот зелёный статус был **ложноположительным**: `check:cycles` сканировал 5 подкаталогов (450 файлов), находил только статические конструкции `import|export … from` и отбрасывал все спецификаторы `@/` и `@omniroute/open-sse/`, поэтому не мог увидеть циклы с динамическим импортом и псевдонимами, преобладавшие в репозитории. Исправлено: теперь гейт обходит `src` + `open-sse` (5023 файла), собирает спецификаторы из AST TypeScript (поэтому `import("…")` учитывается, а `typeof import("…")` в позиции типа — нет) и разрешает `paths` из tsconfig. Он обнаруживает **14** циклов, а не 0. Поскольку 14 существовавших ранее циклов нельзя исправить в PR для гейта, `check:cycles` теперь является **храповым механизмом** (`--ratchet`, порог `metrics.cycles.value = 14` в `quality-baseline.json`, `direction: down`) — он блокирует любую _регрессию_, а количество циклов может только уменьшаться. CI запускает `npm run check:cycles:ratchet`. Постепенное устранение выполняется в рамках **A-01**. `check:circular-deps` (dpdm) остаётся рекомендательной более широкой второй проверкой. +- **Сложность** — ✅ **ГОТОВО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): один проход ESLint, подсчёт по ruleId, поэтому базовые уровни цикломатической сложности + максимального числа строк и когнитивной сложности остаются независимыми; отдельные `check:complexity` / `check:cognitive-complexity` сохранены для локального запуска с `--update`. +- **Защита от галлюцинаций `/api`** — ✅ **ГОТОВО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): одна инвентаризация `src/app/api` в файловой системе, openapi-routes + docs-symbols по-прежнему формируют отчёты независимо; отдельные проверки сохранены для локального запуска. +- **`check:node-runtime` запускается в 11 заданиях** — ⚠️ **низкий ROI.** Каждое выполняется на отдельном раннере, а проверка занимает <1 с; общая экономия составит ~10 с ценой потери дешёвой защиты в каждом задании. Не стоит вносимых изменений. +- **`typecheck:noimplicit:core` в CI lint** — ✅ **удалён из задания lint** (был рекомендательным с `continue-on-error`); блокирующая поверхность типов обеспечивается `typecheck:core` + `check:type-coverage`. Локальный скрипт сохранён. + +### Переключить / решить (политика оператора) + +- `check:openapi-security-tiers` (рекомендательный) — ❌ **НЕЛЬЗЯ беспроблемно перевести в блокирующий режим.** Он завершается с кодом 0, но предупреждает, что у нескольких маршрутов `traffic-inspector` в `LOCAL_ONLY_API_PREFIXES` отсутствует аннотация `x-loopback-only: true`. Для принудительного применения сначала необходимо добавить эти аннотации в `openapi.yaml`. +- `typecheck:noimplicit:core` (рекомендательный) — в значительной степени перекрывается блокирующим храповым механизмом `check:type-coverage`. Перевести на храповой механизм или удалить избыточный второй проход `tsc`. +- `test:vitest:ui` (теперь **блокирующий**) — существовавшие ранее сбои явно исключены в `vitest.config.ts` с отслеживающими комментариями `// #8618`; новые сбои приводят к провалу задания. +- `check:secrets` (gitleaks, блокирующий храповой механизм, зафиксированный на 3 задокументированных ложноположительных результатах) — добавить эти 3 результата в список разрешений, чтобы достичь 0, либо перевести проверку в рекомендательный режим. Перекрывается нативным сканированием секретов GitHub + `check:public-creds`. +- `check:pr-evidence` (блокирующий, ищет шаблоны в тексте тела PR) — высокий риск ложноположительных результатов; его удаление ослабит соблюдение Hard Rule #18, поэтому это действительно вопрос политики. +- `semgrep` (рекомендательный автономный процесс) — перекрывается с CodeQL по семействам OWASP; подключить его базовый уровень к храповому механизму или удалить. --- diff --git a/docs/i18n/ru/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ru/docs/compression/COMPRESSION_GUIDE.md index 4615006aa8dc..c2af73e556a9 100644 --- a/docs/i18n/ru/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ru/docs/compression/COMPRESSION_GUIDE.md @@ -455,8 +455,7 @@ const { messages: aged, saved } = applyAging(messages, { `open-sse/services/compression/outputMode.ts`) проверяет последние три сообщения и пропускает стили для всего хода, если они соответствуют ключевым словам, связанным с безопасностью, необратимыми действиями, уточнениями или чувствительностью к порядку. -Обход выполняется в соответствии со значением переключателя **Auto-Clarity Bypass** -(`cavemanOutputMode.autoClarity`) на панели управления. +Обход выполняется, пока переключатель **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) на панели управления включён, что является значением по умолчанию; при выключенном переключателе выбранные стили применяются и на этих ходах. Если обход разрешает обработку хода, `placeSystemInstruction()` (в том же файле), который никогда не создаёт новый `messages[0]`, помещает блок в первое найденное место diff --git a/docs/i18n/ru/docs/reference/ENVIRONMENT.md b/docs/i18n/ru/docs/reference/ENVIRONMENT.md index c3094bb3d282..539485e1d912 100644 --- a/docs/i18n/ru/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ru/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Обязательные секреты -Эти значения **должны** быть заданы перед первым запуском. Без них приложение либо откажется запускаться, либо будет использовать небезопасные значения по умолчанию. - -| Переменная | Обязательность | По умолчанию | Исходный файл | Описание | -| ---------------------------- | --------------------- | ------------------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Да** | _(нет)_ | `src/lib/auth` | Подписывает/проверяет все cookie-файлы сессий панели управления (JWT). Создайте с помощью `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Да** | _(нет)_ | `src/lib/db/apiKeys.ts` | Ключ шифрования AES для значений API-ключей, хранящихся в SQLite. Создайте с помощью `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Да** | `CHANGEME` | Скрипт начальной настройки | Задаёт первоначальный пароль администратора панели управления (соответствует значению по умолчанию в `.env.example` — он намеренно оставлен явно небезопасным, чтобы обязать вас изменить его). **Измените перед первым использованием.** После входа измените его через «Панель управления → Настройки → Безопасность». | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Да** (в production) | _(не задан)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Общий секрет для внутреннего WebSocket-моста Codex Responses. Аутентифицирует запросы моста между WS-ретранслятором Electron/браузера и OmniRoute. ⚠️ **ОБЯЗАТЕЛЕН в production — если значение не задано, все запросы WS-моста отклоняются.** Создайте с помощью `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Нет | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Явный идентификатор сброса кеша service worker для оболочки PWA (#11779); первый в цепочке разрешения. | -| `SOURCE_VERSION` | Нет | _(не задан)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Второй в цепочке — задаётся сборщиками PaaS (например, в стиле Heroku) и содержит развёрнутый коммит. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Нет | _(вычисляется)_ | `src/shared/components/PwaRegister.tsx` | Публичное значение времени сборки, которое клиент использует для регистрации `/sw.js?v=…`; вычисляется из двух указанных выше значений, а затем из git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Нет (автоматически) | _(автоматически при каждом запуске)_ | `src/server/authz/policies/management.ts` | Секрет процесса, подтверждающий, что доверенная метка IP-адреса узла поступила от собственного HTTP-сервера OmniRoute (`scripts/dev/peer-stamp.mjs`). Промежуточное ПО authz доверяет локальности запроса (ограничению маршрутов LOCAL_ONLY адресами loopback/LAN), только если метка содержит этот токен. Автоматически создаётся при каждом запуске — оставьте незаданным; фиксируйте его только для многопроцессных конфигураций, которым необходимо использовать общую метку. | -| `OMNIROUTE_TRUSTED_PROXIES` | Нет | _(не задан)_ | `scripts/dev/peer-stamp.mjs` | Разделённые запятыми IP-адреса или диапазоны CIDR обратных прокси, чьим заголовкам `X-Forwarded-For` / `X-Real-IP` может доверять список разрешённых/запрещённых IP-адресов, в дополнение к адресам loopback, частных сетей и пограничных узлов Cloudflare, которые всегда считаются доверенными. Требуется только для прокси с любым другим публичным адресом; в противном случае фильтр оценивает собственный адрес этого прокси. | +Незаданные секреты, отмеченные как **Да**, автоматически генерируются при первом запуске (`JWT_SECRET`, `API_KEY_SECRET`) и сохраняются в `server.env` в каталоге данных — задайте их явно, если этот каталог является эфемерным. Исключение — `OMNIROUTE_WS_BRIDGE_SECRET`: он не сохраняется в `server.env` — точки входа сервера создают эфемерное значение на каждый запуск, доступное только мосту внутри процесса, поэтому внешним клиентам моста его нужно задавать явно. + +| Переменная | Обязательность | По умолчанию | Исходный файл | Описание | +| ---------------------------- | --------------------- | ------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Да** | _(нет)_ | `src/lib/auth` | Подписывает/проверяет все cookie-файлы сессий панели управления (JWT). Создайте с помощью `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Да** | _(нет)_ | `src/lib/db/apiKeys.ts` | Ключ шифрования AES для значений API-ключей, хранящихся в SQLite. Создайте с помощью `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Нет | _(не задан)_ | `src/lib/auth/managementPassword.ts` | Задаёт пароль панели управления при запуске, пока ни один пароль ещё не сохранён; игнорируется, как только пароль сохранён. Установки npm и из исходников копируют `.env.example` в `.env`, поэтому стартуют с его общеизвестным значением-заглушкой `CHANGEME`: замените его перед первым запуском или задайте `INITIAL_PASSWORD=` (пустое) в этом `.env`, чтобы создать пароль в мастере первоначальной настройки при первом визите — так же работает незаданное значение (вариант по умолчанию в Docker-образе). Сохранённый пароль меняется через «Панель управления → Настройки → Безопасность» или командой `omniroute reset-password` (`node bin/reset-password.mjs` в рабочей копии исходников) с `DATA_DIR`, указывающим на каталог данных сервера. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Да** (в production) | _(не задан)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Общий секрет для внутреннего WebSocket-моста Codex Responses. Аутентифицирует запросы моста между WS-ретранслятором Electron/браузера и OmniRoute. ⚠️ **ОБЯЗАТЕЛЕН в production — если значение не задано, все запросы WS-моста отклоняются.** Создайте с помощью `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Нет | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Явный идентификатор сброса кеша service worker для оболочки PWA (#11779); первый в цепочке разрешения. | +| `SOURCE_VERSION` | Нет | _(не задан)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Второй в цепочке — задаётся сборщиками PaaS (например, в стиле Heroku) и содержит развёрнутый коммит. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Нет | _(вычисляется)_ | `src/shared/components/PwaRegister.tsx` | Публичное значение времени сборки, которое клиент использует для регистрации `/sw.js?v=…`; вычисляется из двух указанных выше значений, а затем из git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Нет (автоматически) | _(автоматически при каждом запуске)_ | `src/server/authz/policies/management.ts` | Секрет процесса, подтверждающий, что доверенная метка IP-адреса узла поступила от собственного HTTP-сервера OmniRoute (`scripts/dev/peer-stamp.mjs`). Промежуточное ПО authz доверяет локальности запроса (ограничению маршрутов LOCAL_ONLY адресами loopback/LAN), только если метка содержит этот токен. Автоматически создаётся при каждом запуске — оставьте незаданным; фиксируйте его только для многопроцессных конфигураций, которым необходимо использовать общую метку. | +| `OMNIROUTE_TRUSTED_PROXIES` | Нет | _(не задан)_ | `scripts/dev/peer-stamp.mjs` | Разделённые запятыми IP-адреса или диапазоны CIDR обратных прокси, чьим заголовкам `X-Forwarded-For` / `X-Real-IP` может доверять список разрешённых/запрещённых IP-адресов, в дополнение к адресам loopback, частных сетей и пограничных узлов Cloudflare, которые всегда считаются доверенными. Требуется только для прокси с любым другим публичным адресом; в противном случае фильтр оценивает собственный адрес этого прокси. | ### Команды генерации @@ -216,38 +216,38 @@ OmniRoute использует **SQLite** (через `better-sqlite3`) для ## 4. Безопасность и аутентификация -| Переменная | Значение по умолчанию | Исходный файл | Описание | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Соль, объединяемая с аппаратными идентификаторами для создания цифрового отпечатка машины. Для изоляции задавайте отдельное значение для каждого развертывания. | -| `OMNIROUTE_CLI_SALT` | _(не задано = случайная соль для каждой установки, сохраняемая в `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Соль HMAC для формирования локального токена аутентификации CLI. Установка этого значения приводит к ротации всех токенов CLI на машине и всегда имеет приоритет над сохраненной солью. См. `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Устанавливает флаг `Secure` для cookie-файлов сеанса. При работе через HTTPS значение **обязательно должно быть `true`**. | -| `REQUIRE_API_KEY` | `false` | Промежуточное ПО API | Если задано значение `true`, все прокси-запросы `/v1/*` должны содержать действительный ключ API. **Этот флаг не ограничивает доступ к `GET /v1/models`**: для него вместо этого применяется политика входа в Панель управления (`requireAuthForModels`), поэтому ответ `401` от `/v1/models` НЕ означает, что инференс защищен. См. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Позволяет отображать полные значения ключей API в интерфейсе Панели управления. Настраивается с помощью флагов функций Панели управления; представляет угрозу безопасности в экземплярах с общим доступом. | -| `NO_LOG_API_KEY_IDS` | _(пусто)_ | `src/lib/compliance/index.ts` | Разделенные запятыми идентификаторы ключей API, для которых журналирование запросов отключено (соответствие GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(не задано = без ограничений)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервный дневной бюджет запросов, применяемый к ключам API, у которых столбец `rate_limits` имеет значение null. Не задано или пусто: неявное ограничение отсутствует (#2289, #11017). `0` означает то же самое (без ограничений). Положительное целое число N включает ограничения N/день, 5N/неделю и 20N/месяц. Для некорректных непустых значений используются прежние интервалы: 1000/день, 5000/неделю и 20000/месяц. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 МБ) | `src/shared/middleware/bodySizeGuard.ts` | Максимально допустимый размер тела запроса. Полезные нагрузки, превышающие этот предел, отклоняются. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 КБ) | `src/shared/middleware/chatBodyAdmission.ts` | Фактические тела запросов, размер которых не меньше этого порогового значения, перед разбором JSON получают атомарную локальную для процесса квоту допуска для ресурсоемких запросов (путь BYTE, включая `POST /v1/responses`). Используется тот же механизм пропуска при достаточном запасе ресурсов из [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), что и для структурно сложных запросов; при этом по-прежнему действует ограничение `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), чтобы проблема [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) не повторилась. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 МБ) | `src/shared/middleware/chatBodyAdmission.ts` | Жёсткий лимит для маршрутов чата, применяемый к количеству байтов, прочитанных в ходе ограниченного приёма данных, включая запросы с отсутствующим, недопустимым или недостоверным `Content-Length`; при превышении возвращается `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(не задано — нет ограничения по количеству запросов)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** это устаревшее ограничение по КОЛИЧЕСТВУ запросов теперь применяется только в том случае, если задано явно. Если значение не задано (по умолчанию), допуск тяжеловесных запросов чата вместо этого регулируется `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — автоматически вычисляемым БАЙТОВЫМ бюджетом, размер которого определяется фактическим пределом памяти процесса в рамках **одного процесса** (одной кучи V8). Два перекрывающихся запроса `/v1/responses` объёмом около 750 тыс. токенов аварийно завершают кучи размером около 12 ГиБ (#7849) — это предупреждение о **бюджете памяти**, а не жёсткий продуктовый максимум в 2 запроса. Исправный процесс (куча ниже коэффициента сброса нагрузки) МОЖЕТ принимать больше одновременных длительных запросов `/v1/responses` через `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десятки длительных SSE-клиентов (40–50) ограничиваются кучей + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не «максимумом в 2». Бездумное увеличение этого значения с целью «использовать ресурсы хоста» вновь приводит к #7849. Умножайте количество **куч**, используя **N независимых `DATA_DIR`** (#11024); никогда не используйте `replicas>1` для одного файла SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(вычисляется автоматически)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** переопределение автоматически вычисляемого байтового бюджета приёма данных (25% от меньшего из ограничений памяти V8/cgroup, делённые на коэффициент 8-кратного временного увеличения). Вычисленные и явно заданные значения ограничиваются диапазоном 8 МиБ–2 ГиБ. Если тело превышает действующий бюджет, запрос немедленно завершается с ошибкой `413 body_exceeds_budget`; конкуренция между телами, каждое из которых по отдельности может быть обработано, по-прежнему приводит к допускающей повторную попытку ошибке `503`. 40–50 одновременных длительных SSE-клиентов ограничиваются этим бюджетом + кучей, а не жёстким «максимумом в 2». Перед настройкой проверьте `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` по адресу `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Коэффициент сброса нагрузки при давлении на кучу (`heapUsed / heap_size_limit`) для допуска тяжеловесных запросов категорий BYTE и STRUCTURE (#10183, #10268, #10437). Одновременный тяжеловесный запрос сверх `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` отклоняется с допускающей повторную попытку ошибкой `503`, только если использование кучи ТАКЖЕ достигает или превышает этот коэффициент; при нормальном состоянии кучи он допускается за счёт резерва для исправного состояния. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (по умолчанию `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ограниченная дополнительная ёмкость для быстрого пути при нормальном состоянии кучи (#10437) как для STRUCTURE, так и для BYTE (`admitChatRequest`, включая тела ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без этого ограничения каждый запрос при высокой нагрузке, но нормальном состоянии кучи обходил механизм допуска без какого-либо верхнего предела. Когда через путь нормального состояния кучи активируется указанное количество одновременных разрешений, последующие запросы при высокой нагрузке переходят к ТОМУ ЖЕ пути ограниченного ожидания/сброса нагрузки, который используется при реальном давлении на кучу. Значение `0` полностью отключает обход. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Количество сообщений, при котором запрос чата классифицируется как тяжеловесный, даже если размер его тела ниже байтового порога. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Количество инструментов, при котором запрос чата классифицируется как тяжеловесный, даже если размер его тела ниже байтового порога. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Консервативная оценка количества токенов по размеру строк, при которой запрос классифицируется как тяжеловесный; это приблизительная оценка стоимости допуска, а не токенизация для выставления счетов провайдером. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (отключено) | `src/shared/middleware/chatBodyAdmission.ts` | Необязательное, явно включаемое ограничение истории чата. По умолчанию отключено: количество сообщений является политикой развёртывания, а не универсальным свойством запроса, и такое ограничение отклоняет диалоги с окончательной ошибкой `413` до того, как конвейер сжатия сможет сделать их пригодными для обработки. Рост кучи ограничивается `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` и сбросом нагрузки при давлении на кучу. Задайте положительное значение в развёртываниях с ограниченной памятью, которым необходим жёсткий предел; при его превышении возвращается структурированная ошибка `413`, требующая сжатия. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 МБ) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Жёсткий лимит для непотокового ответа вышестоящего сервиса, полностью буферизуемого в памяти. При его превышении чтение ответа вышестоящего сервиса отменяется, а запрос быстро завершается с ошибкой вместо неограниченного роста строки вплоть до исчерпания кучи. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Максимальный объём заголовков ответа вышестоящего сервера, передаваемых по сети, в байтах. При превышении лимита заголовки с более низким приоритетом (например, пользовательские `x-codex-*`, `x-oai-request-id`) отбрасываются, чтобы не выходить за типичные ограничения обратных прокси на размер заголовков. Установите более высокое значение, чтобы передавать больше метаданных вышестоящего сервера ценой увеличения размера заголовков ответа. | -| `CORS_ORIGIN` | _(не задано)_ | `src/server/cors/origins.ts` | Устаревший список разрешённых источников CORS, поддерживающий один источник. Для новых развёртываний рекомендуется использовать `CORS_ALLOWED_ORIGINS`. CORS предназначен только для браузерных API-клиентов, выполняющих межсайтовые запросы; аутентифицированные операции записи в панели управления вместо этого используют запросы из того же источника и привязанную к сеансу защиту от CSRF. | -| `CORS_ALLOWED_ORIGINS` | _(не задано)_ | `src/server/cors/origins.ts` | Разделённый запятыми список разрешённых источников CORS. Подстановочный знак не отправляется, если только явно не настроено `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Предназначенный только для разработки аварийный механизм, позволяющий возвращать любой браузерный заголовок `Origin`. Не включайте его в общих или производственных развёртываниях. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Блокирует обращения к поставщикам, нацеленные на частные, loopback- или link-local-диапазоны IP-адресов. Отключайте только в изолированных тестовых средах. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Разрешает URL-адреса поставщиков, указывающие на частные или локальные сети (localhost, 192.168.x.x, 10.x.x.x и т. д.). **НЕОБХОДИМО для самостоятельно размещаемых поставщиков** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). При значении `false` панель управления отклоняет проверку локальных URL-адресов. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Разрешает добавление и проверку поставщиков по локальным или частным адресам (127.0.0.1, localhost, LAN, частные диапазоны) — применяется только к пути проверки поставщика. **Значение по умолчанию — `true`** (приоритет локальной среды); установите `false`, чтобы применять строгую блокировку всех адресов, кроме общедоступных. Конечные точки облачных метаданных (169.254.169.254, metadata.google.internal) остаются заблокированными независимо от настройки. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Позволяет маршрутам `/v1/audio/*` (транскрипция, речь, перевод) использовать совместимый с OpenAI узел поставщика, размещённый вне localhost. По умолчанию отключено — маршрутизация аудио на удалённый хост изменяет идентификатор исходящего подключения и должна быть явным решением оператора. Loopback-узлы и узлы в частных сетях (localhost, 127.0.0.1, 172.16-31.x) разрешены всегда и не зависят от этой настройки. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Позволяет `POST /v1/rerank` (а также loopback-этапу повторного ранжирования механизма памяти) использовать совместимый с OpenAI узел поставщика, размещённый вне localhost, — компьютер в LAN или пир Tailscale, на котором запущен TEI, Infinity, vLLM и т. д. По умолчанию отключено — маршрутизация на удалённый хост изменяет идентификатор исходящего подключения и должна быть явным решением оператора. Loopback-узлы (localhost, 127.0.0.1, 172.16-31.x) разрешены всегда и не зависят от этой настройки. Удалённые узлы также должны соответствовать политике исходящих URL-адресов поставщиков (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); маршрутизация на хосты облачных метаданных никогда не выполняется. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Когда OIDC включён, отключает вход по паролю, чтобы пользователи могли проходить аутентификацию только через единый вход OIDC. Также поддерживается краткий псевдоним `OIDC_DISABLE_PASSWORD_LOGIN`; флаг функции панели управления с тем же ключом имеет приоритет. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Краткий псевдоним `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Переменная | Значение по умолчанию | Исходный файл | Описание | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Соль, объединяемая с аппаратными идентификаторами для создания цифрового отпечатка машины. Для изоляции задавайте отдельное значение для каждого развертывания. | +| `OMNIROUTE_CLI_SALT` | _(не задано = случайная соль для каждой установки, сохраняемая в `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Соль HMAC для формирования локального токена аутентификации CLI. Установка этого значения приводит к ротации всех токенов CLI на машине и всегда имеет приоритет над сохраненной солью. См. `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Устанавливает флаг `Secure` для cookie-файлов сеанса. При работе через HTTPS значение **обязательно должно быть `true`**. | +| `REQUIRE_API_KEY` | `false` | Промежуточное ПО API | Если задано значение `true`, все прокси-запросы `/v1/*` должны содержать действительный ключ API. **Этот флаг не ограничивает доступ к `GET /v1/models`**: для него вместо этого применяется политика входа в Панель управления (`requireAuthForModels`), поэтому ответ `401` от `/v1/models` НЕ означает, что инференс защищен. См. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Позволяет отображать полные значения ключей API в интерфейсе Панели управления. Настраивается с помощью флагов функций Панели управления; представляет угрозу безопасности в экземплярах с общим доступом. | +| `NO_LOG_API_KEY_IDS` | _(пусто)_ | `src/lib/compliance/index.ts` | Разделенные запятыми идентификаторы ключей API, для которых журналирование запросов отключено (соответствие GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(не задано = без ограничений)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервный дневной бюджет запросов, применяемый к ключам API, у которых столбец `rate_limits` имеет значение null. Не задано или пусто: неявное ограничение отсутствует (#2289, #11017). `0` означает то же самое (без ограничений). Положительное целое число N включает ограничения N/день, 5N/неделю и 20N/месяц. Для некорректных непустых значений используются прежние интервалы: 1000/день, 5000/неделю и 20000/месяц. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 МБ) | `src/shared/middleware/bodySizeGuard.ts` | Максимально допустимый размер тела запроса. Полезные нагрузки, превышающие этот предел, отклоняются. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 КБ) | `src/shared/middleware/chatBodyAdmission.ts` | Фактические тела запросов, размер которых не меньше этого порогового значения, перед разбором JSON получают атомарную локальную для процесса квоту допуска для ресурсоемких запросов (путь BYTE, включая `POST /v1/responses`). Используется тот же механизм пропуска при достаточном запасе ресурсов из [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), что и для структурно сложных запросов; при этом по-прежнему действует ограничение `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), чтобы проблема [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) не повторилась. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 МБ) | `src/shared/middleware/chatBodyAdmission.ts` | Жёсткий лимит для маршрутов чата, применяемый к количеству байтов, прочитанных в ходе ограниченного приёма данных, включая запросы с отсутствующим, недопустимым или недостоверным `Content-Length`; при превышении возвращается `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(не задано — нет ограничения по количеству запросов)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** это устаревшее ограничение по КОЛИЧЕСТВУ запросов теперь применяется только в том случае, если задано явно. Если значение не задано (по умолчанию), допуск тяжеловесных запросов чата вместо этого регулируется `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — автоматически вычисляемым БАЙТОВЫМ бюджетом, размер которого определяется фактическим пределом памяти процесса в рамках **одного процесса** (одной кучи V8). Два перекрывающихся запроса `/v1/responses` объёмом около 750 тыс. токенов аварийно завершают кучи размером около 12 ГиБ (#7849) — это предупреждение о **бюджете памяти**, а не жёсткий продуктовый максимум в 2 запроса. Исправный процесс (куча ниже коэффициента сброса нагрузки) МОЖЕТ принимать больше одновременных длительных запросов `/v1/responses` через `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десятки длительных SSE-клиентов (40–50) ограничиваются кучей + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не «максимумом в 2». Бездумное увеличение этого значения с целью «использовать ресурсы хоста» вновь приводит к #7849. Умножайте количество **куч**, используя **N независимых `DATA_DIR`** (#11024); никогда не используйте `replicas>1` для одного файла SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(вычисляется автоматически)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** переопределение автоматически вычисляемого байтового бюджета приёма данных (25% от меньшего из ограничений памяти V8/cgroup, делённые на коэффициент 8-кратного временного увеличения). Вычисленные и явно заданные значения ограничиваются диапазоном 8 МиБ–2 ГиБ. Если тело превышает действующий бюджет, запрос немедленно завершается с ошибкой `413 body_exceeds_budget`; конкуренция между телами, каждое из которых по отдельности может быть обработано, по-прежнему приводит к допускающей повторную попытку ошибке `503`. 40–50 одновременных длительных SSE-клиентов ограничиваются этим бюджетом + кучей, а не жёстким «максимумом в 2». Перед настройкой проверьте `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` по адресу `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Коэффициент сброса нагрузки при давлении на кучу (`heapUsed / heap_size_limit`) для допуска тяжеловесных запросов категорий BYTE и STRUCTURE (#10183, #10268, #10437). Одновременный тяжеловесный запрос сверх `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` отклоняется с допускающей повторную попытку ошибкой `503`, только если использование кучи ТАКЖЕ достигает или превышает этот коэффициент; при нормальном состоянии кучи он допускается за счёт резерва для исправного состояния. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (по умолчанию `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ограниченная дополнительная ёмкость для быстрого пути при нормальном состоянии кучи (#10437) как для STRUCTURE, так и для BYTE (`admitChatRequest`, включая тела ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без этого ограничения каждый запрос при высокой нагрузке, но нормальном состоянии кучи обходил механизм допуска без какого-либо верхнего предела. Когда через путь нормального состояния кучи активируется указанное количество одновременных разрешений, последующие запросы при высокой нагрузке переходят к ТОМУ ЖЕ пути ограниченного ожидания/сброса нагрузки, который используется при реальном давлении на кучу. Значение `0` полностью отключает обход. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Количество сообщений, при котором запрос чата классифицируется как тяжеловесный, даже если размер его тела ниже байтового порога. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Количество инструментов, при котором запрос чата классифицируется как тяжеловесный, даже если размер его тела ниже байтового порога. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Консервативная оценка количества токенов по размеру строк, при которой запрос классифицируется как тяжеловесный; это приблизительная оценка стоимости допуска, а не токенизация для выставления счетов провайдером. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (отключено) | `src/shared/middleware/chatBodyAdmission.ts` | Необязательное, явно включаемое ограничение истории чата. По умолчанию отключено: количество сообщений является политикой развёртывания, а не универсальным свойством запроса, и такое ограничение отклоняет диалоги с окончательной ошибкой `413` до того, как конвейер сжатия сможет сделать их пригодными для обработки. Рост кучи ограничивается `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` и сбросом нагрузки при давлении на кучу. Задайте положительное значение в развёртываниях с ограниченной памятью, которым необходим жёсткий предел; при его превышении возвращается структурированная ошибка `413`, требующая сжатия. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 МБ) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Жёсткий лимит для непотокового ответа вышестоящего сервиса, полностью буферизуемого в памяти. При его превышении чтение ответа вышестоящего сервиса отменяется, а запрос быстро завершается с ошибкой вместо неограниченного роста строки вплоть до исчерпания кучи. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Максимальный объём заголовков ответа вышестоящего сервера, передаваемых по сети, в байтах. При превышении лимита заголовки с более низким приоритетом (например, пользовательские `x-codex-*`, `x-oai-request-id`) отбрасываются, чтобы не выходить за типичные ограничения обратных прокси на размер заголовков. Установите более высокое значение, чтобы передавать больше метаданных вышестоящего сервера ценой увеличения размера заголовков ответа. | +| `CORS_ORIGIN` | _(не задано)_ | `src/server/cors/origins.ts` | Устаревший список разрешённых источников CORS, поддерживающий один источник. Для новых развёртываний рекомендуется использовать `CORS_ALLOWED_ORIGINS`. CORS предназначен только для браузерных API-клиентов, выполняющих межсайтовые запросы; аутентифицированные операции записи в панели управления вместо этого используют запросы из того же источника и привязанную к сеансу защиту от CSRF. | +| `CORS_ALLOWED_ORIGINS` | _(не задано)_ | `src/server/cors/origins.ts` | Разделённый запятыми список разрешённых источников CORS. Подстановочный знак не отправляется, если только явно не настроено `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Предназначенный только для разработки аварийный механизм, позволяющий возвращать любой браузерный заголовок `Origin`. Не включайте его в общих или производственных развёртываниях. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Устаревший псевдоним. Его переключатель в панели управления (переопределение в базе данных) читается раньше значения этой переменной окружения; значение `false`, `0`, `no` или `off` в любом из них отключает проверки хостов так же, как это делает `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, даже если переключатель этого флага в панели сохранён выключенным; остальные значения оставляют защиту на усмотрение двух флагов ниже. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Отключает проверки хостов защиты исходящих URL-адресов, включая блокировку облачных метаданных (весь диапазон 169.254.0.0/16, metadata.google.internal), на путях поставщика, подчиняющихся режиму защиты: проверка URL-адресов, обнаружение моделей, базовые URL-адреса узлов поставщиков (включая удалённые узлы повторного ранжирования) и тест proxy-fallback, а также на других исходящих путях, смежных с поставщиками, например удалённая загрузка изображений и медиа. Также разрешает частные адреса вебхуков, кроме конечных точек облачных метаданных/link-local, которые остаются заблокированными даже при включённом флаге. Запросы чата, отправляемые через `BaseExecutor.execute()`, сохраняют свою проверку облачных метаданных. Локальные поставщики работают со значениями по умолчанию: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (по умолчанию `true`) уже разрешает локальные URL-адреса и адреса LAN на путях проверки, обнаружения и узлов поставщиков; тест proxy-fallback и частные адреса вебхуков консультируются только с этим флагом и блокируют локальные/LAN-хосты, пока он выключен. Переопределяет `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Значение, сохранённое переключателем в панели управления, имеет приоритет над этой переменной. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Разрешает URL-адреса поставщиков по локальным или частным адресам (127.0.0.1, localhost, LAN, частные диапазоны) для проверки поставщиков, обнаружения моделей, чата и остальных смежных с поставщиками исходящих путей, которыми управляет режим защиты. **Значение по умолчанию — `true`** (приоритет локальной среды): защита при этом блокирует конечные точки облачных метаданных — весь диапазон 169.254.0.0/16 плюс известные имена хостов метаданных (metadata.google.internal, metadata.goog, IMDS Alibaba/Azure/Oracle, fd00:ec2::254). Установите `false` для режима «только общедоступные адреса», который блокирует также частные и loopback-хосты. Защита проверяет имя хоста или IP-литерал в том виде, в каком он записан, до какого-либо DNS-запроса; в режиме «только общедоступные адреса» обёртка безопасной загрузки дополнительно разрешает имя хоста через DNS и отклоняет любой частный ответ. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` отключает эти проверки на путях, перечисленных в строке этого флага. Встроенные локальные поставщики — LM Studio, Ollama, vLLM, Llamafile и Triton — пропускают все проверки хостов при проверке ключей и чате. Значение, сохранённое переключателем в панели управления, имеет приоритет над этой переменной. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Позволяет маршрутам `/v1/audio/*` (транскрипция, речь, перевод) использовать совместимый с OpenAI узел поставщика, размещённый вне localhost. По умолчанию отключено — маршрутизация аудио на удалённый хост изменяет идентификатор исходящего подключения и должна быть явным решением оператора. Loopback-узлы и узлы в частных сетях (localhost, 127.0.0.1, 172.16-31.x) разрешены всегда и не зависят от этой настройки. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Позволяет `POST /v1/rerank` (а также loopback-этапу повторного ранжирования механизма памяти) использовать совместимый с OpenAI узел поставщика, размещённый вне localhost, — компьютер в LAN или пир Tailscale, на котором запущен TEI, Infinity, vLLM и т. д. По умолчанию отключено — маршрутизация на удалённый хост изменяет идентификатор исходящего подключения и должна быть явным решением оператора. Loopback-узлы (localhost, 127.0.0.1, 172.16-31.x) разрешены всегда и не зависят от этой настройки. Базовый URL-адрес удалённого узла также должен соответствовать политике исходящих URL-адресов поставщиков (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Когда OIDC включён, отключает вход по паролю, чтобы пользователи могли проходить аутентификацию только через единый вход OIDC. Также поддерживается краткий псевдоним `OIDC_DISABLE_PASSWORD_LOGIN`; флаг функции панели управления с тем же ключом имеет приоритет. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Краткий псевдоним `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Контрольный список усиления защиты diff --git a/docs/i18n/si/CONTRIBUTING.md b/docs/i18n/si/CONTRIBUTING.md index e65bf5642cae..5ca128a48710 100644 --- a/docs/i18n/si/CONTRIBUTING.md +++ b/docs/i18n/si/CONTRIBUTING.md @@ -402,19 +402,19 @@ docs/ ## Pull Request පිරික්සුම් ලැයිස්තුව - [ ] පරීක්ෂණ සමත් වේ (`npm test`) -- [ ] ලින්ට් කිරීම සමත් වේ (`npm run lint`) -- [ ] ගොඩනැගීම සාර්ථක වේ (`npm run build`) -- [ ] නව පොදු ශ්රිත සහ අතුරුමුහුණත් සඳහා TypeScript වර්ග එක් කර ඇත -- [ ] දෘඪ-කේතනය කළ රහස් හෝ පෙරනිමි විකල්ප අගයන් නොමැත -- [ ] පොදු ඉහළ මට්ටමේ අක්තපත්ර `resolvePublicCred()` හරහා කේතයට ඇතුළත් කර ඇත ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) බලන්න), කිසිවිටෙක ලිටරල ලෙස නොවේ -- [ ] දෝෂ ප්රතිචාර `buildErrorBody()` / `sanitizeErrorMessage()` හරහා යොමු වේ — ප්රතිචාර බොඩි තුළ අමු ස්ටැක් ට්රේස් නොමැත ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) බලන්න) -- [ ] Shell විධාන (`exec` / `spawn`) ධාවනකාල අගයන් තන්තු අන්තර්නිවේෂණය හරහා නොව `env` හරහා ලබා දෙයි -- [ ] සියලු ආදාන Zod ස්කීමා මඟින් වලංගු කර ඇත -- [ ] පරිශීලකයාට පෙනෙන වෙනස්කම් සඳහා `changelog.d/{features|fixes|maintenance}/-.md` යටතේ වෙනස්කම් ලොග් **ඛණ්ඩයක්** එක් කර ඇත ([`changelog.d/README.md`](./changelog.d/README.md) බලන්න) — `CHANGELOG.md` සෘජුවම සංස්කරණය **නොකරන්න**; නිකුත් කිරීමේදී ඛණ්ඩ එක්රැස් කෙරෙන අතර ඒවා PR අතර කිසිවිටෙක ගැටුම් ඇති නොකරයි -- [ ] ලේඛන යාවත්කාලීන කර ඇත (අදාළ නම්) -- [ ] නව CodeQL / Secret-Scanning අනතුරු ඇඟවීම් විවෘත කර නැත, නැතහොත් සෑම එකක්ම අදාළ `docs/security/` ලේඛනය සඳහන් කරන තාක්ෂණික සාධාරණීකරණයක් සමඟ ප්රතික්ෂේප කර ඇත -- [ ] උප ක්රියාවලි ආරම්භ කරන මාර්ග (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` තුළ `isLocalOnlyPath()` ලෙස වර්ගීකරණය කර ඇත — [දැඩි රීතිය #15](docs/security/ROUTE_GUARD_TIERS.md) බලන්න -- [ ] commit පණිවිඩ තුළ `Co-Authored-By` ට්රේලර් නොමැත — commits ගබඩා හිමිකරුගේ Git අනන්යතාව යටතේ පමණක් දිස් විය යුතුය (දැඩි රීතිය #16) +- [ ] Linting සමත් වේ (`npm run lint`) +- [ ] Build කිරීම සාර්ථක වේ (`npm run build`) +- [ ] නව public functions සහ interfaces සඳහා TypeScript types එක් කර ඇත +- [ ] Hardcode කළ secrets හෝ fallback values නොමැත +- [ ] Public upstream credentials, literals ලෙස නොව `resolvePublicCred()` හරහා embed කර ඇත ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) බලන්න) +- [ ] Error responses, `buildErrorBody()` / `sanitizeErrorMessage()` හරහා යොමු වේ — response bodies තුළ raw stack traces නොමැත ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) බලන්න) +- [ ] Shell commands (`exec` / `spawn`), string interpolation හරහා නොව `env` හරහා runtime values ලබා දෙයි +- [ ] සියලු inputs, Zod schemas සමඟ validate කර ඇත +- [ ] පරිශීලකයාට පෙනෙන වෙනස්කම් සඳහා `changelog.d/{features|fixes|maintenance}/-.md` යටතේ Changelog **fragment** එකක් එක් කර ඇත ([`changelog.d/README.md`](./changelog.d/README.md) බලන්න) — `CHANGELOG.md` සෘජුව සංස්කරණය **නොකරන්න**; release අවස්ථාවේදී fragments එකතු කෙරෙන අතර PR අතර කිසිවිටෙක ගැටුම් ඇති නොවේ +- [ ] Documentation යාවත්කාලීන කර ඇත (අදාළ නම්) +- [ ] නව CodeQL / Secret-Scanning alerts කිසිවක් විවෘත කර නැත, නැතහොත් අදාළ `docs/security/` ලේඛනය සඳහන් කරන තාක්ෂණික සාධාරණීකරණයක් සමඟ ඒ සෑම එකක්ම ඉවත දමා ඇත +- [ ] Child processes ආරම්භ කරන routes (`/api/mcp/`, `/api/cli-tools/runtime/`), `src/server/authz/routeGuard.ts` තුළ `isLocalOnlyPath()` ලෙස වර්ගීකරණය කර ඇත — [දැඩි රීතිය #15](docs/security/ROUTE_GUARD_TIERS.md) බලන්න +- [ ] Commit messages තුළ AI/bot `Co-authored-by` trailers නොමැත (දැඩි රීතිය #16) — නැවත භාවිත කරන ලද කාර්යයේ මානව සහයෝගිතාකරුවන්ට සම්මත `Co-authored-by: Name ` trailers මඟින් ගෞරවය ලබා දී ඇත --- diff --git a/docs/i18n/si/docs/architecture/QUALITY_GATES.md b/docs/i18n/si/docs/architecture/QUALITY_GATES.md index a0b5c5e877e6..e0fbb8971b8f 100644 --- a/docs/i18n/si/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/si/docs/architecture/QUALITY_GATES.md @@ -17,261 +17,255 @@ ratchet මූලික රේඛාවක් හෝ සමත්/අසමත ## ගේට් ඉන්වෙන්ටරිය සහ ක්රියාත්මක කිරීමේ පැතිකඩ -### අපේක්ෂක පිළිගැනීම - -CI සහ Quality Gates වැඩප්රවාහ දෙකම ස්ථාවර තීන්දුවක් නිකුත් කරයි: `Gate / CI` සහ -`Gate / Quality`. ඒවායේ අනුවාදගත පිළිගැනීමේ ප්රතිපත්තිය සෑම ඉහළ-ප්රවාහ කාර්යයක්ම -අනිවාර්ය හෝ උපදේශනාත්මක ලෙස ලැයිස්තුගත කරයි. අදාළ වන අනිවාර්ය කාර්යයක් සාර්ථක විය යුතුය: -නොමැති, අවලංගු කළ, මඟහැරුණු, පොරොත්තු සහ නොදන්නා ප්රතිඵල මඟින් PASS තහවුරු කළ නොහැක. -වලංගු docs-only හෝ catalog-only වර්ගීකරණයක් මඟින් කේත මංතීරුවක් අදාළ නොවන බවට පත් කළ -හැකිය; කෙටුම්පත් PR එකක් පිළිගත් අපේක්ෂකයක් නොවේ. `hotfix` ලේබලයක් සාක්ෂි අවශ්යතාව -ඉවත් නොකරයි. - -වැඩප්රවාහ දෙකම PR, main/release ශාඛා වෙත pushes, අතින් dispatch කිරීම සහ -merge-group සිදුවීම් ආවරණය කරයි. Push, dispatch සහ merge-group සම්පූර්ණ තේරීම ක්රියාත්මක -කරයි. සාමාන්යයෙන් self-hosted runners තෝරන කාර්ය සඳහා forks සහ merge groups විසින් -hosted runners භාවිත කරයි; rollout කිරීමට පෙර ප්රමාණවත් hosted ධාරිතාවක් ඇති බව තහවුරු -කළ යුතුය. - -සෑම JSON ලදුපතක්ම check out කළ SHA, වැඩප්රවාහ ධාවනය සහ උත්සාහය හඳුනා දෙයි. -CLI එක checkout/event SHA නොගැළපීමක් ප්රතික්ෂේප කරයි. නව හෝ ඉවත් කළ මංතීරුවක් නිහඬව -අතුරුදහන් වීමට නොහැකි වන පරිදි, වැඩප්රවාහ පරීක්ෂණ මඟින් ප්රතිපත්ති සාමාජිකත්වය තීන්දු -කාර්යයේ `needs` ලැයිස්තුවට බැඳ තබයි. ලදුපත් ආවරණය කරන්නේ ඒවාට අදාළ වැඩප්රවාහය පමණි; -ප්රකාශනය, යෙදවීම හෝ පවතින උපදේශනාත්මක scanner එකක අභ්යන්තර ක්රියාකාරීත්වය නොවේ. -ශාඛා නීති තුළ check නම් දෙකම සක්රිය කිරීම වෙනම පරිපාලන වෙනස්කමකි; මෙම කාර්ය එකතු -කිරීමෙන් පමණක් ශාඛාවක් ආරක්ෂා නොවේ. +### අපේක්ෂක ඇතුළත් කිරීම + +CI සහ Quality Gates කාර්ය ප්රවාහ දෙකම ස්ථාවර තීන්දුවක් නිකුත් කරයි: `Gate / CI` සහ +`Gate / Quality`. ඒවායේ අනුවාදගත ඇතුළත් කිරීමේ ප්රතිපත්තිය සෑම උඩුගං කාර්යයක්ම +අනිවාර්ය හෝ උපදේශාත්මක ලෙස ලැයිස්තුගත කරයි. අදාළ වන අනිවාර්ය කාර්යයක් සාර්ථක විය යුතුය: නොමැති, +අවලංගු කළ, මඟහැරුණු, අපේක්ෂිත සහ නොදන්නා ප්රතිඵලවලට PASS තහවුරු කළ නොහැක. වලංගු +docs-only හෝ catalog-only වර්ගීකරණයකට කේත මංතීරුවක් අදාළ නොවන බවට පත් කළ හැක; +කෙටුම්පත් PR එකක් පිළිගත් අපේක්ෂකයෙකු නොවේ. `hotfix` ලේබලයක් සාක්ෂි අවශ්යතාව ඉවත් නොකරයි. + +කාර්ය ප්රවාහ දෙකම PR, main/release ශාඛා වෙත push කිරීම්, අතින් dispatch කිරීම් සහ +merge-group සිදුවීම් ආවරණය කරයි. Push, dispatch සහ merge-group සම්පූර්ණ තේරීම ක්රියාත්මක කරයි. Fork සහ +merge group, වෙනත් අවස්ථාවල self-hosted runner තෝරාගන්නා කාර්ය සඳහා hosted runner භාවිත කරයි; +නිකුත් කිරීමට පෙර ප්රමාණවත් hosted ධාරිතාව සත්යාපනය කළ යුතුය. + +සෑම JSON ලදුපතක්ම checkout කළ SHA, workflow run සහ attempt හඳුනාගනී. +CLI මඟින් checkout/event SHA නොගැළපීමක් ප්රතික්ෂේප කරයි. නව හෝ ඉවත් කළ මංතීරුවක් නිහඬව අතුරුදහන් වීමට නොහැකි වන පරිදි, +workflow පරීක්ෂණ මඟින් ප්රතිපත්ති සාමාජිකත්වය තීන්දුව ලබාදෙන කාර්යයේ `needs` ලැයිස්තුවට බැඳ තබයි. +ලදුපත් මඟින් ආවරණය කරන්නේ ඒවායේම කාර්ය ප්රවාහය මිස ප්රකාශනය, යෙදවීම හෝ +පවතින උපදේශාත්මක scanner එකක අභ්යන්තර ක්රියාකාරීත්වය නොවේ. ශාඛා නීති තුළ check නාම දෙකම සක්රිය කිරීම +වෙනම පරිපාලන වෙනසකි; මෙම කාර්ය එක් කිරීමෙන් පමණක් ශාඛාවක් ආරක්ෂා නොවේ. ### ස්ථිතික scan ඉන්වෙන්ටරිය අනුවාදගත npm-alias ඉන්වෙන්ටරිය සහ static-scan සාමාජිකත්වය -`config/quality/gate-manifest.json` තුළ ඇත. script නම් සහ නිශ්චිත command -`package.json` සමඟ සසඳා වලංගු කිරීමට `npm run check:gate-manifest` ධාවනය කරන්න; -එක් කිරීම්, ඉවත් කිරීම් සහ command වෙනස්වීම් නිසා local hook එක සහ CI තුළ ඇති -වෙනස්කම්-වර්ගීකරණ කාර්ය දෙකම අසාර්ථක වේ. alias එකක් යනු workflow job එකක්, matrix -instance එකක් හෝ test case එකක් නොවේ: මෙම ගණන් එකිනෙකට හුවමාරු කළ හැකි ලෙස ඉදිරිපත් -නොකළ යුතුය. - -තෝරාගත් aliases ක්රියාත්මක නොකර පරීක්ෂා කිරීමට `npm run quality:scan -- --list` හෝ -`npm run quality:scan:fast -- --list` භාවිත කරන්න. runner එක npm entrypoint එක කැඳවන -බැවින්, එහි runtime එක (වින්යාස කර ඇති තැන්වල Bun ද ඇතුළුව) නොවෙනස්ව පවතී. -manifest එක මෙම පැතිකඩවලින් පිටත aliases වෙනම කැඳවන ඒවා ලෙස සටහන් කරන අතර, read-only -scan පැතිකඩ තුළ maintenance commands තහනම් වේ. - -මෙම පැතිකඩ ආවරණය කරන්නේ static scan එක පමණි. ඒවා product tests, coverage, packaging, -external checks හෝ අපේක්ෂකයකුගේ සම්පූර්ණ release acceptance සහතික නොකරයි. -Workflow admission සඳහා සම්බන්ධිත `config/quality/admission-policy.json` සහ -`scripts/quality/admission-verdict.mjs` භාවිත වේ. Release-observer පැතිකඩ වෙනමම පවතී; -ඒවාට අදාළ checks සහ receipts ස්වාධීනව පරීක්ෂා කරන්න. පහත ගද්ය -ඉන්වෙන්ටරිය යොමුවක් පමණක් වන අතර, gate එකක් සැබවින්ම ධාවනය වූ බවට සාක්ෂියක් නොවේ. - -Scripts පිහිටා ඇත්තේ `scripts/check/` (policy gates) සහ `scripts/quality/` (ratchet engine) -යටතේය. CI සඳහා සත්යයේ මූලාශ්රය `.github/workflows/ci.yml` වේ. +`config/quality/gate-manifest.json` තුළ ඇත. script නාම සහ නිශ්චිත command +`package.json` සමඟ සසඳා වලංගු කිරීමට `npm run check:gate-manifest` ක්රියාත්මක කරන්න; එක් කිරීම්, ඉවත් කිරීම් සහ +command වෙනස්වීම් හේතුවෙන් local hook එක සහ CI තුළ change-classification කාර්ය යන දෙකම අසාර්ථක වේ. +Alias එකක් workflow job එකක්, matrix instance එකක් හෝ test case එකක් නොවේ: මෙම ගණන් +එකිනෙකට හුවමාරු කළ හැකි ලෙස ඉදිරිපත් නොකළ යුතුය. + +තෝරාගත් alias ක්රියාත්මක නොකර පරීක්ෂා කිරීමට `npm run quality:scan -- --list` හෝ `npm run quality:scan:fast -- --list` +භාවිත කරන්න. Runner එක npm entrypoint එක ක්රියාත්මක කරන බැවින්, +එහි runtime එක (වින්යාස කර ඇති තැන්වල Bun ද ඇතුළුව) සුරැකේ. +මෙම පැතිකඩවලට පිටත ඇති alias වෙනම ක්රියාත්මක වන ඒවා ලෙස manifest එකේ සටහන් කර ඇති අතර, +read-only scan පැතිකඩ තුළ maintenance command තහනම්ය. + +මෙම පැතිකඩ ආවරණය කරන්නේ static scan එක පමණි. ඒවා product test, +coverage, packaging, external check හෝ අපේක්ෂකයෙකුගේ සම්පූර්ණ release acceptance සහතික නොකරයි. +Workflow admission සඳහා සබැඳි `config/quality/admission-policy.json` සහ +`scripts/quality/admission-verdict.mjs` භාවිත කරයි. Release-observer පැතිකඩ වෙනම පවතී; +ඒවාට අදාළ check සහ receipt ස්වාධීනව පරීක්ෂා කරන්න. පහත ඇති විස්තරාත්මක +ඉන්වෙන්ටරිය යොමුවක් මිස gate එකක් සැබවින්ම ක්රියාත්මක වූ බවට සාක්ෂියක් නොවේ. + +Script පිහිටා ඇත්තේ `scripts/check/` (ප්රතිපත්ති gate) සහ `scripts/quality/` (ratchet engine) යටතේය. +CI සඳහා සත්යයේ මූලාශ්රය `.github/workflows/ci.yml` වේ. ### Release PR වේගවත් මාර්ගය (`quality.yml`) -`.github/workflows/quality.yml` මඟින් main/release PR, ආරක්ෂිත-ශාඛා pushes, dispatch සහ -merge groups සඳහා CI සම්පූර්ණ කරයි. PR මඟින් මාර්ග-පෙරහන් කළ වේගවත් checks භාවිත කරයි. -ස්ථිරව අක්රිය කර තිබූ අනුපිටපත් build එක ඉවත් කර ඇත; සැබෑ build/package/boot checks -CI තුළ දිගටම පවතී. - -| කාර්යය | විෂය පථය | අවහිරකාරී | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -| `Docs Gates (fast-path)` | Docs/code PR; API docs refs සහ docs-all | ඔව් | -| `Fast Quality Gates` | Code PR; static checks, typecheck, dashboard typecheck, බලපෑමට ලක් වූ unit tests | ඔව් | -| `Forgotten sibling tests` | Code PR; වෙනස් කළ modules, static consumers සහ අපේක්ෂිත sibling tests වෙත හඹා යයි; barrel සහ dynamic-import මාර්ග, යොමු කළ allowlist ව්යතිරේක සමඟ උපදේශනාත්මක diagnostics ලෙස වාර්තා කෙරේ | **උපදේශනාත්මක** | -| `Vitest (fast-path)` | Code PR; වේගවත් vitest suite | ඔව් | -| `Unit Tests fast-path` | Code PR; 4-shard unit suite | ඔව් | -| `No new ESLint warnings` | Code PR; suppressions-aware lint guard | ඔව්, forks ද ඇතුළුව | -| `Merge integrity (changelog + generated skills)` | කෙටුම්පත් නොවන PR; changelog සහ generated skill සමමුහුර්තකරණය | ඔව්, forks ද ඇතුළුව | - -#### අමතක වූ sibling tests වාර්තාව - -`npm run check:forgotten-sibling-tests` test-impact map එක පිටුපස ඇති import resolver එක -නැවත භාවිත කරයි. වෙනස් කළ සෑම production module එකක් සඳහාම, අපේක්ෂිත -test එක pull-request diff තුළ නොමැති විට එය නිර්ණායක -`changed module/symbol -> static consumer -> candidate sibling test` දාම වාර්තා කරයි. -කිසියම් blocking rollout එකකට පෙර calibration කිරීම සඳහා Markdown සාරාංශය සහ JSON -ප්රතිඵලය `forgotten-sibling-tests` workflow artifact එක ලෙස රඳවා තබා ගනී. - -Barrel නැවත අපනයන සහ ගතික ආයාත විභේදන රෝගනිර්ණ සඳහා පමණි; ඒවා කිසි විටෙකත් +`.github/workflows/quality.yml` මඟින් main/release PR, ආරක්ෂිත ශාඛා වෙත +push කිරීම්, dispatch සහ merge group සඳහා CI පූරණය කරයි. PR මඟින් path-filtered වේගවත් check භාවිත කරයි. ස්ථිරව +අක්රිය කළ අනුපිටපත් build එක ඉවත් කර ඇත; සැබෑ build/package/boot check CI තුළ පවතී. + +| කාර්යය | විෂය පථය | අවහිරකාරී | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | +| `Docs Gates (fast-path)` | Docs/code PR; API docs යොමු සහ docs-all | ඔව් | +| `Fast Quality Gates` | Code PR; static check, typecheck, dashboard typecheck, බලපෑමට ලක්වූ unit test | ඔව් | +| `Forgotten sibling tests` | Code PR; වෙනස් කළ module static consumer සහ අපේක්ෂිත sibling test වෙත අනුරේඛනය කරයි; barrel සහ dynamic-import මාර්ග, යොමු කළ allowlist ව්යතිරේක සමඟ උපදේශාත්මක diagnostics ලෙස වාර්තා කරයි | **උපදේශාත්මකයි** | +| `Vitest (fast-path)` | Code PR; වේගවත් vitest කට්ටලය | ඔව් | +| `Unit Tests fast-path` | Code PR; shard 4ක unit කට්ටලය | ඔව් | +| `No new ESLint warnings` | Code PR; suppression පිළිබඳව සැලකිලිමත් වන lint guard | ඔව්, fork ද ඇතුළුව | +| `Merge integrity (changelog + generated skills)` | කෙටුම්පත් නොවන PR; changelog සහ ජනනය කළ skill සමමුහුර්තකරණය | ඔව්, fork ද ඇතුළුව | + +#### අමතක වූ sibling test වාර්තාව + +`npm run check:forgotten-sibling-tests` මඟින් test-impact map එක පිටුපස ඇති import resolver නැවත භාවිත කරයි. +වෙනස් කළ සෑම production module එකක් සඳහාම, අපේක්ෂිත +test එක pull-request diff එකේ නොමැති විට එය නියතික +`changed module/symbol -> static consumer -> candidate sibling test` දාම වාර්තා කරයි. අවහිරකාරී rollout එකකට පෙර ක්රමාංකනය සඳහා Markdown සාරාංශය සහ JSON ප්රතිඵලය +`forgotten-sibling-tests` workflow artifact එක ලෙස රඳවා තබයි. + +Barrel නැවත-අපනයන සහ ගතික ආයාත විභේදන රෝගනිශ්චය සඳහා පමණි; ඒවා කිසිවිටෙකත් අවහිර කරන සොයාගැනීමක් නිර්මාණය නොකරයි. සමාලෝචනය කළ ව්යතිරේක -`config/quality/forgotten-sibling-allowlist.json` තුළ පවතී. සෑම ඇතුළත් කිරීමක්ම පරිභෝජකයා සහ අපේක්ෂක -පරීක්ෂණය නම් කළ යුතු අතර, නිශ්චිත හේතු දැක්වීමක් ලබා දී GitHub issue එකකට හෝ pull request එකකට සබැඳියක් යෙදිය යුතුය. වැරදි ලෙස සැකසූ ඇතුළත් කිරීම් -පෙරනිමියෙන් අසාර්ථක වේ. ව්යතිරේකවලට මකා දැමූ අපේක්ෂක පරීක්ෂණයක් හෝ `.skip`/`.todo` එක් කරන diff එකක් යටපත් කළ නොහැක; -assertion දුර්වල කිරීම සහ අනෙකුත් ආවරණය කිරීම් ස්වාධීනව අවහිර කරන -`check:test-masking` gate එක යටතේම පවතී. +`config/quality/forgotten-sibling-allowlist.json` තුළ පවතී. සෑම ඇතුළත් කිරීමක්ම පාරිභෝගිකයා සහ අපේක්ෂක +පරීක්ෂණය නම් කළ යුතු අතර, නිශ්චිත හේතු දැක්වීමක් ලබා දී GitHub ගැටලුවකට හෝ pull request එකකට සබැඳියක් එක් කළ යුතුය. වැරදි ලෙස සැකසූ ඇතුළත් කිරීම් +සංවෘත ආකාරයෙන් අසාර්ථක වේ. ව්යතිරේකවලට මකා දැමූ අපේක්ෂක පරීක්ෂණයක් හෝ `.skip`/`.todo` එක් කරන වෙනසක් යටපත් කළ නොහැක; +assertion දුර්වල කිරීම සහ අනෙකුත් වසන් කිරීම් ස්වාධීනව අවහිර කරන +`check:test-masking` දොරටුව යටතේම පවතී. ### කාර්යය: `lint` -`main` වෙත යොමු කරන සෑම PR එකකදීම ක්රියාත්මක වේ. අසාර්ථක වුවහොත් merge කිරීම අවහිර කරයි. - -| ස්ක්රිප්ට් (`npm run ...`) | වලංගු කරන්නේ | අවහිර කරයිද | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Node.js අනුවාදය සහාය දක්වන පරාසය තුළ පවතී | ඔව් | -| `check:cycles` | චක්රීය ආයාත — සියලුම `src/` + `open-sse/` මොඩියුල | ඔව් | -| `check:route-validation:t06` | සියලුම routes මත Zod schemas පවතී (Tier 6 ප්රතිපත්තිය) | ඔව් | -| `check:any-budget:t11` | `@ts-expect-error // any` ගණන අයවැය ඉක්මවා නොයයි (Tier 11 catraca) | ඔව් | -| `check:provider-consistency` | `providers.ts` හි ඇති සෑම provider එකකටම `providerRegistry.ts` හි ගැළපෙන entry එකක් ඇත (එමෙන්ම ප්රතිවිරුද්ධ දිශාවටද, allowlist එක තුළ) | ඔව් | -| `check:model-lifecycle` | අතින් නඩත්තු කරන routing table තුන checked-in lifecycle snapshot එක (#11503) සමඟ අනුකූලව පවතී: `FITNESS_TABLE` (`taskFitness.ts`) මඟින් `REGISTRY` හරහා route කළ හැකි retired id එකකටවත් score ලබා නොදේ; සෑම `BUILT_IN_ALIASES` target එකක්ම `REGISTRY` හි තිබෙන අතර retired-id snapshot එකෙහි නොමැත; තවමත් `REGISTRY` හි ඇති සෑම retired id එකක්ම forward කර හෝ `allowedRetiredInCatalog` හි ලැයිස්තුගත කර ඇත; තවද `DEFAULT_DEGRADATION_MAP` හි කිසිදු source එකක් හෝ target එකක් එම snapshot එකෙහි retired ලෙස නොපෙන්වයි. model එකක් දැනට සජීවී upstream එකකින් සපයන බව මෙයින් සනාථ නොවේ. Offline — `config/quality/model-lifecycle.json` සමඟ සසඳයි; එය `npm run quality:refresh-model-lifecycle` මඟින් අතින් refresh කෙරේ (network; CI සමඟ සම්බන්ධ කර නැත). `allowedRetiredInCatalog` යනු burn-down ratchet එකකි: entry එකක් එක් කළ යුත්තේ tracking issue එකක් සමඟ පමණි. | ඔව් | -| `check:fetch-targets` | client-side `src/` හි ඇති සෑම `fetch("/api/...")` එකක්ම සැබෑ `route.ts` එකකට resolve වේ | ඔව් | -| `check:deps` | repo එකේ ඇති සෑම `package.json` එකක් පුරාම `npm install` කළ හැකි සියලු deps `dependency-allowlist.json` හි ඇත; නව unpinned හෝ slopsquatted packages සලකුණු කෙරේ | ඔව් | -| `audit:deps` | `npm audit` (root + electron) — high/critical advisories කිසිවක් නැත (osv `check:vuln-ratchet` සමඟ අතිච්ඡාදනය වේ; Rationalization Backlog බලන්න) | ඔව් | -| `check:lockfile` | `package-lock.json` අඛණ්ඩතාව — https registry, integrity hashes, host overrides නොමැත | ඔව් | -| `check:licenses` | නිෂ්පාදන පරායත්තතා සඳහා SPDX බලපත්ර අවසර ලැයිස්තුව | ඔව් | -| `check:tracked-artifacts` | build artifacts / commit කළ `node_modules` symlinks නොමැත (husky pre-commit තුළද ධාවනය වේ; pre-push හිතාමතාම සැහැල්ලුවෙන් තබා ඇත — #6716) | ඔව් | -| `check:ai-attribution` | PR commits, title හෝ body තුළ AI/bot `Co-Authored-By` trailer හෝ AI-generation footer නොමැත — දැඩි රීතිය #16 (`quality.yml` හි PR→`release/**` සඳහා වන fast-gates loop තුළ — event payload එක කියවයි, PR නොවන විට කිසිවක් නොකරයි — සහ `ci.yml` lint තුළ PR→`main` සඳහා PR-පමණක් වන පියවරක්; එසේම husky `commit-msg` hook එකද; මානව සම-කර්තෘවරුන්ට අවසර ඇත; #14436) | -| `check:vitest-exclusions` | සෑම Vitest exclusion එකක්ම tracking issue එකක් නම් කරන අතර `config/quality/vitest-exclusions.json` තුළ දිස්වේ (#13204) | ඔව් | -| `check:file-size` | කිසිදු source file එකක් extension එකකට අදාළ උපරිම සීමාව ඉක්මවන්නේ නැත (ratchet: විශාල ලෙස ස්ථිර කළ ගොනු `frozen` ලැයිස්තුවේ ඇත) | ඔව් | -| `check:error-helper` | executors/handlers තුළ error responses සඳහා `buildErrorBody()` / `sanitizeErrorMessage()` භාවිත වේ (දැඩි රීතිය #12) | ඔව් | -| `check:migration-numbering` | Migration SQL ගොනු හිඩැස් හෝ අනුපිටපත් නොමැතිව අනුක්රමිකව අංකනය කර ඇත | ඔව් | -| `check:public-creds` | `publicCreds.ts` පිටත සෘජුව ඇතුළත් කළ OAuth `client_id`/`client_secret` හෝ Firebase Web යතුරු නොමැත (දැඩි රීතිය #11) | ඔව් | -| `check:db-rules` | `src/lib/db/` මොඩියුලවලින් පිටත අමු SQL නොමැත; `localDb.ts` වෙතින් barrel-imports නොමැත (දැඩි රීති #2/#5) | ඔව් | -| `check:known-symbols` | ඒවායේ dispatch වගුවල ලියාපදිංචි කර ඇති provider executors, routing strategies සහ translators තැටියේ ඇති ගොනුවලට ගැළපේ — අනාථ හෝ ප්රකාශ නොකළ symbols නොමැත | ඔව් | -| `check:route-guard-membership` | child process එකක් ආරම්භ කරන සෑම route එකක්ම `isLocalOnlyPath()` මඟින් වර්ගීකරණය කර ඇත (දැඩි රීති #15/#17) | ඔව් | -| `check:test-discovery` | repo එකේ ඇති සෑම `*.test.ts` / `*.spec.ts` ගොනුවක්ම අවම වශයෙන් එක් test runner එකක් මඟින් රැස් කරනු ලැබේ (ratchet: `test-discovery-baseline.json` හි අනාථ ලැයිස්තුව හැකිලීම පමණක් සිදු විය හැක) | ඔව් | -| `check:agent-skills-sync` | ජනනය කළ agent-skills කෘති ඒවායේ මූලාශ්ර නාමාවලිය සමඟ ගැළපේ (වෙනස්වීමක් නැත) | -| `check:provider-asset-provenance` | සැපයුම්කරුගේ ලාංඡන/වත්කම් සඳහා වාර්තාගත මූලාරම්භ සටහනක් ඇත | -| `lint:json` | JSON වින්යාස ගොනු නිවැරදිව විග්රහ වන අතර repo lint නීති සපුරාලයි | -| `typecheck:core` | දෝෂ රහිත TypeScript සම්පාදනය (උපදේශාත්මක අනතුරු ඇඟවීම් පමණි) | ඔව් | -| `typecheck:noimplicit:core` | දැඩි `noImplicitAny` — ඉදිරියට යොමු වූවකි; පෙර සිට පවතින බොහෝ ඇමතුම් ස්ථාන සඳහා තවමත් විවරණ අවශ්ය වේ | **උපදේශාත්මකයි** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` වෙත සීමා කළ `tsc` (#7033) — `typecheck:core` හි තෝරාගත් ගොනු 27ක අවසර ලැයිස්තුවට කිසිදු dashboard TSX ගොනුවක් ඇතුළත් නොවන අතර, `next build` ද එය කිසිවිටෙක type-check නොකරයි (`next.config.mjs` විසින් `ignoreBuildErrors: true` සකසයි); එබැවින් එහි අනාථ-හඳුන්වනය ප්රතිගමන (#6625/#6909) CI වෙත නොපෙනුණි. ස්ථාවර කළ එක්-ගොනුවකට/එක්-TS-code එකකට අදාළ සංඛ්යා පාදක රේඛාවකට (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` හි ඇති එකම කල් ඉකුත් වූ අයිතම බලාත්මක කිරීමේ රටාව) එරෙහිව වෙනස්කම් සසඳයි — පාදක රේඛාවේ සංඛ්යාව ඉක්මවන නව දෝෂ පමණක් දොරටුව අසමත් කරයි; පෙර සිට පවතින දෝෂයක් නිවැරදි කළ විට `--update` සමඟ සීමාව පහළට ගෙන එන්න. | ඔව් | +`main` වෙත යොමු කරන සෑම PR එකකදීම ක්රියාත්මක වේ. අසාර්ථක වුවහොත් ඒකාබද්ධ කිරීම අවහිර කරයි. + +| ස්ක්රිප්ට් (`npm run ...`) | වලංගු කරන්නේ | අවහිර කිරීම | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Node.js අනුවාදය සහාය දක්වන පරාසය තුළ පවතී | ඔව් | +| `check:cycles` | `src/` + `open-sse/` සියල්ල හරහා ඇති චක්රීය ආයාත (AST-පදනම් වූ, tsconfig `paths` විභේදනය කළ). තනිව ධාවනය කළ විට = උපදේශාත්මක වන අතර, චක්ර ලැයිස්තුගත කරයි. `check:cycles:ratchet` (CI ධාවනය කරන්නේ මෙයයි) ගණන `quality-baseline.json` තුළ ඇති `metrics.cycles` උපරිම සීමාව ඉක්මවන විට අවහිර කරයි — දැනට 14, `direction: down`, එබැවින් එය අඩු වීමට පමණක් හැකිය (#15159 G-01/G-02) | ඔව් (ratchet) | +| `check:route-validation:t06` | සියලු routes මත Zod schemas පවතින බව (Tier 6 ප්රතිපත්තිය) | ඔව් | +| `check:any-budget:t11` | `@ts-expect-error // any` ගණන අයවැය ඉක්මවා නොයන බව (Tier 11 catraca) | ඔව් | +| `check:provider-consistency` | `providers.ts` හි ඇති සෑම provider එකකටම `providerRegistry.ts` හි ගැළපෙන entry එකක් ඇත (සහ ප්රතිවිරුද්ධවද, allowlist සීමාව තුළ) | ඔව් | +| `check:model-lifecycle` | අතින් නඩත්තු කරන routing tables තුන checked-in lifecycle snapshot (#11503) සමඟ අනුකූලව පවතී: `FITNESS_TABLE` (`taskFitness.ts`) මඟින් `REGISTRY` වෙත route කළ නොහැකි retired id එකකටවත් score ලබා නොදේ; සෑම `BUILT_IN_ALIASES` target එකක්ම `REGISTRY` හි පවතින අතර retired-id snapshot එකෙහි නොපවතී; තවමත් `REGISTRY` හි ඇති සෑම retired id එකක්ම forward කර ඇත, නැතහොත් `allowedRetiredInCatalog` හි ලැයිස්තුගත කර ඇත; තවද `DEFAULT_DEGRADATION_MAP` හි කිසිදු source එකක් හෝ target එකක් එම snapshot එකෙහි retired ලෙස නොපෙන්වයි. මෙය model එකක් දැනට සජීවී upstream එකක් මඟින් සපයන බව සනාථ නොකරයි. Offline — `config/quality/model-lifecycle.json` සමඟ සසඳයි; එය `npm run quality:refresh-model-lifecycle` මඟින් අතින් refresh කරනු ලැබේ (network; CI වෙත සම්බන්ධ කර නැත). `allowedRetiredInCatalog` යනු burn-down ratchet එකකි: entry එකක් එක් කළ යුත්තේ tracking issue එකක් සමඟ පමණි. | ඔව් | +| `check:fetch-targets` | client-side `src/` තුළ ඇති සෑම `fetch("/api/...")` එකක්ම සැබෑ `route.ts` එකකට resolve වේ | ඔව් | +| `check:deps` | repo එකෙහි සෑම `package.json` එකක් පුරාම ඇති `npm install` කළ හැකි සියලු deps, `dependency-allowlist.json` හි ඇත; pin නොකළ හෝ slopsquatted වූ නව packages flag කරනු ලැබේ | ඔව් | +| `audit:deps` | `npm audit` (root + electron) — high/critical advisories කිසිවක් නැත (osv `check:vuln-ratchet` සමඟ overlap වේ; Rationalization Backlog බලන්න) | ඔව් | +| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashes, host overrides නොමැත | ඔව් | +| `check:licenses` | නිෂ්පාදන පරායත්තතා සඳහා SPDX බලපත්ර අවසර ලැයිස්තුව | ඔව් | +| `check:tracked-artifacts` | build artifacts / commit කළ `node_modules` symlinks නොමැත (husky pre-commit තුළද ධාවනය වේ; pre-push හිතාමතාම සැහැල්ලුවෙන් තබා ඇත — #6716) | ඔව් | +| `check:ai-attribution` | PR commits, title හෝ body තුළ AI/bot `Co-Authored-By` trailer හෝ AI-generation footer නොමැත — දැඩි රීතිය #16 (`quality.yml` හි PR→`release/**` සඳහා වන fast-gates loop එක තුළ — event payload එක කියවයි, PR නොවන විට කිසිවක් නොකරයි — සහ PR→`main` සඳහා `ci.yml` lint හි PR-පමණක් වන පියවරක්; එසේම husky `commit-msg` hook එක; මානව co-authors සඳහා අවසර ඇත; #14436) | +| `check:vitest-exclusions` | සෑම Vitest exclusion එකක්ම tracking issue එකක් නම් කරන අතර `config/quality/vitest-exclusions.json` තුළ දිස් වේ (#13204) | ඔව් | +| `check:file-size` | කිසිදු source file එකක් එහි extension එකට අදාළ උපරිම සීමාව ඉක්මවා නොයයි (ratchet: විශාල files `frozen` ලැයිස්තුවේ ස්ථිර කර ඇත) | ඔව් | +| `check:error-helper` | executors/handlers තුළ error responses සඳහා `buildErrorBody()` / `sanitizeErrorMessage()` භාවිත කරයි (දැඩි රීතිය #12) | ඔව් | +| `check:migration-numbering` | Migration SQL ගොනු හිඩැස් හෝ අනුපිටපත් නොමැතිව අනුක්රමිකව අංකනය කර ඇත | ඔව් | +| `check:public-creds` | `publicCreds.ts` පිටත literal OAuth `client_id`/`client_secret` හෝ Firebase Web යතුරු නොමැත (දැඩි රීතිය #11) | ඔව් | +| `check:db-rules` | `src/lib/db/` මොඩියුලවලින් පිටත raw SQL නොමැත; `localDb.ts` වෙතින් barrel-imports නොමැත (දැඩි රීති #2/#5) | ඔව් | +| `check:known-symbols` | ඒවායේ dispatch වගුවල ලියාපදිංචි කර ඇති provider executors, routing strategies සහ translators තැටියේ ඇති ගොනු සමඟ ගැළපේ — අනාථ හෝ ප්රකාශ නොකළ symbols නොමැත | ඔව් | +| `check:route-guard-membership` | child process එකක් ආරම්භ කරන සෑම route එකක්ම `isLocalOnlyPath()` මඟින් වර්ගීකරණය කර ඇත (දැඩි රීති #15/#17) | ඔව් | +| `check:test-discovery` | repo එකේ ඇති සෑම `*.test.ts` / `*.spec.ts` ගොනුවක්ම අවම වශයෙන් එක් test runner එකක් මඟින් එකතු කරනු ලැබේ (ratchet: `test-discovery-baseline.json` හි orphan ලැයිස්තුව හැකිලීමට පමණක් හැකිය) | ඔව් | +| `check:agent-skills-sync` | ජනනය කළ agent-skills කලාකෘති ඒවායේ මූලාශ්ර නාමාවලියට ගැළපේ (වෙනස්වීමක් නොමැත) | +| `check:provider-asset-provenance` | සැපයුම්කරුගේ ලාංඡන/වත්කම් සඳහා වාර්තා කළ මූලාශ්ර ප්රවේශයක් ඇත | +| `lint:json` | JSON වින්යාස ගොනු නිවැරදිව විග්රහ වන අතර repo lint නීති සපුරාලයි | +| `typecheck:core` | දෝෂ නොමැති TypeScript සම්පාදනය (උපදේශාත්මක අනතුරු ඇඟවීම් පමණි) | ඔව් | +| `typecheck:noimplicit:core` | දැඩි `noImplicitAny` — අනාගතය ඉලක්ක කරගත් පරීක්ෂාවකි; පෙර සිට පවතින ඇමතුම් ස්ථාන බොහොමයකට තවමත් විවරණ අවශ්ය වේ | **උපදේශාත්මකයි** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` වෙත සීමා කළ `tsc` (#7033) — `typecheck:core` හි තෝරාගත් ගොනු 27ක අවසර ලැයිස්තුවට කිසිදු dashboard TSX ගොනුවක් ඇතුළත් නොවන අතර, `next build` ද එය කිසිවිටෙක වර්ග-පරීක්ෂා නොකරයි (`next.config.mjs` තුළ `ignoreBuildErrors: true` සකසයි); එබැවින් එහි තිබූ අනාථ හඳුනාගැනීම් සම්බන්ධ ප්රතිගමන (#6625/#6909) CI වෙත නොපෙනුණි. ස්ථිර කළ එක්-ගොනුවකට/එක්-TS-code එකකට අදාළ ගණන් මූලරේඛාවකට (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` හි ඇති යල්පැනීම බලාත්මක කිරීමේ රටාවම) එරෙහිව වෙනස්කම් සසඳයි — මූලරේඛාගත ගණන ඉක්මවන නව දෝෂ පමණක් දොරටුව අසමත් කරයි; පෙර සිට පැවති දෝෂයක් නිවැරදි කළ විට `--update` සමඟ මූලරේඛාව පහළට සකසන්න. | ඔව් | ### කාර්යය: `quality-gate` `test-coverage` පසු ධාවනය වේ. අසමත් වුවහොත් ඒකාබද්ධ කිරීම අවහිර කරයි. -| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිර කිරීම | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| `quality:collect` | `quality-metrics.json` නිකුත් කරයි (ESLint අනතුරු ඇඟවීම් ගණන, ඒකාබද්ධ කළ shard වාර්තාවෙන් ආවරණය) | ඔව් (ratchet ට පෙර ක්රියාත්මක වේ) | -| `quality:ratchet` | `quality-baseline.json` හි සෑම මිනුමක්ම පසුබැස නැත (ESLint අනතුරු ඇඟවීම් ≤ baseline; ආවරණය ≥ baseline) | ඔව් | -| `check:duplication` | කේත අනුපිටපත් වීම (jscpd@4) `quality-baseline.json` හි baseline ඉක්මවා නොයයි | ඔව් | -| `check:complexity` | ගොනු-මට්ටමේ cyclomatic සංකීර්ණතාව සීමාව ඉක්මවා නොයයි (මූලික ESLint `complexity` + `max-lines-per-function`) | ඔව් | -| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — වෙනම ESLint ධාවනයකි; CI තුළ දෙකම තනි `check:complexity-ratchets` පියවර ලෙස ඒකාබද්ධ කර ධාවනය කරයි | ඔව් | -| `check:dead-code` | භාවිත නොකරන exports / ගොනු සඳහා වන ratchet (knip), baseline සමඟ සැසඳීමේදී පසුබැස නැත | ඔව් | -| `check:compression-budget` | සම්පීඩන benchmark අයවැය — එක් එක් engine සඳහා token-savings අවම සීමාවන් පසුබැසිය නොහැක | ඔව් | -| `check:type-coverage` | ටයිප් කළ ප්රතිශතය සඳහා වන ratchet (`type-coverage`) පසුබැස නැත; බොහෝ දුරට `typecheck:noimplicit:core` අන්තර්ග්රහණය කරයි | ඔව් | -| `check:codeql-ratchet` | විවෘත CodeQL ඇඟවීම් ගණන පසුබැස නැත (`gh api` හරහා කියවයි; token නොමැති විට සුමටව මඟ හරියි) — නැවුම් කිරීමේ වාර ගණන සහ අතින් trigger කිරීම: පහත "CodeQL ratchet" බලන්න | ඔව් | +| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිර කිරීම | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `quality:collect` | `quality-metrics.json` නිකුත් කරයි (ESLint අනතුරු ඇඟවීම් ගණන, ඒකාබද්ධ කළ shard වාර්තාවෙන් ආවරණය) | ඔව් (ratchetට පෙර) | +| `quality:ratchet` | `quality-baseline.json` හි සෑම මිනුමක්ම පසුබැසී නොමැත (ESLint අනතුරු ඇඟවීම් ≤ මූලික අගය; ආවරණය ≥ මූලික අගය) | ඔව් | +| `check:duplication` | කේත අනුපිටපත් වීම (jscpd@4) `quality-baseline.json` හි මූලික අගය ඉක්මවා නොයයි | ඔව් | +| `check:complexity` | ගොනු මට්ටමේ චක්රීය සංකීර්ණතාව සීමාව ඉක්මවා නොයයි (මූලික ESLint `complexity` + `max-lines-per-function`) | ඔව් | +| `check:cognitive-complexity` | ප්රජානන සංකීර්ණතා ratchet (`eslint-plugin-sonarjs`) — වෙනම ESLint ධාවනයකි; CI විසින් දෙකම තනි `check:complexity-ratchets` පියවර ලෙස ඒකාබද්ධ කර ධාවනය කරයි | ඔව් | +| `check:dead-code` | භාවිත නොකළ exports / ගොනු ratchet (knip) මූලික අගයට සාපේක්ෂව පසුබැසී නොමැත | ඔව් | +| `check:compression-budget` | සම්පීඩන මිණුම් ලකුණු අයවැය — එක් එක් engine සඳහා token ඉතිරිකිරීමේ අවම අගයන් පසුබැසිය යුතු නොවේ | ඔව් | +| `check:type-coverage` | type කර ඇති ප්රතිශතයේ ratchet (`type-coverage`) පසුබැසී නොමැත; බොහෝ දුරට `typecheck:noimplicit:core` ආවරණය කරයි | ඔව් | +| `check:codeql-ratchet` | විවෘත CodeQL ඇඟවීම් ගණන පසුබැසී නොමැත (`gh api` හරහා කියවයි; token නොමැතිව සුමටව මඟ හරී) — යාවත්කාලීන කිරීමේ වාර ගණන සහ අතින් ක්රියාත්මක කිරීම: පහත "CodeQL ratchet" බලන්න | ඔව් | ### කාර්යය: `quality-extended` -සම්පූර්ණ කාර්යයම උපදේශනාත්මකය (`continue-on-error: true`). npm-මත පදනම් වූ ratchets +සම්පූර්ණ කාර්යයම උපදේශාත්මකය (`continue-on-error: true`). npm-පදනම් වූ ratchets සැබෑ ලෙස ධාවනය වේ; බාහිර scanners `gh release download` හරහා ස්ථාපනය වන අතර binary එකක් -තවමත් නොමැති විට ස්වයංක්රීයව මඟ හරියි (exit 0). +තවමත් නොමැති විට ස්වයංක්රීයව මඟ හරී (exit 0). -| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිර කිරීම | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -| `check:circular-deps` | චක්රීය පරායත්තතා නොමැත (dpdm) | **උපදේශනාත්මක** | -| `check:bundle-size` | Bundle ප්රමාණය සීමාව ඉක්මවා නොයයි | **උපදේශනාත්මක** | -| `check:secrets` | රහස් තොරතුරු පරිලෝකනය (gitleaks) — binary එක නොමැති නම් මඟ හරියි | **උපදේශනාත්මක** | -| `check:vuln-ratchet` | පරායත්තතා දුර්වලතා (osv-scanner) පසුබැස නැත — binary එක නොමැති නම් මඟ හරියි | **උපදේශනාත්මක** | -| `check:workflows` | Workflow lint කිරීම (actionlint + zizmor); නොමැති/බිඳුණු scanners, වලංගු නොවන වාර්තා හෝ නොමැති ratchet baseline එකක් INCOMPLETE ලෙස අසාර්ථක වේ. වලංගු සොයාගැනීම් තෝරාගත් strict/advisory/ratchet ප්රතිපත්තිය අනුගමනය කරයි | ක්රියාත්මක කිරීම අවශ්යයි; CI හි zizmor ratchet අවහිර කරයි | -| `check:openapi-breaking` | මූලික branch එකට සාපේක්ෂව පොදු API ගිවිසුමේ (`openapi.yaml`) බිඳවැටෙන වෙනස්කම් (oasdiff) — `openapiBreaking=N` නිකුත් කරයි; oasdiff නොමැති නම් හෝ මූලික spec එක resolve කළ නොහැකි නම් මඟ හරියි | **උපදේශනාත්මක** | +| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිර කිරීම | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| `check:circular-deps` | චක්රීය dependencies නොමැත (dpdm) | **උපදේශාත්මක** | +| `check:bundle-size` | Bundle ප්රමාණය සීමාව ඉක්මවා නොයයි | **උපදේශාත්මක** | +| `check:secrets` | රහස් පරිලෝකනය (gitleaks) — binary එක නොමැති නම් මඟ හරී | **උපදේශාත්මක** | +| `check:vuln-ratchet` | Dependency දුර්වලතා (osv-scanner) පසුබැසී නොමැත — binary එක නොමැති නම් මඟ හරී | **උපදේශාත්මක** | +| `check:workflows` | Workflow lint කිරීම (actionlint + zizmor); නොමැති/බිඳුණු scanners, වලංගු නොවන වාර්තා හෝ නොමැති ratchet මූලික අගයක් INCOMPLETE ලෙස අසමත් වේ. වලංගු සොයාගැනීම් තෝරාගත් strict/advisory/ratchet ප්රතිපත්තිය අනුගමනය කරයි | ක්රියාත්මක කිරීම අවශ්යයි; CI තුළ zizmor ratchet අවහිර කරයි | +| `check:openapi-breaking` | මූලික branch එකට සාපේක්ෂව පොදු API ගිවිසුමේ (`openapi.yaml`) බිඳවැටෙන වෙනස්කම් (oasdiff) — `openapiBreaking=N` නිකුත් කරයි; oasdiff නොමැති නම් හෝ මූලික spec එක නිරාකරණය කළ නොහැකි නම් මඟ හරී | **උපදේශාත්මක** | ### කාර්යය: `docs-sync-strict` -`main` වෙත වන සෑම PR එකකදීම ධාවනය වේ. අසාර්ථක වුවහොත් merge කිරීම අවහිර කරයි. - -| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිර කරන්නේ | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `check:docs-all` | පහත උප-ගේට් 6 අනුක්රමිකව ධාවනය කරන මෙටා-ගේට් එක | ඔව් | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt අනුවාද අනුකූලතාව | ඔව් | -| ↳ `check:docs-counts` | ගද්යයේ ඇති ගණන් (සපයන්නන් ගණන, සංක්රමණ ගණන ආදිය) සැබෑ ගණන්වල රැචට් කවුළුව තුළ තිබීම | ඔව් | -| ↳ `check:env-doc-sync` | `.env.example` තුළ ඇති සෑම පරිසර විචල්යයක්ම ලේඛන වගුවක ලේඛනගත කර තිබීම සහ එහි ප්රතිලෝමය | ඔව් | -| ↳ `check:deprecated-versions` | ලේඛන තුළ අභාවිත කළ අනුවාද තන්තු නොතිබීම | ඔව් | -| ↳ `check:doc-links` | ලේඛන තුළ ඇති අභ්යන්තර markdown සබැඳි සැබෑ ගොනු වෙත විසඳීම (`[text]`/`(path)` ආකෘතිය) | ඔව් | -| ↳ `check:fabricated-docs` | ලේඛනවල සඳහන් මාර්ග, පරිසර විචල්ය, CLI විධාන, hook නම් සහ ගොනු මාර්ග කේත පදනමේ පැවතීම. `--strict` හරහා දැඩි ගේට් එකක්; ධජය නොමැතිව මෘදු අසමත් වීමක්. | ඔව් (CI තුළ `--strict` හරහා) | -| `check:cli-i18n` | CLI විධාන තන්තු සියලු i18n පෙදෙසි ගොනුවල තිබීම | ඔව් | -| `check:openapi-coverage` | OpenAPI පිරිවිතරය සැබෑ මාර්ග සඳහා අවම වශයෙන් රැචට් කළ පහළ සීමාවක් ආවරණය කිරීම | ඔව් | -| `check:openapi-security-tiers` | `openapi.yaml` තුළ ඇති ආරක්ෂක මට්ටම් විවරණ `routeGuard.ts` වර්ගීකරණ සමඟ අනුකූල වීම | **උපදේශාත්මකයි** | -| `check:openapi-routes` | `openapi.yaml` තුළ ඇති සෑම මාර්ගයක්ම සැබෑ `route.ts` එකකට විසඳීම (ප්රබන්ධ-විරෝධී) | ඔව් | -| `check:docs-symbols` | `docs/**/*.md` තුළ ඇති සෑම `/api/...` යොමුවක්ම සැබෑ `route.ts` එකකට විසඳීම (ප්රබන්ධ-විරෝධී) | ඔව් | -| `i18n translation drift` | i18n පෙදෙසි ගොනුවල පරිවර්තනය නොකළ යතුරු — අනතුරු ඇඟවීමක් පමණි | **උපදේශාත්මකයි** | +`main` වෙත වන සෑම PR එකකදීම ධාවනය වේ. අසමත් වුවහොත් merge කිරීම අවහිර කරයි. + +| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිරකාරී | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- | +| `check:docs-all` | පහත උප-ගේට්ටු 6 අනුක්රමිකව ධාවනය කරන මෙටා-ගේට්ටුවකි | ඔව් | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt අනුවාද අනුකූලතාව | ඔව් | +| ↳ `check:docs-counts` | ගද්යයේ සඳහන් ගණන් (සපයන්නන් ගණන, සංක්රමණ ගණන ආදිය) සත්ය ගණන්වල ratchet පරාසය තුළ තිබීම | ඔව් | +| ↳ `check:env-doc-sync` | `.env.example` හි සෑම env var එකක්ම ලේඛන වගුවක ලේඛනගත කර තිබීම සහ එහි ප්රතිවිරුද්ධය ද සත්ය වීම | ඔව් | +| ↳ `check:deprecated-versions` | ලේඛනවල අත්හැර දැමූ අනුවාද තන්තු නොතිබීම | ඔව් | +| ↳ `check:doc-links` | ලේඛනවල අභ්යන්තර markdown සබැඳි සැබෑ ගොනු වෙත යොමු වීම (`[text]`/`(path)` ආකෘතිය) | ඔව් | +| ↳ `check:fabricated-docs` | ලේඛනවල සඳහන් මාර්ග, env vars, CLI විධාන, hook නම් සහ ගොනු මාර්ග codebase එකේ පැවතීම. `--strict` හරහා දැඩි ගේට්ටුවකි; flag එක නොමැතිව මෘදු අසමත් වීමකි. | ඔව් (CI හි `--strict` හරහා) | +| `check:cli-i18n` | CLI විධාන තන්තු සියලු i18n locale ගොනුවල තිබීම | ඔව් | +| `check:openapi-coverage` | OpenAPI පිරිවිතරය සැබෑ මාර්ගවලින් අවම වශයෙන් ratchet කළ පහළ සීමාවක් ආවරණය කිරීම | ඔව් | +| `check:openapi-security-tiers` | `openapi.yaml` හි ආරක්ෂක මට්ටම් විවරණ `routeGuard.ts` වර්ගීකරණ සමඟ අනුකූල වීම | **උපදේශාත්මක** | +| `check:openapi-routes` | `openapi.yaml` හි සෑම මාර්ගයක්ම සැබෑ `route.ts` එකක් වෙත යොමු වීම (ව්යාජ නිර්මාණ වැළැක්වීම) | ඔව් | +| `check:docs-symbols` | `docs/**/*.md` හි සෑම `/api/...` යොමුවක්ම සැබෑ `route.ts` එකක් වෙත යොමු වීම (ව්යාජ නිර්මාණ වැළැක්වීම) | ඔව් | +| `i18n translation drift` | i18n locale ගොනුවල පරිවර්තනය නොකළ keys — අනතුරු ඇඟවීම පමණි | **උපදේශාත්මක** | ### කාර්යය: `i18n-ui-coverage` -| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිර කරන්නේ | -| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `check-ui-keys-coverage` (පේළිය තුළ) | UI i18n යතුරු ආවරණය ≥ 65% වීම | ඔව් | -| `check-ui-value-drift` (පේළිය තුළ) | නැවත ලියන ලද ඉංග්රීසි **අගයක්** පිටුපස යල්පැන ගිය පරිවර්තනයක් ඉතිරි නොකිරීම | ඔව් | -| `check-new-key-coverage` (පේළිය තුළ) | **නව** ඉංග්රීසි යතුරක් සෑම පෙදෙසකම පරිවර්තනය කර තිබීම — `__MISSING__:` සලකුණක් ප්රතික්ෂේප කෙරේ | ඔව් | -| `check-translation-ratio` | එක් එක් පෙදෙස සඳහා සැබෑ-පරිවර්තන අනුපාතය (අවසර ලැයිස්තුවෙන් පිටත ඉංග්රීසියට සමාන / තැන්පත්කාරක / අස්ථානගත පත්ර) `config/quality/i18n-translation-baseline.json` + ඉඩකඩ නොඉක්මවිය යුතුය | **උපදේශාත්මකයි** | +| ස්ක්රිප්ට් | වලංගු කරන්නේ | අවහිරකාරී | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (inline) | UI i18n key ආවරණය ≥ 65% වීම | ඔව් | +| `check-ui-value-drift` (inline) | නැවත ලියන ලද ඉංග්රීසි **අගයක්** පරණ පරිවර්තනයක් ඉතිරි නොකිරීම | ඔව් | +| `check-new-key-coverage` (inline) | **නව** ඉංග්රීසි key එකක් සෑම locale එකකම පරිවර්තනය කර තිබීම — `__MISSING__:` සලකුණක් ප්රතික්ෂේප කෙරේ | ඔව් | +| `check-translation-ratio` | locale එකකට සැබෑ-පරිවර්තන අනුපාතය (allowlist එකෙන් පිටත ඉංග්රීසියට සමාන / placeholder / නොමැති leaves) `config/quality/i18n-translation-baseline.json` + slack ඉක්මවා නොයා යුතුය | **උපදේශාත්මක** | -`fetch-depth: 0` අවශ්ය වේ — අගය-වෙනස්වීමේ ගේට් එක ඒකාබද්ධ පදනමට එරෙහිව `en.json` වෙනස්කම් සසඳයි. +`fetch-depth: 0` අවශ්ය වේ — value-drift ගේට්ටුව merge base එකට සාපේක්ෂව `en.json` හි වෙනස්කම් සසඳයි. -#### `check-ui-value-drift` — යල්පැන ගිය පරිවර්තන ගේට් එක +#### `check-ui-value-drift` — පරණ-පරිවර්තන ගේට්ටුව -වෙනත් ගේට්වලට ව්යුහාත්මකව දැකිය නොහැකි එක් i18n ප්රතිගමනය මෙය හඳුනා ගනී: ඉංග්රීසි අගයක් -නැවත ලියන නමුත් _පෙර_ ඉංග්රීසි අගයෙන් ව්යුත්පන්න කළ පරිවර්තන ඉතිරි වන බැවින්, -ඉංග්රීසි නොවන පරිශීලකයන් විශ්වාසදායක ලෙස ලියා ඇති, දැන් වැරදි පෙළක් දිගටම කියවයි. +අනෙකුත් ගේට්ටුවලට ව්යුහාත්මකව දැකිය නොහැකි එකම i18n ප්රතිගමනය මෙය හසුකරයි: ඉංග්රීසි අගයක් +නැවත ලියන නමුත් _පෙර_ ඉංග්රීසි අගයෙන් ව්යුත්පන්න කළ පරිවර්තන එලෙසම ඉතිරි වන බැවින්, +ඉංග්රීසි නොවන පරිශීලකයන් විශ්වාසදායක ලෙස ලියා ඇති, නමුත් දැන් වැරදි පෙළ දිගටම කියවයි. -මෙය සැබවින්ම නිකුත් විය. Antigravity පිවිසුම් සහායකය එක් කළ විට (#5203) -`oauthModal.googleOAuthWarning` නැවත ලියන ලදී; **පෙදෙසි 43න් 39ක්** ක්රියාකරුවන්ට "සම්පූර්ණ -URL එක පිටපත් කර පහත අලවන්න" යැයි කියන පෙළ තබාගෙන තිබුණි — එම සපයන්නා සඳහා සම්පූර්ණ -කළ නොහැකි ප්රවාහයකි. පහත හේතු නිසා #8463 තෙක් එය නොදැකී පැවතුණි: +මෙය සැබැවින්ම නිකුත් විය. Antigravity පිවිසුම් සහායකය එක් කළ විට (#5203) +`oauthModal.googleOAuthWarning` නැවත ලියන ලදී; **locale 43න් 39ක්** ක්රියාකරුවන්ට "සම්පූර්ණ +URL එක පිටපත් කර පහළින් අලවන්න" යැයි කියන පෙළ තබාගෙන තිබුණි — එම සපයන්නා සඳහා සම්පූර්ණ කළ +නොහැකි ප්රවාහයකි. පහත හේතු නිසා #8463 වන තෙක් එය අවධානයට ලක් නොවීය: -- `sync-ui-keys` පසුපිරවීම කරන්නේ **නොමැති** යතුරු පමණක් වන අතර, **යල්පැන ගිය** යතුරු කිසිවිටෙක නොවේ; -- `check-ui-keys-coverage` යතුරු _පැවතීම_ ගණන් කරන බැවින්, යල්පැන ගිය පරිවර්තනයක් ආවරණය වූවක් ලෙස ලකුණු ලබයි; -- `check-translation-drift` `docs/i18n//**.md` ලේඛන පිළිබිඹු නිරීක්ෂණය කරයි — - එය කිසිවිටෙක `src/i18n/messages/*.json` කියවන්නේ නැත. 2026-09 නැවත සමමුහුර්ත කිරීමේ සිට - `docs-sync-strict` කාර්යයේ අවහිර කරයි: මූලික ලේඛනයක් සංස්කරණය කරන්න → `npm run i18n:run -- --files=` (කොටස්-මට්ටමේ, අඩු වියදම්). +- `sync-ui-keys` පසුපුරවන්නේ **නොපවතින** keys පමණක් වන අතර, **පරණ** ඒවා කිසිවිටෙක නොවේ; +- `check-ui-keys-coverage` key _පැවතීම_ ගණනය කරන බැවින්, පරණ පරිවර්තනයක් ආවරණය වී ඇති ලෙස ලකුණු ලබයි; +- `check-translation-drift` `docs/i18n//**.md` ලේඛන දර්පණ නිරීක්ෂණය කරයි — + එය කිසිවිටෙක `src/i18n/messages/*.json` නොකියවයි. 2026-09 නැවත සමමුහුර්ත කිරීමේ සිට `docs-sync-strict` + කාර්යයේ අවහිර කරයි: ප්රධාන ලේඛනයක් සංස්කරණය කරන්න → `npm run i18n:run -- --files=` (කොටස් මට්ටමේ, අඩු වියදම්). -**වෙනස්කම් පිළිබඳ දැනුවත්, baseline මත පදනම් නොවූ.** එය merge base හි ඇති `en.json`, working tree එක සමඟ සසඳයි; ඉංග්රීසි අගය වෙනස් වූ සෑම key එකක් සඳහාම, තවමත් නොවෙනස් කළ පරිවර්තනයක් අඩංගු ඕනෑම locale එකක් යල්පැන ඇත. මෙය හිතාමතාම **පෙර සිට පැවති ණය ස්ථාවර කරයි** — දිගු කලක් පැවති පරිවර්තනයක් කුමන පැරණි ඉංග්රීසි පාඨයකින් පැමිණියේ දැයි diff එකකින් හෙළි කළ නොහැකි බැවින්, gate එක විනිශ්චය කරන්නේ වත්මන් වෙනස ස්පර්ශ කරන දෑ පමණි. විකල්පය වන (එක් එක් key එක සඳහා hash baseline එකක්) ~600 KB ප්රමාණයේ ජනනය කළ ගොනුවක් අවශ්ය කරනු ඇත; එය දැනට පවතින විශාලතම baseline එක මෙන් 3 ගුණයක් වන අතර සෑම i18n PR එකකදීම වෙනස් වනු ඇත. +**Diff පිළිබඳ දැනුවත්ය, මූලික පාදකයක් මත රඳා නොපවතී.** එය merge base හි ඇති `en.json`, working tree එක සමඟ සසඳයි; ඉංග්රීසි අගය වෙනස් වූ සෑම key එකක් සඳහාම, තවමත් වෙනස් නොකළ පරිවර්තනයක් දරන ඕනෑම locale එකක් යල්පැන ඇත. මෙය හිතාමතාම **පෙර සිට පවතින ණය ස්ථාවර කරයි** — දිගු කලක් පවතින පරිවර්තනයක් පැමිණියේ කුමන පැරණි ඉංග්රීසි පාඨයෙන්දැයි diff එකකට හෙළි කළ නොහැකි බැවින්, gate එක විනිශ්චය කරන්නේ වත්මන් වෙනස ස්පර්ශ කරන දේ පමණි. විකල්පය (key එකකට hash baseline එකක්) සඳහා ~600 KB ජනනය කළ ගොනුවක් අවශ්ය වන අතර, එය දැනට පවතින විශාලතම baseline එක මෙන් 3× විශාල වන අතර සෑම i18n PR එකකදීම වෙනස්කම් ඇති කරයි. -එය සපුරාලීමට ක්රම දෙකක් ඇත: +එය සපුරාලිය හැකි ක්රම දෙකකි: -1. බලපෑමට ලක්වූ පරිවර්තන යාවත්කාලීන කරන්න, හෝ -2. ඒවා `__MISSING__:` ලෙස සකසන්න — ඉන්පසු runtime එක නිවැරදි කළ ඉංග්රීසි පාඨය සපයයි - (`src/i18n/request.ts::deepMergeFallback`, #7258) සහ key එක පරිවර්තනය සඳහා පෝලිම්ගත වේ. +1. බලපෑමට ලක් වූ පරිවර්තන යාවත්කාලීන කරන්න, හෝ +2. ඒවා `__MISSING__:` ලෙස සකසන්න — එවිට runtime එක නිවැරදි කළ ඉංග්රීසි පාඨය සපයයි + (`src/i18n/request.ts::deepMergeFallback`, #7258), සහ key එක පරිවර්තනය සඳහා පෝලිම්ගත වේ. string එකේ **අර්ථය** වෙනස් වී ඇත්නම්, **key එක නැවත නම් කිරීම** වඩාත් සුදුසුය: නව key එකකට යල්පැන ගිය පරිවර්තනයක් උරුම විය නොහැක. #8463 භාවිත කළ රටාව එයයි. ```bash -npm run i18n:check-value-drift # දැඩි (CI ධාවනය කරන්නේ මෙයයි) +npm run i18n:check-value-drift # දැඩි (CI ධාවනය කරන ආකාරය) npm run i18n:check-value-drift:warn # වාර්තා කිරීම පමණි BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -මූලික catalog එක කියවිය නොහැකි විට (base ref එක නොමැති shallow clone එකක්), `check-openapi-breaking` අනුකරණය කරමින් `SKIP reason=base-unresolved` සමඟ 0 ලෙස පිටවේ. +base catalog එක කියවිය නොහැකි විට (base ref එක නොමැති shallow clone එකක්), `check-openapi-breaking` අනුකරණය කරමින් `SKIP reason=base-unresolved` සමඟ 0 ලෙස පිටවෙයි. ### කාර්යය: `i18n` -සම්පූර්ණ i18n වලංගුකරණ matrix එක (එක් locale එකකට එක් කාර්යයක්). මුළු කාර්යයම උපදේශාත්මකය. +සම්පූර්ණ i18n වලංගුකරණ න්යාසය (locale එකකට එක් කාර්යයක්). සම්පූර්ණ කාර්යයම උපදේශාත්මකය. -| Script | වලංගු කරන්නේ | අවහිර කිරීම | -| ------------------------------- | ---------------------------------------- | -------------------------------------------------------------- | -| `validate_translation.py quick` | එක් එක් locale එකේ පරිවර්තන සම්පූර්ණත්වය | **උපදේශාත්මකයි** (මුළු කාර්යය සඳහාම `continue-on-error: true`) | +| Script | වලංගු කරන්නේ | අවහිර කිරීම | +| ------------------------------- | --------------------------------- | -------------------------------------------------------------- | +| `validate_translation.py quick` | locale එකකට පරිවර්තන සම්පූර්ණත්වය | **උපදේශාත්මකයි** (සම්පූර්ණ කාර්යයේම `continue-on-error: true`) | ### කාර්යය: `pr-test-policy` -pull request සඳහා පමණක් ධාවනය වේ. +pull request මත පමණක් ධාවනය වේ. | Script | වලංගු කරන්නේ | අවහිර කිරීම | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, හෝ `bin/` හි production code වෙනස් කරන PR සඳහා tests ඇතුළත් කිරීම හෝ යාවත්කාලීන කිරීම අනිවාර්යයි (දැඩි නීතිය #8) | ඔව් | -| `check:test-masking` | වෙනස් කළ test ගොනු ශුද්ධ assert ගණන අඩු නොකරන අතර `assert.ok(true)` වැනි tautology එකතු නොකරයි | ඔව් | -| `check:pr-evidence` | PR body එක වෙනස සඳහා test/VPS සාක්ෂි උපුටා දක්වයි (PR ගද්යය grep කිරීමෙන් දැඩි නීතිය #18 යාන්ත්රික කරයි — බිඳෙනසුලුයි, Backlog බලන්න) | ඔව් | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, හෝ `bin/` තුළ production code වෙනස් කරන PR වල පරීක්ෂණ ඇතුළත් කිරීම හෝ යාවත්කාලීන කිරීම අනිවාර්යය (දැඩි රීතිය #8) | ඔව් | +| `check:test-masking` | වෙනස් කළ test ගොනු ශුද්ධ assert ගණන අඩු නොකරන අතර `assert.ok(true)` වැනි පුනරුක්ති එක් නොකරයි | ඔව් | +| `check:pr-evidence` | PR body එක වෙනස සඳහා test/VPS සාක්ෂි උපුටා දක්වයි (PR ගද්යය grep කිරීමෙන් දැඩි රීතිය #18 යාන්ත්රීකරණය කරයි — බිඳෙනසුලුය, Backlog බලන්න) | ඔව් | ### කාර්යය: `test-vitest` -`build` පසු ධාවනය වේ. අසාර්ථක වුවහොත් merge කිරීම අවහිර කරයි. +`build` පසු ධාවනය වේ. අසමත් වුවහොත් merge කිරීම අවහිර කරයි. -| Suite | වලංගු කරන්නේ | අවහිර කිරීම | -| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (tools 110), autoCombo, cache — vitest runner | ඔව් | -| `test:vitest:ui` | UI component tests — vitest runner | **අවහිර කරයි** — පෙර සිට පැවති අසාර්ථකත්ව `vitest.config.ts` තුළ පැහැදිලිව බැහැර කර ඇත; නව අසාර්ථකත්ව කාර්යය අසාර්ථක කරයි | +| Suite | වලංගු කරන්නේ | අවහිර කිරීම | +| ---------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (මෙවලම් 110), autoCombo, cache — vitest runner | ඔව් | +| `test:vitest:ui` | UI component පරීක්ෂණ — vitest runner | **අවහිර කරයි** — පෙර සිට පවතින අසමත්වීම් `vitest.config.ts` තුළ පැහැදිලිව බැහැර කර ඇත; නව අසමත්වීම් කාර්යය අසමත් කරයි | -### රාත්රී workflows (කාලසටහන්ගත, උපදේශාත්මක) +### රාත්රී workflow (කාලසටහන්ගත, උපදේශාත්මක) -මේවා cron කාලසටහනක් මත (සහ `workflow_dispatch` මඟින්) ධාවනය වන අතර කිසිවිටෙකත් PR මත ධාවනය නොවේ. සියල්ලම උපදේශාත්මකය. +මේවා cron කාලසටහනක් මත (සහ `workflow_dispatch`) ධාවනය වන අතර, PR මත කිසිවිටෙකත් ධාවනය නොවේ. සියල්ල උපදේශාත්මකය. -| Workflow | වලංගු කරන්නේ | අවහිර කිරීම | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `nightly-property` | අහඹු seed එකක් සහ ඉහළ ධාවන ගණනක් සහිත fast-check property tests | **උපදේශාත්මකයි** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **උපදේශාත්මකයි** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret එකක් නොමැති විට මඟ හරිනු ලැබේ) | **උපදේශාත්මකයි** | -| `nightly-schemathesis` | `docs/openapi.yaml` භාවිතයෙන් සජීවී OmniRoute එකකට එරෙහි OpenAPI contract fuzzing (schemathesis) — spec උල්ලංඝන / හසුරුවා නොගත් 500s මතු කරයි (Fase 8 B.4) | **උපදේශාත්මකයි** | -| `nightly-mutation` | වේගවත් unit lane එක මත Stryker mutation-testing ලකුණු — නොනැසී පවතින mutants දුර්වල asserts හෙළි කරයි | **උපදේශාත්මකයි** | -| `nightly-compat` | සහාය දක්වන `engines.node` පරාස හරහා Node engine compatibility matrix එක | **උපදේශාත්මකයි** | +| Workflow | වලංගු කරන්නේ | අවහිර කිරීම | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `nightly-property` | අහඹු seed එකක් සහ ඉහළ ධාවන ගණනක් සහිත fast-check property පරීක්ෂණ | **උපදේශාත්මකයි** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **උපදේශාත්මකයි** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret එකක් නොමැතිව මඟ හරිනු ලැබේ) | **උපදේශාත්මකයි** | +| `nightly-schemathesis` | `docs/openapi.yaml` භාවිත කරමින් සජීවී OmniRoute එකකට එරෙහි OpenAPI contract fuzzing (schemathesis) — spec උල්ලංඝන / හසුරුවා නොගත් 500s මතු කරයි (අදියර 8 B.4) | **උපදේශාත්මකයි** | +| `nightly-mutation` | වේගවත් unit lane එක මත Stryker mutation-testing ලකුණු — නොනැසී පවතින mutants දුර්වල asserts මතු කරයි | **උපදේශාත්මකයි** | +| `nightly-compat` | සහය දක්වන `engines.node` පරාස හරහා Node engine අනුකූලතා න්යාසය | **උපදේශාත්මකයි** | --- @@ -541,41 +535,41 @@ connection error එකක් log කිරීම පමණක් කරන අ ## තාර්කිකකරණ පසුබැසීම් ලැයිස්තුව (ROI සමාලෝචනය — අදියර 9 රැල්ල 3) -මෙම ඉන්වෙන්ටරිය 2026-06-17 දින `ci.yml` සමඟ සැසඳීමෙන් සමාදාන කරන ලදී (පෙර අනුවාදයේ +මෙම ලේඛනය 2026-06-17 දින `ci.yml` සමඟ සසඳා ප්රතිසන්ධානය කරන ලදී (පෙර අනුවාදයේ `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` අත්හැරී තිබුණි). සමාදාන කළ කට්ටලය පිළිබඳ ROI සමාලෝචනයකින් -පහත තාර්කිකකරණ අපේක්ෂකයන් හඳුනා ගන්නා ලදී. **ඒකාබද්ධ කිරීම් යාන්ත්රික CI -වෙනස්කම් වේ; තත්ත්ව මාරු කිරීම්/ඉවත් කිරීම් යනු ක්රියාකරු සඳහා වෙන් කළ ප්රතිපත්තිමය තීරණ වේ.** පහත කිසිවක් -තවම යොදා නැත. +`check:codeql-ratchet`, `check:pr-evidence` අත්හැරී තිබුණි). ප්රතිසන්ධානය කළ කට්ටලයේ ROI සමාලෝචනයකින් +පහත තාර්කිකකරණ අපේක්ෂකයන් හඳුනාගන්නා ලදී. **ඒකාබද්ධ කිරීම් යාන්ත්රික CI +වෙනස්කම් වේ; මාරු කිරීම්/ඉවත් කිරීම් ක්රියාකරු සඳහා වෙන් කළ ප්රතිපත්තිමය තීරණ වේ.** පහත කිසිවක් +තවම ක්රියාත්මක කර නොමැත. -**ඉහත ලේඛනගත කර නොමැති තවත් දෑ** (උපදේශාත්මක, අඩු සංඥාවක් සහිත): `docs-lint` කාර්යය -(markdownlint + Vale, සම්පූර්ණ කාර්යයම `continue-on-error`) සහ ස්වාධීන ස්කෑනර් කාර්ය ප්රවාහ +**ඉහත ලේඛනගත නොකළ තවත් දෑ** (උපදේශාත්මක, අඩු සංඥා සහිත): `docs-lint` කාර්යය +(markdownlint + Vale, සම්පූර්ණ කාර්යයටම `continue-on-error`) සහ ස්වාධීන ස්කෑනර් කාර්යප්රවාහ `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` යන්න -`quality-baseline.json` තුළ ඇතත්, `ci.yml` හි අවහිර කරන රැචට්ටුවකට සම්බන්ධ කර නැත — මෙම ප්රමිතිකය +`quality-baseline.json` තුළ ඇත, නමුත් `ci.yml` හි අවහිරකාරී රැචට්ටුවකට සම්බන්ධ කර නැත — මෙම මෙට්රික් එක දැනට අනාථව ඇත. ### ඒකාබද්ධ කිරීම / අනුපිටපත් ඉවත් කිරීම (යාන්ත්රික, අඩු අවදානමක්) -සෑම අපේක්ෂකයෙකුම 2026-06-17 දින සජීවී ද්වාර තත්ත්වයට එරෙහිව වලංගු කරන ලදී (විශ්වාස කරන්න, නමුත් සත්යාපනය කරන්න); -“පැහැදිලි” ලෙස පෙනුණු ඒකාබද්ධ කිරීම් කිහිපයක් ණය සඟවා ඇති බව පෙනී ගිය අතර ඒවා **පිරිසිදු සෘජු ආදේශන නොවේ**. - -- **`check:docs-sync` දෙවරක් ක්රියාත්මක වේ** — `lint` කාර්යය තුළ ස්වාධීනවද, නැවත `check:docs-all` (`docs-sync-strict`) තුළ සහ husky pre-commit hook එක තුළද ක්රියාත්මක වේ. ✅ **අවසන්** — ස්වාධීන `lint` ආමන්ත්රණය ඉවත් කරන ලදී. -- **CVE ස්කෑන් කිරීම** — ❌ **පිරිසිදු ඒකාබද්ධ කිරීමක් නොවේ.** `audit:deps` ඕනෑම ඉහළ/අතිශය තීරණාත්මක CVE එකක් මත දැඩි ලෙස අසාර්ථක වේ; `check:vuln-ratchet` (osv) අසාර්ථක වන්නේ මූලික මට්ටමට සාපේක්ෂව _පසුබෑමක්_ ඇති විට පමණි (දැනට MODERATE 1ක්). අර්ථවිද්යාව වෙනස්ය — `audit:deps` ඉවත් කිරීමෙන් නිරපේක්ෂ ඉහළ/අතිශය තීරණාත්මක ද්වාරය අහිමි වනු ඇත. දෙකම තබා ගන්න. -- **චක්ර හඳුනාගැනීම** — ❌ **පිරිසිදු ඒකාබද්ධ කිරීමක් නොවේ.** `check:circular-deps` (dpdm) මඟින් **චක්ර 91ක්** වාර්තා කරයි (එය උපදේශාත්මක වන්නේ ඒ නිසාය); ඒවා මුලින් විසඳීමකින් තොරව එය අවහිර කරන තත්ත්වයට උසස් කළ නොහැකි අතර, හරිත තත්ත්වයේ පවතින තෝරාගත් `check:cycles` ට වඩා පුළුල් විෂය පථයක් එයට ඇත. `check:cycles` අවහිර කරන ලෙස තබා ගන්න; dpdm චක්ර 91 විසඳීම වෙනම පසුබැසීම් කාර්යයකි. -- **සංකීර්ණත්වය** — ✅ **අවසන්** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): එක් ESLint පරිලෝකනයක්, `ruleId` අනුව ගණනය කරන බැවින් cyclomatic+max-lines සහ cognitive මූලික මට්ටම් ස්වාධීනව පවතී; දේශීය `--update` සඳහා තනි `check:complexity` / `check:cognitive-complexity` තවදුරටත් පවතී. -- **`/api` ප්රති-භ්රාන්තිය** — ✅ **අවසන්** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` සඳහා එක් FS ඉන්වෙන්ටරියක්, openapi-routes + docs-symbols තවමත් ස්වාධීනව වාර්තා කරයි; දේශීය ක්රියාත්මක කිරීම් සඳහා තනි ඒවා තවදුරටත් පවතී. -- **`check:node-runtime` කාර්ය 11ක ක්රියාත්මක වේ** — ⚠️ **අඩු ROI.** සෑම එකක්ම වෙනම runner එකක් වන අතර පරීක්ෂාව තත්පර 1කට අඩුය; ලාභදායී එක්-කාර්ය ආරක්ෂකයක් අහිමි කිරීමට සාපේක්ෂව, සමස්ත ඉතිරිය තත්පර ~10කි. මෙම කැලඹීම වටින්නේ නැත. -- **CI lint මත `typecheck:noimplicit:core`** — ✅ **lint කාර්යයෙන් ඉවත් කරන ලදී** (එය උපදේශාත්මක `continue-on-error` එකක් විය); අවහිර කරන type පෘෂ්ඨය `typecheck:core` + `check:type-coverage` වේ. දේශීය ස්ක්රිප්ට් එක රඳවා ඇත. - -### තත්ත්වය මාරු කිරීම / තීරණය කිරීම (ක්රියාකරු ප්රතිපත්තිය) - -- `check:openapi-security-tiers` (උපදේශාත්මක) — ❌ **පිරිසිදුව තත්ත්වය මාරු කළ නොහැක.** එය 0 සමඟ පිටවන නමුත්, `LOCAL_ONLY_API_PREFIXES` යටතේ ඇති `traffic-inspector` මාර්ග කිහිපයක `x-loopback-only: true` අනුසටහන නොමැති බවට අනතුරු අඟවයි. එය බලාත්මක කිරීමට පළමුව එම අනුසටහන් `openapi.yaml` වෙත එක් කිරීම අවශ්ය වේ. -- `typecheck:noimplicit:core` (උපදේශාත්මක) — අවහිර කරන `check:type-coverage` රැචට්ටුව මඟින් බොහෝ දුරට ආවරණය කර ඇත. එය රැචට්ටුවකට මාරු කරන්න, නැතහොත් අතිරික්ත දෙවන `tsc` වාරය ඉවත් කරන්න. -- `test:vitest:ui` (දැන් **අවහිර කරන**) — පෙර සිට පැවති අසාර්ථකවීම් `vitest.config.ts` තුළ `// #8618` ලුහුබැඳීමේ අදහස් සමඟ පැහැදිලිව බැහැර කර ඇත; නව අසාර්ථකවීම් කාර්යය අසාර්ථක කරයි. -- `check:secrets` (gitleaks, ලේඛනගත ව්යාජ ධනාත්මක 3කදී ස්ථිර කළ අවහිර කරන රැචට්ටුව) — 0 දක්වා ළඟා වීමට එම 3 අවසර ලැයිස්තුවට එක් කරන්න, නැතහොත් උපදේශාත්මක තත්ත්වයට පහත හෙළන්න. GitHub හි ස්වදේශීය secret-scanning + `check:public-creds` සමඟ අතිච්ඡාදනය වේ. -- `check:pr-evidence` (අවහිර කරන, PR-body ගද්යය තුළ සොයයි) — ව්යාජ ධනාත්මක සඳහා ඉහළ අවදානමක් ඇත; ඉවත් කළහොත් Hard Rule #18 බලාත්මක කිරීම දුර්වල වේ, එබැවින් මෙය සැබෑ ප්රතිපත්තිමය තීරණයකි. -- `semgrep` (ස්වාධීන උපදේශාත්මක කාර්යයක්) — OWASP පවුල් සම්බන්ධයෙන් CodeQL සමඟ අතිච්ඡාදනය වේ; එහි මූලික මට්ටම රැචට්ටුවකට සම්බන්ධ කරන්න, නැතහොත් ඉවත් කරන්න. +සෑම අපේක්ෂකයෙකුම 2026-06-17 දින සජීවී ද්වාර තත්ත්වයට එරෙහිව වලංගු කරන ලදී (විශ්වාස කරන්න-නමුත්-තහවුරු කරන්න); +“පැහැදිලි” ලෙස පෙනුණු ඒකාබද්ධ කිරීම් කිහිපයක් ණය සඟවා ඇති බව හෙළි වූ අතර ඒවා **පිරිසිදු** සෘජු ආදේශන නොවේ. + +- **`check:docs-sync` දෙවරක් ධාවනය වේ** — `lint` කාර්යය තුළ ස්වාධීනව සහ නැවත `check:docs-all` (`docs-sync-strict`) තුළ මෙන්ම husky pre-commit හුක් එක තුළද. ✅ **නිමයි** — ස්වාධීන `lint` ආමන්ත්රණය ඉවත් කරන ලදී. +- **CVE ස්කෑන් කිරීම** — ❌ **පිරිසිදු ඒකාබද්ධ කිරීමක් නොවේ.** ඕනෑම ඉහළ/අතිශය වැදගත් CVE එකක් සඳහා `audit:deps` දැඩි ලෙස අසමත් වේ; `check:vuln-ratchet` (osv) අසමත් වන්නේ මූලික රේඛාවට සාපේක්ෂව _පසුබෑමක්_ ඇති විට පමණි (දැනට 1 MODERATE). අර්ථකථන වෙනස්ය — `audit:deps` ඉවත් කිරීමෙන් නිරපේක්ෂ ඉහළ/අතිශය වැදගත් ද්වාරය අහිමි වනු ඇත. දෙකම තබා ගන්න. +- **චක්ර හඳුනාගැනීම** — ✅ **නිමයි** (#15159 G-01/G-02). මෙහි පැරණි පාඨය `check:cycles` යන්න “හරිත, තෝරා සැකසූ” ද්වාරය ලෙස හැඳින්වූ අතර `check:circular-deps` (dpdm) චක්ර 91ක් වාර්තා කළ බැවින් එය අවහිරකාරීව තබා ගැනීම සාධාරණීකරණය කළේය. එම හරිත තත්ත්වය **ව්යාජ හරිත තත්ත්වයක්** විය: `check:cycles` උපබහලුම් 5ක් (ගොනු 450ක්) ස්කෑන් කළ අතර, ස්ථිතික `import|export … from` පමණක් ගැළපූ අතර, සෑම `@/` සහ `@omniroute/open-sse/` විශේෂකයක්ම ඉවත් කළේය; එබැවින් ගබඩාව පුරා ප්රමුඛ වූ ගතික-import + alias චක්ර එයට දැකගත නොහැකි විය. නිවැරදි කරන ලදී: ද්වාරය දැන් `src` + `open-sse` (ගොනු 5023ක්) හරහා ගමන් කරයි, TypeScript AST වෙතින් විශේෂක රැස් කරයි (ඒ අනුව `import("…")` ගණන් ගන්නා අතර type-position `typeof import("…")` ගණන් නොගනී), සහ tsconfig `paths` විසඳයි. එය 0ක් නොව, චක්ර **14ක්** සොයා ගනී. පෙර පැවති චක්ර 14ක් ද්වාර PR එකක් තුළ නිවැරදි කළ නොහැකි බැවින්, `check:cycles` දැන් **රැචට්ටුවකි** (`--ratchet`, `quality-baseline.json` තුළ සීමාව `metrics.cycles.value = 14`, `direction: down`) — එය ඕනෑම _පසුබෑමක්_ අවහිර කරන අතර ගණනට අඩු විය හැක්කේ පමණි. CI විසින් `npm run check:cycles:ratchet` ධාවනය කරයි. අඩු කිරීම **A-01** සමඟ සිදු වේ. පුළුල් දෙවන මතයක් ලෙස `check:circular-deps` (dpdm) උපදේශාත්මකව පවතී. +- **සංකීර්ණත්වය** — ✅ **නිමයි** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): එක් ESLint ගමන් කිරීමක්, චක්රීය+උපරිම-පේළි සහ සංජානන මූලික රේඛා ස්වාධීනව තබා ගැනීමට ruleId අනුව ගණන් කරයි; තනි `check:complexity` / `check:cognitive-complexity` දේශීය `--update` සඳහා පවතී. +- **`/api` ප්රති-මායාකල්පනය** — ✅ **නිමයි** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` සඳහා එක් FS ලැයිස්තුගත කිරීමක්, openapi-routes + docs-symbols තවමත් ස්වාධීනව වාර්තා කරයි; තනි ධාවන දේශීය ධාවන සඳහා පවතී. +- **`check:node-runtime` කාර්ය 11ක් තුළ ධාවනය වේ** — ⚠️ **අඩු ROI.** සෑම එකක්ම වෙනම ධාවකයක් වන අතර පරීක්ෂාව තත්පර 1කට අඩුය; ලාභය සමස්තයෙන් ~තත්පර 10ක් වන අතර, ඒ වෙනුවෙන් ලාභදායී එක්-කාර්ය ආරක්ෂකයක් අහිමි වේ. මෙතරම් වෙනස්කම් කිරීම වටින්නේ නැත. +- **CI lint හි `typecheck:noimplicit:core`** — ✅ **lint කාර්යයෙන් ඉවත් කරන ලදී** (එය උපදේශාත්මක `continue-on-error` එකක් විය); අවහිරකාරී වර්ග පෘෂ්ඨය `typecheck:core` + `check:type-coverage` වේ. දේශීය ස්ක්රිප්ටය තබා ගන්නා ලදී. + +### මාරු කිරීම / තීරණය කිරීම (ක්රියාකරු ප්රතිපත්තිය) + +- `check:openapi-security-tiers` (උපදේශාත්මක) — ❌ **පිරිසිදුව මාරු කළ නොහැක.** එය 0 සමඟ පිටවන නමුත් `LOCAL_ONLY_API_PREFIXES` යටතේ ඇති `traffic-inspector` මාර්ග කිහිපයක `x-loopback-only: true` සටහන නොමැති බවට අනතුරු අඟවයි. එය බලාත්මක කිරීමට පළමුව එම සටහන් `openapi.yaml` වෙත එක් කළ යුතුය. +- `typecheck:noimplicit:core` (උපදේශාත්මක) — අවහිරකාරී `check:type-coverage` රැචට්ටුව මගින් බොහෝ දුරට ආවරණය වේ. රැචට්ටුවකට මාරු කරන්න හෝ අතිරික්ත දෙවන `tsc` ගමන් කිරීම ඉවත් කරන්න. +- `test:vitest:ui` (දැන් **අවහිරකාරී**) — පෙර පැවති අසමත්වීම් `vitest.config.ts` තුළ `// #8618` ලුහුබැඳීමේ අදහස් සමඟ පැහැදිලිව බැහැර කර ඇත; නව අසමත්වීම් කාර්යය අසමත් කරයි. +- `check:secrets` (gitleaks, ලේඛනගත ව්යාජ-ධනාත්මක 3ක ස්ථිර කළ අවහිරකාරී රැචට්ටුවක්) — 0 වෙත ළඟා වීමට එම 3 අවසර ලැයිස්තුවට එක් කරන්න, නැතහොත් උපදේශාත්මක තත්ත්වයට පහත හෙළන්න. GitHub හි ස්වදේශීය රහස්-ස්කෑන් කිරීම + `check:public-creds` සමඟ අතිච්ඡාදනය වේ. +- `check:pr-evidence` (අවහිරකාරී, PR-body ගද්යය තුළ සොයයි) — ව්යාජ-ධනාත්මක අවදානම ඉහළය; ඉවත් කළහොත් දැඩි රීතිය #18 බලාත්මක කිරීම දුර්වල වන බැවින්, මෙය සැබෑ ප්රතිපත්තිමය තීරණයකි. +- `semgrep` (උපදේශාත්මක ස්වාධීන කාර්යප්රවාහයක්) — OWASP පවුල් සඳහා CodeQL සමඟ අතිච්ඡාදනය වේ; එහි මූලික රේඛාව රැචට්ටුවකට සම්බන්ධ කරන්න හෝ ඉවත් කරන්න. --- diff --git a/docs/i18n/si/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/si/docs/compression/COMPRESSION_GUIDE.md index f15b36f76c30..63af6998e67c 100644 --- a/docs/i18n/si/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/si/docs/compression/COMPRESSION_GUIDE.md @@ -451,8 +451,7 @@ Caveman ප්රතිදාන ආකාරය **අභිමතයෙන් `messages` සහිත body එකක, අන්තර්ගත මඟහැරීමක් (`shouldBypassCavemanOutputMode()` තුළ `open-sse/services/compression/outputMode.ts`) අවසන් පණිවිඩ තුන පරීක්ෂා කර, ඒවා එහි ආරක්ෂක, ආපසු හැරවිය නොහැකි ක්රියා, පැහැදිලි කිරීමේ, හෝ අනුපිළිවෙළට සංවේදී මූලපදවලට ගැළපෙන විට -මුළු turn එක සඳහාම ශෛලි මඟහරියි. Dashboard එකේ -**ස්වයංක්රීය පැහැදිලිත්ව මඟහැරීමේ** toggle එක (`cavemanOutputMode.autoClarity`) සකසා ඇති අගය කුමක් වුවත් මඟහැරීම ක්රියාත්මක වේ. +මුළු turn එක සඳහාම ශෛලි මඟහරියි. Dashboard එකේ **ස්වයංක්රීය පැහැදිලිත්ව මඟහැරීමේ** toggle එක (`cavemanOutputMode.autoClarity`) සක්රීයව පවතින තුරු මඟහැරීම ක්රියාත්මක වේ, මෙය පෙරනිමියයි; toggle එක අක්රීය වූ විට, තෝරාගත් ශෛලීන් එම අවස්ථාවලදී ද යොදවයි. මඟහැරීම turn එකට ඉදිරියට යාමට ඉඩ දෙන විට, `placeSystemInstruction()` (එම ගොනුවේම), එය කිසිවිටෙක නව `messages[0]` එකක් නොසාදන අතර, සොයාගන්නා පහත පළමු ස්ථානයේ diff --git a/docs/i18n/si/docs/reference/ENVIRONMENT.md b/docs/i18n/si/docs/reference/ENVIRONMENT.md index 789ee894cb8b..5b6bdfae9818 100644 --- a/docs/i18n/si/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/si/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. අවශ්ය රහස් -පළමු ධාවනයට පෙර මේවා **අනිවාර්යයෙන්ම** සකසා තිබිය යුතුය. ඒවා නොමැතිව, යෙදුම ආරම්භ වීම ප්රතික්ෂේප කරනු ඇත, නැතහොත් අනාරක්ෂිත පෙරනිමි සමඟ ක්රියා කරනු ඇත. - -| විචල්යය | අවශ්යයි | පෙරනිමිය | මූලාශ්ර ගොනුව | විස්තරය | -| ---------------------------- | ---------------------- | ----------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **ඔව්** | _(නැත)_ | `src/lib/auth` | සියලුම උපකරණ පුවරු සැසි කුකී (JWT) අත්සන් කරයි/සත්යාපනය කරයි. `openssl rand -base64 48` මඟින් ජනනය කරන්න. | -| `API_KEY_SECRET` | **ඔව්** | _(නැත)_ | `src/lib/db/apiKeys.ts` | SQLite තුළ නිශ්චලව ඇති API යතුරු අගයන් සඳහා AES සංකේතාංකන යතුර. `openssl rand -hex 32` මඟින් ජනනය කරන්න. | -| `INITIAL_PASSWORD` | **ඔව්** | `CHANGEME` | ආරම්භක සැකසුම් ස්ක්රිප්ටය | ආරම්භක පරිපාලක උපකරණ පුවරු මුරපදය සකසයි (`.env.example` පෙරනිමියට ගැළපේ — වෙනස් කිරීම අනිවාර්ය කිරීම සඳහා පැහැදිලිවම අනාරක්ෂිත ලෙස තබා ඇත). **පළමු භාවිතයට පෙර වෙනස් කරන්න.** පිවිසීමෙන් පසු, උපකරණ පුවරුව → සැකසුම් → ආරක්ෂාව හරහා වෙනස් කරන්න. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **ඔව්** (නිෂ්පාදනයේදී) | _(සකසා නැත)_ | `src/app/api/internal/codex-responses-ws/route.ts` | අභ්යන්තර Codex Responses WebSocket පාලම සඳහා හවුල් රහස. Electron/බ්රවුසර WS රිලේ සහ OmniRoute අතර පාලම් ඉල්ලීම් සත්යාපනය කරයි. ⚠️ **නිෂ්පාදනයේදී අනිවාර්යයි — මෙය සකසා නොමැති විට, සියලුම WS පාලම් ඉල්ලීම් ප්රතික්ෂේප වේ.** `openssl rand -base64 32` මඟින් ජනනය කරන්න. | -| `OMNIROUTE_SW_BUILD_ID` | නැත | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA කවචය සඳහා පැහැදිලි සේවා-කාර්යය cache-busting හැඳුනුම්කාරකය (#11779); විසඳුම් දාමයේ පළමුවැන්නයි. | -| `SOURCE_VERSION` | නැත | _(සකසා නැත)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | දාමයේ දෙවැන්නයි — යොදවා ඇති commit එක ලෙස PaaS ගොඩනැඟුම් පද්ධති (උදා. Heroku ආකාරයේ) විසින් සකසනු ලැබේ. | -| `NEXT_PUBLIC_SW_BUILD_ID` | නැත | _(ව්යුත්පන්න කළ)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` ලියාපදිංචි කිරීමට සේවාලාභියා භාවිත කරන ගොඩනැඟීම්-කාලීන පොදු අගය; ඉහත දෙකෙන් සහ ඉන්පසු git SHA එකෙන් ව්යුත්පන්න කෙරේ. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | නැත (ස්වයංක්රීය) | _(සෑම ආරම්භයකදීම ස්වයංක්රීය)_ | `src/server/authz/policies/management.ts` | විශ්වාසදායක සම-IP මුද්රාව OmniRoute හිම HTTP සේවාදායකයෙන් (`scripts/dev/peer-stamp.mjs`) පැමිණි බව සනාථ කරන එක් ක්රියාවලියකට අදාළ රහස. මුද්රාවෙහි මෙම ටෝකනය තිබෙන විට පමණක් authz මැදිමෘදුකාංගය ඉල්ලීමේ ස්ථානීයභාවය (LOCAL_ONLY මාර්ගවල loopback/LAN පාලනය) විශ්වාස කරයි. සෑම ආරම්භයකදීම ස්වයංක්රීයව ජනනය වේ — සකසා නොතබන්න; මුද්රාව හවුලේ භාවිත කළ යුතු බහු-ක්රියාවලි සැකසුම් සඳහා පමණක් එය ස්ථිර කරන්න. | -| `OMNIROUTE_TRUSTED_PROXIES` | නැත | _(සකසා නැත)_ | `scripts/dev/peer-stamp.mjs` | සෑම විටම විශ්වාස කරන loopback, පෞද්ගලික-ජාල ලිපින සහ Cloudflare අග්රවලට අමතරව, IP අවසර/ප්රතික්ෂේප ලැයිස්තුවට විශ්වාස කළ හැකි `X-Forwarded-For` / `X-Real-IP` සහිත ප්රතිලෝම ප්රොක්සිවල කොමාවෙන් වෙන් කළ IP හෝ CIDR පරාස. වෙනත් ඕනෑම පොදු ලිපිනයක ඇති ප්රොක්සියක් සඳහා පමණක් මෙය අවශ්ය වේ; එසේ නොමැති නම් පෙරහන විනිශ්චය කරන්නේ එම ප්රොක්සියේම ලිපිනයයි. | +**ඔව්** ලෙස ලකුණු කර ඇති, සකසා නොමැති රහස් පළමු ධාවනයේදී ස්වයංක්රීයව ජනනය වී (`JWT_SECRET`, `API_KEY_SECRET`) දත්ත නාමාවලියේ `server.env` තුළ සුරකිනු ලැබේ — එම නාමාවලිය තාවකාලික (ephemeral) නම් ඒවා පැහැදිලිවම සකසන්න. ව්යතිරේකය `OMNIROUTE_WS_BRIDGE_SECRET` ය: එය `server.env` තුළ සුරකිනු නොලැබේ — server entrypoint, එක් boot එකකට සීමා වූ තාවකාලික අගයක් නිර්මාණය කරන අතර එය ක්රියාත්මක වන සැරයට තුළ ඇති පාලමට පමණක් භාවිත කළ හැකිය; එබැවින් බාහිර පාලම් client සඳහා එය පැහැදිලිවම සැකසිය යුතුය. + +| විචල්යය | අවශ්යයි | පෙරනිමිය | මූලාශ්ර ගොනුව | විස්තරය | +| ---------------------------- | ---------------------- | ----------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **ඔව්** | _(නැත)_ | `src/lib/auth` | සියලුම උපකරණ පුවරු සැසි කුකී (JWT) අත්සන් කරයි/සත්යාපනය කරයි. `openssl rand -base64 48` මඟින් ජනනය කරන්න. | +| `API_KEY_SECRET` | **ඔව්** | _(නැත)_ | `src/lib/db/apiKeys.ts` | SQLite තුළ නිශ්චලව ඇති API යතුරු අගයන් සඳහා AES සංකේතාංකන යතුර. `openssl rand -hex 32` මඟින් ජනනය කරන්න. | +| `INITIAL_PASSWORD` | නැත | _(සකසා නැත)_ | `src/lib/auth/managementPassword.ts` | තවම මුරපදයක් සුරකියා නොමැති අතරතුර, ආරම්භයේදී dashboard මුරපදය සකසයි; මුරපදයක් සුරකින ලද පසු එය නොසලකා හරිනු ලැබේ. npm සහ source ස්ථාපන, `.env.example` එක `.env` එකට පිටපත් කරන බැවින්, ඒවා එහි ප්රසිද්ධව දන්නා placeholder එක වන `CHANGEME` සමඟ ආරම්භ වේ: පළමු boot එකට පෙර එය ප්රතිස්ථාපනය කරන්න, නැතහොත් පළමු සංචාරයේදී onboarding wizard එක තුළ මුරපදය සෑදීමට එම `.env` තුළ `INITIAL_PASSWORD=` (හිස්) ලෙස සකසන්න — සකසා නොමැති අගයක් (Docker image එකේ පෙරනිමිය) කරන දේ ද එයයි. සුරකින ලද මුරපදයක් උපකරණ පුවරුව → සැකසුම් → ආරක්ෂාව තුළින් වෙනස් කරන්න, නැතහොත් server එකේ දත්ත නාමාවලියට `DATA_DIR` සකසා `omniroute reset-password` (source checkout එකකදී `node bin/reset-password.mjs`) ක්රියාත්මක කරන්න. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **ඔව්** (නිෂ්පාදනයේදී) | _(සකසා නැත)_ | `src/app/api/internal/codex-responses-ws/route.ts` | අභ්යන්තර Codex Responses WebSocket පාලම සඳහා හවුල් රහස. Electron/බ්රවුසර WS රිලේ සහ OmniRoute අතර පාලම් ඉල්ලීම් සත්යාපනය කරයි. ⚠️ **නිෂ්පාදනයේදී අනිවාර්යයි — මෙය සකසා නොමැති විට, සියලුම WS පාලම් ඉල්ලීම් ප්රතික්ෂේප වේ.** `openssl rand -base64 32` මඟින් ජනනය කරන්න. | +| `OMNIROUTE_SW_BUILD_ID` | නැත | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA කවචය සඳහා පැහැදිලි සේවා-කාර්යය cache-busting හැඳුනුම්කාරකය (#11779); විසඳුම් දාමයේ පළමුවැන්නයි. | +| `SOURCE_VERSION` | නැත | _(සකසා නැත)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | දාමයේ දෙවැන්නයි — යොදවා ඇති commit එක ලෙස PaaS ගොඩනැඟුම් පද්ධති (උදා. Heroku ආකාරයේ) විසින් සකසනු ලැබේ. | +| `NEXT_PUBLIC_SW_BUILD_ID` | නැත | _(ව්යුත්පන්න කළ)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…` ලියාපදිංචි කිරීමට සේවාලාභියා භාවිත කරන ගොඩනැඟීම්-කාලීන පොදු අගය; ඉහත දෙකෙන් සහ ඉන්පසු git SHA එකෙන් ව්යුත්පන්න කෙරේ. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | නැත (ස්වයංක්රීය) | _(සෑම ආරම්භයකදීම ස්වයංක්රීය)_ | `src/server/authz/policies/management.ts` | විශ්වාසදායක සම-IP මුද්රාව OmniRoute හිම HTTP සේවාදායකයෙන් (`scripts/dev/peer-stamp.mjs`) පැමිණි බව සනාථ කරන එක් ක්රියාවලියකට අදාළ රහස. මුද්රාවෙහි මෙම ටෝකනය තිබෙන විට පමණක් authz මැදිමෘදුකාංගය ඉල්ලීමේ ස්ථානීයභාවය (LOCAL_ONLY මාර්ගවල loopback/LAN පාලනය) විශ්වාස කරයි. සෑම ආරම්භයකදීම ස්වයංක්රීයව ජනනය වේ — සකසා නොතබන්න; මුද්රාව හවුලේ භාවිත කළ යුතු බහු-ක්රියාවලි සැකසුම් සඳහා පමණක් එය ස්ථිර කරන්න. | +| `OMNIROUTE_TRUSTED_PROXIES` | නැත | _(සකසා නැත)_ | `scripts/dev/peer-stamp.mjs` | සෑම විටම විශ්වාස කරන loopback, පෞද්ගලික-ජාල ලිපින සහ Cloudflare අග්රවලට අමතරව, IP අවසර/ප්රතික්ෂේප ලැයිස්තුවට විශ්වාස කළ හැකි `X-Forwarded-For` / `X-Real-IP` සහිත ප්රතිලෝම ප්රොක්සිවල කොමාවෙන් වෙන් කළ IP හෝ CIDR පරාස. වෙනත් ඕනෑම පොදු ලිපිනයක ඇති ප්රොක්සියක් සඳහා පමණක් මෙය අවශ්ය වේ; එසේ නොමැති නම් පෙරහන විනිශ්චය කරන්නේ එම ප්රොක්සියේම ලිපිනයයි. | ### ජනන විධාන @@ -216,38 +216,38 @@ OmniRoute සියලුම දත්ත ස්ථිරව තබාගැන ## 4. ආරක්ෂාව සහ සත්යාපනය -| විචල්යය | පෙරනිමිය | මූලාශ්ර ගොනුව | විස්තරය | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | යන්ත්ර ඇඟිලි සලකුණක් නිර්මාණය කිරීම සඳහා දෘඪාංග හඳුනාගැනීම් සමඟ ඒකාබද්ධ කරන salt අගය. හුදකලා කිරීම සඳහා එක් එක් යෙදවීම අනුව මෙය වෙනස් කරන්න. | -| `OMNIROUTE_CLI_SALT` | _(සකසා නැත = එක් එක් ස්ථාපනය සඳහා අහඹු salt අගයක් `/cli-token-salt.json` හි ස්ථිරව සුරකියි)_ | `src/lib/machineToken.ts` | දේශීය CLI සත්යාපන ටෝකනය ව්යුත්පන්න කිරීම සඳහා භාවිත කරන HMAC salt අගය. මෙම අගය සැකසීමෙන් යන්ත්රයේ සියලුම CLI ටෝකන මාරු වන අතර, එය සෑම විටම ස්ථිරව සුරකින ලද salt අගයට වඩා ප්රමුඛත්වය ගනී. `docs/security/CLI_TOKEN.md` බලන්න. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | සැසි කුකීවල `Secure` ධජය සකසයි. HTTPS පසුපස ධාවනය වන විට මෙය **`true` විය යුතුය**. | -| `REQUIRE_API_KEY` | `false` | API මිඩ්ල්වෙයාර් | `true` වූ විට, සියලුම `/v1/*` ප්රොක්සි ඉල්ලීම්වල වලංගු API යතුරක් ඇතුළත් විය යුතුය. **මෙම ධජය `GET /v1/models` වෙත ප්රවේශය පාලනය නොකරයි**; ඒ වෙනුවට එය උපකරණ පුවරුවේ පිවිසුම් තත්ත්වය (`requireAuthForModels`) අනුගමනය කරයි — එබැවින් `/v1/models` වෙතින් ලැබෙන `401` ප්රතිචාරයකින් inference ආරක්ෂිත බව අදහස් **නොවේ**. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) බලන්න. | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | උපකරණ පුවරු UI තුළ සම්පූර්ණ API යතුරු අගයන් අනාවරණය කිරීමට ඉඩ දෙයි. උපකරණ පුවරුවේ විශේෂාංග ධජ හරහා වින්යාස කළ හැක; හවුල් අවස්ථාවලදී මෙය ආරක්ෂක අවදානමකි. | -| `NO_LOG_API_KEY_IDS` | _(හිස්)_ | `src/lib/compliance/index.ts` | ඉල්ලීම් ලොග් කිරීම මඟහරින, කොමාවලින් වෙන් කළ API යතුරු ID ලැයිස්තුවක් (GDPR අනුකූලතාව සඳහා). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(සකසා නැත = සීමාවක් නැත)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` තීරුව null වන API යතුරු සඳහා යොදන දිනකට ඉල්ලීම් සංඛ්යාවේ ආපසු යොදාගන්නා සීමාව. සකසා නොමැති හෝ හිස් නම්: ව්යංග සීමාවක් නැත (#2289, #11017). `0` ද එයට සමානය (සීමාවක් නැත). ධන නිඛිල N අගයක් මඟින් N/දිනකට, 5N/සතියකට සහ 20N/මාසයකට සක්රිය කරයි. වැරදි ආකෘතියකින් යුත් හිස් නොවන අගයන් පැරණි 1000/දිනකට, 5000/සතියකට සහ 20000/මාසයකට කවුළු වෙත ආපසු යයි. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | අවසර දී ඇති උපරිම ඉල්ලීම් body ප්රමාණය. මෙම සීමාව ඉක්මවන payload ප්රතික්ෂේප කරයි. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | මෙම සීමාවට සමාන හෝ එය ඉක්මවන සත්ය ඉල්ලීම් body, JSON විග්රහ කිරීමට පෙර පරමාණුක ක්රියාවලි-දේශීය heavyweight admission lease එක ලබාගනී (`POST /v1/responses` ඇතුළත් BYTE මාර්ගය). ව්යුහ-බර ඉල්ලීම්වල මෙන්ම එකම [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) සෞඛ්ය සම්පන්න අතිරික්ත ධාරිතා ගැලවීම භාවිත කරයි; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) නැවත ඇති නොවන ලෙස, තවමත් `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) මඟින් සීමා කර ඇත. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | සීමා කළ ආදානය අතරතුර කියවන ලද බයිට්වලට එරෙහිව බලාත්මක කරන chat-route දැඩි සීමාව; නොමැති, වලංගු නොවන, හෝ වංචනික `Content-Length` සහිත ඉල්ලීම්ද මෙයට ඇතුළත් වේ; සීමාව ඉක්මවූ විට `413` ලැබේ. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(සකසා නැත — ඉල්ලීම්-ගණන සීමාවක් නැත)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** මෙම පැරණි ඉල්ලීම්-COUNT සීමාව දැන් බලපාන්නේ එය පැහැදිලිව සකසා ඇති විට පමණි. එය සකසා නොතැබූ විට (පෙරනිමිය), heavyweight chat ඇතුළත් කිරීම ඒ වෙනුවට `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` මඟින් පාලනය වේ — **එක් process එකක** (එක් V8 heap එකක) සැබෑ මතක සීමාවෙන් ප්රමාණගත කර ස්වයංක්රීයව ව්යුත්පන්න කළ BYTE අයවැයකි. එකිනෙක අතිච්ඡාදනය වන ~750k-token `/v1/responses` දෙකක් ~12 Gi heaps අවසන් කරයි (#7849) — මෙය **මතක-අයවැය** අනතුරු ඇඟවීමක් මිස 2ක දැඩි නිෂ්පාදන උපරිමයක් නොවේ. සෞඛ්ය සම්පන්න process එකකට (heap එක shed අනුපාතයට පහළින් ඇති විට) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` හරහා තවත් සමගාමී දිගු `/v1/responses` ඇතුළත් කළ හැක. දිගු SSE සේවාලාභීන් දස ගණනක් (40–50) යනු heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 මත රඳා පවතින්නක් මිස “උපරිම 2” නොවේ. “host එක භාවිත කිරීමට” මෙය අන්ධ ලෙස වැඩි කිරීම #7849 නැවත ඇති කරයි. **N ස්වාධීන `DATA_DIR`s** සමඟ **heaps** ගුණ කරන්න (#11024); එක් SQLite ගොනුවක් මත කිසිවිටෙක `replicas>1` භාවිත නොකරන්න. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ස්වයංක්රීයව ව්යුත්පන්න කළ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ස්වයංක්රීයව ව්යුත්පන්න කළ ingest byte අයවැය සඳහා අතික්රමණ අගය (වඩා දැඩි V8/cgroup මතක සීමාවෙන් 25%ක්, තාවකාලික 8x විස්තාරණයෙන් බෙදූ අගය). ව්යුත්පන්න කළ සහ පැහැදිලිව සැකසූ අගයන් 8 MiB–2 GiB අතරට සීමා වේ. ක්රියාත්මක අයවැයට වඩා විශාල body එකක් `413 body_exceeds_budget` සමඟ වහාම අසාර්ථක වේ; තනි තනිව සේවා සැපයිය හැකි bodies අතර තරගකාරී තත්ත්වය නැවත උත්සාහ කළ හැකි `503` ලෙස පවතී. සමගාමී දිගු SSE සේවාලාභීන් 40–50ක් යනු මෙම අයවැය + heap මත රඳා පවතින්නක් මිස දැඩි “උපරිම 2”ක් නොවේ. සුසර කිරීමට පෙර `/api/monitoring/health` හි `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` කියවන්න. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE සහ STRUCTURE heavyweight ඇතුළත් කිරීම් සඳහා heap-pressure shed අනුපාතය (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). සමගාමී heavyweight ඉල්ලීමක් `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ඉක්මවා ගිය විට, heap එකද මෙම අනුපාතයට හෝ ඊට ඉහළින් තිබේ නම් පමණක් නැවත උත්සාහ කළ හැකි `503` සමඟ එය බැහැර කරනු ලැබේ; සෞඛ්ය සම්පන්න heap එකකදී එය healthy-headroom හරහා ඇතුළත් කෙරේ. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (පෙරනිමිය `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE සහ BYTE (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ට සමාන හෝ විශාල bodies ඇතුළුව) **දෙකටම** අදාළ සෞඛ්ය සම්පන්න-heap වේගවත් මාර්ගය (#10437) සඳහා සීමා කළ අමතර ධාරිතාව. මෙම සීමාව නොමැතිව, කාර්යබහුල නමුත් සෞඛ්ය සම්පන්න heap එකක් ඇති සෑම ඉල්ලීමක්ම උපරිම සීමාවකින් තොරව ඇතුළත් කිරීම මඟහරිමින් තිබුණි. healthy-heap මාර්ගය හරහා මේ තරම් සමගාමී leases සක්රිය වූ පසු, තවත් කාර්යබහුල ඉල්ලීම් සැබෑ heap පීඩනය යටතේ භාවිත වන එම සීමා කළ-wait/shed මාර්ගයටම යොමු වේ. `0` මඟින් මෙම මඟහැරීම සම්පූර්ණයෙන්ම අක්රිය කරයි. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | chat ඉල්ලීමක body එක byte සීමාවට පහළින් තිබුණද එය heavyweight ලෙස වර්ගීකරණය කරන පණිවිඩ ගණන. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | chat ඉල්ලීමක body එක byte සීමාවට පහළින් තිබුණද එය heavyweight ලෙස වර්ගීකරණය කරන tool ගණන. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ඉල්ලීමක් heavyweight ලෙස වර්ගීකරණය කරන සංරක්ෂණාත්මක string-size token ඇස්තමේන්තුව; මෙය ඇතුළත් කිරීමේ පිරිවැය සඳහා proxy එකක් මිස සපයන්නාගේ බිල්කරණ tokenization එක නොවේ. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (අක්රියයි) | `src/shared/middleware/chatBodyAdmission.ts` | විකල්ප, කැමැත්තෙන් සක්රිය කළ හැකි chat ඉතිහාස සීමාව. පෙරනිමියෙන් අක්රියයි: පණිවිඩ ගණනක් යනු deployment ප්රතිපත්තියක් මිස ඉල්ලීමක සාර්වත්රික ගුණාංගයක් නොවන අතර, මෙහිදී සීමා කිරීමෙන් compression pipeline එකට සංවාද සේවා සැපයිය හැකි තත්ත්වයට පත් කිරීමට පෙර ඒවා අවසාන `413` එකක් සමඟ ප්රතික්ෂේප වේ. Heap වර්ධනය `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` සහ heap-pressure shed මඟින් සීමා කෙරේ. දැඩි උපරිම සීමාවක් අවශ්ය මතකය සීමා වූ deployments සඳහා ධන අගයක් සකසන්න; එවිට සීමාව ඉක්මවූ විට ව්යුහගත compact-required `413` ලැබේ. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | සම්පූර්ණයෙන්ම මතකයට buffer කරන non-streaming upstream ප්රතිචාරයක් සඳහා දැඩි සීමාව. මෙය ඉක්මවූ විට upstream reader එක අවලංගු කරනු ලබන අතර, heap එක අවසන් වන තෙක් අසීමිත string එකක් වර්ධනය වීමට ඉඩ දෙනවා වෙනුවට ඉල්ලීම ඉක්මනින් අසාර්ථක වේ. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ඉහළ ප්රවාහයේ ප්රතිචාර ශීර්ෂවලින් ඉදිරියට යවන උපරිම wire bytes ප්රමාණය. සීමාව ඉක්මවූ විට, පොදු reverse-proxy ශීර්ෂ සීමා තුළ රැඳී සිටීම සඳහා අඩු ප්රමුඛතා ශීර්ෂ (උදා., අභිරුචි `x-codex-*`, `x-oai-request-id`) ඉවත් කෙරේ. ප්රතිචාර ශීර්ෂයේ ප්රමාණය වැඩි වීමේ පිරිවැය යටතේ ඉහළ ප්රවාහයේ metadata වැඩි ප්රමාණයක් ඉදිරියට යැවීමට මෙය ඉහළ අගයකට සකසන්න. | -| `CORS_ORIGIN` | _(සකසා නැත)_ | `src/server/cors/origins.ts` | පැරණි තනි-origin CORS අවසර ලැයිස්තුව. නව යෙදවුම් සඳහා `CORS_ALLOWED_ORIGINS` භාවිතයට ප්රමුඛත්වය දෙන්න. CORS අවශ්ය වන්නේ cross-origin බ්රවුසර API සේවාලාභීන් සඳහා පමණි; සත්යාපිත dashboard ලිවීම් ඒ වෙනුවට එකම-origin ඉල්ලීම් සහ session එකට බැඳුණු CSRF ආරක්ෂාව භාවිත කරයි. | -| `CORS_ALLOWED_ORIGINS` | _(සකසා නැත)_ | `src/server/cors/origins.ts` | කොමාවලින් වෙන් කළ CORS අවසර ලැයිස්තුව. `CORS_ALLOW_ALL=true` පැහැදිලිව වින්යාස කර නොමැති නම් wildcard එකක් යවනු නොලැබේ. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ඕනෑම බ්රවුසර `Origin` එකක් ප්රතිරාව කිරීමට සංවර්ධනය සඳහා පමණක් වූ ගැලවීමේ විකල්පයකි. හවුල් හෝ නිෂ්පාදන යෙදවුම්වල මෙය සක්රීය නොකරන්න. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | private/loopback/link-local IP පරාස ඉලක්ක කරන provider ඇමතුම් අවහිර කරන්න. හුදකලා පරීක්ෂණ පරිසරවලදී පමණක් අක්රීය කරන්න. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | private/local ජාල වෙත (localhost, 192.168.x.x, 10.x.x.x, ආදිය) යොමු වන provider URL සඳහා අවසර දෙන්න. **ස්වයං-සත්කාරක provider සඳහා අත්යවශ්යය** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). මෙය `false` වූ විට, dashboard එක local URL වලංගු කිරීම ප්රතික්ෂේප කරයි. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | local/private ලිපිනවල (127.0.0.1, localhost, LAN, private පරාස) provider එක් කිරීමට/වලංගු කිරීමට අවසර දෙන්න — මෙය provider වලංගුකරණ මාර්ගයට සීමා වේ. **පෙරනිමිය `true`** (local-first); දැඩි public-only අවහිර කිරීම බලාත්මක කිරීමට `false` ලෙස සකසන්න. Cloud-metadata endpoint (169.254.169.254, metadata.google.internal) කෙසේ වෙතත් අවහිරව පවතී. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` මාර්ගවලට (පිටපත්කිරීම්, කථනය, පරිවර්තන) localhost පිටත සත්කාරකත්වය ලබන OpenAI-අනුකූල provider node එකක් භාවිත කිරීමට ඉඩ දෙන්න. පෙරනිමියෙන් අක්රීයයි — දුරස්ථ host එකකට audio යොමු කිරීමෙන් egress අනන්යතාව වෙනස් වන බැවින් එය පැහැදිලි operator තීරණයක් විය යුතුය. Loopback/private node (localhost, 127.0.0.1, 172.16-31.x) සැමවිටම අවසර ලබා ඇති අතර බලපෑමට ලක් නොවේ. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` වෙත (සහ memory engine හි loopback rerank පියවරට) localhost පිටත සත්කාරකත්වය ලබන OpenAI-අනුකූල provider node එකක් — TEI, Infinity, vLLM ආදිය ධාවනය කරන LAN යන්ත්රයක් හෝ Tailscale peer එකක් — භාවිත කිරීමට ඉඩ දෙන්න. පෙරනිමියෙන් අක්රීයයි — දුරස්ථ host එකකට යොමු කිරීමෙන් egress අනන්යතාව වෙනස් වන බැවින් එය පැහැදිලි operator තීරණයක් විය යුතුය. Loopback node (localhost, 127.0.0.1, 172.16-31.x) සැමවිටම අවසර ලබා ඇති අතර බලපෑමට ලක් නොවේ. දුරස්ථ node, provider outbound URL ප්රතිපත්තිය (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ද සමත් විය යුතුය; cloud-metadata host වෙත කිසිවිටෙක යොමු නොකෙරේ. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC සක්රීය කර ඇති විට, පරිශීලකයන්ට OIDC Single Sign-On හරහා පමණක් සත්යාපනය විය හැකි වන සේ මුරපද පිවිසුම අක්රීය කරන්න. සරල alias එක වන `OIDC_DISABLE_PASSWORD_LOGIN` ද පිළිගනු ලැබේ; එම key එකම ඇති Dashboard Feature Flag එකට ප්රමුඛත්වය හිමි වේ. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` හි සරල alias එකයි (#10889). | +| විචල්යය | පෙරනිමිය | මූලාශ්ර ගොනුව | විස්තරය | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | යන්ත්ර ඇඟිලි සලකුණක් නිර්මාණය කිරීම සඳහා දෘඪාංග හඳුනාගැනීම් සමඟ ඒකාබද්ධ කරන salt අගය. හුදකලා කිරීම සඳහා එක් එක් යෙදවීම අනුව මෙය වෙනස් කරන්න. | +| `OMNIROUTE_CLI_SALT` | _(සකසා නැත = එක් එක් ස්ථාපනය සඳහා අහඹු salt අගයක් `/cli-token-salt.json` හි ස්ථිරව සුරකියි)_ | `src/lib/machineToken.ts` | දේශීය CLI සත්යාපන ටෝකනය ව්යුත්පන්න කිරීම සඳහා භාවිත කරන HMAC salt අගය. මෙම අගය සැකසීමෙන් යන්ත්රයේ සියලුම CLI ටෝකන මාරු වන අතර, එය සෑම විටම ස්ථිරව සුරකින ලද salt අගයට වඩා ප්රමුඛත්වය ගනී. `docs/security/CLI_TOKEN.md` බලන්න. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | සැසි කුකීවල `Secure` ධජය සකසයි. HTTPS පසුපස ධාවනය වන විට මෙය **`true` විය යුතුය**. | +| `REQUIRE_API_KEY` | `false` | API මිඩ්ල්වෙයාර් | `true` වූ විට, සියලුම `/v1/*` ප්රොක්සි ඉල්ලීම්වල වලංගු API යතුරක් ඇතුළත් විය යුතුය. **මෙම ධජය `GET /v1/models` වෙත ප්රවේශය පාලනය නොකරයි**; ඒ වෙනුවට එය උපකරණ පුවරුවේ පිවිසුම් තත්ත්වය (`requireAuthForModels`) අනුගමනය කරයි — එබැවින් `/v1/models` වෙතින් ලැබෙන `401` ප්රතිචාරයකින් inference ආරක්ෂිත බව අදහස් **නොවේ**. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) බලන්න. | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | උපකරණ පුවරු UI තුළ සම්පූර්ණ API යතුරු අගයන් අනාවරණය කිරීමට ඉඩ දෙයි. උපකරණ පුවරුවේ විශේෂාංග ධජ හරහා වින්යාස කළ හැක; හවුල් අවස්ථාවලදී මෙය ආරක්ෂක අවදානමකි. | +| `NO_LOG_API_KEY_IDS` | _(හිස්)_ | `src/lib/compliance/index.ts` | ඉල්ලීම් ලොග් කිරීම මඟහරින, කොමාවලින් වෙන් කළ API යතුරු ID ලැයිස්තුවක් (GDPR අනුකූලතාව සඳහා). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(සකසා නැත = සීමාවක් නැත)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` තීරුව null වන API යතුරු සඳහා යොදන දිනකට ඉල්ලීම් සංඛ්යාවේ ආපසු යොදාගන්නා සීමාව. සකසා නොමැති හෝ හිස් නම්: ව්යංග සීමාවක් නැත (#2289, #11017). `0` ද එයට සමානය (සීමාවක් නැත). ධන නිඛිල N අගයක් මඟින් N/දිනකට, 5N/සතියකට සහ 20N/මාසයකට සක්රිය කරයි. වැරදි ආකෘතියකින් යුත් හිස් නොවන අගයන් පැරණි 1000/දිනකට, 5000/සතියකට සහ 20000/මාසයකට කවුළු වෙත ආපසු යයි. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | අවසර දී ඇති උපරිම ඉල්ලීම් body ප්රමාණය. මෙම සීමාව ඉක්මවන payload ප්රතික්ෂේප කරයි. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | මෙම සීමාවට සමාන හෝ එය ඉක්මවන සත්ය ඉල්ලීම් body, JSON විග්රහ කිරීමට පෙර පරමාණුක ක්රියාවලි-දේශීය heavyweight admission lease එක ලබාගනී (`POST /v1/responses` ඇතුළත් BYTE මාර්ගය). ව්යුහ-බර ඉල්ලීම්වල මෙන්ම එකම [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) සෞඛ්ය සම්පන්න අතිරික්ත ධාරිතා ගැලවීම භාවිත කරයි; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) නැවත ඇති නොවන ලෙස, තවමත් `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) මඟින් සීමා කර ඇත. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | සීමා කළ ආදානය අතරතුර කියවන ලද බයිට්වලට එරෙහිව බලාත්මක කරන chat-route දැඩි සීමාව; නොමැති, වලංගු නොවන, හෝ වංචනික `Content-Length` සහිත ඉල්ලීම්ද මෙයට ඇතුළත් වේ; සීමාව ඉක්මවූ විට `413` ලැබේ. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(සකසා නැත — ඉල්ලීම්-ගණන සීමාවක් නැත)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** මෙම පැරණි ඉල්ලීම්-COUNT සීමාව දැන් බලපාන්නේ එය පැහැදිලිව සකසා ඇති විට පමණි. එය සකසා නොතැබූ විට (පෙරනිමිය), heavyweight chat ඇතුළත් කිරීම ඒ වෙනුවට `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` මඟින් පාලනය වේ — **එක් process එකක** (එක් V8 heap එකක) සැබෑ මතක සීමාවෙන් ප්රමාණගත කර ස්වයංක්රීයව ව්යුත්පන්න කළ BYTE අයවැයකි. එකිනෙක අතිච්ඡාදනය වන ~750k-token `/v1/responses` දෙකක් ~12 Gi heaps අවසන් කරයි (#7849) — මෙය **මතක-අයවැය** අනතුරු ඇඟවීමක් මිස 2ක දැඩි නිෂ්පාදන උපරිමයක් නොවේ. සෞඛ්ය සම්පන්න process එකකට (heap එක shed අනුපාතයට පහළින් ඇති විට) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` හරහා තවත් සමගාමී දිගු `/v1/responses` ඇතුළත් කළ හැක. දිගු SSE සේවාලාභීන් දස ගණනක් (40–50) යනු heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 මත රඳා පවතින්නක් මිස “උපරිම 2” නොවේ. “host එක භාවිත කිරීමට” මෙය අන්ධ ලෙස වැඩි කිරීම #7849 නැවත ඇති කරයි. **N ස්වාධීන `DATA_DIR`s** සමඟ **heaps** ගුණ කරන්න (#11024); එක් SQLite ගොනුවක් මත කිසිවිටෙක `replicas>1` භාවිත නොකරන්න. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(ස්වයංක්රීයව ව්යුත්පන්න කළ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ස්වයංක්රීයව ව්යුත්පන්න කළ ingest byte අයවැය සඳහා අතික්රමණ අගය (වඩා දැඩි V8/cgroup මතක සීමාවෙන් 25%ක්, තාවකාලික 8x විස්තාරණයෙන් බෙදූ අගය). ව්යුත්පන්න කළ සහ පැහැදිලිව සැකසූ අගයන් 8 MiB–2 GiB අතරට සීමා වේ. ක්රියාත්මක අයවැයට වඩා විශාල body එකක් `413 body_exceeds_budget` සමඟ වහාම අසාර්ථක වේ; තනි තනිව සේවා සැපයිය හැකි bodies අතර තරගකාරී තත්ත්වය නැවත උත්සාහ කළ හැකි `503` ලෙස පවතී. සමගාමී දිගු SSE සේවාලාභීන් 40–50ක් යනු මෙම අයවැය + heap මත රඳා පවතින්නක් මිස දැඩි “උපරිම 2”ක් නොවේ. සුසර කිරීමට පෙර `/api/monitoring/health` හි `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` කියවන්න. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE සහ STRUCTURE heavyweight ඇතුළත් කිරීම් සඳහා heap-pressure shed අනුපාතය (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). සමගාමී heavyweight ඉල්ලීමක් `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ඉක්මවා ගිය විට, heap එකද මෙම අනුපාතයට හෝ ඊට ඉහළින් තිබේ නම් පමණක් නැවත උත්සාහ කළ හැකි `503` සමඟ එය බැහැර කරනු ලැබේ; සෞඛ්ය සම්පන්න heap එකකදී එය healthy-headroom හරහා ඇතුළත් කෙරේ. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (පෙරනිමිය `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE සහ BYTE (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` ට සමාන හෝ විශාල bodies ඇතුළුව) **දෙකටම** අදාළ සෞඛ්ය සම්පන්න-heap වේගවත් මාර්ගය (#10437) සඳහා සීමා කළ අමතර ධාරිතාව. මෙම සීමාව නොමැතිව, කාර්යබහුල නමුත් සෞඛ්ය සම්පන්න heap එකක් ඇති සෑම ඉල්ලීමක්ම උපරිම සීමාවකින් තොරව ඇතුළත් කිරීම මඟහරිමින් තිබුණි. healthy-heap මාර්ගය හරහා මේ තරම් සමගාමී leases සක්රිය වූ පසු, තවත් කාර්යබහුල ඉල්ලීම් සැබෑ heap පීඩනය යටතේ භාවිත වන එම සීමා කළ-wait/shed මාර්ගයටම යොමු වේ. `0` මඟින් මෙම මඟහැරීම සම්පූර්ණයෙන්ම අක්රිය කරයි. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | chat ඉල්ලීමක body එක byte සීමාවට පහළින් තිබුණද එය heavyweight ලෙස වර්ගීකරණය කරන පණිවිඩ ගණන. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | chat ඉල්ලීමක body එක byte සීමාවට පහළින් තිබුණද එය heavyweight ලෙස වර්ගීකරණය කරන tool ගණන. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ඉල්ලීමක් heavyweight ලෙස වර්ගීකරණය කරන සංරක්ෂණාත්මක string-size token ඇස්තමේන්තුව; මෙය ඇතුළත් කිරීමේ පිරිවැය සඳහා proxy එකක් මිස සපයන්නාගේ බිල්කරණ tokenization එක නොවේ. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (අක්රියයි) | `src/shared/middleware/chatBodyAdmission.ts` | විකල්ප, කැමැත්තෙන් සක්රිය කළ හැකි chat ඉතිහාස සීමාව. පෙරනිමියෙන් අක්රියයි: පණිවිඩ ගණනක් යනු deployment ප්රතිපත්තියක් මිස ඉල්ලීමක සාර්වත්රික ගුණාංගයක් නොවන අතර, මෙහිදී සීමා කිරීමෙන් compression pipeline එකට සංවාද සේවා සැපයිය හැකි තත්ත්වයට පත් කිරීමට පෙර ඒවා අවසාන `413` එකක් සමඟ ප්රතික්ෂේප වේ. Heap වර්ධනය `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` සහ heap-pressure shed මඟින් සීමා කෙරේ. දැඩි උපරිම සීමාවක් අවශ්ය මතකය සීමා වූ deployments සඳහා ධන අගයක් සකසන්න; එවිට සීමාව ඉක්මවූ විට ව්යුහගත compact-required `413` ලැබේ. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | සම්පූර්ණයෙන්ම මතකයට buffer කරන non-streaming upstream ප්රතිචාරයක් සඳහා දැඩි සීමාව. මෙය ඉක්මවූ විට upstream reader එක අවලංගු කරනු ලබන අතර, heap එක අවසන් වන තෙක් අසීමිත string එකක් වර්ධනය වීමට ඉඩ දෙනවා වෙනුවට ඉල්ලීම ඉක්මනින් අසාර්ථක වේ. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | ඉහළ ප්රවාහයේ ප්රතිචාර ශීර්ෂවලින් ඉදිරියට යවන උපරිම wire bytes ප්රමාණය. සීමාව ඉක්මවූ විට, පොදු reverse-proxy ශීර්ෂ සීමා තුළ රැඳී සිටීම සඳහා අඩු ප්රමුඛතා ශීර්ෂ (උදා., අභිරුචි `x-codex-*`, `x-oai-request-id`) ඉවත් කෙරේ. ප්රතිචාර ශීර්ෂයේ ප්රමාණය වැඩි වීමේ පිරිවැය යටතේ ඉහළ ප්රවාහයේ metadata වැඩි ප්රමාණයක් ඉදිරියට යැවීමට මෙය ඉහළ අගයකට සකසන්න. | +| `CORS_ORIGIN` | _(සකසා නැත)_ | `src/server/cors/origins.ts` | පැරණි තනි-origin CORS අවසර ලැයිස්තුව. නව යෙදවුම් සඳහා `CORS_ALLOWED_ORIGINS` භාවිතයට ප්රමුඛත්වය දෙන්න. CORS අවශ්ය වන්නේ cross-origin බ්රවුසර API සේවාලාභීන් සඳහා පමණි; සත්යාපිත dashboard ලිවීම් ඒ වෙනුවට එකම-origin ඉල්ලීම් සහ session එකට බැඳුණු CSRF ආරක්ෂාව භාවිත කරයි. | +| `CORS_ALLOWED_ORIGINS` | _(සකසා නැත)_ | `src/server/cors/origins.ts` | කොමාවලින් වෙන් කළ CORS අවසර ලැයිස්තුව. `CORS_ALLOW_ALL=true` පැහැදිලිව වින්යාස කර නොමැති නම් wildcard එකක් යවනු නොලැබේ. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ඕනෑම බ්රවුසර `Origin` එකක් ප්රතිරාව කිරීමට සංවර්ධනය සඳහා පමණක් වූ ගැලවීමේ විකල්පයකි. හවුල් හෝ නිෂ්පාදන යෙදවුම්වල මෙය සක්රීය නොකරන්න. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | උරුම alias එකකි. මෙහි dashboard toggle එක (DB override එකක්) මෙම environment අගයට පෙර කියවනු ලැබේ; ඕනෑම එකක `false`, `0`, `no` හෝ `off` ඇති විට, එම flag එකේ dashboard toggle එක off ලෙස සුරකියා තිබුණත්, `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` කරන ආකාරයටම host පරීක්ෂා අක්රීය වේ; අනෙකුත් අගයන් යටතේ guard තීරණය පහත ඇති flag දෙකට හැර යයි. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Guard මාදිලිය අනුගමනය කරන provider මාර්ගවල — URL වලංගුකරණය, model discovery, provider-node base URL (දුරස්ථ rerank node ඇතුළුව) සහ proxy-fallback පරීක්ෂණය, මෙන්ම දුරස්ථ image/media fetch වැනි අනෙකුත් provider-ආසන්න outbound මාර්ග — outbound URL guard එකේ host පරීක්ෂා, cloud-metadata අවහිර කිරීම ඇතුළුව (සම්පූර්ණ 169.254.0.0/16, metadata.google.internal), අක්රීය කරයි. තවද එය private webhook ඉලක්ක සඳහා ද අවසර දෙයි; කෙසේ වෙතත් cloud-metadata/link-local endpoint, මෙම flag එක on වුවත්, අවහිරව පවතී. `BaseExecutor.execute()` හරහා යවන chat ඉල්ලීම් සිය cloud-metadata පරීක්ෂාව රඳවා ගනී. පෙරනිමි සැකසුම් සමඟ local provider ක්රියා කරයි: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (පෙරනිමිය `true`) දැනටමත් වලංගුකරණ, discovery සහ provider-node මාර්ගවල local හා LAN URL සඳහා අවසර දෙයි; proxy-fallback පරීක්ෂණය සහ private webhook ඉලක්ක මෙම flag එක පමණක් සලකා, එය off සිටින තුරු local/LAN host අවහිර කරයි. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` ට වඩා ප්රමුඛ වේ. Dashboard toggle එකෙන් සුරකින ලද අගයක් මෙම විචල්යයට වඩා ප්රමුඛත්වය ගනී. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Provider වලංගුකරණය, model discovery, chat සහ guard මාදිලිය පාලනය කරන අනෙකුත් provider-ආසන්න outbound මාර්ග සඳහා local/private ලිපිනවල (127.0.0.1, localhost, LAN, private පරාස) provider URL සඳහා අවසර දෙන්න. **පෙරනිමිය `true`** (local-first): එවිට guard එක cloud-metadata endpoint — සම්පූර්ණ 169.254.0.0/16 සහ දන්නා metadata hostname (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) — අවහිර කරයි. Public-only මාදිලිය සඳහා `false` සකසන්න; එය private සහ loopback host ද අවහිර කරයි. Guard එක කිසිදු DNS lookup එකකට පෙර, ලියා ඇති පරිදිම hostname හෝ IP literal පරීක්ෂා කරයි; public-only මාදිලියේදී safe-fetch wrapper එක අමතරව hostname එක resolve කර ඕනෑම private පිළිතුරක් ප්රතික්ෂේප කරයි. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` එම flag එකේ පේළියේ ලැයිස්තු වූ මාර්ගවල මෙම පරීක්ෂා අක්රීය කරයි. LM Studio, Ollama, vLLM, Llamafile සහ Triton වැනි built-in local provider, key වලංගුකරණය සහ chat සඳහා සියලුම host පරීක්ෂා මඟහරියි. Dashboard toggle එකෙන් සුරකින ලද අගයක් මෙම විචල්යයට වඩා ප්රමුඛත්වය ගනී. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` මාර්ගවලට (පිටපත්කිරීම්, කථනය, පරිවර්තන) localhost පිටත සත්කාරකත්වය ලබන OpenAI-අනුකූල provider node එකක් භාවිත කිරීමට ඉඩ දෙන්න. පෙරනිමියෙන් අක්රීයයි — දුරස්ථ host එකකට audio යොමු කිරීමෙන් egress අනන්යතාව වෙනස් වන බැවින් එය පැහැදිලි operator තීරණයක් විය යුතුය. Loopback/private node (localhost, 127.0.0.1, 172.16-31.x) සැමවිටම අවසර ලබා ඇති අතර බලපෑමට ලක් නොවේ. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` වෙත (සහ memory engine හි loopback rerank පියවරට) localhost පිටත සත්කාරකත්වය ලබන OpenAI-අනුකූල provider node එකක් — TEI, Infinity, vLLM ආදිය ධාවනය කරන LAN යන්ත්රයක් හෝ Tailscale peer එකක් — භාවිත කිරීමට ඉඩ දෙන්න. පෙරනිමියෙන් අක්රීයයි — දුරස්ථ host එකකට යොමු කිරීමෙන් egress අනන්යතාව වෙනස් වන බැවින් එය පැහැදිලි operator තීරණයක් විය යුතුය. Loopback node (localhost, 127.0.0.1, 172.16-31.x) සැමවිටම අවසර ලබා ඇති අතර බලපෑමට ලක් නොවේ. දුරස්ථ node එකක base URL එක ද provider outbound URL ප්රතිපත්තිය (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) සමත් විය යුතුය. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC සක්රීය කර ඇති විට, පරිශීලකයන්ට OIDC Single Sign-On හරහා පමණක් සත්යාපනය විය හැකි වන සේ මුරපද පිවිසුම අක්රීය කරන්න. සරල alias එක වන `OIDC_DISABLE_PASSWORD_LOGIN` ද පිළිගනු ලැබේ; එම key එකම ඇති Dashboard Feature Flag එකට ප්රමුඛත්වය හිමි වේ. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` හි සරල alias එකයි (#10889). | ### දැඩි ආරක්ෂණ පිරික්සුම් ලැයිස්තුව diff --git a/docs/i18n/sk/CONTRIBUTING.md b/docs/i18n/sk/CONTRIBUTING.md index 340bdba2f38f..46e080c6ea06 100644 --- a/docs/i18n/sk/CONTRIBUTING.md +++ b/docs/i18n/sk/CONTRIBUTING.md @@ -399,22 +399,22 @@ Napíšte jednotkové testy v `tests/unit/`, ktoré pokrývajú minimálne: --- -## Kontrolný zoznam pull requestu +## Kontrolný zoznam pre Pull Request -- [ ] Testy sú úspešné (`npm test`) -- [ ] Kontrola lintovania je úspešná (`npm run lint`) +- [ ] Testy prechádzajú (`npm test`) +- [ ] Kontrola lintingu prechádza (`npm run lint`) - [ ] Zostavenie je úspešné (`npm run build`) -- [ ] Pre nové verejné funkcie a rozhrania boli pridané typy TypeScript +- [ ] Pre nové verejné funkcie a rozhrania sú pridané typy TypeScript - [ ] Žiadne napevno zadané tajné údaje ani záložné hodnoty -- [ ] Verejné upstream prihlasovacie údaje sú vložené pomocou `resolvePublicCred()` (pozrite si [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikdy nie ako literály -- [ ] Chybové odpovede prechádzajú cez `buildErrorBody()` / `sanitizeErrorMessage()` — telá odpovedí neobsahujú nespracované trasovania zásobníka (pozrite si [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Verejné prihlasovacie údaje upstream služieb sú vložené prostredníctvom `resolvePublicCred()` (pozrite si [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikdy nie ako literály +- [ ] Chybové odpovede prechádzajú cez `buildErrorBody()` / `sanitizeErrorMessage()` — v telách odpovedí nie sú žiadne nespracované výpisy zásobníka (pozrite si [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Shellové príkazy (`exec` / `spawn`) odovzdávajú hodnoty za behu prostredníctvom `env`, nie pomocou interpolácie reťazcov -- [ ] Všetky vstupy sú overené pomocou schém Zod -- [ ] Pre zmeny viditeľné používateľom bol do `changelog.d/{features|fixes|maintenance}/-.md` pridaný **fragment** zoznamu zmien (pozrite si [`changelog.d/README.md`](./changelog.d/README.md)) — **neupravujte** priamo `CHANGELOG.md`; fragmenty sa agregujú pri vydaní a medzi pull requestmi nikdy nevytvárajú konflikty -- [ ] Dokumentácia bola aktualizovaná (ak je to relevantné) +- [ ] Všetky vstupy sú validované pomocou schém Zod +- [ ] Pre zmeny viditeľné pre používateľov je pridaný **fragment** záznamu zmien v `changelog.d/{features|fixes|maintenance}/-.md` (pozrite si [`changelog.d/README.md`](./changelog.d/README.md)) — **neupravujte** priamo `CHANGELOG.md`; fragmenty sa agregujú pri vydaní a medzi Pull Requestmi nikdy nevznikajú konflikty +- [ ] Dokumentácia je aktualizovaná (ak je to relevantné) - [ ] Neboli vytvorené žiadne nové upozornenia CodeQL / Secret-Scanning alebo bolo každé z nich zamietnuté s technickým odôvodnením odkazujúcim na príslušný dokument v `docs/security/` -- [ ] Trasy, ktoré spúšťajú podriadené procesy (`/api/mcp/`, `/api/cli-tools/runtime/`), sú klasifikované ako `isLocalOnlyPath()` v `src/server/authz/routeGuard.ts` — pozrite si [Pevné pravidlo č. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Správy commitov neobsahujú riadky `Co-Authored-By` — commity musia byť uvedené výlučne pod identitou Git vlastníka repozitára (Pevné pravidlo č. 16) +- [ ] Trasy, ktoré spúšťajú podradené procesy (`/api/mcp/`, `/api/cli-tools/runtime/`), sú klasifikované ako `isLocalOnlyPath()` v `src/server/authz/routeGuard.ts` — pozrite si [Prísne pravidlo č. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Správy commitov neobsahujú pätičky `Co-authored-by` pre AI/botov (Prísne pravidlo č. 16) — ľudskí spolupracovníci, ktorých práca je opätovne použitá, sú uvedení pomocou štandardných pätičiek `Co-authored-by: Name ` --- diff --git a/docs/i18n/sk/docs/architecture/QUALITY_GATES.md b/docs/i18n/sk/docs/architecture/QUALITY_GATES.md index 05ab0eda0c7d..62e6483be275 100644 --- a/docs/i18n/sk/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/sk/docs/architecture/QUALITY_GATES.md @@ -19,213 +19,211 @@ rovnakého systému nezávislý od nástrojov nájdete v dokumente ### Prijatie kandidáta -Pracovné postupy CI a Quality Gates poskytujú stabilné verdikty: `Gate / CI` a +Workflowy CI a Quality Gates poskytujú stabilný verdikt: `Gate / CI` a `Gate / Quality`. Ich verzovaná politika prijímania uvádza každú nadradenú úlohu -ako povinnú alebo odporúčanú. Každá relevantná povinná úloha musí uspieť: chýbajúce, -zrušené, preskočené, čakajúce a neznáme výsledky nemôžu stanoviť stav PASS. Platná -klasifikácia iba pre dokumentáciu alebo iba pre katalóg môže spôsobiť, že vetva pre -kód nebude relevantná; koncept PR nie je akceptovaným kandidátom. Označenie `hotfix` -neruší požiadavku na dôkazy. - -Oba pracovné postupy pokrývajú PR a odoslania do vetiev main/release, manuálne -spustenie a udalosti merge-group. Odoslanie, spustenie a merge-group vykonávajú -úplný výber. Forky a skupiny zlúčenia používajú hostované runnery pre úlohy, ktoré -by inak vybrali samostatne hostované runnery; pred nasadením treba overiť dostatočnú -hostovanú kapacitu. - -Každé potvrdenie JSON identifikuje checkout SHA, spustenie pracovného postupu a pokus. -CLI odmietne nezhodu SHA medzi checkoutom a udalosťou. Testy pracovných postupov viažu -členstvo v politike na zoznam `needs` úlohy verdiktu, takže nová alebo odstránená vetva -nemôže nepozorovane zmiznúť. Potvrdenia pokrývajú vlastný pracovný postup, nie -publikovanie, nasadenie ani interné fungovanie existujúceho odporúčacieho skenera. -Aktivácia oboch názvov kontrol v pravidlách vetvy je samostatnou administratívnou -zmenou; pridanie týchto úloh samo osebe vetvu nechráni. - -### Inventár statických kontrol - -Verzovaný inventár aliasov npm a členstvo v statických kontrolách sa nachádzajú v +ako povinnú alebo poradnú. Príslušná povinná úloha musí uspieť: chýbajúce, +zrušené, preskočené, čakajúce a neznáme výsledky nemôžu stanoviť PASS. Platná +klasifikácia iba pre dokumentáciu alebo iba pre katalóg môže spôsobiť, že vetva +pre kód nebude relevantná; koncept PR nie je prijateľným kandidátom. Označenie +`hotfix` neruší požiadavku na dôkazy. + +Oba workflowy pokrývajú PR a odoslania zmien do hlavných/vydávacích vetiev, manuálne +spustenie a udalosti skupín zlučovania. Odoslanie zmien, manuálne spustenie a skupiny +zlučovania spúšťajú úplný výber. Forky a skupiny zlučovania používajú hostované +runnery pre úlohy, ktoré by inak vybrali samostatne hostované runnery; pred nasadením +je potrebné overiť dostatočnú kapacitu hostovaných runnerov. + +Každé potvrdenie JSON identifikuje checkoutnutý SHA, beh workflowu a pokus. +CLI odmietne nezhodu medzi SHA checkoutu a udalosti. Testy workflowu viažu členstvo +v politike na zoznam `needs` úlohy s verdiktom, aby nová alebo odstránená vetva +nemohla nepozorovane zmiznúť. Potvrdenia pokrývajú vlastný workflow, nie publikovanie, +nasadenie ani interné fungovanie existujúceho poradného skenera. Aktivácia oboch +názvov kontrol v pravidlách vetvy je samostatná administratívna zmena; pridanie +týchto úloh samo osebe nechráni vetvu. + +### Inventár statického skenovania + +Verzovaný inventár aliasov npm a členstvo v statickom skenovaní sa nachádzajú v `config/quality/gate-manifest.json`. Spustením `npm run check:gate-manifest` overíte -názvy skriptov a presné príkazy voči súboru `package.json`; pridania, odstránenia -a odchýlky príkazov spôsobia zlyhanie lokálneho hooku aj úloh klasifikácie zmien -v CI. Alias nie je úloha pracovného postupu, inštancia matice ani testovací prípad: -tieto počty sa nesmú prezentovať ako vzájomne zameniteľné. +názvy skriptov a presné príkazy voči `package.json`; pridania, odstránenia a +odchýlky príkazov spôsobia zlyhanie lokálneho hooku aj úloh klasifikácie zmien v CI. +Alias nie je úloha workflowu, inštancia matice ani testovací prípad: tieto počty sa +nesmú prezentovať ako vzájomne zameniteľné. Použite `npm run quality:scan -- --list` alebo `npm run quality:scan:fast -- --list` -na kontrolu vybraných aliasov bez ich vykonania. Runner vyvoláva vstupný bod npm, -takže jeho runtime (vrátane Bun tam, kde je nakonfigurovaný) zostáva zachovaný. -Manifest zaznamenáva aliasy mimo týchto profilov ako vyvolávané samostatne a -príkazy údržby sú v profiloch kontroly iba na čítanie zakázané. +na kontrolu vybraných aliasov bez ich spustenia. Runner vyvoláva vstupný bod npm, +takže jeho runtime (vrátane Bun, kde je nakonfigurovaný) zostáva zachovaný. +Manifest zaznamenáva aliasy mimo týchto profilov ako spúšťané samostatne a +príkazy údržby sú v profiloch skenovania určených iba na čítanie zakázané. -Tieto profily pokrývajú iba statické kontroly. Necertifikujú testy produktu, +Tieto profily pokrývajú iba statické skenovanie. Necertifikujú testy produktu, pokrytie, balenie, externé kontroly ani úplné prijatie kandidáta na vydanie. -Prijímanie v pracovnom postupe používa prepojené súbory -`config/quality/admission-policy.json` a `scripts/quality/admission-verdict.mjs`. -Profily pozorovateľa vydania zostávajú oddelené; ich relevantné kontroly a -potvrdenia overujte nezávisle. Textový inventár uvedený nižšie slúži ako referencia, -nie ako dôkaz, že sa brána skutočne spustila. - -Skripty sa nachádzajú v `scripts/check/` (brány politík) a `scripts/quality/` -(ratchet mechanizmus). Zdrojom pravdy pre CI je `.github/workflows/ci.yml`. - -### Zrýchlená cesta PR vydania (`quality.yml`) - -`.github/workflows/quality.yml` dopĺňa CI pre PR do vetiev main/release, odoslania -do chránených vetiev, manuálne spustenia a skupiny zlúčenia. PR používajú rýchle -kontroly filtrované podľa ciest. Trvalo zakázaná duplicitná zostava bola odstránená; -skutočné kontroly zostavenia, balíka a spustenia zostávajú v CI. - -| Úloha | Rozsah | Blokovanie | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -| `Docs Gates (fast-path)` | PR dokumentácie/kódu; odkazy na dokumentáciu API a docs-all | Áno | -| `Fast Quality Gates` | PR kódu; statické kontroly, kontrola typov, kontrola typov dashboardu, ovplyvnené jednotkové testy | Áno | -| `Forgotten sibling tests` | PR kódu; zmenené moduly vysledované k statickým konzumentom a kandidátskym súrodeneckým testom; cesty cez barrel a dynamický import sa hlásia ako odporúčacie diagnostiky s odkazovanými výnimkami zo zoznamu povolení | **Odporúčacie** | -| `Vitest (fast-path)` | PR kódu; rýchly balík testov vitest | Áno | -| `Unit Tests fast-path` | PR kódu; jednotkové testy rozdelené do 4 shardov | Áno | -| `No new ESLint warnings` | PR kódu; kontrola lintu zohľadňujúca potlačenia | Áno, vrátane forkov | -| `Merge integrity (changelog + generated skills)` | PR, ktoré nie sú konceptmi; synchronizácia changelogu a generovaných zručností | Áno, vrátane forkov | - -#### Hlásenie zabudnutých súrodeneckých testov - -`npm run check:forgotten-sibling-tests` opätovne používa resolver importov, na ktorom -je založená mapa vplyvu testov. Pre každý zmenený produkčný modul hlási deterministické -reťazce `changed module/symbol -> static consumer -> candidate sibling test`, keď -kandidátsky test chýba v diff-e pull requestu. Súhrn Markdown a výsledok JSON sa -uchovávajú ako artefakt pracovného postupu `forgotten-sibling-tests` na kalibráciu -pred prípadným zavedením blokovania. - -Re-exporty barrelov a dynamické importy slúžia iba na diagnostiku rozlíšenia; nikdy nevytvárajú -blokujúce zistenie. Skontrolované výnimky sa nachádzajú v +Prijímanie vo workflowe používa prepojené súbory `config/quality/admission-policy.json` a +`scripts/quality/admission-verdict.mjs`. Profily pozorovateľa vydania zostávajú oddelené; +ich príslušné kontroly a potvrdenia kontrolujte nezávisle. Textový +inventár nižšie slúži ako referencia, nie ako dôkaz, že sa brána skutočne spustila. + +Skripty sa nachádzajú v `scripts/check/` (brány politík) a `scripts/quality/` (mechanizmus postupného sprísňovania). +Zdrojom pravdy pre CI je `.github/workflows/ci.yml`. + +### Zrýchlený postup pre PR vydania (`quality.yml`) + +`.github/workflows/quality.yml` dopĺňa CI pri PR do hlavných/vydávacích vetiev, +odoslaniach zmien do chránených vetiev, manuálnom spustení a skupinách zlučovania. +PR používajú rýchle kontroly filtrované podľa ciest. Trvalo zakázané duplicitné +zostavenie bolo odstránené; skutočné kontroly zostavenia/balenia/spustenia zostávajú v CI. + +| Úloha | Rozsah | Blokujúca | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| `Docs Gates (fast-path)` | PR dokumentácie/kódu; referencie dokumentácie API a všetka dokumentácia | Áno | +| `Fast Quality Gates` | PR kódu; statické kontroly, kontrola typov, kontrola typov dashboardu, ovplyvnené jednotkové testy | Áno | +| `Forgotten sibling tests` | PR kódu; zmenené moduly vysledované k statickým konzumentom a kandidátskym súrodeneckým testom; cesty cez barrel súbory a dynamické importy sa hlásia ako poradná diagnostika s odkazmi na výnimky v zozname povolení | **Poradná** | +| `Vitest (fast-path)` | PR kódu; rýchla sada Vitest | Áno | +| `Unit Tests fast-path` | PR kódu; jednotková sada so 4 shardmi | Áno | +| `No new ESLint warnings` | PR kódu; lintovacia kontrola zohľadňujúca potlačenia | Áno, vrátane forkov | +| `Merge integrity (changelog + generated skills)` | PR, ktoré nie sú konceptmi; synchronizácia changelogu a generovaných zručností | Áno, vrátane forkov | + +#### Správa o zabudnutých súrodeneckých testoch + +`npm run check:forgotten-sibling-tests` opätovne používa resolver importov, na ktorom je založená mapa vplyvu testov. +Pre každý zmenený produkčný modul hlási deterministické reťazce +`changed module/symbol -> static consumer -> candidate sibling test`, keď kandidátsky +test chýba v rozdiele pull requestu. Súhrn vo formáte Markdown a výsledok JSON sa uchovávajú ako +artefakt workflowu `forgotten-sibling-tests` na účely kalibrácie pred akýmkoľvek zavedením blokovania. + +Reexporty zo súhrnných modulov a dynamické importy slúžia iba na diagnostiku rozlíšenia; nikdy +nevytvárajú blokujúce zistenie. Skontrolované výnimky sa nachádzajú v `config/quality/forgotten-sibling-allowlist.json`. Každá položka musí uvádzať konzumenta a kandidátsky -test, obsahovať konkrétne odôvodnenie a odkazovať na issue alebo pull request na GitHube. Chybne vytvorené položky spôsobia -zlyhanie. Výnimky nemôžu potlačiť odstránený kandidátsky test ani diff, ktorý pridáva `.skip`/`.todo`; -oslabovanie asercií a ďalšie maskovanie naďalej spadajú pod nezávisle blokujúcu -bránu `check:test-masking`. +test, obsahovať konkrétne odôvodnenie a odkazovať na problém alebo pull request na GitHube. Chybne +vytvorené položky sú automaticky zamietnuté. Výnimky nemôžu potlačiť odstránený kandidátsky test ani +rozdiel, ktorý pridáva `.skip`/`.todo`; oslabovanie asercií a ďalšie maskovanie naďalej spadajú pod +nezávisle blokujúcu bránu `check:test-masking`. ### Úloha: `lint` -Spúšťa sa pri každom PR do `main`. Pri zlyhaní blokuje zlúčenie. - -| Skript (`npm run ...`) | Overuje | Blokujúce | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:node-runtime` | Verzia Node.js je v podporovanom rozsahu | Áno | -| `check:cycles` | Cyklické importy — všetky moduly v `src/` + `open-sse/` | Áno | -| `check:route-validation:t06` | Schémy Zod sú prítomné na všetkých trasách (pravidlá úrovne 6) | Áno | -| `check:any-budget:t11` | Počet `@ts-expect-error // any` neprekračuje limit (kontrolný mechanizmus úrovne 11) | Áno | -| `check:provider-consistency` | Každý poskytovateľ v `providers.ts` má zodpovedajúcu položku v `providerRegistry.ts` (a naopak, v rámci zoznamu povolených položiek) | Áno | -| `check:model-lifecycle` | Tri ručne udržiavané smerovacie tabuľky zostávajú konzistentné so snapshotom životného cyklu uloženým v repozitári (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nehodnotí žiadne vyradené id, ktoré dokáže `REGISTRY` smerovať; každý cieľ `BUILT_IN_ALIASES` sa nachádza v `REGISTRY` a nenachádza sa v snapshote vyradených id; každé vyradené id, ktoré je stále v `REGISTRY`, je presmerované alebo uvedené v `allowedRetiredInCatalog`; a žiadny zdroj ani cieľ `DEFAULT_DEGRADATION_MAP` sa v tomto snapshote nenachádza medzi vyradenými. Toto nedokazuje, že model momentálne poskytuje aktívna upstreamová služba. Offline — porovnáva sa s `config/quality/model-lifecycle.json`, ktorý sa ručne obnovuje pomocou `npm run quality:refresh-model-lifecycle` (vyžaduje sieť; nie je zapojené do CI). `allowedRetiredInCatalog` je postupne sprísňovaná západka: položku pridajte iba spolu so sledovacím problémom. | Áno | -| `check:fetch-targets` | Každé `fetch("/api/...")` v klientskom `src/` smeruje na skutočný `route.ts` | Áno | -| `check:deps` | Všetky závislosti inštalovateľné pomocou `npm install` vo všetkých súboroch `package.json` v repozitári sa nachádzajú v `dependency-allowlist.json`; nové závislosti bez pevne určenej verzie alebo balíky s názvami napodobňujúcimi iné balíky sú označené | Áno | -| `audit:deps` | `npm audit` (koreňový adresár + electron) — žiadne upozornenia s vysokou/kritickou závažnosťou (prekrýva sa s osv `check:vuln-ratchet`; pozrite si zoznam úloh na racionalizáciu) | Áno | -| `check:lockfile` | Integrita `package-lock.json` — register https, hashe integrity, žiadne prepísania hostiteľa | Áno | -| `check:licenses` | Zoznam povolených licencií SPDX pre produkčné závislosti | Áno | -| `check:tracked-artifacts` | Žiadne artefakty zostavenia ani potvrdené symbolické odkazy na `node_modules` (spúšťa sa aj v husky pre-commit; pre-push je zámerne nenáročný — #6716) | Áno | -| `check:ai-attribution` | Žiadna pätička `Co-Authored-By` od AI/bota ani pätička o generovaní pomocou AI v commitoch, názve alebo tele PR — pevné pravidlo č. 16 (v slučke rýchlych kontrol v `quality.yml` pre PR→`release/**` — číta údaje udalosti, mimo PR nevykonáva žiadnu operáciu — a krok iba pre PR v lintovaní v `ci.yml` pre PR→`main`; takisto hook `commit-msg` v husky; ľudskí spoluautori sú povolení; #14436) | -| `check:vitest-exclusions` | Každé vylúčenie Vitest uvádza sledovaný problém a nachádza sa v `config/quality/vitest-exclusions.json` (#13204) | Áno | -| `check:file-size` | Žiadny zdrojový súbor neprekračuje limit pre príslušnú príponu (západka: zmrazené veľké súbory v zozname `frozen`) | Áno | -| `check:error-helper` | Chybové odpovede v exekútoroch/obslužných rutinách používajú `buildErrorBody()` / `sanitizeErrorMessage()` (pevné pravidlo č. 12) | Áno | -| `check:migration-numbering` | Migračné SQL súbory sú číslované postupne, bez medzier alebo duplicít | Áno | -| `check:public-creds` | Žiadne explicitné OAuth `client_id`/`client_secret` ani webové kľúče Firebase mimo `publicCreds.ts` (Pevné pravidlo č. 11) | Áno | -| `check:db-rules` | Žiadne nespracované SQL mimo modulov `src/lib/db/`; žiadne hromadné importy z `localDb.ts` (Pevné pravidlá č. 2/č. 5) | Áno | -| `check:known-symbols` | Vykonávacie moduly poskytovateľov, stratégie smerovania a prekladače zaregistrované vo svojich dispečerských tabuľkách zodpovedajú súborom na disku — žiadne osirotené ani nedeklarované symboly | Áno | -| `check:route-guard-membership` | Každá trasa, ktorá vytvára podradený proces, je klasifikovaná pomocou `isLocalOnlyPath()` (Pevné pravidlá č. 15/č. 17) | Áno | -| `check:test-discovery` | Každý súbor `*.test.ts` / `*.spec.ts` v repozitári je zahrnutý aspoň jedným nástrojom na spúšťanie testov (západka: zoznam osirotených súborov v `test-discovery-baseline.json` sa môže iba zmenšovať) | Áno | -| `check:agent-skills-sync` | Vygenerované artefakty agent-skills sa zhodujú so zdrojovým katalógom (bez odchýlok) | -| `check:provider-asset-provenance` | Logá a aktíva poskytovateľov majú zaznamenaný pôvod | -| `lint:json` | Konfiguračné súbory JSON je možné analyzovať a spĺňajú pravidlá lintovania repozitára | -| `typecheck:core` | Kompilácia TypeScriptu bez chýb (iba informatívne upozornenia) | Áno | -| `typecheck:noimplicit:core` | Striktné `noImplicitAny` — zamerané do budúcnosti; mnohé existujúce miesta volania stále potrebujú anotácie | **Informatívne** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` obmedzené na `src/app/(dashboard)/**` (#7033) — starostlivo vybraný zoznam 27 súborov v `typecheck:core` neobsahuje žiadny dashboardový súbor TSX a ani `next build` ich nikdy typovo nekontroluje (`next.config.mjs` nastavuje `ignoreBuildErrors: true`), takže regresie osirelých identifikátorov v tejto oblasti (#6625/#6909) boli pre CI neviditeľné. Rozdiely sa porovnávajú so zmrazenou východiskovou hodnotou počtu chýb podľa jednotlivých súborov a kódov TS (`config/quality/dashboard-typecheck-baseline.json`, rovnaký vzor vynucovania neaktuálnosti ako pri `check:known-symbols`) — kontrola zlyhá iba pri NOVÝCH chybách nad rámec východiskového počtu; keď sa opraví existujúca chyba, znížte východiskovú hodnotu pomocou `--update`. | Áno | +Spúšťa sa pri každom PR do vetvy `main`. Pri zlyhaní blokuje zlúčenie. + +| Skript (`npm run ...`) | Overuje | Blokujúce | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:node-runtime` | Verzia Node.js je v podporovanom rozsahu | Áno | +| `check:cycles` | Cyklické importy v celom `src/` + `open-sse/` (založené na AST, s rozlíšenými `paths` z tsconfig). Samostatné spustenie = informatívne, vypíše cykly. `check:cycles:ratchet` (spúšťaný v CI) blokuje, keď počet prekročí horný limit `metrics.cycles` v súbore `quality-baseline.json` — aktuálne 14, `direction: down`, takže môže iba klesať (#15159 G-01/G-02) | Áno (západka) | +| `check:route-validation:t06` | Prítomnosť schém Zod na všetkých trasách (pravidlá úrovne 6) | Áno | +| `check:any-budget:t11` | Počet `@ts-expect-error // any` neprekračuje limit (západka úrovne 11) | Áno | +| `check:provider-consistency` | Každý poskytovateľ v `providers.ts` má zodpovedajúci záznam v `providerRegistry.ts` (a naopak, v rámci zoznamu povolených položiek) | Áno | +| `check:model-lifecycle` | Tri ručne udržiavané smerovacie tabuľky zostávajú konzistentné so snapshotom životného cyklu uloženým v repozitári (#11503): `FITNESS_TABLE` (`taskFitness.ts`) nepriraďuje skóre žiadnemu vyradenému id, ktoré dokáže `REGISTRY` smerovať; každý cieľ `BUILT_IN_ALIASES` sa nachádza v `REGISTRY` a nenachádza sa v snapshote vyradených id; každé vyradené id, ktoré je stále v `REGISTRY`, je presmerované alebo uvedené v `allowedRetiredInCatalog`; a žiadny zdroj ani cieľ `DEFAULT_DEGRADATION_MAP` sa v tomto snapshote nejaví ako vyradený. Toto nedokazuje, že model je momentálne poskytovaný aktívnou upstream službou. Offline — porovnáva sa s `config/quality/model-lifecycle.json`, ktorý sa ručne obnovuje pomocou `npm run quality:refresh-model-lifecycle` (vyžaduje sieť; nie je zapojené do CI). `allowedRetiredInCatalog` je postupne sprísňovaný zoznam: záznam pridávajte iba spolu so sledovacou úlohou. | Áno | +| `check:fetch-targets` | Každé `fetch("/api/...")` v klientskej časti `src/` sa prekladá na skutočný `route.ts` | Áno | +| `check:deps` | Všetky závislosti inštalovateľné pomocou `npm install` naprieč každým súborom `package.json` v repozitári sa nachádzajú v `dependency-allowlist.json`; nové závislosti bez pevne určenej verzie alebo balíky s názvami napodobňujúcimi iné balíky sú označené | Áno | +| `audit:deps` | `npm audit` (koreňový projekt + electron) — žiadne upozornenia s vysokou alebo kritickou závažnosťou (prekrýva sa s osv `check:vuln-ratchet`; pozrite si Rationalization Backlog) | Áno | +| `check:lockfile` | Integrita súboru `package-lock.json` — register https, kontrolné súčty integrity, žiadne prepísania hostiteľa | Áno | +| `check:licenses` | Zoznam povolených licencií SPDX pre produkčné závislosti | Áno | +| `check:tracked-artifacts` | Žiadne artefakty zostavenia ani zahrnuté symbolické odkazy na `node_modules` (spúšťa sa aj v husky pre-commit; pre-push je zámerne odľahčený — #6716) | Áno | +| `check:ai-attribution` | Žiadna pätička `Co-Authored-By` od AI/bota ani pätička o generovaní pomocou AI v commitoch, názve či tele PR — Prísne pravidlo č. 16 (v slučke rýchlych kontrol `quality.yml` pre PR→`release/**` — číta dáta udalosti, mimo PR nevykoná nič — a krok len pre PR v kontrole lint súboru `ci.yml` pre PR→`main`; tiež hook husky `commit-msg`; ľudskí spoluautori sú povolení; #14436) | +| `check:vitest-exclusions` | Každé vylúčenie Vitest uvádza sledovaný problém a nachádza sa v `config/quality/vitest-exclusions.json` (#13204) | Áno | +| `check:file-size` | Žiadny zdrojový súbor neprekračuje limit pre danú príponu (postupné sprísňovanie: veľké súbory zmrazené v zozname `frozen`) | Áno | +| `check:error-helper` | Chybové odpovede v executoroch/handleroch používajú `buildErrorBody()` / `sanitizeErrorMessage()` (Prísne pravidlo č. 12) | Áno | +| `check:migration-numbering` | Migračné SQL súbory sú číslované sekvenčne, bez medzier alebo duplicít | Áno | +| `check:public-creds` | Žiadne doslovné OAuth `client_id`/`client_secret` ani webové kľúče Firebase mimo `publicCreds.ts` (Pevné pravidlo č. 11) | Áno | +| `check:db-rules` | Žiadne nespracované SQL mimo modulov `src/lib/db/`; žiadne súhrnné importy z `localDb.ts` (Pevné pravidlá č. 2/5) | Áno | +| `check:known-symbols` | Vykonávacie moduly poskytovateľov, stratégie smerovania a prekladače registrované vo svojich dispečerských tabuľkách sa zhodujú so súbormi na disku — žiadne osirelé ani nedeklarované symboly | Áno | +| `check:route-guard-membership` | Každá trasa, ktorá spúšťa podriadený proces, je klasifikovaná pomocou `isLocalOnlyPath()` (Pevné pravidlá č. 15/17) | Áno | +| `check:test-discovery` | Každý súbor `*.test.ts` / `*.spec.ts` v repozitári je zahrnutý aspoň jedným nástrojom na spúšťanie testov (západka: zoznam osirelých súborov v `test-discovery-baseline.json` sa môže iba zmenšovať) | Áno | +| `check:agent-skills-sync` | Vygenerované artefakty agent-skills zodpovedajú svojmu zdrojovému katalógu (bez odchýlok) | +| `check:provider-asset-provenance` | Logá/aktíva poskytovateľov majú zaznamenaný údaj o pôvode | +| `lint:json` | Konfiguračné súbory JSON sa dajú analyzovať a spĺňajú pravidlá lintovania repozitára | +| `typecheck:core` | Kompilácia TypeScript bez chýb (iba informatívne upozornenia) | Áno | +| `typecheck:noimplicit:core` | Striktné `noImplicitAny` — zamerané do budúcnosti; mnohé existujúce miesta volania stále potrebujú anotácie | **Informatívne** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` obmedzené na `src/app/(dashboard)/**` (#7033) — starostlivo vybraný zoznam 27 súborov v `typecheck:core` neobsahuje žiadne dashboard TSX a ani `next build` ich nikdy typovo nekontroluje (`next.config.mjs` nastavuje `ignoreBuildErrors: true`), takže regresie osirelých identifikátorov v tejto oblasti (#6625/#6909) neboli pre CI viditeľné. Rozdiely sa porovnávajú so zmrazenou východiskovou hodnotou počtu chýb podľa súboru a kódu TS (`config/quality/dashboard-typecheck-baseline.json`, rovnaký vzor vynucovania neaktuálnosti ako pri `check:known-symbols`) — kontrola zlyhá iba pri NOVÝCH chybách nad rámec východiskového počtu; po oprave existujúcej chyby znížte východiskovú hodnotu pomocou `--update`. | Áno | ### Úloha: `quality-gate` Spúšťa sa po `test-coverage`. Pri zlyhaní zablokuje zlúčenie. -| Skript | Overuje | Blokujúce | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -| `quality:collect` | Vytvorí `quality-metrics.json` (počet upozornení ESLint, pokrytie zo zlúčenej správy segmentov) | Áno (predchádza mechanizmu ratchet) | -| `quality:ratchet` | Žiadna metrika v `quality-baseline.json` sa nezhoršila (upozornenia ESLint ≤ základná hodnota; pokrytie ≥ základná hodnota) | Áno | -| `check:duplication` | Duplikácia kódu (jscpd@4) neprekračuje základnú hodnotu v `quality-baseline.json` | Áno | -| `check:complexity` | Cyklomatická zložitosť na úrovni súboru neprekračuje limit (základné pravidlá ESLint `complexity` + `max-lines-per-function`) | Áno | -| `check:cognitive-complexity` | Ratchet kognitívnej zložitosti (`eslint-plugin-sonarjs`) — samostatný priechod ESLint; CI spúšťa oba zlúčené do jediného kroku `check:complexity-ratchets` | Áno | -| `check:dead-code` | Ratchet nepoužitých exportov/súborov (knip) sa v porovnaní so základnou hodnotou nezhoršil | Áno | -| `check:compression-budget` | Rozpočet benchmarku kompresie — minimálne úspory tokenov pre jednotlivé enginy sa nesmú zhoršiť | Áno | -| `check:type-coverage` | Ratchet percentuálneho podielu typovaného kódu (`type-coverage`) sa nezhoršil; do značnej miery nahrádza `typecheck:noimplicit:core` | Áno | -| `check:codeql-ratchet` | Počet otvorených upozornení CodeQL sa nezhoršil (načítava sa cez `gh api`; bez tokenu sa kontrola korektne preskočí) — frekvencia obnovovania a manuálne spustenie: pozrite nižšie „CodeQL ratchet“ | Áno | +| Skript | Overuje | Blokujúce | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `quality:collect` | Vytvára `quality-metrics.json` (počet upozornení ESLint, pokrytie zo zlúčenej správy segmentov) | Áno (predchádza sprísňovaniu) | +| `quality:ratchet` | Žiadna metrika v `quality-baseline.json` sa nezhoršila (upozornenia ESLint ≤ základná hodnota; pokrytie ≥ základná hodnota) | Áno | +| `check:duplication` | Duplikácia kódu (jscpd@4) neprekračuje základnú hodnotu v `quality-baseline.json` | Áno | +| `check:complexity` | Cyklomatická zložitosť na úrovni súborov neprekračuje limit (základné pravidlá ESLint `complexity` + `max-lines-per-function`) | Áno | +| `check:cognitive-complexity` | Sprísňovanie kognitívnej zložitosti (`eslint-plugin-sonarjs`) — samostatný priechod ESLint; CI spúšťa obe kontroly zlúčené do jediného kroku `check:complexity-ratchets` | Áno | +| `check:dead-code` | Sprísňovanie nepoužitých exportov/súborov (knip) sa oproti základnej hodnote nezhoršilo | Áno | +| `check:compression-budget` | Rozpočet benchmarku kompresie — minimálne úspory tokenov pre jednotlivé enginy sa nesmú zhoršiť | Áno | +| `check:type-coverage` | Sprísňovanie percentuálneho podielu typovaného kódu (`type-coverage`) sa nezhoršilo; do veľkej miery nahrádza `typecheck:noimplicit:core` | Áno | +| `check:codeql-ratchet` | Počet otvorených upozornení CodeQL sa nezhoršil (načítava cez `gh api`; bez tokenu sa korektne preskočí) — frekvencia obnovovania a manuálne spustenie: pozrite si časť „Sprísňovanie CodeQL“ nižšie | Áno | ### Úloha: `quality-extended` -Celá úloha má poradný charakter (`continue-on-error: true`). Ratchety založené na npm sa -spúšťajú reálne; externé skenery sa inštalujú pomocou `gh release download` a samy sa -preskočia (exit 0), keď binárny súbor stále chýba. +Celá úloha je informatívna (`continue-on-error: true`). Sprísňovacie kontroly založené na npm sa spúšťajú +naostro; externé skenery sa inštalujú cez `gh release download` a samy sa preskočia (ukončovací kód 0), +ak binárny súbor stále chýba. -| Skript | Overuje | Blokujúce | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| `check:circular-deps` | Žiadne cyklické závislosti (dpdm) | **Poradné** | -| `check:bundle-size` | Veľkosť balíka neprekračuje limit | **Poradné** | -| `check:secrets` | Skenovanie tajných údajov (gitleaks) — preskočí sa, ak binárny súbor chýba | **Poradné** | -| `check:vuln-ratchet` | Zraniteľnosti závislostí (osv-scanner) sa nezhoršili — preskočí sa, ak binárny súbor chýba | **Poradné** | -| `check:workflows` | Kontrola workflowov (actionlint + zizmor); chýbajúce/nefunkčné skenery, neplatné správy alebo chýbajúca základná hodnota ratchetu spôsobia zlyhanie so stavom INCOMPLETE. Platné nálezy sa riadia vybranou prísnou/poradnou/ratchet politikou | Vykonanie je povinné; ratchet zizmor je v CI blokujúci | -| `check:openapi-breaking` | Nekompatibilné zmeny verejného kontraktu API (`openapi.yaml`) oproti základnej vetve (oasdiff) — vytvorí `openapiBreaking=N`; preskočí sa, ak oasdiff chýba alebo základnú špecifikáciu nemožno načítať | **Poradné** | +| Skript | Overuje | Blokujúce | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| `check:circular-deps` | Žiadne cyklické závislosti (dpdm) | **Informatívne** | +| `check:bundle-size` | Veľkosť balíka neprekračuje limit | **Informatívne** | +| `check:secrets` | Skenovanie tajných údajov (gitleaks) — preskočí sa, ak binárny súbor chýba | **Informatívne** | +| `check:vuln-ratchet` | Zraniteľnosti závislostí (osv-scanner) sa nezhoršili — preskočí sa, ak binárny súbor chýba | **Informatívne** | +| `check:workflows` | Kontrola pracovných postupov (actionlint + zizmor); chýbajúce/nefunkčné skenery, neplatné správy alebo chýbajúca základná hodnota sprísňovania zlyhajú so stavom INCOMPLETE. Platné nálezy sa riadia vybranou prísnou/informatívnou/sprísňovacou politikou | Spustenie je povinné; sprísňovanie zizmor je v CI blokujúce | +| `check:openapi-breaking` | Nekompatibilné zmeny verejného kontraktu API (`openapi.yaml`) oproti základnej vetve (oasdiff) — vytvára `openapiBreaking=N`; preskočí sa, ak oasdiff chýba alebo základnú špecifikáciu nemožno načítať | **Informatívne** | ### Úloha: `docs-sync-strict` -Spúšťa sa pri každom PR do `main`. Pri zlyhaní blokuje zlúčenie. - -| Skript | Overuje | Blokujúce | -| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `check:docs-all` | Metakontrola, ktorá postupne spúšťa 6 čiastkových kontrol uvedených nižšie | Áno | -| ↳ `check:docs-sync` | Konzistentnosť verzií v CHANGELOG-u / OpenAPI / llm.txt | Áno | -| ↳ `check:docs-counts` | Počty v texte (počet poskytovateľov, počet migrácií atď.) sú v rámci sprísňujúceho sa tolerančného rozsahu skutočných počtov | Áno | -| ↳ `check:env-doc-sync` | Každá premenná prostredia v `.env.example` je zdokumentovaná v tabuľke dokumentácie a naopak | Áno | -| ↳ `check:deprecated-versions` | V dokumentácii sa nenachádzajú žiadne reťazce zastaraných verzií | Áno | -| ↳ `check:doc-links` | Interné markdownové odkazy v dokumentácii odkazujú na existujúce súbory (formát `[text]`/`(path)`) | Áno | -| ↳ `check:fabricated-docs` | Trasy, premenné prostredia, príkazy CLI, názvy hookov a cesty k súborom uvedené v dokumentácii existujú v kódovej základni. Tvrdá kontrola cez `--strict`; bez príznaku iba upozornenie. | Áno (cez `--strict` v CI) | -| `check:cli-i18n` | Reťazce príkazov CLI sa nachádzajú vo všetkých lokalizačných súboroch i18n | Áno | -| `check:openapi-coverage` | Špecifikácia OpenAPI pokrýva aspoň postupne zvyšované minimum skutočných trás | Áno | -| `check:openapi-security-tiers` | Anotácie úrovní zabezpečenia v `openapi.yaml` sú konzistentné s klasifikáciami v `routeGuard.ts` | **Odporúčacie** | -| `check:openapi-routes` | Každá cesta v `openapi.yaml` odkazuje na skutočný súbor `route.ts` (ochrana proti halucináciám) | Áno | -| `check:docs-symbols` | Každý odkaz `/api/...` v `docs/**/*.md` odkazuje na skutočný súbor `route.ts` (ochrana proti halucináciám) | Áno | -| `i18n translation drift` | Nepreložené kľúče v lokalizačných súboroch i18n — iba upozornenie | **Odporúčacie** | +Spúšťa sa pri každom PR do vetvy `main`. Pri zlyhaní zablokuje zlúčenie. + +| Skript | Overuje | Blokujúce | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `check:docs-all` | Metabrána, ktorá postupne spúšťa 6 nižšie uvedených čiastkových brán | Áno | +| ↳ `check:docs-sync` | Konzistentnosť verzií v CHANGELOG / OpenAPI / llm.txt | Áno | +| ↳ `check:docs-counts` | Počty v texte (počet poskytovateľov, počet migrácií atď.) sú v rámci sprísňujúceho sa tolerančného rozsahu skutočných počtov | Áno | +| ↳ `check:env-doc-sync` | Každá premenná prostredia v `.env.example` je zdokumentovaná v tabuľke dokumentácie a naopak | Áno | +| ↳ `check:deprecated-versions` | V dokumentácii sa nenachádzajú žiadne reťazce zastaraných verzií | Áno | +| ↳ `check:doc-links` | Interné markdownové odkazy v dokumentácii odkazujú na existujúce súbory (vo formáte `[text]`/`(path)`) | Áno | +| ↳ `check:fabricated-docs` | Trasy, premenné prostredia, príkazy CLI, názvy hookov a cesty k súborom uvedené v dokumentácii existujú v kódovej báze. Pevná brána cez `--strict`; bez príznaku mäkké zlyhanie. | Áno (cez `--strict` v CI) | +| `check:cli-i18n` | Reťazce príkazov CLI sa nachádzajú vo všetkých lokalizačných súboroch i18n | Áno | +| `check:openapi-coverage` | Špecifikácia OpenAPI pokrýva aspoň postupne zvyšovanú minimálnu hranicu skutočných trás | Áno | +| `check:openapi-security-tiers` | Anotácie úrovní zabezpečenia v `openapi.yaml` sú konzistentné s klasifikáciami v `routeGuard.ts` | **Odporúčacie** | +| `check:openapi-routes` | Každá cesta v `openapi.yaml` odkazuje na skutočný súbor `route.ts` (ochrana proti halucináciám) | Áno | +| `check:docs-symbols` | Každý odkaz `/api/...` v `docs/**/*.md` odkazuje na skutočný súbor `route.ts` (ochrana proti halucináciám) | Áno | +| `i18n translation drift` | Nepreložené kľúče v lokalizačných súboroch i18n — iba upozornenie | **Odporúčacie** | ### Úloha: `i18n-ui-coverage` -| Skript | Overuje | Blokujúce | -| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (vložený) | Pokrytie kľúčov i18n používateľského rozhrania je ≥ 65 % | Áno | -| `check-ui-value-drift` (vložený) | Prepísaná anglická **hodnota** po sebe nezanechá žiadny zastaraný preklad | Áno | -| `check-new-key-coverage` (vložený) | **Nový** anglický kľúč je preložený v každej lokalizácii — značka `__MISSING__:` je odmietnutá | Áno | -| `check-translation-ratio` | Pomer skutočných prekladov podľa lokalizácie (položky zhodné s angličtinou / zástupné / chýbajúce mimo zoznamu povolených výnimiek) nesmie prekročiť `config/quality/i18n-translation-baseline.json` + toleranciu | **Odporúčacie** | +| Skript | Overuje | Blokujúce | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | +| `check-ui-keys-coverage` (inline) | Pokrytie kľúčov i18n používateľského rozhrania je ≥ 65 % | Áno | +| `check-ui-value-drift` (inline) | Prepísaná anglická **hodnota** po sebe nezanechá žiadny zastaraný preklad | Áno | +| `check-new-key-coverage` (inline) | **Nový** anglický kľúč je preložený v každej lokalizácii — značka `__MISSING__:` sa zamietne | Áno | +| `check-translation-ratio` | Pomer skutočných prekladov v jednotlivých lokalizáciách (hodnoty zhodné s angličtinou / zástupné hodnoty / chýbajúce listy mimo zoznamu povolených výnimiek) nesmie prekročiť `config/quality/i18n-translation-baseline.json` + toleranciu | **Odporúčacie** | -Vyžaduje `fetch-depth: 0` — kontrola odchýlok hodnôt porovnáva `en.json` voči spoločnému predkovi vetiev. +Vyžaduje `fetch-depth: 0` — brána odchýlky hodnôt porovnáva `en.json` so spoločným základom zlúčenia. -#### `check-ui-value-drift` — kontrola zastaraných prekladov +#### `check-ui-value-drift` — brána zastaraných prekladov -Zachytáva jedinú regresiu i18n, ktorú ostatné kontroly zo svojej podstaty nedokážu odhaliť: anglická hodnota -sa prepíše, ale preklady odvodené od _predchádzajúcej_ anglickej verzie zostanú nezmenené, takže -používatelia iných jazykov naďalej čítajú sebavedomo formulovaný, no už nesprávny text. +Zachytáva tú regresiu i18n, ktorú ostatné brány zo svojej podstaty nedokážu odhaliť: anglická hodnota +sa prepíše a preklady odvodené od _predchádzajúcej_ anglickej verzie zostanú nezmenené, takže +používatelia iných jazykov naďalej čítajú presvedčivo formulovaný, no už nesprávny text. -Tento problém sa skutočne dostal do vydanej verzie. `oauthModal.googleOAuthWarning` bol prepísaný pri pridaní -pomocníka na prihlásenie Antigravity (#5203); **39 zo 43 lokalizácií** si ponechalo text, ktorý operátorom -prikazoval „skopírovať celú URL adresu a vložiť ju nižšie“ — postup, ktorý pri tomto poskytovateľovi nemožno -dokončiť. Problém zostal nepovšimnutý až do #8463, pretože: +Toto sa skutočne dostalo do vydania. `oauthModal.googleOAuthWarning` bol prepísaný po pridaní pomocníka +prihlásenia Antigravity (#5203); **39 zo 43 lokalizácií** si ponechalo text, ktorý operátorom prikazoval „skopírovať +celú adresu URL a vložiť ju nižšie“ — postup, ktorý sa pri danom poskytovateľovi nedá dokončiť. Problém +zostal nepovšimnutý až do #8463, pretože: - `sync-ui-keys` dopĺňa iba kľúče, ktoré **chýbajú**, nikdy nie tie, ktoré sú **zastarané**; - `check-ui-keys-coverage` počíta _prítomnosť_ kľúčov, takže zastaraný preklad sa započíta ako pokrytý; -- `check-translation-drift` sleduje zrkadlá dokumentácie `docs/i18n//**.md` — - nikdy nečíta `src/i18n/messages/*.json`. Blokujúce v úlohe `docs-sync-strict` od opätovnej synchronizácie - 2026-09: upravte hlavný dokument → `npm run i18n:run -- --files=` (na úrovni sekcie, nenáročné). +- `check-translation-drift` sleduje zrkadlové kópie dokumentácie `docs/i18n//**.md` — + nikdy nečíta `src/i18n/messages/*.json`. V úlohe `docs-sync-strict` je blokujúci od opätovnej + synchronizácie 2026-09: upravte základný dokument → `npm run i18n:run -- --files=` (na úrovni sekcií, nenáročné). -**Zohľadňuje rozdiely, nie je založená na východiskovom stave.** Porovnáva `en.json` v spoločnom predkovi s -pracovným stromom; pre každý kľúč, ktorého anglická hodnota sa zmenila, je každý jazyk, ktorý stále obsahuje -nezmenený preklad, zastaraný. Týmto sa zámerne **zmrazí už existujúci dlh** — z rozdielu -nemožno zistiť, z ktorej starej anglickej verzie dlhodobý preklad pochádza, takže kontrola posudzuje -iba to, čoho sa aktuálna zmena týka. Alternatíva (východiskový hash pre každý kľúč) by si vyžadovala -~600 KB generovaný súbor, 3× väčší než najväčší existujúci východiskový súbor, ktorý by sa menil pri každom i18n PR. +**Zohľadňuje rozdiely, nie je založená na východiskovom stave.** Porovnáva `en.json` v spoločnom predkovi vetiev +s pracovným stromom; pri každom kľúči, ktorého anglická hodnota sa zmenila, je každý jazyk, ktorý stále obsahuje +nezmenený preklad, zastaraný. Tým sa zámerne **zmrazí už existujúci dlh** — z rozdielu +nemožno zistiť, z ktorej starej anglickej verzie dlhodobo existujúci preklad pochádza, preto kontrola posudzuje +iba to, čo aktuálna zmena ovplyvňuje. Alternatíva (východiskový stav s hashom pre každý kľúč) by si vyžadovala +približne 600 KB generovaný súbor, 3× väčší než najväčší existujúci východiskový súbor, ktorý by sa menil pri každom i18n PR. -Kontrolu možno splniť dvoma spôsobmi: +Požiadavku možno splniť dvoma spôsobmi: 1. aktualizovať dotknuté preklady alebo -2. nastaviť ich na `__MISSING__:` — za behu sa potom zobrazí opravená angličtina +2. nastaviť ich na `__MISSING__:` — aplikácia potom za behu poskytne opravený anglický text (`src/i18n/request.ts::deepMergeFallback`, #7258) a kľúč sa zaradí do frontu na preklad. Ak sa zmenil **význam** reťazca, uprednostnite **premenovanie kľúča**: nový kľúč nemôže zdediť @@ -233,20 +231,20 @@ zastaraný preklad. Tento postup bol použitý v #8463. ```bash npm run i18n:check-value-drift # striktné (spúšťa sa v CI) -npm run i18n:check-value-drift:warn # iba hlásenie +npm run i18n:check-value-drift:warn # iba výpis BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Skončí s kódom 0 a hlásením `SKIP reason=base-unresolved`, keď sa východiskový katalóg nedá načítať (plytký -klon bez východiskovej referencie), rovnako ako `check-openapi-breaking`. +Skončí s kódom 0 a hlásením `SKIP reason=base-unresolved`, keď nemožno načítať základný katalóg (plytký +klon bez základnej referencie), rovnako ako `check-openapi-breaking`. ### Úloha: `i18n` -Úplná matica validácie i18n (jedna úloha pre každý jazyk). Celá úloha je informačná. +Úplná matica validácie i18n (jedna úloha pre každý jazyk). Celá úloha je informatívna. -| Skript | Overuje | Blokovanie | -| ------------------------------- | -------------------------------- | --------------------------------------------------------- | -| `validate_translation.py quick` | Úplnosť prekladu pre každý jazyk | **Informačné** (`continue-on-error: true` pre celú úlohu) | +| Skript | Overuje | Blokovanie | +| ------------------------------- | -------------------------------- | ----------------------------------------------------------- | +| `validate_translation.py quick` | Úplnosť prekladu pre každý jazyk | **Informatívne** (`continue-on-error: true` pre celú úlohu) | ### Úloha: `pr-test-policy` @@ -255,30 +253,30 @@ Spúšťa sa iba pri pull requestoch. | Skript | Overuje | Blokovanie | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | `check:pr-test-policy` | PR, ktoré menia produkčný kód v `src/`, `open-sse/`, `electron/` alebo `bin/`, musia obsahovať alebo aktualizovať testy (Prísne pravidlo č. 8) | Áno | -| `check:test-masking` | Zmenené testovacie súbory neznižujú celkový počet asertov ani nepridávajú tautológie `assert.ok(true)` | Áno | +| `check:test-masking` | Zmenené testovacie súbory neznižujú čistý počet kontrol ani nepridávajú tautológie `assert.ok(true)` | Áno | | `check:pr-evidence` | Telo PR uvádza dôkazy z testov/VPS pre danú zmenu (automatizuje Prísne pravidlo č. 18 prehľadávaním textu PR — krehké, pozri Backlog) | Áno | ### Úloha: `test-vitest` -Spúšťa sa po `build`. Pri zlyhaní blokuje zlúčenie. +Spúšťa sa po `build`. Pri zlyhaní zablokuje zlúčenie. -| Sada | Overuje | Blokovanie | -| ---------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Server MCP (110 nástrojov), autoCombo, cache — spúšťač vitest | Áno | -| `test:vitest:ui` | Testy komponentov používateľského rozhrania — spúšťač vitest | **Blokujúce** — už existujúce zlyhania sú explicitne vylúčené v `vitest.config.ts`; nové zlyhania spôsobia zlyhanie úlohy | +| Sada | Overuje | Blokovanie | +| ---------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Server MCP (110 nástrojov), autoCombo, vyrovnávacia pamäť — testovací nástroj vitest | Áno | +| `test:vitest:ui` | Testy komponentov používateľského rozhrania — testovací nástroj vitest | **Blokujúce** — už existujúce zlyhania sú explicitne vylúčené v `vitest.config.ts`; nové zlyhania spôsobia zlyhanie úlohy | -### Nočné pracovné postupy (plánované, informačné) +### Nočné pracovné postupy (plánované, informatívne) -Spúšťajú sa podľa plánu cron (a pomocou `workflow_dispatch`), nikdy nie pri PR. Všetky sú informačné. +Spúšťajú sa podľa plánu cron (a cez `workflow_dispatch`), nikdy nie pri PR. Všetky sú informatívne. -| Pracovný postup | Overuje | Blokovanie | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | -| `nightly-property` | Testy vlastností fast-check s náhodným počiatočným číslom + vysokým počtom behov | **Informačné** | -| `nightly-resilience` | Kontrola rastu haldy, vkladanie chaotických porúch, záťažové/dlhotrvajúce testovanie k6 | **Informačné** | -| `nightly-llm-security` | Ochrana promptfoo proti injekciám (režim blokovania) + sondy garak (preskočené bez tajného kľúča poskytovateľa) | **Informačné** | -| `nightly-schemathesis` | Fuzzovanie kontraktu OpenAPI (schemathesis) proti spustenej službe OmniRoute pomocou `docs/openapi.yaml` — odhaľuje porušenia špecifikácie / neošetrené chyby 500 (Fáza 8 B.4) | **Informačné** | -| `nightly-mutation` | Skóre mutačného testovania Stryker v rýchlej vetve jednotkových testov — prežívajúce mutanty odhaľujú slabé aserty | **Informačné** | -| `nightly-compat` | Matica kompatibility enginu Node naprieč podporovanými rozsahmi `engines.node` | **Informačné** | +| Pracovný postup | Overuje | Blokovanie | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `nightly-property` | Testy vlastností fast-check s náhodným počiatočným semenom a vysokým počtom spustení | **Informatívne** | +| `nightly-resilience` | Kontrolu rastu haldy, vkladanie porúch na testovanie odolnosti, záťažové/dlhodobé testy k6 | **Informatívne** | +| `nightly-llm-security` | Ochranu promptfoo proti injekciám (režim blokovania) + sondy garak (preskočené bez tajného kľúča poskytovateľa) | **Informatívne** | +| `nightly-schemathesis` | Fuzzovanie kontraktu OpenAPI (schemathesis) voči spustenej službe OmniRoute pomocou `docs/openapi.yaml` — odhaľuje porušenia špecifikácie / neošetrené chyby 500 (Fáza 8 B.4) | **Informatívne** | +| `nightly-mutation` | Skóre mutačného testovania Stryker v rýchlej vetve jednotkových testov — prežívajúce mutanty odhaľujú slabé kontroly | **Informatívne** | +| `nightly-compat` | Maticu kompatibility jadra Node v podporovaných rozsahoch `engines.node` | **Informatívne** | --- @@ -542,43 +540,43 @@ chybu pripojenia a nikdy nepreruší relácie. --- -## Zoznam racionalizácie (preskúmanie ROI — Fáza 9 Vlna 3) +## Zoznam racionalizačných úloh (preskúmanie ROI — fáza 9, vlna 3) -Tento inventár bol 2026-06-17 zosúladený s `ci.yml` (predchádzajúca verzia vynechala +Tento inventár bol 2026-06-17 zosúladený s `ci.yml` (predchádzajúca verzia vynechávala `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Preskúmanie ROI zosúladenej množiny -identifikovalo nasledujúcich kandidátov na racionalizáciu. **Zlúčenia sú mechanické zmeny -CI; prepnutia/odstránenia sú politické rozhodnutia vyhradené pre operátora.** Nič z nižšie +identifikovalo nasledujúcich kandidátov na racionalizáciu. **Zlúčenia sú mechanické zmeny CI; +prepnutia/odstránenia sú politické rozhodnutia vyhradené pre operátora.** Nič z nižšie uvedeného zatiaľ nebolo aplikované. **Vyššie takisto nezdokumentované** (informatívne, nízka výpovedná hodnota): úloha `docs-lint` (markdownlint + Vale, celá úloha s `continue-on-error`) a samostatné pracovné postupy skenerov `semgrep.yml` / `codeql.yml` / `scorecard.yml`. Hodnota `semgrepFindings: 0` sa nachádza v -`quality-baseline.json`, ale nie je prepojená s blokujúcou západkou v `ci.yml` — metrika je +`quality-baseline.json`, ale v `ci.yml` nie je napojená na blokujúcu západku — metrika je momentálne osirelá. ### Zlúčenie / deduplikácia (mechanické, nižšie riziko) -Každý kandidát bol 2026-06-17 overený voči aktuálnemu stavu kontrolných brán (dôveruj, ale preveruj); -ukázalo sa, že viacero „zrejmých“ zlúčení skrýva technický dlh a **nie sú** priamočiarymi náhradami. +Každý kandidát bol 2026-06-17 overený voči aktuálnemu stavu brán (dôveruj, ale preveruj); +ukázalo sa, že viaceré „zjavné“ zlúčenia skrývajú dlh a **nie sú** bezproblémovými náhradami. -- **`check:docs-sync` sa spúšťa dvakrát** — samostatne v úlohe `lint` a znova v rámci `check:docs-all` (`docs-sync-strict`) aj v pre-commit hooku husky. ✅ **HOTOVO** — samostatné spustenie v úlohe `lint` bolo odstránené. -- **Skenovanie CVE** — ❌ **NIE JE to čisté zlúčenie.** `audit:deps` bezpodmienečne zlyhá pri akejkoľvek CVE s vysokou/kritickou závažnosťou; `check:vuln-ratchet` (osv) zlyhá iba pri _regresii_ oproti východiskovému stavu (aktuálne 1 MODERATE). Odlišná sémantika — odstránením `audit:deps` by sa stratila absolútna kontrolná brána pre vysokú/kritickú závažnosť. Ponechať obe. -- **Detekcia cyklov** — ❌ **NIE JE to čisté zlúčenie.** `check:circular-deps` (dpdm) hlási **91 cyklov** (preto má informatívny charakter); nemožno ho zmeniť na blokujúci bez ich predchádzajúceho vyriešenia a má širší rozsah než úspešná, cielene zostavená kontrola `check:cycles`. Ponechať `check:cycles` ako blokujúcu; vyriešenie 91 cyklov dpdm je samostatnou položkou zoznamu úloh. -- **Zložitosť** — ✅ **HOTOVO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jeden prechod ESLint, počítanie podľa ruleId, takže východiskové hodnoty cyklomatickej zložitosti + maximálneho počtu riadkov a kognitívnej zložitosti zostávajú nezávislé; samostatné `check:complexity` / `check:cognitive-complexity` zostávajú zachované na lokálne použitie s `--update`. -- **Ochrana `/api` proti halucináciám** — ✅ **HOTOVO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jeden inventár FS pre `src/app/api`, openapi-routes + docs-symbols naďalej hlásia výsledky nezávisle; samostatné kontroly zostávajú zachované na lokálne spustenia. -- **`check:node-runtime` sa spúšťa v 11 úlohách** — ⚠️ **nízke ROI.** Každá používa samostatný runner a kontrola trvá <1 s; celková úspora je približne 10 s za cenu straty lacnej ochrany v každej úlohe. Nestojí to za tieto zmeny. -- **`typecheck:noimplicit:core` v kontrole lint CI** — ✅ **odstránené z úlohy lint** (bolo informatívne s `continue-on-error`); blokujúci typový povrch tvoria `typecheck:core` + `check:type-coverage`. Lokálny skript zostal zachovaný. +- **`check:docs-sync` sa spúšťa dvakrát** — samostatne v úlohe `lint` a znova v rámci `check:docs-all` (`docs-sync-strict`) a hooku husky pre pre-commit. ✅ **HOTOVO** — samostatné spustenie v úlohe `lint` bolo odstránené. +- **Skenovanie CVE** — ❌ **NIE JE to bezproblémové zlúčenie.** `audit:deps` tvrdo zlyhá pri ľubovoľnej CVE s vysokou/kritickou závažnosťou; `check:vuln-ratchet` (osv) zlyhá iba pri _regresii_ oproti východiskovej hodnote (aktuálne 1 MODERATE). Odlišná sémantika — odstránením `audit:deps` by sa stratila absolútna brána pre vysokú/kritickú závažnosť. Ponechať obe. +- **Detekcia cyklov** — ✅ **HOTOVO** (#15159 G-01/G-02). Starý text tu označoval `check:cycles` za „zelenú, starostlivo zostavenú“ bránu a odôvodňoval jej zachovanie ako blokujúcej tým, že `check:circular-deps` (dpdm) hlásil 91 cyklov. Tento zelený stav bol **falošne zelený**: `check:cycles` skenoval 5 podadresárov (450 súborov), vyhľadával iba statické `import|export … from` a zahadzoval každý špecifikátor `@/` a `@omniroute/open-sse/`, takže nemohol zachytiť cykly dynamických importov a aliasov, ktoré v repozitári prevládali. Opravené: brána teraz prechádza `src` + `open-sse` (5023 súborov), zhromažďuje špecifikátory z AST TypeScriptu (takže `import("…")` sa započítava a `typeof import("…")` v typovej pozícii nie) a rozpoznáva `paths` z tsconfig. Nachádza **14** cyklov, nie 0. Keďže 14 existujúcich cyklov nemožno opraviť v PR pre bránu, `check:cycles` je teraz **západka** (`--ratchet`, strop `metrics.cycles.value = 14` v `quality-baseline.json`, `direction: down`) — blokuje akúkoľvek _regresiu_ a počet môže iba klesať. CI spúšťa `npm run check:cycles:ratchet`. Postupné odstraňovanie prebieha spolu s **A-01**. `check:circular-deps` (dpdm) zostáva informatívny ako širší druhý názor. +- **Zložitosť** — ✅ **HOTOVO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): jeden prechod ESLintom, počítanie podľa ruleId, aby východiskové hodnoty cyklomatickej zložitosti + max-lines a kognitívnej zložitosti zostali nezávislé; jednotlivé `check:complexity` / `check:cognitive-complexity` zostávajú na lokálne použitie s `--update`. +- **Ochrana `/api` proti halucináciám** — ✅ **HOTOVO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): jeden inventár FS pre `src/app/api`, openapi-routes + docs-symbols naďalej hlásia výsledky nezávisle; jednotlivé kontroly zostávajú na lokálne spúšťanie. +- **`check:node-runtime` sa spúšťa v 11 úlohách** — ⚠️ **nízke ROI.** Každá beží na samostatnom runneri a kontrola trvá <1 s; celková úspora je približne 10 s za cenu straty lacnej ochrany v každej úlohe. Nestojí to za súvisiace zmeny. +- **`typecheck:noimplicit:core` v úlohe lint CI** — ✅ **odstránené z úlohy lint** (bolo informatívne s `continue-on-error`); blokujúci typový povrch tvoria `typecheck:core` + `check:type-coverage`. Lokálny skript bol zachovaný. ### Prepnúť / rozhodnúť (politika operátora) -- `check:openapi-security-tiers` (informatívne) — ❌ **NEDÁ SA čisto prepnúť.** Končí s kódom 0, ale upozorňuje, že viacerým trasám `traffic-inspector` v rámci `LOCAL_ONLY_API_PREFIXES` chýba anotácia `x-loopback-only: true`. Jeho vynútenie najskôr vyžaduje pridanie týchto anotácií do `openapi.yaml`. -- `typecheck:noimplicit:core` (informatívne) — z veľkej časti ho nahrádza blokujúca západka `check:type-coverage`. Zmeniť na západku alebo odstrániť redundantný druhý prechod `tsc`. -- `test:vitest:ui` (teraz **blokujúce**) — už existujúce zlyhania sú explicitne vylúčené v `vitest.config.ts` pomocou sledovacích komentárov `// #8618`; nové zlyhania spôsobia zlyhanie úlohy. -- `check:secrets` (gitleaks, blokujúca západka zmrazená na 3 zdokumentovaných falošne pozitívnych nálezoch) — pridať tieto 3 nálezy do zoznamu povolených a dosiahnuť tak 0 alebo zmeniť kontrolu na informatívnu. Prekrýva sa s natívnym skenovaním tajomstiev GitHubu + `check:public-creds`. -- `check:pr-evidence` (blokujúce, prehľadáva text tela PR pomocou grep) — vysoké riziko falošne pozitívnych nálezov; jeho odstránenie oslabí vynucovanie pevného pravidla č. 18, takže ide o skutočné politické rozhodnutie. -- `semgrep` (samostatné informatívne) — pri rodinách OWASP sa prekrýva s CodeQL; prepojiť jeho východiskový stav so západkou alebo ho odstrániť. +- `check:openapi-security-tiers` (informatívne) — ❌ **NEDÁ SA bezproblémovo prepnúť.** Končí s kódom 0, ale upozorňuje, že viacerým trasám `traffic-inspector` pod `LOCAL_ONLY_API_PREFIXES` chýba anotácia `x-loopback-only: true`. Jej vynútenie si najprv vyžaduje pridanie týchto anotácií do `openapi.yaml`. +- `typecheck:noimplicit:core` (informatívne) — do značnej miery ho nahrádza blokujúca západka `check:type-coverage`. Prepnúť na západku alebo odstrániť redundantný druhý prechod `tsc`. +- `test:vitest:ui` (teraz **blokujúce**) — existujúce zlyhania sú explicitne vylúčené vo `vitest.config.ts` pomocou sledovacích komentárov `// #8618`; nové zlyhania spôsobia zlyhanie úlohy. +- `check:secrets` (gitleaks, blokujúca západka zmrazená na 3 zdokumentovaných falošne pozitívnych nálezoch) — pridať tieto 3 prípady do zoznamu povolených a dostať sa na 0 alebo degradovať na informatívnu kontrolu. Prekrýva sa s natívnym skenovaním tajomstiev v GitHube + `check:public-creds`. +- `check:pr-evidence` (blokujúce, prehľadáva prózu v tele PR pomocou grep) — vysoké riziko falošne pozitívnych nálezov; odstránenie oslabí vynucovanie pevného pravidla č. 18, takže ide o skutočné politické rozhodnutie. +- `semgrep` (samostatné informatívne) — pri rodinách OWASP sa prekrýva s CodeQL; napojiť jeho východiskovú hodnotu na západku alebo ho odstrániť. --- diff --git a/docs/i18n/sk/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/sk/docs/compression/COMPRESSION_GUIDE.md index 2a05b936594c..6f418ba71b25 100644 --- a/docs/i18n/sk/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/sk/docs/compression/COMPRESSION_GUIDE.md @@ -469,8 +469,7 @@ jazyk (pozri Výber jazyka nižšie) preklad, namiesto angličtiny sa vloží lo V tele s `messages` kontroluje obídenie podľa obsahu (`shouldBypassCavemanOutputMode()` v `open-sse/services/compression/outputMode.ts`) posledné tri správy a preskočí štýly pre celý ťah, ak zodpovedajú kľúčovým slovám týkajúcim sa bezpečnosti, nezvratných -akcií, objasnenia alebo citlivosti na poradie. Obídenie sa vykonáva podľa nastavenia prepínača -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) na ovládacom paneli. +akcií, objasnenia alebo citlivosti na poradie. Obídenie sa vykonáva, kým je prepínač **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) na ovládacom paneli zapnutý, čo je predvolené nastavenie; pri vypnutom prepínači sa vybrané štýly použijú aj v týchto ťahoch. Keď obídenie umožní spracovanie ťahu, `placeSystemInstruction()` (rovnaký súbor), ktorý nikdy nevytvára nový `messages[0]`, umiestni blok na prvé z týchto nájdených miest: diff --git a/docs/i18n/sk/docs/reference/ENVIRONMENT.md b/docs/i18n/sk/docs/reference/ENVIRONMENT.md index ef4c2e6df4a1..9b0e89858689 100644 --- a/docs/i18n/sk/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sk/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Povinné tajomstvá -Tieto hodnoty **musia** byť nastavené pred prvým spustením. Bez nich sa aplikácia buď odmietne spustiť, alebo bude používať nezabezpečené predvolené nastavenia. - -| Premenná | Povinná | Predvolená hodnota | Zdrojový súbor | Popis | -| ---------------------------- | ------------------- | ----------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Áno** | _(žiadna)_ | `src/lib/auth` | Podpisuje/overuje všetky súbory cookie relácií ovládacieho panela (JWT). Vygenerujte pomocou `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Áno** | _(žiadna)_ | `src/lib/db/apiKeys.ts` | Šifrovací kľúč AES pre hodnoty kľúčov API uložené v SQLite. Vygenerujte pomocou `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Áno** | `CHANGEME` | Zavádzací skript | Nastavuje počiatočné heslo správcu ovládacieho panela (zodpovedá predvolenej hodnote v `.env.example` — zámerne je ponechané očividne nezabezpečené, aby si vynútilo zmenu). **Zmeňte ho pred prvým použitím.** Po prihlásení ho zmeňte cez Ovládací panel → Nastavenia → Zabezpečenie. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Áno** (produkcia) | _(nenastavené)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Zdieľané tajomstvo pre interný most WebSocket služby Codex Responses. Overuje požiadavky mosta medzi sprostredkovaním WS v aplikácii Electron/prehliadači a OmniRoute. ⚠️ **V produkcii POVINNÉ — ak nie je nastavené, všetky požiadavky mosta WS budú odmietnuté.** Vygenerujte pomocou `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nie | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicitný identifikátor na zneplatnenie vyrovnávacej pamäte service workera pre prostredie PWA (#11779); prvý v reťazci určovania hodnoty. | -| `SOURCE_VERSION` | Nie | _(nenastavené)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Druhý v reťazci — nastavujú ho zostavovacie systémy PaaS (napr. v štýle Heroku) na nasadený commit. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nie | _(odvodené)_ | `src/shared/components/PwaRegister.tsx` | Verejná hodnota určená pri zostavení, ktorú klient používa na registráciu `/sw.js?v=…`; odvodzuje sa z dvoch vyššie uvedených hodnôt a následne z git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nie (automaticky) | _(automaticky pri každom spustení)_ | `src/server/authz/policies/management.ts` | Tajomstvo pre každý proces, ktoré dokazuje, že dôveryhodná značka IP adresy partnera pochádza z vlastného HTTP servera OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware authz dôveruje lokálnosti požiadavky (obmedzenie trás LOCAL_ONLY na loopback/LAN) iba vtedy, keď značka obsahuje tento token. Generuje sa automaticky pri každom spustení — ponechajte ho nenastavený; nastavte ho napevno iba pri viacprocesových konfiguráciách, ktoré musia značku zdieľať. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nie | _(nenastavené)_ | `scripts/dev/peer-stamp.mjs` | IP adresy alebo rozsahy CIDR reverzných proxy serverov oddelené čiarkami, ktorých hlavičkám `X-Forwarded-For` / `X-Real-IP` môže zoznam povolených/zakázaných IP adries dôverovať, okrem adries loopback, adries súkromnej siete a koncových uzlov Cloudflare, ktoré sú dôveryhodné vždy. Vyžaduje sa iba pre proxy na akejkoľvek inej verejnej adrese; inak filter posudzuje vlastnú adresu daného proxy servera. | +Nenastavené tajomstvá označené ako **Áno** sa pri prvom spustení automaticky vygenerujú (`JWT_SECRET`, `API_KEY_SECRET`) a uložia do `server.env` v dátovom adresári — nastavte ich explicitne, ak je tento adresár efemérny. Výnimkou je `OMNIROUTE_WS_BRIDGE_SECRET`: ten sa do `server.env` neukladá — vstupné body servera pri každom spustení vygenerujú efemérnu hodnotu, ktorú môže použiť iba most bežiaci v rámci procesu, takže externí klienti mosta si ho musia nastaviť explicitne. + +| Premenná | Povinná | Predvolená hodnota | Zdrojový súbor | Popis | +| ---------------------------- | ------------------- | ----------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Áno** | _(žiadna)_ | `src/lib/auth` | Podpisuje/overuje všetky súbory cookie relácií ovládacieho panela (JWT). Vygenerujte pomocou `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Áno** | _(žiadna)_ | `src/lib/db/apiKeys.ts` | Šifrovací kľúč AES pre hodnoty kľúčov API uložené v SQLite. Vygenerujte pomocou `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nie | _(nenastavené)_ | `src/lib/auth/managementPassword.ts` | Pri spustení nastavuje heslo ovládacieho panela, kým nie je uložené žiadne; po uložení hesla sa ignoruje. Inštalácie cez npm a zo zdrojových kódov skopírujú `.env.example` do `.env`, takže sa spúšťajú s jeho verejne známou zástupnou hodnotou `CHANGEME`: pred prvým spustením ju nahraďte, alebo nastavte `INITIAL_PASSWORD=` (prázdne) v danom `.env`, aby sa heslo vytvorilo v sprievodcovi úvodného nastavenia pri prvej návšteve — rovnako to funguje pri nenastavenej hodnote (predvolenej v obraze Docker). Uložené heslo zmeňte v časti Ovládací panel → Nastavenia → Zabezpečenie alebo spustite `omniroute reset-password` (`node bin/reset-password.mjs` v pracovnej kópii zdrojových kódov) s `DATA_DIR` nastaveným na dátový adresár servera. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Áno** (produkcia) | _(nenastavené)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Zdieľané tajomstvo pre interný most WebSocket služby Codex Responses. Overuje požiadavky mosta medzi sprostredkovaním WS v aplikácii Electron/prehliadači a OmniRoute. ⚠️ **V produkcii POVINNÉ — ak nie je nastavené, všetky požiadavky mosta WS budú odmietnuté.** Vygenerujte pomocou `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nie | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicitný identifikátor na zneplatnenie vyrovnávacej pamäte service workera pre prostredie PWA (#11779); prvý v reťazci určovania hodnoty. | +| `SOURCE_VERSION` | Nie | _(nenastavené)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Druhý v reťazci — nastavujú ho zostavovacie systémy PaaS (napr. v štýle Heroku) na nasadený commit. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nie | _(odvodené)_ | `src/shared/components/PwaRegister.tsx` | Verejná hodnota určená pri zostavení, ktorú klient používa na registráciu `/sw.js?v=…`; odvodzuje sa z dvoch vyššie uvedených hodnôt a následne z git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nie (automaticky) | _(automaticky pri každom spustení)_ | `src/server/authz/policies/management.ts` | Tajomstvo pre každý proces, ktoré dokazuje, že dôveryhodná značka IP adresy partnera pochádza z vlastného HTTP servera OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware authz dôveruje lokálnosti požiadavky (obmedzenie trás LOCAL_ONLY na loopback/LAN) iba vtedy, keď značka obsahuje tento token. Generuje sa automaticky pri každom spustení — ponechajte ho nenastavený; nastavte ho napevno iba pri viacprocesových konfiguráciách, ktoré musia značku zdieľať. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nie | _(nenastavené)_ | `scripts/dev/peer-stamp.mjs` | IP adresy alebo rozsahy CIDR reverzných proxy serverov oddelené čiarkami, ktorých hlavičkám `X-Forwarded-For` / `X-Real-IP` môže zoznam povolených/zakázaných IP adries dôverovať, okrem adries loopback, adries súkromnej siete a koncových uzlov Cloudflare, ktoré sú dôveryhodné vždy. Vyžaduje sa iba pre proxy na akejkoľvek inej verejnej adrese; inak filter posudzuje vlastnú adresu daného proxy servera. | ### Príkazy na generovanie @@ -216,38 +216,38 @@ OmniRoute používa na všetku perzistenciu **SQLite** (prostredníctvom `better ## 4. Bezpečnosť a autentifikácia -| Premenná | Predvolená hodnota | Zdrojový súbor | Popis | -| ------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Soľ kombinovaná s hardvérovými identifikátormi na vytvorenie odtlačku zariadenia. Pre izoláciu ju zmeňte pre každé nasadenie. | -| `OMNIROUTE_CLI_SALT` | _(nenastavené = náhodná soľ pre každú inštaláciu uložená v `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Soľ HMAC na odvodenie lokálneho autentifikačného tokenu CLI. Nastavenie tejto hodnoty zmení všetky tokeny CLI v zariadení a má vždy prednosť pred uloženou soľou. Pozrite si `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Nastavuje príznak `Secure` pre súbory cookie relácie. Pri prevádzke za HTTPS **musí byť nastavený na `true`**. | -| `REQUIRE_API_KEY` | `false` | Middleware API | Keď je nastavené na `true`, všetky proxy požiadavky na `/v1/*` musia obsahovať platný kľúč API. **Tento príznak neobmedzuje `GET /v1/models`**, ktorý sa namiesto toho riadi nastavením prihlasovania riadiaceho panela (`requireAuthForModels`) — odpoveď `401` z `/v1/models` teda NEZNAMENÁ, že inferencia je chránená. Pozrite si `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Umožňuje zobraziť úplné hodnoty kľúčov API v používateľskom rozhraní riadiaceho panela. Dá sa konfigurovať prostredníctvom príznakov funkcií riadiaceho panela; na zdieľaných inštanciách predstavuje bezpečnostné riziko. | -| `NO_LOG_API_KEY_IDS` | _(prázdne)_ | `src/lib/compliance/index.ts` | Čiarkami oddelené identifikátory kľúčov API, pre ktoré sa obchádza zaznamenávanie požiadaviek (súlad s GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenastavené = neobmedzené)_ | `src/shared/utils/apiKeyPolicy.ts` | Záložný denný rozpočet požiadaviek aplikovaný na kľúče API, ktorých stĺpec `rate_limits` má hodnotu null. Nenastavená alebo prázdna hodnota: žiadny implicitný limit (#2289, #11017). Hodnota `0` znamená to isté (bez obmedzenia). Kladné celé číslo N povoľuje N/deň, 5N/týždeň, 20N/mesiac. Neplatné neprázdne hodnoty použijú pôvodné limity 1000/deň, 5000/týždeň, 20000/mesiac. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximálna povolená veľkosť tela požiadavky. Užitočné zaťaženia prekračujúce tento limit budú odmietnuté. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Skutočné telá požiadaviek s veľkosťou rovnou alebo vyššou ako táto prahová hodnota získajú pred spracovaním JSON atomický lokálny prenájom vstupu pre náročné spracovanie v rámci procesu (cesta BYTE vrátane `POST /v1/responses`). Používa rovnaký únik pri dostatočnej rezerve ako štruktúrne náročné požiadavky z [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437); naďalej je však obmedzený hodnotou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), aby sa problém [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) nevrátil. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Pevný limit trasy chatu uplatňovaný voči bajtom načítaným počas obmedzeného príjmu vrátane požiadaviek s chýbajúcou, neplatnou alebo nepravdivou hlavičkou `Content-Length`; prekročenie limitu spôsobí odpoveď `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nenastavené — bez limitu počtu požiadaviek)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** tento starší limit POČTU požiadaviek sa teraz uplatní iba vtedy, keď je explicitne nastavený. Ak zostane nenastavený (predvolené nastavenie), prijímanie náročných chatových požiadaviek sa namiesto toho riadi premennou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaticky odvodeným BAJTOVÝM rozpočtom dimenzovaným podľa skutočného pamäťového limitu procesu v **jednom procese** (jednej halde V8). Dve prekrývajúce sa požiadavky `/v1/responses` s približne 750-tisíc tokenmi spôsobia zlyhanie háld s veľkosťou ~12 GiB (#7849) — ide o varovanie týkajúce sa **pamäťového rozpočtu**, nie o pevné produktové maximum 2. Proces v dobrom stave (halda pod pomerom pre odľahčenie) MÔŽE prijať viac súbežných dlhých požiadaviek `/v1/responses` prostredníctvom `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Desiatky dlhých klientov SSE (40–50) závisia od haldy + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nie od „maxima 2“. Slepé zvýšenie tejto hodnoty s cieľom „využiť hostiteľa“ znova spôsobí #7849. **Haldy** násobte pomocou **N nezávislých `DATA_DIR`** (#11024); nikdy nepoužívajte `replicas>1` nad jedným súborom SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automaticky odvodené)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** prepísanie automaticky odvodeného bajtového rozpočtu príjmu (25 % z prísnejšieho pamäťového limitu V8/cgroup vydelených 8-násobným prechodným zosilnením). Odvodené aj explicitné hodnoty sú obmedzené na rozsah 8 MiB – 2 GiB. Telo väčšie než efektívny rozpočet okamžite zlyhá s `413 body_exceeds_budget`; súperenie medzi telami, ktoré možno samostatne obslúžiť, naďalej vracia opakovateľnú chybu `503`. 40–50 súbežných dlhých klientov SSE závisí od tohto rozpočtu + haldy, nejde o pevné „maximum 2“. Pred ladením si na `/api/monitoring/health` prečítajte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Pomer tlaku na haldu (`heapUsed / heap_size_limit`) na odľahčenie prijímania náročných požiadaviek typu BYTE a STRUCTURE (#10183, #10268, #10437). Súbežná náročná požiadavka nad limit `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` sa odmietne opakovateľnou chybou `503` iba vtedy, keď je halda ZÁROVEŇ na úrovni tohto pomeru alebo vyššie; pri halde v dobrom stave sa prijme prostredníctvom rezervy pre dobrý stav. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (predvolene `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Obmedzená dodatočná kapacita pre rýchlu cestu pri halde v dobrom stave (#10437) pre typy STRUCTURE aj BYTE (`admitChatRequest` vrátane tiel ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez tohto obmedzenia každá požiadavka pri vyťaženom systéme, ale halde v dobrom stave, obchádzala riadenie prijímania bez akéhokoľvek stropu. Keď je prostredníctvom cesty pre haldu v dobrom stave aktívnych toľko súbežných prenájmov, ďalšie požiadavky pri vyťažení prejdú na ROVNAKÚ cestu obmedzeného čakania/odľahčenia, aká sa používa pri skutočnom tlaku na haldu. Hodnota `0` toto obídenie úplne zakáže. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Počet správ, pri ktorom sa chatová požiadavka klasifikuje ako náročná, aj keď je jej telo pod bajtovou prahovou hodnotou. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Počet nástrojov, pri ktorom sa chatová požiadavka klasifikuje ako náročná, aj keď je jej telo pod bajtovou prahovou hodnotou. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervatívny odhad počtu tokenov podľa veľkosti reťazca, pri ktorom sa požiadavka klasifikuje ako náročná; ide o zástupnú hodnotu nákladov na prijatie, nie o tokenizáciu poskytovateľa na účely fakturácie. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (vypnuté) | `src/shared/middleware/chatBodyAdmission.ts` | Voliteľný limit histórie chatu aktivovaný výslovným nastavením. Predvolene je vypnutý: počet správ je zásadou nasadenia, nie univerzálnou vlastnosťou požiadavky, a obmedzenie na tomto mieste odmietne konverzácie konečnou chybou `413` skôr, než ich kompresný kanál dokáže upraviť do obslúžiteľnej podoby. Rast haldy je obmedzený pomocou `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` a odľahčenia pri tlaku na haldu. V nasadeniach s obmedzenou pamäťou, ktoré potrebujú pevný strop, nastavte kladnú hodnotu; prekročenie potom spôsobí štruktúrovanú odpoveď `413` vyžadujúcu kompaktnú podobu. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Pevný limit pre neprúdovú odpoveď nadradeného servera, ktorá sa celá ukladá do vyrovnávacej pamäte. Po jeho prekročení sa čítačka nadradeného servera zruší a požiadavka rýchlo zlyhá namiesto toho, aby reťazec neobmedzene rástol až do vyčerpania haldy. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximálny počet bajtov prenášaných z hlavičiek odpovede nadradeného servera. Po prekročení limitu sa hlavičky s nižšou prioritou (napr. vlastné `x-codex-*`, `x-oai-request-id`) vynechajú, aby sa dodržali bežné limity hlavičiek reverzných proxy serverov. Nastavením vyššej hodnoty sa prenesie viac metadát nadradeného servera za cenu väčšej veľkosti hlavičiek odpovede. | -| `CORS_ORIGIN` | _(nenastavené)_ | `src/server/cors/origins.ts` | Starší zoznam povolených zdrojov CORS podporujúci jeden zdroj. Pri nových nasadeniach uprednostnite `CORS_ALLOWED_ORIGINS`. CORS je určený iba pre klientov API v prehliadači používajúcich požiadavky medzi zdrojmi; autentifikované zápisy ovládacieho panela namiesto toho používajú požiadavky z rovnakého zdroja spolu s ochranou CSRF viazanou na reláciu. | -| `CORS_ALLOWED_ORIGINS` | _(nenastavené)_ | `src/server/cors/origins.ts` | Čiarkami oddelený zoznam povolených zdrojov CORS. Zástupný znak sa neodošle, pokiaľ nie je explicitne nakonfigurované `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Núdzová možnosť určená iba na vývoj, ktorá odošle späť ľubovoľnú hodnotu `Origin` prehliadača. Nepovoľujte ju v zdieľaných ani produkčných nasadeniach. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokuje volania poskytovateľov smerujúce na súkromné, loopback alebo link-local rozsahy IP adries. Vypínajte iba v izolovaných testovacích prostrediach. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Povoľuje adresy URL poskytovateľov smerujúce do súkromných/lokálnych sietí (localhost, 192.168.x.x, 10.x.x.x atď.). **VYŽADUJE SA pre poskytovateľov prevádzkovaných vo vlastnej réžii** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Pri hodnote `false` ovládací panel odmietne overenie lokálnych adries URL. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Povoľuje pridávanie/overovanie poskytovateľov na lokálnych/súkromných adresách (127.0.0.1, localhost, LAN, súkromné rozsahy) — obmedzené na cestu overovania poskytovateľa. **Predvolená hodnota je `true`** (uprednostňuje lokálne adresy); nastavením na `false` vynútite striktné blokovanie všetkého okrem verejných adries. Koncové body cloudových metadát (169.254.169.254, metadata.google.internal) zostávajú zablokované bez ohľadu na toto nastavenie. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Umožňuje trasám `/v1/audio/*` (prepisy, reč, preklady) používať uzol poskytovateľa kompatibilný s OpenAI a hostovaný mimo localhost. Predvolene je vypnuté — smerovanie zvuku na vzdialeného hostiteľa mení identitu odchádzajúcej komunikácie a musí byť explicitným rozhodnutím prevádzkovateľa. Loopback/súkromné uzly (localhost, 127.0.0.1, 172.16-31.x) sú vždy povolené a toto nastavenie ich neovplyvňuje. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Umožňuje požiadavke `POST /v1/rerank` (a loopback kroku opätovného zoradenia pamäťového nástroja) používať uzol poskytovateľa kompatibilný s OpenAI a hostovaný mimo localhost — zariadenie v sieti LAN alebo rovnocenný uzol Tailscale, na ktorom beží TEI, Infinity, vLLM atď. Predvolene je vypnuté — smerovanie na vzdialeného hostiteľa mení identitu odchádzajúcej komunikácie a musí byť explicitným rozhodnutím prevádzkovateľa. Loopback uzly (localhost, 127.0.0.1, 172.16-31.x) sú vždy povolené a toto nastavenie ich neovplyvňuje. Vzdialené uzly musia tiež spĺňať zásady pre odchádzajúce adresy URL poskytovateľa (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); na hostiteľov cloudových metadát sa požiadavky nikdy nesmerujú. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Keď je povolené OIDC, zakáže prihlasovanie heslom, takže používatelia sa môžu autentifikovať iba prostredníctvom jednotného prihlásenia OIDC. Akceptuje sa aj jednoduchý alias `OIDC_DISABLE_PASSWORD_LOGIN`; prednosť má príznak funkcie ovládacieho panela s rovnakým kľúčom. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Jednoduchý alias pre `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Premenná | Predvolená hodnota | Zdrojový súbor | Popis | +| ------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Soľ kombinovaná s hardvérovými identifikátormi na vytvorenie odtlačku zariadenia. Pre izoláciu ju zmeňte pre každé nasadenie. | +| `OMNIROUTE_CLI_SALT` | _(nenastavené = náhodná soľ pre každú inštaláciu uložená v `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Soľ HMAC na odvodenie lokálneho autentifikačného tokenu CLI. Nastavenie tejto hodnoty zmení všetky tokeny CLI v zariadení a má vždy prednosť pred uloženou soľou. Pozrite si `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Nastavuje príznak `Secure` pre súbory cookie relácie. Pri prevádzke za HTTPS **musí byť nastavený na `true`**. | +| `REQUIRE_API_KEY` | `false` | Middleware API | Keď je nastavené na `true`, všetky proxy požiadavky na `/v1/*` musia obsahovať platný kľúč API. **Tento príznak neobmedzuje `GET /v1/models`**, ktorý sa namiesto toho riadi nastavením prihlasovania riadiaceho panela (`requireAuthForModels`) — odpoveď `401` z `/v1/models` teda NEZNAMENÁ, že inferencia je chránená. Pozrite si `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Umožňuje zobraziť úplné hodnoty kľúčov API v používateľskom rozhraní riadiaceho panela. Dá sa konfigurovať prostredníctvom príznakov funkcií riadiaceho panela; na zdieľaných inštanciách predstavuje bezpečnostné riziko. | +| `NO_LOG_API_KEY_IDS` | _(prázdne)_ | `src/lib/compliance/index.ts` | Čiarkami oddelené identifikátory kľúčov API, pre ktoré sa obchádza zaznamenávanie požiadaviek (súlad s GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenastavené = neobmedzené)_ | `src/shared/utils/apiKeyPolicy.ts` | Záložný denný rozpočet požiadaviek aplikovaný na kľúče API, ktorých stĺpec `rate_limits` má hodnotu null. Nenastavená alebo prázdna hodnota: žiadny implicitný limit (#2289, #11017). Hodnota `0` znamená to isté (bez obmedzenia). Kladné celé číslo N povoľuje N/deň, 5N/týždeň, 20N/mesiac. Neplatné neprázdne hodnoty použijú pôvodné limity 1000/deň, 5000/týždeň, 20000/mesiac. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximálna povolená veľkosť tela požiadavky. Užitočné zaťaženia prekračujúce tento limit budú odmietnuté. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Skutočné telá požiadaviek s veľkosťou rovnou alebo vyššou ako táto prahová hodnota získajú pred spracovaním JSON atomický lokálny prenájom vstupu pre náročné spracovanie v rámci procesu (cesta BYTE vrátane `POST /v1/responses`). Používa rovnaký únik pri dostatočnej rezerve ako štruktúrne náročné požiadavky z [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437); naďalej je však obmedzený hodnotou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), aby sa problém [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) nevrátil. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Pevný limit trasy chatu uplatňovaný voči bajtom načítaným počas obmedzeného príjmu vrátane požiadaviek s chýbajúcou, neplatnou alebo nepravdivou hlavičkou `Content-Length`; prekročenie limitu spôsobí odpoveď `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(nenastavené — bez limitu počtu požiadaviek)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** tento starší limit POČTU požiadaviek sa teraz uplatní iba vtedy, keď je explicitne nastavený. Ak zostane nenastavený (predvolené nastavenie), prijímanie náročných chatových požiadaviek sa namiesto toho riadi premennou `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — automaticky odvodeným BAJTOVÝM rozpočtom dimenzovaným podľa skutočného pamäťového limitu procesu v **jednom procese** (jednej halde V8). Dve prekrývajúce sa požiadavky `/v1/responses` s približne 750-tisíc tokenmi spôsobia zlyhanie háld s veľkosťou ~12 GiB (#7849) — ide o varovanie týkajúce sa **pamäťového rozpočtu**, nie o pevné produktové maximum 2. Proces v dobrom stave (halda pod pomerom pre odľahčenie) MÔŽE prijať viac súbežných dlhých požiadaviek `/v1/responses` prostredníctvom `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Desiatky dlhých klientov SSE (40–50) závisia od haldy + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, nie od „maxima 2“. Slepé zvýšenie tejto hodnoty s cieľom „využiť hostiteľa“ znova spôsobí #7849. **Haldy** násobte pomocou **N nezávislých `DATA_DIR`** (#11024); nikdy nepoužívajte `replicas>1` nad jedným súborom SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automaticky odvodené)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** prepísanie automaticky odvodeného bajtového rozpočtu príjmu (25 % z prísnejšieho pamäťového limitu V8/cgroup vydelených 8-násobným prechodným zosilnením). Odvodené aj explicitné hodnoty sú obmedzené na rozsah 8 MiB – 2 GiB. Telo väčšie než efektívny rozpočet okamžite zlyhá s `413 body_exceeds_budget`; súperenie medzi telami, ktoré možno samostatne obslúžiť, naďalej vracia opakovateľnú chybu `503`. 40–50 súbežných dlhých klientov SSE závisí od tohto rozpočtu + haldy, nejde o pevné „maximum 2“. Pred ladením si na `/api/monitoring/health` prečítajte `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Pomer tlaku na haldu (`heapUsed / heap_size_limit`) na odľahčenie prijímania náročných požiadaviek typu BYTE a STRUCTURE (#10183, #10268, #10437). Súbežná náročná požiadavka nad limit `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` sa odmietne opakovateľnou chybou `503` iba vtedy, keď je halda ZÁROVEŇ na úrovni tohto pomeru alebo vyššie; pri halde v dobrom stave sa prijme prostredníctvom rezervy pre dobrý stav. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (predvolene `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Obmedzená dodatočná kapacita pre rýchlu cestu pri halde v dobrom stave (#10437) pre typy STRUCTURE aj BYTE (`admitChatRequest` vrátane tiel ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bez tohto obmedzenia každá požiadavka pri vyťaženom systéme, ale halde v dobrom stave, obchádzala riadenie prijímania bez akéhokoľvek stropu. Keď je prostredníctvom cesty pre haldu v dobrom stave aktívnych toľko súbežných prenájmov, ďalšie požiadavky pri vyťažení prejdú na ROVNAKÚ cestu obmedzeného čakania/odľahčenia, aká sa používa pri skutočnom tlaku na haldu. Hodnota `0` toto obídenie úplne zakáže. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Počet správ, pri ktorom sa chatová požiadavka klasifikuje ako náročná, aj keď je jej telo pod bajtovou prahovou hodnotou. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Počet nástrojov, pri ktorom sa chatová požiadavka klasifikuje ako náročná, aj keď je jej telo pod bajtovou prahovou hodnotou. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervatívny odhad počtu tokenov podľa veľkosti reťazca, pri ktorom sa požiadavka klasifikuje ako náročná; ide o zástupnú hodnotu nákladov na prijatie, nie o tokenizáciu poskytovateľa na účely fakturácie. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (vypnuté) | `src/shared/middleware/chatBodyAdmission.ts` | Voliteľný limit histórie chatu aktivovaný výslovným nastavením. Predvolene je vypnutý: počet správ je zásadou nasadenia, nie univerzálnou vlastnosťou požiadavky, a obmedzenie na tomto mieste odmietne konverzácie konečnou chybou `413` skôr, než ich kompresný kanál dokáže upraviť do obslúžiteľnej podoby. Rast haldy je obmedzený pomocou `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` a odľahčenia pri tlaku na haldu. V nasadeniach s obmedzenou pamäťou, ktoré potrebujú pevný strop, nastavte kladnú hodnotu; prekročenie potom spôsobí štruktúrovanú odpoveď `413` vyžadujúcu kompaktnú podobu. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Pevný limit pre neprúdovú odpoveď nadradeného servera, ktorá sa celá ukladá do vyrovnávacej pamäte. Po jeho prekročení sa čítačka nadradeného servera zruší a požiadavka rýchlo zlyhá namiesto toho, aby reťazec neobmedzene rástol až do vyčerpania haldy. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximálny počet bajtov prenášaných z hlavičiek odpovede nadradeného servera. Po prekročení limitu sa hlavičky s nižšou prioritou (napr. vlastné `x-codex-*`, `x-oai-request-id`) vynechajú, aby sa dodržali bežné limity hlavičiek reverzných proxy serverov. Nastavením vyššej hodnoty sa prenesie viac metadát nadradeného servera za cenu väčšej veľkosti hlavičiek odpovede. | +| `CORS_ORIGIN` | _(nenastavené)_ | `src/server/cors/origins.ts` | Starší zoznam povolených zdrojov CORS podporujúci jeden zdroj. Pri nových nasadeniach uprednostnite `CORS_ALLOWED_ORIGINS`. CORS je určený iba pre klientov API v prehliadači používajúcich požiadavky medzi zdrojmi; autentifikované zápisy ovládacieho panela namiesto toho používajú požiadavky z rovnakého zdroja spolu s ochranou CSRF viazanou na reláciu. | +| `CORS_ALLOWED_ORIGINS` | _(nenastavené)_ | `src/server/cors/origins.ts` | Čiarkami oddelený zoznam povolených zdrojov CORS. Zástupný znak sa neodošle, pokiaľ nie je explicitne nakonfigurované `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Núdzová možnosť určená iba na vývoj, ktorá odošle späť ľubovoľnú hodnotu `Origin` prehliadača. Nepovoľujte ju v zdieľaných ani produkčných nasadeniach. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Starší alias. Jeho prepínač v ovládacom paneli (prepísanie v databáze) sa číta pred hodnotou tejto premennej prostredia; hodnota `false`, `0`, `no` alebo `off` v ktorejkoľvek z nich vypne kontroly hostiteľov rovnako, ako to robí `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, a to aj vtedy, keď je prepínač v paneli tohto príznaku uložený ako vypnutý; pri ostatných hodnotách ponechávajú ochranu na dvoch nižšie uvedených príznakoch. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Vypína kontroly hostiteľov ochrany odchádzajúcich adries URL, vrátane blokovania cloudových metadát (celý rozsah 169.254.0.0/16, metadata.google.internal), na cestách poskytovateľov, ktoré sa riadia režimom ochrany: overovanie adries URL, zisťovanie modelov, základné adresy URL uzlov poskytovateľov (vrátane vzdialených uzlov opätovného zoradenia) a test proxy-fallback, medzi ďalšie odchádzajúce cesty súvisiace s poskytovateľmi, ako je vzdialené načítavanie obrázkov/médií. Povoľuje aj súkromné ciele webhookov, s výnimkou koncových bodov cloudových metadát/link-local, ktoré zostávajú zablokované aj pri zapnutom prízaku. Požiadavky na četovanie odoslané cez `BaseExecutor.execute()` si ponechávajú svoju kontrolu cloudových metadát. Lokálni poskytovatelia fungujú s predvolenými hodnotami: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (predvolene `true`) už povoľuje lokálne adresy URL a adresy v sieti LAN na cestách overovania, zisťovania a uzlov poskytovateľov; test proxy-fallback a súkromné ciele webhookov sa riadia iba týmto príznakom a kým je vypnutý, blokujú lokálnych hostiteľov a hostiteľov v sieti LAN. Prepisuje `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Hodnota uložená prepínačom v ovládacom paneli má prednosť pred touto premennou. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Povoľuje adresy URL poskytovateľov na lokálnych/súkromných adresách (127.0.0.1, localhost, LAN, súkromné rozsahy) pre overovanie poskytovateľov, zisťovanie modelov, četovanie a ďalšie odchádzajúce cesty súvisiace s poskytovateľmi, ktorými režim ochrany riadi. **Predvolená hodnota je `true`** (uprednostňuje lokálne adresy): ochrana vtedy blokuje koncové body cloudových metadát — celý rozsah 169.254.0.0/16 plus známe názvy hostiteľov metadát (metadata.google.internal, metadata.goog, IMDS Alibaba/Azure/Oracle, fd00:ec2::254). Nastavte `false` pre režim iba verejných adries, ktorý blokuje aj súkromných a loopback hostiteľov. Ochrana kontroluje názov hostiteľa alebo IP literál presne tak, ako je zapísaný, ešte pred akýmkoľvek DNS dopytom; v režime iba verejných adries navyše obálka bezpečného načítania prekladá názov hostiteľa a odmieta akúkoľvek súkromnú odpoveď. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` tieto kontroly vypína na cestách uvedených v riadku toho príznaku. Vstavaní lokálni poskytovatelia, ako LM Studio, Ollama, vLLM, Llamafile a Triton, pri overovaní kľúčov a četovaní vynechávajú všetky kontroly hostiteľov. Hodnota uložená prepínačom v ovládacom paneli má prednosť pred touto premennou. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Umožňuje trasám `/v1/audio/*` (prepisy, reč, preklady) používať uzol poskytovateľa kompatibilný s OpenAI a hostovaný mimo localhost. Predvolene je vypnuté — smerovanie zvuku na vzdialeného hostiteľa mení identitu odchádzajúcej komunikácie a musí byť explicitným rozhodnutím prevádzkovateľa. Loopback/súkromné uzly (localhost, 127.0.0.1, 172.16-31.x) sú vždy povolené a toto nastavenie ich neovplyvňuje. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Umožňuje požiadavke `POST /v1/rerank` (a loopback kroku opätovného zoradenia pamäťového nástroja) používať uzol poskytovateľa kompatibilný s OpenAI a hostovaný mimo localhost — zariadenie v sieti LAN alebo rovnocenný uzol Tailscale, na ktorom beží TEI, Infinity, vLLM atď. Predvolene je vypnuté — smerovanie na vzdialeného hostiteľa mení identitu odchádzajúcej komunikácie a musí byť explicitným rozhodnutím prevádzkovateľa. Loopback uzly (localhost, 127.0.0.1, 172.16-31.x) sú vždy povolené a toto nastavenie ich neovplyvňuje. Základná adresa URL vzdialeného uzla musí tiež spĺňať zásady pre odchádzajúce adresy URL poskytovateľa (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Keď je povolené OIDC, zakáže prihlasovanie heslom, takže používatelia sa môžu autentifikovať iba prostredníctvom jednotného prihlásenia OIDC. Akceptuje sa aj jednoduchý alias `OIDC_DISABLE_PASSWORD_LOGIN`; prednosť má príznak funkcie ovládacieho panela s rovnakým kľúčom. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Jednoduchý alias pre `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Kontrolný zoznam zabezpečenia diff --git a/docs/i18n/sl/CONTRIBUTING.md b/docs/i18n/sl/CONTRIBUTING.md index 16b40b81e57a..0055940da7f4 100644 --- a/docs/i18n/sl/CONTRIBUTING.md +++ b/docs/i18n/sl/CONTRIBUTING.md @@ -404,17 +404,17 @@ Napišite teste enot v `tests/unit/`, ki zajemajo najmanj: - [ ] Testi so uspešni (`npm test`) - [ ] Preverjanje sloga kode je uspešno (`npm run lint`) - [ ] Gradnja je uspešna (`npm run build`) -- [ ] Tipi TypeScript so dodani za nove javne funkcije in vmesnike +- [ ] Za nove javne funkcije in vmesnike so dodani tipi TypeScript - [ ] Ni trdo kodiranih skrivnosti ali nadomestnih vrednosti -- [ ] Javne poverilnice izvornega ponudnika so vdelane prek `resolvePublicCred()` (glejte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikoli kot literali -- [ ] Odgovori o napakah potekajo skozi `buildErrorBody()` / `sanitizeErrorMessage()` — v telesih odgovorov ni neobdelanih skladovnih sledi (glejte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Lupinski ukazi (`exec` / `spawn`) posredujejo vrednosti izvajalnega okolja prek `env`, ne prek interpolacije nizov +- [ ] Javne poverilnice nadrejenega projekta so vdelane prek `resolvePublicCred()` (glejte [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), nikoli kot literali +- [ ] Odgovori z napakami se obdelajo prek `buildErrorBody()` / `sanitizeErrorMessage()` — brez neobdelanih sledov sklada v telesih odgovorov (glejte [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Lupinski ukazi (`exec` / `spawn`) posredujejo vrednosti med izvajanjem prek `env`, ne prek interpolacije nizov - [ ] Vsi vnosi so preverjeni s shemami Zod -- [ ] **Fragment** dnevnika sprememb je za spremembe, vidne uporabnikom, dodan v `changelog.d/{features|fixes|maintenance}/-.md` (glejte [`changelog.d/README.md`](./changelog.d/README.md)) — datoteke `CHANGELOG.md` **ne** urejajte neposredno; fragmenti se združijo ob izdaji in med zahtevami za združitev nikoli ne povzročajo sporov +- [ ] Za spremembe, vidne uporabnikom, je dodan **fragment** dnevnika sprememb v `changelog.d/{features|fixes|maintenance}/-.md` (glejte [`changelog.d/README.md`](./changelog.d/README.md)) — datoteke `CHANGELOG.md` **ne** urejajte neposredno; fragmenti se združijo ob izdaji in nikoli ne povzročajo sporov med zahtevami za združitev - [ ] Dokumentacija je posodobljena (če je ustrezno) - [ ] Ni novih opozoril CodeQL / Secret-Scanning oziroma je vsako zavrnjeno s tehnično utemeljitvijo, ki se sklicuje na ustrezen dokument v `docs/security/` -- [ ] Poti, ki ustvarjajo podrejene procese (`/api/mcp/`, `/api/cli-tools/runtime/`), so razvrščene kot `isLocalOnlyPath()` v `src/server/authz/routeGuard.ts` — glejte [Strogo pravilo št. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] V sporočilih potrditev ni zaključnih vrstic `Co-Authored-By` — potrditve morajo biti prikazane izključno pod identiteto Git lastnika repozitorija (Strogo pravilo št. 16) +- [ ] Poti, ki zaženejo podrejene procese (`/api/mcp/`, `/api/cli-tools/runtime/`), so v `src/server/authz/routeGuard.ts` razvrščene kot `isLocalOnlyPath()` — glejte [Strogo pravilo št. 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] V sporočilih potrditev ni zaključnih vrstic `Co-authored-by` za UI/bote (Strogo pravilo št. 16) — človeški sodelavci, katerih delo je ponovno uporabljeno, so navedeni s standardnimi zaključnimi vrsticami `Co-authored-by: Name ` --- diff --git a/docs/i18n/sl/docs/architecture/QUALITY_GATES.md b/docs/i18n/sl/docs/architecture/QUALITY_GATES.md index c427eaaa28c8..6ab26295943f 100644 --- a/docs/i18n/sl/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/sl/docs/architecture/QUALITY_GATES.md @@ -15,263 +15,263 @@ načrt ponovitve istega sistema glejte --- -## Inventar vrat in profili izvajanja +## Inventar pregrad in profili izvajanja -### Sprejem kandidata +### Sprejem kandidatov -Poteka dela CI in Quality Gates vsak objavita stabilno presojo: `Gate / CI` in -`Gate / Quality`. Njuna različicami določena politika sprejema vsako predhodno opravilo -opredeli kot obvezno ali svetovalno. Obvezno opravilo, ki se uporablja, mora uspeti: -manjkajoči, preklicani, preskočeni, čakajoči in neznani rezultati ne morejo potrditi stanja PASS. -Veljavna razvrstitev docs-only ali catalog-only lahko naredi programsko vejo neuporabljivo; +Delovna procesa CI in Quality Gates vsak ustvarita stabilno presojo: `Gate / CI` in +`Gate / Quality`. Njuna različično vodena politika sprejema določa vsako predhodno opravilo +kot obvezno ali svetovalno. Ustrezno obvezno opravilo mora uspeti: manjkajoči, +preklicani, preskočeni, čakajoči in neznani rezultati ne morejo potrditi stanja PASS. Veljavna +razvrstitev zgolj za dokumentacijo ali zgolj za katalog lahko naredi kodno vejo neustrezno; osnutek PR-ja ni sprejet kandidat. Oznaka `hotfix` ne odpravlja zahteve po dokazilih. -Oba poteka dela zajemata PR-je in potiske v glavne/izdajne veje, ročni zagon in -dogodke skupin združevanja. Potiski, ročni zagoni in skupine združevanja izvedejo celoten izbor. -Razcepi in skupine združevanja uporabljajo gostovane izvajalnike za opravila, ki bi sicer izbrala -samostojno gostovane izvajalnike; pred uvedbo je treba preveriti zadostno gostovano zmogljivost. +Oba delovna procesa pokrivata PR-je in potiske v glavne/izdajne veje, ročni zagon ter +dogodke skupin za spajanje. Potiski, ročni zagoni in skupine za spajanje izvedejo celoten izbor. Razvejitve +in skupine za spajanje uporabljajo gostovane izvajalnike za opravila, ki bi sicer izbrala lastno gostovane +izvajalnike; pred uvedbo je treba preveriti zadostno gostovano zmogljivost. -Vsako potrdilo JSON določa SHA prevzete revizije, izvajanje poteka dela in poskus. -CLI zavrne neujemanje med SHA prevzete revizije in dogodka. Preizkusi poteka dela povežejo članstvo v politiki -s seznamom `needs` opravila za presojo, tako da nova ali odstranjena veja ne more neopazno izginiti. -Potrdila zajemajo lasten potek dela, ne pa objave, uvedbe ali notranjega delovanja +Vsako potrdilo JSON določa SHA prevzete različice, izvedbo delovnega procesa in poskus. +CLI zavrne neujemanje SHA med prevzeto različico in dogodkom. Testi delovnega procesa vežejo članstvo v politiki +na seznam `needs` opravila presoje, zato nova ali odstranjena veja ne more neopazno izginiti. +Potrdila pokrivajo lasten delovni proces, ne pa objave, uvedbe ali notranjega delovanja obstoječega svetovalnega pregledovalnika. Aktiviranje obeh imen preverjanj v pravilih vej je ločena skrbniška sprememba; dodajanje teh opravil samo po sebi ne zaščiti veje. ### Inventar statičnega pregleda -Različicami določeni inventar vzdevkov npm in članstvo v statičnem pregledu sta v -`config/quality/gate-manifest.json`. Za preverjanje imen skriptov in natančnih ukazov glede na -`package.json` zaženite `npm run check:gate-manifest`; dodatki, odstranitve in -odstopanja ukazov povzročijo neuspeh tako lokalne kljuke kot opravil za razvrščanje sprememb v CI. -Vzdevek ni opravilo poteka dela, primerek matrike ali preskusni primer: teh števil -se ne sme predstavljati kot medsebojno zamenljivih. +Različično vodeni inventar vzdevkov npm in članstvo v statičnem pregledu sta v +`config/quality/gate-manifest.json`. Za preverjanje imen skriptov in natančnih ukazov +glede na `package.json` zaženite `npm run check:gate-manifest`; dodatki, odstranitve in +odstopanja ukazov povzročijo neuspeh lokalne kljuke in opravil za razvrščanje sprememb v CI. +Vzdevek ni opravilo delovnega procesa, primerek matrike ali testni primer: teh števil +ne smete predstavljati kot medsebojno zamenljivih. Za pregled izbranih vzdevkov brez njihovega izvajanja uporabite `npm run quality:scan -- --list` ali `npm run quality:scan:fast -- --list`. Izvajalnik prikliče vstopno točko npm, zato se njeno izvajalno okolje (vključno z Bun, kjer je konfiguriran) ohrani. -Manifest beleži vzdevke zunaj teh profilov kot ločeno priklicane, vzdrževalni -ukazi pa so v profilih pregledovanja samo za branje prepovedani. +Manifest beleži vzdevke zunaj teh profilov kot ločeno priklicane, ukazi za +vzdrževanje pa so v profilih pregledovanja samo za branje prepovedani. -Ti profili zajemajo samo statični pregled. Ne potrjujejo preskusov izdelka, +Ti profili pokrivajo samo statični pregled. Ne potrjujejo testov izdelka, pokritosti, pakiranja, zunanjih preverjanj ali celotnega sprejema kandidata za izdajo. -Sprejem v poteku dela uporablja povezana `config/quality/admission-policy.json` in -`scripts/quality/admission-verdict.mjs`. Profili za opazovanje izdaj ostajajo ločeni; -njihova veljavna preverjanja in potrdila preglejte neodvisno. Spodnji opisni -inventar je referenca in ne dokaz, da so bila vrata dejansko izvedena. +Sprejem delovnega procesa uporablja povezana `config/quality/admission-policy.json` in +`scripts/quality/admission-verdict.mjs`. Profili opazovalnika izdaj ostajajo ločeni; +njihova ustrezna preverjanja in potrdila preglejte neodvisno. Spodnji besedilni +inventar je referenca in ne dokaz, da se je pregrada dejansko izvedla. -Skripti so v `scripts/check/` (vrata politik) in `scripts/quality/` (mehanizem zaskočke). +Skripti so v `scripts/check/` (pregrade pravilnikov) in `scripts/quality/` (mehanizem postopnega zaostrovanja). Vir resnice za CI je `.github/workflows/ci.yml`. ### Hitra pot za PR izdaje (`quality.yml`) `.github/workflows/quality.yml` dopolnjuje CI pri PR-jih za glavne/izdajne veje, potiskih -v zaščitene veje, ročnih zagonih in skupinah združevanja. PR-ji uporabljajo hitra preverjanja, -filtrirana po poteh. Trajno onemogočena podvojena gradnja je bila odstranjena; dejanska -preverjanja gradnje/pakiranja/zagona ostajajo v CI. - -| Opravilo | Obseg | Blokiranje | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `Docs Gates (fast-path)` | PR-ji dokumentacije/programske kode; reference dokumentacije API in vsa dokumentacija | Da | -| `Fast Quality Gates` | PR-ji programske kode; statična preverjanja, preverjanje tipov, preverjanje tipov nadzorne plošče, prizadeti preizkusi enot | Da | -| `Forgotten sibling tests` | PR-ji programske kode; sledenje spremenjenim modulom do statičnih porabnikov in kandidatnih sorodnih preizkusov; poti prek zbirnih modulov in dinamičnih uvozov so navedene kot svetovalna diagnostika z navedenimi izjemami na seznamu dovoljenih | **Svetovalno** | -| `Vitest (fast-path)` | PR-ji programske kode; hitra zbirka Vitest | Da | -| `Unit Tests fast-path` | PR-ji programske kode; zbirka preizkusov enot v 4 delih | Da | -| `No new ESLint warnings` | PR-ji programske kode; varovalo za preverjanje sloga, ki upošteva opustitve opozoril | Da, vključno z razcepi | -| `Merge integrity (changelog + generated skills)` | PR-ji, ki niso osnutki; usklajevanje dnevnika sprememb in ustvarjenih veščin | Da, vključno z razcepi | - -#### Poročilo o pozabljenih sorodnih preizkusih - -`npm run check:forgotten-sibling-tests` ponovno uporabi razreševalnik uvozov, ki ga uporablja zemljevid vpliva preizkusov. +v zaščitene veje, ročnih zagonih in skupinah za spajanje. PR-ji uporabljajo hitra preverjanja, filtrirana po poteh. Trajno +onemogočena podvojena gradnja je bila odstranjena; prava preverjanja gradnje/pakiranja/zagona ostajajo v CI. + +| Opravilo | Obseg | Blokira | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `Docs Gates (fast-path)` | PR-ji dokumentacije/kode; sklici dokumentacije API in celotna dokumentacija | Da | +| `Fast Quality Gates` | PR-ji kode; statična preverjanja, preverjanje tipov, preverjanje tipov nadzorne plošče, prizadeti testi enot | Da | +| `Forgotten sibling tests` | PR-ji kode; sledenje spremenjenim modulom do statičnih porabnikov in kandidatnih sorodnih testov; poti prek zbirnih modulov in dinamičnega uvoza so sporočene kot svetovalna diagnostika z navedenimi izjemami s seznama dovoljenih | **Svetovalno** | +| `Vitest (fast-path)` | PR-ji kode; hitri paket testov vitest | Da | +| `Unit Tests fast-path` | PR-ji kode; paket testov enot s 4 delci | Da | +| `No new ESLint warnings` | PR-ji kode; zaščita lint, ki upošteva izključitve | Da, vključno z razvejitvami | +| `Merge integrity (changelog + generated skills)` | PR-ji, ki niso osnutki; usklajenost dnevnika sprememb in ustvarjenih veščin | Da, vključno z razvejitvami | + +#### Poročilo o pozabljenih sorodnih testih + +`npm run check:forgotten-sibling-tests` ponovno uporabi razreševalnik uvozov, na katerem temelji zemljevid vpliva testov. Za vsak spremenjeni produkcijski modul poroča o determinističnih verigah -`spremenjeni modul/simbol -> statični porabnik -> kandidatni sorodni preizkus`, kadar kandidatni -preizkus ni prisoten v razliki zahtevka za poteg. Povzetek Markdown in rezultat JSON se ohranita kot -artefakt poteka dela `forgotten-sibling-tests` za umerjanje pred morebitno uvedbo blokiranja. +`spremenjeni modul/simbol -> statični porabnik -> kandidatni sorodni test`, kadar kandidatni +test manjka v spremembah zahtevka za vlečenje. Povzetek Markdown in rezultat JSON se ohranita kot +artefakt delovnega procesa `forgotten-sibling-tests` za umerjanje pred morebitno uvedbo blokiranja. Ponovni izvozi prek zbirnih modulov in dinamični uvozi so namenjeni samo diagnostiki razreševanja; nikoli ne ustvarijo -blokirajoče ugotovitve. Pregledane izjeme so shranjene v -`config/quality/forgotten-sibling-allowlist.json`. Vsak vnos mora navesti potrošnika in kandidatni -test, podati konkretno utemeljitev ter vsebovati povezavo do težave ali zahteve za vključitev sprememb na GitHubu. Nepravilno oblikovani vnosi -se privzeto zavrnejo. Izjeme ne morejo prikriti izbrisanega kandidatnega testa ali spremembe, ki doda `.skip`/`.todo`; -slabljenje trditev in drugo prikrivanje ostajata v pristojnosti neodvisno blokirajočega -preverjanja `check:test-masking`. +blokirajoče ugotovitve. Pregledane izjeme so v datoteki +`config/quality/forgotten-sibling-allowlist.json`. Vsak vnos mora navesti uporabnika in kandidatni +test, podati konkretno utemeljitev ter vsebovati povezavo do GitHub težave ali zahteve za vključitev sprememb. Nepravilno oblikovani vnosi se +privzeto zavrnejo. Izjeme ne morejo prezreti izbrisanega kandidatnega testa ali spremembe, ki doda `.skip`/`.todo`; +oslabitev trditev in druga prikrivanja še naprej obravnava neodvisno blokirajoče +preverjanje `check:test-masking`. ### Opravilo: `lint` Izvede se ob vsaki zahtevi za vključitev sprememb v `main`. Ob neuspehu blokira združitev. -| Skript (`npm run ...`) | Preverja | Blokira | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | Različica Node.js je znotraj podprtega razpona | Da | -| `check:cycles` | Krožne uvoze — vse module v `src/` + `open-sse/` | Da | -| `check:route-validation:t06` | Prisotnost shem Zod na vseh poteh (pravilnik 6. ravni) | Da | -| `check:any-budget:t11` | Število `@ts-expect-error // any` ne presega omejitve (zapornica 11. ravni) | Da | -| `check:provider-consistency` | Vsak ponudnik v `providers.ts` ima ustrezen vnos v `providerRegistry.ts` (in obratno, znotraj seznama dovoljenih) | Da | -| `check:model-lifecycle` | Tri ročno vzdrževane usmerjevalne tabele ostajajo skladne z vključeno sliko življenjskega cikla (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ne ovrednoti nobenega opuščenega identifikatorja, ki ga lahko usmerja `REGISTRY`; vsak cilj `BUILT_IN_ALIASES` je prisoten v `REGISTRY` in odsoten iz slike opuščenih identifikatorjev; vsak opuščeni identifikator, ki je še vedno v `REGISTRY`, je preusmerjen ali naveden v `allowedRetiredInCatalog`; noben vir ali cilj `DEFAULT_DEGRADATION_MAP` pa v tej sliki ni označen kot opuščen. To ne dokazuje, da model trenutno zagotavlja delujoči nadrejeni ponudnik. Brez povezave — primerja z `config/quality/model-lifecycle.json`, ki se ročno osveži z `npm run quality:refresh-model-lifecycle` (zahteva omrežje; ni vključeno v CI). `allowedRetiredInCatalog` je postopoma poostrujoč omejevalni mehanizem: vnos dodajte le skupaj z nalogo za sledenje. | Da | -| `check:fetch-targets` | Vsak `fetch("/api/...")` v odjemalskem delu `src/` se razreši v dejanski `route.ts` | Da | -| `check:deps` | Vse odvisnosti, ki jih je mogoče namestiti z `npm install`, iz vseh datotek `package.json` v repozitoriju so navedene v `dependency-allowlist.json`; novi nepripeti paketi ali paketi z imeni, podobnimi drugim paketom, so označeni | Da | -| `audit:deps` | `npm audit` (korenski projekt + Electron) — brez opozoril visoke/kritične stopnje (prekriva se z OSV `check:vuln-ratchet`; glejte seznam za racionalizacijo) | Da | -| `check:lockfile` | Celovitost datoteke `package-lock.json` — register HTTPS, zgoščene vrednosti celovitosti, brez preglasitev gostitelja | Da | -| `check:licenses` | Seznam dovoljenih licenc SPDX za produkcijske odvisnosti | Da | -| `check:tracked-artifacts` | Brez gradbenih artefaktov / potrjenih simbolnih povezav `node_modules` (izvede se tudi v husky pre-commit; pre-push je namenoma lahek — #6716) | Da | -| `check:ai-attribution` | Brez zaključne vrstice AI/bota `Co-Authored-By` ali noge o ustvarjanju z AI v potrditvah, naslovu ali telesu PR-ja — Strogo pravilo št. 16 (v zanki hitrih preverjanj `quality.yml` za PR→`release/**` — bere vsebino dogodka, zunaj PR-jev pa ne izvede ničesar — in korak samo za PR-je v preverjanju lint datoteke `ci.yml` za PR→`main`; tudi kavelj husky `commit-msg`; človeški soavtorji so dovoljeni; #14436) | -| `check:vitest-exclusions` | Vsaka izključitev Vitest navaja sledilno težavo in je navedena v `config/quality/vitest-exclusions.json` (#13204) | Da | -| `check:file-size` | Nobena datoteka z izvorno kodo ne presega omejitve za posamezno končnico (zaskočni mehanizem: velike datoteke, zamrznjene na seznamu `frozen`) | Da | -| `check:error-helper` | Odgovori o napakah v izvajalnikih/obdelovalnikih uporabljajo `buildErrorBody()` / `sanitizeErrorMessage()` (Strogo pravilo št. 12) | Da | -| `check:migration-numbering` | Datoteke SQL za migracije so zaporedno oštevilčene, brez vrzeli ali podvojenih številk | Da | -| `check:public-creds` | Zunaj datoteke `publicCreds.ts` ni dobesedno zapisanih vrednosti OAuth `client_id`/`client_secret` ali spletnih ključev Firebase (strogo pravilo št. 11) | Da | -| `check:db-rules` | Zunaj modulov `src/lib/db/` ni neobdelanih poizvedb SQL; iz `localDb.ts` ni skupinskih uvozov (strogi pravili št. 2/št. 5) | Da | -| `check:known-symbols` | Izvajalniki ponudnikov, strategije usmerjanja in prevajalniki, registrirani v svojih tabelah razpošiljanja, se ujemajo z datotekami na disku — brez osirotelih ali nedeklariranih simbolov | Da | -| `check:route-guard-membership` | Vsaka pot, ki ustvari podrejeni proces, je razvrščena s funkcijo `isLocalOnlyPath()` (strogi pravili št. 15/št. 17) | Da | -| `check:test-discovery` | Vsako datoteko `*.test.ts` / `*.spec.ts` v repozitoriju zbere vsaj en izvajalnik testov (raglja: seznam osirotelih datotek v `test-discovery-baseline.json` se lahko samo krči) | Da | -| `check:agent-skills-sync` | Ustvarjeni artefakti agent-skills se ujemajo z izvornim katalogom (brez odstopanj) | -| `check:provider-asset-provenance` | Logotipi/sredstva ponudnikov imajo zabeležen vnos o izvoru | -| `lint:json` | Konfiguracijske datoteke JSON se pravilno razčlenijo in izpolnjujejo pravila preverjanja repozitorija | -| `typecheck:core` | Prevajanje TypeScript brez napak (samo svetovalna opozorila) | Da | -| `typecheck:noimplicit:core` | Strogi `noImplicitAny` — usmerjeno v prihodnost; številna obstoječa mesta klicev še vedno potrebujejo anotacije | **Svetovalno** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, omejen na `src/app/(dashboard)/**` (#7033) — skrbno izbran seznam 27 dovoljenih datotek v `typecheck:core` ne vključuje nobene datoteke TSX nadzorne plošče, prav tako pa jih `next build` nikoli ne preverja glede tipov (`next.config.mjs` nastavi `ignoreBuildErrors: true`), zato so bile regresije osirotelih identifikatorjev na tem mestu (#6625/#6909) za CI nevidne. Razlike se primerjajo z zamrznjeno osnovo števila napak za posamezno datoteko/kodo TS (`config/quality/dashboard-typecheck-baseline.json`, enak vzorec uveljavljanja zastarelosti kot pri `check:known-symbols`) — preverjanje spodleti samo zaradi NOVIH napak, ki presegajo osnovno število; ko je obstoječa napaka odpravljena, osnovo zmanjšajte z `--update`. | Da | +| Skript (`npm run ...`) | Preverja | Blokirajoče | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | Različica Node.js je znotraj podprtega razpona | Da | +| `check:cycles` | Krožne uvoze v celotnih `src/` + `open-sse/` (na podlagi AST, z razrešenimi `paths` iz tsconfig). Samostojni ukaz je svetovalen in izpiše cikle. `check:cycles:ratchet` (ki ga izvaja CI) blokira, kadar število preseže zgornjo mejo `metrics.cycles` v `quality-baseline.json` — trenutno 14, `direction: down`, zato se lahko le znižuje (#15159 G-01/G-02) | Da (raglja) | +| `check:route-validation:t06` | Prisotnost shem Zod na vseh poteh (pravilnik 6. ravni) | Da | +| `check:any-budget:t11` | Število `@ts-expect-error // any` ne presega proračuna (raglja 11. ravni) | Da | +| `check:provider-consistency` | Vsak ponudnik v `providers.ts` ima ustrezen vnos v `providerRegistry.ts` (in obratno, znotraj seznama dovoljenih) | Da | +| `check:model-lifecycle` | Tri ročno vzdrževane usmerjevalne tabele ostajajo skladne z vključeno kopijo stanj življenjskega cikla (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ne ocenjuje nobenega opuščenega ID-ja, ki ga lahko usmerja `REGISTRY`; vsak cilj `BUILT_IN_ALIASES` je prisoten v `REGISTRY` in odsoten iz kopije opuščenih ID-jev; vsak opuščeni ID, ki je še vedno v `REGISTRY`, je preusmerjen ali naveden v `allowedRetiredInCatalog`; noben vir ali cilj `DEFAULT_DEGRADATION_MAP` pa v tej kopiji ni označen kot opuščen. To ne dokazuje, da model trenutno zagotavlja dejavni zunanji ponudnik. Brez povezave — primerja z `config/quality/model-lifecycle.json`, ki se ročno osveži z `npm run quality:refresh-model-lifecycle` (zahteva omrežje; ni vključeno v CI). `allowedRetiredInCatalog` je enosmerni seznam za postopno odpravo: vnos dodajte le skupaj z zadevo za sledenje. | Da | +| `check:fetch-targets` | Vsak `fetch("/api/...")` v odjemalskem delu `src/` se razreši v dejanski `route.ts` | Da | +| `check:deps` | Vse odvisnosti, ki jih je mogoče namestiti z `npm install`, v vseh datotekah `package.json` v repozitoriju so navedene v `dependency-allowlist.json`; novi nepripeti paketi ali paketi z imeni, podobnimi obstoječim, so označeni | Da | +| `audit:deps` | `npm audit` (korenski projekt + Electron) — brez opozoril visoke/kritične stopnje (prekriva se s preverjanjem OSV `check:vuln-ratchet`; glejte seznam za poznejšo racionalizacijo) | Da | +| `check:lockfile` | Celovitost datoteke `package-lock.json` — register HTTPS, zgoščene vrednosti celovitosti, brez preglasitev gostitelja | Da | +| `check:licenses` | Seznam dovoljenih licenc SPDX za produkcijske odvisnosti | Da | +| `check:tracked-artifacts` | Brez artefaktov gradnje / potrjenih simbolnih povezav `node_modules` (izvede se tudi v husky pre-commit; pre-push je namenoma minimalen — #6716) | Da | +| `check:ai-attribution` | Brez zaključne vrstice `Co-Authored-By` za UI/bote ali noge o ustvarjanju z UI v potrditvah, naslovu ali telesu zahtevka PR — strogo pravilo št. 16 (v zanki hitrih preverjanj `quality.yml` za PR→`release/**` — prebere koristno vsebino dogodka, zunaj zahtevkov PR pa ne izvede ničesar — in korak samo za PR v preverjanju lint datoteke `ci.yml` za PR→`main`; prav tako kavelj husky `commit-msg`; človeški soavtorji so dovoljeni; #14436) | +| `check:vitest-exclusions` | Vsaka izključitev Vitest navaja sledljivo težavo in je navedena v `config/quality/vitest-exclusions.json` (#13204) | Da | +| `check:file-size` | Nobena izvorna datoteka ne presega omejitve za svojo končnico (zaskočni mehanizem: velike datoteke, zamrznjene na seznamu `frozen`) | Da | +| `check:error-helper` | Odgovori o napakah v izvajalnikih/obdelovalnikih uporabljajo `buildErrorBody()` / `sanitizeErrorMessage()` (strogo pravilo št. 12) | Da | +| `check:migration-numbering` | Datoteke SQL za migracije so oštevilčene zaporedno, brez vrzeli ali podvojenih številk | Da | +| `check:public-creds` | Brez dobesednih poverilnic OAuth `client_id`/`client_secret` ali spletnih ključev Firebase zunaj `publicCreds.ts` (strogo pravilo št. 11) | Da | +| `check:db-rules` | Brez neposrednega SQL-a zunaj modulov `src/lib/db/`; brez zbirnih uvozov iz `localDb.ts` (strogi pravili št. 2/št. 5) | Da | +| `check:known-symbols` | Izvajalniki ponudnikov, strategije usmerjanja in prevajalniki, registrirani v svojih razpošiljevalnih tabelah, se ujemajo z datotekami na disku — brez osirotelih ali nedeklariranih simbolov | Da | +| `check:route-guard-membership` | Vsaka pot, ki ustvari podrejeni proces, je razvrščena s funkcijo `isLocalOnlyPath()` (strogi pravili št. 15/št. 17) | Da | +| `check:test-discovery` | Vsako datoteko `*.test.ts` / `*.spec.ts` v repozitoriju zazna vsaj en izvajalnik testov (zapora: seznam osirotelih datotek v `test-discovery-baseline.json` se lahko le krajša) | Da | +| `check:agent-skills-sync` | Ustvarjeni artefakti agent-skills se ujemajo z izvornim katalogom (brez odstopanj) | +| `check:provider-asset-provenance` | Logotipi/sredstva ponudnikov imajo zabeležen vnos o izvoru | +| `lint:json` | Konfiguracijske datoteke JSON je mogoče razčleniti in izpolnjujejo pravila preverjanja repozitorija | +| `typecheck:core` | Prevajanje TypeScript brez napak (samo svetovalna opozorila) | Da | +| `typecheck:noimplicit:core` | Strogi `noImplicitAny` — usmerjeno v prihodnost; številna obstoječa mesta klicev še vedno potrebujejo anotacije | **Svetovalno** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, omejen na `src/app/(dashboard)/**` (#7033) — skrbno izbran seznam 27 datotek v `typecheck:core` ne vključuje nobene datoteke TSX nadzorne plošče, prav tako pa jih `next build` nikoli ne preverja glede tipov (`next.config.mjs` nastavi `ignoreBuildErrors: true`), zato so bile regresije osirotelih identifikatorjev na tem mestu (#6625/#6909) za CI nevidne. Razlike se primerjajo z zamrznjenim izhodiščem števila napak po datoteki in kodi TS (`config/quality/dashboard-typecheck-baseline.json`, enak vzorec uveljavljanja zastarelosti kot pri `check:known-symbols`) — vrata padejo samo zaradi NOVIH napak, ki presegajo izhodiščno število; ko je obstoječa napaka odpravljena, izhodišče znižajte z `--update`. | Da | ### Opravilo: `quality-gate` Zažene se po `test-coverage`. Ob neuspehu blokira združitev. -| Skript | Preverja | Blokira | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `quality:collect` | Ustvari `quality-metrics.json` (število opozoril ESLint, pokritost iz združenega poročila razdeljenih opravil) | Da (predhodni korak za ratchet) | -| `quality:ratchet` | Nobena metrika v `quality-baseline.json` se ni poslabšala (opozorila ESLint ≤ izhodiščna vrednost; pokritost ≥ izhodiščna vrednost) | Da | -| `check:duplication` | Podvajanje kode (jscpd@4) ne presega izhodiščne vrednosti v `quality-baseline.json` | Da | -| `check:complexity` | Ciklomatska kompleksnost na ravni datoteke ne presega zgornje meje (osnovni pravili ESLint `complexity` + `max-lines-per-function`) | Da | -| `check:cognitive-complexity` | Postopno omejevanje kognitivne kompleksnosti (`eslint-plugin-sonarjs`) — ločen prehod ESLint; CI oba združi v en korak `check:complexity-ratchets` | Da | -| `check:dead-code` | Postopno omejevanje neuporabljenih izvozov/datotek (knip) se glede na izhodiščno vrednost ne poslabša | Da | -| `check:compression-budget` | Proračun primerjalnega preizkusa stiskanja — najnižje dovoljene vrednosti prihranka žetonov za posamezen pogon se ne smejo poslabšati | Da | -| `check:type-coverage` | Postopno omejevanje odstotka tipizirane kode (`type-coverage`) se ne poslabša; večinoma nadomešča `typecheck:noimplicit:core` | Da | -| `check:codeql-ratchet` | Število odprtih opozoril CodeQL se ne poslabša (bere prek `gh api`; brez žetona se preskoči brez napake) — pogostost osveževanja in ročni sprožilec: glejte »Postopno omejevanje CodeQL« spodaj | Da | +| Skript | Preverja | Blokira | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | Ustvari `quality-metrics.json` (število opozoril ESLint, pokritost iz združenega poročila delov) | Da (predpogoj za zaskok) | +| `quality:ratchet` | Nobena metrika v `quality-baseline.json` se ni poslabšala (opozorila ESLint ≤ izhodiščna vrednost; pokritost ≥ izhodiščna vrednost) | Da | +| `check:duplication` | Podvajanje kode (jscpd@4) ne presega izhodiščne vrednosti v `quality-baseline.json` | Da | +| `check:complexity` | Ciklomatična kompleksnost na ravni datoteke ne presega omejitve (osnovni pravili ESLint `complexity` + `max-lines-per-function`) | Da | +| `check:cognitive-complexity` | Zaskok kognitivne kompleksnosti (`eslint-plugin-sonarjs`) — ločen prehod ESLint; CI oba združi v en korak `check:complexity-ratchets` | Da | +| `check:dead-code` | Zaskok neuporabljenih izvozov/datotek (knip) se glede na izhodiščno vrednost ne poslabša | Da | +| `check:compression-budget` | Proračun primerjalnega preizkusa stiskanja — najnižje dovoljene vrednosti prihranka žetonov za posamezen pogon se ne smejo poslabšati | Da | +| `check:type-coverage` | Zaskok odstotka tipizirane kode (`type-coverage`) se ne poslabša; večinoma nadomešča `typecheck:noimplicit:core` | Da | +| `check:codeql-ratchet` | Število odprtih opozoril CodeQL se ne poveča (bere prek `gh api`; brez žetona preverjanje varno preskoči) — pogostost osveževanja in ročni sprožilec: glejte »Zaskok CodeQL« spodaj | Da | ### Opravilo: `quality-extended` -Celotno opravilo je svetovalne narave (`continue-on-error: true`). Postopna omejevanja, ki temeljijo na npm, se dejansko izvedejo; zunanji pregledovalniki se namestijo prek `gh release download` in se sami preskočijo (izhodna koda 0), če binarna datoteka še vedno ni na voljo. +Celotno opravilo je svetovalne narave (`continue-on-error: true`). Zaskoki, ki temeljijo na npm, se +dejansko izvedejo; zunanji pregledovalniki se namestijo prek `gh release download` in se sami preskočijo (izhodna koda 0), +če binarna datoteka še vedno ni na voljo. -| Skript | Preverja | Blokira | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | -| `check:circular-deps` | Ni krožnih odvisnosti (dpdm) | **Svetovalno** | -| `check:bundle-size` | Velikost paketa ne presega zgornje meje | **Svetovalno** | -| `check:secrets` | Iskanje skrivnosti (gitleaks) — preskoči se, če binarna datoteka ni na voljo | **Svetovalno** | -| `check:vuln-ratchet` | Ranljivosti odvisnosti (osv-scanner) se ne poslabšajo — preskoči se, če binarna datoteka ni na voljo | **Svetovalno** | -| `check:workflows` | Preverjanje delovnih tokov (actionlint + zizmor); manjkajoči ali okvarjeni pregledovalniki, neveljavna poročila ali manjkajoča izhodiščna vrednost za postopno omejevanje povzročijo stanje INCOMPLETE. Veljavne ugotovitve upoštevajo izbrani strogi/svetovalni/pravilnik postopnega omejevanja | Izvedba je obvezna; postopno omejevanje zizmor blokira v CI | -| `check:openapi-breaking` | Prelomne spremembe pogodbe javnega API-ja (`openapi.yaml`) glede na osnovno vejo (oasdiff) — ustvari `openapiBreaking=N`; preskoči se, če oasdiff ni na voljo ali osnovne specifikacije ni mogoče razrešiti | **Svetovalno** | +| Skript | Preverja | Blokira | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:circular-deps` | Ni krožnih odvisnosti (dpdm) | **Svetovalno** | +| `check:bundle-size` | Velikost svežnja ne presega omejitve | **Svetovalno** | +| `check:secrets` | Pregled skrivnosti (gitleaks) — preskoči, če binarna datoteka ni na voljo | **Svetovalno** | +| `check:vuln-ratchet` | Ranljivosti odvisnosti (osv-scanner) se ne povečajo — preskoči, če binarna datoteka ni na voljo | **Svetovalno** | +| `check:workflows` | Preverjanje delovnih tokov (actionlint + zizmor); manjkajoči/okvarjeni pregledovalniki, neveljavna poročila ali manjkajoča izhodiščna vrednost zaskoka povzročijo stanje INCOMPLETE. Veljavne ugotovitve sledijo izbranemu strogemu/svetovalnemu pravilniku oziroma pravilniku zaskoka | Izvedba je obvezna; zaskok zizmor blokira v CI | +| `check:openapi-breaking` | Prelomne spremembe pogodbe javnega API-ja (`openapi.yaml`) glede na osnovno vejo (oasdiff) — ustvari `openapiBreaking=N`; preskoči, če oasdiff ni na voljo ali osnovne specifikacije ni mogoče razrešiti | **Svetovalno** | ### Opravilo: `docs-sync-strict` -Izvede se pri vsakem PR-ju v `main`. Ob neuspehu blokira združitev. +Izvede se ob vsakem PR-ju v `main`. Ob neuspehu blokira združitev. | Skript | Preverja | Blokirajoče | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | -| `check:docs-all` | Metaprehod, ki zaporedno izvede spodnjih 6 podprehodov | Da | -| ↳ `check:docs-sync` | Skladnost različic v CHANGELOG / OpenAPI / llm.txt | Da | -| ↳ `check:docs-counts` | Števila v besedilu (število ponudnikov, število migracij itd.) so znotraj drsečega dovoljenega obsega glede na dejanska števila | Da | +| `check:docs-all` | Metaprehod, ki zaporedoma izvede spodnjih 6 podprehodov | Da | +| ↳ `check:docs-sync` | Skladnost različic med CHANGELOG / OpenAPI / llm.txt | Da | +| ↳ `check:docs-counts` | Števila v besedilu (število ponudnikov, število migracij itd.) so znotraj zaostrenega razpona dejanskih vrednosti | Da | | ↳ `check:env-doc-sync` | Vsaka okoljska spremenljivka v `.env.example` je dokumentirana v tabeli dokumentacije in obratno | Da | -| ↳ `check:deprecated-versions` | V dokumentaciji ni nizov z opuščenimi različicami | Da | -| ↳ `check:doc-links` | Notranje markdown povezave v dokumentaciji kažejo na dejanske datoteke (oblika `[besedilo]`/`(path)`) | Da | -| ↳ `check:fabricated-docs` | Poti, okoljske spremenljivke, ukazi CLI, imena kavljev in poti datotek, navedeni v dokumentaciji, obstajajo v kodni zbirki. Strogi prehod z `--strict`; brez zastavice le nezavezujoča napaka. | Da (prek `--strict` v CI) | +| ↳ `check:deprecated-versions` | V dokumentaciji ni nizov z zastarelimi različicami | Da | +| ↳ `check:doc-links` | Notranje povezave markdown v dokumentaciji kažejo na obstoječe datoteke (oblika `[besedilo]`/`(path)`) | Da | +| ↳ `check:fabricated-docs` | Poti, okoljske spremenljivke, ukazi CLI, imena kavljev in poti datotek, navedeni v dokumentaciji, obstajajo v kodni bazi. Strogi prehod prek `--strict`; brez zastavice neuspeh ni blokirajoč. | Da (prek `--strict` v CI) | | `check:cli-i18n` | Nizi ukazov CLI so prisotni v vseh področnih datotekah i18n | Da | -| `check:openapi-coverage` | Specifikacija OpenAPI pokriva vsaj postopoma zviševani spodnji prag dejanskih poti | Da | +| `check:openapi-coverage` | Specifikacija OpenAPI pokriva vsaj zaostreno spodnjo mejo dejanskih poti | Da | | `check:openapi-security-tiers` | Oznake varnostnih ravni v `openapi.yaml` so skladne s klasifikacijami v `routeGuard.ts` | **Svetovalno** | -| `check:openapi-routes` | Vsaka pot v `openapi.yaml` kaže na dejanski `route.ts` (preprečevanje halucinacij) | Da | -| `check:docs-symbols` | Vsak sklic `/api/...` v `docs/**/*.md` kaže na dejanski `route.ts` (preprečevanje halucinacij) | Da | +| `check:openapi-routes` | Vsaka pot v `openapi.yaml` se razreši v dejanski `route.ts` (preprečevanje halucinacij) | Da | +| `check:docs-symbols` | Vsak sklic `/api/...` v `docs/**/*.md` se razreši v dejanski `route.ts` (preprečevanje halucinacij) | Da | | `i18n translation drift` | Neprevedeni ključi v področnih datotekah i18n — samo opozorilo | **Svetovalno** | ### Opravilo: `i18n-ui-coverage` -| Skript | Preverja | Blokirajoče | -| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (vgrajeno) | Pokritost ključev i18n uporabniškega vmesnika je ≥ 65 % | Da | -| `check-ui-value-drift` (vgrajeno) | Spremenjena angleška **vrednost** ne pušča zastarelega prevoda | Da | -| `check-new-key-coverage` (vgrajeno) | **Nov** angleški ključ je preveden v vsakem področnem jeziku — oznaka `__MISSING__:` je zavrnjena | Da | -| `check-translation-ratio` | Delež dejanskih prevodov po področnem jeziku (listi zunaj dovoljenega seznama, ki so enaki angleščini, nadomestni ali manjkajoči) ne sme preseči `config/quality/i18n-translation-baseline.json` + rezerve | **Svetovalno** | +| Skript | Preverja | Blokirajoče | +| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (vgrajeno) | Pokritost ključev i18n uporabniškega vmesnika je ≥ 65 % | Da | +| `check-ui-value-drift` (vgrajeno) | Po spremembi angleške **vrednosti** ne ostane noben zastarel prevod | Da | +| `check-new-key-coverage` (vgrajeno) | **Nov** angleški ključ je preveden v vseh področnih nastavitvah — oznaka `__MISSING__:` je zavrnjena | Da | +| `check-translation-ratio` | Delež dejanskih prevodov po področnih nastavitvah (listi, ki so enaki angleškim / nadomestni / manjkajoči in niso na seznamu dovoljenih) ne sme preseči `config/quality/i18n-translation-baseline.json` + rezerve | **Svetovalno** | -Zahteva `fetch-depth: 0` — prehod za odkrivanje odklona vrednosti primerja spremembe v `en.json` glede na osnovo združitve. +Zahteva `fetch-depth: 0` — prehod za odkrivanje odstopanj vrednosti primerja `en.json` z osnovo združitve. #### `check-ui-value-drift` — prehod za zastarele prevode -Zazna edino regresijo i18n, ki je drugi prehodi strukturno ne morejo opaziti: angleška vrednost -je spremenjena, prevodi, izpeljani iz _prejšnje_ angleške različice, pa ostanejo, zato +Zazna tisto regresijo i18n, ki je drugi prehodi strukturno ne morejo zaznati: angleška vrednost +je spremenjena, prevodi, izpeljani iz _prejšnje_ angleške vrednosti, pa ostanejo, zato neangleško govoreči uporabniki še naprej berejo samozavestno zapisano, vendar zdaj napačno besedilo. -To se je dejansko znašlo v izdaji. `oauthModal.googleOAuthWarning` je bil spremenjen ob uvedbi -pomočnika za prijavo Antigravity (#5203); **39 od 43 področnih jezikov** je ohranilo besedilo, ki je skrbnikom naročalo, naj »kopirajo -celoten URL in ga prilepijo spodaj« — postopek, ki ga pri tem ponudniku ni mogoče dokončati. To je -ostalo neopaženo do #8463, ker: +To se je dejansko znašlo v izdani različici. `oauthModal.googleOAuthWarning` je bil spremenjen ob uvedbi +pomočnika za prijavo Antigravity (#5203); **39 od 43 področnih nastavitev** je ohranilo besedilo, ki je skrbnikom +naročalo, naj »kopirajo celoten URL in ga prilepijo spodaj« — postopka, ki ga pri tem ponudniku ni mogoče +dokončati. To je ostalo neopaženo do #8463, ker: -- `sync-ui-keys` dopolni samo ključe, ki **manjkajo**, nikoli tistih, ki so **zastareli**; +- `sync-ui-keys` zapolni samo ključe, ki so **odsotni**, nikoli pa tistih, ki so **zastareli**; - `check-ui-keys-coverage` šteje _prisotnost_ ključev, zato se zastarel prevod šteje kot pokrit; - `check-translation-drift` spremlja zrcalne kopije dokumentacije `docs/i18n//**.md` — - nikoli ne bere `src/i18n/messages/*.json`. Blokirajoče v opravilu `docs-sync-strict` od - ponovne sinhronizacije 2026-09: uredite osrednji dokument → `npm run i18n:run -- --files=` (na ravni razdelka, hitro). + datotek `src/i18n/messages/*.json` nikoli ne bere. Od ponovne sinhronizacije 2026-09 je blokirajoč v opravilu `docs-sync-strict`: uredite osrednji dokument → `npm run i18n:run -- --files=` (na ravni razdelka, cenovno ugodno). -**Upošteva razlike in ne temelji na izhodišču.** Primerja `en.json` v skupnem predniku združitve z +**Upošteva razlike in se ne opira na referenčno osnovo.** Primerja `en.json` v združitveni osnovi z delovnim drevesom; za vsak ključ, katerega angleška vrednost se je spremenila, je vsak jezik, ki še vedno vsebuje -nespremenjen prevod, zastarel. To namenoma **zamrzne obstoječi dolg** — iz razlike -ni mogoče ugotoviti, iz katere stare angleške vrednosti izvira dolgo obstoječi prevod, zato preverjanje presoja -samo to, česar se dotika trenutna sprememba. Alternativa (izhodišče z zgoščeno vrednostjo za vsak ključ) bi zahtevala -približno 600 KB veliko ustvarjeno datoteko, 3-krat večjo od največjega obstoječega izhodišča, ki bi se spreminjala ob vsakem i18n PR-ju. +nespremenjen prevod, zastarel. To namenoma **zamrzne že obstoječi dolg** — iz razlike +ni mogoče ugotoviti, iz katere stare angleške vrednosti izvira dolgo obstoječi prevod, zato kontrola presoja +samo to, česar se dotika trenutna sprememba. Alternativa (referenčna osnova z zgoščeno vrednostjo za vsak ključ) bi zahtevala +približno 600 KB veliko ustvarjeno datoteko, 3× večjo od največje obstoječe referenčne osnove, ki bi se spreminjala ob vsakem i18n PR-ju. -Preverjanje je mogoče zadovoljiti na dva načina: +Kontrolo je mogoče zadovoljiti na dva načina: 1. posodobite zadevne prevode ali -2. jih nastavite na `__MISSING__:` — izvajalno okolje nato uporabi popravljeno angleško vrednost +2. jih nastavite na `__MISSING__:` — izvajalno okolje nato uporabi popravljeno angleško besedilo (`src/i18n/request.ts::deepMergeFallback`, #7258), ključ pa se uvrsti v čakalno vrsto za prevod. Če se je spremenil **pomen** niza, raje **preimenujte ključ**: novi ključ ne more podedovati zastarelega prevoda. Ta vzorec je bil uporabljen v #8463. ```bash -npm run i18n:check-value-drift # strogo (kar izvaja CI) -npm run i18n:check-value-drift:warn # samo poročanje +npm run i18n:check-value-drift # strogo (kot se izvaja v CI-ju) +npm run i18n:check-value-drift:warn # samo poročilo BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` Konča se s kodo 0 in sporočilom `SKIP reason=base-unresolved`, kadar osnovnega kataloga ni mogoče prebrati (plitko -kloniranje brez osnovnega sklica), podobno kot `check-openapi-breaking`. +kloniranje brez osnovnega sklica), kar je skladno z `check-openapi-breaking`. ### Opravilo: `i18n` -Celotna matrika preverjanja i18n (eno opravilo na jezik). Celotno opravilo je svetovalno. +Celotna matrika preverjanja i18n (eno opravilo na jezik). Celotno opravilo je svetovalne narave. -| Skript | Preverja | Blokiranje | -| ------------------------------- | ------------------------------------ | -------------------------------------------------------------- | -| `validate_translation.py quick` | Popolnost prevoda za posamezni jezik | **Svetovalno** (`continue-on-error: true` za celotno opravilo) | +| Skript | Preverja | Blokiranje | +| ------------------------------- | -------------------------------- | -------------------------------------------------------------- | +| `validate_translation.py quick` | Popolnost prevodov za vsak jezik | **Svetovalno** (`continue-on-error: true` za celotno opravilo) | ### Opravilo: `pr-test-policy` -Izvaja se samo pri zahtevah za vključitev sprememb. +Izvede se samo pri zahtevah za vključitev sprememb. | Skript | Preverja | Blokiranje | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check:pr-test-policy` | PR-ji, ki spreminjajo produkcijsko kodo v `src/`, `open-sse/`, `electron/` ali `bin/`, morajo vključevati ali posodobiti teste (strogo pravilo št. 8) | Da | -| `check:test-masking` | Spremenjene testne datoteke ne zmanjšajo skupnega neto števila preverjanj in ne dodajajo tavtologij `assert.ok(true)` | Da | -| `check:pr-evidence` | Telo PR-ja navaja dokazila o testiranju/VPS za spremembo (avtomatizira strogo pravilo št. 18 z iskanjem po besedilu PR-ja — krhko, glejte seznam odprtih nalog) | Da | +| `check:pr-test-policy` | PR-ji, ki spreminjajo produkcijsko kodo v `src/`, `open-sse/`, `electron/` ali `bin/`, morajo vključiti ali posodobiti teste (strogo pravilo št. 8) | Da | +| `check:test-masking` | Spremenjene testne datoteke ne zmanjšajo neto števila preverjanj in ne dodajajo tavtologij `assert.ok(true)` | Da | +| `check:pr-evidence` | Telo PR-ja navaja dokaze o testiranju/VPS-u za spremembo (avtomatizira strogo pravilo št. 18 z iskanjem po besedilu PR-ja — krhko, glejte seznam odprtih nalog) | Da | ### Opravilo: `test-vitest` Izvede se po `build`. Ob neuspehu blokira združitev. -| Zbirka | Preverja | Blokiranje | -| ---------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `test:vitest` | Strežnik MCP (110 orodij), autoCombo, predpomnilnik — izvajalnik vitest | Da | -| `test:vitest:ui` | Testi komponent uporabniškega vmesnika — izvajalnik vitest | **Blokira** — obstoječe napake so izrecno izključene v `vitest.config.ts`; nove napake povzročijo neuspeh opravila | +| Zbirka | Preverja | Blokiranje | +| ---------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Strežnik MCP (110 orodij), autoCombo, predpomnilnik — izvajalnik vitest | Da | +| `test:vitest:ui` | Testi komponent uporabniškega vmesnika — izvajalnik vitest | **Blokira** — že obstoječe napake so izrecno izključene v `vitest.config.ts`; nove napake povzročijo neuspeh opravila | ### Nočni delovni tokovi (načrtovani, svetovalni) -Izvajajo se po urniku cron (in prek `workflow_dispatch`), nikoli pri PR-jih. Vsi so svetovalni. +Izvajajo se po urniku cron (in prek `workflow_dispatch`), nikoli pri PR-jih. Vsi so svetovalne narave. -| Delovni tok | Preverja | Blokiranje | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | Lastnostni testi fast-check z naključnim semenom in velikim številom izvedb | **Svetovalno** | -| `nightly-resilience` | prag rasti kopice, vbrizgavanje napak za testiranje kaosa, obremenitveni/dolgotrajni testi k6 | **Svetovalno** | -| `nightly-llm-security` | zaščita pred vbrizgavanjem promptfoo (način blokiranja) in sonde garak (preskočene brez skrivnosti ponudnika) | **Svetovalno** | -| `nightly-schemathesis` | Mehko testiranje pogodbe OpenAPI (schemathesis) proti delujočemu OmniRoute z uporabo `docs/openapi.yaml` — razkrije kršitve specifikacije/neobravnavane napake 500 (faza 8 B.4) | **Svetovalno** | -| `nightly-mutation` | Rezultat mutacijskega testiranja Stryker v hitrem pasu testov enot — preživeli mutanti razkrijejo šibka preverjanja | **Svetovalno** | -| `nightly-compat` | Matrika združljivosti izvajalnega okolja Node v podprtih obsegih `engines.node` | **Svetovalno** | +| Delovni tok | Preverja | Blokiranje | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | Lastnostne teste fast-check z naključnim semenom in velikim številom izvajanj | **Svetovalno** | +| `nightly-resilience` | Kontrolo rasti kopice, vbrizgavanje napak za testiranje odpornosti in obremenitvene/dolgotrajne teste k6 | **Svetovalno** | +| `nightly-llm-security` | Zaščito pred vbrizgavanjem promptfoo (način blokiranja) in preizkuse garak (preskočeno brez skrivnosti ponudnika) | **Svetovalno** | +| `nightly-schemathesis` | Mehko testiranje pogodbe OpenAPI (schemathesis) na delujočem OmniRoute z uporabo `docs/openapi.yaml` — razkrije kršitve specifikacije/neobravnavane napake 500 (faza 8 B.4) | **Svetovalno** | +| `nightly-mutation` | Rezultat mutacijskega testiranja Stryker za hitro vejo testov enot — preživeli mutanti razkrijejo šibka preverjanja | **Svetovalno** | +| `nightly-compat` | Matriko združljivosti izvajalnika Node v podprtih razponih `engines.node` | **Svetovalno** | --- @@ -534,43 +534,43 @@ napako povezave in nikoli ne prekine sej. --- -## Zaostanek racionalizacije (pregled ROI — Faza 9 Val 3) +## Seznam za racionalizacijo (pregled ROI — Faza 9, val 3) -Ta inventar je bil 2026-06-17 usklajen z `ci.yml` (prejšnja različica je izpustila +Ta popis je bil 2026-06-17 usklajen z `ci.yml` (prejšnja različica je izpustila `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Pregled ROI usklajenega nabora -je opredelil naslednje kandidate za racionalizacijo. **Združitve so mehanske spremembe CI; -preklopi/opustitve pa so politične odločitve, pridržane upravljavcu.** Nič od spodaj navedenega -še ni uporabljeno. +`check:codeql-ratchet`, `check:pr-evidence`). Pregled ROI usklajenega nabora je +opredelil naslednje kandidate za racionalizacijo. **Združitve so mehanske spremembe CI; +preklopi/opustitve so odločitve o pravilnikih, pridržane operaterju.** Nič od spodaj +navedenega še ni uveljavljeno. -**Prav tako zgoraj ni dokumentirano** (svetovalno, šibek signal): opravilo `docs-lint` -(markdownlint + Vale, celotno opravilo ima `continue-on-error`) in samostojni delovni tokovi pregledovalnikov -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` je v -`quality-baseline.json`, vendar ni povezan z blokirajočim mehanizmom za preprečevanje regresij v `ci.yml` — metrika je -trenutno osirotela. +**Prav tako ni dokumentirano zgoraj** (svetovalno, šibek signal): opravilo `docs-lint` +(markdownlint + Vale, celotno opravilo ima `continue-on-error`) in samostojni delovni tokovi +pregledovalnikov `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` je v +`quality-baseline.json`, vendar ni povezan z blokirno zaskočko v `ci.yml` — metrika je +trenutno nepovezana. ### Združitev / odstranjevanje podvajanj (mehansko, manjše tveganje) -Vsak kandidat je bil 2026-06-17 preverjen glede na dejansko stanje kontrol (zaupaj, vendar preveri); -izkazalo se je, da več »očitnih« združitev skriva tehnični dolg in **niso** neposredno nadomestljive. - -- **`check:docs-sync` se izvede dvakrat** — samostojno v opravilu `lint` in znova znotraj `check:docs-all` (`docs-sync-strict`) ter v predpotrditvenem kavlju husky. ✅ **KONČANO** — samostojni klic v `lint` je odstranjen. -- **Pregledovanje CVE** — ❌ **NI čista združitev.** `audit:deps` povzroči strogo napako ob katerem koli CVE z visoko/kritično resnostjo; `check:vuln-ratchet` (osv) pa odpove samo ob _regresiji_ glede na izhodiščno vrednost (trenutno 1 MODERATE). Semantika se razlikuje — opustitev `audit:deps` bi odstranila absolutno kontrolo za visoko/kritično resnost. Ohranite oboje. -- **Zaznavanje ciklov** — ❌ **NI čista združitev.** `check:circular-deps` (dpdm) poroča o **91 ciklih** (zato je svetovalen); brez njihove predhodne odprave ga ni mogoče povišati v blokirajočo kontrolo, poleg tega ima širši obseg kot uspešna, skrbno izbrana kontrola `check:cycles`. `check:cycles` naj ostane blokirajoč; odprava 91 ciklov dpdm je ločena postavka zaostanka. -- **Kompleksnost** — ✅ **KONČANO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): en prehod ESLint, štetje po ruleId, tako da izhodiščne vrednosti za ciklomatsko kompleksnost + največje število vrstic ter kognitivno kompleksnost ostanejo neodvisne; posamezna ukaza `check:complexity` / `check:cognitive-complexity` ostajata za lokalni `--update`. -- **Preprečevanje halucinacij za `/api`** — ✅ **KONČANO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): en popis datotečnega sistema za `src/app/api`, openapi-routes + docs-symbols še vedno poročata neodvisno; posamezni ukazi ostajajo za lokalna izvajanja. -- **`check:node-runtime` se izvaja v 11 opravilih** — ⚠️ **nizek ROI.** Vsako se izvaja na ločenem izvajalniku in preverjanje traja <1 s; skupni prihranek je ~10 s, v zameno pa bi izgubili poceni zaščito za vsako opravilo. Sprememba ni vredna pretresov. -- **`typecheck:noimplicit:core` v preverjanju lint v CI** — ✅ **odstranjeno iz opravila lint** (bilo je svetovalno z `continue-on-error`); blokirajočo kontrolo površine tipov predstavljata `typecheck:core` + `check:type-coverage`. Lokalni skript je ohranjen. - -### Preklop / odločitev (politika upravljavca) - -- `check:openapi-security-tiers` (svetovalno) — ❌ **NI mogoče preprosto preklopiti.** Konča se s kodo 0, vendar opozori, da več poti `traffic-inspector` pod `LOCAL_ONLY_API_PREFIXES` nima pripisa `x-loopback-only: true`. Pred uveljavitvijo je treba te pripise najprej dodati v `openapi.yaml`. -- `typecheck:noimplicit:core` (svetovalno) — v veliki meri ga nadomešča blokirajoči mehanizem `check:type-coverage` za preprečevanje regresij. Preklopite ga v mehanizem za preprečevanje regresij ali opustite odvečni drugi prehod `tsc`. -- `test:vitest:ui` (zdaj **blokirajoče**) — obstoječe napake so izrecno izključene v `vitest.config.ts` s sledilnimi komentarji `// #8618`; nove napake povzročijo neuspeh opravila. -- `check:secrets` (gitleaks, blokirajoči mehanizem za preprečevanje regresij, zamrznjen pri 3 dokumentiranih lažno pozitivnih zadetkih) — dodajte 3 zadetke na seznam dovoljenih, da dosežete 0, ali ga znižajte na svetovalnega. Prekriva se z izvornim GitHubovim pregledovanjem skrivnosti + `check:public-creds`. -- `check:pr-evidence` (blokirajoče, preiskuje besedilo telesa PR) — visoko tveganje lažno pozitivnih zadetkov; opustitev oslabi uveljavljanje Strogega pravila št. 18, zato gre za pravo politično odločitev. -- `semgrep` (samostojno svetovalno preverjanje) — pri družinah OWASP se prekriva s CodeQL; povežite njegovo izhodiščno vrednost z mehanizmom za preprečevanje regresij ali ga opustite. +Vsak kandidat je bil 2026-06-17 preverjen glede na dejansko stanje prehodov (zaupaj, vendar preveri); +izkazalo se je, da več »očitnih« združitev skriva dolg in **niso** neposredno uporabne zamenjave. + +- **`check:docs-sync` se izvede dvakrat** — samostojno v opravilu `lint` in ponovno znotraj `check:docs-all` (`docs-sync-strict`) ter v huskyjevi zanki pred potrditvijo. ✅ **OPRAVLJENO** — samostojni klic v `lint` je odstranjen. +- **Pregledovanje CVE-jev** — ❌ **NI primeren za neposredno združitev.** `audit:deps` se konča z napako ob katerem koli CVE-ju visoke/kritične stopnje; `check:vuln-ratchet` (osv) odpove samo ob _nazadovanju_ glede na izhodišče (trenutno 1 MODERATE). Različna semantika — opustitev `audit:deps` bi odstranila absolutni prehod za visoko/kritično stopnjo. Obdržite oba. +- **Zaznavanje ciklov** — ✅ **OPRAVLJENO** (#15159 G-01/G-02). Staro besedilo je `check:cycles` imenovalo »zeleni, kurirani« prehod in utemeljevalo, da mora ostati blokiren, ker je `check:circular-deps` (dpdm) poročal o 91 ciklih. Ta zelena oznaka je bila **lažno pozitivna**: `check:cycles` je pregledal 5 podimenikov (450 datotek), prepoznal samo statične `import|export … from` in zavrgel vsak določilnik `@/` ter `@omniroute/open-sse/`, zato ni mogel zaznati ciklov dinamičnih uvozov in vzdevkov, ki so prevladovali v repozitoriju. Popravljeno: prehod zdaj prehodi `src` + `open-sse` (5023 datotek), zbira določilnike iz AST-ja TypeScript (zato `import("…")` šteje, `typeof import("…")` na položaju tipa pa ne) in razrešuje `paths` iz tsconfig. Najde **14** ciklov, ne 0. Ker 14 obstoječih ciklov ni mogoče popraviti v PR-ju prehoda, je `check:cycles` zdaj **zaskočka** (`--ratchet`, zgornja meja `metrics.cycles.value = 14` v `quality-baseline.json`, `direction: down`) — blokira vsako _nazadovanje_, število pa se lahko samo zmanjšuje. CI izvaja `npm run check:cycles:ratchet`. Postopno odpravljanje poteka skupaj z **A-01**. `check:circular-deps` (dpdm) ostaja svetovalen kot širše drugo mnenje. +- **Kompleksnost** — ✅ **OPRAVLJENO** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): en prehod ESLint, štetje po ruleId, tako da izhodišča za ciklomatsko kompleksnost + največje število vrstic ter kognitivno kompleksnost ostanejo neodvisna; posamezna `check:complexity` / `check:cognitive-complexity` ostaneta za lokalni `--update`. +- **Preprečevanje halucinacij `/api`** — ✅ **OPRAVLJENO** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): en popis datotečnega sistema za `src/app/api`, openapi-routes + docs-symbols še vedno poročata neodvisno; posamezni pregledi ostanejo za lokalna izvajanja. +- **`check:node-runtime` se izvaja v 11 opravilih** — ⚠️ **nizek ROI.** Vsako opravilo uporablja ločen izvajalnik in pregled traja <1 s; skupni prihranek je ~10 s, v zameno pa bi izgubili poceni varovalo za vsako opravilo. Ni vredno spreminjanja. +- **`typecheck:noimplicit:core` v preverjanju CI lint** — ✅ **odstranjeno iz opravila lint** (bilo je svetovalno z `continue-on-error`); blokirno površino tipov zagotavljata `typecheck:core` + `check:type-coverage`. Lokalni skript je ohranjen. + +### Preklop / odločitev (pravilnik operaterja) + +- `check:openapi-security-tiers` (svetovalno) — ❌ **NI mogoče neposredno preklopiti.** Konča se s kodo 0, vendar opozori, da več poti `traffic-inspector` pod `LOCAL_ONLY_API_PREFIXES` nima oznake `x-loopback-only: true`. Pred uveljavitvijo je treba te oznake najprej dodati v `openapi.yaml`. +- `typecheck:noimplicit:core` (svetovalno) — večinoma ga nadomešča blokirna zaskočka `check:type-coverage`. Preklopite ga na zaskočko ali opustite odvečni drugi prehod `tsc`. +- `test:vitest:ui` (zdaj **blokirno**) — obstoječe napake so izrecno izključene v `vitest.config.ts` s sledilnimi komentarji `// #8618`; nove napake povzročijo neuspeh opravila. +- `check:secrets` (gitleaks, blokirna zaskočka, zamrznjena pri 3 dokumentiranih lažno pozitivnih zadetkih) — dodajte te 3 na seznam dovoljenih, da dosežete 0, ali ga znižajte na svetovalnega. Prekriva se z GitHubovim vgrajenim pregledovanjem skrivnosti + `check:public-creds`. +- `check:pr-evidence` (blokirno, išče vzorce v proznem besedilu telesa PR-ja) — visoko tveganje lažno pozitivnih zadetkov; opustitev oslabi uveljavljanje strogega pravila št. 18, zato je to dejanska odločitev o pravilniku. +- `semgrep` (samostojno svetovalno) — pri družinah OWASP se prekriva s CodeQL; povežite njegovo izhodišče z zaskočko ali ga opustite. --- diff --git a/docs/i18n/sl/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/sl/docs/compression/COMPRESSION_GUIDE.md index 5ffe741448ce..f55d86b84731 100644 --- a/docs/i18n/sl/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/sl/docs/compression/COMPRESSION_GUIDE.md @@ -484,8 +484,7 @@ navodila vstavi lokalizirano navodilo. Pri telesu z `messages` obhod glede na vsebino (`shouldBypassCavemanOutputMode()` v `open-sse/services/compression/outputMode.ts`) preveri zadnja tri sporočila in preskoči sloge za celoten obrat, ko se ujemajo z njegovimi ključnimi besedami za varnost, -nepovratna dejanja, pojasnjevanje ali občutljivost na vrstni red. Obhod se izvaja glede -na nastavitev stikala **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) na nadzorni plošči. +nepovratna dejanja, pojasnjevanje ali občutljivost na vrstni red. Obhod se izvaja, dokler je stikalo **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) na nadzorni plošči vklopljeno, kar je privzeta nastavitev; ob izklopljenem stikalu velijo izbrani slogi tudi v teh obratih. Ko obhod dovoli obdelavo obrata, `placeSystemInstruction()` (ista datoteka), ki nikoli ne ustvari novega `messages[0]`, postavi blok na prvo najdeno mesto med naslednjimi: diff --git a/docs/i18n/sl/docs/reference/ENVIRONMENT.md b/docs/i18n/sl/docs/reference/ENVIRONMENT.md index def3e192da65..064d9e9c9ec8 100644 --- a/docs/i18n/sl/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sl/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Zahtevane skrivnosti -Te **morajo** biti nastavljene pred prvim zagonom. Brez njih se aplikacija ne bo zagnala ali pa bo delovala z nevarnimi privzetimi nastavitvami. - -| Spremenljivka | Zahtevano | Privzeto | Izvorna datoteka | Opis | -| ---------------------------- | ------------------- | ------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Da** | _(brez)_ | `src/lib/auth` | Podpisuje/preverja vse sejne piškotke nadzorne plošče (JWT). Ustvarite z `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Da** | _(brez)_ | `src/lib/db/apiKeys.ts` | Šifrirni ključ AES za vrednosti ključev API, shranjene v SQLite. Ustvarite z `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Da** | `CHANGEME` | Zagonski skript | Nastavi začetno skrbniško geslo za nadzorno ploščo (ujema se s privzeto vrednostjo v `.env.example` — namenoma je očitno nevarno, da vas prisili k spremembi). **Spremenite ga pred prvo uporabo.** Po prijavi ga spremenite prek Nadzorna plošča → Nastavitve → Varnost. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (produkcija) | _(ni nastavljeno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Skupna skrivnost za notranji most WebSocket za Codex Responses. Preverja pristnost zahtev mostu med posrednikom WS za Electron/brskalnik in OmniRoute. ⚠️ **ZAHTEVANO v produkciji — kadar ni nastavljeno, so vse zahteve mostu WS zavrnjene.** Ustvarite z `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Izrecni identifikator za razveljavitev predpomnilnika servisnega delavca za lupino PWA (#11779); prvi v verigi razreševanja. | -| `SOURCE_VERSION` | Ne | _(ni nastavljeno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi v verigi — graditelji PaaS (npr. v slogu Heroku) ga nastavijo na nameščeno potrditev. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(izpeljano)_ | `src/shared/components/PwaRegister.tsx` | Javna vrednost ob gradnji, ki jo odjemalec uporablja za registracijo `/sw.js?v=…`; izpeljana je iz zgornjih dveh vrednosti in nato iz git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (samodejno) | _(samodejno ob vsakem zagonu)_ | `src/server/authz/policies/management.ts` | Skrivnost posameznega procesa, ki dokazuje, da je žig zaupanja vrednega IP-ja vrstnika prišel iz lastnega strežnika HTTP OmniRoute (`scripts/dev/peer-stamp.mjs`). Vmesna programska oprema authz zaupa lokalnosti zahteve (omejevanje poti LOCAL_ONLY na povratno zanko/LAN) samo, kadar žig vsebuje ta žeton. Samodejno se ustvari ob vsakem zagonu — pustite nenastavljeno; določite ga samo za večprocesne postavitve, ki si morajo deliti žig. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(ni nastavljeno)_ | `scripts/dev/peer-stamp.mjs` | Z vejicami ločeni naslovi IP ali obsegi CIDR povratnih posredniških strežnikov, katerih vrednostim `X-Forwarded-For` / `X-Real-IP` lahko seznam dovoljenih/prepovedanih naslovov IP zaupa poleg povratne zanke, naslovov zasebnega omrežja in robnih strežnikov Cloudflare, ki so vedno zaupanja vredni. Potrebno samo za posredniški strežnik na katerem koli drugem javnem naslovu; sicer filter presoja lastni naslov tega posredniškega strežnika. | +Nenastavljene skrivnosti, označene z **Da**, se ob prvem zagonu samodejno ustvarijo (`JWT_SECRET`, `API_KEY_SECRET`) in shranijo v `server.env` v podatkovnem imeniku — nastavite jih izrecno, kadar je ta imenik začasen. Izjema je `OMNIROUTE_WS_BRIDGE_SECRET`: ta se ne shranjuje v `server.env` — vhodne točke strežnika ob vsakem zagonu ustvarijo začasno vrednost, ki jo lahko uporablja samo most znotraj procesa, zato ga morajo zunanji odjemalci mostu nastaviti izrecno. + +| Spremenljivka | Zahtevano | Privzeto | Izvorna datoteka | Opis | +| ---------------------------- | ------------------- | ------------------------------ | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Da** | _(brez)_ | `src/lib/auth` | Podpisuje/preverja vse sejne piškotke nadzorne plošče (JWT). Ustvarite z `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Da** | _(brez)_ | `src/lib/db/apiKeys.ts` | Šifrirni ključ AES za vrednosti ključev API, shranjene v SQLite. Ustvarite z `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ne | _(ni nastavljeno)_ | `src/lib/auth/managementPassword.ts` | Ob zagonu nastavi geslo nadzorne plošče, dokler shranjenega gesla še ni; ko je geslo shranjeno, se ne upošteva. Namestitve prek npm in iz izvorne kode kopirajo `.env.example` v `.env`, zato se zaženejo z njegovo javno znano nadomestno vrednostjo `CHANGEME`: zamenjajte jo pred prvim zagonom ali v tem `.env` nastavite `INITIAL_PASSWORD=` (prazno), da geslo ustvarite v čarovniku za začetno nastavitev ob prvem obisku — tako kot stori nenastavljena vrednost (privzeta v sliki Docker). Shranjeno geslo spremenite v Nadzorna plošča → Nastavitve → Varnost ali zaženite `omniroute reset-password` (`node bin/reset-password.mjs` v delovni kopiji izvorne kode) s `DATA_DIR`, nastavljenim na podatkovni imenik strežnika. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Da** (produkcija) | _(ni nastavljeno)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Skupna skrivnost za notranji most WebSocket za Codex Responses. Preverja pristnost zahtev mostu med posrednikom WS za Electron/brskalnik in OmniRoute. ⚠️ **ZAHTEVANO v produkciji — kadar ni nastavljeno, so vse zahteve mostu WS zavrnjene.** Ustvarite z `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ne | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Izrecni identifikator za razveljavitev predpomnilnika servisnega delavca za lupino PWA (#11779); prvi v verigi razreševanja. | +| `SOURCE_VERSION` | Ne | _(ni nastavljeno)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Drugi v verigi — graditelji PaaS (npr. v slogu Heroku) ga nastavijo na nameščeno potrditev. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ne | _(izpeljano)_ | `src/shared/components/PwaRegister.tsx` | Javna vrednost ob gradnji, ki jo odjemalec uporablja za registracijo `/sw.js?v=…`; izpeljana je iz zgornjih dveh vrednosti in nato iz git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ne (samodejno) | _(samodejno ob vsakem zagonu)_ | `src/server/authz/policies/management.ts` | Skrivnost posameznega procesa, ki dokazuje, da je žig zaupanja vrednega IP-ja vrstnika prišel iz lastnega strežnika HTTP OmniRoute (`scripts/dev/peer-stamp.mjs`). Vmesna programska oprema authz zaupa lokalnosti zahteve (omejevanje poti LOCAL_ONLY na povratno zanko/LAN) samo, kadar žig vsebuje ta žeton. Samodejno se ustvari ob vsakem zagonu — pustite nenastavljeno; določite ga samo za večprocesne postavitve, ki si morajo deliti žig. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ne | _(ni nastavljeno)_ | `scripts/dev/peer-stamp.mjs` | Z vejicami ločeni naslovi IP ali obsegi CIDR povratnih posredniških strežnikov, katerih vrednostim `X-Forwarded-For` / `X-Real-IP` lahko seznam dovoljenih/prepovedanih naslovov IP zaupa poleg povratne zanke, naslovov zasebnega omrežja in robnih strežnikov Cloudflare, ki so vedno zaupanja vredni. Potrebno samo za posredniški strežnik na katerem koli drugem javnem naslovu; sicer filter presoja lastni naslov tega posredniškega strežnika. | ### Ukazi za ustvarjanje @@ -216,38 +216,38 @@ OmniRoute za vso trajno hrambo uporablja **SQLite** (prek `better-sqlite3`). Te ## 4. Varnost in preverjanje pristnosti -| Spremenljivka | Privzeta vrednost | Izvorna datoteka | Opis | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sol, združena z identifikatorji strojne opreme za ustvarjanje prstnega odtisa naprave. Za izolacijo jo spremenite za vsako uvedbo. | -| `OMNIROUTE_CLI_SALT` | _(nenastavljeno = naključna sol za posamezno namestitev, shranjena v `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sol HMAC za izpeljavo lokalnega žetona za preverjanje pristnosti CLI. Nastavitev te vrednosti zamenja vse žetone CLI v napravi in ima vedno prednost pred shranjeno soljo. Glejte `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Nastavi zastavico `Secure` za sejne piškotke. Pri izvajanju za HTTPS **mora biti nastavljeno na `true`**. | -| `REQUIRE_API_KEY` | `false` | Vmesna programska oprema API | Ko je nastavljeno na `true`, morajo vse posredniške zahteve `/v1/*` vsebovati veljaven ključ API. **Ta zastavica ne omejuje `GET /v1/models`**, ki namesto tega upošteva nastavitev prijave v nadzorno ploščo (`requireAuthForModels`) — zato odgovor `401` iz `/v1/models` NE pomeni, da je izvajanje sklepanja zaščiteno. Glejte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Omogoča razkritje celotnih vrednosti ključev API v uporabniškem vmesniku nadzorne plošče. Nastavljivo prek zastavic funkcij na nadzorni plošči; predstavlja varnostno tveganje v primerkih v skupni rabi. | -| `NO_LOG_API_KEY_IDS` | _(prazno)_ | `src/lib/compliance/index.ts` | Z vejicami ločeni ID-ji ključev API, za katere se beleženje zahtev preskoči (skladnost z GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenastavljeno = neomejeno)_ | `src/shared/utils/apiKeyPolicy.ts` | Nadomestna dnevna omejitev števila zahtev, uporabljena za ključe API, katerih stolpec `rate_limits` je ničeln. Nenastavljeno ali prazno: brez implicitne omejitve (#2289, #11017). `0` pomeni enako (neomejeno). Pozitivno celo število N omogoči N/dan, 5N/teden in 20N/mesec. Napačno oblikovane neprazne vrednosti uporabijo stare omejitve 1000/dan, 5000/teden in 20000/mesec. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Največja dovoljena velikost telesa zahteve. Zavrne koristne tovore, ki presegajo to omejitev. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Dejanska telesa zahtev, ki dosežejo ali presežejo ta prag, pred razčlenjevanjem JSON pridobijo atomski procesno lokalni sprejemni zakup za obsežne zahteve (pot BYTE, vključno z `POST /v1/responses`). Enak varnostni mehanizem razpoložljive rezerve [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kot pri strukturno obsežnih zahtevah; še vedno omejeno z `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), da se težava [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne ponovi. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Trda omejitev poti za klepet, uveljavljena glede na bajte, prebrane med omejenim sprejemanjem, vključno z zahtevami z manjkajočo, neveljavno ali zavajajočo glavo `Content-Length`; prekoračitev prejme `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ni nastavljeno — brez omejitve števila zahtev)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ta podedovana omejitev ŠTEVILA zahtev zdaj velja samo, če je izrecno nastavljena. Če ni nastavljena (privzeto), sprejem zahtevnih zahtev za klepet namesto tega omejuje `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — samodejno izračunan BAJTNI proračun, določen glede na dejansko pomnilniško omejitev procesa v **enem procesu** (ena kopica V8). Dve prekrivajoči se zahtevi `/v1/responses` s približno 750.000 žetoni sesujeta kopice velikosti približno 12 GiB (#7849) — to je opozorilo glede **pomnilniškega proračuna**, ne trda omejitev izdelka na 2. Zdrav proces (kopica pod razmerjem za zavračanje) LAHKO prek `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` sprejme več sočasnih dolgih zahtev `/v1/responses`. Več deset dolgih odjemalcev SSE (40–50) je vprašanje kopice + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ne »največ 2«. Slepo zviševanje te vrednosti, da bi »izkoristili gostitelja«, znova povzroči #7849. Pomnožite **kopice** z **N neodvisnimi imeniki `DATA_DIR`** (#11024); nikoli ne uporabljajte `replicas>1` za eno datoteko SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(samodejno izračunano)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** preglasitev samodejno izračunanega bajtnega proračuna sprejemanja (25 % strožje pomnilniške omejitve V8/cgroup, deljeno z 8-kratno prehodno ojačitvijo). Izračunane in izrecne vrednosti so omejene na razpon 8 MiB–2 GiB. Telo, večje od dejanskega proračuna, se takoj zavrne z `413 body_exceeds_budget`; tekmovanje med telesi, ki jih je posamično mogoče obdelati, ostane ponovno poskusljivo z `503`. 40–50 sočasnih dolgih odjemalcev SSE je vprašanje tega proračuna + kopice, ne trda omejitev »največ 2«. Pred prilagajanjem preberite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Razmerje obremenitve kopice za zavračanje (`heapUsed / heap_size_limit`) pri sprejemanju zahtevnih zahtev vrste BYTE in STRUCTURE (#10183, #10268, #10437). Sočasna zahtevna zahteva, ki presega `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`, se s ponovno poskusljivim `503` zavrne samo, ko je kopica HKRATI na tem razmerju ali nad njim; pri zdravi kopici se sprejme prek dodatne zmogljivosti za zdravo stanje. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (privzeto `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Omejena dodatna zmogljivost za hitro pot pri zdravi kopici (#10437) za **obe** vrsti, STRUCTURE in BYTE (`admitChatRequest`, vključno s telesi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Brez te omejitve je vsaka zahteva ob zasedenosti, vendar zdravi kopici, zaobšla nadzor sprejema brez zgornje meje. Ko je prek poti zdrave kopice dejavnih toliko sočasnih zakupov, nadaljnje zahteve ob zasedenosti nadaljujejo po ISTI poti omejenega čakanja/zavrnitve, kot se uporablja pri dejanski obremenitvi kopice. `0` v celoti onemogoči zaobid. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Število sporočil, pri katerem se zahteva za klepet razvrsti kot zahtevna, tudi če je njeno telo manjše od bajtnega praga. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Število orodij, pri katerem se zahteva za klepet razvrsti kot zahtevna, tudi če je njeno telo manjše od bajtnega praga. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativna ocena števila žetonov na podlagi velikosti nizov, pri kateri se zahteva razvrsti kot zahtevna; to je približek stroška sprejema, ne ponudnikova tokenizacija za obračunavanje. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (onemogočeno) | `src/shared/middleware/chatBodyAdmission.ts` | Izbirna omejitev zgodovine klepeta, ki jo je treba izrecno omogočiti. Privzeto je onemogočena: število sporočil je pravilnik uvedbe, ne univerzalna lastnost zahteve, omejevanje na tem mestu pa zavrne pogovore z dokončnim `413`, preden jih lahko cevovod stiskanja pripravi za obdelavo. Rast kopice omejujeta `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` in zavračanje ob obremenitvi kopice. Pri uvedbah z omejenim pomnilnikom, ki potrebujejo trdo zgornjo mejo, nastavite pozitivno vrednost; prekoračitev nato prejme strukturiran odgovor `413`, ki zahteva strnitev. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Trda omejitev za pretočno neposredovan odgovor nadrejene storitve, ki se v celoti shrani v pomnilnik. Ob prekoračitvi se bralnik nadrejene storitve prekliče in zahteva hitro spodleti, namesto da bi niz neomejeno rasel, dokler ne izčrpa kopice. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Največje število bajtov na žici, posredovanih iz glav odgovora nadrejenega strežnika. Ko je omejitev presežena, se glave z nižjo prednostjo (npr. prilagojene `x-codex-*`, `x-oai-request-id`) opustijo, da se ostane znotraj običajnih omejitev glav povratnih posredniških strežnikov. Nastavite višjo vrednost, če želite posredovati več metapodatkov nadrejenega strežnika na račun večje velikosti glav odgovora. | -| `CORS_ORIGIN` | _(nenastavljeno)_ | `src/server/cors/origins.ts` | Podedovani seznam dovoljenih posameznih izvorov CORS. Za nove uvedbe raje uporabite `CORS_ALLOWED_ORIGINS`. CORS je namenjen samo brskalniškim odjemalcem API-ja iz drugih izvorov; overjeni zapisi nadzorne plošče namesto tega uporabljajo zahteve iz istega izvora skupaj z zaščito CSRF, vezano na sejo. | -| `CORS_ALLOWED_ORIGINS` | _(nenastavljeno)_ | `src/server/cors/origins.ts` | Z vejicami ločen seznam dovoljenih izvorov CORS. Nadomestni znak se ne pošlje, razen če je izrecno nastavljeno `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Zasilna možnost, namenjena samo razvoju, ki vrne kateri koli brskalniški `Origin`. Ne omogočite je v skupnih ali produkcijskih uvedbah. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokira klice ponudnikov, usmerjene na zasebne/povratne/lokalne IP-obsege povezave. Onemogočite samo v izoliranih testnih okoljih. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Dovoli URL-je ponudnikov, ki kažejo na zasebna/lokalna omrežja (localhost, 192.168.x.x, 10.x.x.x itd.). **ZAHTEVANO za ponudnike z lastnim gostovanjem** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Ko je vrednost `false`, nadzorna plošča zavrne preverjanje veljavnosti lokalnih URL-jev. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Dovoli dodajanje/preverjanje veljavnosti ponudnikov na lokalnih/zasebnih naslovih (127.0.0.1, localhost, LAN, zasebni obsegi) — omejeno na pot preverjanja veljavnosti ponudnika. **Privzeto `true`** (prednost lokalnemu); nastavite `false`, da uveljavite strogo blokiranje, ki dovoljuje samo javne naslove. Končne točke metapodatkov v oblaku (169.254.169.254, metadata.google.internal) ostanejo blokirane ne glede na nastavitev. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Dovoli potem `/v1/audio/*` (prepisi, govor, prevodi), da uporabljajo vozlišče ponudnika, združljivo z OpenAI, ki gostuje zunaj localhost. Privzeto izklopljeno — usmerjanje zvoka na oddaljenega gostitelja spremeni identiteto izhodnega prometa in mora biti izrecna odločitev upravljavca. Povratna/zasebna vozlišča (localhost, 127.0.0.1, 172.16-31.x) so vedno dovoljena in nanje ta nastavitev ne vpliva. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Dovoli `POST /v1/rerank` (in koraku povratnega ponovnega razvrščanja pomnilniškega mehanizma), da uporablja vozlišče ponudnika, združljivo z OpenAI, ki gostuje zunaj localhost — napravo v omrežju LAN ali vrstnika Tailscale, v katerem se izvaja TEI, Infinity, vLLM itd. Privzeto izklopljeno — usmerjanje na oddaljenega gostitelja spremeni identiteto izhodnega prometa in mora biti izrecna odločitev upravljavca. Povratna vozlišča (localhost, 127.0.0.1, 172.16-31.x) so vedno dovoljena in nanje ta nastavitev ne vpliva. Oddaljena vozlišča morajo prestati tudi pravilnik za izhodne URL-je ponudnika (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); promet ni nikoli usmerjen na gostitelje metapodatkov v oblaku. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ko je OIDC omogočen, onemogoči prijavo z geslom, tako da se lahko uporabniki overijo samo prek enotne prijave OIDC. Sprejet je tudi goli vzdevek `OIDC_DISABLE_PASSWORD_LOGIN`; prednost ima zastavica funkcije nadzorne plošče z istim ključem. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Goli vzdevek za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Spremenljivka | Privzeta vrednost | Izvorna datoteka | Opis | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Sol, združena z identifikatorji strojne opreme za ustvarjanje prstnega odtisa naprave. Za izolacijo jo spremenite za vsako uvedbo. | +| `OMNIROUTE_CLI_SALT` | _(nenastavljeno = naključna sol za posamezno namestitev, shranjena v `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Sol HMAC za izpeljavo lokalnega žetona za preverjanje pristnosti CLI. Nastavitev te vrednosti zamenja vse žetone CLI v napravi in ima vedno prednost pred shranjeno soljo. Glejte `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Nastavi zastavico `Secure` za sejne piškotke. Pri izvajanju za HTTPS **mora biti nastavljeno na `true`**. | +| `REQUIRE_API_KEY` | `false` | Vmesna programska oprema API | Ko je nastavljeno na `true`, morajo vse posredniške zahteve `/v1/*` vsebovati veljaven ključ API. **Ta zastavica ne omejuje `GET /v1/models`**, ki namesto tega upošteva nastavitev prijave v nadzorno ploščo (`requireAuthForModels`) — zato odgovor `401` iz `/v1/models` NE pomeni, da je izvajanje sklepanja zaščiteno. Glejte `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Omogoča razkritje celotnih vrednosti ključev API v uporabniškem vmesniku nadzorne plošče. Nastavljivo prek zastavic funkcij na nadzorni plošči; predstavlja varnostno tveganje v primerkih v skupni rabi. | +| `NO_LOG_API_KEY_IDS` | _(prazno)_ | `src/lib/compliance/index.ts` | Z vejicami ločeni ID-ji ključev API, za katere se beleženje zahtev preskoči (skladnost z GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(nenastavljeno = neomejeno)_ | `src/shared/utils/apiKeyPolicy.ts` | Nadomestna dnevna omejitev števila zahtev, uporabljena za ključe API, katerih stolpec `rate_limits` je ničeln. Nenastavljeno ali prazno: brez implicitne omejitve (#2289, #11017). `0` pomeni enako (neomejeno). Pozitivno celo število N omogoči N/dan, 5N/teden in 20N/mesec. Napačno oblikovane neprazne vrednosti uporabijo stare omejitve 1000/dan, 5000/teden in 20000/mesec. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Največja dovoljena velikost telesa zahteve. Zavrne koristne tovore, ki presegajo to omejitev. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Dejanska telesa zahtev, ki dosežejo ali presežejo ta prag, pred razčlenjevanjem JSON pridobijo atomski procesno lokalni sprejemni zakup za obsežne zahteve (pot BYTE, vključno z `POST /v1/responses`). Enak varnostni mehanizem razpoložljive rezerve [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kot pri strukturno obsežnih zahtevah; še vedno omejeno z `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), da se težava [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) ne ponovi. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Trda omejitev poti za klepet, uveljavljena glede na bajte, prebrane med omejenim sprejemanjem, vključno z zahtevami z manjkajočo, neveljavno ali zavajajočo glavo `Content-Length`; prekoračitev prejme `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ni nastavljeno — brez omejitve števila zahtev)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ta podedovana omejitev ŠTEVILA zahtev zdaj velja samo, če je izrecno nastavljena. Če ni nastavljena (privzeto), sprejem zahtevnih zahtev za klepet namesto tega omejuje `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — samodejno izračunan BAJTNI proračun, določen glede na dejansko pomnilniško omejitev procesa v **enem procesu** (ena kopica V8). Dve prekrivajoči se zahtevi `/v1/responses` s približno 750.000 žetoni sesujeta kopice velikosti približno 12 GiB (#7849) — to je opozorilo glede **pomnilniškega proračuna**, ne trda omejitev izdelka na 2. Zdrav proces (kopica pod razmerjem za zavračanje) LAHKO prek `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` sprejme več sočasnih dolgih zahtev `/v1/responses`. Več deset dolgih odjemalcev SSE (40–50) je vprašanje kopice + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, ne »največ 2«. Slepo zviševanje te vrednosti, da bi »izkoristili gostitelja«, znova povzroči #7849. Pomnožite **kopice** z **N neodvisnimi imeniki `DATA_DIR`** (#11024); nikoli ne uporabljajte `replicas>1` za eno datoteko SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(samodejno izračunano)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** preglasitev samodejno izračunanega bajtnega proračuna sprejemanja (25 % strožje pomnilniške omejitve V8/cgroup, deljeno z 8-kratno prehodno ojačitvijo). Izračunane in izrecne vrednosti so omejene na razpon 8 MiB–2 GiB. Telo, večje od dejanskega proračuna, se takoj zavrne z `413 body_exceeds_budget`; tekmovanje med telesi, ki jih je posamično mogoče obdelati, ostane ponovno poskusljivo z `503`. 40–50 sočasnih dolgih odjemalcev SSE je vprašanje tega proračuna + kopice, ne trda omejitev »največ 2«. Pred prilagajanjem preberite `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` na `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Razmerje obremenitve kopice za zavračanje (`heapUsed / heap_size_limit`) pri sprejemanju zahtevnih zahtev vrste BYTE in STRUCTURE (#10183, #10268, #10437). Sočasna zahtevna zahteva, ki presega `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`, se s ponovno poskusljivim `503` zavrne samo, ko je kopica HKRATI na tem razmerju ali nad njim; pri zdravi kopici se sprejme prek dodatne zmogljivosti za zdravo stanje. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (privzeto `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Omejena dodatna zmogljivost za hitro pot pri zdravi kopici (#10437) za **obe** vrsti, STRUCTURE in BYTE (`admitChatRequest`, vključno s telesi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Brez te omejitve je vsaka zahteva ob zasedenosti, vendar zdravi kopici, zaobšla nadzor sprejema brez zgornje meje. Ko je prek poti zdrave kopice dejavnih toliko sočasnih zakupov, nadaljnje zahteve ob zasedenosti nadaljujejo po ISTI poti omejenega čakanja/zavrnitve, kot se uporablja pri dejanski obremenitvi kopice. `0` v celoti onemogoči zaobid. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Število sporočil, pri katerem se zahteva za klepet razvrsti kot zahtevna, tudi če je njeno telo manjše od bajtnega praga. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Število orodij, pri katerem se zahteva za klepet razvrsti kot zahtevna, tudi če je njeno telo manjše od bajtnega praga. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konzervativna ocena števila žetonov na podlagi velikosti nizov, pri kateri se zahteva razvrsti kot zahtevna; to je približek stroška sprejema, ne ponudnikova tokenizacija za obračunavanje. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (onemogočeno) | `src/shared/middleware/chatBodyAdmission.ts` | Izbirna omejitev zgodovine klepeta, ki jo je treba izrecno omogočiti. Privzeto je onemogočena: število sporočil je pravilnik uvedbe, ne univerzalna lastnost zahteve, omejevanje na tem mestu pa zavrne pogovore z dokončnim `413`, preden jih lahko cevovod stiskanja pripravi za obdelavo. Rast kopice omejujeta `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` in zavračanje ob obremenitvi kopice. Pri uvedbah z omejenim pomnilnikom, ki potrebujejo trdo zgornjo mejo, nastavite pozitivno vrednost; prekoračitev nato prejme strukturiran odgovor `413`, ki zahteva strnitev. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Trda omejitev za pretočno neposredovan odgovor nadrejene storitve, ki se v celoti shrani v pomnilnik. Ob prekoračitvi se bralnik nadrejene storitve prekliče in zahteva hitro spodleti, namesto da bi niz neomejeno rasel, dokler ne izčrpa kopice. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Največje število bajtov na žici, posredovanih iz glav odgovora nadrejenega strežnika. Ko je omejitev presežena, se glave z nižjo prednostjo (npr. prilagojene `x-codex-*`, `x-oai-request-id`) opustijo, da se ostane znotraj običajnih omejitev glav povratnih posredniških strežnikov. Nastavite višjo vrednost, če želite posredovati več metapodatkov nadrejenega strežnika na račun večje velikosti glav odgovora. | +| `CORS_ORIGIN` | _(nenastavljeno)_ | `src/server/cors/origins.ts` | Podedovani seznam dovoljenih posameznih izvorov CORS. Za nove uvedbe raje uporabite `CORS_ALLOWED_ORIGINS`. CORS je namenjen samo brskalniškim odjemalcem API-ja iz drugih izvorov; overjeni zapisi nadzorne plošče namesto tega uporabljajo zahteve iz istega izvora skupaj z zaščito CSRF, vezano na sejo. | +| `CORS_ALLOWED_ORIGINS` | _(nenastavljeno)_ | `src/server/cors/origins.ts` | Z vejicami ločen seznam dovoljenih izvorov CORS. Nadomestni znak se ne pošlje, razen če je izrecno nastavljeno `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Zasilna možnost, namenjena samo razvoju, ki vrne kateri koli brskalniški `Origin`. Ne omogočite je v skupnih ali produkcijskih uvedbah. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Starejši vzdevek. Njegov preklop na nadzorni plošči (prepis v podatkovni bazi) se prebere pred vrednostjo te okoljske spremenljivke; `false`, `0`, `no` ali `off` pri katerem koli od njiju izklopi preverjanja gostiteljev tako, kot to stori `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, tudi kadar je preklop na plošči te zastavice shranjen kot izklopljen; druge vrednosti pustijo varovanje dvema spodnjima zastavicama. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Izklopi preverjanja gostiteljev varovanja izhodnih URL-jev, vključno z blokado metapodatkov v oblaku (celoten obseg 169.254.0.0/16, metadata.google.internal), na poteh ponudnikov, ki sledijo režimu varovanja: preverjanje veljavnosti URL-jev, odkrivanje modelov, osnovni URL-ji vozlišč ponudnikov (vključno z oddaljenimi vozlišči ponovnega razvrščanja) in preizkus proxy-fallback, med drugimi izhodnimi potmi, povezanimi s ponudniki, kot je oddaljeni prevzem slik/medijev. Dovoljuje tudi zasebne cilje webhookov, razen končnih točk metapodatkov v oblaku/link-local, ki ostanejo blokirane tudi ob vklopljeni zastavici. Zahteve za klepet, poslane prek `BaseExecutor.execute()`, obdržijo svoje preverjanje metapodatkov v oblaku. Lokalni ponudniki delujejo s privzetimi nastavitvami: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (privzeto `true`) že dovoljuje lokalne URL-je in URL-je LAN na poteh preverjanja, odkrivanja in vozlišč ponudnikov; preizkus proxy-fallback in zasebni cilji webhookov upoštevajo samo to zastavico in blokirajo lokalne gostitelje in gostitelje LAN, dokler je izklopljena. Prepiše `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Vrednost, shranjena iz preklopa na nadzorni plošči, ima prednost pred to spremenljivko. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Dovoli URL-je ponudnikov na lokalnih/zasebnih naslovih (127.0.0.1, localhost, LAN, zasebni obsegi) za preverjanje veljavnosti ponudnikov, odkrivanje modelov, klepet in druge izhodne poti, povezane s ponudniki, ki jim vlada režim varovanja. **Privzeto `true`** (prednost lokalnemu): varovanje takrat blokira končne točke metapodatkov v oblaku — celoten obseg 169.254.0.0/16 plus znana imena gostiteljev metapodatkov (metadata.google.internal, metadata.goog, IMDS Alibaba/Azure/Oracle, fd00:ec2::254). Nastavite `false` za režim samo-javno, ki blokira tudi zasebne in povratne gostitelje. Varovanje preverja ime gostitelja ali dobesedno zapisan IP natanko tako, kot je zapisan, še pred vsakim DNS-poizvedovanjem; v režimu samo-javno ovojnica varnega pridobivanja dodatno razreši ime gostitelja in zavrne vsak zasebni odgovor. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` izklopi ta preverjanja na poteh, naštetih v vrstici te zastavice. Vgrajeni lokalni ponudniki, kot so LM Studio, Ollama, vLLM, Llamafile in Triton, pri preverjanju ključev in klepetu preskočijo vsa preverjanja gostiteljev. Vrednost, shranjena iz preklopa na nadzorni plošči, ima prednost pred to spremenljivko. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Dovoli potem `/v1/audio/*` (prepisi, govor, prevodi), da uporabljajo vozlišče ponudnika, združljivo z OpenAI, ki gostuje zunaj localhost. Privzeto izklopljeno — usmerjanje zvoka na oddaljenega gostitelja spremeni identiteto izhodnega prometa in mora biti izrecna odločitev upravljavca. Povratna/zasebna vozlišča (localhost, 127.0.0.1, 172.16-31.x) so vedno dovoljena in nanje ta nastavitev ne vpliva. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Dovoli `POST /v1/rerank` (in koraku povratnega ponovnega razvrščanja pomnilniškega mehanizma), da uporablja vozlišče ponudnika, združljivo z OpenAI, ki gostuje zunaj localhost — napravo v omrežju LAN ali vrstnika Tailscale, v katerem se izvaja TEI, Infinity, vLLM itd. Privzeto izklopljeno — usmerjanje na oddaljenega gostitelja spremeni identiteto izhodnega prometa in mora biti izrecna odločitev upravljavca. Povratna vozlišča (localhost, 127.0.0.1, 172.16-31.x) so vedno dovoljena in nanje ta nastavitev ne vpliva. Osnovni URL oddaljenega vozlišča mora prestati tudi pravilnik za izhodne URL-je ponudnika (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Ko je OIDC omogočen, onemogoči prijavo z geslom, tako da se lahko uporabniki overijo samo prek enotne prijave OIDC. Sprejet je tudi goli vzdevek `OIDC_DISABLE_PASSWORD_LOGIN`; prednost ima zastavica funkcije nadzorne plošče z istim ključem. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Goli vzdevek za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Kontrolni seznam za utrjevanje varnosti diff --git a/docs/i18n/sr/CONTRIBUTING.md b/docs/i18n/sr/CONTRIBUTING.md index 3205f9d49e7a..6bd6260ad6bc 100644 --- a/docs/i18n/sr/CONTRIBUTING.md +++ b/docs/i18n/sr/CONTRIBUTING.md @@ -399,22 +399,24 @@ docs/ --- -## Листа за проверу Pull Request-а +## Контролна листа за Pull Request - [ ] Тестови пролазе (`npm test`) -- [ ] Linting пролази (`npm run lint`) -- [ ] Build успешно пролази (`npm run build`) -- [ ] TypeScript типови додати за нове јавне функције и интерфејсе -- [ ] Нема хардкодованих тајни (secrets) или fallback вредности -- [ ] Јавни upstream креденцијали уграђени преко `resolvePublicCred()` (погледајте [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), никада као литерали -- [ ] Одговори са грешкама пролазе кроз `buildErrorBody()` / `sanitizeErrorMessage()` — нема сирових stack trace-ова у телима одговора (погледајте [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell команде (`exec` / `spawn`) прослеђују runtime вредности преко `env`, а не путем интерполације стринга -- [ ] Сви инпути су валидирани помоћу Zod шема -- [ ] Додат changelog **фрагмент** унутар `changelog.d/{features|fixes|maintenance}/-.md` за промене видљиве корисницима (погледајте [`changelog.d/README.md`](./changelog.d/README.md)) — **немојте** директно уређивати `CHANGELOG.md`; фрагменти се агрегирају приликом издања и никада се не сукобљавају између PR-ова -- [ ] Документација ажурирана (ако је применљиво) -- [ ] Нема нових CodeQL / Secret-Scanning упозорења, или је свако од њих одбачено са техничким образложењем које упућује на релевантан документ из `docs/security/` -- [ ] Руте које покрећу подпроцесе (`/api/mcp/`, `/api/cli-tools/runtime/`) класификоване као `isLocalOnlyPath()` у `src/server/authz/routeGuard.ts` — погледајте [Строго правило бр. 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Нема `Co-Authored-By` додатака у commit порукама — commit-ови морају бити исписани искључиво под Git идентитетом власника репозиторијума (Строго правило бр. 16) +- [ ] Провера lint правила пролази (`npm run lint`) +- [ ] Изградња је успешна (`npm run build`) +- [ ] Додати су TypeScript типови за нове јавне функције и интерфејсе +- [ ] Нема хардкодованих тајни или резервних вредности +- [ ] Јавни приступни подаци узводног система уграђени су помоћу `resolvePublicCred()` (погледајте [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), никада као литерали +- [ ] Одговори са грешкама пролазе кроз `buildErrorBody()` / `sanitizeErrorMessage()` — нема необрађених stack trace-ова у телима одговора (погледајте [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Команде љуске (`exec` / `spawn`) прослеђују вредности током извршавања преко `env`, а не путем интерполације стрингова +- [ ] Сви улази су валидирани помоћу Zod шема +- [ ] Додат је **фрагмент** евиденције измена у `changelog.d/{features|fixes|maintenance}/-.md` за измене видљиве корисницима (погледајте [`changelog.d/README.md`](./changelog.d/README.md)) — немојте директно уређивати `CHANGELOG.md`; фрагменти се обједињују приликом издавања и никада не изазивају конфликте између PR-ова +- [ ] Документација је ажурирана (ако је применљиво) +- [ ] Нису отворена нова CodeQL упозорења нити упозорења Secret-Scanning-а, или је свако одбачено уз техничко образложење које упућује на релевантни документ у `docs/security/` +- [ ] Руте које покрећу подређене процесе (`/api/mcp/`, `/api/cli-tools/runtime/`) класификоване су као `isLocalOnlyPath()` у `src/server/authz/routeGuard.ts` — погледајте [Строго правило #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Нема AI/bot `Co-authored-by` завршних редова у порукама commit-а (Строго правило #16) — људски сарадници чији је рад поново употребљен наводе се помоћу стандардних `Co-authored-by: Name ` завршних редова + +--- ## Издавање diff --git a/docs/i18n/sr/docs/architecture/QUALITY_GATES.md b/docs/i18n/sr/docs/architecture/QUALITY_GATES.md index 00fb73d855c1..f5fdcd29211e 100644 --- a/docs/i18n/sr/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/sr/docs/architecture/QUALITY_GATES.md @@ -19,23 +19,23 @@ ### Прихватање кандидата -CI токови рада и токови рада за капије квалитета емитују стабилан исход: `Gate / CI` и -`Gate / Quality`. Њихова верзионисана политика прихватања наводи сваки узводни посао -као обавезан или саветодаван. Применљив обавезан посао мора бити успешан: недостајући, -отказани, прескочени, резултати на чекању и непознати резултати не могу да утврде PASS. Важећа -класификација само за документацију или само за каталог може учинити програмску путању неприменљивом; -нацрт PR-а није прихваћен кандидат. Ознака `hotfix` не укида захтев за доказима. - -Оба тока рада обухватају PR-ове и прослеђивања на главне/издавачке гране, ручно покретање и -догађаје групе спајања. Прослеђивање, ручно покретање и група спајања извршавају пун избор. Форкови -и групе спајања користе хостоване извршиоце за послове који би иначе изабрали -самостално хостоване извршиоце; пре увођења мора се проверити довољан хостовани капацитет. - -Свака JSON потврда идентификује преузети SHA, извршавање тока рада и покушај. -CLI одбацује неподударност SHA вредности преузете верзије и догађаја. Тестови тока рада повезују чланство у политици -са листом `needs` посла за утврђивање исхода, тако да нова или уклоњена путања не може неприметно нестати. -Потврде покривају сопствени ток рада, а не објављивање, примену или унутрашњи рад -постојећег саветодавног скенера. Активирање оба назива провера у правилима гране представља +CI и Quality Gates токови рада емитују стабилан исход: `Gate / CI` и +`Gate / Quality`. Њихова верзионисана политика прихватања наводи сваки претходни посао +као обавезан или саветодаван. Применљив обавезни посао мора успети: недостајући, +отказани, прескочени, исходи на чекању и непознати исходи не могу установити PASS. Важећа +класификација само за документацију или само за каталог може учинити програмску грану неприменљивом; +нацрт PR-а није прихваћени кандидат. Ознака `hotfix` не укида захтев за доказима. + +Оба тока рада обухватају PR-ове и слања на главне/release гране, ручно покретање и +догађаје групе спајања. Слање, ручно покретање и група спајања извршавају пун избор. Форкови +и групе спајања користе хостоване покретаче за послове који би иначе изабрали самостално хостоване +покретаче; пре увођења мора се проверити довољан хостовани капацитет. + +Свака JSON потврда идентификује преузети SHA, покретање тока рада и покушај. +CLI одбацује неподударање SHA вредности преузетог стања и догађаја. Тестови тока рада везују чланство у политици +за листу `needs` посла који доноси исход, тако да нова или уклоњена грана извршавања не може неприметно нестати. +Потврде обухватају сопствени ток рада, а не објављивање, примену или интерну логику +постојећег саветодавног скенера. Активирање оба назива провере у правилима гране представља засебну административну измену; додавање ових послова само по себи не штити грану. ### Инвентар статичког скенирања @@ -43,190 +43,190 @@ CLI одбацује неподударност SHA вредности преу Верзионисани инвентар npm алијаса и чланство у статичком скенирању налазе се у `config/quality/gate-manifest.json`. Покрените `npm run check:gate-manifest` да бисте проверили називе скрипти и тачне команде у односу на `package.json`; додавања, уклањања и -одступања команди узрокују неуспех и локалне куке и послова класификације измена у CI-ју. -Алијас није посао тока рада, инстанца матрице или тест случај: ови бројеви се +одступања команди доводе до неуспеха и локалне куке и послова класификације измена у CI-ју. +Алијас није посао тока рада, инстанца матрице нити тестни случај: ови бројеви се не смеју представљати као међусобно заменљиви. Користите `npm run quality:scan -- --list` или `npm run quality:scan:fast -- --list` -да бисте прегледали изабране алијасе без њиховог извршавања. Извршилац позива -npm улазну тачку, тако да се њено окружење за извршавање (укључујући Bun тамо где је конфигурисан) задржава. -Манифест бележи алијасе изван ових профила као засебно позване, а +да бисте прегледали изабране алијасе без њиховог извршавања. Покретач позива +npm улазну тачку, тако да је њено окружење за извршавање (укључујући Bun тамо где је конфигурисан) очувано. +Манифест бележи алијасе изван тих профила као засебно позиване, а команде за одржавање су забрањене у профилима скенирања само за читање. -Ови профили покривају само статичко скенирање. Они не потврђују тестове производа, -покривеност, паковање, спољне провере или потпуно прихватање кандидата за издање. -Прихватање у току рада користи повезани `config/quality/admission-policy.json` и -`scripts/quality/admission-verdict.mjs`. Профили за надгледање издања остају одвојени; -њихове применљиве провере и потврде прегледајте независно. Текстуални +Ови профили обухватају само статичко скенирање. Они не сертификују тестове производа, +покривеност, паковање, спољне провере нити потпуно прихватање кандидата за издање. +Прихватање у току рада користи повезане `config/quality/admission-policy.json` и +`scripts/quality/admission-verdict.mjs`. Профили посматрача издања остају засебни; +независно прегледајте њихове применљиве провере и потврде. Текстуални инвентар у наставку служи као референца, а не као доказ да је капија заиста извршена. -Скрипте се налазе у `scripts/check/` (капије политика) и `scripts/quality/` (механизам постепеног пооштравања). +Скрипте се налазе у `scripts/check/` (капије политике) и `scripts/quality/` (механизам постепеног пооштравања). CI извор истине је `.github/workflows/ci.yml`. -### Брза путања PR-а за издање (`quality.yml`) +### Брза путања за PR издања (`quality.yml`) -`.github/workflows/quality.yml` допуњује CI за PR-ове главних/издавачких грана, прослеђивања -на заштићене гране, ручна покретања и групе спајања. PR-ови користе брзе провере филтриране према путањи. Трајно +`.github/workflows/quality.yml` допуњује CI за PR-ове на главним/release гранама, слања +на заштићене гране, ручна покретања и групе спајања. PR-ови користе брзе провере филтриране по путањама. Трајно онемогућена дуплирана изградња је уклоњена; стварне провере изградње/паковања/покретања остају у CI-ју. -| Посао | Опсег | Блокирајући | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `Docs Gates (fast-path)` | PR-ови документације/кода; референце API документације и целокупна документација | Да | -| `Fast Quality Gates` | PR-ови кода; статичке провере, провера типова, провера типова контролне табле, тестови јединица на које измене утичу | Да | -| `Forgotten sibling tests` | PR-ови кода; измењени модули праћени до статичких потрошача и тестова сродних кандидата; путање преко збирних модула и динамичког увоза пријављују се као саветодавна дијагностика, уз наведене изузетке са листе дозвољених ставки | **Саветодавно** | -| `Vitest (fast-path)` | PR-ови кода; брзи Vitest скуп | Да | -| `Unit Tests fast-path` | PR-ови кода; скуп тестова јединица у 4 партиције | Да | -| `No new ESLint warnings` | PR-ови кода; заштита од упозорења линтера која узима у обзир потискивања | Да, укључујући форкове | -| `Merge integrity (changelog + generated skills)` | PR-ови који нису нацрти; синхронизација дневника измена и генерисаних вештина | Да, укључујући форкове | +| Посао | Опсег | Блокирајуће | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `Docs Gates (fast-path)` | PR-ови документације/кода; референце API документације и сва документација | Да | +| `Fast Quality Gates` | PR-ови кода; статичке провере, провера типова, провера типова контролне табле, обухваћени јединични тестови | Да | +| `Forgotten sibling tests` | PR-ови кода; измењени модули праћени до статичких потрошача и кандидата за сродне тестове; путање barrel модула и динамичког увоза пријављују се као саветодавна дијагностика, уз наведене изузетке са листе дозвољених | **Саветодавно** | +| `Vitest (fast-path)` | PR-ови кода; брзи скуп vitest тестова | Да | +| `Unit Tests fast-path` | PR-ови кода; скуп јединичних тестова у 4 шарда | Да | +| `No new ESLint warnings` | PR-ови кода; заштита lint провере која узима у обзир потискивања | Да, укључујући форкове | +| `Merge integrity (changelog + generated skills)` | PR-ови који нису нацрти; синхронизација дневника измена и генерисаних вештина | Да, укључујући форкове | #### Извештај о заборављеним сродним тестовима -`npm run check:forgotten-sibling-tests` поново користи разрешавач увоза који стоји иза мапе утицаја на тестове. +`npm run check:forgotten-sibling-tests` поново користи разрешивач увоза који стоји иза мапе утицаја тестова. За сваки измењени продукциони модул пријављује детерминистичке ланце -`измењени модул/симбол -> статички потрошач -> сродни тест кандидат` када тест кандидат -није присутан у разлици захтева за повлачење. Markdown сажетак и JSON резултат чувају се као +`измењени модул/симбол -> статички потрошач -> кандидат за сродни тест` када кандидат +за тест није присутан у разлици захтева за повлачење. Markdown сажетак и JSON резултат чувају се као артефакт тока рада `forgotten-sibling-tests` ради калибрације пре било каквог увођења блокирања. Barrel поновни извози и динамички увози служе само за дијагностику разрешавања; они никада не стварају блокирајући налаз. Прегледани изузеци налазе се у `config/quality/forgotten-sibling-allowlist.json`. Свака ставка мора да наведе потрошача и кандидатски -тест, пружи конкретно образложење и садржи везу ка GitHub проблему или pull request-у. Неисправне ставке се -подразумевано одбијају. Изузеци не могу да прикрију обрисани кандидатски тест нити измену која додаје `.skip`/`.todo`; +тест, пружи конкретно образложење и садржи везу ка GitHub проблему или захтеву за спајање. Неисправне ставке се +подразумевано одбијају. Изузеци не могу да потисну обрисани кандидатски тест нити diff који додаје `.skip`/`.todo`; слабљење тврдњи и друга прикривања остају у надлежности независно блокирајуће `check:test-masking` контроле. ### Задатак: `lint` -Покреће се за сваки PR ка грани `main`. Блокира спајање у случају неуспеха. - -| Скрипта (`npm run ...`) | Проверава | Блокирајуће | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- | -| `check:node-runtime` | Верзија Node.js-а је унутар подржаног опсега | Да | -| `check:cycles` | Цикличне увозе — све модуле у `src/` + `open-sse/` | Да | -| `check:route-validation:t06` | Zod шеме су присутне на свим рутама (Tier 6 смерница) | Да | -| `check:any-budget:t11` | Број `@ts-expect-error // any` не премашује буџет (Tier 11 catraca) | Да | -| `check:provider-consistency` | Сваки провајдер у `providers.ts` има одговарајући унос у `providerRegistry.ts` (и обрнуто, у оквиру листе дозвољених) | Да | -| `check:model-lifecycle` | Три ручно одржаване табеле рутирања остају усклађене са снимком животног циклуса који је укључен у репозиторијум (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не бодује ниједан повучени id који `REGISTRY` може да рутира; сваки циљ из `BUILT_IN_ALIASES` присутан је у `REGISTRY` и одсутан из снимка повучених id-ова; сваки повучени id који је још увек у `REGISTRY` прослеђује се или је наведен у `allowedRetiredInCatalog`; и ниједан извор или циљ из `DEFAULT_DEGRADATION_MAP` није означен као повучен у том снимку. Ово не доказује да активни узводни сервис тренутно опслужује модел. Офлајн — пореди са `config/quality/model-lifecycle.json`, који се ручно освежава помоћу `npm run quality:refresh-model-lifecycle` (захтева мрежу; није повезано са CI-јем). `allowedRetiredInCatalog` је механизам са постепеним смањивањем: додајте унос само уз проблем за праћење. | Да | -| `check:fetch-targets` | Сваки `fetch("/api/...")` у клијентском `src/` разрешава се на стварни `route.ts` | Да | -| `check:deps` | Све зависности које се могу инсталирати помоћу `npm install` из сваког `package.json` у репозиторијуму налазе се у `dependency-allowlist.json`; нови пакети без фиксиране верзије или са називима који подсећају на постојеће пакете биће означени | Да | -| `audit:deps` | `npm audit` (корени пакет + Electron) — нема упозорења високог/критичног нивоа (преклапа се са OSV провером `check:vuln-ratchet`; погледајте списак за накнадно рационализовање) | Да | -| `check:lockfile` | Интегритет датотеке `package-lock.json` — HTTPS регистар, хешеви интегритета, без замене хостова | Да | -| `check:licenses` | Lista dozvoljenih SPDX licenci za produkcione zavisnosti | Da | -| `check:tracked-artifacts` | Bez artefakata izgradnje / komitovanih `node_modules` simboličkih veza (pokreće se i u husky pre-commit kuki; pre-push je namerno minimalan — #6716) | Da | -| `check:ai-attribution` | Bez AI/bot `Co-Authored-By` prikolice ili podnožja o AI generisanju u PR komitovima, naslovu ili telu — Strogo pravilo #16 (u petlji brzih provera u `quality.yml` za PR→`release/**` — čita sadržaj događaja, ne radi ništa van PR-ova — i korak samo za PR u lint proveri u `ci.yml` za PR→`main`; takođe i husky `commit-msg` kuka; ljudski koautori su dozvoljeni; #14436) | -| `check:vitest-exclusions` | Svako Vitest izuzimanje navodi prateći problem i nalazi se u `config/quality/vitest-exclusions.json` (#13204) | Da | -| `check:file-size` | Nijedna izvorna datoteka ne premašuje ograničenje za odgovarajuću ekstenziju (račva: zamrznute velike datoteke na listi `frozen`) | Da | -| `check:error-helper` | Odgovori sa greškama u izvršiocima/rukovaocima koriste `buildErrorBody()` / `sanitizeErrorMessage()` (Strogo pravilo #12) | Da | -| `check:migration-numbering` | Миграционе SQL датотеке су нумерисане узастопно, без празнина или дупликата | Да | -| `check:public-creds` | Нема дословно наведених OAuth вредности `client_id`/`client_secret` нити Firebase Web кључева изван `publicCreds.ts` (Строго правило бр. 11) | Да | -| `check:db-rules` | Нема необрађеног SQL-а изван модула `src/lib/db/`; нема обједињених увоза из `localDb.ts` (Строга правила бр. 2/5) | Да | -| `check:known-symbols` | Извршиоци добављача, стратегије рутирања и преводиоци регистровани у својим табелама за отпремање подударају се са датотекама на диску — нема неповезаних нити недекларисаних симбола | Да | -| `check:route-guard-membership` | Свака рута која покреће подређени процес класификована је помоћу `isLocalOnlyPath()` (Строга правила бр. 15/17) | Да | -| `check:test-discovery` | Сваку датотеку `*.test.ts` / `*.spec.ts` у репозиторијуму прикупља барем један покретач тестова (запорни механизам: листа неповезаних датотека у `test-discovery-baseline.json` може само да се смањује) | Да | -| `check:agent-skills-sync` | Генерисани agent-skills артефакти одговарају свом изворном каталогу (без одступања) | -| `check:provider-asset-provenance` | Логотипи/ресурси провајдера имају забележен податак о пореклу | -| `lint:json` | JSON конфигурационе датотеке се исправно рашчлањују и задовољавају правила линтовања репозиторијума | -| `typecheck:core` | TypeScript компилација без грешака (само саветодавна упозорења) | Да | -| `typecheck:noimplicit:core` | Строги `noImplicitAny` — усмерено на будуће стање; многа постојећа места позива и даље захтевају анотације | **Саветодавно** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` ограничен на `src/app/(dashboard)/**` (#7033) — пажљиво одабрана листа од 27 датотека у `typecheck:core` не обухвата ниједан dashboard TSX, а ни `next build` никада не проверава њихове типове (`next.config.mjs` поставља `ignoreBuildErrors: true`), па су регресије са идентификаторима без дефиниције у њима (#6625/#6909) биле невидљиве CI-ју. Разлике се пореде са замрзнутом основном вредношћу броја грешака по датотеци и TS коду (`config/quality/dashboard-typecheck-baseline.json`, исти образац провере застарелости као код `check:known-symbols`) — само НОВЕ грешке изнад броја у основној вредности обарају проверу; постепено смањујте основну вредност помоћу `--update` када се постојећа грешка исправи. | Да | +Покреће се при сваком PR-у ка `main`. У случају неуспеха блокира спајање. + +| Скрипта (`npm run ...`) | Проверава | Блокирајуће | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | Верзија Node.js-а је унутар подржаног опсега | Да | +| `check:cycles` | Цикличне увозе у целом `src/` + `open-sse/` (засновано на AST-у, разрешене су tsconfig `paths` путање). Самостално покретање = саветодавно, наводи циклусе. `check:cycles:ratchet` (који CI покреће) блокира када број премаши горњу границу `metrics.cycles` у `quality-baseline.json` — тренутно 14, `direction: down`, тако да може само да се смањује (#15159 G-01/G-02) | Да (механизам постепеног пооштравања) | +| `check:route-validation:t06` | Zod шеме су присутне на свим рутама (политика нивоа 6) | Да | +| `check:any-budget:t11` | Број `@ts-expect-error // any` не премашује ограничење (catraca нивоа 11) | Да | +| `check:provider-consistency` | Сваки провајдер у `providers.ts` има одговарајући унос у `providerRegistry.ts` (и обрнуто, у оквиру листе дозвољених) | Да | +| `check:model-lifecycle` | Три ручно одржаване табеле рутирања остају усклађене са снимком животног циклуса који је укључен у репозиторијум (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не бодује ниједан повучени id који `REGISTRY` може да рутира; сваки циљ из `BUILT_IN_ALIASES` присутан је у `REGISTRY` и одсутан из снимка повучених id-ова; сваки повучени id који је и даље у `REGISTRY` прослеђује се или је наведен у `allowedRetiredInCatalog`; и ниједан извор или циљ из `DEFAULT_DEGRADATION_MAP` није означен као повучен у том снимку. Ово не доказује да модел тренутно опслужује активни узводни систем. Ради ван мреже — пореди са `config/quality/model-lifecycle.json`, који се ручно освежава помоћу `npm run quality:refresh-model-lifecycle` (захтева мрежу; није повезано са CI-јем). `allowedRetiredInCatalog` је механизам постепеног смањивања: унос додајте само уз проблем за праћење. | Да | +| `check:fetch-targets` | Сваки `fetch("/api/...")` у клијентском `src/` разрешава се на стварни `route.ts` | Да | +| `check:deps` | Све зависности које се могу инсталирати помоћу `npm install` у сваком `package.json` у репозиторијуму налазе се у `dependency-allowlist.json`; нови неприковани или slopsquatted пакети се означавају | Да | +| `audit:deps` | `npm audit` (корен + electron) — нема упозорења високог/критичног нивоа (преклапа се са osv `check:vuln-ratchet`; погледајте Заостатак за рационализацију) | Да | +| `check:lockfile` | Интегритет датотеке `package-lock.json` — https регистар, хешеви интегритета, без замена хоста | Да | +| `check:licenses` | Листа дозвољених SPDX лиценци за продукционе зависности | Да | +| `check:tracked-artifacts` | Нема артефаката изградње / комитованих симболичких веза ка `node_modules` (такође се покреће у husky pre-commit куки; pre-push је намерно поједностављен — #6716) | Да | +| `check:ai-attribution` | Нема AI/bot `Co-Authored-By` завршног реда нити подножја о AI генерисању у PR комитовима, наслову или телу — Строго правило #16 (у `quality.yml` петљи брзих провера за PR→`release/**` — чита садржај догађаја и не ради ништа ван PR-ова — и корак само за PR у `ci.yml` lint провери за PR→`main`; такође и husky `commit-msg` кука; људски коаутори су дозвољени; #14436) | +| `check:vitest-exclusions` | Свако Vitest изузеће наводи проблем за праћење и налази се у `config/quality/vitest-exclusions.json` (#13204) | Да | +| `check:file-size` | Ниједна изворна датотека не премашује ограничење за своју екстензију (чегртаљка: велике датотеке су замрзнуте у листи `frozen`) | Да | +| `check:error-helper` | Одговори о грешкама у извршиоцима/обрађивачима користе `buildErrorBody()` / `sanitizeErrorMessage()` (Строго правило #12) | Да | +| `check:migration-numbering` | Миграционе SQL датотеке су нумерисане редом, без празнина или дупликата | Да | +| `check:public-creds` | Нема дословних OAuth вредности `client_id`/`client_secret` нити Firebase Web кључева изван `publicCreds.ts` (строго правило бр. 11) | Да | +| `check:db-rules` | Нема сировог SQL-а изван модула у `src/lib/db/`; нема збирних увоза из `localDb.ts` (строга правила бр. 2/5) | Да | +| `check:known-symbols` | Извршиоци добављача, стратегије усмеравања и преводиоци регистровани у својим табелама за отпремање подударају се са датотекама на диску — нема неповезаних или непријављених симбола | Да | +| `check:route-guard-membership` | Свака рута која покреће подређени процес класификована је помоћу `isLocalOnlyPath()` (строга правила бр. 15/17) | Да | +| `check:test-discovery` | Свака датотека `*.test.ts` / `*.spec.ts` у репозиторијуму обухваћена је најмање једним покретачем тестова (механизам са чегртаљком: листа неповезаних датотека у `test-discovery-baseline.json` може само да се смањује) | Да | +| `check:agent-skills-sync` | Генерисани agent-skills артефакти одговарају свом изворном каталогу (без одступања) | +| `check:provider-asset-provenance` | Логотипи/ресурси добављача имају забележен податак о пореклу | +| `lint:json` | JSON конфигурационе датотеке се успешно рашчлањују и задовољавају правила провере репозиторијума | +| `typecheck:core` | TypeScript компилација без грешака (само саветодавна упозорења) | Да | +| `typecheck:noimplicit:core` | Строги `noImplicitAny` — усмерено ка будућности; многа постојећа места позива и даље захтевају анотације | **Саветодавно** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` ограничен на `src/app/(dashboard)/**` (#7033) — пажљиво одабрана листа од 27 датотека у `typecheck:core` не обухвата ниједан dashboard TSX, а ни `next build` га никада не проверава типски (`next.config.mjs` поставља `ignoreBuildErrors: true`), па су регресије са идентификаторима без дефиниције у њему (#6625/#6909) биле невидљиве CI-ју. Разлике се пореде са замрзнутом основном линијом броја грешака по датотеци и TS коду (`config/quality/dashboard-typecheck-baseline.json`, исти образац за спровођење застарелости као код `check:known-symbols`) — само НОВЕ грешке изнад броја у основној линији доводе до неуспеха контроле; постепено смањујте основну линију помоћу `--update` када се исправи постојећа грешка. | Да | ### Задатак: `quality-gate` Покреће се након `test-coverage`. Неуспех блокира спајање. -| Скрипта | Проверава | Блокирајуће | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `quality:collect` | Генерише `quality-metrics.json` (број ESLint упозорења, покривеност из обједињеног извештаја сегмената) | Да (претходи механизму ratchet) | -| `quality:ratchet` | Ниједна метрика у `quality-baseline.json` није назадовала (ESLint упозорења ≤ основна вредност; покривеност ≥ основна вредност) | Да | -| `check:duplication` | Дуплирање кода (jscpd@4) не премашује основну вредност у `quality-baseline.json` | Да | -| `check:complexity` | Цикломатска сложеност на нивоу датотеке не премашује ограничење (основни ESLint `complexity` + `max-lines-per-function`) | Да | -| `check:cognitive-complexity` | Ratchet когнитивне сложености (`eslint-plugin-sonarjs`) — засебно покретање ESLint-а; CI оба покреће обједињено као један корак `check:complexity-ratchets` | Да | -| `check:dead-code` | Ratchet некоришћених извоза / датотека (knip) не назадује у односу на основну вредност | Да | -| `check:compression-budget` | Буџет за мерење перформанси компресије — минималне уштеде токена по механизму не смеју назадовати | Да | -| `check:type-coverage` | Ratchet процента типизованог кода (`type-coverage`) не назадује; у великој мери обухвата `typecheck:noimplicit:core` | Да | -| `check:codeql-ratchet` | Број отворених CodeQL упозорења не назадује (чита преко `gh api`; безбедно прескаче без токена) — учесталост освежавања и ручно покретање: погледајте „CodeQL ratchet“ испод | Да | - -### Посао: `quality-extended` - -Цео посао је саветодаван (`continue-on-error: true`). Ratchet провере засноване на npm-у заиста се -извршавају; спољни скенери се инсталирају преко `gh release download` и сами се прескачу (излазни кôд 0) +| Скрипта | Проверава | Блокирајуће | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| `quality:collect` | Генерише `quality-metrics.json` (број ESLint упозорења, покривеност из обједињеног извештаја партиција) | Да (претходи механизму прага) | +| `quality:ratchet` | Ниједна метрика у `quality-baseline.json` није назадовала (ESLint упозорења ≤ референтна вредност; покривеност ≥ референтна вредност) | Да | +| `check:duplication` | Дуплирање кода (jscpd@4) не премашује референтну вредност у `quality-baseline.json` | Да | +| `check:complexity` | Цикломатска сложеност на нивоу датотеке не премашује горњу границу (основни ESLint `complexity` + `max-lines-per-function`) | Да | +| `check:cognitive-complexity` | Механизам прага когнитивне сложености (`eslint-plugin-sonarjs`) — засебно ESLint извршавање; CI обједињује оба у један корак `check:complexity-ratchets` | Да | +| `check:dead-code` | Механизам прага за некоришћене извозе / датотеке (knip) не назадује у односу на референтну вредност | Да | +| `check:compression-budget` | Буџет бенчмарка компресије — минималне уштеде токена по механизму не смеју да назадују | Да | +| `check:type-coverage` | Механизам прага за проценат типизираног кода (`type-coverage`) не назадује; у великој мери обухвата `typecheck:noimplicit:core` | Да | +| `check:codeql-ratchet` | Број отворених CodeQL упозорења не назадује (чита преко `gh api`; безбедно прескаче без токена) — учесталост освежавања и ручно покретање: погледајте „CodeQL механизам прага“ испод | Да | + +### Задатак: `quality-extended` + +Цео задатак је саветодаван (`continue-on-error: true`). Механизми прага засновани на npm-у заиста се извршавају; +спољни скенери се инсталирају преко `gh release download` и сами се прескачу (излазни код 0) када бинарна датотека и даље није присутна. -| Скрипта | Проверава | Блокирајуће | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| `check:circular-deps` | Нема кружних зависности (dpdm) | **Саветодавно** | -| `check:bundle-size` | Величина пакета не премашује ограничење | **Саветодавно** | -| `check:secrets` | Скенирање тајни (gitleaks) — прескаче се ако бинарна датотека није присутна | **Саветодавно** | -| `check:vuln-ratchet` | Рањивости зависности (osv-scanner) не назадују — прескаче се ако бинарна датотека није присутна | **Саветодавно** | -| `check:workflows` | Провера радних токова (actionlint + zizmor); скенери који недостају или не раде, неважећи извештаји или основна ratchet вредност која недостаје узрокују неуспех са статусом INCOMPLETE. Важећи налази прате изабрану строгу/саветодавну/ratchet политику | Извршавање је обавезно; zizmor ratchet блокира у CI-ју | -| `check:openapi-breaking` | Неусклађене измене јавног API уговора (`openapi.yaml`) у односу на основну грану (oasdiff) — генерише `openapiBreaking=N`; прескаче се ако oasdiff није присутан или основна спецификација не може да се разреши | **Саветодавно** | - -### Посао: `docs-sync-strict` - -Покреће се при сваком PR-у ка грани `main`. Блокира спајање у случају неуспеха. - -| Скрипта | Проверава | Блокирајуће | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | -| `check:docs-all` | Мета-услов који редом покреће 6 услова у наставку | Да | -| ↳ `check:docs-sync` | Усклађеност верзија у CHANGELOG-у / OpenAPI-ју / llm.txt | Да | -| ↳ `check:docs-counts` | Бројеви у тексту (број добављача, број миграција итд.) налазе се унутар прогресивно ограниченог опсега стварних вредности | Да | -| ↳ `check:env-doc-sync` | Свака променљива окружења у `.env.example` документована је у табели документације и обрнуто | Да | -| ↳ `check:deprecated-versions` | У документацији нема застарелих ознака верзија | Да | -| ↳ `check:doc-links` | Интерне markdown везе у документацији упућују на постојеће датотеке (облик `[text]`/`(path)`) | Да | -| ↳ `check:fabricated-docs` | Руте, променљиве окружења, CLI команде, називи hook-ова и путање датотека наведени у документацији постоје у бази кода. Строг услов уз `--strict`; без те опције неуспех је неблокирајући. | Да (помоћу `--strict` у CI-ју) | -| `check:cli-i18n` | Ниске CLI команди присутне су у свим i18n датотекама локализације | Да | -| `check:openapi-coverage` | OpenAPI спецификација покрива најмање прогресивно повећаван минимални број стварних рута | Да | -| `check:openapi-security-tiers` | Ознаке безбедносних нивоа у `openapi.yaml` усклађене су са класификацијама у `routeGuard.ts` | **Саветодавно** | -| `check:openapi-routes` | Свака путања у `openapi.yaml` одговара стварној датотеци `route.ts` (заштита од халуцинација) | Да | -| `check:docs-symbols` | Свака референца `/api/...` у `docs/**/*.md` одговара стварној датотеци `route.ts` (заштита од халуцинација) | Да | -| `i18n translation drift` | Непреведени кључеви у i18n датотекама локализације — само упозорење | **Саветодавно** | +| Скрипта | Проверава | Блокирајуће | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | +| `check:circular-deps` | Нема кружних зависности (dpdm) | **Саветодавно** | +| `check:bundle-size` | Величина пакета не премашује горњу границу | **Саветодавно** | +| `check:secrets` | Скенирање тајни (gitleaks) — прескаче се ако бинарна датотека није присутна | **Саветодавно** | +| `check:vuln-ratchet` | Рањивости зависности (osv-scanner) не назадују — прескаче се ако бинарна датотека није присутна | **Саветодавно** | +| `check:workflows` | Провера токова рада (actionlint + zizmor); скенери који недостају или не раде, неважећи извештаји или референтна вредност механизма прага која недостаје узрокују статус INCOMPLETE. Важећи налази прате изабрану строгу/саветодавну политику или политику прага | Извршавање је обавезно; zizmor механизам прага блокира у CI-ју | +| `check:openapi-breaking` | Неусклађене измене јавног API уговора (`openapi.yaml`) у односу на основну грану (oasdiff) — генерише `openapiBreaking=N`; прескаче се ако oasdiff није присутан или се основна спецификација не може разрешити | **Саветодавно** | + +### Задатак: `docs-sync-strict` + +Покреће се при сваком PR-у ка `main`. Блокира спајање у случају неуспеха. + +| Скрипта | Проверава | Блокирајуће | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `check:docs-all` | Мета-капија која редом покреће 6 поткапија наведених испод | Да | +| ↳ `check:docs-sync` | Усклађеност верзија у CHANGELOG-у, OpenAPI-ју и llm.txt | Да | +| ↳ `check:docs-counts` | Бројеви у тексту (број добављача, број миграција итд.) налазе се унутар постепено пооштраваног опсега стварних вредности | Да | +| ↳ `check:env-doc-sync` | Свака променљива окружења у `.env.example` документована је у табели документације и обрнуто | Да | +| ↳ `check:deprecated-versions` | У документацији нема ниски застарелих верзија | Да | +| ↳ `check:doc-links` | Интерне markdown везе у документацији разрешавају се у стварне датотеке (облик `[текст]`/`(путања)`) | Да | +| ↳ `check:fabricated-docs` | Руте, променљиве окружења, CLI команде, називи hook-ова и путање датотека наведени у документацији постоје у бази кода. Строга капија преко `--strict`; без ове опције грешка није блокирајућа. | Да (преко `--strict` у CI-ју) | +| `check:cli-i18n` | Ниске CLI команди присутне су у свим i18n датотекама локализација | Да | +| `check:openapi-coverage` | OpenAPI спецификација покрива најмање постепено повећавани минимални праг стварних рута | Да | +| `check:openapi-security-tiers` | Ознаке безбедносних нивоа у `openapi.yaml` усклађене су са класификацијама у `routeGuard.ts` | **Саветодавно** | +| `check:openapi-routes` | Свака путања у `openapi.yaml` разрешава се у стварни `route.ts` (спречавање халуцинација) | Да | +| `check:docs-symbols` | Свака референца `/api/...` у `docs/**/*.md` разрешава се у стварни `route.ts` (спречавање халуцинација) | Да | +| `i18n translation drift` | Непреведени кључеви у i18n датотекама локализација — само упозорење | **Саветодавно** | ### Задатак: `i18n-ui-coverage` -| Скрипта | Проверава | Блокирајуће | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | -| `check-ui-keys-coverage` (уграђено) | Покривеност UI i18n кључева је ≥ 65% | Да | -| `check-ui-value-drift` (уграђено) | Измењена енглеска **вредност** не оставља за собом застарели превод | Да | -| `check-new-key-coverage` (уграђено) | **Нови** енглески кључ преведен је у свакој локализацији — ознака `__MISSING__:` се одбија | Да | -| `check-translation-ratio` | Удео стварних превода по локализацији (листови идентични енглеском / резервисане вредности / листови који недостају ван листе дозвољених) не сме премашити `config/quality/i18n-translation-baseline.json` + толеранцију | **Саветодавно** | +| Скрипта | Проверава | Блокирајуће | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (уграђено) | Покривеност UI i18n кључева је ≥ 65% | Да | +| `check-ui-value-drift` (уграђено) | Измењена енглеска **вредност** не оставља за собом застарели превод | Да | +| `check-new-key-coverage` (уграђено) | **Нови** енглески кључ преведен је у свакој локализацији — маркер `__MISSING__:` се одбија | Да | +| `check-translation-ratio` | Удео стварних превода по локализацији (листови идентични енглеском / резервисани текст / недостајући листови ван листе дозвољених изузетака) не сме премашити `config/quality/i18n-translation-baseline.json` + толеранцију | **Саветодавно** | -Захтева `fetch-depth: 0` — услов за одступање вредности пореди разлике у `en.json` у односу на основу спајања. +Захтева `fetch-depth: 0` — капија за одступање вредности пореди `en.json` са заједничком базом спајања. -#### `check-ui-value-drift` — услов за застареле преводе +#### `check-ui-value-drift` — капија за застареле преводе -Открива ону i18n регресију коју остали услови структурно не могу да уоче: енглеска вредност -се измени, док преводи изведени из _претходног_ енглеског остану непромењени, па +Открива једну i18n регресију коју друге капије структурно не могу да уоче: енглеска вредност +се измени, а преводи изведени из _претходног_ енглеског остану непромењени, па корисници који не говоре енглески настављају да читају самоуверено формулисан, али сада нетачан текст. Ово се заиста нашло у издању. `oauthModal.googleOAuthWarning` је измењен када је додат Antigravity помоћник за пријављивање (#5203); **39 од 43 локализације** задржало је текст који оператерима говори да „копирају -цео URL и налепе га испод“ — ток који за тог добављача није могуће довршити. То је +целу URL адресу и налепе је испод“ — ток који код тог добављача није могуће довршити. То је остало непримећено све до #8463 зато што: -- `sync-ui-keys` допуњава само кључеве који су **одсутни**, никада оне који су **застарели**; -- `check-ui-keys-coverage` броји _присутност_ кључа, па се застарели превод рачуна као покривен; -- `check-translation-drift` прати документационе копије у `docs/i18n//**.md` — +- `sync-ui-keys` допуњава само кључеве који **недостају**, никада оне који су **застарели**; +- `check-ui-keys-coverage` рачуна _присуство_ кључа, па се застарели превод рачуна као покривен; +- `check-translation-drift` прати документационе одразе `docs/i18n//**.md` — никада не чита `src/i18n/messages/*.json`. Блокирајуће у задатку `docs-sync-strict` од - поновне синхронизације 2026-09: измените основни документ → `npm run i18n:run -- --files=` (на нивоу одељка, брзо). + поновне синхронизације 2026-09: измените основни документ → `npm run i18n:run -- --files=` (на нивоу одељка, јефтино). **Узима у обзир разлике, без ослањања на основно стање.** Пореди `en.json` у основи спајања са радним стаблом; за сваки кључ чија се енглеска вредност променила, сваки локалитет који и даље садржи -нетакнути превод сматра се застарелим. Ово намерно **замрзава већ постојећи дуг** — разлика -не може да открије из које је старе енглеске верзије настао дугогодишњи превод, па контрола оцењује -само оно што тренутна измена обухвата. Алтернатива (основно стање хеша за сваки кључ) захтевала би -генерисану датотеку од око 600 KB, 3× већу од највећег постојећег основног стања, која би се мењала при сваком i18n PR-у. +нетакнут превод сматра се застарелим. Ово намерно **замрзава постојећи дуг** — разлика +не може да открије из ког старог енглеског текста потиче дуготрајни превод, па контролни механизам процењује +само оно што тренутна измена дотиче. Алтернатива (основно стање хеша за сваки кључ) захтевала би +генерисану датотеку од ~600 KB, 3× већу од највећег постојећег основног стања, која би се мењала при сваком i18n PR-у. Постоје два начина да се услов испуни: 1. ажурирати обухваћене преводе или -2. поставити их на `__MISSING__:` — током извршавања се тада приказује исправљени енглески +2. поставити их на `__MISSING__:<нови енглески текст>` — окружење за извршавање тада испоручује исправљени енглески текст (`src/i18n/request.ts::deepMergeFallback`, #7258), а кључ се ставља у ред за превођење. Ако се променило **значење** ниске, пожељно је **преименовати кључ**: нови кључ не може да наследи -застарели превод. Тај образац је употребљен у #8463. +застарели превод. То је образац коришћен у #8463. ```bash -npm run i18n:check-value-drift # строго (оно што CI покреће) +npm run i18n:check-value-drift # строго (оно што CI извршава) npm run i18n:check-value-drift:warn # само извештај BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` @@ -234,45 +234,45 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift Завршава се кодом 0 уз `SKIP reason=base-unresolved` када основни каталог не може да се прочита (плитко клонирање без основне референце), по узору на `check-openapi-breaking`. -### Посао: `i18n` +### Задатак: `i18n` -Пуна матрица i18n провере (један посао по локалитету). Цео посао је саветодаван. +Пуна матрица i18n провере (један задатак по локалитету). Цео задатак је саветодаван. -| Скрипта | Проверава | Блокирање | -| ------------------------------- | ------------------------------- | -------------------------------------------------------- | -| `validate_translation.py quick` | Потпуност превода по локалитету | **Саветодавно** (`continue-on-error: true` за цео посао) | +| Скрипта | Проверава | Блокирање | +| ------------------------------- | ------------------------------- | ---------------------------------------------------------- | +| `validate_translation.py quick` | Потпуност превода по локалитету | **Саветодавно** (`continue-on-error: true` за цео задатак) | -### Посао: `pr-test-policy` +### Задатак: `pr-test-policy` -Покреће се само за захтеве за спајање. +Извршава се само за захтеве за повлачење. -| Скрипта | Проверава | Блокирање | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `check:pr-test-policy` | PR-ови који мењају продукциони кôд у `src/`, `open-sse/`, `electron/` или `bin/` морају да укључе или ажурирају тестове (Строго правило #8) | Да | -| `check:test-masking` | Измењене датотеке тестова не смањују нето број провера нити додају таутологије `assert.ok(true)` | Да | -| `check:pr-evidence` | Тело PR-а наводи доказе из тестова/VPS-а за измену (аутоматизује Строго правило #18 претраживањем текста PR-а — непоуздано, видети Заостатак) | Да | +| Скрипта | Проверава | Блокирање | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `check:pr-test-policy` | PR-ови који мењају продукциони кôд у `src/`, `open-sse/`, `electron/` или `bin/` морају да укључе или ажурирају тестове (строго правило #8) | Да | +| `check:test-masking` | Измењене тестне датотеке не смањују укупан нето број провера нити додају таутологије `assert.ok(true)` | Да | +| `check:pr-evidence` | Тело PR-а наводи доказе о тестирању/VPS-у за измену (аутоматизује строго правило #18 претрагом текста PR-а — крхко, видети Backlog) | Да | -### Посао: `test-vitest` +### Задатак: `test-vitest` -Покреће се након `build`. Блокира спајање у случају неуспеха. +Извршава се после `build`. У случају неуспеха блокира спајање. -| Пакет тестова | Проверава | Блокирање | -| ---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP сервер (110 алата), autoCombo, кеш — vitest покретач | Да | -| `test:vitest:ui` | Тестови UI компоненти — vitest покретач | **Блокира** — већ постојећи неуспеси су изричито изузети у `vitest.config.ts`; нови неуспеси обарају посао | +| Пакет тестова | Проверава | Блокирање | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP сервер (110 алата), autoCombo, кеш — vitest покретач | Да | +| `test:vitest:ui` | Тестови UI компоненти — vitest покретач | **Блокира** — постојећи неуспеси су изричито изузети у `vitest.config.ts`; нови неуспеси доводе до неуспеха задатка | ### Ноћни токови рада (заказани, саветодавни) -Покрећу се по cron распореду (и путем `workflow_dispatch`), никада за PR-ове. Сви су саветодавни. +Они се извршавају према cron распореду (и путем `workflow_dispatch`), никада за PR-ове. Сви су саветодавни. -| Ток рада | Проверава | Блокирање | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | fast-check тестове својстава са насумичним почетним семеном + великим бројем покретања | **Саветодавно** | -| `nightly-resilience` | контролу раста хипа, убризгавање хаотичних грешака, k6 тестове оптерећења/дуготрајног рада | **Саветодавно** | -| `nightly-llm-security` | promptfoo заштиту од убризгавања (режим блокирања) + garak сонде (прескачу се без тајне добављача) | **Саветодавно** | -| `nightly-schemathesis` | фузинг OpenAPI уговора (schemathesis) над активним OmniRoute-ом помоћу `docs/openapi.yaml` — открива кршења спецификације / необрађене грешке 500 (Фаза 8 B.4) | **Саветодавно** | -| `nightly-mutation` | Stryker резултат мутационог тестирања над брзом граном јединичних тестова — преживели мутанти откривају слабе провере | **Саветодавно** | -| `nightly-compat` | матрицу компатибилности Node механизма у свим подржаним опсезима `engines.node` | **Саветодавно** | +| Ток рада | Проверава | Блокирање | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `nightly-property` | fast-check тестове својстава са насумичним почетним параметром + великим бројем покретања | **Саветодавно** | +| `nightly-resilience` | контролу раста хипа, хаотично убризгавање грешака, k6 тестове оптерећења/издржљивости | **Саветодавно** | +| `nightly-llm-security` | promptfoo заштиту од убризгавања (режим блокирања) + garak сонде (прескачу се без тајне добављача) | **Саветодавно** | +| `nightly-schemathesis` | расплинуто тестирање OpenAPI уговора (schemathesis) над активним OmniRoute-ом помоћу `docs/openapi.yaml` — открива кршења спецификације / необрађене грешке 500 (фаза 8 B.4) | **Саветодавно** | +| `nightly-mutation` | резултат Stryker мутационог тестирања над брзом путањом јединичних тестова — преживели мутанти откривају слабе провере | **Саветодавно** | +| `nightly-compat` | матрицу компатибилности Node механизма за подржане опсеге `engines.node` | **Саветодавно** | --- @@ -536,7 +536,7 @@ CodeQL: Default → Advanced. Немојте додавати окидач `sche --- -## Заостатак рационализације (преглед ROI — фаза 9, талас 3) +## Заостатак рационализације (преглед ROI — Фаза 9 Талас 3) Овај инвентар је усклађен са `ci.yml` 2026-06-17 (претходна верзија је изоставила `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, @@ -549,30 +549,30 @@ CodeQL: Default → Advanced. Немојте додавати окидач `sche **Такође није документовано изнад** (саветодавно, слаб сигнал): посао `docs-lint` (markdownlint + Vale, цео посао има `continue-on-error`) и самостални токови рада скенера `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` се налази у -`quality-baseline.json`, али није повезан са блокирајућим прагом у `ci.yml` — метрика је +`quality-baseline.json`, али није повезан са блокирајућим механизмом постепеног пооштравања у `ci.yml` — метрика је тренутно неповезана. -### Спајање / уклањање дупликата (механички, мањи ризик) +### Спајање / уклањање дупликата (механички, нижи ризик) Сваки кандидат је проверен у односу на актуелно стање контролних механизама 2026-06-17 (веруј, али провери); -испоставило се да неколико „очигледних“ спајања скрива технички дуг и **нису** директне замене без додатних измена. +испоставило се да неколико „очигледних“ спајања прикрива технички дуг и **нису** непосредне замене без додатних измена. -- **`check:docs-sync` се покреће двапут** — самостално у послу `lint`, а затим поново унутар `check:docs-all` (`docs-sync-strict`) и husky pre-commit куке. ✅ **ЗАВРШЕНО** — самостално позивање у оквиру `lint` је уклоњено. -- **CVE скенирање** — ❌ **НИЈЕ чисто спајање.** `audit:deps` безусловно пада за сваки CVE високог/критичног нивоа; `check:vuln-ratchet` (osv) пада само при _регресији_ у односу на основну вредност (тренутно 1 MODERATE). Различита семантика — уклањањем `audit:deps` изгубила би се апсолутна контрола за високи/критични ниво. Задржати оба. -- **Откривање циклуса** — ❌ **НИЈЕ чисто спајање.** `check:circular-deps` (dpdm) пријављује **91 циклус** (због тога је саветодаван); не може постати блокирајући пре него што се они реше, а има и шири опсег од успешног, пажљиво одабраног `check:cycles`. Задржати `check:cycles` као блокирајући; решавање 91 dpdm циклуса представља засебну ставку заостатка. -- **Сложеност** — ✅ **ЗАВРШЕНО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): један ESLint пролаз, бројање по ruleId тако да основне вредности за цикломатску сложеност+максималан број линија и когнитивну сложеност остану независне; појединачне провере `check:complexity` / `check:cognitive-complexity` остају за локално покретање са `--update`. -- **Спречавање халуцинација за `/api`** — ✅ **ЗАВРШЕНО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): један FS инвентар директоријума `src/app/api`, openapi-routes + docs-symbols и даље извештавају независно; појединачне провере остају за локална покретања. -- **`check:node-runtime` се покреће у 11 послова** — ⚠️ **низак ROI.** Сваки се извршава на засебном покретачу, а провера траје <1s; укупна уштеда је ~10s, по цену губитка јефтине заштите по послу. Није вредно измена. -- **`typecheck:noimplicit:core` у CI lint-у** — ✅ **уклоњен из посла lint** (био је саветодаван са `continue-on-error`); блокирајућу површину типова чине `typecheck:core` + `check:type-coverage`. Локална скрипта је задржана. +- **`check:docs-sync` се покреће два пута** — самостално у послу `lint`, а затим поново унутар `check:docs-all` (`docs-sync-strict`) и husky pre-commit hook-а. ✅ **ЗАВРШЕНО** — самостално позивање у `lint` је уклоњено. +- **CVE скенирање** — ❌ **НИЈЕ чисто спајање.** `audit:deps` завршава неуспехом при било ком CVE-у високог/критичног нивоа; `check:vuln-ratchet` (osv) завршава неуспехом само при _регресији_ у односу на полазну вредност (тренутно 1 MODERATE). Различита семантика — уклањањем `audit:deps` изгубио би се апсолутни контролни механизам за висок/критичан ниво. Задржати оба. +- **Откривање циклуса** — ✅ **ЗАВРШЕНО** (#15159 G-01/G-02). Стари текст је овде називао `check:cycles` „зеленим, ручно одабраним“ контролним механизмом и оправдавао задржавање његовог блокирајућег статуса тиме што је `check:circular-deps` (dpdm) пријављивао 91 циклус. То зелено стање било је **лажно зелено**: `check:cycles` је скенирао 5 потдиректоријума (450 датотека), препознавао само статички образац `import|export … from` и одбацивао сваки спецификатор `@/` и `@omniroute/open-sse/`, па није могао да уочи циклусе динамичког увоза и алијаса који су доминирали репозиторијумом. Исправљено: контролни механизам сада обилази `src` + `open-sse` (5023 датотеке), прикупља спецификаторе из TypeScript AST-а (тако да се `import("…")` рачуна, док се `typeof import("…")` на позицији типа не рачуна) и разрешава `paths` из tsconfig-а. Проналази **14** циклуса, а не 0. Пошто 14 постојећих циклуса не може да се исправи у PR-у за контролни механизам, `check:cycles` је сада механизам **постепеног пооштравања** (`--ratchet`, горња граница `metrics.cycles.value = 14` у `quality-baseline.json`, `direction: down`) — блокира сваку _регресију_, а број може само да опада. CI покреће `npm run check:cycles:ratchet`. Постепено смањивање се спроводи у оквиру **A-01**. `check:circular-deps` (dpdm) остаје саветодаван као шире друго мишљење. +- **Сложеност** — ✅ **ЗАВРШЕНО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): један ESLint пролаз, бројање по ruleId-у како би полазне вредности за цикломатску сложеност+максималан број редова и когнитивну сложеност остале независне; појединачни `check:complexity` / `check:cognitive-complexity` остају за локални `--update`. +- **`/api` заштита од халуцинација** — ✅ **ЗАВРШЕНО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): један FS инвентар за `src/app/api`, openapi-routes + docs-symbols и даље извештавају независно; појединачне провере остају за локална покретања. +- **`check:node-runtime` се покреће у 11 послова** — ⚠️ **низак ROI.** Сваки се извршава на засебном runner-у, а провера траје <1s; укупна уштеда је ~10s, уз губитак јефтине заштите по послу. Није вредно ремећења. +- **`typecheck:noimplicit:core` у CI lint-у** — ✅ **уклоњено из lint посла** (било је саветодавно уз `continue-on-error`); блокирајућу површину типова чине `typecheck:core` + `check:type-coverage`. Локална скрипта је задржана. ### Променити статус / одлучити (политика оператера) -- `check:openapi-security-tiers` (саветодавно) — ❌ **НИЈЕ могуће чисто пребацити.** Завршава се кодом 0, али упозорава да неколико `traffic-inspector` рута под `LOCAL_ONLY_API_PREFIXES` нема анотацију `x-loopback-only: true`. Његово спровођење најпре захтева додавање тих анотација у `openapi.yaml`. -- `typecheck:noimplicit:core` (саветодавно) — у великој мери га обухвата блокирајући праг `check:type-coverage`. Пребацити га на праг или уклонити сувишни други `tsc` пролаз. -- `test:vitest:ui` (сада **блокирајући**) — раније постојећи неуспеси су изричито искључени у `vitest.config.ts` помоћу коментара за праћење `// #8618`; нови неуспеси обарају посао. -- `check:secrets` (gitleaks, блокирајући праг замрзнут на 3 документована лажно позитивна резултата) — додати та 3 резултата на листу дозвољених како би се достигла 0, или спустити проверу на саветодавни ниво. Преклапа се са GitHub-овим изворним скенирањем тајни + `check:public-creds`. -- `check:pr-evidence` (блокирајући, претражује прозни текст тела PR-а) — висок ризик од лажно позитивних резултата; уклањање слаби спровођење строгог правила бр. 18, па је ово стварна одлука о политици. -- `semgrep` (саветодавни самостални ток) — преклапа се са CodeQL-ом за OWASP породице; повезати његову основну вредност са прагом или га уклонити. +- `check:openapi-security-tiers` (саветодавно) — ❌ **НИЈЕ могуће чисто променити статус.** Завршава се кодом 0, али упозорава да неколико `traffic-inspector` рута под `LOCAL_ONLY_API_PREFIXES` нема анотацију `x-loopback-only: true`. Његово наметање прво захтева додавање тих анотација у `openapi.yaml`. +- `typecheck:noimplicit:core` (саветодавно) — у великој мери је обухваћено блокирајућим механизмом постепеног пооштравања `check:type-coverage`. Претворити га у механизам постепеног пооштравања или уклонити сувишни други `tsc` пролаз. +- `test:vitest:ui` (сада **блокирајуће**) — постојећи неуспеси су изричито изузети у `vitest.config.ts` уз коментаре за праћење `// #8618`; нови неуспеси обарају посао. +- `check:secrets` (gitleaks, блокирајући механизам постепеног пооштравања замрзнут на 3 документована лажно позитивна резултата) — додати та 3 резултата на листу дозвољених како би се достигла 0 или спустити статус на саветодавни. Преклапа се са GitHub-овим уграђеним скенирањем тајни + `check:public-creds`. +- `check:pr-evidence` (блокирајуће, претражује прозни текст PR-а) — висок ризик од лажно позитивних резултата; уклањање слаби спровођење строгог правила бр. 18, па је ово стварна одлука о политици. +- `semgrep` (саветодаван, самосталан) — преклапа се са CodeQL-ом за OWASP породице; повезати његову полазну вредност са механизмом постепеног пооштравања или га уклонити. --- diff --git a/docs/i18n/sr/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/sr/docs/compression/COMPRESSION_GUIDE.md index b1688b58d693..f75a36014a2b 100644 --- a/docs/i18n/sr/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/sr/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ const { messages: aged, saved } = applyAging(messages, { У телу које садржи `messages`, заобилажење на основу садржаја (`shouldBypassCavemanOutputMode()` у `open-sse/services/compression/outputMode.ts`) проверава последње три поруке и прескаче стилове за цео потез када се подударе са кључним речима за безбедност, неповратне радње, -појашњења или радње осетљиве на редослед. Заобилажење се извршава у складу са подешавањем -прекидача **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) на контролној табли. +појашњења или радње осетљиве на редослед. Заобилажење се извршава док је прекидач **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) на контролној табли укључен, што је подразумевана поставка; када је прекидач искључен, изабрани стилови важе и на тим потезима. Када заобилажење пропусти потез, `placeSystemInstruction()` (иста датотека), који никада не прави нови `messages[0]`, поставља блок на прво од следећих места које пронађе: diff --git a/docs/i18n/sr/docs/reference/ENVIRONMENT.md b/docs/i18n/sr/docs/reference/ENVIRONMENT.md index edc136230522..7ced09152478 100644 --- a/docs/i18n/sr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sr/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Обавезне тајне -Оне **морају** бити подешене пре првог покретања. Без њих, апликација ће или одбити да се покрене или ће радити са небезбедним подразумеваним вредностима. - -| Променљива | Обавезно | Подразумевано | Изворна датотека | Опис | -| ---------------------------- | --------------------- | ----------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Да** | _(нема)_ | `src/lib/auth` | Потписује/проверава све колачиће сесија контролне табле (JWT). Генеришите помоћу `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Да** | _(нема)_ | `src/lib/db/apiKeys.ts` | AES кључ за шифровање вредности API кључева ускладиштених у SQLite бази. Генеришите помоћу `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Да** | `CHANGEME` | Скрипта за почетно подешавање | Поставља почетну администраторску лозинку за контролну таблу (подудара се са подразумеваном вредношћу из `.env.example` — намерно је очигледно небезбедна како би се изнудила промена). **Промените је пре прве употребе.** Након пријављивања, промените је путем Контролна табла → Подешавања → Безбедност. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Да** (у продукцији) | _(није постављено)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Дељена тајна за интерни WebSocket мост Codex Responses. Потврђује аутентичност захтева моста између Electron/прегледачког WS релеја и OmniRoute-а. ⚠️ **ОБАВЕЗНО у продукцији — када није постављена, сви захтеви ка WS мосту се одбијају.** Генеришите помоћу `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Не | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Експлицитни идентификатор за поништавање кеша сервисног радника за PWA љуску (#11779); први у ланцу разрешавања. | -| `SOURCE_VERSION` | Не | _(није постављено)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Други у ланцу — постављају га PaaS системи за изградњу (нпр. у стилу Heroku-а) на примењени комит. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Не | _(изведено)_ | `src/shared/components/PwaRegister.tsx` | Јавна вредност у време изградње коју клијент користи за регистровање `/sw.js?v=…`; изводи се из претходне две вредности, а затим из git SHA вредности. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Не (аутоматски) | _(аутоматски при сваком покретању)_ | `src/server/authz/policies/management.ts` | Тајна по процесу која доказује да је ознака поуздане IP адресе равноправног чвора потекла са сопственог HTTP сервера OmniRoute-а (`scripts/dev/peer-stamp.mjs`). Посреднички слој за ауторизацију верује локалности захтева (ограничавање LOCAL_ONLY рута на повратну петљу/LAN) само када ознака садржи овај токен. Аутоматски се генерише при сваком покретању — оставите је неподешену; фиксирајте је само за вишепроцесна окружења која морају да деле ознаку. | -| `OMNIROUTE_TRUSTED_PROXIES` | Не | _(није постављено)_ | `scripts/dev/peer-stamp.mjs` | IP адресе или CIDR опсези обрнутих проксија, раздвојени зарезима, чијим вредностима `X-Forwarded-For` / `X-Real-IP` листа дозвољених/забрањених IP адреса може да верује, поред адреса повратне петље, адреса приватне мреже и Cloudflare рубних сервера, којима се увек верује. Потребно је само за прокси на некој другој јавној адреси; у супротном, филтер процењује сопствену адресу тог проксија. | +Непостављене тајне означене са **Да** аутоматски се генеришу при првом покретању (`JWT_SECRET`, `API_KEY_SECRET`) и чувају у `server.env` у директоријуму података — подесите их експлицитно када је тај директоријум привремен. Изузетак је `OMNIROUTE_WS_BRIDGE_SECRET`: она се не чува у `server.env` — улазне тачке сервера при сваком покретању креирају привремену вредност коју може користити само мост унутар процеса, те је спољни клијенти моста морају поставити експлицитно. + +| Променљива | Обавезно | Подразумевано | Изворна датотека | Опис | +| ---------------------------- | --------------------- | ----------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Да** | _(нема)_ | `src/lib/auth` | Потписује/проверава све колачиће сесија контролне табле (JWT). Генеришите помоћу `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Да** | _(нема)_ | `src/lib/db/apiKeys.ts` | AES кључ за шифровање вредности API кључева ускладиштених у SQLite бази. Генеришите помоћу `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Не | _(није постављено)_ | `src/lib/auth/managementPassword.ts` | Поставља лозинку контролне табле при покретању докле год ниједна није сачувана; игнорише се чим је лозинка сачувана. npm и инсталације из изворног кода копирају `.env.example` у `.env`, те почињу са његовом јавно познатом вредношћу-чуваром места `CHANGEME`: замените је пре првог покретања, или поставите `INITIAL_PASSWORD=` (празно) у том `.env` да бисте створили лозинку у чаробњаку за почетно подешавање при првој посети — исто као што чини непостављена вредност (подразумевана у Docker слици). Сачувану лозинку промените у Контролна табла → Подешавања → Безбедност, или покрените `omniroute reset-password` (`node bin/reset-password.mjs` у радној копији изворног кода) са `DATA_DIR` постављеним на директоријум података сервера. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Да** (у продукцији) | _(није постављено)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Дељена тајна за интерни WebSocket мост Codex Responses. Потврђује аутентичност захтева моста између Electron/прегледачког WS релеја и OmniRoute-а. ⚠️ **ОБАВЕЗНО у продукцији — када није постављена, сви захтеви ка WS мосту се одбијају.** Генеришите помоћу `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Не | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Експлицитни идентификатор за поништавање кеша сервисног радника за PWA љуску (#11779); први у ланцу разрешавања. | +| `SOURCE_VERSION` | Не | _(није постављено)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Други у ланцу — постављају га PaaS системи за изградњу (нпр. у стилу Heroku-а) на примењени комит. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Не | _(изведено)_ | `src/shared/components/PwaRegister.tsx` | Јавна вредност у време изградње коју клијент користи за регистровање `/sw.js?v=…`; изводи се из претходне две вредности, а затим из git SHA вредности. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Не (аутоматски) | _(аутоматски при сваком покретању)_ | `src/server/authz/policies/management.ts` | Тајна по процесу која доказује да је ознака поуздане IP адресе равноправног чвора потекла са сопственог HTTP сервера OmniRoute-а (`scripts/dev/peer-stamp.mjs`). Посреднички слој за ауторизацију верује локалности захтева (ограничавање LOCAL_ONLY рута на повратну петљу/LAN) само када ознака садржи овај токен. Аутоматски се генерише при сваком покретању — оставите је неподешену; фиксирајте је само за вишепроцесна окружења која морају да деле ознаку. | +| `OMNIROUTE_TRUSTED_PROXIES` | Не | _(није постављено)_ | `scripts/dev/peer-stamp.mjs` | IP адресе или CIDR опсези обрнутих проксија, раздвојени зарезима, чијим вредностима `X-Forwarded-For` / `X-Real-IP` листа дозвољених/забрањених IP адреса може да верује, поред адреса повратне петље, адреса приватне мреже и Cloudflare рубних сервера, којима се увек верује. Потребно је само за прокси на некој другој јавној адреси; у супротном, филтер процењује сопствену адресу тог проксија. | ### Команде за генерисање @@ -216,38 +216,38 @@ OmniRoute користи **SQLite** (преко `better-sqlite3`) за трај ## 4. Безбедност и аутентификација -| Променљива | Подразумевана вредност | Изворна датотека | Опис | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Со која се комбинује са хардверским идентификаторима ради генерисања машинског отиска. Промените је за сваку имплементацију ради изолације. | -| `OMNIROUTE_CLI_SALT` | _(није постављено = насумична со за сваку инсталацију, сачувана у `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC со за извођење локалног CLI токена за аутентификацију. Постављање ове вредности ротира све CLI токене на машини и увек има предност над сачуваном сољу. Погледајте `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Поставља ознаку `Secure` на колачићима сесије. **Мора бити `true`** када се извршава иза HTTPS-а. | -| `REQUIRE_API_KEY` | `false` | API посреднички софтвер | Када је `true`, сви `/v1/*` прокси захтеви морају да садрже важећи API кључ. **Ова ознака не контролише `GET /v1/models`**, који уместо тога прати поставку пријављивања контролне табле (`requireAuthForModels`) — стога `401` одговор са `/v1/models` НЕ значи да је инференција заштићена. Погледајте `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Омогућава приказивање пуних вредности API кључева у корисничком интерфејсу контролне табле. Може се конфигурисати у одељку за функционалне ознаке контролне табле; представља безбедносни ризик на дељеним инстанцама. | -| `NO_LOG_API_KEY_IDS` | _(празно)_ | `src/lib/compliance/index.ts` | Зарезима раздвојени ID-ови API кључева који заобилазе евидентирање захтева (усклађеност са GDPR-ом). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(није постављено = неограничено)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервни дневни буџет захтева који се примењује на API кључеве чија је колона `rate_limits` null. Није постављено или је празно: нема имплицитног ограничења (#2289, #11017). `0` има исто значење (неограничено). Позитиван цео број N омогућава N/дан, 5N/недељу, 20N/месец. Неисправне вредности које нису празне користе резервне застареле периоде од 1000/дан, 5000/недељу и 20000/месец. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Највећа дозвољена величина тела захтева. Одбија корисне садржаје који премашују ово ограничење. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Стварна тела захтева чија је величина једнака овом прагу или већа од њега преузимају атомски, процесно локални закуп за пријем захтевних захтева пре JSON рашчлањивања (BYTE путања, укључујући `POST /v1/responses`). Исти механизам за излаз при довољној резерви ресурса из [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) као код структурно захтевних захтева; и даље је ограничено помоћу `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), тако да се [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) не врати. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Строго ограничење за руте ћаскања које се примењује на број бајтова прочитаних током ограниченог уноса, укључујући захтеве са недостајућим, неважећим или нетачним `Content-Length`; прекорачење добија `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(није подешено — нема ограничења броја захтева)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-разгранавање:** ово застарело ограничење БРОЈА захтева сада важи само када је изричито подешено. Када није подешено (подразумевано), прихватање захтевних захтева за ћаскање уместо тога контролише `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — аутоматски изведен БАЈТНИ буџет одређен на основу стварног меморијског ограничења процеса у **једном процесу** (једна V8 хип меморија). Два преклапајућа `/v1/responses` захтева са око 750 хиљада токена обарају хип меморије од око 12 GiB (#7849) — упозорење о **меморијском буџету**, а не строго ограничење производа на 2. Здрав процес (хип меморија испод односа за одбацивање) МОЖЕ да прихвати више истовремених дугих `/v1/responses` путем `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десетине дуготрајних SSE клијената (40–50) зависе од хип меморије + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не од „максимално 2“. Слепо повећавање ове вредности ради „коришћења целог хоста“ поново уводи #7849. Умножавајте **хип меморије** помоћу **N независних `DATA_DIR` директоријума** (#11024); никада не користите `replicas>1` над једном SQLite датотеком. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(аутоматски изведено)_ | `src/shared/middleware/admissionBudget.ts` | **#503-разгранавање:** замена за аутоматски изведен бајтни буџет уноса (25% строжег V8/cgroup меморијског ограничења подељено са 8x пролазног увећања). Изведене и изричите вредности ограничавају се на 8 MiB–2 GiB. Тело веће од ефективног буџета одмах не успева са `413 body_exceeds_budget`; надметање између тела која се појединачно могу обрадити остаје поновљиво са `503`. 40–50 истовремених дуготрајних SSE клијената зависи од овог буџета + хип меморије, а не од строгог ограничења „максимално 2“. Пре подешавања прочитајте `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` на `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Однос за одбацивање услед притиска на хип меморију (`heapUsed / heap_size_limit`) при прихватању захтевних захтева типа BYTE и STRUCTURE (#10183, #10268, #10437). Истовремени захтевни захтев изнад `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` одбацује се са поновљивом грешком `503` само када је хип меморија ТАКОЂЕ на овом односу или изнад њега; при здравој хип меморији прихвата се путем додатног капацитета за здраво стање. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (подразумевано `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ограничени додатни капацитет за брзу путању при здравој хип меморији (#10437) и за STRUCTURE и за BYTE (`admitChatRequest`, укључујући тела ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без овог ограничења, сваки захтев при заузетом, али здравом хипу заобилазио је контролу прихватања без горње границе. Када је оволико истовремених закупа активно путем путање за здраву хип меморију, додатни захтеви током заузећа прелазе на ИСТУ путању ограниченог чекања/одбацивања која се користи при стварном притиску на хип меморију. `0` потпуно онемогућава заобилажење. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Број порука који захтев за ћаскање класификује као захтеван чак и када је његово тело испод бајтног прага. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Број алата који захтев за ћаскање класификује као захтеван чак и када је његово тело испод бајтног прага. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Конзервативна процена броја токена на основу величине ниске која класификује захтев као захтеван; ово је посредна мера трошка прихватања, а не токенизација коју добављач користи за наплату. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (онемогућено) | `src/shared/middleware/chatBodyAdmission.ts` | Опционо ограничење историје ћаскања које се изричито укључује. Подразумевано је онемогућено: број порука је смерница примене, а не универзално својство захтева, а постављање ограничења овде одбацује разговоре терминалном грешком `413` пре него што их процес компресије може учинити обрадивим. Раст хип меморије ограничен је помоћу `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` и одбацивања услед притиска на хип меморију. Подесите позитивну вредност у применама са ограниченом меморијом којима је потребна строга горња граница; прекорачење тада добија структурирани одговор `413` који захтева сажимање. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Строго ограничење за нестримујући одговор узводног сервиса који се у потпуности баферује у меморији. Након прекорачења, читач узводног сервиса се отказује и захтев брзо не успева, уместо да неограничена ниска расте док се хип меморија не исцрпи. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalan broj bajtova prosleđenih iz zaglavlja odgovora uzvodnog servera. Kada se budžet prekorači, zaglavlja nižeg prioriteta (npr. prilagođena `x-codex-*`, `x-oai-request-id`) izostavljaju se kako bi se ostalo u okviru uobičajenih ograničenja veličine zaglavlja reverznog proksija. Postavite višu vrednost da biste prosledili više uzvodnih metapodataka, po cenu veće veličine zaglavlja odgovora. | -| `CORS_ORIGIN` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | Zastarela CORS lista dozvoljenih pojedinačnih izvora. Za nova postavljanja koristite `CORS_ALLOWED_ORIGINS`. CORS je namenjen samo API klijentima pregledača koji koriste različite izvore; autentifikovani upisi na kontrolnoj tabli umesto toga koriste zahteve sa istog izvora uz CSRF zaštitu povezanu sa sesijom. | -| `CORS_ALLOWED_ORIGINS` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | CORS lista dozvoljenih izvora razdvojenih zarezima. Džoker se ne šalje osim ako je `CORS_ALLOW_ALL=true` izričito podešeno. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Zaobilazno rešenje isključivo za razvoj koje vraća bilo koji `Origin` pregledača. Nemojte ga omogućavati u deljenim ili produkcionim okruženjima. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blokira pozive ka provajderima koji ciljaju privatne, povratne ili link-lokalne opsege IP adresa. Onemogućite samo u izolovanim testnim okruženjima. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Dozvoljava URL-ove provajdera koji upućuju na privatne/lokalne mreže (localhost, 192.168.x.x, 10.x.x.x itd.). **OBAVEZNO za provajdere koji se samostalno hostuju** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Kada je `false`, kontrolna tabla odbija validaciju lokalnih URL-ova. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Dozvoljava dodavanje/validaciju provajdera na lokalnim/privatnim adresama (127.0.0.1, localhost, LAN, privatni opsezi) — ograničeno na putanju validacije provajdera. **Podrazumevano je `true`** (lokalno ima prednost); postavite na `false` da biste nametnuli strogo blokiranje svega osim javnih adresa. Krajnje tačke metapodataka u oblaku (169.254.169.254, metadata.google.internal) ostaju blokirane bez obzira na ovo podešavanje. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Omogućava da rute `/v1/audio/*` (transkripcije, govor, prevodi) koriste čvor provajdera kompatibilan sa OpenAI-jem koji je hostovan izvan localhost-a. Podrazumevano je isključeno — usmeravanje zvuka ka udaljenom hostu menja izlazni identitet i mora biti izričita odluka operatera. Povratni/privatni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvek su dozvoljeni i ovo podešavanje ne utiče na njih. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Omogućava da `POST /v1/rerank` (i povratni korak ponovnog rangiranja mehanizma memorije) koristi čvor provajdera kompatibilan sa OpenAI-jem koji je hostovan izvan localhost-a — uređaj na LAN-u ili Tailscale ravnopravni čvor koji izvršava TEI, Infinity, vLLM itd. Podrazumevano je isključeno — usmeravanje ka udaljenom hostu menja izlazni identitet i mora biti izričita odluka operatera. Povratni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvek su dozvoljeni i ovo podešavanje ne utiče na njih. Udaljeni čvorovi takođe moraju proći politiku odlaznih URL-ova provajdera (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); zahtevi se nikada ne usmeravaju ka hostovima metapodataka u oblaku. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kada je OIDC omogućen, onemogućava prijavljivanje lozinkom kako bi korisnici mogli da se autentifikuju isključivo putem OIDC jedinstvenog prijavljivanja. Prihvata se i osnovni alias `OIDC_DISABLE_PASSWORD_LOGIN`; zastavica funkcionalnosti kontrolne table sa istim ključem ima prioritet. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Osnovni alias za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Променљива | Подразумевана вредност | Изворна датотека | Опис | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Со која се комбинује са хардверским идентификаторима ради генерисања машинског отиска. Промените је за сваку имплементацију ради изолације. | +| `OMNIROUTE_CLI_SALT` | _(није постављено = насумична со за сваку инсталацију, сачувана у `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC со за извођење локалног CLI токена за аутентификацију. Постављање ове вредности ротира све CLI токене на машини и увек има предност над сачуваном сољу. Погледајте `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Поставља ознаку `Secure` на колачићима сесије. **Мора бити `true`** када се извршава иза HTTPS-а. | +| `REQUIRE_API_KEY` | `false` | API посреднички софтвер | Када је `true`, сви `/v1/*` прокси захтеви морају да садрже важећи API кључ. **Ова ознака не контролише `GET /v1/models`**, који уместо тога прати поставку пријављивања контролне табле (`requireAuthForModels`) — стога `401` одговор са `/v1/models` НЕ значи да је инференција заштићена. Погледајте `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Омогућава приказивање пуних вредности API кључева у корисничком интерфејсу контролне табле. Може се конфигурисати у одељку за функционалне ознаке контролне табле; представља безбедносни ризик на дељеним инстанцама. | +| `NO_LOG_API_KEY_IDS` | _(празно)_ | `src/lib/compliance/index.ts` | Зарезима раздвојени ID-ови API кључева који заобилазе евидентирање захтева (усклађеност са GDPR-ом). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(није постављено = неограничено)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервни дневни буџет захтева који се примењује на API кључеве чија је колона `rate_limits` null. Није постављено или је празно: нема имплицитног ограничења (#2289, #11017). `0` има исто значење (неограничено). Позитиван цео број N омогућава N/дан, 5N/недељу, 20N/месец. Неисправне вредности које нису празне користе резервне застареле периоде од 1000/дан, 5000/недељу и 20000/месец. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Највећа дозвољена величина тела захтева. Одбија корисне садржаје који премашују ово ограничење. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Стварна тела захтева чија је величина једнака овом прагу или већа од њега преузимају атомски, процесно локални закуп за пријем захтевних захтева пре JSON рашчлањивања (BYTE путања, укључујући `POST /v1/responses`). Исти механизам за излаз при довољној резерви ресурса из [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) као код структурно захтевних захтева; и даље је ограничено помоћу `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), тако да се [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) не врати. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Строго ограничење за руте ћаскања које се примењује на број бајтова прочитаних током ограниченог уноса, укључујући захтеве са недостајућим, неважећим или нетачним `Content-Length`; прекорачење добија `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(није подешено — нема ограничења броја захтева)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-разгранавање:** ово застарело ограничење БРОЈА захтева сада важи само када је изричито подешено. Када није подешено (подразумевано), прихватање захтевних захтева за ћаскање уместо тога контролише `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — аутоматски изведен БАЈТНИ буџет одређен на основу стварног меморијског ограничења процеса у **једном процесу** (једна V8 хип меморија). Два преклапајућа `/v1/responses` захтева са око 750 хиљада токена обарају хип меморије од око 12 GiB (#7849) — упозорење о **меморијском буџету**, а не строго ограничење производа на 2. Здрав процес (хип меморија испод односа за одбацивање) МОЖЕ да прихвати више истовремених дугих `/v1/responses` путем `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десетине дуготрајних SSE клијената (40–50) зависе од хип меморије + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не од „максимално 2“. Слепо повећавање ове вредности ради „коришћења целог хоста“ поново уводи #7849. Умножавајте **хип меморије** помоћу **N независних `DATA_DIR` директоријума** (#11024); никада не користите `replicas>1` над једном SQLite датотеком. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(аутоматски изведено)_ | `src/shared/middleware/admissionBudget.ts` | **#503-разгранавање:** замена за аутоматски изведен бајтни буџет уноса (25% строжег V8/cgroup меморијског ограничења подељено са 8x пролазног увећања). Изведене и изричите вредности ограничавају се на 8 MiB–2 GiB. Тело веће од ефективног буџета одмах не успева са `413 body_exceeds_budget`; надметање између тела која се појединачно могу обрадити остаје поновљиво са `503`. 40–50 истовремених дуготрајних SSE клијената зависи од овог буџета + хип меморије, а не од строгог ограничења „максимално 2“. Пре подешавања прочитајте `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` на `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Однос за одбацивање услед притиска на хип меморију (`heapUsed / heap_size_limit`) при прихватању захтевних захтева типа BYTE и STRUCTURE (#10183, #10268, #10437). Истовремени захтевни захтев изнад `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` одбацује се са поновљивом грешком `503` само када је хип меморија ТАКОЂЕ на овом односу или изнад њега; при здравој хип меморији прихвата се путем додатног капацитета за здраво стање. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (подразумевано `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Ограничени додатни капацитет за брзу путању при здравој хип меморији (#10437) и за STRUCTURE и за BYTE (`admitChatRequest`, укључујући тела ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без овог ограничења, сваки захтев при заузетом, али здравом хипу заобилазио је контролу прихватања без горње границе. Када је оволико истовремених закупа активно путем путање за здраву хип меморију, додатни захтеви током заузећа прелазе на ИСТУ путању ограниченог чекања/одбацивања која се користи при стварном притиску на хип меморију. `0` потпуно онемогућава заобилажење. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Број порука који захтев за ћаскање класификује као захтеван чак и када је његово тело испод бајтног прага. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Број алата који захтев за ћаскање класификује као захтеван чак и када је његово тело испод бајтног прага. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Конзервативна процена броја токена на основу величине ниске која класификује захтев као захтеван; ово је посредна мера трошка прихватања, а не токенизација коју добављач користи за наплату. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (онемогућено) | `src/shared/middleware/chatBodyAdmission.ts` | Опционо ограничење историје ћаскања које се изричито укључује. Подразумевано је онемогућено: број порука је смерница примене, а не универзално својство захтева, а постављање ограничења овде одбацује разговоре терминалном грешком `413` пре него што их процес компресије може учинити обрадивим. Раст хип меморије ограничен је помоћу `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` и одбацивања услед притиска на хип меморију. Подесите позитивну вредност у применама са ограниченом меморијом којима је потребна строга горња граница; прекорачење тада добија структурирани одговор `413` који захтева сажимање. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Строго ограничење за нестримујући одговор узводног сервиса који се у потпуности баферује у меморији. Након прекорачења, читач узводног сервиса се отказује и захтев брзо не успева, уместо да неограничена ниска расте док се хип меморија не исцрпи. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maksimalan broj bajtova prosleđenih iz zaglavlja odgovora uzvodnog servera. Kada se budžet prekorači, zaglavlja nižeg prioriteta (npr. prilagođena `x-codex-*`, `x-oai-request-id`) izostavljaju se kako bi se ostalo u okviru uobičajenih ograničenja veličine zaglavlja reverznog proksija. Postavite višu vrednost da biste prosledili više uzvodnih metapodataka, po cenu veće veličine zaglavlja odgovora. | +| `CORS_ORIGIN` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | Zastarela CORS lista dozvoljenih pojedinačnih izvora. Za nova postavljanja koristite `CORS_ALLOWED_ORIGINS`. CORS je namenjen samo API klijentima pregledača koji koriste različite izvore; autentifikovani upisi na kontrolnoj tabli umesto toga koriste zahteve sa istog izvora uz CSRF zaštitu povezanu sa sesijom. | +| `CORS_ALLOWED_ORIGINS` | _(nije postavljeno)_ | `src/server/cors/origins.ts` | CORS lista dozvoljenih izvora razdvojenih zarezima. Džoker se ne šalje osim ako je `CORS_ALLOW_ALL=true` izričito podešeno. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Zaobilazno rešenje isključivo za razvoj koje vraća bilo koji `Origin` pregledača. Nemojte ga omogućavati u deljenim ili produkcionim okruženjima. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Legacy alias. Njegov prekidač na kontrolnoj tabli (DB override) čita se pre vrednosti ove environment promenljive; `false`, `0`, `no` ili `off` u bilo kojem od njih isključuje provere hostova isto kao `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, čak i kada je prekidač na tabli te zastavice sačuvan kao isključen; ostale vrednosti prepštaju zaštitu dvema zastavicama ispod. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Isključuje provere hostova zaštite odlaznih URL-ova, uključujući blokadu metapodataka u oblaku (ceo opseg 169.254.0.0/16, metadata.google.internal), na putanjama provajdera koje prate režim zaštite: validacija URL-ova, otkrivanje modela, bazni URL-ovi čvorova provajdera (uključujući udaljene čvorove za re-ranking) i test proxy-fallbacka, među ostalim odlaznim putanjama vezanim za provajdere, poput udaljenog preuzimanja slika/medija. Takođe dozvoljava privatne webhook ciljeve, osim krajnjih tačaka metapodataka u oblaku/link-lokalnih, koje ostaju blokirane i uz uključenu zastavicu. Zahtevi za chat poslati kroz `BaseExecutor.execute()` zadržavaju svoju proveru metapodataka u oblaku. Lokalni provajderi rade sa podrazumevanim vrednostima: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (podrazumevano `true`) već dozvoljava lokalne i LAN URL-ove na putanjama validacije, otkrivanja i čvorova provajdera; test proxy-fallbacka i privatni webhook ciljevi konsultuju samo ovu zastavicu i blokiraju lokalne/LAN hostove dok je isključena. Zaobilazi `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Vrednost sačuvana sa prekidača na kontrolnoj tabli ima prednost nad ovom promenljivom. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Dozvoljava URL-ove provajdera na lokalnim/privatnim adresama (127.0.0.1, localhost, LAN, privatni opsezi) za validaciju provajdera, otkrivanje modela, chat i ostale odlazne putanje vezane za provajdere kojima upravlja režim zaštite. **Podrazumevano je `true`** (lokalno ima prednost): zaštita tada blokira krajnje tačke metapodataka u oblaku — ceo opseg 169.254.0.0/16 plus poznata imena hostova metapodataka (metadata.google.internal, metadata.goog, IMDS za Alibaba/Azure/Oracle, fd00:ec2::254). Postavite `false` za režim samo-javno, koji blokira i privatne i povratne hostove. Zaštita proverava ime hosta ili IP literal onako kako je zapisano, pre bilo kakvog DNS upita; u režimu samo-javno omotač sigurnog preuzimanja dodatno razrešava ime hosta i odbija bilo koji privatni odgovor. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` isključuje ove provere na putanjama navedenim u redu te zastavice. Ugrađeni lokalni provajderi, poput LM Studio, Ollama, vLLM, Llamafile i Triton, preskaču sve provere hostova pri validaciji ključeva i chatu. Vrednost sačuvana sa prekidača na kontrolnoj tabli ima prednost nad ovom promenljivom. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Omogućava da rute `/v1/audio/*` (transkripcije, govor, prevodi) koriste čvor provajdera kompatibilan sa OpenAI-jem koji je hostovan izvan localhost-a. Podrazumevano je isključeno — usmeravanje zvuka ka udaljenom hostu menja izlazni identitet i mora biti izričita odluka operatera. Povratni/privatni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvek su dozvoljeni i ovo podešavanje ne utiče na njih. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Omogućava da `POST /v1/rerank` (i povratni korak ponovnog rangiranja mehanizma memorije) koristi čvor provajdera kompatibilan sa OpenAI-jem koji je hostovan izvan localhost-a — uređaj na LAN-u ili Tailscale ravnopravni čvor koji izvršava TEI, Infinity, vLLM itd. Podrazumevano je isključeno — usmeravanje ka udaljenom hostu menja izlazni identitet i mora biti izričita odluka operatera. Povratni čvorovi (localhost, 127.0.0.1, 172.16-31.x) uvek su dozvoljeni i ovo podešavanje ne utiče na njih. Bazni URL udaljenog čvora takođe mora proći politiku odlaznih URL-ova provajdera (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kada je OIDC omogućen, onemogućava prijavljivanje lozinkom kako bi korisnici mogli da se autentifikuju isključivo putem OIDC jedinstvenog prijavljivanja. Prihvata se i osnovni alias `OIDC_DISABLE_PASSWORD_LOGIN`; zastavica funkcionalnosti kontrolne table sa istim ključem ima prioritet. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Osnovni alias za `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Kontrolna lista za bezbednosno ojačavanje diff --git a/docs/i18n/sv/CONTRIBUTING.md b/docs/i18n/sv/CONTRIBUTING.md index f56ed565dc97..0b5c86311272 100644 --- a/docs/i18n/sv/CONTRIBUTING.md +++ b/docs/i18n/sv/CONTRIBUTING.md @@ -389,20 +389,20 @@ Skriv enhetstester i `tests/unit/` som minst omfattar: ## Checklista för pull request -- [ ] Tester godkänns (`npm test`) -- [ ] Lintning godkänns (`npm run lint`) +- [ ] Testerna godkänns (`npm test`) +- [ ] Lintningen godkänns (`npm run lint`) - [ ] Bygget lyckas (`npm run build`) - [ ] TypeScript-typer har lagts till för nya publika funktioner och gränssnitt - [ ] Inga hårdkodade hemligheter eller reservvärden -- [ ] Publika autentiseringsuppgifter för uppströmsresurser bäddas in via `resolvePublicCred()` (se [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), aldrig som literaler -- [ ] Felsvar dirigeras via `buildErrorBody()` / `sanitizeErrorMessage()` — inga råa stackspårningar i svarskroppar (se [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Skalkommandon (`exec` / `spawn`) skickar värden vid körning via `env`, inte via stränginterpolering +- [ ] Publika autentiseringsuppgifter för uppströmskällor bäddas in via `resolvePublicCred()` (se [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), aldrig som literaler +- [ ] Felsvar hanteras via `buildErrorBody()` / `sanitizeErrorMessage()` — inga råa stackspårningar i svarskroppar (se [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Skalkommandon (`exec` / `spawn`) skickar körtidsvärden via `env`, inte via stränginterpolering - [ ] Alla indata valideras med Zod-scheman -- [ ] Ett **fragment** för ändringsloggen har lagts till under `changelog.d/{features|fixes|maintenance}/-.md` för användarsynliga ändringar (se [`changelog.d/README.md`](./changelog.d/README.md)) — redigera **inte** `CHANGELOG.md` direkt; fragment sammanställs vid lansering och orsakar aldrig konflikter mellan pull requests +- [ ] Ett **fragment** för ändringsloggen har lagts till under `changelog.d/{features|fixes|maintenance}/-.md` för användarsynliga ändringar (se [`changelog.d/README.md`](./changelog.d/README.md)) — redigera **inte** `CHANGELOG.md` direkt; fragmenten sammanställs vid lansering och orsakar aldrig konflikter mellan pull requests - [ ] Dokumentationen har uppdaterats (om tillämpligt) -- [ ] Inga nya CodeQL-varningar eller varningar från hemlighetsskanning har skapats, alternativt har var och en avfärdats med en teknisk motivering som hänvisar till relevant dokument i `docs/security/` -- [ ] Rutter som startar underprocesser (`/api/mcp/`, `/api/cli-tools/runtime/`) har klassificerats som `isLocalOnlyPath()` i `src/server/authz/routeGuard.ts` — se [hård regel nr 15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Inga `Co-Authored-By`-slutrader i commit-meddelanden — commits måste visas enbart under databasägarens Git-identitet (hård regel nr 16) +- [ ] Inga nya CodeQL-/Secret-Scanning-varningar har skapats, eller så har var och en avfärdats med en teknisk motivering som hänvisar till relevant dokument under `docs/security/` +- [ ] Rutter som startar underordnade processer (`/api/mcp/`, `/api/cli-tools/runtime/`) har klassificerats som `isLocalOnlyPath()` i `src/server/authz/routeGuard.ts` — se [Hård regel nr 15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Inga `Co-authored-by`-trailrar för AI/botar i commit-meddelanden (Hård regel nr 16) — mänskliga medarbetare vars arbete återanvänds krediteras med vanliga `Co-authored-by: Name `-trailrar --- diff --git a/docs/i18n/sv/docs/architecture/QUALITY_GATES.md b/docs/i18n/sv/docs/architecture/QUALITY_GATES.md index 5e3403912c35..15787e8015e4 100644 --- a/docs/i18n/sv/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/sv/docs/architecture/QUALITY_GATES.md @@ -15,27 +15,27 @@ plan för att replikera samma system finns i --- -## Inventering av grindar och körningsprofiler +## Inventering av kontrollsteg och körningsprofiler ### Godkännande av kandidater -Arbetsflödena CI och Quality Gates avger varsitt stabilt utfall: `Gate / CI` och -`Gate / Quality`. Deras versionshanterade godkännandepolicy anger varje uppströmsjobb +Arbetsflödena CI och Quality Gates avger vart och ett ett stabilt utfall: `Gate / CI` och +`Gate / Quality`. Deras versionshanterade godkännandepolicy listar varje uppströmsjobb som obligatoriskt eller rådgivande. Ett tillämpligt obligatoriskt jobb måste lyckas: saknade, avbrutna, överhoppade, väntande och okända resultat kan inte fastställa PASS. En giltig -klassificering som endast dokumentation eller endast katalog kan göra ett kodspår ej tillämpligt; -ett utkast till PR godtas inte som kandidat. En `hotfix`-etikett innebär inte att beviskraven frångås. - -Båda arbetsflödena omfattar PR:er och pushar till main-/release-grenar, manuell körning och -merge-group-händelser. Push, manuell körning och merge-group kör hela urvalet. Förgreningar -och merge-grupper använder värdbaserade körmiljöer för jobb som annars väljer lokalt driftade -körmiljöer; tillräcklig värdbaserad kapacitet måste verifieras före utrullning. - -Varje JSON-kvitto identifierar utcheckad SHA, arbetsflödeskörning och försök. -CLI:t avvisar om SHA för utcheckning och händelse inte matchar. Arbetsflödestester binder policymedlemskap -till utfallsjobbets `needs`-lista så att ett nytt eller borttaget spår inte kan försvinna obemärkt. -Kvittona omfattar det egna arbetsflödet, inte publicering, driftsättning eller de interna delarna -av en befintlig rådgivande skanner. Att aktivera båda kontrollnamnen i grenreglerna är en +klassificering som enbart dokumentation eller enbart katalog kan göra ett kodspår ej tillämpligt; +en PR i utkastläge är inte en godkänd kandidat. Etiketten `hotfix` innebär inte att beviskrav frångås. + +Båda arbetsflödena omfattar PR:er och pushar till main/release-grenar, manuell start och +merge-group-händelser. Push, manuell start och merge-group kör hela urvalet. Forkar +och merge groups använder värdbaserade runners för jobb som annars väljer självhanterade +runners; tillräcklig värdbaserad kapacitet måste verifieras före utrullning. + +Varje JSON-kvitto identifierar utcheckad SHA, arbetsflödeskörning och körningsförsök. +CLI-verktyget avvisar om SHA för utcheckning och händelse inte matchar. Arbetsflödestester binder policymedlemskap +till utfallsjobbets `needs`-lista så att ett nytt eller borttaget spår inte obemärkt kan försvinna. +Kvittona omfattar det egna arbetsflödet, inte publicering, driftsättning eller den interna funktionen +hos en befintlig rådgivande skanner. Att aktivera båda kontrollnamnen i grenregler är en separat administrativ ändring; att lägga till dessa jobb skyddar inte i sig en gren. ### Inventering av statisk skanning @@ -43,45 +43,45 @@ separat administrativ ändring; att lägga till dessa jobb skyddar inte i sig en Den versionshanterade inventeringen av npm-alias och medlemskapet för statisk skanning finns i `config/quality/gate-manifest.json`. Kör `npm run check:gate-manifest` för att validera skriptnamn och exakta kommandon mot `package.json`; tillägg, borttagningar och -kommandoavvikelser gör att både den lokala kroken och jobb för ändringsklassificering i CI misslyckas. +kommandoavvikelser gör att både den lokala hooken och ändringsklassificeringsjobben i CI misslyckas. Ett alias är inte ett arbetsflödesjobb, en matrisinstans eller ett testfall: dessa antal får inte framställas som utbytbara. Använd `npm run quality:scan -- --list` eller `npm run quality:scan:fast -- --list` -för att granska de valda aliasen utan att köra dem. Körmiljön anropar -npm-startpunkten, så dess exekveringsmiljö (inklusive Bun där det konfigurerats) bevaras. +för att inspektera valda alias utan att köra dem. Runnern anropar +npm-startpunkten, så dess exekveringsmiljö (inklusive Bun där det är konfigurerat) bevaras. Manifestet registrerar alias utanför dessa profiler som separat anropade, och underhållskommandon är förbjudna i skrivskyddade skanningsprofiler. Dessa profiler omfattar endast den statiska skanningen. De certifierar inte produkttester, -täckning, paketering, externa kontroller eller en kandidats fullständiga releasegodkännande. -Godkännande i arbetsflödet använder länkade `config/quality/admission-policy.json` och -`scripts/quality/admission-verdict.mjs`. Profiler för releaseövervakning förblir separata; -granska deras tillämpliga kontroller och kvitton oberoende av varandra. Textinventeringen -nedan är en referens, inte ett bevis på att en grind faktiskt kördes. +täckning, paketering, externa kontroller eller en kandidats fullständiga versionsgodkännande. +Arbetsflödets godkännande använder den länkade `config/quality/admission-policy.json` och +`scripts/quality/admission-verdict.mjs`. Profiler för versionsövervakning förblir separata; +inspektera deras tillämpliga kontroller och kvitton oberoende. Textinventeringen +nedan är en referens, inte ett bevis på att ett kontrollsteg faktiskt kördes. -Skript finns under `scripts/check/` (policygrindar) och `scripts/quality/` (ratchet-motor). +Skript finns under `scripts/check/` (policykontroller) och `scripts/quality/` (ratchet-motor). Sanningskällan för CI är `.github/workflows/ci.yml`. -### Snabbspår för release-PR (`quality.yml`) +### Snabbspår för versions-PR (`quality.yml`) -`.github/workflows/quality.yml` kompletterar CI för PR:er mot main-/release-grenar, pushar till -skyddade grenar, manuella körningar och merge-grupper. PR:er använder sökvägsfiltrerade snabbkontroller. Det permanent -inaktiverade duplicerade bygget har tagits bort; de riktiga kontrollerna för bygge/paketering/uppstart finns kvar i CI. +`.github/workflows/quality.yml` kompletterar CI för PR:er mot main/release, pushar till skyddade grenar, +manuell start och merge groups. PR:er använder sökvägsfiltrerade snabbkontroller. Det permanent +inaktiverade duplicerade bygget har tagits bort; de verkliga kontrollerna för bygge/paketering/uppstart finns kvar i CI. -| Jobb | Omfattning | Blockerande | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `Docs Gates (fast-path)` | Dokumentations-/kod-PR:er; referenser i API-dokumentation och all dokumentation | Ja | -| `Fast Quality Gates` | Kod-PR:er; statiska kontroller, typkontroll, typkontroll av kontrollpanelen, påverkade enhetstester | Ja | -| `Forgotten sibling tests` | Kod-PR:er; ändrade moduler spåras till statiska konsumenter och möjliga syskontroller; sökvägar via samlingsfiler och dynamisk import rapporteras som rådgivande diagnostik, med refererade undantag i tillåtelselistan | **Rådgivande** | -| `Vitest (fast-path)` | Kod-PR:er; snabb Vitest-svit | Ja | -| `Unit Tests fast-path` | Kod-PR:er; enhetstestsvit med 4 shards | Ja | -| `No new ESLint warnings` | Kod-PR:er; undertryckningsmedvetet lint-skydd | Ja, inklusive förgreningar | -| `Merge integrity (changelog + generated skills)` | PR:er som inte är utkast; synkronisering av ändringslogg och genererade färdigheter | Ja, inklusive förgreningar | +| Job | Omfattning | Blockerande | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | +| `Docs Gates (fast-path)` | Dokumentations-/kod-PR:er; referenser i API-dokumentation och docs-all | Ja | +| `Fast Quality Gates` | Kod-PR:er; statiska kontroller, typkontroll, typkontroll av dashboard, påverkade enhetstester | Ja | +| `Forgotten sibling tests` | Kod-PR:er; ändrade moduler spåras till statiska konsumenter och möjliga syskontroller; barrel- och dynamic-import-sökvägar rapporteras som rådgivande diagnostik, med refererade undantag i tillåtelselistan | **Rådgivande** | +| `Vitest (fast-path)` | Kod-PR:er; snabb Vitest-svit | Ja | +| `Unit Tests fast-path` | Kod-PR:er; enhetstestsvit med 4 shards | Ja | +| `No new ESLint warnings` | Kod-PR:er; undertryckningsmedveten lint-kontroll | Ja, inklusive forkar | +| `Merge integrity (changelog + generated skills)` | PR:er som inte är utkast; synkronisering av ändringslogg och genererade skills | Ja, inklusive forkar | -#### Rapport om glömda syskontroller +#### Rapport över bortglömda syskontroller -`npm run check:forgotten-sibling-tests` återanvänder importmatcharen bakom kartan över testpåverkan. +`npm run check:forgotten-sibling-tests` återanvänder importlösaren bakom kartan över testpåverkan. För varje ändrad produktionsmodul rapporterar den deterministiska kedjor av typen `ändrad modul/symbol -> statisk konsument -> möjlig syskontroll` när den möjliga kontrollen saknas i pull request-diffen. Markdown-sammanfattningen och JSON-resultatet bevaras som @@ -89,138 +89,138 @@ arbetsflödesartefakten `forgotten-sibling-tests` för kalibrering före en even Barrel-återexporter och dynamiska importer används endast för upplösningsdiagnostik; de skapar aldrig ett blockerande fynd. Granskade undantag finns i -`config/quality/forgotten-sibling-allowlist.json`. Varje post måste ange konsumenten och det föreslagna -testet, ge en specifik motivering och länka till ett GitHub-ärende eller en pull request. Felaktiga poster -avvisas. Undantag kan inte undertrycka ett borttaget föreslaget test eller en diff som lägger till `.skip`/`.todo`; -försvagning av kontroller och annan maskering hanteras fortsatt av den oberoende blockerande -`check:test-masking`-kontrollen. +`config/quality/forgotten-sibling-allowlist.json`. Varje post måste ange konsumenten och det möjliga +testet, ge en specifik motivering och länka till ett GitHub-ärende eller en pull request. Felaktigt utformade poster leder +till blockering. Undantag kan inte undertrycka ett borttaget möjligt test eller en diff som lägger till `.skip`/`.todo`; +försvagning av assertioner och annan maskering hanteras fortsatt av den oberoende blockerande +`check:test-masking`-grinden. ### Jobb: `lint` Körs för varje PR till `main`. Blockerar sammanslagning vid fel. -| Skript (`npm run ...`) | Validerar | Blockerande | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | Node.js-versionen ligger inom det intervall som stöds | Ja | -| `check:cycles` | Cirkulära importer — alla moduler i `src/` + `open-sse/` | Ja | -| `check:route-validation:t06` | Zod-scheman finns för alla routes (policy för nivå 6) | Ja | -| `check:any-budget:t11` | Antalet `@ts-expect-error // any` överskrider inte budgeten (nivå 11-spärr) | Ja | -| `check:provider-consistency` | Varje leverantör i `providers.ts` har en motsvarande post i `providerRegistry.ts` (och vice versa, inom tillåtelselistan) | Ja | -| `check:model-lifecycle` | De tre manuellt underhållna routningstabellerna förblir konsekventa med den incheckade livscykelögonblicksbilden (#11503): `FITNESS_TABLE` (`taskFitness.ts`) poängsätter inget utfasat id som `REGISTRY` kan routa; varje mål för `BUILT_IN_ALIASES` finns i `REGISTRY` och saknas i ögonblicksbilden över utfasade id:n; varje utfasat id som fortfarande finns i `REGISTRY` vidarebefordras eller listas i `allowedRetiredInCatalog`; och varken källor eller mål i `DEFAULT_DEGRADATION_MAP` förekommer som utfasade i den ögonblicksbilden. Detta bevisar inte att en modell för närvarande tillhandahålls av en aktiv uppströmsleverantör. Offline — jämför med `config/quality/model-lifecycle.json`, som uppdateras manuellt med `npm run quality:refresh-model-lifecycle` (nätverk; inte integrerat i CI). `allowedRetiredInCatalog` är en stegvis avvecklingsspärr: lägg endast till en post med ett spårningsärende. | Ja | -| `check:fetch-targets` | Varje `fetch("/api/...")` i `src/` på klientsidan leder till en faktisk `route.ts` | Ja | -| `check:deps` | Alla beroenden som kan installeras med `npm install` i varje `package.json` i kodförrådet finns i `dependency-allowlist.json`; nya ofästa eller felskrivningskapade paket flaggas | Ja | -| `audit:deps` | `npm audit` (rot + Electron) — inga allvarliga/kritiska säkerhetsmeddelanden (överlappar OSV-kontrollen `check:vuln-ratchet`; se rationaliseringsbackloggen) | Ja | -| `check:lockfile` | Integriteten för `package-lock.json` — HTTPS-register, integritetshashar, inga värdåsidosättningar | Ja | -| `check:licenses` | SPDX-licenstillåtelselista för produktionsberoenden | Ja | -| `check:tracked-artifacts` | Inga byggartefakter/incheckade symboliska länkar till `node_modules` (körs även i husky pre-commit; pre-push är avsiktligt lättviktig — #6716) | Ja | -| `check:ai-attribution` | Ingen AI-/bot-`Co-Authored-By`-trailer eller AI-genererad sidfot i PR-commits, titel eller beskrivning — hård regel #16 (i snabbslingan i `quality.yml` för PR→`release/**` — läser händelsens nyttolast, gör inget utanför PR:er — samt ett steg endast för PR:er i lint-jobbet i `ci.yml` för PR→`main`; även husky-hooken `commit-msg`; mänskliga medförfattare är tillåtna; #14436) | -| `check:vitest-exclusions` | Varje Vitest-undantag anger ett spårningsärende och finns i `config/quality/vitest-exclusions.json` (#13204) | Ja | -| `check:file-size` | Ingen källfil överskrider gränsen för respektive filändelse (spärr: frysta stora filer i listan `frozen`) | Ja | -| `check:error-helper` | Felsvar i exekverare/hanterare använder `buildErrorBody()`/`sanitizeErrorMessage()` (hård regel #12) | Ja | -| `check:migration-numbering` | SQL-migreringsfiler är sekventiellt numrerade, utan luckor eller dubbletter | Ja | -| `check:public-creds` | Inga literala OAuth-`client_id`/`client_secret`-värden eller Firebase-webbnycklar utanför `publicCreds.ts` (strikt regel nr 11) | Ja | -| `check:db-rules` | Ingen rå SQL utanför modulerna i `src/lib/db/`; inga barrel-importer från `localDb.ts` (strikta regler nr 2/nr 5) | Ja | -| `check:known-symbols` | Provider-exekverare, routningsstrategier och översättare som är registrerade i sina dispatch-tabeller matchar filerna på disk — inga föräldralösa eller odeklarerade symboler | Ja | -| `check:route-guard-membership` | Varje route som startar en underordnad process klassificeras av `isLocalOnlyPath()` (strikta regler nr 15/nr 17) | Ja | -| `check:test-discovery` | Varje `*.test.ts`- eller `*.spec.ts`-fil i repot samlas in av minst en test runner (spärrmekanism: listan över föräldralösa filer i `test-discovery-baseline.json` kan endast krympa) | Ja | -| `check:agent-skills-sync` | Genererade agent-skills-artefakter matchar sin källkatalog (ingen avvikelse) | -| `check:provider-asset-provenance` | Leverantörslogotyper/-resurser har en registrerad provenienspost | -| `lint:json` | JSON-konfigurationsfiler kan tolkas och uppfyller repo-lintreglerna | -| `typecheck:core` | TypeScript-kompilering utan fel (endast rådgivande varningar) | Ja | -| `typecheck:noimplicit:core` | Strikt `noImplicitAny` — framåtblickande; många befintliga anropsplatser behöver fortfarande annoteringar | **Rådgivande** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` begränsad till `src/app/(dashboard)/**` (#7033) — den kuraterade listan med 27 tillåtna filer i `typecheck:core` omfattar ingen TSX för instrumentpanelen, och `next build` typkontrollerar den inte heller (`next.config.mjs` anger `ignoreBuildErrors: true`), så regressioner med fristående identifierare där (#6625/#6909) var osynliga för CI. Jämför mot en fryst baslinje för antal per fil/per TS-kod (`config/quality/dashboard-typecheck-baseline.json`, samma mönster för kontroll av inaktualitet som `check:known-symbols`) — endast NYA fel utöver det baslinjedefinierade antalet gör att kontrollen misslyckas; skruva ned baslinjen med `--update` när ett befintligt fel åtgärdas. | Ja | +| Skript (`npm run ...`) | Validerar | Blockerande | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | +| `check:node-runtime` | Node.js-versionen ligger inom det intervall som stöds | Ja | +| `check:cycles` | Cirkulära importer i hela `src/` + `open-sse/` (AST-baserat, `paths` i tsconfig löses upp). Utan argument = rådgivande, listar cyklerna. `check:cycles:ratchet` (det som CI kör) blockerar när antalet överstiger taket `metrics.cycles` i `quality-baseline.json` — för närvarande 14, `direction: down`, så det kan bara minska (#15159 G-01/G-02) | Ja (ratchet) | +| `check:route-validation:t06` | Zod-scheman finns på alla rutter (policy för nivå 6) | Ja | +| `check:any-budget:t11` | Antalet `@ts-expect-error // any` överskrider inte budgeten (catraca för nivå 11) | Ja | +| `check:provider-consistency` | Varje leverantör i `providers.ts` har en motsvarande post i `providerRegistry.ts` (och vice versa, inom tillåtelselistan) | Ja | +| `check:model-lifecycle` | De tre manuellt underhållna routningstabellerna förblir konsekventa med den incheckade livscykelögonblicksbilden (#11503): `FITNESS_TABLE` (`taskFitness.ts`) poängsätter inget utfasat id som `REGISTRY` kan routa; varje mål för `BUILT_IN_ALIASES` finns i `REGISTRY` och saknas i ögonblicksbilden över utfasade id:n; varje utfasat id som fortfarande finns i `REGISTRY` vidarebefordras eller listas i `allowedRetiredInCatalog`; och ingen källa eller destination i `DEFAULT_DEGRADATION_MAP` visas som utfasad i den ögonblicksbilden. Detta bevisar inte att en modell för närvarande tillhandahålls av en aktiv uppströmsleverantör. Offline — jämför mot `config/quality/model-lifecycle.json`, som uppdateras manuellt med `npm run quality:refresh-model-lifecycle` (nätverk; inte inkopplat i CI). `allowedRetiredInCatalog` är en stegvis avvecklingsspärr: lägg endast till en post med ett spårningsärende. | Ja | +| `check:fetch-targets` | Varje `fetch("/api/...")` i `src/` på klientsidan matchar en faktisk `route.ts` | Ja | +| `check:deps` | Alla beroenden som kan installeras med `npm install` i varje `package.json` i kodförrådet finns i `dependency-allowlist.json`; nya ofästa eller felskrivningskapade paket flaggas | Ja | +| `audit:deps` | `npm audit` (rot + electron) — inga varningar med hög/kritisk allvarlighetsgrad (överlappar osv `check:vuln-ratchet`; se rationaliseringslistan) | Ja | +| `check:lockfile` | Integritet för `package-lock.json` — https-register, integritetshashar, inga värdåsidosättningar | Ja | +| `check:licenses` | SPDX-licenstillåtelselista för produktionsberoenden | Ja | +| `check:tracked-artifacts` | Inga byggartefakter/incheckade symboliska länkar till `node_modules` (körs även i huskys pre-commit; pre-push är avsiktligt lättviktig — #6716) | Ja | +| `check:ai-attribution` | Ingen AI-/bot-`Co-Authored-By`-trailer eller AI-genererad sidfot i PR-commits, titel eller beskrivning — hård regel #16 (i `quality.yml`:s fast-gates-loop för PR→`release/**` — läser händelsens nyttolast, gör inget utanför PR:er — samt ett PR-specifikt steg i `ci.yml`:s lint för PR→`main`; även huskys `commit-msg`-hook; mänskliga medförfattare tillåts; #14436) | +| `check:vitest-exclusions` | Varje Vitest-undantag anger ett spårningsärende och finns i `config/quality/vitest-exclusions.json` (#13204) | Ja | +| `check:file-size` | Ingen källkodsfil överskrider gränsen per filändelse (spärrmekanism: frysta stora filer i listan `frozen`) | Ja | +| `check:error-helper` | Felsvar i exekverare/hanterare använder `buildErrorBody()`/`sanitizeErrorMessage()` (hård regel #12) | Ja | +| `check:migration-numbering` | SQL-migreringsfiler är sekventiellt numrerade, utan luckor eller dubbletter | Ja | +| `check:public-creds` | Inga bokstavliga OAuth-värden för `client_id`/`client_secret` eller Firebase Web-nycklar utanför `publicCreds.ts` (Hård regel #11) | Ja | +| `check:db-rules` | Ingen rå SQL utanför modulerna i `src/lib/db/`; inga barrel-importer från `localDb.ts` (Hårda regler #2/#5) | Ja | +| `check:known-symbols` | Provider-exekverare, routningsstrategier och översättare som är registrerade i sina dispatch-tabeller matchar filerna på disken – inga föräldralösa eller odeklarerade symboler | Ja | +| `check:route-guard-membership` | Varje route som startar en underordnad process klassificeras av `isLocalOnlyPath()` (Hårda regler #15/#17) | Ja | +| `check:test-discovery` | Varje `*.test.ts`- / `*.spec.ts`-fil i repot samlas in av minst en testkörare (spärrmekanism: listan över föräldralösa filer i `test-discovery-baseline.json` kan bara krympa) | Ja | +| `check:agent-skills-sync` | Genererade agent-skills-artefakter matchar sin källkatalog (ingen avvikelse) | +| `check:provider-asset-provenance` | Leverantörslogotyper/-resurser har en registrerad provenienspost | +| `lint:json` | JSON-konfigurationsfiler kan tolkas och uppfyller repositoriets lintregler | +| `typecheck:core` | TypeScript-kompilering utan fel (endast rådgivande varningar) | Ja | +| `typecheck:noimplicit:core` | Strikt `noImplicitAny` — framåtblickande; många befintliga anropsställen behöver fortfarande annoteringar | **Rådgivande** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` begränsat till `src/app/(dashboard)/**` (#7033) — den handplockade tillåtelselistan med 27 filer i `typecheck:core` inkluderar inga TSX-filer för instrumentpanelen, och `next build` typkontrollerar dem inte heller (`next.config.mjs` anger `ignoreBuildErrors: true`), så regressioner med övergivna identifierare där (#6625/#6909) var osynliga för CI. Jämför mot en fryst baslinje över antalet fel per fil och TS-kod (`config/quality/dashboard-typecheck-baseline.json`, samma mönster för kontroll av inaktualitet som `check:known-symbols`) — endast NYA fel utöver baslinjens antal gör att kontrollen misslyckas; sänk stegvis med `--update` när ett befintligt fel har åtgärdats. | Ja | ### Jobb: `quality-gate` Körs efter `test-coverage`. Blockerar sammanslagning vid fel. -| Skript | Validerar | Blockerande | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | Genererar `quality-metrics.json` (antal ESLint-varningar, täckning från sammanslagen shard-rapport) | Ja (före ratchet-steget) | -| `quality:ratchet` | Inget mätvärde i `quality-baseline.json` har försämrats (ESLint-varningar ≤ baslinje; täckning ≥ baslinje) | Ja | -| `check:duplication` | Kodduplicering (jscpd@4) överskrider inte baslinjen i `quality-baseline.json` | Ja | -| `check:complexity` | Cyklomatisk komplexitet på filnivå överskrider inte gränsen (ESLint-kärnreglerna `complexity` + `max-lines-per-function`) | Ja | -| `check:cognitive-complexity` | Ratchet för kognitiv komplexitet (`eslint-plugin-sonarjs`) — separat ESLint-körning; CI kör båda sammanslagna som det enda steget `check:complexity-ratchets` | Ja | -| `check:dead-code` | Ratchet för oanvända exporter/filer (knip) försämras inte jämfört med baslinjen | Ja | -| `check:compression-budget` | Budget för komprimeringsbenchmark — miniminivåerna för tokenbesparingar per motor får inte försämras | Ja | -| `check:type-coverage` | Ratchet för procentandel typad kod (`type-coverage`) försämras inte; ersätter till stor del `typecheck:noimplicit:core` | Ja | -| `check:codeql-ratchet` | Antalet öppna CodeQL-varningar försämras inte (läses via `gh api`; hoppas över utan fel om token saknas) — uppdateringsfrekvens och manuell utlösning: se "CodeQL-ratchet" nedan | Ja | +| Skript | Validerar | Blockerande | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | Skapar `quality-metrics.json` (antal ESLint-varningar, täckning från den sammanslagna shard-rapporten) | Ja (före ratchet-steget) | +| `quality:ratchet` | Varje mätvärde i `quality-baseline.json` har inte försämrats (ESLint-varningar ≤ baslinjen; täckning ≥ baslinjen) | Ja | +| `check:duplication` | Kodduplicering (jscpd@4) överskrider inte baslinjen i `quality-baseline.json` | Ja | +| `check:complexity` | Cyklomatisk komplexitet på filnivå överskrider inte gränsen (ESLint-kärnans `complexity` + `max-lines-per-function`) | Ja | +| `check:cognitive-complexity` | Ratchet för kognitiv komplexitet (`eslint-plugin-sonarjs`) — separat ESLint-körning; CI kör båda sammanslagna som det enda steget `check:complexity-ratchets` | Ja | +| `check:dead-code` | Ratchet för oanvända exporter/filer (knip) försämras inte jämfört med baslinjen | Ja | +| `check:compression-budget` | Budget för komprimeringsbenchmark — minimikraven för tokenbesparingar per motor får inte försämras | Ja | +| `check:type-coverage` | Ratchet för procentandel typad kod (`type-coverage`) försämras inte; ersätter till stor del `typecheck:noimplicit:core` | Ja | +| `check:codeql-ratchet` | Antalet öppna CodeQL-varningar försämras inte (läses via `gh api`; hoppas över utan fel om token saknas) — uppdateringsfrekvens och manuell utlösare: se "CodeQL-ratchet" nedan | Ja | ### Jobb: `quality-extended` Hela jobbet är rådgivande (`continue-on-error: true`). De npm-baserade ratchet-kontrollerna körs -på riktigt; de externa skannrarna installeras via `gh release download` och hoppar över sig själva (exit 0) +på riktigt; de externa skannrarna installeras via `gh release download` och hoppar själva över körningen (exit 0) när en binärfil fortfarande saknas. -| Skript | Validerar | Blockerande | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:circular-deps` | Inga cirkulära beroenden (dpdm) | **Rådgivande** | -| `check:bundle-size` | Paketstorleken överskrider inte gränsen | **Rådgivande** | -| `check:secrets` | Sökning efter hemligheter (gitleaks) — hoppas över om binärfilen saknas | **Rådgivande** | -| `check:vuln-ratchet` | Sårbarheter i beroenden (osv-scanner) försämras inte — hoppas över om binärfilen saknas | **Rådgivande** | -| `check:workflows` | Lintning av arbetsflöden (actionlint + zizmor); saknade/trasiga skannrar, ogiltiga rapporter eller saknad ratchet-baslinje ger felet INCOMPLETE. Giltiga fynd följer vald strikt/rådgivande/ratchet-policy | Körning krävs; zizmor-ratchet blockerar i CI | -| `check:openapi-breaking` | Bakåtinkompatibla ändringar i det publika API-kontraktet (`openapi.yaml`) jämfört med basgrenen (oasdiff) — genererar `openapiBreaking=N`; hoppas över om oasdiff saknas eller basspecifikationen inte kan lösas | **Rådgivande** | +| Skript | Validerar | Blockerande | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `check:circular-deps` | Inga cirkulära beroenden (dpdm) | **Rådgivande** | +| `check:bundle-size` | Paketstorleken överskrider inte gränsen | **Rådgivande** | +| `check:secrets` | Skanning efter hemligheter (gitleaks) — hoppas över om binärfilen saknas | **Rådgivande** | +| `check:vuln-ratchet` | Sårbarheter i beroenden (osv-scanner) försämras inte — hoppas över om binärfilen saknas | **Rådgivande** | +| `check:workflows` | Lintning av arbetsflöden (actionlint + zizmor); saknade/trasiga skannrar, ogiltiga rapporter eller saknad ratchet-baslinje ger resultatet INCOMPLETE. Giltiga fynd följer vald strikt/rådgivande/ratchet-policy | Körning krävs; zizmor-ratchet blockerar i CI | +| `check:openapi-breaking` | Bakåtinkompatibla ändringar i det offentliga API-kontraktet (`openapi.yaml`) jämfört med basgrenen (oasdiff) — genererar `openapiBreaking=N`; hoppas över om oasdiff saknas eller basspecifikationen inte kan lösas | **Rådgivande** | ### Jobb: `docs-sync-strict` Körs för varje PR till `main`. Blockerar sammanslagning vid fel. -| Skript | Validerar | Blockerande | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `check:docs-all` | Metagrind som kör de 6 delgrindarna nedan sekventiellt | Ja | -| ↳ `check:docs-sync` | Versionsöverensstämmelse mellan CHANGELOG / OpenAPI / llm.txt | Ja | -| ↳ `check:docs-counts` | Antal i löptext (antal leverantörer, antal migreringar osv.) ligger inom ratchet-fönstret för de verkliga antalen | Ja | -| ↳ `check:env-doc-sync` | Varje miljövariabel i `.env.example` är dokumenterad i en dokumentationstabell, och vice versa | Ja | -| ↳ `check:deprecated-versions` | Inga utfasade versionssträngar i dokumentationen | Ja | -| ↳ `check:doc-links` | Interna markdown-länkar i dokumentationen pekar på verkliga filer (`[text]`/`(path)`-format) | Ja | -| ↳ `check:fabricated-docs` | Rutter, miljövariabler, CLI-kommandon, hook-namn och filsökvägar som anges i dokumentationen finns i kodbasen. Hård grind via `--strict`; mjukt fel utan flaggan. | Ja (via `--strict` i CI) | -| `check:cli-i18n` | CLI-kommandosträngar finns i alla i18n-lokalfiler | Ja | -| `check:openapi-coverage` | OpenAPI-specifikationen täcker minst en stegvis höjd miniminivå av verkliga rutter | Ja | -| `check:openapi-security-tiers` | Säkerhetsnivåannoteringarna i `openapi.yaml` överensstämmer med klassificeringarna i `routeGuard.ts` | **Rådgivande** | -| `check:openapi-routes` | Varje sökväg i `openapi.yaml` motsvarar en verklig `route.ts` (motverkar hallucinationer) | Ja | -| `check:docs-symbols` | Varje `/api/...`-referens i `docs/**/*.md` motsvarar en verklig `route.ts` (motverkar hallucinationer) | Ja | -| `i18n translation drift` | Oöversatta nycklar i i18n-lokalfiler — endast varning | **Rådgivande** | +| Skript | Validerar | Blockerande | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `check:docs-all` | Metagrind som kör de 6 delgrindarna nedan sekventiellt | Ja | +| ↳ `check:docs-sync` | Versionskonsekvens mellan CHANGELOG / OpenAPI / llm.txt | Ja | +| ↳ `check:docs-counts` | Antal i löptext (antal leverantörer, antal migreringar osv.) ligger inom spärrintervallet för de verkliga antalen | Ja | +| ↳ `check:env-doc-sync` | Varje miljövariabel i `.env.example` är dokumenterad i en dokumentationstabell och vice versa | Ja | +| ↳ `check:deprecated-versions` | Inga utfasade versionssträngar i dokumentationen | Ja | +| ↳ `check:doc-links` | Interna markdown-länkar i dokumentationen pekar på verkliga filer (`[text]`/`(path)`-format) | Ja | +| ↳ `check:fabricated-docs` | Rutter, miljövariabler, CLI-kommandon, hooknamn och filsökvägar som anges i dokumentationen finns i kodbasen. Hård grind via `--strict`; mjukt fel utan flaggan. | Ja (via `--strict` i CI) | +| `check:cli-i18n` | CLI-kommandosträngar finns i alla i18n-språkfiler | Ja | +| `check:openapi-coverage` | OpenAPI-specifikationen täcker minst ett successivt höjt minimikrav för verkliga rutter | Ja | +| `check:openapi-security-tiers` | Säkerhetsnivåannoteringar i `openapi.yaml` överensstämmer med klassificeringarna i `routeGuard.ts` | **Rådgivande** | +| `check:openapi-routes` | Varje sökväg i `openapi.yaml` motsvarar en verklig `route.ts` (motverkar hallucinationer) | Ja | +| `check:docs-symbols` | Varje `/api/...`-referens i `docs/**/*.md` motsvarar en verklig `route.ts` (motverkar hallucinationer) | Ja | +| `i18n translation drift` | Oöversatta nycklar i i18n-språkfiler — endast varning | **Rådgivande** | ### Jobb: `i18n-ui-coverage` -| Skript | Validerar | Blockerande | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (inline) | Täckningen för UI-i18n-nycklar är ≥ 65 % | Ja | -| `check-ui-value-drift` (inline) | Ett omskrivet engelskt **värde** lämnar ingen inaktuell översättning kvar | Ja | -| `check-new-key-coverage` (inline) | En **ny** engelsk nyckel är översatt i varje lokal — en `__MISSING__:`-markör avvisas | Ja | -| `check-translation-ratio` | Andelen verkliga översättningar per lokal (löv utanför undantagslistan som är identiska med engelskan, platshållare eller saknas) får inte överstiga `config/quality/i18n-translation-baseline.json` + marginal | **Rådgivande** | +| Skript | Validerar | Blockerande | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `check-ui-keys-coverage` (infogat) | Täckningen för i18n-nycklar i användargränssnittet är ≥ 65 % | Ja | +| `check-ui-value-drift` (infogat) | Ett omskrivet engelskt **värde** lämnar inte kvar någon inaktuell översättning | Ja | +| `check-new-key-coverage` (infogat) | En **ny** engelsk nyckel är översatt i varje språkversion — en `__MISSING__:`-markör avvisas | Ja | +| `check-translation-ratio` | Andelen verkliga översättningar per språkversion (oförändrade engelska texter / platshållare / saknade löv utanför undantagslistan) får inte överstiga `config/quality/i18n-translation-baseline.json` + marginal | **Rådgivande** | -Kräver `fetch-depth: 0` — värdeavvikelsegrinden jämför `en.json` mot merge-basen. +Kräver `fetch-depth: 0` — grinden för värdeavvikelse jämför `en.json` mot sammanslagningsbasen. -#### `check-ui-value-drift` — grind för inaktuella översättningar +#### `check-ui-value-drift` — grind mot inaktuella översättningar -Upptäcker den enda i18n-regression som de andra grindarna strukturellt inte kan se: ett engelskt värde -skrivs om medan översättningarna som baserades på den _tidigare_ engelska texten lämnas kvar, så att -användare som inte använder engelska fortsätter att läsa självsäkert formulerad men numera felaktig text. +Fångar den enda i18n-regression som de andra grindarna strukturellt inte kan upptäcka: ett engelskt värde +skrivs om medan översättningarna som härletts från den _tidigare_ engelska texten ligger kvar, så att +icke-engelskspråkiga användare fortsätter läsa självsäkert formulerad men nu felaktig text. -Detta nådde faktiskt produktion. `oauthModal.googleOAuthWarning` skrevs om när inloggningshjälpen för Antigravity -lanserades (#5203); **39 av 43 lokaler** behöll text som instruerade operatörer att ”kopiera hela -URL:en och klistra in den nedan” — ett flöde som inte kan slutföras för den leverantören. Det +Detta levererades faktiskt. `oauthModal.googleOAuthWarning` skrevs om när inloggningshjälpen för Antigravity +lades till (#5203); **39 av 43 språkversioner** behöll text som uppmanade operatörer att ”kopiera den +fullständiga URL:en och klistra in den nedan” — ett flöde som inte kan slutföras för den leverantören. Det förblev oupptäckt fram till #8463 eftersom: -- `sync-ui-keys` endast fyller i nycklar som **saknas**, aldrig sådana som är **inaktuella**; -- `check-ui-keys-coverage` räknar nycklars _förekomst_, så en inaktuell översättning räknas som täckt; -- `check-translation-drift` spårar dokumentationsspeglingarna i `docs/i18n//**.md` — +- `sync-ui-keys` fyller endast i nycklar som **saknas**, aldrig sådana som är **inaktuella**; +- `check-ui-keys-coverage` räknar _förekomsten_ av nycklar, så en inaktuell översättning räknas som täckt; +- `check-translation-drift` spårar dokumentationsspeglarna i `docs/i18n//**.md` — den läser aldrig `src/i18n/messages/*.json`. Blockerande i jobbet `docs-sync-strict` sedan - omsynkroniseringen 2026-09: redigera ett centralt dokument → `npm run i18n:run -- --files=` (på avsnittsnivå, resurssnålt). + omsynkroniseringen 2026-09: redigera ett centralt dokument → `npm run i18n:run -- --files=` (på avsnittsnivå, billigt). -**Diffmedveten, inte baslinjestödd.** Den jämför `en.json` vid sammanslagningsbasen med -arbetsträdet; för varje nyckel vars engelska värde har ändrats är alla språkversioner som fortfarande har en -orörd översättning inaktuella. Detta **fryser avsiktligt befintlig skuld** — en diff -kan inte visa vilken äldre engelsk text en sedan länge befintlig översättning kom från, så kontrollen bedömer -endast det som den aktuella ändringen berör. Alternativet (en hashbaslinje per nyckel) skulle kräva -en genererad fil på ~600 KB, 3× större än den största befintliga baslinjen, med ändringar i varje i18n-PR. +**Differensmedveten, inte baslinjestödd.** Den jämför `en.json` vid sammanslagningsbasen med +arbetsträdet. För varje nyckel vars engelska värde har ändrats är varje språkversion som +fortfarande innehåller en orörd översättning inaktuell. Detta **fryser avsiktligt befintlig skuld** — en differens +kan inte visa vilken äldre engelsk text en sedan länge befintlig översättning härstammar från, så kontrollen bedömer +endast det som den aktuella ändringen berör. Alternativet (en baslinje med hash per nyckel) skulle kräva +en genererad fil på ~600 KB, tre gånger större än den största befintliga baslinjen, som skulle ändras vid varje i18n-PR. Det finns två sätt att uppfylla kravet: 1. uppdatera de berörda översättningarna, eller -2. ange dem som `__MISSING__:` — körningen visar då den korrigerade engelska texten - (`src/i18n/request.ts::deepMergeFallback`, #7258) och nyckeln köas för översättning. +2. ange dem som `__MISSING__:` — vid körning används då den korrigerade engelska texten + (`src/i18n/request.ts::deepMergeFallback`, #7258), och nyckeln köas för översättning. Om strängens **betydelse** har ändrats bör du i stället **byta namn på nyckeln**: en ny nyckel kan inte ärva en inaktuell översättning. Det är mönstret som användes i #8463. @@ -231,48 +231,48 @@ npm run i18n:check-value-drift:warn # endast rapportering BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Avslutas med 0 och `SKIP reason=base-unresolved` när baskatalogen inte kan läsas (ytlig -klon utan basreferensen), i likhet med `check-openapi-breaking`. +Avslutas med 0 och `SKIP reason=base-unresolved` när baskatalogen inte kan läsas (grund +klon utan basreferensen), i enlighet med `check-openapi-breaking`. ### Jobb: `i18n` Fullständig matris för i18n-validering (ett jobb per språkversion). Hela jobbet är rådgivande. -| Skript | Validerar | Blockerande | -| ------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | -| `validate_translation.py quick` | Översättningsfullständighet per språkversion | **Rådgivande** (`continue-on-error: true` för hela jobbet) | +| Skript | Validerar | Blockerande | +| ------------------------------- | ----------------------------------------------- | ---------------------------------------------------------- | +| `validate_translation.py quick` | Översättningens fullständighet per språkversion | **Rådgivande** (`continue-on-error: true` för hela jobbet) | ### Jobb: `pr-test-policy` Körs endast för pull requests. -| Skript | Validerar | Blockerande | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | PR:er som ändrar produktionskod i `src/`, `open-sse/`, `electron/` eller `bin/` måste inkludera eller uppdatera tester (hård regel nr 8) | Ja | -| `check:test-masking` | Ändrade testfiler minskar inte nettoantalet asserts och lägger inte till tautologier av typen `assert.ok(true)` | Ja | -| `check:pr-evidence` | PR-texten hänvisar till test-/VPS-underlag för ändringen (automatiserar hård regel nr 18 genom grep-sökning i PR-text — bräckligt, se Backlog) | Ja | +| Skript | Validerar | Blockerande | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check:pr-test-policy` | PR:er som ändrar produktionskod i `src/`, `open-sse/`, `electron/` eller `bin/` måste inkludera eller uppdatera tester (hård regel nr 8) | Ja | +| `check:test-masking` | Ändrade testfiler minskar inte nettoantalet verifieringar och lägger inte till tautologier av typen `assert.ok(true)` | Ja | +| `check:pr-evidence` | PR-beskrivningen hänvisar till test-/VPS-bevis för ändringen (automatiserar hård regel nr 18 genom att söka i PR-texten — bräckligt, se backloggen) | Ja | ### Jobb: `test-vitest` Körs efter `build`. Blockerar sammanslagning vid fel. -| Testsvit | Validerar | Blockerande | -| ---------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP-server (110 verktyg), autoCombo, cache — vitest-körning | Ja | -| `test:vitest:ui` | UI-komponenttester — vitest-körning | **Blockerande** — befintliga fel undantas uttryckligen i `vitest.config.ts`; nya fel gör att jobbet misslyckas | +| Testsvit | Validerar | Blockerande | +| ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP-server (110 verktyg), autoCombo, cache — vitest-körning | Ja | +| `test:vitest:ui` | Tester av UI-komponenter — vitest-körning | **Blockerande** — befintliga fel är uttryckligen undantagna i `vitest.config.ts`; nya fel gör att jobbet misslyckas | ### Nattliga arbetsflöden (schemalagda, rådgivande) -Dessa körs enligt ett cron-schema (och `workflow_dispatch`), aldrig för PR:er. Alla är rådgivande. +Dessa körs enligt ett cron-schema (och `workflow_dispatch`), aldrig för PR:er. Samtliga är rådgivande. -| Arbetsflöde | Validerar | Blockerande | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | fast-check-egenskapstester med ett slumpmässigt startvärde + stort antal körningar | **Rådgivande** | -| `nightly-resilience` | kontroll av heap-tillväxt, kaosbaserad felinjektion, k6-belastnings-/uthållighetstestning | **Rådgivande** | -| `nightly-llm-security` | promptfoo-injektionsskydd (blockeringsläge) + garak-prober (hoppas över utan en leverantörshemlighet) | **Rådgivande** | -| `nightly-schemathesis` | Fuzzning av OpenAPI-kontrakt (schemathesis) mot en aktiv OmniRoute med `docs/openapi.yaml` — blottlägger specifikationsöverträdelser/oavlyssnade 500-fel (Fas 8 B.4) | **Rådgivande** | -| `nightly-mutation` | Poäng för Stryker-mutationstestning över den snabba enhetstestbanan — överlevande mutanter blottlägger svaga asserts | **Rådgivande** | -| `nightly-compat` | Kompatibilitetsmatris för Node-motorn över de stödda `engines.node`-intervallen | **Rådgivande** | +| Arbetsflöde | Validerar | Blockerande | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | fast-check-egenskapstester med ett slumpmässigt startvärde + många körningar | **Rådgivande** | +| `nightly-resilience` | kontroll av heap-tillväxt, kaosbaserad felinjektion, k6-belastnings-/uthållighetstest | **Rådgivande** | +| `nightly-llm-security` | promptfoo-skydd mot injektion (blockeringsläge) + garak-prober (hoppas över utan en leverantörshemlighet) | **Rådgivande** | +| `nightly-schemathesis` | Fuzzning av OpenAPI-kontrakt (schemathesis) mot en aktiv OmniRoute med `docs/openapi.yaml` — synliggör specifikationsöverträdelser/ohanterade 500-fel (fas 8 B.4) | **Rådgivande** | +| `nightly-mutation` | Poäng från Stryker-mutationstestning för den snabba enhetstestbanan — överlevande mutanter synliggör svaga verifieringar | **Rådgivande** | +| `nightly-compat` | Kompatibilitetsmatris för Node-motorn över de `engines.node`-intervall som stöds | **Rådgivande** | --- @@ -536,9 +536,9 @@ anslutningsfel och avbryter aldrig sessioner. --- -## Rationaliseringsbacklogg (ROI-granskning — Fas 9 Våg 3) +## Rationaliseringsbacklogg (ROI-granskning — fas 9 våg 3) -Denna inventering stämdes av mot `ci.yml` den 2026-06-17 (den föregående versionen utelämnade +Den här inventeringen stämdes av mot `ci.yml` den 2026-06-17 (den föregående versionen utelämnade `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). En ROI-granskning av den avstämda uppsättningen @@ -546,33 +546,33 @@ identifierade följande kandidater för rationalisering. **Sammanslagningarna ä ändringar; växlingarna/borttagningarna är policybeslut som är förbehållna operatören.** Inget nedan har tillämpats ännu. -**Också odokumenterade ovan** (rådgivande, låg signal): jobbet `docs-lint` +**Inte heller dokumenterat ovan** (rådgivande, låg signal): jobbet `docs-lint` (markdownlint + Vale, hela jobbet har `continue-on-error`) och de fristående skanningsarbetsflödena `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` finns i -`quality-baseline.json` men är inte kopplat till en blockerande spärr i `ci.yml` — mätvärdet är +`quality-baseline.json` men är inte kopplat till någon blockerande spärrmekanism i `ci.yml` — mätvärdet är för närvarande frikopplat. -### Slå samman / deduplicera (mekaniskt, lägre risk) +### Slå ihop / deduplicera (mekaniskt, lägre risk) -Varje kandidat validerades mot det aktiva grindläget den 2026-06-17 (lita men verifiera); -flera ”uppenbara” sammanslagningar visade sig dölja skuld och är **inte** rena direktlösningar. +Varje kandidat validerades mot det aktuella grindläget den 2026-06-17 (lita men verifiera); +flera ”uppenbara” sammanslagningar visade sig dölja skuld och är **inte** problemfria direktlösningar. -- **`check:docs-sync` körs två gånger** — fristående i jobbet `lint` och igen inuti `check:docs-all` (`docs-sync-strict`) samt i huskys pre-commit-hook. ✅ **KLART** — den fristående `lint`-körningen har tagits bort. -- **CVE-skanning** — ❌ **INTE en ren sammanslagning.** `audit:deps` leder till ett hårt fel vid varje CVE med allvarlighetsgrad high/critical; `check:vuln-ratchet` (osv) misslyckas endast vid en _regression_ jämfört med baslinjen (för närvarande 1 MODERATE). Olika semantik — att ta bort `audit:deps` skulle innebära att den absoluta grinden för high/critical försvinner. Behåll båda. -- **Cykeldetektering** — ❌ **INTE en ren sammanslagning.** `check:circular-deps` (dpdm) rapporterar **91 cykler** (det är därför den är rådgivande); den kan inte göras blockerande utan att dessa först åtgärdas, och den har ett bredare omfång än den godkända, handplockade `check:cycles`. Behåll `check:cycles` som blockerande; att åtgärda de 91 dpdm-cyklerna är en separat backloggpost. -- **Komplexitet** — ✅ **KLART** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): en ESLint-genomgång, räknar per ruleId så att baslinjerna för cyklomatisk komplexitet+max-lines och kognitiv komplexitet förblir oberoende; separata `check:complexity` / `check:cognitive-complexity` finns kvar för lokal `--update`. -- **Antihallucination för `/api`** — ✅ **KLART** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): en FS-inventering av `src/app/api`, openapi-routes + docs-symbols rapporterar fortfarande oberoende av varandra; de separata kontrollerna finns kvar för lokala körningar. -- **`check:node-runtime` körs i 11 jobb** — ⚠️ **låg ROI.** Varje körning sker på en separat runner och kontrollen tar <1 s; total besparing ~10 s, till priset av att en billig skyddskontroll per jobb går förlorad. Inte värt omarbetningen. -- **`typecheck:noimplicit:core` i CI-lintning** — ✅ **borttaget från lint-jobbet** (var rådgivande med `continue-on-error`); den blockerande typytan utgörs av `typecheck:core` + `check:type-coverage`. Det lokala skriptet har behållits. +- **`check:docs-sync` körs två gånger** — fristående i jobbet `lint` och återigen inuti `check:docs-all` (`docs-sync-strict`) samt i husky-pre-commit-kroken. ✅ **KLART** — den fristående anropningen i `lint` har tagits bort. +- **CVE-skanning** — ❌ **INTE en problemfri sammanslagning.** `audit:deps` stoppar ovillkorligen vid varje CVE med hög/kritisk allvarlighetsgrad; `check:vuln-ratchet` (osv) misslyckas endast vid en _regression_ jämfört med baslinjen (för närvarande 1 MODERATE). Olika semantik — att ta bort `audit:deps` skulle innebära att den absoluta grinden för hög/kritisk allvarlighetsgrad försvinner. Behåll båda. +- **Cykeldetektering** — ✅ **KLART** (#15159 G-01/G-02). Den gamla texten här kallade `check:cycles` för den ”gröna, handplockade” grinden och motiverade att den skulle förbli blockerande eftersom `check:circular-deps` (dpdm) rapporterade 91 cykler. Det gröna resultatet var **falskt grönt**: `check:cycles` skannade 5 underkataloger (450 filer), matchade endast statiska `import|export … from` och ignorerade varje `@/`- och `@omniroute/open-sse/`-specifierare, så den kunde inte se de cykler med dynamisk import + alias som dominerade kodbasen. Åtgärdat: grinden går nu igenom `src` + `open-sse` (5023 filer), samlar in specifierare från TypeScript-AST:n (så `import("…")` räknas medan `typeof import("…")` i typposition inte gör det) och löser tsconfig-`paths`. Den hittar **14** cykler, inte 0. Eftersom 14 befintliga cykler inte kan åtgärdas i en grind-PR är `check:cycles` nu en **spärrmekanism** (`--ratchet`, tak `metrics.cycles.value = 14` i `quality-baseline.json`, `direction: down`) — den blockerar varje _regression_ och antalet kan endast minska. CI kör `npm run check:cycles:ratchet`. Nedarbetningen följer med **A-01**. `check:circular-deps` (dpdm) förblir rådgivande som en bredare andrahandsbedömning. +- **Komplexitet** — ✅ **KLART** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): en ESLint-genomgång, räknar per ruleId så att baslinjerna för cyklomatisk komplexitet + maximalt antal rader och kognitiv komplexitet förblir oberoende; individuella `check:complexity` / `check:cognitive-complexity` finns kvar för lokal `--update`. +- **Antihallucination för `/api`** — ✅ **KLART** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): en FS-inventering av `src/app/api`, openapi-routes + docs-symbols rapporterar fortfarande oberoende; individuella kontroller finns kvar för lokala körningar. +- **`check:node-runtime` körs i 11 jobb** — ⚠️ **låg ROI.** Varje jobb har en separat körmiljö och kontrollen tar <1 s; total besparing ~10 s, på bekostnad av att förlora ett billigt skydd per jobb. Inte värt omarbetningen. +- **`typecheck:noimplicit:core` i CI-lint** — ✅ **borttaget från lint-jobbet** (var rådgivande med `continue-on-error`); den blockerande typytan är `typecheck:core` + `check:type-coverage`. Det lokala skriptet har behållits. ### Växla / besluta (operatörspolicy) -- `check:openapi-security-tiers` (rådgivande) — ❌ **KAN INTE växlas direkt.** Det avslutas med 0 men varnar för att flera `traffic-inspector`-rutter under `LOCAL_ONLY_API_PREFIXES` saknar annoteringen `x-loopback-only: true`. För att göra det tvingande måste dessa annoteringar först läggas till i `openapi.yaml`. -- `typecheck:noimplicit:core` (rådgivande) — ersätts till stor del av den blockerande `check:type-coverage`-spärren. Gör om den till en spärr eller ta bort det redundanta andra `tsc`-passet. -- `test:vitest:ui` (nu **blockerande**) — befintliga fel undantas uttryckligen i `vitest.config.ts` med spårningskommentarer av typen `// #8618`; nya fel gör att jobbet misslyckas. -- `check:secrets` (gitleaks, blockerande spärr fryst vid 3 dokumenterade falska positiva resultat) — lägg till de 3 i tillåtelselistan för att nå 0, eller nedgradera till rådgivande. Överlappar GitHubs inbyggda hemlighetsskanning + `check:public-creds`. -- `check:pr-evidence` (blockerande, söker igenom PR-beskrivningens prosa med grep) — hög risk för falska positiva resultat; om den tas bort försvagas efterlevnaden av Hård regel nr 18, så detta är ett genuint policybeslut. -- `semgrep` (rådgivande, fristående) — överlappar CodeQL för OWASP-familjerna; koppla dess baslinje till en spärr eller ta bort den. +- `check:openapi-security-tiers` (rådgivande) — ❌ **KAN INTE växlas utan föregående åtgärder.** Den avslutas med 0 men varnar för att flera `traffic-inspector`-rutter under `LOCAL_ONLY_API_PREFIXES` saknar annoteringen `x-loopback-only: true`. För att verkställa den måste dessa annoteringar först läggas till i `openapi.yaml`. +- `typecheck:noimplicit:core` (rådgivande) — ersätts till stor del av den blockerande spärrmekanismen `check:type-coverage`. Växla till en spärrmekanism eller ta bort den redundanta andra `tsc`-körningen. +- `test:vitest:ui` (nu **blockerande**) — befintliga fel exkluderas uttryckligen i `vitest.config.ts` med spårningskommentarer av typen `// #8618`; nya fel gör att jobbet misslyckas. +- `check:secrets` (gitleaks, blockerande spärrmekanism fryst vid 3 dokumenterade falska positiva resultat) — tillåtelselista de 3 för att nå 0 eller nedgradera till rådgivande. Överlappar GitHubs inbyggda hemlighetsskanning + `check:public-creds`. +- `check:pr-evidence` (blockerande, söker med grep i PR-beskrivningens prosa) — hög risk för falska positiva resultat; att ta bort den försvagar upprätthållandet av hård regel nr 18, så detta är ett verkligt policybeslut. +- `semgrep` (fristående rådgivande) — överlappar CodeQL för OWASP-familjerna; koppla dess baslinje till en spärrmekanism eller ta bort den. --- diff --git a/docs/i18n/sv/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/sv/docs/compression/COMPRESSION_GUIDE.md index 23bc5f6e9c73..ad1c40b73caa 100644 --- a/docs/i18n/sv/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/sv/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ i stället för den engelska. För en body med `messages` kontrollerar en innehållsförbikoppling (`shouldBypassCavemanOutputMode()` i `open-sse/services/compression/outputMode.ts`) de tre senaste meddelandena och hoppar över stilarna för hela interaktionen när de matchar dess nyckelord för säkerhet, oåterkalleliga åtgärder, -förtydliganden eller ordningskänslighet. Förbikopplingen följer inställningen för instrumentpanelens -växlingsknapp **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`). +förtydliganden eller ordningskänslighet. Förbikopplingen körs medan instrumentpanelens växlingsknapp **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) är påslagen, vilket är standardinställningen; med knappen avslagen gäller de valda stilarna även i dessa interaktioner. När förbikopplingen släpper igenom interaktionen placerar `placeSystemInstruction()` (samma fil), som aldrig skapar en ny `messages[0]`, blocket på den första av följande platser som hittas: diff --git a/docs/i18n/sv/docs/reference/ENVIRONMENT.md b/docs/i18n/sv/docs/reference/ENVIRONMENT.md index 335c7ec67d81..f00de61f775e 100644 --- a/docs/i18n/sv/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sv/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Obligatoriska hemligheter -Dessa **måste** anges före den första körningen. Utan dem kommer applikationen antingen att vägra starta eller använda osäkra standardvärden. - -| Variabel | Obligatorisk | Standardvärde | Källfil | Beskrivning | -| ---------------------------- | ------------------- | ------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ja** | _(inget)_ | `src/lib/auth` | Signerar/verifierar alla sessionscookies för kontrollpanelen (JWT). Generera med `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ja** | _(inget)_ | `src/lib/db/apiKeys.ts` | AES-krypteringsnyckel för lagrade API-nyckelvärden i SQLite. Generera med `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ja** | `CHANGEME` | Bootstrap-skript | Anger det ursprungliga administratörslösenordet för kontrollpanelen (motsvarar standardvärdet i `.env.example` – avsiktligt uppenbart osäkert för att tvinga fram en ändring). **Ändra före första användningen.** Efter inloggning ändrar du det via Kontrollpanel → Inställningar → Säkerhet. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (produktion) | _(inte angivet)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Delad hemlighet för den interna WebSocket-bryggan för Codex Responses. Autentiserar bryggförfrågningar mellan WS-reläet i Electron/webbläsaren och OmniRoute. ⚠️ **OBLIGATORISK i produktion – när den inte är angiven avvisas alla WS-bryggförfrågningar.** Generera med `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Nej | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicit cachebrytnings-id för service worker-cachen i PWA-skalet (#11779); först i upplösningskedjan. | -| `SOURCE_VERSION` | Nej | _(inte angivet)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Andra värdet i kedjan – anges av PaaS-byggsystem (t.ex. Heroku-liknande) som den distribuerade committen. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Nej | _(härlett)_ | `src/shared/components/PwaRegister.tsx` | Offentligt värde vid byggtid som klienten använder för att registrera `/sw.js?v=…`; härleds från de två ovanstående och därefter git-SHA:n. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Nej (automatiskt) | _(automatiskt per start)_ | `src/server/authz/policies/management.ts` | Hemlighet per process som bevisar att den betrodda peer-IP-stämpeln kommer från OmniRoutes egen HTTP-server (`scripts/dev/peer-stamp.mjs`). Behörighetsmellanprogramvaran litar endast på förfrågans lokalitet (loopback/LAN-begränsning av `LOCAL_ONLY`-rutter) när stämpeln innehåller denna token. Genereras automatiskt vid varje start – lämna den oangiven; lås den endast för flerprocesskonfigurationer som måste dela stämpeln. | -| `OMNIROUTE_TRUSTED_PROXIES` | Nej | _(inte angivet)_ | `scripts/dev/peer-stamp.mjs` | Kommaseparerade IP-adresser eller CIDR-intervall för omvända proxyservrar vars `X-Forwarded-For` / `X-Real-IP` får betros av listan över tillåtna/nekade IP-adresser, utöver loopback, privata nätverksadresser och Cloudflare-kanter, som alltid är betrodda. Behövs endast för en proxy på någon annan offentlig adress; annars är det proxyns egen adress som filtret bedömer. | +Hemligheter markerade **Ja** som inte är angivna genereras automatiskt vid den första körningen (`JWT_SECRET`, `API_KEY_SECRET`) och sparas i `server.env` i datakatalogen – ange dem uttryckligen när den katalogen är tillfällig. Undantaget är `OMNIROUTE_WS_BRIDGE_SECRET`: den sparas inte i `server.env` – serverstartpunkterna skapar ett tillfälligt värde per start som endast den interna processbryggan kan använda, så externa bryggklienter måste ha den angiven uttryckligen. + +| Variabel | Obligatorisk | Standardvärde | Källfil | Beskrivning | +| ---------------------------- | ------------------- | ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ja** | _(inget)_ | `src/lib/auth` | Signerar/verifierar alla sessionscookies för kontrollpanelen (JWT). Generera med `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ja** | _(inget)_ | `src/lib/db/apiKeys.ts` | AES-krypteringsnyckel för lagrade API-nyckelvärden i SQLite. Generera med `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Nej | _(inte angivet)_ | `src/lib/auth/managementPassword.ts` | Anger kontrollpanelens lösenord vid start så länge inget lösenord har sparats; ignoreras så snart ett lösenord har sparats. Installationer via npm och från källkod kopierar `.env.example` till `.env` och startar därför med dess offentligt kända platshållare `CHANGEME`: byt ut den före första start, eller ange `INITIAL_PASSWORD=` (tomt) i den `.env`-filen för att skapa lösenordet i installationsguiden vid första besöket, vilket ett oangivet värde (Docker-avbildningens standardvärde) gör. Ändra ett sparat lösenord via Kontrollpanel → Inställningar → Säkerhet, eller kör `omniroute reset-password` (`node bin/reset-password.mjs` i en källkodsutcheckning) med `DATA_DIR` angiven till serverns datakatalog. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ja** (produktion) | _(inte angivet)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Delad hemlighet för den interna WebSocket-bryggan för Codex Responses. Autentiserar bryggförfrågningar mellan WS-reläet i Electron/webbläsaren och OmniRoute. ⚠️ **OBLIGATORISK i produktion – när den inte är angiven avvisas alla WS-bryggförfrågningar.** Generera med `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Nej | _(git-SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicit cachebrytnings-id för service worker-cachen i PWA-skalet (#11779); först i upplösningskedjan. | +| `SOURCE_VERSION` | Nej | _(inte angivet)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Andra värdet i kedjan – anges av PaaS-byggsystem (t.ex. Heroku-liknande) som den distribuerade committen. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Nej | _(härlett)_ | `src/shared/components/PwaRegister.tsx` | Offentligt värde vid byggtid som klienten använder för att registrera `/sw.js?v=…`; härleds från de två ovanstående och därefter git-SHA:n. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Nej (automatiskt) | _(automatiskt per start)_ | `src/server/authz/policies/management.ts` | Hemlighet per process som bevisar att den betrodda peer-IP-stämpeln kommer från OmniRoutes egen HTTP-server (`scripts/dev/peer-stamp.mjs`). Behörighetsmellanprogramvaran litar endast på förfrågans lokalitet (loopback/LAN-begränsning av `LOCAL_ONLY`-rutter) när stämpeln innehåller denna token. Genereras automatiskt vid varje start – lämna den oangiven; lås den endast för flerprocesskonfigurationer som måste dela stämpeln. | +| `OMNIROUTE_TRUSTED_PROXIES` | Nej | _(inte angivet)_ | `scripts/dev/peer-stamp.mjs` | Kommaseparerade IP-adresser eller CIDR-intervall för omvända proxyservrar vars `X-Forwarded-For` / `X-Real-IP` får betros av listan över tillåtna/nekade IP-adresser, utöver loopback, privata nätverksadresser och Cloudflare-kanter, som alltid är betrodda. Behövs endast för en proxy på någon annan offentlig adress; annars är det proxyns egen adress som filtret bedömer. | ### Genereringskommandon @@ -216,38 +216,38 @@ OmniRoute använder **SQLite** (via `better-sqlite3`) för all beständig lagrin ## 4. Säkerhet och autentisering -| Variabel | Standardvärde | Källfil | Beskrivning | -| ------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt som kombineras med maskinvaruidentifierare för att skapa ett maskinfingeravtryck. Ändra för varje driftsättning för att säkerställa isolering. | -| `OMNIROUTE_CLI_SALT` | _(inte angivet = slumpmässigt salt per installation som sparas i `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt för härledning av den lokala CLI-autentiseringstoken. Om detta värde anges roteras alla CLI-token på maskinen, och värdet har alltid företräde framför det sparade saltet. Se `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Anger flaggan `Secure` för sessionscookies. **Måste vara `true`** vid körning bakom HTTPS. | -| `REQUIRE_API_KEY` | `false` | API-mellanprogramvara | När värdet är `true` måste alla proxyförfrågningar till `/v1/*` innehålla en giltig API-nyckel. **Den här flaggan styr inte åtkomsten till `GET /v1/models`**, som i stället följer inloggningsinställningen för kontrollpanelen (`requireAuthForModels`) – därför innebär ett `401`-svar från `/v1/models` INTE att inferens är skyddad. Se `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Tillåter att fullständiga API-nyckelvärden visas i kontrollpanelens gränssnitt. Kan konfigureras via kontrollpanelens funktionsflaggor; utgör en säkerhetsrisk i delade instanser. | -| `NO_LOG_API_KEY_IDS` | _(tomt)_ | `src/lib/compliance/index.ts` | Kommaseparerade API-nyckel-ID:n som undantas från loggning av förfrågningar (GDPR-efterlevnad). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(inte angivet = obegränsat)_ | `src/shared/utils/apiKeyPolicy.ts` | Reservbudget per dag för förfrågningar som tillämpas på API-nycklar vars kolumn `rate_limits` är null. Inte angivet eller tomt: ingen implicit gräns (#2289, #11017). `0` innebär samma sak (obegränsat). Ett positivt heltal N aktiverar N/dag, 5N/vecka och 20N/månad. Felaktiga värden som inte är tomma återgår till de äldre intervallen 1000/dag, 5000/vecka och 20000/månad. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Högsta tillåtna storlek på förfrågningskroppen. Avvisar nyttolaster som överskrider denna gräns. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiska förfrågningskroppar som är lika stora som eller större än detta tröskelvärde erhåller det atomära, proceslokala tillträdeslåset för tungviktiga förfrågningar före JSON-tolkning (BYTE-sökvägen, inklusive `POST /v1/responses`). Samma [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-undantag för tillräcklig ledig kapacitet som för strukturtunga förfrågningar gäller; fortfarande begränsat av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), så att [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) inte återkommer. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Hård gräns för chattrutter som tillämpas på byte som läses under begränsad inläsning, inklusive förfrågningar där `Content-Length` saknas, är ogiltig eller är missvisande; överskridande ger `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(inte angivet — ingen gräns för antal förfrågningar)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** denna äldre gräns för ANTAL förfrågningar tillämpas nu endast när den uttryckligen anges. Om den lämnas oangiven (standardvärdet) styrs insläppet av tunga chattförfrågningar i stället av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — en automatiskt härledd BYTE-budget dimensionerad utifrån processens faktiska minnesgräns i **en process** (en V8-heap). Två överlappande `/v1/responses` med ~750 000 token får heapar på ~12 Gi att avbrytas (#7849) — en **minnesbudgetvarning**, inte en hård produktgräns på 2. En frisk process (heapen under avlastningskvoten) KAN släppa in fler samtidiga långa `/v1/responses` via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Tiotals långvariga SSE-klienter (40–50) begränsas av heapen + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, inte av ”max 2”. Att blint höja detta för att ”använda värden” återinför #7849. Multiplicera **heapar** med **N oberoende `DATA_DIR`-kataloger** (#11024); använd aldrig `replicas>1` för en och samma SQLite-fil. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatiskt härledd)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** åsidosättning av den automatiskt härledda bytebudgeten för inläsning (25 % av den stramare minnesgränsen för V8/cgroup dividerat med 8× tillfällig förstärkning). Härledda och uttryckliga värden begränsas till 8 MiB–2 GiB. En kropp som är större än den effektiva budgeten misslyckas omedelbart med `413 body_exceeds_budget`; resurskonkurrens mellan kroppar som var för sig kan hanteras ger fortsatt ett återförsöksbart `503`. 40–50 samtidiga långvariga SSE-klienter begränsas av denna budget + heapen, inte av ett hårt ”max 2”. Läs `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` på `/api/monitoring/health` före justering. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Avlastningskvot för heaptryck (`heapUsed / heap_size_limit`) för BYTE- och STRUCTURE-baserat insläpp av tunga förfrågningar (#10183, #10268, #10437). En samtidig tung förfrågan som överskrider `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` avlastas endast med det återförsöksbara svaret `503` när heapen OCKSÅ ligger på eller över denna kvot; med en frisk heap släpps den i stället in via extra kapacitet för frisk heap. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standardvärde `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begränsad extrakapacitet för snabbvägen vid frisk heap (#10437) för **både** STRUCTURE och BYTE (`admitChatRequest`, inklusive kroppar ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Utan denna gräns gick varje förfrågan vid hög belastning men frisk heap förbi insläppskontrollen utan något tak. När så här många samtidiga lås är aktiva via vägen för frisk heap går ytterligare förfrågningar vid hög belastning vidare till SAMMA väg för begränsad väntan/avlastning som används vid faktiskt heaptryck. `0` inaktiverar förbikopplingen helt. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Antal meddelanden som klassificerar en chattförfrågan som tung även när dess kropp ligger under bytetröskeln. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Antal verktyg som klassificerar en chattförfrågan som tung även när dess kropp ligger under bytetröskeln. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservativ uppskattning av antal token baserad på strängstorlek som klassificerar en förfrågan som tung; detta är ett mått på insläppskostnad, inte leverantörens tokenisering för fakturering. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (inaktiverad) | `src/shared/middleware/chatBodyAdmission.ts` | Valfritt, uttryckligen aktiverat tak för chatthistorik. Inaktiverat som standard: ett antal meddelanden är en driftsättningspolicy, inte en universell egenskap hos en förfrågan, och en begränsning här avvisar konversationer med ett slutgiltigt `413` innan komprimeringskedjan kan göra dem hanterbara. Heaptillväxten begränsas av `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` och avlastningen vid heaptryck. Ange ett positivt värde i minnesbegränsade driftsättningar som behöver ett hårt tak; överskridande ger då ett strukturerat `413` som anger att komprimering krävs. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Hård gräns för ett icke-strömmande svar från uppströmstjänsten som buffras helt i minnet. Om gränsen överskrids avbryts uppströmsläsaren och förfrågan misslyckas snabbt i stället för att en obegränsad sträng växer tills heapen är uttömd. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximalt antal byte på nätet som vidarebefordras från uppströmssvarets huvuden. När budgeten överskrids tas huvuden med lägre prioritet (t.ex. anpassade `x-codex-*`, `x-oai-request-id`) bort för att hålla sig inom vanliga omvända proxyservrars gränser för huvuden. Ange ett högre värde för att vidarebefordra mer uppströmsmetadata, på bekostnad av större svarshuvuden. | -| `CORS_ORIGIN` | _(ej angivet)_ | `src/server/cors/origins.ts` | Äldre CORS-tillåtelselista för ett enskilt ursprung. Använd helst `CORS_ALLOWED_ORIGINS` för nya driftsättningar. CORS gäller endast för webbläsarbaserade API-klienter från andra ursprung; autentiserade skrivningar i kontrollpanelen använder i stället förfrågningar från samma ursprung samt sessionsbunden CSRF-skydd. | -| `CORS_ALLOWED_ORIGINS` | _(ej angivet)_ | `src/server/cors/origins.ts` | Kommaseparerad CORS-tillåtelselista. Inget jokertecken skickas om inte `CORS_ALLOW_ALL=true` uttryckligen har konfigurerats. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Nödutväg endast för utveckling som återger valfritt `Origin` från webbläsaren. Aktivera inte detta i delade miljöer eller produktionsmiljöer. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Blockera leverantörsanrop som riktas mot privata IP-intervall, loopbackintervall eller länklokala IP-intervall. Inaktivera endast i isolerade testmiljöer. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Tillåt leverantörs-URL:er som pekar på privata/lokala nätverk (localhost, 192.168.x.x, 10.x.x.x osv.). **KRÄVS för leverantörer med egen drift** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). När värdet är `false` avvisar kontrollpanelen validering av lokala URL:er. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Tillåt att leverantörer läggs till/valideras på lokala/privata adresser (127.0.0.1, localhost, LAN, privata intervall) – begränsat till sökvägen för leverantörsvalidering. **Standardvärde `true`** (lokalt först); ange `false` för att tillämpa strikt blockering som endast tillåter offentliga adresser. Slutpunkter för molnmetadata (169.254.169.254, metadata.google.internal) förblir blockerade oavsett inställning. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Låt vägarna `/v1/audio/*` (transkriberingar, tal, översättningar) använda en OpenAI-kompatibel leverantörsnod som finns utanför localhost. Av som standard – dirigering av ljud till en fjärrvärd ändrar identiteten för utgående trafik och måste vara ett uttryckligt operatörsbeslut. Loopback-/privata noder (localhost, 127.0.0.1, 172.16-31.x) är alltid tillåtna och påverkas inte. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Låt `POST /v1/rerank` (och minnesmotorns loopback-steg för omrankning) använda en OpenAI-kompatibel leverantörsnod som finns utanför localhost – en LAN-dator eller Tailscale-nod som kör TEI, Infinity, vLLM osv. Av som standard – dirigering till en fjärrvärd ändrar identiteten för utgående trafik och måste vara ett uttryckligt operatörsbeslut. Loopback-noder (localhost, 127.0.0.1, 172.16-31.x) är alltid tillåtna och påverkas inte. Fjärrnoder måste även godkännas av leverantörspolicyn för utgående URL:er (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); värdar för molnmetadata dirigeras aldrig till. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | När OIDC är aktiverat inaktiveras lösenordsinloggning så att användare endast kan autentisera sig via enkel inloggning med OIDC. Det korta aliaset `OIDC_DISABLE_PASSWORD_LOGIN` godtas också; kontrollpanelens funktionsflagga med samma nyckel har företräde. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kort alias för `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Variabel | Standardvärde | Källfil | Beskrivning | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt som kombineras med maskinvaruidentifierare för att skapa ett maskinfingeravtryck. Ändra för varje driftsättning för att säkerställa isolering. | +| `OMNIROUTE_CLI_SALT` | _(inte angivet = slumpmässigt salt per installation som sparas i `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | HMAC-salt för härledning av den lokala CLI-autentiseringstoken. Om detta värde anges roteras alla CLI-token på maskinen, och värdet har alltid företräde framför det sparade saltet. Se `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Anger flaggan `Secure` för sessionscookies. **Måste vara `true`** vid körning bakom HTTPS. | +| `REQUIRE_API_KEY` | `false` | API-mellanprogramvara | När värdet är `true` måste alla proxyförfrågningar till `/v1/*` innehålla en giltig API-nyckel. **Den här flaggan styr inte åtkomsten till `GET /v1/models`**, som i stället följer inloggningsinställningen för kontrollpanelen (`requireAuthForModels`) – därför innebär ett `401`-svar från `/v1/models` INTE att inferens är skyddad. Se `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Tillåter att fullständiga API-nyckelvärden visas i kontrollpanelens gränssnitt. Kan konfigureras via kontrollpanelens funktionsflaggor; utgör en säkerhetsrisk i delade instanser. | +| `NO_LOG_API_KEY_IDS` | _(tomt)_ | `src/lib/compliance/index.ts` | Kommaseparerade API-nyckel-ID:n som undantas från loggning av förfrågningar (GDPR-efterlevnad). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(inte angivet = obegränsat)_ | `src/shared/utils/apiKeyPolicy.ts` | Reservbudget per dag för förfrågningar som tillämpas på API-nycklar vars kolumn `rate_limits` är null. Inte angivet eller tomt: ingen implicit gräns (#2289, #11017). `0` innebär samma sak (obegränsat). Ett positivt heltal N aktiverar N/dag, 5N/vecka och 20N/månad. Felaktiga värden som inte är tomma återgår till de äldre intervallen 1000/dag, 5000/vecka och 20000/månad. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Högsta tillåtna storlek på förfrågningskroppen. Avvisar nyttolaster som överskrider denna gräns. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Faktiska förfrågningskroppar som är lika stora som eller större än detta tröskelvärde erhåller det atomära, proceslokala tillträdeslåset för tungviktiga förfrågningar före JSON-tolkning (BYTE-sökvägen, inklusive `POST /v1/responses`). Samma [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437)-undantag för tillräcklig ledig kapacitet som för strukturtunga förfrågningar gäller; fortfarande begränsat av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110), så att [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) inte återkommer. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Hård gräns för chattrutter som tillämpas på byte som läses under begränsad inläsning, inklusive förfrågningar där `Content-Length` saknas, är ogiltig eller är missvisande; överskridande ger `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(inte angivet — ingen gräns för antal förfrågningar)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** denna äldre gräns för ANTAL förfrågningar tillämpas nu endast när den uttryckligen anges. Om den lämnas oangiven (standardvärdet) styrs insläppet av tunga chattförfrågningar i stället av `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — en automatiskt härledd BYTE-budget dimensionerad utifrån processens faktiska minnesgräns i **en process** (en V8-heap). Två överlappande `/v1/responses` med ~750 000 token får heapar på ~12 Gi att avbrytas (#7849) — en **minnesbudgetvarning**, inte en hård produktgräns på 2. En frisk process (heapen under avlastningskvoten) KAN släppa in fler samtidiga långa `/v1/responses` via `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Tiotals långvariga SSE-klienter (40–50) begränsas av heapen + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, inte av ”max 2”. Att blint höja detta för att ”använda värden” återinför #7849. Multiplicera **heapar** med **N oberoende `DATA_DIR`-kataloger** (#11024); använd aldrig `replicas>1` för en och samma SQLite-fil. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(automatiskt härledd)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** åsidosättning av den automatiskt härledda bytebudgeten för inläsning (25 % av den stramare minnesgränsen för V8/cgroup dividerat med 8× tillfällig förstärkning). Härledda och uttryckliga värden begränsas till 8 MiB–2 GiB. En kropp som är större än den effektiva budgeten misslyckas omedelbart med `413 body_exceeds_budget`; resurskonkurrens mellan kroppar som var för sig kan hanteras ger fortsatt ett återförsöksbart `503`. 40–50 samtidiga långvariga SSE-klienter begränsas av denna budget + heapen, inte av ett hårt ”max 2”. Läs `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` på `/api/monitoring/health` före justering. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Avlastningskvot för heaptryck (`heapUsed / heap_size_limit`) för BYTE- och STRUCTURE-baserat insläpp av tunga förfrågningar (#10183, #10268, #10437). En samtidig tung förfrågan som överskrider `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` avlastas endast med det återförsöksbara svaret `503` när heapen OCKSÅ ligger på eller över denna kvot; med en frisk heap släpps den i stället in via extra kapacitet för frisk heap. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standardvärde `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Begränsad extrakapacitet för snabbvägen vid frisk heap (#10437) för **både** STRUCTURE och BYTE (`admitChatRequest`, inklusive kroppar ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Utan denna gräns gick varje förfrågan vid hög belastning men frisk heap förbi insläppskontrollen utan något tak. När så här många samtidiga lås är aktiva via vägen för frisk heap går ytterligare förfrågningar vid hög belastning vidare till SAMMA väg för begränsad väntan/avlastning som används vid faktiskt heaptryck. `0` inaktiverar förbikopplingen helt. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Antal meddelanden som klassificerar en chattförfrågan som tung även när dess kropp ligger under bytetröskeln. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Antal verktyg som klassificerar en chattförfrågan som tung även när dess kropp ligger under bytetröskeln. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Konservativ uppskattning av antal token baserad på strängstorlek som klassificerar en förfrågan som tung; detta är ett mått på insläppskostnad, inte leverantörens tokenisering för fakturering. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (inaktiverad) | `src/shared/middleware/chatBodyAdmission.ts` | Valfritt, uttryckligen aktiverat tak för chatthistorik. Inaktiverat som standard: ett antal meddelanden är en driftsättningspolicy, inte en universell egenskap hos en förfrågan, och en begränsning här avvisar konversationer med ett slutgiltigt `413` innan komprimeringskedjan kan göra dem hanterbara. Heaptillväxten begränsas av `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` och avlastningen vid heaptryck. Ange ett positivt värde i minnesbegränsade driftsättningar som behöver ett hårt tak; överskridande ger då ett strukturerat `413` som anger att komprimering krävs. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Hård gräns för ett icke-strömmande svar från uppströmstjänsten som buffras helt i minnet. Om gränsen överskrids avbryts uppströmsläsaren och förfrågan misslyckas snabbt i stället för att en obegränsad sträng växer tills heapen är uttömd. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Maximalt antal byte på nätet som vidarebefordras från uppströmssvarets huvuden. När budgeten överskrids tas huvuden med lägre prioritet (t.ex. anpassade `x-codex-*`, `x-oai-request-id`) bort för att hålla sig inom vanliga omvända proxyservrars gränser för huvuden. Ange ett högre värde för att vidarebefordra mer uppströmsmetadata, på bekostnad av större svarshuvuden. | +| `CORS_ORIGIN` | _(ej angivet)_ | `src/server/cors/origins.ts` | Äldre CORS-tillåtelselista för ett enskilt ursprung. Använd helst `CORS_ALLOWED_ORIGINS` för nya driftsättningar. CORS gäller endast för webbläsarbaserade API-klienter från andra ursprung; autentiserade skrivningar i kontrollpanelen använder i stället förfrågningar från samma ursprung samt sessionsbunden CSRF-skydd. | +| `CORS_ALLOWED_ORIGINS` | _(ej angivet)_ | `src/server/cors/origins.ts` | Kommaseparerad CORS-tillåtelselista. Inget jokertecken skickas om inte `CORS_ALLOW_ALL=true` uttryckligen har konfigurerats. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Nödutväg endast för utveckling som återger valfritt `Origin` från webbläsaren. Aktivera inte detta i delade miljöer eller produktionsmiljöer. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Äldre alias. Dess toggle i kontrollpanelen (en åsidosättning i databasen) läses innan detta miljövärde; `false`, `0`, `no` eller `off` i något av dem stänger av värdkontrollerna på samma sätt som `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` gör, även när den flaggans toggle i kontrollpanelen är sparad som av; andra värden lämnar över skyddet till de två flaggorna nedan. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Stänger av skyddets värdkontroller för utgående URL:er, inklusive blockeringen av molnmetadata (hela 169.254.0.0/16, metadata.google.internal), på de leverantörssökvägar som följer skyddsläget: URL-validering, modellidentifiering, bas-URL:er för leverantörsnoder (inklusive fjärrnoder för omrankning) och proxy-reservtestet, bland andra utgående sökvägar nära leverantörer, som hämtning av fjärrbilder/-media. Den tillåter även privata webhook-mål, utom slutpunkter för molnmetadata/länklokala adresser, som förblir blockerade även när denna flagga är på. Chattförfrågningar som skickas via `BaseExecutor.execute()` behåller sin molnmetadatakontroll. Lokala leverantörer fungerar med standardvärdena: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (standard `true`) tillåter redan lokala URL:er och LAN-URL:er på sökvägarna för validering, identifiering och leverantörsnoder; proxy-reservtestet och privata webhook-mål konsulterar endast denna flagga och blockerar lokala/LAN-värdar medan den är av. Åsidosätter `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Ett värde som sparats från kontrollpanelens toggle har företräde framför denna variabel. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Tillåt leverantörs-URL:er på lokala/privata adresser (127.0.0.1, localhost, LAN, privata intervall) för leverantörsvalidering, modellidentifiering, chatt och de övriga utgående sökvägarna nära leverantörer som skyddsläget styr. **Standardvärde `true`** (lokalt först): skyddet blockerar då slutpunkter för molnmetadata – hela 169.254.0.0/16 samt de kända metadata-värdnamnen (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Ange `false` för läget endast offentligt, som även blockerar privata värdar och loopback-värdar. Skyddet kontrollerar värdnamnet eller IP-literalen som den är skriven, innan någon DNS-uppslagning; i läget endast offentligt löser dessutom safe-fetch-omslutningen upp värdnamnet och vägrar alla privata svar. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` stänger av dessa kontroller på de sökvägar som dess rad listar. Inbyggda lokala leverantörer, som LM Studio, Ollama, vLLM, Llamafile och Triton, hoppar över alla värdkontroller för nyckelvalidering och chatt. Ett värde som sparats från kontrollpanelens toggle har företräde framför denna variabel. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Låt vägarna `/v1/audio/*` (transkriberingar, tal, översättningar) använda en OpenAI-kompatibel leverantörsnod som finns utanför localhost. Av som standard – dirigering av ljud till en fjärrvärd ändrar identiteten för utgående trafik och måste vara ett uttryckligt operatörsbeslut. Loopback-/privata noder (localhost, 127.0.0.1, 172.16-31.x) är alltid tillåtna och påverkas inte. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Låt `POST /v1/rerank` (och minnesmotorns loopback-steg för omrankning) använda en OpenAI-kompatibel leverantörsnod som finns utanför localhost – en LAN-dator eller Tailscale-nod som kör TEI, Infinity, vLLM osv. Av som standard – dirigering till en fjärrvärd ändrar identiteten för utgående trafik och måste vara ett uttryckligt operatörsbeslut. Loopback-noder (localhost, 127.0.0.1, 172.16-31.x) är alltid tillåtna och påverkas inte. En fjärrnods bas-URL måste även godkännas av leverantörspolicyn för utgående URL:er (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | När OIDC är aktiverat inaktiveras lösenordsinloggning så att användare endast kan autentisera sig via enkel inloggning med OIDC. Det korta aliaset `OIDC_DISABLE_PASSWORD_LOGIN` godtas också; kontrollpanelens funktionsflagga med samma nyckel har företräde. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Kort alias för `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Checklista för säkerhetshärdning diff --git a/docs/i18n/sw/CONTRIBUTING.md b/docs/i18n/sw/CONTRIBUTING.md index b5083526c56b..5ef92f120782 100644 --- a/docs/i18n/sw/CONTRIBUTING.md +++ b/docs/i18n/sw/CONTRIBUTING.md @@ -391,18 +391,18 @@ Andika majaribio ya vipengele katika `tests/unit/` yanayojumuisha angalau: - [ ] Majaribio yanafaulu (`npm test`) - [ ] Ukaguzi wa lint unafaulu (`npm run lint`) -- [ ] Ujenzi unafanikiwa (`npm run build`) +- [ ] Uundaji unafaulu (`npm run build`) - [ ] Aina za TypeScript zimeongezwa kwa functions na interfaces mpya za umma -- [ ] Hakuna siri au thamani mbadala zilizowekwa moja kwa moja -- [ ] Vitambulisho vya umma vya upstream vimepachikwa kupitia `resolvePublicCred()` (angalia [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), kamwe si kama literals -- [ ] Majibu ya hitilafu hupitishwa kupitia `buildErrorBody()` / `sanitizeErrorMessage()` — hakuna stack traces ghafi katika miili ya majibu (angalia [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Amri za shell (`exec` / `spawn`) hupitisha thamani za runtime kupitia `env`, si kupitia uingizaji wa thamani kwenye string -- [ ] Ingizo zote zimethibitishwa kwa schemas za Zod -- [ ] **Kipande** cha changelog kimeongezwa chini ya `changelog.d/{features|fixes|maintenance}/-.md` kwa mabadiliko yanayoonekana kwa mtumiaji (angalia [`changelog.d/README.md`](./changelog.d/README.md)) — **usihariri** `CHANGELOG.md` moja kwa moja; vipande huunganishwa wakati wa toleo na kamwe havikinzani kati ya PR -- [ ] Nyaraka zimesasishwa (ikiwa inatumika) -- [ ] Hakuna arifa mpya za CodeQL / Secret-Scanning zilizofunguliwa, au kila moja imepuuzwa kwa uhalalishaji wa kiufundi unaorejelea hati husika ya `docs/security/` -- [ ] Routes zinazoanzisha child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) zimeainishwa kama `isLocalOnlyPath()` katika `src/server/authz/routeGuard.ts` — angalia [Kanuni Isiyobadilika #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Hakuna trailers za `Co-Authored-By` katika commit messages — commits lazima zionekane chini ya Git identity ya mmiliki wa repository pekee (Kanuni Isiyobadilika #16) +- [ ] Hakuna siri au thamani mbadala zilizowekwa moja kwa moja kwenye msimbo +- [ ] Vitambulisho vya upstream vya umma vimepachikwa kupitia `resolvePublicCred()` (tazama [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), kamwe si kama literals +- [ ] Majibu ya hitilafu yanapitia `buildErrorBody()` / `sanitizeErrorMessage()` — hakuna stack traces ghafi katika response bodies (tazama [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Amri za shell (`exec` / `spawn`) hupitisha thamani za wakati wa utekelezaji kupitia `env`, si kupitia string interpolation +- [ ] Ingizo zote zimethibitishwa kwa Zod schemas +- [ ] **Kipande** cha changelog kimeongezwa chini ya `changelog.d/{features|fixes|maintenance}/-.md` kwa mabadiliko yanayoonekana kwa watumiaji (tazama [`changelog.d/README.md`](./changelog.d/README.md)) — **usihariri** `CHANGELOG.md` moja kwa moja; vipande huunganishwa wakati wa toleo na kamwe havikinzani kati ya PRs +- [ ] Nyaraka zimesasishwa (ikiwa inahusika) +- [ ] Hakuna arifa mpya za CodeQL / Secret-Scanning zilizofunguliwa, au kila moja imekataliwa kwa uhalalisho wa kiufundi unaorejelea hati husika ya `docs/security/` +- [ ] Routes zinazoanzisha child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) zimeainishwa kama `isLocalOnlyPath()` katika `src/server/authz/routeGuard.ts` — tazama [Kanuni Kali #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Hakuna trailers za AI/bot za `Co-authored-by` katika commit messages (Kanuni Kali #16) — washirika wanadamu ambao kazi yao imetumika tena wanatambuliwa kwa trailers za kawaida za `Co-authored-by: Name ` --- diff --git a/docs/i18n/sw/docs/architecture/QUALITY_GATES.md b/docs/i18n/sw/docs/architecture/QUALITY_GATES.md index 4f49b04d7bab..11cfeb48fce8 100644 --- a/docs/i18n/sw/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/sw/docs/architecture/QUALITY_GATES.md @@ -17,262 +17,262 @@ wa kuiga mfumo huo huo, angalia ## Orodha ya malango na wasifu wa utekelezaji -### Uidhinishaji wa mgombea +### Kukubaliwa kwa mgombea Mitiririko ya kazi ya CI na Quality Gates kila mmoja hutoa uamuzi thabiti: `Gate / CI` na -`Gate / Quality`. Sera yao ya uidhinishaji yenye matoleo huorodhesha kila kazi ya awali -kama ya lazima au ya ushauri. Kazi husika ya lazima lazima ifanikiwe: matokeo +`Gate / Quality`. Sera yao ya kukubali yenye matoleo huorodhesha kila kazi ya awali +kuwa ya lazima au ya ushauri. Kazi husika ya lazima lazima ifanikiwe: matokeo yanayokosekana, yaliyoghairiwa, yaliyorukwa, yanayosubiri na yasiyojulikana hayawezi kuthibitisha PASS. Uainishaji halali wa docs-only au catalog-only unaweza kufanya njia ya msimbo isihusike; PR ya rasimu si mgombea anayekubalika. Lebo ya `hotfix` haiondoi hitaji la ushahidi. -Mitiririko yote miwili ya kazi inahusu PR na misukumo kwenda kwenye matawi ya main/release, uanzishaji wa mwenyewe na -matukio ya merge-group. Push, dispatch na merge-group huendesha uteuzi kamili. Forks -na merge groups hutumia waendeshaji wanaopangishwa kwa kazi ambazo vinginevyo huchagua waendeshaji -wanaojipangisha; uwezo wa kutosha wa huduma iliyopangishwa lazima uthibitishwe kabla ya uzinduzi. +Mitiririko yote miwili ya kazi inahusisha PR na usukumaji kwenda kwenye matawi ya main/release, uanzishaji wa +mikono na matukio ya merge-group. Usukumaji, uanzishaji na merge-group hutekeleza uteuzi kamili. Forks +na merge groups hutumia hosted runners kwa kazi ambazo vinginevyo huchagua self-hosted +runners; uwezo wa kutosha wa hosted lazima uthibitishwe kabla ya kusambaza mabadiliko. -Kila risiti ya JSON hutambulisha SHA iliyopakuliwa, utekelezaji wa mtiririko wa kazi na jaribio. -CLI hukataa kutolingana kwa SHA ya checkout/event. Majaribio ya mtiririko wa kazi huunganisha uanachama wa sera -na orodha ya `needs` ya kazi ya uamuzi ili njia mpya au iliyoondolewa isiweze kutoweka kimya kimya. -Risiti zinahusu mtiririko wao wenyewe wa kazi, si uchapishaji, upelekaji, au vipengele vya ndani -vya kichanganuzi kilichopo cha ushauri. Kuwasha majina yote mawili ya ukaguzi katika kanuni za tawi ni -badiliko tofauti la kiutawala; kuongeza kazi hizi pekee hakulindi tawi. +Kila risiti ya JSON hutambulisha SHA iliyochukuliwa, utekelezaji wa mtiririko wa kazi na jaribio. +CLI hukataa kutolingana kwa SHA ya checkout/event. Majaribio ya mtiririko wa kazi hufungamanisha uanachama wa sera +na orodha ya `needs` ya kazi ya uamuzi ili njia mpya au iliyoondolewa isipotee bila kutambuliwa. +Risiti hushughulikia mtiririko wake wenyewe wa kazi, si uchapishaji, upelekaji, au utendaji wa ndani +wa kichanganuzi kilichopo cha ushauri. Kuwasha majina yote mawili ya ukaguzi katika kanuni za tawi ni +badiliko tofauti la kiutawala; kuongeza kazi hizi hakulindi tawi moja kwa moja. ### Orodha ya uchanganuzi tuli -Orodha ya npm-alias yenye matoleo na uanachama wa uchanganuzi tuli hupatikana katika -`config/quality/gate-manifest.json`. Endesha `npm run check:gate-manifest` ili kuthibitisha -majina ya hati na amri kamili dhidi ya `package.json`; nyongeza, uondoaji na -mabadiliko ya amri husababisha kushindwa kwa hook ya ndani na kazi za uainishaji wa mabadiliko katika CI. -Alias si kazi ya mtiririko wa kazi, instance ya matrix au kesi ya jaribio: idadi hizi hazipaswi -kuwasilishwa kana kwamba zinaweza kutumika kwa kubadilishana. +Orodha ya npm-alias yenye matoleo na uanachama wa uchanganuzi tuli vinapatikana katika +`config/quality/gate-manifest.json`. Tekeleza `npm run check:gate-manifest` ili kuthibitisha +majina ya script na amri halisi dhidi ya `package.json`; nyongeza, uondoaji na +mabadiliko ya amri husababisha hook ya ndani na kazi za uainishaji wa mabadiliko katika CI kushindwa. +Alias si kazi ya mtiririko wa kazi, instance ya matrix au test case: hesabu hizi hazipaswi +kuwasilishwa kana kwamba zinaweza kubadilishana. Tumia `npm run quality:scan -- --list` au `npm run quality:scan:fast -- --list` -kukagua alias zilizochaguliwa bila kuzitekeleza. Kiendeshaji huita -sehemu ya kuingilia ya npm, kwa hivyo runtime yake (ikiwemo Bun pale iliposanidiwa) hudumishwa. -Manifest hurekodi alias zilizo nje ya wasifu huo kama zinazoitwa kando, na +kukagua aliases zilizochaguliwa bila kuzitekeleza. Runner huanzisha +npm entrypoint, hivyo runtime yake (ikiwemo Bun pale iliposanidiwa) huhifadhiwa. +Manifest hurekodi aliases zilizo nje ya wasifu huo kuwa zinaanzishwa kivyake, na amri za matengenezo zimepigwa marufuku katika wasifu wa uchanganuzi wa kusoma pekee. Wasifu huu unahusu uchanganuzi tuli pekee. Hauthibitishi majaribio ya bidhaa, -coverage, ufungashaji, ukaguzi wa nje au uidhinishaji kamili wa toleo la mgombea. -Uidhinishaji wa mtiririko wa kazi hutumia `config/quality/admission-policy.json` iliyounganishwa na -`scripts/quality/admission-verdict.mjs`. Wasifu wa release-observer hubaki tofauti; -kagua ukaguzi na risiti zake husika kwa kujitegemea. Orodha ya maelezo -iliyo hapa chini ni marejeleo, si uthibitisho kwamba lango liliendeshwa. +coverage, packaging, ukaguzi wa nje au ukubalifu kamili wa mgombea kwa toleo. +Ukubalishaji wa mtiririko wa kazi hutumia `config/quality/admission-policy.json` iliyounganishwa na +`scripts/quality/admission-verdict.mjs`. Wasifu wa release-observer unasalia tofauti; +kagua ukaguzi na risiti zake husika kivyake. Orodha ya maelezo +iliyo hapa chini ni rejeleo, si uthibitisho kwamba lango lilitekelezwa. -Hati zipo chini ya `scripts/check/` (malango ya sera) na `scripts/quality/` (injini ya ratchet). -Chanzo cha ukweli cha CI ni `.github/workflows/ci.yml`. +Scripts zinapatikana chini ya `scripts/check/` (malango ya sera) na `scripts/quality/` (injini ya ratchet). +Chanzo rasmi cha CI ni `.github/workflows/ci.yml`. ### Njia ya haraka ya PR ya toleo (`quality.yml`) -`.github/workflows/quality.yml` hukamilisha CI kwenye PR za main/release, misukumo ya protected-branch, -dispatch na merge groups. PR hutumia ukaguzi wa haraka uliochujwa kwa njia. Build rudufu iliyokuwa -imezimwa kabisa iliondolewa; ukaguzi halisi wa build/package/boot unabaki katika CI. - -| Kazi | Upeo | Inazuia | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `Docs Gates (fast-path)` | PR za nyaraka/msimbo; marejeleo ya nyaraka za API na docs-all | Ndiyo | -| `Fast Quality Gates` | PR za msimbo; ukaguzi tuli, typecheck, typecheck ya dashboard, majaribio ya unit yaliyoathiriwa | Ndiyo | -| `Forgotten sibling tests` | PR za msimbo; moduli zilizobadilishwa zinazofuatiliwa hadi kwa watumiaji tuli na majaribio sibling yanayoweza kuchaguliwa; njia za barrel na dynamic-import huripotiwa kama uchunguzi wa ushauri, pamoja na tofauti zilizorejelewa za allowlist | **Ushauri** | -| `Vitest (fast-path)` | PR za msimbo; mkusanyiko wa haraka wa vitest | Ndiyo | -| `Unit Tests fast-path` | PR za msimbo; mkusanyiko wa unit wenye shard 4 | Ndiyo | -| `No new ESLint warnings` | PR za msimbo; ulinzi wa lint unaotambua suppressions | Ndiyo, ikijumuisha forks | -| `Merge integrity (changelog + generated skills)` | PR zisizo rasimu; usawazishaji wa changelog na skill zilizozalishwa | Ndiyo, ikijumuisha forks | - -#### Ripoti ya majaribio sibling yaliyosahaulika - -`npm run check:forgotten-sibling-tests` hutumia tena kitatuzi cha import kilicho nyuma ya ramani ya athari za majaribio. -Kwa kila moduli ya uzalishaji iliyobadilishwa, huripoti minyororo bainifu ya -`changed module/symbol -> static consumer -> candidate sibling test` wakati jaribio linaloweza kuchaguliwa -halipo katika diff ya pull-request. Muhtasari wa Markdown na matokeo ya JSON huhifadhiwa kama -artifact ya mtiririko wa kazi ya `forgotten-sibling-tests` kwa ajili ya urekebishaji kabla ya uzinduzi wowote unaozuia. - -Uhamishaji upya wa barrel na uingizaji dhabiti ni uchunguzi wa utatuzi pekee; hauwahi kuunda +`.github/workflows/quality.yml` hukamilisha CI kwenye PR za main/release, usukumaji kwenye matawi +yanayolindwa, uanzishaji na merge groups. PR hutumia ukaguzi wa haraka uliochujwa kulingana na path. Build nakala +iliyozimwa kabisa iliondolewa; ukaguzi halisi wa build/package/boot unasalia katika CI. + +| Kazi | Upeo | Inazuia | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `Docs Gates (fast-path)` | PR za docs/code; marejeleo ya API docs na docs-all | Ndiyo | +| `Fast Quality Gates` | PR za msimbo; ukaguzi tuli, typecheck, typecheck ya dashboard, unit tests zilizoathiriwa | Ndiyo | +| `Forgotten sibling tests` | PR za msimbo; moduli zilizobadilishwa zinazofuatiliwa hadi kwa watumiaji tuli na sibling tests zinazoweza kuchaguliwa; njia za barrel na dynamic-import huripotiwa kama uchunguzi wa ushauri, pamoja na vighairi vya allowlist vilivyorejelewa | **Ushauri** | +| `Vitest (fast-path)` | PR za msimbo; mkusanyiko wa haraka wa vitest | Ndiyo | +| `Unit Tests fast-path` | PR za msimbo; mkusanyiko wa unit wa shards 4 | Ndiyo | +| `No new ESLint warnings` | PR za msimbo; kinga ya lint inayozingatia suppressions | Ndiyo, ikiwemo forks | +| `Merge integrity (changelog + generated skills)` | PR zisizo rasimu; ulandanishaji wa changelog na skill zilizozalishwa | Ndiyo, ikiwemo forks | + +#### Ripoti ya forgotten sibling tests + +`npm run check:forgotten-sibling-tests` hutumia tena import resolver iliyo nyuma ya ramani ya athari za majaribio. +Kwa kila moduli ya uzalishaji iliyobadilishwa, huripoti misururu thabiti ya +`changed module/symbol -> static consumer -> candidate sibling test` wakati test inayoweza kuchaguliwa +haipo katika tofauti ya pull-request. Muhtasari wa Markdown na matokeo ya JSON huhifadhiwa kama +artifact ya mtiririko wa kazi ya `forgotten-sibling-tests` kwa ajili ya usawazishaji kabla ya utekelezaji wowote wa kuzuia. + +Uhamishaji upya wa barrel na uingizaji wa kidinamiki ni uchunguzi wa utatuzi pekee; kamwe hauleti ugunduzi unaozuia. Vighairi vilivyokaguliwa vinapatikana katika `config/quality/forgotten-sibling-allowlist.json`. Kila ingizo lazima litaje jaribio la mtumiaji na la mgombea, -litoe sababu mahususi, na liunganishe na suala au ombi la kuvuta la GitHub. Maingizo yenye muundo usio sahihi -hukataliwa kwa chaguo-msingi. Vighairi haviwezi kuficha jaribio la mgombea lililofutwa au tofauti inayoongeza `.skip`/`.todo`; -udhoofishaji wa madai na ufichaji mwingine hubaki chini ya lango linalozuia kwa kujitegemea la -`check:test-masking`. +litoe sababu mahususi, na liunganishe suala au ombi la kuvuta la GitHub. Maingizo yenye muundo usio sahihi hushindwa +kwa hali funge. Vighairi haviwezi kuficha jaribio la mgombea lililofutwa au tofauti inayoongeza `.skip`/`.todo`; +udhoofishaji wa madai na ufichaji mwingine unasalia chini ya lango la +`check:test-masking` linalozuia kwa kujitegemea. ### Kazi: `lint` -Huendeshwa kwenye kila PR kwenda `main`. Huzuia uunganishaji ikishindwa. - -| Hati (`npm run ...`) | Huthibitisha | Inazuia | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:node-runtime` | Toleo la Node.js liko ndani ya masafa yanayotumika | Ndiyo | -| `check:cycles` | Uingizaji wa mzunguko — moduli zote za `src/` + `open-sse/` | Ndiyo | -| `check:route-validation:t06` | Skima za Zod zipo kwenye njia zote (sera ya Kiwango cha 6) | Ndiyo | -| `check:any-budget:t11` | Idadi ya `@ts-expect-error // any` haizidi bajeti (catraca ya Kiwango cha 11) | Ndiyo | -| `check:provider-consistency` | Kila mtoa huduma katika `providers.ts` ana ingizo linalolingana katika `providerRegistry.ts` (na kinyume chake, ndani ya orodha ya ruhusa) | Ndiyo | -| `check:model-lifecycle` | Majedwali matatu ya uelekezaji yanayotunzwa kwa mikono yanaendelea kuwiana na nakala ya hali ya mzunguko wa maisha iliyohifadhiwa (#11503): `FITNESS_TABLE` (`taskFitness.ts`) haitoi alama kwa id yoyote iliyostaafishwa ambayo `REGISTRY` inaweza kuelekeza; kila lengo la `BUILT_IN_ALIASES` lipo katika `REGISTRY` na halipo katika nakala ya id zilizostaafishwa; kila id iliyostaafishwa ambayo bado ipo katika `REGISTRY` inaelekezwa kwingine au imeorodheshwa katika `allowedRetiredInCatalog`; na hakuna chanzo au lengo la `DEFAULT_DEGRADATION_MAP` linaloonekana kuwa limestaafishwa katika nakala hiyo. Hili halithibitishi kwamba modeli inahudumiwa kwa sasa na huduma ya juu iliyo hai. Nje ya mtandao — hulinganisha dhidi ya `config/quality/model-lifecycle.json`, inayosasishwa kwa mikono kwa kutumia `npm run quality:refresh-model-lifecycle` (mtandao; haijaunganishwa kwenye CI). `allowedRetiredInCatalog` ni utaratibu wa kupunguza orodha hatua kwa hatua: ongeza ingizo tu likiwa na suala la ufuatiliaji. | Ndiyo | -| `check:fetch-targets` | Kila `fetch("/api/...")` katika `src/` ya upande wa mteja huelekezwa kwenye `route.ts` halisi | Ndiyo | -| `check:deps` | Vitegemezi vyote vinavyoweza kusakinishwa kwa `npm install` katika kila `package.json` kwenye hazina vimo katika `dependency-allowlist.json`; vifurushi vipya visivyofungwa kwenye toleo mahususi au vilivyosakinishwa kimakosa kutokana na majina yanayofanana vinaalamishwa | Ndiyo | -| `audit:deps` | `npm audit` (mzizi + electron) — hakuna tahadhari za kiwango cha juu/kritikali (inaingiliana na osv `check:vuln-ratchet`; tazama Orodha ya Maboresho ya Kurahisisha) | Ndiyo | -| `check:lockfile` | Uadilifu wa `package-lock.json` — sajili ya https, heshi za uadilifu, hakuna ubatilishaji wa seva pangishi | Ndiyo | -| `check:licenses` | Orodha ya leseni za SPDX zinazoruhusiwa kwa vitegemezi vya uzalishaji | Ndiyo | -| `check:tracked-artifacts` | Hakuna mabaki ya ujenzi / viungo vya kiishara vya `node_modules` vilivyowekwa kwenye hazina (pia huendeshwa katika husky pre-commit; pre-push imefanywa nyepesi kimakusudi — #6716) | Ndiyo | -| `check:ai-attribution` | Hakuna mstari wa mwisho wa AI/boti wa `Co-Authored-By` au kijachini cha uzalishaji wa AI katika commits, kichwa au maelezo ya PR — Kanuni Ngumu #16 (katika kitanzi cha fast-gates cha `quality.yml` kwa PR→`release/**` — husoma payload ya tukio, haifanyi chochote nje ya PR — na hatua ya PR pekee katika lint ya `ci.yml` kwa PR→`main`; pia hook ya husky ya `commit-msg`; waandishi wenza wa kibinadamu wanaruhusiwa; #14436) | -| `check:vitest-exclusions` | Kila kutengwa kwa Vitest hutaja suala la ufuatiliaji na huonekana katika `config/quality/vitest-exclusions.json` (#13204) | Ndiyo | -| `check:file-size` | Hakuna faili ya msimbo-chanzo inayozidi kikomo cha kila kiendelezi (ratchet: faili kubwa zilizogandishwa katika orodha ya `frozen`) | Ndiyo | -| `check:error-helper` | Majibu ya hitilafu katika vitekelezaji/vishughulikiaji hutumia `buildErrorBody()` / `sanitizeErrorMessage()` (Kanuni Ngumu #12) | Ndiyo | -| `check:migration-numbering` | Faili za SQL za uhamishaji zimepewa nambari zinazofuatana, bila mapengo au marudio | Ndiyo | -| `check:public-creds` | Hakuna thamani halisi za OAuth `client_id`/`client_secret` au funguo za Firebase Web nje ya `publicCreds.ts` (Kanuni Ngumu #11) | Ndiyo | -| `check:db-rules` | Hakuna SQL ghafi nje ya moduli za `src/lib/db/`; hakuna uingizaji wa pamoja kutoka `localDb.ts` (Kanuni Ngumu #2/#5) | Ndiyo | -| `check:known-symbols` | Vitekelezaji vya watoa huduma, mikakati ya uelekezaji, na vitafsiri vilivyosajiliwa katika majedwali yao ya usambazaji vinalingana na faili zilizo kwenye diski—hakuna alama yatima au ambazo hazijatangazwa | Ndiyo | -| `check:route-guard-membership` | Kila njia inayoanzisha mchakato mtoto imeainishwa na `isLocalOnlyPath()` (Kanuni Ngumu #15/#17) | Ndiyo | -| `check:test-discovery` | Kila faili ya `*.test.ts` / `*.spec.ts` katika hazina hukusanywa na angalau kiendesha majaribio kimoja (ratchet: orodha ya faili yatima katika `test-discovery-baseline.json` inaweza kupungua tu) | Ndiyo | -| `check:agent-skills-sync` | Artefakti zilizozalishwa za agent-skills zinalingana na katalogi yao chanzo (hakuna tofauti) | -| `check:provider-asset-provenance` | Nembo/rasilimali za mtoa huduma zina ingizo lililorekodiwa la asili | -| `lint:json` | Faili za usanidi za JSON zinachanganuliwa na zinakidhi kanuni za lint za repo | -| `typecheck:core` | Ukusanyaji wa TypeScript bila hitilafu (maonyo ya ushauri pekee) | Ndiyo | -| `typecheck:noimplicit:core` | `noImplicitAny` kali — kwa ajili ya siku zijazo; maeneo mengi ya simu yaliyokuwepo tayari bado yanahitaji ufafanuzi wa aina | **Ushauri** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` yenye upeo wa `src/app/(dashboard)/**` (#7033) — orodha iliyoratibiwa ya faili 27 inayoruhusiwa ya `typecheck:core` haijumuishi TSX yoyote ya dashibodi, na `next build` pia haikagui aina zake kamwe (`next.config.mjs` huweka `ignoreBuildErrors: true`), kwa hivyo marejeo yaliyovunjika ya vitambulisho huko (#6625/#6909) hayakuonekana na CI. Tofauti hulinganishwa na msingi uliogandishwa wa idadi kwa kila faili/kila msimbo wa TS (`config/quality/dashboard-typecheck-baseline.json`, muundo uleule wa utekelezaji dhidi ya msingi uliopitwa na wakati kama `check:known-symbols`) — ni hitilafu MPYA pekee zinazozidi idadi iliyowekwa kwenye msingi ndizo zinazofanya lango lishindwe; punguza hatua kwa hatua kwa kutumia `--update` hitilafu iliyokuwepo tayari inaporekebishwa. | Ndiyo | +Huendeshwa kwa kila PR kwenda `main`. Huzuia uunganishaji inaposhindwa. + +| Hati (`npm run ...`) | Huthibitisha | Inazuia | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Toleo la Node.js liko ndani ya masafa yanayotumika | Ndiyo | +| `check:cycles` | Uingizaji wa mzunguko katika `src/` + `open-sse/` yote (unategemea AST, `paths` za tsconfig zimetatuliwa). Bare = ushauri, huorodhesha mizunguko. `check:cycles:ratchet` (kile ambacho CI huendesha) huzuia idadi inapozidi kikomo cha `metrics.cycles` katika `quality-baseline.json` — kwa sasa 14, `direction: down`, kwa hivyo inaweza kupungua pekee (#15159 G-01/G-02) | Ndiyo (ratchet) | +| `check:route-validation:t06` | Schema za Zod zipo kwenye route zote (sera ya Tier 6) | Ndiyo | +| `check:any-budget:t11` | Idadi ya `@ts-expect-error // any` haizidi bajeti (catraca ya Tier 11) | Ndiyo | +| `check:provider-consistency` | Kila mtoa huduma katika `providers.ts` ana ingizo linalolingana katika `providerRegistry.ts` (na kinyume chake, ndani ya orodha ya kuruhusu) | Ndiyo | +| `check:model-lifecycle` | Majedwali matatu ya uelekezaji yanayotunzwa kwa mkono yanaendelea kuendana na picha ya hali ya mzunguko wa maisha iliyohifadhiwa (#11503): `FITNESS_TABLE` (`taskFitness.ts`) haitoi alama kwa kitambulisho chochote kilichostaafishwa ambacho `REGISTRY` inaweza kuelekeza; kila lengo la `BUILT_IN_ALIASES` lipo katika `REGISTRY` na halipo katika picha ya hali ya vitambulisho vilivyostaafishwa; kila kitambulisho kilichostaafishwa ambacho bado kipo katika `REGISTRY` kinaelekezwa mbele au kimeorodheshwa katika `allowedRetiredInCatalog`; na hakuna chanzo wala lengo la `DEFAULT_DEGRADATION_MAP` linaloonekana kuwa limestaafishwa katika picha hiyo ya hali. Hili halithibitishi kwamba modeli kwa sasa inahudumiwa na chanzo hai cha juu. Nje ya mtandao — hulinganisha dhidi ya `config/quality/model-lifecycle.json`, inayosasishwa kwa mkono kwa kutumia `npm run quality:refresh-model-lifecycle` (mtandao; haijaunganishwa katika CI). `allowedRetiredInCatalog` ni utaratibu wa kupunguza hatua kwa hatua: ongeza ingizo tu likiwa na suala la ufuatiliaji. | Ndiyo | +| `check:fetch-targets` | Kila `fetch("/api/...")` katika `src/` ya upande wa mteja hutatuliwa hadi `route.ts` halisi | Ndiyo | +| `check:deps` | Vitegemezi vyote vinavyoweza kusakinishwa kwa `npm install` katika kila `package.json` kwenye hifadhi vipo katika `dependency-allowlist.json`; vifurushi vipya visivyofungwa kwa toleo maalum au vilivyosajiliwa kwa jina linalofanana kimakusudi vinawekewa alama | Ndiyo | +| `audit:deps` | `npm audit` (mzizi + electron) — hakuna tahadhari za kiwango cha juu/hatari sana (inaingiliana na osv `check:vuln-ratchet`; angalia Orodha ya Masuala ya Kuratibiwa) | Ndiyo | +| `check:lockfile` | Uadilifu wa `package-lock.json` — sajili ya https, heshi za uadilifu, hakuna ubatilishaji wa seva mwenyeji | Ndiyo | +| `check:licenses` | Orodha ya leseni za SPDX zinazoruhusiwa kwa vitegemezi vya uzalishaji | Ndiyo | +| `check:tracked-artifacts` | Hakuna mabaki ya ujenzi / viungo vya ishara vya `node_modules` vilivyowekwa kwenye hazina (pia huendeshwa katika husky pre-commit; pre-push imefanywa kuwa nyepesi kimakusudi — #6716) | Ndiyo | +| `check:ai-attribution` | Hakuna kionjo cha `Co-Authored-By` cha AI/boti au kijachini cha uzalishaji wa AI katika commit, kichwa au maelezo ya PR — Kanuni Ngumu #16 (katika mzunguko wa ukaguzi wa haraka wa `quality.yml` kwa PR→`release/**` — husoma payload ya tukio, haifanyi chochote nje ya PR — na hatua ya PR pekee katika lint ya `ci.yml` kwa PR→`main`; pia hook ya husky `commit-msg`; waandishi-wenza binadamu wanaruhusiwa; #14436) | +| `check:vitest-exclusions` | Kila utengaji wa Vitest hutaja suala la ufuatiliaji na huonekana katika `config/quality/vitest-exclusions.json` (#13204) | Ndiyo | +| `check:file-size` | Hakuna faili ya msimbo chanzo inayozidi kikomo cha kila kiendelezi (ratchet: faili kubwa zilizogandishwa katika orodha ya `frozen`) | Ndiyo | +| `check:error-helper` | Majibu ya hitilafu katika vitekelezaji/vishughulikiaji hutumia `buildErrorBody()` / `sanitizeErrorMessage()` (Kanuni Ngumu #12) | Ndiyo | +| `check:migration-numbering` | Faili za SQL za uhamishaji zimepewa nambari kwa mfuatano, bila mapengo au marudio | Ndiyo | +| `check:public-creds` | Hakuna OAuth `client_id`/`client_secret` halisi au funguo za Firebase Web nje ya `publicCreds.ts` (Kanuni Ngumu #11) | Ndiyo | +| `check:db-rules` | Hakuna SQL ghafi nje ya moduli za `src/lib/db/`; hakuna uingizaji wa pamoja kutoka `localDb.ts` (Kanuni Ngumu #2/#5) | Ndiyo | +| `check:known-symbols` | Vitekelezaji vya watoa huduma, mikakati ya uelekezaji, na vitafsiri vilivyosajiliwa katika majedwali yao ya usambazaji vinalingana na faili kwenye diski — hakuna alama zisizo na faili husika au ambazo hazijatangazwa | Ndiyo | +| `check:route-guard-membership` | Kila njia inayozindua mchakato mtoto imeainishwa na `isLocalOnlyPath()` (Kanuni Ngumu #15/#17) | Ndiyo | +| `check:test-discovery` | Kila faili ya `*.test.ts` / `*.spec.ts` katika hazina inakusanywa na angalau kiendeshaji kimoja cha majaribio (ratchet: orodha ya faili zisizokusanywa katika `test-discovery-baseline.json` inaweza kupungua pekee) | Ndiyo | +| `check:agent-skills-sync` | Artifakti zilizozalishwa za agent-skills zinalingana na katalogi yao chanzo (hakuna kupotoka) | +| `check:provider-asset-provenance` | Nembo/rasilimali za watoa huduma zina ingizo la asili lililorekodiwa | +| `lint:json` | Faili za usanidi wa JSON zinachanganuliwa na kukidhi kanuni za lint za repo | +| `typecheck:core` | Ukusanyaji wa TypeScript bila hitilafu (maonyo ya ushauri pekee) | Ndiyo | +| `typecheck:noimplicit:core` | `noImplicitAny` kali — inalenga siku zijazo; maeneo mengi ya mwito yaliyokuwepo tayari bado yanahitaji maelezo ya aina | **Ushauri** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` inayolenga `src/app/(dashboard)/**` (#7033) — orodha iliyoruhusiwa ya faili 27 iliyochaguliwa kwa `typecheck:core` haijumuishi TSX yoyote ya dashibodi, na `next build` pia haiikagui aina kamwe (`next.config.mjs` huweka `ignoreBuildErrors: true`), kwa hivyo hitilafu za utambulisho usio na marejeleo huko (#6625/#6909) hazikuonekana kwa CI. Hulinganisha tofauti dhidi ya msingi uliogandishwa wa idadi kwa kila faili/kila msimbo wa TS (`config/quality/dashboard-typecheck-baseline.json`, muundo uleule wa utekelezaji dhidi ya hali iliyopitwa na wakati kama `check:known-symbols`) — ni hitilafu MPYA pekee zinazozidi idadi ya msingi zinazofanya lango lishindwe; punguza msingi hatua kwa hatua kwa `--update` hitilafu iliyokuwepo tayari inaporekebishwa. | Ndiyo | ### Kazi: `quality-gate` -Huendeshwa baada ya `test-coverage`. Huzuia uunganishaji inaposhindwa. +Huendeshwa baada ya `test-coverage`. Huzuia uunganishaji ikishindwa. -| Skripti | Huthibitisha | Inazuia | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | -| `quality:collect` | Hutoa `quality-metrics.json` (idadi ya maonyo ya ESLint, ufunikaji kutoka kwenye ripoti iliyounganishwa ya sehemu) | Ndiyo (kabla ya ratchet) | -| `quality:ratchet` | Kila kipimo katika `quality-baseline.json` hakijarudi nyuma (maonyo ya ESLint ≤ kiwango cha msingi; ufunikaji ≥ kiwango cha msingi) | Ndiyo | -| `check:duplication` | Urudiaji wa msimbo (jscpd@4) hauzidi kiwango cha msingi katika `quality-baseline.json` | Ndiyo | -| `check:complexity` | Utata wa kisaiklomati katika kiwango cha faili hauzidi kikomo (ESLint ya msingi `complexity` + `max-lines-per-function`) | Ndiyo | -| `check:cognitive-complexity` | Ratchet ya utata wa kiutambuzi (`eslint-plugin-sonarjs`) — utekelezaji tofauti wa ESLint; CI huendesha zote mbili zikiwa zimeunganishwa kama hatua moja ya `check:complexity-ratchets` | Ndiyo | -| `check:dead-code` | Ratchet ya exports / faili zisizotumika (knip) hairudi nyuma ikilinganishwa na kiwango cha msingi | Ndiyo | -| `check:compression-budget` | Bajeti ya kipimo cha utendaji wa ubanaji — viwango vya chini vya uokoaji wa tokeni kwa kila injini havipaswi kurudi nyuma | Ndiyo | -| `check:type-coverage` | Ratchet ya asilimia iliyowekewa aina (`type-coverage`) hairudi nyuma; kwa kiasi kikubwa inajumuisha `typecheck:noimplicit:core` | Ndiyo | -| `check:codeql-ratchet` | Idadi ya arifa zilizo wazi za CodeQL hairudi nyuma (husoma kupitia `gh api`; huruka bila hitilafu pasipo tokeni) — marudio ya uonyeshaji upya na kichochezi cha mkono: tazama "Ratchet ya CodeQL" hapa chini | Ndiyo | +| Skripti | Huthibitisha | Inazuia | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | Hutoa `quality-metrics.json` (idadi ya maonyo ya ESLint, kiwango cha ufunikaji kutoka kwenye ripoti iliyounganishwa ya shard) | Ndiyo (kabla ya ratchet) | +| `quality:ratchet` | Kila kipimo katika `quality-baseline.json` hakijarudi nyuma (maonyo ya ESLint ≤ kiwango cha msingi; ufunikaji ≥ kiwango cha msingi) | Ndiyo | +| `check:duplication` | Urudiaji wa msimbo (jscpd@4) hauzidi kiwango cha msingi katika `quality-baseline.json` | Ndiyo | +| `check:complexity` | Utata wa kisiklomati katika kiwango cha faili hauzidi kikomo (ESLint ya msingi `complexity` + `max-lines-per-function`) | Ndiyo | +| `check:cognitive-complexity` | Ratchet ya utata wa kiutambuzi (`eslint-plugin-sonarjs`) — ukaguzi tofauti wa ESLint; CI huendesha zote mbili zikiwa zimeunganishwa kama hatua moja ya `check:complexity-ratchets` | Ndiyo | +| `check:dead-code` | Ratchet ya exports / faili zisizotumika (knip) hairudi nyuma ikilinganishwa na kiwango cha msingi | Ndiyo | +| `check:compression-budget` | Bajeti ya kipimo cha ulinganishi wa mbano — viwango vya chini vya uokoaji wa tokeni kwa kila injini havipaswi kurudi nyuma | Ndiyo | +| `check:type-coverage` | Ratchet ya asilimia iliyowekewa aina (`type-coverage`) hairudi nyuma; kwa kiasi kikubwa inajumuisha `typecheck:noimplicit:core` | Ndiyo | +| `check:codeql-ratchet` | Idadi ya arifa zilizo wazi za CodeQL hairudi nyuma (husoma kupitia `gh api`; huruka kwa ustaarabu bila tokeni) — marudio ya kuonyesha upya na uanzishaji wa mikono: angalia "Ratchet ya CodeQL" hapa chini | Ndiyo | ### Kazi: `quality-extended` -Kazi nzima ni ya ushauri (`continue-on-error: true`). Ratchet zinazotumia npm huendeshwa -kihalisi; vikaguzi vya nje husakinishwa kupitia `gh release download` na hujiruka vyenyewe (exit 0) -wakati faili tekelezi bado haipo. +Kazi nzima ni ya ushauri (`continue-on-error: true`). Ratchet zinazotegemea npm huendeshwa +kikamilifu; vichanganuzi vya nje husakinishwa kupitia `gh release download` na hujiruka vyenyewe (exit 0) +wakati binary bado haipo. -| Skripti | Huthibitisha | Inazuia | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| `check:circular-deps` | Hakuna vitegemezi vya mzunguko (dpdm) | **Ya ushauri** | -| `check:bundle-size` | Ukubwa wa kifurushi hauzidi kikomo | **Ya ushauri** | -| `check:secrets` | Uchanganuzi wa siri (gitleaks) — huruka ikiwa faili tekelezi haipo | **Ya ushauri** | -| `check:vuln-ratchet` | Udhaifu wa vitegemezi (osv-scanner) haurudi nyuma — huruka ikiwa faili tekelezi haipo | **Ya ushauri** | -| `check:workflows` | Ukaguzi wa mtiririko wa kazi (actionlint + zizmor); vikaguzi vinavyokosekana/vilivyoharibika, ripoti batili au kiwango cha msingi cha ratchet kinachokosekana hushindwa kama INCOMPLETE. Matokeo halali hufuata sera iliyochaguliwa ya madhubuti/ushauri/ratchet | Utekelezaji unahitajika; ratchet ya zizmor inazuia katika CI | -| `check:openapi-breaking` | Mabadiliko yanayovunja mkataba wa API ya umma (`openapi.yaml`) dhidi ya tawi la msingi (oasdiff) — hutoa `openapiBreaking=N`; huruka ikiwa oasdiff haipo au spec ya msingi haiwezi kutatuliwa | **Ya ushauri** | +| Skripti | Huthibitisha | Inazuia | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | +| `check:circular-deps` | Hakuna vitegemezi vya mzunguko (dpdm) | **Ya ushauri** | +| `check:bundle-size` | Ukubwa wa bundle hauzidi kikomo | **Ya ushauri** | +| `check:secrets` | Uchanganuzi wa siri (gitleaks) — huruka ikiwa binary haipo | **Ya ushauri** | +| `check:vuln-ratchet` | Udhaifu wa vitegemezi (osv-scanner) haurudi nyuma — huruka ikiwa binary haipo | **Ya ushauri** | +| `check:workflows` | Ukaguzi wa workflow (actionlint + zizmor); vichanganuzi vinavyokosekana/kuharibika, ripoti batili au kukosekana kwa kiwango cha msingi cha ratchet husababisha INCOMPLETE. Matokeo halali hufuata sera iliyochaguliwa ya strict/advisory/ratchet | Utekelezaji unahitajika; ratchet ya zizmor huzuia katika CI | +| `check:openapi-breaking` | Mabadiliko yanayovunja mkataba wa API ya umma (`openapi.yaml`) dhidi ya tawi la msingi (oasdiff) — hutoa `openapiBreaking=N`; huruka ikiwa oasdiff haipo au spec ya msingi haiwezi kutatuliwa | **Ya ushauri** | ### Kazi: `docs-sync-strict` -Huendeshwa kwenye kila PR kwenda `main`. Huzuia kuunganisha endapo itashindwa. - -| Skripti | Inathibitisha | Inazuia | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `check:docs-all` | Lango kuu linalotekeleza malango madogo 6 yaliyo hapa chini kwa mfuatano | Ndiyo | -| ↳ `check:docs-sync` | Ulinganifu wa matoleo ya CHANGELOG / OpenAPI / llm.txt | Ndiyo | -| ↳ `check:docs-counts` | Idadi katika maelezo (idadi ya watoa huduma, idadi ya uhamishaji, n.k.) ziko ndani ya dirisha la hatua la idadi halisi | Ndiyo | -| ↳ `check:env-doc-sync` | Kila kigezo cha mazingira katika `.env.example` kimeandikwa katika jedwali la nyaraka, na kinyume chake | Ndiyo | -| ↳ `check:deprecated-versions` | Hakuna mifuatano ya matoleo yaliyopitwa na wakati katika nyaraka | Ndiyo | -| ↳ `check:doc-links` | Viungo vya ndani vya markdown katika nyaraka vinaelekeza kwenye faili halisi (muundo wa `[text]`/`(path)`) | Ndiyo | -| ↳ `check:fabricated-docs` | Njia, vigezo vya mazingira, amri za CLI, majina ya hook, na njia za faili zilizotajwa katika nyaraka zipo katika msingi wa msimbo. Lango gumu kupitia `--strict`; hushindwa bila kuzuia bila bendera. | Ndiyo (kupitia `--strict` katika CI) | -| `check:cli-i18n` | Mifuatano ya amri za CLI ipo katika faili zote za lugha za i18n | Ndiyo | -| `check:openapi-coverage` | Vipimo vya OpenAPI vinashughulikia angalau kiwango cha chini kinachoongezeka hatua kwa hatua cha njia halisi | Ndiyo | -| `check:openapi-security-tiers` | Maelezo ya viwango vya usalama katika `openapi.yaml` yanalingana na uainishaji wa `routeGuard.ts` | **Ushauri** | -| `check:openapi-routes` | Kila path katika `openapi.yaml` inaelekeza kwenye `route.ts` halisi (kuzuia taarifa za kubuniwa) | Ndiyo | -| `check:docs-symbols` | Kila rejeleo la `/api/...` katika `docs/**/*.md` linaelekeza kwenye `route.ts` halisi (kuzuia taarifa za kubuniwa) | Ndiyo | -| `i18n translation drift` | Funguo ambazo hazijatafsiriwa katika faili za lugha za i18n — onyo pekee | **Ushauri** | +Huendeshwa kwenye kila PR kwenda `main`. Huzuia uunganishaji ikifeli. + +| Skripti | Inathibitisha | Inazuia | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ | +| `check:docs-all` | Kizuizi kikuu kinachoendesha vizuizi vidogo 6 vilivyo hapa chini kwa mfuatano | Ndiyo | +| ↳ `check:docs-sync` | Ulinganifu wa matoleo ya CHANGELOG / OpenAPI / llm.txt | Ndiyo | +| ↳ `check:docs-counts` | Idadi zilizo katika maandishi (idadi ya watoa huduma, idadi ya uhamishaji, n.k.) ziko ndani ya dirisha la ratchet la idadi halisi | Ndiyo | +| ↳ `check:env-doc-sync` | Kila kigezo cha mazingira katika `.env.example` kimeandikwa katika jedwali la nyaraka, na kinyume chake | Ndiyo | +| ↳ `check:deprecated-versions` | Hakuna mifuatano ya matoleo yaliyopitwa na wakati katika nyaraka | Ndiyo | +| ↳ `check:doc-links` | Viungo vya ndani vya markdown katika nyaraka vinaelekeza kwenye faili halisi (muundo wa `[text]`/`(path)`) | Ndiyo | +| ↳ `check:fabricated-docs` | Njia, vigezo vya mazingira, amri za CLI, majina ya hook, na njia za faili zilizotajwa katika nyaraka zipo katika codebase. Kizuizi kigumu kupitia `--strict`; hushindwa kwa tahadhari bila flag. | Ndiyo (kupitia `--strict` katika CI) | +| `check:cli-i18n` | Mifuatano ya amri za CLI ipo katika faili zote za lugha za i18n | Ndiyo | +| `check:openapi-coverage` | Vipimo vya OpenAPI vinajumuisha angalau kiwango cha chini kilichowekwa kwa ratchet cha njia halisi | Ndiyo | +| `check:openapi-security-tiers` | Maelezo ya viwango vya usalama katika `openapi.yaml` yanaendana na uainishaji wa `routeGuard.ts` | **Ushauri** | +| `check:openapi-routes` | Kila njia katika `openapi.yaml` inaelekeza kwenye `route.ts` halisi (kuzuia uzushi) | Ndiyo | +| `check:docs-symbols` | Kila rejeleo la `/api/...` katika `docs/**/*.md` linaelekeza kwenye `route.ts` halisi (kuzuia uzushi) | Ndiyo | +| `i18n translation drift` | Funguo ambazo hazijatafsiriwa katika faili za lugha za i18n — onyo pekee | **Ushauri** | ### Kazi: `i18n-ui-coverage` -| Skripti | Inathibitisha | Inazuia | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check-ui-keys-coverage` (inline) | Ushughulikiaji wa funguo za UI za i18n ni ≥ 65% | Ndiyo | -| `check-ui-value-drift` (inline) | **Thamani** ya Kiingereza iliyoandikwa upya haiachi tafsiri yoyote ya zamani | Ndiyo | -| `check-new-key-coverage` (inline) | Ufunguo **mpya** wa Kiingereza umetafsiriwa katika kila lugha — alama ya `__MISSING__:` inakataliwa | Ndiyo | -| `check-translation-ratio` | Uwiano wa tafsiri halisi kwa kila lugha (zinazofanana na Kiingereza / vibadala / thamani zinazokosekana nje ya orodha ya kuruhusu) haupaswi kuzidi `config/quality/i18n-translation-baseline.json` + nafasi ya ziada | **Ushauri** | +| Skripti | Inathibitisha | Inazuia | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check-ui-keys-coverage` (inline) | Ujumuishaji wa funguo za i18n za UI ni ≥ 65% | Ndiyo | +| `check-ui-value-drift` (inline) | **Thamani** ya Kiingereza iliyoandikwa upya haiachi tafsiri yoyote ya zamani | Ndiyo | +| `check-new-key-coverage` (inline) | Ufunguo **mpya** wa Kiingereza umetafsiriwa katika kila lugha — kiashirio cha `__MISSING__:` kinakataliwa | Ndiyo | +| `check-translation-ratio` | Uwiano wa tafsiri halisi kwa kila lugha (zinazofanana na Kiingereza / kishikilia nafasi / zilizoachwa bila kutafsiri nje ya orodha inayoruhusiwa) haupaswi kuzidi `config/quality/i18n-translation-baseline.json` + nafasi ya ziada | **Ushauri** | -Inahitaji `fetch-depth: 0` — lango la mabadiliko ya thamani hulinganisha tofauti za `en.json` dhidi ya msingi wa kuunganisha. +Inahitaji `fetch-depth: 0` — kizuizi cha mabadiliko ya thamani hulinganisha `en.json` dhidi ya merge base. -#### `check-ui-value-drift` — lango la tafsiri zilizopitwa na wakati +#### `check-ui-value-drift` — kizuizi cha tafsiri za zamani -Hunasa hitilafu moja ya i18n ambayo malango mengine hayawezi kuiona kimuundo: thamani ya Kiingereza +Hugundua hitilafu moja ya i18n ambayo vizuizi vingine haviwezi kuiona kimuundo: thamani ya Kiingereza inaandikwa upya huku tafsiri zilizotokana na Kiingereza cha _awali_ zikibaki, hivyo -watumiaji wasiotumia Kiingereza huendelea kusoma maandishi yenye kujiamini lakini ambayo sasa si sahihi. +watumiaji wasiotumia Kiingereza wanaendelea kusoma maandishi yenye uhakika lakini ambayo sasa si sahihi. -Hili lilitolewa katika toleo halisi. `oauthModal.googleOAuthWarning` iliandikwa upya wakati kisaidizi cha -kuingia cha Antigravity kilipoanzishwa (#5203); **lugha 39 kati ya 43** zilibakiza maandishi yaliyowaambia waendeshaji “kunakili -URL kamili na kuibandika hapa chini” — mtiririko ambao hauwezi kukamilika kwa mtoa huduma huyo. Hili +Hili lilitolewa kwa watumiaji katika hali halisi. `oauthModal.googleOAuthWarning` iliandikwa upya wakati kisaidizi cha kuingia cha Antigravity +kilipoletwa (#5203); **lugha 39 kati ya 43** zilibaki na maandishi yaliyowaambia waendeshaji "kunakili +URL kamili na kuibandika hapa chini" — mtiririko ambao hauwezi kukamilika kwa mtoa huduma huyo. Hili halikugunduliwa hadi #8463 kwa sababu: -- `sync-ui-keys` hujaza tu funguo ambazo **hazipo**, kamwe si zile ambazo **zimepitwa na wakati**; -- `check-ui-keys-coverage` huhesabu _uwepo_ wa ufunguo, hivyo tafsiri iliyopitwa na wakati huhesabiwa kuwa imeshughulikiwa; +- `sync-ui-keys` huongeza tu funguo ambazo **hazipo**, kamwe si zile ambazo ni **za zamani**; +- `check-ui-keys-coverage` huhesabu _uwepo_ wa funguo, hivyo tafsiri ya zamani huhesabiwa kuwa imejumuishwa; - `check-translation-drift` hufuatilia vioo vya nyaraka vya `docs/i18n//**.md` — - haisomi kamwe `src/i18n/messages/*.json`. Inazuia katika kazi ya `docs-sync-strict` tangu - usawazishaji upya wa 2026-09: hariri waraka mkuu → `npm run i18n:run -- --files=` (kwa kiwango cha sehemu, gharama ndogo). + haisomi kamwe `src/i18n/messages/*.json`. Inazuia katika kazi `docs-sync-strict` tangu usawazishaji upya wa + 2026-09: hariri hati kuu → `npm run i18n:run -- --files=` (kwa kiwango cha sehemu, gharama nafuu). -**Inatambua diff, haitegemei baseline.** Inalinganisha `en.json` kwenye merge base dhidi ya -working tree; kwa kila key ambayo thamani yake ya Kiingereza imebadilika, locale yoyote ambayo bado ina -tafsiri ambayo haijaguswa imepitwa na wakati. Hii kwa makusudi **inagandisha deni lililokuwepo awali** — diff -haiwezi kufichua tafsiri ya muda mrefu ilitokana na Kiingereza gani cha zamani, kwa hivyo gate hutathmini -tu kile ambacho badiliko la sasa linagusa. Njia mbadala (baseline ya hash kwa kila key) ingehitaji -faili inayozalishwa ya ~600 KB, kubwa mara 3 kuliko baseline kubwa zaidi iliyopo, na ingebadilika katika kila PR ya i18n. +**Inatambua tofauti, haitegemei baseline.** Hulinganisha `en.json` kwenye merge base dhidi ya +working tree; kwa kila ufunguo ambao thamani yake ya Kiingereza imebadilika, locale yoyote ambayo bado ina +tafsiri ambayo haijaguswa imepitwa na wakati. Hii kwa makusudi **hufungia deni lililokuwepo awali** — diff +haiwezi kubaini tafsiri ya muda mrefu ilitokana na Kiingereza gani cha zamani, kwa hivyo gate hutathmini +tu kile ambacho badiliko la sasa linagusa. Njia mbadala (baseline ya hash kwa kila ufunguo) ingeongeza +faili iliyozalishwa ya ~600 KB, mara 3 ya baseline kubwa zaidi iliyopo, huku ikibadilika katika kila PR ya i18n. -Njia mbili za kuitimiza: +Njia mbili za kukidhi sharti hili: 1. sasisha tafsiri zilizoathiriwa, au 2. ziweke kuwa `__MISSING__:` — wakati wa utekelezaji kisha hutoa Kiingereza kilichosahihishwa - (`src/i18n/request.ts::deepMergeFallback`, #7258), na key huingizwa kwenye foleni ya kutafsiriwa. + (`src/i18n/request.ts::deepMergeFallback`, #7258) na ufunguo huwekwa kwenye foleni ya kutafsiriwa. -Ikiwa **maana** ya string imebadilika, ni bora **kubadilisha jina la key**: key mpya haiwezi kurithi -tafsiri iliyopitwa na wakati. Huo ndio muundo uliotumiwa na #8463. +Ikiwa **maana** ya kifungu imebadilika, ni vyema zaidi **kubadilisha jina la ufunguo**: ufunguo mpya hauwezi kurithi +tafsiri iliyopitwa na wakati. Huo ndio mtindo uliotumiwa na #8463. ```bash -npm run i18n:check-value-drift # madhubuti (kile ambacho CI huendesha) +npm run i18n:check-value-drift # hali kali (kile CI huendesha) npm run i18n:check-value-drift:warn # toa ripoti pekee BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Hutoka kwa 0 ikiwa na `SKIP reason=base-unresolved` wakati katalogi ya msingi haiwezi kusomwa (shallow +Hutoka kwa msimbo 0 pamoja na `SKIP reason=base-unresolved` wakati katalogi ya msingi haiwezi kusomwa (shallow clone isiyo na base ref), sawa na `check-openapi-breaking`. -### Job: `i18n` +### Kazi: `i18n` -Matrix kamili ya uthibitishaji wa i18n (job moja kwa kila locale). Job nzima ni ya ushauri. +Matriki kamili ya uthibitishaji wa i18n (kazi moja kwa kila locale). Kazi nzima ni ya ushauri. -| Script | Huthibitisha | Uzuiaji | -| ------------------------------- | ------------------------------------ | ----------------------------------------------------------- | -| `validate_translation.py quick` | Ukamilifu wa tafsiri kwa kila locale | **Ya ushauri** (`continue-on-error: true` kwenye job nzima) | +| Skripti | Huthibitisha | Uzuiaji | +| ------------------------------- | ------------------------------------ | ------------------------------------------------------------ | +| `validate_translation.py quick` | Ukamilifu wa tafsiri kwa kila locale | **Ya ushauri** (`continue-on-error: true` kwenye kazi nzima) | -### Job: `pr-test-policy` +### Kazi: `pr-test-policy` -Huendeshwa kwenye pull requests pekee. +Huendeshwa kwenye pull request pekee. -| Script | Huthibitisha | Uzuiaji | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `check:pr-test-policy` | PR zinazobadilisha code ya uzalishaji katika `src/`, `open-sse/`, `electron/`, au `bin/` lazima zijumuishe au zisasisha tests (Kanuni Ngumu #8) | Ndiyo | -| `check:test-masking` | Faili za test zilizobadilishwa hazipunguzi jumla halisi ya idadi ya assertions wala kuongeza tautolojia za `assert.ok(true)` | Ndiyo | -| `check:pr-evidence` | Maelezo ya PR yanataja ushahidi wa test/VPS wa badiliko (huifanya Kanuni Ngumu #18 kuwa ya kiotomatiki kwa kutafuta maandishi ya PR — ni dhaifu, angalia Orodha ya kazi zilizobaki) | Ndiyo | +| Skripti | Huthibitisha | Uzuiaji | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `check:pr-test-policy` | PR zinazobadilisha msimbo wa uzalishaji katika `src/`, `open-sse/`, `electron/`, au `bin/` lazima zijumuishe au zisasisha majaribio (Kanuni Ngumu #8) | Ndiyo | +| `check:test-masking` | Faili za majaribio zilizobadilishwa hazipunguzi jumla halisi ya idadi ya assert wala kuongeza tautolojia za `assert.ok(true)` | Ndiyo | +| `check:pr-evidence` | Maelezo ya PR yanataja ushahidi wa jaribio/VPS kuhusu badiliko (hufanyia Kanuni Ngumu #18 utekelezaji kiotomatiki kwa kutumia grep kwenye maelezo ya PR — ni dhaifu, angalia Backlog) | Ndiyo | -### Job: `test-vitest` +### Kazi: `test-vitest` -Huendeshwa baada ya `build`. Huzuia merge ikifeli. +Huendeshwa baada ya `build`. Huzuia merge ikishindwa. -| Suite | Huthibitisha | Uzuiaji | -| ---------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (tools 110), autoCombo, cache — vitest runner | Ndiyo | -| `test:vitest:ui` | Tests za UI components — vitest runner | **Huzuia** — failures zilizokuwepo awali zimeondolewa waziwazi katika `vitest.config.ts`; failures mpya husababisha job ifeli | +| Suite | Huthibitisha | Uzuiaji | +| ---------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Seva ya MCP (zana 110), autoCombo, cache — kiendesha vitest | Ndiyo | +| `test:vitest:ui` | Majaribio ya vijenzi vya UI — kiendesha vitest | **Huzuia** — hitilafu zilizokuwepo awali zimeondolewa waziwazi katika `vitest.config.ts`; hitilafu mpya husababisha kazi kushindwa | -### Workflows za kila usiku (zimeratibiwa, za ushauri) +### Workflow za kila usiku (zimeratibiwa, za ushauri) Hizi huendeshwa kwa ratiba ya cron (na `workflow_dispatch`), kamwe si kwenye PR. Zote ni za ushauri. -| Workflow | Huthibitisha | Uzuiaji | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | Tests za property za fast-check zenye seed nasibu + idadi kubwa ya runs | **Ya ushauri** | -| `nightly-resilience` | gate ya ukuaji wa heap, chaos fault-injection, load/soak ya k6 | **Ya ushauri** | -| `nightly-llm-security` | ulinzi wa injection wa promptfoo (block mode) + probes za garak (hurukwa bila provider secret) | **Ya ushauri** | -| `nightly-schemathesis` | Fuzzing ya mkataba wa OpenAPI (schemathesis) dhidi ya OmniRoute inayofanya kazi kwa kutumia `docs/openapi.yaml` — hufichua ukiukaji wa spec / 500 ambazo hazijashughulikiwa (Awamu ya 8 B.4) | **Ya ushauri** | -| `nightly-mutation` | Alama ya mutation-testing ya Stryker kwenye mkondo wa haraka wa unit — mutants zinazonusurika hufichua assertions dhaifu | **Ya ushauri** | -| `nightly-compat` | Matrix ya uoanifu wa Node engine katika safu zinazotumika za `engines.node` | **Ya ushauri** | +| Workflow | Huthibitisha | Uzuiaji | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | Majaribio ya sifa ya fast-check yenye seed ya nasibu + idadi kubwa ya utekelezaji | **Ya ushauri** | +| `nightly-resilience` | gate ya ukuaji wa heap, uingizaji wa hitilafu wa chaos, mzigo/soak wa k6 | **Ya ushauri** | +| `nightly-llm-security` | kinga ya promptfoo dhidi ya injection (hali ya block) + uchunguzi wa garak (hurukwa bila siri ya mtoa huduma) | **Ya ushauri** | +| `nightly-schemathesis` | Fuzzing ya mkataba wa OpenAPI (schemathesis) dhidi ya OmniRoute inayotumika kwa kutumia `docs/openapi.yaml` — huibua ukiukaji wa spec / hitilafu za 500 zisizoshughulikiwa (Awamu ya 8 B.4) | **Ya ushauri** | +| `nightly-mutation` | Alama ya majaribio ya mutation ya Stryker kwenye mkondo wa haraka wa unit — mutants zinazonusurika huibua assert dhaifu | **Ya ushauri** | +| `nightly-compat` | Matriki ya uoanifu wa injini ya Node katika masafa yanayotumika ya `engines.node` | **Ya ushauri** | --- @@ -536,42 +536,42 @@ kosa la muunganisho na kamwe halivunji vipindi. --- -## Orodha ya Kazi za Urasimishaji (mapitio ya ROI — Awamu ya 9 Wimbi la 3) +## Orodha ya Maboresho Yanayosubiri (mapitio ya ROI — Awamu ya 9 Wimbi la 3) Orodha hii ilioanishwa na `ci.yml` tarehe 2026-06-17 (toleo la awali liliacha `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, `check:codeql-ratchet`, `check:pr-evidence`). Mapitio ya ROI ya seti iliyoanishwa -yalibaini vipengee vifuatavyo vinavyoweza kurasimishwa. **Miunganisho ni mabadiliko ya kiufundi ya CI; -mabadiliko/ufutaji ni maamuzi ya sera yaliyohifadhiwa kwa mwendeshaji.** Hakuna chochote hapa chini -ambacho bado kimetekelezwa. - -**Pia hayajaandikwa hapo juu** (ya ushauri, ishara dhaifu): job ya `docs-lint` -(markdownlint + Vale, job nzima ikiwa na `continue-on-error`) na workflows huru za uchanganuzi -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` ipo katika -`quality-baseline.json` lakini haijaunganishwa na ratchet inayozuia katika `ci.yml` — kipimo hicho +yalibaini yafuatayo kuwa mambo yanayoweza kurahisishwa. **Miunganisho hiyo ni mabadiliko +ya kimakanika ya CI; mabadiliko ya hali/ufutaji ni maamuzi ya sera yaliyohifadhiwa kwa +mwendeshaji.** Hakuna kilicho hapa chini ambacho kimetekelezwa bado. + +**Pia hayajaandikwa hapo juu** (ya ushauri, ishara dhaifu): kazi ya `docs-lint` +(markdownlint + Vale, kazi nzima ikiwa na `continue-on-error`) na mitiririko huru ya kazi +ya uchanganuzi `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` ipo +katika `quality-baseline.json` lakini haijaunganishwa na ratchet inayozuia katika `ci.yml` — kipimo hicho kwa sasa hakijaunganishwa na chochote. -### Unganisha / ondoa urudufu (kiufundi, hatari ndogo) +### Unganisha / ondoa urudufu (kimakanika, hatari ndogo) -Kila kipengee kilithibitishwa dhidi ya hali halisi ya gate tarehe 2026-06-17 (amini-lakini-thibitisha); -miunganisho kadhaa iliyoonekana kuwa "dhahiri" ilibainika kuficha deni na **si** mbadala safi. +Kila kipengele kilithibitishwa dhidi ya hali halisi ya vizuizi tarehe 2026-06-17 (amini-lakini-thibitisha); +miunganisho kadhaa "iliyo dhahiri" ilibainika kuficha deni na **si** vibadala safi vinavyoweza kuwekwa moja kwa moja. -- **`check:docs-sync` huendeshwa mara mbili** — kivyake katika job ya `lint` na tena ndani ya `check:docs-all` (`docs-sync-strict`) na hook ya husky ya pre-commit. ✅ **IMEKAMILIKA** — utekelezaji wa pekee wa `lint` umeondolewa. -- **Uchanganuzi wa CVE** — ❌ **SI muunganisho safi.** `audit:deps` hushindwa moja kwa moja kwa CVE yoyote ya kiwango cha juu/muhimu; `check:vuln-ratchet` (osv) hushindwa tu kunapokuwa na _kurudi nyuma_ ikilinganishwa na baseline (kwa sasa 1 MODERATE). Semantiki ni tofauti — kuondoa `audit:deps` kungeondoa gate kamili ya kiwango cha juu/muhimu. Zihifadhi zote mbili. -- **Ugunduzi wa mizunguko** — ❌ **SI muunganisho safi.** `check:circular-deps` (dpdm) huripoti **mizunguko 91** (hiyo ndiyo sababu ni ya ushauri); haiwezi kupandishwa kuwa ya kuzuia bila kwanza kuitatua, na ina upeo mpana zaidi kuliko `check:cycles` iliyochaguliwa kwa makusudi na inayofaulu. Endelea kuweka `check:cycles` kama ya kuzuia; kutatua mizunguko 91 ya dpdm ni kazi tofauti katika orodha ya kazi. -- **Utata** — ✅ **IMEKAMILIKA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): upitishaji mmoja wa ESLint, huhesabu kwa ruleId ili baseline za cyclomatic+max-lines na cognitive ziendelee kuwa huru; `check:complexity` / `check:cognitive-complexity` binafsi zinasalia kwa `--update` ya ndani. -- **Uzuiaji wa uzushi wa `/api`** — ✅ **IMEKAMILIKA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): orodha moja ya FS ya `src/app/api`, openapi-routes + docs-symbols bado huripoti kivyake; kila moja inasalia kwa utekelezaji wa ndani. -- **`check:node-runtime` huendeshwa katika jobs 11** — ⚠️ **ROI ndogo.** Kila moja ni runner tofauti na ukaguzi ni <1s; jumla ya muda unaookolewa ni ~10s, dhidi ya kupoteza ulinzi wa bei nafuu kwa kila job. Haifai usumbufu wa mabadiliko hayo. -- **`typecheck:noimplicit:core` kwenye lint ya CI** — ✅ **imeondolewa kwenye job ya lint** (ilikuwa ya ushauri ikiwa na `continue-on-error`); eneo la aina linalozuia ni `typecheck:core` + `check:type-coverage`. Script ya ndani imehifadhiwa. +- **`check:docs-sync` huendeshwa mara mbili** — kivyake katika kazi ya `lint` na tena ndani ya `check:docs-all` (`docs-sync-strict`) na hook ya husky ya pre-commit. ✅ **IMEKAMILIKA** — mwito wa pekee wa `lint` umeondolewa. +- **Uchanganuzi wa CVE** — ❌ **SI muunganisho safi.** `audit:deps` hushindwa moja kwa moja kwa CVE yoyote ya kiwango cha juu/muhimu; `check:vuln-ratchet` (osv) hushindwa tu kunapokuwa na _kurudi nyuma_ ikilinganishwa na baseline (kwa sasa 1 MODERATE). Maana zake ni tofauti — kuondoa `audit:deps` kungeondoa kizuizi kamili cha kiwango cha juu/muhimu. Zihifadhi zote mbili. +- **Ugunduzi wa mizunguko** — ✅ **IMEKAMILIKA** (#15159 G-01/G-02). Maandishi ya zamani hapa yaliita `check:cycles` kizuizi "cha kijani, kilichoratibiwa" na kuhalalisha kukiweka kama kizuizi kwa sababu `check:circular-deps` (dpdm) iliripoti mizunguko 91. Hali hiyo ya kijani ilikuwa **hali ya kijani ya uongo**: `check:cycles` ilichanganua saraka ndogo 5 (faili 450), ililinganisha tu `import|export … from` tuli, na ilitupilia mbali kila kibainishi cha `@/` na `@omniroute/open-sse/`, kwa hivyo haikuweza kuona mizunguko ya dynamic-import + alias iliyotawala repo. Imerekebishwa: kizuizi sasa hupitia `src` + `open-sse` (faili 5023), hukusanya vibainishi kutoka TypeScript AST (kwa hivyo `import("…")` huhesabiwa lakini `typeof import("…")` ya nafasi ya aina haihesabiwi), na hutatua `paths` za tsconfig. Kinapata mizunguko **14**, si 0. Kwa kuwa mizunguko 14 iliyokuwepo awali haiwezi kurekebishwa katika PR ya kizuizi, `check:cycles` sasa ni **ratchet** (`--ratchet`, kikomo `metrics.cycles.value = 14` katika `quality-baseline.json`, `direction: down`) — huzuia _kurudi nyuma_ kokote na idadi inaweza kupungua tu. CI huendesha `npm run check:cycles:ratchet`. Upunguzaji wa hatua kwa hatua unaendelea pamoja na **A-01**. `check:circular-deps` (dpdm) inabaki kuwa ya ushauri kama maoni mapana ya pili. +- **Uchangamani** — ✅ **IMEKAMILIKA** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): mpito mmoja wa ESLint, huhesabu kwa ruleId ili baseline za cyclomatic+max-lines na cognitive zibaki huru; `check:complexity` / `check:cognitive-complexity` binafsi zinabaki kwa `--update` ya ndani. +- **Kuzuia uzushi wa `/api`** — ✅ **IMEKAMILIKA** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): orodha moja ya FS ya `src/app/api`, openapi-routes + docs-symbols bado huripoti kivyake; kila moja inabaki kwa utekelezaji wa ndani. +- **`check:node-runtime` huendeshwa katika kazi 11** — ⚠️ **ROI ndogo.** Kila moja ni runner tofauti na ukaguzi ni <1s; jumla ya muda unaookolewa ni ~10s, dhidi ya kupoteza ulinzi nafuu wa kila kazi. Haifai usumbufu wa mabadiliko. +- **`typecheck:noimplicit:core` kwenye lint ya CI** — ✅ **imeondolewa kwenye kazi ya lint** (ilikuwa ya ushauri ikiwa na `continue-on-error`); sehemu ya aina inayozuia ni `typecheck:core` + `check:type-coverage`. Script ya ndani imehifadhiwa. -### Badilisha / amua (sera ya mwendeshaji) +### Badilisha hali / amua (sera ya mwendeshaji) -- `check:openapi-security-tiers` (ya ushauri) — ❌ **HAIWEZI kubadilishwa moja kwa moja.** Hutoka kwa 0 lakini huonya kwamba routes kadhaa za `traffic-inspector` chini ya `LOCAL_ONLY_API_PREFIXES` hazina ufafanuzi wa `x-loopback-only: true`. Kuitekeleza kunahitaji kwanza kuongeza maelezo hayo kwenye `openapi.yaml`. -- `typecheck:noimplicit:core` (ya ushauri) — kwa kiasi kikubwa imejumuishwa na ratchet ya `check:type-coverage` inayozuia. Ibadilishe iwe ratchet au ondoa upitishaji wa pili wa `tsc` wenye urudufu. -- `test:vitest:ui` (sasa **inazuia**) — hitilafu zilizokuwepo tayari zimeondolewa waziwazi katika `vitest.config.ts` kwa maoni ya ufuatiliaji ya `// #8618`; hitilafu mpya husababisha job ishindwe. -- `check:secrets` (gitleaks, ratchet inayozuia iliyogandishwa kwenye matokeo 3 ya uongo yaliyoandikwa) — yaweke hayo 3 kwenye allowlist ili kufikia 0, au ishushwe kuwa ya ushauri. Inaingiliana na uchanganuzi asilia wa siri wa GitHub + `check:public-creds`. -- `check:pr-evidence` (inazuia, hutafuta maandishi katika maelezo ya PR kwa grep) — hatari kubwa ya matokeo ya uongo; kuiondoa hudhoofisha utekelezaji wa Kanuni Ngumu #18, kwa hivyo huu ni uamuzi halisi wa sera. +- `check:openapi-security-tiers` (ya ushauri) — ❌ **HAIWEZI kubadilishwa hali kwa usafi.** Hutoka kwa 0 lakini huonya kuwa routes kadhaa za `traffic-inspector` chini ya `LOCAL_ONLY_API_PREFIXES` hazina anoteshini ya `x-loopback-only: true`. Kuilazimisha kunahitaji kwanza kuongeza anoteshini hizo kwenye `openapi.yaml`. +- `typecheck:noimplicit:core` (ya ushauri) — kwa kiasi kikubwa imejumuishwa na ratchet inayozuia ya `check:type-coverage`. Ibadilishe kuwa ratchet au ondoa mpito wa pili wa `tsc` unaojirudia. +- `test:vitest:ui` (sasa **inazuia**) — hitilafu zilizokuwepo awali zimeondolewa waziwazi katika `vitest.config.ts` kwa maoni ya ufuatiliaji ya `// #8618`; hitilafu mpya husababisha kazi kushindwa. +- `check:secrets` (gitleaks, ratchet inayozuia iliyogandishwa katika false-positive 3 zilizoandikwa) — ziweke hizo 3 kwenye allowlist ili kufikia 0, au ishushe kuwa ya ushauri. Inaingiliana na uchanganuzi asilia wa siri wa GitHub + `check:public-creds`. +- `check:pr-evidence` (inazuia, hutafuta maandishi ya mwili wa PR kwa grep) — hatari kubwa ya false-positive; kuiondoa kunadhoofisha utekelezaji wa Hard Rule #18, kwa hivyo hili ni chaguo halisi la sera. - `semgrep` (huru na ya ushauri) — inaingiliana na CodeQL kwa familia za OWASP; unganisha baseline yake na ratchet au iondoe. --- diff --git a/docs/i18n/sw/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/sw/docs/compression/COMPRESSION_GUIDE.md index 118b3d854889..1ed65a6bab4e 100644 --- a/docs/i18n/sw/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/sw/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ hudungwa badala ya Kiingereza. Kwenye body yenye `messages`, upitaji wa maudhui (`shouldBypassCavemanOutputMode()` katika `open-sse/services/compression/outputMode.ts`) hukagua jumbe tatu za mwisho na kuruka mitindo kwa turn nzima zinapolingana na maneno yake muhimu ya usalama, kitendo kisichoweza kutenduliwa, -ufafanuzi, au yanayotegemea mpangilio. Upitaji hufanya kazi kulingana na mpangilio wa -kigeuzi cha **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) kwenye dashibodi. +ufafanuzi, au yanayotegemea mpangilio. Upitaji hufanya kazi wakati kigeuzi cha **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) kwenye dashibodi kimewashwa, ambacho ni chaguo-msingi; kigeuzi kikizimwa, mitindo iliyochaguliwa hutumika katika turn hizo pia. Upitaji unaporuhusu turn kuendelea, `placeSystemInstruction()` (faili hiyo hiyo), ambayo haiundi kamwe `messages[0]` mpya, huweka kizuizi katika sehemu ya kwanza kati ya hizi inayopatikana: diff --git a/docs/i18n/sw/docs/reference/ENVIRONMENT.md b/docs/i18n/sw/docs/reference/ENVIRONMENT.md index 01e4f4070976..a5308699acef 100644 --- a/docs/i18n/sw/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/sw/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Siri Zinazohitajika -Hizi **lazima** ziwekwe kabla ya uendeshaji wa kwanza. Bila hizo, programu itakataa kuanza au itafanya kazi kwa chaguo-msingi zisizo salama. - -| Kigezo | Kinahitajika | Chaguo-msingi | Faili Chanzo | Maelezo | -| ---------------------------- | ---------------------- | --------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ndiyo** | _(hakuna)_ | `src/lib/auth` | Hutia saini/huthibitisha vidakuzi vyote vya kipindi cha dashibodi (JWT). Zalisha kwa kutumia `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Ndiyo** | _(hakuna)_ | `src/lib/db/apiKeys.ts` | Ufunguo wa usimbaji fiche wa AES kwa thamani za funguo za API zilizohifadhiwa katika SQLite. Zalisha kwa kutumia `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Ndiyo** | `CHANGEME` | Hati ya uanzishaji | Huweka nenosiri la awali la dashibodi ya msimamizi (linalingana na chaguo-msingi la `.env.example` — limeachwa bila usalama kwa makusudi ili kulazimisha libadilishwe). **Libadilishe kabla ya matumizi ya kwanza.** Baada ya kuingia, libadilishe kupitia Dashibodi → Mipangilio → Usalama. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ndiyo** (uzalishaji) | _(haijawekwa)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Siri inayoshirikiwa kwa daraja la ndani la WebSocket la Codex Responses. Huthibitisha maombi ya daraja kati ya kiwasilishaji cha WS cha Electron/kivinjari na OmniRoute. ⚠️ **INAHITAJIKA katika uzalishaji — ikiwa haijawekwa, maombi yote ya daraja la WS yanakataliwa.** Zalisha kwa kutumia `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Hapana | _(SHA ya git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Kitambulisho bayana cha kuvunja akiba ya service worker kwa shell ya PWA (#11779); cha kwanza katika mnyororo wa utatuzi. | -| `SOURCE_VERSION` | Hapana | _(haijawekwa)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Cha pili katika mnyororo — huwekwa na wajenzi wa PaaS (kwa mfano, wa mtindo wa Heroku) kama commit iliyotumwa. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Hapana | _(imetokana)_ | `src/shared/components/PwaRegister.tsx` | Thamani ya umma ya wakati wa ujenzi ambayo mteja hutumia kusajili `/sw.js?v=…`; hutokana na viwili vilivyo hapo juu, kisha SHA ya git. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Hapana (otomatiki) | _(otomatiki kwa kila uanzishaji)_ | `src/server/authz/policies/management.ts` | Siri ya kila mchakato inayothibitisha kuwa muhuri wa IP ya rika inayoaminika ulitoka kwenye seva ya HTTP ya OmniRoute yenyewe (`scripts/dev/peer-stamp.mjs`). Middleware ya authz huamini eneo la ombi (uzuiaji wa loopback/LAN kwa njia za LOCAL_ONLY) ikiwa tu muhuri una tokeni hii. Huzalishwa kiotomatiki kila uanzishaji — iache bila kuwekwa; iweke thamani ya kudumu tu kwa usanidi wa michakato mingi unaolazimika kushiriki muhuri. | -| `OMNIROUTE_TRUSTED_PROXIES` | Hapana | _(haijawekwa)_ | `scripts/dev/peer-stamp.mjs` | IP au masafa ya CIDR ya proksi za kinyume, yaliyotenganishwa kwa koma, ambayo orodha ya kuruhusu/kuzuia IP inaweza kuamini `X-Forwarded-For` / `X-Real-IP` zake, pamoja na anwani za loopback, mtandao binafsi na kingo za Cloudflare, ambazo huaminika kila wakati. Inahitajika tu kwa proksi iliyo kwenye anwani nyingine yoyote ya umma; vinginevyo anwani ya proksi yenyewe ndiyo inayotathminiwa na kichujio. | +Siri zilizowekwa alama ya **Ndiyo** ambazo hazijawekwa huzalishwa kiotomatiki wakati wa uendeshaji wa kwanza (`JWT_SECRET`, `API_KEY_SECRET`) na huhifadhiwa katika `server.env` katika saraka ya data — ziweke wazi ikiwa saraka hiyo ni ya muda. Ubaguzi ni `OMNIROUTE_WS_BRIDGE_SECRET`: haihifadhiwi katika `server.env` — sehemu za kuingia za seva hutoa thamani ya muda ya kila kuwasha ambayo daraja la ndani la mchakato pekee ndiyo linaweza kutumia, kwa hivyo wateja wa nje wa daraja wanaihitaji kuwekwa wazi. + +| Kigezo | Kinahitajika | Chaguo-msingi | Faili Chanzo | Maelezo | +| ---------------------------- | ---------------------- | --------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ndiyo** | _(hakuna)_ | `src/lib/auth` | Hutia saini/huthibitisha vidakuzi vyote vya kipindi cha dashibodi (JWT). Zalisha kwa kutumia `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Ndiyo** | _(hakuna)_ | `src/lib/db/apiKeys.ts` | Ufunguo wa usimbaji fiche wa AES kwa thamani za funguo za API zilizohifadhiwa katika SQLite. Zalisha kwa kutumia `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Hapana | _(haijawekwa)_ | `src/lib/auth/managementPassword.ts` | Huweka nenosiri la dashibodi wakati wa kuwasha ikiwa hakuna nenosiri lililohifadhiwa bado; hupuuzwa mara nenosiri limehifadhiwa. Usakinishaji wa npm na wa chanzo hukopi `.env.example` kwenda `.env`, kwa hivyo huanza na nafasi yake ya kushikilia `CHANGEME` iliyojulikana hadharani: ibadilishe kabla ya kuwasha kwa mara ya kwanza, au weka `INITIAL_PASSWORD=` (tupu) katika faili hiyo ya `.env` ili kutengeneza nenosiri katika kichawi cha utangulizi wakati wa kutembelea kwa mara ya kwanza, kama thamani isiyowekwa (chaguo-msingi la picha ya Docker) inavyofanya. Badilisha nenosiri lililohifadhiwa katika Dashibodi → Mipangilio → Usalama, au endesha `omniroute reset-password` (`node bin/reset-password.mjs` katika nakala ya chanzo) ukiwa `DATA_DIR` imewekwa kwa saraka ya data ya seva. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ndiyo** (uzalishaji) | _(haijawekwa)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Siri inayoshirikiwa kwa daraja la ndani la WebSocket la Codex Responses. Huthibitisha maombi ya daraja kati ya kiwasilishaji cha WS cha Electron/kivinjari na OmniRoute. ⚠️ **INAHITAJIKA katika uzalishaji — ikiwa haijawekwa, maombi yote ya daraja la WS yanakataliwa.** Zalisha kwa kutumia `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Hapana | _(SHA ya git)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Kitambulisho bayana cha kuvunja akiba ya service worker kwa shell ya PWA (#11779); cha kwanza katika mnyororo wa utatuzi. | +| `SOURCE_VERSION` | Hapana | _(haijawekwa)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Cha pili katika mnyororo — huwekwa na wajenzi wa PaaS (kwa mfano, wa mtindo wa Heroku) kama commit iliyotumwa. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Hapana | _(imetokana)_ | `src/shared/components/PwaRegister.tsx` | Thamani ya umma ya wakati wa ujenzi ambayo mteja hutumia kusajili `/sw.js?v=…`; hutokana na viwili vilivyo hapo juu, kisha SHA ya git. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Hapana (otomatiki) | _(otomatiki kwa kila uanzishaji)_ | `src/server/authz/policies/management.ts` | Siri ya kila mchakato inayothibitisha kuwa muhuri wa IP ya rika inayoaminika ulitoka kwenye seva ya HTTP ya OmniRoute yenyewe (`scripts/dev/peer-stamp.mjs`). Middleware ya authz huamini eneo la ombi (uzuiaji wa loopback/LAN kwa njia za LOCAL_ONLY) ikiwa tu muhuri una tokeni hii. Huzalishwa kiotomatiki kila uanzishaji — iache bila kuwekwa; iweke thamani ya kudumu tu kwa usanidi wa michakato mingi unaolazimika kushiriki muhuri. | +| `OMNIROUTE_TRUSTED_PROXIES` | Hapana | _(haijawekwa)_ | `scripts/dev/peer-stamp.mjs` | IP au masafa ya CIDR ya proksi za kinyume, yaliyotenganishwa kwa koma, ambayo orodha ya kuruhusu/kuzuia IP inaweza kuamini `X-Forwarded-For` / `X-Real-IP` zake, pamoja na anwani za loopback, mtandao binafsi na kingo za Cloudflare, ambazo huaminika kila wakati. Inahitajika tu kwa proksi iliyo kwenye anwani nyingine yoyote ya umma; vinginevyo anwani ya proksi yenyewe ndiyo inayotathminiwa na kichujio. | ### Amri za Uzalishaji @@ -216,38 +216,38 @@ OmniRoute hutumia **SQLite** (kupitia `better-sqlite3`) kwa uhifadhi wote endele ## 4. Usalama na Uthibitishaji -| Kigezo | Chaguo-msingi | Faili Chanzo | Maelezo | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Chumvi inayounganishwa na vitambulisho vya maunzi ili kuunda alama ya kipekee ya mashine. Ibadilishe kwa kila usambazaji ili kuhakikisha utengaji. | -| `OMNIROUTE_CLI_SALT` | _(haijawekwa = chumvi nasibu kwa kila usakinishaji inayohifadhiwa katika `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Chumvi ya HMAC ya kutengeneza tokeni ya uthibitishaji ya CLI ya ndani. Kuweka thamani hii hubadilisha tokeni zote za CLI kwenye mashine na daima hupewa kipaumbele kuliko chumvi iliyohifadhiwa. Angalia `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Huweka alama ya `Secure` kwenye vidakuzi vya kikao. **Lazima iwe `true`** wakati wa kuendesha nyuma ya HTTPS. | -| `REQUIRE_API_KEY` | `false` | Programu-kati ya API | Ikiwa `true`, maombi yote ya proksi ya `/v1/*` lazima yajumuishe ufunguo halali wa API. **Alama hii haidhibiti `GET /v1/models`**, ambayo badala yake hufuata hali ya kuingia ya dashibodi (`requireAuthForModels`) — kwa hivyo jibu la `401` kutoka `/v1/models` HALIMAANISHI kuwa uinferensi umelindwa. Angalia `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Huruhusu kuonyesha thamani kamili za funguo za API katika kiolesura cha Dashibodi. Inaweza kusanidiwa kupitia Alama za Vipengele za Dashibodi; ni hatari ya kiusalama kwenye mifumo inayoshirikiwa. | -| `NO_LOG_API_KEY_IDS` | _(tupu)_ | `src/lib/compliance/index.ts` | Vitambulisho vya funguo za API vilivyotenganishwa kwa koma ambavyo havihusishwi katika uwekaji kumbukumbu wa maombi (uzingatiaji wa GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(haijawekwa = bila kikomo)_ | `src/shared/utils/apiKeyPolicy.ts` | Bajeti mbadala ya maombi kwa siku inayotumika kwa funguo za API ambazo safu yake ya `rate_limits` ni null. Ikiwa haijawekwa au ni tupu: hakuna kikomo kinachowekwa kimyakimya (#2289, #11017). `0` ni sawa (bila kikomo). Namba kamili chanya N huwezesha N/siku, 5N/wiki, 20N/mwezi. Thamani zisizo tupu zilizo na muundo batili hurudi kutumia mipaka ya zamani ya 1000/siku, 5000/wiki, 20000/mwezi. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Ukubwa wa juu unaoruhusiwa wa mwili wa ombi. Hukataa data zinazozidi kikomo hiki. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Miili halisi ya maombi yenye ukubwa unaolingana na au unaozidi kiwango hiki huchukua ukodishaji atomiki wa ndani ya mchakato wa udhibiti wa uingizaji wa kazi nzito kabla ya kuchanganua JSON (njia ya BYTE, ikijumuisha `POST /v1/responses`). Njia ileile ya kuepuka wakati nafasi salama ya ziada inapatikana ya [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kama ilivyo kwa miundo mizito; bado inadhibitiwa na `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ili [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) isijirudie. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (MB 50) | `src/shared/middleware/chatBodyAdmission.ts` | Kikomo cha juu kabisa cha njia ya gumzo kinachotekelezwa dhidi ya baiti zilizosomwa wakati wa uingizaji wenye mipaka, ikijumuisha maombi yenye `Content-Length` inayokosekana, batili, au isiyo ya kweli; ziada hupokea `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(haijawekwa — hakuna kikomo cha idadi ya maombi)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** kikomo hiki cha zamani cha IDADI ya maombi sasa hutumika tu kinapowekwa waziwazi. Kikiachwa bila kuwekwa (chaguo-msingi), upokeaji wa maombi mazito ya gumzo badala yake hudhibitiwa na `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — bajeti ya BAITI inayokokotolewa kiotomatiki kutoka kwenye kikomo halisi cha kumbukumbu ya mchakato katika **mchakato mmoja** (rundo moja la V8). Maombi mawili yanayopishana ya `/v1/responses` yenye takriban tokeni 750k husababisha kusitishwa kwa marundo ya takriban Gi 12 (#7849) — onyo la **bajeti ya kumbukumbu**, si kiwango cha juu kabisa cha bidhaa cha 2. Mchakato wenye afya (rundo likiwa chini ya uwiano wa kupunguza mzigo) UNAWEZA kupokea maombi zaidi marefu ya `/v1/responses` kwa wakati mmoja kupitia `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Makumi ya wateja wa muda mrefu wa SSE (40–50) hutegemea rundo + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, si “kiwango cha juu 2”. Kuongeza thamani hii bila tahadhari ili “kutumia seva pangishi” hurudisha tatizo la #7849. Zidisha **marundo** kwa kutumia **`DATA_DIR` N zinazojitegemea** (#11024); kamwe usitumie `replicas>1` kwenye faili moja ya SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(inayotokana na ukokotoaji wa kiotomatiki)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ubatilishaji wa bajeti ya baiti za uingizaji inayokokotolewa kiotomatiki (25% ya kikomo kidogo zaidi kati ya kumbukumbu ya V8/cgroup, ikigawanywa kwa ukuzaji wa muda wa 8x). Thamani zilizokokotolewa na zilizowekwa waziwazi hubanwa ndani ya MiB 8–GiB 2. Mwili mkubwa kuliko bajeti inayotumika hushindwa mara moja kwa `413 body_exceeds_budget`; ushindani kati ya miili inayoweza kuhudumiwa kila mmoja hubaki kuwa `503` inayoweza kujaribiwa tena. Wateja 40–50 wa muda mrefu wa SSE wanaotumika kwa wakati mmoja hutegemea bajeti hii + rundo, si “kiwango cha juu 2” kisichobadilika. Soma `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` katika `/api/monitoring/health` kabla ya kurekebisha mipangilio. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Uwiano wa kupunguza mzigo kutokana na shinikizo la rundo (`heapUsed / heap_size_limit`) kwa upokeaji mzito wa aina ya BAITI na MUUNDO (#10183, #10268, #10437). Ombi zito la wakati mmoja linalozidi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` hupunguzwa tu kwa `503` inayoweza kujaribiwa tena ikiwa rundo PIA liko katika au juu ya uwiano huu; kwenye rundo lenye afya hupokelewa kupitia nafasi ya ziada ya rundo lenye afya badala yake. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (chaguo-msingi `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Uwezo wa ziada wenye mipaka kwa njia ya haraka ya rundo lenye afya (#10437) katika MUUNDO na BAITI **zote mbili** (`admitChatRequest`, ikijumuisha miili ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bila mpaka huu, kila ombi lenye shughuli nyingi lakini lenye rundo lenye afya lilipita ukaguzi wa upokeaji bila kikomo. Pindi idadi hii ya mikataba ya wakati mmoja inapokuwa hai kupitia njia ya rundo lenye afya, maombi zaidi yenye shughuli nyingi huangukia kwenye njia ILE ILE ya kusubiri kwa muda wenye kikomo/kupunguza mzigo inayotumiwa chini ya shinikizo halisi la rundo. `0` huzima upitaji huu kabisa. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Idadi ya ujumbe inayoliainisha ombi la gumzo kuwa zito hata wakati mwili wake uko chini ya kiwango cha baiti. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Idadi ya zana inayoliainisha ombi la gumzo kuwa zito hata wakati mwili wake uko chini ya kiwango cha baiti. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Makadirio ya kihafidhina ya tokeni kulingana na ukubwa wa mifuatano ya herufi yanayoliainisha ombi kuwa zito; hiki ni kiwakilishi cha gharama ya upokeaji, si ugawaji wa tokeni wa mtoa huduma kwa madhumuni ya malipo. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (imezimwa) | `src/shared/middleware/chatBodyAdmission.ts` | Kikomo cha hiari kinachowezeshwa kwa kuchagua kwa historia ya gumzo. Kimezimwa kwa chaguo-msingi: idadi ya ujumbe ni sera ya utekelezaji, si sifa ya jumla ya ombi, na kuweka kikomo hapa hukataa mazungumzo kwa `413` ya mwisho kabla ya mfumo wa mfinyazo kuweza kuyafanya yahudumike. Ukuaji wa rundo unadhibitiwa na `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` pamoja na upunguzaji wa mzigo unaotokana na shinikizo la rundo. Weka thamani chanya kwenye utekelezaji wenye kumbukumbu ndogo unaohitaji kikomo cha juu kabisa; ziada kisha hupokea `413` yenye muundo inayoashiria kuwa mfinyazo unahitajika. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (MB 64) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Kikomo cha juu kabisa cha jibu la chanzo lisilotiririshwa ambalo huhifadhiwa lote kwenye kumbukumbu. Likizidi kikomo hiki, kisomaji cha chanzo hughairiwa na ombi hushindwa mara moja badala ya kukuza mfuatano wa herufi usio na kikomo hadi rundo liishe. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Idadi ya juu ya baiti za mtandao zinazosambazwa kutoka kwenye vichwa vya majibu ya upande wa juu. Bajeti inapozidiwa, vichwa vya kipaumbele cha chini (k.m., `x-codex-*`, `x-oai-request-id`) huondolewa ili kubaki ndani ya vikomo vya kawaida vya vichwa vya reverse-proxy. Weka thamani ya juu zaidi ili kusambaza metadata zaidi ya upande wa juu, kwa gharama ya kuongeza ukubwa wa vichwa vya majibu. | -| `CORS_ORIGIN` | _(haijawekwa)_ | `src/server/cors/origins.ts` | Orodha ya zamani ya CORS ya asili moja zinazoruhusiwa. Pendelea `CORS_ALLOWED_ORIGINS` kwa usambazaji mpya. CORS ni kwa ajili ya wateja wa API wa kivinjari wanaotoka kwenye asili tofauti pekee; uandishi uliothibitishwa wa dashibodi hutumia maombi ya asili sawa pamoja na ulinzi wa CSRF unaofungamanishwa na kipindi badala yake. | -| `CORS_ALLOWED_ORIGINS` | _(haijawekwa)_ | `src/server/cors/origins.ts` | Orodha ya CORS ya asili zinazoruhusiwa, iliyotenganishwa kwa koma. Hakuna wildcard inayotumwa isipokuwa `CORS_ALLOW_ALL=true` isanidiwe waziwazi. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Njia ya dharura ya mazingira ya usanidi pekee ya kurudisha `Origin` yoyote ya kivinjari. Usiwashe kwenye usambazaji wa pamoja au wa uzalishaji. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Zuia miito ya watoa huduma inayolenga masafa ya IP ya faragha/loopback/link-local. Zima tu katika mazingira ya majaribio yaliyotengwa. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Ruhusu URL za watoa huduma zinazoelekeza kwenye mitandao ya faragha/ya ndani (localhost, 192.168.x.x, 10.x.x.x, n.k.). **INAHITAJIKA kwa watoa huduma wanaopangishwa kibinafsi** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Ikiwa `false`, dashibodi hukataa uthibitishaji wa URL za ndani. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Ruhusu kuongeza/kuthibitisha watoa huduma kwenye anwani za ndani/faragha (127.0.0.1, localhost, LAN, masafa ya faragha) — hii inahusu njia ya uthibitishaji wa mtoa huduma pekee. **Chaguo-msingi ni `true`** (kuanza na mazingira ya ndani); weka `false` ili kutekeleza uzuiaji mkali wa anwani za umma pekee. Vituo vya metadata vya wingu (169.254.169.254, metadata.google.internal) hubaki vimezuiwa bila kujali. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ruhusu njia za `/v1/audio/*` (unukuzi, matamshi, tafsiri) zitumie nodi ya mtoa huduma inayoendana na OpenAI na iliyopangishwa nje ya localhost. Imezimwa kwa chaguo-msingi — kuelekeza sauti kwenye seva ya mbali hubadilisha utambulisho wa data inayotoka na lazima iwe uamuzi wa wazi wa mwendeshaji. Nodi za loopback/faragha (localhost, 127.0.0.1, 172.16-31.x) zinaruhusiwa kila wakati na haziathiriwi. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ruhusu `POST /v1/rerank` (na hatua ya loopback ya kupanga upya ya injini ya kumbukumbu) itumie nodi ya mtoa huduma inayoendana na OpenAI na iliyopangishwa nje ya localhost — mashine ya LAN au kifaa-rika cha Tailscale kinachoendesha TEI, Infinity, vLLM, n.k. Imezimwa kwa chaguo-msingi — kuelekeza kwenye seva ya mbali hubadilisha utambulisho wa data inayotoka na lazima iwe uamuzi wa wazi wa mwendeshaji. Nodi za loopback (localhost, 127.0.0.1, 172.16-31.x) zinaruhusiwa kila wakati na haziathiriwi. Nodi za mbali lazima pia zipite sera ya URL zinazotoka ya mtoa huduma (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); seva za metadata za wingu hazielekezewi kamwe. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC inapowashwa, zima kuingia kwa nenosiri ili watumiaji waweze kuthibitisha utambulisho kupitia OIDC Single Sign-On pekee. Lakabu ya moja kwa moja `OIDC_DISABLE_PASSWORD_LOGIN` pia inakubaliwa; Alama ya Kipengele cha Dashibodi yenye ufunguo huohuo ina kipaumbele. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Lakabu ya moja kwa moja ya `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Kigezo | Chaguo-msingi | Faili Chanzo | Maelezo | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Chumvi inayounganishwa na vitambulisho vya maunzi ili kuunda alama ya kipekee ya mashine. Ibadilishe kwa kila usambazaji ili kuhakikisha utengaji. | +| `OMNIROUTE_CLI_SALT` | _(haijawekwa = chumvi nasibu kwa kila usakinishaji inayohifadhiwa katika `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Chumvi ya HMAC ya kutengeneza tokeni ya uthibitishaji ya CLI ya ndani. Kuweka thamani hii hubadilisha tokeni zote za CLI kwenye mashine na daima hupewa kipaumbele kuliko chumvi iliyohifadhiwa. Angalia `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Huweka alama ya `Secure` kwenye vidakuzi vya kikao. **Lazima iwe `true`** wakati wa kuendesha nyuma ya HTTPS. | +| `REQUIRE_API_KEY` | `false` | Programu-kati ya API | Ikiwa `true`, maombi yote ya proksi ya `/v1/*` lazima yajumuishe ufunguo halali wa API. **Alama hii haidhibiti `GET /v1/models`**, ambayo badala yake hufuata hali ya kuingia ya dashibodi (`requireAuthForModels`) — kwa hivyo jibu la `401` kutoka `/v1/models` HALIMAANISHI kuwa uinferensi umelindwa. Angalia `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Huruhusu kuonyesha thamani kamili za funguo za API katika kiolesura cha Dashibodi. Inaweza kusanidiwa kupitia Alama za Vipengele za Dashibodi; ni hatari ya kiusalama kwenye mifumo inayoshirikiwa. | +| `NO_LOG_API_KEY_IDS` | _(tupu)_ | `src/lib/compliance/index.ts` | Vitambulisho vya funguo za API vilivyotenganishwa kwa koma ambavyo havihusishwi katika uwekaji kumbukumbu wa maombi (uzingatiaji wa GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(haijawekwa = bila kikomo)_ | `src/shared/utils/apiKeyPolicy.ts` | Bajeti mbadala ya maombi kwa siku inayotumika kwa funguo za API ambazo safu yake ya `rate_limits` ni null. Ikiwa haijawekwa au ni tupu: hakuna kikomo kinachowekwa kimyakimya (#2289, #11017). `0` ni sawa (bila kikomo). Namba kamili chanya N huwezesha N/siku, 5N/wiki, 20N/mwezi. Thamani zisizo tupu zilizo na muundo batili hurudi kutumia mipaka ya zamani ya 1000/siku, 5000/wiki, 20000/mwezi. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Ukubwa wa juu unaoruhusiwa wa mwili wa ombi. Hukataa data zinazozidi kikomo hiki. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Miili halisi ya maombi yenye ukubwa unaolingana na au unaozidi kiwango hiki huchukua ukodishaji atomiki wa ndani ya mchakato wa udhibiti wa uingizaji wa kazi nzito kabla ya kuchanganua JSON (njia ya BYTE, ikijumuisha `POST /v1/responses`). Njia ileile ya kuepuka wakati nafasi salama ya ziada inapatikana ya [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) kama ilivyo kwa miundo mizito; bado inadhibitiwa na `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ili [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) isijirudie. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (MB 50) | `src/shared/middleware/chatBodyAdmission.ts` | Kikomo cha juu kabisa cha njia ya gumzo kinachotekelezwa dhidi ya baiti zilizosomwa wakati wa uingizaji wenye mipaka, ikijumuisha maombi yenye `Content-Length` inayokosekana, batili, au isiyo ya kweli; ziada hupokea `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(haijawekwa — hakuna kikomo cha idadi ya maombi)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** kikomo hiki cha zamani cha IDADI ya maombi sasa hutumika tu kinapowekwa waziwazi. Kikiachwa bila kuwekwa (chaguo-msingi), upokeaji wa maombi mazito ya gumzo badala yake hudhibitiwa na `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — bajeti ya BAITI inayokokotolewa kiotomatiki kutoka kwenye kikomo halisi cha kumbukumbu ya mchakato katika **mchakato mmoja** (rundo moja la V8). Maombi mawili yanayopishana ya `/v1/responses` yenye takriban tokeni 750k husababisha kusitishwa kwa marundo ya takriban Gi 12 (#7849) — onyo la **bajeti ya kumbukumbu**, si kiwango cha juu kabisa cha bidhaa cha 2. Mchakato wenye afya (rundo likiwa chini ya uwiano wa kupunguza mzigo) UNAWEZA kupokea maombi zaidi marefu ya `/v1/responses` kwa wakati mmoja kupitia `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Makumi ya wateja wa muda mrefu wa SSE (40–50) hutegemea rundo + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, si “kiwango cha juu 2”. Kuongeza thamani hii bila tahadhari ili “kutumia seva pangishi” hurudisha tatizo la #7849. Zidisha **marundo** kwa kutumia **`DATA_DIR` N zinazojitegemea** (#11024); kamwe usitumie `replicas>1` kwenye faili moja ya SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(inayotokana na ukokotoaji wa kiotomatiki)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ubatilishaji wa bajeti ya baiti za uingizaji inayokokotolewa kiotomatiki (25% ya kikomo kidogo zaidi kati ya kumbukumbu ya V8/cgroup, ikigawanywa kwa ukuzaji wa muda wa 8x). Thamani zilizokokotolewa na zilizowekwa waziwazi hubanwa ndani ya MiB 8–GiB 2. Mwili mkubwa kuliko bajeti inayotumika hushindwa mara moja kwa `413 body_exceeds_budget`; ushindani kati ya miili inayoweza kuhudumiwa kila mmoja hubaki kuwa `503` inayoweza kujaribiwa tena. Wateja 40–50 wa muda mrefu wa SSE wanaotumika kwa wakati mmoja hutegemea bajeti hii + rundo, si “kiwango cha juu 2” kisichobadilika. Soma `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` katika `/api/monitoring/health` kabla ya kurekebisha mipangilio. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Uwiano wa kupunguza mzigo kutokana na shinikizo la rundo (`heapUsed / heap_size_limit`) kwa upokeaji mzito wa aina ya BAITI na MUUNDO (#10183, #10268, #10437). Ombi zito la wakati mmoja linalozidi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` hupunguzwa tu kwa `503` inayoweza kujaribiwa tena ikiwa rundo PIA liko katika au juu ya uwiano huu; kwenye rundo lenye afya hupokelewa kupitia nafasi ya ziada ya rundo lenye afya badala yake. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (chaguo-msingi `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Uwezo wa ziada wenye mipaka kwa njia ya haraka ya rundo lenye afya (#10437) katika MUUNDO na BAITI **zote mbili** (`admitChatRequest`, ikijumuisha miili ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Bila mpaka huu, kila ombi lenye shughuli nyingi lakini lenye rundo lenye afya lilipita ukaguzi wa upokeaji bila kikomo. Pindi idadi hii ya mikataba ya wakati mmoja inapokuwa hai kupitia njia ya rundo lenye afya, maombi zaidi yenye shughuli nyingi huangukia kwenye njia ILE ILE ya kusubiri kwa muda wenye kikomo/kupunguza mzigo inayotumiwa chini ya shinikizo halisi la rundo. `0` huzima upitaji huu kabisa. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Idadi ya ujumbe inayoliainisha ombi la gumzo kuwa zito hata wakati mwili wake uko chini ya kiwango cha baiti. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Idadi ya zana inayoliainisha ombi la gumzo kuwa zito hata wakati mwili wake uko chini ya kiwango cha baiti. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Makadirio ya kihafidhina ya tokeni kulingana na ukubwa wa mifuatano ya herufi yanayoliainisha ombi kuwa zito; hiki ni kiwakilishi cha gharama ya upokeaji, si ugawaji wa tokeni wa mtoa huduma kwa madhumuni ya malipo. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (imezimwa) | `src/shared/middleware/chatBodyAdmission.ts` | Kikomo cha hiari kinachowezeshwa kwa kuchagua kwa historia ya gumzo. Kimezimwa kwa chaguo-msingi: idadi ya ujumbe ni sera ya utekelezaji, si sifa ya jumla ya ombi, na kuweka kikomo hapa hukataa mazungumzo kwa `413` ya mwisho kabla ya mfumo wa mfinyazo kuweza kuyafanya yahudumike. Ukuaji wa rundo unadhibitiwa na `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` pamoja na upunguzaji wa mzigo unaotokana na shinikizo la rundo. Weka thamani chanya kwenye utekelezaji wenye kumbukumbu ndogo unaohitaji kikomo cha juu kabisa; ziada kisha hupokea `413` yenye muundo inayoashiria kuwa mfinyazo unahitajika. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (MB 64) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Kikomo cha juu kabisa cha jibu la chanzo lisilotiririshwa ambalo huhifadhiwa lote kwenye kumbukumbu. Likizidi kikomo hiki, kisomaji cha chanzo hughairiwa na ombi hushindwa mara moja badala ya kukuza mfuatano wa herufi usio na kikomo hadi rundo liishe. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Idadi ya juu ya baiti za mtandao zinazosambazwa kutoka kwenye vichwa vya majibu ya upande wa juu. Bajeti inapozidiwa, vichwa vya kipaumbele cha chini (k.m., `x-codex-*`, `x-oai-request-id`) huondolewa ili kubaki ndani ya vikomo vya kawaida vya vichwa vya reverse-proxy. Weka thamani ya juu zaidi ili kusambaza metadata zaidi ya upande wa juu, kwa gharama ya kuongeza ukubwa wa vichwa vya majibu. | +| `CORS_ORIGIN` | _(haijawekwa)_ | `src/server/cors/origins.ts` | Orodha ya zamani ya CORS ya asili moja zinazoruhusiwa. Pendelea `CORS_ALLOWED_ORIGINS` kwa usambazaji mpya. CORS ni kwa ajili ya wateja wa API wa kivinjari wanaotoka kwenye asili tofauti pekee; uandishi uliothibitishwa wa dashibodi hutumia maombi ya asili sawa pamoja na ulinzi wa CSRF unaofungamanishwa na kipindi badala yake. | +| `CORS_ALLOWED_ORIGINS` | _(haijawekwa)_ | `src/server/cors/origins.ts` | Orodha ya CORS ya asili zinazoruhusiwa, iliyotenganishwa kwa koma. Hakuna wildcard inayotumwa isipokuwa `CORS_ALLOW_ALL=true` isanidiwe waziwazi. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Njia ya dharura ya mazingira ya usanidi pekee ya kurudisha `Origin` yoyote ya kivinjari. Usiwashe kwenye usambazaji wa pamoja au wa uzalishaji. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Jina mbadala la zamani. Swichi yake ya dashibodi (ubadilishaji wa hifadhidata) husomwa kabla ya thamani hii ya mazingira; `false`, `0`, `no` au `off` katika mojawapo huzima ukaguzi wa wenyeji kwa njia ile ile `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` inavyofanya, hata kama swichi ya dashibodi ya bendera hiyo imehifadhiwa kama imezimwa; thamani nyingine zinaacha ulinzi kwa bendera mbili zilizo hapa chini. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Huzima ukaguzi wa wenyeji wa ulinzi wa URL zinazotoka, pamoja na uzuiaji wa metadata ya wingu (169.254.0.0/16 yote, metadata.google.internal), kwenye njia za mtoa huduma zinazofuata hali ya ulinzi: uthibitishaji wa URL, ugunduzi wa modeli, URL za msingi za nodi za mtoa huduma (ikiwa ni pamoja na nodi za mbali za kupanga upya) na jaribio la akiba ya proksi, miongoni mwa njia zingine zinazotoka zinazohusiana na watoa huduma, kama vile kuvuta picha/vyombo vya habari kutoka mbali. Pia huruhusu malengo ya webhook ya faragha, isipokuwa vituo vya metadata ya wingu/link-local, ambavyo hubaki vikizuiwa hata bendera hii ikiwa imewashwa. Maombi ya mazungumzo yanayotumwa kupitia `BaseExecutor.execute()` hubaki na ukaguzi wake wa metadata ya wingu. Watoa huduma wa ndani hufanya kazi kwa chaguo-msingi: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (chaguo-msingi `true`) tayari inaruhusu URL za ndani na za LAN kwenye njia za uthibitishaji, ugunduzi na nodi za mtoa huduma; jaribio la akiba ya proksi na malengo ya webhook ya faragha hushauriana na bendera hii pekee na huzuia wenyeji wa ndani/LAN wakati imezimwa. Inabatilisha `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Thamani iliyohifadhiwa kutoka kwenye swichi ya dashibodi ina kipaumbele kuliko kigezo hiki. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ruhusu URL za watoa huduma kwenye anwani za ndani/faragha (127.0.0.1, localhost, LAN, masafa ya faragha) kwa uthibitishaji wa mtoa huduma, ugunduzi wa modeli, mazungumzo na njia zingine zinazotoka zinazohusiana na watoa huduma ambazo hali ya ulinzi inazitawala. **Chaguo-msingi ni `true`** (kuanza na mazingira ya ndani): ulinzi huzuia vituo vya metadata ya wingu — 169.254.0.0/16 yote pamoja na majina ya wenyeji ya metadata yanayojulikana (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Weka `false` kwa hali ya umma pekee, ambayo pia huzuia wenyeji wa faragha na wa loopback. Ulinzi hukagua jina la mwenyeji au IP kama ilivyoandikwa, kabla ya utafutaji wowote wa DNS; katika hali ya umma pekee kifungashio cha safe-fetch pia kinatatua jina la mwenyeji na kukataa jibu lolote la faragha. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` huzima ukaguzi huu kwenye njia ambazo safu yake inaorodhesha. Watoa huduma wa ndani waliojumuishwa, kama LM Studio, Ollama, vLLM, Llamafile na Triton, huepuka ukaguzi wote wa wenyeji kwa uthibitishaji wa funguo na mazungumzo. Thamani iliyohifadhiwa kutoka kwenye swichi ya dashibodi ina kipaumbele kuliko kigezo hiki. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Ruhusu njia za `/v1/audio/*` (unukuzi, matamshi, tafsiri) zitumie nodi ya mtoa huduma inayoendana na OpenAI na iliyopangishwa nje ya localhost. Imezimwa kwa chaguo-msingi — kuelekeza sauti kwenye seva ya mbali hubadilisha utambulisho wa data inayotoka na lazima iwe uamuzi wa wazi wa mwendeshaji. Nodi za loopback/faragha (localhost, 127.0.0.1, 172.16-31.x) zinaruhusiwa kila wakati na haziathiriwi. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Ruhusu `POST /v1/rerank` (na hatua ya loopback ya kupanga upya ya injini ya kumbukumbu) itumie nodi ya mtoa huduma inayoendana na OpenAI na iliyopangishwa nje ya localhost — mashine ya LAN au kifaa-rika cha Tailscale kinachoendesha TEI, Infinity, vLLM, n.k. Imezimwa kwa chaguo-msingi — kuelekeza kwenye seva ya mbali hubadilisha utambulisho wa data inayotoka na lazima iwe uamuzi wa wazi wa mwendeshaji. Nodi za loopback (localhost, 127.0.0.1, 172.16-31.x) zinaruhusiwa kila wakati na haziathiriwi. URL ya msingi ya nodi ya mbali lazima pia ipite sera ya URL zinazotoka ya mtoa huduma (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC inapowashwa, zima kuingia kwa nenosiri ili watumiaji waweze kuthibitisha utambulisho kupitia OIDC Single Sign-On pekee. Lakabu ya moja kwa moja `OIDC_DISABLE_PASSWORD_LOGIN` pia inakubaliwa; Alama ya Kipengele cha Dashibodi yenye ufunguo huohuo ina kipaumbele. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Lakabu ya moja kwa moja ya `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Orodha ya Ukaguzi wa Uimarishaji diff --git a/docs/i18n/ta/CONTRIBUTING.md b/docs/i18n/ta/CONTRIBUTING.md index c19a3d1d7091..8be0bee151ee 100644 --- a/docs/i18n/ta/CONTRIBUTING.md +++ b/docs/i18n/ta/CONTRIBUTING.md @@ -392,16 +392,16 @@ OAuth நற்சான்றுகளை `src/lib/oauth/constants/oauth.ts`- - [ ] Linting வெற்றிபெறுகிறது (`npm run lint`) - [ ] Build வெற்றிபெறுகிறது (`npm run build`) - [ ] புதிய public functions மற்றும் interfaces-க்கு TypeScript types சேர்க்கப்பட்டுள்ளன -- [ ] Hardcoded secrets அல்லது fallback values எதுவும் இல்லை -- [ ] Public upstream credentials, literals-ஆக அல்லாமல் `resolvePublicCred()` வழியாக உட்பொதிக்கப்பட்டுள்ளன ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)-ஐப் பார்க்கவும்) +- [ ] Hardcoded secrets அல்லது fallback values இல்லை +- [ ] Public upstream credentials, literals-ஆக ஒருபோதும் இல்லாமல், `resolvePublicCred()` வழியாகப் பதிக்கப்பட்டுள்ளன ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)-ஐப் பார்க்கவும்) - [ ] Error responses, `buildErrorBody()` / `sanitizeErrorMessage()` வழியாக அனுப்பப்படுகின்றன — response bodies-இல் raw stack traces இல்லை ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)-ஐப் பார்க்கவும்) - [ ] Shell commands (`exec` / `spawn`), runtime values-ஐ string interpolation வழியாக அல்லாமல் `env` வழியாக அனுப்புகின்றன - [ ] அனைத்து inputs-உம் Zod schemas மூலம் சரிபார்க்கப்பட்டுள்ளன -- [ ] பயனர்களுக்குத் தெரியக்கூடிய மாற்றங்களுக்கு `changelog.d/{features|fixes|maintenance}/-.md`-இன் கீழ் Changelog **fragment** சேர்க்கப்பட்டுள்ளது ([`changelog.d/README.md`](./changelog.d/README.md)-ஐப் பார்க்கவும்) — `CHANGELOG.md`-ஐ நேரடியாகத் திருத்த **வேண்டாம்**; fragments, release நேரத்தில் ஒருங்கிணைக்கப்படுவதால் PR-களுக்கு இடையே ஒருபோதும் முரண்படாது +- [ ] பயனர்கள் காணக்கூடிய மாற்றங்களுக்கு `changelog.d/{features|fixes|maintenance}/-.md`-இன் கீழ் Changelog **fragment** சேர்க்கப்பட்டுள்ளது ([`changelog.d/README.md`](./changelog.d/README.md)-ஐப் பார்க்கவும்) — `CHANGELOG.md`-ஐ நேரடியாகத் திருத்த **வேண்டாம்**; fragments வெளியீட்டு நேரத்தில் ஒருங்கிணைக்கப்படுவதால், PR-களுக்கு இடையே ஒருபோதும் முரண்படாது - [ ] ஆவணங்கள் புதுப்பிக்கப்பட்டுள்ளன (பொருந்தினால்) -- [ ] புதிய CodeQL / Secret-Scanning alerts எதுவும் உருவாக்கப்படவில்லை, அல்லது ஒவ்வொன்றும் தொடர்புடைய `docs/security/` ஆவணத்தை மேற்கோளிட்டு தொழில்நுட்ப நியாயத்துடன் நிராகரிக்கப்பட்டுள்ளது -- [ ] Child processes-ஐ உருவாக்கும் routes (`/api/mcp/`, `/api/cli-tools/runtime/`), `src/server/authz/routeGuard.ts`-இல் `isLocalOnlyPath()` என வகைப்படுத்தப்பட்டுள்ளன — [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md)-ஐப் பார்க்கவும் -- [ ] Commit messages-இல் `Co-Authored-By` trailers இல்லை — commits, repository உரிமையாளரின் Git identity-இன் கீழ் மட்டுமே தோன்ற வேண்டும் (Hard Rule #16) +- [ ] புதிய CodeQL / Secret-Scanning alerts எதுவும் உருவாக்கப்படவில்லை; அல்லது ஒவ்வொன்றும் தொடர்புடைய `docs/security/` ஆவணத்தைக் குறிப்பிடும் தொழில்நுட்ப நியாயத்துடன் நிராகரிக்கப்பட்டுள்ளது +- [ ] Child processes-ஐத் தொடங்கும் routes (`/api/mcp/`, `/api/cli-tools/runtime/`), `src/server/authz/routeGuard.ts`-இல் `isLocalOnlyPath()` என வகைப்படுத்தப்பட்டுள்ளன — [Hard Rule #15](docs/security/ROUTE_GUARD_TIERS.md)-ஐப் பார்க்கவும் +- [ ] Commit messages-இல் AI/bot `Co-authored-by` trailers இல்லை (Hard Rule #16) — மீண்டும் பயன்படுத்தப்பட்ட பணிக்குரிய மனித collaborators, நிலையான `Co-authored-by: Name ` trailers மூலம் குறிப்பிடப்பட்டுள்ளனர் --- diff --git a/docs/i18n/ta/docs/architecture/QUALITY_GATES.md b/docs/i18n/ta/docs/architecture/QUALITY_GATES.md index a655988c810a..30be51815b62 100644 --- a/docs/i18n/ta/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ta/docs/architecture/QUALITY_GATES.md @@ -15,208 +15,208 @@ ratchet baseline அல்லது pass/fail கொள்கையைப் ப --- -## கேட் இருப்புப் பட்டியல் மற்றும் செயலாக்க சுயவிவரங்கள் - -### வேட்பாளர் ஏற்பு - -CI மற்றும் Quality Gates பணிப்பாய்வுகள் ஒவ்வொன்றும் நிலையான ஒரு தீர்ப்பை வெளியிடுகின்றன: `Gate / CI` மற்றும் -`Gate / Quality`. அவற்றின் பதிப்பிடப்பட்ட ஏற்புக் கொள்கை, மேல்நிலைப் பணிகள் அனைத்தையும் -கட்டாயமானவை அல்லது ஆலோசனை சார்ந்தவை எனப் பட்டியலிடுகிறது. பொருந்தக்கூடிய கட்டாயப் பணி வெற்றிபெற வேண்டும்: -காணாமல் போன, ரத்துசெய்யப்பட்ட, தவிர்க்கப்பட்ட, நிலுவையிலுள்ள மற்றும் அறியப்படாத முடிவுகள் PASS என்பதை நிறுவ முடியாது. செல்லுபடியாகும் -docs-only அல்லது catalog-only வகைப்பாடு ஒரு code lane-ஐப் பொருந்தாததாக மாற்றலாம்; -வரைவு PR ஏற்றுக்கொள்ளப்பட்ட வேட்பாளர் அல்ல. `hotfix` லேபிள் ஆதாரத் தேவையை நீக்காது. - -இரு பணிப்பாய்வுகளும் PR-கள், main/release கிளைகளுக்கான push-கள், கைமுறை dispatch மற்றும் -merge-group நிகழ்வுகளை உள்ளடக்குகின்றன. Push, dispatch மற்றும் merge-group முழுத் தேர்வையும் இயக்குகின்றன. Fork-கள் -மற்றும் merge group-கள், இயல்பாக self-hosted runner-களைத் தேர்ந்தெடுக்கும் பணிகளுக்கு hosted runner-களைப் பயன்படுத்துகின்றன; -வெளியீட்டிற்கு முன் போதுமான hosted திறன் சரிபார்க்கப்பட வேண்டும். - -ஒவ்வொரு JSON ரசீதும் checkout செய்யப்பட்ட SHA, workflow run மற்றும் attempt-ஐ அடையாளப்படுத்துகிறது. -checkout/event SHA பொருந்தாமையை CLI நிராகரிக்கிறது. புதிய அல்லது நீக்கப்பட்ட lane ஒன்று கவனிக்கப்படாமல் மறைந்துவிடாதபடி, -பணிப்பாய்வு சோதனைகள் கொள்கை உறுப்புரிமையை verdict job-இன் `needs` பட்டியலுடன் பிணைக்கின்றன. -இந்த ரசீதுகள் அவற்றின் சொந்த பணிப்பாய்வை மட்டுமே உள்ளடக்குகின்றன; publication, deployment அல்லது ஏற்கனவே உள்ள -advisory scanner-இன் உள் செயல்பாடுகளை அல்ல. கிளை விதிகளில் இரு check பெயர்களையும் செயல்படுத்துவது -தனியான நிர்வாக மாற்றமாகும்; இந்தப் பணிகளைச் சேர்ப்பது மட்டுமே ஒரு கிளையைப் பாதுகாக்காது. - -### நிலையான ஸ்கேன் இருப்புப் பட்டியல் - -பதிப்பிடப்பட்ட npm-alias இருப்புப் பட்டியலும் static-scan உறுப்புரிமையும் -`config/quality/gate-manifest.json`-இல் உள்ளன. script பெயர்களையும் துல்லியமான command-களையும் -`package.json`-க்கு எதிராகச் சரிபார்க்க `npm run check:gate-manifest`-ஐ இயக்கவும்; சேர்த்தல்கள், நீக்கல்கள் மற்றும் -command மாற்றங்கள் local hook மற்றும் CI-இல் உள்ள change-classification பணிகள் இரண்டிலும் தோல்வியை ஏற்படுத்தும். -ஒரு alias என்பது workflow job, matrix instance அல்லது test case அல்ல: இந்த எண்ணிக்கைகள் -ஒன்றுக்கொன்று மாற்றிப் பயன்படுத்தக்கூடியவை எனக் காட்டப்படக் கூடாது. +## கேட்டுகளின் பட்டியல் மற்றும் செயல்படுத்தல் சுயவிவரங்கள் + +### வேட்பாளர் அனுமதி + +CI மற்றும் Quality Gates பணிப்பாய்வுகள் ஒவ்வொன்றும் நிலையான தீர்ப்பை வெளியிடுகின்றன: `Gate / CI` மற்றும் +`Gate / Quality`. அவற்றின் பதிப்பிடப்பட்ட அனுமதிக் கொள்கை, ஒவ்வொரு மேல்நிலைப் பணியையும் +கட்டாயமானதாகவோ ஆலோசனைக்குரியதாகவோ பட்டியலிடுகிறது. பொருந்தக்கூடிய கட்டாயப் பணி வெற்றிபெற வேண்டும்: விடுபட்ட, +ரத்துசெய்யப்பட்ட, தவிர்க்கப்பட்ட, நிலுவையிலுள்ள மற்றும் அறியப்படாத முடிவுகளால் PASS என்பதை உறுதிப்படுத்த முடியாது. செல்லுபடியாகும் +ஆவணங்கள்-மட்டும் அல்லது பட்டியல்-மட்டும் வகைப்பாடு, குறியீட்டுத் தடத்தைப் பொருந்தாததாக்கலாம்; +வரைவு PR ஏற்றுக்கொள்ளப்பட்ட வேட்பாளர் அல்ல. `hotfix` சிட்டை ஆதாரத் தேவையை நீக்காது. + +இரு பணிப்பாய்வுகளும் PR-களையும் main/release கிளைகளுக்கான push-களையும், கைமுறை dispatch மற்றும் +merge-group நிகழ்வுகளையும் உள்ளடக்குகின்றன. Push, dispatch மற்றும் merge-group ஆகியவை முழுத் தேர்வையும் இயக்குகின்றன. Fork-களும் +merge group-களும், வழக்கமாக self-hosted runner-களைத் தேர்ந்தெடுக்கும் பணிகளுக்கு hosted runner-களைப் பயன்படுத்துகின்றன; +வெளியீட்டுக்கு முன் போதுமான hosted திறன் சரிபார்க்கப்பட வேண்டும். + +ஒவ்வொரு JSON ரசீதும் checkout செய்யப்பட்ட SHA, பணிப்பாய்வு இயக்கம் மற்றும் முயற்சியை அடையாளப்படுத்துகிறது. +checkout/event SHA பொருந்தாமையை CLI நிராகரிக்கிறது. ஒரு புதிய அல்லது நீக்கப்பட்ட தடம் அமைதியாக மறைந்துவிடாதபடி, +கொள்கை உறுப்புரிமையை தீர்ப்புப் பணியின் `needs` பட்டியலுடன் பணிப்பாய்வுச் சோதனைகள் பிணைக்கின்றன. +இந்த ரசீதுகள் அவற்றின் சொந்தப் பணிப்பாய்வை உள்ளடக்குகின்றன; வெளியீடு, deployment அல்லது ஏற்கனவே உள்ள +ஆலோசனை scanner-இன் உள்ளக செயல்பாடுகளை அல்ல. Branch விதிகளில் இரு check பெயர்களையும் செயல்படுத்துவது +தனியான நிர்வாக மாற்றமாகும்; இந்தப் பணிகளைச் சேர்ப்பது மட்டும் ஒரு branch-ஐப் பாதுகாக்காது. + +### நிலையான scan பட்டியல் + +பதிப்பிடப்பட்ட npm-alias பட்டியலும் static-scan உறுப்புரிமையும் +`config/quality/gate-manifest.json`-இல் உள்ளன. Script பெயர்களையும் சரியான command-களையும் +`package.json`-உடன் ஒப்பிட்டு சரிபார்க்க `npm run check:gate-manifest`-ஐ இயக்கவும்; சேர்த்தல்கள், நீக்கங்கள் மற்றும் +command மாறுபாடுகள் local hook மற்றும் CI-இன் மாற்ற-வகைப்பாட்டுப் பணிகள் இரண்டையும் தோல்வியடையச் செய்கின்றன. +ஒரு alias என்பது பணிப்பாய்வுப் பணி, matrix instance அல்லது test case அல்ல: இந்த எண்ணிக்கைகளை +ஒன்றுக்கொன்று மாற்றிப் பயன்படுத்தக்கூடியவையாகக் காட்டக்கூடாது. தேர்ந்தெடுக்கப்பட்ட alias-களை இயக்காமல் ஆய்வு செய்ய `npm run quality:scan -- --list` அல்லது -`npm run quality:scan:fast -- --list`-ஐப் பயன்படுத்தவும். runner, npm entrypoint-ஐ இயக்குவதால், -அதன் runtime பாதுகாக்கப்படுகிறது; உள்ளமைக்கப்பட்ட இடங்களில் Bun-உம் இதில் அடங்கும். -இந்தச் சுயவிவரங்களுக்கு வெளியே உள்ள alias-களை தனியாக இயக்கப்படுபவையாக manifest பதிவு செய்கிறது; மேலும் -படிக்க மட்டும் அனுமதிக்கும் scan profile-களில் maintenance command-கள் தடைசெய்யப்பட்டுள்ளன. +`npm run quality:scan:fast -- --list` பயன்படுத்தவும். Runner npm entrypoint-ஐ அழைப்பதால், +அதன் runtime (உள்ளமைக்கப்பட்ட இடங்களில் Bun உட்பட) பாதுகாக்கப்படுகிறது. +அந்தச் சுயவிவரங்களுக்கு வெளியே உள்ள alias-கள் தனித்தனியாக அழைக்கப்படுவதாக manifest பதிவுசெய்கிறது; மேலும் +பராமரிப்பு command-கள் படிக்க-மட்டும் scan சுயவிவரங்களில் தடைசெய்யப்பட்டுள்ளன. -இந்தச் சுயவிவரங்கள் static scan-ஐ மட்டுமே உள்ளடக்குகின்றன. இவை product test-கள், -coverage, packaging, வெளிப்புற check-கள் அல்லது ஒரு வேட்பாளரின் முழுமையான release acceptance-ஐச் சான்றளிப்பதில்லை. -Workflow admission, இணைக்கப்பட்ட `config/quality/admission-policy.json` மற்றும் -`scripts/quality/admission-verdict.mjs`-ஐப் பயன்படுத்துகிறது. Release-observer profile-கள் தனியாகவே இருக்கும்; -அவற்றுக்குப் பொருந்தும் check-களையும் ரசீதுகளையும் தனித்தனியாக ஆய்வு செய்யவும். கீழேயுள்ள உரைநடை -இருப்புப் பட்டியல் ஒரு மேற்கோள் மட்டுமே; ஒரு gate உண்மையில் இயக்கப்பட்டது என்பதற்கான ஆதாரம் அல்ல. +இந்தச் சுயவிவரங்கள் static scan-ஐ மட்டுமே உள்ளடக்குகின்றன. இவை தயாரிப்புச் சோதனைகள், +coverage, packaging, வெளிப்புற check-கள் அல்லது ஒரு வேட்பாளரின் முழுமையான release ஏற்றுக்கொள்ளுதலைச் சான்றளிப்பதில்லை. +பணிப்பாய்வு அனுமதி, இணைக்கப்பட்டுள்ள `config/quality/admission-policy.json` மற்றும் +`scripts/quality/admission-verdict.mjs`-ஐப் பயன்படுத்துகிறது. Release-observer சுயவிவரங்கள் தனித்தே உள்ளன; +அவற்றின் பொருந்தக்கூடிய check-களையும் ரசீதுகளையும் தனித்தனியாக ஆய்வு செய்யவும். கீழே உள்ள உரைநடைப் +பட்டியல் ஒரு குறிப்புதான்; ஒரு gate உண்மையில் இயங்கியது என்பதற்கான ஆதாரம் அல்ல. -Script-கள் `scripts/check/` (policy gate-கள்) மற்றும் `scripts/quality/` (ratchet engine) ஆகியவற்றின் கீழ் உள்ளன. -CI-க்கான ஒற்றை உண்மை ஆதாரம் `.github/workflows/ci.yml` ஆகும். +Script-கள் `scripts/check/` (கொள்கை gate-கள்) மற்றும் `scripts/quality/` (ratchet engine) ஆகியவற்றின் கீழ் உள்ளன. +CI-க்கான அதிகாரப்பூர்வ மூலம் `.github/workflows/ci.yml` ஆகும். ### Release PR விரைவுப் பாதை (`quality.yml`) -`.github/workflows/quality.yml`, main/release PR-கள், பாதுகாக்கப்பட்ட கிளைகளுக்கான -push-கள், dispatch மற்றும் merge group-களில் CI-க்கு துணையாகச் செயல்படுகிறது. PR-கள் பாதை-வடிகட்டப்பட்ட விரைவுச் சோதனைகளைப் பயன்படுத்துகின்றன. நிரந்தரமாக -முடக்கப்பட்டிருந்த நகல் build அகற்றப்பட்டது; உண்மையான build/package/boot சோதனைகள் CI-இல் தொடர்ந்து உள்ளன. +`.github/workflows/quality.yml`, main/release PR-கள், பாதுகாக்கப்பட்ட branch-களுக்கான +push-கள், dispatch மற்றும் merge group-களில் CI-ஐ நிறைவுசெய்கிறது. PR-கள் path-filter செய்யப்பட்ட விரைவான check-களைப் பயன்படுத்துகின்றன. நிரந்தரமாக +முடக்கப்பட்டிருந்த நகல் build அகற்றப்பட்டது; உண்மையான build/package/boot check-கள் CI-இல் தொடர்ந்து உள்ளன. -| பணி | வரம்பு | தடுப்பதா | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `Docs Gates (fast-path)` | Docs/code PR-கள்; API docs refs மற்றும் docs-all | ஆம் | -| `Fast Quality Gates` | Code PR-கள்; static check-கள், typecheck, dashboard typecheck, பாதிக்கப்பட்ட unit test-கள் | ஆம் | -| `Forgotten sibling tests` | Code PR-கள்; மாற்றப்பட்ட module-களிலிருந்து static consumer-கள் மற்றும் candidate sibling test-கள் வரை பின்தொடர்தல்; barrel மற்றும் dynamic-import பாதைகள் ஆலோசனை நோயறிதல்களாக அறிக்கையிடப்படுகின்றன, குறிப்பிடப்பட்ட allowlist விதிவிலக்குகளுடன் | **ஆலோசனை சார்ந்தது** | -| `Vitest (fast-path)` | Code PR-கள்; விரைவான vitest suite | ஆம் | -| `Unit Tests fast-path` | Code PR-கள்; 4-shard unit suite | ஆம் | -| `No new ESLint warnings` | Code PR-கள்; suppression-களை அறிந்த lint guard | ஆம், fork-கள் உட்பட | -| `Merge integrity (changelog + generated skills)` | வரைவு அல்லாத PR-கள்; changelog மற்றும் உருவாக்கப்பட்ட skill ஒத்திசைவு | ஆம், fork-கள் உட்பட | +| பணி | வரம்பு | தடுப்பது | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | +| `Docs Gates (fast-path)` | ஆவணங்கள்/குறியீட்டு PR-கள்; API ஆவணக் குறிப்புகள் மற்றும் அனைத்து ஆவணங்களும் | ஆம் | +| `Fast Quality Gates` | குறியீட்டு PR-கள்; static check-கள், typecheck, dashboard typecheck, தாக்கமுற்ற unit test-கள் | ஆம் | +| `Forgotten sibling tests` | குறியீட்டு PR-கள்; மாற்றப்பட்ட module-களிலிருந்து static consumer-கள் மற்றும் சாத்தியமான sibling test-கள் வரை தடமறிதல்; barrel மற்றும் dynamic-import path-கள், குறிப்பிடப்பட்ட allowlist விதிவிலக்குகளுடன், ஆலோசனை diagnostics ஆக அறிவிக்கப்படுகின்றன | **ஆலோசனைக்குரியது** | +| `Vitest (fast-path)` | குறியீட்டு PR-கள்; விரைவான vitest suite | ஆம் | +| `Unit Tests fast-path` | குறியீட்டு PR-கள்; 4-shard unit suite | ஆம் | +| `No new ESLint warnings` | குறியீட்டு PR-கள்; suppression-களை அறிந்த lint guard | ஆம், fork-கள் உட்பட | +| `Merge integrity (changelog + generated skills)` | வரைவு அல்லாத PR-கள்; changelog மற்றும் உருவாக்கப்பட்ட skill ஒத்திசைவு | ஆம், fork-கள் உட்பட | #### மறக்கப்பட்ட sibling test-கள் அறிக்கை `npm run check:forgotten-sibling-tests`, test-impact map-க்குப் பின்னால் உள்ள import resolver-ஐ மீண்டும் பயன்படுத்துகிறது. -மாற்றப்பட்ட ஒவ்வொரு production module-க்கும், candidate -test pull-request diff-இல் இல்லாதபோது, தீர்மானகரமான `changed module/symbol -> static consumer -> candidate sibling test` சங்கிலிகளை அறிக்கையிடுகிறது. -எந்தவொரு blocking rollout-க்கும் முன் அளவுத்திருத்தத்திற்காக Markdown summary மற்றும் JSON result ஆகியவை -`forgotten-sibling-tests` workflow artifact ஆகத் தக்கவைக்கப்படுகின்றன. +மாற்றப்பட்ட ஒவ்வொரு production module-க்கும், சாத்தியமான +test pull-request diff-இல் இல்லாதபோது, நிர்ணயிக்கப்பட்ட +`changed module/symbol -> static consumer -> candidate sibling test` சங்கிலிகளை இது அறிவிக்கிறது. தடுப்புச் செயலாக்கத்திற்கு முந்தைய அளவுத்திருத்தத்திற்காக, +Markdown சுருக்கமும் JSON முடிவும் `forgotten-sibling-tests` பணிப்பாய்வு artifact ஆகத் தக்கவைக்கப்படுகின்றன. -Barrel மறு-ஏற்றுமதிகளும் dynamic imports-உம் resolution கண்டறிதல்களுக்கு மட்டுமே பயன்படுத்தப்படுகின்றன; அவை ஒருபோதும் -தடுக்கும் கண்டறிதலை உருவாக்காது. மதிப்பாய்வு செய்யப்பட்ட விதிவிலக்குகள் +Barrel மறு-ஏற்றுமதிகளும் dynamic imports-உம் resolution கண்டறிதல்களுக்காக மட்டுமே; அவை ஒருபோதும் +தடுக்கும் finding-ஐ உருவாக்காது. மதிப்பாய்வு செய்யப்பட்ட விதிவிலக்குகள் `config/quality/forgotten-sibling-allowlist.json`-இல் உள்ளன. ஒவ்வொரு பதிவும் consumer மற்றும் candidate -test-ஐக் குறிப்பிட வேண்டும், ஒரு குறிப்பிட்ட காரணத்தை வழங்க வேண்டும், மேலும் ஒரு GitHub issue அல்லது pull request-க்கு இணைக்க வேண்டும். தவறான வடிவமைப்பைக் கொண்ட பதிவுகள் -தானாகவே தோல்வியடையும். நீக்கப்பட்ட candidate test அல்லது `.skip`/`.todo`-ஐச் சேர்க்கும் diff-ஐ விதிவிலக்குகளால் புறக்கணிக்க முடியாது; -assertion-ஐ வலுவிழக்கச் செய்தல் மற்றும் பிற மறைத்தல் செயல்கள், சுயாதீனமாகத் தடுக்கும் -`check:test-masking` gate-இன் பொறுப்பிலேயே தொடரும். +test-ஐக் குறிப்பிட வேண்டும், குறிப்பிட்ட காரணத்தை வழங்க வேண்டும், மேலும் ஒரு GitHub issue அல்லது pull request-ஐ இணைக்க வேண்டும். தவறாக வடிவமைக்கப்பட்ட பதிவுகள் +மூடிய நிலையில் தோல்வியடையும். நீக்கப்பட்ட candidate test அல்லது `.skip`/`.todo`-ஐச் சேர்க்கும் diff-ஐ விதிவிலக்குகளால் மறைக்க முடியாது; +assertion-ஐ பலவீனப்படுத்துதல் மற்றும் பிற masking செயல்கள், தனித்தனியாகத் தடுக்கும் +`check:test-masking` gate-இன் பொறுப்பிலேயே தொடர்கின்றன. ### பணி: `lint` `main`-க்கான ஒவ்வொரு PR-இலும் இயங்கும். தோல்வியடைந்தால் merge செய்வதைத் தடுக்கும். -| Script (`npm run ...`) | சரிபார்ப்பவை | தடுக்கும் தன்மை | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -| `check:node-runtime` | Node.js பதிப்பு ஆதரிக்கப்படும் வரம்பிற்குள் உள்ளது | ஆம் | -| `check:cycles` | சுழற்சி imports — அனைத்து `src/` + `open-sse/` modules | ஆம் | -| `check:route-validation:t06` | அனைத்து routes-இலும் Zod schemas உள்ளன (Tier 6 கொள்கை) | ஆம் | -| `check:any-budget:t11` | `@ts-expect-error // any` எண்ணிக்கை வரம்பை மீறவில்லை (Tier 11 catraca) | ஆம் | -| `check:provider-consistency` | `providers.ts`-இல் உள்ள ஒவ்வொரு provider-க்கும் `providerRegistry.ts`-இல் பொருந்தும் பதிவு உள்ளது (மற்றும் மறுதிசையிலும், allowlist-க்குள்) | ஆம் | -| `check:model-lifecycle` | கைமுறையாகப் பராமரிக்கப்படும் மூன்று routing அட்டவணைகள், repository-இல் சேர்க்கப்பட்ட lifecycle snapshot (#11503)-உடன் சீராக உள்ளன: `FITNESS_TABLE` (`taskFitness.ts`), `REGISTRY` route செய்யக்கூடிய ஓய்வுபெற்ற எந்த id-க்கும் score வழங்காது; ஒவ்வொரு `BUILT_IN_ALIASES` target-உம் `REGISTRY`-இல் உள்ளது மற்றும் ஓய்வுபெற்ற id snapshot-இல் இல்லை; `REGISTRY`-இல் இன்னும் உள்ள ஒவ்வொரு ஓய்வுபெற்ற id-யும் forward செய்யப்பட்டுள்ளது அல்லது `allowedRetiredInCatalog`-இல் பட்டியலிடப்பட்டுள்ளது; மேலும் எந்த `DEFAULT_DEGRADATION_MAP` source அல்லது target-உம் அந்த snapshot-இல் ஓய்வுபெற்றதாகத் தோன்றாது. ஒரு model தற்போது செயலிலுள்ள upstream மூலம் வழங்கப்படுகிறது என்பதை இது நிரூபிக்காது. Offline — `config/quality/model-lifecycle.json`-உடன் ஒப்பிடுகிறது; இது `npm run quality:refresh-model-lifecycle` மூலம் கைமுறையாகப் புதுப்பிக்கப்படுகிறது (network; CI-உடன் இணைக்கப்படவில்லை). `allowedRetiredInCatalog` என்பது படிப்படியாகக் குறைக்கும் ratchet: tracking issue இருந்தால் மட்டுமே ஒரு பதிவைச் சேர்க்கவும். | ஆம் | -| `check:fetch-targets` | client-side `src/`-இல் உள்ள ஒவ்வொரு `fetch("/api/...")`-உம் உண்மையான `route.ts`-க்குத் தீர்மானிக்கப்படுகிறது | ஆம் | -| `check:deps` | repo-இல் உள்ள ஒவ்வொரு `package.json` முழுவதிலும் `npm install` செய்யக்கூடிய அனைத்து deps-உம் `dependency-allowlist.json`-இல் உள்ளன; புதிதாக pin செய்யப்படாத அல்லது slopsquat செய்யப்பட்ட packages குறியிடப்படுகின்றன | ஆம் | -| `audit:deps` | `npm audit` (root + electron) — high/critical advisories எதுவுமில்லை (osv `check:vuln-ratchet`-உடன் ஒன்றிணைகிறது; Rationalization Backlog-ஐப் பார்க்கவும்) | ஆம் | -| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashes, host overrides எதுவுமில்லை | ஆம் | -| `check:licenses` | உற்பத்தி சார்புகளுக்கான SPDX உரிம அனுமதிப்பட்டியல் | ஆம் | -| `check:tracked-artifacts` | உருவாக்கக் கலைப்பொருட்கள் / commit செய்யப்பட்ட `node_modules` குறியீட்டு இணைப்புகள் எதுவும் இல்லை (husky pre-commit-இலும் இயங்கும்; pre-push வேண்டுமென்றே இலகுவாக வைக்கப்பட்டுள்ளது — #6716) | ஆம் | -| `check:ai-attribution` | PR commit-கள், தலைப்பு அல்லது உள்ளடக்கத்தில் AI/bot `Co-Authored-By` trailer அல்லது AI-உருவாக்க footer எதுவும் இல்லை — கடுமையான விதி #16 (`quality.yml`-இன் PR→`release/**` fast-gates சுழற்சியில் — event payload-ஐப் படிக்கும், PR அல்லாதபோது எந்தச் செயல்பாடும் செய்யாது — மேலும் PR→`main`-க்கான `ci.yml` lint-இல் PR-க்கு மட்டும் உரிய படிநிலை; husky `commit-msg` hook-லும்; மனித இணை ஆசிரியர்கள் அனுமதிக்கப்படுகிறார்கள்; #14436) | -| `check:vitest-exclusions` | ஒவ்வொரு Vitest விலக்கலும் ஒரு கண்காணிப்புச் சிக்கலைக் குறிப்பிடுவதுடன் `config/quality/vitest-exclusions.json`-இல் இடம்பெறுகிறது (#13204) | ஆம் | -| `check:file-size` | எந்த மூலக் கோப்பும் கோப்பு நீட்சிவாரியான உச்சவரம்பை மீறவில்லை (ratchet: `frozen` பட்டியலில் மாற்றமின்றி வைக்கப்பட்ட பெரிய கோப்புகள்) | ஆம் | -| `check:error-helper` | executor-கள்/handler-களில் உள்ள பிழைப் பதில்கள் `buildErrorBody()` / `sanitizeErrorMessage()`-ஐப் பயன்படுத்துகின்றன (கடுமையான விதி #12) | ஆம் | -| `check:migration-numbering` | Migration SQL கோப்புகள் இடைவெளிகளோ நகல்களோ இல்லாமல் தொடர்ச்சியாக எண்ணிடப்பட்டுள்ளன | ஆம் | -| `check:public-creds` | `publicCreds.ts`-க்கு வெளியே நேரடி OAuth `client_id`/`client_secret` அல்லது Firebase Web விசைகள் இல்லை (கடுமையான விதி #11) | ஆம் | -| `check:db-rules` | `src/lib/db/` தொகுதிகளுக்கு வெளியே நேரடி SQL இல்லை; `localDb.ts`-இலிருந்து barrel-import-கள் இல்லை (கடுமையான விதிகள் #2/#5) | ஆம் | -| `check:known-symbols` | அவற்றின் dispatch அட்டவணைகளில் பதிவுசெய்யப்பட்ட provider executor-கள், routing strategy-கள் மற்றும் translator-கள் வட்டில் உள்ள கோப்புகளுடன் பொருந்துகின்றன — தொடர்பற்ற அல்லது அறிவிக்கப்படாத symbol-கள் இல்லை | ஆம் | -| `check:route-guard-membership` | child process-ஐ உருவாக்கும் ஒவ்வொரு route-உம் `isLocalOnlyPath()` மூலம் வகைப்படுத்தப்பட்டுள்ளது (கடுமையான விதிகள் #15/#17) | ஆம் | -| `check:test-discovery` | repo-வில் உள்ள ஒவ்வொரு `*.test.ts` / `*.spec.ts` கோப்பும் குறைந்தது ஒரு test runner-ஆல் சேகரிக்கப்படுகிறது (ratchet: `test-discovery-baseline.json`-இல் உள்ள orphan பட்டியல் சுருங்க மட்டுமே முடியும்) | ஆம் | -| `check:agent-skills-sync` | உருவாக்கப்பட்ட agent-skills கலைப்பொருட்கள் அவற்றின் மூலப் பட்டியலுடன் பொருந்துகின்றன (வேறுபாடு இல்லை) | -| `check:provider-asset-provenance` | வழங்குநர் லோகோக்கள்/கலைப்பொருட்களுக்கு பதிவுசெய்யப்பட்ட மூலாதாரப் பதிவு உள்ளது | -| `lint:json` | JSON கட்டமைப்புக் கோப்புகள் சரியாகப் பகுக்கப்பட்டு repo lint விதிகளைப் பூர்த்தி செய்கின்றன | -| `typecheck:core` | பிழைகள் இல்லாத TypeScript தொகுத்தல் (ஆலோசனை எச்சரிக்கைகள் மட்டும்) | ஆம் | -| `typecheck:noimplicit:core` | கடுமையான `noImplicitAny` — எதிர்கால நோக்குடையது; முன்பிருந்த பல அழைப்பிடங்களுக்கு இன்னும் வகைக் குறிப்புகள் தேவை | **ஆலோசனை** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**`-க்கு வரம்பிடப்பட்ட `tsc` (#7033) — `typecheck:core`-இன் கவனமாகத் தேர்ந்தெடுக்கப்பட்ட 27-கோப்பு அனுமதிப்பட்டியலில் எந்த dashboard TSX-உம் இடம்பெறவில்லை; `next build` அதற்கான வகைச் சரிபார்ப்பையும் ஒருபோதும் செய்வதில்லை (`next.config.mjs` ஆனது `ignoreBuildErrors: true` என அமைக்கிறது). எனவே, அங்குள்ள தொடர்பற்ற அடையாளங்காட்டி பின்னடைவுகள் (#6625/#6909) CI-க்குப் புலப்படாமல் இருந்தன. நிலைப்படுத்தப்பட்ட ஒவ்வொரு கோப்பு/ஒவ்வொரு TS-குறியீட்டு எண்ணிக்கை அடிப்படைக்கோட்டுடன் (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` போன்ற அதே காலாவதி-அமலாக்க முறை) வேறுபாடுகள் ஒப்பிடப்படுகின்றன — அடிப்படைக்கோட்டு எண்ணிக்கைக்கு அப்பாற்பட்ட புதிய பிழைகள் மட்டுமே நுழைவாயிலைத் தோல்வியடையச் செய்யும்; முன்பிருந்த பிழை ஒன்று சரிசெய்யப்படும்போது `--update` மூலம் அடிப்படைக்கோட்டை படிப்படியாகக் குறைக்கவும். | ஆம் | +| ஸ்கிரிப்ட் (`npm run ...`) | சரிபார்ப்பது | தடுப்பது | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | +| `check:node-runtime` | Node.js பதிப்பு ஆதரிக்கப்படும் வரம்பிற்குள் உள்ளது | ஆம் | +| `check:cycles` | `src/` + `open-sse/` முழுவதிலுமான சுழற்சி imports (AST அடிப்படையிலானது, tsconfig `paths` resolve செய்யப்படுகின்றன). Bare முறை = ஆலோசனை மட்டும்; சுழற்சிகளைப் பட்டியலிடும். `check:cycles:ratchet` (CI இயக்குவது) எண்ணிக்கை `quality-baseline.json`-இல் உள்ள `metrics.cycles` உச்சவரம்பை மீறும்போது தடுக்கும் — தற்போது 14, `direction: down`, எனவே அது குறைய மட்டுமே முடியும் (#15159 G-01/G-02) | ஆம் (ratchet) | +| `check:route-validation:t06` | அனைத்து routes-இலும் Zod schemas இருப்பது (Tier 6 கொள்கை) | ஆம் | +| `check:any-budget:t11` | `@ts-expect-error // any` எண்ணிக்கை budget-ஐ மீறவில்லை (Tier 11 catraca) | ஆம் | +| `check:provider-consistency` | `providers.ts`-இல் உள்ள ஒவ்வொரு வழங்குநருக்கும் `providerRegistry.ts`-இல் பொருந்தும் பதிவு உள்ளது (அதேபோல் மறுதிசையிலும், அனுமதிப்பட்டியலுக்குள்) | ஆம் | +| `check:model-lifecycle` | கைமுறையாகப் பராமரிக்கப்படும் மூன்று வழிப்படுத்தல் அட்டவணைகளும் களஞ்சியத்தில் சேர்க்கப்பட்ட வாழ்க்கைச்சுழற்சி நிலைப்படத்துடன் (#11503) ஒத்திசைவாக இருக்கும்: `REGISTRY` வழிப்படுத்தக்கூடிய ஓய்வுபெற்ற எந்த id-க்கும் `FITNESS_TABLE` (`taskFitness.ts`) மதிப்பெண் வழங்காது; ஒவ்வொரு `BUILT_IN_ALIASES` இலக்கும் `REGISTRY`-இல் இருக்கும் மற்றும் ஓய்வுபெற்ற-id நிலைப்படத்தில் இருக்காது; `REGISTRY`-இல் இன்னும் உள்ள ஒவ்வொரு ஓய்வுபெற்ற id-யும் முன்னனுப்பப்படும் அல்லது `allowedRetiredInCatalog`-இல் பட்டியலிடப்படும்; மேலும் எந்த `DEFAULT_DEGRADATION_MAP` மூலமோ இலக்கோ அந்த நிலைப்படத்தில் ஓய்வுபெற்றதாக இடம்பெறாது. ஒரு மாதிரி தற்போது நேரடி மேல்நிலைச் சேவையால் வழங்கப்படுகிறது என்பதை இது நிரூபிக்காது. இணையமில்லா முறை — `config/quality/model-lifecycle.json` உடன் ஒப்பிடுகிறது; இது `npm run quality:refresh-model-lifecycle` மூலம் கைமுறையாகப் புதுப்பிக்கப்படுகிறது (பிணையம் தேவை; CI-இல் இணைக்கப்படவில்லை). `allowedRetiredInCatalog` என்பது படிப்படியாகக் குறைக்கும் தடுப்புக் கருவி: கண்காணிப்புச் சிக்கல் இருந்தால் மட்டுமே ஒரு பதிவைச் சேர்க்கவும். | ஆம் | +| `check:fetch-targets` | கிளையன்ட் தரப்பு `src/`-இல் உள்ள ஒவ்வொரு `fetch("/api/...")`-உம் உண்மையான `route.ts`-க்குத் தீர்மானிக்கப்படுகிறது | ஆம் | +| `check:deps` | களஞ்சியத்திலுள்ள ஒவ்வொரு `package.json`-இலும் காணப்படும், `npm install` மூலம் நிறுவக்கூடிய அனைத்து சார்புகளும் `dependency-allowlist.json`-இல் உள்ளன; புதிதாகப் பதிப்பு நிலைநிறுத்தப்படாத அல்லது slopsquatting செய்யப்பட்ட தொகுப்புகள் குறியிடப்படுகின்றன | ஆம் | +| `audit:deps` | `npm audit` (மூலம் + electron) — அதிக/தீவிர ஆபத்துள்ள அறிவுறுத்தல்கள் எதுவும் இல்லை (osv `check:vuln-ratchet` உடன் மேற்படுகிறது; Rationalization Backlog-ஐப் பார்க்கவும்) | ஆம் | +| `check:lockfile` | `package-lock.json` முழுமைத்தன்மை — https பதிவகம், முழுமைத்தன்மை ஹாஷ்கள், ஹோஸ்ட் மேலெழுதல்கள் இல்லை | ஆம் | +| `check:licenses` | உற்பத்தி சார்புகளுக்கான SPDX உரிம அனுமதிப்பட்டியல் | ஆம் | +| `check:tracked-artifacts` | build artifacts / commit செய்யப்பட்ட `node_modules` symlinks எதுவும் இல்லை (husky pre-commit-இலும் இயங்கும்; pre-push திட்டமிட்டே இலகுவாக உள்ளது — #6716) | ஆம் | +| `check:ai-attribution` | PR commit-கள், தலைப்பு அல்லது body-இல் AI/bot `Co-Authored-By` trailer அல்லது AI-generation footer எதுவும் இல்லை — கடுமையான விதி #16 (`quality.yml`-இன் PR→`release/**`-க்கான fast-gates loop-இல் — event payload-ஐப் படிக்கும், PR அல்லாதவற்றில் எந்தச் செயல்பாடும் இல்லை — மேலும் `ci.yml` lint-இல் PR→`main`-க்கான PR-மட்டும் படி; husky `commit-msg` hook-உம் இதில் அடங்கும்; மனித இணை ஆசிரியர்கள் அனுமதிக்கப்படுகின்றனர்; #14436) | +| `check:vitest-exclusions` | ஒவ்வொரு Vitest exclusion-உம் ஒரு tracking issue-ஐக் குறிப்பிடுவதுடன் `config/quality/vitest-exclusions.json`-இல் இடம்பெற வேண்டும் (#13204) | ஆம் | +| `check:file-size` | எந்த source file-உம் அதன் extension-க்கான வரம்பை மீறக்கூடாது (ratchet: பெரிய உறையவைக்கப்பட்ட கோப்புகள் `frozen` பட்டியலில் உள்ளன) | ஆம் | +| `check:error-helper` | executors/handlers-இல் உள்ள பிழை responses, `buildErrorBody()` / `sanitizeErrorMessage()` ஆகியவற்றைப் பயன்படுத்துகின்றன (கடுமையான விதி #12) | ஆம் | +| `check:migration-numbering` | Migration SQL கோப்புகள் இடைவெளிகளோ நகல்களோ இல்லாமல் தொடர்ச்சியாக எண்ணிடப்பட்டுள்ளன | ஆம் | +| `check:public-creds` | `publicCreds.ts`-க்கு வெளியே நேரடி OAuth `client_id`/`client_secret` அல்லது Firebase Web விசைகள் இல்லை (கடுமையான விதி #11) | ஆம் | +| `check:db-rules` | `src/lib/db/` தொகுதிகளுக்கு வெளியே நேரடி SQL இல்லை; `localDb.ts`-இலிருந்து barrel-import-கள் இல்லை (கடுமையான விதிகள் #2/#5) | ஆம் | +| `check:known-symbols` | அவற்றின் dispatch அட்டவணைகளில் பதிவுசெய்யப்பட்ட provider executor-கள், routing strategy-கள் மற்றும் translator-கள் வட்டில் உள்ள கோப்புகளுடன் பொருந்துகின்றன — தொடர்பற்ற அல்லது அறிவிக்கப்படாத குறியீடுகள் இல்லை | ஆம் | +| `check:route-guard-membership` | child process-ஐ உருவாக்கும் ஒவ்வொரு route-உம் `isLocalOnlyPath()` மூலம் வகைப்படுத்தப்பட்டுள்ளது (கடுமையான விதிகள் #15/#17) | ஆம் | +| `check:test-discovery` | repo-வில் உள்ள ஒவ்வொரு `*.test.ts` / `*.spec.ts` கோப்பும் குறைந்தது ஒரு test runner மூலம் சேகரிக்கப்படுகிறது (ratchet: `test-discovery-baseline.json`-இல் உள்ள தொடர்பற்ற கோப்புகளின் பட்டியல் குறைய மட்டுமே முடியும்) | ஆம் | +| `check:agent-skills-sync` | உருவாக்கப்பட்ட agent-skills கலைப்பொருட்கள் அவற்றின் மூலப் பட்டியலுடன் பொருந்துகின்றன (விலகல் இல்லை) | +| `check:provider-asset-provenance` | வழங்குநர் லோகோக்கள்/கலைப்பொருட்களுக்கு பதிவுசெய்யப்பட்ட மூலம் குறித்த பதிவு உள்ளது | +| `lint:json` | JSON கட்டமைப்புக் கோப்புகள் பாகுபடுத்தப்பட்டு repo lint விதிகளைப் பூர்த்தி செய்கின்றன | +| `typecheck:core` | பிழைகள் இல்லாத TypeScript தொகுத்தல் (ஆலோசனை எச்சரிக்கைகள் மட்டும்) | ஆம் | +| `typecheck:noimplicit:core` | கண்டிப்பான `noImplicitAny` — எதிர்கால நோக்குடையது; முன்பே உள்ள பல அழைப்பிடங்களுக்கு இன்னும் சிறுகுறிப்புகள் தேவை | **ஆலோசனை** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**`-க்கு வரம்பிடப்பட்ட `tsc` (#7033) — `typecheck:core`-இன் தேர்ந்தெடுக்கப்பட்ட 27-கோப்பு அனுமதிப்பட்டியலில் dashboard TSX எதுவும் சேர்க்கப்படவில்லை; மேலும் `next build` அதனை ஒருபோதும் வகைச் சரிபார்ப்பதில்லை (`next.config.mjs`-இல் `ignoreBuildErrors: true` அமைக்கப்பட்டுள்ளது). எனவே, அங்குள்ள தனித்துவிடப்பட்ட அடையாளங்காட்டி பின்னடைவுகள் (#6625/#6909) CI-க்குப் புலப்படவில்லை. உறையவைக்கப்பட்ட ஒவ்வொரு-கோப்பு/ஒவ்வொரு-TS-குறியீட்டு எண்ணிக்கை அடிப்படையுடன் (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` போன்ற அதே பழையநிலை-அமலாக்க முறை) வேறுபாடுகள் ஒப்பிடப்படுகின்றன — அடிப்படை எண்ணிக்கையைத் தாண்டிய புதிய பிழைகள் மட்டுமே வாயிலைக் தோல்வியடையச் செய்யும்; முன்பே உள்ள பிழை ஒன்று சரிசெய்யப்படும்போது `--update` மூலம் அடிப்படையைக் குறைக்கவும். | ஆம் | ### பணி: `quality-gate` -`test-coverage`-க்குப் பிறகு இயங்கும். தோல்வியடைந்தால் இணைப்பைத் தடுக்கும். +`test-coverage`-க்குப் பிறகு இயங்கும். தோல்வியடைந்தால் ஒன்றிணைப்பைத் தடுக்கும். -| ஸ்கிரிப்ட் | சரிபார்ப்பது | தடுக்கும் தன்மை | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `quality:collect` | `quality-metrics.json`-ஐ உருவாக்குகிறது (ESLint எச்சரிக்கை எண்ணிக்கை, ஒன்றிணைக்கப்பட்ட ஷார்டு அறிக்கையிலிருந்து கவரேஜ்) | ஆம் (ratchet-க்கு முந்தையது) | -| `quality:ratchet` | `quality-baseline.json`-இல் உள்ள ஒவ்வொரு அளவீடும் பின்னடைவு அடையவில்லை (ESLint எச்சரிக்கைகள் ≤ அடிப்படை; கவரேஜ் ≥ அடிப்படை) | ஆம் | -| `check:duplication` | குறியீட்டு நகலெடுப்பு (jscpd@4), `quality-baseline.json`-இல் உள்ள அடிப்படையை மீறவில்லை | ஆம் | -| `check:complexity` | கோப்பு-நிலை சைக்ளோமேட்டிக் சிக்கல்தன்மை உச்சவரம்பை மீறவில்லை (மைய ESLint `complexity` + `max-lines-per-function`) | ஆம் | -| `check:cognitive-complexity` | அறிவாற்றல் சிக்கல்தன்மை ratchet (`eslint-plugin-sonarjs`) — தனி ESLint இயக்கம்; CI இரண்டையும் ஒன்றிணைத்து ஒற்றை `check:complexity-ratchets` படியாக இயக்குகிறது | ஆம் | -| `check:dead-code` | பயன்படுத்தப்படாத exports / கோப்புகளுக்கான ratchet (knip), அடிப்படையுடன் ஒப்பிடும்போது பின்னடைவு அடையவில்லை | ஆம் | -| `check:compression-budget` | சுருக்க ஒப்பீட்டுச் சோதனை வரம்பு — ஒவ்வொரு engine-க்குமான token சேமிப்பின் குறைந்தபட்ச அளவுகள் பின்னடைவு அடையக்கூடாது | ஆம் | -| `check:type-coverage` | தட்டச்சிடப்பட்ட சதவீதத்திற்கான ratchet (`type-coverage`) பின்னடைவு அடையவில்லை; பெருமளவில் `typecheck:noimplicit:core`-ஐ உள்ளடக்குகிறது | ஆம் | -| `check:codeql-ratchet` | திறந்த CodeQL விழிப்பூட்டல்களின் எண்ணிக்கை பின்னடைவு அடையவில்லை (`gh api` வழியாகப் படிக்கிறது; token இல்லையெனில் சிக்கலின்றித் தவிர்க்கிறது) — புதுப்பிப்பு இடைவெளி மற்றும் கைமுறைத் தூண்டல்: கீழே உள்ள "CodeQL ratchet"-ஐப் பார்க்கவும் | ஆம் | +| ஸ்கிரிப்ட் | சரிபார்ப்பது | தடுக்கும் தன்மை | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `quality:collect` | `quality-metrics.json`-ஐ உருவாக்குகிறது (ESLint எச்சரிக்கை எண்ணிக்கை, ஒன்றிணைக்கப்பட்ட shard அறிக்கையிலிருந்து coverage) | ஆம் (ratchet-க்கு முந்தைய நிலை) | +| `quality:ratchet` | `quality-baseline.json`-இல் உள்ள ஒவ்வொரு metric-உம் பின்னடைவு அடையவில்லை (ESLint எச்சரிக்கைகள் ≤ baseline; coverage ≥ baseline) | ஆம் | +| `check:duplication` | குறியீட்டு நகலெடுப்பு (jscpd@4), `quality-baseline.json`-இல் உள்ள baseline-ஐ மீறவில்லை | ஆம் | +| `check:complexity` | கோப்பு-நிலை cyclomatic complexity வரம்பை மீறவில்லை (மைய ESLint `complexity` + `max-lines-per-function`) | ஆம் | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — தனியான ESLint இயக்கம்; CI இரண்டையும் ஒற்றை `check:complexity-ratchets` படிநிலையாக ஒன்றிணைத்து இயக்குகிறது | ஆம் | +| `check:dead-code` | பயன்படுத்தப்படாத exports / கோப்புகளுக்கான ratchet (knip), baseline-உடன் ஒப்பிடும்போது பின்னடைவு அடையவில்லை | ஆம் | +| `check:compression-budget` | சுருக்க benchmark வரம்பு — engine-வாரியான token சேமிப்பின் குறைந்தபட்ச அளவுகள் பின்னடைவு அடையக்கூடாது | ஆம் | +| `check:type-coverage` | வகையிடப்பட்ட சதவீத ratchet (`type-coverage`) பின்னடைவு அடையவில்லை; பெருமளவில் `typecheck:noimplicit:core`-ஐ உள்ளடக்குகிறது | ஆம் | +| `check:codeql-ratchet` | திறந்த CodeQL alert எண்ணிக்கை பின்னடைவு அடையவில்லை (`gh api` வழியாகப் படிக்கிறது; token இல்லாதபோது சீராகத் தவிர்க்கிறது) — புதுப்பிப்பு இடைவெளி மற்றும் கைமுறைத் தொடக்கம்: கீழே உள்ள "CodeQL ratchet"-ஐப் பார்க்கவும் | ஆம் | ### பணி: `quality-extended` -முழுப் பணியும் ஆலோசனைத் தன்மை கொண்டது (`continue-on-error: true`). npm-அடிப்படையிலான ratchet-கள் -உண்மையாக இயங்குகின்றன; வெளிப்புற ஸ்கேனர்கள் `gh release download` வழியாக நிறுவப்பட்டு, binary இன்னும் -இல்லாதபோது தாமாகவே தவிர்க்கப்படுகின்றன (exit 0). +முழுப் பணியும் ஆலோசனை சார்ந்தது (`continue-on-error: true`). npm-அடிப்படையிலான ratchet-கள் +உண்மையாக இயக்கப்படுகின்றன; வெளிப்புற scanner-கள் `gh release download` வழியாக நிறுவப்பட்டு, binary +இன்னும் இல்லாதபோது தாமாகவே தவிர்க்கின்றன (exit 0). -| ஸ்கிரிப்ட் | சரிபார்ப்பது | தடுக்கும் தன்மை | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -| `check:circular-deps` | சுழற்சி சார்புகள் இல்லை (dpdm) | **ஆலோசனை** | -| `check:bundle-size` | Bundle அளவு உச்சவரம்பை மீறவில்லை | **ஆலோசனை** | -| `check:secrets` | இரகசியத் தகவல் ஸ்கேனிங் (gitleaks) — binary இல்லையெனில் தவிர்க்கிறது | **ஆலோசனை** | -| `check:vuln-ratchet` | சார்புநிலை பாதிப்புகள் (osv-scanner) பின்னடைவு அடையவில்லை — binary இல்லையெனில் தவிர்க்கிறது | **ஆலோசனை** | -| `check:workflows` | Workflow lint (actionlint + zizmor); இல்லாத/செயலிழந்த ஸ்கேனர்கள், செல்லாத அறிக்கைகள் அல்லது இல்லாத ratchet அடிப்படை ஆகியவை INCOMPLETE எனத் தோல்வியடையும். செல்லுபடியான கண்டறிதல்கள் தேர்ந்தெடுக்கப்பட்ட கடுமையான/ஆலோசனை/ratchet கொள்கையைப் பின்பற்றும் | இயக்கம் அவசியம்; CI-இல் zizmor ratchet தடுக்கும் | -| `check:openapi-breaking` | அடிப்படைக் கிளையுடன் ஒப்பிடும்போது பொது API ஒப்பந்தத்தில் (`openapi.yaml`) உள்ள இணக்கத்தை முறிக்கும் மாற்றங்கள் (oasdiff) — `openapiBreaking=N`-ஐ உருவாக்குகிறது; oasdiff இல்லையெனில் அல்லது அடிப்படை விவரக்குறிப்பைத் தீர்மானிக்க முடியாவிட்டால் தவிர்க்கிறது | **ஆலோசனை** | +| ஸ்கிரிப்ட் | சரிபார்ப்பது | தடுக்கும் தன்மை | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| `check:circular-deps` | சுற்றுச்சார்புகள் எதுவும் இல்லை (dpdm) | **ஆலோசனை சார்ந்தது** | +| `check:bundle-size` | Bundle அளவு வரம்பை மீறவில்லை | **ஆலோசனை சார்ந்தது** | +| `check:secrets` | ரகசியத் தகவல் scan செய்தல் (gitleaks) — binary இல்லையெனில் தவிர்க்கிறது | **ஆலோசனை சார்ந்தது** | +| `check:vuln-ratchet` | சார்பு பாதிப்புகள் (osv-scanner) பின்னடைவு அடையவில்லை — binary இல்லையெனில் தவிர்க்கிறது | **ஆலோசனை சார்ந்தது** | +| `check:workflows` | Workflow lint (actionlint + zizmor); scanner-கள் இல்லாமை/செயலிழப்பு, செல்லாத அறிக்கைகள் அல்லது ratchet baseline இல்லாமை ஆகியவை INCOMPLETE எனத் தோல்வியடையும். செல்லுபடியாகும் கண்டறிதல்கள் தேர்ந்தெடுக்கப்பட்ட strict/advisory/ratchet கொள்கையைப் பின்பற்றும் | இயக்கம் அவசியம்; CI-இல் zizmor ratchet தடுக்கும் | +| `check:openapi-breaking` | அடிப்படை branch-உடன் ஒப்பிடும்போது பொது API ஒப்பந்தத்தில் (`openapi.yaml`) ஏற்படும் முறிவு மாற்றங்கள் (oasdiff) — `openapiBreaking=N`-ஐ உருவாக்குகிறது; oasdiff இல்லையெனில் அல்லது அடிப்படை spec-ஐத் தீர்மானிக்க முடியாவிட்டால் தவிர்க்கிறது | **ஆலோசனை சார்ந்தது** | ### பணி: `docs-sync-strict` -`main`-க்கான ஒவ்வொரு PR-இலும் இயங்குகிறது. தோல்வியுற்றால் ஒன்றிணைப்பைத் தடுக்கிறது. - -| ஸ்கிரிப்ட் | சரிபார்ப்பவை | தடுக்கும் நிலை | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `check:docs-all` | கீழேயுள்ள 6 துணை-வாயில்களையும் தொடர்ச்சியாக இயக்கும் மெட்டா-வாயில் | ஆம் | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt பதிப்பு ஒத்திசைவு | ஆம் | -| ↳ `check:docs-counts` | உரையில் உள்ள எண்ணிக்கைகள் (வழங்குநர் எண்ணிக்கை, இடமாற்ற எண்ணிக்கை போன்றவை) உண்மையான எண்ணிக்கைகளின் ராட்செட் வரம்பிற்குள் உள்ளன | ஆம் | -| ↳ `check:env-doc-sync` | `.env.example`-இல் உள்ள ஒவ்வொரு env var-உம் ஆவண அட்டவணையில் ஆவணப்படுத்தப்பட்டுள்ளது; அதேபோல் மறுதிசையிலும் சரிபார்க்கப்படுகிறது | ஆம் | -| ↳ `check:deprecated-versions` | ஆவணங்களில் வழக்கொழிந்த பதிப்புச் சரங்கள் இல்லை | ஆம் | -| ↳ `check:doc-links` | ஆவணங்களில் உள்ள உள் markdown இணைப்புகள் உண்மையான கோப்புகளுக்குச் செல்கின்றன (`[text]`/`(path)` வடிவம்) | ஆம் | -| ↳ `check:fabricated-docs` | ஆவணங்களில் குறிப்பிடப்பட்டுள்ள வழித்தடங்கள், env vars, CLI கட்டளைகள், hook பெயர்கள் மற்றும் கோப்புப் பாதைகள் codebase-இல் உள்ளன. `--strict` வழியாகக் கடின வாயில்; flag இல்லாமல் மென்மையான தோல்வி. | ஆம் (CI-இல் `--strict` வழியாக) | -| `check:cli-i18n` | CLI கட்டளைச் சரங்கள் அனைத்து i18n locale கோப்புகளிலும் உள்ளன | ஆம் | -| `check:openapi-coverage` | OpenAPI விவரக்குறிப்பு உண்மையான வழித்தடங்களின் ராட்செட் செய்யப்பட்ட குறைந்தபட்ச அளவையாவது உள்ளடக்குகிறது | ஆம் | -| `check:openapi-security-tiers` | `openapi.yaml`-இல் உள்ள பாதுகாப்பு அடுக்கு குறிப்புரைகள் `routeGuard.ts` வகைப்பாடுகளுடன் ஒத்திசைவாக உள்ளன | **ஆலோசனை** | -| `check:openapi-routes` | `openapi.yaml`-இல் உள்ள ஒவ்வொரு பாதையும் உண்மையான `route.ts`-க்குச் செல்கிறது (கற்பனையான தகவலைத் தடுப்பது) | ஆம் | -| `check:docs-symbols` | `docs/**/*.md`-இல் உள்ள ஒவ்வொரு `/api/...` குறிப்பும் உண்மையான `route.ts`-க்குச் செல்கிறது (கற்பனையான தகவலைத் தடுப்பது) | ஆம் | -| `i18n translation drift` | i18n locale கோப்புகளில் மொழிபெயர்க்கப்படாத விசைகள் — எச்சரிக்கை மட்டும் | **ஆலோசனை** | +`main`-க்கான ஒவ்வொரு PR-இலும் இயக்கப்படுகிறது. தோல்வி ஏற்பட்டால் merge செய்வதைத் தடுக்கிறது. + +| ஸ்கிரிப்ட் | சரிபார்ப்பது | தடுக்கும் தன்மை | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `check:docs-all` | கீழே உள்ள 6 துணை-வாயில்களை வரிசையாக இயக்கும் மெட்டா-வாயில் | ஆம் | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt பதிப்பு ஒத்திசைவு | ஆம் | +| ↳ `check:docs-counts` | உரைநடையில் உள்ள எண்ணிக்கைகள் (வழங்குநர் எண்ணிக்கை, இடம்பெயர்வு எண்ணிக்கை போன்றவை) உண்மையான எண்ணிக்கைகளின் ராட்செட் சாளரத்திற்குள் உள்ளனவா | ஆம் | +| ↳ `check:env-doc-sync` | `.env.example`-இல் உள்ள ஒவ்வொரு சூழல் மாறியும் ஆவண அட்டவணையில் ஆவணப்படுத்தப்பட்டுள்ளது, மேலும் அதற்கு நேர்மாறாகவும் உள்ளதா | ஆம் | +| ↳ `check:deprecated-versions` | ஆவணங்களில் வழக்கொழிந்த பதிப்புச் சரங்கள் எதுவும் இல்லையா | ஆம் | +| ↳ `check:doc-links` | ஆவணங்களில் உள்ள உள் markdown இணைப்புகள் உண்மையான கோப்புகளைக் குறிக்கின்றனவா (`[text]`/`(path)` வடிவம்) | ஆம் | +| ↳ `check:fabricated-docs` | ஆவணங்களில் குறிப்பிடப்பட்ட வழித்தடங்கள், சூழல் மாறிகள், CLI கட்டளைகள், hook பெயர்கள் மற்றும் கோப்புப் பாதைகள் codebase-இல் உள்ளனவா. `--strict` வழியாகக் கடுமையான வாயில்; கொடி இல்லாமல் மென்-தோல்வி. | ஆம் (CI-இல் `--strict` வழியாக) | +| `check:cli-i18n` | CLI கட்டளைச் சரங்கள் அனைத்து i18n locale கோப்புகளிலும் உள்ளனவா | ஆம் | +| `check:openapi-coverage` | OpenAPI விவரக்குறிப்பு உண்மையான வழித்தடங்களுக்கான ராட்செட் செய்யப்பட்ட குறைந்தபட்ச வரம்பையாவது உள்ளடக்குகிறதா | ஆம் | +| `check:openapi-security-tiers` | `openapi.yaml`-இல் உள்ள பாதுகாப்பு அடுக்கு சிறுகுறிப்புகள் `routeGuard.ts` வகைப்பாடுகளுடன் ஒத்திசைவாக உள்ளனவா | **ஆலோசனை** | +| `check:openapi-routes` | `openapi.yaml`-இல் உள்ள ஒவ்வொரு பாதையும் உண்மையான `route.ts`-ஐக் குறிக்கிறதா (கற்பிதத் தடுப்பு) | ஆம் | +| `check:docs-symbols` | `docs/**/*.md`-இல் உள்ள ஒவ்வொரு `/api/...` குறிப்பும் உண்மையான `route.ts`-ஐக் குறிக்கிறதா (கற்பிதத் தடுப்பு) | ஆம் | +| `i18n translation drift` | i18n locale கோப்புகளில் மொழிபெயர்க்கப்படாத விசைகள் — எச்சரிக்கை மட்டும் | **ஆலோசனை** | ### பணி: `i18n-ui-coverage` -| ஸ்கிரிப்ட் | சரிபார்ப்பவை | தடுக்கும் நிலை | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (inline) | UI i18n விசைகளின் கவரேஜ் ≥ 65% | ஆம் | -| `check-ui-value-drift` (inline) | மீண்டும் எழுதப்பட்ட ஆங்கில **மதிப்பு**, பழைய மொழிபெயர்ப்பு எதையும் எஞ்சவிடாது | ஆம் | -| `check-new-key-coverage` (inline) | ஒரு **புதிய** ஆங்கில விசை ஒவ்வொரு locale-இலும் மொழிபெயர்க்கப்பட்டுள்ளது — `__MISSING__:` குறியீடு நிராகரிக்கப்படுகிறது | ஆம் | -| `check-translation-ratio` | ஒரு locale-க்கான உண்மையான மொழிபெயர்ப்பு விகிதம் (அனுமதிப் பட்டியலுக்கு வெளியே ஆங்கிலத்துடன் ஒரே மாதிரியாக உள்ள / placeholder / விடுபட்ட இலைகள்) `config/quality/i18n-translation-baseline.json` + slack-ஐ மீறக்கூடாது | **ஆலோசனை** | +| ஸ்கிரிப்ட் | சரிபார்ப்பது | தடுக்கும் தன்மை | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (inline) | UI i18n விசை உள்ளடக்கம் ≥ 65% ஆக உள்ளதா | ஆம் | +| `check-ui-value-drift` (inline) | மீண்டும் எழுதப்பட்ட ஓர் ஆங்கில **மதிப்பு**, காலாவதியான மொழிபெயர்ப்பு எதையும் விட்டுச் செல்லவில்லையா | ஆம் | +| `check-new-key-coverage` (inline) | ஒரு **புதிய** ஆங்கில விசை ஒவ்வொரு locale-இலும் மொழிபெயர்க்கப்பட்டுள்ளதா — `__MISSING__:` குறியீடு நிராகரிக்கப்படும் | ஆம் | +| `check-translation-ratio` | locale-வாரியான உண்மை-மொழிபெயர்ப்பு விகிதம் (அனுமதிப்புப் பட்டியலுக்கு வெளியே ஆங்கிலத்துடன் ஒரே மாதிரியான / placeholder / விடுபட்ட இலைகள்) `config/quality/i18n-translation-baseline.json` + தளர்வை மீறக்கூடாது | **ஆலோசனை** | -`fetch-depth: 0` தேவை — மதிப்பு-மாறுபாட்டு வாயில், merge base-க்கு எதிராக `en.json`-ஐ diff செய்கிறது. +`fetch-depth: 0` தேவை — மதிப்பு-விலகல் வாயில், ஒன்றிணைப்பு அடிப்படைக்கு எதிராக `en.json`-ஐ ஒப்பிடுகிறது. -#### `check-ui-value-drift` — பழைய மொழிபெயர்ப்பு வாயில் +#### `check-ui-value-drift` — காலாவதியான-மொழிபெயர்ப்பு வாயில் -மற்ற வாயில்கள் கட்டமைப்பு ரீதியாகக் கண்டறிய முடியாத ஒரே i18n பின்னடைவை இது கண்டறிகிறது: ஓர் ஆங்கில மதிப்பு -மீண்டும் எழுதப்பட்ட பிறகும், _முந்தைய_ ஆங்கிலத்திலிருந்து உருவாக்கப்பட்ட மொழிபெயர்ப்புகள் அப்படியே எஞ்சிவிடுவதால், -ஆங்கிலமல்லாத பயனர்கள் நம்பிக்கையூட்டும் வகையில் எழுதப்பட்டிருந்தாலும் இப்போது தவறான உரையைத் தொடர்ந்து படிக்கிறார்கள். +மற்ற வாயில்களால் கட்டமைப்பு ரீதியாகக் கண்டறிய முடியாத ஒரு i18n பின்னடைவை இது கண்டறிகிறது: ஓர் ஆங்கில மதிப்பு +மீண்டும் எழுதப்பட்ட பின்னரும், _முந்தைய_ ஆங்கிலத்திலிருந்து பெறப்பட்ட மொழிபெயர்ப்புகள் அப்படியே இருப்பதால், +ஆங்கிலமல்லாத மொழிகளைப் பயன்படுத்துவோர் நம்பிக்கையுடன் எழுதப்பட்ட, ஆனால் இப்போது தவறான உரையையே தொடர்ந்து படிக்கிறார்கள். -இது உண்மையிலேயே வெளியீட்டுக்குச் சென்றது. Antigravity உள்நுழைவு உதவி சேர்க்கப்பட்டபோது (#5203) +இது உண்மையிலேயே வெளியீட்டில் இடம்பெற்றது. Antigravity உள்நுழைவு உதவி சேர்க்கப்பட்டபோது (#5203), `oauthModal.googleOAuthWarning` மீண்டும் எழுதப்பட்டது; **43 locale-களில் 39**-இல், இயக்குநர்களிடம் "முழு -URL-ஐ நகலெடுத்து கீழே ஒட்டவும்" என்று கூறும் உரை அப்படியே இருந்தது — அந்த வழங்குநருக்காக நிறைவுசெய்ய -முடியாத ஒரு செயல்முறை. பின்வரும் காரணங்களால் #8463 வரை இது கவனிக்கப்படவில்லை: +URL-ஐ நகலெடுத்து கீழே ஒட்டவும்" என்று கூறும் உரை அப்படியே இருந்தது — அந்த வழங்குநருக்காக நிறைவு செய்ய முடியாத ஒரு செயல்முறை. பின்வரும் காரணங்களால் +#8463 வரை இது கவனிக்கப்படவில்லை: -- `sync-ui-keys`, **இல்லாத** விசைகளை மட்டுமே பின்னிரப்புகிறது; **பழைய** விசைகளை ஒருபோதும் புதுப்பிப்பதில்லை; -- `check-ui-keys-coverage` விசை _இருப்பதை_ மட்டுமே கணக்கிடுகிறது; எனவே பழைய மொழிபெயர்ப்பும் உள்ளடக்கப்பட்டதாக மதிப்பெண் பெறுகிறது; -- `check-translation-drift`, `docs/i18n//**.md` ஆவணப் பிரதிகளைப் பின்தொடர்கிறது — - அது `src/i18n/messages/*.json`-ஐ ஒருபோதும் படிப்பதில்லை. 2026-09 மறு-ஒத்திசைவிலிருந்து `docs-sync-strict` பணியில் தடுக்கும் நிலை அமலில் உள்ளது: ஒரு முக்கிய ஆவணத்தைத் திருத்தவும் → `npm run i18n:run -- --files=` (பிரிவு-நிலை, குறைந்த செலவு). +- `sync-ui-keys`, **இல்லாத** விசைகளை மட்டுமே நிரப்புகிறது; **காலாவதியான** விசைகளை ஒருபோதும் அல்ல; +- `check-ui-keys-coverage`, விசை _இருப்பை_ எண்ணுகிறது; எனவே காலாவதியான மொழிபெயர்ப்பும் உள்ளடக்கப்பட்டதாக மதிப்பெண் பெறுகிறது; +- `check-translation-drift`, `docs/i18n//**.md` ஆவணப் பிரதிகளைத் தடமறிகிறது — + அது `src/i18n/messages/*.json`-ஐ ஒருபோதும் படிப்பதில்லை. 2026-09 மறு-ஒத்திசைவிலிருந்து `docs-sync-strict` பணியில் தடுப்பதாக உள்ளது: முதன்மை ஆவணத்தைத் திருத்தவும் → `npm run i18n:run -- --files=` (பிரிவு-நிலை, செலவு குறைவு). -**வேறுபாடு சார்ந்தது; அடிப்படைப் பதிவால் ஆதரிக்கப்படாதது.** இது merge base-இல் உள்ள `en.json`-ஐ working tree உடன் ஒப்பிடுகிறது; ஆங்கில மதிப்பு மாறிய ஒவ்வொரு key-க்கும், இன்னும் மாற்றப்படாத மொழிபெயர்ப்பைக் கொண்டிருக்கும் locale காலாவதியானதாகும். இது வேண்டுமென்றே **முன்பே இருக்கும் கடனை உறையவைக்கிறது** — நீண்டகாலமாக இருக்கும் ஒரு மொழிபெயர்ப்பு எந்தப் பழைய ஆங்கிலத்திலிருந்து வந்தது என்பதை ஒரு diff-ஆல் வெளிப்படுத்த முடியாது; எனவே தற்போதைய மாற்றம் தொடும் பகுதிகளை மட்டுமே gate மதிப்பிடுகிறது. மாற்று அணுகுமுறைக்கு (ஒவ்வொரு key-க்கும் ஒரு hash baseline) சுமார் 600 KB அளவிலான உருவாக்கப்பட்ட கோப்பு தேவைப்படும்; இது தற்போதுள்ள மிகப்பெரிய baseline-ஐ விட 3× பெரியது, மேலும் ஒவ்வொரு i18n PR-இலும் மாறிக்கொண்டிருக்கும். +**Diff-aware, baseline-backed அல்ல.** இது merge base-இல் உள்ள `en.json`-ஐ working tree உடன் ஒப்பிடுகிறது; ஆங்கில மதிப்பு மாறியுள்ள ஒவ்வொரு key-க்கும், இன்னும் மாற்றப்படாத மொழிபெயர்ப்பைக் கொண்டிருக்கும் locale காலாவதியானதாகும். இது வேண்டுமென்றே **முன்பே இருந்த கடனை உறையவைக்கிறது** — நீண்டகாலமாக இருக்கும் ஒரு மொழிபெயர்ப்பு எந்தப் பழைய ஆங்கில உரையிலிருந்து வந்தது என்பதை diff-ஆல் கண்டறிய முடியாது; எனவே தற்போதைய மாற்றம் தொடும் பகுதிகளை மட்டுமே gate மதிப்பிடுகிறது. மாற்று வழியான (ஒவ்வொரு key-க்கும் hash baseline) சுமார் 600 KB அளவுள்ள உருவாக்கப்பட்ட கோப்பைத் தேவைப்படுத்தும்; அது தற்போதுள்ள மிகப்பெரிய baseline-ஐ விட 3× பெரியது மற்றும் ஒவ்வொரு i18n PR-லும் தொடர்ந்து மாறும். -இதைப் பூர்த்தி செய்ய இரண்டு வழிகள் உள்ளன: +இதைப் பூர்த்திசெய்ய இரண்டு வழிகள் உள்ளன: 1. பாதிக்கப்பட்ட மொழிபெயர்ப்புகளைப் புதுப்பிக்கவும், அல்லது -2. அவற்றை `__MISSING__:` என அமைக்கவும் — பின்னர் runtime திருத்தப்பட்ட ஆங்கிலத்தை வழங்கும் +2. அவற்றை `__MISSING__:` என அமைக்கவும் — பின்னர் runtime திருத்தப்பட்ட ஆங்கில உரையை வழங்கும் (`src/i18n/request.ts::deepMergeFallback`, #7258), மேலும் அந்த key மொழிபெயர்ப்புக்கான வரிசையில் சேர்க்கப்படும். -சரத்தின் **பொருள்** மாறியிருந்தால், **key-ஐ மறுபெயரிடுவதை** விரும்பவும்: ஒரு புதிய key காலாவதியான மொழிபெயர்ப்பை மரபுரிமையாகப் பெற முடியாது. #8463 பயன்படுத்திய முறை இதுதான். +சரத்தின் **பொருள்** மாறியிருந்தால், **key-ஐ மறுபெயரிடுவதை** விரும்பவும்: ஒரு புதிய key காலாவதியான மொழிபெயர்ப்பை மரபாகப் பெற முடியாது. #8463 பயன்படுத்திய முறை இதுதான். ```bash npm run i18n:check-value-drift # கடுமையானது (CI இயக்குவது) @@ -224,47 +224,47 @@ npm run i18n:check-value-drift:warn # அறிக்கை மட்டு BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -அடிப்படை catalog-ஐப் படிக்க முடியாதபோது (base ref இல்லாத shallow clone), `check-openapi-breaking`-ஐப் பிரதிபலிக்கும் வகையில், `SKIP reason=base-unresolved` உடன் 0-ஆக வெளியேறும். +அடிப்படை catalog-ஐப் படிக்க முடியாதபோது (base ref இல்லாத shallow clone), `check-openapi-breaking`-ஐப் பிரதிபலிக்கும் விதமாக `SKIP reason=base-unresolved` உடன் 0 என்ற exit code-ஐ வழங்குகிறது. ### பணி: `i18n` -முழுமையான i18n சரிபார்ப்பு matrix (ஒவ்வொரு locale-க்கும் ஒரு பணி). முழுப் பணியும் ஆலோசனைத் தன்மையுடையது. +முழுமையான i18n சரிபார்ப்பு matrix (ஒவ்வொரு locale-க்கும் ஒரு பணி). முழுப் பணியும் ஆலோசனை சார்ந்தது. -| Script | சரிபார்ப்பது | தடுக்கும் தன்மை | -| ------------------------------- | ------------------------------------------- | --------------------------------------------------------------- | -| `validate_translation.py quick` | ஒவ்வொரு locale-க்குமான மொழிபெயர்ப்பு முழுமை | **ஆலோசனை மட்டும்** (முழுப் பணியிலும் `continue-on-error: true`) | +| Script | சரிபார்ப்பது | தடுக்கும் தன்மை | +| ------------------------------- | ------------------------------------------- | ----------------------------------------------------------------- | +| `validate_translation.py quick` | ஒவ்வொரு locale-க்குமான மொழிபெயர்ப்பு முழுமை | **ஆலோசனை சார்ந்தது** (முழுப் பணியிலும் `continue-on-error: true`) | ### பணி: `pr-test-policy` Pull request-களில் மட்டும் இயங்கும். -| Script | சரிபார்ப்பது | தடுக்கும் தன்மை | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, அல்லது `bin/`-இல் உள்ள production code-ஐ மாற்றும் PR-கள் சோதனைகளைச் சேர்க்கவோ புதுப்பிக்கவோ வேண்டும் (கடுமையான விதி #8) | ஆம் | -| `check:test-masking` | மாற்றப்பட்ட சோதனைக் கோப்புகள் மொத்த assert எண்ணிக்கையைக் குறைக்கக்கூடாது அல்லது `assert.ok(true)` போன்ற tautology-களைச் சேர்க்கக்கூடாது | ஆம் | -| `check:pr-evidence` | PR body மாற்றத்திற்கான சோதனை/VPS ஆதாரத்தைக் குறிப்பிடுகிறது (PR உரையில் grep செய்வதன் மூலம் கடுமையான விதி #18-ஐ இயந்திரமயமாக்குகிறது — எளிதில் உடையக்கூடியது; Backlog-ஐப் பார்க்கவும்) | ஆம் | +| Script | சரிபார்ப்பது | தடுக்கும் தன்மை | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, அல்லது `bin/`-இல் உள்ள production code-ஐ மாற்றும் PR-கள் சோதனைகளைச் சேர்க்க அல்லது புதுப்பிக்க வேண்டும் (கடுமையான விதி #8) | ஆம் | +| `check:test-masking` | மாற்றப்பட்ட சோதனைக் கோப்புகள் மொத்த assert எண்ணிக்கையைக் குறைக்கவில்லை அல்லது `assert.ok(true)` tautology-களைச் சேர்க்கவில்லை | ஆம் | +| `check:pr-evidence` | மாற்றத்திற்கான சோதனை/VPS ஆதாரத்தை PR body மேற்கோள் காட்டுகிறது (PR உரையை grep செய்வதன் மூலம் கடுமையான விதி #18-ஐ இயந்திரமயமாக்குகிறது — எளிதில் உடையக்கூடியது, Backlog-ஐப் பார்க்கவும்) | ஆம் | ### பணி: `test-vitest` `build`-க்குப் பிறகு இயங்கும். தோல்வியடைந்தால் merge-ஐத் தடுக்கும். -| தொகுப்பு | சரிபார்ப்பது | தடுக்கும் தன்மை | -| ---------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 கருவிகள்), autoCombo, cache — vitest runner | ஆம் | -| `test:vitest:ui` | UI component சோதனைகள் — vitest runner | **தடுக்கும்** — முன்பே இருக்கும் தோல்விகள் `vitest.config.ts`-இல் வெளிப்படையாக விலக்கப்பட்டுள்ளன; புதிய தோல்விகள் பணியைத் தோல்வியடையச் செய்யும் | +| தொகுப்பு | சரிபார்ப்பது | தடுக்கும் தன்மை | +| ---------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 கருவிகள்), autoCombo, cache — vitest runner | ஆம் | +| `test:vitest:ui` | UI component சோதனைகள் — vitest runner | **தடுக்கும்** — முன்பே இருந்த தோல்விகள் `vitest.config.ts`-இல் வெளிப்படையாக விலக்கப்பட்டுள்ளன; புதிய தோல்விகள் பணியைத் தோல்வியடையச் செய்யும் | -### இரவுநேர workflow-கள் (திட்டமிடப்பட்டவை, ஆலோசனை மட்டும்) +### இரவுநேர workflow-கள் (திட்டமிடப்பட்டவை, ஆலோசனை சார்ந்தவை) -இவை cron அட்டவணைப்படி (மற்றும் `workflow_dispatch` மூலம்) இயங்குகின்றன; PR-களில் ஒருபோதும் இயங்காது. அனைத்தும் ஆலோசனைத் தன்மையுடையவை. +இவை cron அட்டவணையில் (மற்றும் `workflow_dispatch` வழியாக) இயங்கும்; PR-களில் ஒருபோதும் இயங்காது. அனைத்தும் ஆலோசனை சார்ந்தவை. -| Workflow | சரிபார்ப்பது | தடுக்கும் தன்மை | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `nightly-property` | சீரற்ற seed மற்றும் அதிக run எண்ணிக்கையுடன் fast-check property சோதனைகள் | **ஆலோசனை மட்டும்** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **ஆலோசனை மட்டும்** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret இல்லாமல் தவிர்க்கப்படும்) | **ஆலோசனை மட்டும்** | -| `nightly-schemathesis` | `docs/openapi.yaml`-ஐப் பயன்படுத்தி இயங்கும் OmniRoute-க்கு எதிரான OpenAPI contract fuzzing (schemathesis) — specification மீறல்கள் / கையாளப்படாத 500 பிழைகளை வெளிக்கொணர்கிறது (கட்டம் 8 B.4) | **ஆலோசனை மட்டும்** | -| `nightly-mutation` | வேகமான unit lane-இல் Stryker mutation-testing மதிப்பெண் — தப்பிப்பிழைக்கும் mutants பலவீனமான assert-களை வெளிக்கொணர்கின்றன | **ஆலோசனை மட்டும்** | -| `nightly-compat` | ஆதரிக்கப்படும் `engines.node` வரம்புகள் முழுவதும் Node engine compatibility matrix | **ஆலோசனை மட்டும்** | +| Workflow | சரிபார்ப்பது | தடுக்கும் தன்மை | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `nightly-property` | சீரற்ற seed மற்றும் அதிக run எண்ணிக்கையுடன் fast-check property சோதனைகள் | **ஆலோசனை சார்ந்தது** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **ஆலோசனை சார்ந்தது** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret இல்லாமல் தவிர்க்கப்படும்) | **ஆலோசனை சார்ந்தது** | +| `nightly-schemathesis` | `docs/openapi.yaml`-ஐப் பயன்படுத்தி இயங்கும் OmniRoute-க்கு எதிரான OpenAPI contract fuzzing (schemathesis) — spec மீறல்கள் / கையாளப்படாத 500 பிழைகளை வெளிக்கொணரும் (கட்டம் 8 B.4) | **ஆலோசனை சார்ந்தது** | +| `nightly-mutation` | வேகமான unit lane மீதான Stryker mutation-testing மதிப்பெண் — தப்பிப் பிழைக்கும் mutants பலவீனமான assert-களை வெளிக்கொணரும் | **ஆலோசனை சார்ந்தது** | +| `nightly-compat` | ஆதரிக்கப்படும் `engines.node` வரம்புகள் முழுவதுமான Node engine இணக்கத்தன்மை matrix | **ஆலோசனை சார்ந்தது** | --- @@ -527,43 +527,43 @@ CI வாயில்களுக்கு அப்பால், OmniRoute ஒ --- -## சீராக்கல் நிலுவைப் பட்டியல் (ROI மதிப்பாய்வு — கட்டம் 9 அலை 3) +## நியாயப்படுத்தல் நிலுவைப் பட்டியல் (ROI மதிப்பாய்வு — கட்டம் 9 அலை 3) -இந்தப் பட்டியல் 2026-06-17 அன்று `ci.yml` உடன் ஒப்பிட்டு சரிசெய்யப்பட்டது (முந்தைய பதிப்பில் +இந்தப் பட்டியல் 2026-06-17 அன்று `ci.yml` உடன் ஒத்திசைத்துச் சரிபார்க்கப்பட்டது (முந்தைய பதிப்பில் `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` ஆகியவை விடுபட்டிருந்தன). சரிசெய்யப்பட்ட தொகுப்பின் ROI மதிப்பாய்வு -பின்வரும் சீராக்கல் வேட்பாளர்களை அடையாளம் கண்டது. **இணைப்புகள் இயந்திரரீதியான CI -மாற்றங்கள்; நிலைமாற்றங்கள்/நீக்கங்கள் இயக்குநருக்காக ஒதுக்கப்பட்ட கொள்கை முடிவுகள்.** கீழே உள்ள எதுவும் -இன்னும் செயல்படுத்தப்படவில்லை. - -**மேலே ஆவணப்படுத்தப்படாதவை** (ஆலோசனை மட்டும், குறைந்த சமிக்ஞை): `docs-lint` பணி -(markdownlint + Vale, முழுப் பணிக்கும் `continue-on-error`) மற்றும் தனித்தியங்கும் ஸ்கேனர் பணிப்பாய்வுகளான -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `quality-baseline.json`-இல் -`semgrepFindings: 0` உள்ளது, ஆனால் `ci.yml`-இல் தடுக்கும் ratchet ஒன்றுடன் இணைக்கப்படவில்லை — இந்த அளவீடு +`check:codeql-ratchet`, `check:pr-evidence` ஆகியவை விடுபட்டிருந்தன). ஒத்திசைக்கப்பட்ட தொகுப்பின் ROI மதிப்பாய்வு +பின்வரும் நியாயப்படுத்தல் வாய்ப்புகளைக் கண்டறிந்தது. **ஒன்றிணைப்புகள் இயந்திரரீதியான CI +மாற்றங்கள்; மாற்றங்கள்/நீக்கங்கள் இயக்குநருக்காக ஒதுக்கப்பட்ட கொள்கை முடிவுகள்.** கீழே உள்ள எதுவும் +இன்னும் பயன்படுத்தப்படவில்லை. + +**மேலே ஆவணப்படுத்தப்படாதவையும் உள்ளன** (ஆலோசனை மட்டுமே, குறைந்த சமிக்ஞை): `docs-lint` பணி +(markdownlint + Vale, முழுப் பணிக்கும் `continue-on-error`) மற்றும் தனித்தனி ஸ்கேனர் பணிப்பாய்வுகளான +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` என்பது +`quality-baseline.json`-இல் உள்ளது, ஆனால் `ci.yml`-இல் தடுக்கும் ratchet ஒன்றுடன் இணைக்கப்படவில்லை — இந்த அளவீடு தற்போது தொடர்பற்ற நிலையில் உள்ளது. -### இணைத்தல் / நகல் நீக்கம் (இயந்திரரீதியானது, குறைந்த அபாயம்) +### ஒன்றிணைத்தல் / நகல்நீக்கம் (இயந்திரரீதியானது, குறைந்த ஆபத்து) -ஒவ்வொரு வேட்பாளரும் 2026-06-17 அன்று நேரடி gate நிலைக்கு எதிராகச் சரிபார்க்கப்பட்டது (நம்பினாலும் சரிபார்); -பல "வெளிப்படையான" இணைப்புகள் கடனை மறைத்திருப்பது தெரியவந்ததால் அவை **தடையின்றி மாற்றக்கூடியவை அல்ல**. +ஒவ்வொரு வாய்ப்பும் 2026-06-17 அன்று நடைமுறையிலிருந்த gate நிலைக்கு எதிராகச் சரிபார்க்கப்பட்டது (நம்பு-ஆனால்-சரிபார்); +பல "வெளிப்படையான" ஒன்றிணைப்புகள் மறைந்திருந்த கடனை மூடிமறைத்தது தெரியவந்ததால், அவை **சுத்தமான** நேரடி மாற்றீடுகள் அல்ல. -- **`check:docs-sync` இருமுறை இயங்குகிறது** — `lint` பணியில் தனியாகவும், `check:docs-all` (`docs-sync-strict`) மற்றும் husky pre-commit hook ஆகியவற்றுக்குள்ளும் மீண்டும் இயங்குகிறது. ✅ **முடிந்தது** — தனித்த `lint` இயக்கம் நீக்கப்பட்டது. -- **CVE ஸ்கேனிங்** — ❌ **தடையற்ற இணைப்பு அல்ல.** எந்தவொரு high/critical CVE இருந்தாலும் `audit:deps` கடுமையாகத் தோல்வியடைகிறது; `check:vuln-ratchet` (osv), baseline உடன் ஒப்பிடும்போது ஒரு _பின்னடைவு_ ஏற்பட்டால் மட்டுமே தோல்வியடைகிறது (தற்போது 1 MODERATE). பொருளியல் வேறுபட்டது — `audit:deps`-ஐ நீக்குவது முழுமையான high/critical gate-ஐ இழக்கச் செய்யும். இரண்டையும் வைத்திருக்கவும். -- **சுழற்சி கண்டறிதல்** — ❌ **தடையற்ற இணைப்பு அல்ல.** `check:circular-deps` (dpdm) **91 சுழற்சிகளை** அறிக்கையிடுகிறது (அதனால்தான் அது ஆலோசனை நிலையிலுள்ளது); அவற்றை முதலில் தீர்க்காமல் அதைத் தடுக்கும் நிலைக்கு உயர்த்த முடியாது, மேலும் green நிலையில் உள்ள, தேர்ந்தெடுக்கப்பட்ட `check:cycles`-ஐ விட இதன் நோக்கெல்லை பரந்தது. `check:cycles`-ஐ தடுக்கும் நிலையில் வைத்திருக்கவும்; 91 dpdm சுழற்சிகளைத் தீர்ப்பது தனி நிலுவைப் பணியாகும். -- **சிக்கல்தன்மை** — ✅ **முடிந்தது** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ஒரே ESLint நடை, ruleId வாரியாக எண்ணுவதால் cyclomatic+max-lines மற்றும் cognitive baseline-கள் தனித்தனியாகவே இருக்கின்றன; தனிப்பட்ட `check:complexity` / `check:cognitive-complexity` உள்ளூர் `--update` பயன்பாட்டிற்காகத் தொடர்ந்து உள்ளன. -- **`/api` தவறான உருவாக்கத் தடுப்பு** — ✅ **முடிந்தது** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-க்கான ஒரே FS பட்டியல்; openapi-routes + docs-symbols இன்னும் தனித்தனியாக அறிக்கையிடுகின்றன; தனிப்பட்டவை உள்ளூர் இயக்கங்களுக்காகத் தொடர்ந்து உள்ளன. -- **`check:node-runtime` 11 பணிகளில் இயங்குகிறது** — ⚠️ **குறைந்த ROI.** ஒவ்வொன்றும் தனித்தனி runner ஆகும், மேலும் இந்தச் சோதனை <1s மட்டுமே எடுக்கும்; மலிவான ஒவ்வொரு பணிக்குமான பாதுகாப்பை இழப்பதற்கு எதிராக, மொத்தச் சேமிப்பு ~10s மட்டுமே. இந்த மாற்றக் குழப்பத்திற்குத் தகுதியானதல்ல. -- **CI lint-இல் `typecheck:noimplicit:core`** — ✅ **lint பணியிலிருந்து நீக்கப்பட்டது** (ஆலோசனை `continue-on-error` ஆக இருந்தது); தடுக்கும் type surface என்பது `typecheck:core` + `check:type-coverage`. உள்ளூர் script தக்கவைக்கப்பட்டது. +- **`check:docs-sync` இருமுறை இயங்குகிறது** — `lint` பணியில் தனியாகவும், `check:docs-all` (`docs-sync-strict`) மற்றும் husky pre-commit hook ஆகியவற்றிற்குள்ளும் இயங்குகிறது. ✅ **முடிந்தது** — தனித்தனி `lint` அழைப்பு அகற்றப்பட்டது. +- **CVE ஸ்கேனிங்** — ❌ **சுத்தமான ஒன்றிணைப்பு அல்ல.** உயர்/மிக முக்கியமான எந்த CVE இருந்தாலும் `audit:deps` உடனடியாகத் தோல்வியடைகிறது; `check:vuln-ratchet` (osv) baseline உடன் ஒப்பிடும்போது ஒரு _பின்னடைவு_ ஏற்பட்டால் மட்டுமே தோல்வியடைகிறது (தற்போது 1 MODERATE). வெவ்வேறு பொருளியல் — `audit:deps`-ஐ நீக்கினால் முழுமையான உயர்/மிக முக்கியமான gate இழக்கப்படும். இரண்டையும் வைத்திருக்கவும். +- **சுழற்சி கண்டறிதல்** — ✅ **முடிந்தது** (#15159 G-01/G-02). இங்கிருந்த பழைய உரை `check:cycles`-ஐ "பச்சை நிலையிலுள்ள, தேர்ந்தெடுக்கப்பட்ட" gate எனக் குறிப்பிட்டு, `check:circular-deps` (dpdm) 91 சுழற்சிகளைப் புகாரளித்ததால் அதைத் தடுக்கும் நிலையில் வைத்திருப்பதை நியாயப்படுத்தியது. அந்தப் பச்சை நிலை ஒரு **தவறான பச்சை நிலை**: `check:cycles` 5 துணைக் கோப்பகங்களை (450 கோப்புகள்) ஸ்கேன் செய்தது, நிலையான `import|export … from` உடன் மட்டுமே பொருத்தியது, மேலும் ஒவ்வொரு `@/` மற்றும் `@omniroute/open-sse/` specifier-ஐயும் விலக்கியது; எனவே repo-வில் ஆதிக்கம் செலுத்திய dynamic-import + alias சுழற்சிகளை அதனால் காண முடியவில்லை. சரிசெய்யப்பட்டது: இப்போது gate `src` + `open-sse` முழுவதையும் (5023 கோப்புகள்) பார்வையிட்டு, TypeScript AST-இலிருந்து specifier-களைச் சேகரிக்கிறது (எனவே `import("…")` கணக்கில் கொள்ளப்படும், ஆனால் type-position `typeof import("…")` கணக்கில் கொள்ளப்படாது), மேலும் tsconfig `paths`-ஐத் தீர்க்கிறது. இது 0 அல்ல, **14** சுழற்சிகளைக் கண்டறிகிறது. முன்பே இருந்த 14 சுழற்சிகளை ஒரு gate PR-இல் சரிசெய்ய முடியாது என்பதால், `check:cycles` இப்போது ஒரு **ratchet** (`--ratchet`, `quality-baseline.json`-இல் உச்சவரம்பு `metrics.cycles.value = 14`, `direction: down`) — எந்தப் _பின்னடைவையும்_ இது தடுக்கிறது, மேலும் எண்ணிக்கை குறைய மட்டுமே முடியும். CI `npm run check:cycles:ratchet`-ஐ இயக்குகிறது. படிப்படியான குறைப்பு **A-01** உடன் தொடர்கிறது. பரந்த இரண்டாவது மதிப்பீடாக `check:circular-deps` (dpdm) ஆலோசனை நிலையில் தொடர்கிறது. +- **சிக்கல்தன்மை** — ✅ **முடிந்தது** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ஒரே ESLint பார்வையிடல், ruleId அடிப்படையிலான எண்ணிக்கைகள்; எனவே cyclomatic+max-lines மற்றும் cognitive baseline-கள் தனித்தனியாகவே இருக்கும்; உள்ளூர் `--update` பயன்பாட்டிற்காகத் தனிப்பட்ட `check:complexity` / `check:cognitive-complexity` தொடர்ந்து வைக்கப்பட்டுள்ளன. +- **`/api` மாயத்தோற்றத் தடுப்பு** — ✅ **முடிந்தது** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`-இன் ஒரே FS பட்டியல், openapi-routes + docs-symbols இன்னும் தனித்தனியாகப் புகாரளிக்கின்றன; உள்ளூர் இயக்கங்களுக்காகத் தனிப்பட்டவை தொடர்ந்து வைக்கப்பட்டுள்ளன. +- **`check:node-runtime` 11 பணிகளில் இயங்குகிறது** — ⚠️ **குறைந்த ROI.** ஒவ்வொன்றும் தனித்தனி runner மற்றும் சரிபார்ப்பு <1s; மலிவான ஒவ்வொரு-பணிக்குமான பாதுகாப்பை இழப்பதற்கு எதிராக, மொத்தச் சேமிப்பு ~10s மட்டுமே. மாற்றக் குழப்பத்திற்கு இது தகுதியானதல்ல. +- **CI lint-இல் `typecheck:noimplicit:core`** — ✅ **lint பணியிலிருந்து அகற்றப்பட்டது** (ஆலோசனை நிலை `continue-on-error` ஆக இருந்தது); தடுக்கும் type பரப்பு `typecheck:core` + `check:type-coverage` ஆகும். உள்ளூர் script தக்கவைக்கப்பட்டது. -### நிலைமாற்றம் / முடிவு (இயக்குநர் கொள்கை) +### மாற்றுதல் / முடிவெடுத்தல் (இயக்குநர் கொள்கை) -- `check:openapi-security-tiers` (ஆலோசனை) — ❌ **தடையின்றி நிலைமாற்ற முடியாது.** இது 0-உடன் வெளியேறுகிறது, ஆனால் `LOCAL_ONLY_API_PREFIXES`-இன் கீழ் உள்ள பல `traffic-inspector` route-களில் `x-loopback-only: true` annotation இல்லை என எச்சரிக்கிறது. இதை அமல்படுத்துவதற்கு முதலில் அந்த annotation-களை `openapi.yaml`-இல் சேர்க்க வேண்டும். -- `typecheck:noimplicit:core` (ஆலோசனை) — தடுக்கும் `check:type-coverage` ratchet பெரும்பாலும் இதை உள்ளடக்குகிறது. இதை ratchet ஆக மாற்றவும் அல்லது தேவையற்ற இரண்டாவது `tsc` pass-ஐ நீக்கவும். -- `test:vitest:ui` (இப்போது **தடுக்கும் நிலையில்**) — ஏற்கெனவே இருந்த தோல்விகள் `vitest.config.ts`-இல் `// #8618` கண்காணிப்புக் குறிப்புகளுடன் வெளிப்படையாக விலக்கப்பட்டுள்ளன; புதிய தோல்விகள் பணியைத் தோல்வியடையச் செய்யும். -- `check:secrets` (gitleaks, ஆவணப்படுத்தப்பட்ட 3 false-positive-களில் உறையவைக்கப்பட்ட தடுக்கும் ratchet) — 0-ஐ அடைய அந்த 3-ஐ allowlist செய்யவும் அல்லது ஆலோசனை நிலைக்குத் தாழ்த்தவும். GitHub-இன் சொந்த secret-scanning + `check:public-creds` உடன் ஒன்றிணைகிறது. -- `check:pr-evidence` (தடுக்கும் நிலையில், PR-body உரையை grep செய்கிறது) — அதிக false-positive அபாயம்; நீக்கப்பட்டால் Hard Rule #18 அமலாக்கம் பலவீனமடையும், எனவே இது உண்மையான கொள்கை முடிவாகும். -- `semgrep` (தனித்தியங்கும் ஆலோசனை) — OWASP குடும்பங்களுக்கு CodeQL உடன் ஒன்றிணைகிறது; அதன் baseline-ஐ ஒரு ratchet உடன் இணைக்கவும் அல்லது நீக்கவும். +- `check:openapi-security-tiers` (ஆலோசனை நிலை) — ❌ **சுத்தமாகத் தடுக்கும் நிலைக்கு மாற்ற முடியாது.** இது 0 என்ற நிலையில் வெளியேறினாலும், `LOCAL_ONLY_API_PREFIXES`-இன் கீழுள்ள பல `traffic-inspector` route-களில் `x-loopback-only: true` annotation இல்லை என எச்சரிக்கிறது. இதை அமல்படுத்த முதலில் அந்த annotation-களை `openapi.yaml`-இல் சேர்க்க வேண்டும். +- `typecheck:noimplicit:core` (ஆலோசனை நிலை) — தடுக்கும் `check:type-coverage` ratchet இதன் பெரும்பகுதியை ஏற்கெனவே உள்ளடக்குகிறது. இதை ratchet ஆக மாற்றவும் அல்லது தேவையற்ற இரண்டாவது `tsc` pass-ஐ நீக்கவும். +- `test:vitest:ui` (இப்போது **தடுக்கும் நிலையில்**) — முன்பே இருந்த தோல்விகள் `vitest.config.ts`-இல் `// #8618` கண்காணிப்புக் குறிப்புகளுடன் வெளிப்படையாக விலக்கப்பட்டுள்ளன; புதிய தோல்விகள் பணியைத் தோல்வியடையச் செய்யும். +- `check:secrets` (gitleaks, ஆவணப்படுத்தப்பட்ட 3 false-positive-களில் நிலைநிறுத்தப்பட்ட தடுக்கும் ratchet) — 0-ஐ எட்ட அந்த 3-ஐ allowlist செய்யவும், அல்லது ஆலோசனை நிலைக்குக் குறைக்கவும். GitHub-இன் சொந்த secret-scanning + `check:public-creds` உடன் இது ஒன்றோடொன்று மேலெழுகிறது. +- `check:pr-evidence` (தடுக்கும் நிலையில், PR-body உரையை grep செய்கிறது) — false-positive ஆபத்து அதிகம்; இதை நீக்கினால் Hard Rule #18 அமலாக்கம் பலவீனமடையும், எனவே இது உண்மையான கொள்கை முடிவு. +- `semgrep` (தனித்தனி ஆலோசனை நிலை) — OWASP குடும்பங்களில் CodeQL உடன் ஒன்றோடொன்று மேலெழுகிறது; அதன் baseline-ஐ ratchet ஒன்றுடன் இணைக்கவும் அல்லது நீக்கவும். --- diff --git a/docs/i18n/ta/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ta/docs/compression/COMPRESSION_GUIDE.md index fa4461b7574c..4f923386a37a 100644 --- a/docs/i18n/ta/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ta/docs/compression/COMPRESSION_GUIDE.md @@ -451,8 +451,7 @@ Caveman வெளியீட்டு முறை **விருப்பத `messages` கொண்ட உடலில், உள்ளடக்கத் தவிர்ப்பு (`open-sse/services/compression/outputMode.ts`-இல் உள்ள `shouldBypassCavemanOutputMode()`) கடைசி மூன்று செய்திகளைச் சரிபார்த்து, அவை அதன் பாதுகாப்பு, மீளமுடியாத செயல், தெளிவுபடுத்தல் அல்லது வரிசை-உணர்திறன் முக்கியச் சொற்களுடன் பொருந்தும்போது -முழுச் சுற்றுக்கும் பாணிகளைத் தவிர்க்கிறது. டாஷ்போர்டின் **Auto-Clarity Bypass** மாற்றி -(`cavemanOutputMode.autoClarity`) எந்த நிலையில் அமைக்கப்பட்டிருக்கிறதோ அதன்படி தவிர்ப்பு இயங்குகிறது. +முழுச் சுற்றுக்கும் பாணிகளைத் தவிர்க்கிறது. டாஷ்போர்டின் **Auto-Clarity Bypass** மாற்றி (`cavemanOutputMode.autoClarity`) இயக்கப்பட்டிருக்கும் வரை தவிர்ப்பு இயங்குகிறது, இதுவே இயல்புநிலை; மாற்றி அணைக்கப்பட்டிருக்கும்போது தேர்ந்தெடுத்த பாணிகள் அந்தச் சுற்றுகளிலும் பொருந்தும். தவிர்ப்பு சுற்றை அனுமதிக்கும்போது, புதிய `messages[0]`-ஐ ஒருபோதும் உருவாக்காத அதே கோப்பிலுள்ள `placeSystemInstruction()`, கண்டறியும் கீழ்க்கண்ட முதல் இடத்தில் தொகுதியை வைக்கிறது: diff --git a/docs/i18n/ta/docs/reference/ENVIRONMENT.md b/docs/i18n/ta/docs/reference/ENVIRONMENT.md index 3aca5f8efb12..8c4b325948b3 100644 --- a/docs/i18n/ta/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ta/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. தேவையான இரகசியங்கள் -முதல் இயக்கத்திற்கு முன் இவை **கட்டாயமாக** அமைக்கப்பட வேண்டும். இவை இல்லையெனில், பயன்பாடு தொடங்க மறுக்கும் அல்லது பாதுகாப்பற்ற இயல்புநிலை அமைப்புகளுடன் இயங்கும். - -| மாறி | தேவை | இயல்புநிலை | மூலக் கோப்பு | விளக்கம் | -| ---------------------------- | ---------------------------- | -------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **ஆம்** | _(எதுவுமில்லை)_ | `src/lib/auth` | அனைத்து டாஷ்போர்டு அமர்வு குக்கிகளிலும் (JWT) கையொப்பமிடுகிறது/அவற்றைச் சரிபார்க்கிறது. `openssl rand -base64 48` மூலம் உருவாக்கவும். | -| `API_KEY_SECRET` | **ஆம்** | _(எதுவுமில்லை)_ | `src/lib/db/apiKeys.ts` | SQLite-இல் சேமிக்கப்பட்டுள்ள API விசை மதிப்புகளுக்கான AES குறியாக்க விசை. `openssl rand -hex 32` மூலம் உருவாக்கவும். | -| `INITIAL_PASSWORD` | **ஆம்** | `CHANGEME` | பூட்ஸ்ட்ராப் ஸ்கிரிப்ட் | ஆரம்ப நிர்வாக டாஷ்போர்டு கடவுச்சொல்லை அமைக்கிறது (`.env.example` இயல்புநிலையுடன் பொருந்துகிறது — மாற்றத்தை கட்டாயப்படுத்துவதற்காக வெளிப்படையாகப் பாதுகாப்பற்றதாக வைக்கப்பட்டுள்ளது). **முதல் பயன்பாட்டிற்கு முன் மாற்றவும்.** உள்நுழைந்த பிறகு, Dashboard → Settings → Security வழியாக மாற்றவும். | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **ஆம்** (உற்பத்திச் சூழலில்) | _(அமைக்கப்படவில்லை)_ | `src/app/api/internal/codex-responses-ws/route.ts` | உட்புற Codex Responses WebSocket பிரிட்ஜிற்கான பகிரப்பட்ட இரகசியம். Electron/உலாவி WS ரிலே மற்றும் OmniRoute ஆகியவற்றுக்கு இடையிலான பிரிட்ஜ் கோரிக்கைகளை அங்கீகரிக்கிறது. ⚠️ **உற்பத்திச் சூழலில் கட்டாயம் — இது அமைக்கப்படாமல் இருக்கும்போது, அனைத்து WS பிரிட்ஜ் கோரிக்கைகளும் நிராகரிக்கப்படும்.** `openssl rand -base64 32` மூலம் உருவாக்கவும். | -| `OMNIROUTE_SW_BUILD_ID` | இல்லை | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ஷெல்லுக்கான வெளிப்படையான சர்வீஸ்-வொர்க்கர் கேச்-பஸ்டிங் அடையாளம் (#11779); தீர்மான வரிசையில் முதலாவது. | -| `SOURCE_VERSION` | இல்லை | _(அமைக்கப்படவில்லை)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | வரிசையில் இரண்டாவது — வரிசைப்படுத்தப்பட்ட கமிட்டாக PaaS பில்டர்களால் (எ.கா. Heroku பாணி) அமைக்கப்படுகிறது. | -| `NEXT_PUBLIC_SW_BUILD_ID` | இல்லை | _(பெறப்பட்டது)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…`-ஐப் பதிவு செய்ய கிளையன்ட் பயன்படுத்தும் பில்ட்-நேர பொது மதிப்பு; மேலே உள்ள இரண்டிலிருந்தும், பின்னர் git SHA-இலிருந்தும் பெறப்படுகிறது. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | இல்லை (தானியங்கி) | _(ஒவ்வொரு பூட்டிலும் தானியங்கி)_ | `src/server/authz/policies/management.ts` | நம்பகமான பியர்-IP முத்திரை OmniRoute-இன் சொந்த HTTP சேவையகத்திலிருந்து (`scripts/dev/peer-stamp.mjs`) வந்ததை நிரூபிக்கும் ஒவ்வொரு செயல்முறைக்குமான இரகசியம். முத்திரையில் இந்த டோக்கன் இருந்தால் மட்டுமே, authz மிடில்வேர் கோரிக்கையின் உள்ளூர்த் தன்மையை (LOCAL_ONLY ரூட்களுக்கான லூப்பேக்/LAN நுழைவுக் கட்டுப்பாடு) நம்பும். ஒவ்வொரு பூட்டிலும் தானாக உருவாக்கப்படும் — அமைக்காமல் விடவும்; முத்திரையைப் பகிர வேண்டிய பல-செயல்முறை அமைப்புகளுக்கு மட்டும் இதை நிலையாக அமைக்கவும். | -| `OMNIROUTE_TRUSTED_PROXIES` | இல்லை | _(அமைக்கப்படவில்லை)_ | `scripts/dev/peer-stamp.mjs` | IP அனுமதி/மறுப்புப் பட்டியல் நம்பக்கூடிய `X-Forwarded-For` / `X-Real-IP` மதிப்புகளைக் கொண்ட ரிவர்ஸ் ப்ராக்ஸிகளின் காற்புள்ளியால் பிரிக்கப்பட்ட IP-கள் அல்லது CIDR வரம்புகள். இவை எப்போதும் நம்பப்படும் லூப்பேக், தனிப்பட்ட-நெட்வொர்க் முகவரிகள் மற்றும் Cloudflare எட்ஜ்களுக்கு மேலதிகமானவை. வேறு ஏதேனும் பொது முகவரியில் உள்ள ப்ராக்ஸிக்கு மட்டுமே இது தேவை; இல்லையெனில், வடிகட்டி மதிப்பிடுவது அந்தப் ப்ராக்ஸியின் சொந்த முகவரியைத்தான். | +**ஆம்** எனக் குறிக்கப்பட்ட, அமைக்கப்படாத இரகசியங்கள் முதல் இயக்கத்தின்போது தானாக உருவாக்கப்பட்டு (`JWT_SECRET`, `API_KEY_SECRET`), தரவுக் கோப்பகத்திலுள்ள `server.env`-இல் சேமிக்கப்படும் — அந்தக் கோப்பகம் நிலையற்றதாக (ephemeral) இருந்தால் இவற்றை வெளிப்படையாக அமைக்கவும். விதிவிலக்கு `OMNIROUTE_WS_BRIDGE_SECRET`: இது `server.env`-இல் சேமிக்கப்படாது — சேவையக நுழைவுப்புள்ளிகள் ஒவ்வொரு துவக்கத்திற்கும் ஒரு நிலையற்ற மதிப்பை உருவாக்குகின்றன, அதை செயல்முறைக்குள்ள பிரிட்ஜ் மட்டுமே பயன்படுத்த முடியும்; எனவே வெளி பிரிட்ஜ் கிளையன்ட்கள் இதை வெளிப்படையாக அமைத்திருக்க வேண்டும். + +| மாறி | தேவை | இயல்புநிலை | மூலக் கோப்பு | விளக்கம் | +| ---------------------------- | ---------------------------- | -------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **ஆம்** | _(எதுவுமில்லை)_ | `src/lib/auth` | அனைத்து டாஷ்போர்டு அமர்வு குக்கிகளிலும் (JWT) கையொப்பமிடுகிறது/அவற்றைச் சரிபார்க்கிறது. `openssl rand -base64 48` மூலம் உருவாக்கவும். | +| `API_KEY_SECRET` | **ஆம்** | _(எதுவுமில்லை)_ | `src/lib/db/apiKeys.ts` | SQLite-இல் சேமிக்கப்பட்டுள்ள API விசை மதிப்புகளுக்கான AES குறியாக்க விசை. `openssl rand -hex 32` மூலம் உருவாக்கவும். | +| `INITIAL_PASSWORD` | இல்லை | _(அமைக்கப்படவில்லை)_ | `src/lib/auth/managementPassword.ts` | இன்னும் கடவுச்சொல் எதுவும் சேமிக்கப்படாத நிலையில், துவக்கத்தின்போது டாஷ்போர்டு கடவுச்சொல்லை அமைக்கிறது; ஒரு கடவுச்சொல் சேமிக்கப்பட்டவுடன் இது புறக்கணிக்கப்படும். npm மற்றும் மூல நிறுவல்கள் `.env.example`-ஐ `.env`-ஆக நகலெடுப்பதால், அவை அதன் பொதுவாக அறியப்பட்ட பிளேஸ்ஹோல்டரான `CHANGEME` உடன் தொடங்குகின்றன: முதல் துவக்கத்திற்கு முன் அதை மாற்றவும், அல்லது அமைக்கப்படாத மதிப்பு (Docker படிமத்தின் இயல்புநிலை) செய்வதுபோல, முதல் வருகையின்போது அறிமுக வழிகாட்டியில் கடவுச்சொல்லை உருவாக்க அந்த `.env`-இல் `INITIAL_PASSWORD=` (காலியாக) என அமைக்கவும். சேமிக்கப்பட்ட கடவுச்சொல்லை Dashboard → Settings → Security வழியாக மாற்றவும், அல்லது `DATA_DIR`-ஐ சேவையகத்தின் தரவுக் கோப்பகமாக அமைத்து `omniroute reset-password`-ஐ (மூல சரிபார்ப்பில் `node bin/reset-password.mjs`) இயக்கவும். | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **ஆம்** (உற்பத்திச் சூழலில்) | _(அமைக்கப்படவில்லை)_ | `src/app/api/internal/codex-responses-ws/route.ts` | உட்புற Codex Responses WebSocket பிரிட்ஜிற்கான பகிரப்பட்ட இரகசியம். Electron/உலாவி WS ரிலே மற்றும் OmniRoute ஆகியவற்றுக்கு இடையிலான பிரிட்ஜ் கோரிக்கைகளை அங்கீகரிக்கிறது. ⚠️ **உற்பத்திச் சூழலில் கட்டாயம் — இது அமைக்கப்படாமல் இருக்கும்போது, அனைத்து WS பிரிட்ஜ் கோரிக்கைகளும் நிராகரிக்கப்படும்.** `openssl rand -base64 32` மூலம் உருவாக்கவும். | +| `OMNIROUTE_SW_BUILD_ID` | இல்லை | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA ஷெல்லுக்கான வெளிப்படையான சர்வீஸ்-வொர்க்கர் கேச்-பஸ்டிங் அடையாளம் (#11779); தீர்மான வரிசையில் முதலாவது. | +| `SOURCE_VERSION` | இல்லை | _(அமைக்கப்படவில்லை)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | வரிசையில் இரண்டாவது — வரிசைப்படுத்தப்பட்ட கமிட்டாக PaaS பில்டர்களால் (எ.கா. Heroku பாணி) அமைக்கப்படுகிறது. | +| `NEXT_PUBLIC_SW_BUILD_ID` | இல்லை | _(பெறப்பட்டது)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…`-ஐப் பதிவு செய்ய கிளையன்ட் பயன்படுத்தும் பில்ட்-நேர பொது மதிப்பு; மேலே உள்ள இரண்டிலிருந்தும், பின்னர் git SHA-இலிருந்தும் பெறப்படுகிறது. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | இல்லை (தானியங்கி) | _(ஒவ்வொரு பூட்டிலும் தானியங்கி)_ | `src/server/authz/policies/management.ts` | நம்பகமான பியர்-IP முத்திரை OmniRoute-இன் சொந்த HTTP சேவையகத்திலிருந்து (`scripts/dev/peer-stamp.mjs`) வந்ததை நிரூபிக்கும் ஒவ்வொரு செயல்முறைக்குமான இரகசியம். முத்திரையில் இந்த டோக்கன் இருந்தால் மட்டுமே, authz மிடில்வேர் கோரிக்கையின் உள்ளூர்த் தன்மையை (LOCAL_ONLY ரூட்களுக்கான லூப்பேக்/LAN நுழைவுக் கட்டுப்பாடு) நம்பும். ஒவ்வொரு பூட்டிலும் தானாக உருவாக்கப்படும் — அமைக்காமல் விடவும்; முத்திரையைப் பகிர வேண்டிய பல-செயல்முறை அமைப்புகளுக்கு மட்டும் இதை நிலையாக அமைக்கவும். | +| `OMNIROUTE_TRUSTED_PROXIES` | இல்லை | _(அமைக்கப்படவில்லை)_ | `scripts/dev/peer-stamp.mjs` | IP அனுமதி/மறுப்புப் பட்டியல் நம்பக்கூடிய `X-Forwarded-For` / `X-Real-IP` மதிப்புகளைக் கொண்ட ரிவர்ஸ் ப்ராக்ஸிகளின் காற்புள்ளியால் பிரிக்கப்பட்ட IP-கள் அல்லது CIDR வரம்புகள். இவை எப்போதும் நம்பப்படும் லூப்பேக், தனிப்பட்ட-நெட்வொர்க் முகவரிகள் மற்றும் Cloudflare எட்ஜ்களுக்கு மேலதிகமானவை. வேறு ஏதேனும் பொது முகவரியில் உள்ள ப்ராக்ஸிக்கு மட்டுமே இது தேவை; இல்லையெனில், வடிகட்டி மதிப்பிடுவது அந்தப் ப்ராக்ஸியின் சொந்த முகவரியைத்தான். | ### உருவாக்கக் கட்டளைகள் @@ -217,38 +217,38 @@ echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ## 4. பாதுகாப்பு & அங்கீகாரம் -| மாறி | இயல்புநிலை | மூலக் கோப்பு | விளக்கம் | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | இயந்திரக் கைரேகையை உருவாக்குவதற்காக வன்பொருள் அடையாளங்காட்டிகளுடன் இணைக்கப்படும் சால்ட். தனிமைப்படுத்தலுக்காக ஒவ்வொரு நிறுவலிலும் இதை மாற்றவும். | -| `OMNIROUTE_CLI_SALT` | _(அமைக்கப்படவில்லை = ஒவ்வொரு நிறுவலுக்கும் சீரற்ற சால்ட், `/cli-token-salt.json`-இல் நிலையாகச் சேமிக்கப்படும்)_ | `src/lib/machineToken.ts` | உள்ளூர் CLI அங்கீகார டோக்கனைப் பெறுவதற்கான HMAC சால்ட். இந்த மதிப்பை அமைப்பது இயந்திரத்திலுள்ள அனைத்து CLI டோக்கன்களையும் சுழற்சி செய்து, நிலையாகச் சேமிக்கப்பட்ட சால்ட்டைவிட எப்போதும் முன்னுரிமை பெறும். `docs/security/CLI_TOKEN.md`-ஐப் பார்க்கவும். | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | அமர்வு குக்கீகளில் `Secure` கொடியை அமைக்கிறது. HTTPS-க்குப் பின்னால் இயங்கும்போது இது **`true` ஆக இருக்க வேண்டும்**. | -| `REQUIRE_API_KEY` | `false` | API மிடில்வேர் | `true` ஆக இருக்கும்போது, அனைத்து `/v1/*` ப்ராக்ஸி கோரிக்கைகளிலும் செல்லுபடியாகும் API விசை இருக்க வேண்டும். **இந்தக் கொடி `GET /v1/models`-ஐக் கட்டுப்படுத்தாது**; அதற்குப் பதிலாக அது டாஷ்போர்டு உள்நுழைவு நிலைப்பாட்டை (`requireAuthForModels`) பின்பற்றுகிறது — எனவே `/v1/models`-இலிருந்து கிடைக்கும் `401`, அனுமானம் பாதுகாக்கப்பட்டுள்ளது என்று பொருள்படாது. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695)-ஐப் பார்க்கவும். | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | டாஷ்போர்டு UI-இல் முழுமையான API விசை மதிப்புகளை வெளிப்படுத்த அனுமதிக்கிறது. டாஷ்போர்டு அம்சக் கொடிகளிலிருந்து உள்ளமைக்கலாம்; பகிரப்பட்ட நிகழ்வுகளில் இது பாதுகாப்பு அபாயமாகும். | -| `NO_LOG_API_KEY_IDS` | _(காலி)_ | `src/lib/compliance/index.ts` | கோரிக்கைப் பதிவைத் தவிர்க்கும், காற்புள்ளியால் பிரிக்கப்பட்ட API விசை ID-கள் (GDPR இணக்கம்). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(அமைக்கப்படவில்லை = வரம்பற்றது)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` நெடுவரிசை null ஆக உள்ள API விசைகளுக்குப் பயன்படுத்தப்படும் ஒரு நாளுக்கான மாற்று கோரிக்கை வரம்பு. அமைக்கப்படாத அல்லது காலியான மதிப்பு: மறைமுக உச்சவரம்பு இல்லை (#2289, #11017). `0` என்பதும் இதே பொருளைக் கொண்டது (வரம்பற்றது). நேர்மறை முழு எண் N ஆனது N/நாள், 5N/வாரம், 20N/மாதம் ஆகியவற்றைச் செயல்படுத்துகிறது. தவறான வடிவமைப்புடைய, காலியல்லாத மதிப்புகள், முந்தைய 1000/நாள், 5000/வாரம், 20000/மாதம் சாளரங்களுக்கு மாற்றாக மாறும். | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | அனுமதிக்கப்படும் அதிகபட்ச கோரிக்கை உடல் அளவு. இந்த வரம்பை மீறும் பேலோடுகளை நிராகரிக்கிறது. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | இந்த வரம்புக்கு இணையான அல்லது அதற்கு மேற்பட்ட உண்மையான கோரிக்கை உடல்கள், JSON பாகுபடுத்தலுக்கு முன் அணுக்கமான, செயல்முறை-உள்ளூர் ஹெவிவெயிட் அனுமதிக் குத்தகையைப் பெறுகின்றன (`POST /v1/responses` உட்பட BYTE பாதை). கட்டமைப்பு-கனமான பாதையில் உள்ள அதே [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ஆரோக்கியமான ஹெட்ரூம் தப்புவழி இதிலும் உள்ளது; இருப்பினும் [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) மீண்டும் நிகழாதபடி, இது இன்னும் `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110)-ஆல் வரம்பிடப்படுகிறது. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | வரையறுக்கப்பட்ட உள்ளிழுத்தலின்போது வாசிக்கப்பட்ட பைட்டுகளின் அடிப்படையில் செயல்படுத்தப்படும் அரட்டை வழித்தடக் கடின வரம்பு; `Content-Length` விடுபட்ட, தவறான அல்லது உண்மைக்குப் புறம்பான கோரிக்கைகளும் இதில் அடங்கும்; வரம்பு மீறினால் `413` பெறப்படும். | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(அமைக்கப்படவில்லை — கோரிக்கை எண்ணிக்கை வரம்பு இல்லை)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** இந்தப் பழைய கோரிக்கை-எண்ணிக்கை வரம்பு இப்போது வெளிப்படையாக அமைக்கப்பட்டால் மட்டுமே செயல்படும். இதை அமைக்காமல் விட்டால் (இயல்புநிலை), கனரக அரட்டை அனுமதி அதற்குப் பதிலாக `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` மூலம் கட்டுப்படுத்தப்படும் — இது **ஒரே செயல்முறையில்** (ஒரு V8 heap) செயல்முறையின் உண்மையான நினைவக உச்சவரம்பிலிருந்து தானாகக் கணிக்கப்படும் பைட் அளவுத்திட்டம். ஒன்றுடன் ஒன்று மேற்பொருந்தும் ~750k-token `/v1/responses` கோரிக்கைகள் ~12 GiB heap-களைச் செயலிழக்கச் செய்கின்றன (#7849) — இது ஒரு **நினைவக அளவுத்திட்ட** எச்சரிக்கை, தயாரிப்பின் கடினமான அதிகபட்சம் 2 என்பதல்ல. ஆரோக்கியமான செயல்முறை (வெளியேற்ற விகிதத்திற்குக் கீழே உள்ள heap) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` வழியாக மேலும் பல ஒரேநேர நீண்ட `/v1/responses` கோரிக்கைகளை அனுமதிக்கலாம். பல்லாயிரக்கணக்கல்லாத பல நீண்ட SSE கிளையன்ட்கள் (40–50) என்பது heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 சார்ந்தது; “அதிகபட்சம் 2” அல்ல. “ஹோஸ்டைப் பயன்படுத்த” இதை கண்மூடித்தனமாக உயர்த்துவது #7849-ஐ மீண்டும் அறிமுகப்படுத்தும். **N தனித்தனி `DATA_DIR`-களுடன்** **heap-களை** பெருக்குங்கள் (#11024); ஒரே SQLite கோப்பில் ஒருபோதும் `replicas>1` பயன்படுத்த வேண்டாம். | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(தானாகக் கணிக்கப்பட்டது)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** தானாகக் கணிக்கப்படும் உள்ளிழுத்தல் பைட் அளவுத்திட்டத்திற்கான மேலெழுதும் அமைப்பு (V8/cgroup நினைவக உச்சவரம்புகளில் இறுக்கமானதன் 25%, 8x நிலையற்ற பெருக்கத்தால் வகுக்கப்பட்டது). கணிக்கப்பட்ட மற்றும் வெளிப்படையான மதிப்புகள் 8 MiB–2 GiB வரம்புக்குள் கட்டுப்படுத்தப்படும். நடைமுறை அளவுத்திட்டத்தைவிடப் பெரிய body உடனடியாக `413 body_exceeds_budget` உடன் தோல்வியடையும்; தனித்தனியாகச் சேவை செய்யக்கூடிய body-களுக்கிடையேயான போட்டி, மீண்டும் முயற்சிக்கக்கூடிய `503` ஆகவே இருக்கும். ஒரேநேரத்தில் 40–50 நீண்ட SSE கிளையன்ட்களை ஆதரிப்பது இந்த அளவுத்திட்டம் + heap சார்ந்தது; கடினமான “அதிகபட்சம் 2” அல்ல. நுண்ணமைப்பதற்கு முன் `/api/monitoring/health`-இல் `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ஆகியவற்றைப் படிக்கவும். | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE மற்றும் STRUCTURE கனரக அனுமதிக்கான heap அழுத்த வெளியேற்ற விகிதம் (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ஐத் தாண்டும் ஒரேநேர கனரக கோரிக்கை, heap-உம் இந்த விகிதத்தில் அல்லது அதற்கு மேல் இருந்தால் மட்டுமே மீண்டும் முயற்சிக்கக்கூடிய `503` உடன் வெளியேற்றப்படும்; ஆரோக்கியமான heap-இல் அது healthy-headroom வழியாக அனுமதிக்கப்படும். | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (இயல்புநிலை `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE மற்றும் BYTE ஆகிய **இரண்டிலும்** (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES`-க்கு ≥ ஆன body-கள் உட்பட) ஆரோக்கியமான-heap விரைவுப் பாதைக்கான வரையறுக்கப்பட்ட கூடுதல் திறன் (#10437). இந்த வரம்பு இல்லாமல், பிஸியாக இருந்தாலும் ஆரோக்கியமான heap கொண்ட ஒவ்வொரு கோரிக்கையும் எந்த உச்சவரம்பும் இன்றி அனுமதியைத் தவிர்த்தது. healthy-heap பாதை வழியாக இத்தனை ஒரேநேர lease-கள் செயலிலுள்ளவுடன், மேலும் வரும் பிஸியான கோரிக்கைகள் உண்மையான heap அழுத்தத்தின்போது பயன்படுத்தப்படும் அதே வரையறுக்கப்பட்ட-காத்திருப்பு/வெளியேற்றப் பாதைக்குச் செல்லும். `0` இந்தத் தவிர்ப்பை முழுமையாக முடக்கும். | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ஒரு அரட்டை கோரிக்கையின் body பைட் வரம்புக்குக் கீழே இருந்தாலும், அதை கனரகமானதாக வகைப்படுத்தும் செய்தி எண்ணிக்கை. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ஒரு அரட்டை கோரிக்கையின் body பைட் வரம்புக்குக் கீழே இருந்தாலும், அதை கனரகமானதாக வகைப்படுத்தும் கருவி எண்ணிக்கை. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ஒரு கோரிக்கையைக் கனரகமானதாக வகைப்படுத்தும் எச்சரிக்கையான சரம்-அளவு token மதிப்பீடு; இது அனுமதிச் செலவுக்கான பதிலீட்டு அளவீடே தவிர, வழங்குநரின் கட்டண tokenization அல்ல. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (முடக்கப்பட்டது) | `src/shared/middleware/chatBodyAdmission.ts` | விருப்பத்தேர்வாக இயக்கக்கூடிய அரட்டை வரலாற்று வரம்பு. இயல்பாக முடக்கப்பட்டுள்ளது: செய்தி எண்ணிக்கை என்பது நிறுவல் கொள்கையே தவிர, ஒரு கோரிக்கையின் உலகளாவிய பண்பு அல்ல; மேலும் இங்கே வரம்பிடுவது, சுருக்கச் செயல்தொடர் அவற்றைச் சேவை செய்யக்கூடியதாக மாற்றுவதற்கு முன்பே உரையாடல்களை இறுதியான `413` உடன் நிராகரிக்கும். Heap வளர்ச்சி `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` மற்றும் heap அழுத்த வெளியேற்றத்தால் கட்டுப்படுத்தப்படுகிறது. கடின உச்சவரம்பு தேவைப்படும் நினைவகக் கட்டுப்பாடுள்ள நிறுவல்களில் நேர்மறை மதிப்பை அமைக்கவும்; வரம்பு மீறினால் கட்டமைக்கப்பட்ட compact-required `413` பெறப்படும். | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | நினைவகத்தில் முழுமையாக இடையகப்படுத்தப்படும் streaming அல்லாத upstream பதிலுக்கான கடின வரம்பு. இதைத் தாண்டினால் upstream reader ரத்துசெய்யப்பட்டு, heap தீரும்வரை வரம்பின்றி வளரும் சரத்தை உருவாக்குவதற்குப் பதிலாக கோரிக்கை உடனடியாகத் தோல்வியடையும். | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | அப்ஸ்ட்ரீம் பதில் தலைப்புகளிலிருந்து முன்னனுப்பப்படும் அதிகபட்ச வயர் பைட்டுகள். வரம்பு மீறப்படும்போது, பொதுவான ரிவர்ஸ்-ப்ராக்ஸி தலைப்பு வரம்புகளுக்குள் இருக்க, குறைந்த முன்னுரிமையுள்ள தலைப்புகள் (எ.கா., தனிப்பயன் `x-codex-*`, `x-oai-request-id`) நீக்கப்படும். பதில் தலைப்பின் அளவு பெரிதாகும் செலவில் கூடுதல் அப்ஸ்ட்ரீம் மெட்டாடேட்டாவை முன்னனுப்ப, இதை அதிகமாக அமைக்கவும். | -| `CORS_ORIGIN` | _(அமைக்கப்படவில்லை)_ | `src/server/cors/origins.ts` | மரபுவழி ஒற்றை-மூல CORS அனுமதிப்பட்டியல். புதிய நிறுவல்களுக்கு `CORS_ALLOWED_ORIGINS`-ஐப் பயன்படுத்துவது விரும்பத்தக்கது. CORS என்பது குறுக்கு-மூல உலாவி API கிளையன்ட்களுக்கு மட்டுமே; அங்கீகரிக்கப்பட்ட டாஷ்போர்டு எழுதுதல் கோரிக்கைகள் அதற்குப் பதிலாக ஒரே-மூலக் கோரிக்கைகளையும் அமர்வுடன் பிணைக்கப்பட்ட CSRF பாதுகாப்பையும் பயன்படுத்துகின்றன. | -| `CORS_ALLOWED_ORIGINS` | _(அமைக்கப்படவில்லை)_ | `src/server/cors/origins.ts` | காற்புள்ளியால் பிரிக்கப்பட்ட CORS அனுமதிப்பட்டியல். `CORS_ALLOW_ALL=true` வெளிப்படையாக உள்ளமைக்கப்படாவிட்டால் வைல்ட்கார்டு எதுவும் அனுப்பப்படாது. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | எந்த உலாவி `Origin`-ஐயும் எதிரொலிக்கச் செய்வதற்கான, மேம்பாட்டுக்கு மட்டும் உரிய தப்புவழி. பகிரப்பட்ட அல்லது உற்பத்தி நிறுவல்களில் இதைச் செயல்படுத்த வேண்டாம். | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | தனிப்பட்ட/லூப்பேக்/லிங்க்-லோக்கல் IP வரம்புகளை இலக்காகக் கொண்ட வழங்குநர் அழைப்புகளைத் தடுக்கவும். தனிமைப்படுத்தப்பட்ட சோதனைச் சூழல்களில் மட்டும் இதை முடக்கவும். | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | தனிப்பட்ட/உள்ளூர் நெட்வொர்க்குகளைச் சுட்டும் வழங்குநர் URL-களை அனுமதிக்கவும் (localhost, 192.168.x.x, 10.x.x.x போன்றவை). **சுயமாக ஹோஸ்ட் செய்யப்பட்ட வழங்குநர்களுக்கு இது கட்டாயம்** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). `false` ஆக இருக்கும்போது, உள்ளூர் URL-களைச் சரிபார்ப்பதை டாஷ்போர்டு நிராகரிக்கும். | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | உள்ளூர்/தனிப்பட்ட முகவரிகளில் (127.0.0.1, localhost, LAN, தனிப்பட்ட வரம்புகள்) வழங்குநர்களைச் சேர்ப்பதை/சரிபார்ப்பதை அனுமதிக்கவும் — இது வழங்குநர் சரிபார்ப்புப் பாதைக்கு மட்டுமே வரம்பிடப்பட்டுள்ளது. **இயல்புநிலை `true`** (உள்ளூர்-முதல்); பொதுமுகவரிகள் மட்டுமே அனுமதிக்கப்படும் கடுமையான தடுப்பைச் செயல்படுத்த `false` என அமைக்கவும். கிளவுட்-மெட்டாடேட்டா முனைப்புள்ளிகள் (169.254.169.254, metadata.google.internal) எந்நிலையிலும் தடுக்கப்பட்டே இருக்கும். (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` வழித்தடங்கள் (ஒலிப்பதிவாக்கங்கள், பேச்சு, மொழிபெயர்ப்புகள்) localhost-க்கு வெளியே ஹோஸ்ட் செய்யப்பட்ட OpenAI-இணக்கமான வழங்குநர் நோடைப் பயன்படுத்த அனுமதிக்கவும். இயல்பாக முடக்கப்பட்டுள்ளது — ஆடியோவை தொலைநிலை ஹோஸ்டுக்கு வழிப்படுத்துவது வெளியேற்ற அடையாளத்தை மாற்றும் என்பதால், அது வெளிப்படையான இயக்குநர் முடிவாக இருக்க வேண்டும். லூப்பேக்/தனிப்பட்ட நோடுகள் (localhost, 127.0.0.1, 172.16-31.x) எப்போதும் அனுமதிக்கப்படுகின்றன; இவ்வமைப்பால் பாதிக்கப்படாது. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (மற்றும் நினைவக எஞ்சினின் லூப்பேக் மறுதரவரிசைப்படுத்தல் படிநிலை) localhost-க்கு வெளியே ஹோஸ்ட் செய்யப்பட்ட OpenAI-இணக்கமான வழங்குநர் நோடைப் பயன்படுத்த அனுமதிக்கவும் — அதாவது TEI, Infinity, vLLM போன்றவற்றை இயக்கும் LAN கணினி அல்லது Tailscale பியர். இயல்பாக முடக்கப்பட்டுள்ளது — தொலைநிலை ஹோஸ்டுக்கு வழிப்படுத்துவது வெளியேற்ற அடையாளத்தை மாற்றும் என்பதால், அது வெளிப்படையான இயக்குநர் முடிவாக இருக்க வேண்டும். லூப்பேக் நோடுகள் (localhost, 127.0.0.1, 172.16-31.x) எப்போதும் அனுமதிக்கப்படுகின்றன; இவ்வமைப்பால் பாதிக்கப்படாது. தொலைநிலை நோடுகள் வழங்குநரின் வெளிச்செல்லும் URL கொள்கையையும் (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) கடக்க வேண்டும்; கிளவுட்-மெட்டாடேட்டா ஹோஸ்டுகளுக்கு ஒருபோதும் வழிப்படுத்தப்படாது. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC செயல்படுத்தப்பட்டிருக்கும்போது, பயனர்கள் OIDC ஒற்றை உள்நுழைவு வழியாக மட்டுமே அங்கீகரிக்கப்படும்படி கடவுச்சொல் உள்நுழைவை முடக்கவும். எளிய மாற்றுப்பெயரான `OIDC_DISABLE_PASSWORD_LOGIN` என்பதும் ஏற்கப்படும்; அதே விசையைக் கொண்ட டாஷ்போர்டு அம்சக் கொடி முன்னுரிமை பெறும். (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-இன் எளிய மாற்றுப்பெயர் (#10889). | +| மாறி | இயல்புநிலை | மூலக் கோப்பு | விளக்கம் | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | இயந்திரக் கைரேகையை உருவாக்குவதற்காக வன்பொருள் அடையாளங்காட்டிகளுடன் இணைக்கப்படும் சால்ட். தனிமைப்படுத்தலுக்காக ஒவ்வொரு நிறுவலிலும் இதை மாற்றவும். | +| `OMNIROUTE_CLI_SALT` | _(அமைக்கப்படவில்லை = ஒவ்வொரு நிறுவலுக்கும் சீரற்ற சால்ட், `/cli-token-salt.json`-இல் நிலையாகச் சேமிக்கப்படும்)_ | `src/lib/machineToken.ts` | உள்ளூர் CLI அங்கீகார டோக்கனைப் பெறுவதற்கான HMAC சால்ட். இந்த மதிப்பை அமைப்பது இயந்திரத்திலுள்ள அனைத்து CLI டோக்கன்களையும் சுழற்சி செய்து, நிலையாகச் சேமிக்கப்பட்ட சால்ட்டைவிட எப்போதும் முன்னுரிமை பெறும். `docs/security/CLI_TOKEN.md`-ஐப் பார்க்கவும். | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | அமர்வு குக்கீகளில் `Secure` கொடியை அமைக்கிறது. HTTPS-க்குப் பின்னால் இயங்கும்போது இது **`true` ஆக இருக்க வேண்டும்**. | +| `REQUIRE_API_KEY` | `false` | API மிடில்வேர் | `true` ஆக இருக்கும்போது, அனைத்து `/v1/*` ப்ராக்ஸி கோரிக்கைகளிலும் செல்லுபடியாகும் API விசை இருக்க வேண்டும். **இந்தக் கொடி `GET /v1/models`-ஐக் கட்டுப்படுத்தாது**; அதற்குப் பதிலாக அது டாஷ்போர்டு உள்நுழைவு நிலைப்பாட்டை (`requireAuthForModels`) பின்பற்றுகிறது — எனவே `/v1/models`-இலிருந்து கிடைக்கும் `401`, அனுமானம் பாதுகாக்கப்பட்டுள்ளது என்று பொருள்படாது. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695)-ஐப் பார்க்கவும். | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | டாஷ்போர்டு UI-இல் முழுமையான API விசை மதிப்புகளை வெளிப்படுத்த அனுமதிக்கிறது. டாஷ்போர்டு அம்சக் கொடிகளிலிருந்து உள்ளமைக்கலாம்; பகிரப்பட்ட நிகழ்வுகளில் இது பாதுகாப்பு அபாயமாகும். | +| `NO_LOG_API_KEY_IDS` | _(காலி)_ | `src/lib/compliance/index.ts` | கோரிக்கைப் பதிவைத் தவிர்க்கும், காற்புள்ளியால் பிரிக்கப்பட்ட API விசை ID-கள் (GDPR இணக்கம்). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(அமைக்கப்படவில்லை = வரம்பற்றது)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` நெடுவரிசை null ஆக உள்ள API விசைகளுக்குப் பயன்படுத்தப்படும் ஒரு நாளுக்கான மாற்று கோரிக்கை வரம்பு. அமைக்கப்படாத அல்லது காலியான மதிப்பு: மறைமுக உச்சவரம்பு இல்லை (#2289, #11017). `0` என்பதும் இதே பொருளைக் கொண்டது (வரம்பற்றது). நேர்மறை முழு எண் N ஆனது N/நாள், 5N/வாரம், 20N/மாதம் ஆகியவற்றைச் செயல்படுத்துகிறது. தவறான வடிவமைப்புடைய, காலியல்லாத மதிப்புகள், முந்தைய 1000/நாள், 5000/வாரம், 20000/மாதம் சாளரங்களுக்கு மாற்றாக மாறும். | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | அனுமதிக்கப்படும் அதிகபட்ச கோரிக்கை உடல் அளவு. இந்த வரம்பை மீறும் பேலோடுகளை நிராகரிக்கிறது. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | இந்த வரம்புக்கு இணையான அல்லது அதற்கு மேற்பட்ட உண்மையான கோரிக்கை உடல்கள், JSON பாகுபடுத்தலுக்கு முன் அணுக்கமான, செயல்முறை-உள்ளூர் ஹெவிவெயிட் அனுமதிக் குத்தகையைப் பெறுகின்றன (`POST /v1/responses` உட்பட BYTE பாதை). கட்டமைப்பு-கனமான பாதையில் உள்ள அதே [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) ஆரோக்கியமான ஹெட்ரூம் தப்புவழி இதிலும் உள்ளது; இருப்பினும் [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) மீண்டும் நிகழாதபடி, இது இன்னும் `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110)-ஆல் வரம்பிடப்படுகிறது. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | வரையறுக்கப்பட்ட உள்ளிழுத்தலின்போது வாசிக்கப்பட்ட பைட்டுகளின் அடிப்படையில் செயல்படுத்தப்படும் அரட்டை வழித்தடக் கடின வரம்பு; `Content-Length` விடுபட்ட, தவறான அல்லது உண்மைக்குப் புறம்பான கோரிக்கைகளும் இதில் அடங்கும்; வரம்பு மீறினால் `413` பெறப்படும். | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(அமைக்கப்படவில்லை — கோரிக்கை எண்ணிக்கை வரம்பு இல்லை)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** இந்தப் பழைய கோரிக்கை-எண்ணிக்கை வரம்பு இப்போது வெளிப்படையாக அமைக்கப்பட்டால் மட்டுமே செயல்படும். இதை அமைக்காமல் விட்டால் (இயல்புநிலை), கனரக அரட்டை அனுமதி அதற்குப் பதிலாக `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` மூலம் கட்டுப்படுத்தப்படும் — இது **ஒரே செயல்முறையில்** (ஒரு V8 heap) செயல்முறையின் உண்மையான நினைவக உச்சவரம்பிலிருந்து தானாகக் கணிக்கப்படும் பைட் அளவுத்திட்டம். ஒன்றுடன் ஒன்று மேற்பொருந்தும் ~750k-token `/v1/responses` கோரிக்கைகள் ~12 GiB heap-களைச் செயலிழக்கச் செய்கின்றன (#7849) — இது ஒரு **நினைவக அளவுத்திட்ட** எச்சரிக்கை, தயாரிப்பின் கடினமான அதிகபட்சம் 2 என்பதல்ல. ஆரோக்கியமான செயல்முறை (வெளியேற்ற விகிதத்திற்குக் கீழே உள்ள heap) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` வழியாக மேலும் பல ஒரேநேர நீண்ட `/v1/responses` கோரிக்கைகளை அனுமதிக்கலாம். பல்லாயிரக்கணக்கல்லாத பல நீண்ட SSE கிளையன்ட்கள் (40–50) என்பது heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 சார்ந்தது; “அதிகபட்சம் 2” அல்ல. “ஹோஸ்டைப் பயன்படுத்த” இதை கண்மூடித்தனமாக உயர்த்துவது #7849-ஐ மீண்டும் அறிமுகப்படுத்தும். **N தனித்தனி `DATA_DIR`-களுடன்** **heap-களை** பெருக்குங்கள் (#11024); ஒரே SQLite கோப்பில் ஒருபோதும் `replicas>1` பயன்படுத்த வேண்டாம். | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(தானாகக் கணிக்கப்பட்டது)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** தானாகக் கணிக்கப்படும் உள்ளிழுத்தல் பைட் அளவுத்திட்டத்திற்கான மேலெழுதும் அமைப்பு (V8/cgroup நினைவக உச்சவரம்புகளில் இறுக்கமானதன் 25%, 8x நிலையற்ற பெருக்கத்தால் வகுக்கப்பட்டது). கணிக்கப்பட்ட மற்றும் வெளிப்படையான மதிப்புகள் 8 MiB–2 GiB வரம்புக்குள் கட்டுப்படுத்தப்படும். நடைமுறை அளவுத்திட்டத்தைவிடப் பெரிய body உடனடியாக `413 body_exceeds_budget` உடன் தோல்வியடையும்; தனித்தனியாகச் சேவை செய்யக்கூடிய body-களுக்கிடையேயான போட்டி, மீண்டும் முயற்சிக்கக்கூடிய `503` ஆகவே இருக்கும். ஒரேநேரத்தில் 40–50 நீண்ட SSE கிளையன்ட்களை ஆதரிப்பது இந்த அளவுத்திட்டம் + heap சார்ந்தது; கடினமான “அதிகபட்சம் 2” அல்ல. நுண்ணமைப்பதற்கு முன் `/api/monitoring/health`-இல் `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ஆகியவற்றைப் படிக்கவும். | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE மற்றும் STRUCTURE கனரக அனுமதிக்கான heap அழுத்த வெளியேற்ற விகிதம் (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`-ஐத் தாண்டும் ஒரேநேர கனரக கோரிக்கை, heap-உம் இந்த விகிதத்தில் அல்லது அதற்கு மேல் இருந்தால் மட்டுமே மீண்டும் முயற்சிக்கக்கூடிய `503` உடன் வெளியேற்றப்படும்; ஆரோக்கியமான heap-இல் அது healthy-headroom வழியாக அனுமதிக்கப்படும். | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (இயல்புநிலை `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE மற்றும் BYTE ஆகிய **இரண்டிலும்** (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES`-க்கு ≥ ஆன body-கள் உட்பட) ஆரோக்கியமான-heap விரைவுப் பாதைக்கான வரையறுக்கப்பட்ட கூடுதல் திறன் (#10437). இந்த வரம்பு இல்லாமல், பிஸியாக இருந்தாலும் ஆரோக்கியமான heap கொண்ட ஒவ்வொரு கோரிக்கையும் எந்த உச்சவரம்பும் இன்றி அனுமதியைத் தவிர்த்தது. healthy-heap பாதை வழியாக இத்தனை ஒரேநேர lease-கள் செயலிலுள்ளவுடன், மேலும் வரும் பிஸியான கோரிக்கைகள் உண்மையான heap அழுத்தத்தின்போது பயன்படுத்தப்படும் அதே வரையறுக்கப்பட்ட-காத்திருப்பு/வெளியேற்றப் பாதைக்குச் செல்லும். `0` இந்தத் தவிர்ப்பை முழுமையாக முடக்கும். | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | ஒரு அரட்டை கோரிக்கையின் body பைட் வரம்புக்குக் கீழே இருந்தாலும், அதை கனரகமானதாக வகைப்படுத்தும் செய்தி எண்ணிக்கை. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | ஒரு அரட்டை கோரிக்கையின் body பைட் வரம்புக்குக் கீழே இருந்தாலும், அதை கனரகமானதாக வகைப்படுத்தும் கருவி எண்ணிக்கை. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ஒரு கோரிக்கையைக் கனரகமானதாக வகைப்படுத்தும் எச்சரிக்கையான சரம்-அளவு token மதிப்பீடு; இது அனுமதிச் செலவுக்கான பதிலீட்டு அளவீடே தவிர, வழங்குநரின் கட்டண tokenization அல்ல. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (முடக்கப்பட்டது) | `src/shared/middleware/chatBodyAdmission.ts` | விருப்பத்தேர்வாக இயக்கக்கூடிய அரட்டை வரலாற்று வரம்பு. இயல்பாக முடக்கப்பட்டுள்ளது: செய்தி எண்ணிக்கை என்பது நிறுவல் கொள்கையே தவிர, ஒரு கோரிக்கையின் உலகளாவிய பண்பு அல்ல; மேலும் இங்கே வரம்பிடுவது, சுருக்கச் செயல்தொடர் அவற்றைச் சேவை செய்யக்கூடியதாக மாற்றுவதற்கு முன்பே உரையாடல்களை இறுதியான `413` உடன் நிராகரிக்கும். Heap வளர்ச்சி `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` மற்றும் heap அழுத்த வெளியேற்றத்தால் கட்டுப்படுத்தப்படுகிறது. கடின உச்சவரம்பு தேவைப்படும் நினைவகக் கட்டுப்பாடுள்ள நிறுவல்களில் நேர்மறை மதிப்பை அமைக்கவும்; வரம்பு மீறினால் கட்டமைக்கப்பட்ட compact-required `413` பெறப்படும். | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | நினைவகத்தில் முழுமையாக இடையகப்படுத்தப்படும் streaming அல்லாத upstream பதிலுக்கான கடின வரம்பு. இதைத் தாண்டினால் upstream reader ரத்துசெய்யப்பட்டு, heap தீரும்வரை வரம்பின்றி வளரும் சரத்தை உருவாக்குவதற்குப் பதிலாக கோரிக்கை உடனடியாகத் தோல்வியடையும். | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | அப்ஸ்ட்ரீம் பதில் தலைப்புகளிலிருந்து முன்னனுப்பப்படும் அதிகபட்ச வயர் பைட்டுகள். வரம்பு மீறப்படும்போது, பொதுவான ரிவர்ஸ்-ப்ராக்ஸி தலைப்பு வரம்புகளுக்குள் இருக்க, குறைந்த முன்னுரிமையுள்ள தலைப்புகள் (எ.கா., தனிப்பயன் `x-codex-*`, `x-oai-request-id`) நீக்கப்படும். பதில் தலைப்பின் அளவு பெரிதாகும் செலவில் கூடுதல் அப்ஸ்ட்ரீம் மெட்டாடேட்டாவை முன்னனுப்ப, இதை அதிகமாக அமைக்கவும். | +| `CORS_ORIGIN` | _(அமைக்கப்படவில்லை)_ | `src/server/cors/origins.ts` | மரபுவழி ஒற்றை-மூல CORS அனுமதிப்பட்டியல். புதிய நிறுவல்களுக்கு `CORS_ALLOWED_ORIGINS`-ஐப் பயன்படுத்துவது விரும்பத்தக்கது. CORS என்பது குறுக்கு-மூல உலாவி API கிளையன்ட்களுக்கு மட்டுமே; அங்கீகரிக்கப்பட்ட டாஷ்போர்டு எழுதுதல் கோரிக்கைகள் அதற்குப் பதிலாக ஒரே-மூலக் கோரிக்கைகளையும் அமர்வுடன் பிணைக்கப்பட்ட CSRF பாதுகாப்பையும் பயன்படுத்துகின்றன. | +| `CORS_ALLOWED_ORIGINS` | _(அமைக்கப்படவில்லை)_ | `src/server/cors/origins.ts` | காற்புள்ளியால் பிரிக்கப்பட்ட CORS அனுமதிப்பட்டியல். `CORS_ALLOW_ALL=true` வெளிப்படையாக உள்ளமைக்கப்படாவிட்டால் வைல்ட்கார்டு எதுவும் அனுப்பப்படாது. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | எந்த உலாவி `Origin`-ஐயும் எதிரொலிக்கச் செய்வதற்கான, மேம்பாட்டுக்கு மட்டும் உரிய தப்புவழி. பகிரப்பட்ட அல்லது உற்பத்தி நிறுவல்களில் இதைச் செயல்படுத்த வேண்டாம். | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | மரபு மாற்றுப்பெயர் (alias). இதனுடைய டாஷ்போர்டு நிலைமாற்றி (ஒரு DB மேலெழுதல்) இந்த சூழல் மதிப்பை விட முன்பே படிக்கப்படுகிறது; இரண்டில் ஏதேனும் ஒன்றில் `false`, `0`, `no` அல்லது `off` இருந்தால், அந்தக் கொடியின் டாஷ்போர்டு நிலைமாற்றி முடக்கப்பட்ட நிலையில் சேமிக்கப்பட்டிருந்தாலும், ஹோஸ்ட் சரிபார்ப்புகள் `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` செய்வதுபோலவே முடக்கப்படும்; மற்ற மதிப்புகள் காவலைக் கீழுள்ள இரண்டுக் கொடிகளிடம் விட்டுவிடும். | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | காவல் பயன்முறையைப் பின்பற்றும் வழங்குநர் பாதைகளில் — URL சரிபார்ப்பு, மாதிரி கண்டறிதல், வழங்குநர்-நோடு அடிப்படை URL-கள் (தொலைநிலை மறுதரவரிசை நோடுகள் உட்பட), ப்ராக்ஸி-ஃபால்பேக் சோதனை, மேலும் தொலைநிலை படம்/மீடியா எடுத்தல் போன்ற வழங்குநர்-சார்ந்த வெளிச்செல்லும் பாதைகள் — வெளிச்செல்லும் URL காவலின் ஹோஸ்ட் சரிபார்ப்புகளை, கிளவுட்-மெட்டாடேட்டா தடுப்பும் சேர்த்து (169.254.0.0/16 முழுவதும், metadata.google.internal), முடக்குகிறது. இது தனிப்பட்ட webhook இலக்குகளையும் அனுமதிக்கிறது; ஆனால் கிளவுட்-மெட்டாடேட்டா/லிங்க்-லோக்கல் முனைப்புள்ளிகள், இந்தக் கொடி இயக்கத்தில் இருந்தாலும், தடுக்கப்பட்டே இருக்கும். `BaseExecutor.execute()` வழியாக அனுப்பப்படும் அரட்டை கோரிக்கைகள் தங்கள் கிளவுட்-மெட்டாடேட்டா சரிபார்ப்பைத் தக்கவைத்துக்கொள்ளும். உள்ளூர் வழங்குநர்கள் இயல்புநிலை அமைப்புடனேயே வேலை செய்யும்: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (இயல்புநிலை `true`) ஏற்கனவே சரிபார்ப்பு, கண்டறிதல் மற்றும் வழங்குநர்-நோடு பாதைகளில் உள்ளூர்/LAN URL-களை அனுமதிக்கிறது; ப்ராக்ஸி-ஃபால்பேக் சோதனை மற்றும் தனிப்பட்ட webhook இலக்குகள் இந்தக் கொடியை மட்டுமே பார்க்கும், இது முடக்கத்தில் இருக்கும்வரை உள்ளூர்/LAN ஹோஸ்டுகளைத் தடுக்கும். `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`-ஐ மேலெழுதும். டாஷ்போர்டு நிலைமாற்றியிலிருந்து சேமிக்கப்பட்ட மதிப்பு இந்த மாறியை விட முன்னுரிமை பெறும். | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | வழங்குநர் சரிபார்ப்பு, மாதிரி கண்டறிதல், அரட்டை மற்றும் காவல் பயன்முறை நிர்வகிக்கும் பிற வழங்குநர்-சார்ந்த வெளிச்செல்லும் பாதைகளுக்கு, உள்ளூர்/தனிப்பட்ட முகவரிகளில் (127.0.0.1, localhost, LAN, தனிப்பட்ட வரம்புகள்) வழங்குநர் URL-களை அனுமதிக்கவும். **இயல்புநிலை `true`** (உள்ளூர்-முதல்): அப்போது காவல் கிளவுட்-மெட்டாடேட்டா முனைப்புள்ளிகளைத் தடுக்கும் — 169.254.0.0/16 முழுவதும் மற்றும் அறியப்பட்ட மெட்டாடேட்டா ஹோஸ்ட்பெயர்கள் (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). பொது-மட்டும் பயன்முறைக்கு `false` என அமைக்கவும்; அது தனிப்பட்ட மற்றும் லூப்பேக் ஹோஸ்டுகளையும் தடுக்கும். எந்த DNS தேடலுக்கும் முன், காவல் எழுதப்பட்டதுபோலவே ஹோஸ்ட்பெயர் அல்லது IP லிட்டரலைச் சரிபார்க்கும்; பொது-மட்டும் பயன்முறையில் safe-fetch ரேப்பர் கூடுதலாக ஹோஸ்ட்பெயரை முடிவுசெய்து, தனிப்பட்ட பதில் எதுவும் வந்தால் நிராகரிக்கும். `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` இந்தச் சரிபார்ப்புகளை அந்த வரிசை பட்டியலிடும் பாதைகளில் முடக்கும். LM Studio, Ollama, vLLM, Llamafile, Triton போன்ற உள்ளமைக்கப்பட்ட உள்ளூர் வழங்குநர்கள், விசை சரிபார்ப்பு மற்றும் அரட்டைக்கான அனைத்து ஹோஸ்ட் சரிபார்ப்புகளையும் தவிர்க்கின்றன. டாஷ்போர்டு நிலைமாற்றியிலிருந்து சேமிக்கப்பட்ட மதிப்பு இந்த மாறியை விட முன்னுரிமை பெறும். (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` வழித்தடங்கள் (ஒலிப்பதிவாக்கங்கள், பேச்சு, மொழிபெயர்ப்புகள்) localhost-க்கு வெளியே ஹோஸ்ட் செய்யப்பட்ட OpenAI-இணக்கமான வழங்குநர் நோடைப் பயன்படுத்த அனுமதிக்கவும். இயல்பாக முடக்கப்பட்டுள்ளது — ஆடியோவை தொலைநிலை ஹோஸ்டுக்கு வழிப்படுத்துவது வெளியேற்ற அடையாளத்தை மாற்றும் என்பதால், அது வெளிப்படையான இயக்குநர் முடிவாக இருக்க வேண்டும். லூப்பேக்/தனிப்பட்ட நோடுகள் (localhost, 127.0.0.1, 172.16-31.x) எப்போதும் அனுமதிக்கப்படுகின்றன; இவ்வமைப்பால் பாதிக்கப்படாது. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (மற்றும் நினைவக எஞ்சினின் லூப்பேக் மறுதரவரிசைப்படுத்தல் படிநிலை) localhost-க்கு வெளியே ஹோஸ்ட் செய்யப்பட்ட OpenAI-இணக்கமான வழங்குநர் நோடைப் பயன்படுத்த அனுமதிக்கவும் — அதாவது TEI, Infinity, vLLM போன்றவற்றை இயக்கும் LAN கணினி அல்லது Tailscale பியர். இயல்பாக முடக்கப்பட்டுள்ளது — தொலைநிலை ஹோஸ்டுக்கு வழிப்படுத்துவது வெளியேற்ற அடையாளத்தை மாற்றும் என்பதால், அது வெளிப்படையான இயக்குநர் முடிவாக இருக்க வேண்டும். லூப்பேக் நோடுகள் (localhost, 127.0.0.1, 172.16-31.x) எப்போதும் அனுமதிக்கப்படுகின்றன; இவ்வமைப்பால் பாதிக்கப்படாது. தொலைநிலை நோடின் அடிப்படை URL-ம் வழங்குநரின் வெளிச்செல்லும் URL கொள்கையை (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) கடக்க வேண்டும். | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC செயல்படுத்தப்பட்டிருக்கும்போது, பயனர்கள் OIDC ஒற்றை உள்நுழைவு வழியாக மட்டுமே அங்கீகரிக்கப்படும்படி கடவுச்சொல் உள்நுழைவை முடக்கவும். எளிய மாற்றுப்பெயரான `OIDC_DISABLE_PASSWORD_LOGIN` என்பதும் ஏற்கப்படும்; அதே விசையைக் கொண்ட டாஷ்போர்டு அம்சக் கொடி முன்னுரிமை பெறும். (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN`-இன் எளிய மாற்றுப்பெயர் (#10889). | ### பாதுகாப்பை வலுப்படுத்தும் சரிபார்ப்புப் பட்டியல் diff --git a/docs/i18n/te/CONTRIBUTING.md b/docs/i18n/te/CONTRIBUTING.md index 112075afb644..2644bd19cf30 100644 --- a/docs/i18n/te/CONTRIBUTING.md +++ b/docs/i18n/te/CONTRIBUTING.md @@ -380,22 +380,22 @@ docs/ # Documentation --- -## పుల్ రిక్వెస్ట్ తనిఖీ జాబితా +## పుల్ రిక్వెస్ట్ చెక్లిస్ట్ - [ ] పరీక్షలు విజయవంతమయ్యాయి (`npm test`) - [ ] లింటింగ్ విజయవంతమైంది (`npm run lint`) - [ ] బిల్డ్ విజయవంతమైంది (`npm run build`) - [ ] కొత్త పబ్లిక్ ఫంక్షన్లు మరియు ఇంటర్ఫేస్ల కోసం TypeScript టైప్లు జోడించబడ్డాయి - [ ] హార్డ్కోడ్ చేసిన సీక్రెట్లు లేదా ఫాల్బ్యాక్ విలువలు లేవు -- [ ] పబ్లిక్ అప్స్ట్రీమ్ క్రెడెన్షియల్లు లిటరల్లుగా కాకుండా `resolvePublicCred()` ద్వారా పొందుపరచబడ్డాయి ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) చూడండి) -- [ ] ఎర్రర్ రెస్పాన్స్లు `buildErrorBody()` / `sanitizeErrorMessage()` ద్వారా రూట్ చేయబడతాయి — రెస్పాన్స్ బాడీలలో ముడి స్టాక్ ట్రేస్లు ఉండకూడదు ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) చూడండి) +- [ ] పబ్లిక్ అప్స్ట్రీమ్ క్రెడెన్షియల్స్ను లిటరల్స్గా కాకుండా `resolvePublicCred()` ద్వారా పొందుపరిచారు ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) చూడండి) +- [ ] ఎర్రర్ రెస్పాన్స్లు `buildErrorBody()` / `sanitizeErrorMessage()` ద్వారా పంపబడతాయి — రెస్పాన్స్ బాడీలలో ముడి స్టాక్ ట్రేస్లు ఉండవు ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) చూడండి) - [ ] షెల్ కమాండ్లు (`exec` / `spawn`) రన్టైమ్ విలువలను స్ట్రింగ్ ఇంటర్పోలేషన్ ద్వారా కాకుండా `env` ద్వారా పంపుతాయి - [ ] అన్ని ఇన్పుట్లు Zod స్కీమాలతో ధ్రువీకరించబడ్డాయి - [ ] వినియోగదారులకు కనిపించే మార్పుల కోసం `changelog.d/{features|fixes|maintenance}/-.md` కింద చేంజ్లాగ్ **ఫ్రాగ్మెంట్** జోడించబడింది ([`changelog.d/README.md`](./changelog.d/README.md) చూడండి) — `CHANGELOG.md`ను నేరుగా సవరించవద్దు; ఫ్రాగ్మెంట్లు విడుదల సమయంలో సమీకరించబడతాయి మరియు PRల మధ్య ఎప్పటికీ విభేదించవు - [ ] డాక్యుమెంటేషన్ నవీకరించబడింది (వర్తిస్తే) -- [ ] కొత్త CodeQL / Secret-Scanning అలర్ట్లు ఏవీ తెరవబడలేదు, లేదా ప్రతి అలర్ట్ సంబంధిత `docs/security/` డాక్యుమెంట్ను ప్రస్తావించే సాంకేతిక సమర్థనతో విస్మరించబడింది +- [ ] కొత్త CodeQL / Secret-Scanning అలర్ట్లు ఏవీ తెరవబడలేదు లేదా ప్రతి అలర్ట్ను సంబంధిత `docs/security/` డాక్యుమెంట్ను సూచించే సాంకేతిక సమర్థనతో డిస్మిస్ చేశారు - [ ] చైల్డ్ ప్రాసెస్లను ప్రారంభించే రూట్లు (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts`లో `isLocalOnlyPath()`గా వర్గీకరించబడ్డాయి — [కఠిన నియమం #15](docs/security/ROUTE_GUARD_TIERS.md) చూడండి -- [ ] కమిట్ సందేశాలలో `Co-Authored-By` ట్రైలర్లు లేవు — కమిట్లు పూర్తిగా రిపాజిటరీ యజమాని Git గుర్తింపు కింద మాత్రమే కనిపించాలి (కఠిన నియమం #16) +- [ ] కమిట్ సందేశాలలో AI/బాట్ `Co-authored-by` ట్రైలర్లు లేవు (కఠిన నియమం #16) — తిరిగి ఉపయోగించిన పనికి చెందిన మానవ సహకారులకు ప్రామాణిక `Co-authored-by: Name ` ట్రైలర్లతో క్రెడిట్ ఇవ్వబడింది --- diff --git a/docs/i18n/te/docs/architecture/QUALITY_GATES.md b/docs/i18n/te/docs/architecture/QUALITY_GATES.md index c64204421354..7aaeb20607c2 100644 --- a/docs/i18n/te/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/te/docs/architecture/QUALITY_GATES.md @@ -17,255 +17,261 @@ ratchet baseline లేదా pass/fail విధానాన్ని ఉపయ ## గేట్ ఇన్వెంటరీ మరియు అమలు ప్రొఫైల్లు -### అభ్యర్థి అనుమతి +### అభ్యర్థి ప్రవేశం -CI మరియు Quality Gates వర్క్ఫ్లోలు ఒక్కొక్కటి స్థిరమైన తీర్పును వెలువరిస్తాయి: `Gate / CI` మరియు -`Gate / Quality`. వాటి వెర్షన్ చేయబడిన అనుమతి విధానం ప్రతి అప్స్ట్రీమ్ జాబ్ను -తప్పనిసరి లేదా సలహాత్మకమైనదిగా పేర్కొంటుంది. వర్తించే తప్పనిసరి జాబ్ విజయవంతం కావాలి: లేని, +CI మరియు Quality Gates వర్క్ఫ్లోలు ఒక్కొక్కటి స్థిరమైన తీర్పును ఉత్పత్తి చేస్తాయి: `Gate / CI` మరియు +`Gate / Quality`. వాటి వెర్షన్ చేయబడిన ప్రవేశ విధానం ప్రతి అప్స్ట్రీమ్ జాబ్ను +తప్పనిసరి లేదా సలహాపరమైనదిగా జాబితా చేస్తుంది. వర్తించే తప్పనిసరి జాబ్ తప్పనిసరిగా విజయవంతం కావాలి: లేని, రద్దయిన, దాటవేయబడిన, పెండింగ్లో ఉన్న మరియు తెలియని ఫలితాలు PASSను నిర్ధారించలేవు. చెల్లుబాటు అయ్యే -docs-only లేదా catalog-only వర్గీకరణ ఒక కోడ్ లేన్ను వర్తించనిదిగా చేయగలదు; -డ్రాఫ్ట్ PR ఆమోదించబడిన అభ్యర్థి కాదు. `hotfix` లేబుల్ సాక్ష్యాధార అవసరాన్ని మినహాయించదు. +docs-only లేదా catalog-only వర్గీకరణ కోడ్ లేన్ను వర్తించనిదిగా చేయగలదు; +డ్రాఫ్ట్ PR ఆమోదించబడిన అభ్యర్థి కాదు. `hotfix` లేబుల్ ఆధారాల అవసరాన్ని మినహాయించదు. -రెండు వర్క్ఫ్లోలు PRలు మరియు main/release బ్రాంచ్లకు పుష్లు, మాన్యువల్ డిస్పాచ్ మరియు -merge-group ఈవెంట్లను కవర్ చేస్తాయి. Push, dispatch మరియు merge-group పూర్తి ఎంపికను అమలు చేస్తాయి. Forkలు -మరియు merge groupలు, లేనిపక్షంలో self-hosted రన్నర్లను ఎంచుకునే జాబ్ల కోసం hosted రన్నర్లను ఉపయోగిస్తాయి; +రెండు వర్క్ఫ్లోలు PRలను, main/release బ్రాంచ్లకు పుష్లను, మాన్యువల్ డిస్పాచ్ను మరియు +merge-group ఈవెంట్లను కవర్ చేస్తాయి. పుష్, డిస్పాచ్ మరియు merge-group పూర్తి ఎంపికను అమలు చేస్తాయి. సాధారణంగా self-hosted +రన్నర్లను ఎంచుకునే జాబ్ల కోసం ఫోర్క్లు మరియు merge groupలు hosted రన్నర్లను ఉపయోగిస్తాయి; రోల్అవుట్కు ముందు తగిన hosted సామర్థ్యాన్ని ధృవీకరించాలి. ప్రతి JSON రసీదు చెక్అవుట్ చేసిన SHA, వర్క్ఫ్లో రన్ మరియు ప్రయత్నాన్ని గుర్తిస్తుంది. -చెక్అవుట్/event SHA సరిపోలకపోతే CLI తిరస్కరిస్తుంది. వర్క్ఫ్లో పరీక్షలు విధాన సభ్యత్వాన్ని -తీర్పు జాబ్ యొక్క `needs` జాబితాకు బంధిస్తాయి, అందువల్ల కొత్తగా జోడించిన లేదా తొలగించిన లేన్ నిశ్శబ్దంగా అదృశ్యం కాలేదు. +చెక్అవుట్/event SHA అసమతుల్యతను CLI తిరస్కరిస్తుంది. కొత్త లేదా తొలగించిన లేన్ నిశ్శబ్దంగా కనుమరుగవకుండా, +వర్క్ఫ్లో పరీక్షలు విధాన సభ్యత్వాన్ని తీర్పు జాబ్ యొక్క `needs` జాబితాకు అనుసంధానిస్తాయి. రసీదులు వాటి స్వంత వర్క్ఫ్లోను కవర్ చేస్తాయి; ప్రచురణ, డిప్లాయ్మెంట్ లేదా ఇప్పటికే ఉన్న -సలహాత్మక స్కానర్ అంతర్గతాలను కాదు. బ్రాంచ్ నియమాలలో రెండు చెక్ పేర్లను సక్రియం చేయడం -వేరొక పరిపాలనా మార్పు; ఈ జాబ్లను జోడించడం మాత్రమే బ్రాంచ్ను రక్షించదు. +సలహాపరమైన స్కానర్ అంతర్గత అంశాలను కాదు. బ్రాంచ్ నియమాలలో రెండు చెక్ పేర్లను సక్రియం చేయడం +ఒక ప్రత్యేక పరిపాలనా మార్పు; ఈ జాబ్లను జోడించడం మాత్రమే బ్రాంచ్ను రక్షించదు. ### స్టాటిక్ స్కాన్ ఇన్వెంటరీ వెర్షన్ చేయబడిన npm-alias ఇన్వెంటరీ మరియు స్టాటిక్-స్కాన్ సభ్యత్వం `config/quality/gate-manifest.json`లో ఉంటాయి. స్క్రిప్ట్ పేర్లు మరియు ఖచ్చితమైన కమాండ్లను -`package.json`తో పోల్చి ధృవీకరించడానికి `npm run check:gate-manifest` అమలు చేయండి; జోడింపులు, తొలగింపులు మరియు +`package.json`తో సరిపోల్చి ధృవీకరించడానికి `npm run check:gate-manifest`ను అమలు చేయండి; జోడింపులు, తొలగింపులు మరియు కమాండ్ వ్యత్యాసాలు స్థానిక హుక్ మరియు CIలోని మార్పు-వర్గీకరణ జాబ్లు రెండింటినీ విఫలం చేస్తాయి. -ఒక alias అనేది వర్క్ఫ్లో జాబ్, మ్యాట్రిక్స్ ఇన్స్టాన్స్ లేదా టెస్ట్ కేస్ కాదు: ఈ లెక్కలను -పరస్పరం మార్పిడి చేయదగినవిగా చూపకూడదు. +ఒక alias అనేది వర్క్ఫ్లో జాబ్, మ్యాట్రిక్స్ ఇన్స్టాన్స్ లేదా టెస్ట్ కేస్ కాదు: ఈ గణనలను +పరస్పరం మార్చుకోదగినవిగా చూపకూడదు. ఎంచుకున్న aliasలను అమలు చేయకుండా పరిశీలించడానికి `npm run quality:scan -- --list` లేదా `npm run quality:scan:fast -- --list` -ఉపయోగించండి. రన్నర్ -npm ఎంట్రీపాయింట్ను అమలు చేస్తుంది, కాబట్టి దాని రన్టైమ్ (కాన్ఫిగర్ చేసిన చోట Bunతో సహా) అలాగే ఉంటుంది. -మానిఫెస్ట్ ఆ ప్రొఫైల్ల వెలుపల ఉన్న aliasలను విడిగా అమలు చేయబడేవిగా నమోదు చేస్తుంది, మరియు -రీడ్-ఓన్లీ స్కాన్ ప్రొఫైల్లలో నిర్వహణ కమాండ్లు నిషేధించబడ్డాయి. - -ఈ ప్రొఫైల్లు స్టాటిక్ స్కాన్ను మాత్రమే కవర్ చేస్తాయి. అవి ఉత్పత్తి పరీక్షలు, -కవరేజ్, ప్యాకేజింగ్, బాహ్య తనిఖీలు లేదా అభ్యర్థి యొక్క పూర్తి విడుదల ఆమోదాన్ని ధృవీకరించవు. -వర్క్ఫ్లో అనుమతి లింక్ చేయబడిన `config/quality/admission-policy.json` మరియు -`scripts/quality/admission-verdict.mjs`ను ఉపయోగిస్తుంది. Release-observer ప్రొఫైల్లు వేరుగా ఉంటాయి; -వాటికి వర్తించే తనిఖీలు మరియు రసీదులను స్వతంత్రంగా పరిశీలించండి. క్రింద ఉన్న వచన -ఇన్వెంటరీ ఒక సూచన మాత్రమే; గేట్ నిజంగా అమలైందనడానికి అది రుజువు కాదు. +ఉపయోగించండి. రన్నర్ npm ఎంట్రీపాయింట్ను ఆహ్వానిస్తుంది, కాబట్టి దాని రన్టైమ్ +(కాన్ఫిగర్ చేసిన చోట Bunతో సహా) అలాగే నిల్వ ఉంటుంది. +మానిఫెస్ట్ ఈ ప్రొఫైల్ల వెలుపల ఉన్న aliasలను విడిగా ఆహ్వానించబడినవిగా నమోదు చేస్తుంది, మరియు +read-only స్కాన్ ప్రొఫైల్లలో నిర్వహణ కమాండ్లు నిషేధించబడ్డాయి. + +ఈ ప్రొఫైల్లు స్టాటిక్ స్కాన్ను మాత్రమే కవర్ చేస్తాయి. ఇవి ప్రోడక్ట్ పరీక్షలు, +కవరేజ్, ప్యాకేజింగ్, బాహ్య చెక్లు లేదా అభ్యర్థి యొక్క పూర్తి విడుదల ఆమోదాన్ని ధృవీకరించవు. +వర్క్ఫ్లో ప్రవేశం అనుసంధానించిన `config/quality/admission-policy.json` మరియు +`scripts/quality/admission-verdict.mjs`ను ఉపయోగిస్తుంది. Release-observer ప్రొఫైల్లు వేరుగానే ఉంటాయి; +వాటికి వర్తించే చెక్లు మరియు రసీదులను స్వతంత్రంగా పరిశీలించండి. దిగువనున్న గద్య +ఇన్వెంటరీ ఒక సూచన మాత్రమే, గేట్ వాస్తవంగా అమలైందనే రుజువు కాదు. స్క్రిప్ట్లు `scripts/check/` (విధాన గేట్లు) మరియు `scripts/quality/` (ర్యాచెట్ ఇంజిన్) కింద ఉంటాయి. CIకి ప్రామాణిక మూలం `.github/workflows/ci.yml`. -### విడుదల PR ఫాస్ట్-పాత్ (`quality.yml`) +### విడుదల PR వేగవంతమైన మార్గం (`quality.yml`) `.github/workflows/quality.yml` main/release PRలు, రక్షిత-బ్రాంచ్ -పుష్లు, dispatch మరియు merge groupలపై CIకి అనుబంధంగా పనిచేస్తుంది. PRలు పాత్-ఫిల్టర్ చేసిన వేగవంతమైన తనిఖీలను ఉపయోగిస్తాయి. శాశ్వతంగా -నిలిపివేయబడిన డూప్లికేట్ బిల్డ్ తొలగించబడింది; అసలు build/package/boot తనిఖీలు CIలోనే ఉంటాయి. - -| జాబ్ | పరిధి | నిరోధక స్థితి | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `Docs Gates (fast-path)` | Docs/code PRలు; API docs రిఫరెన్స్లు మరియు docs-all | అవును | -| `Fast Quality Gates` | Code PRలు; స్టాటిక్ తనిఖీలు, typecheck, dashboard typecheck, ప్రభావిత unit tests | అవును | -| `Forgotten sibling tests` | Code PRలు; మార్చిన మాడ్యూల్ల నుంచి స్టాటిక్ వినియోగదారులు మరియు సంభావ్య sibling tests వరకు ట్రేస్ చేయడం; barrel మరియు dynamic-import పాత్లు సూచనాత్మక డయాగ్నస్టిక్స్గా నివేదించబడతాయి, సూచించబడిన allowlist మినహాయింపులతో సహా | **సలహాత్మకం** | -| `Vitest (fast-path)` | Code PRలు; వేగవంతమైన vitest సూట్ | అవును | -| `Unit Tests fast-path` | Code PRలు; 4-shard unit సూట్ | అవును | -| `No new ESLint warnings` | Code PRలు; suppressions-aware lint గార్డ్ | అవును, forksతో సహా | -| `Merge integrity (changelog + generated skills)` | డ్రాఫ్ట్ కాని PRలు; changelog మరియు రూపొందించిన skill సమకాలీకరణ | అవును, forksతో సహా | - -#### మరచిపోయిన sibling tests నివేదిక - -`npm run check:forgotten-sibling-tests` test-impact మ్యాప్ వెనుక ఉన్న import resolverను తిరిగి ఉపయోగిస్తుంది. +పుష్లు, డిస్పాచ్ మరియు merge groupలపై CIకి పూరకంగా పనిచేస్తుంది. PRలు పాత్ ఆధారంగా ఫిల్టర్ చేసిన వేగవంతమైన చెక్లను ఉపయోగిస్తాయి. శాశ్వతంగా +నిలిపివేసిన నకిలీ బిల్డ్ తొలగించబడింది; అసలు build/package/boot చెక్లు CIలోనే ఉన్నాయి. + +| జాబ్ | పరిధి | నిరోధకం | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | +| `Docs Gates (fast-path)` | Docs/code PRలు; API docs refs మరియు docs-all | అవును | +| `Fast Quality Gates` | Code PRలు; స్టాటిక్ చెక్లు, typecheck, dashboard typecheck, ప్రభావిత unit tests | అవును | +| `Forgotten sibling tests` | Code PRలు; మార్చిన మాడ్యూల్ల నుంచి స్టాటిక్ వినియోగదారులు మరియు సంభావ్య sibling tests వరకు ట్రేస్ చేస్తుంది; barrel మరియు dynamic-import మార్గాలు సూచించబడిన allowlist మినహాయింపులతో సలహాపరమైన డయాగ్నస్టిక్స్గా నివేదించబడతాయి | **సలహాపరమైనది** | +| `Vitest (fast-path)` | Code PRలు; వేగవంతమైన vitest సూట్ | అవును | +| `Unit Tests fast-path` | Code PRలు; 4-shard unit సూట్ | అవును | +| `No new ESLint warnings` | Code PRలు; suppressions-aware lint గార్డ్ | అవును, ఫోర్క్లతో సహా | +| `Merge integrity (changelog + generated skills)` | డ్రాఫ్ట్ కాని PRలు; changelog మరియు రూపొందించిన skill సమకాలీకరణ | అవును, ఫోర్క్లతో సహా | + +#### మరిచిపోయిన sibling tests నివేదిక + +`npm run check:forgotten-sibling-tests` టెస్ట్-ఇంపాక్ట్ మ్యాప్ వెనుక ఉన్న import resolverను తిరిగి ఉపయోగిస్తుంది. మార్చబడిన ప్రతి production module కోసం, సంభావ్య -test pull-request diffలో లేనప్పుడు ఇది నిర్ణీతమైన -`changed module/symbol -> static consumer -> candidate sibling test` చైన్లను నివేదిస్తుంది. ఏదైనా నిరోధక రోల్అవుట్కు ముందు కాలిబ్రేషన్ కోసం Markdown సారాంశం మరియు JSON ఫలితం +టెస్ట్ pull-request diffలో లేనప్పుడు ఇది నిర్ధారిత +`changed module/symbol -> static consumer -> candidate sibling test` గొలుసులను నివేదిస్తుంది. ఏదైనా నిరోధక రోల్అవుట్కు ముందు కాలిబ్రేషన్ కోసం Markdown సారాంశం మరియు JSON ఫలితం `forgotten-sibling-tests` వర్క్ఫ్లో ఆర్టిఫాక్ట్గా నిల్వ చేయబడతాయి. -బ్యారెల్ రీ-ఎక్స్పోర్ట్లు మరియు డైనమిక్ ఇంపోర్ట్లు కేవలం రిజల్యూషన్ డయాగ్నస్టిక్స్ మాత్రమే; అవి ఎప్పుడూ -బ్లాకింగ్ ఫైండింగ్ను సృష్టించవు. సమీక్షించిన మినహాయింపులు -`config/quality/forgotten-sibling-allowlist.json`లో ఉంటాయి. ప్రతి ఎంట్రీ తప్పనిసరిగా కన్స్యూమర్ మరియు అభ్యర్థి -టెస్ట్ను పేర్కొనాలి, నిర్దిష్టమైన కారణాన్ని ఇవ్వాలి, అలాగే GitHub ఇష్యూ లేదా పుల్ రిక్వెస్ట్కు లింక్ చేయాలి. తప్పుగా రూపొందించిన ఎంట్రీలు -క్లోజ్డ్గా విఫలమవుతాయి. తొలగించిన అభ్యర్థి టెస్ట్ను లేదా `.skip`/`.todo`ను జోడించే డిఫ్ను మినహాయింపులు నిరోధించలేవు; -అసెర్షన్ బలహీనపరచడం మరియు ఇతర మాస్కింగ్ స్వతంత్రంగా బ్లాక్ చేసే -`check:test-masking` గేట్ పరిధిలోనే ఉంటాయి. - -### జాబ్: `lint` - -`main`కు చేసే ప్రతి PRపై రన్ అవుతుంది. విఫలమైతే మెర్జ్ను బ్లాక్ చేస్తుంది. - -| స్క్రిప్ట్ (`npm run ...`) | ధృవీకరించేది | బ్లాకింగ్ | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | Node.js వెర్షన్ మద్దతు ఉన్న పరిధిలో ఉంది | అవును | -| `check:cycles` | సర్క్యులర్ ఇంపోర్ట్లు — అన్ని `src/` + `open-sse/` మాడ్యూల్లు | అవును | -| `check:route-validation:t06` | అన్ని రూట్లలో Zod స్కీమాలు ఉన్నాయి (Tier 6 విధానం) | అవును | -| `check:any-budget:t11` | `@ts-expect-error // any` సంఖ్య బడ్జెట్ను మించదు (Tier 11 catraca) | అవును | -| `check:provider-consistency` | `providers.ts` లోని ప్రతి providerకు `providerRegistry.ts` లో సరిపోలే entry ఉంది (మరియు allowlist పరిధిలో దీనికి విరుద్ధంగా కూడా) | అవును | -| `check:model-lifecycle` | చేతితో నిర్వహించే మూడు routing tableలు checked-in lifecycle snapshot (#11503)తో స్థిరంగా ఉంటాయి: `FITNESS_TABLE` (`taskFitness.ts`) అనేది `REGISTRY` route చేయగల retired idకు score ఇవ్వదు; ప్రతి `BUILT_IN_ALIASES` target `REGISTRY`లో ఉంటుంది మరియు retired-id snapshotలో ఉండదు; ఇప్పటికీ `REGISTRY`లో ఉన్న ప్రతి retired id forward చేయబడుతుంది లేదా `allowedRetiredInCatalog`లో జాబితా చేయబడుతుంది; అలాగే ఆ snapshotలో retiredగా కనిపించే ఏ source లేదా target కూడా `DEFAULT_DEGRADATION_MAP`లో ఉండదు. ఒక model ప్రస్తుతం live upstream ద్వారా అందించబడుతోందని ఇది నిరూపించదు. Offline — చేతితో `npm run quality:refresh-model-lifecycle` (network; CIలో అనుసంధానించబడలేదు) ద్వారా refresh చేయబడిన `config/quality/model-lifecycle.json`తో పోలుస్తుంది. `allowedRetiredInCatalog` అనేది burn-down ratchet: tracking issue ఉన్నప్పుడు మాత్రమే entryని జోడించండి. | అవును | -| `check:fetch-targets` | client-side `src/`లోని ప్రతి `fetch("/api/...")` నిజమైన `route.ts`కు resolve అవుతుంది | అవును | -| `check:deps` | repoలోని ప్రతి `package.json` అంతటా ఉన్న `npm install` చేయగల అన్ని deps `dependency-allowlist.json`లో ఉంటాయి; కొత్తగా pin చేయని లేదా slopsquat చేసిన packageలు flag చేయబడతాయి | అవును | -| `audit:deps` | `npm audit` (root + electron) — high/critical advisoryలు లేవు (osv `check:vuln-ratchet`తో overlap అవుతుంది; Rationalization Backlog చూడండి) | అవును | -| `check:lockfile` | `package-lock.json` సమగ్రత — https registry, integrity hashలు, host overrideలు లేవు | అవును | -| `check:licenses` | ప్రొడక్షన్ డిపెండెన్సీల కోసం SPDX లైసెన్స్ అనుమతి జాబితా | అవును | -| `check:tracked-artifacts` | బిల్డ్ ఆర్టిఫాక్ట్లు / కమిట్ చేసిన `node_modules` సింబాలిక్ లింక్లు ఉండకూడదు (husky pre-commitలో కూడా అమలవుతుంది; pre-push ఉద్దేశపూర్వకంగా తేలికగా ఉంటుంది — #6716) | అవును | -| `check:ai-attribution` | PR కమిట్లు, శీర్షిక లేదా బాడీలో AI/bot `Co-Authored-By` ట్రైలర్ లేదా AI-జనరేషన్ ఫుటర్ ఉండకూడదు — కఠిన నియమం #16 (`quality.yml`లోని PR→`release/**` ఫాస్ట్-గేట్స్ లూప్లో — ఈవెంట్ పేలోడ్ను చదువుతుంది, PRలు కానప్పుడు ఏమీ చేయదు — మరియు PR→`main` కోసం `ci.yml` లింట్లోని PR-మాత్రమే దశ; అలాగే husky `commit-msg` హుక్; మానవ సహ-రచయితలకు అనుమతి ఉంది; #14436) | -| `check:vitest-exclusions` | ప్రతి Vitest మినహాయింపులో ఒక ట్రాకింగ్ ఇష్యూ పేర్కొనబడి, అది `config/quality/vitest-exclusions.json`లో కనిపించాలి (#13204) | అవును | -| `check:file-size` | ఏ సోర్స్ ఫైల్ కూడా దాని ఎక్స్టెన్షన్కు నిర్దేశించిన గరిష్ఠ పరిమితిని మించకూడదు (రాచెట్: `frozen` జాబితాలో స్థిరపరచిన పెద్ద ఫైళ్లు) | అవును | -| `check:error-helper` | ఎగ్జిక్యూటర్లు/హ్యాండ్లర్లలోని ఎర్రర్ రెస్పాన్స్లు `buildErrorBody()` / `sanitizeErrorMessage()`ను ఉపయోగించాలి (కఠిన నియమం #12) | అవును | -| `check:migration-numbering` | Migration SQL ఫైళ్లు ఖాళీలు లేదా నకళ్లు లేకుండా వరుస సంఖ్యలతో ఉంటాయి | అవును | -| `check:public-creds` | `publicCreds.ts` వెలుపల లిటరల్ OAuth `client_id`/`client_secret` లేదా Firebase Web కీలు ఉండవు (కఠిన నియమం #11) | అవును | -| `check:db-rules` | `src/lib/db/` మాడ్యూల్స్ వెలుపల ముడి SQL ఉండదు; `localDb.ts` నుండి barrel-imports ఉండవు (కఠిన నియమాలు #2/#5) | అవును | -| `check:known-symbols` | వాటి dispatch పట్టికలలో నమోదు చేసిన provider executors, routing strategies, మరియు translators డిస్క్లోని ఫైళ్లతో సరిపోలుతాయి — అనాథ లేదా ప్రకటించని symbols ఉండవు | అవును | -| `check:route-guard-membership` | child processను ప్రారంభించే ప్రతి route, `isLocalOnlyPath()` ద్వారా వర్గీకరించబడుతుంది (కఠిన నియమాలు #15/#17) | అవును | -| `check:test-discovery` | repoలోని ప్రతి `*.test.ts` / `*.spec.ts` ఫైల్ కనీసం ఒక test runner ద్వారా సేకరించబడుతుంది (ratchet: `test-discovery-baseline.json`లోని orphan list కేవలం తగ్గగలదు) | అవును | -| `check:agent-skills-sync` | రూపొందించిన agent-skills ఆర్టిఫాక్ట్లు వాటి మూల క్యాటలాగ్తో సరిపోలుతాయి (ఎటువంటి వ్యత్యాసం లేదు) | -| `check:provider-asset-provenance` | ప్రొవైడర్ లోగోలు/అసెట్లకు నమోదు చేసిన మూలాధార ఎంట్రీ ఉంటుంది | -| `lint:json` | JSON కాన్ఫిగ్ ఫైల్లు పార్స్ అవుతాయి మరియు repo lint నియమాలను అనుసరిస్తాయి | -| `typecheck:core` | లోపాలు లేకుండా TypeScript కంపైలేషన్ (సూచనాత్మక హెచ్చరికలు మాత్రమే) | అవును | -| `typecheck:noimplicit:core` | కఠినమైన `noImplicitAny` — భవిష్యత్ను దృష్టిలో ఉంచుకున్నది; ముందే ఉన్న అనేక కాల్ సైట్లకు ఇంకా అనోటేషన్లు అవసరం | **సూచనాత్మకం** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**`కు పరిమితమైన `tsc` (#7033) — `typecheck:core` యొక్క జాగ్రత్తగా ఎంపిక చేసిన 27-ఫైల్ అనుమతి జాబితాలో ఎలాంటి dashboard TSX లేదు, అలాగే `next build` కూడా దానిని ఎప్పుడూ టైప్-చెక్ చేయదు (`next.config.mjs`లో `ignoreBuildErrors: true` సెట్ చేయబడింది), కాబట్టి అక్కడి అనాథ-ఐడెంటిఫైయర్ రిగ్రెషన్లు (#6625/#6909) CIకి కనిపించలేదు. స్థిరీకరించిన ప్రతి-ఫైల్/ప్రతి-TS-కోడ్ కౌంట్ బేస్లైన్తో (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` మాదిరిగానే అదే stale-enforcement నమూనా) తేడాలను పోలుస్తుంది — బేస్లైన్ కౌంట్కు మించిన కొత్త లోపాలు మాత్రమే గేట్ను విఫలం చేస్తాయి; ముందే ఉన్న లోపం పరిష్కరించినప్పుడు `--update`తో బేస్లైన్ను తగ్గించండి. | అవును | +Barrel re-exports మరియు dynamic imports అనేవి resolution diagnostics మాత్రమే; అవి ఎప్పటికీ +నిరోధక findingను సృష్టించవు. సమీక్షించిన మినహాయింపులు +`config/quality/forgotten-sibling-allowlist.json`లో ఉంటాయి. ప్రతి entry తప్పనిసరిగా consumer మరియు candidate +testను పేర్కొనాలి, నిర్దిష్ట కారణాన్ని ఇవ్వాలి, అలాగే GitHub issue లేదా pull requestకు link చేయాలి. సరిగ్గా రూపొందించని entries +closedగా విఫలమవుతాయి. తొలగించిన candidate testను లేదా `.skip`/`.todo`ను జోడించే diffను మినహాయింపులు అణచివేయలేవు; +assertion weakening మరియు ఇతర masking అంశాలు స్వతంత్రంగా నిరోధించే +`check:test-masking` gate పరిధిలోనే ఉంటాయి. -### జాబ్: `quality-gate` - -`test-coverage` తర్వాత నడుస్తుంది. విఫలమైతే విలీనాన్ని నిరోధిస్తుంది. - -| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధక స్థితి | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `quality:collect` | `quality-metrics.json`ను ఉత్పత్తి చేస్తుంది (ESLint హెచ్చరికల సంఖ్య, విలీనం చేసిన shard నివేదిక నుండి coverage) | అవును (ratchetకు upstream) | -| `quality:ratchet` | `quality-baseline.json`లోని ప్రతి metric దిగజారలేదు (ESLint హెచ్చరికలు ≤ baseline; coverage ≥ baseline) | అవును | -| `check:duplication` | కోడ్ నకిలీకరణ (jscpd@4) `quality-baseline.json`లోని baselineను మించదు | అవును | -| `check:complexity` | ఫైల్-స్థాయి cyclomatic complexity పరిమితిని మించదు (core ESLint `complexity` + `max-lines-per-function`) | అవును | -| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — ప్రత్యేక ESLint pass; CI రెండింటినీ ఒకే `check:complexity-ratchets` దశగా విలీనం చేసి అమలు చేస్తుంది | అవును | -| `check:dead-code` | ఉపయోగించని exports / files ratchet (knip) baselineతో పోలిస్తే దిగజారదు | అవును | -| `check:compression-budget` | Compression benchmark budget — ఒక్కో engineకు token-savings కనిష్ఠ పరిమితులు దిగజారకూడదు | అవును | -| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) దిగజారదు; ఇది ఎక్కువగా `typecheck:noimplicit:core`ను సమగ్రంగా కవర్ చేస్తుంది | అవును | -| `check:codeql-ratchet` | తెరిచి ఉన్న CodeQL alerts సంఖ్య పెరగదు (`gh api` ద్వారా చదువుతుంది; token లేకపోతే సురక్షితంగా skip చేస్తుంది) — refresh cadence మరియు manual trigger కోసం: దిగువనున్న "CodeQL ratchet" చూడండి | అవును | - -### Job: `quality-extended` - -మొత్తం job సలహాపూర్వకమైనది (`continue-on-error: true`). npm-ఆధారిత ratchets -వాస్తవంగా అమలవుతాయి; బాహ్య scanners `gh release download` ద్వారా install అవుతాయి మరియు binary -ఇంకా అందుబాటులో లేకపోతే స్వయంగా skip అవుతాయి (exit 0). - -| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధక స్థితి | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | -| `check:circular-deps` | Circular dependencies లేవు (dpdm) | **సలహాపూర్వకం** | -| `check:bundle-size` | Bundle పరిమాణం నిర్దేశిత పరిమితిని మించదు | **సలహాపూర్వకం** | -| `check:secrets` | Secret scanning (gitleaks) — binary లేకపోతే skip అవుతుంది | **సలహాపూర్వకం** | -| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) దిగజారవు — binary లేకపోతే skip అవుతుంది | **సలహాపూర్వకం** | -| `check:workflows` | Workflow lint (actionlint + zizmor); scanners లేకపోయినా/పనిచేయకపోయినా, reports చెల్లనివైనా లేదా ratchet baseline లేకపోయినా INCOMPLETEగా విఫలమవుతుంది. చెల్లుబాటు అయ్యే findings ఎంచుకున్న strict/advisory/ratchet policyని అనుసరిస్తాయి | అమలు తప్పనిసరి; CIలో zizmor ratchet నిరోధకమైనది | -| `check:openapi-breaking` | base branchతో పోలిస్తే public API contract (`openapi.yaml`)లో breaking changes (oasdiff) — `openapiBreaking=N`ను ఉత్పత్తి చేస్తుంది; oasdiff లేకపోతే లేదా base specను resolve చేయలేకపోతే skip అవుతుంది | **సలహాపూర్వకం** | - -### Job: `docs-sync-strict` +### Job: `lint` `main`కు వచ్చే ప్రతి PRపై అమలవుతుంది. విఫలమైతే mergeను నిరోధిస్తుంది. -| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధించేది | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | -| `check:docs-all` | దిగువనున్న 6 ఉప-గేట్లను వరుసగా అమలు చేసే మెటా-గేట్ | అవును | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt సంస్కరణ స్థిరత్వం | అవును | -| ↳ `check:docs-counts` | గద్యంలో ఉన్న లెక్కలు (ప్రొవైడర్ల సంఖ్య, మైగ్రేషన్ల సంఖ్య మొదలైనవి) వాస్తవ లెక్కల రాచెట్ పరిధిలో ఉంటాయి | అవును | -| ↳ `check:env-doc-sync` | `.env.example`లోని ప్రతి env var డాక్యుమెంటేషన్ పట్టికలో నమోదు చేయబడింది, అలాగే దానికి విరుద్ధంగానూ | అవును | -| ↳ `check:deprecated-versions` | డాక్యుమెంటేషన్లో కాలం చెల్లిన సంస్కరణ స్ట్రింగ్లు లేవు | అవును | -| ↳ `check:doc-links` | డాక్యుమెంటేషన్లోని అంతర్గత markdown లింక్లు వాస్తవ ఫైల్లకు పరిష్కరించబడతాయి (`[text]`/`(path)` రూపం) | అవును | -| ↳ `check:fabricated-docs` | డాక్యుమెంటేషన్లో పేర్కొన్న రూట్లు, env varలు, CLI కమాండ్లు, హుక్ పేర్లు మరియు ఫైల్ పాత్లు కోడ్బేస్లో ఉన్నాయి. `--strict` ద్వారా కఠిన గేట్; ఫ్లాగ్ లేకుండా సాఫ్ట్-ఫెయిల్. | అవును (CIలో `--strict` ద్వారా) | -| `check:cli-i18n` | CLI కమాండ్ స్ట్రింగ్లు అన్ని i18n లోకేల్ ఫైల్లలో ఉన్నాయి | అవును | -| `check:openapi-coverage` | OpenAPI స్పెక్ వాస్తవ రూట్లలో కనీసం రాచెట్ చేయబడిన కనిష్ఠ పరిమితిని కవర్ చేస్తుంది | అవును | -| `check:openapi-security-tiers` | `openapi.yaml`లోని భద్రతా స్థాయి అనోటేషన్లు `routeGuard.ts` వర్గీకరణలకు అనుగుణంగా ఉన్నాయి | **సలహాత్మకం** | -| `check:openapi-routes` | `openapi.yaml`లోని ప్రతి పాత్ వాస్తవ `route.ts`కు పరిష్కరించబడుతుంది (కల్పిత సమాచారం నిరోధం) | అవును | -| `check:docs-symbols` | `docs/**/*.md`లోని ప్రతి `/api/...` సూచన వాస్తవ `route.ts`కు పరిష్కరించబడుతుంది (కల్పిత సమాచారం నిరోధం) | అవును | -| `i18n translation drift` | i18n లోకేల్ ఫైల్లలో అనువదించని కీలు — హెచ్చరిక మాత్రమే | **సలహాత్మకం** | +| Script (`npm run ...`) | ధృవీకరించేది | నిరోధకమా | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:node-runtime` | Node.js version మద్దతు ఉన్న rangeలో ఉందో లేదో | అవును | +| `check:cycles` | `src/` + `open-sse/` అంతటా circular imports (AST-ఆధారితం, tsconfig `paths` resolve చేయబడతాయి). Bare = advisory, cyclesను జాబితా చేస్తుంది. `check:cycles:ratchet` (CI అమలు చేసేది) count, `quality-baseline.json`లోని `metrics.cycles` ceilingను మించినప్పుడు నిరోధిస్తుంది — ప్రస్తుతం 14, `direction: down`, కాబట్టి అది తగ్గడం మాత్రమే సాధ్యం (#15159 G-01/G-02) | అవును (ratchet) | +| `check:route-validation:t06` | అన్ని routesలో Zod schemas ఉన్నాయో లేదో (Tier 6 policy) | అవును | +| `check:any-budget:t11` | `@ts-expect-error // any` count budgetను మించదని (Tier 11 catraca) | అవును | +| `check:provider-consistency` | `providers.ts`లోని ప్రతి providerకు `providerRegistry.ts`లో సరిపోలే entry ఉంటుంది (మరియు దీనికి విరుద్ధంగా కూడా, allowlist పరిధిలో) | అవును | +| `check:model-lifecycle` | చేతితో నిర్వహించే మూడు routing tableలు checked-in lifecycle snapshot (#11503)తో స్థిరంగా ఉంటాయి: `FITNESS_TABLE` (`taskFitness.ts`)లో `REGISTRY` route చేయగల retired idకు score ఉండదు; ప్రతి `BUILT_IN_ALIASES` target `REGISTRY`లో ఉంటుంది మరియు retired-id snapshotలో ఉండదు; `REGISTRY`లో ఇప్పటికీ ఉన్న ప్రతి retired id forward చేయబడుతుంది లేదా `allowedRetiredInCatalog`లో జాబితా చేయబడుతుంది; అలాగే ఏ `DEFAULT_DEGRADATION_MAP` source లేదా target కూడా ఆ snapshotలో retiredగా కనిపించదు. ఒక model ప్రస్తుతం live upstream ద్వారా అందించబడుతోందని ఇది నిరూపించదు. Offline — `config/quality/model-lifecycle.json`తో పోలుస్తుంది; ఇది `npm run quality:refresh-model-lifecycle`తో చేతితో refresh చేయబడుతుంది (network; CIలో అనుసంధానించబడలేదు). `allowedRetiredInCatalog` ఒక burn-down ratchet: tracking issue ఉన్నప్పుడు మాత్రమే entryని జోడించండి. | అవును | +| `check:fetch-targets` | client-side `src/`లోని ప్రతి `fetch("/api/...")` నిజమైన `route.ts`కు resolve అవుతుంది | అవును | +| `check:deps` | repoలోని ప్రతి `package.json` అంతటా ఉన్న, `npm install` చేయగల అన్ని deps `dependency-allowlist.json`లో ఉంటాయి; కొత్త unpinned లేదా slopsquatted packageలు flag చేయబడతాయి | అవును | +| `audit:deps` | `npm audit` (root + electron) — high/critical advisoryలు లేవు (osv `check:vuln-ratchet`తో overlap అవుతుంది; Rationalization Backlog చూడండి) | అవును | +| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashలు, host overrideలు లేవు | అవును | +| `check:licenses` | ప్రొడక్షన్ డిపెండెన్సీల కోసం SPDX లైసెన్స్ అనుమతి జాబితా | అవును | +| `check:tracked-artifacts` | బిల్డ్ ఆర్టిఫాక్ట్లు / కమిట్ చేసిన `node_modules` సిమ్లింక్లు లేవు (husky pre-commitలో కూడా అమలవుతుంది; pre-push ఉద్దేశపూర్వకంగా తేలికగా ఉంటుంది — #6716) | అవును | +| `check:ai-attribution` | PR కమిట్లు, శీర్షిక లేదా బాడీలో AI/bot `Co-Authored-By` ట్రైలర్ లేదా AI-జనరేషన్ ఫుటర్ ఉండకూడదు — కఠిన నియమం #16 (`quality.yml`లోని PR→`release/**` కోసం fast-gates లూప్లో — ఈవెంట్ పేలోడ్ను చదువుతుంది, PRలు కానప్పుడు ఏమీ చేయదు — మరియు PR→`main` కోసం `ci.yml` lintలో PRకు మాత్రమే వర్తించే దశ; అలాగే husky `commit-msg` హుక్; మానవ సహ-రచయితలకు అనుమతి ఉంది; #14436) | +| `check:vitest-exclusions` | ప్రతి Vitest మినహాయింపు ఒక ట్రాకింగ్ ఇష్యూను పేర్కొని, `config/quality/vitest-exclusions.json`లో కనిపించాలి (#13204) | అవును | +| `check:file-size` | ఏ సోర్స్ ఫైల్ కూడా ప్రతి ఎక్స్టెన్షన్కు నిర్ణయించిన గరిష్ఠ పరిమితిని మించకూడదు (రాచెట్: `frozen` జాబితాలో స్థిరీకరించిన పెద్ద ఫైళ్లు) | అవును | +| `check:error-helper` | ఎగ్జిక్యూటర్లు/హ్యాండ్లర్లలోని ఎర్రర్ రెస్పాన్స్లు `buildErrorBody()` / `sanitizeErrorMessage()`ను ఉపయోగించాలి (కఠిన నియమం #12) | అవును | +| `check:migration-numbering` | Migration SQL ఫైళ్లు ఖాళీలు లేదా నకళ్లు లేకుండా వరుసగా సంఖ్యీకరించబడ్డాయి | అవును | +| `check:public-creds` | `publicCreds.ts` వెలుపల లిటరల్ OAuth `client_id`/`client_secret` లేదా Firebase Web కీలు లేవు (కఠిన నియమం #11) | అవును | +| `check:db-rules` | `src/lib/db/` మాడ్యూళ్ల వెలుపల రా SQL లేదు; `localDb.ts` నుండి బ్యారెల్ ఇంపోర్ట్లు లేవు (కఠిన నియమాలు #2/#5) | అవును | +| `check:known-symbols` | వాటి డిస్పాచ్ పట్టికల్లో నమోదు చేయబడిన ప్రొవైడర్ ఎగ్జిక్యూటర్లు, రూటింగ్ వ్యూహాలు మరియు ట్రాన్స్లేటర్లు డిస్క్లోని ఫైళ్లతో సరిపోలుతాయి — అనాథ లేదా ప్రకటించని సింబల్లు లేవు | అవును | +| `check:route-guard-membership` | చైల్డ్ ప్రాసెస్ను ప్రారంభించే ప్రతి రూట్ `isLocalOnlyPath()` ద్వారా వర్గీకరించబడింది (కఠిన నియమాలు #15/#17) | అవును | +| `check:test-discovery` | రెపోలోని ప్రతి `*.test.ts` / `*.spec.ts` ఫైల్ కనీసం ఒక టెస్ట్ రన్నర్ ద్వారా సేకరించబడుతుంది (రాచెట్: `test-discovery-baseline.json`లోని అనాథల జాబితా కేవలం తగ్గగలదు) | అవును | +| `check:agent-skills-sync` | రూపొందించబడిన agent-skills ఆర్టిఫాక్ట్లు వాటి మూల కేటలాగ్తో సరిపోలుతాయి (వ్యతాసం లేదు) | +| `check:provider-asset-provenance` | ప్రొవైడర్ లోగోలు/ఆస్తులకు నమోదు చేసిన మూలాధార ఎంట్రీ ఉంటుంది | +| `lint:json` | JSON కాన్ఫిగరేషన్ ఫైళ్లు పార్స్ అవుతాయి మరియు repo lint నియమాలను పాటిస్తాయి | +| `typecheck:core` | లోపాలు లేకుండా TypeScript కంపైలేషన్ (సలహా హెచ్చరికలు మాత్రమే) | అవును | +| `typecheck:noimplicit:core` | కఠినమైన `noImplicitAny` — భవిష్యత్-ఆధారితం; ముందే ఉన్న అనేక కాల్ సైట్లకు ఇప్పటికీ అనోటేషన్లు అవసరం | **సలహాపూర్వకం** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**`కు పరిమితమైన `tsc` (#7033) — `typecheck:core`లోని జాగ్రత్తగా ఎంపిక చేసిన 27-ఫైల్ allowlistలో ఏ dashboard TSX కూడా లేదు, అలాగే `next build` కూడా దానిని ఎప్పుడూ టైప్-చెక్ చేయదు (`next.config.mjs`లో `ignoreBuildErrors: true` సెట్ చేయబడింది), కాబట్టి అక్కడి అనాథ-ఐడెంటిఫైయర్ రిగ్రెషన్లు (#6625/#6909) CIకి కనిపించలేదు. స్థిరపరచిన ప్రతి-ఫైల్/ప్రతి-TS-కోడ్ గణన బేస్లైన్తో (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` మాదిరిగానే అదే stale-enforcement నమూనా) తేడాలను పోలుస్తుంది — బేస్లైన్లో ఉన్న గణనకు మించిన కొత్త లోపాలు మాత్రమే గేట్ను విఫలం చేస్తాయి; ముందే ఉన్న లోపం పరిష్కరించబడినప్పుడు `--update`తో పరిమితిని తగ్గించండి. | అవును | + +### జాబ్: `quality-gate` + +`test-coverage` తర్వాత రన్ అవుతుంది. విఫలమైతే విలీనాన్ని నిరోధిస్తుంది. + +| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధించేది | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `quality:collect` | `quality-metrics.json`ను ఉత్పత్తి చేస్తుంది (ESLint హెచ్చరికల సంఖ్య, విలీనం చేసిన షార్డ్ నివేదిక నుండి కవరేజ్) | అవును (రాచెట్కు అప్స్ట్రీమ్) | +| `quality:ratchet` | `quality-baseline.json`లోని ప్రతి మెట్రిక్ తిరోగమించలేదు (ESLint హెచ్చరికలు ≤ బేస్లైన్; కవరేజ్ ≥ బేస్లైన్) | అవును | +| `check:duplication` | కోడ్ నకిలీకరణ (jscpd@4) `quality-baseline.json`లోని బేస్లైన్ను మించదు | అవును | +| `check:complexity` | ఫైల్-స్థాయి సైక్లోమాటిక్ సంక్లిష్టత పరిమితిని మించదు (కోర్ ESLint `complexity` + `max-lines-per-function`) | అవును | +| `check:cognitive-complexity` | కాగ్నిటివ్ సంక్లిష్టత రాచెట్ (`eslint-plugin-sonarjs`) — ప్రత్యేక ESLint పాస్; CI రెండింటినీ కలిపి ఒకే `check:complexity-ratchets` దశగా అమలు చేస్తుంది | అవును | +| `check:dead-code` | ఉపయోగించని ఎక్స్పోర్ట్లు / ఫైళ్ల రాచెట్ (knip) బేస్లైన్తో పోలిస్తే తిరోగమించదు | అవును | +| `check:compression-budget` | కంప్రెషన్ బెంచ్మార్క్ బడ్జెట్ — ఒక్కో ఇంజిన్కు సంబంధించిన టోకెన్-పొదుపు కనిష్ఠ పరిమితులు తిరోగమించకూడదు | అవును | +| `check:type-coverage` | టైప్ చేయబడిన శాతం రాచెట్ (`type-coverage`) తిరోగమించదు; ఇది ఎక్కువగా `typecheck:noimplicit:core`ను కలుపుకుంటుంది | అవును | +| `check:codeql-ratchet` | తెరిచి ఉన్న CodeQL అలర్ట్ల సంఖ్య తిరోగమించదు (`gh api` ద్వారా చదువుతుంది; టోకెన్ లేకపోతే సురక్షితంగా దాటవేస్తుంది) — రిఫ్రెష్ అవధి మరియు మాన్యువల్ ట్రిగ్గర్ కోసం: దిగువ "CodeQL రాచెట్" చూడండి | అవును | + +### జాబ్: `quality-extended` + +మొత్తం జాబ్ సలహాపూర్వకమైనది (`continue-on-error: true`). npm-ఆధారిత రాచెట్లు +వాస్తవంగా అమలవుతాయి; బాహ్య స్కానర్లు `gh release download` ద్వారా ఇన్స్టాల్ అవుతాయి మరియు +బైనరీ ఇంకా లేకుంటే స్వయంగా దాటవేస్తాయి (exit 0). + +| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధించేది | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| `check:circular-deps` | సర్క్యులర్ డిపెండెన్సీలు లేవు (dpdm) | **సలహాపూర్వకం** | +| `check:bundle-size` | బండిల్ పరిమాణం గరిష్ఠ పరిమితిని మించదు | **సలహాపూర్వకం** | +| `check:secrets` | సీక్రెట్ స్కానింగ్ (gitleaks) — బైనరీ లేకుంటే దాటవేస్తుంది | **సలహాపూర్వకం** | +| `check:vuln-ratchet` | డిపెండెన్సీ దుర్బలతలు (osv-scanner) తిరోగమించవు — బైనరీ లేకుంటే దాటవేస్తుంది | **సలహాపూర్వకం** | +| `check:workflows` | వర్క్ఫ్లో లింట్ (actionlint + zizmor); స్కానర్లు లేకపోయినా/పాడైనా, నివేదికలు చెల్లనివైనా లేదా రాచెట్ బేస్లైన్ లేకపోయినా INCOMPLETEగా విఫలమవుతుంది. చెల్లుబాటు అయ్యే ఫైండింగ్లు ఎంచుకున్న కఠిన/సలహాపూర్వక/రాచెట్ విధానాన్ని అనుసరిస్తాయి | అమలు తప్పనిసరి; CIలో zizmor రాచెట్ నిరోధిస్తుంది | +| `check:openapi-breaking` | బేస్ బ్రాంచ్తో పోలిస్తే పబ్లిక్ API ఒప్పందానికి (`openapi.yaml`) బ్రేకింగ్ మార్పులు (oasdiff) — `openapiBreaking=N`ను ఉత్పత్తి చేస్తుంది; oasdiff లేకుంటే లేదా బేస్ స్పెక్ను పరిష్కరించలేకపోతే దాటవేస్తుంది | **సలహాపూర్వకం** | + +### జాబ్: `docs-sync-strict` + +`main`కు వచ్చే ప్రతి PRపై అమలవుతుంది. వైఫల్యం సంభవిస్తే విలీనాన్ని నిరోధిస్తుంది. + +| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధకమా | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `check:docs-all` | దిగువనున్న 6 ఉప-గేట్లను వరుసగా అమలు చేసే మెటా-గేట్ | అవును | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt సంస్కరణ అనుగుణ్యత | అవును | +| ↳ `check:docs-counts` | గద్యంలోని సంఖ్యలు (ప్రొవైడర్ల సంఖ్య, మైగ్రేషన్ల సంఖ్య మొదలైనవి) వాస్తవ సంఖ్యల రాచెట్ పరిధిలో ఉన్నాయి | అవును | +| ↳ `check:env-doc-sync` | `.env.example`లోని ప్రతి env var docs పట్టికలో డాక్యుమెంట్ చేయబడింది, అలాగే దానికి విరుద్ధంగా కూడా | అవును | +| ↳ `check:deprecated-versions` | docsలో విరమించబడిన సంస్కరణ స్ట్రింగ్లు లేవు | అవును | +| ↳ `check:doc-links` | docsలోని అంతర్గత markdown లింక్లు వాస్తవ ఫైల్లకు పరిష్కరించబడతాయి (`[text]`/`(path)` రూపం) | అవును | +| ↳ `check:fabricated-docs` | docsలో పేర్కొన్న రూట్లు, env vars, CLI కమాండ్లు, hook పేర్లు, ఫైల్ పాత్లు codebaseలో ఉన్నాయి. `--strict` ద్వారా కఠిన గేట్; ఫ్లాగ్ లేకుండా సాఫ్ట్-ఫెయిల్. | అవును (CIలో `--strict` ద్వారా) | +| `check:cli-i18n` | CLI కమాండ్ స్ట్రింగ్లు అన్ని i18n locale ఫైల్లలో ఉన్నాయి | అవును | +| `check:openapi-coverage` | OpenAPI స్పెక్ వాస్తవ రూట్లలో కనీసం రాచెట్ చేయబడిన కనిష్ఠ స్థాయిని కవర్ చేస్తుంది | అవును | +| `check:openapi-security-tiers` | `openapi.yaml`లోని భద్రతా స్థాయి annotationలు `routeGuard.ts` వర్గీకరణలతో అనుగుణంగా ఉన్నాయి | **సలహాత్మకం** | +| `check:openapi-routes` | `openapi.yaml`లోని ప్రతి పాత్ వాస్తవ `route.ts`కు పరిష్కరించబడుతుంది (భ్రమాత్మక సమాచార నిరోధం) | అవును | +| `check:docs-symbols` | `docs/**/*.md`లోని ప్రతి `/api/...` సూచన వాస్తవ `route.ts`కు పరిష్కరించబడుతుంది (భ్రమాత్మక సమాచార నిరోధం) | అవును | +| `i18n translation drift` | i18n locale ఫైల్లలో అనువదించని keyలు — హెచ్చరిక మాత్రమే | **సలహాత్మకం** | ### జాబ్: `i18n-ui-coverage` -| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధించేది | -| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `check-ui-keys-coverage` (ఇన్లైన్) | UI i18n కీ కవరేజ్ ≥ 65% | అవును | -| `check-ui-value-drift` (ఇన్లైన్) | తిరిగి వ్రాయబడిన ఆంగ్ల **విలువ** పాత అనువాదం ఏదీ మిగలకుండా చూస్తుంది | అవును | -| `check-new-key-coverage` (ఇన్లైన్) | ఒక **కొత్త** ఆంగ్ల కీ ప్రతి లోకేల్లో అనువదించబడుతుంది — `__MISSING__:` మార్కర్ తిరస్కరించబడుతుంది | అవును | -| `check-translation-ratio` | ప్రతి లోకేల్కు వాస్తవ-అనువాద నిష్పత్తి (అనుమతి జాబితా వెలుపల ఆంగ్లంతో సమానమైన / ప్లేస్హోల్డర్ / తప్పిపోయిన లీవ్లు) `config/quality/i18n-translation-baseline.json` + సడలింపును మించకూడదు | **సలహాత్మకం** | +| స్క్రిప్ట్ | ధృవీకరించేది | నిరోధకమా | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `check-ui-keys-coverage` (inline) | UI i18n key కవరేజ్ ≥ 65% | అవును | +| `check-ui-value-drift` (inline) | తిరిగి రాసిన ఆంగ్ల **విలువ** పాత అనువాదాన్ని ఏదీ మిగలనివ్వదు | అవును | +| `check-new-key-coverage` (inline) | ఒక **కొత్త** ఆంగ్ల key ప్రతి localeలో అనువదించబడుతుంది — `__MISSING__:` మార్కర్ తిరస్కరించబడుతుంది | అవును | +| `check-translation-ratio` | ప్రతి localeకు వాస్తవ-అనువాద నిష్పత్తి (allowlist వెలుపల ఆంగ్లంతో సమానమైన / placeholder / లేని leafలు) `config/quality/i18n-translation-baseline.json` + slackను మించకూడదు | **సలహాత్మకం** | -`fetch-depth: 0` అవసరం — విలువ-డ్రిఫ్ట్ గేట్ `en.json`ను మెర్జ్ బేస్తో పోల్చి తేడాలను గుర్తిస్తుంది. +`fetch-depth: 0` అవసరం — value-drift గేట్ merge baseతో `en.json`ను diff చేస్తుంది. #### `check-ui-value-drift` — పాత-అనువాద గేట్ -ఇతర గేట్లు నిర్మాణాత్మకంగా గుర్తించలేని ఒక i18n రిగ్రెషన్ను ఇది పట్టుకుంటుంది: ఒక ఆంగ్ల విలువ -తిరిగి వ్రాయబడినా, _మునుపటి_ ఆంగ్లం ఆధారంగా రూపొందించిన అనువాదాలు అలాగే మిగిలిపోతాయి, అందువల్ల -ఆంగ్లేతర వినియోగదారులు ధీమాగా వ్రాయబడిన, ఇప్పుడు తప్పుగా ఉన్న ప్రతిని చదువుతూనే ఉంటారు. +ఇతర గేట్లు నిర్మాణాత్మకంగా గుర్తించలేని ఒక i18n regressionను ఇది పట్టుకుంటుంది: ఒక ఆంగ్ల విలువ +తిరిగి రాయబడినా, _మునుపటి_ ఆంగ్లం ఆధారంగా రూపొందించిన అనువాదాలు అలాగే మిగిలిపోతాయి, అందువల్ల +ఆంగ్లేతర వినియోగదారులు నమ్మకంగా రాసినప్పటికీ ఇప్పుడు తప్పుగా ఉన్న పాఠ్యాన్నే చదువుతూ ఉంటారు. -ఇది నిజంగానే విడుదలైంది. Antigravity లాగిన్ సహాయకం వచ్చినప్పుడు (#5203) -`oauthModal.googleOAuthWarning` తిరిగి వ్రాయబడింది; **43 లోకేల్లలో 39** ఆపరేటర్లకు "పూర్తి -URLను కాపీ చేసి దిగువ అతికించండి" అని చెప్పే వచనాన్ని అలాగే ఉంచాయి — ఆ ప్రొవైడర్ కోసం పూర్తి -చేయలేని ప్రవాహం అది. #8463 వరకు ఇది గుర్తించబడలేదు, ఎందుకంటే: +ఇది నిజంగానే విడుదలైంది. Antigravity login helper వచ్చినప్పుడు (#5203) +`oauthModal.googleOAuthWarning` తిరిగి రాయబడింది; **43 localeలలో 39**లో ఆపరేటర్లకు "పూర్తి +URLను కాపీ చేసి దిగువన పేస్ట్ చేయండి" అని చెప్పే పాఠ్యం అలాగే ఉండిపోయింది — ఆ ప్రొవైడర్కు పూర్తి +చేయలేని ఫ్లో అది. #8463 వరకు ఇది గుర్తించబడలేదు, ఎందుకంటే: -- `sync-ui-keys` **లేని** కీలను మాత్రమే బ్యాక్ఫిల్ చేస్తుంది, **పాతబడిన** వాటిని ఎప్పుడూ చేయదు; -- `check-ui-keys-coverage` కీ _ఉనికిని_ లెక్కిస్తుంది, కాబట్టి పాత అనువాదం కవర్ అయినట్లుగా స్కోర్ పొందుతుంది; -- `check-translation-drift` అనేది `docs/i18n//**.md` డాక్యుమెంటేషన్ మిర్రర్లను ట్రాక్ చేస్తుంది — - అది `src/i18n/messages/*.json`ను ఎప్పుడూ చదవదు. 2026-09 రీ-సింక్ నుండి `docs-sync-strict` జాబ్లో నిరోధిస్తోంది: - ప్రధాన డాక్ను సవరించండి → `npm run i18n:run -- --files=` (విభాగ-స్థాయి, చవకైనది). +- `sync-ui-keys` **లేని** keyలను మాత్రమే backfill చేస్తుంది, **పాతబడిన** వాటిని ఎప్పుడూ చేయదు; +- `check-ui-keys-coverage` key _ఉనికిని_ లెక్కిస్తుంది, కాబట్టి పాత అనువాదం కవర్ అయినట్లుగా స్కోర్ పొందుతుంది; +- `check-translation-drift` అనేది `docs/i18n//**.md` డాక్యుమెంటేషన్ mirrorలను ట్రాక్ చేస్తుంది — + అది `src/i18n/messages/*.json`ను ఎప్పుడూ చదవదు. 2026-09 re-sync నుండి `docs-sync-strict` జాబ్లో నిరోధకంగా ఉంది: + ప్రధాన docను సవరించండి → `npm run i18n:run -- --files=` (విభాగ-స్థాయి, తక్కువ ఖర్చుతో కూడినది). -**Diff-aware, baseline-backed కాదు.** ఇది merge base వద్ద ఉన్న `en.json`ను working treeతో పోలుస్తుంది; ఆంగ్ల విలువ మారిన ప్రతి keyకి, ఇంకా మార్పులేని అనువాదాన్ని కలిగి ఉన్న ఏ locale అయినా staleగా పరిగణించబడుతుంది. ఇది ఉద్దేశపూర్వకంగా **ఇప్పటికే ఉన్న రుణాన్ని స్థిరపరుస్తుంది** — చాలా కాలంగా ఉన్న అనువాదం ఏ పాత ఆంగ్లం నుంచి వచ్చిందో diff వెల్లడించలేదు, కాబట్టి gate ప్రస్తుత మార్పు తాకిన వాటిని మాత్రమే పరిశీలిస్తుంది. ప్రత్యామ్నాయం (ప్రతి keyకి ఒక hash baseline) కోసం ~600 KB generated file అవసరమవుతుంది; ఇది ప్రస్తుతం ఉన్న అతిపెద్ద baseline కంటే 3× పెద్దది, అలాగే ప్రతి i18n PRలో మార్పులకు లోనవుతుంది. +**Diff-aware, baseline-backed కాదు.** ఇది merge base వద్ద ఉన్న `en.json`ను +working treeతో పోలుస్తుంది; ఇంగ్లీష్ విలువ మారిన ప్రతి key విషయంలో, ఇప్పటికీ మార్పు చేయని +అనువాదాన్ని కలిగి ఉన్న ఏ locale అయినా staleగా పరిగణించబడుతుంది. ఇది ఉద్దేశపూర్వకంగా **ముందే ఉన్న బకాయిని స్థిరపరుస్తుంది** — దీర్ఘకాలంగా ఉన్న అనువాదం ఏ పాత ఇంగ్లీష్ నుండి వచ్చిందో diff వెల్లడించలేదు, కాబట్టి gate +ప్రస్తుత మార్పు ప్రభావితం చేసిన వాటినే అంచనా వేస్తుంది. ప్రత్యామ్నాయమైన (ప్రతి-key hash baseline) కోసం +~600 KB generated file అవసరమవుతుంది; ఇది ప్రస్తుతం ఉన్న అతిపెద్ద baseline కంటే 3× పెద్దది, అలాగే ప్రతి i18n PRలో మారుతూ ఉంటుంది. -దీన్ని సంతృప్తిపరచడానికి రెండు మార్గాలు: +దీనిని సంతృప్తిపరచడానికి రెండు మార్గాలు: 1. ప్రభావితమైన అనువాదాలను నవీకరించండి, లేదా -2. వాటిని `__MISSING__:`గా సెట్ చేయండి — అప్పుడు runtime సరిచేసిన ఆంగ్లాన్ని అందిస్తుంది +2. వాటిని `__MISSING__:`గా సెట్ చేయండి — అప్పుడు runtime సరిచేసిన ఇంగ్లీష్ను అందిస్తుంది (`src/i18n/request.ts::deepMergeFallback`, #7258), అలాగే ఆ key అనువాదం కోసం queueలో చేరుతుంది. -string యొక్క **అర్థం** మారితే, **key పేరును మార్చడానికే** ప్రాధాన్యం ఇవ్వండి: కొత్త key stale అనువాదాన్ని వారసత్వంగా పొందలేదు. #8463 ఉపయోగించిన నమూనా ఇదే. +string యొక్క **అర్థం** మారితే, **key పేరు మార్చడానికి** ప్రాధాన్యం ఇవ్వండి: కొత్త key stale అనువాదాన్ని +వారసత్వంగా పొందలేదు. #8463 ఉపయోగించిన pattern ఇదే. ```bash -npm run i18n:check-value-drift # కఠినం (CI అమలు చేసేది) +npm run i18n:check-value-drift # కఠినమైనది (CI అమలు చేసేది) npm run i18n:check-value-drift:warn # నివేదిక మాత్రమే BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -base catalogను చదవలేనప్పుడు (base ref లేని shallow clone), `check-openapi-breaking`ను అనుసరిస్తూ `SKIP reason=base-unresolved`తో 0గా నిష్క్రమిస్తుంది. +base catalogను చదవలేనప్పుడు (base ref లేని shallow +clone), `check-openapi-breaking`ను అనుకరిస్తూ `SKIP reason=base-unresolved`తో 0 exit అవుతుంది. ### Job: `i18n` -పూర్తి i18n ధ్రువీకరణ matrix (ప్రతి localeకు ఒక job). మొత్తం job సలహాత్మకమైనది. +పూర్తి i18n validation matrix (ప్రతి localeకు ఒక job). మొత్తం job advisory మాత్రమే. -| Script | ధ్రువీకరించేది | నిరోధించేది | -| ------------------------------- | ------------------------------ | ------------------------------------------------------ | -| `validate_translation.py quick` | ప్రతి localeకు అనువాద సంపూర్ణత | **సలహాత్మకం** (మొత్తం jobపై `continue-on-error: true`) | +| Script | ధృవీకరించేది | Blocking | +| ------------------------------- | ------------------------------ | ----------------------------------------------------- | +| `validate_translation.py quick` | ప్రతి localeలో అనువాద సంపూర్ణత | **Advisory** (మొత్తం jobపై `continue-on-error: true`) | ### Job: `pr-test-policy` pull requestలపై మాత్రమే అమలవుతుంది. -| Script | ధ్రువీకరించేది | నిరోధించేది | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, లేదా `bin/`లోని production codeను మార్చే PRలు తప్పనిసరిగా testలను చేర్చాలి లేదా నవీకరించాలి (Hard Rule #8) | అవును | -| `check:test-masking` | మార్చిన test fileలు మొత్తం assert సంఖ్యను తగ్గించవు లేదా `assert.ok(true)` tautologyలను జోడించవు | అవును | -| `check:pr-evidence` | PR bodyలో మార్పుకు సంబంధించిన test/VPS ఆధారాలు పేర్కొనాలి (PR proseలో grep చేయడం ద్వారా Hard Rule #18ను యాంత్రీకరిస్తుంది — సున్నితమైనది, Backlog చూడండి) | అవును | +| Script | ధృవీకరించేది | Blocking | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`, లేదా `bin/`లో production codeను మార్చే PRలు తప్పనిసరిగా testలను చేర్చాలి లేదా నవీకరించాలి (Hard Rule #8) | అవును | +| `check:test-masking` | మార్చిన test fileలు మొత్తం assert countను తగ్గించవు లేదా `assert.ok(true)` tautologyలను జోడించవు | అవును | +| `check:pr-evidence` | మార్పు కోసం PR bodyలో test/VPS ఆధారాలను ప్రస్తావించాలి (PR proseను grep చేయడం ద్వారా Hard Rule #18ను యాంత్రీకరిస్తుంది — ఇది సున్నితమైనది, Backlog చూడండి) | అవును | ### Job: `test-vitest` `build` తర్వాత అమలవుతుంది. విఫలమైతే mergeను నిరోధిస్తుంది. -| Suite | ధ్రువీకరించేది | నిరోధించేది | -| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | అవును | -| `test:vitest:ui` | UI component testలు — vitest runner | **నిరోధిస్తుంది** — ఇప్పటికే ఉన్న వైఫల్యాలు `vitest.config.ts`లో స్పష్టంగా మినహాయించబడ్డాయి; కొత్త వైఫల్యాలు jobను విఫలం చేస్తాయి | +| Suite | ధృవీకరించేది | Blocking | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | అవును | +| `test:vitest:ui` | UI component testలు — vitest runner | **Blocking** — ముందే ఉన్న failureలు `vitest.config.ts`లో స్పష్టంగా మినహాయించబడ్డాయి; కొత్త failureలు jobను విఫలం చేస్తాయి | -### Nightly workflowలు (షెడ్యూల్ చేయబడినవి, సలహాత్మకమైనవి) +### Nightly workflowలు (షెడ్యూల్ చేయబడినవి, advisory) -ఇవి cron scheduleలో (మరియు `workflow_dispatch` ద్వారా) అమలవుతాయి, PRలపై ఎప్పుడూ అమలవవు. అన్నీ సలహాత్మకమైనవే. +ఇవి cron scheduleలో (మరియు `workflow_dispatch` ద్వారా) అమలవుతాయి, PRలపై ఎప్పుడూ అమలవవు. అన్నీ advisory మాత్రమే. -| Workflow | ధ్రువీకరించేది | నిరోధించేది | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `nightly-property` | random seed + అధిక run countతో fast-check property testలు | **సలహాత్మకం** | -| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **సలహాత్మకం** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probeలు (provider secret లేకుండా దాటవేయబడతాయి) | **సలహాత్మకం** | -| `nightly-schemathesis` | `docs/openapi.yaml`ను ఉపయోగించి live OmniRouteపై OpenAPI contract fuzzing (schemathesis) — spec ఉల్లంఘనలు / నిర్వహించని 500లను బయటపెడుతుంది (Fase 8 B.4) | **సలహాత్మకం** | -| `nightly-mutation` | వేగవంతమైన unit laneపై Stryker mutation-testing score — మిగిలిపోయిన mutantలు బలహీనమైన assertలను బయటపెడతాయి | **సలహాత్మకం** | -| `nightly-compat` | మద్దతు ఉన్న `engines.node` rangeలన్నింటిలో Node engine compatibility matrix | **సలహాత్మకం** | +| Workflow | ధృవీకరించేది | Blocking | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `nightly-property` | random seed + అధిక run countతో fast-check property testలు | **Advisory** | +| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Advisory** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probeలు (provider secret లేకపోతే skip చేయబడతాయి) | **Advisory** | +| `nightly-schemathesis` | `docs/openapi.yaml`ను ఉపయోగించి live OmniRouteపై OpenAPI contract fuzzing (schemathesis) — spec ఉల్లంఘనలు / నిర్వహించని 500లను వెలికితీస్తుంది (Fase 8 B.4) | **Advisory** | +| `nightly-mutation` | వేగవంతమైన unit laneపై Stryker mutation-testing score — మనుగడలో ఉన్న mutantలు బలహీనమైన assertలను వెలికితీస్తాయి | **Advisory** | +| `nightly-compat` | మద్దతు ఉన్న `engines.node` rangeల అంతటా Node engine compatibility matrix | **Advisory** | --- @@ -534,41 +540,41 @@ TypeScript లాంగ్వేజ్ సర్వర్ను అందుబ ## హేతుబద్ధీకరణ బ్యాక్లాగ్ (ROI సమీక్ష — దశ 9 తరంగం 3) -ఈ జాబితాను 2026-06-17న `ci.yml`తో సరిచూశారు (మునుపటి సంస్కరణలో +ఈ ఇన్వెంటరీని 2026-06-17న `ci.yml`తో సరిపోల్చి సరిచేశారు (మునుపటి సంస్కరణలో `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` చేర్చబడలేదు). సరిచూసిన సమితిపై నిర్వహించిన ROI సమీక్ష -క్రింది హేతుబద్ధీకరణ అభ్యర్థులను గుర్తించింది. **విలీనాలు యాంత్రిక CI -మార్పులు; మార్పిడి/తొలగింపు నిర్ణయాలు ఆపరేటర్కు కేటాయించిన విధానపరమైన నిర్ణయాలు.** దిగువన ఉన్నవేవీ +`check:codeql-ratchet`, `check:pr-evidence` విస్మరించబడ్డాయి). సరిపోల్చిన సమితిపై నిర్వహించిన ROI సమీక్ష +కింది హేతుబద్ధీకరణ అభ్యర్థులను గుర్తించింది. **విలీనాలు యాంత్రిక CI +మార్పులు; మార్చడం/తొలగించడం అనేవి ఆపరేటర్కు కేటాయించిన విధానపరమైన నిర్ణయాలు.** దిగువన ఉన్నవేవీ ఇంకా వర్తింపజేయబడలేదు. **పైన డాక్యుమెంట్ చేయనివి కూడా ఉన్నాయి** (సలహాపూర్వకమైనవి, తక్కువ సంకేతం): `docs-lint` జాబ్ (markdownlint + Vale, మొత్తం జాబ్కు `continue-on-error`) మరియు స్వతంత్ర స్కానర్ వర్క్ఫ్లోలు -`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` అనేది -`quality-baseline.json`లో ఉంది, కానీ `ci.yml`లో బ్లాకింగ్ రాచెట్కు అనుసంధానించబడలేదు — ఈ మెట్రిక్ -ప్రస్తుతం అనాథగా ఉంది. +`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `quality-baseline.json`లో +`semgrepFindings: 0` ఉంది, కానీ `ci.yml`లోని బ్లాకింగ్ రాచెట్కు అది అనుసంధానించబడలేదు — ప్రస్తుతం ఈ మెట్రిక్కు +ఏ అనుసంధానమూ లేదు. ### విలీనం / నకిలీల తొలగింపు (యాంత్రికం, తక్కువ ప్రమాదం) -ప్రతి అభ్యర్థిని 2026-06-17న అమలులో ఉన్న గేట్ స్థితితో పోల్చి ధృవీకరించారు (నమ్మండి-కానీ-ధృవీకరించండి); -"స్పష్టమైనవి"గా కనిపించిన అనేక విలీనాలు వాస్తవానికి సాంకేతిక ఋణాన్ని దాచిపెట్టాయని తేలింది మరియు అవి **శుభ్రమైన** ప్రత్యామ్నాయాలు కావు. +ప్రతి అభ్యర్థినీ 2026-06-17న ప్రత్యక్ష గేట్ స్థితితో పోల్చి ధ్రువీకరించారు (నమ్మండి-కానీ-ధ్రువీకరించండి); +"స్పష్టమైనవి" అనిపించిన అనేక విలీనాలు వాస్తవానికి సాంకేతిక రుణాన్ని దాచిపెట్టాయని తేలింది, కాబట్టి అవి **శుభ్రమైన** ప్రత్యామ్నాయాలు కావు. -- **`check:docs-sync` రెండుసార్లు నడుస్తుంది** — `lint` జాబ్లో స్వతంత్రంగా, అలాగే `check:docs-all` (`docs-sync-strict`) లోపల మరియు husky ప్రీ-కమిట్ హుక్లో మరోసారి. ✅ **పూర్తయింది** — స్వతంత్ర `lint` అమలు తొలగించబడింది. -- **CVE స్కానింగ్** — ❌ **శుభ్రమైన విలీనం కాదు.** ఏదైనా అధిక/క్రిటికల్ CVE ఉంటే `audit:deps` పూర్తిగా విఫలమవుతుంది; `check:vuln-ratchet` (osv) మాత్రం బేస్లైన్తో పోలిస్తే _తిరోగమనం_ ఉన్నప్పుడు మాత్రమే విఫలమవుతుంది (ప్రస్తుతం 1 MODERATE). అర్థాలు వేర్వేరు — `audit:deps`ను తొలగిస్తే సంపూర్ణ అధిక/క్రిటికల్ గేట్ పోతుంది. రెండింటినీ ఉంచండి. -- **సైకిల్ గుర్తింపు** — ❌ **శుభ్రమైన విలీనం కాదు.** `check:circular-deps` (dpdm) **91 సైకిళ్లను** నివేదిస్తుంది (అందుకే ఇది సలహాపూర్వకంగా ఉంది); మొదట వాటిని పరిష్కరించకుండా దీన్ని బ్లాకింగ్గా మార్చలేరు, అలాగే ఇది విజయవంతంగా ఉన్న, ఎంపిక చేసిన `check:cycles` కంటే విస్తృత పరిధిని కలిగి ఉంది. `check:cycles`ను బ్లాకింగ్గా ఉంచండి; 91 dpdm సైకిళ్ల పరిష్కారం ప్రత్యేక బ్యాక్లాగ్ అంశం. -- **సంక్లిష్టత** — ✅ **పూర్తయింది** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ఒక ESLint వాక్, ruleId ఆధారంగా లెక్కిస్తుంది కాబట్టి సైక్లోమాటిక్+గరిష్ఠ-లైన్లు మరియు కాగ్నిటివ్ బేస్లైన్లు స్వతంత్రంగా ఉంటాయి; స్థానిక `--update` కోసం విడివిడిగా ఉన్న `check:complexity` / `check:cognitive-complexity` అలాగే కొనసాగుతాయి. -- **`/api` యాంటీ-హాల్యూసినేషన్** — ✅ **పూర్తయింది** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` కోసం ఒక FS ఇన్వెంటరీ, openapi-routes + docs-symbols ఇప్పటికీ స్వతంత్రంగా నివేదిస్తాయి; స్థానిక అమలుల కోసం విడివిడిగా ఉన్నవి అలాగే కొనసాగుతాయి. -- **`check:node-runtime` 11 జాబ్లలో నడుస్తుంది** — ⚠️ **తక్కువ ROI.** ప్రతి ఒక్కటీ ప్రత్యేక రన్నర్ మరియు ఈ తనిఖీ <1s; చవకైన ప్రతి-జాబ్ రక్షణను కోల్పోవడంతో పోలిస్తే మొత్తం ఆదా ~10s మాత్రమే. ఈ మార్పుల కలకలం విలువైనది కాదు. -- **CI lintలో `typecheck:noimplicit:core`** — ✅ **lint జాబ్ నుండి తొలగించబడింది** (ఇది సలహాపూర్వక `continue-on-error`గా ఉండేది); బ్లాకింగ్ టైప్ ఉపరితలం `typecheck:core` + `check:type-coverage`. స్థానిక స్క్రిప్ట్ కొనసాగించబడింది. +- **`check:docs-sync` రెండుసార్లు నడుస్తుంది** — `lint` జాబ్లో స్వతంత్రంగా, అలాగే `check:docs-all` (`docs-sync-strict`)లో మరియు husky pre-commit హుక్లో మరోసారి. ✅ **పూర్తయింది** — స్వతంత్ర `lint` అమలు తొలగించబడింది. +- **CVE స్కానింగ్** — ❌ **శుభ్రమైన విలీనం కాదు.** ఏదైనా అధిక/క్రిటికల్ CVE ఉంటే `audit:deps` పూర్తిగా విఫలమవుతుంది; `check:vuln-ratchet` (osv) మాత్రం బేస్లైన్తో పోలిస్తే _తిరోగమనం_ ఉన్నప్పుడు మాత్రమే విఫలమవుతుంది (ప్రస్తుతం 1 MODERATE). అర్థాలు వేర్వేరు — `audit:deps`ను తొలగిస్తే సంపూర్ణ అధిక/క్రిటికల్ గేట్ కోల్పోతాం. రెండింటినీ ఉంచండి. +- **సైకిల్ గుర్తింపు** — ✅ **పూర్తయింది** (#15159 G-01/G-02). ఇక్కడి పాత పాఠ్యం `check:cycles`ను "గ్రీన్, జాగ్రత్తగా ఎంపిక చేసిన" గేట్గా పేర్కొని, `check:circular-deps` (dpdm) 91 సైకిళ్లను నివేదించినందున దానిని బ్లాకింగ్గా ఉంచడాన్ని సమర్థించింది. ఆ గ్రీన్ ఒక **తప్పుడు గ్రీన్**: `check:cycles` 5 ఉపడైరెక్టరీలను (450 ఫైళ్లు) స్కాన్ చేసింది, స్థిర `import|export … from`తో మాత్రమే సరిపోల్చింది, అలాగే ప్రతి `@/` మరియు `@omniroute/open-sse/` స్పెసిఫైయర్ను వదిలేసింది; అందువల్ల రిపోలో అధికంగా ఉన్న డైనమిక్-import + అలియాస్ సైకిళ్లను అది చూడలేకపోయింది. పరిష్కరించబడింది: ఇప్పుడు గేట్ `src` + `open-sse`లో సంచరిస్తుంది (5023 ఫైళ్లు), TypeScript AST నుంచి స్పెసిఫైయర్లను సేకరిస్తుంది (కాబట్టి `import("…")` లెక్కించబడుతుంది, కానీ టైప్-స్థానంలోని `typeof import("…")` లెక్కించబడదు), అలాగే tsconfig `paths`ను పరిష్కరిస్తుంది. ఇది 0 కాకుండా **14** సైకిళ్లను కనుగొంటుంది. ముందే ఉన్న 14 సైకిళ్లను గేట్ PRలో పరిష్కరించడం సాధ్యం కానందున, `check:cycles` ఇప్పుడు ఒక **రాచెట్** (`--ratchet`, `quality-baseline.json`లో పరిమితి `metrics.cycles.value = 14`, `direction: down`) — ఇది ఏదైనా _తిరోగమనాన్ని_ బ్లాక్ చేస్తుంది, కౌంట్ తగ్గడం మాత్రమే సాధ్యమవుతుంది. CI `npm run check:cycles:ratchet`ను నడుపుతుంది. తగ్గింపు ప్రక్రియ **A-01**తో కలిసి కొనసాగుతుంది. విస్తృతమైన రెండో అభిప్రాయంగా `check:circular-deps` (dpdm) సలహాపూర్వకంగానే ఉంటుంది. +- **సంక్లిష్టత** — ✅ **పూర్తయింది** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ఒక ESLint వాక్, ruleId ఆధారంగా లెక్కిస్తుంది కాబట్టి cyclomatic+max-lines మరియు cognitive బేస్లైన్లు స్వతంత్రంగా ఉంటాయి; స్థానిక `--update` కోసం విడివిడిగా ఉన్న `check:complexity` / `check:cognitive-complexity` అలాగే ఉంటాయి. +- **`/api` యాంటీ-హాల్యూసినేషన్** — ✅ **పూర్తయింది** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api`కు ఒకే FS ఇన్వెంటరీ, openapi-routes + docs-symbols ఇప్పటికీ విడివిడిగా నివేదిస్తాయి; స్థానిక అమలుల కోసం విడివిడిగా ఉన్నవి అలాగే ఉంటాయి. +- **`check:node-runtime` 11 జాబ్లలో నడుస్తుంది** — ⚠️ **తక్కువ ROI.** ప్రతి ఒక్కటీ ప్రత్యేక రన్నర్, అలాగే చెక్ <1s; చౌకైన ప్రతి-జాబ్ రక్షణను కోల్పోవడంతో పోలిస్తే మొత్తం ఆదా ~10s మాత్రమే. ఈ మార్పుల కలకలం విలువైనది కాదు. +- **CI lintలో `typecheck:noimplicit:core`** — ✅ **lint జాబ్ నుంచి తొలగించబడింది** (ఇది సలహాపూర్వక `continue-on-error`గా ఉండేది); బ్లాకింగ్ టైప్ సర్ఫేస్ `typecheck:core` + `check:type-coverage`. స్థానిక స్క్రిప్ట్ అలాగే ఉంచబడింది. ### మార్చడం / నిర్ణయించడం (ఆపరేటర్ విధానం) -- `check:openapi-security-tiers` (సలహాపూర్వకం) — ❌ **నేరుగా మార్చడం సాధ్యం కాదు.** ఇది 0తో నిష్క్రమిస్తుంది, కానీ `LOCAL_ONLY_API_PREFIXES` కింద ఉన్న అనేక `traffic-inspector` రూట్లలో `x-loopback-only: true` అనోటేషన్ లేదని హెచ్చరిస్తుంది. దీన్ని అమలు చేయడానికి ముందుగా ఆ అనోటేషన్లను `openapi.yaml`కు జోడించాలి. -- `typecheck:noimplicit:core` (సలహాపూర్వకం) — బ్లాకింగ్ `check:type-coverage` రాచెట్లో చాలావరకు ఇమిడిపోయింది. దీన్ని రాచెట్గా మార్చండి లేదా అనవసరంగా పునరావృతమయ్యే రెండవ `tsc` పాస్ను తొలగించండి. +- `check:openapi-security-tiers` (సలహాపూర్వకం) — ❌ **శుభ్రంగా మార్చడం సాధ్యం కాదు.** ఇది 0తో నిష్క్రమిస్తుంది, కానీ `LOCAL_ONLY_API_PREFIXES` కింద ఉన్న అనేక `traffic-inspector` రూట్లలో `x-loopback-only: true` అనోటేషన్ లేదని హెచ్చరిస్తుంది. దీన్ని అమలు చేయాలంటే ముందుగా ఆ అనోటేషన్లను `openapi.yaml`కు జోడించాలి. +- `typecheck:noimplicit:core` (సలహాపూర్వకం) — బ్లాకింగ్ `check:type-coverage` రాచెట్లో ఇది ఎక్కువగా అంతర్భాగమైంది. దీన్ని రాచెట్గా మార్చండి లేదా అనవసరంగా పునరావృతమయ్యే రెండో `tsc` పాస్ను తొలగించండి. - `test:vitest:ui` (ఇప్పుడు **బ్లాకింగ్**) — ముందే ఉన్న వైఫల్యాలు `vitest.config.ts`లో `// #8618` ట్రాకింగ్ వ్యాఖ్యలతో స్పష్టంగా మినహాయించబడ్డాయి; కొత్త వైఫల్యాలు జాబ్ను విఫలం చేస్తాయి. -- `check:secrets` (gitleaks, డాక్యుమెంట్ చేసిన 3 తప్పుడు-పాజిటివ్ల వద్ద స్థిరపరచిన బ్లాకింగ్ రాచెట్) — 0కు చేరుకోవడానికి ఆ 3ను అనుమతి జాబితాలో చేర్చండి లేదా సలహాపూర్వక స్థాయికి తగ్గించండి. GitHub స్థానిక సీక్రెట్-స్కానింగ్ + `check:public-creds`తో అతివ్యాప్తి చెందుతుంది. -- `check:pr-evidence` (బ్లాకింగ్, PR-బాడీ గద్యాన్ని greps చేస్తుంది) — తప్పుడు-పాజిటివ్ ప్రమాదం అధికం; తొలగిస్తే కఠిన నియమం #18 అమలు బలహీనమవుతుంది, కాబట్టి ఇది నిజమైన విధానపరమైన నిర్ణయం. -- `semgrep` (సలహాపూర్వక స్వతంత్రం) — OWASP కుటుంబాల విషయంలో CodeQLతో అతివ్యాప్తి చెందుతుంది; దాని బేస్లైన్ను రాచెట్కు అనుసంధానించండి లేదా తొలగించండి. +- `check:secrets` (gitleaks, డాక్యుమెంట్ చేసిన 3 తప్పుడు-పాజిటివ్ల వద్ద స్థిరపరచిన బ్లాకింగ్ రాచెట్) — 0కు చేరుకోవడానికి ఆ 3ను అనుమతి జాబితాలో చేర్చండి లేదా సలహాపూర్వక స్థాయికి తగ్గించండి. ఇది GitHub స్థానిక secret-scanning + `check:public-creds`తో అతివ్యాప్తి చెందుతుంది. +- `check:pr-evidence` (బ్లాకింగ్, PR-body గద్యాన్ని greps చేస్తుంది) — తప్పుడు-పాజిటివ్ ప్రమాదం అధికం; దీన్ని తొలగిస్తే Hard Rule #18 అమలు బలహీనపడుతుంది, కాబట్టి ఇది నిజమైన విధానపరమైన నిర్ణయం. +- `semgrep` (సలహాపూర్వక స్వతంత్ర అమలు) — OWASP వర్గాల విషయంలో CodeQLతో అతివ్యాప్తి చెందుతుంది; దాని బేస్లైన్ను రాచెట్కు అనుసంధానించండి లేదా తొలగించండి. --- diff --git a/docs/i18n/te/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/te/docs/compression/COMPRESSION_GUIDE.md index bb0288b07b09..af88672fac5f 100644 --- a/docs/i18n/te/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/te/docs/compression/COMPRESSION_GUIDE.md @@ -451,9 +451,7 @@ const { messages: aged, saved } = applyAging(messages, { `messages` ఉన్న బాడీలో, కంటెంట్ బైపాస్ (`shouldBypassCavemanOutputMode()` in `open-sse/services/compression/outputMode.ts`) చివరి మూడు సందేశాలను తనిఖీ చేసి, అవి భద్రత, తిరిగి మార్చలేని చర్య, స్పష్టీకరణ లేదా క్రమ-సున్నితమైన కీలకపదాలతో -సరిపోలినప్పుడు మొత్తం టర్న్కు శైలులను దాటవేస్తుంది. డ్యాష్బోర్డ్లోని -**స్వయంచాలక-స్పష్టత బైపాస్** టాగుల్ (`cavemanOutputMode.autoClarity`) ఏ విధంగా -సెట్ చేయబడిందో దాని ప్రకారమే బైపాస్ అమలవుతుంది. +సరిపోలినప్పుడు మొత్తం టర్న్కు శైలులను దాటవేస్తుంది. డ్యాష్బోర్డ్లోని **స్వయంచాలక-స్పష్టత బైపాస్** టాగుల్ (`cavemanOutputMode.autoClarity`) ఆన్‌లో ఉన్నంత వరకు బైపాస్ అమలవుతుంది, ఇదే అప్రమేయం; టాగుల్ ఆఫ్‌లో ఉన్నప్పుడు ఎంచుకున్న శైలులు ఆ టర్న్లలో కూడా వర్తిస్తాయి. బైపాస్ టర్న్ను అనుమతించినప్పుడు, ఎప్పుడూ కొత్త `messages[0]`ను సృష్టించని `placeSystemInstruction()` (అదే ఫైల్), తాను కనుగొనే క్రింది వాటిలో మొదటిదానిలో diff --git a/docs/i18n/te/docs/reference/ENVIRONMENT.md b/docs/i18n/te/docs/reference/ENVIRONMENT.md index 81231c128a75..e4f4f4ebae23 100644 --- a/docs/i18n/te/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/te/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. అవసరమైన రహస్యాలు -మొదటిసారి అమలు చేయడానికి ముందు వీటిని **తప్పనిసరిగా** సెట్ చేయాలి. ఇవి లేకపోతే, అప్లికేషన్ ప్రారంభం కావడానికి నిరాకరిస్తుంది లేదా అసురక్షిత డిఫాల్ట్లతో పనిచేస్తుంది. - -| వేరియబుల్ | అవసరం | డిఫాల్ట్ | సోర్స్ ఫైల్ | వివరణ | -| ---------------------------- | ---------------------- | --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **అవును** | _(ఏదీ లేదు)_ | `src/lib/auth` | అన్ని డ్యాష్బోర్డ్ సెషన్ కుకీలకు (JWT) సంతకం చేస్తుంది/వాటిని ధృవీకరిస్తుంది. `openssl rand -base64 48`తో రూపొందించండి. | -| `API_KEY_SECRET` | **అవును** | _(ఏదీ లేదు)_ | `src/lib/db/apiKeys.ts` | SQLiteలో నిల్వలో ఉన్న API కీ విలువల కోసం AES ఎన్క్రిప్షన్ కీ. `openssl rand -hex 32`తో రూపొందించండి. | -| `INITIAL_PASSWORD` | **అవును** | `CHANGEME` | బూట్స్ట్రాప్ స్క్రిప్ట్ | ప్రారంభ అడ్మిన్ డ్యాష్బోర్డ్ పాస్వర్డ్ను సెట్ చేస్తుంది (`.env.example` డిఫాల్ట్తో సరిపోలుతుంది — మార్పు చేయించేందుకు ఉద్దేశపూర్వకంగా అసురక్షితంగా ఉంచబడింది). **మొదటిసారి ఉపయోగించే ముందు మార్చండి.** లాగిన్ అయిన తర్వాత, డ్యాష్బోర్డ్ → సెట్టింగ్లు → భద్రత ద్వారా మార్చండి. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **అవును** (ప్రొడక్షన్) | _(సెట్ చేయలేదు)_ | `src/app/api/internal/codex-responses-ws/route.ts` | అంతర్గత Codex Responses WebSocket బ్రిడ్జ్ కోసం భాగస్వామ్య రహస్యం. Electron/బ్రౌజర్ WS రిలే మరియు OmniRoute మధ్య బ్రిడ్జ్ అభ్యర్థనలను ధృవీకరిస్తుంది. ⚠️ **ప్రొడక్షన్లో అవసరం — ఇది సెట్ చేయనప్పుడు, అన్ని WS బ్రిడ్జ్ అభ్యర్థనలు తిరస్కరించబడతాయి.** `openssl rand -base64 32`తో రూపొందించండి. | -| `OMNIROUTE_SW_BUILD_ID` | కాదు | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA షెల్ (#11779) కోసం స్పష్టమైన సర్వీస్-వర్కర్ కాష్-బస్టింగ్ id; పరిష్కార క్రమంలో మొదటిది. | -| `SOURCE_VERSION` | కాదు | _(సెట్ చేయలేదు)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | క్రమంలో రెండవది — డిప్లాయ్ చేసిన కమిట్గా PaaS బిల్డర్లు (ఉదా. Heroku-శైలి) సెట్ చేస్తాయి. | -| `NEXT_PUBLIC_SW_BUILD_ID` | కాదు | _(ఉత్పన్నమైనది)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…`ను రిజిస్టర్ చేయడానికి క్లయింట్ ఉపయోగించే బిల్డ్-టైమ్ పబ్లిక్ విలువ; పై రెండింటి నుంచి, ఆపై git SHA నుంచి ఉత్పన్నమవుతుంది. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | కాదు (స్వయంచాలకం) | _(ప్రతి బూట్కు స్వయంచాలకం)_ | `src/server/authz/policies/management.ts` | విశ్వసనీయ peer-IP స్టాంప్ OmniRoute సొంత HTTP సర్వర్ (`scripts/dev/peer-stamp.mjs`) నుంచే వచ్చిందని నిరూపించే ప్రతి-ప్రాసెస్ రహస్యం. స్టాంప్లో ఈ టోకెన్ ఉన్నప్పుడు మాత్రమే authz మిడిల్వేర్ అభ్యర్థన స్థానికతను (LOCAL_ONLY రూట్ల loopback/LAN గేటింగ్) విశ్వసిస్తుంది. ప్రతి బూట్లో స్వయంచాలకంగా రూపొందించబడుతుంది — సెట్ చేయకుండా వదిలేయండి; స్టాంప్ను భాగస్వామ్యం చేయాల్సిన మల్టీ-ప్రాసెస్ సెటప్లకు మాత్రమే దీన్ని స్థిరపరచండి. | -| `OMNIROUTE_TRUSTED_PROXIES` | కాదు | _(సెట్ చేయలేదు)_ | `scripts/dev/peer-stamp.mjs` | IP అనుమతి/నిరాకరణ జాబితా విశ్వసించగల reverse proxyల కామాతో వేరు చేసిన IPలు లేదా CIDR పరిధులు; ఎల్లప్పుడూ విశ్వసించబడే loopback, private-network చిరునామాలు మరియు Cloudflare edgeలకు అదనంగా ఇవి వర్తిస్తాయి. ఏదైనా ఇతర పబ్లిక్ చిరునామాలో ఉన్న proxyకి మాత్రమే ఇది అవసరం; లేకపోతే ఫిల్టర్ ఆ proxy సొంత చిరునామానే మూల్యాంకనం చేస్తుంది. | +**అవును** గా గుర్తించబడిన, సెట్ చేయని రహస్యాలు మొదటి అమలు సమయంలో స్వయంచాలకంగా రూపొందించబడతాయి (`JWT_SECRET`, `API_KEY_SECRET`) మరియు డేటా డైరెక్టరీలోని `server.env`లో నిల్వ చేయబడతాయి — ఆ డైరెక్టరీ తాత్కాలికమైనదైతే వీటిని స్పష్టంగా సెట్ చేయండి. మినహాయింపు `OMNIROUTE_WS_BRIDGE_SECRET`: అది `server.env`లో నిల్వ చేయబడదు — సర్వర్ ఎంట్రీపాయింట్‌లు ప్రతి బూట్‌కు ఒక తాత్కాలిక విలువను సృష్టిస్తాయి, దాన్ని ప్రాసెస్ లోపలి బ్రిడ్జ్ మాత్రమే ఉపయోగించగలదు, కాబట్టి బాహ్య బ్రిడ్జ్ క్లయింట్‌లు దాన్ని స్పష్టంగా సెట్ చేయాలి. + +| వేరియబుల్ | అవసరం | డిఫాల్ట్ | సోర్స్ ఫైల్ | వివరణ | +| ---------------------------- | ---------------------- | --------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **అవును** | _(ఏదీ లేదు)_ | `src/lib/auth` | అన్ని డ్యాష్బోర్డ్ సెషన్ కుకీలకు (JWT) సంతకం చేస్తుంది/వాటిని ధృవీకరిస్తుంది. `openssl rand -base64 48`తో రూపొందించండి. | +| `API_KEY_SECRET` | **అవును** | _(ఏదీ లేదు)_ | `src/lib/db/apiKeys.ts` | SQLiteలో నిల్వలో ఉన్న API కీ విలువల కోసం AES ఎన్క్రిప్షన్ కీ. `openssl rand -hex 32`తో రూపొందించండి. | +| `INITIAL_PASSWORD` | కాదు | _(సెట్ చేయలేదు)_ | `src/lib/auth/managementPassword.ts` | ఇంకా పాస్‌వర్డ్ ఏదీ సేవ్ చేయబడని సమయంలో స్టార్టప్‌లో డ్యాష్‌బోర్డ్ పాస్‌వర్డ్‌ను సెట్ చేస్తుంది; పాస్‌వర్డ్ సేవ్ చేయబడిన తర్వాత ఇది విస్మరించబడుతుంది. npm మరియు సోర్స్ ఇన్‌స్టాల్‌లు `.env.example`ను `.env`కి కాపీ చేస్తాయి, కాబట్టి అవి దాని బహిరంగంగా తెలిసిన ప్లేస్‌హోల్డర్ `CHANGEME`తో ప్రారంభమవుతాయి: మొదటి బూట్‌కు ముందు దాన్ని భర్తీ చేయండి, లేదా మొదటి సందర్శన సమయంలో ఆన్‌బోర్డింగ్ విజార్డ్‌లో పాస్‌వర్డ్‌ను సృష్టించడానికి, సెట్ చేయని విలువ (Docker ఇమేజ్ డిఫాల్ట్) చేసినట్టుగా, ఆ `.env`లో `INITIAL_PASSWORD=` (ఖాళీగా) సెట్ చేయండి. సేవ్ చేసిన పాస్‌వర్డ్‌ను డ్యాష్‌బోర్డ్ → సెట్టింగ్లు → భద్రతలో మార్చండి, లేదా `DATA_DIR`ను సర్వర్ యొక్క డేటా డైరెక్టరీగా సెట్ చేసి `omniroute reset-password`ను (సోర్స్ చెక్‌అవుట్‌లో `node bin/reset-password.mjs`) అమలు చేయండి. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **అవును** (ప్రొడక్షన్) | _(సెట్ చేయలేదు)_ | `src/app/api/internal/codex-responses-ws/route.ts` | అంతర్గత Codex Responses WebSocket బ్రిడ్జ్ కోసం భాగస్వామ్య రహస్యం. Electron/బ్రౌజర్ WS రిలే మరియు OmniRoute మధ్య బ్రిడ్జ్ అభ్యర్థనలను ధృవీకరిస్తుంది. ⚠️ **ప్రొడక్షన్లో అవసరం — ఇది సెట్ చేయనప్పుడు, అన్ని WS బ్రిడ్జ్ అభ్యర్థనలు తిరస్కరించబడతాయి.** `openssl rand -base64 32`తో రూపొందించండి. | +| `OMNIROUTE_SW_BUILD_ID` | కాదు | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA షెల్ (#11779) కోసం స్పష్టమైన సర్వీస్-వర్కర్ కాష్-బస్టింగ్ id; పరిష్కార క్రమంలో మొదటిది. | +| `SOURCE_VERSION` | కాదు | _(సెట్ చేయలేదు)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | క్రమంలో రెండవది — డిప్లాయ్ చేసిన కమిట్గా PaaS బిల్డర్లు (ఉదా. Heroku-శైలి) సెట్ చేస్తాయి. | +| `NEXT_PUBLIC_SW_BUILD_ID` | కాదు | _(ఉత్పన్నమైనది)_ | `src/shared/components/PwaRegister.tsx` | `/sw.js?v=…`ను రిజిస్టర్ చేయడానికి క్లయింట్ ఉపయోగించే బిల్డ్-టైమ్ పబ్లిక్ విలువ; పై రెండింటి నుంచి, ఆపై git SHA నుంచి ఉత్పన్నమవుతుంది. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | కాదు (స్వయంచాలకం) | _(ప్రతి బూట్కు స్వయంచాలకం)_ | `src/server/authz/policies/management.ts` | విశ్వసనీయ peer-IP స్టాంప్ OmniRoute సొంత HTTP సర్వర్ (`scripts/dev/peer-stamp.mjs`) నుంచే వచ్చిందని నిరూపించే ప్రతి-ప్రాసెస్ రహస్యం. స్టాంప్లో ఈ టోకెన్ ఉన్నప్పుడు మాత్రమే authz మిడిల్వేర్ అభ్యర్థన స్థానికతను (LOCAL_ONLY రూట్ల loopback/LAN గేటింగ్) విశ్వసిస్తుంది. ప్రతి బూట్లో స్వయంచాలకంగా రూపొందించబడుతుంది — సెట్ చేయకుండా వదిలేయండి; స్టాంప్ను భాగస్వామ్యం చేయాల్సిన మల్టీ-ప్రాసెస్ సెటప్లకు మాత్రమే దీన్ని స్థిరపరచండి. | +| `OMNIROUTE_TRUSTED_PROXIES` | కాదు | _(సెట్ చేయలేదు)_ | `scripts/dev/peer-stamp.mjs` | IP అనుమతి/నిరాకరణ జాబితా విశ్వసించగల reverse proxyల కామాతో వేరు చేసిన IPలు లేదా CIDR పరిధులు; ఎల్లప్పుడూ విశ్వసించబడే loopback, private-network చిరునామాలు మరియు Cloudflare edgeలకు అదనంగా ఇవి వర్తిస్తాయి. ఏదైనా ఇతర పబ్లిక్ చిరునామాలో ఉన్న proxyకి మాత్రమే ఇది అవసరం; లేకపోతే ఫిల్టర్ ఆ proxy సొంత చిరునామానే మూల్యాంకనం చేస్తుంది. | ### రూపొందించే కమాండ్లు @@ -217,38 +217,38 @@ OmniRoute మొత్తం డేటా నిల్వ కోసం **SQLite* ## 4. భద్రత & ప్రామాణీకరణ -| వేరియబుల్ | డిఫాల్ట్ | సోర్స్ ఫైల్ | వివరణ | -| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | మెషీన్ ఫింగర్ప్రింటింగ్ కోసం హార్డ్వేర్ ఐడెంటిఫైయర్లతో కలిపే సాల్ట్. వేర్పాటు కోసం ప్రతి డిప్లాయ్మెంట్కు దీన్ని మార్చండి. | -| `OMNIROUTE_CLI_SALT` | _(సెట్ చేయకపోతే = ప్రతి ఇన్స్టాలేషన్కు యాదృచ్ఛిక సాల్ట్, `/cli-token-salt.json` వద్ద నిల్వ చేయబడుతుంది)_ | `src/lib/machineToken.ts` | స్థానిక CLI ప్రామాణీకరణ టోకెన్ను ఉత్పాదించడానికి ఉపయోగించే HMAC సాల్ట్. ఈ విలువను సెట్ చేయడం వల్ల మెషీన్లోని అన్ని CLI టోకెన్లు రొటేట్ అవుతాయి మరియు నిల్వ చేసిన సాల్ట్ కంటే దీనికే ఎల్లప్పుడూ ప్రాధాన్యత ఉంటుంది. `docs/security/CLI_TOKEN.md` చూడండి. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | సెషన్ కుకీలపై `Secure` ఫ్లాగ్ను సెట్ చేస్తుంది. HTTPS వెనుక రన్ చేస్తున్నప్పుడు ఇది **తప్పనిసరిగా `true` అయి ఉండాలి**. | -| `REQUIRE_API_KEY` | `false` | API మిడిల్వేర్ | `true` అయినప్పుడు, అన్ని `/v1/*` ప్రాక్సీ అభ్యర్థనలలో చెల్లుబాటు అయ్యే API కీ ఉండాలి. **ఈ ఫ్లాగ్ `GET /v1/models`ను నియంత్రించదు**, బదులుగా అది డ్యాష్బోర్డ్ లాగిన్ విధానాన్ని (`requireAuthForModels`) అనుసరిస్తుంది — కాబట్టి `/v1/models` నుండి వచ్చే `401`, ఇన్ఫరెన్స్ రక్షించబడిందని సూచించదు. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) చూడండి. | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | డ్యాష్బోర్డ్ UIలో పూర్తి API కీ విలువలను చూపించడానికి అనుమతిస్తుంది. డ్యాష్బోర్డ్ ఫీచర్ ఫ్లాగ్ల నుండి కాన్ఫిగర్ చేయవచ్చు; షేర్డ్ ఇన్స్టాన్స్లలో ఇది భద్రతా ప్రమాదం. | -| `NO_LOG_API_KEY_IDS` | _(ఖాళీ)_ | `src/lib/compliance/index.ts` | అభ్యర్థన లాగింగ్ను దాటవేసే, కామాలతో వేరు చేసిన API కీ IDలు (GDPR అనుగుణ్యత కోసం). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(సెట్ చేయకపోతే = అపరిమితం)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` కాలమ్ nullగా ఉన్న API కీలకు వర్తించే రోజువారీ అభ్యర్థన బడ్జెట్కు ఫాల్బ్యాక్. సెట్ చేయకపోతే లేదా ఖాళీగా ఉంటే: అంతర్లీన పరిమితి ఉండదు (#2289, #11017). `0` కూడా ఇదే అర్థాన్ని కలిగి ఉంటుంది (అపరిమితం). ధన పూర్ణసంఖ్య N అనేది N/రోజు, 5N/వారం, 20N/నెల పరిమితులను ప్రారంభిస్తుంది. తప్పుగా రూపొందించిన ఖాళీ కాని విలువలు పాత 1000/రోజు, 5000/వారం, 20000/నెల విండోలకు ఫాల్బ్యాక్ అవుతాయి. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | అనుమతించబడే గరిష్ఠ అభ్యర్థన బాడీ పరిమాణం. ఈ పరిమితిని మించిన పేలోడ్లను తిరస్కరిస్తుంది. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ఈ థ్రెషోల్డ్కు సమానంగా లేదా అంతకంటే ఎక్కువగా ఉన్న వాస్తవ అభ్యర్థన బాడీలు JSON పార్సింగ్కు ముందు అటామిక్ ప్రాసెస్-లోకల్ హెవీవెయిట్ అడ్మిషన్ లీజ్ను తీసుకుంటాయి (`POST /v1/responses`తో సహా BYTE మార్గం). స్ట్రక్చర్-హెవీకి ఉన్న అదే [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) హెల్తీ-హెడ్రూమ్ ఎస్కేప్ వర్తిస్తుంది; అయినప్పటికీ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) మళ్లీ సంభవించకుండా `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ద్వారా పరిమితం చేయబడుతుంది. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | పరిమిత ఇన్జెషన్ సమయంలో చదివిన బైట్లపై అమలు చేయబడే చాట్-రూట్ కఠిన పరిమితి; `Content-Length` లేని, చెల్లని లేదా వాస్తవానికి విరుద్ధమైన అభ్యర్థనలు కూడా ఇందులో ఉంటాయి; పరిమితిని మించిన వాటికి `413` అందుతుంది. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(సెట్ చేయలేదు — అభ్యర్థనల సంఖ్యపై పరిమితి లేదు)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ఫ్యాన్అవుట్:** ఈ పాత అభ్యర్థన-సంఖ్య పరిమితి ఇప్పుడు స్పష్టంగా సెట్ చేసినప్పుడు మాత్రమే వర్తిస్తుంది. దీన్ని సెట్ చేయకుండా ఉంచితే (డిఫాల్ట్), హెవీవెయిట్ చాట్ అడ్మిషన్ బదులుగా `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ద్వారా నియంత్రించబడుతుంది — ఇది **ఒక ప్రాసెస్లో** (ఒక V8 హీప్) ప్రాసెస్ యొక్క వాస్తవ మెమరీ పరిమితి ఆధారంగా పరిమాణం స్వయంచాలకంగా నిర్ణయించబడే బైట్ బడ్జెట్. ఒకదానితో ఒకటి అతివ్యాప్తి చెందే ~750k-టోకెన్ `/v1/responses` రెండు, ~12 Gi హీప్లను అబార్ట్ చేస్తాయి (#7849) — ఇది **మెమరీ-బడ్జెట్** హెచ్చరిక, 2 అనే కఠినమైన ఉత్పత్తి గరిష్ఠ పరిమితి కాదు. ఆరోగ్యకరమైన ప్రాసెస్ (హీప్ షెడ్ నిష్పత్తి కంటే దిగువన ఉన్నప్పుడు), `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ద్వారా మరిన్ని సమకాలీన దీర్ఘ `/v1/responses` అభ్యర్థనలను అనుమతించవచ్చు. పదుల సంఖ్యలో దీర్ఘ SSE క్లయింట్లు (40–50) అనేది హీప్ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110కు సంబంధించిన విషయం, “గరిష్ఠం 2” కాదు. “హోస్ట్ను ఉపయోగించుకోవడానికి” దీన్ని గుడ్డిగా పెంచడం #7849ను మళ్లీ ప్రవేశపెడుతుంది. **N స్వతంత్ర `DATA_DIR`లతో** **హీప్లను** గుణించండి (#11024); ఒక SQLite ఫైల్పై ఎప్పుడూ `replicas>1` ఉపయోగించవద్దు. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(స్వయంచాలకంగా ఉత్పన్నమైనది)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ఫ్యాన్అవుట్:** స్వయంచాలకంగా ఉత్పన్నమయ్యే ఇన్జెస్ట్ బైట్ బడ్జెట్కు ఓవర్రైడ్ (మరింత కఠినమైన V8/cgroup మెమరీ పరిమితిలో 25%ను 8x తాత్కాలిక ఆంప్లిఫికేషన్తో భాగించిన విలువ). ఉత్పన్నమైన మరియు స్పష్టంగా పేర్కొన్న విలువలు 8 MiB–2 GiB మధ్యకు పరిమితం చేయబడతాయి. ప్రభావవంతమైన బడ్జెట్ కంటే పెద్ద బాడీ వెంటనే `413 body_exceeds_budget`తో విఫలమవుతుంది; విడివిడిగా సేవలందించగల బాడీల మధ్య పోటీ మాత్రం మళ్లీ ప్రయత్నించగల `503`గానే ఉంటుంది. 40–50 సమకాలీన దీర్ఘ SSE క్లయింట్లు అనేది ఈ బడ్జెట్ + హీప్కు సంబంధించిన విషయం, కఠినమైన “గరిష్ఠం 2” కాదు. ట్యూనింగ్కు ముందు `/api/monitoring/health` వద్ద `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`ను చదవండి. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE మరియు STRUCTURE హెవీవెయిట్ అడ్మిషన్ కోసం హీప్-ఒత్తిడి షెడ్ నిష్పత్తి (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` దాటిన సమకాలీన హెవీవెయిట్ అభ్యర్థన, హీప్ కూడా ఈ నిష్పత్తి వద్ద లేదా దానికంటే ఎక్కువగా ఉన్నప్పుడు మాత్రమే మళ్లీ ప్రయత్నించగల `503`తో షెడ్ చేయబడుతుంది; ఆరోగ్యకరమైన హీప్లో అది బదులుగా హెల్తీ-హెడ్రూమ్ ద్వారా అనుమతించబడుతుంది. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (డిఫాల్ట్ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE మరియు BYTE రెండింటిపైనా (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES`కు సమానమైన లేదా అంతకంటే పెద్ద బాడీలతో సహా) ఆరోగ్యకరమైన-హీప్ ఫాస్ట్ పాత్ (#10437) కోసం పరిమిత అదనపు సామర్థ్యం. ఈ పరిమితి లేకుండా, ప్రతి బిజీగా ఉన్నప్పటికీ ఆరోగ్యకరమైన-హీప్ అభ్యర్థన ఎటువంటి గరిష్ఠ పరిమితి లేకుండా అడ్మిషన్ను దాటవేసేది. హెల్తీ-హీప్ పాత్ ద్వారా ఇన్ని సమకాలీన లీజ్లు యాక్టివ్ అయిన తర్వాత, తదుపరి బిజీ అభ్యర్థనలు వాస్తవ హీప్ ఒత్తిడిలో ఉపయోగించే అదే పరిమిత-నిరీక్షణ/షెడ్ పాత్కు వెళ్తాయి. `0` బైపాస్ను పూర్తిగా నిలిపివేస్తుంది. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | చాట్ అభ్యర్థన బాడీ బైట్ థ్రెషోల్డ్ కంటే తక్కువగా ఉన్నప్పటికీ దాన్ని హెవీవెయిట్గా వర్గీకరించే సందేశాల సంఖ్య. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | చాట్ అభ్యర్థన బాడీ బైట్ థ్రెషోల్డ్ కంటే తక్కువగా ఉన్నప్పటికీ దాన్ని హెవీవెయిట్గా వర్గీకరించే టూల్ల సంఖ్య. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | అభ్యర్థనను హెవీవెయిట్గా వర్గీకరించే సాంప్రదాయబద్ధమైన స్ట్రింగ్-పరిమాణ టోకెన్ అంచనా; ఇది అడ్మిషన్-ఖర్చు ప్రాక్సీ మాత్రమే, ప్రొవైడర్ బిల్లింగ్ టోకనైజేషన్ కాదు. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (నిలిపివేయబడింది) | `src/shared/middleware/chatBodyAdmission.ts` | ఐచ్ఛికంగా ప్రారంభించగల చాట్ చరిత్ర పరిమితి. డిఫాల్ట్గా నిలిపివేయబడింది: సందేశాల సంఖ్య అనేది డిప్లాయ్మెంట్ విధానం, అభ్యర్థన యొక్క సార్వత్రిక లక్షణం కాదు; ఇక్కడ పరిమితం చేయడం వల్ల కంప్రెషన్ పైప్లైన్ వాటిని సేవలందించగలిగేలా చేయడానికి ముందే సంభాషణలు అంతిమ `413`తో తిరస్కరించబడతాయి. హీప్ వృద్ధి `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` మరియు హీప్-ఒత్తిడి షెడ్ ద్వారా పరిమితం చేయబడుతుంది. కఠినమైన గరిష్ఠ పరిమితి అవసరమైన మెమరీ-పరిమిత డిప్లాయ్మెంట్లలో ధనాత్మక విలువను సెట్ చేయండి; అప్పుడు పరిమితిని మించిన వాటికి నిర్మితమైన compact-required `413` అందుతుంది. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | పూర్తిగా మెమరీలోకి బఫర్ చేయబడే నాన్-స్ట్రీమింగ్ అప్స్ట్రీమ్ ప్రతిస్పందనకు కఠిన పరిమితి. దీనిని దాటితే అప్స్ట్రీమ్ రీడర్ రద్దు చేయబడుతుంది మరియు హీప్ ఖాళీ అయ్యే వరకు అపరిమిత స్ట్రింగ్ పెరగనివ్వకుండా అభ్యర్థన వేగంగా విఫలమవుతుంది. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | అప్స్ట్రీమ్ ప్రతిస్పందన హెడర్ల నుంచి ఫార్వర్డ్ చేయగల గరిష్ఠ వైర్ బైట్లు. బడ్జెట్ మించిపోయినప్పుడు, సాధారణ రివర్స్-ప్రాక్సీ హెడర్ పరిమితుల్లో ఉండేందుకు తక్కువ ప్రాధాన్యత గల హెడర్లు (ఉదా., అనుకూల `x-codex-*`, `x-oai-request-id`) తొలగించబడతాయి. ప్రతిస్పందన హెడర్ పరిమాణం పెరుగుతుందనే మూల్యంతో మరింత అప్స్ట్రీమ్ మెటాడేటాను ఫార్వర్డ్ చేయడానికి దీన్ని ఎక్కువ విలువకు సెట్ చేయండి. | -| `CORS_ORIGIN` | _(సెట్ చేయలేదు)_ | `src/server/cors/origins.ts` | పాత ఒకే-మూలం CORS అనుమతి జాబితా. కొత్త డిప్లాయ్మెంట్ల కోసం `CORS_ALLOWED_ORIGINS`కు ప్రాధాన్యం ఇవ్వండి. CORS అనేది క్రాస్-ఆరిజిన్ బ్రౌజర్ API క్లయింట్ల కోసం మాత్రమే; ప్రమాణీకరించిన డ్యాష్బోర్డ్ రైట్లు బదులుగా అదే-మూల అభ్యర్థనలతో పాటు సెషన్కు అనుసంధానించిన CSRF రక్షణను ఉపయోగిస్తాయి. | -| `CORS_ALLOWED_ORIGINS` | _(సెట్ చేయలేదు)_ | `src/server/cors/origins.ts` | కామాలతో వేరు చేసిన CORS అనుమతి జాబితా. `CORS_ALLOW_ALL=true`ను స్పష్టంగా కాన్ఫిగర్ చేస్తే తప్ప వైల్డ్కార్డ్ పంపబడదు. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ఏ బ్రౌజర్ `Origin`నైనా ప్రతిధ్వనించేందుకు డెవలప్మెంట్లో మాత్రమే ఉపయోగించాల్సిన ప్రత్యామ్నాయ మార్గం. భాగస్వామ్య లేదా ప్రొడక్షన్ డిప్లాయ్మెంట్లలో దీన్ని ప్రారంభించవద్దు. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | ప్రైవేట్/లూప్బ్యాక్/లింక్-లోకల్ IP పరిధులను లక్ష్యంగా చేసుకునే ప్రొవైడర్ కాల్లను నిరోధించండి. వేరుచేసిన పరీక్షా ఎన్విరాన్మెంట్లలో మాత్రమే నిలిపివేయండి. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | ప్రైవేట్/లోకల్ నెట్వర్క్లను (localhost, 192.168.x.x, 10.x.x.x మొదలైనవి) సూచించే ప్రొవైడర్ URLలను అనుమతించండి. **స్వయంగా హోస్ట్ చేసిన ప్రొవైడర్లకు అవసరం** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). ఇది `false`గా ఉన్నప్పుడు, లోకల్ URLల ధ్రువీకరణను డ్యాష్బోర్డ్ తిరస్కరిస్తుంది. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | లోకల్/ప్రైవేట్ చిరునామాల్లో (127.0.0.1, localhost, LAN, ప్రైవేట్ పరిధులు) ప్రొవైడర్లను జోడించడం/ధ్రువీకరించడం అనుమతించండి — ఇది ప్రొవైడర్ ధ్రువీకరణ మార్గానికి మాత్రమే పరిమితం. **డిఫాల్ట్ `true`** (లోకల్కు ప్రథమ ప్రాధాన్యం); కేవలం పబ్లిక్ చిరునామాలను మాత్రమే అనుమతించే కఠిన నిరోధాన్ని అమలు చేయడానికి `false`గా సెట్ చేయండి. క్లౌడ్-మెటాడేటా ఎండ్పాయింట్లు (169.254.169.254, metadata.google.internal) ఏ సందర్భంలోనైనా నిరోధించబడతాయి. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` రూట్లు (ట్రాన్స్క్రిప్షన్లు, స్పీచ్, అనువాదాలు) localhostకు వెలుపల హోస్ట్ చేసిన OpenAI-అనుకూల ప్రొవైడర్ నోడ్ను ఉపయోగించేందుకు అనుమతించండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది — ఆడియోను రిమోట్ హోస్ట్కు రూట్ చేయడం వల్ల ఎగ్రెస్ గుర్తింపు మారుతుంది, కాబట్టి ఇది ఆపరేటర్ స్పష్టంగా తీసుకోవాల్సిన నిర్ణయం. లూప్బ్యాక్/ప్రైవేట్ నోడ్లు (localhost, 127.0.0.1, 172.16-31.x) ఎల్లప్పుడూ అనుమతించబడతాయి మరియు దీనివల్ల ప్రభావితం కావు. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (మరియు మెమరీ ఇంజిన్ యొక్క లూప్బ్యాక్ రీరాంక్ దశ) localhostకు వెలుపల హోస్ట్ చేసిన OpenAI-అనుకూల ప్రొవైడర్ నోడ్ను ఉపయోగించేందుకు అనుమతించండి — అంటే TEI, Infinity, vLLM మొదలైనవి నడుపుతున్న LAN మెషీన్ లేదా Tailscale పీర్. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది — రిమోట్ హోస్ట్కు రూట్ చేయడం వల్ల ఎగ్రెస్ గుర్తింపు మారుతుంది, కాబట్టి ఇది ఆపరేటర్ స్పష్టంగా తీసుకోవాల్సిన నిర్ణయం. లూప్బ్యాక్ నోడ్లు (localhost, 127.0.0.1, 172.16-31.x) ఎల్లప్పుడూ అనుమతించబడతాయి మరియు దీనివల్ల ప్రభావితం కావు. రిమోట్ నోడ్లు ప్రొవైడర్ అవుట్బౌండ్ URL విధానాన్ని కూడా (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) తప్పనిసరిగా దాటాలి; క్లౌడ్-మెటాడేటా హోస్ట్లకు ఎప్పటికీ రూట్ చేయబడదు. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ప్రారంభించబడినప్పుడు, వినియోగదారులు OIDC Single Sign-On ద్వారా మాత్రమే ప్రమాణీకరించగలిగేలా పాస్వర్డ్ లాగిన్ను నిలిపివేయండి. సాధారణ అలియాస్ `OIDC_DISABLE_PASSWORD_LOGIN` కూడా ఆమోదించబడుతుంది; అదే కీ కలిగిన డ్యాష్బోర్డ్ ఫీచర్ ఫ్లాగ్కు ప్రాధాన్యం ఉంటుంది. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` యొక్క సాధారణ అలియాస్ (#10889). | +| వేరియబుల్ | డిఫాల్ట్ | సోర్స్ ఫైల్ | వివరణ | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | మెషీన్ ఫింగర్ప్రింటింగ్ కోసం హార్డ్వేర్ ఐడెంటిఫైయర్లతో కలిపే సాల్ట్. వేర్పాటు కోసం ప్రతి డిప్లాయ్మెంట్కు దీన్ని మార్చండి. | +| `OMNIROUTE_CLI_SALT` | _(సెట్ చేయకపోతే = ప్రతి ఇన్స్టాలేషన్కు యాదృచ్ఛిక సాల్ట్, `/cli-token-salt.json` వద్ద నిల్వ చేయబడుతుంది)_ | `src/lib/machineToken.ts` | స్థానిక CLI ప్రామాణీకరణ టోకెన్ను ఉత్పాదించడానికి ఉపయోగించే HMAC సాల్ట్. ఈ విలువను సెట్ చేయడం వల్ల మెషీన్లోని అన్ని CLI టోకెన్లు రొటేట్ అవుతాయి మరియు నిల్వ చేసిన సాల్ట్ కంటే దీనికే ఎల్లప్పుడూ ప్రాధాన్యత ఉంటుంది. `docs/security/CLI_TOKEN.md` చూడండి. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | సెషన్ కుకీలపై `Secure` ఫ్లాగ్ను సెట్ చేస్తుంది. HTTPS వెనుక రన్ చేస్తున్నప్పుడు ఇది **తప్పనిసరిగా `true` అయి ఉండాలి**. | +| `REQUIRE_API_KEY` | `false` | API మిడిల్వేర్ | `true` అయినప్పుడు, అన్ని `/v1/*` ప్రాక్సీ అభ్యర్థనలలో చెల్లుబాటు అయ్యే API కీ ఉండాలి. **ఈ ఫ్లాగ్ `GET /v1/models`ను నియంత్రించదు**, బదులుగా అది డ్యాష్బోర్డ్ లాగిన్ విధానాన్ని (`requireAuthForModels`) అనుసరిస్తుంది — కాబట్టి `/v1/models` నుండి వచ్చే `401`, ఇన్ఫరెన్స్ రక్షించబడిందని సూచించదు. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) చూడండి. | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | డ్యాష్బోర్డ్ UIలో పూర్తి API కీ విలువలను చూపించడానికి అనుమతిస్తుంది. డ్యాష్బోర్డ్ ఫీచర్ ఫ్లాగ్ల నుండి కాన్ఫిగర్ చేయవచ్చు; షేర్డ్ ఇన్స్టాన్స్లలో ఇది భద్రతా ప్రమాదం. | +| `NO_LOG_API_KEY_IDS` | _(ఖాళీ)_ | `src/lib/compliance/index.ts` | అభ్యర్థన లాగింగ్ను దాటవేసే, కామాలతో వేరు చేసిన API కీ IDలు (GDPR అనుగుణ్యత కోసం). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(సెట్ చేయకపోతే = అపరిమితం)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` కాలమ్ nullగా ఉన్న API కీలకు వర్తించే రోజువారీ అభ్యర్థన బడ్జెట్కు ఫాల్బ్యాక్. సెట్ చేయకపోతే లేదా ఖాళీగా ఉంటే: అంతర్లీన పరిమితి ఉండదు (#2289, #11017). `0` కూడా ఇదే అర్థాన్ని కలిగి ఉంటుంది (అపరిమితం). ధన పూర్ణసంఖ్య N అనేది N/రోజు, 5N/వారం, 20N/నెల పరిమితులను ప్రారంభిస్తుంది. తప్పుగా రూపొందించిన ఖాళీ కాని విలువలు పాత 1000/రోజు, 5000/వారం, 20000/నెల విండోలకు ఫాల్బ్యాక్ అవుతాయి. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | అనుమతించబడే గరిష్ఠ అభ్యర్థన బాడీ పరిమాణం. ఈ పరిమితిని మించిన పేలోడ్లను తిరస్కరిస్తుంది. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | ఈ థ్రెషోల్డ్కు సమానంగా లేదా అంతకంటే ఎక్కువగా ఉన్న వాస్తవ అభ్యర్థన బాడీలు JSON పార్సింగ్కు ముందు అటామిక్ ప్రాసెస్-లోకల్ హెవీవెయిట్ అడ్మిషన్ లీజ్ను తీసుకుంటాయి (`POST /v1/responses`తో సహా BYTE మార్గం). స్ట్రక్చర్-హెవీకి ఉన్న అదే [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) హెల్తీ-హెడ్రూమ్ ఎస్కేప్ వర్తిస్తుంది; అయినప్పటికీ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) మళ్లీ సంభవించకుండా `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ద్వారా పరిమితం చేయబడుతుంది. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | పరిమిత ఇన్జెషన్ సమయంలో చదివిన బైట్లపై అమలు చేయబడే చాట్-రూట్ కఠిన పరిమితి; `Content-Length` లేని, చెల్లని లేదా వాస్తవానికి విరుద్ధమైన అభ్యర్థనలు కూడా ఇందులో ఉంటాయి; పరిమితిని మించిన వాటికి `413` అందుతుంది. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(సెట్ చేయలేదు — అభ్యర్థనల సంఖ్యపై పరిమితి లేదు)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-ఫ్యాన్అవుట్:** ఈ పాత అభ్యర్థన-సంఖ్య పరిమితి ఇప్పుడు స్పష్టంగా సెట్ చేసినప్పుడు మాత్రమే వర్తిస్తుంది. దీన్ని సెట్ చేయకుండా ఉంచితే (డిఫాల్ట్), హెవీవెయిట్ చాట్ అడ్మిషన్ బదులుగా `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` ద్వారా నియంత్రించబడుతుంది — ఇది **ఒక ప్రాసెస్లో** (ఒక V8 హీప్) ప్రాసెస్ యొక్క వాస్తవ మెమరీ పరిమితి ఆధారంగా పరిమాణం స్వయంచాలకంగా నిర్ణయించబడే బైట్ బడ్జెట్. ఒకదానితో ఒకటి అతివ్యాప్తి చెందే ~750k-టోకెన్ `/v1/responses` రెండు, ~12 Gi హీప్లను అబార్ట్ చేస్తాయి (#7849) — ఇది **మెమరీ-బడ్జెట్** హెచ్చరిక, 2 అనే కఠినమైన ఉత్పత్తి గరిష్ఠ పరిమితి కాదు. ఆరోగ్యకరమైన ప్రాసెస్ (హీప్ షెడ్ నిష్పత్తి కంటే దిగువన ఉన్నప్పుడు), `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ద్వారా మరిన్ని సమకాలీన దీర్ఘ `/v1/responses` అభ్యర్థనలను అనుమతించవచ్చు. పదుల సంఖ్యలో దీర్ఘ SSE క్లయింట్లు (40–50) అనేది హీప్ + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110కు సంబంధించిన విషయం, “గరిష్ఠం 2” కాదు. “హోస్ట్ను ఉపయోగించుకోవడానికి” దీన్ని గుడ్డిగా పెంచడం #7849ను మళ్లీ ప్రవేశపెడుతుంది. **N స్వతంత్ర `DATA_DIR`లతో** **హీప్లను** గుణించండి (#11024); ఒక SQLite ఫైల్పై ఎప్పుడూ `replicas>1` ఉపయోగించవద్దు. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(స్వయంచాలకంగా ఉత్పన్నమైనది)_ | `src/shared/middleware/admissionBudget.ts` | **#503-ఫ్యాన్అవుట్:** స్వయంచాలకంగా ఉత్పన్నమయ్యే ఇన్జెస్ట్ బైట్ బడ్జెట్కు ఓవర్రైడ్ (మరింత కఠినమైన V8/cgroup మెమరీ పరిమితిలో 25%ను 8x తాత్కాలిక ఆంప్లిఫికేషన్తో భాగించిన విలువ). ఉత్పన్నమైన మరియు స్పష్టంగా పేర్కొన్న విలువలు 8 MiB–2 GiB మధ్యకు పరిమితం చేయబడతాయి. ప్రభావవంతమైన బడ్జెట్ కంటే పెద్ద బాడీ వెంటనే `413 body_exceeds_budget`తో విఫలమవుతుంది; విడివిడిగా సేవలందించగల బాడీల మధ్య పోటీ మాత్రం మళ్లీ ప్రయత్నించగల `503`గానే ఉంటుంది. 40–50 సమకాలీన దీర్ఘ SSE క్లయింట్లు అనేది ఈ బడ్జెట్ + హీప్కు సంబంధించిన విషయం, కఠినమైన “గరిష్ఠం 2” కాదు. ట్యూనింగ్కు ముందు `/api/monitoring/health` వద్ద `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`ను చదవండి. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BYTE మరియు STRUCTURE హెవీవెయిట్ అడ్మిషన్ కోసం హీప్-ఒత్తిడి షెడ్ నిష్పత్తి (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` దాటిన సమకాలీన హెవీవెయిట్ అభ్యర్థన, హీప్ కూడా ఈ నిష్పత్తి వద్ద లేదా దానికంటే ఎక్కువగా ఉన్నప్పుడు మాత్రమే మళ్లీ ప్రయత్నించగల `503`తో షెడ్ చేయబడుతుంది; ఆరోగ్యకరమైన హీప్లో అది బదులుగా హెల్తీ-హెడ్రూమ్ ద్వారా అనుమతించబడుతుంది. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (డిఫాల్ట్ `1`) | `src/shared/middleware/chatBodyAdmission.ts` | STRUCTURE మరియు BYTE రెండింటిపైనా (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES`కు సమానమైన లేదా అంతకంటే పెద్ద బాడీలతో సహా) ఆరోగ్యకరమైన-హీప్ ఫాస్ట్ పాత్ (#10437) కోసం పరిమిత అదనపు సామర్థ్యం. ఈ పరిమితి లేకుండా, ప్రతి బిజీగా ఉన్నప్పటికీ ఆరోగ్యకరమైన-హీప్ అభ్యర్థన ఎటువంటి గరిష్ఠ పరిమితి లేకుండా అడ్మిషన్ను దాటవేసేది. హెల్తీ-హీప్ పాత్ ద్వారా ఇన్ని సమకాలీన లీజ్లు యాక్టివ్ అయిన తర్వాత, తదుపరి బిజీ అభ్యర్థనలు వాస్తవ హీప్ ఒత్తిడిలో ఉపయోగించే అదే పరిమిత-నిరీక్షణ/షెడ్ పాత్కు వెళ్తాయి. `0` బైపాస్ను పూర్తిగా నిలిపివేస్తుంది. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | చాట్ అభ్యర్థన బాడీ బైట్ థ్రెషోల్డ్ కంటే తక్కువగా ఉన్నప్పటికీ దాన్ని హెవీవెయిట్గా వర్గీకరించే సందేశాల సంఖ్య. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | చాట్ అభ్యర్థన బాడీ బైట్ థ్రెషోల్డ్ కంటే తక్కువగా ఉన్నప్పటికీ దాన్ని హెవీవెయిట్గా వర్గీకరించే టూల్ల సంఖ్య. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | అభ్యర్థనను హెవీవెయిట్గా వర్గీకరించే సాంప్రదాయబద్ధమైన స్ట్రింగ్-పరిమాణ టోకెన్ అంచనా; ఇది అడ్మిషన్-ఖర్చు ప్రాక్సీ మాత్రమే, ప్రొవైడర్ బిల్లింగ్ టోకనైజేషన్ కాదు. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (నిలిపివేయబడింది) | `src/shared/middleware/chatBodyAdmission.ts` | ఐచ్ఛికంగా ప్రారంభించగల చాట్ చరిత్ర పరిమితి. డిఫాల్ట్గా నిలిపివేయబడింది: సందేశాల సంఖ్య అనేది డిప్లాయ్మెంట్ విధానం, అభ్యర్థన యొక్క సార్వత్రిక లక్షణం కాదు; ఇక్కడ పరిమితం చేయడం వల్ల కంప్రెషన్ పైప్లైన్ వాటిని సేవలందించగలిగేలా చేయడానికి ముందే సంభాషణలు అంతిమ `413`తో తిరస్కరించబడతాయి. హీప్ వృద్ధి `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` మరియు హీప్-ఒత్తిడి షెడ్ ద్వారా పరిమితం చేయబడుతుంది. కఠినమైన గరిష్ఠ పరిమితి అవసరమైన మెమరీ-పరిమిత డిప్లాయ్మెంట్లలో ధనాత్మక విలువను సెట్ చేయండి; అప్పుడు పరిమితిని మించిన వాటికి నిర్మితమైన compact-required `413` అందుతుంది. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | పూర్తిగా మెమరీలోకి బఫర్ చేయబడే నాన్-స్ట్రీమింగ్ అప్స్ట్రీమ్ ప్రతిస్పందనకు కఠిన పరిమితి. దీనిని దాటితే అప్స్ట్రీమ్ రీడర్ రద్దు చేయబడుతుంది మరియు హీప్ ఖాళీ అయ్యే వరకు అపరిమిత స్ట్రింగ్ పెరగనివ్వకుండా అభ్యర్థన వేగంగా విఫలమవుతుంది. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | అప్స్ట్రీమ్ ప్రతిస్పందన హెడర్ల నుంచి ఫార్వర్డ్ చేయగల గరిష్ఠ వైర్ బైట్లు. బడ్జెట్ మించిపోయినప్పుడు, సాధారణ రివర్స్-ప్రాక్సీ హెడర్ పరిమితుల్లో ఉండేందుకు తక్కువ ప్రాధాన్యత గల హెడర్లు (ఉదా., అనుకూల `x-codex-*`, `x-oai-request-id`) తొలగించబడతాయి. ప్రతిస్పందన హెడర్ పరిమాణం పెరుగుతుందనే మూల్యంతో మరింత అప్స్ట్రీమ్ మెటాడేటాను ఫార్వర్డ్ చేయడానికి దీన్ని ఎక్కువ విలువకు సెట్ చేయండి. | +| `CORS_ORIGIN` | _(సెట్ చేయలేదు)_ | `src/server/cors/origins.ts` | పాత ఒకే-మూలం CORS అనుమతి జాబితా. కొత్త డిప్లాయ్మెంట్ల కోసం `CORS_ALLOWED_ORIGINS`కు ప్రాధాన్యం ఇవ్వండి. CORS అనేది క్రాస్-ఆరిజిన్ బ్రౌజర్ API క్లయింట్ల కోసం మాత్రమే; ప్రమాణీకరించిన డ్యాష్బోర్డ్ రైట్లు బదులుగా అదే-మూల అభ్యర్థనలతో పాటు సెషన్కు అనుసంధానించిన CSRF రక్షణను ఉపయోగిస్తాయి. | +| `CORS_ALLOWED_ORIGINS` | _(సెట్ చేయలేదు)_ | `src/server/cors/origins.ts` | కామాలతో వేరు చేసిన CORS అనుమతి జాబితా. `CORS_ALLOW_ALL=true`ను స్పష్టంగా కాన్ఫిగర్ చేస్తే తప్ప వైల్డ్కార్డ్ పంపబడదు. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ఏ బ్రౌజర్ `Origin`నైనా ప్రతిధ్వనించేందుకు డెవలప్మెంట్లో మాత్రమే ఉపయోగించాల్సిన ప్రత్యామ్నాయ మార్గం. భాగస్వామ్య లేదా ప్రొడక్షన్ డిప్లాయ్మెంట్లలో దీన్ని ప్రారంభించవద్దు. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | లెగసీ అలియాస్. దీని డ్యాష్‌బోర్డ్ స్విచ్ (ఒక DB ఓవర్‌రైడ్) ఈ ఎన్విరాన్‌మెంట్ విలువ కంటే ముందే చదవబడుతుంది; రెండింటిలో ఏదైనా ఒకటి `false`, `0`, `no` లేదా `off` అయితే, ఆ ఫ్లాగ్ యొక్క డ్యాష్‌బోర్డ్ స్విచ్ ఆఫ్‌గా సేవ్ చేయబడి ఉన్నా కూడా, హోస్ట్ తనిఖీలు ఆఫ్ అవుతాయి — `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` చేసినట్టుగా; ఇతర విలువలు గార్డ్‌ను కింది రెండు ఫ్లాగ్‌లకు వదిలివేస్తాయి. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | గార్డ్ మోడ్‌ను అనుసరించే ప్రొవైడర్ మార్గాల్లో — URL ధ్రువీకరణ, మోడల్ డిస్కవరీ, ప్రొవైడర్-నోడ్ బేస్ URLలు (రిమోట్ రీరాంక్ నోడ్‌లతో సహా), ప్రాక్సీ-ఫాల్‌బ్యాక్ టెస్ట్, మరియు రిమోట్ ఇమేజ్/మీడియా ఫెచ్ వంటి ఇతర ప్రొవైడర్-సంబంధిత అవుట్‌బౌండ్ మార్గాల్లో — అవుట్‌బౌండ్ URL గార్డ్ యొక్క హోస్ట్ తనిఖీలను, క్లౌడ్-మెటాడేటా బ్లాక్‌తో సహా (169.254.0.0/16 మొత్తం, metadata.google.internal), ఆఫ్ చేస్తుంది. ఇది ప్రైవేట్ webhook లక్ష్యాలను కూడా అనుమతిస్తుంది; కానీ క్లౌడ్-మెటాడేటా/లింక్-లోకల్ ఎండ్‌పాయింట్‌లు ఈ ఫ్లాగ్ ఆన్‌లో ఉన్నా కూడా నిరోధించబడినట్లే ఉంటాయి. `BaseExecutor.execute()` ద్వారా పంపబడే చాట్ అభ్యర్థనలు వాటి క్లౌడ్-మెటాడేటా తనిఖీని నిలుపుకుంటాయి. లోకల్ ప్రొవైడర్‌లు డిఫాల్ట్‌లతోనే పనిచేస్తాయి: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (డిఫాల్ట్ `true`) ఇప్పటికే ధ్రువీకరణ, డిస్కవరీ మరియు ప్రొవైడర్-నోడ్ మార్గాల్లో లోకల్/LAN URLలను అనుమతిస్తుంది; ప్రాక్సీ-ఫాల్‌బ్యాక్ టెస్ట్ మరియు ప్రైవేట్ webhook లక్ష్యాలు ఈ ఫ్లాగ్‌ను మాత్రమే చూస్తాయి మరియు ఇది ఆఫ్‌లో ఉన్నంతవరకు లోకల్/LAN హోస్ట్‌లను బ్లాక్ చేస్తాయి. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`ను ఓవర్‌రైడ్ చేస్తుంది. డ్యాష్‌బోర్డ్ స్విచ్ నుండి సేవ్ చేయబడిన విలువ ఈ వేరియబుల్ కంటే ప్రాధాన్యత పొందుతుంది. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ప్రొవైడర్ ధ్రువీకరణ, మోడల్ డిస్కవరీ, చాట్ మరియు గార్డ్ మోడ్ నియంత్రించే ఇతర ప్రొవైడర్-సంబంధిత అవుట్‌బౌండ్ మార్గాల కోసం లోకల్/ప్రైవేట్ చిరునామాల్లో (127.0.0.1, localhost, LAN, ప్రైవేట్ పరిధులు) ప్రొవైడర్ URLలను అనుమతించండి. **డిఫాల్ట్ `true`** (లోకల్‌కు ప్రథమ ప్రాధాన్యం): అప్పుడు గార్డ్ క్లౌడ్-మెటాడేటా ఎండ్‌పాయింట్‌లను బ్లాక్ చేస్తుంది — 169.254.0.0/16 మొత్తం మరియు తెలిసిన మెటాడేటా హోస్ట్‌పేర్లు (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). పబ్లిక్-మాత్రమే మోడ్ కోసం `false`గా సెట్ చేయండి; అది ప్రైవేట్ మరియు లూప్‌బ్యాక్ హోస్ట్‌లను కూడా బ్లాక్ చేస్తుంది. ఏ DNS లుకప్ జరగక ముందే, గార్డ్ హోస్ట్‌పేమీ లేదా IP లిటరల్‌ను రాసిన విధంగానే తనిఖీ చేస్తుంది; పబ్లిక్-మాత్రమే మోడ్‌లో safe-fetch ర్యాపర్ అదనంగా హోస్ట్‌పేమీను రిజాల్వ్ చేసి, ఏదైనా ప్రైవేట్ సమాధానాన్ని తిరస్కరిస్తుంది. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ఈ తనిఖీలను దాని వరుస జాబితా చేసే మార్గాల్లో ఆఫ్ చేస్తుంది. LM Studio, Ollama, vLLM, Llamafile, Triton వంటి బిల్ట్-ఇన్ లోకల్ ప్రొవైడర్‌లు కీ ధ్రువీకరణ మరియు చాట్ కోసం అన్ని హోస్ట్ తనిఖీలను వదిలివేస్తాయి. డ్యాష్‌బోర్డ్ స్విచ్ నుండి సేవ్ చేయబడిన విలువ ఈ వేరియబుల్ కంటే ప్రాధాన్యత పొందుతుంది. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` రూట్లు (ట్రాన్స్క్రిప్షన్లు, స్పీచ్, అనువాదాలు) localhostకు వెలుపల హోస్ట్ చేసిన OpenAI-అనుకూల ప్రొవైడర్ నోడ్ను ఉపయోగించేందుకు అనుమతించండి. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది — ఆడియోను రిమోట్ హోస్ట్కు రూట్ చేయడం వల్ల ఎగ్రెస్ గుర్తింపు మారుతుంది, కాబట్టి ఇది ఆపరేటర్ స్పష్టంగా తీసుకోవాల్సిన నిర్ణయం. లూప్బ్యాక్/ప్రైవేట్ నోడ్లు (localhost, 127.0.0.1, 172.16-31.x) ఎల్లప్పుడూ అనుమతించబడతాయి మరియు దీనివల్ల ప్రభావితం కావు. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (మరియు మెమరీ ఇంజిన్ యొక్క లూప్బ్యాక్ రీరాంక్ దశ) localhostకు వెలుపల హోస్ట్ చేసిన OpenAI-అనుకూల ప్రొవైడర్ నోడ్ను ఉపయోగించేందుకు అనుమతించండి — అంటే TEI, Infinity, vLLM మొదలైనవి నడుపుతున్న LAN మెషీన్ లేదా Tailscale పీర్. డిఫాల్ట్గా ఆఫ్లో ఉంటుంది — రిమోట్ హోస్ట్కు రూట్ చేయడం వల్ల ఎగ్రెస్ గుర్తింపు మారుతుంది, కాబట్టి ఇది ఆపరేటర్ స్పష్టంగా తీసుకోవాల్సిన నిర్ణయం. లూప్బ్యాక్ నోడ్లు (localhost, 127.0.0.1, 172.16-31.x) ఎల్లప్పుడూ అనుమతించబడతాయి మరియు దీనివల్ల ప్రభావితం కావు. రిమోట్ నోడ్ యొక్క బేస్ URL కూడా ప్రొవైడర్ అవుట్‌బౌండ్ URL విధానాన్ని (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) దాటాలి. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC ప్రారంభించబడినప్పుడు, వినియోగదారులు OIDC Single Sign-On ద్వారా మాత్రమే ప్రమాణీకరించగలిగేలా పాస్వర్డ్ లాగిన్ను నిలిపివేయండి. సాధారణ అలియాస్ `OIDC_DISABLE_PASSWORD_LOGIN` కూడా ఆమోదించబడుతుంది; అదే కీ కలిగిన డ్యాష్బోర్డ్ ఫీచర్ ఫ్లాగ్కు ప్రాధాన్యం ఉంటుంది. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` యొక్క సాధారణ అలియాస్ (#10889). | ### పటిష్ఠత తనిఖీ జాబితా diff --git a/docs/i18n/th/CONTRIBUTING.md b/docs/i18n/th/CONTRIBUTING.md index a4c6f3ded19a..503a1d51b689 100644 --- a/docs/i18n/th/CONTRIBUTING.md +++ b/docs/i18n/th/CONTRIBUTING.md @@ -394,15 +394,15 @@ docs/ # Documentation - [ ] การ Build สำเร็จ (`npm run build`) - [ ] เพิ่ม TypeScript types สำหรับ public functions และ interfaces ใหม่แล้ว - [ ] ไม่มี secrets หรือ fallback values ที่ hardcode ไว้ -- [ ] Public upstream credentials ถูกฝังผ่าน `resolvePublicCred()` (ดู [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) และห้ามใช้เป็น literals +- [ ] ฝัง public upstream credentials ผ่าน `resolvePublicCred()` (ดู [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) และไม่ใช้ literals โดยเด็ดขาด - [ ] Error responses ส่งผ่าน `buildErrorBody()` / `sanitizeErrorMessage()` — ไม่มี raw stack traces ใน response bodies (ดู [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) - [ ] Shell commands (`exec` / `spawn`) ส่ง runtime values ผ่าน `env` ไม่ใช่ผ่าน string interpolation -- [ ] Inputs ทั้งหมดได้รับการตรวจสอบด้วย Zod schemas -- [ ] เพิ่ม **fragment** ของ Changelog ภายใต้ `changelog.d/{features|fixes|maintenance}/-.md` สำหรับการเปลี่ยนแปลงที่ส่งผลต่อผู้ใช้แล้ว (ดู [`changelog.d/README.md`](./changelog.d/README.md)) — **ห้าม** แก้ไข `CHANGELOG.md` โดยตรง; fragments จะถูกรวบรวมเมื่อออกรุ่น และจะไม่เกิดข้อขัดแย้งระหว่าง PRs +- [ ] ตรวจสอบ inputs ทั้งหมดด้วย Zod schemas +- [ ] เพิ่ม **fragment** ของ Changelog ภายใต้ `changelog.d/{features|fixes|maintenance}/-.md` สำหรับการเปลี่ยนแปลงที่ส่งผลต่อผู้ใช้ (ดู [`changelog.d/README.md`](./changelog.d/README.md)) — **ห้าม** แก้ไข `CHANGELOG.md` โดยตรง; fragments จะถูกรวบรวมเมื่อเผยแพร่ release และจะไม่เกิด conflict ระหว่าง PRs - [ ] อัปเดตเอกสารแล้ว (หากเกี่ยวข้อง) -- [ ] ไม่มี CodeQL / Secret-Scanning alerts ใหม่ หรือแต่ละรายการถูกปิดโดยมีเหตุผลเชิงเทคนิคที่อ้างอิงเอกสาร `docs/security/` ที่เกี่ยวข้อง -- [ ] Routes ที่ spawn child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) ถูกจัดประเภทเป็น `isLocalOnlyPath()` ใน `src/server/authz/routeGuard.ts` — ดู [กฎเคร่งครัด #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] ไม่มี trailers `Co-Authored-By` ใน commit messages — commits ต้องแสดงภายใต้ Git identity ของเจ้าของ repository เท่านั้น (กฎเคร่งครัด #16) +- [ ] ไม่มี CodeQL / Secret-Scanning alerts ใหม่ หรือแต่ละรายการถูก dismiss พร้อมเหตุผลทางเทคนิคที่อ้างอิงเอกสาร `docs/security/` ที่เกี่ยวข้อง +- [ ] Routes ที่ spawn child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) ถูกจัดประเภทเป็น `isLocalOnlyPath()` ใน `src/server/authz/routeGuard.ts` — ดู [กฎบังคับ #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] ไม่มี trailers `Co-authored-by` ของ AI/bot ใน commit messages (กฎบังคับ #16) — human collaborators ที่นำผลงานมาใช้จะได้รับเครดิตด้วย trailers มาตรฐาน `Co-authored-by: Name ` --- diff --git a/docs/i18n/th/docs/architecture/QUALITY_GATES.md b/docs/i18n/th/docs/architecture/QUALITY_GATES.md index e86575f8303a..febebab693fb 100644 --- a/docs/i18n/th/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/th/docs/architecture/QUALITY_GATES.md @@ -15,264 +15,264 @@ --- -## รายการเกตและโปรไฟล์การดำเนินงาน +## รายการเกตและโปรไฟล์การดำเนินการ ### การรับแคนดิเดต เวิร์กโฟลว์ CI และ Quality Gates ต่างให้ผลตัดสินที่คงที่ ได้แก่ `Gate / CI` และ -`Gate / Quality` นโยบายการรับเข้าแบบมีเวอร์ชันของเวิร์กโฟลว์เหล่านี้ระบุงานต้นทางทุกงาน -ว่าเป็นงานที่จำเป็นหรืองานเชิงแนะนำ งานที่จำเป็นและมีผลบังคับใช้ต้องสำเร็จ: ผลลัพธ์ที่หายไป -ถูกยกเลิก ถูกข้าม อยู่ระหว่างดำเนินการ หรือไม่ทราบ ไม่สามารถใช้ยืนยัน PASS ได้ การจัดประเภท -เฉพาะเอกสารหรือเฉพาะแค็ตตาล็อกที่ถูกต้องอาจทำให้เลนโค้ดไม่มีผลบังคับใช้ได้ -แต่ PR แบบร่างไม่ถือเป็นแคนดิเดตที่ได้รับการยอมรับ ป้ายกำกับ `hotfix` ไม่ได้ยกเว้นข้อกำหนดด้านหลักฐาน - -ทั้งสองเวิร์กโฟลว์ครอบคลุม PR และการพุชไปยังสาขา main/release, การสั่งทำงานด้วยตนเอง และ -เหตุการณ์ merge-group การพุช การสั่งทำงาน และ merge-group จะเรียกใช้ชุดการตรวจสอบทั้งหมด Fork -และ merge group ใช้รันเนอร์แบบโฮสต์สำหรับงานที่ในกรณีอื่นจะเลือกรันเนอร์แบบโฮสต์เอง -โดยต้องตรวจสอบว่ามีความจุแบบโฮสต์เพียงพอก่อนเปิดใช้งาน - -ใบรับรอง JSON แต่ละรายการระบุ SHA ที่เช็กเอาต์ การรันเวิร์กโฟลว์ และความพยายามในการรัน -CLI จะปฏิเสธเมื่อ SHA ของการเช็กเอาต์และเหตุการณ์ไม่ตรงกัน การทดสอบเวิร์กโฟลว์จะผูกสมาชิกของนโยบาย -เข้ากับรายการ `needs` ของงานที่ตัดสินผล เพื่อไม่ให้เลนที่เพิ่มใหม่หรือถูกลบหายไปโดยไม่มีการแจ้งเตือน -ใบรับรองครอบคลุมเฉพาะเวิร์กโฟลว์ของตนเอง ไม่ครอบคลุมการเผยแพร่ การปรับใช้ หรือการทำงานภายใน -ของสแกนเนอร์เชิงแนะนำที่มีอยู่ การเปิดใช้ชื่อการตรวจสอบทั้งสองชื่อในกฎของสาขาเป็น -การเปลี่ยนแปลงด้านการดูแลระบบที่แยกต่างหาก การเพิ่มงานเหล่านี้ไม่ได้ปกป้องสาขาโดยตัวมันเอง - -### รายการการสแกนแบบสถิต - -รายการ npm-alias แบบมีเวอร์ชันและสมาชิกของการสแกนแบบสถิตอยู่ใน -`config/quality/gate-manifest.json` เรียกใช้ `npm run check:gate-manifest` เพื่อตรวจสอบ -ชื่อสคริปต์และคำสั่งที่ตรงกันทุกประการกับ `package.json`; การเพิ่ม การลบ และ -การเปลี่ยนแปลงของคำสั่งจะทำให้ทั้งฮุกภายในเครื่องและงานจัดประเภทการเปลี่ยนแปลงใน CI ล้มเหลว -alias ไม่ใช่งานเวิร์กโฟลว์ อินสแตนซ์เมทริกซ์ หรือกรณีทดสอบ: ห้ามนำจำนวนเหล่านี้ -มาแสดงราวกับว่าสามารถใช้แทนกันได้ +`Gate / Quality` นโยบายการรับที่มีการกำหนดเวอร์ชันของแต่ละเวิร์กโฟลว์ระบุทุกงานต้นทาง +ว่าเป็นงานบังคับหรืองานให้คำแนะนำ งานบังคับที่เกี่ยวข้องต้องสำเร็จ โดยผลลัพธ์ที่หายไป +ถูกยกเลิก ถูกข้าม อยู่ระหว่างดำเนินการ หรือไม่ทราบสถานะ ไม่สามารถยืนยัน PASS ได้ การจัดประเภท +docs-only หรือ catalog-only ที่ถูกต้องอาจทำให้เลนโค้ดไม่เกี่ยวข้องได้ +แต่ PR แบบร่างไม่ถือเป็นแคนดิเดตที่ยอมรับได้ ป้ายกำกับ `hotfix` ไม่ได้ยกเว้นข้อกำหนดด้านหลักฐาน + +ทั้งสองเวิร์กโฟลว์ครอบคลุม PR และการพุชไปยังบรานช์ main/release การสั่งทำงานด้วยตนเอง และ +เหตุการณ์ merge-group การพุช การสั่งทำงาน และ merge-group จะรันชุดการตรวจสอบทั้งหมด Fork +และ merge group ใช้ hosted runner สำหรับงานที่โดยปกติจะเลือก self-hosted runner +โดยต้องตรวจสอบว่ามีขีดความสามารถของ hosted runner เพียงพอก่อนเปิดใช้งาน + +ใบรับรอง JSON แต่ละรายการระบุ SHA ที่ checkout, workflow run และ attempt +CLI จะปฏิเสธเมื่อ SHA ของ checkout/event ไม่ตรงกัน การทดสอบเวิร์กโฟลว์ผูกสมาชิกของนโยบาย +เข้ากับรายการ `needs` ของงานตัดสินผล เพื่อไม่ให้เลนที่เพิ่มใหม่หรือถูกลบหายไปโดยไม่มีการแจ้งเตือน +ใบรับรองครอบคลุมเฉพาะเวิร์กโฟลว์ของตนเอง ไม่ครอบคลุมการเผยแพร่ การดีพลอย หรือรายละเอียดภายใน +ของ advisory scanner ที่มีอยู่ การเปิดใช้งานชื่อการตรวจสอบทั้งสองในกฎของบรานช์เป็น +การเปลี่ยนแปลงด้านการดูแลระบบที่แยกต่างหาก การเพิ่มงานเหล่านี้ไม่ได้ปกป้องบรานช์โดยอัตโนมัติ + +### รายการ static scan + +รายการ npm-alias ที่มีการกำหนดเวอร์ชันและสมาชิกของ static-scan อยู่ใน +`config/quality/gate-manifest.json` รัน `npm run check:gate-manifest` เพื่อตรวจสอบ +ชื่อสคริปต์และคำสั่งที่ตรงกันทุกประการเทียบกับ `package.json` การเพิ่ม การลบ และ +การเปลี่ยนแปลงคำสั่งจะทำให้ทั้ง local hook และงานจำแนกการเปลี่ยนแปลงใน CI ล้มเหลว +alias ไม่ใช่งานเวิร์กโฟลว์ matrix instance หรือ test case ดังนั้นต้องไม่แสดงจำนวนเหล่านี้ +ราวกับว่าสามารถใช้แทนกันได้ ใช้ `npm run quality:scan -- --list` หรือ `npm run quality:scan:fast -- --list` -เพื่อตรวจสอบ alias ที่เลือกโดยไม่เรียกใช้งาน รันเนอร์จะเรียกใช้ -จุดเริ่มต้นของ npm ดังนั้นรันไทม์ของจุดเริ่มต้นดังกล่าว (รวมถึง Bun ในตำแหน่งที่กำหนดค่าไว้) จึงยังคงเดิม -แมนิเฟสต์จะบันทึก alias ที่อยู่นอกโปรไฟล์เหล่านั้นว่าเรียกใช้แยกต่างหาก และ -ห้ามใช้คำสั่งบำรุงรักษาในโปรไฟล์การสแกนแบบอ่านอย่างเดียว - -โปรไฟล์เหล่านี้ครอบคลุมเฉพาะการสแกนแบบสถิต โดยไม่ได้รับรองการทดสอบผลิตภัณฑ์ -ความครอบคลุม การแพ็กเกจ การตรวจสอบภายนอก หรือการยอมรับแคนดิเดตสำหรับรีลีสอย่างครบถ้วน -การรับเข้าโดยเวิร์กโฟลว์ใช้ `config/quality/admission-policy.json` ที่เชื่อมโยงไว้และ +เพื่อตรวจสอบ alias ที่เลือกโดยไม่เรียกใช้งาน runner จะเรียกผ่าน +npm entrypoint ดังนั้น runtime ของรายการดังกล่าว (รวมถึง Bun เมื่อกำหนดค่าไว้) จะยังคงเดิม +manifest บันทึก alias ที่อยู่นอกโปรไฟล์เหล่านี้ว่าเป็นรายการที่เรียกแยกต่างหาก และ +ห้ามใช้คำสั่งบำรุงรักษาในโปรไฟล์สแกนแบบอ่านอย่างเดียว + +โปรไฟล์เหล่านี้ครอบคลุมเฉพาะ static scan เท่านั้น ไม่ได้รับรองการทดสอบผลิตภัณฑ์ +coverage, packaging, การตรวจสอบภายนอก หรือการยอมรับแคนดิเดตสำหรับรีลีสอย่างครบถ้วน +การรับของเวิร์กโฟลว์ใช้ `config/quality/admission-policy.json` ที่เชื่อมโยงไว้และ `scripts/quality/admission-verdict.mjs` โปรไฟล์ release-observer ยังคงแยกต่างหาก -ให้ตรวจสอบการตรวจเช็กและใบรับรองที่มีผลบังคับใช้ของโปรไฟล์เหล่านั้นโดยอิสระ ข้อมูลรายการ -แบบข้อความด้านล่างนี้มีไว้ใช้อ้างอิง ไม่ใช่หลักฐานว่าเกตได้ทำงานจริง +ให้ตรวจสอบการตรวจเช็กและใบรับรองที่เกี่ยวข้องของโปรไฟล์เหล่านั้นโดยอิสระ รายการแบบร้อยแก้ว +ด้านล่างเป็นข้อมูลอ้างอิง ไม่ใช่หลักฐานว่าเกตได้ทำงานจริง -สคริปต์อยู่ภายใต้ `scripts/check/` (เกตตามนโยบาย) และ `scripts/quality/` (กลไก ratchet) +สคริปต์อยู่ภายใต้ `scripts/check/` (เกตนโยบาย) และ `scripts/quality/` (กลไก ratchet) แหล่งข้อมูลจริงของ CI คือ `.github/workflows/ci.yml` ### เส้นทางด่วนสำหรับ Release PR (`quality.yml`) -`.github/workflows/quality.yml` เสริมการทำงานของ CI สำหรับ PR ของ main/release, -การพุชไปยังสาขาที่ได้รับการปกป้อง การสั่งทำงาน และ merge group โดย PR จะใช้การตรวจสอบแบบรวดเร็ว -ที่กรองตามพาธ บิลด์ซ้ำที่ปิดใช้งานอย่างถาวรถูกลบออกแล้ว ส่วนการตรวจสอบบิลด์/แพ็กเกจ/การบูตจริงยังคงอยู่ใน CI +`.github/workflows/quality.yml` ทำงานเสริม CI สำหรับ PR ของ main/release, การพุชไปยังบรานช์ +ที่ได้รับการปกป้อง การสั่งทำงาน และ merge group โดย PR จะใช้การตรวจสอบแบบเร็วที่กรองตาม path +การ build ซ้ำที่ถูกปิดใช้งานอย่างถาวรถูกลบแล้ว ส่วนการตรวจสอบ build/package/boot จริงยังคงอยู่ใน CI -| งาน | ขอบเขต | มีผลบล็อก | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `Docs Gates (fast-path)` | PR ด้านเอกสาร/โค้ด; การอ้างอิงเอกสาร API และเอกสารทั้งหมด | ใช่ | -| `Fast Quality Gates` | PR ด้านโค้ด; การตรวจสอบแบบสถิต, typecheck, typecheck ของแดชบอร์ด, การทดสอบหน่วยที่ได้รับผลกระทบ | ใช่ | -| `Forgotten sibling tests` | PR ด้านโค้ด; ติดตามโมดูลที่เปลี่ยนแปลงไปยังผู้ใช้แบบสถิตและการทดสอบ sibling ที่เป็นไปได้; มีการรายงานพาธของ barrel และ dynamic-import เป็นการวินิจฉัยเชิงแนะนำ พร้อมข้อยกเว้นใน allowlist ที่อ้างอิงไว้ | **เชิงแนะนำ** | -| `Vitest (fast-path)` | PR ด้านโค้ด; ชุดทดสอบ vitest แบบรวดเร็ว | ใช่ | -| `Unit Tests fast-path` | PR ด้านโค้ด; ชุดทดสอบหน่วยแบบ 4 shard | ใช่ | -| `No new ESLint warnings` | PR ด้านโค้ด; การ์ด lint ที่รับรู้ suppression | ใช่ รวมถึง fork | -| `Merge integrity (changelog + generated skills)` | PR ที่ไม่ใช่แบบร่าง; การซิงค์ changelog และ skill ที่สร้างขึ้น | ใช่ รวมถึง fork | +| งาน | ขอบเขต | การบล็อก | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `Docs Gates (fast-path)` | PR ของเอกสาร/โค้ด; การอ้างอิงเอกสาร API และ docs-all | ใช่ | +| `Fast Quality Gates` | PR ของโค้ด; การตรวจสอบแบบ static, typecheck, typecheck ของแดชบอร์ด และ unit test ที่ได้รับผลกระทบ | ใช่ | +| `Forgotten sibling tests` | PR ของโค้ด; ติดตามโมดูลที่เปลี่ยนแปลงไปยัง static consumer และ sibling test ที่เป็นไปได้ โดยรายงานเส้นทางผ่าน barrel และ dynamic-import เป็นการวินิจฉัยเชิงคำแนะนำ พร้อมข้อยกเว้น allowlist ที่อ้างอิงไว้ | **ให้คำแนะนำ** | +| `Vitest (fast-path)` | PR ของโค้ด; ชุดทดสอบ vitest แบบเร็ว | ใช่ | +| `Unit Tests fast-path` | PR ของโค้ด; ชุด unit test แบบ 4 shard | ใช่ | +| `No new ESLint warnings` | PR ของโค้ด; ตัวป้องกัน lint ที่คำนึงถึง suppression | ใช่ รวมถึง fork | +| `Merge integrity (changelog + generated skills)` | PR ที่ไม่ใช่แบบร่าง; การซิงค์ changelog และ skill ที่สร้างขึ้น | ใช่ รวมถึง fork | -#### รายงานการทดสอบ sibling ที่ถูกลืม +#### รายงาน Forgotten sibling tests -`npm run check:forgotten-sibling-tests` ใช้ตัวแก้ไข import ที่อยู่เบื้องหลังแมปผลกระทบต่อการทดสอบซ้ำ +`npm run check:forgotten-sibling-tests` ใช้ import resolver ที่อยู่เบื้องหลังแผนที่ผลกระทบต่อการทดสอบซ้ำ สำหรับทุกโมดูล production ที่เปลี่ยนแปลง ระบบจะรายงานสายโซ่ -`changed module/symbol -> static consumer -> candidate sibling test` แบบกำหนดผลได้ เมื่อการทดสอบ -ที่เป็นไปได้นั้นไม่มีอยู่ใน diff ของ pull request ข้อมูลสรุป Markdown และผลลัพธ์ JSON จะถูกเก็บไว้เป็น -อาร์ติแฟกต์เวิร์กโฟลว์ `forgotten-sibling-tests` เพื่อการปรับเทียบก่อนเปิดใช้งานให้มีผลบล็อกใดๆ - -การ re-export ผ่าน barrel และ dynamic import ใช้เพื่อวินิจฉัยการ resolve เท่านั้น โดยจะไม่ก่อให้เกิด -ผลการตรวจสอบที่บล็อกการดำเนินการ ข้อยกเว้นที่ผ่านการตรวจสอบแล้วอยู่ใน -`config/quality/forgotten-sibling-allowlist.json` แต่ละรายการต้องระบุ consumer และการทดสอบที่เป็น -candidate ให้เหตุผลที่เฉพาะเจาะจง และลิงก์ไปยัง GitHub issue หรือ pull request รายการที่มีรูปแบบไม่ถูกต้องจะ -ถูกปฏิเสธโดยค่าเริ่มต้น ข้อยกเว้นไม่สามารถระงับกรณีที่การทดสอบ candidate ถูกลบ หรือ diff ที่เพิ่ม `.skip`/`.todo`; -การลดความเข้มงวดของ assertion และการปกปิดปัญหาในรูปแบบอื่นยังคงอยู่ภายใต้ความรับผิดชอบของเกต -`check:test-masking` ซึ่งบล็อกอย่างเป็นอิสระ +`changed module/symbol -> static consumer -> candidate sibling test` ที่ให้ผลลัพธ์แบบกำหนดแน่นอน +เมื่อการเปลี่ยนแปลงของ pull request ไม่มีการทดสอบที่เป็นไปได้ดังกล่าว สรุป Markdown และผลลัพธ์ JSON +จะถูกเก็บไว้เป็นอาร์ติแฟกต์เวิร์กโฟลว์ `forgotten-sibling-tests` เพื่อใช้ปรับเทียบก่อนเปิดใช้การบล็อกใดๆ + +การส่งออกซ้ำผ่าน barrel และ dynamic imports เป็นเพียงการวินิจฉัยการ resolve เท่านั้น โดยจะไม่ก่อให้เกิด +ผลการตรวจที่บล็อกการดำเนินการ ข้อยกเว้นที่ผ่านการตรวจสอบแล้วอยู่ใน +`config/quality/forgotten-sibling-allowlist.json` แต่ละรายการต้องระบุ consumer และ candidate +test ให้เหตุผลที่เฉพาะเจาะจง และลิงก์ไปยัง GitHub issue หรือ pull request รายการที่มีรูปแบบไม่ถูกต้องจะล้มเหลว +แบบปิด ข้อยกเว้นไม่สามารถระงับ candidate test ที่ถูกลบหรือ diff ที่เพิ่ม `.skip`/`.todo` +ได้ ส่วนการทำให้ assertion อ่อนลงและการปกปิดรูปแบบอื่น ๆ ยังคงอยู่ภายใต้ gate +`check:test-masking` ซึ่งบล็อกการดำเนินการโดยอิสระ ### งาน: `lint` -ทำงานกับทุก PR ที่ส่งไปยัง `main` และบล็อกการ merge เมื่อดำเนินการไม่สำเร็จ - -| สคริปต์ (`npm run ...`) | สิ่งที่ตรวจสอบ | บล็อกหรือไม่ | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | -| `check:node-runtime` | เวอร์ชัน Node.js อยู่ภายในช่วงที่รองรับ | ใช่ | -| `check:cycles` | Circular import — โมดูลทั้งหมดใน `src/` + `open-sse/` | ใช่ | -| `check:route-validation:t06` | มี Zod schema ในทุก route (นโยบาย Tier 6) | ใช่ | -| `check:any-budget:t11` | จำนวน `@ts-expect-error // any` ไม่เกินงบประมาณที่กำหนด (catraca ระดับ Tier 11) | ใช่ | -| `check:provider-consistency` | ผู้ให้บริการทุกรายใน `providers.ts` มีรายการที่ตรงกันใน `providerRegistry.ts` (และในทางกลับกัน ภายใน allowlist) | ใช่ | -| `check:model-lifecycle` | ตารางการกำหนดเส้นทางสามตารางที่ดูแลด้วยตนเองยังคงสอดคล้องกับสแนปช็อตวงจรชีวิตที่เช็กอินไว้ (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ไม่ให้คะแนน id ที่เลิกใช้งานแล้วซึ่ง `REGISTRY` สามารถกำหนดเส้นทางได้; เป้าหมายของ `BUILT_IN_ALIASES` ทุกรายการมีอยู่ใน `REGISTRY` และไม่อยู่ในสแนปช็อต id ที่เลิกใช้งานแล้ว; id ที่เลิกใช้งานแล้วทุกรายการซึ่งยังอยู่ใน `REGISTRY` จะถูกส่งต่อหรือระบุไว้ใน `allowedRetiredInCatalog`; และไม่มีต้นทางหรือปลายทางของ `DEFAULT_DEGRADATION_MAP` รายการใดปรากฏว่าเลิกใช้งานแล้วในสแนปช็อตดังกล่าว ทั้งนี้ไม่ได้พิสูจน์ว่าโมเดลกำลังให้บริการโดย upstream ที่ใช้งานอยู่ในปัจจุบัน ทำงานแบบออฟไลน์ — เปรียบเทียบกับ `config/quality/model-lifecycle.json` ซึ่งรีเฟรชด้วยตนเองโดยใช้ `npm run quality:refresh-model-lifecycle` (ใช้เครือข่าย; ไม่ได้เชื่อมเข้ากับ CI) `allowedRetiredInCatalog` เป็นกลไก ratchet สำหรับทยอยลดจำนวน: เพิ่มรายการได้เฉพาะเมื่อมี issue สำหรับติดตามเท่านั้น | ใช่ | -| `check:fetch-targets` | `fetch("/api/...")` ทุกรายการใน `src/` ฝั่งไคลเอนต์ชี้ไปยัง `route.ts` ที่มีอยู่จริง | ใช่ | -| `check:deps` | dependency ทั้งหมดที่ติดตั้งได้ด้วย `npm install` จาก `package.json` ทุกไฟล์ใน repo อยู่ใน `dependency-allowlist.json`; แพ็กเกจใหม่ที่ไม่ได้ตรึงเวอร์ชันหรืออาจเป็น slopsquatting จะถูกแจ้งเตือน | ใช่ | -| `audit:deps` | `npm audit` (root + electron) — ไม่มีคำเตือนระดับสูง/วิกฤต (ซ้ำซ้อนกับ OSV `check:vuln-ratchet`; ดู Rationalization Backlog) | ใช่ | -| `check:lockfile` | ความถูกต้องสมบูรณ์ของ `package-lock.json` — ใช้รีจิสทรี HTTPS, มีแฮชตรวจสอบความถูกต้อง และไม่มีการแทนที่โฮสต์ | ใช่ | -| `check:licenses` | รายการอนุญาตไลเซนส์ SPDX สำหรับ dependencies ที่ใช้ใน production | ใช่ | -| `check:tracked-artifacts` | ไม่มี build artifacts / symlinks ของ `node_modules` ที่ถูก commit (ทำงานใน husky pre-commit ด้วย; pre-push ถูกออกแบบให้ทำงานแบบเบา — #6716) | ใช่ | -| `check:ai-attribution` | ไม่มี trailer `Co-Authored-By` ของ AI/บอต หรือ footer ที่ระบุการสร้างโดย AI ใน commits, title หรือ body ของ PR — กฎเข้มงวดข้อที่ 16 (อยู่ในลูป fast-gates ของ `quality.yml` สำหรับ PR→`release/**` — อ่าน event payload และไม่ดำเนินการใดๆ หากไม่ใช่ PR — และเป็นขั้นตอนสำหรับ PR เท่านั้นใน lint ของ `ci.yml` สำหรับ PR→`main`; รวมถึง hook `commit-msg` ของ husky ด้วย; อนุญาตผู้เขียนร่วมที่เป็นมนุษย์; #14436) | -| `check:vitest-exclusions` | ทุก exclusion ของ Vitest ต้องระบุ tracking issue และปรากฏใน `config/quality/vitest-exclusions.json` (#13204) | ใช่ | -| `check:file-size` | ไม่มี source file ใดมีขนาดเกินขีดจำกัดของแต่ละนามสกุลไฟล์ (ratchet: ไฟล์ขนาดใหญ่ที่ตรึงไว้ในรายการ `frozen`) | ใช่ | -| `check:error-helper` | การตอบกลับข้อผิดพลาดใน executors/handlers ใช้ `buildErrorBody()` / `sanitizeErrorMessage()` (กฎเข้มงวดข้อที่ 12) | ใช่ | -| `check:migration-numbering` | ไฟล์ Migration SQL มีการกำหนดหมายเลขเรียงตามลำดับ โดยไม่มีหมายเลขขาดหายหรือซ้ำกัน | ใช่ | -| `check:public-creds` | ไม่มี OAuth `client_id`/`client_secret` แบบค่าตรงตัวหรือคีย์ Firebase Web อยู่นอก `publicCreds.ts` (กฎบังคับ #11) | ใช่ | -| `check:db-rules` | ไม่มี SQL ดิบอยู่นอกโมดูล `src/lib/db/`; ไม่มีการนำเข้าแบบ barrel จาก `localDb.ts` (กฎบังคับ #2/#5) | ใช่ | -| `check:known-symbols` | ตัวดำเนินการของผู้ให้บริการ กลยุทธ์การกำหนดเส้นทาง และตัวแปลที่ลงทะเบียนในตาราง dispatch ของตนตรงกับไฟล์บนดิสก์ — ไม่มีสัญลักษณ์ที่ไม่มีการใช้งานหรือไม่ได้ประกาศ | ใช่ | -| `check:route-guard-membership` | ทุกเส้นทางที่สร้างโปรเซสลูกได้รับการจัดประเภทโดย `isLocalOnlyPath()` (กฎบังคับ #15/#17) | ใช่ | -| `check:test-discovery` | ไฟล์ `*.test.ts` / `*.spec.ts` ทุกไฟล์ใน repo ถูกรวบรวมโดย test runner อย่างน้อยหนึ่งตัว (ratchet: รายการไฟล์ที่ไม่มีตัวรวบรวมใน `test-discovery-baseline.json` ลดลงได้เท่านั้น) | ใช่ | -| `check:agent-skills-sync` | อาร์ติแฟกต์ agent-skills ที่สร้างขึ้นตรงกับแค็ตตาล็อกต้นทาง (ไม่มีความคลาดเคลื่อน) | -| `check:provider-asset-provenance` | โลโก้/แอสเซ็ตของผู้ให้บริการมีรายการแหล่งที่มาที่บันทึกไว้ | -| `lint:json` | ไฟล์กำหนดค่า JSON สามารถแยกวิเคราะห์ได้และเป็นไปตามกฎ lint ของ repo | -| `typecheck:core` | คอมไพล์ TypeScript โดยไม่มีข้อผิดพลาด (มีเฉพาะคำเตือนเชิงแนะนำ) | ใช่ | -| `typecheck:noimplicit:core` | `noImplicitAny` แบบเข้มงวด — สำหรับการปรับปรุงในอนาคต; จุดเรียกใช้งานเดิมจำนวนมากยังคงต้องเพิ่มคำอธิบายชนิด | **เชิงแนะนำ** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` ที่จำกัดขอบเขตไว้เฉพาะ `src/app/(dashboard)/**` (#7033) — รายการอนุญาต 27 ไฟล์ที่คัดสรรไว้ของ `typecheck:core` ไม่มี TSX ของแดชบอร์ดรวมอยู่เลย และ `next build` ก็ไม่เคยตรวจสอบชนิดของไฟล์เหล่านั้นเช่นกัน (`next.config.mjs` ตั้งค่า `ignoreBuildErrors: true`) ดังนั้นการถดถอยจากตัวระบุที่ไม่มีการอ้างอิงในบริเวณนั้น (#6625/#6909) จึงไม่ปรากฏต่อ CI โดยจะเปรียบเทียบความแตกต่างกับค่าฐานจำนวนข้อผิดพลาดที่ตรึงไว้แยกตามไฟล์/รหัส TS (`config/quality/dashboard-typecheck-baseline.json` ซึ่งใช้รูปแบบการบังคับใช้กับโค้ดที่ค้างอยู่เช่นเดียวกับ `check:known-symbols`) — เฉพาะข้อผิดพลาดใหม่ที่เกินจำนวนในค่าฐานเท่านั้นที่จะทำให้เกตล้มเหลว; ลดค่าฐานลงด้วย `--update` เมื่อแก้ไขข้อผิดพลาดเดิมได้ | ใช่ | +ทำงานกับทุก PR ที่ส่งไปยัง `main` และบล็อกการ merge เมื่อเกิดความล้มเหลว + +| สคริปต์ (`npm run ...`) | สิ่งที่ตรวจสอบ | การบล็อก | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | +| `check:node-runtime` | เวอร์ชัน Node.js อยู่ภายในช่วงที่รองรับ | ใช่ | +| `check:cycles` | การ import แบบวนรอบทั่วทั้ง `src/` + `open-sse/` (อิง AST และ resolve `paths` ของ tsconfig แล้ว) โหมดปกติ = ให้คำแนะนำและแสดงรายการวงจร `check:cycles:ratchet` (ซึ่งเป็นสิ่งที่ CI เรียกใช้) จะบล็อกเมื่อจำนวนเกินเพดาน `metrics.cycles` ใน `quality-baseline.json` — ปัจจุบันคือ 14 โดยมี `direction: down` ดังนั้นค่าจึงลดลงได้เท่านั้น (#15159 G-01/G-02) | ใช่ (ratchet) | +| `check:route-validation:t06` | มี Zod schemas อยู่ในทุก route (นโยบาย Tier 6) | ใช่ | +| `check:any-budget:t11` | จำนวน `@ts-expect-error // any` ไม่เกินงบประมาณ (catraca ระดับ Tier 11) | ใช่ | +| `check:provider-consistency` | ผู้ให้บริการทุกรายใน `providers.ts` มีรายการที่ตรงกันใน `providerRegistry.ts` (และในทางกลับกัน ภายใน allowlist) | ใช่ | +| `check:model-lifecycle` | ตารางการกำหนดเส้นทางทั้งสามรายการที่ดูแลด้วยตนเองยังคงสอดคล้องกับสแนปช็อตวงจรชีวิตที่เช็กอินไว้ (#11503): `FITNESS_TABLE` (`taskFitness.ts`) ไม่ให้คะแนน id ที่เลิกใช้งานแล้วซึ่ง `REGISTRY` สามารถกำหนดเส้นทางไปถึงได้; เป้าหมายทั้งหมดของ `BUILT_IN_ALIASES` มีอยู่ใน `REGISTRY` และไม่มีอยู่ในสแนปช็อต id ที่เลิกใช้งานแล้ว; id ที่เลิกใช้งานแล้วทุกรายการซึ่งยังอยู่ใน `REGISTRY` จะถูกส่งต่อหรือระบุไว้ใน `allowedRetiredInCatalog`; และไม่มีต้นทางหรือปลายทางของ `DEFAULT_DEGRADATION_MAP` ที่ปรากฏว่าเลิกใช้งานแล้วในสแนปช็อตดังกล่าว การตรวจสอบนี้ไม่ได้พิสูจน์ว่าโมเดลหนึ่งกำลังให้บริการโดยต้นทางที่ใช้งานจริงอยู่ในปัจจุบัน ทำงานแบบออฟไลน์ — เปรียบเทียบกับ `config/quality/model-lifecycle.json` ซึ่งรีเฟรชด้วยตนเองโดยใช้ `npm run quality:refresh-model-lifecycle` (ใช้เครือข่าย; ไม่ได้เชื่อมเข้ากับ CI) `allowedRetiredInCatalog` เป็นกลไก ratchet เพื่อลดจำนวนรายการ: ให้เพิ่มรายการเฉพาะเมื่อมี issue สำหรับติดตามเท่านั้น | ใช่ | +| `check:fetch-targets` | ทุก `fetch("/api/...")` ใน `src/` ฝั่งไคลเอ็นต์สามารถชี้ไปยัง `route.ts` ที่มีอยู่จริงได้ | ใช่ | +| `check:deps` | dependency ทั้งหมดที่ติดตั้งได้ด้วย `npm install` จากทุกไฟล์ `package.json` ใน repo ต้องอยู่ใน `dependency-allowlist.json`; package ใหม่ที่ไม่ได้ตรึงเวอร์ชันหรืออาจเป็น slopsquatting จะถูกแจ้งเตือน | ใช่ | +| `audit:deps` | `npm audit` (รูท + electron) — ไม่มีคำแนะนำด้านความปลอดภัยระดับสูง/วิกฤต (ทับซ้อนกับ osv `check:vuln-ratchet`; ดู Rationalization Backlog) | ใช่ | +| `check:lockfile` | ความถูกต้องของ `package-lock.json` — รีจิสทรี https, แฮชตรวจสอบความสมบูรณ์, ไม่มีการเขียนทับโฮสต์ | ใช่ | +| `check:licenses` | รายการที่อนุญาตของใบอนุญาต SPDX สำหรับ dependencies ที่ใช้ใน production | ใช่ | +| `check:tracked-artifacts` | ไม่มี artifacts จากการ build / symlinks ของ `node_modules` ที่ถูก commit (ทำงานใน husky pre-commit ด้วย; pre-push ถูกตั้งใจให้ทำงานเพียงเล็กน้อย — #6716) | ใช่ | +| `check:ai-attribution` | ไม่มี trailer `Co-Authored-By` ของ AI/บอต หรือ footer ที่ระบุการสร้างโดย AI ใน commits, ชื่อ หรือเนื้อหาของ PR — กฎเคร่งครัด #16 (อยู่ในลูป fast-gates ของ `quality.yml` สำหรับ PR→`release/**` — อ่าน event payload และไม่ดำเนินการเมื่อไม่ใช่ PR — และขั้นตอนเฉพาะ PR ใน lint ของ `ci.yml` สำหรับ PR→`main`; รวมถึง hook `commit-msg` ของ husky; อนุญาตผู้เขียนร่วมที่เป็นมนุษย์; #14436) | +| `check:vitest-exclusions` | ข้อยกเว้น Vitest ทุกรายการต้องระบุ tracking issue และปรากฏอยู่ใน `config/quality/vitest-exclusions.json` (#13204) | ใช่ | +| `check:file-size` | ไม่มีไฟล์ source ใดเกินขีดจำกัดของนามสกุลนั้น (ratchet: ไฟล์ขนาดใหญ่ที่ถูกตรึงอยู่ในรายการ `frozen`) | ใช่ | +| `check:error-helper` | การตอบกลับข้อผิดพลาดใน executors/handlers ใช้ `buildErrorBody()` / `sanitizeErrorMessage()` (กฎเคร่งครัด #12) | ใช่ | +| `check:migration-numbering` | ไฟล์ Migration SQL มีหมายเลขเรียงตามลำดับ โดยไม่มีหมายเลขขาดหายหรือซ้ำกัน | ใช่ | +| `check:public-creds` | ไม่มีค่า OAuth `client_id`/`client_secret` หรือคีย์ Firebase Web แบบค่าคงที่อยู่นอก `publicCreds.ts` (กฎเคร่งครัด #11) | ใช่ | +| `check:db-rules` | ไม่มี SQL ดิบอยู่นอกโมดูล `src/lib/db/`; ไม่มีการนำเข้าแบบ barrel จาก `localDb.ts` (กฎเคร่งครัด #2/#5) | ใช่ | +| `check:known-symbols` | ตัวดำเนินการของผู้ให้บริการ กลยุทธ์การกำหนดเส้นทาง และตัวแปลที่ลงทะเบียนในตาราง dispatch ของตน สอดคล้องกับไฟล์บนดิสก์ — ไม่มีสัญลักษณ์ที่ไม่ได้ถูกอ้างอิงหรือไม่ได้ประกาศ | ใช่ | +| `check:route-guard-membership` | ทุกเส้นทางที่สร้างโพรเซสลูกได้รับการจัดประเภทโดย `isLocalOnlyPath()` (กฎเคร่งครัด #15/#17) | ใช่ | +| `check:test-discovery` | ทุกไฟล์ `*.test.ts` / `*.spec.ts` ใน repo ถูกรวบรวมโดยตัวรันการทดสอบอย่างน้อยหนึ่งตัว (ratchet: รายการไฟล์ที่ไม่ถูกรวบรวมใน `test-discovery-baseline.json` ทำได้เพียงลดลงเท่านั้น) | ใช่ | +| `check:agent-skills-sync` | อาร์ติแฟกต์ agent-skills ที่สร้างขึ้นตรงกับแค็ตตาล็อกต้นทาง (ไม่มีความคลาดเคลื่อน) | +| `check:provider-asset-provenance` | โลโก้/แอสเซ็ตของผู้ให้บริการมีรายการบันทึกแหล่งที่มา | +| `lint:json` | ไฟล์การกำหนดค่า JSON สามารถแยกวิเคราะห์ได้และเป็นไปตามกฎ lint ของรีโพ | +| `typecheck:core` | คอมไพล์ TypeScript โดยไม่มีข้อผิดพลาด (มีเพียงคำเตือนเชิงแนะนำ) | ใช่ | +| `typecheck:noimplicit:core` | ใช้ `noImplicitAny` แบบเข้มงวด — เป็นแนวทางสำหรับอนาคต โดยจุดเรียกใช้งานเดิมจำนวนมากยังคงต้องเพิ่มคำอธิบายชนิดข้อมูล | **เชิงแนะนำ** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` ที่จำกัดขอบเขตไว้เฉพาะ `src/app/(dashboard)/**` (#7033) — รายการอนุญาต 27 ไฟล์ที่คัดสรรไว้ของ `typecheck:core` ไม่มี TSX ของแดชบอร์ดรวมอยู่เลย และ `next build` ก็ไม่เคยตรวจสอบชนิดข้อมูลของไฟล์เหล่านั้นเช่นกัน (`next.config.mjs` ตั้งค่า `ignoreBuildErrors: true`) ดังนั้นรีเกรสชันของตัวระบุที่ไม่มีการอ้างอิงในส่วนนั้น (#6625/#6909) จึงไม่ปรากฏให้ CI ตรวจพบ เปรียบเทียบความแตกต่างกับค่าฐานจำนวนข้อผิดพลาดต่อไฟล์/ต่อรหัส TS ที่ตรึงไว้ (`config/quality/dashboard-typecheck-baseline.json` โดยใช้รูปแบบการบังคับใช้กับรายการที่ล้าสมัยเช่นเดียวกับ `check:known-symbols`) — เฉพาะข้อผิดพลาดใหม่ที่เกินกว่าจำนวนในค่าฐานเท่านั้นที่จะทำให้เกตล้มเหลว ให้ปรับค่าฐานลดลงด้วย `--update` เมื่อแก้ไขข้อผิดพลาดเดิมแล้ว | ใช่ | ### งาน: `quality-gate` ทำงานหลังจาก `test-coverage` และบล็อกการผสานเมื่อเกิดความล้มเหลว -| สคริปต์ | ตรวจสอบ | บล็อกการดำเนินการ | +| สคริปต์ | ตรวจสอบความถูกต้อง | การบล็อก | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `quality:collect` | สร้าง `quality-metrics.json` (จำนวนคำเตือน ESLint, coverage จากรายงาน shard ที่รวมแล้ว) | ใช่ (ทำงานก่อน ratchet) | -| `quality:ratchet` | แต่ละเมตริกใน `quality-baseline.json` ต้องไม่ถดถอย (คำเตือน ESLint ≤ baseline; coverage ≥ baseline) | ใช่ | +| `quality:collect` | สร้าง `quality-metrics.json` (จำนวนคำเตือน ESLint, coverage จากรายงาน shard ที่ผสานแล้ว) | ใช่ (ทำงานก่อน ratchet) | +| `quality:ratchet` | แต่ละ metric ใน `quality-baseline.json` ต้องไม่ถดถอย (คำเตือน ESLint ≤ baseline; coverage ≥ baseline) | ใช่ | | `check:duplication` | การทำซ้ำของโค้ด (jscpd@4) ต้องไม่เกิน baseline ใน `quality-baseline.json` | ใช่ | -| `check:complexity` | ความซับซ้อนเชิงวัฏจักรระดับไฟล์ต้องไม่เกินขีดจำกัด (กฎหลักของ ESLint `complexity` + `max-lines-per-function`) | ใช่ | -| `check:cognitive-complexity` | ratchet ความซับซ้อนเชิงการรับรู้ (`eslint-plugin-sonarjs`) — ทำงานเป็น ESLint pass แยกต่างหาก; CI เรียกใช้ทั้งสองแบบรวมเป็นขั้นตอนเดียวคือ `check:complexity-ratchets` | ใช่ | -| `check:dead-code` | ratchet สำหรับ exports / ไฟล์ที่ไม่ได้ใช้ (knip) ต้องไม่ถดถอยเมื่อเทียบกับ baseline | ใช่ | -| `check:compression-budget` | งบประมาณเบนช์มาร์กการบีบอัด — ค่าขั้นต่ำของการประหยัด token ต่อ engine ต้องไม่ถดถอย | ใช่ | -| `check:type-coverage` | ratchet เปอร์เซ็นต์โค้ดที่ระบุ type (`type-coverage`) ต้องไม่ถดถอย; โดยส่วนใหญ่ครอบคลุม `typecheck:noimplicit:core` | ใช่ | -| `check:codeql-ratchet` | จำนวนการแจ้งเตือน CodeQL ที่เปิดอยู่ต้องไม่ถดถอย (อ่านผ่าน `gh api`; ข้ามอย่างปลอดภัยเมื่อไม่มี token) — รอบการรีเฟรชและการทริกเกอร์ด้วยตนเอง: ดู "CodeQL ratchet" ด้านล่าง | ใช่ | +| `check:complexity` | cyclomatic complexity ระดับไฟล์ต้องไม่เกินเพดาน (กฎหลัก `complexity` + `max-lines-per-function` ของ ESLint) | ใช่ | +| `check:cognitive-complexity` | ratchet ของ cognitive complexity (`eslint-plugin-sonarjs`) — รัน ESLint แยกต่างหาก; CI รันทั้งสองแบบรวมเป็นขั้นตอนเดียว `check:complexity-ratchets` | ใช่ | +| `check:dead-code` | ratchet ของ export / ไฟล์ที่ไม่ได้ใช้ (knip) ต้องไม่ถดถอยเมื่อเทียบกับ baseline | ใช่ | +| `check:compression-budget` | งบประมาณ benchmark การบีบอัด — ค่าขั้นต่ำของการประหยัด token ต่อ engine ต้องไม่ถดถอย | ใช่ | +| `check:type-coverage` | ratchet ของเปอร์เซ็นต์ที่ระบุ type (`type-coverage`) ต้องไม่ถดถอย; โดยส่วนใหญ่ครอบคลุม `typecheck:noimplicit:core` | ใช่ | +| `check:codeql-ratchet` | จำนวน alert ของ CodeQL ที่ยังเปิดอยู่ต้องไม่ถดถอย (อ่านผ่าน `gh api`; ข้ามอย่างเหมาะสมเมื่อไม่มี token) — รอบการรีเฟรชและการเรียกใช้ด้วยตนเอง: ดู "CodeQL ratchet" ด้านล่าง | ใช่ | ### งาน: `quality-extended` -งานทั้งหมดเป็นเพียงคำแนะนำ (`continue-on-error: true`) ratchet ที่ใช้ npm จะทำงาน -จริง ส่วน scanner ภายนอกจะติดตั้งผ่าน `gh release download` และข้ามการทำงานเอง (exit 0) -เมื่อยังไม่พบ binary +งานทั้งหมดมีไว้เพื่อให้คำแนะนำ (`continue-on-error: true`) ratchet ที่ใช้ npm จะรันจริง +ส่วน scanner ภายนอกจะติดตั้งผ่าน `gh release download` และข้ามตัวเอง (exit 0) +เมื่อยังไม่มี binary -| สคริปต์ | ตรวจสอบ | บล็อกการดำเนินการ | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| `check:circular-deps` | ไม่มี dependency แบบวนรอบ (dpdm) | **คำแนะนำ** | -| `check:bundle-size` | ขนาด bundle ต้องไม่เกินขีดจำกัด | **คำแนะนำ** | -| `check:secrets` | การสแกน secret (gitleaks) — ข้ามหากไม่พบ binary | **คำแนะนำ** | -| `check:vuln-ratchet` | ช่องโหว่ของ dependency (osv-scanner) ต้องไม่ถดถอย — ข้ามหากไม่พบ binary | **คำแนะนำ** | -| `check:workflows` | การ lint workflow (actionlint + zizmor); scanner ที่ไม่มีหรือเสีย รายงานที่ไม่ถูกต้อง หรือ baseline ของ ratchet ที่หายไปจะล้มเหลวด้วยสถานะ INCOMPLETE ส่วนสิ่งที่ตรวจพบอย่างถูกต้องจะเป็นไปตามนโยบาย strict/advisory/ratchet ที่เลือก | จำเป็นต้องดำเนินการ; zizmor ratchet บล็อก CI | -| `check:openapi-breaking` | การเปลี่ยนแปลงที่ทำให้สัญญา public API (`openapi.yaml`) ใช้งานร่วมกันไม่ได้ เมื่อเทียบกับ branch ฐาน (oasdiff) — สร้าง `openapiBreaking=N`; ข้ามหากไม่พบ oasdiff หรือไม่สามารถ resolve spec ฐานได้ | **คำแนะนำ** | +| สคริปต์ | ตรวจสอบความถูกต้อง | การบล็อก | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| `check:circular-deps` | ไม่มี dependency แบบวนเป็นวงกลม (dpdm) | **คำแนะนำ** | +| `check:bundle-size` | ขนาด bundle ต้องไม่เกินเพดาน | **คำแนะนำ** | +| `check:secrets` | การสแกน secret (gitleaks) — ข้ามหากไม่มี binary | **คำแนะนำ** | +| `check:vuln-ratchet` | ช่องโหว่ของ dependency (osv-scanner) ต้องไม่ถดถอย — ข้ามหากไม่มี binary | **คำแนะนำ** | +| `check:workflows` | การ lint workflow (actionlint + zizmor); scanner ที่ไม่มี/ใช้งานไม่ได้ รายงานที่ไม่ถูกต้อง หรือ ratchet baseline ที่หายไป จะล้มเหลวด้วยสถานะ INCOMPLETE ส่วนสิ่งที่ตรวจพบซึ่งถูกต้องจะเป็นไปตามนโยบาย strict/advisory/ratchet ที่เลือก | ต้องดำเนินการ; zizmor ratchet บล็อกใน CI | +| `check:openapi-breaking` | การเปลี่ยนแปลงที่ทำให้สัญญา public API (`openapi.yaml`) ใช้งานร่วมกันไม่ได้เมื่อเทียบกับ base branch (oasdiff) — สร้าง `openapiBreaking=N`; ข้ามหากไม่มี oasdiff หรือไม่สามารถ resolve base spec ได้ | **คำแนะนำ** | ### งาน: `docs-sync-strict` -ทำงานกับทุก PR ที่ส่งไปยัง `main` และบล็อกการ merge เมื่อทำงานล้มเหลว - -| สคริปต์ | ตรวจสอบความถูกต้อง | มีผลบล็อก | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `check:docs-all` | เมตาเกตที่เรียกใช้เกตย่อยทั้ง 6 รายการด้านล่างตามลำดับ | ใช่ | -| ↳ `check:docs-sync` | ความสอดคล้องของเวอร์ชันระหว่าง CHANGELOG / OpenAPI / llm.txt | ใช่ | -| ↳ `check:docs-counts` | จำนวนที่ระบุในข้อความอธิบาย (จำนวนผู้ให้บริการ จำนวนการย้ายข้อมูล เป็นต้น) อยู่ภายในช่วง ratchet ของจำนวนจริง | ใช่ | -| ↳ `check:env-doc-sync` | ตัวแปรสภาพแวดล้อมทุกตัวใน `.env.example` มีการบันทึกไว้ในตารางเอกสาร และในทางกลับกัน | ใช่ | -| ↳ `check:deprecated-versions` | ไม่มีสตริงเวอร์ชันที่เลิกใช้แล้วในเอกสาร | ใช่ | -| ↳ `check:doc-links` | ลิงก์ markdown ภายในเอกสารชี้ไปยังไฟล์ที่มีอยู่จริง (รูปแบบ `[ข้อความ]`/`(path)`) | ใช่ | -| ↳ `check:fabricated-docs` | เส้นทาง ตัวแปรสภาพแวดล้อม คำสั่ง CLI ชื่อ hook และพาธไฟล์ที่อ้างถึงในเอกสารมีอยู่จริงในฐานโค้ด เป็นเกตบังคับเมื่อใช้ `--strict`; ล้มเหลวแบบไม่บล็อกหากไม่มีแฟล็กนี้ | ใช่ (ผ่าน `--strict` ใน CI) | -| `check:cli-i18n` | สตริงคำสั่ง CLI มีอยู่ในไฟล์ locale ของ i18n ทุกไฟล์ | ใช่ | -| `check:openapi-coverage` | ข้อกำหนด OpenAPI ครอบคลุมเส้นทางจริงอย่างน้อยตามค่าขั้นต่ำที่กำหนดด้วย ratchet | ใช่ | -| `check:openapi-security-tiers` | คำอธิบายประกอบระดับความปลอดภัยใน `openapi.yaml` สอดคล้องกับการจัดประเภทใน `routeGuard.ts` | **คำแนะนำ** | -| `check:openapi-routes` | ทุกพาธใน `openapi.yaml` เชื่อมโยงไปยัง `route.ts` ที่มีอยู่จริง (ป้องกันข้อมูลที่ถูกแต่งขึ้น) | ใช่ | -| `check:docs-symbols` | การอ้างอิง `/api/...` ทุกรายการใน `docs/**/*.md` เชื่อมโยงไปยัง `route.ts` ที่มีอยู่จริง (ป้องกันข้อมูลที่ถูกแต่งขึ้น) | ใช่ | -| `i18n translation drift` | คีย์ที่ยังไม่ได้แปลในไฟล์ locale ของ i18n — แสดงคำเตือนเท่านั้น | **คำแนะนำ** | +รันในทุก PR ที่ส่งไปยัง `main` และบล็อกการ merge เมื่อล้มเหลว + +| สคริปต์ | สิ่งที่ตรวจสอบความถูกต้อง | การบล็อก | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `check:docs-all` | เมตาเกตที่เรียกใช้เกตย่อยทั้ง 6 รายการด้านล่างตามลำดับ | ใช่ | +| ↳ `check:docs-sync` | ความสอดคล้องของเวอร์ชันระหว่าง CHANGELOG / OpenAPI / llm.txt | ใช่ | +| ↳ `check:docs-counts` | จำนวนที่ระบุในเนื้อหา (จำนวนผู้ให้บริการ จำนวนการย้ายข้อมูล ฯลฯ) อยู่ภายในช่วง ratchet ของจำนวนจริง | ใช่ | +| ↳ `check:env-doc-sync` | ตัวแปรสภาพแวดล้อมทุกตัวใน `.env.example` มีการบันทึกไว้ในตารางเอกสาร และในทางกลับกัน | ใช่ | +| ↳ `check:deprecated-versions` | ไม่มีสตริงเวอร์ชันที่เลิกใช้แล้วในเอกสาร | ใช่ | +| ↳ `check:doc-links` | ลิงก์ markdown ภายในเอกสารชี้ไปยังไฟล์ที่มีอยู่จริง (รูปแบบ `[text]`/`(path)`) | ใช่ | +| ↳ `check:fabricated-docs` | เส้นทาง ตัวแปรสภาพแวดล้อม คำสั่ง CLI ชื่อ hook และพาธไฟล์ที่อ้างถึงในเอกสารมีอยู่จริงในฐานโค้ด เป็นเกตแบบบังคับผ่าน `--strict`; หากไม่มีแฟล็กจะล้มเหลวแบบไม่บล็อก | ใช่ (ผ่าน `--strict` ใน CI) | +| `check:cli-i18n` | สตริงคำสั่ง CLI มีอยู่ในไฟล์โลเคล i18n ทั้งหมด | ใช่ | +| `check:openapi-coverage` | ข้อกำหนด OpenAPI ครอบคลุมเส้นทางจริงอย่างน้อยตามค่าขั้นต่ำที่กำหนดแบบ ratchet | ใช่ | +| `check:openapi-security-tiers` | คำอธิบายประกอบระดับความปลอดภัยใน `openapi.yaml` สอดคล้องกับการจัดประเภทใน `routeGuard.ts` | **คำแนะนำ** | +| `check:openapi-routes` | ทุกพาธใน `openapi.yaml` เชื่อมโยงไปยัง `route.ts` ที่มีอยู่จริง (ป้องกันการสร้างข้อมูลเท็จ) | ใช่ | +| `check:docs-symbols` | ทุกการอ้างอิง `/api/...` ใน `docs/**/*.md` เชื่อมโยงไปยัง `route.ts` ที่มีอยู่จริง (ป้องกันการสร้างข้อมูลเท็จ) | ใช่ | +| `i18n translation drift` | คีย์ที่ยังไม่ได้แปลในไฟล์โลเคล i18n — แสดงคำเตือนเท่านั้น | **คำแนะนำ** | ### งาน: `i18n-ui-coverage` -| สคริปต์ | ตรวจสอบความถูกต้อง | มีผลบล็อก | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check-ui-keys-coverage` (inline) | ความครอบคลุมของคีย์ i18n สำหรับ UI อยู่ที่ ≥ 65% | ใช่ | -| `check-ui-value-drift` (inline) | เมื่อมีการเขียน **ค่า** ภาษาอังกฤษใหม่ ต้องไม่มีคำแปลเก่าค้างอยู่ | ใช่ | -| `check-new-key-coverage` (inline) | คีย์ภาษาอังกฤษ **ใหม่** ได้รับการแปลในทุก locale — ไม่อนุญาตให้มีเครื่องหมาย `__MISSING__:` | ใช่ | -| `check-translation-ratio` | อัตราส่วนคำแปลจริงต่อ locale (ค่าที่เหมือนภาษาอังกฤษ / ตัวแทนข้อความ / คีย์ที่ขาดหายซึ่งอยู่นอก allowlist) ต้องไม่เกิน `config/quality/i18n-translation-baseline.json` + ค่าผ่อนผัน | **คำแนะนำ** | +| สคริปต์ | สิ่งที่ตรวจสอบความถูกต้อง | การบล็อก | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check-ui-keys-coverage` (inline) | ความครอบคลุมของคีย์ i18n สำหรับ UI ≥ 65% | ใช่ | +| `check-ui-value-drift` (inline) | เมื่อเขียน **ค่า** ภาษาอังกฤษใหม่ จะต้องไม่มีคำแปลเก่าที่ยังคงหลงเหลืออยู่ | ใช่ | +| `check-new-key-coverage` (inline) | คีย์ภาษาอังกฤษ **ใหม่** ต้องได้รับการแปลในทุกโลเคล — เครื่องหมาย `__MISSING__:` จะถูกปฏิเสธ | ใช่ | +| `check-translation-ratio` | อัตราส่วนคำแปลจริงต่อโลเคล (รายการที่เหมือนกับภาษาอังกฤษ / เป็นตัวยึดตำแหน่ง / ขาดหายไปซึ่งอยู่นอกรายการอนุญาต) ต้องไม่เกิน `config/quality/i18n-translation-baseline.json` + ค่าผ่อนผัน | **คำแนะนำ** | -ต้องใช้ `fetch-depth: 0` — เกตตรวจจับความคลาดเคลื่อนของค่าใช้ diff ระหว่าง `en.json` กับ merge base +ต้องใช้ `fetch-depth: 0` — เกต value-drift จะเปรียบเทียบความแตกต่างของ `en.json` กับฐานการผสาน -#### `check-ui-value-drift` — เกตตรวจจับคำแปลเก่าค้าง +#### `check-ui-value-drift` — เกตตรวจจับคำแปลที่ล้าสมัย -ตรวจจับการถดถอยของ i18n รูปแบบหนึ่งที่เกตอื่นไม่สามารถตรวจพบได้ในเชิงโครงสร้าง: ค่า -ภาษาอังกฤษถูกเขียนใหม่ แต่คำแปลที่อิงจากภาษาอังกฤษเวอร์ชัน_ก่อนหน้า_ยังคงอยู่ ทำให้ -ผู้ใช้ที่ไม่ได้ใช้ภาษาอังกฤษยังคงอ่านข้อความที่เขียนอย่างมั่นใจแต่ไม่ถูกต้องอีกต่อไป +ตรวจจับการถดถอยของ i18n รูปแบบหนึ่งที่เกตอื่นไม่สามารถตรวจพบได้ในเชิงโครงสร้าง กล่าวคือ เมื่อค่า +ภาษาอังกฤษถูกเขียนใหม่ แต่คำแปลที่อ้างอิงจากภาษาอังกฤษฉบับ_ก่อนหน้า_ยังคงอยู่ ทำให้ +ผู้ใช้ที่ไม่ได้ใช้ภาษาอังกฤษยังคงอ่านข้อความที่ดูน่าเชื่อถือแต่ไม่ถูกต้องอีกต่อไป -ปัญหานี้เคยถูกปล่อยใช้งานจริง `oauthModal.googleOAuthWarning` ถูกเขียนใหม่เมื่อเพิ่มตัวช่วย -เข้าสู่ระบบ Antigravity (#5203); **39 จาก 43 locale** ยังคงมีข้อความที่บอกผู้ดูแลระบบให้ "คัดลอก -URL แบบเต็มแล้ววางไว้ด้านล่าง" — ขั้นตอนที่ไม่สามารถดำเนินการจนเสร็จสำหรับผู้ให้บริการรายนั้น ปัญหานี้ -ไม่มีใครสังเกตเห็นจนถึง #8463 เนื่องจาก: +ปัญหานี้เกิดขึ้นจริงในรุ่นที่เผยแพร่ `oauthModal.googleOAuthWarning` ถูกเขียนใหม่เมื่อมีการเพิ่มตัวช่วย +เข้าสู่ระบบ Antigravity (#5203); **39 จาก 43 โลเคล** ยังคงมีข้อความที่บอกผู้ดูแลให้ "คัดลอก +URL แบบเต็มแล้ววางไว้ด้านล่าง" — ซึ่งเป็นขั้นตอนที่ไม่สามารถดำเนินการจนเสร็จสิ้นสำหรับผู้ให้บริการรายนั้นได้ ปัญหานี้ +ไม่มีใครสังเกตเห็นจนกระทั่ง #8463 เนื่องจาก: -- `sync-ui-keys` เติมเฉพาะคีย์ที่ **ไม่มีอยู่** เท่านั้น ไม่เคยเติมคีย์ที่ **ล้าสมัย**; -- `check-ui-keys-coverage` นับเพียงว่า _มี_ คีย์อยู่หรือไม่ ดังนั้นคำแปลเก่าค้างจึงถูกนับว่าครอบคลุมแล้ว; +- `sync-ui-keys` เติมคีย์ย้อนหลังเฉพาะคีย์ที่ **ไม่มีอยู่** เท่านั้น ไม่ใช่คีย์ที่ **ล้าสมัย**; +- `check-ui-keys-coverage` นับการ_มีอยู่_ของคีย์ ดังนั้นคำแปลที่ล้าสมัยจึงถูกนับว่าครอบคลุมแล้ว; - `check-translation-drift` ติดตามสำเนาเอกสาร `docs/i18n//**.md` — - แต่ไม่เคยอ่าน `src/i18n/messages/*.json` เป็นเกตบังคับในงาน `docs-sync-strict` ตั้งแต่การ - ซิงค์ใหม่เมื่อ 2026-09: แก้ไขเอกสารหลัก → `npm run i18n:run -- --files=` (ระดับส่วน ประหยัดทรัพยากร) + แต่ไม่เคยอ่าน `src/i18n/messages/*.json` บล็อกในงาน `docs-sync-strict` ตั้งแต่การซิงค์ใหม่ + 2026-09: แก้ไขเอกสารหลัก → `npm run i18n:run -- --files=` (ระดับส่วน ประหยัดทรัพยากร) -**ตรวจจับความแตกต่างโดยไม่อิง baseline** ระบบจะเปรียบเทียบ `en.json` ณ merge base กับ -working tree สำหรับทุกคีย์ที่ค่าภาษาอังกฤษเปลี่ยนแปลง locale ใดก็ตามที่ยังคงมี -คำแปลเดิมซึ่งไม่ได้รับการแก้ไขจะถือว่าล้าสมัย วิธีนี้จงใจ **ตรึงหนี้เดิมที่มีอยู่** — diff -ไม่สามารถบอกได้ว่าคำแปลที่มีมานานนั้นมาจากข้อความภาษาอังกฤษเก่าเวอร์ชันใด ดังนั้น gate จึงตรวจสอบ -เฉพาะสิ่งที่การเปลี่ยนแปลงปัจจุบันแตะต้องเท่านั้น ทางเลือกอื่น (baseline แบบแฮชต่อคีย์) จะทำให้ต้องมี -ไฟล์ที่สร้างขึ้นขนาดประมาณ 600 KB ซึ่งใหญ่กว่า baseline ที่มีอยู่และใหญ่ที่สุดถึง 3 เท่า และจะเปลี่ยนแปลงในทุก i18n PR +**รับรู้ความแตกต่าง ไม่ได้อิง baseline** โดยจะเปรียบเทียบ `en.json` ณ merge base กับ +working tree สำหรับทุก key ที่ค่า English เปลี่ยนแปลง locale ใดก็ตามที่ยังคงมี +คำแปลเดิมที่ไม่ได้แก้ไขจะถือว่าล้าสมัย วิธีนี้ตั้งใจ **ตรึงหนี้เดิมที่มีอยู่** — diff +ไม่สามารถระบุได้ว่าคำแปลที่มีมานานแปลมาจาก English เวอร์ชันเก่าใด ดังนั้น gate จึงตรวจสอบ +เฉพาะสิ่งที่การเปลี่ยนแปลงปัจจุบันแตะต้องเท่านั้น ทางเลือกอื่น (baseline แบบ hash ต่อ key) จะทำให้ต้องมี +ไฟล์ที่สร้างขึ้นขนาดประมาณ 600 KB ซึ่งใหญ่กว่า baseline ที่มีอยู่ขนาดใหญ่ที่สุด 3 เท่า และเปลี่ยนแปลงทุกครั้งที่มี i18n PR มีสองวิธีที่จะทำให้ผ่าน: 1. อัปเดตคำแปลที่ได้รับผลกระทบ หรือ -2. ตั้งค่าเป็น `__MISSING__:` — จากนั้น runtime จะส่งคืนข้อความภาษาอังกฤษที่แก้ไขแล้ว - (`src/i18n/request.ts::deepMergeFallback`, #7258) และคีย์นั้นจะถูกเพิ่มเข้าคิวเพื่อรอการแปล +2. ตั้งค่าเป็น `__MISSING__:` — จากนั้น runtime จะให้บริการ English ที่แก้ไขแล้ว + (`src/i18n/request.ts::deepMergeFallback`, #7258) และนำ key เข้าคิวเพื่อรอการแปล -หาก **ความหมาย** ของข้อความเปลี่ยนไป ควรเลือก **เปลี่ยนชื่อคีย์**: คีย์ใหม่จะไม่สามารถรับช่วง +หาก **ความหมาย** ของ string เปลี่ยนไป ควรเลือก **เปลี่ยนชื่อ key**: key ใหม่ไม่สามารถรับช่วง คำแปลที่ล้าสมัยได้ นี่คือรูปแบบที่ #8463 ใช้ ```bash -npm run i18n:check-value-drift # เข้มงวด (แบบที่ CI เรียกใช้) +npm run i18n:check-value-drift # เข้มงวด (สิ่งที่ CI เรียกใช้) npm run i18n:check-value-drift:warn # รายงานเท่านั้น BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -จบการทำงานด้วยรหัส 0 พร้อม `SKIP reason=base-unresolved` เมื่อไม่สามารถอ่านแค็ตตาล็อกฐานได้ (shallow +ออกด้วยสถานะ 0 พร้อม `SKIP reason=base-unresolved` เมื่อไม่สามารถอ่าน catalog ฐานได้ (shallow clone ที่ไม่มี base ref) ซึ่งสอดคล้องกับ `check-openapi-breaking` ### งาน: `i18n` -เมทริกซ์การตรวจสอบ i18n แบบเต็ม (หนึ่งงานต่อ locale) ทั้งงานมีสถานะเป็นคำแนะนำ +เมทริกซ์การตรวจสอบ i18n แบบเต็ม (หนึ่งงานต่อ locale) งานทั้งหมดเป็นเพียงคำแนะนำ -| สคริปต์ | สิ่งที่ตรวจสอบ | การบล็อก | -| ------------------------------- | ----------------------------- | -------------------------------------------------- | -| `validate_translation.py quick` | ความครบถ้วนของคำแปลต่อ locale | **คำแนะนำ** (`continue-on-error: true` กับทั้งงาน) | +| สคริปต์ | ตรวจสอบ | การบล็อก | +| ------------------------------- | --------------------------------- | ----------------------------------------------------- | +| `validate_translation.py quick` | ความครบถ้วนของคำแปลในแต่ละ locale | **คำแนะนำ** (`continue-on-error: true` สำหรับทั้งงาน) | ### งาน: `pr-test-policy` -ทำงานเฉพาะใน pull request +ทำงานเฉพาะบน pull request -| สคริปต์ | สิ่งที่ตรวจสอบ | การบล็อก | +| สคริปต์ | ตรวจสอบ | การบล็อก | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | PR ที่เปลี่ยนแปลงโค้ด production ใน `src/`, `open-sse/`, `electron/` หรือ `bin/` ต้องเพิ่มหรืออัปเดตการทดสอบ (กฎบังคับ #8) | ใช่ | -| `check:test-masking` | ไฟล์ทดสอบที่เปลี่ยนแปลงต้องไม่ลดจำนวน assert สุทธิหรือเพิ่มสัจนิรันดร์ `assert.ok(true)` | ใช่ | -| `check:pr-evidence` | เนื้อหา PR ต้องอ้างอิงหลักฐานการทดสอบ/VPS สำหรับการเปลี่ยนแปลง (ทำให้กฎบังคับ #18 เป็นระบบอัตโนมัติด้วยการ grep ข้อความใน PR — เปราะบาง โปรดดู Backlog) | ใช่ | +| `check:pr-test-policy` | PR ที่เปลี่ยน production code ใน `src/`, `open-sse/`, `electron/` หรือ `bin/` ต้องเพิ่มหรืออัปเดต test (กฎตายตัว #8) | ใช่ | +| `check:test-masking` | ไฟล์ test ที่เปลี่ยนแปลงต้องไม่ลดจำนวน assert สุทธิหรือเพิ่ม tautology แบบ `assert.ok(true)` | ใช่ | +| `check:pr-evidence` | เนื้อหา PR อ้างอิงหลักฐานจาก test/VPS สำหรับการเปลี่ยนแปลงนั้น (ทำให้กฎตายตัว #18 เป็นกลไกอัตโนมัติด้วยการ grep ข้อความใน PR — เปราะบาง โปรดดู Backlog) | ใช่ | ### งาน: `test-vitest` ทำงานหลัง `build` และบล็อกการ merge เมื่อล้มเหลว -| ชุดการทดสอบ | สิ่งที่ตรวจสอบ | การบล็อก | -| ---------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | เซิร์ฟเวอร์ MCP (110 เครื่องมือ), autoCombo, แคช — ตัวรัน vitest | ใช่ | -| `test:vitest:ui` | การทดสอบคอมโพเนนต์ UI — ตัวรัน vitest | **บล็อก** — ความล้มเหลวที่มีอยู่ก่อนแล้วถูกยกเว้นไว้อย่างชัดเจนใน `vitest.config.ts`; ความล้มเหลวใหม่จะทำให้งานล้มเหลว | +| ชุดทดสอบ | ตรวจสอบ | การบล็อก | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | MCP server (110 tools), autoCombo, cache — ตัวรัน vitest | ใช่ | +| `test:vitest:ui` | test สำหรับ UI component — ตัวรัน vitest | **บล็อก** — failure ที่มีอยู่ก่อนถูกยกเว้นไว้อย่างชัดเจนใน `vitest.config.ts`; failure ใหม่จะทำให้งานล้มเหลว | -### เวิร์กโฟลว์รายคืน (ตามกำหนดเวลา, เป็นคำแนะนำ) +### Workflow รายคืน (ตามกำหนดเวลา, เป็นคำแนะนำ) -เวิร์กโฟลว์เหล่านี้ทำงานตามกำหนดเวลา cron (และ `workflow_dispatch`) โดยจะไม่ทำงานใน PR ทั้งหมดมีสถานะเป็นคำแนะนำ +รายการเหล่านี้ทำงานตามกำหนดเวลา cron (และ `workflow_dispatch`) โดยไม่ทำงานบน PR และทั้งหมดเป็นเพียงคำแนะนำ -| เวิร์กโฟลว์ | สิ่งที่ตรวจสอบ | การบล็อก | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `nightly-property` | การทดสอบ property ด้วย fast-check โดยใช้ seed แบบสุ่มและจำนวนรอบสูง | **คำแนะนำ** | -| `nightly-resilience` | gate ตรวจสอบการเติบโตของ heap, การฉีดความผิดพลาดแบบ chaos, การทดสอบโหลด/soak ด้วย k6 | **คำแนะนำ** | -| `nightly-llm-security` | การป้องกัน injection ด้วย promptfoo (โหมดบล็อก) + โพรบ garak (ข้ามเมื่อไม่มี provider secret) | **คำแนะนำ** | -| `nightly-schemathesis` | การ fuzz สัญญา OpenAPI (schemathesis) กับ OmniRoute ที่ทำงานอยู่โดยใช้ `docs/openapi.yaml` — เปิดเผยการละเมิดข้อกำหนด / ข้อผิดพลาด 500 ที่ไม่ได้รับการจัดการ (ระยะ 8 B.4) | **คำแนะนำ** | -| `nightly-mutation` | คะแนนการทดสอบ mutation ของ Stryker บนเลน unit ที่รวดเร็ว — mutant ที่รอดเผยให้เห็น assert ที่อ่อนแอ | **คำแนะนำ** | -| `nightly-compat` | เมทริกซ์ความเข้ากันได้ของ Node engine ครอบคลุมช่วง `engines.node` ที่รองรับ | **คำแนะนำ** | +| Workflow | ตรวจสอบ | การบล็อก | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `nightly-property` | property test ด้วย fast-check โดยใช้ seed แบบสุ่มและจำนวนรอบการทำงานสูง | **คำแนะนำ** | +| `nightly-resilience` | gate ตรวจการเติบโตของ heap, การฉีด fault แบบ chaos, การทดสอบ load/soak ด้วย k6 | **คำแนะนำ** | +| `nightly-llm-security` | ตัวป้องกัน injection ของ promptfoo (โหมด block) + probe ของ garak (ข้ามเมื่อไม่มี provider secret) | **คำแนะนำ** | +| `nightly-schemathesis` | การ fuzz สัญญา OpenAPI (schemathesis) กับ OmniRoute ที่ทำงานอยู่ โดยใช้ `docs/openapi.yaml` — เปิดเผยการละเมิด spec / 500 ที่ไม่ได้รับการจัดการ (Fase 8 B.4) | **คำแนะนำ** | +| `nightly-mutation` | คะแนน mutation testing ของ Stryker บน lane unit แบบเร็ว — mutant ที่รอดเผยให้เห็น assert ที่อ่อนแอ | **คำแนะนำ** | +| `nightly-compat` | เมทริกซ์ความเข้ากันได้ของ Node engine ครอบคลุมช่วง `engines.node` ที่รองรับ | **คำแนะนำ** | --- @@ -535,43 +535,43 @@ allowlist ที่เพิ่มขึ้นเรื่อย ๆ เป็ --- -## รายการงานค้างสำหรับการปรับให้มีเหตุผล (การทบทวน ROI — ระยะ 9 รอบ 3) +## รายการงานค้างสำหรับการปรับปรุงให้มีเหตุผล (การทบทวน ROI — ระยะ 9 รอบ 3) -รายการนี้ได้รับการตรวจสอบเทียบกับ `ci.yml` เมื่อ 2026-06-17 (เวอร์ชันก่อนหน้านี้ไม่ได้รวม +รายการนี้ได้รับการตรวจสอบเทียบกับ `ci.yml` เมื่อ 2026-06-17 (เวอร์ชันก่อนหน้านี้ไม่ได้ระบุ `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`) การทบทวน ROI ของชุดที่ตรวจสอบแล้ว -ระบุรายการที่อาจปรับให้มีเหตุผลดังต่อไปนี้ **การรวมเป็นการเปลี่ยนแปลง CI -เชิงกล ส่วนการสลับ/นำออกเป็นการตัดสินใจเชิงนโยบายที่สงวนไว้ให้ผู้ดำเนินการ** ขณะนี้ยังไม่มีการนำ -รายการด้านล่างไปใช้ +`check:codeql-ratchet`, `check:pr-evidence`) การทบทวน ROI ของชุดรายการที่ตรวจสอบแล้ว +พบตัวเลือกสำหรับการปรับปรุงให้มีเหตุผลดังต่อไปนี้ **การรวมเป็นการเปลี่ยนแปลง CI +เชิงกล ส่วนการสลับ/ยกเลิกเป็นการตัดสินใจเชิงนโยบายที่สงวนไว้สำหรับผู้ปฏิบัติการ** รายการด้านล่าง +ยังไม่มีการนำไปใช้ -**รายการที่ยังไม่ได้จัดทำเอกสารไว้ข้างต้นเช่นกัน** (เป็นเพียงคำแนะนำ สัญญาณต่ำ): งาน `docs-lint` +**รายการที่ยังไม่ได้บันทึกไว้ข้างต้นด้วย** (เชิงแนะนำ, สัญญาณต่ำ): งาน `docs-lint` (markdownlint + Vale โดยทั้งงานตั้งค่า `continue-on-error`) และเวิร์กโฟลว์สแกนเนอร์แบบแยกเดี่ยว `semgrep.yml` / `codeql.yml` / `scorecard.yml` ค่า `semgrepFindings: 0` อยู่ใน -`quality-baseline.json` แต่ยังไม่ได้เชื่อมเข้ากับ ratchet แบบบล็อกใน `ci.yml` — ปัจจุบันเมตริกนี้ -ไม่ได้ถูกใช้งาน - -### รวม / ขจัดรายการซ้ำ (เชิงกล ความเสี่ยงต่ำกว่า) - -แต่ละรายการได้รับการตรวจสอบกับสถานะ gate ที่ใช้งานจริงเมื่อ 2026-06-17 (เชื่อใจแต่ต้องตรวจสอบ); -การรวมหลายรายการที่ดู “ชัดเจน” กลับพบว่าซ่อนหนี้ทางเทคนิคไว้ และ **ไม่สามารถนำมาแทนที่ได้อย่างเรียบร้อย** - -- **`check:docs-sync` ทำงานสองครั้ง** — ทำงานแบบแยกเดี่ยวในงาน `lint` และทำงานอีกครั้งภายใน `check:docs-all` (`docs-sync-strict`) รวมถึงใน husky pre-commit hook ✅ **เสร็จสิ้น** — นำการเรียกใช้แบบแยกเดี่ยวใน `lint` ออกแล้ว -- **การสแกน CVE** — ❌ **ไม่สามารถรวมได้อย่างเรียบร้อย** `audit:deps` จะล้มเหลวทันทีเมื่อพบ CVE ระดับ high/critical; `check:vuln-ratchet` (osv) จะล้มเหลวเฉพาะเมื่อเกิด _การถดถอย_ เมื่อเทียบกับ baseline (ปัจจุบันมีระดับ MODERATE 1 รายการ) ความหมายแตกต่างกัน — การนำ `audit:deps` ออกจะทำให้สูญเสีย gate แบบสัมบูรณ์สำหรับระดับ high/critical ให้คงทั้งสองรายการไว้ -- **การตรวจหาวงจร** — ❌ **ไม่สามารถรวมได้อย่างเรียบร้อย** `check:circular-deps` (dpdm) รายงาน **91 วงจร** (นี่คือสาเหตุที่เป็นเพียงคำแนะนำ); ไม่สามารถเลื่อนให้เป็นแบบบล็อกได้โดยไม่แก้ไขวงจรเหล่านั้นก่อน และมีขอบเขตกว้างกว่า `check:cycles` ที่ได้รับการคัดสรรและมีสถานะผ่าน ให้คง `check:cycles` เป็นแบบบล็อกไว้ ส่วนการแก้ไขวงจร dpdm ทั้ง 91 รายการเป็นงานค้างอีกชุดหนึ่ง -- **ความซับซ้อน** — ✅ **เสร็จสิ้น** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): เดินตรวจ ESLint เพียงรอบเดียว นับตาม ruleId เพื่อให้ baseline ของ cyclomatic+max-lines และ cognitive ยังคงแยกจากกัน; ยังคงเก็บ `check:complexity` / `check:cognitive-complexity` แต่ละรายการไว้สำหรับ `--update` ในเครื่อง -- **การป้องกันการสร้างข้อมูลเท็จของ `/api`** — ✅ **เสร็จสิ้น** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): จัดทำบัญชีรายการ FS ของ `src/app/api` เพียงครั้งเดียว โดย openapi-routes + docs-symbols ยังคงรายงานแยกจากกัน; ยังคงเก็บแต่ละรายการไว้สำหรับการเรียกใช้ในเครื่อง -- **`check:node-runtime` ทำงานใน 11 งาน** — ⚠️ **ROI ต่ำ** แต่ละงานอยู่บน runner แยกกัน และการตรวจสอบใช้เวลาน้อยกว่า 1 วินาที; ประหยัดเวลารวมได้ประมาณ 10 วินาที แต่ต้องแลกกับการสูญเสียตัวป้องกันราคาถูกในแต่ละงาน ไม่คุ้มกับความวุ่นวายจากการเปลี่ยนแปลง -- **`typecheck:noimplicit:core` ใน CI lint** — ✅ **นำออกจากงาน lint แล้ว** (เดิมเป็นคำแนะนำโดยตั้งค่า `continue-on-error`); พื้นผิวประเภทข้อมูลแบบบล็อกคือ `typecheck:core` + `check:type-coverage` ยังคงเก็บสคริปต์ในเครื่องไว้ - -### สลับ / ตัดสินใจ (นโยบายของผู้ดำเนินการ) - -- `check:openapi-security-tiers` (คำแนะนำ) — ❌ **ไม่สามารถสลับได้อย่างเรียบร้อย** คำสั่งจบการทำงานด้วยค่า 0 แต่เตือนว่า route ของ `traffic-inspector` หลายรายการภายใต้ `LOCAL_ONLY_API_PREFIXES` ไม่มี annotation `x-loopback-only: true` การบังคับใช้จำเป็นต้องเพิ่ม annotation เหล่านั้นใน `openapi.yaml` ก่อน -- `typecheck:noimplicit:core` (คำแนะนำ) — ถูกแทนที่โดย ratchet แบบบล็อก `check:type-coverage` ไปแล้วเป็นส่วนใหญ่ ให้สลับเป็น ratchet หรือนำการรัน `tsc` รอบที่สองซึ่งซ้ำซ้อนออก -- `test:vitest:ui` (ขณะนี้ **เป็นแบบบล็อก**) — failure ที่มีอยู่ก่อนถูกยกเว้นไว้อย่างชัดเจนใน `vitest.config.ts` พร้อมความคิดเห็นติดตาม `// #8618`; failure ใหม่จะทำให้งานล้มเหลว -- `check:secrets` (gitleaks, ratchet แบบบล็อกซึ่งตรึงไว้ที่ false-positive ที่มีเอกสารกำกับ 3 รายการ) — เพิ่มทั้ง 3 รายการใน allowlist เพื่อให้เหลือ 0 หรือลดระดับเป็นคำแนะนำ ซ้ำซ้อนกับ secret-scanning แบบเนทีฟของ GitHub + `check:public-creds` -- `check:pr-evidence` (แบบบล็อก, grep ข้อความร้อยแก้วใน PR body) — มีความเสี่ยงเกิด false-positive สูง; หากนำออกจะทำให้การบังคับใช้ Hard Rule #18 อ่อนแอลง ดังนั้นนี่จึงเป็นการตัดสินใจเชิงนโยบายอย่างแท้จริง -- `semgrep` (คำแนะนำแบบแยกเดี่ยว) — ซ้ำซ้อนกับ CodeQL สำหรับกลุ่ม OWASP; เชื่อม baseline เข้ากับ ratchet หรือนำออก +`quality-baseline.json` แต่ยังไม่ได้เชื่อมต่อกับ ratchet แบบบล็อกใน `ci.yml` — ปัจจุบันเมตริกนี้ +ไม่มีการใช้งาน + +### รวม / ขจัดความซ้ำซ้อน (เชิงกล, ความเสี่ยงต่ำกว่า) + +ตัวเลือกแต่ละรายการได้รับการตรวจสอบกับสถานะ gate ที่ใช้งานจริงเมื่อ 2026-06-17 (เชื่อใจแต่ต้องตรวจสอบ) +การรวมหลายรายการที่ดู “ชัดเจน” กลับพบว่าซ่อนหนี้ทางเทคนิคไว้ และ **ไม่ใช่** การแทนที่ที่ทำได้โดยตรงอย่างปลอดภัย + +- **`check:docs-sync` ทำงานสองครั้ง** — ทำงานแยกเดี่ยวในงาน `lint` และทำงานอีกครั้งภายใน `check:docs-all` (`docs-sync-strict`) รวมถึงใน husky pre-commit hook ✅ **เสร็จแล้ว** — นำการเรียกใช้แบบแยกเดี่ยวใน `lint` ออกแล้ว +- **การสแกน CVE** — ❌ **ไม่ใช่การรวมที่ทำได้โดยตรงอย่างปลอดภัย** `audit:deps` ล้มเหลวทันทีเมื่อพบ CVE ระดับสูง/วิกฤตใด ๆ ส่วน `check:vuln-ratchet` (osv) จะล้มเหลวเฉพาะเมื่อมี _การถดถอย_ เมื่อเทียบกับ baseline (ปัจจุบันมีระดับ MODERATE 1 รายการ) ความหมายต่างกัน — การนำ `audit:deps` ออกจะทำให้สูญเสีย gate แบบค่าสัมบูรณ์สำหรับระดับสูง/วิกฤต ให้คงทั้งสองรายการไว้ +- **การตรวจจับวงจร** — ✅ **เสร็จแล้ว** (#15159 G-01/G-02) ข้อความเดิมในส่วนนี้เรียก `check:cycles` ว่าเป็น gate ที่ “ผ่านและคัดสรรมาแล้ว” และให้เหตุผลว่าควรคงการบล็อกไว้ เนื่องจาก `check:circular-deps` (dpdm) รายงานวงจร 91 รายการ แต่การผ่านนั้นเป็น **ผลผ่านลวง**: `check:cycles` สแกนไดเรกทอรีย่อย 5 รายการ (450 ไฟล์) จับคู่เฉพาะ `import|export … from` แบบสแตติก และตัด specifier ที่เป็น `@/` และ `@omniroute/open-sse/` ทั้งหมดออก จึงไม่สามารถตรวจพบวงจรจาก dynamic import + alias ซึ่งเป็นวงจรส่วนใหญ่ใน repo ได้ แก้ไขแล้ว: ขณะนี้ gate ตรวจไล่ผ่าน `src` + `open-sse` (5023 ไฟล์), รวบรวม specifier จาก TypeScript AST (ดังนั้น `import("…")` จะถูกนับ แต่ `typeof import("…")` ในตำแหน่งประเภทจะไม่ถูกนับ) และ resolve `paths` ของ tsconfig โดยพบวงจร **14** รายการ ไม่ใช่ 0 เนื่องจากไม่สามารถแก้ไขวงจรเดิมทั้ง 14 รายการใน gate PR ได้ ขณะนี้ `check:cycles` จึงเป็น **ratchet** (`--ratchet`, เพดาน `metrics.cycles.value = 14` ใน `quality-baseline.json`, `direction: down`) — โดยจะบล็อก _การถดถอย_ ใด ๆ และจำนวนวงจรจะลดลงได้เท่านั้น CI เรียกใช้ `npm run check:cycles:ratchet` การทยอยลดจำนวนจะดำเนินไปพร้อมกับ **A-01** ส่วน `check:circular-deps` (dpdm) ยังคงเป็นรายการเชิงแนะนำเพื่อใช้เป็นความเห็นที่สองในขอบเขตที่กว้างกว่า +- **ความซับซ้อน** — ✅ **เสร็จแล้ว** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ตรวจไล่ด้วย ESLint เพียงรอบเดียว และนับตาม ruleId เพื่อให้ baseline ของ cyclomatic+max-lines และ cognitive ยังคงเป็นอิสระจากกัน ส่วน `check:complexity` / `check:cognitive-complexity` แต่ละรายการยังคงไว้สำหรับ `--update` ภายในเครื่อง +- **การป้องกันข้อมูลหลอนของ `/api`** — ✅ **เสร็จแล้ว** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): จัดทำรายการ FS ของ `src/app/api` เพียงครั้งเดียว โดย openapi-routes + docs-symbols ยังคงรายงานแยกกัน และยังคงรายการแต่ละรายการไว้สำหรับการเรียกใช้ภายในเครื่อง +- **`check:node-runtime` ทำงานใน 11 งาน** — ⚠️ **ROI ต่ำ** แต่ละงานใช้ runner แยกกันและการตรวจสอบใช้เวลาน้อยกว่า 1 วินาที เวลาที่ประหยัดได้รวมประมาณ 10 วินาที ซึ่งไม่คุ้มกับการสูญเสียตัวป้องกันราคาถูกในแต่ละงานและความวุ่นวายจากการเปลี่ยนแปลง +- **`typecheck:noimplicit:core` ใน CI lint** — ✅ **นำออกจากงาน lint แล้ว** (เดิมเป็นเชิงแนะนำโดยใช้ `continue-on-error`) พื้นผิวประเภทที่บล็อกคือ `typecheck:core` + `check:type-coverage` โดยยังคงสคริปต์สำหรับใช้งานภายในเครื่องไว้ + +### สลับ / ตัดสินใจ (นโยบายของผู้ปฏิบัติการ) + +- `check:openapi-security-tiers` (เชิงแนะนำ) — ❌ **ไม่สามารถสลับได้โดยตรงอย่างปลอดภัย** แม้จะจบการทำงานด้วย 0 แต่เตือนว่า route หลายรายการของ `traffic-inspector` ภายใต้ `LOCAL_ONLY_API_PREFIXES` ไม่มี annotation `x-loopback-only: true` การบังคับใช้จำเป็นต้องเพิ่ม annotation เหล่านั้นใน `openapi.yaml` ก่อน +- `typecheck:noimplicit:core` (เชิงแนะนำ) — ถูกแทนที่โดย ratchet `check:type-coverage` ที่บล็อกอยู่เป็นส่วนใหญ่แล้ว ให้สลับเป็น ratchet หรือนำการรัน `tsc` รอบที่สองซึ่งซ้ำซ้อนออก +- `test:vitest:ui` (ขณะนี้ **บล็อก**) — ความล้มเหลวที่มีอยู่เดิมถูกยกเว้นไว้อย่างชัดเจนใน `vitest.config.ts` พร้อมความคิดเห็นติดตาม `// #8618` ส่วนความล้มเหลวใหม่จะทำให้งานล้มเหลว +- `check:secrets` (gitleaks ซึ่งเป็น ratchet แบบบล็อกที่ตรึงไว้ที่ผลบวกลวงที่มีเอกสารกำกับ 3 รายการ) — เพิ่มทั้ง 3 รายการลงใน allowlist เพื่อให้เหลือ 0 หรือลดระดับเป็นเชิงแนะนำ มีขอบเขตทับซ้อนกับการสแกน secret แบบเนทีฟของ GitHub + `check:public-creds` +- `check:pr-evidence` (บล็อก โดยค้นหาข้อความในเนื้อหา PR ด้วย grep) — มีความเสี่ยงสูงที่จะเกิดผลบวกลวง หากนำออกจะทำให้การบังคับใช้กฎตายตัวข้อที่ 18 อ่อนแอลง ดังนั้นนี่จึงเป็นการตัดสินใจเชิงนโยบายอย่างแท้จริง +- `semgrep` (แบบแยกเดี่ยวเชิงแนะนำ) — มีขอบเขตทับซ้อนกับ CodeQL สำหรับกลุ่ม OWASP ให้เชื่อม baseline ของมันเข้ากับ ratchet หรือนำออก --- diff --git a/docs/i18n/th/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/th/docs/compression/COMPRESSION_GUIDE.md index fb6258094f8d..0b29a7a13077 100644 --- a/docs/i18n/th/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/th/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ const { messages: aged, saved } = applyAging(messages, { สำหรับ body ที่มี `messages` การข้ามตามเนื้อหา (`shouldBypassCavemanOutputMode()` ใน `open-sse/services/compression/outputMode.ts`) จะตรวจสอบสามข้อความล่าสุดและข้าม สไตล์สำหรับทั้ง turn เมื่อข้อความตรงกับคีย์เวิร์ดด้านความปลอดภัย การกระทำที่ย้อนกลับไม่ได้ -การขอคำชี้แจง หรือลำดับที่มีผล การข้ามนี้ทำงานตามค่าที่ตั้งไว้ในสวิตช์ -**การข้ามเพื่อความชัดเจนอัตโนมัติ** (`cavemanOutputMode.autoClarity`) ของแดชบอร์ด +การขอคำชี้แจง หรือลำดับที่มีผล การข้ามนี้ทำงานขณะที่สวิตช์ **การข้ามเพื่อความชัดเจนอัตโนมัติ** (`cavemanOutputMode.autoClarity`) ของแดชบอร์ดเปิดอยู่ ซึ่งเป็นค่าเริ่มต้น เมื่อสวิตช์ปิดอยู่ สไตล์ที่เลือกจะมีผลใน turn เหล่านั้นด้วย เมื่อการตรวจสอบอนุญาตให้ turn ผ่าน `placeSystemInstruction()` (ไฟล์เดียวกัน) ซึ่ง จะไม่สร้าง `messages[0]` ใหม่ จะวางบล็อกไว้ในตำแหน่งแรกที่พบตามลำดับต่อไปนี้: diff --git a/docs/i18n/th/docs/reference/ENVIRONMENT.md b/docs/i18n/th/docs/reference/ENVIRONMENT.md index 7f0f607bf040..c60fca022c2a 100644 --- a/docs/i18n/th/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/th/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Secret ที่จำเป็น -ค่าเหล่านี้ **ต้อง** ถูกกำหนดก่อนการรันครั้งแรก หากไม่มีค่าเหล่านี้ แอปพลิเคชันจะปฏิเสธการเริ่มต้นหรือทำงานโดยใช้ค่าเริ่มต้นที่ไม่ปลอดภัย - -| ตัวแปร | จำเป็น | ค่าเริ่มต้น | ไฟล์ต้นทาง | คำอธิบาย | -| ---------------------------- | -------------------- | ---------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **ใช่** | _(ไม่มี)_ | `src/lib/auth` | ใช้ลงนาม/ตรวจสอบคุกกี้เซสชันของแดชบอร์ดทั้งหมด (JWT) สร้างด้วย `openssl rand -base64 48` | -| `API_KEY_SECRET` | **ใช่** | _(ไม่มี)_ | `src/lib/db/apiKeys.ts` | คีย์เข้ารหัส AES สำหรับค่าคีย์ API ที่จัดเก็บอยู่ใน SQLite สร้างด้วย `openssl rand -hex 32` | -| `INITIAL_PASSWORD` | **ใช่** | `CHANGEME` | สคริปต์ Bootstrap | กำหนดรหัสผ่านเริ่มต้นสำหรับผู้ดูแลระบบของแดชบอร์ด (ตรงกับค่าเริ่มต้นใน `.env.example` — ตั้งใจให้เห็นชัดว่าไม่ปลอดภัยเพื่อบังคับให้เปลี่ยน) **เปลี่ยนก่อนใช้งานครั้งแรก** หลังจากเข้าสู่ระบบ ให้เปลี่ยนผ่าน Dashboard → Settings → Security | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **ใช่** (production) | _(ไม่ได้กำหนด)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secret ที่ใช้ร่วมกันสำหรับบริดจ์ WebSocket ภายในของ Codex Responses ใช้ยืนยันตัวตนคำขอบริดจ์ระหว่างรีเลย์ WS ของ Electron/เบราว์เซอร์กับ OmniRoute ⚠️ **จำเป็นใน production — เมื่อไม่ได้กำหนดค่า คำขอบริดจ์ WS ทั้งหมดจะถูกปฏิเสธ** สร้างด้วย `openssl rand -base64 32` | -| `OMNIROUTE_SW_BUILD_ID` | ไม่ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID สำหรับทำให้แคชของ service worker ไม่ตรงกันอย่างชัดเจนสำหรับ PWA shell (#11779) โดยเป็นค่าแรกในลำดับการแก้ไขค่า | -| `SOURCE_VERSION` | ไม่ | _(ไม่ได้กำหนด)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ค่าลำดับที่สอง — กำหนดโดยตัวสร้างของ PaaS (เช่น แบบ Heroku) ให้เป็น commit ที่นำไปใช้งาน | -| `NEXT_PUBLIC_SW_BUILD_ID` | ไม่ | _(ได้จากการคำนวณ)_ | `src/shared/components/PwaRegister.tsx` | ค่าสาธารณะในขั้นตอน build ที่ไคลเอนต์ใช้เพื่อลงทะเบียน `/sw.js?v=…`; คำนวณจากสองค่าด้านบน แล้วตามด้วย git SHA | -| `OMNIROUTE_PEER_STAMP_TOKEN` | ไม่ (อัตโนมัติ) | _(อัตโนมัติต่อการบูต)_ | `src/server/authz/policies/management.ts` | Secret ต่อโปรเซสที่ใช้พิสูจน์ว่าตราประทับ peer-IP ที่เชื่อถือได้มาจากเซิร์ฟเวอร์ HTTP ของ OmniRoute เอง (`scripts/dev/peer-stamp.mjs`) มิดเดิลแวร์ authz จะเชื่อถือความเป็น local ของคำขอ (การควบคุมเส้นทาง LOCAL_ONLY ด้วย loopback/LAN) เฉพาะเมื่อตราประทับมีโทเค็นนี้ ระบบจะสร้างค่าใหม่โดยอัตโนมัติทุกครั้งที่บูต — ไม่ต้องกำหนดค่าไว้; ให้ตรึงค่าเฉพาะการตั้งค่าแบบหลายโปรเซสที่จำเป็นต้องใช้ตราประทับร่วมกัน | -| `OMNIROUTE_TRUSTED_PROXIES` | ไม่ | _(ไม่ได้กำหนด)_ | `scripts/dev/peer-stamp.mjs` | IP หรือช่วง CIDR ของ reverse proxy ที่คั่นด้วยเครื่องหมายจุลภาค ซึ่งรายการอนุญาต/ปฏิเสธ IP สามารถเชื่อถือ `X-Forwarded-For` / `X-Real-IP` จากพร็อกซีเหล่านั้นได้ นอกเหนือจาก loopback, ที่อยู่เครือข่ายส่วนตัว และ edge ของ Cloudflare ซึ่งได้รับความเชื่อถือเสมอ จำเป็นเฉพาะสำหรับพร็อกซีที่ใช้ที่อยู่สาธารณะอื่นเท่านั้น มิฉะนั้นตัวกรองจะพิจารณาที่อยู่ของพร็อกซีเอง | +Secret ที่ทำเครื่องหมายว่า **ใช่** และยังไม่ได้กำหนด จะถูกสร้างขึ้นอัตโนมัติเมื่อรันครั้งแรก (`JWT_SECRET`, `API_KEY_SECRET`) และจัดเก็บไว้ใน `server.env` ในไดเรกทอรีข้อมูล — ให้กำหนดค่าเหล่านี้อย่างชัดเจนเมื่อไดเรกทอรีดังกล่าวเป็นแบบชั่วคราว ข้อยกเว้นคือ `OMNIROUTE_WS_BRIDGE_SECRET`: ค่านี้จะไม่ถูกจัดเก็บใน `server.env` — จุดเริ่มต้นของเซิร์ฟเวอร์จะสร้างค่าชั่วคราวต่อการบูตหนึ่งค่าซึ่งมีเพียงบริดจ์ในโปรเซสเดียวกันเท่านั้นที่ใช้ได้ ดังนั้นไคลเอนต์บริดจ์ภายนอกจึงต้องกำหนดค่านี้อย่างชัดเจน + +| ตัวแปร | จำเป็น | ค่าเริ่มต้น | ไฟล์ต้นทาง | คำอธิบาย | +| ---------------------------- | -------------------- | ---------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **ใช่** | _(ไม่มี)_ | `src/lib/auth` | ใช้ลงนาม/ตรวจสอบคุกกี้เซสชันของแดชบอร์ดทั้งหมด (JWT) สร้างด้วย `openssl rand -base64 48` | +| `API_KEY_SECRET` | **ใช่** | _(ไม่มี)_ | `src/lib/db/apiKeys.ts` | คีย์เข้ารหัส AES สำหรับค่าคีย์ API ที่จัดเก็บอยู่ใน SQLite สร้างด้วย `openssl rand -hex 32` | +| `INITIAL_PASSWORD` | ไม่ | _(ไม่ได้กำหนด)_ | `src/lib/auth/managementPassword.ts` | กำหนดรหัสผ่านของแดชบอร์ดตอนเริ่มระบบในขณะที่ยังไม่มีรหัสผ่านที่บันทึกไว้ และจะถูกละเว้นเมื่อมีรหัสผ่านที่บันทึกไว้แล้ว การติดตั้งแบบ npm และแบบซอร์สจะคัดลอก `.env.example` ไปเป็น `.env` จึงเริ่มต้นด้วยค่าตัวแทนที่รู้กันโดยทั่วไปของไฟล์นั้นคือ `CHANGEME` ให้แทนที่ก่อนบูตครั้งแรก หรือตั้ง `INITIAL_PASSWORD=` (ว่างเปล่า) ในไฟล์ `.env` นั้นเพื่อสร้างรหัสผ่านในตัวช่วยเริ่มต้นใช้งานเมื่อเข้าชมครั้งแรก ซึ่งเป็นสิ่งที่ค่าที่ไม่ได้กำหนด (ค่าเริ่มต้นของอิมเมจ Docker) ทำ เปลี่ยนรหัสผ่านที่บันทึกไว้ใน Dashboard → Settings → Security หรือรัน `omniroute reset-password` (`node bin/reset-password.mjs` ใน source checkout) โดยตั้ง `DATA_DIR` ให้ชี้ไปยังไดเรกทอรีข้อมูลของเซิร์ฟเวอร์ | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **ใช่** (production) | _(ไม่ได้กำหนด)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Secret ที่ใช้ร่วมกันสำหรับบริดจ์ WebSocket ภายในของ Codex Responses ใช้ยืนยันตัวตนคำขอบริดจ์ระหว่างรีเลย์ WS ของ Electron/เบราว์เซอร์กับ OmniRoute ⚠️ **จำเป็นใน production — เมื่อไม่ได้กำหนดค่า คำขอบริดจ์ WS ทั้งหมดจะถูกปฏิเสธ** สร้างด้วย `openssl rand -base64 32` | +| `OMNIROUTE_SW_BUILD_ID` | ไม่ | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID สำหรับทำให้แคชของ service worker ไม่ตรงกันอย่างชัดเจนสำหรับ PWA shell (#11779) โดยเป็นค่าแรกในลำดับการแก้ไขค่า | +| `SOURCE_VERSION` | ไม่ | _(ไม่ได้กำหนด)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ค่าลำดับที่สอง — กำหนดโดยตัวสร้างของ PaaS (เช่น แบบ Heroku) ให้เป็น commit ที่นำไปใช้งาน | +| `NEXT_PUBLIC_SW_BUILD_ID` | ไม่ | _(ได้จากการคำนวณ)_ | `src/shared/components/PwaRegister.tsx` | ค่าสาธารณะในขั้นตอน build ที่ไคลเอนต์ใช้เพื่อลงทะเบียน `/sw.js?v=…`; คำนวณจากสองค่าด้านบน แล้วตามด้วย git SHA | +| `OMNIROUTE_PEER_STAMP_TOKEN` | ไม่ (อัตโนมัติ) | _(อัตโนมัติต่อการบูต)_ | `src/server/authz/policies/management.ts` | Secret ต่อโปรเซสที่ใช้พิสูจน์ว่าตราประทับ peer-IP ที่เชื่อถือได้มาจากเซิร์ฟเวอร์ HTTP ของ OmniRoute เอง (`scripts/dev/peer-stamp.mjs`) มิดเดิลแวร์ authz จะเชื่อถือความเป็น local ของคำขอ (การควบคุมเส้นทาง LOCAL_ONLY ด้วย loopback/LAN) เฉพาะเมื่อตราประทับมีโทเค็นนี้ ระบบจะสร้างค่าใหม่โดยอัตโนมัติทุกครั้งที่บูต — ไม่ต้องกำหนดค่าไว้; ให้ตรึงค่าเฉพาะการตั้งค่าแบบหลายโปรเซสที่จำเป็นต้องใช้ตราประทับร่วมกัน | +| `OMNIROUTE_TRUSTED_PROXIES` | ไม่ | _(ไม่ได้กำหนด)_ | `scripts/dev/peer-stamp.mjs` | IP หรือช่วง CIDR ของ reverse proxy ที่คั่นด้วยเครื่องหมายจุลภาค ซึ่งรายการอนุญาต/ปฏิเสธ IP สามารถเชื่อถือ `X-Forwarded-For` / `X-Real-IP` จากพร็อกซีเหล่านั้นได้ นอกเหนือจาก loopback, ที่อยู่เครือข่ายส่วนตัว และ edge ของ Cloudflare ซึ่งได้รับความเชื่อถือเสมอ จำเป็นเฉพาะสำหรับพร็อกซีที่ใช้ที่อยู่สาธารณะอื่นเท่านั้น มิฉะนั้นตัวกรองจะพิจารณาที่อยู่ของพร็อกซีเอง | ### คำสั่งสำหรับสร้างค่า @@ -215,38 +215,38 @@ OmniRoute ใช้ **SQLite** (ผ่าน `better-sqlite3`) สำหรั ## 4. ความปลอดภัยและการยืนยันตัวตน -| ตัวแปร | ค่าเริ่มต้น | ไฟล์ต้นฉบับ | คำอธิบาย | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ค่า Salt ที่ใช้ร่วมกับตัวระบุฮาร์ดแวร์เพื่อสร้างลายนิ้วมือของเครื่อง ควรเปลี่ยนแยกตามแต่ละการปรับใช้เพื่อให้มีการแยกส่วน | -| `OMNIROUTE_CLI_SALT` | _(ไม่ได้ตั้งค่า = ค่า Salt แบบสุ่มต่อการติดตั้ง ซึ่งจัดเก็บถาวรไว้ที่ `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | ค่า Salt สำหรับ HMAC ที่ใช้สร้างโทเค็นยืนยันตัวตน CLI ภายในเครื่อง การตั้งค่านี้จะหมุนเวียนโทเค็น CLI ทั้งหมดบนเครื่อง และมีลำดับความสำคัญเหนือค่า Salt ที่จัดเก็บถาวรเสมอ ดู `docs/security/CLI_TOKEN.md` | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ตั้งค่าแฟล็ก `Secure` บนคุกกี้เซสชัน **ต้องเป็น `true`** เมื่อทำงานอยู่เบื้องหลัง HTTPS | -| `REQUIRE_API_KEY` | `false` | มิดเดิลแวร์ API | เมื่อเป็น `true` คำขอพร็อกซี `/v1/*` ทั้งหมดต้องมี API key ที่ถูกต้อง **แฟล็กนี้ไม่ได้ควบคุม `GET /v1/models`** ซึ่งจะเป็นไปตามสถานะการเข้าสู่ระบบของแดชบอร์ด (`requireAuthForModels`) แทน ดังนั้นการได้รับ `401` จาก `/v1/models` จึงไม่ได้หมายความว่าการอนุมานได้รับการป้องกัน ดู `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | อนุญาตให้เปิดเผยค่า API key แบบเต็มใน UI ของแดชบอร์ด สามารถกำหนดค่าได้จากแฟล็กคุณลักษณะของแดชบอร์ด และมีความเสี่ยงด้านความปลอดภัยบนอินสแตนซ์ที่ใช้ร่วมกัน | -| `NO_LOG_API_KEY_IDS` | _(ว่าง)_ | `src/lib/compliance/index.ts` | ID ของ API key ที่คั่นด้วยเครื่องหมายจุลภาค ซึ่งจะข้ามการบันทึกคำขอ (เพื่อให้สอดคล้องกับ GDPR) | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ไม่ได้ตั้งค่า = ไม่จำกัด)_ | `src/shared/utils/apiKeyPolicy.ts` | งบประมาณคำขอต่อวันสำรองที่ใช้กับ API key ซึ่งคอลัมน์ `rate_limits` เป็น null หากไม่ได้ตั้งค่าหรือเว้นว่าง: ไม่มีขีดจำกัดโดยนัย (#2289, #11017) ค่า `0` มีความหมายเหมือนกัน (ไม่จำกัด) จำนวนเต็มบวก N จะเปิดใช้ N/วัน, 5N/สัปดาห์, 20N/เดือน ค่าที่ไม่ว่างแต่มีรูปแบบไม่ถูกต้องจะย้อนกลับไปใช้ช่วงจำกัดแบบเดิมที่ 1000/วัน, 5000/สัปดาห์, 20000/เดือน | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ขนาดเนื้อหาคำขอสูงสุดที่อนุญาต ปฏิเสธเพย์โหลดที่เกินขีดจำกัดนี้ | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | เนื้อหาคำขอจริงที่มีขนาดเท่ากับหรือสูงกว่าเกณฑ์นี้จะได้รับสัญญาเช่าสิทธิ์การรับโหลดหนักแบบอะตอมมิกภายในโปรเซส ก่อนแยกวิเคราะห์ JSON (เส้นทาง BYTE รวมถึง `POST /v1/responses`) ใช้กลไกหลีกเลี่ยงเมื่อมีพื้นที่ทรัพยากรสำรองเพียงพอแบบเดียวกับกรณีที่มีโครงสร้างซับซ้อนใน [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) แต่ยังคงถูกจำกัดด้วย `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) เพื่อไม่ให้ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) กลับมาเกิดขึ้นอีก | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ขีดจำกัดสูงสุดแบบตายตัวของเส้นทางแชต ซึ่งบังคับใช้กับจำนวนไบต์ที่อ่านระหว่างการรับข้อมูลแบบมีขอบเขต รวมถึงคำขอที่ไม่มี `Content-Length` มีค่าไม่ถูกต้อง หรือระบุค่าไม่ตรงความจริง ส่วนที่เกินจะได้รับ `413` | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ไม่ได้ตั้งค่า — ไม่มีขีดจำกัดจำนวนคำขอ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ขีดจำกัดจำนวนคำขอแบบเดิมนี้จะมีผลเฉพาะเมื่อตั้งค่าไว้อย่างชัดเจนเท่านั้น หากไม่ได้ตั้งค่าไว้ (ค่าเริ่มต้น) การรับคำขอแชตที่ใช้ทรัพยากรสูงจะถูกควบคุมด้วย `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` แทน ซึ่งเป็นงบประมาณแบบไบต์ที่คำนวณอัตโนมัติจากเพดานหน่วยความจำจริงของโปรเซสใน **หนึ่งโปรเซส** (หนึ่ง V8 heap) คำขอ `/v1/responses` ที่มีโทเค็นประมาณ 750k สองคำขอซ้อนทับกันทำให้ heap ขนาดประมาณ 12 Gi ล้ม (#7849) — นี่เป็นคำเตือนเรื่อง **งบประมาณหน่วยความจำ** ไม่ใช่ค่าสูงสุดตายตัวของผลิตภัณฑ์ที่ 2 คำขอ โปรเซสที่มีสถานะปกติ (heap ต่ำกว่าอัตราส่วนการลดโหลด) อาจรับคำขอ `/v1/responses` แบบยาวพร้อมกันได้มากขึ้นผ่าน `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ไคลเอนต์ SSE แบบยาวหลายสิบราย (40–50 ราย) ถูกจำกัดด้วย heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ไม่ใช่ “สูงสุด 2” การเพิ่มค่านี้โดยไม่พิจารณาเพื่อ “ใช้ทรัพยากรของโฮสต์” จะทำให้ปัญหา #7849 กลับมาอีก เพิ่มจำนวน **heap** ด้วย **`DATA_DIR` อิสระ N รายการ** (#11024) และห้ามใช้ `replicas>1` กับไฟล์ SQLite ไฟล์เดียว | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(คำนวณอัตโนมัติ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ค่าสำหรับแทนที่งบประมาณไบต์ในการรับข้อมูลที่คำนวณอัตโนมัติ (25% ของเพดานหน่วยความจำ V8/cgroup ที่เข้มงวดกว่า หารด้วยการขยายตัวชั่วคราว 8 เท่า) ทั้งค่าที่คำนวณและค่าที่ระบุไว้อย่างชัดเจนจะถูกจำกัดให้อยู่ระหว่าง 8 MiB–2 GiB เนื้อหาคำขอที่ใหญ่กว่างบประมาณที่มีผลจะล้มเหลวทันทีด้วย `413 body_exceeds_budget` ส่วนการแย่งทรัพยากรระหว่างเนื้อหาคำขอที่แต่ละรายการสามารถให้บริการได้ยังคงลองใหม่ได้ด้วย `503` ไคลเอนต์ SSE แบบยาวที่ทำงานพร้อมกัน 40–50 รายถูกจำกัดด้วยงบประมาณนี้ + heap ไม่ใช่ค่า “สูงสุด 2” แบบตายตัว โปรดอ่าน `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ที่ `/api/monitoring/health` ก่อนปรับแต่ง | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | อัตราส่วนการลดโหลดเมื่อ heap อยู่ภายใต้แรงกดดัน (`heapUsed / heap_size_limit`) สำหรับการรับคำขอที่ใช้ทรัพยากรสูงแบบ BYTE และ STRUCTURE (#10183, #10268, #10437) คำขอที่ใช้ทรัพยากรสูงซึ่งทำงานพร้อมกันเกิน `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` จะถูกลดโหลดด้วย `503` ที่ลองใหม่ได้ก็ต่อเมื่อ heap อยู่ที่หรือสูงกว่าอัตราส่วนนี้ **ด้วย** เท่านั้น หาก heap มีสถานะปกติ คำขอจะได้รับการยอมรับผ่านพื้นที่สำรองเมื่อสถานะปกติแทน | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ค่าเริ่มต้น `1`) | `src/shared/middleware/chatBodyAdmission.ts` | ความจุเพิ่มเติมแบบมีขอบเขตสำหรับเส้นทางด่วนเมื่อ heap มีสถานะปกติ (#10437) ทั้งในกรณี STRUCTURE และ BYTE (`admitChatRequest` รวมถึงเนื้อหาคำขอ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`) หากไม่มีขอบเขตนี้ ทุกคำขอที่ระบบไม่ว่างแต่ heap ยังมีสถานะปกติจะข้ามการควบคุมการรับคำขอโดยไม่มีเพดาน เมื่อมี lease ที่ทำงานพร้อมกันผ่านเส้นทาง heap สถานะปกติครบตามจำนวนนี้แล้ว คำขอเพิ่มเติมในช่วงที่ระบบไม่ว่างจะเข้าสู่เส้นทางรอแบบมีขอบเขต/ลดโหลด **เดียวกัน** กับที่ใช้เมื่อ heap อยู่ภายใต้แรงกดดันจริง การตั้งค่าเป็น `0` จะปิดการข้ามนี้ทั้งหมด | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | จำนวนข้อความที่ใช้จัดประเภทคำขอแชตว่าใช้ทรัพยากรสูง แม้ว่าเนื้อหาคำขอจะมีขนาดต่ำกว่าเกณฑ์ไบต์ | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | จำนวนเครื่องมือที่ใช้จัดประเภทคำขอแชตว่าใช้ทรัพยากรสูง แม้ว่าเนื้อหาคำขอจะมีขนาดต่ำกว่าเกณฑ์ไบต์ | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ค่าประมาณจำนวนโทเค็นแบบระมัดระวังจากขนาดสตริง ซึ่งใช้จัดประเภทคำขอว่าใช้ทรัพยากรสูง ค่านี้เป็นตัวแทนต้นทุนสำหรับการควบคุมการรับคำขอ ไม่ใช่การแบ่งโทเค็นสำหรับการเรียกเก็บเงินของผู้ให้บริการ | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ปิดใช้งาน) | `src/shared/middleware/chatBodyAdmission.ts` | ขีดจำกัดประวัติแชตแบบเลือกเปิดใช้ ปิดใช้งานโดยค่าเริ่มต้น เนื่องจากจำนวนข้อความเป็นนโยบายของการติดตั้งใช้งาน ไม่ใช่คุณสมบัติสากลของคำขอ และการจำกัดที่นี่จะปฏิเสธการสนทนาด้วย `413` แบบยุติคำขอ ก่อนที่ไปป์ไลน์การบีบอัดจะทำให้สามารถให้บริการได้ การเพิ่มขึ้นของ heap ถูกจำกัดด้วย `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` และการลดโหลดเมื่อ heap อยู่ภายใต้แรงกดดัน ให้ตั้งค่าเป็นจำนวนบวกในการติดตั้งใช้งานที่มีหน่วยความจำจำกัดและต้องการเพดานแบบตายตัว เมื่อเกินขีดจำกัดจะได้รับ `413` แบบมีโครงสร้างที่ระบุว่าจำเป็นต้องย่อข้อมูล | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ขีดจำกัดสูงสุดแบบตายตัวสำหรับการตอบกลับจากต้นทางที่ไม่ใช่แบบสตรีม ซึ่งถูกบัฟเฟอร์ไว้ในหน่วยความจำทั้งหมด หากเกินขีดจำกัดนี้ ตัวอ่านจากต้นทางจะถูกยกเลิกและคำขอจะล้มเหลวอย่างรวดเร็ว แทนที่จะปล่อยให้สตริงขยายตัวโดยไม่มีขอบเขตจนกว่า heap จะหมด | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | จำนวนไบต์สูงสุดบนสายที่ส่งต่อจากส่วนหัวการตอบกลับของต้นทาง เมื่อเกินงบประมาณ ส่วนหัวที่มีลำดับความสำคัญต่ำกว่า (เช่น `x-codex-*`, `x-oai-request-id`) จะถูกละทิ้งเพื่อให้อยู่ภายในขีดจำกัดส่วนหัวทั่วไปของ reverse proxy ตั้งค่าให้สูงขึ้นเพื่อส่งต่อข้อมูลเมตาจากต้นทางได้มากขึ้น โดยแลกกับขนาดส่วนหัวการตอบกลับที่ใหญ่ขึ้น | -| `CORS_ORIGIN` | _(ไม่ได้ตั้งค่า)_ | `src/server/cors/origins.ts` | รายการอนุญาต CORS แบบต้นทางเดียวรุ่นเก่า สำหรับการปรับใช้ใหม่ ควรใช้ `CORS_ALLOWED_ORIGINS` CORS มีไว้สำหรับไคลเอนต์ API บนเบราว์เซอร์ที่เรียกข้ามต้นทางเท่านั้น ส่วนการเขียนข้อมูลบนแดชบอร์ดที่ผ่านการยืนยันตัวตนจะใช้คำขอจากต้นทางเดียวกันร่วมกับการป้องกัน CSRF ที่ผูกกับเซสชันแทน | -| `CORS_ALLOWED_ORIGINS` | _(ไม่ได้ตั้งค่า)_ | `src/server/cors/origins.ts` | รายการอนุญาต CORS ที่คั่นด้วยจุลภาค จะไม่มีการส่งไวลด์การ์ด เว้นแต่จะกำหนดค่า `CORS_ALLOW_ALL=true` ไว้อย่างชัดเจน | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ทางเลี่ยงสำหรับการพัฒนาเท่านั้น เพื่อสะท้อนค่า `Origin` ใดๆ ของเบราว์เซอร์กลับไป ห้ามเปิดใช้ในการปรับใช้แบบใช้ร่วมกันหรือในสภาพแวดล้อมการใช้งานจริง | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | บล็อกการเรียกผู้ให้บริการที่กำหนดเป้าหมายไปยังช่วง IP ส่วนตัว/ลูปแบ็ก/ลิงก์โลคัล ปิดใช้เฉพาะในสภาพแวดล้อมทดสอบที่แยกออกจากระบบอื่นเท่านั้น | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | อนุญาต URL ของผู้ให้บริการที่ชี้ไปยังเครือข่ายส่วนตัว/ภายในเครื่อง (localhost, 192.168.x.x, 10.x.x.x ฯลฯ) **จำเป็นสำหรับผู้ให้บริการที่โฮสต์เอง** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG) เมื่อเป็น `false` แดชบอร์ดจะปฏิเสธการตรวจสอบความถูกต้องของ URL ภายในเครื่อง | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | อนุญาตให้เพิ่ม/ตรวจสอบความถูกต้องของผู้ให้บริการบนที่อยู่ภายในเครื่อง/ส่วนตัว (127.0.0.1, localhost, LAN, ช่วงที่อยู่ส่วนตัว) — จำกัดขอบเขตเฉพาะเส้นทางการตรวจสอบผู้ให้บริการ **ค่าเริ่มต้นคือ `true`** (ให้ความสำคัญกับภายในเครื่อง); ตั้งเป็น `false` เพื่อบังคับใช้การบล็อกที่เข้มงวดโดยอนุญาตเฉพาะที่อยู่สาธารณะ ปลายทางข้อมูลเมตาของคลาวด์ (169.254.169.254, metadata.google.internal) จะยังคงถูกบล็อกเสมอ (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | อนุญาตให้เส้นทาง `/v1/audio/*` (การถอดเสียง การสังเคราะห์เสียง และการแปล) ใช้โหนดผู้ให้บริการที่เข้ากันได้กับ OpenAI ซึ่งโฮสต์อยู่นอก localhost ปิดไว้โดยค่าเริ่มต้น — การกำหนดเส้นทางเสียงไปยังโฮสต์ระยะไกลจะเปลี่ยนอัตลักษณ์ขาออกและต้องเป็นการตัดสินใจอย่างชัดเจนของผู้ปฏิบัติงาน โหนดลูปแบ็ก/ส่วนตัว (localhost, 127.0.0.1, 172.16-31.x) จะได้รับอนุญาตเสมอและไม่ได้รับผลกระทบ (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | อนุญาตให้ `POST /v1/rerank` (รวมถึงขั้นตอนจัดอันดับใหม่แบบลูปแบ็กของเอนจินหน่วยความจำ) ใช้โหนดผู้ให้บริการที่เข้ากันได้กับ OpenAI ซึ่งโฮสต์อยู่นอก localhost — เครื่องใน LAN หรือเพียร์ Tailscale ที่ใช้งาน TEI, Infinity, vLLM ฯลฯ ปิดไว้โดยค่าเริ่มต้น — การกำหนดเส้นทางไปยังโฮสต์ระยะไกลจะเปลี่ยนอัตลักษณ์ขาออกและต้องเป็นการตัดสินใจอย่างชัดเจนของผู้ปฏิบัติงาน โหนดลูปแบ็ก (localhost, 127.0.0.1, 172.16-31.x) จะได้รับอนุญาตเสมอและไม่ได้รับผลกระทบ นอกจากนี้ โหนดระยะไกลต้องผ่านนโยบาย URL ขาออกของผู้ให้บริการ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); ระบบจะไม่กำหนดเส้นทางไปยังโฮสต์ข้อมูลเมตาของคลาวด์ | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | เมื่อเปิดใช้ OIDC ให้ปิดการเข้าสู่ระบบด้วยรหัสผ่าน เพื่อให้ผู้ใช้สามารถยืนยันตัวตนผ่านการลงชื่อเข้าระบบครั้งเดียวด้วย OIDC เท่านั้น ทั้งนี้ยังรองรับชื่อแทนแบบสั้น `OIDC_DISABLE_PASSWORD_LOGIN`; แฟล็กคุณลักษณะของแดชบอร์ดที่ใช้คีย์เดียวกันจะมีลำดับความสำคัญสูงกว่า (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | ชื่อแทนแบบสั้นของ `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889) | +| ตัวแปร | ค่าเริ่มต้น | ไฟล์ต้นฉบับ | คำอธิบาย | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | ค่า Salt ที่ใช้ร่วมกับตัวระบุฮาร์ดแวร์เพื่อสร้างลายนิ้วมือของเครื่อง ควรเปลี่ยนแยกตามแต่ละการปรับใช้เพื่อให้มีการแยกส่วน | +| `OMNIROUTE_CLI_SALT` | _(ไม่ได้ตั้งค่า = ค่า Salt แบบสุ่มต่อการติดตั้ง ซึ่งจัดเก็บถาวรไว้ที่ `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | ค่า Salt สำหรับ HMAC ที่ใช้สร้างโทเค็นยืนยันตัวตน CLI ภายในเครื่อง การตั้งค่านี้จะหมุนเวียนโทเค็น CLI ทั้งหมดบนเครื่อง และมีลำดับความสำคัญเหนือค่า Salt ที่จัดเก็บถาวรเสมอ ดู `docs/security/CLI_TOKEN.md` | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | ตั้งค่าแฟล็ก `Secure` บนคุกกี้เซสชัน **ต้องเป็น `true`** เมื่อทำงานอยู่เบื้องหลัง HTTPS | +| `REQUIRE_API_KEY` | `false` | มิดเดิลแวร์ API | เมื่อเป็น `true` คำขอพร็อกซี `/v1/*` ทั้งหมดต้องมี API key ที่ถูกต้อง **แฟล็กนี้ไม่ได้ควบคุม `GET /v1/models`** ซึ่งจะเป็นไปตามสถานะการเข้าสู่ระบบของแดชบอร์ด (`requireAuthForModels`) แทน ดังนั้นการได้รับ `401` จาก `/v1/models` จึงไม่ได้หมายความว่าการอนุมานได้รับการป้องกัน ดู `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695) | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | อนุญาตให้เปิดเผยค่า API key แบบเต็มใน UI ของแดชบอร์ด สามารถกำหนดค่าได้จากแฟล็กคุณลักษณะของแดชบอร์ด และมีความเสี่ยงด้านความปลอดภัยบนอินสแตนซ์ที่ใช้ร่วมกัน | +| `NO_LOG_API_KEY_IDS` | _(ว่าง)_ | `src/lib/compliance/index.ts` | ID ของ API key ที่คั่นด้วยเครื่องหมายจุลภาค ซึ่งจะข้ามการบันทึกคำขอ (เพื่อให้สอดคล้องกับ GDPR) | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ไม่ได้ตั้งค่า = ไม่จำกัด)_ | `src/shared/utils/apiKeyPolicy.ts` | งบประมาณคำขอต่อวันสำรองที่ใช้กับ API key ซึ่งคอลัมน์ `rate_limits` เป็น null หากไม่ได้ตั้งค่าหรือเว้นว่าง: ไม่มีขีดจำกัดโดยนัย (#2289, #11017) ค่า `0` มีความหมายเหมือนกัน (ไม่จำกัด) จำนวนเต็มบวก N จะเปิดใช้ N/วัน, 5N/สัปดาห์, 20N/เดือน ค่าที่ไม่ว่างแต่มีรูปแบบไม่ถูกต้องจะย้อนกลับไปใช้ช่วงจำกัดแบบเดิมที่ 1000/วัน, 5000/สัปดาห์, 20000/เดือน | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | ขนาดเนื้อหาคำขอสูงสุดที่อนุญาต ปฏิเสธเพย์โหลดที่เกินขีดจำกัดนี้ | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | เนื้อหาคำขอจริงที่มีขนาดเท่ากับหรือสูงกว่าเกณฑ์นี้จะได้รับสัญญาเช่าสิทธิ์การรับโหลดหนักแบบอะตอมมิกภายในโปรเซส ก่อนแยกวิเคราะห์ JSON (เส้นทาง BYTE รวมถึง `POST /v1/responses`) ใช้กลไกหลีกเลี่ยงเมื่อมีพื้นที่ทรัพยากรสำรองเพียงพอแบบเดียวกับกรณีที่มีโครงสร้างซับซ้อนใน [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) แต่ยังคงถูกจำกัดด้วย `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) เพื่อไม่ให้ [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) กลับมาเกิดขึ้นอีก | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | ขีดจำกัดสูงสุดแบบตายตัวของเส้นทางแชต ซึ่งบังคับใช้กับจำนวนไบต์ที่อ่านระหว่างการรับข้อมูลแบบมีขอบเขต รวมถึงคำขอที่ไม่มี `Content-Length` มีค่าไม่ถูกต้อง หรือระบุค่าไม่ตรงความจริง ส่วนที่เกินจะได้รับ `413` | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ไม่ได้ตั้งค่า — ไม่มีขีดจำกัดจำนวนคำขอ)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** ขีดจำกัดจำนวนคำขอแบบเดิมนี้จะมีผลเฉพาะเมื่อตั้งค่าไว้อย่างชัดเจนเท่านั้น หากไม่ได้ตั้งค่าไว้ (ค่าเริ่มต้น) การรับคำขอแชตที่ใช้ทรัพยากรสูงจะถูกควบคุมด้วย `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` แทน ซึ่งเป็นงบประมาณแบบไบต์ที่คำนวณอัตโนมัติจากเพดานหน่วยความจำจริงของโปรเซสใน **หนึ่งโปรเซส** (หนึ่ง V8 heap) คำขอ `/v1/responses` ที่มีโทเค็นประมาณ 750k สองคำขอซ้อนทับกันทำให้ heap ขนาดประมาณ 12 Gi ล้ม (#7849) — นี่เป็นคำเตือนเรื่อง **งบประมาณหน่วยความจำ** ไม่ใช่ค่าสูงสุดตายตัวของผลิตภัณฑ์ที่ 2 คำขอ โปรเซสที่มีสถานะปกติ (heap ต่ำกว่าอัตราส่วนการลดโหลด) อาจรับคำขอ `/v1/responses` แบบยาวพร้อมกันได้มากขึ้นผ่าน `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` ไคลเอนต์ SSE แบบยาวหลายสิบราย (40–50 ราย) ถูกจำกัดด้วย heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 ไม่ใช่ “สูงสุด 2” การเพิ่มค่านี้โดยไม่พิจารณาเพื่อ “ใช้ทรัพยากรของโฮสต์” จะทำให้ปัญหา #7849 กลับมาอีก เพิ่มจำนวน **heap** ด้วย **`DATA_DIR` อิสระ N รายการ** (#11024) และห้ามใช้ `replicas>1` กับไฟล์ SQLite ไฟล์เดียว | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(คำนวณอัตโนมัติ)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** ค่าสำหรับแทนที่งบประมาณไบต์ในการรับข้อมูลที่คำนวณอัตโนมัติ (25% ของเพดานหน่วยความจำ V8/cgroup ที่เข้มงวดกว่า หารด้วยการขยายตัวชั่วคราว 8 เท่า) ทั้งค่าที่คำนวณและค่าที่ระบุไว้อย่างชัดเจนจะถูกจำกัดให้อยู่ระหว่าง 8 MiB–2 GiB เนื้อหาคำขอที่ใหญ่กว่างบประมาณที่มีผลจะล้มเหลวทันทีด้วย `413 body_exceeds_budget` ส่วนการแย่งทรัพยากรระหว่างเนื้อหาคำขอที่แต่ละรายการสามารถให้บริการได้ยังคงลองใหม่ได้ด้วย `503` ไคลเอนต์ SSE แบบยาวที่ทำงานพร้อมกัน 40–50 รายถูกจำกัดด้วยงบประมาณนี้ + heap ไม่ใช่ค่า “สูงสุด 2” แบบตายตัว โปรดอ่าน `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ที่ `/api/monitoring/health` ก่อนปรับแต่ง | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | อัตราส่วนการลดโหลดเมื่อ heap อยู่ภายใต้แรงกดดัน (`heapUsed / heap_size_limit`) สำหรับการรับคำขอที่ใช้ทรัพยากรสูงแบบ BYTE และ STRUCTURE (#10183, #10268, #10437) คำขอที่ใช้ทรัพยากรสูงซึ่งทำงานพร้อมกันเกิน `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` จะถูกลดโหลดด้วย `503` ที่ลองใหม่ได้ก็ต่อเมื่อ heap อยู่ที่หรือสูงกว่าอัตราส่วนนี้ **ด้วย** เท่านั้น หาก heap มีสถานะปกติ คำขอจะได้รับการยอมรับผ่านพื้นที่สำรองเมื่อสถานะปกติแทน | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (ค่าเริ่มต้น `1`) | `src/shared/middleware/chatBodyAdmission.ts` | ความจุเพิ่มเติมแบบมีขอบเขตสำหรับเส้นทางด่วนเมื่อ heap มีสถานะปกติ (#10437) ทั้งในกรณี STRUCTURE และ BYTE (`admitChatRequest` รวมถึงเนื้อหาคำขอ ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`) หากไม่มีขอบเขตนี้ ทุกคำขอที่ระบบไม่ว่างแต่ heap ยังมีสถานะปกติจะข้ามการควบคุมการรับคำขอโดยไม่มีเพดาน เมื่อมี lease ที่ทำงานพร้อมกันผ่านเส้นทาง heap สถานะปกติครบตามจำนวนนี้แล้ว คำขอเพิ่มเติมในช่วงที่ระบบไม่ว่างจะเข้าสู่เส้นทางรอแบบมีขอบเขต/ลดโหลด **เดียวกัน** กับที่ใช้เมื่อ heap อยู่ภายใต้แรงกดดันจริง การตั้งค่าเป็น `0` จะปิดการข้ามนี้ทั้งหมด | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | จำนวนข้อความที่ใช้จัดประเภทคำขอแชตว่าใช้ทรัพยากรสูง แม้ว่าเนื้อหาคำขอจะมีขนาดต่ำกว่าเกณฑ์ไบต์ | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | จำนวนเครื่องมือที่ใช้จัดประเภทคำขอแชตว่าใช้ทรัพยากรสูง แม้ว่าเนื้อหาคำขอจะมีขนาดต่ำกว่าเกณฑ์ไบต์ | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | ค่าประมาณจำนวนโทเค็นแบบระมัดระวังจากขนาดสตริง ซึ่งใช้จัดประเภทคำขอว่าใช้ทรัพยากรสูง ค่านี้เป็นตัวแทนต้นทุนสำหรับการควบคุมการรับคำขอ ไม่ใช่การแบ่งโทเค็นสำหรับการเรียกเก็บเงินของผู้ให้บริการ | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (ปิดใช้งาน) | `src/shared/middleware/chatBodyAdmission.ts` | ขีดจำกัดประวัติแชตแบบเลือกเปิดใช้ ปิดใช้งานโดยค่าเริ่มต้น เนื่องจากจำนวนข้อความเป็นนโยบายของการติดตั้งใช้งาน ไม่ใช่คุณสมบัติสากลของคำขอ และการจำกัดที่นี่จะปฏิเสธการสนทนาด้วย `413` แบบยุติคำขอ ก่อนที่ไปป์ไลน์การบีบอัดจะทำให้สามารถให้บริการได้ การเพิ่มขึ้นของ heap ถูกจำกัดด้วย `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` และการลดโหลดเมื่อ heap อยู่ภายใต้แรงกดดัน ให้ตั้งค่าเป็นจำนวนบวกในการติดตั้งใช้งานที่มีหน่วยความจำจำกัดและต้องการเพดานแบบตายตัว เมื่อเกินขีดจำกัดจะได้รับ `413` แบบมีโครงสร้างที่ระบุว่าจำเป็นต้องย่อข้อมูล | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | ขีดจำกัดสูงสุดแบบตายตัวสำหรับการตอบกลับจากต้นทางที่ไม่ใช่แบบสตรีม ซึ่งถูกบัฟเฟอร์ไว้ในหน่วยความจำทั้งหมด หากเกินขีดจำกัดนี้ ตัวอ่านจากต้นทางจะถูกยกเลิกและคำขอจะล้มเหลวอย่างรวดเร็ว แทนที่จะปล่อยให้สตริงขยายตัวโดยไม่มีขอบเขตจนกว่า heap จะหมด | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | จำนวนไบต์สูงสุดบนสายที่ส่งต่อจากส่วนหัวการตอบกลับของต้นทาง เมื่อเกินงบประมาณ ส่วนหัวที่มีลำดับความสำคัญต่ำกว่า (เช่น `x-codex-*`, `x-oai-request-id`) จะถูกละทิ้งเพื่อให้อยู่ภายในขีดจำกัดส่วนหัวทั่วไปของ reverse proxy ตั้งค่าให้สูงขึ้นเพื่อส่งต่อข้อมูลเมตาจากต้นทางได้มากขึ้น โดยแลกกับขนาดส่วนหัวการตอบกลับที่ใหญ่ขึ้น | +| `CORS_ORIGIN` | _(ไม่ได้ตั้งค่า)_ | `src/server/cors/origins.ts` | รายการอนุญาต CORS แบบต้นทางเดียวรุ่นเก่า สำหรับการปรับใช้ใหม่ ควรใช้ `CORS_ALLOWED_ORIGINS` CORS มีไว้สำหรับไคลเอนต์ API บนเบราว์เซอร์ที่เรียกข้ามต้นทางเท่านั้น ส่วนการเขียนข้อมูลบนแดชบอร์ดที่ผ่านการยืนยันตัวตนจะใช้คำขอจากต้นทางเดียวกันร่วมกับการป้องกัน CSRF ที่ผูกกับเซสชันแทน | +| `CORS_ALLOWED_ORIGINS` | _(ไม่ได้ตั้งค่า)_ | `src/server/cors/origins.ts` | รายการอนุญาต CORS ที่คั่นด้วยจุลภาค จะไม่มีการส่งไวลด์การ์ด เว้นแต่จะกำหนดค่า `CORS_ALLOW_ALL=true` ไว้อย่างชัดเจน | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | ทางเลี่ยงสำหรับการพัฒนาเท่านั้น เพื่อสะท้อนค่า `Origin` ใดๆ ของเบราว์เซอร์กลับไป ห้ามเปิดใช้ในการปรับใช้แบบใช้ร่วมกันหรือในสภาพแวดล้อมการใช้งานจริง | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | ชื่อแฝงแบบเดิม (legacy alias) สวิตช์ในแดชบอร์ดของค่านี้ (การแทนที่ค่าใน DB) จะถูกอ่านก่อนค่าตัวแปรสภาพแวดล้อมนี้ ค่า `false`, `0`, `no` หรือ `off` ในทั้งสองที่จะปิดการตรวจสอบโฮสต์ในลักษณะเดียวกับ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` แม้ว่าสวิตช์ในแดชบอร์ดของแฟล็กนั้นจะถูกบันทึกไว้เป็นปิดก็ตาม ส่วนค่าอื่น ๆ จะทิ้งการป้องกันไว้ให้แฟล็กสองตัวด้านล่าง | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | ปิดการตรวจสอบโฮสต์ของตัวป้องกัน URL ขาออก รวมถึงการบล็อกข้อมูลเมตาของคลาวด์ (ทั้งหมดของ 169.254.0.0/16, metadata.google.internal) บนเส้นทางของผู้ให้บริการที่เป็นไปตามโหมดการป้องกัน: การตรวจสอบ URL, การค้นหาโมเดล, URL ฐานของโหนดผู้ให้บริการ (รวมถึงโหนดจัดอันดับใหม่ระยะไกล) และการทดสอบสำรองแบบพร็อกซี ตลอดจนเส้นทางขาออกอื่น ๆ ที่เกี่ยวข้องกับผู้ให้บริการ เช่น การดึงรูปภาพ/สื่อระยะไกล นอกจากนี้ยังอนุญาตปลายทาง webhook แบบส่วนตัว ยกเว้นปลายทางข้อมูลเมตาของคลาวด์/ลิงก์โลคัล ซึ่งยังคงถูกบล็อกแม้เปิดแฟล็กนี้อยู่ คำขอแชทที่ส่งผ่าน `BaseExecutor.execute()` ยังคงมีการตรวจสอบข้อมูลเมตาของคลาวด์ของตัวเอง ผู้ให้บริการภายในเครื่องทำงานได้ด้วยค่าเริ่มต้น: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ค่าเริ่มต้น `true`) อนุญาต URL ภายในเครื่องและ LAN บนเส้นทางตรวจสอบ ค้นหา และโหนดผู้ให้บริการอยู่แล้ว ส่วนการทดสอบสำรองแบบพร็อกซีและปลายทาง webhook แบบส่วนตัวจะดูเฉพาะแฟล็กนี้และบล็อกโฮสต์ภายในเครื่อง/LAN ขณะที่แฟล็กปิดอยู่ แทนที่ `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` ค่าที่บันทึกจากสวิตช์ในแดชบอร์ดมีความสำคัญเหนือตัวแปรนี้ | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | อนุญาต URL ของผู้ให้บริการบนที่อยู่ภายในเครื่อง/ส่วนตัว (127.0.0.1, localhost, LAN, ช่วงที่อยู่ส่วนตัว) สำหรับการตรวจสอบผู้ให้บริการ การค้นหาโมเดล การแชท และเส้นทางขาออกอื่น ๆ ที่เกี่ยวข้องกับผู้ให้บริการซึ่งอยู่ภายใต้โหมดการป้องกัน **ค่าเริ่มต้นคือ `true`** (ให้ความสำคัญกับภายในเครื่อง): ตัวป้องกันจะบล็อกปลายทางข้อมูลเมตาของคลาวด์ — ทั้งหมดของ 169.254.0.0/16 รวมถึงชื่อโฮสต์ข้อมูลเมตาที่รู้จัก (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254) ตั้งเป็น `false` สำหรับโหมดอนุญาตเฉพาะสาธารณะ ซึ่งจะบล็อกโฮสต์ส่วนตัวและลูปแบ็กด้วย ตัวป้องกันตรวจสอบชื่อโฮสต์หรือ IP ลิเทอรัลตามที่เขียนไว้ ก่อนการค้นหา DNS ใด ๆ ในโหมดอนุญาตเฉพาะสาธารณะ เรปเปอร์ safe-fetch จะค้นหาชื่อโฮสต์เพิ่มเติมและปฏิเสธคำตอบส่วนตัวใด ๆ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` จะปิดการตรวจสอบเหล่านี้บนเส้นทางที่แถวของแฟล็กนั้นระบุ ผู้ให้บริการภายในเครื่องในตัว เช่น LM Studio, Ollama, vLLM, Llamafile และ Triton จะข้ามการตรวจสอบโฮสต์ทั้งหมดสำหรับการตรวจสอบคีย์และการแชท ค่าที่บันทึกจากสวิตช์ในแดชบอร์ดมีความสำคัญเหนือตัวแปรนี้ (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | อนุญาตให้เส้นทาง `/v1/audio/*` (การถอดเสียง การสังเคราะห์เสียง และการแปล) ใช้โหนดผู้ให้บริการที่เข้ากันได้กับ OpenAI ซึ่งโฮสต์อยู่นอก localhost ปิดไว้โดยค่าเริ่มต้น — การกำหนดเส้นทางเสียงไปยังโฮสต์ระยะไกลจะเปลี่ยนอัตลักษณ์ขาออกและต้องเป็นการตัดสินใจอย่างชัดเจนของผู้ปฏิบัติงาน โหนดลูปแบ็ก/ส่วนตัว (localhost, 127.0.0.1, 172.16-31.x) จะได้รับอนุญาตเสมอและไม่ได้รับผลกระทบ (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | อนุญาตให้ `POST /v1/rerank` (รวมถึงขั้นตอนจัดอันดับใหม่แบบลูปแบ็กของเอนจินหน่วยความจำ) ใช้โหนดผู้ให้บริการที่เข้ากันได้กับ OpenAI ซึ่งโฮสต์อยู่นอก localhost — เครื่องใน LAN หรือเพียร์ Tailscale ที่ใช้งาน TEI, Infinity, vLLM ฯลฯ ปิดไว้โดยค่าเริ่มต้น — การกำหนดเส้นทางไปยังโฮสต์ระยะไกลจะเปลี่ยนอัตลักษณ์ขาออกและต้องเป็นการตัดสินใจอย่างชัดเจนของผู้ปฏิบัติงาน โหนดลูปแบ็ก (localhost, 127.0.0.1, 172.16-31.x) จะได้รับอนุญาตเสมอและไม่ได้รับผลกระทบ นอกจากนี้ URL ฐานของโหนดระยะไกลต้องผ่านนโยบาย URL ขาออกของผู้ให้บริการ (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | เมื่อเปิดใช้ OIDC ให้ปิดการเข้าสู่ระบบด้วยรหัสผ่าน เพื่อให้ผู้ใช้สามารถยืนยันตัวตนผ่านการลงชื่อเข้าระบบครั้งเดียวด้วย OIDC เท่านั้น ทั้งนี้ยังรองรับชื่อแทนแบบสั้น `OIDC_DISABLE_PASSWORD_LOGIN`; แฟล็กคุณลักษณะของแดชบอร์ดที่ใช้คีย์เดียวกันจะมีลำดับความสำคัญสูงกว่า (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | ชื่อแทนแบบสั้นของ `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889) | ### รายการตรวจสอบการเสริมความปลอดภัย diff --git a/docs/i18n/tr/CONTRIBUTING.md b/docs/i18n/tr/CONTRIBUTING.md index 5e61fa1b4715..c25d111a5a3d 100644 --- a/docs/i18n/tr/CONTRIBUTING.md +++ b/docs/i18n/tr/CONTRIBUTING.md @@ -397,19 +397,19 @@ Model tanımlarını `open-sse/config/providerRegistry.ts` dosyasına ekleyin. ## Pull Request Kontrol Listesi - [ ] Testler geçiyor (`npm test`) -- [ ] Lint denetimleri geçiyor (`npm run lint`) +- [ ] Lint denetimi geçiyor (`npm run lint`) - [ ] Derleme başarıyla tamamlanıyor (`npm run build`) -- [ ] Yeni herkese açık işlevler ve arayüzler için TypeScript türleri eklendi +- [ ] Yeni genel kullanıma açık fonksiyonlar ve arayüzler için TypeScript türleri eklendi - [ ] Sabit kodlanmış gizli bilgiler veya geri dönüş değerleri yok -- [ ] Herkese açık üst sağlayıcı kimlik bilgileri, sabit değerler olarak değil, `resolvePublicCred()` aracılığıyla gömüldü (bkz. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) -- [ ] Hata yanıtları `buildErrorBody()` / `sanitizeErrorMessage()` üzerinden yönlendiriliyor — yanıt gövdelerinde ham yığın izleri yok (bkz. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Kabuk komutları (`exec` / `spawn`), çalışma zamanı değerlerini dize enterpolasyonu aracılığıyla değil `env` üzerinden iletiyor -- [ ] Tüm girdiler Zod şemalarıyla doğrulanıyor -- [ ] Kullanıcıya yönelik değişiklikler için `changelog.d/{features|fixes|maintenance}/-.md` altına değişiklik günlüğü **parçası** eklendi (bkz. [`changelog.d/README.md`](./changelog.d/README.md)) — `CHANGELOG.md` dosyasını doğrudan **düzenlemeyin**; parçalar sürüm yayımlanırken birleştirilir ve PR'ler arasında hiçbir zaman çakışmaz -- [ ] Belgeler güncellendi (geçerliyse) -- [ ] Yeni CodeQL / Secret-Scanning uyarısı açılmadı veya her biri ilgili `docs/security/` belgesine atıfta bulunan teknik bir gerekçeyle kapatıldı -- [ ] Alt süreç başlatan rotalar (`/api/mcp/`, `/api/cli-tools/runtime/`), `src/server/authz/routeGuard.ts` içinde `isLocalOnlyPath()` olarak sınıflandırıldı — bkz. [Kesin Kural #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Commit mesajlarında `Co-Authored-By` alt bilgileri yok — commit'ler yalnızca depo sahibinin Git kimliği altında görünmelidir (Kesin Kural #16) +- [ ] Genel kullanıma açık üst kaynak kimlik bilgileri değişmez değer olarak değil, `resolvePublicCred()` aracılığıyla gömüldü (bkz. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)) +- [ ] Hata yanıtları `buildErrorBody()` / `sanitizeErrorMessage()` üzerinden yönlendiriliyor — yanıt gövdelerinde işlenmemiş yığın izleri yok (bkz. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Kabuk komutları (`exec` / `spawn`), çalışma zamanı değerlerini dize interpolasyonu yoluyla değil `env` aracılığıyla iletiyor +- [ ] Tüm girdiler Zod şemalarıyla doğrulandı +- [ ] Kullanıcıya yönelik değişiklikler için `changelog.d/{features|fixes|maintenance}/-.md` altında değişiklik günlüğü **parçası** eklendi (bkz. [`changelog.d/README.md`](./changelog.d/README.md)) — `CHANGELOG.md` dosyasını doğrudan düzenlemeyin; parçalar sürüm yayımlanırken birleştirilir ve PR'lar arasında hiçbir zaman çakışmaz +- [ ] Dokümantasyon güncellendi (uygunsa) +- [ ] Yeni CodeQL / Secret-Scanning uyarısı açılmadı veya her biri ilgili `docs/security/` belgesine atıfta bulunan teknik bir gerekçeyle reddedildi +- [ ] Alt süreç başlatan rotalar (`/api/mcp/`, `/api/cli-tools/runtime/`), `src/server/authz/routeGuard.ts` içindeki `isLocalOnlyPath()` kapsamında sınıflandırıldı — bkz. [Kesin Kural #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Commit mesajlarında AI/bot `Co-authored-by` son bilgileri yok (Kesin Kural #16) — çalışmalarından yararlanılan insan iş ortaklarına standart `Co-authored-by: Name ` son bilgileriyle atıfta bulunuldu --- diff --git a/docs/i18n/tr/docs/architecture/QUALITY_GATES.md b/docs/i18n/tr/docs/architecture/QUALITY_GATES.md index 29777480513a..85cb9cfb0440 100644 --- a/docs/i18n/tr/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/tr/docs/architecture/QUALITY_GATES.md @@ -19,217 +19,219 @@ sınıflandırması ve araçtan bağımsız çoğaltma planı için ### Aday kabulü -CI ve Quality Gates iş akışlarının her biri kararlı bir karar üretir: `Gate / CI` ve +CI ve Quality Gates iş akışlarının her biri kararlı bir sonuç üretir: `Gate / CI` ve `Gate / Quality`. Bunların sürümlendirilmiş kabul politikası, her üst akış işini zorunlu veya tavsiye niteliğinde olarak listeler. Uygulanabilir bir zorunlu iş başarılı olmalıdır: eksik, iptal edilmiş, atlanmış, beklemede olan ve bilinmeyen sonuçlar PASS -durumunu sağlayamaz. Geçerli bir yalnızca dokümantasyon veya yalnızca katalog sınıflandırması, -bir kod hattını uygulanamaz hâle getirebilir; taslak bir PR kabul edilmiş bir aday değildir. -`hotfix` etiketi kanıt gerekliliğini ortadan kaldırmaz. +durumunu sağlayamaz. Geçerli bir yalnızca dokümantasyon veya yalnızca katalog +sınıflandırması, bir kod hattını uygulanamaz hâle getirebilir; taslak PR kabul edilen +bir aday değildir. `hotfix` etiketi kanıt gerekliliğini ortadan kaldırmaz. Her iki iş akışı da PR'ları ve main/release dallarına gönderimleri, manuel tetiklemeyi ve -birleştirme grubu olaylarını kapsar. Gönderim, tetikleme ve birleştirme grubu, tam seçimi -çalıştırır. Fork'lar ve birleştirme grupları, normalde self-hosted çalıştırıcıları seçen işler -için hosted çalıştırıcıları kullanır; kullanıma sunmadan önce yeterli hosted kapasitesi -doğrulanmalıdır. - -Her JSON makbuzu, checkout yapılan SHA'yı, iş akışı çalıştırmasını ve denemesini tanımlar. -CLI, checkout/olay SHA uyuşmazlığını reddeder. İş akışı testleri, politika üyeliğini karar -işinin `needs` listesine bağlar; böylece yeni veya kaldırılmış bir hat sessizce ortadan -kaybolamaz. Makbuzlar kendi iş akışlarını kapsar; yayınlamayı, dağıtımı veya mevcut bir -tavsiye niteliğindeki tarayıcının iç işleyişini kapsamaz. Her iki kontrol adını dal -kurallarında etkinleştirmek ayrı bir idari değişikliktir; bu işlerin eklenmesi tek başına -bir dalı korumaz. +birleştirme grubu olaylarını kapsar. Gönderim, tetikleme ve birleştirme grubu tam seçimi +çalıştırır. Fork'lar ve birleştirme grupları, aksi durumda kendi kendine barındırılan +çalıştırıcıları seçecek işler için barındırılan çalıştırıcıları kullanır; kullanıma +almadan önce yeterli barındırılan kapasite doğrulanmalıdır. + +Her JSON makbuzu, kullanıma alınan SHA'yı, iş akışı çalıştırmasını ve denemeyi tanımlar. +CLI, kullanıma alınan SHA ile olay SHA'sı arasındaki uyuşmazlığı reddeder. İş akışı +testleri, politika üyeliğini sonuç işinin `needs` listesine bağlar; böylece yeni veya +kaldırılmış bir hat sessizce ortadan kaybolamaz. Makbuzlar kendi iş akışlarını kapsar; +yayınlamayı, dağıtımı veya mevcut bir tavsiye tarayıcısının iç işleyişini kapsamaz. +Dal kurallarında her iki kontrol adını etkinleştirmek ayrı bir idari değişikliktir; +bu işlerin eklenmesi tek başına bir dalı korumaz. ### Statik tarama envanteri -Sürümlendirilmiş npm takma adı envanteri ve statik tarama üyeliği +Sürümlendirilmiş npm alias envanteri ve statik tarama üyeliği `config/quality/gate-manifest.json` içinde bulunur. Betik adlarını ve tam komutları -`package.json` ile karşılaştırarak doğrulamak için `npm run check:gate-manifest` komutunu -çalıştırın; eklemeler, kaldırmalar ve komut sapmaları hem yerel kancanın hem de CI'daki -değişiklik sınıflandırma işlerinin başarısız olmasına neden olur. Bir takma ad; iş akışı işi, -matris örneği veya test vakası değildir: bu sayılar birbirinin yerine kullanılabilirmiş gibi -sunulmamalıdır. - -Seçilen takma adları çalıştırmadan incelemek için `npm run quality:scan -- --list` veya -`npm run quality:scan:fast -- --list` kullanın. Çalıştırıcı npm giriş noktasını çağırır; -dolayısıyla çalışma zamanı (yapılandırılmışsa Bun dâhil) korunur. Manifest, bu profillerin -dışındaki takma adları ayrı olarak çağrılanlar şeklinde kaydeder ve salt okunur tarama +`package.json` ile karşılaştırarak doğrulamak için `npm run check:gate-manifest` +komutunu çalıştırın; eklemeler, kaldırmalar ve komut sapmaları hem yerel hook'un hem +de CI'daki değişiklik sınıflandırma işlerinin başarısız olmasına neden olur. +Bir alias; iş akışı işi, matris örneği veya test vakası değildir: bu sayılar birbirinin +yerine kullanılabilirmiş gibi sunulmamalıdır. + +Seçilen alias'ları çalıştırmadan incelemek için +`npm run quality:scan -- --list` veya `npm run quality:scan:fast -- --list` +komutunu kullanın. Çalıştırıcı npm giriş noktasını çağırır; böylece çalışma zamanı +(yapılandırıldığı yerlerde Bun dâhil) korunur. Manifest, bu profillerin dışındaki +alias'ları ayrı olarak çağrılanlar şeklinde kaydeder ve salt okunur tarama profillerinde bakım komutları yasaktır. -Bu profiller yalnızca statik taramayı kapsar. Ürün testlerini, kapsamı, paketlemeyi, harici -kontrolleri veya bir adayın tam sürüm kabulünü doğrulamazlar. İş akışı kabulü, bağlantılı -`config/quality/admission-policy.json` ve `scripts/quality/admission-verdict.mjs` dosyalarını -kullanır. Sürüm gözlemcisi profilleri ayrı kalır; bunların uygulanabilir kontrollerini ve -makbuzlarını bağımsız olarak inceleyin. Aşağıdaki metinsel envanter bir referanstır; bir -geçidin gerçekten çalıştığının kanıtı değildir. +Bu profiller yalnızca statik taramayı kapsar. Ürün testlerini, kapsamı, paketlemeyi, +harici kontrolleri veya bir adayın tam sürüm kabulünü onaylamazlar. İş akışı kabulü, +bağlantılı `config/quality/admission-policy.json` ile +`scripts/quality/admission-verdict.mjs` dosyalarını kullanır. Release-observer +profilleri ayrı kalır; bunların uygulanabilir kontrollerini ve makbuzlarını bağımsız +olarak inceleyin. Aşağıdaki açıklamalı envanter bir referanstır; bir geçidin gerçekten +çalıştığının kanıtı değildir. -Betikler `scripts/check/` (politika geçitleri) ve `scripts/quality/` (ratchet motoru) altında -bulunur. CI için doğruluğun kaynağı `.github/workflows/ci.yml` dosyasıdır. +Betikler `scripts/check/` (politika geçitleri) ve `scripts/quality/` (ratchet motoru) +altında bulunur. CI için doğruluğun kaynağı `.github/workflows/ci.yml` dosyasıdır. ### Sürüm PR'ı hızlı yolu (`quality.yml`) -`.github/workflows/quality.yml`; main/release PR'larında, korumalı dal gönderimlerinde, -tetiklemelerde ve birleştirme gruplarında CI'ı tamamlar. PR'lar, yol filtreli hızlı kontroller -kullanır. Kalıcı olarak devre dışı bırakılmış yinelenen derleme kaldırılmıştır; gerçek -derleme/paketleme/önyükleme kontrolleri CI'da kalır. +`.github/workflows/quality.yml`; main/release PR'larında, korumalı dal +gönderimlerinde, tetiklemelerde ve birleştirme gruplarında CI'ı tamamlar. PR'lar, +yol filtreli hızlı kontrolleri kullanır. Kalıcı olarak devre dışı bırakılmış yinelenen +derleme kaldırılmıştır; gerçek derleme/paketleme/önyükleme kontrolleri CI'da kalır. | İş | Kapsam | Engelleyici | | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | | `Docs Gates (fast-path)` | Dokümantasyon/kod PR'ları; API dokümantasyonu referansları ve tüm dokümantasyon | Evet | -| `Fast Quality Gates` | Kod PR'ları; statik kontroller, tür denetimi, dashboard tür denetimi, etkilenen birim testleri | Evet | +| `Fast Quality Gates` | Kod PR'ları; statik kontroller, tür kontrolü, pano tür kontrolü, etkilenen birim testleri | Evet | | `Forgotten sibling tests` | Kod PR'ları; statik tüketicilere ve aday kardeş testlere kadar izlenen değiştirilmiş modüller; barrel ve dinamik içe aktarma yolları, referans verilen izin listesi istisnalarıyla birlikte tavsiye niteliğinde tanılar olarak raporlanır | **Tavsiye niteliğinde** | | `Vitest (fast-path)` | Kod PR'ları; hızlı vitest paketi | Evet | | `Unit Tests fast-path` | Kod PR'ları; 4 parçalı birim testi paketi | Evet | -| `No new ESLint warnings` | Kod PR'ları; bastırmaları dikkate alan lint koruması | Evet, fork'lar dâhil | -| `Merge integrity (changelog + generated skills)` | Taslak olmayan PR'lar; değişiklik günlüğü ve oluşturulan becerilerin eşitlemesi | Evet, fork'lar dâhil | +| `No new ESLint warnings` | Kod PR'ları; bastırma farkındalıklı lint koruması | Evet, fork'lar dâhil | +| `Merge integrity (changelog + generated skills)` | Taslak olmayan PR'lar; değişiklik günlüğü ve oluşturulan skill'lerin senkronizasyonu | Evet, fork'lar dâhil | #### Unutulan kardeş testler raporu `npm run check:forgotten-sibling-tests`, test etki haritasının arkasındaki içe aktarma çözümleyicisini yeniden kullanır. Değiştirilen her üretim modülü için, aday test -pull-request farkında bulunmadığında deterministik -`değiştirilen modül/sembol -> statik tüketici -> aday kardeş test` zincirlerini raporlar. -Markdown özeti ve JSON sonucu, herhangi bir engelleyici kullanıma sunma işleminden önce -kalibrasyon amacıyla `forgotten-sibling-tests` iş akışı yapıtı olarak saklanır. +pull request farkında bulunmadığında belirlenebilir +`değiştirilen modül/sembol -> statik tüketici -> aday kardeş test` zincirlerini +raporlar. Markdown özeti ve JSON sonucu, engelleyici nitelikte herhangi bir kullanıma +alma öncesindeki kalibrasyon için `forgotten-sibling-tests` iş akışı artifact'i olarak +saklanır. Barrel yeniden dışa aktarımları ve dinamik içe aktarımlar yalnızca çözümleme tanılamalarıdır; hiçbir zaman engelleyici bir bulgu oluşturmazlar. İncelenmiş istisnalar -`config/quality/forgotten-sibling-allowlist.json` içinde bulunur. Her girdi, tüketiciyi ve aday -testi belirtmeli, belirli bir gerekçe sunmalı ve bir GitHub issue'suna veya pull request'e bağlantı vermelidir. Hatalı biçimlendirilmiş girdiler -kapalı şekilde başarısız olur. İstisnalar, silinmiş bir aday testi veya `.skip`/`.todo` ekleyen bir diff'i baskılayamaz; +`config/quality/forgotten-sibling-allowlist.json` içinde bulunur. Her girdi, tüketici ve aday +testi adlandırmalı, belirli bir gerekçe sunmalı ve bir GitHub issue veya pull request bağlantısı içermelidir. Hatalı biçimlendirilmiş girdiler +kapalı durumda başarısız olur. İstisnalar, silinmiş bir aday testi veya `.skip`/`.todo` ekleyen bir diff'i bastıramaz; assertion zayıflatma ve diğer maskeleme işlemleri, bağımsız olarak engelleyici olan `check:test-masking` geçidinin sorumluluğunda kalır. ### İş: `lint` -`main` dalına yönelik her PR'da çalışır. Başarısızlık durumunda birleştirmeyi engeller. - -| Betik (`npm run ...`) | Doğruladığı | Engelleyici | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -| `check:node-runtime` | Node.js sürümünün desteklenen aralıkta olması | Evet | -| `check:cycles` | Döngüsel içe aktarımlar — tüm `src/` + `open-sse/` modülleri | Evet | -| `check:route-validation:t06` | Tüm rotalarda Zod şemalarının bulunması (Tier 6 politikası) | Evet | -| `check:any-budget:t11` | `@ts-expect-error // any` sayısının bütçeyi aşmaması (Tier 11 catraca) | Evet | -| `check:provider-consistency` | `providers.ts` içindeki her sağlayıcının `providerRegistry.ts` içinde eşleşen bir girdisi vardır (ve izin listesi dahilinde bunun tersi de geçerlidir) | Evet | -| `check:model-lifecycle` | Elle sürdürülen üç yönlendirme tablosu, depoya kaydedilmiş yaşam döngüsü anlık görüntüsüyle (#11503) tutarlı kalır: `FITNESS_TABLE` (`taskFitness.ts`), `REGISTRY` tarafından yönlendirilebilen kullanımdan kaldırılmış hiçbir kimliğe puan vermez; her `BUILT_IN_ALIASES` hedefi `REGISTRY` içinde bulunur ve kullanımdan kaldırılmış kimliklerin anlık görüntüsünde yer almaz; hâlâ `REGISTRY` içinde bulunan kullanımdan kaldırılmış her kimlik yönlendirilir veya `allowedRetiredInCatalog` içinde listelenir; ayrıca hiçbir `DEFAULT_DEGRADATION_MAP` kaynağı veya hedefi bu anlık görüntüde kullanımdan kaldırılmış olarak görünmez. Bu, bir modelin şu anda çalışan bir yukarı akış tarafından sunulduğunu kanıtlamaz. Çevrimdışı — elle `npm run quality:refresh-model-lifecycle` (ağ gerektirir; CI'a bağlı değildir) komutuyla yenilenen `config/quality/model-lifecycle.json` ile karşılaştırır. `allowedRetiredInCatalog`, yalnızca azalan yönde ilerleyen bir mandaldır: yalnızca bir takip kaydıyla birlikte girdi ekleyin. | Evet | -| `check:fetch-targets` | İstemci tarafındaki `src/` içinde bulunan her `fetch("/api/...")`, gerçek bir `route.ts` dosyasına çözümlenir | Evet | -| `check:deps` | Depodaki her `package.json` genelinde `npm install` ile kurulabilen tüm bağımlılıklar `dependency-allowlist.json` içindedir; sabitlenmemiş veya yazım benzerliğinden yararlanılarak kötü niyetle adlandırılmış yeni paketler işaretlenir | Evet | -| `audit:deps` | `npm audit` (kök + electron) — yüksek/kritik düzeyde güvenlik bildirimi yoktur (osv `check:vuln-ratchet` ile örtüşür; Rationalization Backlog bölümüne bakın) | Evet | -| `check:lockfile` | `package-lock.json` bütünlüğü — https kayıt defteri, bütünlük karmaları, ana makine geçersiz kılmaları yoktur | Evet | -| `check:licenses` | Üretim bağımlılıkları için SPDX lisans izin listesi | Evet | -| `check:tracked-artifacts` | Derleme artefaktları / commit edilmiş `node_modules` sembolik bağlantıları yok (husky pre-commit sırasında da çalışır; pre-push kasıtlı olarak hafiftir — #6716) | Evet | -| `check:ai-attribution` | PR commit'lerinde, başlığında veya gövdesinde AI/bot `Co-Authored-By` son bilgisi ya da AI tarafından oluşturma alt bilgisi yok — Kesin Kural #16 (`quality.yml` içindeki PR→`release/**` hızlı kontrol döngüsünde — olay yükünü okur, PR dışında işlem yapmaz — ve PR→`main` için `ci.yml` lint işlemindeki yalnızca PR'lara özel bir adımda; ayrıca husky `commit-msg` kancasında; insan ortak yazarlarına izin verilir; #14436) | -| `check:vitest-exclusions` | Her Vitest hariç tutma kaydı bir takip sorununu belirtir ve `config/quality/vitest-exclusions.json` içinde yer alır (#13204) | Evet | -| `check:file-size` | Hiçbir kaynak dosyası uzantı başına sınırı aşmaz (kademeli sıkılaştırma: büyük dosyalar `frozen` listesinde dondurulmuştur) | Evet | -| `check:error-helper` | Yürütücülerdeki/işleyicilerdeki hata yanıtları `buildErrorBody()` / `sanitizeErrorMessage()` kullanır (Kesin Kural #12) | Evet | -| `check:migration-numbering` | Migration SQL dosyaları boşluk veya yinelenen numara olmadan sıralı biçimde numaralandırılmıştır | Evet | -| `check:public-creds` | `publicCreds.ts` dışında değişmez OAuth `client_id`/`client_secret` veya Firebase Web anahtarı yoktur (Kesin Kural #11) | Evet | -| `check:db-rules` | `src/lib/db/` modülleri dışında ham SQL yoktur; `localDb.ts` üzerinden barrel import kullanılmaz (Kesin Kurallar #2/#5) | Evet | -| `check:known-symbols` | Dağıtım tablolarına kaydedilen sağlayıcı yürütücüleri, yönlendirme stratejileri ve dönüştürücüler diskteki dosyalarla eşleşir — sahipsiz veya bildirilmemiş sembol yoktur | Evet | -| `check:route-guard-membership` | Alt süreç başlatan her rota `isLocalOnlyPath()` tarafından sınıflandırılır (Kesin Kurallar #15/#17) | Evet | -| `check:test-discovery` | Depodaki her `*.test.ts` / `*.spec.ts` dosyası en az bir test çalıştırıcısı tarafından toplanır (kademeli sıkılaştırma: `test-discovery-baseline.json` içindeki sahipsiz dosyalar listesi yalnızca küçülebilir) | Evet | -| `check:agent-skills-sync` | Oluşturulan agent-skills yapıtları kaynak kataloglarıyla eşleşir (sapma yok) | -| `check:provider-asset-provenance` | Sağlayıcı logoları/varlıkları kayıtlı bir kaynak bilgisi girdisi taşır | -| `lint:json` | JSON yapılandırma dosyaları ayrıştırılır ve depo lint kurallarını karşılar | -| `typecheck:core` | Hatasız TypeScript derlemesi (yalnızca bilgilendirme amaçlı uyarılar) | Evet | -| `typecheck:noimplicit:core` | Katı `noImplicitAny` — ileriye dönük; önceden mevcut birçok çağrı noktasının hâlâ ek açıklamalara ihtiyacı var | **Bilgilendirme amaçlı** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` ile sınırlandırılmış `tsc` (#7033) — `typecheck:core`'un özenle seçilmiş 27 dosyalık izin listesi hiçbir dashboard TSX dosyasını içermiyor ve `next build` de bunların tür denetimini hiçbir zaman yapmıyor (`next.config.mjs`, `ignoreBuildErrors: true` olarak ayarlıyor); dolayısıyla buradaki sahipsiz tanımlayıcı regresyonları (#6625/#6909) CI tarafından görülemiyordu. Sabitlenmiş dosya başına/TS kodu başına hata sayısı temel çizgisiyle (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ile aynı eskime denetimi modeli) karşılaştırma yapar — yalnızca temel çizgideki sayıyı aşan YENİ hatalar geçidin başarısız olmasına neden olur; önceden mevcut bir hata düzeltildiğinde `--update` ile temel çizgiyi aşağı çekin. | Evet | +`main` dalına gönderilen her PR'da çalışır. Başarısızlık durumunda birleştirmeyi engeller. + +| Betik (`npm run ...`) | Doğruladığı | Engelleyici | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| `check:node-runtime` | Node.js sürümünün desteklenen aralıkta olduğunu | Evet | +| `check:cycles` | `src/` + `open-sse/` genelindeki döngüsel içe aktarımlar (AST tabanlı, tsconfig `paths` çözümlenir). Temel sürüm tavsiye niteliğindedir ve döngüleri listeler. `check:cycles:ratchet` (CI'ın çalıştırdığı), sayı `quality-baseline.json` içindeki `metrics.cycles` üst sınırını aştığında engeller — şu anda 14, `direction: down`; dolayısıyla yalnızca düşebilir (#15159 G-01/G-02) | Evet (ratchet) | +| `check:route-validation:t06` | Tüm route'larda Zod şemalarının bulunduğunu (Tier 6 politikası) | Evet | +| `check:any-budget:t11` | `@ts-expect-error // any` sayısının bütçeyi aşmadığını (Tier 11 catraca) | Evet | +| `check:provider-consistency` | `providers.ts` içindeki her sağlayıcının `providerRegistry.ts` içinde eşleşen bir girdisi vardır (ve izin verilenler listesi kapsamında bunun tersi de geçerlidir) | Evet | +| `check:model-lifecycle` | Elle sürdürülen üç yönlendirme tablosu, depoya kaydedilmiş yaşam döngüsü anlık görüntüsüyle (#11503) tutarlı kalır: `FITNESS_TABLE` (`taskFitness.ts`), `REGISTRY` tarafından yönlendirilebilen kullanımdan kaldırılmış hiçbir kimliğe puan vermez; her `BUILT_IN_ALIASES` hedefi `REGISTRY` içinde bulunur ve kullanımdan kaldırılmış kimliklerin anlık görüntüsünde bulunmaz; `REGISTRY` içinde hâlâ bulunan kullanımdan kaldırılmış her kimlik başka bir yere yönlendirilir veya `allowedRetiredInCatalog` içinde listelenir; ayrıca hiçbir `DEFAULT_DEGRADATION_MAP` kaynağı veya hedefi bu anlık görüntüde kullanımdan kaldırılmış olarak görünmez. Bu, bir modelin şu anda canlı bir yukarı akış tarafından sunulduğunu kanıtlamaz. Çevrimdışı — elle `npm run quality:refresh-model-lifecycle` (ağ erişimi gerektirir; CI'a bağlı değildir) kullanılarak yenilenen `config/quality/model-lifecycle.json` ile karşılaştırır. `allowedRetiredInCatalog`, kademeli azaltma mandalıdır: yalnızca bir takip kaydıyla birlikte girdi ekleyin. | Evet | +| `check:fetch-targets` | İstemci tarafındaki `src/` içinde yer alan her `fetch("/api/...")`, gerçek bir `route.ts` dosyasına çözümlenir | Evet | +| `check:deps` | Depodaki her `package.json` genelinde `npm install` ile kurulabilen tüm bağımlılıklar `dependency-allowlist.json` içindedir; yeni sabitlenmemiş veya yazım hatasıyla taklit edilmiş paketler işaretlenir | Evet | +| `audit:deps` | `npm audit` (kök + electron) — yüksek/kritik önem derecesinde güvenlik bildirimi yoktur (osv `check:vuln-ratchet` ile örtüşür; Rationalization Backlog'a bakın) | Evet | +| `check:lockfile` | `package-lock.json` bütünlüğü — https kayıt deposu, bütünlük karmaları, ana makine geçersiz kılmaları yok | Evet | +| `check:licenses` | Üretim bağımlılıkları için SPDX lisans izin listesi | Evet | +| `check:tracked-artifacts` | Derleme çıktıları / commit'lenmiş `node_modules` sembolik bağlantıları yok (husky pre-commit sırasında da çalışır; pre-push kasıtlı olarak hafiftir — #6716) | Evet | +| `check:ai-attribution` | PR commit'lerinde, başlığında veya gövdesinde AI/bot `Co-Authored-By` son bilgisi ya da AI üretimi alt bilgisi yok — Kesin Kural #16 (`release/**` hedefli PR'ler için `quality.yml` hızlı kontrol döngüsünde — olay yükünü okur, PR dışında işlem yapmaz — ve `main` hedefli PR'ler için `ci.yml` lint işlemindeki yalnızca PR'ye özel bir adımda; ayrıca husky `commit-msg` kancasında; insan ortak yazarlarına izin verilir; #14436) | +| `check:vitest-exclusions` | Her Vitest hariç tutma kaydı bir takip sorunu belirtir ve `config/quality/vitest-exclusions.json` içinde yer alır (#13204) | Evet | +| `check:file-size` | Hiçbir kaynak dosyası, uzantı başına belirlenen sınırı aşmaz (mandal: büyük dosyalar `frozen` listesinde dondurulur) | Evet | +| `check:error-helper` | Yürütücülerdeki/işleyicilerdeki hata yanıtları `buildErrorBody()` / `sanitizeErrorMessage()` kullanır (Kesin Kural #12) | Evet | +| `check:migration-numbering` | Migration SQL dosyaları boşluk veya yinelenen numara olmadan sıralı şekilde numaralandırılmıştır | Evet | +| `check:public-creds` | `publicCreds.ts` dışında değişmez OAuth `client_id`/`client_secret` veya Firebase Web anahtarları yoktur (Kesin Kural #11) | Evet | +| `check:db-rules` | `src/lib/db/` modülleri dışında ham SQL yoktur; `localDb.ts` dosyasından barrel import kullanılmaz (Kesin Kurallar #2/#5) | Evet | +| `check:known-symbols` | Dağıtım tablolarına kaydedilen sağlayıcı yürütücüleri, yönlendirme stratejileri ve dönüştürücüler diskteki dosyalarla eşleşir — sahipsiz veya bildirilmemiş sembol yoktur | Evet | +| `check:route-guard-membership` | Alt süreç başlatan her rota `isLocalOnlyPath()` tarafından sınıflandırılır (Kesin Kurallar #15/#17) | Evet | +| `check:test-discovery` | Depodaki her `*.test.ts` / `*.spec.ts` dosyası en az bir test çalıştırıcısı tarafından toplanır (mandal: `test-discovery-baseline.json` içindeki sahipsizler listesi yalnızca küçülebilir) | Evet | +| `check:agent-skills-sync` | Oluşturulan agent-skills yapıtları kaynak kataloglarıyla eşleşir (sapma yok) | +| `check:provider-asset-provenance` | Sağlayıcı logoları/varlıkları kayıtlı bir kaynak bilgisi girdisi taşır | +| `lint:json` | JSON yapılandırma dosyaları ayrıştırılır ve depo lint kurallarını karşılar | +| `typecheck:core` | Hatasız TypeScript derlemesi (yalnızca bilgilendirme amaçlı uyarılar) | Evet | +| `typecheck:noimplicit:core` | Katı `noImplicitAny` — ileriye dönük; önceden var olan birçok çağrı noktasının hâlâ ek açıklamalara ihtiyacı var | **Bilgilendirme amaçlı** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` kapsamındaki `tsc` (#7033) — `typecheck:core`'un özenle seçilmiş 27 dosyalık izin listesi hiçbir dashboard TSX dosyasını içermiyor ve `next build` de bunların tür denetimini hiçbir zaman yapmıyor (`next.config.mjs`, `ignoreBuildErrors: true` olarak ayarlanmış), bu nedenle buradaki sahipsiz tanımlayıcı regresyonları (#6625/#6909) CI tarafından görülemiyordu. Sabitlenmiş dosya başına/TS kodu başına hata sayısı temel çizgisiyle (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` ile aynı eskime denetimi kalıbı) karşılaştırma yapılır — yalnızca temel çizgideki sayıyı aşan YENİ hatalar geçidi başarısız kılar; önceden var olan bir hata düzeltildiğinde `--update` ile temel çizgiyi aşağı çekin. | Evet | ### İş: `quality-gate` -`test-coverage` sonrasında çalışır. Başarısız olması durumunda birleştirmeyi engeller. +`test-coverage` sonrasında çalışır. Başarısızlık durumunda birleştirmeyi engeller. -| Betik | Doğruladığı | Engelleyici | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | -| `quality:collect` | `quality-metrics.json` dosyasını oluşturur (ESLint uyarı sayısı, birleştirilmiş parça raporundaki kapsam) | Evet (mandalın ön koşulu) | -| `quality:ratchet` | `quality-baseline.json` içindeki hiçbir metrik gerilememiştir (ESLint uyarıları ≤ temel değer; kapsam ≥ temel değer) | Evet | -| `check:duplication` | Kod yinelemesi (jscpd@4), `quality-baseline.json` içindeki temel değeri aşmaz | Evet | -| `check:complexity` | Dosya düzeyindeki döngüsel karmaşıklık üst sınırı aşmaz (temel ESLint `complexity` + `max-lines-per-function`) | Evet | -| `check:cognitive-complexity` | Bilişsel karmaşıklık mandalı (`eslint-plugin-sonarjs`) — ayrı ESLint geçişi; CI, her ikisini tek bir `check:complexity-ratchets` adımı olarak birleştirip çalıştırır | Evet | -| `check:dead-code` | Kullanılmayan dışa aktarımlar / dosyalar mandalı (knip), temel değere kıyasla gerilemez | Evet | -| `check:compression-budget` | Sıkıştırma karşılaştırma testi bütçesi — motor başına token tasarrufu alt sınırları gerilememelidir | Evet | -| `check:type-coverage` | Tür atanmış kod yüzdesi mandalı (`type-coverage`) gerilemez; `typecheck:noimplicit:core` kontrolünü büyük ölçüde kapsar | Evet | -| `check:codeql-ratchet` | Açık CodeQL uyarılarının sayısı gerilemez (`gh api` aracılığıyla okunur; token olmadan sorunsuzca atlanır) — yenileme sıklığı ve manuel tetikleme için aşağıdaki "CodeQL mandalı" bölümüne bakın | Evet | +| Betik | Doğruladığı | Engelleyici | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `quality:collect` | `quality-metrics.json` üretir (ESLint uyarı sayısı, birleştirilmiş parça raporundan kapsam) | Evet (ratchet'ın ön koşulu) | +| `quality:ratchet` | `quality-baseline.json` içindeki her metriğin gerilemediğini doğrular (ESLint uyarıları ≤ temel değer; kapsam ≥ temel değer) | Evet | +| `check:duplication` | Kod tekrarının (jscpd@4) `quality-baseline.json` içindeki temel değeri aşmadığını doğrular | Evet | +| `check:complexity` | Dosya düzeyindeki döngüsel karmaşıklığın üst sınırı aşmadığını doğrular (temel ESLint `complexity` + `max-lines-per-function`) | Evet | +| `check:cognitive-complexity` | Bilişsel karmaşıklık ratchet'ı (`eslint-plugin-sonarjs`) — ayrı ESLint geçişi; CI, her ikisini tek bir `check:complexity-ratchets` adımı olarak birleştirip çalıştırır | Evet | +| `check:dead-code` | Kullanılmayan dışa aktarımlar / dosyalar ratchet'ının (knip) temel değere kıyasla gerilemediğini doğrular | Evet | +| `check:compression-budget` | Sıkıştırma karşılaştırma testi bütçesi — motor başına token tasarrufu alt sınırlarının gerilemediğini doğrular | Evet | +| `check:type-coverage` | Türlendirilmiş yüzde ratchet'ının (`type-coverage`) gerilemediğini doğrular; büyük ölçüde `typecheck:noimplicit:core` kapsamını içerir | Evet | +| `check:codeql-ratchet` | Açık CodeQL uyarılarının sayısının artmadığını doğrular (`gh api` üzerinden okur; token olmadan sorunsuzca atlar) — yenileme sıklığı ve manuel tetikleme için aşağıdaki "CodeQL ratchet" bölümüne bakın | Evet | ### İş: `quality-extended` -İşin tamamı tavsiye niteliğindedir (`continue-on-error: true`). npm tabanlı mandallar -gerçek anlamda çalışır; harici tarayıcılar `gh release download` aracılığıyla kurulur ve -ikili dosya hâlâ mevcut değilse kendilerini atlar (çıkış kodu 0). +İşin tamamı bilgilendirme amaçlıdır (`continue-on-error: true`). npm tabanlı ratchet'lar +gerçekten çalışır; harici tarayıcılar `gh release download` aracılığıyla yüklenir ve bir +ikili dosya hâlâ yoksa kendilerini atlar (çıkış kodu 0). -| Betik | Doğruladığı | Engelleyici | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | -| `check:circular-deps` | Döngüsel bağımlılık yoktur (dpdm) | **Tavsiye niteliğinde** | -| `check:bundle-size` | Paket boyutu üst sınırı aşmaz | **Tavsiye niteliğinde** | -| `check:secrets` | Gizli bilgi taraması (gitleaks) — ikili dosya yoksa atlanır | **Tavsiye niteliğinde** | -| `check:vuln-ratchet` | Bağımlılık güvenlik açıkları (osv-scanner) gerilemez — ikili dosya yoksa atlanır | **Tavsiye niteliğinde** | -| `check:workflows` | İş akışı lint denetimi (actionlint + zizmor); eksik/bozuk tarayıcılar, geçersiz raporlar veya eksik mandal temel değeri INCOMPLETE olarak başarısız olur. Geçerli bulgular, seçilen katı/tavsiye/mandal politikasını izler | Çalıştırılması zorunludur; zizmor mandalı CI'da engelleyicidir | -| `check:openapi-breaking` | Temel dala kıyasla genel API sözleşmesindeki (`openapi.yaml`) geriye dönük uyumsuz değişiklikler (oasdiff) — `openapiBreaking=N` çıktısını üretir; oasdiff yoksa veya temel belirtim çözümlenemiyorsa atlanır | **Tavsiye niteliğinde** | +| Betik | Doğruladığı | Engelleyici | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `check:circular-deps` | Döngüsel bağımlılık olmadığını doğrular (dpdm) | **Bilgilendirme amaçlı** | +| `check:bundle-size` | Paket boyutunun üst sınırı aşmadığını doğrular | **Bilgilendirme amaçlı** | +| `check:secrets` | Gizli bilgi taraması (gitleaks) — ikili dosya yoksa atlanır | **Bilgilendirme amaçlı** | +| `check:vuln-ratchet` | Bağımlılık güvenlik açıklarının (osv-scanner) artmadığını doğrular — ikili dosya yoksa atlanır | **Bilgilendirme amaçlı** | +| `check:workflows` | İş akışı lint denetimi (actionlint + zizmor); eksik/bozuk tarayıcılar, geçersiz raporlar veya eksik ratchet temel değeri INCOMPLETE olarak başarısız olur. Geçerli bulgular seçilen katı/bilgilendirme/ratchet politikasını izler | Çalıştırılması zorunludur; zizmor ratchet'ı CI'da engelleyicidir | +| `check:openapi-breaking` | Genel API sözleşmesindeki (`openapi.yaml`) temel dala kıyasla geriye dönük uyumsuz değişiklikleri doğrular (oasdiff) — `openapiBreaking=N` üretir; oasdiff yoksa veya temel belirtim çözümlenemiyorsa atlanır | **Bilgilendirme amaçlı** | ### İş: `docs-sync-strict` -`main` dalına yönelik her PR'da çalışır. Başarısızlık durumunda birleştirmeyi engeller. - -| Betik | Doğruladığı | Engelleyici | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| `check:docs-all` | Aşağıdaki 6 alt geçidi sırayla çalıştıran meta geçit | Evet | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt sürüm tutarlılığı | Evet | -| ↳ `check:docs-counts` | Düzyazıdaki sayılar (sağlayıcı sayısı, geçiş sayısı vb.) gerçek sayıların mandallı aralığı içindedir | Evet | -| ↳ `check:env-doc-sync` | `.env.example` içindeki her ortam değişkeni bir dokümantasyon tablosunda belgelenmiştir ve bunun tersi de geçerlidir | Evet | -| ↳ `check:deprecated-versions` | Dokümantasyonda kullanımdan kaldırılmış sürüm dizeleri yoktur | Evet | -| ↳ `check:doc-links` | Dokümantasyondaki dahili markdown bağlantıları gerçek dosyalara çözümlenir (`[metin]`/`(yol)` biçimi) | Evet | -| ↳ `check:fabricated-docs` | Dokümantasyonda belirtilen rotalar, ortam değişkenleri, CLI komutları, kanca adları ve dosya yolları kod tabanında mevcuttur. `--strict` ile katı geçit; bayrak olmadan esnek hata. | Evet (CI'da `--strict` aracılığıyla) | -| `check:cli-i18n` | CLI komut dizeleri tüm i18n yerel ayar dosyalarında mevcuttur | Evet | -| `check:openapi-coverage` | OpenAPI belirtimi, gerçek rotaların en az mandallanmış alt sınırını kapsar | Evet | -| `check:openapi-security-tiers` | `openapi.yaml` içindeki güvenlik katmanı ek açıklamaları `routeGuard.ts` sınıflandırmalarıyla tutarlıdır | **Öneri niteliğinde** | -| `check:openapi-routes` | `openapi.yaml` içindeki her yol gerçek bir `route.ts` dosyasına çözümlenir (halüsinasyon önleme) | Evet | -| `check:docs-symbols` | `docs/**/*.md` içindeki her `/api/...` başvurusu gerçek bir `route.ts` dosyasına çözümlenir (halüsinasyon önleme) | Evet | -| `i18n translation drift` | i18n yerel ayar dosyalarındaki çevrilmemiş anahtarlar — yalnızca uyarı | **Öneri niteliğinde** | +`main` dalına gönderilen her PR'da çalışır. Başarısızlık durumunda birleştirmeyi engeller. + +| Betik | Doğruladığı | Engelleyici | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| `check:docs-all` | Aşağıdaki 6 alt geçidi sıralı olarak çalıştıran meta geçit | Evet | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt sürüm tutarlılığı | Evet | +| ↳ `check:docs-counts` | Düzyazıdaki sayılar (sağlayıcı sayısı, geçiş sayısı vb.) gerçek sayıların mandallı aralığı içindedir | Evet | +| ↳ `check:env-doc-sync` | `.env.example` içindeki her ortam değişkeni bir dokümantasyon tablosunda belgelenmiştir ve bunun tersi de geçerlidir | Evet | +| ↳ `check:deprecated-versions` | Dokümantasyonda kullanım dışı bırakılmış sürüm dizeleri yoktur | Evet | +| ↳ `check:doc-links` | Dokümantasyondaki dahili markdown bağlantıları gerçek dosyalara çözümlenir (`[metin]`/`(yol)` biçimi) | Evet | +| ↳ `check:fabricated-docs` | Dokümantasyonda belirtilen rotalar, ortam değişkenleri, CLI komutları, kanca adları ve dosya yolları kod tabanında mevcuttur. `--strict` ile katı geçit; bayrak olmadan hata yalnızca uyarı niteliğindedir. | Evet (CI'da `--strict` aracılığıyla) | +| `check:cli-i18n` | CLI komut dizeleri tüm i18n yerel ayar dosyalarında mevcuttur | Evet | +| `check:openapi-coverage` | OpenAPI belirtimi, gerçek rotalar için en az mandallı bir alt sınırı kapsar | Evet | +| `check:openapi-security-tiers` | `openapi.yaml` içindeki güvenlik katmanı ek açıklamaları `routeGuard.ts` sınıflandırmalarıyla tutarlıdır | **Tavsiye niteliğinde** | +| `check:openapi-routes` | `openapi.yaml` içindeki her yol gerçek bir `route.ts` dosyasına çözümlenir (halüsinasyon önleme) | Evet | +| `check:docs-symbols` | `docs/**/*.md` içindeki her `/api/...` referansı gerçek bir `route.ts` dosyasına çözümlenir (halüsinasyon önleme) | Evet | +| `i18n translation drift` | i18n yerel ayar dosyalarındaki çevrilmemiş anahtarlar — yalnızca uyarı | **Tavsiye niteliğinde** | ### İş: `i18n-ui-coverage` -| Betik | Doğruladığı | Engelleyici | -| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `check-ui-keys-coverage` (satır içi) | UI i18n anahtar kapsamı ≥ %65'tir | Evet | -| `check-ui-value-drift` (satır içi) | Yeniden yazılmış bir İngilizce **değer**, geride eski çeviri bırakmaz | Evet | -| `check-new-key-coverage` (satır içi) | **Yeni** bir İngilizce anahtar her yerel ayarda çevrilmiştir — `__MISSING__:` işareti reddedilir | Evet | -| `check-translation-ratio` | Yerel ayar başına gerçek çeviri oranı (izin verilenler listesinin dışındaki İngilizceyle aynı / yer tutucu / eksik yapraklar) `config/quality/i18n-translation-baseline.json` + tolerans değerini aşmamalıdır | **Öneri niteliğinde** | +| Betik | Doğruladığı | Engelleyici | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `check-ui-keys-coverage` (satır içi) | UI i18n anahtar kapsamı ≥ %65'tir | Evet | +| `check-ui-value-drift` (satır içi) | Yeniden yazılmış bir İngilizce **değer**, geride güncelliğini yitirmiş çeviri bırakmaz | Evet | +| `check-new-key-coverage` (satır içi) | **Yeni** bir İngilizce anahtar her yerel ayarda çevrilmiştir — `__MISSING__:` işaretçisi reddedilir | Evet | +| `check-translation-ratio` | Yerel ayar başına gerçek çeviri oranı (izin verilenler listesinin dışındaki İngilizceyle aynı / yer tutucu / eksik yapraklar) `config/quality/i18n-translation-baseline.json` + tolerans değerini aşmamalıdır | **Tavsiye niteliğinde** | `fetch-depth: 0` gerektirir — değer sapması geçidi, `en.json` dosyasını birleştirme tabanıyla karşılaştırır. -#### `check-ui-value-drift` — eski çeviri geçidi +#### `check-ui-value-drift` — güncelliğini yitirmiş çeviri geçidi -Diğer geçitlerin yapısal olarak göremediği tek i18n gerilemesini yakalar: İngilizce bir değer -yeniden yazılır ancak _önceki_ İngilizceden türetilen çeviriler geride kalır; böylece -İngilizce konuşmayan kullanıcılar kendinden emin bir dille yazılmış, artık yanlış olan metni okumaya devam eder. +Diğer geçitlerin yapısal olarak göremediği i18n gerilemesini yakalar: İngilizce bir değer +yeniden yazılırken _önceki_ İngilizce metinden türetilen çeviriler geride kalır; dolayısıyla +İngilizce dışındaki dilleri kullanan kullanıcılar kendinden emin bir dille yazılmış, artık yanlış olan metni okumaya devam eder. Bu gerçekten yayımlandı. Antigravity oturum açma yardımcısı eklendiğinde (#5203) -`oauthModal.googleOAuthWarning` yeniden yazıldı; **43 yerel ayarın 39'u**, operatörlere "tam -URL'yi kopyalayıp aşağıya yapıştırmalarını" söyleyen metni korudu — bu sağlayıcı için +`oauthModal.googleOAuthWarning` yeniden yazıldı; **43 yerel ayarın 39'u**, operatörlere +"tam URL'yi kopyalayıp aşağıya yapıştırmalarını" söyleyen metni korudu — bu sağlayıcıda tamamlanması mümkün olmayan bir akış. Şu nedenlerle #8463'e kadar fark edilmedi: -- `sync-ui-keys` yalnızca **bulunmayan** anahtarları geriye dönük doldurur, **eskimiş** olanları asla doldurmaz; -- `check-ui-keys-coverage` anahtar _varlığını_ sayar, dolayısıyla eski bir çeviri kapsanmış olarak puanlanır; +- `sync-ui-keys` yalnızca **bulunmayan** anahtarları tamamlar, **güncelliğini yitirmiş** olanları hiçbir zaman güncellemez; +- `check-ui-keys-coverage` anahtar _varlığını_ sayar; dolayısıyla güncelliğini yitirmiş bir çeviri, kapsam dâhilinde olarak değerlendirilir; - `check-translation-drift`, `docs/i18n//**.md` dokümantasyon yansımalarını izler — - `src/i18n/messages/*.json` dosyalarını asla okumaz. 2026-09 yeniden eşitlemesinden bu yana - `docs-sync-strict` işinde engelleyicidir: temel bir belgeyi düzenleyin → `npm run i18n:run -- --files=` (bölüm düzeyinde, düşük maliyetli). + `src/i18n/messages/*.json` dosyalarını hiçbir zaman okumaz. 2026-09 yeniden senkronizasyonundan beri `docs-sync-strict` işinde engelleyicidir: temel bir dokümanı düzenleyin → `npm run i18n:run -- --files=` (bölüm düzeyinde, düşük maliyetli). -**Fark duyarlı, temel sürüm destekli değil.** Birleştirme tabanındaki `en.json` ile -çalışma ağacındaki sürümü karşılaştırır; İngilizce değeri değişen her anahtar için hâlâ -dokunulmamış çeviriyi içeren yerel ayar güncelliğini yitirmiş sayılır. Bu, kasıtlı olarak -**önceden var olan borcu dondurur** — bir fark, uzun süredir mevcut olan bir çevirinin hangi -eski İngilizce metinden geldiğini ortaya çıkaramaz; bu nedenle geçit yalnızca mevcut -değişikliğin dokunduğu öğeleri değerlendirir. Alternatif yöntem (anahtar başına karma temel -sürümü), mevcut en büyük temel sürümün 3 katı olan yaklaşık 600 KB boyutunda üretilmiş bir -dosya gerektirir ve her i18n PR'ında dosyanın değişmesine yol açardı. +**Fark duyarlı, temel durum destekli değil.** Birleştirme tabanındaki `en.json` dosyasını +çalışma ağacıyla karşılaştırır; İngilizce değeri değişen her anahtar için hâlâ +değiştirilmemiş çeviriyi tutan tüm yerel ayarlar güncelliğini yitirmiş sayılır. Bu yaklaşım, +önceden var olan borcu bilinçli olarak **dondurur** — bir fark, uzun süredir kullanılan bir +çevirinin hangi eski İngilizce metinden geldiğini gösteremez; bu nedenle geçit yalnızca +mevcut değişikliğin dokunduğu öğeleri değerlendirir. Alternatif yaklaşım (anahtar başına +karma taban çizgisi), mevcut en büyük taban çizgisinin 3 katı büyüklüğünde, yaklaşık 600 KB'lık +oluşturulmuş bir dosya gerektirir ve her i18n PR'ında gereksiz değişiklik üretirdi. -Bunu karşılamanın iki yolu vardır: +Bunu sağlamanın iki yolu vardır: -1. etkilenen çevirileri güncelleyin veya -2. bunları `__MISSING__:` olarak ayarlayın — çalışma zamanı daha sonra düzeltilmiş - İngilizce metni sunar (`src/i18n/request.ts::deepMergeFallback`, #7258) ve anahtar çeviri - kuyruğuna alınır. +1. etkilenen çevirileri güncellemek veya +2. bunları `__MISSING__:` olarak ayarlamak — çalışma zamanı daha sonra düzeltilmiş + İngilizce metni sunar (`src/i18n/request.ts::deepMergeFallback`, #7258) ve anahtarı çeviri + kuyruğuna ekler. Dizenin **anlamı** değiştiyse **anahtarı yeniden adlandırmayı** tercih edin: yeni bir anahtar, güncelliğini yitirmiş bir çeviriyi devralamaz. #8463'te kullanılan kalıp budur. @@ -241,48 +243,48 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` Temel katalog okunamadığında (temel ref olmadan sığ klon), `check-openapi-breaking` -davranışını yansıtacak şekilde `SKIP reason=base-unresolved` ile 0 koduyla çıkar. +davranışını yansıtarak `SKIP reason=base-unresolved` ile 0 çıkış kodu döndürür. ### İş: `i18n` Tam i18n doğrulama matrisi (yerel ayar başına bir iş). İşin tamamı tavsiye niteliğindedir. -| Betik | Doğruladığı | Engelleyici | -| ------------------------------- | ---------------------------------- | ------------------------------------------------------------ | -| `validate_translation.py quick` | Yerel ayar başına çeviri bütünlüğü | **Tavsiye niteliğinde** (tüm işte `continue-on-error: true`) | +| Betik | Doğruladığı | Engelleyici | +| ------------------------------- | ------------------------------------- | ------------------------------------------------------------ | +| `validate_translation.py quick` | Yerel ayar başına çeviri eksiksizliği | **Tavsiye niteliğinde** (tüm işte `continue-on-error: true`) | ### İş: `pr-test-policy` -Yalnızca pull request'lerde çalışır. +Yalnızca çekme isteklerinde çalışır. | Betik | Doğruladığı | Engelleyici | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` veya `bin/` içindeki üretim kodunu değiştiren PR'lar test eklemeli veya güncellemelidir (Katı Kural #8) | Evet | -| `check:test-masking` | Değiştirilen test dosyaları net assert sayısını azaltmaz veya `assert.ok(true)` totolojileri eklemez | Evet | -| `check:pr-evidence` | PR gövdesi, değişiklik için test/VPS kanıtına atıfta bulunur (PR metnini grep ile tarayarak Katı Kural #18'i mekanikleştirir — kırılgandır, bkz. Backlog) | Evet | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` veya `bin/` içindeki üretim kodunu değiştiren PR'lar testleri içermeli veya güncellemelidir (Katı Kural #8) | Evet | +| `check:test-masking` | Değiştirilen test dosyaları net doğrulama sayısını azaltmaz veya `assert.ok(true)` totolojileri eklemez | Evet | +| `check:pr-evidence` | PR açıklaması değişiklik için test/VPS kanıtlarına atıfta bulunur (PR metnini grep'leyerek Katı Kural #18'i otomatikleştirir — kırılgandır, bkz. Backlog) | Evet | ### İş: `test-vitest` -`build` sonrasında çalışır. Başarısız olduğunda birleştirmeyi engeller. +`build` işleminden sonra çalışır. Başarısız olduğunda birleştirmeyi engeller. -| Paket | Doğruladığı | Engelleyici | -| ---------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP sunucusu (110 araç), autoCombo, önbellek — vitest çalıştırıcısı | Evet | -| `test:vitest:ui` | UI bileşen testleri — vitest çalıştırıcısı | **Engelleyici** — önceden var olan hatalar `vitest.config.ts` içinde açıkça hariç tutulur; yeni hatalar işi başarısız kılar | +| Test paketi | Doğruladığı | Engelleyici | +| ---------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP sunucusu (110 araç), autoCombo, önbellek — vitest çalıştırıcısı | Evet | +| `test:vitest:ui` | UI bileşen testleri — vitest çalıştırıcısı | **Engelleyici** — önceden var olan başarısızlıklar `vitest.config.ts` içinde açıkça hariç tutulur; yeni başarısızlıklar işi başarısız kılar | ### Gecelik iş akışları (zamanlanmış, tavsiye niteliğinde) -Bunlar bir cron zamanlamasıyla (ve `workflow_dispatch` ile) çalışır; PR'larda hiçbir zaman +Bunlar cron zamanlamasında (ve `workflow_dispatch` ile) çalışır; PR'larda hiçbir zaman çalışmaz. Tümü tavsiye niteliğindedir. -| İş akışı | Doğruladığı | Engelleyici | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `nightly-property` | Rastgele seed ve yüksek çalıştırma sayısıyla fast-check özellik testleri | **Tavsiye niteliğinde** | -| `nightly-resilience` | Heap büyüme geçidi, kaos hata enjeksiyonu, k6 yük/dayanıklılık testi | **Tavsiye niteliğinde** | -| `nightly-llm-security` | promptfoo enjeksiyon koruması (engelleme modu) + garak probları (sağlayıcı sırrı olmadan atlanır) | **Tavsiye niteliğinde** | -| `nightly-schemathesis` | `docs/openapi.yaml` kullanarak çalışan bir OmniRoute'a karşı OpenAPI sözleşme fuzz testi (schemathesis) — spesifikasyon ihlallerini / işlenmemiş 500 hatalarını ortaya çıkarır (Aşama 8 B.4) | **Tavsiye niteliğinde** | -| `nightly-mutation` | Hızlı birim test hattı üzerinde Stryker mutasyon testi puanı — hayatta kalan mutantlar zayıf assert'leri ortaya çıkarır | **Tavsiye niteliğinde** | -| `nightly-compat` | Desteklenen `engines.node` aralıkları genelinde Node motoru uyumluluk matrisi | **Tavsiye niteliğinde** | +| İş akışı | Doğruladığı | Engelleyici | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `nightly-property` | Rastgele tohum ve yüksek çalıştırma sayısıyla fast-check özellik testleri | **Tavsiye niteliğinde** | +| `nightly-resilience` | Bellek yığını büyüme geçidi, kaos hata enjeksiyonu, k6 yük/dayanıklılık testleri | **Tavsiye niteliğinde** | +| `nightly-llm-security` | promptfoo enjeksiyon koruması (engelleme modu) + garak probları (sağlayıcı gizli değeri yoksa atlanır) | **Tavsiye niteliğinde** | +| `nightly-schemathesis` | `docs/openapi.yaml` kullanılarak çalışan bir OmniRoute'a karşı OpenAPI sözleşme fuzz testi (schemathesis) — spesifikasyon ihlallerini / işlenmeyen 500 hatalarını ortaya çıkarır (Fase 8 B.4) | **Tavsiye niteliğinde** | +| `nightly-mutation` | Hızlı birim hattında Stryker mutasyon testi puanı — hayatta kalan mutantlar zayıf doğrulamaları ortaya çıkarır | **Tavsiye niteliğinde** | +| `nightly-compat` | Desteklenen `engines.node` aralıkları genelinde Node motoru uyumluluk matrisi | **Tavsiye niteliğinde** | --- @@ -544,43 +546,43 @@ bağlantı hatasını günlüğe kaydeder ve oturumları hiçbir zaman bozmaz. --- -## Rasyonalizasyon İş Listesi (ROI incelemesi — Aşama 9 Dalga 3) +## Rasyonalizasyon İş Listesi (ROI incelemesi — Faz 9 Dalga 3) -Bu envanter, 2026-06-17 tarihinde `ci.yml` ile karşılaştırılarak mutabık hâle getirildi (önceki sürümde +Bu envanter, 2026-06-17 tarihinde `ci.yml` ile karşılaştırılarak uzlaştırıldı (önceki sürümde `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` yer almıyordu). Mutabık hâle getirilen kümenin ROI incelemesi, +`check:codeql-ratchet`, `check:pr-evidence` yer almıyordu). Uzlaştırılmış kümenin ROI incelemesi aşağıdaki rasyonalizasyon adaylarını belirledi. **Birleştirmeler mekanik CI -değişiklikleridir; etkinleştirme/kaldırma kararları ise operatöre bırakılmış politika kararlarıdır.** Aşağıdakilerin hiçbiri -henüz uygulanmadı. +değişiklikleridir; etkinleştirme/kaldırma işlemleri ise operatöre bırakılmış politika kararlarıdır.** Aşağıdakilerin hiçbiri +henüz uygulanmamıştır. **Yukarıda ayrıca belgelenmemiş olanlar** (tavsiye niteliğinde, düşük sinyalli): `docs-lint` işi (markdownlint + Vale, işin tamamında `continue-on-error`) ve bağımsız tarayıcı iş akışları `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0`, -`quality-baseline.json` içinde yer alıyor ancak `ci.yml` içinde engelleyici bir mandala bağlı değil — metrik -şu anda sahipsiz. +`quality-baseline.json` içinde yer almaktadır ancak `ci.yml` içinde engelleyici bir ratchet'a +bağlı değildir — metrik şu anda sahipsizdir. ### Birleştirme / tekilleştirme (mekanik, daha düşük risk) -Her aday, 2026-06-17 tarihinde canlı eşik durumuna göre doğrulandı (güven ama doğrula); -"bariz" görünen birkaç birleştirmenin aslında teknik borcu gizlediği ortaya çıktı ve bunlar **doğrudan uygulanabilecek temiz değişiklikler değildir**. +Her aday, 2026-06-17 tarihinde canlı gate durumuna karşı doğrulandı (güven ama doğrula); +“bariz” görünen bazı birleştirmelerin borç gizlediği ortaya çıktı ve bunlar **temiz birer doğrudan ikame değildir**. -- **`check:docs-sync` iki kez çalışıyor** — `lint` işi içinde bağımsız olarak, ayrıca `check:docs-all` (`docs-sync-strict`) ve husky pre-commit kancası içinde tekrar çalışıyor. ✅ **TAMAMLANDI** — bağımsız `lint` çağrısı kaldırıldı. -- **CVE taraması** — ❌ **Temiz bir birleştirme DEĞİL.** `audit:deps`, herhangi bir yüksek/kritik CVE durumunda kesin olarak başarısız olur; `check:vuln-ratchet` (osv) ise yalnızca referans çizgisine kıyasla bir _gerileme_ olduğunda başarısız olur (şu anda 1 MODERATE). Anlamları farklıdır — `audit:deps` kaldırılırsa mutlak yüksek/kritik eşiği kaybedilir. Her ikisini de koruyun. -- **Döngü tespiti** — ❌ **Temiz bir birleştirme DEĞİL.** `check:circular-deps` (dpdm) **91 döngü** bildiriyor (tavsiye niteliğinde olmasının nedeni bu); bunlar önce çözülmeden engelleyici hâle getirilemez ve yeşil durumda olan, özenle seçilmiş `check:cycles` denetiminden daha geniş bir kapsama sahiptir. `check:cycles` engelleyici olarak kalsın; 91 dpdm döngüsünün çözülmesi ayrı bir iş listesidir. -- **Karmaşıklık** — ✅ **TAMAMLANDI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): tek bir ESLint geçişi, ruleId bazında sayım yapar; böylece çevrimsel karmaşıklık+max-lines ve bilişsel referans çizgileri bağımsız kalır; ayrı `check:complexity` / `check:cognitive-complexity` komutları yerel `--update` kullanımı için korunur. -- **`/api` halüsinasyon önleme** — ✅ **TAMAMLANDI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` için tek bir FS envanteri; openapi-routes + docs-symbols hâlâ bağımsız raporlama yapar; ayrı komutlar yerel çalıştırmalar için korunur. -- **`check:node-runtime` 11 işte çalışıyor** — ⚠️ **düşük ROI.** Her biri ayrı bir çalıştırıcıdır ve denetim <1 sn sürer; ucuz bir iş başına korumanın kaybedilmesine karşılık toplam tasarruf ~10 sn'dir. Değişiklik zahmetine değmez. -- **CI lint üzerindeki `typecheck:noimplicit:core`** — ✅ **lint işinden kaldırıldı** (tavsiye niteliğinde `continue-on-error` idi); engelleyici tür yüzeyi `typecheck:core` + `check:type-coverage` ile sağlanır. Yerel betik korundu. +- **`check:docs-sync` iki kez çalışıyor** — `lint` işi içinde bağımsız olarak ve ayrıca `check:docs-all` (`docs-sync-strict`) ile husky pre-commit hook'u içinde. ✅ **TAMAMLANDI** — bağımsız `lint` çağrısı kaldırıldı. +- **CVE taraması** — ❌ **Temiz bir birleştirme DEĞİL.** `audit:deps`, herhangi bir yüksek/kritik CVE olduğunda kesin olarak başarısız olur; `check:vuln-ratchet` (osv) ise yalnızca baseline'a kıyasla bir _gerileme_ olduğunda başarısız olur (şu anda 1 MODERATE). Semantikleri farklıdır — `audit:deps` kaldırılırsa mutlak yüksek/kritik gate kaybedilir. İkisini de koruyun. +- **Döngü tespiti** — ✅ **TAMAMLANDI** (#15159 G-01/G-02). Buradaki eski metin, `check:cycles` için “yeşil, özenle seçilmiş” gate ifadesini kullanıyor ve `check:circular-deps` (dpdm) 91 döngü bildirdiği için bunun engelleyici olarak tutulmasını gerekçelendiriyordu. Bu yeşil durum **yalancı yeşildi**: `check:cycles` 5 alt dizini (450 dosya) tarıyor, yalnızca statik `import|export … from` ifadelerini eşleştiriyor ve tüm `@/` ile `@omniroute/open-sse/` belirticilerini eliyordu; dolayısıyla depoda baskın olan dinamik import + alias döngülerini göremiyordu. Düzeltildi: gate artık `src` + `open-sse` dizinlerini (5023 dosya) dolaşıyor, belirticileri TypeScript AST'sinden topluyor (böylece `import("…")` sayılırken tür konumundaki `typeof import("…")` sayılmıyor) ve tsconfig `paths` değerlerini çözümlüyor. 0 değil, **14** döngü buluyor. Önceden var olan 14 döngü bir gate PR'ında düzeltilemeyeceğinden `check:cycles` artık bir **ratchet**'tır (`--ratchet`, `quality-baseline.json` içinde tavan `metrics.cycles.value = 14`, `direction: down`) — herhangi bir _gerilemeyi_ engeller ve sayı yalnızca azalabilir. CI, `npm run check:cycles:ratchet` komutunu çalıştırır. Azaltma çalışmaları **A-01** ile birlikte yürütülür. `check:circular-deps` (dpdm), daha geniş kapsamlı ikinci görüş olarak tavsiye niteliğinde kalır. +- **Karmaşıklık** — ✅ **TAMAMLANDI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): tek bir ESLint taraması, cyclomatic+max-lines ve cognitive baseline'larının bağımsız kalması için ruleId bazında sayım yapar; ayrı `check:complexity` / `check:cognitive-complexity` komutları yerel `--update` işlemleri için korunur. +- **`/api` anti-halüsinasyon** — ✅ **TAMAMLANDI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` için tek bir FS envanteri; openapi-routes + docs-symbols hâlâ bağımsız olarak rapor verir; ayrı komutlar yerel çalıştırmalar için korunur. +- **`check:node-runtime` 11 işte çalışıyor** — ⚠️ **düşük ROI.** Her biri ayrı bir runner'dır ve kontrol <1 sn sürer; ucuz bir iş başına korumanın kaybedilmesine karşılık toplam tasarruf ~10 sn'dir. Bu değişikliğe değmez. +- **CI lint'te `typecheck:noimplicit:core`** — ✅ **lint işinden kaldırıldı** (tavsiye niteliğinde `continue-on-error` idi); engelleyici tür yüzeyi `typecheck:core` + `check:type-coverage` şeklindedir. Yerel script korundu. ### Etkinleştirme / karar verme (operatör politikası) -- `check:openapi-security-tiers` (tavsiye niteliğinde) — ❌ **Temiz şekilde etkinleştirilemez.** 0 ile çıkar ancak `LOCAL_ONLY_API_PREFIXES` altındaki birkaç `traffic-inspector` rotasında `x-loopback-only: true` ek açıklamasının bulunmadığı konusunda uyarır. Bunu zorunlu kılmak için önce bu ek açıklamaların `openapi.yaml` dosyasına eklenmesi gerekir. -- `typecheck:noimplicit:core` (tavsiye niteliğinde) — büyük ölçüde engelleyici `check:type-coverage` mandalı tarafından kapsanıyor. Bir mandala dönüştürün veya gereksiz ikinci `tsc` geçişini kaldırın. -- `test:vitest:ui` (artık **engelleyici**) — önceden var olan hatalar, `vitest.config.ts` içinde `// #8618` takip yorumlarıyla açıkça hariç tutulmuştur; yeni hatalar işin başarısız olmasına neden olur. -- `check:secrets` (gitleaks, belgelenmiş 3 yanlış pozitif değerinde dondurulmuş engelleyici mandal) — 0'a ulaşmak için bu 3 öğeyi izin verilenler listesine ekleyin veya denetimi tavsiye niteliğine indirin. GitHub'ın yerleşik gizli bilgi taraması + `check:public-creds` ile örtüşür. -- `check:pr-evidence` (engelleyici, PR gövdesindeki metni grep ile tarar) — yanlış pozitif riski yüksektir; kaldırılması Katı Kural #18'in uygulanmasını zayıflatır, dolayısıyla bu gerçek bir politika kararıdır. -- `semgrep` (tavsiye niteliğinde bağımsız iş akışı) — OWASP aileleri açısından CodeQL ile örtüşür; referans çizgisini bir mandala bağlayın veya kaldırın. +- `check:openapi-security-tiers` (tavsiye niteliğinde) — ❌ **Temiz biçimde etkinleştirilemez.** 0 koduyla çıkar ancak `LOCAL_ONLY_API_PREFIXES` altındaki bazı `traffic-inspector` route'larında `x-loopback-only: true` annotation'ının eksik olduğu konusunda uyarır. Bunu zorunlu kılmak için önce söz konusu annotation'ların `openapi.yaml` dosyasına eklenmesi gerekir. +- `typecheck:noimplicit:core` (tavsiye niteliğinde) — büyük ölçüde engelleyici `check:type-coverage` ratchet'ı tarafından kapsanır. Bir ratchet'a dönüştürün veya yinelenen ikinci `tsc` geçişini kaldırın. +- `test:vitest:ui` (artık **engelleyici**) — önceden var olan hatalar, `vitest.config.ts` içinde `// #8618` takip yorumlarıyla açıkça hariç tutulmuştur; yeni hatalar işi başarısız kılar. +- `check:secrets` (gitleaks, belgelenmiş 3 false-positive değerinde dondurulmuş engelleyici ratchet) — 0'a ulaşmak için bu 3 öğeyi allowlist'e ekleyin veya tavsiye niteliğine düşürün. GitHub'ın yerleşik secret-scanning özelliği + `check:public-creds` ile örtüşür. +- `check:pr-evidence` (engelleyici, PR gövdesindeki düzyazıda grep yapar) — false-positive riski yüksektir; kaldırılması Hard Rule #18 uygulamasını zayıflatır, dolayısıyla bu gerçek bir politika kararıdır. +- `semgrep` (tavsiye niteliğinde bağımsız iş akışı) — OWASP aileleri bakımından CodeQL ile örtüşür; baseline'ını bir ratchet'a bağlayın veya kaldırın. --- diff --git a/docs/i18n/tr/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/tr/docs/compression/COMPRESSION_GUIDE.md index d3e5f365d248..8d62b66cbef0 100644 --- a/docs/i18n/tr/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/tr/docs/compression/COMPRESSION_GUIDE.md @@ -457,9 +457,7 @@ yapmaz. Çözümlenen dilin (aşağıdaki Dil seçimi bölümüne bakın) bir ç `messages` içeren bir gövdede, `open-sse/services/compression/outputMode.ts` içindeki bir içerik atlama denetimi (`shouldBypassCavemanOutputMode()`) son üç mesajı kontrol eder ve bunlar güvenlik, geri döndürülemez eylem, açıklama isteme veya sıraya duyarlı anahtar -sözcüklerle eşleştiğinde tüm tur için stilleri atlar. Atlama işlemi, panodaki -**Auto-Clarity Bypass** anahtarı (`cavemanOutputMode.autoClarity`) hangi değere -ayarlanmışsa ona göre çalışır. +sözcüklerle eşleştiğinde tüm tur için stilleri atlar. Atlama işlemi, panodaki **Auto-Clarity Bypass** anahtarı (`cavemanOutputMode.autoClarity`) açık olduğu sürece çalışır; bu varsayılan durumdur. Anahtar kapalıyken seçilen stiller bu turlarda da uygulanır. Atlama denetimi turun geçmesine izin verdiğinde, aynı dosyadaki ve hiçbir zaman yeni bir `messages[0]` oluşturmayan `placeSystemInstruction()`, bloğu bulduğu ilk uygun konuma diff --git a/docs/i18n/tr/docs/reference/ENVIRONMENT.md b/docs/i18n/tr/docs/reference/ENVIRONMENT.md index 436a017fc14c..74306ed4bdcc 100644 --- a/docs/i18n/tr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/tr/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Gerekli Gizli Değerler -Bunlar ilk çalıştırmadan önce **mutlaka** ayarlanmalıdır. Bunlar olmadan uygulama ya başlatılmayı reddeder ya da güvenli olmayan varsayılanlarla çalışır. - -| Değişken | Gerekli | Varsayılan | Kaynak Dosya | Açıklama | -| ---------------------------- | ------------------- | --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Evet** | _(yok)_ | `src/lib/auth` | Tüm kontrol paneli oturum çerezlerini (JWT) imzalar/doğrular. `openssl rand -base64 48` ile oluşturun. | -| `API_KEY_SECRET` | **Evet** | _(yok)_ | `src/lib/db/apiKeys.ts` | SQLite'ta bekleyen API anahtarı değerleri için AES şifreleme anahtarıdır. `openssl rand -hex 32` ile oluşturun. | -| `INITIAL_PASSWORD` | **Evet** | `CHANGEME` | Önyükleme betiği | İlk yönetici kontrol paneli parolasını ayarlar (`.env.example` varsayılanıyla eşleşir — değişikliği zorunlu kılmak için açıkça güvensiz tutulmuştur). **İlk kullanımdan önce değiştirin.** Oturum açtıktan sonra Kontrol Paneli → Ayarlar → Güvenlik yoluyla değiştirin. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Evet** (üretimde) | _(ayarlanmamış)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Dahili Codex Responses WebSocket köprüsü için paylaşılan gizli değerdir. Electron/tarayıcı WS aktarıcısı ile OmniRoute arasındaki köprü isteklerinin kimliğini doğrular. ⚠️ **Üretimde GEREKLİDİR — ayarlanmadığında tüm WS köprüsü istekleri reddedilir.** `openssl rand -base64 32` ile oluşturun. | -| `OMNIROUTE_SW_BUILD_ID` | Hayır | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA kabuğu için önbellek geçersiz kılmaya yönelik açık service worker kimliği (#11779); çözümleme zincirinde ilk sıradadır. | -| `SOURCE_VERSION` | Hayır | _(ayarlanmamış)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zincirde ikinci sıradadır — PaaS derleyicileri (ör. Heroku tarzı) tarafından dağıtılan commit değerine ayarlanır. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Hayır | _(türetilmiş)_ | `src/shared/components/PwaRegister.tsx` | İstemcinin `/sw.js?v=…` kaydını yapmak için kullandığı, derleme zamanında belirlenen herkese açık değer; yukarıdaki iki değerden, ardından git SHA'dan türetilir. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Hayır (otomatik) | _(her başlatmada otomatik)_ | `src/server/authz/policies/management.ts` | Güvenilir eş IP damgasının OmniRoute'un kendi HTTP sunucusundan (`scripts/dev/peer-stamp.mjs`) geldiğini kanıtlayan süreç başına gizli değerdir. authz ara yazılımı, istek yerelliğine (LOCAL_ONLY rotalarının geri döngü/LAN geçit denetimi) yalnızca damga bu belirteci taşıdığında güvenir. Her başlatmada otomatik oluşturulur — ayarlamadan bırakın; yalnızca damgayı paylaşması gereken çok süreçli kurulumlarda sabitleyin. | -| `OMNIROUTE_TRUSTED_PROXIES` | Hayır | _(ayarlanmamış)_ | `scripts/dev/peer-stamp.mjs` | IP izin/ret listesinin `X-Forwarded-For` / `X-Real-IP` bilgilerine güvenebileceği ters proxy'lerin virgülle ayrılmış IP'leri veya CIDR aralıklarıdır; her zaman güvenilen geri döngü, özel ağ adresleri ve Cloudflare uç noktalarına ek olarak kullanılır. Yalnızca başka bir genel adresteki proxy için gereklidir; aksi takdirde filtre proxy'nin kendi adresini değerlendirir. | +**Evet** olarak işaretlenmiş ve ayarlanmamış gizli değerler ilk çalıştırmada otomatik olarak oluşturulur (`JWT_SECRET`, `API_KEY_SECRET`) ve veri dizinindeki `server.env` dosyasına kalıcı olarak yazılır — bu dizin geçiciyse bu değerleri açıkça ayarlayın. İstisna `OMNIROUTE_WS_BRIDGE_SECRET` değeridir: bu değer `server.env` dosyasına yazılmaz — sunucu giriş noktaları, yalnızca süreç içi köprüünün kullanabileceği, her açılışta geçerli olan kısa ömürlü bir değer oluşturur; bu nedenle harici köprü istemcilerinin bu değeri açıkça ayarlaması gerekir. + +| Değişken | Gerekli | Varsayılan | Kaynak Dosya | Açıklama | +| ---------------------------- | ------------------- | --------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Evet** | _(yok)_ | `src/lib/auth` | Tüm kontrol paneli oturum çerezlerini (JWT) imzalar/doğrular. `openssl rand -base64 48` ile oluşturun. | +| `API_KEY_SECRET` | **Evet** | _(yok)_ | `src/lib/db/apiKeys.ts` | SQLite'ta bekleyen API anahtarı değerleri için AES şifreleme anahtarıdır. `openssl rand -hex 32` ile oluşturun. | +| `INITIAL_PASSWORD` | Hayır | _(ayarlanmamış)_ | `src/lib/auth/managementPassword.ts` | Henüz kaydedilmiş bir parola yokken açılış sırasında pano parolasını ayarlar; bir parola kaydedildikten sonra yok sayılır. npm ve kaynak kurulumları `.env.example` dosyasını `.env` dosyasına kopyalar, dolayısıyla herkesin bildiği `CHANGEME` yer tutucusuyla başlarlar: ilk açılıştan önce değiştirin ya da ilk ziyarette parolayı onboarding sihirbazında oluşturmak için (ayarlanmamış değerin, yani Docker imgesinin varsayılanının yaptığı gibi) o `.env` dosyasında `INITIAL_PASSWORD=` (boş) olarak ayarlayın. Kaydedilmiş bir parolayı Kontrol Paneli → Ayarlar → Güvenlik yolundan değiştirin ya da `DATA_DIR` değerini sunucunun veri dizinine ayarlayarak `omniroute reset-password` komutunu (kaynak checkout'unda `node bin/reset-password.mjs`) çalıştırın. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Evet** (üretimde) | _(ayarlanmamış)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Dahili Codex Responses WebSocket köprüsü için paylaşılan gizli değerdir. Electron/tarayıcı WS aktarıcısı ile OmniRoute arasındaki köprü isteklerinin kimliğini doğrular. ⚠️ **Üretimde GEREKLİDİR — ayarlanmadığında tüm WS köprüsü istekleri reddedilir.** `openssl rand -base64 32` ile oluşturun. | +| `OMNIROUTE_SW_BUILD_ID` | Hayır | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA kabuğu için önbellek geçersiz kılmaya yönelik açık service worker kimliği (#11779); çözümleme zincirinde ilk sıradadır. | +| `SOURCE_VERSION` | Hayır | _(ayarlanmamış)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zincirde ikinci sıradadır — PaaS derleyicileri (ör. Heroku tarzı) tarafından dağıtılan commit değerine ayarlanır. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Hayır | _(türetilmiş)_ | `src/shared/components/PwaRegister.tsx` | İstemcinin `/sw.js?v=…` kaydını yapmak için kullandığı, derleme zamanında belirlenen herkese açık değer; yukarıdaki iki değerden, ardından git SHA'dan türetilir. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Hayır (otomatik) | _(her başlatmada otomatik)_ | `src/server/authz/policies/management.ts` | Güvenilir eş IP damgasının OmniRoute'un kendi HTTP sunucusundan (`scripts/dev/peer-stamp.mjs`) geldiğini kanıtlayan süreç başına gizli değerdir. authz ara yazılımı, istek yerelliğine (LOCAL_ONLY rotalarının geri döngü/LAN geçit denetimi) yalnızca damga bu belirteci taşıdığında güvenir. Her başlatmada otomatik oluşturulur — ayarlamadan bırakın; yalnızca damgayı paylaşması gereken çok süreçli kurulumlarda sabitleyin. | +| `OMNIROUTE_TRUSTED_PROXIES` | Hayır | _(ayarlanmamış)_ | `scripts/dev/peer-stamp.mjs` | IP izin/ret listesinin `X-Forwarded-For` / `X-Real-IP` bilgilerine güvenebileceği ters proxy'lerin virgülle ayrılmış IP'leri veya CIDR aralıklarıdır; her zaman güvenilen geri döngü, özel ağ adresleri ve Cloudflare uç noktalarına ek olarak kullanılır. Yalnızca başka bir genel adresteki proxy için gereklidir; aksi takdirde filtre proxy'nin kendi adresini değerlendirir. | ### Oluşturma Komutları @@ -218,38 +218,38 @@ OmniRoute, tüm kalıcı depolama işlemleri için **SQLite** (`better-sqlite3` ## 4. Güvenlik ve Kimlik Doğrulama -| Değişken | Varsayılan | Kaynak Dosya | Açıklama | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Makine parmak izi oluşturmak için donanım tanımlayıcılarıyla birleştirilen salt değeri. İzolasyon için her dağıtımda değiştirin. | -| `OMNIROUTE_CLI_SALT` | _(ayarlanmamış = kurulum başına rastgele oluşturulan ve `/cli-token-salt.json` konumunda kalıcı olarak saklanan salt)_ | `src/lib/machineToken.ts` | Yerel CLI kimlik doğrulama belirtecini türetmek için kullanılan HMAC salt değeri. Bu değerin ayarlanması, makinedeki tüm CLI belirteçlerini yeniler ve her zaman kalıcı olarak saklanan salt değerine göre önceliklidir. Bkz. `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Oturum çerezlerinde `Secure` bayrağını ayarlar. HTTPS arkasında çalışırken **`true` olmalıdır**. | -| `REQUIRE_API_KEY` | `false` | API ara yazılımı | `true` olduğunda, tüm `/v1/*` proxy istekleri geçerli bir API anahtarı içermelidir. **Bu bayrak `GET /v1/models` erişimini denetlemez**; bu uç nokta bunun yerine Pano oturum açma yaklaşımını (`requireAuthForModels`) izler. Dolayısıyla `/v1/models` tarafından döndürülen bir `401`, çıkarımın korunduğu anlamına GELMEZ. Bkz. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Pano kullanıcı arayüzünde API anahtarı değerlerinin tamamının görüntülenmesine izin verir. Pano Özellik Bayrakları üzerinden yapılandırılabilir; paylaşılan örneklerde güvenlik riski oluşturur. | -| `NO_LOG_API_KEY_IDS` | _(boş)_ | `src/lib/compliance/index.ts` | İstek günlüklemesini atlayan, virgülle ayrılmış API anahtarı kimlikleri (GDPR uyumluluğu). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ayarlanmamış = sınırsız)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` sütunu null olan API anahtarlarına uygulanan günlük yedek istek bütçesi. Ayarlanmamış veya boş: örtük sınır yoktur (#2289, #11017). `0` da aynı anlama gelir (sınırsız). Pozitif N tam sayısı; günlük N, haftalık 5N, aylık 20N sınırlarını etkinleştirir. Hatalı biçimlendirilmiş boş olmayan değerler, eski günlük 1000, haftalık 5000, aylık 20000 pencerelerine geri döner. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | İzin verilen maksimum istek gövdesi boyutu. Bu sınırı aşan yükleri reddeder. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Bu eşiğe ulaşan veya eşiği aşan gerçek istek gövdeleri, JSON ayrıştırılmadan önce atomik ve işlem-yerel ağır yük kabul tahsisini alır (BYTE yolu, `POST /v1/responses` dâhil). Yapısal olarak ağır isteklerdekiyle aynı [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) sağlıklı boş kapasite kaçışını kullanır; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) sorununun geri dönmemesi için yine de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ile sınırlandırılır. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Sınırlı alım sırasında okunan baytlara uygulanan sohbet rotası kesin üst sınırı; eksik, geçersiz veya gerçeğe aykırı `Content-Length` içeren istekler de buna dahildir. Sınırı aşanlar `413` alır. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ayarlanmamış — istek sayısı sınırı yok)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** bu eski istek SAYISI sınırı artık yalnızca açıkça ayarlandığında uygulanır. Ayarlanmamış bırakıldığında (varsayılan), ağır sohbet kabulü bunun yerine `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` tarafından sınırlandırılır — **tek bir süreçteki** (tek bir V8 heap'i) sürecin gerçek bellek üst sınırından boyutlandırılan, otomatik türetilmiş bir BAYT bütçesi. Birbiriyle çakışan yaklaşık 750 bin token'lık iki `/v1/responses` isteği, yaklaşık 12 GiB'lik heap'leri çökertir (#7849) — bu, 2 şeklinde kesin bir ürün üst sınırı değil, bir **bellek bütçesi** uyarısıdır. Sağlıklı bir süreç (heap, yük atma oranının altındaysa), `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` aracılığıyla daha fazla eşzamanlı uzun `/v1/responses` isteğini kabul EDEBİLİR. Onlarca uzun SSE istemcisi (40–50), “en fazla 2” değil, heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 meselesidir. Bunu “ana makineyi kullanmak” amacıyla düşünmeden yükseltmek #7849 sorununu yeniden ortaya çıkarır. **N bağımsız `DATA_DIR`** ile **heap'leri** çoğaltın (#11024); tek bir SQLite dosyasında asla `replicas>1` kullanmayın. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(otomatik türetilir)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** otomatik türetilen alım bayt bütçesini geçersiz kılma ayarı (daha kısıtlayıcı olan V8/cgroup bellek üst sınırının %25'inin 8 kat geçici büyütme oranına bölünmesi). Türetilen ve açıkça belirtilen değerler 8 MiB–2 GiB aralığıyla sınırlandırılır. Etkin bütçeden büyük bir gövde anında `413 body_exceeds_budget` hatasıyla başarısız olur; ayrı ayrı işlenebilir gövdeler arasındaki çekişme, yeniden denenebilir `503` olarak kalır. 40–50 eşzamanlı uzun SSE istemcisi, kesin bir “en fazla 2” sınırı değil, bu bütçe + heap meselesidir. Ayarlama yapmadan önce `/api/monitoring/health` adresindeki `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` değerlerini okuyun. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BAYT ve YAPI bakımından ağır kabul için heap baskısı yük atma oranı (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` sınırını aşan eşzamanlı ağır bir istek, yalnızca heap de bu orana eşit veya bunun üzerindeyse yeniden denenebilir `503` ile geri çevrilir; sağlıklı bir heap'te bunun yerine sağlıklı ek kapasite üzerinden kabul edilir. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (varsayılan `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **Hem** YAPI hem de BAYT (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` veya daha büyük gövdeler dahil) için sağlıklı heap hızlı yoluna (#10437) yönelik sınırlı ek kapasite. Bu sınır olmadan, yoğun fakat sağlıklı heap'e sahip her istek, herhangi bir üst sınır olmaksızın kabul denetimini atlıyordu. Sağlıklı heap yolu üzerinden bu sayıda eşzamanlı kiralama etkin olduğunda, sonraki yoğun istekler gerçek heap baskısı altında kullanılan AYNI sınırlı bekleme/yük atma yoluna yönlendirilir. `0`, atlamayı tamamen devre dışı bırakır. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdesi bayt eşiğinin altında olsa bile bir sohbet isteğini ağır olarak sınıflandıran mesaj sayısı. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdesi bayt eşiğinin altında olsa bile bir sohbet isteğini ağır olarak sınıflandıran araç sayısı. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Bir isteği ağır olarak sınıflandıran, dize boyutuna dayalı ihtiyatlı token tahmini; bu, sağlayıcının faturalandırma tokenleştirmesi değil, kabul maliyeti için kullanılan bir vekil ölçümdür. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (devre dışı) | `src/shared/middleware/chatBodyAdmission.ts` | İsteğe bağlı olarak etkinleştirilebilen sohbet geçmişi sınırı. Varsayılan olarak devre dışıdır: mesaj sayısı, bir isteğin evrensel özelliği değil, dağıtım politikasıdır ve burada sınır koymak, sıkıştırma işlem hattı konuşmaları işlenebilir hâle getirmeden önce onları nihai bir `413` ile reddeder. Heap büyümesi, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ve heap baskısı yük atma mekanizmasıyla sınırlandırılır. Kesin bir üst sınıra ihtiyaç duyan bellek kısıtlı dağıtımlarda pozitif bir değer ayarlayın; bu durumda sınırı aşanlar, sıkıştırma gerektiğini belirten yapılandırılmış bir `413` alır. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Tamamı bellekte arabelleğe alınan, akışsız bir yukarı akış yanıtı için kesin üst sınır. Bu sınır aşıldığında yukarı akış okuyucusu iptal edilir ve istek, heap tükenene kadar sınırsız bir dize büyütmek yerine hızla başarısız olur. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Üst kaynak yanıt başlıklarından iletilecek maksimum kablo baytı. Bütçe aşıldığında, yaygın ters proxy başlık sınırları içinde kalmak için daha düşük öncelikli başlıklar (ör. özel `x-codex-*`, `x-oai-request-id`) kaldırılır. Daha büyük yanıt başlığı boyutu karşılığında daha fazla üst kaynak meta verisi iletmek için daha yüksek bir değere ayarlayın. | -| `CORS_ORIGIN` | _(ayarlanmamış)_ | `src/server/cors/origins.ts` | Eski tek kaynaklı CORS izin listesi. Yeni dağıtımlar için `CORS_ALLOWED_ORIGINS` tercih edin. CORS yalnızca farklı kaynaklardan erişen tarayıcı API istemcileri içindir; kimliği doğrulanmış pano yazma işlemleri bunun yerine aynı kaynaklı istekleri ve oturuma bağlı CSRF korumasını kullanır. | -| `CORS_ALLOWED_ORIGINS` | _(ayarlanmamış)_ | `src/server/cors/origins.ts` | Virgülle ayrılmış CORS izin listesi. `CORS_ALLOW_ALL=true` açıkça yapılandırılmadığı sürece joker karakter gönderilmez. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Herhangi bir tarayıcı `Origin` değerini yansıtmak için yalnızca geliştirmeye yönelik kaçış mekanizması. Paylaşılan veya üretim dağıtımlarında etkinleştirmeyin. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Özel/geri döngü/yerel bağlantı IP aralıklarını hedefleyen sağlayıcı çağrılarını engelleyin. Yalnızca yalıtılmış test ortamlarında devre dışı bırakın. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Özel/yerel ağlara (localhost, 192.168.x.x, 10.x.x.x vb.) işaret eden sağlayıcı URL'lerine izin verin. **Kendi kendine barındırılan sağlayıcılar için ZORUNLUDUR** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). `false` olduğunda pano, yerel URL'lerin doğrulanmasını reddeder. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Yerel/özel adreslerde (127.0.0.1, localhost, LAN, özel aralıklar) sağlayıcı eklenmesine/doğrulanmasına izin verin — yalnızca sağlayıcı doğrulama yolu kapsamında geçerlidir. **Varsayılan `true`** (önce yerel); yalnızca genele açık adreslere izin veren katı engellemeyi uygulamak için `false` olarak ayarlayın. Bulut meta veri uç noktaları (169.254.169.254, metadata.google.internal) her durumda engellenmeye devam eder. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` rotalarının (deşifre, konuşma, çeviri) localhost dışında barındırılan OpenAI uyumlu bir sağlayıcı düğümünü kullanmasına izin verin. Varsayılan olarak kapalıdır — ses verilerini uzak bir ana bilgisayara yönlendirmek, çıkış kimliğini değiştirir ve operatörün açık kararı olmalıdır. Geri döngü/özel düğümlere (localhost, 127.0.0.1, 172.16-31.x) her zaman izin verilir ve bunlar etkilenmez. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` işleminin (ve bellek motorunun geri döngü yeniden sıralama adımının) localhost dışında barındırılan OpenAI uyumlu bir sağlayıcı düğümünü — TEI, Infinity, vLLM vb. çalıştıran bir LAN makinesini veya Tailscale eşini — kullanmasına izin verin. Varsayılan olarak kapalıdır — uzak bir ana bilgisayara yönlendirme, çıkış kimliğini değiştirir ve operatörün açık kararı olmalıdır. Geri döngü düğümlerine (localhost, 127.0.0.1, 172.16-31.x) her zaman izin verilir ve bunlar etkilenmez. Uzak düğümler ayrıca sağlayıcının giden URL politikasından (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) geçmelidir; bulut meta veri ana bilgisayarlarına hiçbir zaman yönlendirme yapılmaz. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC etkinleştirildiğinde, kullanıcıların yalnızca OIDC Tek Oturum Açma aracılığıyla kimlik doğrulaması yapabilmesi için parola ile oturum açmayı devre dışı bırakın. Çıplak `OIDC_DISABLE_PASSWORD_LOGIN` takma adı da kabul edilir; aynı anahtara sahip Pano Özellik Bayrağı önceliklidir. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` için çıplak takma ad (#10889). | +| Değişken | Varsayılan | Kaynak Dosya | Açıklama | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Makine parmak izi oluşturmak için donanım tanımlayıcılarıyla birleştirilen salt değeri. İzolasyon için her dağıtımda değiştirin. | +| `OMNIROUTE_CLI_SALT` | _(ayarlanmamış = kurulum başına rastgele oluşturulan ve `/cli-token-salt.json` konumunda kalıcı olarak saklanan salt)_ | `src/lib/machineToken.ts` | Yerel CLI kimlik doğrulama belirtecini türetmek için kullanılan HMAC salt değeri. Bu değerin ayarlanması, makinedeki tüm CLI belirteçlerini yeniler ve her zaman kalıcı olarak saklanan salt değerine göre önceliklidir. Bkz. `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Oturum çerezlerinde `Secure` bayrağını ayarlar. HTTPS arkasında çalışırken **`true` olmalıdır**. | +| `REQUIRE_API_KEY` | `false` | API ara yazılımı | `true` olduğunda, tüm `/v1/*` proxy istekleri geçerli bir API anahtarı içermelidir. **Bu bayrak `GET /v1/models` erişimini denetlemez**; bu uç nokta bunun yerine Pano oturum açma yaklaşımını (`requireAuthForModels`) izler. Dolayısıyla `/v1/models` tarafından döndürülen bir `401`, çıkarımın korunduğu anlamına GELMEZ. Bkz. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Pano kullanıcı arayüzünde API anahtarı değerlerinin tamamının görüntülenmesine izin verir. Pano Özellik Bayrakları üzerinden yapılandırılabilir; paylaşılan örneklerde güvenlik riski oluşturur. | +| `NO_LOG_API_KEY_IDS` | _(boş)_ | `src/lib/compliance/index.ts` | İstek günlüklemesini atlayan, virgülle ayrılmış API anahtarı kimlikleri (GDPR uyumluluğu). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(ayarlanmamış = sınırsız)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` sütunu null olan API anahtarlarına uygulanan günlük yedek istek bütçesi. Ayarlanmamış veya boş: örtük sınır yoktur (#2289, #11017). `0` da aynı anlama gelir (sınırsız). Pozitif N tam sayısı; günlük N, haftalık 5N, aylık 20N sınırlarını etkinleştirir. Hatalı biçimlendirilmiş boş olmayan değerler, eski günlük 1000, haftalık 5000, aylık 20000 pencerelerine geri döner. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | İzin verilen maksimum istek gövdesi boyutu. Bu sınırı aşan yükleri reddeder. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Bu eşiğe ulaşan veya eşiği aşan gerçek istek gövdeleri, JSON ayrıştırılmadan önce atomik ve işlem-yerel ağır yük kabul tahsisini alır (BYTE yolu, `POST /v1/responses` dâhil). Yapısal olarak ağır isteklerdekiyle aynı [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) sağlıklı boş kapasite kaçışını kullanır; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) sorununun geri dönmemesi için yine de `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ile sınırlandırılır. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Sınırlı alım sırasında okunan baytlara uygulanan sohbet rotası kesin üst sınırı; eksik, geçersiz veya gerçeğe aykırı `Content-Length` içeren istekler de buna dahildir. Sınırı aşanlar `413` alır. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(ayarlanmamış — istek sayısı sınırı yok)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** bu eski istek SAYISI sınırı artık yalnızca açıkça ayarlandığında uygulanır. Ayarlanmamış bırakıldığında (varsayılan), ağır sohbet kabulü bunun yerine `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` tarafından sınırlandırılır — **tek bir süreçteki** (tek bir V8 heap'i) sürecin gerçek bellek üst sınırından boyutlandırılan, otomatik türetilmiş bir BAYT bütçesi. Birbiriyle çakışan yaklaşık 750 bin token'lık iki `/v1/responses` isteği, yaklaşık 12 GiB'lik heap'leri çökertir (#7849) — bu, 2 şeklinde kesin bir ürün üst sınırı değil, bir **bellek bütçesi** uyarısıdır. Sağlıklı bir süreç (heap, yük atma oranının altındaysa), `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` aracılığıyla daha fazla eşzamanlı uzun `/v1/responses` isteğini kabul EDEBİLİR. Onlarca uzun SSE istemcisi (40–50), “en fazla 2” değil, heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 meselesidir. Bunu “ana makineyi kullanmak” amacıyla düşünmeden yükseltmek #7849 sorununu yeniden ortaya çıkarır. **N bağımsız `DATA_DIR`** ile **heap'leri** çoğaltın (#11024); tek bir SQLite dosyasında asla `replicas>1` kullanmayın. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(otomatik türetilir)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** otomatik türetilen alım bayt bütçesini geçersiz kılma ayarı (daha kısıtlayıcı olan V8/cgroup bellek üst sınırının %25'inin 8 kat geçici büyütme oranına bölünmesi). Türetilen ve açıkça belirtilen değerler 8 MiB–2 GiB aralığıyla sınırlandırılır. Etkin bütçeden büyük bir gövde anında `413 body_exceeds_budget` hatasıyla başarısız olur; ayrı ayrı işlenebilir gövdeler arasındaki çekişme, yeniden denenebilir `503` olarak kalır. 40–50 eşzamanlı uzun SSE istemcisi, kesin bir “en fazla 2” sınırı değil, bu bütçe + heap meselesidir. Ayarlama yapmadan önce `/api/monitoring/health` adresindeki `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` değerlerini okuyun. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BAYT ve YAPI bakımından ağır kabul için heap baskısı yük atma oranı (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` sınırını aşan eşzamanlı ağır bir istek, yalnızca heap de bu orana eşit veya bunun üzerindeyse yeniden denenebilir `503` ile geri çevrilir; sağlıklı bir heap'te bunun yerine sağlıklı ek kapasite üzerinden kabul edilir. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (varsayılan `1`) | `src/shared/middleware/chatBodyAdmission.ts` | **Hem** YAPI hem de BAYT (`admitChatRequest`, `OMNIROUTE_CHAT_LARGE_BODY_BYTES` veya daha büyük gövdeler dahil) için sağlıklı heap hızlı yoluna (#10437) yönelik sınırlı ek kapasite. Bu sınır olmadan, yoğun fakat sağlıklı heap'e sahip her istek, herhangi bir üst sınır olmaksızın kabul denetimini atlıyordu. Sağlıklı heap yolu üzerinden bu sayıda eşzamanlı kiralama etkin olduğunda, sonraki yoğun istekler gerçek heap baskısı altında kullanılan AYNI sınırlı bekleme/yük atma yoluna yönlendirilir. `0`, atlamayı tamamen devre dışı bırakır. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdesi bayt eşiğinin altında olsa bile bir sohbet isteğini ağır olarak sınıflandıran mesaj sayısı. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Gövdesi bayt eşiğinin altında olsa bile bir sohbet isteğini ağır olarak sınıflandıran araç sayısı. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Bir isteği ağır olarak sınıflandıran, dize boyutuna dayalı ihtiyatlı token tahmini; bu, sağlayıcının faturalandırma tokenleştirmesi değil, kabul maliyeti için kullanılan bir vekil ölçümdür. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (devre dışı) | `src/shared/middleware/chatBodyAdmission.ts` | İsteğe bağlı olarak etkinleştirilebilen sohbet geçmişi sınırı. Varsayılan olarak devre dışıdır: mesaj sayısı, bir isteğin evrensel özelliği değil, dağıtım politikasıdır ve burada sınır koymak, sıkıştırma işlem hattı konuşmaları işlenebilir hâle getirmeden önce onları nihai bir `413` ile reddeder. Heap büyümesi, `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ve heap baskısı yük atma mekanizmasıyla sınırlandırılır. Kesin bir üst sınıra ihtiyaç duyan bellek kısıtlı dağıtımlarda pozitif bir değer ayarlayın; bu durumda sınırı aşanlar, sıkıştırma gerektiğini belirten yapılandırılmış bir `413` alır. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Tamamı bellekte arabelleğe alınan, akışsız bir yukarı akış yanıtı için kesin üst sınır. Bu sınır aşıldığında yukarı akış okuyucusu iptal edilir ve istek, heap tükenene kadar sınırsız bir dize büyütmek yerine hızla başarısız olur. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Üst kaynak yanıt başlıklarından iletilecek maksimum kablo baytı. Bütçe aşıldığında, yaygın ters proxy başlık sınırları içinde kalmak için daha düşük öncelikli başlıklar (ör. özel `x-codex-*`, `x-oai-request-id`) kaldırılır. Daha büyük yanıt başlığı boyutu karşılığında daha fazla üst kaynak meta verisi iletmek için daha yüksek bir değere ayarlayın. | +| `CORS_ORIGIN` | _(ayarlanmamış)_ | `src/server/cors/origins.ts` | Eski tek kaynaklı CORS izin listesi. Yeni dağıtımlar için `CORS_ALLOWED_ORIGINS` tercih edin. CORS yalnızca farklı kaynaklardan erişen tarayıcı API istemcileri içindir; kimliği doğrulanmış pano yazma işlemleri bunun yerine aynı kaynaklı istekleri ve oturuma bağlı CSRF korumasını kullanır. | +| `CORS_ALLOWED_ORIGINS` | _(ayarlanmamış)_ | `src/server/cors/origins.ts` | Virgülle ayrılmış CORS izin listesi. `CORS_ALLOW_ALL=true` açıkça yapılandırılmadığı sürece joker karakter gönderilmez. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Herhangi bir tarayıcı `Origin` değerini yansıtmak için yalnızca geliştirmeye yönelik kaçış mekanizması. Paylaşılan veya üretim dağıtımlarında etkinleştirmeyin. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Eski takma ad (alias). Bu değişkenin panodaki geçiş düğmesi (bir veritabanı geçersiz kılması) bu ortam değerinden önce okunur; ikisinden birinde `false`, `0`, `no` veya `off` olması, o bayrağın panodaki geçiş düğmesi kapalı olarak kaydedilmiş olsa bile, ana bilgisayar denetimlerini `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` yaptığı şekilde kapatır; diğer değerler korumayı aşağıdaki iki bayrağa bırakır. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Giden URL korumasının ana bilgisayar denetimlerini, bulut meta verisi engellemesi de dahil (169.254.0.0/16'nın tamamı, metadata.google.internal), koruma modunu izleyen sağlayıcı yollarında kapatır: URL doğrulama, model keşfi, sağlayıcı düğümü temel URL'leri (uzak yeniden sıralama düğümleri dahil) ve vekil geri dönüş testi; ayrıca uzak görüntü/medya getirme gibi diğer sağlayıcıya yakın giden yollar. Bunun yanı sıra özel webhook hedeflerine de izin verir; yalnızca bulut meta verisi/yerel bağlantı uç noktaları, bu bayrak açık olsa bile engellenmeye devam eder. `BaseExecutor.execute()` üzerinden gönderilen sohbet istekleri bulut meta verisi denetimini korur. Yerel sağlayıcılar varsayılanlarla çalışır: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (varsayılan `true`) doğrulama, keşif ve sağlayıcı düğümü yollarında yerel ve LAN URL'lerine zaten izin verir; vekil geri dönüş testi ile özel webhook hedefleri yalnızca bu bayrağa bakar ve bayrak kapalıyken yerel/LAN ana bilgisayarlarını engeller. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` değerini geçersiz kılar. Panodaki geçiş düğmesinden kaydedilen bir değer bu değişkene göre önceliklidir. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Sağlayıcı doğrulaması, model keşfi, sohbet ve koruma modunun yönettiği diğer sağlayıcıya yakın giden yollar için yerel/özel adreslerdeki (127.0.0.1, localhost, LAN, özel aralıklar) sağlayıcı URL'lerine izin verin. **Varsayılan `true`** (önce yerel): koruma bu durumda bulut meta verisi uç noktalarını engeller — 169.254.0.0/16'nın tamamı ve bilinen meta veri ana bilgisayar adları (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Yalnızca genele açık mod için `false` olarak ayarlayın; bu mod özel ve geri döngü ana bilgisayarlarını da engeller. Koruma, herhangi bir DNS aramasından önce ana bilgisayar adını veya IP sabit değerini yazıldığı gibi denetler; yalnızca genele açık modda safe-fetch sarmalayıcısı ayrıca ana bilgisayar adını çözer ve herhangi bir özel yanıtı reddeder. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, bu denetimleri kendi satırının listelediği yollarda kapatır. LM Studio, Ollama, vLLM, Llamafile ve Triton gibi yerleşik yerel sağlayıcılar, anahtar doğrulaması ve sohbet için tüm ana bilgisayar denetimlerini atlar. Panodaki geçiş düğmesinden kaydedilen bir değer bu değişkene göre önceliklidir. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` rotalarının (deşifre, konuşma, çeviri) localhost dışında barındırılan OpenAI uyumlu bir sağlayıcı düğümünü kullanmasına izin verin. Varsayılan olarak kapalıdır — ses verilerini uzak bir ana bilgisayara yönlendirmek, çıkış kimliğini değiştirir ve operatörün açık kararı olmalıdır. Geri döngü/özel düğümlere (localhost, 127.0.0.1, 172.16-31.x) her zaman izin verilir ve bunlar etkilenmez. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` işleminin (ve bellek motorunun geri döngü yeniden sıralama adımının) localhost dışında barındırılan OpenAI uyumlu bir sağlayıcı düğümünü — TEI, Infinity, vLLM vb. çalıştıran bir LAN makinesini veya Tailscale eşini — kullanmasına izin verin. Varsayılan olarak kapalıdır — uzak bir ana bilgisayara yönlendirme, çıkış kimliğini değiştirir ve operatörün açık kararı olmalıdır. Geri döngü düğümlerine (localhost, 127.0.0.1, 172.16-31.x) her zaman izin verilir ve bunlar etkilenmez. Bir uzak düğümün temel URL'si de ayrıca sağlayıcının giden URL politikasından (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) geçmelidir. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC etkinleştirildiğinde, kullanıcıların yalnızca OIDC Tek Oturum Açma aracılığıyla kimlik doğrulaması yapabilmesi için parola ile oturum açmayı devre dışı bırakın. Çıplak `OIDC_DISABLE_PASSWORD_LOGIN` takma adı da kabul edilir; aynı anahtara sahip Pano Özellik Bayrağı önceliklidir. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` için çıplak takma ad (#10889). | ### Güçlendirme Kontrol Listesi diff --git a/docs/i18n/uk-UA/CONTRIBUTING.md b/docs/i18n/uk-UA/CONTRIBUTING.md index 95bf0e19d6c0..18e40058f7bb 100644 --- a/docs/i18n/uk-UA/CONTRIBUTING.md +++ b/docs/i18n/uk-UA/CONTRIBUTING.md @@ -386,18 +386,18 @@ docs/ # Documentation - [ ] Тести проходять (`npm test`) - [ ] Перевірка лінтером проходить (`npm run lint`) -- [ ] Збірка виконується успішно (`npm run build`) +- [ ] Збірка успішна (`npm run build`) - [ ] Додано типи TypeScript для нових публічних функцій та інтерфейсів - [ ] Немає жорстко закодованих секретів або резервних значень -- [ ] Публічні облікові дані вбудовано через `resolvePublicCred()` (див. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), а не задано як літерали +- [ ] Публічні облікові дані upstream-сервісів вбудовано через `resolvePublicCred()` (див. [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), а не як літерали - [ ] Відповіді з помилками проходять через `buildErrorBody()` / `sanitizeErrorMessage()` — у тілах відповідей немає необроблених трасувань стека (див. [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Команди оболонки (`exec` / `spawn`) передають значення середовища виконання через `env`, а не через інтерполяцію рядків +- [ ] Команди оболонки (`exec` / `spawn`) передають значення часу виконання через `env`, а не через інтерполяцію рядків - [ ] Усі вхідні дані перевіряються за допомогою схем Zod - [ ] Для змін, видимих користувачам, додано **фрагмент** журналу змін у `changelog.d/{features|fixes|maintenance}/-.md` (див. [`changelog.d/README.md`](./changelog.d/README.md)) — **не** редагуйте `CHANGELOG.md` безпосередньо; фрагменти об’єднуються під час випуску й ніколи не спричиняють конфліктів між PR - [ ] Документацію оновлено (якщо застосовно) - [ ] Не створено нових сповіщень CodeQL / Secret-Scanning або кожне з них відхилено з технічним обґрунтуванням і посиланням на відповідний документ у `docs/security/` -- [ ] Маршрути, які запускають дочірні процеси (`/api/mcp/`, `/api/cli-tools/runtime/`), класифіковано як `isLocalOnlyPath()` у `src/server/authz/routeGuard.ts` — див. [жорстке правило №15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] У повідомленнях комітів немає завершальних рядків `Co-Authored-By` — коміти мають відображатися виключно під Git-ідентичністю власника репозиторію (жорстке правило №16) +- [ ] Маршрути, що запускають дочірні процеси (`/api/mcp/`, `/api/cli-tools/runtime/`), класифіковано як `isLocalOnlyPath()` у `src/server/authz/routeGuard.ts` — див. [Жорстке правило №15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] У повідомленнях комітів немає трейлерів AI/ботів `Co-authored-by` (Жорстке правило №16) — людей-співавторів, чию роботу повторно використано, зазначено стандартними трейлерами `Co-authored-by: Name ` --- diff --git a/docs/i18n/uk-UA/docs/architecture/QUALITY_GATES.md b/docs/i18n/uk-UA/docs/architecture/QUALITY_GATES.md index 75fc87e39cf4..dcb4ccb24333 100644 --- a/docs/i18n/uk-UA/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/uk-UA/docs/architecture/QUALITY_GATES.md @@ -19,229 +19,224 @@ ### Допуск кандидата -Кожен із робочих процесів CI та Quality Gates формує стабільний вердикт: `Gate / CI` і -`Gate / Quality`. Їхня версіонована політика допуску визначає кожне завдання верхнього рівня +Робочі процеси CI і Quality Gates створюють стабільні вердикти: `Gate / CI` і +`Gate / Quality`. Їхня версіонована політика допуску визначає кожне попереднє завдання як обов’язкове або рекомендаційне. Застосовне обов’язкове завдання має завершитися успішно: -відсутні, скасовані, пропущені, незавершені й невідомі результати не можуть підтвердити PASS. -Коректна класифікація «лише документація» або «лише каталог» може зробити гілку перевірок коду незастосовною; -чернетка PR не вважається прийнятним кандидатом. Мітка `hotfix` не скасовує вимоги щодо доказів. +відсутні, скасовані, пропущені, незавершені та невідомі результати не можуть підтвердити PASS. +Коректна класифікація docs-only або catalog-only може зробити гілку виконання коду незастосовною; +чернетка PR не вважається прийнятним кандидатом. Мітка `hotfix` не скасовує вимогу щодо доказів. -Обидва робочі процеси охоплюють PR і надсилання змін до гілок main/release, ручний запуск і -події групи злиття. Для надсилання змін, ручного запуску та групи злиття виконується повний набір перевірок. Форки -та групи злиття використовують розміщені виконавці для завдань, які за інших умов вибирають самостійно розміщені -виконавці; перед розгортанням необхідно перевірити достатність ресурсів розміщених виконавців. +Обидва робочі процеси охоплюють PR і пуші до гілок main/release, ручний запуск і +події merge-group. Для пушів, ручних запусків і merge-group виконується повний набір. +Форки та групи злиття використовують розміщені ранери для завдань, які за інших умов вибирають +самостійно розміщені ранери; перед розгортанням необхідно перевірити достатню місткість розміщених ранерів. Кожна JSON-квитанція ідентифікує SHA отриманої версії, запуск робочого процесу та спробу. -CLI відхиляє невідповідність між SHA отриманої версії та події. Тести робочого процесу пов’язують належність до політики -зі списком `needs` завдання, що формує вердикт, тому нова або видалена гілка перевірок не може непомітно зникнути. -Квитанції охоплюють лише власний робочий процес, а не публікацію, розгортання чи внутрішню логіку -наявного рекомендаційного сканера. Активація обох назв перевірок у правилах гілок є +CLI відхиляє невідповідність SHA отриманої версії та події. Тести робочого процесу пов’язують належність до політики +зі списком `needs` завдання вердикту, щоб нова або видалена гілка виконання не могла непомітно зникнути. +Квитанції охоплюють власний робочий процес, а не публікацію, розгортання чи внутрішню логіку +наявного рекомендаційного сканера. Активація обох назв перевірок у правилах гілки є окремою адміністративною зміною; додавання цих завдань саме по собі не захищає гілку. ### Інвентаризація статичного сканування -Версіонована інвентаризація псевдонімів npm і склад статичного сканування містяться у +Версіонований реєстр npm-псевдонімів і склад статичного сканування містяться у `config/quality/gate-manifest.json`. Запустіть `npm run check:gate-manifest`, щоб перевірити назви скриптів і точні команди за `package.json`; додавання, видалення та -розбіжності команд спричиняють помилку як у локальному хуку, так і в завданнях класифікації змін у CI. -Псевдонім не є завданням робочого процесу, екземпляром матриці чи тестовим випадком: ці показники +розбіжності команд призводять до невдачі як локального хука, так і завдань класифікації змін у CI. +Псевдонім не є завданням робочого процесу, екземпляром матриці чи тестовим випадком: ці кількості не можна подавати як взаємозамінні. Використовуйте `npm run quality:scan -- --list` або `npm run quality:scan:fast -- --list`, -щоб переглянути вибрані псевдоніми без їх виконання. Виконавець запускає +щоб переглянути вибрані псевдоніми без їх виконання. Ранер викликає точку входу npm, тому її середовище виконання (включно з Bun, де його налаштовано) зберігається. -Маніфест позначає псевдоніми поза цими профілями як такі, що запускаються окремо, а +Маніфест реєструє псевдоніми поза цими профілями як такі, що викликаються окремо, а команди обслуговування заборонені у профілях сканування лише для читання. Ці профілі охоплюють лише статичне сканування. Вони не сертифікують тести продукту, покриття, пакування, зовнішні перевірки чи повний допуск кандидата до випуску. Для допуску в робочому процесі використовуються пов’язані `config/quality/admission-policy.json` і -`scripts/quality/admission-verdict.mjs`. Профілі спостереження за випуском залишаються окремими; +`scripts/quality/admission-verdict.mjs`. Профілі спостерігача за випуском залишаються окремими; перевіряйте їхні застосовні перевірки та квитанції незалежно. Наведена нижче текстова -інвентаризація є довідковою інформацією, а не доказом того, що гейт фактично виконувався. +інвентаризація є довідковою, а не доказом фактичного виконання гейта. -Скрипти містяться в `scripts/check/` (гейти політики) і `scripts/quality/` (рушій поетапного підвищення вимог). +Скрипти містяться в `scripts/check/` (гейти політик) і `scripts/quality/` (рушій ратчету). Джерелом істини для CI є `.github/workflows/ci.yml`. -### Швидкий шлях для PR випуску (`quality.yml`) +### Швидкий шлях PR випуску (`quality.yml`) -`.github/workflows/quality.yml` доповнює CI для PR до гілок main/release, надсилання змін -до захищених гілок, ручного запуску та груп злиття. Для PR використовуються швидкі перевірки з фільтрацією за шляхами. Назавжди +`.github/workflows/quality.yml` доповнює CI для PR у main/release, пушів до захищених гілок, +ручних запусків і груп злиття. PR використовують швидкі перевірки з фільтрацією за шляхами. Постійно вимкнену дублювальну збірку видалено; фактичні перевірки збірки, пакування та запуску залишаються в CI. -| Завдання | Область застосування | Блокування | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| `Docs Gates (fast-path)` | PR документації/коду; посилання в документації API та вся документація | Так | -| `Fast Quality Gates` | PR коду; статичні перевірки, перевірка типів, перевірка типів панелі керування, відповідні модульні тести | Так | -| `Forgotten sibling tests` | PR коду; змінені модулі зіставляються зі статичними споживачами та потенційними сусідніми тестами; шляхи через агрегувальні модулі та динамічний імпорт подаються як рекомендаційна діагностика з указаними винятками зі списку дозволів | **Рекомендаційне** | -| `Vitest (fast-path)` | PR коду; швидкий набір тестів vitest | Так | -| `Unit Tests fast-path` | PR коду; набір модульних тестів у 4 сегментах | Так | -| `No new ESLint warnings` | PR коду; перевірка lint з урахуванням приглушень | Так, включно з форками | -| `Merge integrity (changelog + generated skills)` | PR, що не є чернетками; синхронізація журналу змін і згенерованих навичок | Так, включно з форками | - -#### Звіт про забуті сусідні тести - -`npm run check:forgotten-sibling-tests` повторно використовує засіб визначення імпортів, що лежить в основі карти впливу тестів. -Для кожного зміненого робочого модуля він формує детерміновані ланцюжки -`змінений модуль/символ -> статичний споживач -> потенційний сусідній тест`, якщо потенційний -тест відсутній у різниці pull request. Підсумок Markdown і результат JSON зберігаються як -артефакт робочого процесу `forgotten-sibling-tests` для калібрування перед будь-яким запровадженням блокування. - -Повторні експорти через barrel-файли та динамічні імпорти використовуються лише для діагностики розв’язання залежностей; вони ніколи не створюють -блокувального результату перевірки. Перевірені винятки зберігаються у -`config/quality/forgotten-sibling-allowlist.json`. Кожен запис має вказувати споживача та тест-кандидат, -містити конкретне обґрунтування й посилання на issue або pull request у GitHub. Некоректно сформовані записи призводять -до блокування. Винятки не можуть приховати видалений тест-кандидат або diff, який додає `.skip`/`.todo`; -послаблення перевірок та інші способи маскування залишаються сферою відповідальності незалежного блокувального -гейта `check:test-masking`. +| Завдання | Область | Блокування | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `Docs Gates (fast-path)` | PR документації/коду; посилання на документацію API та повна документація | Так | +| `Fast Quality Gates` | PR коду; статичні перевірки, перевірка типів, перевірка типів панелі керування, заторкнуті модульні тести | Так | +| `Forgotten sibling tests` | PR коду; змінені модулі, простежені до статичних споживачів і потенційних споріднених тестів; шляхи barrel-файлів і динамічного імпорту подаються як рекомендаційна діагностика із зазначеними винятками зі списку дозволів | **Рекомендаційне** | +| `Vitest (fast-path)` | PR коду; швидкий набір vitest | Так | +| `Unit Tests fast-path` | PR коду; набір модульних тестів із 4 шардів | Так | +| `No new ESLint warnings` | PR коду; перевірка lint з урахуванням придушень | Так, включно з форками | +| `Merge integrity (changelog + generated skills)` | PR, що не є чернетками; синхронізація журналу змін і згенерованих навичок | Так, включно з форками | + +#### Звіт про забуті споріднені тести + +`npm run check:forgotten-sibling-tests` повторно використовує розпізнавач імпортів, що лежить в основі карти впливу тестів. +Для кожного зміненого робочого модуля він повідомляє детерміновані ланцюжки +`змінений модуль/символ -> статичний споживач -> потенційний споріднений тест`, коли потенційний +тест відсутній у diff запиту на злиття. Зведення Markdown і результат JSON зберігаються як +артефакт робочого процесу `forgotten-sibling-tests` для калібрування перед будь-яким розгортанням блокування. + +Барельні реекспорти та динамічні іморти використовуються лише для діагностики розв’язання; вони ніколи не створюють +блокувального результату. Перевірені винятки містяться у +`config/quality/forgotten-sibling-allowlist.json`. Кожен запис має вказувати споживача й тест-кандидат, +містити конкретне обґрунтування та посилання на issue або pull request у GitHub. Некоректно сформовані записи +призводять до блокування. Винятки не можуть приховати видалений тест-кандидат або diff, що додає `.skip`/`.todo`; +послаблення перевірок та інше маскування залишаються у сфері відповідальності незалежного блокувального +шлюзу `check:test-masking`. ### Завдання: `lint` Запускається для кожного PR до `main`. У разі невдачі блокує злиття. -| Скрипт (`npm run ...`) | Перевіряє | Блокувальний | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | -| `check:node-runtime` | Версія Node.js перебуває в межах підтримуваного діапазону | Так | -| `check:cycles` | Циклічні імпорти — усі модулі `src/` + `open-sse/` | Так | -| `check:route-validation:t06` | Наявність схем Zod для всіх маршрутів (політика рівня 6) | Так | -| `check:any-budget:t11` | Кількість `@ts-expect-error // any` не перевищує бюджет (catraca рівня 11) | Так | -| `check:provider-consistency` | Кожен провайдер у `providers.ts` має відповідний запис у `providerRegistry.ts` (і навпаки, у межах списку дозволених) | Так | -| `check:model-lifecycle` | Три вручну підтримувані таблиці маршрутизації залишаються узгодженими із зафіксованим у репозиторії знімком життєвого циклу (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не оцінює жоден виведений з експлуатації ідентифікатор, який може маршрутизувати `REGISTRY`; кожна ціль `BUILT_IN_ALIASES` наявна в `REGISTRY` і відсутня у знімку виведених з експлуатації ідентифікаторів; кожен виведений з експлуатації ідентифікатор, який усе ще є в `REGISTRY`, перенаправляється або вказаний у `allowedRetiredInCatalog`; і жодне джерело чи ціль `DEFAULT_DEGRADATION_MAP` не позначені як виведені з експлуатації в цьому знімку. Це не доводить, що модель наразі обслуговується активним зовнішнім сервісом. Перевірка виконується офлайн — шляхом порівняння з `config/quality/model-lifecycle.json`, який оновлюється вручну за допомогою `npm run quality:refresh-model-lifecycle` (потребує мережі; не інтегровано в CI). `allowedRetiredInCatalog` — це храповий механізм поступового скорочення: додавайте запис лише за наявності завдання для відстеження. | Так | -| `check:fetch-targets` | Кожен `fetch("/api/...")` у клієнтській частині `src/` відповідає реальному `route.ts` | Так | -| `check:deps` | Усі залежності, які можна встановити за допомогою `npm install`, у кожному `package.json` репозиторію наявні в `dependency-allowlist.json`; нові незакріплені або слопсквотингові пакети позначаються | Так | -| `audit:deps` | `npm audit` (кореневий каталог + electron) — немає попереджень високого/критичного рівня (частково дублює osv `check:vuln-ratchet`; див. список завдань із раціоналізації) | Так | -| `check:lockfile` | Цілісність `package-lock.json` — реєстр https, хеші цілісності, відсутність перевизначень хостів | Так | -| `check:licenses` | Список дозволених ліцензій SPDX для виробничих залежностей | Так | -| `check:tracked-artifacts` | Відсутні артефакти збірки / закомічені символічні посилання `node_modules` (також запускається в husky pre-commit; pre-push навмисно полегшений — #6716) | Так | -| `check:ai-attribution` | Відсутні трейлер AI/бота `Co-Authored-By` або примітка про генерацію ШІ в комітах, заголовку чи описі PR — жорстке правило №16 (у циклі швидких перевірок `quality.yml` для PR→`release/**` — читає дані події, не виконує жодних дій поза PR — і на кроці лише для PR у lint-перевірці `ci.yml` для PR→`main`; також у хуку husky `commit-msg`; співавтори-люди дозволені; #14436) | -| `check:vitest-exclusions` | Кожне виключення Vitest містить посилання на задачу для відстеження та наявне в `config/quality/vitest-exclusions.json` (#13204) | Так | -| `check:file-size` | Жоден файл вихідного коду не перевищує обмеження для відповідного розширення (храповий механізм: зафіксовані великі файли у списку `frozen`) | Так | -| `check:error-helper` | Відповіді з помилками у виконавцях/обробниках використовують `buildErrorBody()` / `sanitizeErrorMessage()` (жорстке правило №12) | Так | -| `check:migration-numbering` | SQL-файли міграцій пронумеровані послідовно, без пропусків і дублікатів | Так | -| `check:public-creds` | Жодних літеральних OAuth `client_id`/`client_secret` або вебключів Firebase поза `publicCreds.ts` (жорстке правило №11) | Так | -| `check:db-rules` | Жодного необробленого SQL поза модулями `src/lib/db/`; жодних імпортів із реекспортних модулів з `localDb.ts` (жорсткі правила №2/№5) | Так | -| `check:known-symbols` | Виконавці провайдерів, стратегії маршрутизації та транслятори, зареєстровані у відповідних таблицях диспетчеризації, відповідають файлам на диску — без осиротілих або неоголошених символів | Так | -| `check:route-guard-membership` | Кожен маршрут, що породжує дочірній процес, класифікований за допомогою `isLocalOnlyPath()` (жорсткі правила №15/№17) | Так | -| `check:test-discovery` | Кожен файл `*.test.ts` / `*.spec.ts` у репозиторії збирається принаймні одним засобом запуску тестів (храповий механізм: список осиротілих тестів у `test-discovery-baseline.json` може лише скорочуватися) | Так | -| `check:agent-skills-sync` | Згенеровані артефакти навичок агентів відповідають їхньому вихідному каталогу (без розбіжностей) | -| `check:provider-asset-provenance` | Логотипи/ресурси провайдерів мають зареєстрований запис про походження | -| `lint:json` | Файли конфігурації JSON успішно аналізуються та відповідають правилам лінтингу репозиторію | -| `typecheck:core` | Компіляція TypeScript без помилок (лише рекомендаційні попередження) | Так | -| `typecheck:noimplicit:core` | Суворий режим `noImplicitAny` — орієнтований на майбутнє; багато наявних місць виклику все ще потребують анотацій | **Рекомендаційна перевірка** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc`, обмежений областю `src/app/(dashboard)/**` (#7033) — ретельно відібраний список дозволених 27 файлів у `typecheck:core` не містить жодного TSX-файлу панелі керування, а `next build` також ніколи не перевіряє її типи (`next.config.mjs` задає `ignoreBuildErrors: true`), тому регресії з ідентифікаторами без оголошення (#6625/#6909) залишалися непомітними для CI. Відмінності порівнюються із зафіксованим базовим рівнем кількості помилок для кожного файлу та коду TS (`config/quality/dashboard-typecheck-baseline.json`, той самий шаблон перевірки застарілості, що й у `check:known-symbols`) — лише НОВІ помилки понад базову кількість спричиняють невиконання перевірки; знижуйте базовий рівень за допомогою `--update`, коли наявну помилку виправлено. | Так | +| Скрипт (`npm run ...`) | Що перевіряє | Блокувальний | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:node-runtime` | Версія Node.js перебуває в межах підтримуваного діапазону | Так | +| `check:cycles` | Циклічні імпорти в усіх `src/` + `open-sse/` (на основі AST, із розв’язанням `paths` із tsconfig). Базовий режим = рекомендаційний, виводить список циклів. `check:cycles:ratchet` (режим, який запускає CI) блокує, коли кількість перевищує граничне значення `metrics.cycles` у `quality-baseline.json` — наразі 14, `direction: down`, тому воно може лише зменшуватися (#15159 G-01/G-02) | Так (ratchet) | +| `check:route-validation:t06` | Наявність схем Zod у всіх маршрутах (політика рівня 6) | Так | +| `check:any-budget:t11` | Кількість `@ts-expect-error // any` не перевищує бюджет (catraca рівня 11) | Так | +| `check:provider-consistency` | Кожен провайдер у `providers.ts` має відповідний запис у `providerRegistry.ts` (і навпаки, у межах списку дозволених) | Так | +| `check:model-lifecycle` | Три таблиці маршрутизації, що підтримуються вручну, залишаються узгодженими зі збереженим у репозиторії знімком життєвого циклу (#11503): `FITNESS_TABLE` (`taskFitness.ts`) не оцінює жоден виведений з експлуатації ідентифікатор, який може маршрутизувати `REGISTRY`; кожна ціль `BUILT_IN_ALIASES` присутня в `REGISTRY` і відсутня у знімку виведених з експлуатації ідентифікаторів; кожен виведений з експлуатації ідентифікатор, який досі є в `REGISTRY`, перенаправляється або зазначений у `allowedRetiredInCatalog`; і жодне джерело чи ціль `DEFAULT_DEGRADATION_MAP` не позначені як виведені з експлуатації в цьому знімку. Це не доводить, що модель наразі обслуговується активним зовнішнім постачальником. Автономна перевірка — порівнює з `config/quality/model-lifecycle.json`, який оновлюється вручну за допомогою `npm run quality:refresh-model-lifecycle` (потребує мережі; не інтегровано в CI). `allowedRetiredInCatalog` — це механізм поступового скорочення: додавайте запис лише за наявності задачі для відстеження. | Так | +| `check:fetch-targets` | Кожен `fetch("/api/...")` у клієнтському коді `src/` веде до реального `route.ts` | Так | +| `check:deps` | Усі залежності, які можна встановити через `npm install`, у кожному `package.json` репозиторію присутні в `dependency-allowlist.json`; нові залежності без зафіксованої версії або пакети з назвами, схожими на назви інших пакетів, позначаються | Так | +| `audit:deps` | `npm audit` (кореневий каталог + electron) — немає рекомендацій високого/критичного рівня (частково збігається з osv `check:vuln-ratchet`; див. перелік завдань із раціоналізації) | Так | +| `check:lockfile` | Цілісність `package-lock.json` — реєстр https, хеші цілісності, відсутність перевизначень хостів | Так | +| `check:licenses` | Список дозволених ліцензій SPDX для робочих залежностей | Так | +| `check:tracked-artifacts` | Відсутність артефактів збірки / зафіксованих символічних посилань `node_modules` (також запускається в husky pre-commit; pre-push навмисно спрощений — #6716) | Так | +| `check:ai-attribution` | Відсутність трейлера AI/бота `Co-Authored-By` або примітки про генерацію ШІ в комітах, заголовку чи описі PR — жорстке правило №16 (у циклі швидких перевірок `quality.yml` для PR→`release/**` — читає дані події, нічого не робить поза PR — і на кроці лише для PR у перевірці lint у `ci.yml` для PR→`main`; також у хуку husky `commit-msg`; співавтори-люди дозволені; #14436) | +| `check:vitest-exclusions` | Кожне виключення Vitest містить посилання на задачу для відстеження та наявне в `config/quality/vitest-exclusions.json` (#13204) | Так | +| `check:file-size` | Жоден файл вихідного коду не перевищує обмеження для відповідного розширення (храповий механізм: зафіксовані великі файли у списку `frozen`) | Так | +| `check:error-helper` | Для відповідей про помилки у виконавцях/обробниках використовуються `buildErrorBody()` / `sanitizeErrorMessage()` (жорстке правило №12) | Так | +| `check:migration-numbering` | SQL-файли міграцій мають послідовну нумерацію без пропусків або дублікатів | Так | +| `check:public-creds` | Немає літеральних OAuth `client_id`/`client_secret` або вебключів Firebase поза `publicCreds.ts` (Жорстке правило №11) | Так | +| `check:db-rules` | Немає необробленого SQL поза модулями `src/lib/db/`; немає імпортів через барель із `localDb.ts` (Жорсткі правила №2/№5) | Так | +| `check:known-symbols` | Виконавці провайдерів, стратегії маршрутизації та перекладачі, зареєстровані у відповідних таблицях диспетчеризації, відповідають файлам на диску — без осиротілих або неоголошених символів | Так | +| `check:route-guard-membership` | Кожен маршрут, який запускає дочірній процес, класифіковано за допомогою `isLocalOnlyPath()` (Жорсткі правила №15/№17) | Так | +| `check:test-discovery` | Кожен файл `*.test.ts` / `*.spec.ts` у репозиторії виявляється принаймні одним засобом запуску тестів (храповий механізм: список осиротілих файлів у `test-discovery-baseline.json` може лише скорочуватися) | Так | +| `check:agent-skills-sync` | Згенеровані артефакти навичок агентів відповідають їхньому вихідному каталогу (без розбіжностей) | +| `check:provider-asset-provenance` | Логотипи/ресурси постачальників мають зафіксований запис про походження | +| `lint:json` | Файли конфігурації JSON успішно аналізуються та відповідають правилам перевірки репозиторію | +| `typecheck:core` | Компіляція TypeScript без помилок (лише рекомендаційні попередження) | Так | +| `typecheck:noimplicit:core` | Суворий режим `noImplicitAny` — орієнтований на майбутнє; багато наявних місць виклику все ще потребують анотацій | **Рекомендаційно** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc`, обмежений областю `src/app/(dashboard)/**` (#7033), — сформований вручну список дозволених 27 файлів у `typecheck:core` не містить жодного TSX-файлу панелі керування, а `next build` також ніколи не перевіряє їхні типи (`next.config.mjs` задає `ignoreBuildErrors: true`), тому регресії з осиротілими ідентифікаторами в них (#6625/#6909) були невидимими для CI. Відмінності порівнюються із зафіксованою базовою кількістю помилок для кожного файлу й кожного коду TypeScript (`config/quality/dashboard-typecheck-baseline.json`, за тим самим принципом контролю застарілості, що й `check:known-symbols`) — перевірка завершується невдало лише за наявності НОВИХ помилок понад базову кількість; зменшуйте базове значення за допомогою `--update`, коли наявну помилку виправлено. | Так | ### Завдання: `quality-gate` -Запускається після `test-coverage`. У разі невиконання блокує злиття. +Запускається після `test-coverage`. У разі невдачі блокує злиття. -| Скрипт | Що перевіряє | Блокування | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `quality:collect` | Створює `quality-metrics.json` (кількість попереджень ESLint, покриття з об’єднаного звіту сегментів) | Так (передує ratchet-перевірці) | -| `quality:ratchet` | Жодна метрика в `quality-baseline.json` не погіршилася (попередження ESLint ≤ базового рівня; покриття ≥ базового рівня) | Так | -| `check:duplication` | Дублювання коду (jscpd@4) не перевищує базовий рівень у `quality-baseline.json` | Так | -| `check:complexity` | Цикломатична складність на рівні файлу не перевищує граничного значення (базові правила ESLint `complexity` + `max-lines-per-function`) | Так | -| `check:cognitive-complexity` | Ratchet-перевірка когнітивної складності (`eslint-plugin-sonarjs`) — окремий прохід ESLint; CI запускає обидві перевірки разом як єдиний крок `check:complexity-ratchets` | Так | -| `check:dead-code` | Ratchet-перевірка невикористовуваних експортів / файлів (knip) не погіршилася порівняно з базовим рівнем | Так | -| `check:compression-budget` | Бюджет бенчмарку стиснення — мінімальні пороги економії токенів для кожного рушія не повинні погіршуватися | Так | -| `check:type-coverage` | Ratchet-перевірка відсотка типізованого коду (`type-coverage`) не погіршилася; значною мірою охоплює `typecheck:noimplicit:core` | Так | -| `check:codeql-ratchet` | Кількість відкритих сповіщень CodeQL не збільшилася (зчитується через `gh api`; коректний пропуск без токена) — періодичність оновлення та ручний запуск: див. «Ratchet-перевірка CodeQL» нижче | Так | +| Скрипт | Перевіряє | Блокування | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `quality:collect` | Створює `quality-metrics.json` (кількість попереджень ESLint, покриття з об’єднаного звіту сегментів) | Так (передує ratchet) | +| `quality:ratchet` | Жодна метрика в `quality-baseline.json` не погіршилася (попередження ESLint ≤ базового рівня; покриття ≥ базового рівня) | Так | +| `check:duplication` | Дублювання коду (jscpd@4) не перевищує базовий рівень у `quality-baseline.json` | Так | +| `check:complexity` | Цикломатична складність на рівні файлу не перевищує граничного значення (основні правила ESLint `complexity` + `max-lines-per-function`) | Так | +| `check:cognitive-complexity` | Ratchet когнітивної складності (`eslint-plugin-sonarjs`) — окремий прохід ESLint; CI запускає обидві перевірки разом як єдиний крок `check:complexity-ratchets` | Так | +| `check:dead-code` | Ratchet невикористаних експортів / файлів (knip) не погіршується порівняно з базовим рівнем | Так | +| `check:compression-budget` | Бюджет бенчмарку стиснення — мінімальні значення економії токенів для кожного рушія не повинні погіршуватися | Так | +| `check:type-coverage` | Ratchet відсотка типізованого коду (`type-coverage`) не погіршується; значною мірою охоплює `typecheck:noimplicit:core` | Так | +| `check:codeql-ratchet` | Кількість відкритих сповіщень CodeQL не збільшується (зчитується через `gh api`; коректний пропуск без токена) — частоту оновлення та ручний запуск див. у розділі «CodeQL ratchet» нижче | Так | ### Завдання: `quality-extended` Усе завдання має рекомендаційний характер (`continue-on-error: true`). Ratchet-перевірки на основі npm -виконуються повноцінно; зовнішні сканери встановлюються через `gh release download` і самостійно пропускають перевірку (exit 0), -якщо бінарний файл усе ще відсутній. - -| Скрипт | Що перевіряє | Блокування | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| `check:circular-deps` | Відсутність циклічних залежностей (dpdm) | **Рекомендаційне** | -| `check:bundle-size` | Розмір бандла не перевищує граничного значення | **Рекомендаційне** | -| `check:secrets` | Сканування секретів (gitleaks) — пропускається, якщо бінарний файл відсутній | **Рекомендаційне** | -| `check:vuln-ratchet` | Вразливості залежностей (osv-scanner) не збільшилися — пропускається, якщо бінарний файл відсутній | **Рекомендаційне** | -| `check:workflows` | Лінтинг робочих процесів (actionlint + zizmor); відсутні/несправні сканери, недійсні звіти або відсутній базовий рівень ratchet-перевірки призводять до стану INCOMPLETE. Дійсні знахідки обробляються відповідно до вибраної суворої/рекомендаційної/ratchet-політики | Виконання обов’язкове; ratchet-перевірка zizmor блокує в CI | -| `check:openapi-breaking` | Несумісні зміни публічного контракту API (`openapi.yaml`) порівняно з базовою гілкою (oasdiff) — створює `openapiBreaking=N`; пропускається, якщо oasdiff відсутній або базову специфікацію неможливо отримати | **Рекомендаційне** | +виконуються фактично; зовнішні сканери встановлюються через `gh release download` і самостійно пропускають +перевірку (exit 0), якщо виконуваний файл усе ще відсутній. + +| Скрипт | Перевіряє | Блокування | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| `check:circular-deps` | Відсутність циклічних залежностей (dpdm) | **Рекомендаційне** | +| `check:bundle-size` | Розмір пакета не перевищує граничного значення | **Рекомендаційне** | +| `check:secrets` | Сканування секретів (gitleaks) — пропускається, якщо виконуваний файл відсутній | **Рекомендаційне** | +| `check:vuln-ratchet` | Вразливості залежностей (osv-scanner) не збільшуються — пропускається, якщо виконуваний файл відсутній | **Рекомендаційне** | +| `check:workflows` | Лінтинг робочих процесів (actionlint + zizmor); відсутні/несправні сканери, недійсні звіти або відсутня базова лінія ratchet спричиняють стан INCOMPLETE. Чинні знахідки обробляються відповідно до вибраної суворої/рекомендаційної/ratchet-політики | Виконання обов’язкове; ratchet zizmor блокує в CI | +| `check:openapi-breaking` | Несумісні зміни публічного контракту API (`openapi.yaml`) порівняно з базовою гілкою (oasdiff) — виводить `openapiBreaking=N`; пропускається, якщо oasdiff відсутній або базову специфікацію неможливо отримати | **Рекомендаційне** | ### Завдання: `docs-sync-strict` -Запускається для кожного PR до `main`. У разі невдачі блокує злиття. - -| Скрипт | Що перевіряє | Блокувальний | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -| `check:docs-all` | Мета-шлюз, який послідовно запускає 6 наведених нижче підшлюзів | Так | -| ↳ `check:docs-sync` | Узгодженість версій у CHANGELOG / OpenAPI / llm.txt | Так | -| ↳ `check:docs-counts` | Числові дані в тексті (кількість провайдерів, міграцій тощо) перебувають у межах вікна храповика відносно фактичних значень | Так | -| ↳ `check:env-doc-sync` | Кожна змінна середовища в `.env.example` задокументована в таблиці документації і навпаки | Так | -| ↳ `check:deprecated-versions` | У документації немає рядків із застарілими версіями | Так | -| ↳ `check:doc-links` | Внутрішні markdown-посилання в документації ведуть до реальних файлів (у форматі `[text]`/`(path)`) | Так | -| ↳ `check:fabricated-docs` | Маршрути, змінні середовища, команди CLI, назви хуків і шляхи до файлів, згадані в документації, існують у кодовій базі. Жорсткий шлюз із `--strict`; без прапорця помилка не блокує. | Так (через `--strict` у CI) | -| `check:cli-i18n` | Рядки команд CLI наявні в усіх файлах локалей i18n | Так | -| `check:openapi-coverage` | Специфікація OpenAPI охоплює щонайменше порогову кількість реальних маршрутів, визначену храповиком | Так | -| `check:openapi-security-tiers` | Анотації рівнів безпеки в `openapi.yaml` узгоджуються з класифікаціями в `routeGuard.ts` | **Рекомендаційний** | -| `check:openapi-routes` | Кожен шлях у `openapi.yaml` відповідає реальному `route.ts` (захист від галюцинацій) | Так | -| `check:docs-symbols` | Кожне посилання `/api/...` у `docs/**/*.md` відповідає реальному `route.ts` (захист від галюцинацій) | Так | -| `i18n translation drift` | Неперекладені ключі у файлах локалей i18n — лише попередження | **Рекомендаційний** | +Запускається для кожного PR до `main`. У разі помилки блокує злиття. + +| Скрипт | Перевіряє | Блокувальний | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- | +| `check:docs-all` | Мета-перевірка, яка послідовно запускає 6 наведених нижче підперевірок | Так | +| ↳ `check:docs-sync` | Узгодженість версій у CHANGELOG / OpenAPI / llm.txt | Так | +| ↳ `check:docs-counts` | Числові дані в тексті (кількість провайдерів, міграцій тощо) перебувають у межах допустимого порога відносно фактичних значень | Так | +| ↳ `check:env-doc-sync` | Кожна змінна середовища з `.env.example` задокументована в таблиці документації, і навпаки | Так | +| ↳ `check:deprecated-versions` | У документації немає рядків із застарілими версіями | Так | +| ↳ `check:doc-links` | Внутрішні markdown-посилання в документації ведуть до реальних файлів (у форматі `[текст]`/`(шлях)`) | Так | +| ↳ `check:fabricated-docs` | Маршрути, змінні середовища, команди CLI, назви хуків і шляхи до файлів, згадані в документації, існують у кодовій базі. Жорстка перевірка через `--strict`; без прапорця помилка є некритичною. | Так (через `--strict` у CI) | +| `check:cli-i18n` | Рядки команд CLI наявні в усіх файлах локалей i18n | Так | +| `check:openapi-coverage` | Специфікація OpenAPI охоплює щонайменше порогову кількість реальних маршрутів | Так | +| `check:openapi-security-tiers` | Анотації рівнів безпеки в `openapi.yaml` узгоджені з класифікаціями в `routeGuard.ts` | **Рекомендаційна** | +| `check:openapi-routes` | Кожен шлях у `openapi.yaml` відповідає реальному `route.ts` (захист від галюцинацій) | Так | +| `check:docs-symbols` | Кожне посилання `/api/...` у `docs/**/*.md` відповідає реальному `route.ts` (захист від галюцинацій) | Так | +| `i18n translation drift` | Неперекладені ключі у файлах локалей i18n — лише попередження | **Рекомендаційна** | ### Завдання: `i18n-ui-coverage` -| Скрипт | Що перевіряє | Блокувальний | -| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -| `check-ui-keys-coverage` (вбудований) | Покриття ключів i18n інтерфейсу становить ≥ 65% | Так | -| `check-ui-value-drift` (вбудований) | Після переписування англійського **значення** не залишається застарілих перекладів | Так | -| `check-new-key-coverage` (вбудований) | **Новий** англійський ключ перекладено в кожній локалі — маркер `__MISSING__:` відхиляється | Так | -| `check-translation-ratio` | Частка справжніх перекладів для кожної локалі (ідентичні англійським / заповнювачі / відсутні кінцеві вузли поза списком дозволених) не повинна перевищувати `config/quality/i18n-translation-baseline.json` + допустиме відхилення | **Рекомендаційний** | +| Скрипт | Перевіряє | Блокувальний | +| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `check-ui-keys-coverage` (вбудовано) | Покриття ключів i18n інтерфейсу становить ≥ 65% | Так | +| `check-ui-value-drift` (вбудовано) | Після зміни англійського **значення** не залишається застарілого перекладу | Так | +| `check-new-key-coverage` (вбудовано) | **Новий** англійський ключ перекладено в кожній локалі — маркер `__MISSING__:` не допускається | Так | +| `check-translation-ratio` | Частка справжніх перекладів для кожної локалі (елементи, ідентичні англійським / заповнювачі / відсутні елементи поза списком дозволених винятків) не повинна перевищувати `config/quality/i18n-translation-baseline.json` + допустимий запас | **Рекомендаційна** | -Потребує `fetch-depth: 0` — шлюз розбіжностей значень порівнює `en.json` із базою злиття. +Потребує `fetch-depth: 0` — перевірка змін значень порівнює `en.json` із базою злиття. -#### `check-ui-value-drift` — шлюз застарілих перекладів +#### `check-ui-value-drift` — перевірка застарілих перекладів -Виявляє єдину регресію i18n, яку інші шлюзи структурно не можуть побачити: англійське значення -переписано, а переклади, створені на основі _попереднього_ англійського тексту, залишилися, тож -неангломовні користувачі продовжують читати впевнено сформульований, але вже неправильний текст. +Виявляє одну регресію i18n, яку інші перевірки структурно не здатні помітити: англійське значення +переписано, а переклади, створені на основі _попереднього_ англійського тексту, залишаються, через що +неангломовні користувачі й надалі читають упевнено сформульований, але вже неправильний текст. -Це справді потрапило у випуск. `oauthModal.googleOAuthWarning` було переписано після додавання -помічника входу Antigravity (#5203); у **39 із 43 локалей** залишився текст, який указував операторам -«скопіювати повну URL-адресу та вставити її нижче» — процес, який неможливо завершити для цього -провайдера. Це залишалося непоміченим до #8463, оскільки: +Це справді потрапило у випуск. `oauthModal.googleOAuthWarning` було переписано після появи допоміжного +засобу входу Antigravity (#5203); у **39 із 43 локалей** залишився текст, який вказував операторам «скопіювати +повну URL-адресу та вставити її нижче» — процес, який неможливо завершити для цього провайдера. Це +залишалося непоміченим до #8463, оскільки: -- `sync-ui-keys` додає лише **відсутні** ключі, але ніколи не оновлює **застарілі**; +- `sync-ui-keys` додає лише **відсутні** ключі й ніколи не оновлює **застарілі**; - `check-ui-keys-coverage` враховує _наявність_ ключа, тому застарілий переклад вважається покритим; -- `check-translation-drift` відстежує дзеркала документації `docs/i18n//**.md` — - він ніколи не читає `src/i18n/messages/*.json`. Блокує в завданні `docs-sync-strict` після - повторної синхронізації 2026-09: відредагуйте основний документ → `npm run i18n:run -- --files=` (на рівні розділу, швидко). +- `check-translation-drift` відстежує дзеркальні копії документації `docs/i18n//**.md` — + він ніколи не читає `src/i18n/messages/*.json`. Перевірка є блокувальною в завданні `docs-sync-strict` після + повторної синхронізації 2026-09: змініть основний документ → `npm run i18n:run -- --files=` (на рівні розділу, швидко). -**Враховує різницю, а не базову лінію.** Він порівнює `en.json` у базі злиття з -робочим деревом; для кожного ключа, англійське значення якого змінилося, будь-яка локаль, -що все ще містить незмінену версію перекладу, вважається застарілою. Це навмисно -**заморожує наявний борг** — за різницею неможливо визначити, з якої старої англійської -версії походить давній переклад, тому перевірка оцінює лише те, чого торкається поточна -зміна. Альтернатива (базова лінія хешів для кожного ключа) потребувала б створення -згенерованого файла розміром ~600 КБ, утричі більшого за найбільшу наявну базову лінію, -який змінювався б у кожному PR з інтернаціоналізації. +**Враховує diff, а не спирається на базовий стан.** Перевірка порівнює `en.json` у базовій точці злиття з +робочим деревом; для кожного ключа, англійське значення якого змінилося, будь-яка локаль, що досі містить +незміненний переклад, вважається застарілою. Це навмисно **фіксує вже наявний технічний борг** — diff +не може показати, з якої старої англійської версії походить давній переклад, тому перевірка оцінює +лише те, чого торкається поточна зміна. Альтернатива (базовий стан із хешем для кожного ключа) потребувала б +згенерованого файла розміром близько 600 КБ, утричі більшого за найбільший наявний базовий файл, який змінювався б у кожному i18n PR. -Є два способи пройти перевірку: +Виконати вимогу можна двома способами: 1. оновити відповідні переклади або -2. задати для них значення `__MISSING__:` — тоді середовище виконання - повертає виправлений англійський текст (`src/i18n/request.ts::deepMergeFallback`, #7258), - а ключ додається до черги на переклад. +2. установити для них значення `__MISSING__:<нова англійська версія>` — тоді під час виконання повертається виправлений англійський текст + (`src/i18n/request.ts::deepMergeFallback`, #7258), а ключ додається до черги на переклад. -Якщо змінився **зміст** рядка, краще **перейменувати ключ**: новий ключ не може -успадкувати застарілий переклад. Саме цей підхід було використано в #8463. +Якщо змінився **зміст** рядка, краще **перейменувати ключ**: новий ключ не може успадкувати +застарілий переклад. Саме цей підхід використано в #8463. ```bash -npm run i18n:check-value-drift # суворий режим (виконується в CI) +npm run i18n:check-value-drift # суворий режим (використовується в CI) npm run i18n:check-value-drift:warn # лише звіт BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Завершується з кодом 0 і повідомленням `SKIP reason=base-unresolved`, якщо базовий каталог -не вдається прочитати (поверхневий клон без базового посилання), аналогічно до -`check-openapi-breaking`. +Завершується з кодом 0 і повідомленням `SKIP reason=base-unresolved`, якщо базовий каталог неможливо прочитати (поверхневе +клонування без базового ref), аналогічно до `check-openapi-breaking`. ### Завдання: `i18n` -Повна матриця перевірки інтернаціоналізації (одне завдання на локаль). Усе завдання має -рекомендаційний характер. +Повна матриця перевірки i18n (одне завдання для кожної локалі). Усе завдання має рекомендаційний характер. | Скрипт | Що перевіряє | Блокування | | ------------------------------- | ----------------------------------- | ------------------------------------------------------------------ | @@ -249,36 +244,35 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ### Завдання: `pr-test-policy` -Виконується лише для запитів на злиття. +Виконується лише для pull request. -| Скрипт | Що перевіряє | Блокування | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check:pr-test-policy` | PR, які змінюють робочий код у `src/`, `open-sse/`, `electron/` або `bin/`, мають містити або оновлювати тести (жорстке правило №8) | Так | -| `check:test-masking` | Змінені файли тестів не зменшують загальну кількість перевірок і не додають тавтологій `assert.ok(true)` | Так | -| `check:pr-evidence` | Опис PR містить підтвердження тестування/VPS для зміни (автоматизує жорстке правило №18 через пошук у тексті PR — ненадійно, див. беклог) | Так | +| Скрипт | Що перевіряє | Блокування | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check:pr-test-policy` | PR, які змінюють робочий код у `src/`, `open-sse/`, `electron/` або `bin/`, мають додавати чи оновлювати тести (жорстке правило №8) | Так | +| `check:test-masking` | Змінені тестові файли не зменшують загальну кількість перевірок і не додають тавтологій `assert.ok(true)` | Так | +| `check:pr-evidence` | Текст PR містить свідчення тестування/VPS для зміни (автоматизує жорстке правило №18 за допомогою grep тексту PR — ненадійно, див. Backlog) | Так | ### Завдання: `test-vitest` Виконується після `build`. У разі невдачі блокує злиття. -| Набір | Що перевіряє | Блокування | -| ---------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | Сервер MCP (110 інструментів), autoCombo, кеш — рушій vitest | Так | -| `test:vitest:ui` | Тести компонентів інтерфейсу — рушій vitest | **Блокує** — наявні раніше збої явно виключено у `vitest.config.ts`; нові збої спричиняють невиконання завдання | +| Набір тестів | Що перевіряє | Блокування | +| ---------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | Сервер MCP (110 інструментів), autoCombo, кеш — засіб запуску vitest | Так | +| `test:vitest:ui` | Тести компонентів UI — засіб запуску vitest | **Блокує** — наявні раніше збої явно виключено у `vitest.config.ts`; нові збої призводять до невиконання завдання | -### Нічні робочі процеси (за розкладом, рекомендаційні) +### Щонічні робочі процеси (за розкладом, рекомендаційні) -Вони виконуються за розкладом cron (і через `workflow_dispatch`), але ніколи не виконуються -для PR. Усі вони мають рекомендаційний характер. +Вони виконуються за розкладом cron (і через `workflow_dispatch`), але ніколи не виконуються для PR. Усі вони мають рекомендаційний характер. -| Робочий процес | Що перевіряє | Блокування | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `nightly-property` | Тести властивостей fast-check із випадковим початковим значенням і великою кількістю запусків | **Рекомендаційне** | -| `nightly-resilience` | Перевірку зростання купи, хаотичне внесення збоїв, навантажувальне/тривале тестування k6 | **Рекомендаційне** | -| `nightly-llm-security` | Захист від ін'єкцій promptfoo (режим блокування) + перевірки garak (пропускаються без секрету постачальника) | **Рекомендаційне** | -| `nightly-schemathesis` | Фаззинг контракту OpenAPI (schemathesis) на активному OmniRoute з використанням `docs/openapi.yaml` — виявляє порушення специфікації / необроблені помилки 500 (етап 8 B.4) | **Рекомендаційне** | -| `nightly-mutation` | Оцінку мутаційного тестування Stryker для швидкого набору модульних тестів — уцілілі мутанти виявляють слабкі перевірки | **Рекомендаційне** | -| `nightly-compat` | Матрицю сумісності рушія Node для підтримуваних діапазонів `engines.node` | **Рекомендаційне** | +| Робочий процес | Що перевіряє | Блокування | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| `nightly-property` | Тести властивостей fast-check із випадковим початковим значенням і великою кількістю запусків | **Рекомендаційне** | +| `nightly-resilience` | Перевірку зростання купи, хаотичне внесення відмов, навантажувальне/тривале тестування k6 | **Рекомендаційне** | +| `nightly-llm-security` | Захист від ін’єкцій promptfoo (режим блокування) і зондування garak (пропускається без секрету постачальника) | **Рекомендаційне** | +| `nightly-schemathesis` | Фаззинг контракту OpenAPI (schemathesis) на запущеному OmniRoute з використанням `docs/openapi.yaml` — виявляє порушення специфікації / необроблені помилки 500 (Fase 8 B.4) | **Рекомендаційне** | +| `nightly-mutation` | Показник мутаційного тестування Stryker для швидкого набору модульних тестів — мутанти, що вижили, виявляють слабкі перевірки | **Рекомендаційне** | +| `nightly-compat` | Матрицю сумісності рушія Node для підтримуваних діапазонів `engines.node` | **Рекомендаційне** | --- @@ -548,43 +542,43 @@ push (`release-green`); пакетно виконується лише фікс --- -## Беклог раціоналізації (перегляд ROI — Фаза 9 Хвиля 3) +## Беклог раціоналізації (огляд ROI — Фаза 9 Хвиля 3) Цей перелік було звірено з `ci.yml` 2026-06-17 (у попередній версії було пропущено `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Перегляд ROI звіреного набору -виявив наведені нижче кандидатури для раціоналізації. **Об’єднання є механічними змінами CI; -перемикання/видалення є політичними рішеннями, зарезервованими за оператором.** Наразі нічого -з наведеного нижче не застосовано. +`check:codeql-ratchet`, `check:pr-evidence`). Огляд ROI звіреного набору +виявив наведені нижче кандидатури для раціоналізації. **Об’єднання є механічними змінами +CI; увімкнення/видалення є політичними рішеннями, зарезервованими за оператором.** Нічого +з наведеного нижче ще не застосовано. -**Також не задокументовано вище** (рекомендаційне, низька інформативність): завдання `docs-lint` -(markdownlint + Vale, усе завдання з `continue-on-error`) та окремі робочі процеси сканерів +**Також не задокументовано вище** (рекомендаційні перевірки, слабкий сигнал): завдання `docs-lint` +(markdownlint + Vale, для всього завдання встановлено `continue-on-error`) та окремі робочі процеси сканерів `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` міститься у -`quality-baseline.json`, але не підключено до блокувального храповика в `ci.yml` — метрика -наразі не використовується. - -### Об’єднання / усунення дублювання (механічне, нижчий ризик) - -Кожного кандидата було перевірено на відповідність актуальному стану шлюзів 2026-06-17 (довіряй, але перевіряй); -виявилося, що кілька «очевидних» об’єднань приховують технічний борг і **не** є готовими прямими замінами. - -- **`check:docs-sync` запускається двічі** — окремо в завданні `lint` і ще раз усередині `check:docs-all` (`docs-sync-strict`) та хука husky pre-commit. ✅ **ВИКОНАНО** — окремий виклик у `lint` видалено. -- **Сканування CVE** — ❌ **НЕ є простим об’єднанням.** `audit:deps` завершується жорсткою помилкою за будь-якої CVE високої/критичної важливості; `check:vuln-ratchet` (osv) завершується помилкою лише в разі _регресії_ відносно базового рівня (наразі 1 MODERATE). Семантика різна — видалення `audit:deps` призвело б до втрати абсолютного шлюзу для високої/критичної важливості. Залишити обидва. -- **Виявлення циклів** — ❌ **НЕ є простим об’єднанням.** `check:circular-deps` (dpdm) повідомляє про **91 цикл** (саме тому він має рекомендаційний статус); його не можна зробити блокувальним, не усунувши їх спочатку, і він має ширше охоплення, ніж успішний, ретельно підібраний `check:cycles`. Залишити `check:cycles` блокувальним; усунення 91 циклу dpdm є окремим пунктом беклогу. -- **Складність** — ✅ **ВИКОНАНО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): один прохід ESLint, підрахунок за ruleId, щоб базові рівні цикломатичної складності+максимальної кількості рядків і когнітивної складності залишалися незалежними; окремі `check:complexity` / `check:cognitive-complexity` збережено для локального `--update`. -- **Антигалюцинація `/api`** — ✅ **ВИКОНАНО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): один перелік FS для `src/app/api`, openapi-routes + docs-symbols і надалі звітують незалежно; окремі перевірки збережено для локальних запусків. -- **`check:node-runtime` запускається в 11 завданнях** — ⚠️ **низький ROI.** Кожне виконується на окремому runner, а перевірка триває <1с; загальна економія ~10с ціною втрати дешевого запобіжника для кожного завдання. Не варте супутніх змін. -- **`typecheck:noimplicit:core` у lint CI** — ✅ **видалено із завдання lint** (мало рекомендаційний статус `continue-on-error`); блокувальна поверхня типів — `typecheck:core` + `check:type-coverage`. Локальний скрипт збережено. - -### Перемкнути / вирішити (політика оператора) - -- `check:openapi-security-tiers` (рекомендаційний) — ❌ **НЕ можна просто перемкнути.** Він завершується з кодом 0, але попереджає, що в кількох маршрутах `traffic-inspector` у `LOCAL_ONLY_API_PREFIXES` немає анотації `x-loopback-only: true`. Для примусового застосування спочатку потрібно додати ці анотації до `openapi.yaml`. -- `typecheck:noimplicit:core` (рекомендаційний) — значною мірою охоплюється блокувальним храповиком `check:type-coverage`. Перетворити на храповик або видалити надлишковий другий прохід `tsc`. -- `test:vitest:ui` (тепер **блокувальний**) — попередньо наявні збої явно виключено у `vitest.config.ts` за допомогою коментарів відстеження `// #8618`; нові збої спричиняють падіння завдання. -- `check:secrets` (gitleaks, блокувальний храповик, зафіксований на рівні 3 задокументованих хибнопозитивних спрацювань) — додати ці 3 випадки до списку дозволів, щоб досягти 0, або понизити до рекомендаційного статусу. Перетинається з нативним скануванням секретів GitHub + `check:public-creds`. -- `check:pr-evidence` (блокувальний, виконує grep у прозовому тексті тіла PR) — високий ризик хибнопозитивних спрацювань; видалення послабить забезпечення виконання Жорсткого правила №18, тож це справді політичне рішення. -- `semgrep` (окремий рекомендаційний процес) — перетинається з CodeQL для сімейств OWASP; підключити його базовий рівень до храповика або видалити. +`quality-baseline.json`, але не підключено до блокувального храповика в `ci.yml` — наразі +ця метрика осиротіла. + +### Об’єднання / усунення дублювання (механічні зміни, нижчий ризик) + +Кожну кандидатуру було перевірено проти актуального стану шлюзів 2026-06-17 (довіряй, але перевіряй); +виявилося, що кілька «очевидних» об’єднань приховують борг і **не** є готовими безпечними замінами. + +- **`check:docs-sync` запускається двічі** — окремо в завданні `lint` і ще раз усередині `check:docs-all` (`docs-sync-strict`) та в husky-хуку перед комітом. ✅ **ВИКОНАНО** — окремий виклик у `lint` видалено. +- **Сканування CVE** — ❌ **НЕ є простим об’єднанням.** `audit:deps` жорстко завершується помилкою за будь-якої CVE високої/критичної серйозності; `check:vuln-ratchet` (osv) завершується помилкою лише в разі _регресії_ відносно базового рівня (наразі 1 MODERATE). Семантика різна — видалення `audit:deps` призвело б до втрати абсолютного шлюзу для високих/критичних вразливостей. Залишити обидві перевірки. +- **Виявлення циклів** — ✅ **ВИКОНАНО** (#15159 G-01/G-02). У старому тексті тут `check:cycles` називався «зеленим, вибірковим» шлюзом, а збереження його блокувальним обґрунтовувалося тим, що `check:circular-deps` (dpdm) повідомляв про 91 цикл. Цей зелений статус був **хибно зеленим**: `check:cycles` сканував 5 підкаталогів (450 файлів), зіставляв лише статичні `import|export … from` і відкидав усі специфікатори `@/` та `@omniroute/open-sse/`, тому не міг побачити цикли з динамічним імпортом і псевдонімами, які переважали в репозиторії. Виправлено: тепер шлюз обходить `src` + `open-sse` (5023 файли), збирає специфікатори з AST TypeScript (тому `import("…")` враховується, а `typeof import("…")` у позиції типу — ні) та розпізнає `paths` із tsconfig. Він знаходить **14** циклів, а не 0. Оскільки 14 наявних циклів неможливо виправити в PR зі зміною шлюзу, `check:cycles` тепер є **храповиком** (`--ratchet`, верхня межа `metrics.cycles.value = 14` у `quality-baseline.json`, `direction: down`) — він блокує будь-яку _регресію_, і кількість може лише зменшуватися. CI запускає `npm run check:cycles:ratchet`. Поступове скорочення виконується разом з **A-01**. `check:circular-deps` (dpdm) залишається рекомендаційною ширшою другою думкою. +- **Складність** — ✅ **ВИКОНАНО** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): один обхід ESLint, підрахунок за ruleId, завдяки чому базові рівні цикломатичної складності+максимальної кількості рядків і когнітивної складності залишаються незалежними; окремі `check:complexity` / `check:cognitive-complexity` збережено для локального `--update`. +- **Захист від галюцинацій щодо `/api`** — ✅ **ВИКОНАНО** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): одна інвентаризація `src/app/api` у файловій системі, openapi-routes + docs-symbols усе ще звітують незалежно; окремі перевірки збережено для локальних запусків. +- **`check:node-runtime` запускається в 11 завданнях** — ⚠️ **низький ROI.** Кожне завдання виконується на окремому раннері, а перевірка триває <1 с; загальна економія становить ~10 с ціною втрати дешевого захисного механізму для кожного завдання. Не варте зайвих змін. +- **`typecheck:noimplicit:core` у lint CI** — ✅ **видалено із завдання lint** (було рекомендаційним із `continue-on-error`); блокувальна поверхня типів — `typecheck:core` + `check:type-coverage`. Локальний скрипт збережено. + +### Увімкнути / вирішити (політика оператора) + +- `check:openapi-security-tiers` (рекомендаційна перевірка) — ❌ **НЕ можна просто зробити блокувальною.** Вона завершується з кодом 0, але попереджає, що в кількох маршрутах `traffic-inspector` під `LOCAL_ONLY_API_PREFIXES` відсутня анотація `x-loopback-only: true`. Для її примусового застосування спочатку потрібно додати ці анотації до `openapi.yaml`. +- `typecheck:noimplicit:core` (рекомендаційна перевірка) — значною мірою перекривається блокувальним храповиком `check:type-coverage`. Перетворити на храповик або видалити надлишковий другий прохід `tsc`. +- `test:vitest:ui` (тепер **блокувальна перевірка**) — наявні збої явно виключено у `vitest.config.ts` за допомогою коментарів відстеження `// #8618`; нові збої спричиняють помилку завдання. +- `check:secrets` (gitleaks, блокувальний храповик, зафіксований на рівні 3 задокументованих хибнопозитивних результатів) — додати ці 3 результати до списку дозволених, щоб досягти 0, або понизити до рекомендаційної перевірки. Перекривається з нативним скануванням секретів GitHub + `check:public-creds`. +- `check:pr-evidence` (блокувальна перевірка, що шукає шаблони в прозі тіла PR) — високий ризик хибнопозитивних результатів; її видалення послабить дотримання Hard Rule #18, тому це справжнє політичне рішення. +- `semgrep` (окрема рекомендаційна перевірка) — перекривається з CodeQL щодо сімейств OWASP; підключити її базовий рівень до храповика або видалити. --- diff --git a/docs/i18n/uk-UA/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/uk-UA/docs/compression/COMPRESSION_GUIDE.md index 31cadd00337b..82f73ef33a2a 100644 --- a/docs/i18n/uk-UA/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/uk-UA/docs/compression/COMPRESSION_GUIDE.md @@ -466,8 +466,7 @@ const { messages: aged, saved } = applyAging(messages, { Для тіла з `messages` обхід за вмістом (`shouldBypassCavemanOutputMode()` у `open-sse/services/compression/outputMode.ts`) перевіряє останні три повідомлення та пропускає стилі для всього ходу, коли вони відповідають ключовим словам, пов'язаним із безпекою, -незворотними діями, уточненням або чутливістю до порядку. Обхід працює відповідно до значення перемикача -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) на панелі керування. +незворотними діями, уточненням або чутливістю до порядку. Обхід працює, поки перемикач **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) на панелі керування увімкнено, що є значенням за замовчуванням; коли перемикач вимкнено, обрані стилі застосовуються і на цих ходах. Коли обхід дозволяє обробити хід, `placeSystemInstruction()` (у тому самому файлі), який ніколи не створює новий `messages[0]`, розміщує блок у першому знайденому з наведених місць: diff --git a/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md b/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md index fb96aa266171..0873e61f44f4 100644 --- a/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/uk-UA/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Обов’язкові секрети -Їх **потрібно** встановити до першого запуску. Без них застосунок або відмовиться запускатися, або працюватиме з небезпечними значеннями за замовчуванням. - -| Змінна | Обов’язкова | Значення за замовчуванням | Файл джерела | Опис | -| ---------------------------- | ------------------------------- | --------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Так** | _(немає)_ | `src/lib/auth` | Підписує/перевіряє всі файли cookie сеансів панелі керування (JWT). Згенеруйте за допомогою `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Так** | _(немає)_ | `src/lib/db/apiKeys.ts` | Ключ шифрування AES для значень ключів API, що зберігаються в SQLite. Згенеруйте за допомогою `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Так** | `CHANGEME` | Скрипт початкового налаштування | Встановлює початковий пароль адміністратора панелі керування (відповідає значенню за замовчуванням у `.env.example` — навмисно залишено очевидно небезпечним, щоб змусити його змінити). **Змініть перед першим використанням.** Після входу змініть його через Панель керування → Налаштування → Безпека. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Так** (у робочому середовищі) | _(не встановлено)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Спільний секрет для внутрішнього моста WebSocket Codex Responses. Автентифікує запити моста між ретранслятором WS Electron/браузера та OmniRoute. ⚠️ **ОБОВ’ЯЗКОВИЙ у робочому середовищі — якщо його не встановлено, усі запити до моста WS відхиляються.** Згенеруйте за допомогою `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Ні | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Явний ідентифікатор для анулювання кешу service worker оболонки PWA (#11779); перший у ланцюжку визначення значення. | -| `SOURCE_VERSION` | Ні | _(не встановлено)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Другий у ланцюжку — встановлюється засобами збирання PaaS (наприклад, у стилі Heroku) як розгорнутий коміт. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Ні | _(похідне значення)_ | `src/shared/components/PwaRegister.tsx` | Публічне значення часу збирання, яке клієнт використовує для реєстрації `/sw.js?v=…`; виводиться з двох наведених вище значень, а потім із git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Ні (автоматично) | _(автоматично під час кожного запуску)_ | `src/server/authz/policies/management.ts` | Секрет окремого процесу, який підтверджує, що довірена позначка IP-адреси вузла надійшла від власного HTTP-сервера OmniRoute (`scripts/dev/peer-stamp.mjs`). Проміжне ПЗ авторизації довіряє локальності запиту (обмеження маршрутів LOCAL_ONLY адресами зворотного зв’язку/LAN), лише якщо позначка містить цей токен. Автоматично генерується під час кожного запуску — не встановлюйте його; задавайте фіксоване значення лише для багатопроцесних конфігурацій, які мають спільно використовувати цю позначку. | -| `OMNIROUTE_TRUSTED_PROXIES` | Ні | _(не встановлено)_ | `scripts/dev/peer-stamp.mjs` | Розділений комами список IP-адрес або діапазонів CIDR зворотних проксі-серверів, чиїм заголовкам `X-Forwarded-For` / `X-Real-IP` може довіряти список дозволених/заборонених IP-адрес, на додаток до адрес зворотного зв’язку, адрес приватної мережі та граничних вузлів Cloudflare, яким довіряють завжди. Потрібно лише для проксі-сервера з будь-якою іншою публічною адресою; інакше фільтр оцінює власну адресу цього проксі-сервера. | +Не встановлені секрети, позначені **Так**, автоматично генеруються під час першого запуску (`JWT_SECRET`, `API_KEY_SECRET`) і зберігаються у файлі `server.env` у каталозі даних — установіть їх явно, якщо цей каталог є тимчасовим. Виняток становить `OMNIROUTE_WS_BRIDGE_SECRET`: цей секрет не зберігається у `server.env` — точки входу сервера створюють короткочасне значення на кожне завантаження, яке може використовувати лише міст у межах самого процесу, тому зовнішнім клієнтам моста потрібно встановлювати його явно. + +| Змінна | Обов’язкова | Значення за замовчуванням | Файл джерела | Опис | +| ---------------------------- | ------------------------------- | --------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **Так** | _(немає)_ | `src/lib/auth` | Підписує/перевіряє всі файли cookie сеансів панелі керування (JWT). Згенеруйте за допомогою `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Так** | _(немає)_ | `src/lib/db/apiKeys.ts` | Ключ шифрування AES для значень ключів API, що зберігаються в SQLite. Згенеруйте за допомогою `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Ні | _(не встановлено)_ | `src/lib/auth/managementPassword.ts` | Встановлює пароль панелі керування під час запуску, поки жодного паролю ще не збережено; ігнорується після збереження паролю. Встановлення через npm і з вихідного коду копіює `.env.example` у `.env`, тож вони починають із загальновідомим заповнювачем `CHANGEME` із цього файлу: замініть його до першого запуску або встановіть `INITIAL_PASSWORD=` (порожнє) у тому `.env`, щоб створити пароль у майстрі налаштування під час першого візиту — так само, як це працює з невстановленим значенням (типовим для образу Docker). Змінюйте збережений пароль через Панель керування → Налаштування → Безпека або запустіть `omniroute reset-password` (`node bin/reset-password.mjs` у копії вихідного коду), встановивши `DATA_DIR` на каталог даних сервера. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Так** (у робочому середовищі) | _(не встановлено)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Спільний секрет для внутрішнього моста WebSocket Codex Responses. Автентифікує запити моста між ретранслятором WS Electron/браузера та OmniRoute. ⚠️ **ОБОВ’ЯЗКОВИЙ у робочому середовищі — якщо його не встановлено, усі запити до моста WS відхиляються.** Згенеруйте за допомогою `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Ні | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Явний ідентифікатор для анулювання кешу service worker оболонки PWA (#11779); перший у ланцюжку визначення значення. | +| `SOURCE_VERSION` | Ні | _(не встановлено)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Другий у ланцюжку — встановлюється засобами збирання PaaS (наприклад, у стилі Heroku) як розгорнутий коміт. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Ні | _(похідне значення)_ | `src/shared/components/PwaRegister.tsx` | Публічне значення часу збирання, яке клієнт використовує для реєстрації `/sw.js?v=…`; виводиться з двох наведених вище значень, а потім із git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Ні (автоматично) | _(автоматично під час кожного запуску)_ | `src/server/authz/policies/management.ts` | Секрет окремого процесу, який підтверджує, що довірена позначка IP-адреси вузла надійшла від власного HTTP-сервера OmniRoute (`scripts/dev/peer-stamp.mjs`). Проміжне ПЗ авторизації довіряє локальності запиту (обмеження маршрутів LOCAL_ONLY адресами зворотного зв’язку/LAN), лише якщо позначка містить цей токен. Автоматично генерується під час кожного запуску — не встановлюйте його; задавайте фіксоване значення лише для багатопроцесних конфігурацій, які мають спільно використовувати цю позначку. | +| `OMNIROUTE_TRUSTED_PROXIES` | Ні | _(не встановлено)_ | `scripts/dev/peer-stamp.mjs` | Розділений комами список IP-адрес або діапазонів CIDR зворотних проксі-серверів, чиїм заголовкам `X-Forwarded-For` / `X-Real-IP` може довіряти список дозволених/заборонених IP-адрес, на додаток до адрес зворотного зв’язку, адрес приватної мережі та граничних вузлів Cloudflare, яким довіряють завжди. Потрібно лише для проксі-сервера з будь-якою іншою публічною адресою; інакше фільтр оцінює власну адресу цього проксі-сервера. | ### Команди генерування @@ -216,38 +216,38 @@ OmniRoute використовує **SQLite** (через `better-sqlite3`) дл ## 4. Безпека та автентифікація -| Змінна | Значення за замовчуванням | Файл джерела | Опис | -| ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Сіль, що поєднується з апаратними ідентифікаторами для створення відбитка машини. Змінюйте для кожного розгортання, щоб забезпечити ізоляцію. | -| `OMNIROUTE_CLI_SALT` | _(не задано = випадкова сіль для кожного встановлення, збережена в `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Сіль HMAC для формування локального токена автентифікації CLI. Установлення цього значення змінює всі токени CLI на машині й завжди має пріоритет над збереженою сіллю. Див. `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Установлює прапорець `Secure` для файлів cookie сеансу. Під час роботи за HTTPS значення **має бути `true`**. | -| `REQUIRE_API_KEY` | `false` | проміжне ПЗ API | Якщо значення `true`, усі проксі-запити `/v1/*` мають містити дійсний ключ API. **Цей прапорець не обмежує `GET /v1/models`**, який натомість дотримується режиму входу до панелі керування (`requireAuthForModels`), тому відповідь `401` від `/v1/models` НЕ означає, що інференс захищено. Див. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Дозволяє показувати повні значення ключів API в інтерфейсі панелі керування. Налаштовується через прапорці функцій панелі керування; становить ризик безпеці у спільних екземплярах. | -| `NO_LOG_API_KEY_IDS` | _(порожньо)_ | `src/lib/compliance/index.ts` | Розділені комами ідентифікатори ключів API, для яких журналювання запитів не виконується (відповідність GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(не задано = без обмежень)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервний добовий бюджет запитів, що застосовується до ключів API, у яких стовпець `rate_limits` має значення null. Не задано або порожньо: неявного обмеження немає (#2289, #11017). `0` означає те саме (без обмежень). Додатне ціле число N установлює N/день, 5N/тиждень, 20N/місяць. Для некоректних непорожніх значень використовуються застарілі ліміти: 1000/день, 5000/тиждень, 20000/місяць. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 МБ) | `src/shared/middleware/bodySizeGuard.ts` | Максимально допустимий розмір тіла запиту. Відхиляє корисні навантаження, що перевищують це обмеження. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 КБ) | `src/shared/middleware/chatBodyAdmission.ts` | Фактичні тіла запитів, розмір яких дорівнює цьому порогу або перевищує його, отримують атомарну локальну для процесу оренду допуску для ресурсоємних запитів перед розбором JSON (шлях BYTE, включно з `POST /v1/responses`). Застосовується той самий механізм виходу за наявності достатнього запасу ресурсів [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), що й для структурно складних запитів; обмеження `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) усе ще діє, щоб проблема [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) не повернулася. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 МБ) | `src/shared/middleware/chatBodyAdmission.ts` | Жорстке обмеження для маршрутів чату, що застосовується до кількості байтів, прочитаних під час обмеженого приймання даних, зокрема для запитів із відсутнім, недійсним або неправдивим `Content-Length`; у разі перевищення повертається `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(не задано — без обмеження кількості запитів)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** це застаріле обмеження КІЛЬКОСТІ запитів тепер діє лише тоді, коли його задано явно. Якщо його не задано (типове значення), допуск великовагових запитів чату натомість обмежується `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — автоматично визначеним БАЙТОВИМ бюджетом, розрахованим на основі фактичного ліміту пам’яті процесу в межах **одного процесу** (однієї купи V8). Два паралельні запити `/v1/responses` приблизно по 750 тис. токенів аварійно завершують процес із купою ~12 ГіБ (#7849) — це попередження щодо **бюджету пам’яті**, а не жорсткий продуктовий максимум у 2 запити. Справний процес (купа нижче порога скидання) МОЖЕ допускати більше одночасних довгих запитів `/v1/responses` через `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десятки довгих SSE-клієнтів (40–50) обмежуються купою + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не «максимумом 2». Сліпе підвищення цього значення, щоб «використати ресурси хоста», знову спричиняє #7849. Масштабуйте **купи** за допомогою **N незалежних `DATA_DIR`** (#11024); ніколи не використовуйте `replicas>1` для одного файлу SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(визначається автоматично)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** перевизначення автоматично визначеного байтового бюджету приймання даних (25% від меншого ліміту пам’яті V8/cgroup, поділені на 8-кратне тимчасове збільшення). Автоматично визначені та явно задані значення обмежуються діапазоном 8 МіБ–2 ГіБ. Тіло, розмір якого перевищує ефективний бюджет, одразу завершується помилкою `413 body_exceeds_budget`; конкуренція між тілами, кожне з яких окремо можна обслужити, залишається повторюваною помилкою `503`. 40–50 одночасних довгих SSE-клієнтів обмежуються цим бюджетом + купою, а не жорстким «максимумом 2». Перед налаштуванням перевірте `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` за адресою `/api/monitoring/health`. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Поріг скидання через тиск на купу (`heapUsed / heap_size_limit`) для допуску великовагових запитів за БАЙТАМИ і СТРУКТУРОЮ (#10183, #10268, #10437). Паралельний великоваговий запит понад `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` скидається з повторюваною помилкою `503` лише тоді, коли використання купи ТАКОЖ дорівнює цьому порогу або перевищує його; за нормального стану купи запит допускається через додатковий резерв. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (типово `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Обмежена додаткова місткість швидкого шляху за нормального стану купи (#10437) для допуску як за СТРУКТУРОЮ, так і за БАЙТАМИ (`admitChatRequest`, зокрема для тіл розміром ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без цього обмеження кожен запит за високого навантаження, але нормального стану купи, оминав механізм допуску без будь-якої верхньої межі. Щойно через шлях для нормального стану купи активується така кількість одночасних оренд, подальші запити за високого навантаження переходять до ТОГО САМОГО шляху обмеженого очікування/скидання, що використовується за фактичного тиску на купу. `0` повністю вимикає обхід. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Кількість повідомлень, за якої запит чату класифікується як великоваговий, навіть якщо розмір його тіла менший за байтовий поріг. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Кількість інструментів, за якої запит чату класифікується як великоваговий, навіть якщо розмір його тіла менший за байтовий поріг. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Консервативна оцінка кількості токенів за розміром рядка, за якої запит класифікується як великоваговий; це наближена оцінка вартості допуску, а не токенізація для тарифікації постачальника. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (вимкнено) | `src/shared/middleware/chatBodyAdmission.ts` | Необов’язкове обмеження історії чату, яке вмикається явно. Типово вимкнено: кількість повідомлень є політикою розгортання, а не універсальною властивістю запиту, і обмеження на цьому етапі відхиляє розмови з остаточною помилкою `413`, перш ніж конвеєр стиснення зможе зробити їх придатними для обслуговування. Зростання купи обмежується `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` і скиданням через тиск на купу. У розгортаннях з обмеженою пам’яттю, де потрібна жорстка верхня межа, задайте додатне значення; у разі перевищення повертатиметься структурована помилка `413` із вимогою стиснення. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 МБ) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Жорстке обмеження для непотокової відповіді від вищого сервера, яка повністю буферизується в пам’яті. Після його перевищення читання відповіді від вищого сервера скасовується, а запит швидко завершується помилкою замість необмеженого збільшення рядка до вичерпання купи. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Максимальна кількість байтів, що передаються мережею із заголовків відповіді висхідного сервера. Коли бюджет перевищено, заголовки з нижчим пріоритетом (наприклад, користувацькі `x-codex-*`, `x-oai-request-id`) відкидаються, щоб не перевищувати поширені обмеження зворотних проксі на розмір заголовків. Установіть більше значення, щоб передавати більше метаданих висхідного сервера ціною збільшення розміру заголовків відповіді. | -| `CORS_ORIGIN` | _(не задано)_ | `src/server/cors/origins.ts` | Застарілий список дозволених джерел CORS для одного джерела. Для нових розгортань віддавайте перевагу `CORS_ALLOWED_ORIGINS`. CORS призначено лише для міждоменних браузерних клієнтів API; натомість автентифіковані операції запису з панелі керування використовують запити з того самого джерела та прив’язаний до сеансу захист CSRF. | -| `CORS_ALLOWED_ORIGINS` | _(не задано)_ | `src/server/cors/origins.ts` | Розділений комами список дозволених джерел CORS. Символ узагальнення не надсилається, якщо явно не налаштовано `CORS_ALLOW_ALL=true`. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Аварійний механізм лише для розробки, який дає змогу віддзеркалювати будь-який браузерний `Origin`. Не вмикайте у спільних або виробничих розгортаннях. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Блокує виклики постачальників, спрямовані на приватні, loopback- або link-local-діапазони IP-адрес. Вимикайте лише в ізольованих тестових середовищах. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Дозволяє URL-адреси постачальників, що вказують на приватні або локальні мережі (localhost, 192.168.x.x, 10.x.x.x тощо). **ОБОВ’ЯЗКОВО для самостійно розміщених постачальників** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Коли встановлено `false`, панель керування відхиляє перевірку локальних URL-адрес. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Дозволяє додавати та перевіряти постачальників за локальними або приватними адресами (127.0.0.1, localhost, LAN, приватні діапазони) — застосовується лише до шляху перевірки постачальника. **Типове значення — `true`** (пріоритет локального використання); установіть `false`, щоб примусово блокувати всі адреси, крім загальнодоступних. Кінцеві точки хмарних метаданих (169.254.169.254, metadata.google.internal) залишаються заблокованими незалежно від цього. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Дозволяє маршрутам `/v1/audio/*` (транскрипції, мовлення, переклади) використовувати сумісний з OpenAI вузол постачальника, розміщений поза localhost. Типово вимкнено — маршрутизація аудіо на віддалений хост змінює вихідну ідентичність і має бути явним рішенням оператора. Loopback- і приватні вузли (localhost, 127.0.0.1, 172.16-31.x) завжди дозволені, і це налаштування на них не впливає. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Дозволяє `POST /v1/rerank` (а також loopback-етап повторного ранжування рушія пам’яті) використовувати сумісний з OpenAI вузол постачальника, розміщений поза localhost — пристрій у LAN або вузол Tailscale, на якому працює TEI, Infinity, vLLM тощо. Типово вимкнено — маршрутизація на віддалений хост змінює вихідну ідентичність і має бути явним рішенням оператора. Loopback-вузли (localhost, 127.0.0.1, 172.16-31.x) завжди дозволені, і це налаштування на них не впливає. Віддалені вузли також мають відповідати політиці вихідних URL-адрес постачальника (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); маршрутизація до хостів хмарних метаданих ніколи не виконується. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Коли OIDC увімкнено, вимикає вхід за паролем, щоб користувачі могли автентифікуватися лише через єдиний вхід OIDC. Також підтримується короткий псевдонім `OIDC_DISABLE_PASSWORD_LOGIN`; прапорець функції панелі керування з тим самим ключем має вищий пріоритет. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Короткий псевдонім для `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Змінна | Значення за замовчуванням | Файл джерела | Опис | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Сіль, що поєднується з апаратними ідентифікаторами для створення відбитка машини. Змінюйте для кожного розгортання, щоб забезпечити ізоляцію. | +| `OMNIROUTE_CLI_SALT` | _(не задано = випадкова сіль для кожного встановлення, збережена в `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Сіль HMAC для формування локального токена автентифікації CLI. Установлення цього значення змінює всі токени CLI на машині й завжди має пріоритет над збереженою сіллю. Див. `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Установлює прапорець `Secure` для файлів cookie сеансу. Під час роботи за HTTPS значення **має бути `true`**. | +| `REQUIRE_API_KEY` | `false` | проміжне ПЗ API | Якщо значення `true`, усі проксі-запити `/v1/*` мають містити дійсний ключ API. **Цей прапорець не обмежує `GET /v1/models`**, який натомість дотримується режиму входу до панелі керування (`requireAuthForModels`), тому відповідь `401` від `/v1/models` НЕ означає, що інференс захищено. Див. `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Дозволяє показувати повні значення ключів API в інтерфейсі панелі керування. Налаштовується через прапорці функцій панелі керування; становить ризик безпеці у спільних екземплярах. | +| `NO_LOG_API_KEY_IDS` | _(порожньо)_ | `src/lib/compliance/index.ts` | Розділені комами ідентифікатори ключів API, для яких журналювання запитів не виконується (відповідність GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(не задано = без обмежень)_ | `src/shared/utils/apiKeyPolicy.ts` | Резервний добовий бюджет запитів, що застосовується до ключів API, у яких стовпець `rate_limits` має значення null. Не задано або порожньо: неявного обмеження немає (#2289, #11017). `0` означає те саме (без обмежень). Додатне ціле число N установлює N/день, 5N/тиждень, 20N/місяць. Для некоректних непорожніх значень використовуються застарілі ліміти: 1000/день, 5000/тиждень, 20000/місяць. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 МБ) | `src/shared/middleware/bodySizeGuard.ts` | Максимально допустимий розмір тіла запиту. Відхиляє корисні навантаження, що перевищують це обмеження. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 КБ) | `src/shared/middleware/chatBodyAdmission.ts` | Фактичні тіла запитів, розмір яких дорівнює цьому порогу або перевищує його, отримують атомарну локальну для процесу оренду допуску для ресурсоємних запитів перед розбором JSON (шлях BYTE, включно з `POST /v1/responses`). Застосовується той самий механізм виходу за наявності достатнього запасу ресурсів [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437), що й для структурно складних запитів; обмеження `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) усе ще діє, щоб проблема [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) не повернулася. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 МБ) | `src/shared/middleware/chatBodyAdmission.ts` | Жорстке обмеження для маршрутів чату, що застосовується до кількості байтів, прочитаних під час обмеженого приймання даних, зокрема для запитів із відсутнім, недійсним або неправдивим `Content-Length`; у разі перевищення повертається `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(не задано — без обмеження кількості запитів)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** це застаріле обмеження КІЛЬКОСТІ запитів тепер діє лише тоді, коли його задано явно. Якщо його не задано (типове значення), допуск великовагових запитів чату натомість обмежується `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — автоматично визначеним БАЙТОВИМ бюджетом, розрахованим на основі фактичного ліміту пам’яті процесу в межах **одного процесу** (однієї купи V8). Два паралельні запити `/v1/responses` приблизно по 750 тис. токенів аварійно завершують процес із купою ~12 ГіБ (#7849) — це попередження щодо **бюджету пам’яті**, а не жорсткий продуктовий максимум у 2 запити. Справний процес (купа нижче порога скидання) МОЖЕ допускати більше одночасних довгих запитів `/v1/responses` через `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Десятки довгих SSE-клієнтів (40–50) обмежуються купою + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, а не «максимумом 2». Сліпе підвищення цього значення, щоб «використати ресурси хоста», знову спричиняє #7849. Масштабуйте **купи** за допомогою **N незалежних `DATA_DIR`** (#11024); ніколи не використовуйте `replicas>1` для одного файлу SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(визначається автоматично)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** перевизначення автоматично визначеного байтового бюджету приймання даних (25% від меншого ліміту пам’яті V8/cgroup, поділені на 8-кратне тимчасове збільшення). Автоматично визначені та явно задані значення обмежуються діапазоном 8 МіБ–2 ГіБ. Тіло, розмір якого перевищує ефективний бюджет, одразу завершується помилкою `413 body_exceeds_budget`; конкуренція між тілами, кожне з яких окремо можна обслужити, залишається повторюваною помилкою `503`. 40–50 одночасних довгих SSE-клієнтів обмежуються цим бюджетом + купою, а не жорстким «максимумом 2». Перед налаштуванням перевірте `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` за адресою `/api/monitoring/health`. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Поріг скидання через тиск на купу (`heapUsed / heap_size_limit`) для допуску великовагових запитів за БАЙТАМИ і СТРУКТУРОЮ (#10183, #10268, #10437). Паралельний великоваговий запит понад `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` скидається з повторюваною помилкою `503` лише тоді, коли використання купи ТАКОЖ дорівнює цьому порогу або перевищує його; за нормального стану купи запит допускається через додатковий резерв. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (типово `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Обмежена додаткова місткість швидкого шляху за нормального стану купи (#10437) для допуску як за СТРУКТУРОЮ, так і за БАЙТАМИ (`admitChatRequest`, зокрема для тіл розміром ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Без цього обмеження кожен запит за високого навантаження, але нормального стану купи, оминав механізм допуску без будь-якої верхньої межі. Щойно через шлях для нормального стану купи активується така кількість одночасних оренд, подальші запити за високого навантаження переходять до ТОГО САМОГО шляху обмеженого очікування/скидання, що використовується за фактичного тиску на купу. `0` повністю вимикає обхід. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Кількість повідомлень, за якої запит чату класифікується як великоваговий, навіть якщо розмір його тіла менший за байтовий поріг. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Кількість інструментів, за якої запит чату класифікується як великоваговий, навіть якщо розмір його тіла менший за байтовий поріг. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Консервативна оцінка кількості токенів за розміром рядка, за якої запит класифікується як великоваговий; це наближена оцінка вартості допуску, а не токенізація для тарифікації постачальника. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (вимкнено) | `src/shared/middleware/chatBodyAdmission.ts` | Необов’язкове обмеження історії чату, яке вмикається явно. Типово вимкнено: кількість повідомлень є політикою розгортання, а не універсальною властивістю запиту, і обмеження на цьому етапі відхиляє розмови з остаточною помилкою `413`, перш ніж конвеєр стиснення зможе зробити їх придатними для обслуговування. Зростання купи обмежується `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` і скиданням через тиск на купу. У розгортаннях з обмеженою пам’яттю, де потрібна жорстка верхня межа, задайте додатне значення; у разі перевищення повертатиметься структурована помилка `413` із вимогою стиснення. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 МБ) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Жорстке обмеження для непотокової відповіді від вищого сервера, яка повністю буферизується в пам’яті. Після його перевищення читання відповіді від вищого сервера скасовується, а запит швидко завершується помилкою замість необмеженого збільшення рядка до вичерпання купи. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Максимальна кількість байтів, що передаються мережею із заголовків відповіді висхідного сервера. Коли бюджет перевищено, заголовки з нижчим пріоритетом (наприклад, користувацькі `x-codex-*`, `x-oai-request-id`) відкидаються, щоб не перевищувати поширені обмеження зворотних проксі на розмір заголовків. Установіть більше значення, щоб передавати більше метаданих висхідного сервера ціною збільшення розміру заголовків відповіді. | +| `CORS_ORIGIN` | _(не задано)_ | `src/server/cors/origins.ts` | Застарілий список дозволених джерел CORS для одного джерела. Для нових розгортань віддавайте перевагу `CORS_ALLOWED_ORIGINS`. CORS призначено лише для міждоменних браузерних клієнтів API; натомість автентифіковані операції запису з панелі керування використовують запити з того самого джерела та прив’язаний до сеансу захист CSRF. | +| `CORS_ALLOWED_ORIGINS` | _(не задано)_ | `src/server/cors/origins.ts` | Розділений комами список дозволених джерел CORS. Символ узагальнення не надсилається, якщо явно не налаштовано `CORS_ALLOW_ALL=true`. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Аварійний механізм лише для розробки, який дає змогу віддзеркалювати будь-який браузерний `Origin`. Не вмикайте у спільних або виробничих розгортаннях. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Застарілий псевдонім (alias). Його перемикач на панелі керування (перевизначення в базі даних) зчитується раніше за це значення середовища; `false`, `0`, `no` або `off` у будь-якому з них вимикає перевірки хостів так само, як це робить `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true`, навіть якщо перемикач цього прапорця на панелі збережено у вимкненому стані; інші значення залишають захист на розсуд двох прапорців нижче. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Вимикає перевірки хостів захисту вихідних URL-адрес, включно з блокуванням хмарних метаданих (усього діапазону 169.254.0.0/16, metadata.google.internal), на шляхах постачальників, що підпорядковуються режиму захисту: перевірка URL-адрес, виявлення моделей, базові URL-адреси вузлів постачальників (зокрема віддалені вузли повторного ранжування) і тест резервного проксі, а також інші вихідні шляхи, суміжні з постачальниками, як-от завантаження віддалених зображень/медіа. Також дозволяє приватні цілі webhook, за винятком кінцевих точок хмарних метаданих/link-local, які залишаються заблокованими навіть коли цей прапорець увімкнено. Запити чату, що надсилаються через `BaseExecutor.execute()`, зберігають свою перевірку хмарних метаданих. Локальні постачальники працюють із типовими значеннями: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (типово `true`) уже дозволяє локальні та LAN URL-адреси на шляхах перевірки, виявлення та вузлів постачальників; тест резервного проксі та приватні цілі webhook зважають лише на цей прапорець і блокують локальні/LAN-хости, доки він вимкнено. Перевизначає `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Значення, збережене з перемикача на панелі керування, має пріоритет над цією змінною. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Дозволяє URL-адреси постачальників за локальними або приватними адресами (127.0.0.1, localhost, LAN, приватні діапазони) для перевірки постачальників, виявлення моделей, чату та інших вихідних шляхів, суміжних з постачальниками, якими керує режим захисту. **Типове значення — `true`** (пріоритет локального використання): у цьому разі захист блокує кінцеві точки хмарних метаданих — увесь діапазон 169.254.0.0/16 плюс відомі імена хостів метаданих (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Установіть `false` для режиму «лише загальнодоступні», який блокує також приватні та loopback-хости. Захист перевіряє ім’я хоста або IP-літерал у тому вигляді, в якому воно записане, до будь-якого DNS-запиту; у режимі «лише загальнодоступні» обгортка safe-fetch додатково розв’язує ім’я хоста і відхиляє будь-яку приватну відповідь. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` вимикає ці перевірки на шляхах, перелічених у його рядку. Вбудовані локальні постачальники, як-от LM Studio, Ollama, vLLM, Llamafile і Triton, пропускають усі перевірки хостів для перевірки ключів і чату. Значення, збережене з перемикача на панелі керування, має пріоритет над цією змінною. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Дозволяє маршрутам `/v1/audio/*` (транскрипції, мовлення, переклади) використовувати сумісний з OpenAI вузол постачальника, розміщений поза localhost. Типово вимкнено — маршрутизація аудіо на віддалений хост змінює вихідну ідентичність і має бути явним рішенням оператора. Loopback- і приватні вузли (localhost, 127.0.0.1, 172.16-31.x) завжди дозволені, і це налаштування на них не впливає. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Дозволяє `POST /v1/rerank` (а також loopback-етап повторного ранжування рушія пам’яті) використовувати сумісний з OpenAI вузол постачальника, розміщений поза localhost — пристрій у LAN або вузол Tailscale, на якому працює TEI, Infinity, vLLM тощо. Типово вимкнено — маршрутизація на віддалений хост змінює вихідну ідентичність і має бути явним рішенням оператора. Loopback-вузли (localhost, 127.0.0.1, 172.16-31.x) завжди дозволені, і це налаштування на них не впливає. Базова URL-адреса віддаленого вузла також має відповідати політиці вихідних URL-адрес постачальника (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Коли OIDC увімкнено, вимикає вхід за паролем, щоб користувачі могли автентифікуватися лише через єдиний вхід OIDC. Також підтримується короткий псевдонім `OIDC_DISABLE_PASSWORD_LOGIN`; прапорець функції панелі керування з тим самим ключем має вищий пріоритет. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Короткий псевдонім для `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Контрольний список посилення захисту diff --git a/docs/i18n/ur/CONTRIBUTING.md b/docs/i18n/ur/CONTRIBUTING.md index c187e9a6857c..4ebb91f17982 100644 --- a/docs/i18n/ur/CONTRIBUTING.md +++ b/docs/i18n/ur/CONTRIBUTING.md @@ -388,20 +388,20 @@ OAuth اسناد `src/lib/oauth/constants/oauth.ts` میں اور سروس `src/ ## پُل ریکویسٹ چیک لسٹ -- [ ] ٹیسٹ کامیاب ہوں (`npm test`) -- [ ] لنٹنگ کامیاب ہو (`npm run lint`) -- [ ] بلڈ کامیاب ہو (`npm run build`) -- [ ] نئے پبلک فنکشنز اور انٹرفیسز کے لیے TypeScript ٹائپس شامل کی گئی ہوں -- [ ] کوئی ہارڈ کوڈ کردہ راز یا فال بیک ویلیوز نہ ہوں -- [ ] پبلک اپ اسٹریم اسناد `resolvePublicCred()` کے ذریعے شامل کی گئی ہوں ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) دیکھیں)، کبھی بھی لٹریلز کی صورت میں نہیں -- [ ] خرابی کے جوابات `buildErrorBody()` / `sanitizeErrorMessage()` کے ذریعے بھیجے جائیں — جوابی باڈیز میں کوئی خام اسٹیک ٹریس نہ ہو ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) دیکھیں) -- [ ] شیل کمانڈز (`exec` / `spawn`) رن ٹائم ویلیوز کو اسٹرنگ انٹرپولیشن کے بجائے `env` کے ذریعے پاس کریں +- [ ] ٹیسٹس کامیاب ہوں (`npm test`) +- [ ] لِنٹنگ کامیاب ہو (`npm run lint`) +- [ ] بِلڈ کامیاب ہو (`npm run build`) +- [ ] نئے عوامی فنکشنز اور انٹرفیسز کے لیے TypeScript ٹائپس شامل کی گئی ہوں +- [ ] کوئی ہارڈ کوڈ شدہ راز یا فال بیک اقدار نہ ہوں +- [ ] عوامی اپ اسٹریم اسناد `resolvePublicCred()` کے ذریعے شامل کی گئی ہوں ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) دیکھیں)، کبھی بھی لِٹرلز کے طور پر نہیں +- [ ] خرابی کے جوابات `buildErrorBody()` / `sanitizeErrorMessage()` کے ذریعے بھیجے جائیں — جوابی باڈیز میں خام اسٹیک ٹریسز نہ ہوں ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) دیکھیں) +- [ ] شیل کمانڈز (`exec` / `spawn`) رن ٹائم اقدار کو اسٹرنگ انٹرپولیشن کے بجائے `env` کے ذریعے منتقل کریں - [ ] تمام اِن پٹس کی Zod اسکیماؤں کے ذریعے توثیق کی گئی ہو -- [ ] صارف کو متاثر کرنے والی تبدیلیوں کے لیے `changelog.d/{features|fixes|maintenance}/-.md` کے تحت چینج لاگ **فریگمنٹ** شامل کیا گیا ہو ([`changelog.d/README.md`](./changelog.d/README.md) دیکھیں) — `CHANGELOG.md` میں براہِ راست ترمیم **نہ** کریں؛ فریگمنٹس کو ریلیز کے وقت یکجا کیا جاتا ہے اور ان میں مختلف PRs کے درمیان کبھی تصادم نہیں ہوتا +- [ ] صارف کو نظر آنے والی تبدیلیوں کے لیے `changelog.d/{features|fixes|maintenance}/-.md` کے تحت چینج لاگ **فریگمنٹ** شامل کیا گیا ہو ([`changelog.d/README.md`](./changelog.d/README.md) دیکھیں) — `CHANGELOG.md` میں براہِ راست ترمیم **نہ** کریں؛ فریگمنٹس کو ریلیز کے وقت یکجا کیا جاتا ہے اور ان میں مختلف پُل ریکویسٹس کے درمیان کبھی تنازع نہیں ہوتا - [ ] دستاویزات اپ ڈیٹ کی گئی ہوں (اگر قابلِ اطلاق ہو) -- [ ] کوئی نیا CodeQL / Secret-Scanning الرٹ نہ کھولا گیا ہو، یا ہر الرٹ کو متعلقہ `docs/security/` دستاویز کا حوالہ دینے والی تکنیکی توجیہ کے ساتھ مسترد کیا گیا ہو +- [ ] کوئی نیا CodeQL / Secret-Scanning الرٹ نہ کھلا ہو، یا ہر الرٹ کو متعلقہ `docs/security/` دستاویز کا حوالہ دیتے ہوئے تکنیکی جواز کے ساتھ مسترد کیا گیا ہو - [ ] چائلڈ پراسیسز شروع کرنے والے روٹس (`/api/mcp/`، `/api/cli-tools/runtime/`) کو `src/server/authz/routeGuard.ts` میں `isLocalOnlyPath()` کے طور پر درجہ بند کیا گیا ہو — [سخت اصول #15](docs/security/ROUTE_GUARD_TIERS.md) دیکھیں -- [ ] کمٹ پیغامات میں `Co-Authored-By` ٹریلرز نہ ہوں — کمٹس صرف ریپوزٹری مالک کی Git شناخت کے تحت ظاہر ہونے چاہییں (سخت اصول #16) +- [ ] کمٹ پیغامات میں AI/بوٹ کے `Co-authored-by` ٹریلرز نہ ہوں (سخت اصول #16) — جن انسانی معاونین کا کام دوبارہ استعمال کیا گیا ہو، انہیں معیاری `Co-authored-by: Name ` ٹریلرز کے ذریعے کریڈٹ دیا جائے --- diff --git a/docs/i18n/ur/docs/architecture/QUALITY_GATES.md b/docs/i18n/ur/docs/architecture/QUALITY_GATES.md index fd353d881d15..f9898e4956ab 100644 --- a/docs/i18n/ur/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/ur/docs/architecture/QUALITY_GATES.md @@ -15,53 +15,53 @@ ratchet baseline یا pass/fail پالیسی استعمال کرتا ہے، او --- -## گیٹس کی فہرست اور نفاذی پروفائلز +## گیٹس کی فہرست اور عمل درآمد کی پروفائلز ### امیدوار کی منظوری CI اور Quality Gates ورک فلوز میں سے ہر ایک ایک مستحکم فیصلہ جاری کرتا ہے: `Gate / CI` اور -`Gate / Quality`۔ ان کی ورژن شدہ منظوری پالیسی ہر upstream job کو لازمی یا مشاورتی -کے طور پر درج کرتی ہے۔ قابلِ اطلاق لازمی job کا کامیاب ہونا ضروری ہے: غائب، -منسوخ، نظر انداز شدہ، زیرِ التوا اور نامعلوم نتائج PASS ثابت نہیں کر سکتے۔ صرف -دستاویزات یا صرف کیٹلاگ کی درست درجہ بندی کسی code lane کو ناقابلِ اطلاق بنا سکتی ہے؛ -draft PR قابلِ قبول امیدوار نہیں ہے۔ `hotfix` لیبل ثبوت کی شرط ختم نہیں کرتا۔ +`Gate / Quality`۔ ان کی ورژن شدہ منظوری پالیسی ہر upstream job کو +لازمی یا مشاورتی کے طور پر درج کرتی ہے۔ کسی قابل اطلاق لازمی job کا کامیاب ہونا ضروری ہے: غیر موجود، +منسوخ شدہ، چھوڑے گئے، زیر التوا اور نامعلوم نتائج PASS ثابت نہیں کر سکتے۔ صرف docs +یا صرف catalog کی درست درجہ بندی کسی code lane کو ناقابل اطلاق بنا سکتی ہے؛ +draft PR قابل قبول امیدوار نہیں ہے۔ `hotfix` لیبل شواہد کی ضرورت ختم نہیں کرتا۔ دونوں ورک فلوز PRs اور main/release برانچز پر pushes، manual dispatch اور merge-group events کا احاطہ کرتے ہیں۔ Push، dispatch اور merge-group مکمل انتخاب چلاتے ہیں۔ Forks -اور merge groups ان jobs کے لیے hosted runners استعمال کرتے ہیں جو بصورتِ دیگر self-hosted -runners منتخب کرتے؛ rollout سے پہلے کافی hosted capacity کی تصدیق ضروری ہے۔ +اور merge groups ان jobs کے لیے hosted runners استعمال کرتے ہیں جو بصورت دیگر self-hosted +runners منتخب کرتے؛ rollout سے پہلے hosted capacity کی کفایت کی تصدیق ضروری ہے۔ -ہر JSON رسید checked-out SHA، workflow run اور attempt کی شناخت کرتی ہے۔ -CLI checkout/event SHA کے عدم مطابقت کو مسترد کرتا ہے۔ Workflow tests پالیسی کی رکنیت کو +ہر JSON receipt چیک آؤٹ شدہ SHA، workflow run اور attempt کی شناخت کرتی ہے۔ +CLI checkout/event SHA کے عدم تطابق کو مسترد کرتا ہے۔ Workflow tests پالیسی کی رکنیت کو verdict job کی `needs` فہرست سے منسلک کرتے ہیں تاکہ کوئی نئی یا حذف شدہ lane خاموشی سے غائب نہ ہو سکے۔ -رسیدیں اپنے متعلقہ workflow کا احاطہ کرتی ہیں، publication، deployment، یا کسی موجودہ -مشاورتی scanner کے اندرونی حصوں کا نہیں۔ Branch rules میں دونوں check names فعال کرنا ایک -علیحدہ انتظامی تبدیلی ہے؛ ان jobs کو شامل کرنا بذاتِ خود کسی branch کو محفوظ نہیں کرتا۔ +Receipts اپنے workflow کا احاطہ کرتی ہیں، publication، deployment یا کسی موجودہ +مشاورتی scanner کے اندرونی حصوں کا نہیں۔ Branch rules میں دونوں check names کو فعال کرنا +ایک الگ انتظامی تبدیلی ہے؛ ان jobs کو شامل کرنا بذات خود کسی branch کو محفوظ نہیں کرتا۔ ### Static scan کی فہرست -ورژن شدہ npm-alias فہرست اور static-scan رکنیت +ورژن شدہ npm-alias فہرست اور static-scan کی رکنیت `config/quality/gate-manifest.json` میں موجود ہیں۔ Script names اور عین commands کو `package.json` کے مقابل validate کرنے کے لیے `npm run check:gate-manifest` چلائیں؛ اضافے، اخراج اور command drift مقامی hook اور CI میں change-classification jobs دونوں کو ناکام کر دیتے ہیں۔ -کوئی alias، workflow job، matrix instance یا test case نہیں ہوتا: ان کی تعداد کو -باہم قابلِ تبادلہ کے طور پر پیش نہیں کیا جانا چاہیے۔ +کوئی alias، workflow job، matrix instance یا test case نہیں ہوتا: ان اعداد کو +باہم قابل تبادلہ کے طور پر پیش نہیں کیا جانا چاہیے۔ -منتخب aliases کو چلائے بغیر دیکھنے کے لیے `npm run quality:scan -- --list` یا +منتخب aliases کو چلائے بغیر ان کا جائزہ لینے کے لیے `npm run quality:scan -- --list` یا `npm run quality:scan:fast -- --list` استعمال کریں۔ Runner، -npm entrypoint کو invoke کرتا ہے، اس لیے اس کا runtime (بشمول Bun، جہاں configure کیا گیا ہو) برقرار رہتا ہے۔ -Manifest ان profiles سے باہر موجود aliases کو علیحدہ طور پر invoke کیے جانے والے aliases کے طور پر درج کرتا ہے، اور +npm entrypoint کو چلاتا ہے، اس لیے اس کا runtime (بشمول ان مقامات کے جہاں Bun ترتیب دیا گیا ہو) برقرار رہتا ہے۔ +Manifest ان profiles سے باہر کے aliases کو الگ سے چلائے جانے والے aliases کے طور پر درج کرتا ہے، اور read-only scan profiles میں maintenance commands ممنوع ہیں۔ -یہ profiles صرف static scan کا احاطہ کرتے ہیں۔ یہ product tests، -coverage، packaging، external checks یا کسی امیدوار کی مکمل release acceptance کی تصدیق نہیں کرتے۔ +یہ profiles صرف static scan کا احاطہ کرتی ہیں۔ یہ product tests، +coverage، packaging، external checks یا کسی امیدوار کی مکمل release acceptance کی تصدیق نہیں کرتیں۔ Workflow admission منسلک `config/quality/admission-policy.json` اور -`scripts/quality/admission-verdict.mjs` استعمال کرتا ہے۔ Release-observer profiles علیحدہ رہتے ہیں؛ -ان کے قابلِ اطلاق checks اور receipts کا آزادانہ معائنہ کریں۔ ذیل کی نثری +`scripts/quality/admission-verdict.mjs` استعمال کرتی ہے۔ Release-observer profiles الگ رہتی ہیں؛ +ان کے قابل اطلاق checks اور receipts کا آزادانہ جائزہ لیں۔ ذیل میں دی گئی نثری فہرست ایک حوالہ ہے، اس بات کا ثبوت نہیں کہ کوئی gate حقیقتاً چلا تھا۔ Scripts، `scripts/check/` (policy gates) اور `scripts/quality/` (ratchet engine) کے تحت موجود ہیں۔ -CI کا حتمی ماخذ `.github/workflows/ci.yml` ہے۔ +CI کے لیے اصل ماخذ `.github/workflows/ci.yml` ہے۔ ### Release PR کا تیز راستہ (`quality.yml`) @@ -69,154 +69,154 @@ CI کا حتمی ماخذ `.github/workflows/ci.yml` ہے۔ pushes، dispatch اور merge groups پر CI کی تکمیل کرتا ہے۔ PRs، path-filtered تیز checks استعمال کرتے ہیں۔ مستقل طور پر غیر فعال duplicate build ہٹا دیا گیا؛ حقیقی build/package/boot checks بدستور CI میں موجود ہیں۔ -| Job | دائرۂ کار | مسدود کنندہ | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | Docs/code PRs؛ API docs refs اور docs-all | ہاں | -| `Fast Quality Gates` | Code PRs؛ static checks، typecheck، dashboard typecheck، متاثرہ unit tests | ہاں | -| `Forgotten sibling tests` | Code PRs؛ تبدیل شدہ modules کو static consumers اور ممکنہ sibling tests تک trace کیا جاتا ہے؛ barrel اور dynamic-import paths کو مشاورتی diagnostics کے طور پر رپورٹ کیا جاتا ہے، جن میں حوالہ شدہ allowlist exceptions شامل ہیں | **مشاورتی** | -| `Vitest (fast-path)` | Code PRs؛ تیز vitest suite | ہاں | -| `Unit Tests fast-path` | Code PRs؛ 4-shard unit suite | ہاں | -| `No new ESLint warnings` | Code PRs؛ suppressions-aware lint guard | ہاں، بشمول forks | -| `Merge integrity (changelog + generated skills)` | Non-draft PRs؛ changelog اور generated skill sync | ہاں، بشمول forks | +| Job | دائرۂ کار | Blocking | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `Docs Gates (fast-path)` | Docs/code PRs؛ API docs refs اور docs-all | ہاں | +| `Fast Quality Gates` | Code PRs؛ static checks، typecheck، dashboard typecheck، متاثرہ unit tests | ہاں | +| `Forgotten sibling tests` | Code PRs؛ تبدیل شدہ modules سے static consumers اور ممکنہ sibling tests تک سراغ؛ barrel اور dynamic-import کے راستے مشاورتی تشخیصی معلومات کے طور پر رپورٹ کیے جاتے ہیں، جن میں حوالہ شدہ allowlist استثنا شامل ہیں | **مشاورتی** | +| `Vitest (fast-path)` | Code PRs؛ تیز vitest suite | ہاں | +| `Unit Tests fast-path` | Code PRs؛ 4-shard unit suite | ہاں | +| `No new ESLint warnings` | Code PRs؛ suppressions-aware lint guard | ہاں، forks سمیت | +| `Merge integrity (changelog + generated skills)` | غیر draft PRs؛ changelog اور generated skill sync | ہاں، forks سمیت | #### بھولے ہوئے sibling tests کی رپورٹ -`npm run check:forgotten-sibling-tests`، test-impact map کے پسِ پشت import resolver کو دوبارہ استعمال کرتا ہے۔ +`npm run check:forgotten-sibling-tests`، test-impact map کے پس منظر میں موجود import resolver کو دوبارہ استعمال کرتا ہے۔ ہر تبدیل شدہ production module کے لیے، جب ممکنہ test، pull-request diff میں موجود نہ ہو تو یہ قطعی -`changed module/symbol -> static consumer -> candidate sibling test` سلسلوں کی رپورٹ دیتا ہے۔ Markdown summary اور JSON result کو -کسی بھی blocking rollout سے پہلے calibration کے لیے `forgotten-sibling-tests` workflow artifact کے طور پر محفوظ رکھا جاتا ہے۔ - -Barrel ری ایکسپورٹس اور ڈائنامک امپورٹس صرف ریزولوشن کی تشخیص کے لیے ہیں؛ یہ کبھی بھی -بلاک کرنے والی نشاندہی پیدا نہیں کرتے۔ نظرثانی شدہ استثنا -`config/quality/forgotten-sibling-allowlist.json` میں موجود ہیں۔ ہر اندراج میں صارف اور امیدوار -ٹیسٹ کا نام، ایک مخصوص وجہ، اور GitHub issue یا pull request کا لنک ہونا ضروری ہے۔ ناقص اندراجات -بند حالت میں ناکام ہوتے ہیں۔ استثنا حذف شدہ امیدوار ٹیسٹ یا ایسی diff کو نظرانداز نہیں کر سکتے جو `.skip`/`.todo` -شامل کرتی ہو؛ assertions کو کمزور کرنا اور دیگر پردہ پوشی بدستور آزادانہ طور پر بلاک کرنے والے -`check:test-masking` گیٹ کی ذمہ داری ہیں۔ - -### جاب: `lint` - -`main` کے لیے ہر PR پر چلتا ہے۔ ناکامی کی صورت میں مرج کو بلاک کرتا ہے۔ - -| اسکرپٹ (`npm run ...`) | توثیق | بلاک کرنے والا | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:node-runtime` | Node.js کا ورژن معاونت یافتہ حد کے اندر ہے | ہاں | -| `check:cycles` | سرکلر امپورٹس — تمام `src/` + `open-sse/` ماڈیولز | ہاں | -| `check:route-validation:t06` | تمام روٹس پر Zod اسکیمے موجود ہیں (درجہ 6 کی پالیسی) | ہاں | -| `check:any-budget:t11` | `@ts-expect-error // any` کی تعداد بجٹ سے زیادہ نہیں ہے (درجہ 11 catraca) | ہاں | -| `check:provider-consistency` | `providers.ts` میں موجود ہر provider کا `providerRegistry.ts` میں مماثل اندراج موجود ہے (اور اس کے برعکس بھی، allowlist کے اندر) | ہاں | -| `check:model-lifecycle` | ہاتھ سے برقرار رکھی جانے والی تین routing tables، checked-in lifecycle snapshot (#11503) کے ساتھ ہم آہنگ رہتی ہیں: `FITNESS_TABLE` (`taskFitness.ts`) کسی ایسے retired id کو score نہیں کرتی جسے `REGISTRY` route کر سکتی ہو؛ ہر `BUILT_IN_ALIASES` target، `REGISTRY` میں موجود اور retired-id snapshot سے غیر موجود ہے؛ `REGISTRY` میں اب بھی موجود ہر retired id کو forward کیا جاتا ہے یا `allowedRetiredInCatalog` میں درج کیا جاتا ہے؛ اور کوئی بھی `DEFAULT_DEGRADATION_MAP` source یا target اس snapshot میں retired ظاہر نہیں ہوتا۔ اس سے یہ ثابت نہیں ہوتا کہ کوئی model فی الحال live upstream کے ذریعے فراہم کیا جا رہا ہے۔ Offline — `config/quality/model-lifecycle.json` کے ساتھ موازنہ کرتا ہے، جسے `npm run quality:refresh-model-lifecycle` کے ذریعے دستی طور پر تازہ کیا جاتا ہے (network؛ CI میں شامل نہیں)۔ `allowedRetiredInCatalog` ایک burn-down ratchet ہے: کوئی اندراج صرف tracking issue کے ساتھ شامل کریں۔ | ہاں | -| `check:fetch-targets` | client-side `src/` میں موجود ہر `fetch("/api/...")` ایک حقیقی `route.ts` تک resolve ہوتا ہے | ہاں | -| `check:deps` | repo میں موجود ہر `package.json` کی تمام `npm install` کے قابل deps، `dependency-allowlist.json` میں موجود ہیں؛ نئے unpinned یا slopsquatted packages کو flag کیا جاتا ہے | ہاں | -| `audit:deps` | `npm audit` (root + electron) — کوئی high/critical advisory نہیں (osv `check:vuln-ratchet` کے ساتھ overlap؛ Rationalization Backlog دیکھیں) | ہاں | -| `check:lockfile` | `package-lock.json` کی integrity — https registry، integrity hashes، کوئی host overrides نہیں | ہاں | -| `check:licenses` | پروڈکشن dependencies کے لیے SPDX license allowlist | ہاں | -| `check:tracked-artifacts` | کوئی build artifacts / committed `node_modules` symlinks نہیں (husky pre-commit میں بھی چلتا ہے؛ pre-push کو جان بوجھ کر ہلکا رکھا گیا ہے — #6716) | ہاں | -| `check:ai-attribution` | PR commits، title یا body میں کوئی AI/bot `Co-Authored-By` trailer یا AI-generation footer نہیں — سخت اصول #16 (`quality.yml` کے PR→`release/**` fast-gates loop میں — event payload پڑھتا ہے، PRs کے علاوہ no-op رہتا ہے — اور PR→`main` کے لیے `ci.yml` lint میں صرف PR والا step؛ نیز husky `commit-msg` hook؛ انسانی co-authors کی اجازت ہے؛ #14436) | -| `check:vitest-exclusions` | ہر Vitest exclusion میں tracking issue درج ہو اور وہ `config/quality/vitest-exclusions.json` میں موجود ہو (#13204) | ہاں | -| `check:file-size` | کوئی source file فی-extension حد سے تجاوز نہ کرے (ratchet: بڑی منجمد files، `frozen` list میں) | ہاں | -| `check:error-helper` | executors/handlers میں error responses، `buildErrorBody()` / `sanitizeErrorMessage()` استعمال کریں (سخت اصول #12) | ہاں | -| `check:migration-numbering` | Migration SQL فائلیں مسلسل نمبروں کے ساتھ ہوں، کوئی خلا یا نقل شدہ نمبر نہ ہو | ہاں | -| `check:public-creds` | `publicCreds.ts` کے علاوہ کہیں بھی OAuth کے لفظی `client_id`/`client_secret` یا Firebase Web keys موجود نہ ہوں (سخت اصول #11) | ہاں | -| `check:db-rules` | `src/lib/db/` ماڈیولز کے علاوہ کہیں بھی خام SQL موجود نہ ہو؛ `localDb.ts` سے کوئی barrel-imports نہ ہوں (سخت اصول #2/#5) | ہاں | -| `check:known-symbols` | اپنی dispatch tables میں رجسٹر شدہ provider executors، routing strategies، اور translators ڈسک پر موجود فائلوں سے مطابقت رکھتے ہوں — کوئی غیر منسلک یا غیر اعلان شدہ symbols نہ ہوں | ہاں | -| `check:route-guard-membership` | child process شروع کرنے والا ہر route، `isLocalOnlyPath()` کے ذریعے درجہ بند کیا گیا ہو (سخت اصول #15/#17) | ہاں | -| `check:test-discovery` | repo میں موجود ہر `*.test.ts` / `*.spec.ts` فائل کم از کم ایک test runner کے ذریعے جمع کی جائے (ratchet: `test-discovery-baseline.json` میں orphan list صرف کم ہو سکتی ہے) | ہاں | -| `check:agent-skills-sync` | تیار کردہ agent-skills artifacts اپنے ماخذ catalog سے مطابقت رکھتے ہیں (کوئی drift نہیں) | -| `check:provider-asset-provenance` | Provider logos/assets کے ساتھ provenance کا اندراج ریکارڈ شدہ ہے | -| `lint:json` | JSON config files parse ہوتی ہیں اور repo کے lint rules کو پورا کرتی ہیں | -| `typecheck:core` | TypeScript compilation بغیر errors کے (صرف advisory warnings) | ہاں | -| `typecheck:noimplicit:core` | سخت `noImplicitAny` — مستقبل کو مدنظر رکھنے والا؛ پہلے سے موجود بہت سی call sites کو اب بھی annotations درکار ہیں | **مشاورتی** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` کا دائرہ `src/app/(dashboard)/**` تک محدود ہے (#7033) — `typecheck:core` کی منتخب کردہ 27-file allowlist میں کوئی dashboard TSX شامل نہیں، اور `next build` بھی کبھی اس کی type-checking نہیں کرتا (`next.config.mjs` میں `ignoreBuildErrors: true` مقرر ہے)، اس لیے وہاں orphaned-identifier regressions (#6625/#6909) CI کو نظر نہیں آتی تھیں۔ ایک منجمد، فی file/فی TS-code count baseline (`config/quality/dashboard-typecheck-baseline.json`، وہی stale-enforcement pattern جو `check:known-symbols` میں ہے) کے مقابلے میں diffs — صرف baseline میں موجود count سے زیادہ نئے errors gate کو ناکام کرتے ہیں؛ پہلے سے موجود error درست ہونے پر `--update` کے ذریعے اسے بتدریج کم کریں۔ | ہاں | +`changed module/symbol -> static consumer -> candidate sibling test` سلسلے رپورٹ کرتا ہے۔ Markdown summary اور JSON result کو +کسی blocking rollout سے پہلے calibration کے لیے `forgotten-sibling-tests` workflow artifact کے طور پر برقرار رکھا جاتا ہے۔ + +Barrel re-exports اور dynamic imports صرف resolution diagnostics ہیں؛ یہ کبھی بھی +blocking finding پیدا نہیں کرتے۔ جائزہ شدہ استثنا +`config/quality/forgotten-sibling-allowlist.json` میں موجود ہوتے ہیں۔ ہر اندراج میں consumer اور candidate +test کا نام، ایک مخصوص توجیہ، اور GitHub issue یا pull request کا لنک لازماً ہونا چاہیے۔ ناقص اندراجات +fail closed ہوتے ہیں۔ استثنا کسی حذف شدہ candidate test یا `.skip`/`.todo` شامل کرنے والے diff کو suppress نہیں کر سکتے؛ +assertion weakening اور دیگر masking بدستور آزادانہ طور پر blocking +`check:test-masking` gate کے دائرۂ اختیار میں رہتے ہیں۔ + +### Job: `lint` + +`main` کے ہر PR پر چلتا ہے۔ ناکامی کی صورت میں merge کو روکتا ہے۔ + +| Script (`npm run ...`) | توثیق کرتا ہے | Blocking | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Node.js کا version معاونت یافتہ range کے اندر ہے | ہاں | +| `check:cycles` | پورے `src/` + `open-sse/` میں circular imports (AST پر مبنی، tsconfig کے `paths` resolve کیے جاتے ہیں)۔ Bare = advisory، cycles کی فہرست دکھاتا ہے۔ `check:cycles:ratchet` (جسے CI چلاتا ہے) اس وقت block کرتا ہے جب تعداد `quality-baseline.json` میں `metrics.cycles` کی حد سے تجاوز کرے — فی الحال 14، `direction: down`، لہٰذا یہ صرف کم ہو سکتی ہے (#15159 G-01/G-02) | ہاں (ratchet) | +| `check:route-validation:t06` | تمام routes پر Zod schemas موجود ہیں (Tier 6 policy) | ہاں | +| `check:any-budget:t11` | `@ts-expect-error // any` کی تعداد budget سے تجاوز نہیں کرتی (Tier 11 catraca) | ہاں | +| `check:provider-consistency` | `providers.ts` میں موجود ہر provider کے لیے `providerRegistry.ts` میں مماثل اندراج موجود ہے (اور اس کے برعکس بھی، allowlist کے اندر) | ہاں | +| `check:model-lifecycle` | ہاتھ سے برقرار رکھی گئی تین routing tables، checked-in lifecycle snapshot (#11503) کے ساتھ مطابقت رکھتی ہیں: `FITNESS_TABLE` (`taskFitness.ts`) کسی ایسے retired id کو اسکور نہیں کرتی جسے `REGISTRY` route کر سکتی ہو؛ ہر `BUILT_IN_ALIASES` target، `REGISTRY` میں موجود اور retired-id snapshot میں غیر موجود ہے؛ `REGISTRY` میں اب بھی موجود ہر retired id کو forward کیا گیا ہے یا وہ `allowedRetiredInCatalog` میں درج ہے؛ اور کوئی `DEFAULT_DEGRADATION_MAP` source یا target اس snapshot میں retired نظر نہیں آتا۔ اس سے یہ ثابت نہیں ہوتا کہ کوئی model فی الحال کسی live upstream کے ذریعے فراہم کیا جا رہا ہے۔ Offline — `config/quality/model-lifecycle.json` کے ساتھ موازنہ کرتا ہے، جسے `npm run quality:refresh-model-lifecycle` کے ذریعے دستی طور پر refresh کیا جاتا ہے (network؛ CI سے منسلک نہیں)۔ `allowedRetiredInCatalog` ایک burn-down ratchet ہے: کوئی اندراج صرف tracking issue کے ساتھ شامل کریں۔ | ہاں | +| `check:fetch-targets` | client-side `src/` میں موجود ہر `fetch("/api/...")` ایک حقیقی `route.ts` تک resolve ہوتا ہے | ہاں | +| `check:deps` | repo میں موجود ہر `package.json` کی تمام `npm install` کے قابل deps، `dependency-allowlist.json` میں موجود ہیں؛ نئے unpinned یا slopsquatted packages کو نشان زد کیا جاتا ہے | ہاں | +| `audit:deps` | `npm audit` (root + electron) — کوئی high/critical advisory نہیں (osv کے `check:vuln-ratchet` کے ساتھ overlap کرتا ہے؛ Rationalization Backlog دیکھیں) | ہاں | +| `check:lockfile` | `package-lock.json` کی سالمیت — https registry، integrity hashes، کوئی host override نہیں | ہاں | +| `check:licenses` | پروڈکشن dependencies کے لیے SPDX license allowlist | ہاں | +| `check:tracked-artifacts` | کوئی build artifacts / committed `node_modules` symlinks نہیں (husky pre-commit میں بھی چلتا ہے؛ pre-push کو دانستہ طور پر ہلکا رکھا گیا ہے — #6716) | ہاں | +| `check:ai-attribution` | PR commits، title یا body میں کوئی AI/bot `Co-Authored-By` trailer یا AI-generation footer نہیں — سخت اصول #16 (`quality.yml` کے fast-gates loop میں PR→`release/**` کے لیے — event payload پڑھتا ہے، PRs کے علاوہ کوئی کارروائی نہیں کرتا — اور `ci.yml` lint میں PR→`main` کے لیے صرف PR والا مرحلہ؛ نیز husky `commit-msg` hook؛ انسانی co-authors کی اجازت ہے؛ #14436) | +| `check:vitest-exclusions` | ہر Vitest exclusion میں tracking issue درج ہو اور وہ `config/quality/vitest-exclusions.json` میں موجود ہو (#13204) | ہاں | +| `check:file-size` | کوئی source file فی-extension مقررہ حد سے تجاوز نہ کرے (ratchet: بڑی frozen files، `frozen` فہرست میں) | ہاں | +| `check:error-helper` | executors/handlers میں error responses، `buildErrorBody()` / `sanitizeErrorMessage()` استعمال کریں (سخت اصول #12) | ہاں | +| `check:migration-numbering` | مائیگریشن SQL فائلیں تسلسل کے ساتھ نمبر شدہ ہیں، ان میں کوئی خلا یا تکرار نہیں | ہاں | +| `check:public-creds` | `publicCreds.ts` سے باہر کوئی لفظی OAuth `client_id`/`client_secret` یا Firebase Web کلیدیں موجود نہیں (سخت اصول #11) | ہاں | +| `check:db-rules` | `src/lib/db/` ماڈیولز سے باہر کوئی خام SQL موجود نہیں؛ `localDb.ts` سے کوئی barrel-imports نہیں (سخت اصول #2/#5) | ہاں | +| `check:known-symbols` | اپنی dispatch tables میں رجسٹر شدہ provider executors، routing strategies، اور translators ڈسک پر موجود فائلوں سے مطابقت رکھتے ہیں — کوئی غیر منسلک یا غیر اعلان شدہ symbols نہیں | ہاں | +| `check:route-guard-membership` | child process شروع کرنے والا ہر route، `isLocalOnlyPath()` کے ذریعے درجہ بند ہے (سخت اصول #15/#17) | ہاں | +| `check:test-discovery` | repo میں موجود ہر `*.test.ts` / `*.spec.ts` فائل کم از کم ایک test runner کے ذریعے اکٹھی کی جاتی ہے (ratchet: `test-discovery-baseline.json` میں orphan list صرف کم ہو سکتی ہے) | ہاں | +| `check:agent-skills-sync` | تیار کردہ agent-skills آرٹیفیکٹس اپنے ماخذ کیٹلاگ سے مطابقت رکھتے ہیں (کوئی انحراف نہیں) | +| `check:provider-asset-provenance` | فراہم کنندہ کے لوگوز/اثاثوں کے ساتھ ماخذ کی ایک ریکارڈ شدہ اندراج موجود ہے | +| `lint:json` | JSON کنفیگریشن فائلیں درست طور پر پارس ہوتی ہیں اور repo کے lint قواعد پر پورا اترتی ہیں | +| `typecheck:core` | TypeScript کی بغیر خامیوں کے compilation (صرف مشاورتی انتباہات) | ہاں | +| `typecheck:noimplicit:core` | سخت `noImplicitAny` — مستقبل پر مرکوز؛ پہلے سے موجود بہت سے call sites کو اب بھی annotations درکار ہیں | **مشاورتی** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` کا دائرہ `src/app/(dashboard)/**` (#7033) تک محدود ہے — `typecheck:core` کی منتخب کردہ 27 فائلوں کی allowlist میں dashboard کی کوئی TSX فائل شامل نہیں، اور `next build` بھی کبھی اس کی type-checking نہیں کرتا (`next.config.mjs` میں `ignoreBuildErrors: true` مقرر ہے)، اس لیے وہاں orphaned-identifier regressions (#6625/#6909) CI کو دکھائی نہیں دیتی تھیں۔ ایک منجمد فی فائل/فی TS-code تعداد کی baseline (`config/quality/dashboard-typecheck-baseline.json`، وہی stale-enforcement طرز جو `check:known-symbols` میں ہے) کے مقابلے میں diffs — baseline تعداد سے زیادہ صرف نئی خامیاں gate کو ناکام کرتی ہیں؛ پہلے سے موجود خامی درست ہونے پر `--update` کے ذریعے baseline کم کریں۔ | ہاں | ### Job: `quality-gate` `test-coverage` کے بعد چلتا ہے۔ ناکامی کی صورت میں merge کو روکتا ہے۔ -| اسکرپٹ | توثیق کرتا ہے | بلاک کرنے والا | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `quality:collect` | `quality-metrics.json` تیار کرتا ہے (ESLint انتباہات کی تعداد، ضم شدہ shard رپورٹ سے کوریج) | ہاں (ratchet سے پہلے) | -| `quality:ratchet` | `quality-baseline.json` میں ہر میٹرک میں تنزلی نہیں ہوئی (ESLint انتباہات ≤ baseline؛ کوریج ≥ baseline) | ہاں | -| `check:duplication` | کوڈ کی نقل (jscpd@4) `quality-baseline.json` میں موجود baseline سے تجاوز نہیں کرتی | ہاں | -| `check:complexity` | فائل کی سطح کی cyclomatic complexity مقررہ حد سے تجاوز نہیں کرتی (بنیادی ESLint `complexity` + `max-lines-per-function`) | ہاں | -| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — علیحدہ ESLint مرحلہ؛ CI دونوں کو واحد `check:complexity-ratchets` مرحلے کے طور پر ضم کر کے چلاتا ہے | ہاں | -| `check:dead-code` | غیر استعمال شدہ exports / فائلوں کا ratchet (knip)، baseline کے مقابلے میں تنزلی نہیں کرتا | ہاں | -| `check:compression-budget` | کمپریشن benchmark بجٹ — ہر engine کے token-savings کی کم از کم حدوں میں تنزلی نہیں ہونی چاہیے | ہاں | -| `check:type-coverage` | ٹائپ شدہ فیصد کا ratchet (`type-coverage`) تنزلی نہیں کرتا؛ بڑی حد تک `typecheck:noimplicit:core` کو شامل کر لیتا ہے | ہاں | -| `check:codeql-ratchet` | کھلے CodeQL alerts کی تعداد میں اضافہ نہیں ہوتا (`gh api` کے ذریعے پڑھتا ہے؛ token کے بغیر بحفاظت نظرانداز کرتا ہے) — تازہ کاری کی مدت اور دستی trigger کے لیے: ذیل میں "CodeQL ratchet" دیکھیں | ہاں | +| اسکرپٹ | توثیق | رکاوٹ بننے والا | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | +| `quality:collect` | `quality-metrics.json` بناتا ہے (ESLint انتباہات کی تعداد، ضم شدہ shard رپورٹ سے کوریج) | ہاں (ratchet سے پہلے) | +| `quality:ratchet` | `quality-baseline.json` میں ہر میٹرک میں تنزلی نہیں ہوئی (ESLint انتباہات ≤ baseline؛ کوریج ≥ baseline) | ہاں | +| `check:duplication` | کوڈ کی نقل (jscpd@4) `quality-baseline.json` میں موجود baseline سے تجاوز نہیں کرتی | ہاں | +| `check:complexity` | فائل کی سطح کی cyclomatic complexity مقررہ حد سے تجاوز نہیں کرتی (بنیادی ESLint `complexity` + `max-lines-per-function`) | ہاں | +| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — علیحدہ ESLint مرحلہ؛ CI دونوں کو ایک `check:complexity-ratchets` مرحلے کے طور پر ضم کر کے چلاتا ہے | ہاں | +| `check:dead-code` | غیر استعمال شدہ exports / فائلوں کا ratchet (knip)، baseline کے مقابلے میں تنزلی کا شکار نہیں ہوتا | ہاں | +| `check:compression-budget` | Compression benchmark کا بجٹ — ہر engine کے لیے token-savings کی کم از کم حدود میں تنزلی نہیں ہونی چاہیے | ہاں | +| `check:type-coverage` | فیصدی typed ratchet (`type-coverage`) میں تنزلی نہیں ہوتی؛ بڑی حد تک `typecheck:noimplicit:core` کا احاطہ کرتا ہے | ہاں | +| `check:codeql-ratchet` | کھلے CodeQL alerts کی تعداد میں اضافہ نہیں ہوتا (`gh api` کے ذریعے پڑھتا ہے؛ token کے بغیر مناسب طور پر skip کرتا ہے) — تازہ کاری کی مدت اور دستی trigger: ذیل میں "CodeQL ratchet" دیکھیں | ہاں | ### جاب: `quality-extended` -پوری جاب مشاورتی ہے (`continue-on-error: true`)۔ npm پر مبنی ratchets حقیقی طور پر -چلتے ہیں؛ بیرونی scanners، `gh release download` کے ذریعے انسٹال ہوتے ہیں اور جب -binary پھر بھی موجود نہ ہو تو خود بخود نظرانداز ہو جاتے ہیں (exit 0)۔ +مکمل جاب مشاورتی ہے (`continue-on-error: true`)۔ npm پر مبنی ratchets حقیقتاً +چلتے ہیں؛ بیرونی scanners، `gh release download` کے ذریعے install ہوتے ہیں اور جب +binary پھر بھی موجود نہ ہو تو خود skip ہو جاتے ہیں (exit 0)۔ -| اسکرپٹ | توثیق کرتا ہے | بلاک کرنے والا | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | -| `check:circular-deps` | کوئی circular dependencies نہیں (dpdm) | **مشاورتی** | -| `check:bundle-size` | bundle کا حجم مقررہ حد سے تجاوز نہیں کرتا | **مشاورتی** | -| `check:secrets` | secrets کی scanning (gitleaks) — binary موجود نہ ہو تو نظرانداز کرتا ہے | **مشاورتی** | -| `check:vuln-ratchet` | dependencies کی vulnerabilities (osv-scanner) میں اضافہ نہیں ہوتا — binary موجود نہ ہو تو نظرانداز کرتا ہے | **مشاورتی** | -| `check:workflows` | workflow lint (actionlint + zizmor)؛ غائب/خراب scanners، غیر درست رپورٹس یا غائب ratchet baseline کو INCOMPLETE کے طور پر ناکام کرتے ہیں۔ درست نتائج منتخب کردہ strict/advisory/ratchet پالیسی کی پیروی کرتے ہیں | چلانا لازمی ہے؛ CI میں zizmor ratchet بلاک کرنے والا ہے | -| `check:openapi-breaking` | بنیادی branch کے مقابلے میں عوامی API contract (`openapi.yaml`) میں breaking changes (oasdiff) — `openapiBreaking=N` خارج کرتا ہے؛ oasdiff موجود نہ ہو یا بنیادی spec قابلِ حل نہ ہو تو نظرانداز کرتا ہے | **مشاورتی** | +| اسکرپٹ | توثیق | رکاوٹ بننے والا | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | +| `check:circular-deps` | کوئی circular dependencies نہیں (dpdm) | **مشاورتی** | +| `check:bundle-size` | Bundle کا حجم مقررہ حد سے تجاوز نہیں کرتا | **مشاورتی** | +| `check:secrets` | Secrets کی scanning (gitleaks) — binary موجود نہ ہو تو skip کرتا ہے | **مشاورتی** | +| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) میں اضافہ نہیں ہوتا — binary موجود نہ ہو تو skip کرتا ہے | **مشاورتی** | +| `check:workflows` | Workflow lint (actionlint + zizmor)؛ غائب/خراب scanners، نامعتبر reports یا غائب ratchet baseline کی صورت میں INCOMPLETE کے طور پر ناکام ہوتا ہے۔ درست نتائج منتخب کردہ strict/advisory/ratchet پالیسی پر عمل کرتے ہیں | چلانا لازمی ہے؛ CI میں zizmor ratchet رکاوٹ بننے والا ہے | +| `check:openapi-breaking` | بنیادی branch کے مقابلے میں عوامی API contract (`openapi.yaml`) میں breaking changes (oasdiff) — `openapiBreaking=N` خارج کرتا ہے؛ oasdiff غائب ہو یا بنیادی spec قابلِ حل نہ ہو تو skip کرتا ہے | **مشاورتی** | ### جاب: `docs-sync-strict` -`main` کی ہر PR پر چلتی ہے۔ ناکامی کی صورت میں merge کو روکتی ہے۔ +`main` کے ہر PR پر چلتا ہے۔ ناکامی کی صورت میں merge کو روکتا ہے۔ -| اسکرپٹ | توثیق کرتا ہے | رکاوٹ بننے والا | +| اسکرپٹ | توثیق | مسدود کنندہ | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | -| `check:docs-all` | میٹا گیٹ جو ذیل کے 6 ذیلی گیٹس کو ترتیب وار چلاتا ہے | ہاں | +| `check:docs-all` | میٹا گیٹ جو ذیل کے 6 ذیلی گیٹس کو یکے بعد دیگرے چلاتا ہے | ہاں | | ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt کے ورژنز میں مطابقت | ہاں | -| ↳ `check:docs-counts` | نثری متن میں دی گئی تعدادیں (provider کی تعداد، migration کی تعداد، وغیرہ) حقیقی تعدادوں کی ratchet ونڈو کے اندر ہوں | ہاں | -| ↳ `check:env-doc-sync` | `.env.example` میں موجود ہر env var دستاویزات کے کسی جدول میں درج ہو، اور اس کے برعکس بھی | ہاں | -| ↳ `check:deprecated-versions` | دستاویزات میں فرسودہ ورژن اسٹرنگز موجود نہ ہوں | ہاں | -| ↳ `check:doc-links` | دستاویزات میں داخلی markdown لنکس حقیقی فائلوں تک پہنچیں (`[text]`/`(path)` فارم) | ہاں | -| ↳ `check:fabricated-docs` | دستاویزات میں مذکور routes، env vars، CLI کمانڈز، hook نام، اور فائل پاتھ codebase میں موجود ہوں۔ `--strict` کے ذریعے سخت گیٹ؛ فلیگ کے بغیر soft-fail۔ | ہاں (CI میں `--strict` کے ذریعے) | -| `check:cli-i18n` | CLI کمانڈ اسٹرنگز تمام i18n locale فائلوں میں موجود ہوں | ہاں | -| `check:openapi-coverage` | OpenAPI spec حقیقی routes کی کم از کم ratcheted حد کا احاطہ کرے | ہاں | -| `check:openapi-security-tiers` | `openapi.yaml` میں security tier annotations، `routeGuard.ts` کی classifications سے مطابقت رکھیں | **مشاورتی** | -| `check:openapi-routes` | `openapi.yaml` میں ہر path ایک حقیقی `route.ts` تک پہنچے (خیالی مواد کی روک تھام) | ہاں | -| `check:docs-symbols` | `docs/**/*.md` میں ہر `/api/...` حوالہ ایک حقیقی `route.ts` تک پہنچے (خیالی مواد کی روک تھام) | ہاں | +| ↳ `check:docs-counts` | نثری متن میں دی گئی تعدادیں (فراہم کنندگان کی تعداد، مائیگریشنز کی تعداد، وغیرہ) حقیقی تعدادوں کی ratchet ونڈو کے اندر ہیں | ہاں | +| ↳ `check:env-doc-sync` | `.env.example` میں موجود ہر env var دستاویزات کے کسی ٹیبل میں درج ہے، اور اس کے برعکس بھی | ہاں | +| ↳ `check:deprecated-versions` | دستاویزات میں کوئی متروک ورژن اسٹرنگ موجود نہیں | ہاں | +| ↳ `check:doc-links` | دستاویزات میں اندرونی markdown لنکس حقیقی فائلوں تک پہنچتے ہیں (`[text]`/`(path)` فارمیٹ) | ہاں | +| ↳ `check:fabricated-docs` | دستاویزات میں مذکور routes، env vars، CLI کمانڈز، hook نام، اور فائل پاتھ codebase میں موجود ہیں۔ `--strict` کے ذریعے سخت گیٹ؛ فلیگ کے بغیر soft-fail۔ | ہاں (CI میں `--strict` کے ذریعے) | +| `check:cli-i18n` | CLI کمانڈ اسٹرنگز تمام i18n locale فائلوں میں موجود ہیں | ہاں | +| `check:openapi-coverage` | OpenAPI spec حقیقی routes کی کم از کم ratcheted حد کا احاطہ کرتی ہے | ہاں | +| `check:openapi-security-tiers` | `openapi.yaml` میں سیکیورٹی درجے کی annotations، `routeGuard.ts` کی classifications سے مطابقت رکھتی ہیں | **مشاورتی** | +| `check:openapi-routes` | `openapi.yaml` میں ہر path کسی حقیقی `route.ts` تک پہنچتا ہے (اینٹی ہیلوسینیشن) | ہاں | +| `check:docs-symbols` | `docs/**/*.md` میں ہر `/api/...` حوالہ کسی حقیقی `route.ts` تک پہنچتا ہے (اینٹی ہیلوسینیشن) | ہاں | | `i18n translation drift` | i18n locale فائلوں میں غیر ترجمہ شدہ keys — صرف انتباہ | **مشاورتی** | ### جاب: `i18n-ui-coverage` -| اسکرپٹ | توثیق کرتا ہے | رکاوٹ بننے والا | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (inline) | UI i18n key coverage ≥ 65% ہو | ہاں | -| `check-ui-value-drift` (inline) | دوبارہ لکھی گئی انگریزی **value** اپنے پیچھے کوئی فرسودہ ترجمہ نہ چھوڑے | ہاں | -| `check-new-key-coverage` (inline) | ایک **نئی** انگریزی key کا ہر locale میں ترجمہ ہو — `__MISSING__:` marker مسترد کر دیا جاتا ہے | ہاں | -| `check-translation-ratio` | ہر locale کے لیے حقیقی ترجمے کا تناسب (allowlist سے باہر انگریزی سے یکساں / placeholder / missing leaves) `config/quality/i18n-translation-baseline.json` + slack سے متجاوز نہ ہو | **مشاورتی** | +| اسکرپٹ | توثیق | مسدود کنندہ | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `check-ui-keys-coverage` (inline) | UI i18n key کوریج ≥ 65% ہے | ہاں | +| `check-ui-value-drift` (inline) | دوبارہ لکھی گئی انگریزی **value** اپنے پیچھے کوئی فرسودہ ترجمہ نہیں چھوڑتی | ہاں | +| `check-new-key-coverage` (inline) | ایک **نئی** انگریزی key ہر locale میں ترجمہ شدہ ہے — `__MISSING__:` مارکر مسترد کر دیا جاتا ہے | ہاں | +| `check-translation-ratio` | فی locale حقیقی ترجمے کا تناسب (allowlist سے باہر انگریزی سے مماثل / placeholder / غائب leaves) `config/quality/i18n-translation-baseline.json` + slack سے تجاوز نہیں کرنا چاہیے | **مشاورتی** | -`fetch-depth: 0` درکار ہے — value-drift گیٹ merge base کے مقابلے میں `en.json` کا diff لیتا ہے۔ +`fetch-depth: 0` درکار ہے — value-drift گیٹ، merge base کے مقابلے میں `en.json` کا diff لیتا ہے۔ #### `check-ui-value-drift` — فرسودہ ترجمے کا گیٹ -یہ اس واحد i18n regression کو پکڑتا ہے جسے دوسرے گیٹس ساختی طور پر نہیں دیکھ سکتے: کسی انگریزی value کو -دوبارہ لکھا جاتا ہے اور _پچھلی_ انگریزی سے اخذ کردہ تراجم باقی رہ جاتے ہیں، جس کے نتیجے میں -غیر انگریزی صارفین پراعتماد انداز میں لکھا ہوا، مگر اب غلط متن پڑھتے رہتے ہیں۔ +یہ اس واحد i18n regression کو پکڑتا ہے جسے دیگر گیٹس ساختی طور پر نہیں دیکھ سکتے: ایک انگریزی value +دوبارہ لکھی جاتی ہے اور _سابقہ_ انگریزی سے اخذ کردہ تراجم برقرار رہتے ہیں، لہٰذا +غیر انگریزی صارفین پُراعتماد انداز میں لکھی ہوئی، مگر اب غلط عبارت پڑھتے رہتے ہیں۔ یہ مسئلہ حقیقتاً ریلیز ہوا تھا۔ جب Antigravity -login helper شامل کیا گیا (#5203) تو `oauthModal.googleOAuthWarning` دوبارہ لکھا گیا؛ **43 میں سے 39 locales** میں ایسا متن برقرار رہا جو operators کو "مکمل -URL کاپی کر کے اسے نیچے پیسٹ کرنے" کو کہتا تھا — ایسا flow جو اس provider کے لیے مکمل ہی نہیں ہو سکتا۔ یہ +لاگ اِن helper شامل کیا گیا (#5203) تو `oauthModal.googleOAuthWarning` دوبارہ لکھا گیا؛ **43 میں سے 39 locales** میں وہ متن برقرار رہا جو آپریٹرز کو "مکمل +URL کاپی کر کے ذیل میں پیسٹ کریں" کہتا تھا — ایسا فلو جو اس provider کے لیے مکمل نہیں ہو سکتا۔ یہ #8463 تک نظر انداز رہا، کیونکہ: -- `sync-ui-keys` صرف **غائب** keys کو backfill کرتا ہے، **فرسودہ** keys کو کبھی نہیں؛ -- `check-ui-keys-coverage` key کی _موجودگی_ شمار کرتا ہے، اس لیے فرسودہ ترجمہ بھی covered شمار ہوتا ہے؛ -- `check-translation-drift`، `docs/i18n//**.md` کی documentation mirrors کو track کرتا ہے — - یہ کبھی `src/i18n/messages/*.json` نہیں پڑھتا۔ 2026-09 re-sync کے بعد سے job `docs-sync-strict` میں blocking ہے: کسی core doc میں ترمیم کریں → `npm run i18n:run -- --files=` (section-level، کم خرچ)۔ +- `sync-ui-keys` صرف ان keys کو backfill کرتا ہے جو **غائب** ہوں، ان کو کبھی نہیں جو **فرسودہ** ہوں؛ +- `check-ui-keys-coverage` key کی _موجودگی_ شمار کرتا ہے، لہٰذا فرسودہ ترجمہ covered شمار ہوتا ہے؛ +- `check-translation-drift`، `docs/i18n//**.md` دستاویزی mirrors کو ٹریک کرتا ہے — + یہ کبھی `src/i18n/messages/*.json` نہیں پڑھتا۔ 2026-09 re-sync سے جاب `docs-sync-strict` میں مسدود کنندہ ہے: بنیادی دستاویز میں ترمیم کریں → `npm run i18n:run -- --files=` (section-level، کم خرچ)۔ -**Diff سے باخبر، baseline پر منحصر نہیں۔** یہ merge base پر موجود `en.json` کا working tree سے موازنہ کرتا ہے؛ ہر اُس key کے لیے جس کی انگریزی value تبدیل ہوئی ہو، کوئی بھی locale جس میں اب بھی غیر تبدیل شدہ ترجمہ موجود ہو، فرسودہ تصور کیا جاتا ہے۔ یہ دانستہ طور پر **پہلے سے موجود قرض کو منجمد رکھتا ہے** — کوئی diff یہ ظاہر نہیں کر سکتا کہ ایک طویل عرصے سے موجود ترجمہ کس پرانی انگریزی سے آیا تھا، اس لیے gate صرف اسی چیز کا جائزہ لیتا ہے جسے موجودہ تبدیلی چھوتی ہے۔ متبادل (فی key hash baseline) کے لیے تقریباً 600 KB کی generated file درکار ہوگی، جو موجودہ سب سے بڑے baseline سے 3× بڑی ہے، اور ہر i18n PR پر تبدیل ہوگی۔ +**Diff سے آگاہ، baseline پر منحصر نہیں۔** یہ merge base پر موجود `en.json` کا working tree سے موازنہ کرتا ہے؛ ہر اُس key کے لیے جس کی انگریزی value تبدیل ہوئی ہو، ایسا کوئی بھی locale جس میں اب بھی غیر تبدیل شدہ ترجمہ موجود ہو، فرسودہ سمجھا جاتا ہے۔ یہ جان بوجھ کر **پہلے سے موجود قرض کو منجمد رکھتا ہے** — diff یہ ظاہر نہیں کر سکتا کہ کوئی دیرینہ ترجمہ کس پرانی انگریزی سے اخذ ہوا تھا، اس لیے gate صرف اُن چیزوں کا فیصلہ کرتا ہے جنہیں موجودہ تبدیلی چھوتی ہے۔ متبادل (فی key hash baseline) کے لیے تقریباً 600 KB کی generated file درکار ہوتی، جو موجودہ سب سے بڑے baseline سے 3× بڑی ہوتی اور ہر i18n PR پر تبدیل ہوتی۔ -اسے پورا کرنے کے دو طریقے ہیں: +اسے مطمئن کرنے کے دو طریقے ہیں: 1. متاثرہ تراجم کو اپ ڈیٹ کریں، یا 2. انہیں `__MISSING__:` پر سیٹ کریں — پھر runtime درست انگریزی پیش کرتا ہے - (`src/i18n/request.ts::deepMergeFallback`, #7258) اور key ترجمے کے لیے قطار میں شامل ہو جاتی ہے۔ + (`src/i18n/request.ts::deepMergeFallback`، #7258) اور key ترجمے کی قطار میں شامل ہو جاتی ہے۔ -اگر string کا **مفہوم** تبدیل ہوا ہے تو **key کا نام تبدیل کرنے** کو ترجیح دیں: نئی key کسی فرسودہ ترجمے کو وراثت میں نہیں لے سکتی۔ #8463 میں یہی pattern استعمال کیا گیا تھا۔ +اگر string کا **مفہوم** تبدیل ہوا ہے، تو **key کا نام تبدیل کرنے** کو ترجیح دیں: نئی key کسی فرسودہ ترجمے کو وراثت میں نہیں لے سکتی۔ #8463 میں یہی pattern استعمال ہوا تھا۔ ```bash npm run i18n:check-value-drift # سخت (جو CI چلاتا ہے) @@ -224,47 +224,47 @@ npm run i18n:check-value-drift:warn # صرف رپورٹ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -جب base catalog پڑھا نہ جا سکے (base ref کے بغیر shallow clone)، تو `SKIP reason=base-unresolved` کے ساتھ 0 پر exit کرتا ہے، جو `check-openapi-breaking` کے طرزِ عمل کی عکاسی کرتا ہے۔ +جب base catalog پڑھا نہ جا سکے (base ref کے بغیر shallow clone)، تو `SKIP reason=base-unresolved` کے ساتھ 0 پر exit کرتا ہے، بالکل `check-openapi-breaking` کی طرح۔ ### Job: `i18n` -مکمل i18n validation matrix (ہر locale کے لیے ایک job)۔ پوری job مشاورتی ہے۔ +مکمل i18n validation matrix (ہر locale کے لیے ایک job)۔ پورا job مشاورتی ہے۔ -| Script | توثیق کرتا ہے | Blocking | -| ------------------------------- | --------------------------- | --------------------------------------------------- | -| `validate_translation.py quick` | ہر locale کے ترجمے کی تکمیل | **مشاورتی** (پوری job پر `continue-on-error: true`) | +| Script | کیا validate کرتا ہے | Blocking | +| ------------------------------- | ------------------------------- | --------------------------------------------------- | +| `validate_translation.py quick` | ہر locale کے لیے ترجمے کی تکمیل | **مشاورتی** (پورے job پر `continue-on-error: true`) | ### Job: `pr-test-policy` صرف pull requests پر چلتا ہے۔ -| Script | توثیق کرتا ہے | Blocking | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/`، یا `bin/` میں production code تبدیل کرنے والے PRs میں tests شامل یا اپ ڈیٹ ہونے چاہییں (Hard Rule #8) | ہاں | -| `check:test-masking` | تبدیل شدہ test files، asserts کی مجموعی تعداد کم نہیں کرتیں یا `assert.ok(true)` جیسی tautologies شامل نہیں کرتیں | ہاں | -| `check:pr-evidence` | PR body میں تبدیلی کے لیے test/VPS شواہد کا حوالہ موجود ہو (PR کے نثری متن کو grep کر کے Hard Rule #18 کو خودکار بناتا ہے — نازک، Backlog دیکھیں) | ہاں | +| Script | کیا validate کرتا ہے | Blocking | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | وہ PRs جو `src/`، `open-sse/`، `electron/`، یا `bin/` میں production code تبدیل کرتے ہیں، لازماً tests شامل یا اپ ڈیٹ کریں (Hard Rule #8) | ہاں | +| `check:test-masking` | تبدیل شدہ test files مجموعی assert count کم نہ کریں اور `assert.ok(true)` جیسی tautologies شامل نہ کریں | ہاں | +| `check:pr-evidence` | PR body میں تبدیلی کے لیے test/VPS evidence کا حوالہ ہو (PR prose کو grep کر کے Hard Rule #18 کو خودکار بناتا ہے — نازک، Backlog دیکھیں) | ہاں | ### Job: `test-vitest` `build` کے بعد چلتا ہے۔ ناکامی کی صورت میں merge کو روکتا ہے۔ -| Suite | توثیق کرتا ہے | Blocking | -| ---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 tools)، autoCombo، cache — vitest runner | ہاں | -| `test:vitest:ui` | UI component tests — vitest runner | **Blocking** — پہلے سے موجود ناکامیوں کو `vitest.config.ts` میں واضح طور پر خارج کیا گیا ہے؛ نئی ناکامیاں job کو ناکام کر دیتی ہیں | +| Suite | کیا validate کرتا ہے | Blocking | +| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 tools)، autoCombo، cache — vitest runner | ہاں | +| `test:vitest:ui` | UI component tests — vitest runner | **Blocking** — پہلے سے موجود failures کو `vitest.config.ts` میں واضح طور پر خارج کیا گیا ہے؛ نئی failures job کو ناکام کرتی ہیں | -### Nightly workflows (طے شدہ، مشاورتی) +### Nightly workflows (scheduled، مشاورتی) -یہ cron schedule (اور `workflow_dispatch`) پر چلتے ہیں، PRs پر کبھی نہیں۔ سبھی مشاورتی ہیں۔ +یہ cron schedule پر (اور `workflow_dispatch` کے ذریعے) چلتے ہیں، PRs پر کبھی نہیں۔ سب مشاورتی ہیں۔ -| Workflow | توثیق کرتا ہے | Blocking | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `nightly-property` | random seed اور زیادہ run count کے ساتھ fast-check property tests | **مشاورتی** | -| `nightly-resilience` | heap-growth gate، chaos fault-injection، k6 load/soak | **مشاورتی** | -| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret کے بغیر چھوڑ دیے جاتے ہیں) | **مشاورتی** | -| `nightly-schemathesis` | `docs/openapi.yaml` استعمال کرتے ہوئے live OmniRoute کے خلاف OpenAPI contract fuzzing (schemathesis) — spec کی خلاف ورزیوں / غیر سنبھالی گئی 500s کو نمایاں کرتا ہے (مرحلہ 8 B.4) | **مشاورتی** | -| `nightly-mutation` | تیز unit lane پر Stryker mutation-testing score — بچ جانے والے mutants کمزور asserts کو نمایاں کرتے ہیں | **مشاورتی** | -| `nightly-compat` | معاونت یافتہ `engines.node` ranges میں Node engine compatibility matrix | **مشاورتی** | +| Workflow | کیا validate کرتا ہے | Blocking | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `nightly-property` | random seed اور زیادہ run count کے ساتھ fast-check property tests | **مشاورتی** | +| `nightly-resilience` | heap-growth gate، chaos fault-injection، k6 load/soak | **مشاورتی** | +| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (provider secret نہ ہونے پر skip ہوتے ہیں) | **مشاورتی** | +| `nightly-schemathesis` | `docs/openapi.yaml` استعمال کرتے ہوئے live OmniRoute کے خلاف OpenAPI contract fuzzing (schemathesis) — spec violations / unhandled 500s سامنے لاتا ہے (Fase 8 B.4) | **مشاورتی** | +| `nightly-mutation` | تیز unit lane پر Stryker mutation-testing score — بچ جانے والے mutants کمزور asserts کو نمایاں کرتے ہیں | **مشاورتی** | +| `nightly-compat` | معاونت یافتہ `engines.node` ranges میں Node engine compatibility matrix | **مشاورتی** | --- @@ -527,41 +527,41 @@ CI گیٹس کے علاوہ، OmniRoute ایک **اختیاری** `agent-lsp` ا ## معقولیت سازی کا بیک لاگ (ROI جائزہ — مرحلہ 9 لہر 3) -اس فہرست کا 2026-06-17 کو `ci.yml` کے مقابل موازنہ کیا گیا تھا (پچھلے ورژن میں +اس فہرست کا 2026-06-17 کو `ci.yml` کے مقابل موازنہ کر کے تطبیق کی گئی (پچھلے ورژن میں `audit:deps`، `check:tracked-artifacts`، `check:lockfile`، `check:licenses`، `check:dead-code`، `check:cognitive-complexity`، `check:type-coverage`، -`check:codeql-ratchet`، `check:pr-evidence` شامل نہیں تھے)۔ ہم آہنگ کردہ مجموعے کے ROI جائزے -میں معقولیت سازی کے درج ذیل امیدواروں کی نشاندہی ہوئی۔ **انضمام محض میکانکی CI -تبدیلیاں ہیں؛ پلٹنے/ہٹانے کے فیصلے پالیسی سے متعلق ہیں جو آپریٹر کے لیے مختص ہیں۔** ذیل میں دی گئی -کوئی چیز ابھی لاگو نہیں کی گئی۔ +`check:codeql-ratchet`، `check:pr-evidence` شامل نہیں تھے)۔ تطبیق شدہ مجموعے کے ROI جائزے +نے معقولیت سازی کے درج ذیل امیدواروں کی نشاندہی کی۔ **انضمام محض میکانکی CI +تبدیلیاں ہیں؛ پلٹانا/حذف کرنا پالیسی فیصلے ہیں جو آپریٹر کے لیے محفوظ ہیں۔** ذیل میں سے کچھ بھی +ابھی لاگو نہیں کیا گیا۔ -**اوپر درج نہ ہونے والی چیزیں بھی** (مشاورتی، کمزور اشارہ): `docs-lint` جاب +**اوپر یہ بھی دستاویزی شکل میں درج نہیں** (مشاورتی، کم سگنل): `docs-lint` جاب (markdownlint + Vale، پوری جاب پر `continue-on-error`) اور علیحدہ اسکینر ورک فلوز `semgrep.yml` / `codeql.yml` / `scorecard.yml`۔ `semgrepFindings: 0` -`quality-baseline.json` میں موجود ہے، لیکن `ci.yml` میں کسی بلاک کرنے والے ریچیٹ سے منسلک نہیں — یہ میٹرک -فی الحال غیر منسلک ہے۔ +`quality-baseline.json` میں موجود ہے لیکن `ci.yml` میں کسی بلاکنگ ریچٹ سے منسلک نہیں — یہ میٹرک +فی الحال غیر مربوط ہے۔ ### انضمام / نقل کا خاتمہ (میکانکی، کم خطرہ) -ہر امیدوار کی 2026-06-17 کو فعال گیٹ کی حالت کے مقابل تصدیق کی گئی (اعتماد کریں، مگر تصدیق بھی کریں)؛ -کئی "ظاہری طور پر واضح" انضمامات دراصل چھپا ہوا تکنیکی قرض رکھتے تھے اور **بلا تبدیلی براہِ راست متبادل نہیں** ہیں۔ - -- **`check:docs-sync` دو مرتبہ چلتا ہے** — `lint` جاب میں علیحدہ طور پر، اور پھر `check:docs-all` (`docs-sync-strict`) اور husky pre-commit ہک کے اندر۔ ✅ **مکمل** — علیحدہ `lint` اجرا ہٹا دیا گیا۔ -- **CVE اسکیننگ** — ❌ **صاف انضمام نہیں۔** `audit:deps` کسی بھی high/critical CVE پر قطعی طور پر ناکام ہو جاتا ہے؛ `check:vuln-ratchet` (osv) صرف baseline کے مقابلے میں کسی _تنزلی_ پر ناکام ہوتا ہے (فی الحال 1 MODERATE)۔ مفہوم مختلف ہیں — `audit:deps` ہٹانے سے مطلق high/critical گیٹ ختم ہو جائے گا۔ دونوں برقرار رکھیں۔ -- **سائیکل کی شناخت** — ❌ **صاف انضمام نہیں۔** `check:circular-deps` (dpdm) **91 سائیکلز** رپورٹ کرتا ہے (اسی لیے یہ مشاورتی ہے)؛ پہلے ان کو حل کیے بغیر اسے بلاک کرنے والے درجے پر نہیں لایا جا سکتا، اور اس کا دائرۂ کار سبز، منتخب `check:cycles` سے زیادہ وسیع ہے۔ `check:cycles` کو بلاک کرنے والا رکھیں؛ 91 dpdm سائیکلز کو حل کرنا ایک علیحدہ بیک لاگ ہے۔ -- **پیچیدگی** — ✅ **مکمل** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ایک ESLint واک، `ruleId` کے لحاظ سے شمار کرتی ہے تاکہ cyclomatic+max-lines اور cognitive baselines آزاد رہیں؛ انفرادی `check:complexity` / `check:cognitive-complexity` مقامی `--update` کے لیے برقرار ہیں۔ -- **`/api` اینٹی ہیلوسینیشن** — ✅ **مکمل** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` کی ایک FS فہرست سازی، openapi-routes + docs-symbols بدستور آزادانہ رپورٹ کرتے ہیں؛ انفرادی چیکس مقامی اجرا کے لیے برقرار ہیں۔ -- **`check:node-runtime` 11 جابز میں چلتا ہے** — ⚠️ **کم ROI۔** ہر ایک علیحدہ runner ہے اور چیک <1s لیتا ہے؛ ایک سستے فی جاب حفاظتی چیک کو کھونے کے مقابلے میں کل بچت ~10s ہے۔ اس تبدیلی کی زحمت کے قابل نہیں۔ -- **CI lint پر `typecheck:noimplicit:core`** — ✅ **lint جاب سے ہٹا دیا گیا** (یہ مشاورتی `continue-on-error` تھا)؛ بلاک کرنے والی type surface اب `typecheck:core` + `check:type-coverage` ہے۔ مقامی اسکرپٹ برقرار رکھا گیا۔ - -### پلٹیں / فیصلہ کریں (آپریٹر پالیسی) - -- `check:openapi-security-tiers` (مشاورتی) — ❌ **اسے صاف طور پر پلٹا نہیں جا سکتا۔** یہ 0 کے ساتھ خارج ہوتا ہے، مگر متنبہ کرتا ہے کہ `LOCAL_ONLY_API_PREFIXES` کے تحت متعدد `traffic-inspector` روٹس میں `x-loopback-only: true` تشریح موجود نہیں۔ اسے نافذ کرنے کے لیے پہلے وہ تشریحات `openapi.yaml` میں شامل کرنا ضروری ہیں۔ -- `typecheck:noimplicit:core` (مشاورتی) — بڑی حد تک بلاک کرنے والے `check:type-coverage` ریچیٹ میں شامل ہے۔ اسے ریچیٹ میں بدلیں یا فالتو دوسرے `tsc` پاس کو ہٹا دیں۔ -- `test:vitest:ui` (اب **بلاک کرنے والا**) — پہلے سے موجود ناکامیاں `vitest.config.ts` میں `// #8618` ٹریکنگ تبصروں کے ساتھ واضح طور پر خارج کی گئی ہیں؛ نئی ناکامیاں جاب کو ناکام کر دیتی ہیں۔ -- `check:secrets` (gitleaks، 3 دستاویزی false-positives پر منجمد بلاک کرنے والا ریچیٹ) — 0 تک پہنچنے کے لیے ان 3 کو allowlist کریں، یا اسے مشاورتی درجے پر لے جائیں۔ یہ GitHub کی مقامی secret-scanning + `check:public-creds` سے متجاوز ہے۔ -- `check:pr-evidence` (بلاک کرنے والا، PR-body نثر میں greps کرتا ہے) — false-positive کا زیادہ خطرہ؛ اسے ہٹانے سے Hard Rule #18 کا نفاذ کمزور ہوتا ہے، لہٰذا یہ حقیقی پالیسی فیصلہ ہے۔ -- `semgrep` (مشاورتی علیحدہ ورک فلو) — OWASP خاندانوں کے لیے CodeQL سے متجاوز ہے؛ اس کے baseline کو ریچیٹ سے منسلک کریں یا اسے ہٹا دیں۔ +ہر امیدوار کی 2026-06-17 کو فعال گیٹ کی حالت کے مقابل توثیق کی گئی (اعتماد کریں مگر تصدیق بھی کریں)؛ +کئی "واضح" انضمامات میں چھپا ہوا قرض سامنے آیا اور وہ **صاف ستھرے براہِ راست متبادل نہیں** ہیں۔ + +- **`check:docs-sync` دو بار چلتا ہے** — `lint` جاب میں علیحدہ طور پر اور دوبارہ `check:docs-all` (`docs-sync-strict`) اور husky pre-commit ہک کے اندر۔ ✅ **مکمل** — علیحدہ `lint` اجرا ہٹا دیا گیا۔ +- **CVE اسکیننگ** — ❌ **صاف انضمام نہیں۔** `audit:deps` کسی بھی high/critical CVE پر قطعی طور پر ناکام ہو جاتا ہے؛ `check:vuln-ratchet` (osv) صرف بنیادی خط کے مقابل کسی _تنزلی_ پر ناکام ہوتا ہے (فی الحال 1 MODERATE)۔ معنویات مختلف ہیں — `audit:deps` حذف کرنے سے مطلق high/critical گیٹ ختم ہو جائے گا۔ دونوں برقرار رکھیں۔ +- **چکر کی کھوج** — ✅ **مکمل** (#15159 G-01/G-02)۔ یہاں موجود پرانے متن نے `check:cycles` کو "سبز، منتخب" گیٹ کہا تھا اور اسے بلاکنگ رکھنے کا جواز یہ دیا تھا کہ `check:circular-deps` (dpdm) نے 91 چکروں کی اطلاع دی۔ وہ سبز حالت ایک **غلط سبز حالت** تھی: `check:cycles` نے 5 ذیلی ڈائریکٹریاں (450 فائلیں) اسکین کیں، صرف جامد `import|export … from` سے مطابقت کی، اور ہر `@/` اور `@omniroute/open-sse/` تخصیص کار کو خارج کر دیا، اس لیے یہ ان dynamic-import + alias چکروں کو نہیں دیکھ سکتا تھا جو ریپو میں غالب تھے۔ درست کر دیا گیا: گیٹ اب `src` + `open-sse` (5023 فائلیں) میں چلتا ہے، TypeScript AST سے تخصیص کار جمع کرتا ہے (لہٰذا `import("…")` شمار ہوتا ہے جبکہ ٹائپ پوزیشن میں `typeof import("…")` شمار نہیں ہوتا)، اور tsconfig `paths` کو حل کرتا ہے۔ اسے 0 نہیں بلکہ **14** چکر ملتے ہیں۔ چونکہ گیٹ PR میں پہلے سے موجود 14 چکروں کو درست نہیں کیا جا سکتا، اس لیے `check:cycles` اب ایک **ریچٹ** ہے (`--ratchet`، `quality-baseline.json` میں حد `metrics.cycles.value = 14`، `direction: down`) — یہ کسی بھی _تنزلی_ کو روکتا ہے اور تعداد صرف کم ہو سکتی ہے۔ CI میں `npm run check:cycles:ratchet` چلتا ہے۔ مرحلہ وار کمی **A-01** کے ساتھ آگے بڑھے گی۔ `check:circular-deps` (dpdm) ایک وسیع تر ثانوی رائے کے طور پر مشاورتی رہے گا۔ +- **پیچیدگی** — ✅ **مکمل** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ایک ESLint واک، `ruleId` کے لحاظ سے شمار، تاکہ cyclomatic+max-lines اور cognitive بنیادی خطوط آزاد رہیں؛ انفرادی `check:complexity` / `check:cognitive-complexity` مقامی `--update` کے لیے برقرار ہیں۔ +- **`/api` اینٹی ہیلوسینیشن** — ✅ **مکمل** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` کی ایک FS فہرست سازی، openapi-routes + docs-symbols اب بھی آزادانہ طور پر رپورٹ کرتے ہیں؛ انفرادی چیکس مقامی اجرا کے لیے برقرار ہیں۔ +- **`check:node-runtime` 11 جابز میں چلتا ہے** — ⚠️ **کم ROI۔** ہر ایک الگ رنر ہے اور چیک <1s ہے؛ کل بچت ~10s ہے، جبکہ اس کے بدلے ہر جاب کا ایک سستا حفاظتی چیک ختم ہو جائے گا۔ اتنی تبدیلی کے قابل نہیں۔ +- **CI lint پر `typecheck:noimplicit:core`** — ✅ **lint جاب سے ہٹا دیا گیا** (یہ مشاورتی `continue-on-error` تھا)؛ بلاکنگ ٹائپ سطح `typecheck:core` + `check:type-coverage` ہے۔ مقامی اسکرپٹ برقرار رکھا گیا۔ + +### پلٹائیں / فیصلہ کریں (آپریٹر پالیسی) + +- `check:openapi-security-tiers` (مشاورتی) — ❌ **صاف طور پر پلٹنے کے قابل نہیں۔** یہ 0 کے ساتھ خارج ہوتا ہے لیکن خبردار کرتا ہے کہ `LOCAL_ONLY_API_PREFIXES` کے تحت کئی `traffic-inspector` روٹس میں `x-loopback-only: true` تشریح موجود نہیں۔ اسے نافذ کرنے کے لیے پہلے `openapi.yaml` میں یہ تشریحات شامل کرنا ضروری ہے۔ +- `typecheck:noimplicit:core` (مشاورتی) — بڑی حد تک بلاکنگ `check:type-coverage` ریچٹ میں شامل ہے۔ اسے ریچٹ میں تبدیل کریں یا فالتو دوسرے `tsc` پاس کو حذف کریں۔ +- `test:vitest:ui` (اب **بلاکنگ**) — پہلے سے موجود ناکامیاں `vitest.config.ts` میں `// #8618` ٹریکنگ تبصروں کے ساتھ واضح طور پر خارج کی گئی ہیں؛ نئی ناکامیاں جاب کو ناکام کر دیتی ہیں۔ +- `check:secrets` (gitleaks، 3 دستاویزی false-positives پر منجمد بلاکنگ ریچٹ) — 0 تک پہنچنے کے لیے ان 3 کو allowlist کریں، یا اسے مشاورتی درجے پر لے جائیں۔ یہ GitHub کی مقامی secret-scanning + `check:public-creds` سے متجاوز ہے۔ +- `check:pr-evidence` (بلاکنگ، PR-body نثر میں grep کرتا ہے) — false-positive کا زیادہ خطرہ؛ اسے حذف کرنے سے Hard Rule #18 کا نفاذ کمزور ہو جائے گا، اس لیے یہ حقیقی پالیسی فیصلہ ہے۔ +- `semgrep` (مشاورتی علیحدہ ورک فلو) — OWASP خاندانوں کے لیے CodeQL سے متجاوز ہے؛ اس کے بنیادی خط کو کسی ریچٹ سے منسلک کریں یا حذف کر دیں۔ --- diff --git a/docs/i18n/ur/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/ur/docs/compression/COMPRESSION_GUIDE.md index cb83761e18eb..590b33b050a9 100644 --- a/docs/i18n/ur/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/ur/docs/compression/COMPRESSION_GUIDE.md @@ -454,8 +454,7 @@ Caveman آؤٹ پٹ موڈ **اختیاری** ہے — اسے combo config کے `messages` والے body پر، مواد کو نظر انداز کرنے کا طریقہ (`shouldBypassCavemanOutputMode()`، `open-sse/services/compression/outputMode.ts` میں) آخری تین پیغامات کی جانچ کرتا ہے اور جب وہ اس کے سیکیورٹی، ناقابلِ واپسی عمل، وضاحت طلب، یا ترتیب کے لحاظ سے حساس کلیدی الفاظ سے میل کھائیں -تو پوری turn کے لیے انداز چھوڑ دیتا ہے۔ بائی پاس dashboard کے -**خودکار وضاحت بائی پاس** toggle (`cavemanOutputMode.autoClarity`) کی موجودہ ترتیب کے مطابق چلتا ہے۔ +تو پوری turn کے لیے انداز چھوڑ دیتا ہے۔ جب تک dashboard کے **خودکار وضاحت بائی پاس** toggle (`cavemanOutputMode.autoClarity`) آن رہتا ہے، بائی پاس چلتا رہتا ہے، جو کہ پہلے سے طے شدہ ترتیب ہے؛ toggle کے بند ہونے پر منتخب انداز اُن turn پر بھی لاگو ہوتے ہیں۔ جب بائی پاس turn کو گزرنے دیتا ہے، تو `placeSystemInstruction()` (اسی فائل میں)، جو کبھی نیا `messages[0]` نہیں بناتا، بلاک کو ان میں سے پہلی دستیاب جگہ پر رکھتا ہے: diff --git a/docs/i18n/ur/docs/reference/ENVIRONMENT.md b/docs/i18n/ur/docs/reference/ENVIRONMENT.md index 79287f0afa7a..87852f75143b 100644 --- a/docs/i18n/ur/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/ur/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. مطلوبہ راز -پہلی بار چلانے سے پہلے انہیں **لازماً** سیٹ کرنا ضروری ہے۔ ان کے بغیر ایپلیکیشن یا تو شروع ہونے سے انکار کر دے گی یا غیر محفوظ ڈیفالٹ ترتیبات کے ساتھ کام کرے گی۔ - -| متغیر | مطلوب | ڈیفالٹ | ماخذ فائل | وضاحت | -| ---------------------------- | ----------------- | -------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **ہاں** | _(کوئی نہیں)_ | `src/lib/auth` | تمام ڈیش بورڈ سیشن کوکیز (JWT) کو سائن اور ان کی تصدیق کرتا ہے۔ `openssl rand -base64 48` کے ذریعے بنائیں۔ | -| `API_KEY_SECRET` | **ہاں** | _(کوئی نہیں)_ | `src/lib/db/apiKeys.ts` | SQLite میں محفوظ API کلید کی اقدار کے لیے AES انکرپشن کلید۔ `openssl rand -hex 32` کے ذریعے بنائیں۔ | -| `INITIAL_PASSWORD` | **ہاں** | `CHANGEME` | بوٹ اسٹریپ اسکرپٹ | ابتدائی ایڈمن ڈیش بورڈ پاس ورڈ مقرر کرتا ہے (`.env.example` کے ڈیفالٹ سے مطابقت رکھتا ہے — اسے جان بوجھ کر واضح طور پر غیر محفوظ رکھا گیا ہے تاکہ تبدیلی لازمی ہو)۔ **پہلے استعمال سے قبل تبدیل کریں۔** لاگ اِن کے بعد، ڈیش بورڈ → ترتیبات → سیکیورٹی کے ذریعے تبدیل کریں۔ | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **ہاں** (پروڈکشن) | _(سیٹ نہیں)_ | `src/app/api/internal/codex-responses-ws/route.ts` | اندرونی Codex Responses WebSocket برج کے لیے مشترکہ راز۔ Electron/browser WS ریلے اور OmniRoute کے درمیان برج کی درخواستوں کی توثیق کرتا ہے۔ ⚠️ **پروڈکشن میں لازمی — سیٹ نہ ہونے پر WS برج کی تمام درخواستیں مسترد کر دی جاتی ہیں۔** `openssl rand -base64 32` کے ذریعے بنائیں۔ | -| `OMNIROUTE_SW_BUILD_ID` | نہیں | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA شیل (#11779) کے لیے واضح service-worker کیش بسٹنگ آئی ڈی؛ ریزولیوشن سلسلے میں سب سے پہلے۔ | -| `SOURCE_VERSION` | نہیں | _(سیٹ نہیں)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | سلسلے میں دوسرا — PaaS بلڈرز (مثلاً Heroku طرز کے) اسے تعینات کردہ commit کے طور پر سیٹ کرتے ہیں۔ | -| `NEXT_PUBLIC_SW_BUILD_ID` | نہیں | _(اخذ کردہ)_ | `src/shared/components/PwaRegister.tsx` | بلڈ کے وقت کی عوامی قدر جسے کلائنٹ `/sw.js?v=…` رجسٹر کرنے کے لیے استعمال کرتا ہے؛ یہ پہلے اوپر دی گئی دو اقدار، اور پھر git SHA سے اخذ ہوتی ہے۔ | -| `OMNIROUTE_PEER_STAMP_TOKEN` | نہیں (خودکار) | _(ہر بوٹ پر خودکار)_ | `src/server/authz/policies/management.ts` | ہر پراسیس کا راز، جو ثابت کرتا ہے کہ قابلِ اعتماد peer-IP اسٹیمپ OmniRoute کے اپنے HTTP سرور (`scripts/dev/peer-stamp.mjs`) سے آیا ہے۔ authz مڈل ویئر درخواست کی مقامیت (LOCAL_ONLY روٹس کی loopback/LAN گیٹنگ) پر صرف اس وقت بھروسا کرتا ہے جب اسٹیمپ میں یہ ٹوکن موجود ہو۔ ہر بوٹ پر خودکار طور پر بنتا ہے — اسے سیٹ نہ کریں؛ صرف ان multi-process سیٹ اپس کے لیے اسے مقرر کریں جنہیں اسٹیمپ مشترک رکھنا ضروری ہو۔ | -| `OMNIROUTE_TRUSTED_PROXIES` | نہیں | _(سیٹ نہیں)_ | `scripts/dev/peer-stamp.mjs` | ریورس پراکسیز کے کوما سے الگ کردہ IPs یا CIDR رینجز، جن کے `X-Forwarded-For` / `X-Real-IP` پر IP اجازت/ممانعت فہرست بھروسا کر سکتی ہے؛ اس کے علاوہ loopback، نجی نیٹ ورک پتے اور Cloudflare edges ہمیشہ قابلِ اعتماد ہوتے ہیں۔ یہ صرف کسی دوسرے عوامی پتے پر موجود پراکسی کے لیے درکار ہے؛ بصورتِ دیگر فلٹر پراکسی کے اپنے پتے کو جانچتا ہے۔ | +**ہاں** کے ساتھ نشان زد اور سیٹ نہ کیے گئے راز پہلی بار چلنے پر خودکار طور پر بنائے جاتے ہیں (`JWT_SECRET`، `API_KEY_SECRET`) اور ڈیٹا ڈائریکٹری میں `server.env` میں محفوظ ہوتے ہیں — اگر وہ ڈائریکٹری عارضی ہو تو انہیں واضح طور پر سیٹ کریں۔ اس کا استثنا `OMNIROUTE_WS_BRIDGE_SECRET` ہے: یہ `server.env` میں محفوظ نہیں ہوتا — سرور انٹری پوائنٹس ہر بوت پر ایک عارضی ویلیو بناتے ہیں جسے صرف اسی پروسیس کے اندر موجود برج استعمال کر سکتا ہے، اس لیے بیرونی برج کلائنٹس کو اسے واضح طور پر سیٹ کرنا ضروری ہے۔ + +| متغیر | مطلوب | ڈیفالٹ | ماخذ فائل | وضاحت | +| ---------------------------- | ----------------- | -------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **ہاں** | _(کوئی نہیں)_ | `src/lib/auth` | تمام ڈیش بورڈ سیشن کوکیز (JWT) کو سائن اور ان کی تصدیق کرتا ہے۔ `openssl rand -base64 48` کے ذریعے بنائیں۔ | +| `API_KEY_SECRET` | **ہاں** | _(کوئی نہیں)_ | `src/lib/db/apiKeys.ts` | SQLite میں محفوظ API کلید کی اقدار کے لیے AES انکرپشن کلید۔ `openssl rand -hex 32` کے ذریعے بنائیں۔ | +| `INITIAL_PASSWORD` | نہیں | _(سیٹ نہیں)_ | `src/lib/auth/managementPassword.ts` | جب تک کوئی پاس ورڈ محفوظ نہ ہوا ہو، اسٹارٹ اپ پر ڈیش بورڈ کا پاس ورڈ مقرر کرتا ہے؛ پاس ورڈ محفوظ ہونے کے بعد نظرانداز کر دیا جاتا ہے۔ npm اور سورس انسٹالیشنز `.env.example` کو `.env` میں کاپی کرتی ہیں، اس لیے وہ اس فائل کے عوامی سطح پر معلوم پلیس ہولڈر `CHANGEME` کے ساتھ شروع ہوتی ہیں: پہلے بوت سے قبل اسے تبدیل کریں، یا جیسا کچھ سیٹ نہ کردہ ویلیو (Docker امیج کا ڈیفالٹ) کرتا ہے، پہلی آمد پر پاس ورڈ آن بورڈنگ وزارڈ میں بنانے کے لیے اسی `.env` میں `INITIAL_PASSWORD=` (خالی) سیٹ کریں۔ محفوظ شدہ پاس ورڈ کو ڈیش بورڈ → ترتیبات → سیکیورٹی میں تبدیل کریں، یا `DATA_DIR` کو سرور کی ڈیٹا ڈائریکٹری پر سیٹ کر کے `omniroute reset-password` چلائیں (سورس چیک آؤٹ میں `node bin/reset-password.mjs`)۔ | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **ہاں** (پروڈکشن) | _(سیٹ نہیں)_ | `src/app/api/internal/codex-responses-ws/route.ts` | اندرونی Codex Responses WebSocket برج کے لیے مشترکہ راز۔ Electron/browser WS ریلے اور OmniRoute کے درمیان برج کی درخواستوں کی توثیق کرتا ہے۔ ⚠️ **پروڈکشن میں لازمی — سیٹ نہ ہونے پر WS برج کی تمام درخواستیں مسترد کر دی جاتی ہیں۔** `openssl rand -base64 32` کے ذریعے بنائیں۔ | +| `OMNIROUTE_SW_BUILD_ID` | نہیں | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA شیل (#11779) کے لیے واضح service-worker کیش بسٹنگ آئی ڈی؛ ریزولیوشن سلسلے میں سب سے پہلے۔ | +| `SOURCE_VERSION` | نہیں | _(سیٹ نہیں)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | سلسلے میں دوسرا — PaaS بلڈرز (مثلاً Heroku طرز کے) اسے تعینات کردہ commit کے طور پر سیٹ کرتے ہیں۔ | +| `NEXT_PUBLIC_SW_BUILD_ID` | نہیں | _(اخذ کردہ)_ | `src/shared/components/PwaRegister.tsx` | بلڈ کے وقت کی عوامی قدر جسے کلائنٹ `/sw.js?v=…` رجسٹر کرنے کے لیے استعمال کرتا ہے؛ یہ پہلے اوپر دی گئی دو اقدار، اور پھر git SHA سے اخذ ہوتی ہے۔ | +| `OMNIROUTE_PEER_STAMP_TOKEN` | نہیں (خودکار) | _(ہر بوٹ پر خودکار)_ | `src/server/authz/policies/management.ts` | ہر پراسیس کا راز، جو ثابت کرتا ہے کہ قابلِ اعتماد peer-IP اسٹیمپ OmniRoute کے اپنے HTTP سرور (`scripts/dev/peer-stamp.mjs`) سے آیا ہے۔ authz مڈل ویئر درخواست کی مقامیت (LOCAL_ONLY روٹس کی loopback/LAN گیٹنگ) پر صرف اس وقت بھروسا کرتا ہے جب اسٹیمپ میں یہ ٹوکن موجود ہو۔ ہر بوٹ پر خودکار طور پر بنتا ہے — اسے سیٹ نہ کریں؛ صرف ان multi-process سیٹ اپس کے لیے اسے مقرر کریں جنہیں اسٹیمپ مشترک رکھنا ضروری ہو۔ | +| `OMNIROUTE_TRUSTED_PROXIES` | نہیں | _(سیٹ نہیں)_ | `scripts/dev/peer-stamp.mjs` | ریورس پراکسیز کے کوما سے الگ کردہ IPs یا CIDR رینجز، جن کے `X-Forwarded-For` / `X-Real-IP` پر IP اجازت/ممانعت فہرست بھروسا کر سکتی ہے؛ اس کے علاوہ loopback، نجی نیٹ ورک پتے اور Cloudflare edges ہمیشہ قابلِ اعتماد ہوتے ہیں۔ یہ صرف کسی دوسرے عوامی پتے پر موجود پراکسی کے لیے درکار ہے؛ بصورتِ دیگر فلٹر پراکسی کے اپنے پتے کو جانچتا ہے۔ | ### تخلیق کے کمانڈز @@ -240,11 +240,11 @@ OmniRoute تمام مستقل ڈیٹا کے لیے **SQLite** (`better-sqlite3` | `CORS_ORIGIN` | _(سیٹ نہیں)_ | `src/server/cors/origins.ts` | واحد اوریجن کے لیے سابقہ CORS اجازت فہرست۔ نئی تنصیبات کے لیے `CORS_ALLOWED_ORIGINS` کو ترجیح دیں۔ CORS صرف کراس اوریجن براؤزر API کلائنٹس کے لیے ہے؛ توثیق شدہ ڈیش بورڈ رائٹس اس کے بجائے اسی اوریجن کی درخواستوں کے ساتھ سیشن سے منسلک CSRF تحفظ استعمال کرتی ہیں۔ | | `CORS_ALLOWED_ORIGINS` | _(سیٹ نہیں)_ | `src/server/cors/origins.ts` | کاما سے جدا کردہ CORS اجازت فہرست۔ وائلڈ کارڈ اس وقت تک نہیں بھیجا جاتا جب تک `CORS_ALLOW_ALL=true` واضح طور پر کنفیگر نہ کیا گیا ہو۔ | | `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | کسی بھی براؤزر `Origin` کو واپس بھیجنے کے لیے صرف ڈیولپمنٹ میں استعمال ہونے والا ہنگامی راستہ۔ اسے مشترکہ یا پروڈکشن تنصیبات پر فعال نہ کریں۔ | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | نجی/لوپ بیک/لنک لوکل IP رینجز کو ہدف بنانے والی پرووائیڈر کالز بلاک کریں۔ اسے صرف الگ تھلگ ٹیسٹ ماحول میں غیر فعال کریں۔ | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | نجی/مقامی نیٹ ورکس (localhost، 192.168.x.x، 10.x.x.x وغیرہ) کی طرف اشارہ کرنے والے پرووائیڈر URLs کی اجازت دیں۔ **خود میزبانی کیے گئے پرووائیڈرز کے لیے لازمی ہے** (LM Studio، Ollama، vLLM، Llamafile، Triton، SearXNG)۔ `false` ہونے پر، ڈیش بورڈ مقامی URLs کی توثیق مسترد کر دیتا ہے۔ | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | مقامی/نجی ایڈریسز (127.0.0.1، localhost، LAN، نجی رینجز) پر پرووائیڈرز شامل کرنے/ان کی توثیق کرنے کی اجازت دیں — اس کا دائرۂ کار پرووائیڈر کی توثیق کے راستے تک محدود ہے۔ **ڈیفالٹ `true`** (مقامی ترجیح)؛ صرف عوامی ایڈریسز کی سخت پابندی نافذ کرنے کے لیے اسے `false` پر سیٹ کریں۔ کلاؤڈ میٹا ڈیٹا اینڈ پوائنٹس (169.254.169.254، metadata.google.internal) ہر صورت میں بلاک رہتے ہیں۔ (#5066) | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | پرانی ایلایئس (alias)۔ اس کا ڈیش بورڈ ٹوگل (ایک ڈیٹابیس اوور رائیڈ) اس ماحول ویلیو سے پہلے پڑھا جاتا ہے؛ دونوں میں سے کسی ایک میں `false`، `0`، `no` یا `off` ہونے پر ہوسٹ چیکز بند ہو جاتے ہیں — بالکل اسی طرح جیسے `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` کرتا ہے — چاہے اس فلیگ کا ڈیش بورڈ ٹوگل بند محفوظ کیا گیا ہو؛ دیگر ویلیوز گارڈ کو نیچے دیے گئے دونوں فلیگز کے حوالے کر دیتی ہیں۔ | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | گارڈ موڈ کی پیروی کرنے والے پرووائیڈر راستوں پر — URL توثیق، ماڈل دریافت، پرووائیڈر نوڈ بیس URLs (ریموٹ ری رینک نوڈز سمیت)، پراکسی فال بیک ٹیسٹ اور ریموٹ تصویر/میڈیا فچ جیسے دیگر پرووائیڈر سے متعلق آؤٹ باؤنڈ راستے — آؤٹ باؤنڈ URL گارڈ کے ہوسٹ چیکز بند کر دیتا ہے، کلاؤڈ میٹا ڈیٹا بلاک سمیت (پورا 169.254.0.0/16، metadata.google.internal)۔ یہ نجی ویب ہوک ٹارگٹس کی بھی اجازت دیتا ہے، سوائے کلاؤڈ میٹا ڈیٹا/لنک لوکل اینڈ پوائنٹس کے، جو اس فلیگ آن ہونے کے باوجود بلاک رہتے ہیں۔ `BaseExecutor.execute()` کے ذریعے بھیجی گئی چیٹ درخواستیں اپنا کلاؤڈ میٹا ڈیٹا چیک برقرار رکھتی ہیں۔ مقامی پرووائیڈرز ڈیفالٹس کے ساتھ ہی کام کرتے ہیں: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (ڈیفالٹ `true`) توثیق، دریافت اور پرووائیڈر نوڈ راستوں پر مقامی اور LAN URLs کی پہلے ہی اجازت دیتا ہے؛ پراکسی فال بیک ٹیسٹ اور نجی ویب ہوک ٹارگٹس صرف اس فلیگ کو دیکھتے ہیں اور جب تک یہ بند ہے مقامی/LAN ہوسٹس کو بلاک رکھتے ہیں۔ `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` کو اوور رائیڈ کرتا ہے۔ ڈیش بورڈ ٹوگل سے محفوظ شدہ ویلیو اس متغیر پر فوقیت رکھتا ہے۔ | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | پرووائیڈر توثیق، ماڈل دریافت، چیٹ اور دیگر پرووائیڈر سے متعلق ان آؤٹ باؤنڈ راستوں کے لیے — جن پر گارڈ موڈ حکمرانی کرتا ہے — مقامی/نجی ایڈریسز (127.0.0.1، localhost، LAN، نجی رینجز) پر پرووائیڈر URLs کی اجازت دیتا ہے۔ **ڈیفالٹ `true`** (مقامی ترجیح): اس صورت میں گارڈ کلاؤڈ میٹا ڈیٹا اینڈ پوائنٹس بلاک کرتا ہے — پورا 169.254.0.0/16 اور معروف میٹا ڈیٹا ہوسٹ نام (metadata.google.internal، metadata.goog، Alibaba/Azure/Oracle IMDS، fd00:ec2::254)۔ صرف عوامی موڈ کے لیے اسے `false` پر سیٹ کریں؛ وہ نجی اور لوپ بیک ہوسٹس کو بھی بلاک کرتا ہے۔ گارڈ کسی بھی DNS لک اپ سے پہلے ہوسٹ نام یا IP لٹرل کو لکھے گئے انداز میں ہی چیک کرتا ہے؛ صرف عوامی موڈ میں safe-fetch ریپر اضافی طور پر ہوسٹ نام کو ریزولو کرتا ہے اور کسی بھی نجی جواب کو مسترد کر دیتا ہے۔ `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ان چیکز کو اس کی صف میں درج راستوں پر بند کر دیتا ہے۔ Built-in مقامی پرووائیڈرز — جیسے LM Studio، Ollama، vLLM، Llamafile اور Triton — کلید کی توثیق اور چیٹ کے لیے تمام ہوسٹ چیکز کو چھوڑ دیتے ہیں۔ ڈیش بورڈ ٹوگل سے محفوظ شدہ ویلیو اس متغیر پر فوقیت رکھتا ہے۔ (#5066) | | `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` روٹس (ٹرانسکرپشنز، اسپیچ، ترجمے) کو localhost سے باہر میزبانی کیے گئے OpenAI سے مطابقت رکھنے والے پرووائیڈر نوڈ کے استعمال کی اجازت دیں۔ بطور ڈیفالٹ بند ہے — آڈیو کو کسی ریموٹ ہوسٹ کی طرف روٹ کرنے سے ایگریس شناخت بدل جاتی ہے اور اس کے لیے آپریٹر کا واضح فیصلہ ضروری ہے۔ لوپ بیک/نجی نوڈز (localhost، 127.0.0.1، 172.16-31.x) ہمیشہ مجاز اور غیر متاثر رہتے ہیں۔ (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (اور میموری انجن کے لوپ بیک ری رینک مرحلے) کو localhost سے باہر میزبانی کیے گئے OpenAI سے مطابقت رکھنے والے پرووائیڈر نوڈ کے استعمال کی اجازت دیں — مثلاً TEI، Infinity، vLLM وغیرہ چلانے والا LAN باکس یا Tailscale پیئر۔ بطور ڈیفالٹ بند ہے — کسی ریموٹ ہوسٹ کی طرف روٹنگ سے ایگریس شناخت بدل جاتی ہے اور اس کے لیے آپریٹر کا واضح فیصلہ ضروری ہے۔ لوپ بیک نوڈز (localhost، 127.0.0.1، 172.16-31.x) ہمیشہ مجاز اور غیر متاثر رہتے ہیں۔ ریموٹ نوڈز کو پرووائیڈر آؤٹ باؤنڈ URL پالیسی (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) پر بھی پورا اترنا چاہیے؛ کلاؤڈ میٹا ڈیٹا ہوسٹس کی طرف کبھی روٹنگ نہیں کی جاتی۔ | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` (اور میموری انجن کے لوپ بیک ری رینک مرحلے) کو localhost سے باہر میزبانی کیے گئے OpenAI سے مطابقت رکھنے والے پرووائیڈر نوڈ کے استعمال کی اجازت دیں — مثلاً TEI، Infinity، vLLM وغیرہ چلانے والا LAN باکس یا Tailscale پیئر۔ بطور ڈیفالٹ بند ہے — کسی ریموٹ ہوسٹ کی طرف روٹنگ سے ایگریس شناخت بدل جاتی ہے اور اس کے لیے آپریٹر کا واضح فیصلہ ضروری ہے۔ لوپ بیک نوڈز (localhost، 127.0.0.1، 172.16-31.x) ہمیشہ مجاز اور غیر متاثر رہتے ہیں۔ کسی ریموٹ نوڈ کے بیس URL کو بھی پرووائیڈر آؤٹ باؤنڈ URL پالیسی (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) پر پورا اترنا چاہیے۔ | | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC فعال ہونے پر، پاس ورڈ لاگ اِن غیر فعال کریں تاکہ صارفین صرف OIDC سنگل سائن آن کے ذریعے توثیق کر سکیں۔ سادہ عرف `OIDC_DISABLE_PASSWORD_LOGIN` بھی قبول کیا جاتا ہے؛ اسی کلید کا ڈیش بورڈ فیچر فلیگ ترجیح رکھتا ہے۔ (#10889) | | `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` کا سادہ عرف (#10889)۔ | diff --git a/docs/i18n/uz/CONTRIBUTING.md b/docs/i18n/uz/CONTRIBUTING.md index acc407c3d2c5..3a4654458aa8 100644 --- a/docs/i18n/uz/CONTRIBUTING.md +++ b/docs/i18n/uz/CONTRIBUTING.md @@ -399,22 +399,22 @@ Model taʼriflarini `open-sse/config/providerRegistry.ts` fayliga qoʻshing. --- -## Pull Request nazorat roʻyxati +## Pull Request tekshiruv roʻyxati - [ ] Testlar muvaffaqiyatli oʻtadi (`npm test`) -- [ ] Lint tekshiruvi muvaffaqiyatli oʻtadi (`npm run lint`) -- [ ] Yigʻish muvaffaqiyatli yakunlanadi (`npm run build`) +- [ ] Linting tekshiruvi muvaffaqiyatli oʻtadi (`npm run lint`) +- [ ] Build muvaffaqiyatli yakunlanadi (`npm run build`) - [ ] Yangi ommaviy funksiyalar va interfeyslar uchun TypeScript turlari qoʻshilgan -- [ ] Qattiq kodlangan maxfiy maʼlumotlar yoki zaxira qiymatlar mavjud emas -- [ ] Ommaviy yuqori oqim hisob maʼlumotlari `resolvePublicCred()` orqali joylashtirilgan ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) hujjatiga qarang), hech qachon literal sifatida emas -- [ ] Xato javoblari `buildErrorBody()` / `sanitizeErrorMessage()` orqali oʻtadi — javob tanalarida xom stek treyslari mavjud emas ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) hujjatiga qarang) +- [ ] Hardkodlangan maxfiy maʼlumotlar yoki zaxira qiymatlar mavjud emas +- [ ] Ommaviy upstream hisob maʼlumotlari literal qiymatlar sifatida emas, `resolvePublicCred()` orqali kiritilgan ([`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md) ga qarang) +- [ ] Xato javoblari `buildErrorBody()` / `sanitizeErrorMessage()` orqali yoʻnaltirilgan — javob tanalarida xom stack trace mavjud emas ([`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md) ga qarang) - [ ] Shell buyruqlari (`exec` / `spawn`) bajarilish vaqtidagi qiymatlarni satr interpolyatsiyasi orqali emas, `env` orqali uzatadi -- [ ] Barcha kiruvchi maʼlumotlar Zod sxemalari yordamida tekshirilgan -- [ ] Foydalanuvchiga taʼsir qiladigan oʻzgarishlar uchun `changelog.d/{features|fixes|maintenance}/-.md` ostiga oʻzgarishlar jurnali **fragmenti** qoʻshilgan ([`changelog.d/README.md`](./changelog.d/README.md) hujjatiga qarang) — `CHANGELOG.md` faylini bevosita tahrirlamang; fragmentlar reliz vaqtida birlashtiriladi va PRlar oʻrtasida hech qachon ziddiyat keltirib chiqarmaydi +- [ ] Barcha kirish maʼlumotlari Zod sxemalari bilan tekshirilgan +- [ ] Foydalanuvchiga taʼsir qiluvchi oʻzgarishlar uchun `changelog.d/{features|fixes|maintenance}/-.md` ostiga changelog **fragmenti** qoʻshilgan ([`changelog.d/README.md`](./changelog.d/README.md) ga qarang) — `CHANGELOG.md` faylini toʻgʻridan-toʻgʻri tahrirlamang; fragmentlar reliz vaqtida birlashtiriladi va PRlar oʻrtasida hech qachon ziddiyatga olib kelmaydi - [ ] Hujjatlar yangilangan (agar tegishli boʻlsa) -- [ ] Yangi CodeQL / Secret-Scanning ogohlantirishlari ochilmagan yoki ularning har biri tegishli `docs/security/` hujjatiga havola qiluvchi texnik asos bilan rad etilgan -- [ ] Ichki jarayonlarni ishga tushiradigan marshrutlar (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` faylida `isLocalOnlyPath()` sifatida tasniflangan — [15-qatʼiy qoida](docs/security/ROUTE_GUARD_TIERS.md)ga qarang -- [ ] Commit xabarlarida `Co-Authored-By` yakuniy satrlari mavjud emas — commitlar faqat repozitoriy egasining Git identifikatori ostida koʻrinishi kerak (16-qatʼiy qoida) +- [ ] Yangi CodeQL / Secret-Scanning ogohlantirishlari yaratilmagan yoki ularning har biri tegishli `docs/security/` hujjatiga havola qilingan texnik asos bilan rad etilgan +- [ ] Quyi jarayonlarni ishga tushiradigan marshrutlar (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` ichida `isLocalOnlyPath()` sifatida tasniflangan — [Qatʼiy qoida #15](docs/security/ROUTE_GUARD_TIERS.md) ga qarang +- [ ] Commit xabarlarida AI/botga tegishli `Co-authored-by` treylerlari mavjud emas (Qatʼiy qoida #16) — ishidan qayta foydalanilgan inson hamkorlar standart `Co-authored-by: Name ` treylerlari bilan eʼtirof etiladi --- diff --git a/docs/i18n/uz/docs/architecture/QUALITY_GATES.md b/docs/i18n/uz/docs/architecture/QUALITY_GATES.md index bf893919888b..8e5ef8e49231 100644 --- a/docs/i18n/uz/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/uz/docs/architecture/QUALITY_GATES.md @@ -16,220 +16,227 @@ va vositalarga bogʻliq boʻlmagan takrorlash rejasi uchun --- -## Nazoratlar inventari va bajarish profillari +## Geytlar roʻyxati va bajarish profillari ### Nomzodni qabul qilish CI va Quality Gates ish jarayonlarining har biri barqaror hukm chiqaradi: `Gate / CI` va `Gate / Quality`. Ularning versiyalangan qabul qilish siyosati har bir yuqori oqimdagi vazifani -majburiy yoki tavsiyaviy sifatida belgilaydi. Qoʻllanadigan majburiy vazifa muvaffaqiyatli -bajarilishi shart: mavjud boʻlmagan, bekor qilingan, oʻtkazib yuborilgan, kutilayotgan va nomaʼlum +majburiy yoki tavsiyaviy sifatida belgilaydi. Qoʻllaniladigan majburiy vazifa muvaffaqiyatli +yakunlanishi kerak: mavjud boʻlmagan, bekor qilingan, oʻtkazib yuborilgan, kutilayotgan va nomaʼlum natijalar PASS holatini tasdiqlay olmaydi. Faqat hujjatlarga yoki faqat katalogga tegishli haqiqiy -tasnif kod yoʻnalishini qoʻllanmaydigan qilishi mumkin; qoralama PR qabul qilinadigan nomzod emas. -`hotfix` yorligʻi dalil taqdim etish talabini bekor qilmaydi. - -Har ikkala ish jarayoni PRlarni va main/release shoxlariga pushlarni, qoʻlda ishga tushirishni hamda -merge-group hodisalarini qamrab oladi. Push, qoʻlda ishga tushirish va merge-group toʻliq tanlovni -bajaradi. Forklar va merge-grouplar odatda self-hosted runnerlarni tanlaydigan vazifalar uchun -hosted runnerlardan foydalanadi; joriy etishdan oldin yetarli hosted sigʻimi tekshirilishi kerak. - -Har bir JSON tasdiq yozuvi checkout qilingan SHA, ish jarayoni bajarilishi va urinishni aniqlaydi. -CLI checkout/event SHA nomuvofiqligini rad etadi. Ish jarayoni testlari siyosat aʼzoligini hukm -vazifasining `needs` roʻyxatiga bogʻlaydi, shunda yangi yoki olib tashlangan yoʻnalish sezdirmasdan -yoʻqolib qolmaydi. Tasdiq yozuvlari nashr qilish, joylashtirish yoki mavjud tavsiyaviy skanerning -ichki ishlashini emas, oʻz ish jarayonini qamrab oladi. Shox qoidalarida har ikkala tekshiruv nomini -faollashtirish alohida maʼmuriy oʻzgarishdir; bu vazifalarni qoʻshishning oʻzi shoxni himoyalamaydi. - -### Statik skanerlash inventari - -Versiyalangan npm aliaslari inventari va statik skanerlash aʼzoligi +tasnif kod yoʻnalishini qoʻllanilmaydigan qilishi mumkin; qoralama PR qabul qilinadigan nomzod emas. +`hotfix` yorligʻi dalillarni taqdim etish talabini bekor qilmaydi. + +Har ikkala ish jarayoni PRʼlarni va main/release shoxlariga push qilishni, qoʻlda ishga tushirishni +hamda merge-group hodisalarini qamrab oladi. Push, dispatch va merge-group toʻliq tanlovni ishga +tushiradi. Forklar va merge-grouplar aks holda self-hosted runnerlarni tanlaydigan vazifalar uchun +hosted runnerlardan foydalanadi; joriy etishdan oldin hosted runnerlarning yetarli sigʻimi +tekshirilishi kerak. + +Har bir JSON kvitansiyasi checkout qilingan SHA, ish jarayoni ishga tushirilishi va urinishni +aniqlaydi. CLI checkout/event SHA nomuvofiqligini rad etadi. Ish jarayoni testlari siyosat +aʼzoligini hukm vazifasining `needs` roʻyxatiga bogʻlaydi, shuning uchun yangi yoki olib tashlangan +yoʻnalish sezdirmasdan yoʻqolib qololmaydi. Kvitansiyalar nashr qilish, joylashtirish yoki mavjud +tavsiyaviy skanerning ichki qismlarini emas, faqat oʻz ish jarayonini qamrab oladi. Shox +qoidalarida ikkala tekshiruv nomini faollashtirish alohida maʼmuriy oʻzgarishdir; bu vazifalarni +qoʻshishning oʻzi shoxni himoya qilmaydi. + +### Statik skanerlash roʻyxati + +Versiyalangan npm-alias roʻyxati va statik skanerlash aʼzoligi `config/quality/gate-manifest.json` faylida joylashgan. Skript nomlari va aniq buyruqlarni `package.json` bilan solishtirib tekshirish uchun `npm run check:gate-manifest` buyrugʻini bajaring; -qoʻshishlar, olib tashlashlar va buyruqlardagi ogʻishlar ham mahalliy hook, ham CI dagi -oʻzgarishlarni tasniflash vazifalarini muvaffaqiyatsiz qiladi. Alias ish jarayoni vazifasi, matritsa -nusxasi yoki test holati emas: bu miqdorlarni oʻzaro teng deb koʻrsatmaslik kerak. +qoʻshishlar, olib tashlashlar va buyruqlardagi chetlanishlar ham lokal hook, ham CIʼdagi +oʻzgarishlarni tasniflash vazifalarini muvaffaqiyatsiz yakunlaydi. Alias ish jarayoni vazifasi, +matritsa nusxasi yoki test holati emas: bu miqdorlarni oʻzaro almashtiriladigan tarzda koʻrsatish +mumkin emas. Tanlangan aliaslarni bajarmasdan koʻrib chiqish uchun `npm run quality:scan -- --list` yoki `npm run quality:scan:fast -- --list` buyrugʻidan foydalaning. Runner npm kirish nuqtasini ishga tushiradi, shuning uchun uning bajarilish muhiti (jumladan, sozlangan joylarda Bun) saqlanadi. -Manifest ushbu profillardan tashqaridagi aliaslarni alohida ishga tushiriladigan sifatida qayd etadi -va faqat oʻqish uchun moʻljallangan skanerlash profillarida texnik xizmat buyruqlari taqiqlanadi. +Manifest ushbu profillardan tashqaridagi aliaslarni alohida ishga tushiriladigan sifatida qayd +etadi va faqat oʻqish uchun moʻljallangan skanerlash profillarida texnik xizmat buyruqlari +taqiqlanadi. Bu profillar faqat statik skanerlashni qamrab oladi. Ular mahsulot testlari, qamrov, paketlash, tashqi tekshiruvlar yoki nomzodning relizga toʻliq qabul qilinishini tasdiqlamaydi. Ish jarayoniga qabul qilish bogʻlangan `config/quality/admission-policy.json` va -`scripts/quality/admission-verdict.mjs` fayllaridan foydalanadi. Release-observer profillari alohida -qoladi; ularning qoʻllanadigan tekshiruvlari va tasdiq yozuvlarini mustaqil ravishda koʻrib chiqing. -Quyidagi matnli inventar maʼlumotnoma boʻlib, nazoratning amalda bajarilganiga dalil emas. - -Skriptlar `scripts/check/` (siyosat nazoratlari) va `scripts/quality/` (ratchet mexanizmi) ostida -joylashgan. CI uchun asosiy haqiqat manbasi `.github/workflows/ci.yml` hisoblanadi. - -### Reliz PR uchun tezkor yoʻl (`quality.yml`) - -`.github/workflows/quality.yml` main/release PRlarida, himoyalangan shoxlarga pushlarda, qoʻlda -ishga tushirishda va merge-grouplarda CI imkoniyatlarini toʻldiradi. PRlar yoʻl boʻyicha -filtrlangan tezkor tekshiruvlardan foydalanadi. Doimiy ravishda oʻchirib qoʻyilgan takroriy build -olib tashlandi; haqiqiy build/package/boot tekshiruvlari CI da saqlanib qolgan. - -| Vazifa | Qamrov | Bloklovchi | -| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `Docs Gates (fast-path)` | Hujjatlar/kod PRlari; API hujjatlari havolalari va barcha hujjatlar | Ha | -| `Fast Quality Gates` | Kod PRlari; statik tekshiruvlar, typecheck, dashboard typecheck, taʼsirlangan unit testlar | Ha | -| `Forgotten sibling tests` | Kod PRlari; oʻzgartirilgan modullar statik isteʼmolchilar va nomzod sibling testlargacha kuzatiladi; barrel va dynamic-import yoʻllari allowlistdagi istisnolarga havolalar bilan tavsiyaviy diagnostika sifatida xabar qilinadi | **Tavsiyaviy** | -| `Vitest (fast-path)` | Kod PRlari; tezkor vitest toʻplami | Ha | -| `Unit Tests fast-path` | Kod PRlari; 4-shardli unit testlar toʻplami | Ha | -| `No new ESLint warnings` | Kod PRlari; suppressions-aware lint nazorati | Ha, forklar ham | -| `Merge integrity (changelog + generated skills)` | Qoralama boʻlmagan PRlar; changelog va yaratilgan skilllar sinxronligi | Ha, forklar ham | +`scripts/quality/admission-verdict.mjs` fayllaridan foydalanadi. Release-observer profillari +alohida qoladi; ularning tegishli tekshiruvlari va kvitansiyalarini mustaqil ravishda koʻrib +chiqing. Quyidagi matnli roʻyxat maʼlumotnoma boʻlib, geyt haqiqatda ishga tushganining isboti +emas. + +Skriptlar `scripts/check/` (siyosat geytlari) va `scripts/quality/` (ratchet mexanizmi) ostida +joylashgan. CI uchun haqiqat manbasi `.github/workflows/ci.yml` hisoblanadi. + +### Reliz PRʼlari uchun tezkor yoʻl (`quality.yml`) + +`.github/workflows/quality.yml` main/release PRʼlari, himoyalangan shoxlarga push qilish, +dispatch va merge-grouplarda CIʼni toʻldiradi. PRʼlar yoʻl boʻyicha filtrlangan tezkor +tekshiruvlardan foydalanadi. Doimiy ravishda oʻchirib qoʻyilgan takroriy build olib tashlangan; +haqiqiy build/package/boot tekshiruvlari CIʼda qoladi. + +| Vazifa | Qamrov | Bloklovchi | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `Docs Gates (fast-path)` | Hujjatlar/kod PRʼlari; API hujjatlari havolalari va barcha hujjatlar | Ha | +| `Fast Quality Gates` | Kod PRʼlari; statik tekshiruvlar, typecheck, dashboard typecheck, taʼsirlangan unit testlar | Ha | +| `Forgotten sibling tests` | Kod PRʼlari; oʻzgargan modullardan statik isteʼmolchilar va nomzod sibling testlargacha kuzatish; barrel va dynamic-import yoʻllari allowlistdagi tegishli istisnolar bilan tavsiyaviy diagnostika sifatida xabar qilinadi | **Tavsiyaviy** | +| `Vitest (fast-path)` | Kod PRʼlari; tezkor vitest toʻplami | Ha | +| `Unit Tests fast-path` | Kod PRʼlari; 4-shardli unit testlar toʻplami | Ha | +| `No new ESLint warnings` | Kod PRʼlari; suppressionlarni hisobga oluvchi lint himoyasi | Ha, forklar ham kiradi | +| `Merge integrity (changelog + generated skills)` | Qoralama boʻlmagan PRʼlar; changelog va yaratilgan skill sinxronizatsiyasi | Ha, forklar ham kiradi | #### Unutilgan sibling testlar hisoboti -`npm run check:forgotten-sibling-tests` test taʼsiri xaritasi ortidagi import resolverdan qayta -foydalanadi. Har bir oʻzgartirilgan production moduli uchun, agar nomzod test pull-request diffida -boʻlmasa, u deterministik -`oʻzgartirilgan modul/simvol -> statik isteʼmolchi -> nomzod sibling test` zanjirlarini xabar qiladi. -Markdown xulosasi va JSON natijasi bloklovchi rejimni joriy etishdan oldin kalibrlash uchun -`forgotten-sibling-tests` ish jarayoni artefakti sifatida saqlanadi. +`npm run check:forgotten-sibling-tests` test taʼsiri xaritasi asosidagi import resolveridan qayta +foydalanadi. Har bir oʻzgartirilgan ishlab chiqarish moduli uchun nomzod test pull-request diffida +mavjud boʻlmaganda deterministik +`oʻzgartirilgan modul/belgi -> statik isteʼmolchi -> nomzod sibling test` zanjirlarini hisobotga +kiritadi. Markdown xulosasi va JSON natijasi har qanday bloklovchi joriy etishdan oldin kalibrlash +uchun `forgotten-sibling-tests` ish jarayoni artefakti sifatida saqlanadi. -Barrel qayta eksportlari va dinamik importlar faqat aniqlik diagnostikasi hisoblanadi; ular hech qachon +Barrel qayta eksportlari va dinamik importlar faqat rezolyutsiya diagnostikasi hisoblanadi; ular hech qachon bloklovchi topilma yaratmaydi. Koʻrib chiqilgan istisnolar `config/quality/forgotten-sibling-allowlist.json` faylida saqlanadi. Har bir yozuv isteʼmolchi va nomzod -testni koʻrsatishi, aniq asos keltirishi hamda GitHub muammosi yoki pull request’iga havola berishi kerak. Notoʻgʻri tuzilgan yozuvlar -yopiq holatda xatoga uchraydi. Istisnolar oʻchirilgan nomzod testni yoki `.skip`/`.todo` qoʻshadigan diff’ni yashira olmaydi; -assertion’larni zaiflashtirish va boshqa niqoblash usullari mustaqil ravishda bloklovchi -`check:test-masking` nazoratiga tegishli boʻlib qoladi. +testni koʻrsatishi, aniq asos keltirishi hamda GitHub muammosi yoki pull request’ga havola berishi kerak. Notoʻgʻri shakllantirilgan yozuvlar +yopiq holatda xatolik bilan yakunlanadi. Istisnolar oʻchirilgan nomzod testni yoki `.skip`/`.todo` qoʻshadigan diff’ni yashira olmaydi; +assertion’larni zaiflashtirish va boshqa niqoblash holatlari mustaqil ravishda bloklovchi +`check:test-masking` darvozasi nazoratida qoladi. ### Vazifa: `lint` `main` uchun har bir PR’da ishga tushadi. Xatolik yuz bersa, birlashtirishni bloklaydi. -| Skript (`npm run ...`) | Tekshiradi | Bloklovchi | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `check:node-runtime` | Node.js versiyasi qoʻllab-quvvatlanadigan diapazonda ekanini | Ha | -| `check:cycles` | Siklik importlar — barcha `src/` + `open-sse/` modullari | Ha | -| `check:route-validation:t06` | Zod sxemalari barcha route’larda mavjudligini (Tier 6 siyosati) | Ha | -| `check:any-budget:t11` | `@ts-expect-error // any` soni belgilangan limitdan oshmasligini (Tier 11 catraca) | Ha | -| `check:provider-consistency` | `providers.ts` ichidagi har bir provayder `providerRegistry.ts` ichida mos yozuvga ega (va aksincha, ruxsat berilganlar roʻyxati doirasida) | Ha | -| `check:model-lifecycle` | Qoʻlda yuritiladigan uchta marshrutlash jadvali repozitoriyga kiritilgan hayot sikli surati (#11503) bilan muvofiq qoladi: `FITNESS_TABLE` (`taskFitness.ts`) `REGISTRY` marshrutlay oladigan, foydalanishdan chiqarilgan hech bir id uchun ball bermaydi; har bir `BUILT_IN_ALIASES` maqsadi `REGISTRY` ichida mavjud va foydalanishdan chiqarilgan id lar suratida yoʻq; `REGISTRY` ichida hamon mavjud boʻlgan har bir foydalanishdan chiqarilgan id boshqa joyga yoʻnaltiriladi yoki `allowedRetiredInCatalog` ichida keltiriladi; shuningdek, `DEFAULT_DEGRADATION_MAP` dagi hech bir manba yoki maqsad ushbu suratda foydalanishdan chiqarilgan deb koʻrsatilmagan. Bu modelga ayni paytda faol yuqori oqim xizmati tomonidan xizmat koʻrsatilayotganini isbotlamaydi. Oflayn — `config/quality/model-lifecycle.json` bilan taqqoslaydi; u `npm run quality:refresh-model-lifecycle` yordamida qoʻlda yangilanadi (tarmoq talab etiladi; CI tizimiga ulanmagan). `allowedRetiredInCatalog` — bosqichma-bosqich qisqartirish mexanizmi: yozuvni faqat kuzatuv masalasi bilan birga qoʻshing. | Ha | -| `check:fetch-targets` | Mijoz tomonidagi `src/` ichidagi har bir `fetch("/api/...")` haqiqiy `route.ts` fayliga yechiladi | Ha | -| `check:deps` | Repozitoriydagi har bir `package.json` boʻylab `npm install` orqali oʻrnatiladigan barcha bogʻliqliklar `dependency-allowlist.json` ichida mavjud; versiyasi mahkamlanmagan yoki slopsquattingga uchragan yangi paketlar belgilab qoʻyiladi | Ha | -| `audit:deps` | `npm audit` (ildiz + electron) — yuqori/kritik darajadagi tavsiyalar yoʻq (osv `check:vuln-ratchet` bilan qisman takrorlanadi; Ratsionalizatsiya beklogiga qarang) | Ha | -| `check:lockfile` | `package-lock.json` yaxlitligi — https reyestri, yaxlitlik xeshlari, xost almashtirishlari yoʻq | Ha | -| `check:licenses` | Ishlab chiqarish bogʻliqliklari uchun SPDX litsenziyalari ruxsat etilgan roʻyxati | Ha | -| `check:tracked-artifacts` | Build artefaktlari / commit qilingan `node_modules` ramziy havolalari yoʻq (husky pre-commit jarayonida ham ishga tushadi; pre-push ataylab yengil — #6716) | Ha | -| `check:ai-attribution` | PR commitlari, sarlavhasi yoki matnida AI/bot `Co-Authored-By` treyleri yoki AI tomonidan yaratilganlik futeri yoʻq — Qatʼiy qoida #16 (`quality.yml` faylidagi PR→`release/**` uchun fast-gates siklida — hodisa payloadini oʻqiydi, PRlardan tashqarida hech qanday amal bajarmaydi — hamda `ci.yml` faylidagi PR→`main` uchun lint jarayonining faqat PRga oid bosqichida; shuningdek, husky `commit-msg` hookida; inson hammualliflariga ruxsat beriladi; #14436) | -| `check:vitest-exclusions` | Har bir Vitest istisnosi kuzatuv issuesini koʻrsatadi va `config/quality/vitest-exclusions.json` faylida mavjud (#13204) | Ha | -| `check:file-size` | Hech bir manba fayli kengaytma boʻyicha belgilangan limitdan oshmaydi (ratchet: katta hajmli muzlatilgan fayllar `frozen` roʻyxatida) | Ha | -| `check:error-helper` | Executorlar/handlerlardagi xato javoblari `buildErrorBody()` / `sanitizeErrorMessage()` dan foydalanadi (Qatʼiy qoida #12) | Ha | -| `check:migration-numbering` | Migratsiya SQL fayllari ketma-ket raqamlangan, bo‘shliqlar yoki takrorlanishlar yo‘q | Ha | -| `check:public-creds` | `publicCreds.ts` faylidan tashqarida literal OAuth `client_id`/`client_secret` yoki Firebase Web kalitlari yo‘q (Qat’iy qoida #11) | Ha | -| `check:db-rules` | `src/lib/db/` modullaridan tashqarida xom SQL yo‘q; `localDb.ts` faylidan barrel-importlar yo‘q (Qat’iy qoidalar #2/#5) | Ha | -| `check:known-symbols` | Dispetcherlik jadvallarida ro‘yxatdan o‘tkazilgan provayder ijrochilari, marshrutlash strategiyalari va tarjimonlar diskdagi fayllarga mos keladi — yetim yoki e’lon qilinmagan simvollar yo‘q | Ha | -| `check:route-guard-membership` | Ichki jarayonni ishga tushiradigan har bir marshrut `isLocalOnlyPath()` tomonidan tasniflangan (Qat’iy qoidalar #15/#17) | Ha | -| `check:test-discovery` | Repodagi har bir `*.test.ts` / `*.spec.ts` fayli kamida bitta test ishga tushirgichi tomonidan aniqlanadi (xrapovik: `test-discovery-baseline.json` ichidagi yetimlar ro‘yxati faqat qisqarishi mumkin) | Ha | -| `check:agent-skills-sync` | Yaratilgan agent-skills artefaktlari ularning manba katalogiga mos keladi (tafovut yoʻq) | -| `check:provider-asset-provenance` | Provayder logotiplari/aktivlari uchun qayd etilgan kelib chiqish yozuvi mavjud | -| `lint:json` | JSON konfiguratsiya fayllari tahlildan muvaffaqiyatli oʻtadi va repo lint qoidalariga javob beradi | -| `typecheck:core` | TypeScript xatolarsiz kompilyatsiya qilinadi (faqat tavsiyaviy ogohlantirishlar) | Ha | -| `typecheck:noimplicit:core` | Qatʼiy `noImplicitAny` — kelajakka yoʻnaltirilgan; avvaldan mavjud koʻplab chaqiruv joylari hali ham annotatsiyalarni talab qiladi | **Tavsiyaviy** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `src/app/(dashboard)/**` doirasidagi `tsc` (#7033) — `typecheck:core`ning saralangan 27 faylli ruxsat roʻyxati hech qanday dashboard TSX faylini oʻz ichiga olmaydi, `next build` esa uni umuman tur tekshiruvidan oʻtkazmaydi (`next.config.mjs` faylida `ignoreBuildErrors: true` oʻrnatilgan), shu sababli u yerdagi yetim identifikator regressiyalari (#6625/#6909) CI uchun koʻrinmas edi. Har bir fayl/TS kodi boʻyicha muzlatilgan xatolar soni bazaviy koʻrsatkichi (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` bilan bir xil eskirganlikni nazorat qilish andozasi) bilan farqlar tekshiriladi — faqat bazaviy sondan ortiq YANGI xatolar tekshiruvdan oʻtolmaslikka sabab boʻladi; avvaldan mavjud xato tuzatilganda `--update` bilan bazaviy koʻrsatkichni pasaytiring. | Ha | +| Skript (`npm run ...`) | Tekshiradi | Bloklovchi | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `check:node-runtime` | Node.js versiyasi qoʻllab-quvvatlanadigan diapazonda ekanini | Ha | +| `check:cycles` | Butun `src/` + `open-sse/` boʻylab siklik importlarni (AST asosida, tsconfig `paths` rezolyutsiya qilingan). Oddiy ishga tushirish = tavsiyaviy, sikllarni roʻyxatlaydi. `check:cycles:ratchet` (CI ishga tushiradigan variant) sikllar soni `quality-baseline.json` faylidagi `metrics.cycles` chegarasidan oshganda bloklaydi — hozir 14, `direction: down`, shu sababli u faqat kamayishi mumkin (#15159 G-01/G-02) | Ha (ratchet) | +| `check:route-validation:t06` | Barcha routelarda Zod sxemalari mavjudligini (Tier 6 siyosati) | Ha | +| `check:any-budget:t11` | `@ts-expect-error // any` soni budjetdan oshmasligini (Tier 11 catraca) | Ha | +| `check:provider-consistency` | `providers.ts` ichidagi har bir provayder `providerRegistry.ts` ichida mos yozuvga ega (va aksincha, ruxsat etilgan roʻyxat doirasida) | Ha | +| `check:model-lifecycle` | Qoʻlda yuritiladigan uchta marshrutlash jadvali repozitoriyga kiritilgan hayotiy sikl surati (#11503) bilan muvofiq qoladi: `FITNESS_TABLE` (`taskFitness.ts`) `REGISTRY` marshrutlay oladigan, foydalanishdan chiqarilgan hech bir id uchun ball bermaydi; har bir `BUILT_IN_ALIASES` maqsadi `REGISTRY` ichida mavjud va foydalanishdan chiqarilgan id lar suratida yoʻq; `REGISTRY` ichida hamon mavjud boʻlgan har bir foydalanishdan chiqarilgan id boshqa manzilga yoʻnaltirilgan yoki `allowedRetiredInCatalog` ichida koʻrsatilgan; va `DEFAULT_DEGRADATION_MAP` ichidagi hech bir manba yoki maqsad ushbu suratda foydalanishdan chiqarilgan sifatida koʻrinmaydi. Bu modelga hozirda faol yuqori oqim tomonidan xizmat koʻrsatilayotganini isbotlamaydi. Oflayn — qoʻlda `npm run quality:refresh-model-lifecycle` (tarmoq; CI ga ulanmagan) orqali yangilanadigan `config/quality/model-lifecycle.json` bilan taqqoslaydi. `allowedRetiredInCatalog` — bosqichma-bosqich qisqartirish mexanizmi: yozuvni faqat kuzatuv muammosi bilan birga qoʻshing. | Ha | +| `check:fetch-targets` | Mijoz tomonidagi `src/` ichidagi har bir `fetch("/api/...")` haqiqiy `route.ts` fayliga mos keladi | Ha | +| `check:deps` | Repozitoriydagi har bir `package.json` boʻylab `npm install` orqali oʻrnatiladigan barcha bogʻliqliklar `dependency-allowlist.json` ichida mavjud; yangi aniq versiyaga mahkamlanmagan yoki slopsquattingga uchragan paketlar belgilanadi | Ha | +| `audit:deps` | `npm audit` (ildiz + electron) — yuqori/kritik darajadagi tavsiyalar yoʻq (osv `check:vuln-ratchet` bilan ustma-ust tushadi; Ratsionalizatsiya beklogiga qarang) | Ha | +| `check:lockfile` | `package-lock.json` yaxlitligi — https reyestri, yaxlitlik xeshlari, xost almashtirishlari yoʻq | Ha | +| `check:licenses` | Ishlab chiqarish bogʻliqliklari uchun ruxsat etilgan SPDX litsenziyalari roʻyxati | Ha | +| `check:tracked-artifacts` | Build artefaktlari / repozitoriyga commit qilingan `node_modules` ramziy havolalari mavjud emas (husky pre-commit jarayonida ham ishga tushadi; pre-push ataylab yengil qoldirilgan — #6716) | Ha | +| `check:ai-attribution` | PR commitlari, sarlavhasi yoki matnida AI/bot `Co-Authored-By` treyleri yoki AI tomonidan yaratilganlik futeri mavjud emas — Qatʼiy qoida #16 (`quality.yml` faylidagi PR→`release/**` uchun fast-gates siklida — hodisa payloadini oʻqiydi, PR boʻlmaganda hech narsa qilmaydi — va `ci.yml` lint jarayonidagi PR→`main` uchun faqat PRga tegishli qadamda; shuningdek, husky `commit-msg` hukida; inson hammualliflariga ruxsat beriladi; #14436) | +| `check:vitest-exclusions` | Har bir Vitest istisnosi kuzatuv muammosini koʻrsatadi va `config/quality/vitest-exclusions.json` faylida mavjud (#13204) | Ha | +| `check:file-size` | Hech bir manba fayli kengaytma uchun belgilangan limitdan oshmaydi (ratchet: katta fayllar `frozen` roʻyxatida muzlatilgan) | Ha | +| `check:error-helper` | Ijrochi va ishlov beruvchilardagi xato javoblari `buildErrorBody()` / `sanitizeErrorMessage()` dan foydalanadi (Qatʼiy qoida #12) | Ha | +| `check:migration-numbering` | Migratsiya SQL fayllari bo‘shliqlar va takrorlanishlarsiz ketma-ket raqamlangan | Ha | +| `check:public-creds` | `publicCreds.ts` faylidan tashqarida literal OAuth `client_id`/`client_secret` yoki Firebase Web kalitlari mavjud emas (Qat’iy qoida #11) | Ha | +| `check:db-rules` | `src/lib/db/` modullaridan tashqarida bevosita SQL mavjud emas; `localDb.ts` faylidan barrel-importlar mavjud emas (Qat’iy qoidalar #2/#5) | Ha | +| `check:known-symbols` | Dispetcher jadvallarida ro‘yxatdan o‘tkazilgan provayder ijrochilari, marshrutlash strategiyalari va tarjimonlar diskdagi fayllarga mos keladi — yetim yoki e’lon qilinmagan belgilar mavjud emas | Ha | +| `check:route-guard-membership` | Ichki jarayonni ishga tushiradigan har bir marshrut `isLocalOnlyPath()` tomonidan tasniflanadi (Qat’iy qoidalar #15/#17) | Ha | +| `check:test-discovery` | Repodagi har bir `*.test.ts` / `*.spec.ts` fayli kamida bitta test ishga tushiruvchisi tomonidan aniqlanadi (ratchet: `test-discovery-baseline.json` faylidagi yetimlar ro‘yxati faqat qisqarishi mumkin) | Ha | +| `check:agent-skills-sync` | Yaratilgan agent-skills artefaktlari ularning manba katalogiga mos keladi (tafovut yoʻq) | +| `check:provider-asset-provenance` | Provayder logotiplari/aktivlari uchun qayd etilgan kelib chiqish yozuvi mavjud | +| `lint:json` | JSON konfiguratsiya fayllari muvaffaqiyatli tahlil qilinadi va repo lint qoidalariga mos keladi | +| `typecheck:core` | TypeScript xatolarsiz kompilyatsiya qilinadi (faqat tavsiyaviy ogohlantirishlar) | Ha | +| `typecheck:noimplicit:core` | Qatʼiy `noImplicitAny` — kelajakka yoʻnaltirilgan; avvaldan mavjud koʻplab chaqiruv joylari hali ham annotatsiyalarni talab qiladi | **Tavsiyaviy** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `src/app/(dashboard)/**` doirasidagi `tsc` (#7033) — `typecheck:core`ning saralangan 27 faylli ruxsat roʻyxati hech qanday dashboard TSX faylini oʻz ichiga olmaydi, `next build` esa uni umuman tur tekshiruvidan oʻtkazmaydi (`next.config.mjs` faylida `ignoreBuildErrors: true` oʻrnatilgan), shu sababli u yerdagi yetim identifikator regressiyalari (#6625/#6909) CI uchun koʻrinmas edi. Har bir fayl va har bir TS kodi boʻyicha muzlatilgan xatolar soni bazaviy koʻrsatkichi (`config/quality/dashboard-typecheck-baseline.json`, `check:known-symbols` bilan bir xil eskirganlikni tekshirish andozasi) bilan farqlar solishtiriladi — faqat bazaviy koʻrsatkichdagi sondan ortiq YANGI xatolar tekshiruvni muvaffaqiyatsiz qiladi; avvaldan mavjud xato tuzatilganda `--update` orqali bazaviy koʻrsatkichni pasaytiring. | Ha | ### Vazifa: `quality-gate` `test-coverage`dan keyin ishga tushadi. Muvaffaqiyatsizlik yuz bersa, birlashtirishni bloklaydi. -| Skript | Tekshiradi | Bloklovchi | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -| `quality:collect` | `quality-metrics.json` faylini yaratadi (ESLint ogohlantirishlari soni, birlashtirilgan shard hisobotidan qamrov) | Ha (ratchetdan oldin) | -| `quality:ratchet` | `quality-baseline.json` faylidagi har bir metrika yomonlashmagan (ESLint ogohlantirishlari ≤ bazaviy qiymat; qamrov ≥ bazaviy qiymat) | Ha | -| `check:duplication` | Kod takrorlanishi (jscpd@4) `quality-baseline.json` faylidagi bazaviy qiymatdan oshmaydi | Ha | -| `check:complexity` | Fayl darajasidagi siklomatik murakkablik chegaradan oshmaydi (asosiy ESLint `complexity` + `max-lines-per-function`) | Ha | -| `check:cognitive-complexity` | Kognitiv murakkablik ratcheti (`eslint-plugin-sonarjs`) — alohida ESLint tekshiruvi; CI ikkalasini yagona `check:complexity-ratchets` bosqichi sifatida birlashtirib ishga tushiradi | Ha | -| `check:dead-code` | Ishlatilmaydigan eksportlar / fayllar ratcheti (knip) bazaviy qiymatga nisbatan yomonlashmaydi | Ha | -| `check:compression-budget` | Siqish benchmarki byudjeti — har bir engine uchun token tejashning quyi chegaralari yomonlashmasligi kerak | Ha | -| `check:type-coverage` | Tiplashtirilganlik foizi ratcheti (`type-coverage`) yomonlashmaydi; asosan `typecheck:noimplicit:core` o‘rnini bosadi | Ha | -| `check:codeql-ratchet` | Ochiq CodeQL ogohlantirishlari soni yomonlashmaydi (`gh api` orqali o‘qiydi; token bo‘lmasa, xatosiz o‘tkazib yuboradi) — yangilash davriyligi va qo‘lda ishga tushirish: quyidagi "CodeQL ratchet" bo‘limiga qarang | Ha | +| Skript | Tekshiradi | Bloklovchi | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `quality:collect` | `quality-metrics.json` faylini yaratadi (ESLint ogohlantirishlari soni, birlashtirilgan shard hisobotidan qamrov) | Ha (ratchetdan oldingi bosqich) | +| `quality:ratchet` | `quality-baseline.json` ichidagi har bir metrika yomonlashmagan (ESLint ogohlantirishlari ≤ bazaviy qiymat; qamrov ≥ bazaviy qiymat) | Ha | +| `check:duplication` | Kod takrorlanishi (jscpd@4) `quality-baseline.json` ichidagi bazaviy qiymatdan oshmaydi | Ha | +| `check:complexity` | Fayl darajasidagi siklomatik murakkablik chegaradan oshmaydi (asosiy ESLint `complexity` + `max-lines-per-function`) | Ha | +| `check:cognitive-complexity` | Kognitiv murakkablik ratcheti (`eslint-plugin-sonarjs`) — alohida ESLint tekshiruvi; CI ikkalasini yagona `check:complexity-ratchets` bosqichi sifatida birlashtirib ishga tushiradi | Ha | +| `check:dead-code` | Ishlatilmaydigan eksportlar/fayllar ratcheti (knip) bazaviy qiymatga nisbatan yomonlashmaydi | Ha | +| `check:compression-budget` | Siqish benchmarki byudjeti — har bir mexanizm uchun token tejashning minimal chegaralari yomonlashmasligi kerak | Ha | +| `check:type-coverage` | Tiplashtirilgan kod foizi ratcheti (`type-coverage`) yomonlashmaydi; asosan `typecheck:noimplicit:core` o‘rnini bosadi | Ha | +| `check:codeql-ratchet` | Ochiq CodeQL ogohlantirishlari soni yomonlashmaydi (`gh api` orqali o‘qiydi; token bo‘lmasa, xatosiz o‘tkazib yuboradi) — yangilash davriyligi va qo‘lda ishga tushirish: quyidagi «CodeQL ratcheti» bo‘limiga qarang | Ha | ### Vazifa: `quality-extended` Butun vazifa tavsiyaviy (`continue-on-error: true`). npm asosidagi ratchetlar amalda ishga tushadi; tashqi skanerlar `gh release download` orqali o‘rnatiladi va binar fayl -hali ham mavjud bo‘lmasa, o‘zini o‘zi o‘tkazib yuboradi (exit 0). +hali ham mavjud bo‘lmasa, o‘zini o‘zi o‘tkazib yuboradi (0 kodi bilan chiqadi). -| Skript | Tekshiradi | Bloklovchi | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `check:circular-deps` | Siklik bog‘liqliklar yo‘q (dpdm) | **Tavsiyaviy** | -| `check:bundle-size` | Bundle hajmi chegaradan oshmaydi | **Tavsiyaviy** | -| `check:secrets` | Sirlarni skanerlash (gitleaks) — binar fayl bo‘lmasa, o‘tkazib yuboradi | **Tavsiyaviy** | -| `check:vuln-ratchet` | Bog‘liqliklardagi zaifliklar (osv-scanner) yomonlashmaydi — binar fayl bo‘lmasa, o‘tkazib yuboradi | **Tavsiyaviy** | -| `check:workflows` | Workflow lintingi (actionlint + zizmor); yo‘q yoki buzilgan skanerlar, yaroqsiz hisobotlar yoxud mavjud bo‘lmagan ratchet bazaviy qiymati tekshiruvni INCOMPLETE holatida muvaffaqiyatsiz qiladi. Haqiqiy topilmalar tanlangan qat’iy/tavsiyaviy/ratchet siyosatiga amal qiladi | Bajarilishi shart; zizmor ratcheti CI’da bloklovchi | -| `check:openapi-breaking` | Ommaviy API shartnomasidagi (`openapi.yaml`) asosiy branchga nisbatan moslikni buzuvchi o‘zgarishlar (oasdiff) — `openapiBreaking=N` chiqaradi; oasdiff mavjud bo‘lmasa yoki asosiy spetsifikatsiyani aniqlab bo‘lmasa, o‘tkazib yuboradi | **Tavsiyaviy** | +| Skript | Tekshiradi | Bloklovchi | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | +| `check:circular-deps` | Siklik bog‘liqliklar yo‘q (dpdm) | **Tavsiyaviy** | +| `check:bundle-size` | To‘plam hajmi chegaradan oshmaydi | **Tavsiyaviy** | +| `check:secrets` | Sirlarni skanerlash (gitleaks) — binar fayl mavjud bo‘lmasa, o‘tkazib yuboradi | **Tavsiyaviy** | +| `check:vuln-ratchet` | Bog‘liqliklardagi zaifliklar (osv-scanner) yomonlashmaydi — binar fayl mavjud bo‘lmasa, o‘tkazib yuboradi | **Tavsiyaviy** | +| `check:workflows` | Ish jarayonlarini lint tekshiruvidan o‘tkazish (actionlint + zizmor); mavjud bo‘lmagan/ishlamaydigan skanerlar, yaroqsiz hisobotlar yoki mavjud bo‘lmagan ratchet bazaviy qiymati INCOMPLETE sifatida muvaffaqiyatsiz tugaydi. Haqiqiy topilmalar tanlangan qat’iy/tavsiyaviy/ratchet siyosatiga amal qiladi | Bajarilishi shart; zizmor ratcheti CI’da bloklaydi | +| `check:openapi-breaking` | Ommaviy API shartnomasidagi (`openapi.yaml`) asosiy tarmoqqa nisbatan buzuvchi o‘zgarishlar (oasdiff) — `openapiBreaking=N` chiqaradi; oasdiff mavjud bo‘lmasa yoki asosiy spetsifikatsiyani aniqlab bo‘lmasa, o‘tkazib yuboradi | **Tavsiyaviy** | ### Vazifa: `docs-sync-strict` -`main` branchiga yuborilgan har bir PR uchun ishga tushadi. Muvaffaqiyatsizlik yuz bersa, birlashtirishni bloklaydi. - -| Skript | Tekshiradi | Bloklovchi | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | -| `check:docs-all` | Quyidagi 6 ta quyi tekshiruvni ketma-ket ishga tushiradigan meta-tekshiruv | Ha | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versiyalarining muvofiqligi | Ha | -| ↳ `check:docs-counts` | Matndagi sonlar (provayderlar soni, migratsiyalar soni va hokazo) haqiqiy sonlarning ratchet oynasi doirasida ekanini | Ha | -| ↳ `check:env-doc-sync` | `.env.example` ichidagi har bir muhit o‘zgaruvchisi hujjatlardagi jadvalda qayd etilgani va aksincha ekanini | Ha | -| ↳ `check:deprecated-versions` | Hujjatlarda eskirgan versiya satrlari yo‘qligini | Ha | -| ↳ `check:doc-links` | Hujjatlardagi ichki markdown havolalari haqiqiy fayllarga olib borishini (`[text]`/`(path)` shakli) | Ha | -| ↳ `check:fabricated-docs` | Hujjatlarda keltirilgan marshrutlar, muhit o‘zgaruvchilari, CLI buyruqlari, hook nomlari va fayl yo‘llari kod bazasida mavjudligini. `--strict` orqali qat’iy tekshiruv; bayroqsiz — yumshoq xatolik. | Ha (CI ichida `--strict` orqali) | -| `check:cli-i18n` | CLI buyruq satrlari barcha i18n lokal fayllarida mavjudligini | Ha | -| `check:openapi-coverage` | OpenAPI spetsifikatsiyasi haqiqiy marshrutlarning kamida ratchet orqali belgilangan minimal miqdorini qamrab olishini | Ha | -| `check:openapi-security-tiers` | `openapi.yaml` ichidagi xavfsizlik darajasi annotatsiyalari `routeGuard.ts` tasniflariga mos kelishini | **Tavsiyaviy** | -| `check:openapi-routes` | `openapi.yaml` ichidagi har bir yo‘l haqiqiy `route.ts` fayliga mos kelishini (gallyutsinatsiyaga qarshi) | Ha | -| `check:docs-symbols` | `docs/**/*.md` ichidagi har bir `/api/...` havolasi haqiqiy `route.ts` fayliga mos kelishini (gallyutsinatsiyaga qarshi) | Ha | -| `i18n translation drift` | i18n lokal fayllaridagi tarjima qilinmagan kalitlar — faqat ogohlantirish | **Tavsiyaviy** | +`main` tarmog‘iga yuborilgan har bir PR uchun ishga tushadi. Muvaffaqiyatsizlikda birlashtirishni bloklaydi. + +| Skript | Tekshiradi | Bloklovchi | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | +| `check:docs-all` | Quyidagi 6 ta quyi tekshiruvni ketma-ket ishga tushiradigan meta-tekshiruv | Ha | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt versiyalarining muvofiqligi | Ha | +| ↳ `check:docs-counts` | Matndagi sonlar (provayderlar soni, migratsiyalar soni va hokazo) haqiqiy sonlarning ratchet oraligʻida ekanligi | Ha | +| ↳ `check:env-doc-sync` | `.env.example` ichidagi har bir muhit oʻzgaruvchisi hujjatlardagi jadvalda qayd etilgani va aksincha | Ha | +| ↳ `check:deprecated-versions` | Hujjatlarda eskirgan versiya satrlari yoʻqligi | Ha | +| ↳ `check:doc-links` | Hujjatlardagi ichki markdown havolalari haqiqiy fayllarga olib borishi (`[matn]`/`(yoʻl)` shakli) | Ha | +| ↳ `check:fabricated-docs` | Hujjatlarda keltirilgan marshrutlar, muhit oʻzgaruvchilari, CLI buyruqlari, hook nomlari va fayl yoʻllari kod bazasida mavjudligi. `--strict` orqali qatʼiy tekshiruv; bayroqsiz yumshoq xato. | Ha (CI ichida `--strict` orqali) | +| `check:cli-i18n` | CLI buyruq satrlari barcha i18n lokal fayllarida mavjudligi | Ha | +| `check:openapi-coverage` | OpenAPI spetsifikatsiyasi haqiqiy marshrutlarning kamida ratchet orqali belgilangan minimal miqdorini qamrab olishi | Ha | +| `check:openapi-security-tiers` | `openapi.yaml` ichidagi xavfsizlik darajasi annotatsiyalari `routeGuard.ts` tasniflariga mosligi | **Maslahat** | +| `check:openapi-routes` | `openapi.yaml` ichidagi har bir yoʻl haqiqiy `route.ts` fayliga olib borishi (gallyutsinatsiyaga qarshi) | Ha | +| `check:docs-symbols` | `docs/**/*.md` ichidagi har bir `/api/...` havolasi haqiqiy `route.ts` fayliga olib borishi (gallyutsinatsiyaga qarshi) | Ha | +| `i18n translation drift` | i18n lokal fayllaridagi tarjima qilinmagan kalitlar — faqat ogohlantirish | **Maslahat** | ### Vazifa: `i18n-ui-coverage` -| Skript | Tekshiradi | Bloklovchi | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `check-ui-keys-coverage` (inline) | UI i18n kalitlarining qamrovi ≥ 65% ekanini | Ha | -| `check-ui-value-drift` (inline) | Qayta yozilgan inglizcha **qiymat** ortida eskirgan tarjima qolmasligini | Ha | -| `check-new-key-coverage` (inline) | **Yangi** inglizcha kalit har bir lokalda tarjima qilinganini — `__MISSING__:` belgisi qabul qilinmaydi | Ha | -| `check-translation-ratio` | Har bir lokal uchun haqiqiy tarjimalar nisbati (ruxsat etilganlar ro‘yxatidan tashqaridagi inglizcha bilan bir xil / o‘rinbosar / yetishmayotgan qismlar) `config/quality/i18n-translation-baseline.json` + ruxsat etilgan og‘ishdan oshmasligi kerak | **Tavsiyaviy** | +| Skript | Tekshiradi | Bloklovchi | +| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | +| `check-ui-keys-coverage` (ichki) | UI i18n kalitlari qamrovi ≥ 65% | Ha | +| `check-ui-value-drift` (ichki) | Qayta yozilgan inglizcha **qiymat** ortida eskirgan tarjima qolmasligi | Ha | +| `check-new-key-coverage` (ichki) | **Yangi** inglizcha kalit har bir lokalda tarjima qilingan boʻlishi — `__MISSING__:` belgisi rad etiladi | Ha | +| `check-translation-ratio` | Har bir lokal uchun haqiqiy tarjimalar nisbati (ruxsat etilganlar roʻyxatidan tashqaridagi inglizcha bilan bir xil / toʻldiruvchi / yetishmayotgan qiymatlar) `config/quality/i18n-translation-baseline.json` + ruxsat etilgan ogʻishdan oshmasligi kerak | **Maslahat** | -`fetch-depth: 0` talab qilinadi — qiymatlar o‘zgarishini tekshirish jarayoni `en.json` faylini birlashtirish bazasi bilan solishtiradi. +`fetch-depth: 0` talab qilinadi — qiymat ogʻishi tekshiruvi `en.json` faylini birlashtirish bazasi bilan taqqoslaydi. -#### `check-ui-value-drift` — eskirgan tarjimalarni tekshirish +#### `check-ui-value-drift` — eskirgan tarjimalar tekshiruvi Boshqa tekshiruvlar tuzilmaviy jihatdan aniqlay olmaydigan i18n regressiyasini aniqlaydi: inglizcha qiymat -qayta yoziladi, ammo _avvalgi_ inglizcha matndan olingan tarjimalar o‘zgarishsiz qoladi, natijada -ingliz tilidan boshqa tildagi foydalanuvchilar ishonchli ohangda yozilgan, ammo endi noto‘g‘ri bo‘lgan matnni o‘qishda davom etadi. - -Bu muammo amalda relizga chiqib ketgan. Antigravity tizimiga kirish yordamchisi qo‘shilganda (#5203) -`oauthModal.googleOAuthWarning` qayta yozilgan; **43 ta lokaldan 39 tasida** operatorlarga «to‘liq -URL manzilini nusxalab, quyidagi maydonga joylashtirish» kerakligini aytuvchi matn saqlanib qolgan — -bu provayder uchun bunday jarayonni yakunlab bo‘lmaydi. Muammo #8463 gacha aniqlanmagan, chunki: - -- `sync-ui-keys` faqat **mavjud bo‘lmagan** kalitlarni avtomatik to‘ldiradi, **eskirganlarini** esa hech qachon yangilamaydi; -- `check-ui-keys-coverage` kalitning _mavjudligini_ hisoblaydi, shu sababli eskirgan tarjima qamrab olingan deb baholanadi; -- `check-translation-drift` `docs/i18n//**.md` hujjat ko‘zgularini kuzatadi — - u `src/i18n/messages/*.json` fayllarini hech qachon o‘qimaydi. 2026-09 qayta sinxronlashidan beri `docs-sync-strict` vazifasida bloklovchi: asosiy hujjatni tahrirlang → `npm run i18n:run -- --files=` (bo‘lim darajasida, tezkor). - -**Diffni hisobga oladi, bazaviy holatga tayanmaydi.** U birlashtirish bazasidagi `en.json` faylini +qayta yoziladi, ammo _avvalgi_ inglizcha matn asosida yaratilgan tarjimalar oʻzgarishsiz qoladi, natijada +ingliz tilida soʻzlashmaydigan foydalanuvchilar ishonchli ohangda yozilgan, ammo endi notoʻgʻri matnni oʻqishda davom etadilar. + +Bu holat amalda relizga chiqib ketgan. Antigravity tizimiga kirish yordamchisi qoʻshilganda (#5203) +`oauthModal.googleOAuthWarning` qayta yozilgan; **43 ta lokaldan 39 tasi** operatorlarga «toʻliq +URL manzilini nusxalab, uni quyiga joylashtirish» kerakligini aytuvchi matnni saqlab qolgan — bu provayder +uchun yakunlab boʻlmaydigan jarayon. Quyidagi sabablarga koʻra bu holat #8463 gacha +sezilmagan: + +- `sync-ui-keys` faqat **mavjud boʻlmagan** kalitlarni toʻldiradi, **eskirganlarini** esa hech qachon yangilamaydi; +- `check-ui-keys-coverage` kalitning _mavjudligini_ hisoblaydi, shuning uchun eskirgan tarjima qamrab olingan deb hisoblanadi; +- `check-translation-drift` `docs/i18n//**.md` hujjat nusxalarini kuzatadi — + u hech qachon `src/i18n/messages/*.json` fayllarini oʻqimaydi. 2026-09 qayta sinxronlashdan beri + `docs-sync-strict` vazifasida bloklovchi: asosiy hujjatni tahrirlang → `npm run i18n:run -- --files=` (boʻlim darajasida, kam xarajatli). + +**Farqlarni hisobga oladi, boshlangʻich holatga tayanmaydi.** U birlashtirish bazasidagi `en.json` faylini ishchi daraxt bilan taqqoslaydi; inglizcha qiymati oʻzgargan har bir kalit uchun hali ham -oʻzgartirilmagan tarjimani saqlayotgan har qanday lokal eskirgan hisoblanadi. Bu ataylab -**avvaldan mavjud qarzdorlikni muzlatadi** — diff uzoq vaqtdan beri mavjud tarjima qaysi eski -inglizcha matndan kelib chiqqanini aniqlay olmaydi, shuning uchun tekshiruv faqat joriy -oʻzgarish taʼsir qilgan qismlarni baholaydi. Muqobil yechim (har bir kalit uchun xesh bazaviy -holati) ~600 KB hajmdagi generatsiya qilingan faylni talab qiladi, bu mavjud eng katta bazaviy -fayldan 3× katta boʻlib, har bir i18n PRda qayta-qayta oʻzgaradi. +oʻzgartirilmagan tarjimani saqlayotgan har qanday lokalizatsiya eskirgan hisoblanadi. Bu ataylab +**avvaldan mavjud qarzdorlikni muzlatadi** — farq uzoq vaqtdan beri mavjud tarjima qaysi eski +inglizcha matndan kelib chiqqanini aniqlay olmaydi, shuning uchun toʻsiq faqat joriy oʻzgarish +tegadigan qismlarni baholaydi. Muqobil yechim (har bir kalit uchun xesh boshlangʻich holati) +taxminan 600 KB hajmdagi generatsiya qilingan faylni talab qilardi; bu mavjud eng katta +boshlangʻich holatdan 3 baravar katta va har bir i18n PRda oʻzgarib turardi. -Undan oʻtishning ikki yoʻli bor: +Uni qanoatlantirishning ikki yoʻli bor: 1. taʼsirlangan tarjimalarni yangilash yoki -2. ularni `__MISSING__:` qiymatiga oʻrnatish — shunda bajarilish muhiti tuzatilgan - inglizcha matnni taqdim etadi (`src/i18n/request.ts::deepMergeFallback`, #7258) va kalit - tarjima navbatiga qoʻshiladi. +2. ularni `__MISSING__:` qiymatiga oʻrnatish — shunda bajarilish muhiti + tuzatilgan inglizcha matnni taqdim etadi (`src/i18n/request.ts::deepMergeFallback`, #7258) + va kalit tarjima navbatiga qoʻshiladi. -Agar satrning **maʼnosi** oʻzgargan boʻlsa, **kalit nomini oʻzgartirishni** afzal koʻring: -yangi kalit eskirgan tarjimani meros qilib ololmaydi. #8463 aynan shu andozadan foydalangan. +Agar satrning **maʼnosi** oʻzgargan boʻlsa, **kalit nomini oʻzgartirish** afzal: yangi kalit +eskirgan tarjimani meros qilib ololmaydi. #8463 aynan shu andozadan foydalangan. ```bash npm run i18n:check-value-drift # qatʼiy (CI ishga tushiradigan variant) @@ -238,15 +245,16 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` Bazaviy katalogni oʻqib boʻlmaganda (bazaviy refsiz sayoz klon) `SKIP reason=base-unresolved` -bilan 0 kodida yakunlanadi va `check-openapi-breaking` xatti-harakatini takrorlaydi. +bilan 0 kodida yakunlanadi; bu `check-openapi-breaking` xatti-harakatiga mos keladi. ### Vazifa: `i18n` -Toʻliq i18n tekshiruv matritsasi (har bir lokal uchun bittadan vazifa). Butun vazifa tavsiyaviy. +Toʻliq i18n tekshiruv matritsasi (har bir lokalizatsiya uchun bittadan vazifa). Butun vazifa +tavsiyaviy hisoblanadi. -| Skript | Tekshiradi | Bloklash | -| ------------------------------- | -------------------------------------- | --------------------------------------------------------- | -| `validate_translation.py quick` | Har bir lokal tarjimasining toʻliqligi | **Tavsiyaviy** (butun vazifada `continue-on-error: true`) | +| Skript | Tekshiradi | Bloklash | +| ------------------------------- | ---------------------------------------------- | --------------------------------------------------------- | +| `validate_translation.py quick` | Har bir lokalizatsiya tarjimasining toʻliqligi | **Tavsiyaviy** (butun vazifada `continue-on-error: true`) | ### Vazifa: `pr-test-policy` @@ -254,32 +262,32 @@ Faqat pull requestlarda ishga tushadi. | Skript | Tekshiradi | Bloklash | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` yoki `bin/` ichidagi ishlab chiqarish kodini oʻzgartiruvchi PRlar testlarni qoʻshishi yoki yangilashi shart (Qatʼiy qoida #8) | Ha | -| `check:test-masking` | Oʻzgartirilgan test fayllari tasdiqlar sof sonini kamaytirmaydi yoki `assert.ok(true)` tavtologiyalarini qoʻshmaydi | Ha | -| `check:pr-evidence` | PR tavsifida oʻzgarish uchun test/VPS dalillari keltirilgan (PR matnini grep orqali tekshirib, Qatʼiy qoida #18ni mexanizatsiyalaydi — moʻrt, Backlogga qarang) | Ha | +| `check:pr-test-policy` | `src/`, `open-sse/`, `electron/` yoki `bin/` ichidagi ishlab chiqarish kodini oʻzgartiradigan PRlar testlarni qoʻshishi yoki yangilashi shart (Qatʼiy qoida #8) | Ha | +| `check:test-masking` | Oʻzgartirilgan test fayllari tasdiqlar umumiy sonini kamaytirmaydi yoki `assert.ok(true)` tavtologiyalarini qoʻshmaydi | Ha | +| `check:pr-evidence` | PR matnida oʻzgarish uchun test/VPS dalillari keltirilgan (PR matnini grep qilish orqali Qatʼiy qoida #18ni avtomatlashtiradi — moʻrt, Backlogga qarang) | Ha | ### Vazifa: `test-vitest` `build`dan keyin ishga tushadi. Xatolik yuz bersa, birlashtirishni bloklaydi. -| Toʻplam | Tekshiradi | Bloklash | -| ---------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP serveri (110 ta vosita), autoCombo, kesh — vitest ishga tushiruvchisi | Ha | -| `test:vitest:ui` | UI komponent testlari — vitest ishga tushiruvchisi | **Bloklaydi** — avvaldan mavjud xatoliklar `vitest.config.ts`da aniq istisno qilingan; yangi xatoliklar vazifaning bajarilmasligiga olib keladi | +| Toʻplam | Tekshiradi | Bloklash | +| ---------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP serveri (110 ta vosita), autoCombo, kesh — vitest test ijrochisi | Ha | +| `test:vitest:ui` | UI komponent testlari — vitest test ijrochisi | **Bloklaydi** — avvaldan mavjud xatoliklar `vitest.config.ts`da aniq istisno qilingan; yangi xatoliklar vazifani yiqitadi | ### Tungi ish jarayonlari (rejalashtirilgan, tavsiyaviy) -Ular cron jadvali boʻyicha (va `workflow_dispatch` orqali) ishga tushadi, PRlarda esa hech qachon -ishga tushmaydi. Ularning barchasi tavsiyaviy. +Ular cron jadvali boʻyicha (va `workflow_dispatch` orqali) ishga tushadi, PRlarda esa hech +qachon ishga tushmaydi. Ularning barchasi tavsiyaviy. -| Ish jarayoni | Tekshiradi | Bloklash | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `nightly-property` | tasodifiy seed va koʻp sonli ishga tushirishlar bilan fast-check xususiyat testlari | **Tavsiyaviy** | -| `nightly-resilience` | heap oʻsishi nazorati, tartibsizlik asosidagi xatolik kiritish, k6 yuklama/uzoq muddatli yuklama sinovi | **Tavsiyaviy** | -| `nightly-llm-security` | promptfoo inʼeksiya himoyasi (bloklash rejimi) + garak zondlari (provayder siri boʻlmasa oʻtkazib yuboriladi) | **Tavsiyaviy** | -| `nightly-schemathesis` | `docs/openapi.yaml` yordamida ishlayotgan OmniRoutega qarshi OpenAPI shartnomasini fuzzing qilish (schemathesis) — spetsifikatsiya buzilishlari / qayta ishlanmagan 500 xatolarini yuzaga chiqaradi (Fase 8 B.4) | **Tavsiyaviy** | -| `nightly-mutation` | tezkor unit yoʻnalishidagi Stryker mutatsion testlash bali — omon qolgan mutantlar zaif tasdiqlarni yuzaga chiqaradi | **Tavsiyaviy** | -| `nightly-compat` | qoʻllab-quvvatlanadigan `engines.node` diapazonlari boʻylab Node mexanizmi moslik matritsasi | **Tavsiyaviy** | +| Ish jarayoni | Tekshiradi | Bloklash | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `nightly-property` | tasodifiy boshlangʻich qiymat va koʻp sonli ishga tushirishlar bilan fast-check xususiyat testlari | **Tavsiyaviy** | +| `nightly-resilience` | heap oʻsishi toʻsigʻi, nosozliklarni xaotik kiritish, k6 yuklama/uzoq muddatli yuklama sinovlari | **Tavsiyaviy** | +| `nightly-llm-security` | promptfoo inyeksiya himoyasi (bloklash rejimi) + garak tekshiruvlari (provayder siri boʻlmasa oʻtkazib yuboriladi) | **Tavsiyaviy** | +| `nightly-schemathesis` | `docs/openapi.yaml` yordamida ishlayotgan OmniRoutega qarshi OpenAPI shartnomasini fuzzing qilish (schemathesis) — spetsifikatsiya buzilishlari / qayta ishlanmagan 500 xatolarini aniqlaydi (Fase 8 B.4) | **Tavsiyaviy** | +| `nightly-mutation` | tezkor modul testlari yoʻlagi boʻyicha Stryker mutatsion testlash bahosi — saqlanib qolgan mutantlar zaif tasdiqlarni koʻrsatadi | **Tavsiyaviy** | +| `nightly-compat` | qoʻllab-quvvatlanadigan `engines.node` diapazonlari boʻyicha Node dvigateli moslik matritsasi | **Tavsiyaviy** | --- @@ -551,43 +559,43 @@ hech qachon seanslarni buzmaydi. --- -## Ratsionalizatsiya uchun vazifalar roʻyxati (ROI tahlili — 9-bosqich, 3-toʻlqin) +## Ratsionalizatsiya uchun vazifalar roʻyxati (ROI koʻrib chiqishi — 9-bosqich, 3-toʻlqin) -Ushbu inventar 2026-06-17 kuni `ci.yml` bilan solishtirilib muvofiqlashtirildi (oldingi versiyada +Ushbu inventar 2026-06-17 sanasida `ci.yml` bilan solishtirib muvofiqlashtirildi (avvalgi versiyada `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence` keltirilmagan edi). Muvofiqlashtirilgan toʻplamning ROI tahlili +`check:codeql-ratchet`, `check:pr-evidence` tushirib qoldirilgan edi). Muvofiqlashtirilgan toʻplamning ROI tahlili quyidagi ratsionalizatsiya nomzodlarini aniqladi. **Birlashtirishlar mexanik CI -oʻzgarishlaridir; yoqish/oʻchirish va olib tashlashlar esa operator vakolatidagi siyosiy qarorlardir.** Quyidagilarning hech biri +oʻzgarishlaridir; holatni almashtirish/oʻchirish esa operator ixtiyoridagi siyosiy qarorlardir.** Quyidagilarning hech biri hali qoʻllanmagan. -**Yuqorida hujjatlashtirilmagan yana bir jihat** (tavsiyaviy, signal darajasi past): `docs-lint` vazifasi +**Yuqorida hujjatlashtirilmagan boshqa elementlar ham bor** (tavsiyaviy, signal darajasi past): `docs-lint` vazifasi (markdownlint + Vale, butun vazifa uchun `continue-on-error`) va alohida skaner ish jarayonlari `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` -`quality-baseline.json` ichida mavjud, ammo `ci.yml` dagi bloklovchi ratchet bilan bogʻlanmagan — metrika +`quality-baseline.json` ichida mavjud, ammo `ci.yml` ichidagi bloklovchi ratchet’ga ulanmagan — metrika hozirda yetim qolgan. -### Birlashtirish / takrorlanishni bartaraf etish (mexanik, xavfi pastroq) +### Birlashtirish / takrorlarni yoʻqotish (mexanik, xavfi pastroq) -Har bir nomzod 2026-06-17 kuni amaldagi gate holatiga nisbatan tekshirildi (ishon, ammo tekshir); -bir nechta «yaqqol» birlashtirishlar aslida yashirin qarzdorlikni oʻz ichiga olishi aniqlandi va ular **tayyor holda almashtirishga** yaramaydi. +Har bir nomzod 2026-06-17 sanasidagi amaldagi gate holatiga nisbatan tekshirildi (ishoning, ammo tekshiring); +bir nechta «ravshan» birlashtirishlar aslida yashirin texnik qarzni berkitishi aniqlandi va ular **toʻgʻridan-toʻgʻri qoʻllashga** yaroqli emas. -- **`check:docs-sync` ikki marta ishga tushadi** — `lint` vazifasida alohida va yana `check:docs-all` (`docs-sync-strict`) hamda husky pre-commit hook ichida. ✅ **BAJARILDI** — `lint` ichidagi alohida chaqiruv olib tashlandi. -- **CVE skanerlash** — ❌ **Toza birlashtirish EMAS.** `audit:deps` har qanday yuqori/kritik CVE holatida qatʼiy xato bilan yakunlanadi; `check:vuln-ratchet` (osv) esa faqat boshlangʻich koʻrsatkichga nisbatan _regressiya_ yuz berganda xato beradi (hozirda 1 ta MODERATE). Semantikalari turlicha — `audit:deps` olib tashlansa, mutlaq yuqori/kritik darajadagi gate yoʻqoladi. Ikkalasi ham saqlansin. -- **Sikllarni aniqlash** — ❌ **Toza birlashtirish EMAS.** `check:circular-deps` (dpdm) **91 ta sikl** haqida xabar beradi (shu sababli u tavsiyaviy); avval ularni bartaraf etmasdan uni bloklovchi holatga oʻtkazib boʻlmaydi va uning qamrovi yashil holatdagi, saralangan `check:cycles` dan kengroq. `check:cycles` bloklovchi boʻlib qolsin; 91 ta dpdm siklini bartaraf etish alohida vazifalar roʻyxatidir. -- **Murakkablik** — ✅ **BAJARILDI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): bitta ESLint oʻtishi, cyclomatic+max-lines va cognitive boshlangʻich koʻrsatkichlari mustaqil qolishi uchun hisoblash `ruleId` boʻyicha amalga oshiriladi; alohida `check:complexity` / `check:cognitive-complexity` mahalliy `--update` uchun saqlanib qoladi. -- **`/api` anti-gallyutsinatsiyasi** — ✅ **BAJARILDI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` uchun bitta FS inventarizatsiyasi, openapi-routes + docs-symbols hali ham mustaqil hisobot beradi; alohida tekshiruvlar mahalliy ishga tushirish uchun saqlanadi. -- **`check:node-runtime` 11 ta vazifada ishga tushadi** — ⚠️ **ROI past.** Har biri alohida runner va tekshiruv <1s davom etadi; jami tejash ~10s, evaziga esa har bir vazifa uchun arzon himoya yoʻqoladi. Bunday oʻzgarishga arzimaydi. -- **CI lint ichidagi `typecheck:noimplicit:core`** — ✅ **lint vazifasidan olib tashlandi** (avval tavsiyaviy `continue-on-error` edi); bloklovchi tip yuzasi — `typecheck:core` + `check:type-coverage`. Mahalliy skript saqlab qolindi. +- **`check:docs-sync` ikki marta ishga tushadi** — `lint` vazifasida alohida va yana `check:docs-all` (`docs-sync-strict`) ichida hamda husky pre-commit hook’ida. ✅ **BAJARILDI** — alohida `lint` chaqiruvi olib tashlandi. +- **CVE skanerlash** — ❌ **Toza birlashtirish EMAS.** `audit:deps` har qanday high/critical CVE’da qatʼiy xato bilan toʻxtaydi; `check:vuln-ratchet` (osv) esa faqat boshlangʻich koʻrsatkichga nisbatan _regressiya_ boʻlganda xato beradi (hozirda 1 MODERATE). Semantikasi turlicha — `audit:deps` olib tashlansa, mutlaq high/critical gate yoʻqoladi. Ikkalasini ham saqlang. +- **Sikllarni aniqlash** — ✅ **BAJARILDI** (#15159 G-01/G-02). Bu yerdagi eski matnda `check:cycles` «yashil, saralangan» gate deb atalgan va `check:circular-deps` (dpdm) 91 ta sikl haqida xabar bergani sababli uni bloklovchi sifatida saqlash asoslangan edi. Bu yashil holat **soxta yashil** edi: `check:cycles` 5 ta quyi katalogni (450 ta fayl) skanerlagan, faqat statik `import|export … from` bilan moslikni topgan va barcha `@/` hamda `@omniroute/open-sse/` spetsifikatorlarini tashlab yuborgan, shu sababli repozitoriyda ustun boʻlgan dinamik import + alias sikllarini koʻra olmagan. Tuzatildi: gate endi `src` + `open-sse` boʻylab yuradi (5023 ta fayl), spetsifikatorlarni TypeScript AST’dan yigʻadi (shuning uchun `import("…")` hisobga olinadi, tur pozitsiyasidagi `typeof import("…")` esa olinmaydi) va tsconfig `paths` qiymatlarini aniqlaydi. U 0 emas, **14** ta sikl topadi. Avvaldan mavjud 14 ta siklni gate PR ichida tuzatib boʻlmagani sababli `check:cycles` endi **ratchet** (`--ratchet`, `quality-baseline.json` ichida yuqori chegara `metrics.cycles.value = 14`, `direction: down`) — u har qanday _regressiya_ ni bloklaydi va son faqat kamayishi mumkin. CI `npm run check:cycles:ratchet` buyrugʻini ishga tushiradi. Bosqichma-bosqich kamaytirish **A-01** bilan birga olib boriladi. `check:circular-deps` (dpdm) kengroq ikkinchi xulosa sifatida tavsiyaviy holatda qoladi. +- **Murakkablik** — ✅ **BAJARILDI** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): bitta ESLint oʻtishi, hisoblash ruleId boʻyicha amalga oshiriladi, shuning uchun cyclomatic+max-lines va cognitive boshlangʻich koʻrsatkichlari mustaqil qoladi; alohida `check:complexity` / `check:cognitive-complexity` lokal `--update` uchun saqlanadi. +- **`/api` anti-gallyutsinatsiyasi** — ✅ **BAJARILDI** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): `src/app/api` uchun yagona FS inventari, openapi-routes + docs-symbols hamon mustaqil hisobot beradi; alohida tekshiruvlar lokal ishga tushirishlar uchun saqlanadi. +- **`check:node-runtime` 11 ta vazifada ishga tushadi** — ⚠️ **ROI past.** Har biri alohida runner va tekshiruv <1s davom etadi; umumiy tejash ~10s, buning evaziga har bir vazifa uchun arzon himoya yoʻqoladi. Bunday oʻzgartirishlar bilan bogʻliq ortiqcha tashvishga arzimaydi. +- **CI lint’dagi `typecheck:noimplicit:core`** — ✅ **lint vazifasidan olib tashlandi** (tavsiyaviy `continue-on-error` edi); bloklovchi tur yuzasi `typecheck:core` + `check:type-coverage` hisoblanadi. Lokal script saqlab qolindi. -### Yoqish/oʻchirish / qaror qilish (operator siyosati) +### Holatni almashtirish / qaror qabul qilish (operator siyosati) -- `check:openapi-security-tiers` (tavsiyaviy) — ❌ **Toʻgʻridan-toʻgʻri bloklovchi qilib boʻlmaydi.** U 0 kodi bilan yakunlanadi, ammo `LOCAL_ONLY_API_PREFIXES` ostidagi bir nechta `traffic-inspector` marshrutlarida `x-loopback-only: true` annotatsiyasi yoʻqligi haqida ogohlantiradi. Uni majburiy qilish uchun avval ushbu annotatsiyalarni `openapi.yaml` fayliga qoʻshish kerak. -- `typecheck:noimplicit:core` (tavsiyaviy) — asosan bloklovchi `check:type-coverage` ratcheti bilan qamrab olingan. Uni ratchetga aylantiring yoki takroriy ikkinchi `tsc` oʻtishini olib tashlang. -- `test:vitest:ui` (hozir **bloklovchi**) — avvaldan mavjud xatolar `vitest.config.ts` ichida `// #8618` kuzatuv izohlari bilan aniq istisno qilingan; yangi xatolar vazifani muvaffaqiyatsiz yakunlaydi. -- `check:secrets` (gitleaks, hujjatlashtirilgan 3 ta yolgʻon musbat natijada qotirilgan bloklovchi ratchet) — 0 ga tushirish uchun ushbu 3 tasini ruxsat etilganlar roʻyxatiga kiriting yoki tavsiyaviy darajaga tushiring. GitHub’ning ichki secret-scanning funksiyasi + `check:public-creds` bilan ustma-ust tushadi. -- `check:pr-evidence` (bloklovchi, PR matnidagi nasrni grep orqali tekshiradi) — yolgʻon musbat natijalar xavfi yuqori; olib tashlansa, Hard Rule #18 ijrosi zaiflashadi, shuning uchun bu haqiqiy siyosiy qarordir. -- `semgrep` (alohida tavsiyaviy tekshiruv) — OWASP oilalari boʻyicha CodeQL bilan ustma-ust tushadi; uning boshlangʻich koʻrsatkichini ratchetga ulang yoki olib tashlang. +- `check:openapi-security-tiers` (tavsiyaviy) — ❌ **Toza tarzda almashtirib boʻlmaydi.** U 0 kodi bilan yakunlanadi, ammo `LOCAL_ONLY_API_PREFIXES` ostidagi bir nechta `traffic-inspector` marshrutlarida `x-loopback-only: true` annotatsiyasi yoʻqligi haqida ogohlantiradi. Uni majburiy qilish uchun avval ushbu annotatsiyalarni `openapi.yaml` fayliga qoʻshish kerak. +- `typecheck:noimplicit:core` (tavsiyaviy) — asosan bloklovchi `check:type-coverage` ratchet’i bilan qamrab olingan. Uni ratchet’ga aylantiring yoki takroriy ikkinchi `tsc` oʻtishini olib tashlang. +- `test:vitest:ui` (hozir **bloklovchi**) — avvaldan mavjud nosozliklar `vitest.config.ts` ichida `// #8618` kuzatuv izohlari bilan ochiq tarzda istisno qilingan; yangi nosozliklar vazifani muvaffaqiyatsiz yakunlaydi. +- `check:secrets` (gitleaks, hujjatlashtirilgan 3 ta yolgʻon ijobiy natijada muzlatilgan bloklovchi ratchet) — 0 ga yetkazish uchun ushbu 3 tasini ruxsat etilganlar roʻyxatiga kiriting yoki tavsiyaviy darajaga tushiring. GitHub’ning ichki secret-scanning funksiyasi + `check:public-creds` bilan ustma-ust tushadi. +- `check:pr-evidence` (bloklovchi, PR-body matnini grep orqali qidiradi) — yolgʻon ijobiy natija xavfi yuqori; olib tashlansa, Hard Rule #18 ijrosi zaiflashadi, shuning uchun bu haqiqiy siyosiy qarordir. +- `semgrep` (alohida tavsiyaviy) — OWASP oilalari boʻyicha CodeQL bilan ustma-ust tushadi; uning boshlangʻich koʻrsatkichini ratchet’ga ulang yoki olib tashlang. --- diff --git a/docs/i18n/uz/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/uz/docs/compression/COMPRESSION_GUIDE.md index 60899dc4bfe0..9345afb47bca 100644 --- a/docs/i18n/uz/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/uz/docs/compression/COMPRESSION_GUIDE.md @@ -454,9 +454,7 @@ inglizcha koʻrsatma oʻrniga mahalliylashtirilgan koʻrsatma kiritiladi. `messages` mavjud boʻlgan tanada kontentni chetlab oʻtish (`shouldBypassCavemanOutputMode()` `open-sse/services/compression/outputMode.ts` ichida) oxirgi uchta xabarni tekshiradi va ular xavfsizlik, qaytarib boʻlmaydigan amal, aniqlashtirish yoki tartibga bogʻliq kalit -soʻzlarga mos kelsa, butun navbat uchun uslublarni oʻtkazib yuboradi. Chetlab oʻtish -boshqaruv panelidagi **Avtomatik aniqlik uchun chetlab oʻtish** almashtirgichi -(`cavemanOutputMode.autoClarity`) qanday sozlangan boʻlsa, shunga muvofiq ishlaydi. +soʻzlarga mos kelsa, butun navbat uchun uslublarni oʻtkazib yuboradi. Chetlab oʻtish, boshqaruv panelidagi **Avtomatik aniqlik uchun chetlab oʻtish** almashtirgichi (`cavemanOutputMode.autoClarity`) yoqilgan boʻlsa ishlaydi — bu standart holat. Almashtirgich oʻchirilganda tanlangan uslublar bu navbatlarda ham qoʻllanadi. Chetlab oʻtish navbatni davom ettirishga ruxsat berganda, `placeSystemInstruction()` (xuddi shu faylda) hech qachon yangi `messages[0]` yaratmaydi va blokni quyidagilardan diff --git a/docs/i18n/uz/docs/reference/ENVIRONMENT.md b/docs/i18n/uz/docs/reference/ENVIRONMENT.md index 7fe795a74726..b41ab1732752 100644 --- a/docs/i18n/uz/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/uz/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Majburiy sirlar -Ular birinchi ishga tushirishdan oldin **albatta** sozlanishi kerak. Ularsiz ilova ishga tushishni rad etadi yoki xavfsiz bo‘lmagan standart sozlamalar bilan ishlaydi. - -| O‘zgaruvchi | Majburiy | Standart qiymat | Manba fayli | Tavsif | -| ---------------------------- | ---------------------------------- | --------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Ha** | _(mavjud emas)_ | `src/lib/auth` | Boshqaruv panelidagi barcha seans cookie-fayllarini (JWT) imzolaydi/tekshiradi. `openssl rand -base64 48` bilan yarating. | -| `API_KEY_SECRET` | **Ha** | _(mavjud emas)_ | `src/lib/db/apiKeys.ts` | SQLite’da saqlanayotgan API kaliti qiymatlari uchun AES shifrlash kaliti. `openssl rand -hex 32` bilan yarating. | -| `INITIAL_PASSWORD` | **Ha** | `CHANGEME` | Boshlang‘ich sozlash skripti | Administrator boshqaruv panelining dastlabki parolini belgilaydi (`.env.example` faylidagi standart qiymatga mos — o‘zgartirishga majburlash uchun ataylab yaqqol xavfsiz bo‘lmagan holda qoldirilgan). **Birinchi foydalanishdan oldin o‘zgartiring.** Tizimga kirgandan so‘ng Dashboard → Settings → Security orqali o‘zgartiring. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ha** (ishlab chiqarish muhitida) | _(sozlanmagan)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Ichki Codex Responses WebSocket ko‘prigi uchun umumiy sir. Electron/brauzer WS releyi va OmniRoute o‘rtasidagi ko‘prik so‘rovlarini autentifikatsiya qiladi. ⚠️ **Ishlab chiqarish muhitida MAJBURIY — sozlanmagan bo‘lsa, barcha WS ko‘prik so‘rovlari rad etiladi.** `openssl rand -base64 32` bilan yarating. | -| `OMNIROUTE_SW_BUILD_ID` | Yo‘q | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA qobig‘i uchun service worker keshini aniq yangilash identifikatori (#11779); aniqlash zanjirida birinchi o‘rinda turadi. | -| `SOURCE_VERSION` | Yo‘q | _(sozlanmagan)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zanjirdagi ikkinchi qiymat — PaaS yig‘uvchilari (masalan, Heroku uslubidagilar) tomonidan joylashtirilgan commit sifatida belgilanadi. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Yo‘q | _(hosil qilinadi)_ | `src/shared/components/PwaRegister.tsx` | Mijoz `/sw.js?v=…` faylini ro‘yxatdan o‘tkazish uchun foydalanadigan yig‘ish vaqtidagi ochiq qiymat; yuqoridagi ikkita qiymatdan, so‘ng git SHA’dan hosil qilinadi. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Yo‘q (avtomatik) | _(har ishga tushishda avtomatik)_ | `src/server/authz/policies/management.ts` | Ishonchli tengdosh IP tamg‘asi OmniRoute’ning o‘z HTTP serveridan (`scripts/dev/peer-stamp.mjs`) kelganini tasdiqlovchi har bir jarayonga xos sir. Authz oraliq dasturi so‘rovning mahalliyligiga (LOCAL_ONLY marshrutlarini loopback/LAN asosida cheklash) faqat tamg‘ada ushbu token mavjud bo‘lganda ishonadi. Har ishga tushishda avtomatik yaratiladi — sozlamasdan qoldiring; faqat tamg‘ani ulashishi kerak bo‘lgan ko‘p jarayonli konfiguratsiyalar uchun uni qat’iy belgilang. | -| `OMNIROUTE_TRUSTED_PROXIES` | Yo‘q | _(sozlanmagan)_ | `scripts/dev/peer-stamp.mjs` | IP ruxsat berish/rad etish ro‘yxati ishonishi mumkin bo‘lgan teskari proksilarning vergul bilan ajratilgan IP manzillari yoki CIDR diapazonlari. Bu doimo ishonchli hisoblanadigan loopback, xususiy tarmoq manzillari va Cloudflare chekka tugunlariga qo‘shimcha hisoblanadi. Faqat boshqa ommaviy manzildagi proksi uchun kerak; aks holda filtr proksining o‘z manzilini tekshiradi. | +**Ha** deb belgilangan, sozlanmagan sirlar birinchi ishga tushirishda avtomatik generatsiya qilinadi (`JWT_SECRET`, `API_KEY_SECRET`) va maʼlumotlar katalogidagi `server.env` faylida saqlanadi — agar ushbu katalog vaqtincha (efemer) bo‘lsa, ularni aniq belgilab qo‘ying. Istisno — `OMNIROUTE_WS_BRIDGE_SECRET`: u `server.env` faylida saqlanmaydi — server kirish nuqtalari har bir yuklanishda faqat o‘sha jarayon ichidagi ko‘prik ishlatishi mumkin bo‘lgan vaqtincha qiymat yaratadi, shuning uchun tashqi ko‘prik mijozlari uni aniq belgilashi kerak. + +| O‘zgaruvchi | Majburiy | Standart qiymat | Manba fayli | Tavsif | +| ---------------------------- | ---------------------------------- | --------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Ha** | _(mavjud emas)_ | `src/lib/auth` | Boshqaruv panelidagi barcha seans cookie-fayllarini (JWT) imzolaydi/tekshiradi. `openssl rand -base64 48` bilan yarating. | +| `API_KEY_SECRET` | **Ha** | _(mavjud emas)_ | `src/lib/db/apiKeys.ts` | SQLite’da saqlanayotgan API kaliti qiymatlari uchun AES shifrlash kaliti. `openssl rand -hex 32` bilan yarating. | +| `INITIAL_PASSWORD` | Yo‘q | _(sozlanmagan)_ | `src/lib/auth/managementPassword.ts` | Yuklanish paytida, hali parol saqlanmagan bo‘lsa, boshqaruv paneli parolini belgilaydi; parol saqlangandan so‘ng e’tiborga olinmaydi. npm va manba koddan o‘rnatishlar `.env.example` faylini `.env` ga nusxalaydi, shuning uchun ular uning hammaga ma’lum bo‘lgan `CHANGEME` to‘ldiruvchisi bilan boshlanadi: birinchi yuklanishdan oldin uni almashtiring, yoki sozlanmagan qiymat (Docker tasvirining standarti) qilganidek, birinchi tashrifda parolni sozlash ustasida yaratish uchun ushbu `.env` faylida `INITIAL_PASSWORD=` (bo‘sh) deb belgilang. Saqlangan parolni Dashboard → Settings → Security orqali o‘zgartiring yoki `DATA_DIR` ni serverning maʼlumotlar katalogiga belgilab, `omniroute reset-password` buyrug‘ini (manba nusxasida `node bin/reset-password.mjs`) ishga tushiring. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Ha** (ishlab chiqarish muhitida) | _(sozlanmagan)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Ichki Codex Responses WebSocket ko‘prigi uchun umumiy sir. Electron/brauzer WS releyi va OmniRoute o‘rtasidagi ko‘prik so‘rovlarini autentifikatsiya qiladi. ⚠️ **Ishlab chiqarish muhitida MAJBURIY — sozlanmagan bo‘lsa, barcha WS ko‘prik so‘rovlari rad etiladi.** `openssl rand -base64 32` bilan yarating. | +| `OMNIROUTE_SW_BUILD_ID` | Yo‘q | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA qobig‘i uchun service worker keshini aniq yangilash identifikatori (#11779); aniqlash zanjirida birinchi o‘rinda turadi. | +| `SOURCE_VERSION` | Yo‘q | _(sozlanmagan)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Zanjirdagi ikkinchi qiymat — PaaS yig‘uvchilari (masalan, Heroku uslubidagilar) tomonidan joylashtirilgan commit sifatida belgilanadi. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Yo‘q | _(hosil qilinadi)_ | `src/shared/components/PwaRegister.tsx` | Mijoz `/sw.js?v=…` faylini ro‘yxatdan o‘tkazish uchun foydalanadigan yig‘ish vaqtidagi ochiq qiymat; yuqoridagi ikkita qiymatdan, so‘ng git SHA’dan hosil qilinadi. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Yo‘q (avtomatik) | _(har ishga tushishda avtomatik)_ | `src/server/authz/policies/management.ts` | Ishonchli tengdosh IP tamg‘asi OmniRoute’ning o‘z HTTP serveridan (`scripts/dev/peer-stamp.mjs`) kelganini tasdiqlovchi har bir jarayonga xos sir. Authz oraliq dasturi so‘rovning mahalliyligiga (LOCAL_ONLY marshrutlarini loopback/LAN asosida cheklash) faqat tamg‘ada ushbu token mavjud bo‘lganda ishonadi. Har ishga tushishda avtomatik yaratiladi — sozlamasdan qoldiring; faqat tamg‘ani ulashishi kerak bo‘lgan ko‘p jarayonli konfiguratsiyalar uchun uni qat’iy belgilang. | +| `OMNIROUTE_TRUSTED_PROXIES` | Yo‘q | _(sozlanmagan)_ | `scripts/dev/peer-stamp.mjs` | IP ruxsat berish/rad etish ro‘yxati ishonishi mumkin bo‘lgan teskari proksilarning vergul bilan ajratilgan IP manzillari yoki CIDR diapazonlari. Bu doimo ishonchli hisoblanadigan loopback, xususiy tarmoq manzillari va Cloudflare chekka tugunlariga qo‘shimcha hisoblanadi. Faqat boshqa ommaviy manzildagi proksi uchun kerak; aks holda filtr proksining o‘z manzilini tekshiradi. | ### Yaratish buyruqlari @@ -217,38 +217,38 @@ OmniRoute barcha doimiy saqlash amallari uchun **SQLite**dan (`better-sqlite3` o ## 4. Xavfsizlik va autentifikatsiya -| Oʻzgaruvchi | Standart qiymat | Manba fayli | Tavsif | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Qurilma identifikatorini yaratish uchun apparat identifikatorlari bilan birlashtiriladigan salt. Izolyatsiya uchun har bir joylashtirishda uni oʻzgartiring. | -| `OMNIROUTE_CLI_SALT` | _(oʻrnatilmagan = har bir oʻrnatish uchun tasodifiy salt, `/cli-token-salt.json` manzilida saqlanadi)_ | `src/lib/machineToken.ts` | Mahalliy CLI autentifikatsiya tokenini hosil qilish uchun HMAC salt. Bu qiymatni belgilash qurilmadagi barcha CLI tokenlarini yangilaydi va har doim saqlangan saltdan ustun turadi. `docs/security/CLI_TOKEN.md` fayliga qarang. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Seans cookie fayllarida `Secure` bayrogʻini oʻrnatadi. HTTPS ortida ishlaganda **`true` boʻlishi shart**. | -| `REQUIRE_API_KEY` | `false` | API vositachi dasturi | `true` boʻlganda, barcha `/v1/*` proksi soʻrovlari haqiqiy API kalitini oʻz ichiga olishi kerak. **Bu bayroq `GET /v1/models` soʻrovini cheklamaydi**, u buning oʻrniga boshqaruv paneliga kirish holatiga (`requireAuthForModels`) amal qiladi — shu sababli `/v1/models` dan olingan `401` xulosa chiqarish himoyalanganini anglatmaydi. `docs/security/INFERENCE_AUTH_POSTURE.md` fayliga qarang (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Boshqaruv paneli interfeysida API kalitlarining toʻliq qiymatlarini koʻrsatishga ruxsat beradi. Boshqaruv panelidagi funksiya bayroqlari orqali sozlanadi; umumiy nusxalarda xavfsizlik xatarini tugʻdiradi. | -| `NO_LOG_API_KEY_IDS` | _(boʻsh)_ | `src/lib/compliance/index.ts` | Soʻrovlarni jurnalga yozishni chetlab oʻtadigan, vergul bilan ajratilgan API kalit identifikatorlari (GDPR talablariga muvofiqlik). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(oʻrnatilmagan = cheklanmagan)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` ustuni null boʻlgan API kalitlariga qoʻllanadigan kunlik soʻrovlar zaxirasining muqobil qiymati. Oʻrnatilmagan yoki boʻsh boʻlsa: yashirin cheklov yoʻq (#2289, #11017). `0` ham xuddi shunday (cheklanmagan). Musbat butun N soni N/kun, 5N/hafta, 20N/oy limitlarini yoqadi. Notoʻgʻri shakllangan boʻsh boʻlmagan qiymatlar eski 1000/kun, 5000/hafta, 20000/oy oynalariga qaytadi. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Soʻrov tanasining ruxsat etilgan maksimal hajmi. Ushbu limitdan oshadigan foydali yuklarni rad etadi. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ushbu chegaraga teng yoki undan katta haqiqiy soʻrov tanalari JSON tahlilidan oldin jarayon uchun lokal boʻlgan ogʻir yuklamani atomar qabul qilish ijarasini oladi (BYTE yoʻli, jumladan `POST /v1/responses`). Tuzilmasi murakkab soʻrovlar bilan bir xil [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) sogʻlom zaxira sigʻimi istisnosi qoʻllanadi; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) qaytmasligi uchun u hanuz `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) bilan cheklanadi. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Chegaralangan qabul qilish jarayonida oʻqilgan baytlarga nisbatan qoʻllanadigan chat yoʻnalishining qatʼiy chegarasi; bunda `Content-Length` koʻrsatilmagan, notoʻgʻri yoki haqiqiy qiymatni aks ettirmaydigan soʻrovlar ham hisobga olinadi; chegaradan oshganda `413` qaytariladi. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(oʻrnatilmagan — soʻrovlar soni boʻyicha cheklov yoʻq)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** bu eski soʻrovlar SONI cheklovi endi faqat aniq oʻrnatilganda amal qiladi. Oʻrnatilmagan holatda (standart), katta yukli chat soʻrovlarini qabul qilish `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` orqali boshqariladi — bu **bitta jarayonda** (bitta V8 uyumi) jarayonning haqiqiy xotira chegarasidan avtomatik hisoblab chiqariladigan BAYT byudjetidir. Bir-birini qoplaydigan ikkita ~750k-tokenli `/v1/responses` soʻrovi ~12 Gi uyumlarni toʻxtatadi (#7849) — bu qatʼiy mahsulot maksimumi 2 ekanini emas, **xotira byudjeti** haqidagi ogohlantirishni anglatadi. Sogʻlom jarayon (uyum tashlab yuborish nisbatidan past boʻlsa) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` orqali bir vaqtning oʻzida koʻproq uzoq `/v1/responses` soʻrovlarini qabul qilishi MUMKIN. Oʻnlab uzoq SSE mijozlari (40–50) “maksimum 2” emas, balki uyum + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 bilan bogʻliq. “Xostdan foydalanish” uchun bu qiymatni koʻr-koʻrona oshirish #7849 muammosini qayta keltirib chiqaradi. **N ta mustaqil `DATA_DIR`** yordamida **uyumlar** sonini koʻpaytiring (#11024); bitta SQLite faylida hech qachon `replicas>1` ishlatmang. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(avtomatik hisoblab chiqariladi)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** avtomatik hisoblab chiqariladigan qabul qilish bayt byudjetini almashtirish qiymati (eng kichik V8/cgroup xotira chegarasining 25% qismi 8x vaqtinchalik kuchayishga boʻlinadi). Hisoblab chiqarilgan va aniq berilgan qiymatlar 8 MiB–2 GiB oraligʻi bilan cheklanadi. Samarali byudjetdan kattaroq tana darhol `413 body_exceeds_budget` xatosi bilan rad etiladi; alohida-alohida xizmat koʻrsatish mumkin boʻlgan tanalar oʻrtasidagi raqobat qayta urinish mumkin boʻlgan `503` holatida qoladi. Bir vaqtning oʻzida 40–50 ta uzoq SSE mijozi qatʼiy “maksimum 2” emas, balki ushbu byudjet + uyum bilan bogʻliq. Sozlashdan oldin `/api/monitoring/health` manzilida `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` qiymatlarini tekshiring. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BAYT va TUZILMA boʻyicha katta yukli soʻrovlarni qabul qilish uchun uyum bosimi sababli soʻrovlarni tashlab yuborish nisbati (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` chegarasidan oshgan parallel katta yukli soʻrov faqat uyum HAM ushbu nisbatga yetgan yoki undan oshgan boʻlsa, qayta urinish mumkin boʻlgan `503` bilan rad etiladi; sogʻlom uyumda esa u sogʻlom zaxira sigʻimi orqali qabul qilinadi. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standart `1`) | `src/shared/middleware/chatBodyAdmission.ts` | TUZILMA va BAYT (`admitChatRequest`, jumladan tanasi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` boʻlgan soʻrovlar) uchun sogʻlom uyum tezkor yoʻlining (#10437) chegaralangan qoʻshimcha sigʻimi. Bu cheklov boʻlmasa, band, ammo uyumi sogʻlom boʻlgan har bir soʻrov hech qanday yuqori chegarasiz qabul nazoratini chetlab oʻtardi. Sogʻlom uyum yoʻli orqali shuncha parallel ijara faol boʻlgach, keyingi band soʻrovlar haqiqiy uyum bosimida ishlatiladigan XUDDI OʻSHA chegaralangan kutish/tashlab yuborish yoʻliga oʻtadi. `0` chetlab oʻtishni butunlay oʻchiradi. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Tanasi bayt chegarasidan kichik boʻlsa ham, chat soʻrovini katta yukli deb tasniflaydigan xabarlar soni. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Tanasi bayt chegarasidan kichik boʻlsa ham, chat soʻrovini katta yukli deb tasniflaydigan vositalar soni. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Soʻrovni katta yukli deb tasniflaydigan satr hajmiga asoslangan konservativ token bahosi; bu provayder hisob-kitobidagi tokenlashtirish emas, balki qabul qilish xarajatining taxminiy koʻrsatkichidir. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (oʻchirilgan) | `src/shared/middleware/chatBodyAdmission.ts` | Ixtiyoriy ravishda yoqiladigan chat tarixining chegarasi. Standart holatda oʻchirilgan: xabarlar soni soʻrovning universal xususiyati emas, balki joylashtirish siyosatidir va bu yerda cheklash siqish konveyeri suhbatlarga xizmat koʻrsatilishini taʼminlashidan oldin ularni yakuniy `413` bilan rad etadi. Uyum oʻsishi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` va uyum bosimi sababli soʻrovlarni tashlab yuborish mexanizmi bilan chegaralanadi. Qatʼiy yuqori chegaraga muhtoj, xotirasi cheklangan joylashtirishlarda musbat qiymat oʻrnating; shunda chegaradan oshish tuzilmaviy, ixchamlashtirish talab qilinadigan `413` javobini oladi. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Xotirada toʻliq buferlanadigan oqimsiz yuqori oqim javobi uchun qatʼiy chegara. Bu chegaradan oshganda yuqori oqim oʻquvchisi bekor qilinadi va uyum tugaguncha cheklanmagan satr oʻsib borishi oʻrniga soʻrov tezda xato bilan yakunlanadi. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Yuqori oqim javob sarlavhalaridan uzatiladigan baytlarning maksimal hajmi. Limit oshib ketganda, odatiy teskari proksi sarlavha limitlaridan oshmaslik uchun ustuvorligi pastroq sarlavhalar (masalan, maxsus `x-codex-*`, `x-oai-request-id`) olib tashlanadi. Javob sarlavhasi hajmi kattalashishi evaziga yuqori oqimdan ko‘proq metama’lumot uzatish uchun kattaroq qiymat belgilang. | -| `CORS_ORIGIN` | _(o‘rnatilmagan)_ | `src/server/cors/origins.ts` | CORS uchun eski, bitta manbali ruxsat ro‘yxati. Yangi joylashtirishlar uchun `CORS_ALLOWED_ORIGINS` afzal. CORS faqat turli manbalardan murojaat qiluvchi brauzer API mijozlari uchun mo‘ljallangan; autentifikatsiyadan o‘tgan boshqaruv panelidagi yozish amallari esa bir xil manbali so‘rovlar va seansga bog‘langan CSRF himoyasidan foydalanadi. | -| `CORS_ALLOWED_ORIGINS` | _(o‘rnatilmagan)_ | `src/server/cors/origins.ts` | Vergul bilan ajratilgan CORS ruxsat ro‘yxati. `CORS_ALLOW_ALL=true` aniq sozlanmaguncha, universal belgi yuborilmaydi. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Istalgan brauzer `Origin` qiymatini aks ettirishga imkon beruvchi, faqat ishlab chiqish uchun mo‘ljallangan istisno mexanizmi. Uni umumiy yoki ishlab chiqarish muhitidagi joylashtirishlarda yoqmang. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Xususiy/loopback/link-local IP diapazonlariga yo‘naltirilgan provayder chaqiruvlarini bloklaydi. Faqat izolyatsiyalangan sinov muhitlarida o‘chiring. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Xususiy/mahalliy tarmoqlarga (localhost, 192.168.x.x, 10.x.x.x va hokazo) yo‘naltirilgan provayder URL manzillariga ruxsat beradi. **Mustaqil joylashtirilgan provayderlar uchun TALAB QILINADI** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). `false` bo‘lganda, boshqaruv paneli mahalliy URL manzillarini tekshirishni rad etadi. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Mahalliy/xususiy manzillardagi (127.0.0.1, localhost, LAN, xususiy diapazonlar) provayderlarni qo‘shish/tekshirishga ruxsat beradi — faqat provayderni tekshirish yo‘liga taalluqli. **Standart qiymat `true`** (avvalo mahalliy); faqat ommaviy manzillarga ruxsat beruvchi qat’iy bloklashni qo‘llash uchun `false` qilib belgilang. Bulut metama’lumotlari endpointlari (169.254.169.254, metadata.google.internal) bundan qat’i nazar bloklangan holda qoladi. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` yo‘nalishlariga (transkripsiyalar, nutq, tarjimalar) localhost tashqarisida joylashtirilgan OpenAI bilan mos provayder tugunidan foydalanishga ruxsat beradi. Standart holatda o‘chiq — audioni masofaviy xostga yo‘naltirish chiquvchi trafik identifikatsiyasini o‘zgartiradi va operatorning aniq qarori bo‘lishi kerak. Loopback/xususiy tugunlarga (localhost, 127.0.0.1, 172.16-31.x) har doim ruxsat beriladi va bu sozlama ularga ta’sir qilmaydi. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` so‘roviga (va xotira mexanizmining loopback qayta tartiblash bosqichiga) localhost tashqarisida joylashtirilgan OpenAI bilan mos provayder tugunidan — TEI, Infinity, vLLM va boshqalar ishlayotgan LAN qurilmasi yoki Tailscale tugunidan — foydalanishga ruxsat beradi. Standart holatda o‘chiq — masofaviy xostga yo‘naltirish chiquvchi trafik identifikatsiyasini o‘zgartiradi va operatorning aniq qarori bo‘lishi kerak. Loopback tugunlariga (localhost, 127.0.0.1, 172.16-31.x) har doim ruxsat beriladi va bu sozlama ularga ta’sir qilmaydi. Masofaviy tugunlar provayderning chiquvchi URL siyosatidan (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) ham o‘tishi kerak; trafik hech qachon bulut metama’lumotlari xostlariga yo‘naltirilmaydi. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC yoqilganda, foydalanuvchilar faqat OIDC yagona kirish tizimi orqali autentifikatsiyadan o‘tishi uchun parol bilan kirishni o‘chiradi. Oddiy `OIDC_DISABLE_PASSWORD_LOGIN` taxallusi ham qabul qilinadi; ayni kalitga ega boshqaruv paneli funksiya bayrog‘i ustuvor hisoblanadi. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` uchun oddiy taxallus (#10889). | +| Oʻzgaruvchi | Standart qiymat | Manba fayli | Tavsif | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Qurilma identifikatorini yaratish uchun apparat identifikatorlari bilan birlashtiriladigan salt. Izolyatsiya uchun har bir joylashtirishda uni oʻzgartiring. | +| `OMNIROUTE_CLI_SALT` | _(oʻrnatilmagan = har bir oʻrnatish uchun tasodifiy salt, `/cli-token-salt.json` manzilida saqlanadi)_ | `src/lib/machineToken.ts` | Mahalliy CLI autentifikatsiya tokenini hosil qilish uchun HMAC salt. Bu qiymatni belgilash qurilmadagi barcha CLI tokenlarini yangilaydi va har doim saqlangan saltdan ustun turadi. `docs/security/CLI_TOKEN.md` fayliga qarang. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Seans cookie fayllarida `Secure` bayrogʻini oʻrnatadi. HTTPS ortida ishlaganda **`true` boʻlishi shart**. | +| `REQUIRE_API_KEY` | `false` | API vositachi dasturi | `true` boʻlganda, barcha `/v1/*` proksi soʻrovlari haqiqiy API kalitini oʻz ichiga olishi kerak. **Bu bayroq `GET /v1/models` soʻrovini cheklamaydi**, u buning oʻrniga boshqaruv paneliga kirish holatiga (`requireAuthForModels`) amal qiladi — shu sababli `/v1/models` dan olingan `401` xulosa chiqarish himoyalanganini anglatmaydi. `docs/security/INFERENCE_AUTH_POSTURE.md` fayliga qarang (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Boshqaruv paneli interfeysida API kalitlarining toʻliq qiymatlarini koʻrsatishga ruxsat beradi. Boshqaruv panelidagi funksiya bayroqlari orqali sozlanadi; umumiy nusxalarda xavfsizlik xatarini tugʻdiradi. | +| `NO_LOG_API_KEY_IDS` | _(boʻsh)_ | `src/lib/compliance/index.ts` | Soʻrovlarni jurnalga yozishni chetlab oʻtadigan, vergul bilan ajratilgan API kalit identifikatorlari (GDPR talablariga muvofiqlik). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(oʻrnatilmagan = cheklanmagan)_ | `src/shared/utils/apiKeyPolicy.ts` | `rate_limits` ustuni null boʻlgan API kalitlariga qoʻllanadigan kunlik soʻrovlar zaxirasining muqobil qiymati. Oʻrnatilmagan yoki boʻsh boʻlsa: yashirin cheklov yoʻq (#2289, #11017). `0` ham xuddi shunday (cheklanmagan). Musbat butun N soni N/kun, 5N/hafta, 20N/oy limitlarini yoqadi. Notoʻgʻri shakllangan boʻsh boʻlmagan qiymatlar eski 1000/kun, 5000/hafta, 20000/oy oynalariga qaytadi. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Soʻrov tanasining ruxsat etilgan maksimal hajmi. Ushbu limitdan oshadigan foydali yuklarni rad etadi. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Ushbu chegaraga teng yoki undan katta haqiqiy soʻrov tanalari JSON tahlilidan oldin jarayon uchun lokal boʻlgan ogʻir yuklamani atomar qabul qilish ijarasini oladi (BYTE yoʻli, jumladan `POST /v1/responses`). Tuzilmasi murakkab soʻrovlar bilan bir xil [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) sogʻlom zaxira sigʻimi istisnosi qoʻllanadi; [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) qaytmasligi uchun u hanuz `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) bilan cheklanadi. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Chegaralangan qabul qilish jarayonida oʻqilgan baytlarga nisbatan qoʻllanadigan chat yoʻnalishining qatʼiy chegarasi; bunda `Content-Length` koʻrsatilmagan, notoʻgʻri yoki haqiqiy qiymatni aks ettirmaydigan soʻrovlar ham hisobga olinadi; chegaradan oshganda `413` qaytariladi. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(oʻrnatilmagan — soʻrovlar soni boʻyicha cheklov yoʻq)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** bu eski soʻrovlar SONI cheklovi endi faqat aniq oʻrnatilganda amal qiladi. Oʻrnatilmagan holatda (standart), katta yukli chat soʻrovlarini qabul qilish `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` orqali boshqariladi — bu **bitta jarayonda** (bitta V8 uyumi) jarayonning haqiqiy xotira chegarasidan avtomatik hisoblab chiqariladigan BAYT byudjetidir. Bir-birini qoplaydigan ikkita ~750k-tokenli `/v1/responses` soʻrovi ~12 Gi uyumlarni toʻxtatadi (#7849) — bu qatʼiy mahsulot maksimumi 2 ekanini emas, **xotira byudjeti** haqidagi ogohlantirishni anglatadi. Sogʻlom jarayon (uyum tashlab yuborish nisbatidan past boʻlsa) `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` orqali bir vaqtning oʻzida koʻproq uzoq `/v1/responses` soʻrovlarini qabul qilishi MUMKIN. Oʻnlab uzoq SSE mijozlari (40–50) “maksimum 2” emas, balki uyum + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110 bilan bogʻliq. “Xostdan foydalanish” uchun bu qiymatni koʻr-koʻrona oshirish #7849 muammosini qayta keltirib chiqaradi. **N ta mustaqil `DATA_DIR`** yordamida **uyumlar** sonini koʻpaytiring (#11024); bitta SQLite faylida hech qachon `replicas>1` ishlatmang. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(avtomatik hisoblab chiqariladi)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** avtomatik hisoblab chiqariladigan qabul qilish bayt byudjetini almashtirish qiymati (eng kichik V8/cgroup xotira chegarasining 25% qismi 8x vaqtinchalik kuchayishga boʻlinadi). Hisoblab chiqarilgan va aniq berilgan qiymatlar 8 MiB–2 GiB oraligʻi bilan cheklanadi. Samarali byudjetdan kattaroq tana darhol `413 body_exceeds_budget` xatosi bilan rad etiladi; alohida-alohida xizmat koʻrsatish mumkin boʻlgan tanalar oʻrtasidagi raqobat qayta urinish mumkin boʻlgan `503` holatida qoladi. Bir vaqtning oʻzida 40–50 ta uzoq SSE mijozi qatʼiy “maksimum 2” emas, balki ushbu byudjet + uyum bilan bogʻliq. Sozlashdan oldin `/api/monitoring/health` manzilida `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` qiymatlarini tekshiring. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | BAYT va TUZILMA boʻyicha katta yukli soʻrovlarni qabul qilish uchun uyum bosimi sababli soʻrovlarni tashlab yuborish nisbati (`heapUsed / heap_size_limit`) (#10183, #10268, #10437). `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` chegarasidan oshgan parallel katta yukli soʻrov faqat uyum HAM ushbu nisbatga yetgan yoki undan oshgan boʻlsa, qayta urinish mumkin boʻlgan `503` bilan rad etiladi; sogʻlom uyumda esa u sogʻlom zaxira sigʻimi orqali qabul qilinadi. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (standart `1`) | `src/shared/middleware/chatBodyAdmission.ts` | TUZILMA va BAYT (`admitChatRequest`, jumladan tanasi ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES` boʻlgan soʻrovlar) uchun sogʻlom uyum tezkor yoʻlining (#10437) chegaralangan qoʻshimcha sigʻimi. Bu cheklov boʻlmasa, band, ammo uyumi sogʻlom boʻlgan har bir soʻrov hech qanday yuqori chegarasiz qabul nazoratini chetlab oʻtardi. Sogʻlom uyum yoʻli orqali shuncha parallel ijara faol boʻlgach, keyingi band soʻrovlar haqiqiy uyum bosimida ishlatiladigan XUDDI OʻSHA chegaralangan kutish/tashlab yuborish yoʻliga oʻtadi. `0` chetlab oʻtishni butunlay oʻchiradi. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Tanasi bayt chegarasidan kichik boʻlsa ham, chat soʻrovini katta yukli deb tasniflaydigan xabarlar soni. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Tanasi bayt chegarasidan kichik boʻlsa ham, chat soʻrovini katta yukli deb tasniflaydigan vositalar soni. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Soʻrovni katta yukli deb tasniflaydigan satr hajmiga asoslangan konservativ token bahosi; bu provayder hisob-kitobidagi tokenlashtirish emas, balki qabul qilish xarajatining taxminiy koʻrsatkichidir. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (oʻchirilgan) | `src/shared/middleware/chatBodyAdmission.ts` | Ixtiyoriy ravishda yoqiladigan chat tarixining chegarasi. Standart holatda oʻchirilgan: xabarlar soni soʻrovning universal xususiyati emas, balki joylashtirish siyosatidir va bu yerda cheklash siqish konveyeri suhbatlarga xizmat koʻrsatilishini taʼminlashidan oldin ularni yakuniy `413` bilan rad etadi. Uyum oʻsishi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` va uyum bosimi sababli soʻrovlarni tashlab yuborish mexanizmi bilan chegaralanadi. Qatʼiy yuqori chegaraga muhtoj, xotirasi cheklangan joylashtirishlarda musbat qiymat oʻrnating; shunda chegaradan oshish tuzilmaviy, ixchamlashtirish talab qilinadigan `413` javobini oladi. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Xotirada toʻliq buferlanadigan oqimsiz yuqori oqim javobi uchun qatʼiy chegara. Bu chegaradan oshganda yuqori oqim oʻquvchisi bekor qilinadi va uyum tugaguncha cheklanmagan satr oʻsib borishi oʻrniga soʻrov tezda xato bilan yakunlanadi. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Yuqori oqim javob sarlavhalaridan uzatiladigan baytlarning maksimal hajmi. Limit oshib ketganda, odatiy teskari proksi sarlavha limitlaridan oshmaslik uchun ustuvorligi pastroq sarlavhalar (masalan, maxsus `x-codex-*`, `x-oai-request-id`) olib tashlanadi. Javob sarlavhasi hajmi kattalashishi evaziga yuqori oqimdan ko‘proq metama’lumot uzatish uchun kattaroq qiymat belgilang. | +| `CORS_ORIGIN` | _(o‘rnatilmagan)_ | `src/server/cors/origins.ts` | CORS uchun eski, bitta manbali ruxsat ro‘yxati. Yangi joylashtirishlar uchun `CORS_ALLOWED_ORIGINS` afzal. CORS faqat turli manbalardan murojaat qiluvchi brauzer API mijozlari uchun mo‘ljallangan; autentifikatsiyadan o‘tgan boshqaruv panelidagi yozish amallari esa bir xil manbali so‘rovlar va seansga bog‘langan CSRF himoyasidan foydalanadi. | +| `CORS_ALLOWED_ORIGINS` | _(o‘rnatilmagan)_ | `src/server/cors/origins.ts` | Vergul bilan ajratilgan CORS ruxsat ro‘yxati. `CORS_ALLOW_ALL=true` aniq sozlanmaguncha, universal belgi yuborilmaydi. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Istalgan brauzer `Origin` qiymatini aks ettirishga imkon beruvchi, faqat ishlab chiqish uchun mo‘ljallangan istisno mexanizmi. Uni umumiy yoki ishlab chiqarish muhitidagi joylashtirishlarda yoqmang. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Eski taxallus (alias). Uning boshqaruv panelidagi tugmasi (maʼlumotlar bazasidagi override) ushbu muhit qiymatidan oldin o‘qiladi; ikkalasidan birida `false`, `0`, `no` yoki `off` bo‘lsa, xost tekshiruvlari `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` qilganidek o‘chiriladi — hatto ushbu flagning boshqaruv paneli tugmasi o‘chiq holatda saqlangan bo‘lsa ham; qolgan qiymatlar himoyani quyidagi ikki flagga topshiradi. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Himoya rejimiga bo‘ysunuvchi provayder yo‘llarida — URL tekshirish, modellarni kashf qilish, provayder tuguni asosiy URL manzillari (masofaviy qayta tartiblash tugunlari bilan birga), proksi zaxira sinovi, shuningdek masofaviy rasm/media olish kabi boshqa provayderga aloqador chiquvchi yo‘llarda — chiquvchi URL himoyasining xost tekshiruvlarini, bulut metamaʼlumotlari bloki bilan birga (butun 169.254.0.0/16, metadata.google.internal), o‘chiradi. Bundan tashqari, xususiy webhook manzillariga ham ruxsat beradi; faqat bulut metamaʼlumotlari/link-local endpointlari ushbu flag yoniq bo‘lsa ham bloklangan holda qoladi. `BaseExecutor.execute()` orqali yuboriladigan chat so‘rovlari o‘zining bulut metamaʼlumotlari tekshiruvini saqlab qoladi. Mahalliy provayderlar standart sozlamalar bilan ishlaydi: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (standart `true`) allaqachon tekshirish, kashf qilish va provayder tuguni yo‘llarida mahalliy hamda LAN URL manzillariga ruxsat beradi; proksi zaxira sinovi va xususiy webhook manzillari faqat ushbu flagga qaraydi va u o‘chiq turganda mahalliy/LAN xostlarini bloklaydi. `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false` qiymatini bekor qiladi (override). Boshqaruv paneli tugmasidan saqlangan qiymat ushbu o‘zgaruvchidan ustun turadi. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Provayderni tekshirish, modellarni kashf qilish, chat va himoya rejimi boshqaradigan boshqa provayderga aloqador chiquvchi yo‘llar uchun mahalliy/xususiy manzillardagi (127.0.0.1, localhost, LAN, xususiy diapazonlar) provayder URL manzillariga ruxsat beradi. **Standart qiymat `true`** (avvalo mahalliy): bu holda himoya bulut metamaʼlumotlari endpointlarini bloklaydi — butun 169.254.0.0/16 va ma’lum bulut metamaʼlumotlari xost nomlari (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Faqat ommaviy rejim uchun `false` qilib belgilang; u xususiy va loopback xostlarini ham bloklaydi. Himoya har qanday DNS so‘rovidan oldin xost nomini yoki IP literalni yozilgan holicha tekshiradi; faqat ommaviy rejimda safe-fetch o‘rami qo‘shimcha ravishda xost nomini aniqlaydi va har qanday xususiy javobni rad etadi. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` bu tekshiruvlarni o‘z qatorida sanab o‘tilgan yo‘llarda o‘chiradi. Ichki mahalliy provayderlar — LM Studio, Ollama, vLLM, Llamafile va Triton kabililar — kalit tekshiruvi va chat uchun barcha xost tekshiruvlarini o‘tkazib yuboradi. Boshqaruv paneli tugmasidan saqlangan qiymat ushbu o‘zgaruvchidan ustun turadi. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | `/v1/audio/*` yo‘nalishlariga (transkripsiyalar, nutq, tarjimalar) localhost tashqarisida joylashtirilgan OpenAI bilan mos provayder tugunidan foydalanishga ruxsat beradi. Standart holatda o‘chiq — audioni masofaviy xostga yo‘naltirish chiquvchi trafik identifikatsiyasini o‘zgartiradi va operatorning aniq qarori bo‘lishi kerak. Loopback/xususiy tugunlarga (localhost, 127.0.0.1, 172.16-31.x) har doim ruxsat beriladi va bu sozlama ularga ta’sir qilmaydi. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | `POST /v1/rerank` so‘roviga (va xotira mexanizmining loopback qayta tartiblash bosqichiga) localhost tashqarisida joylashtirilgan OpenAI bilan mos provayder tugunidan — TEI, Infinity, vLLM va boshqalar ishlayotgan LAN qurilmasi yoki Tailscale tugunidan — foydalanishga ruxsat beradi. Standart holatda o‘chiq — masofaviy xostga yo‘naltirish chiquvchi trafik identifikatsiyasini o‘zgartiradi va operatorning aniq qarori bo‘lishi kerak. Loopback tugunlariga (localhost, 127.0.0.1, 172.16-31.x) har doim ruxsat beriladi va bu sozlama ularga ta’sir qilmaydi. Masofaviy tugunning asosiy URL manzili ham provayderning chiquvchi URL siyosatidan (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`) o‘tishi kerak. | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | OIDC yoqilganda, foydalanuvchilar faqat OIDC yagona kirish tizimi orqali autentifikatsiyadan o‘tishi uchun parol bilan kirishni o‘chiradi. Oddiy `OIDC_DISABLE_PASSWORD_LOGIN` taxallusi ham qabul qilinadi; ayni kalitga ega boshqaruv paneli funksiya bayrog‘i ustuvor hisoblanadi. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` uchun oddiy taxallus (#10889). | ### Mustahkamlash nazorat ro‘yxati diff --git a/docs/i18n/vi/CONTRIBUTING.md b/docs/i18n/vi/CONTRIBUTING.md index 816871fdbf34..9b7114f958ce 100644 --- a/docs/i18n/vi/CONTRIBUTING.md +++ b/docs/i18n/vi/CONTRIBUTING.md @@ -389,20 +389,20 @@ Viết các kiểm thử đơn vị trong `tests/unit/`, tối thiểu bao gồm ## Danh sách kiểm tra Pull Request -- [ ] Các bài kiểm thử đều vượt qua (`npm test`) -- [ ] Quá trình kiểm tra lint vượt qua (`npm run lint`) -- [ ] Quá trình build thành công (`npm run build`) +- [ ] Các bài kiểm thử đều đạt (`npm test`) +- [ ] Kiểm tra lint đạt (`npm run lint`) +- [ ] Build thành công (`npm run build`) - [ ] Đã thêm kiểu TypeScript cho các hàm và interface công khai mới -- [ ] Không có secret hoặc giá trị dự phòng được mã hóa cứng -- [ ] Thông tin xác thực upstream công khai được nhúng thông qua `resolvePublicCred()` (xem [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), tuyệt đối không dùng giá trị literal -- [ ] Phản hồi lỗi được xử lý thông qua `buildErrorBody()` / `sanitizeErrorMessage()` — không đưa stack trace thô vào nội dung phản hồi (xem [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Các lệnh shell (`exec` / `spawn`) truyền giá trị runtime qua `env`, không thông qua nội suy chuỗi +- [ ] Không có secret hoặc giá trị dự phòng được hardcode +- [ ] Thông tin xác thực upstream công khai được nhúng qua `resolvePublicCred()` (xem [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), tuyệt đối không dùng giá trị literal +- [ ] Các phản hồi lỗi được xử lý qua `buildErrorBody()` / `sanitizeErrorMessage()` — không đưa stack trace thô vào nội dung phản hồi (xem [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Các lệnh shell (`exec` / `spawn`) truyền giá trị tại thời điểm chạy qua `env`, không dùng phép nội suy chuỗi - [ ] Tất cả đầu vào đều được xác thực bằng schema Zod -- [ ] Đã thêm **fragment** changelog vào `changelog.d/{features|fixes|maintenance}/-.md` cho các thay đổi ảnh hưởng đến người dùng (xem [`changelog.d/README.md`](./changelog.d/README.md)) — **không** chỉnh sửa trực tiếp `CHANGELOG.md`; các fragment được tổng hợp khi phát hành và không bao giờ xung đột giữa các PR +- [ ] Đã thêm **fragment** changelog vào `changelog.d/{features|fixes|maintenance}/-.md` đối với các thay đổi ảnh hưởng đến người dùng (xem [`changelog.d/README.md`](./changelog.d/README.md)) — **không** chỉnh sửa trực tiếp `CHANGELOG.md`; các fragment được tổng hợp khi phát hành và không bao giờ gây xung đột giữa các PR - [ ] Đã cập nhật tài liệu (nếu áp dụng) -- [ ] Không tạo cảnh báo CodeQL / Secret-Scanning mới, hoặc mỗi cảnh báo đã được loại bỏ kèm lý do kỹ thuật có tham chiếu đến tài liệu `docs/security/` liên quan -- [ ] Các route tạo tiến trình con (`/api/mcp/`, `/api/cli-tools/runtime/`) được phân loại là `isLocalOnlyPath()` trong `src/server/authz/routeGuard.ts` — xem [Quy tắc bắt buộc #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Không có trailer `Co-Authored-By` trong thông điệp commit — các commit phải chỉ hiển thị danh tính Git của chủ sở hữu repository (Quy tắc bắt buộc #16) +- [ ] Không phát sinh cảnh báo CodeQL / Secret-Scanning mới, hoặc mỗi cảnh báo đều đã được loại bỏ kèm giải thích kỹ thuật có tham chiếu đến tài liệu liên quan trong `docs/security/` +- [ ] Các route khởi chạy tiến trình con (`/api/mcp/`, `/api/cli-tools/runtime/`) được phân loại là `isLocalOnlyPath()` trong `src/server/authz/routeGuard.ts` — xem [Quy tắc bắt buộc #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] Không có trailer `Co-authored-by` của AI/bot trong thông điệp commit (Quy tắc bắt buộc #16) — các cộng tác viên là con người có công việc được tái sử dụng phải được ghi nhận bằng trailer `Co-authored-by: Name ` tiêu chuẩn --- diff --git a/docs/i18n/vi/docs/architecture/QUALITY_GATES.md b/docs/i18n/vi/docs/architecture/QUALITY_GATES.md index 6c5052ee3e47..04de89f1a8a4 100644 --- a/docs/i18n/vi/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/vi/docs/architecture/QUALITY_GATES.md @@ -20,209 +20,211 @@ tái triển khai không phụ thuộc công cụ của cùng hệ thống này, ### Tiếp nhận ứng viên Mỗi workflow CI và Quality Gates đều đưa ra một phán quyết ổn định: `Gate / CI` và -`Gate / Quality`. Chính sách tiếp nhận được quản lý theo phiên bản của chúng liệt kê mọi job -thượng nguồn là bắt buộc hoặc mang tính tư vấn. Một job bắt buộc có áp dụng phải thành công: -các kết quả bị thiếu, bị hủy, bị bỏ qua, đang chờ xử lý và không xác định không thể thiết lập PASS. Một -phân loại hợp lệ chỉ dành cho tài liệu hoặc chỉ dành cho danh mục có thể khiến một lane mã không áp dụng; -PR nháp không phải là ứng viên được chấp nhận. Nhãn `hotfix` không miễn trừ yêu cầu bằng chứng. - -Cả hai workflow đều bao phủ PR và các lần push lên nhánh main/release, kích hoạt thủ công và +`Gate / Quality`. Chính sách tiếp nhận có phiên bản của chúng liệt kê mọi job thượng nguồn +là bắt buộc hoặc mang tính tư vấn. Một job bắt buộc có áp dụng phải thành công: các kết quả +bị thiếu, bị hủy, bị bỏ qua, đang chờ xử lý và không xác định không thể xác lập PASS. Một +phân loại hợp lệ chỉ dành cho tài liệu hoặc chỉ dành cho danh mục có thể khiến một lane mã +không áp dụng; PR nháp không phải là ứng viên được chấp nhận. Nhãn `hotfix` không miễn trừ +yêu cầu về bằng chứng. + +Cả hai workflow đều bao phủ PR và các lần push đến nhánh main/release, kích hoạt thủ công và sự kiện merge-group. Push, dispatch và merge-group chạy toàn bộ lựa chọn. Các fork -và merge group sử dụng runner được lưu trữ cho những job vốn chọn runner -tự lưu trữ; phải xác minh có đủ năng lực runner được lưu trữ trước khi triển khai. +và merge group sử dụng runner được lưu trữ cho những job vốn chọn runner tự lưu trữ; +phải xác minh có đủ dung lượng runner được lưu trữ trước khi triển khai. Mỗi biên nhận JSON xác định SHA đã checkout, lần chạy workflow và lần thử. -CLI từ chối trường hợp SHA của checkout/sự kiện không khớp. Các kiểm thử workflow ràng buộc tư cách thành viên trong chính sách -với danh sách `needs` của job phán quyết để một lane mới hoặc bị xóa không thể âm thầm biến mất. -Các biên nhận chỉ bao phủ chính workflow của chúng, không bao gồm việc phát hành, triển khai hoặc hoạt động nội bộ -của một trình quét tư vấn hiện có. Kích hoạt cả hai tên kiểm tra trong quy tắc nhánh là một -thay đổi quản trị riêng biệt; việc thêm các job này tự nó không bảo vệ nhánh. +CLI từ chối khi SHA của checkout/sự kiện không khớp. Các kiểm thử workflow ràng buộc tư cách thành viên trong chính sách +với danh sách `needs` của job phán quyết, để một lane mới hoặc bị loại bỏ không thể âm thầm biến mất. +Các biên nhận bao phủ chính workflow tương ứng, không bao gồm việc xuất bản, triển khai hoặc phần nội bộ +của một trình quét tư vấn hiện có. Việc kích hoạt cả hai tên kiểm tra trong quy tắc nhánh là một +thay đổi quản trị riêng biệt; việc thêm các job này tự nó không bảo vệ một nhánh. ### Danh mục quét tĩnh -Danh mục npm-alias được quản lý theo phiên bản và tư cách thành viên quét tĩnh nằm trong +Danh mục npm-alias có phiên bản và tư cách thành viên quét tĩnh nằm trong `config/quality/gate-manifest.json`. Chạy `npm run check:gate-manifest` để xác thực -tên script và lệnh chính xác đối chiếu với `package.json`; các mục thêm, xóa và -sai lệch lệnh sẽ khiến cả hook cục bộ lẫn các job phân loại thay đổi trong CI thất bại. -Một alias không phải là job workflow, phiên bản matrix hoặc ca kiểm thử: không được -trình bày các số lượng này như thể có thể hoán đổi cho nhau. +tên script và lệnh chính xác dựa trên `package.json`; việc thêm, xóa và +sai lệch lệnh sẽ làm thất bại cả hook cục bộ lẫn các job phân loại thay đổi trong CI. +Một alias không phải là job workflow, thực thể ma trận hoặc ca kiểm thử: không được +trình bày các số lượng này như thể chúng có thể hoán đổi cho nhau. Sử dụng `npm run quality:scan -- --list` hoặc `npm run quality:scan:fast -- --list` để kiểm tra các alias đã chọn mà không thực thi chúng. Runner gọi -điểm vào npm, do đó môi trường runtime của nó (bao gồm Bun ở nơi được cấu hình) được giữ nguyên. -Manifest ghi lại các alias bên ngoài những hồ sơ này là được gọi riêng biệt, và +điểm vào npm, nên môi trường runtime của nó (bao gồm Bun tại nơi được cấu hình) được giữ nguyên. +Manifest ghi lại các alias nằm ngoài những hồ sơ này là được gọi riêng biệt, và các lệnh bảo trì bị cấm trong hồ sơ quét chỉ đọc. -Các hồ sơ này chỉ bao phủ quá trình quét tĩnh. Chúng không chứng nhận kiểm thử sản phẩm, -độ bao phủ, đóng gói, kiểm tra bên ngoài hoặc toàn bộ tiêu chí chấp nhận phát hành của ứng viên. -Quy trình tiếp nhận workflow sử dụng `config/quality/admission-policy.json` được liên kết và -`scripts/quality/admission-verdict.mjs`. Các hồ sơ release-observer vẫn độc lập; -hãy kiểm tra riêng các mục kiểm tra và biên nhận áp dụng của chúng. Danh mục -diễn giải bên dưới là tài liệu tham khảo, không phải bằng chứng rằng một gate đã thực sự chạy. +Các hồ sơ này chỉ bao phủ quét tĩnh. Chúng không chứng nhận kiểm thử sản phẩm, +độ bao phủ, đóng gói, kiểm tra bên ngoài hoặc việc chấp nhận phát hành đầy đủ của ứng viên. +Việc tiếp nhận trong workflow sử dụng `config/quality/admission-policy.json` được liên kết và +`scripts/quality/admission-verdict.mjs`. Các hồ sơ release-observer vẫn tách biệt; +hãy kiểm tra độc lập các phép kiểm tra và biên nhận áp dụng cho chúng. Danh mục +mô tả bên dưới là tài liệu tham khảo, không phải bằng chứng rằng một gate thực sự đã chạy. -Các script nằm trong `scripts/check/` (gate chính sách) và `scripts/quality/` (bộ máy ratchet). -Nguồn thông tin chính xác của CI là `.github/workflows/ci.yml`. +Các script nằm trong `scripts/check/` (gate chính sách) và `scripts/quality/` (công cụ ratchet). +Nguồn chân lý của CI là `.github/workflows/ci.yml`. ### Đường dẫn nhanh cho PR phát hành (`quality.yml`) -`.github/workflows/quality.yml` bổ sung cho CI trên các PR main/release, các lần push lên -nhánh được bảo vệ, dispatch và merge group. Các PR sử dụng kiểm tra nhanh được lọc theo đường dẫn. Bản build trùng lặp -bị vô hiệu hóa vĩnh viễn đã được loại bỏ; các bước kiểm tra build/package/boot thực sự vẫn nằm trong CI. +`.github/workflows/quality.yml` bổ trợ CI trên các PR main/release, các lần push đến nhánh +được bảo vệ, dispatch và merge group. PR sử dụng các phép kiểm tra nhanh được lọc theo đường dẫn. +Bản build trùng lặp bị vô hiệu hóa vĩnh viễn đã được loại bỏ; các phép kiểm tra build/package/boot thực sự vẫn nằm trong CI. -| Job | Phạm vi | Chặn | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | -| `Docs Gates (fast-path)` | PR tài liệu/mã; tham chiếu tài liệu API và toàn bộ tài liệu | Có | -| `Fast Quality Gates` | PR mã; kiểm tra tĩnh, kiểm tra kiểu, kiểm tra kiểu dashboard, kiểm thử đơn vị bị ảnh hưởng | Có | -| `Forgotten sibling tests` | PR mã; lần theo các module đã thay đổi đến consumer tĩnh và kiểm thử sibling ứng viên; các đường dẫn barrel và dynamic-import được báo cáo dưới dạng chẩn đoán tư vấn, kèm các ngoại lệ trong allowlist được tham chiếu | **Tư vấn** | -| `Vitest (fast-path)` | PR mã; bộ kiểm thử vitest nhanh | Có | -| `Unit Tests fast-path` | PR mã; bộ kiểm thử đơn vị gồm 4 shard | Có | -| `No new ESLint warnings` | PR mã; cơ chế bảo vệ lint có nhận biết suppression | Có, bao gồm fork | -| `Merge integrity (changelog + generated skills)` | PR không phải bản nháp; đồng bộ changelog và skill được tạo | Có, bao gồm fork | +| Job | Phạm vi | Chặn | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `Docs Gates (fast-path)` | PR tài liệu/mã; tham chiếu tài liệu API và toàn bộ tài liệu | Có | +| `Fast Quality Gates` | PR mã; kiểm tra tĩnh, kiểm tra kiểu, kiểm tra kiểu dashboard, kiểm thử đơn vị bị ảnh hưởng | Có | +| `Forgotten sibling tests` | PR mã; các mô-đun đã thay đổi được truy vết tới consumer tĩnh và kiểm thử sibling ứng viên; các đường dẫn barrel và dynamic-import được báo cáo dưới dạng chẩn đoán tư vấn, với các ngoại lệ allowlist được tham chiếu | **Tư vấn** | +| `Vitest (fast-path)` | PR mã; bộ vitest nhanh | Có | +| `Unit Tests fast-path` | PR mã; bộ kiểm thử đơn vị 4 shard | Có | +| `No new ESLint warnings` | PR mã; biện pháp bảo vệ lint có nhận biết suppression | Có, bao gồm fork | +| `Merge integrity (changelog + generated skills)` | PR không phải bản nháp; đồng bộ changelog và skill được tạo | Có, bao gồm fork | #### Báo cáo kiểm thử sibling bị bỏ quên `npm run check:forgotten-sibling-tests` tái sử dụng trình phân giải import phía sau bản đồ tác động kiểm thử. -Đối với mỗi module production đã thay đổi, lệnh này báo cáo các chuỗi xác định +Đối với mọi mô-đun production đã thay đổi, lệnh này báo cáo các chuỗi xác định `changed module/symbol -> static consumer -> candidate sibling test` khi kiểm thử ứng viên -không có trong phần diff của pull request. Bản tóm tắt Markdown và kết quả JSON được lưu giữ dưới dạng -artifact workflow `forgotten-sibling-tests` để hiệu chỉnh trước mọi đợt triển khai có tính chặn. - -Các hoạt động tái xuất qua barrel và import động chỉ là chẩn đoán phân giải; chúng không bao giờ tạo ra một phát hiện chặn. Các ngoại lệ đã được xem xét nằm trong -`config/quality/forgotten-sibling-allowlist.json`. Mỗi mục phải nêu tên test tiêu thụ và test ứng viên, -đưa ra lý do cụ thể và liên kết đến một issue hoặc pull request trên GitHub. Các mục không đúng định dạng sẽ bị từ chối -theo cơ chế đóng. Ngoại lệ không thể bỏ qua một test ứng viên đã bị xóa hoặc một diff thêm `.skip`/`.todo`; +không có trong diff của pull request. Bản tóm tắt Markdown và kết quả JSON được lưu giữ dưới dạng +artifact workflow `forgotten-sibling-tests` để hiệu chỉnh trước bất kỳ đợt triển khai chặn nào. + +Các tái xuất barrel và import động chỉ là chẩn đoán phân giải; chúng không bao giờ tạo ra một +phát hiện có tính chặn. Các ngoại lệ đã được xem xét nằm trong +`config/quality/forgotten-sibling-allowlist.json`. Mỗi mục phải nêu tên bài kiểm thử phía sử dụng và bài kiểm thử +ứng viên, đưa ra lý do cụ thể và liên kết đến một issue hoặc pull request trên GitHub. Các mục sai định dạng sẽ +bị từ chối theo nguyên tắc đóng. Ngoại lệ không thể bỏ qua một bài kiểm thử ứng viên đã bị xóa hoặc một diff thêm `.skip`/`.todo`; việc làm yếu assertion và các hình thức che giấu khác vẫn thuộc phạm vi của cổng chặn độc lập `check:test-masking`. -### Job: `lint` +### Công việc: `lint` Chạy trên mọi PR vào `main`. Chặn hợp nhất khi thất bại. -| Script (`npm run ...`) | Xác thực | Chặn | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:node-runtime` | Phiên bản Node.js nằm trong phạm vi được hỗ trợ | Có | -| `check:cycles` | Import vòng — tất cả module trong `src/` + `open-sse/` | Có | -| `check:route-validation:t06` | Schema Zod hiện diện trên tất cả route (chính sách Tier 6) | Có | -| `check:any-budget:t11` | Số lượng `@ts-expect-error // any` không vượt quá ngân sách (catraca Tier 11) | Có | -| `check:provider-consistency` | Mọi provider trong `providers.ts` đều có mục tương ứng trong `providerRegistry.ts` (và ngược lại, trong phạm vi allowlist) | Có | -| `check:model-lifecycle` | Ba bảng định tuyến được duy trì thủ công luôn nhất quán với ảnh chụp nhanh vòng đời đã được đưa vào kho mã nguồn (#11503): `FITNESS_TABLE` (`taskFitness.ts`) không chấm điểm bất kỳ id đã ngừng hoạt động nào mà `REGISTRY` có thể định tuyến; mọi đích của `BUILT_IN_ALIASES` đều có trong `REGISTRY` và không có trong ảnh chụp nhanh id đã ngừng hoạt động; mọi id đã ngừng hoạt động nhưng vẫn còn trong `REGISTRY` đều được chuyển tiếp hoặc được liệt kê trong `allowedRetiredInCatalog`; và không có nguồn hoặc đích nào của `DEFAULT_DEGRADATION_MAP` xuất hiện dưới dạng đã ngừng hoạt động trong ảnh chụp nhanh đó. Điều này không chứng minh rằng một mô hình hiện đang được cung cấp bởi upstream đang hoạt động. Ngoại tuyến — so sánh với `config/quality/model-lifecycle.json`, được làm mới thủ công bằng `npm run quality:refresh-model-lifecycle` (cần mạng; không được tích hợp vào CI). `allowedRetiredInCatalog` là một cơ chế siết giảm dần: chỉ thêm mục khi có issue theo dõi. | Có | -| `check:fetch-targets` | Mọi `fetch("/api/...")` trong `src/` phía client đều phân giải tới một `route.ts` thực sự | Có | -| `check:deps` | Tất cả các dependency có thể cài đặt bằng `npm install` trong mọi `package.json` của repo đều có trong `dependency-allowlist.json`; các package mới không được ghim phiên bản hoặc có dấu hiệu slopsquatting sẽ bị gắn cờ | Có | -| `audit:deps` | `npm audit` (thư mục gốc + electron) — không có cảnh báo mức cao/nghiêm trọng (trùng lặp với osv `check:vuln-ratchet`; xem Rationalization Backlog) | Có | -| `check:lockfile` | Tính toàn vẹn của `package-lock.json` — registry HTTPS, hàm băm toàn vẹn, không ghi đè host | Có | -| `check:licenses` | Danh sách cho phép giấy phép SPDX đối với các phần phụ thuộc dùng trong môi trường production | Có | -| `check:tracked-artifacts` | Không có artifact bản dựng / symlink `node_modules` đã được commit (cũng chạy trong pre-commit của husky; pre-push được chủ ý giữ nhẹ — #6716) | Có | -| `check:ai-attribution` | Không có trailer `Co-Authored-By` của AI/bot hoặc footer về việc tạo bằng AI trong các commit, tiêu đề hoặc nội dung PR — Quy tắc cứng #16 (trong vòng lặp cổng kiểm tra nhanh `quality.yml` cho PR→`release/**` — đọc payload sự kiện, không thực hiện gì ngoài PR — và một bước chỉ dành cho PR trong tác vụ lint của `ci.yml` đối với PR→`main`; đồng thời có hook `commit-msg` của husky; cho phép đồng tác giả là con người; #14436) | -| `check:vitest-exclusions` | Mỗi mục loại trừ Vitest đều nêu một issue theo dõi và xuất hiện trong `config/quality/vitest-exclusions.json` (#13204) | Có | -| `check:file-size` | Không có tệp nguồn nào vượt quá giới hạn theo phần mở rộng (cơ chế ratchet: các tệp lớn được cố định trong danh sách `frozen`) | Có | -| `check:error-helper` | Các phản hồi lỗi trong executor/handler sử dụng `buildErrorBody()` / `sanitizeErrorMessage()` (Quy tắc cứng #12) | Có | -| `check:migration-numbering` | Các tệp SQL migration được đánh số tuần tự, không có khoảng trống hoặc số trùng lặp | Có | -| `check:public-creds` | Không có giá trị OAuth `client_id`/`client_secret` dạng literal hoặc khóa Firebase Web bên ngoài `publicCreds.ts` (Quy tắc bắt buộc #11) | Có | -| `check:db-rules` | Không có SQL thô bên ngoài các mô-đun `src/lib/db/`; không import qua barrel từ `localDb.ts` (Quy tắc bắt buộc #2/#5) | Có | -| `check:known-symbols` | Các executor của provider, chiến lược định tuyến và trình chuyển đổi được đăng ký trong bảng điều phối tương ứng phải khớp với các tệp trên đĩa — không có symbol mồ côi hoặc chưa được khai báo | Có | -| `check:route-guard-membership` | Mọi route khởi tạo tiến trình con đều được phân loại bởi `isLocalOnlyPath()` (Quy tắc bắt buộc #15/#17) | Có | -| `check:test-discovery` | Mọi tệp `*.test.ts` / `*.spec.ts` trong repo đều được ít nhất một trình chạy kiểm thử thu thập (cơ chế ratchet: danh sách tệp mồ côi trong `test-discovery-baseline.json` chỉ có thể thu hẹp) | Có | -| `check:agent-skills-sync` | Các artifact agent-skills được tạo khớp với danh mục nguồn tương ứng (không có sai lệch) | -| `check:provider-asset-provenance` | Logo/asset của nhà cung cấp có mục xuất xứ được ghi lại | -| `lint:json` | Các file cấu hình JSON được phân tích cú pháp thành công và đáp ứng các quy tắc lint của repo | -| `typecheck:core` | Biên dịch TypeScript không có lỗi (chỉ có cảnh báo mang tính khuyến nghị) | Có | -| `typecheck:noimplicit:core` | `noImplicitAny` nghiêm ngặt — hướng tới tương lai; nhiều vị trí gọi đã tồn tại từ trước vẫn cần chú thích | **Khuyến nghị** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` được giới hạn trong `src/app/(dashboard)/**` (#7033) — danh sách cho phép gồm 27 file được tuyển chọn của `typecheck:core` không bao gồm bất kỳ TSX nào của dashboard, và `next build` cũng không bao giờ kiểm tra kiểu cho chúng (`next.config.mjs` đặt `ignoreBuildErrors: true`), vì vậy các lỗi hồi quy về định danh mồ côi tại đó (#6625/#6909) không được CI phát hiện. So sánh chênh lệch với đường cơ sở cố định về số lượng theo từng file/mã TS (`config/quality/dashboard-typecheck-baseline.json`, cùng mẫu thực thi đối với dữ liệu cũ như `check:known-symbols`) — chỉ các lỗi MỚI vượt quá số lượng trong đường cơ sở mới khiến cổng kiểm tra thất bại; điều chỉnh giảm bằng `--update` khi một lỗi tồn tại từ trước được sửa. | Có | +| Script (`npm run ...`) | Xác thực | Chặn | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | +| `check:node-runtime` | Phiên bản Node.js nằm trong phạm vi được hỗ trợ | Có | +| `check:cycles` | Các import vòng trong toàn bộ `src/` + `open-sse/` (dựa trên AST, phân giải `paths` của tsconfig). Chế độ thuần túy = tư vấn, liệt kê các chu trình. `check:cycles:ratchet` (chế độ CI chạy) sẽ chặn khi số lượng vượt quá ngưỡng `metrics.cycles` trong `quality-baseline.json` — hiện là 14, `direction: down`, vì vậy giá trị này chỉ có thể giảm (#15159 G-01/G-02) | Có (ratchet) | +| `check:route-validation:t06` | Các schema Zod hiện diện trên tất cả các route (chính sách Tier 6) | Có | +| `check:any-budget:t11` | Số lượng `@ts-expect-error // any` không vượt quá ngân sách (catraca Tier 11) | Có | +| `check:provider-consistency` | Mọi nhà cung cấp trong `providers.ts` đều có một mục tương ứng trong `providerRegistry.ts` (và ngược lại, trong phạm vi danh sách cho phép) | Có | +| `check:model-lifecycle` | Ba bảng định tuyến được duy trì thủ công luôn nhất quán với bản chụp vòng đời đã được đưa vào kho mã nguồn (#11503): `FITNESS_TABLE` (`taskFitness.ts`) không chấm điểm bất kỳ id đã ngừng hoạt động nào mà `REGISTRY` có thể định tuyến; mọi đích của `BUILT_IN_ALIASES` đều có trong `REGISTRY` và không có trong bản chụp id đã ngừng hoạt động; mọi id đã ngừng hoạt động vẫn còn trong `REGISTRY` đều được chuyển tiếp hoặc được liệt kê trong `allowedRetiredInCatalog`; và không có nguồn hoặc đích nào của `DEFAULT_DEGRADATION_MAP` xuất hiện dưới dạng đã ngừng hoạt động trong bản chụp đó. Điều này không chứng minh rằng một mô hình hiện đang được cung cấp bởi một hệ thống thượng nguồn đang hoạt động. Ngoại tuyến — so sánh với `config/quality/model-lifecycle.json`, được làm mới thủ công bằng `npm run quality:refresh-model-lifecycle` (cần mạng; không được tích hợp vào CI). `allowedRetiredInCatalog` là cơ chế siết chặt dần để giảm tồn đọng: chỉ thêm mục khi có vấn đề theo dõi tương ứng. | Có | +| `check:fetch-targets` | Mọi `fetch("/api/...")` trong `src/` phía máy khách đều phân giải tới một `route.ts` thực sự tồn tại | Có | +| `check:deps` | Tất cả các phần phụ thuộc có thể cài đặt bằng `npm install` trên mọi `package.json` trong kho mã nguồn đều có trong `dependency-allowlist.json`; các gói mới không được ghim phiên bản hoặc có dấu hiệu slopsquatting sẽ bị gắn cờ | Có | +| `audit:deps` | `npm audit` (thư mục gốc + electron) — không có cảnh báo mức cao/nghiêm trọng (trùng lặp với `check:vuln-ratchet` của osv; xem Danh sách tồn đọng cần hợp lý hóa) | Có | +| `check:lockfile` | Tính toàn vẹn của `package-lock.json` — registry https, hàm băm toàn vẹn, không ghi đè máy chủ | Có | +| `check:licenses` | Danh sách cho phép giấy phép SPDX đối với các phụ thuộc production | Có | +| `check:tracked-artifacts` | Không có artifact bản dựng / symlink `node_modules` được commit (cũng chạy trong husky pre-commit; pre-push được cố ý giữ nhẹ — #6716) | Có | +| `check:ai-attribution` | Không có trailer `Co-Authored-By` của AI/bot hoặc footer cho biết nội dung do AI tạo trong các commit, tiêu đề hoặc nội dung PR — Quy tắc cứng #16 (trong vòng lặp fast-gates của `quality.yml` cho PR→`release/**` — đọc payload sự kiện, không thực hiện gì ngoài PR — và một bước chỉ dành cho PR trong tác vụ lint của `ci.yml` đối với PR→`main`; cũng áp dụng trong hook husky `commit-msg`; cho phép đồng tác giả là con người; #14436) | +| `check:vitest-exclusions` | Mỗi mục loại trừ Vitest phải nêu một issue theo dõi và xuất hiện trong `config/quality/vitest-exclusions.json` (#13204) | Có | +| `check:file-size` | Không có tệp nguồn nào vượt quá giới hạn theo phần mở rộng (ratchet: các tệp lớn bị đóng băng trong danh sách `frozen`) | Có | +| `check:error-helper` | Phản hồi lỗi trong các executor/handler sử dụng `buildErrorBody()` / `sanitizeErrorMessage()` (Quy tắc cứng #12) | Có | +| `check:migration-numbering` | Các tệp SQL migration được đánh số tuần tự, không có khoảng trống hoặc số trùng lặp | Có | +| `check:public-creds` | Không có giá trị OAuth `client_id`/`client_secret` dạng literal hoặc khóa Firebase Web bên ngoài `publicCreds.ts` (Quy tắc bắt buộc #11) | Có | +| `check:db-rules` | Không có SQL thô bên ngoài các mô-đun `src/lib/db/`; không barrel-import từ `localDb.ts` (Quy tắc bắt buộc #2/#5) | Có | +| `check:known-symbols` | Các trình thực thi provider, chiến lược định tuyến và trình chuyển đổi được đăng ký trong bảng điều phối tương ứng phải khớp với các tệp trên đĩa — không có symbol mồ côi hoặc chưa được khai báo | Có | +| `check:route-guard-membership` | Mọi route tạo tiến trình con đều được phân loại bởi `isLocalOnlyPath()` (Quy tắc bắt buộc #15/#17) | Có | +| `check:test-discovery` | Mọi tệp `*.test.ts` / `*.spec.ts` trong repo đều được ít nhất một test runner thu thập (ratchet: danh sách tệp mồ côi trong `test-discovery-baseline.json` chỉ có thể thu hẹp) | Có | +| `check:agent-skills-sync` | Các artifact agent-skills được tạo khớp với catalog nguồn của chúng (không có sai lệch) | +| `check:provider-asset-provenance` | Logo/asset của nhà cung cấp có mục xuất xứ được ghi nhận | +| `lint:json` | Các tệp cấu hình JSON có thể được phân tích cú pháp và đáp ứng các quy tắc lint của repo | +| `typecheck:core` | Biên dịch TypeScript không có lỗi (chỉ có cảnh báo mang tính tư vấn) | Có | +| `typecheck:noimplicit:core` | `noImplicitAny` nghiêm ngặt — hướng đến tương lai; nhiều vị trí gọi có sẵn vẫn cần chú thích | **Tư vấn** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` giới hạn trong `src/app/(dashboard)/**` (#7033) — danh sách cho phép gồm 27 tệp được tuyển chọn của `typecheck:core` không bao gồm bất kỳ tệp TSX nào của dashboard, và `next build` cũng không bao giờ kiểm tra kiểu cho chúng (`next.config.mjs` đặt `ignoreBuildErrors: true`), vì vậy các lỗi hồi quy về định danh không tồn tại tại đó (#6625/#6909) không bị CI phát hiện. So sánh với đường cơ sở cố định về số lượng lỗi theo từng tệp/từng mã TS (`config/quality/dashboard-typecheck-baseline.json`, cùng mẫu thực thi đối với dữ liệu lỗi thời như `check:known-symbols`) — chỉ các lỗi MỚI vượt quá số lượng trong đường cơ sở mới khiến cổng kiểm tra thất bại; điều chỉnh giảm dần bằng `--update` khi một lỗi có sẵn được khắc phục. | Có | ### Job: `quality-gate` Chạy sau `test-coverage`. Chặn hợp nhất khi thất bại. -| Script | Xác thực | Chặn | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | -| `quality:collect` | Xuất `quality-metrics.json` (số lượng cảnh báo ESLint, độ bao phủ từ báo cáo phân đoạn đã hợp nhất) | Có (trước bước ratchet) | -| `quality:ratchet` | Mỗi chỉ số trong `quality-baseline.json` không bị suy giảm (cảnh báo ESLint ≤ đường cơ sở; độ bao phủ ≥ đường cơ sở) | Có | -| `check:duplication` | Mức độ trùng lặp mã (jscpd@4) không vượt quá đường cơ sở trong `quality-baseline.json` | Có | -| `check:complexity` | Độ phức tạp cyclomatic ở cấp độ tệp không vượt quá giới hạn (quy tắc `complexity` cốt lõi của ESLint + `max-lines-per-function`) | Có | -| `check:cognitive-complexity` | Ratchet độ phức tạp nhận thức (`eslint-plugin-sonarjs`) — lượt chạy ESLint riêng biệt; CI chạy cả hai và hợp nhất thành một bước `check:complexity-ratchets` duy nhất | Có | -| `check:dead-code` | Ratchet các export / tệp không được sử dụng (knip) không bị suy giảm so với đường cơ sở | Có | -| `check:compression-budget` | Ngân sách benchmark nén — ngưỡng tối thiểu về mức tiết kiệm token cho từng engine không được suy giảm | Có | -| `check:type-coverage` | Ratchet tỷ lệ phần trăm được định kiểu (`type-coverage`) không bị suy giảm; phần lớn bao hàm `typecheck:noimplicit:core` | Có | -| `check:codeql-ratchet` | Số lượng cảnh báo CodeQL đang mở không bị tăng (đọc qua `gh api`; bỏ qua an toàn khi không có token) — nhịp làm mới và kích hoạt thủ công: xem "CodeQL ratchet" bên dưới | Có | - -### Công việc: `quality-extended` - -Toàn bộ công việc mang tính tư vấn (`continue-on-error: true`). Các ratchet dựa trên npm được chạy +| Script | Xác thực | Chặn | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `quality:collect` | Xuất `quality-metrics.json` (số lượng cảnh báo ESLint, độ bao phủ từ báo cáo shard đã hợp nhất) | Có (thượng nguồn của ratchet) | +| `quality:ratchet` | Mỗi chỉ số trong `quality-baseline.json` không bị suy giảm (cảnh báo ESLint ≤ đường cơ sở; độ bao phủ ≥ đường cơ sở) | Có | +| `check:duplication` | Mức độ trùng lặp mã (jscpd@4) không vượt quá đường cơ sở trong `quality-baseline.json` | Có | +| `check:complexity` | Độ phức tạp cyclomatic ở cấp độ tệp không vượt quá giới hạn (quy tắc ESLint cốt lõi `complexity` + `max-lines-per-function`) | Có | +| `check:cognitive-complexity` | Ratchet độ phức tạp nhận thức (`eslint-plugin-sonarjs`) — lượt chạy ESLint riêng biệt; CI chạy cả hai và hợp nhất thành một bước `check:complexity-ratchets` duy nhất | Có | +| `check:dead-code` | Ratchet các tệp / export không được sử dụng (knip) không suy giảm so với đường cơ sở | Có | +| `check:compression-budget` | Ngân sách benchmark nén — mức sàn tiết kiệm token cho từng engine không được suy giảm | Có | +| `check:type-coverage` | Ratchet tỷ lệ phần trăm được định kiểu (`type-coverage`) không suy giảm; phần lớn bao hàm `typecheck:noimplicit:core` | Có | +| `check:codeql-ratchet` | Số lượng cảnh báo CodeQL đang mở không tăng (đọc qua `gh api`; bỏ qua an toàn khi không có token) — về chu kỳ làm mới và kích hoạt thủ công: xem "CodeQL ratchet" bên dưới | Có | + +### Job: `quality-extended` + +Toàn bộ job mang tính tư vấn (`continue-on-error: true`). Các ratchet dựa trên npm được chạy thực tế; các trình quét bên ngoài được cài đặt qua `gh release download` và tự bỏ qua (exit 0) -khi vẫn chưa có tệp nhị phân. - -| Script | Xác thực | Chặn | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- | -| `check:circular-deps` | Không có phụ thuộc vòng (dpdm) | **Tư vấn** | -| `check:bundle-size` | Kích thước bundle không vượt quá giới hạn | **Tư vấn** | -| `check:secrets` | Quét bí mật (gitleaks) — bỏ qua nếu không có tệp nhị phân | **Tư vấn** | -| `check:vuln-ratchet` | Các lỗ hổng phụ thuộc (osv-scanner) không bị tăng — bỏ qua nếu không có tệp nhị phân | **Tư vấn** | -| `check:workflows` | Kiểm tra workflow (actionlint + zizmor); trình quét bị thiếu/hỏng, báo cáo không hợp lệ hoặc thiếu đường cơ sở ratchet sẽ thất bại với trạng thái INCOMPLETE. Các phát hiện hợp lệ tuân theo chính sách nghiêm ngặt/tư vấn/ratchet đã chọn | Bắt buộc thực thi; ratchet zizmor chặn trong CI | -| `check:openapi-breaking` | Các thay đổi phá vỡ hợp đồng API công khai (`openapi.yaml`) so với nhánh cơ sở (oasdiff) — xuất `openapiBreaking=N`; bỏ qua nếu không có oasdiff hoặc không thể phân giải đặc tả cơ sở | **Tư vấn** | - -### Công việc: `docs-sync-strict` - -Chạy trên mọi PR tới `main`. Chặn hợp nhất nếu thất bại. - -| Script | Xác thực | Chặn | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -| `check:docs-all` | Cổng tổng hợp chạy tuần tự 6 cổng con bên dưới | Có | -| ↳ `check:docs-sync` | Tính nhất quán về phiên bản giữa CHANGELOG / OpenAPI / llm.txt | Có | -| ↳ `check:docs-counts` | Các số liệu trong nội dung (số lượng nhà cung cấp, số lượng migration, v.v.) nằm trong khoảng ratchet của số liệu thực tế | Có | -| ↳ `check:env-doc-sync` | Mọi biến môi trường trong `.env.example` đều được ghi lại trong một bảng tài liệu và ngược lại | Có | -| ↳ `check:deprecated-versions` | Không có chuỗi phiên bản đã ngừng hỗ trợ trong tài liệu | Có | -| ↳ `check:doc-links` | Các liên kết markdown nội bộ trong tài liệu phân giải thành tệp có thật (dạng `[text]`/`(path)`) | Có | -| ↳ `check:fabricated-docs` | Các route, biến môi trường, lệnh CLI, tên hook và đường dẫn tệp được đề cập trong tài liệu phải tồn tại trong codebase. Cổng cứng qua `--strict`; lỗi mềm nếu không có cờ. | Có (qua `--strict` trong CI) | -| `check:cli-i18n` | Các chuỗi lệnh CLI có mặt trong tất cả các tệp locale i18n | Có | -| `check:openapi-coverage` | Đặc tả OpenAPI bao phủ ít nhất mức sàn được ratchet của các route thực tế | Có | -| `check:openapi-security-tiers` | Các chú thích cấp độ bảo mật trong `openapi.yaml` nhất quán với các phân loại trong `routeGuard.ts` | **Khuyến nghị** | -| `check:openapi-routes` | Mọi đường dẫn trong `openapi.yaml` phân giải thành một `route.ts` có thật (chống bịa đặt) | Có | -| `check:docs-symbols` | Mọi tham chiếu `/api/...` trong `docs/**/*.md` phân giải thành một `route.ts` có thật (chống bịa đặt) | Có | -| `i18n translation drift` | Các khóa chưa được dịch trong tệp locale i18n — chỉ cảnh báo | **Khuyến nghị** | - -### Tác vụ: `i18n-ui-coverage` - -| Script | Xác thực | Chặn | -| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `check-ui-keys-coverage` (nội tuyến) | Độ bao phủ khóa i18n của UI là ≥ 65% | Có | -| `check-ui-value-drift` (nội tuyến) | Một **giá trị** tiếng Anh được viết lại không để sót bản dịch cũ nào | Có | -| `check-new-key-coverage` (nội tuyến) | Một khóa tiếng Anh **mới** được dịch trong mọi locale — marker `__MISSING__:` sẽ bị từ chối | Có | -| `check-translation-ratio` | Tỷ lệ bản dịch thực tế theo từng locale (các mục giống hệt tiếng Anh / placeholder / bị thiếu ngoài allowlist) không được vượt quá `config/quality/i18n-translation-baseline.json` + biên dư | **Khuyến nghị** | - -Yêu cầu `fetch-depth: 0` — cổng value-drift so sánh khác biệt của `en.json` với merge base. +khi vẫn không có binary. + +| Script | Xác thực | Chặn | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| `check:circular-deps` | Không có phụ thuộc vòng (dpdm) | **Tư vấn** | +| `check:bundle-size` | Kích thước bundle không vượt quá giới hạn | **Tư vấn** | +| `check:secrets` | Quét bí mật (gitleaks) — bỏ qua nếu không có binary | **Tư vấn** | +| `check:vuln-ratchet` | Các lỗ hổng phụ thuộc (osv-scanner) không tăng — bỏ qua nếu không có binary | **Tư vấn** | +| `check:workflows` | Lint workflow (actionlint + zizmor); trình quét bị thiếu/hỏng, báo cáo không hợp lệ hoặc thiếu đường cơ sở ratchet sẽ thất bại với trạng thái INCOMPLETE. Các phát hiện hợp lệ tuân theo chính sách nghiêm ngặt/tư vấn/ratchet đã chọn | Bắt buộc thực thi; ratchet zizmor chặn trong CI | +| `check:openapi-breaking` | Các thay đổi gây phá vỡ hợp đồng API công khai (`openapi.yaml`) so với nhánh cơ sở (oasdiff) — xuất `openapiBreaking=N`; bỏ qua nếu không có oasdiff hoặc không thể phân giải đặc tả cơ sở | **Tư vấn** | + +### Job: `docs-sync-strict` + +Chạy trên mọi PR vào `main`. Chặn hợp nhất khi thất bại. + +| Script | Xác thực | Chặn | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | +| `check:docs-all` | Cổng meta chạy tuần tự 6 cổng con bên dưới | Có | +| ↳ `check:docs-sync` | Tính nhất quán về phiên bản giữa CHANGELOG / OpenAPI / llm.txt | Có | +| ↳ `check:docs-counts` | Các số liệu trong nội dung (số lượng nhà cung cấp, số lượng migration, v.v.) nằm trong cửa sổ ratchet của số liệu thực tế | Có | +| ↳ `check:env-doc-sync` | Mọi biến môi trường trong `.env.example` đều được ghi lại trong một bảng tài liệu và ngược lại | Có | +| ↳ `check:deprecated-versions` | Không có chuỗi phiên bản đã ngừng hỗ trợ trong tài liệu | Có | +| ↳ `check:doc-links` | Các liên kết markdown nội bộ trong tài liệu phân giải tới tệp thực (`[text]`/`(path)` form) | Có | +| ↳ `check:fabricated-docs` | Các route, biến môi trường, lệnh CLI, tên hook và đường dẫn tệp được viện dẫn trong tài liệu phải tồn tại trong codebase. Cổng cứng qua `--strict`; thất bại mềm khi không có cờ. | Có (qua `--strict` trong CI) | +| `check:cli-i18n` | Các chuỗi lệnh CLI hiện diện trong tất cả các tệp locale i18n | Có | +| `check:openapi-coverage` | Đặc tả OpenAPI bao phủ ít nhất ngưỡng sàn được ratchet của các route thực tế | Có | +| `check:openapi-security-tiers` | Các chú thích cấp độ bảo mật trong `openapi.yaml` nhất quán với các phân loại trong `routeGuard.ts` | **Khuyến nghị** | +| `check:openapi-routes` | Mọi path trong `openapi.yaml` phân giải tới một `route.ts` thực (chống bịa đặt) | Có | +| `check:docs-symbols` | Mọi tham chiếu `/api/...` trong `docs/**/*.md` phân giải tới một `route.ts` thực (chống bịa đặt) | Có | +| `i18n translation drift` | Các key chưa được dịch trong tệp locale i18n — chỉ cảnh báo | **Khuyến nghị** | + +### Job: `i18n-ui-coverage` + +| Script | Xác thực | Chặn | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | +| `check-ui-keys-coverage` (inline) | Độ bao phủ key i18n của UI là ≥ 65% | Có | +| `check-ui-value-drift` (inline) | Một **value** tiếng Anh được viết lại không để lại bản dịch lỗi thời nào | Có | +| `check-new-key-coverage` (inline) | Một key tiếng Anh **mới** được dịch trong mọi locale — marker `__MISSING__:` sẽ bị từ chối | Có | +| `check-translation-ratio` | Tỷ lệ bản dịch thực tế theo từng locale (các leaf giống tiếng Anh / placeholder / bị thiếu nằm ngoài allowlist) không được vượt quá `config/quality/i18n-translation-baseline.json` + slack | **Khuyến nghị** | + +Cần `fetch-depth: 0` — cổng value-drift so sánh khác biệt của `en.json` với merge base. #### `check-ui-value-drift` — cổng phát hiện bản dịch lỗi thời -Phát hiện một dạng hồi quy i18n mà các cổng khác không thể nhận biết về mặt cấu trúc: một giá trị tiếng Anh -được viết lại nhưng các bản dịch bắt nguồn từ phiên bản tiếng Anh _trước đó_ vẫn còn nguyên, khiến -người dùng không dùng tiếng Anh tiếp tục đọc nội dung có vẻ chắc chắn nhưng giờ đã sai. +Phát hiện một dạng hồi quy i18n mà các cổng khác về mặt cấu trúc không thể nhận ra: một value tiếng Anh +được viết lại nhưng các bản dịch bắt nguồn từ tiếng Anh _trước đó_ vẫn còn nguyên, khiến +người dùng không sử dụng tiếng Anh tiếp tục đọc nội dung có vẻ đầy chắc chắn nhưng nay đã sai. -Lỗi này thực sự đã được phát hành. `oauthModal.googleOAuthWarning` được viết lại khi trình hỗ trợ đăng nhập -Antigravity được bổ sung (#5203); **39 trong số 43 locale** vẫn giữ nội dung yêu cầu người vận hành "sao chép -toàn bộ URL và dán vào bên dưới" — một quy trình không thể hoàn tất đối với nhà cung cấp đó. Lỗi này +Điều này đã thực sự được phát hành. `oauthModal.googleOAuthWarning` đã được viết lại khi trình trợ giúp đăng nhập +Antigravity được đưa vào (#5203); **39 trong số 43 locale** vẫn giữ nội dung yêu cầu người vận hành "sao chép +URL đầy đủ và dán vào bên dưới" — một luồng không thể hoàn tất đối với nhà cung cấp đó. Vấn đề này không được phát hiện cho đến #8463 vì: -- `sync-ui-keys` chỉ điền bổ sung các khóa **không tồn tại**, không bao giờ cập nhật các khóa **lỗi thời**; -- `check-ui-keys-coverage` đếm _sự hiện diện_ của khóa, nên một bản dịch lỗi thời vẫn được tính là đã bao phủ; +- `sync-ui-keys` chỉ điền bù các key **không tồn tại**, không bao giờ xử lý các key **lỗi thời**; +- `check-ui-keys-coverage` đếm _sự hiện diện_ của key, vì vậy một bản dịch lỗi thời vẫn được tính là đã bao phủ; - `check-translation-drift` theo dõi các bản sao tài liệu `docs/i18n//**.md` — - nó không bao giờ đọc `src/i18n/messages/*.json`. Bị chặn trong tác vụ `docs-sync-strict` kể từ lần - đồng bộ lại 2026-09: chỉnh sửa tài liệu cốt lõi → `npm run i18n:run -- --files=` (theo từng phần, chi phí thấp). + nó không bao giờ đọc `src/i18n/messages/*.json`. Có tính chặn trong job `docs-sync-strict` kể từ lần + đồng bộ lại 2026-09: chỉnh sửa tài liệu cốt lõi → `npm run i18n:run -- --files=` (theo từng phần, ít tốn kém). **Nhận biết diff, không dựa trên baseline.** Cơ chế này so sánh `en.json` tại merge base với -working tree; với mỗi khóa có giá trị tiếng Anh đã thay đổi, mọi locale vẫn giữ bản dịch -chưa được cập nhật đều bị xem là lỗi thời. Điều này có chủ đích **đóng băng khoản nợ tồn đọng** — diff +working tree; với mỗi key có giá trị tiếng Anh thay đổi, bất kỳ locale nào vẫn giữ bản +dịch chưa được cập nhật đều bị coi là lỗi thời. Điều này chủ ý **đóng băng khoản nợ tồn đọng** — một diff không thể cho biết một bản dịch lâu đời bắt nguồn từ phiên bản tiếng Anh cũ nào, vì vậy cổng kiểm tra -chỉ đánh giá những gì thay đổi hiện tại tác động đến. Phương án thay thế (baseline hash theo từng khóa) sẽ tạo ra -một tệp sinh tự động khoảng 600 KB, lớn gấp 3 lần baseline lớn nhất hiện có và thay đổi trong mọi PR i18n. +chỉ đánh giá những gì thay đổi hiện tại tác động đến. Phương án thay thế (baseline hash theo từng key) sẽ cần +một file được tạo có dung lượng ~600 KB, lớn gấp 3 lần baseline lớn nhất hiện có và thay đổi liên tục trong mỗi PR i18n. Có hai cách để đáp ứng yêu cầu: 1. cập nhật các bản dịch bị ảnh hưởng, hoặc -2. đặt chúng thành `__MISSING__:` — khi đó runtime sẽ cung cấp bản tiếng Anh đã sửa - (`src/i18n/request.ts::deepMergeFallback`, #7258) và đưa khóa vào hàng đợi dịch. +2. đặt chúng thành `__MISSING__:` — khi đó runtime sẽ cung cấp nội dung tiếng Anh đã sửa + (`src/i18n/request.ts::deepMergeFallback`, #7258) và key sẽ được đưa vào hàng đợi dịch thuật. -Nếu **ý nghĩa** của chuỗi thay đổi, nên ưu tiên **đổi tên khóa**: khóa mới không thể kế thừa -bản dịch lỗi thời. Đây là mẫu mà #8463 đã sử dụng. +Nếu **ý nghĩa** của chuỗi đã thay đổi, hãy ưu tiên **đổi tên key**: một key mới không thể kế thừa +bản dịch lỗi thời. Đây là mẫu được sử dụng trong #8463. ```bash npm run i18n:check-value-drift # nghiêm ngặt (chế độ CI chạy) @@ -230,48 +232,48 @@ npm run i18n:check-value-drift:warn # chỉ báo cáo BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Thoát với mã 0 cùng `SKIP reason=base-unresolved` khi không thể đọc catalog cơ sở (shallow -clone không có base ref), tương tự `check-openapi-breaking`. +Thoát với mã 0 kèm `SKIP reason=base-unresolved` khi không thể đọc catalog cơ sở (bản sao nông +không có ref cơ sở), tương tự `check-openapi-breaking`. ### Job: `i18n` -Ma trận xác thực i18n đầy đủ (mỗi locale một job). Toàn bộ job chỉ mang tính khuyến nghị. +Ma trận xác thực i18n đầy đủ (một job cho mỗi locale). Toàn bộ job mang tính tham khảo. -| Script | Nội dung xác thực | Khả năng chặn | -| ------------------------------- | ------------------------------------------ | ------------------------------------------------------------ | -| `validate_translation.py quick` | Mức độ hoàn chỉnh của bản dịch theo locale | **Khuyến nghị** (`continue-on-error: true` trên toàn bộ job) | +| Script | Xác thực | Chặn | +| ------------------------------- | ------------------------------------------ | ---------------------------------------------------------- | +| `validate_translation.py quick` | Mức độ hoàn chỉnh của bản dịch theo locale | **Tham khảo** (`continue-on-error: true` trên toàn bộ job) | ### Job: `pr-test-policy` -Chỉ chạy trên pull request. +Chỉ chạy trên các pull request. -| Script | Nội dung xác thực | Khả năng chặn | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -| `check:pr-test-policy` | Các PR thay đổi mã production trong `src/`, `open-sse/`, `electron/` hoặc `bin/` phải thêm hoặc cập nhật test (Quy tắc cứng #8) | Có | -| `check:test-masking` | Các tệp test đã thay đổi không làm giảm tổng số assert ròng hoặc thêm các mệnh đề hiển nhiên `assert.ok(true)` | Có | -| `check:pr-evidence` | Nội dung PR trích dẫn bằng chứng test/VPS cho thay đổi (cơ giới hóa Quy tắc cứng #18 bằng cách grep văn bản PR — mong manh, xem Backlog) | Có | +| Script | Xác thực | Chặn | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---- | +| `check:pr-test-policy` | Các PR thay đổi mã production trong `src/`, `open-sse/`, `electron/` hoặc `bin/` phải bao gồm hoặc cập nhật test (Quy tắc cứng #8) | Có | +| `check:test-masking` | Các file test đã thay đổi không làm giảm tổng số assert ròng hoặc thêm các mệnh đề hiển nhiên `assert.ok(true)` | Có | +| `check:pr-evidence` | Nội dung PR trích dẫn bằng chứng test/VPS cho thay đổi (cơ giới hóa Quy tắc cứng #18 bằng cách grep văn bản PR — dễ lỗi, xem Backlog) | Có | ### Job: `test-vitest` -Chạy sau `build`. Chặn merge khi thất bại. +Chạy sau `build`. Chặn merge nếu thất bại. -| Suite | Nội dung xác thực | Khả năng chặn | -| ---------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 công cụ), autoCombo, cache — trình chạy vitest | Có | -| `test:vitest:ui` | Test thành phần UI — trình chạy vitest | **Chặn** — các lỗi tồn tại từ trước được loại trừ rõ ràng trong `vitest.config.ts`; lỗi mới khiến job thất bại | +| Bộ test | Xác thực | Chặn | +| ---------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `test:vitest` | MCP server (110 công cụ), autoCombo, cache — trình chạy vitest | Có | +| `test:vitest:ui` | Test thành phần UI — trình chạy vitest | **Chặn** — các lỗi tồn tại từ trước được loại trừ rõ ràng trong `vitest.config.ts`; lỗi mới sẽ làm job thất bại | -### Workflow chạy hằng đêm (theo lịch, mang tính khuyến nghị) +### Workflow hằng đêm (theo lịch, mang tính tham khảo) -Các workflow này chạy theo lịch cron (và `workflow_dispatch`), không bao giờ chạy trên PR. Tất cả đều mang tính khuyến nghị. +Các workflow này chạy theo lịch cron (và `workflow_dispatch`), không bao giờ chạy trên PR. Tất cả đều mang tính tham khảo. -| Workflow | Nội dung xác thực | Khả năng chặn | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| `nightly-property` | Test thuộc tính fast-check với seed ngẫu nhiên + số lượt chạy cao | **Khuyến nghị** | -| `nightly-resilience` | Cổng kiểm tra tăng trưởng heap, chèn lỗi hỗn loạn, tải/ngâm tải bằng k6 | **Khuyến nghị** | -| `nightly-llm-security` | Bộ bảo vệ chống prompt injection của promptfoo (chế độ chặn) + các phép dò garak (bỏ qua khi không có provider secret) | **Khuyến nghị** | -| `nightly-schemathesis` | Fuzzing hợp đồng OpenAPI (schemathesis) trên OmniRoute đang chạy bằng `docs/openapi.yaml` — phát hiện vi phạm đặc tả / lỗi 500 chưa được xử lý (Giai đoạn 8 B.4) | **Khuyến nghị** | -| `nightly-mutation` | Điểm mutation testing của Stryker trên luồng unit test nhanh — các mutant sống sót cho thấy assert yếu | **Khuyến nghị** | -| `nightly-compat` | Ma trận tương thích Node engine trên các dải `engines.node` được hỗ trợ | **Khuyến nghị** | +| Workflow | Xác thực | Chặn | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `nightly-property` | Các property test bằng fast-check với seed ngẫu nhiên + số lượt chạy cao | **Tham khảo** | +| `nightly-resilience` | Cổng kiểm tra tăng trưởng heap, chèn lỗi hỗn loạn, kiểm thử tải/soak bằng k6 | **Tham khảo** | +| `nightly-llm-security` | Cơ chế bảo vệ khỏi injection bằng promptfoo (chế độ chặn) + các probe garak (bỏ qua khi không có secret của provider) | **Tham khảo** | +| `nightly-schemathesis` | Fuzzing hợp đồng OpenAPI (schemathesis) trên một OmniRoute đang chạy bằng `docs/openapi.yaml` — phát hiện vi phạm đặc tả / lỗi 500 chưa được xử lý (Giai đoạn 8 B.4) | **Tham khảo** | +| `nightly-mutation` | Điểm kiểm thử đột biến Stryker trên lane unit test nhanh — các mutant sống sót cho thấy assert yếu | **Tham khảo** | +| `nightly-compat` | Ma trận tương thích Node engine trên các dải `engines.node` được hỗ trợ | **Tham khảo** | --- @@ -534,43 +536,43 @@ lỗi kết nối và không bao giờ làm gián đoạn các phiên làm việ --- -## Danh sách tồn đọng cần hợp lý hóa (đánh giá ROI — Giai đoạn 9 Đợt 3) +## Danh sách tồn đọng cần hợp lý hóa (rà soát ROI — Giai đoạn 9 Đợt 3) -Danh mục này đã được đối soát với `ci.yml` vào ngày 2026-06-17 (phiên bản trước đã bỏ sót +Danh mục này đã được đối chiếu với `ci.yml` vào ngày 2026-06-17 (phiên bản trước đã bỏ sót `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Một đợt đánh giá ROI đối với tập hợp đã đối soát -đã xác định các ứng viên hợp lý hóa sau đây. **Các thao tác hợp nhất là những thay đổi CI -mang tính cơ học; việc chuyển đổi/loại bỏ là các quyết định chính sách dành cho người vận hành.** Chưa có nội dung nào -dưới đây được áp dụng. +`check:codeql-ratchet`, `check:pr-evidence`). Quá trình rà soát ROI của tập hợp đã đối chiếu +xác định các ứng viên hợp lý hóa sau đây. **Các mục hợp nhất là thay đổi CI mang tính +cơ học; việc chuyển đổi/loại bỏ là các quyết định chính sách dành cho người vận hành.** Chưa có +nội dung nào dưới đây được áp dụng. -**Ngoài ra chưa được ghi lại ở trên** (mang tính khuyến nghị, tín hiệu thấp): job `docs-lint` -(markdownlint + Vale, toàn bộ job dùng `continue-on-error`) và các workflow quét độc lập +**Ngoài ra còn chưa được ghi lại ở trên** (mang tính tư vấn, tín hiệu thấp): tác vụ `docs-lint` +(markdownlint + Vale, toàn bộ tác vụ dùng `continue-on-error`) và các quy trình quét độc lập `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` có trong -`quality-baseline.json` nhưng chưa được nối với một cơ chế chốt chặn tăng dần có tính bắt buộc trong `ci.yml` — chỉ số này +`quality-baseline.json` nhưng chưa được kết nối với một cơ chế chốt chặn tăng dần có tính ngăn chặn trong `ci.yml` — chỉ số này hiện đang bị bỏ rời. -### Hợp nhất / loại bỏ trùng lặp (cơ học, rủi ro thấp hơn) +### Hợp nhất / khử trùng lặp (mang tính cơ học, rủi ro thấp hơn) -Mỗi ứng viên đã được xác thực dựa trên trạng thái cổng kiểm soát thực tế vào ngày 2026-06-17 (tin tưởng nhưng vẫn xác minh); -một số thao tác hợp nhất “hiển nhiên” hóa ra lại che giấu nợ kỹ thuật và **không** thể thay thế trực tiếp một cách an toàn. +Mỗi ứng viên đã được xác thực dựa trên trạng thái cổng kiểm soát thực tế vào ngày 2026-06-17 (tin tưởng nhưng phải xác minh); +một số phép hợp nhất “hiển nhiên” hóa ra lại che giấu nợ kỹ thuật và **không** thể thay thế trực tiếp một cách an toàn. -- **`check:docs-sync` chạy hai lần** — chạy độc lập trong job `lint` và chạy lại bên trong `check:docs-all` (`docs-sync-strict`) cũng như hook pre-commit của husky. ✅ **HOÀN TẤT** — đã xóa lần gọi độc lập trong `lint`. -- **Quét CVE** — ❌ **KHÔNG thể hợp nhất trực tiếp một cách an toàn.** `audit:deps` thất bại cứng khi có bất kỳ CVE mức high/critical nào; `check:vuln-ratchet` (osv) chỉ thất bại khi có _sự suy giảm_ so với đường cơ sở (hiện là 1 MODERATE). Ngữ nghĩa khác nhau — việc loại bỏ `audit:deps` sẽ làm mất cổng kiểm soát tuyệt đối cho mức high/critical. Giữ cả hai. -- **Phát hiện chu trình** — ❌ **KHÔNG thể hợp nhất trực tiếp một cách an toàn.** `check:circular-deps` (dpdm) báo cáo **91 chu trình** (đó là lý do nó chỉ mang tính khuyến nghị); không thể chuyển nó thành bắt buộc nếu chưa xử lý các chu trình này trước, và phạm vi của nó rộng hơn `check:cycles` đã được tuyển chọn và đang ở trạng thái đạt. Giữ `check:cycles` ở trạng thái bắt buộc; việc xử lý 91 chu trình dpdm là một hạng mục tồn đọng riêng. -- **Độ phức tạp** — ✅ **HOÀN TẤT** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): một lượt duyệt ESLint, đếm theo ruleId để các đường cơ sở cyclomatic+max-lines và cognitive vẫn độc lập; các lệnh riêng lẻ `check:complexity` / `check:cognitive-complexity` vẫn được giữ lại để cập nhật cục bộ bằng `--update`. -- **Chống ảo giác `/api`** — ✅ **HOÀN TẤT** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): một lần kiểm kê FS đối với `src/app/api`, openapi-routes + docs-symbols vẫn báo cáo độc lập; các lệnh riêng lẻ vẫn được giữ lại để chạy cục bộ. -- **`check:node-runtime` chạy trong 11 job** — ⚠️ **ROI thấp.** Mỗi job dùng một runner riêng và phép kiểm tra mất <1 giây; tổng thời gian tiết kiệm khoảng 10 giây, đổi lại là mất một lớp bảo vệ rẻ trên từng job. Không đáng để gây xáo trộn. -- **`typecheck:noimplicit:core` trong CI lint** — ✅ **đã xóa khỏi job lint** (trước đây mang tính khuyến nghị với `continue-on-error`); bề mặt kiểu bắt buộc là `typecheck:core` + `check:type-coverage`. Script cục bộ vẫn được giữ lại. +- **`check:docs-sync` chạy hai lần** — chạy độc lập trong tác vụ `lint` và chạy lại bên trong `check:docs-all` (`docs-sync-strict`) cũng như hook pre-commit của husky. ✅ **ĐÃ XONG** — đã loại bỏ lệnh gọi độc lập trong `lint`. +- **Quét CVE** — ❌ **KHÔNG thể hợp nhất một cách an toàn.** `audit:deps` dừng với lỗi cứng khi có bất kỳ CVE mức cao/nghiêm trọng nào; `check:vuln-ratchet` (osv) chỉ thất bại khi có _hồi quy_ so với đường cơ sở (hiện có 1 MODERATE). Ngữ nghĩa khác nhau — loại bỏ `audit:deps` sẽ làm mất cổng kiểm soát tuyệt đối cho mức cao/nghiêm trọng. Giữ cả hai. +- **Phát hiện chu trình** — ✅ **ĐÃ XONG** (#15159 G-01/G-02). Nội dung cũ ở đây gọi `check:cycles` là cổng kiểm soát “xanh, được tuyển chọn” và biện minh cho việc duy trì trạng thái ngăn chặn của nó vì `check:circular-deps` (dpdm) báo cáo 91 chu trình. Trạng thái xanh đó là **xanh giả**: `check:cycles` quét 5 thư mục con (450 tệp), chỉ khớp với `import|export … from` tĩnh và loại bỏ mọi specifier `@/` cùng `@omniroute/open-sse/`, nên không thể thấy các chu trình dynamic-import + alias chiếm ưu thế trong kho mã. Đã khắc phục: cổng kiểm soát giờ duyệt `src` + `open-sse` (5023 tệp), thu thập các specifier từ TypeScript AST (do đó `import("…")` được tính còn `typeof import("…")` ở vị trí kiểu thì không) và phân giải `paths` của tsconfig. Nó tìm thấy **14** chu trình, không phải 0. Vì 14 chu trình tồn tại từ trước không thể được khắc phục trong một PR cổng kiểm soát, `check:cycles` hiện là một **cơ chế chốt chặn tăng dần** (`--ratchet`, mức trần `metrics.cycles.value = 14` trong `quality-baseline.json`, `direction: down`) — nó chặn mọi _hồi quy_ và số lượng chỉ có thể giảm. CI chạy `npm run check:cycles:ratchet`. Việc giảm dần được thực hiện cùng với **A-01**. `check:circular-deps` (dpdm) vẫn mang tính tư vấn như một ý kiến thứ hai có phạm vi rộng hơn. +- **Độ phức tạp** — ✅ **ĐÃ XONG** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): một lượt duyệt ESLint, đếm theo ruleId để các đường cơ sở cyclomatic+max-lines và cognitive vẫn độc lập; từng lệnh `check:complexity` / `check:cognitive-complexity` vẫn được giữ lại cho `--update` cục bộ. +- **Chống ảo giác `/api`** — ✅ **ĐÃ XONG** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): một lần kiểm kê hệ thống tệp của `src/app/api`, openapi-routes + docs-symbols vẫn báo cáo độc lập; các lệnh riêng lẻ vẫn được giữ lại để chạy cục bộ. +- **`check:node-runtime` chạy trong 11 tác vụ** — ⚠️ **ROI thấp.** Mỗi tác vụ dùng một runner riêng và phép kiểm tra mất <1 giây; tổng mức tiết kiệm khoảng 10 giây, nhưng đổi lại sẽ mất một cơ chế bảo vệ rẻ tiền cho từng tác vụ. Không đáng để gây xáo trộn. +- **`typecheck:noimplicit:core` trong lint CI** — ✅ **đã loại bỏ khỏi tác vụ lint** (trước đây mang tính tư vấn với `continue-on-error`); bề mặt kiểu có tính ngăn chặn là `typecheck:core` + `check:type-coverage`. Script cục bộ vẫn được giữ lại. ### Chuyển đổi / quyết định (chính sách của người vận hành) -- `check:openapi-security-tiers` (khuyến nghị) — ❌ **KHÔNG thể chuyển sang bắt buộc một cách an toàn ngay lập tức.** Nó thoát với mã 0 nhưng cảnh báo rằng một số route `traffic-inspector` thuộc `LOCAL_ONLY_API_PREFIXES` thiếu chú thích `x-loopback-only: true`. Muốn thực thi bắt buộc thì trước tiên phải thêm các chú thích đó vào `openapi.yaml`. -- `typecheck:noimplicit:core` (khuyến nghị) — phần lớn đã được cơ chế chốt chặn tăng dần bắt buộc `check:type-coverage` bao phủ. Chuyển thành cơ chế chốt chặn tăng dần hoặc loại bỏ lượt chạy `tsc` thứ hai bị dư thừa. -- `test:vitest:ui` (hiện **bắt buộc**) — các lỗi có từ trước được loại trừ rõ ràng trong `vitest.config.ts` bằng các chú thích theo dõi `// #8618`; các lỗi mới sẽ làm job thất bại. -- `check:secrets` (gitleaks, cơ chế chốt chặn tăng dần bắt buộc được cố định ở 3 kết quả dương tính giả đã ghi nhận) — đưa 3 trường hợp này vào allowlist để đạt 0, hoặc hạ xuống mức khuyến nghị. Trùng lặp với tính năng quét bí mật gốc của GitHub + `check:public-creds`. -- `check:pr-evidence` (bắt buộc, dùng grep trên văn bản trong phần thân PR) — rủi ro dương tính giả cao; việc loại bỏ sẽ làm suy yếu khả năng thực thi Quy tắc Cứng #18, vì vậy đây thực sự là một quyết định chính sách. -- `semgrep` (workflow độc lập mang tính khuyến nghị) — trùng lặp với CodeQL đối với các nhóm OWASP; nối đường cơ sở của nó với một cơ chế chốt chặn tăng dần hoặc loại bỏ. +- `check:openapi-security-tiers` (mang tính tư vấn) — ❌ **KHÔNG thể chuyển đổi trực tiếp một cách an toàn.** Nó thoát với mã 0 nhưng cảnh báo rằng một số route `traffic-inspector` bên dưới `LOCAL_ONLY_API_PREFIXES` thiếu chú thích `x-loopback-only: true`. Muốn thực thi bắt buộc, trước tiên phải thêm các chú thích đó vào `openapi.yaml`. +- `typecheck:noimplicit:core` (mang tính tư vấn) — phần lớn đã được cơ chế chốt chặn tăng dần có tính ngăn chặn `check:type-coverage` bao hàm. Chuyển thành cơ chế chốt chặn tăng dần hoặc loại bỏ lượt chạy `tsc` thứ hai bị trùng lặp. +- `test:vitest:ui` (hiện **có tính ngăn chặn**) — các lỗi tồn tại từ trước được loại trừ rõ ràng trong `vitest.config.ts` bằng các chú thích theo dõi `// #8618`; lỗi mới sẽ khiến tác vụ thất bại. +- `check:secrets` (gitleaks, cơ chế chốt chặn tăng dần có tính ngăn chặn được cố định ở 3 trường hợp dương tính giả đã được ghi lại) — đưa 3 trường hợp này vào danh sách cho phép để đạt 0, hoặc hạ xuống thành tư vấn. Trùng lặp với tính năng quét bí mật gốc của GitHub + `check:public-creds`. +- `check:pr-evidence` (có tính ngăn chặn, dùng grep trên văn bản phần thân PR) — nguy cơ dương tính giả cao; nếu loại bỏ sẽ làm suy yếu việc thực thi Quy tắc Cứng #18, vì vậy đây thực sự là một quyết định chính sách. +- `semgrep` (quy trình độc lập mang tính tư vấn) — trùng lặp với CodeQL đối với các nhóm OWASP; kết nối đường cơ sở của nó với một cơ chế chốt chặn tăng dần hoặc loại bỏ. --- diff --git a/docs/i18n/vi/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/vi/docs/compression/COMPRESSION_GUIDE.md index a5fcbb4c4f55..8cccd9723375 100644 --- a/docs/i18n/vi/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/vi/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ chèn thay cho tiếng Anh. Trên nội dung có `messages`, cơ chế bỏ qua theo nội dung (`shouldBypassCavemanOutputMode()` trong `open-sse/services/compression/outputMode.ts`) kiểm tra ba tin nhắn gần nhất và bỏ qua các phong cách cho toàn bộ lượt khi chúng khớp với từ khóa về bảo mật, hành động không thể đảo ngược, -làm rõ hoặc phụ thuộc thứ tự. Cơ chế bỏ qua hoạt động theo bất kỳ giá trị nào được đặt cho nút chuyển -**Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) trên bảng điều khiển. +làm rõ hoặc phụ thuộc thứ tự. Cơ chế bỏ qua hoạt động trong khi nút chuyển **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) trên bảng điều khiển đang bật, đây là giá trị mặc định; khi nút chuyển tắt, các phong cách đã chọn cũng được áp dụng trong những lượt đó. Khi cơ chế bỏ qua cho phép lượt tiếp tục, `placeSystemInstruction()` (cùng tệp), vốn không bao giờ tạo `messages[0]` mới, đặt khối vào vị trí đầu tiên tìm thấy trong các vị trí sau: diff --git a/docs/i18n/vi/docs/reference/ENVIRONMENT.md b/docs/i18n/vi/docs/reference/ENVIRONMENT.md index 3182cab69599..202895ccdc66 100644 --- a/docs/i18n/vi/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/vi/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Các bí mật bắt buộc -Các giá trị này **phải** được thiết lập trước lần chạy đầu tiên. Nếu thiếu, ứng dụng sẽ từ chối khởi động hoặc hoạt động với các giá trị mặc định không an toàn. - -| Biến | Bắt buộc | Mặc định | Tệp nguồn | Mô tả | -| ---------------------------- | ------------------------------ | ----------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Có** | _(không có)_ | `src/lib/auth` | Ký/xác minh tất cả cookie phiên của bảng điều khiển (JWT). Tạo bằng `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Có** | _(không có)_ | `src/lib/db/apiKeys.ts` | Khóa mã hóa AES cho các giá trị khóa API được lưu trữ trong SQLite. Tạo bằng `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Có** | `CHANGEME` | Tập lệnh khởi tạo | Đặt mật khẩu ban đầu cho quản trị viên bảng điều khiển (khớp với giá trị mặc định trong `.env.example` — được cố ý giữ ở trạng thái rõ ràng là không an toàn để buộc phải thay đổi). **Hãy thay đổi trước lần sử dụng đầu tiên.** Sau khi đăng nhập, hãy thay đổi qua Bảng điều khiển → Cài đặt → Bảo mật. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Có** (môi trường production) | _(chưa đặt)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Bí mật dùng chung cho cầu nối WebSocket nội bộ của Codex Responses. Xác thực các yêu cầu cầu nối giữa trình chuyển tiếp WS của Electron/trình duyệt và OmniRoute. ⚠️ **BẮT BUỘC trong môi trường production — khi chưa được đặt, tất cả yêu cầu đến cầu nối WS đều bị từ chối.** Tạo bằng `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Không | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID phá bộ nhớ đệm service worker được chỉ định rõ ràng cho lớp vỏ PWA (#11779); đứng đầu trong chuỗi phân giải. | -| `SOURCE_VERSION` | Không | _(chưa đặt)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Đứng thứ hai trong chuỗi — được các trình xây dựng PaaS (ví dụ kiểu Heroku) đặt thành commit đã triển khai. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Không | _(được suy ra)_ | `src/shared/components/PwaRegister.tsx` | Giá trị công khai tại thời điểm dựng mà máy khách dùng để đăng ký `/sw.js?v=…`; được suy ra từ hai giá trị trên, sau đó đến git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Không (tự động) | _(tự động mỗi lần khởi động)_ | `src/server/authz/policies/management.ts` | Bí mật riêng theo từng tiến trình, chứng minh dấu IP ngang hàng đáng tin cậy đến từ chính máy chủ HTTP của OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware authz chỉ tin cậy tính cục bộ của yêu cầu (cơ chế giới hạn loopback/LAN đối với các tuyến LOCAL_ONLY) khi dấu này chứa token đó. Được tự động tạo sau mỗi lần khởi động — hãy để trống; chỉ cố định giá trị này cho các thiết lập đa tiến trình cần dùng chung dấu. | -| `OMNIROUTE_TRUSTED_PROXIES` | Không | _(chưa đặt)_ | `scripts/dev/peer-stamp.mjs` | Danh sách phân tách bằng dấu phẩy gồm các IP hoặc dải CIDR của proxy ngược mà danh sách cho phép/từ chối IP có thể tin cậy các giá trị `X-Forwarded-For` / `X-Real-IP` của chúng, ngoài các địa chỉ loopback, địa chỉ mạng riêng và các nút biên Cloudflare vốn luôn được tin cậy. Chỉ cần thiết cho proxy nằm trên bất kỳ địa chỉ công khai nào khác; nếu không, địa chỉ riêng của proxy đó sẽ là địa chỉ mà bộ lọc đánh giá. | +Các bí mật chưa đặt được đánh dấu **Có** sẽ được tự động tạo khi lần chạy đầu tiên (`JWT_SECRET`, `API_KEY_SECRET`) và lưu vào `server.env` trong thư mục dữ liệu — hãy đặt chúng một cách rõ ràng khi thư mục đó là tạm thời. Ngoại lệ là `OMNIROUTE_WS_BRIDGE_SECRET`: giá trị này không được lưu vào `server.env` — các điểm vào của máy chủ tạo ra một giá trị tạm thời cho mỗi lần khởi động mà chỉ cầu nối trong cùng tiến trình mới dùng được, vì vậy các client cầu nối bên ngoài cần đặt nó một cách rõ ràng. + +| Biến | Bắt buộc | Mặc định | Tệp nguồn | Mô tả | +| ---------------------------- | ------------------------------ | ----------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Có** | _(không có)_ | `src/lib/auth` | Ký/xác minh tất cả cookie phiên của bảng điều khiển (JWT). Tạo bằng `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Có** | _(không có)_ | `src/lib/db/apiKeys.ts` | Khóa mã hóa AES cho các giá trị khóa API được lưu trữ trong SQLite. Tạo bằng `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Không | _(chưa đặt)_ | `src/lib/auth/managementPassword.ts` | Đặt mật khẩu dashboard khi khởi động miễn là chưa có mật khẩu nào được lưu; bị bỏ qua khi đã có mật khẩu được lưu. Các bản cài đặt qua npm và từ mã nguồn sao chép `.env.example` thành `.env`, nên chúng khởi động với placeholder công khai `CHANGEME` của tệp đó: thay thế nó trước lần khởi động đầu tiên, hoặc đặt `INITIAL_PASSWORD=` (rỗng) trong tệp `.env` đó để tạo mật khẩu trong trình hướng dẫn thiết lập khi truy cập lần đầu, giống như giá trị chưa đặt (mặc định của image Docker) vẫn làm. Thay đổi mật khẩu đã lưu trong Bảng điều khiển → Cài đặt → Bảo mật, hoặc chạy `omniroute reset-password` (`node bin/reset-password.mjs` trong bản mã nguồn) với `DATA_DIR` trỏ đến thư mục dữ liệu của máy chủ. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Có** (môi trường production) | _(chưa đặt)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Bí mật dùng chung cho cầu nối WebSocket nội bộ của Codex Responses. Xác thực các yêu cầu cầu nối giữa trình chuyển tiếp WS của Electron/trình duyệt và OmniRoute. ⚠️ **BẮT BUỘC trong môi trường production — khi chưa được đặt, tất cả yêu cầu đến cầu nối WS đều bị từ chối.** Tạo bằng `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Không | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID phá bộ nhớ đệm service worker được chỉ định rõ ràng cho lớp vỏ PWA (#11779); đứng đầu trong chuỗi phân giải. | +| `SOURCE_VERSION` | Không | _(chưa đặt)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Đứng thứ hai trong chuỗi — được các trình xây dựng PaaS (ví dụ kiểu Heroku) đặt thành commit đã triển khai. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Không | _(được suy ra)_ | `src/shared/components/PwaRegister.tsx` | Giá trị công khai tại thời điểm dựng mà máy khách dùng để đăng ký `/sw.js?v=…`; được suy ra từ hai giá trị trên, sau đó đến git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Không (tự động) | _(tự động mỗi lần khởi động)_ | `src/server/authz/policies/management.ts` | Bí mật riêng theo từng tiến trình, chứng minh dấu IP ngang hàng đáng tin cậy đến từ chính máy chủ HTTP của OmniRoute (`scripts/dev/peer-stamp.mjs`). Middleware authz chỉ tin cậy tính cục bộ của yêu cầu (cơ chế giới hạn loopback/LAN đối với các tuyến LOCAL_ONLY) khi dấu này chứa token đó. Được tự động tạo sau mỗi lần khởi động — hãy để trống; chỉ cố định giá trị này cho các thiết lập đa tiến trình cần dùng chung dấu. | +| `OMNIROUTE_TRUSTED_PROXIES` | Không | _(chưa đặt)_ | `scripts/dev/peer-stamp.mjs` | Danh sách phân tách bằng dấu phẩy gồm các IP hoặc dải CIDR của proxy ngược mà danh sách cho phép/từ chối IP có thể tin cậy các giá trị `X-Forwarded-For` / `X-Real-IP` của chúng, ngoài các địa chỉ loopback, địa chỉ mạng riêng và các nút biên Cloudflare vốn luôn được tin cậy. Chỉ cần thiết cho proxy nằm trên bất kỳ địa chỉ công khai nào khác; nếu không, địa chỉ riêng của proxy đó sẽ là địa chỉ mà bộ lọc đánh giá. | ### Các lệnh tạo bí mật @@ -217,38 +217,38 @@ OmniRoute sử dụng **SQLite** (thông qua `better-sqlite3`) cho toàn bộ d ## 4. Bảo mật & Xác thực -| Biến | Mặc định | Tệp nguồn | Mô tả | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt được kết hợp với các mã định danh phần cứng để tạo dấu vân tay máy. Hãy thay đổi theo từng lần triển khai để đảm bảo tính cô lập. | -| `OMNIROUTE_CLI_SALT` | _(không đặt = salt ngẫu nhiên cho mỗi lần cài đặt, được lưu tại `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC dùng để tạo token xác thực CLI cục bộ. Việc đặt giá trị này sẽ xoay vòng tất cả token CLI trên máy và luôn được ưu tiên hơn salt đã lưu. Xem `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Đặt cờ `Secure` trên cookie phiên. **Phải là `true`** khi chạy phía sau HTTPS. | -| `REQUIRE_API_KEY` | `false` | Middleware API | Khi là `true`, tất cả yêu cầu proxy `/v1/*` phải bao gồm một khóa API hợp lệ. **Cờ này không kiểm soát `GET /v1/models`**, endpoint này tuân theo trạng thái đăng nhập của bảng điều khiển (`requireAuthForModels`) — vì vậy, phản hồi `401` từ `/v1/models` KHÔNG có nghĩa là hoạt động suy luận đã được bảo vệ. Xem `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Cho phép hiển thị đầy đủ giá trị khóa API trong giao diện người dùng của Bảng điều khiển. Có thể cấu hình từ phần Cờ tính năng của Bảng điều khiển; gây rủi ro bảo mật trên các phiên bản dùng chung. | -| `NO_LOG_API_KEY_IDS` | _(trống)_ | `src/lib/compliance/index.ts` | Danh sách ID khóa API được phân tách bằng dấu phẩy sẽ bỏ qua việc ghi nhật ký yêu cầu (tuân thủ GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(không đặt = không giới hạn)_ | `src/shared/utils/apiKeyPolicy.ts` | Hạn mức yêu cầu dự phòng mỗi ngày được áp dụng cho các khóa API có cột `rate_limits` là null. Không đặt hoặc để trống: không có giới hạn ngầm định (#2289, #11017). `0` cũng tương đương như vậy (không giới hạn). Số nguyên dương N cho phép N/ngày, 5N/tuần, 20N/tháng. Các giá trị không rỗng nhưng sai định dạng sẽ quay về các khoảng giới hạn cũ là 1000/ngày, 5000/tuần, 20000/tháng. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Kích thước nội dung yêu cầu tối đa được phép. Từ chối các payload vượt quá giới hạn này. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Nội dung yêu cầu thực tế bằng hoặc lớn hơn ngưỡng này sẽ nhận lease tiếp nhận nguyên tử dành cho tác vụ nặng, cục bộ trong tiến trình trước khi phân tích JSON (đường dẫn BYTE, bao gồm `POST /v1/responses`). Có cùng cơ chế thoát khi headroom ở trạng thái lành mạnh như [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) dành cho nội dung nặng về cấu trúc; vẫn bị giới hạn bởi `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) để [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) không tái diễn. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Giới hạn cứng của tuyến chat, được thực thi dựa trên số byte đọc trong quá trình tiếp nhận có giới hạn, bao gồm các yêu cầu thiếu, không hợp lệ hoặc khai báo không trung thực `Content-Length`; phần vượt quá sẽ nhận `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(không đặt — không giới hạn số lượng yêu cầu)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** giới hạn SỐ LƯỢNG yêu cầu cũ này giờ chỉ có hiệu lực khi được đặt rõ ràng. Khi không đặt (mặc định), việc tiếp nhận chat nặng được kiểm soát bởi `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — một ngân sách BYTE được tự động suy ra, định cỡ theo giới hạn bộ nhớ thực tế của tiến trình trong **một tiến trình** (một heap V8). Hai yêu cầu `/v1/responses` chồng lấn, mỗi yêu cầu khoảng 750k token, làm sập các heap khoảng 12 GiB (#7849) — đây là cảnh báo về **ngân sách bộ nhớ**, không phải giới hạn sản phẩm cứng là 2. Một tiến trình khỏe mạnh (heap dưới tỷ lệ loại bỏ) CÓ THỂ tiếp nhận thêm các yêu cầu `/v1/responses` dài đồng thời thông qua `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Hàng chục máy khách SSE chạy lâu (40–50) phụ thuộc vào heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, không phải “tối đa 2”. Việc tăng giá trị này một cách mù quáng để “tận dụng máy chủ” sẽ tái diễn #7849. Hãy nhân rộng **các heap** với **N `DATA_DIR` độc lập** (#11024); tuyệt đối không dùng `replicas>1` trên một tệp SQLite. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(được tự động suy ra)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** giá trị ghi đè cho ngân sách byte tiếp nhận được tự động suy ra (25% của giới hạn bộ nhớ V8/cgroup chặt hơn, chia cho hệ số khuếch đại tạm thời 8x). Cả giá trị suy ra và giá trị đặt rõ ràng đều bị giới hạn trong khoảng 8 MiB–2 GiB. Phần thân lớn hơn ngân sách hiệu dụng sẽ thất bại ngay lập tức với `413 body_exceeds_budget`; tranh chấp giữa các phần thân mà riêng lẻ vẫn có thể phục vụ sẽ tiếp tục trả về `503` có thể thử lại. 40–50 máy khách SSE dài đồng thời phụ thuộc vào ngân sách này + heap, không phải giới hạn cứng “tối đa 2”. Hãy đọc `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` tại `/api/monitoring/health` trước khi tinh chỉnh. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Tỷ lệ loại bỏ khi heap chịu áp lực (`heapUsed / heap_size_limit`) đối với việc tiếp nhận yêu cầu nặng theo BYTE và STRUCTURE (#10183, #10268, #10437). Một yêu cầu nặng đồng thời vượt quá `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` chỉ bị loại bỏ với mã `503` có thể thử lại khi heap CŨNG đạt hoặc vượt tỷ lệ này; trên heap khỏe mạnh, yêu cầu được tiếp nhận thông qua dung lượng dự phòng khi khỏe mạnh. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (mặc định `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Dung lượng bổ sung có giới hạn cho đường dẫn nhanh khi heap khỏe mạnh (#10437), áp dụng cho **cả** STRUCTURE và BYTE (`admitChatRequest`, bao gồm các phần thân ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Nếu không có giới hạn này, mọi yêu cầu khi hệ thống bận nhưng heap vẫn khỏe mạnh đều bỏ qua kiểm soát tiếp nhận mà không có mức trần. Khi số lượng lease đồng thời hoạt động qua đường dẫn heap khỏe mạnh đạt mức này, các yêu cầu tiếp theo trong lúc bận sẽ chuyển sang CÙNG đường dẫn chờ có giới hạn/loại bỏ được dùng khi heap thực sự chịu áp lực. `0` vô hiệu hóa hoàn toàn cơ chế bỏ qua này. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Số lượng tin nhắn khiến một yêu cầu chat được phân loại là nặng ngay cả khi phần thân nhỏ hơn ngưỡng byte. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Số lượng công cụ khiến một yêu cầu chat được phân loại là nặng ngay cả khi phần thân nhỏ hơn ngưỡng byte. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Ước tính token thận trọng dựa trên kích thước chuỗi, dùng để phân loại một yêu cầu là nặng; đây là giá trị đại diện cho chi phí tiếp nhận, không phải cơ chế token hóa phục vụ tính phí của nhà cung cấp. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (đã vô hiệu hóa) | `src/shared/middleware/chatBodyAdmission.ts` | Giới hạn lịch sử chat tùy chọn cần chủ động bật. Mặc định bị vô hiệu hóa: số lượng tin nhắn là chính sách triển khai, không phải thuộc tính chung của một yêu cầu, và việc giới hạn tại đây sẽ từ chối các cuộc trò chuyện bằng mã `413` dứt điểm trước khi quy trình nén có thể khiến chúng trở nên có thể phục vụ. Mức tăng trưởng heap được giới hạn bởi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` và cơ chế loại bỏ khi heap chịu áp lực. Hãy đặt một giá trị dương trên các môi trường triển khai bị hạn chế bộ nhớ và cần mức trần cứng; khi đó, phần vượt quá sẽ nhận phản hồi `413` có cấu trúc, yêu cầu rút gọn. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Giới hạn cứng cho phản hồi thượng nguồn không phát trực tuyến được lưu đệm hoàn toàn trong bộ nhớ. Khi vượt mức này, trình đọc thượng nguồn sẽ bị hủy và yêu cầu thất bại nhanh, thay vì tiếp tục tăng một chuỗi không giới hạn cho đến khi heap cạn kiệt. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Số byte tối đa được chuyển tiếp qua đường truyền từ các header phản hồi thượng nguồn. Khi vượt quá giới hạn, các header có mức ưu tiên thấp hơn (ví dụ: `x-codex-*`, `x-oai-request-id`) sẽ bị loại bỏ để duy trì trong giới hạn header phổ biến của reverse proxy. Đặt giá trị cao hơn để chuyển tiếp nhiều metadata thượng nguồn hơn, nhưng kích thước header phản hồi sẽ lớn hơn. | -| `CORS_ORIGIN` | _(chưa đặt)_ | `src/server/cors/origins.ts` | Danh sách cho phép CORS theo một origin kiểu cũ. Ưu tiên dùng `CORS_ALLOWED_ORIGINS` cho các lần triển khai mới. CORS chỉ dành cho các máy khách API trình duyệt khác origin; thay vào đó, các thao tác ghi trên dashboard đã xác thực sử dụng yêu cầu cùng origin kết hợp với biện pháp bảo vệ CSRF gắn với phiên. | -| `CORS_ALLOWED_ORIGINS` | _(chưa đặt)_ | `src/server/cors/origins.ts` | Danh sách cho phép CORS được phân tách bằng dấu phẩy. Không gửi ký tự đại diện trừ khi `CORS_ALLOW_ALL=true` được cấu hình rõ ràng. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Cơ chế thoát chỉ dành cho môi trường phát triển để phản hồi lại bất kỳ `Origin` nào của trình duyệt. Không bật trong các bản triển khai dùng chung hoặc môi trường production. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Chặn các lệnh gọi đến nhà cung cấp nhắm vào các dải IP riêng/loopback/link-local. Chỉ vô hiệu hóa trong các môi trường kiểm thử cô lập. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Cho phép URL của nhà cung cấp trỏ đến các mạng riêng/cục bộ (localhost, 192.168.x.x, 10.x.x.x, v.v.). **BẮT BUỘC đối với các nhà cung cấp tự lưu trữ** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Khi là `false`, dashboard sẽ từ chối xác thực các URL cục bộ. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Cho phép thêm/xác thực nhà cung cấp tại các địa chỉ cục bộ/riêng (127.0.0.1, localhost, LAN, các dải riêng) — chỉ áp dụng cho đường dẫn xác thực nhà cung cấp. **Mặc định là `true`** (ưu tiên cục bộ); đặt thành `false` để thực thi việc chỉ cho phép địa chỉ công khai một cách nghiêm ngặt. Các endpoint metadata đám mây (169.254.169.254, metadata.google.internal) vẫn luôn bị chặn. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Cho phép các route `/v1/audio/*` (chuyển lời nói thành văn bản, giọng nói, bản dịch) sử dụng một node nhà cung cấp tương thích OpenAI được lưu trữ bên ngoài localhost. Mặc định tắt — định tuyến âm thanh đến máy chủ từ xa sẽ thay đổi danh tính lưu lượng ra và phải là quyết định rõ ràng của người vận hành. Các node loopback/riêng (localhost, 127.0.0.1, 172.16-31.x) luôn được phép và không bị ảnh hưởng. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Cho phép `POST /v1/rerank` (và bước xếp hạng lại loopback của công cụ bộ nhớ) sử dụng một node nhà cung cấp tương thích OpenAI được lưu trữ bên ngoài localhost — một máy trong LAN hoặc máy ngang hàng Tailscale đang chạy TEI, Infinity, vLLM, v.v. Mặc định tắt — việc định tuyến đến máy chủ từ xa sẽ thay đổi danh tính lưu lượng ra và phải là quyết định rõ ràng của người vận hành. Các node loopback (localhost, 127.0.0.1, 172.16-31.x) luôn được phép và không bị ảnh hưởng. Các node từ xa cũng phải đáp ứng chính sách URL đi ra của nhà cung cấp (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); lưu lượng không bao giờ được định tuyến đến các máy chủ metadata đám mây. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Khi OIDC được bật, vô hiệu hóa đăng nhập bằng mật khẩu để người dùng chỉ có thể xác thực thông qua Đăng nhập một lần OIDC. Bí danh rút gọn `OIDC_DISABLE_PASSWORD_LOGIN` cũng được chấp nhận; Cờ tính năng Dashboard có cùng khóa sẽ được ưu tiên. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Bí danh rút gọn của `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Biến | Mặc định | Tệp nguồn | Mô tả | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt được kết hợp với các mã định danh phần cứng để tạo dấu vân tay máy. Hãy thay đổi theo từng lần triển khai để đảm bảo tính cô lập. | +| `OMNIROUTE_CLI_SALT` | _(không đặt = salt ngẫu nhiên cho mỗi lần cài đặt, được lưu tại `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC dùng để tạo token xác thực CLI cục bộ. Việc đặt giá trị này sẽ xoay vòng tất cả token CLI trên máy và luôn được ưu tiên hơn salt đã lưu. Xem `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Đặt cờ `Secure` trên cookie phiên. **Phải là `true`** khi chạy phía sau HTTPS. | +| `REQUIRE_API_KEY` | `false` | Middleware API | Khi là `true`, tất cả yêu cầu proxy `/v1/*` phải bao gồm một khóa API hợp lệ. **Cờ này không kiểm soát `GET /v1/models`**, endpoint này tuân theo trạng thái đăng nhập của bảng điều khiển (`requireAuthForModels`) — vì vậy, phản hồi `401` từ `/v1/models` KHÔNG có nghĩa là hoạt động suy luận đã được bảo vệ. Xem `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Cho phép hiển thị đầy đủ giá trị khóa API trong giao diện người dùng của Bảng điều khiển. Có thể cấu hình từ phần Cờ tính năng của Bảng điều khiển; gây rủi ro bảo mật trên các phiên bản dùng chung. | +| `NO_LOG_API_KEY_IDS` | _(trống)_ | `src/lib/compliance/index.ts` | Danh sách ID khóa API được phân tách bằng dấu phẩy sẽ bỏ qua việc ghi nhật ký yêu cầu (tuân thủ GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(không đặt = không giới hạn)_ | `src/shared/utils/apiKeyPolicy.ts` | Hạn mức yêu cầu dự phòng mỗi ngày được áp dụng cho các khóa API có cột `rate_limits` là null. Không đặt hoặc để trống: không có giới hạn ngầm định (#2289, #11017). `0` cũng tương đương như vậy (không giới hạn). Số nguyên dương N cho phép N/ngày, 5N/tuần, 20N/tháng. Các giá trị không rỗng nhưng sai định dạng sẽ quay về các khoảng giới hạn cũ là 1000/ngày, 5000/tuần, 20000/tháng. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Kích thước nội dung yêu cầu tối đa được phép. Từ chối các payload vượt quá giới hạn này. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Nội dung yêu cầu thực tế bằng hoặc lớn hơn ngưỡng này sẽ nhận lease tiếp nhận nguyên tử dành cho tác vụ nặng, cục bộ trong tiến trình trước khi phân tích JSON (đường dẫn BYTE, bao gồm `POST /v1/responses`). Có cùng cơ chế thoát khi headroom ở trạng thái lành mạnh như [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) dành cho nội dung nặng về cấu trúc; vẫn bị giới hạn bởi `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) để [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) không tái diễn. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Giới hạn cứng của tuyến chat, được thực thi dựa trên số byte đọc trong quá trình tiếp nhận có giới hạn, bao gồm các yêu cầu thiếu, không hợp lệ hoặc khai báo không trung thực `Content-Length`; phần vượt quá sẽ nhận `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(không đặt — không giới hạn số lượng yêu cầu)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** giới hạn SỐ LƯỢNG yêu cầu cũ này giờ chỉ có hiệu lực khi được đặt rõ ràng. Khi không đặt (mặc định), việc tiếp nhận chat nặng được kiểm soát bởi `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — một ngân sách BYTE được tự động suy ra, định cỡ theo giới hạn bộ nhớ thực tế của tiến trình trong **một tiến trình** (một heap V8). Hai yêu cầu `/v1/responses` chồng lấn, mỗi yêu cầu khoảng 750k token, làm sập các heap khoảng 12 GiB (#7849) — đây là cảnh báo về **ngân sách bộ nhớ**, không phải giới hạn sản phẩm cứng là 2. Một tiến trình khỏe mạnh (heap dưới tỷ lệ loại bỏ) CÓ THỂ tiếp nhận thêm các yêu cầu `/v1/responses` dài đồng thời thông qua `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Hàng chục máy khách SSE chạy lâu (40–50) phụ thuộc vào heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, không phải “tối đa 2”. Việc tăng giá trị này một cách mù quáng để “tận dụng máy chủ” sẽ tái diễn #7849. Hãy nhân rộng **các heap** với **N `DATA_DIR` độc lập** (#11024); tuyệt đối không dùng `replicas>1` trên một tệp SQLite. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(được tự động suy ra)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** giá trị ghi đè cho ngân sách byte tiếp nhận được tự động suy ra (25% của giới hạn bộ nhớ V8/cgroup chặt hơn, chia cho hệ số khuếch đại tạm thời 8x). Cả giá trị suy ra và giá trị đặt rõ ràng đều bị giới hạn trong khoảng 8 MiB–2 GiB. Phần thân lớn hơn ngân sách hiệu dụng sẽ thất bại ngay lập tức với `413 body_exceeds_budget`; tranh chấp giữa các phần thân mà riêng lẻ vẫn có thể phục vụ sẽ tiếp tục trả về `503` có thể thử lại. 40–50 máy khách SSE dài đồng thời phụ thuộc vào ngân sách này + heap, không phải giới hạn cứng “tối đa 2”. Hãy đọc `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` tại `/api/monitoring/health` trước khi tinh chỉnh. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Tỷ lệ loại bỏ khi heap chịu áp lực (`heapUsed / heap_size_limit`) đối với việc tiếp nhận yêu cầu nặng theo BYTE và STRUCTURE (#10183, #10268, #10437). Một yêu cầu nặng đồng thời vượt quá `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` chỉ bị loại bỏ với mã `503` có thể thử lại khi heap CŨNG đạt hoặc vượt tỷ lệ này; trên heap khỏe mạnh, yêu cầu được tiếp nhận thông qua dung lượng dự phòng khi khỏe mạnh. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (mặc định `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Dung lượng bổ sung có giới hạn cho đường dẫn nhanh khi heap khỏe mạnh (#10437), áp dụng cho **cả** STRUCTURE và BYTE (`admitChatRequest`, bao gồm các phần thân ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Nếu không có giới hạn này, mọi yêu cầu khi hệ thống bận nhưng heap vẫn khỏe mạnh đều bỏ qua kiểm soát tiếp nhận mà không có mức trần. Khi số lượng lease đồng thời hoạt động qua đường dẫn heap khỏe mạnh đạt mức này, các yêu cầu tiếp theo trong lúc bận sẽ chuyển sang CÙNG đường dẫn chờ có giới hạn/loại bỏ được dùng khi heap thực sự chịu áp lực. `0` vô hiệu hóa hoàn toàn cơ chế bỏ qua này. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Số lượng tin nhắn khiến một yêu cầu chat được phân loại là nặng ngay cả khi phần thân nhỏ hơn ngưỡng byte. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Số lượng công cụ khiến một yêu cầu chat được phân loại là nặng ngay cả khi phần thân nhỏ hơn ngưỡng byte. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Ước tính token thận trọng dựa trên kích thước chuỗi, dùng để phân loại một yêu cầu là nặng; đây là giá trị đại diện cho chi phí tiếp nhận, không phải cơ chế token hóa phục vụ tính phí của nhà cung cấp. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (đã vô hiệu hóa) | `src/shared/middleware/chatBodyAdmission.ts` | Giới hạn lịch sử chat tùy chọn cần chủ động bật. Mặc định bị vô hiệu hóa: số lượng tin nhắn là chính sách triển khai, không phải thuộc tính chung của một yêu cầu, và việc giới hạn tại đây sẽ từ chối các cuộc trò chuyện bằng mã `413` dứt điểm trước khi quy trình nén có thể khiến chúng trở nên có thể phục vụ. Mức tăng trưởng heap được giới hạn bởi `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` và cơ chế loại bỏ khi heap chịu áp lực. Hãy đặt một giá trị dương trên các môi trường triển khai bị hạn chế bộ nhớ và cần mức trần cứng; khi đó, phần vượt quá sẽ nhận phản hồi `413` có cấu trúc, yêu cầu rút gọn. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Giới hạn cứng cho phản hồi thượng nguồn không phát trực tuyến được lưu đệm hoàn toàn trong bộ nhớ. Khi vượt mức này, trình đọc thượng nguồn sẽ bị hủy và yêu cầu thất bại nhanh, thay vì tiếp tục tăng một chuỗi không giới hạn cho đến khi heap cạn kiệt. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Số byte tối đa được chuyển tiếp qua đường truyền từ các header phản hồi thượng nguồn. Khi vượt quá giới hạn, các header có mức ưu tiên thấp hơn (ví dụ: `x-codex-*`, `x-oai-request-id`) sẽ bị loại bỏ để duy trì trong giới hạn header phổ biến của reverse proxy. Đặt giá trị cao hơn để chuyển tiếp nhiều metadata thượng nguồn hơn, nhưng kích thước header phản hồi sẽ lớn hơn. | +| `CORS_ORIGIN` | _(chưa đặt)_ | `src/server/cors/origins.ts` | Danh sách cho phép CORS theo một origin kiểu cũ. Ưu tiên dùng `CORS_ALLOWED_ORIGINS` cho các lần triển khai mới. CORS chỉ dành cho các máy khách API trình duyệt khác origin; thay vào đó, các thao tác ghi trên dashboard đã xác thực sử dụng yêu cầu cùng origin kết hợp với biện pháp bảo vệ CSRF gắn với phiên. | +| `CORS_ALLOWED_ORIGINS` | _(chưa đặt)_ | `src/server/cors/origins.ts` | Danh sách cho phép CORS được phân tách bằng dấu phẩy. Không gửi ký tự đại diện trừ khi `CORS_ALLOW_ALL=true` được cấu hình rõ ràng. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Cơ chế thoát chỉ dành cho môi trường phát triển để phản hồi lại bất kỳ `Origin` nào của trình duyệt. Không bật trong các bản triển khai dùng chung hoặc môi trường production. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Bí danh cũ (alias). Công tắc dashboard của nó (một giá trị ghi đè trong CSDL) được đọc trước giá trị biến môi trường này; `false`, `0`, `no` hoặc `off` ở bất kỳ đâu trong hai nơi sẽ tắt các kiểm tra máy chủ theo cách `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` vẫn làm, kể cả khi công tắc dashboard của cờ đó được lưu ở trạng thái tắt; các giá trị khác giao bộ bảo vệ cho hai cờ bên dưới. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Tắt các kiểm tra máy chủ của bộ bảo vệ URL đi ra, bao gồm cả việc chặn metadata đám mây (toàn bộ 169.254.0.0/16, metadata.google.internal), trên các đường dẫn nhà cung cấp tuân theo chế độ bảo vệ: xác thực URL, khám phá mô hình, URL cơ sở của node nhà cung cấp (bao gồm cả các node xếp hạng lại từ xa) và phép thử dự phòng proxy, cùng các đường dẫn đi ra khác liên quan đến nhà cung cấp như tải hình ảnh/phương tiện từ xa. Nó cũng cho phép các đích webhook riêng, ngoại trừ các endpoint metadata đám mây/link-local vốn vẫn bị chặn kể cả khi cờ này bật. Các yêu cầu trò chuyện được gửi qua `BaseExecutor.execute()` vẫn giữ kiểm tra metadata đám mây của mình. Các nhà cung cấp cục bộ hoạt động tốt với mặc định: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (mặc định `true`) đã cho phép URL cục bộ và LAN trên các đường dẫn xác thực, khám phá và node nhà cung cấp; phép thử dự phòng proxy và các đích webhook riêng chỉ tham chiếu cờ này và chặn các máy chủ cục bộ/LAN khi nó tắt. Ghi đè `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Giá trị được lưu từ công tắc dashboard được ưu tiên hơn biến này. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Cho phép URL nhà cung cấp tại các địa chỉ cục bộ/riêng (127.0.0.1, localhost, LAN, các dải riêng) cho xác thực nhà cung cấp, khám phá mô hình, trò chuyện và các đường dẫn đi ra khác liên quan đến nhà cung cấp mà chế độ bảo vệ chi phối. **Mặc định là `true`** (ưu tiên cục bộ): khi đó bộ bảo vệ chặn các endpoint metadata đám mây — toàn bộ 169.254.0.0/16 cùng các tên máy chủ metadata đã biết (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Đặt thành `false` cho chế độ chỉ công khai, chế độ này chặn cả các máy chủ riêng và loopback. Bộ bảo vệ kiểm tra tên máy chủ hoặc địa chỉ IP theo đúng như đã ghi, trước mọi tra cứu DNS; ở chế độ chỉ công khai, wrapper safe-fetch còn phân giải thêm tên máy chủ và từ chối mọi câu trả lời thuộc dải riêng. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` tắt các kiểm tra này trên những đường dẫn mà hàng của nó liệt kê. Các nhà cung cấp cục bộ tích hợp sẵn như LM Studio, Ollama, vLLM, Llamafile và Triton bỏ qua mọi kiểm tra máy chủ cho xác thực khóa và trò chuyện. Giá trị được lưu từ công tắc dashboard được ưu tiên hơn biến này. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Cho phép các route `/v1/audio/*` (chuyển lời nói thành văn bản, giọng nói, bản dịch) sử dụng một node nhà cung cấp tương thích OpenAI được lưu trữ bên ngoài localhost. Mặc định tắt — định tuyến âm thanh đến máy chủ từ xa sẽ thay đổi danh tính lưu lượng ra và phải là quyết định rõ ràng của người vận hành. Các node loopback/riêng (localhost, 127.0.0.1, 172.16-31.x) luôn được phép và không bị ảnh hưởng. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Cho phép `POST /v1/rerank` (và bước xếp hạng lại loopback của công cụ bộ nhớ) sử dụng một node nhà cung cấp tương thích OpenAI được lưu trữ bên ngoài localhost — một máy trong LAN hoặc máy ngang hàng Tailscale đang chạy TEI, Infinity, vLLM, v.v. Mặc định tắt — việc định tuyến đến máy chủ từ xa sẽ thay đổi danh tính lưu lượng ra và phải là quyết định rõ ràng của người vận hành. Các node loopback (localhost, 127.0.0.1, 172.16-31.x) luôn được phép và không bị ảnh hưởng. URL cơ sở của node từ xa cũng phải đáp ứng chính sách URL đi ra của nhà cung cấp (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Khi OIDC được bật, vô hiệu hóa đăng nhập bằng mật khẩu để người dùng chỉ có thể xác thực thông qua Đăng nhập một lần OIDC. Bí danh rút gọn `OIDC_DISABLE_PASSWORD_LOGIN` cũng được chấp nhận; Cờ tính năng Dashboard có cùng khóa sẽ được ưu tiên. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Bí danh rút gọn của `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Danh sách kiểm tra tăng cường bảo mật diff --git a/docs/i18n/yo/CONTRIBUTING.md b/docs/i18n/yo/CONTRIBUTING.md index 365960e574fd..72ef872d4204 100644 --- a/docs/i18n/yo/CONTRIBUTING.md +++ b/docs/i18n/yo/CONTRIBUTING.md @@ -400,19 +400,19 @@ Kọ àwọn unit test sínú `tests/unit/` tí yóò kéré tán bo: ## Àtòjọ Àyẹ̀wò Pull Request - [ ] Àwọn ìdánwò kọjá (`npm test`) -- [ ] Linting kọjá (`npm run lint`) -- [ ] Build ṣàṣeyọrí (`npm run build`) -- [ ] A fi àwọn type TypeScript kún un fún àwọn function àti interface gbogbogbò tuntun -- [ ] Kò sí secret tàbí iye fallback tí a kọ sínú kóòdù -- [ ] A fi àwọn ẹ̀rí ìdánimọ̀ upstream gbogbogbò sínú kóòdù nípasẹ̀ `resolvePublicCred()` (wo [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), kì í ṣe gẹ́gẹ́ bí literal láé -- [ ] Àwọn ìdáhùn àṣìṣe gba inú `buildErrorBody()` / `sanitizeErrorMessage()` kọjá — kò sí stack trace aláìṣe-àtúnṣe nínú body ìdáhùn (wo [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Àwọn àṣẹ shell (`exec` / `spawn`) ń fi àwọn iye runtime ránṣẹ́ nípasẹ̀ `env`, kì í ṣe nípasẹ̀ string interpolation -- [ ] A fìdí gbogbo àwọn input múlẹ̀ pẹ̀lú schema Zod -- [ ] A fi **fragment** changelog kún abẹ́ `changelog.d/{features|fixes|maintenance}/-.md` fún àwọn àyípadà tí olumulo yóò rí (wo [`changelog.d/README.md`](./changelog.d/README.md)) — **má ṣe** ṣàtúnṣe `CHANGELOG.md` ní tààrà; a máa ń kó àwọn fragment jọ nígbà release, wọn kì í sì í fa ìtakora láàárín àwọn PR láé -- [ ] A ṣe àfikún sí àkọsílẹ̀ ìtọ́sọ́nà (bí ó bá yẹ) -- [ ] Kò sí ìkìlọ̀ CodeQL / Secret-Scanning tuntun tí a ṣí, tàbí a kọ ọ̀kọ̀ọ̀kan sílẹ̀ pẹ̀lú ìdáláre ìmọ̀-ẹ̀rọ tó tọ́ka sí doc tó bá a mu nínú `docs/security/` +- [ ] Àyẹ̀wò lint kọjá (`npm run lint`) +- [ ] Ìkọ́lé ṣàṣeyọrí (`npm run build`) +- [ ] Àwọn irú TypeScript ti jẹ́ àfikún fún àwọn iṣẹ́ àti àwọn interface tuntun tí ó wà fún gbogbo ènìyàn +- [ ] Kò sí àwọn àṣírí tàbí iye àfẹ́yìntì tí a kọ sínú kóòdù ní tààrà +- [ ] Àwọn ẹ̀rí ìdánimọ̀ upstream tí ó wà fún gbogbo ènìyàn ni a fi sínú rẹ̀ nípasẹ̀ `resolvePublicCred()` (wo [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)), kì í ṣe gẹ́gẹ́ bí literal láé +- [ ] Àwọn ìdáhùn àṣìṣe ń gba `buildErrorBody()` / `sanitizeErrorMessage()` kọjá — kò sí àwọn stack trace aise nínú ara ìdáhùn (wo [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Àwọn àṣẹ shell (`exec` / `spawn`) ń fi àwọn iye runtime ránṣẹ́ nípasẹ̀ `env`, kì í ṣe nípasẹ̀ ìṣọ̀kan ọ̀rọ̀ +- [ ] Gbogbo àwọn input ni a fìdí rẹ̀ múlẹ̀ pẹ̀lú àwọn schema Zod +- [ ] A ti ṣàfikún **àjákù** changelog sí abẹ́ `changelog.d/{features|fixes|maintenance}/-.md` fún àwọn ìyípadà tí àwọn aṣàmúlò lè rí (wo [`changelog.d/README.md`](./changelog.d/README.md)) — **má ṣe** ṣàtúnṣe `CHANGELOG.md` ní tààrà; a máa ń kó àwọn àjákù jọ ní àkókò ìtújáde, wọn kì í sì í forí gbárí láàárín àwọn PR +- [ ] A ti mú àwọn ìwé àlàyé dọ́gba (bí ó bá yẹ) +- [ ] Kò sí ìkìlọ̀ CodeQL / Secret-Scanning tuntun tí a ṣí, tàbí a ti kọ ọ̀kọ̀ọ̀kan sílẹ̀ pẹ̀lú ìdáláre ìmọ̀ ẹ̀rọ tí ó tọ́ka sí ìwé `docs/security/` tó yẹ - [ ] Àwọn route tí ń dá child process sílẹ̀ (`/api/mcp/`, `/api/cli-tools/runtime/`) ni a pín sí `isLocalOnlyPath()` nínú `src/server/authz/routeGuard.ts` — wo [Òfin Líle #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] Kò sí trailer `Co-Authored-By` nínú àwọn ìfiránṣẹ́ commit — àwọn commit gbọ́dọ̀ farahàn lábẹ́ Git identity olówó repository nìkan (Òfin Líle #16) +- [ ] Kò sí trailer `Co-authored-by` ti AI/bot nínú àwọn ọ̀rọ̀ commit (Òfin Líle #16) — àwọn alábàáṣiṣẹ́ ènìyàn tí a tún lo iṣẹ́ wọn ni a fún ní ìkànsí pẹ̀lú àwọn trailer boṣewa `Co-authored-by: Name ` --- diff --git a/docs/i18n/yo/docs/architecture/QUALITY_GATES.md b/docs/i18n/yo/docs/architecture/QUALITY_GATES.md index 9e140c4cdebc..ba51afb6329e 100644 --- a/docs/i18n/yo/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/yo/docs/architecture/QUALITY_GATES.md @@ -15,264 +15,262 @@ tí kò sinmi lé irinṣẹ́ kan pàtó fún ètò kan náà, wo --- -## Àkójọ àwọn gẹ́ẹ̀tì àti àwọn prófáìlì ìṣiṣẹ́ +## Àkójọ àwọn ẹnubodè àti àwọn prófáìlì ìṣiṣẹ́ -### Gbígba olùdíje wọlé +### Gbigba olùdíje -Àwọn ìṣàn iṣẹ́ CI àti Quality Gates kọ̀ọ̀kan ń ṣe ìdájọ́ tó dúró ṣinṣin jáde: `Gate / CI` àti -`Gate / Quality`. Ìlànà gbígba wọlé tí a fi ẹ̀yà ṣe rẹ̀ ṣe àtòjọ gbogbo iṣẹ́ orísun gẹ́gẹ́ bí ohun -tí a nílò tàbí ohun ìmọ̀ràn. Iṣẹ́ tí a nílò, tí ó sì kan ọ̀ràn náà, gbọ́dọ̀ ṣàṣeyọrí: àwọn èsì -tí ó sọnù, tí a fagilé, tí a fo kọjá, tí ó ṣì ń dúró àti tí a kò mọ̀ kò lè fi ìpinnu PASS múlẹ̀. Ìṣètò -docs-nìkan tàbí catalog-nìkan tó péye lè mú kí ọ̀nà kóòdù kan má ṣe kan ọ̀ràn náà; -PR àkọsílẹ̀ kì í ṣe olùdíje tí a gbà. Àmì `hotfix` kì í yọ ohun tí a nílò láti fi ẹ̀rí hàn kúrò. +Àwọn ìṣàn-iṣẹ́ CI àti Quality Gates kọ̀ọ̀kan ń ṣe àbájáde ìdájọ́ tó dúró ṣinṣin: `Gate / CI` àti +`Gate / Quality`. Ìlànà gbigba wọn tí a ní ẹ̀yà rẹ̀ ṣe àkójọ gbogbo iṣẹ́ ìpele-òkè +gẹ́gẹ́ bí èyí tí a nílò tàbí èyí tó jẹ́ ìmọ̀ràn. Iṣẹ́ tí a nílò, tí ó sì kàn, gbọ́dọ̀ ṣàṣeyọrí: +àbájáde tí kò sí, tí a fagilé, tí a fo, tí ó ṣì ń dúró tàbí tí a kò mọ̀ kò lè fi ìdánilójú PASS múlẹ̀. +Ìsọ̀rí docs-only tàbí catalog-only tó fẹsẹ̀ múlẹ̀ lè mú kí ọ̀nà kóòdù kan má ṣe kàn; +PR tó ṣì jẹ́ àkọsílẹ̀ kì í ṣe olùdíje tí a gbà. Àmì `hotfix` kò yọ ìbéèrè ẹ̀rí kúrò. -Àwọn ìṣàn iṣẹ́ méjèèjì bo àwọn PR àti ìfiránṣẹ́ sí àwọn ẹ̀ka main/release, ìpilẹ̀ṣẹ̀ ọwọ́ àti -àwọn ìṣẹ̀lẹ̀ merge-group. Push, dispatch àti merge-group máa ń lo gbogbo àṣàyàn náà. Forks -àti àwọn merge groups máa ń lo àwọn runner tí a gbàlejò fún àwọn iṣẹ́ tí ì bá yan àwọn runner -tí a gbàlejò fúnra ẹni; a gbọ́dọ̀ ṣàyẹ̀wò pé agbára ìgbàlejò tó pọ̀ tó wà kí a tó tú u jáde. +Àwọn ìṣàn-iṣẹ́ méjèèjì bo àwọn PR àti àwọn ìfìránṣẹ́ sí àwọn ẹ̀ka main/release, ìbẹ̀rẹ̀ afọwọ́ṣe àti +àwọn ìṣẹ̀lẹ̀ merge-group. Push, dispatch àti merge-group máa ń ṣiṣẹ́ gbogbo àṣàyàn náà. Àwọn fork +àti merge group máa ń lo àwọn runner tí a gbalejo fún àwọn iṣẹ́ tí yóò máa yan àwọn runner +tí a gbalejo fúnra wa ní ọ̀nà míì; a gbọ́dọ̀ ṣàyẹ̀wò pé agbára ìgbalejo tó tó wà ṣáájú ìmújáde. -Ìwé ẹ̀rí JSON kọ̀ọ̀kan ń tọ́ka SHA tí a checkout, ìṣiṣẹ́ ìṣàn iṣẹ́ àti ìgbìyànjú rẹ̀. -CLI máa kọ àìbáramu láàárín SHA checkout àti ti ìṣẹ̀lẹ̀. Àwọn àyẹ̀wò ìṣàn iṣẹ́ so jíjẹ́ ọmọ ẹgbẹ́ ìlànà -mọ́ àtòjọ `needs` ti iṣẹ́ ìdájọ́ náà, kí ọ̀nà tuntun tàbí èyí tí a yọ má bàa pòórá ní ìkọ̀kọ̀. -Àwọn ìwé ẹ̀rí náà bo ìṣàn iṣẹ́ tiwọn, kì í ṣe ìtẹ̀jáde, ìmúṣiṣẹ́, tàbí àwọn ìlànà inú -ẹ̀rọ àyẹ̀wò ìmọ̀ràn tó ti wà tẹ́lẹ̀. Mímú orúkọ àyẹ̀wò méjèèjì ṣiṣẹ́ nínú àwọn òfin ẹ̀ka jẹ́ -ìyípadà ìṣàkóso lọ́tọ̀; fífi àwọn iṣẹ́ wọ̀nyí kún un kò dáàbò bo ẹ̀ka kan fúnra rẹ̀. +Gbogbo ìwé-ẹ̀rí JSON ń ṣe ìdánimọ̀ SHA tí a checkout, ìṣiṣẹ́ ìṣàn-iṣẹ́ àti ìgbìyànjú. +CLI kọ ìyàtọ̀ láàárín SHA checkout àti SHA ìṣẹ̀lẹ̀. Àwọn ìdánwò ìṣàn-iṣẹ́ so ọmọ ẹgbẹ́ ìlànà +mọ́ àtòjọ `needs` ti iṣẹ́ ìdájọ́, kí ọ̀nà tuntun tàbí ọ̀nà tí a yọ má bàa lè pòórá láìsí ìkìlọ̀. +Àwọn ìwé-ẹ̀rí náà bo ìṣàn-iṣẹ́ tiwọn nìkan, kì í ṣe títẹ̀jáde, ìmúṣiṣẹ́, tàbí àwọn ìlànà inú +scanner ìmọ̀ràn tó ti wà. Mímú orúkọ check méjèèjì ṣiṣẹ́ nínú àwọn òfin ẹ̀ka jẹ́ +àtúnṣe ìṣàkóso ọ̀tọ̀; fífi àwọn iṣẹ́ wọ̀nyí kún un kò dáàbò bo ẹ̀ka kan fúnra rẹ̀. -### Àkójọ àyẹ̀wò aimi +### Àkójọ scan aláìyípadà -Àkójọ npm-alias tí a fi ẹ̀yà ṣe àti jíjẹ́ ọmọ ẹgbẹ́ àyẹ̀wò aimi wà nínú +Àkójọ npm-alias tí a ní ẹ̀yà rẹ̀ àti ọmọ ẹgbẹ́ static-scan wà nínú `config/quality/gate-manifest.json`. Ṣiṣe `npm run check:gate-manifest` láti fìdí -àwọn orúkọ script àti àwọn àṣẹ gangan múlẹ̀ ní ìfiwéra pẹ̀lú `package.json`; àfikún, ìyọkúrò àti -yíyí àṣẹ padà yóò mú kí hook agbègbè àti àwọn iṣẹ́ ìṣètò-irú-ìyípadà nínú CI kùnà. -Alias kì í ṣe iṣẹ́ ìṣàn iṣẹ́, matrix instance tàbí test case: a kò gbọ́dọ̀ fi iye àwọn wọ̀nyí -hàn bí ẹni pé wọ́n lè paarọ̀ ara wọn. +àwọn orúkọ script àti àwọn àṣẹ gangan múlẹ̀ ní ìbámu pẹ̀lú `package.json`; àwọn àfikún, ìyọkúrò àti +ìyípadà àṣẹ máa ń mú kí hook agbègbè àti àwọn iṣẹ́ ìsọ̀rí àyípadà nínú CI kùnà. +Alias kì í ṣe iṣẹ́ ìṣàn-iṣẹ́, àpẹẹrẹ matrix tàbí test case: a kò gbọ́dọ̀ gbé àwọn iye wọ̀nyí kalẹ̀ +bí ohun tí a lè fi rọ́pò ara wọn. Lo `npm run quality:scan -- --list` tàbí `npm run quality:scan:fast -- --list` -láti ṣàyẹ̀wò àwọn alias tí a yàn láì ṣiṣẹ́ wọn. Runner náà ń pe +láti ṣàyẹ̀wò àwọn alias tí a yàn láìṣe wọn. Runner náà pe npm entrypoint, nítorí náà runtime rẹ̀ (pẹ̀lú Bun níbi tí a ti ṣètò rẹ̀) ni a pa mọ́. -Manifest náà ṣàkọsílẹ̀ àwọn alias tí kò sí nínú àwọn prófáìlì wọ̀nyí gẹ́gẹ́ bí èyí tí a pè lọ́tọ̀, àti pé -a kò gba àwọn àṣẹ ìtọ́jú láàyè nínú àwọn prófáìlì àyẹ̀wò kíkà-nìkan. +Manifest náà ṣàkọsílẹ̀ àwọn alias tí kò sí nínú àwọn prófáìlì wọ̀nyí gẹ́gẹ́ bí èyí tí a pè lọ́tọ̀ọ̀tọ̀, àti pé +a kò gba àwọn àṣẹ ìtọju láàyè nínú àwọn prófáìlì scan kíkà-nìkan. -Àwọn prófáìlì wọ̀nyí bo àyẹ̀wò aimi nìkan. Wọn kò jẹ́rìí sí àwọn àyẹ̀wò ọjà, -coverage, packaging, àwọn àyẹ̀wò òde tàbí gbogbo ìtẹ́wọ́gbà ìtújáde olùdíje kan. -Gbígba wọlé nípasẹ̀ ìṣàn iṣẹ́ ń lo `config/quality/admission-policy.json` tí a so pọ̀ àti +Àwọn prófáìlì wọ̀nyí bo static scan nìkan. Wọn kì í jẹ́rìí àwọn ìdánwò ọjà, +coverage, packaging, àwọn check òde tàbí gbogbo ìfọwọ́sí ìtújáde olùdíje kan. +Gbigba ìṣàn-iṣẹ́ lo `config/quality/admission-policy.json` tí a sopọ̀ mọ́ àti `scripts/quality/admission-verdict.mjs`. Àwọn prófáìlì release-observer ṣì wà lọ́tọ̀; -ṣàyẹ̀wò àwọn àyẹ̀wò àti ìwé ẹ̀rí tí ó kan wọn lọ́tọ̀. Àkójọ àlàyé -tó wà nísàlẹ̀ jẹ́ ìtọ́kasí, kì í ṣe ẹ̀rí pé gẹ́ẹ̀tì kan ṣiṣẹ́ ní tòótọ́. +ṣàyẹ̀wò àwọn check àti ìwé-ẹ̀rí wọn tó kàn lọ́tọ̀ọ̀tọ̀. Àkójọ àlàyé +ní ìsàlẹ̀ jẹ́ ìtọ́kasí, kì í ṣe ẹ̀rí pé ẹnubodè kan ṣiṣẹ́ ní tòótọ́. -Àwọn script wà lábẹ́ `scripts/check/` (àwọn gẹ́ẹ̀tì ìlànà) àti `scripts/quality/` (ẹ̀rọ ratchet). +Àwọn script wà lábẹ́ `scripts/check/` (àwọn ẹnubodè ìlànà) àti `scripts/quality/` (ẹ́ńjìnnì ratchet). Orísun òtítọ́ CI ni `.github/workflows/ci.yml`. ### Ọ̀nà yíyára fún PR ìtújáde (`quality.yml`) -`.github/workflows/quality.yml` ń ṣe àfikún CI lórí àwọn PR main/release, àwọn -ìfiránṣẹ́ sí ẹ̀ka tí a dáàbò bo, dispatch àti merge groups. Àwọn PR máa ń lo àwọn àyẹ̀wò yíyára tí a fi path yàn. -A ti yọ duplicate build tí a pa títí láé kúrò; àwọn àyẹ̀wò build/package/boot gidi ṣì wà nínú CI. +`.github/workflows/quality.yml` ṣe àfikún CI lórí àwọn PR main/release, àwọn push sí ẹ̀ka tí a dáàbò bo, +dispatch àti merge group. Àwọn PR lo àwọn check yíyára tí a fi path ṣe àlẹ́mọ́. A yọ duplicate build +tí a ti pa títí láé kúrò; àwọn check build/package/boot gidi ṣì wà nínú CI. -| Iṣẹ́ | Ìwọ̀n | Dídènà | -| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| `Docs Gates (fast-path)` | Àwọn PR docs/kóòdù; àwọn ìtọ́kasí docs API àti docs-all | Bẹ́ẹ̀ ni | -| `Fast Quality Gates` | Àwọn PR kóòdù; àwọn àyẹ̀wò aimi, typecheck, dashboard typecheck, àwọn unit test tí ìyípadà kan | Bẹ́ẹ̀ ni | -| `Forgotten sibling tests` | Àwọn PR kóòdù; a tọpa àwọn module tí a yí padà sí àwọn static consumer àti àwọn candidate sibling test; àwọn path barrel àti dynamic-import ni a máa ń jọ̀wọ́ gẹ́gẹ́ bí ìwádìí ìmọ̀ràn, pẹ̀lú àwọn àyọkúrò allowlist tí a tọ́ka sí | **Ìmọ̀ràn** | -| `Vitest (fast-path)` | Àwọn PR kóòdù; fast vitest suite | Bẹ́ẹ̀ ni | -| `Unit Tests fast-path` | Àwọn PR kóòdù; 4-shard unit suite | Bẹ́ẹ̀ ni | -| `No new ESLint warnings` | Àwọn PR kóòdù; suppressions-aware lint guard | Bẹ́ẹ̀ ni, pẹ̀lú forks | -| `Merge integrity (changelog + generated skills)` | Àwọn PR tí kì í ṣe àkọsílẹ̀; ìbámu changelog àti generated skill | Bẹ́ẹ̀ ni, pẹ̀lú forks | +| Iṣẹ́ | Ààlà | Dídènà | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `Docs Gates (fast-path)` | Àwọn PR docs/kóòdù; àwọn ìtọ́kasí API docs àti docs-all | Bẹ́ẹ̀ ni | +| `Fast Quality Gates` | Àwọn PR kóòdù; àwọn static check, typecheck, dashboard typecheck, àwọn unit test tí ó ní ipa | Bẹ́ẹ̀ ni | +| `Forgotten sibling tests` | Àwọn PR kóòdù; a tọpa àwọn module tí ó yí padà sí àwọn static consumer àti àwọn candidate sibling test; àwọn ipa ọ̀nà barrel àti dynamic-import ni a máa ń jọ̀wọ́ gẹ́gẹ́ bí àyẹ̀wò ìmọ̀ràn, pẹ̀lú àwọn àyọkà allowlist tí a tọ́ka sí | **Ìmọ̀ràn** | +| `Vitest (fast-path)` | Àwọn PR kóòdù; àkójọpọ̀ vitest yíyára | Bẹ́ẹ̀ ni | +| `Unit Tests fast-path` | Àwọn PR kóòdù; àkójọpọ̀ unit ológe mẹ́rin | Bẹ́ẹ̀ ni | +| `No new ESLint warnings` | Àwọn PR kóòdù; olùṣọ́ lint tó mọ̀ nípa suppression | Bẹ́ẹ̀ ni, pẹ̀lú àwọn fork | +| `Merge integrity (changelog + generated skills)` | Àwọn PR tí kì í ṣe àkọsílẹ̀; ìmúdójúìwọ̀n changelog àti generated skill | Bẹ́ẹ̀ ni, pẹ̀lú àwọn fork | #### Ìròyìn àwọn sibling test tí a gbàgbé -`npm run check:forgotten-sibling-tests` tún máa ń lo import resolver tó wà lẹ́yìn test-impact map. -Fún gbogbo production module tí a yí padà, ó máa ń jọ̀wọ́ àwọn ẹ̀wọ̀n -`changed module/symbol -> static consumer -> candidate sibling test` tó ṣeé tún ṣe lọ́nà kan náà nígbà tí candidate -test náà kò sí nínú diff pull-request. A pa àkótán Markdown àti èsì JSON mọ́ gẹ́gẹ́ bí -artifact ìṣàn iṣẹ́ `forgotten-sibling-tests` fún ìṣètò ìwọ̀n kí ìtújáde dídènà èyíkéyìí tó bẹ̀rẹ̀. +`npm run check:forgotten-sibling-tests` tún lo import resolver tó wà lẹ́yìn test-impact map. +Fún gbogbo production module tí ó yí padà, ó máa ń ròyìn àwọn ẹ̀wọ̀n +`changed module/symbol -> static consumer -> candidate sibling test` tí àbájáde wọn kò yí padà +nígbà tí candidate test náà kò bá sí nínú diff pull-request. A máa ń pa àkótán Markdown àti àbájáde JSON mọ́ gẹ́gẹ́ bí +artifact ìṣàn-iṣẹ́ `forgotten-sibling-tests` fún ìṣètò ìwọ̀n ṣáájú ìmújáde dídènà èyíkéyìí. -Àwọn àtún-gbejáde barrel àti àwọn ìgbéwọlé aláyípadà jẹ́ fún ìṣàwárí ìṣòro ìyànjú nìkan; wọn kì í dá -àbájáde ìdènà sílẹ̀ láé. Àwọn àfikún tí a ti ṣàyẹ̀wò wà nínú -`config/quality/forgotten-sibling-allowlist.json`. Gbogbo àkọsílẹ̀ gbọ́dọ̀ dárúkọ test olùlò àti test -olùdíje, sọ ìdí pàtó kan, kí ó sì tọ́ka sí issue tàbí pull request GitHub kan. Àwọn àkọsílẹ̀ tí ìṣètò wọn kò pé máa ń -kùnà ní ọ̀nà tí ó ní ààbò. Àwọn àfikún kò lè fi test olùdíje tí a ti pa rẹ́ tàbí diff kan tí ó ṣàfikún `.skip`/`.todo` pamọ́; -dídẹkun agbára assertion àti àwọn ọ̀nà ìbòjú mìíràn ṣì jẹ́ ojúṣe ẹnu-ọ̀nà -`check:test-masking` tí ń dí lọ́tọ̀. +Àwọn àtúndásílẹ̀ láti inú barrel àti àwọn dynamic imports jẹ́ fún àyẹ̀wò ìṣàwárí nìkan; wọn kì í dá +àbájáde tí ń díwọ̀ sílẹ̀ láéláé. Àwọn ìyọkúrò tí a ti ṣàyẹ̀wò wà nínú +`config/quality/forgotten-sibling-allowlist.json`. Gbogbo àkọsílẹ̀ gbọ́dọ̀ dárúkọ consumer àti candidate +test, ṣàlàyé ìdí pàtó, kí ó sì sopọ̀ mọ́ GitHub issue tàbí pull request. Àwọn àkọsílẹ̀ tí kò tọ́ máa ń +kọ̀ láìfàyè gba nǹkan. Àwọn ìyọkúrò kò lè fi candidate test tí a ti parẹ́ pamọ́ tàbí diff tí ó ṣàfikún `.skip`/`.todo`; +dídín agbára assertion kù àti àwọn ìbòjú mìíràn ṣì jẹ́ ojúṣe gate +`check:test-masking` tí ń díwọ̀ lọ́tọ̀. ### Iṣẹ́: `lint` -Ó máa ń ṣiṣẹ́ lórí gbogbo PR sí `main`. Ó máa ń dènà merge bí ó bá kùnà. - -| Script (`npm run ...`) | Ohun tí ó ń fìdí rẹ̀ múlẹ̀ | Ṣé ó ń dí merge? | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -| `check:node-runtime` | Ẹ̀yà Node.js wà láàárín ìwọ̀n tí a ṣe àtìlẹ́yìn fún | Bẹ́ẹ̀ni | -| `check:cycles` | Àwọn ìgbéwọlé aláyíká — gbogbo àwọn module `src/` + `open-sse/` | Bẹ́ẹ̀ni | -| `check:route-validation:t06` | Àwọn schema Zod wà lórí gbogbo route (ìlànà Tier 6) | Bẹ́ẹ̀ni | -| `check:any-budget:t11` | Iye `@ts-expect-error // any` kò kọjá budget (catraca Tier 11) | Bẹ́ẹ̀ni | -| `check:provider-consistency` | Gbogbo olùpèsè inú `providers.ts` ní àkọsílẹ̀ tó bá a mu nínú `providerRegistry.ts` (àti ní ìdàkejì, láàárín allowlist) | Bẹ́ẹ̀ni | -| `check:model-lifecycle` | Àwọn tábìlì ìdarí mẹ́ta tí a ń tọ́jú lọ́wọ́ dúró ní ìbámu pẹ̀lú àwòrán ìgbésí-ayé tí a ti fi sínú ibi ìpamọ́ (#11503): `FITNESS_TABLE` (`taskFitness.ts`) kò fún id tí a ti dáwọ́ dúró, tí `REGISTRY` lè darí sí, ní àmì; gbogbo ibi-afẹ́ `BUILT_IN_ALIASES` wà nínú `REGISTRY`, wọn kò sì sí nínú àwòrán àwọn id tí a ti dáwọ́ dúró; gbogbo id tí a ti dáwọ́ dúró tí ó ṣì wà nínú `REGISTRY` ni a darí sí ibòmíràn tàbí tí a ṣe àkójọ rẹ̀ nínú `allowedRetiredInCatalog`; kò sì sí orísun tàbí ibi-afẹ́ `DEFAULT_DEGRADATION_MAP` kankan tó hàn gẹ́gẹ́ bí èyí tí a ti dáwọ́ dúró nínú àwòrán náà. Èyí kò fi ẹ̀rí múlẹ̀ pé upstream tó ń ṣiṣẹ́ ń pèsè model kan lọ́wọ́lọ́wọ́. Aisinipo — ó ń fi wé `config/quality/model-lifecycle.json`, tí a ń sọ di tuntun lọ́wọ́ pẹ̀lú `npm run quality:refresh-model-lifecycle` (nẹ́tíwọ́ọ̀kì; a kò so mọ́ CI). `allowedRetiredInCatalog` jẹ́ ratchet ìdínkù díẹ̀díẹ̀: fi àkọsílẹ̀ kan kún un nìkan tí issue ìtọ́pasẹ̀ bá wà. | Bẹ́ẹ̀ni | -| `check:fetch-targets` | Gbogbo `fetch("/api/...")` nínú `src/` ẹ̀gbẹ́ client ń yanjú sí `route.ts` gidi kan | Bẹ́ẹ̀ni | -| `check:deps` | Gbogbo deps tí a lè fi `npm install` sí láti inú gbogbo `package.json` inú repo wà nínú `dependency-allowlist.json`; a máa sàmì sí àwọn package tuntun tí a kò pin mọ́ version tàbí tí a slopsquat | Bẹ́ẹ̀ni | -| `audit:deps` | `npm audit` (root + electron) — kò sí ìkìlọ̀ ewu gíga/pàtàkì (ó fara pọ̀ mọ́ osv `check:vuln-ratchet`; wo Àkójọ Iṣẹ́ Ìṣètò Ìdáláre) | Bẹ́ẹ̀ni | -| `check:lockfile` | ìdúróṣinṣin `package-lock.json` — registry https, hash ìdúróṣinṣin, kò sí ìkọjá host | Bẹ́ẹ̀ni | -| `check:licenses` | Àtòjọ ìyọ̀ǹda ìwé-àṣẹ SPDX fún àwọn dependency production | Bẹ́ẹ̀ni | -| `check:tracked-artifacts` | Kò gbọdọ̀ sí àwọn artifact build / àwọn symlink `node_modules` tí a commit (ó tún ń ṣiṣẹ́ nínú husky pre-commit; a mọ̀ọ́mọ̀ jẹ́ kí pre-push fẹ́ẹ́rẹ́ — #6716) | Bẹ́ẹ̀ni | -| `check:ai-attribution` | Kò gbọdọ̀ sí trailer AI/bot `Co-Authored-By` tàbí footer ìṣẹ̀dá AI nínú àwọn commit PR, àkọlé tàbí body — Òfin Líle #16 (nínú loop fast-gates `quality.yml` fún PR→`release/**` — ó ń ka event payload, kò sì ṣe ohunkóhun níta àwọn PR — àti ìgbésẹ̀ PR-nìkan nínú lint `ci.yml` fún PR→`main`; pẹ̀lú hook husky `commit-msg`; a gba àwọn alábàáṣèkọ̀wé ènìyàn láàyè; #14436) | -| `check:vitest-exclusions` | Gbogbo exclusion Vitest gbọdọ̀ dárúkọ issue ìtọ́pasẹ̀ kan, kí ó sì wà nínú `config/quality/vitest-exclusions.json` (#13204) | Bẹ́ẹ̀ni | -| `check:file-size` | Kò sí fáìlì source kankan tí ó gbọdọ̀ kọjá òpin fún extension kọ̀ọ̀kan (ratchet: àwọn fáìlì ńlá tí a ti dì mọ́lẹ̀ nínú àtòjọ `frozen`) | Bẹ́ẹ̀ni | -| `check:error-helper` | Àwọn response error nínú executors/handlers máa ń lo `buildErrorBody()` / `sanitizeErrorMessage()` (Òfin Líle #12) | Bẹ́ẹ̀ni | -| `check:migration-numbering` | Àwọn fáìlì Migration SQL ní nọ́ńbà tó tẹ̀lé ara wọn, láìsí àlàfo tàbí nọ́ńbà àtúnṣe | Bẹ́ẹ̀ni | -| `check:public-creds` | Kò sí OAuth `client_id`/`client_secret` tàbí àwọn kọ́kọ́rọ́ Firebase Web tí a kọ ní tààrà níta `publicCreds.ts` (Òfin Líle #11) | Bẹ́ẹ̀ni | -| `check:db-rules` | Kò sí SQL àìṣe-láàárín níta àwọn module `src/lib/db/`; kò sí àwọn barrel-import láti `localDb.ts` (Àwọn Òfin Líle #2/#5) | Bẹ́ẹ̀ni | -| `check:known-symbols` | Àwọn executor provider, àwọn ìlànà routing, àti àwọn translator tí a forúkọsílẹ̀ sínú àwọn tábìlì dispatch wọn bá àwọn fáìlì tó wà lórí disiki mu — kò sí àwọn àmì tí kò ní olùtọ́jú tàbí tí a kò kéde | Bẹ́ẹ̀ni | -| `check:route-guard-membership` | Gbogbo route tó ń dá process ọmọ sílẹ̀ ni a pín sí ẹ̀ka nípasẹ̀ `isLocalOnlyPath()` (Àwọn Òfin Líle #15/#17) | Bẹ́ẹ̀ni | -| `check:test-discovery` | Gbogbo fáìlì `*.test.ts` / `*.spec.ts` inú repo ni ó kéré tán test runner kan ń kó jọ (ratchet: àkójọ orphan inú `test-discovery-baseline.json` lè dín kù nìkan) | Bẹ́ẹ̀ni | -| `check:agent-skills-sync` | Àwọn artifact agent-skills tí a ṣe jáde bá katalọ́ọ̀gù orísun wọn mu (kò sí ìyapa) | -| `check:provider-asset-provenance` | Àwọn àmì ilé-iṣẹ́/dúkìá olùpèsè ní àkọsílẹ̀ ìpilẹ̀ṣẹ̀ tí a ti ṣàkọsílẹ̀ | -| `lint:json` | Àwọn fáìlì àtúnṣe JSON ṣe parse, wọ́n sì tẹ̀lé àwọn òfin lint repo náà | -| `typecheck:core` | Ìṣàkójọpọ̀ TypeScript láìsí àṣìṣe (àwọn ìkìlọ̀ ìmọ̀ràn nìkan) | Bẹ́ẹ̀ ni | -| `typecheck:noimplicit:core` | `noImplicitAny` tó muna — fún ọjọ́ iwájú; ọ̀pọ̀ ibi ìpè tó ti wà tẹ́lẹ̀ ṣì nílò àwọn àlàyé irú | **Ìmọ̀ràn** (`continue-on-error: true`) | -| `check:dashboard-typecheck` | `tsc` tí a fi ààlà sí `src/app/(dashboard)/**` (#7033) — àtòjọ ìyọ̀ǹda fáìlì 27 tí a ti yan fún `typecheck:core` kò ní TSX dashboard kankan, àti pé `next build` kì í ṣe àyẹ̀wò irú rẹ̀ pẹ̀lú (`next.config.mjs` ṣètò `ignoreBuildErrors: true`), nítorí náà àwọn ìpadàsẹ́yìn ìdánimọ̀ aláìní-olùtọ́jú níbẹ̀ (#6625/#6909) kò hàn sí CI. Ó ń ṣe ìfiwéra pẹ̀lú ìpìlẹ̀ iye àṣìṣe tí a ti dì fún fáìlì kọ̀ọ̀kan/kóòdù TS kọ̀ọ̀kan (`config/quality/dashboard-typecheck-baseline.json`, àpẹẹrẹ ìfipámú ohun tí ó ti gbó kan náà bí `check:known-symbols`) — àwọn àṣìṣe TUNTUN tó kọjá iye ìpìlẹ̀ nìkan ni yóò mú kí ẹnubodè náà kùnà; dín ìpìlẹ̀ náà kù pẹ̀lú `--update` nígbà tí a bá tún àṣìṣe tó ti wà tẹ́lẹ̀ ṣe. | Bẹ́ẹ̀ ni | +Ó máa ń ṣiṣẹ́ lórí gbogbo PR sí `main`. Ó máa ń dí ìdàpọ̀ tí ó bá kùnà. + +| Script (`npm run ...`) | Ohun tí ó ń fìdí rẹ̀ múlẹ̀ | Ìdíwọ̀ | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | +| `check:node-runtime` | Ẹ̀dà Node.js wà láàárín ààlà tí a ṣe àtìlẹ́yìn fún | Bẹ́ẹ̀ ni | +| `check:cycles` | Àwọn import oníyípo jákèjádò gbogbo `src/` + `open-sse/` (ó dá lórí AST, a sì ti yanjú `paths` ti tsconfig). Bare = ìmọ̀ràn, ó máa ń ṣe àtòjọ àwọn cycle náà. `check:cycles:ratchet` (èyí tí CI ń ṣiṣẹ́) máa ń díwọ̀ nígbà tí iye náà bá kọjá òrùlé `metrics.cycles` nínú `quality-baseline.json` — lọ́wọ́lọ́wọ́ 14, `direction: down`, nítorí náà ó lè dín kù nìkan (#15159 G-01/G-02) | Bẹ́ẹ̀ ni (ratchet) | +| `check:route-validation:t06` | Àwọn schema Zod wà lórí gbogbo route (ìlànà Tier 6) | Bẹ́ẹ̀ ni | +| `check:any-budget:t11` | Iye `@ts-expect-error // any` kò kọjá budget (Tier 11 catraca) | Bẹ́ẹ̀ ni | +| `check:provider-consistency` | Gbogbo olupèsè inú `providers.ts` ní àkọsílẹ̀ tó bá a mu nínú `providerRegistry.ts` (àti ní òdìkejì, láàárín allowlist) | Bẹ́ẹ̀ ni | +| `check:model-lifecycle` | Àwọn tábìlì ìtọ́sọ́nà mẹ́ta tí a ń tọ́jú pẹ̀lú ọwọ́ dúró ní ìbámu pẹ̀lú àwòrán lifecycle tí a ti fi sínú ibi ìpamọ́ (#11503): `FITNESS_TABLE` (`taskFitness.ts`) kò fún retired id kankan tí `REGISTRY` lè darí ní score; gbogbo target `BUILT_IN_ALIASES` wà nínú `REGISTRY`, wọn kò sì sí nínú àwòrán retired-id; gbogbo retired id tí ó ṣì wà nínú `REGISTRY` ni a forward tàbí tí a ṣe àkójọ rẹ̀ sínú `allowedRetiredInCatalog`; bẹ́ẹ̀ sì ni kò sí source tàbí target `DEFAULT_DEGRADATION_MAP` kankan tó hàn gẹ́gẹ́ bí retired nínú àwòrán náà. Èyí kò fi hàn pé upstream tó ń ṣiṣẹ́ ń pèsè model kan lọ́wọ́lọ́wọ́. Offline — ó ń fi wé `config/quality/model-lifecycle.json`, tí a ń sọ di tuntun pẹ̀lú ọwọ́ nípasẹ̀ `npm run quality:refresh-model-lifecycle` (network; a kò so mọ́ CI). `allowedRetiredInCatalog` jẹ́ burn-down ratchet: fi àkọsílẹ̀ kún un kìkì pẹ̀lú tracking issue. | Bẹ́ẹ̀ ni | +| `check:fetch-targets` | Gbogbo `fetch("/api/...")` nínú `src/` ti ẹ̀gbẹ́ client ń tọ́ka sí `route.ts` gidi kan | Bẹ́ẹ̀ ni | +| `check:deps` | Gbogbo deps tí `npm install` lè fi sórí ẹ̀rọ káàkiri gbogbo `package.json` inú repo wà nínú `dependency-allowlist.json`; a máa samì sí àwọn package tuntun tí a kò pin sí version pàtó tàbí tí a slopsquat | Bẹ́ẹ̀ ni | +| `audit:deps` | `npm audit` (root + electron) — kò sí ìkìlọ̀ high/critical (ó fara pọ̀ mọ́ osv `check:vuln-ratchet`; wo Rationalization Backlog) | Bẹ́ẹ̀ ni | +| `check:lockfile` | Ìdúróṣinṣin `package-lock.json` — https registry, integrity hashes, kò sí host overrides | Bẹ́ẹ̀ ni | +| `check:licenses` | Àtòjọ ìyọ̀nda àwọn ìwé-àṣẹ SPDX fún àwọn ìgbẹ́kẹ̀lé production | Bẹ́ẹ̀ni | +| `check:tracked-artifacts` | Kò sí àwọn artifact build / àwọn symlink `node_modules` tí a commit (ó tún ń ṣiṣẹ́ nínú husky pre-commit; pre-push jẹ́ fífẹ́ẹ́rẹ́ pẹ̀lú ìmọ̀ọ́mọ̀ — #6716) | Bẹ́ẹ̀ni | +| `check:ai-attribution` | Kò sí trailer `Co-Authored-By` ti AI/bot tàbí footer ìpilẹ̀ṣẹ̀ AI nínú àwọn commit PR, àkọlé tàbí ara — Òfin Líle #16 (nínú loop fast-gates `quality.yml` fún PR→`release/**` — ó ń ka payload event, kò sì ṣe ohunkóhun nígbà tí kì í ṣe PR — àti ìgbésẹ̀ PR-nìkan nínú lint `ci.yml` fún PR→`main`; àti hook husky `commit-msg`; àwọn olùkọ̀wé alábàáṣiṣẹ́ ènìyàn jẹ́ ìyọ̀nda; #14436) | +| `check:vitest-exclusions` | Gbogbo ìyọkúrò Vitest gbọ́dọ̀ sọ issue ìtọ́pasẹ̀ kan, kí ó sì fara hàn nínú `config/quality/vitest-exclusions.json` (#13204) | Bẹ́ẹ̀ni | +| `check:file-size` | Kò sí fáìlì source kankan tó kọjá òpin fún extension kọ̀ọ̀kan (ratchet: àwọn fáìlì ńlá tí a ti di mọ́ inú àtòjọ `frozen`) | Bẹ́ẹ̀ni | +| `check:error-helper` | Àwọn response àṣìṣe nínú executors/handlers máa ń lo `buildErrorBody()` / `sanitizeErrorMessage()` (Òfin Líle #12) | Bẹ́ẹ̀ni | +| `check:migration-numbering` | Àwọn fáìlì Migration SQL ní nọ́ńbà tó tẹ̀ lé ara wọn, kò sí àlàfo tàbí nọ́ńbà àdáwòkọ | Bẹ́ẹ̀ ni | +| `check:public-creds` | Kò sí OAuth `client_id`/`client_secret` tàbí àwọn kọ́kọ́rọ́ Firebase Web tí a kọ ní tààrà níta `publicCreds.ts` (Òfin Líle #11) | Bẹ́ẹ̀ ni | +| `check:db-rules` | Kò sí raw SQL níta àwọn module `src/lib/db/`; kò sí barrel-import láti `localDb.ts` (Àwọn Òfin Líle #2/#5) | Bẹ́ẹ̀ ni | +| `check:known-symbols` | Àwọn executor provider, àwọn ọgbọ́n routing, àti àwọn translator tí a forúkọsílẹ̀ sínú àwọn tábìlì dispatch wọn bá àwọn fáìlì tó wà lórí disk mu — kò sí symbol tí kò ní ìbáṣepọ̀ tàbí tí a kò kéde | Bẹ́ẹ̀ ni | +| `check:route-guard-membership` | Gbogbo route tó dá child process sílẹ̀ ni a pín sí ẹ̀ka pẹ̀lú `isLocalOnlyPath()` (Àwọn Òfin Líle #15/#17) | Bẹ́ẹ̀ ni | +| `check:test-discovery` | Gbogbo fáìlì `*.test.ts` / `*.spec.ts` inú repo ni ó kéré tán test runner kan ń kó jọ (ratchet: àkójọ orphan inú `test-discovery-baseline.json` lè dín kù nìkan) | Bẹ́ẹ̀ ni | +| `check:agent-skills-sync` | Àwọn artifact agent-skills tí a ṣẹ̀dá bá katalogi orísun wọn mu (kò sí ìyapa) | +| `check:provider-asset-provenance` | Àwọn àmì/asset olùpèsè ní àkọsílẹ̀ provenance tí a ti ṣàkọsílẹ̀ | +| `lint:json` | Àwọn fáìlì config JSON lè ṣe parse, wọ́n sì tẹ̀lé àwọn òfin lint repo náà | +| `typecheck:core` | Ìṣàkójọ TypeScript láìsí àṣìṣe (ìkìlọ̀ ìmọ̀ràn nìkan) | Bẹ́ẹ̀ ni | +| `typecheck:noimplicit:core` | `noImplicitAny` tó muna — èyí ń wo ọjọ́ iwájú; ọ̀pọ̀ àwọn ibi ìpè tó ti wà tẹ́lẹ̀ ṣì nílò àwọn àlàyé irú | **Ìmọ̀ràn** (`continue-on-error: true`) | +| `check:dashboard-typecheck` | `tsc` tí a fi ààlà sí `src/app/(dashboard)/**` (#7033) — àkójọ ìyọ̀ǹda fáìlì 27 tí a ti yan fún `typecheck:core` kò ní TSX dashboard kankan, `next build` kò sì ṣe àyẹ̀wò irú rẹ̀ rárá (`next.config.mjs` ṣètò `ignoreBuildErrors: true`), nítorí náà àwọn ìpadàsẹ́yìn identifier aláìní olùtọ́jú níbẹ̀ (#6625/#6909) kò hàn sí CI. Ó ń fi àwọn ìyàtọ̀ wé ìpìlẹ̀ iye tó di dídì fún fáìlì kọ̀ọ̀kan/kóòdù TS kọ̀ọ̀kan (`config/quality/dashboard-typecheck-baseline.json`, àpẹẹrẹ ìmúṣẹ́ ohun tó ti pẹ́ kan náà bí `check:known-symbols`) — àwọn àṣìṣe TUNTUN tí ó kọjá iye tó wà nínú ìpìlẹ̀ nìkan ló máa mú kí ẹnubodè náà kùnà; dín iye náà kù pẹ̀lú `--update` nígbà tí a bá ṣàtúnṣe àṣìṣe tó ti wà tẹ́lẹ̀. | Bẹ́ẹ̀ ni | ### Iṣẹ́: `quality-gate` -Ó máa ń ṣiṣẹ́ lẹ́yìn `test-coverage`. Ó máa ń dí ìdàpọ̀ dúró bí ó bá kùnà. +Ó máa ń ṣiṣẹ́ lẹ́yìn `test-coverage`. Ó máa ń dènà ìdapọ̀ bí ó bá kùnà. -| Script | Ohun tí ó ṣàyẹ̀wò | Dídènà | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | -| `quality:collect` | Ṣẹ̀dá `quality-metrics.json` (iye ìkìlọ̀ ESLint, àpapọ̀ ìdánwò láti inú ìròyìn shard tí a dapọ̀) | Bẹ́ẹ̀ ni (ṣáájú ratchet) | -| `quality:ratchet` | Metric kọ̀ọ̀kan nínú `quality-baseline.json` kò ti sẹ́yìn (àwọn ìkìlọ̀ ESLint ≤ baseline; àpapọ̀ ìdánwò ≥ baseline) | Bẹ́ẹ̀ ni | -| `check:duplication` | Àtúnṣe kóòdù kan náà (jscpd@4) kò kọjá baseline nínú `quality-baseline.json` | Bẹ́ẹ̀ ni | -| `check:complexity` | Ìṣòro cyclomatic ní ìpele fáìlì kò kọjá òpin (ESLint `complexity` àkọ́kọ́ + `max-lines-per-function`) | Bẹ́ẹ̀ ni | -| `check:cognitive-complexity` | Ratchet ìṣòro ìrònú (`eslint-plugin-sonarjs`) — ìṣiṣẹ́ ESLint ọ̀tọ̀; CI ń ṣiṣẹ́ àwọn méjèèjì tí a dapọ̀ gẹ́gẹ́ bí ìgbésẹ̀ `check:complexity-ratchets` kan ṣoṣo | Bẹ́ẹ̀ ni | -| `check:dead-code` | Ratchet àwọn export / fáìlì tí a kò lò (knip) kò sẹ́yìn ní ìfiwéra pẹ̀lú baseline | Bẹ́ẹ̀ ni | -| `check:compression-budget` | Ìnáwó benchmark fún compression — àwọn ìwọ̀n token-savings tó kéré jù lọ fún engine kọ̀ọ̀kan kò gbọ́dọ̀ sẹ́yìn | Bẹ́ẹ̀ ni | -| `check:type-coverage` | Ratchet ìdá ọgọ́rùn-ún tí a fi type sí (`type-coverage`) kò sẹ́yìn; ó bo ọ̀pọ̀ jù lọ nínú `typecheck:noimplicit:core` | Bẹ́ẹ̀ ni | -| `check:codeql-ratchet` | Iye ìkìlọ̀ CodeQL tó ṣí kò sẹ́yìn (ń kà nípasẹ̀ `gh api`; yóò fo pẹ̀lẹ́pẹ̀lẹ́ láìsí token) — fún àkókò ìsọdọ̀tun àti ìmúṣiṣẹ́ ọwọ́: wo "CodeQL ratchet" ní ìsàlẹ̀ | Bẹ́ẹ̀ ni | +| Skripti | Ohun tí ó ń fọwọ́sí | Ìdènà | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `quality:collect` | Ṣẹ̀dá `quality-metrics.json` (iye ìkìlọ̀ ESLint, ìborí láti inú ìròyìn shard tí a ti dapọ̀) | Bẹ́ẹ̀ni (ṣáájú ratchet) | +| `quality:ratchet` | Mẹ́tríìkì kọ̀ọ̀kan nínú `quality-baseline.json` kò ti bàjẹ́ sẹ́yìn (àwọn ìkìlọ̀ ESLint ≤ baseline; ìborí ≥ baseline) | Bẹ́ẹ̀ni | +| `check:duplication` | Ìṣàtúnlò kóòdù (jscpd@4) kò kọjá baseline nínú `quality-baseline.json` | Bẹ́ẹ̀ni | +| `check:complexity` | Ìdíjú cyclomatic ní ìpele fáìlì kò kọjá òpin (ESLint `complexity` àkọ́kọ́ + `max-lines-per-function`) | Bẹ́ẹ̀ni | +| `check:cognitive-complexity` | Ratchet ìdíjú ìrònú (`eslint-plugin-sonarjs`) — ìṣiṣẹ́ ESLint ọ̀tọ̀; CI ń ṣiṣẹ́ àwọn méjèèjì tí a dapọ̀ gẹ́gẹ́ bí ìgbésẹ̀ `check:complexity-ratchets` kan ṣoṣo | Bẹ́ẹ̀ni | +| `check:dead-code` | Ratchet àwọn export / fáìlì tí a kò lò (knip) kò bàjẹ́ sẹ́yìn ní ìfiwéra pẹ̀lú baseline | Bẹ́ẹ̀ni | +| `check:compression-budget` | Ìnáwó àdánwò ìfisúnkí — àwọn ìpele kékeré jù fún ìfipamọ́ token fún engine kọ̀ọ̀kan kò gbọ́dọ̀ bàjẹ́ sẹ́yìn | Bẹ́ẹ̀ni | +| `check:type-coverage` | Ratchet ìdá ọgọ́rùn-ún tí a fi type sí (`type-coverage`) kò bàjẹ́ sẹ́yìn; ó bo ọ̀pọ̀ jù lọ ohun tí `typecheck:noimplicit:core` ń ṣe | Bẹ́ẹ̀ni | +| `check:codeql-ratchet` | Iye àwọn ìkìlọ̀ CodeQL tó ṣí kò bàjẹ́ sẹ́yìn (ń kà nípasẹ̀ `gh api`; yóò fo ní àìléwu láìsí token) — fún bí a ṣe ń sọ ọ́ dọ̀tun àti ìmúnṣiṣẹ́ afọwọ́ṣe: wo "CodeQL ratchet" ní ìsàlẹ̀ | Bẹ́ẹ̀ni | ### Iṣẹ́: `quality-extended` Gbogbo iṣẹ́ náà jẹ́ ìmọ̀ràn (`continue-on-error: true`). Àwọn ratchet tó dá lórí npm ń ṣiṣẹ́ -gan-an; àwọn scanner òde ń fi ara wọn sílẹ̀ nípasẹ̀ `gh release download`, wọ́n sì ń fo fúnra wọn (exit 0) -nígbà tí binary kan kò bá sí síbẹ̀. - -| Script | Ohun tí ó ṣàyẹ̀wò | Dídènà | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | -| `check:circular-deps` | Kò sí àwọn dependency tó ń yípo (dpdm) | **Ìmọ̀ràn** | -| `check:bundle-size` | Ìwọ̀n bundle kò kọjá òpin | **Ìmọ̀ràn** | -| `check:secrets` | Ṣíṣàyẹ̀wò àṣírí (gitleaks) — yóò fo bí binary kò bá sí | **Ìmọ̀ràn** | -| `check:vuln-ratchet` | Àwọn àìlera dependency (osv-scanner) kò sẹ́yìn — yóò fo bí binary kò bá sí | **Ìmọ̀ràn** | -| `check:workflows` | Lint workflow (actionlint + zizmor); scanner tí kò sí/tó bàjẹ́, ìròyìn tí kò péye tàbí baseline ratchet tí kò sí yóò kuna gẹ́gẹ́ bí INCOMPLETE. Àwọn àbájáde tó péye ń tẹ̀lé ìlànà strict/advisory/ratchet tí a yàn | Ìṣiṣẹ́ pọn dandan; ratchet zizmor ń dí CI | -| `check:openapi-breaking` | Àwọn àyípadà tó ń fọ́ àdéhùn API gbogbogbò (`openapi.yaml`) ní ìfiwéra pẹ̀lú ẹ̀ka ìpìlẹ̀ (oasdiff) — ń ṣẹ̀dá `openapiBreaking=N`; yóò fo bí oasdiff kò bá sí tàbí tí a kò lè tú base spec | **Ìmọ̀ràn** | +gan-an; àwọn scanner òde ń fi sórí ẹrọ nípasẹ̀ `gh release download`, wọ́n sì máa ń fo fúnra wọn (exit 0) +nígbà tí binary kò bá sí síbẹ̀. + +| Skripti | Ohun tí ó ń fọwọ́sí | Ìdènà | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| `check:circular-deps` | Kò sí àwọn dependency tó ń yí ká (dpdm) | **Ìmọ̀ràn** | +| `check:bundle-size` | Ìwọ̀n bundle kò kọjá òpin | **Ìmọ̀ràn** | +| `check:secrets` | Ṣíṣàyẹ̀wò ohun àṣírí (gitleaks) — yóò fo bí binary kò bá sí | **Ìmọ̀ràn** | +| `check:vuln-ratchet` | Àwọn àìlera dependency (osv-scanner) kò bàjẹ́ sẹ́yìn — yóò fo bí binary kò bá sí | **Ìmọ̀ràn** | +| `check:workflows` | Lint workflow (actionlint + zizmor); àwọn scanner tí kò sí/tó bàjẹ́, àwọn ìròyìn tí kò tọ́ tàbí baseline ratchet tí kò sí yóò kùnà gẹ́gẹ́ bí INCOMPLETE. Àwọn àbájáde tó tọ́ ń tẹ̀lé ìlànà strict/advisory/ratchet tí a yàn | Ìṣiṣẹ́ pọndandan; ratchet zizmor jẹ́ ìdènà ní CI | +| `check:openapi-breaking` | Àwọn àyípadà tó ń ba àdéhùn API gbogbogbò (`openapi.yaml`) jẹ́ ní ìfiwéra pẹ̀lú ẹ̀ka ìpìlẹ̀ (oasdiff) — ń ṣẹ̀dá `openapiBreaking=N`; yóò fo bí oasdiff kò bá sí tàbí tí spec ìpìlẹ̀ kò bá ṣeé yanjú | **Ìmọ̀ràn** | ### Iṣẹ́: `docs-sync-strict` -Ó ń ṣiṣẹ́ lórí gbogbo PR sí `main`. Ó ń dí ìdapọ̀ tí ìkùnà bá ṣẹlẹ̀. - -| Skripti | Ohun tí ó ń fìdí rẹ̀ múlẹ̀ | Dídènà | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | -| `check:docs-all` | Ẹnubodè-àpapọ̀ tí ń ṣiṣẹ́ àwọn ẹnubodè-kékeré mẹ́fà tó wà nísàlẹ̀ lọ́nà tẹ̀lé-tẹ̀lé | Bẹ́ẹ̀ ni | -| ↳ `check:docs-sync` | Ìbámu ẹ̀yà CHANGELOG / OpenAPI / llm.txt | Bẹ́ẹ̀ ni | -| ↳ `check:docs-counts` | Àwọn iye nínú àkọsílẹ̀ (iye olùpèsè, iye ìṣíkiri, àti bẹ́ẹ̀ bẹ́ẹ̀ lọ) wà láàárín fèrèsé ratchet ti àwọn iye gidi | Bẹ́ẹ̀ ni | -| ↳ `check:env-doc-sync` | Gbogbo env var inú `.env.example` ni a ṣàkọsílẹ̀ sínú tábìlì ìwé-àkọsílẹ̀, àti ní ọ̀nà ìdàkejì náà | Bẹ́ẹ̀ ni | -| ↳ `check:deprecated-versions` | Kò sí ọ̀rọ̀ ẹ̀yà tí a ti dáwọ́ dúró nínú ìwé-àkọsílẹ̀ | Bẹ́ẹ̀ ni | -| ↳ `check:doc-links` | Àwọn ọ̀nà asopọ̀ markdown inú ìwé-àkọsílẹ̀ tọ́ka sí àwọn fáìlì gidi (fọ́ọ̀mù `[text]`/`(path)`) | Bẹ́ẹ̀ ni | -| ↳ `check:fabricated-docs` | Àwọn route, env var, àṣẹ CLI, orúkọ hook, àti ọ̀nà fáìlì tí a tọ́ka sí nínú ìwé-àkọsílẹ̀ wà nínú ibi-kóòdù. Ẹnubodè líle nípasẹ̀ `--strict`; ìkùnà-rọrùn láìsí àsìá náà. | Bẹ́ẹ̀ ni (nípasẹ̀ `--strict` nínú CI) | -| `check:cli-i18n` | Àwọn ọ̀rọ̀ àṣẹ CLI wà nínú gbogbo fáìlì locale i18n | Bẹ́ẹ̀ ni | -| `check:openapi-coverage` | Àlàyé OpenAPI bo ó kéré tán ìpele ratchet kan ti àwọn route gidi | Bẹ́ẹ̀ ni | -| `check:openapi-security-tiers` | Àwọn àkọlé ìpele ààbò nínú `openapi.yaml` bá àwọn ìsọ̀rí `routeGuard.ts` mu | **Ìmọ̀ràn** | -| `check:openapi-routes` | Gbogbo path nínú `openapi.yaml` tọ́ka sí `route.ts` gidi kan (ìdènà ìrò-káàkiri) | Bẹ́ẹ̀ ni | -| `check:docs-symbols` | Gbogbo ìtọ́kasí `/api/...` nínú `docs/**/*.md` tọ́ka sí `route.ts` gidi kan (ìdènà ìrò-káàkiri) | Bẹ́ẹ̀ ni | -| `i18n translation drift` | Àwọn key tí a kò tíì túmọ̀ nínú àwọn fáìlì locale i18n — ìkìlọ̀ nìkan | **Ìmọ̀ràn** | +Ó ń ṣiṣẹ́ lórí gbogbo PR sí `main`. Ó ń dí ìdapọ̀ mọ́ bí ó bá kùnà. + +| Skripti | Ohun tí ó ń fìdí rẹ̀ múlẹ̀ | Dídènà | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| `check:docs-all` | Ẹnubodè àpapọ̀ tó ń ṣiṣẹ́ àwọn ẹnubodè kékeré mẹ́fà tó wà nísàlẹ̀ ní ọ̀kọ̀ọ̀kan | Bẹ́ẹ̀ni | +| ↳ `check:docs-sync` | Ìbámu ẹ̀yà CHANGELOG / OpenAPI / llm.txt | Bẹ́ẹ̀ni | +| ↳ `check:docs-counts` | Àwọn iye nínú àlàyé (iye àwọn olùpèsè, iye àwọn ìṣíkiri, àti bẹ́ẹ̀ bẹ́ẹ̀ lọ) wà láàárín fèrèsé ratchet ti àwọn iye gidi | Bẹ́ẹ̀ni | +| ↳ `check:env-doc-sync` | Gbogbo env var inú `.env.example` ni a ṣàkọsílẹ̀ sínú tábìlì docs kan, bẹ́ẹ̀ sì ni ní òdìkejì | Bẹ́ẹ̀ni | +| ↳ `check:deprecated-versions` | Kò sí àwọn ọ̀rọ̀ ẹ̀yà tí a ti dáwọ́ lílò dúró nínú docs | Bẹ́ẹ̀ni | +| ↳ `check:doc-links` | Àwọn ìjápọ̀ markdown inú docs ń tọ́ka sí àwọn fáìlì gidi (fọ́ọ̀mù `[text]`/`(path)`) | Bẹ́ẹ̀ni | +| ↳ `check:fabricated-docs` | Àwọn ipa-ọ̀nà, env vars, àwọn àṣẹ CLI, àwọn orúkọ hook, àti àwọn ipa-ọ̀nà fáìlì tí a tọ́ka sí nínú docs wà nínú codebase. Ẹnubodè líle nípasẹ̀ `--strict`; ìkùnà rírọ̀ láìsí flag. | Bẹ́ẹ̀ni (nípasẹ̀ `--strict` nínú CI) | +| `check:cli-i18n` | Àwọn ọ̀rọ̀ àṣẹ CLI wà nínú gbogbo fáìlì locale i18n | Bẹ́ẹ̀ni | +| `check:openapi-coverage` | Sipesifikesonu OpenAPI bo ó kéré tán ìpele ìsàlẹ̀ ratchet ti àwọn ipa-ọ̀nà gidi | Bẹ́ẹ̀ni | +| `check:openapi-security-tiers` | Àwọn àkọsílẹ̀ ìpele ààbò nínú `openapi.yaml` bá àwọn ìpínsísọ̀rí `routeGuard.ts` mu | **Ìmọ̀ràn** | +| `check:openapi-routes` | Gbogbo path inú `openapi.yaml` ń tọ́ka sí `route.ts` gidi kan (ààbò lòdì sí ìpilẹ̀ṣẹ̀ ohun tí kò sí) | Bẹ́ẹ̀ni | +| `check:docs-symbols` | Gbogbo ìtọ́kasí `/api/...` nínú `docs/**/*.md` ń tọ́ka sí `route.ts` gidi kan (ààbò lòdì sí ìpilẹ̀ṣẹ̀ ohun tí kò sí) | Bẹ́ẹ̀ni | +| `i18n translation drift` | Àwọn key tí a kò túmọ̀ nínú àwọn fáìlì locale i18n — ìkìlọ̀ nìkan | **Ìmọ̀ràn** | ### Iṣẹ́: `i18n-ui-coverage` -| Skripti | Ohun tí ó ń fìdí rẹ̀ múlẹ̀ | Dídènà | -| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check-ui-keys-coverage` (nínú laini) | Ìborí key i18n UI jẹ́ ≥ 65% | Bẹ́ẹ̀ ni | -| `check-ui-value-drift` (nínú laini) | Àtúnkọ iye Gẹ̀ẹ́sì kan kò fi ìtumọ̀ àtijọ́ kankan sílẹ̀ | Bẹ́ẹ̀ ni | -| `check-new-key-coverage` (nínú laini) | Key Gẹ̀ẹ́sì **tuntun** kan ni a túmọ̀ ní gbogbo locale — a kọ àmì `__MISSING__:` | Bẹ́ẹ̀ ni | -| `check-translation-ratio` | Ìpín ìtumọ̀ gidi fún locale kọ̀ọ̀kan (èyí tó jọ Gẹ̀ẹ́sì / àfipò / àwọn ewé tó sọnù lẹ́yìn allowlist) kò gbọdọ̀ kọjá `config/quality/i18n-translation-baseline.json` + ààyè àfikún | **Ìmọ̀ràn** | +| Skripti | Ohun tí ó ń fìdí rẹ̀ múlẹ̀ | Dídènà | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check-ui-keys-coverage` (inline) | Ìborí key i18n UI jẹ́ ≥ 65% | Bẹ́ẹ̀ni | +| `check-ui-value-drift` (inline) | Àtúnkọ **value** èdè Gẹ̀ẹ́sì kò fi ìtumọ̀ àtijọ́ kankan sílẹ̀ | Bẹ́ẹ̀ni | +| `check-new-key-coverage` (inline) | A túmọ̀ key èdè Gẹ̀ẹ́sì **tuntun** nínú gbogbo locale — a kọ àmì `__MISSING__:` | Bẹ́ẹ̀ni | +| `check-translation-ratio` | Ìpín ìtumọ̀ gidi fún locale kọ̀ọ̀kan (èyí tó jọ èdè Gẹ̀ẹ́sì / placeholder / àwọn ewé tí ó sọnù lẹ́yìn allowlist) kò gbọ́dọ̀ kọjá `config/quality/i18n-translation-baseline.json` + àyè àfikún | **Ìmọ̀ràn** | Ó nílò `fetch-depth: 0` — ẹnubodè value-drift ń ṣe diff `en.json` pẹ̀lú merge base. -#### `check-ui-value-drift` — ẹnubodè ìtumọ̀-àtijọ́ +#### `check-ui-value-drift` — ẹnubodè ìtumọ̀ àtijọ́ -Ó ń mú àṣìṣe i18n kan ṣoṣo tí àwọn ẹnubodè yòókù kò lè rí nípa ìṣètò: a tún iye Gẹ̀ẹ́sì kan kọ -ṣùgbọ́n àwọn ìtumọ̀ tí a dá láti inú Gẹ̀ẹ́sì _tẹ́lẹ̀_ ṣì wà, nítorí náà -àwọn olumulo tí kì í lo Gẹ̀ẹ́sì ń bá a lọ láti ka ọ̀rọ̀ tó dáni lójú ṣùgbọ́n tí ó ti di àṣìṣe báyìí. +Ó ń ṣàwárí àṣìṣe i18n kan ṣoṣo tí àwọn ẹnubodè mìíràn kò lè rí nípa ìṣètò: a tún value èdè Gẹ̀ẹ́sì +kọ, ṣùgbọ́n àwọn ìtumọ̀ tí a dá láti inú èdè Gẹ̀ẹ́sì _ìṣáájú_ ṣì wà, nítorí náà +àwọn aṣàmúlò tí kì í ṣe ti èdè Gẹ̀ẹ́sì ń bá a lọ láti ka ọ̀rọ̀ tó dájú lójú ṣùgbọ́n tó ti di àṣìṣe. -Èyí ti wọ ẹ̀yà ìmúlò gidi rí. A tún `oauthModal.googleOAuthWarning` kọ nígbà tí olùrànlọ́wọ́ -wọlé Antigravity dé (#5203); **39 nínú locale 43** pa ọ̀rọ̀ tó ń sọ fún àwọn olùṣàkóso láti "ṣẹ̀dà -URL kíkún kí wọ́n sì lẹ̀ ẹ́ sísàlẹ̀" mọ́ — ìṣàn iṣẹ́ tí kò lè parí fún olùpèsè yẹn. A kò +Èyí ti dé sí ìmújáde ní ti gidi. A tún `oauthModal.googleOAuthWarning` kọ nígbà tí olùrànlọ́wọ́ ìwọlé Antigravity +dé (#5203); **39 nínú 43 locale** pa ọ̀rọ̀ tó sọ fún àwọn olùdarí láti “da URL kíkún náà kọ, kí wọ́n sì lẹ̀ ẹ́ sí ìsàlẹ̀” mọ́ — ìṣàn kan tí kò lè parí fún olùpèsè yẹn. A kò ṣàkíyèsí rẹ̀ títí di #8463 nítorí pé: -- `sync-ui-keys` ń fi àwọn key tí **kò sí** nìkan kún un, kì í ṣe àwọn tó ti **di àtijọ́**; -- `check-ui-keys-coverage` ń ka _wíwà_ key, nítorí náà ìtumọ̀ àtijọ́ kan gba àmì bí èyí tí a ti bo; -- `check-translation-drift` ń tọpinpin àwọn àwòrán-dídára ìwé-àkọsílẹ̀ `docs/i18n//**.md` — - kò ka `src/i18n/messages/*.json` láéláé. Ó ti ń dí iṣẹ́ dúró nínú job `docs-sync-strict` láti ìṣàmúdọ̀tun - 2026-09: ṣàtúnṣe core doc kan → `npm run i18n:run -- --files=` (ní ìpele abala, kò ná púpọ̀). +- `sync-ui-keys` máa ń ṣe àfikún àwọn key tí wọ́n **kò sí** nìkan, kì í ṣe àwọn tó ti **di àtijọ́**; +- `check-ui-keys-coverage` ń ka _wíwà_ key, nítorí náà ìtumọ̀ àtijọ́ máa ń gba àmì bí èyí tí a bo; +- `check-translation-drift` ń tọpa àwọn ẹ̀dà àkọsílẹ̀ `docs/i18n//**.md` — + kò ka `src/i18n/messages/*.json` rárá. Ó ti ń dí iṣẹ́ dúró nínú job `docs-sync-strict` láti ìmúdọ́gba 2026-09: ṣàtúnṣe doc pàtàkì kan → `npm run i18n:run -- --files=` (ní ìpele abala, kò ná púpọ̀). -**Ó mọ ìyàtọ̀ (diff), kò gbára lé baseline.** Ó ń fi `en.json` ní merge base wé +**Ó mọ ìyàtọ̀, kò gbára lé baseline.** Ó ń fi `en.json` ní merge base wé working tree; fún gbogbo key tí iye Gẹ̀ẹ́sì rẹ̀ yí padà, locale èyíkéyìí tí ó ṣì ní -ìtumọ̀ tí a kò fọwọ́ kàn ti di àtijọ́. Èyí mọ̀ọ́mọ̀ **ń dá gbèsè tó ti wà tẹ́lẹ̀ dúró** — diff -kò lè fi Gẹ̀ẹ́sì àtijọ́ tí ìtumọ̀ tó ti wà fún ìgbà pípẹ́ ti wá hàn, nítorí náà gate náà ń ṣàyẹ̀wò -ohun tí àyípadà lọ́wọ́lọ́wọ́ kàn nìkan. Yíyan ọ̀nà mìíràn (baseline hash kan fún key kọ̀ọ̀kan) yóò gba -fáìlì tí a ṣe jáde tó tó ~600 KB, ìlọ́po mẹ́ta baseline tó tóbi jù lọ tó wà, tí yóò sì máa yí padà lórí gbogbo PR i18n. +ìtumọ̀ tí a kò fọwọ́ kàn ti di àtijọ́. Èyí mọ̀ọ́mọ̀ **di gbèsè tó ti wà tẹ́lẹ̀ mú** — diff +kò lè fi Gẹ̀ẹ́sì àtijọ́ tí ìtumọ̀ kan tó ti wà fún ìgbà pípẹ́ ti wá hàn, nítorí náà gate náà +ń ṣe ìdájọ́ ohun tí àyípadà lọ́wọ́lọ́wọ́ bá fọwọ́ kàn nìkan. Àṣàyàn kejì (baseline hash fún key kọ̀ọ̀kan) yóò gba +fáìlì tí a ṣẹ̀dá tó tó ~600 KB, ìlọ́po mẹ́ta baseline tó tóbi jù lọ tó wà, tí yóò sì máa yí padà lórí gbogbo i18n PR. -Ọ̀nà méjì ló wà láti mú un ṣẹ: +Ọ̀nà méjì láti mú un ṣẹ: -1. ṣe ìmúdójúìwọ̀n àwọn ìtumọ̀ tí ọ̀ràn náà kàn, tàbí -2. ṣètò wọn sí `__MISSING__:` — runtime náà yóò wá fi Gẹ̀ẹ́sì tí a ti ṣàtúnṣe hàn +1. ṣe àfikún sí àwọn ìtumọ̀ tí ọ̀rọ̀ náà kàn, tàbí +2. ṣètò wọn sí `__MISSING__:` — runtime yóò wá fi Gẹ̀ẹ́sì tí a ti ṣàtúnṣe náà hàn (`src/i18n/request.ts::deepMergeFallback`, #7258), key náà yóò sì wọ ìlà fún ìtumọ̀. -Tí **ìtumọ̀ ọ̀rọ̀** string náà bá yí padà, ó dára kí o **sọ key náà lórúkọ tuntun**: key tuntun kò lè jogún -ìtumọ̀ àtijọ́. Àpẹẹrẹ yẹn ni #8463 lò. +Tí **ìtumọ̀ inú** string náà bá yí padà, ó dára jù láti **sọ key náà lórúkọ tuntun**: key tuntun kò lè jogún +ìtumọ̀ àtijọ́. Èyí ni àpẹẹrẹ tí #8463 lò. ```bash -npm run i18n:check-value-drift # líle (èyí tí CI ń ṣiṣẹ́) +npm run i18n:check-value-drift # tó muna (èyí tí CI ń ṣiṣẹ́) npm run i18n:check-value-drift:warn # ìròyìn nìkan BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -Ó jáde pẹ̀lú 0 àti `SKIP reason=base-unresolved` nígbà tí a kò bá lè ka catalog ìpìlẹ̀ (shallow +Ó máa jáde pẹ̀lú 0 àti `SKIP reason=base-unresolved` nígbà tí kò bá ṣeé ka catalog ìpìlẹ̀ (shallow clone tí kò ní base ref), gẹ́gẹ́ bí `check-openapi-breaking`. ### Iṣẹ́: `i18n` -Matrix ìfọwọ́sí i18n kíkún (iṣẹ́ kan fún locale kọ̀ọ̀kan). Gbogbo iṣẹ́ náà jẹ́ ìmọ̀ràn. +Matrix ìfọwọ́sí i18n kíkún (iṣẹ́ kan fún locale kọ̀ọ̀kan). Gbogbo iṣẹ́ náà jẹ́ àmọ̀ràn. | Script | Ohun tí ó ń fọwọ́sí | Dídènà | | ------------------------------- | ---------------------------- | ------------------------------------------------------ | -| `validate_translation.py quick` | Pípé ìtumọ̀ fún locale kọ̀ọ̀kan | **Ìmọ̀ràn** (`continue-on-error: true` lórí gbogbo iṣẹ́) | +| `validate_translation.py quick` | Pípé ìtumọ̀ fún locale kọ̀ọ̀kan | **Àmọ̀ràn** (`continue-on-error: true` lórí gbogbo iṣẹ́) | ### Iṣẹ́: `pr-test-policy` Ó ń ṣiṣẹ́ lórí pull requests nìkan. -| Script | Ohun tí ó ń fọwọ́sí | Dídènà | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | -| `check:pr-test-policy` | Àwọn PR tó yí production code padà nínú `src/`, `open-sse/`, `electron/`, tàbí `bin/` gbọ́dọ̀ fi test kún un tàbí ṣe ìmúdójúìwọ̀n rẹ̀ (Òfin Líle #8) | Bẹ́ẹ̀ ni | -| `check:test-masking` | Àwọn fáìlì test tí a yí padà kò dín àpapọ̀ iye assert kù, wọn kò sì ṣàfikún tautology `assert.ok(true)` | Bẹ́ẹ̀ ni | -| `check:pr-evidence` | Ara PR tọ́ka sí ẹ̀rí test/VPS fún àyípadà náà (ó sọ Òfin Líle #18 di aládàáṣiṣẹ́ nípa lílo grep lórí ọ̀rọ̀ PR — ó rọrùn láti bàjẹ́, wo Backlog) | Bẹ́ẹ̀ ni | +| Script | Ohun tí ó ń fọwọ́sí | Dídènà | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | +| `check:pr-test-policy` | Àwọn PR tí ó yí production code padà nínú `src/`, `open-sse/`, `electron/`, tàbí `bin/` gbọ́dọ̀ ní àwọn test tàbí kí wọ́n ṣe àfikún sí wọn (Òfin Líle #8) | Bẹ́ẹ̀ ni | +| `check:test-masking` | Àwọn fáìlì test tí a yí padà kò dín àpapọ̀ iye assert kù, wọn kò sì ṣàfikún àwọn tautology `assert.ok(true)` | Bẹ́ẹ̀ ni | +| `check:pr-evidence` | Ara PR tọ́ka sí ẹ̀rí test/VPS fún àyípadà náà (ó sọ Òfin Líle #18 di aládàáṣiṣẹ́ nípa lílo grep lórí ọ̀rọ̀ PR — ó rọrùn láti bàjẹ́, wo Backlog) | Bẹ́ẹ̀ ni | ### Iṣẹ́: `test-vitest` -Ó ń ṣiṣẹ́ lẹ́yìn `build`. Ó ń dènà merge tí ó bá kùnà. +Ó ń ṣiṣẹ́ lẹ́yìn `build`. Ó máa ń dí merge tí ó bá kùnà. -| Suite | Ohun tí ó ń fọwọ́sí | Dídènà | -| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -| `test:vitest` | MCP server (110 tools), autoCombo, cache — vitest runner | Bẹ́ẹ̀ ni | -| `test:vitest:ui` | Àwọn test component UI — vitest runner | **Ó ń dènà** — a yọ àwọn ìkùnà tó ti wà tẹ́lẹ̀ kúrò ní pàtó nínú `vitest.config.ts`; àwọn ìkùnà tuntun yóò mú kí iṣẹ́ náà kùnà | +| Suite | Ohun tí ó ń fọwọ́sí | Dídènà | +| ---------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `test:vitest` | MCP server (irinṣẹ́ 110), autoCombo, cache — vitest runner | Bẹ́ẹ̀ ni | +| `test:vitest:ui` | Àwọn test component UI — vitest runner | **Ó ń díni** — àwọn ìkùnà tó ti wà tẹ́lẹ̀ ni a yọ kúrò ní pàtó nínú `vitest.config.ts`; àwọn ìkùnà tuntun yóò mú kí iṣẹ́ náà kùnà | -### Àwọn workflow alẹ́ (tí a ṣètò sí àkókò, ìmọ̀ràn) +### Àwọn workflow alẹ́ (tí a ṣètò sí àkókò, àmọ̀ràn) -Àwọn wọ̀nyí ń ṣiṣẹ́ lórí ìṣètò cron (àti `workflow_dispatch`), wọn kì í ṣiṣẹ́ lórí PR rárá. Gbogbo wọn jẹ́ ìmọ̀ràn. +Àwọn wọ̀nyí ń ṣiṣẹ́ lórí ìṣètò cron (àti `workflow_dispatch`), wọn kì í ṣiṣẹ́ lórí PR. Gbogbo wọn jẹ́ àmọ̀ràn. -| Workflow | Ohun tí ó ń fọwọ́sí | Dídènà | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | -| `nightly-property` | Àwọn property test fast-check pẹ̀lú seed aláìlétò + iye ìṣiṣẹ́ tó ga | **Ìmọ̀ràn** | -| `nightly-resilience` | gate ìdàgbàsókè heap, chaos fault-injection, load/soak k6 | **Ìmọ̀ràn** | -| `nightly-llm-security` | guard injection promptfoo (block mode) + àwọn probe garak (a máa foju kọ wọ́n láìsí provider secret) | **Ìmọ̀ràn** | -| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) sí OmniRoute tó ń ṣiṣẹ́ ní lílo `docs/openapi.yaml` — ó ń ṣí àwọn ìrúfin spec / àwọn 500 tí a kò bójú tó payá (Ìpele 8 B.4) | **Ìmọ̀ràn** | -| `nightly-mutation` | Àmì mutation-testing Stryker lórí fast unit lane — àwọn mutant tó yè ń ṣí àwọn assert aláìlágbára payá | **Ìmọ̀ràn** | -| `nightly-compat` | Matrix ìbámu engine Node káàkiri àwọn range `engines.node` tí a ṣe àtìlẹ́yìn fún | **Ìmọ̀ràn** | +| Workflow | Ohun tí ó ń fọwọ́sí | Dídènà | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `nightly-property` | Àwọn property test fast-check pẹ̀lú seed aláìlẹ́sẹ̀sẹ̀ + iye run tó ga | **Àmọ̀ràn** | +| `nightly-resilience` | gate ìdàgbàsókè heap, chaos fault-injection, load/soak k6 | **Àmọ̀ràn** | +| `nightly-llm-security` | olùṣọ́ injection promptfoo (block mode) + àwọn probe garak (a máa fò ó tí kò bá sí provider secret) | **Àmọ̀ràn** | +| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) lòdì sí OmniRoute tó ń ṣiṣẹ́ nípa lílo `docs/openapi.yaml` — ń fi àwọn ìrúfin spec / àwọn 500 tí a kò bójú tó hàn (Fase 8 B.4) | **Àmọ̀ràn** | +| `nightly-mutation` | Iye mutation-testing Stryker lórí fast unit lane — àwọn mutant tó yè ń fi àwọn assert aláìlágbára hàn | **Àmọ̀ràn** | +| `nightly-compat` | Matrix ìbámu engine Node káàkiri àwọn range `engines.node` tí a ṣe àtìlẹ́yìn fún | **Àmọ̀ràn** | --- @@ -536,43 +534,44 @@ kò gbé e wọlé láìfọwọ́ṣe (ìwọ ni yóò yan afárá MCP↔LSP t --- -## Àtòjọ Àwọn Iṣẹ́ Ìṣètòpadà (àgbéyẹ̀wò ROI — Ìpele 9 Ìgbì 3) +## Àtòjọ Àwọn Iṣẹ́ Ìṣàlàyé (àtúnyẹ̀wò ROI — Ìpele 9 Ìgbì 3) -A ṣe àfiwé àtòjọ yìí pẹ̀lú `ci.yml` ní 2026-06-17 (ẹ̀dà tó ṣáájú kò ní +A ṣe ìbámu àkójọ yìí pẹ̀lú `ci.yml` ní 2026-06-17 (ẹ̀yà àtẹ̀yìnwá kò ní `audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`, `check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`, -`check:codeql-ratchet`, `check:pr-evidence`). Àgbéyẹ̀wò ROI lórí àkójọpọ̀ tí a ti ṣe àtúnṣe rẹ̀ -ṣàfihàn àwọn olùdíje ìṣètòpadà wọ̀nyí. **Àwọn ìsopọ̀ náà jẹ́ àwọn àyípadà CI -aláìdíjú; àwọn ìyípadà/iparẹ́ jẹ́ ìpinnu ìlànà tí a fi sílẹ̀ fún olùṣiṣẹ́.** Kò sí ohun tó wà nísàlẹ̀ +`check:codeql-ratchet`, `check:pr-evidence`). Àtúnyẹ̀wò ROI lórí àkójọ tí a mú bá ara wọn mu +ṣàwárí àwọn olùdíje ìṣàlàyé wọ̀nyí. **Àwọn ìṣọ̀kan náà jẹ́ àwọn àyípadà CI +aláìlọ́gbọ́n-ọ̀nà; àwọn ìyípadà/iparẹ́ jẹ́ àwọn ìpinnu ìlànà tí a fi sílẹ̀ fún olùṣàkóso.** Kò sí ohunkóhun nísàlẹ̀ tí a ti lò síbẹ̀. -**Ohun mìíràn tí a kò ṣàkọsílẹ̀ lókè** (ìmọ̀ràn nìkan, àmì tí kò lágbára): iṣẹ́ `docs-lint` -(markdownlint + Vale, gbogbo iṣẹ́ náà ní `continue-on-error`) àti àwọn ìṣàn-iṣẹ́ ọlọ́jẹ̀ àdáṣo +**Ohun mìíràn tí a kò ṣàkọsílẹ̀ lókè** (ìmọ̀ràn, àmì aláìlágbára): iṣẹ́ `docs-lint` +(markdownlint + Vale, gbogbo iṣẹ́ náà jẹ́ `continue-on-error`) àti àwọn ìṣàn-iṣẹ́ aṣàyẹ̀wò olóminira `semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` wà nínú -`quality-baseline.json` ṣùgbọ́n a kò so ó mọ́ ratchet ìdènà kan nínú `ci.yml` — metric náà -kò ní ohun tó ń lò ó lọ́wọ́lọ́wọ́. - -### Ìsopọ̀ / yíyọ àtúnṣe kúrò (aláìdíjú, ewu kéré) - -A fìdí olùdíje kọ̀ọ̀kan múlẹ̀ pẹ̀lú ipò gate tó ń ṣiṣẹ́ ní 2026-06-17 (gbọ́kànlé-ṣùgbọ́n-ṣàyẹ̀wò); -ọ̀pọ̀ ìsopọ̀ tó dà bíi pé ó “hàn gbangba” ló fi hàn pé gbèsè wà níbẹ̀, wọ́n sì **kì í ṣe** àròpò tààrà tó mọ́. - -- **`check:docs-sync` ń ṣiṣẹ́ lẹ́ẹ̀mejì** — ní àdáṣo nínú iṣẹ́ `lint` àti lẹ́ẹ̀kan sí i nínú `check:docs-all` (`docs-sync-strict`) àti husky pre-commit hook. ✅ **A TI ṢE É** — a yọ ìpè `lint` àdáṣo náà kúrò. -- **Ṣíṣàyẹ̀wò CVE** — ❌ **KÌ Í ṣe ìsopọ̀ tó mọ́.** `audit:deps` yóò kọ̀ láìfọ̀rọ̀ sábẹ́ ahọ́n sọ lórí CVE èyíkéyìí tó jẹ́ high/critical; `check:vuln-ratchet` (osv) yóò kùnà nìkan nígbà tí _ìfàsẹ́yìn_ bá wà ní ìfiwéra pẹ̀lú baseline (lọ́wọ́lọ́wọ́, MODERATE 1). Ìtumọ̀ wọn yàtọ̀ — pípa `audit:deps` rẹ́ yóò mú kí gate high/critical pípé náà sọnù. Jẹ́ kí àwọn méjèèjì dúró. -- **Ṣíṣàwárí cycle** — ❌ **KÌ Í ṣe ìsopọ̀ tó mọ́.** `check:circular-deps` (dpdm) ròyìn **cycle 91** (ìdí nìyẹn tí ó fi jẹ́ ìmọ̀ràn nìkan); a kò lè gbé e ga sí ìdènà láìkọ́kọ́ yanjú wọn, ó sì ní ààlà tó gbòòrò ju `check:cycles` aláwọ̀ ewé tí a fara balẹ̀ ṣètò lọ. Jẹ́ kí `check:cycles` máa dí ohun tí kò bá yẹ; yíyanjú àwọn cycle dpdm 91 náà jẹ́ iṣẹ́ àtòjọ tirẹ̀. -- **Ìdíjú** — ✅ **A TI ṢE É** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ìrìn ESLint kan ṣoṣo, ó ń ka gẹ́gẹ́ bí ruleId kí àwọn baseline cyclomatic+max-lines àti cognitive lè máa dúró lọ́tọ̀; `check:complexity` / `check:cognitive-complexity` kọ̀ọ̀kan ṣì wà fún `--update` agbègbè. -- **Ìdènà àròsọ èké `/api`** — ✅ **A TI ṢE É** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): àkójọ FS kan ṣoṣo ti `src/app/api`, openapi-routes + docs-symbols ṣì ń ròyìn lọ́tọ̀; àwọn ti ọ̀kọ̀ọ̀kan ṣì wà fún ìṣiṣẹ́ agbègbè. -- **`check:node-runtime` ń ṣiṣẹ́ nínú iṣẹ́ 11** — ⚠️ **ROI kéré.** Ọ̀kọ̀ọ̀kan jẹ́ runner ọ̀tọ̀, àyẹ̀wò náà sì jẹ́ <1s; àkókò àpapọ̀ tí a lè fipamọ́ jẹ́ ~10s, ní ìfiwéra pẹ̀lú pípàdánù olùṣọ́ olówó-pọ́ọ́kú fún iṣẹ́ kọ̀ọ̀kan. Kò yẹ kí a fa ìyípadà ńlá nítorí rẹ̀. -- **`typecheck:noimplicit:core` lórí lint CI** — ✅ **a yọ ọ́ kúrò nínú iṣẹ́ lint** (ó jẹ́ `continue-on-error` ìmọ̀ràn tẹ́lẹ̀); ààlà type tó ń dí ohun tí kò bá yẹ ni `typecheck:core` + `check:type-coverage`. A fi script agbègbè náà sílẹ̀. - -### Yípadà / pinnu (ìlànà olùṣiṣẹ́) - -- `check:openapi-security-tiers` (ìmọ̀ràn nìkan) — ❌ **A KÒ lè yí i padà ní mímọ́.** Ó parí pẹ̀lú 0 ṣùgbọ́n ó kìlọ̀ pé ọ̀pọ̀ route `traffic-inspector` lábẹ́ `LOCAL_ONLY_API_PREFIXES` kò ní annotation `x-loopback-only: true`. Fífipá mú un ṣiṣẹ́ béèrè pé kí a kọ́kọ́ fi àwọn annotation wọ̀nyẹn kún `openapi.yaml`. -- `typecheck:noimplicit:core` (ìmọ̀ràn nìkan) — ratchet `check:type-coverage` tó ń dí ohun tí kò bá yẹ ti bo ọ̀pọ̀ jù lọ nínú rẹ̀. Yí i padà sí ratchet tàbí yọ pass `tsc` kejì tó ṣe àtúnṣe iṣẹ́ kan náà kúrò. -- `test:vitest:ui` (tó ti ń **dí ohun tí kò bá yẹ** báyìí) — àwọn ìkùnà tó ti wà tẹ́lẹ̀ ni a yọ kúrò ní kedere nínú `vitest.config.ts` pẹ̀lú àwọn àsọyé ìtọ́pinpin `// #8618`; àwọn ìkùnà tuntun yóò mú kí iṣẹ́ náà kùnà. -- `check:secrets` (gitleaks, ratchet tó ń dí ohun tí kò bá yẹ, tí a dì mọ́ false-positive 3 tí a ti ṣàkọsílẹ̀) — fi àwọn 3 náà sí allowlist láti dé 0, tàbí sọ ọ́ kalẹ̀ sí ìmọ̀ràn nìkan. Ó ní àdàkọ iṣẹ́ pẹ̀lú secret-scanning abinibi GitHub + `check:public-creds`. -- `check:pr-evidence` (ó ń dí ohun tí kò bá yẹ, ó ń fi grep wá ọ̀rọ̀ inú PR-body) — ewu false-positive ga; pípa á rẹ́ yóò sọ ìfipá-mú Ìlànà Líle #18 di aláìlágbára, nítorí náà èyí jẹ́ ìpinnu ìlànà gidi. -- `semgrep` (àdáṣo ìmọ̀ràn nìkan) — ó ní àdàkọ iṣẹ́ pẹ̀lú CodeQL fún àwọn ẹbí OWASP; so baseline rẹ̀ mọ́ ratchet tàbí yọ ọ́ kúrò. +`quality-baseline.json` ṣùgbọ́n a kò so mọ́ ratchet ìdènà nínú `ci.yml` — òṣùwọ̀n náà +kò ní ohun tí ó ń lò ó lọ́wọ́lọ́wọ́. + +### Ṣọ̀kan / yọ àtúnsí kúrò (aláìlọ́gbọ́n-ọ̀nà, ewu kéré) + +A fìdí olùdíje kọ̀ọ̀kan múlẹ̀ pẹ̀lú ipò ẹnu-ọ̀nà tó ń ṣiṣẹ́ ní 2026-06-17 (gbọ́kànlé-ṣùgbọ́n-ṣàyẹ̀wò); +ọ̀pọ̀ àwọn ìṣọ̀kan tí ó dàbí ẹni pé ó “hàn gbangba” fi hàn pé wọ́n ń fi gbèsè pamọ́, wọn kò sì jẹ́ +àwọn àfidípò tààrà **rárá**. + +- **`check:docs-sync` ń ṣiṣẹ́ lẹ́ẹ̀mejì** — gẹ́gẹ́ bí ohun olóminira nínú iṣẹ́ `lint` àti lẹ́ẹ̀kan sí i nínú `check:docs-all` (`docs-sync-strict`) àti ìkọ̀ husky pre-commit. ✅ **TI PARÍ** — a yọ ìpè `lint` olóminira náà kúrò. +- **Àyẹ̀wò CVE** — ❌ **KÌ Í ṢE ìṣọ̀kan tó mọ́ tónítóní.** `audit:deps` máa ń kùnà pátápátá lórí CVE èyíkéyìí tó ga/pàtàkì; `check:vuln-ratchet` (osv) máa ń kùnà nìkan lórí _ìfàsẹ́yìn_ ní ìfiwéra pẹ̀lú baseline (lọ́wọ́lọ́wọ́ 1 MODERATE). Ìtumọ̀ wọn yàtọ̀ — píparẹ́ `audit:deps` yóò mú kí ẹnu-ọ̀nà pípé fún ga/pàtàkì pàdánù. Ẹ jẹ́ kí àwọn méjèèjì wà. +- **Ìṣàwárí cycle** — ✅ **TI PARÍ** (#15159 G-01/G-02). Ọ̀rọ̀ àtijọ́ níbí pe `check:cycles` ní ẹnu-ọ̀nà “aláwọ̀ ewé, tí a ṣàyàn pẹ̀lú ìṣọ́ra” ó sì ṣàlàyé pé kí ó máa dí iṣẹ́ lọ́nà nítorí `check:circular-deps` (dpdm) ròyìn cycle 91. Àwọ̀ ewé yẹn jẹ́ **àwọ̀ ewé èké**: `check:cycles` ṣàyẹ̀wò àwọn subdirectory 5 (fáìlì 450), ó bá `import|export … from` aimi nìkan mu, ó sì sọ gbogbo specifier `@/` àti `@omniroute/open-sse/` nù, nítorí náà kò lè rí àwọn cycle dynamic-import + alias tí ó pọ̀ jù nínú repo. A ti tún un ṣe: ẹnu-ọ̀nà náà ń rìn ká `src` + `open-sse` báyìí (fáìlì 5023), ó ń kó àwọn specifier láti TypeScript AST (nítorí náà `import("…")` wúlò ṣùgbọ́n `typeof import("…")` ní ipò type kò wúlò), ó sì ń yanjú `paths` tsconfig. Ó rí cycle **14**, kì í ṣe 0. Nítorí pé a kò lè tún àwọn cycle 14 tó ti wà tẹ́lẹ̀ ṣe nínú PR ẹnu-ọ̀nà kan, `check:cycles` ti di **ratchet** báyìí (`--ratchet`, òrùlé `metrics.cycles.value = 14` nínú `quality-baseline.json`, `direction: down`) — ó ń dí _ìfàsẹ́yìn_ èyíkéyìí, iye náà sì lè dín kù nìkan. CI ń ṣiṣẹ́ `npm run check:cycles:ratchet`. Ìdínkù díẹ̀díẹ̀ máa lọ pẹ̀lú **A-01**. `check:circular-deps` (dpdm) ṣì jẹ́ ìmọ̀ràn gẹ́gẹ́ bí èrò kejì tó gbòòrò. +- **Ìdíjú** — ✅ **TI PARÍ** (`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`): ìrìn ESLint kan ṣoṣo, ó ń ka nípasẹ̀ ruleId kí baseline cyclomatic+max-lines àti cognitive lè dúró lọ́tọ̀ọ̀tọ̀; `check:complexity` / `check:cognitive-complexity` kọ̀ọ̀kan ṣì wà fún `--update` agbègbè. +- **Ìdènà ìròsọ `/api`** — ✅ **TI PARÍ** (`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`): àkójọ FS kan ṣoṣo ti `src/app/api`, openapi-routes + docs-symbols ṣì ń ròyìn lọ́tọ̀ọ̀tọ̀; àwọn ti ẹnìkọ̀ọ̀kan ṣì wà fún ṣíṣiṣẹ́ agbègbè. +- **`check:node-runtime` ń ṣiṣẹ́ nínú iṣẹ́ 11** — ⚠️ **ROI kéré.** Ọ̀kọ̀ọ̀kan jẹ́ runner lọ́tọ̀, àyẹ̀wò náà sì jẹ́ <1s; àpapọ̀ àkókò tí a lè fipamọ́ ~10s, ní ìfiwéra pẹ̀lú pípàdánù olùṣọ́ olówó-pọ́ọ́kú fún iṣẹ́ kọ̀ọ̀kan. Kò tọ́ ìrúkèrúdò náà. +- **`typecheck:noimplicit:core` lórí lint CI** — ✅ **a ti yọ ọ́ kúrò nínú iṣẹ́ lint** (ó jẹ́ `continue-on-error` ìmọ̀ràn); àyè type tó ń dí iṣẹ́ ni `typecheck:core` + `check:type-coverage`. A dá script agbègbè sílẹ̀. + +### Yípadà / pinnu (ìlànà olùṣàkóso) + +- `check:openapi-security-tiers` (ìmọ̀ràn) — ❌ **KÒ ṢEÉ yípadà ní mímọ́.** Ó jáde pẹ̀lú 0 ṣùgbọ́n ó kìlọ̀ pé ọ̀pọ̀ àwọn route `traffic-inspector` lábẹ́ `LOCAL_ONLY_API_PREFIXES` kò ní àkọsílẹ̀ `x-loopback-only: true`. Fífi agbára mú un nílò fífi àwọn àkọsílẹ̀ yẹn kún `openapi.yaml` lákọ̀ọ́kọ́. +- `typecheck:noimplicit:core` (ìmọ̀ràn) — ratchet `check:type-coverage` tó ń dí iṣẹ́ ti bo ọ́ púpọ̀. Yí i padà sí ratchet tàbí pa ìrìn `tsc` kejì tó tún ara rẹ̀ ṣe rẹ́. +- `test:vitest:ui` (tó jẹ́ **olùdènà** báyìí) — àwọn ìkùnà tó ti wà tẹ́lẹ̀ ni a yọ kúrò ní pàtó nínú `vitest.config.ts` pẹ̀lú àwọn àlàyé ìtọ́pasẹ̀ `// #8618`; àwọn ìkùnà tuntun máa ń mú kí iṣẹ́ náà kùnà. +- `check:secrets` (gitleaks, ratchet olùdènà tí a dì mọ́ àwọn false-positive 3 tí a ṣàkọsílẹ̀) — fi àwọn 3 náà sí allowlist láti dé 0, tàbí sọ ọ́ kalẹ̀ sí ìmọ̀ràn. Ó ní àgbélébùú pẹ̀lú secret-scanning abinibi GitHub + `check:public-creds`. +- `check:pr-evidence` (olùdènà, ń lo grep lórí ọ̀rọ̀ inú PR-body) — ewu false-positive ga; píparẹ́ rẹ̀ máa sọ ìmúlò Hard Rule #18 di aláìlágbára, nítorí náà èyí jẹ́ ìpinnu ìlànà gidi. +- `semgrep` (olóminira ìmọ̀ràn) — ó ní àgbélébùú pẹ̀lú CodeQL fún àwọn ẹbí OWASP; so baseline rẹ̀ mọ́ ratchet kan tàbí pa á rẹ́. --- diff --git a/docs/i18n/yo/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/yo/docs/compression/COMPRESSION_GUIDE.md index ebf04c87f155..9e76edd54835 100644 --- a/docs/i18n/yo/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/yo/docs/compression/COMPRESSION_GUIDE.md @@ -467,8 +467,7 @@ a ó fi sí i dípò Gẹ̀ẹ́sì. Lórí body tí ó ní `messages`, content bypass kan (`shouldBypassCavemanOutputMode()` nínú `open-sse/services/compression/outputMode.ts`) ń yẹ àwọn ìfiránṣẹ́ mẹ́ta tó kẹ́yìn wò, ó sì ń fò àwọn ara náà kọjá fún gbogbo turn náà nígbà tí wọ́n bá àwọn keyword tó jẹ mọ́ ààbò, -ìgbésẹ̀ tí a kò lè dá padà, ìbéèrè ìṣàlàyé, tàbí ìtòlẹ́sẹẹsẹ tó ṣe pàtàkì mu. Bypass náà ń ṣiṣẹ́ ní ìbámu pẹ̀lú -bí a ṣe ṣètò toggle **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) lórí dashboard. +ìgbésẹ̀ tí a kò lè dá padà, ìbéèrè ìṣàlàyé, tàbí ìtòlẹ́sẹẹsẹ tó ṣe pàtàkì mu. Bypass náà ń ṣiṣẹ́ nígbà tí toggle **Auto-Clarity Bypass** (`cavemanOutputMode.autoClarity`) ṣí wà, èyí ni ààyò àkọ́kọ́; nígbà tí a bá pa á mọ́, àwọn ara tí a yàn náà ń ṣiṣẹ́ lórí àwọn turn yìí. Nígbà tí bypass bá jẹ́ kí turn náà kọjá, `placeSystemInstruction()` (fáìlì kan náà), èyí tí kò dá `messages[0]` tuntun sílẹ̀ láéláé, ń fi búlọ́ọ̀kù náà sí àkọ́kọ́ nínú àwọn wọ̀nyí tí ó bá rí: diff --git a/docs/i18n/yo/docs/reference/ENVIRONMENT.md b/docs/i18n/yo/docs/reference/ENVIRONMENT.md index bed0a6425a63..642bc548b1e9 100644 --- a/docs/i18n/yo/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/yo/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. Àwọn Àṣírí Tí A Nílò -Àwọn wọ̀nyí **gbọ́dọ̀** ti ṣètò kí ìṣiṣẹ́ àkọ́kọ́ tó bẹ̀rẹ̀. Láìsí wọn, ohun èlò náà yóò kọ̀ láti bẹ̀rẹ̀ tàbí yóò ṣiṣẹ́ pẹ̀lú àwọn ètò àìléwu àiyipada. - -| Àyípadà | Ó Dọ̀rọ̀ | Àiyipada | Fáìlì Orísun | Àpèjúwe | -| ---------------------------- | ---------------------- | ---------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **Bẹ́ẹ̀ni** | _(kò sí)_ | `src/lib/auth` | Ó ń fọwọ́sí/ṣàyẹ̀wò gbogbo àwọn kúkì ìpàdé dashboard (JWT). Ṣẹ̀dá rẹ̀ pẹ̀lú `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Bẹ́ẹ̀ni** | _(kò sí)_ | `src/lib/db/apiKeys.ts` | Kọ́kọ́rọ́ ìpamọ́ AES fún àwọn iye kọ́kọ́rọ́ API tí a fipamọ́ sínú SQLite. Ṣẹ̀dá rẹ̀ pẹ̀lú `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Bẹ́ẹ̀ni** | `CHANGEME` | Skripù ìmúrasílẹ̀ | Ó ń ṣètò ọ̀rọ̀ aṣínà àkọ́kọ́ fún dashboard alákòóso (ó bá àiyipada `.env.example` mu — a mọ̀ọ́mọ̀ fi sílẹ̀ láìléwu láti fi dandan mú kí a yí i padà). **Yí i padà kí o tó lò ó fún ìgbà àkọ́kọ́.** Lẹ́yìn ìwọlé, yí i padà nípasẹ̀ Dashboard → Settings → Security. | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **Bẹ́ẹ̀ni** (production) | _(a kò ṣètò)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Àṣírí àjọpín fún afárá WebSocket Codex Responses inú. Ó ń fìdí àwọn ìbéèrè afárá láàárín Electron/browser WS relay àti OmniRoute múlẹ̀. ⚠️ **Ó DỌ̀RỌ̀ ní production — nígbà tí a kò bá ṣètò rẹ̀, gbogbo àwọn ìbéèrè afárá WS ni a ó kọ̀.** Ṣẹ̀dá rẹ̀ pẹ̀lú `openssl rand -base64 32`. | -| `OMNIROUTE_SW_BUILD_ID` | Rárá | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID fífi cache service-worker di tuntun ní kedere fún PWA shell (#11779); òun ni àkọ́kọ́ nínú ọ̀wọ̀ ìyanjú. | -| `SOURCE_VERSION` | Rárá | _(a kò ṣètò)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Èkejì nínú ọ̀wọ̀ náà — àwọn olùkọ́ PaaS (fún àpẹẹrẹ, irú Heroku) ló ń ṣètò rẹ̀ gẹ́gẹ́ bí commit tí a gbé jáde. | -| `NEXT_PUBLIC_SW_BUILD_ID` | Rárá | _(a yọ jáde)_ | `src/shared/components/PwaRegister.tsx` | Iye gbogbogbò ní àkókò build tí client ń lò láti forúkọsílẹ̀ `/sw.js?v=…`; a yọ ọ́ jáde láti inú àwọn méjèèjì tó wà lókè, lẹ́yìn náà git SHA. | -| `OMNIROUTE_PEER_STAMP_TOKEN` | Rárá (aládàáṣiṣẹ́) | _(aládàáṣiṣẹ́ ní ìgbà boot kọ̀ọ̀kan)_ | `src/server/authz/policies/management.ts` | Àṣírí fún process kọ̀ọ̀kan tó ń fi hàn pé àmi peer-IP tí a fọkàn tán wá láti inú HTTP server OmniRoute fúnra rẹ̀ (`scripts/dev/peer-stamp.mjs`). Middleware authz máa ń fọkàn tán pé ìbéèrè jẹ́ ti àdúgbò (ìdènà loopback/LAN fún àwọn route LOCAL_ONLY) kìkì nígbà tí àmi náà bá ní token yìí. A máa ń ṣẹ̀dá rẹ̀ fúnra rẹ̀ ní gbogbo ìgbà boot — fi í sílẹ̀ láìṣètò; ṣètò rẹ̀ sí iye kan pàtó nìkan fún àwọn ìṣètò multi-process tí ó gbọ́dọ̀ pín àmi náà. | -| `OMNIROUTE_TRUSTED_PROXIES` | Rárá | _(a kò ṣètò)_ | `scripts/dev/peer-stamp.mjs` | Àwọn IP tàbí àwọn ààlà CIDR ti àwọn reverse proxy, tí a yà sọ́tọ̀ pẹ̀lú àmì kọ́mà, tí àtòjọ fífàyègba/kíkọ IP lè gbàgbọ́ pé `X-Forwarded-For` / `X-Real-IP` wọn tọ̀nà, ní àfikún sí loopback, àwọn àdírẹ́sì nẹ́tíwọ́ọ̀kì aládàáni àti àwọn edge Cloudflare, èyí tí a máa ń fọkàn tán nígbà gbogbo. A nílò rẹ̀ nìkan fún proxy tó wà lórí àdírẹ́sì gbogbogbò mìíràn; bí kò bá rí bẹ́ẹ̀, àdírẹ́sì proxy fúnra rẹ̀ ni filter yóò ṣàyẹ̀wò. | +Àwọn àṣírí tí a kò ṣètò tí a ti fi àmì **Bẹ́ẹ̀ni** ṣàfihàn ni a máa ń ṣẹ̀dá ní àìní-nìkan nígbà ìṣiṣẹ́ àkọ́kọ́ (`JWT_SECRET`, `API_KEY_SECRET`), a sì máa ń fi pamọ́ sí `server.env` nínú foldè data — ṣètò wọn ní kedere tí foldè yẹn bá jẹ́ ti àìgbájú. Ìyàsọ́tọ̀ ni `OMNIROUTE_WS_BRIDGE_SECRET`: a kò fi pamọ́ sí `server.env` — àwọn ìbẹ̀rẹ̀ server máa ń ṣẹ̀dá iye àìgbájú fún ìbẹ̀rẹ̀ ẹ̀rọ̀ kọ̀ọ̀kan tí afárá tí ó wà nínú ilana yìí nìkan ló lè lò ó, nítorí náà àwọn alábàáṣepọ̀ afárá ìta nilo kí a ṣètò rẹ̀ ní kedere. + +| Àyípadà | Ó Dọ̀rọ̀ | Àiyipada | Fáìlì Orísun | Àpèjúwe | +| ---------------------------- | ---------------------- | ---------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Bẹ́ẹ̀ni** | _(kò sí)_ | `src/lib/auth` | Ó ń fọwọ́sí/ṣàyẹ̀wò gbogbo àwọn kúkì ìpàdé dashboard (JWT). Ṣẹ̀dá rẹ̀ pẹ̀lú `openssl rand -base64 48`. | +| `API_KEY_SECRET` | **Bẹ́ẹ̀ni** | _(kò sí)_ | `src/lib/db/apiKeys.ts` | Kọ́kọ́rọ́ ìpamọ́ AES fún àwọn iye kọ́kọ́rọ́ API tí a fipamọ́ sínú SQLite. Ṣẹ̀dá rẹ̀ pẹ̀lú `openssl rand -hex 32`. | +| `INITIAL_PASSWORD` | Rárá | _(a kò ṣètò)_ | `src/lib/auth/managementPassword.ts` | Ó ń ṣètò ọ̀rọ̀ aṣínà dashboard nígbà ìbẹ̀rẹ̀ ẹ̀rọ̀ nígbà tí a kò bá ti fi ọ̀rọ̀ aṣínà pamọ́; a kò yọ ó lọ́wọ́ lẹ́yìn tí a bá ti fi ọ̀rọ̀ aṣínà pamọ́. Àwọn ìgbedójúde npm àti source máa ń da `.env.example` kọ́gba `.env` lọ, nítorí náà wọ́n bẹ̀rẹ̀ pẹ̀lú placeholder rẹ̀ tí gbogbo ènìyàn mọ̀ `CHANGEME`: yí i padà kí o tó bẹ̀rẹ̀ ẹ̀rọ̀ fún ìgbà àkọ́kọ́, tàbí ṣètò `INITIAL_PASSWORD=` (òfo) nínú `.env` yẹn láti ṣẹ̀dá ọ̀rọ̀ aṣínà nínú onboarding wizard nígbà àbẹ̀wò àkọ́kọ́, gẹ́gẹ́ bí iye tí a kò ṣètò (àìfọwọ́yí àwòrán Docker) ṣe ń ṣe. Yí ọ̀rọ̀ aṣínà tí a fi pamọ́ padà ní Dashboard → Settings → Security, tàbí ṣiṣẹ́ `omniroute reset-password` (`node bin/reset-password.mjs` nínú source checkout) pẹ̀lú kí o ṣètò `DATA_DIR` sí foldè data server. | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Bẹ́ẹ̀ni** (production) | _(a kò ṣètò)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Àṣírí àjọpín fún afárá WebSocket Codex Responses inú. Ó ń fìdí àwọn ìbéèrè afárá láàárín Electron/browser WS relay àti OmniRoute múlẹ̀. ⚠️ **Ó DỌ̀RỌ̀ ní production — nígbà tí a kò bá ṣètò rẹ̀, gbogbo àwọn ìbéèrè afárá WS ni a ó kọ̀.** Ṣẹ̀dá rẹ̀ pẹ̀lú `openssl rand -base64 32`. | +| `OMNIROUTE_SW_BUILD_ID` | Rárá | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | ID fífi cache service-worker di tuntun ní kedere fún PWA shell (#11779); òun ni àkọ́kọ́ nínú ọ̀wọ̀ ìyanjú. | +| `SOURCE_VERSION` | Rárá | _(a kò ṣètò)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Èkejì nínú ọ̀wọ̀ náà — àwọn olùkọ́ PaaS (fún àpẹẹrẹ, irú Heroku) ló ń ṣètò rẹ̀ gẹ́gẹ́ bí commit tí a gbé jáde. | +| `NEXT_PUBLIC_SW_BUILD_ID` | Rárá | _(a yọ jáde)_ | `src/shared/components/PwaRegister.tsx` | Iye gbogbogbò ní àkókò build tí client ń lò láti forúkọsílẹ̀ `/sw.js?v=…`; a yọ ọ́ jáde láti inú àwọn méjèèjì tó wà lókè, lẹ́yìn náà git SHA. | +| `OMNIROUTE_PEER_STAMP_TOKEN` | Rárá (aládàáṣiṣẹ́) | _(aládàáṣiṣẹ́ ní ìgbà boot kọ̀ọ̀kan)_ | `src/server/authz/policies/management.ts` | Àṣírí fún process kọ̀ọ̀kan tó ń fi hàn pé àmi peer-IP tí a fọkàn tán wá láti inú HTTP server OmniRoute fúnra rẹ̀ (`scripts/dev/peer-stamp.mjs`). Middleware authz máa ń fọkàn tán pé ìbéèrè jẹ́ ti àdúgbò (ìdènà loopback/LAN fún àwọn route LOCAL_ONLY) kìkì nígbà tí àmi náà bá ní token yìí. A máa ń ṣẹ̀dá rẹ̀ fúnra rẹ̀ ní gbogbo ìgbà boot — fi í sílẹ̀ láìṣètò; ṣètò rẹ̀ sí iye kan pàtó nìkan fún àwọn ìṣètò multi-process tí ó gbọ́dọ̀ pín àmi náà. | +| `OMNIROUTE_TRUSTED_PROXIES` | Rárá | _(a kò ṣètò)_ | `scripts/dev/peer-stamp.mjs` | Àwọn IP tàbí àwọn ààlà CIDR ti àwọn reverse proxy, tí a yà sọ́tọ̀ pẹ̀lú àmì kọ́mà, tí àtòjọ fífàyègba/kíkọ IP lè gbàgbọ́ pé `X-Forwarded-For` / `X-Real-IP` wọn tọ̀nà, ní àfikún sí loopback, àwọn àdírẹ́sì nẹ́tíwọ́ọ̀kì aládàáni àti àwọn edge Cloudflare, èyí tí a máa ń fọkàn tán nígbà gbogbo. A nílò rẹ̀ nìkan fún proxy tó wà lórí àdírẹ́sì gbogbogbò mìíràn; bí kò bá rí bẹ́ẹ̀, àdírẹ́sì proxy fúnra rẹ̀ ni filter yóò ṣàyẹ̀wò. | ### Àwọn Àṣẹ Ìṣẹ̀dá @@ -215,38 +215,38 @@ OmniRoute ń lo **SQLite** (nípasẹ̀ `better-sqlite3`) fún gbogbo ìtọ́j ## 4. Ààbò & Ìfàṣẹsí -| Àyípadà | Àkọ́kọ́ | Fáìlì Orísun | Àpèjúwe | -| ------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt tí a darapọ̀ mọ́ àwọn àmì ìdánimọ̀ ohun èlò fún ṣíṣe ìtẹ̀wé ìka ẹ̀rọ. Yí i padà fún ìmúṣiṣẹ́ kọ̀ọ̀kan láti rí i dájú pé wọ́n yà sọ́tọ̀. | -| `OMNIROUTE_CLI_SALT` | _(a kò ṣètò = salt aláìlẹ́gbẹ́ fún ìfibọ̀ kọ̀ọ̀kan tí a tọ́jú sí `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC fún ṣíṣẹ̀dá token ìfàṣẹsí CLI abẹ́nú. Ṣíṣètò iye yìí yí gbogbo àwọn token CLI lórí ẹ̀rọ náà padà, ó sì máa ń ní ipò àkọ́kọ́ ju salt tí a tọ́jú lọ. Wo `docs/security/CLI_TOKEN.md`. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Ṣètò àsìá `Secure` lórí àwọn cookie sáà. **Ó gbọ́dọ̀ jẹ́ `true`** nígbà tí a bá ń ṣiṣẹ́ lẹ́yìn HTTPS. | -| `REQUIRE_API_KEY` | `false` | API middleware | Nígbà tí ó bá jẹ́ `true`, gbogbo àwọn ìbéèrè aṣojú `/v1/*` gbọ́dọ̀ ní kọ́kọ́rọ́ API tó fẹsẹ̀ múlẹ̀. **Àsìá yìí kì í dí `GET /v1/models` mọ́**, nítorí ó ń tẹ̀lé ìlànà ìwọlé dasibodu (`requireAuthForModels`) dípò rẹ̀ — nítorí náà, `401` láti `/v1/models` kò túmọ̀ sí pé a dáàbò bo ìṣirò àbájáde. Wo `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Fàyè gba fífi iye kọ́kọ́rọ́ API kíkún hàn nínú ojú-ìlò Dasibodu. A lè ṣètò rẹ̀ láti inú Àwọn Àsìá Ẹ̀yà Dasibodu; ó jẹ́ ewu ààbò lórí àwọn instance tí ọ̀pọ̀ ènìyàn ń lò. | -| `NO_LOG_API_KEY_IDS` | _(òfìfo)_ | `src/lib/compliance/index.ts` | Àwọn ID kọ́kọ́rọ́ API tí a fi àmì kọ́má yà sọ́tọ̀, tí kò ní jẹ́ kí a ṣàkọsílẹ̀ ìbéèrè (ìbámu pẹ̀lú GDPR). | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(a kò ṣètò = àìlópin)_ | `src/shared/utils/apiKeyPolicy.ts` | Ìpín ìbéèrè ojoojúmọ́ àfidípò tí a lò fún àwọn kọ́kọ́rọ́ API tí ìlà `rate_limits` wọn jẹ́ null. Tí a kò bá ṣètò rẹ̀ tàbí tó ṣófo: kò sí ààlà àìfojúrí (#2289, #11017). `0` náà rí bẹ́ẹ̀ (àìlópin). Odidi nǹkan rere N ń mú N/ọjọ́, 5N/ọ̀sẹ̀, 20N/oṣù ṣiṣẹ́. Àwọn iye tí kò ṣófo tí a kọ lọ́nà àìtọ́ máa ń padà sí àwọn àkókò àtijọ́ 1000/ọjọ́, 5000/ọ̀sẹ̀, 20000/oṣù. | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Ìwọ̀n ara ìbéèrè tó pọ̀ jù tí a fàyè gbà. Ó kọ àwọn payload tó kọjá ààlà yìí. | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Àwọn ara ìbéèrè gangan tó dé tàbí kọjá ààlà yìí máa ń gba lease ìgbàwọlé iṣẹ́-àdúgbò tó wúwo ní ẹ̀ẹ̀kan ṣáájú ìtúpalẹ̀ JSON (ọ̀nà BYTE, pẹ̀lú `POST /v1/responses`). Ọ̀nà àsálà àyè-afikún tó ní ìlera kan náà bí [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) fún èyí tó wuwo ní ìgbékalẹ̀; síbẹ̀, `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ṣì fi ààlà sí i kí [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) má bàa padà. | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Òpin líle fún route ìbánisọ̀rọ̀ tí a fipá mú lórí iye byte tí a kà nígbà ìgbàwọlé aláàlà, pẹ̀lú àwọn ìbéèrè tí `Content-Length` wọn kò sí, kò tọ̀nà, tàbí kò sọ òótọ́; èyí tó bá kọjá ààlà yóò gba `413`. | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(a kò ṣètò rẹ̀ — kò sí òpin iye ìbéèrè)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** òpin iye-ÌBÉÈRÈ àtijọ́ yìí máa ń ṣiṣẹ́ nìkan nígbà tí a bá ṣètò rẹ̀ ní tààrà. Tí a kò bá ṣètò rẹ̀ (èyí ni àìyípadà), ìgbàwọlé ìbánisọ̀rọ̀ tó wuwo yóò jẹ́ dídarí dípò bẹ́ẹ̀ nípasẹ̀ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — ìṣúná BYTE tí a ṣe àfàyọ láìfọwọ́yí, tí a gbé ìwọ̀n rẹ̀ ka orí òpin memory gidi ti process náà nínú **process kan** (heap V8 kan). Àwọn `/v1/responses` méjì tó yí ara wọn ká, tí ọ̀kọ̀ọ̀kan ní nǹkan bí token 750k, ń mú kí heap ~12 Gi ṣubú (#7849) — ìkìlọ̀ **ìṣúná-memory** ni, kì í ṣe òpin líle ọjà ti 2. Process tó ní ìlera (heap wà ní ìsàlẹ̀ ratio ìyọkúrò) LÈ gba àwọn `/v1/responses` gígùn mìíràn tí ń ṣiṣẹ́ lẹ́ẹ̀kan náà nípasẹ̀ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Ọ̀pọ̀lọpọ̀ client SSE gígùn (40–50) dá lórí heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, kì í ṣe “ó pọ̀ jù lọ 2”. Gbígbé èyí ga láìronú láti “lo host náà” yóò tún mú #7849 padà. Ṣe ìlọ́po **heap** pẹ̀lú **`DATA_DIR` olómìnira N** (#11024); má ṣe lo `replicas>1` lórí fáìlì SQLite kan láé. | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(a ṣe àfàyọ rẹ̀ láìfọwọ́yí)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** àtúnṣe fún ìṣúná byte ìgbàwọlé tí a ṣe àfàyọ láìfọwọ́yí (25% ti òpin memory V8/cgroup tó há jù, tí a pín nípasẹ̀ ìlọ́po ìgbà díẹ̀ 8x). Àwọn iye tí a ṣe àfàyọ àti tí a sọ ní tààrà ni a dín mọ́ ààrin 8 MiB–2 GiB. Body tó tóbi ju ìṣúná tó ń ṣiṣẹ́ lọ yóò kùnà lẹ́sẹ̀kẹsẹ̀ pẹ̀lú `413 body_exceeds_budget`; ìjàkadì láàárín àwọn body tí iṣẹ́ lè bójú tó lọ́kọ̀ọ̀kan ṣì lè tún gbìyànjú pẹ̀lú `503`. Client SSE gígùn 40–50 tí ń ṣiṣẹ́ lẹ́ẹ̀kan náà dá lórí ìṣúná yìí + heap, kì í ṣe “ó pọ̀ jù lọ 2” líle. Ka `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ní `/api/monitoring/health` kí o tó ṣe àtúnṣe. | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Ratio ìyọkúrò nítorí ìfúnpá heap (`heapUsed / heap_size_limit`) fún ìgbàwọlé tó wuwo ti BYTE àti STRUCTURE (#10183, #10268, #10437). Ìbéèrè wuwo kan tó ń ṣiṣẹ́ lẹ́ẹ̀kan náà tí ó kọjá `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ni a ó yọ kúrò pẹ̀lú `503` tó ṣeé tún gbìyànjú NÌKAN nígbà tí heap náà BÁKAN NÁÀ bá dé ratio yìí tàbí kọjá rẹ̀; lórí heap tó ní ìlera, a ó gbà á nípasẹ̀ healthy-headroom dípò bẹ́ẹ̀. | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (àìyípadà `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Agbára àfikún aláàlà fún ọ̀nà yíyára heap tó ní ìlera (#10437) lórí **mejeeji** STRUCTURE àti BYTE (`admitChatRequest`, pẹ̀lú àwọn body ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Láìsí ààlà yìí, gbogbo ìbéèrè tí iṣẹ́ pọ̀ fún ṣùgbọ́n heap rẹ̀ ní ìlera máa ń ré kọjá ìgbàwọlé láìsí òpin. Nígbà tí iye lease tó ń ṣiṣẹ́ lẹ́ẹ̀kan náà bá ti ṣiṣẹ́ tó báyìí nípasẹ̀ ọ̀nà heap tó ní ìlera, àwọn ìbéèrè mìíràn tí iṣẹ́ pọ̀ fún yóò bọ́ sí ọ̀nà ìdúró-aláàlà/ìyọkúrò KAN NÁÀ tí a ń lò lábẹ́ ìfúnpá heap gidi. `0` máa ń pa ọ̀nà ìrékọjá náà pátápátá. | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Iye message tó ń pín ìbéèrè ìbánisọ̀rọ̀ sí ẹ̀ka tó wuwo, kódà nígbà tí body rẹ̀ bá wà ní ìsàlẹ̀ ààlà byte. | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Iye tool tó ń pín ìbéèrè ìbánisọ̀rọ̀ sí ẹ̀ka tó wuwo, kódà nígbà tí body rẹ̀ bá wà ní ìsàlẹ̀ ààlà byte. | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Ìṣírò token oníṣọ́ra tó dá lórí ìwọ̀n string, tí ó ń pín ìbéèrè sí ẹ̀ka tó wuwo; aṣojú iye owó ìgbàwọlé ni èyí, kì í ṣe tokenization ìdíyelé provider. | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (a ti pa á) | `src/shared/middleware/chatBodyAdmission.ts` | Òpin àṣàyàn fún itan ìbánisọ̀rọ̀ tí a gbọdọ̀ yàn láti lò. A pa á ní àìyípadà: iye message jẹ́ ìlànà deployment, kì í ṣe àbùdá gbogbo ìbéèrè, àti fífi òpin sí i níbí máa ń kọ àwọn ìjíròrò sílẹ̀ pẹ̀lú `413` ìparí kí pipeline compression tó lè jẹ́ kí iṣẹ́ lè bójú tó wọn. Ìdàgbàsókè heap jẹ́ dídín mọ́ ààlà nípasẹ̀ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` àti ìyọkúrò nítorí ìfúnpá heap. Ṣètò iye rere lórí àwọn deployment tí memory wọn ní ààlà, tí wọ́n sì nílò òpin líle; èyí tó bá kọjá ààlà yóò gba `413` tó ní ètò, tó sì ń béèrè compression. | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Òpin líle fún response upstream tí kì í ṣe streaming, tí a fi gbogbo rẹ̀ pamọ́ sínú memory. Tó bá kọjá èyí, a ó fagilé reader upstream, ìbéèrè náà yóò sì kùnà lẹ́sẹ̀kẹsẹ̀ dípò kí string tí kò ní ààlà máa dàgbà títí heap yóò fi tán. | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Iye báìtì lórí nẹ́tíwọ́ọ̀kì tó pọ̀ jù tí a máa darí láti inú àwọn àkọlé ìdáhùn upstream. Nígbà tí iye yìí bá kọjá ààlà, a máa yọ àwọn àkọlé tí ààyò wọn kéré (fún àpẹẹrẹ, `x-codex-*`, `x-oai-request-id`) kúrò kí wọ́n lè dúró láàárín ààlà àkọlé reverse-proxy tí ó wọ́pọ̀. Ṣètò rẹ̀ sí iye tó ga jù láti darí metadata upstream púpọ̀ sí i, pẹ̀lú àbájáde pé ìwọ̀n àkọlé ìdáhùn yóò tóbi sí i. | -| `CORS_ORIGIN` | _(a kò ṣètò)_ | `src/server/cors/origins.ts` | Àtòjọ ìyọ̀ǹda CORS fún origin kan ṣoṣo ti àtijọ́. Fún àwọn ìmúṣiṣẹ́ tuntun, yan `CORS_ALLOWED_ORIGINS` dípò rẹ̀. CORS wà fún àwọn oníbàárà API aṣàwákiri cross-origin nìkan; àwọn ìkọ̀wé dashboard tí a ti jẹ́rìí sí ń lo àwọn ìbéèrè same-origin pẹ̀lú ààbò CSRF tó so mọ́ session dípò rẹ̀. | -| `CORS_ALLOWED_ORIGINS` | _(a kò ṣètò)_ | `src/server/cors/origins.ts` | Àtòjọ ìyọ̀ǹda CORS tí kọ́má yà sọ́tọ̀. A kò ní fi wildcard ránṣẹ́ àyàfi tí a bá ṣètò `CORS_ALLOW_ALL=true` ní kedere. | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Ọ̀nà àbáyọ fún ìdàgbàsókè nìkan láti dá `Origin` aṣàwákiri èyíkéyìí padà. Má ṣe mú un ṣiṣẹ́ lórí àwọn ìmúṣiṣẹ́ tí a pín tàbí ti production. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Dí àwọn ìpè provider tí ó ń dojú kọ private/loopback/link-local IP ranges. Mú un ṣiṣẹ́ kúrò nínú àwọn àyíká ìdánwò tí a yà sọ́tọ̀ nìkan. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Fàyè gba àwọn URL provider tí ó tọ́ka sí àwọn nẹ́tíwọ́ọ̀kì private/local (localhost, 192.168.x.x, 10.x.x.x, àti bẹ́ẹ̀ bẹ́ẹ̀ lọ). **Ó ṢE PÀTÀKÌ fún àwọn provider tí a gbàlejò fúnra ẹni** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). Nígbà tí ó jẹ́ `false`, dashboard yóò kọ ìfọwọ́sí àwọn URL local. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Fàyè gba fífi àwọn provider kún un àti ṣíṣe ìfọwọ́sí wọn lórí àwọn àdírẹ́sì local/private (127.0.0.1, localhost, LAN, private ranges) — ó kan ọ̀nà ìfọwọ́sí provider nìkan. **Àìfọwọ́yí `true`** (local-first); ṣètò sí `false` láti fipá mú ìdènà public-only tó muna. Àwọn endpoint metadata awọsánmà (169.254.169.254, metadata.google.internal) ṣì jẹ́ dídínà láìka èyí sí. (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Jẹ́ kí àwọn route `/v1/audio/*` (àwọn ìkọ̀wé-ohùn-sí-ọ̀rọ̀, ọ̀rọ̀, àwọn ìtumọ̀) lo node provider tó bá OpenAI mu, tí a gbàlejò ní òde localhost. Ó wà ní pípa ní àìfọwọ́yí — dídarí audio sí host jíjìn ń yí ìdánimọ̀ egress padà, ó sì gbọ́dọ̀ jẹ́ ìpinnu operator tí a ṣe ní kedere. Àwọn node loopback/private (localhost, 127.0.0.1, 172.16-31.x) ni a máa ń gba láàyè ní gbogbo ìgbà, èyí kò sì ní ipa lórí wọn. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Jẹ́ kí `POST /v1/rerank` (àti ìgbésẹ̀ loopback rerank ti ẹ́ńjìnnì memory) lo node provider tó bá OpenAI mu, tí a gbàlejò ní òde localhost — ẹ̀rọ LAN kan tàbí peer Tailscale tí ó ń ṣiṣẹ́ TEI, Infinity, vLLM, àti bẹ́ẹ̀ bẹ́ẹ̀ lọ. Ó wà ní pípa ní àìfọwọ́yí — dídarí sí host jíjìn ń yí ìdánimọ̀ egress padà, ó sì gbọ́dọ̀ jẹ́ ìpinnu operator tí a ṣe ní kedere. Àwọn node loopback (localhost, 127.0.0.1, 172.16-31.x) ni a máa ń gba láàyè ní gbogbo ìgbà, èyí kò sì ní ipa lórí wọn. Àwọn node jíjìn tún gbọ́dọ̀ kọjá ìlànà URL outbound ti provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); a kì í darí sí àwọn host metadata awọsánmà láé. | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Nígbà tí OIDC bá ṣiṣẹ́, mú wíwọlé pẹ̀lú ọ̀rọ̀ aṣínà ṣiṣẹ́ kúrò kí àwọn aṣàmúlò lè jẹ́rìí sí ara wọn nípasẹ̀ OIDC Single Sign-On nìkan. A tún gba alias lasán `OIDC_DISABLE_PASSWORD_LOGIN`; Dashboard Feature Flag tó ní key kan náà ní ààyò. (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias lasán fún `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | +| Àyípadà | Àkọ́kọ́ | Fáìlì Orísun | Àpèjúwe | +| ------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt tí a darapọ̀ mọ́ àwọn àmì ìdánimọ̀ ohun èlò fún ṣíṣe ìtẹ̀wé ìka ẹ̀rọ. Yí i padà fún ìmúṣiṣẹ́ kọ̀ọ̀kan láti rí i dájú pé wọ́n yà sọ́tọ̀. | +| `OMNIROUTE_CLI_SALT` | _(a kò ṣètò = salt aláìlẹ́gbẹ́ fún ìfibọ̀ kọ̀ọ̀kan tí a tọ́jú sí `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | Salt HMAC fún ṣíṣẹ̀dá token ìfàṣẹsí CLI abẹ́nú. Ṣíṣètò iye yìí yí gbogbo àwọn token CLI lórí ẹ̀rọ náà padà, ó sì máa ń ní ipò àkọ́kọ́ ju salt tí a tọ́jú lọ. Wo `docs/security/CLI_TOKEN.md`. | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Ṣètò àsìá `Secure` lórí àwọn cookie sáà. **Ó gbọ́dọ̀ jẹ́ `true`** nígbà tí a bá ń ṣiṣẹ́ lẹ́yìn HTTPS. | +| `REQUIRE_API_KEY` | `false` | API middleware | Nígbà tí ó bá jẹ́ `true`, gbogbo àwọn ìbéèrè aṣojú `/v1/*` gbọ́dọ̀ ní kọ́kọ́rọ́ API tó fẹsẹ̀ múlẹ̀. **Àsìá yìí kì í dí `GET /v1/models` mọ́**, nítorí ó ń tẹ̀lé ìlànà ìwọlé dasibodu (`requireAuthForModels`) dípò rẹ̀ — nítorí náà, `401` láti `/v1/models` kò túmọ̀ sí pé a dáàbò bo ìṣirò àbájáde. Wo `docs/security/INFERENCE_AUTH_POSTURE.md` (#13695). | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Fàyè gba fífi iye kọ́kọ́rọ́ API kíkún hàn nínú ojú-ìlò Dasibodu. A lè ṣètò rẹ̀ láti inú Àwọn Àsìá Ẹ̀yà Dasibodu; ó jẹ́ ewu ààbò lórí àwọn instance tí ọ̀pọ̀ ènìyàn ń lò. | +| `NO_LOG_API_KEY_IDS` | _(òfìfo)_ | `src/lib/compliance/index.ts` | Àwọn ID kọ́kọ́rọ́ API tí a fi àmì kọ́má yà sọ́tọ̀, tí kò ní jẹ́ kí a ṣàkọsílẹ̀ ìbéèrè (ìbámu pẹ̀lú GDPR). | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(a kò ṣètò = àìlópin)_ | `src/shared/utils/apiKeyPolicy.ts` | Ìpín ìbéèrè ojoojúmọ́ àfidípò tí a lò fún àwọn kọ́kọ́rọ́ API tí ìlà `rate_limits` wọn jẹ́ null. Tí a kò bá ṣètò rẹ̀ tàbí tó ṣófo: kò sí ààlà àìfojúrí (#2289, #11017). `0` náà rí bẹ́ẹ̀ (àìlópin). Odidi nǹkan rere N ń mú N/ọjọ́, 5N/ọ̀sẹ̀, 20N/oṣù ṣiṣẹ́. Àwọn iye tí kò ṣófo tí a kọ lọ́nà àìtọ́ máa ń padà sí àwọn àkókò àtijọ́ 1000/ọjọ́, 5000/ọ̀sẹ̀, 20000/oṣù. | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Ìwọ̀n ara ìbéèrè tó pọ̀ jù tí a fàyè gbà. Ó kọ àwọn payload tó kọjá ààlà yìí. | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | Àwọn ara ìbéèrè gangan tó dé tàbí kọjá ààlà yìí máa ń gba lease ìgbàwọlé iṣẹ́-àdúgbò tó wúwo ní ẹ̀ẹ̀kan ṣáájú ìtúpalẹ̀ JSON (ọ̀nà BYTE, pẹ̀lú `POST /v1/responses`). Ọ̀nà àsálà àyè-afikún tó ní ìlera kan náà bí [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) fún èyí tó wuwo ní ìgbékalẹ̀; síbẹ̀, `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) ṣì fi ààlà sí i kí [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) má bàa padà. | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Òpin líle fún route ìbánisọ̀rọ̀ tí a fipá mú lórí iye byte tí a kà nígbà ìgbàwọlé aláàlà, pẹ̀lú àwọn ìbéèrè tí `Content-Length` wọn kò sí, kò tọ̀nà, tàbí kò sọ òótọ́; èyí tó bá kọjá ààlà yóò gba `413`. | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(a kò ṣètò rẹ̀ — kò sí òpin iye ìbéèrè)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:** òpin iye-ÌBÉÈRÈ àtijọ́ yìí máa ń ṣiṣẹ́ nìkan nígbà tí a bá ṣètò rẹ̀ ní tààrà. Tí a kò bá ṣètò rẹ̀ (èyí ni àìyípadà), ìgbàwọlé ìbánisọ̀rọ̀ tó wuwo yóò jẹ́ dídarí dípò bẹ́ẹ̀ nípasẹ̀ `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` — ìṣúná BYTE tí a ṣe àfàyọ láìfọwọ́yí, tí a gbé ìwọ̀n rẹ̀ ka orí òpin memory gidi ti process náà nínú **process kan** (heap V8 kan). Àwọn `/v1/responses` méjì tó yí ara wọn ká, tí ọ̀kọ̀ọ̀kan ní nǹkan bí token 750k, ń mú kí heap ~12 Gi ṣubú (#7849) — ìkìlọ̀ **ìṣúná-memory** ni, kì í ṣe òpin líle ọjà ti 2. Process tó ní ìlera (heap wà ní ìsàlẹ̀ ratio ìyọkúrò) LÈ gba àwọn `/v1/responses` gígùn mìíràn tí ń ṣiṣẹ́ lẹ́ẹ̀kan náà nípasẹ̀ `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM`. Ọ̀pọ̀lọpọ̀ client SSE gígùn (40–50) dá lórí heap + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110, kì í ṣe “ó pọ̀ jù lọ 2”. Gbígbé èyí ga láìronú láti “lo host náà” yóò tún mú #7849 padà. Ṣe ìlọ́po **heap** pẹ̀lú **`DATA_DIR` olómìnira N** (#11024); má ṣe lo `replicas>1` lórí fáìlì SQLite kan láé. | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(a ṣe àfàyọ rẹ̀ láìfọwọ́yí)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:** àtúnṣe fún ìṣúná byte ìgbàwọlé tí a ṣe àfàyọ láìfọwọ́yí (25% ti òpin memory V8/cgroup tó há jù, tí a pín nípasẹ̀ ìlọ́po ìgbà díẹ̀ 8x). Àwọn iye tí a ṣe àfàyọ àti tí a sọ ní tààrà ni a dín mọ́ ààrin 8 MiB–2 GiB. Body tó tóbi ju ìṣúná tó ń ṣiṣẹ́ lọ yóò kùnà lẹ́sẹ̀kẹsẹ̀ pẹ̀lú `413 body_exceeds_budget`; ìjàkadì láàárín àwọn body tí iṣẹ́ lè bójú tó lọ́kọ̀ọ̀kan ṣì lè tún gbìyànjú pẹ̀lú `503`. Client SSE gígùn 40–50 tí ń ṣiṣẹ́ lẹ́ẹ̀kan náà dá lórí ìṣúná yìí + heap, kì í ṣe “ó pọ̀ jù lọ 2” líle. Ka `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity` ní `/api/monitoring/health` kí o tó ṣe àtúnṣe. | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | Ratio ìyọkúrò nítorí ìfúnpá heap (`heapUsed / heap_size_limit`) fún ìgbàwọlé tó wuwo ti BYTE àti STRUCTURE (#10183, #10268, #10437). Ìbéèrè wuwo kan tó ń ṣiṣẹ́ lẹ́ẹ̀kan náà tí ó kọjá `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` ni a ó yọ kúrò pẹ̀lú `503` tó ṣeé tún gbìyànjú NÌKAN nígbà tí heap náà BÁKAN NÁÀ bá dé ratio yìí tàbí kọjá rẹ̀; lórí heap tó ní ìlera, a ó gbà á nípasẹ̀ healthy-headroom dípò bẹ́ẹ̀. | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (àìyípadà `1`) | `src/shared/middleware/chatBodyAdmission.ts` | Agbára àfikún aláàlà fún ọ̀nà yíyára heap tó ní ìlera (#10437) lórí **mejeeji** STRUCTURE àti BYTE (`admitChatRequest`, pẹ̀lú àwọn body ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`). Láìsí ààlà yìí, gbogbo ìbéèrè tí iṣẹ́ pọ̀ fún ṣùgbọ́n heap rẹ̀ ní ìlera máa ń ré kọjá ìgbàwọlé láìsí òpin. Nígbà tí iye lease tó ń ṣiṣẹ́ lẹ́ẹ̀kan náà bá ti ṣiṣẹ́ tó báyìí nípasẹ̀ ọ̀nà heap tó ní ìlera, àwọn ìbéèrè mìíràn tí iṣẹ́ pọ̀ fún yóò bọ́ sí ọ̀nà ìdúró-aláàlà/ìyọkúrò KAN NÁÀ tí a ń lò lábẹ́ ìfúnpá heap gidi. `0` máa ń pa ọ̀nà ìrékọjá náà pátápátá. | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | Iye message tó ń pín ìbéèrè ìbánisọ̀rọ̀ sí ẹ̀ka tó wuwo, kódà nígbà tí body rẹ̀ bá wà ní ìsàlẹ̀ ààlà byte. | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Iye tool tó ń pín ìbéèrè ìbánisọ̀rọ̀ sí ẹ̀ka tó wuwo, kódà nígbà tí body rẹ̀ bá wà ní ìsàlẹ̀ ààlà byte. | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | Ìṣírò token oníṣọ́ra tó dá lórí ìwọ̀n string, tí ó ń pín ìbéèrè sí ẹ̀ka tó wuwo; aṣojú iye owó ìgbàwọlé ni èyí, kì í ṣe tokenization ìdíyelé provider. | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0` (a ti pa á) | `src/shared/middleware/chatBodyAdmission.ts` | Òpin àṣàyàn fún itan ìbánisọ̀rọ̀ tí a gbọdọ̀ yàn láti lò. A pa á ní àìyípadà: iye message jẹ́ ìlànà deployment, kì í ṣe àbùdá gbogbo ìbéèrè, àti fífi òpin sí i níbí máa ń kọ àwọn ìjíròrò sílẹ̀ pẹ̀lú `413` ìparí kí pipeline compression tó lè jẹ́ kí iṣẹ́ lè bójú tó wọn. Ìdàgbàsókè heap jẹ́ dídín mọ́ ààlà nípasẹ̀ `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` àti ìyọkúrò nítorí ìfúnpá heap. Ṣètò iye rere lórí àwọn deployment tí memory wọn ní ààlà, tí wọ́n sì nílò òpin líle; èyí tó bá kọjá ààlà yóò gba `413` tó ní ètò, tó sì ń béèrè compression. | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | Òpin líle fún response upstream tí kì í ṣe streaming, tí a fi gbogbo rẹ̀ pamọ́ sínú memory. Tó bá kọjá èyí, a ó fagilé reader upstream, ìbéèrè náà yóò sì kùnà lẹ́sẹ̀kẹsẹ̀ dípò kí string tí kò ní ààlà máa dàgbà títí heap yóò fi tán. | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | Iye báìtì lórí nẹ́tíwọ́ọ̀kì tó pọ̀ jù tí a máa darí láti inú àwọn àkọlé ìdáhùn upstream. Nígbà tí iye yìí bá kọjá ààlà, a máa yọ àwọn àkọlé tí ààyò wọn kéré (fún àpẹẹrẹ, `x-codex-*`, `x-oai-request-id`) kúrò kí wọ́n lè dúró láàárín ààlà àkọlé reverse-proxy tí ó wọ́pọ̀. Ṣètò rẹ̀ sí iye tó ga jù láti darí metadata upstream púpọ̀ sí i, pẹ̀lú àbájáde pé ìwọ̀n àkọlé ìdáhùn yóò tóbi sí i. | +| `CORS_ORIGIN` | _(a kò ṣètò)_ | `src/server/cors/origins.ts` | Àtòjọ ìyọ̀ǹda CORS fún origin kan ṣoṣo ti àtijọ́. Fún àwọn ìmúṣiṣẹ́ tuntun, yan `CORS_ALLOWED_ORIGINS` dípò rẹ̀. CORS wà fún àwọn oníbàárà API aṣàwákiri cross-origin nìkan; àwọn ìkọ̀wé dashboard tí a ti jẹ́rìí sí ń lo àwọn ìbéèrè same-origin pẹ̀lú ààbò CSRF tó so mọ́ session dípò rẹ̀. | +| `CORS_ALLOWED_ORIGINS` | _(a kò ṣètò)_ | `src/server/cors/origins.ts` | Àtòjọ ìyọ̀ǹda CORS tí kọ́má yà sọ́tọ̀. A kò ní fi wildcard ránṣẹ́ àyàfi tí a bá ṣètò `CORS_ALLOW_ALL=true` ní kedere. | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Ọ̀nà àbáyọ fún ìdàgbàsókè nìkan láti dá `Origin` aṣàwákiri èyíkéyìí padà. Má ṣe mú un ṣiṣẹ́ lórí àwọn ìmúṣiṣẹ́ tí a pín tàbí ti production. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Orúkọ àtijọ́ (alias). A máa ń ka bọtini rẹ̀ lórí dashboard (DB override) kí a tó ka iye environment yìí; `false`, `0`, `no` tàbí `off` nínú èyí kọ̀ọ̀kan yóò pa àwọn ìyẹ̀síntì host dábi bí `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ṣe ń ṣe, bí ó tilẹ̀ jẹ́ wípé a ti fi bọtini dashboard flag yẹn pamọ́ sí ipò pa; àwọn iye míràn a fi olùṣọ́ sílẹ̀ fún àwọn flag meji tí ó wà ní ìsàlẹ̀. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Ó ń pa àwọn ìyẹ̀síntì host ti olùṣọ́ URL outbound, pẹ̀lú ìdènà metadata awọsánmà (gbogbo 169.254.0.0/16, metadata.google.internal), lórí àwọn ọ̀nà provider tí ó ń tẹ̀lé mode olùṣọ́: ìfọwọ́sí URL, ìwádìí model, àwọn URL ipilẹ̀ node provider (pẹ̀lú àwọn node rerank jíjìn), àti ìdánwò proxy-fallback, àti àwọn ọ̀nà outbound míràn tí ó sunmọ́ provider bí ìmúsórò àwòrán/media jíjìn. Ó tún ń fàyè gba àwọn ète webhook private, àfi àwọn endpoint metadata awọsánmà/link-local tí ó ṣì jẹ́ dídínà bí ó tilẹ̀ jẹ́ pé flag yìí wà ní títàn. Àwọn ìbéèrè chat tí a fi ránṣẹ́ lọ́dọ̀ `BaseExecutor.execute()` ń tọ́ju ìyẹ̀síntì metadata awọsánmà tirẹ̀. Àwọn provider local ń ṣiṣẹ́ pẹ̀lú àwọn àìfọwọ́yí: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (àìfọwọ́yí `true`) ti ń fàyè gba àwọn URL local àti LAN lórí àwọn ọ̀nà ìfọwọ́sí, ìwádìí àti node provider; ìdánwò proxy-fallback àti àwọn ète webhook private ń béèrè lọ́wọ́ flag yìí nìkan, wọ́n sì ń dinà lórí àwọn host local/LAN nígbà tí ó wà ní pípa. Ó borí `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. Iye tí a fi pamọ́ láti bọtini dashboard ń gbayì jù oníyípo yìí lọ. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Fàyè gba àwọn URL provider lórí àwọn àdírẹ́sì local/private (127.0.0.1, localhost, LAN, private ranges) fún ìfọwọ́sí provider, ìwádìí model, chat, àti àwọn ọ̀nà outbound míràn tí ó sunmọ́ provider tí mode olùṣọ́ ń ṣàkóso. **Àìfọwọ́yí `true`** (local-first): olùṣọ́ náà yóò dinà lórí àwọn endpoint metadata awọsánmà — gbogbo 169.254.0.0/16 àti àwọn orúkọ host metadata tí a mọ̀ (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Ṣètò sí `false` fún mode public-only, èyí tí ó ń dinà lórí àwọn host private àti loopback pẹ̀lú. Olùṣọ́ ń yẹ orúkọ host tàbí IP literal wò gẹ́gẹ́ bí a ti kọ ọ́, kí a tó ṣe ìwádìí DNS kankan; ní mode public-only, wrapper safe-fetch tún máa ń yan orúkọ host padà, ó sì máa ń kọ gbogbo ìdáhùn private. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` ń pa àwọn ìyẹ̀síntì wọnyí kúrò lórí àwọn ọ̀nà tí ilà rẹ̀ ṣàkójọ. Àwọn provider local tí a fi kún inú, bíi LM Studio, Ollama, vLLM, Llamafile àti Triton, ń yọ gbogbo ìyẹ̀síntì host kúrò fún ìfọwọ́sí bótini àti chat. Iye tí a fi pamọ́ láti bọtini dashboard ń gbayì jù oníyípo yìí lọ. (#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Jẹ́ kí àwọn route `/v1/audio/*` (àwọn ìkọ̀wé-ohùn-sí-ọ̀rọ̀, ọ̀rọ̀, àwọn ìtumọ̀) lo node provider tó bá OpenAI mu, tí a gbàlejò ní òde localhost. Ó wà ní pípa ní àìfọwọ́yí — dídarí audio sí host jíjìn ń yí ìdánimọ̀ egress padà, ó sì gbọ́dọ̀ jẹ́ ìpinnu operator tí a ṣe ní kedere. Àwọn node loopback/private (localhost, 127.0.0.1, 172.16-31.x) ni a máa ń gba láàyè ní gbogbo ìgbà, èyí kò sì ní ipa lórí wọn. (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Jẹ́ kí `POST /v1/rerank` (àti ìgbésẹ̀ loopback rerank ti ẹ́ńjìnnì memory) lo node provider tó bá OpenAI mu, tí a gbàlejò ní òde localhost — ẹ̀rọ LAN kan tàbí peer Tailscale tí ó ń ṣiṣẹ́ TEI, Infinity, vLLM, àti bẹ́ẹ̀ bẹ́ẹ̀ lọ. Ó wà ní pípa ní àìfọwọ́yí — dídarí sí host jíjìn ń yí ìdánimọ̀ egress padà, ó sì gbọ́dọ̀ jẹ́ ìpinnu operator tí a ṣe ní kedere. Àwọn node loopback (localhost, 127.0.0.1, 172.16-31.x) ni a máa ń gba láàyè ní gbogbo ìgbà, èyí kò sì ní ipa lórí wọn. URL ipilẹ̀ node jíjìn tún gbọ́dọ̀ kọjá ìlànà URL outbound ti provider (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Nígbà tí OIDC bá ṣiṣẹ́, mú wíwọlé pẹ̀lú ọ̀rọ̀ aṣínà ṣiṣẹ́ kúrò kí àwọn aṣàmúlò lè jẹ́rìí sí ara wọn nípasẹ̀ OIDC Single Sign-On nìkan. A tún gba alias lasán `OIDC_DISABLE_PASSWORD_LOGIN`; Dashboard Feature Flag tó ní key kan náà ní ààyò. (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Alias lasán fún `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | ### Àtòjọ Àyẹ̀wò Fífi Ààbò Múlẹ̀ diff --git a/docs/i18n/zh-CN/CONTRIBUTING.md b/docs/i18n/zh-CN/CONTRIBUTING.md index b59033b30a09..729c42ad4013 100644 --- a/docs/i18n/zh-CN/CONTRIBUTING.md +++ b/docs/i18n/zh-CN/CONTRIBUTING.md @@ -404,16 +404,16 @@ docs/ - [ ] 代码检查通过(`npm run lint`) - [ ] 构建成功(`npm run build`) - [ ] 已为新的公共函数和接口添加 TypeScript 类型 -- [ ] 不含硬编码密钥或回退值 -- [ ] 通过 `resolvePublicCred()` 嵌入上游公共凭据(参阅 [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)),绝不使用字面量 -- [ ] 错误响应通过 `buildErrorBody()` / `sanitizeErrorMessage()` 处理——响应正文中不含原始堆栈跟踪(参阅 [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell 命令(`exec` / `spawn`)通过 `env` 而非字符串插值传递运行时值 -- [ ] 所有输入均使用 Zod 模式验证 -- [ ] 对于面向用户的更改,已在 `changelog.d/{features|fixes|maintenance}/-.md` 下添加变更日志**片段**(参阅 [`changelog.d/README.md`](./changelog.d/README.md))——**不要**直接编辑 `CHANGELOG.md`;片段会在发布时聚合,且不同 PR 之间绝不会发生冲突 +- [ ] 不包含硬编码的密钥或回退值 +- [ ] 公共上游凭据通过 `resolvePublicCred()` 嵌入(参见 [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)),绝不使用字面量 +- [ ] 错误响应通过 `buildErrorBody()` / `sanitizeErrorMessage()` 处理——响应正文中不得包含原始堆栈跟踪(参见 [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell 命令(`exec` / `spawn`)通过 `env` 传递运行时值,而不是使用字符串插值 +- [ ] 所有输入均使用 Zod schema 进行验证 +- [ ] 对于面向用户的更改,已在 `changelog.d/{features|fixes|maintenance}/-.md` 下添加变更日志**片段**(参见 [`changelog.d/README.md`](./changelog.d/README.md))——请勿直接编辑 `CHANGELOG.md`;片段会在发布时聚合,并且不同 PR 之间绝不会发生冲突 - [ ] 文档已更新(如适用) -- [ ] 未产生新的 CodeQL / Secret-Scanning 警报,或者每个警报均已驳回,并提供引用相关 `docs/security/` 文档的技术理由 -- [ ] 会生成子进程的路由(`/api/mcp/`、`/api/cli-tools/runtime/`)已在 `src/server/authz/routeGuard.ts` 中归类为 `isLocalOnlyPath()`——参阅[硬性规则 #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] 提交消息中不含 `Co-Authored-By` 尾注——提交必须仅显示在仓库所有者的 Git 身份下(硬性规则 #16) +- [ ] 未引入新的 CodeQL / Secret-Scanning 警报,或者每个警报均已驳回,并提供了引用相关 `docs/security/` 文档的技术理由 +- [ ] 会生成子进程的路由(`/api/mcp/`、`/api/cli-tools/runtime/`)已在 `src/server/authz/routeGuard.ts` 中归类为 `isLocalOnlyPath()`——参见[硬性规则 #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] 提交消息中不包含 AI/机器人 `Co-authored-by` 尾注(硬性规则 #16)——若复用了人类协作者的工作,则使用标准的 `Co-authored-by: Name ` 尾注注明其贡献 --- diff --git a/docs/i18n/zh-CN/docs/architecture/QUALITY_GATES.md b/docs/i18n/zh-CN/docs/architecture/QUALITY_GATES.md index c44bc0b1dc9b..57db2900417c 100644 --- a/docs/i18n/zh-CN/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/zh-CN/docs/architecture/QUALITY_GATES.md @@ -12,197 +12,200 @@ --- -## 门禁清单与执行配置 +## 门禁清单和执行配置 ### 候选版本准入 -CI 和 Quality Gates 工作流各自都会生成一个稳定的判定结果:`Gate / CI` 和 -`Gate / Quality`。其版本化准入策略会将每个上游作业列为必需项或建议项。所有适用的 -必需作业都必须成功:缺失、已取消、已跳过、待处理和未知结果均不能确定为 PASS。有效的 -仅文档或仅目录分类可以使代码通道不适用;草稿 PR 不属于可接受的候选版本。`hotfix` -标签不能免除证据要求。 +CI 和 Quality Gates 工作流分别生成稳定的判定结果:`Gate / CI` 和 +`Gate / Quality`。其版本化准入策略会将每个上游作业列为必需或建议。 +所有适用的必需作业都必须成功:缺失、已取消、已跳过、待处理和未知结果均无法确立 PASS。 +有效的仅文档或仅目录分类可以使代码通道不适用; +草稿 PR 不会被接受为候选版本。`hotfix` 标签不能免除证据要求。 这两个工作流均涵盖 PR、向 main/release 分支的推送、手动触发以及 -merge-group 事件。推送、手动触发和 merge-group 会运行完整选择。对于原本选择 -self-hosted runner 的作业,fork 和 merge group 会改用 hosted runner;在推广之前, -必须验证 hosted runner 容量是否充足。 +合并组事件。推送、手动触发和合并组会运行完整选择。对于原本选择自托管 +运行器的作业,复刻仓库和合并组会使用托管运行器;在推出前必须验证 +托管容量是否充足。 -每份 JSON 回执都会标识检出的 SHA、工作流运行及尝试次数。 -CLI 会拒绝检出 SHA 与事件 SHA 不匹配的情况。工作流测试会将策略成员关系绑定到 -判定作业的 `needs` 列表,因此新增或移除的通道无法静默消失。 -这些回执仅覆盖其所属工作流,不涵盖发布、部署或现有建议型扫描器的内部机制。 -在分支规则中启用这两个检查名称是一项独立的管理变更;添加这些作业本身并不会保护分支。 +每份 JSON 收据都会标识签出的 SHA、工作流运行及其尝试次数。 +CLI 会拒绝签出 SHA 与事件 SHA 不匹配的情况。工作流测试会将策略成员关系 +绑定到判定作业的 `needs` 列表,因此新增或移除的通道不会悄然消失。 +这些收据涵盖其自身工作流,但不涵盖发布、部署,也不涵盖现有建议型扫描器的 +内部实现。在分支规则中启用这两个检查名称是一项单独的管理变更; +添加这些作业本身并不会保护分支。 ### 静态扫描清单 -版本化的 npm 别名清单和静态扫描成员关系位于 -`config/quality/gate-manifest.json`。运行 `npm run check:gate-manifest`,对照 -`package.json` 验证脚本名称和精确命令;新增、移除和命令偏移都会导致本地钩子和 CI -中的变更分类作业失败。别名并非工作流作业、矩阵实例或测试用例:不得将这些计数表述为可互换。 +版本化 npm 别名清单和静态扫描成员关系位于 +`config/quality/gate-manifest.json`。运行 `npm run check:gate-manifest`, +以对照 `package.json` 验证脚本名称和准确命令;任何新增、移除和 +命令漂移都会导致本地钩子以及 CI 中的变更分类作业失败。 +别名并非工作流作业、矩阵实例或测试用例:不得将这些计数视为可互换。 使用 `npm run quality:scan -- --list` 或 `npm run quality:scan:fast -- --list` -可以检查选中的别名而不执行它们。运行器调用 npm 入口点,因此会保留其运行时 -(包括已配置的 Bun)。清单会将这些配置之外的别名记录为单独调用,并且只读扫描配置中 +可以在不执行的情况下检查所选别名。运行器会调用 npm 入口点, +因此其运行时(包括已配置的 Bun)会得以保留。 +清单会将这些配置之外的别名记录为单独调用,并且只读扫描配置中 禁止使用维护命令。 -这些配置仅涵盖静态扫描。它们不对产品测试、覆盖率、打包、外部检查或候选版本的完整发布准入 -进行认证。工作流准入使用关联的 `config/quality/admission-policy.json` 和 -`scripts/quality/admission-verdict.mjs`。发布观察器配置仍保持独立; -请分别检查其适用的检查项和回执。下方的文字清单仅供参考,不能证明门禁实际运行过。 +这些配置仅涵盖静态扫描。它们不认证产品测试、覆盖率、打包、 +外部检查或候选版本的完整发布验收。工作流准入使用关联的 +`config/quality/admission-policy.json` 和 +`scripts/quality/admission-verdict.mjs`。发布观察器配置仍然独立; +请分别检查其适用检查项和收据。下方的文字清单仅供参考, +不能证明门禁实际运行过。 脚本位于 `scripts/check/`(策略门禁)和 `scripts/quality/`(棘轮引擎)下。 CI 的事实来源是 `.github/workflows/ci.yml`。 ### 发布 PR 快速路径(`quality.yml`) -`.github/workflows/quality.yml` 是 CI 的补充,适用于 main/release PR、向受保护分支的 -推送、手动触发和 merge group。PR 使用经过路径筛选的快速检查。已永久禁用的重复构建已被移除; -真正的构建、打包和启动检查仍保留在 CI 中。 +`.github/workflows/quality.yml` 为 main/release PR、受保护分支推送、 +手动触发和合并组补充 CI。PR 使用按路径筛选的快速检查。 +已移除永久禁用的重复构建;实际的构建/打包/启动检查仍保留在 CI 中。 -| 作业 | 范围 | 阻断性 | -| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- | -| `Docs Gates (fast-path)` | 文档/代码 PR;API 文档引用和全部文档 | 是 | -| `Fast Quality Gates` | 代码 PR;静态检查、类型检查、仪表板类型检查、受影响的单元测试 | 是 | -| `Forgotten sibling tests` | 代码 PR;将已变更模块追踪到静态使用方和候选同级测试;barrel 和动态导入路径会作为建议性诊断报告,并包含引用的允许列表例外 | **建议性** | -| `Vitest (fast-path)` | 代码 PR;快速 vitest 测试套件 | 是 | -| `Unit Tests fast-path` | 代码 PR;4 分片单元测试套件 | 是 | -| `No new ESLint warnings` | 代码 PR;可识别抑制项的 lint 防护 | 是,包括 fork | -| `Merge integrity (changelog + generated skills)` | 非草稿 PR;变更日志和生成的 skill 同步 | 是,包括 fork | +| 作业 | 范围 | 阻断性 | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `Docs Gates (fast-path)` | 文档/代码 PR;API 文档引用和全部文档 | 是 | +| `Fast Quality Gates` | 代码 PR;静态检查、类型检查、仪表板类型检查、受影响的单元测试 | 是 | +| `Forgotten sibling tests` | 代码 PR;将已更改模块追踪到静态使用方和候选同级测试;桶文件和动态导入路径会作为建议性诊断报告,并包含引用的允许列表例外 | **建议性** | +| `Vitest (fast-path)` | 代码 PR;快速 vitest 测试套件 | 是 | +| `Unit Tests fast-path` | 代码 PR;4 分片单元测试套件 | 是 | +| `No new ESLint warnings` | 代码 PR;感知抑制规则的 lint 防护 | 是,包括复刻仓库 | +| `Merge integrity (changelog + generated skills)` | 非草稿 PR;变更日志和生成的技能同步 | 是,包括复刻仓库 | #### 遗漏的同级测试报告 -`npm run check:forgotten-sibling-tests` 会复用测试影响映射所使用的导入解析器。 -对于每个已变更的生产模块,当候选测试未出现在 pull request 差异中时,它会报告确定性的 -`已变更模块/符号 -> 静态使用方 -> 候选同级测试` 链。Markdown 摘要和 JSON 结果会作为 -`forgotten-sibling-tests` 工作流工件保留,用于在任何阻断性推广之前进行校准。 - -桶文件重导出和动态导入仅用于解析诊断;它们绝不会产生阻塞性发现。经审核的例外位于 -`config/quality/forgotten-sibling-allowlist.json`。每个条目都必须指定使用方和候选 -测试,给出具体理由,并链接至 GitHub issue 或 pull request。格式错误的条目将按失败处理。 -例外无法抑制已删除的候选测试,也无法抑制新增 `.skip`/`.todo` 的差异; -断言弱化及其他掩盖行为仍由独立阻塞的 +`npm run check:forgotten-sibling-tests` 会复用测试影响映射背后的导入解析器。 +对于每个已更改的生产模块,当候选测试未出现在拉取请求差异中时,它会报告确定性的 +`已更改模块/符号 -> 静态使用方 -> 候选同级测试` 链。 +Markdown 摘要和 JSON 结果会作为 `forgotten-sibling-tests` 工作流工件保留, +以便在任何阻断性推广之前进行校准。 + +桶式重导出和动态导入仅用于解析诊断;它们绝不会产生 +阻塞性发现。经审核的例外位于 +`config/quality/forgotten-sibling-allowlist.json`。每个条目都必须指明使用方和候选 +测试,提供具体理由,并链接到 GitHub issue 或 pull request。格式错误的条目将按拒绝处理。 +例外无法忽略已删除的候选测试,也无法忽略新增 `.skip`/`.todo` 的差异; +断言弱化和其他掩盖行为仍由独立阻塞的 `check:test-masking` 门禁负责。 ### 作业:`lint` 在每个提交到 `main` 的 PR 上运行。失败时阻止合并。 -| 脚本 (`npm run ...`) | 验证内容 | 是否阻塞 | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:node-runtime` | Node.js 版本是否在支持的范围内 | 是 | -| `check:cycles` | 循环导入 — 所有 `src/` + `open-sse/` 模块 | 是 | -| `check:route-validation:t06` | 所有路由上均存在 Zod schema(第 6 层策略) | 是 | -| `check:any-budget:t11` | `@ts-expect-error // any` 的数量不超过预算(第 11 层 catraca) | 是 | -| `check:provider-consistency` | `providers.ts` 中的每个提供者在 `providerRegistry.ts` 中都有匹配的条目(反之亦然,但仅限允许列表范围内) | 是 | -| `check:model-lifecycle` | 三个手工维护的路由表与已签入的生命周期快照(#11503)保持一致:`FITNESS_TABLE`(`taskFitness.ts`)不会为 `REGISTRY` 可路由的任何已停用 id 评分;每个 `BUILT_IN_ALIASES` 目标都存在于 `REGISTRY` 中,且不在已停用 id 快照中;`REGISTRY` 中仍存在的每个已停用 id 都会被转发或列入 `allowedRetiredInCatalog`;并且 `DEFAULT_DEGRADATION_MAP` 的源或目标均未在该快照中标记为已停用。这并不能证明某个模型当前正由在线上游提供服务。离线检查——与 `config/quality/model-lifecycle.json` 比较;该文件通过 `npm run quality:refresh-model-lifecycle` 手动刷新(需要网络;未接入 CI)。`allowedRetiredInCatalog` 是一种逐步清零的棘轮机制:仅在有关联的跟踪 issue 时才可添加条目。 | 是 | -| `check:fetch-targets` | 客户端 `src/` 中的每个 `fetch("/api/...")` 都会解析到真实存在的 `route.ts` | 是 | -| `check:deps` | 仓库中每个 `package.json` 内所有可通过 `npm install` 安装的依赖项都位于 `dependency-allowlist.json` 中;新增的未固定版本或疑似拼写抢注的软件包会被标记 | 是 | -| `audit:deps` | `npm audit`(根目录 + electron)——无高危/严重级别的安全公告(与 osv `check:vuln-ratchet` 重叠;请参阅合理化待办事项) | 是 | -| `check:lockfile` | `package-lock.json` 完整性——使用 https 注册表、包含完整性哈希、无主机覆盖 | 是 | -| `check:licenses` | 生产依赖项的 SPDX 许可证允许列表 | 是 | -| `check:tracked-artifacts` | 不允许存在构建产物或已提交的 `node_modules` 符号链接(也会在 husky pre-commit 中运行;pre-push 有意保持轻量——#6716) | 是 | -| `check:ai-attribution` | PR 提交、标题或正文中不得包含 AI/机器人 `Co-Authored-By` 尾注或 AI 生成页脚——硬性规则 #16(在针对 PR→`release/**` 的 `quality.yml` 快速门禁循环中——读取事件负载,在非 PR 情况下不执行任何操作——以及针对 PR→`main` 的 `ci.yml` lint 中仅限 PR 的步骤;也包括 husky `commit-msg` 钩子;允许人类共同作者;#14436) | -| `check:vitest-exclusions` | 每个 Vitest 排除项都必须注明一个跟踪议题,并出现在 `config/quality/vitest-exclusions.json` 中(#13204) | 是 | -| `check:file-size` | 任何源文件都不得超过其扩展名对应的上限(棘轮机制:大型文件冻结在 `frozen` 列表中) | 是 | -| `check:error-helper` | 执行器/处理程序中的错误响应须使用 `buildErrorBody()` / `sanitizeErrorMessage()`(硬性规则 #12) | 是 | -| `check:migration-numbering` | Migration SQL 文件按顺序编号,无缺号或重复编号 | 是 | -| `check:public-creds` | 除 `publicCreds.ts` 外,不得出现字面量 OAuth `client_id`/`client_secret` 或 Firebase Web 密钥(硬性规则 #11) | 是 | -| `check:db-rules` | `src/lib/db/` 模块之外不得出现原始 SQL;不得从 `localDb.ts` 进行桶导入(硬性规则 #2/#5) | 是 | -| `check:known-symbols` | 在各自分发表中注册的提供者执行器、路由策略和转换器必须与磁盘上的文件匹配——不得存在孤立或未声明的符号 | 是 | -| `check:route-guard-membership` | 每个会生成子进程的路由都必须由 `isLocalOnlyPath()` 进行分类(硬性规则 #15/#17) | 是 | -| `check:test-discovery` | 仓库中的每个 `*.test.ts` / `*.spec.ts` 文件都必须由至少一个测试运行器收集(棘轮机制:`test-discovery-baseline.json` 中的孤立文件列表只能缩短) | 是 | -| `check:agent-skills-sync` | 生成的 agent-skills 工件与其源目录一致(无偏移) | -| `check:provider-asset-provenance` | Provider 徽标/资源包含已记录的来源条目 | -| `lint:json` | JSON 配置文件可被解析,并符合仓库的 lint 规则 | -| `typecheck:core` | TypeScript 编译无错误(仅有建议性警告) | 是 | -| `typecheck:noimplicit:core` | 严格的 `noImplicitAny`——面向未来;许多已有调用点仍需添加注解 | **建议性**(`continue-on-error: true`) | -| `check:dashboard-typecheck` | 范围限定为 `src/app/(dashboard)/**` 的 `tsc`(#7033)——`typecheck:core` 精选的 27 文件允许列表不包含任何 dashboard TSX,而 `next build` 也从不对其进行类型检查(`next.config.mjs` 设置了 `ignoreBuildErrors: true`),因此其中的孤立标识符回归(#6625/#6909)对 CI 不可见。与按文件/TS 代码计数的冻结基线(`config/quality/dashboard-typecheck-baseline.json`,采用与 `check:known-symbols` 相同的过期强制检查模式)进行差异比较——只有超出基线计数的新增错误才会导致门禁失败;修复已有错误后,使用 `--update` 逐步下调基线。 | 是 | +| 脚本(`npm run ...`) | 验证内容 | 阻塞性 | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Node.js 版本位于支持的范围内 | 是 | +| `check:cycles` | 检查整个 `src/` + `open-sse/` 中的循环导入(基于 AST,并解析 tsconfig `paths`)。单独运行时仅提供建议,并列出循环。`check:cycles:ratchet`(CI 实际运行的命令)会在数量超过 `quality-baseline.json` 中的 `metrics.cycles` 上限时阻塞——当前上限为 14,`direction: down`,因此该值只能下降(#15159 G-01/G-02) | 是(棘轮式) | +| `check:route-validation:t06` | 所有路由都存在 Zod schema(第 6 层策略) | 是 | +| `check:any-budget:t11` | `@ts-expect-error // any` 的数量不超过预算(第 11 层棘轮机制) | 是 | +| `check:provider-consistency` | `providers.ts` 中的每个提供者在 `providerRegistry.ts` 中都有匹配的条目(反之亦然,但仅限允许列表范围内) | 是 | +| `check:model-lifecycle` | 三个人工维护的路由表与已签入的生命周期快照 (#11503) 保持一致:`FITNESS_TABLE` (`taskFitness.ts`) 不会为 `REGISTRY` 可路由的任何已停用 id 评分;每个 `BUILT_IN_ALIASES` 目标都存在于 `REGISTRY` 中,且不存在于已停用 id 快照中;`REGISTRY` 中仍存在的每个已停用 id 均已被转发或列入 `allowedRetiredInCatalog`;并且 `DEFAULT_DEGRADATION_MAP` 的源或目标都未在该快照中显示为已停用。这并不能证明某个模型当前正由在线上游提供服务。离线检查——与 `config/quality/model-lifecycle.json` 进行比较,该文件通过 `npm run quality:refresh-model-lifecycle` 手动刷新(需要网络;未接入 CI)。`allowedRetiredInCatalog` 是一个只减不增的棘轮机制:仅在有关联的跟踪 issue 时才添加条目。 | 是 | +| `check:fetch-targets` | 客户端 `src/` 中的每个 `fetch("/api/...")` 都会解析到真实的 `route.ts` | 是 | +| `check:deps` | 仓库中每个 `package.json` 里所有可通过 `npm install` 安装的依赖项均存在于 `dependency-allowlist.json` 中;新增的未固定版本或疑似拼写抢注的软件包会被标记 | 是 | +| `audit:deps` | `npm audit`(根目录 + electron)——无高危/严重级别的安全公告(与 osv `check:vuln-ratchet` 重叠;参见合理化待办事项) | 是 | +| `check:lockfile` | `package-lock.json` 完整性——使用 https 注册表、包含完整性哈希、无主机覆盖 | 是 | +| `check:licenses` | 生产依赖项的 SPDX 许可证允许列表 | 是 | +| `check:tracked-artifacts` | 不存在构建产物/已提交的 `node_modules` 符号链接(也在 husky pre-commit 中运行;pre-push 有意保持轻量 — #6716) | 是 | +| `check:ai-attribution` | PR 提交、标题或正文中不得包含 AI/机器人 `Co-Authored-By` 尾注或 AI 生成页脚 — 强制规则 #16(位于 `quality.yml` 针对 PR→`release/**` 的快速门禁循环中 — 读取事件负载,在非 PR 情况下不执行任何操作 — 以及 `ci.yml` lint 中仅针对 PR→`main` 的步骤;也包括 husky `commit-msg` 钩子;允许人类共同作者;#14436) | +| `check:vitest-exclusions` | 每个 Vitest 排除项都需注明跟踪 issue,并出现在 `config/quality/vitest-exclusions.json` 中(#13204) | 是 | +| `check:file-size` | 源文件不得超过各扩展名对应的大小上限(棘轮机制:`frozen` 列表中冻结的大文件) | 是 | +| `check:error-helper` | 执行器/处理器中的错误响应使用 `buildErrorBody()` / `sanitizeErrorMessage()`(强制规则 #12) | 是 | +| `check:migration-numbering` | Migration SQL 文件按顺序编号,无间断或重复 | 是 | +| `check:public-creds` | 除 `publicCreds.ts` 外,不得出现字面量 OAuth `client_id`/`client_secret` 或 Firebase Web 密钥(硬性规则 #11) | 是 | +| `check:db-rules` | `src/lib/db/` 模块之外不得出现原始 SQL;不得从 `localDb.ts` 进行桶式导入(硬性规则 #2/#5) | 是 | +| `check:known-symbols` | 在各自分派表中注册的提供者执行器、路由策略和转换器必须与磁盘上的文件匹配——不得存在孤立或未声明的符号 | 是 | +| `check:route-guard-membership` | 每个会生成子进程的路由都必须由 `isLocalOnlyPath()` 进行分类(硬性规则 #15/#17) | 是 | +| `check:test-discovery` | 仓库中的每个 `*.test.ts` / `*.spec.ts` 文件都必须由至少一个测试运行器收集(棘轮机制:`test-discovery-baseline.json` 中的孤立文件列表只能缩短) | 是 | +| `check:agent-skills-sync` | 生成的 agent-skills 构件与其源目录一致(无漂移) | +| `check:provider-asset-provenance` | Provider 徽标/资源具有已记录的来源条目 | +| `lint:json` | JSON 配置文件可成功解析并符合仓库的 lint 规则 | +| `typecheck:core` | TypeScript 编译无错误(仅有提示性警告) | 是 | +| `typecheck:noimplicit:core` | 严格执行 `noImplicitAny`——面向未来;许多既有调用点仍需添加注解 | **提示性**(`continue-on-error: true`) | +| `check:dashboard-typecheck` | 针对 `src/app/(dashboard)/**` 范围运行 `tsc`(#7033)——`typecheck:core` 精选的 27 个文件白名单不包含任何 dashboard TSX,且 `next build` 也从不对其进行类型检查(`next.config.mjs` 设置了 `ignoreBuildErrors: true`),因此其中的孤立标识符回归(#6625/#6909)对 CI 不可见。与按文件/TS 错误代码计数的冻结基线(`config/quality/dashboard-typecheck-baseline.json`,采用与 `check:known-symbols` 相同的陈旧项强制检查模式)进行差异比较——只有超出基线计数的新增错误才会导致门禁失败;修复既有错误后,使用 `--update` 逐步降低基线。 | 是 | ### 作业:`quality-gate` 在 `test-coverage` 之后运行。失败时阻止合并。 -| 脚本 | 验证内容 | 阻塞性 | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -| `quality:collect` | 生成 `quality-metrics.json`(ESLint 警告数量、来自合并分片报告的覆盖率) | 是(ratchet 的上游步骤) | -| `quality:ratchet` | `quality-baseline.json` 中的每项指标均未退化(ESLint 警告数 ≤ 基线;覆盖率 ≥ 基线) | 是 | -| `check:duplication` | 代码重复度(jscpd@4)不超过 `quality-baseline.json` 中的基线 | 是 | -| `check:complexity` | 文件级圈复杂度不超过上限(核心 ESLint `complexity` + `max-lines-per-function`) | 是 | -| `check:cognitive-complexity` | 认知复杂度 ratchet(`eslint-plugin-sonarjs`)——单独执行 ESLint;CI 将两者合并为单个 `check:complexity-ratchets` 步骤运行 | 是 | -| `check:dead-code` | 未使用的导出/文件 ratchet(knip)相较基线未退化 | 是 | -| `check:compression-budget` | 压缩基准预算——各引擎的 token 节省量下限不得退化 | 是 | -| `check:type-coverage` | 类型标注百分比 ratchet(`type-coverage`)未退化;基本涵盖 `typecheck:noimplicit:core` | 是 | -| `check:codeql-ratchet` | 未解决的 CodeQL 警报数量未增加(通过 `gh api` 读取;无 token 时平稳跳过)——刷新频率和手动触发方式:请参阅下方的“CodeQL ratchet” | 是 | +| 脚本 | 验证内容 | 阻塞性 | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `quality:collect` | 生成 `quality-metrics.json`(ESLint 警告数、合并分片报告中的覆盖率) | 是(位于棘轮检查的上游) | +| `quality:ratchet` | `quality-baseline.json` 中的各项指标均未退化(ESLint 警告数 ≤ 基线;覆盖率 ≥ 基线) | 是 | +| `check:duplication` | 代码重复率(jscpd@4)不超过 `quality-baseline.json` 中的基线 | 是 | +| `check:complexity` | 文件级圈复杂度不超过上限(核心 ESLint `complexity` + `max-lines-per-function`) | 是 | +| `check:cognitive-complexity` | 认知复杂度棘轮检查(`eslint-plugin-sonarjs`)— 单独执行 ESLint;CI 将两项检查合并为单个 `check:complexity-ratchets` 步骤运行 | 是 | +| `check:dead-code` | 未使用的导出/文件棘轮检查(knip)相对于基线没有退化 | 是 | +| `check:compression-budget` | 压缩基准预算 — 各引擎的令牌节省下限不得退化 | 是 | +| `check:type-coverage` | 类型标注百分比棘轮检查(`type-coverage`)没有退化;基本涵盖 `typecheck:noimplicit:core` | 是 | +| `check:codeql-ratchet` | 未解决的 CodeQL 警报数量没有增加(通过 `gh api` 读取;无令牌时正常跳过)— 刷新频率和手动触发方式:参见下方的“CodeQL 棘轮检查” | 是 | ### 作业:`quality-extended` -整个作业均为建议性检查(`continue-on-error: true`)。基于 npm 的 ratchet 会实际运行; -外部扫描器通过 `gh release download` 安装,当二进制文件仍不存在时会自行跳过(退出码为 0)。 +整个作业仅提供建议(`continue-on-error: true`)。基于 npm 的棘轮检查会实际运行; +外部扫描器通过 `gh release download` 安装,并在二进制文件仍不存在时自行跳过(退出码为 0)。 -| 脚本 | 验证内容 | 阻塞性 | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:circular-deps` | 不存在循环依赖(dpdm) | **建议性** | -| `check:bundle-size` | 包体积不超过上限 | **建议性** | -| `check:secrets` | 密钥扫描(gitleaks)——二进制文件不存在时跳过 | **建议性** | -| `check:vuln-ratchet` | 依赖项漏洞(osv-scanner)未退化——二进制文件不存在时跳过 | **建议性** | -| `check:workflows` | 工作流 lint(actionlint + zizmor);扫描器缺失/损坏、报告无效或 ratchet 基线缺失时,将以 INCOMPLETE 状态失败。有效发现遵循所选的严格/建议性/ratchet 策略 | 必须执行;zizmor ratchet 在 CI 中具有阻塞性 | -| `check:openapi-breaking` | 公共 API 契约(`openapi.yaml`)相对于基础分支的破坏性变更(oasdiff)——生成 `openapiBreaking=N`;若 oasdiff 不存在或无法解析基础规范,则跳过 | **建议性** | +| 脚本 | 验证内容 | 阻塞性 | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `check:circular-deps` | 不存在循环依赖(dpdm) | **建议性** | +| `check:bundle-size` | 包大小不超过上限 | **建议性** | +| `check:secrets` | 密钥扫描(gitleaks)— 二进制文件不存在时跳过 | **建议性** | +| `check:vuln-ratchet` | 依赖项漏洞(osv-scanner)没有增加 — 二进制文件不存在时跳过 | **建议性** | +| `check:workflows` | 工作流 lint 检查(actionlint + zizmor);扫描器缺失/损坏、报告无效或棘轮基线缺失时,以 INCOMPLETE 状态失败。有效发现遵循选定的严格/建议性/棘轮策略 | 必须执行;zizmor 棘轮检查在 CI 中具有阻塞性 | +| `check:openapi-breaking` | 检查公共 API 契约(`openapi.yaml`)相对于基础分支的破坏性变更(oasdiff)— 输出 `openapiBreaking=N`;oasdiff 不存在或基础规范无法解析时跳过 | **建议性** | ### 作业:`docs-sync-strict` -在每个提交到 `main` 的 PR 上运行。失败时阻止合并。 - -| 脚本 | 验证内容 | 阻断性 | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| `check:docs-all` | 作为元门禁,依次运行下方 6 个子门禁 | 是 | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt 版本一致性 | 是 | -| ↳ `check:docs-counts` | 文档正文中的数量(提供者数量、迁移数量等)处于实际数量的棘轮窗口内 | 是 | -| ↳ `check:env-doc-sync` | `.env.example` 中的每个环境变量均记录在文档表格中,反之亦然 | 是 | -| ↳ `check:deprecated-versions` | 文档中不存在已弃用的版本字符串 | 是 | -| ↳ `check:doc-links` | 文档中的内部 markdown 链接可解析到实际文件(`[text]`/`(path)` 形式) | 是 | -| ↳ `check:fabricated-docs` | 文档中引用的路由、环境变量、CLI 命令、钩子名称和文件路径均存在于代码库中。使用 `--strict` 时为硬门禁;不使用该标志时允许软失败。 | 是(CI 中通过 `--strict`) | -| `check:cli-i18n` | 所有 i18n 区域设置文件中均存在 CLI 命令字符串 | 是 | -| `check:openapi-coverage` | OpenAPI 规范至少覆盖实际路由数量的棘轮下限 | 是 | -| `check:openapi-security-tiers` | `openapi.yaml` 中的安全层级注解与 `routeGuard.ts` 中的分类一致 | **建议性** | -| `check:openapi-routes` | `openapi.yaml` 中的每个路径都可解析到真实的 `route.ts`(防止臆造) | 是 | -| `check:docs-symbols` | `docs/**/*.md` 中的每个 `/api/...` 引用都可解析到真实的 `route.ts`(防止臆造) | 是 | -| `i18n translation drift` | i18n 区域设置文件中未翻译的键——仅警告 | **建议性** | +在每个以 `main` 为目标分支的 PR 上运行。失败时阻止合并。 + +| 脚本 | 验证内容 | 阻塞性 | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| `check:docs-all` | 元门禁,依次运行下方 6 个子门禁 | 是 | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt 版本一致性 | 是 | +| ↳ `check:docs-counts` | 文本中的数量(提供者数量、迁移数量等)处于实际数量的棘轮窗口范围内 | 是 | +| ↳ `check:env-doc-sync` | `.env.example` 中的每个环境变量都记录在文档表格中,反之亦然 | 是 | +| ↳ `check:deprecated-versions` | 文档中不存在已弃用的版本字符串 | 是 | +| ↳ `check:doc-links` | 文档中的内部 markdown 链接可解析到实际文件(`[text]`/`(path)` 形式) | 是 | +| ↳ `check:fabricated-docs` | 文档中引用的路由、环境变量、CLI 命令、钩子名称和文件路径均存在于代码库中。使用 `--strict` 时为硬门禁;不带该标志时为软失败。 | 是(CI 中通过 `--strict`) | +| `check:cli-i18n` | 所有 i18n 区域设置文件中均存在 CLI 命令字符串 | 是 | +| `check:openapi-coverage` | OpenAPI 规范覆盖的实际路由数量至少达到棘轮式下限 | 是 | +| `check:openapi-security-tiers` | `openapi.yaml` 中的安全层级注解与 `routeGuard.ts` 的分类一致 | **建议性** | +| `check:openapi-routes` | `openapi.yaml` 中的每个路径都可解析到真实的 `route.ts`(防止虚构) | 是 | +| `check:docs-symbols` | `docs/**/*.md` 中的每个 `/api/...` 引用都可解析到真实的 `route.ts`(防止虚构) | 是 | +| `i18n translation drift` | i18n 区域设置文件中未翻译的键——仅警告 | **建议性** | ### 作业:`i18n-ui-coverage` -| 脚本 | 验证内容 | 阻断性 | -| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check-ui-keys-coverage`(内联) | UI i18n 键覆盖率 ≥ 65% | 是 | -| `check-ui-value-drift`(内联) | 重写英文**值**后,不会遗留过时的翻译 | 是 | -| `check-new-key-coverage`(内联) | **新增的**英文键在每个区域设置中均已翻译——拒绝 `__MISSING__:` 标记 | 是 | -| `check-translation-ratio` | 每个区域设置的真实翻译比例(与英文相同的内容 / 占位符 / 白名单之外的缺失叶节点)不得超过 `config/quality/i18n-translation-baseline.json` + 宽限值 | **建议性** | +| 脚本 | 验证内容 | 阻塞性 | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check-ui-keys-coverage`(内联) | UI i18n 键覆盖率 ≥ 65% | 是 | +| `check-ui-value-drift`(内联) | 重写后的英文**值**不会留下陈旧的翻译 | 是 | +| `check-new-key-coverage`(内联) | **新增的**英文键已在每个区域设置中翻译——拒绝 `__MISSING__:` 标记 | 是 | +| `check-translation-ratio` | 每个区域设置的真实翻译比率(允许列表之外与英文相同、占位符或缺失的叶节点)不得超过 `config/quality/i18n-translation-baseline.json` + 宽限值 | **建议性** | 需要 `fetch-depth: 0`——值漂移门禁会将 `en.json` 与合并基准进行差异比较。 -#### `check-ui-value-drift`——过时翻译门禁 +#### `check-ui-value-drift`——陈旧翻译门禁 -用于捕获其他门禁在结构上无法发现的一类 i18n 回归:英文值 -被重写,但基于_先前_英文内容生成的翻译仍然保留,导致 -非英语用户继续阅读措辞笃定、但现已错误的文案。 +它能够捕获其他门禁从结构上无法发现的一类 i18n 回归:英文值已被重写,但基于_先前_英文生成的翻译仍被保留下来,导致非英语用户继续阅读措辞笃定但现已错误的文案。 -这曾真实发布到生产环境中。Antigravity -登录辅助功能上线时(#5203),`oauthModal.googleOAuthWarning` 被重写;**43 个区域设置中有 39 个** -仍保留着提示运维人员“复制完整 URL 并粘贴到下方”的文本——对于该提供者,这一流程根本无法完成。直到 #8463 才发现该问题,原因如下: +这个问题确实曾发布到生产环境。引入 Antigravity 登录辅助工具(#5203)时,`oauthModal.googleOAuthWarning` 被重写;**43 个区域设置中有 39 个**仍保留着要求运维人员“复制完整 URL 并将其粘贴到下方”的文本——对于该提供者,此流程根本无法完成。直到 #8463 才发现这一问题,原因如下: -- `sync-ui-keys` 只补全**缺失**的键,从不处理**过时**的键; -- `check-ui-keys-coverage` 统计键是否_存在_,因此过时翻译仍会被计为已覆盖; +- `sync-ui-keys` 只会回填**缺失**的键,而不会处理**陈旧**的键; +- `check-ui-keys-coverage` 统计键是否_存在_,因此陈旧翻译仍会被计为已覆盖; - `check-translation-drift` 跟踪的是 `docs/i18n//**.md` 文档镜像—— - 它从不读取 `src/i18n/messages/*.json`。自 2026-09 重新同步以来,该检查在作业 `docs-sync-strict` 中具有阻断性:编辑核心文档 → `npm run i18n:run -- --files=`(按章节执行,开销较低)。 + 它从不读取 `src/i18n/messages/*.json`。自 2026-09 重新同步以来,它在作业 `docs-sync-strict` 中具有阻塞性:编辑核心文档 → `npm run i18n:run -- --files=`(按章节处理,开销较低)。 -**感知差异,而非依赖基线。** 它会比较合并基点处的 `en.json` 与工作树;对于英语值发生变化的每个键,任何仍保留未修改译文的语言区域都将被视为过时。这有意**冻结了既有债务**——差异无法揭示长期存在的译文源自哪个旧英语文本,因此该门禁只判断当前变更所涉及的内容。另一种方案(为每个键维护哈希基线)会产生一个约 600 KB 的生成文件,是现有最大基线的 3 倍,并且会在每个 i18n PR 中频繁变动。 +**感知差异,而非依赖基线。** 它会将合并基点处的 `en.json` 与工作树进行比较;对于英文值发生变化的每个键,任何仍保留未修改译文的语言区域都将被视为过期。此机制有意**冻结既有欠账**——差异无法揭示长期存在的译文源自哪个旧英文文本,因此该门禁仅判断当前变更所涉及的内容。另一种方案(逐键哈希基线)会产生一个约 600 KB 的生成文件,是现有最大基线的 3 倍,并且会在每个 i18n PR 中频繁变动。 -有两种方式可以满足要求: +有两种方式可满足此门禁: 1. 更新受影响的译文,或 -2. 将其设置为 `__MISSING__:`——随后运行时会提供修正后的英语文本 +2. 将其设置为 `__MISSING__:<新英文文本>`——随后运行时会提供修正后的英文文本 (`src/i18n/request.ts::deepMergeFallback`,#7258),并将该键加入待翻译队列。 -如果字符串的**含义**发生了变化,优先考虑**重命名该键**:新键不会继承过时的译文。这正是 #8463 所采用的模式。 +如果字符串的**含义**发生了变化,优先考虑**重命名键**:新键不会继承过期的译文。这正是 #8463 使用的模式。 ```bash npm run i18n:check-value-drift # 严格模式(CI 运行的模式) @@ -210,11 +213,11 @@ npm run i18n:check-value-drift:warn # 仅报告 BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -当无法读取基础目录(浅克隆中没有基础引用)时,以状态码 0 退出并输出 `SKIP reason=base-unresolved`,与 `check-openapi-breaking` 的行为一致。 +当无法读取基础目录时(浅克隆中没有基础引用),以状态码 0 退出并输出 `SKIP reason=base-unresolved`,与 `check-openapi-breaking` 的行为一致。 ### 作业:`i18n` -完整的 i18n 验证矩阵(每个语言区域一个作业)。整个作业均为建议性检查。 +完整的 i18n 验证矩阵(每个语言区域一个作业)。整个作业仅提供建议,不阻塞合并。 | 脚本 | 验证内容 | 是否阻塞 | | ------------------------------- | ------------------------ | ------------------------------------------------------ | @@ -224,33 +227,33 @@ BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift 仅在拉取请求上运行。 -| 脚本 | 验证内容 | 是否阻塞 | -| ---------------------- | ------------------------------------------------------------------------------------------------------ | -------- | -| `check:pr-test-policy` | 修改 `src/`、`open-sse/`、`electron/` 或 `bin/` 中生产代码的 PR 必须包含或更新测试(硬性规则 #8) | 是 | -| `check:test-masking` | 变更后的测试文件不会减少断言净数量,也不会添加 `assert.ok(true)` 这类恒真断言 | 是 | -| `check:pr-evidence` | PR 正文引用该变更的测试/VPS 证据(通过 grep 检索 PR 文本来自动执行硬性规则 #18——较脆弱,参见待办事项) | 是 | +| 脚本 | 验证内容 | 是否阻塞 | +| ---------------------- | -------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | 修改 `src/`、`open-sse/`、`electron/` 或 `bin/` 中生产代码的 PR 必须包含或更新测试(硬性规则 #8) | 是 | +| `check:test-masking` | 变更的测试文件不得减少断言净数量,也不得添加 `assert.ok(true)` 这类恒真断言 | 是 | +| `check:pr-evidence` | PR 正文需列出该变更的测试/VPS 证据(通过 grep PR 文本将硬性规则 #18 机制化——较脆弱,参见待办事项) | 是 | ### 作业:`test-vitest` 在 `build` 之后运行。失败时阻止合并。 -| 测试套件 | 验证内容 | 是否阻塞 | -| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------- | -| `test:vitest` | MCP 服务器(110 个工具)、autoCombo、缓存——vitest 运行器 | 是 | -| `test:vitest:ui` | UI 组件测试——vitest 运行器 | **阻塞**——`vitest.config.ts` 中已明确排除既有失败;新增失败会导致作业失败 | +| 测试套件 | 验证内容 | 是否阻塞 | +| ---------------- | -------------------------------------------------------- | ----------------------------------------------------------------------- | +| `test:vitest` | MCP 服务器(110 个工具)、autoCombo、缓存——vitest 运行器 | 是 | +| `test:vitest:ui` | UI 组件测试——vitest 运行器 | **阻塞**——`vitest.config.ts` 中明确排除了既有失败;新失败会导致作业失败 | -### 每夜工作流(定时运行,建议性) +### 夜间工作流(定时运行、建议性) -这些工作流按 cron 计划运行(也可通过 `workflow_dispatch` 运行),绝不会在 PR 上运行。所有工作流均为建议性。 +这些工作流按 cron 计划运行(也可通过 `workflow_dispatch` 运行),从不在 PR 上运行。它们均为建议性检查。 -| 工作流 | 验证内容 | 是否阻塞 | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `nightly-property` | 使用随机种子和高运行次数的 fast-check 属性测试 | **建议性** | -| `nightly-resilience` | 堆增长门禁、混沌故障注入、k6 负载/浸泡测试 | **建议性** | -| `nightly-llm-security` | promptfoo 注入防护(阻止模式)+ garak 探测(没有提供者密钥时跳过) | **建议性** | -| `nightly-schemathesis` | 使用 `docs/openapi.yaml` 针对实时 OmniRoute 进行 OpenAPI 契约模糊测试(schemathesis)——揭示规范违规/未处理的 500 错误(阶段 8 B.4) | **建议性** | -| `nightly-mutation` | 对快速单元测试通道执行 Stryker 变异测试评分——存活的变异体会暴露薄弱的断言 | **建议性** | -| `nightly-compat` | 跨受支持的 `engines.node` 范围执行 Node 引擎兼容性矩阵 | **建议性** | +| 工作流 | 验证内容 | 是否阻塞 | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `nightly-property` | 使用随机种子和高运行次数的 fast-check 属性测试 | **建议性** | +| `nightly-resilience` | 堆增长门禁、混沌故障注入、k6 负载/浸泡测试 | **建议性** | +| `nightly-llm-security` | promptfoo 注入防护(阻止模式)+ garak 探测(没有提供者密钥时跳过) | **建议性** | +| `nightly-schemathesis` | 使用 `docs/openapi.yaml` 对实时 OmniRoute 执行 OpenAPI 契约模糊测试(schemathesis)——暴露规范违规/未处理的 500 错误(阶段 8 B.4) | **建议性** | +| `nightly-mutation` | 对快速单元测试通道进行 Stryker 变异测试评分——存活的变异体会暴露薄弱的断言 | **建议性** | +| `nightly-compat` | 覆盖受支持 `engines.node` 范围的 Node 引擎兼容性矩阵 | **建议性** | --- @@ -496,42 +499,41 @@ JSON,十秒钟即可完成,也是解除红色 PR 阻塞的最快方式。** --- -## 合理化待办清单(ROI 审查 — 阶段 9 第 3 波) +## 合理化待办清单(ROI 审查 — 第 9 阶段第 3 波) -此清单已于 2026-06-17 根据 `ci.yml` 完成核对(先前版本遗漏了 +此清单已于 2026-06-17 与 `ci.yml` 核对(先前版本遗漏了 `audit:deps`、`check:tracked-artifacts`、`check:lockfile`、`check:licenses`、 `check:dead-code`、`check:cognitive-complexity`、`check:type-coverage`、 -`check:codeql-ratchet`、`check:pr-evidence`)。对核对后集合进行的 ROI 审查 -确定了以下合理化候选项。**合并属于机械性的 CI -变更;启用/移除则属于由操作方决定的策略决策。** 以下内容均尚未实施。 - -**上文还未记录的项目**(仅供参考、信号较弱):`docs-lint` 作业 -(markdownlint + Vale,整个作业设置 `continue-on-error`),以及独立的扫描器工作流 -`semgrep.yml` / `codeql.yml` / `scorecard.yml`。`quality-baseline.json` 中包含 -`semgrepFindings: 0`,但它未与 `ci.yml` 中的阻塞式棘轮机制关联——该指标 -目前处于孤立状态。 - -### 合并 / 去重(机械性操作,风险较低) - -每个候选项均已于 2026-06-17 根据实际门禁状态完成验证(先信任、后核实); -事实证明,若干“显而易见”的合并隐藏了技术债务,**不能**直接安全替换。 - -- **`check:docs-sync` 运行两次**——一次独立运行于 `lint` 作业中,另一次运行于 `check:docs-all`(`docs-sync-strict`)及 husky 的 pre-commit 钩子中。✅ **已完成**——已移除 `lint` 中的独立调用。 -- **CVE 扫描**——❌ **不能直接合并。** `audit:deps` 遇到任何高危/严重 CVE 都会硬性失败;`check:vuln-ratchet`(osv)仅在相较基线出现_回归_时失败(当前为 1 个 MODERATE)。两者语义不同——移除 `audit:deps` 将失去针对高危/严重漏洞的绝对门禁。两者均保留。 -- **循环依赖检测**——❌ **不能直接合并。** `check:circular-deps`(dpdm)报告了 **91 个循环**(这正是它仅作参考的原因);在解决这些循环之前,不能将其提升为阻塞项,而且其范围比当前通过的、经过筛选的 `check:cycles` 更广。保留 `check:cycles` 作为阻塞项;解决 91 个 dpdm 循环应作为单独的待办事项。 -- **复杂度**——✅ **已完成**(`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`):仅执行一次 ESLint 遍历,按 ruleId 计数,因此圈复杂度+最大行数与认知复杂度基线仍保持独立;单独的 `check:complexity` / `check:cognitive-complexity` 仍保留,供本地执行 `--update`。 -- **`/api` 防幻觉检查**——✅ **已完成**(`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`):仅对 `src/app/api` 执行一次文件系统清点,openapi-routes + docs-symbols 仍分别独立报告;单独检查仍保留,供本地运行。 -- **`check:node-runtime` 在 11 个作业中运行**——⚠️ **ROI 较低。** 每个作业都使用独立运行器,且该检查耗时不足 1 秒;总计仅节省约 10 秒,却会失去一个成本低廉的逐作业保护措施。不值得为此折腾。 -- **CI lint 中的 `typecheck:noimplicit:core`**——✅ **已从 lint 作业中移除**(此前通过 `continue-on-error` 设为仅供参考);阻塞式类型覆盖面由 `typecheck:core` + `check:type-coverage` 提供。保留本地脚本。 - -### 启用 / 决策(操作方策略) - -- `check:openapi-security-tiers`(仅供参考)——❌ **无法直接转为阻塞。** 它以状态码 0 退出,但会警告 `LOCAL_ONLY_API_PREFIXES` 下的若干 `traffic-inspector` 路由缺少 `x-loopback-only: true` 注解。强制执行前,必须先向 `openapi.yaml` 添加这些注解。 -- `typecheck:noimplicit:core`(仅供参考)——其作用大部分已被阻塞式 `check:type-coverage` 棘轮机制涵盖。应将其转为棘轮机制,或移除这个重复的第二次 `tsc` 检查。 -- `test:vitest:ui`(现已**阻塞**)——预先存在的失败已在 `vitest.config.ts` 中明确排除,并附有 `// #8618` 跟踪注释;新增失败将导致作业失败。 -- `check:secrets`(gitleaks,阻塞式棘轮机制固定在 3 个已记录的误报)——将这 3 个误报加入允许列表以降至 0,或将其降级为仅供参考。与 GitHub 原生机密扫描及 `check:public-creds` 存在重叠。 -- `check:pr-evidence`(阻塞式,使用 grep 检查 PR 正文)——误报风险较高;若移除,将削弱对硬性规则 #18 的执行,因此这确实需要作出策略决策。 -- `semgrep`(独立运行,仅供参考)——在 OWASP 类别上与 CodeQL 重叠;应将其基线接入棘轮机制,或将其移除。 +`check:codeql-ratchet`、`check:pr-evidence`)。对核对后的集合进行 ROI 审查后, +确定了以下合理化候选项。**合并属于机械性的 CI 变更;切换/移除则属于保留给操作者决定的策略事项。** +以下内容尚未应用。 + +**上文还未记录的项目**(建议性、低信号):`docs-lint` 作业 +(markdownlint + Vale,整个作业设置了 `continue-on-error`),以及独立的扫描器工作流 +`semgrep.yml` / `codeql.yml` / `scorecard.yml`。`semgrepFindings: 0` 位于 +`quality-baseline.json` 中,但尚未连接到 `ci.yml` 中的阻断式棘轮机制——该指标目前处于孤立状态。 + +### 合并/去重(机械性变更,风险较低) + +每个候选项均已于 2026-06-17 根据实际门禁状态进行验证(信任但要核实); +事实证明,若干“显而易见”的合并隐藏了技术债务,因而**不能**直接无缝替换。 + +- **`check:docs-sync` 运行两次**——一次独立运行于 `lint` 作业中,另一次运行于 `check:docs-all`(`docs-sync-strict`)和 husky pre-commit 钩子中。✅ **已完成**——已移除 `lint` 中的独立调用。 +- **CVE 扫描**——❌ **无法干净合并。** `audit:deps` 遇到任何高危/严重 CVE 都会硬失败;`check:vuln-ratchet`(osv)仅在相对于基线出现_回退_时失败(当前为 1 个 MODERATE)。两者语义不同——移除 `audit:deps` 将失去针对高危/严重漏洞的绝对门禁。两者均予以保留。 +- **循环检测**——✅ **已完成**(#15159 G-01/G-02)。此处旧文本将 `check:cycles` 称为“绿色、精心筛选的”门禁,并以 `check:circular-deps`(dpdm)报告 91 个循环为由,主张继续将其作为阻断门禁。这个绿色状态是一个**假绿**:`check:cycles` 仅扫描了 5 个子目录(450 个文件),只匹配静态 `import|export … from`,并丢弃了所有 `@/` 和 `@omniroute/open-sse/` 说明符,因此无法发现仓库中占主导地位的动态导入 + 别名循环。现已修复:该门禁现在遍历 `src` + `open-sse`(5023 个文件),从 TypeScript AST 收集说明符(因此 `import("…")` 会计入,而类型位置中的 `typeof import("…")` 不会计入),并解析 tsconfig `paths`。检测到的是 **14** 个循环,而非 0 个。由于无法在门禁 PR 中修复 14 个既有循环,`check:cycles` 现在采用**棘轮机制**(`--ratchet`,上限为 `quality-baseline.json` 中的 `metrics.cycles.value = 14`,`direction: down`)——它会阻止任何_回退_,且数量只能下降。CI 运行 `npm run check:cycles:ratchet`。消减工作随 **A-01** 一并推进。`check:circular-deps`(dpdm)继续作为范围更广的建议性第二意见。 +- **复杂度**——✅ **已完成**(`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`):一次 ESLint 遍历,按 ruleId 计数,因此圈复杂度 + 最大行数与认知复杂度的基线保持独立;单独的 `check:complexity` / `check:cognitive-complexity` 仍保留,供本地执行 `--update` 使用。 +- **`/api` 反幻觉检查**——✅ **已完成**(`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`):仅对 `src/app/api` 执行一次 FS 清点,openapi-routes + docs-symbols 仍独立报告;单独的检查仍保留,供本地运行。 +- **`check:node-runtime` 在 11 个作业中运行**——⚠️ **ROI 较低。** 每个作业使用独立的运行器,且该检查耗时不到 1 秒;总共节省约 10 秒,却会失去成本低廉的逐作业防护。不值得为此折腾。 +- **CI lint 中的 `typecheck:noimplicit:core`**——✅ **已从 lint 作业中移除**(此前为设置了 `continue-on-error` 的建议性检查);阻断式类型门禁由 `typecheck:core` + `check:type-coverage` 构成。本地脚本予以保留。 + +### 切换/决策(操作者策略) + +- `check:openapi-security-tiers`(建议性)——❌ **无法直接干净地切换。** 它以 0 退出,但会警告 `LOCAL_ONLY_API_PREFIXES` 下的若干 `traffic-inspector` 路由缺少 `x-loopback-only: true` 注解。若要强制执行,必须先将这些注解添加到 `openapi.yaml`。 +- `typecheck:noimplicit:core`(建议性)——已大体被阻断式 `check:type-coverage` 棘轮涵盖。可将其改为棘轮,或移除这个冗余的第二次 `tsc` 执行。 +- `test:vitest:ui`(现为**阻断式**)——既有失败已在 `vitest.config.ts` 中通过 `// #8618` 跟踪注释明确排除;新失败将导致作业失败。 +- `check:secrets`(gitleaks,阻断式棘轮,冻结于 3 个已记录的误报)——将这 3 项加入允许列表以降至 0,或将其降级为建议性检查。它与 GitHub 原生 secret-scanning + `check:public-creds` 重叠。 +- `check:pr-evidence`(阻断式,使用 grep 检查 PR 正文)——误报风险较高;若移除,将削弱对硬性规则 #18 的执行,因此这确实需要策略决策。 +- `semgrep`(独立的建议性检查)——在 OWASP 类别方面与 CodeQL 重叠;将其基线接入棘轮机制,或将其移除。 --- diff --git a/docs/i18n/zh-CN/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/zh-CN/docs/compression/COMPRESSION_GUIDE.md index 57ab9a67b5d5..06b0068e3d65 100644 --- a/docs/i18n/zh-CN/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/zh-CN/docs/compression/COMPRESSION_GUIDE.md @@ -443,8 +443,7 @@ const { messages: aged, saved } = applyAging(messages, { 对于包含 `messages` 的请求体,内容绕过机制( `open-sse/services/compression/outputMode.ts` 中的 `shouldBypassCavemanOutputMode()`)会检查最后三条消息,并在 消息匹配安全性、不可逆操作、澄清或顺序敏感关键词时,跳过整个轮次的样式。 -是否运行该绕过机制取决于仪表板中的 **Auto-Clarity Bypass** 开关 -(`cavemanOutputMode.autoClarity`)设置。 +该绕过机制在仪表板中的 **Auto-Clarity Bypass** 开关(`cavemanOutputMode.autoClarity`)处于开启状态时运行,这也是默认设置;开关关闭时,所选样式同样会应用到这些轮次。 当绕过机制允许该轮次继续时,`placeSystemInstruction()`(同一文件)不会 创建新的 `messages[0]`,而是将该块放置在以下首个符合条件的位置: diff --git a/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md b/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md index 6b68343f90c1..d7ade5fe0d2a 100644 --- a/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/zh-CN/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. 必需的密钥 -这些密钥在首次运行前**必须**设置。缺少这些密钥时,应用程序将拒绝启动,或使用不安全的默认值运行。 - -| 变量 | 是否必需 | 默认值 | 源文件 | 描述 | -| ---------------------------- | ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `JWT_SECRET` | **是** | _(无)_ | `src/lib/auth` | 用于签名/验证所有仪表板会话 Cookie(JWT)。使用 `openssl rand -base64 48` 生成。 | -| `API_KEY_SECRET` | **是** | _(无)_ | `src/lib/db/apiKeys.ts` | 用于对 SQLite 中静态存储的 API 密钥值进行 AES 加密。使用 `openssl rand -hex 32` 生成。 | -| `INITIAL_PASSWORD` | **是** | `CHANGEME` | 引导脚本 | 设置初始的管理员仪表板密码(与 `.env.example` 的默认值一致——故意保持明显不安全,以强制进行更改)。**请在首次使用前更改。**登录后,通过“仪表板 → 设置 → 安全”进行更改。 | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **是**(生产环境) | _(未设置)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 内部 Codex Responses WebSocket 桥接器的共享密钥。用于验证 Electron/浏览器 WS 中继与 OmniRoute 之间的桥接请求。⚠️ **生产环境中必需——未设置时,所有 WS 桥接请求都会被拒绝。**使用 `openssl rand -base64 32` 生成。 | -| `OMNIROUTE_SW_BUILD_ID` | 否 | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA 外壳的显式 Service Worker 缓存失效 ID(#11779);在解析链中优先使用。 | -| `SOURCE_VERSION` | 否 | _(未设置)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | 解析链中的第二项——由 PaaS 构建器(例如 Heroku 风格的构建器)设置为已部署的提交。 | -| `NEXT_PUBLIC_SW_BUILD_ID` | 否 | _(派生值)_ | `src/shared/components/PwaRegister.tsx` | 客户端用于注册 `/sw.js?v=…` 的构建时公开值;依次从上述两个值派生,最后回退到 git SHA。 | -| `OMNIROUTE_PEER_STAMP_TOKEN` | 否(自动) | _(每次启动时自动生成)_ | `src/server/authz/policies/management.ts` | 进程级密钥,用于证明可信的对等端 IP 标记来自 OmniRoute 自身的 HTTP 服务器(`scripts/dev/peer-stamp.mjs`)。仅当该标记包含此令牌时,授权中间件才会信任请求的本地性(对 LOCAL_ONLY 路由进行环回/LAN 限制)。每次启动时自动生成——请保持未设置;仅当必须在多进程设置间共享该标记时才固定此值。 | -| `OMNIROUTE_TRUSTED_PROXIES` | 否 | _(未设置)_ | `scripts/dev/peer-stamp.mjs` | 以逗号分隔的反向代理 IP 或 CIDR 范围;IP 允许/拒绝列表可以信任来自这些代理的 `X-Forwarded-For` / `X-Real-IP`,此外,环回地址、私有网络地址和 Cloudflare 边缘节点始终受信任。仅当代理使用其他公共地址时才需要设置;否则,过滤器将依据该代理自身的地址进行判断。 | +标记为**是**且尚未设置的密钥会在首次运行时自动生成(`JWT_SECRET`、`API_KEY_SECRET`),并持久化到数据目录中的 `server.env`——当该目录为临时目录时,请显式设置它们。例外是 `OMNIROUTE_WS_BRIDGE_SECRET`:它不会持久化到 `server.env`——服务器入口点会在每次启动时生成一个仅进程内桥接器可用的临时值,因此外部桥接客户端需要显式设置它。 + +| 变量 | 是否必需 | 默认值 | 源文件 | 描述 | +| ---------------------------- | ------------------ | ------------------------ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **是** | _(无)_ | `src/lib/auth` | 用于签名/验证所有仪表板会话 Cookie(JWT)。使用 `openssl rand -base64 48` 生成。 | +| `API_KEY_SECRET` | **是** | _(无)_ | `src/lib/db/apiKeys.ts` | 用于对 SQLite 中静态存储的 API 密钥值进行 AES 加密。使用 `openssl rand -hex 32` 生成。 | +| `INITIAL_PASSWORD` | 否 | _(未设置)_ | `src/lib/auth/managementPassword.ts` | 在尚无已保存密码时,于启动时设置仪表板密码;一旦密码已保存即被忽略。npm 与源码安装会将 `.env.example` 复制为 `.env`,因此它们以其公开已知的占位符 `CHANGEME` 启动:请在首次启动前替换它,或在那个 `.env` 中将 `INITIAL_PASSWORD=` 设置为空值,以便在首次访问时于入门向导中创建密码——与未设置该值(Docker 镜像的默认行为)时的效果相同。已保存的密码可在“仪表板 → 设置 → 安全”中更改,或在将 `DATA_DIR` 设置为服务器的数据目录后运行 `omniroute reset-password`(源码检出中为 `node bin/reset-password.mjs`)。 | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **是**(生产环境) | _(未设置)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 内部 Codex Responses WebSocket 桥接器的共享密钥。用于验证 Electron/浏览器 WS 中继与 OmniRoute 之间的桥接请求。⚠️ **生产环境中必需——未设置时,所有 WS 桥接请求都会被拒绝。**使用 `openssl rand -base64 32` 生成。 | +| `OMNIROUTE_SW_BUILD_ID` | 否 | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA 外壳的显式 Service Worker 缓存失效 ID(#11779);在解析链中优先使用。 | +| `SOURCE_VERSION` | 否 | _(未设置)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | 解析链中的第二项——由 PaaS 构建器(例如 Heroku 风格的构建器)设置为已部署的提交。 | +| `NEXT_PUBLIC_SW_BUILD_ID` | 否 | _(派生值)_ | `src/shared/components/PwaRegister.tsx` | 客户端用于注册 `/sw.js?v=…` 的构建时公开值;依次从上述两个值派生,最后回退到 git SHA。 | +| `OMNIROUTE_PEER_STAMP_TOKEN` | 否(自动) | _(每次启动时自动生成)_ | `src/server/authz/policies/management.ts` | 进程级密钥,用于证明可信的对等端 IP 标记来自 OmniRoute 自身的 HTTP 服务器(`scripts/dev/peer-stamp.mjs`)。仅当该标记包含此令牌时,授权中间件才会信任请求的本地性(对 LOCAL_ONLY 路由进行环回/LAN 限制)。每次启动时自动生成——请保持未设置;仅当必须在多进程设置间共享该标记时才固定此值。 | +| `OMNIROUTE_TRUSTED_PROXIES` | 否 | _(未设置)_ | `scripts/dev/peer-stamp.mjs` | 以逗号分隔的反向代理 IP 或 CIDR 范围;IP 允许/拒绝列表可以信任来自这些代理的 `X-Forwarded-For` / `X-Real-IP`,此外,环回地址、私有网络地址和 Cloudflare 边缘节点始终受信任。仅当代理使用其他公共地址时才需要设置;否则,过滤器将依据该代理自身的地址进行判断。 | ### 生成命令 @@ -214,38 +214,38 @@ OmniRoute 使用 **SQLite**(通过 `better-sqlite3`)实现所有持久化功 ## 4. 安全与身份验证 -| 变量 | 默认值 | 源文件 | 说明 | -| ------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | 与硬件标识符组合使用的盐值,用于生成机器指纹。应在每次部署时更改,以实现隔离。 | -| `OMNIROUTE_CLI_SALT` | _(未设置 = 每次安装时随机生成盐值,并持久化至 `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | 用于派生本地 CLI 身份验证令牌的 HMAC 盐值。设置此值会轮换该机器上的所有 CLI 令牌,并且其优先级始终高于持久化的盐值。请参阅 `docs/security/CLI_TOKEN.md`。 | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | 设置会话 Cookie 的 `Secure` 标志。在 HTTPS 后方运行时,**必须设为 `true`**。 | -| `REQUIRE_API_KEY` | `false` | API 中间件 | 当设为 `true` 时,所有 `/v1/*` 代理请求都必须包含有效的 API 密钥。**此标志不控制 `GET /v1/models`**;后者改为遵循仪表板登录策略(`requireAuthForModels`)——因此,`/v1/models` 返回 `401` 并不意味着推理已受到保护。请参阅 `docs/security/INFERENCE_AUTH_POSTURE.md`(#13695)。 | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允许在仪表板 UI 中显示完整的 API 密钥值。可通过仪表板的功能标志进行配置;在共享实例上存在安全风险。 | -| `NO_LOG_API_KEY_IDS` | _(空)_ | `src/lib/compliance/index.ts` | 以逗号分隔的 API 密钥 ID,这些密钥的请求不会被记录(用于 GDPR 合规)。 | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(未设置 = 无限制)_ | `src/shared/utils/apiKeyPolicy.ts` | 应用于 `rate_limits` 列为 null 的 API 密钥的每日备用请求配额。未设置或为空:不设隐式上限(#2289、#11017)。`0` 同样表示无限制。正整数 N 表示每天 N 次、每周 5N 次、每月 20N 次。格式错误的非空值将回退到旧版限制:每天 1000 次、每周 5000 次、每月 20000 次。 | -| `MAX_BODY_SIZE_BYTES` | `10485760`(10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 允许的最大请求正文大小。超过此限制的载荷将被拒绝。 | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144`(256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | 实际请求正文达到或超过此阈值时,会在 JSON 解析之前获取原子的、进程本地的重量级准入租约(BYTE 路径,包括 `POST /v1/responses`)。采用与结构复杂请求相同的 [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) 健康余量逃生机制;同时仍受 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) 限制,从而避免 [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) 问题再次出现。 | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | 聊天路由的硬上限,根据受限摄取期间读取的字节数强制执行,包括缺失、无效或虚报 `Content-Length` 的请求;超出限制时返回 `413`。 | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(未设置 — 无请求数量上限)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503 扇出:**此旧版请求数量上限现在仅在显式设置时生效。保持未设置(默认值)时,重量级聊天准入改由 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` 限制——这是在**单个进程**(单个 V8 堆)中,根据进程的实际内存上限自动推导的字节预算。两个重叠的约 75 万 token 的 `/v1/responses` 会使约 12 GiB 的堆崩溃(#7849)——这是**内存预算**警告,并非产品的硬性最大并发数为 2。健康进程(堆低于卸载比率)可通过 `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` 接纳更多并发的长 `/v1/responses`。数十个长 SSE 客户端(40–50 个)的限制取决于堆 + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110,而不是“最多 2 个”。盲目提高此值以“用满主机”会重新引发 #7849。应通过 **N 个独立的 `DATA_DIR`** 增加**堆**的数量(#11024);切勿让一个 SQLite 文件使用 `replicas>1`。 | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(自动推导)_ | `src/shared/middleware/admissionBudget.ts` | **#503 扇出:**用于覆盖自动推导的摄取字节预算(V8/cgroup 两者中较严格的内存上限的 25%,再除以 8 倍的瞬时放大系数)。推导值和显式值均限制在 8 MiB–2 GiB 范围内。大于有效预算的请求体会立即以 `413 body_exceeds_budget` 失败;各自可被处理的请求体之间发生争用时,仍返回可重试的 `503`。40–50 个并发长 SSE 客户端的限制取决于此预算 + 堆,而不是硬性的“最多 2 个”。调整前,请在 `/api/monitoring/health` 中查看 `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`。 | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | 用于字节和结构型重量级准入的堆压力卸载比率(`heapUsed / heap_size_limit`)(#10183、#10268、#10437)。只有当堆同时达到或超过此比率时,超过 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 的并发重量级请求才会被卸载,并返回可重试的 `503`;在堆健康时,则通过健康余量路径接纳。 | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`(默认值为 `1`) | `src/shared/middleware/chatBodyAdmission.ts` | 为结构和字节(`admitChatRequest`,包括请求体 ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`)两种类型的健康堆快速路径(#10437)提供有界的额外容量。若无此边界,每个繁忙但堆健康的请求都能绕过准入,且没有上限。一旦通过健康堆路径激活的并发租约达到此数量,后续繁忙请求将转入与实际堆压力下相同的有界等待/卸载路径。`0` 会完全禁用绕过机制。 | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | 即使请求体低于字节阈值,也会将聊天请求归类为重量级请求的消息数量。 | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | 即使请求体低于字节阈值,也会将聊天请求归类为重量级请求的工具数量。 | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | 用于将请求归类为重量级请求的保守字符串大小 token 估算值;这是准入成本的代理指标,而不是提供者计费用的 token 化方式。 | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0`(已禁用) | `src/shared/middleware/chatBodyAdmission.ts` | 可选启用的聊天历史记录上限。默认禁用:消息数量属于部署策略,而不是请求的通用属性;在此处设置上限会在压缩管道能够使对话变得可处理之前,就以终止性的 `413` 拒绝对话。堆增长由 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 和堆压力卸载机制限制。对于需要硬性上限的内存受限部署,可将其设置为正值;超出限制时,将返回结构化的“需要压缩”`413`。 | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | 完整缓冲到内存中的非流式上游响应的硬性上限。超过此上限时,将取消上游读取器并使请求快速失败,避免字符串无限增长直至耗尽堆。 | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | 从上游响应标头转发的最大传输字节数。超出预算时,将丢弃优先级较低的标头(例如自定义 `x-codex-*`、`x-oai-request-id`),以保持在常见反向代理的标头限制范围内。可将其设置得更高,以转发更多上游元数据,但会增大响应标头的大小。 | -| `CORS_ORIGIN` | _(未设置)_ | `src/server/cors/origins.ts` | 旧版单源 CORS 允许列表。对于新部署,建议使用 `CORS_ALLOWED_ORIGINS`。CORS 仅适用于跨源浏览器 API 客户端;经过身份验证的仪表板写入操作改为使用同源请求以及与会话绑定的 CSRF 防护。 | -| `CORS_ALLOWED_ORIGINS` | _(未设置)_ | `src/server/cors/origins.ts` | 以逗号分隔的 CORS 允许列表。除非明确配置 `CORS_ALLOW_ALL=true`,否则不会发送通配符。 | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 仅用于开发环境的应急选项,可回显任何浏览器 `Origin`。请勿在共享部署或生产部署中启用。 | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | 阻止以私有、环回或链路本地 IP 范围为目标的提供者调用。仅可在隔离的测试环境中禁用。 | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | 允许指向私有/本地网络(localhost、192.168.x.x、10.x.x.x 等)的提供者 URL。**自托管提供者必须启用此项**(LM Studio、Ollama、vLLM、Llamafile、Triton、SearXNG)。当值为 `false` 时,仪表板会拒绝验证本地 URL。 | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | 允许添加/验证位于本地/私有地址(127.0.0.1、localhost、LAN、私有地址范围)的提供者——仅限提供者验证路径。**默认值为 `true`**(本地优先);设置为 `false` 可强制执行仅允许公共地址的严格阻止策略。无论如何,云元数据端点(169.254.169.254、metadata.google.internal)始终会被阻止。 (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | 允许 `/v1/audio/*` 路由(转录、语音、翻译)使用托管在 localhost 之外且兼容 OpenAI 的提供者节点。默认关闭——将音频路由到远程主机会改变出站身份,因此必须由运维人员明确决定是否启用。环回/私有节点(localhost、127.0.0.1、172.16-31.x)始终允许使用且不受影响。 (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | 允许 `POST /v1/rerank`(以及内存引擎的环回重排序步骤)使用托管在 localhost 之外且兼容 OpenAI 的提供者节点——例如运行 TEI、Infinity、vLLM 等的 LAN 主机或 Tailscale 对等节点。默认关闭——路由到远程主机会改变出站身份,因此必须由运维人员明确决定是否启用。环回节点(localhost、127.0.0.1、172.16-31.x)始终允许使用且不受影响。远程节点还必须通过提供者出站 URL 策略(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`);请求绝不会被路由到云元数据主机。 | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | 启用 OIDC 后,禁用密码登录,使用户只能通过 OIDC 单点登录进行身份验证。也接受简短别名 `OIDC_DISABLE_PASSWORD_LOGIN`;同名的仪表板功能标志具有更高优先级。 (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` 的简短别名 (#10889)。 | +| 变量 | 默认值 | 源文件 | 说明 | +| ------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | 与硬件标识符组合使用的盐值,用于生成机器指纹。应在每次部署时更改,以实现隔离。 | +| `OMNIROUTE_CLI_SALT` | _(未设置 = 每次安装时随机生成盐值,并持久化至 `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | 用于派生本地 CLI 身份验证令牌的 HMAC 盐值。设置此值会轮换该机器上的所有 CLI 令牌,并且其优先级始终高于持久化的盐值。请参阅 `docs/security/CLI_TOKEN.md`。 | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | 设置会话 Cookie 的 `Secure` 标志。在 HTTPS 后方运行时,**必须设为 `true`**。 | +| `REQUIRE_API_KEY` | `false` | API 中间件 | 当设为 `true` 时,所有 `/v1/*` 代理请求都必须包含有效的 API 密钥。**此标志不控制 `GET /v1/models`**;后者改为遵循仪表板登录策略(`requireAuthForModels`)——因此,`/v1/models` 返回 `401` 并不意味着推理已受到保护。请参阅 `docs/security/INFERENCE_AUTH_POSTURE.md`(#13695)。 | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允许在仪表板 UI 中显示完整的 API 密钥值。可通过仪表板的功能标志进行配置;在共享实例上存在安全风险。 | +| `NO_LOG_API_KEY_IDS` | _(空)_ | `src/lib/compliance/index.ts` | 以逗号分隔的 API 密钥 ID,这些密钥的请求不会被记录(用于 GDPR 合规)。 | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(未设置 = 无限制)_ | `src/shared/utils/apiKeyPolicy.ts` | 应用于 `rate_limits` 列为 null 的 API 密钥的每日备用请求配额。未设置或为空:不设隐式上限(#2289、#11017)。`0` 同样表示无限制。正整数 N 表示每天 N 次、每周 5N 次、每月 20N 次。格式错误的非空值将回退到旧版限制:每天 1000 次、每周 5000 次、每月 20000 次。 | +| `MAX_BODY_SIZE_BYTES` | `10485760`(10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 允许的最大请求正文大小。超过此限制的载荷将被拒绝。 | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144`(256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | 实际请求正文达到或超过此阈值时,会在 JSON 解析之前获取原子的、进程本地的重量级准入租约(BYTE 路径,包括 `POST /v1/responses`)。采用与结构复杂请求相同的 [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) 健康余量逃生机制;同时仍受 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) 限制,从而避免 [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) 问题再次出现。 | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | 聊天路由的硬上限,根据受限摄取期间读取的字节数强制执行,包括缺失、无效或虚报 `Content-Length` 的请求;超出限制时返回 `413`。 | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(未设置 — 无请求数量上限)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503 扇出:**此旧版请求数量上限现在仅在显式设置时生效。保持未设置(默认值)时,重量级聊天准入改由 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` 限制——这是在**单个进程**(单个 V8 堆)中,根据进程的实际内存上限自动推导的字节预算。两个重叠的约 75 万 token 的 `/v1/responses` 会使约 12 GiB 的堆崩溃(#7849)——这是**内存预算**警告,并非产品的硬性最大并发数为 2。健康进程(堆低于卸载比率)可通过 `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` 接纳更多并发的长 `/v1/responses`。数十个长 SSE 客户端(40–50 个)的限制取决于堆 + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110,而不是“最多 2 个”。盲目提高此值以“用满主机”会重新引发 #7849。应通过 **N 个独立的 `DATA_DIR`** 增加**堆**的数量(#11024);切勿让一个 SQLite 文件使用 `replicas>1`。 | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(自动推导)_ | `src/shared/middleware/admissionBudget.ts` | **#503 扇出:**用于覆盖自动推导的摄取字节预算(V8/cgroup 两者中较严格的内存上限的 25%,再除以 8 倍的瞬时放大系数)。推导值和显式值均限制在 8 MiB–2 GiB 范围内。大于有效预算的请求体会立即以 `413 body_exceeds_budget` 失败;各自可被处理的请求体之间发生争用时,仍返回可重试的 `503`。40–50 个并发长 SSE 客户端的限制取决于此预算 + 堆,而不是硬性的“最多 2 个”。调整前,请在 `/api/monitoring/health` 中查看 `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`。 | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | 用于字节和结构型重量级准入的堆压力卸载比率(`heapUsed / heap_size_limit`)(#10183、#10268、#10437)。只有当堆同时达到或超过此比率时,超过 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 的并发重量级请求才会被卸载,并返回可重试的 `503`;在堆健康时,则通过健康余量路径接纳。 | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT`(默认值为 `1`) | `src/shared/middleware/chatBodyAdmission.ts` | 为结构和字节(`admitChatRequest`,包括请求体 ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`)两种类型的健康堆快速路径(#10437)提供有界的额外容量。若无此边界,每个繁忙但堆健康的请求都能绕过准入,且没有上限。一旦通过健康堆路径激活的并发租约达到此数量,后续繁忙请求将转入与实际堆压力下相同的有界等待/卸载路径。`0` 会完全禁用绕过机制。 | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | 即使请求体低于字节阈值,也会将聊天请求归类为重量级请求的消息数量。 | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | 即使请求体低于字节阈值,也会将聊天请求归类为重量级请求的工具数量。 | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | 用于将请求归类为重量级请求的保守字符串大小 token 估算值;这是准入成本的代理指标,而不是提供者计费用的 token 化方式。 | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0`(已禁用) | `src/shared/middleware/chatBodyAdmission.ts` | 可选启用的聊天历史记录上限。默认禁用:消息数量属于部署策略,而不是请求的通用属性;在此处设置上限会在压缩管道能够使对话变得可处理之前,就以终止性的 `413` 拒绝对话。堆增长由 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 和堆压力卸载机制限制。对于需要硬性上限的内存受限部署,可将其设置为正值;超出限制时,将返回结构化的“需要压缩”`413`。 | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | 完整缓冲到内存中的非流式上游响应的硬性上限。超过此上限时,将取消上游读取器并使请求快速失败,避免字符串无限增长直至耗尽堆。 | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | 从上游响应标头转发的最大传输字节数。超出预算时,将丢弃优先级较低的标头(例如自定义 `x-codex-*`、`x-oai-request-id`),以保持在常见反向代理的标头限制范围内。可将其设置得更高,以转发更多上游元数据,但会增大响应标头的大小。 | +| `CORS_ORIGIN` | _(未设置)_ | `src/server/cors/origins.ts` | 旧版单源 CORS 允许列表。对于新部署,建议使用 `CORS_ALLOWED_ORIGINS`。CORS 仅适用于跨源浏览器 API 客户端;经过身份验证的仪表板写入操作改为使用同源请求以及与会话绑定的 CSRF 防护。 | +| `CORS_ALLOWED_ORIGINS` | _(未设置)_ | `src/server/cors/origins.ts` | 以逗号分隔的 CORS 允许列表。除非明确配置 `CORS_ALLOW_ALL=true`,否则不会发送通配符。 | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 仅用于开发环境的应急选项,可回显任何浏览器 `Origin`。请勿在共享部署或生产部署中启用。 | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | 旧别名。其仪表板开关(一种数据库覆盖值)会先于此环境变量被读取;两者中任一为 `false`、`0`、`no` 或 `off` 时,都会以 `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` 的方式关闭主机检查——即使该标志的仪表板开关已保存为关闭也不例外;其他值则将防护交给下面两个标志处理。 | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | 在遵循防护模式的提供者路径上关闭出站 URL 防护的主机检查,包括云元数据阻止(整个 169.254.0.0/16、metadata.google.internal):URL 验证、模型发现、提供者节点基础 URL(包括远程重排序节点)和代理回退测试,以及其他与提供者相关的出站路径,例如远程图片/媒体获取。它还允许私有 webhook 目标,但云元数据/链路本地端点除外——即使开启此标志,它们仍会被阻止。通过 `BaseExecutor.execute()` 分发的聊天请求仍保留其云元数据检查。本地提供者在默认配置下即可工作:`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS`(默认 `true`)已允许在验证、发现和提供者节点路径上使用本地和 LAN URL;代理回退测试与私有 webhook 目标仅参考此标志,并在其关闭时阻止本地/LAN 主机。覆盖 `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`。从仪表板开关保存的值优先于此变量。 | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | 允许在本地/私有地址(127.0.0.1、localhost、LAN、私有地址范围)上使用提供者 URL,适用于提供者验证、模型发现、聊天以及防护模式管辖的其他与提供者相关的出站路径。**默认值为 `true`**(本地优先):此时防护会阻止云元数据端点——整个 169.254.0.0/16 以及已知的元数据主机名(metadata.google.internal、metadata.goog、Alibaba/Azure/Oracle IMDS、fd00:ec2::254)。设置为 `false` 可进入仅公共模式,该模式还会阻止私有和环回主机。防护在执行任何 DNS 查找之前,会按书写形式检查主机名或 IP 字面量;在仅公共模式下,safe-fetch 包装器还会额外解析主机名并拒绝任何私有应答。`OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` 会在其所在行列出的路径上关闭这些检查。内置的本地提供者(如 LM Studio、Ollama、vLLM、Llamafile 和 Triton)在密钥验证和聊天时会跳过所有主机检查。从仪表板开关保存的值优先于此变量。(#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | 允许 `/v1/audio/*` 路由(转录、语音、翻译)使用托管在 localhost 之外且兼容 OpenAI 的提供者节点。默认关闭——将音频路由到远程主机会改变出站身份,因此必须由运维人员明确决定是否启用。环回/私有节点(localhost、127.0.0.1、172.16-31.x)始终允许使用且不受影响。 (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | 允许 `POST /v1/rerank`(以及内存引擎的环回重排序步骤)使用托管在 localhost 之外且兼容 OpenAI 的提供者节点——例如运行 TEI、Infinity、vLLM 等的 LAN 主机或 Tailscale 对等节点。默认关闭——路由到远程主机会改变出站身份,因此必须由运维人员明确决定是否启用。环回节点(localhost、127.0.0.1、172.16-31.x)始终允许使用且不受影响。远程节点的基础 URL 还必须通过提供者出站 URL 策略(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)。 | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | 启用 OIDC 后,禁用密码登录,使用户只能通过 OIDC 单点登录进行身份验证。也接受简短别名 `OIDC_DISABLE_PASSWORD_LOGIN`;同名的仪表板功能标志具有更高优先级。 (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` 的简短别名 (#10889)。 | ### 加固检查清单 diff --git a/docs/i18n/zh-TW/CONTRIBUTING.md b/docs/i18n/zh-TW/CONTRIBUTING.md index 734d1868adf8..2dd7ba08074f 100644 --- a/docs/i18n/zh-TW/CONTRIBUTING.md +++ b/docs/i18n/zh-TW/CONTRIBUTING.md @@ -393,22 +393,22 @@ docs/ --- -## 提取要求檢查清單 +## Pull Request 檢查清單 - [ ] 測試通過(`npm test`) -- [ ] Linting 通過(`npm run lint`) +- [ ] Lint 檢查通過(`npm run lint`) - [ ] 建置成功(`npm run build`) -- [ ] 為新的公開函式與介面新增 TypeScript 類型 -- [ ] 沒有硬式編碼的機密或後備值 -- [ ] 公開上游認證資訊透過 `resolvePublicCred()` 嵌入(請參閱 [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)),絕不以常值形式 -- [ ] 錯誤回應會經過 `buildErrorBody()` / `sanitizeErrorMessage()` — 回應主體中沒有原始的堆疊追蹤(請參閱 [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) -- [ ] Shell 指令(`exec` / `spawn`)透過 `env` 傳遞執行階段值,而非透過字串插補 -- [ ] 所有輸入都使用 Zod schema 驗證 -- [ ] Changelog **片段** 新增於 `changelog.d/{features|fixes|maintenance}/-.md` 下,用於使用者可見的變更(請參閱 [`changelog.d/README.md`](./changelog.d/README.md))— **請勿**直接編輯 `CHANGELOG.md`;片段會在發佈時彙總,且永遠不會在 PR 之間發生衝突 -- [ ] 文件已更新(如適用) -- [ ] 沒有開啟新的 CodeQL / Secret-Scanning 警示,或者每個都已使用技術理由駁回,並參照相關的 `docs/security/` 文件 -- [ ] 會生成子處理程序的路由(`/api/mcp/`, `/api/cli-tools/runtime/`)在 `src/server/authz/routeGuard.ts` 中被歸類為 `isLocalOnlyPath()` — 請參閱 [硬性規則 #15](docs/security/ROUTE_GUARD_TIERS.md) -- [ ] 提交訊息中沒有 `Co-Authored-By` 尾標 — 提交必須僅以儲存庫擁有者的 Git 身分出現(硬性規則 #16) +- [ ] 已為新的公開函式與介面新增 TypeScript 類型 +- [ ] 沒有硬編碼的密鑰或備援值 +- [ ] 公開的上游憑證透過 `resolvePublicCred()` 嵌入(請參閱 [`docs/security/PUBLIC_CREDS.md`](./docs/security/PUBLIC_CREDS.md)),絕不以字面值形式嵌入 +- [ ] 錯誤回應透過 `buildErrorBody()` / `sanitizeErrorMessage()` 處理——回應主體中不得包含原始堆疊追蹤(請參閱 [`docs/security/ERROR_SANITIZATION.md`](./docs/security/ERROR_SANITIZATION.md)) +- [ ] Shell 命令(`exec` / `spawn`)透過 `env` 傳遞執行階段值,而非使用字串插值 +- [ ] 所有輸入均使用 Zod schema 進行驗證 +- [ ] 若有面向使用者的變更,已在 `changelog.d/{features|fixes|maintenance}/-.md` 下新增變更日誌**片段**(請參閱 [`changelog.d/README.md`](./changelog.d/README.md))——請**勿**直接編輯 `CHANGELOG.md`;片段會在發布時彙整,且各 PR 之間永遠不會發生衝突 +- [ ] 已更新文件(如適用) +- [ ] 未新增任何 CodeQL / Secret-Scanning 警示,或每個警示均已附上引用相關 `docs/security/` 文件的技術理由並予以駁回 +- [ ] 會產生子行程的路由(`/api/mcp/`、`/api/cli-tools/runtime/`)已在 `src/server/authz/routeGuard.ts` 中分類為 `isLocalOnlyPath()`——請參閱[硬性規則 #15](docs/security/ROUTE_GUARD_TIERS.md) +- [ ] 提交訊息中不得包含 AI/機器人的 `Co-authored-by` 尾註(硬性規則 #16)——若重用了人類協作者的工作,應使用標準的 `Co-authored-by: Name ` 尾註予以署名 --- diff --git a/docs/i18n/zh-TW/docs/architecture/QUALITY_GATES.md b/docs/i18n/zh-TW/docs/architecture/QUALITY_GATES.md index b6a24c356c9a..402cca1f712b 100644 --- a/docs/i18n/zh-TW/docs/architecture/QUALITY_GATES.md +++ b/docs/i18n/zh-TW/docs/architecture/QUALITY_GATES.md @@ -17,202 +17,199 @@ ## 閘門清單與執行設定檔 -### 候選版本准入 +### 候選項目准入 -CI 與 Quality Gates 工作流程各自產生穩定的判定結果:`Gate / CI` 與 -`Gate / Quality`。其版本化准入政策會將每個上游作業列為必要或建議項目。每個適用的 -必要作業都必須成功:缺失、已取消、已略過、待處理及未知的結果皆無法確立為 PASS。 -有效的僅文件或僅目錄分類,可使程式碼執行路徑不適用;草稿 PR 不會被接受為候選版本。 -`hotfix` 標籤不會免除證據要求。 +CI 與 Quality Gates 工作流程各自產生一個穩定的判定結果:`Gate / CI` 與 +`Gate / Quality`。其版本化的准入政策會列舉每個上游工作,並將其標示為必要或建議。所有適用的必要工作都必須成功:缺失、 +已取消、已跳過、待處理及未知結果皆無法確立 PASS。有效的僅文件或僅目錄分類可使程式碼執行線不適用; +草稿 PR 不會被視為可接受的候選項目。`hotfix` 標籤不會免除證據要求。 -這兩個工作流程涵蓋 PR、推送至 main/release 分支、手動分派及 -合併群組事件。推送、分派及合併群組會執行完整選項。分支與合併群組會對原本選擇 -自架執行器的作業改用託管執行器;推出前必須確認有足夠的託管容量。 +這兩個工作流程皆涵蓋 PR、推送至 main/release 分支、手動分派及 +merge-group 事件。推送、分派及 merge-group 會執行完整選項。Fork +與 merge group 會對原本選用自託管執行器的工作改用託管執行器;在推出前,必須確認託管容量充足。 -每份 JSON 收據都會識別簽出的 SHA、工作流程執行及嘗試次數。 -CLI 會拒絕簽出 SHA 與事件 SHA 不符的情況。工作流程測試會將政策成員資格繫結至 -判定作業的 `needs` 清單,使新增或移除的執行路徑無法在未被察覺的情況下消失。 -收據涵蓋其自身工作流程,而不涵蓋發布、部署或現有建議性掃描器的內部作業。 -在分支規則中啟用這兩個檢查名稱屬於另一項獨立的管理變更;加入這些作業本身並不會保護分支。 +每份 JSON 收據都會識別已簽出的 SHA、工作流程執行及嘗試次數。 +CLI 會拒絕簽出 SHA 與事件 SHA 不相符的情況。工作流程測試會將政策成員資格 +繫結至判定工作的 `needs` 清單,使新增或移除的執行線無法悄然消失。 +這些收據僅涵蓋其自身的工作流程,不涵蓋發布、部署或現有建議性掃描器的內部運作。 +在分支規則中啟用這兩個檢查名稱是另一項獨立的管理變更;新增這些工作本身並不會保護分支。 ### 靜態掃描清單 版本化的 npm 別名清單與靜態掃描成員資格位於 -`config/quality/gate-manifest.json`。執行 `npm run check:gate-manifest`,以根據 -`package.json` 驗證指令碼名稱與確切命令;任何新增、移除或命令偏移,都會使本機掛鉤及 -CI 中的變更分類作業失敗。別名並不是工作流程作業、矩陣執行個體或測試案例:不得將這些數量 -視為可互換的數據。 +`config/quality/gate-manifest.json`。執行 `npm run check:gate-manifest`,以依據 +`package.json` 驗證指令碼名稱與確切命令;新增、移除及命令漂移都會使本機掛鉤與 CI 中的變更分類工作失敗。 +別名不是工作流程工作、矩陣執行個體或測試案例:這些數量不得被描述為可互換。 使用 `npm run quality:scan -- --list` 或 `npm run quality:scan:fast -- --list` -可在不執行的情況下檢查所選別名。執行器會叫用 npm 進入點,因此會保留其執行階段 -(包括已設定使用 Bun 的情況)。資訊清單會將這些設定檔以外的別名記錄為另行叫用, -而唯讀掃描設定檔中禁止使用維護命令。 - -這些設定檔僅涵蓋靜態掃描。它們不會驗證產品測試、涵蓋率、封裝、外部檢查或 -候選版本的完整發布驗收。工作流程准入使用連結的 -`config/quality/admission-policy.json` 與 -`scripts/quality/admission-verdict.mjs`。發布觀察器設定檔仍保持獨立; -請個別檢查其適用的檢查項目與收據。下方的文字清單僅供參考,不能證明閘門實際上已執行。 - -指令碼位於 `scripts/check/`(政策閘門)與 `scripts/quality/`(棘輪引擎)下。 +可在不執行的情況下檢查所選別名。執行器會呼叫 +npm 進入點,因此會保留其執行階段(包括已設定使用 Bun 的情況)。 +資訊清單會將這些設定檔之外的別名記錄為個別呼叫項目,且唯讀掃描設定檔中禁止使用維護命令。 + +這些設定檔僅涵蓋靜態掃描。它們不會認證產品測試、 +覆蓋率、封裝、外部檢查或候選項目的完整發布驗收。 +工作流程准入使用連結的 `config/quality/admission-policy.json` 與 +`scripts/quality/admission-verdict.mjs`。發布觀察器設定檔維持獨立; +請分別檢查其適用檢查與收據。下方的文字 +清單僅供參考,不能證明閘門實際執行過。 + +指令碼位於 `scripts/check/`(政策閘門)與 `scripts/quality/`(棘輪引擎)之下。 CI 的單一事實來源為 `.github/workflows/ci.yml`。 ### 發布 PR 快速路徑(`quality.yml`) -`.github/workflows/quality.yml` 在 main/release PR、受保護分支推送、 -分派及合併群組方面補充 CI。PR 會使用依路徑篩選的快速檢查。已移除永久停用的重複建置; -實際的建置/封裝/啟動檢查仍保留在 CI 中。 +`.github/workflows/quality.yml` 在 main/release PR、受保護分支 +推送、分派及 merge group 上補充 CI。PR 使用依路徑篩選的快速檢查。永久 +停用的重複建置已移除;實際的建置/封裝/啟動檢查仍保留在 CI 中。 -| 作業 | 範圍 | 阻擋性 | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------ | -| `Docs Gates (fast-path)` | 文件/程式碼 PR;API 文件參照及所有文件 | 是 | -| `Fast Quality Gates` | 程式碼 PR;靜態檢查、類型檢查、儀表板類型檢查、受影響的單元測試 | 是 | -| `Forgotten sibling tests` | 程式碼 PR;追蹤已變更模組的靜態取用端及候選同層測試;桶狀匯出與動態匯入路徑會回報為建議性診斷,並附上所參照的允許清單例外項目 | **建議性** | -| `Vitest (fast-path)` | 程式碼 PR;快速 vitest 測試套件 | 是 | -| `Unit Tests fast-path` | 程式碼 PR;4 分片單元測試套件 | 是 | -| `No new ESLint warnings` | 程式碼 PR;可辨識抑制設定的 lint 防護 | 是,包括分支 | -| `Merge integrity (changelog + generated skills)` | 非草稿 PR;變更記錄與產生技能的同步 | 是,包括分支 | +| 工作 | 範圍 | 阻擋性 | +| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `Docs Gates (fast-path)` | 文件/程式碼 PR;API 文件參照與全部文件 | 是 | +| `Fast Quality Gates` | 程式碼 PR;靜態檢查、類型檢查、儀表板類型檢查、受影響的單元測試 | 是 | +| `Forgotten sibling tests` | 程式碼 PR;將變更的模組追蹤至靜態使用端與候選同層測試;彙總模組與動態匯入路徑會回報為建議性診斷,並附帶所參照的允許清單例外 | **建議性** | +| `Vitest (fast-path)` | 程式碼 PR;快速 vitest 測試套件 | 是 | +| `Unit Tests fast-path` | 程式碼 PR;4 分片單元測試套件 | 是 | +| `No new ESLint warnings` | 程式碼 PR;可感知抑制設定的 lint 防護 | 是,包括 fork | +| `Merge integrity (changelog + generated skills)` | 非草稿 PR;變更日誌與產生的技能同步 | 是,包括 fork | #### 遺漏的同層測試報告 `npm run check:forgotten-sibling-tests` 會重複使用測試影響對應表背後的匯入解析器。 -對於每個已變更的正式環境模組,若候選測試未包含在提取要求的差異中,它會回報具確定性的 -`已變更模組/符號 -> 靜態取用端 -> 候選同層測試` 鏈結。Markdown 摘要與 JSON 結果會保留為 +對於每個已變更的正式環境模組,當候選測試不存在於 pull request 差異中時,它會回報具確定性的 +`變更的模組/符號 -> 靜態使用端 -> 候選同層測試` 鏈結。Markdown 摘要與 JSON 結果會保留為 `forgotten-sibling-tests` 工作流程成品,以便在任何阻擋性推出之前進行校準。 -Barrel 重新匯出與動態匯入僅用於解析診斷;它們絕不會產生阻擋性發現。 -已審核的例外位於 -`config/quality/forgotten-sibling-allowlist.json`。每個項目都必須指定使用方與候選 -測試、提供具體理由,並連結至 GitHub issue 或 pull request。格式錯誤的項目會以 -封閉方式失敗。例外無法抑制已刪除的候選測試,或新增 `.skip`/`.todo` 的差異; -弱化斷言與其他遮蔽行為仍由獨立阻擋的 +Barrel 重新匯出與動態匯入僅作為解析診斷;它們絕不會產生 +阻擋性發現。經審查的例外位於 +`config/quality/forgotten-sibling-allowlist.json`。每個項目都必須指明取用端與候選 +測試、提供具體理由,並連結至 GitHub issue 或 pull request。格式錯誤的項目會採取 +封閉式失敗。例外無法略過已刪除的候選測試,亦無法略過新增 `.skip`/`.todo` 的差異; +弱化斷言及其他遮蔽行為仍由獨立且具阻擋性的 `check:test-masking` 閘門負責。 ### 工作:`lint` -在每個提交至 `main` 的 PR 上執行。失敗時阻擋合併。 - -| 指令碼 (`npm run ...`) | 驗證內容 | 是否阻擋 | -| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -| `check:node-runtime` | Node.js 版本位於支援範圍內 | 是 | -| `check:cycles` | 循環匯入 — 所有 `src/` + `open-sse/` 模組 | 是 | -| `check:route-validation:t06` | 所有路由皆存在 Zod schema(Tier 6 政策) | 是 | -| `check:any-budget:t11` | `@ts-expect-error // any` 的數量未超過預算(Tier 11 catraca) | 是 | -| `check:provider-consistency` | `providers.ts` 中的每個提供者在 `providerRegistry.ts` 中都有相符的項目(反之亦然,但僅限允許清單內) | 是 | -| `check:model-lifecycle` | 三個手動維護的路由表會與簽入版本控制的生命週期快照(#11503)保持一致:`FITNESS_TABLE`(`taskFitness.ts`)不會為任何 `REGISTRY` 可路由的已退役 id 評分;每個 `BUILT_IN_ALIASES` 目標都存在於 `REGISTRY` 中,且不在已退役 id 快照中;`REGISTRY` 中仍存在的每個已退役 id 都會被轉送,或列於 `allowedRetiredInCatalog` 中;而且任何 `DEFAULT_DEGRADATION_MAP` 的來源或目標都不會在該快照中顯示為已退役。這無法證明某個模型目前仍由即時上游服務提供。離線作業 — 與 `config/quality/model-lifecycle.json` 比較;該檔案需使用 `npm run quality:refresh-model-lifecycle` 手動重新整理(需要網路;未接入 CI)。`allowedRetiredInCatalog` 是逐步清零的棘輪機制:只有在有追蹤議題時才能新增項目。 | 是 | -| `check:fetch-targets` | 用戶端 `src/` 中的每個 `fetch("/api/...")` 都會解析至實際存在的 `route.ts` | 是 | -| `check:deps` | 儲存庫中每個 `package.json` 內所有可透過 `npm install` 安裝的相依套件,都必須列於 `dependency-allowlist.json` 中;新增的未鎖定版本或疑似名稱搶註套件會被標記 | 是 | -| `audit:deps` | `npm audit`(根目錄 + electron)— 不得有高風險/重大風險公告(與 osv `check:vuln-ratchet` 重疊;請參閱合理化待辦清單) | 是 | -| `check:lockfile` | `package-lock.json` 完整性 — 使用 https 登錄庫、具備完整性雜湊,且無主機覆寫 | 是 | -| `check:licenses` | 正式環境相依套件的 SPDX 授權允許清單 | 是 | -| `check:tracked-artifacts` | 不得有建置產物/已提交的 `node_modules` 符號連結(也會在 husky pre-commit 中執行;pre-push 刻意保持輕量 — #6716) | 是 | -| `check:ai-attribution` | PR 提交、標題或內文中不得包含 AI/機器人的 `Co-Authored-By` 尾註或 AI 生成註腳 — 強制規則 #16(位於 `quality.yml` 中針對 PR→`release/**` 的快速閘門迴圈 — 讀取事件承載資料,非 PR 時不執行任何操作 — 以及 `ci.yml` lint 中僅針對 PR→`main` 的步驟;另包含 husky `commit-msg` 掛鉤;允許人類共同作者;#14436) | -| `check:vitest-exclusions` | 每個 Vitest 排除項目都必須註明追蹤議題,並列於 `config/quality/vitest-exclusions.json` 中(#13204) | 是 | -| `check:file-size` | 原始碼檔案不得超過各副檔名的上限(棘輪機制:大型凍結檔案列於 `frozen` 清單中) | 是 | -| `check:error-helper` | 執行器/處理常式中的錯誤回應須使用 `buildErrorBody()`/`sanitizeErrorMessage()`(強制規則 #12) | 是 | -| `check:migration-numbering` | Migration SQL 檔案依序編號,沒有缺號或重複編號 | 是 | -| `check:public-creds` | 除了 `publicCreds.ts` 之外,不得出現 OAuth `client_id`/`client_secret` 或 Firebase Web 金鑰的字面值(硬性規則 #11) | 是 | -| `check:db-rules` | `src/lib/db/` 模組之外不得出現原始 SQL;不得從 `localDb.ts` 進行 barrel import(硬性規則 #2/#5) | 是 | -| `check:known-symbols` | 已在各自分派表中註冊的提供者執行器、路由策略及轉譯器,必須與磁碟上的檔案相符,不得有孤立或未宣告的符號 | 是 | -| `check:route-guard-membership` | 每個會產生子行程的路由都必須由 `isLocalOnlyPath()` 分類(硬性規則 #15/#17) | 是 | -| `check:test-discovery` | 儲存庫中的每個 `*.test.ts` / `*.spec.ts` 檔案都必須由至少一個測試執行器收集(棘輪限制:`test-discovery-baseline.json` 中的孤立檔案清單只能縮減) | 是 | -| `check:agent-skills-sync` | 產生的 agent-skills 成品與其來源目錄相符(無偏差) | -| `check:provider-asset-provenance` | Provider 標誌/資產具有已記錄的來源資訊項目 | -| `lint:json` | JSON 設定檔可正確解析,且符合儲存庫的 lint 規則 | -| `typecheck:core` | TypeScript 編譯無錯誤(僅有提示性警告) | 是 | -| `typecheck:noimplicit:core` | 嚴格的 `noImplicitAny`——著眼未來;許多既有呼叫位置仍需加上類型註記 | **提示性**(`continue-on-error: true`) | -| `check:dashboard-typecheck` | 將 `tsc` 範圍限定於 `src/app/(dashboard)/**` (#7033)——`typecheck:core` 精選的 27 個檔案允許清單不包含任何儀表板 TSX,而 `next build` 也從不對其進行類型檢查(`next.config.mjs` 設定了 `ignoreBuildErrors: true`),因此其中的孤立識別碼迴歸問題 (#6625/#6909) 過去無法被 CI 偵測。與凍結的每檔案/每 TS 程式碼計數基準(`config/quality/dashboard-typecheck-baseline.json`,採用與 `check:known-symbols` 相同的過時強制執行模式)進行差異比較——只有超出基準計數的新增錯誤才會導致閘門失敗;修正既有錯誤時,使用 `--update` 逐步下調基準。 | 是 | - -### 工作:`quality-gate` +在每個針對 `main` 的 PR 上執行。失敗時阻擋合併。 + +| 指令碼(`npm run ...`) | 驗證內容 | 阻擋性 | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | +| `check:node-runtime` | Node.js 版本是否位於支援範圍內 | 是 | +| `check:cycles` | 檢查整個 `src/` + `open-sse/` 中的循環匯入(基於 AST,並解析 tsconfig `paths`)。直接執行 = 僅提供建議並列出循環。`check:cycles:ratchet`(CI 實際執行的項目)會在數量超過 `quality-baseline.json` 中的 `metrics.cycles` 上限時阻擋;目前上限為 14,`direction: down`,因此只能下降(#15159 G-01/G-02) | 是(棘輪機制) | +| `check:route-validation:t06` | 所有路由上皆存在 Zod schema(第 6 級政策) | 是 | +| `check:any-budget:t11` | `@ts-expect-error // any` 的數量不超過預算(第 11 級 catraca) | 是 | +| `check:provider-consistency` | `providers.ts` 中的每個提供者都在 `providerRegistry.ts` 中有對應的項目(反之亦然,限允許清單內) | 是 | +| `check:model-lifecycle` | 三個手動維護的路由表會與已簽入版本控制的生命週期快照 (#11503) 保持一致:`FITNESS_TABLE` (`taskFitness.ts`) 不會為任何 `REGISTRY` 可路由的已退役 id 評分;每個 `BUILT_IN_ALIASES` 目標都存在於 `REGISTRY` 中,且不在已退役 id 快照中;`REGISTRY` 中仍存在的每個已退役 id 都會被轉送,或列於 `allowedRetiredInCatalog` 中;而且任何 `DEFAULT_DEGRADATION_MAP` 的來源或目標都不會在該快照中顯示為已退役。這並不能證明某個模型目前確實由正常運作的上游提供服務。離線作業——與 `config/quality/model-lifecycle.json` 比較;此檔案需透過 `npm run quality:refresh-model-lifecycle` 手動重新整理(需要網路;未整合至 CI)。`allowedRetiredInCatalog` 是一種逐步清零的棘輪機制:僅在有追蹤議題時才新增項目。 | 是 | +| `check:fetch-targets` | 用戶端 `src/` 中的每個 `fetch("/api/...")` 都會解析至實際存在的 `route.ts` | 是 | +| `check:deps` | 儲存庫中每個 `package.json` 內所有可透過 `npm install` 安裝的相依套件,都必須列於 `dependency-allowlist.json` 中;新增的未鎖定版本或疑似名稱仿冒套件會被標記 | 是 | +| `audit:deps` | `npm audit`(根目錄 + electron)——不得有高風險/嚴重等級的安全公告(與 osv `check:vuln-ratchet` 重疊;請參閱合理化待辦清單) | 是 | +| `check:lockfile` | `package-lock.json` 完整性——使用 https registry、具備完整性雜湊,且不得覆寫主機 | 是 | +| `check:licenses` | 正式環境相依套件的 SPDX 授權允許清單 | 是 | +| `check:tracked-artifacts` | 不得有建置產物/已提交的 `node_modules` 符號連結(也會在 husky pre-commit 中執行;pre-push 刻意維持輕量——#6716) | 是 | +| `check:ai-attribution` | PR 提交、標題或內文中不得有 AI/機器人的 `Co-Authored-By` 結尾標記或 AI 生成頁尾——強制規則 #16(位於 `quality.yml` 針對 PR→`release/**` 的 fast-gates 迴圈中——會讀取事件酬載,非 PR 時不執行任何操作——以及 `ci.yml` lint 中僅針對 PR→`main` 的步驟;另包含 husky `commit-msg` hook;允許人類共同作者;#14436) | +| `check:vitest-exclusions` | 每個 Vitest 排除項目都必須指定追蹤議題,並出現在 `config/quality/vitest-exclusions.json` 中(#13204) | 是 | +| `check:file-size` | 任何原始碼檔案皆不得超過各副檔名的上限(棘輪機制:大型檔案凍結於 `frozen` 清單中) | 是 | +| `check:error-helper` | 執行器/處理常式中的錯誤回應須使用 `buildErrorBody()`/`sanitizeErrorMessage()`(強制規則 #12) | 是 | +| `check:migration-numbering` | Migration SQL 檔案採用連續編號,沒有缺號或重複編號 | 是 | +| `check:public-creds` | 除了 `publicCreds.ts` 之外,不得出現 OAuth `client_id`/`client_secret` 或 Firebase Web 金鑰常值(硬性規則 #11) | 是 | +| `check:db-rules` | `src/lib/db/` 模組之外不得出現原始 SQL;不得從 `localDb.ts` 進行 barrel import(硬性規則 #2/#5) | 是 | +| `check:known-symbols` | 註冊於各自分派表中的提供者執行器、路由策略及轉譯器,必須與磁碟上的檔案相符,不得有孤立或未宣告的符號 | 是 | +| `check:route-guard-membership` | 每個會產生子行程的路由,都必須由 `isLocalOnlyPath()` 分類(硬性規則 #15/#17) | 是 | +| `check:test-discovery` | 儲存庫中的每個 `*.test.ts` / `*.spec.ts` 檔案,都必須由至少一個測試執行器收集(棘輪機制:`test-discovery-baseline.json` 中的孤立清單只能縮減) | 是 | +| `check:agent-skills-sync` | 產生的 agent-skills 成品與其來源目錄相符(無偏移) | +| `check:provider-asset-provenance` | 提供者標誌/資產具有已記錄的來源證明項目 | +| `lint:json` | JSON 設定檔可正確解析,且符合儲存庫的 lint 規則 | +| `typecheck:core` | TypeScript 編譯無錯誤(僅有提示性警告) | 是 | +| `typecheck:noimplicit:core` | 嚴格的 `noImplicitAny` — 前瞻性檢查;許多既有呼叫位置仍需補上類型註記 | **提示性**(`continue-on-error: true`) | +| `check:dashboard-typecheck` | 將 `tsc` 範圍限定為 `src/app/(dashboard)/**`(#7033)— `typecheck:core` 精選的 27 個檔案允許清單不包含任何儀表板 TSX,而 `next build` 也從不對其執行類型檢查(`next.config.mjs` 設定了 `ignoreBuildErrors: true`),因此其中的孤立識別碼迴歸問題(#6625/#6909)對 CI 而言不可見。此檢查會與凍結的「每個檔案/每個 TS 錯誤代碼」計數基準(`config/quality/dashboard-typecheck-baseline.json`,採用與 `check:known-symbols` 相同的過期強制執行模式)進行差異比較 — 只有超出基準計數的「新」錯誤才會使此閘門失敗;修正既有錯誤時,可使用 `--update` 逐步下調基準。 | 是 | + +### 作業:`quality-gate` 在 `test-coverage` 之後執行。失敗時會阻止合併。 -| 指令碼 | 驗證項目 | 阻擋性 | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| `quality:collect` | 產生 `quality-metrics.json`(ESLint 警告數、來自合併分片報告的覆蓋率) | 是(棘輪檢查的上游) | -| `quality:ratchet` | `quality-baseline.json` 中的每個指標皆未退步(ESLint 警告數 ≤ 基準;覆蓋率 ≥ 基準) | 是 | -| `check:duplication` | 程式碼重複率(jscpd@4)未超過 `quality-baseline.json` 中的基準 | 是 | -| `check:complexity` | 檔案層級的循環複雜度未超過上限(核心 ESLint `complexity` + `max-lines-per-function`) | 是 | -| `check:cognitive-complexity` | 認知複雜度棘輪檢查(`eslint-plugin-sonarjs`)— 獨立的 ESLint 檢查流程;CI 將兩者合併為單一的 `check:complexity-ratchets` 步驟執行 | 是 | -| `check:dead-code` | 未使用的匯出項目/檔案棘輪檢查(knip)相較於基準未退步 | 是 | -| `check:compression-budget` | 壓縮基準測試預算 — 各引擎的 token 節省率下限不得退步 | 是 | -| `check:type-coverage` | 類型標註百分比棘輪檢查(`type-coverage`)未退步;大致涵蓋 `typecheck:noimplicit:core` | 是 | -| `check:codeql-ratchet` | 未解決的 CodeQL 警示數未退步(透過 `gh api` 讀取;無權杖時正常跳過)— 更新頻率與手動觸發方式:請參閱下方的「CodeQL 棘輪檢查」 | 是 | +| 指令碼 | 驗證項目 | 阻擋性 | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| `quality:collect` | 產生 `quality-metrics.json`(ESLint 警告數量、來自合併分片報告的覆蓋率) | 是(棘輪檢查的上游) | +| `quality:ratchet` | `quality-baseline.json` 中的每項指標均未退步(ESLint 警告數 ≤ 基準;覆蓋率 ≥ 基準) | 是 | +| `check:duplication` | 程式碼重複(jscpd@4)未超過 `quality-baseline.json` 中的基準 | 是 | +| `check:complexity` | 檔案層級的循環複雜度未超過上限(核心 ESLint `complexity` + `max-lines-per-function`) | 是 | +| `check:cognitive-complexity` | 認知複雜度棘輪檢查(`eslint-plugin-sonarjs`)— 獨立的 ESLint 檢查流程;CI 將兩者合併為單一的 `check:complexity-ratchets` 步驟 | 是 | +| `check:dead-code` | 未使用的匯出項目/檔案棘輪檢查(knip)相較於基準未退步 | 是 | +| `check:compression-budget` | 壓縮基準測試預算 — 各引擎的 token 節省下限不得退步 | 是 | +| `check:type-coverage` | 類型覆蓋百分比棘輪檢查(`type-coverage`)未退步;大致涵蓋 `typecheck:noimplicit:core` | 是 | +| `check:codeql-ratchet` | 未解決的 CodeQL 警示數量未退步(透過 `gh api` 讀取;無權杖時正常略過)— 更新頻率與手動觸發方式:請參閱下方的「CodeQL 棘輪檢查」 | 是 | ### 工作:`quality-extended` -整個工作皆為建議性質(`continue-on-error: true`)。以 npm 為基礎的棘輪檢查會實際執行; -外部掃描器則透過 `gh release download` 安裝,若二進位檔仍不存在,將自行跳過(exit 0)。 +整個工作皆為建議性質(`continue-on-error: true`)。以 npm 為基礎的棘輪檢查會實際執行;外部掃描器則透過 `gh release download` 安裝,若二進位檔仍不存在,便會自行略過(exit 0)。 -| 指令碼 | 驗證項目 | 阻擋性 | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `check:circular-deps` | 無循環相依性(dpdm) | **建議性** | -| `check:bundle-size` | 套件大小未超過上限 | **建議性** | -| `check:secrets` | 機密資訊掃描(gitleaks)— 若二進位檔不存在則跳過 | **建議性** | -| `check:vuln-ratchet` | 相依套件漏洞(osv-scanner)未退步 — 若二進位檔不存在則跳過 | **建議性** | -| `check:workflows` | 工作流程 lint(actionlint + zizmor);掃描器缺失/損壞、報告無效或棘輪基準缺失時,會以 INCOMPLETE 判定失敗。有效的發現項目則遵循所選的嚴格/建議/棘輪政策 | 必須執行;zizmor 棘輪檢查在 CI 中具有阻擋性 | -| `check:openapi-breaking` | 公開 API 合約(`openapi.yaml`)相較於基礎分支的破壞性變更(oasdiff)— 產生 `openapiBreaking=N`;若 oasdiff 不存在或無法解析基礎規格則跳過 | **建議性** | +| 指令碼 | 驗證項目 | 阻擋性 | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | +| `check:circular-deps` | 無循環相依性(dpdm) | **建議性** | +| `check:bundle-size` | 套件組合大小未超過上限 | **建議性** | +| `check:secrets` | 機密資訊掃描(gitleaks)— 若二進位檔不存在則略過 | **建議性** | +| `check:vuln-ratchet` | 相依套件漏洞(osv-scanner)未退步 — 若二進位檔不存在則略過 | **建議性** | +| `check:workflows` | 工作流程 lint(actionlint + zizmor);掃描器缺失/故障、報告無效或缺少棘輪基準時,會以 INCOMPLETE 判定失敗。有效發現項目則遵循所選的嚴格/建議/棘輪政策 | 必須執行;zizmor 棘輪檢查在 CI 中具阻擋性 | +| `check:openapi-breaking` | 相較於基礎分支,公開 API 契約(`openapi.yaml`)的破壞性變更(oasdiff)— 產生 `openapiBreaking=N`;若 oasdiff 不存在或無法解析基礎規格則略過 | **建議性** | ### 工作:`docs-sync-strict` -每個針對 `main` 的 PR 都會執行。失敗時會阻擋合併。 - -| 指令碼 | 驗證內容 | 阻擋性 | -| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| `check:docs-all` | 依序執行下列 6 個子閘門的中繼閘門 | 是 | -| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt 的版本一致性 | 是 | -| ↳ `check:docs-counts` | 文章中的計數(提供者數量、遷移數量等)是否位於實際計數的棘輪容許範圍內 | 是 | -| ↳ `check:env-doc-sync` | `.env.example` 中的每個環境變數都有記錄在文件表格中,反之亦然 | 是 | -| ↳ `check:deprecated-versions` | 文件中沒有已棄用的版本字串 | 是 | -| ↳ `check:doc-links` | 文件中的內部 markdown 連結會解析至實際檔案(`[text]`/`(path)` 格式) | 是 | -| ↳ `check:fabricated-docs` | 文件中引用的路由、環境變數、CLI 命令、hook 名稱及檔案路徑皆存在於程式碼庫中。透過 `--strict` 作為硬性閘門;未使用該旗標時則為軟性失敗。 | 是(在 CI 中透過 `--strict`) | -| `check:cli-i18n` | CLI 命令字串存在於所有 i18n 語系檔案中 | 是 | -| `check:openapi-coverage` | OpenAPI 規格涵蓋的實際路由數至少達到棘輪式下限 | 是 | -| `check:openapi-security-tiers` | `openapi.yaml` 中的安全層級註解與 `routeGuard.ts` 的分類一致 | **建議性** | -| `check:openapi-routes` | `openapi.yaml` 中的每個路徑都會解析至實際的 `route.ts`(防止幻覺) | 是 | -| `check:docs-symbols` | `docs/**/*.md` 中的每個 `/api/...` 參照都會解析至實際的 `route.ts`(防止幻覺) | 是 | -| `i18n translation drift` | i18n 語系檔案中未翻譯的鍵值——僅警告 | **建議性** | +每個以 `main` 為目標的 PR 都會執行。失敗時會阻擋合併。 + +| 指令碼 | 驗證內容 | 阻擋性 | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- | +| `check:docs-all` | 依序執行下列 6 個子閘門的中繼閘門 | 是 | +| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt 的版本一致性 | 是 | +| ↳ `check:docs-counts` | 文字中的數量(提供者數量、遷移數量等)位於實際數量的棘輪允許範圍內 | 是 | +| ↳ `check:env-doc-sync` | `.env.example` 中的每個環境變數都記錄於文件表格中,反之亦然 | 是 | +| ↳ `check:deprecated-versions` | 文件中沒有已棄用的版本字串 | 是 | +| ↳ `check:doc-links` | 文件中的內部 markdown 連結會解析至實際檔案(`[text]`/`(path)` 格式) | 是 | +| ↳ `check:fabricated-docs` | 文件中引用的路由、環境變數、CLI 命令、掛鉤名稱及檔案路徑確實存在於程式碼庫中。使用 `--strict` 時為硬性閘門;不加此旗標時僅軟性失敗。 | 是(CI 中透過 `--strict`) | +| `check:cli-i18n` | CLI 命令字串存在於所有 i18n 語系檔案中 | 是 | +| `check:openapi-coverage` | OpenAPI 規格至少涵蓋依棘輪機制設定下限數量的實際路由 | 是 | +| `check:openapi-security-tiers` | `openapi.yaml` 中的安全層級註解與 `routeGuard.ts` 的分類一致 | **建議性** | +| `check:openapi-routes` | `openapi.yaml` 中的每個路徑都能解析至實際的 `route.ts`(防止虛構) | 是 | +| `check:docs-symbols` | `docs/**/*.md` 中的每個 `/api/...` 參照都能解析至實際的 `route.ts`(防止虛構) | 是 | +| `i18n translation drift` | i18n 語系檔案中未翻譯的鍵值——僅發出警告 | **建議性** | ### 工作:`i18n-ui-coverage` -| 指令碼 | 驗證內容 | 阻擋性 | -| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `check-ui-keys-coverage`(內嵌) | UI i18n 鍵值涵蓋率 ≥ 65% | 是 | -| `check-ui-value-drift`(內嵌) | 重寫英文**值**後,不會留下過時的翻譯 | 是 | -| `check-new-key-coverage`(內嵌) | **新增的**英文鍵值已翻譯至每個語系——不接受 `__MISSING__:` 標記 | 是 | -| `check-translation-ratio` | 各語系的實際翻譯比例(允許清單以外與英文相同/預留位置/缺漏的葉節點)不得超過 `config/quality/i18n-translation-baseline.json` + 鬆弛值 | **建議性** | +| 指令碼 | 驗證內容 | 阻擋性 | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `check-ui-keys-coverage`(內嵌) | UI i18n 鍵值涵蓋率 ≥ 65% | 是 | +| `check-ui-value-drift`(內嵌) | 改寫英文**值**後,不會留下過時的翻譯 | 是 | +| `check-new-key-coverage`(內嵌) | **新增的**英文鍵值已在每個語系中翻譯——拒絕 `__MISSING__:` 標記 | 是 | +| `check-translation-ratio` | 每個語系的實際翻譯比例(與英文相同/預留位置/缺漏的葉節點,不含允許清單內項目)不得超過 `config/quality/i18n-translation-baseline.json` + 寬限值 | **建議性** | -需要 `fetch-depth: 0`——值漂移閘門會將 `en.json` 與合併基底進行差異比較。 +需要 `fetch-depth: 0`——值漂移閘門會比較 `en.json` 與合併基準之間的差異。 #### `check-ui-value-drift`——過時翻譯閘門 -捕捉其他閘門在結構上無法發現的那一類 i18n 迴歸:英文值 -已被重寫,但從_先前_英文衍生的翻譯仍然保留,因此 -非英語使用者會繼續讀到語氣確定、但如今已不正確的文案。 +捕捉其他閘門在結構上無法發現的一種 i18n 迴歸:英文值已遭改寫, +但衍生自_先前_英文內容的翻譯仍被保留下來,導致非英語使用者繼續讀到語氣肯定、 +但如今已不正確的文案。 -這確實曾發佈至正式環境。Antigravity -登入輔助工具推出時(#5203),`oauthModal.googleOAuthWarning` 被重寫;**43 個語系中有 39 個** -仍保留文字,要求操作人員「複製完整 URL 並貼到下方」——對該提供者而言,此流程根本無法完成。直到 #8463 才被發現,原因如下: +這確實曾發布到正式環境。Antigravity 登入輔助工具上線時(#5203), +`oauthModal.googleOAuthWarning` 曾被改寫;**43 個語系中有 39 個**仍保留告知操作人員「複製完整 URL 並貼到下方」的文字——對該提供者而言,此流程根本無法完成。直到 #8463 才有人發現,原因如下: -- `sync-ui-keys` 只會回填**缺少**的鍵值,絕不處理**過時**的鍵值; -- `check-ui-keys-coverage` 計算鍵值是否_存在_,因此過時翻譯仍會被視為已涵蓋; +- `sync-ui-keys` 只會回填**不存在**的鍵值,絕不處理**過時**的鍵值; +- `check-ui-keys-coverage` 計算的是鍵值是否_存在_,因此過時翻譯仍會被視為已涵蓋; - `check-translation-drift` 追蹤的是 `docs/i18n//**.md` 文件鏡像—— - 它從不讀取 `src/i18n/messages/*.json`。自 2026-09 重新同步後,已在工作 `docs-sync-strict` 中設為阻擋性檢查:編輯核心文件 → `npm run i18n:run -- --files=`(章節層級,成本低)。 + 它從不讀取 `src/i18n/messages/*.json`。自 2026-09 重新同步起,在工作 `docs-sync-strict` 中具阻擋性:編輯核心文件 → `npm run i18n:run -- --files=`(以章節為單位,成本低)。 -**感知差異,而非以基準檔為依據。** 它會比較合併基準點的 `en.json` 與 -工作樹;對於英文值已變更的每個鍵,任何仍保留未修改翻譯的語系都會被視為過時。這會刻意 -**凍結既有債務**——差異無法揭示長期存在的翻譯源自哪個舊英文,因此此關卡 -只會判斷目前變更所觸及的內容。替代方案(每個鍵各自使用雜湊基準)將產生一個約 -600 KB 的生成檔案,是現有最大基準檔的 3 倍,且會在每個 i18n PR 中反覆變動。 +**感知差異,而非以基準為依據。** 它會比較合併基準點上的 `en.json` 與 +工作樹;對於每個英文值已變更的鍵,任何仍保留未修改翻譯的語系都會被視為過時。這刻意 +**凍結既有技術債**——差異無法顯示長期存在的翻譯源自哪個舊版英文,因此此閘門 +只判斷目前變更所觸及的內容。替代方案(每個鍵各自採用雜湊基準)將需要一個約 600 KB +的產生檔案,是目前最大基準檔案的 3 倍,且每個 i18n PR 都會造成大量變動。 -有兩種方式可以滿足此要求: +有兩種方式可滿足此要求: 1. 更新受影響的翻譯,或 -2. 將它們設為 `__MISSING__:`——執行階段之後會提供修正後的英文 - (`src/i18n/request.ts::deepMergeFallback`, #7258),並將該鍵排入翻譯佇列。 +2. 將它們設為 `__MISSING__:`——執行階段隨後會提供修正後的英文 + (`src/i18n/request.ts::deepMergeFallback`,#7258),並將該鍵排入翻譯佇列。 -如果字串的**含義**有所變更,請優先考慮**重新命名鍵**:新鍵不會繼承 -過時的翻譯。#8463 採用的就是這種模式。 +如果字串的**含義**已變更,請優先選擇**重新命名該鍵**:新鍵不會繼承 +過時的翻譯。#8463 採用的就是此模式。 ```bash npm run i18n:check-value-drift # 嚴格模式(CI 執行的模式) @@ -220,48 +217,48 @@ npm run i18n:check-value-drift:warn # 僅回報 BASE_REF=origin/release/vX.Y.Z npm run i18n:check-value-drift ``` -當無法讀取基準目錄時(淺層複製中沒有基準參照),會以 `SKIP reason=base-unresolved` -及狀態碼 0 結束,與 `check-openapi-breaking` 的行為一致。 +當無法讀取基礎目錄(未包含基礎參照的淺層複製)時,會以 0 結束並顯示 +`SKIP reason=base-unresolved`,與 `check-openapi-breaking` 的行為一致。 -### 作業:`i18n` +### 工作:`i18n` -完整的 i18n 驗證矩陣(每個語系各一個作業)。整個作業僅供參考。 +完整的 i18n 驗證矩陣(每個語系一個工作)。整個工作僅供參考。 | 指令碼 | 驗證內容 | 是否阻擋 | | ------------------------------- | ------------------ | -------------------------------------------------------- | -| `validate_translation.py quick` | 各語系的翻譯完整性 | **僅供參考**(整個作業皆設為 `continue-on-error: true`) | +| `validate_translation.py quick` | 各語系的翻譯完整性 | **僅供參考**(整個工作設定為 `continue-on-error: true`) | -### 作業:`pr-test-policy` +### 工作:`pr-test-policy` 僅在提取要求上執行。 -| 指令碼 | 驗證內容 | 是否阻擋 | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | -------- | -| `check:pr-test-policy` | 變更 `src/`、`open-sse/`、`electron/` 或 `bin/` 中正式環境程式碼的 PR,必須包含或更新測試(強制規則 #8) | 是 | -| `check:test-masking` | 變更的測試檔案不得減少斷言淨數量,亦不得新增 `assert.ok(true)` 恆真式 | 是 | -| `check:pr-evidence` | PR 內文必須引用此變更的測試/VPS 證據(透過 grep 搜尋 PR 文字,將強制規則 #18 機制化——此方式較脆弱,請參閱待辦清單) | 是 | +| 指令碼 | 驗證內容 | 是否阻擋 | +| ---------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | +| `check:pr-test-policy` | 變更 `src/`、`open-sse/`、`electron/` 或 `bin/` 中正式環境程式碼的 PR,必須包含或更新測試(強制規則 #8) | 是 | +| `check:test-masking` | 已變更的測試檔案不得降低斷言淨數量,亦不得新增 `assert.ok(true)` 恆真式 | 是 | +| `check:pr-evidence` | PR 內文須引用此變更的測試/VPS 證據(透過搜尋 PR 文字將強制規則 #18 機械化——此方式並不穩健,請參閱待辦清單) | 是 | -### 作業:`test-vitest` +### 工作:`test-vitest` -在 `build` 後執行。失敗時會阻止合併。 +在 `build` 之後執行。失敗時會阻止合併。 -| 測試套件 | 驗證內容 | 是否阻擋 | -| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------- | -| `test:vitest` | MCP 伺服器(110 個工具)、autoCombo、快取——vitest 執行器 | 是 | -| `test:vitest:ui` | UI 元件測試——vitest 執行器 | **會阻擋**——`vitest.config.ts` 中已明確排除既有失敗;新的失敗會導致作業失敗 | +| 測試套件 | 驗證內容 | 是否阻擋 | +| ---------------- | -------------------------------------------------------- | -------------------------------------------------------------------------- | +| `test:vitest` | MCP 伺服器(110 個工具)、autoCombo、快取——vitest 執行器 | 是 | +| `test:vitest:ui` | UI 元件測試——vitest 執行器 | **阻擋**——既有失敗已在 `vitest.config.ts` 中明確排除;新的失敗會使工作失敗 | ### 每夜工作流程(排程執行,僅供參考) -這些工作流程會依 cron 排程執行(也可透過 `workflow_dispatch` 執行),絕不會在 PR 上執行。全部都僅供參考。 +這些工作流程依 cron 排程(以及 `workflow_dispatch`)執行,絕不會在 PR 上執行。全部僅供參考。 | 工作流程 | 驗證內容 | 是否阻擋 | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------ | | `nightly-property` | 使用隨機種子與高執行次數的 fast-check 屬性測試 | **僅供參考** | -| `nightly-resilience` | 堆積成長關卡、混沌故障注入、k6 負載/浸泡測試 | **僅供參考** | -| `nightly-llm-security` | promptfoo 注入防護(阻擋模式)+ garak 探測(沒有提供者密鑰時略過) | **僅供參考** | -| `nightly-schemathesis` | 針對即時 OmniRoute,使用 `docs/openapi.yaml` 進行 OpenAPI 合約模糊測試(schemathesis)——揭露規格違規/未處理的 500 錯誤(階段 8 B.4) | **僅供參考** | -| `nightly-mutation` | 對快速單元測試通道執行 Stryker 突變測試評分——存活的突變體可揭露薄弱的斷言 | **僅供參考** | -| `nightly-compat` | 跨受支援 `engines.node` 範圍的 Node 引擎相容性矩陣 | **僅供參考** | +| `nightly-resilience` | 堆積成長閘門、混沌故障注入、k6 負載/浸泡測試 | **僅供參考** | +| `nightly-llm-security` | promptfoo 注入防護(阻擋模式)+ garak 探測(沒有提供者密鑰時會略過) | **僅供參考** | +| `nightly-schemathesis` | 使用 `docs/openapi.yaml`,針對即時 OmniRoute 進行 OpenAPI 合約模糊測試(schemathesis)——揭露規格違規/未處理的 500 錯誤(階段 8 B.4) | **僅供參考** | +| `nightly-mutation` | 對快速單元測試通道進行 Stryker 突變測試評分——存活的突變體會揭露薄弱的斷言 | **僅供參考** | +| `nightly-compat` | 涵蓋受支援 `engines.node` 範圍的 Node 引擎相容性矩陣 | **僅供參考** | --- @@ -508,43 +505,42 @@ JSON,十秒即可完成,也是解除紅燈 PR 阻塞最快的方法。 --- -## 合理化待辦清單(ROI 審查 — 第 9 階段第 3 波) +## 合理化待辦清單(ROI 審查 — 階段 9 波次 3) -此清單已於 2026-06-17 依據 `ci.yml` 完成核對(先前版本遺漏了 +此清單已於 2026-06-17 依據 `ci.yml` 進行核對(先前版本遺漏了 `audit:deps`、`check:tracked-artifacts`、`check:lockfile`、`check:licenses`、 `check:dead-code`、`check:cognitive-complexity`、`check:type-coverage`、 -`check:codeql-ratchet`、`check:pr-evidence`)。對核對後集合進行的 ROI 審查 -找出了下列合理化候選項目。**合併屬於機械式 CI -變更;切換/移除則是保留給操作者決定的政策事項。** 以下內容 -尚未套用。 +`check:codeql-ratchet`、`check:pr-evidence`)。對核對後集合進行 ROI 審查, +找出了以下合理化候選項目。**合併屬於機械式 CI 變更;切換/移除則是保留給操作人員的政策決策。** +以下內容尚未套用。 -**上文亦未記載**(諮詢性質、訊號偏低):`docs-lint` 工作 -(markdownlint + Vale,整個工作設定 `continue-on-error`),以及獨立的掃描器工作流程 +**上方亦未記錄的項目**(建議性、訊號偏弱):`docs-lint` 工作 +(markdownlint + Vale,整個工作設定為 `continue-on-error`),以及獨立的掃描器工作流程 `semgrep.yml` / `codeql.yml` / `scorecard.yml`。`semgrepFindings: 0` 位於 -`quality-baseline.json` 中,但尚未連接至 `ci.yml` 內的阻擋式棘輪機制——此指標 +`quality-baseline.json` 中,但並未連接到 `ci.yml` 內具阻擋效果的棘輪機制——此指標 目前處於孤立狀態。 ### 合併/去重(機械式、風險較低) -每個候選項目都已於 2026-06-17 依照實際閘門狀態完成驗證(信任但須查證); -數個「顯而易見」的合併實際上隱藏了技術債,**並非**可直接替換的乾淨方案。 - -- **`check:docs-sync` 執行兩次**——在 `lint` 工作中獨立執行一次,並在 `check:docs-all`(`docs-sync-strict`)與 husky pre-commit hook 中再次執行。✅ **已完成**——已移除 `lint` 中的獨立呼叫。 -- **CVE 掃描**——❌ **無法乾淨合併。** `audit:deps` 遇到任何高風險/嚴重 CVE 時都會直接失敗;`check:vuln-ratchet`(osv)僅在相較基準線出現_退步_時失敗(目前為 1 個 MODERATE)。兩者語意不同——移除 `audit:deps` 將失去高風險/嚴重漏洞的絕對閘門。保留兩者。 -- **循環相依偵測**——❌ **無法乾淨合併。** `check:circular-deps`(dpdm)回報 **91 個循環**(這正是其僅具諮詢性質的原因);在先解決這些循環之前,無法將其提升為阻擋項目,而且其範圍比目前通過且經過篩選的 `check:cycles` 更廣。保留 `check:cycles` 的阻擋性;解決 91 個 dpdm 循環本身是另一項待辦事項。 -- **複雜度**——✅ **已完成**(`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`):單次 ESLint 掃描,依 ruleId 計數,使循環複雜度 + max-lines 與認知複雜度基準線維持獨立;個別的 `check:complexity` / `check:cognitive-complexity` 保留供本機使用 `--update`。 -- **`/api` 反幻覺檢查**——✅ **已完成**(`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`):對 `src/app/api` 進行單次 FS 清查,openapi-routes + docs-symbols 仍會各自回報;個別檢查保留供本機執行。 -- **`check:node-runtime` 在 11 個工作中執行**——⚠️ **ROI 偏低。** 每個工作都使用獨立 runner,且檢查耗時不到 1 秒;總計僅節省約 10 秒,代價卻是失去低成本的逐工作防護。不值得為此折騰。 -- **CI lint 上的 `typecheck:noimplicit:core`**——✅ **已從 lint 工作移除**(原為設定 `continue-on-error` 的諮詢性檢查);阻擋式類型介面由 `typecheck:core` + `check:type-coverage` 負責。本機腳本保留。 - -### 切換/決策(操作者政策) - -- `check:openapi-security-tiers`(諮詢性)——❌ **無法直接切換。** 它以 0 結束,但會警告 `LOCAL_ONLY_API_PREFIXES` 下數個 `traffic-inspector` 路由缺少 `x-loopback-only: true` 註解。若要強制執行,必須先將這些註解加入 `openapi.yaml`。 -- `typecheck:noimplicit:core`(諮詢性)——大致已由阻擋式 `check:type-coverage` 棘輪機制涵蓋。將其切換為棘輪機制,或移除重複的第二次 `tsc` 掃描。 -- `test:vitest:ui`(現在為**阻擋式**)——既有失敗已在 `vitest.config.ts` 中明確排除,並附上 `// #8618` 追蹤註解;新的失敗會使工作失敗。 -- `check:secrets`(gitleaks,阻擋式棘輪機制固定於 3 個已記錄的誤報)——將這 3 個項目加入允許清單以降至 0,或降級為諮詢性檢查。與 GitHub 原生秘密掃描 + `check:public-creds` 重疊。 -- `check:pr-evidence`(阻擋式,使用 grep 搜尋 PR 正文文字)——誤報風險高;若移除會削弱對強制規則 #18 的執行,因此這確實是一項政策決策。 -- `semgrep`(諮詢性獨立工作流程)——在 OWASP 類別方面與 CodeQL 重疊;將其基準線接入棘輪機制,或予以移除。 +每個候選項目都已於 2026-06-17 對照實際閘門狀態進行驗證(信任但要查證); +數個「顯而易見」的合併其實隱藏了技術債,**並非**可直接替換的乾淨方案。 + +- **`check:docs-sync` 執行兩次**——在 `lint` 工作中獨立執行一次,並於 `check:docs-all`(`docs-sync-strict`)和 husky pre-commit hook 中再次執行。✅ **已完成**——已移除 `lint` 中的獨立呼叫。 +- **CVE 掃描**——❌ **不是乾淨的合併。** `audit:deps` 遇到任何高/嚴重 CVE 時都會直接失敗;`check:vuln-ratchet`(osv)僅在相較基準發生_退步_時失敗(目前為 1 個 MODERATE)。兩者語意不同——移除 `audit:deps` 將失去絕對的高/嚴重風險閘門。兩者皆保留。 +- **循環偵測**——✅ **已完成**(#15159 G-01/G-02)。此處舊文字將 `check:cycles` 稱為「綠燈、經過篩選的」閘門,並因 `check:circular-deps`(dpdm)回報 91 個循環,而主張其應繼續作為阻擋閘門。該綠燈其實是**假綠燈**:`check:cycles` 僅掃描 5 個子目錄(450 個檔案)、只比對靜態的 `import|export … from`,並捨棄所有 `@/` 和 `@omniroute/open-sse/` 說明符,因此無法看見存放庫中占主導地位的動態匯入 + 別名循環。現已修正:閘門目前會遍歷 `src` + `open-sse`(5023 個檔案),從 TypeScript AST 收集說明符(因此 `import("…")` 會計入,而類型位置的 `typeof import("…")` 不會),並解析 tsconfig `paths`。它找出了 **14** 個循環,而非 0。由於無法在閘門 PR 中修正 14 個既有循環,`check:cycles` 現已改為**棘輪機制**(`--ratchet`,`quality-baseline.json` 中的上限為 `metrics.cycles.value = 14`,`direction: down`)——它會阻擋任何_退步_,且數量只能下降。CI 執行 `npm run check:cycles:ratchet`。逐步消減工作併入 **A-01**。`check:circular-deps`(dpdm)保留為涵蓋範圍更廣的建議性第二意見。 +- **複雜度**——✅ **已完成**(`check:complexity-ratchets` / `eslint.complexity-ratchets.config.mjs`):只進行一次 ESLint 遍歷,依 ruleId 計數,使循環複雜度 + 最大行數與認知複雜度的基準維持獨立;個別的 `check:complexity` / `check:cognitive-complexity` 則保留供本機使用 `--update`。 +- **`/api` 反幻覺**——✅ **已完成**(`check:api-docs-refs` + `scripts/check/lib/apiRoutes.mjs`):只對 `src/app/api` 進行一次 FS 清查,openapi-routes + docs-symbols 仍會各自獨立回報;個別檢查保留供本機執行。 +- **`check:node-runtime` 會在 11 個工作中執行**——⚠️ **ROI 偏低。** 每個工作皆使用獨立 runner,而此檢查耗時不到 1 秒;總共僅節省約 10 秒,代價卻是失去成本低廉的逐工作防護。不值得為此進行改動。 +- **CI lint 上的 `typecheck:noimplicit:core`**——✅ **已從 lint 工作中移除**(原本是建議性的 `continue-on-error`);具阻擋效果的類型檢查面為 `typecheck:core` + `check:type-coverage`。本機腳本則予以保留。 + +### 切換/決策(操作人員政策) + +- `check:openapi-security-tiers`(建議性)——❌ **無法直接切換。** 它會以 0 結束,但會警告 `LOCAL_ONLY_API_PREFIXES` 下有數個 `traffic-inspector` 路由缺少 `x-loopback-only: true` 註解。若要強制執行,必須先將這些註解新增至 `openapi.yaml`。 +- `typecheck:noimplicit:core`(建議性)——大致已由具阻擋效果的 `check:type-coverage` 棘輪機制涵蓋。可將其切換為棘輪機制,或移除重複的第二次 `tsc` 執行。 +- `test:vitest:ui`(目前**具阻擋效果**)——既有失敗已在 `vitest.config.ts` 中透過 `// #8618` 追蹤註解明確排除;新的失敗會使工作失敗。 +- `check:secrets`(gitleaks,具阻擋效果的棘輪機制,凍結於 3 個已記錄的誤報)——將這 3 個項目加入允許清單以降至 0,或降級為建議性。與 GitHub 原生秘密掃描 + `check:public-creds` 有重疊。 +- `check:pr-evidence`(具阻擋效果,對 PR 內文執行 grep)——誤報風險高;若移除,將削弱硬性規則 #18 的執行力,因此這是真正的政策決策。 +- `semgrep`(建議性的獨立工作)——在 OWASP 類別上與 CodeQL 重疊;將其基準連接至棘輪機制,或予以移除。 --- diff --git a/docs/i18n/zh-TW/docs/compression/COMPRESSION_GUIDE.md b/docs/i18n/zh-TW/docs/compression/COMPRESSION_GUIDE.md index 30011129d95e..5b3be3670802 100644 --- a/docs/i18n/zh-TW/docs/compression/COMPRESSION_GUIDE.md +++ b/docs/i18n/zh-TW/docs/compression/COMPRESSION_GUIDE.md @@ -434,8 +434,7 @@ const { messages: aged, saved } = applyAging(messages, { 對於含有 `messages` 的主體,內容略過機制(位於 `open-sse/services/compression/outputMode.ts` 的 `shouldBypassCavemanOutputMode()`)會檢查 最後三則訊息,若符合安全性、不可逆操作、釐清或順序敏感關鍵字, -便會略過整個回合的樣式。略過機制是否執行,取決於儀表板的 -**Auto-Clarity Bypass** 開關(`cavemanOutputMode.autoClarity`)設定。 +便會略過整個回合的樣式。略過機制會在儀表板的 **Auto-Clarity Bypass** 開關(`cavemanOutputMode.autoClarity`)開啟時執行,這也是預設值;開關關閉時,所選樣式同樣會套用到那些回合。 當略過機制允許該回合繼續時,`placeSystemInstruction()`(同一檔案) 絕不會建立新的 `messages[0]`,而會將區塊放入以下第一個找到的位置: diff --git a/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md b/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md index d1257be7b708..2ef3da9c21c1 100644 --- a/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/zh-TW/docs/reference/ENVIRONMENT.md @@ -49,19 +49,19 @@ ## 1. 必要的密鑰 -這些密鑰在首次執行前**必須**設定。若未設定,應用程式將拒絕啟動,或使用不安全的預設值運作。 - -| 變數 | 必要性 | 預設值 | 來源檔案 | 說明 | -| ---------------------------- | ------------------ | ------------------------ | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **是** | _(無)_ | `src/lib/auth` | 簽署/驗證所有儀表板工作階段 Cookie(JWT)。使用 `openssl rand -base64 48` 產生。 | -| `API_KEY_SECRET` | **是** | _(無)_ | `src/lib/db/apiKeys.ts` | 用於加密儲存在 SQLite 中之 API 金鑰值的 AES 加密金鑰。使用 `openssl rand -hex 32` 產生。 | -| `INITIAL_PASSWORD` | **是** | `CHANGEME` | 啟動程序指令碼 | 設定初始管理員儀表板密碼(與 `.env.example` 預設值相同——刻意維持明顯不安全的設定以強制進行變更)。**請在首次使用前變更。**登入後,請透過「儀表板 → 設定 → 安全性」進行變更。 | -| `OMNIROUTE_WS_BRIDGE_SECRET` | **是**(正式環境) | _(未設定)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 內部 Codex Responses WebSocket 橋接器的共用密鑰。用於驗證 Electron/瀏覽器 WS 中繼與 OmniRoute 之間的橋接請求。⚠️ **正式環境中為必要設定——未設定時,所有 WS 橋接請求都會遭到拒絕。**使用 `openssl rand -base64 32` 產生。 | -| `OMNIROUTE_SW_BUILD_ID` | 否 | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA 殼層的明確 Service Worker 快取失效 ID(#11779);在解析鏈中優先使用。 | -| `SOURCE_VERSION` | 否 | _(未設定)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | 解析鏈中的第二順位——由 PaaS 建置工具(例如 Heroku 類型的工具)設定為已部署的提交版本。 | -| `NEXT_PUBLIC_SW_BUILD_ID` | 否 | _(衍生)_ | `src/shared/components/PwaRegister.tsx` | 用戶端用來註冊 `/sw.js?v=…` 的建置時公開值;依序衍生自上述兩個值,最後再使用 git SHA。 | -| `OMNIROUTE_PEER_STAMP_TOKEN` | 否(自動) | _(每次啟動時自動產生)_ | `src/server/authz/policies/management.ts` | 用於證明受信任對等 IP 戳記來自 OmniRoute 自有 HTTP 伺服器(`scripts/dev/peer-stamp.mjs`)的個別程序密鑰。只有在戳記包含此權杖時,授權中介軟體才會信任請求的本地性(針對 LOCAL_ONLY 路由的回送位址/LAN 閘控)。每次啟動時會自動產生——請保持未設定;僅在必須共用此戳記的多程序設定中固定此值。 | -| `OMNIROUTE_TRUSTED_PROXIES` | 否 | _(未設定)_ | `scripts/dev/peer-stamp.mjs` | 以逗號分隔的反向代理 IP 或 CIDR 範圍;IP 允許/拒絕清單可信任來自這些代理的 `X-Forwarded-For`/`X-Real-IP`,此外,回送位址、私人網路位址及 Cloudflare 邊緣節點一律受信任。僅當代理位於任何其他公用位址時才需要設定;否則,篩選器將判定該代理本身的位址。 | +標記為**是**且尚未設定的密鑰會在首次執行時自動產生(`JWT_SECRET`、`API_KEY_SECRET`),並持久化到資料目錄中的 `server.env`——當該目錄為臨時目錄時,請明確設定這些密鑰。例外是 `OMNIROUTE_WS_BRIDGE_SECRET`:它不會持久化到 `server.env`——伺服器進入點會在每次開機時產生一個僅供同程序內橋接器使用的臨時值,因此外部橋接用戶端需要明確設定它。 + +| 變數 | 必要性 | 預設值 | 來源檔案 | 說明 | +| ---------------------------- | ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `JWT_SECRET` | **是** | _(無)_ | `src/lib/auth` | 簽署/驗證所有儀表板工作階段 Cookie(JWT)。使用 `openssl rand -base64 48` 產生。 | +| `API_KEY_SECRET` | **是** | _(無)_ | `src/lib/db/apiKeys.ts` | 用於加密儲存在 SQLite 中之 API 金鑰值的 AES 加密金鑰。使用 `openssl rand -hex 32` 產生。 | +| `INITIAL_PASSWORD` | 否 | _(未設定)_ | `src/lib/auth/managementPassword.ts` | 在尚無已儲存密碼時,於啟動時設定儀表板密碼;一旦密碼已儲存即會被忽略。npm 與原始碼安裝會將 `.env.example` 複製為 `.env`,因此它們以其公開已知的預留位置 `CHANGEME` 啟動:請在首次開機前更換它,或在那份 `.env` 中將 `INITIAL_PASSWORD=` 設為空值,以便在首次造訪時於入門精靈中建立密碼——與未設定該值(Docker 映像檔的預設行為)時的效果相同。已儲存的密碼可在「儀表板 → 設定 → 安全性」中變更,或在將 `DATA_DIR` 設為伺服器的資料目錄後執行 `omniroute reset-password`(原始碼檢出中為 `node bin/reset-password.mjs`)。 | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **是**(正式環境) | _(未設定)_ | `src/app/api/internal/codex-responses-ws/route.ts` | 內部 Codex Responses WebSocket 橋接器的共用密鑰。用於驗證 Electron/瀏覽器 WS 中繼與 OmniRoute 之間的橋接請求。⚠️ **正式環境中為必要設定——未設定時,所有 WS 橋接請求都會遭到拒絕。**使用 `openssl rand -base64 32` 產生。 | +| `OMNIROUTE_SW_BUILD_ID` | 否 | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | PWA 殼層的明確 Service Worker 快取失效 ID(#11779);在解析鏈中優先使用。 | +| `SOURCE_VERSION` | 否 | _(未設定)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | 解析鏈中的第二順位——由 PaaS 建置工具(例如 Heroku 類型的工具)設定為已部署的提交版本。 | +| `NEXT_PUBLIC_SW_BUILD_ID` | 否 | _(衍生)_ | `src/shared/components/PwaRegister.tsx` | 用戶端用來註冊 `/sw.js?v=…` 的建置時公開值;依序衍生自上述兩個值,最後再使用 git SHA。 | +| `OMNIROUTE_PEER_STAMP_TOKEN` | 否(自動) | _(每次啟動時自動產生)_ | `src/server/authz/policies/management.ts` | 用於證明受信任對等 IP 戳記來自 OmniRoute 自有 HTTP 伺服器(`scripts/dev/peer-stamp.mjs`)的個別程序密鑰。只有在戳記包含此權杖時,授權中介軟體才會信任請求的本地性(針對 LOCAL_ONLY 路由的回送位址/LAN 閘控)。每次啟動時會自動產生——請保持未設定;僅在必須共用此戳記的多程序設定中固定此值。 | +| `OMNIROUTE_TRUSTED_PROXIES` | 否 | _(未設定)_ | `scripts/dev/peer-stamp.mjs` | 以逗號分隔的反向代理 IP 或 CIDR 範圍;IP 允許/拒絕清單可信任來自這些代理的 `X-Forwarded-For`/`X-Real-IP`,此外,回送位址、私人網路位址及 Cloudflare 邊緣節點一律受信任。僅當代理位於任何其他公用位址時才需要設定;否則,篩選器將判定該代理本身的位址。 | ### 產生命令 @@ -214,38 +214,38 @@ OmniRoute 使用 **SQLite**(透過 `better-sqlite3`)進行所有資料持久 ## 4. 安全性與驗證 -| 變數 | 預設值 | 來源檔案 | 說明 | -| ------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | 與硬體識別碼結合,用於產生機器指紋的鹽值。每次部署時應變更,以確保隔離。 | -| `OMNIROUTE_CLI_SALT` | _(未設定 = 每次安裝隨機產生鹽值,並保存於 `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | 用於衍生本機 CLI 驗證權杖的 HMAC 鹽值。設定此值會輪替該機器上的所有 CLI 權杖,且其優先順序一律高於已保存的鹽值。請參閱 `docs/security/CLI_TOKEN.md`。 | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | 設定工作階段 Cookie 的 `Secure` 旗標。在 HTTPS 後方執行時,**必須設為 `true`**。 | -| `REQUIRE_API_KEY` | `false` | API 中介軟體 | 設為 `true` 時,所有 `/v1/*` Proxy 請求都必須包含有效的 API 金鑰。**此旗標不會管控 `GET /v1/models`**;後者改為遵循儀表板的登入設定(`requireAuthForModels`)——因此,`/v1/models` 傳回 `401` 並不表示推論受到保護。請參閱 `docs/security/INFERENCE_AUTH_POSTURE.md`(#13695)。 | -| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允許在儀表板 UI 中顯示完整的 API 金鑰值。可透過儀表板的功能旗標進行設定;在共用執行個體上可能造成安全風險。 | -| `NO_LOG_API_KEY_IDS` | _(空白)_ | `src/lib/compliance/index.ts` | 以逗號分隔的 API 金鑰 ID,這些 ID 可略過請求記錄(符合 GDPR 規範)。 | -| `DEFAULT_RATE_LIMIT_PER_DAY` | _(未設定 = 無限制)_ | `src/shared/utils/apiKeyPolicy.ts` | 套用至 `rate_limits` 欄位為 null 之 API 金鑰的備援每日請求配額。未設定或空白:沒有隱含上限(#2289、#11017)。`0` 亦相同(無限制)。正整數 N 會啟用每日 N 次、每週 5N 次、每月 20N 次的限制。格式錯誤的非空白值會回復為舊版的每日 1000 次、每週 5000 次、每月 20000 次的限制視窗。 | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 允許的請求本文大小上限。超過此限制的承載資料將被拒絕。 | -| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | 實際請求本文大小達到或超過此閾值時,會在 JSON 解析前取得不可分割的行程內重量級准入租約(BYTE 路徑,包括 `POST /v1/responses`)。採用與結構繁重請求相同的 [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) 健康餘裕脫離機制;仍受 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) 限制,以防止 [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) 再次發生。 | -| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | 對聊天路由施加的硬性上限,依有限制擷取期間實際讀取的位元組數執行,包括缺少、無效或不實 `Content-Length` 的請求;超出上限時會收到 `413`。 | -| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(未設定 — 無請求數量上限)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:**此舊版請求數量上限現在僅在明確設定時才會生效。若維持未設定(預設值),重量級聊天准入會改由 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` 控制——這是依單一行程(單一 V8 堆積)的實際記憶體上限自動推導出的位元組預算。兩個重疊、約 750k-token 的 `/v1/responses` 會使約 12 GiB 的堆積中止(#7849)——這是**記憶體預算**警告,而不是產品硬性上限為 2。健康的行程(堆積低於卸載比率)可透過 `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` 准入更多並行的長時間 `/v1/responses`。數十個長時間 SSE 用戶端(40–50 個)取決於堆積 + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110,而不是「最多 2 個」。盲目提高此值以「使用主機資源」會再次引發 #7849。請使用 **N 個獨立的 `DATA_DIR`** 來增加**堆積**數量(#11024);切勿讓 `replicas>1` 共用同一個 SQLite 檔案。 | -| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(自動推導)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:**覆寫自動推導的擷取位元組預算(取 V8/cgroup 記憶體上限中較嚴格者的 25%,再除以 8 倍暫時性放大係數)。推導值與明確設定值都會限制在 8 MiB–2 GiB。大於有效預算的主體會立即以 `413 body_exceeds_budget` 失敗;個別可處理主體之間的資源爭用則仍會回傳可重試的 `503`。40–50 個並行的長時間 SSE 用戶端取決於此預算 + 堆積,而不是硬性的「最多 2 個」。調校前,請先在 `/api/monitoring/health` 讀取 `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`。 | -| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | 用於位元組和結構重量級准入的堆積壓力卸載比率(`heapUsed / heap_size_limit`)(#10183、#10268、#10437)。超過 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 的並行重量級請求,只有在堆積同時達到或高於此比率時,才會以可重試的 `503` 卸載;在健康的堆積上,則會透過健康餘裕予以准入。 | -| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (預設為 `1`) | `src/shared/middleware/chatBodyAdmission.ts` | 健康堆積快速路徑(#10437)的有界額外容量,同時適用於結構和位元組(`admitChatRequest`,包括主體 ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`)。若沒有此界限,每個繁忙但堆積健康的請求都會無上限地繞過准入控制。一旦有這麼多並行租約透過健康堆積路徑處於作用中,後續繁忙請求便會轉入與實際堆積壓力下相同的有界等待/卸載路徑。`0` 會完全停用此繞過機制。 | -| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | 即使主體低於位元組閾值,仍會將聊天請求分類為重量級的訊息數量。 | -| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | 即使主體低於位元組閾值,仍會將聊天請求分類為重量級的工具數量。 | -| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | 用於將請求分類為重量級的保守字串大小 token 估算值;這是准入成本的替代指標,而不是提供者計費使用的 token 化方式。 | -| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0`(已停用) | `src/shared/middleware/chatBodyAdmission.ts` | 可選擇啟用的聊天記錄上限。預設停用:訊息數量是部署政策,而不是請求的通用屬性;在此處設定上限,會在壓縮管線有機會使對話變得可處理之前,就以終止性的 `413` 拒絕對話。堆積成長由 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 和堆積壓力卸載機制限制。記憶體受限且需要硬性上限的部署可設定正值;超出時將收到結構化的「需要壓縮」`413`。 | -| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | 對完整緩衝至記憶體中的非串流上游回應所設的硬性上限。超過此上限時,會取消上游讀取器並使請求快速失敗,而不是讓無界字串持續增長直到耗盡堆積。 | -| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | 從上游回應標頭轉送的最大線路位元組數。超出預算時,會捨棄優先順序較低的標頭(例如自訂 `x-codex-*`、`x-oai-request-id`),以維持在常見反向代理伺服器的標頭限制內。提高此值可轉送更多上游中繼資料,但會增加回應標頭大小。 | -| `CORS_ORIGIN` | _(未設定)_ | `src/server/cors/origins.ts` | 舊版單一來源 CORS 允許清單。新部署應優先使用 `CORS_ALLOWED_ORIGINS`。CORS 僅適用於跨來源的瀏覽器 API 用戶端;已驗證身分的儀表板寫入操作改為使用同源請求,並搭配工作階段繫結的 CSRF 防護。 | -| `CORS_ALLOWED_ORIGINS` | _(未設定)_ | `src/server/cors/origins.ts` | 以逗號分隔的 CORS 允許清單。除非明確設定 `CORS_ALLOW_ALL=true`,否則不會傳送萬用字元。 | -| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 僅供開發使用的緊急選項,可回應任何瀏覽器 `Origin`。請勿在共用或正式環境部署中啟用。 | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | 封鎖以私人/回送/連結本機 IP 範圍為目標的提供者呼叫。僅可在隔離的測試環境中停用。 | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | 允許指向私人/本機網路(localhost、192.168.x.x、10.x.x.x 等)的提供者 URL。**自架提供者必須啟用此設定**(LM Studio、Ollama、vLLM、Llamafile、Triton、SearXNG)。設為 `false` 時,儀表板會拒絕驗證本機 URL。 | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | 允許新增/驗證位於本機/私人位址(127.0.0.1、localhost、LAN、私人範圍)的提供者——僅適用於提供者驗證路徑。**預設為 `true`**(本機優先);設為 `false` 可強制實施僅允許公用位址的嚴格封鎖。無論如何,雲端中繼資料端點(169.254.169.254、metadata.google.internal)仍會維持封鎖。 (#5066) | -| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | 允許 `/v1/audio/*` 路由(轉錄、語音、翻譯)使用架設於 localhost 以外位置且與 OpenAI 相容的提供者節點。預設關閉——將音訊路由至遠端主機會改變輸出流量身分,且必須由操作人員明確決定。回送/私人節點(localhost、127.0.0.1、172.16-31.x)一律允許且不受影響。 (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | 允許 `POST /v1/rerank`(以及記憶體引擎的回送重新排序步驟)使用架設於 localhost 以外位置且與 OpenAI 相容的提供者節點——例如在 LAN 主機或 Tailscale 對等節點上執行的 TEI、Infinity、vLLM 等。預設關閉——路由至遠端主機會改變輸出流量身分,且必須由操作人員明確決定。回送節點(localhost、127.0.0.1、172.16-31.x)一律允許且不受影響。遠端節點也必須通過提供者的輸出 URL 政策(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`);絕不會路由至雲端中繼資料主機。 | -| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | 啟用 OIDC 時,停用密碼登入,讓使用者只能透過 OIDC 單一登入進行驗證。也接受簡短別名 `OIDC_DISABLE_PASSWORD_LOGIN`;相同鍵名的儀表板功能旗標具有較高優先順序。 (#10889) | -| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` 的簡短別名 (#10889)。 | +| 變數 | 預設值 | 來源檔案 | 說明 | +| ------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | 與硬體識別碼結合,用於產生機器指紋的鹽值。每次部署時應變更,以確保隔離。 | +| `OMNIROUTE_CLI_SALT` | _(未設定 = 每次安裝隨機產生鹽值,並保存於 `/cli-token-salt.json`)_ | `src/lib/machineToken.ts` | 用於衍生本機 CLI 驗證權杖的 HMAC 鹽值。設定此值會輪替該機器上的所有 CLI 權杖,且其優先順序一律高於已保存的鹽值。請參閱 `docs/security/CLI_TOKEN.md`。 | +| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | 設定工作階段 Cookie 的 `Secure` 旗標。在 HTTPS 後方執行時,**必須設為 `true`**。 | +| `REQUIRE_API_KEY` | `false` | API 中介軟體 | 設為 `true` 時,所有 `/v1/*` Proxy 請求都必須包含有效的 API 金鑰。**此旗標不會管控 `GET /v1/models`**;後者改為遵循儀表板的登入設定(`requireAuthForModels`)——因此,`/v1/models` 傳回 `401` 並不表示推論受到保護。請參閱 `docs/security/INFERENCE_AUTH_POSTURE.md`(#13695)。 | +| `ALLOW_API_KEY_REVEAL` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | 允許在儀表板 UI 中顯示完整的 API 金鑰值。可透過儀表板的功能旗標進行設定;在共用執行個體上可能造成安全風險。 | +| `NO_LOG_API_KEY_IDS` | _(空白)_ | `src/lib/compliance/index.ts` | 以逗號分隔的 API 金鑰 ID,這些 ID 可略過請求記錄(符合 GDPR 規範)。 | +| `DEFAULT_RATE_LIMIT_PER_DAY` | _(未設定 = 無限制)_ | `src/shared/utils/apiKeyPolicy.ts` | 套用至 `rate_limits` 欄位為 null 之 API 金鑰的備援每日請求配額。未設定或空白:沒有隱含上限(#2289、#11017)。`0` 亦相同(無限制)。正整數 N 會啟用每日 N 次、每週 5N 次、每月 20N 次的限制。格式錯誤的非空白值會回復為舊版的每日 1000 次、每週 5000 次、每月 20000 次的限制視窗。 | +| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | 允許的請求本文大小上限。超過此限制的承載資料將被拒絕。 | +| `OMNIROUTE_CHAT_LARGE_BODY_BYTES` | `262144` (256 KB) | `src/shared/middleware/chatBodyAdmission.ts` | 實際請求本文大小達到或超過此閾值時,會在 JSON 解析前取得不可分割的行程內重量級准入租約(BYTE 路徑,包括 `POST /v1/responses`)。採用與結構繁重請求相同的 [#10437](https://github.com/diegosouzapw/OmniRoute/pull/10437) 健康餘裕脫離機制;仍受 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / [#10110](https://github.com/diegosouzapw/OmniRoute/issues/10110) 限制,以防止 [#7849](https://github.com/diegosouzapw/OmniRoute/issues/7849) 再次發生。 | +| `OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES` | `52428800` (50 MB) | `src/shared/middleware/chatBodyAdmission.ts` | 對聊天路由施加的硬性上限,依有限制擷取期間實際讀取的位元組數執行,包括缺少、無效或不實 `Content-Length` 的請求;超出上限時會收到 `413`。 | +| `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` | _(未設定 — 無請求數量上限)_ | `src/shared/middleware/chatBodyAdmission.ts` | **#503-fanout:**此舊版請求數量上限現在僅在明確設定時才會生效。若維持未設定(預設值),重量級聊天准入會改由 `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` 控制——這是依單一行程(單一 V8 堆積)的實際記憶體上限自動推導出的位元組預算。兩個重疊、約 750k-token 的 `/v1/responses` 會使約 12 GiB 的堆積中止(#7849)——這是**記憶體預算**警告,而不是產品硬性上限為 2。健康的行程(堆積低於卸載比率)可透過 `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` 准入更多並行的長時間 `/v1/responses`。數十個長時間 SSE 用戶端(40–50 個)取決於堆積 + `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` / #10110,而不是「最多 2 個」。盲目提高此值以「使用主機資源」會再次引發 #7849。請使用 **N 個獨立的 `DATA_DIR`** 來增加**堆積**數量(#11024);切勿讓 `replicas>1` 共用同一個 SQLite 檔案。 | +| `OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES` | _(自動推導)_ | `src/shared/middleware/admissionBudget.ts` | **#503-fanout:**覆寫自動推導的擷取位元組預算(取 V8/cgroup 記憶體上限中較嚴格者的 25%,再除以 8 倍暫時性放大係數)。推導值與明確設定值都會限制在 8 MiB–2 GiB。大於有效預算的主體會立即以 `413 body_exceeds_budget` 失敗;個別可處理主體之間的資源爭用則仍會回傳可重試的 `503`。40–50 個並行的長時間 SSE 用戶端取決於此預算 + 堆積,而不是硬性的「最多 2 個」。調校前,請先在 `/api/monitoring/health` 讀取 `chatAdmission.maxInflightBytes` / `budgetSource` / `pressureSeverity`。 | +| `OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO` | `0.75` | `src/shared/middleware/chatBodyAdmission.ts` | 用於位元組和結構重量級准入的堆積壓力卸載比率(`heapUsed / heap_size_limit`)(#10183、#10268、#10437)。超過 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 的並行重量級請求,只有在堆積同時達到或高於此比率時,才會以可重試的 `503` 卸載;在健康的堆積上,則會透過健康餘裕予以准入。 | +| `OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM` | `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` (預設為 `1`) | `src/shared/middleware/chatBodyAdmission.ts` | 健康堆積快速路徑(#10437)的有界額外容量,同時適用於結構和位元組(`admitChatRequest`,包括主體 ≥ `OMNIROUTE_CHAT_LARGE_BODY_BYTES`)。若沒有此界限,每個繁忙但堆積健康的請求都會無上限地繞過准入控制。一旦有這麼多並行租約透過健康堆積路徑處於作用中,後續繁忙請求便會轉入與實際堆積壓力下相同的有界等待/卸載路徑。`0` 會完全停用此繞過機制。 | +| `OMNIROUTE_CHAT_HEAVY_MESSAGE_COUNT` | `200` | `src/shared/middleware/chatBodyAdmission.ts` | 即使主體低於位元組閾值,仍會將聊天請求分類為重量級的訊息數量。 | +| `OMNIROUTE_CHAT_HEAVY_TOOL_COUNT` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | 即使主體低於位元組閾值,仍會將聊天請求分類為重量級的工具數量。 | +| `OMNIROUTE_CHAT_HEAVY_ESTIMATED_TOKENS` | `32000` | `src/shared/middleware/chatBodyAdmission.ts` | 用於將請求分類為重量級的保守字串大小 token 估算值;這是准入成本的替代指標,而不是提供者計費使用的 token 化方式。 | +| `OMNIROUTE_CHAT_HARD_MAX_MESSAGES` | `0`(已停用) | `src/shared/middleware/chatBodyAdmission.ts` | 可選擇啟用的聊天記錄上限。預設停用:訊息數量是部署政策,而不是請求的通用屬性;在此處設定上限,會在壓縮管線有機會使對話變得可處理之前,就以終止性的 `413` 拒絕對話。堆積成長由 `OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT` 和堆積壓力卸載機制限制。記憶體受限且需要硬性上限的部署可設定正值;超出時將收到結構化的「需要壓縮」`413`。 | +| `OMNIROUTE_MAX_NONSTREAMING_RESPONSE_BYTES` | `67108864` (64 MB) | `open-sse/handlers/chatCore/nonStreamingResponseBody.ts` | 對完整緩衝至記憶體中的非串流上游回應所設的硬性上限。超過此上限時,會取消上游讀取器並使請求快速失敗,而不是讓無界字串持續增長直到耗盡堆積。 | +| `OMNIROUTE_FORWARDING_HEADER_BUDGET_BYTES` | `768` | `open-sse/handlers/chatCore/responseHeaders.ts` | 從上游回應標頭轉送的最大線路位元組數。超出預算時,會捨棄優先順序較低的標頭(例如自訂 `x-codex-*`、`x-oai-request-id`),以維持在常見反向代理伺服器的標頭限制內。提高此值可轉送更多上游中繼資料,但會增加回應標頭大小。 | +| `CORS_ORIGIN` | _(未設定)_ | `src/server/cors/origins.ts` | 舊版單一來源 CORS 允許清單。新部署應優先使用 `CORS_ALLOWED_ORIGINS`。CORS 僅適用於跨來源的瀏覽器 API 用戶端;已驗證身分的儀表板寫入操作改為使用同源請求,並搭配工作階段繫結的 CSRF 防護。 | +| `CORS_ALLOWED_ORIGINS` | _(未設定)_ | `src/server/cors/origins.ts` | 以逗號分隔的 CORS 允許清單。除非明確設定 `CORS_ALLOW_ALL=true`,否則不會傳送萬用字元。 | +| `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | 僅供開發使用的緊急選項,可回應任何瀏覽器 `Origin`。請勿在共用或正式環境部署中啟用。 | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | 舊有別名(alias)。其儀表板開關(一種資料庫覆寫值)會先於此環境變數被讀取;兩者中任一為 `false`、`0`、`no` 或 `off` 時,都會以 `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` 的方式關閉主機檢查——即使該旗標的儀表板開關已儲存為關閉狀態亦然;其他值則將防護交由下面兩個旗標處理。 | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | 在遵循防護模式的提供者路徑上關閉輸出 URL 防護的主機檢查,包括雲端中繼資料封鎖(整個 169.254.0.0/16、metadata.google.internal):URL 驗證、模型探索、提供者節點基礎 URL(包括遠端重新排序節點)與代理伺服器後援測試,以及其他與提供者相關的輸出路徑,例如遠端圖片/媒體擷取。它也允許私人 webhook 目標,但雲端中繼資料/連結本機端點除外——即使啟用此旗標,它們仍會被封鎖。透過 `BaseExecutor.execute()` 分派的聊天請求仍會保留其雲端中繼資料檢查。本機提供者在預設設定下即可運作:`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS`(預設 `true`)已允許在驗證、探索與提供者節點路徑上使用本機與 LAN URL;代理伺服器後援測試與私人 webhook 目標僅參考此旗標,並在其關閉時封鎖本機/LAN 主機。覆寫 `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`。從儀表板開關儲存的值優先於此變數。 | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | 允許在本機/私人位址(127.0.0.1、localhost、LAN、私人範圍)上使用提供者 URL,適用於提供者驗證、模型探索、聊天,以及防護模式所管轄的其他與提供者相關的輸出路徑。**預設為 `true`**(本機優先):此時防護會封鎖雲端中繼資料端點——整個 169.254.0.0/16 以及已知的中繼資料主機名稱(metadata.google.internal、metadata.goog、Alibaba/Azure/Oracle IMDS、fd00:ec2::254)。設為 `false` 可進入僅限公用模式,該模式還會封鎖私人與回送主機。防護會在進行任何 DNS 查詢之前,按書寫原樣檢查主機名稱或 IP 常值;在僅限公用模式下,safe-fetch 包裝器還會額外解析主機名稱並拒絕任何私人回應。`OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` 會在其所屬列列出的路徑上關閉這些檢查。內建的本機提供者(如 LM Studio、Ollama、vLLM、Llamafile 與 Triton)在金鑰驗證與聊天時會略過所有主機檢查。從儀表板開關儲存的值優先於此變數。(#5066) | +| `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | 允許 `/v1/audio/*` 路由(轉錄、語音、翻譯)使用架設於 localhost 以外位置且與 OpenAI 相容的提供者節點。預設關閉——將音訊路由至遠端主機會改變輸出流量身分,且必須由操作人員明確決定。回送/私人節點(localhost、127.0.0.1、172.16-31.x)一律允許且不受影響。 (#3963) | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | 允許 `POST /v1/rerank`(以及記憶體引擎的回送重新排序步驟)使用架設於 localhost 以外位置且與 OpenAI 相容的提供者節點——例如在 LAN 主機或 Tailscale 對等節點上執行的 TEI、Infinity、vLLM 等。預設關閉——路由至遠端主機會改變輸出流量身分,且必須由操作人員明確決定。回送節點(localhost、127.0.0.1、172.16-31.x)一律允許且不受影響。遠端節點的基礎 URL 也必須通過提供者的輸出 URL 政策(`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`)。 | +| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | 啟用 OIDC 時,停用密碼登入,讓使用者只能透過 OIDC 單一登入進行驗證。也接受簡短別名 `OIDC_DISABLE_PASSWORD_LOGIN`;相同鍵名的儀表板功能旗標具有較高優先順序。 (#10889) | +| `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` 的簡短別名 (#10889)。 | ### 強化檢查清單 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index ed4fafcb442d..7bd1fb055be7 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -2755,6 +2755,11 @@ paths: put: tags: [Compression] summary: Update global compression settings + description: >- + Partial update: only the sent top-level keys change. `engines` merges by engine id — + an entry overwrites only the fields it sends, engines left out are kept as-is, and + until an engines row is stored the merge base is the map derived from the legacy + per-engine settings. security: - ManagementSessionAuth: [] requestBody: @@ -2775,6 +2780,19 @@ paths: autoTriggerTokens: type: integer minimum: 0 + engines: + type: object + description: >- + Per-engine toggles, merged by engine id into the stored map. Each entry + needs `enabled`; `level` survives an entry that omits it. + additionalProperties: + type: object + properties: + enabled: + type: boolean + level: + type: string + required: [enabled] rtkConfig: type: object additionalProperties: true @@ -3323,6 +3341,20 @@ paths: localCoreEndpoint: type: string nullable: true + coreConfigPath: + type: string + nullable: true + description: >- + Absolute adopted sing-box config path (lowercase .json). + Empty means no generation; each sync writes + `.generated` beside it, never into it. + coreBinaryPath: + type: string + nullable: true + description: >- + Absolute path of the core binary used to verify the + generated file before replacing the adopted file. + Empty means write beside it only. updateIntervalMinutes: type: integer default: 60 @@ -3369,7 +3401,7 @@ paths: summary: Update a proxy subscription description: >- Partial update — only fields present in the body are changed - (name/url/mode/ruleProviders/localCoreEndpoint/updateIntervalMinutes/enabled). + (name/url/mode/ruleProviders/localCoreEndpoint/coreConfigPath/coreBinaryPath/updateIntervalMinutes/enabled). security: - ManagementSessionAuth: [] parameters: @@ -3395,6 +3427,12 @@ paths: localCoreEndpoint: type: string nullable: true + coreConfigPath: + type: string + nullable: true + coreBinaryPath: + type: string + nullable: true updateIntervalMinutes: type: integer enabled: @@ -4037,11 +4075,13 @@ paths: schema: { type: string } responses: "200": - description: Token revoked + description: Token revoked (alreadyRevoked is true when it was already revoked) "403": description: Insufficient scope "404": - description: Token not found or already revoked + description: Token not found + "409": + description: Prefix matches more than one live token; nothing revoked # ─── CLI Tools ───────────────────────────────────────────────── @@ -6041,9 +6081,9 @@ paths: summary: Complete OIDC login for the dashboard admin gate description: | Validates the `state` cookie, exchanges the authorization `code` for tokens, - verifies the ID token against the issuer's JWKS (audience = client id), and — - if `oidcAllowedSubjects` is configured — checks the token's `sub`/`email` against - that allowlist. On success it mints the same 30-day `auth_token` dashboard-session + verifies the ID token against the issuer's JWKS (audience = client id), and + checks the token's `sub`/`email` against `oidcAllowedSubjects`, which must hold at + least one entry (an empty list ends in `not_configured`). On success it mints the same 30-day `auth_token` dashboard-session JWT used by password login and redirects to `/dashboard`. parameters: - name: code @@ -15033,6 +15073,19 @@ components: localCoreEndpoint: type: string nullable: true + coreConfigPath: + type: string + nullable: true + description: >- + Absolute adopted sing-box config path (lowercase .json). + Empty means no generation. + coreBinaryPath: + type: string + nullable: true + description: >- + Absolute path of the core binary used to verify the generated + file before replacing the adopted file. Null means write + beside it only. updateIntervalMinutes: type: integer lastFetchedAt: diff --git a/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md b/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md index b88e6e19dbe2..9cc592620368 100644 --- a/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md +++ b/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md @@ -1,7 +1,7 @@ --- title: "OmniRoute Fly.io Deployment Guide" -version: 3.8.40 -lastUpdated: 2026-06-28 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # OmniRoute Fly.io Deployment Guide @@ -154,10 +154,10 @@ The current project does not set `INITIAL_PASSWORD` because this deployment does If it is not set: -- The startup log will indicate the default password is `CHANGEME` -- You should change the login password in system settings as soon as possible after deployment +- You create the dashboard password in the onboarding wizard on first visit. Complete the password step: skipping it turns dashboard login off, which leaves the dashboard open to anyone who can reach the app URL +- Fly's proxy reaches the app from a non-loopback address, so after you submit the password the wizard asks for a one-time bootstrap token, which the app prints to its log at that moment: `flyctl logs --no-tail -a omniroute | Select-String BOOTSTRAP`. If that line has scrolled out of the log, restart the app and submit the wizard again to print a new token -If you want to initialize the backend password unattended, you can add it later: +To set the password unattended instead, add it before the first dashboard visit (once a password is saved, `INITIAL_PASSWORD` is ignored): - `INITIAL_PASSWORD` @@ -255,7 +255,7 @@ Notes: - `OMNIROUTE_WS_BRIDGE_SECRET` is required in production; missing it will break the WebSocket bridge handshake -If you also want to set an initial password: +If you also want to set an initial password, set it before the first dashboard visit: ```powershell flyctl secrets set INITIAL_PASSWORD=your-strong-password -a omniroute @@ -453,7 +453,7 @@ Verify both of the following: ### 12.5 Can It Run Without `INITIAL_PASSWORD`? -Yes, it can run. It will fall back to the default `CHANGEME` password. It is recommended to change the backend password as soon as possible in production. +Yes. With `INITIAL_PASSWORD` unset, you create the dashboard password in the onboarding wizard on first visit (section 5.2 covers the one-time bootstrap token and the password step). --- diff --git a/docs/reference/API_REFERENCE.md b/docs/reference/API_REFERENCE.md index da2692c0e323..4d6587787567 100644 --- a/docs/reference/API_REFERENCE.md +++ b/docs/reference/API_REFERENCE.md @@ -1,7 +1,7 @@ --- title: "API Reference" -version: 3.8.51 -lastUpdated: 2026-08-31 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # API Reference @@ -517,8 +517,8 @@ POST /v1/music/generations { "model": "kie/suno-v4.0", "prompt": "..." } > nodes (`localhost`, `127.0.0.1`, `172.16.0.0/12`) are always eligible. Nodes on any other > host — a LAN box or Tailscale peer — are eligible only when the operator enables the > `RERANK_REMOTE_PROVIDER_NODES` feature flag **and** the node's base URL passes the provider -> outbound URL policy (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); -> cloud-metadata hosts are never routed to. The memory engine's rerank step calls this route over +> outbound URL policy (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). +> The memory engine's rerank step calls this route over > loopback, so the same rule governs `rerankProviderModel` in the Memory settings. > > **Local server shapes:** the node is called at `/v1/rerank` and, on 404, at `/rerank` diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 85ef42253ca6..d373ae72a1f9 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -1,7 +1,7 @@ --- title: "Environment Variables Reference" -version: 3.8.50 -lastUpdated: 2026-08-18 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # Environment Variables Reference @@ -51,13 +51,13 @@ lastUpdated: 2026-08-18 ## 1. Required Secrets -These **must** be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults. +Unset secrets marked **Yes** are auto-generated on the first run (`JWT_SECRET`, `API_KEY_SECRET`) and persisted to `server.env` in the data directory — set them explicitly when that directory is ephemeral. The exception is `OMNIROUTE_WS_BRIDGE_SECRET`: it is not persisted to `server.env` — the server entrypoints mint an ephemeral per-boot value only the in-process bridge can use, so external bridge clients need it set explicitly. | Variable | Required | Default | Source File | Description | | ---------------------------- | -------------------- | ----------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. | | `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Yes** | `CHANGEME` | Bootstrap script | Sets the initial admin dashboard password (matches `.env.example` default — kept obviously insecure to force a change). **Change before first use.** After login, change via Dashboard → Settings → Security. | +| `INITIAL_PASSWORD` | No | _(unset)_ | `src/lib/auth/managementPassword.ts` | Sets the dashboard password at startup while none is saved yet; ignored once a password is saved. npm and source installs copy `.env.example` to `.env`, so they start with its publicly known placeholder `CHANGEME`: replace it before first boot, or set `INITIAL_PASSWORD=` (empty) in that `.env` to create the password in the onboarding wizard on first visit, as an unset value (the Docker image's default) does. Change a saved password in Dashboard → Settings → Security, or run `omniroute reset-password` (`node bin/reset-password.mjs` in a source checkout) with `DATA_DIR` set to the server's data directory. | | `OMNIROUTE_WS_BRIDGE_SECRET` | **Yes** (production) | _(unset)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Shared secret for the internal Codex Responses WebSocket bridge. Authenticates bridge requests between the Electron/browser WS relay and OmniRoute. ⚠️ **REQUIRED in production — when unset, all WS bridge requests are rejected.** Generate with `openssl rand -base64 32`. | | `OMNIROUTE_SW_BUILD_ID` | No | _(git SHA)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Explicit service-worker cache-busting id for the PWA shell (#11779); first in the resolution chain. | | `SOURCE_VERSION` | No | _(unset)_ | `next.config.mjs`, `scripts/build/assembleStandalone.mjs` | Second in the chain — set by PaaS builders (e.g. Heroku-style) as the deployed commit. | @@ -121,6 +121,8 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari | `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Flush interval (ms) for the batched spend/cost writer. Lower values reduce write coalescing; higher values reduce DB contention. | | `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Max buffered spend entries before a forced flush. Raise on high-QPS deployments; lower when bounded memory matters more. | | `OMNIROUTE_PROXY_FETCH_DEBUG` | _(unset)_ | `open-sse/utils/proxyFetch.ts` | Set to `"true"` to emit `[ProxyFetch]` debug logs on the Vercel relay path. Off by default to avoid leaking routing hints. | +| `OMNIROUTE_PROXY_CORE_BINARY_PATH` | _(unset)_ | `src/lib/proxySubscription/coreConfig/sync.ts` | Absolute path of the local proxy core binary (a file named `sing-box`) that checks a generated core configuration before it replaces the adopted file. Unset writes `.generated` beside the adopted file only. Host environment only: never stored in the database nor accepted through the management API, because the binary is executed. | +| `OMNIROUTE_PROXY_CORE_RELOAD_COMMAND` | _(unset)_ | `src/lib/proxySubscription/coreConfig/reload.ts` | How the local proxy core reloads after a verified config replacement: a JSON argument array run without a shell, or exactly `external` when an outside observer applies the file. Never stored in the database nor exposed via the API. | | `PROXY_LOG_INCLUDE_IPS` | `false` | `src/lib/proxyLogger.ts` | Set to `"true"` or `"1"` to include client/egress IPs and the account prefix in the verbose `[ProxyEgress]` process-log line. Kept OFF by default so the process log does not leak IPs or the account prefix. | | `OMNIROUTE_DEBUG` | _(unset)_ | `bin/cli/commands/quota.mjs` | Set to `1` to print per-request timing diagnostics (`[omniroute] GET completed in Nms`) from the CLI quota commands to stderr. | | `OMNIROUTE_HEALTHCHECK_PATH` | _(auto)_ | `scripts/dev/healthcheck.mjs` | Explicit path probed by the container health check. Unset, the probe derives it from `OMNIROUTE_BASE_PATH`; setting it opts back into the deep monitoring endpoint. | @@ -242,11 +244,11 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari | `CORS_ORIGIN` | _(unset)_ | `src/server/cors/origins.ts` | Legacy single-origin CORS allowlist. Prefer `CORS_ALLOWED_ORIGINS` for new deployments. CORS is only for cross-origin browser API clients; authenticated dashboard writes use same-origin requests plus session-bound CSRF protection instead. | | `CORS_ALLOWED_ORIGINS` | _(unset)_ | `src/server/cors/origins.ts` | Comma-separated CORS allowlist. No wildcard is sent unless `CORS_ALLOW_ALL=true` is explicitly configured. | | `CORS_ALLOW_ALL` | `false` | `src/server/cors/origins.ts` | Development-only escape hatch to echo any browser `Origin`. Do not enable on shared or production deployments. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuard.ts` | Allow provider URLs pointing to private/local networks (localhost, 192.168.x.x, 10.x.x.x, etc.). **REQUIRED for self-hosted providers** (LM Studio, Ollama, vLLM, Llamafile, Triton, SearXNG). When `false`, the dashboard rejects validation of local URLs. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuard.ts` | Allow adding/validating providers on local/private addresses (127.0.0.1, localhost, LAN, private ranges) — scoped to the provider validation path. **Default `true`** (local-first); set `false` to enforce strict public-only blocking. Cloud-metadata endpoints (169.254.169.254, metadata.google.internal) stay blocked regardless. (#5066) | +| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Legacy alias. Its dashboard toggle (a DB override) is read before this environment value; `false`, `0`, `no`, or `off` in either turns the host checks off the way `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` does, even when that flag's dashboard toggle is saved off; other values leave the guard to the two flags below. | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | `false` | `src/shared/network/outboundUrlGuardPolicy.ts` | Turns off the outbound URL guard's host checks, cloud-metadata block included (all of 169.254.0.0/16, metadata.google.internal), on the provider paths that follow the guard mode: URL validation, model discovery, provider-node base URLs (including remote rerank nodes), and the proxy-fallback test, among other provider-adjacent outbound paths such as remote image/media fetch. It also allows private webhook targets, except cloud-metadata/link-local endpoints, which stay blocked even with this flag on. Chat requests dispatched through `BaseExecutor.execute()` keep their cloud-metadata check. Local providers work with the defaults: `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` (default `true`) already allows local and LAN URLs on the validation, discovery, and provider-node paths; the proxy-fallback test and private webhook targets consult only this flag and block local/LAN hosts while it is off. Overrides `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=false`. A value saved from the dashboard toggle takes precedence over this variable. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | `true` | `src/shared/network/outboundUrlGuardPolicy.ts` | Allow provider URLs on local/private addresses (127.0.0.1, localhost, LAN, private ranges) for provider validation, model discovery, chat, and the other provider-adjacent outbound paths the guard mode governs. **Default `true`** (local-first): the guard then blocks cloud-metadata endpoints — all of 169.254.0.0/16 plus the known metadata hostnames (metadata.google.internal, metadata.goog, Alibaba/Azure/Oracle IMDS, fd00:ec2::254). Set `false` for public-only mode, which blocks private and loopback hosts too. The guard checks the hostname or IP literal as written, before any DNS lookup; in public-only mode the safe-fetch wrapper additionally resolves the hostname and refuses any private answer. `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true` turns these checks off on the paths its row lists. Built-in local providers, such as LM Studio, Ollama, vLLM, Llamafile, and Triton, skip all host checks for key validation and chat. A value saved from the dashboard toggle takes precedence over this variable. (#5066) | | `AUDIO_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/audioProviderNodes.ts` | Let the `/v1/audio/*` routes (transcriptions, speech, translations) use an OpenAI-compatible provider node hosted outside localhost. Off by default — routing audio to a remote host changes egress identity and must be an explicit operator decision. Loopback/private nodes (localhost, 127.0.0.1, 172.16-31.x) are always allowed and unaffected. (#3963) | -| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Let `POST /v1/rerank` (and the memory engine's loopback rerank step) use an OpenAI-compatible provider node hosted outside localhost — a LAN box or Tailscale peer running TEI, Infinity, vLLM, etc. Off by default — routing to a remote host changes egress identity and must be an explicit operator decision. Loopback nodes (localhost, 127.0.0.1, 172.16-31.x) are always allowed and unaffected. Remote nodes must also pass the provider outbound URL policy (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`); cloud-metadata hosts are never routed to. | +| `RERANK_REMOTE_PROVIDER_NODES` | `false` | `src/app/api/v1/_shared/rerankProviderNodes.ts` | Let `POST /v1/rerank` (and the memory engine's loopback rerank step) use an OpenAI-compatible provider node hosted outside localhost — a LAN box or Tailscale peer running TEI, Infinity, vLLM, etc. Off by default — routing to a remote host changes egress identity and must be an explicit operator decision. Loopback nodes (localhost, 127.0.0.1, 172.16-31.x) are always allowed and unaffected. A remote node's base URL must also pass the provider outbound URL policy (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` / `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`). | | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | When OIDC is enabled, disable password login so users can only authenticate via OIDC Single Sign-On. The bare alias `OIDC_DISABLE_PASSWORD_LOGIN` is also accepted; the Dashboard Feature Flag of the same key takes precedence. (#10889) | | `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | `src/app/api/auth/login/route.ts` | Bare alias of `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` (#10889). | @@ -374,6 +376,14 @@ OmniRoute provides a two-layer defense: request-side injection scanning and resp Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress control, geo-routing, or IP masking. +`OMNIROUTE_UPSTREAM_HTTP2_ENABLED` defaults to `true`. Set it to `false`, `0`, `no`, or +`off` to force HTTP/1.1 on the Undici dispatchers in `open-sse/utils/proxyDispatcher.ts` +and `open-sse/utils/proxyFetch.ts`: direct egress, HTTP(S)/SOCKS proxies, relays, and +their fresh-socket retries. This preserves TLS certificate verification, proxy +authentication, connection limits, and keep-alive settings. Restart OmniRoute after +changing it because dispatchers are cached and relay agents are created at startup. +The switch does not control wreq TLS fingerprinting or provider-specific transports. + | Variable | Default | Source File | Description | | ---------------------------------------- | --------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | Enable SOCKS5 proxy agent for upstream calls. Opt-out with `false`. | @@ -805,6 +815,7 @@ REQUEST_TIMEOUT_MS (global override) | `OMNIROUTE_CODEX_APPSERVER_SANDBOX` | _(unset)_ | Sandbox policy passed to the app-server turn (e.g. `read-only`, `workspace-write`, `danger-full-access`). When unset the executor defaults to `workspace-write` (hardened; previously `danger-full-access`). Per-connection override: `providerSpecificData.codexAppServerSandbox`. | | `OMNIROUTE_CODEX_APPSERVER_AUTO_APPROVE` | `false` | Auto-approve the app-server's own approval prompts (command/file/permission execution on the host). Off by default — prompts are auto-denied; harness tool calls are unaffected (they travel the separate `item/tool/call` passthrough). Accepts `true`/`1`/`yes`. Per-connection override: `providerSpecificData.codexAppServerAutoApprove`. | | `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive response headers. | +| `OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS` | `110000` | Maximum wait for response headers on streaming requests (ms). `0` disables this cap; an explicit provider `fetchStartTimeoutCapMs` takes precedence. | | `OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS` | `30000` (30s) | Maximum response-start wait (ms) for each direct no-proxy attempt. A timeout retries once on a fresh socket; set `0` to disable the bound and retain the previous behavior. | | `OMNIROUTE_LOCAL_DIRECT_HEADERS_TIMEOUT_MS` | `300000` (5 min) | Response-start floor (ms) for direct no-proxy attempts whose target host is private/LAN/loopback (for example `localhost`, `host.docker.internal`, `192.168.x.x`), so a native Ollama cold start is not cut off by the 30s default above. Acts as a floor: the effective wait is the larger of this value and `OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS`; `0` or an invalid value removes the local floor. Remote hosts are unaffected. | | `OMNIROUTE_DIRECT_RESPONSE_RETRY_TIMEOUT_MS` | `600000` (10 min) | Ceiling (ms) for the fresh-socket RETRY attempt after a pooled-attempt response-start timeout above (#13703). Applies only when the caller already attached its own deadline signal (the resolved connection/model/provider/`FETCH_TIMEOUT_MS` cascade); that signal is the real bound and fires first in the intended path, so this is a generous backstop rather than a flat cap — without it, the retry reused the identical short `OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS` window as the pooled attempt and 504'd healthy slow-TTFB reasoning models. Never allowed below the flat floor above; when the caller supplies no deadline signal at all, the retry keeps the flat floor unchanged. | @@ -1163,6 +1174,8 @@ desktop install. | `MODEL_CATALOG_INCLUDE_NAMES` | `true` | `src/shared/constants/featureFlagDefinitions.ts` | Include display-friendly `name` fields in `/v1/models` responses. Disable for clients that expect IDs only. | | `CATALOG_BUILD_TIMEOUT_MS` | `8000` (8s) | `src/app/api/v1/models/catalogCache.ts` | Cold-path wait bound for a coalesced `GET /v1/models` catalog rebuild (#12627). On timeout, a last-good 200 is served when one exists. | | `OMNIROUTE_SYNCED_CATALOG_STALE_AFTER_MS` | `2592000000` (30 days) | `src/lib/db/models/activeSyncedCatalog.ts` | Age after which a connection's synced model list stops being authoritative for routing and fails open to the registry (#12849). Never-timestamped rows count as stale. | +| `OMNIROUTE_ANALYTICS_CACHE_TTL_MS` | `60000` (60s) | `src/lib/usage/analyticsResponseCache.ts` | How long a slow `GET /api/usage/analytics` response is reused for the same query string (#15629). `0` disables the cache. | +| `OMNIROUTE_ANALYTICS_CACHE_MIN_COMPUTE_MS` | `1000` (1s) | `src/lib/usage/analyticsResponseCache.ts` | Only analytics responses that took at least this long to build are cached, so small instances always see fresh data. | | `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | Max wait for NanoBanana image generation jobs. | | `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana job polling frequency. | | `ADOBE_FIREFLY_SUBMIT_BASE_DELAY_MS` | `8000` | `open-sse/services/adobeFireflyUpscale.ts` | Base delay for the Adobe Firefly upscale submit-retry exponential backoff. | @@ -1225,9 +1238,13 @@ Anthropic-compatible provider instead. | `PROXY_HEALTH_AUTO_DEACTIVATE` | `false` | `src/lib/proxyHealth/statusPolicy.ts` | When `false` (default), automated reachability probes (the scheduler + the `/api/settings/proxies/auto-test` "Test All" button) are **read-only** and never write a proxy's status — only the operator sets active/inactive, so a flaky probe can't strand an assigned proxy (#6246). Set `true` to restore the legacy test-and-set behaviour. | | `FLUSH_EMPTY_RETRY_ENABLED` | `false` | `src/shared/utils/featureFlags.ts` | Opt-in feature flag (see [FEATURE_FLAGS.md](./FEATURE_FLAGS.md); a dashboard DB override wins). `true` (or `1`, `yes`) retries empty translated streaming turns through the normal credential path (up to `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX` retries) instead of exposing an empty 200 or an empty-content 502. | | `PROXY_POOL_EGRESS_OBSERVATION` | `false` | `src/shared/utils/featureFlags.ts` | Opt-in feature flag (see [FEATURE_FLAGS.md](./FEATURE_FLAGS.md); a dashboard DB override wins). `true` (or `1`, `yes`) shows the read-only pool egress observation under a proxy pool in the dashboard (distinct egress IPs, connections and the most seen behind one IP over the last 24 h, from the proxy log). Never used for routing. | +| `PROXY_OPERATOR_EGRESS_ENABLED` | `false` | `src/shared/utils/featureFlags.ts` | Opt-in feature flag (see [FEATURE_FLAGS.md](./FEATURE_FLAGS.md); a dashboard DB override wins). `true` (or `1`, `yes`) accepts operator-pushed dated observed addresses per pool member and merges them with the journal read for display and pool order. | +| `PROXY_OPERATOR_EGRESS_WINDOW_HOURS` | `24` | `src/lib/db/proxyOperatorEgress.ts` | Observation window in hours for operator-pushed egress rows (read on every read; no restart needed). Bounded to 1..168; out-of-range, non-numeric, or empty falls back to 24 with a single process-wide warning, never a throw. | | `PROXY_AUTO_REMOVE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler auto-remove proxies after repeated consecutive failures. | | `PROXY_AUTO_REMOVE_AFTER` | `3` | `src/lib/proxyHealth/scheduler.ts` | Consecutive failures before the scheduler auto-removes a proxy (when `PROXY_AUTO_REMOVE=true`). | | `PROXY_AUTO_DISABLE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler soft-disable (status `dead`, never deleted) a proxy after repeated consecutive failures, instead of removing it. Non-destructive alternative to `PROXY_AUTO_REMOVE`: the proxy drops out of pool/rotation resolution immediately (the alive-status filter used by scope-pool resolution already excludes it) and is automatically re-activated once it starts passing probes again. Shares the `PROXY_AUTO_REMOVE_AFTER` threshold. If both flags are `true`, `PROXY_AUTO_REMOVE` wins. | +| `PROXY_HEALTH_PASSIVE_SKIP` | `false` | `src/lib/proxyHealth/scheduler.ts` | Opt-in passive skip: when `true`, the scheduler skips the live probe for an endpoint whose recent production traffic already proves it (at least one success and no attributed proxy failure in the window, exactly one provider observed). Off by default: every proxy is probed. | +| `PROXY_PASSIVE_WINDOW_MS` | `300000` | `src/lib/proxyHealth/passiveVerdict.ts` | Window in ms of production traffic the passive skip looks at (bounded `60000..599999`). Shorter than the 10-minute sweep interval so a recent failure dominates instead of being diluted. | | `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Effective precedence is Feature Flags DB override > env var > default. | | `RATE_LIMIT_MAX_WAIT_MS` | `30000` (30s) | `src/lib/resilience/settings.ts` | Default **queue-wait** budget: how long a request may wait for a provider slot and sit queued before it is rejected, never reaching the upstream. The timer is cleared once the job starts executing — execution is bounded separately by `RATE_LIMIT_EXECUTION_MAX_WAIT_MS`. Env sets the default only: a persisted `resilienceSettings.requestQueue.maxWaitMs`, and then a per-connection `rateLimitOverrides.maxWaitMs`, each win over it. | | `RATE_LIMIT_EXECUTION_MAX_WAIT_MS` | `600000` (10 min) | `open-sse/services/rateLimitManager.ts` | Ceiling for how long an admitted request may stay in execution before its rate-limit reservation expires — decoupled from the queue-wait budget so slow fetch-start on non-incremental gateways does not time out (#12027). | @@ -1608,7 +1625,7 @@ value below unset in production deployments. ### Docs translation pipeline Used by `scripts/i18n/run-translation.mjs` (the `npm run i18n:run` command). -All five variables are unset by default — set them in `.env` only on machines +All six variables are unset by default — set them in `.env` only on machines that should be able to run the docs translator. | Variable | Default | Source File | Description | @@ -1617,6 +1634,7 @@ that should be able to run the docs translator. | `OMNIROUTE_TRANSLATION_API_KEY` | _(unset)_ | `scripts/i18n/run-translation.mjs` | Bearer token for the translation backend (never logged). | | `OMNIROUTE_TRANSLATION_MODEL` | _(unset)_ | `scripts/i18n/run-translation.mjs` | Model id, e.g. `gpt-4o-mini` or `cx/gpt-5.4-mini`. | | `OMNIROUTE_TRANSLATION_TIMEOUT_MS` | `60000` | `scripts/i18n/run-translation.mjs` | Per-request timeout in milliseconds. | +| `OMNIROUTE_TRANSLATION_REASONING_EFFORT` | _(unset)_ | `scripts/i18n/lib/translate-backend.mjs` | Optional reasoning control, sent as-is to the backend when set. | | `OMNIROUTE_TRANSLATION_CONCURRENCY` | `4` | `scripts/i18n/run-translation.mjs` | Parallel translation requests when running over multiple files / locales. | --- diff --git a/docs/reference/FEATURE_FLAGS.md b/docs/reference/FEATURE_FLAGS.md index 00208b1bc359..1b20df1d8e39 100644 --- a/docs/reference/FEATURE_FLAGS.md +++ b/docs/reference/FEATURE_FLAGS.md @@ -1,7 +1,7 @@ --- title: "Feature Flags" -version: 3.8.51 -lastUpdated: 2026-09-03 +version: 3.8.52 +lastUpdated: 2026-10-05 --- # Feature Flags @@ -46,7 +46,7 @@ A boolean flag is considered **enabled** when its effective value is `"true"`, ## Flag Catalog -80 flags across 6 categories. **Default** is the definition default — the value +81 flags across 6 categories. **Default** is the definition default — the value used when neither a DB override nor an environment variable is present. ### Security (10) @@ -59,37 +59,38 @@ used when neither a DB override nor an environment variable is present. | `PII_REDACTION_ENABLED` | boolean | `false` | Redact PII from requests (independent of `INPUT_SANITIZER_MODE`). | | `PII_RESPONSE_SANITIZATION` | boolean | `false` | Sanitize PII from provider responses. | | `PII_RESPONSE_SANITIZATION_MODE` | enum | `redact` | Mode for PII response sanitization. Values: `redact`, `warn`, `block`, `off`. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | boolean | `true` | Block outbound requests to private/internal IP ranges. | +| `OUTBOUND_SSRF_GUARD_ENABLED` | boolean | `true` | Legacy alias: a value saved on this flag's dashboard toggle is read before the environment; `false`, `0`, `no`, or `off` in either turns the outbound URL guard's host checks off like `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS`. | | `ALLOW_API_KEY_REVEAL` | boolean | `false` | Allow authenticated dashboard users to reveal stored API keys instead of only seeing masked values. | | `AUTH_LOG_INCLUDE_ACCOUNT_ID` | boolean | `false` | Include account prefix in AUTH log lines (e.g. "Using account: abc12345..."). Disabled by default so account identifiers are redacted from shared/multi-tenant process logs. Independent from Debug Mode; flipping Debug Mode does not reveal this. | | `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | boolean | `false` | When OIDC is enabled, disable password login so users can only authenticate via OIDC Single Sign-On. When disabled (default), both password login and OIDC are available. | -### Network (22) - -| Key | Type | Default | Restart | Description | -| ----------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ENABLE_TLS_FINGERPRINT` | boolean | `false` | ✓ | Enable TLS fingerprint stealth mode. | -| `AUDIO_REMOTE_PROVIDER_NODES` | boolean | `false` | | Allow the /v1/audio/* routes to use OpenAI-compatible provider nodes hosted outside localhost. Off by default — routing audio to a remote host changes egress identity and must be an explicit operator decision. Loopback nodes are always allowed and unaffected. | -| `RERANK_REMOTE_PROVIDER_NODES` | boolean | `false` | | Allow POST /v1/rerank (and the memory engine's loopback rerank step) to use OpenAI-compatible provider nodes hosted outside localhost. Off by default — routing to a remote host changes egress identity and must be an explicit operator decision. Loopback nodes are always allowed; remote nodes must also pass the provider outbound URL policy. | -| `PROXY_AUTO_SELECT_ENABLED` | boolean | `false` | | When no proxy is assigned to a connection, auto-select the first working proxy from the registry. Off by default (otherwise any registry proxy becomes a global fallback — #3332). | -| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | boolean | `false` | | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Off by default because this can change egress IP. | -| `NETWORK_ROTATION_SHARED_EGRESS_GUARD` | boolean | `true` | | On a network exception (timeout, connection refused/reset) for a multi-account rotation executor, when the failing account has no dedicated proxy, apply a short cooldown and skip other proxy-less accounts for the rest of the request instead of retrying each one. On by default (safe: no egress IP change, only reduces latency/cooldown risk on shared-egress accounts). Disable to restore immediate propagation on the first proxy-less throw. | -| `ROTATION_ATTRIBUTION` | boolean | `false` | | Opencode rotation records which account served or was skipped (masked ids only, never full account ids) and links proxy log entries to their request, so the operator can tell skipped accounts apart from unused ones. Off by default. | -| `PROXY_SKIP_RECENTLY_FAILED` | boolean | `true` | | Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. On by default; `false` restores plain selection. | -| `PROXY_POOL_SHARED_EGRESS_ORDER` | boolean | `false` | | For providers whose quota is bucketed by egress address, rank a pool member sharing a recently refused member's observed egress address just below healthy members. Order only, never excluded. Needs PROXY_SKIP_RECENTLY_FAILED, which produces the refusal signal it reads. Off by default. | -| `PROXY_POOL_EGRESS_OBSERVATION` | boolean | `false` | | Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h and how many connections used them. Read-only, computed from the proxy log, never used for routing. Off by default. | -| `OPENCODE_RESPONSES_STALL_ROTATION` | boolean | `false` | | For the OpenCode executor, watch the first body byte of a streamed Responses reply (window: `RESPONSES_FIRST_BYTE_TIMEOUT_MS`, default `15000`). A 2xx Responses stream that stays silent past the window is treated as stalled: the account is cooled down and the request rotates to the next account once; a second stall fails fast. Off by default: stalled streams keep today's wait until the stream readiness timeout. | -| `OPENCODE_USER_BLOCKED_ROTATION` | boolean | `false` | | OpenCode executor: on a 403/451 carrying a `user_blocked` refusal (not geo, not a Cloudflare fingerprint rejection), cool the refused account down and rotate to the next account at most once per request; a second refusal is returned as-is, without a success mark. Off by default: routing around an upstream user block can look like evasion and spread the flag across the fleet. | -| `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` | boolean | `false` | | OpenCode rotation: after two consecutive transient upstream failures (5xx or an empty 400), pause before the next account — 1.5s doubling per further failure, capped at 6s per pause and 10s per request, skipped on client disconnect; the failed body is released before waiting. Off by default: failover stays immediate. | -| `OPENCODE_PARK_AND_RESUME` | boolean | `false` | | OpenCode rotation: park the request after repeated transient 429s (or a fresh pool-strain marker) with a heartbeat, then replay one capped leg of up to 3 sequential accounts instead of fanning out the whole fleet. Off by default: every 429 rotates to the next account exactly as before. | -| `STREAM_READINESS_STALL_RETRY` | boolean | `false` | | Streaming chat: when the first upstream body stalls before producing a usable event, issue one bounded second attempt through the same routing path with the same readiness budget and no account penalty. Off by default: a stalled first body fails the request without a retry. | -| `FLUSH_EMPTY_RETRY_ENABLED` | boolean | `false` | | On translated streaming turns, when the upstream turn carries no usable content (reasoning-only completion or zero valuable chunks), issue bounded retries through the normal credential path (up to `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX`) before anything is exposed to the client. Off by default: empty turns keep the current behavior (empty 200 or empty-content 502). | -| `OPENCODE_POOL_RESELECT` | boolean | `false` | | OpenCode rotation: after a 429 from an egress-bucketed provider on a proxy-less account under an ambient pool context, ask the connection pool for another member for the next attempt instead of retrying the same egress address. Orders, never excludes: an exhausted pool keeps the current behavior. Off by default: every 429 rotates to the next account exactly as before. | -| `OPENCODE_RATE_LIMITED_429_EARLY_STOP` | boolean | `false` | | OpenCode rotation: stop the account wave at the first 429 classified as a real rate limit (parseable `Retry-After`, or a body naming a rate/usage limit) and return that upstream 429 unchanged. Unclassified 429s keep rotating. Off by default: the free tier is limited per egress IP (#9611), so every 429 rotates and an exhausted wave returns the last upstream 429. | -| `MITM_DISABLE_TLS_VERIFY` | boolean | `false` | ✓ | Disable TLS certificate verification for the MITM proxy. **Danger.** | -| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | boolean | `false` | | Allow provider URLs pointing to private/internal networks. | -| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | boolean | `true` | | Allow adding/validating providers on local/private addresses (127.0.0.1, localhost, LAN). On by default (local-first); disable for strict public-only blocking. Cloud-metadata stays blocked. | -| `ENABLE_CC_COMPATIBLE_PROVIDER` | boolean | `false` | ✓ | Enable Claude Code compatible provider mode. | +### Network (23) + +| Key | Type | Default | Restart | Description | +| ----------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ENABLE_TLS_FINGERPRINT` | boolean | `false` | ✓ | Enable TLS fingerprint stealth mode. | +| `AUDIO_REMOTE_PROVIDER_NODES` | boolean | `false` | | Allow the /v1/audio/* routes to use OpenAI-compatible provider nodes hosted outside localhost. Off by default — routing audio to a remote host changes egress identity and must be an explicit operator decision. Loopback nodes are always allowed and unaffected. | +| `RERANK_REMOTE_PROVIDER_NODES` | boolean | `false` | | Allow POST /v1/rerank (and the memory engine's loopback rerank step) to use OpenAI-compatible provider nodes hosted outside localhost. Off by default — routing to a remote host changes egress identity and must be an explicit operator decision. Loopback nodes are always allowed; remote nodes must also pass the provider outbound URL policy. | +| `PROXY_AUTO_SELECT_ENABLED` | boolean | `false` | | When no proxy is assigned to a connection, auto-select the first working proxy from the registry. Off by default (otherwise any registry proxy becomes a global fallback — #3332). | +| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | boolean | `false` | | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Off by default because this can change egress IP. | +| `NETWORK_ROTATION_SHARED_EGRESS_GUARD` | boolean | `true` | | On a network exception (timeout, connection refused/reset) for a multi-account rotation executor, when the failing account has no dedicated proxy, apply a short cooldown and skip other proxy-less accounts for the rest of the request instead of retrying each one. On by default (safe: no egress IP change, only reduces latency/cooldown risk on shared-egress accounts). Disable to restore immediate propagation on the first proxy-less throw. | +| `ROTATION_ATTRIBUTION` | boolean | `false` | | Opencode rotation records which account served or was skipped (masked ids only, never full account ids) and links proxy log entries to their request, so the operator can tell skipped accounts apart from unused ones. Off by default. | +| `PROXY_SKIP_RECENTLY_FAILED` | boolean | `true` | | Proxy pools and the per-account rotation of opencode stop re-serving a proxy that just failed (refused TCP probe, or a 429 received through it) for a per-process period that doubles on each repeat, up to a cap. No proxy status is written; with every candidate set aside the choice is unchanged. On by default; `false` restores plain selection. | +| `PROXY_POOL_SHARED_EGRESS_ORDER` | boolean | `false` | | For providers whose quota is bucketed by egress address, rank a pool member sharing a recently refused member's observed egress address just below healthy members. Order only, never excluded. Needs PROXY_SKIP_RECENTLY_FAILED, which produces the refusal signal it reads. Off by default. | +| `PROXY_POOL_EGRESS_OBSERVATION` | boolean | `false` | | Show, under a proxy pool in the dashboard, how many observed egress IPs served its members over the last 24 h and how many connections used them. Read-only, computed from the proxy log, never used for routing. Off by default. | +| `PROXY_OPERATOR_EGRESS_ENABLED` | boolean | `false` | | Accept operator-pushed dated observed addresses per pool member and merge them with the journal read for display and pool order. Off by default: the push route answers 404 and pool reads behave exactly as before. | +| `OPENCODE_RESPONSES_STALL_ROTATION` | boolean | `false` | | For the OpenCode executor, watch the first body byte of a streamed Responses reply (window: `RESPONSES_FIRST_BYTE_TIMEOUT_MS`, default `15000`). A 2xx Responses stream that stays silent past the window is treated as stalled: the account is cooled down and the request rotates to the next account once; a second stall fails fast. Off by default: stalled streams keep today's wait until the stream readiness timeout. | +| `OPENCODE_USER_BLOCKED_ROTATION` | boolean | `false` | | OpenCode executor: on a 403/451 carrying a `user_blocked` refusal (not geo, not a Cloudflare fingerprint rejection), cool the refused account down and rotate to the next account at most once per request; a second refusal is returned as-is, without a success mark. Off by default: routing around an upstream user block can look like evasion and spread the flag across the fleet. | +| `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` | boolean | `false` | | OpenCode rotation: after two consecutive transient upstream failures (5xx or an empty 400), pause before the next account — 1.5s doubling per further failure, capped at 6s per pause and 10s per request, skipped on client disconnect; the failed body is released before waiting. Off by default: failover stays immediate. | +| `OPENCODE_PARK_AND_RESUME` | boolean | `false` | | OpenCode rotation: park the request after repeated transient 429s (or a fresh pool-strain marker) with a heartbeat, then replay one capped leg of up to 3 sequential accounts instead of fanning out the whole fleet. Off by default: every 429 rotates to the next account exactly as before. | +| `STREAM_READINESS_STALL_RETRY` | boolean | `false` | | Streaming chat: when the first upstream body stalls before producing a usable event, issue one bounded second attempt through the same routing path with the same readiness budget and no account penalty. Off by default: a stalled first body fails the request without a retry. | +| `FLUSH_EMPTY_RETRY_ENABLED` | boolean | `false` | | On translated streaming turns, when the upstream turn carries no usable content (reasoning-only completion or zero valuable chunks), issue bounded retries through the normal credential path (up to `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX`) before anything is exposed to the client. Off by default: empty turns keep the current behavior (empty 200 or empty-content 502). | +| `OPENCODE_POOL_RESELECT` | boolean | `false` | | OpenCode rotation: after a 429 from an egress-bucketed provider on a proxy-less account under an ambient pool context, ask the connection pool for another member for the next attempt instead of retrying the same egress address. Orders, never excludes: an exhausted pool keeps the current behavior. Off by default: every 429 rotates to the next account exactly as before. | +| `OPENCODE_RATE_LIMITED_429_EARLY_STOP` | boolean | `false` | | OpenCode rotation: stop the account wave at the first 429 classified as a real rate limit (parseable `Retry-After`, or a body naming a rate/usage limit) and return that upstream 429 unchanged. Unclassified 429s keep rotating. Off by default: the free tier is limited per egress IP (#9611), so every 429 rotates and an exhausted wave returns the last upstream 429. | +| `MITM_DISABLE_TLS_VERIFY` | boolean | `false` | ✓ | Disable TLS certificate verification for the MITM proxy. **Danger.** | +| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | boolean | `false` | | Turns off the outbound URL guard's host checks, cloud-metadata block included, on provider URL validation, model discovery, provider-node base URLs, and the proxy-fallback test, and allows private webhook targets. Local and LAN URLs already pass by default on the validation, discovery, and provider-node paths (`OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS`); the proxy-fallback test and private webhook targets consult only `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` and block local/LAN hosts while it is off. | +| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | boolean | `true` | | Allow provider URLs on local/private addresses (127.0.0.1, localhost, LAN). On by default (local-first): the guard then blocks cloud-metadata endpoints (all of 169.254.0.0/16 plus the known metadata hostnames). Disable for strict public-only blocking: private and loopback hosts are blocked too. | +| `ENABLE_CC_COMPATIBLE_PROVIDER` | boolean | `false` | ✓ | Enable Claude Code compatible provider mode. | ### Policies (5) diff --git a/docs/reference/PROVIDER_REFERENCE.md b/docs/reference/PROVIDER_REFERENCE.md index 61c7664a5759..914c9152d42c 100644 --- a/docs/reference/PROVIDER_REFERENCE.md +++ b/docs/reference/PROVIDER_REFERENCE.md @@ -1,14 +1,14 @@ --- title: "Provider Reference" -version: 3.8.51 -lastUpdated: 2026-09-24 +version: 3.8.52 +lastUpdated: 2026-10-04 --- # Provider Reference > **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand. > Regenerate with: `npm run gen:provider-reference` -> **Last generated:** 2026-09-24 +> **Last generated:** 2026-10-04 Total providers: **358**. See category breakdown below. @@ -44,7 +44,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `codex-app-server` | `cxa` | OpenAI Codex (App-Server) | No-auth | [link](https://developers.openai.com/codex/cli) | No token stored by OmniRoute. The Codex CLI app-server manages its own ChatGPT sign-in (~/.codex/auth.json, auto-refreshed). Use “Sign in with ChatGPT” if the CLI is not yet authenticated. | — | | `devin-cli-agentic` | `dva` | Devin CLI Agentic Bridge | No-auth | [link](https://docs.devin.ai/work-with-devin/devin-cli) | Authentication is owned by the official Devin CLI in its isolated bridge volume. | emulated | | `duckduckgo-web` | `ddgw` | DuckDuckGo AI Chat | No-auth | [link](https://duckduckgo.com/duckchat) | No credentials required — DuckDuckGo AI Chat is anonymous and free. | emulated | -| `opencode` | `oc` | OpenCode Free | No-auth | [link](https://opencode.ai) | No API key required — uses OpenCode's public free endpoint. | — | +| `opencode` | `oc` | OpenCode Free | No-auth | [link](https://opencode.ai) | No API key required — OpenCode's free tier can only be used from within OpenCode (client-contract requests). | — | | `uncloseai` | `unc` | UncloseAI | No-auth | [link](https://uncloseai.com) | No auth required. API accepts any non-empty string as key for identification. If older built-in models return 404, use Available Models → Import from /models or Auto-Sync; verified live model: Lorbus/Qwen3.6-27B-int4-AutoRound. | — | | `veoaifree-web` | `veo-free` | Veo AI Free | No-auth, video | [link](https://veoaifree.com) | No auth required. Rate limited to 6 requests/hour per IP. | — | | `zcode` | `zc` | ZCode (GLM Coding Plan) | No-auth | [link](https://zcode.z.ai) | No API key stored by OmniRoute. The local ZCode app-server uses the existing builtin:zai-coding-plan login. | — | @@ -194,7 +194,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `freebuff` | `freebuff` | Freebuff | API key | [link](https://freebuff.com) | Enter Freebuff / Codebuff Auth Token (obtained via CLI login or automated harvester). | | `freeinference` | `freeinference` | FreeInference | API key, aggregator | [link](https://freeinference.org) | Free research access without a card; non-Harvard applicants require manual approval and no numeric quota is publicly guaranteed. | | `freemodel-dev` | `fmd` | FreeModel.dev | API key | [link](https://freemodel.dev) | $300 free credits on signup — no credit card required. Access GPT-5.4 and GPT-5.5 (OpenAI's latest flagship models) through an OpenAI-compatible API. | -| `freetheai` | `fta` | FreeTheAi | API key, aggregator | [link](https://freetheai.xyz) | Join the FreeTheAi Discord to get your free API key. | +| `freetheai` | `fta` | FreeTheAi | API key, aggregator | [link](https://freetheai.org) | Sign up at https://freetheai.org/signup for a free API key. A daily check-in unlocks the free models; linking Discord is optional and only raises the daily limit. | | `friendliai` | `friendli` | FriendliAI | API key | [link](https://friendli.ai) | Free tier for serverless inference — no credit card required | | `g4f-gemini` | `g4fgem` | g4f.space — Gemini | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html). | | `g4f-groq` | `g4fgroq` | g4f.space — Groq | API key, aggregator | [link](https://g4f.space) | Bake anonymous cake credits at g4f.dev/chat, or use a g4f.dev member key (create one at g4f.dev/members.html). | @@ -289,7 +289,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `perplexity` | `pplx` | Perplexity | API key | [link](https://www.perplexity.ai) | — | | `perplexity-agent` | `pplx-agent` | Perplexity Agent | API key | [link](https://www.perplexity.ai) | Use your Perplexity API key. OmniRoute routes Agent API model IDs through Perplexity's Responses-compatible endpoint. | | `piapi` | `pi` | PiAPI | API key, aggregator | [link](https://piapi.ai) | — | -| `pioneer` | `pn` | Pioneer AI | API key | [link](https://pioneer.ai) | No free tier — paid plans start with $40 platform credits | +| `pioneer` | `pn` | Pioneer AI | API key | [link](https://pioneer.ai) | — | | `plamo` | `plamo` | PLaMo | API key | [link](https://plamo.preferredai.jp/api) | — | | `poe` | `poe` | Poe | API key, aggregator | [link](https://creator.poe.com/api-reference) | Bearer API key for the Poe OpenAI-compatible API. | | `poixe-ai` | `poixe-ai` | Poixe AI | API key, aggregator | [link](https://poixe.com) | Current public free limits are small and model-group specific: 2 RPM/5 RPD for large-cup models and 20 RPM/50 RPD for small-cup models. | @@ -446,7 +446,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each - Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts) - Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts) -- Executors: [`open-sse/executors/`](../../open-sse/executors/) (127 implementations) +- Executors: [`open-sse/executors/`](../../open-sse/executors/) (136 implementations) - Translators: [`open-sse/translator/`](../../open-sse/translator/) ## See Also diff --git a/docs/routing/AUTO-COMBO.md b/docs/routing/AUTO-COMBO.md index 615d0c23427e..3a666f723301 100644 --- a/docs/routing/AUTO-COMBO.md +++ b/docs/routing/AUTO-COMBO.md @@ -209,6 +209,17 @@ The Auto-Combo Engine dynamically selects the best provider/model for each reque | `quality` | 0.03 | Feedback-driven output-quality signal from the routing-event quality tracker; candidates without observations receive a neutral 0.5 | | `reliability` | 0.00 | Observed success share, `1 - failureRate`, from 24h of usage history behind a ten-sample floor (real-time metrics otherwise); candidates with no observations read as 1.0. Disabled by default | +> **Unreadable quota (#15347).** When a provider has a quota fetcher but it returns nothing +> readable (failed fetch, missing credentials, message-only usage, or a malformed snapshot with no +> parseable `windows` and no finite `percentUsed`, including `percentUsed: null`), the candidate +> scores `0` on the `quota` axis and its final score is multiplied by +> `UNREADABLE_QUOTA_SOFT_DEPRIORITIZE_FACTOR` (0.5, `autoStrategy.ts`), the same soft-penalty +> pattern as the connection-status penalty. A failed usage read is evidence about the telemetry, +> not the provider, so it ranks strictly below any real reading, including a confirmed 100%-used +> one, but is never hard-blocked or evicted. A provider with no cap reports `unlimited: true` and +> scores as full quota; a provider with no registered quota fetcher has no telemetry source at all +> and keeps the default of `100`. + **Sum:** `0.1429 + 0.1605 + 0.1429 + 0.1143 + 0.0762 + (7 × 0.0476) + 0.00 + 0.00 + 0.03 + 0.00 = 1.0` as declared in `DEFAULT_WEIGHTS`; user-configured weights are renormalized into a distribution by `normalizeScoringWeights()` before scoring. ## Mode Packs diff --git a/docs/routing/PROXY_CORE_CONFIG.md b/docs/routing/PROXY_CORE_CONFIG.md new file mode 100644 index 000000000000..3ba41f11f96a --- /dev/null +++ b/docs/routing/PROXY_CORE_CONFIG.md @@ -0,0 +1,80 @@ +--- +title: "Local Proxy-Core Configuration" +--- + +# Local proxy-core configuration (opt-in) + +This subscription field generates a local-core config file. Empty (default) +means no generation: sync behaves exactly as before. + +- Set **Core config file** to the absolute path of the adopted sing-box + config (lowercase `.json` extension, e.g. + `/var/lib/omniroute/sing-box/config.json`). +- Each sync renders the model and writes `.generated` **beside** the + adopted file with an atomic, symlink-refusing write (mode 0600). Without a + core binary on the host the adopted file is never modified. +- **Verified replacement (host opt-in).** When the host environment sets + `OMNIROUTE_PROXY_CORE_BINARY_PATH` to the absolute path of a core binary + (today only a file named `sing-box`), each sync checks the rendered text + with ` check -c ` (no shell, 10 s timeout) and, only + after a pass, atomically replaces the adopted file, keeping the previous + one as `.prev`. A failed check or replace leaves the adopted file in + place and writes `.generated` with a warning. The binary path is + read from the environment only: it is never stored in the database and + the management API refuses a `coreBinaryPath` field, because the binary is + executed (Hard Rule #15). +- Only sections whose tags start with `omniroute-` are owned: owned entries + are removed then replaced, everything else (`dns`, `log`, + `experimental/clash_api`, user rules and outbounds, unknown keys) is kept + verbatim. Only sing-box is rendered so far. +- Refused without writing: unparseable JSON, a non-object root, or a file + with neither an `inbounds` nor an `outbounds` array. Nodes whose source + isn't sing-box-shaped are skipped with a reason, never converted. +- Failures and refusals surface as a sync warning and never fail the sync. + When an earlier warning already owns the sync status, generation is + skipped and the skip is logged server-side. Removing the subscription + removes its `.generated` copy and never the adopted file (the + operator's live core configuration); changing the path leaves any + previously written `.generated` file in place. +- The last switch line shows the refusal motive that fired it; live switch + activity is also visible in the application log under [SelectorControl]. +- Without any selector group the model stays empty: nodes are neither + rendered nor listed. The switch reason shows as a generic label (quota, + unreachable, transport, slow). + + activity is also visible in the application log under [SelectorControl]. + +- Without any selector group the model stays empty: nodes are neither + rendered nor listed. The switch reason shows as a generic label (quota, + unreachable, transport, slow). + +## Reloading the core after a verified replacement + +A replacement only reloads the core when two conditions hold: the verified +apply step reported `replaced`, and the rendered member digest changed +(same member tags with different node parameters reload nothing; a +beside-write without replacement reloads nothing either). + +Exactly one reload channel must be declared, in this order: + +1. **Control API** — a control URL is set on the subscription and the + active core offers an API reload. Only Clash-compatible cores qualify + (`PUT /configs?force=true`, body `{"path": ""}`); + sing-box offers no such endpoint and reloads through the command below. +2. **Command** — `OMNIROUTE_PROXY_CORE_RELOAD_COMMAND` holds a JSON + argument array, e.g. + `["/usr/bin/systemctl","--user","reload","sing-box"]`. It runs via + `execFile` with no shell and a 30 s timeout, so trapped arguments + (`$(…)`, spaces) pass through verbatim. Never stored in the database + nor exposed via the API. +3. **External observer** — the same variable holds exactly `external`: + nothing runs, the outside watcher applies the file. + +With nothing declared after a replacement, the sync stays `ok` with a +warning naming the three options (warning code CORE_RELOAD_UNDECLARED). A +failing command keeps the sync `ok` (warning code CORE_RELOAD_FAILED). Reloads are paced at +most once per 60 s per subscription; a second replacement inside the window +runs once at the deadline. Deleting the subscription clears its pending +state. The record exposes `coreReloadMode` +(`api`/`command`/`external`/`undeclared`, computed, never stored), shown in +the UI as "Applied by: …". The core is never restarted automatically. diff --git a/docs/security/ERROR_SANITIZATION.md b/docs/security/ERROR_SANITIZATION.md index e3ffe04c7764..a2d8d8bc246b 100644 --- a/docs/security/ERROR_SANITIZATION.md +++ b/docs/security/ERROR_SANITIZATION.md @@ -149,6 +149,40 @@ construct topology-bearing messages in the first place. When adding a new route or executor, copy the assertion pattern from this file. The coverage gate (`npm run test:coverage`) enforces ≥60% statements/lines/functions/branches — error paths must be covered. +### The static gate: `npm run check:error-helper` + +`scripts/check/check-error-helper.mjs` scans `open-sse/executors/`, `open-sse/handlers/`, `open-sse/mcp-server/` and every `src/app/api/**/route.ts` for a raw caught error (`err.message` / `err.stack`) or a raw upstream `body.error.message` reaching a client-facing body. + +**Trust is call-scoped, never file-scoped** (G-03, #15159). The gate used to skip an entire file the moment it saw any import from a `utils/error` path — a file-scoped exemption applied to a call-scoped hazard. One correct `import { sanitizeErrorMessage }` permanently excused every other sink in the file, which is how a live leak shipped green. Now a line is trusted only when it actually routes through a sanctioned builder or sanitizer: + +| Line shape | Trusted? | +| --------------------------------------------------------------------------------------------------------------- | ------------------ | +| calls `sanitizeErrorMessage` / `buildErrorBody` / `createErrorResult` / `toSafeMcpErrorMessage` / … | yes | +| calls a canonical builder **that this file imports** from `open-sse/utils/error` or `src/lib/api/errorResponse` | yes | +| a sanctioned builder is called **multi-line**, so the `message:` field sits on a later line | yes | +| calls a file-local `function errorResponse(...)` whose own body sanitizes | yes | +| forwards `err.message` / `err.stack` anywhere else | **no — violation** | + +Two consequences worth knowing: + +- Importing `errorResponse` is _not_ blanket trust. A file that defines its own `errorResponse` still gets flagged at the call site, because the gate resolves trust per symbol, not per file. The same holds for `createErrorResponse`. +- `const body = buildErrorBody(status, sanitizeErrorMessage(msg))` followed by `error: body.error.message` is the **sanitized** idiom used across the `*-fetch.ts` executors and is not flagged. + +Both sanctioned builder modules count: `open-sse/utils/error.ts` and `src/lib/api/errorResponse.ts`. The second is what the ~54 route handlers outside `open-sse` use, and it sanitizes both of its exports. + +Two shapes that are **not** violations, both of which the gate once reported as leaks: + +- a raw error inside an **audit row** — `saveCallLog({ error: err.message })`, `logToolCall(...)`, or a logger that takes a message first (`log.error("BATCHES", "sweep failed", { error: err.message })`). The client-facing response on the next lines may well be a static `buildErrorBody`. +- a **multi-line** sanctioned builder call, where the `message:` field names no builder at all: + ```ts + return createErrorResponse({ + status: 400, + message: error.message, + }); + ``` + +`KNOWN_MISSING_ERROR_HELPER` freezes pre-existing violations so the gate blocks only _new_ ones. `assertNoStale` drops an entry automatically once its violation is fixed, so the freeze cannot ossify. Regression guards: `tests/unit/check-error-helper.test.ts` and `tests/unit/check-error-helper-call-scope.test.ts`. + ## 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. diff --git a/electron/main.js b/electron/main.js index 7daff0284e57..11b97f58f87b 100644 --- a/electron/main.js +++ b/electron/main.js @@ -214,12 +214,26 @@ function tightenServerEnv(dataDir, serverEnvPath) { } } +// Mirrors src/lib/dataPaths.ts::getDefaultDataDir (and scripts/build/bootstrap-env.mjs). +// Electron's main process is CJS and cannot import those ESM modules, so the order is +// copied verbatim: explicit DATA_DIR → an EXISTING legacy ~/.omniroute → %APPDATA% +// (Windows) → $XDG_CONFIG_HOME (when set) → ~/.omniroute. Without the legacy check the +// desktop app persisted server.env under $XDG_CONFIG_HOME while the server it spawns +// opened ~/.omniroute/storage.sqlite. function resolveDataDir(overridePath, env = process.env) { if (overridePath && overridePath.trim()) return path.resolve(overridePath); const configured = env.DATA_DIR?.trim(); if (configured) return path.resolve(configured); + // Preserve an existing legacy dir so an upgrade never splits secrets from the database. + const legacyDir = path.join(require("os").homedir(), ".omniroute"); + try { + if (fs.statSync(legacyDir).isDirectory()) return legacyDir; + } catch { + // absent or unreadable — fall through to the platform default + } + if (process.platform === "win32") { const appData = env.APPDATA || path.join(require("os").homedir(), "AppData", "Roaming"); return path.join(appData, "omniroute"); @@ -228,7 +242,7 @@ function resolveDataDir(overridePath, env = process.env) { const xdg = env.XDG_CONFIG_HOME?.trim(); if (xdg) return path.join(path.resolve(xdg), "omniroute"); - return path.join(require("os").homedir(), ".omniroute"); + return legacyDir; } function getPreferredEnvFilePath(env = process.env) { diff --git a/electron/package-lock.json b/electron/package-lock.json index 8c98c80d08ea..4525ce95950e 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -12,7 +12,7 @@ "electron-updater": "^6.8.9" }, "devDependencies": { - "electron": "^44.4.3", + "electron": "^44.5.1", "electron-builder": "^26.15.3" }, "engines": { @@ -297,45 +297,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@electron/windows-sign": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", - "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "cross-dirname": "^0.1.0", - "debug": "^4.3.4", - "fs-extra": "^11.1.1", - "minimist": "^1.2.8", - "postject": "^1.0.0-alpha.6" - }, - "bin": { - "electron-windows-sign": "bin/electron-windows-sign.js" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.4.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", - "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -1130,15 +1091,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cross-dirname": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", - "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1415,9 +1367,9 @@ } }, "node_modules/electron": { - "version": "44.4.3", - "resolved": "https://registry.npmjs.org/electron/-/electron-44.4.3.tgz", - "integrity": "sha512-LTpSFTB40qVCXIX5xMo+cgHI/Jjkbjw7VpB26PccEbroqOn72LBukeaDwPVo1fBYzSzs0c9iPuAucFCO7Tw81Q==", + "version": "44.5.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.5.1.tgz", + "integrity": "sha512-lx7AYoFIiyvEvgvpyWO9kOC60xtNPpiPZAth6Fmg9NutzpCAMLwpUlaBY2EXfZyIgUdzEmLMj9KvK0Vrokl1yQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1459,19 +1411,6 @@ "node": ">=14.0.0" } }, - "node_modules/electron-builder-squirrel-windows": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", - "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "app-builder-lib": "26.15.3", - "builder-util": "26.15.3", - "electron-winstaller": "5.4.0" - } - }, "node_modules/electron-publish": { "version": "26.15.3", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", @@ -1506,66 +1445,6 @@ "tiny-typed-emitter": "^2.1.0" } }, - "node_modules/electron-winstaller": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", - "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@electron/asar": "^3.2.1", - "debug": "^4.1.1", - "fs-extra": "^7.0.1", - "lodash": "^4.17.21", - "temp": "^0.9.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "optionalDependencies": { - "@electron/windows-sign": "^1.1.2" - } - }, - "node_modules/electron-winstaller/node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/electron-winstaller/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "license": "MIT", - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-winstaller/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2098,9 +1977,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", "dev": true, "license": "BSD-2-Clause" }, @@ -2480,20 +2359,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2757,36 +2622,6 @@ "node": ">=18" } }, - "node_modules/postject": { - "version": "1.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", - "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "commander": "^9.4.0" - }, - "bin": { - "postject": "dist/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/postject/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -2981,21 +2816,6 @@ "node": ">= 4" } }, - "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -3251,21 +3071,6 @@ "node": ">=18" } }, - "node_modules/temp": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", - "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mkdirp": "^0.5.1", - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", diff --git a/electron/package.json b/electron/package.json index ce41fd8641c3..73cf06c34afe 100644 --- a/electron/package.json +++ b/electron/package.json @@ -28,7 +28,7 @@ "electron-updater": "^6.8.9" }, "devDependencies": { - "electron": "^44.4.3", + "electron": "^44.5.1", "electron-builder": "^26.15.3" }, "overrides": { diff --git a/eslint.config.mjs b/eslint.config.mjs index ce1d4585273d..9cb666021b53 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -229,6 +229,9 @@ const eslintConfig = [ ".omnivscodeagent/**", // _tasks/ — planning/handoff/research artifacts (gitignored, external code) "_tasks/**", + // _artifacts/ — per-session review/recheck scratch inside worktrees + // (gitignored; workflow scripts with top-level return) + "_artifacts/**", // .agents/ — skill definitions + their helper scripts (gitignored; the // canonical copy lives here and is symlinked into .claude/). ".agents/**", diff --git a/open-sse/config/codexClient.ts b/open-sse/config/codexClient.ts index 36b0bfe37b3a..823182e94e91 100644 --- a/open-sse/config/codexClient.ts +++ b/open-sse/config/codexClient.ts @@ -12,9 +12,23 @@ const DEFAULT_CODEX_USER_AGENT_PLATFORM = "Windows 10.0.26200"; const DEFAULT_CODEX_USER_AGENT_ARCH = "x64"; const CODEX_VERSION_OVERRIDE_ENV = "CODEX_CLIENT_VERSION"; const CODEX_USER_AGENT_OVERRIDE_ENV = "CODEX_USER_AGENT"; +const CODEX_RELEASE_URL = "https://api.github.com/repos/openai/codex/releases/latest"; +export const CODEX_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000; +export const CODEX_VERSION_FETCH_TIMEOUT_MS = 20_000; const SAFE_HEADER_TOKEN_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/; const SAFE_HEADER_VALUE_PATTERN = /^[\x20-\x7E]{1,200}$/; const SAFE_CODEX_SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/; +const CODEX_DOTTED_TRIPLE_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/; + +type CodexVersionCache = { + fetchedAt: number; + version: string; +}; + +type FetchLike = typeof fetch; + +let versionCache: CodexVersionCache | null = null; +let versionInFlight: Promise | null = null; function getSafeEnvValue(name: string, pattern: RegExp): string | null { const raw = process.env[name]; @@ -26,11 +40,97 @@ function getSafeEnvValue(name: string, pattern: RegExp): string | null { return normalized; } +function parseCodexReleaseTag(tagName: unknown): string | null { + if (typeof tagName !== "string") return null; + const stripped = tagName.trim().replace(/^rust-v/, ""); + const match = CODEX_DOTTED_TRIPLE_PATTERN.exec(stripped); + return match ? match[0] : null; +} + +function compareDottedTriple(left: string, right: string): number { + const leftParts = left.split(".").map((part) => Number.parseInt(part, 10)); + const rightParts = right.split(".").map((part) => Number.parseInt(part, 10)); + for (let i = 0; i < 3; i += 1) { + if (leftParts[i] !== rightParts[i]) return leftParts[i] - rightParts[i]; + } + return 0; +} + +function pickCodexVersionAtLeastPin(candidate: string | null): string { + if (candidate && compareDottedTriple(candidate, DEFAULT_CODEX_CLIENT_VERSION) > 0) { + return candidate; + } + return DEFAULT_CODEX_CLIENT_VERSION; +} + +async function fetchCodexReleaseTag(fetchImpl: FetchLike): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), CODEX_VERSION_FETCH_TIMEOUT_MS); + try { + const response = await fetchImpl(CODEX_RELEASE_URL, { + headers: { + Accept: "application/json", + "User-Agent": "OmniRoute-CodexVersion/1.0", + }, + signal: controller.signal, + }); + if (!response.ok) return null; + const payload = (await response.json()) as { tag_name?: unknown }; + return parseCodexReleaseTag(payload?.tag_name); + } catch { + return null; + } finally { + clearTimeout(timeoutId); + } +} + +/** + * Refresh the advertised Codex CLI version from the latest GitHub release. + * A fetched dotted triple newer than the pin replaces it; any failure, a + * malformed tag, or an older release keeps the pin. Cached for six hours. + */ +export function resolveCodexClientVersion(fetchImpl: FetchLike = fetch): Promise { + const now = Date.now(); + if (versionCache && now - versionCache.fetchedAt < CODEX_VERSION_CACHE_TTL_MS) { + return Promise.resolve(pickCodexVersionAtLeastPin(versionCache.version)); + } + if (versionInFlight) return versionInFlight; + + versionInFlight = (async () => { + const fetched = await fetchCodexReleaseTag(fetchImpl); + const version = pickCodexVersionAtLeastPin(fetched ?? versionCache?.version ?? null); + if (fetched) { + versionCache = { fetchedAt: Date.now(), version }; + } + return version; + })(); + + return versionInFlight.finally(() => { + versionInFlight = null; + }); +} + +export function getCachedCodexClientVersion(): string { + return pickCodexVersionAtLeastPin(versionCache?.version ?? null); +} + +export function seedCodexClientVersionCache(version: string, fetchedAt = Date.now()): void { + if (!CODEX_DOTTED_TRIPLE_PATTERN.test(version)) { + throw new TypeError(`Invalid Codex client version: ${version}`); + } + versionCache = { fetchedAt, version }; +} + +export function clearCodexClientVersionCache(): void { + versionCache = null; + versionInFlight = null; +} + export function getCodexClientVersion(): string { - return ( - getSafeEnvValue(CODEX_VERSION_OVERRIDE_ENV, SAFE_HEADER_TOKEN_PATTERN) || - DEFAULT_CODEX_CLIENT_VERSION - ); + const override = getSafeEnvValue(CODEX_VERSION_OVERRIDE_ENV, SAFE_HEADER_TOKEN_PATTERN); + if (override) return override; + if (!process.env.NODE_TEST_CONTEXT && !versionCache && !versionInFlight) void resolveCodexClientVersion(); + return getCachedCodexClientVersion(); } /** diff --git a/open-sse/config/context1m.ts b/open-sse/config/context1m.ts index 134df22bf88b..059a99898ecc 100644 --- a/open-sse/config/context1m.ts +++ b/open-sse/config/context1m.ts @@ -15,6 +15,7 @@ export const CONTEXT_1M_SUPPORTED_MODELS = [ "claude-fable-5", "claude-sonnet-5", + "claude-sonnet-5-5", "claude-sonnet-4-6", "claude-opus-4-8", "claude-opus-4-7", diff --git a/open-sse/config/providerErrorRules.ts b/open-sse/config/providerErrorRules.ts index 4c6e4e094cf7..761599546961 100644 --- a/open-sse/config/providerErrorRules.ts +++ b/open-sse/config/providerErrorRules.ts @@ -104,6 +104,23 @@ export function setOperatorProviderErrorRules( // exhausted treated as transient 429) and #10880 (egress-bucketed cooldown). function buildOpencodeRules(): ProviderErrorRule[] { return [ + { + // A 400/429 reading "endpoint is unavailable" fails over to a short model-scope + // cooldown so the next request skips the refused model instead of + // retrying it. First: the body marker is the most specific signal and + // wins over the generic header/counter rules below on conflicts. + id: "opencode-endpoint-unavailable", + match: ({ status, body }) => { + if (status !== 400 && status !== 429) return null; + const text = JSON.stringify(body ?? "").toLowerCase(); + if (!text.includes("endpoint is unavailable")) return null; + return { + reason: "model_capacity", + scope: "model", + cooldownMs: 300_000, + }; + }, + }, { id: "opencode-monthly-quota-resets-in", match: ({ status, body }) => { @@ -525,7 +542,7 @@ export function parseResetCountdownMs(text: string): number | null { } /** - * Opencode-family "Upstream request failed: Model is unavailable." 400: the rule's + * Opencode-family "Upstream request failed: Model is unavailable." 400/429: the rule's * model-scope match, or null for any other provider, status or rule. Takes the raw * error text so it stays independent of FULL_TEXT_RULE_PROVIDERS (#10880). */ @@ -535,7 +552,11 @@ export function getOpencodeModelUnavailableMatch( headers: Headers | Record | null | undefined, errorText: unknown ): ProviderErrorRuleMatch | null { - if (status !== 400 || !provider || !OPENCODE_RULE_FAMILY.includes(provider.toLowerCase())) { + if ( + (status !== 400 && status !== 429) || + !provider || + !OPENCODE_RULE_FAMILY.includes(provider.toLowerCase()) + ) { return null; } const match = getProviderErrorRuleMatch(provider, status, headers, errorText); diff --git a/open-sse/config/providerFieldStrips.ts b/open-sse/config/providerFieldStrips.ts index b38e6823ec75..65b94d9cbe9f 100644 --- a/open-sse/config/providerFieldStrips.ts +++ b/open-sse/config/providerFieldStrips.ts @@ -120,12 +120,27 @@ export function replaceRedactedAdvisorResults(body: T): { body: T; replaced: return { body: { ...(body as object), messages: nextMessages } as T, replaced }; } /** Immutably drop request fields Groq rejects with a 400. */ -export function stripGroqUnsupportedFields>(body: T): T { +export function stripGroqUnsupportedFields>( + body: T, + model?: string, + forcedEffort?: string +): T { if (!body || typeof body !== "object") return body; const next: Record = { ...body }; delete next.logprobs; delete next.logit_bias; delete next.top_logprobs; + // Groq Chat Completions accepts reasoning_effort, not the other protocol envelopes. + const requestedEffort = + forcedEffort ?? + next.reasoning_effort ?? + (next.reasoning as Record | undefined)?.effort ?? + (next.output_config as Record | undefined)?.effort; + if (model === "openai/gpt-oss-20b" && (requestedEffort === "none" || requestedEffort === "low")) { + next.reasoning_effort = "low"; + } + delete next.reasoning; + delete next.output_config; if (Array.isArray(next.messages)) { next.messages = next.messages.map((m) => { if (m && typeof m === "object") { diff --git a/open-sse/config/providerHeaderProfiles.ts b/open-sse/config/providerHeaderProfiles.ts index 91025f6522cf..6810076e5b8e 100644 --- a/open-sse/config/providerHeaderProfiles.ts +++ b/open-sse/config/providerHeaderProfiles.ts @@ -6,11 +6,23 @@ import type { AntigravityClientProfile } from "@/shared/constants/antigravityCli // VS Code Copilot Chat extension. The CLI's `copilot-developer-cli` integration // id is the catalog-unlock lever: it exposes the full entitled model set // (gemini-3.x, gpt-5.4-nano, the full opus reasoning range) where `vscode-chat` -// returns a narrower list. Version strings track the live-captured CLI 1.0.88. +// returns a narrower list. The default version tracks the supported CLI +// package (1.0.91) and follows a newer `@github/copilot` publish when the +// registry answers. Copilot gates models on that version and 400s a pin +// that is behind. export const GITHUB_COPILOT_API_VERSION = "2026-08-01"; -export const GITHUB_COPILOT_CLI_VERSION = "1.0.88"; +export const GITHUB_COPILOT_CLI_VERSION = "1.0.91"; const GITHUB_COPILOT_VERSION_OVERRIDE_ENV = "GITHUB_COPILOT_CLI_VERSION"; const SAFE_COPILOT_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/; +const COPILOT_DOTTED_TRIPLE_PATTERN = /^\d+\.\d+\.\d+$/; +const NPM_GITHUB_COPILOT_LATEST_URL = "https://registry.npmjs.org/@github/copilot/latest"; +export const GITHUB_COPILOT_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000; +export const GITHUB_COPILOT_VERSION_FETCH_TIMEOUT_MS = 20_000; + +type CopilotFetchLike = typeof fetch; + +let copilotVersionCache: { fetchedAt: number; version: string } | null = null; +let copilotVersionInFlight: Promise | null = null; function getSafeCopilotEnvValue(name: string, pattern: RegExp): string | null { const raw = typeof process === "undefined" ? undefined : process.env?.[name]; @@ -22,12 +34,100 @@ function getSafeCopilotEnvValue(name: string, pattern: RegExp): string | null { return normalized; } +function parseCopilotDottedTriple(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return COPILOT_DOTTED_TRIPLE_PATTERN.test(trimmed) ? trimmed : null; +} + +function compareCopilotDottedTriple(left: string, right: string): number { + const leftParts = left.split(".").map((part) => Number.parseInt(part, 10) || 0); + const rightParts = right.split(".").map((part) => Number.parseInt(part, 10) || 0); + for (let i = 0; i < 3; i += 1) { + if (leftParts[i] !== rightParts[i]) return leftParts[i] - rightParts[i]; + } + return 0; +} + +/** A fetched version replaces the pin only when it is strictly newer. */ +function pickCopilotVersionAtLeastPin(candidate: string | null): string { + if (candidate && compareCopilotDottedTriple(candidate, GITHUB_COPILOT_CLI_VERSION) > 0) { + return candidate; + } + return GITHUB_COPILOT_CLI_VERSION; +} + +function readFreshCopilotVersionCache(now = Date.now()): string | null { + if (!copilotVersionCache) return null; + if (now - copilotVersionCache.fetchedAt >= GITHUB_COPILOT_VERSION_CACHE_TTL_MS) return null; + return copilotVersionCache.version; +} + +/** + * Refresh the advertised Copilot CLI version from `@github/copilot` on npm. + * A dotted triple newer than 1.0.91 replaces it; any failure, a non-triple, + * or an older publish keeps the pin. Cached for six hours. The fetch aborts + * at 20s — registry responses from this host regularly exceed a few seconds. + */ +export function resolveGitHubCopilotCliVersion( + fetchImpl: CopilotFetchLike = fetch +): Promise { + const now = Date.now(); + const fresh = readFreshCopilotVersionCache(now); + if (fresh) return Promise.resolve(pickCopilotVersionAtLeastPin(fresh)); + if (copilotVersionInFlight) return copilotVersionInFlight; + + copilotVersionInFlight = (async () => { + let resolved: string | null = null; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), GITHUB_COPILOT_VERSION_FETCH_TIMEOUT_MS); + try { + const response = await fetchImpl(NPM_GITHUB_COPILOT_LATEST_URL, { + headers: { + Accept: "application/json", + "User-Agent": "OmniRoute-CopilotVersion/1.0", + }, + signal: controller.signal, + }); + if (response.ok) { + const payload = (await response.json()) as { version?: unknown }; + resolved = parseCopilotDottedTriple(payload?.version); + } + } catch { + resolved = null; + } finally { + clearTimeout(timeoutId); + } + + if (resolved && compareCopilotDottedTriple(resolved, GITHUB_COPILOT_CLI_VERSION) > 0) { + copilotVersionCache = { fetchedAt: Date.now(), version: resolved }; + } + return pickCopilotVersionAtLeastPin(resolved ?? readFreshCopilotVersionCache()); + })(); + + const current = copilotVersionInFlight; + void current.finally(() => { + if (copilotVersionInFlight === current) copilotVersionInFlight = null; + }); + return current; +} + +/** Test seam: drop the registry cache so the next resolve hits the network. */ +export function resetGitHubCopilotCliVersionCache(): void { + copilotVersionCache = null; + copilotVersionInFlight = null; +} + /** Captured pin, overridable via GITHUB_COPILOT_CLI_VERSION (#12417). */ export function getGitHubCopilotCliVersion(): string { - return ( - getSafeCopilotEnvValue(GITHUB_COPILOT_VERSION_OVERRIDE_ENV, SAFE_COPILOT_VERSION_PATTERN) || - GITHUB_COPILOT_CLI_VERSION + const override = getSafeCopilotEnvValue( + GITHUB_COPILOT_VERSION_OVERRIDE_ENV, + SAFE_COPILOT_VERSION_PATTERN ); + if (override) return override; + if (!process.env.NODE_TEST_CONTEXT && !readFreshCopilotVersionCache() && !copilotVersionInFlight) + void resolveGitHubCopilotCliVersion(); + return pickCopilotVersionAtLeastPin(readFreshCopilotVersionCache()); } export const GITHUB_COPILOT_EDITOR_VERSION = `copilot/${GITHUB_COPILOT_CLI_VERSION}`; @@ -82,14 +182,199 @@ export const QWEN_STAINLESS_LANG = "js"; export const QODER_DEFAULT_USER_AGENT = "Qoder-Cli"; -// CODEBUDDY_CN_USER_AGENT is the single source of truth for the CLI/CodeBuddy version +// CODEBUDDY_CN_USER_AGENT is the captured pin for the CLI/CodeBuddy version // string. It MUST stay identical across OAuth (src/lib/oauth/constants/oauth.ts), chat // completions (open-sse/config/providers/registry/codebuddy-cn/index.ts) and usage/quota -// (open-sse/services/usage/codebuddy-cn.ts) — a mismatched version string across a +// (open-sse/services/usage/codebuddy-cn.ts) - a mismatched version string across a // single account's auth vs. chat calls is exactly the kind of internally-inconsistent -// client fingerprint Tencent's WAF flags as anomalous (#12702). +// client fingerprint Tencent's WAF flags as anomalous (#12702). Request paths read +// getCodeBuddyCnUserAgent(), which may replace both numbers with a newer npm publish. export const CODEBUDDY_CN_USER_AGENT = "CLI/2.108.1 CodeBuddy/2.108.1"; +const QWEN_DOTTED_TRIPLE_PATTERN = /^\d+\.\d+\.\d+$/; +const NPM_QWEN_CODE_LATEST_URL = "https://registry.npmjs.org/@qwen-code/qwen-code/latest"; +const NPM_CODEBUDDY_LATEST_URL = "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest"; +export const QWEN_CLI_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000; +export const QWEN_CLI_VERSION_FETCH_TIMEOUT_MS = 20_000; +export const CODEBUDDY_CN_VERSION_CACHE_TTL_MS = QWEN_CLI_VERSION_CACHE_TTL_MS; +export const CODEBUDDY_CN_VERSION_FETCH_TIMEOUT_MS = QWEN_CLI_VERSION_FETCH_TIMEOUT_MS; + +type NpmFetchLike = typeof fetch; + +type VersionCache = { fetchedAt: number; version: string }; + +let qwenVersionCache: VersionCache | null = null; +let qwenVersionInFlight: Promise | null = null; +let codeBuddyVersionCache: VersionCache | null = null; +let codeBuddyVersionInFlight: Promise | null = null; + +function parseDottedTriple(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return QWEN_DOTTED_TRIPLE_PATTERN.test(trimmed) ? trimmed : null; +} + +function compareDottedTriple(left: string, right: string): number { + const leftParts = left.split(".").map((part) => Number.parseInt(part, 10) || 0); + const rightParts = right.split(".").map((part) => Number.parseInt(part, 10) || 0); + for (let i = 0; i < 3; i += 1) { + if (leftParts[i] !== rightParts[i]) return leftParts[i] - rightParts[i]; + } + return 0; +} + +function readFreshVersionCache( + cache: VersionCache | null, + ttlMs: number, + now = Date.now() +): string | null { + if (!cache) return null; + if (now - cache.fetchedAt >= ttlMs) return null; + return cache.version; +} + +function shouldAutoRefreshCliVersion(): boolean { + if (typeof process === "undefined") return false; + return !process.env.NODE_TEST_CONTEXT; +} + +/** + * Warm one npm `latest` document. A dotted triple newer than `pin` is cached + * for six hours; a rejected fetch, a non-2xx, a non-triple, or an older + * publish leaves the pin in place. The fetch aborts at 20s. + */ +function refreshNpmDottedTriple(options: { + url: string; + pin: string; + userAgent: string; + timeoutMs: number; + ttlMs: number; + readCache: () => VersionCache | null; + setCache: (next: VersionCache) => void; + readInFlight: () => Promise | null; + setInFlight: (next: Promise | null) => void; + fetchImpl: NpmFetchLike; +}): Promise { + const fresh = readFreshVersionCache(options.readCache(), options.ttlMs); + if (fresh && compareDottedTriple(fresh, options.pin) > 0) return Promise.resolve(fresh); + const existing = options.readInFlight(); + if (existing) return existing; + + const current = (async () => { + let resolved: string | null = null; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs); + try { + const response = await options.fetchImpl(options.url, { + headers: { + Accept: "application/json", + "User-Agent": options.userAgent, + }, + signal: controller.signal, + }); + if (response.ok) { + const payload = (await response.json()) as { version?: unknown }; + resolved = parseDottedTriple(payload?.version); + } + } catch { + resolved = null; + } finally { + clearTimeout(timeoutId); + } + + if (resolved && compareDottedTriple(resolved, options.pin) > 0) { + options.setCache({ fetchedAt: Date.now(), version: resolved }); + return resolved; + } + const stillFresh = readFreshVersionCache(options.readCache(), options.ttlMs); + if (stillFresh && compareDottedTriple(stillFresh, options.pin) > 0) return stillFresh; + return options.pin; + })(); + + options.setInFlight(current); + void current.finally(() => { + if (options.readInFlight() === current) options.setInFlight(null); + }); + return current; +} + +/** Sync hot path. Cache if newer than the pin, else the pin. Never waits. */ +export function getQwenCliVersion(): string { + if (shouldAutoRefreshCliVersion() && !qwenVersionInFlight) { + const fresh = readFreshVersionCache(qwenVersionCache, QWEN_CLI_VERSION_CACHE_TTL_MS); + if (!fresh || compareDottedTriple(fresh, QWEN_CLI_VERSION) <= 0) { + void refreshQwenCliVersion(); + } + } + const fresh = readFreshVersionCache(qwenVersionCache, QWEN_CLI_VERSION_CACHE_TTL_MS); + if (fresh && compareDottedTriple(fresh, QWEN_CLI_VERSION) > 0) return fresh; + return QWEN_CLI_VERSION; +} + +export function refreshQwenCliVersion(fetchImpl: NpmFetchLike = fetch): Promise { + return refreshNpmDottedTriple({ + url: NPM_QWEN_CODE_LATEST_URL, + pin: QWEN_CLI_VERSION, + userAgent: "OmniRoute-QwenCodeVersion/1.0", + timeoutMs: QWEN_CLI_VERSION_FETCH_TIMEOUT_MS, + ttlMs: QWEN_CLI_VERSION_CACHE_TTL_MS, + readCache: () => qwenVersionCache, + setCache: (next) => { + qwenVersionCache = next; + }, + readInFlight: () => qwenVersionInFlight, + setInFlight: (next) => { + qwenVersionInFlight = next; + }, + fetchImpl, + }); +} + +export function resetQwenCliVersionCache(): void { + qwenVersionCache = null; + qwenVersionInFlight = null; +} + +function formatCodeBuddyCnUserAgent(version: string): string { + return `CLI/${version} CodeBuddy/${version}`; +} + +/** Sync hot path. Both numbers move together, or the captured pin stays. */ +export function getCodeBuddyCnUserAgent(): string { + if (shouldAutoRefreshCliVersion() && !codeBuddyVersionInFlight) { + const fresh = readFreshVersionCache(codeBuddyVersionCache, CODEBUDDY_CN_VERSION_CACHE_TTL_MS); + if (!fresh) void refreshCodeBuddyCnUserAgent(); + } + const fresh = readFreshVersionCache(codeBuddyVersionCache, CODEBUDDY_CN_VERSION_CACHE_TTL_MS); + if (fresh) return formatCodeBuddyCnUserAgent(fresh); + return CODEBUDDY_CN_USER_AGENT; +} + +export function refreshCodeBuddyCnUserAgent(fetchImpl: NpmFetchLike = fetch): Promise { + const pinVersion = CODEBUDDY_CN_USER_AGENT.match(/(\d+\.\d+\.\d+)/)?.[1] ?? "0.0.0"; + return refreshNpmDottedTriple({ + url: NPM_CODEBUDDY_LATEST_URL, + pin: pinVersion, + userAgent: "OmniRoute-CodeBuddyVersion/1.0", + timeoutMs: CODEBUDDY_CN_VERSION_FETCH_TIMEOUT_MS, + ttlMs: CODEBUDDY_CN_VERSION_CACHE_TTL_MS, + readCache: () => codeBuddyVersionCache, + setCache: (next) => { + codeBuddyVersionCache = next; + }, + readInFlight: () => codeBuddyVersionInFlight, + setInFlight: (next) => { + codeBuddyVersionInFlight = next; + }, + fetchImpl, + }).then((version) => formatCodeBuddyCnUserAgent(version)); +} + +export function resetCodeBuddyCnVersionCache(): void { + codeBuddyVersionCache = null; + codeBuddyVersionInFlight = null; +} + export const KIRO_SDK_USER_AGENT = "AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"; export const KIRO_AMZ_USER_AGENT = "aws-sdk-js/3.0.0 kiro-ide/1.0.0"; export const KIRO_STREAMING_TARGET = @@ -102,8 +387,8 @@ export function getGitHubCopilotChatHeaders( initiator = GITHUB_COPILOT_DEFAULT_INITIATOR, options: { vision?: boolean; intent?: string; integrationId?: string } = {} ): Record { - // Matches the live @github/copilot CLI 1.0.88 inference request 1:1 (MITM- - // captured). NOTE the CLI does NOT send `editor-plugin-version` nor + // Header shape follows a live @github/copilot CLI 1.0.88 inference capture. + // NOTE the CLI does NOT send `editor-plugin-version` nor // `x-vscode-user-agent-library-version` on the inference path — those belong // to the VS Code Copilot Chat extension, not the CLI. Sending an incomplete // OR an over-complete header fingerprint is itself a flagging signal, so we @@ -174,7 +459,7 @@ export function normalizeStainlessArch(arch: string = getRuntimeArch()): string return arch ? `other:${arch}` : "unknown"; } -export function getQwenCliUserAgent(version = QWEN_CLI_VERSION): string { +export function getQwenCliUserAgent(version = getQwenCliVersion()): string { // Qoder's DashScope-compatible backend expects Qwen Code's runtime-derived wire identity. // Keep it runtime-derived so packaged deployments use their own platform/architecture. return `QwenCode/${version} (${getRuntimePlatform()}; ${getRuntimeArch()})`; diff --git a/open-sse/config/providerModels.ts b/open-sse/config/providerModels.ts index a152affcc58a..1dd32a5b0d0b 100644 --- a/open-sse/config/providerModels.ts +++ b/open-sse/config/providerModels.ts @@ -286,6 +286,18 @@ export function getModelTargetFormat(aliasOrId: string, modelId: string): string // executor actually sends the request (same pattern as the openai "-pro" heuristic above, // #5842). if ((alias === "gh" || alias === "ghe-copilot") && /claude/i.test(bareModelId)) return "claude"; + // #15499: GheCopilotExecutor.buildUrl sends any unregistered `gpt-6*` id to + // /responses (`/^gpt-6/i`, same gate as supportsResponsesEndpoint). A missing + // catalog tag otherwise leaves the body on the provider's chat-completions + // format (`messages` / `max_tokens`), which /responses rejects. github.com + // Copilot does not use that URL regex, so this stays ghe-copilot-only. + if ( + alias === "ghe-copilot" && + /^gpt-6/i.test(bareModelId) && + !/gemini|claude/i.test(bareModelId) + ) { + return "openai-responses"; + } // Model-level targetFormat is provider-scoped: a catalog entry declares how THIS // provider's endpoint serves the model — do NOT import another provider's tag. // #9994 scoped this for providers WITH a catalog; #10072 extends it to catalogless @@ -324,7 +336,11 @@ export function getModelTimeoutMs(aliasOrId: string, modelId: string): number | } const CLAUDE_MODEL_PATTERN = /(?:^|[\/._-])claude(?:[._-]|$)/; -const CLAUDE_MAX_EFFORT_UNSUPPORTED_FAMILY_PATTERNS = [/(?:^|[\/._-])haiku(?:[._-]|$)/] as const; +const CLAUDE_MAX_EFFORT_UNSUPPORTED_FAMILY_PATTERNS = [ + /(?:^|[\/._-])haiku(?:[._-]|$)/, + // Sonnet 5.5 caps effort at xhigh; max returns a 400. + /(?:^|[\/._-])claude-sonnet-5-5(?:[._-]|$)/, +] as const; const ANTHROPIC_COMPATIBLE_PREFIX = "anthropic-compatible-"; export function supportsClaudeMaxEffort(modelId: string | null | undefined): boolean { diff --git a/open-sse/config/providers/registry/anthropic/index.ts b/open-sse/config/providers/registry/anthropic/index.ts index 2710c191403f..286b918c8f1d 100644 --- a/open-sse/config/providers/registry/anthropic/index.ts +++ b/open-sse/config/providers/registry/anthropic/index.ts @@ -85,6 +85,13 @@ export const anthropicProvider: RegistryEntry = { // Sonnet 5 rejects non-default sampling params with a 400 (adaptive-only). unsupportedParams: ["temperature", "top_p", "top_k"], }, + { + id: "claude-sonnet-5-5", + name: "Claude Sonnet 5.5", + contextLength: 1048576, + supportedThinkingEfforts: ["low", "medium", "high", "xhigh"], + unsupportedParams: ["temperature", "top_p", "top_k"], + }, { id: "claude-sonnet-4.6", name: "Claude Sonnet 4.6" }, { id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5" }, { id: "claude-haiku-4.5", name: "Claude Haiku 4.5" }, diff --git a/open-sse/config/providers/registry/claude/index.ts b/open-sse/config/providers/registry/claude/index.ts index 9bf7419df3e7..300924236962 100644 --- a/open-sse/config/providers/registry/claude/index.ts +++ b/open-sse/config/providers/registry/claude/index.ts @@ -110,6 +110,17 @@ export const claudeProvider: RegistryEntry = { // reasoning steered by output_config.effort). Mirrors the Opus/Fable entries. unsupportedParams: ["temperature", "top_p", "top_k"], }, + { + id: "claude-sonnet-5-5", + name: "Claude Sonnet 5.5", + contextLength: 1000000, + maxOutputTokens: 128000, + supportsXHighEffort: true, + // Sonnet 5.5 caps effort at xhigh. Listing the tiers here makes the + // executor lower a requested "max" to "xhigh" instead of forwarding it. + supportedThinkingEfforts: ["low", "medium", "high", "xhigh"], + unsupportedParams: ["temperature", "top_p", "top_k"], + }, { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", diff --git a/open-sse/config/providers/registry/claude/web/index.ts b/open-sse/config/providers/registry/claude/web/index.ts index 07c487236241..6876810fcad9 100644 --- a/open-sse/config/providers/registry/claude/web/index.ts +++ b/open-sse/config/providers/registry/claude/web/index.ts @@ -34,6 +34,7 @@ export const claude_webProvider: RegistryEntry = { { id: "claude-opus-4-7", name: "Claude Opus 4.7 (web)", toolCalling: false }, { id: "claude-opus-4-6", name: "Claude Opus 4.6 (web)", toolCalling: false }, { id: "claude-sonnet-5", name: "Claude Sonnet 5 (web)", toolCalling: false }, + { id: "claude-sonnet-5-5", name: "Claude Sonnet 5.5 (web)", toolCalling: false }, { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (web)", toolCalling: false }, { id: "claude-haiku-4-5-20251001", diff --git a/open-sse/config/providers/registry/command-code/index.ts b/open-sse/config/providers/registry/command-code/index.ts index 2a8bd998c890..34c6938d67ff 100644 --- a/open-sse/config/providers/registry/command-code/index.ts +++ b/open-sse/config/providers/registry/command-code/index.ts @@ -32,24 +32,37 @@ export const command_codeProvider: RegistryEntry = { { id: "claude-opus-4-7", name: "Claude Opus 4.7 (CC)", + targetFormat: "claude", supportsReasoning: true, supportedThinkingEfforts: COMMAND_CODE_REASONING_EFFORTS, supportsVision: true, contextLength: 200000, maxOutputTokens: 32000, }, + { + id: "claude-fable-5", + name: "Claude Fable 5 (CC)", + targetFormat: "claude", + }, { id: "claude-opus-4-6", name: "Claude Opus 4.6 (CC)", + targetFormat: "claude", supportsReasoning: true, supportedThinkingEfforts: COMMAND_CODE_REASONING_EFFORTS, supportsVision: true, contextLength: 200000, maxOutputTokens: 32000, }, + { + id: "claude-sonnet-5-5", + name: "Claude Sonnet 5.5 (CC)", + targetFormat: "claude", + }, { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (CC)", + targetFormat: "claude", supportsReasoning: true, supportedThinkingEfforts: COMMAND_CODE_REASONING_EFFORTS, supportsVision: true, @@ -59,6 +72,7 @@ export const command_codeProvider: RegistryEntry = { { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5 (CC)", + targetFormat: "claude", supportsReasoning: true, supportedThinkingEfforts: COMMAND_CODE_REASONING_EFFORTS, supportsVision: true, diff --git a/open-sse/config/providers/registry/cursor/index.ts b/open-sse/config/providers/registry/cursor/index.ts index 4b5b3a86defa..cb89440288d2 100644 --- a/open-sse/config/providers/registry/cursor/index.ts +++ b/open-sse/config/providers/registry/cursor/index.ts @@ -91,6 +91,25 @@ export const cursorProvider: RegistryEntry = { 300_000, "claude-fable-5-1" ), + ...withOneMillionContext( + [ + { id: "claude-opus-5-5-max-fast", name: "Claude Opus 5.5 Max Fast" }, + { id: "claude-opus-5-5-max", name: "Claude Opus 5.5 Max" }, + { id: "claude-opus-5-5-xhigh-fast", name: "Claude Opus 5.5 Xhigh Fast" }, + { id: "claude-opus-5-5-xhigh", name: "Claude Opus 5.5 Xhigh" }, + { id: "claude-opus-5-5-high-fast", name: "Claude Opus 5.5 High Fast" }, + { id: "claude-opus-5-5-high", name: "Claude Opus 5.5 High" }, + { id: "claude-opus-5-5-medium-fast", name: "Claude Opus 5.5 Medium Fast" }, + { id: "claude-opus-5-5-medium", name: "Claude Opus 5.5 Medium" }, + { id: "claude-opus-5-5-low-fast", name: "Claude Opus 5.5 Low Fast" }, + { id: "claude-opus-5-5-low", name: "Claude Opus 5.5 Low" }, + ], + "Claude Opus 5.5", + 300_000, + "claude-opus-5-5", + // No `-1m` wire mapping exists for this family in requestedModelParameters yet. + () => false + ), ...withOneMillionContext( [ { id: "claude-opus-5-thinking-max-fast", name: "Claude Opus 5 Max Thinking Fast" }, diff --git a/open-sse/config/providers/registry/freetheai/index.ts b/open-sse/config/providers/registry/freetheai/index.ts index b0b3911e8943..04b357f6372d 100644 --- a/open-sse/config/providers/registry/freetheai/index.ts +++ b/open-sse/config/providers/registry/freetheai/index.ts @@ -1,23 +1,29 @@ import type { RegistryEntry } from "../../shared.ts"; -// FreeTheAi — OpenAI-compatible gateway with a Discord-signup free tier -// (issue #6670). Same shape as the chutes aggregator entries: -// standard OpenAI chat/completions + /v1/models discovery, so no custom -// executor/translator is needed. +// FreeTheAi — OpenAI-compatible gateway with a free tier (issue #6670). +// Same shape as the chutes aggregator entries: standard OpenAI +// chat/completions + /v1/models discovery, so no custom executor/translator +// is needed. +// +// #15385: the gateway moved freetheai.xyz → freetheai.org (the .xyz host is +// dead) and signup moved off Discord. Model ids are namespaced per upstream +// group and MUST be sent in full — `fta/zai/glm-5.3`, not a bare `glm-5.3`. export const freetheaiProvider: RegistryEntry = { id: "freetheai", alias: "fta", format: "openai", executor: "default", - baseUrl: "https://api.freetheai.xyz/v1/chat/completions", - modelsUrl: "https://api.freetheai.xyz/v1/models", + baseUrl: "https://api.freetheai.org/v1/chat/completions", + modelsUrl: "https://api.freetheai.org/v1/models", authType: "apikey", authHeader: "bearer", passthroughModels: true, defaultContextLength: 128000, + // Fallback only — `passthroughModels: true` makes the live /v1/models list + // authoritative after the first sync. These are the upstream-documented ids; + // the daily check-in (not this seed) is what actually unlocks them. models: [ - { id: "gpt-4o-mini", name: "GPT-4o Mini" }, - { id: "llama-3.3-70b-instruct", name: "Llama 3.3 70B" }, - { id: "deepseek-chat", name: "DeepSeek Chat" }, + { id: "fta/zai/glm-5.3", name: "GLM 5.3 (zai)" }, + { id: "fta/kimi/k3", name: "Kimi K3" }, ], }; diff --git a/open-sse/config/providers/registry/ghe-copilot/index.ts b/open-sse/config/providers/registry/ghe-copilot/index.ts index cb559b064d9c..d7330d9d4d3f 100644 --- a/open-sse/config/providers/registry/ghe-copilot/index.ts +++ b/open-sse/config/providers/registry/ghe-copilot/index.ts @@ -35,6 +35,7 @@ export const gheCopilotProvider: RegistryEntry = { { id: "claude-fable-5", name: "Claude Fable 5", + targetFormat: "claude", contextLength: 1000000, maxOutputTokens: 64000, supportsReasoning: true, @@ -44,6 +45,7 @@ export const gheCopilotProvider: RegistryEntry = { { id: "claude-opus-5", name: "Claude Opus 5", + targetFormat: "claude", contextLength: 1000000, maxOutputTokens: 64000, supportsReasoning: true, @@ -54,6 +56,7 @@ export const gheCopilotProvider: RegistryEntry = { { id: "claude-opus-4.8-fast", name: "Claude Opus 4.8 (fast mode)", + targetFormat: "claude", contextLength: 1000000, maxOutputTokens: 64000, supportsReasoning: true, @@ -64,6 +67,7 @@ export const gheCopilotProvider: RegistryEntry = { { id: "claude-opus-4.8", name: "Claude Opus 4.8", + targetFormat: "claude", contextLength: 1000000, maxOutputTokens: 64000, supportsReasoning: true, @@ -74,6 +78,7 @@ export const gheCopilotProvider: RegistryEntry = { { id: "claude-opus-4.7", name: "Claude Opus 4.7", + targetFormat: "claude", contextLength: 1000000, maxOutputTokens: 64000, supportsReasoning: true, @@ -83,12 +88,14 @@ export const gheCopilotProvider: RegistryEntry = { { id: "claude-sonnet-4.6", name: "Claude Sonnet 4.6", + targetFormat: "claude", contextLength: 1000000, maxOutputTokens: 64000, }, { id: "claude-opus-4.5", name: "Claude Opus 4.5", + targetFormat: "claude", contextLength: 200000, maxOutputTokens: 32000, supportsReasoning: true, @@ -97,18 +104,29 @@ export const gheCopilotProvider: RegistryEntry = { { id: "claude-sonnet-5", name: "Claude Sonnet 5", + targetFormat: "claude", contextLength: 1000000, maxOutputTokens: 64000, }, + { + id: "claude-sonnet-5-5", + name: "Claude Sonnet 5.5", + targetFormat: "claude", + contextLength: 1000000, + maxOutputTokens: 128000, + supportedThinkingEfforts: ["low", "medium", "high", "xhigh"], + }, { id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5", + targetFormat: "claude", contextLength: 200000, maxOutputTokens: 32000, }, { id: "claude-haiku-4.5", name: "Claude Haiku 4.5", + targetFormat: "claude", contextLength: 200000, maxOutputTokens: 32000, }, diff --git a/open-sse/config/providers/registry/github/index.ts b/open-sse/config/providers/registry/github/index.ts index 96d42ee19652..f6dda3310b69 100644 --- a/open-sse/config/providers/registry/github/index.ts +++ b/open-sse/config/providers/registry/github/index.ts @@ -121,6 +121,14 @@ export const githubProvider: RegistryEntry = { contextLength: 1000000, maxOutputTokens: 64000, }, + { + id: "claude-sonnet-5-5", + name: "Claude Sonnet 5.5", + targetFormat: "claude", + contextLength: 1000000, + maxOutputTokens: 128000, + supportedThinkingEfforts: ["low", "medium", "high", "xhigh"], + }, { id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5", diff --git a/open-sse/config/providers/registry/kimi/coding/runtime.ts b/open-sse/config/providers/registry/kimi/coding/runtime.ts index 8c6a19ae4432..2cf185d525a5 100644 --- a/open-sse/config/providers/registry/kimi/coding/runtime.ts +++ b/open-sse/config/providers/registry/kimi/coding/runtime.ts @@ -57,8 +57,133 @@ export function normalizeKimiDeviceId(value: unknown): string { ].join("-"); } +const KIMI_VERSION_OVERRIDE_ENV = "KIMI_CLI_VERSION"; +const DOTTED_TRIPLE_PATTERN = /^\d+\.\d+\.\d+$/; +const NPM_KIMI_CODE_LATEST_URL = "https://registry.npmjs.org/@moonshot-ai/kimi-code/latest"; +export const KIMI_CODE_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000; +export const KIMI_CODE_VERSION_FETCH_TIMEOUT_MS = 20_000; + +type FetchLike = typeof fetch; + +let cachedVersion: string | null = null; +let cachedAt = 0; +let inFlight: Promise | null = null; +let fetchImpl: FetchLike = fetch; + +function parseDottedTriple(value: unknown): string | null { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + return DOTTED_TRIPLE_PATTERN.test(trimmed) ? trimmed : null; +} + +function compareDottedTriple(a: string, b: string): number { + const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0); + const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0); + for (let i = 0; i < 3; i += 1) { + if (aParts[i] !== bParts[i]) return aParts[i] - bParts[i]; + } + return 0; +} + +function pickAtLeastPin(version: string | null): string { + if (!version || compareDottedTriple(version, KIMI_CODE_CLI_VERSION) <= 0) { + return KIMI_CODE_CLI_VERSION; + } + return version; +} + +function readEnvOverride(): string | null { + const raw = typeof process === "undefined" ? undefined : process.env?.[KIMI_VERSION_OVERRIDE_ENV]; + const normalized = sanitizeKimiHeaderValue(raw, ""); + return normalized || null; +} + +function readFreshCache(): string | null { + if (!cachedVersion) return null; + if (Date.now() - cachedAt >= KIMI_CODE_VERSION_CACHE_TTL_MS) return null; + return cachedVersion; +} + +/** + * Sync hot path. Env override wins. Otherwise a cached registry version newer + * than the pin, else the pin. Outside tests, a stale cache starts one + * background refresh; this call itself never waits on the network. + */ export function getKimiCodeCliVersion(): string { - return sanitizeKimiHeaderValue(process.env.KIMI_CLI_VERSION, KIMI_CODE_CLI_VERSION); + const override = readEnvOverride(); + if (override) return override; + if (typeof process !== "undefined" && !process.env.NODE_TEST_CONTEXT && !readFreshCache() && !inFlight) { + void refreshKimiCodeCliVersion(); + } + return pickAtLeastPin(readFreshCache()); +} + +/** + * Warm the npm cache (20s timeout, 6h TTL, coalesced, never rejects). + * A failure, or a publish that is not newer than the pin, leaves the pin + * (or the previous fresh cache) in place. + */ +export function refreshKimiCodeCliVersion(): Promise { + const override = readEnvOverride(); + if (override) return Promise.resolve(override); + + const fresh = readFreshCache(); + if (fresh) return Promise.resolve(pickAtLeastPin(fresh)); + + if (inFlight) return inFlight; + + inFlight = (async () => { + let resolved: string | null = null; + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), KIMI_CODE_VERSION_FETCH_TIMEOUT_MS); + try { + const response = await fetchImpl(NPM_KIMI_CODE_LATEST_URL, { + headers: { + Accept: "application/json", + "User-Agent": "OmniRoute-KimiCodeVersion/1.0", + }, + signal: controller.signal, + }); + if (response.ok) { + const payload = (await response.json()) as { version?: unknown }; + resolved = parseDottedTriple(payload?.version); + } + } finally { + clearTimeout(timeoutId); + } + } catch { + resolved = null; + } + + if (resolved && compareDottedTriple(resolved, KIMI_CODE_CLI_VERSION) > 0) { + cachedVersion = resolved; + cachedAt = Date.now(); + } + return pickAtLeastPin(resolved ?? readFreshCache()); + })(); + + const current = inFlight; + void current.finally(() => { + if (inFlight === current) inFlight = null; + }); + return current; +} + +/** Test seam: drop the registry cache so the next refresh hits the network. */ +export function resetKimiCodeCliVersionCache(): void { + cachedVersion = null; + cachedAt = 0; + inFlight = null; +} + +/** Test seam: replace the registry fetch. Production leaves the global fetch. */ +export function setKimiCodeCliVersionFetch(next: FetchLike): void { + fetchImpl = next; +} + +export function resetKimiCodeCliVersionFetch(): void { + fetchImpl = fetch; } export function getKimiCodeCliUserAgent(): string { diff --git a/open-sse/config/providers/registry/opencode/go/index.ts b/open-sse/config/providers/registry/opencode/go/index.ts index d8536678176f..8bb1b8dcebb2 100644 --- a/open-sse/config/providers/registry/opencode/go/index.ts +++ b/open-sse/config/providers/registry/opencode/go/index.ts @@ -361,6 +361,27 @@ export const opencode_goProvider: RegistryEntry = { targetFormat: "openai-responses", maxOutputTokens: 128000, }, + // OpenCode Zen Go serves gpt-6-luna ONLY on /responses: live-verified + // 2026-10-02, POST /chat/completions returns 400 + // `{"type":"error","error":{"type":"ModelProtocolUnsupported","message": + // "Model does not support this protocol."}}` while POST /responses returns + // 200 with a normal completion. Same failure mode as #12196 (gpt-5.6-luna): + // without a registry entry getModelTargetFormat() returns null, + // resolveOpencodeTargetFormat() falls back to "openai" and + // OpencodeExecutor.buildUrl() posts to /chat/completions. + // Effort vocabulary also live-verified against the go upstream: none/low/ + // medium/high/xhigh/max -> 200, "ultra" -> 400 (so declaring the exact set + // lets sanitizeReasoningEffortForProvider clamp instead of forwarding a + // 400). max_output_tokens 128000 accepted; the provider's + // requestDefaults.maxTokens is 16384 without an explicit value. + { + id: "gpt-6-luna", + name: "GPT-6 Luna", + supportsReasoning: true, + supportedThinkingEfforts: ["none", "low", "medium", "high", "xhigh", "max"], + targetFormat: "openai-responses", + maxOutputTokens: 128000, + }, // Console Go free GLM-tier model (live-verified 2026-08-23): the upstream // rejects every reasoning_effort outside {low, high, max} whenever tools // are present — "[1210] This model always engages in thinking and cannot diff --git a/open-sse/config/providers/registry/perplexity/agent/index.ts b/open-sse/config/providers/registry/perplexity/agent/index.ts index 9214136cc77d..dd2dd0d39f5c 100644 --- a/open-sse/config/providers/registry/perplexity/agent/index.ts +++ b/open-sse/config/providers/registry/perplexity/agent/index.ts @@ -10,6 +10,9 @@ export const perplexityAgentProvider: RegistryEntry = { testKeyModelsUrl: "https://api.perplexity.ai/v1/models", authType: "apikey", authHeader: "bearer", + headers: { + "X-Pplx-Integration": "omniroute", + }, passthroughModels: true, liveCatalogAuthoritative: false, models: [ diff --git a/open-sse/config/providers/registry/perplexity/index.ts b/open-sse/config/providers/registry/perplexity/index.ts index a8857fdcc9ca..45222ebc62ef 100644 --- a/open-sse/config/providers/registry/perplexity/index.ts +++ b/open-sse/config/providers/registry/perplexity/index.ts @@ -10,6 +10,9 @@ export const perplexityProvider: RegistryEntry = { testKeyModelsUrl: "https://api.perplexity.ai/v1/models", authType: "apikey", authHeader: "bearer", + headers: { + "X-Pplx-Integration": "omniroute", + }, models: [ { id: "sonar-deep-research", name: "Sonar Deep Research" }, { id: "sonar-reasoning-pro", name: "Sonar Reasoning Pro" }, diff --git a/open-sse/config/providers/registry/vertex/index.ts b/open-sse/config/providers/registry/vertex/index.ts index 1a8975e2e307..fac4bc1f55b2 100644 --- a/open-sse/config/providers/registry/vertex/index.ts +++ b/open-sse/config/providers/registry/vertex/index.ts @@ -51,6 +51,7 @@ export const vertexProvider: RegistryEntry = { { id: "claude-fable-5", name: "Claude Fable 5 (Vertex)", targetFormat: "claude" }, { id: "claude-opus-5", name: "Claude Opus 5 (Vertex)", targetFormat: "claude" }, { id: "claude-sonnet-5", name: "Claude Sonnet 5 (Vertex)", targetFormat: "claude" }, + { id: "claude-sonnet-5-5", name: "Claude Sonnet 5.5 (Vertex)", targetFormat: "claude" }, { id: "claude-opus-4-8", name: "Claude Opus 4.8 (Vertex)", targetFormat: "claude" }, { id: "claude-opus-4-7", name: "Claude Opus 4.7 (Vertex)", targetFormat: "claude" }, { id: "claude-opus-4-6", name: "Claude Opus 4.6 (Vertex)", targetFormat: "claude" }, diff --git a/open-sse/config/providers/registry/vertex/partner/index.ts b/open-sse/config/providers/registry/vertex/partner/index.ts index 0ce2be94ac6c..2db77403a7df 100644 --- a/open-sse/config/providers/registry/vertex/partner/index.ts +++ b/open-sse/config/providers/registry/vertex/partner/index.ts @@ -23,6 +23,7 @@ export const vertex_partnerProvider: RegistryEntry = { { id: "claude-fable-5", name: "Claude Fable 5", targetFormat: "claude" }, { id: "claude-opus-5", name: "Claude Opus 5", targetFormat: "claude" }, { id: "claude-sonnet-5", name: "Claude Sonnet 5", targetFormat: "claude" }, + { id: "claude-sonnet-5-5", name: "Claude Sonnet 5.5", targetFormat: "claude" }, { id: "claude-opus-4-8", name: "Claude Opus 4.8", targetFormat: "claude" }, { id: "claude-opus-4-7", name: "Claude Opus 4.7", targetFormat: "claude" }, { id: "claude-opus-4-6", name: "Claude Opus 4.6", targetFormat: "claude" }, diff --git a/open-sse/config/providers/registry/zai/index.ts b/open-sse/config/providers/registry/zai/index.ts index bc4e9784d39e..f09a2d1de627 100644 --- a/open-sse/config/providers/registry/zai/index.ts +++ b/open-sse/config/providers/registry/zai/index.ts @@ -12,6 +12,10 @@ export const zaiProvider: RegistryEntry = { authType: "apikey", authHeader: "x-api-key", headers: getAnthropicCompatHeaders(), + // Discovery on this provider's Anthropic-compat /models surface omits the + // coding-plan glm-5.3-flash family that the OpenAI-compatible endpoint + // serves; see unionRegistryDispatchModels for the opt-in semantics. + registryDispatchUnion: true, // Real upstream model IDs only. GLM-5.3-family models are tagged for z.ai's // OpenAI-compatible Coding Plan endpoint because their documented reasoning // selector is `reasoning_effort` (low|high|max) and GLM-5.3-Flash supports diff --git a/open-sse/config/providers/shared.ts b/open-sse/config/providers/shared.ts index e2c50d84b627..1010457005d7 100644 --- a/open-sse/config/providers/shared.ts +++ b/open-sse/config/providers/shared.ts @@ -168,6 +168,15 @@ export interface RegistryEntry { requestDefaults?: ProviderRequestDefaults; oauth?: RegistryOAuth; models: RegistryModel[]; + /** + * Opt-in for unionRegistryDispatchModels: targetFormat-tagged models join + * authoritative live catalogs (dispatch AND listing) when this provider's + * discovery surface is known to under-report — z.ai's Anthropic-compat + * /models omitting the coding-plan glm-5.3-flash family is the motivating + * case. Providers whose discovery omissions instead mean per-account + * entitlement must NOT opt in: their #12137-style gating would be bypassed. + */ + registryDispatchUnion?: boolean; /** Provider-native reasoning vocabulary for reasoning-capable passthrough models * that do not have an explicit per-model declaration. */ defaultSupportedThinkingEfforts?: readonly string[]; diff --git a/open-sse/executors/antigravity.ts b/open-sse/executors/antigravity.ts index 9652901894f2..d71e954f639e 100644 --- a/open-sse/executors/antigravity.ts +++ b/open-sse/executors/antigravity.ts @@ -834,6 +834,7 @@ export class AntigravityExecutor extends BaseExecutor { enable_thinking: _enableThinking, thinking_budget: _thinkingBudget, enabledCreditTypes: _enabledCreditTypes, + stream: _streamField, ...passthroughFields } = normalizedBody; diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 4602900853af..3a634b65d02f 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -43,6 +43,7 @@ import { resolveKeyForRequest } from "../services/apiKeyRotator.ts"; import type { KeyHealth } from "../services/apiKeyRotator.ts"; import { getOpenAICompatibleType, isClaudeCodeCompatible } from "../services/provider.ts"; import { usesCcWireImage } from "../services/ccWireImageBuiltins.ts"; +import { getForcedReasoningEffort } from "../utils/reasoningRuleContext.ts"; import { runWithOnPersist, getRefreshLeadMs, @@ -893,7 +894,9 @@ export class BaseExecutor { ); if (this.provider === "groq") { transformedBody = stripGroqUnsupportedFields( - transformedBody as Record + transformedBody as Record, + model, + getForcedReasoningEffort(requestCredentials) ) as typeof transformedBody; } // A previous URL in this execute() already hit a thinking_budget 400 and @@ -924,11 +927,11 @@ export class BaseExecutor { capMs: this.config?.fetchStartTimeoutCapMs, }); const fetchStartTimeoutMs = fetchStartTimeoutPolicy.timeoutMs; - if (fetchStartTimeoutPolicy.capped) { - log?.debug?.( - "TIMEOUT", - `fetch-start timeout capped ${fetchStartTimeoutPolicy.baseTimeoutMs}ms -> ${fetchStartTimeoutMs}ms (streaming)` - ); + if (stream) { + const timeoutMessage = fetchStartTimeoutPolicy.capped + ? `fetch-start timeout capped ${fetchStartTimeoutPolicy.baseTimeoutMs}ms -> ${fetchStartTimeoutMs}ms (streaming)` + : `fetch-start timeout ${fetchStartTimeoutMs}ms (streaming)`; + log?.debug?.("TIMEOUT", timeoutMessage); } try { diff --git a/open-sse/executors/base/reasoningEffort.ts b/open-sse/executors/base/reasoningEffort.ts index 0e8691936c84..7fb18e35f339 100644 --- a/open-sse/executors/base/reasoningEffort.ts +++ b/open-sse/executors/base/reasoningEffort.ts @@ -68,6 +68,12 @@ export const GROK_45_PATTERN = /(?:^|\/|\b)grok-4\.5/i; export const GROK_46_PATTERN = /(?:^|\/|\b)grok-4\.6/i; export const GLM_53_FAMILY_PATTERN = /(?:^|\/|\b)glm-5\.3(?:$|-)/i; export const GLM_52_FAMILY_PATTERN = /(?:^|\/|\b)glm-5\.2(?:$|-)/i; +/** + * Xiaomi MiMo V2.5 and V2.6 ids (`mimo-v2.5`, `mimo-v2.5-pro`, `mimo-v2.6-pro`, + * `mimo-v2.6-flash`). The trailing `(?:$|-)` keeps the unsuffixed V2 line + * (`mimo-v2-flash`, `mimo-v2-omni`) and later minors (`mimo-v2.7`) out. + */ +export const MIMO_V25_V26_PATTERN = /(?:^|\/|\b)mimo-v2\.[56](?:$|-)/i; export function isCommandCodeProvider(provider: string): boolean { return provider === "command-code" || provider === "cmd" || provider === "command_code"; @@ -471,6 +477,57 @@ export function sanitizeReasoningEffortForProvider( return body; } + // ── Xiaomi MiMo V2.5 / V2.6 on OpenCode gateways ───────────────────────── + // Live contract (probed 2026-10 against opencode-go, combo bypassed) for + // `mimo-v2.6-pro` and `mimo-v2.6-flash`, applied to V2.5 as well: + // none | low | medium | high → accepted (`none` passes through) + // minimal | max | xhigh → 400 "Invalid request parameters" + // Ceiling: xhigh/max/ultra → high. Floor: minimal → low, the nearest + // accepted rung (`low` is verified on both V2.6 models). Same shape as the + // Muse Spark block above, which remaps the one rejected neighbour rather + // than forwarding a value the gateway 400s. + // The 400 names no enum, so parseReasoningEffortEnum returns null and the + // learned clamp-and-retry does not run. The opt-in opaque probe (#14895, + // OMNIROUTE_REASONING_EFFORT_PROBE_PROVIDERS, default off) steps one rung + // down — max → xhigh — which this gateway also rejects, so it cannot reach + // `high` after the max-tier rewrite below has already turned xhigh into max. + // + // The `-` aliases (`mimo-v2.5-max`) stay a separate parseEffortLevel + // path and are not rewritten here. The opencode-go registry still lists + // mimo-v2.5 supportedThinkingEfforts: ["high", "max"]. That row is the + // suffix-alias catalog, and it disagrees with this flat-field clamp. Do not + // "fix" that by letting flat max through: the opaque-400 probe test records + // that mimo-v2.5-pro returns the same 400 + // (tests/unit/reasoning-effort-opaque-400-probe.test.ts). EFFORT_TIERS in + // executors/opencode.ts documents that suffix table, not this contract. + // + // Family-scoped inside the OpenCode provider family (opencode-go, + // opencode-zen, opencode, opencode_go). Those four all take the xhigh→max + // rewrite below; gating on provider === "opencode-go" alone would leave the + // same 400 on the other three. A provider-wide clamp would break models on + // those gateways whose registry declares literal max (deepseek-v4-pro, + // glm-5.2, kimi-k3, qwen3.7-plus, ox-alpha-free). OpenRouter is left alone: + // this file already excludes it from the max rewrite because its API expects + // xhigh, and the MiMo ceiling was not probed there. Command Code is left + // alone because xhigh/max are native tiers on that validator. + if (isOpencodeGoProvider(provider) && MIMO_V25_V26_PATTERN.test(modelStr)) { + if (effortStr === "xhigh" || effortStr === "max" || effortStr === "ultra") { + log?.info?.( + "REASONING_SANITIZE", + `${provider}/${modelStr}: clamped reasoning_effort ${effortStr} → high (MiMo ceiling)` + ); + return writeEffortValue(b, "high", c); + } + if (effortStr === "minimal") { + log?.info?.( + "REASONING_SANITIZE", + `${provider}/${modelStr}: clamped reasoning_effort minimal → low (MiMo floor)` + ); + return writeEffortValue(b, "low", c); + } + return body; + } + // `minimal` is a sub-`low` reasoning tier some catalogs advertise (e.g. // Muse Spark via models.dev) and the Codex provider accepts natively — but // Command Code rejects it outright: diff --git a/open-sse/executors/codex.ts b/open-sse/executors/codex.ts index 0ef82bba8c01..d2f50497854e 100644 --- a/open-sse/executors/codex.ts +++ b/open-sse/executors/codex.ts @@ -36,6 +36,7 @@ import { withCodexFingerprintCredentials, } from "../config/codexIdentity.ts"; import { getAccessToken } from "../services/tokenRefresh.ts"; +import { isUnrecoverableRefreshError } from "../services/tokenRefresh/shared.ts"; import { sanitizeCodexResponsesInput } from "../services/responsesInputSanitizer.ts"; import { applyReasoningInputPolicy } from "../services/reasoningInputPolicy.ts"; import { getForcedReasoningEffort } from "../utils/reasoningRuleContext.ts"; @@ -44,7 +45,10 @@ import { getThinkingBudgetConfig, ThinkingMode } from "../services/thinkingBudge import { CORS_HEADERS } from "../utils/cors.ts"; import { projectCodexPublicError } from "../utils/codexPublicError.ts"; import { errorResponse } from "../utils/error.ts"; -import { buildSyntheticResponsesFailedEvent } from "../utils/responsesSequence.ts"; +import { + buildSyntheticResponsesFailureId, + buildSyntheticResponsesFailedEvent, +} from "../utils/responsesSequence.ts"; import { hasCodexSsePeekProgress } from "./codex/ssePeekProgress.ts"; import { normalizeCodexResponsesInput } from "../utils/responsesInputNormalization.ts"; import * as prl from "../utils/providerRequestLogging.ts"; @@ -495,7 +499,7 @@ function toCodexResponseFailedEvent(parsed: Record): Record = { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, Accept: stream ? "text/event-stream" : "application/json", }; + if (getModelTargetFormat("command-code", model) === "claude") { + headers["anthropic-version"] = ANTHROPIC_VERSION_HEADER; + } mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders); let upstream = await fetch(url, { diff --git a/open-sse/executors/cursor.ts b/open-sse/executors/cursor.ts index 1a6e177a455b..40f664d00fa1 100644 --- a/open-sse/executors/cursor.ts +++ b/open-sse/executors/cursor.ts @@ -13,6 +13,7 @@ declare const EdgeRuntime: string | undefined; import { BaseExecutor, mergeUpstreamExtraHeaders } from "./base.ts"; import { PROVIDERS, HTTP_STATUS } from "../config/constants.ts"; import { getAccessToken } from "../services/tokenRefresh.ts"; +import { currentAppliedProxySink } from "../utils/proxyFetch.ts"; import { buildAgentRequestBody, decodeAgentServerMessage, @@ -1021,11 +1022,12 @@ export class CursorExecutor extends BaseExecutor { } } - buildHeaders(credentials) { + async buildHeaders(credentials) { const ghostMode = credentials.providerSpecificData?.ghostMode !== false; const cleanToken = stripCursorOAuthTokenPrefix(credentials.accessToken ?? ""); const requestId = crypto.randomUUID(); const traceParent = `00-${crypto.randomBytes(16).toString("hex")}-${crypto.randomBytes(8).toString("hex")}-01`; + const clientVersion = formatCursorAgentClientVersion(await getCursorAgentCliVersion()); // Mirrors cursor-agent's actual headers for agent.v1.AgentService/Run. // Notably: no x-cursor-checksum, no machineId, no x-amzn-trace-id. @@ -1040,7 +1042,7 @@ export class CursorExecutor extends BaseExecutor { traceparent: traceParent, "user-agent": "connect-es/1.6.1", "x-cursor-client-type": "cli", - "x-cursor-client-version": formatCursorAgentClientVersion(getCursorAgentCliVersion()), + "x-cursor-client-version": clientVersion, "x-ghost-mode": ghostMode ? "true" : "false", "x-original-request-id": requestId, "x-request-id": requestId, @@ -1252,7 +1254,7 @@ export class CursorExecutor extends BaseExecutor { url = await resolveCursorAgentUrl(executionCredentials, signal); } catch (err) { const message = err instanceof Error ? err.message : String(err); - const headers = this.buildHeaders(executionCredentials); + const headers = await this.buildHeaders(executionCredentials); return { response: new Response( JSON.stringify({ @@ -1272,7 +1274,7 @@ export class CursorExecutor extends BaseExecutor { transformedBody: body, }; } - const headers = this.buildHeaders(executionCredentials); + const headers = await this.buildHeaders(executionCredentials); mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders); const messages: ChatMessage[] = body.messages || []; @@ -1460,7 +1462,15 @@ export class CursorExecutor extends BaseExecutor { }; } if (opened.status !== 200) { - const errBuf = await opened.consumeError(); + // Publish the received status so proxy health counts it as upstream. + const sink = currentAppliedProxySink(); + if (sink) sink.upstreamStatus = opened.status; + let errBuf: Buffer; + try { + errBuf = await opened.consumeError(); + } catch { + errBuf = Buffer.alloc(0); + } const errText = errBuf.toString("utf8") || "Unknown error"; if (opened.status === HTTP_STATUS.UNAUTHORIZED && isCursorApiKey(credentials.apiKey)) { invalidateCursorSessionToken(credentials.apiKey); diff --git a/open-sse/executors/cursor/agentEndpoint.ts b/open-sse/executors/cursor/agentEndpoint.ts index e1168df5b766..f3288fd1dabc 100644 --- a/open-sse/executors/cursor/agentEndpoint.ts +++ b/open-sse/executors/cursor/agentEndpoint.ts @@ -78,7 +78,7 @@ async function fetchCursorAgentUrls( "content-type": "application/proto", "user-agent": "connect-es/1.6.1", "x-cursor-client-type": "cli", - "x-cursor-client-version": formatCursorAgentClientVersion(getCursorAgentCliVersion()), + "x-cursor-client-version": formatCursorAgentClientVersion(await getCursorAgentCliVersion()), }, body: Buffer.alloc(0), signal: signal ? mergeAbortSignals(signal, timeoutSignal) : timeoutSignal, diff --git a/open-sse/executors/cursor/builtinToolBridge.ts b/open-sse/executors/cursor/builtinToolBridge.ts index 1aafea71cb64..bd606cb505f1 100644 --- a/open-sse/executors/cursor/builtinToolBridge.ts +++ b/open-sse/executors/cursor/builtinToolBridge.ts @@ -32,12 +32,87 @@ type JsonSchema = { additionalProperties?: unknown; }; -const DIRECT_SHELL_TOOL_NAMES = ["bash", "shell", "run_terminal_cmd"]; -const TODO_WRITE_TOOL_NAMES = ["todowrite", "todo_write"]; -const GREP_TOOL_NAMES = ["grep", "search", "ripgrep", "grep_search"]; -const LS_TOOL_NAMES = ["glob", "ls", "list", "list_dir", "list_directory"]; -const WRITE_TOOL_NAMES = ["write", "write_file", "create_file"]; -const FETCH_TOOL_NAMES = ["webfetch", "web_fetch", "fetch"]; +const DIRECT_SHELL_TOOL_NAMES = [ + "bash", + "shell", + "run_terminal_cmd", + "runcommand", + "run_command", + "execute", + "terminal", +]; +const TODO_WRITE_TOOL_NAMES = [ + "todowrite", + "todo_write", + "write_todos", + "update_todos", + "set_todos", +]; +const GREP_TOOL_NAMES = [ + "grep", + "search", + "ripgrep", + "grep_search", + "codebase_search", + "file_search", + "search_file", + "search_code", + "search_files", + "content_search", + "find_in_files", +]; +const LS_TOOL_NAMES = [ + "glob", + "ls", + "list", + "list_dir", + "list_directory", + "listfiles", + "list_files", + "find_files", + "file_search", + "dir", +]; +const WRITE_TOOL_NAMES = [ + "write", + "write_file", + "create_file", + "edit_file", + "apply_patch", + "patch", + "str_replace", + "create", + "save_file", +]; +const FETCH_TOOL_NAMES = [ + "webfetch", + "web_fetch", + "http_get", + "download", + "browse", + "web_search", + "search_web", +]; +/** Other clients own these short names. Never select them as Cursor bridge targets. */ +const NON_CURSOR_GENERIC_TOOL_NAMES = new Set([ + "exec", + "run", + "command", + "update", + "edit", + "fetch", +]); +const READ_TOOL_NAMES = [ + "read", + "read_file", + "view", + "cat", + "open", + "readlines", + "read_file_range", + "file_read", + "get_file", +]; const BRIDGE_DESCRIPTION = "Run Cursor-requested shell command"; const ROOT_SCHEMA_KEYS = new Set([ "$schema", @@ -299,7 +374,7 @@ function readBridge( event: Extract, tools: McpToolDefinition[] ): CursorBuiltinToolBridge | null { - for (const tool of namedTools(tools, ["read", "read_file"])) { + for (const tool of namedTools(tools, READ_TOOL_NAMES)) { const schema = schemaFor(tool); if (!schema) continue; const properties = schemaProperties(schema); @@ -462,7 +537,7 @@ export function bridgeCursorPiTool( switch (event.kind) { case "exec_pi_read": { if (!event.path.trim()) return null; - for (const tool of namedTools(tools, ["read", "read_file"])) { + for (const tool of namedTools(tools, READ_TOOL_NAMES)) { const schema = schemaFor(tool); if (!schema) continue; const properties = schemaProperties(schema); @@ -740,8 +815,184 @@ export function bridgeCursorNativeTodoWrite( return null; } +/** + * Shape-based fallback: try to bridge by matching the schema property keys + * when name-based matching did not find a candidate. + * Following the deepseekWebTools precedence, this is strictly fail-closed: + * if EXACTLY ONE declared tool can express the request, adopt it. If zero or + * two or more tools match, return null (ambiguity -> fail closed). + */ +function bridgeSingleShapeCandidate( + tools: T[], + tryBridge: (tool: T) => CursorBuiltinToolBridge | null +): CursorBuiltinToolBridge | null { + const matches: CursorBuiltinToolBridge[] = []; + for (const tool of tools) { + if (NON_CURSOR_GENERIC_TOOL_NAMES.has(tool.name.toLowerCase())) continue; + const bridged = tryBridge(tool); + if (bridged) matches.push(bridged); + } + // Exactly one candidate expresses the event -> safe to bridge dynamically. + // Zero matches -> null (unsupported schema). + // Two or more matches -> null (ambiguous intent, fail closed). + return matches.length === 1 ? matches[0] : null; +} + +function singleToolReadBridge( + tool: McpToolDefinition, + event: Extract +): CursorBuiltinToolBridge | null { + const schema = schemaFor(tool); + if (!schema) return null; + const properties = schemaProperties(schema); + const pathKey = selectProperty(schema, properties, ["filePath", "path", "file_path"], "string"); + if (!pathKey) return null; + const args: Record = { [pathKey]: event.path }; + let rangeFits = true; + for (const [key, value] of [ + ["offset", event.offset], + ["limit", event.limit], + ] as const) { + if (value === undefined) continue; + if (!propertyAcceptsInteger(properties[key], value)) { + rangeFits = false; + break; + } + args[key] = value; + } + if (!rangeFits) return null; + if (hasAllRequired(schema, args)) return { toolName: tool.name, arguments: args }; + return null; +} + +function singleToolGrepBridge( + tool: McpToolDefinition, + event: Extract +): CursorBuiltinToolBridge | null { + if (event.outputMode && !["content", "files_with_matches", "count"].includes(event.outputMode)) + return null; + const schema = schemaFor(tool); + if (!schema) return null; + const properties = schemaProperties(schema); + if (!event.pattern.trim()) { + if (!event.glob.trim()) return null; + // An empty pattern with a glob is a file-search (ls) request. Only bridge + // to tools that express directory/ls semantics, not content-grep tools. + if (!LS_TOOL_NAMES.includes(tool.name.toLowerCase())) return null; + const patternKey = selectProperty(schema, properties, ["pattern", "glob"], "string"); + if (!patternKey) return null; + const args: Record = { [patternKey]: event.glob }; + const pathKey = selectProperty(schema, properties, ["path", "dir", "directory"], "string"); + if (pathKey && event.path) args[pathKey] = event.path; + if (hasAllRequired(schema, args)) return { toolName: tool.name, arguments: args }; + return null; + } + const patternKey = selectProperty(schema, properties, ["pattern", "query", "regex"], "string"); + if (!patternKey) return null; + const args: Record = { [patternKey]: event.pattern }; + const pathKey = selectProperty(schema, properties, ["path", "dir", "directory"], "string"); + if (pathKey && event.path) args[pathKey] = event.path; + const globKey = selectProperty(schema, properties, ["include", "glob", "filePattern"], "string"); + if (globKey && event.glob) args[globKey] = event.glob; + if (hasAllRequired(schema, args)) return { toolName: tool.name, arguments: args }; + return null; +} + +function singleToolLsBridge( + tool: McpToolDefinition, + event: Extract +): CursorBuiltinToolBridge | null { + if (!event.path.trim()) return null; + const schema = schemaFor(tool); + if (!schema) return null; + const properties = schemaProperties(schema); + const pathKey = selectProperty(schema, properties, ["path", "dir", "directory"], "string"); + if (!pathKey) return null; + const args: Record = { [pathKey]: event.path }; + const patternKey = selectProperty(schema, properties, ["pattern", "glob"], "string"); + if (patternKey) args[patternKey] = "*"; + if (hasAllRequired(schema, args)) return { toolName: tool.name, arguments: args }; + return null; +} + +function singleToolWriteBridge( + tool: McpToolDefinition, + event: Extract +): CursorBuiltinToolBridge | null { + if ( + !event.path.trim() || + event.hasFileBytes || + (event.encodingHint && !["utf8", "utf-8"].includes(event.encodingHint.toLowerCase())) + ) + return null; + const schema = schemaFor(tool); + if (!schema) return null; + const properties = schemaProperties(schema); + const pathKey = selectProperty(schema, properties, ["filePath", "path", "file_path"], "string"); + const contentKey = selectProperty( + schema, + properties, + ["content", "contents", "text", "file_text"], + "string" + ); + if (!pathKey || !contentKey) return null; + const args: Record = { [pathKey]: event.path, [contentKey]: event.fileText }; + if (hasAllRequired(schema, args)) return { toolName: tool.name, arguments: args }; + return null; +} + +function singleToolFetchBridge( + tool: McpToolDefinition, + event: Extract +): CursorBuiltinToolBridge | null { + if (!event.url.trim()) return null; + const schema = schemaFor(tool); + if (!schema) return null; + const properties = schemaProperties(schema); + const urlKey = selectProperty(schema, properties, ["url", "uri", "link"], "string"); + if (!urlKey) return null; + const args: Record = { [urlKey]: event.url }; + if (hasAllRequired(schema, args)) return { toolName: tool.name, arguments: args }; + return null; +} + +function singleToolDirectShellBridge( + tool: McpToolDefinition, + event: Extract< + ExecServerEvent, + { kind: "exec_shell" | "exec_shell_stream" | "exec_mini_swe_bash" } + > +): CursorBuiltinToolBridge | null { + const schema = schemaFor(tool); + if (!schema) return null; + const properties = schemaProperties(schema); + const commandKey = selectProperty(schema, properties, ["command", "cmd"], "string"); + if (!commandKey) return null; + + const args: Record = { [commandKey]: event.command }; + const cwdKey = selectProperty( + schema, + properties, + ["workdir", "cwd", "workingDirectory", "working_directory"], + "string" + ); + if (cwdKey && event.workingDir) args[cwdKey] = event.workingDir; + const timeoutKey = selectProperty(schema, properties, ["timeout"], "number"); + const timeoutMs = event.timeout > 0 ? event.timeout : 0; + if (timeoutKey && timeoutMs > 0) args[timeoutKey] = timeoutMs; + if (propertySupports(properties.description, "string")) { + args.description = BRIDGE_DESCRIPTION; + } + if (hasAllRequired(schema, args)) return { toolName: tool.name, arguments: args }; + return null; +} + /** * Convert a Cursor-native built-in request into a declared external tool call. + * Resolution chain: + * 1. Name-based match via expanded allowlists (deterministic, zero risk). + * 2. Shape-based fallback: if exactly ONE declared tool's schema expresses + * the request, adopt it (preserves fail-closed on ambiguity). * Only event variants whose complete arguments are decoded are supported. * Unknown or constrained schemas fail closed and retain typed rejection. */ @@ -750,11 +1001,36 @@ export function bridgeCursorBuiltinTool( tools: McpToolDefinition[], platform?: CursorClientPlatform ): CursorBuiltinToolBridge | null { - if (event.kind === "exec_read") return readBridge(event, tools); - if (event.kind === "exec_grep") return grepBridge(event, tools); - if (event.kind === "exec_ls") return lsBridge(event, tools); - if (event.kind === "exec_write") return writeBridge(event, tools); - if (event.kind === "exec_fetch") return fetchBridge(event, tools); + if (event.kind === "exec_read") { + return ( + readBridge(event, tools) ?? + bridgeSingleShapeCandidate(tools, (tool) => singleToolReadBridge(tool, event)) + ); + } + if (event.kind === "exec_grep") { + return ( + grepBridge(event, tools) ?? + bridgeSingleShapeCandidate(tools, (tool) => singleToolGrepBridge(tool, event)) + ); + } + if (event.kind === "exec_ls") { + return ( + lsBridge(event, tools) ?? + bridgeSingleShapeCandidate(tools, (tool) => singleToolLsBridge(tool, event)) + ); + } + if (event.kind === "exec_write") { + return ( + writeBridge(event, tools) ?? + bridgeSingleShapeCandidate(tools, (tool) => singleToolWriteBridge(tool, event)) + ); + } + if (event.kind === "exec_fetch") { + return ( + fetchBridge(event, tools) ?? + bridgeSingleShapeCandidate(tools, (tool) => singleToolFetchBridge(tool, event)) + ); + } if ( event.kind !== "exec_shell" && event.kind !== "exec_shell_stream" && @@ -766,5 +1042,9 @@ export function bridgeCursorBuiltinTool( if (!event.command.trim()) return null; const background = event.kind === "exec_bg_shell" || event.isBackground; if (background) return ptySpawnBridge(event, tools, platform); - return directShellBridge(event, tools) ?? ptySpawnBridge(event, tools, platform); + return ( + directShellBridge(event, tools) ?? + ptySpawnBridge(event, tools, platform) ?? + bridgeSingleShapeCandidate(tools, (tool) => singleToolDirectShellBridge(tool, event)) + ); } diff --git a/open-sse/executors/cursor/cursorErrors.ts b/open-sse/executors/cursor/cursorErrors.ts index 9d05eaab6d31..fa67f91d771a 100644 --- a/open-sse/executors/cursor/cursorErrors.ts +++ b/open-sse/executors/cursor/cursorErrors.ts @@ -31,7 +31,14 @@ const REQUEST_TOO_LARGE_PATTERNS: (string | RegExp)[] = [ ]; export type CursorErrorKind = - "rate_limit" | "auth" | "invalid" | "overload" | "timeout" | "connection" | "upstream"; + | "rate_limit" + | "auth" + | "not_found" + | "invalid" + | "overload" + | "timeout" + | "connection" + | "upstream"; export type ClassifiedCursorError = { kind: CursorErrorKind; @@ -90,15 +97,13 @@ export function classifyCursorErrorKind(rawMessage: string): CursorErrorKind { } if (QUOTA_RATE_CUES.some((cue) => lower.includes(cue))) return "rate_limit"; - // Live Cursor out-of-usage for premium models often surfaces as: - // not_found: AI Model Not Found (reset after 109h …) - // OmniRoute may also append "(reset after …)" after classification; treat the - // Cursor-specific "AI Model Not Found" cue as rate/quota either way. - if ( - lower.includes("ai model not found") || - (lower.includes("reset after") && lower.includes("model not found")) - ) { - return "rate_limit"; + // Cursor answers both an unknown model id and some out-of-usage premium + // requests with `not_found: AI Model Not Found`. Only the out-of-usage form + // carries a reset hint (`… (reset after 109h …)`); without one the id itself + // is unknown to Cursor, which is a caller error and must not cool the + // connection down as a rate limit. + if (lower.includes("model not found")) { + return lower.includes("reset after") ? "rate_limit" : "not_found"; } if ( @@ -163,6 +168,8 @@ function kindToStatus(kind: CursorErrorKind): number { return 429; case "auth": return 401; + case "not_found": + return 404; case "invalid": return 400; case "overload": @@ -180,6 +187,7 @@ function kindToType(kind: CursorErrorKind): string { return "rate_limit_error"; case "auth": return "authentication_error"; + case "not_found": case "invalid": return "invalid_request_error"; default: @@ -193,6 +201,8 @@ function kindPrefix(kind: CursorErrorKind): string { return "Cursor rate limit / usage exceeded"; case "auth": return "Cursor authentication failed"; + case "not_found": + return "Cursor model not found"; case "invalid": return "Cursor invalid request"; case "overload": diff --git a/open-sse/executors/cursor/execRejections.ts b/open-sse/executors/cursor/execRejections.ts index c85b9ba21398..2c885bcc37bd 100644 --- a/open-sse/executors/cursor/execRejections.ts +++ b/open-sse/executors/cursor/execRejections.ts @@ -23,6 +23,11 @@ const BUILTIN_TOOL_REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead."; export function buildExecRejection(event: ExecServerEvent): Buffer | null { + if (process.env.CURSOR_DEBUG || process.env.NODE_ENV === "development") { + console.info( + `[cursor-bridge] native tool rejection for event kind=${event.kind}: ${BUILTIN_TOOL_REJECT_REASON}` + ); + } if (isExtraExecEvent(event)) { return encodeExtraExecResult(event); } diff --git a/open-sse/executors/ghe-copilot.ts b/open-sse/executors/ghe-copilot.ts index ee532149b1b8..d78fa4b6ab87 100644 --- a/open-sse/executors/ghe-copilot.ts +++ b/open-sse/executors/ghe-copilot.ts @@ -42,14 +42,20 @@ export class GheCopilotExecutor extends GithubExecutor { (typeof psd?.copilotApiUrl === "string" ? psd.copilotApiUrl : undefined) || (typeof psd?.copilotProxyUrl === "string" ? psd.copilotProxyUrl : undefined); if (apiOrProxy) { - const base = apiOrProxy.replace(/\/chat\/completions\/?$/, "").replace(/\/responses\/?$/, "").replace(/\/+$/, ""); + const base = apiOrProxy + .replace(/\/chat\/completions\/?$/, "") + .replace(/\/responses\/?$/, "") + .replace(/\/+$/, ""); return base.endsWith("/chat/completions") ? base : `${base}/chat/completions`; } const gheUrl = psd?.gheUrl as string | undefined; if (!gheUrl) { throw new Error("GHE Copilot executor requires gheUrl in providerSpecificData"); } - const base = gheUrl.replace(/\/chat\/completions\/?$/, "").replace(/\/responses\/?$/, "").replace(/\/+$/, ""); + const base = gheUrl + .replace(/\/chat\/completions\/?$/, "") + .replace(/\/responses\/?$/, "") + .replace(/\/+$/, ""); return base.endsWith("/chat/completions") ? base : `${base}/chat/completions`; } @@ -63,14 +69,20 @@ export class GheCopilotExecutor extends GithubExecutor { (typeof psd?.copilotApiUrl === "string" ? psd.copilotApiUrl : undefined) || (typeof psd?.copilotProxyUrl === "string" ? psd.copilotProxyUrl : undefined); if (apiOrProxy) { - const base = apiOrProxy.replace(/\/chat\/completions\/?$/, "").replace(/\/responses\/?$/, "").replace(/\/+$/, ""); + const base = apiOrProxy + .replace(/\/chat\/completions\/?$/, "") + .replace(/\/responses\/?$/, "") + .replace(/\/+$/, ""); return `${base}/responses`; } const gheUrl = psd?.gheUrl as string | undefined; if (!gheUrl) { throw new Error("GHE Copilot executor requires gheUrl in providerSpecificData"); } - const base = gheUrl.replace(/\/chat\/completions\/?$/, "").replace(/\/responses\/?$/, "").replace(/\/+$/, ""); + const base = gheUrl + .replace(/\/chat\/completions\/?$/, "") + .replace(/\/responses\/?$/, "") + .replace(/\/+$/, ""); return `${base}/responses`; } @@ -87,7 +99,9 @@ export class GheCopilotExecutor extends GithubExecutor { (typeof psd?.copilotProxyUrl === "string" ? psd.copilotProxyUrl : undefined); const host = apiOrProxy || (psd?.gheUrl as string | undefined); if (!host) { - throw new Error("GHE Copilot executor requires copilotApiUrl or gheUrl in providerSpecificData"); + throw new Error( + "GHE Copilot executor requires copilotApiUrl or gheUrl in providerSpecificData" + ); } const base = host .replace(/\/v1\/messages\/?$/, "") @@ -107,7 +121,12 @@ export class GheCopilotExecutor extends GithubExecutor { : model; } - override buildUrl(model: string, stream: boolean, urlIndex = 0, credentials: ProviderCredentials | null = null): string { + override buildUrl( + model: string, + stream: boolean, + urlIndex = 0, + credentials: ProviderCredentials | null = null + ): string { const bareModel = this.stripPrefix(model); const targetFormat = getModelTargetFormat("ghe-copilot", bareModel); // Claude models: ALWAYS route to the Anthropic-native /v1/messages shim @@ -118,7 +137,7 @@ export class GheCopilotExecutor extends GithubExecutor { return this.getMessagesBase(credentials); } if ( - (targetFormat === "openai-responses" || /codex/i.test(bareModel)) && + (targetFormat === "openai-responses" || /codex|^gpt-6/i.test(bareModel)) && this.supportsResponsesEndpoint(bareModel) ) { return this.getResponsesBase(credentials); @@ -204,13 +223,13 @@ export class GheCopilotExecutor extends GithubExecutor { // GHE OAuth token endpoint const baseUrl = gheUrl.replace(/\/chat\/completions\/?$/, "").replace(/\/responses\/?$/, ""); const tokenUrl = `${baseUrl}/login/oauth/access_token`; - + const params = new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: this.config.clientId, }); - + if (this.config.clientSecret) { params.set("client_secret", this.config.clientSecret); } @@ -223,7 +242,7 @@ export class GheCopilotExecutor extends GithubExecutor { }, body: params, }); - + if (!response.ok) return null; const tokens = await response.json(); log?.info?.("TOKEN", "GHE GitHub token refreshed"); @@ -271,7 +290,11 @@ export class GheCopilotExecutor extends GithubExecutor { ); if (!githubTokens?.accessToken) return null; - const copilotResult = await this.refreshCopilotToken(githubTokens.accessToken, log, credentials); + const copilotResult = await this.refreshCopilotToken( + githubTokens.accessToken, + log, + credentials + ); if (!copilotResult) return githubTokens; return { @@ -288,7 +311,11 @@ export class GheCopilotExecutor extends GithubExecutor { * buildUrl routes chat/responses traffic to the correct enterprise host. */ override async refreshCredentials(credentials: ProviderCredentials, log?: ExecutorLog | null) { - const copilotResult = await this.refreshCopilotToken(credentials?.accessToken, log, credentials); + const copilotResult = await this.refreshCopilotToken( + credentials?.accessToken, + log, + credentials + ); if (!copilotResult && credentials?.refreshToken) { return this.refreshViaGitHubToken(credentials, log); @@ -308,4 +335,4 @@ export class GheCopilotExecutor extends GithubExecutor { } } -export default GheCopilotExecutor; \ No newline at end of file +export default GheCopilotExecutor; diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index 397e06b8ea7e..45b210050efb 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -84,8 +84,8 @@ import { withRequestShapeRetry } from "./opencodeRequestShape.ts"; // contract applies), and existing importers keep resolving it from the executor. export { isPremiumOpencodeModel }; import { - guardResponsesStall, isResponsesFirstByteTimeout, + makeStallGuardedCall, setupStallGuard, } from "./opencodeResponsesStall.ts"; import { discardResponseBody } from "./opencodeResponseBody.ts"; @@ -215,6 +215,22 @@ export class OpencodeExecutor extends BaseExecutor { return isPremiumOpencodeModel(model, provider); } + /** + * Note the outcome of a forced-stream response without reading its body: a success + * confirms the borrowed shape, anything else carries no verdict (the paths that hold + * the verdict note it explicitly where they already read it). + */ + private noteForcedStreamOutcome(input: ExecuteInput, result: ExecutorExecuteResult): void { + const attempt = attemptFor(input.body); + const response = + result instanceof Response ? result : "response" in result ? result.response : null; + noteFreeTierOutcome(attempt, { + ok: !!response?.ok, + status: response?.ok ? (response.status ?? null) : null, + bodyText: null, + }); + } + /** * The target format and the client session of the request being served. While `execute()` * runs they live in that request's own context (this instance is shared and requests @@ -351,14 +367,42 @@ export class OpencodeExecutor extends BaseExecutor { input: ExecuteInput, result: ExecutorExecuteResult ): ExecutorExecuteResult { - noteFreeTierOutcome(attemptFor(input.body), "response" in result && !!result.response?.ok); + this.noteForcedStreamOutcome(input, result); if (input.stream) return result; - if (!("response" in result) || !result.response) return result; + if (!(result instanceof Response)) { + if (!("response" in result) || !result.response) return result; + } // Non-null exactly when the contract applied: stands in for the old surface/model guard. const model = attemptFor(input.body)?.model; if (!model) return result; - const response = rebuildJsonFromForcedStream(result.response, this._requestFormat, model); - return response === result.response ? result : { ...result, response }; + if (result instanceof Response) { + const rebuilt = rebuildJsonFromForcedStream(result, this._requestFormat, model); + return rebuilt === result ? result : rebuilt; + } + const rebuilt = rebuildJsonFromForcedStream(result.response, this._requestFormat, model); + return rebuilt === result.response ? result : { ...result, response: rebuilt }; + } + + /** + * Count a refusal that says something about the borrowed tools, on a path + * that already holds the verdict. Only 403/451 carry that verdict, so only + * they pay for a body read — anything else leaves the store alone. + */ + private async noteFreeTierRefusal( + input: ExecuteInput, + response: Response, + log: ExecuteInput["log"] + ): Promise { + const attempt = attemptFor(input.body); + if (!attempt || !attempt.borrowed || attempt.probe) return; + if (response.status !== 403 && response.status !== 451) return; + let bodyText: string | null = null; + try { + bodyText = await response.clone().text(); + } catch { + log?.debug?.("OPENCODE", "body read failed on borrowed-shape check"); + } + noteFreeTierOutcome(attempt, { ok: false, status: response.status, bodyText }); } private normalizeMuseSparkResponse( @@ -562,7 +606,14 @@ export class OpencodeExecutor extends BaseExecutor { const hasProxies = accounts.some((a) => a.proxy !== null); // Opt-in Responses first-byte stall guard; 0 = no-op. const stallWindowMs = setupStallGuard(input.stream, this._requestFormat, log, cid).windowMs; - const guardStall = (r: T) => guardResponsesStall(r, stallWindowMs, input.signal); + const guardStall = makeStallGuardedCall( + input.stream, + this._requestFormat, + stallWindowMs, + input.signal, + log, + cid + ); const headersWait = headersWaitState( input, this._requestFormat, @@ -598,11 +649,15 @@ export class OpencodeExecutor extends BaseExecutor { ) as unknown as Promise ); if (retryAfterRefusal) { + await this.noteFreeTierRefusal(input, retryAfterRefusal.response, log); return this.finalizeForcedStream( input, this.normalizeMuseSparkResponse(input, retryAfterRefusal) ); } + if (single.response.status === 403 || single.response.status === 451) { + await this.noteFreeTierRefusal(input, single.response, log); + } if (single.response.status === 400) { let bodyText: string | null = null; try { @@ -705,7 +760,7 @@ export class OpencodeExecutor extends BaseExecutor { this.buildUrl(String(input.model ?? ""), Boolean(input.stream)), resolveProxyForRequest ); - const { readAppliedKey, keyOfMember } = appliedEgress; + const { readAppliedKey, keyOfMember, noteRefused } = appliedEgress; for (let attempt = 0; attempt < accounts.length + emptyRejectionBudget; attempt++) { appliedEgress.resetAttempt(); @@ -832,7 +887,8 @@ export class OpencodeExecutor extends BaseExecutor { addedWait.causes.add("throttle"); publishAddedWait(); } - appliedEgress.rememberServed(account); // Served (post repick), never acquire-time. + appliedEgress.rememberServed(account); // Served (post repick); the attempt egress label reads it here. + const egress = egressPacing.egressLabel(account, readAppliedKey); let result: HttpExecuteResult; try { const { outcome, waitMs } = await headersWaitDispatch( @@ -866,7 +922,7 @@ export class OpencodeExecutor extends BaseExecutor { }); // same settle as the stall arm log?.warn?.( "OPENCODE", - `${cid}no response headers within ${waitMs}ms on account ${masked}, rotating to next…` + `${cid}no response headers within ${waitMs}ms on account ${masked}, rotating to next… ${egress}` ); continue; } @@ -887,7 +943,7 @@ export class OpencodeExecutor extends BaseExecutor { }); log?.warn?.( "OPENCODE", - `${cid}stream stalled on account ${masked}, ${rotate ? "rotating…" : "not rotating again"} (${reason})` + `${cid}stream stalled on account ${masked}, ${rotate ? "rotating…" : "not rotating again"} (${reason}) ${egress}` ); if (!rotate) egressPacing.throwPacedError(egressRelease, err); continue; @@ -906,21 +962,21 @@ export class OpencodeExecutor extends BaseExecutor { lastSharedEgressError = err; log?.warn?.( "OPENCODE", - `${cid}network error on account ${masked} (no dedicated proxy, shared egress), cooldown — trying next… (${reason})` + `${cid}network error on account ${masked} (no dedicated proxy, shared egress), cooldown — trying next… (${reason}) ${egress}` ); egressPacing.releasePacingSlot(egressRelease); continue; } log?.warn?.( "OPENCODE", - `${cid}network error on account ${masked} (no dedicated proxy, shared egress) — not rotating (${reason})` + `${cid}network error on account ${masked} (no dedicated proxy, shared egress) — not rotating (${reason}) ${egress}` ); egressPacing.throwPacedError(egressRelease, err); } markCooldown(account); log?.warn?.( "OPENCODE", - `${cid}network error on account ${masked}, rotating to next… (${reason})` + `${cid}network error on account ${masked}, rotating to next… (${reason}) ${egress}` ); egressPacing.releasePacingSlot(egressRelease); continue; @@ -951,7 +1007,7 @@ export class OpencodeExecutor extends BaseExecutor { result.response, isOpencodeRateLimited429EarlyStopEnabled ); - egressPacing.log429Outcome(log, cid, arm, masked, setAsideMs); + egressPacing.log429Outcome(log, cid, arm, masked, setAsideMs, egress); if (arm === "stop") { if (attributionOn && skippedCooldown.size > 0) { this.logSkippedCooldownAccounts(log, cid, skippedCooldown); @@ -1032,7 +1088,7 @@ export class OpencodeExecutor extends BaseExecutor { lastPoolKey = poolReselectKeyOf(next); log?.warn?.( "OPENCODE", - `${cid}pool re-selected egress for account ${masked} after 429, retrying on another member…` + `${cid}pool re-selected egress for account ${masked} after 429, retrying on another member… ${egressPacing.egressLabel({ proxy: next as ScopedAccount["proxy"], fingerprint: account.fingerprint })}` ); } } @@ -1046,7 +1102,7 @@ export class OpencodeExecutor extends BaseExecutor { transientStreak = priorTransientStreak + 1; log?.warn?.( "OPENCODE", - `${cid}transient upstream ${status} on account ${masked} (proxy ${key ?? "direct"}), rotating to next…` + `${cid}transient upstream ${status} on account ${masked}, rotating to next… ${egress}` ); // Deliberately a separate branch from the 400-empty arm below, // not one merged `if`: this arm never touches the body, the 400 @@ -1069,7 +1125,8 @@ export class OpencodeExecutor extends BaseExecutor { const key = proxyKeyOf(account.proxy); if (key !== null) geoTriedProxyKeys.add(key); else directTried = true; - log?.warn?.("OPENCODE", `${cid}geo-blocked on account ${masked}, rotating…`); + const setAsideMs = noteRefused(account, skipRecentlyFailed, "geo_blocked"); + egressPacing.logRefusedOutcome(log, cid, masked, setAsideMs, "geo-blocked", egress); // Single account with a proxy: 0 retries (same egress = dead latency). // (The fast path above already covers single-without-proxy; here length===1 WITH proxy.) if (accounts.length === 1) { @@ -1096,7 +1153,7 @@ export class OpencodeExecutor extends BaseExecutor { const rotate = userBlockedRotations === 0 && accounts.length > 1; log?.warn?.( "OPENCODE", - `${cid}user_blocked ${status} on account ${masked} (proxy ${key ?? "direct"}), ${rotate ? "rotating to next account once…" : "returning the refusal"}` + `${cid}user_blocked ${status} on account ${masked}, ${rotate ? "rotating to next account once…" : "returning the refusal"} ${egress}` ); if (!rotate) { if (attributionOn && skippedCooldown.size > 0) { @@ -1112,6 +1169,11 @@ export class OpencodeExecutor extends BaseExecutor { // request shape), not this account. Handled in opencodeFreeTierRetry.ts // (one bounded retry with observed tools appended, then unchanged return). if (bodyText !== null && isOpencodeFreeTierRefusal(status, bodyText)) { + noteFreeTierOutcome(attemptFor(input.body), { + ok: false, + status, + bodyText, + }); if (attributionOn && skippedCooldown.size > 0) { this.logSkippedCooldownAccounts(log, cid, skippedCooldown); } @@ -1154,7 +1216,7 @@ export class OpencodeExecutor extends BaseExecutor { transientStreak = priorTransientStreak + 1; log?.warn?.( "OPENCODE", - `${cid}upstream empty rejection on account ${masked} (${chatcmplId}), rotating to next…` + `${cid}upstream empty rejection on account ${masked} (${chatcmplId}), rotating to next… ${egress}` ); continue; } @@ -1296,6 +1358,7 @@ export class OpencodeExecutor extends BaseExecutor { forwardOpencodeClientHeaders(headers, clientHeaders ?? {}, { synthesizeRequestId: true, cliDefaults, + keepAgentUserAgent: this._surface() === "go" && !gatedScope, // #15311 sessionBody: projectOpencodeSessionBody(body), }); } diff --git a/open-sse/executors/opencodeEgressThrottle.ts b/open-sse/executors/opencodeEgressThrottle.ts index 35510e9b2ec3..885a64d26b7b 100644 --- a/open-sse/executors/opencodeEgressThrottle.ts +++ b/open-sse/executors/opencodeEgressThrottle.ts @@ -24,7 +24,9 @@ import { noteProxyRefusal, proxyEgressKey, recordSlowOverrun, + type ProxyRefusalKind, } from "../utils/proxyRefusalMemory.ts"; +import { stripIpv6Brackets } from "../utils/proxyFamily.ts"; export const DIRECT_EGRESS_SENTINEL = "direct"; @@ -112,6 +114,53 @@ export function egressKeyOf(proxy: { host: string; port: number } | null): strin return proxyEgressKey(proxy) ?? DIRECT_EGRESS_SENTINEL; } +/** Port written in the authority, which `new URL()` drops at the scheme default. */ +function explicitEgressPortOf(url: string): string | null { + const start = url.indexOf("://"); + if (start === -1) return null; + const rest = url.slice(start + 3); + const slash = rest.indexOf("/"); + const authority = slash === -1 ? rest : rest.slice(0, slash); + const colon = authority.lastIndexOf(":"); + if (colon === -1 || colon < authority.lastIndexOf("@") || colon < authority.lastIndexOf("]")) { + return null; + } + const port = authority.slice(colon + 1); + if (!/^\d+$/.test(port)) return null; + const n = Number(port); + return n >= 1 && n <= 65535 ? String(n) : null; +} + +/** + * Log label for the egress really applied to one attempt: `(proxy :)` + * with host and port only, `(proxy direct)` when no proxy applies. Parses the + * applied key directly (it already holds `scheme://`); only the extracted + * hostname and port are ever printed, so member keys carrying user info cannot + * leak. Fail-open: any unusable key reports direct, never throws. + */ +export function egressLabel( + account: AppliedEgressAccount, + readApplied?: AppliedEgressReader | null +): string { + let key: string; + try { + key = resolveAppliedEgressKey(account, readApplied); + } catch { + return "(proxy direct)"; + } + if (!key || key === DIRECT_EGRESS_SENTINEL) return "(proxy direct)"; + try { + const bare = key.replace(/\?family=(ipv4|ipv6)$/, ""); + const parsed = new URL(bare); + const port = explicitEgressPortOf(bare) || parsed.port || null; + const host = stripIpv6Brackets(parsed.hostname).toLowerCase(); + if (!host || !port || !/^\d+$/.test(port)) return "(proxy direct)"; + return `(proxy ${host}:${port})`; + } catch { + return "(proxy direct)"; + } +} + // --------------------------------------------------------------------------- // Applied egress key: pool-served accounts without their own proxy share the // `direct` sentinel above, although the request actually leaves through the @@ -162,7 +211,11 @@ export interface AppliedEgressTracker { keyOfMember: (account: AppliedEgressAccount) => string | null; resetAttempt: () => void; rememberServed: (account: AppliedEgressAccount) => void; - noteRefused: (account: AppliedEgressAccount, skipRecentlyFailed: boolean) => number | null; + noteRefused: ( + account: AppliedEgressAccount, + skipRecentlyFailed: boolean, + kind?: ProxyRefusalKind + ) => number | null; } /** @@ -197,8 +250,11 @@ export function createAppliedEgressTracker( if (attemptAmbientKey === undefined) attemptAmbientKey = resolveAmbientKey(); return attemptAmbientKey; }; - const noteRefused = (a: AppliedEgressAccount, skipRecentlyFailed: boolean) => - noteRefusedMember(a.proxy, skipRecentlyFailed, readAppliedKey, a.fingerprint); + const noteRefused = ( + a: AppliedEgressAccount, + skipRecentlyFailed: boolean, + kind: ProxyRefusalKind = "ip_quota_429" + ) => noteRefusedMember(a.proxy, skipRecentlyFailed, readAppliedKey, a.fingerprint, kind); return { readAppliedKey, keyOfMember: (a) => (a.proxy !== null ? proxyEgressKey(a.proxy) : readAppliedKey(a)), @@ -494,7 +550,8 @@ export function noteRefusedMember( proxy: { host: string; port: number } | null, skipRecentlyFailed: boolean, readApplied?: AppliedEgressReader | null, - fingerprint?: string + fingerprint?: string, + kind: ProxyRefusalKind = "ip_quota_429" ): number | null { if (!skipRecentlyFailed) return null; const key = @@ -502,7 +559,7 @@ export function noteRefusedMember( ? proxyEgressKey(proxy) : resolveAppliedEgressKey({ proxy, fingerprint }, readApplied); if (key === null || key === DIRECT_EGRESS_SENTINEL) return null; - return noteProxyRefusal(key, "ip_quota_429"); + return noteProxyRefusal(key, kind); } /** @@ -579,6 +636,32 @@ export function releasePacingSlot(release: (() => void) | null): void { release?.(); } +/** + * Log one refused-member outcome on the request logger. The wording lives + * here so each arm holds one call; the loop control stays at the seam. + */ +export function logRefusedOutcome( + log: { warn?: (tag: string, message: string) => void } | undefined, + cid: string, + masked: string, + setAsideMs: number | null, + refusal: "geo-blocked" | "burst 429", + egress = "" +): void { + // Egress label position is pinned by tests/unit/opencode-egress-label.test.ts: + // after the account for a burst 429, at the very end for a geo-block. + const burst = refusal === "burst 429"; + log?.warn?.( + "OPENCODE", + `${cid}${refusal} on account ${masked}` + + (burst && egress ? ` ${egress}` : "") + + (setAsideMs ? `, member set aside for ${Math.round(setAsideMs / 1000)}s` : "") + + ", rotating" + + (burst ? " to next" : "") + + "…" + + (!burst && egress ? ` ${egress}` : "") + ); +} /** * Log one 429 outcome on the request logger. The stop/park/rotate wording * lives here so the arm holds one call; the loop control stays at the seam. @@ -588,22 +671,21 @@ export function log429Outcome( cid: string, arm: "stop" | "park" | "rotate", masked: string, - setAsideMs: number | null + setAsideMs: number | null, + label: string ): void { if (arm === "stop") { - log?.warn?.("OPENCODE", `${cid}rate-limited 429 on account ${masked}, stopping the wave`); - } else if (arm === "park") { log?.warn?.( "OPENCODE", - `${cid}fleet backing off: slot budget used, parking (returning last answer)` + `${cid}rate-limited 429 on account ${masked} ${label}, stopping the wave` ); - } else { + } else if (arm === "park") { log?.warn?.( "OPENCODE", - `${cid}burst 429 on account ${masked}` + - (setAsideMs ? `, member set aside for ${Math.round(setAsideMs / 1000)}s` : "") + - ", rotating to next…" + `${cid}fleet backing off ${label}: slot budget used, parking (returning last answer)` ); + } else { + logRefusedOutcome(log, cid, masked, setAsideMs, "burst 429", label); } } /** diff --git a/open-sse/executors/opencodeFreeTierContract.ts b/open-sse/executors/opencodeFreeTierContract.ts index 39aa84de0e5f..328333a59b74 100644 --- a/open-sse/executors/opencodeFreeTierContract.ts +++ b/open-sse/executors/opencodeFreeTierContract.ts @@ -23,11 +23,13 @@ */ import { parseSSEToOpenAIResponse, parseSSEToResponsesOutput } from "../handlers/sseParser.ts"; import { + confirmBorrowedToolNames, getObservedToolNames, noteRefusedBorrowedToolNames, recordAcceptedToolNames, resolvePlaceholderNames, } from "./opencodeToolObservation.ts"; +import { isOpencodeFreeTierRefusal } from "./opencodeGeoBlock.ts"; import { forgetAttempt, planShape, @@ -393,7 +395,7 @@ export function prepareFreeTierRequest( shape: chosen, key, probe: plan.probe, - replayNote: () => noteFreeTierOutcome({ ...attempt, probe: false }, false), + replayNote: (verdict) => noteFreeTierOutcome({ ...attempt, probe: false }, verdict), }); } return { @@ -410,15 +412,34 @@ function withStreaming(body: T): T { return { ...(body as Record), stream: true } as T; } +/** + * What the upstream answered, with enough detail to tell a refusal about the + * tools apart from any other failure. A bare boolean keeps the legacy callers + * compiling: `true` counts as an acceptance, `false` carries no verdict and is + * never counted. + */ +export interface FreeTierOutcome { + readonly ok: boolean; + readonly status: number | null; + readonly bodyText: string | null; +} + /** * Feed a gated request's outcome back, so the next one borrows a shape that still works. * * An accepted request teaches which names the upstream takes right now; a refused one only - * teaches something when the names it carried came from the store. + * teaches something when the names it carried came from the store AND the refusal says + * something about the tools (a 403/451 carrying the refusal signals — a 429, a 5xx or a + * refusal about something else leaves the store alone). */ -export function noteFreeTierOutcome(attempt: FreeTierContractAttempt | null, ok: boolean): void { +export function noteFreeTierOutcome( + attempt: FreeTierContractAttempt | null, + outcome: boolean | FreeTierOutcome +): void { if (!attempt) return; - if (ok) { + const decided: FreeTierOutcome = + typeof outcome === "boolean" ? { ok: outcome, status: null, bodyText: null } : outcome; + if (decided.ok) { if (attempt.clientToolNames.length > 0) { recordAcceptedToolNames( attempt.provider, @@ -426,11 +447,15 @@ export function noteFreeTierOutcome(attempt: FreeTierContractAttempt | null, ok: attempt.session, attempt.clientToolNames ); + } else if (attempt.borrowed && !attempt.probe) { + confirmBorrowedToolNames(attempt.provider, attempt.model, attempt.session); } return; } if (attempt.probe) return; - if (attempt.borrowed) { + if (!attempt.borrowed) return; + if (decided.status === null) return; + if (isOpencodeFreeTierRefusal(decided.status, decided.bodyText)) { noteRefusedBorrowedToolNames(attempt.provider, attempt.model, attempt.session); } } diff --git a/open-sse/executors/opencodeFreeTierRetry.ts b/open-sse/executors/opencodeFreeTierRetry.ts index 2462ee61eb0a..95caf46a86de 100644 --- a/open-sse/executors/opencodeFreeTierRetry.ts +++ b/open-sse/executors/opencodeFreeTierRetry.ts @@ -39,6 +39,8 @@ function retryScopeApplies(ctx: RetryCtx, input: ExecutorInput, status: number): if (!isGatedFreeTierRequest(ctx.surface, ctx.provider, String(input.model ?? ""))) return false; // Borrowed tools are the store's own names coming back: the retry would add // nothing, and the refusal is already counted by noteFreeTierOutcome below. + // Only a refusal naming the model clears borrowed tools (#15475); other + // failures leave the streak untouched. if (ctx.borrowed) return false; return !!input.body && typeof input.body === "object" && !Array.isArray(input.body); } diff --git a/open-sse/executors/opencodeGeoBlock.ts b/open-sse/executors/opencodeGeoBlock.ts index 32ec130d7b9c..c81dbe55a60c 100644 --- a/open-sse/executors/opencodeGeoBlock.ts +++ b/open-sse/executors/opencodeGeoBlock.ts @@ -92,6 +92,25 @@ export function isOpencodeFreeTierRefusal(status: number, bodyText: string | nul return FREE_TIER_SIGNALS.some((signal) => lower.includes(signal)); } +// Exact upstream token seen on title-shaped refusals: a 403 or 451 carrying +// `insufficient_quota` may refuse the request shape rather than the account. +// The shape replay probes the other shape once; more specific refusals +// (fingerprint, geo, user_blocked) win; anything else is not a shape refusal. +const QUOTA_SHAPE_SIGNAL = "insufficient_quota"; + +export function isOpencodeQuotaShapeRefusal(status: number, bodyText: string | null): boolean { + if (status !== 403 && status !== 451) return false; + const text = String(bodyText || ""); + if ( + isFingerprintRejection(text) || + isOpencodeGeoBlocked(status, text) || + isOpencodeUserBlocked(status, text) + ) { + return false; + } + return text.toLowerCase().includes(QUOTA_SHAPE_SIGNAL); +} + export function proxyKeyOf(proxy: { host: string; port: number } | null): string | null { if (!proxy) return null; return `${proxy.host}:${proxy.port}`; diff --git a/open-sse/executors/opencodeRequestShape.ts b/open-sse/executors/opencodeRequestShape.ts index 678590900ba1..1c1407c9bb33 100644 --- a/open-sse/executors/opencodeRequestShape.ts +++ b/open-sse/executors/opencodeRequestShape.ts @@ -11,8 +11,9 @@ * (`WeakMap`), never on the executor: it is a shared instance and requests overlap. */ import { createHash } from "node:crypto"; -import { isOpencodeFreeTierRefusal } from "./opencodeGeoBlock.ts"; +import { isOpencodeFreeTierRefusal, isOpencodeQuotaShapeRefusal } from "./opencodeGeoBlock.ts"; import type { ExecuteInput, ExecutorExecuteResult } from "./base.ts"; +import type { FreeTierOutcome } from "./opencodeFreeTierContract.ts"; export type RequestShape = "tools" | "bare"; @@ -23,8 +24,12 @@ export interface InjectionContext { readonly key: string; /** False when a refusal is final for this request: its outcome is noted as it happens. */ readonly probe: boolean; - /** Replays the outcome note that was deferred while a replay was still possible. */ - readonly replayNote: () => void; + /** + * Replays the outcome note that was deferred while a replay was still possible. The + * verdict travels with the note, so a refusal that says something about the borrowed + * tools still counts once no replay can rescue it. + */ + readonly replayNote: (verdict: FreeTierOutcome) => void; } /** Prompt classes that the upstream accepts bare. Bounded; least recently confirmed goes first. */ @@ -137,17 +142,27 @@ function responseOf(result: ExecutorExecuteResult): Response | null { return result && "response" in result && result.response ? result.response : null; } -async function isShapeRefusal(response: Response, log: ExecuteInput["log"]): Promise { - if (response.status !== 403 && response.status !== 451) return false; +async function readVerdict(response: Response, log: ExecuteInput["log"]): Promise { + const status = response.status; + if (status !== 403 && status !== 451) return { ok: false, status, bodyText: null }; try { - return isOpencodeFreeTierRefusal(response.status, await response.clone().text()); + return { ok: false, status, bodyText: await response.clone().text() }; } catch { // Unreadable body: treated as "not a shape refusal", so the response is returned as-is. log?.debug?.("OPENCODE", "refusal body unreadable, no replay"); - return false; + return { ok: false, status, bodyText: null }; } } +async function isShapeRefusal(verdict: FreeTierOutcome): Promise { + if (verdict.status !== 403 && verdict.status !== 451) return false; + if (verdict.bodyText === null) return false; + return ( + isOpencodeFreeTierRefusal(verdict.status, verdict.bodyText) || + isOpencodeQuotaShapeRefusal(verdict.status, verdict.bodyText) + ); +} + /** * Run one execution and, when the upstream refuses a request whose tool shape we chose, * replay it once in the other shape. @@ -156,10 +171,10 @@ async function isShapeRefusal(response: Response, log: ExecuteInput["log"]): Pro * shape. A request whose tools the client declared is never touched (no context recorded). */ /** What a replay taught: the shape that worked is remembered, two refusals pause the class. */ -function learnFromReplay(ctx: InjectionContext, accepted: boolean): void { +function learnFromReplay(ctx: InjectionContext, accepted: boolean, verdict: FreeTierOutcome): void { if (!accepted) { pause(ctx.key); - ctx.replayNote(); + ctx.replayNote(verdict); } else if (ctx.shape === "tools") { rememberBare(ctx.key); } else { @@ -175,16 +190,17 @@ async function replayInOtherShape( refused: Response, origin: object ): Promise { + const verdict = await readVerdict(refused, input.log); await refused.body?.cancel().catch(() => undefined); forced.set(origin, ctx.shape === "bare" ? "tools" : "bare"); let second: ExecutorExecuteResult; try { second = await run(input); } catch (error) { - ctx.replayNote(); + ctx.replayNote(verdict); throw error; } - learnFromReplay(ctx, responseOf(second)?.ok === true); + learnFromReplay(ctx, responseOf(second)?.ok === true, verdict); return second; } @@ -205,8 +221,9 @@ export async function withRequestShapeRetry( const ctx = isObject(origin) ? contexts.get(origin) : undefined; const response = responseOf(first); if (!ctx || !isObject(origin) || !response || response.ok || !ctx.probe) return first; - if (input.signal?.aborted || !(await isShapeRefusal(response, input.log))) { - ctx.replayNote(); + const verdict = await readVerdict(response, input.log); + if (input.signal?.aborted || !(await isShapeRefusal(verdict))) { + ctx.replayNote(verdict); return first; } return await replayInOtherShape(input, run, ctx, response, origin); diff --git a/open-sse/executors/opencodeResponsesStall.ts b/open-sse/executors/opencodeResponsesStall.ts index f14bc8240e5b..8579512d6329 100644 --- a/open-sse/executors/opencodeResponsesStall.ts +++ b/open-sse/executors/opencodeResponsesStall.ts @@ -22,9 +22,12 @@ import { getResponsesFirstByteTimeoutMs, getUpstreamTimeoutConfig, } from "@/shared/utils/runtimeTimeouts"; -import { guardResponsesStreamFirstByte } from "../utils/firstByteWatchdog.ts"; +import { + guardResponsesStreamFirstByte, + isResponsesFirstByteTimeout, +} from "../utils/firstByteWatchdog.ts"; -export { isResponsesFirstByteTimeout } from "../utils/firstByteWatchdog.ts"; +export { isResponsesFirstByteTimeout }; export type StallGuardSetup = { windowMs: number; @@ -81,6 +84,139 @@ export function resolveResponsesStallWindowMs( return configured > cap ? cap : configured; } +/** + * Shadow first-byte counters: occurrences and cumulative silent time measured + * while the guard stays off, plus shadow-only failures (clone or watch + * errors, read through the shared debug sink). Three module scalars, + * constant size by construction — nothing grows per request. + */ +let shadowOccurrences = 0; +let shadowTotalMs = 0; +let shadowFailures = 0; + +export type StallShadowOptions = { + stream: boolean | undefined; + requestFormat: string | null; + windowMs: number; + signal?: AbortSignal | null; + log?: { + warn?: (tag: string, message: string) => void; + debug?: (tag: string, message: string) => void; + } | null; + cid?: string; + readBound?: () => number; + readConfigured?: () => number; +}; + +function shadowApplies( + result: T, + stream: boolean | undefined, + requestFormat: string | null, + windowMs: number +): result is T & { response: Response } { + if (!stream || requestFormat !== "openai-responses" || windowMs !== 0) return false; + if (!result || typeof result !== "object" || !("response" in result)) return false; + const response = (result as { response: Response }).response; + return !!response?.ok && !!response.body; +} + +function shadowWindowMs(readBound: () => number, readConfigured: () => number): number | null { + const configured = readConfigured(); + if (configured === 0) return null; + const cap = readBound(); + return cap > 0 ? Math.min(configured, cap) : configured; +} + +function reportShadowHit( + log: StallShadowOptions["log"], + cid: string, + windowMs: number, + elapsedMs: number +): void { + shadowOccurrences += 1; + shadowTotalMs += elapsedMs; + log?.warn?.( + "OPENCODE", + `${cid}silent streamed reply past ${windowMs}ms ` + + `(shadow count ${shadowOccurrences}, total ${shadowTotalMs}ms, ` + + `watch failures ${shadowFailures})` + ); +} + +function reportShadowMiss(log: StallShadowOptions["log"], cid: string): void { + shadowFailures += 1; + log?.debug?.("OPENCODE", `${cid}silent stream shadow watch ended`); +} + +/** + * Passive shadow of the stall guard: when the guard does not apply + * (`windowMs` 0) but the request is a streamed Responses reply with a 2xx + * body, watches a clone of the body for the configured window and counts how + * often it would have fired. Never rotates, never cools down, never rejects + * toward the caller: the input result is handed back untouched and every + * shadow failure lands in `shadowFailures` instead. + */ +export function noteStallShadow(result: T, options: StallShadowOptions): T { + const { + stream, + requestFormat, + windowMs, + signal, + log, + cid = "", + readBound = () => getUpstreamTimeoutConfig().streamReadinessTimeoutMs, + readConfigured = getResponsesFirstByteTimeoutMs, + } = options; + if (!shadowApplies(result, stream, requestFormat, windowMs)) return result; + const window = shadowWindowMs(readBound, readConfigured); + if (window === null) return result; + let clone: Response; + try { + clone = result.response.clone(); + } catch { + shadowFailures += 1; + return result; + } + const startedAt = Date.now(); + void guardResponsesStreamFirstByte(clone, window, signal).then( + () => { + void clone.body?.cancel().catch(() => { + shadowFailures += 1; + }); + }, + (error: unknown) => { + if (isResponsesFirstByteTimeout(error)) { + reportShadowHit(log, cid, window, Date.now() - startedAt); + return; + } + reportShadowMiss(log, cid); + } + ); + return result; +} + +/** + * Builds the request-scoped stall wrapper: counts silent streamed replies + * that would have fired past the configured window while the guard stays + * off, then applies the guard itself. Keeps the executor call site a one-line + * wiring hunk; the shadow runs inside `noteStallShadow` next to the guard it + * mirrors and never rotates, cools down, or rejects toward the caller. + */ +export function makeStallGuardedCall( + stream: boolean | undefined, + requestFormat: string | null, + windowMs: number, + signal?: AbortSignal | null, + log?: { warn?: (tag: string, message: string) => void } | null, + cid = "" +): (result: T | Promise) => Promise { + return (result: T | Promise): Promise => + Promise.resolve(result).then((resolved) => { + noteStallShadow(resolved, { stream, requestFormat, windowMs, signal, log, cid }); + return guardResponsesStall(resolved, windowMs, signal); + }); +} + /** * Returns `result` itself when `windowMs` is 0 or it carries no 2xx body; * otherwise resolves once the first body byte arrives, or throws diff --git a/open-sse/executors/opencodeToolObservation.ts b/open-sse/executors/opencodeToolObservation.ts index 0659aec3a395..07964c1d7554 100644 --- a/open-sse/executors/opencodeToolObservation.ts +++ b/open-sse/executors/opencodeToolObservation.ts @@ -125,6 +125,22 @@ export function noteRefusedBorrowedToolNames( consecutiveRefusals.delete(key); } +/** + * Confirm a borrowed shape that just worked: the streak of refusals is dropped, + * the entry itself is left alone (no rewrite, so its eviction rank is kept). + * + * Called only when a request that BORROWED this entry was answered OK. A 200 on + * borrowed names proves the shape still works, which cancels the noise the + * refusal threshold exists to absorb. + */ +export function confirmBorrowedToolNames(provider: string, model: string, session?: string): void { + const key = observed.has(keyOf(provider, model, session)) + ? keyOf(provider, model, session) + : keyOf(provider, model); + if (!observed.has(key)) return; + consecutiveRefusals.delete(key); +} + /** The names last seen accepted for this surface, model and — when known — session. */ export function getObservedToolNames( provider: string, diff --git a/open-sse/executors/trae.ts b/open-sse/executors/trae.ts index 6330c6ac1056..5b4a02b741e4 100644 --- a/open-sse/executors/trae.ts +++ b/open-sse/executors/trae.ts @@ -19,6 +19,7 @@ import { BaseExecutor, mergeUpstreamExtraHeaders } from "./base.ts"; import { PROVIDERS } from "../config/constants.ts"; import { sanitizeErrorMessage } from "../utils/error.ts"; +import { currentAppliedProxySink } from "../utils/proxyFetch.ts"; import { resolvePublicCred } from "../utils/publicCreds.ts"; import { resolveTraeApiHost } from "../utils/traeHost.ts"; @@ -169,7 +170,12 @@ export class TraeExecutor extends BaseExecutor { signal: signal || undefined, }); const text = await res.text(); - if (!res.ok) throw new Error(`[${res.status}] ${text}`); + if (!res.ok) { + // Publish the received status so proxy health counts it as upstream. + const sink = currentAppliedProxySink(); + if (sink) sink.upstreamStatus = res.status; + throw new Error(`[${res.status}] ${text}`); + } const json = JSON.parse(text); if (json?.code !== 0) throw new Error(`Trae create_session: ${JSON.stringify(json)}`); return { sessionId: json.data.chat_session_id, messageId: json.data.message_id }; @@ -188,15 +194,25 @@ export class TraeExecutor extends BaseExecutor { ): Promise { const url = `${this.base()}/chat_sessions/${sessionId}/events?reply_to_message_id=${encodeURIComponent(replyTo)}`; const ctrl = new AbortController(); - // If the caller's signal is already aborted, abort upfront so we don't open - // a network request the consumer no longer wants. - if (signal?.aborted) ctrl.abort(); const timer = setTimeout(() => ctrl.abort(new Error("trae stream timeout")), STREAM_TIMEOUT_MS); const onAbort = () => ctrl.abort(); - if (signal) signal.addEventListener("abort", onAbort, { once: true }); + if (signal) { + // If the caller's signal is already aborted, abort upfront so we don't open + // a network request the consumer no longer wants. + if (signal.aborted) ctrl.abort(); + signal.addEventListener("abort", onAbort, { once: true }); + } try { const res = await fetch(url, { method: "GET", headers, signal: ctrl.signal }); - if (!res.ok || !res.body) throw new Error(`[${res.status}] events stream failed`); + if (!res.ok || !res.body) { + // A 2xx with no body is a local stream failure. Only a real HTTP + // error counts as an upstream status for proxy health. + if (!res.ok) { + const sink = currentAppliedProxySink(); + if (sink) sink.upstreamStatus = res.status; + } + throw new Error(`[${res.status}] events stream failed`); + } const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = ""; diff --git a/open-sse/executors/zed-hosted.ts b/open-sse/executors/zed-hosted.ts index 03209e00ca93..5d4901f8e208 100644 --- a/open-sse/executors/zed-hosted.ts +++ b/open-sse/executors/zed-hosted.ts @@ -47,6 +47,7 @@ import { import { buildErrorBody } from "../utils/error.ts"; import { hasUsefulStreamContent } from "../utils/streamReadiness.ts"; import { resolveSuppressThinkClose, THINKING_MARKER_HEADER } from "../utils/thinkCloseMarker.ts"; +import { getZedClientVersion } from "./zedClientVersion.ts"; // Wire values for the `provider` field of POST /completions. These are NOT // display names: cloud.zed.dev matches them exactly, and an unrecognized value @@ -528,7 +529,7 @@ export class ZedHostedExecutor extends BaseExecutor { Accept: "application/x-ndjson, text/event-stream, */*", "User-Agent": `OmniRoute/zed-hosted`, "x-zed-version": - (this.config as Record)?.appVersion?.toString() || "0.200.0", + (this.config as Record)?.appVersion?.toString() || getZedClientVersion(), [ZED_HEADERS.clientSupportsStatus]: "true", [ZED_HEADERS.clientSupportsStreamEnded]: "true", }, diff --git a/open-sse/executors/zedClientVersion.ts b/open-sse/executors/zedClientVersion.ts new file mode 100644 index 000000000000..00f77e8fd782 --- /dev/null +++ b/open-sse/executors/zedClientVersion.ts @@ -0,0 +1,115 @@ +/** + * Advertised Zed editor version for the x-zed-version header. + * + * The captured pin is the floor. A newer dotted triple from the latest + * zed-industries/zed GitHub release replaces it for 6 hours. The lookup is + * fire-and-forget; a cold or failed fetch stays on the pin. An explicit + * executor config.appVersion still wins over both. + */ + +export const ZED_CLIENT_VERSION = "0.200.0"; + +const ZED_RELEASE_URL = "https://api.github.com/repos/zed-industries/zed/releases/latest"; +export const ZED_VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000; +export const ZED_VERSION_FETCH_TIMEOUT_MS = 20_000; +const DOTTED_TRIPLE_PATTERN = /^\d+\.\d+\.\d+$/; + +type FetchLike = typeof fetch; + +let cachedVersion: string | null = null; +let cachedAt = 0; +let inFlight: Promise | null = null; + +function parseReleaseTag(tagName: unknown): string | null { + if (typeof tagName !== "string") return null; + const stripped = tagName.trim().replace(/^v/, ""); + return DOTTED_TRIPLE_PATTERN.test(stripped) ? stripped : null; +} + +function compareDottedTriple(left: string, right: string): number { + const leftParts = left.split(".").map((part) => Number.parseInt(part, 10) || 0); + const rightParts = right.split(".").map((part) => Number.parseInt(part, 10) || 0); + for (let i = 0; i < 3; i += 1) { + if (leftParts[i] !== rightParts[i]) return leftParts[i] - rightParts[i]; + } + return 0; +} + +function pickAtLeastPin(version: string | null): string { + if (!version || compareDottedTriple(version, ZED_CLIENT_VERSION) <= 0) { + return ZED_CLIENT_VERSION; + } + return version; +} + +function readFreshCache(): string | null { + if (!cachedVersion) return null; + if (Date.now() - cachedAt >= ZED_VERSION_CACHE_TTL_MS) return null; + return cachedVersion; +} + +/** + * Sync hot path. A cached release newer than the pin, else the pin. + * Outside tests, a stale cache starts one background refresh; this call + * itself never waits on the network. + */ +export function getZedClientVersion(): string { + if (typeof process !== "undefined" && !process.env.NODE_TEST_CONTEXT && !readFreshCache() && !inFlight) { + void refreshZedClientVersion(); + } + return pickAtLeastPin(readFreshCache()); +} + +/** + * Warm the GitHub cache (20s timeout, 6h TTL, coalesced, never rejects). + * Any failure — network, non-2xx, or a tag that is not a dotted triple — + * leaves the pin (or the previous fresh cache) in place. A fetched version + * is stored only when it is newer than the pin. + */ +export function refreshZedClientVersion(fetchImpl: FetchLike = fetch): Promise { + const fresh = readFreshCache(); + if (fresh) return Promise.resolve(pickAtLeastPin(fresh)); + if (inFlight) return inFlight; + + inFlight = (async () => { + let resolved: string | null = null; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), ZED_VERSION_FETCH_TIMEOUT_MS); + try { + const response = await fetchImpl(ZED_RELEASE_URL, { + headers: { + Accept: "application/json", + "User-Agent": "OmniRoute-ZedVersion/1.0", + }, + signal: controller.signal, + }); + if (response.ok) { + const payload = (await response.json()) as { tag_name?: unknown }; + resolved = parseReleaseTag(payload?.tag_name); + } + } catch { + resolved = null; + } finally { + clearTimeout(timeoutId); + } + + if (resolved && compareDottedTriple(resolved, ZED_CLIENT_VERSION) > 0) { + cachedVersion = resolved; + cachedAt = Date.now(); + } + return pickAtLeastPin(resolved ?? readFreshCache()); + })(); + + const current = inFlight; + void current.finally(() => { + if (inFlight === current) inFlight = null; + }); + return current; +} + +/** Test seam: drop the release cache so the next refresh hits the network. */ +export function resetZedClientVersionCache(): void { + cachedVersion = null; + cachedAt = 0; + inFlight = null; +} diff --git a/open-sse/handlers/audioTranscription.ts b/open-sse/handlers/audioTranscription.ts index 1aba51834035..567c402855f7 100644 --- a/open-sse/handlers/audioTranscription.ts +++ b/open-sse/handlers/audioTranscription.ts @@ -1,4 +1,5 @@ import { CORS_HEADERS } from "../utils/cors.ts"; +import { upstreamErrorResponse } from "../utils/audioResponse.ts"; import { Buffer } from "node:buffer"; /** * Audio Transcription Handler @@ -34,34 +35,21 @@ type TranscriptionCredentials = { }; /** - * Return a CORS error response from an upstream fetch failure + * Return a CORS error response from an upstream fetch failure. + * + * #15159: re-exported from `open-sse/utils/audioResponse.ts` instead of being defined + * here a second time. Two identical helpers is how the raw upstream passthrough + * survived in BOTH files unnoticed while ~35 call sites depended on it; the single + * definition is what makes the next fix a one-place change. + * + * Re-exported rather than re-pointed at the importers so + * `open-sse/handlers/openrouterTranscription.ts`, which imports this path, keeps + * working unchanged. + * + * Imported AND re-exported: a bare `export … from` does not create a local binding, and + * the transcription providers below call `upstreamErrorResponse` by name. */ -export function upstreamErrorResponse(res, errText) { - // Always return JSON so the client can parse the error reliably - let errorMessage: string; - try { - const parsed = JSON.parse(errText); - // Guard against `parsed.error` or `parsed.detail` being objects - const raw = - parsed?.err_msg || - parsed?.error?.message || - (typeof parsed?.error === "string" ? parsed.error : null) || - parsed?.message || - (typeof parsed?.detail === "string" ? parsed.detail : parsed?.detail?.message) || - null; - errorMessage = raw ? String(raw) : errText || `Upstream error (${res.status})`; - } catch { - errorMessage = errText || `Upstream error (${res.status})`; - } - - return Response.json( - { error: { message: errorMessage, code: res.status } }, - { - status: res.status, - headers: { ...CORS_HEADERS }, - } - ); -} +export { upstreamErrorResponse }; /** * Validate a path segment to prevent path traversal / SSRF. @@ -642,18 +630,25 @@ async function handleKieAudioTranscription(providerConfig, file, modelId, token) typeof err === "object" && err !== null && "status" in err ? Number((err as { status?: unknown }).status) || 502 : 502; - return Response.json( - { - error: { - message: err instanceof Error ? err.message : "Kie transcription createTask failed", - code: status, - }, - }, - { - status, - headers: { ...CORS_HEADERS }, - } + // E-02 (#15159): the caught error is the RAW upstream body text — + // `kieExecutor.createTask` throws `new Error(await res.text())` + // (open-sse/executors/kie.ts:57-61) — so building the body by hand put + // upstream stack frames and credential-shaped substrings straight into the + // client response. Route it through the canonical builder instead; the + // sanitizing `errorResponse` is already imported by this file and the Kie + // poll path below already uses it. + // + // CORS headers are merged back on because this route only sets them on the + // OPTIONS preflight (src/app/api/v1/audio/transcriptions/route.ts:75-82), + // never on the POST response, so dropping them would break browser clients. + const response = errorResponse( + status, + err instanceof Error ? err.message : "Kie transcription createTask failed" ); + for (const [header, value] of Object.entries(CORS_HEADERS)) { + response.headers.set(header, value); + } + return response; } const taskId = data?.data?.taskId || data?.taskId; diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index e618526b9d2f..64609693704c 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -14,13 +14,12 @@ import { import { buildFailureUsageRecord, readCpaAuthIndex, - projectFailureUsageErrorCode, type FailureUsageAggregate, } from "./chatCore/failureUsage.ts"; import { createTranslationFailureResult } from "./chatCore/translationFailure.ts"; import { + estimateCalibratedFinalInputTokens, estimateFinalInputTokenBreakdown, - estimateFinalInputTokens, } from "./chatCore/contextEstimation.ts"; import { extractSystemRoleMessages, @@ -41,31 +40,17 @@ import { detectClassifierFormat, buildDefaultAllowClaudeMessage, } from "./chatCore/claudeClassifierCompat.ts"; -import { buildPostCallGuardrailContext } from "./chatCore/postCallGuardrailContext.ts"; -import { storeSemanticCacheResponse } from "./chatCore/semanticCacheStore.ts"; -import { buildNonStreamingResponseHeaders } from "./chatCore/nonStreamingResponseHeaders.ts"; -import { maybeWrapForcedNonStreamingResponsesJson } from "./chatCore/responsesJsonToSse.ts"; import { enforceOutputTokenBudget } from "./chatCore/outputTokenBudget.ts"; -import { maybeConvertJsonBodyToSse } from "./chatCore/jsonBodyToSse.ts"; import { withResilienceActionsContext } from "./chatCore/resilienceAttemptContext.ts"; -import { runEmptyTurnRetryLoop } from "./chatCore/emptyTurnRetryLoop.ts"; import { notePreviousResponseResumed } from "./chatCore/resumedResilienceNotes.ts"; -import { assembleStreamingResponseHeaders } from "./chatCore/streamingResponseHeaders.ts"; -import { storeStreamingSemanticCacheResponse } from "./chatCore/streamingSemanticCacheStore.ts"; -import { captureStreamReasoningForReplay } from "./chatCore/streamReasoningCapture.ts"; -import { assembleStreamingPipeline } from "./chatCore/streamingPipeline.ts"; +import { runStreamingResponse } from "./chatCore/streamingResponse.ts"; +import { runStreamingTail } from "./chatCore/streamingTail.ts"; import { sanitizeChatRequestBody } from "./chatCore/sanitization.ts"; import { applyReasoningInputPolicy, resolveIncompatibleReasoningAction, } from "../services/reasoningInputPolicy.ts"; -import { - createRoutingEvent, - emitRoutingEvent, - outcomeFromStatus, -} from "../services/routing/index.ts"; -import { routingFinishReason } from "./chatCore/routingFinishReason.ts"; import { getHeaderValueCaseInsensitive, isNoMemoryRequested, @@ -73,22 +58,13 @@ import { } from "./chatCore/headers.ts"; import { - getCodexClientSessionId, isCodexOriginatedHeaders, isClaudeCodeOriginatedHeaders, } from "../config/codexIdentity.ts"; -import { - noteCodexTurnStateProvenance, - readCodexTurnStateHeader, -} from "../config/codexTurnState.ts"; import { trackDevice, extractIpFromHeaders } from "../services/deviceTracker.ts"; import { getCombosCached } from "./chatCore/comboContextCache.ts"; export { clearCombosCache, clearUpstreamProxyConfigCache } from "./chatCore/comboContextCache.ts"; -import { - resolveAccountSemaphoreKey, - resolveAccountSemaphoreMaxConcurrency, - buildClaudePromptCacheLogMeta, -} from "./chatCore/executorHelpers.ts"; +import { resolveAccountSemaphoreKey } from "./chatCore/executorHelpers.ts"; import { shouldUseNativeCodexPassthrough, shouldUseNativeXaiResponsesPassthrough, @@ -98,29 +74,12 @@ import { stripClaudeRejectedTopLevelFields, isClaudeCodeSemanticPassthroughRequest, } from "./chatCore/passthroughHelpers.ts"; -import { recoverAnthropicThinkingSignature } from "./chatCore/thinkingSignatureRecovery.ts"; -import { maybeFallbackAfterReadiness } from "./chatCore/streamReadinessFallback.ts"; -import { runProviderExecutionPipeline } from "./chatCore/providerExecutionPipeline.ts"; -import { runNonStreamingProviderLeg } from "./chatCore/nonStreamingProviderLeg.ts"; -import type { NonStreamingProviderLegResult } from "@/lib/skills/toolLoopTypes.ts"; -import { - applyServerOwnedToolLoopIfNeeded, - derivePostInjectionRequestIdentity, - followUpLegInput, -} from "./chatCore/serverOwnedToolLoopWire.ts"; -import { finalizeToolLoopError } from "./chatCore/nonStreamingFinalization.ts"; -import { markCodexScopeRateLimited } from "./chatCore/codexFailover.ts"; -import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; + import { buildStreamingResponseHeaders, - materializeDeduplicatedExecutionResult, - stripNextMiddlewareControlHeaders, stripStaleForwardingHeaders, } from "./chatCore/responseHeaders.ts"; -import { - forwardDashboardEventToLiveWs, - maybeSyncClaudeExtraUsageState, -} from "./chatCore/telemetryHelpers.ts"; +import { forwardDashboardEventToLiveWs } from "./chatCore/telemetryHelpers.ts"; // Re-export the previously inline-defined helpers so existing importers of these // symbols from chatCore.ts (tests, sibling modules) keep resolving after the split. export { @@ -141,11 +100,10 @@ import { stripStore, usesClaudeBridge } from "./chatCore/agentRouterProtocol.ts" import { normalizeClaudeToolsForDispatch } from "./chatCore/claudeToolDefaults.ts"; import { injectCustomSystemPrompt, - injectSystemPromptPostTranslation, injectSystemPromptPreTranslation, } from "../services/systemPrompt.ts"; import { applyProviderSystemTransforms } from "../services/systemTransforms.ts"; -import { translateRequest, needsTranslation } from "../translator/index.ts"; +import { translateRequest } from "../translator/index.ts"; import { applyReasoningRuleDirective } from "@/lib/reasoningRouting/policy"; import { withReasoningRuleContext } from "../utils/reasoningRuleContext.ts"; import { FORMATS } from "../translator/formats.ts"; @@ -153,28 +111,14 @@ import { collectCustomToolNamesForSourceFormat } from "../translator/request/ope import { sanitizeKiroTools } from "../utils/kiroSanitizer.ts"; import { splitMisplacedToolResults } from "../translator/helpers/claudeHelper.ts"; import { ensureCacheControlOnLastUserMessage } from "../services/claudeCodeConstraints.ts"; -import { - createSSETransformStreamWithLogger, - createPassthroughStreamWithLogger, - COLORS, -} from "../utils/stream.ts"; -import { ensureStreamReadiness } from "../utils/streamReadiness.ts"; -import { requestTtftMs, streamEmittedOutput } from "../utils/streamTiming.ts"; -import { resolveSuppressThinkClose, THINKING_MARKER_HEADER } from "../utils/thinkCloseMarker.ts"; -import { resolveStreamReadinessTimeout } from "../utils/streamReadinessPolicy.ts"; +import { THINKING_MARKER_HEADER } from "../utils/thinkCloseMarker.ts"; import { resolveAgentGoalPolicy } from "../utils/agentGoalPolicy.ts"; -import { hasActiveClaudeThinking } from "../utils/thinkingBudget.ts"; import { createStreamController } from "../utils/streamHandler.ts"; import * as streamFailure from "../utils/streamFailureFinalization.ts"; import { normalizeUsage } from "../utils/usageTracking.ts"; -import { - refreshWithRetry, - isUnrecoverableRefreshError, - runWithOnPersist, - runWithCasGuard, -} from "../services/tokenRefresh.ts"; +import { refreshWithRetry, runWithOnPersist, runWithCasGuard } from "../services/tokenRefresh.ts"; import { createRequestLogger } from "../utils/requestLogger.ts"; -import { createPreparedRequestLogger, runWithCapture } from "../utils/providerRequestLogging.ts"; +import { createPreparedRequestLogger } from "../utils/providerRequestLogging.ts"; import { summarizeToolSources } from "../utils/toolSources.ts"; import { applyResponsesPreviousResponseIdPolicy } from "../utils/responsesStatePolicy.ts"; import { applyClaudeEffortVariant } from "./chatCore/claudeEffortVariant.ts"; @@ -186,8 +130,11 @@ import { import { shouldUseMidConversationSystem } from "../executors/claudeIdentity.ts"; import { echoModelInObject } from "../services/responseModelEcho.ts"; import { getUnsupportedParams, REGISTRY } from "../config/providerRegistry.ts"; -import { shouldSkipCredentialRefresh } from "./chatCore/skipCredentialRefresh.ts"; + import { checkToolCallingRequiredButUnsupported } from "./chatCore/toolCallingRequiredCheck.ts"; +import { buildExecutorClientHeaders } from "./chatCore/executorClientHeaders.ts"; +import { executeProviderRequest as executeProviderRequestLeaf } from "./chatCore/executeProviderRequest.ts"; +import { runNonStreamingResponse } from "./chatCore/nonStreamingResponse.ts"; import { supportsMaxTokens, getResolvedModelCapabilities, @@ -205,19 +152,10 @@ import { isServerOwnedToolLoopEnabled, } from "@/shared/utils/featureFlags.ts"; import { resolveNoAuthEchoModel } from "./chatCore/noAuthEchoModel.ts"; -import { - REASONING_BUFFER_MIN_TRIGGER, - buildReasoningProbeTruncatedResponse, - isEmptyContentUpstreamFailure, - isTinyBudgetReasoningProbe, - toPositiveInteger, -} from "../services/reasoningTokenBuffer.ts"; +import { toPositiveInteger } from "../services/reasoningTokenBuffer.ts"; import { buildErrorBody, createErrorResult, - parseUpstreamError, - formatProviderError, - projectPublicErrorIdentifier, sanitizeErrorMessage, sanitizeUpstreamDetails, } from "../utils/error.ts"; @@ -231,41 +169,22 @@ import { COOLDOWN_MS, HTTP_STATUS, PROVIDER_MAX_TOKENS, - STREAM_READINESS_MAX_TIMEOUT_MS, - STREAM_READINESS_TIMEOUT_MS, - ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE, - STREAM_RECOVERY, DEFAULT_MAX_TOKENS, STREAM_DISCONNECT_GRACE_PERIOD_MS, } from "../config/constants.ts"; -import { applyStatusRestatement } from "../config/upstreamStatusRestatement.ts"; -import { createRecoverableStream, makeContinuationBody } from "../services/streamRecovery.ts"; -import { buildContinuationLogHooks } from "./chatCore/recoveryTraceLogging.ts"; -import { - resolveResilienceSettings, - isStreamRecoveryExplicitlyConfigured, -} from "@/lib/resilience/settings"; + +import { resolveResilienceSettings } from "@/lib/resilience/settings"; import { classifyProviderError, PROVIDER_ERROR_TYPES } from "../services/errorClassifier.ts"; import { isOpencodeFreeTierRefusalForProvider } from "../executors/opencodeGeoBlock.ts"; import { noteOpencodeFreeTierSkip } from "../services/opencodeFreeTierSkip.ts"; import { updateProviderConnection, getProviderConnectionById } from "@/lib/db/providers"; -import { wasRefreshTokenRotated } from "@omniroute/open-sse/services/refreshSerializer.ts"; + import { connectionHasExtraKeys } from "../services/apiKeyRotator.ts"; import { recordKeyHealthStatus as recordKeyHealthStatusFor } from "./chatCore/keyHealth.ts"; import { getSkillsModelIdForFormat } from "./chatCore/skillsFormat.ts"; -import { readNonStreamingResponseBody } from "./chatCore/nonStreamingResponseBody.ts"; -import { - createSafeAbortError, - createStreamingErrorResult, - isSemaphoreCapacityError, - formatStreamRecoveryRetryWarning, - getSafeErrorMetadata, - getUpstreamErrorIdentifier, -} from "./chatCore/streamErrorResult.ts"; -import { wrapReadableStreamWithFinalize } from "./chatCore/streamFinalize.ts"; +import { isSemaphoreCapacityError, getSafeErrorMetadata } from "./chatCore/streamErrorResult.ts"; import { buildCacheUsageLogMeta } from "./chatCore/cacheUsageMeta.ts"; -import { buildExecutorClientHeaders } from "./chatCore/executorClientHeaders.ts"; -import { getExecutionConnectionId } from "./chatCore/executionCredentials.ts"; + import { resolveExecutionCredentials as resolveExecutionCredentialsFor } from "./chatCore/executionCredentials.ts"; import { createExecutorResolver } from "./chatCore/executorProxy.ts"; import type { ClaudeMessage } from "./chatCore/claudeMessageTypes.ts"; @@ -276,7 +195,7 @@ import { } from "./chatCore/attemptLogging.ts"; import { stageTrace } from "./chatCore/stageTrace.ts"; import { attachCompressionUsageReceiptAfterAnalytics as attachCompressionUsageReceiptAfterAnalyticsFor } from "./chatCore/compressionUsageReceipt.ts"; -import { prepareUpstreamBody } from "./chatCore/upstreamBody.ts"; + import { getQuotaScopeLabelForProvider } from "../services/antigravityQuotaFamily.ts"; import { excludeConnectionForCooldown } from "./chatCore/connectionCooldown.ts"; import { handleRequestRejectedFailure } from "./chatCore/requestRejectedFailure.ts"; @@ -297,13 +216,9 @@ import { initialPendingBody, updatePendingScope, } from "@/lib/usage/pendingRequestScope"; -import { recordCost, recordChatCallCost, buildCostCtx } from "@/domain/costRules"; -import { meteredBudgetCost } from "@/lib/usage/meteredBudgetPolicy"; +import { recordChatCallCost, buildCostCtx } from "@/domain/costRules"; import { calculateCost } from "@/lib/usage/costCalculator"; -import { - buildClaudePassthroughToolNameMap, - mergeResponseToolNameMap, -} from "./chatCore/passthroughToolNames.ts"; +import { buildClaudePassthroughToolNameMap } from "./chatCore/passthroughToolNames.ts"; import { createDisabledCompressionConfig, resolveCompressionSettings, @@ -311,8 +226,8 @@ import { import type { EnforceDecision } from "@/lib/quota/types"; import { isCompressionExcluded } from "../services/compression/exclusions.ts"; import { + defaultComboForRequest, isBuiltinStackedPipeline, - isStackedCompressionCombo, type RuntimeCompressionCombo, } from "./chatCore/compressionComboPredicates.ts"; import { emitOutputStyleTelemetry } from "./chatCore/outputStyleTelemetry.ts"; @@ -326,23 +241,11 @@ import { recordCompressionCacheStats } from "./chatCore/compressionCacheStats.ts import { writeCavemanOutputAnalytics } from "./chatCore/cavemanOutputAnalytics.ts"; import { scheduleQuotaShareConsumption } from "./chatCore/quotaShareConsumption.ts"; import { emitRequestGamificationEvent } from "./chatCore/gamificationEvent.ts"; -import { - runPluginOnResponseHook, - runPluginOnStreamCompleteHook, -} from "./chatCore/pluginOnResponse.ts"; -import { scheduleStreamingQuotaShareConsumption } from "./chatCore/streamingQuotaShare.ts"; -import { recordStreamingUsageStats } from "./chatCore/streamingUsageStats.ts"; -import { recordStreamingCost, buildStreamLedgerDetails } from "./chatCore/streamingCost.ts"; +import { runPluginOnResponseHook } from "./chatCore/pluginOnResponse.ts"; import { isJsonRecord } from "./chatCore/nonStreamingResponseParse.ts"; import { recordNonStreamingUsageStats } from "./chatCore/nonStreamingUsageStats.ts"; -import { - normalizeExecutorResult, - executeWithUpstreamStartTimeout, - getExecutorTimeoutMs, - resolveConnectionTimeoutMs, -} from "./chatCore/upstreamTimeouts.ts"; import { getModelNormalizeToolCallId, getModelPreserveOpenAIDeveloperRole } from "@/lib/db/models"; -import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/services/auth"; +import { extractSessionAffinityKey } from "@/sse/services/auth"; import { assertExclusiveConnectionLeaseFence } from "@/lib/db/exclusiveConnectionLeases"; import { getCacheControlSettings } from "@/lib/cacheControlSettings"; @@ -365,39 +268,22 @@ import { type EffectiveServiceTier, } from "./chatCore/serviceTier.ts"; import { isCompactResponsesEndpoint } from "../executors/codex.ts"; -import { persistCodexChildQuotaResponse } from "../services/codexAccount/index.ts"; -import { invalidateCodexQuotaCache } from "../services/codexQuotaFetcher.ts"; -import { invalidateGenericQuotaCacheOnStatus } from "../services/genericQuotaFetcher.ts"; import { extractUsageFromResponse } from "./usageExtractor.ts"; import { - withRateLimit, updateFromHeaders, updateFromResponseBody, initializeRateLimits, - resolveRequestQueueMaxWaitMs, } from "../services/rateLimitManager.ts"; -import * as localLimiterErrors from "../services/rateLimitManager/errors.ts"; -import { rethrowAdmissionError, remainingQueueBudgetMs } from "./chatCore/queueBudget.ts"; -import { - acquireMany as acquireConcurrencyGates, - markBlocked as markAccountSemaphoreBlocked, -} from "../services/accountSemaphore.ts"; +import { markBlocked as markAccountSemaphoreBlocked } from "../services/accountSemaphore.ts"; import { lockModel, lockModelIfPerModelQuota, recordCoreOwnedAntigravityQuotaState, shouldDeferAntigravityQuotaStateToCaller, } from "../services/accountFallback.ts"; -import { clearPostOutputFailureStreak } from "../services/accountFallback/postOutputFailureStreak.ts"; import { saveIdempotency } from "@/lib/idempotencyLayer"; -import { - isModelUnavailableError, - getNextFamilyFallback, - isContextOverflowError, - findLargerContextModel, - getModelFamily, -} from "../services/modelFamilyFallback.ts"; -import { computeRequestHash, deduplicate, shouldDeduplicate } from "../services/requestDedup.ts"; + +import { computeRequestHash, shouldDeduplicate } from "../services/requestDedup.ts"; import { compressContext, estimateTokens, @@ -420,33 +306,17 @@ import { generateRequestId } from "@/shared/utils/requestId"; import { isLocalStreamLifecycleError } from "@/shared/utils/circuitBreaker"; import { shouldIsolateProbeFailures } from "@/shared/utils/probeOrigin"; import { writeTerminalStatus } from "@/shared/utils/terminalStatus"; -import { extractFacts } from "@/lib/memory/extraction"; import { handleToolCallExecution } from "@/lib/skills/interception"; -import { MEMORY_BUILTIN_TOOL_NAMES } from "@/lib/skills/memoryBuiltins"; -import { resolveProviderId } from "@/shared/constants/providers"; import { getClaudeCodeCompatibleRequestDefaults } from "@/lib/providers/requestDefaults"; import { buildClaudeCodeCompatibleRequest, resolveClaudeCodeCompatibleSessionId, } from "../services/claudeCodeCompatible.ts"; import { setGeminiThoughtSignatureMode } from "../services/geminiThoughtSignatureStore.ts"; -import { - classifyModelScope429, - getModelScopeRetryDelayMs, - isModelScopeProvider, -} from "../services/modelscopePolicy.ts"; -import { - incrementRequestCount, - incrementTokenUsage, - isTpmExhausted, -} from "../services/geminiRateLimitTracker.ts"; +import { classifyModelScope429, isModelScopeProvider } from "../services/modelscopePolicy.ts"; +import { incrementTokenUsage, isTpmExhausted } from "../services/geminiRateLimitTracker.ts"; import { getProactiveCompressionRatio } from "@/lib/db/compression"; -type ChatCoreExecutorResult = ReturnType & { - _executionCredentials?: Record; - _accountSemaphoreRelease?: () => void; -}; - /** * #12150 P1b: shape of handleChatCore's optional `videoBridgeLog` param — see * its destructure default below. `handleChatCore`'s own params object has no @@ -1672,15 +1542,20 @@ async function handleChatCoreInner({ try { const { getDefaultCompressionCombo } = await import("../../src/lib/db/compressionCombos.ts"); - const defaultCompressionCombo = getDefaultCompressionCombo(); - if ( - isStackedCompressionCombo(defaultCompressionCombo as RuntimeCompressionCombo | null) && - applyCompressionComboConfig(defaultCompressionCombo as RuntimeCompressionCombo | null) - ) { + const defaultCompressionCombo = defaultComboForRequest( + getDefaultCompressionCombo() as RuntimeCompressionCombo | null, + { config, header: compressionHeader, combos: namedCombos } + ); + if (applyCompressionComboConfig(defaultCompressionCombo)) { log?.debug?.( "COMPRESSION", `Default compression combo applied: ${defaultCompressionCombo?.id}` ); + } else if (compressionHeader) { + log?.debug?.( + "COMPRESSION", + `Default compression combo not applied (header: ${compressionHeader})` + ); } } catch (err) { log?.debug?.( @@ -2207,7 +2082,8 @@ async function handleChatCoreInner({ // filtering is advisory and may preserve an all-incompatible pool; this is the // hard boundary that prevents a too-large prompt (or a negative token budget) // from reaching an OpenAI-compatible upstream such as NVIDIA NIM. - let finalEstimatedInputTokens = estimateFinalInputTokens(body as Record); + // #14931: scaled by the learned actual/estimated ratio (factor 1.0 cold). + let finalEstimatedInputTokens = estimateCalibratedFinalInputTokens(body, provider, effectiveModel); // Reuse the already-resolved `contextLimit` (may have been narrowed to the // per-target combo window above, resolveComboContextLimit) instead of a bare // getTokenLimit(provider, effectiveModel) re-fetch, which would silently @@ -2237,7 +2113,7 @@ async function handleChatCoreInner({ dropMissingMappedItems: true, }) : lastResortResult.body; - finalEstimatedInputTokens = estimateFinalInputTokens(body as Record); + finalEstimatedInputTokens = estimateCalibratedFinalInputTokens(body, provider, effectiveModel); const finalInputBreakdown = estimateFinalInputTokenBreakdown( body as Record ); @@ -2251,6 +2127,7 @@ async function handleChatCoreInner({ } } + const calibrationEstimatedInputTokens = finalEstimatedInputTokens; // #14931 pairing const modelOutputCap = toPositiveInteger( getExplicitModelOutputCap({ provider, model: effectiveModel }) ); @@ -3123,511 +3000,63 @@ async function handleChatCoreInner({ ? computeRequestHash(dedupRequestBody, apiKeyInfo?.id, trustedEffortContext) : null; - const executeProviderRequest = async (modelToCall = effectiveModel, allowDedup = false) => { - const execute = async () => { - // Upstream body preparation extracted to chatCore/upstreamBody.ts (#3501 — first internal - // sub-slice of executeProviderRequest); produces the body sent upstream (payload rules + - // tool-limit truncation + prompt_cache_key injection). - let bodyToSend = await prepareUpstreamBody({ - translatedBody, - modelToCall, - ...trustedEffortContext, - provider, - targetFormat, - credentials: getExecutionCredentials(), - log, - bypassDefaultToolLimit: isOpencodeClient, - isOpencodeClient, - rawBody: body, - clientRawRequest, - }); - - // Global System Prompt — SINGLE injection point (post-translation) for - // carrier-ful targets. The old unconditional pre-translation pass - // (former chatCore injectSystemPrompt call) was removed: it chained - // with this pass to inject prefix/suffix 2-3x and dual-wrote - // body.system + messages[] on the claude path, which strict upstreams - // (HCP-Vision vLLM: "System message must be at the beginning") reject - // with 400. Format-aware via targetFormat: messages[] (openai/codex — - // prefix FIRST system, suffix LAST), claude `system` field, gemini - // `systemInstruction`, responses `instructions`. Carrier-less targets - // (kiro user-fold, antigravity Cloud Code envelope) are covered by the - // gated PRE-translation pass before translateRequest instead. - bodyToSend = injectSystemPromptPostTranslation(bodyToSend, { targetFormat }); - - updatePendingScope(pendingScope, { - providerRequest: bodyToSend, - stage: "payload_prepared", - }); - - let releaseRawResultAccountSemaphore = () => {}; - try { - const rawResult: ChatCoreExecutorResult = await (async () => { - let attempts = 0; - const isModelScopeForRequest = isModelScope(); - const maxAttempts = isModelScopeForRequest ? 3 : provider === "codex" ? 3 : 1; - - while (attempts < maxAttempts) { - trace("pre_executor", { attempt: attempts }); - updatePendingScope(pendingScope, { - stage: "sending_to_provider", - }); - const execCreds = getExecutionCredentials(); - const executionConnectionId = getExecutionConnectionId(execCreds); - const attemptConnectionId = executionConnectionId || connectionId; - const accountSemaphoreMaxConcurrency = resolveAccountSemaphoreMaxConcurrency(execCreds); - const accountSemaphoreKey = resolveAccountSemaphoreKey({ - provider, - model: modelToCall, - connectionId: attemptConnectionId, - credentials: execCreds, - }); - const canonicalProviderKey = resolveProviderId(String(provider).trim().toLowerCase()); - const providerConcurrency = - resilienceSettings.providerQuotaOverrides[canonicalProviderKey] - ?.providerConcurrency ?? 0; - - trace("pre_semaphore", { - semaphoreKey: accountSemaphoreKey, - max: accountSemaphoreMaxConcurrency, - }); - if (accountSemaphoreKey && accountSemaphoreMaxConcurrency != null) { - updatePendingScope(pendingScope, { - stage: "waiting_account_slot", - }); - } - const maxWaitMs = resolveRequestQueueMaxWaitMs( - provider, - undefined, - attemptConnectionId ?? undefined - ); - const gateStartedAt = Date.now(); - const releaseAccountSemaphore = await acquireConcurrencyGates( - [ - { - key: "global", - maxConcurrency: resilienceSettings.requestQueue.globalConcurrentRequests, - }, - { - key: `provider:${canonicalProviderKey}`, - maxConcurrency: providerConcurrency, - }, - { - key: accountSemaphoreKey || "", - maxConcurrency: accountSemaphoreKey ? accountSemaphoreMaxConcurrency : null, - }, - ], - { - timeoutMs: maxWaitMs, - maxQueueSize: resilienceSettings.requestQueue.maxQueueDepth, - signal: streamController.signal, - } - ).catch(rethrowAdmissionError); - const remainingAfterGate = remainingQueueBudgetMs(maxWaitMs, gateStartedAt); - trace("post_semaphore", { maxWaitMs, remainingAfterGate }); - updatePendingScope(pendingScope, { - stage: "waiting_rate_limit", - }); - - try { - trace("pre_rate_limit", { connectionId: attemptConnectionId }); - const rawExecutorResult = await withRateLimit( - provider, - attemptConnectionId, - modelToCall, - async () => { - trace("inside_rate_limit", { connectionId: attemptConnectionId }); - updatePendingScope(pendingScope, { - stage: "rate_limit_slot_acquired", - }); - assertManagedLeaseFence(attemptConnectionId); - return executeWithUpstreamStartTimeout({ - executor, - provider, - model: modelToCall, - connectionTimeoutMs: resolveConnectionTimeoutMs( - execCreds?.providerSpecificData - ), - signal: streamController.signal, - log, - execute: (signal) => - runWithCapture(providerRequestCapture, () => - executor.execute({ - model: modelToCall, - body: bodyToSend, - stream: upstreamStream, - credentials: execCreds, - signal, - log, - extendedContext, - upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall), - clientHeaders: getExecutorClientHeaders(), - clientResponseFormat, - onCredentialsRefreshed, - skipUpstreamRetry, - contextEditing: { enabled: contextEditingEnabled }, - correlationId, - }) - ), - }); - }, - streamController.signal, - remainingAfterGate, - correlationId ?? undefined, - { - executor: executor as unknown as { getTimeoutMs?: () => unknown }, - providerSpecificData: execCreds?.providerSpecificData, - } - ); - const res = normalizeExecutorResult(rawExecutorResult); - trace("post_executor", { status: res?.response?.status }); - - // When a payload override rewrote body.model (custom-model alias → - // real upstream id, e.g. `gemini-3.7-flash-high` → `gemini-3.7-flash`), - // log and track the WIRE model so dashboards/telemetry reflect what - // actually shipped and Gemini rate-limit accounting uses the real id - // (the executor already built its URL from the same rewritten model). - const wireModel = - typeof res.model === "string" && res.model ? res.model : modelToCall; - if (wireModel !== modelToCall) { - log?.debug?.( - "PAYLOAD_RULES", - `Payload rules rewrote model for URL: requested=${modelToCall} wire=${wireModel}` - ); - } - - if ( - provider === "codex" && - attemptConnectionId && - !(await shouldIsolateProbeFailures()) - ) { - try { - const persistedQuota = await persistCodexChildQuotaResponse({ - connectionId: String(attemptConnectionId), - model: modelToCall || model || requestedModel || "", - headers: normalizeHeaders(res.response.headers), - status: res.response.status, - }); - if (persistedQuota) { - execCreds.providerSpecificData = persistedQuota.providerSpecificData; - if (persistedQuota.exhaustionLog) { - log?.debug?.("CODEX", persistedQuota.exhaustionLog); - } - } - if (res.response.status === 429) { - invalidateCodexQuotaCache(String(attemptConnectionId)); - } - } catch (err) { - const errMessage = err instanceof Error ? err.message : String(err); - log?.debug?.("CODEX", `Failed to persist codex quota state: ${errMessage}`); - } - } else if (attemptConnectionId && res.response.status === 429) { - // Dropped generic quota cache after 429 - invalidateGenericQuotaCacheOnStatus({ - provider, - connectionId: String(attemptConnectionId), - status: res.response.status, - isolateProbe: await shouldIsolateProbeFailures(), - }); - } - - // Track Gemini RPM + RPD request counts for 429 classification - if (provider === "gemini") { - incrementRequestCount(wireModel); - } - - updatePendingScope(pendingScope, { - stage: "provider_response_started", - }); - - if ( - stream && - (res.response.ok || - res.response.status === HTTP_STATUS.UNAUTHORIZED || - res.response.status === HTTP_STATUS.FORBIDDEN) && - executionConnectionId && - !(await shouldIsolateProbeFailures()) - ) { - const failureDetail = res.response.ok - ? "" - : await res.response - .clone() - .text() - .catch(() => ""); - recordKeyHealthStatus(res.response.status, execCreds, res.transport, failureDetail); - } - - if (isModelScope() && res.response.status === 429 && attempts < maxAttempts - 1) { - const bodyPeek = await res.response - .clone() - .text() - .catch(() => ""); - const normalizedHeaders = normalizeHeaders(res.response.headers); - const decision = classifyModelScope429(bodyPeek, normalizedHeaders); - if (decision.retryable) { - const delay = getModelScopeRetryDelayMs(normalizedHeaders, attempts); - log?.warn?.( - "MODELSCOPE_RETRY", - `429 ${decision.kind}; retrying in ${delay}ms (model remaining: ${decision.snapshot.modelRemaining ?? "unknown"})` - ); - releaseAccountSemaphore(); - await new Promise((r) => setTimeout(r, delay)); - attempts++; - continue; - } - } - - // For streaming: release the semaphore when the client drains or cancels the stream. - // Non-2xx streams must drop the slot before returning so the pipeline can rotate - // accounts without holding the failed connection's concurrency gate. Do NOT - // cancel() the body here — the pipeline clones it (BYOP 422 / toOutcome). - if (stream) { - const originalBody = res.response.body; - const okStatus = res.response.status >= 200 && res.response.status < 300; - if (!originalBody || !okStatus) { - releaseAccountSemaphore(); - return { - ...res, - _executionCredentials: execCreds, - }; - } - - // Opt-in transparent stream recovery (free-claude-code port, default OFF). - // Only engages for a successful (2xx) stream — an error body must never be - // held or replayed. Setting is read once here from the cached resolved - // resilience settings; the default path is byte-for-byte unchanged. - let streamRecoveryEnabled = false; - let continueMidStreamEnabled = false; - let throughputWatchdog = - resolveResilienceSettings(null).streamRecovery.throughputWatchdog; - if (okStatus) { - try { - // Reuse the request-consolidated settings read (see line ~2076) — no - // second DB/cache hit. Default OFF when the setting is absent. - const sr = resolveResilienceSettings(settings).streamRecovery; - // Fail-closed: the agent-goal-policy heuristic may only ADD recovery - // when the operator has no explicit configuration. If the operator - // explicitly configured stream recovery (env var or DB/settings - // override), that value always wins — the goal policy must never - // re-enable recovery the operator explicitly turned off. - const operatorExplicit = isStreamRecoveryExplicitlyConfigured(settings); - const goalOverride = !operatorExplicit && agentGoalPolicy.streamRecoveryEnabled; - streamRecoveryEnabled = sr.enabled || goalOverride; - continueMidStreamEnabled = sr.continueMidStream === true; - throughputWatchdog = sr.throughputWatchdog; - if (goalOverride && !sr.enabled) { - log?.info?.( - "AGENT_GOAL", - `agentGoalPolicy override: stream recovery enabled for goal request requestId=${traceId} model=${modelToCall || model || requestedModel || "unknown"}` - ); - } - } catch { - streamRecoveryEnabled = false; - continueMidStreamEnabled = false; - throughputWatchdog = - resolveResilienceSettings(null).streamRecovery.throughputWatchdog; - } - } - - let clientBody: ReadableStream; - if (streamRecoveryEnabled || throughputWatchdog.enabled) { - // Run the SAME upstream (same account/creds) with a given body and return - // its 2xx stream, or null. Used both by the early-retry re-open (same body) - // and the mid-stream continuation (assistant-prefilled body). - const runUpstreamStream = async ( - body: unknown - ): Promise | null> => { - try { - assertManagedLeaseFence(attemptConnectionId); - const retryRaw = await executeWithUpstreamStartTimeout({ - executor, - provider, - model: modelToCall, - connectionTimeoutMs: resolveConnectionTimeoutMs( - execCreds?.providerSpecificData - ), - signal: streamController.signal, - log, - execute: (signal) => - runWithCapture(providerRequestCapture, () => - executor.execute({ - model: modelToCall, - body, - stream: upstreamStream, - credentials: execCreds, - signal, - log, - extendedContext, - upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall), - clientHeaders: getExecutorClientHeaders(), - clientResponseFormat, - onCredentialsRefreshed, - skipUpstreamRetry, - contextEditing: { enabled: contextEditingEnabled }, - correlationId, - }) - ), - }); - const retryRes = normalizeExecutorResult(retryRaw); - const retryOk = - retryRes.response.status >= 200 && retryRes.response.status < 300; - if (retryOk && retryRes.response.body) { - return retryRes.response.body as ReadableStream; - } - await retryRes.response.body?.cancel().catch(() => {}); - return null; - } catch { - return null; - } - }; - - // Mid-stream continuation (Fase 4.4): re-request with the partial text as an - // assistant prefill. Gated by its own setting and only for OpenAI-compatible - // request bodies, chat or Responses (makeContinuationBody returns null otherwise). - const continueStream = continueMidStreamEnabled - ? (assistantSoFar: string) => { - const continuationBody = makeContinuationBody( - bodyToSend as Record, - assistantSoFar - ); - return continuationBody - ? runUpstreamStream(continuationBody) - : Promise.resolve(null); - } - : undefined; - - clientBody = createRecoverableStream( - originalBody as ReadableStream, - () => runUpstreamStream(bodyToSend), - { - finalize: releaseAccountSemaphore, - onRetry: (attempt, err) => - log?.warn?.( - "STREAM_RECOVERY", - formatStreamRecoveryRetryWarning( - attempt, - STREAM_RECOVERY.EARLY_RETRY_MAX, - err - ) - ), - continueStream, - ...buildContinuationLogHooks(log, correlationId), - throughputWatchdog, - onWatchdogAbort: () => - log?.warn?.( - "STREAM_WATCHDOG", - "active upstream stream stayed below the configured useful-output rate" - ), - } - ); - } else { - clientBody = wrapReadableStreamWithFinalize( - originalBody, - releaseAccountSemaphore - ); - } - - return { - ...res, - _executionCredentials: execCreds, - response: new Response(clientBody, { - status: res.response.status, - statusText: res.response.statusText, - headers: new Headers(normalizeHeaders(res.response.headers)), - }), - }; - } - - return { - ...res, - _executionCredentials: execCreds, - _accountSemaphoreRelease: releaseAccountSemaphore, - }; - } catch (error) { - releaseAccountSemaphore(); - throw error; - } - } - })(); - - if (stream) { - return rawResult; - } - - // Non-stream: release semaphore immediately after reading full response body. - const status = rawResult.response.status; - - releaseRawResultAccountSemaphore = - typeof rawResult._accountSemaphoreRelease === "function" - ? rawResult._accountSemaphoreRelease - : () => {}; - - const statusText = rawResult.response.statusText; - const headersObj = normalizeHeaders(rawResult.response.headers); - const responseHeaders = new Headers(headersObj); - stripStaleForwardingHeaders(responseHeaders); - stripNextMiddlewareControlHeaders(responseHeaders); - // The upstream headers (turn-state included) are about to be committed - // to the client — record which connection minted the blob so a later - // cross-account echo can be stripped (Codex failover guard). - if (provider === "codex" && readCodexTurnStateHeader(responseHeaders)) { - noteCodexTurnStateProvenance( - getCodexClientSessionId(clientRawRequest?.headers), - rawResult._executionCredentials?.connectionId ?? credentials?.connectionId - ); - } - const contentType = (responseHeaders.get("content-type") || "").toLowerCase(); - const payload = await readNonStreamingResponseBody( - rawResult.response, - contentType, - upstreamStream - ); - // Use the exact execution credential selected for this request. Model capability - // failures stay in routing telemetry; authoritative success only recovers this key. - if ( - rawResult._executionCredentials?.connectionId && - (rawResult._executionCredentials.apiKey || rawResult._executionCredentials.accessToken) - ) { - recordKeyHealthStatus( - status, - rawResult._executionCredentials, - rawResult.transport, - status >= 400 ? payload : "" - ); - } - releaseRawResultAccountSemaphore(); - releaseRawResultAccountSemaphore = () => {}; - - return { - ...rawResult, - response: new Response(payload, { status, statusText, headers: responseHeaders }), - _dedupSnapshot: { - status, - statusText, - headers: (() => { - const arr: [string, string][] = []; - responseHeaders.forEach((v, k) => arr.push([k, v])); - return arr; - })(), - payload, - }, - }; - } catch (error) { - releaseRawResultAccountSemaphore(); - throw error; - } - }; - - if (allowDedup && dedupEnabled && dedupHash) { - const dedupResult = await deduplicate(dedupHash, execute); - if (dedupResult.wasDeduplicated) { - log?.debug?.("DEDUP", `Joined in-flight request hash=${dedupHash}`); - } - return materializeDeduplicatedExecutionResult(dedupResult.result); - } - - return execute(); + const executeProviderRequestDeps = { + agentGoalPolicy, + assertManagedLeaseFence, + buildUpstreamHeadersForExecute, + clientRawRequest, + clientResponseFormat, + connectionId, + contextEditingEnabled, + correlationId, + credentials, + dedupEnabled, + dedupHash, + effectiveModel, + executor, + extendedContext, + getExecutionCredentials, + getExecutorClientHeaders, + isModelScope, + isOpencodeClient, + log, + model, + onCredentialsRefreshed, + pendingScope, + pendingConnId, + pendingRequestId, + provider, + providerRequestCapture, + rawBody: body, + recordKeyHealthStatus, + requestedModel, + resilienceSettings, + settings, + skipUpstreamRetry, + stream, + streamController, + targetFormat, + trace, + traceId, + translatedBody, + trustedEffortContext, + upstreamStream, + userAgent, + }; + // The pre-decomposition inline executeProviderRequest closed over the LIVE + // `translatedBody` binding: every mid-flight rebinding (pipeline wire update, + // tool-loop follow-up, thinking-signature recovery) was visible to the next + // dispatch. The deps object above only holds a snapshot, so the leg leaves + // must push each rebinding back through this setter to keep the wire send + // reading the current body. + const syncExecuteTranslatedBody = (next: unknown) => { + translatedBody = next as typeof translatedBody; + executeProviderRequestDeps.translatedBody = next as Record; }; + const executeProviderRequest = ( + modelToCall: string = effectiveModel, + allowDedup: boolean = false + ) => executeProviderRequestLeaf(executeProviderRequestDeps, modelToCall, allowDedup); const registeredProviderRequest = translatedBody && typeof translatedBody === "object" && !Array.isArray(translatedBody) @@ -3777,7 +3206,13 @@ async function handleChatCoreInner({ ), 3, log, - provider + provider, + { + ...(casConnectionId ? { connectionId: casConnectionId } : {}), + scope: resilienceSettings.tokenRefreshBreaker.scope, + failureThreshold: resilienceSettings.tokenRefreshBreaker.failureThreshold, + cooldownMs: resilienceSettings.tokenRefreshBreaker.cooldownMs, + } )) as null | Record; if (newCredentials?.accessToken || newCredentials?.copilotToken) { @@ -4076,7 +3511,7 @@ async function handleChatCoreInner({ errorConnectionId === "noauth" && isOpencodeFreeTierRefusalForProvider(provider, statusCode, message) ) { - noteOpencodeFreeTierSkip(provider); + noteOpencodeFreeTierSkip(provider, Date.now(), undefined, targetModel); } } else if (errorType === PROVIDER_ERROR_TYPES.GEO_BLOCKED) { // Google regional refusal: account-independent, non-terminal; park the connection @@ -4145,2290 +3580,293 @@ async function handleChatCoreInner({ let pipelineRecovered = false; if (stream) { - try { - const pipelineOutcome = await runProviderExecutionPipeline({ - policy: { - allowAccountRotation: !managedLease && comboStrategy !== "context-relay", - allowModelFallback: true, - expectedConnectionId: managedLease - ? String(getCurrentConnectionId() || connectionId || "") || undefined - : undefined, - }, - target: { - provider, - requestedModel: effectiveModel, - sourceFormat, - targetFormat, - stream, - }, - connection: { - initialConnectionId: String(getCurrentConnectionId() || connectionId || ""), - getCurrentConnectionId: () => getCurrentConnectionId() || undefined, - getCredentials: () => (credentials || {}) as Record, - replaceCredentials: (next) => { - Object.assign(credentials, next); - }, - onCredentialsRefreshed: handleCredentialsRefreshed, - refreshCredentials: executeRefreshCredentials, - assertManagedLeaseFence: (id) => { - assertManagedLeaseFence(id); - }, - getProviderCredentials, - }, - wire: { - body: translatedBody as Record, - currentModel, - triedModels, - setBodyAndModel: (body, model) => { - translatedBody = body as typeof translatedBody; - currentModel = model; - triedModels.add(model); - }, - }, - state: { - updatePendingStage: (stage, data) => { - updatePendingScope(pendingScope, { stage, ...(data || {}) }); - }, - recordRateLimitHeaders: updateFromHeaders, - recordRateLimitBody: updateFromResponseBody, - writeTerminalStatus, - persistConnectionPatch: updateProviderConnection, - setConnectionRateLimitedUntil: async (id, untilMs) => { - const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); - setConnectionRateLimitUntil(id, untilMs); - }, - lockModel, - recordAntigravityQuotaState: recordCoreOwnedAntigravityQuotaState, - markAccountSemaphoreBlocked: (key) => { - markAccountSemaphoreBlocked(key, Date.now() + 60_000); - }, - isolateProbeFailures: () => shouldIsolateProbeFailures(), - onCodexScopeRateLimited: async (params) => { - await markCodexScopeRateLimited({ - failedConnectionId: params.failedConnectionId, - model: params.model, - rateLimitedUntil: params.rateLimitedUntil, - credentials: (params.credentials || credentials) as { - connectionId?: string | null; - providerSpecificData?: unknown; - }, - }); - }, - onClearSessionAffinity: () => { - const key = - sessionAffinityKey || - extractSessionAffinityKey(body, clientRawRequest?.headers) || - null; - if (!key) return; - try { - deleteSessionAccountAffinity(key, "codex"); - } catch { - // best-effort - } - }, - onAuditAccountRotation: (params) => { - logAuditEvent({ - action: params.action, - actor: apiKeyInfo?.name || "system", - target: params.newConnectionId, - details: { - failed_connection_id: params.failedConnectionId, - new_connection_id: params.newConnectionId, - attempt: params.attempt, - retry_after_ms: params.retryAfterMs, - }, - }); - }, - }, - sendProviderAttempt: (modelToCall, allowDedup) => - executeProviderRequest(modelToCall, allowDedup), - }); - - pipelineRecovered = true; - currentModel = pipelineOutcome.model; - if (pipelineOutcome.kind === "error") { - providerResponse = pipelineOutcome.result.response; - providerUrl = ""; - providerHeaders = normalizeHeaders(pipelineOutcome.result.response.headers); - finalBody = translatedBody; - } else { - const result = { - response: pipelineOutcome.response, - url: pipelineOutcome.url, - headers: pipelineOutcome.headers, - transformedBody: pipelineOutcome.transformedBody, - }; - providerResponse = result.response; - providerUrl = result.url; - providerHeaders = result.headers; - finalBody = providerRequestCapture.body(result.transformedBody); - } - const responseConnectionId = getCurrentConnectionId(); - effectiveServiceTier = resolveEffectiveServiceTier(finalBody); - claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta( - targetFormat, - finalBody, - providerHeaders, - clientRawRequest?.headers - ); - - // Log target request (final request to provider) - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - // Update rate limiter from response headers (learn limits dynamically) - updateFromHeaders( - provider, - responseConnectionId, - providerResponse.headers, - providerResponse.status, - model - ); - - // Store rate-limit headers for quota saturation signals - try { - const { storeRateLimitHeaders } = await import("@/lib/quota/saturationSignals"); - storeRateLimitHeaders( - responseConnectionId, - provider, - providerResponse.headers as Record - ); - } catch { - // fail-open: saturation signal is best-effort - } - } catch (error) { - trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); - const errorMetadata = getSafeErrorMetadata(error); - const managedLeaseFenceCode = getManagedLeaseFenceErrorCode(errorMetadata.code); - if (managedLeaseFenceCode) return managedLeaseFenceErrorResult(managedLeaseFenceCode); - // isSemaphoreCapacityError already reads the code through getSafeErrorMetadata, - // so a hostile rejection cannot escape this classification. - if (isSemaphoreCapacityError(error)) { - const semaphoreCode = errorMetadata.code as string; - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${semaphoreCode}`, - }).catch(() => {}); - const failureMessage = sanitizeErrorMessage(errorMetadata.message) || "Semaphore timeout"; - persistAttemptLogs({ - status: HTTP_STATUS.RATE_LIMITED, - error: failureMessage, - providerRequest: finalBody || translatedBody, - clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage), - claudeCacheMeta: claudePromptCacheLogMeta, - cacheSource: "upstream", - }); - persistFailureUsage(HTTP_STATUS.RATE_LIMITED, semaphoreCode); - const result = stream - ? createStreamingErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage, semaphoreCode) - : createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage); - return { - ...result, - errorType: "account_semaphore_capacity", - errorCode: semaphoreCode, - }; - } - // abort(reason) can reject with a raw string lacking `name`/`status`; classify - // it through isLocalStreamLifecycleError so it maps to 499 rather than the - // 502 provider-failure default. - let isRequestAborted = errorMetadata.name === "AbortError"; - if (!isRequestAborted) { - try { - isRequestAborted = isLocalStreamLifecycleError(error); - } catch { - // A hostile Proxy must not escape the provider-error boundary during classification. - } - } - // #8376: proxyFetch tags unreachable transport failures so they remain - // distinguishable from ordinary provider 5xx responses. - const isProxyUnreachableFailure = - !isRequestAborted && errorMetadata.errorCode === "proxy_unreachable"; - const errorCode = errorMetadata.code; - const localRateLimitFailure = localLimiterErrors.getClientSafeLocalRateLimitError(error); - const failureStatus = isRequestAborted - ? 499 - : isProxyUnreachableFailure - ? HTTP_STATUS.BAD_GATEWAY - : localRateLimitFailure - ? localRateLimitFailure.status - : errorMetadata.name === "TimeoutError" || errorMetadata.name === "BodyTimeoutError" - ? HTTP_STATUS.GATEWAY_TIMEOUT - : errorMetadata.status - ? errorMetadata.status - : HTTP_STATUS.BAD_GATEWAY; - const failureMessage = isRequestAborted - ? "Request aborted" - : (() => { - try { - return formatProviderError( - localRateLimitFailure ?? error, - provider, - model, - failureStatus - ); - } catch { - // Formatting is diagnostic only; hostile rejection metadata falls back safely. - return errorMetadata.message || "Upstream provider error"; - } - })(); - const safeFailureMessage = sanitizeErrorMessage(failureMessage) || "Upstream provider error"; - const upstreamErrorCode = - localRateLimitFailure?.code ?? - (isProxyUnreachableFailure ? "proxy_unreachable" : errorCode); - // Tag our own deadline timeouts (fetch-start TimeoutError / body BodyTimeoutError, - // both surfaced as a 504) as "upstream_timeout" so the cooldown layer can tell a - // slow-but-not-failed request apart from a real provider 5xx. (Antigravity already - // tags its pre-response timeout via the code below.) - const isOwnDeadlineTimeout = - failureStatus === HTTP_STATUS.GATEWAY_TIMEOUT && - (errorMetadata.name === "TimeoutError" || errorMetadata.name === "BodyTimeoutError"); - const upstreamErrorType = - upstreamErrorCode === ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE || isOwnDeadlineTimeout - ? "upstream_timeout" - : failureStatus === 401 - ? "authentication_error" - : undefined; - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${failureStatus}`, - }).catch(() => {}); - persistAttemptLogs({ - status: failureStatus, - error: safeFailureMessage, - providerRequest: finalBody || translatedBody, - // On a client-abort (AbortError), the client already disconnected before - // we ever got here — this body is what we WOULD have sent, not what was - // actually delivered. Logging it as `clientResponse` is misleading (the - // dashboard reads that field as "what the client received"), so omit it - // for this case; `error` above already records the failure reason. - clientResponse: - errorMetadata.name === "AbortError" - ? undefined - : buildErrorBody(failureStatus, failureMessage), - claudeCacheMeta: claudePromptCacheLogMeta, - cacheSource: "upstream", - }); - if (isRequestAborted) { - streamController.handleError(createSafeAbortError()); - return createErrorResult(499, "Request aborted"); - } - const persistentErrorCode = projectFailureUsageErrorCode({ - statusCode: failureStatus, - message: failureMessage, - errorCode: projectPublicErrorIdentifier( - upstreamErrorCode || errorMetadata.name, - "upstream_error" - ), - errorType: upstreamErrorType, - }); - persistFailureUsage(failureStatus, persistentErrorCode); - console.log(`${COLORS.red}[ERROR] ${safeFailureMessage}${COLORS.reset}`); - if (stream && upstreamErrorCode) { - const result = createStreamingErrorResult( - failureStatus, - failureMessage, - upstreamErrorCode, - upstreamErrorType - ); - localLimiterErrors.markTrustedLocalRateLimitResponse(result.response, error); - return { - ...result, - errorType: upstreamErrorType, - errorCode: upstreamErrorCode, - }; - } - const result = createErrorResult( - failureStatus, - failureMessage, - null, - upstreamErrorCode, - upstreamErrorType - ); - localLimiterErrors.markTrustedLocalRateLimitResponse(result.response, error); - return result; - } - let upstreamErrorParsed = false; - let parsedStatusCode = providerResponse.status; - let parsedMessage = ""; - let parsedRetryAfterMs: number | null = null; - let upstreamErrorBody: unknown = null; - - // Track whether stream_options was present and stripped — if so, 401/403 after - // that may be from the modification rather than a genuine auth failure, so we - // skip the credential refresh attempt in that case. - const hadStreamOptions = - targetFormat === FORMATS.OPENAI_RESPONSES && "stream_options" in translatedBody; - if (hadStreamOptions) { - delete translatedBody.stream_options; + const streamingOutcome = await runStreamingResponse({ + apiKeyInfo, + persistAttemptLogs, + buildUpstreamHeadersForExecute, + resolveEffectiveServiceTier, + clientResponseFormat, + correlationId, + extendedContext, + executeProviderRequest, + applyProviderFailureClassification, + assertManagedLeaseFence, + body, + clientRawRequest, + comboStrategy, + connectionId, + contextEditingEnabled, + credentials, + effectiveModel, + executeRefreshCredentials, + executor, + getCurrentConnectionId, + getExecutionCredentials, + getExecutorClientHeaders, + getManagedLeaseFenceErrorCode, + handleCredentialsRefreshed, + isCombo, + isOpencodeClient, + log, + managedLease, + managedLeaseFenceErrorResult, + model, + onCredentialsRefreshed, + pendingScope, + pendingConnId, + pendingRequestId, + persistFailureUsage, + provider, + providerRequestCapture, + reqLogger, + resilienceSettings, + sessionAffinityKey, + skillRequestId, + sourceFormat, + stream, + streamController, + syncExecuteTranslatedBody, + targetFormat, + triedModels, + trustedEffortContext, + upstreamStream, + userAgent, + claudePromptCacheLogMeta, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + translatedBody, + }); + if (streamingOutcome) { + translatedBody = streamingOutcome.carry.translatedBody; + currentModel = streamingOutcome.carry.currentModel; + effectiveServiceTier = streamingOutcome.carry.effectiveServiceTier; + finalBody = streamingOutcome.carry.finalBody; + pipelineRecovered = streamingOutcome.carry.pipelineRecovered; + providerHeaders = streamingOutcome.carry.providerHeaders; + providerResponse = streamingOutcome.carry.providerResponse; + providerUrl = streamingOutcome.carry.providerUrl; } + if (streamingOutcome && streamingOutcome.response) return streamingOutcome.response; + } - // Handle 401/403 - try token refresh using executor - // T-PROBE: probe-origin failures never attempt the refresh — a probe must - // not consume a rotating refresh token nor persist an "expired" - // deactivation on refresh failure (#9817). The 401/403 then flows into - // the normal providerFailure classification (record-only in probe mode). - if ( - (providerResponse.status === HTTP_STATUS.UNAUTHORIZED || - providerResponse.status === HTTP_STATUS.FORBIDDEN) && - !hadStreamOptions && // Skip refresh if failure may be from stream_options removal, not auth - !(await shouldIsolateProbeFailures()) && - !(await shouldSkipCredentialRefresh(provider, providerResponse)) - ) { - // Fix A: wrap refreshCredentials in runWithOnPersist so the persist callback - // executes INSIDE the per-connection mutex held by getAccessToken. This makes - // [network refresh + DB write + outer-state mutation] one atomic step and - // prevents concurrent requests from reading a stale refreshToken before the - // DB has been updated (refresh_token_reused on Codex/OpenAI). - // - // Not every executor routes refresh through getAccessToken (e.g. github.ts - // calls refreshCopilotToken directly). When the persistFn doesn't fire from - // inside getAccessToken, we still need to do the credentials mutation + user - // callback after refreshCredentials returns. The `persistFnRan` flag tracks - // which path executed so we don't double-fire (race-prone) or skip (regression). - // Front 3: remember the refresh_token we are about to present so that, if the - // refresh fails as unrecoverable, we can tell a genuine death apart from a - // stale-token reuse that a concurrent/sibling refresh already rotated past. - const attemptedRefreshToken = - typeof credentials?.refreshToken === "string" ? credentials.refreshToken : null; - let persistFnRan = false; - const persistFn = onCredentialsRefreshed - ? async (refreshResult: Record) => { - persistFnRan = true; - // Mutate the shared credentials object so subsequent executor calls - // in this request see the new tokens. Runs INSIDE the mutex. - Object.assign(credentials, refreshResult); - await onCredentialsRefreshed(refreshResult); - } - : undefined; - - // #4038: build a compare-and-swap reread so getAccessToken can skip the persist if a - // concurrent writer (sibling request / HealthCheck / replica) already rotated this - // connection's refresh_token past the one we presented — overwriting would revert it - // and revoke the token family. No connectionId ⇒ no guard (behavior unchanged). - const casConnectionId = - typeof credentials?.connectionId === "string" ? credentials.connectionId.trim() : ""; - const casReread = casConnectionId - ? async () => { - const latest = await getProviderConnectionById(casConnectionId); - return typeof latest?.refreshToken === "string" ? latest.refreshToken : null; - } - : null; + // Non-streaming response + if (!stream) { + const nonStreamingOutcome = await runNonStreamingResponse({ + apiKeyInfo, + appendRequestLog, + applyProviderFailureClassification, + assertManagedLeaseFence, + attachCompressionUsageReceiptAfterAnalytics, + body, + bodyForCacheWrite, + buildCacheUsageLogMeta, + buildCostCtx, + buildErrorBody, + calculateCost, + calibrationEstimatedInputTokens, + claudePromptCacheLogMeta, + clientRawRequest, + clientRequestedResponsesStream, + clientResponseFormat, + comboStrategy, + compressionResponseMeta, + connectionId, + contextEditingEnabled, + copilotCompatibleReasoning, + createErrorResult, + credentials, + currentModel, + customToolNames, + describeMalformedNonStream, + detectMalformedNonStream, + echoModel, + echoModelInObject, + effectiveModel, + effectiveServiceTier, + emitRequestGamificationEvent, + endpointPath, + executeProviderRequest, + executeRefreshCredentials, + extractSessionAffinityKey, + extractUsageFromResponse, + fallbackAttempts, + finalBody, + finalizePendingScope, + getCurrentConnectionId, + getManagedLeaseFenceErrorCode, + getModelNormalizeToolCallId, + getModelPreserveOpenAIDeveloperRole, + getSafeErrorMetadata, + getSkillsModelIdForFormat, + guardrailRegistry, + handleCredentialsRefreshed, + handleToolCallExecution, + idempotencyKey, + incrementTokenUsage, + injectionResult, + isClaudeCodeCompatible, + isCombo, + isJsonRecord, + isLocalStreamLifecycleError, + isResponsesEndpoint, + isSemaphoreCapacityError, + isServerOwnedToolLoopEnabled, + log, + logAuditEvent, + managedLease, + managedLeaseFenceErrorResult, + markAccountSemaphoreBlocked, + memoryOwnerId, + memorySettings, + model, + normalizeHeaders, + normalizeUsage, + onRequestSuccess, + pendingConnId, + pendingRequestId, + pendingScope, + persistAttemptLogs, + persistFailureUsage, + pipelineRecovered, + pipelineSessionId, + preserveCacheControl, + provider, + providerHeaders, + providerRequestCapture, + providerResponse, + reasoningCacheScope, + reasoningReplayHistory, + recordChatCallCost, + recordContextEditingTelemetryHook, + recordCoreOwnedAntigravityQuotaState, + recordNonStreamingUsageStats, + reportMalformed200, + reqLogger, + requestToolIdentityMap, + resolveReportedServiceTier, + runMemoryExtractionGate, + runPluginOnResponseHook, + sanitizeErrorMessage, + sanitizeUpstreamDetails, + saveIdempotency, + scheduleQuotaShareConsumption, + semanticCacheEnabled, + sessionAffinityKey, + shouldIsolateProbeFailures, + skillRequestId, + sourceFormat, + startTime, + stream, + targetFormat, + toolNameMap, + traceEnabled, + traceId, + trackPendingRequest, + translateRequest, + translatedBody, + triedModels, + updateFromHeaders, + updateFromResponseBody, + updatePendingScope, + updateProviderConnection, + videoBridgeObserved, + webFetchFallbackPlan, + webSearchFallbackPlan, + syncExecuteTranslatedBody, + }); + if (nonStreamingOutcome) { + translatedBody = nonStreamingOutcome.carry.translatedBody; + currentModel = nonStreamingOutcome.carry.currentModel; + finalBody = nonStreamingOutcome.carry.finalBody; + providerResponse = nonStreamingOutcome.carry.providerResponse; + providerHeaders = nonStreamingOutcome.carry.providerHeaders; + effectiveServiceTier = nonStreamingOutcome.carry.effectiveServiceTier; + claudePromptCacheLogMeta = nonStreamingOutcome.carry.claudePromptCacheLogMeta; + reasoningReplayHistory = nonStreamingOutcome.carry.reasoningReplayHistory; + pipelineRecovered = nonStreamingOutcome.carry.pipelineRecovered; + return nonStreamingOutcome.response; + } + } - const newCredentials = (await refreshWithRetry( - () => - runWithCasGuard( - casReread ? { expectedRefreshToken: attemptedRefreshToken, reread: casReread } : null, - () => runWithOnPersist(persistFn, () => executor.refreshCredentials(credentials, log)) - ), - 3, - log, - provider // Explicitly pass the provider to avoid universally tripping the "unknown" circuit breaker - )) as null | { - accessToken?: string; - copilotToken?: string; - }; - - if (newCredentials?.accessToken || newCredentials?.copilotToken) { - log?.info?.("TOKEN", `${provider?.toUpperCase()} | refreshed`); - - // Fall back to post-mutex mutation only for executors that don't route - // through getAccessToken (and therefore never fire onPersist). For - // executors that DO route through it (Codex, Claude, Gemini, etc.) the - // mutation already happened atomically inside the mutex. - if (!persistFnRan) { - Object.assign(credentials, newCredentials); - if (onCredentialsRefreshed) { - await onCredentialsRefreshed(newCredentials); - } - } - - // Retry with new credentials — model + extra headers follow translatedBody.model so they - // stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback). - try { - const retryModelId = String(translatedBody.model || effectiveModel); - const retryBody = await prepareUpstreamBody({ - translatedBody, - modelToCall: retryModelId, - ...trustedEffortContext, - provider, - targetFormat, - credentials: getExecutionCredentials(), - log, - bypassDefaultToolLimit: isOpencodeClient, - isOpencodeClient, - rawBody: body, - clientRawRequest, - }); - assertManagedLeaseFence(getExecutionConnectionId(getExecutionCredentials())); - const retryResult = normalizeExecutorResult( - await runWithCapture(providerRequestCapture, () => - executor.execute({ - model: retryModelId, - body: retryBody, - stream: upstreamStream, - credentials: getExecutionCredentials(), - signal: streamController.signal, - log, - extendedContext, - upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId), - clientHeaders: getExecutorClientHeaders(), - clientResponseFormat, - onCredentialsRefreshed, - skipUpstreamRetry: isCombo, - contextEditing: { enabled: contextEditingEnabled }, - correlationId, - }) - ) - ); - - if (retryResult.response.ok) { - providerResponse = retryResult.response; - providerUrl = retryResult.url; - providerHeaders = new Headers(retryResult.headers || {}); - finalBody = providerRequestCapture.body(retryResult.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - upstreamErrorParsed = false; // Reset since new response is OK - } else { - providerResponse = retryResult.response; - upstreamErrorParsed = false; // Let it be parsed downstream - } - } catch (retryErr) { - const retryLeaseFenceCode = getManagedLeaseFenceErrorCode( - getUpstreamErrorIdentifier(retryErr) - ); - if (retryLeaseFenceCode) return managedLeaseFenceErrorResult(retryLeaseFenceCode); - // Refresh succeeded but the retry leg failed (network blip, AbortError, - // executor throw). Don't swallow — the operator-visible signal "the user - // saw 401 even though auth was actually fixed" is much more confusing - // than the original 401 alone. Surface at error level with sanitization. - log?.error?.( - "TOKEN", - `${provider?.toUpperCase()} | retry after refresh failed: ${sanitizeErrorMessage(retryErr)}` - ); - } - } else { - log?.warn?.("TOKEN", `${provider?.toUpperCase()} | refresh failed`); - if (isUnrecoverableRefreshError(newCredentials) && onCredentialsRefreshed) { - // Front 3 (reuse-race tolerance): before deactivating, re-read the DB. - // If a sibling/concurrent refresh already rotated this connection's - // refresh_token (common for Codex/OpenAI under one shared Auth0 client), - // the failure we saw was a stale-token reuse — the account is healthy - // with the newer token, so keep it active instead of killing it. - let alreadyRotated = false; - if (typeof connectionId === "string" && connectionId && attemptedRefreshToken) { - try { - const latest = await getProviderConnectionById(connectionId); - if (wasRefreshTokenRotated(attemptedRefreshToken, latest?.refreshToken)) { - alreadyRotated = true; - log?.warn?.( - "TOKEN", - `${provider.toUpperCase()} | refresh_token already rotated by a concurrent refresh — keeping connection active` - ); - } - } catch { - // DB read failed — fall through to the safe default (deactivate). - } - } - if (!alreadyRotated) { - await onCredentialsRefreshed({ testStatus: "expired", isActive: false }); - } - } - } - } - - // Check provider response - return error info for fallback handling - providerFailure: if (!providerResponse.ok) { - trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); - - let statusCode = providerResponse.status; - let message = ""; - let retryAfterMs: number | null = null; - let upstreamErrorCode: string | undefined; - let upstreamErrorType: string | undefined; - - if (upstreamErrorParsed) { - statusCode = parsedStatusCode; - message = parsedMessage; - retryAfterMs = parsedRetryAfterMs; - } else { - const details = await parseUpstreamError(providerResponse, provider); - statusCode = details.statusCode; - message = details.message; - retryAfterMs = details.retryAfterMs; - upstreamErrorBody = details.responseBody; - upstreamErrorCode = typeof details.errorCode === "string" ? details.errorCode : undefined; - upstreamErrorType = typeof details.errorType === "string" ? details.errorType : undefined; - } - - // Gateways like agentrouter misstate temporary quota exhaustion as 403/400, - // which downstream classification treats as AUTH_ERROR and clients like - // Claude Code treat as permanent. Restate to 429 (+ synthetic Retry-After) - // BEFORE any classification so both the fallback engine and the surfaced - // client status see a retryable error. Registry-scoped per provider. - const restatement = applyStatusRestatement({ - provider, - status: statusCode, - message, - body: upstreamErrorBody, - retryAfterMs, - }); - if (restatement.ruleId) { - statusCode = restatement.status; - retryAfterMs = restatement.retryAfterMs; - log?.info?.( - "STATUS_RESTATE", - `${provider} ${restatement.fromStatus}→${statusCode} (${restatement.ruleId})` - ); - } - - const signatureRecovery = pipelineRecovered - ? { attempted: false, succeeded: false, execution: null, error: null, recoveryBody: null } - : await recoverAnthropicThinkingSignature({ - provider, - statusCode, - message, - body: translatedBody, - execute: async (recoveryBody) => { - translatedBody = recoveryBody as typeof translatedBody; - return executeProviderRequest(currentModel, false); - }, - parseError: (response) => parseUpstreamError(response, provider), - }); - if (!pipelineRecovered && signatureRecovery.attempted && signatureRecovery.execution) { - providerResponse = signatureRecovery.execution.response; - if (signatureRecovery.succeeded) { - providerUrl = signatureRecovery.execution.url; - providerHeaders = signatureRecovery.execution.headers; - finalBody = providerRequestCapture.body(signatureRecovery.execution.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - log?.info?.( - "THINKING_SIGNATURE", - `Recovered ${provider}/${currentModel} after one historical-thinking retry` - ); - } else if (signatureRecovery.error) { - statusCode = signatureRecovery.error.statusCode; - message = signatureRecovery.error.message; - retryAfterMs = signatureRecovery.error.retryAfterMs; - upstreamErrorBody = signatureRecovery.error.responseBody; - upstreamErrorCode = - typeof signatureRecovery.error.errorCode === "string" - ? signatureRecovery.error.errorCode - : undefined; - upstreamErrorType = - typeof signatureRecovery.error.errorType === "string" - ? signatureRecovery.error.errorType - : undefined; - } - } - - if (signatureRecovery.succeeded) break providerFailure; - - // #10281 — tiny-budget reasoning probes (e.g. Claude Code's `/model` check - // sends `max_tokens: 1`): the model burns the whole budget on thinking, and - // some upstreams (e.g. api.cline.bot for deepseek-v4-flash) answer the empty - // outcome with a 5xx ("empty response content") instead of a truncated 200. - // Answer such probes with a valid truncated response rather than relaying the - // upstream failure — which would also mark the connection unavailable and - // poison fallback/cooldown bookkeeping for a request that is only a probe. - if ( - !stream && - isTinyBudgetReasoningProbe({ model: currentModel, body: finalBody || translatedBody }) && - isEmptyContentUpstreamFailure(statusCode, message) - ) { - providerResponse = buildReasoningProbeTruncatedResponse({ - model: currentModel, - maxTokens: toPositiveInteger( - (finalBody || translatedBody)?.max_tokens ?? - (finalBody || translatedBody)?.max_completion_tokens - ), - requestId: skillRequestId, - }); - log?.warn?.( - "PROBE", - `Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${message}"` - ); - break providerFailure; - } - - const errorConnectionId = getCurrentConnectionId() || connectionId; - await applyProviderFailureClassification({ - statusCode, - message, - headers: providerResponse.headers, - upstreamErrorBody, - retryAfterMs, - targetModel: currentModel, - }); - - appendRequestLog({ - model, - provider, - connectionId: errorConnectionId, - status: `FAILED ${statusCode}`, - }).catch(() => {}); - - const errMsg = formatProviderError(new Error(message), provider, model, statusCode); - const safeErrMsg = sanitizeErrorMessage(errMsg) || "Upstream provider error"; - const safeUpstreamErrorBody = sanitizeUpstreamDetails(upstreamErrorBody); - console.log(`${COLORS.red}[ERROR] ${safeErrMsg}${COLORS.reset}`); - - // Log Antigravity retry time if available - if (retryAfterMs && provider === "antigravity") { - const retrySeconds = Math.ceil(retryAfterMs / 1000); - log?.debug?.("RETRY", `Antigravity quota reset in ${retrySeconds}s (${retryAfterMs}ms)`); - } - - // Log error with full request body for debugging - reqLogger.logError(new Error(message), finalBody || translatedBody); - reqLogger.logProviderResponse( - providerResponse.status, - providerResponse.statusText, - providerResponse.headers, - safeUpstreamErrorBody - ); - - // ── T5: Intra-family model fallback ────────────────────────────────────── - // Before returning a model-unavailable error upstream, try sibling models - // from the same family. This keeps the request alive on the same account - // instead of failing the entire combo. - if (!pipelineRecovered && isModelUnavailableError(statusCode, message, provider)) { - const nextModel = getNextFamilyFallback(currentModel, triedModels, provider); - if (nextModel) { - triedModels.add(nextModel); - currentModel = nextModel; - translatedBody.model = nextModel; - log?.info?.( - "MODEL_FALLBACK", - `${model} unavailable (${statusCode}) → trying ${nextModel}` - ); - // Re-execute with the fallback model - try { - const fallbackResult = await executeProviderRequest(nextModel, false); - if (fallbackResult.response.ok) { - providerResponse = fallbackResult.response; - providerUrl = fallbackResult.url; - providerHeaders = fallbackResult.headers; - finalBody = providerRequestCapture.body(fallbackResult.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - // Continue processing with the fallback response — skip error return - log?.info?.("MODEL_FALLBACK", `Serving ${nextModel} as fallback for ${model}`); - // Jump to streaming/non-streaming handling below - // We fall through by NOT returning here - } else { - // Fallback also failed — return original error - persistAttemptLogs({ - status: statusCode, - error: safeErrMsg, - providerRequest: finalBody || translatedBody, - providerResponse: safeUpstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, "model_unavailable"); - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); - } - } catch { - persistAttemptLogs({ - status: statusCode, - error: safeErrMsg, - providerRequest: finalBody || translatedBody, - providerResponse: safeUpstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, "model_unavailable"); - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); - } - } else { - persistAttemptLogs({ - status: statusCode, - error: safeErrMsg, - providerRequest: finalBody || translatedBody, - providerResponse: safeUpstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, "model_unavailable"); - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); - } - } else if (isContextOverflowError(statusCode, message)) { - const familyCandidates = getModelFamily(currentModel, provider).filter( - (m) => m !== currentModel && !triedModels.has(m) - ); - const nextModel = - findLargerContextModel(currentModel, familyCandidates, provider) ?? - getNextFamilyFallback(currentModel, triedModels, provider); - if (nextModel) { - triedModels.add(nextModel); - currentModel = nextModel; - translatedBody.model = nextModel; - log?.info?.( - "CONTEXT_OVERFLOW_FALLBACK", - `${model} context overflow → trying ${nextModel}` - ); - try { - const fallbackResult = await executeProviderRequest(nextModel, false); - if (fallbackResult.response.ok) { - providerResponse = fallbackResult.response; - providerUrl = fallbackResult.url; - providerHeaders = fallbackResult.headers; - finalBody = providerRequestCapture.body(fallbackResult.transformedBody); - reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); - updatePendingScope(pendingScope, { - providerRequest: finalBody, - providerUrl, - stage: "provider_response_started", - }); - log?.info?.( - "CONTEXT_OVERFLOW_FALLBACK", - `Serving ${nextModel} as fallback for ${model}` - ); - } else { - persistAttemptLogs({ - status: statusCode, - error: safeErrMsg, - providerRequest: finalBody || translatedBody, - providerResponse: safeUpstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, "context_overflow"); - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); - } - } catch { - persistAttemptLogs({ - status: statusCode, - error: safeErrMsg, - providerRequest: finalBody || translatedBody, - providerResponse: safeUpstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, "context_overflow"); - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); - } - } else { - persistAttemptLogs({ - status: statusCode, - error: safeErrMsg, - providerRequest: finalBody || translatedBody, - providerResponse: safeUpstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, "context_overflow"); - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); - } - } else { - persistAttemptLogs({ - status: statusCode, - error: safeErrMsg, - providerRequest: finalBody || translatedBody, - providerResponse: safeUpstreamErrorBody, - clientResponse: buildErrorBody(statusCode, errMsg), - cacheSource: "upstream", - }); - persistFailureUsage(statusCode, `upstream_${statusCode}`); - - // Emergency budget fallback is orchestrated exclusively by the routing layer - // (src/sse/handlers/chat.ts), which resolves credentials FOR the emergency - // provider through account selection. The executor-level hop that used to - // live here re-sent the FAILING provider's credentials to the emergency - // provider's endpoint (e.g. the OpenAI API key to integrate.api.nvidia.com) - // — a cross-provider credential leak that also never succeeded upstream. - return createErrorResult( - statusCode, - errMsg, - retryAfterMs, - upstreamErrorCode, - upstreamErrorType, - upstreamErrorBody, - { passthrough: sourceFormat === FORMATS.CLAUDE } - ); - } - // ── End T5 ─────────────────────────────────────────────────────────────── - } - } - - // Non-streaming response - if (!stream) { - try { - const runNonStreamingPipeline = async ({ - policy, - model: pipelineModel, - translatedBody: wireBody, - }) => { - translatedBody = wireBody as typeof translatedBody; - currentModel = pipelineModel; - triedModels.add(pipelineModel); - return runProviderExecutionPipeline({ - policy, - target: { - provider, - requestedModel: pipelineModel, - sourceFormat, - targetFormat, - stream: false, - }, - connection: { - initialConnectionId: String(getCurrentConnectionId() || connectionId || ""), - getCurrentConnectionId: () => getCurrentConnectionId() || undefined, - getCredentials: () => (credentials || {}) as Record, - replaceCredentials: (next) => { - Object.assign(credentials, next); - }, - onCredentialsRefreshed: handleCredentialsRefreshed, - refreshCredentials: executeRefreshCredentials, - assertManagedLeaseFence: (id) => { - assertManagedLeaseFence(id); - }, - getProviderCredentials, - }, - wire: { - body: translatedBody as Record, - currentModel, - triedModels, - setBodyAndModel: (nextBody, nextModel) => { - translatedBody = nextBody as typeof translatedBody; - currentModel = nextModel; - triedModels.add(nextModel); - }, - }, - state: { - updatePendingStage: (stage, data) => { - updatePendingScope(pendingScope, { stage, ...(data || {}) }); - }, - recordRateLimitHeaders: updateFromHeaders, - recordRateLimitBody: updateFromResponseBody, - writeTerminalStatus, - persistConnectionPatch: updateProviderConnection, - setConnectionRateLimitedUntil: async (id, untilMs) => { - const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); - setConnectionRateLimitUntil(id, untilMs); - }, - lockModel, - recordAntigravityQuotaState: recordCoreOwnedAntigravityQuotaState, - markAccountSemaphoreBlocked: (key) => { - markAccountSemaphoreBlocked(key, Date.now() + 60_000); - }, - isolateProbeFailures: () => shouldIsolateProbeFailures(), - onCodexScopeRateLimited: async (params) => { - await markCodexScopeRateLimited({ - failedConnectionId: params.failedConnectionId, - model: params.model, - rateLimitedUntil: params.rateLimitedUntil, - credentials: (params.credentials || credentials) as { - connectionId?: string | null; - providerSpecificData?: unknown; - }, - }); - }, - onClearSessionAffinity: () => { - const key = - sessionAffinityKey || - extractSessionAffinityKey(body, clientRawRequest?.headers) || - null; - if (!key) return; - try { - deleteSessionAccountAffinity(key, "codex"); - } catch { - // best-effort - } - }, - onAuditAccountRotation: (params) => { - logAuditEvent({ - action: params.action, - actor: apiKeyInfo?.name || "system", - target: params.newConnectionId, - details: { - failed_connection_id: params.failedConnectionId, - new_connection_id: params.newConnectionId, - attempt: params.attempt, - retry_after_ms: params.retryAfterMs, - }, - }); - }, - }, - sendProviderAttempt: (modelToCall, allowDedup) => - executeProviderRequest(modelToCall, allowDedup), - }); - }; - - let toolLoopRan = false; - let toolLoopUsage = null; - let legResult = await runNonStreamingProviderLeg({ - phase: "initial", - sourceBody: (body || {}) as Record, - expectedConnectionId: managedLease - ? String(getCurrentConnectionId() || connectionId || "") || undefined - : undefined, - allowAccountRotation: !managedLease && comboStrategy !== "context-relay", - allowModelFallback: true, - executeProviderRequest: (modelToCall, allowDedup) => - executeProviderRequest(modelToCall, allowDedup), - runProviderExecution: runNonStreamingPipeline, - setRequestWireState: ({ translatedBody: nextBody, effectiveModel: nextModel }) => { - translatedBody = nextBody as typeof translatedBody; - currentModel = nextModel; - triedModels.add(nextModel); - }, - sourceFormat, - targetFormat, - clientResponseFormat, - provider, - model: effectiveModel, - connectionId: String(getCurrentConnectionId() || connectionId || ""), - getCurrentConnectionId: () => getCurrentConnectionId() || undefined, - effectiveModel: currentModel, - translatedBody: translatedBody as Record, - toolNameMap, - customToolNames, - requestToolIdentityMap, - reasoningCacheScope, - reasoningReplayHistory, - videoTranscriptSensitive: videoBridgeObserved, - clientHeaders: clientRawRequest?.headers ?? null, - isClaudeCodeCompatible, - log, - }); - - if (legResult.kind === "error") { - const err = legResult.result; - const errMessage = - err?.rawMessage || - (err?.originalError instanceof Error ? err.originalError.message : err?.error) || - ""; - const errHeaders = err?.upstreamHeaders || err?.response?.headers; - const errUpstreamBody = err?.upstreamErrorBody; - if (err) { - await applyProviderFailureClassification({ - statusCode: err.status, - message: errMessage, - headers: errHeaders, - upstreamErrorBody: errUpstreamBody, - retryAfterMs: err.retryAfterMs ?? null, - targetModel: currentModel, - }); - } - - const captured = providerRequestCapture.latest?.() ?? null; - finalBody = captured?.body ?? finalBody ?? translatedBody; - if (captured) { - reqLogger.logTargetRequest(captured.url, captured.headers, captured.body); - } - reqLogger.logError(new Error(err.error || "Provider request failed"), finalBody); - const isNetworkThrow = Boolean(err.originalError); - if (err.response && !isNetworkThrow) { - reqLogger.logProviderResponse( - err.status, - err.response.statusText || "Error", - err.response.headers, - err.response - ); - } - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${err.status}`, - }).catch(() => {}); - persistAttemptLogs({ - status: err.status, - error: err.error || "Provider request failed", - providerRequest: finalBody || translatedBody, - providerResponse: isNetworkThrow ? undefined : err.response, - // On a client abort the client already disconnected before we got here, so this - // body is what we WOULD have sent, not what was delivered. The dashboard reads - // `clientResponse` as "what the client received", so logging it misleads — - // `error` above already records the reason. The pre-#12867 path omitted it here; - // the leg-based path must keep doing so. - clientResponse: isLocalStreamLifecycleError(err.originalError) - ? undefined - : buildErrorBody(err.status, err.error || "Provider request failed"), - cacheSource: "upstream", - }); - persistFailureUsage(err.status, err.errorCode || `upstream_${err.status}`); - trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); - return err; - } - - pipelineRecovered = true; - const expectedConn = managedLease - ? String(getCurrentConnectionId() || connectionId || "") || undefined - : undefined; - // The identity is the tool loop's execution fence key, and deriveToolRequestIdentity - // canonicalizes the body — which by design rejects Dates, Maps and class instances. - // It was computed eagerly, so a body carrying any of those threw on EVERY - // non-streaming request even with SERVER_OWNED_TOOL_LOOP_ENABLED off (the default). - // Derive it only when the loop can run, and fail closed rather than crash: no - // identity means no fence, and without a fence the loop must not run. - let toolLoopEnabled = isServerOwnedToolLoopEnabled(); - let postInjectionRequestIdentity = ""; - if (toolLoopEnabled) { - try { - postInjectionRequestIdentity = derivePostInjectionRequestIdentity({ - apiKeyId: memoryOwnerId || "local", - headers: clientRawRequest?.headers ?? null, - skillRequestId, - postInjectionBody: (body || {}) as Record, - }); - } catch (identityError) { - log?.warn?.( - "SERVER_OWNED_TOOL_LOOP", - `request body is not canonicalizable, skipping the loop: ${ - identityError instanceof Error ? identityError.message : "unknown" - }` - ); - toolLoopEnabled = false; - } - } - const loopApply = await applyServerOwnedToolLoopIfNeeded({ - enabled: toolLoopEnabled, - stream, - isResponsesEndpoint, - sourceFormat, - initialLeg: legResult, - sourceBody: (body || {}) as Record, - skillsModelId: getSkillsModelIdForFormat(sourceFormat), - executionContext: { - apiKeyId: memoryOwnerId || "local", - sessionId: pipelineSessionId, - requestId: skillRequestId, - requestIdentity: postInjectionRequestIdentity, - builtinToolNames: injectionResult.builtinToolNames, - injectedCustomSkillNames: injectionResult.injectedCustomSkillNames, - customSkillExecutionEnabled: - Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true, - executionFenceEnabled: true, - provider, - model: effectiveModel, - }, - abortSignal: clientRawRequest?.signal, - expectedConnectionId: expectedConn, - followUpLeg: async (nextSourceBody) => { - translatedBody = translateRequest( - sourceFormat, - targetFormat, - model, - { ...nextSourceBody }, - false, - credentials, - provider, - reqLogger, - { - normalizeToolCallId: getModelNormalizeToolCallId( - provider || "", - model || "", - sourceFormat - ), - preserveDeveloperRole: getModelPreserveOpenAIDeveloperRole( - provider || "", - model || "", - sourceFormat - ), - preserveCacheControl, - signatureNamespace: connectionId, - copilotClient: copilotCompatibleReasoning, - reasoningCacheScope, - onReasoningReplayHistory: (messages) => { - reasoningReplayHistory = messages; - }, - } - ); - return runNonStreamingProviderLeg( - followUpLegInput( - { - executeProviderRequest: (modelToCall, allowDedup) => - executeProviderRequest(modelToCall, allowDedup), - runProviderExecution: runNonStreamingPipeline, - setRequestWireState: ({ translatedBody: nextBody, effectiveModel: nextModel }) => { - translatedBody = nextBody as typeof translatedBody; - currentModel = nextModel; - triedModels.add(nextModel); - }, - sourceFormat, - targetFormat, - clientResponseFormat, - provider, - model: effectiveModel, - connectionId: String(getCurrentConnectionId() || connectionId || ""), - getCurrentConnectionId: () => getCurrentConnectionId() || undefined, - effectiveModel: currentModel, - translatedBody: translatedBody as Record, - toolNameMap, - customToolNames, - requestToolIdentityMap, - reasoningCacheScope, - reasoningReplayHistory, - videoTranscriptSensitive: videoBridgeObserved, - clientHeaders: clientRawRequest?.headers ?? null, - isClaudeCodeCompatible, - log, - }, - nextSourceBody, - expectedConn - ) - ); - }, - logReceipt: (receipt) => reqLogger.logToolLoopReceipt(receipt), - }); - if (loopApply.kind === "error") { - return await finalizeToolLoopError({ - loop: loopApply.loop, - model, - provider, - connectionId: pendingConnId, - providerRequest: loopApply.loop.finalProviderRequest || finalBody || translatedBody, - persistFailureUsage, - persistAttemptLogs, - trackPendingRequest, - pendingRequestId, - }); - } - // `legResult` is declared as the full NonStreamingProviderLegResult union. The - // `kind === "error"` guard above narrows it to the ok variant, but the conditional - // reassignment below widens it back to the declared type, so every field read past - // this point lost the narrowing — 13 TS2339 diagnostics under - // tsconfig.typecheck-api.json, which pulls chatCore.ts in through the route while - // tsconfig.typecheck-core.json does not. Pin the ok variant in its own binding: - // `loopApply.leg` is already `NonStreamingProviderLegResult & { kind: "ok" }`, - // so no cast is involved. - let okLeg: NonStreamingProviderLegResult & { kind: "ok" } = legResult; - if (loopApply.kind === "ok") { - toolLoopRan = true; - toolLoopUsage = loopApply.usage; - okLeg = loopApply.leg; - } - - if (okLeg.upstreamResponse) { - providerResponse = okLeg.upstreamResponse; - providerHeaders = normalizeHeaders(okLeg.upstreamResponse.headers); - } else { - providerResponse = new Response(null, { - status: 200, - headers: okLeg.headers, - }); - providerHeaders = normalizeHeaders(okLeg.headers); - } - finalBody = providerRequestCapture.body(okLeg.providerRequest || translatedBody); - // Built inside executeProviderRequest on the pre-#12867 path. The leg now owns the - // first non-streaming send, so that assignment never runs here and the meta stayed - // null — `_omniroute.claudePromptCache` silently vanished from every call log on - // this path. Same inputs, same helper, at the point where they are available. - claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta( - targetFormat, - finalBody, - providerHeaders, - clientRawRequest?.headers - ); - const capturedOk = providerRequestCapture.latest?.(); - reqLogger.logTargetRequest( - okLeg.requestUrl || capturedOk?.url || "", - okLeg.requestHeaders || capturedOk?.headers || {}, - capturedOk?.body ?? finalBody - ); - const responseBody = okLeg.providerBody; - const responsePayloadFormat = okLeg.responsePayloadFormat; - const looksLikeSSE = okLeg.looksLikeSSE; - let translatedResponse = okLeg.response; - const memoryExtractionResponse = okLeg.responseForMemoryExtraction; - reqLogger.logProviderResponse( - 200, - "OK", - providerResponse.headers, - looksLikeSSE - ? { _streamed: true, _format: "sse-json", summary: responseBody } - : responseBody - ); - effectiveServiceTier = resolveReportedServiceTier(responseBody) ?? effectiveServiceTier; - if (onRequestSuccess) { - await onRequestSuccess(); - } - const successConnectionId = getCurrentConnectionId(); - await maybeSyncClaudeExtraUsageState({ - provider, - connectionId: successConnectionId, - providerSpecificData: credentials?.providerSpecificData, - log, - }); - const usage = toolLoopUsage ?? extractUsageFromResponse(responseBody, provider); - const cacheUsageLogMeta = buildCacheUsageLogMeta(usage); - if (usage && typeof usage === "object") { - attachCompressionUsageReceiptAfterAnalytics(usage as Record, "provider"); - if (provider === "gemini") { - const promptTokens = - typeof (usage as Record).prompt_tokens === "number" - ? ((usage as Record).prompt_tokens as number) - : 0; - if (promptTokens > 0) incrementTokenUsage(model, promptTokens); - } - } - recordContextEditingTelemetryHook({ - contextEditingEnabled, - provider, - responseBody, - skillRequestId, - log, - }); - appendRequestLog({ - model, - provider, - connectionId: successConnectionId, - tokens: usage, - status: "200 OK", - }).catch(() => {}); - recordNonStreamingUsageStats(usage, { - traceEnabled, - provider, - connectionId: successConnectionId, - model, - startTime, - apiKeyInfo, - effectiveServiceTier, - isCombo, - comboStrategy, - endpoint: endpointPath, cpaAuthIndex: readCpaAuthIndex(providerResponse), - }); - - // #12150 P1b surface 3 (fix round 1): a video-bridge-observed request's - // request- AND response-derived text both carry the full transcript (the - // flattened description on the request side, the model's own reply on - // the response side) — neither may populate durable Memory. See - // runMemoryExtractionGate for the shared gate + extraction wiring, unit - // tested directly in tests/unit/video-bridge-memory-suppression.test.ts. - runMemoryExtractionGate({ - memoryOwnerId, - memorySettings, - videoBridgeObserved, - pipelineSessionId, - requestBody: body as Record, - responseBody: memoryExtractionResponse as Record | null, - extractFacts, - log, - }); - - const customSkillExecutionEnabled = - Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true; - const builtinToolNames = [ - webSearchFallbackPlan.toolName, - webFetchFallbackPlan.toolName, - ...(memoryOwnerId && memorySettings?.enabled ? MEMORY_BUILTIN_TOOL_NAMES : []), - ].filter((name): name is string => Boolean(name)); - if (!toolLoopRan && (customSkillExecutionEnabled || builtinToolNames.length > 0)) { - const skillSessionId = pipelineSessionId; - - translatedResponse = await handleToolCallExecution( - translatedResponse, - getSkillsModelIdForFormat(sourceFormat), - { - apiKeyId: memoryOwnerId || "local", - sessionId: skillSessionId, - requestId: skillRequestId, - builtinToolNames, - customSkillExecutionEnabled, - provider, - model: effectiveModel, - } - ); - } - - const guardrailContext = buildPostCallGuardrailContext({ - apiKeyInfo, - body, - clientRawRequest, - log, - model, - provider, - responsePayloadFormat, - clientResponseFormat, - }); - const postCallGuardrails = await guardrailRegistry.runPostCallHooks( - translatedResponse, - guardrailContext - ); - translatedResponse = postCallGuardrails.response; - - const responseUsage = isJsonRecord(usage) - ? usage - : isJsonRecord(translatedResponse.usage) - ? translatedResponse.usage - : null; - const costUsage = normalizeUsage(responseUsage); - const estimatedCost = costUsage - ? await calculateCost(provider, model, costUsage, { serviceTier: effectiveServiceTier }) - : 0; - const chatCostCtx = buildCostCtx(provider, model, usage, effectiveServiceTier, traceId); - - if (postCallGuardrails.blocked) { - const guardrailMessage = postCallGuardrails.message || "Response blocked by guardrail"; - persistAttemptLogs({ - status: HTTP_STATUS.BAD_REQUEST, - tokens: usage, - responseBody, - providerRequest: finalBody || translatedBody, - providerResponse: looksLikeSSE - ? { - _streamed: true, - _format: "sse-json", - summary: responseBody, - } - : responseBody, - clientResponse: buildErrorBody(HTTP_STATUS.BAD_REQUEST, guardrailMessage), - claudeCacheMeta: claudePromptCacheLogMeta, - claudeCacheUsageMeta: cacheUsageLogMeta, - cacheSource: "upstream", - }); - recordChatCallCost(apiKeyInfo, meteredBudgetCost(provider, estimatedCost), chatCostCtx, false); - log?.warn?.( - "GUARDRAIL", - `Response blocked by ${postCallGuardrails.guardrail || "guardrail"}: ${guardrailMessage}` - ); - finalizePendingScope(pendingScope, { - providerResponse: responseBody, - clientResponse: translatedResponse, - }); - return createErrorResult(HTTP_STATUS.BAD_REQUEST, guardrailMessage); - } - - // Validate the *translated* response actually carries client-usable output. - // isEmptyContentResponse (above) runs on the raw responseBody before translation; - // this check runs after translation + sanitization + tool-call execution to catch - // cases where a provider returns a structurally valid raw body that translates into - // choices:[] or output:[] with no usable content (Responses API shape included). - const malformedTranslatedReason = detectMalformedNonStream(translatedResponse, provider); - if (malformedTranslatedReason) { - const totalLatency = Date.now() - startTime; - const rawBytes = (() => { - try { - return JSON.stringify(responseBody || {}).length; - } catch { - return -1; - } - })(); - reportMalformed200({ - mode: "nonstream", - provider, - model, - connectionId, - reason: malformedTranslatedReason, - recvBytes: rawBytes, - recvLines: -1, - emitted: -1, - events: {}, - ttftMs: totalLatency, - elapsedMs: totalLatency, - }); - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, - }).catch(() => {}); - const malformed = describeMalformedNonStream(translatedResponse, malformedTranslatedReason); - const malformedMessage = `[${provider}/${model}] ${malformed.message}`; - const malformedClientBody = buildErrorBody( - HTTP_STATUS.BAD_GATEWAY, - malformedMessage, - undefined, - { code: malformed.code, type: malformed.type } - ); - const sanitizedMalformedResponse = sanitizeUpstreamDetails(responseBody); - const sanitizedMalformedProviderResponse = looksLikeSSE - ? { _streamed: true, _format: "sse-json", summary: sanitizedMalformedResponse } - : sanitizedMalformedResponse; - persistAttemptLogs({ - status: HTTP_STATUS.BAD_GATEWAY, - tokens: usage, - responseBody: sanitizedMalformedResponse, - providerRequest: finalBody || translatedBody, - providerResponse: sanitizedMalformedProviderResponse, - clientResponse: malformedClientBody, - claudeCacheMeta: claudePromptCacheLogMeta, - claudeCacheUsageMeta: cacheUsageLogMeta, - cacheSource: "upstream", - }); - persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "malformed_translated_response"); - trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); - // Routing event (feedback foundation) — record the malformed outcome so - // the quality tracker de-prioritizes this model over time. - void emitRoutingEvent( - createRoutingEvent({ - requestId: traceId || pendingRequestId || "unknown", - provider: provider || "unknown", - model: model || "unknown", - strategy: isCombo ? (comboStrategy ?? "combo") : "direct", - latencyMs: Date.now() - startTime, - ttftMs: null, - inputTokens: null, - outputTokens: null, - cost: null, - retries: 0, - fallbackUsed: false, // combo-level fallback tracked by decisionTrace - outcome: "malformed", - status: HTTP_STATUS.BAD_GATEWAY, - finishReason: routingFinishReason(translatedResponse), - connectionId: credentials?.connectionId ?? null, - }) - ); - return createErrorResult( - HTTP_STATUS.BAD_GATEWAY, - malformedMessage, - null, - malformed.code, - malformed.type - ); - } - - // ── Phase 9.1: Cache store (non-streaming, temp=0) ── - storeSemanticCacheResponse({ - enabled: semanticCacheEnabled, - body: bodyForCacheWrite, - headers: clientRawRequest?.headers, - translatedResponse, - model, - // The dual-layer manager scopes entries per provider (cacheByProvider); - // lookup passes the resolved provider, so the write must too (#14159). - provider, - apiKeyId: apiKeyInfo?.id ?? undefined, - usage, - log, - videoTranscriptSensitive: videoBridgeObserved, - }); - - // ── Phase 9.2: Save for idempotency ── - // Reuse the key resolved by checkIdempotencyCache() above (single derivation per - // request). (#3821-review LEDGER-6) - saveIdempotency(idempotencyKey, translatedResponse, 200); - reqLogger.logConvertedResponse(translatedResponse); - persistAttemptLogs({ - status: 200, - tokens: usage, - responseBody, - providerRequest: finalBody || translatedBody, - providerResponse: looksLikeSSE - ? { - _streamed: true, - _format: "sse-json", - summary: responseBody, - } - : responseBody, - clientResponse: translatedResponse, - claudeCacheMeta: claudePromptCacheLogMeta, - claudeCacheUsageMeta: cacheUsageLogMeta, - cacheSource: "upstream", - }); - recordChatCallCost(apiKeyInfo, meteredBudgetCost(provider, estimatedCost), chatCostCtx, true); - - // === Quota Share POST-hook (B/F7) — fire-and-forget, fail-open === - await scheduleQuotaShareConsumption({ - apiKeyId: apiKeyInfo?.id, - connectionId: credentials?.connectionId, - provider, - model, - usage, - estimatedCost, - log, - }); - // === /Quota Share POST-hook === - - // ── Gamification event (fire-and-forget) ── - await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider }); - - finalizePendingScope(pendingScope, { - providerResponse: responseBody, - clientResponse: translatedResponse, - }); - const responseHeaders = buildNonStreamingResponseHeaders({ - provider, - model, - startTime, - responseUsage, - estimatedCost, - requestId: skillRequestId, - compressionResponseMeta, - comboStrategy, - fallbackAttempts, - }); - // #6426: align response body `model` with the `X-OmniRoute-Model` header - // (both must be the resolved backend model). Some upstreams (notably legacy - // /v1/completions text-completion path) return a body `model` field that - // differs from the resolved backend id we advertised in the header, leaving - // strict clients unable to reconcile the two. Rewrite body.model to `model` - // FIRST, then let #1311 echo override it when the opt-in setting is on. - if (typeof model === "string" && model) echoModelInObject(translatedResponse, model); - // #1311: echo the requested alias/combo name in the non-streaming response model. - if (echoModel) echoModelInObject(translatedResponse, echoModel); - - // ── Plugin onResponse hook (fire-and-forget) ── - // #8395: the streaming branch below already calls this; the non-streaming - // (stream:false) branch returned without it, so onResponse never fired for - // non-streaming requests at all. - await runPluginOnResponseHook({ - requestId: traceId, - body, - model, - provider, - apiKeyInfo, - headers: clientRawRequest?.headers, - response: { status: 200, data: translatedResponse }, - }); - - // Routing event (feedback foundation) — fire-and-forget, cheap. - void emitRoutingEvent( - createRoutingEvent({ - requestId: traceId || pendingRequestId || "unknown", - provider: provider || "unknown", - model: model || "unknown", - strategy: isCombo ? (comboStrategy ?? "combo") : "direct", - latencyMs: Date.now() - startTime, - ttftMs: null, - inputTokens: - usage && typeof usage === "object" - ? (() => { - const promptTokens = (usage as Record).prompt_tokens; - return typeof promptTokens === "number" && Number.isFinite(promptTokens) - ? promptTokens - : null; - })() - : null, - outputTokens: - usage && typeof usage === "object" - ? (() => { - const completionTokens = (usage as Record).completion_tokens; - return typeof completionTokens === "number" && Number.isFinite(completionTokens) - ? completionTokens - : null; - })() - : null, - cost: Number.isFinite(estimatedCost) ? estimatedCost : null, - retries: 0, - fallbackUsed: false, // combo-level fallback tracked by decisionTrace - outcome: "success", - status: 200, - finishReason: routingFinishReason(translatedResponse), - connectionId: credentials?.connectionId ?? null, - }) - ); - - return { - success: true, - response: maybeWrapForcedNonStreamingResponsesJson({ - clientRequestedResponsesStream, - body: translatedResponse, - headers: responseHeaders, - }), - }; - } catch (error) { - trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); - const errorMetadata = getSafeErrorMetadata(error); - const managedLeaseFenceCode = getManagedLeaseFenceErrorCode(errorMetadata.code); - if (managedLeaseFenceCode) return managedLeaseFenceErrorResult(managedLeaseFenceCode); - // isSemaphoreCapacityError already reads the code through getSafeErrorMetadata, - // so a hostile rejection cannot escape this classification. - if (isSemaphoreCapacityError(error)) { - const semaphoreCode = errorMetadata.code as string; - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${semaphoreCode}`, - }).catch(() => {}); - const failureMessage = sanitizeErrorMessage(errorMetadata.message) || "Semaphore timeout"; - persistAttemptLogs({ - status: HTTP_STATUS.RATE_LIMITED, - error: failureMessage, - providerRequest: finalBody || translatedBody, - clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage), - claudeCacheMeta: claudePromptCacheLogMeta, - cacheSource: "upstream", - }); - persistFailureUsage(HTTP_STATUS.RATE_LIMITED, semaphoreCode); - const result = createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage); - return { - ...result, - errorType: "account_semaphore_capacity", - errorCode: semaphoreCode, - }; - } - throw error; - } - } - - // Streaming response - // #3089 — some "reasoning" openai-compatible upstreams ignore a stream:true - // request and return a complete application/json chat-completion body instead - // of an SSE stream. The readiness check below only recognizes SSE `data:` - // frames, so that body produced a spurious STREAM_EARLY_EOF / HTTP 502 even - // though it carried valid content/reasoning_content. Detect a JSON (non-SSE) - // upstream body and synthesize an equivalent OpenAI SSE stream so the - // streaming pipeline (and the client) get a valid stream. - providerResponse = await maybeConvertJsonBodyToSse(providerResponse, { log, provider, model }); - const streamReadinessPolicy = resolveStreamReadinessTimeout({ - baseTimeoutMs: STREAM_READINESS_TIMEOUT_MS, - provider, - model, - body: (finalBody || translatedBody) as Record | null | undefined, - sourceBody: body as Record | null | undefined, - maxTimeoutMs: agentGoalPolicy.detected - ? Math.max(STREAM_READINESS_MAX_TIMEOUT_MS, agentGoalPolicy.readinessMaxTimeoutMs) - : STREAM_READINESS_MAX_TIMEOUT_MS, - cascadeTimeoutMs: getExecutorTimeoutMs( - executor, - provider, - model, - resolveConnectionTimeoutMs(credentials?.providerSpecificData) - ), - }); - if (streamReadinessPolicy.timeoutMs !== streamReadinessPolicy.baseTimeoutMs) { - log?.debug?.( - "STREAM", - `adaptive readiness timeout=${streamReadinessPolicy.timeoutMs}ms base=${streamReadinessPolicy.baseTimeoutMs}ms reason=${streamReadinessPolicy.reasons.join(",")}` - ); - } - - let streamReadiness = await ensureStreamReadiness(providerResponse, { - timeoutMs: streamReadinessPolicy.timeoutMs, - maxTimeoutMs: streamReadinessPolicy.maxTimeoutMs, - provider, - model, - log, - }); - // A stall is an upstream issue, not an account fault — the executor loop - // already ended at headers, so this bounded retry is the only recovery left. - const fallback = await maybeFallbackAfterReadiness({ - streamReadiness, - clientAborted: streamController.signal.aborted, - failedConnectionId: getCurrentConnectionId(), - failedBody: providerResponse, + const streamingTailOutcome = await runStreamingTail({ + agentGoalPolicy, + modelInfo, + forcedConnectionId, + apiKeyInfo, + attachCompressionUsageReceiptAfterAnalytics, + body, + bodyForCacheWrite, + calibrationEstimatedInputTokens, + claudePromptCacheLogMeta, + clientRawRequest, + clientResponseFormat, + comboStrategy, + compressionResponseMeta, + connectionId, + contextEditingEnabled, + copilotCompatibleReasoning, + correlationId, + createPiiTransform, + credentials, currentModel, - streamReadinessPolicy, - provider, - model, - log, - reqLogger, - providerUrl, - providerHeaders, - finalBody, - translatedBody, + customToolNames, + echoModel, + effectiveModel, + effectiveServiceTier, + endpointPath, executeProviderRequest, - providerRequestCapture, - }); - streamReadiness = fallback.readiness; - providerResponse = fallback.providerResponse; - finalBody = fallback.finalBody; - if (streamReadiness.ok === false) { - const { response: failureResponse, reason } = streamReadiness; - const { classificationReason, upstreamDiagnostic } = streamReadiness; - trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); - appendRequestLog({ - model, - provider, - connectionId, - status: `FAILED ${failureResponse.status}`, - }).catch(() => {}); - persistAttemptLogs({ - status: failureResponse.status, - error: reason, - providerRequest: finalBody || translatedBody, - clientResponse: buildErrorBody( - failureResponse.status, - classificationReason, - upstreamDiagnostic ? { error: { message: upstreamDiagnostic } } : undefined - ), - claudeCacheMeta: claudePromptCacheLogMeta, - cacheSource: "upstream", - }); - persistFailureUsage(failureResponse.status, streamReadiness.code); - // Do NOT call onStreamFailure — a stream stall is an upstream issue, - // not an account/quota failure. Marking the account unavailable here - // would lock out legitimate accounts when the upstream hangs. - return { - success: false, - status: failureResponse.status, - error: reason, - classificationError: classificationReason, - errorType: streamReadiness.type, - errorCode: streamReadiness.code, - response: failureResponse, - }; - } - providerResponse = streamReadiness.response; - - // Flush-empty retry (opt-in `FLUSH_EMPTY_RETRY_ENABLED`, default off): when the - // upstream turn carries no usable content (reasoning-only 200, or a - // zero-valuable-chunk turn that the empty-stream guard would turn into a 502), - // issue bounded retries through the normal credential path BEFORE anything is - // exposed to the client — in particular before `onRequestSuccess` below. - // Empty turns are stochastic upstream misses, not account faults, so no - // cooldown: the retry prefers another allowed connection, a single slot - // replays itself, and a leased or pinned connection never rotates (#14715). - // Budget: `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX` retries, then fall - // back to the current behavior. Translate-path streams only (mirror of the - // empty-stream guard); flag off = byte-for-byte unchanged. Bounded reader - // (abandon past the cap, never a full `text()` read); the original - // reconstructed response is piped, only the bounded copy is classified. - // Known TTFT cost when armed: a small valid turn under the cap is fully - // buffered before the first client byte (flag off by default, so the - // streaming path is untouched unless opted in). - if (stream && providerResponse.ok && providerResponse.body) { - let flushEmptyRetryArmed = false; - try { - flushEmptyRetryArmed = isFeatureFlagEnabled("FLUSH_EMPTY_RETRY_ENABLED"); - } catch { - flushEmptyRetryArmed = false; - } - const isTranslatePath = - targetFormat === FORMATS.OPENAI_RESPONSES || - needsTranslation(targetFormat, clientResponseFormat); - if (flushEmptyRetryArmed && isTranslatePath) { - const retried = await runEmptyTurnRetryLoop({ - providerResponse, - credentials, - provider, - currentModel, - model, - targetFormat, - clientResponseFormat, - isAborted: () => clientRawRequest?.signal?.aborted === true, - timeoutMs: streamReadinessPolicy.timeoutMs, - maxTimeoutMs: streamReadinessPolicy.maxTimeoutMs, - maxRetries: STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX, - translatedBody, - finalBody, - providerUrl, - providerHeaders, - correlationId, - traceId, - log, - getProviderCredentials, - routing: { leased: Boolean(managedLease), forcedConnectionId, apiKey: apiKeyInfo }, - executeProviderRequest, - logTargetRequest: (url, headers, body) => reqLogger.logTargetRequest(url, headers, body), - captureBody: (body) => providerRequestCapture.body(body), - }); - providerResponse = retried.providerResponse; - if (retried.adopted) finalBody = retried.finalBody; - } - } - - // Notify success - caller can clear error status if needed - if (onRequestSuccess) { - await onRequestSuccess(); - } - - const responseHeaders = assembleStreamingResponseHeaders({ - providerHeaders: providerResponse.headers, - provider, + executor, + fallbackAttempts, + finalBody, + getCurrentConnectionId, + isCombo, + isDroidCLI, + isResponsesEndpoint, + log, + managedLease, + memoryOwnerId, + memorySettings, model, + onRequestSuccess, + onStreamFailure, + pendingConnId, pendingRequestId, - compressionResponseMeta, - comboStrategy, - fallbackAttempts, - isCombo, // #14116: foreign-account quota-header strip (only meaningful when true) - requestedConnectionId: forcedConnectionId || null, - selectedConnectionId: credentials?.connectionId ?? null, - }); - - // The streaming headers (turn-state included, when present) are committed to - // the client from here on — record which connection minted the blob so a - // later cross-account echo can be stripped (Codex failover guard). The - // in-place failover update means `credentials` is the winning account. - if (provider === "codex" && readCodexTurnStateHeader(providerResponse.headers)) { - noteCodexTurnStateProvenance( - getCodexClientSessionId(clientRawRequest?.headers), - credentials?.connectionId - ); - } - - // Create transform stream with logger for streaming response - let transformStream; - const responseToolNameMap = mergeResponseToolNameMap( - toolNameMap, - (finalBody as Record | null | undefined) ?? null - ); - - let streamCompletionRecorded = false; - let streamFailureCompletionRecorded = false; - - // Callback to save call log when stream completes (include responseBody when provided by stream) - let streamTimingOriginOffsetMs: number | null = null; // startTime → StreamTiming start - const onStreamComplete = ({ - status: streamStatus, - usage: streamUsage, - responseBody: streamResponseBody, - providerPayload, - clientPayload, - reasoningMeta: streamReasoningMeta, - error: streamError, - errorCode: streamErrorCode, - firstOutputMs, - itlMs: streamItlMs, - interrupted: _streamInterrupted, - }) => { - const ttft = requestTtftMs(streamTimingOriginOffsetMs, firstOutputMs); - const normalizedStreamStatus = streamStatus || 200; - if (streamCompletionRecorded) return; - streamCompletionRecorded = true; - if (normalizedStreamStatus !== 200) { - if (streamFailureCompletionRecorded) return; - streamFailureCompletionRecorded = true; - } - const cacheUsageLogMeta = buildCacheUsageLogMeta(streamUsage); - const streamConnectionId = getCurrentConnectionId(); - - if (normalizedStreamStatus === 200) { - clearPostOutputFailureStreak(provider, streamConnectionId, modelInfo.model); - void maybeSyncClaudeExtraUsageState({ - provider, - connectionId: streamConnectionId, - providerSpecificData: credentials?.providerSpecificData, - log, - }); - } - - if (normalizedStreamStatus === 200 && streamResponseBody) { - captureStreamReasoningForReplay({ - streamResponseBody, - clientResponseFormat, - responseToolNameMap, - providerRequestBody: finalBody || translatedBody || body, - translatedBody, - reasoningReplayHistory, - provider, - model, - reasoningCacheScope, - videoTranscriptSensitive: videoBridgeObserved, - }); - } - effectiveServiceTier = resolveReportedServiceTier(streamResponseBody) ?? effectiveServiceTier; - - // Context Editing telemetry (streaming): the reconstructed stream body now carries - // context_management.applied_edits from the final message_delta snapshot. Mirror the - // non-streaming hook so streaming context-clear savings also surface under engine - // "context-editing" in compression analytics. Best-effort, Claude-only. - if (normalizedStreamStatus === 200) { - recordContextEditingTelemetryHook({ - contextEditingEnabled, - provider, - responseBody: streamResponseBody, - skillRequestId, - log, - }); - } - - streamFailure.finalizeStreamRequestLog({ - pendingRequestId, - model, - provider, - connectionId: streamConnectionId, - providerResponse: providerPayload ?? streamResponseBody ?? undefined, - clientResponse: clientPayload ?? streamResponseBody ?? undefined, - status: normalizedStreamStatus, - error: streamError, - errorCode: streamErrorCode, - }); - - // Track cache token metrics for streaming responses - if (streamUsage && typeof streamUsage === "object") { - attachCompressionUsageReceiptAfterAnalytics(streamUsage as Record, "stream"); - // Track Gemini token consumption for TPM rate-limit pre-check - if (provider === "gemini") { - const promptTokens = - typeof (streamUsage as Record).prompt_tokens === "number" - ? ((streamUsage as Record).prompt_tokens as number) - : 0; - if (promptTokens > 0) incrementTokenUsage(model, promptTokens); - } - } - recordStreamingUsageStats(streamUsage, { - provider, - model, - streamStatus: normalizedStreamStatus, - startTime, - ttft, - streamErrorCode, - connectionId: streamConnectionId, - apiKeyInfo, - effectiveServiceTier, - isCombo, - comboStrategy, - endpoint: endpointPath, cpaAuthIndex: readCpaAuthIndex(providerResponse), - }); - - // Routing event (feedback foundation) — fire-and-forget, cheap, never blocks - // the stream. Feeds the quality tracker + optional OTel exporter. - void emitRoutingEvent( - createRoutingEvent({ - requestId: traceId || pendingRequestId || "unknown", - provider: provider || "unknown", - model: model || "unknown", - strategy: isCombo ? (comboStrategy ?? "combo") : "direct", - latencyMs: Date.now() - startTime, - ttftMs: typeof ttft === "number" && Number.isFinite(ttft) && ttft >= 0 ? ttft : null, - itlMs: - typeof streamItlMs === "number" && Number.isFinite(streamItlMs) && streamItlMs >= 0 - ? streamItlMs - : null, - inputTokens: - streamUsage && typeof streamUsage === "object" - ? (() => { - const promptTokens = (streamUsage as Record).prompt_tokens; - return typeof promptTokens === "number" && Number.isFinite(promptTokens) - ? promptTokens - : null; - })() - : null, - outputTokens: - streamUsage && typeof streamUsage === "object" - ? (() => { - const completionTokens = (streamUsage as Record).completion_tokens; - return typeof completionTokens === "number" && Number.isFinite(completionTokens) - ? completionTokens - : null; - })() - : null, - cost: null, - retries: 0, - fallbackUsed: false, // combo-level fallback tracked by decisionTrace - outcome: - normalizedStreamStatus === 200 - ? "success" - : streamErrorCode === "stream_interrupted" || streamErrorCode === "aborted" - ? "stream_interrupted" - : outcomeFromStatus(normalizedStreamStatus), - status: normalizedStreamStatus, - finishReason: routingFinishReason(streamResponseBody), - connectionId: streamConnectionId ?? credentials?.connectionId ?? null, - }) - ); - - persistAttemptLogs({ - status: normalizedStreamStatus, - error: streamError || undefined, - tokens: streamUsage || {}, - responseBody: streamResponseBody ?? undefined, - providerRequest: finalBody || translatedBody, - providerResponse: providerPayload, - clientResponse: clientPayload ?? streamResponseBody ?? undefined, - claudeCacheMeta: claudePromptCacheLogMeta, - claudeCacheUsageMeta: cacheUsageLogMeta, - cacheSource: "upstream", - // #13130: persist TTFT so call_logs.ttft_ms lets the dashboard compute - // generation-time TPS instead of wall-clock TPS. - ttft, - reasoningMeta: streamReasoningMeta ?? null, - }); - - recordStreamingCost({ - apiKeyId: apiKeyInfo?.id, - provider, - model, - streamUsage, - serviceTier: effectiveServiceTier, - calculateCost, - // Only the budget-consumable share may draw down the allowance. - recordCost: (apiKeyId, cost, details) => { - const budgetCost = meteredBudgetCost(provider, cost); - if (budgetCost > 0) recordCost(apiKeyId, budgetCost, details); - }, - ledger: buildStreamLedgerDetails(effectiveServiceTier, normalizedStreamStatus < 400, traceId), - }); - - // === Quota Share POST-hook streaming (B/F7) — fire-and-forget, fail-open === - // Resolve the real per-request cost (calculateCost) so USD-unit pools accrue - // on streaming traffic too; this previously recorded usd:0 hardcoded, which - // meant DeepSeek-style `usd/monthly` shared pools never blocked on streams. - scheduleStreamingQuotaShareConsumption({ - apiKeyId: apiKeyInfo?.id, - connectionId: credentials?.connectionId, - provider, - model, - streamUsage, - streamStatus: normalizedStreamStatus, - serviceTier: effectiveServiceTier, - calculateCost, - log, - }); - // === /Quota Share POST-hook streaming === - - if (streamStatus === 200) { - // #12150 P1b surface 3 (fix round 1): see the matching non-streaming - // gate above — an observed request populates NO durable memory from - // either the request-derived text or this streamed response. - runMemoryExtractionGate({ - memoryOwnerId, - memorySettings, - videoBridgeObserved, - pipelineSessionId, - requestBody: body as Record, - responseBody: (streamResponseBody ?? null) as Record | null, - extractFacts, - log, - }); - } - - // Semantic cache: store assembled streaming response for future cache hits - storeStreamingSemanticCacheResponse({ - enabled: semanticCacheEnabled, - streamStatus, - streamResponseBody, - body: bodyForCacheWrite, - headers: clientRawRequest?.headers, - model, - provider, - apiKeyId: apiKeyInfo?.id ?? undefined, - streamUsage, - log, - videoTranscriptSensitive: videoBridgeObserved, - }); - - // Plugin onStreamComplete hook — fire-and-forget, fail-open (#9571) - // Pass traceId as requestId so plugins can correlate the stream-completion event - // with the originating request (the same id used for onRequest/onResponse). (#11825) - runPluginOnStreamCompleteHook({ - status: normalizedStreamStatus, - usage: streamUsage as Record | undefined, - ttft, - model, - provider, - errorCode: streamErrorCode, - startTime, - requestId: traceId, - }); - }; - - const streamFailureFinalizers = streamFailure.createStreamFailureFinalizers({ - isFailureCompletionRecorded: () => streamFailureCompletionRecorded, - isStreamCompletionRecorded: () => streamCompletionRecorded, - onStreamComplete, + persistAttemptLogs, persistFailureUsage, - onStreamFailure, - hasEmittedOutput: () => streamEmittedOutput(transformStream), - }); - const handleStreamFailure = streamFailureFinalizers.handleStreamFailure; - onPipelineStreamError = streamFailureFinalizers.onPipelineStreamError; - // #9653: gives a genuine, race-delayed completion a chance to land (see - // createClientDisconnectGraceHandler's doc comment) before persisting a false - // 499/0-tokens for a request that actually delivered its full response. - onClientDisconnectFinalize = streamFailure.createClientDisconnectGraceHandler({ - isStreamCompletionRecorded: () => streamCompletionRecorded, - gracePeriodMs: STREAM_DISCONNECT_GRACE_PERIOD_MS, - finalize: (event) => - handleStreamFailure({ - status: 499, - message: `Client disconnected: ${event.reason}`, - code: "client_disconnected", - type: "client_disconnected", - }), - }); - - // For providers using Responses API format, translate stream back to openai (Chat Completions) format - // UNLESS client is Droid CLI which expects openai-responses format back - const needsResponsesTranslation = - targetFormat === FORMATS.OPENAI_RESPONSES && - clientResponseFormat === FORMATS.OPENAI && - !isResponsesEndpoint && - !isDroidCLI; - const streamStateBody = finalBody || body; - - // Client's explicit thinking intent (Anthropic Messages shape). Claude Code - // sends `{type:"enabled"}` or `{type:"adaptive"}` to opt into relaying - // upstream reasoning_content as Claude thinking blocks; `{type:"disabled"}` - // or an omitted `thinking` field opts out. Kept false for every other - // client schema (OpenAI / Responses), which never express intent through - // `body.thinking`. Mirrors hasActiveClaudeThinking() so the request and - // response sides agree on what counts as "thinking requested" — a prior - // inline `=== "enabled"` check silently suppressed `adaptive` (the intent - // Claude Code actually sends), leaking the mismatch as a broken tool-call - // turn (call log 1787566395384-bab9ab: reasoning dropped → model emitted - // DSML tool-call markers as plain text → incomplete `stop` finish). - const requestedThinking = hasActiveClaudeThinking((body ?? {}) as Record); - - streamTimingOriginOffsetMs = Date.now() - startTime; - if (needsResponsesTranslation) { - // Provider returns openai-responses, translate to openai (Chat Completions) that clients expect - log?.debug?.("STREAM", `Responses translation mode: openai-responses → openai`); - transformStream = createSSETransformStreamWithLogger( - "openai-responses", - "openai", - provider, - reqLogger, - responseToolNameMap, - model, - connectionId, - streamStateBody, - onStreamComplete, - apiKeyInfo, - handleStreamFailure, - copilotCompatibleReasoning, - false, - requestedThinking, - customToolNames, - // openai-responses → openai translation still wants the namespace identity - // map for #7936-style round-trip closure when the client also speaks - // Responses (Codex CLI). - requestToolIdentityMap - ); - } else if (needsTranslation(targetFormat, clientResponseFormat)) { - // Standard translation for other providers - log?.debug?.("STREAM", `Translation mode: ${targetFormat} → ${clientResponseFormat}`); - transformStream = createSSETransformStreamWithLogger( - targetFormat, - clientResponseFormat, - provider, - reqLogger, - responseToolNameMap, - model, - connectionId, - streamStateBody, - onStreamComplete, - apiKeyInfo, - handleStreamFailure, - copilotCompatibleReasoning, - // Suppress the `` close marker for clients that render it verbatim - // (e.g. OpenCode by UA; any client via `x-omniroute-thinking-marker: off`); - // preserved for Claude Code / Cursor and unknown clients by default (#5245 / - // #5312). Responses API clients always suppress it (structured reasoning - // items make the marker meaningless); otherwise the header wins over the - // UA allowlist. - resolveSuppressThinkClose({ - userAgent: streamUserAgent, - thinkingMarkerHeader, - clientResponseFormat, - }), - requestedThinking, - customToolNames, - requestToolIdentityMap - ); - } else { - log?.debug?.("STREAM", `Standard passthrough mode`); - transformStream = createPassthroughStreamWithLogger( - provider, - reqLogger, - responseToolNameMap, - model, - connectionId, - streamStateBody, - onStreamComplete, - apiKeyInfo, - handleStreamFailure, - clientResponseFormat, - requestToolIdentityMap - ); - } - - const finalStream = assembleStreamingPipeline({ - providerResponse, - transformStream, - streamController, - createPiiTransform, - clientRawRequestHeaders: clientRawRequest?.headers, - clientResponseFormat, - echoModel, - responseHeaders, - // Same adaptive budget the pre-handoff readiness gate above just used — - // reasoning models that legitimately take a while to say anything keep - // that same patience for their first REAL content, not just their first - // lifecycle frame. See pipeWithDisconnect's own doc comment. - contentStallTimeoutMs: streamReadinessPolicy.timeoutMs, - }); - const clientFacingStream = wrapReadableStreamWithFinalize( - finalStream, - releaseTurnExecution - ); - - // ── Gamification event (fire-and-forget) ── - await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider }); - - // ── Plugin onResponse hook (fire-and-forget) ── - await runPluginOnResponseHook({ - requestId: traceId, - body, - model, + pipelineSessionId, provider, - apiKeyInfo, - headers: clientRawRequest?.headers, - response: { status: 200, streamed: true }, + providerHeaders, + providerRequestCapture, + providerResponse, + providerUrl, + reasoningCacheScope, + reasoningReplayHistory, + releaseTurnExecution, + reqLogger, + requestToolIdentityMap, + resolveReportedServiceTier, + semanticCacheEnabled, + skillRequestId, + startTime, + stream, + streamController, + streamUserAgent, + targetFormat, + thinkingMarkerHeader, + toolNameMap, + traceId, + translatedBody, + videoBridgeObserved, }); - - const response = new Response(clientFacingStream, { - headers: responseHeaders, - }); - turnExecutionHandedOffToStream = true; - return { - success: true, - response, - }; + onPipelineStreamError = streamingTailOutcome.carry.onPipelineStreamError; + onClientDisconnectFinalize = streamingTailOutcome.carry.onClientDisconnectFinalize; + turnExecutionHandedOffToStream = streamingTailOutcome.carry.turnExecutionHandedOffToStream; + return streamingTailOutcome.result; } finally { if (!turnExecutionHandedOffToStream) { releaseTurnExecution(); diff --git a/open-sse/handlers/chatCore/claudeEffortVariant.ts b/open-sse/handlers/chatCore/claudeEffortVariant.ts index 5e5f9a779243..0256d9278505 100644 --- a/open-sse/handlers/chatCore/claudeEffortVariant.ts +++ b/open-sse/handlers/chatCore/claudeEffortVariant.ts @@ -16,6 +16,7 @@ import { splitClaudeEffortSuffix } from "../../config/providerModels.ts"; import { isClaudeCodeCompatibleProvider } from "../../services/claudeCodeCompatible.ts"; import { FORMATS } from "../../translator/formats.ts"; import { isKnownClaudeEffortBaseModel } from "../../utils/claudeEffortVariants.ts"; +import { isAntigravityLiteralTierModelId } from "../../utils/antigravityLiteralModelIds.ts"; import { isDevinLiteralModelIdProvider } from "../../utils/devinLiteralModelIds.ts"; /** @@ -61,6 +62,12 @@ export function applyClaudeEffortVariant(opts: { return { effectiveModel, log: null }; } + // Antigravity Claude 5.x tiers are distinct upstream ids (`claude-opus-5-5-high`); + // the bare base id does not exist on the Cloud Code backend. + if (isAntigravityLiteralTierModelId(provider, effectiveModel)) { + return { effectiveModel, log: null }; + } + if (typeof effectiveModel === "string") { const { baseModel, effort } = splitClaudeEffortSuffix(effectiveModel); const isDirectClaudeLane = provider === "claude" || isClaudeCodeCompatibleProvider(provider); diff --git a/open-sse/handlers/chatCore/compressionComboPredicates.ts b/open-sse/handlers/chatCore/compressionComboPredicates.ts index d73b4324cb72..a730606a6afc 100644 --- a/open-sse/handlers/chatCore/compressionComboPredicates.ts +++ b/open-sse/handlers/chatCore/compressionComboPredicates.ts @@ -1,13 +1,20 @@ /** - * chatCore compression-combo predicates (Quality Gate v2 / Fase 9 — chatCore god-file + * chatCore compression-combo helpers (Quality Gate v2 / Fase 9 — chatCore god-file * decomposition, #3501). * - * Pure predicates extracted from handleChatCore's compression setup: detect the built-in - * RTK→caveman stacked pipeline and whether a runtime combo has at least one pipeline layer. - * No handler state is captured; behaviour is byte-identical to the previous inline closures. + * Extracted from handleChatCore's compression setup: the two predicates detect the built-in + * RTK→caveman stacked pipeline and whether a runtime combo has at least one pipeline layer + * (byte-identical to the original inline closures); defaultComboForRequest resolves the legacy + * default combo for one request — yielding to a plan the request header chose, otherwise + * applying the lossy request policy to the combo's pipeline. No handler state is captured. */ -import type { CompressionConfig } from "../../services/compression/types.ts"; +import type { + CompressionConfig, + CompressionPipelineStep, +} from "../../services/compression/types.ts"; +import { applyLossyRequestPolicy } from "../../services/compression/lossyRequestPolicy.ts"; +import { planFromHeader } from "../../services/compression/planResolution.ts"; export type RuntimeCompressionCombo = { id: string; @@ -39,3 +46,26 @@ export function isStackedCompressionCombo( // page) must still apply. applyCompressionComboConfig already guards length === 0. return Boolean(compressionCombo && compressionCombo.pipeline.length >= 1); } + +/** + * The legacy default combo as this request may run it, or null when it must not apply. A plan + * the request header chose (a named combo, engine:, default) wins over every operator layer. + * The default combo is operator configuration, not a request opt-in, so its lossy steps get the + * same policy resolveBasePlan applies to every other plan. + */ +export function defaultComboForRequest( + compressionCombo: RuntimeCompressionCombo | null, + request: { + config: CompressionConfig; + header: string | null; + combos: Record; + } +): RuntimeCompressionCombo | null { + if (!isStackedCompressionCombo(compressionCombo)) return null; + if (request.header && planFromHeader(request.config, request.header, request.combos)) return null; + const { stackedPipeline } = applyLossyRequestPolicy( + { mode: "stacked", stackedPipeline: compressionCombo.pipeline }, + request.header + ); + return { ...compressionCombo, pipeline: stackedPipeline as RuntimeCompressionCombo["pipeline"] }; +} diff --git a/open-sse/handlers/chatCore/contextEstimation.ts b/open-sse/handlers/chatCore/contextEstimation.ts index 796b317ec036..ee571a6f6752 100644 --- a/open-sse/handlers/chatCore/contextEstimation.ts +++ b/open-sse/handlers/chatCore/contextEstimation.ts @@ -1,5 +1,9 @@ import { adaptBodyForCompression } from "../../services/compression/bodyAdapter.ts"; import { estimateTokens } from "../../services/contextManager.ts"; +import { + applyEstimatorCalibration, + recordEstimatorCalibrationFromUsage, +} from "../../services/estimatorCalibration.ts"; type JsonRecord = Record; @@ -41,3 +45,50 @@ export function estimateFinalInputTokenBreakdown( export function estimateFinalInputTokens(requestBody: JsonRecord | null | undefined): number { return estimateFinalInputTokenBreakdown(requestBody).total; } + +function toolsPresentIn(requestBody: JsonRecord | null | undefined): boolean { + const tools = requestBody?.tools; + return Array.isArray(tools) && tools.length > 0; +} + +/** + * #14931: the raw chars/4 estimate scaled by the learned actual/estimated + * ratio for this provider/model × tools shape (services/estimatorCalibration). + * The factor is 1.0 on cold start, so callers see the raw estimate until + * trusted observations exist. Sits next to estimateFinalInputTokens so the + * calibrated and raw numbers share one code path (same breakdown, same basis). + */ +export function estimateCalibratedFinalInputTokens( + requestBody: unknown, + provider: string | null | undefined, + model: string | null | undefined +): number { + const record = asJsonRecord(requestBody); + const estimated = estimateFinalInputTokens(record); + if (estimated <= 0) return estimated; + return applyEstimatorCalibration(provider, model, estimated, toolsPresentIn(record)); +} + +/** + * #14931: pair a guard-acted estimate with the provider's own prompt_tokens so + * the calibration keeps learning. Thin adapter so the streaming and + * non-streaming completion paths share one call shape; a no-op when the usage + * carries no token count or the observation is rejected by the service. + */ +export function recordFinalInputCalibration( + requestBody: unknown, + provider: string | null | undefined, + model: string | null | undefined, + estimatedTokens: number, + usage: unknown, + aggregatedUsage = false +): void { + recordEstimatorCalibrationFromUsage({ + provider, + model, + estimatedTokens, + usage, + toolsPresent: toolsPresentIn(asJsonRecord(requestBody)), + aggregatedUsage, + }); +} diff --git a/open-sse/handlers/chatCore/executeProviderRequest.ts b/open-sse/handlers/chatCore/executeProviderRequest.ts new file mode 100644 index 000000000000..fa7c536cde99 --- /dev/null +++ b/open-sse/handlers/chatCore/executeProviderRequest.ts @@ -0,0 +1,688 @@ +/** + * One provider send. Lifted out of handleChatCore. The body is the tip + * implementation; the only change is that closed-over request state arrives + * through deps. + */ + +import type { BaseExecutor } from "../../executors/base.ts"; +import type { AgentGoalPolicy } from "../../utils/agentGoalPolicy.ts"; +import { prepareUpstreamBody } from "./upstreamBody.ts"; +import { getExecutionConnectionId } from "./executionCredentials.ts"; +import { + resolveAccountSemaphoreKey, + resolveAccountSemaphoreMaxConcurrency, +} from "./executorHelpers.ts"; +import { + materializeDeduplicatedExecutionResult, + stripNextMiddlewareControlHeaders, + stripStaleForwardingHeaders, +} from "./responseHeaders.ts"; +import { readNonStreamingResponseBody } from "./nonStreamingResponseBody.ts"; +import { wrapReadableStreamWithFinalize } from "./streamFinalize.ts"; +import { + normalizeExecutorResult, + executeWithUpstreamStartTimeout, + resolveConnectionTimeoutMs, +} from "./upstreamTimeouts.ts"; +import { getCodexClientSessionId } from "../../config/codexIdentity.ts"; +import { + noteCodexTurnStateProvenance, + readCodexTurnStateHeader, +} from "../../config/codexTurnState.ts"; +import { HTTP_STATUS, STREAM_RECOVERY } from "../../config/constants.ts"; +import { createRecoverableStream, makeContinuationBody } from "../../services/streamRecovery.ts"; +import { persistCodexChildQuotaResponse } from "../../services/codexAccount/index.ts"; +import { invalidateCodexQuotaCache } from "../../services/codexQuotaFetcher.ts"; +import { invalidateGenericQuotaCacheOnStatus } from "../../services/genericQuotaFetcher.ts"; +import { withRateLimit, resolveRequestQueueMaxWaitMs } from "../../services/rateLimitManager.ts"; +import { acquireMany as acquireConcurrencyGates } from "../../services/accountSemaphore.ts"; +import { rethrowAdmissionError, remainingQueueBudgetMs } from "./queueBudget.ts"; +import { deduplicate } from "../../services/requestDedup.ts"; +import { + classifyModelScope429, + getModelScopeRetryDelayMs, +} from "../../services/modelscopePolicy.ts"; +import { incrementRequestCount } from "../../services/geminiRateLimitTracker.ts"; +import { normalizeHeaders } from "../../utils/headers.ts"; +import { runWithCapture } from "../../utils/providerRequestLogging.ts"; +import type { Capture } from "../../utils/providerRequestLogging.ts"; +import { + isStreamRecoveryExplicitlyConfigured, + resolveResilienceSettings, + type ResilienceSettings, +} from "@/lib/resilience/settings"; +import { updatePendingScope, type PendingRequestScope } from "@/lib/usage/pendingRequestScope"; +import { shouldIsolateProbeFailures } from "@/shared/utils/probeOrigin"; +import { resolveProviderId } from "@/shared/constants/providers"; +import { buildContinuationLogHooks } from "./recoveryTraceLogging.ts"; +import { formatStreamRecoveryRetryWarning } from "./streamErrorResult.ts"; +import { injectSystemPromptPostTranslation } from "../../services/systemPrompt.ts"; + +export type ChatCoreExecutorResult = { + response: Response; + url?: string; + transport?: string; + headers?: Record; + body?: unknown; + _accountSemaphoreRelease?: () => void; + _executionCredentials?: Record; + _dedupSnapshot?: { + status: number; + statusText: string; + headers: [string, string][]; + payload: string; + }; + [key: string]: unknown; +}; + +export type TrustedEffortContext = { + originModel?: string; + resolvedThinkingEffort?: string | null; + defaultThinkingEffort?: string | null; +}; + +type ChatLog = + | { + debug?: (...args: unknown[]) => void; + info?: (...args: unknown[]) => void; + warn?: (...args: unknown[]) => void; + error?: (...args: unknown[]) => void; + } + | null + | undefined; + +export type ExecuteProviderRequestDeps = { + agentGoalPolicy: AgentGoalPolicy; + assertManagedLeaseFence: (attemptConnectionId: string | null | undefined) => void; + buildUpstreamHeadersForExecute: (modelToCall: string) => Record; + clientRawRequest: { + headers?: Headers | Record | null; + signal?: AbortSignal | null; + }; + clientResponseFormat: string | null | undefined; + connectionId: string | null | undefined; + contextEditingEnabled?: boolean; + correlationId: string | null | undefined; + credentials: Record | null | undefined; + dedupEnabled: boolean; + dedupHash: string | null; + effectiveModel: string; + executor: Pick; + extendedContext?: boolean; + getExecutionCredentials: () => Record; + getExecutorClientHeaders: () => Record; + isModelScope: () => boolean; + isOpencodeClient: boolean; + log: ChatLog; + model: string; + onCredentialsRefreshed: + ((next: Record) => void | Promise) | null | undefined; + pendingScope: PendingRequestScope; + provider: string; + providerRequestCapture: Capture; + rawBody: Record; + recordKeyHealthStatus: ( + status: number, + creds: Record | null | undefined, + transport?: string, + failureDetail?: string + ) => void; + requestedModel: string; + resilienceSettings: ResilienceSettings; + settings: Record | null | undefined; + skipUpstreamRetry: boolean; + stream: boolean; + streamController: { signal: AbortSignal }; + targetFormat: string; + trace: (label: string, extra?: Record) => void; + traceId: string; + translatedBody: Record; + trustedEffortContext: TrustedEffortContext; + upstreamStream: boolean; + userAgent: string | undefined; +}; + +export async function executeProviderRequest( + deps: ExecuteProviderRequestDeps, + modelToCall: string = deps.effectiveModel, + allowDedup: boolean = false +): Promise { + const { + agentGoalPolicy, + assertManagedLeaseFence, + buildUpstreamHeadersForExecute, + clientRawRequest, + clientResponseFormat, + connectionId, + contextEditingEnabled, + correlationId, + credentials, + dedupEnabled, + dedupHash, + executor, + extendedContext, + getExecutionCredentials, + getExecutorClientHeaders, + isModelScope, + isOpencodeClient, + log, + model, + onCredentialsRefreshed, + pendingScope, + provider, + providerRequestCapture, + rawBody, + recordKeyHealthStatus, + requestedModel, + resilienceSettings, + settings, + skipUpstreamRetry, + stream, + streamController, + targetFormat, + trace, + traceId, + translatedBody, + trustedEffortContext, + upstreamStream, + userAgent: _userAgent, + } = deps; + const body = rawBody; + const execute = async () => { + // Upstream body preparation extracted to chatCore/upstreamBody.ts (#3501 — first internal + // sub-slice of executeProviderRequest); produces the body sent upstream (payload rules + + // tool-limit truncation + prompt_cache_key injection). + let bodyToSend = await prepareUpstreamBody({ + translatedBody, + modelToCall, + ...trustedEffortContext, + provider, + targetFormat, + credentials: getExecutionCredentials(), + log, + bypassDefaultToolLimit: isOpencodeClient, + isOpencodeClient, + rawBody: body, + clientRawRequest, + }); + + // Global System Prompt — SINGLE injection point (post-translation) for + // carrier-ful targets. The old unconditional pre-translation pass + // (former chatCore injectSystemPrompt call) was removed: it chained + // with this pass to inject prefix/suffix 2-3x and dual-wrote + // body.system + messages[] on the claude path, which strict upstreams + // (HCP-Vision vLLM: "System message must be at the beginning") reject + // with 400. Format-aware via targetFormat: messages[] (openai/codex — + // prefix FIRST system, suffix LAST), claude `system` field, gemini + // `systemInstruction`, responses `instructions`. Carrier-less targets + // (kiro user-fold, antigravity Cloud Code envelope) are covered by the + // gated PRE-translation pass before translateRequest instead. + bodyToSend = injectSystemPromptPostTranslation(bodyToSend, { targetFormat }); + + updatePendingScope(pendingScope, { + providerRequest: bodyToSend, + stage: "payload_prepared", + }); + + let releaseRawResultAccountSemaphore = () => {}; + try { + const rawResult: ChatCoreExecutorResult = await (async () => { + let attempts = 0; + const isModelScopeForRequest = isModelScope(); + const maxAttempts = isModelScopeForRequest ? 3 : provider === "codex" ? 3 : 1; + + while (attempts < maxAttempts) { + trace("pre_executor", { attempt: attempts }); + updatePendingScope(pendingScope, { + stage: "sending_to_provider", + }); + const execCreds = getExecutionCredentials(); + const executionConnectionId = getExecutionConnectionId(execCreds); + const attemptConnectionId = executionConnectionId || connectionId; + const accountSemaphoreMaxConcurrency = resolveAccountSemaphoreMaxConcurrency(execCreds); + const accountSemaphoreKey = resolveAccountSemaphoreKey({ + provider, + model: modelToCall, + connectionId: attemptConnectionId, + credentials: execCreds, + }); + const canonicalProviderKey = resolveProviderId(String(provider).trim().toLowerCase()); + const providerConcurrency = + resilienceSettings.providerQuotaOverrides[canonicalProviderKey]?.providerConcurrency ?? + 0; + + trace("pre_semaphore", { + semaphoreKey: accountSemaphoreKey, + max: accountSemaphoreMaxConcurrency, + }); + if (accountSemaphoreKey && accountSemaphoreMaxConcurrency != null) { + updatePendingScope(pendingScope, { + stage: "waiting_account_slot", + }); + } + const maxWaitMs = resolveRequestQueueMaxWaitMs( + provider, + undefined, + attemptConnectionId ?? undefined + ); + const gateStartedAt = Date.now(); + const releaseAccountSemaphore = await acquireConcurrencyGates( + [ + { + key: "global", + maxConcurrency: resilienceSettings.requestQueue.globalConcurrentRequests, + }, + { + key: `provider:${canonicalProviderKey}`, + maxConcurrency: providerConcurrency, + }, + { + key: accountSemaphoreKey || "", + maxConcurrency: accountSemaphoreKey ? accountSemaphoreMaxConcurrency : null, + }, + ], + { + timeoutMs: maxWaitMs, + maxQueueSize: resilienceSettings.requestQueue.maxQueueDepth, + signal: streamController.signal, + } + ).catch(rethrowAdmissionError); + const remainingAfterGate = remainingQueueBudgetMs(maxWaitMs, gateStartedAt); + trace("post_semaphore", { maxWaitMs, remainingAfterGate }); + updatePendingScope(pendingScope, { + stage: "waiting_rate_limit", + }); + + try { + trace("pre_rate_limit", { connectionId: attemptConnectionId }); + const rawExecutorResult = await withRateLimit( + provider, + attemptConnectionId, + modelToCall, + async () => { + trace("inside_rate_limit", { connectionId: attemptConnectionId }); + updatePendingScope(pendingScope, { + stage: "rate_limit_slot_acquired", + }); + assertManagedLeaseFence(attemptConnectionId); + return executeWithUpstreamStartTimeout({ + executor, + provider, + model: modelToCall, + connectionTimeoutMs: resolveConnectionTimeoutMs(execCreds?.providerSpecificData), + signal: streamController.signal, + log, + execute: (signal) => + runWithCapture(providerRequestCapture, () => + executor.execute({ + model: modelToCall, + body: bodyToSend, + stream: upstreamStream, + credentials: execCreds, + signal, + log, + extendedContext, + upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall), + clientHeaders: getExecutorClientHeaders(), + clientResponseFormat, + onCredentialsRefreshed, + skipUpstreamRetry, + contextEditing: { enabled: contextEditingEnabled }, + correlationId, + }) + ), + }); + }, + streamController.signal, + remainingAfterGate, + correlationId ?? undefined, + { + executor: executor as unknown as { getTimeoutMs?: () => unknown }, + providerSpecificData: execCreds?.providerSpecificData, + } + ); + const res = normalizeExecutorResult(rawExecutorResult); + trace("post_executor", { status: res?.response?.status }); + + // When a payload override rewrote body.model (custom-model alias → + // real upstream id, e.g. `gemini-3.7-flash-high` → `gemini-3.7-flash`), + // log and track the WIRE model so dashboards/telemetry reflect what + // actually shipped and Gemini rate-limit accounting uses the real id + // (the executor already built its URL from the same rewritten model). + const wireModel = typeof res.model === "string" && res.model ? res.model : modelToCall; + if (wireModel !== modelToCall) { + log?.debug?.( + "PAYLOAD_RULES", + `Payload rules rewrote model for URL: requested=${modelToCall} wire=${wireModel}` + ); + } + + if ( + provider === "codex" && + attemptConnectionId && + !(await shouldIsolateProbeFailures()) + ) { + try { + const persistedQuota = await persistCodexChildQuotaResponse({ + connectionId: String(attemptConnectionId), + model: modelToCall || model || requestedModel || "", + headers: normalizeHeaders(res.response.headers), + status: res.response.status, + }); + if (persistedQuota) { + execCreds.providerSpecificData = persistedQuota.providerSpecificData; + if (persistedQuota.exhaustionLog) { + log?.debug?.("CODEX", persistedQuota.exhaustionLog); + } + } + if (res.response.status === 429) { + invalidateCodexQuotaCache(String(attemptConnectionId)); + } + } catch (err) { + const errMessage = err instanceof Error ? err.message : String(err); + log?.debug?.("CODEX", `Failed to persist codex quota state: ${errMessage}`); + } + } else if (attemptConnectionId && res.response.status === 429) { + // Dropped generic quota cache after 429 + invalidateGenericQuotaCacheOnStatus({ + provider, + connectionId: String(attemptConnectionId), + status: res.response.status, + isolateProbe: await shouldIsolateProbeFailures(), + }); + } + + // Track Gemini RPM + RPD request counts for 429 classification + if (provider === "gemini") { + incrementRequestCount(wireModel); + } + + updatePendingScope(pendingScope, { + stage: "provider_response_started", + }); + + if ( + stream && + (res.response.ok || + res.response.status === HTTP_STATUS.UNAUTHORIZED || + res.response.status === HTTP_STATUS.FORBIDDEN) && + executionConnectionId && + !(await shouldIsolateProbeFailures()) + ) { + const failureDetail = res.response.ok + ? "" + : await res.response + .clone() + .text() + .catch(() => ""); + recordKeyHealthStatus(res.response.status, execCreds, res.transport, failureDetail); + } + + if (isModelScope() && res.response.status === 429 && attempts < maxAttempts - 1) { + const bodyPeek = await res.response + .clone() + .text() + .catch(() => ""); + const normalizedHeaders = normalizeHeaders(res.response.headers); + const decision = classifyModelScope429(bodyPeek, normalizedHeaders); + if (decision.retryable) { + const delay = getModelScopeRetryDelayMs(normalizedHeaders, attempts); + log?.warn?.( + "MODELSCOPE_RETRY", + `429 ${decision.kind}; retrying in ${delay}ms (model remaining: ${decision.snapshot.modelRemaining ?? "unknown"})` + ); + releaseAccountSemaphore(); + await new Promise((r) => setTimeout(r, delay)); + attempts++; + continue; + } + } + + // For streaming: release the semaphore when the client drains or cancels the stream. + // Non-2xx streams must drop the slot before returning so the pipeline can rotate + // accounts without holding the failed connection's concurrency gate. Do NOT + // cancel() the body here — the pipeline clones it (BYOP 422 / toOutcome). + if (stream) { + const originalBody = res.response.body; + const okStatus = res.response.status >= 200 && res.response.status < 300; + if (!originalBody || !okStatus) { + releaseAccountSemaphore(); + return { + ...res, + _executionCredentials: execCreds, + }; + } + + // Opt-in transparent stream recovery (free-claude-code port, default OFF). + // Only engages for a successful (2xx) stream — an error body must never be + // held or replayed. Setting is read once here from the cached resolved + // resilience settings; the default path is byte-for-byte unchanged. + let streamRecoveryEnabled = false; + let continueMidStreamEnabled = false; + let throughputWatchdog = + resolveResilienceSettings(null).streamRecovery.throughputWatchdog; + if (okStatus) { + try { + // Reuse the request-consolidated settings read (see line ~2076) — no + // second DB/cache hit. Default OFF when the setting is absent. + const sr = resolveResilienceSettings(settings).streamRecovery; + // Fail-closed: the agent-goal-policy heuristic may only ADD recovery + // when the operator has no explicit configuration. If the operator + // explicitly configured stream recovery (env var or DB/settings + // override), that value always wins — the goal policy must never + // re-enable recovery the operator explicitly turned off. + const operatorExplicit = isStreamRecoveryExplicitlyConfigured(settings); + const goalOverride = !operatorExplicit && agentGoalPolicy.streamRecoveryEnabled; + streamRecoveryEnabled = sr.enabled || goalOverride; + continueMidStreamEnabled = sr.continueMidStream === true; + throughputWatchdog = sr.throughputWatchdog; + if (goalOverride && !sr.enabled) { + log?.info?.( + "AGENT_GOAL", + `agentGoalPolicy override: stream recovery enabled for goal request requestId=${traceId} model=${modelToCall || model || requestedModel || "unknown"}` + ); + } + } catch { + streamRecoveryEnabled = false; + continueMidStreamEnabled = false; + throughputWatchdog = + resolveResilienceSettings(null).streamRecovery.throughputWatchdog; + } + } + + let clientBody: ReadableStream; + if (streamRecoveryEnabled || throughputWatchdog.enabled) { + // Run the SAME upstream (same account/creds) with a given body and return + // its 2xx stream, or null. Used both by the early-retry re-open (same body) + // and the mid-stream continuation (assistant-prefilled body). + const runUpstreamStream = async ( + body: unknown + ): Promise | null> => { + try { + assertManagedLeaseFence(attemptConnectionId); + const retryRaw = await executeWithUpstreamStartTimeout({ + executor, + provider, + model: modelToCall, + connectionTimeoutMs: resolveConnectionTimeoutMs( + execCreds?.providerSpecificData + ), + signal: streamController.signal, + log, + execute: (signal) => + runWithCapture(providerRequestCapture, () => + executor.execute({ + model: modelToCall, + body, + stream: upstreamStream, + credentials: execCreds, + signal, + log, + extendedContext, + upstreamExtraHeaders: buildUpstreamHeadersForExecute(modelToCall), + clientHeaders: getExecutorClientHeaders(), + clientResponseFormat, + onCredentialsRefreshed, + skipUpstreamRetry, + contextEditing: { enabled: contextEditingEnabled }, + correlationId, + }) + ), + }); + const retryRes = normalizeExecutorResult(retryRaw); + const retryOk = + retryRes.response.status >= 200 && retryRes.response.status < 300; + if (retryOk && retryRes.response.body) { + return retryRes.response.body as ReadableStream; + } + await retryRes.response.body?.cancel().catch(() => {}); + return null; + } catch { + return null; + } + }; + + // Mid-stream continuation (Fase 4.4): re-request with the partial text as an + // assistant prefill. Gated by its own setting and only for OpenAI-compatible + // bodies (makeContinuationBody returns null otherwise). + const continueStream = continueMidStreamEnabled + ? (assistantSoFar: string) => { + const continuationBody = makeContinuationBody( + bodyToSend as Record, + assistantSoFar + ); + return continuationBody + ? runUpstreamStream(continuationBody) + : Promise.resolve(null); + } + : undefined; + + clientBody = createRecoverableStream( + originalBody as ReadableStream, + () => runUpstreamStream(bodyToSend), + { + finalize: releaseAccountSemaphore, + onRetry: (attempt, err) => + log?.warn?.( + "STREAM_RECOVERY", + formatStreamRecoveryRetryWarning( + attempt, + STREAM_RECOVERY.EARLY_RETRY_MAX, + err + ) + ), + continueStream, + ...buildContinuationLogHooks(log, correlationId), + throughputWatchdog, + onWatchdogAbort: () => + log?.warn?.( + "STREAM_WATCHDOG", + "active upstream stream stayed below the configured useful-output rate" + ), + } + ); + } else { + clientBody = wrapReadableStreamWithFinalize(originalBody, releaseAccountSemaphore); + } + + return { + ...res, + _executionCredentials: execCreds, + response: new Response(clientBody, { + status: res.response.status, + statusText: res.response.statusText, + headers: new Headers(normalizeHeaders(res.response.headers)), + }), + }; + } + + return { + ...res, + _executionCredentials: execCreds, + _accountSemaphoreRelease: releaseAccountSemaphore, + }; + } catch (error) { + releaseAccountSemaphore(); + throw error; + } + } + })(); + + if (stream) { + return rawResult; + } + + // Non-stream: release semaphore immediately after reading full response body. + const status = rawResult.response.status; + + releaseRawResultAccountSemaphore = + typeof rawResult._accountSemaphoreRelease === "function" + ? rawResult._accountSemaphoreRelease + : () => {}; + + const statusText = rawResult.response.statusText; + const headersObj = normalizeHeaders(rawResult.response.headers); + const responseHeaders = new Headers(headersObj); + stripStaleForwardingHeaders(responseHeaders); + stripNextMiddlewareControlHeaders(responseHeaders); + // The upstream headers (turn-state included) are about to be committed + // to the client — record which connection minted the blob so a later + // cross-account echo can be stripped (Codex failover guard). + if (provider === "codex" && readCodexTurnStateHeader(responseHeaders)) { + noteCodexTurnStateProvenance( + getCodexClientSessionId(clientRawRequest?.headers), + rawResult._executionCredentials?.connectionId ?? credentials?.connectionId + ); + } + const contentType = (responseHeaders.get("content-type") || "").toLowerCase(); + const payload = await readNonStreamingResponseBody( + rawResult.response, + contentType, + upstreamStream + ); + // Use the exact execution credential selected for this request. Model capability + // failures stay in routing telemetry; authoritative success only recovers this key. + if ( + rawResult._executionCredentials?.connectionId && + (rawResult._executionCredentials.apiKey || rawResult._executionCredentials.accessToken) + ) { + recordKeyHealthStatus( + status, + rawResult._executionCredentials, + rawResult.transport, + status >= 400 ? payload : "" + ); + } + releaseRawResultAccountSemaphore(); + releaseRawResultAccountSemaphore = () => {}; + + return { + ...rawResult, + response: new Response(payload, { status, statusText, headers: responseHeaders }), + _dedupSnapshot: { + status, + statusText, + headers: (() => { + const arr: [string, string][] = []; + responseHeaders.forEach((v, k) => arr.push([k, v])); + return arr; + })(), + payload, + }, + }; + } catch (error) { + releaseRawResultAccountSemaphore(); + throw error; + } + }; + + if (allowDedup && dedupEnabled && dedupHash) { + const dedupResult = await deduplicate(dedupHash, execute); + if (dedupResult.wasDeduplicated) { + log?.debug?.("DEDUP", `Joined in-flight request hash=${dedupHash}`); + } + return materializeDeduplicatedExecutionResult(dedupResult.result); + } + + return execute(); +} diff --git a/open-sse/handlers/chatCore/nonStreamingClientTranslate.ts b/open-sse/handlers/chatCore/nonStreamingClientTranslate.ts index 49b23d6cb8ec..4f6251e24aab 100644 --- a/open-sse/handlers/chatCore/nonStreamingClientTranslate.ts +++ b/open-sse/handlers/chatCore/nonStreamingClientTranslate.ts @@ -182,7 +182,12 @@ export function translateNonStreamingClientResponse( item.namespace = identity.namespace; item.name = identity.name; } - Object.assign(item, plaintextCollaborationFields(item.namespace, item.name)); + // #15088 - the empty marker means "this call was translated from + // plaintext Chat Completions". A native Responses body already carries + // the protocol's own encrypted_function_args (present or absent). + if (responsePayloadFormat !== FORMATS.OPENAI_RESPONSES) { + Object.assign(item, plaintextCollaborationFields(item.namespace, item.name)); + } } } } else if (clientResponseFormat === FORMATS.OPENAI) { diff --git a/open-sse/handlers/chatCore/nonStreamingResponse.ts b/open-sse/handlers/chatCore/nonStreamingResponse.ts new file mode 100644 index 000000000000..58f2f82f27d2 --- /dev/null +++ b/open-sse/handlers/chatCore/nonStreamingResponse.ts @@ -0,0 +1,1122 @@ +/** + * Non-streaming response path, lifted out of handleChatCore. + * The body matches the barrel. Closed-over request state arrives through + * deps, and the values the barrel reads afterwards leave through carry. + */ + +import { meteredBudgetCost } from "@/lib/usage/meteredBudgetPolicy"; +import { readCpaAuthIndex } from "../chatCore/failureUsage.ts"; + +import { createRoutingEvent, emitRoutingEvent } from "../../services/routing/index.ts"; + +import { buildClaudePromptCacheLogMeta } from "../chatCore/executorHelpers.ts"; + +import { + shouldUseNativeCodexPassthrough, + shouldUseNativeXaiResponsesPassthrough, + redactPassthroughThinkingSignatures, + isClaudeCodeSemanticPassthroughRequest, +} from "../chatCore/passthroughHelpers.ts"; + +import { + applyServerOwnedToolLoopIfNeeded, + derivePostInjectionRequestIdentity, + followUpLegInput, +} from "../chatCore/serverOwnedToolLoopWire.ts"; + +import { + buildStreamingResponseHeaders, + stripStaleForwardingHeaders, +} from "../chatCore/responseHeaders.ts"; + +import { maybeSyncClaudeExtraUsageState } from "../chatCore/telemetryHelpers.ts"; +import { recordFinalInputCalibration } from "./contextEstimation.ts"; + +export { + shouldUseNativeCodexPassthrough, + shouldUseNativeXaiResponsesPassthrough, + redactPassthroughThinkingSignatures, + isClaudeCodeSemanticPassthroughRequest, + buildStreamingResponseHeaders, + stripStaleForwardingHeaders, +}; + +import { HTTP_STATUS } from "../../config/constants.ts"; + +import { lockModel } from "../../services/accountFallback.ts"; + +import { buildPostCallGuardrailContext } from "../chatCore/postCallGuardrailContext.ts"; +import { buildNonStreamingResponseHeaders } from "../chatCore/nonStreamingResponseHeaders.ts"; +import { maybeWrapForcedNonStreamingResponsesJson } from "../chatCore/responsesJsonToSse.ts"; +import { runProviderExecutionPipeline } from "../chatCore/providerExecutionPipeline.ts"; +import { runNonStreamingProviderLeg } from "../chatCore/nonStreamingProviderLeg.ts"; +import type { NonStreamingProviderLegResult } from "@/lib/skills/toolLoopTypes.ts"; +import { finalizeToolLoopError } from "../chatCore/nonStreamingFinalization.ts"; +import { markCodexScopeRateLimited } from "../chatCore/codexFailover.ts"; +import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; +import { writeTerminalStatus } from "@/shared/utils/terminalStatus"; +import { MEMORY_BUILTIN_TOOL_NAMES } from "@/lib/skills/memoryBuiltins"; + +import { storeSemanticCacheResponse } from "../chatCore/semanticCacheStore.ts"; +import { routingFinishReason } from "../chatCore/routingFinishReason.ts"; +import { getProviderCredentials } from "@/sse/services/auth"; +import { extractFacts } from "@/lib/memory/extraction"; +// The leaf body is unchanged from the barrel, so its closed-over values keep +// the barrel's types. This alias only exists so the deps bag type-checks. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Loose = any; +export type NonStreamingDeps = Record & { [k: string]: Loose }; + +export async function runNonStreamingResponse(deps: NonStreamingDeps) { + const { + apiKeyInfo, + appendRequestLog, + applyProviderFailureClassification, + assertManagedLeaseFence, + attachCompressionUsageReceiptAfterAnalytics, + body, + bodyForCacheWrite, + buildCacheUsageLogMeta, + buildCostCtx, + buildErrorBody, + calculateCost, + calibrationEstimatedInputTokens, + claudePromptCacheLogMeta: _claudePromptCacheLogMeta, + clientRawRequest, + comboStrategy, + connectionId, + copilotCompatibleReasoning, + createErrorResult, + credentials, + currentModel: _currentModel, + describeMalformedNonStream, + detectMalformedNonStream, + echoModel, + echoModelInObject, + effectiveModel, + effectiveServiceTier: _effectiveServiceTier, + emitRequestGamificationEvent, + endpointPath, + executeProviderRequest, + executeRefreshCredentials, + extractSessionAffinityKey, + extractUsageFromResponse, + finalBody: _finalBody, + finalizePendingScope, + getCurrentConnectionId, + getManagedLeaseFenceErrorCode, + getModelNormalizeToolCallId, + getModelPreserveOpenAIDeveloperRole, + getSafeErrorMetadata, + getSkillsModelIdForFormat, + guardrailRegistry, + handleCredentialsRefreshed, + handleToolCallExecution, + idempotencyKey, + incrementTokenUsage, + injectionResult, + isCombo, + isJsonRecord, + isLocalStreamLifecycleError, + isSemaphoreCapacityError, + isServerOwnedToolLoopEnabled, + log, + logAuditEvent, + managedLease, + managedLeaseFenceErrorResult, + markAccountSemaphoreBlocked, + memoryOwnerId, + memorySettings, + model, + normalizeHeaders, + normalizeUsage, + onRequestSuccess, + pendingConnId, + pendingRequestId, + pendingScope, + persistAttemptLogs, + persistFailureUsage, + pipelineSessionId, + provider, + providerHeaders: _providerHeaders, + providerRequestCapture, + providerResponse: _providerResponse, + reasoningReplayHistory: _reasoningReplayHistory, + recordChatCallCost, + recordContextEditingTelemetryHook, + recordCoreOwnedAntigravityQuotaState, + recordNonStreamingUsageStats, + reportMalformed200, + reqLogger, + resolveReportedServiceTier, + runMemoryExtractionGate, + runPluginOnResponseHook, + sanitizeErrorMessage, + sanitizeUpstreamDetails, + saveIdempotency, + scheduleQuotaShareConsumption, + semanticCacheEnabled, + sessionAffinityKey, + shouldIsolateProbeFailures, + skillRequestId, + sourceFormat, + startTime, + targetFormat, + traceId, + syncExecuteTranslatedBody, + trackPendingRequest, + translateRequest, + translatedBody: _translatedBody, + triedModels, + updateFromHeaders, + updateFromResponseBody, + updatePendingScope, + updateProviderConnection, + webFetchFallbackPlan, + webSearchFallbackPlan, + clientRequestedResponsesStream, + clientResponseFormat, + compressionResponseMeta, + contextEditingEnabled, + customToolNames, + fallbackAttempts, + isClaudeCodeCompatible, + isResponsesEndpoint, + preserveCacheControl, + reasoningCacheScope, + requestToolIdentityMap, + stream, + toolNameMap, + traceEnabled, + videoBridgeObserved, + } = deps; + + let claudePromptCacheLogMeta, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + reasoningReplayHistory, + translatedBody; + claudePromptCacheLogMeta = _claudePromptCacheLogMeta; + currentModel = _currentModel; + effectiveServiceTier = _effectiveServiceTier; + finalBody = _finalBody; + providerHeaders = _providerHeaders; + providerResponse = _providerResponse; + reasoningReplayHistory = _reasoningReplayHistory; + translatedBody = _translatedBody; + pipelineRecovered = false; + + try { + const runNonStreamingPipeline = async ({ + policy, + model: pipelineModel, + translatedBody: wireBody, + }) => { + translatedBody = wireBody as typeof translatedBody; + syncExecuteTranslatedBody(translatedBody); + currentModel = pipelineModel; + triedModels.add(pipelineModel); + return runProviderExecutionPipeline({ + policy, + target: { + provider, + requestedModel: pipelineModel, + sourceFormat, + targetFormat, + stream: false, + }, + connection: { + initialConnectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + getCredentials: () => (credentials || {}) as Record, + replaceCredentials: (next) => { + Object.assign(credentials, next); + }, + onCredentialsRefreshed: handleCredentialsRefreshed, + refreshCredentials: executeRefreshCredentials, + assertManagedLeaseFence: (id) => { + assertManagedLeaseFence(id); + }, + getProviderCredentials, + }, + wire: { + body: translatedBody as Record, + currentModel, + triedModels, + setBodyAndModel: (nextBody, nextModel) => { + translatedBody = nextBody as typeof translatedBody; + syncExecuteTranslatedBody(translatedBody); + currentModel = nextModel; + triedModels.add(nextModel); + }, + }, + state: { + updatePendingStage: (stage, data) => { + updatePendingScope(pendingScope, { stage, ...(data || {}) }); + }, + recordRateLimitHeaders: updateFromHeaders, + recordRateLimitBody: updateFromResponseBody, + writeTerminalStatus, + persistConnectionPatch: updateProviderConnection, + setConnectionRateLimitedUntil: async (id, untilMs) => { + const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); + setConnectionRateLimitUntil(id, untilMs); + }, + lockModel, + recordAntigravityQuotaState: recordCoreOwnedAntigravityQuotaState, + markAccountSemaphoreBlocked: (key) => { + markAccountSemaphoreBlocked(key, Date.now() + 60_000); + }, + isolateProbeFailures: () => shouldIsolateProbeFailures(), + onCodexScopeRateLimited: async (params) => { + await markCodexScopeRateLimited({ + failedConnectionId: params.failedConnectionId, + model: params.model, + rateLimitedUntil: params.rateLimitedUntil, + credentials: (params.credentials || credentials) as { + connectionId?: string | null; + providerSpecificData?: unknown; + }, + }); + }, + onClearSessionAffinity: () => { + const key = + sessionAffinityKey || + extractSessionAffinityKey(body, clientRawRequest?.headers) || + null; + if (!key) return; + try { + deleteSessionAccountAffinity(key, "codex"); + } catch { + // best-effort + } + }, + onAuditAccountRotation: (params) => { + logAuditEvent({ + action: params.action, + actor: apiKeyInfo?.name || "system", + target: params.newConnectionId, + details: { + failed_connection_id: params.failedConnectionId, + new_connection_id: params.newConnectionId, + attempt: params.attempt, + retry_after_ms: params.retryAfterMs, + }, + }); + }, + }, + sendProviderAttempt: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + }); + }; + + let toolLoopRan = false; + let toolLoopUsage = null; + let legResult = await runNonStreamingProviderLeg({ + phase: "initial", + sourceBody: (body || {}) as Record, + expectedConnectionId: managedLease + ? String(getCurrentConnectionId() || connectionId || "") || undefined + : undefined, + allowAccountRotation: !managedLease && comboStrategy !== "context-relay", + allowModelFallback: true, + executeProviderRequest: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + runProviderExecution: runNonStreamingPipeline, + setRequestWireState: ({ translatedBody: nextBody, effectiveModel: nextModel }) => { + translatedBody = nextBody as typeof translatedBody; + syncExecuteTranslatedBody(translatedBody); + currentModel = nextModel; + triedModels.add(nextModel); + }, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + model: effectiveModel, + connectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + effectiveModel: currentModel, + translatedBody: translatedBody as Record, + toolNameMap, + customToolNames, + requestToolIdentityMap, + reasoningCacheScope, + reasoningReplayHistory, + videoTranscriptSensitive: videoBridgeObserved, + clientHeaders: clientRawRequest?.headers ?? null, + isClaudeCodeCompatible, + log, + }); + + if (legResult.kind === "error") { + const err = legResult.result; + const errMessage = + err?.rawMessage || + (err?.originalError instanceof Error ? err.originalError.message : err?.error) || + ""; + const errHeaders = err?.upstreamHeaders || err?.response?.headers; + const errUpstreamBody = err?.upstreamErrorBody; + if (err) { + await applyProviderFailureClassification({ + statusCode: err.status, + message: errMessage, + headers: errHeaders, + upstreamErrorBody: errUpstreamBody, + retryAfterMs: err.retryAfterMs ?? null, + targetModel: currentModel, + }); + } + + const captured = providerRequestCapture.latest?.() ?? null; + finalBody = captured?.body ?? finalBody ?? translatedBody; + if (captured) { + reqLogger.logTargetRequest(captured.url, captured.headers, captured.body); + } + reqLogger.logError(new Error(err.error || "Provider request failed"), finalBody); + const isNetworkThrow = Boolean(err.originalError); + if (err.response && !isNetworkThrow) { + reqLogger.logProviderResponse( + err.status, + err.response.statusText || "Error", + err.response.headers, + err.response + ); + } + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${err.status}`, + }).catch(() => {}); + persistAttemptLogs({ + status: err.status, + error: err.error || "Provider request failed", + providerRequest: finalBody || translatedBody, + providerResponse: isNetworkThrow ? undefined : err.response, + // On a client abort the client already disconnected before we got here, so this + // body is what we WOULD have sent, not what was delivered. The dashboard reads + // `clientResponse` as "what the client received", so logging it misleads — + // `error` above already records the reason. The pre-#12867 path omitted it here; + // the leg-based path must keep doing so. + clientResponse: isLocalStreamLifecycleError(err.originalError) + ? undefined + : buildErrorBody(err.status, err.error || "Provider request failed"), + cacheSource: "upstream", + }); + persistFailureUsage(err.status, err.errorCode || `upstream_${err.status}`); + trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); + return { + response: err, + carry: { + translatedBody, + currentModel, + finalBody, + providerResponse, + providerHeaders, + effectiveServiceTier, + claudePromptCacheLogMeta, + reasoningReplayHistory, + pipelineRecovered, + }, + }; + } + + pipelineRecovered = true; + const expectedConn = managedLease + ? String(getCurrentConnectionId() || connectionId || "") || undefined + : undefined; + // The identity is the tool loop's execution fence key, and deriveToolRequestIdentity + // canonicalizes the body — which by design rejects Dates, Maps and class instances. + // It was computed eagerly, so a body carrying any of those threw on EVERY + // non-streaming request even with SERVER_OWNED_TOOL_LOOP_ENABLED off (the default). + // Derive it only when the loop can run, and fail closed rather than crash: no + // identity means no fence, and without a fence the loop must not run. + let toolLoopEnabled = isServerOwnedToolLoopEnabled(); + let postInjectionRequestIdentity = ""; + if (toolLoopEnabled) { + try { + postInjectionRequestIdentity = derivePostInjectionRequestIdentity({ + apiKeyId: memoryOwnerId || "local", + headers: clientRawRequest?.headers ?? null, + skillRequestId, + postInjectionBody: (body || {}) as Record, + }); + } catch (identityError) { + log?.warn?.( + "SERVER_OWNED_TOOL_LOOP", + `request body is not canonicalizable, skipping the loop: ${ + identityError instanceof Error ? identityError.message : "unknown" + }` + ); + toolLoopEnabled = false; + } + } + const loopApply = await applyServerOwnedToolLoopIfNeeded({ + enabled: toolLoopEnabled, + stream, + isResponsesEndpoint, + sourceFormat, + initialLeg: legResult, + sourceBody: (body || {}) as Record, + skillsModelId: getSkillsModelIdForFormat(sourceFormat), + executionContext: { + apiKeyId: memoryOwnerId || "local", + sessionId: pipelineSessionId, + requestId: skillRequestId, + requestIdentity: postInjectionRequestIdentity, + builtinToolNames: injectionResult.builtinToolNames, + injectedCustomSkillNames: injectionResult.injectedCustomSkillNames, + customSkillExecutionEnabled: + Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true, + executionFenceEnabled: true, + provider, + model: effectiveModel, + }, + abortSignal: clientRawRequest?.signal, + expectedConnectionId: expectedConn, + followUpLeg: async (nextSourceBody) => { + translatedBody = translateRequest( + sourceFormat, + targetFormat, + model, + { ...nextSourceBody }, + false, + credentials, + provider, + reqLogger, + { + normalizeToolCallId: getModelNormalizeToolCallId( + provider || "", + model || "", + sourceFormat + ), + preserveDeveloperRole: getModelPreserveOpenAIDeveloperRole( + provider || "", + model || "", + sourceFormat + ), + preserveCacheControl, + signatureNamespace: connectionId, + copilotClient: copilotCompatibleReasoning, + reasoningCacheScope, + onReasoningReplayHistory: (messages) => { + reasoningReplayHistory = messages; + }, + } + ); + syncExecuteTranslatedBody(translatedBody); + return runNonStreamingProviderLeg( + followUpLegInput( + { + executeProviderRequest: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + runProviderExecution: runNonStreamingPipeline, + setRequestWireState: ({ translatedBody: nextBody, effectiveModel: nextModel }) => { + translatedBody = nextBody as typeof translatedBody; + syncExecuteTranslatedBody(translatedBody); + currentModel = nextModel; + triedModels.add(nextModel); + }, + sourceFormat, + targetFormat, + clientResponseFormat, + provider, + model: effectiveModel, + connectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + effectiveModel: currentModel, + translatedBody: translatedBody as Record, + toolNameMap, + customToolNames, + requestToolIdentityMap, + reasoningCacheScope, + reasoningReplayHistory, + videoTranscriptSensitive: videoBridgeObserved, + clientHeaders: clientRawRequest?.headers ?? null, + isClaudeCodeCompatible, + log, + }, + nextSourceBody, + expectedConn + ) + ); + }, + logReceipt: (receipt) => reqLogger.logToolLoopReceipt(receipt), + }); + if (loopApply.kind === "error") { + return { + response: await finalizeToolLoopError({ + loop: loopApply.loop, + model, + provider, + connectionId: pendingConnId, + providerRequest: loopApply.loop.finalProviderRequest || finalBody || translatedBody, + persistFailureUsage, + persistAttemptLogs, + trackPendingRequest, + pendingRequestId, + }), + carry: { + translatedBody, + currentModel, + finalBody, + providerResponse, + providerHeaders, + effectiveServiceTier, + claudePromptCacheLogMeta, + reasoningReplayHistory, + pipelineRecovered, + }, + }; + } + // `legResult` is declared as the full NonStreamingProviderLegResult union. The + // `kind === "error"` guard above narrows it to the ok variant, but the conditional + // reassignment below widens it back to the declared type, so every field read past + // this point lost the narrowing — 13 TS2339 diagnostics under + // tsconfig.typecheck-api.json, which pulls chatCore.ts in through the route while + // tsconfig.typecheck-core.json does not. Pin the ok variant in its own binding: + // `loopApply.leg` is already `NonStreamingProviderLegResult & { kind: "ok" }`, + // so no cast is involved. + let okLeg: NonStreamingProviderLegResult & { kind: "ok" } = legResult; + if (loopApply.kind === "ok") { + toolLoopRan = true; + toolLoopUsage = loopApply.usage; + okLeg = loopApply.leg; + } + + if (okLeg.upstreamResponse) { + providerResponse = okLeg.upstreamResponse; + providerHeaders = normalizeHeaders(okLeg.upstreamResponse.headers); + } else { + providerResponse = new Response(null, { + status: 200, + headers: okLeg.headers, + }); + providerHeaders = normalizeHeaders(okLeg.headers); + } + finalBody = providerRequestCapture.body(okLeg.providerRequest || translatedBody); + // Built inside executeProviderRequest on the pre-#12867 path. The leg now owns the + // first non-streaming send, so that assignment never runs here and the meta stayed + // null — `_omniroute.claudePromptCache` silently vanished from every call log on + // this path. Same inputs, same helper, at the point where they are available. + claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta( + targetFormat, + finalBody, + providerHeaders, + clientRawRequest?.headers + ); + const capturedOk = providerRequestCapture.latest?.(); + reqLogger.logTargetRequest( + okLeg.requestUrl || capturedOk?.url || "", + okLeg.requestHeaders || capturedOk?.headers || {}, + capturedOk?.body ?? finalBody + ); + const responseBody = okLeg.providerBody; + const responsePayloadFormat = okLeg.responsePayloadFormat; + const looksLikeSSE = okLeg.looksLikeSSE; + let translatedResponse = okLeg.response; + const memoryExtractionResponse = okLeg.responseForMemoryExtraction; + reqLogger.logProviderResponse( + 200, + "OK", + providerResponse.headers, + looksLikeSSE ? { _streamed: true, _format: "sse-json", summary: responseBody } : responseBody + ); + effectiveServiceTier = resolveReportedServiceTier(responseBody) ?? effectiveServiceTier; + if (onRequestSuccess) { + await onRequestSuccess(); + } + const successConnectionId = getCurrentConnectionId(); + await maybeSyncClaudeExtraUsageState({ + provider, + connectionId: successConnectionId, + providerSpecificData: credentials?.providerSpecificData, + log, + }); + const usage = toolLoopUsage ?? extractUsageFromResponse(responseBody, provider); + recordFinalInputCalibration( + body, + provider, + effectiveModel, + calibrationEstimatedInputTokens, + usage, + toolLoopUsage != null + ); + const cacheUsageLogMeta = buildCacheUsageLogMeta(usage); + if (usage && typeof usage === "object") { + attachCompressionUsageReceiptAfterAnalytics(usage as Record, "provider"); + if (provider === "gemini") { + const promptTokens = + typeof (usage as Record).prompt_tokens === "number" + ? ((usage as Record).prompt_tokens as number) + : 0; + if (promptTokens > 0) incrementTokenUsage(model, promptTokens); + } + } + recordContextEditingTelemetryHook({ + contextEditingEnabled, + provider, + responseBody, + skillRequestId, + log, + }); + appendRequestLog({ + model, + provider, + connectionId: successConnectionId, + tokens: usage, + status: "200 OK", + }).catch(() => {}); + recordNonStreamingUsageStats(usage, { + traceEnabled, + provider, + connectionId: successConnectionId, + model, + startTime, + apiKeyInfo, + effectiveServiceTier, + isCombo, + comboStrategy, + endpoint: endpointPath, + cpaAuthIndex: readCpaAuthIndex(providerResponse), + }); + + // #12150 P1b surface 3 (fix round 1): a video-bridge-observed request's + // request- AND response-derived text both carry the full transcript (the + // flattened description on the request side, the model's own reply on + // the response side) — neither may populate durable Memory. See + // runMemoryExtractionGate for the shared gate + extraction wiring, unit + // tested directly in tests/unit/video-bridge-memory-suppression.test.ts. + runMemoryExtractionGate({ + memoryOwnerId, + memorySettings, + videoBridgeObserved, + pipelineSessionId, + requestBody: body as Record, + responseBody: memoryExtractionResponse as Record | null, + extractFacts, + log, + }); + + const customSkillExecutionEnabled = + Boolean(memoryOwnerId) && memorySettings?.skillsEnabled === true; + const builtinToolNames = [ + webSearchFallbackPlan.toolName, + webFetchFallbackPlan.toolName, + ...(memoryOwnerId && memorySettings?.enabled ? MEMORY_BUILTIN_TOOL_NAMES : []), + ].filter((name): name is string => Boolean(name)); + if (!toolLoopRan && (customSkillExecutionEnabled || builtinToolNames.length > 0)) { + const skillSessionId = pipelineSessionId; + + translatedResponse = await handleToolCallExecution( + translatedResponse, + getSkillsModelIdForFormat(sourceFormat), + { + apiKeyId: memoryOwnerId || "local", + sessionId: skillSessionId, + requestId: skillRequestId, + builtinToolNames, + customSkillExecutionEnabled, + provider, + model: effectiveModel, + } + ); + } + + const guardrailContext = buildPostCallGuardrailContext({ + apiKeyInfo, + body, + clientRawRequest, + log, + model, + provider, + responsePayloadFormat, + clientResponseFormat, + }); + const postCallGuardrails = await guardrailRegistry.runPostCallHooks( + translatedResponse, + guardrailContext + ); + translatedResponse = postCallGuardrails.response; + + const responseUsage = isJsonRecord(usage) + ? usage + : isJsonRecord(translatedResponse.usage) + ? translatedResponse.usage + : null; + const costUsage = normalizeUsage(responseUsage); + const estimatedCost = costUsage + ? await calculateCost(provider, model, costUsage, { serviceTier: effectiveServiceTier }) + : 0; + const chatCostCtx = buildCostCtx(provider, model, usage, effectiveServiceTier, traceId); + + if (postCallGuardrails.blocked) { + const guardrailMessage = postCallGuardrails.message || "Response blocked by guardrail"; + persistAttemptLogs({ + status: HTTP_STATUS.BAD_REQUEST, + tokens: usage, + responseBody, + providerRequest: finalBody || translatedBody, + providerResponse: looksLikeSSE + ? { + _streamed: true, + _format: "sse-json", + summary: responseBody, + } + : responseBody, + clientResponse: buildErrorBody(HTTP_STATUS.BAD_REQUEST, guardrailMessage), + claudeCacheMeta: claudePromptCacheLogMeta, + claudeCacheUsageMeta: cacheUsageLogMeta, + cacheSource: "upstream", + }); + recordChatCallCost( + apiKeyInfo, + meteredBudgetCost(provider, estimatedCost), + chatCostCtx, + false + ); + log?.warn?.( + "GUARDRAIL", + `Response blocked by ${postCallGuardrails.guardrail || "guardrail"}: ${guardrailMessage}` + ); + finalizePendingScope(pendingScope, { + providerResponse: responseBody, + clientResponse: translatedResponse, + }); + return { + response: createErrorResult(HTTP_STATUS.BAD_REQUEST, guardrailMessage), + carry: { + translatedBody, + currentModel, + finalBody, + providerResponse, + providerHeaders, + effectiveServiceTier, + claudePromptCacheLogMeta, + reasoningReplayHistory, + pipelineRecovered, + }, + }; + } + + // Validate the *translated* response actually carries client-usable output. + // isEmptyContentResponse (above) runs on the raw responseBody before translation; + // this check runs after translation + sanitization + tool-call execution to catch + // cases where a provider returns a structurally valid raw body that translates into + // choices:[] or output:[] with no usable content (Responses API shape included). + const malformedTranslatedReason = detectMalformedNonStream(translatedResponse, provider); + if (malformedTranslatedReason) { + const totalLatency = Date.now() - startTime; + const rawBytes = (() => { + try { + return JSON.stringify(responseBody || {}).length; + } catch { + return -1; + } + })(); + reportMalformed200({ + mode: "nonstream", + provider, + model, + connectionId, + reason: malformedTranslatedReason, + recvBytes: rawBytes, + recvLines: -1, + emitted: -1, + events: {}, + ttftMs: totalLatency, + elapsedMs: totalLatency, + }); + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${HTTP_STATUS.BAD_GATEWAY}`, + }).catch(() => {}); + const malformed = describeMalformedNonStream(translatedResponse, malformedTranslatedReason); + const malformedMessage = `[${provider}/${model}] ${malformed.message}`; + const malformedClientBody = buildErrorBody( + HTTP_STATUS.BAD_GATEWAY, + malformedMessage, + undefined, + { code: malformed.code, type: malformed.type } + ); + const sanitizedMalformedResponse = sanitizeUpstreamDetails(responseBody); + const sanitizedMalformedProviderResponse = looksLikeSSE + ? { _streamed: true, _format: "sse-json", summary: sanitizedMalformedResponse } + : sanitizedMalformedResponse; + persistAttemptLogs({ + status: HTTP_STATUS.BAD_GATEWAY, + tokens: usage, + responseBody: sanitizedMalformedResponse, + providerRequest: finalBody || translatedBody, + providerResponse: sanitizedMalformedProviderResponse, + clientResponse: malformedClientBody, + claudeCacheMeta: claudePromptCacheLogMeta, + claudeCacheUsageMeta: cacheUsageLogMeta, + cacheSource: "upstream", + }); + persistFailureUsage(HTTP_STATUS.BAD_GATEWAY, "malformed_translated_response"); + trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); + // Routing event (feedback foundation) — record the malformed outcome so + // the quality tracker de-prioritizes this model over time. + void emitRoutingEvent( + createRoutingEvent({ + requestId: traceId || pendingRequestId || "unknown", + provider: provider || "unknown", + model: model || "unknown", + strategy: isCombo ? (comboStrategy ?? "combo") : "direct", + latencyMs: Date.now() - startTime, + ttftMs: null, + inputTokens: null, + outputTokens: null, + cost: null, + retries: 0, + fallbackUsed: false, // combo-level fallback tracked by decisionTrace + outcome: "malformed", + status: HTTP_STATUS.BAD_GATEWAY, + finishReason: routingFinishReason(translatedResponse), + connectionId: credentials?.connectionId ?? null, + }) + ); + return { + response: createErrorResult( + HTTP_STATUS.BAD_GATEWAY, + malformedMessage, + null, + malformed.code, + malformed.type + ), + carry: { + translatedBody, + currentModel, + finalBody, + providerResponse, + providerHeaders, + effectiveServiceTier, + claudePromptCacheLogMeta, + reasoningReplayHistory, + pipelineRecovered, + }, + }; + } + + // ── Phase 9.1: Cache store (non-streaming, temp=0) ── + storeSemanticCacheResponse({ + enabled: semanticCacheEnabled, + body: bodyForCacheWrite, + headers: clientRawRequest?.headers, + translatedResponse, + model, + // The dual-layer manager scopes entries per provider (cacheByProvider); + // lookup passes the resolved provider, so the write must too (#14159). + provider, + apiKeyId: apiKeyInfo?.id ?? undefined, + usage, + log, + videoTranscriptSensitive: videoBridgeObserved, + }); + + // ── Phase 9.2: Save for idempotency ── + // Reuse the key resolved by checkIdempotencyCache() above (single derivation per + // request). (#3821-review LEDGER-6) + saveIdempotency(idempotencyKey, translatedResponse, 200); + reqLogger.logConvertedResponse(translatedResponse); + persistAttemptLogs({ + status: 200, + tokens: usage, + responseBody, + providerRequest: finalBody || translatedBody, + providerResponse: looksLikeSSE + ? { + _streamed: true, + _format: "sse-json", + summary: responseBody, + } + : responseBody, + clientResponse: translatedResponse, + claudeCacheMeta: claudePromptCacheLogMeta, + claudeCacheUsageMeta: cacheUsageLogMeta, + cacheSource: "upstream", + }); + recordChatCallCost(apiKeyInfo, meteredBudgetCost(provider, estimatedCost), chatCostCtx, true); + + // === Quota Share POST-hook (B/F7) — fire-and-forget, fail-open === + await scheduleQuotaShareConsumption({ + apiKeyId: apiKeyInfo?.id, + connectionId: credentials?.connectionId, + provider, + model, + usage, + estimatedCost, + log, + }); + // === /Quota Share POST-hook === + + // ── Gamification event (fire-and-forget) ── + await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider }); + + finalizePendingScope(pendingScope, { + providerResponse: responseBody, + clientResponse: translatedResponse, + }); + const responseHeaders = buildNonStreamingResponseHeaders({ + provider, + model, + startTime, + responseUsage, + estimatedCost, + requestId: skillRequestId, + compressionResponseMeta, + comboStrategy, + fallbackAttempts, + }); + // #6426: align response body `model` with the `X-OmniRoute-Model` header + // (both must be the resolved backend model). Some upstreams (notably legacy + // /v1/completions text-completion path) return a body `model` field that + // differs from the resolved backend id we advertised in the header, leaving + // strict clients unable to reconcile the two. Rewrite body.model to `model` + // FIRST, then let #1311 echo override it when the opt-in setting is on. + if (typeof model === "string" && model) echoModelInObject(translatedResponse, model); + // #1311: echo the requested alias/combo name in the non-streaming response model. + if (echoModel) echoModelInObject(translatedResponse, echoModel); + + // ── Plugin onResponse hook (fire-and-forget) ── + // #8395: the streaming branch below already calls this; the non-streaming + // (stream:false) branch returned without it, so onResponse never fired for + // non-streaming requests at all. + await runPluginOnResponseHook({ + requestId: traceId, + body, + model, + provider, + apiKeyInfo, + headers: clientRawRequest?.headers, + response: { status: 200, data: translatedResponse }, + }); + + // Routing event (feedback foundation) — fire-and-forget, cheap. + void emitRoutingEvent( + createRoutingEvent({ + requestId: traceId || pendingRequestId || "unknown", + provider: provider || "unknown", + model: model || "unknown", + strategy: isCombo ? (comboStrategy ?? "combo") : "direct", + latencyMs: Date.now() - startTime, + ttftMs: null, + inputTokens: + usage && typeof usage === "object" + ? (() => { + const promptTokens = (usage as Record).prompt_tokens; + return typeof promptTokens === "number" && Number.isFinite(promptTokens) + ? promptTokens + : null; + })() + : null, + outputTokens: + usage && typeof usage === "object" + ? (() => { + const completionTokens = (usage as Record).completion_tokens; + return typeof completionTokens === "number" && Number.isFinite(completionTokens) + ? completionTokens + : null; + })() + : null, + cost: Number.isFinite(estimatedCost) ? estimatedCost : null, + retries: 0, + fallbackUsed: false, // combo-level fallback tracked by decisionTrace + outcome: "success", + status: 200, + finishReason: routingFinishReason(translatedResponse), + connectionId: credentials?.connectionId ?? null, + }) + ); + + return { + response: { + success: true, + response: maybeWrapForcedNonStreamingResponsesJson({ + clientRequestedResponsesStream, + body: translatedResponse, + headers: responseHeaders, + }), + }, + carry: { + translatedBody, + currentModel, + finalBody, + providerResponse, + providerHeaders, + effectiveServiceTier, + claudePromptCacheLogMeta, + reasoningReplayHistory, + pipelineRecovered, + }, + }; + } catch (error) { + trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); + const errorMetadata = getSafeErrorMetadata(error); + const managedLeaseFenceCode = getManagedLeaseFenceErrorCode(errorMetadata.code); + if (managedLeaseFenceCode) + return { + response: managedLeaseFenceErrorResult(managedLeaseFenceCode), + carry: { + translatedBody, + currentModel, + finalBody, + providerResponse, + providerHeaders, + effectiveServiceTier, + claudePromptCacheLogMeta, + reasoningReplayHistory, + pipelineRecovered, + }, + }; + // isSemaphoreCapacityError already reads the code through getSafeErrorMetadata, + // so a hostile rejection cannot escape this classification. + if (isSemaphoreCapacityError(error)) { + const semaphoreCode = errorMetadata.code as string; + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${semaphoreCode}`, + }).catch(() => {}); + const failureMessage = sanitizeErrorMessage(errorMetadata.message) || "Semaphore timeout"; + persistAttemptLogs({ + status: HTTP_STATUS.RATE_LIMITED, + error: failureMessage, + providerRequest: finalBody || translatedBody, + clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage), + claudeCacheMeta: claudePromptCacheLogMeta, + cacheSource: "upstream", + }); + persistFailureUsage(HTTP_STATUS.RATE_LIMITED, semaphoreCode); + const result = createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage); + return { + response: { + ...result, + errorType: "account_semaphore_capacity", + errorCode: semaphoreCode, + }, + carry: { + translatedBody, + currentModel, + finalBody, + providerResponse, + providerHeaders, + effectiveServiceTier, + claudePromptCacheLogMeta, + reasoningReplayHistory, + pipelineRecovered, + }, + }; + } + throw error; + } +} diff --git a/open-sse/handlers/chatCore/streamingResponse.ts b/open-sse/handlers/chatCore/streamingResponse.ts new file mode 100644 index 000000000000..ea758940b3ba --- /dev/null +++ b/open-sse/handlers/chatCore/streamingResponse.ts @@ -0,0 +1,1253 @@ +import { projectFailureUsageErrorCode } from "./failureUsage.ts"; + +export { extractSystemRoleMessages, relocateDirectiveOnlyMessages } from "./claudeSystemRole.ts"; + +export { clearCombosCache, clearUpstreamProxyConfigCache } from "./comboContextCache.ts"; +import { buildClaudePromptCacheLogMeta } from "./executorHelpers.ts"; +import { + shouldUseNativeCodexPassthrough, + shouldUseNativeXaiResponsesPassthrough, + redactPassthroughThinkingSignatures, + isClaudeCodeSemanticPassthroughRequest, +} from "./passthroughHelpers.ts"; +import { recoverAnthropicThinkingSignature } from "../chatCore/thinkingSignatureRecovery.ts"; +import { runProviderExecutionPipeline } from "../chatCore/providerExecutionPipeline.ts"; +import { markCodexScopeRateLimited } from "../chatCore/codexFailover.ts"; +import { deleteSessionAccountAffinity } from "@/lib/db/sessionAccountAffinity"; +import { buildStreamingResponseHeaders, stripStaleForwardingHeaders } from "./responseHeaders.ts"; + +// Re-export the previously inline-defined helpers so existing importers of these +// symbols from chatCore.ts (tests, sibling modules) keep resolving after the split. +export { + shouldUseNativeCodexPassthrough, + shouldUseNativeXaiResponsesPassthrough, + redactPassthroughThinkingSignatures, + isClaudeCodeSemanticPassthroughRequest, + buildStreamingResponseHeaders, + stripStaleForwardingHeaders, +}; + +import { normalizeHeaders } from "../../utils/headers.ts"; + +import { FORMATS } from "../../translator/formats.ts"; + +import { COLORS } from "../../utils/stream.ts"; + +import { + refreshWithRetry, + isUnrecoverableRefreshError, + runWithOnPersist, + runWithCasGuard, +} from "../../services/tokenRefresh.ts"; + +import { runWithCapture } from "../../utils/providerRequestLogging.ts"; + +import { shouldSkipCredentialRefresh } from "../chatCore/skipCredentialRefresh.ts"; + +import { + REASONING_BUFFER_MIN_TRIGGER, + buildReasoningProbeTruncatedResponse, + isEmptyContentUpstreamFailure, + isTinyBudgetReasoningProbe, + toPositiveInteger, +} from "../../services/reasoningTokenBuffer.ts"; +import { + buildErrorBody, + createErrorResult, + parseUpstreamError, + formatProviderError, + projectPublicErrorIdentifier, + sanitizeErrorMessage, + sanitizeUpstreamDetails, +} from "../../utils/error.ts"; + +import { HTTP_STATUS, ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE } from "../../config/constants.ts"; +import { applyStatusRestatement } from "../../config/upstreamStatusRestatement.ts"; + +import { updateProviderConnection, getProviderConnectionById } from "@/lib/db/providers"; +import { wasRefreshTokenRotated } from "@omniroute/open-sse/services/refreshSerializer.ts"; + +import { + createSafeAbortError, + createStreamingErrorResult, + isSemaphoreCapacityError, + getSafeErrorMetadata, + getUpstreamErrorIdentifier, +} from "./streamErrorResult.ts"; + +import { getExecutionConnectionId } from "../chatCore/executionCredentials.ts"; + +import { prepareUpstreamBody } from "../chatCore/upstreamBody.ts"; + +import { logAuditEvent } from "@/lib/compliance"; + +import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb"; +import { updatePendingScope } from "@/lib/usage/pendingRequestScope"; + +import { normalizeExecutorResult } from "./upstreamTimeouts.ts"; + +import { getProviderCredentials, extractSessionAffinityKey } from "@/sse/services/auth"; + +import { updateFromHeaders, updateFromResponseBody } from "../../services/rateLimitManager.ts"; +import * as localLimiterErrors from "../../services/rateLimitManager/errors.ts"; +import { markBlocked as markAccountSemaphoreBlocked } from "../../services/accountSemaphore.ts"; +import { lockModel, recordCoreOwnedAntigravityQuotaState } from "../../services/accountFallback.ts"; + +import { + isModelUnavailableError, + getNextFamilyFallback, + isContextOverflowError, + findLargerContextModel, + getModelFamily, +} from "../../services/modelFamilyFallback.ts"; + +import { isLocalStreamLifecycleError } from "@/shared/utils/circuitBreaker"; +import { shouldIsolateProbeFailures } from "@/shared/utils/probeOrigin"; +import { writeTerminalStatus } from "@/shared/utils/terminalStatus"; + +/** + * Core chat handler - shared between SSE and Worker + * Returns { success, response, status, error } for caller to handle fallback + * @param {object} options + * @param {object} options.body - Request body + * @param {object} options.modelInfo - { provider, model } + * @param {object} options.credentials - Provider credentials + * @param {object} options.log - Logger instance (optional) + * @param {function} options.onCredentialsRefreshed - Callback when credentials are refreshed + * @param {function} options.onRequestSuccess - Callback when request succeeds (to clear error status) + * @param {function} options.onDisconnect - Callback when client disconnects + * @param {string} options.connectionId - Connection ID for usage tracking + * @param {object} options.apiKeyInfo - API key metadata for usage attribution + * @param {string} options.comboName - Combo name if this is a combo request + * @param {string} options.comboStrategy - Combo routing strategy (e.g., 'priority', 'cost-optimized') + * @param {boolean} options.isCombo - Whether this request is from a combo + * @param {string} options.connectionId - Connection ID for settings lookup + */ +// extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported above so +// existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here. + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- deps bag mirrors the barrel closure +type Loose = any; + +export type StreamingDeps = Record; + +export async function runStreamingResponse(deps: StreamingDeps) { + const { + apiKeyInfo, + persistAttemptLogs, + buildUpstreamHeadersForExecute, + resolveEffectiveServiceTier, + clientResponseFormat, + correlationId, + extendedContext, + executeProviderRequest, + applyProviderFailureClassification, + assertManagedLeaseFence, + body, + clientRawRequest, + comboStrategy, + connectionId, + contextEditingEnabled, + credentials, + effectiveModel, + executeRefreshCredentials, + executor, + getCurrentConnectionId, + getExecutionCredentials, + getExecutorClientHeaders, + getManagedLeaseFenceErrorCode, + handleCredentialsRefreshed, + isCombo, + isOpencodeClient, + log, + managedLease, + managedLeaseFenceErrorResult, + model, + onCredentialsRefreshed, + pendingConnId, + pendingRequestId, + pendingScope, + persistFailureUsage, + provider, + providerRequestCapture, + reqLogger, + resilienceSettings, + sessionAffinityKey, + skillRequestId, + sourceFormat, + stream, + streamController, + syncExecuteTranslatedBody, + targetFormat, + triedModels, + trustedEffortContext, + upstreamStream, + } = deps; + + let claudePromptCacheLogMeta, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + translatedBody; + + claudePromptCacheLogMeta = deps.claudePromptCacheLogMeta; + currentModel = deps.currentModel; + effectiveServiceTier = deps.effectiveServiceTier; + finalBody = deps.finalBody; + pipelineRecovered = deps.pipelineRecovered; + void effectiveServiceTier; + providerHeaders = deps.providerHeaders; + providerResponse = deps.providerResponse; + providerUrl = deps.providerUrl; + translatedBody = deps.translatedBody; + try { + const pipelineOutcome = await runProviderExecutionPipeline({ + policy: { + allowAccountRotation: !managedLease && comboStrategy !== "context-relay", + allowModelFallback: true, + expectedConnectionId: managedLease + ? String(getCurrentConnectionId() || connectionId || "") || undefined + : undefined, + }, + target: { + provider, + requestedModel: effectiveModel, + sourceFormat, + targetFormat, + stream, + }, + connection: { + initialConnectionId: String(getCurrentConnectionId() || connectionId || ""), + getCurrentConnectionId: () => getCurrentConnectionId() || undefined, + getCredentials: () => (credentials || {}) as Record, + replaceCredentials: (next) => { + Object.assign(credentials, next); + }, + onCredentialsRefreshed: handleCredentialsRefreshed, + refreshCredentials: executeRefreshCredentials, + assertManagedLeaseFence: (id) => { + assertManagedLeaseFence(id); + }, + getProviderCredentials, + }, + wire: { + body: translatedBody as Record, + currentModel, + triedModels, + setBodyAndModel: (body, model) => { + translatedBody = body as typeof translatedBody; + syncExecuteTranslatedBody(translatedBody); + currentModel = model; + triedModels.add(model); + }, + }, + state: { + updatePendingStage: (stage, data) => { + updatePendingScope(pendingScope, { stage, ...(data || {}) }); + }, + recordRateLimitHeaders: updateFromHeaders, + recordRateLimitBody: updateFromResponseBody, + writeTerminalStatus, + persistConnectionPatch: updateProviderConnection, + setConnectionRateLimitedUntil: async (id, untilMs) => { + const { setConnectionRateLimitUntil } = await import("@/lib/db/providers"); + setConnectionRateLimitUntil(id, untilMs); + }, + lockModel, + recordAntigravityQuotaState: recordCoreOwnedAntigravityQuotaState, + markAccountSemaphoreBlocked: (key) => { + markAccountSemaphoreBlocked(key, Date.now() + 60_000); + }, + isolateProbeFailures: () => shouldIsolateProbeFailures(), + onCodexScopeRateLimited: async (params) => { + await markCodexScopeRateLimited({ + failedConnectionId: params.failedConnectionId, + model: params.model, + rateLimitedUntil: params.rateLimitedUntil, + credentials: (params.credentials || credentials) as { + connectionId?: string | null; + providerSpecificData?: unknown; + }, + }); + }, + onClearSessionAffinity: () => { + const key = + sessionAffinityKey || + extractSessionAffinityKey(body, clientRawRequest?.headers) || + null; + if (!key) return; + try { + deleteSessionAccountAffinity(key, "codex"); + } catch { + // best-effort + } + }, + onAuditAccountRotation: (params) => { + logAuditEvent({ + action: params.action, + actor: apiKeyInfo?.name || "system", + target: params.newConnectionId, + details: { + failed_connection_id: params.failedConnectionId, + new_connection_id: params.newConnectionId, + attempt: params.attempt, + retry_after_ms: params.retryAfterMs, + }, + }); + }, + }, + sendProviderAttempt: (modelToCall, allowDedup) => + executeProviderRequest(modelToCall, allowDedup), + }); + + pipelineRecovered = true; + currentModel = pipelineOutcome.model; + if (pipelineOutcome.kind === "error") { + providerResponse = pipelineOutcome.result.response; + providerUrl = ""; + providerHeaders = normalizeHeaders(pipelineOutcome.result.response.headers); + finalBody = translatedBody; + } else { + const result = { + response: pipelineOutcome.response, + url: pipelineOutcome.url, + headers: pipelineOutcome.headers, + transformedBody: pipelineOutcome.transformedBody, + }; + providerResponse = result.response; + providerUrl = result.url; + providerHeaders = result.headers; + finalBody = providerRequestCapture.body(result.transformedBody); + } + const responseConnectionId = getCurrentConnectionId(); + effectiveServiceTier = resolveEffectiveServiceTier(finalBody); + claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta( + targetFormat, + finalBody, + providerHeaders, + clientRawRequest?.headers + ); + + // Log target request (final request to provider) + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + // Update rate limiter from response headers (learn limits dynamically) + updateFromHeaders( + provider, + responseConnectionId, + providerResponse.headers, + providerResponse.status, + model + ); + + // Store rate-limit headers for quota saturation signals + try { + const { storeRateLimitHeaders } = await import("@/lib/quota/saturationSignals"); + storeRateLimitHeaders( + responseConnectionId, + provider, + providerResponse.headers as Record + ); + } catch { + // fail-open: saturation signal is best-effort + } + } catch (error) { + trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); + const errorMetadata = getSafeErrorMetadata(error); + const managedLeaseFenceCode = getManagedLeaseFenceErrorCode(errorMetadata.code); + if (managedLeaseFenceCode) + return { + response: managedLeaseFenceErrorResult(managedLeaseFenceCode), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + // isSemaphoreCapacityError already reads the code through getSafeErrorMetadata, + // so a hostile rejection cannot escape this classification. + if (isSemaphoreCapacityError(error)) { + const semaphoreCode = errorMetadata.code as string; + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${semaphoreCode}`, + }).catch(() => {}); + const failureMessage = sanitizeErrorMessage(errorMetadata.message) || "Semaphore timeout"; + persistAttemptLogs({ + status: HTTP_STATUS.RATE_LIMITED, + error: failureMessage, + providerRequest: finalBody || translatedBody, + clientResponse: buildErrorBody(HTTP_STATUS.RATE_LIMITED, failureMessage), + claudeCacheMeta: claudePromptCacheLogMeta, + cacheSource: "upstream", + }); + persistFailureUsage(HTTP_STATUS.RATE_LIMITED, semaphoreCode); + const result = stream + ? createStreamingErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage, semaphoreCode) + : createErrorResult(HTTP_STATUS.RATE_LIMITED, failureMessage); + return { + response: { + ...result, + errorType: "account_semaphore_capacity", + errorCode: semaphoreCode, + }, + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + // abort(reason) can reject with a raw string lacking `name`/`status`; classify + // it through isLocalStreamLifecycleError so it maps to 499 rather than the + // 502 provider-failure default. + let isRequestAborted = errorMetadata.name === "AbortError"; + if (!isRequestAborted) { + try { + isRequestAborted = isLocalStreamLifecycleError(error); + } catch { + // A hostile Proxy must not escape the provider-error boundary during classification. + } + } + // #8376: proxyFetch tags unreachable transport failures so they remain + // distinguishable from ordinary provider 5xx responses. + const isProxyUnreachableFailure = + !isRequestAborted && errorMetadata.errorCode === "proxy_unreachable"; + const errorCode = errorMetadata.code; + const localRateLimitFailure = localLimiterErrors.getClientSafeLocalRateLimitError(error); + const failureStatus = isRequestAborted + ? 499 + : isProxyUnreachableFailure + ? HTTP_STATUS.BAD_GATEWAY + : localRateLimitFailure + ? localRateLimitFailure.status + : errorMetadata.name === "TimeoutError" || errorMetadata.name === "BodyTimeoutError" + ? HTTP_STATUS.GATEWAY_TIMEOUT + : errorMetadata.status + ? errorMetadata.status + : HTTP_STATUS.BAD_GATEWAY; + const failureMessage = isRequestAborted + ? "Request aborted" + : (() => { + try { + return formatProviderError( + localRateLimitFailure ?? error, + provider, + model, + failureStatus + ); + } catch { + // Formatting is diagnostic only; hostile rejection metadata falls back safely. + return errorMetadata.message || "Upstream provider error"; + } + })(); + const safeFailureMessage = sanitizeErrorMessage(failureMessage) || "Upstream provider error"; + const upstreamErrorCode = + localRateLimitFailure?.code ?? (isProxyUnreachableFailure ? "proxy_unreachable" : errorCode); + // Tag our own deadline timeouts (fetch-start TimeoutError / body BodyTimeoutError, + // both surfaced as a 504) as "upstream_timeout" so the cooldown layer can tell a + // slow-but-not-failed request apart from a real provider 5xx. (Antigravity already + // tags its pre-response timeout via the code below.) + const isOwnDeadlineTimeout = + failureStatus === HTTP_STATUS.GATEWAY_TIMEOUT && + (errorMetadata.name === "TimeoutError" || errorMetadata.name === "BodyTimeoutError"); + const upstreamErrorType = + upstreamErrorCode === ANTIGRAVITY_PRE_RESPONSE_TIMEOUT_CODE || isOwnDeadlineTimeout + ? "upstream_timeout" + : failureStatus === 401 + ? "authentication_error" + : undefined; + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${failureStatus}`, + }).catch(() => {}); + persistAttemptLogs({ + status: failureStatus, + error: safeFailureMessage, + providerRequest: finalBody || translatedBody, + // On a client-abort (AbortError), the client already disconnected before + // we ever got here — this body is what we WOULD have sent, not what was + // actually delivered. Logging it as `clientResponse` is misleading (the + // dashboard reads that field as "what the client received"), so omit it + // for this case; `error` above already records the failure reason. + clientResponse: + errorMetadata.name === "AbortError" + ? undefined + : buildErrorBody(failureStatus, failureMessage), + claudeCacheMeta: claudePromptCacheLogMeta, + cacheSource: "upstream", + }); + if (isRequestAborted) { + streamController.handleError(createSafeAbortError()); + return { + response: createErrorResult(499, "Request aborted"), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + const persistentErrorCode = projectFailureUsageErrorCode({ + statusCode: failureStatus, + message: failureMessage, + errorCode: projectPublicErrorIdentifier( + upstreamErrorCode || errorMetadata.name, + "upstream_error" + ), + errorType: upstreamErrorType, + }); + persistFailureUsage(failureStatus, persistentErrorCode); + console.log(`${COLORS.red}[ERROR] ${safeFailureMessage}${COLORS.reset}`); + if (stream && upstreamErrorCode) { + const result = createStreamingErrorResult( + failureStatus, + failureMessage, + upstreamErrorCode, + upstreamErrorType + ); + localLimiterErrors.markTrustedLocalRateLimitResponse(result.response, error); + return { + response: { + ...result, + errorType: upstreamErrorType, + errorCode: upstreamErrorCode, + }, + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + const result = createErrorResult( + failureStatus, + failureMessage, + null, + upstreamErrorCode, + upstreamErrorType + ); + localLimiterErrors.markTrustedLocalRateLimitResponse(result.response, error); + return { + response: result, + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + let upstreamErrorParsed = false; + let parsedStatusCode = providerResponse.status; + let parsedMessage = ""; + let parsedRetryAfterMs: number | null = null; + let upstreamErrorBody: unknown = null; + + // Track whether stream_options was present and stripped — if so, 401/403 after + // that may be from the modification rather than a genuine auth failure, so we + // skip the credential refresh attempt in that case. + const hadStreamOptions = + targetFormat === FORMATS.OPENAI_RESPONSES && "stream_options" in translatedBody; + if (hadStreamOptions) { + delete translatedBody.stream_options; + } + + // Handle 401/403 - try token refresh using executor + // T-PROBE: probe-origin failures never attempt the refresh — a probe must + // not consume a rotating refresh token nor persist an "expired" + // deactivation on refresh failure (#9817). The 401/403 then flows into + // the normal providerFailure classification (record-only in probe mode). + if ( + (providerResponse.status === HTTP_STATUS.UNAUTHORIZED || + providerResponse.status === HTTP_STATUS.FORBIDDEN) && + !hadStreamOptions && // Skip refresh if failure may be from stream_options removal, not auth + !(await shouldIsolateProbeFailures()) && + !(await shouldSkipCredentialRefresh(provider, providerResponse)) + ) { + // Fix A: wrap refreshCredentials in runWithOnPersist so the persist callback + // executes INSIDE the per-connection mutex held by getAccessToken. This makes + // [network refresh + DB write + outer-state mutation] one atomic step and + // prevents concurrent requests from reading a stale refreshToken before the + // DB has been updated (refresh_token_reused on Codex/OpenAI). + // + // Not every executor routes refresh through getAccessToken (e.g. github.ts + // calls refreshCopilotToken directly). When the persistFn doesn't fire from + // inside getAccessToken, we still need to do the credentials mutation + user + // callback after refreshCredentials returns. The `persistFnRan` flag tracks + // which path executed so we don't double-fire (race-prone) or skip (regression). + // Front 3: remember the refresh_token we are about to present so that, if the + // refresh fails as unrecoverable, we can tell a genuine death apart from a + // stale-token reuse that a concurrent/sibling refresh already rotated past. + const attemptedRefreshToken = + typeof credentials?.refreshToken === "string" ? credentials.refreshToken : null; + let persistFnRan = false; + const persistFn = onCredentialsRefreshed + ? async (refreshResult: Record) => { + persistFnRan = true; + // Mutate the shared credentials object so subsequent executor calls + // in this request see the new tokens. Runs INSIDE the mutex. + Object.assign(credentials, refreshResult); + await onCredentialsRefreshed(refreshResult); + } + : undefined; + + // #4038: build a compare-and-swap reread so getAccessToken can skip the persist if a + // concurrent writer (sibling request / HealthCheck / replica) already rotated this + // connection's refresh_token past the one we presented — overwriting would revert it + // and revoke the token family. No connectionId ⇒ no guard (behavior unchanged). + const casConnectionId = + typeof credentials?.connectionId === "string" ? credentials.connectionId.trim() : ""; + const casReread = casConnectionId + ? async () => { + const latest = await getProviderConnectionById(casConnectionId); + return typeof latest?.refreshToken === "string" ? latest.refreshToken : null; + } + : null; + + const newCredentials = (await refreshWithRetry( + () => + runWithCasGuard( + casReread ? { expectedRefreshToken: attemptedRefreshToken, reread: casReread } : null, + () => runWithOnPersist(persistFn, () => executor.refreshCredentials(credentials, log)) + ), + 3, + log, + provider, // Explicitly pass the provider to avoid universally tripping the "unknown" circuit breaker + { + ...(casConnectionId ? { connectionId: casConnectionId } : {}), + scope: resilienceSettings.tokenRefreshBreaker.scope, + failureThreshold: resilienceSettings.tokenRefreshBreaker.failureThreshold, + cooldownMs: resilienceSettings.tokenRefreshBreaker.cooldownMs, + } + )) as null | { + accessToken?: string; + copilotToken?: string; + }; + + if (newCredentials?.accessToken || newCredentials?.copilotToken) { + log?.info?.("TOKEN", `${provider?.toUpperCase()} | refreshed`); + + // Fall back to post-mutex mutation only for executors that don't route + // through getAccessToken (and therefore never fire onPersist). For + // executors that DO route through it (Codex, Claude, Gemini, etc.) the + // mutation already happened atomically inside the mutex. + if (!persistFnRan) { + Object.assign(credentials, newCredentials); + if (onCredentialsRefreshed) { + await onCredentialsRefreshed(newCredentials); + } + } + + // Retry with new credentials — model + extra headers follow translatedBody.model so they + // stay aligned if this block ever runs after a path that mutates body.model (e.g. fallback). + try { + const retryModelId = String(translatedBody.model || effectiveModel); + const retryBody = await prepareUpstreamBody({ + translatedBody, + modelToCall: retryModelId, + ...trustedEffortContext, + provider, + targetFormat, + credentials: getExecutionCredentials(), + log, + bypassDefaultToolLimit: isOpencodeClient, + isOpencodeClient, + rawBody: body, + clientRawRequest, + }); + assertManagedLeaseFence(getExecutionConnectionId(getExecutionCredentials())); + const retryResult = normalizeExecutorResult( + await runWithCapture(providerRequestCapture, () => + executor.execute({ + model: retryModelId, + body: retryBody, + stream: upstreamStream, + credentials: getExecutionCredentials(), + signal: streamController.signal, + log, + extendedContext, + upstreamExtraHeaders: buildUpstreamHeadersForExecute(retryModelId), + clientHeaders: getExecutorClientHeaders(), + clientResponseFormat, + onCredentialsRefreshed, + skipUpstreamRetry: isCombo, + contextEditing: { enabled: contextEditingEnabled }, + correlationId, + }) + ) + ); + + if (retryResult.response.ok) { + providerResponse = retryResult.response; + providerUrl = retryResult.url; + providerHeaders = new Headers(retryResult.headers || {}); + finalBody = providerRequestCapture.body(retryResult.transformedBody); + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + upstreamErrorParsed = false; // Reset since new response is OK + } else { + providerResponse = retryResult.response; + upstreamErrorParsed = false; // Let it be parsed downstream + } + } catch (retryErr) { + const retryLeaseFenceCode = getManagedLeaseFenceErrorCode( + getUpstreamErrorIdentifier(retryErr) + ); + if (retryLeaseFenceCode) + return { + response: managedLeaseFenceErrorResult(retryLeaseFenceCode), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + // Refresh succeeded but the retry leg failed (network blip, AbortError, + // executor throw). Don't swallow — the operator-visible signal "the user + // saw 401 even though auth was actually fixed" is much more confusing + // than the original 401 alone. Surface at error level with sanitization. + log?.error?.( + "TOKEN", + `${provider?.toUpperCase()} | retry after refresh failed: ${sanitizeErrorMessage(retryErr)}` + ); + } + } else { + log?.warn?.("TOKEN", `${provider?.toUpperCase()} | refresh failed`); + if (isUnrecoverableRefreshError(newCredentials) && onCredentialsRefreshed) { + // Front 3 (reuse-race tolerance): before deactivating, re-read the DB. + // If a sibling/concurrent refresh already rotated this connection's + // refresh_token (common for Codex/OpenAI under one shared Auth0 client), + // the failure we saw was a stale-token reuse — the account is healthy + // with the newer token, so keep it active instead of killing it. + let alreadyRotated = false; + if (typeof connectionId === "string" && connectionId && attemptedRefreshToken) { + try { + const latest = await getProviderConnectionById(connectionId); + if (wasRefreshTokenRotated(attemptedRefreshToken, latest?.refreshToken)) { + alreadyRotated = true; + log?.warn?.( + "TOKEN", + `${provider.toUpperCase()} | refresh_token already rotated by a concurrent refresh — keeping connection active` + ); + } + } catch { + // DB read failed — fall through to the safe default (deactivate). + } + } + if (!alreadyRotated) { + await onCredentialsRefreshed({ testStatus: "expired", isActive: false }); + } + } + } + } + + // Check provider response - return error info for fallback handling + providerFailure: if (!providerResponse.ok) { + trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); + + let statusCode = providerResponse.status; + let message = ""; + let retryAfterMs: number | null = null; + let upstreamErrorCode: string | undefined; + let upstreamErrorType: string | undefined; + + if (upstreamErrorParsed) { + statusCode = parsedStatusCode; + message = parsedMessage; + retryAfterMs = parsedRetryAfterMs; + } else { + const details = await parseUpstreamError(providerResponse, provider); + statusCode = details.statusCode; + message = details.message; + retryAfterMs = details.retryAfterMs; + upstreamErrorBody = details.responseBody; + upstreamErrorCode = typeof details.errorCode === "string" ? details.errorCode : undefined; + upstreamErrorType = typeof details.errorType === "string" ? details.errorType : undefined; + } + + // Gateways like agentrouter misstate temporary quota exhaustion as 403/400, + // which downstream classification treats as AUTH_ERROR and clients like + // Claude Code treat as permanent. Restate to 429 (+ synthetic Retry-After) + // BEFORE any classification so both the fallback engine and the surfaced + // client status see a retryable error. Registry-scoped per provider. + const restatement = applyStatusRestatement({ + provider, + status: statusCode, + message, + body: upstreamErrorBody, + retryAfterMs, + }); + if (restatement.ruleId) { + statusCode = restatement.status; + retryAfterMs = restatement.retryAfterMs; + log?.info?.( + "STATUS_RESTATE", + `${provider} ${restatement.fromStatus}→${statusCode} (${restatement.ruleId})` + ); + } + + const signatureRecovery = pipelineRecovered + ? { attempted: false, succeeded: false, execution: null, error: null, recoveryBody: null } + : await recoverAnthropicThinkingSignature({ + provider, + statusCode, + message, + body: translatedBody, + execute: async (recoveryBody) => { + translatedBody = recoveryBody as typeof translatedBody; + syncExecuteTranslatedBody(translatedBody); + return executeProviderRequest(currentModel, false); + }, + parseError: (response) => parseUpstreamError(response, provider), + }); + if (!pipelineRecovered && signatureRecovery.attempted && signatureRecovery.execution) { + providerResponse = signatureRecovery.execution.response; + if (signatureRecovery.succeeded) { + providerUrl = signatureRecovery.execution.url; + providerHeaders = signatureRecovery.execution.headers; + finalBody = providerRequestCapture.body(signatureRecovery.execution.transformedBody); + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + log?.info?.( + "THINKING_SIGNATURE", + `Recovered ${provider}/${currentModel} after one historical-thinking retry` + ); + } else if (signatureRecovery.error) { + statusCode = signatureRecovery.error.statusCode; + message = signatureRecovery.error.message; + retryAfterMs = signatureRecovery.error.retryAfterMs; + upstreamErrorBody = signatureRecovery.error.responseBody; + upstreamErrorCode = + typeof signatureRecovery.error.errorCode === "string" + ? signatureRecovery.error.errorCode + : undefined; + upstreamErrorType = + typeof signatureRecovery.error.errorType === "string" + ? signatureRecovery.error.errorType + : undefined; + } + } + + if (signatureRecovery.succeeded) break providerFailure; + + // #10281 — tiny-budget reasoning probes (e.g. Claude Code's `/model` check + // sends `max_tokens: 1`): the model burns the whole budget on thinking, and + // some upstreams (e.g. api.cline.bot for deepseek-v4-flash) answer the empty + // outcome with a 5xx ("empty response content") instead of a truncated 200. + // Answer such probes with a valid truncated response rather than relaying the + // upstream failure — which would also mark the connection unavailable and + // poison fallback/cooldown bookkeeping for a request that is only a probe. + if ( + !stream && + isTinyBudgetReasoningProbe({ model: currentModel, body: finalBody || translatedBody }) && + isEmptyContentUpstreamFailure(statusCode, message) + ) { + providerResponse = buildReasoningProbeTruncatedResponse({ + model: currentModel, + maxTokens: toPositiveInteger( + (finalBody || translatedBody)?.max_tokens ?? + (finalBody || translatedBody)?.max_completion_tokens + ), + requestId: skillRequestId, + }); + log?.warn?.( + "PROBE", + `Reasoning probe (max_tokens < ${REASONING_BUFFER_MIN_TRIGGER}) answered with truncated 200 — upstream reported "${message}"` + ); + break providerFailure; + } + + const errorConnectionId = getCurrentConnectionId() || connectionId; + await applyProviderFailureClassification({ + statusCode, + message, + headers: providerResponse.headers, + upstreamErrorBody, + retryAfterMs, + targetModel: currentModel, + }); + + appendRequestLog({ + model, + provider, + connectionId: errorConnectionId, + status: `FAILED ${statusCode}`, + }).catch(() => {}); + + const errMsg = formatProviderError(new Error(message), provider, model, statusCode); + const safeErrMsg = sanitizeErrorMessage(errMsg) || "Upstream provider error"; + const safeUpstreamErrorBody = sanitizeUpstreamDetails(upstreamErrorBody); + console.log(`${COLORS.red}[ERROR] ${safeErrMsg}${COLORS.reset}`); + + // Log Antigravity retry time if available + if (retryAfterMs && provider === "antigravity") { + const retrySeconds = Math.ceil(retryAfterMs / 1000); + log?.debug?.("RETRY", `Antigravity quota reset in ${retrySeconds}s (${retryAfterMs}ms)`); + } + + // Log error with full request body for debugging + reqLogger.logError(new Error(message), finalBody || translatedBody); + reqLogger.logProviderResponse( + providerResponse.status, + providerResponse.statusText, + providerResponse.headers, + safeUpstreamErrorBody + ); + + // Rate limiter updated in applyProviderFailureClassification + + // ── T5: Intra-family model fallback ────────────────────────────────────── + // Before returning a model-unavailable error upstream, try sibling models + // from the same family. This keeps the request alive on the same account + // instead of failing the entire combo. + if (!pipelineRecovered && isModelUnavailableError(statusCode, message, provider)) { + const nextModel = getNextFamilyFallback(currentModel, triedModels, provider); + if (nextModel) { + triedModels.add(nextModel); + currentModel = nextModel; + translatedBody.model = nextModel; + log?.info?.("MODEL_FALLBACK", `${model} unavailable (${statusCode}) → trying ${nextModel}`); + // Re-execute with the fallback model + try { + const fallbackResult = await executeProviderRequest(nextModel, false); + if (fallbackResult.response.ok) { + providerResponse = fallbackResult.response; + providerUrl = fallbackResult.url; + providerHeaders = fallbackResult.headers; + finalBody = providerRequestCapture.body(fallbackResult.transformedBody); + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + // Continue processing with the fallback response — skip error return + log?.info?.("MODEL_FALLBACK", `Serving ${nextModel} as fallback for ${model}`); + // Jump to streaming/non-streaming handling below + // We fall through by NOT returning here + } else { + // Fallback also failed — return original error + persistAttemptLogs({ + status: statusCode, + error: safeErrMsg, + providerRequest: finalBody || translatedBody, + providerResponse: safeUpstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "model_unavailable"); + return { + response: createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + } catch { + persistAttemptLogs({ + status: statusCode, + error: safeErrMsg, + providerRequest: finalBody || translatedBody, + providerResponse: safeUpstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "model_unavailable"); + return { + response: createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + } else { + persistAttemptLogs({ + status: statusCode, + error: safeErrMsg, + providerRequest: finalBody || translatedBody, + providerResponse: safeUpstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "model_unavailable"); + return { + response: createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + } else if (isContextOverflowError(statusCode, message)) { + const familyCandidates = getModelFamily(currentModel, provider).filter( + (m) => m !== currentModel && !triedModels.has(m) + ); + const nextModel = + findLargerContextModel(currentModel, familyCandidates, provider) ?? + getNextFamilyFallback(currentModel, triedModels, provider); + if (nextModel) { + triedModels.add(nextModel); + currentModel = nextModel; + translatedBody.model = nextModel; + log?.info?.("CONTEXT_OVERFLOW_FALLBACK", `${model} context overflow → trying ${nextModel}`); + try { + const fallbackResult = await executeProviderRequest(nextModel, false); + if (fallbackResult.response.ok) { + providerResponse = fallbackResult.response; + providerUrl = fallbackResult.url; + providerHeaders = fallbackResult.headers; + finalBody = providerRequestCapture.body(fallbackResult.transformedBody); + reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody); + updatePendingScope(pendingScope, { + providerRequest: finalBody, + providerUrl, + stage: "provider_response_started", + }); + log?.info?.( + "CONTEXT_OVERFLOW_FALLBACK", + `Serving ${nextModel} as fallback for ${model}` + ); + } else { + persistAttemptLogs({ + status: statusCode, + error: safeErrMsg, + providerRequest: finalBody || translatedBody, + providerResponse: safeUpstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "context_overflow"); + return { + response: createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + } catch { + persistAttemptLogs({ + status: statusCode, + error: safeErrMsg, + providerRequest: finalBody || translatedBody, + providerResponse: safeUpstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "context_overflow"); + return { + response: createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + } else { + persistAttemptLogs({ + status: statusCode, + error: safeErrMsg, + providerRequest: finalBody || translatedBody, + providerResponse: safeUpstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, "context_overflow"); + return { + response: createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + } else { + persistAttemptLogs({ + status: statusCode, + error: safeErrMsg, + providerRequest: finalBody || translatedBody, + providerResponse: safeUpstreamErrorBody, + clientResponse: buildErrorBody(statusCode, errMsg), + cacheSource: "upstream", + }); + persistFailureUsage(statusCode, `upstream_${statusCode}`); + + // Emergency budget fallback is orchestrated exclusively by the routing layer + // (src/sse/handlers/chat.ts), which resolves credentials FOR the emergency + // provider through account selection. The executor-level hop that used to + // live here re-sent the FAILING provider's credentials to the emergency + // provider's endpoint (e.g. the OpenAI API key to integrate.api.nvidia.com) + // — a cross-provider credential leak that also never succeeded upstream. + return { + response: createErrorResult( + statusCode, + errMsg, + retryAfterMs, + upstreamErrorCode, + upstreamErrorType, + upstreamErrorBody, + { passthrough: sourceFormat === FORMATS.CLAUDE } + ), + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; + } + // ── End T5 ─────────────────────────────────────────────────────────────── + } + return { + carry: { + translatedBody, + currentModel, + effectiveServiceTier, + finalBody, + pipelineRecovered, + providerHeaders, + providerResponse, + providerUrl, + }, + }; +} diff --git a/open-sse/handlers/chatCore/streamingTail.ts b/open-sse/handlers/chatCore/streamingTail.ts new file mode 100644 index 000000000000..404e234681a1 --- /dev/null +++ b/open-sse/handlers/chatCore/streamingTail.ts @@ -0,0 +1,759 @@ +import { getCodexClientSessionId } from "../../config/codexIdentity.ts"; +import { + noteCodexTurnStateProvenance, + readCodexTurnStateHeader, +} from "../../config/codexTurnState.ts"; +import { + STREAM_DISCONNECT_GRACE_PERIOD_MS, + STREAM_READINESS_MAX_TIMEOUT_MS, + STREAM_READINESS_TIMEOUT_MS, + STREAM_RECOVERY, +} from "../../config/constants.ts"; +import { incrementTokenUsage } from "../../services/geminiRateLimitTracker.ts"; +import { requestTtftMs, streamEmittedOutput } from "../../utils/streamTiming.ts"; +import { clearPostOutputFailureStreak } from "../../services/accountFallback/postOutputFailureStreak.ts"; +import { + createRoutingEvent, + emitRoutingEvent, + outcomeFromStatus, +} from "../../services/routing/index.ts"; +import { FORMATS } from "../../translator/formats.ts"; +import { needsTranslation } from "../../translator/index.ts"; +import { runEmptyTurnRetryLoop } from "./emptyTurnRetryLoop.ts"; +import { buildErrorBody } from "../../utils/error.ts"; +import { + createPassthroughStreamWithLogger, + createSSETransformStreamWithLogger, +} from "../../utils/stream.ts"; +import * as streamFailure from "../../utils/streamFailureFinalization.ts"; +import { ensureStreamReadiness } from "../../utils/streamReadiness.ts"; +import { resolveStreamReadinessTimeout } from "../../utils/streamReadinessPolicy.ts"; +import { maybeFallbackAfterReadiness } from "./streamReadinessFallback.ts"; +import { captureStreamReasoningForReplay } from "./streamReasoningCapture.ts"; +import { meteredBudgetCost } from "@/lib/usage/meteredBudgetPolicy"; +import { resolveSuppressThinkClose } from "../../utils/thinkCloseMarker.ts"; +import { hasActiveClaudeThinking } from "../../utils/thinkingBudget.ts"; +import { buildCacheUsageLogMeta } from "./cacheUsageMeta.ts"; +import { recordContextEditingTelemetryHook } from "./contextEditingTelemetry.ts"; +import { readCpaAuthIndex } from "./failureUsage.ts"; +import { emitRequestGamificationEvent } from "./gamificationEvent.ts"; +import { maybeConvertJsonBodyToSse } from "./jsonBodyToSse.ts"; +import { runMemoryExtractionGate } from "./memoryExtraction.ts"; +import { mergeResponseToolNameMap } from "./passthroughToolNames.ts"; +import { runPluginOnResponseHook, runPluginOnStreamCompleteHook } from "./pluginOnResponse.ts"; +import { routingFinishReason } from "./routingFinishReason.ts"; +import { wrapReadableStreamWithFinalize } from "./streamFinalize.ts"; +import { buildStreamLedgerDetails, recordStreamingCost } from "./streamingCost.ts"; +import { assembleStreamingPipeline } from "./streamingPipeline.ts"; +import { scheduleStreamingQuotaShareConsumption } from "./streamingQuotaShare.ts"; +import { assembleStreamingResponseHeaders } from "./streamingResponseHeaders.ts"; +import { storeStreamingSemanticCacheResponse } from "./streamingSemanticCacheStore.ts"; +import { recordFinalInputCalibration } from "./contextEstimation.ts"; +import { recordStreamingUsageStats } from "./streamingUsageStats.ts"; +import { maybeSyncClaudeExtraUsageState } from "./telemetryHelpers.ts"; +import { getExecutorTimeoutMs, resolveConnectionTimeoutMs } from "./upstreamTimeouts.ts"; +import { recordCost } from "@/domain/costRules"; +import { extractFacts } from "@/lib/memory/extraction"; +import { calculateCost } from "@/lib/usage/costCalculator"; +import { appendRequestLog, trackPendingRequest } from "@/lib/usageDb"; +import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags.ts"; +import { getProviderCredentials } from "@/sse/services/auth"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- deps bag mirrors the barrel closure +type Loose = any; + +export type StreamingTailDeps = Record; + +export async function runStreamingTail(deps: StreamingTailDeps) { + const { + agentGoalPolicy, + apiKeyInfo, + attachCompressionUsageReceiptAfterAnalytics, + body, + bodyForCacheWrite, + calibrationEstimatedInputTokens, + claudePromptCacheLogMeta, + clientRawRequest, + clientResponseFormat, + comboStrategy, + compressionResponseMeta, + connectionId, + contextEditingEnabled, + copilotCompatibleReasoning, + correlationId, + createPiiTransform, + credentials, + currentModel, + customToolNames, + echoModel, + effectiveModel, + endpointPath, + executeProviderRequest, + executor, + fallbackAttempts, + getCurrentConnectionId, + forcedConnectionId, + isCombo, + isDroidCLI, + isResponsesEndpoint, + log, + managedLease, + memoryOwnerId, + memorySettings, + model, + modelInfo, + onRequestSuccess, + onStreamFailure, + pendingConnId, + pendingRequestId, + persistAttemptLogs, + persistFailureUsage, + pipelineSessionId, + provider, + providerHeaders, + providerRequestCapture, + providerUrl, + reasoningCacheScope, + reasoningReplayHistory, + releaseTurnExecution, + reqLogger, + requestToolIdentityMap, + resolveReportedServiceTier, + semanticCacheEnabled, + skillRequestId, + startTime, + stream, + streamController, + streamUserAgent, + targetFormat, + thinkingMarkerHeader, + toolNameMap, + traceId, + translatedBody, + videoBridgeObserved, + } = deps; + let providerResponse, finalBody, effectiveServiceTier; + providerResponse = deps.providerResponse; + finalBody = deps.finalBody; + effectiveServiceTier = deps.effectiveServiceTier; + let onPipelineStreamError = null; + let onClientDisconnectFinalize = null; + let turnExecutionHandedOffToStream = false; + + // Streaming response + // #3089 — some "reasoning" openai-compatible upstreams ignore a stream:true + // request and return a complete application/json chat-completion body instead + // of an SSE stream. The readiness check below only recognizes SSE `data:` + // frames, so that body produced a spurious STREAM_EARLY_EOF / HTTP 502 even + // though it carried valid content/reasoning_content. Detect a JSON (non-SSE) + // upstream body and synthesize an equivalent OpenAI SSE stream so the + // streaming pipeline (and the client) get a valid stream. + providerResponse = await maybeConvertJsonBodyToSse(providerResponse, { log, provider, model }); + const streamReadinessPolicy = resolveStreamReadinessTimeout({ + baseTimeoutMs: STREAM_READINESS_TIMEOUT_MS, + provider, + model, + body: (finalBody || translatedBody) as Record | null | undefined, + sourceBody: body as Record | null | undefined, + maxTimeoutMs: agentGoalPolicy.detected + ? Math.max(STREAM_READINESS_MAX_TIMEOUT_MS, agentGoalPolicy.readinessMaxTimeoutMs) + : STREAM_READINESS_MAX_TIMEOUT_MS, + cascadeTimeoutMs: getExecutorTimeoutMs( + executor, + provider, + model, + resolveConnectionTimeoutMs(credentials?.providerSpecificData) + ), + }); + if (streamReadinessPolicy.timeoutMs !== streamReadinessPolicy.baseTimeoutMs) { + log?.debug?.( + "STREAM", + `adaptive readiness timeout=${streamReadinessPolicy.timeoutMs}ms base=${streamReadinessPolicy.baseTimeoutMs}ms reason=${streamReadinessPolicy.reasons.join(",")}` + ); + } + + let streamReadiness = await ensureStreamReadiness(providerResponse, { + timeoutMs: streamReadinessPolicy.timeoutMs, + maxTimeoutMs: streamReadinessPolicy.maxTimeoutMs, + provider, + model, + log, + }); + // A stall is an upstream issue, not an account fault — the executor loop + // already ended at headers, so this bounded retry is the only recovery left. + const fallback = await maybeFallbackAfterReadiness({ + streamReadiness, + clientAborted: streamController.signal.aborted, + failedConnectionId: getCurrentConnectionId(), + failedBody: providerResponse, + currentModel, + streamReadinessPolicy, + provider, + model, + log, + reqLogger, + providerUrl, + providerHeaders, + finalBody, + translatedBody, + executeProviderRequest, + providerRequestCapture, + }); + streamReadiness = fallback.readiness; + providerResponse = fallback.providerResponse; + finalBody = fallback.finalBody; + if (streamReadiness.ok === false) { + const { response: failureResponse, reason } = streamReadiness; + const { classificationReason, upstreamDiagnostic } = streamReadiness; + trackPendingRequest(model, provider, pendingConnId, false, undefined, pendingRequestId); + appendRequestLog({ + model, + provider, + connectionId, + status: `FAILED ${failureResponse.status}`, + }).catch(() => {}); + persistAttemptLogs({ + status: failureResponse.status, + error: reason, + providerRequest: finalBody || translatedBody, + clientResponse: buildErrorBody( + failureResponse.status, + classificationReason, + upstreamDiagnostic ? { error: { message: upstreamDiagnostic } } : undefined + ), + claudeCacheMeta: claudePromptCacheLogMeta, + cacheSource: "upstream", + }); + persistFailureUsage(failureResponse.status, streamReadiness.code); + // Do NOT call onStreamFailure — a stream stall is an upstream issue, + // not an account/quota failure. Marking the account unavailable here + // would lock out legitimate accounts when the upstream hangs. + return { + result: { + success: false, + status: failureResponse.status, + error: reason, + classificationError: classificationReason, + errorType: streamReadiness.type, + errorCode: streamReadiness.code, + response: failureResponse, + }, + carry: { onPipelineStreamError, onClientDisconnectFinalize, turnExecutionHandedOffToStream }, + }; + } + providerResponse = streamReadiness.response; + + // Flush-empty retry (opt-in `FLUSH_EMPTY_RETRY_ENABLED`, default off): when the + // upstream turn carries no usable content (reasoning-only 200, or a + // zero-valuable-chunk turn that the empty-stream guard would turn into a 502), + // issue bounded retries through the normal credential path BEFORE anything is + // exposed to the client — in particular before `onRequestSuccess` below. + // Empty turns are stochastic upstream misses, not account faults, so no + // cooldown and no forced exclusion: the round-robin picker may rotate + // fingerprint slots opportunistically, a single slot simply replays the same + // account. Budget: `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX` retries, then fall + // back to the current behavior. Translate-path streams only (mirror of the + // empty-stream guard); flag off = byte-for-byte unchanged. Bounded reader + // (abandon past the cap, never a full `text()` read); the original + // reconstructed response is piped, only the bounded copy is classified. + // Known TTFT cost when armed: a small valid turn under the cap is fully + // buffered before the first client byte (flag off by default, so the + // streaming path is untouched unless opted in). + if (stream && providerResponse.ok && providerResponse.body) { + let flushEmptyRetryArmed = false; + try { + flushEmptyRetryArmed = isFeatureFlagEnabled("FLUSH_EMPTY_RETRY_ENABLED"); + } catch { + flushEmptyRetryArmed = false; + } + const isTranslatePath = + targetFormat === FORMATS.OPENAI_RESPONSES || + needsTranslation(targetFormat, clientResponseFormat); + if (flushEmptyRetryArmed && isTranslatePath) { + const retried = await runEmptyTurnRetryLoop({ + providerResponse, + credentials, + provider, + currentModel, + model, + targetFormat, + clientResponseFormat, + isAborted: () => clientRawRequest?.signal?.aborted === true, + timeoutMs: streamReadinessPolicy.timeoutMs, + maxTimeoutMs: streamReadinessPolicy.maxTimeoutMs, + maxRetries: STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX, + translatedBody, + finalBody, + providerUrl, + providerHeaders, + correlationId, + traceId, + log, + getProviderCredentials, + routing: { leased: Boolean(managedLease), forcedConnectionId, apiKey: apiKeyInfo }, + executeProviderRequest, + logTargetRequest: (url, headers, body) => reqLogger.logTargetRequest(url, headers, body), + captureBody: (body) => providerRequestCapture.body(body), + }); + providerResponse = retried.providerResponse; + if (retried.adopted) finalBody = retried.finalBody; + } + } + + // Notify success - caller can clear error status if needed + if (onRequestSuccess) { + await onRequestSuccess(); + } + + const responseHeaders = assembleStreamingResponseHeaders({ + providerHeaders: providerResponse.headers, + provider, + model, + pendingRequestId, + compressionResponseMeta, + comboStrategy, + fallbackAttempts, + isCombo, // #14116: foreign-account quota-header strip (only meaningful when true) + requestedConnectionId: forcedConnectionId || null, + selectedConnectionId: credentials?.connectionId ?? null, + }); + + // The streaming headers (turn-state included, when present) are committed to + // the client from here on — record which connection minted the blob so a + // later cross-account echo can be stripped (Codex failover guard). The + // in-place failover update means `credentials` is the winning account. + if (provider === "codex" && readCodexTurnStateHeader(providerResponse.headers)) { + noteCodexTurnStateProvenance( + getCodexClientSessionId(clientRawRequest?.headers), + credentials?.connectionId + ); + } + + // Create transform stream with logger for streaming response + let transformStream; + const responseToolNameMap = mergeResponseToolNameMap( + toolNameMap, + (finalBody as Record | null | undefined) ?? null + ); + + let streamCompletionRecorded = false; + let streamFailureCompletionRecorded = false; + + // Callback to save call log when stream completes (include responseBody when provided by stream) + let streamTimingOriginOffsetMs: number | null = null; // startTime → StreamTiming start + const onStreamComplete = ({ + status: streamStatus, + usage: streamUsage, + responseBody: streamResponseBody, + providerPayload, + clientPayload, + reasoningMeta: streamReasoningMeta, + error: streamError, + errorCode: streamErrorCode, + firstOutputMs, + itlMs: streamItlMs, + interrupted: _streamInterrupted, + }) => { + const ttft = requestTtftMs(streamTimingOriginOffsetMs, firstOutputMs); + const normalizedStreamStatus = streamStatus || 200; + if (streamCompletionRecorded) return; + streamCompletionRecorded = true; + if (normalizedStreamStatus !== 200) { + if (streamFailureCompletionRecorded) return; + streamFailureCompletionRecorded = true; + } + const cacheUsageLogMeta = buildCacheUsageLogMeta(streamUsage); + const streamConnectionId = getCurrentConnectionId(); + + if (normalizedStreamStatus === 200) { + clearPostOutputFailureStreak(provider, streamConnectionId, modelInfo.model); + void maybeSyncClaudeExtraUsageState({ + provider, + connectionId: streamConnectionId, + providerSpecificData: credentials?.providerSpecificData, + log, + }); + } + + if (normalizedStreamStatus === 200 && streamResponseBody) { + captureStreamReasoningForReplay({ + streamResponseBody, + clientResponseFormat, + responseToolNameMap, + providerRequestBody: finalBody || translatedBody || body, + translatedBody, + reasoningReplayHistory, + provider, + model, + reasoningCacheScope, + videoTranscriptSensitive: videoBridgeObserved, + }); + } + effectiveServiceTier = resolveReportedServiceTier(streamResponseBody) ?? effectiveServiceTier; + + // Context Editing telemetry (streaming): the reconstructed stream body now carries + // context_management.applied_edits from the final message_delta snapshot. Mirror the + // non-streaming hook so streaming context-clear savings also surface under engine + // "context-editing" in compression analytics. Best-effort, Claude-only. + if (normalizedStreamStatus === 200) { + recordContextEditingTelemetryHook({ + contextEditingEnabled, + provider, + responseBody: streamResponseBody, + skillRequestId, + log, + }); + } + + streamFailure.finalizeStreamRequestLog({ + pendingRequestId, + model, + provider, + connectionId: streamConnectionId, + providerResponse: providerPayload ?? streamResponseBody ?? undefined, + clientResponse: clientPayload ?? streamResponseBody ?? undefined, + status: normalizedStreamStatus, + error: streamError, + errorCode: streamErrorCode, + }); + + // Track cache token metrics for streaming responses + if (streamUsage && typeof streamUsage === "object") { + attachCompressionUsageReceiptAfterAnalytics(streamUsage as Record, "stream"); + // Track Gemini token consumption for TPM rate-limit pre-check + if (provider === "gemini") { + const promptTokens = + typeof (streamUsage as Record).prompt_tokens === "number" + ? ((streamUsage as Record).prompt_tokens as number) + : 0; + if (promptTokens > 0) incrementTokenUsage(model, promptTokens); + } + } + recordFinalInputCalibration( + body, + provider, + effectiveModel, + calibrationEstimatedInputTokens, + streamUsage + ); + recordStreamingUsageStats(streamUsage, { + provider, + model, + streamStatus: normalizedStreamStatus, + startTime, + ttft, + streamErrorCode, + connectionId: streamConnectionId, + apiKeyInfo, + effectiveServiceTier, + isCombo, + comboStrategy, + endpoint: endpointPath, + cpaAuthIndex: readCpaAuthIndex(providerResponse), + }); + + // Routing event (feedback foundation) — fire-and-forget, cheap, never blocks + // the stream. Feeds the quality tracker + optional OTel exporter. + void emitRoutingEvent( + createRoutingEvent({ + requestId: traceId || pendingRequestId || "unknown", + provider: provider || "unknown", + model: model || "unknown", + strategy: isCombo ? (comboStrategy ?? "combo") : "direct", + latencyMs: Date.now() - startTime, + ttftMs: typeof ttft === "number" && Number.isFinite(ttft) && ttft >= 0 ? ttft : null, + itlMs: + typeof streamItlMs === "number" && Number.isFinite(streamItlMs) && streamItlMs >= 0 + ? streamItlMs + : null, + inputTokens: + streamUsage && typeof streamUsage === "object" + ? (() => { + const promptTokens = (streamUsage as Record).prompt_tokens; + return typeof promptTokens === "number" && Number.isFinite(promptTokens) + ? promptTokens + : null; + })() + : null, + outputTokens: + streamUsage && typeof streamUsage === "object" + ? (() => { + const completionTokens = (streamUsage as Record).completion_tokens; + return typeof completionTokens === "number" && Number.isFinite(completionTokens) + ? completionTokens + : null; + })() + : null, + cost: null, + retries: 0, + fallbackUsed: false, // combo-level fallback tracked by decisionTrace + outcome: + normalizedStreamStatus === 200 + ? "success" + : streamErrorCode === "stream_interrupted" || streamErrorCode === "aborted" + ? "stream_interrupted" + : outcomeFromStatus(normalizedStreamStatus), + status: normalizedStreamStatus, + finishReason: routingFinishReason(streamResponseBody), + connectionId: streamConnectionId ?? credentials?.connectionId ?? null, + }) + ); + + persistAttemptLogs({ + status: normalizedStreamStatus, + error: streamError || undefined, + tokens: streamUsage || {}, + responseBody: streamResponseBody ?? undefined, + providerRequest: finalBody || translatedBody, + providerResponse: providerPayload, + clientResponse: clientPayload ?? streamResponseBody ?? undefined, + claudeCacheMeta: claudePromptCacheLogMeta, + claudeCacheUsageMeta: cacheUsageLogMeta, + cacheSource: "upstream", + // #13130: persist TTFT so call_logs.ttft_ms lets the dashboard compute + // generation-time TPS instead of wall-clock TPS. + ttft, + reasoningMeta: streamReasoningMeta ?? null, + }); + + recordStreamingCost({ + apiKeyId: apiKeyInfo?.id, + provider, + model, + streamUsage, + serviceTier: effectiveServiceTier, + calculateCost, + // Only the budget-consumable share may draw down the allowance. + recordCost: (apiKeyId, cost, details) => { + const budgetCost = meteredBudgetCost(provider, cost); + if (budgetCost > 0) recordCost(apiKeyId, budgetCost, details); + }, + ledger: buildStreamLedgerDetails(effectiveServiceTier, normalizedStreamStatus < 400, traceId), + }); + + // === Quota Share POST-hook streaming (B/F7) — fire-and-forget, fail-open === + // Resolve the real per-request cost (calculateCost) so USD-unit pools accrue + // on streaming traffic too; this previously recorded usd:0 hardcoded, which + // meant DeepSeek-style `usd/monthly` shared pools never blocked on streams. + scheduleStreamingQuotaShareConsumption({ + apiKeyId: apiKeyInfo?.id, + connectionId: credentials?.connectionId, + provider, + model, + streamUsage, + streamStatus: normalizedStreamStatus, + serviceTier: effectiveServiceTier, + calculateCost, + log, + }); + // === /Quota Share POST-hook streaming === + + if (streamStatus === 200) { + // #12150 P1b surface 3 (fix round 1): see the matching non-streaming + // gate above — an observed request populates NO durable memory from + // either the request-derived text or this streamed response. + runMemoryExtractionGate({ + memoryOwnerId, + memorySettings, + videoBridgeObserved, + pipelineSessionId, + requestBody: body as Record, + responseBody: (streamResponseBody ?? null) as Record | null, + extractFacts, + log, + }); + } + + // Semantic cache: store assembled streaming response for future cache hits + storeStreamingSemanticCacheResponse({ + enabled: semanticCacheEnabled, + streamStatus, + streamResponseBody, + body: bodyForCacheWrite, + headers: clientRawRequest?.headers, + model, + provider, + apiKeyId: apiKeyInfo?.id ?? undefined, + streamUsage, + log, + videoTranscriptSensitive: videoBridgeObserved, + }); + + // Plugin onStreamComplete hook — fire-and-forget, fail-open (#9571) + // Pass traceId as requestId so plugins can correlate the stream-completion event + // with the originating request (the same id used for onRequest/onResponse). (#11825) + runPluginOnStreamCompleteHook({ + status: normalizedStreamStatus, + usage: streamUsage as Record | undefined, + ttft, + model, + provider, + errorCode: streamErrorCode, + startTime, + requestId: traceId, + }); + }; + + const streamFailureFinalizers = streamFailure.createStreamFailureFinalizers({ + isFailureCompletionRecorded: () => streamFailureCompletionRecorded, + isStreamCompletionRecorded: () => streamCompletionRecorded, + onStreamComplete, + persistFailureUsage, + onStreamFailure, + hasEmittedOutput: () => streamEmittedOutput(transformStream), + }); + const handleStreamFailure = streamFailureFinalizers.handleStreamFailure; + onPipelineStreamError = streamFailureFinalizers.onPipelineStreamError; + // #9653: gives a genuine, race-delayed completion a chance to land (see + // createClientDisconnectGraceHandler's doc comment) before persisting a false + // 499/0-tokens for a request that actually delivered its full response. + onClientDisconnectFinalize = streamFailure.createClientDisconnectGraceHandler({ + isStreamCompletionRecorded: () => streamCompletionRecorded, + gracePeriodMs: STREAM_DISCONNECT_GRACE_PERIOD_MS, + finalize: (event) => + handleStreamFailure({ + status: 499, + message: `Client disconnected: ${event.reason}`, + code: "client_disconnected", + type: "client_disconnected", + }), + }); + + // For providers using Responses API format, translate stream back to openai (Chat Completions) format + // UNLESS client is Droid CLI which expects openai-responses format back + const needsResponsesTranslation = + targetFormat === FORMATS.OPENAI_RESPONSES && + clientResponseFormat === FORMATS.OPENAI && + !isResponsesEndpoint && + !isDroidCLI; + const streamStateBody = finalBody || body; + + // Client's explicit thinking intent (Anthropic Messages shape). Claude Code + // sends `{type:"enabled"}` or `{type:"adaptive"}` to opt into relaying + // upstream reasoning_content as Claude thinking blocks; `{type:"disabled"}` + // or an omitted `thinking` field opts out. Kept false for every other + // client schema (OpenAI / Responses), which never express intent through + // `body.thinking`. Mirrors hasActiveClaudeThinking() so the request and + // response sides agree on what counts as "thinking requested" — a prior + // inline `=== "enabled"` check silently suppressed `adaptive` (the intent + // Claude Code actually sends), leaking the mismatch as a broken tool-call + // turn (call log 1787566395384-bab9ab: reasoning dropped → model emitted + // DSML tool-call markers as plain text → incomplete `stop` finish). + const requestedThinking = hasActiveClaudeThinking((body ?? {}) as Record); + + streamTimingOriginOffsetMs = Date.now() - startTime; + if (needsResponsesTranslation) { + // Provider returns openai-responses, translate to openai (Chat Completions) that clients expect + log?.debug?.("STREAM", `Responses translation mode: openai-responses → openai`); + transformStream = createSSETransformStreamWithLogger( + "openai-responses", + "openai", + provider, + reqLogger, + responseToolNameMap, + model, + connectionId, + streamStateBody, + onStreamComplete, + apiKeyInfo, + handleStreamFailure, + copilotCompatibleReasoning, + false, + requestedThinking, + customToolNames, + // openai-responses → openai translation still wants the namespace identity + // map for #7936-style round-trip closure when the client also speaks + // Responses (Codex CLI). + requestToolIdentityMap + ); + } else if (needsTranslation(targetFormat, clientResponseFormat)) { + // Standard translation for other providers + log?.debug?.("STREAM", `Translation mode: ${targetFormat} → ${clientResponseFormat}`); + transformStream = createSSETransformStreamWithLogger( + targetFormat, + clientResponseFormat, + provider, + reqLogger, + responseToolNameMap, + model, + connectionId, + streamStateBody, + onStreamComplete, + apiKeyInfo, + handleStreamFailure, + copilotCompatibleReasoning, + // Suppress the `` close marker for clients that render it verbatim + // (e.g. OpenCode by UA; any client via `x-omniroute-thinking-marker: off`); + // preserved for Claude Code / Cursor and unknown clients by default (#5245 / + // #5312). Responses API clients always suppress it (structured reasoning + // items make the marker meaningless); otherwise the header wins over the + // UA allowlist. + resolveSuppressThinkClose({ + userAgent: streamUserAgent, + thinkingMarkerHeader, + clientResponseFormat, + }), + requestedThinking, + customToolNames, + requestToolIdentityMap + ); + } else { + log?.debug?.("STREAM", `Standard passthrough mode`); + transformStream = createPassthroughStreamWithLogger( + provider, + reqLogger, + responseToolNameMap, + model, + connectionId, + streamStateBody, + onStreamComplete, + apiKeyInfo, + handleStreamFailure, + clientResponseFormat, + requestToolIdentityMap + ); + } + + const finalStream = assembleStreamingPipeline({ + providerResponse, + transformStream, + streamController, + createPiiTransform, + clientRawRequestHeaders: clientRawRequest?.headers, + clientResponseFormat, + echoModel, + responseHeaders, + // Same adaptive budget the pre-handoff readiness gate above just used — + // reasoning models that legitimately take a while to say anything keep + // that same patience for their first REAL content, not just their first + // lifecycle frame. See pipeWithDisconnect's own doc comment. + contentStallTimeoutMs: streamReadinessPolicy.timeoutMs, + }); + const clientFacingStream = wrapReadableStreamWithFinalize(finalStream, releaseTurnExecution); + + // ── Gamification event (fire-and-forget) ── + await emitRequestGamificationEvent({ apiKeyId: apiKeyInfo?.id, model, provider }); + + // ── Plugin onResponse hook (fire-and-forget) ── + await runPluginOnResponseHook({ + requestId: traceId, + body, + model, + provider, + apiKeyInfo, + headers: clientRawRequest?.headers, + response: { status: 200, streamed: true }, + }); + + const response = new Response(clientFacingStream, { + headers: responseHeaders, + }); + turnExecutionHandedOffToStream = true; + return { + result: { + success: true, + response, + }, + carry: { onPipelineStreamError, onClientDisconnectFinalize, turnExecutionHandedOffToStream }, + }; +} diff --git a/open-sse/handlers/responseSanitizer.ts b/open-sse/handlers/responseSanitizer.ts index 717708c9414e..44071d4462ca 100644 --- a/open-sse/handlers/responseSanitizer.ts +++ b/open-sse/handlers/responseSanitizer.ts @@ -888,12 +888,18 @@ function sanitizeResponsesOutputItem(item: unknown, index: number): JsonRecord | if (type === "function_call") { const callId = toString(itemRecord.call_id) || toString(itemRecord.id) || `call_${index}`; + const namespace = toString(itemRecord.namespace); return { id: toString(itemRecord.id) || `fc_${callId}`, type: "function_call", call_id: callId, name: toString(itemRecord.name) || "", arguments: stripZeroWidthToolArgumentJson(itemRecord.arguments), + ...(namespace ? { namespace } : {}), + ...(itemRecord.status !== undefined ? { status: itemRecord.status } : {}), + ...(itemRecord.encrypted_function_args !== undefined + ? { encrypted_function_args: itemRecord.encrypted_function_args } + : {}), }; } diff --git a/open-sse/handlers/search.ts b/open-sse/handlers/search.ts index e68603e45972..fc536b75c4dd 100644 --- a/open-sse/handlers/search.ts +++ b/open-sse/handlers/search.ts @@ -118,11 +118,30 @@ interface SearchHandlerOptions { /** Connection ID (proxy resolution + call-log attribution) and API key ID (per-key proxy). */ connectionId?: string; apiKeyId?: string; + /** + * Whole-request budget in ms. Replaces the 15s constant when set, and is + * also the ceiling for a provider's own timeout. Unset keeps both as they + * are, so a deployment that never touches it behaves exactly as before. + */ + timeoutMs?: number; + /** Per-provider timeout overrides in ms, keyed by provider id. */ + providerTimeoutsMs?: Record; } // ── Constants ──────────────────────────────────────────────────────────── -const GLOBAL_TIMEOUT_MS = 15_000; +const DEFAULT_GLOBAL_TIMEOUT_MS = 15_000; +const MIN_SEARCH_TIMEOUT_MS = 1_000; +const MAX_SEARCH_TIMEOUT_MS = 120_000; + +function clampSearchTimeoutMs(override: number | undefined, fallback: number, floor: number): number { + if (typeof override !== "number" || !Number.isFinite(override)) return fallback; + return Math.min(MAX_SEARCH_TIMEOUT_MS, Math.max(floor, Math.floor(override))); +} + +function resolveGlobalTimeoutMs(override?: number): number { + return clampSearchTimeoutMs(override, DEFAULT_GLOBAL_TIMEOUT_MS, MIN_SEARCH_TIMEOUT_MS); +} // Non-retriable HTTP status codes — fail immediately, don't try alternate const NON_RETRIABLE = new Set([400, 401, 403, 404]); @@ -1252,11 +1271,12 @@ async function tryZaiMCPProvider( providerSpecificData: Record | undefined, startTime: number, globalStartTime: number, + globalTimeoutMs: number, log?: any ): Promise { const { query, searchType, maxResults } = params; - const remainingGlobal = GLOBAL_TIMEOUT_MS - (Date.now() - globalStartTime); + const remainingGlobal = globalTimeoutMs - (Date.now() - globalStartTime); const timeout = Math.min(config.timeoutMs, Math.max(remainingGlobal, 1000)); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); @@ -1437,8 +1457,12 @@ export async function handleSearch(options: SearchHandlerOptions): Promise 0) { + primaryConfig = { ...primaryConfig, timeoutMs: providerTimeoutOverride }; + } if (primaryConfig.disabled) { return { success: false, @@ -1529,7 +1556,8 @@ export async function handleSearch(options: SearchHandlerOptions): Promise, startTime: number, globalStartTime: number, + globalTimeoutMs: number, log?: { info?: (tag: string, message: string) => void; error?: (tag: string, message: string) => void; } | null ): Promise { const { query, searchType, maxResults } = params; - const remainingGlobal = GLOBAL_TIMEOUT_MS - (Date.now() - globalStartTime); + const remainingGlobal = globalTimeoutMs - (Date.now() - globalStartTime); const timeout = Math.min(config.timeoutMs, Math.max(remainingGlobal, 1000)); if (log) { @@ -1689,7 +1720,8 @@ async function tryProvider( globalStartTime: number, log?: any, connectionId?: string, - apiKeyId?: string + apiKeyId?: string, + globalTimeoutMs: number = DEFAULT_GLOBAL_TIMEOUT_MS ): Promise { const startTime = Date.now(); const providerSpecificData = @@ -1709,7 +1741,7 @@ async function tryProvider( const { query, searchType, maxResults } = params; if (config.id === "duckduckgo-free") { - return tryDuckDuckGoFreeProvider(config, params, startTime, globalStartTime, log); + return tryDuckDuckGoFreeProvider(config, params, startTime, globalStartTime, globalTimeoutMs, log); } if (config.id === "zai-search" && token) { @@ -1720,6 +1752,7 @@ async function tryProvider( providerSpecificData, startTime, globalStartTime, + globalTimeoutMs, log ); } @@ -1741,7 +1774,7 @@ async function tryProvider( const { proxy, proxyLevel } = await resolveSearchProxy(connectionId, apiKeyId, config.id); // Timeout: min of provider timeout and remaining global timeout - const remainingGlobal = GLOBAL_TIMEOUT_MS - (Date.now() - globalStartTime); + const remainingGlobal = globalTimeoutMs - (Date.now() - globalStartTime); const timeout = Math.min(config.timeoutMs, Math.max(remainingGlobal, 1000)); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); diff --git a/open-sse/handlers/search/perplexitySearch.ts b/open-sse/handlers/search/perplexitySearch.ts index f21c402fb86c..ebf7d517ce26 100644 --- a/open-sse/handlers/search/perplexitySearch.ts +++ b/open-sse/handlers/search/perplexitySearch.ts @@ -94,7 +94,11 @@ export function buildPerplexityRequest( url: config.baseUrl, init: { method: "POST", - headers: { "Content-Type": "application/json", Authorization: `Bearer ${params.token}` }, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${params.token}`, + "X-Pplx-Integration": "omniroute", + }, body: JSON.stringify(body), }, }; diff --git a/open-sse/mcp-server/analyticsShape.ts b/open-sse/mcp-server/analyticsShape.ts new file mode 100644 index 000000000000..b294e36e2db7 --- /dev/null +++ b/open-sse/mcp-server/analyticsShape.ts @@ -0,0 +1,84 @@ +/** + * Readers for the /api/usage/analytics response. + * + * The route returns totals under `summary` and per-model rows under `byModel`. + * It has no top-level `totalCost`, `requestCount`, `successRate` or + * `avgLatencyMs`, and it only honours a `range` query param (1d/7d/30d/...), + * never `period`. The MCP handlers used to read the missing top-level fields + * and pass `period=session`, so every cost, request count and provider metric + * they reported was zero. These readers are the single place that knows the + * real shape. + */ + +type JsonRecord = Record; + +function asRecord(value: unknown): JsonRecord { + return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; +} + +function asArray(value: unknown): JsonRecord[] { + return Array.isArray(value) + ? value.filter((item): item is JsonRecord => asRecord(item) === item) + : []; +} + +function asNumber(value: unknown): number { + return typeof value === "number" && Number.isFinite(value) ? value : 0; +} + +const PERIOD_TO_RANGE: Record = { + session: "1d", + day: "1d", + week: "7d", + month: "30d", +}; + +export function analyticsRangeForPeriod(period: string | undefined): string { + return (period && PERIOD_TO_RANGE[period]) || "30d"; +} + +export interface AnalyticsTotals { + totalCost: number; + requestCount: number; + promptTokens: number; + completionTokens: number; +} + +export function readAnalyticsTotals(raw: unknown): AnalyticsTotals { + const summary = asRecord(asRecord(raw).summary); + return { + totalCost: asNumber(summary.totalCost), + requestCount: asNumber(summary.totalRequests), + promptTokens: asNumber(summary.promptTokens), + completionTokens: asNumber(summary.completionTokens), + }; +} + +export interface ProviderMetrics { + requestCount: number; + avgLatencyMs: number; + successRate: number; +} + +// byModel rows carry per-model request counts, latency and a success percentage. +// A provider's numbers are the request-weighted aggregate of its rows. +export function readProviderMetrics(raw: unknown, provider: string): ProviderMetrics { + const rows = asArray(asRecord(raw).byModel).filter((row) => row.provider === provider); + const requestCount = rows.reduce((sum, row) => sum + asNumber(row.requests), 0); + if (requestCount === 0) return { requestCount: 0, avgLatencyMs: 0, successRate: 0 }; + + const latencyWeighted = rows.reduce( + (sum, row) => sum + asNumber(row.avgLatencyMs) * asNumber(row.requests), + 0 + ); + const successful = rows.reduce((sum, row) => { + const pct = typeof row.successRatePct === "string" ? Number(row.successRatePct) : 0; + return sum + (Number.isFinite(pct) ? pct : 0) * asNumber(row.requests); + }, 0); + + return { + requestCount, + avgLatencyMs: Math.round(latencyWeighted / requestCount), + successRate: Math.round((successful / requestCount / 100) * 10000) / 10000, + }; +} diff --git a/open-sse/mcp-server/catalog.ts b/open-sse/mcp-server/catalog.ts index fcb0ea6dbeff..73f4ba09ef97 100644 --- a/open-sse/mcp-server/catalog.ts +++ b/open-sse/mcp-server/catalog.ts @@ -2,6 +2,7 @@ import { getCodexRequestDefaults } from "../../src/lib/providers/requestDefaults import { getProviderConnections } from "../../src/lib/db/providers.ts"; import { providerLacksModelListing } from "../../src/lib/providers/modelListingCapability.ts"; import { AI_PROVIDERS, NOAUTH_PROVIDERS } from "../../src/shared/constants/providers.ts"; +import { omniRouteFetch } from "./internalFetch.ts"; type JsonRecord = Record; type McpCatalogStatus = "available" | "degraded" | "unavailable"; @@ -301,8 +302,12 @@ export async function getMcpModelsCatalog( listProviderConnections?: () => Promise; } = {} ): Promise { - const fetchJson = - deps.fetchJson ?? ((path: string) => import("./server.ts").then((m) => m.omniRouteFetch(path))); + // #15159 M-06: a static import of the shared hop. This used to be + // `import("./server.ts").then((m) => m.omniRouteFetch(path))` — a workaround for the hop + // living inside the file that registers these tools, which closed the + // `server.ts -> catalog.ts` edge in the dependency graph (cycle 194). The hop is now a + // leaf module, so the round-trip through server.ts is unnecessary. + const fetchJson = deps.fetchJson ?? omniRouteFetch; const listProviderConnections = deps.listProviderConnections ?? getProviderConnections; const aliasMap = buildProviderAliasMap(); const normalizeProviderId = (value: string) => aliasMap[value] || value; diff --git a/open-sse/mcp-server/internalFetch.ts b/open-sse/mcp-server/internalFetch.ts new file mode 100644 index 000000000000..4d3c016f00fe --- /dev/null +++ b/open-sse/mcp-server/internalFetch.ts @@ -0,0 +1,68 @@ +/** + * The single server-to-server hop from an MCP tool to OmniRoute's own HTTP API. + * + * #15159 M-06 — this used to exist as three copies: + * + * server.ts:199 omniRouteFetch env read: lazy + * tools/advancedTools.ts:37 apiFetch env read: module load + * tools/pickFastestModel.ts:17 apiFetch env read: module load + * + * Three consequences, all fixed by having one: + * + * 1. The two tool-side copies snapshotted `OMNIROUTE_API_KEY` and the base URL at MODULE + * LOAD, so a key configured after import — the normal shape for a test, and for any + * process that sets its environment late — was silently dropped and the hop went out + * unauthenticated instead of failing loudly. Every read here is per-call. + * + * 2. The two catalogs reached the hop through `import("./server.ts")`, because the hop + * lived in the same file that registers the tools. That dynamic import closed the + * `server.ts -> catalog.ts` and `server.ts -> radarCatalog.ts` edges in the dependency + * graph (cycles 194 and 195). The hop is now a leaf both can import directly. + * + * 3. The copies hardcoded `AbortSignal.timeout(30000)` while server.ts used + * `mcpFetchTimeoutSignal("management")`, so the documented OMNIROUTE_MCP_FETCH_TIMEOUT_MS + * override silently did not apply to the advanced-tools and pick-fastest tool paths. + * Note this narrows that path's default from 30s to MCP_FETCH_TIMEOUT_MS (10s) — + * override the env knob if a tool legitimately needs longer. + * + * This module is also where M-01's prescribed split puts the hop, so that refactor can + * move the `handle*` bodies into `tools/canonical/*.ts` without re-introducing a cycle. + */ +import { getMcpHttpAuthHeadersForInternalFetch } from "./httpAuthContext.ts"; +import { getInternalServiceAuthHeaders } from "../../src/lib/api/internalServiceAuth.ts"; +import { resolveOmniRouteBaseUrl } from "../../src/shared/utils/resolveOmniRouteBaseUrl.ts"; +import { mcpFetchTimeoutSignal } from "./fetchTimeout.ts"; + +/** + * Read per-call, never at module load: a snapshot taken at import time silently drops a + * key that is set afterwards. + */ +function getOmniRouteApiKey(): string { + return process.env.OMNIROUTE_API_KEY || ""; +} + +export async function omniRouteFetch(path: string, options: RequestInit = {}): Promise { + const url = `${resolveOmniRouteBaseUrl()}${path}`; + const apiKey = getOmniRouteApiKey(); + const headers: Record = { + "Content-Type": "application/json", + // Static env key is only a fallback; the per-caller MCP identity forwarded via + // withMcpHttpAuthContext must win over it (#5819). + ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), + ...getMcpHttpAuthHeadersForInternalFetch(), + ...((options.headers as Record) || {}), + // Authenticate only the server-to-server hop. This does not replace or + // weaken the caller identity forwarded above. + ...getInternalServiceAuthHeaders(), + }; + + const signal = options.signal || mcpFetchTimeoutSignal("management"); + const response = await fetch(url, { ...options, headers, signal }); + + if (!response.ok) { + const errorText = await response.text().catch(() => "Unknown error"); + throw new Error(`OmniRoute API error [${response.status}]: ${errorText}`); + } + + return response.json(); +} diff --git a/open-sse/mcp-server/radarCatalog.ts b/open-sse/mcp-server/radarCatalog.ts index e17ce724bb07..cb07a7f2a645 100644 --- a/open-sse/mcp-server/radarCatalog.ts +++ b/open-sse/mcp-server/radarCatalog.ts @@ -5,6 +5,7 @@ import { radarCatalogInput, radarCatalogOutput } from "./schemas/radarCatalog.ts import type { McpToolExtraLike } from "./scopeEnforcement.ts"; import type { TextToolResult } from "./toolResult.ts"; import { sanitizeErrorMessage } from "../utils/error.ts"; +import { omniRouteFetch } from "./internalFetch.ts"; type JsonRecord = Record; @@ -111,9 +112,10 @@ export async function getMcpRadarCatalog( args: McpRadarCatalogArgs, deps: McpRadarCatalogDeps = {} ) { - const fetchJson = - deps.fetchJson ?? - ((path: string) => import("./server.ts").then((module) => module.omniRouteFetch(path))); + // #15159 M-06: static import of the shared hop. See the same note in catalog.ts — the + // `import("./server.ts")` workaround this replaces is what closed the + // `server.ts -> radarCatalog.ts` edge (cycle 195). + const fetchJson = deps.fetchJson ?? omniRouteFetch; const raw = record(await fetchJson("/api/radar/catalog")); const providerFilter = args.provider?.trim().toLowerCase(); const familyFilter = args.familyId?.trim().toLowerCase(); diff --git a/open-sse/mcp-server/server.ts b/open-sse/mcp-server/server.ts index 5b68d5270a5f..bf3ff8d3d8ee 100644 --- a/open-sse/mcp-server/server.ts +++ b/open-sse/mcp-server/server.ts @@ -43,13 +43,12 @@ import { startMcpHeartbeat } from "./runtimeHeartbeat.ts"; import { countUniqueMcpTools } from "./toolCount.ts"; import { z } from "zod"; import { closeAuditDb, logToolCall } from "./audit.ts"; +import { analyticsRangeForPeriod, readAnalyticsTotals } from "./analyticsShape.ts"; import { evaluateToolScopes, resolveCallerScopeContext, type McpToolExtraLike, } from "./scopeEnforcement.ts"; -import { getMcpHttpAuthHeadersForInternalFetch } from "./httpAuthContext.ts"; -import { getInternalServiceAuthHeaders } from "../../src/lib/api/internalServiceAuth.ts"; import { handleSimulateRoute, handleSetBudgetGuard, @@ -92,7 +91,6 @@ import { } from "../services/compression/engines/mcpAccessibility/constants.ts"; import { getDbInstance, ensureDbInitialized } from "../../src/lib/db/core.ts"; import { normalizeQuotaResponse } from "../../src/shared/contracts/quota.ts"; -import { resolveOmniRouteBaseUrl } from "../../src/shared/utils/resolveOmniRouteBaseUrl.ts"; import { isMcpScopeEnforcementEnabled } from "../../src/shared/utils/featureFlags.ts"; import { toSafeMcpErrorMessage } from "./errorMessage.ts"; import { mcpFetchTimeoutSignal } from "./fetchTimeout.ts"; @@ -101,7 +99,6 @@ import { registerRadarCatalogTool } from "./radarCatalog.ts"; import type { TextToolResult } from "./toolResult.ts"; export { getMcpModelsCatalog } from "./catalog.ts"; -const OMNIROUTE_BASE_URL = resolveOmniRouteBaseUrl(); const MCP_ALLOWED_SCOPES = new Set( (process.env.OMNIROUTE_MCP_SCOPES || "") .split(",") @@ -192,35 +189,17 @@ function normalizeComboModels( }); } -function getOmniRouteApiKey(): string { - return process.env.OMNIROUTE_API_KEY || ""; -} - -export async function omniRouteFetch(path: string, options: RequestInit = {}): Promise { - const url = `${OMNIROUTE_BASE_URL}${path}`; - const apiKey = getOmniRouteApiKey(); - const headers: Record = { - "Content-Type": "application/json", - // Static env key is only a fallback; the per-caller MCP identity forwarded via - // withMcpHttpAuthContext must win over it (#5819). - ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), - ...getMcpHttpAuthHeadersForInternalFetch(), - ...((options.headers as Record) || {}), - // Authenticate only the server-to-server hop. This does not replace or - // weaken the caller identity forwarded above. - ...getInternalServiceAuthHeaders(), - }; - - const signal = options.signal || mcpFetchTimeoutSignal("management"); - const response = await fetch(url, { ...options, headers, signal }); - - if (!response.ok) { - const errorText = await response.text().catch(() => "Unknown error"); - throw new Error(`OmniRoute API error [${response.status}]: ${errorText}`); - } +/** + * Re-exported rather than defined here, and imported (not just re-exported) because the + * tool handlers below call it by name — a bare `export … from` creates no local binding. + * + * #15159 M-06: the hop moved to its own leaf module so that `catalog.ts` and + * `radarCatalog.ts` can import it directly instead of reaching back into this file + * through `import("./server.ts")`, which closed two cycles in the dependency graph. + */ +import { omniRouteFetch } from "./internalFetch.ts"; - return response.json(); -} +export { omniRouteFetch }; function withScopeEnforcement( toolName: string, @@ -572,26 +551,20 @@ async function handleCostReport(args: { period?: string }) { const start = Date.now(); try { const period = args.period || "session"; - const rangeMap: Record = { - session: "1d", - day: "1d", - week: "7d", - month: "30d", - }; - const range = rangeMap[period] || "30d"; + const range = analyticsRangeForPeriod(period); const raw = toRecord( await omniRouteFetch(`/api/usage/analytics?range=${encodeURIComponent(range)}`) ); - const tokenCount = toRecord(raw.tokenCount); + const totals = readAnalyticsTotals(raw); const budget = toRecord(raw.budget); const result = { period, - totalCost: toNumber(raw.totalCost, 0), - requestCount: toNumber(raw.requestCount, 0), + totalCost: totals.totalCost, + requestCount: totals.requestCount, tokenCount: { - prompt: toNumber(tokenCount.prompt, 0), - completion: toNumber(tokenCount.completion, 0), + prompt: totals.promptTokens, + completion: totals.completionTokens, }, byProvider: toArray(raw.byProvider), byModel: toArray(raw.byModel), diff --git a/open-sse/mcp-server/tools/advancedTools.ts b/open-sse/mcp-server/tools/advancedTools.ts index 1f656e5aeba7..a0e5f2a104e0 100644 --- a/open-sse/mcp-server/tools/advancedTools.ts +++ b/open-sse/mcp-server/tools/advancedTools.ts @@ -17,11 +17,14 @@ */ import { logToolCall } from "../audit.ts"; +import { readAnalyticsTotals, readProviderMetrics } from "../analyticsShape.ts"; import { toSafeMcpErrorMessage } from "../errorMessage.ts"; -import { getMcpHttpAuthHeadersForInternalFetch } from "../httpAuthContext.ts"; -import { getInternalServiceAuthHeaders } from "../../../src/lib/api/internalServiceAuth.ts"; +// #15159 M-06: the hop lives in one place now. The private copy this replaced read +// OMNIROUTE_API_KEY and the base URL at module load, so a key configured after import was +// silently dropped; it also hardcoded a 30s timeout that ignored OMNIROUTE_MCP_FETCH_TIMEOUT_MS. +import { omniRouteFetch as apiFetch } from "../internalFetch.ts"; +import { mcpFetchTimeoutSignal } from "../fetchTimeout.ts"; import { normalizeQuotaResponse } from "../../../src/shared/contracts/quota.ts"; -import { resolveOmniRouteBaseUrl } from "../../../src/shared/utils/resolveOmniRouteBaseUrl.ts"; import { getComboModelProvider, getComboModelString, @@ -33,28 +36,6 @@ import type { } from "../../../src/shared/constants/routingStrategies.ts"; import { normalizeRoutingStrategy } from "../../../src/shared/constants/routingStrategies.ts"; -const OMNIROUTE_BASE_URL = resolveOmniRouteBaseUrl(); -const OMNIROUTE_API_KEY = process.env.OMNIROUTE_API_KEY || ""; - -async function apiFetch(path: string, options: RequestInit = {}): Promise { - const url = `${OMNIROUTE_BASE_URL}${path}`; - const headers: Record = { - "Content-Type": "application/json", - // Static env key is only a fallback; the per-caller MCP identity forwarded via - // withMcpHttpAuthContext must win over it (#5819). - ...(OMNIROUTE_API_KEY ? { Authorization: `Bearer ${OMNIROUTE_API_KEY}` } : {}), - ...getMcpHttpAuthHeadersForInternalFetch(), - ...((options.headers as Record) || {}), - ...getInternalServiceAuthHeaders(), - }; - const response = await fetch(url, { ...options, headers, signal: AbortSignal.timeout(30000) }); - if (!response.ok) { - const text = await response.text().catch(() => "Unknown error"); - throw new Error(`API [${response.status}]: ${text}`); - } - return response.json(); -} - type JsonRecord = Record; interface ComboModel { @@ -340,8 +321,8 @@ export async function handleSetBudgetGuard(args: { // Get current session cost let spent = 0; try { - const analytics = toRecord(await apiFetch("/api/usage/analytics?period=session")); - spent = toNumber(analytics.totalCost, 0); + const analytics = toRecord(await apiFetch("/api/usage/analytics?range=1d")); + spent = readAnalyticsTotals(analytics).totalCost; } catch { /* ignore if analytics not available */ } @@ -552,6 +533,13 @@ export async function handleTestCombo(args: { comboId: string; testPrompt: strin max_tokens: 50, stream: false, }), + // #15159 M-06 + #9717: this is the one hop in this module that waits on an + // upstream provider — every provider in the combo is probed in parallel, so a + // cold or slow one easily outlives the management-read budget. The module's + // deleted private copy hardcoded 30s; inheriting the shared hop would have + // silently dropped it to MCP_FETCH_TIMEOUT_MS (10s) and aborted live probes, + // which is exactly the failure #9717 was filed for on `route_request`. + signal: mcpFetchTimeoutSignal("upstream"), }) ); const usage = toRecord(resp.usage); @@ -626,7 +614,7 @@ export async function handleGetProviderMetrics(args: { provider: string }) { const [healthRaw, quotaRaw, analyticsRaw] = await Promise.allSettled([ apiFetch("/api/monitoring/health"), apiFetch(`/api/usage/quota?provider=${encodeURIComponent(args.provider)}`), - apiFetch(`/api/usage/analytics?period=session&provider=${encodeURIComponent(args.provider)}`), + apiFetch(`/api/usage/analytics?range=1d&provider=${encodeURIComponent(args.provider)}`), ]); const health = healthRaw.status === "fulfilled" ? toRecord(healthRaw.value) : {}; @@ -641,11 +629,13 @@ export async function handleGetProviderMetrics(args: { provider: string }) { ); const providerQuota = quota.providers.find((p) => p.provider === args.provider) || null; + const providerMetrics = readProviderMetrics(analytics, args.provider); + const result = { provider: args.provider, - successRate: toNumber(analytics.successRate, 1.0), - requestCount: toNumber(analytics.requestCount, 0), - avgLatencyMs: toNumber(analytics.avgLatencyMs, 0), + successRate: providerMetrics.successRate, + requestCount: providerMetrics.requestCount, + avgLatencyMs: providerMetrics.avgLatencyMs, p50LatencyMs: toNumber(analytics.p50LatencyMs, 0), p95LatencyMs: toNumber(analytics.p95LatencyMs, 0), p99LatencyMs: toNumber(analytics.p99LatencyMs, 0), @@ -841,21 +831,19 @@ export async function handleSyncPricing(args: { sources?: string[]; dryRun?: boo export async function handleGetSessionSnapshot() { const start = Date.now(); try { - const analytics = toRecord( - await apiFetch("/api/usage/analytics?period=session").catch(() => ({})) - ); - const tokenCount = toRecord(analytics.tokenCount); + const analytics = toRecord(await apiFetch("/api/usage/analytics?range=1d").catch(() => ({}))); + const totals = readAnalyticsTotals(analytics); const byModel = toArrayOfRecords(analytics.byModel); const byProvider = toArrayOfRecords(analytics.byProvider); const result = { sessionStart: toString(analytics.sessionStart, new Date().toISOString()), duration: toString(analytics.duration, "unknown"), - requestCount: toNumber(analytics.requestCount, 0), - costTotal: toNumber(analytics.totalCost, 0), + requestCount: totals.requestCount, + costTotal: totals.totalCost, tokenCount: { - prompt: toNumber(tokenCount.prompt, 0), - completion: toNumber(tokenCount.completion, 0), + prompt: totals.promptTokens, + completion: totals.completionTokens, }, topModels: byModel.slice(0, 5).map((model) => ({ model: toString(model.model, "unknown"), diff --git a/open-sse/mcp-server/tools/pickFastestModel.ts b/open-sse/mcp-server/tools/pickFastestModel.ts index 680c9f370d76..90afe52a7cfb 100644 --- a/open-sse/mcp-server/tools/pickFastestModel.ts +++ b/open-sse/mcp-server/tools/pickFastestModel.ts @@ -1,9 +1,9 @@ import { logToolCall } from "../audit.ts"; import { toSafeMcpErrorMessage } from "../errorMessage.ts"; -import { getMcpHttpAuthHeadersForInternalFetch } from "../httpAuthContext.ts"; -import { getInternalServiceAuthHeaders } from "../../../src/lib/api/internalServiceAuth.ts"; +// #15159 M-06: one shared hop. The private copy this replaced read OMNIROUTE_API_KEY and +// the base URL at module load and hardcoded a 30s timeout. +import { omniRouteFetch as apiFetch } from "../internalFetch.ts"; import { normalizeQuotaResponse } from "../../../src/shared/contracts/quota.ts"; -import { resolveOmniRouteBaseUrl } from "../../../src/shared/utils/resolveOmniRouteBaseUrl.ts"; import { getComboModelProvider, getComboModelString, @@ -13,26 +13,6 @@ import type { AutoRoutingStrategyValue } from "../../../src/shared/constants/rou import { rankBySpeed, DEFAULT_SPEED_WEIGHTS } from "../../services/autoCombo/speedRanking.ts"; import type { SpeedCandidate } from "../../services/autoCombo/speedRanking.ts"; -const OMNIROUTE_BASE_URL = resolveOmniRouteBaseUrl(); -const OMNIROUTE_API_KEY = process.env.OMNIROUTE_API_KEY || ""; - -async function apiFetch(path: string, options: RequestInit = {}): Promise { - const url = `${OMNIROUTE_BASE_URL}${path}`; - const headers: Record = { - "Content-Type": "application/json", - ...(OMNIROUTE_API_KEY ? { Authorization: `Bearer ${OMNIROUTE_API_KEY}` } : {}), - ...getMcpHttpAuthHeadersForInternalFetch(), - ...((options.headers as Record) || {}), - ...getInternalServiceAuthHeaders(), - }; - const response = await fetch(url, { ...options, headers, signal: AbortSignal.timeout(30000) }); - if (!response.ok) { - const text = await response.text().catch(() => "Unknown error"); - throw new Error(`API [${response.status}]: ${text}`); - } - return response.json(); -} - type JsonRecord = Record; interface ComboModel { provider: string; @@ -112,7 +92,7 @@ async function fetchTelemetrySources(): Promise { apiFetch("/api/combos"), apiFetch("/api/monitoring/health"), apiFetch("/api/usage/quota"), - apiFetch("/api/usage/analytics?period=session"), + apiFetch("/api/usage/analytics?range=1d"), ]); const analytics = toRecord(settledValue(analyticsRaw)); diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index 0801ddca9784..22912dbea02e 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -2109,6 +2109,11 @@ export function checkFallbackError( ); if (forbiddenMatch) return ruleScopedResult(forbiddenMatch); } + // 429 reading "endpoint is unavailable" fails over to the rule-owned model cooldown. + if (status === HTTP_STATUS.RATE_LIMITED && provider) { + const unavailable = getOpencodeModelUnavailableMatch(provider, status, headers, errorStr); + if (unavailable) return ruleScopedResult(unavailable); + } if ( status === HTTP_STATUS.FORBIDDEN && diff --git a/open-sse/services/antigravityQuotaFamily.ts b/open-sse/services/antigravityQuotaFamily.ts index bfdbfa8d05ff..1cabf082ed1f 100644 --- a/open-sse/services/antigravityQuotaFamily.ts +++ b/open-sse/services/antigravityQuotaFamily.ts @@ -76,6 +76,20 @@ export function quotaWindowNamesForScope( return scoped.length > 0 ? scoped : names; } +/** + * Finite `percentUsed` from a raw quota field, or `null` when it is absent, blank or + * non-numeric. `Number(null)`, `Number("")`, `Number(" ")` and `Number(false)` are all 0, + * which would read an unreported window as "0% used" (#15347). + */ +export function finitePercentUsed(raw: unknown): number | null { + if (typeof raw === "number") return Number.isFinite(raw) ? raw : null; + if (typeof raw === "string" && raw.trim() !== "") { + const parsed = Number(raw); + return Number.isFinite(parsed) ? parsed : null; + } + return null; +} + /** Min remaining % across scoped windows, or 100 when an Antigravity family scope matched none. */ export function remainingPercentFromQuotaWindows( rawWindows: Record, @@ -87,8 +101,8 @@ export function remainingPercentFromQuotaWindows( for (const name of namesToScan) { const windowInfo = rawWindows[name]; if (!windowInfo || typeof windowInfo !== "object") continue; - const percentUsed = Number((windowInfo as Record).percentUsed); - if (!Number.isFinite(percentUsed)) continue; + const percentUsed = finitePercentUsed((windowInfo as Record).percentUsed); + if (percentUsed === null) continue; const remaining = Math.max(0, Math.min(100, (1 - percentUsed) * 100)); minRemaining = minRemaining === null ? remaining : Math.min(minRemaining, remaining); } @@ -137,9 +151,7 @@ export function selectAntigravityQuotaWindowNames( // unrelated Gemini sibling can never dilute exact-model exhaustion. This is // intentionally version-agnostic for newly discovered Gemini Flash releases. if (exactWindows.length === 0) { - const flashMatch = bareModel.match( - /^(gemini-\d+(?:\.\d+)*-flash)(?:-(?:high|medium|low))?$/ - ); + const flashMatch = bareModel.match(/^(gemini-\d+(?:\.\d+)*-flash)(?:-(?:high|medium|low))?$/); if (flashMatch) { const technicalTieredModel = `${flashMatch[1]}-tiered`; exactWindows = quotaNames.filter((windowName) => { @@ -153,5 +165,7 @@ export function selectAntigravityQuotaWindowNames( const scoped = [...exactWindows, ...aggregateWindows]; if (scoped.length > 0) return scoped; - return quotaNames.filter((windowName) => getAntigravityQuotaFamily(windowName) === requestedFamily); + return quotaNames.filter( + (windowName) => getAntigravityQuotaFamily(windowName) === requestedFamily + ); } diff --git a/open-sse/services/autoCombo/resilienceCandidateFilter.ts b/open-sse/services/autoCombo/resilienceCandidateFilter.ts index 9a0b168ace42..8aaf9a1f3024 100644 --- a/open-sse/services/autoCombo/resilienceCandidateFilter.ts +++ b/open-sse/services/autoCombo/resilienceCandidateFilter.ts @@ -87,7 +87,7 @@ export function filterResilienceBlockedCandidates { if (c.resolvedSupportsVision !== undefined) { - return c.resolvedSupportsVision || isVisionModelId(c.model); + // #vision-pool: same bridge exclusion as the on-demand path below — + // registry entries whose catalog overstates vision support are forced + // through the vision bridge and must never be picked as vision-capable. + const capable = c.resolvedSupportsVision || isVisionModelId(c.model); + if (!capable) return false; + return !isVisionBridgeForcedModel(`${c.provider}/${c.model}`); } try { const caps = getResolvedModelCapabilities({ provider: c.provider, model: c.model }); const capable = caps.supportsVision === true || isVisionModelId(c.model); if (!capable) return false; - // #vison-pool: registry entries whose catalog OVERSTATES vision support + // #vision-pool: registry entries whose catalog OVERSTATES vision support // (opencode-go/opencode-zen/tokenrouter — the backend models are text-only) // are forced through the vision bridge by isVisionBridgeForcedModel. // They must never be selected as the vision-capable candidate itself. diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index ac8de9b1b34d..ef37acd6e680 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -239,20 +239,40 @@ export function poolMedianP95Ms( } const BOOTSTRAP_WARN_WINDOW_MS = 3600_000; -export let bootstrapLatencyHits = 0; // exported for testability (reset in tests) -export let bootstrapLatencyTotal = 0; +export type BootstrapSource = "table" | "pool-median" | "constant"; +export const bootstrapSourceCounts: Record = { + table: 0, + "pool-median": 0, + constant: 0, +}; let bootstrapWarnedAt = 0; export function resetBootstrapCounters(): void { - bootstrapLatencyHits = 0; - bootstrapLatencyTotal = 0; + bootstrapSourceCounts.table = 0; + bootstrapSourceCounts["pool-median"] = 0; + bootstrapSourceCounts.constant = 0; bootstrapWarnedAt = 0; } +// Table lookup shared by the bootstrap provenance helpers below. Exact +// normalization only (no provider/ prefix strip): callers pass parsed.model, +// so stripping here would be dead code. Pricing strips prefixes; combo does not. +function lookupTable(model: string): number | undefined { + return DEFAULT_MODEL_P95_MS[String(model || "").toLowerCase()]; +} +// Single provenance authority: classifies a pre-resolved table value plus the +// pool median. bootstrapMs routes through it so the hot path performs +// exactly one table lookup on every call. +export function bootstrapSourceFromTable( + table: number | undefined, + poolMedian: number | undefined +): BootstrapSource { + if (table !== undefined) return "table"; + if (poolMedian !== undefined) return "pool-median"; + return "constant"; +} export function bootstrapMs(model: string, poolMedian: number | undefined): number { - bootstrapLatencyTotal++; - const table = DEFAULT_MODEL_P95_MS[String(model || "").toLowerCase()]; - if (table !== undefined) return table; - bootstrapLatencyHits++; - return poolMedian ?? 1500; + const table = lookupTable(model); + bootstrapSourceCounts[bootstrapSourceFromTable(table, poolMedian)]++; + return table ?? poolMedian ?? 1500; } // Pure and testable without timers: the throttled 1h warn + cold-start exemption live here. @@ -268,11 +288,22 @@ export function shouldWarnBootstrap( return now - lastWarn >= BOOTSTRAP_WARN_WINDOW_MS; } -function maybeWarnBootstrapDominant(hasStats: boolean): void { +// Deterministic on the module counters (not pure): reads bootstrapSourceCounts. +// Table hits stay in the denominator to preserve the 30% threshold semantics +// but out of the message — only estimates are reported. +export function formatBootstrapWarning(): string { + const { table, "pool-median": pm, constant } = bootstrapSourceCounts; + const total = table + pm + constant; + const hits = pm + constant; + return `[combo] bootstrap latency dominant (${hits}/${total}, pool-median: ${pm}, constant: ${constant}) — scoring runs on guesses`; +} + +export function maybeWarnBootstrapDominant(hasStats: boolean): void { + const { table, "pool-median": pm, constant } = bootstrapSourceCounts; if ( !shouldWarnBootstrap( - bootstrapLatencyHits, - bootstrapLatencyTotal, + pm + constant, + table + pm + constant, hasStats, Date.now(), bootstrapWarnedAt @@ -280,9 +311,7 @@ function maybeWarnBootstrapDominant(hasStats: boolean): void { ) return; bootstrapWarnedAt = Date.now(); - console.warn( - `[combo] bootstrap latency dominant (${bootstrapLatencyHits}/${bootstrapLatencyTotal}) — scoring runs on guesses` - ); + console.warn(formatBootstrapWarning()); } export async function buildAutoCandidates( @@ -458,6 +487,7 @@ export async function buildAutoCandidates( // time (scoreAutoTargets → STATUS_SOFT_DEPRIORITIZE_FACTOR) instead. let statusPenalty = false; let statusPenaltyReason: string | undefined; + let quotaUnreadable = false; if (statusCutoffReason) { quotaCutoffBlocked = true; quotaCutoffReason = statusCutoffReason; @@ -488,10 +518,22 @@ export async function buildAutoCandidates( const quota = await quotaPromises.get(quotaKey)!; resetWindowAffinity = calculateAutoResetWindowAffinity(quota, resetWindowConfig); if (!quotaCutoffBlocked) { - quotaRemaining = quotaRemainingPercentFromQuota(quota, { + const remaining = quotaRemainingPercentFromQuota(quota, { provider, requestedModel: modelStr, }); + if (remaining === null) { + // #15347: this provider HAS a quota fetcher but gave us nothing readable (failed + // fetch, missing credentials, message-only or malformed payload). That is evidence + // about the telemetry, not the provider, so it must not outrank providers that + // reported a real percentage. Worst on the quota axis plus a soft penalty at + // scoring time (like #4540), so it ranks strictly below any real reading without + // being blocked or evicted. Unlimited plans report `unlimited: true` and score 100. + quotaRemaining = 0; + quotaUnreadable = true; + } else { + quotaRemaining = remaining; + } } if (!quotaCutoffBlocked && quotaCutoffEnabled) { const cutoffDecision = evaluateQuotaCutoff( @@ -540,6 +582,7 @@ export async function buildAutoCandidates( quotaCutoffReason, statusPenalty, statusPenaltyReason, + quotaUnreadable, connectionPoolSize: connectionPoolCounts.get(provider) ?? 1, connectionId: target.connectionId ?? undefined, authType, diff --git a/open-sse/services/combo/autoStrategy.ts b/open-sse/services/combo/autoStrategy.ts index 4db43b580dcf..f60aaf457e4d 100644 --- a/open-sse/services/combo/autoStrategy.ts +++ b/open-sse/services/combo/autoStrategy.ts @@ -76,6 +76,13 @@ export const STATUS_SOFT_DEPRIORITIZE_FACTOR = Number( process.env.STATUS_SOFT_DEPRIORITIZE_FACTOR ?? "0.5" ); +// #15347: unreadable-quota soft-deprioritization factor. +// A candidate whose provider has a quota fetcher that returned nothing readable +// (quotaUnreadable) already scores 0 on the quota axis; this multiplier makes it strictly +// lower than even a real exhausted reading with otherwise identical factors, without +// blocking or evicting it. Routing then prefers providers whose usage we can actually see. +export const UNREADABLE_QUOTA_SOFT_DEPRIORITIZE_FACTOR = 0.5; + // G2: Module-level registry of active combo execution candidates. // Maps executionKey → Map. // Populated by buildAutoCandidates registrations; cleaned up after each execution. @@ -406,6 +413,10 @@ export function scoreAutoTargets( if ("statusPenalty" in candidate && candidate.statusPenalty === true) { score *= STATUS_SOFT_DEPRIORITIZE_FACTOR; } + // #15347: malformed quota snapshot — penalise, never block. + if ("quotaUnreadable" in candidate && candidate.quotaUnreadable === true) { + score *= UNREADABLE_QUOTA_SOFT_DEPRIORITIZE_FACTOR; + } return { target, factors, @@ -516,7 +527,8 @@ export async function expandAutoComboCandidatePool( // A provider whose custom rows are all non-chat still has user models, so it // must not fall back to its static chat catalog. const hasUserModels = - userVisibleIds.size > 0 || customModels.some((m) => m.id && !hiddenModels?.has(m.id)); + userVisibleIds.size > 0 || + customModels.some((m: { id?: string }) => m.id && !hiddenModels?.has(m.id)); const expandIds = hasUserModels ? Array.from(userVisibleIds) : getProviderModels(providerId).map((m) => m.id); diff --git a/open-sse/services/combo/comboAttemptLoop.ts b/open-sse/services/combo/comboAttemptLoop.ts index a324ecab3e65..fc2ec1e03def 100644 --- a/open-sse/services/combo/comboAttemptLoop.ts +++ b/open-sse/services/combo/comboAttemptLoop.ts @@ -19,6 +19,8 @@ import { errorResponseWithComboDiagnostics, unavailableResponse, } from "../../utils/error.ts"; +import { estimateSizeFast } from "../../utils/estimateSize.ts"; +import { jsonLength } from "../../utils/jsonSize.ts"; import { COMBO_FAILURE_THRESHOLD, recordComboFailure } from "./failureTracker.ts"; import { buildAllTargetsCoolingDownResponse, @@ -61,6 +63,25 @@ import { executeTargetAttempt } from "./executeTargetAttempt.ts"; import { buildComboDiag } from "./executeTargetClassify.ts"; import type { AttemptLoopDeps, AttemptLoopState, ExecuteTargetResult } from "./attemptLoopTypes.ts"; +/** A second in-flight copy of a body larger than this sits in the TLS send buffer. */ +const HEDGE_MAX_BODY_BYTES = 256 * 1024; + +/** + * Hedging sends the body twice. The fast estimate rejects oversized values + * without allocating a JSON string. `jsonLength` then confirms the exact + * serialized size; a cycle or BigInt makes it throw, and that unknown size + * must not hedge — recording 0 bytes would send the body twice anyway. + */ +function isBodySmallEnoughToHedge(body: unknown): boolean { + try { + const estimated = estimateSizeFast(body, HEDGE_MAX_BODY_BYTES); + if (!Number.isFinite(estimated) || estimated > HEDGE_MAX_BODY_BYTES) return false; + return jsonLength(body) <= HEDGE_MAX_BODY_BYTES; + } catch { + return false; + } +} + /** * Resolve the earliest known connection cooldown across every target's eligible * pool. A target may pin one connection, allowlist several, or leave selection to @@ -250,6 +271,10 @@ export async function dispatchWithCooldownRetry(opts: { state.abortControllers = new Map(); const rejectedModelKeys = (state.requestScopedRejectedModelKeys ??= new Set()); const zeroLatencyOptimizationsEnabled = deps.config.zeroLatencyOptimizationsEnabled === true; + // A hedged target sends the same body a second time. Bodies past this + // size sit in the native TLS send buffer until the network drains them, + // and a slow upstream holds both copies for the whole headers wait. + const bodySmallEnoughToHedge = isBodySmallEnoughToHedge(deps.body); const hasProtectedPriorityTarget = deps.strategy === "priority" && state.orderedTargets.some((target) => target.fallbackOnlyOnQuotaExhaustion === true); @@ -323,6 +348,7 @@ export async function dispatchWithCooldownRetry(opts: { if ( zeroLatencyOptimizationsEnabled && deps.config.hedging && + bodySmallEnoughToHedge && !hasProtectedPriorityTarget && i + 1 < state.orderedTargets.length ) { diff --git a/open-sse/services/combo/comboPredicates.ts b/open-sse/services/combo/comboPredicates.ts index b7bec951f36d..45d43d22a71d 100644 --- a/open-sse/services/combo/comboPredicates.ts +++ b/open-sse/services/combo/comboPredicates.ts @@ -7,7 +7,7 @@ */ import { EXECUTOR_CONTRACT_VIOLATION_CODE } from "../../config/constants.ts"; -import { remainingPercentFromQuotaWindows } from "../antigravityQuotaFamily.ts"; +import { finitePercentUsed, remainingPercentFromQuotaWindows } from "../antigravityQuotaFamily.ts"; import { errorResponse } from "../../utils/error.ts"; import { parseModel } from "../model.ts"; import { isSelfInflictedUpstreamTimeout } from "../../handlers/chatCore/cooldownClassification.ts"; @@ -323,6 +323,29 @@ export function isRequestScopedUpstreamFailure(error?: { ); } +export function isComboTargetTimeoutFailure(error?: { + code?: string | null; + type?: string | null; +}): boolean { + const code = typeof error?.code === "string" ? error.code.toLowerCase() : ""; + const type = typeof error?.type === "string" ? error.type.toLowerCase() : ""; + return code === "combo_target_timeout" || type === "combo_target_timeout"; +} + +/** + * Model lockout is per model, not per provider. A local target timeout must + * lock that model so the next request does not spend another gate wait on it. + * Other request-scoped failures (context length, local queue) stay unlocked. + * The provider breaker still uses the request-scoped flag and does not see this. + */ +export function shouldRecordModelLockoutForComboFailure( + requestScopedFailure: boolean, + error?: { code?: string | null; type?: string | null } +): boolean { + if (!requestScopedFailure) return true; + return isComboTargetTimeoutFailure(error); +} + /** Request-scoped classification that also has access to the HTTP body. */ export function isComboRequestScopedFailure( response: Response, @@ -580,12 +603,23 @@ export function clampPercent(value: number): number { return Math.max(0, Math.min(100, value)); } +/** + * Remaining quota (0..100) from a fetched quota snapshot, or `null` when the snapshot is + * UNREADABLE: missing, not an object, or an object with no parseable window and no finite + * `percentUsed` (#15347). A telemetry failure is evidence about the telemetry, not the + * provider, so it must never be reported as full quota; callers decide how it ranks. + * + * `null` from a quota fetcher means "could not read it" (network error, missing credentials, + * message-only usage). A provider with no cap is NOT that: it reports `unlimited: true` + * (see `convertUsageToQuotaInfo`), which is a real reading of full headroom. + */ export function quotaRemainingPercentFromQuota( quota: unknown, scope?: { provider?: string | null; requestedModel?: string | null } -): number { - if (!quota || typeof quota !== "object") return 100; +): number | null { + if (!quota || typeof quota !== "object") return null; const record = quota as Record; + if (record.unlimited === true) return 100; const windows = record.windows; if (windows && typeof windows === "object" && !Array.isArray(windows)) { @@ -595,9 +629,9 @@ export function quotaRemainingPercentFromQuota( if (record.limitReached === true) return 0; - const percentUsed = Number(record.percentUsed); - if (Number.isFinite(percentUsed)) return clampPercent((1 - percentUsed) * 100); - return 100; + const percentUsed = finitePercentUsed(record.percentUsed); + if (percentUsed !== null) return clampPercent((1 - percentUsed) * 100); + return null; } export const QUOTA_BLOCKING_CONNECTION_STATUSES = new Set([ diff --git a/open-sse/services/combo/dispatchPrelude.ts b/open-sse/services/combo/dispatchPrelude.ts index 32efb3dbd8ce..4b28e6872dba 100644 --- a/open-sse/services/combo/dispatchPrelude.ts +++ b/open-sse/services/combo/dispatchPrelude.ts @@ -51,6 +51,7 @@ import { releaseRejectedQualityResponse, validateResponseQuality, } from "./validateQuality.ts"; +import { isTrustedEmptyTurn } from "./emptyTurnTrust.ts"; import type { ComboCollectionLike, ComboLike, @@ -236,7 +237,9 @@ async function evaluatePinnedResponse(args: { pinnedClone, clientRequestedStream, log, - config.responseValidation + config.responseValidation, + null, + await isTrustedEmptyTurn(parseModel(pinnedModel).provider, pinnedResult) ); releaseQualityClone(pinnedClone, pinnedResult, pinnedQuality); if (pinnedQuality.valid) return pinnedResult; diff --git a/open-sse/services/combo/emptyTurnTrust.ts b/open-sse/services/combo/emptyTurnTrust.ts new file mode 100644 index 000000000000..28bc5e3bf667 --- /dev/null +++ b/open-sse/services/combo/emptyTurnTrust.ts @@ -0,0 +1,29 @@ +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { isOfficialAnthropicBaseUrl } from "../../utils/anthropicHost.ts"; + +/** Only the selected first-party Anthropic connection can complete without content blocks. */ +export async function isTrustedEmptyTurn( + provider: string | null, + response: Response, + fallbackConnectionId?: string | null +): Promise { + if (provider !== null && provider !== "claude" && provider !== "anthropic") return false; + + const selectedConnectionId = response.headers.get("X-OmniRoute-Selected-Connection-Id"); + // An unresolved alias cannot use a planned connection as proof of actual dispatch. + const connectionId = selectedConnectionId || (provider === null ? null : fallbackConnectionId); + if (!connectionId) return false; + + try { + const connection = await getCachedProviderConnectionById(connectionId); + if (connection?.provider !== "claude" && connection?.provider !== "anthropic") return false; + if (provider !== null && connection.provider !== provider) return false; + const data = connection.providerSpecificData; + if (data != null && (typeof data !== "object" || Array.isArray(data))) return false; + const baseUrl = (data as Record | null)?.baseUrl; + if (baseUrl != null && typeof baseUrl !== "string") return false; + return isOfficialAnthropicBaseUrl(typeof baseUrl === "string" ? baseUrl : ""); + } catch { + return false; + } +} diff --git a/open-sse/services/combo/executeTargetAttempt.ts b/open-sse/services/combo/executeTargetAttempt.ts index 91e73052280b..563e6b184bef 100644 --- a/open-sse/services/combo/executeTargetAttempt.ts +++ b/open-sse/services/combo/executeTargetAttempt.ts @@ -60,6 +60,7 @@ import { shouldSkipForPredictedTtft, shouldRecordProviderBreakerFailure, isComboRequestScopedFailure as isScopedFailure, + shouldRecordModelLockoutForComboFailure, isStreamReadinessFailureErrorBody, isStreamEarlyEofErrorBody, isLocalKeyPolicyBreachErrorBody, @@ -79,6 +80,7 @@ import { releaseQualityClone, releaseRejectedQualityResponse, } from "./validateQuality.ts"; +import { isTrustedEmptyTurn } from "./emptyTurnTrust.ts"; import { isQuotaExhaustionResponse, recordQuotaExhaustionClassification, @@ -386,7 +388,9 @@ export async function executeTargetAttempt(opts: { qualityClone, deps.clientRequestedStream, deps.log, - deps.config.responseValidation as ResponseValidationConfig | null | undefined + deps.config.responseValidation as ResponseValidationConfig | null | undefined, + null, + await isTrustedEmptyTurn(provider, result, target.connectionId) ); releaseQualityClone(qualityClone, result, quality); if (!quality.valid) { @@ -1077,7 +1081,7 @@ export async function executeTargetAttempt(opts: { provider && rawModel && retry === 0 && - !scopedFailure && + shouldRecordModelLockoutForComboFailure(scopedFailure, structuredError) && !isConnectionScopedClaudeQuota ) { const mlSettings = resolveModelLockoutSettings(deps.settings); @@ -1167,7 +1171,12 @@ export async function executeTargetAttempt(opts: { if (i > 0) state.fallbackCount++; // Wire combo failures into the resilience dashboard (model-level lockout) // alongside the provider-level cooldown below — they govern different scopes. - if (provider && rawModel && !scopedFailure && !isConnectionScopedClaudeQuota) { + if ( + provider && + rawModel && + shouldRecordModelLockoutForComboFailure(scopedFailure, structuredError) && + !isConnectionScopedClaudeQuota + ) { const mlSettings = resolveModelLockoutSettings(deps.settings); if (mlSettings.enabled && mlSettings.errorCodes.includes(result.status)) { recordModelLockoutFailure( diff --git a/open-sse/services/combo/quotaScoring.ts b/open-sse/services/combo/quotaScoring.ts index 1b415f490cb5..e8e1549fb444 100644 --- a/open-sse/services/combo/quotaScoring.ts +++ b/open-sse/services/combo/quotaScoring.ts @@ -18,10 +18,14 @@ import { resolveProviderId } from "../../../src/shared/constants/providers.ts"; const RESET_AWARE_SESSION_WINDOW_MS = 5 * 60 * 60 * 1000; const RESET_AWARE_WEEKLY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000; +const RESET_AWARE_MONTHLY_WINDOW_MS = 30 * 24 * 60 * 60 * 1000; const RESET_AWARE_SESSION_REMAINING_WEIGHT = 0.45; const RESET_AWARE_SESSION_RESET_PRESSURE_WEIGHT = 0.55; const RESET_AWARE_WEEKLY_REMAINING_WEIGHT = 0.25; const RESET_AWARE_WEEKLY_RESET_PRESSURE_WEIGHT = 0.75; +const RESET_AWARE_MONTHLY_WEIGHT = 0.25; +const RESET_AWARE_MONTHLY_REMAINING_WEIGHT = 0.25; +const RESET_AWARE_MONTHLY_RESET_PRESSURE_WEIGHT = 0.75; const RESET_AWARE_DEFAULTS = { sessionWeight: 0.35, weeklyWeight: 0.65, @@ -314,9 +318,8 @@ function scoreQuotaWindow( } /** - * Fraction of each window still available, 0-1, with the same fallbacks the - * score uses: a missing window reads the snapshot-wide percentUsed, and a - * snapshot with no usable number at all reads as half spent. + * Session and weekly windows use the same fallback as the score when absent. + * Monthly quota participates only when its window is present. * * Shared by the score and the leftover percent on purpose. If the two ever * resolved a window differently, an account could land in one pool while @@ -326,11 +329,16 @@ function resolveWindowRemaining(quota: Record) { const overallPercentUsed = clamp01(finiteNumberOrNull(quota.percentUsed) ?? 0.5); const sessionWindow = resolveQuotaWindowByName(quota, "session"); const weeklyWindow = resolveQuotaWindowByName(quota, "weekly"); + const monthlyWindow = resolveQuotaWindowByName(quota, "monthly"); return { sessionWindow, weeklyWindow, + monthlyWindow, sessionRemaining: clamp01(1 - (sessionWindow?.percentUsed ?? overallPercentUsed)), weeklyRemaining: clamp01(1 - (weeklyWindow?.percentUsed ?? overallPercentUsed)), + monthlyRemaining: monthlyWindow + ? clamp01(1 - (monthlyWindow.percentUsed ?? overallPercentUsed)) + : null, }; } @@ -341,8 +349,14 @@ export function scoreResetAwareQuota( if (!quota || !isRecord(quota)) return { score: 0.5 }; if (quota.limitReached === true) return { score: -Infinity }; - const { sessionWindow, weeklyWindow, sessionRemaining, weeklyRemaining } = - resolveWindowRemaining(quota); + const { + sessionWindow, + weeklyWindow, + monthlyWindow, + sessionRemaining, + weeklyRemaining, + monthlyRemaining, + } = resolveWindowRemaining(quota); const sessionScore = scoreQuotaWindow( sessionRemaining, sessionWindow?.resetAt, @@ -358,6 +372,16 @@ export function scoreResetAwareQuota( RESET_AWARE_WEEKLY_RESET_PRESSURE_WEIGHT ); let score = config.sessionWeight * sessionScore + config.weeklyWeight * weeklyScore; + if (monthlyWindow && monthlyRemaining !== null) { + const monthlyScore = scoreQuotaWindow( + monthlyRemaining, + monthlyWindow.resetAt, + RESET_AWARE_MONTHLY_WINDOW_MS, + RESET_AWARE_MONTHLY_REMAINING_WEIGHT, + RESET_AWARE_MONTHLY_RESET_PRESSURE_WEIGHT + ); + score = (1 - RESET_AWARE_MONTHLY_WEIGHT) * score + RESET_AWARE_MONTHLY_WEIGHT * monthlyScore; + } if (config.exhaustionGuard > 0 && sessionRemaining < config.exhaustionGuard) { score *= Math.max(0.05, sessionRemaining / config.exhaustionGuard); @@ -451,8 +475,12 @@ export function scoreExpiryFirstQuota( export function getResetAwareRemainingPercent(quota: unknown): number { if (!quota || !isRecord(quota)) return 100; if (quota.limitReached === true) return 0; - const { sessionRemaining, weeklyRemaining } = resolveWindowRemaining(quota); - return Number((Math.min(sessionRemaining, weeklyRemaining) * 100).toFixed(6)); + const { sessionRemaining, weeklyRemaining, monthlyRemaining } = resolveWindowRemaining(quota); + const remaining = + monthlyRemaining === null + ? Math.min(sessionRemaining, weeklyRemaining) + : Math.min(sessionRemaining, weeklyRemaining, monthlyRemaining); + return Number((remaining * 100).toFixed(6)); } /** diff --git a/open-sse/services/combo/recordLkgpPin.ts b/open-sse/services/combo/recordLkgpPin.ts index eeb63885c08f..2428a2ac408a 100644 --- a/open-sse/services/combo/recordLkgpPin.ts +++ b/open-sse/services/combo/recordLkgpPin.ts @@ -24,9 +24,14 @@ async function writePins( setLKGPFn: SetLkgp | undefined ): Promise { const set = setLKGPFn ?? (await import("@/lib/db/settings")).setLKGP; + const comboKey = comboId || comboName; + if (comboKey === comboName) { + await set(comboName, comboKey, provider, connectionId); + return; + } await Promise.all([ set(comboName, executionKey, provider, connectionId), - set(comboName, comboId || comboName, provider, connectionId), + set(comboName, comboKey, provider, connectionId), ]); } diff --git a/open-sse/services/combo/roundRobinCombo.ts b/open-sse/services/combo/roundRobinCombo.ts index 7cad80aef594..40ce67c842e1 100644 --- a/open-sse/services/combo/roundRobinCombo.ts +++ b/open-sse/services/combo/roundRobinCombo.ts @@ -81,6 +81,7 @@ import { releaseRejectedQualityResponse, toRetryAfterDisplayValue, } from "./validateQuality.ts"; +import { isTrustedEmptyTurn } from "./emptyTurnTrust.ts"; import { TRANSIENT_FOR_SEMAPHORE, MAX_FALLBACK_WAIT_MS, @@ -734,7 +735,9 @@ export async function handleRoundRobinCombo({ rrClone, clientRequestedStream, log, - config.responseValidation + config.responseValidation, + null, + await isTrustedEmptyTurn(provider, result, targetForAttempt.connectionId) ); releaseQualityClone(rrClone, result, quality); if (!quality.valid) { diff --git a/open-sse/services/combo/runtimeUnits.ts b/open-sse/services/combo/runtimeUnits.ts index 86f4ffc68202..5a0a2a31892f 100644 --- a/open-sse/services/combo/runtimeUnits.ts +++ b/open-sse/services/combo/runtimeUnits.ts @@ -17,6 +17,7 @@ import { releaseQualityClone, releaseRejectedQualityResponse, } from "./validateQuality.ts"; +import { isTrustedEmptyTurn } from "./emptyTurnTrust.ts"; import type { ResponseValidationConfig } from "./responseValidation.ts"; import type { ComboCollectionLike, @@ -335,7 +336,9 @@ export async function executeRuntimeUnitCombo(args: { clientRequestedStream, args.log, args.config.responseValidation as ResponseValidationConfig | undefined, - args.signal + args.signal, + unit.kind === "model" && + (await isTrustedEmptyTurn(unit.provider, response, unit.connectionId)) ); releaseQualityClone(unitClone, response, quality); if (quality.valid) { diff --git a/open-sse/services/combo/sessionStickiness.ts b/open-sse/services/combo/sessionStickiness.ts index 7347730a6028..6929dff9b62d 100644 --- a/open-sse/services/combo/sessionStickiness.ts +++ b/open-sse/services/combo/sessionStickiness.ts @@ -85,9 +85,7 @@ interface StickyEntry { * Injectable saturation fetcher seam (for unit tests). * Returns HeadroomSaturation or undefined when unknown. */ -export type SaturationFetcher = ( - connectionId: string -) => Promise; +export type SaturationFetcher = (connectionId: string) => Promise; // ─── Saturation fetcher seam ───────────────────────────────────────────────── @@ -187,9 +185,7 @@ export type QuotaExhaustionChecker = (connectionId: string) => boolean; let _quotaExhaustionOverride: QuotaExhaustionChecker | null = null; /** Test-only: inject the quota-exhaustion checker; pass null to restore default. */ -export function __setStickinessQuotaCheckerForTests( - checker: QuotaExhaustionChecker | null -): void { +export function __setStickinessQuotaCheckerForTests(checker: QuotaExhaustionChecker | null): void { _quotaExhaustionOverride = checker; } @@ -450,6 +446,16 @@ export interface ApplyStickinessResult { stuck: boolean; } +/** + * #15241: when the caller needs to preserve the combo's declared first target, + * a sticky binding pointing at a mid-list connection must not silently become + * runtime try-slot #1. The binding's hygiene gates below still run; only the + * promotion is barred. + */ +export interface ApplyStickinessOptions { + respectDeclaredOrder?: boolean; +} + /** * Attempt to promote the sticky connection to the front of `orderedTargets`. * @@ -469,12 +475,15 @@ export interface ApplyStickinessResult { * @param orderedTargets Targets already ordered by the combo strategy. * @param messages Request body.messages. * @param namespace Combo identity that owns this sticky binding. + * @param options #15241 — `respectDeclaredOrder` bars a mid-list promotion + * for strategies whose first target is operator-declared. * @returns Result with (possibly reordered) targets. */ export async function applySessionStickiness( orderedTargets: ResolvedComboTarget[], messages: Array<{ role?: string; content?: unknown }> | null | undefined, - namespace?: string + namespace?: string, + options?: ApplyStickinessOptions ): Promise { const noOp: ApplyStickinessResult = { targets: orderedTargets, messageHash: null, stuck: false }; @@ -530,6 +539,12 @@ export async function applySessionStickiness( return { targets: orderedTargets, messageHash, stuck: false }; } + // #15241: a caller preserving an operator-declared head keeps its order — + // a mid-list sticky connection stays mid-list. + if (options?.respectDeclaredOrder && stickyIdx > 0) { + return { targets: orderedTargets, messageHash, stuck: false }; + } + // Promote the sticky target to position 0 const reordered = [ orderedTargets[stickyIdx], diff --git a/open-sse/services/combo/targetResolution.ts b/open-sse/services/combo/targetResolution.ts index e55f9ac767bd..cf79de23b46b 100644 --- a/open-sse/services/combo/targetResolution.ts +++ b/open-sse/services/combo/targetResolution.ts @@ -535,7 +535,12 @@ async function applyContinuityFilters( // #7270: normalize both wire shapes (.messages / Responses-API .input) so the // stickiness key is derivable on the /v1/responses surface, not just Chat Completions. normalizeStickinessMessages(body as { messages?: unknown; input?: unknown }), - combo.name + combo.name, + // #15241: priority is an operator-declared failover order. A mid-list + // success must not silently become runtime try-slot #1 while the stored + // hop list still names another head. Other strategies keep their existing + // session-stickiness behavior. + { respectDeclaredOrder: strategy === "priority" } ); let orderedTargets = sticky.targets; if (!cacheStrategyAffinityApplied) { diff --git a/open-sse/services/combo/types.ts b/open-sse/services/combo/types.ts index ce80f90bc11a..55197b0085bd 100644 --- a/open-sse/services/combo/types.ts +++ b/open-sse/services/combo/types.ts @@ -206,6 +206,13 @@ export type AutoProviderCandidate = ProviderCandidate & { statusPenalty?: boolean; /** Diagnostic reason for statusPenalty (the connection testStatus that triggered it). */ statusPenaltyReason?: string; + /** + * #15347: true when this provider has a quota fetcher but it returned nothing readable + * (failed fetch, missing credentials, message-only or malformed payload). The candidate + * is NOT blocked or evicted: scoring multiplies it by + * UNREADABLE_QUOTA_SOFT_DEPRIORITIZE_FACTOR so it ranks strictly below any real reading. + */ + quotaUnreadable?: boolean; }; export type ResolvedComboTarget = { diff --git a/open-sse/services/combo/validateQuality.ts b/open-sse/services/combo/validateQuality.ts index ad189e7062e5..0163f0d85268 100644 --- a/open-sse/services/combo/validateQuality.ts +++ b/open-sse/services/combo/validateQuality.ts @@ -119,6 +119,8 @@ interface SseLifecycleFlags { hasContentBlock: boolean; hasRealContent: boolean; hasLifecycleEnd: boolean; + hasMessageStop: boolean; + stopReason: string | null; } /** Read `parsed.` as a nested object bag, or null when absent/not an object. */ @@ -212,10 +214,14 @@ function applySseLifecycleEvent( return false; case "message_stop": flags.hasLifecycleEnd = true; + flags.hasMessageStop = true; return false; - case "message_delta": + case "message_delta": { + const stopReason = asObject(parsed, "delta")?.stop_reason; + if (typeof stopReason === "string") flags.stopReason = stopReason; if (messageDeltaEndsLifecycle(parsed)) flags.hasLifecycleEnd = true; return false; + } default: return false; } @@ -296,7 +302,9 @@ function classifyStreamingUpstreamFailure(parsed: unknown): StreamingUpstreamFai const requestScoped = type === "invalid_request_error" || code === "invalid_request_error" || + type === "context_length_exceeded" || code === "context_length_exceeded" || + type === "context_window_exceeded" || code === "context_window_exceeded"; const message = sanitizeErrorMessage(normalized.message).slice(0, 300); return { @@ -343,7 +351,8 @@ export async function validateResponseQuality( isStreaming: boolean, log: { warn?: (...args: unknown[]) => void }, responseValidation?: ResponseValidationConfig | null, - signal?: AbortSignal | null + signal?: AbortSignal | null, + trustedEmptyTurn = false ): Promise { // Issue #3685: For Claude SSE streaming responses, use a BOUNDED PEEK to // detect the empty-content-block pattern (content_filter stop_reason with @@ -399,6 +408,8 @@ export async function validateResponseQuality( hasContentBlock: false, hasRealContent: false, hasLifecycleEnd: false, + hasMessageStop: false, + stopReason: null, }; // #7285: OpenAI-shape lifecycle tracking, parallel to `sse` above. const openAi: OpenAiLifecycleFlags = { hasChoicePayload: false, hasTerminalMarker: false }; @@ -602,6 +613,17 @@ export async function validateResponseQuality( } if (sse.hasMessageStart && sse.hasLifecycleEnd && !sse.hasRealContent) { + // A first-party Claude empty turn with an ordinary stop is a valid + // response. Require the final message_stop and NO opened blocks: + // an empty start/stop block (#1382) or content_filter still fails over. + if ( + trustedEmptyTurn && + sse.hasMessageStop && + !sse.hasContentBlock && + (sse.stopReason === "end_turn" || sse.stopReason === "stop_sequence") + ) { + return { valid: true, clonedResponse: buildReplayResponse(reader) }; + } // Complete Claude lifecycle with zero content blocks, or with // content_block_start/stop pairs that never carried real text/ // thinking/tool_use content (#1382 — tool-heavy claude→openai diff --git a/open-sse/services/compression/compressionWorker.js b/open-sse/services/compression/compressionWorker.js new file mode 100644 index 000000000000..f7626908b92b --- /dev/null +++ b/open-sse/services/compression/compressionWorker.js @@ -0,0 +1,3 @@ +// Build-time placeholder for Turbopack's worker module trace (#12797). +// scripts/build/colocate-standalone.mjs replaces this with the bundled worker. +export {}; diff --git a/open-sse/services/compression/compressionWorkerPool.ts b/open-sse/services/compression/compressionWorkerPool.ts index 4db9acc59a18..0c2bf1962fcd 100644 --- a/open-sse/services/compression/compressionWorkerPool.ts +++ b/open-sse/services/compression/compressionWorkerPool.ts @@ -67,9 +67,15 @@ function runtimeAnchors(): string[] { export function resolveWorkerFile(): string { const anchors = runtimeAnchors(); - // Prod first: the .js under the install root. + // The tracked .js file is only a Turbopack build-time placeholder in the source tree. + // A colocated bundle has its own ESM scope, written by colocate-standalone.mjs. const jsRoot = firstAncestorWith(anchors, WORKER_JS_REL); - if (jsRoot) return join(jsRoot, WORKER_JS_REL); + if ( + jsRoot && + (!existsSync(join(jsRoot, WORKER_TS_REL)) || + existsSync(join(dirname(join(jsRoot, WORKER_JS_REL)), "package.json"))) + ) + return join(jsRoot, WORKER_JS_REL); // Dev: the .ts source. const tsRoot = firstAncestorWith(anchors, WORKER_TS_REL); diff --git a/open-sse/services/compression/engines/cavemanAdapter.ts b/open-sse/services/compression/engines/cavemanAdapter.ts index 3b0cd4e1165d..2bcb91254ac7 100644 --- a/open-sse/services/compression/engines/cavemanAdapter.ts +++ b/open-sse/services/compression/engines/cavemanAdapter.ts @@ -60,12 +60,6 @@ const AGGRESSIVE_SCHEMA: EngineConfigField[] = [ min: 0, max: 1, }, - { - key: "preserveSystemPrompt", - type: "boolean", - label: "Preserve system prompt", - defaultValue: true, - }, ]; const ULTRA_SCHEMA: EngineConfigField[] = [ @@ -111,18 +105,8 @@ const ULTRA_SCHEMA: EngineConfigField[] = [ min: 0, max: 32768, }, - { - key: "preserveSystemPrompt", - type: "boolean", - label: "Preserve system prompt", - defaultValue: true, - }, ]; -function ok(): EngineValidationResult { - return { valid: true, errors: [] }; -} - function isRecord(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } @@ -211,16 +195,9 @@ function validateUltraConfig(config: Record): EngineValidationR return { valid: errors.length === 0, errors }; } -// Lite only honors `preserveSystemPrompt` (model/vision are runtime, not user config). -// Previously this engine wrongly exposed AGGRESSIVE_SCHEMA, surfacing irrelevant -// summarizer/threshold fields in the per-engine config UI. +// Lite exposes only the tool-truncation controls. System-prompt preservation is a +// global settings-level flag (not tunable per engine), so it must not appear here. const LITE_SCHEMA: EngineConfigField[] = [ - { - key: "preserveSystemPrompt", - type: "boolean", - label: "Preserve system prompt", - defaultValue: true, - }, { key: "compressToolResults", type: "boolean", diff --git a/open-sse/services/compression/engines/llmlingua/onnxWorker.js b/open-sse/services/compression/engines/llmlingua/onnxWorker.js new file mode 100644 index 000000000000..5ca3d1ab8f20 --- /dev/null +++ b/open-sse/services/compression/engines/llmlingua/onnxWorker.js @@ -0,0 +1,3 @@ +// Build-time placeholder for Turbopack's worker module trace (#12797). +// scripts/build/colocate-standalone.mjs replaces this when optional dependencies are installed. +export {}; diff --git a/open-sse/services/compression/engines/llmlingua/worker.ts b/open-sse/services/compression/engines/llmlingua/worker.ts index cb0ba815d6d8..bd86d60cccff 100644 --- a/open-sse/services/compression/engines/llmlingua/worker.ts +++ b/open-sse/services/compression/engines/llmlingua/worker.ts @@ -186,9 +186,15 @@ let idleTimer: NodeJS.Timeout | null = null; export function resolveWorkerFile(): { workerFile: string; execArgv: string[] } { const anchors = runtimeAnchors(); - // Prod first: the esbuild'd .js under the install root. + // The tracked .js file is only a Turbopack build-time placeholder in the source tree. + // A colocated bundle has its own ESM scope, written by colocate-standalone.mjs. const jsRoot = firstAncestorWith(anchors, WORKER_JS_REL); - if (jsRoot) return { workerFile: path.join(jsRoot, WORKER_JS_REL), execArgv: [] }; + if ( + jsRoot && + (!fs.existsSync(path.join(jsRoot, WORKER_TS_REL)) || + fs.existsSync(path.join(path.dirname(path.join(jsRoot, WORKER_JS_REL)), "package.json"))) + ) + return { workerFile: path.join(jsRoot, WORKER_JS_REL), execArgv: [] }; // Dev: the .ts source (tsx loader). const tsRoot = firstAncestorWith(anchors, WORKER_TS_REL); @@ -266,15 +272,20 @@ function ensureWorker(): Worker { pump(); }); - const failOpenAndRespawn = () => { + const failOpenAndRespawn = (detail: string) => { // Resolve every pending entry fail-open, then drop the worker so the next call respawns. + if (pending.size > 0) notifyCompressionFailOpen(detail); failAllPending(); if (worker === w) worker = null; busy = false; }; - w.on("error", failOpenAndRespawn); - w.on("exit", failOpenAndRespawn); + w.on("error", (error: Error) => { + failOpenAndRespawn(`llmlingua worker error: ${sanitizeErrorMessage(error.message)}`); + }); + w.on("exit", (code: number) => { + failOpenAndRespawn(`llmlingua worker exit: ${code}`); + }); worker = w; return w; diff --git a/open-sse/services/compression/outputStyles/apply.ts b/open-sse/services/compression/outputStyles/apply.ts index 9c4e06c5c5a7..e56d8fb16bfd 100644 --- a/open-sse/services/compression/outputStyles/apply.ts +++ b/open-sse/services/compression/outputStyles/apply.ts @@ -103,17 +103,27 @@ function resolveStyles( return resolved; } -/** Build the combined instruction body (no marker, no trailing boundary). Pure / deterministic. */ +/** + * Build the combined instruction body (no marker, no boundary block), ending with one + * space when the last style's own text puts whitespace (or nothing) before + * SHARED_BOUNDARIES, and with none when a non-whitespace character directly precedes + * it. Pure / deterministic. + */ function buildStyleInstructions(resolved: OutputStyleSelectionEntry[], language: string): string { const parts: string[] = []; + let separator = " "; for (const { id, level } of resolved) { const meta = outputStyleMeta(id); const localized = meta.i18n?.[language]; const levels = localized ?? meta.levels; + const text = levels[level]; // Strip the per-style boundary so the combined boundary block is appended once below. - parts.push(levels[level].replace(SHARED_BOUNDARIES, "").trim()); + parts.push(text.replace(SHARED_BOUNDARIES, "").trim()); + // The separator mirrors whatever spacing the style's own text puts before SHARED_BOUNDARIES. + const at = text.indexOf(SHARED_BOUNDARIES); + separator = at > 0 && !/\s/.test(text[at - 1]) ? "" : " "; } - return parts.join("\n"); + return `${parts.join("\n")}${separator}`; } /** @@ -138,6 +148,29 @@ function buildStyleBoundaries(resolved: OutputStyleSelectionEntry[], language: s return [...new Set(clauses)].join("\n"); } +/** + * The block applyOutputStyles injects for a selection: marker line, style + * instructions, then the boundary block. "" when no style in the selection + * resolves. Pure / deterministic; exported so the dashboard preview renders + * the same string a request carries. + */ +export function buildOutputStylesInstruction( + selection: OutputStyleSelectionEntry[], + language = "en" +): string { + const resolved = resolveStyles(selection ?? [], language); + if (resolved.length === 0) return ""; + + // The boundary block follows the instruction body with the spacing the last style's own + // text puts before SHARED_BOUNDARIES, so a legacy single-style (terse-prose) injection + // matches the old caveman output mode below the marker line in every legacy language + // (D-A5 back-compat): terse-prose declares no `boundaries`, so its block is exactly + // SHARED_BOUNDARIES as before. + // A style that declares one gets the shared clause AND its own, in catalog order. + const combined = `${buildStyleInstructions(resolved, language)}${buildStyleBoundaries(resolved, language)}`; + return `${OUTPUT_STYLE_MARKER}\n${combined}`; +} + /** * Inject one or more output styles deterministically and appended to the system prompt. * - Selection resolved in catalog order; unknown/locale-mismatched styles dropped. @@ -159,13 +192,7 @@ export function applyOutputStyles( return { body, applied: false, skippedReason: "no_styles" }; } - // Single space before the boundary block so a legacy single-style - // (terse-prose) injection stays byte-identical to the old caveman output mode - // (D-A5 back-compat): terse-prose declares no `boundaries`, so its block is - // exactly SHARED_BOUNDARIES as before. A style that declares one gets the - // shared clause AND its own, in catalog order. - const combined = `${buildStyleInstructions(resolved, language)} ${buildStyleBoundaries(resolved, language)}`; - const instruction = `${OUTPUT_STYLE_MARKER}\n${combined}`; + const instruction = buildOutputStylesInstruction(resolved, language); const messages = Array.isArray(body.messages) ? body.messages : null; if (!messages || messages.length === 0) { diff --git a/open-sse/services/compression/outputStyles/backCompat.ts b/open-sse/services/compression/outputStyles/backCompat.ts index 82631b1baa15..cb2fd12a10f0 100644 --- a/open-sse/services/compression/outputStyles/backCompat.ts +++ b/open-sse/services/compression/outputStyles/backCompat.ts @@ -14,7 +14,8 @@ interface ConfigSlice { * Resolve the effective output-style selection (D-A5 back-compat). * Precedence: an explicit non-empty `outputStyles` wins; otherwise a stored * `cavemanOutputMode` (when enabled) maps to `[{ terse-prose, }]`, - * keeping existing installs byte-identical until they opt into other styles. + * so existing installs keep the legacy instruction text below the marker line + * until they opt into other styles. * Pure; never throws. */ export function resolveOutputStyleSelection(config: ConfigSlice): OutputStyleSelectionEntry[] { diff --git a/open-sse/services/compression/outputStyles/catalog.ts b/open-sse/services/compression/outputStyles/catalog.ts index 0a649ae48a60..fde2a74f7ffe 100644 --- a/open-sse/services/compression/outputStyles/catalog.ts +++ b/open-sse/services/compression/outputStyles/catalog.ts @@ -40,27 +40,23 @@ export const SAFETY_BOUNDARIES = * settings panel both enumerate this object, so no other file needs to change (D-A1). * Declaration order is the deterministic concatenation order used by the injector. */ + +// The terse-prose text mirrors the legacy caveman table: en is the default `levels`, +// every other language an `i18n` override. Derived rather than hand-copied, so a +// language added to the legacy table cannot be missed here; +// output-styles-legacy-parity.test.ts guards the mirror from the other side. +const { en: TERSE_PROSE_LEVELS, ...TERSE_PROSE_I18N } = CAVEMAN_INSTRUCTION_BY_LANGUAGE; + export const OUTPUT_STYLE_CATALOG: Record = { "terse-prose": { id: "terse-prose", label: "Terse prose", description: "Drop filler/articles/hedging; keep technical substance exact.", - // Migrated verbatim from the caveman output mode (outputMode.ts) — referenced (not - // re-typed) so the back-compat injection stays byte-identical across ALL languages, - // not just English (the legacy mode localized to en/pt-BR/ja/id). - levels: CAVEMAN_INSTRUCTION_BY_LANGUAGE.en, - i18n: { - "pt-BR": CAVEMAN_INSTRUCTION_BY_LANGUAGE["pt-BR"], - es: CAVEMAN_INSTRUCTION_BY_LANGUAGE.es, - de: CAVEMAN_INSTRUCTION_BY_LANGUAGE.de, - fr: CAVEMAN_INSTRUCTION_BY_LANGUAGE.fr, - it: CAVEMAN_INSTRUCTION_BY_LANGUAGE.it, - ru: CAVEMAN_INSTRUCTION_BY_LANGUAGE.ru, - zh: CAVEMAN_INSTRUCTION_BY_LANGUAGE.zh, - ja: CAVEMAN_INSTRUCTION_BY_LANGUAGE.ja, - id: CAVEMAN_INSTRUCTION_BY_LANGUAGE.id, - vi: CAVEMAN_INSTRUCTION_BY_LANGUAGE.vi, - }, + // Referenced from the caveman output mode (outputMode.ts) so the back-compat injection + // matches the legacy text below the marker line in every language + // CAVEMAN_INSTRUCTION_BY_LANGUAGE covers. + levels: TERSE_PROSE_LEVELS, + i18n: TERSE_PROSE_I18N, }, "less-code": { id: "less-code", diff --git a/open-sse/services/compression/planResolution.ts b/open-sse/services/compression/planResolution.ts index e636b4928b42..0590cc5bb567 100644 --- a/open-sse/services/compression/planResolution.ts +++ b/open-sse/services/compression/planResolution.ts @@ -54,7 +54,12 @@ export function planFromHeader( } const combo = combos[lower] ?? combos[h]; - return combo ? withSource({ mode: "stacked", stackedPipeline: combo }, "request-header") : null; + // Only a real, non-empty pipeline is a chosen plan: an empty-array combo would otherwise win + // the precedence race and let the builtin fallback pipeline run lossy unrequested, and the + // plain-object lookup resolves prototype keys (constructor, toString) to truthy garbage. + return Array.isArray(combo) && combo.length > 0 + ? withSource({ mode: "stacked", stackedPipeline: combo }, "request-header") + : null; } /** Renders the X-OmniRoute-Compression response header value. */ diff --git a/open-sse/services/estimatorCalibration.ts b/open-sse/services/estimatorCalibration.ts new file mode 100644 index 000000000000..fbd89cb3e156 --- /dev/null +++ b/open-sse/services/estimatorCalibration.ts @@ -0,0 +1,164 @@ +/** + * Feedback calibration for the context-guard token estimator (#14931). + * + * `estimateTokens` prices every part of a request at a flat chars/4. That is + * accurate for prose (~4.2 chars/token in OpenAI-family tokenizers) but badly + * wrong for tool-schema JSON, whose true density is tokenizer-dependent: + * o200k packs it at ~4.7 chars/token while Qwen-family tokenizers reach + * ~25 chars/token — a 5x divergence no single static ratio can cover + * (cross-checked with js-tiktoken in the #14931 thread). + * + * So instead of guessing a density, learn it: every completed request reports + * `usage.prompt_tokens` from the provider. The ratio actual/estimated is + * tracked as an exponential moving average per (provider, model), split into + * a tools-present and a tools-absent bucket — agent traffic (Codex et al.) is + * ~90% tool schema while chat traffic is ~0%, and their densities differ by + * construction. The next estimate for the same shape is multiplied by that + * factor. + * + * Safety properties: + * - Cold start is byte-identical to the old behavior: no bucket or fewer than + * MIN_SAMPLES observations → factor 1.0. + * - The factor is clamped to [0.15, 2.0]: a false-negative guard decision is + * recoverable (upstream 400 → combo failover), a false-positive local 400 is + * not, but a toothless guard is also unacceptable — 2x is the ceiling. + * - Observations with a wild ratio (<0.05 or >20) or a tiny estimate are + * rejected: they come from shape discontinuities or aggregated multi-leg + * usage, not from tokenizer density. + * - State is in-memory only: a restart returns to factor 1.0 and re-learns. + * Worst case is the current behavior, never worse. + * - `OMNIROUTE_ESTIMATOR_CALIBRATION=off|0|false` disables recording and + * application entirely. + */ + +const EMA_ALPHA = 0.25; +const MIN_SAMPLES = 3; +export const ESTIMATOR_CALIBRATION_FACTOR_MIN = 0.15; +export const ESTIMATOR_CALIBRATION_FACTOR_MAX = 2.0; +const MIN_ESTIMATE_FOR_OBSERVATION = 1000; +const RATIO_ACCEPT_MIN = 0.05; +const RATIO_ACCEPT_MAX = 20; + +type CalibrationBucket = { + /** Current EMA of actual/estimated. */ + factor: number; + /** Observations absorbed so far (buckets apply only at >= MIN_SAMPLES). */ + samples: number; +}; + +const buckets = new Map(); + +function calibrationDisabled(): boolean { + const value = process.env.OMNIROUTE_ESTIMATOR_CALIBRATION; + return value === "off" || value === "0" || value === "false"; +} + +export function calibrationBucketKey( + provider: string | null | undefined, + model: string | null | undefined, + toolsPresent: boolean +): string { + return `${provider ?? "unknown"}/${model ?? "unknown"}|tools=${toolsPresent ? 1 : 0}`; +} + +function clampFactor(value: number): number { + return Math.min( + ESTIMATOR_CALIBRATION_FACTOR_MAX, + Math.max(ESTIMATOR_CALIBRATION_FACTOR_MIN, value) + ); +} + +/** + * Current calibration factor for a request shape. 1.0 unless enough trusted + * observations exist for this (provider, model, tools) bucket. + */ +export function getEstimatorCalibrationFactor( + provider: string | null | undefined, + model: string | null | undefined, + toolsPresent: boolean +): number { + if (calibrationDisabled()) return 1; + const bucket = buckets.get(calibrationBucketKey(provider, model, toolsPresent)); + if (!bucket || bucket.samples < MIN_SAMPLES) return 1; + return bucket.factor; +} + +/** + * Scale an estimate by the learned factor. Returns the input unchanged on cold + * start, when disabled, or for non-positive estimates. + */ +export function applyEstimatorCalibration( + provider: string | null | undefined, + model: string | null | undefined, + estimatedTokens: number, + toolsPresent: boolean +): number { + if (!Number.isFinite(estimatedTokens) || estimatedTokens <= 0) return estimatedTokens; + const factor = getEstimatorCalibrationFactor(provider, model, toolsPresent); + if (factor === 1) return estimatedTokens; + return Math.max(1, Math.round(estimatedTokens * factor)); +} + +function readPromptTokens(usage: unknown): number | null { + if (!usage || typeof usage !== "object") return null; + const record = usage as Record; + // OpenAI-compat `prompt_tokens` and Claude-native `input_tokens` are the two + // shapes the normalized usage objects carry in chatCore. + for (const field of ["prompt_tokens", "input_tokens"]) { + const value = record[field]; + if (typeof value === "number" && Number.isFinite(value) && value > 0) return value; + } + return null; +} + +/** + * Absorb one (estimated, provider-reported prompt tokens) pair. No-op when + * disabled, when the estimate is too small to carry a stable ratio, when the + * usage object carries no token count, or when `aggregatedUsage` marks a + * multi-leg tool-loop sum (its prompt_tokens double-counts re-sent history and + * would inflate the learned factor). + */ +export function recordEstimatorCalibrationFromUsage(input: { + provider?: string | null; + model?: string | null; + estimatedTokens: number; + usage: unknown; + toolsPresent: boolean; + aggregatedUsage?: boolean; +}): void { + if (calibrationDisabled()) return; + if (input.aggregatedUsage) return; + if ( + !Number.isFinite(input.estimatedTokens) || + input.estimatedTokens < MIN_ESTIMATE_FOR_OBSERVATION + ) { + return; + } + const actualTokens = readPromptTokens(input.usage); + if (actualTokens == null) return; + const ratio = actualTokens / input.estimatedTokens; + if (ratio < RATIO_ACCEPT_MIN || ratio > RATIO_ACCEPT_MAX) return; + + const key = calibrationBucketKey(input.provider, input.model, input.toolsPresent); + const bucket = buckets.get(key); + if (!bucket) { + buckets.set(key, { factor: clampFactor(ratio), samples: 1 }); + return; + } + bucket.factor = clampFactor(bucket.factor + EMA_ALPHA * (clampFactor(ratio) - bucket.factor)); + bucket.samples += 1; +} + +/** Test-only: forget everything learned so far. */ +export function resetEstimatorCalibrationForTests(): void { + buckets.clear(); +} + +/** Test-only: introspect a bucket without going through the factor gate. */ +export function getEstimatorCalibrationBucketForTests( + provider: string | null | undefined, + model: string | null | undefined, + toolsPresent: boolean +): { factor: number; samples: number } | undefined { + return buckets.get(calibrationBucketKey(provider, model, toolsPresent)); +} diff --git a/open-sse/services/genericQuotaFetcher.ts b/open-sse/services/genericQuotaFetcher.ts index 7137867f0b19..e51243e8fc0b 100644 --- a/open-sse/services/genericQuotaFetcher.ts +++ b/open-sse/services/genericQuotaFetcher.ts @@ -261,7 +261,17 @@ export function convertUsageToQuotaInfo( windows[name] = { percentUsed, resetAt: resetAtForQuota(entry) }; } - if (Object.keys(windows).length === 0) return null; + if (Object.keys(windows).length === 0) { + // #15347: every reported window is unlimited. That is a known reading (the provider + // has no cap), not a failed one, so it must not look like the `null` of a failed read. + const reportsUnlimited = Object.values(quotasObj as Record).some( + (entry) => + !!entry && + typeof entry === "object" && + (entry as { unlimited?: unknown }).unlimited === true + ); + return reportsUnlimited ? { used: 0, total: 0, percentUsed: 0, unlimited: true } : null; + } const requestedFamily = isAntigravityProvider(context.provider) && context.requestedModel diff --git a/open-sse/services/githubCopilotModels.ts b/open-sse/services/githubCopilotModels.ts index 39d112f014b2..658937239133 100644 --- a/open-sse/services/githubCopilotModels.ts +++ b/open-sse/services/githubCopilotModels.ts @@ -74,6 +74,8 @@ export type GitHubCopilotModel = { id: string; name: string; owned_by: string; + supportedEndpoints?: string[]; + targetFormat?: "openai-responses"; }; type RawRecord = Record; @@ -88,6 +90,48 @@ function toNonEmptyString(value: unknown): string | null { return trimmed.length > 0 ? trimmed : null; } +function getSupportedEndpoints(item: RawRecord): string[] { + const capabilities = asRecord(item.capabilities); + const endpoints = Array.isArray(item.supported_endpoints) + ? item.supported_endpoints + : Array.isArray(capabilities.supported_endpoints) + ? capabilities.supported_endpoints + : []; + return Array.from( + new Set(endpoints.map(toNonEmptyString).filter((e): e is string => !!e)) + ).sort(); +} + +function getEndpointMetadata(item: RawRecord, modelId: string): Partial { + const paths = getSupportedEndpoints(item).map((endpoint) => + endpoint.toLowerCase().replace(/^\/+/, "").replace(/^v1\//, "").replace(/\/$/, "") + ); + // The public catalog uses endpoint kinds rather than upstream URL paths. + const supportedEndpoints = Array.from( + new Set( + paths + .map((path) => + path === "chat/completions" || path === "chat-completions" || path === "messages" + ? "chat" + : path + ) + .filter(Boolean) + ) + ).sort(); + if (supportedEndpoints.length === 0) return {}; + // Copilot's endpoint contract, not the model's version, determines the wire format. + // Keep chat-capable models on their existing route and mirror the executor's + // defensive exclusion of Claude/Gemini from the Responses endpoint. + const responsesOnly = + supportedEndpoints.includes("responses") && + !supportedEndpoints.includes("chat") && + !/claude|gemini/i.test(modelId); + return { + supportedEndpoints, + ...(responsesOnly ? { targetFormat: "openai-responses" as const } : {}), + }; +} + // Decide whether a live /models row is a routable chat model. Capability-driven // (rename-robust) rather than an id allowlist: any model the account is entitled // to whose capabilities.type is "chat" (or that carries a chat-shaped @@ -155,7 +199,7 @@ export function parseGitHubCopilotModels(data: unknown): GitHubCopilotModel[] { if (!isRoutableChatModel(item)) continue; seen.add(id); const name = toNonEmptyString(item.name) || toNonEmptyString(item.display_name) || id; - models.push({ id, name, owned_by: "github" }); + models.push({ id, name, owned_by: "github", ...getEndpointMetadata(item, id) }); } return models; diff --git a/open-sse/services/kiroModels.ts b/open-sse/services/kiroModels.ts index 5a64410fc48d..b33ca4020a45 100644 --- a/open-sse/services/kiroModels.ts +++ b/open-sse/services/kiroModels.ts @@ -187,7 +187,12 @@ function expandKiroModels(data: unknown): KiroModel[] { if (!upstreamId) continue; const display = formatDisplayName(item.modelName || item.name, upstreamId, item.rateMultiplier); const tokenLimits = asRecord(item.tokenLimits); - const contextLength = Number(tokenLimits.maxInputTokens) || 200000; + // Only the upstream number. Stamping a 200k default here would be persisted as the + // discovered window and pinned as an auto:discovery override over the registry / + // models.dev window (claude-sonnet-5 is 1M). + const maxInputTokens = Number(tokenLimits.maxInputTokens); + const contextLength = + Number.isFinite(maxInputTokens) && maxInputTokens > 0 ? maxInputTokens : undefined; const rateMultiplier = Number(item.rateMultiplier); const promptCaching = parsePromptCaching(item.promptCaching); @@ -196,7 +201,7 @@ function expandKiroModels(data: unknown): KiroModel[] { seen.add(variant.id); expanded.push({ ...variant, - contextLength, + ...(contextLength ? { contextLength } : {}), rateMultiplier: Number.isFinite(rateMultiplier) ? rateMultiplier : 1.0, upstreamModelId: upstreamId, description: toNonEmptyString(item.description) || "", diff --git a/open-sse/services/modelFamilyFallback.ts b/open-sse/services/modelFamilyFallback.ts index 6226c1217b04..3366f960d10b 100644 --- a/open-sse/services/modelFamilyFallback.ts +++ b/open-sse/services/modelFamilyFallback.ts @@ -91,6 +91,7 @@ const FAMILY_FALLBACK_TEMPLATES: Record = { "claude-opus-4-6-thinking": ["claude-opus-4-6", "claude-opus-4-5-20251101"], // Claude Sonnet family — Sonnet 5 is the newest tier; degrade to 4.6 → 4.5 → 4. + "claude-sonnet-5-5": ["claude-sonnet-5", "claude-sonnet-4-6", "claude-sonnet-4-5-20250929"], "claude-sonnet-5": [ "claude-sonnet-4-6", "claude-sonnet-4-5-20250929", diff --git a/open-sse/services/opencodeFreeTierSkip.ts b/open-sse/services/opencodeFreeTierSkip.ts index c56ee904a4d2..e2657b7a173f 100644 --- a/open-sse/services/opencodeFreeTierSkip.ts +++ b/open-sse/services/opencodeFreeTierSkip.ts @@ -5,52 +5,214 @@ * The refusal is request-scoped (never a model lockout / connection cooldown), * but re-picking the same noauth candidate on every subsequent auto-combo turn * only burns latency until the operator adjusts the request shape. This is an - * in-process, provider-keyed TTL skip — not resilience state: keyed OpenCode + * in-process, provider + model keyed TTL skip — not resilience state: keyed OpenCode * connections keep their own account selection and are never dropped here. * + * A refusal recorded with a model pauses that model only; sibling models on + * the same provider stay eligible. A refusal recorded without a model keeps + * the previous behaviour and covers the whole provider. + * * Default TTL ~3 minutes: long enough to stop a tight retry loop, short enough * that a fixed request shape becomes eligible again without operator action. */ const DEFAULT_SKIP_TTL_MS = 3 * 60 * 1000; -/** provider id (lowercase, opencode*) → skip-until epoch ms. */ +/** Upper bound on live entries; every entry has a deletion path (see below). */ +const MAX_SKIP_ENTRIES = 512; + +/** + * Pause key: provider id (lowercase, opencode*) alone for a provider-wide + * pause, or provider + model (both normalized) for a model-scoped pause. + * A single flat map keeps insertion order, which doubles as eviction order. + */ const skips = new Map(); function isOpencodeProvider(provider: string | null | undefined): boolean { return typeof provider === "string" && provider.toLowerCase().startsWith("opencode"); } +function normalizeModel(model: string | null | undefined): string | null { + if (typeof model !== "string") return null; + const trimmed = model.trim().toLowerCase(); + return trimmed.length === 0 ? null : trimmed; +} + +function buildSkipKey(provider: string, model: string | null | undefined): string { + const providerKey = provider.toLowerCase(); + const normalized = normalizeModel(model); + return normalized === null ? providerKey : `${providerKey}\n${normalized}`; +} + +function readEntry(key: string, now: number): number | null { + const until = skips.get(key); + if (until === undefined) return null; + if (until <= now) { + skips.delete(key); + return null; + } + return until; +} + +/** Drop every entry the reader below would already treat as gone. */ +function evictExpiredEntries(now: number): void { + for (const [storedKey, until] of skips) { + if (until <= now) skips.delete(storedKey); + } +} + +function evictOldestEntries(): void { + while (skips.size >= MAX_SKIP_ENTRIES) { + const oldest = skips.keys().next(); + if (oldest.done) break; + skips.delete(oldest.value); + } +} + /** * Record a free-tier refusal pause for an opencode* provider. Non-opencode * providers are ignored (the free-tier signal is scoped to that family). + * With a model, only that model pauses; without one, the whole provider + * pauses, as before. */ export function noteOpencodeFreeTierSkip( provider: string | null | undefined, now: number = Date.now(), - ttlMs: number = DEFAULT_SKIP_TTL_MS + ttlMs: number = DEFAULT_SKIP_TTL_MS, + model?: string | null ): void { if (!isOpencodeProvider(provider)) return; - skips.set(String(provider).toLowerCase(), now + ttlMs); + const key = buildSkipKey(String(provider), model); + if (skips.size >= MAX_SKIP_ENTRIES && !skips.has(key)) { + evictExpiredEntries(now); + evictOldestEntries(); + } + skips.set(key, now + ttlMs); } -/** True while an active free-tier skip covers this opencode* provider. */ +/** + * True while an active free-tier skip covers this opencode* provider + model + * pair. A provider-wide pause covers every model; without a model, any live + * pause for the provider counts, so callers that cannot name a model keep + * the previous fail-closed behaviour. + */ export function isOpencodeFreeTierSkipped( provider: string | null | undefined, - now: number = Date.now() + now: number = Date.now(), + model?: string | null ): boolean { if (!isOpencodeProvider(provider)) return false; - const key = String(provider).toLowerCase(); - const until = skips.get(key); - if (until === undefined) return false; - if (until <= now) { - skips.delete(key); - return false; + const providerKey = String(provider).toLowerCase(); + const normalized = normalizeModel(model); + if (normalized !== null) { + if (readEntry(`${providerKey}\n${normalized}`, now) !== null) return true; + return readEntry(providerKey, now) !== null; } - return true; + if (readEntry(providerKey, now) !== null) return true; + const prefix = `${providerKey}\n`; + for (const [storedKey, until] of skips) { + if (!storedKey.startsWith(prefix)) continue; + if (until <= now) { + skips.delete(storedKey); + continue; + } + return true; + } + return false; } /** Test helper — drop every active skip. */ export function clearOpencodeFreeTierSkips(): void { skips.clear(); } + +/** + * Remaining pause time in ms for an opencode* provider + model pair, or null + * when no active pause covers it. Read-only companion to + * `isOpencodeFreeTierSkipped`: an expired entry is dropped lazily, so a + * null remainder hands the connection back instead of a zero-second + * cooldown. Without a model, reports the longest live remainder for the + * provider, so the caller waits out the worst case instead of retrying + * into a still-paused model. + */ +export function getOpencodeFreeTierSkipRemainingMs( + provider: string | null | undefined, + now: number = Date.now(), + model?: string | null +): number | null { + if (!isOpencodeProvider(provider)) return null; + const providerKey = String(provider).toLowerCase(); + const normalized = normalizeModel(model); + if (normalized !== null) { + const exact = readEntry(`${providerKey}\n${normalized}`, now); + if (exact !== null) return exact - now; + const wide = readEntry(providerKey, now); + return wide === null ? null : wide - now; + } + const wide = readEntry(providerKey, now); + let longest = wide === null ? null : wide - now; + const prefix = `${providerKey}\n`; + for (const [storedKey, until] of skips) { + if (!storedKey.startsWith(prefix)) continue; + if (until <= now) { + skips.delete(storedKey); + continue; + } + const remaining = until - now; + if (longest === null || remaining > longest) longest = remaining; + } + return longest; +} +const FREE_TIER_PAUSE_REASON = "Free-tier request refused (429)"; + +/** + * Scan live entries that belong to one provider prefix. An entry belongs to + * the prefix when it equals it (provider-wide pause) or continues it after + * a newline (model-scoped pause), so `opencode-x` never matches `opencode`. + * An empty prefix matches every entry (stored keys are opencode* by + * construction of the writer above). Expired entries are dropped lazily, + * like `readEntry`. `visit` returns true to stop the scan, false to continue. + */ +function scanProviderEntries( + prefix: string, + now: number, + visit: (storedKey: string, until: number) => boolean +): void { + for (const [storedKey, until] of skips) { + if (prefix !== "" && storedKey !== prefix && !storedKey.startsWith(`${prefix}\n`)) continue; + if (until <= now) { + skips.delete(storedKey); + continue; + } + if (visit(storedKey, until)) return; + } +} + +/** + * Read-only list of the active free-tier pauses: provider, model (null for a + * provider-wide pause), pause end as an ISO string, and the refusal motive. + * An expired pause reads as absent. Without a provider, every active pause + * is listed; a provider outside the opencode family lists nothing. + */ +export function listOpencodeFreeTierPauses( + provider?: string | null, + now: number = Date.now() +): Array<{ provider: string; model: string | null; until: string; reason: string }> { + if (provider !== undefined && provider !== null && !isOpencodeProvider(provider)) return []; + const prefix = provider === undefined || provider === null ? "" : String(provider).toLowerCase(); + const pauses: Array<{ provider: string; model: string | null; until: string; reason: string }> = + []; + scanProviderEntries(prefix, now, (storedKey, until) => { + const cut = storedKey.lastIndexOf("\n"); + const head = cut === -1 ? storedKey : storedKey.slice(0, cut); + if (!isOpencodeProvider(head)) return false; + pauses.push({ + provider: head, + model: cut === -1 ? null : normalizeModel(storedKey.slice(cut + 1)), + until: new Date(until).toISOString(), + reason: FREE_TIER_PAUSE_REASON, + }); + return false; + }); + return pauses; +} diff --git a/open-sse/services/providerCostData.ts b/open-sse/services/providerCostData.ts index 675514d816eb..505f065e71b4 100644 --- a/open-sse/services/providerCostData.ts +++ b/open-sse/services/providerCostData.ts @@ -7,6 +7,9 @@ export interface ModelPricing { outputCostPer1M: number; isFree: boolean; freeQuotaLimit?: number; + // True only when the value is a conservative guess (final fallback path). + // Absent on legacy objects; explicit false on every catalog-sourced path. + isEstimated?: boolean; } export const KNOWN_MODEL_PRICING: Record = { @@ -18,7 +21,8 @@ export const KNOWN_MODEL_PRICING: Record = { "claude-opus-4-8": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, "claude-opus-4-7": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false }, "claude-sonnet-4-6": { inputCostPer1M: 3.0, outputCostPer1M: 15.0, isFree: false }, - "claude-sonnet-5": { inputCostPer1M: 3.0, outputCostPer1M: 15.0, isFree: false }, + "claude-sonnet-5": { inputCostPer1M: 2.0, outputCostPer1M: 10.0, isFree: false }, + "claude-sonnet-5-5": { inputCostPer1M: 2.0, outputCostPer1M: 10.0, isFree: false }, "claude-haiku-4-5": { inputCostPer1M: 0.8, outputCostPer1M: 4.0, isFree: false }, "gemini-2.5-flash": { inputCostPer1M: 0.15, outputCostPer1M: 0.6, isFree: false }, "gemini-2.5-pro": { inputCostPer1M: 1.25, outputCostPer1M: 5.0, isFree: false }, @@ -44,7 +48,7 @@ export function getModelPricing(provider: string, model: string): ModelPricing { .pop()! .toLowerCase(); const providerHit = KNOWN_MODEL_PRICING[`${provider}/${normalized}`.toLowerCase()]; - if (providerHit) return providerHit; + if (providerHit) return { ...providerHit, isEstimated: false }; const defaultPricing = getDefaultPricingForModel(provider, model); if (defaultPricing) { const inputCostPer1M = Number(defaultPricing.input); @@ -54,14 +58,15 @@ export function getModelPricing(provider: string, model: string): ModelPricing { inputCostPer1M, outputCostPer1M, isFree: inputCostPer1M === 0 && outputCostPer1M === 0, + isEstimated: false, }; } } const genericHit = KNOWN_MODEL_PRICING[normalized]; - if (genericHit) return genericHit; + if (genericHit) return { ...genericHit, isEstimated: false }; if (isFreeModel(provider, { id: normalized })) - return { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true }; - return { inputCostPer1M: 5.0, outputCostPer1M: 15.0, isFree: false }; + return { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true, isEstimated: false }; + return { inputCostPer1M: 5.0, outputCostPer1M: 15.0, isFree: false, isEstimated: true }; } /** Input cost per 1M tokens a virtual auto-combo candidate is scored at. */ diff --git a/open-sse/services/qoderCli.ts b/open-sse/services/qoderCli.ts index 1dcc42dc6f22..f2c3482c7571 100644 --- a/open-sse/services/qoderCli.ts +++ b/open-sse/services/qoderCli.ts @@ -702,6 +702,20 @@ export function parseQoderCliFailure(stderrText: string, stdoutText = ""): Qoder return { status: 504, message: combined, code: "timeout" }; } + // Qoder answers a temporarily throttled account with a queued/busy envelope + // (upstream code 10605) instead of an HTTP error. Classifying it as a generic + // 502 upstream_error parked the connection on the long failure cooldown, so a + // brief upstream hiccup made whole PAT pools look dead. 503 maps to the short + // serviceUnavailable cooldown, which lets routing retry or fail over at once. + if ( + normalized.includes("10605") || + normalized.includes("queued") || + normalized.includes("is busy") || + normalized.includes("server busy") + ) { + return { status: 503, message: combined, code: "upstream_busy" }; + } + return { status: 502, message: combined, code: "upstream_error" }; } @@ -967,8 +981,13 @@ export async function validateQoderCliPat({ }; } - // A successful `--list-models` prints the catalog (a table headed by "MODEL"). - if (run.ok && normalized.includes("model")) { + // A successful `--list-models` exits 0 and prints the catalog (a table headed + // by "MODEL"). Trust the exit code plus the parsed catalog instead of the + // literal header string: the header is not part of the CLI contract, so a + // table-format change, a localized header, or an alias/JSON-producing build + // made valid PATs fail validation while the CLI itself reported success. + const catalog = parseQoderCliModelNames(run.stdout); + if (run.ok && catalog.length > 0 && !/error|failed/i.test(normalized)) { return { valid: true, error: null, unsupported: false }; } diff --git a/open-sse/services/quotaFetchThrottle.ts b/open-sse/services/quotaFetchThrottle.ts index d8791fc8d71d..8a2c4050b58f 100644 --- a/open-sse/services/quotaFetchThrottle.ts +++ b/open-sse/services/quotaFetchThrottle.ts @@ -65,17 +65,21 @@ export class MinIntervalThrottle { }); try { await prev; + // The chain is single-file here, so this read cannot race another acquire. const now = this.clock.now(); - if (this.lastStart !== 0) { - const jitter = this.jitterMs > 0 ? Math.floor(this.rand() * this.jitterMs) : 0; - const wait = this.lastStart + this.minIntervalMs + jitter - now; - if (wait > 0) await this.clock.sleep(wait); - } - this.lastStart = this.clock.now(); + const startAt = + this.lastStart === 0 ? now : this.lastStart + this.minIntervalMs + this.nextJitter(); + this.lastStart = startAt; + const wait = startAt - now; + if (wait > 0) await this.clock.sleep(wait); } finally { release(); } } + + private nextJitter(): number { + return this.jitterMs > 0 ? Math.floor(this.rand() * this.jitterMs) : 0; + } } /** @@ -116,3 +120,8 @@ export function throttleQuotaFetch(): Promise { export function resetQuotaFetchThrottle(): void { _sharedThrottle = null; } + +/** Test-only: install a throttle as the process-wide gate. */ +export function setQuotaFetchThrottleForTests(throttle: MinIntervalThrottle | null): void { + _sharedThrottle = throttle; +} diff --git a/open-sse/services/quotaPreflight.ts b/open-sse/services/quotaPreflight.ts index 24d8d83f5739..7d60239a6d33 100644 --- a/open-sse/services/quotaPreflight.ts +++ b/open-sse/services/quotaPreflight.ts @@ -21,6 +21,7 @@ import { isCompatibleProviderConnectionId } from "@/shared/utils/compatibleProviderId"; import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags"; import { isClaudeExtraUsageAllowed } from "@/lib/providers/claudeExtraUsage"; +import { isCodexQuotaFilteringDisabled } from "@/lib/providers/codexQuotaFiltering"; // #14359 — import the leaf, NOT "@/domain/quotaCache": quotaCache → usage.ts → usage/openrouter.ts → // openrouterQuotaFetcher.ts → this file, so importing quotaCache here closes an ESM init cycle // that deadlocks the esbuild MCP bundle (tests/unit/build/mcp-bundle-startup.test.ts). @@ -78,6 +79,13 @@ export interface QuotaInfo { windowMonthly?: QuotaWindowInfo; /** True when the upstream usage endpoint explicitly reports exhausted quota. */ limitReached?: boolean; + /** + * True when the provider reports NO cap at all (every reported window is unlimited). It is + * a real, known reading of full headroom, not a failed one: `null` from a quota fetcher + * means "could not read it", so unlimited plans are marked here instead of returning null + * (#15347). `percentUsed` is 0 on such a snapshot. + */ + unlimited?: boolean; } export type QuotaFetcher = ( @@ -323,10 +331,11 @@ export function evaluateQuotaCutoff( scope?: QuotaCutoffScope ): PreflightQuotaResult { if (!quota) return { proceed: true }; - // Operator-enabled Claude extra usage is billed after the 5h session quota - // is gone. Pre-dispatch must not skip the account before Anthropic sees the - // request; blockExtraUsage=false is the only opt-in. - if (isClaudeExtraUsageAllowed(scope?.provider, scope?.providerSpecificData)) { + // Explicit local quota opt-outs leave billing eligibility to the upstream service. + if ( + isClaudeExtraUsageAllowed(scope?.provider, scope?.providerSpecificData) || + isCodexQuotaFilteringDisabled(scope?.provider, scope?.providerSpecificData) + ) { return { proceed: true, quotaPercent: quota.percentUsed }; } // #14359 — same escape as the dispatch-time predicates: a recent success is not exhaustion. diff --git a/open-sse/services/quotaTrackersBatch.ts b/open-sse/services/quotaTrackersBatch.ts index e66d41627226..ff82e19e37e4 100644 --- a/open-sse/services/quotaTrackersBatch.ts +++ b/open-sse/services/quotaTrackersBatch.ts @@ -1,11 +1,15 @@ /** * quotaTrackersBatch.ts — startup registration for batch quota trackers * (AgentRouter, v0-vercel, freemodel-dev, grok-cli, xai-oauth, firecrawl, - * llmgateway, lyceum). + * context7, tavily, llmgateway, lyceum). * - * Kept in a dedicated module (rather than adding more inline calls to - * `src/sse/handlers/chat.ts`, which is a frozen file at its LOC baseline) so the - * chokepoint file only needs a single import + a single call. + * Do not call `registerQuotaTrackersBatch()` at module scope. Webpack emits + * `quotaPreflight.ts` as an async module; a top-level call invokes + * `registerQuotaFetcher` before that live binding is a function + * (`(0 , e.Zd) is not a function`), rejects the chat-route module, and caches + * an empty HTTP 500 for every later chat/messages/responses request. + * Callers must invoke it once imports have resolved: the `chat.ts` module body, + * and `instrumentation-node.ts` after `await import`. */ import { registerAgentrouterQuotaFetcher } from "./agentrouterQuotaFetcher.ts"; @@ -31,9 +35,3 @@ export function registerQuotaTrackersBatch(): void { registerLlmgatewayQuotaFetcher(); registerLyceumQuotaFetcher(); } - -// Side-effect registration at module load, mirroring the sibling -// registerXQuotaFetcher() calls in chat.ts — done here (rather than as an -// additional call line in chat.ts) to keep the frozen chokepoint file's net -// diff to a single import line. -registerQuotaTrackersBatch(); diff --git a/open-sse/services/rateLimitManager/wedgeWatchdog.ts b/open-sse/services/rateLimitManager/wedgeWatchdog.ts index 624a413880b1..42c5a80189d4 100644 --- a/open-sse/services/rateLimitManager/wedgeWatchdog.ts +++ b/open-sse/services/rateLimitManager/wedgeWatchdog.ts @@ -18,6 +18,7 @@ interface LimiterWedgeWatchdogDependencies { trackBackground: (promise: Promise) => void; log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; + debug?: (...args: unknown[]) => void; } /** @@ -77,7 +78,11 @@ export class LimiterWedgeWatchdog { private async tick(now: number): Promise { const { limiters, limiterLastUsed, log, trackBackground, warn } = this.dependencies; + const debug = this.dependencies.debug ?? ((...args: unknown[]) => console.debug(...args)); + let evicted = 0; + let maxIdleMs = 0; + let hasValidIdle = false; for (const [key, limiter] of Array.from(limiters)) { const lastUsed = limiterLastUsed.get(key) ?? 0; if (now - lastUsed <= INACTIVE_LIMITER_MS) continue; @@ -85,15 +90,27 @@ export class LimiterWedgeWatchdog { const counts = limiter.counts(); if (counts.QUEUED > 0 || counts.RUNNING > 0 || counts.EXECUTING > 0) continue; + // Keep the eviction policy untouched: the row is removed even when its + // timestamp is missing. Only the headline figure skips corrupt rows so + // one missing timestamp cannot dominate the reported oldest idle age. + if (lastUsed > 0) { + maxIdleMs = Math.max(maxIdleMs, now - lastUsed); + hasValidIdle = true; + } + evicted += 1; limiters.delete(key); this.queueProgressAt.delete(limiter); limiterLastUsed.delete(key); - log( + debug( `[RATE-LIMIT] Evicting idle limiter: ${key} ` + `(inactive for ${Math.round((now - lastUsed) / 1000)}s)` ); trackBackground(limiter.disconnect()); } + if (evicted > 0) { + const oldestIdleSec = hasValidIdle ? Math.round(maxIdleMs / 1000) : 0; + log(`[RATE-LIMIT] Evicted idle limiters: ${evicted} (oldest idle ${oldestIdleSec}s)`); + } for (const [key, limiter] of Array.from(limiters)) { const snapshot = await this.getStableIdleCapacity(key, limiter, now); diff --git a/open-sse/services/tierResolver.ts b/open-sse/services/tierResolver.ts index 7fbe15f16dc8..09755a286090 100644 --- a/open-sse/services/tierResolver.ts +++ b/open-sse/services/tierResolver.ts @@ -250,6 +250,7 @@ export function classifyTier(provider: string, model: string): TierAssignment { tier = PROVIDER_TIER.PREMIUM; reason = `Cost-based: $${pricing.inputCostPer1M}/M input > cheap threshold ($${currentConfig.defaults.cheapThreshold}/M)`; } + if (pricing.isEstimated === true) reason += " (estimated)"; const assignment: TierAssignment = { provider, diff --git a/open-sse/services/tokenRefresh/circuitBreaker.ts b/open-sse/services/tokenRefresh/circuitBreaker.ts index ae5b503ea675..0c431ffeb5d0 100644 --- a/open-sse/services/tokenRefresh/circuitBreaker.ts +++ b/open-sse/services/tokenRefresh/circuitBreaker.ts @@ -15,8 +15,78 @@ import { isUnrecoverableRefreshError } from "./shared.ts"; const _circuitBreaker: Record = {}; const CIRCUIT_BREAKER_THRESHOLD = 5; // consecutive failures before tripping const CIRCUIT_BREAKER_COOLDOWN = 30 * 60 * 1000; // 30 minutes +const CIRCUIT_BREAKER_REGISTRY_CAP = 500; // max entries; blocked entries are never evicted const REFRESH_TIMEOUT_MS = 30_000; // 30s max per refresh attempt +export type TokenRefreshBreakerScope = "provider" | "connection"; + +export interface TokenRefreshBreakerOptions { + connectionId?: string; + scope?: TokenRefreshBreakerScope; + failureThreshold?: number; + cooldownMs?: number; +} + +function resolveBreakerOptions(options?: TokenRefreshBreakerOptions): { + scope: TokenRefreshBreakerScope; + failureThreshold: number; + cooldownMs: number; +} { + const raw = options ?? {}; + return { + scope: raw.scope === "connection" ? "connection" : "provider", + failureThreshold: + typeof raw.failureThreshold === "number" && + Number.isFinite(raw.failureThreshold) && + raw.failureThreshold >= 1 + ? Math.min(100, Math.trunc(raw.failureThreshold)) + : CIRCUIT_BREAKER_THRESHOLD, + cooldownMs: + // Direct callers (tests) may inject short cooldowns; production values + // come from normalized settings (>= 60s). + typeof raw.cooldownMs === "number" && Number.isFinite(raw.cooldownMs) && raw.cooldownMs > 0 + ? Math.min(24 * 60 * 60 * 1000, Math.max(1, Math.trunc(raw.cooldownMs))) + : CIRCUIT_BREAKER_COOLDOWN, + }; +} + +// Same key shape as the connection-scoped request breaker +// (open-sse/services/connectionCircuitBreaker.ts): one dead account must not +// block the sibling accounts on the same provider. Falls back to the provider +// key when no usable connection id is given. +function breakerKey( + provider: string, + connectionId: string | undefined, + scope: TokenRefreshBreakerScope +): string { + if (scope !== "connection") return provider; + const trimmed = typeof connectionId === "string" ? connectionId.trim() : ""; + return trimmed ? `${provider}::conn::${trimmed}` : provider; +} + +function evictExpiredBreakerEntries(now: number): void { + for (const [key, state] of Object.entries(_circuitBreaker)) { + if (state.blockedUntil && state.blockedUntil <= now) { + delete _circuitBreaker[key]; + } + } +} + +function makeBreakerRoom(now: number): boolean { + evictExpiredBreakerEntries(now); + if (Object.keys(_circuitBreaker).length < CIRCUIT_BREAKER_REGISTRY_CAP) return true; + // Full of live entries: evict one non-blocked entry, never a blocked one. + // When every entry is blocked, refuse the insert instead of growing + // unbounded or evicting live protection. + for (const [key, state] of Object.entries(_circuitBreaker)) { + if (!state.blockedUntil || state.blockedUntil <= now) { + delete _circuitBreaker[key]; + return true; + } + } + return false; +} + interface CircuitBreakerStatusEntry { failures: number; blocked: boolean; @@ -32,13 +102,18 @@ interface RefreshLoggerLike { /** * Check if a provider is circuit-breaker blocked. */ -export function isProviderBlocked(provider: string): boolean { - const state = _circuitBreaker[provider]; +export function isProviderBlocked(provider: string, options?: TokenRefreshBreakerOptions): boolean { + const { scope } = resolveBreakerOptions(options); + const key = breakerKey(provider, options?.connectionId, scope); + const state = _circuitBreaker[key]; if (!state) return false; if (!state.blockedUntil) return false; + // Absolute expiry: the trip instant is fixed at recordFailure time, so any + // read past the stored timestamp resets — regardless of the cooldown the + // current read carries. A read is never "more blocked" than the trip. if (state.blockedUntil > Date.now()) return true; // Cooldown expired — reset - delete _circuitBreaker[provider]; + delete _circuitBreaker[key]; return false; } @@ -62,27 +137,44 @@ export function getCircuitBreakerStatus(): Record= CIRCUIT_BREAKER_REGISTRY_CAP && + !_circuitBreaker[key] + ) { + if (!makeBreakerRoom(Date.now())) { + log?.warn?.( + "TOKEN_REFRESH", + `Circuit breaker registry full (${CIRCUIT_BREAKER_REGISTRY_CAP} blocked entries), skipping record for ${key}` + ); + return; + } + } + if (!_circuitBreaker[key]) { + _circuitBreaker[key] = { failures: 0, blockedUntil: 0 }; } - _circuitBreaker[provider].failures++; + _circuitBreaker[key].failures++; - if (_circuitBreaker[provider].failures >= CIRCUIT_BREAKER_THRESHOLD) { - _circuitBreaker[provider].blockedUntil = Date.now() + CIRCUIT_BREAKER_COOLDOWN; + if (_circuitBreaker[key].failures >= threshold) { + _circuitBreaker[key].blockedUntil = Date.now() + cooldownMs; log?.error?.( "TOKEN_REFRESH", - `🔴 Circuit breaker tripped for ${provider}: ${CIRCUIT_BREAKER_THRESHOLD} consecutive failures. ` + - `Blocked for ${CIRCUIT_BREAKER_COOLDOWN / 60000}min. Provider needs re-authentication.` + `🔴 Circuit breaker tripped for ${key}: ${threshold} consecutive failures. ` + + `Blocked for ${cooldownMs / 60000}min. Provider needs re-authentication.` ); } } @@ -128,11 +220,14 @@ export async function refreshWithRetry( refreshFn, maxRetries = 3, log: RefreshLogger = null, - provider = "unknown" + provider = "unknown", + options?: TokenRefreshBreakerOptions ) { + const effective = resolveBreakerOptions(options); + const key = breakerKey(provider, options?.connectionId, effective.scope); // Circuit breaker check - if (isProviderBlocked(provider)) { - log?.warn?.("TOKEN_REFRESH", `⚡ Circuit breaker active for ${provider}, skipping refresh`); + if (isProviderBlocked(provider, options)) { + log?.warn?.("TOKEN_REFRESH", `⚡ Circuit breaker active for ${key}, skipping refresh`); return null; } @@ -148,12 +243,12 @@ export async function refreshWithRetry( if (isUnrecoverableRefreshError(result)) { log?.warn?.( "TOKEN_REFRESH", - `Unrecoverable refresh error for ${provider}: ${result.error} — skipping retries` + `Unrecoverable refresh error for ${key}: ${result.error} — skipping retries` ); return result; } if (result) { - recordSuccess(provider); + recordSuccess(key); return result; } } catch (error) { @@ -162,7 +257,7 @@ export async function refreshWithRetry( } // All retries exhausted — record failure for circuit breaker - recordFailure(provider, log); - log?.error?.("TOKEN_REFRESH", `All ${maxRetries} retry attempts failed for ${provider}`); + recordFailure(key, effective.failureThreshold, effective.cooldownMs, log); + log?.error?.("TOKEN_REFRESH", `All ${maxRetries} retry attempts failed for ${key}`); return null; } diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts index 8bacffc6cb2a..077533ae0c0c 100644 --- a/open-sse/services/usage.ts +++ b/open-sse/services/usage.ts @@ -43,6 +43,7 @@ import { import { getCursorUsage } from "./usage/cursor.ts"; import { getKimiUsage } from "./usage/kimi.ts"; import { getCodexUsage } from "./usage/codex.ts"; +import { throttleQuotaFetch } from "./quotaFetchThrottle.ts"; import { getClaudeUsage, getClaudePlanLabel } from "./usage/claude.ts"; import { getKiroUsage, buildKiroUsageResult, discoverKiroProfileArn } from "./usage/kiro.ts"; // Re-exported para os testes kiro-* (importam de services/usage). @@ -154,6 +155,11 @@ export async function getUsageForProvider( case "claude": return await getClaudeUsage(accessToken); case "codex": + // /me/status and the quota-cache refresh reach this fetch with no + // caller-side pacing. Gate the start here so those paths, and any later + // one, cannot burst the usage endpoint. Callers that already acquired the + // shared gate wait at most one more interval. + await throttleQuotaFetch(); return await getCodexUsage(accessToken, providerSpecificData); case "cursor": return await getCursorUsage(accessToken || "", providerSpecificData); diff --git a/open-sse/services/usage/codex.ts b/open-sse/services/usage/codex.ts index 64b37c01838e..3d8b44b85fc2 100644 --- a/open-sse/services/usage/codex.ts +++ b/open-sse/services/usage/codex.ts @@ -17,6 +17,14 @@ const CODEX_CONFIG = { usageUrl: "https://chatgpt.com/backend-api/wham/usage", }; +type CodexUsageFetch = (input: string, init: RequestInit) => Promise; +let codexUsageFetch: CodexUsageFetch = (input, init) => fetch(input, init); + +/** Test-only: replace the usage request. Pass null to restore global fetch. */ +export function setCodexUsageFetchForTests(fetchImpl: CodexUsageFetch | null): void { + codexUsageFetch = fetchImpl ?? ((input, init) => fetch(input, init)); +} + /** * Codex (OpenAI) Usage - Fetch from ChatGPT backend API * IMPORTANT: Uses persisted workspaceId from OAuth to ensure correct workspace binding. @@ -46,7 +54,7 @@ export async function getCodexUsage( headers["chatgpt-account-id"] = accountId; } - const response = await fetch(CODEX_CONFIG.usageUrl, { + const response = await codexUsageFetch(CODEX_CONFIG.usageUrl, { method: "GET", headers, }); diff --git a/open-sse/services/xaiOauthQuotaFetcher.ts b/open-sse/services/xaiOauthQuotaFetcher.ts index f76cdfe229f7..44a3dca43452 100644 --- a/open-sse/services/xaiOauthQuotaFetcher.ts +++ b/open-sse/services/xaiOauthQuotaFetcher.ts @@ -19,8 +19,8 @@ * * Cache: 60s in-memory TTL keyed by connectionId. * - * Registration: registerXaiOauthQuotaFetcher() via quotaTrackersBatch side-effect - * import (before registerGenericQuotaFetchers). + * Registration: registerXaiOauthQuotaFetcher() via registerQuotaTrackersBatch() + * (before registerGenericQuotaFetchers). Not a module-load side effect. */ import { diff --git a/open-sse/translator/helpers/geminiHelper.ts b/open-sse/translator/helpers/geminiHelper.ts index 9689b2a377ed..d197d20b1257 100644 --- a/open-sse/translator/helpers/geminiHelper.ts +++ b/open-sse/translator/helpers/geminiHelper.ts @@ -138,6 +138,32 @@ export const DEFAULT_SAFETY_SETTINGS = [ { category: "HARM_CATEGORY_HARASSMENT", threshold: "OFF" }, ]; +// Google AI Studio rejects `thinkingBudget: 0` on Flash-Lite models +// (gemini-flash-lite-latest, gemini-3.5-flash-lite, …) with a bare +// 400 INVALID_ARGUMENT, while the same request succeeds when thinkingBudget is +// omitted (`{ includeThoughts: false }`) or thinkingConfig is absent entirely. +const GEMINI_FLASH_LITE_PATTERN = /(?:^|[-_/])(?:flash[-_]lite|lite)(?:$|[-_.])/i; + +export function isGeminiFlashLiteModel(model: unknown): boolean { + return typeof model === "string" && GEMINI_FLASH_LITE_PATTERN.test(model); +} + +/** + * Build a Gemini thinkingConfig, dropping a zero thinkingBudget for Flash-Lite + * models (which 400 on it). Other models keep the explicit `thinkingBudget: 0` + * contract (#6813 / #6943). + */ +export function buildGeminiThinkingConfig( + model: unknown, + thinkingBudget: number, + includeThoughts: boolean +): { thinkingBudget?: number; includeThoughts: boolean } { + if (thinkingBudget === 0 && isGeminiFlashLiteModel(model)) { + return { includeThoughts }; + } + return { thinkingBudget, includeThoughts }; +} + function normalizeAudioMimeType(format: unknown): string { const normalized = typeof format === "string" && format.trim() ? format.trim().toLowerCase() : "wav"; diff --git a/open-sse/translator/helpers/responsesTerminalStatus.ts b/open-sse/translator/helpers/responsesTerminalStatus.ts index 243104de22ee..0949ceae895c 100644 --- a/open-sse/translator/helpers/responsesTerminalStatus.ts +++ b/open-sse/translator/helpers/responsesTerminalStatus.ts @@ -5,7 +5,7 @@ * vendor/codex-chatgpt-web/bridge.ts — and not silently as "completed", which * gives a caller no signal that the output was cut off mid-generation rather * than the model actually finishing. An upstream error is a harder failure and - * still wins: with `hasUpstreamError` the response is left untouched. + * still wins: with `hasUpstreamError` emit the failure terminal. * * Mutates `response` in place and returns the terminal SSE event type to emit. */ @@ -13,14 +13,15 @@ export function finalizeResponsesTerminalStatus( response: Record, finishReason: unknown, hasUpstreamError = false -): "response.completed" | "response.incomplete" { +): "response.completed" | "response.incomplete" | "response.failed" { + if (hasUpstreamError) return "response.failed"; const reason = finishReason === "length" ? "max_output_tokens" : finishReason === "content_filter" ? "content_filter" : undefined; - if (hasUpstreamError || !reason) return "response.completed"; + if (!reason) return "response.completed"; response.status = "incomplete"; response.incomplete_details = { reason }; return "response.incomplete"; diff --git a/open-sse/translator/request/claude-to-gemini.ts b/open-sse/translator/request/claude-to-gemini.ts index a566e2add9a9..bf5acbbd5133 100644 --- a/open-sse/translator/request/claude-to-gemini.ts +++ b/open-sse/translator/request/claude-to-gemini.ts @@ -1,6 +1,10 @@ import { register } from "../registry.ts"; import { FORMATS } from "../formats.ts"; -import { DEFAULT_SAFETY_SETTINGS, cleanJSONSchemaForAntigravity } from "../helpers/geminiHelper.ts"; +import { + DEFAULT_SAFETY_SETTINGS, + buildGeminiThinkingConfig, + cleanJSONSchemaForAntigravity, +} from "../helpers/geminiHelper.ts"; import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts"; import { buildGeminiThoughtSignatureKey, @@ -316,14 +320,12 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) { // but thinkingBudgetCap:24576, meaning it supports thinking via budget). // Models not in MODEL_SPECS (thinkingBudgetCap=undefined) default to allowed. if (cappedBudget > 0 || getModelSpec(model)?.thinkingBudgetCap !== 0) { - result.generationConfig.thinkingConfig = { - thinkingBudget: cappedBudget, - // #6813: `budget_tokens: 0` on this explicit path is the client's dynamic-thinking - // sentinel, not an off-switch — includeThoughts stays true regardless of the - // (possibly cap-clamped) budget value. Only the reasoning_effort/output_config.effort - // paths below treat a resulting budget of 0 as "thinking disabled". - includeThoughts: true, - }; + // #6813: `budget_tokens: 0` on this explicit path is the client's dynamic-thinking + // sentinel, not an off-switch — includeThoughts stays true regardless of the + // (possibly cap-clamped) budget value. Only the reasoning_effort/output_config.effort + // paths below treat a resulting budget of 0 as "thinking disabled". + // Flash-Lite models 400 on thinkingBudget 0, so it is omitted for them. + result.generationConfig.thinkingConfig = buildGeminiThinkingConfig(model, cappedBudget, true); } } else if (typeof body.output_config?.effort === "string") { const effort = body.output_config.effort.toLowerCase(); diff --git a/open-sse/translator/request/openai-to-gemini.ts b/open-sse/translator/request/openai-to-gemini.ts index 91a7ce009c3b..29f2473e0757 100644 --- a/open-sse/translator/request/openai-to-gemini.ts +++ b/open-sse/translator/request/openai-to-gemini.ts @@ -20,12 +20,17 @@ import { getModelSpec } from "../../../src/shared/constants/modelSpecs.ts"; import { DEFAULT_SAFETY_SETTINGS, + buildGeminiThinkingConfig, convertOpenAIContentToParts, extractTextContent, tryParseJSON, cleanJSONSchemaForAntigravity, } from "../helpers/geminiHelper.ts"; import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts"; +import { + normalizeGeminiToolCallIds, + validateChronologicalToolCallIds, +} from "./openai-to-gemini/toolCallIds.ts"; import { type GeminiGenerationConfig, isVertexGeminiProvider, @@ -276,13 +281,14 @@ function openaiToGeminiBase( // requested budget down to 0. Omitting thinkingConfig entirely here regressed // the pre-#6943 native-defaults contract (thinkingBudget 0 / includeThoughts // false must still be present) and crashed callers that read - // .thinkingConfig.thinkingBudget unconditionally. + // .thinkingConfig.thinkingBudget unconditionally. Flash-Lite models are the + // exception: AI Studio 400s on thinkingBudget 0, so it is omitted there. // Gemini 3.x exception: an explicit thinkingLevel makes the numeric budget // redundant (the deprecated field is dropped when the level is forwarded), so // only include thinkingBudget when no explicit level was supplied. result.generationConfig.thinkingConfig = explicitThinkingLevel ? { includeThoughts: budget !== 0 } - : { thinkingBudget: budget, includeThoughts: budget !== 0 }; + : buildGeminiThinkingConfig(model, budget, budget !== 0); } // 2. Claude format: thinking (type: enabled, budget_tokens) // Use an explicit numeric check (not truthy) so an explicit `budget_tokens: 0` — the @@ -304,7 +310,7 @@ function openaiToGeminiBase( if (cappedBudget > 0 || getModelSpec(model)?.thinkingBudgetCap !== 0) { result.generationConfig.thinkingConfig = explicitThinkingLevel ? { includeThoughts: cappedBudget !== 0 } - : { thinkingBudget: cappedBudget, includeThoughts: cappedBudget !== 0 }; + : buildGeminiThinkingConfig(model, cappedBudget, cappedBudget !== 0); } } } @@ -377,6 +383,7 @@ function openaiToGeminiBase( // OpenCode's known abort/cancel bug) reaches Google's Cloud Code envelope as an unpaired // functionCall, which Vertex's Claude backend rejects with HTTP 400. const rawMessages = body.messages as Array> | undefined; + validateChronologicalToolCallIds(rawMessages); const messages = rawMessages && Array.isArray(rawMessages) ? (fixToolPairs(rawMessages) as Array>) @@ -655,6 +662,8 @@ function openaiToGeminiBase( // Guard the one alternation violation the merge above cannot reach: history // that opens with a functionCall-bearing turn instead of a user turn. result.contents = ensureHistoryDoesNotOpenWithFunctionCall(result.contents); + // Signatures must be resolved using client IDs before assigning unique wire IDs (#15312). + result.contents = normalizeGeminiToolCallIds(result.contents); // Convert tools const bodyTools = body.tools as Array> | undefined; diff --git a/open-sse/translator/request/openai-to-gemini/toolCallIds.ts b/open-sse/translator/request/openai-to-gemini/toolCallIds.ts new file mode 100644 index 000000000000..bd0c2540d8c9 --- /dev/null +++ b/open-sse/translator/request/openai-to-gemini/toolCallIds.ts @@ -0,0 +1,99 @@ +import type { GeminiContent, GeminiPart } from "./helpers.ts"; + +type NativeTool = Record & { id: string }; + +function ambiguousCallError(): Error { + return Object.assign(new Error("Cannot translate overlapping tool calls with the same ID."), { + statusCode: 400, + errorType: "invalid_request_error", + }); +} + +function trackPendingCall(pending: Set, id: unknown): void { + if (typeof id !== "string" || !id) return; + if (pending.has(id)) throw ambiguousCallError(); + pending.add(id); +} + +export function validateChronologicalToolCallIds( + messages: Record[] | undefined +): void { + if (!Array.isArray(messages)) return; + const pending = new Set(); + for (const message of messages) { + if (message.role === "assistant" && Array.isArray(message.tool_calls)) { + for (const call of message.tool_calls as Array>) { + if (call.type === "function") trackPendingCall(pending, call.id); + } + } else if (message.role === "tool" && typeof message.tool_call_id === "string") { + pending.delete(message.tool_call_id); + } + } +} + +function readNativeTool( + part: GeminiPart, + key: "functionCall" | "functionResponse" +): NativeTool | null { + const value = part[key]; + if (!value || typeof value !== "object") return null; + const tool = value as Record; + return typeof tool.id === "string" && tool.id ? (tool as NativeTool) : null; +} + +function reserveToolIds(contents: GeminiContent[]): Set { + const ids = new Set(); + for (const content of contents) { + for (const part of content.parts) { + for (const key of ["functionCall", "functionResponse"] as const) { + const tool = readNativeTool(part, key); + if (tool) ids.add(tool.id); + } + } + } + return ids; +} + +function allocateWireId( + id: string, + reserved: Set, + nextOccurrence: Map +): string { + let occurrence = nextOccurrence.get(id) ?? 1; + if (occurrence === 1) { + nextOccurrence.set(id, 2); + return id; + } + let wireId: string; + do { + wireId = `${id}_occ${occurrence++}`; + } while (reserved.has(wireId)); + reserved.add(wireId); + nextOccurrence.set(id, occurrence); + return wireId; +} + +export function normalizeGeminiToolCallIds(contents: GeminiContent[]): GeminiContent[] { + const reserved = reserveToolIds(contents); + const nextOccurrence = new Map(); + const pending = new Map(); + return contents.map((content) => ({ + ...content, + parts: content.parts.map((part) => { + const call = readNativeTool(part, "functionCall"); + if (call) { + if (pending.has(call.id)) throw ambiguousCallError(); + const wireId = allocateWireId(call.id, reserved, nextOccurrence); + pending.set(call.id, wireId); + return wireId === call.id ? part : { ...part, functionCall: { ...call, id: wireId } }; + } + const response = readNativeTool(part, "functionResponse"); + if (!response) return part; + const wireId = pending.get(response.id); + pending.delete(response.id); + return wireId === undefined || wireId === response.id + ? part + : { ...part, functionResponse: { ...response, id: wireId } }; + }), + })); +} diff --git a/open-sse/translator/response/openai-responses.ts b/open-sse/translator/response/openai-responses.ts index 2550d0343e1c..263d96059130 100644 --- a/open-sse/translator/response/openai-responses.ts +++ b/open-sse/translator/response/openai-responses.ts @@ -814,7 +814,7 @@ function sendCompleted(state, emit) { const output = buildDenseOutput(state); // Surface upstream mid-stream errors (e.g. Gemini 503) in the - // Responses-API `response.completed` event instead of silently emitting + // Responses-API `response.failed` event instead of silently emitting // `status: "completed"`. The error is set by the Gemini-to-OpenAI // translator or the OpenAI-Responses translator itself when the upstream // SSE stream emits a JSON error object after partial content. @@ -850,6 +850,17 @@ function flushEvents(state) { const { events, emit } = createEventEmitter(state); + // EOF is not a Chat Completions finish signal. Preserve partial items, but + // surface the missing upstream terminal instead of manufacturing success. + if (!state.finishReason && !state.upstreamError) { + state.upstreamError = { + status: 502, + type: "server_error", + code: "stream_early_eof", + message: "Upstream stream ended without a terminal marker", + }; + } + for (const i in state.msgItemAdded) closeMessage(state, emit, i); closeReasoning(state, emit); for (const i in state.funcCallIds) closeToolCall(state, emit, i); diff --git a/open-sse/utils/antigravityLiteralModelIds.ts b/open-sse/utils/antigravityLiteralModelIds.ts new file mode 100644 index 000000000000..d87f45f8f29e --- /dev/null +++ b/open-sse/utils/antigravityLiteralModelIds.ts @@ -0,0 +1,64 @@ +/** + * Antigravity (`antigravity` / `ag` / `agy`) serves Claude 5.x the way it serves + * Gemini 3.8 Flash: every reasoning tier is its OWN upstream model id. The live + * `:fetchAvailableModels` roster lists `claude-opus-5-5-low`, `claude-opus-5-5-medium`, + * `claude-opus-5-5-high` (same for Sonnet 5.5) and no bare `claude-opus-5-5`. + * + * For these ids a trailing `-{low,medium,high}` is NOT a client-side effort variant: + * `applyClaudeEffortVariant` stripping it dispatches the bare base id, which the + * Cloud Code backend rejects (observed as `429 Resource has been exhausted` on every + * account, even ones that never used Claude). Keep them literal. + * + * Claude 4.x Antigravity ids (`claude-opus-4-6-thinking`, `claude-sonnet-4-6`) carry + * no tier suffix and are unaffected. + */ + +const ANTIGRAVITY_PROVIDER_TOKENS = new Set(["antigravity", "ag", "agy"]); + +// claude----, major >= 5 (the generation that moved to +// per-tier upstream ids on Antigravity). +const ANTIGRAVITY_TIERED_CLAUDE_RE = + /^claude-(?:opus|sonnet|haiku)-(?:[5-9]|\d{2,})-\d+-(?:low|medium|high)$/i; +// Same generation without a tier suffix (a hypothetical future bare id). +const ANTIGRAVITY_CLAUDE_5_BASE_RE = /^claude-(?:opus|sonnet|haiku)-(?:[5-9]|\d{2,})-\d+$/i; + +function bareProviderToken(value: string): string { + const slash = value.indexOf("/"); + return slash >= 0 ? value.slice(0, slash) : value; +} + +function bareModelId(value: string): string { + const slash = value.lastIndexOf("/"); + return slash >= 0 ? value.slice(slash + 1) : value; +} + +/** True when `provider` (id, alias, or `provider/model`) is an Antigravity surface. */ +export function isAntigravityProvider(provider: string | null | undefined): boolean { + if (typeof provider !== "string" || provider.length === 0) return false; + return ANTIGRAVITY_PROVIDER_TOKENS.has(bareProviderToken(provider).toLowerCase()); +} + +/** + * True when `model` on `provider` is an Antigravity Claude id whose tier suffix is part + * of the upstream model id and must be dispatched verbatim. + */ +export function isAntigravityLiteralTierModelId( + provider: string | null | undefined, + model: string | null | undefined +): boolean { + if (!isAntigravityProvider(provider)) return false; + if (typeof model !== "string" || model.length === 0) return false; + return ANTIGRAVITY_TIERED_CLAUDE_RE.test(bareModelId(model)); +} + +/** + * True when a catalog entry `/claude--<5+>-` must not + * get synthesized `-` effort variants. Antigravity publishes its tiers as real + * catalog ids; synthesized ones would be dispatched verbatim (see above) and could name + * ids that do not exist upstream (e.g. `-xhigh`). + */ +export function isAntigravityClaudeTierFamilyBase(qualifiedId: string): boolean { + const slash = qualifiedId.indexOf("/"); + if (slash <= 0 || !isAntigravityProvider(qualifiedId.slice(0, slash))) return false; + return ANTIGRAVITY_CLAUDE_5_BASE_RE.test(bareModelId(qualifiedId)); +} diff --git a/open-sse/utils/audioResponse.ts b/open-sse/utils/audioResponse.ts index d0c6bf61d784..772032d4b246 100644 --- a/open-sse/utils/audioResponse.ts +++ b/open-sse/utils/audioResponse.ts @@ -8,6 +8,8 @@ * itself — which would create a circular import. */ import { CORS_HEADERS } from "./cors.ts"; +import { errorResponse } from "./error.ts"; +import { sanitizeErrorMessage } from "./errorSanitization.ts"; /** * Pull a human-readable error message out of a parsed upstream JSON error body. @@ -28,25 +30,56 @@ function extractUpstreamErrorMessage(parsed) { /** * Return a CORS error response from an upstream fetch failure. + * + * #15159: this used to interpolate the raw upstream body into `error.message`. It is + * called with `await res.text()` from ~35 provider call sites (audioSpeech, the TTS + * executors, and every transcription provider), so anything a provider chose to return — + * a stack frame, an absolute path, a credential echoed in its own error text — reached + * the client verbatim. That is what the repo's own rule forbids: + * `upstreamErrorResponse.ts:39-41` says an opaque body must not be echoed "even + * sanitized fragments", and `moderations.ts` / `ocr.ts` already comply. + * + * Now: + * - a JSON body keeps its shape through `buildSanitizedUpstreamErrorResponse`, which + * runs `sanitizeUpstreamDetails` over every value (credentials, paths, stack tails, + * credential-shaped keys are dropped recursively); + * - an opaque / non-JSON body gets the canonical envelope with a fixed fallback, never + * the provider's text; + * - CORS is re-applied afterwards, because the canonical builders do not emit it and + * these routes set CORS only on the OPTIONS preflight. */ export function upstreamErrorResponse(res: Response, errText: string): Response { - // Always return JSON so the client can detect 401/credential errors reliably - let errorMessage: string; + const status = res.status; + const fallbackMessage = `Upstream error (${status})`; + const text = errText ?? ""; + + // Only a JSON body's own message is reused. An opaque body (HTML / plaintext — which + // providers return constantly despite the JSON content type) is never echoed, per + // upstreamErrorResponse.ts:39-41: such pages can carry credentials or implementation + // details OUTSIDE the patterns the canonical sanitizer knows about, so echoing even a + // "sanitized fragment" is unsafe. + let upstreamMessage: string | null = null; try { - const parsed = JSON.parse(errText); - errorMessage = - extractUpstreamErrorMessage(parsed) || errText || `Upstream error (${res.status})`; + const parsed: unknown = JSON.parse(text); + if (parsed && typeof parsed === "object") { + upstreamMessage = extractUpstreamErrorMessage(parsed); + } } catch { - errorMessage = errText || `Upstream error (${res.status})`; + upstreamMessage = null; } - return Response.json( - { error: { message: errorMessage, code: res.status } }, - { - status: res.status, - headers: { ...CORS_HEADERS }, - } - ); + // `errorResponse` runs sanitizeErrorMessage internally, so the upstream's reason is + // kept when it is safe prose ("Rate limit exceeded for this organization") and reduced + // when it is not (stack frames, absolute paths, credential-shaped strings). + const safeMessage = upstreamMessage ? sanitizeErrorMessage(upstreamMessage) : ""; + const response = errorResponse(status, safeMessage || fallbackMessage); + + // The canonical builders do not emit CORS, and these routes set it only on the OPTIONS + // preflight — re-apply it or browser clients break (same trap as #15159 E-02). + for (const [header, value] of Object.entries(CORS_HEADERS)) { + response.headers.set(header, value); + } + return response; } /** diff --git a/open-sse/utils/ccDiscoveryAliases.ts b/open-sse/utils/ccDiscoveryAliases.ts index 70fda8989b87..87d912e6b7e6 100644 --- a/open-sse/utils/ccDiscoveryAliases.ts +++ b/open-sse/utils/ccDiscoveryAliases.ts @@ -35,11 +35,6 @@ const ALREADY_CLAUDE_RE = /^(?:claude|anthropic)(?:\/|$)/i; // Ids that already carry a reasoning-effort suffix — v1 only mirrors base ids. const CLAUDE_EFFORT_SUFFIX_RE = /-(?:xhigh|high|medium|low)$/i; const NO_THINKING_PREFIX = "no-think/"; -// Built-in `auto`/`auto/*` combos are synthesized by createBuiltinAutoCombo, NOT -// stored in the DB combos table — the request-path resolver (getComboByName) can't -// find them, so mirroring them would advertise a `claude/combo/auto/*` id that the -// request path rejects. Skip them; DB-defined combos (arbitrary names) still mirror. -const BUILTIN_AUTO_COMBO_RE = /^auto(?:\/|$)/; interface CcDiscoveryCatalogEntry { id?: unknown; @@ -89,8 +84,6 @@ export function appendCcDiscoveryAliases( if (!isEnabled(model)) continue; const isCombo = model.owned_by === "combo"; - // Skip built-in auto combos — advertised-but-unroutable (see the regex above). - if (isCombo && BUILTIN_AUTO_COMBO_RE.test(id)) continue; const aliasId = isCombo ? `${CC_DISCOVERY_COMBO_PREFIX}${id}` : `${CC_DISCOVERY_PREFIX}${id}`; const label = typeof model.name === "string" && model.name ? model.name : id; diff --git a/open-sse/utils/claudeEffortVariants.ts b/open-sse/utils/claudeEffortVariants.ts index e0a1e90f38cc..acf50e8bc612 100644 --- a/open-sse/utils/claudeEffortVariants.ts +++ b/open-sse/utils/claudeEffortVariants.ts @@ -32,6 +32,7 @@ import { getModelSpec } from "@/shared/constants/modelSpecs"; import { extendCodexGpt56EffortValues } from "@/shared/reasoning/effortStandardization"; import { supportsXHighEffort } from "../config/providerModels.ts"; +import { isAntigravityClaudeTierFamilyBase } from "./antigravityLiteralModelIds.ts"; import { isDevinLiteralModelIdProvider } from "./devinLiteralModelIds.ts"; /** Base reasoning-effort levels advertised for every effort-capable Claude model. */ @@ -115,6 +116,9 @@ export function shouldExposeClaudeEffortVariants( if (providerSlash > 0 && isDevinLiteralModelIdProvider(id.slice(0, providerSlash))) { return false; } + // Antigravity Claude 5.x tiers are real catalog ids kept literal at dispatch + // (antigravityLiteralModelIds.ts) — never synthesize phantom tiers on a bare id. + if (isAntigravityClaudeTierFamilyBase(id)) return false; const name = bareModelName(id); if (KIRO_OPUS_5_MAX_VARIANT_RE.test(name)) return false; diff --git a/open-sse/utils/cursorAgentCliVersion.ts b/open-sse/utils/cursorAgentCliVersion.ts index ba0dda576a28..ce00aad6348a 100644 --- a/open-sse/utils/cursorAgentCliVersion.ts +++ b/open-sse/utils/cursorAgentCliVersion.ts @@ -4,8 +4,8 @@ * Wire header: `x-cursor-client-version: cli-${id}` where `id` is a dated * build like `2026.07.08-0c04a8a` (not the IDE `3.x` semver). * - * Resolution: CURSOR_AGENT_CLI_VERSION env → local install detect → - * disk-cached installer scrape (stale-while-revalidate) → pin. + * Resolution: CURSOR_AGENT_CLI_VERSION env, then local install detect, + * then a disk-cached installer scrape (stale-while-revalidate), then the pin. */ import { @@ -24,15 +24,14 @@ import { CURSOR_AGENT_CLI_VERSION } from "./cursorAgentCliVersionPin.ts"; export { CURSOR_AGENT_CLI_VERSION }; const VERSION_ID_RE = /^\d{4}\.\d{2}\.\d{2}-[0-9a-f]+$/; -const CACHE_TTL_MS = 60 * 60 * 1000; +const CACHE_TTL_MS = 6 * 60 * 60 * 1000; const INSTALL_URL = "https://cursor.com/install"; -const REMOTE_TIMEOUT_MS = 5_000; +const REMOTE_TIMEOUT_MS = 20_000; const VERSION_CACHE_FILE = "cursor-agent-cli-version.json"; let cachedVersion: string | null = null; let cachedAt = 0; -let remoteRefreshInFlight: Promise | null = null; -let remoteRefreshScheduled = false; +let remoteRefreshInFlight: Promise | null = null; /** Test seam: override fetch for installer scrape. */ let fetchImpl: typeof fetch = fetch; @@ -43,6 +42,24 @@ export function isCursorAgentCliVersionId(value: string): boolean { return VERSION_ID_RE.test(value); } +/** Calendar date embedded in a `YYYY.MM.DD-` build id, or null. */ +export function cursorAgentCliVersionDate(id: string): string | null { + if (!isCursorAgentCliVersionId(id)) return null; + return id.slice(0, 10); +} + +/** + * Prefer `candidate` only when its date is strictly later than `floor`. + * A missing, invalid, or older-or-equal candidate keeps the floor (the pin). + */ +export function preferNewerCursorAgentCliVersion(floor: string, candidate: string | null): string { + if (!candidate || !isCursorAgentCliVersionId(candidate)) return floor; + const floorDate = cursorAgentCliVersionDate(floor); + const candidateDate = cursorAgentCliVersionDate(candidate); + if (!floorDate || !candidateDate || candidateDate <= floorDate) return floor; + return candidate; +} + export function formatCursorAgentClientVersion(id: string): string { return `cli-${id}`; } @@ -160,89 +177,101 @@ function writeDiskVersionCache(cache: DiskVersionCache): void { } async function fetchInstallerVersionId(): Promise { - const response = await fetchImpl(INSTALL_URL, { - signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS), + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), REMOTE_TIMEOUT_MS); + try { + const response = await fetchImpl(INSTALL_URL, { + signal: controller.signal, + }); + if (!response.ok) return null; + const text = await response.text(); + return extractVersionIdFromInstallerScript(text); + } catch { + return null; + } finally { + clearTimeout(timeoutId); + } +} + +function ensureRemoteVersion(): Promise { + if (remoteRefreshInFlight) return remoteRefreshInFlight; + remoteRefreshInFlight = (async () => { + const id = await fetchInstallerVersionId(); + if (id) writeDiskVersionCache({ version: id, fetchedAt: Date.now() }); + return id; + })().finally(() => { + remoteRefreshInFlight = null; }); - if (!response.ok) return null; - const text = await response.text(); - return extractVersionIdFromInstallerScript(text); + return remoteRefreshInFlight; } -function scheduleRemoteVersionRefresh(): void { - if (remoteRefreshInFlight || remoteRefreshScheduled) return; - // Defer so sync header resolution never starts network in the same turn. - remoteRefreshScheduled = true; - setTimeout(() => { - remoteRefreshScheduled = false; - if (remoteRefreshInFlight) return; - remoteRefreshInFlight = (async () => { - try { - const id = await fetchInstallerVersionId(); - if (id) writeDiskVersionCache({ version: id, fetchedAt: Date.now() }); - } catch { - // Ignore — pin / stale cache remain valid. - } finally { - remoteRefreshInFlight = null; - } - })(); - }, 0); +function resolveLocalCursorAgentCliVersion(now: number): string | null { + const fromEnv = process.env.CURSOR_AGENT_CLI_VERSION?.trim(); + if (fromEnv && isCursorAgentCliVersionId(fromEnv)) return fromEnv; + + const home = process.env.HOME || process.env.USERPROFILE || homedir(); + const fromFs = detectCursorAgentCliVersionFromFs(home); + if (fromFs) return fromFs; + + const disk = readDiskVersionCache(); + if (disk && now - disk.fetchedAt < CACHE_TTL_MS) { + return preferNewerCursorAgentCliVersion(CURSOR_AGENT_CLI_VERSION, disk.version); + } + return null; } /** - * Resolve CLI build id synchronously for request headers. - * Env → local FS → disk cache (refresh in background if stale) → pin. + * CLI build id for `x-cursor-client-version`. + * Env and a local install win. Otherwise https://cursor.com/install is fetched + * (20s timeout, cached 6h) and used only when its date is strictly later than + * the pin. A rejected or unusable fetch keeps the pin. */ -export function getCursorAgentCliVersion(): string { +export async function getCursorAgentCliVersion(): Promise { const now = Date.now(); if (cachedVersion && now - cachedAt < CACHE_TTL_MS) { return cachedVersion; } - const fromEnv = process.env.CURSOR_AGENT_CLI_VERSION?.trim(); - if (fromEnv && isCursorAgentCliVersionId(fromEnv)) { - cachedVersion = fromEnv; - cachedAt = now; - return cachedVersion; - } - - const home = process.env.HOME || process.env.USERPROFILE || homedir(); - const fromFs = detectCursorAgentCliVersionFromFs(home); - if (fromFs) { - cachedVersion = fromFs; + const local = resolveLocalCursorAgentCliVersion(now); + if (local && local !== CURSOR_AGENT_CLI_VERSION) { + cachedVersion = local; cachedAt = now; return cachedVersion; } const disk = readDiskVersionCache(); - if (disk) { - cachedVersion = disk.version; - cachedAt = now; - // Stale-while-revalidate (oakimov): always serve disk cache; refresh in - // background when fresh (keep warm) or stale. - scheduleRemoteVersionRefresh(); - return cachedVersion; + if (disk && now - disk.fetchedAt < CACHE_TTL_MS) { + const cached = preferNewerCursorAgentCliVersion(CURSOR_AGENT_CLI_VERSION, disk.version); + if (cached !== CURSOR_AGENT_CLI_VERSION) { + cachedVersion = cached; + cachedAt = now; + return cached; + } } - scheduleRemoteVersionRefresh(); - return CURSOR_AGENT_CLI_VERSION; + // Do not block the request on the installer scrape. Use the disk cache or + // the pin now, and let the fetch land for the next call. + if (!process.env.NODE_TEST_CONTEXT) void ensureRemoteVersion().catch(() => null); + const resolved = preferNewerCursorAgentCliVersion( + CURSOR_AGENT_CLI_VERSION, + disk?.version ?? null + ); + cachedVersion = resolved; + cachedAt = Date.now(); + return resolved; } /** * Await a remote installer scrape (tests / warm-up). Writes disk cache on success. */ export async function refreshCursorAgentCliVersionFromInstaller(): Promise { - try { - const id = await fetchInstallerVersionId(); - if (id) { - writeDiskVersionCache({ version: id, fetchedAt: Date.now() }); - cachedVersion = id; - cachedAt = Date.now(); - return id; - } - } catch { - /* ignore */ - } - return null; + const id = await fetchInstallerVersionId(); + if (!id) return null; + const resolved = preferNewerCursorAgentCliVersion(CURSOR_AGENT_CLI_VERSION, id); + writeDiskVersionCache({ version: resolved, fetchedAt: Date.now() }); + cachedVersion = resolved; + cachedAt = Date.now(); + return resolved; } /** Exposed for testing: reset the in-memory cache. */ @@ -250,7 +279,6 @@ export function resetCursorAgentCliVersionCache(): void { cachedVersion = null; cachedAt = 0; remoteRefreshInFlight = null; - remoteRefreshScheduled = false; } /** Exposed for testing: inject fetch + cache dir. */ diff --git a/open-sse/utils/diagnostics.ts b/open-sse/utils/diagnostics.ts index 2c0f581607e8..c9d8136e5f2e 100644 --- a/open-sse/utils/diagnostics.ts +++ b/open-sse/utils/diagnostics.ts @@ -11,7 +11,10 @@ import { sanitizeErrorMessage } from "./error.ts"; import { classifyFakeSuccessBody } from "../services/errorClassifier.ts"; -import { SYNTHETIC_RESPONSES_SEQUENCE_NUMBER } from "./responsesSequence.ts"; +import { + buildSyntheticResponsesFailureId, + SYNTHETIC_RESPONSES_SEQUENCE_NUMBER, +} from "./responsesSequence.ts"; // ── Types ──────────────────────────────────────────────────────────────────── @@ -150,9 +153,10 @@ export function synthResponsesFailure(reason?: MalformedReason): string { // #14330: this frame is synthesized outside the real per-stream sequence // counter, so it uses the shared synthetic seed instead of omitting the // required field — a strict Responses decoder aborts without it. + // #15202: `response.id` must be a string; `null` aborts those same decoders. sequence_number: SYNTHETIC_RESPONSES_SEQUENCE_NUMBER, response: { - id: null, + id: buildSyntheticResponsesFailureId(), status: "failed", error: { type: "stream_error", diff --git a/open-sse/utils/fetchStartTimeoutPolicy.ts b/open-sse/utils/fetchStartTimeoutPolicy.ts index 3ebe79f03097..8566004e0323 100644 --- a/open-sse/utils/fetchStartTimeoutPolicy.ts +++ b/open-sse/utils/fetchStartTimeoutPolicy.ts @@ -20,6 +20,7 @@ export type FetchStartTimeoutPolicyInput = { /** Only streaming requests are capped — non-streaming keeps the flat default. */ stream?: boolean | null; capMs?: number; + env?: Record; }; export type FetchStartTimeoutPolicyResult = { @@ -34,6 +35,24 @@ export type FetchStartTimeoutPolicyResult = { // own headers-phase watchdog always fires before the client gives up on its own. export const CODEX_CLIENT_ABORT_MS = 120_000; export const DEFAULT_FETCH_START_TIMEOUT_CAP_MS = 110_000; +const FETCH_START_TIMEOUT_CAP_ENV = "OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS"; +let invalidFetchStartTimeoutCapWarned = false; + +function resolveConfiguredFetchStartTimeoutCapMs(env: Record): number { + const raw = env[FETCH_START_TIMEOUT_CAP_ENV]; + if (raw == null || raw.trim() === "") return DEFAULT_FETCH_START_TIMEOUT_CAP_MS; + + const parsed = Number(raw); + if (Number.isFinite(parsed) && parsed >= 0) return Math.floor(parsed); + + if (!invalidFetchStartTimeoutCapWarned) { + console.warn( + `[TIMEOUT] Invalid ${FETCH_START_TIMEOUT_CAP_ENV}="${raw}". Using default ${DEFAULT_FETCH_START_TIMEOUT_CAP_MS}.` + ); + invalidFetchStartTimeoutCapWarned = true; + } + return DEFAULT_FETCH_START_TIMEOUT_CAP_MS; +} export function resolveFetchStartTimeout( input: FetchStartTimeoutPolicyInput @@ -43,7 +62,9 @@ export function resolveFetchStartTimeout( return { timeoutMs: baseTimeoutMs, baseTimeoutMs, capped: false }; } - const capMs = Math.max(0, Math.floor(input.capMs ?? DEFAULT_FETCH_START_TIMEOUT_CAP_MS)); + const configuredCapMs = + input.capMs ?? resolveConfiguredFetchStartTimeoutCapMs(input.env ?? process.env); + const capMs = Math.max(0, Math.floor(configuredCapMs)); if (capMs <= 0 || baseTimeoutMs <= capMs) { return { timeoutMs: baseTimeoutMs, baseTimeoutMs, capped: false }; } diff --git a/open-sse/utils/opencodeHeaders.ts b/open-sse/utils/opencodeHeaders.ts index 3d36e4e40d9d..bccf375f9fe2 100644 --- a/open-sse/utils/opencodeHeaders.ts +++ b/open-sse/utils/opencodeHeaders.ts @@ -32,6 +32,17 @@ export function satisfiesOpencodeUserAgentContract(userAgent: string | null | un return major > 1 || (major === 1 && minor >= MINIMUM_USER_AGENT_MINOR); } +// SDK and HTTP-library defaults: never an agent's own identity, so they are always +// replaced. Bounded alternation, anchored at the start — no backtracking risk. +const GENERIC_CLIENT_USER_AGENT_RE = + /^(?:curl|wget|libcurl|python-requests|python-httpx|python-urllib|aiohttp|httpie|node-fetch|node|undici|axios|got|ky|bun|deno|go-http-client|okhttp|java|apache-httpclient|postmanruntime|insomnia|openai|anthropic|async ?openai|openai-python)(?:[/\s]|$)/i; + +/** Whether a User-Agent is a generic SDK or HTTP-library default rather than an agent's own. */ +export function isGenericClientUserAgent(userAgent: string | null | undefined): boolean { + const value = String(userAgent || "").trim(); + return !value || GENERIC_CLIENT_USER_AGENT_RE.test(value); +} + /** * The session id the caller supplied, if any. * @@ -153,6 +164,9 @@ function findHeader(headers: Record, name: string): string | und * that is not already the OpenCode CLI (e.g. curl/8.5.0) is REPLACED with the * synthesized CLI UA, because opencode.ai's free tier rejects generic client UAs * from datacenter IPs with FreeUsageLimitError 429. (#5997, follow-up #10229) + * @param options.keepAgentUserAgent - OpenCode Go (#15311): keep a client User-Agent that + * names the agent itself, as Go's client requirements ask. A generic SDK / HTTP-library + * UA is still replaced, and a missing one is still filled. * @param options.sessionBody - Request body fields used to generate a * conversation-stable session fingerprint (model, system, messages or input, tools). * When provided, x-opencode-session is a deterministic hash instead of a random @@ -164,6 +178,7 @@ export function forwardOpencodeClientHeaders( options?: { synthesizeRequestId?: boolean; cliDefaults?: { userAgent: string; client: string; project: string }; + keepAgentUserAgent?: boolean; sessionBody?: OpencodeSessionBody; } ): void { @@ -197,7 +212,12 @@ export function forwardOpencodeClientHeaders( // 4. OpencodeExecutor-only: synthesize the OpenCode CLI identity Cloudflare expects // on VPS egress, for any key the client did not supply (#5997). if (options?.cliDefaults) { - applyCliDefaults(headers, options.cliDefaults, options.sessionBody); + applyCliDefaults( + headers, + options.cliDefaults, + options.sessionBody, + options.keepAgentUserAgent === true + ); } } @@ -225,16 +245,25 @@ function applySessionFallback( * like the OpenCode CLI (opencode-cli/...) is preserved so the real CLI's versioned * identity stays intact. (#5997, follow-up) */ +/** + * Whether the client's User-Agent survives CLI synthesis. A UA that satisfies the upstream + * contract is always kept; the previous rule kept anything starting with `opencode-cli/`, + * which carries no parsable version and is refused by the free tier. With + * `keepAgentUserAgent` (OpenCode Go, #15311) an agent's own UA is kept too, because Go asks + * third-party agents to identify themselves — but never a generic SDK / HTTP-library UA. + */ +function keepsClientUserAgent(userAgent: string | undefined, keepAgentUserAgent: boolean) { + if (satisfiesOpencodeUserAgentContract(userAgent)) return true; + return keepAgentUserAgent && !isGenericClientUserAgent(userAgent); +} + function applyCliDefaults( headers: Record, cliDefaults: { userAgent: string; client: string; project: string }, - sessionBody?: OpencodeSessionBody + sessionBody?: OpencodeSessionBody, + keepAgentUserAgent = false ): void { - // A client User-Agent is kept only when it already satisfies the upstream contract. - // The previous rule kept anything starting with `opencode-cli/`, which carries no - // parsable version and is refused by the free tier. - const existingUa = headers["User-Agent"] || headers["user-agent"]; - if (!satisfiesOpencodeUserAgentContract(existingUa)) { + if (!keepsClientUserAgent(headers["User-Agent"] || headers["user-agent"], keepAgentUserAgent)) { setUserAgentHeader(headers, cliDefaults.userAgent); } headers["x-opencode-client"] ||= cliDefaults.client; diff --git a/open-sse/utils/proxyDispatcher.ts b/open-sse/utils/proxyDispatcher.ts index 5848a90b00a4..1079bfd2a42b 100644 --- a/open-sse/utils/proxyDispatcher.ts +++ b/open-sse/utils/proxyDispatcher.ts @@ -65,13 +65,25 @@ type ProxyConfigObject = { family?: string; }; -function getDispatcherOptions(hostname?: string) { - const timeouts = getUpstreamTimeoutConfig(process.env, (message) => { +export function isUpstreamHttp2Enabled( + env: Record = process.env +): boolean { + const raw = (env.OMNIROUTE_UPSTREAM_HTTP2_ENABLED ?? "").trim().toLowerCase(); + return !["false", "0", "no", "off"].includes(raw); +} + +function getDispatcherOptions( + hostname?: string, + env: Record = process.env +) { + const allowH2 = isUpstreamHttp2Enabled(env); + const timeouts = getUpstreamTimeoutConfig(env, (message) => { console.warn(`[ProxyDispatcher] ${message}`); }); const localEgress = isLocalEgressHostname(hostname); return { + allowH2, headersTimeout: timeouts.fetchHeadersTimeoutMs, bodyTimeout: timeouts.fetchBodyTimeoutMs, connectTimeout: timeouts.fetchConnectTimeoutMs, @@ -101,6 +113,7 @@ function getDispatcherOptions(hostname?: string) { // IPv6 route to host.docker.internal is dead inside the container (verified // 2026-09-21) and the default 1 s wait is pure latency on every healthy request. connect: { + allowH2, autoSelectFamily: true, autoSelectFamilyAttemptTimeout: localEgress ? LOCAL_AUTO_SELECT_FAMILY_ATTEMPT_TIMEOUT_MS @@ -127,7 +140,7 @@ export function getProxyDispatcherConnectionLimit( } function getProxyDispatcherOptions(env: Record = process.env) { - const options = getDispatcherOptions(); + const options = getDispatcherOptions(undefined, env); // #9100: restore keep-alive on the proxy path. The previous hard-coded // keepAliveTimeout: 1 (1ms) destroyed the pooled socket right after every // response, forcing a fresh TCP+TLS+CONNECT handshake per request. Proxies @@ -145,6 +158,9 @@ function getProxyDispatcherOptions(env: Record = pro // connection instead of each opening its own socket (#4163 regression). return { ...options, + // ProxyAgent ignores connect and builds both TLS hops separately (#15313). + requestTls: { allowH2: options.allowH2 }, + proxyTls: { allowH2: options.allowH2 }, connections: getProxyDispatcherConnectionLimit(env), keepAliveTimeout: Math.max(options.keepAliveTimeout, 30_000), keepAliveMaxTimeout: Math.max(options.keepAliveMaxTimeout, 60_000), @@ -170,7 +186,7 @@ export function getDefaultDispatcherConnectionLimit( } function getDefaultDispatcherOptions(env: Record = process.env) { - const options = getDispatcherOptions(); + const options = getDispatcherOptions(undefined, env); // #4580 — On the direct egress path, undici's default pipelining (1) let a long // SSE stream monopolize the single pooled socket per origin. Keep the public // connection-limit option here, but getDefaultDispatcher() fans it out across @@ -566,7 +582,13 @@ function buildProxyDispatcher( ...options, ...(proxyAuthorization ? { token: proxyAuthorization } : {}), ...(family !== null - ? { proxyTls: { family, autoSelectFamily: false } as ProxyAgent.Options["proxyTls"] } + ? { + proxyTls: { + ...options.proxyTls, + family, + autoSelectFamily: false, + } as ProxyAgent.Options["proxyTls"], + } : {}), }); } diff --git a/open-sse/utils/proxyFetch.ts b/open-sse/utils/proxyFetch.ts index 35a950818e6a..075d8a225700 100644 --- a/open-sse/utils/proxyFetch.ts +++ b/open-sse/utils/proxyFetch.ts @@ -11,6 +11,7 @@ import { getRetryDispatcher, isLocalEgressHostname, isRelayType, + isUpstreamHttp2Enabled, normalizeProxyUrl, proxyConfigToUrl, proxyUrlForLogs, @@ -43,14 +44,14 @@ import { // pipelines POST (SSE is POST), so a single socket would serialize every // concurrent stream; 4 sockets give 4 parallel streams. h2 relays are // unaffected — streams multiplex over one socket, so the pool stays at a single -// connection while streams drain. `allowH2: true` keeps that h2 fast path for -// Vercel / Deno / Cloudflare. +// connection while streams drain. HTTP/2 stays enabled by default for +// Vercel / Deno / Cloudflare; operators can opt out when needed. const RELAY_POOL_AGENT_OPTIONS = { keepAliveTimeout: 30_000, keepAliveMaxTimeout: 60_000, pipelining: 4, connections: 4, - allowH2: true, + allowH2: isUpstreamHttp2Enabled(), } as const; const RELAY_POOL_AGENT = new Agent(RELAY_POOL_AGENT_OPTIONS); @@ -62,7 +63,7 @@ const RELAY_RETRY_AGENT = new Agent({ keepAliveMaxTimeout: 1, pipelining: 0, connections: 1, - allowH2: true, + allowH2: isUpstreamHttp2Enabled(), }); // A hung relay must fail BEFORE the client/agent timeout (typically 30s) so the @@ -118,7 +119,6 @@ const TLS_PROVIDER_PROFILE: Record = { type TlsProfileResult = { browserProfile?: string; os?: string }; function tlsProfileForProvider(provider: string | null | undefined): TlsProfileResult { - if (!provider) return {}; const p = TLS_PROVIDER_PROFILE[provider.trim().toLowerCase()]; return p ? { browserProfile: p.browser, os: p.os } : {}; @@ -379,6 +379,8 @@ const TLS_ALLOWED_OPTION_KEYS: Record = { method: true, redirect: true, signal: true, + // Next.js cache/revalidation metadata. It is not forwarded to wreq. + next: true, }; function isWreqBodySupported(body: unknown): boolean { diff --git a/open-sse/utils/proxyRefusalMemory.ts b/open-sse/utils/proxyRefusalMemory.ts index 0b26de765c49..90a25239c13d 100644 --- a/open-sse/utils/proxyRefusalMemory.ts +++ b/open-sse/utils/proxyRefusalMemory.ts @@ -10,7 +10,14 @@ * (callers gate writes and decisions on it) and it stays free of the proxy dispatcher, so * the DB layer can consult it without loading undici or the SOCKS connector. */ -import { notifyProxyTransition } from "./proxyTransitionListeners.ts"; +import { notifyProxyTransition, getSharedRefusalStore } from "./proxyTransitionListeners.ts"; +import type { + RefusalState, + SharedRefusalStore, + SlowOverrun, + TransportFailure, + TransportSuccess, +} from "./proxyTransitionListeners.ts"; import { stripIpv6Brackets } from "./proxyFamily.ts"; // Field trends show a refused egress rarely recovers within minutes, so the @@ -60,6 +67,7 @@ export const REFUSAL_POLICIES: { ip_quota_429: RefusalPolicy; transport: { baseMs: 60_000; maxMs: 600_000 }; slow: { baseMs: 60_000; maxMs: 600_000 }; + geo_blocked: { baseMs: 60_000; maxMs: 600_000 }; } = { /** The TCP probe could not open a connection to the proxy. */ proxy_unreachable: { baseMs: 60_000, maxMs: 600_000 }, @@ -77,12 +85,16 @@ export const REFUSAL_POLICIES: { * a refused probe, kept apart from the quota curve. */ slow: { baseMs: 60_000, maxMs: 600_000 }, + /** The provider refused this region through this member; short set-aside. */ + geo_blocked: { baseMs: 60_000, maxMs: 600_000 }, }; export type ProxyRefusalKind = keyof typeof REFUSAL_POLICIES; -// `seq` orders set-aside events so a cache can tell whether it already saw this one. -type RefusalState = { streak: number; until: number; seq: number }; +// Shared across duplicated server module copies (see getSharedRefusalStore): +// rebind on each module evaluation so HMR keeps the same object. +const store: SharedRefusalStore = getSharedRefusalStore(); +const memory: Map = store.memory; const MAX_ENTRIES = 1000; const REFUSAL_KINDS = Object.keys(REFUSAL_POLICIES) as ProxyRefusalKind[]; @@ -91,8 +103,54 @@ const DEFAULT_PORTS: Record = { http: "8080", https: "443", sock const RELAY_TYPES = new Set(["vercel", "deno", "cloudflare"]); const FAMILY_MARKER = /\?family=(ipv4|ipv6)$/; -const memory = new Map(); -let refusalSeq = 0; +/** + * Credential-free label for one egress key (`scheme://user@host:port` as + * proxyEgressKey writes it): `scheme://host:port`. String surgery only, no + * `new URL` (empty userinfo and bare IPv6 break URL parsing). Never receives + * anything but egress keys, which never carry passwords or observed addresses. + */ +export function describeEgressForLog(key: string): string { + const bare = key.replace(FAMILY_MARKER, ""); + const schemeEnd = bare.indexOf("://"); + const scheme = (schemeEnd === -1 ? "http" : bare.slice(0, schemeEnd)).toLowerCase(); + const rest = schemeEnd === -1 ? bare : bare.slice(schemeEnd + 3); + const at = rest.lastIndexOf("@"); + const hostPort = at === -1 ? rest : rest.slice(at + 1); + const slash = hostPort.indexOf("/"); + const authority = slash === -1 ? hostPort : hostPort.slice(0, slash); + const colon = authority.lastIndexOf(":"); + const rawHost = colon === -1 ? authority : authority.slice(0, colon); + const port = colon === -1 ? "" : authority.slice(colon + 1); + const host = stripIpv6Brackets(rawHost).toLowerCase(); + const bracketed = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; + return port ? `${scheme}://${bracketed}:${port}` : `${scheme}://${bracketed}`; +} + +// Instance proof: fixed at module load so two readers seeing different values +// spot a duplicated store at a glance. Random hex, not the pid: two copies in +// one process share the pid but not this value. +const storeInstanceId = Math.floor(Math.random() * 0xffffffff) + .toString(16) + .padStart(8, "0"); + +/** Opaque id of this store instance, set once at module load. */ +export function getRefusalStoreInstance(): string { + return storeInstanceId; +} + +// Duplicate-load alarm: counts module loads on shared globalThis keys (plain +// Symbol.for scalars, independent of any shared-store object) and warns once. +const LOADS_KEY = Symbol.for("omniroute.proxyRefusalMemory.loads"); +const WARNED_KEY = Symbol.for("omniroute.proxyRefusalMemory.warned"); +{ + const holder = globalThis as unknown as { [key: symbol]: unknown }; + const loads = typeof holder[LOADS_KEY] === "number" ? (holder[LOADS_KEY] as number) : 0; + holder[LOADS_KEY] = loads + 1; + if (loads + 1 > 1 && !holder[WARNED_KEY]) { + holder[WARNED_KEY] = true; + console.warn("[ProxyRefusalMemory] module loaded more than once (duplicated bundle copy)"); + } +} const textField = (value: unknown): string => (typeof value === "string" ? value : ""); @@ -201,7 +259,7 @@ function insertState(key: string, kind: ProxyRefusalKind, nowMs: number): number const periodMs = Math.min(policy.baseMs * 2 ** (streak - 1), policy.maxMs); const id = entryId(key, kind); memory.delete(id); - memory.set(id, { streak, until: nowMs + periodMs, seq: ++refusalSeq }); + memory.set(id, { streak, until: nowMs + periodMs, seq: ++store.seq.value }); if (memory.size > MAX_ENTRIES) { const oldest = memory.keys().next().value; if (oldest !== undefined) memory.delete(oldest); @@ -227,9 +285,14 @@ export function noteProxyRefusal( nowMs: number = Date.now() ): number | null { if (key === null) return null; + const before = readState(key, kind, nowMs); const periodMs = insertState(key, kind, nowMs); if (periodMs < 0) return null; + const streak = (before?.streak ?? 0) + 1; notifyProxyTransition({ key, kind, periodMs, until: nowMs + periodMs }); + console.warn( + `[ProxyRefusalMemory] set aside ${describeEgressForLog(key)} kind=${kind} periodMs=${periodMs} streak=${streak}` + ); return periodMs; } @@ -247,8 +310,13 @@ export function noteProxyMemberRefusal( nowMs: number = Date.now() ): number | null { if (entryKey === null || member === null) return null; + const before = readMemberState(entryKey, member, kind, nowMs); const periodMs = insertState(keyForEntryMember(entryKey, member), kind, nowMs); - return periodMs < 0 ? null : periodMs; + if (periodMs < 0) return null; + console.warn( + `[ProxyRefusalMemory] set aside ${describeEgressForLog(entryKey)} member=${member} kind=${kind} periodMs=${periodMs} streak=${(before?.streak ?? 0) + 1}` + ); + return periodMs; } /** The proxy answered again: end its period now, keep the streak so a repeat doubles. */ @@ -259,7 +327,14 @@ export function noteProxyRecovered( ): void { if (key === null) return; const state = readState(key, kind, nowMs); - if (state && state.until > nowMs) state.until = nowMs; + if (!state || state.until <= nowMs) return; + const policy = REFUSAL_POLICIES[kind]; + const periodMs = Math.min(policy.baseMs * 2 ** (state.streak - 1), policy.maxMs); + const setAsideAt = state.until - periodMs; + console.warn( + `[ProxyRefusalMemory] recovered ${describeEgressForLog(key)} kind=${kind} setAsideMs=${nowMs - setAsideAt} streak=${state.streak}` + ); + state.until = nowMs; } /** A response came back through this proxy: forget every refusal kind for it. */ @@ -391,7 +466,7 @@ export function listEntryMembers(entryKey: string | null): string[] { /** Sequence number of the last set-aside event recorded in this process (0 = none yet). */ export function getProxyRefusalSeq(): number { - return refusalSeq; + return store.seq.value; } /** @@ -488,11 +563,8 @@ export const TRANSPORT_EVIDENCE_WINDOW_MS = 300_000; export const TRANSPORT_EVIDENCE_THRESHOLD = 3; const MAX_TRANSPORT_EVIDENCE = 1000; -type TransportFailure = { key: string; destination: string; at: number }; -type TransportSuccess = { destination: string; key: string; at: number }; - -const transportFailures: TransportFailure[] = []; -const transportSuccesses: TransportSuccess[] = []; +const transportFailures: TransportFailure[] = store.transportFailures; +const transportSuccesses: TransportSuccess[] = store.transportSuccesses; // Lazy purge mirrors readState: entries older than the evidence window plus // twice the transport cap can no longer contribute, so drop them on read. @@ -563,6 +635,38 @@ export function __resetTransportEvidenceForTesting(): void { transportSuccesses.length = 0; } +/** + * Read-only transport proof for one entry key: tagged failures through this + * egress plus successes to the same destinations through a different egress, + * both inside TRANSPORT_EVIDENCE_WINDOW_MS. Runs the existing lazy purge + * first (bounded and pre-existing, never a refusal write) so counts never + * grow stale. + */ +export function countTransportEvidenceFor( + key: string | null, + nowMs: number = Date.now() +): { failures: number; crossSuccesses: number } { + if (key === null || (transportFailures.length === 0 && transportSuccesses.length === 0)) { + return { failures: 0, crossSuccesses: 0 }; + } + purgeTransportEvidence(nowMs); + const from = nowMs - TRANSPORT_EVIDENCE_WINDOW_MS; + let failures = 0; + let destinations: string[] | null = null; + for (const f of transportFailures) { + if (f.key === key && f.at >= from) { + failures++; + (destinations ??= []).push(f.destination); + } + } + if (destinations === null) return { failures: 0, crossSuccesses: 0 }; + let crossSuccesses = 0; + for (const s of transportSuccesses) { + if (s.key !== key && s.at >= from && destinations.includes(s.destination)) crossSuccesses++; + } + return { failures, crossSuccesses }; +} + /** Test-only: current evidence store sizes. */ export function __transportEvidenceSizeForTesting(): { failures: number; successes: number } { return { failures: transportFailures.length, successes: transportSuccesses.length }; @@ -577,9 +681,7 @@ export const SLOW_OVERRUN_WINDOW_MS = 300_000; export const SLOW_OVERRUN_THRESHOLD = 3; const MAX_SLOW_OVERRUNS = 1000; -type SlowOverrun = { key: string; at: number }; - -const slowOverruns: SlowOverrun[] = []; +const slowOverruns: SlowOverrun[] = store.slowOverruns; // Lazy purge mirrors readState: entries older than the evidence window plus // twice the slow cap can no longer contribute, so drop them on record. diff --git a/open-sse/utils/proxyTransitionListeners.ts b/open-sse/utils/proxyTransitionListeners.ts index 6c79788e6ab0..7add2068de35 100644 --- a/open-sse/utils/proxyTransitionListeners.ts +++ b/open-sse/utils/proxyTransitionListeners.ts @@ -7,7 +7,8 @@ * in this file so the store stays pure and cycle-free. */ -export type ProxyTransitionKind = "ip_quota_429" | "proxy_unreachable" | "transport" | "slow"; +export type ProxyTransitionKind = + "ip_quota_429" | "proxy_unreachable" | "transport" | "slow" | "geo_blocked"; export interface ProxyTransition { key: string; @@ -18,17 +19,72 @@ export interface ProxyTransition { export type ProxyTransitionListener = (transition: ProxyTransition) => void; -const listeners = new Set(); +export interface SharedRefusalStore { + memory: Map; + seq: { value: number }; + transportFailures: TransportFailure[]; + transportSuccesses: TransportSuccess[]; + slowOverruns: SlowOverrun[]; + listeners: Set; + listenerKeys: Map; +} + +export type RefusalState = { streak: number; until: number; seq: number }; +export type TransportFailure = { key: string; destination: string; at: number }; +export type TransportSuccess = { destination: string; key: string; at: number }; +export type SlowOverrun = { key: string; at: number }; + +const SHARED_STORE_KEY = Symbol.for("omniroute.proxyRefusalMemory"); + +/** + * Process-wide refusal store, shared across duplicated server module copies. + * Lazy `??=` init mirrors getPatchState in proxyFetch.ts: data only, no + * closures, so re-evaluation (HMR) rebinds the same object. + */ +export function getSharedRefusalStore(): SharedRefusalStore { + const holder = globalThis as unknown as Record; + let store = holder[SHARED_STORE_KEY]; + if (!store) { + store = { + memory: new Map(), + seq: { value: 0 }, + transportFailures: [], + transportSuccesses: [], + slowOverruns: [], + listeners: new Set(), + listenerKeys: new Map(), + }; + holder[SHARED_STORE_KEY] = store; + } + return store; +} -export function onProxyTransition(listener: ProxyTransitionListener): () => void { - listeners.add(listener); +export function onProxyTransition(listener: ProxyTransitionListener, key?: string): () => void { + const store = getSharedRefusalStore(); + if (key !== undefined) { + const existing = store.listenerKeys.get(key); + if (existing !== undefined) { + const current = existing; + return () => { + store.listeners.delete(current); + if (store.listenerKeys.get(key) === current) store.listenerKeys.delete(key); + }; + } + store.listeners.add(listener); + store.listenerKeys.set(key, listener); + return () => { + store.listeners.delete(listener); + if (store.listenerKeys.get(key) === listener) store.listenerKeys.delete(key); + }; + } + store.listeners.add(listener); return () => { - listeners.delete(listener); + store.listeners.delete(listener); }; } export function notifyProxyTransition(transition: ProxyTransition): void { - for (const listener of listeners) { + for (const listener of getSharedRefusalStore().listeners) { try { listener(transition); } catch (err) { @@ -39,10 +95,12 @@ export function notifyProxyTransition(transition: ProxyTransition): void { /** Test-only: number of registered listeners. */ export function __listenerCountForTesting(): number { - return listeners.size; + return getSharedRefusalStore().listeners.size; } /** Test-only: forget all listeners. */ export function __resetProxyTransitionListenersForTesting(): void { - listeners.clear(); + const store = getSharedRefusalStore(); + store.listeners.clear(); + store.listenerKeys.clear(); } diff --git a/open-sse/utils/responsesSequence.ts b/open-sse/utils/responsesSequence.ts index 2d7fb916433a..43e1056ad7ad 100644 --- a/open-sse/utils/responsesSequence.ts +++ b/open-sse/utils/responsesSequence.ts @@ -25,3 +25,27 @@ export function buildSyntheticResponsesFailedEvent( response, }; } + +/** + * Synthetic `response.id` for the keepalive `response.in_progress` frame (#15202). + * + * The value is constant on purpose: the frame is emitted once as the very first + * stream byte, before the upstream has produced a real response id. Being a + * string (never `null`) matters because strict Responses decoders validate + * `response.id` as `string | undefined` and abort the stream on the first frame. + */ +export const SYNTHETIC_RESPONSES_KEEPALIVE_ID = "resp_keepalive_omniroute"; + +/** + * Build a fresh string id for a synthesized `response.failed` frame (#15202). + * + * The `resp_error_*` prefix mirrors the precedent in streamErrorFormat.ts. A + * fresh id per frame keeps two failures in the same request from colliding. + */ +export function buildSyntheticResponsesFailureId(): string { + const unique = + typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" + ? crypto.randomUUID() + : `${Date.now()}-${Math.random().toString(36).slice(2)}`; + return `resp_error_${unique}`; +} diff --git a/open-sse/utils/socksConnectorWithFamily.ts b/open-sse/utils/socksConnectorWithFamily.ts index 5e9fb0c4fa10..64d17ee6168e 100644 --- a/open-sse/utils/socksConnectorWithFamily.ts +++ b/open-sse/utils/socksConnectorWithFamily.ts @@ -56,8 +56,11 @@ export function socksConnectorWithFamily( // Sequential budget: both phases bounded by the same connectTimeout → wall-time up to 60s for https // (vs 30s direct). Shared-deadline alternative rejected as unjustified complexity. const build = _buildConnectorForTest ?? buildConnector; + // This custom connector bypasses Agent.allowH2, and buildConnector defaults allowH2 to true, + // so ALPN still negotiated h2 over SOCKS. Pin the TLS hop to HTTP/1.1 as well. + const h1Opts = { ...tlsOpts, allowH2: false }; const undiciConnect = build( - tlsTimeout !== undefined ? { ...tlsOpts, timeout: tlsTimeout } : tlsOpts + tlsTimeout !== undefined ? { ...h1Opts, timeout: tlsTimeout } : h1Opts ); const socketOptions = buildSocksFamilySocketOptions(family); return async (options, callback) => { @@ -99,6 +102,9 @@ export function createSocksDispatcherWithFamily( }; return new Agent({ ...rest, + // Undici 8 negotiates HTTP/2 by default; its h2 client over SOCKS reset streams + // (ERR_HTTP2_STREAM_ERROR) and emitted listener-less stream errors that crashed the process. + allowH2: false, connect: socksConnectorWithFamily(proxy, family, connect, connectTimeout), }); } diff --git a/open-sse/utils/sseHeartbeat.ts b/open-sse/utils/sseHeartbeat.ts index 2763ba0e5e83..cd1b8e7759a0 100644 --- a/open-sse/utils/sseHeartbeat.ts +++ b/open-sse/utils/sseHeartbeat.ts @@ -5,16 +5,21 @@ * @changes * - [2026-07-28] [Cursor Grok 4.5] - Brand-neutral default OpenAI keepalive id/model */ -import { SYNTHETIC_RESPONSES_SEQUENCE_NUMBER } from "./responsesSequence.ts"; +import { + SYNTHETIC_RESPONSES_KEEPALIVE_ID, + SYNTHETIC_RESPONSES_SEQUENCE_NUMBER, +} from "./responsesSequence.ts"; const HEARTBEAT_ENCODER = new TextEncoder(); -// #14330: a bare {"type":"response.in_progress"} frame has no `sequence_number` or -// `response` object, so a strict Responses decoder (openai-python, Codex/OpenCode/Grok -// CLIs) aborts on it. Every typed Responses event requires both fields. +// #14330 / #15202: a bare {"type":"response.in_progress"} frame has no +// `sequence_number` or `response` object, so a strict Responses decoder +// (openai-python, Codex/OpenCode/Grok CLIs) aborts on it. Every typed Responses +// event requires both fields, and `response.id` must be a string — never `null`, +// which the 3.8.51 fix left in place and broke the same decoders it targeted. const OPENAI_RESPONSES_IN_PROGRESS_PAYLOAD = `data: ${JSON.stringify({ type: "response.in_progress", sequence_number: SYNTHETIC_RESPONSES_SEQUENCE_NUMBER, - response: { id: null, status: "in_progress" }, + response: { id: SYNTHETIC_RESPONSES_KEEPALIVE_ID, status: "in_progress" }, })}\n\n`; export const DEFAULT_SSE_HEARTBEAT_INTERVAL_MS = 15_000; diff --git a/open-sse/utils/stream.ts b/open-sse/utils/stream.ts index 76a4b2772c31..11d058e7208d 100644 --- a/open-sse/utils/stream.ts +++ b/open-sse/utils/stream.ts @@ -464,19 +464,21 @@ type ClaudeEmptyResponseLifecycle = { hasMessageDelta: boolean; hasMessageStop: boolean; hasError: boolean; + stopReason: string | null; syntheticContentInjected: boolean; warningLogged: boolean; }; const SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT = ""; -function createClaudeEmptyResponseLifecycle(): ClaudeEmptyResponseLifecycle { +export function createClaudeEmptyResponseLifecycle(): ClaudeEmptyResponseLifecycle { return { hasMessageStart: false, hasContentBlock: false, hasMessageDelta: false, hasMessageStop: false, hasError: false, + stopReason: null, syntheticContentInjected: false, warningLogged: false, }; @@ -492,7 +494,7 @@ function isClaudeEventPayload(payload: unknown): boolean { return getClaudeEventType(payload) !== null; } -function updateClaudeEmptyResponseLifecycle( +export function updateClaudeEmptyResponseLifecycle( lifecycle: ClaudeEmptyResponseLifecycle, payload: unknown ) { @@ -510,6 +512,12 @@ function updateClaudeEmptyResponseLifecycle( break; case "message_delta": lifecycle.hasMessageDelta = true; + { + const delta = (payload as JsonRecord).delta; + const reason = + delta && typeof delta === "object" ? (delta as JsonRecord).stop_reason : null; + if (typeof reason === "string" && reason) lifecycle.stopReason = reason; + } break; case "message_stop": lifecycle.hasMessageStop = true; @@ -904,9 +912,15 @@ export function createSSEStream(options: StreamOptions = {}) { // both translate mode (openai-responses → claude/openai) and Responses passthrough. let lastSeenResponsesSequenceNumber = -1; const isDuplicateResponsesSequence = (value: unknown): boolean => { - if (typeof value !== "number" || !Number.isFinite(value)) return false; - if (value <= lastSeenResponsesSequenceNumber) return true; - lastSeenResponsesSequenceNumber = value; + const numeric = + typeof value === "number" + ? value + : typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value) + ? Number(value) + : NaN; + if (!Number.isSafeInteger(numeric)) return false; + if (numeric <= lastSeenResponsesSequenceNumber) return true; + lastSeenResponsesSequenceNumber = numeric; return false; }; const streamStartedAt = Date.now(); @@ -1787,18 +1801,18 @@ export function createSSEStream(options: StreamOptions = {}) { parsed.response.output ); } - // #7936 — restore `namespace` + `name` fields on passthrough - // Responses function_call items for downstream Codex clients. - if ( - parsed.type === "response.output_item.added" || - parsed.type === "response.output_item.done" || - parsed.type === "response.completed" - ) { + // #7936 - restore `namespace` + `name` on passthrough Responses + // function_call items. The restoration mutates `parsed`, so a + // real change must be re-serialized; otherwise the client still + // receives the flattened wire name. + const responsesIdentityRestored = + (parsed.type === "response.output_item.added" || + parsed.type === "response.output_item.done" || + parsed.type === "response.completed") && restoreResponsesPassthroughFunctionCallIdentity( parsed as JsonRecord, requestToolIdentityMap ); - } if ( parsed.type === "response.completed" && passthroughResponsesPendingFunctionCalls.size > 0 @@ -1843,7 +1857,8 @@ export function createSSEStream(options: StreamOptions = {}) { textualToolCallBackfilled || responsesIdsNormalized || usageNormalized || - responsesCommentaryStrippedFromCompleted + responsesCommentaryStrippedFromCompleted || + responsesIdentityRestored ) { output = `data: ${JSON.stringify(parsed)}\n\n`; injectedUsage = true; @@ -2975,7 +2990,7 @@ export function createSSEStream(options: StreamOptions = {}) { // Flush pending translation events BEFORE erroring the stream. // This lets the openai-responses translator emit a proper - // `response.completed` with `status: "failed"` and close any + // `response.failed` with `status: "failed"` and close any // open items (reasoning, tool calls, etc.), instead of silently // aborting the stream and leaving partial items dangling. try { @@ -3032,6 +3047,18 @@ export function createSSEStream(options: StreamOptions = {}) { } } + // A translator can discover a missing upstream terminal during flush. + // Record that failure before usage estimation or successful completion. + if (state?.upstreamError) { + const err = state.upstreamError; + const publicErrorMessage = buildErrorBody(err.status, err.message).error.message; + abortStreamFailure(controller, err, publicErrorMessage, { + notifyComplete: true, + preserveQueuedChunks: true, + }); + return; + } + if (sourceFormat === FORMATS.CLAUDE) { if (shouldAbortClaudeStream()) { emitClaudeEmptyStreamErrorAndAbort(controller); diff --git a/open-sse/utils/streamClaudeEmptyBody.ts b/open-sse/utils/streamClaudeEmptyBody.ts index 7daa6876088e..d387972b830a 100644 --- a/open-sse/utils/streamClaudeEmptyBody.ts +++ b/open-sse/utils/streamClaudeEmptyBody.ts @@ -21,13 +21,23 @@ type ClaudeEmptyLifecycleLike = { hasMessageStart: boolean; hasMessageDelta: boolean; hasMessageStop: boolean; + stopReason?: string | null; }; +const CLEAN_EMPTY_STOP_REASONS = new Set(["end_turn", "stop_sequence"]); + export function shouldAbortEmptyClaudeStream( lifecycle: ClaudeEmptyLifecycleLike, sawAnyUpstreamPayload: boolean ): boolean { if (lifecycle.hasError || lifecycle.hasContentBlock) return false; + if ( + sawAnyUpstreamPayload && + lifecycle.hasMessageStop && + CLEAN_EMPTY_STOP_REASONS.has(lifecycle.stopReason || "") + ) { + return false; + } const hasPartialLifecycle = lifecycle.hasMessageStart || lifecycle.hasMessageDelta || lifecycle.hasMessageStop; return hasPartialLifecycle || !sawAnyUpstreamPayload; diff --git a/open-sse/utils/streamFailureBoundary.ts b/open-sse/utils/streamFailureBoundary.ts index bb3da7c850fa..f2c4aef0a95e 100644 --- a/open-sse/utils/streamFailureBoundary.ts +++ b/open-sse/utils/streamFailureBoundary.ts @@ -33,7 +33,7 @@ export function createStreamFailureAborter(context: AborterContext) { controller: TransformStreamDefaultController, failure: StreamFailurePayload, publicMessage: string, - options: { notifyComplete?: boolean } = {} + options: { notifyComplete?: boolean; preserveQueuedChunks?: boolean } = {} ): void => { let handled = false; context.timing.markInterrupted(); @@ -71,6 +71,11 @@ export function createStreamFailureAborter(context: AborterContext) { } context.clearIdleTimer(); if (!handled) context.clearPendingRequest(); - controller.error(context.markPendingRequestCleared(new Error(safeMessage))); + // At EOF the caller has already queued an explicit failure terminal. Let + // flush close normally: controller.error() would discard those queued + // deltas and the failure event before a slow reader can consume them. + if (!options.preserveQueuedChunks) { + controller.error(context.markPendingRequestCleared(new Error(safeMessage))); + } }; } diff --git a/open-sse/utils/streamHandler.ts b/open-sse/utils/streamHandler.ts index cef58650b1a5..7c55d45050fe 100644 --- a/open-sse/utils/streamHandler.ts +++ b/open-sse/utils/streamHandler.ts @@ -6,6 +6,10 @@ import { PENDING_REQUEST_CLEARED_MARKER } from "./stream.ts"; import { createCompletedResponsesToolHandoffWatcher } from "./responsesToolHandoff.ts"; import { createStreamContentWatcher, type StreamContentWatcher } from "./streamReadiness.ts"; import { hasOpenReasoning } from "./emptyTurnRetry.ts"; +import { + buildSyntheticResponsesFailureId, + SYNTHETIC_RESPONSES_SEQUENCE_NUMBER, +} from "./responsesSequence.ts"; // Stream handler with disconnect detection - shared for all providers @@ -472,8 +476,13 @@ export function buildStreamErrorChunks( if (isResponsesClientFormat(clientResponseFormat)) { const errorEvent = { type: "response.failed", + // #15202: synthesized response.failed frames must satisfy the Responses event + // union — a string `response.id` and a numeric `sequence_number`. The 3.8.51 + // fix set both fields on the other synthetic sites but left this one as + // `id: null` with no sequence, so a strict decoder aborts here too. + sequence_number: SYNTHETIC_RESPONSES_SEQUENCE_NUMBER, response: { - id: null, + id: buildSyntheticResponsesFailureId(), status: "failed", error: { message: publicErrorMessage, diff --git a/open-sse/utils/streamReadiness.ts b/open-sse/utils/streamReadiness.ts index 3d2e351ac5db..c8708e86b8af 100644 --- a/open-sse/utils/streamReadiness.ts +++ b/open-sse/utils/streamReadiness.ts @@ -47,6 +47,18 @@ function hasNonEmptyString(value: unknown): boolean { return typeof value === "string" && value.length > 0; } +// A Claude thinking or signature delta is proof the model is working, even +// when its payload carries no readable text (encrypted reasoning, empty +// signature envelope). Presence of a non-empty `thinking` or `signature` +// string on a typed delta object counts as liveness — never as user-visible +// output. Plain `signature: ""` bootstraps stay excluded: only a non-empty +// value passes. +function hasThinkingLiveness(value: Record): boolean { + const deltaType = value.type; + if (deltaType !== "thinking_delta" && deltaType !== "signature_delta") return false; + return hasNonEmptyString(value.thinking) || hasNonEmptyString(value.signature); +} + function hasUsefulValue(value: unknown): boolean { if (hasNonEmptyString(value)) return true; if (Array.isArray(value)) return value.some(hasUsefulValue); @@ -60,6 +72,8 @@ function hasUsefulValue(value: unknown): boolean { // This shape is specific to Responses streams; chat-completion frames do not produce it. if (value.type === "compaction" && hasNonEmptyString(value.encrypted_content)) return true; + if (hasThinkingLiveness(value)) return true; + for (const key of [ "content", "text", diff --git a/open-sse/utils/streamReadinessPolicy.ts b/open-sse/utils/streamReadinessPolicy.ts index 7f9896a2bf68..4e1bff259bec 100644 --- a/open-sse/utils/streamReadinessPolicy.ts +++ b/open-sse/utils/streamReadinessPolicy.ts @@ -68,22 +68,26 @@ function isClaudeFormatReasoningProvider(provider?: string | null): boolean { function isCodexGpt5x(provider?: string | null, model?: string | null): boolean { const normalizedProvider = (provider || "").toLowerCase(); const normalizedModel = (model || "").toLowerCase(); - // Match the gpt-5.x family (gpt-5, gpt-5.1, gpt-5.5, ...) on the codex provider. - return normalizedProvider === "codex" && /gpt-5(\.\d+)?/.test(normalizedModel); + // Match the gpt-5.x family and its successors (gpt-5, gpt-5.5, gpt-6, gpt-6.1, ...) + // on the codex provider. + return normalizedProvider === "codex" && /gpt-[5-9](\.\d+)?/.test(normalizedModel); } +const HIGH_REASONING_EFFORTS = ["high", "xhigh", "max"]; + /** * High-reasoning targets can do a cold, expensive reasoning warm-up even for - * small prompts. Detect "high" or "max" reasoning effort either from - * the model alias suffix (`...-high`) or from the request body's reasoning effort - * field (OpenAI `reasoning_effort` or Responses API `reasoning.effort`). + * small prompts. Detect "high", "xhigh" or "max" reasoning effort either from + * the model alias suffix (`...-high`, `...-xhigh`, `...-max`) or from the request + * body's reasoning effort field (OpenAI `reasoning_effort` or Responses API + * `reasoning.effort`). */ function isHighReasoningEffort( model: string | null | undefined, body: StreamReadinessBody ): boolean { const normalizedModel = (model || "").toLowerCase(); - if (/-high\b/.test(normalizedModel) || normalizedModel.endsWith("-high")) return true; + if (/-(?:x?high|max)\b/.test(normalizedModel)) return true; const effort = (() => { const direct = body?.["reasoning_effort"]; @@ -95,7 +99,7 @@ function isHighReasoningEffort( } return ""; })(); - return ["high", "max"].includes(effort.toLowerCase()); + return HIGH_REASONING_EFFORTS.includes(effort.toLowerCase()); } /** diff --git a/open-sse/vendor/codex-chatgpt-web/chatgpt-session.ts b/open-sse/vendor/codex-chatgpt-web/chatgpt-session.ts index 6df2fe5d8307..3e52e1e8be2a 100644 --- a/open-sse/vendor/codex-chatgpt-web/chatgpt-session.ts +++ b/open-sse/vendor/codex-chatgpt-web/chatgpt-session.ts @@ -7,6 +7,7 @@ export const CHATGPT_COMPOSER_SELECTOR = [ '[data-testid="prompt-textarea"]', "#prompt-textarea", '[contenteditable="true"][data-lexical-editor="true"]', + '[contenteditable="true"][role="textbox"].ProseMirror', ].join(", "); export const CHATGPT_EFFORT_CONTROL_SELECTOR = [ 'button[aria-haspopup="menu"][data-tone="neutral"]', diff --git a/package-lock.json b/package-lock.json index 846e1da20205..d2cd2f43e9d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "material-symbols": "^0.47.0", "mermaid": "^11.17.2", "monaco-editor": "^0.56.0", - "next": "16.3.5", + "next": "16.3.6", "next-intl": "^4.14.0", "next-themes": "^0.4.6", "node-machine-id": "^1.1.12", @@ -133,7 +133,7 @@ "dpdm": "^4.3.0", "esbuild": "0.28.2", "eslint": "^10.10.0", - "eslint-config-next": "16.3.5", + "eslint-config-next": "16.3.6", "eslint-plugin-react-hooks": "7.1.1", "eslint-plugin-sonarjs": "^4.1.0", "espree": "^11.2.0", @@ -4439,9 +4439,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", - "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.5.tgz", + "integrity": "sha512-7VZM+SVdEcUUqSQeNI3zM8Qs/BhQKZndPo2h5VkYkAM8Iz0wJIa8mKV5ekQGqG8UUsnkQ0NMxIxwkIHYvj0qOw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6560,15 +6560,15 @@ "license": "MIT" }, "node_modules/@next/env": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.5.tgz", - "integrity": "sha512-NWEXVDMqoEo0ktmU6u0sE2Vg0LOcsD7NnOTJNo3/fEaTfsg+F1bMIxuDmQbda4e3yTIQwVdUREF2yIuMOusKtg==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.6.tgz", + "integrity": "sha512-x9Vblze1EbtltQYnNH38xCPWU3TVfBd1eXqA3+w9+BTpedkkdNpAaltXlGQ/nsc1+E0mVTNrtcbX3GoO09zeLQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.5.tgz", - "integrity": "sha512-PGfSeItHJ12DH8t+6sEbuMe59NE5rAhCfgk06QKTH2ne9VUL1JlaXdYXY3B8RaiF2SO50rDYvd39TulP6A6xZQ==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.6.tgz", + "integrity": "sha512-jowwDX+7DOlDIjJLgTMxudw+k37QnWu1JkZLkSi9MaJBfDYcfhAPMKBhXL0idYzFN/AGg//axnOR4cLkHX/Rng==", "dev": true, "license": "MIT", "dependencies": { @@ -6577,9 +6577,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.5.tgz", - "integrity": "sha512-pMmGgETfKvElucLHtVaeiMRbp2zUbvKx7b1yGko0liBz3cw1mKSggWN/Rp/wPz8z+E1O82u3r4L1Co+ZS5hokQ==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.6.tgz", + "integrity": "sha512-E/7GEqaUkt8mk/T8v9lAnrhzR06kdq1ZBkC12F8tAMkdIadwNp3H1KqHynDHrpcTlGCUdq/qu6vUL2aYVyYBdw==", "cpu": [ "arm64" ], @@ -6593,9 +6593,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.5.tgz", - "integrity": "sha512-76VaGYvf6HPa5/w12yLkE3dXTn9AfdEviI79oEL3aZoAmRLc9rWitjWqyjViVysK/ht/y9YKzFkBrUdi/wGkow==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.6.tgz", + "integrity": "sha512-yBE893/nDWTlaiBD1p+qgt7NUen4U5R6FXyH0s67Npq1S3E0cVSef1WIXC2xBRgQvwAvJq6DnS6Y6PrY0cy4Ew==", "cpu": [ "x64" ], @@ -6609,9 +6609,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.5.tgz", - "integrity": "sha512-zKDELJ5jSQMHeO/hmXUQsAzagX4bQD4OiMi3pQ5FbUj+yK506oLVHnKA2YXMlbg1EHHqJYtyePOgByIDXD1lqw==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.6.tgz", + "integrity": "sha512-KJDpjBqBPYlvkivmyrp+Qys6k/7ksbqGQvRVc6ZEGfR+cjQxx+nUkJaWmNZJsmoOrqYNbaXByF8wa0lBwDhB3Q==", "cpu": [ "arm64" ], @@ -6628,9 +6628,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.5.tgz", - "integrity": "sha512-7Vql0pgzCoHagv6+FNOZoqmJqA52c6zeVbhtS/47qFozO1MSx4ms7x7GHiciY8R5CDsSMKMQjJEryoJLcsBIbA==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.6.tgz", + "integrity": "sha512-mqNg2K+hvWskSRb/QM+Ix412DvBsuSF0XV+frTSw5vmoucNnIlynFwKYew8D01bfATErMOM7Bujrf0BA5DRKFA==", "cpu": [ "arm64" ], @@ -6647,9 +6647,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.5.tgz", - "integrity": "sha512-NH/xzehyHEFWE2nlcZon7TB/0+H4shfWCi7S1zka815XCOhJDYZhoeJtOYy0dh0WVRWACVXSyGNFFytoMxUhRg==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.6.tgz", + "integrity": "sha512-nFncBNGAYouRHjRVaITs9beZRfhX4ssVwpnvPIAbkZVH6LtGoAVlH4bJ8Cnf9SOo9bsXgPFer/GdHtEE3JNOkw==", "cpu": [ "x64" ], @@ -6666,9 +6666,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.5.tgz", - "integrity": "sha512-lV4+EhWMfS8jcC+EH2nn/Cm5cn6XsgbE07bU9tMH8fCo0tNAqhyzi1b5wQ/Tn6NGFTvKDY65w3ZH95EjwBRAnQ==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.6.tgz", + "integrity": "sha512-5Mf3cHDGR/Iz0ng2Bj3zUR3p5QS9YK3Hn2QiAfavFmyF48zwThAjpFoiTKNIcOHLYS4zEk+gzyJ/9deQ2ZB8yQ==", "cpu": [ "x64" ], @@ -6685,9 +6685,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.5.tgz", - "integrity": "sha512-/wKzAREX2RF++MhicjDbg8tGn2AiBIM0+EFeTFKoUEUbW5D6amCJehd5Z5G1H5/gxNdgnwoXMcHz24H/c2tGkQ==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.6.tgz", + "integrity": "sha512-0jkJy0C2kbrJWTk4YLa3xk80pVBpx8FCHJym7CnUfDAXe/FWv5qT7SQJbR0KuemyxaEDlEx5WT4VQJoTW+/9Qw==", "cpu": [ "arm64" ], @@ -6701,9 +6701,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.5.tgz", - "integrity": "sha512-LNdCHzgLFc+UeqMS84LzXPaeBRKyqDN9OMyFAr1OrB0XrNw78IRrEVtZvvA7245W/HsaoeVOQX9jPjPk8jojwA==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.6.tgz", + "integrity": "sha512-/YXjI1e5OXcZ7YpxRwgP/1jAV/SBKTzeVKqN2mk7mLpcICsyn3Gl5+dIfDTJp70M0ccMhyMMRso4v6mPDCGepg==", "cpu": [ "x64" ], @@ -19591,9 +19591,9 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.14.tgz", - "integrity": "sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==", + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", + "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -20600,13 +20600,13 @@ } }, "node_modules/eslint-config-next": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.5.tgz", - "integrity": "sha512-wPjq9MLQuWykHs8tsm2gH6OJThk6N27L8Te2JatlaGZ7jT1gtgGmJKukygL28xOlWsg5J1a1bGy/PvLyQC8OYA==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.6.tgz", + "integrity": "sha512-1Upt3U7BDwU+ilpe2byZjAfts9oNq4d4fv/zXEvs8/4yS+cwOQW/WCxUNy8gCDquX67SzeehDvKblVC6ZBMocQ==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.3.5", + "@next/eslint-plugin-next": "16.3.6", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", @@ -23938,34 +23938,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/ibm-cloud-sdk-core/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ibm-cloud-sdk-core/node_modules/axios": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.0.tgz", - "integrity": "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" - } - }, "node_modules/ibm-cloud-sdk-core/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -24013,21 +23985,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/ibm-cloud-sdk-core/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/ibm-cloud-sdk-core/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -30411,12 +30368,12 @@ } }, "node_modules/next": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/next/-/next-16.3.5.tgz", - "integrity": "sha512-MdtsTgzyfCPRLC6uJ1mN8ao7lyJ4BB0U6Inhnx3gta1UcCIdHK3yxLG0E8OWQteWD8/Q0qb8A5o7wJaL8M9y2w==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.6.tgz", + "integrity": "sha512-L+otWM/aQbYTx98aZhgEoMb4bZAXx1YVW4UMA/vuCyCoWG5HJyZUili8QAkqzrcC+5///tsz3s0M+SlyB5bLMw==", "license": "MIT", "dependencies": { - "@next/env": "16.3.5", + "@next/env": "16.3.6", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -30430,14 +30387,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.3.5", - "@next/swc-darwin-x64": "16.3.5", - "@next/swc-linux-arm64-gnu": "16.3.5", - "@next/swc-linux-arm64-musl": "16.3.5", - "@next/swc-linux-x64-gnu": "16.3.5", - "@next/swc-linux-x64-musl": "16.3.5", - "@next/swc-win32-arm64-msvc": "16.3.5", - "@next/swc-win32-x64-msvc": "16.3.5", + "@next/swc-darwin-arm64": "16.3.6", + "@next/swc-darwin-x64": "16.3.6", + "@next/swc-linux-arm64-gnu": "16.3.6", + "@next/swc-linux-arm64-musl": "16.3.6", + "@next/swc-linux-x64-gnu": "16.3.6", + "@next/swc-linux-x64-musl": "16.3.6", + "@next/swc-win32-arm64-msvc": "16.3.6", + "@next/swc-win32-x64-msvc": "16.3.6", "sharp": "^0.35.4" }, "peerDependencies": { diff --git a/package.json b/package.json index 140a33aadc1d..cc527986bb96 100644 --- a/package.json +++ b/package.json @@ -131,17 +131,17 @@ "electron:build:mac": "npm run build && cd electron && npm run build:mac", "electron:build:linux": "npm run build && cd electron && npm run build:linux", "electron:smoke:packaged": "node scripts/dev/smoke-electron-packaged.mjs", - "test": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", - "test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", - "test:unit:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", - "test:unit:ci:shard": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD \"tests/unit/serial/**/*.test.ts\"", - "test:unit:fast": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "test": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "test:unit:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "test:unit:ci:shard": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD \"tests/unit/serial/**/*.test.ts\"", + "test:unit:fast": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "test:scoped": "bash scripts/quality/test-scoped.sh", "test:scoped:staged": "bash scripts/quality/test-scoped.sh --staged", "test:scoped:full": "bash scripts/quality/test-scoped.sh --full", "test:unit:shard": "concurrently --kill-others-on-fail -n s1,s2 \"npm:test:unit:shard:1\" \"npm:test:unit:shard:2\"", - "test:unit:shard:1": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"", - "test:unit:shard:2": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"", + "test:unit:shard:1": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"", + "test:unit:shard:2": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"", "test:bun:db": "bun test tests/unit/db-adapters/bunSqliteAdapter.test.ts tests/unit/db-adapters/driverFactory.test.ts tests/unit/db-adapters/cliSqlite.test.mjs", "test:plan3": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test tests/unit/plan3-p0.test.ts", "test:fixes": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test tests/unit/fixes-p1.test.ts", @@ -294,7 +294,7 @@ "release:contributors": "node scripts/release/gen-contributors.mjs", "release:uncovered": "node scripts/release/list-uncovered-commits.mjs", "release:reconcile": "node scripts/release/reconcile-changelog.mjs", - "test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", + "test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial", "test:unit:serial": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/unit/serial/**/*.test.ts\"", "alibaba:sync-allowlist": "node --import tsx/esm scripts/ops/sync-alibaba-allowlist.mjs", "check:vitest-exclusions": "node scripts/check/check-vitest-exclusions.mjs", @@ -348,7 +348,7 @@ "material-symbols": "^0.47.0", "mermaid": "^11.17.2", "monaco-editor": "^0.56.0", - "next": "16.3.5", + "next": "16.3.6", "next-intl": "^4.14.0", "next-themes": "^0.4.6", "node-machine-id": "^1.1.12", @@ -429,7 +429,7 @@ "dpdm": "^4.3.0", "esbuild": "0.28.2", "eslint": "^10.10.0", - "eslint-config-next": "16.3.5", + "eslint-config-next": "16.3.6", "eslint-plugin-react-hooks": "7.1.1", "eslint-plugin-sonarjs": "^4.1.0", "espree": "^11.2.0", @@ -498,6 +498,7 @@ "sharp": "^0.35.4", "postcss": "^8.5.18", "ip-address": "^10.7.2", + "axios": "^1.20.0", "qs": "^6.16.0", "uuid": "^14.0.2", "form-data": "^4.0.6", diff --git a/public/openapi.yaml b/public/openapi.yaml index 7a9f771cb298..a17d5602325f 100644 --- a/public/openapi.yaml +++ b/public/openapi.yaml @@ -2812,11 +2812,13 @@ paths: schema: { type: string } responses: "200": - description: Token revoked + description: Token revoked (alreadyRevoked is true when it was already revoked) "403": description: Insufficient scope "404": - description: Token not found or already revoked + description: Token not found + "409": + description: Prefix matches more than one live token; nothing revoked # ─── CLI Tools ───────────────────────────────────────────────── diff --git a/scripts/build/assembleStandalone.mjs b/scripts/build/assembleStandalone.mjs index 9fbdf4643aad..a50f2195585d 100644 --- a/scripts/build/assembleStandalone.mjs +++ b/scripts/build/assembleStandalone.mjs @@ -1185,6 +1185,11 @@ export function assembleStandalone({ // 6. Optionally copy native assets + extra modules (synchronous) if (copyNatives) { copyNativeAssetsAndExtraModules(projectRoot, resolvedOutDir); + // copyNativeAssetsAndExtraModules recopies public/ with force, which + // overwrites the build-id stamp copyStaticAndPublic just wrote. Stamp + // again after that copy, or sw.js ships with the generic cache name and + // a browser keeps the previous deploy's worker. + stampServiceWorkerBuildId(resolvedOutDir); // Repair hollow externalized package dirs in BOTH locations Turbopack's standalone // tracer can populate: the top-level bundle node_modules, and — for projects with a // custom distDir (see next.config.mjs) — the nested /node_modules mirrored diff --git a/scripts/build/bootstrap-env.mjs b/scripts/build/bootstrap-env.mjs index ae7905451f73..4f012f7cf472 100644 --- a/scripts/build/bootstrap-env.mjs +++ b/scripts/build/bootstrap-env.mjs @@ -32,13 +32,28 @@ const OPTIONAL_OAUTH_SECRETS = [ { keys: ["QODER_OAUTH_CLIENT_SECRET"], label: "Qoder OAuth" }, ]; -// ── Resolve DATA_DIR (mirrors dataPaths.ts logic) ─────────────────────────── -function resolveDataDir(overridePath, env = process.env) { +// ── Resolve DATA_DIR (mirrors src/lib/dataPaths.ts::getDefaultDataDir) ───────── +// Kept self-contained on purpose: assembleStandalone.mjs copies this file alone into the +// standalone build, so it cannot import src/lib/dataPaths.ts or bin/cli/data-dir.mjs. The +// order MUST stay identical to those two: explicit DATA_DIR → an EXISTING legacy +// ~/.omniroute → %APPDATA% (Windows) → $XDG_CONFIG_HOME (when set) → ~/.omniroute. +// Skipping the legacy check made a machine with XDG_CONFIG_HOME exported persist +// server.env (incl. STORAGE_ENCRYPTION_KEY) under ~/.config/omniroute while the app opened +// ~/.omniroute/storage.sqlite and the CLI read ~/.omniroute/.env — two keys, one database. +export function resolveDataDir(overridePath, env = process.env) { if (overridePath?.trim()) return resolve(overridePath); const configured = env.DATA_DIR?.trim(); if (configured) return resolve(configured); + // Preserve an existing legacy dir so an upgrade never splits secrets from the database. + const legacyDir = join(homedir(), ".omniroute"); + try { + if (statSync(legacyDir).isDirectory()) return legacyDir; + } catch { + // absent or unreadable — fall through to the platform default + } + if (process.platform === "win32") { const appData = env.APPDATA || join(homedir(), "AppData", "Roaming"); return join(appData, "omniroute"); @@ -47,7 +62,7 @@ function resolveDataDir(overridePath, env = process.env) { const xdg = env.XDG_CONFIG_HOME?.trim(); if (xdg) return join(resolve(xdg), "omniroute"); - return join(homedir(), ".omniroute"); + return legacyDir; } function getPreferredEnvFilePath(env = process.env) { @@ -324,9 +339,32 @@ export function bootstrapEnv({ dataDirOverride, quiet = false } = {}) { log(" These providers will not work until configured."); } - // ── Warn about default password ──────────────────────────────────────────── - if (merged.INITIAL_PASSWORD === "CHANGEME" || !merged.INITIAL_PASSWORD?.trim()) { - log("⚠️ INITIAL_PASSWORD is not set — using default 'CHANGEME'. Change it in Settings!"); + // ── Warn about the initial dashboard password ────────────────────────────── + // Bootstrap reads process.env, one .env file, and server.env. Next.js can still fill + // an unset INITIAL_PASSWORD from its own .env files, so the unset notice hedges. + const initialPassword = merged.INITIAL_PASSWORD; + // Placeholder variants (" changeme ", "Changeme") become equally guessable passwords, + // so the comparison normalizes case and surrounding whitespace. + const isPlaceholderLike = + typeof initialPassword === "string" && initialPassword.trim().toUpperCase() === "CHANGEME"; + if (isPlaceholderLike) { + log("⚠️ INITIAL_PASSWORD matches the .env.example placeholder 'CHANGEME', a publicly known"); + log(" password. If no dashboard password is saved yet, that value becomes the password."); + log(" Set your own INITIAL_PASSWORD before first boot. In Docker, do it before the"); + log(" container's first start: a host browser reaches the container as a remote client,"); + log(" and the login refuses the exact placeholder CHANGEME from remote clients (case or"); + log(" whitespace variants are not refused remotely). Elsewhere, change the password"); + log(" right away: sign in from localhost and use Dashboard → Settings → Security, or run"); + log(" `omniroute reset-password` (`node bin/reset-password.mjs` in a source checkout)"); + log(" with DATA_DIR set to this server's data directory."); + } else if (!initialPassword) { + log("ℹ️ INITIAL_PASSWORD is unset here. Unless a .env file that Next.js loads sets it,"); + log(" a fresh install asks you to create the dashboard password in the onboarding wizard."); + } else if (!initialPassword.trim()) { + log("⚠️ INITIAL_PASSWORD is only whitespace. If no dashboard password is saved yet, that"); + log(" whitespace becomes the password, and it works from any address. Set a real"); + log(" INITIAL_PASSWORD before first boot, or change the password right away in"); + log(" Dashboard → Settings → Security."); } // ── Decrypt-probe: verify STORAGE_ENCRYPTION_KEY matches encrypted data (#1622) ─ diff --git a/scripts/build/colocate-standalone.mjs b/scripts/build/colocate-standalone.mjs index ea82693367cd..41afd1ca1a42 100644 --- a/scripts/build/colocate-standalone.mjs +++ b/scripts/build/colocate-standalone.mjs @@ -16,7 +16,7 @@ * * Run manually after a build, or automatically via the `postbuild` npm hook. */ -import { cpSync, existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { runBuildTool } from "./buildToolRunner.mjs"; @@ -156,7 +156,10 @@ function main() { dirname(compressionWorkerDest), ]; + const workerDest = join(STANDALONE, WORKER_REL); if (!hasOptionals) { + // Turbopack may trace the source placeholder into standalone; never ship it as a worker. + rmSync(workerDest, { force: true }); console.log( "[colocate-standalone] optional SLM deps absent at root node_modules — LLMLingua stays fail-open (slim install)." ); @@ -165,39 +168,7 @@ function main() { } // 1) Bundle the worker the resolver expects: /open-sse/.../onnxWorker.js - const workerDest = join(STANDALONE, WORKER_REL); - if (!existsSync(workerDest)) { - mkdirSync(dirname(workerDest), { recursive: true }); - try { - runBuildTool( - "esbuild", - "esbuild", - [ - join( - ROOT, - "open-sse", - "services", - "compression", - "engines", - "llmlingua", - "onnxWorker.ts" - ), - "--bundle", - "--platform=node", - "--packages=external", - "--format=esm", - `--outfile=${workerDest}`, - ], - { stdio: "inherit" } - ); - console.log("[colocate-standalone] ✅ LLMLingua worker bundled into standalone tree"); - } catch (err) { - console.warn("[colocate-standalone] ⚠️ worker bundle error:", err.message); - } - } else { - console.log("[colocate-standalone] worker already present (skipping bundle)"); - } - workerDirs.push(dirname(workerDest)); + if (bundleOptionalWorker(workerDest)) workerDirs.push(dirname(workerDest)); // 2) Co-locate the optional-dep closure (NO-CLOBBER, same semantics as colocateOptionals.mjs) const srcNm = join(ROOT, "node_modules"); @@ -221,6 +192,37 @@ function main() { writeEsmWorkerScopes(workerDirs); } +/** + * Bundle the optional LLMLingua worker over any traced source placeholder. + * + * On failure the destination is removed, so the runtime resolver cannot pick an + * empty placeholder and LLMLingua stays on its missing-worker fail-open path. + */ +export function bundleOptionalWorker(workerDest, build = runBuildTool) { + mkdirSync(dirname(workerDest), { recursive: true }); + try { + build( + "esbuild", + "esbuild", + [ + join(ROOT, "open-sse", "services", "compression", "engines", "llmlingua", "onnxWorker.ts"), + "--bundle", + "--platform=node", + "--packages=external", + "--format=esm", + `--outfile=${workerDest}`, + ], + { stdio: "inherit" } + ); + console.log("[colocate-standalone] ✅ LLMLingua worker bundled into standalone tree"); + return true; + } catch (err) { + rmSync(workerDest, { force: true }); + console.warn("[colocate-standalone] ⚠️ worker bundle error:", err.message); + return false; + } +} + // Run as a script (npm `postbuild` hook), but stay importable for unit tests. const entryScript = process.argv[1] ? pathToFileURL(process.argv[1]).href : null; if (entryScript === import.meta.url) { diff --git a/scripts/check/check-error-helper.mjs b/scripts/check/check-error-helper.mjs index 290e5f397468..de30c6b9c271 100644 --- a/scripts/check/check-error-helper.mjs +++ b/scripts/check/check-error-helper.mjs @@ -12,10 +12,18 @@ // CodeQL's js/stack-trace-exposure does not understand the custom sanitizer, so this // static gate is the canonical enforcement. See docs/security/ERROR_SANITIZATION.md. // -// Conservative by design: a file is flagged ONLY when it both (a) appears to forward -// a RAW error value into a response/result body AND (b) imports nothing from a -// utils/error path. Files that import the helper are trusted (the `body.error.message` -// they reference is the sanitized output of buildErrorBody, not raw upstream). +// Conservative by design: a line is flagged ONLY when it forwards a RAW error +// value into a client-facing response/result body WITHOUT routing it through a +// sanctioned builder/sanitizer on that same line. +// +// G-03 (#15159): trust is CALL-scoped, never FILE-scoped. This gate used to skip an +// entire file the moment it saw any import from a utils/error path, which is a +// file-scoped exemption applied to a call-scoped hazard — one correct +// `import { sanitizeErrorMessage }` permanently excused every other sink in the +// file. That is precisely how the audit's live E-09 shipped green: +// deepseek-web.ts imports the sanitizer for one call site while a second, +// file-local `errorResponse` builder forwarded raw upstream `errBody.msg` to the +// client. See SANITIZED_CALL below for the replacement. import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; @@ -39,16 +47,171 @@ const IS_API_ROUTE = /^src\/app\/api\/.+\/route\.tsx?$/; // with no utils/error import) and should become a tracked cleanup issue: route the // message through sanitizeErrorMessage()/buildErrorBody()/makeExecutorErrorResult(). // Do NOT add new entries without a justification — that defeats the gate. +// +// G-03 (#15159): every entry below was INVISIBLE to this gate until the +// file-level `if (ERROR_HELPER_IMPORT.test(source)) continue` skip was replaced +// with call-scoped trust. None is a regression from the fix — they are debt this +// gate finally reports. `assertNoStale` removes an entry automatically once its +// violation is gone, so freezing cannot ossify. +// +// TWO ENTRIES WERE REMOVED after re-auditing this list rather than trusting it. +// The first freeze pass reported 23; a line-by-line audit found two were NOT +// violations, and freezing a non-violation is how an allowlist stops meaning +// anything: +// * src/app/api/v1/batches/delete-completed/route.ts — the raw `err.message` / +// `err.stack` is inside `log.error("BATCHES", "sweep failed", { … })`, an audit +// row. The client response is a static buildErrorBody. The gate's +// INTERNAL_SINK_CALL anchor required the opener line to END with `{`, which every +// message-first logger fails. +// * src/app/api/headroom/start/route.ts — the message sits inside a multi-line +// `createErrorResponse({ … })` call. That builder lives in +// `src/lib/api/errorResponse.ts` (not `utils/error`) and sanitizes both exports +// (#15159 E-13), and per-LINE trust cannot see a builder named on a previous line. +// Both gate bugs are fixed, with regression guards in +// tests/unit/check-error-helper-false-positives-15159.test.ts. +// +// The honest count is 21. export const KNOWN_MISSING_ERROR_HELPER = new Set([ - // --- original open-sse/executors + handlers scope (pre-6A.8) --- - // --- 6A.8 expanded scope: src/app/api/**/route.ts pre-existing violations --- - // (empty — #8233 made open-sse/executors/muse-spark-web.ts import sanitizeErrorMessage, - // resolving the last frozen violator; see docs/security/ERROR_SANITIZATION.md) + // --- Provider-auth import/export routes: `error: error.message` on a thrown + // provider error. 8 sites, one shape. + // + // LATENT, NOT LIVE — corrected after a second audit. An earlier version of this + // comment claimed these "leak an upstream/credential-adjacent message verbatim", which + // was wrong. Each route's GENERIC branch already routes through `sanitizeErrorMessage`; + // only the TYPED branch forwards `error.message` raw, and every one of the 34 + // `new (Agy|Claude|Codex)AuthFileError(...)` throw sites across + // `src/lib/oauth/utils/{agy,claude,codex}Auth*.ts` passes a STATIC app-authored + // literal — zero interpolation, so no upstream body, token or path can arrive. + // They stay frozen because the guarantee is a maintenance invariant, not a property of + // the routes: a new `throw new CodexAuthFileError(`refresh failed: ${upstream.body}`)` + // would leak through all eight, and this gate cannot see it (these files DO import the + // sanitizer). Guarded by tests/unit/provider-auth-error-message-literals-15159.test.ts, + // which fails and names the site. The right fix for such a site is the throw site. + "src/app/api/providers/agy-auth/apply-local/route.ts", + "src/app/api/providers/agy-auth/import/route.ts", + "src/app/api/providers/claude-auth/import/route.ts", + "src/app/api/providers/codex-auth/import/route.ts", + "src/app/api/providers/[id]/claude-auth/apply-local/route.ts", + "src/app/api/providers/[id]/claude-auth/export/route.ts", + "src/app/api/providers/[id]/codex-auth/apply-local/route.ts", + "src/app/api/providers/[id]/codex-auth/export/route.ts", + + // --- Image generation/upscale/video handlers: `error: err.message` on an + // internal result object that a caller later serializes. The sibling + // `errorText = sanitizeErrorMessage(...)` on the next line proves the intent. + // This is the class E-15 closed at stability.ts but missed here. + "open-sse/handlers/imageGeneration/providers/adobeFirefly.ts", + "open-sse/handlers/imageUpscale/adobeFirefly.ts", + "open-sse/handlers/videoGeneration/adobeFireflyHandler.ts", + + // --- toJsonErrorPayload passthrough: unsanitized upstream envelope -> new + // Response(JSON.stringify(...)). The audit's E-15 pointed at upscale and was + // closed on a different site, so this one is still live in both siblings. + "src/app/api/v1/images/upscale/route.ts", + "src/app/api/v1/images/generations/route.ts", + + // --- Executors forwarding a raw caught error into the response body. + "open-sse/executors/copilot-web.ts", + "open-sse/executors/cloudflare-playground.ts", + "open-sse/executors/veoaifree-web.ts", + "open-sse/executors/yuanbao-web.ts", + + // --- API routes returning a caught error message directly. Note + // grok-build-settings:230 forwards a TYPED `GrokBuildConfigConflictError` (an + // app-defined message, not upstream text) and is latent for the same reason as the + // provider-auth block above; the sibling branch one line down already sanitizes. + "src/app/api/cli-tools/grok-build-settings/route.ts", + "src/app/api/github-skills/route.ts", // inner per-target catch in a results map + "src/app/api/skills/collect/install/route.ts", // inner per-target catch + "src/app/api/webhooks/[id]/test/route.ts", ]); -// Import specifiers that count as "uses the error helper" (path ends in utils/error). -const ERROR_HELPER_IMPORT = - /\bfrom\s*["'](?:\.{1,2}\/)*(?:open-sse\/)?utils\/error(?:\.[tj]s)?["']|@omniroute\/open-sse\/utils\/error/; +// G-03 (#15159): a line that actually routes an error value through a sanitizer +// is trusted. This is what replaced the former FILE-level skip +// (`if (ERROR_HELPER_IMPORT.test(source)) continue`), which let one import +// anywhere exempt every sink in the file — the blind spot the audit's live E-09 +// (deepseek-web.ts: imports the sanitizer, keeps a raw file-local builder) +// shipped green through. +// +// Judging the CALL instead of the FILE is strictly more precise: `RAW_ERR` has a +// `(?} names imported from a sanctioned error module. */ +function importedErrorHelperNames(source) { + const names = new Set(); + for (const match of source.matchAll(ERROR_HELPER_IMPORT_STATEMENT)) { + const clause = match[1] ?? match[2] ?? ""; + for (const part of clause.split(",")) { + // `errorResponse as er` -> the imported name is the ORIGINAL binding. + const original = part + .trim() + .split(/\s+as\s+/)[0] + ?.trim(); + if (original) names.add(original); + } + } + return names; +} // A caught-error identifier whose .message/.stack is RAW (not sanitized): the leading // token must be exactly `err` / `error` / `e` (optionally `(err as Error)` cast), and @@ -56,6 +219,7 @@ const ERROR_HELPER_IMPORT = // does not match, only our own caught `err.message` / `error.stack` / `(err as …).msg`. // The `(? 0), because the enclosing-construct walk tests only the line +// that opens the construct, so an unanchored match let incidental logger calls win. const INTERNAL_SINK_CALL = - /\b(?:saveCallLog|logToolCall|log\??\.\w+|console\.\w+|reqLogger\.\w+)\s*\(\s*\{?\s*$/; + /\b(?:saveCallLog|logToolCall|log\??\.\w+|console\.\w+|reqLogger\.\w+)\s*\(\s*(?:["'`][^"'`]*["'`]\s*,\s*)*\{?\s*$/; // A line that is constructing a client-facing response/result body. const RESPONSE_LINE = @@ -101,12 +273,90 @@ const RAW_ERR_FIELD_RELAXED = new RegExp(String.raw`\b(?:message|error)\s*:\s*[^ const RAW_ERR_INTERP = new RegExp(String.raw`\$\{[^}]*` + RAW_ERR); // Upstream `body.error.message` forwarded into a field without a sanitize call. -const RAW_BODY_ERR = /\b(?:message|error)\s*:\s*[^,}\n]*\bbody\.error\.message\b/; +// G-03: excludes envelopes this file built itself via a sanctioned builder — +// `const body = buildErrorBody(status, sanitizeErrorMessage(msg))` followed by +// `error: body.error.message` is the SANITIZED idiom used across the +// *-fetch.ts executors, and matching it textually flagged compliant files the +// moment the file-level skip was removed. A `body` that arrives from an upstream +// fetch (no local buildErrorBody) is still flagged. +const RAW_BODY_ERR = /\b(?:message|error)\s*:\s*[^,}\n]*\b(\w+)\.error\.message\b/; +// The pre-G-03 rule matched ONLY a literal `body.error.message`. G-03 does NOT +// widen it to any receiver — that is a separate, much larger rule change (it +// would flag every upstream-passthrough site in the repo). The receiver group +// exists only so a locally-built envelope can be excluded below. +const RAW_BODY_ERR_LEGACY = /\b(?:message|error)\s*:\s*[^,}\n]*\bbody\.error\.message\b/; + +// `const = (` — a locally built, already-sanitized envelope. +const LOCAL_ENVELOPE_DECL = /\b(?:const|let|var)\s+(\w+)\s*=\s*(?:\w+\.)?\w+\s*\(/; + +/** + * Variable names holding an envelope this file built through a sanctioned builder. + * + * @param {string} source + * @returns {Set} + */ +function locallySanitizedEnvelopes(source) { + const safe = new Set(); + for (const line of source.split("\n").map((l) => l.replace(/\/\/.*$/, ""))) { + const name = line.match(LOCAL_ENVELOPE_DECL)?.[1]; + if (!name) continue; + const built = line.slice(line.indexOf(name) + name.length); + if (trustedBuilder(built)) safe.add(name); + } + return safe; +} + +/** Does this expression call a sanctioned builder/sanitizer? */ +function trustedBuilder(expression) { + return ( + SANITIZED_CALL.test(expression) || + CANONICAL_BUILDER_NAMES.some((name) => new RegExp(`\\b${name}\\s*\\(`).test(expression)) + ); +} // A response-builder CALL that takes a message argument (client-facing). A tainted // local variable (assigned from a raw error) passed here is a leak. +// `createErrorResponse` / `createErrorResponseFromUnknown` belong here for the same +// reason they are in CANONICAL_BUILDER_NAMES: they are the sanctioned builder for +// everything outside open-sse, so a raw error passed to one is the same defect as a raw +// error passed to `errorResponse`. const RESPONSE_BUILDER_CALL = - /\b(?:errResp|makeErrorResponse|errorResponse)\s*\(|\bresponse\s*:\s*(?:errResp|makeErrorResponse|errorResponse|new\s+Response)\s*\(/; + /\b(?:errResp|makeErrorResponse|errorResponse|createErrorResponseFromUnknown|createErrorResponse)\s*\(|\bresponse\s*:\s*(?:errResp|makeErrorResponse|errorResponse|new\s+Response)\s*\(/; + +// A builder name in CALL position (not its declaration / definition). Keeps the +// G-03 ternary-argument rule from matching `function errorResponse(status, message)`. +const CALL_ARG_PREFIX = /\b(?:return|await|yield)\b|[:=]\s*$/; + +// A FILE-LOCAL builder declaration: `function errorResponse(`, `const +// errorResponse = (`. When its body sanitizes, calls to it are safe even though +// the file never imported the canonical one — this is the shape E-09's fix left +// behind (sanitizeErrorMessage moved INSIDE deepseek-web.ts's local builder). +const LOCAL_BUILDER_DECL = /\b(?:function|const|let|var)\s+(\w+)\s*[=(]/; + +/** + * Names of file-local builder functions whose own body sanitizes. Those names are + * safe to call with a raw message, so the G-03 ternary rule must not fire on them. + * + * @param {string} source + * @returns {Set} + */ +function sanitizingLocalBuilders(source) { + const safe = new Set(); + const lines = source.split("\n").map((l) => l.replace(/\/\/.*$/, "")); + for (let i = 0; i < lines.length; i++) { + const name = lines[i].match(LOCAL_BUILDER_DECL)?.[1]; + if (!name) continue; + // Scan the declaration body (to its closing brace, bounded) for a sanitizer. + for (let j = i; j < lines.length && j - i < 30; j++) { + if (SANITIZED_CALL.test(lines[j])) { + safe.add(name); + break; + } + if (j > i && /^\s*}\s*$/.test(lines[j])) break; // body ended, unsanitized + } + } + return safe; +} // G-11 (#15159): MCP tool results are `{ content: [{ type: "text", text }], isError: true }` // — the client-facing sink for MCP tools, NOT an HTTP Response builder. A raw error @@ -142,15 +392,41 @@ const TAINT_DECL = new RegExp( * response-builder call (errResp / makeErrorResponse / errorResponse / new Response) * or into any client-facing result field (G-11). */ +/** + * Is this single line routing an error value through a sanctioned builder/sanitizer? + * + * Only builders actually imported from utils/error count — a file that imports just + * `sanitizeErrorMessage` gets no builder trust at all, which is the whole point of + * resolving per symbol rather than per file (G-03). + * + * @param {string} line + * @param {Set} importedNames names imported from a utils/error path + * @param {Set} localBuilders file-local builders whose body sanitizes + * @returns {boolean} + */ +function isTrustedLine(line, importedNames, localBuilders) { + if (SANITIZED_CALL.test(line)) return true; + if (!CANONICAL_BUILDER.test(line)) return false; + return CANONICAL_BUILDER_NAMES.some( + (name) => + (importedNames.has(name) || localBuilders.has(name)) && + new RegExp(`\\b${name}\\s*\\(`).test(line) + ); +} + function forwardsRawError(source, isMcpServer = false) { const lines = source.split("\n").map((l) => l.replace(/\/\/.*$/, "")); + const importedNames = importedErrorHelperNames(source); + const localBuilders = sanitizingLocalBuilders(source); + const safeEnvelopes = locallySanitizedEnvelopes(source); + const trusted = (line) => isTrustedLine(line, importedNames, localBuilders); // Pass 1: collect tainted local variables (raw error, no sanitize on the line). const tainted = new Set(); for (const line of lines) { if (INTERNAL_SINK.test(line)) continue; const m = line.match(TAINT_DECL); - if (m && !/sanitize/i.test(line)) tainted.add(m[1]); + if (m && !trusted(line)) tainted.add(m[1]); } const taintedUse = tainted.size > 0 ? new RegExp(String.raw`\b(?:${[...tainted].join("|")})\b`) : null; @@ -160,10 +436,33 @@ function forwardsRawError(source, isMcpServer = false) { const line = lines[i]; if (!line.trim()) continue; if (INTERNAL_SINK.test(line)) continue; // log / audit / throw / reject + // G-03: trust the CALL, not the file. A line that routes the error through a + // sanctioned builder/sanitizer is clean even though it also mentions + // err.message — the raw identifier still matches RAW_ERR here, so without + // this guard every correct call in the repo would be reported. + // + // `enclosedByTrustedBuilder` is required, not decorative: sanctioned builders are + // routinely called MULTI-LINE, with the message on a later line — + // return createErrorResponse({ status: 400, message: error.message, ... }); + // The `message:` line mentions err.message and contains no builder call, so a + // same-line-only check reported compliant `createErrorResponse` / `buildErrorBody` + // call sites as leaks. + if (trusted(line) || enclosedByTrustedBuilder(lines, i, importedNames, localBuilders)) continue; if (TAINT_DECL.test(line)) continue; // the assignment itself is not the leak const isMcp = isMcpServer; const directLeak = + // G-03: a raw caught error handed straight to a response builder as a + // ternary argument — `errorResponse(500, err instanceof Error ? err.message + // : String(err))`. No `message:` field, no `${}` interpolation and no + // declared local, so neither RAW_ERR_FIELD nor RAW_ERR_INTERP nor the + // tainted-local rule sees it. This is the shape deepseek-web.ts used at + // :1037/:1060 and it is exactly what the old file-level skip hid. + // + // `return`/`await`/`: ` prefixes keep this on genuine CALL sites: without + // them it would match the builder's own `function errorResponse(...)` + // declaration and flag every well-formed file in the repo. + (CALL_ARG_PREFIX.test(line) && RAW_ERR_RE.test(line) && RESPONSE_BUILDER_CALL.test(line)) || RAW_ERR_FIELD.test(line) || (isMcp && RAW_ERR_FIELD_RELAXED.test(line) && !/sanitize/i.test(line)) || RAW_ERR_FIELD_INTERP.test(line) || @@ -173,7 +472,11 @@ function forwardsRawError(source, isMcpServer = false) { // Multi-line OpenAI error envelope: a raw-error interpolation that sits inside // an enclosing `error: {` / `message:` field of a `new Response(` body. (RAW_ERR_INTERP.test(line) && enclosedByErrorResponseBody(lines, i)) || - (RAW_BODY_ERR.test(line) && !/sanitize/i.test(line)); + (RAW_BODY_ERR_LEGACY.test(line) && + !/sanitize/i.test(line) && + // G-03: `body.error.message` off an envelope this file built through a + // sanctioned builder is the sanitized idiom, not a leak. + !safeEnvelopes.has(line.match(RAW_BODY_ERR)?.[1] ?? "")); // G-11: a tainted alias (`const msg = err.message`) used in an MCP tool-result // field is a leak. A sanitize call on the use line clears the taint. @@ -240,6 +543,35 @@ function enclosedByInternalSinkCall(lines, idx) { // Field opener that is part of an OpenAI-style error envelope (`error: {` / `message:`). const ERROR_FIELD_OPENER = /\b(?:error|message)\s*:\s*[`{]?\s*$/; +/** + * Walk back to the line that opens the construct enclosing `idx`, and report whether + * that opener is a sanctioned builder/sanitizer call. + * + * Same brace-depth walk as `enclosedByInternalSinkCall`, inverted in meaning: this one + * grants trust instead of suppressing. It exists because a multi-line + * `createErrorResponse({ ... message: err.message ... })` puts the offending field on a + * line that names no builder at all, so a same-line check cannot see the sanitization. + * + * @param {string[]} lines + * @param {number} idx + * @returns {boolean} + */ +function enclosedByTrustedBuilder(lines, idx, importedNames, localBuilders) { + let depth = 0; + for (let j = idx; j >= 0 && idx - j < 80; j--) { + const l = lines[j].replace(/\/\/.*$/, ""); + for (let k = l.length - 1; k >= 0; k--) { + const ch = l[k]; + if (ch === ")" || ch === "}") depth++; + else if (ch === "(" || ch === "{") { + if (depth === 0) return isTrustedLine(l.slice(0, k + 1), importedNames, localBuilders); + depth--; + } + } + } + return false; +} + /** * Walk back from `idx` to the nearest enclosing `{`/`(` opener; if it opens an error * envelope field (`error: {` / `message:`) AND a `new Response(` / `response:` builder @@ -271,7 +603,6 @@ export function findErrorHelperViolations(files, allowlist) { const violations = []; for (const { path: rel, source } of files) { if (allowlist.has(rel)) continue; - if (ERROR_HELPER_IMPORT.test(source)) continue; // trusts the helper const isMcpServer = rel.startsWith("open-sse/mcp-server/"); if (forwardsRawError(source, isMcpServer)) violations.push(rel); } diff --git a/scripts/check/check-file-size.mjs b/scripts/check/check-file-size.mjs index 4bfc87a7a5a2..08673b621a7a 100644 --- a/scripts/check/check-file-size.mjs +++ b/scripts/check/check-file-size.mjs @@ -179,12 +179,18 @@ function main() { const testCap = baseline.testCap; const testFrozen = baseline.testFrozen || {}; const currentTests = collectTestLoc(); + // PR mode (#8522): base LOC comes from the test collector's files, so a test file that + // already drifted on the base is judged against its base size, like a source file. + const testBaseLoc = + BASE_REF && typeof testCap === "number" + ? getBaseLoc(BASE_REF, Object.keys(currentTests)) + : undefined; const { violations: testViolations, improvements: testImprovements, redundant: testRedundant, } = typeof testCap === "number" - ? evaluateFileSizes(currentTests, testFrozen, testCap, BASE_REF ? baseLoc : undefined) + ? evaluateFileSizes(currentTests, testFrozen, testCap, testBaseLoc) : { violations: [], improvements: [], redundant: [] }; if (UPDATE) { diff --git a/scripts/check/check-migration-numbering.mjs b/scripts/check/check-migration-numbering.mjs index 86799b51b3a8..4caf46f2f63d 100644 --- a/scripts/check/check-migration-numbering.mjs +++ b/scripts/check/check-migration-numbering.mjs @@ -50,8 +50,10 @@ export const KNOWN_DUPLICATE_VERSIONS = new Set([ // (149_api_key_combo_access.sql) — nenhuma das duas é mais um gap. O // stale-enforcement exige que cada reserva seja removida quando os arquivos // correspondentes aterrissarem na release. +// #15564 landed 197 and 198. 199 (#15314), 200, and 201 (#15354) also exist. +// None of 197–201 is a gap. // --------------------------------------------------------------------------- -export const KNOWN_GAPS = new Set(["026", "055", "121"]); // 121: número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12); 144/145 aterrissaram na release (radar offers/intel cache), 148/149 aterrissaram (provider_quota_state, api_key_combo_access) +export const KNOWN_GAPS = new Set(["026", "055", "121"]); // 121: número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12); 144/145 aterrissaram na release (radar offers/intel cache), 148/149 aterrissaram (provider_quota_state, api_key_combo_access); 197–201 exist on disk and are not gaps function pad3(n) { return String(n).padStart(3, "0"); diff --git a/scripts/check/check-test-discovery.mjs b/scripts/check/check-test-discovery.mjs index 89301ebb4556..b3060b459320 100644 --- a/scripts/check/check-test-discovery.mjs +++ b/scripts/check/check-test-discovery.mjs @@ -58,7 +58,7 @@ export const COLLECTORS = [ // abaixo). Subdir novo: adicione aqui E nos scripts (o drift-check + o gate de // órfãos forçam a manutenção em sincronia). { - glob: "tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts", + glob: "tests/unit/{api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts", sources: ["package.json"], }, // Node native runner — tests/unit/dashboard/** roda numa invocação separada com o hook diff --git a/scripts/check/check-workflows.mjs b/scripts/check/check-workflows.mjs index 3384f3cdba6b..439910c7478a 100644 --- a/scripts/check/check-workflows.mjs +++ b/scripts/check/check-workflows.mjs @@ -40,6 +40,7 @@ import { execFileSync, spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; +import { load as yamlLoad } from "js-yaml"; import { findProvenanceOnSelfHosted, formatProvenanceFinding } from "./lib/provenanceRunner.mjs"; const ROOT = process.cwd(); @@ -282,6 +283,112 @@ export function runZizmor(workflowsDir) { // Main // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Scheduled-run guard: scheduled executions stay in the upstream repository +// --------------------------------------------------------------------------- + +const SCHEDULED_GUARD_EVENT = "github.event_name != 'schedule'"; +const SCHEDULED_GUARD_REPO = "github.repository"; + +/** + * Reads the trigger section of a parsed workflow document. The `on` key is + * read defensively (`doc[true] ?? doc["on"]`) so both YAML 1.1 (boolean true) + * and YAML 1.2 (string "on") parses stay green across parser versions. + * @param {unknown} doc parsed workflow document + * @returns {unknown} the `on` section, or undefined + */ +function readTriggerSection(doc) { + if (!doc || typeof doc !== "object") return undefined; + const record = /** @type {Record} */ (doc); + return record[true] ?? record["on"]; +} + +/** + * Whether a parsed trigger section declares a `schedule` trigger. Accepts the + * object form (`schedule:` with cron entries), the bare string form, and the + * array form of `on:`. + * @param {unknown} onSection parsed `on` section + * @returns {boolean} + */ +function hasScheduleTrigger(onSection) { + if (onSection == null) return false; + if (typeof onSection === "string") return onSection === "schedule"; + if (Array.isArray(onSection)) return onSection.includes("schedule"); + if (typeof onSection !== "object") return false; + return Object.prototype.hasOwnProperty.call(onSection, "schedule"); +} + +/** + * Whether a job-level `if` carries the upstream-only scheduled-run guard. + * @param {unknown} condition the job's `if` value + * @returns {boolean} + */ +function hasScheduledGuard(condition) { + return ( + typeof condition === "string" && + condition.includes(SCHEDULED_GUARD_EVENT) && + condition.includes(SCHEDULED_GUARD_REPO) + ); +} + +/** + * Lists scheduled jobs missing the upstream-only guard. Fail-closed: an + * unreadable file, invalid YAML, or a document without a usable `jobs` map is + * reported, never silently accepted. A workflow without a `schedule` trigger + * reports nothing. Never throws for these cases; a non-array argument is a + * usage error and still throws. + * @param {string[]} files absolute workflow paths + * @returns {{ file: string, job: string, reason: string }[]} + */ +export function findScheduledJobsWithoutGuard(files) { + if (!Array.isArray(files)) throw new TypeError("files must be an array"); + const findings = []; + for (const file of files) { + let text; + try { + text = fs.readFileSync(file, "utf8"); + } catch { + findings.push({ file, job: "", reason: "unreadable or missing jobs" }); + continue; + } + let doc; + try { + doc = yamlLoad(text); + } catch { + findings.push({ file, job: "", reason: "unreadable or missing jobs" }); + continue; + } + if (!hasScheduleTrigger(readTriggerSection(doc))) continue; + const jobs = doc && typeof doc === "object" ? doc.jobs : undefined; + if (!jobs || typeof jobs !== "object" || Array.isArray(jobs)) { + findings.push({ file, job: "", reason: "unreadable or missing jobs" }); + continue; + } + for (const [jobName, job] of Object.entries(jobs)) { + if (!job || typeof job !== "object" || Array.isArray(job)) { + findings.push({ file, job: jobName, reason: "unreadable or missing jobs" }); + continue; + } + if (!hasScheduledGuard(job.if)) { + findings.push({ file, job: jobName, reason: "scheduled job without upstream guard" }); + } + } + } + return findings; +} + +/** + * Runs the scheduled-run guard over the given workflow files. + * @param {string[]} files absolute workflow paths + * @returns {{ file: string, job: string, reason: string }[]} + */ +export function runScheduledGuardCheck(files) { + return findScheduledJobsWithoutGuard(files).map((f) => ({ + ...f, + file: path.relative(ROOT, f.file), + })); +} + /** * Hard rule (not a lint count): `--provenance` inside a job that runs on a * self-hosted runner. npm answers 422 at the registry, and in v3.8.50 that @@ -383,6 +490,18 @@ function main() { // version that produced it. See zizmorVersion(). process.stdout.write(`zizmorVersion=${scannerVersion}\n`); process.stdout.write(`provenanceRunnerFindings=${provenanceFindings.length}\n`); + const scheduledGuardFindings = runScheduledGuardCheck(workflowFiles); + if (scheduledGuardFindings.length > 0) { + console.error( + `[check-workflows] scheduled-guard: ${scheduledGuardFindings.length} finding(s) — HARD RULE:` + ); + scheduledGuardFindings.forEach((f) => + console.error(` ${f.file}: job "${f.job}" — ${f.reason}`) + ); + } else if (!QUIET) { + console.log("[check-workflows] scheduled-guard: OK (0 findings)"); + } + process.stdout.write(`scheduledGuardFindings=${scheduledGuardFindings.length}\n`); if ((STRICT || RATCHET) && provenanceFindings.length > 0) { console.error( `\n[check-workflows] FAIL — ${provenanceFindings.length} job(s) publish with --provenance from a self-hosted runner.\n` + @@ -392,6 +511,15 @@ function main() { process.exit(1); } + if ((STRICT || RATCHET) && scheduledGuardFindings.length > 0) { + console.error( + `\n[check-workflows] FAIL — ${scheduledGuardFindings.length} scheduled job(s) without the upstream-only guard.\n` + + " Scheduled runs must stay in diegosouzapw/OmniRoute: keep the job-level `if:` naming both\n" + + " `github.event_name != 'schedule'` and `github.repository`." + ); + process.exit(1); + } + if (STRICT && total > 0) { console.error(`\n[check-workflows] FAIL — ${total} workflow finding(s) total (--strict mode).`); process.exit(1); diff --git a/scripts/check/newCodeMode.mjs b/scripts/check/newCodeMode.mjs index bf9f5659593d..040e9633f2ba 100644 --- a/scripts/check/newCodeMode.mjs +++ b/scripts/check/newCodeMode.mjs @@ -37,12 +37,34 @@ function git(args, opts = {}) { }).trim(); } -/** Merge-base between the PR base ref and HEAD (falls back to the ref itself). */ -export function resolveMergeBase(baseRef) { +/** + * Merge-base between the PR base ref and HEAD (falls back to the ref itself). + * + * GitHub checks PRs out at `refs/pull/N/merge`: a merge commit whose FIRST parent + * is the live base tip at job time. `github.event.pull_request.base.sha` stays at + * the commit the PR was OPENED against, so once the base moves, + * `merge-base(ref, HEAD)` silently diffs the base's own newer commits onto the PR + * (#15346/#15322 — a #12740 base change blamed on unrelated PRs). When HEAD is a + * merge commit and `baseRef` is an ancestor of HEAD^1, compare against HEAD^1: + * that diff is exactly the PR's new code, matching GitHub's "Files changed" view. + */ +export function resolveMergeBase(baseRef, { cwd = ROOT } = {}) { + try { + const parents = git(["rev-list", "--parents", "-n", "1", "HEAD"], { cwd }).split(/\s+/); + const firstParent = parents[1]; + if (parents.length > 2 && firstParent) { + // Throws (exit 1) when baseRef is NOT an ancestor of the live base tip — + // i.e. a local diverged branch, where merge-base semantics still apply. + git(["merge-base", "--is-ancestor", baseRef, firstParent], { cwd }); + return firstParent; + } + } catch { + /* not a merge commit, or baseRef not an ancestor — fall through */ + } try { - return git(["merge-base", baseRef, "HEAD"]); + return git(["merge-base", baseRef, "HEAD"], { cwd }); } catch { - return git(["rev-parse", baseRef]); + return git(["rev-parse", baseRef], { cwd }); } } diff --git a/scripts/dev/diff-chatcore-leaves.mjs b/scripts/dev/diff-chatcore-leaves.mjs new file mode 100644 index 000000000000..9fe065a82c53 --- /dev/null +++ b/scripts/dev/diff-chatcore-leaves.mjs @@ -0,0 +1,400 @@ +#!/usr/bin/env node +// scripts/dev/diff-chatcore-leaves.mjs +// +// Region diff that proves the four response-path leaves in open-sse/handlers/chatCore/ are +// mechanical lifts of the monolithic handleChatCore on the base ref. +// +// For every leaf it (1) slices the lifted region out of the BASE chatCore.ts (region.base: +// startPattern/endPattern), (2) slices the corresponding body out of the leaf (region.leaf), (3) +// tokenizes both with the TypeScript scanner (comments, whitespace and prettier re-wrap noise +// such as trailing commas vanish), (4) runs a Myers token diff and groups the edits into hunks. +// A hunk is "mechanical" only when it is one of the documented lift seams (see classifyHunk): +// the syncExecuteTranslatedBody(translatedBody) re-sync, the `return X` -> +// `return { response: X, carry: {...} }` wrapping with the exact per-leaf carry keys, and the +// region-boundary tokens that stay behind in handleChatCore. Every other hunk is printed as +// NON-MECHANICAL and the script exits 1 (exit 2 = a region could not be extracted). +// +// Usage: node scripts/dev/diff-chatcore-leaves.mjs [--base ] [--verbose] +// default base: origin/release/v3.8.52 (the pre-split chatCore.ts that the leaves lift from). + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { createRequire } from "node:module"; +import { fileURLToPath } from "node:url"; + +const require = createRequire(import.meta.url); +const ts = require("typescript"); + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const argv = process.argv.slice(2); +const flag = (name) => argv.includes(name); +const opt = (name, fallback) => (argv.includes(name) ? argv[argv.indexOf(name) + 1] : fallback); +const BASE_REF = opt("--base", "origin/release/v3.8.52"); +const VERBOSE = flag("--verbose"); +const BASE_FILE = "open-sse/handlers/chatCore.ts"; + +/** Normalize a source slice to comparable lines (drops blanks and comment-only lines). */ +export function normalize(text) { + const out = []; + let inBlock = false; + for (const raw of text.split("\n")) { + let line = raw.trim(); + if (inBlock) { + if (line.includes("*/")) inBlock = false; + continue; + } + if (line.startsWith("/*")) { + if (!line.includes("*/")) inBlock = true; + continue; + } + if (line === "" || line.startsWith("//")) continue; + line = line.replace(/\s+/g, " "); + // prettier re-wraps change trailing commas / semicolons-only differences after dedent + out.push(line); + } + return out; +} + +/** + * Slice a region out of NORMALIZED lines. The region starts at the first `startPattern` match + * (searched after the first `after` match when given) and ends at the first `endPattern` match + * after the start (the LAST match when `endMode: "last"`). The end line is excluded unless + * `inclusiveEnd`. + */ +export function sliceRegion( + lines, + { after, startPattern, endPattern, endMode = "first", inclusiveEnd } +) { + let from = 0; + if (after) { + from = lines.findIndex((l) => after.test(l)); + if (from < 0) throw new Error(`anchor ${after} not found`); + } + let s = -1; + for (let i = from; i < lines.length; i++) { + if (startPattern.test(lines[i])) { + s = i; + break; + } + } + if (s < 0) throw new Error(`start pattern ${startPattern} not found`); + let e = -1; + if (endMode === "last") { + for (let i = lines.length - 1; i > s; i--) { + if (endPattern.test(lines[i])) { + e = i; + break; + } + } + } else { + for (let i = s + 1; i < lines.length; i++) { + if (endPattern.test(lines[i])) { + e = i; + break; + } + } + } + if (e < 0) throw new Error(`end pattern ${endPattern} not found after the start`); + return lines.slice(s, inclusiveEnd ? e + 1 : e); +} + +/** Tokenize TypeScript source (comments/whitespace dropped, prettier-wrap noise removed). */ +export function tokenize(text) { + const scanner = ts.createScanner(ts.ScriptTarget.ES2022, true, ts.LanguageVariant.Standard, text); + const tokens = []; + const stack = []; // "tpl" | "brace" + let prevKind = ts.SyntaxKind.Unknown; + const exprEnd = new Set([ + ts.SyntaxKind.Identifier, + ts.SyntaxKind.CloseParenToken, + ts.SyntaxKind.CloseBracketToken, + ts.SyntaxKind.NumericLiteral, + ts.SyntaxKind.StringLiteral, + ts.SyntaxKind.NoSubstitutionTemplateLiteral, + ts.SyntaxKind.TemplateTail, + ts.SyntaxKind.RegularExpressionLiteral, + ts.SyntaxKind.ThisKeyword, + ts.SyntaxKind.TrueKeyword, + ts.SyntaxKind.FalseKeyword, + ts.SyntaxKind.NullKeyword, + ]); + for (let kind = scanner.scan(); kind !== ts.SyntaxKind.EndOfFileToken; kind = scanner.scan()) { + if (kind === ts.SyntaxKind.CloseBraceToken && stack[stack.length - 1] === "tpl") { + kind = scanner.reScanTemplateToken(false); + if (kind === ts.SyntaxKind.TemplateTail) stack.pop(); + } else if (kind === ts.SyntaxKind.CloseBraceToken) { + stack.pop(); + } else if (kind === ts.SyntaxKind.OpenBraceToken) { + stack.push("brace"); + } else if (kind === ts.SyntaxKind.TemplateHead) { + stack.push("tpl"); + } else if ( + (kind === ts.SyntaxKind.SlashToken || kind === ts.SyntaxKind.SlashEqualsToken) && + !exprEnd.has(prevKind) + ) { + kind = scanner.reScanSlashToken(); + } + tokens.push(scanner.getTokenText()); + prevKind = kind; + } + // prettier adds/removes trailing commas when it re-wraps after the dedent + return tokens.filter((t, i) => !(t === "," && /^[)\]}]$/.test(tokens[i + 1] ?? ""))); +} + +/** Myers O(ND) diff over token arrays. Returns edit hunks {aStart,aEnd,bStart,bEnd}. */ +export function myersHunks(a, b, maxD = 20000) { + const n = a.length; + const m = b.length; + const max = Math.min(n + m, maxD); + const trace = []; + let v = new Int32Array(2 * max + 2); + const off = max; + let found = -1; + for (let d = 0; d <= max && found < 0; d++) { + trace.push(v.slice()); + for (let k = -d; k <= d; k += 2) { + let x = + k === -d || (k !== d && v[off + k - 1] < v[off + k + 1]) + ? v[off + k + 1] + : v[off + k - 1] + 1; + let y = x - k; + while (x < n && y < m && a[x] === b[y]) { + x++; + y++; + } + v[off + k] = x; + if (x >= n && y >= m) { + found = d; + break; + } + } + } + if (found < 0) throw new Error(`token diff exceeds ${maxD} edits - regions are not comparable`); + // backtrack + const edits = []; // {type: "del"|"ins", ai, bi} + let x = n; + let y = m; + for (let d = found; d > 0; d--) { + const vv = trace[d]; + const k = x - y; + const prevK = k === -d || (k !== d && vv[off + k - 1] < vv[off + k + 1]) ? k + 1 : k - 1; + const prevX = vv[off + prevK]; + const prevY = prevX - prevK; + while (x > prevX && y > prevY) { + x--; + y--; + } + if (x === prevX) edits.push({ type: "ins", ai: x, bi: y - 1 }); + else edits.push({ type: "del", ai: x - 1, bi: y }); + x = prevX; + y = prevY; + } + edits.reverse(); + // group into hunks (merge edits separated by < 4 matching tokens) + const hunks = []; + for (const e of edits) { + const last = hunks[hunks.length - 1]; + const aPos = e.ai; + const bPos = e.bi; + if (last && aPos - last.aEnd < 4 && bPos - last.bEnd < 4 + (aPos - last.aEnd)) { + // extend + if (e.type === "del") last.aEnd = Math.max(last.aEnd, e.ai + 1); + else last.bEnd = Math.max(last.bEnd, e.bi + 1); + if (e.type === "del") last.bEnd = Math.max(last.bEnd, e.bi); + else last.aEnd = Math.max(last.aEnd, e.ai); + } else { + hunks.push({ + aStart: e.ai, + aEnd: e.type === "del" ? e.ai + 1 : e.ai, + bStart: e.bi, + bEnd: e.type === "ins" ? e.bi + 1 : e.bi, + }); + } + } + return hunks; +} + +// Regions. `base` slices the pre-split chatCore.ts, `leaf` slices the leaf file. Patterns run +// against NORMALIZED lines (trimmed, comments and blanks dropped), so indentation is irrelevant. +const LEAF_AFTER_DEPS = /^\} = deps;$/; +export const REGIONS = [ + { + name: "executeProviderRequest", + file: "open-sse/handlers/chatCore/executeProviderRequest.ts", + carryKeys: [], + allowRemoved: [], + base: { + startPattern: /^const execute = async \(\) => \{$/, + endPattern: /^return execute\(\);$/, + inclusiveEnd: true, + }, + leaf: { + after: LEAF_AFTER_DEPS, + startPattern: /^const execute = async \(\) => \{$/, + endPattern: /^return execute\(\);$/, + inclusiveEnd: true, + }, + }, + { + name: "streamingResponse", + file: "open-sse/handlers/chatCore/streamingResponse.ts", + carryKeys: [ + "translatedBody", + "currentModel", + "effectiveServiceTier", + "finalBody", + "pipelineRecovered", + "providerHeaders", + "providerResponse", + "providerUrl", + ], + allowRemoved: ["; } } }"], // closers of `if (stream) {` / handleChatCoreInner left in the barrel + base: { + after: /^let pipelineRecovered = false;$/, + startPattern: /^try \{$/, + endPattern: /^if \(!stream\) \{$/, + }, + leaf: { + after: LEAF_AFTER_DEPS, + startPattern: /^try \{$/, + endPattern: /^return \{$/, + endMode: "last", + }, + }, + { + name: "nonStreamingResponse", + file: "open-sse/handlers/chatCore/nonStreamingResponse.ts", + carryKeys: [ + "translatedBody", + "currentModel", + "finalBody", + "providerResponse", + "providerHeaders", + "effectiveServiceTier", + "claudePromptCacheLogMeta", + "reasoningReplayHistory", + "pipelineRecovered", + ], + allowRemoved: ["if ( ! stream ) {", "}"], // the `if (!stream)` opener / its closer stay in the barrel + base: { + startPattern: /^if \(!stream\) \{$/, + endPattern: /^providerResponse = await maybeConvertJsonBodyToSse\(/, + }, + leaf: { + after: LEAF_AFTER_DEPS, + startPattern: /^try \{$/, + endPattern: /^\}$/, + endMode: "last", + }, + }, + { + name: "streamingTail", + file: "open-sse/handlers/chatCore/streamingTail.ts", + carryKeys: [ + "onPipelineStreamError", + "onClientDisconnectFinalize", + "turnExecutionHandedOffToStream", + ], + allowRemoved: [ + // handleChatCoreInner's finally (turn-execution release) stays in the barrel + "; } finally { if ( ! turnExecutionHandedOffToStream ) { releaseTurnExecution ( ) ; } } }", + ], + base: { + startPattern: /^providerResponse = await maybeConvertJsonBodyToSse\(/, + endPattern: /^(export )?function isTokenExpiringSoon/, + }, + leaf: { + after: LEAF_AFTER_DEPS, + startPattern: /^providerResponse = await maybeConvertJsonBodyToSse\(/, + endPattern: /^\}$/, + endMode: "last", + }, + }, +]; + +const SYNC_CALL = "syncExecuteTranslatedBody ( translatedBody ) ;"; +const WRAP_OPEN = new Set(["{ response :", "response : {", "result : {"]); + +/** Returns a short reason when the hunk is a documented lift seam, else null. */ +export function classifyHunk(removed, added, region) { + const carry = `carry : { ${region.carryKeys.join(" , ")} }`; + if (removed === "" && added === SYNC_CALL) return "re-sync of the live translatedBody closure"; + if (removed === "" && WRAP_OPEN.has(added)) + return "return value wrapped as { response|result, carry }"; + const closeRe = new RegExp(`^, ${carry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} \\}( ;( \\})*)?$`); + if ((removed === "" || region.allowRemoved.includes(removed)) && closeRe.test(added)) { + return "carry object closing the wrapped return"; + } + const wrapValue = removed.match(/^[\w$]+$/); + if (wrapValue && added === `{ response : ${removed} , ${carry} }`) { + return "returned identifier wrapped with the carry object"; + } + if (region.allowRemoved.includes(removed) && added === "") + return "region boundary left in handleChatCore"; + return null; +} + +function readBase() { + try { + return execFileSync("git", ["show", `${BASE_REF}:${BASE_FILE}`], { + cwd: ROOT, + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + } catch (err) { + console.error( + `[diff-chatcore-leaves] cannot read ${BASE_FILE} from ${BASE_REF}: ${err.message}` + ); + process.exit(2); + } +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + main(); +} + +function main() { + console.log(`[diff-chatcore-leaves] base ref: ${BASE_REF}`); + const baseLines = normalize(readBase()); + let total = 0; + for (const region of REGIONS) { + const leafLines = normalize(fs.readFileSync(path.join(ROOT, region.file), "utf8")); + let bt; + let lt; + try { + bt = tokenize(sliceRegion(baseLines, region.base).join("\n")); + lt = tokenize(sliceRegion(leafLines, region.leaf).join("\n")); + } catch (err) { + console.error(`[diff-chatcore-leaves] ${region.name}: cannot extract region: ${err.message}`); + process.exit(2); + } + const hunks = myersHunks(bt, lt); + let mechanical = 0; + const bad = []; + for (const h of hunks) { + const removed = bt.slice(h.aStart, h.aEnd).join(" "); + const added = lt.slice(h.bStart, h.bEnd).join(" "); + const reason = classifyHunk(removed, added, region); + if (reason) { + mechanical++; + if (VERBOSE) console.log(` ok ${reason}: -[${removed}] +[${added.slice(0, 120)}]`); + } else { + bad.push({ h, removed, added }); + } + } + total += bad.length; + console.log( + `${bad.length === 0 ? "OK " : "FAIL"} ${region.name}: base ${bt.length} tokens, leaf ${lt.length} tokens, ` + + `${mechanical} mechanical hunk(s), ${bad.length} NON-MECHANICAL` + ); + for (const { h, removed, added } of bad) { + console.log(` @@ after: ${bt.slice(Math.max(0, h.aStart - 8), h.aStart).join(" ")}`); + if (removed) console.log(` - ${removed}`); + if (added) console.log(` + ${added}`); + } + } + console.log(`\n[diff-chatcore-leaves] non-mechanical hunks: ${total}`); + if (total === 0) console.log("[diff-chatcore-leaves] all four leaves are mechanical lifts"); + process.exit(total === 0 ? 0 : 1); +} diff --git a/scripts/dev/listen-with-retry.mjs b/scripts/dev/listen-with-retry.mjs new file mode 100644 index 000000000000..30f33068aa75 --- /dev/null +++ b/scripts/dev/listen-with-retry.mjs @@ -0,0 +1,53 @@ +// scripts/dev/listen-with-retry.mjs +// net.Server.listen with a bounded EADDRINUSE retry. +// +// The dev server binds its port only AFTER next prepare() (~15-18s), while test +// harnesses pick the port with a bind(0)-release at module load. Anything that +// claims the port inside that window (the previous test file's server still +// tearing down, a leftover process) used to kill the boot with EADDRINUSE and +// red the whole integration job. The conflicts this window actually produces +// are transient, so retry; a port that is still busy after the last attempt +// rejects with the original error, so a genuine conflict still fails loudly. + +/** + * Listen on `port`/`host`, retrying while the error is EADDRINUSE. + * + * @param {import("node:net").Server} server + * @param {{ port: number, host?: string, attempts?: number, delayMs?: number, + * log?: (msg: string) => void }} options + * `attempts` is the total number of tries (default 6 — 1 try + 5 retries). + * @returns {Promise} the bound + * address (post-listen errors are the caller's business again). + */ +export function listenWithRetry(server, options) { + const { port, host, attempts = 6, delayMs = 5000, log = console.warn } = options; + let triesLeft = attempts; + return new Promise((resolve, reject) => { + const listen = () => { + const onListen = () => { + cleanup(); + resolve(server.address()); + }; + const onError = (error) => { + cleanup(); + triesLeft -= 1; + if (error?.code !== "EADDRINUSE" || triesLeft <= 0) { + reject(error); + return; + } + log( + `[listen] port ${port} busy (EADDRINUSE) — retrying in ${delayMs}ms (${triesLeft} attempt(s) left)` + ); + setTimeout(listen, delayMs); + }; + const cleanup = () => { + server.removeListener("listening", onListen); + server.removeListener("error", onError); + }; + server.once("listening", onListen); + server.once("error", onError); + server.listen(port, host); + }; + listen(); + }); +} diff --git a/scripts/dev/next-upgrade-relay.mjs b/scripts/dev/next-upgrade-relay.mjs new file mode 100644 index 000000000000..9952de02cd99 --- /dev/null +++ b/scripts/dev/next-upgrade-relay.mjs @@ -0,0 +1,29 @@ +import { EventEmitter } from "node:events"; + +/** + * Keeps Next's own WebSocket upgrade listener off the custom HTTP server. + * + * `next()` (NextCustomServer.setupWebSocketHandler) attaches its router upgrade handler on the + * first request it serves, to `options.httpServer || req.socket.server`. On the run-next.mjs + * server that listener runs *alongside* the custom dispatcher, sees every upgrade, and + * `socket.end()`s any whose path resolves to an app route (router-server.js: `if (matchedOutput) + * return socket.end()`). `/v1/responses` and `/v1/ws` resolve to `/api/v1/...` app routes, so the + * sockets the Responses WebSocket proxy and the /v1/ws bridge had already claimed were cut. + * + * Pass `target` as next()'s `httpServer`: Next then attaches to this relay instead, and the + * dispatcher calls `forward()` only for upgrades nothing else claimed (e.g. dev HMR). Note that + * `nextApp.getUpgradeHandler()` is a no-op in Next 16 (NextNodeServer.handleUpgrade is empty), + * so this relay is the only path HMR upgrades reach Next's router. + */ +export function createNextUpgradeRelay() { + const target = new EventEmitter(); + return { + target, + /** @returns true when Next's upgrade listener was attached and received the upgrade. */ + forward(req, socket, head) { + if (target.listenerCount("upgrade") === 0) return false; + target.emit("upgrade", req, socket, head); + return true; + }, + }; +} diff --git a/scripts/dev/run-next.mjs b/scripts/dev/run-next.mjs index 055e9f5d7d94..f93577020ff1 100644 --- a/scripts/dev/run-next.mjs +++ b/scripts/dev/run-next.mjs @@ -8,6 +8,7 @@ import { bootstrapEnv } from "../build/bootstrap-env.mjs"; import { resolveRuntimePorts, withRuntimePortEnv } from "../build/runtime-env.mjs"; import { createOmnirouteWsBridge } from "./v1-ws-bridge.mjs"; import { createResponsesWsProxy } from "./responses-ws-proxy.mjs"; +import { createNextUpgradeRelay } from "./next-upgrade-relay.mjs"; import { ensurePeerStampToken, stampPeerIp } from "./peer-stamp.mjs"; import methodGuard from "./http-method-guard.cjs"; import headResponseGuard from "./head-response-guard.cjs"; @@ -17,6 +18,7 @@ import { randomUUID } from "node:crypto"; import { getMainServerTimeoutConfig } from "./main-server-timeouts.mjs"; import { createSystemdNotifier } from "./systemd-notify.mjs"; import { attachRequestStreamGuards, installProcessCrashGuard } from "./httpClientAbortGuard.mjs"; +import { listenWithRetry } from "./listen-with-retry.mjs"; const { maybeHandleDisallowedMethod } = methodGuard; const { wrapRequestListenerWithHeadResponseGuard } = headResponseGuard; @@ -129,6 +131,10 @@ ensurePeerStampToken(); if (!useTurbopack) { delete process.env.TURBOPACK; } +// Next attaches its own upgrade listener to `httpServer` (default: the server of the first +// request), which would end upgrades the dispatcher below already owns (/v1/responses, /v1/ws). +// See next-upgrade-relay.mjs. +const nextUpgradeRelay = createNextUpgradeRelay(); function createNextApp() { return next({ dev, @@ -137,6 +143,7 @@ function createNextApp() { port: dashboardPort, turbopack: useTurbopack, webpack: !useTurbopack, + httpServer: nextUpgradeRelay.target, }); } @@ -221,6 +228,8 @@ async function start() { if (responsesWsHandled) return; const handled = await wsBridge.handleUpgrade(req, socket, head); if (handled) return; + // Next's router upgrade handler (dev HMR) — only for upgrades nothing above claimed. + if (nextUpgradeRelay.forward(req, socket, head)) return; await upgradeHandler(req, socket, head); } catch (error) { if (!socket.destroyed) { @@ -230,11 +239,6 @@ async function start() { } }); - server.on("error", (error) => { - console.error("[FATAL] Next custom server failed:", error); - process.exit(1); - }); - let isShuttingDown = false; const shutdown = async (signal) => { if (isShuttingDown) { @@ -267,14 +271,26 @@ async function start() { process.on("SIGINT", () => void shutdown("SIGINT")); process.on("SIGTERM", () => void shutdown("SIGTERM")); - server.listen(dashboardPort, hostname, () => { - const bundler = dev ? (useTurbopack ? "turbopack" : "webpack") : "production"; - console.log( - `[Next] ${mode} server listening on http://${hostname}:${dashboardPort} (${bundler})` - ); - systemdNotifier.ready(); - systemdNotifier.startWatchdog(); + // Bind with a bounded EADDRINUSE retry: test harnesses pick this port with a + // bind(0)-release at module load, ~15-18s before prepare() finishes, so a + // transient occupant must not kill the boot (base-red #15306). + try { + await listenWithRetry(server, { port: dashboardPort, host: hostname }); + } catch (error) { + console.error("[FATAL] Next custom server failed:", error); + process.exit(1); + } + // Post-listen server errors stay fatal — during-listen ones were the retry helper's. + server.on("error", (error) => { + console.error("[FATAL] Next custom server failed:", error); + process.exit(1); }); + const bundler = dev ? (useTurbopack ? "turbopack" : "webpack") : "production"; + console.log( + `[Next] ${mode} server listening on http://${hostname}:${dashboardPort} (${bundler})` + ); + systemdNotifier.ready(); + systemdNotifier.startWatchdog(); } start().catch((error) => { diff --git a/scripts/dev/standalone-server-ws.mjs b/scripts/dev/standalone-server-ws.mjs index ec4508f295e4..fee62315e251 100644 --- a/scripts/dev/standalone-server-ws.mjs +++ b/scripts/dev/standalone-server-ws.mjs @@ -27,15 +27,79 @@ installProcessCrashGuard(); // service. No-op outside systemd (no NOTIFY_SOCKET). const systemdNotifier = createSystemdNotifier(); let systemdReadySent = false; -// NOTE: if an operator sets NEXT_MANUAL_SIG_HANDLE=1, Next never registers its -// own signal cleanup and these once() handlers would suppress Node's default -// signal exit (process lingers until systemd's stop-timeout SIGKILL). Nothing -// in this repo sets that var; acceptable, documented behavior. -process.once("SIGINT", () => systemdNotifier.stopping()); -process.once("SIGTERM", () => systemdNotifier.stopping()); const originalCreateServer = http.createServer.bind(http); const proxiesByPort = new Map(); +/** Every server created through the patched factory (Next's main listener included). */ +const createdServers = new Set(); + +// Shutdown ownership (same model as scripts/dev/run-next.mjs, #12074). Next's start-server +// registers SIGINT/SIGTERM handlers that server.close() and then process.exit(143) as soon as no +// connection is left. That raced OmniRoute's async cleanup (src/lib/gracefulShutdown.ts: spend +// batch flush, call-log flush, DB checkpoint) and usually won, so a SIGTERM (docker stop, +// systemctl stop, Ctrl+C) dropped that work. This wrapper owns process exit instead: +// - NEXT_MANUAL_SIG_HANDLE tells Next not to install its handlers. Next reads it once, in its +// 'listening' handler; it is cleared right after (see the factory below) so spawned children +// (embedded services, some of them Next apps) do not inherit it and ignore SIGTERM. +// - __omnirouteCustomServerOwnsShutdown makes initGracefulShutdown() register its cleanup as +// globalThis.__omnirouteRequestShutdown instead of installing competing signal listeners. +// Both must be set before ./server.js loads. +// Consequence: Next's own SIGTERM handler is disabled in standalone, so its nextServer.close() +// no longer runs and pending after() / waitUntil work is NOT awaited on shutdown. Anyone adding a +// data-writing after() must flush it in OmniRoute's shutdown (src/lib/gracefulShutdown.ts). +// Empty or unset counts as "not set" (an empty value is falsy for Next, which would install its +// handler and bring back the exit-143 race). +const ownsNextSignalEnv = !process.env.NEXT_MANUAL_SIG_HANDLE; +if (ownsNextSignalEnv) process.env.NEXT_MANUAL_SIG_HANDLE = "1"; +globalThis.__omnirouteCustomServerOwnsShutdown = true; + +// Bounded: gracefulShutdown waits up to SHUTDOWN_TIMEOUT_MS for in-flight requests, then cleans +// up; the margin covers the flush/checkpoint work. +const SHUTDOWN_CLEANUP_MARGIN_MS = 5000; +function resolveForceExitMs() { + const drainMs = Number.parseInt(process.env.SHUTDOWN_TIMEOUT_MS || "30000", 10); + return (Number.isFinite(drainMs) && drainMs >= 0 ? drainMs : 30000) + SHUTDOWN_CLEANUP_MARGIN_MS; +} + +let shutdownStarted = false; +async function shutdown(signal) { + // Duplicate signals are normal (a terminal Ctrl+C reaches both this process and the parent + // launcher, which forwards SIGTERM too); the force-exit timer bounds the whole sequence. + if (shutdownStarted) return; + shutdownStarted = true; + const forceExitMs = resolveForceExitMs(); + const forceExitTimer = setTimeout(() => { + console.warn(`[Shutdown] Cleanup still running after ${forceExitMs}ms; forcing exit.`); + process.exit(0); + }, forceExitMs); + forceExitTimer.unref?.(); + systemdNotifier.stopping(); + try { + // Stop accepting connections; in-flight requests keep running until the drain below. + for (const server of createdServers) { + if (server.listening) server.close(() => {}); + server.closeIdleConnections?.(); + } + } catch (error) { + console.error("[Shutdown] Closing HTTP servers failed:", error?.message ?? error); + } + try { + // Undefined when instrumentation never got that far (signal during boot): just exit. + await globalThis.__omnirouteRequestShutdown?.(signal); + } catch (error) { + console.error("[Shutdown] Cleanup failed during", signal, error?.message ?? error); + } finally { + clearTimeout(forceExitTimer); + // One macrotask before exit (#13306: let sql.js/libuv teardown settle on Windows). + setTimeout(() => process.exit(0), 0); + } +} + +process.on("SIGINT", () => void shutdown("SIGINT")); +process.on("SIGTERM", () => void shutdown("SIGTERM")); +// #8045: Windows maps console-window close to SIGHUP; gracefulShutdown used to handle it here. +process.on("SIGHUP", () => void shutdown("SIGHUP")); + const { wrapRequestListenerWithMethodGuard } = methodGuard; const { wrapRequestListenerWithHeadResponseGuard } = headResponseGuard; @@ -183,6 +247,8 @@ http.createServer = function createServerWithResponsesWs(...args) { // listener); otherwise the original http.Server. The downstream .on/.addListener // patches below apply identically to both (https.Server extends http.Server). const server = createServerListener(args, tlsOptions, { createHttp: originalCreateServer }); + createdServers.add(server); + server.once("close", () => createdServers.delete(server)); // Node's http.Server default keepAliveTimeout (5_000ms) races pooled // keep-alive HTTP clients that idle longer than that between requests (e.g. // the JVM java.net.http.HttpClient used by JetBrains AI Assistant), which @@ -237,6 +303,13 @@ http.createServer = function createServerWithResponsesWs(...args) { // sd_notify READY once the main listener is actually accepting, then arm // the watchdog keep-alive interval (unref'd — never keeps the process up). server.once("listening", () => { + // Next reads NEXT_MANUAL_SIG_HANDLE synchronously in its own 'listening' handler, which runs + // right after this one; clear it on the next turn so child processes never inherit it. + if (ownsNextSignalEnv) { + setImmediate(() => { + delete process.env.NEXT_MANUAL_SIG_HANDLE; + }); + } if (systemdReadySent) return; systemdReadySent = true; systemdNotifier.ready(); diff --git a/scripts/dev/sync-env.mjs b/scripts/dev/sync-env.mjs index f0c58460d3d7..c12eb84953ef 100644 --- a/scripts/dev/sync-env.mjs +++ b/scripts/dev/sync-env.mjs @@ -12,7 +12,7 @@ * - Skips commented lines from .env.example */ -import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs"; +import { copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { randomBytes } from "node:crypto"; import { createRequire } from "node:module"; import { dirname, join, resolve } from "node:path"; @@ -68,11 +68,22 @@ const CRYPTO_SECRETS = { */ const ENCRYPTION_BOUND_KEYS = new Set([]); -// ── Resolve DATA_DIR (mirrors bootstrap-env.mjs / dataPaths.ts) ───────────── -function resolveDataDir(env = process.env) { +// ── Resolve DATA_DIR (mirrors src/lib/dataPaths.ts::getDefaultDataDir) ───────── +// Same self-contained copy as scripts/build/bootstrap-env.mjs (this file ships in the npm +// package on its own). Order: explicit DATA_DIR → an EXISTING legacy ~/.omniroute → +// %APPDATA% (Windows) → $XDG_CONFIG_HOME (when set) → ~/.omniroute. +export function resolveDataDir(env = process.env) { const configured = env.DATA_DIR?.trim(); if (configured) return resolve(configured); + // Preserve an existing legacy dir so an upgrade never splits secrets from the database. + const legacyDir = join(homedir(), ".omniroute"); + try { + if (statSync(legacyDir).isDirectory()) return legacyDir; + } catch { + // absent or unreadable — fall through to the platform default + } + if (process.platform === "win32") { const appData = env.APPDATA || join(homedir(), "AppData", "Roaming"); return join(appData, "omniroute"); @@ -81,7 +92,7 @@ function resolveDataDir(env = process.env) { const xdg = env.XDG_CONFIG_HOME?.trim(); if (xdg) return join(resolve(xdg), "omniroute"); - return join(homedir(), ".omniroute"); + return legacyDir; } /** diff --git a/scripts/i18n/lib/translate-backend.mjs b/scripts/i18n/lib/translate-backend.mjs index 77e4ca7d9ee6..c6c3a86e7b9c 100644 --- a/scripts/i18n/lib/translate-backend.mjs +++ b/scripts/i18n/lib/translate-backend.mjs @@ -11,6 +11,7 @@ * OMNIROUTE_TRANSLATION_API_KEY bearer token * OMNIROUTE_TRANSLATION_MODEL model id * OMNIROUTE_TRANSLATION_TIMEOUT_MS per-request timeout (default 60000) + * OMNIROUTE_TRANSLATION_REASONING_EFFORT optional, sent as-is when set * * Two translation modes are exposed: * - `translateString(en, localeEntry, backend)` — one request per string. @@ -45,10 +46,21 @@ export function backendConfig() { const apiKey = requireEnv("OMNIROUTE_TRANSLATION_API_KEY"); const model = requireEnv("OMNIROUTE_TRANSLATION_MODEL"); const timeoutMs = Number(process.env.OMNIROUTE_TRANSLATION_TIMEOUT_MS || 60000); - return { apiUrl, apiKey, model, timeoutMs }; + const reasoningEffort = process.env.OMNIROUTE_TRANSLATION_REASONING_EFFORT?.trim(); + return { + apiUrl, + apiKey, + model, + timeoutMs, + ...(reasoningEffort ? { reasoningEffort } : {}), + }; } -export async function callChat(messages, { apiUrl, apiKey, model, timeoutMs }, retry = 0) { +export async function callChat( + messages, + { apiUrl, apiKey, model, timeoutMs, reasoningEffort }, + retry = 0 +) { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), timeoutMs); try { @@ -63,6 +75,7 @@ export async function callChat(messages, { apiUrl, apiKey, model, timeoutMs }, r messages, temperature: 0.15, stream: false, + ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}), }), signal: ctrl.signal, }); @@ -73,7 +86,7 @@ export async function callChat(messages, { apiUrl, apiKey, model, timeoutMs }, r const wait = 1500 + retry * 1500; logWarn(`upstream ${res.status} — retrying after ${wait}ms`); await new Promise((r) => setTimeout(r, wait)); - return callChat(messages, { apiUrl, apiKey, model, timeoutMs }, retry + 1); + return callChat(messages, { apiUrl, apiKey, model, timeoutMs, reasoningEffort }, retry + 1); } throw new Error(`upstream ${res.status}: ${text.slice(0, 200)}`); } @@ -87,7 +100,7 @@ export async function callChat(messages, { apiUrl, apiKey, model, timeoutMs }, r if (err?.name === "AbortError") { if (retry < 1) { logWarn(`timeout after ${timeoutMs}ms — retrying`); - return callChat(messages, { apiUrl, apiKey, model, timeoutMs }, retry + 1); + return callChat(messages, { apiUrl, apiKey, model, timeoutMs, reasoningEffort }, retry + 1); } throw new Error(`timeout after ${timeoutMs}ms`); } @@ -98,7 +111,7 @@ export async function callChat(messages, { apiUrl, apiKey, model, timeoutMs }, r ) { logWarn(`network error: ${err.message} — retrying`); await new Promise((r) => setTimeout(r, 1500)); - return callChat(messages, { apiUrl, apiKey, model, timeoutMs }, retry + 1); + return callChat(messages, { apiUrl, apiKey, model, timeoutMs, reasoningEffort }, retry + 1); } throw err; } finally { @@ -235,3 +248,152 @@ export async function translateBatch(entries, localeEntry, backend) { } return parsed; } + +// ----- Multi-locale batch mode ----------------------------------------------- +// One chat request translates up to N keys into M locales. The answer is one +// JSON object per line, one line per locale: `{"":{"":""}}`. +// A broken line fails only its own locale — the other lines still apply, and +// the caller retries the failed locales through the single-locale path. + +export function multiLocaleBatchSystem(targetDescs) { + const targets = targetDescs.map((l) => `${l.code} = ${l.english ?? l.code}`).join(", "); + const count = targetDescs.length; + return [ + `You translate short interface strings from English into each target language. Target languages (code = name): ${targets}.`, + `Keep ICU placeholders like {count}, plural syntax, rich-text tags (never translate a tag name), URLs and quoted '' spans unchanged.`, + `Answer with exactly ${count} line${count === 1 ? "" : "s"}, one JSON object per line, one line per target language in the order given, keeping the ids of the input object: {"":{"":""}}. No prose, no code fence.`, + ].join("\n"); +} + +// Repair candidates for a single answer line, mirroring the tolerant reader +// of the local translation launcher: a trailing brace too many or missing. +function repairLocaleLine(line) { + const candidates = [ + line, + line.replace(/\}\s*$/, ""), + `${line}}`, + line.replace(/\}\s*\}\s*$/, "}"), + ]; + for (const candidate of candidates) { + try { + const parsed = JSON.parse(candidate); + if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) { + return parsed; + } + } catch { + /* try the next candidate */ + } + } + return null; +} + +/** + * Pure parser for a multi-locale batch answer. Tries a whole JSON object + * first, then falls back to one JSON object per line. Every expected locale + * must map to an OWN object holding every expected id as an OWN non-empty + * string; locales that fail this check land in `failedLocales` instead of + * failing the whole parse. Unknown locale codes are ignored, the first + * occurrence of a duplicated code wins, values are trimmed. + * + * @param {string} text raw assistant content + * @param {string[]} expectedLocaleCodes locale codes the caller sent + * @param {string[]} expectedIds short caller ids (`1..K`) used in the request + * @returns {{ perLocale: Map>, failedLocales: string[] }} + */ +export function parseMultiLocaleBatchResponse(text, expectedLocaleCodes, expectedIds) { + const expected = new Set(expectedLocaleCodes); + // Strip fences and per-line trailing commas so pasted answers still parse. + const cleaned = String(text) + .trim() + .replace(/^```(?:json)?\s*/i, "") + .replace(/\s*```$/, "") + .trim(); + const merged = new Map(); + const record = (code, row) => { + if (!expected.has(code) || merged.has(code)) return; + if (row === null || typeof row !== "object" || Array.isArray(row)) return; + merged.set(code, row); + }; + if (cleaned) { + // Whole-object attempt: `{"fr":{"1":"x"},"de":{"1":"y"}}`. + let whole = null; + try { + const parsed = JSON.parse(cleaned); + if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) whole = parsed; + } catch { + whole = null; + } + if (whole) { + for (const [code, row] of Object.entries(whole)) record(code, row); + } else { + for (const raw of cleaned.split("\n")) { + const line = raw.trim().replace(/,$/, ""); + if (!line) continue; + const parsed = repairLocaleLine(line); + if (!parsed) continue; + for (const [code, row] of Object.entries(parsed)) record(code, row); + } + } + } + const perLocale = new Map(); + const failedLocales = []; + for (const code of expectedLocaleCodes) { + const row = merged.get(code); + if (row === undefined) { + failedLocales.push(code); + continue; + } + const values = new Map(); + let ok = true; + for (const id of expectedIds) { + if (!Object.hasOwn(row, id) || typeof row[id] !== "string") { + ok = false; + break; + } + const value = row[id].trim(); + if (!value) { + ok = false; + break; + } + values.set(id, value); + } + if (ok) perLocale.set(code, values); + else failedLocales.push(code); + } + return { perLocale, failedLocales }; +} + +/** + * Translates up to N keys into M locales in ONE chat request. `keyEntries` + * are `{ id, text }` pairs using the caller's key paths; the request uses + * short ids (`1..K`) so the answer repeats less text, and the returned maps + * are keyed back by caller id. Returns a partial result when at least one + * locale parses — the caller retries `failedLocales` through `translateBatch`. + * Throws when the request itself fails or when no locale could be read. + * + * @returns {Promise<{ perLocale: Map>, failedLocales: string[] }>} + */ +export async function translateMultiLocaleBatch(keyEntries, localeEntries, backend) { + const ids = keyEntries.map((_, i) => String(i + 1)); + const payload = Object.fromEntries(keyEntries.map((e, i) => [ids[i], e.text])); + const messages = [ + { role: "system", content: multiLocaleBatchSystem(localeEntries) }, + { role: "user", content: JSON.stringify(payload) }, + ]; + const out = await callChat(messages, backend); + const expectedCodes = localeEntries.map((l) => l.code); + const parsed = parseMultiLocaleBatchResponse(out, expectedCodes, ids); + const perLocale = new Map(); + for (const [code, values] of parsed.perLocale) { + const mapped = new Map(); + keyEntries.forEach((entry, i) => { + const value = values.get(ids[i]); + mapped.set(entry.id, preserveIcuLiteralQuotes(entry.text, value)); + }); + perLocale.set(code, mapped); + } + if (perLocale.size === 0 && expectedCodes.length > 0) { + throw new Error("multi-locale batch translated no locale: no locale translated"); + } + return { perLocale, failedLocales: parsed.failedLocales }; +} diff --git a/scripts/i18n/sync-ui-keys.mjs b/scripts/i18n/sync-ui-keys.mjs index 6c4f16289784..8c89194954c8 100755 --- a/scripts/i18n/sync-ui-keys.mjs +++ b/scripts/i18n/sync-ui-keys.mjs @@ -37,7 +37,12 @@ import path from "node:path"; import process from "node:process"; import { fileURLToPath, pathToFileURL } from "node:url"; -import { backendConfig, translateBatch, translateString } from "./lib/translate-backend.mjs"; +import { + backendConfig, + translateBatch, + translateMultiLocaleBatch, + translateString, +} from "./lib/translate-backend.mjs"; // ----- .env loader -------------------------------------------------------- // Loads variables from a local `.env` (gitignored) into process.env without @@ -109,7 +114,7 @@ function logError(...parts) { console.error("[i18n-ui-sync] ERROR", ...parts); } -function parseArgs(argv) { +export function parseArgs(argv) { const opts = { locales: null, dryRun: false, @@ -117,6 +122,7 @@ function parseArgs(argv) { retranslateIdentical: false, concurrency: null, batchSize: 1, + localesPerRequest: 1, catalog: "ui", }; for (const arg of argv.slice(2)) { @@ -142,6 +148,10 @@ function parseArgs(argv) { // Whole numbers only: a fractional size would make the slice windows // overlap; NaN / 0 / negatives mean "per-string" (1). opts.batchSize = Math.max(1, Math.floor(Number(arg.slice(13))) || 1); + } else if (arg.startsWith("--locales-per-request=")) { + // How many locales one translation request covers. Whole numbers only; + // NaN / 0 / negatives mean "one locale per request" (1, the default). + opts.localesPerRequest = Math.max(1, Math.floor(Number(arg.slice(22))) || 1); } else if (arg === "--help" || arg === "-h") { console.log( [ @@ -159,6 +169,10 @@ function parseArgs(argv) { " --batch-size= Placeholders per translation request (default: 1).", " n>1 sends up to n strings as one JSON object; a batch", " that fails or cannot be parsed falls back to one-by-one", + " --locales-per-request= Locales per translation request (default: 1).", + " n>1 translates up to n locales per request, one JSON", + " object per locale per line; a locale whose line cannot", + " be parsed falls back to one locale at a time", ].join("\n") ); process.exit(0); @@ -229,7 +243,7 @@ const FORBIDDEN_KEYS = new Set(["__proto__", "prototype", "constructor"]); * Returns a tuple: { merged, addedPaths } so the caller can report the * additions and (optionally) translate them. */ -function mergeMissing(source, target) { +export function mergeMissing(source, target) { const addedPaths = []; function walk(srcNode, tgtNode, prefix) { @@ -253,10 +267,7 @@ function mergeMissing(source, target) { const nextPrefix = prefix ? `${prefix}.${key}` : key; let tgtChild; if (isPlainObject(tgtNode) && Object.prototype.hasOwnProperty.call(tgtNode, key)) { - // Read the property via Object.entries instead of dynamic bracket - // access to keep static analyzers happy. - const entry = Object.entries(tgtNode).find(([k]) => k === key); - tgtChild = entry ? entry[1] : undefined; + tgtChild = tgtNode[key]; } out[key] = walk(value, tgtChild, nextPrefix); } @@ -281,7 +292,7 @@ function countPlaceholders(node) { // tooling can share it. Only the concurrency limiter stays here. // Simple promise-based semaphore (avoid runtime deps). -function createLimiter(max) { +export function createLimiter(max) { let active = 0; const queue = []; const next = () => { @@ -307,6 +318,71 @@ function createLimiter(max) { }); } +/** + * Slices locale codes into consecutive groups of up to `size` codes, keeping + * the input order (config / on-disk order). An empty input emits no chunk. + */ +export function chunkLocales(codes, size) { + const n = Math.max(1, Math.floor(Number(size) || 1)); + const chunks = []; + for (let i = 0; i < codes.length; i += n) chunks.push(codes.slice(i, i + n)); + return chunks; +} + +function stripPlaceholder(value) { + return typeof value === "string" && value.startsWith(PLACEHOLDER_PREFIX) + ? value.slice(PLACEHOLDER_PREFIX.length) + : value; +} + +/** + * Unions the placeholder maps of one locale chunk into translation tasks. + * `placeholdersByLocale` maps each locale code to its `path → english` map + * (walked from that locale's merged tree, same order as `translatePlaceholders` + * uses). Tasks keep first-seen walk order, extras appended in encounter order, + * and each task names the locales that carry the path. + * + * A path whose English source diverges across the chunk is excluded from the + * multi-locale request and reported in `divergent` (same path with a + * different source text per locale cannot share one request); the caller + * translates those one locale at a time. + * + * @returns {{ tasks: Array<{ path: string, en: string, locales: string[] }>, divergent: Array<{ path: string, perLocale: Map }> }} + */ +export function collectChunkTasks(placeholdersByLocale, { splitDivergent = false } = {}) { + const order = []; + const seen = new Map(); + for (const [code, byPath] of placeholdersByLocale) { + for (const [path, rawEn] of byPath) { + const en = stripPlaceholder(rawEn); + if (!seen.has(path)) { + seen.set(path, { en, locales: [code], perLocale: new Map([[code, en]]) }); + order.push(path); + } else { + const entry = seen.get(path); + entry.perLocale.set(code, en); + if (entry.en === en) { + if (!entry.locales.includes(code)) entry.locales.push(code); + } else if (!splitDivergent) { + if (!entry.locales.includes(code)) entry.locales.push(code); + } + } + } + } + const tasks = []; + const divergent = []; + for (const path of order) { + const entry = seen.get(path); + const distinct = new Set(entry.perLocale.values()); + if (splitDivergent && distinct.size > 1) { + divergent.push({ path, perLocale: entry.perLocale }); + } else { + tasks.push({ path, en: entry.en, locales: entry.locales }); + } + } + return { tasks, divergent }; +} + /** * Walks a merged tree, finding every leaf that starts with PLACEHOLDER_PREFIX * and replacing it with the translation produced by the backend. @@ -319,7 +395,46 @@ function createLimiter(max) { * cannot be parsed — or whose upstream call fails — is retried one string at * a time, so a bad batch never loses more than the per-string path would. */ +export function collectPlaceholderPaths(merged) { + const paths = []; + function walk(node, prefix) { + if (typeof node === "string") { + if (node.startsWith(PLACEHOLDER_PREFIX)) paths.push(prefix); + return; + } + if (!isPlainObject(node)) return; + for (const [key, value] of Object.entries(node)) { + walk(value, prefix ? `${prefix}.${key}` : key); + } + } + walk(merged, ""); + return paths; +} + +function getByPath(root, dotted) { + return dotted + .split(".") + .reduce((node, key) => (isPlainObject(node) ? node[key] : undefined), root); +} + +function setByPath(root, dotted, value) { + const keys = dotted.split("."); + let node = root; + for (let i = 0; i < keys.length - 1; i++) node = node[keys[i]]; + node[keys[keys.length - 1]] = value; +} + async function translatePlaceholders(merged, localeEntry, backend, concurrency, batchSize = 1) { + return translatePlaceholdersExport(merged, localeEntry, backend, concurrency, batchSize); +} + +export async function translatePlaceholdersExport( + merged, + localeEntry, + backend, + concurrency, + batchSize = 1 +) { const tasks = []; function collect(node, parent, key) { if (typeof node === "string") { @@ -397,28 +512,197 @@ async function translatePlaceholders(merged, localeEntry, backend, concurrency, return { translated: translatedCount, failed }; } -// ----- Main ---------------------------------------------------------------- +/** + * Builds one chunk's translation work without submitting anything: divergent + * paths are kept aside for the single-locale fallback, convergent paths are + * sliced into groups of `batchSize` keys sharing one multi-locale request. + * Pure planning — every network call happens in `runLocaleGroup`, submitted + * through the shared limiter by `runLocaleChunks` (never nested). + * + * @returns {{ divergent: Array<{ path: string, perLocale: Map }>, groups: Array<{ entries: Array<{ id: string, text: string }>, codes: string[], tasks: Array<{ path: string, en: string, locales: string[] }> }> }} + */ +export function planLocaleChunk(chunk, ctx) { + const { mergedByLocale, config, opts } = ctx; + const batchSize = opts.batchSize ?? 1; + const byLocale = new Map(); + for (const code of chunk) { + const merged = mergedByLocale.get(code); + if (!merged) continue; + const byPath = new Map(); + for (const p of collectPlaceholderPaths(merged)) { + byPath.set(p, getByPath(merged, p)); + } + if (byPath.size > 0) byLocale.set(code, byPath); + } + if (byLocale.size === 0) return { divergent: [], groups: [] }; + const known = (code) => config.locales.some((l) => l.code === code); + const { tasks, divergent } = collectChunkTasks(byLocale, { splitDivergent: true }); + const kept = tasks.filter((task) => task.locales.some(known)); + const groups = []; + for (let i = 0; i < kept.length; i += batchSize) { + const slice = kept.slice(i, i + batchSize); + groups.push({ + entries: slice.map((task) => ({ id: task.path, text: task.en })), + codes: [...new Set(slice.flatMap((task) => task.locales))].filter(known), + tasks: slice, + }); + } + return { divergent, groups }; +} -async function processLocale(locale, source, config, opts, backend) { - const localePath = path.join(MESSAGES_DIR, `${locale}.json`); - let target = {}; - if (existsSync(localePath)) { +/** + * Runs one planned group: one multi-locale request, then the single-locale + * fallback for every (locale, path) still untranslated. Returns per-locale + * translated and failed counts so the caller never shares one stats object + * across locales. Counts successful wrapper calls (`multiRequests` / + * `singleRequests`) plus locales whose line could not be read + * (`failedLocales`); the 6-request-vs-66 gateway count stays with the live + * measurement, which also sees retries inside the chat client. + */ +export async function runLocaleGroup(plan, ctx) { + const { mergedByLocale, config, backend, counters } = ctx; + const multi = ctx.multi ?? translateMultiLocaleBatch; + const singleBatch = ctx.singleBatch ?? translateBatch; + const singleString = ctx.singleString ?? translateString; + const perLocale = new Map(); + const failedBy = new Map(); + const bump = (code, n = 1) => perLocale.set(code, (perLocale.get(code) ?? 0) + n); + const fail = (code, n = 1) => failedBy.set(code, (failedBy.get(code) ?? 0) + n); + + for (const { path: p, perLocale: sources } of plan.divergent ?? []) { + for (const [code, en] of sources) { + const localeEntry = config.locales.find((l) => l.code === code); + if (!localeEntry) { + logWarn(`${code}: not present in config/i18n.json — skipping translation`); + failedBy.set(code, (failedBy.get(code) ?? 0) + 1); + continue; + } + try { + const out = await singleBatch([{ id: "s0", text: en }], localeEntry, backend); + setByPath(mergedByLocale.get(code), p, out.get("s0")); + counters.singleRequests++; + bump(code); + } catch { + try { + const value = await singleString(en, localeEntry, backend); + setByPath(mergedByLocale.get(code), p, value); + counters.singleRequests++; + bump(code); + } catch (inner) { + fail(code); + logWarn(`translation failed for ${code}: ${inner.message}`); + } + } + } + } + + for (const group of plan.groups ?? []) { + const localeEntries = group.codes + .map((code) => config.locales.find((l) => l.code === code)) + .filter(Boolean); + const missingCodes = group.codes.filter((code) => !config.locales.some((l) => l.code === code)); + for (const code of missingCodes) { + logWarn(`${code}: not present in config/i18n.json — skipping translation`); + fail(code, group.tasks.filter((task) => task.locales.includes(code)).length); + } + if (localeEntries.length === 0) continue; + const done = new Set(); try { - target = await loadJson(localePath); + const out = await multi(group.entries, localeEntries, backend); + counters.multiRequests++; + for (const [code, values] of out.perLocale) { + for (const task of group.tasks) { + if (!task.locales.includes(code)) continue; + const value = values.get(task.path); + if (typeof value === "string") { + setByPath(mergedByLocale.get(code), task.path, value); + bump(code); + done.add(`${code}::${task.path}`); + } + } + } + for (const code of out.failedLocales ?? []) { + logWarn(`multi-locale line unreadable for ${code} — retrying one locale at a time`); + counters.failedLocales++; + } + // Locales the answer never mentioned fail the same way. + const answered = new Set([...out.perLocale.keys(), ...(out.failedLocales ?? [])]); + for (const entry of localeEntries) { + if (!answered.has(entry.code)) { + logWarn(`multi-locale answer missing ${entry.code} — retrying one locale at a time`); + counters.failedLocales++; + } + } } catch (err) { - logWarn(`${locale}: failed to parse existing JSON — starting fresh (${err.message})`); - target = {}; + logWarn( + `multi-locale batch of ${group.tasks.length} keys x ${localeEntries.length} locales failed (${err.message}) — retrying one locale at a time` + ); + } + // Per-locale fallback for every (locale, path) still untranslated. + const pending = new Map(); + for (const task of group.tasks) { + for (const entry of localeEntries) { + if (!task.locales.includes(entry.code)) continue; + if (done.has(`${entry.code}::${task.path}`)) continue; + if (!pending.has(entry.code)) pending.set(entry.code, []); + pending.get(entry.code).push({ task, entry }); + } + } + for (const [code, items] of pending) { + const entry = items[0].entry; + const batchEntries = items.map(({ task: pendingTask }, i) => ({ + id: `s${i}`, + text: pendingTask.en, + })); + try { + const out = await singleBatch(batchEntries, entry, backend); + counters.singleRequests++; + items.forEach(({ task }, i) => { + setByPath(mergedByLocale.get(code), task.path, out.get(`s${i}`)); + bump(code); + done.add(`${code}::${task.path}`); + }); + } catch { + for (const { task } of items) { + try { + const value = await singleString(task.en, entry, backend); + counters.singleRequests++; + setByPath(mergedByLocale.get(code), task.path, value); + bump(code); + } catch (inner) { + fail(code); + logWarn(`translation failed for ${code}: ${inner.message}`); + } + } + } } - } else { - logWarn(`${locale}: messages file did not exist — creating it`); } + return { perLocale, failedBy }; +} - const { merged, addedPaths } = mergeMissing(source, target); - if (opts.retranslateIdentical && locale !== SOURCE_LOCALE) { - const allow = new Set((await loadJson(resolveCatalog(opts.catalog).allowlistPath)).keys ?? []); - const flagged = markIdenticalAsMissing(merged, source, allow); - logInfo(`${locale}: ${flagged} English leaves flagged for retranslation`); +/** + * Runs every chunk's planned groups through the shared limiter — one limiter + * hop per group, never nested, so more chunks than slots cannot deadlock. + * Small helper kept so tests can drive the exact fan-out `main` uses. + */ +export async function runLocaleChunks(plans, ctx) { + const { limit } = ctx; + const jobs = []; + for (const plan of plans) { + if (plan.divergent.length > 0) { + jobs.push(limit(() => runLocaleGroup({ divergent: plan.divergent, groups: [] }, ctx))); + } + for (const group of plan.groups) { + jobs.push(limit(() => runLocaleGroup({ divergent: [], groups: [group] }, ctx))); + } } + return Promise.all(jobs); +} + +// ----- Main ---------------------------------------------------------------- + +async function processLocale(locale, source, config, opts, backend) { + const { merged, addedPaths } = await loadMergedLocale(locale, source, opts); const placeholderCountBefore = countPlaceholders(merged); let translateStats = { translated: 0, failed: 0 }; @@ -505,25 +789,129 @@ async function main() { backend.concurrency = opts.concurrency ?? Number(process.env.OMNIROUTE_TRANSLATION_CONCURRENCY || 4); const batchInfo = opts.batchSize > 1 ? `, batch=${opts.batchSize}` : ""; + const localesInfo = + (opts.localesPerRequest ?? 1) > 1 ? `, locales-per-request=${opts.localesPerRequest}` : ""; logInfo( - `backend: ${backend.apiUrl} (model=${backend.model}, concurrency=${backend.concurrency}${batchInfo}, timeout=${backend.timeoutMs}ms)` + `backend: ${backend.apiUrl} (model=${backend.model}, concurrency=${backend.concurrency}${batchInfo}${localesInfo}, timeout=${backend.timeoutMs}ms)` ); } const startMs = Date.now(); let totalAdded = 0; let totalTranslated = 0; - for (const locale of targetLocales) { - const result = await processLocale(locale, source, config, opts, backend); - totalAdded += result.addedPaths.length; - totalTranslated += result.translated; + let multiRequests = 0; + let singleRequests = 0; + let failedLocales = 0; + const localesPerRequest = opts.localesPerRequest ?? 1; + if (localesPerRequest > 1 && opts.translateMarkers && !opts.dryRun && backend) { + // Multi-locale path: merge every locale first, fan every planned group + // out through one shared limiter (one hop per group, never nested), + // then write each file sequentially in config order (same order and + // same write rule as the single-locale loop below). + const mergedByLocale = new Map(); + const addedByLocale = new Map(); + for (const locale of targetLocales) { + const loaded = await loadMergedLocale(locale, source, opts); + mergedByLocale.set(locale, loaded.merged); + addedByLocale.set(locale, loaded.addedPaths); + } + const concurrency = + opts.concurrency ?? Number(process.env.OMNIROUTE_TRANSLATION_CONCURRENCY || 4); + const limit = createLimiter(concurrency); + const counters = { multiRequests: 0, singleRequests: 0, failedLocales: 0 }; + const chunkCtx = { config, opts, backend, limit, counters, mergedByLocale }; + const chunks = chunkLocales(targetLocales, localesPerRequest); + // Per-locale translated counts: groups land on disjoint (locale, path) + // sets, so increments below never race on one key. + const translatedByLocale = new Map(targetLocales.map((code) => [code, 0])); + const failedByLocale = new Map(targetLocales.map((code) => [code, 0])); + const plans = chunks.map((chunk) => planLocaleChunk(chunk, chunkCtx)); + const results = await runLocaleChunks(plans, chunkCtx); + for (const stats of results) { + for (const [code, n] of stats.perLocale) { + translatedByLocale.set(code, (translatedByLocale.get(code) ?? 0) + n); + } + for (const [code, n] of stats.failedBy) { + failedByLocale.set(code, (failedByLocale.get(code) ?? 0) + n); + } + } + multiRequests = counters.multiRequests; + singleRequests = counters.singleRequests; + failedLocales = counters.failedLocales; + for (const locale of targetLocales) { + const merged = mergedByLocale.get(locale); + const addedPaths = addedByLocale.get(locale); + const stats = { + translated: translatedByLocale.get(locale) ?? 0, + failed: failedByLocale.get(locale) ?? 0, + }; + totalAdded += addedPaths.length; + totalTranslated += stats.translated; + await writeLocaleResult(locale, merged, addedPaths, stats, opts); + } + } else { + for (const locale of targetLocales) { + const result = await processLocale(locale, source, config, opts, backend); + totalAdded += result.addedPaths.length; + totalTranslated += result.translated; + } } const elapsedSec = ((Date.now() - startMs) / 1000).toFixed(1); + const requestsInfo = + localesPerRequest > 1 && multiRequests + singleRequests > 0 + ? `, requests=${multiRequests + singleRequests} (multi=${multiRequests}, single=${singleRequests}${failedLocales ? `, failed-locales=${failedLocales}` : ""})` + : ""; logInfo( - `summary: locales=${targetLocales.length}, added=${totalAdded}, translated=${totalTranslated}, elapsed=${elapsedSec}s` + `summary: locales=${targetLocales.length}, added=${totalAdded}, translated=${totalTranslated}${localesPerRequest > 1 ? `, locales-per-request=${localesPerRequest}` : ""}${requestsInfo}, elapsed=${elapsedSec}s` ); } +/** + * Loads one locale file, merges the missing keys and flags identical leaves + * for retranslation. Shared by the single-locale `processLocale` and the + * multi-locale fan-out in `main` so both read files the same way. + */ +async function loadMergedLocale(locale, source, opts) { + const localePath = path.join(MESSAGES_DIR, `${locale}.json`); + let target = {}; + if (existsSync(localePath)) { + try { + target = await loadJson(localePath); + } catch (err) { + logWarn(`${locale}: failed to parse existing JSON — starting fresh (${err.message})`); + target = {}; + } + } else { + logWarn(`${locale}: messages file did not exist — creating it`); + } + const { merged, addedPaths } = mergeMissing(source, target); + if (opts.retranslateIdentical && locale !== SOURCE_LOCALE) { + const allow = new Set((await loadJson(resolveCatalog(opts.catalog).allowlistPath)).keys ?? []); + const flagged = markIdenticalAsMissing(merged, source, allow); + logInfo(`${locale}: ${flagged} English leaves flagged for retranslation`); + } + return { merged, addedPaths }; +} + +async function writeLocaleResult(locale, merged, addedPaths, stats, opts) { + const localePath = path.join(MESSAGES_DIR, `${locale}.json`); + const placeholderCountAfter = countPlaceholders(merged); + const summary = `${locale}: +${addedPaths.length} missing keys (${placeholderCountAfter} __MISSING__, ${stats.translated} translated${stats.failed ? `, ${stats.failed} failed` : ""})`; + if (opts.dryRun) { + logInfo(`[DRY] ${summary}`); + return { addedPaths, translated: stats.translated }; + } + const before = existsSync(localePath) ? await fs.readFile(localePath, "utf8") : ""; + const after = JSON.stringify(merged, null, 2) + "\n"; + if (before === after) { + logInfo(`${locale}: already in sync (no changes)`); + return { addedPaths, translated: stats.translated }; + } + await fs.writeFile(localePath, after, "utf8"); + logInfo(summary); + return { addedPaths, translated: stats.translated }; +} + const isDirectRun = import.meta.url === pathToFileURL(process.argv[1]).href; if (isDirectRun) { main().catch((err) => { diff --git a/scripts/quality/validate-release-green.mjs b/scripts/quality/validate-release-green.mjs index a525c8bebb9f..4f223623393e 100644 --- a/scripts/quality/validate-release-green.mjs +++ b/scripts/quality/validate-release-green.mjs @@ -17,6 +17,7 @@ // a real defect; exit 1. // • DRIFT checks (eslint WARNINGS, cognitive-complexity, file-size, cyclomatic // complexity, dead-code, type-coverage, compression-budget, openapi-coverage, +// pricing-freshness, // workflow-lint/zizmor, codeql-ratchet) → ratchet drift accrued across the // cycle is NOT a contributor's fault; it is reported and rebaselined by the // maintainer at release. Drift NEVER changes the exit code, so wiring this as @@ -694,6 +695,15 @@ async function main() { "run", "check:codeql-ratchet", ]); + // Pricing data untouched for 90 days: a clock, not a regression, so it can't belong to a + // PR gate (it would red every PR at once). Reported here, refreshed at release. + await driftCmd( + "pricing-freshness", + "Pricing freshness (90 days)", + npmCmd, + ["run", "check:pricing-freshness"], + "touched within 90 days" + ); // Docs sync + fabricated-docs (strict) is a real-defect gate (invented env vars / // routes, i18n mirror drift) — HARD. @@ -703,9 +713,10 @@ async function main() { ]); if (!QUICK) { - // These are the gates that catch inherited base-red tests from cycle PRs (the fast-path - // PR→release does NOT run unit/vitest/integration per-PR — the v3.8.42 release PR exploded - // with 15 such reds). They run SILENTLY for many minutes; the announce line above + these + // These are the gates that catch inherited base-red tests from cycle PRs (the v3.8.42 + // release PR exploded with 15 such reds). Non-draft code PRs into release/** run the unit + // suite and test:vitest per PR (quality.yml fast-unit, fast-vitest); only integration is + // absent. These gates run SILENTLY for many minutes; the announce line above + these // hard ceilings keep a long-but-healthy run from being mistaken for a hang (the ceiling also // converts a genuine DB-handle hang into a visible failure instead of an infinite block). // The slow suites are INDEPENDENT processes (each self-isolates DATA_DIR) with diff --git a/scripts/release/merge-train.sh b/scripts/release/merge-train.sh index f29a25dc710c..be9c84c5a7f0 100755 --- a/scripts/release/merge-train.sh +++ b/scripts/release/merge-train.sh @@ -319,7 +319,7 @@ if [ "$FAST" = "1" ]; then | grep -E '\.test\.(ts|mjs)$' || true) # Subdirs test:unit actually runs (package.json allowlist) — anything else under # tests/unit// belongs to another runner (e.g. autoCombo -> vitest). - UNIT_SUBDIRS=",api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage," + UNIT_SUBDIRS=",api,auth,authz,build,chatcore,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage," MAIN=() DASH=() SERIAL=() diff --git a/skills/omni-inference/references/endpoints.md b/skills/omni-inference/references/endpoints.md index 5a69063b7e1f..812f7ee81e06 100644 --- a/skills/omni-inference/references/endpoints.md +++ b/skills/omni-inference/references/endpoints.md @@ -440,7 +440,7 @@ curl https://localhost:20128/api/v1/management/proxy-subscriptions/{id} \ Update a proxy subscription -Partial update — only fields present in the body are changed (name/url/mode/ruleProviders/localCoreEndpoint/updateIntervalMinutes/enabled). +Partial update — only fields present in the body are changed (name/url/mode/ruleProviders/localCoreEndpoint/coreConfigPath/coreBinaryPath/updateIntervalMinutes/enabled). ```bash curl -X PATCH https://localhost:20128/api/v1/management/proxy-subscriptions/{id} \ diff --git a/skills/omni-providers/references/endpoints.md b/skills/omni-providers/references/endpoints.md index 3a2b613c3ca3..9365c48f1ab8 100644 --- a/skills/omni-providers/references/endpoints.md +++ b/skills/omni-providers/references/endpoints.md @@ -172,6 +172,8 @@ curl -X POST https://localhost:20128/api/providers/test-batch \ Validate provider credentials +Spawns a fixed ACP summarizer binary to validate credentials — loopback-only (Hard Rules #15/#17, audit #15159 S-01). + ```bash curl -X POST https://localhost:20128/api/providers/validate \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" \ @@ -495,6 +497,8 @@ curl -X POST https://localhost:20128/api/providers/{id}/sync-models \ POST providers › bulk +Reaches the same spawn as /api/providers/validate (webProvidersB.ts) — loopback-only (Hard Rules #15/#17, audit #15159 S-01). + ```bash curl -X POST https://localhost:20128/api/providers/bulk \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" \ @@ -694,6 +698,8 @@ curl https://localhost:20128/api/providers/health-matrix \ POST providers › import +Reaches the same spawn as /api/providers/validate (webProvidersB.ts) — loopback-only (Hard Rules #15/#17, audit #15159 S-01). + ```bash curl -X POST https://localhost:20128/api/providers/import \ -H "Authorization: Bearer $OMNIROUTE_TOKEN" \ diff --git a/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx b/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx index 09446dfb168a..92c80b32fbe4 100644 --- a/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx +++ b/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx @@ -19,6 +19,7 @@ export default function DefaultToolCard({ activeProviders = [], cloudEnabled = false, batchStatus, + catalog = [], }) { const t = useTranslations("cliTools"); const translateOrFallback = useCallback( @@ -232,12 +233,14 @@ export default function DefaultToolCard({ models: getSelectedModels(), model: getSelectedModels()[0], modelLabels: getSelectedModelLabelMap(), + catalog, }), null, 2 ); }, [ baseUrlWithV1, + catalog, getSelectedModels, getSelectedModelLabelMap, replaceVars, @@ -291,6 +294,7 @@ export default function DefaultToolCard({ model: modelValue, models: isMultiModelTool ? getSelectedModels() : undefined, modelLabels: getSelectedModelLabelMap(), + ...(usesOpenCodePreview ? { catalog } : {}), }), }); const data = await res.json(); diff --git a/src/app/(dashboard)/dashboard/cli-code/components/ToolDetailClient.tsx b/src/app/(dashboard)/dashboard/cli-code/components/ToolDetailClient.tsx index fdad0931f865..58c449e3191a 100644 --- a/src/app/(dashboard)/dashboard/cli-code/components/ToolDetailClient.tsx +++ b/src/app/(dashboard)/dashboard/cli-code/components/ToolDetailClient.tsx @@ -243,6 +243,7 @@ export default function ToolDetailClient({ toolId, category }: ToolDetailClientP hasActiveProviders, cloudEnabled, availableModels, + catalog: dynamicModels, }; const renderCard = () => { diff --git a/src/app/(dashboard)/dashboard/compression/exclusions/ExclusionsPanel.tsx b/src/app/(dashboard)/dashboard/compression/exclusions/ExclusionsPanel.tsx index 5c72b8919024..b9d3b306402f 100644 --- a/src/app/(dashboard)/dashboard/compression/exclusions/ExclusionsPanel.tsx +++ b/src/app/(dashboard)/dashboard/compression/exclusions/ExclusionsPanel.tsx @@ -8,7 +8,7 @@ // `normalizeCompressionExclusions` (open-sse/services/compression/exclusions.ts). // Default (empty list) preserves pre-existing behavior exactly. -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslations } from "next-intl"; import Card from "@/shared/components/Card"; import Button from "@/shared/components/Button"; @@ -21,28 +21,38 @@ function parsePatterns(raw: string): string[] { .filter((line) => line.length > 0); } +// A load or save that gets no reply in this time counts as failed, so one stalled request +// cannot leave the panel locked or the save spinning for good. +const REQUEST_TIMEOUT_MS = 15_000; + export default function ExclusionsPanel() { const t = useTranslations("settings"); const [raw, setRaw] = useState(""); const [loading, setLoading] = useState(true); + // Set when the stored list could not be read: the HTTP status, or "" when the request or its + // body failed. The editor stays locked, so Save cannot replace the stored list with an empty one. + const [loadError, setLoadError] = useState(null); const [saving, setSaving] = useState(false); const [status, setStatus] = useState<"" | "saved" | "error">(""); + const savedTimer = useRef>(undefined); useEffect(() => { - fetch("/api/settings/compression") - .then((r) => (r.ok ? r.json() : null)) - .then((data: { exclusions?: string[] } | null) => { - if (data && Array.isArray(data.exclusions)) { - setRaw(data.exclusions.join("\n")); - } + fetch("/api/settings/compression", { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) }) + .then(async (r) => { + const data: { exclusions?: unknown } | null = r.ok ? await r.json() : null; + if (Array.isArray(data?.exclusions)) setRaw(data.exclusions.join("\n")); + else setLoadError(r.ok ? "" : String(r.status)); }) - .catch(() => {}) + .catch(() => setLoadError("")) .finally(() => setLoading(false)); }, []); const patterns = parsePatterns(raw); + const locked = loading || saving || loadError !== null; const save = async () => { + // Each save stops the previous save's timer, so that timer cannot hide this save's message. + clearTimeout(savedTimer.current); setSaving(true); setStatus(""); try { @@ -50,9 +60,10 @@ export default function ExclusionsPanel() { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ exclusions: patterns }), + signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), }); setStatus(res.ok ? "saved" : "error"); - if (res.ok) setTimeout(() => setStatus(""), 2000); + if (res.ok) savedTimer.current = setTimeout(() => setStatus(""), 2000); } catch { setStatus("error"); } finally { @@ -67,31 +78,53 @@ export default function ExclusionsPanel() { data-testid="compression-exclusions-panel" >
+ {loadError !== null && ( +

+ + {t("failedLoadWithStatus", { status: loadError || t("unknownError") })} +

+ )} +
+
+ `); + try { + const matches = [...dom.window.document.querySelectorAll(CHATGPT_COMPOSER_SELECTOR)]; + assert.deepEqual( + matches.map((element) => element.id), + ["current", "prompt-textarea", "legacy"] + ); + } finally { + dom.window.close(); + } +}); diff --git a/tests/unit/check-error-helper-call-scope.test.ts b/tests/unit/check-error-helper-call-scope.test.ts new file mode 100644 index 000000000000..481af338aada --- /dev/null +++ b/tests/unit/check-error-helper-call-scope.test.ts @@ -0,0 +1,156 @@ +// #15159 / G-03 — call-scoped trust for the Rule #12 error-sanitization gate. +// +// THE DEFECT THIS LOCKS DOWN +// -------------------------- +// `findErrorHelperViolations` used to skip an ENTIRE FILE the moment it saw any +// import from a `utils/error` path: +// +// if (ERROR_HELPER_IMPORT.test(source)) continue; // trusts the helper +// +// That is a file-scoped exemption applied to a call-scoped hazard. One +// `import { sanitizeErrorMessage } from "../utils/error.ts"` — needed and +// correct on its own line — permanently exempts every other sink in the file. +// The audit's live E-09 (`deepseek-web.ts`) shipped green through exactly this +// hole: the file imports the sanitizer for one call site while a second, +// file-local `errorResponse` builder forwarded raw `errBody.msg` to the client. +// +// The fix inverts the trust: judge the CALL, not the file. A line that actually +// routes through a sanitizer is clean; a line that forwards a raw caught error +// into a client-facing body is a violation whether or not the file imports +// anything. +// +// This file is deliberately SEPARATE from `check-error-helper.test.ts`, whose +// two "does NOT flag a file that imports utils/error" cases encoded the old +// (wrong) behaviour and had to be inverted rather than extended. +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +// @ts-expect-error — .mjs gate module has no type declarations; runtime shape is known. +import { findErrorHelperViolations } from "../../scripts/check/check-error-helper.mjs"; + +type FileEntry = { path: string; source: string }; +type FindFn = (files: FileEntry[], allowlist: Set) => string[]; +const find = findErrorHelperViolations as FindFn; + +const EMPTY = new Set(); +const P = "open-sse/executors/leaky.ts"; + +function run(source: string, path = P): string[] { + return find([{ path, source } as FileEntry], EMPTY); +} + +// --- RED: the blind spot itself, both import styles --- + +test("G-03: a utils/error import does NOT exempt an unrelated raw leak in the same file", () => { + const src = `import { sanitizeErrorMessage } from "../utils/error.ts"; + export function a(err: Error) { return sanitizeErrorMessage(err); } + export function b(err: Error) { + return new Response(JSON.stringify({ error: { message: err.message } })); + }`; + assert.deepEqual(run(src), [P]); +}); + +test("G-03: same blind spot via the workspace alias import", () => { + const src = `import { buildErrorBody } from "@omniroute/open-sse/utils/error"; + export function a(err: Error) { return buildErrorBody(502, sanitize(err)); } + export function b(err: Error) { + return new Response(JSON.stringify({ error: { message: err.stack } })); + }`; + assert.deepEqual(run(src), [P]); +}); + +test("G-03: the import used by a LATER function does not launder an EARLIER raw sink", () => { + // Order matters for the temptation: the sanitized call sits after the leak, so + // a reviewer scanning top-down sees "sanitizeErrorMessage" and waves it through. + const src = `import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; + export function leaky(err: Error) { + return { error: { message: err.message } }; + } + export function safe(err: Error) { + return sanitizeErrorMessage(err.message); + }`; + assert.deepEqual(run(src), [P]); +}); + +test("G-03: a file-local builder fed a caught err.message is flagged despite the import", () => { + // The E-09 shape, with a CAUGHT error as the source so it sits inside this + // gate's remit (RAW_ERR matches a caught identifier; a parsed upstream body + // field like errBody.msg is a separate, wider class — see the note below). + const src = `import { sanitizeErrorMessage } from "../utils/error.ts"; + function errorResponse(status: number, message: string) { + return Response.json({ error: { message } }, { status }); + } + export function exec(err: unknown) { + return errorResponse(500, err instanceof Error ? err.message : String(err)); + }`; + assert.deepEqual(run(src), [P]); +}); + +// DOCUMENTED LIMITATION, not a claim of coverage: this gate matches a CAUGHT +// error identifier (`err`/`error`/`e` + `.message`/`.stack`) and the specific +// `body.error.message` upstream shape. It does NOT model an arbitrary parsed +// upstream payload (`errBody.msg`, `parsed.detail`, `json.error`) being +// interpolated into a file-local builder — that is the wider "upstream body +// passthrough" class. E-09's real leak was that shape, so closing it took the +// sanitizer INSIDE the file-local builder (deepseek-web.ts:100) rather than +// teaching this gate to parse upstream payloads. Widening it here would trade a +// precise gate for a noisy one; it is tracked separately. +test("G-03: the canonical E-09 shape (sanitizer inside the local builder) is clean", () => { + const src = `import { sanitizeErrorMessage } from "../utils/error.ts"; + function errorResponse(status: number, message: string) { + return Response.json({ error: { message: sanitizeErrorMessage(message) } }, { status }); + } + export function exec(err: unknown) { + return errorResponse(500, err instanceof Error ? err.message : String(err)); + }`; + assert.deepEqual(run(src), []); +}); + +// --- GREEN: the fix must not regress genuinely-clean files --- + +test("G-03: a file whose every error path is sanitized stays clean", () => { + const src = `import { buildErrorBody, sanitizeErrorMessage } from "../utils/error.ts"; + export function build(err: Error) { + return new Response(JSON.stringify(buildErrorBody(502, sanitizeErrorMessage(err.message)))); + }`; + assert.deepEqual(run(src), []); +}); + +test("G-03: an unrelated utils/error import alongside a clean static message stays clean", () => { + // Guards against over-correction: removing the file-level skip must not start + // flagging every file in the repo that merely imports the helper. + const src = `import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; + export function build() { + return new Response(JSON.stringify({ error: { message: "Upstream unavailable" } })); + }`; + assert.deepEqual(run(src), []); +}); + +test("G-03: a sanitized alias assignment is not treated as a tainted local", () => { + const src = `import { sanitizeErrorMessage } from "../utils/error.ts"; + export function build(err: Error) { + const msg = sanitizeErrorMessage(err.message); + return new Response(JSON.stringify({ error: { message: msg } })); + }`; + assert.deepEqual(run(src), []); +}); + +// --- META: make the regression non-reintroducible at the source level --- + +test("G-03 META: the gate never skips a whole file for importing the helper", () => { + // Behavioral tests above could be defeated by re-adding the skip in a form + // they do not exercise. Assert the absence of the mechanism itself, so the + // file-scoped trust cannot come back silently. + const gateUrl = new URL("../../scripts/check/check-error-helper.mjs", import.meta.url); + // Strip comments first: the fix documents the removed pattern in prose, and a + // raw-source regex would match its own explanation. + const code = readFileSync(gateUrl, "utf8") + .replace(/\/\*[\s\S]*?\*\//g, "") + .replace(/^\s*\/\/.*$/gm, ""); + + assert.doesNotMatch( + code, + /if\s*\(\s*\w*ERROR_HELPER_IMPORT\w*\.test\s*\(\s*source\s*\)\s*\)\s*continue/, + "check-error-helper.mjs must not skip a whole file because it imports utils/error" + ); +}); diff --git a/tests/unit/check-error-helper-false-positives-15159.test.ts b/tests/unit/check-error-helper-false-positives-15159.test.ts new file mode 100644 index 000000000000..1dd3e1291bde --- /dev/null +++ b/tests/unit/check-error-helper-false-positives-15159.test.ts @@ -0,0 +1,136 @@ +// Regression guards for audit #15159 / G-03 — false positives found while auditing +// the gate's own frozen backlog. +// +// G-03 replaced a file-level trust skip with call-scoped trust. That is strictly more +// correct in intent, but two shapes of COMPLIANT code were reported as leaks because +// the gate only looked at the single line carrying the message. Both were discovered by +// re-auditing the 23 files frozen in `KNOWN_MISSING_ERROR_HELPER`: two of them were not +// violations at all, and freezing a non-violation is how an allowlist stops meaning +// anything. +// +// A gate that over-reports is not a safe gate — it trains reviewers to ignore it, and it +// buries the real findings. These cases are the difference between "23 pre-existing +// violations" and the honest count. +import assert from "node:assert/strict"; +import { test } from "node:test"; +// @ts-expect-error — .mjs gate module has no type declarations; runtime shape is known. +import { findErrorHelperViolations } from "../../scripts/check/check-error-helper.mjs"; + +type FileEntry = { path: string; source: string }; +type FindFn = (files: FileEntry[], allowlist: Set) => string[]; +const find = findErrorHelperViolations as FindFn; +const EMPTY = new Set(); +const ROUTE = "src/app/api/probe/route.ts"; + +function run(source: string): string[] { + return find([{ path: ROUTE, source } as FileEntry], EMPTY); +} + +test("FP-1: a raw error inside a message-first logger call is an audit row, not a leak", () => { + // Real shape from src/app/api/v1/batches/delete-completed/route.ts:99-103 — the + // client response on the next lines is a static buildErrorBody, so nothing leaked: + // + // log.error("BATCHES", "delete-completed sweep failed", { + // error: err instanceof Error ? { message: err.message, stack: err.stack } : String(err), + // }); + // return NextResponse.json(buildErrorBody(500, "Failed to delete completed batches"), …); + // + // The old anchor required the opener line to END with `{`, which every message-first + // logger fails. Relaxing it without keeping the anchor suppresses EVERY violation + // (measured: 23 -> 0), so the pattern accepts leading string arguments and still + // requires the argument object's `{`. + const src = `export async function sweep(err: unknown) { + log.error("BATCHES", "sweep failed", { + route: "/api/x", + error: err instanceof Error ? { message: err.message, stack: err.stack } : String(err), + }); + return NextResponse.json(buildErrorBody(500, "Failed to delete completed batches"), { + status: 500, + }); + }`; + assert.deepEqual(run(src), []); +}); + +test("FP-1b: the same log shape with a pino-style object argument is also an audit row", () => { + const src = `export function handler(err: unknown) { + reqLogger.error({ err, path: "/x" }, "failed"); + log.warn("upstream", { detail: err.message }); + return ok; + }`; + assert.deepEqual(run(src), []); +}); + +test("FP-2: a sanctioned builder called MULTI-LINE is not a leak on its field lines", () => { + // Real shape from src/app/api/headroom/start/route.ts:36-40. `createErrorResponse` + // lives in src/lib/api/errorResponse.ts — NOT under utils/error — and sanitizes both + // its exports (#15159 E-13). The `message:` line names no builder at all, so + // same-line-only trust reported it. + const src = `import { createErrorResponse } from "@/lib/api/errorResponse"; + export async function start() { + try { + await run(); + } catch (error) { + return createErrorResponse({ + status: 400, + message: error.message, + type: "invalid_request", + details: { code: "NOT_INSTALLED" }, + }); + } + }`; + assert.deepEqual(run(src), []); +}); + +test("FP-2b: the same applies to the open-sse canonical builder across lines", () => { + const src = `import { buildErrorBody } from "@omniroute/open-sse/utils/error"; + export function fail(err: unknown) { + return new Response( + JSON.stringify( + buildErrorBody(502, err instanceof Error ? err.message : "upstream failed") + ), + { status: 502 } + ); + }`; + assert.deepEqual(run(src), []); +}); + +// --- The false-positive fixes must NOT blind the gate --- + +test("FP-3: a multi-line RAW response envelope is still flagged", () => { + // The control for FP-2: `new Response(` is not a sanctioned builder, so the new + // enclosing-builder walk must NOT rescue it. Uses the `${}` interpolation shape the + // gate documents for multi-line envelopes — the plain-ternary variant of this is a + // separate, pre-existing blind spot recorded at the bottom of this file. + const src = `export function fail(err: unknown) { + return new Response( + JSON.stringify({ + error: { + message: \`upstream failed: \${err instanceof Error ? err.message : "?"}\`, + }, + }), + { status: 500 } + ); + }`; + assert.deepEqual(run(src), [ROUTE]); +}); + +test("FP-3b: an UNIMPORTED createErrorResponse name does not buy trust", () => { + // A file can define its own `createErrorResponse` without importing the sanctioned one. + // Trust is per symbol AND per import — this is the E-09 shape wearing a new name. + const src = `function createErrorResponse(status: number, message: string) { + return Response.json({ error: { message } }, { status }); + } + export function fail(err: unknown) { + return createErrorResponse(500, err instanceof Error ? err.message : "failed"); + }`; + assert.deepEqual(run(src), [ROUTE]); +}); + +test("FP-3c: a raw error in an ordinary object literal is still flagged", () => { + // The control for FP-1: relaxing the logger anchor must not excuse a plain result + // object, which is how the 21 remaining frozen violations were found. + const src = `export function build(err: unknown) { + return { target: "a", ok: false, error: (err as Error).message }; + }`; + assert.deepEqual(run(src), [ROUTE]); +}); diff --git a/tests/unit/check-error-helper.test.ts b/tests/unit/check-error-helper.test.ts index 53752b885d07..aba3245f1bee 100644 --- a/tests/unit/check-error-helper.test.ts +++ b/tests/unit/check-error-helper.test.ts @@ -4,6 +4,7 @@ // or helper-importing files) is locked down as a regression guard. import test from "node:test"; import assert from "node:assert/strict"; +import { existsSync } from "node:fs"; // @ts-expect-error — .mjs gate module has no type declarations; runtime shape is known. import { findErrorHelperViolations, @@ -87,17 +88,25 @@ test("flags forwarded upstream body.error.message without sanitize", () => { }); // --- Negative cases: the gate must NOT flag these (conservative, no false positives) --- - -test("does NOT flag a file that imports utils/error (relative)", () => { +// +// G-03 (#15159) INVERTED the next two cases. They used to assert "a file that +// imports utils/error is never flagged", which is exactly the blind spot that let +// the audit's live E-09 (deepseek-web.ts: imports the sanitizer, keeps a raw +// file-local builder) ship green. Their fixtures contain a REAL raw leak, so +// asserting `[]` locked the bug in. Trust is now call-scoped: an import only +// excuses the line that actually routes through it. Do NOT "restore" these — the +// call-scoped contract is asserted in check-error-helper-call-scope.test.ts. + +test("G-03: a file importing only the sanitizer is STILL flagged for a raw leak", () => { const src = `import { sanitizeErrorMessage } from "../utils/error.ts"; function build(err: Error) { return { error: { message: err.message } }; }`; - assert.deepEqual(run(src), []); + assert.deepEqual(run(src), ["open-sse/executors/x.ts"]); }); -test("does NOT flag a file that imports utils/error (workspace alias)", () => { +test("G-03: importing a builder name does not license a separate raw Response body", () => { const src = `import { buildErrorBody } from "@omniroute/open-sse/utils/error"; function build(err: Error) { return new Response(JSON.stringify({ error: { message: \`x \${err.message}\` } })); }`; - assert.deepEqual(run(src), []); + assert.deepEqual(run(src), ["open-sse/executors/x.ts"]); }); test("does NOT flag raw err.message inside a saveCallLog audit row", () => { @@ -159,12 +168,22 @@ test("an allowlisted path is suppressed even when it would otherwise flag", () = assert.deepEqual(find([{ path, source: src } as FileEntry], new Set([path])), []); }); -test("the shipped allowlist freezes exactly the known current violators (all scopes)", () => { - const frozen = [...allowlist].sort(); - // 6A.8: expanded scope includes src/app/api/**/route.ts. - // The original open-sse/executors+handlers violations were resolved before 6A.8 landed, - // so only the newly-discovered API route violations remain frozen. - assert.deepEqual(frozen, []); +test("every shipped allowlist entry is a real on-disk source file", () => { + // G-03 (#15159): this gate now sees 23 pre-existing Rule #12 violations that the + // old file-level skip made invisible, so they are frozen (see the justification + // blocks in check-error-helper.mjs). Freezing is only honest if every entry + // points at a file that exists — a typo here would silently disable the gate + // for a path that later gets "fixed" by rename. + // + // Note the allowlist was NOT empty before this change either in spirit: the + // previous assertion was a hardcoded `[]`, which passed for a set of zero and + // therefore could not detect a stale or bogus entry. The gate's own + // `assertNoStale` in main() catches entries that stop violating; this test + // catches entries that never existed. + assert.ok(allowlist.size > 0, "allowlist should not be empty after G-03"); + for (const entry of allowlist) { + assert.ok(existsSync(new URL(`../../${entry}`, import.meta.url)), `missing file: ${entry}`); + } }); async function assertRouteRemovedFromMissingHelperAllowlist(path: string) { @@ -209,12 +228,18 @@ test("returns multiple violating paths and preserves input order", () => { const files: FileEntry[] = [ { path: "open-sse/executors/a.ts", source: `return { error: { message: err.message } };` }, { + // G-03: `x` is not a canonical builder, so this import buys no trust and + // the raw `err.message` is a violation like any other. path: "open-sse/executors/b.ts", source: `import { x } from "../utils/error.ts"; return { error: err.message };`, }, { path: "open-sse/executors/c.ts", source: `return { error: e.stack };` }, ]; - assert.deepEqual(find(files, EMPTY), ["open-sse/executors/a.ts", "open-sse/executors/c.ts"]); + assert.deepEqual(find(files, EMPTY), [ + "open-sse/executors/a.ts", + "open-sse/executors/b.ts", + "open-sse/executors/c.ts", + ]); }); // --- 6A.8: expanded scope (MCP server + API route.ts) --- diff --git a/tests/unit/check-migration-numbering.test.ts b/tests/unit/check-migration-numbering.test.ts index 0d5c1528969a..ef523c0b7f4b 100644 --- a/tests/unit/check-migration-numbering.test.ts +++ b/tests/unit/check-migration-numbering.test.ts @@ -103,6 +103,12 @@ test("frozen allowlists match the documented legacy and stacked-series gaps", () assert.ok((KNOWN_GAPS as Set).has("026")); assert.ok((KNOWN_GAPS as Set).has("055")); assert.ok((KNOWN_GAPS as Set).has("121")); + // #15564 landed 197 and 198. 199 (#15314), 200, and 201 (#15354) exist and are not gaps. + assert.equal((KNOWN_GAPS as Set).has("197"), false); + assert.equal((KNOWN_GAPS as Set).has("198"), false); + assert.equal((KNOWN_GAPS as Set).has("199"), false); + assert.equal((KNOWN_GAPS as Set).has("200"), false); + assert.equal((KNOWN_GAPS as Set).has("201"), false); assert.equal((KNOWN_GAPS as Set).has("143"), false); assert.equal((KNOWN_GAPS as Set).has("144"), false); assert.equal((KNOWN_GAPS as Set).has("145"), false); diff --git a/tests/unit/claude-code-client-version-fetch.test.ts b/tests/unit/claude-code-client-version-fetch.test.ts new file mode 100644 index 000000000000..cde6ece049a3 --- /dev/null +++ b/tests/unit/claude-code-client-version-fetch.test.ts @@ -0,0 +1,100 @@ +/** + * Registry lookup for the advertised Claude Code client version. + * + * Anthropic gates models on the version OmniRoute sends. The captured pin is + * the floor; a newer dotted triple from the npm `latest` document replaces it + * for 6 hours. A rejected fetch, a non-triple, or an older publish stays on + * the pin. CLAUDE_CODE_CLIENT_VERSION in the environment beats both. + */ +import assert from "node:assert/strict"; +import test from "node:test"; + +const canonical = await import("../../src/shared/constants/claudeCodeClient.ts"); + +const NPM_LATEST = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +async function withEnv( + entries: Record, + fn: () => T | Promise +): Promise { + const previous = new Map(); + for (const [key, value] of Object.entries(entries)) { + previous.set(key, process.env[key]); + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + try { + return await fn(); + } finally { + for (const [key, value] of previous.entries()) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +test.afterEach(() => { + canonical.resetClaudeCodeClientVersionCache(); +}); + +test("a fetched version newer than the pin is returned", async () => { + const urls: string[] = []; + const fetchMock = async (url: string | URL | Request) => { + urls.push(String(url)); + return jsonResponse({ version: "2.1.291" }); + }; + + await withEnv({ CLAUDE_CODE_CLIENT_VERSION: undefined }, async () => { + assert.equal( + await canonical.refreshClaudeCodeClientVersion(fetchMock as typeof fetch), + "2.1.291" + ); + assert.equal(canonical.getClaudeCodeClientVersion(), "2.1.291"); + }); + assert.equal(urls.length, 1); + assert.equal(urls[0], NPM_LATEST); +}); + +test("a fetch that rejects falls back to the pin", async () => { + const fetchMock = async () => { + throw new Error("registry unreachable"); + }; + + await withEnv({ CLAUDE_CODE_CLIENT_VERSION: undefined }, async () => { + assert.equal( + await canonical.refreshClaudeCodeClientVersion(fetchMock as typeof fetch), + canonical.CLAUDE_CODE_CLIENT_VERSION + ); + assert.equal(canonical.getClaudeCodeClientVersion(), canonical.CLAUDE_CODE_CLIENT_VERSION); + }); +}); + +test("the env override beats the fetched value", async () => { + let calls = 0; + const fetchMock = async () => { + calls += 1; + return jsonResponse({ version: "9.9.9" }); + }; + + await withEnv({ CLAUDE_CODE_CLIENT_VERSION: "2.1.259" }, async () => { + assert.equal( + await canonical.refreshClaudeCodeClientVersion(fetchMock as typeof fetch), + "2.1.259" + ); + assert.equal(canonical.getClaudeCodeClientVersion(), "2.1.259"); + }); + assert.equal(calls, 0); +}); diff --git a/tests/unit/claude-fable-5-1.test.ts b/tests/unit/claude-fable-5-1.test.ts index b494db3d3a81..d31291055c7c 100644 --- a/tests/unit/claude-fable-5-1.test.ts +++ b/tests/unit/claude-fable-5-1.test.ts @@ -168,5 +168,6 @@ test("Claude Fable 5.1 pricing matches Anthropic's published rates", () => { inputCostPer1M: 10, outputCostPer1M: 50, isFree: false, + isEstimated: false, }); }); diff --git a/tests/unit/claude-scoped-quota-auth.test.ts b/tests/unit/claude-scoped-quota-auth.test.ts index f87430b1199d..0d0c24ebaa7d 100644 --- a/tests/unit/claude-scoped-quota-auth.test.ts +++ b/tests/unit/claude-scoped-quota-auth.test.ts @@ -380,6 +380,77 @@ test("Claude session preflight allows upstream recovery opt-ins without bypassin ); }); +test("predictive critical weekly warning with quota remaining is not preflight exhaustion", async () => { + const connection = await seedConnection("claude", { + name: "claude-predictive-weekly-warning", + authType: "oauth", + accessToken: "claude-predictive-weekly-access", + refreshToken: "claude-predictive-weekly-refresh", + }); + const now = Date.now(); + const weeklyReset = new Date(now + 3 * 24 * 60 * 60 * 1000).toISOString(); + const sessionReset = new Date(now + 4 * 60 * 60 * 1000).toISOString(); + const { quotas, modelQuotas } = normalizeClaudeUsageQuotas({ + limits: [ + { + kind: "session", + percent: 3, + resets_at: sessionReset, + is_active: false, + severity: "normal", + }, + { + kind: "weekly_all", + percent: 92, + resets_at: weeklyReset, + is_active: true, + severity: "critical", + }, + { + kind: "weekly_scoped", + percent: 0, + resets_at: weeklyReset, + is_active: false, + severity: "normal", + scope: { model: { display_name: "Fable" } }, + }, + ], + }); + quotaCache.setQuotaCache(connection.id, "claude", quotas, modelQuotas); + + assert.equal(quotas["weekly (7d)"].remainingPercentage, 8); + assert.equal( + quotaCache.isQuotaExhaustedForRequest(connection.id, "claude", "claude-opus-5-5"), + false + ); + + const selected = await auth.getProviderCredentials("claude", null, null, "claude-opus-5-5"); + assert.equal(selected?.connectionId, connection.id); + assert.notEqual(selected?.allRateLimited, true); + assert.equal(String(selected?.lastError ?? "").includes("reset after 5m"), false); + + const exhausted = normalizeClaudeUsageQuotas({ + limits: [ + { + kind: "weekly_all", + percent: 100, + resets_at: weeklyReset, + is_active: true, + severity: "critical", + }, + ], + }); + quotaCache.setQuotaCache(connection.id, "claude", exhausted.quotas, exhausted.modelQuotas); + assert.equal( + quotaCache.isQuotaExhaustedForRequest(connection.id, "claude", "claude-opus-5-5"), + true + ); + const blocked = await auth.getProviderCredentials("claude", null, null, "claude-opus-5-5"); + assert.equal(blocked.allRateLimited, true); + assert.equal(String(blocked.lastError ?? "").includes("reset after 5m"), false); + assert.equal(blocked.retryAfter, weeklyReset); +}); + test("active critical current limits route by upstream state without a percent threshold", () => { const connectionId = "claude-current-active-state"; const now = Date.now(); diff --git a/tests/unit/claude-sonnet-5-5.test.ts b/tests/unit/claude-sonnet-5-5.test.ts new file mode 100644 index 000000000000..604b7bbb29e3 --- /dev/null +++ b/tests/unit/claude-sonnet-5-5.test.ts @@ -0,0 +1,98 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { getModelPricing } from "../../open-sse/services/providerCostData.ts"; +import { CLAUDE_SONNET_5_PRICING } from "../../src/shared/constants/pricing/shared-tiers.ts"; +import { + getModelsByProviderId, + supportsClaudeMaxEffort, +} from "../../open-sse/config/providerModels.ts"; +import { modelSupportsContext1mBeta } from "../../open-sse/config/context1m.ts"; +import { getNextFamilyFallback } from "../../open-sse/services/modelFamilyFallback.ts"; +import { getDefaultPricing } from "../../src/shared/constants/pricing.ts"; +import { + getModelSpec, + normalizeForcedToolChoiceForModel, + normalizeThinkingForModel, +} from "../../src/shared/constants/modelSpecs.ts"; + +const MODEL_ID = "claude-sonnet-5-5"; + +test("Claude Sonnet 5.5 rejects disabled thinking and forced tool choice", () => { + const spec = getModelSpec(MODEL_ID); + assert.equal(spec?.contextWindow, 1_000_000); + assert.equal(spec?.maxOutputTokens, 128_000); + assert.equal(spec?.adaptiveThinkingOnly, true); + assert.equal(spec?.rejectsThinkingDisabled, true); + assert.equal(spec?.rejectsForcedToolChoice, true); + + const withoutDisabled = normalizeThinkingForModel( + { model: MODEL_ID, thinking: { type: "disabled" }, marker: true }, + MODEL_ID + ); + assert.equal("thinking" in withoutDisabled, false); + assert.equal(withoutDisabled.marker, true); + + for (const toolChoice of [ + "required", + "any", + { type: "any" }, + { type: "tool", name: "read_file" }, + ]) { + const tools = [{ name: "read_file" }]; + const relaxed = normalizeForcedToolChoiceForModel( + { model: MODEL_ID, tools, tool_choice: toolChoice }, + MODEL_ID + ); + assert.equal("tool_choice" in relaxed, false, JSON.stringify(toolChoice)); + assert.deepEqual(relaxed.tools, tools); + } + + const kept = normalizeForcedToolChoiceForModel( + { model: MODEL_ID, tool_choice: { type: "auto" } }, + MODEL_ID + ); + assert.deepEqual(kept.tool_choice, { type: "auto" }); +}); + +test("Claude Sonnet 5 still accepts disabled thinking", () => { + const spec = getModelSpec("claude-sonnet-5"); + assert.notEqual(spec?.rejectsThinkingDisabled, true); + + const kept = normalizeThinkingForModel( + { model: "claude-sonnet-5", thinking: { type: "disabled" } }, + "claude-sonnet-5" + ); + assert.deepEqual(kept.thinking, { type: "disabled" }); +}); + +test("Claude Sonnet 5.5 caps effort at xhigh", () => { + assert.equal(supportsClaudeMaxEffort(MODEL_ID), false); + assert.equal(supportsClaudeMaxEffort("claude/claude-sonnet-5-5"), false); + assert.equal(supportsClaudeMaxEffort("claude-sonnet-5"), true); +}); + +test("Claude Sonnet 5.5 is priced at the published rate", () => { + const pricing = getModelPricing("claude", MODEL_ID); + assert.equal(pricing?.inputCostPer1M, 2); + assert.equal(pricing?.outputCostPer1M, 10); + assert.equal(CLAUDE_SONNET_5_PRICING.input, 2); + assert.equal(CLAUDE_SONNET_5_PRICING.output, 10); + assert.equal(CLAUDE_SONNET_5_PRICING.cached, 0.2); + assert.equal(CLAUDE_SONNET_5_PRICING.cache_creation, 2.5); + + const anthropic = (getDefaultPricing() as Record>) + .anthropic[MODEL_ID]; + assert.equal(anthropic.input, 2); +}); + +test("Claude Sonnet 5.5 is registered on the first-party providers", () => { + for (const providerId of ["claude", "anthropic", "vertex", "vertex-partner", "github"]) { + const ids = new Set(getModelsByProviderId(providerId).map((entry) => entry.id)); + assert.ok(ids.has(MODEL_ID), `${providerId} must expose ${MODEL_ID}`); + } + const claude = getModelsByProviderId("claude").find((entry) => entry.id === MODEL_ID); + assert.deepEqual(claude?.supportedThinkingEfforts, ["low", "medium", "high", "xhigh"]); + assert.equal(modelSupportsContext1mBeta(MODEL_ID), true); + assert.equal(getNextFamilyFallback(`claude/${MODEL_ID}`, new Set()), "claude/claude-sonnet-5"); +}); diff --git a/tests/unit/cli-client-version-env-12417.test.ts b/tests/unit/cli-client-version-env-12417.test.ts index 263bff3e77c7..8ed92ad5ca3b 100644 --- a/tests/unit/cli-client-version-env-12417.test.ts +++ b/tests/unit/cli-client-version-env-12417.test.ts @@ -71,8 +71,8 @@ test("#12417 getClaudeCodeClientVersion ignores an unsafe env override", async ( }); }); -test("#12417 Copilot pin stays the captured 1.0.88 CLI", () => { - assert.equal(copilot.GITHUB_COPILOT_CLI_VERSION, "1.0.88"); +test("#12417 Copilot pin stays the supported 1.0.91 CLI", () => { + assert.equal(copilot.GITHUB_COPILOT_CLI_VERSION, "1.0.91"); }); test("#12417 getGitHubCopilotCliVersion falls back to the captured pin", async () => { @@ -85,10 +85,7 @@ test("#12417 getGitHubCopilotChatHeaders honors a safe env override", async () = await withEnv({ GITHUB_COPILOT_CLI_VERSION: "1.0.82" }, () => { assert.equal(copilot.getGitHubCopilotCliVersion(), "1.0.82"); const headers = copilot.getGitHubCopilotChatHeaders(); - assert.equal( - headers["user-agent"], - `copilot/1.0.82 (${process.platform}) term/unknown` - ); + assert.equal(headers["user-agent"], `copilot/1.0.82 (${process.platform}) term/unknown`); assert.equal(headers["editor-version"], "copilot/1.0.82"); }); }); @@ -117,7 +114,7 @@ test("#12417 applyFingerprint Copilot UA follows the env, pin const does not", a { model: "gpt-4o", messages: [] } ); assert.equal(result.headers["User-Agent"], "GitHubCopilotChat/1.0.82"); - assert.equal(copilot.GITHUB_COPILOT_CHAT_USER_AGENT, "GitHubCopilotChat/1.0.88"); + assert.equal(copilot.GITHUB_COPILOT_CHAT_USER_AGENT, "GitHubCopilotChat/1.0.91"); }); }); diff --git a/tests/unit/cli-npm-exec-dep0190-15327.test.ts b/tests/unit/cli-npm-exec-dep0190-15327.test.ts new file mode 100644 index 000000000000..dc1b08f9d03e --- /dev/null +++ b/tests/unit/cli-npm-exec-dep0190-15327.test.ts @@ -0,0 +1,212 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; + +import { npmBin, npmExecOptions, npmExecInvocation } from "../../bin/cli/npm-exec.mjs"; +import * as update from "../../bin/cli/commands/update.mjs"; + +// #15327 — on Windows + Node ≥ 24 every `omniroute update` (and the doctor's +// version check) printed: +// +// (node:NNNN) [DEP0190] DeprecationWarning: Passing args to a child process +// with shell option true can lead to security vulnerabilities, as the arguments +// are not escaped, only concatenated. +// +// #11335 had fixed the EINVAL by enabling `shell` on win32 so `npm.cmd` can be +// spawned at all (nodejs/node#52554), but it kept the argv-array call shape. Node's +// `normalizeSpawnArguments` warns on exactly that combination — `options.shell` set +// AND `args.length > 0` — and then builds `${file} ${args.join(" ")}` itself. So +// the two fixes collided: the shell is required for the `.cmd` shim and forbidden +// alongside an argv array. +// +// The fix passes the already-joined command line as `file` with an EMPTY args +// array — byte-identical input to the shell, minus the deprecation — and +// npmExecInvocation refuses to build that string from anything but literals so +// Hard Rule #13 keeps holding now that the shell sees a raw string. +// +// The warning itself is unreachable from a test (Node only emits DEP0190 on +// Node ≥ 22, and only once per process), so the guard asserts the property Node +// keys the warning on: no argv array travels with `shell: true`. + +const VERSION_ARGS = ["view", "omniroute", "version", "--prefer-online"]; + +/** Node's own condition for DEP0190, from lib/child_process.js. */ +function dep0190WouldFire(args: string[], options: Record): boolean { + return Boolean(options.shell) && args.length > 0; +} + +test("#15327 win32 spawns npm with a joined command line and no argv array", () => { + const [file, args, options] = npmExecInvocation("win32", npmBin("win32"), VERSION_ARGS, { + timeoutMs: 15000, + }); + + // The shell still has to be on, or npm.cmd cannot be spawned at all (#11335). + assert.equal(options.shell, true, "win32 must keep the shell so npm.cmd can be spawned"); + assert.equal(options.windowsHide, true); + assert.equal(options.timeout, 15000); + + // …but the argv array that triggers DEP0190 must be empty. + assert.deepEqual(args, [], `win32 must pass no argv array, got ${JSON.stringify(args)}`); + assert.equal( + dep0190WouldFire(args, options), + false, + "spawning with shell:true AND a non-empty args array is what DEP0190 fires on" + ); + assert.equal(file, "npm.cmd view omniroute version --prefer-online"); +}); + +test("#15327 the joined command line carries only the literal tokens it was given", () => { + const [file] = npmExecInvocation("win32", npmBin("win32"), VERSION_ARGS); + + // Every token survives the join verbatim — nothing quoted, escaped or reordered. + for (const token of [npmBin("win32"), ...VERSION_ARGS]) { + assert.ok( + (file as string).split(" ").includes(token), + `${JSON.stringify(token)} must appear verbatim in ${JSON.stringify(file)}` + ); + } + assert.equal( + (file as string).split(" ").length, + 1 + VERSION_ARGS.length, + "the join must add no quoting and drop no token" + ); +}); + +test("#15327 off win32 the argv array is passed through untouched", () => { + for (const platform of ["linux", "darwin"] as const) { + const [file, args, options] = npmExecInvocation(platform, npmBin(platform), VERSION_ARGS, { + timeoutMs: 15000, + }); + + assert.equal(file, npmBin(platform)); + assert.deepEqual(args, VERSION_ARGS, `${platform} must keep the argv array`); + assert.equal(options.shell, false); + // No shell, so nothing can trigger DEP0190 regardless of the argv. + assert.equal(dep0190WouldFire(args, options), false); + } +}); + +test("#15327 npmExecInvocation refuses to join a non-literal token (Hard Rule #13)", () => { + // The shell now receives a raw string, so a token that could change how the shell + // parses the line must fail loudly instead of being concatenated into it. + const unsafe = [ + "omniroute && whoami", + "omniroute; whoami", + "omniroute | more", + "$(whoami)", + "`whoami`", + "a b", + 'omniroute"', + "omniroute'", + "C:\\Users\\John Doe\\pkg", + "^&calc", + ]; + + for (const token of unsafe) { + assert.throws( + () => npmExecInvocation("win32", npmBin("win32"), [token]), + /Hard Rule #13/, + `${JSON.stringify(token)} must not be joined into a shell command line` + ); + } + + // The same tokens are harmless off Windows, where no shell parses the line — the + // new restriction must not leak onto the platforms that never needed one. + for (const token of unsafe) { + const [, args] = npmExecInvocation("linux", npmBin("linux"), [token]); + assert.deepEqual(args, [token], `linux must pass ${JSON.stringify(token)} through`); + } +}); + +test("#15327 npmExecInvocation still accepts the literal shapes the CLI spawns", () => { + const literals = [ + "view", + "changelog", + "install", + "--prefer-online", + "--include=optional", + "--legacy-peer-deps", + "omniroute", + "@omniroute/opencode-plugin-v2", + "3.8.52", + "latest", + "npm.cmd", + ]; + + for (const token of literals) { + const [file, args, options] = npmExecInvocation("win32", "npm.cmd", [token]); + assert.deepEqual(args, []); + assert.equal(file, `npm.cmd ${token}`); + assert.equal(dep0190WouldFire(args, options), false); + } +}); + +test("#15327 the CLI's npm calls all resolve through npmExecInvocation", () => { + const src = fs.readFileSync( + new URL("../../bin/cli/commands/update.mjs", import.meta.url), + "utf8" + ); + + // The defect: an argv array handed to execFile alongside the win32 shell. + assert.equal( + /npmExecOptions\(\s*process\.platform/.test(src), + false, + "update.mjs must not build npm exec options itself — npmExecInvocation owns the win32 shape" + ); + + const binCalls = src.match(/npmBin\(/g) || []; + const invocationCalls = src.match(/npmExecInvocation\(/g) || []; + assert.ok(binCalls.length >= 2, "both the version and changelog lookups must be covered"); + assert.equal( + binCalls.length, + invocationCalls.length, + "every npmBin() call site must go through npmExecInvocation()" + ); +}); + +test("#15327 getLatestVersion passes no argv array on win32", async () => { + let captured: { file: string; args: string[]; options: Record } | null = null; + const fakeExec = async (file: string, args: string[], options: Record) => { + captured = { file, args, options }; + return { stdout: "3.8.53\n" }; + }; + + const latest = await update.getLatestVersion(fakeExec, "win32"); + + assert.equal(latest, "3.8.53"); + assert.ok(captured, "exec must be invoked"); + const call = captured as unknown as { + file: string; + args: string[]; + options: Record; + }; + + assert.deepEqual(call.args, [], "win32 must not pass an argv array alongside shell:true"); + assert.equal(dep0190WouldFire(call.args, call.options), false); + assert.equal(call.file, "npm.cmd view omniroute version --prefer-online"); + // The #4376 query itself is unchanged. + assert.ok(call.file.includes("--prefer-online")); +}); + +test("#15327 getLatestVersion keeps its argv array off win32", async () => { + let captured: { file: string; args: string[] } | null = null; + const fakeExec = async (file: string, args: string[]) => { + captured = { file, args }; + return { stdout: "3.8.53\n" }; + }; + + const latest = await update.getLatestVersion(fakeExec, "linux"); + assert.equal(latest, "3.8.53"); + + const call = captured as unknown as { file: string; args: string[] }; + assert.equal(call.file, "npm"); + assert.deepEqual(call.args, ["view", "omniroute", "version", "--prefer-online"]); +}); + +test("#15327 the shell is still enabled exactly where #11335 needed it", () => { + // Guards the other half of the collision: dropping the argv array must not have + // been "fixed" by turning the shell off, which reintroduces the #11335 EINVAL. + assert.equal(npmExecOptions("win32").shell, true); + assert.equal(npmExecOptions("linux").shell, false); + assert.equal(npmExecOptions("darwin").shell, false); +}); diff --git a/tests/unit/cli-serve-port-in-use-preflight.test.mjs b/tests/unit/cli-serve-port-in-use-preflight.test.mjs index c46ba9cbc8d3..72d4db2238ff 100644 --- a/tests/unit/cli-serve-port-in-use-preflight.test.mjs +++ b/tests/unit/cli-serve-port-in-use-preflight.test.mjs @@ -66,6 +66,52 @@ test("findListeningPids ignores TIME_WAIT and other ports", async () => { assert.deepEqual(pids, [4]); }); +test("findListeningPids asks lsof for LISTEN sockets with the listen address", async () => { + const calls = []; + await findListeningPids(20128, { + platform: "linux", + execFileAsync: async (_cmd, args) => { + calls.push(args); + return { stdout: "" }; + }, + }); + assert.deepEqual( + calls[0], + ["-nP", "-iTCP:20128", "-sTCP:LISTEN"], + "must keep the listen address so a listener on another address is not a conflict" + ); +}); + +test("findListeningPids ignores a listener on another address when binding loopback (#15424)", async () => { + const lsof = "socat 9001 houminxi 6u IPv4 0x1 0t0 TCP 192.168.1.50:20128 (LISTEN)\n"; + const pids = await findListeningPids(20128, { + platform: "linux", + host: "127.0.0.1", + execFileAsync: async () => ({ stdout: lsof }), + }); + assert.deepEqual(pids, [], "a LAN-only forwarder does not block a loopback bind"); +}); + +test("findListeningPids still reports a wildcard listener when binding loopback (#15424)", async () => { + const lsof = "node 19348 houminxi 23u IPv4 0x1 0t0 TCP *:20128 (LISTEN)\n"; + const pids = await findListeningPids(20128, { + platform: "linux", + host: "127.0.0.1", + execFileAsync: async () => ({ stdout: lsof }), + }); + assert.deepEqual(pids, [19348], "0.0.0.0 / * holds every address, including loopback"); +}); + +test("findListeningPids does not treat a longer port as the target port", async () => { + const lsof = "node 1443 houminxi 23u IPv4 0x1 0t0 TCP 127.0.0.1:1443 (LISTEN)\n"; + const pids = await findListeningPids(443, { + platform: "linux", + host: "127.0.0.1", + execFileAsync: async () => ({ stdout: lsof }), + }); + assert.deepEqual(pids, [], "1443 must not match a search for 443"); +}); + test("findListeningPids reports the PID holding the port (posix lsof)", async () => { const pids = await findListeningPids(20128, { platform: "linux", @@ -81,24 +127,21 @@ test("findListeningPids reports the PID holding the port (posix lsof)", async () // "Port 20128 is already in use by PID " while `ss -ltn` showed the // port free — a gateway that could not restart because something else had once // connected to it. Same defect class already fixed in stop.mjs::killByPortPosix. -test( - "findListeningPids asks lsof for LISTEN sockets only, not every client on the port", - async () => { - const calls = []; - await findListeningPids(20128, { - platform: "linux", - execFileAsync: async (_cmd, args) => { - calls.push(args); - return { stdout: "" }; - }, - }); - assert.deepEqual( - calls[0], - ["-nP", "-t", "-iTCP:20128", "-sTCP:LISTEN"], - "must scope discovery to TCP listeners so client sockets cannot fake a conflict" - ); - } -); +test("findListeningPids asks lsof for LISTEN sockets only, not every client on the port", async () => { + const calls = []; + await findListeningPids(20128, { + platform: "linux", + execFileAsync: async (_cmd, args) => { + calls.push(args); + return { stdout: "" }; + }, + }); + assert.deepEqual( + calls[0], + ["-nP", "-iTCP:20128", "-sTCP:LISTEN"], + "must scope discovery to TCP listeners and keep the address" + ); +}); test("findListeningPids treats an empty lsof result as a free port", async () => { const noMatch = Object.assign(new Error("lsof exited with no matches"), { diff --git a/tests/unit/cli-token-delete-route.test.ts b/tests/unit/cli-token-delete-route.test.ts new file mode 100644 index 000000000000..1a418ed1cc43 --- /dev/null +++ b/tests/unit/cli-token-delete-route.test.ts @@ -0,0 +1,75 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// DELETE /api/cli/tokens/:id. Auth is forced on so a retry of an already-revoked +// token is the behavior under test, not an open management endpoint. +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-cli-token-delete-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; +process.env.INITIAL_PASSWORD = "test-pass"; + +const core = await import("../../src/lib/db/core.ts"); +const at = await import("../../src/lib/db/accessTokens.ts"); +const { DELETE } = await import("../../src/app/api/cli/tokens/[id]/route.ts"); + +test.after(() => { + try { + core.resetDbInstance(); + } catch {} + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch {} + delete process.env.INITIAL_PASSWORD; +}); + +function deleteToken(id: string, bearer: string): Promise { + return DELETE( + new Request(`http://localhost:20128/api/cli/tokens/${id}`, { + method: "DELETE", + headers: { authorization: `Bearer ${bearer}` }, + }), + { params: Promise.resolve({ id }) } + ); +} + +test("DELETE of an already-revoked token succeeds so a lost response can be retried", async () => { + const admin = at.createAccessToken({ name: "admin", scope: "admin" }); + const target = at.createAccessToken({ name: "target", scope: "read" }); + + const first = await deleteToken(target.record.id, admin.secret); + assert.equal(first.status, 200); + const firstBody = await first.json(); + assert.equal(firstBody.success, true); + assert.equal(firstBody.alreadyRevoked, false); + + const retry = await deleteToken(target.record.id, admin.secret); + assert.equal(retry.status, 200); + const retryBody = await retry.json(); + assert.equal(retryBody.success, true); + assert.equal(retryBody.alreadyRevoked, true); + assert.equal(retryBody.id, target.record.id); +}); + +test("DELETE of an unknown token is still 404", async () => { + const admin = at.createAccessToken({ name: "admin-miss", scope: "admin" }); + const missing = await deleteToken("tok_missing", admin.secret); + assert.equal(missing.status, 404); +}); + +test("DELETE by a prefix shared by two live tokens refuses both", async () => { + const admin = at.createAccessToken({ name: "admin-amb", scope: "admin" }); + const first = at.createAccessToken({ name: "amb-a", scope: "read" }); + const second = at.createAccessToken({ name: "amb-b", scope: "write" }); + core + .getDbInstance() + .prepare("UPDATE cli_access_tokens SET token_prefix = ? WHERE id = ?") + .run(first.record.tokenPrefix, second.record.id); + + const response = await deleteToken(first.record.tokenPrefix, admin.secret); + assert.equal(response.status, 409); + assert.equal(at.getAccessToken(first.record.id)?.revokedAt, null); + assert.equal(at.getAccessToken(second.record.id)?.revokedAt, null); +}); diff --git a/tests/unit/cli-tools.test.ts b/tests/unit/cli-tools.test.ts index 289785bb1ada..cc6ccc3100a0 100644 --- a/tests/unit/cli-tools.test.ts +++ b/tests/unit/cli-tools.test.ts @@ -119,7 +119,7 @@ test("CLI fingerprint preserves Codex executor User-Agent and maps legacy Copilo // #10952 bumped GITHUB_COPILOT_CLI_VERSION 0.54.0 -> 1.0.88; the fingerprint // pin tracks the advertised upstream CLI version. - assert.equal(copilot.headers["User-Agent"], "GitHubCopilotChat/1.0.88"); + assert.equal(copilot.headers["User-Agent"], "GitHubCopilotChat/1.0.91"); }); test("CLI fingerprint keeps legacy Copilot settings functional without exposing duplicate UI toggles", () => { diff --git a/tests/unit/cli-update-npm-win32-11335.test.ts b/tests/unit/cli-update-npm-win32-11335.test.ts index 3766960fc9e7..3701c8831437 100644 --- a/tests/unit/cli-update-npm-win32-11335.test.ts +++ b/tests/unit/cli-update-npm-win32-11335.test.ts @@ -55,14 +55,23 @@ test("#11335 every npm call in update.mjs routes through the helper", () => { 'update.mjs must not spawn a literal "npm" — use npmBin()' ); - const npmBinCalls = src.match(/npmBin\(\)/g) || []; - const optionCalls = src.match(/npmExecOptions\(/g) || []; + const npmBinCalls = src.match(/npmBin\(/g) || []; + // #15327 moved the win32 exec shape into npmExecInvocation(), which is now the only + // thing allowed to enable the shell — it hands execFile a joined command line with + // an empty argv array, since `shell: true` alongside a non-empty argv array is what + // DEP0190 fires on. + const invocationCalls = src.match(/npmExecInvocation\(/g) || []; assert.equal( npmBinCalls.length, - optionCalls.length, - "each npmBin() call site must pass npmExecOptions() alongside it" + invocationCalls.length, + "each npmBin() call site must go through npmExecInvocation() alongside it" ); assert.ok(npmBinCalls.length >= 2, "both the version and changelog lookups must be covered"); + assert.equal( + /npmExecOptions\(/.test(src), + false, + "update.mjs must not enable the shell itself — npmExecInvocation() owns that shape" + ); }); test("#11335 the shell is only enabled where argv is literal (Hard Rule #13)", () => { @@ -70,9 +79,11 @@ test("#11335 the shell is only enabled where argv is literal (Hard Rule #13)", ( new URL("../../bin/cli/commands/update.mjs", import.meta.url), "utf8" ); - // Both call sites pass a literal argv array; nothing interpolated reaches the - // shell. If that ever changes, this assertion is the thing that should fail. - const argvArrays = src.match(/npmBin\(\),\s*\n?\s*\[[^\]]*\]/g) || []; + // Both call sites pass a literal argv array to npmExecInvocation(); nothing + // interpolated reaches the shell. If that ever changes, this assertion is the thing + // that should fail. npmExecInvocation() additionally refuses to join a token that + // is not a literal — covered in cli-npm-exec-dep0190-15327.test.ts. + const argvArrays = src.match(/npmExecInvocation\([\s\S]*?\[([^\]]*)\]/g) || []; assert.ok(argvArrays.length >= 2); for (const argv of argvArrays) { assert.equal(/\$\{|\+\s*\w|\.\.\./.test(argv), false, `argv must stay literal: ${argv}`); diff --git a/tests/unit/cli/provider-crud.test.ts b/tests/unit/cli/provider-crud.test.ts index c3b71e133e6a..f43413daac89 100644 --- a/tests/unit/cli/provider-crud.test.ts +++ b/tests/unit/cli/provider-crud.test.ts @@ -110,6 +110,27 @@ test("negated --no-credential is treated as a control flag, not the literal stri }); }); +test("allow-no-credential is sent in the add-provider body only when the flag is set", () => { + const allowed = buildProviderPayload( + "ollama-local", + { name: "local", allowNoCredential: true }, + undefined + ); + assert.equal(allowed.allowNoCredential, true); + assert.equal("apiKey" in allowed, false); + + const omitted = buildProviderPayload("ollama-local", { name: "local" }, undefined); + assert.equal("allowNoCredential" in omitted, false); + + const withKey = buildProviderPayload( + "glm", + { name: "work", allowNoCredential: true }, + "provider-secret" + ); + assert.equal(withKey.allowNoCredential, true); + assert.equal(withKey.apiKey, "provider-secret"); +}); + test("provider JSON output redacts raw credentials recursively", () => { const redacted = redactProviderResponse({ connection: { diff --git a/tests/unit/codex-client-version-fetch.test.ts b/tests/unit/codex-client-version-fetch.test.ts new file mode 100644 index 000000000000..80307c02e848 --- /dev/null +++ b/tests/unit/codex-client-version-fetch.test.ts @@ -0,0 +1,81 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { DEFAULT_CODEX_CLIENT_VERSION } from "../../src/shared/constants/codexClient.ts"; +import { + clearCodexClientVersionCache, + getCodexClientVersion, + resolveCodexClientVersion, +} from "../../open-sse/config/codexClient.ts"; + +const CODEX_RELEASE_URL = "https://api.github.com/repos/openai/codex/releases/latest"; + +function releaseFetch(tagName: string): typeof fetch { + return (async (url: string | URL | Request) => { + assert.equal(String(url), CODEX_RELEASE_URL); + return new Response(JSON.stringify({ tag_name: tagName }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }) as typeof fetch; +} + +async function withEnv( + entries: Record, + fn: () => T | Promise +): Promise { + const previous = new Map(); + for (const [key, value] of Object.entries(entries)) { + previous.set(key, process.env[key]); + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + try { + return await fn(); + } finally { + for (const [key, value] of previous.entries()) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +test.afterEach(() => { + clearCodexClientVersionCache(); + delete process.env.CODEX_CLIENT_VERSION; +}); + +test("a fetched Codex release newer than the pin is returned", async () => { + await withEnv({ CODEX_CLIENT_VERSION: undefined }, async () => { + const version = await resolveCodexClientVersion(releaseFetch("rust-v0.160.1")); + assert.equal(version, "0.160.1"); + assert.equal(getCodexClientVersion(), "0.160.1"); + assert.notEqual(version, DEFAULT_CODEX_CLIENT_VERSION); + }); +}); + +test("a rejected fetch falls back to the pinned Codex version", async () => { + await withEnv({ CODEX_CLIENT_VERSION: undefined }, async () => { + const failingFetch = (async () => { + throw new Error("network down"); + }) as typeof fetch; + + const version = await resolveCodexClientVersion(failingFetch); + assert.equal(version, DEFAULT_CODEX_CLIENT_VERSION); + assert.equal(getCodexClientVersion(), DEFAULT_CODEX_CLIENT_VERSION); + }); +}); + +test("CODEX_CLIENT_VERSION beats a newer fetched release", async () => { + await withEnv({ CODEX_CLIENT_VERSION: "0.99.0" }, async () => { + const version = await resolveCodexClientVersion(releaseFetch("rust-v0.160.1")); + assert.equal(version, "0.160.1"); + assert.equal(getCodexClientVersion(), "0.99.0"); + }); +}); diff --git a/tests/unit/codex-connection-edit-6562.test.ts b/tests/unit/codex-connection-edit-6562.test.ts index a1df77a5ac88..dfbf8325bb91 100644 --- a/tests/unit/codex-connection-edit-6562.test.ts +++ b/tests/unit/codex-connection-edit-6562.test.ts @@ -148,13 +148,10 @@ test("PUT /api/providers/[id] persists a Codex OAuth edit when priority already string, unknown >; - // `updateProviderConnection` renormalizes every same-provider connection's - // priority to a dense 1..N sequence whenever `priority` is part of the - // update (`_reorderConnections`, src/lib/db/providers.ts) — this is the - // connection's first successful edit, so it lands at rank 1 (only Codex - // connection in this test). The point of this assertion is that the save - // *persisted* at all instead of 400ing before ever reaching that step. - assert.equal(persisted.priority, 1); + // Saving an unrelated edit must preserve the priority already accepted by + // the edit schema, rather than silently collapsing this account to rank 1. + assert.equal(body.connection.priority, 142); + assert.equal(persisted.priority, 142); const persistedPsd = persisted.providerSpecificData as Record; assert.deepEqual(persistedPsd.requestDefaults, { reasoningEffort: "high" }); }); diff --git a/tests/unit/codex-dead-token-helper.test.ts b/tests/unit/codex-dead-token-helper.test.ts new file mode 100644 index 000000000000..8d460fcccde8 --- /dev/null +++ b/tests/unit/codex-dead-token-helper.test.ts @@ -0,0 +1,75 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { CodexExecutor } from "../../open-sse/executors/codex.ts"; +import { refreshAndUpdateCredentialsWithResolver } from "../../src/lib/usage/providerLimits/credentialRefresh.ts"; +import { isUnrecoverableRefreshError } from "../../open-sse/services/tokenRefresh/shared.ts"; + +test("CodexExecutor dead refresh token keeps stale credentials in the shared helper", async (t) => { + // Dead-token sentinel in the shared helper: stale access token keeps the + // connection, no token raises re-authorize — both base behaviors. The real + // dead-token sentinel comes from the production executor below (fetch + // stubbed with an invalid_grant response); the helper's executor call is + // stubbed to replay that same value so the test exercises the guard branch + // without the network lane or the database (the stub returns before any + // write; persistence is covered by the provider-limits suites). Proof that + // the stub equals production: the sentinel passes the shared classifier + // and the stubbed value is the executor's own return object, not a literal. + // Stub-vs-real equivalence: the stub replays the exact object the real + // CodexExecutor returned above (same reference), so the helper receives + // identical input; the only stubbed seam is how the value arrives (direct + // return instead of network fetch plus serializeRefresh lane), which cannot + // change the guard outcome — the guard reads only the value. + // QuotaAutoPing path: createDefaultQuotaAutoPingDeps wires the same helper + // with the real Codex loader (quotaAutoPing.ts:155-156), and + // refreshConnectionForPing (quotaAutoPing.ts:374-392) returns + // refreshed.connection on success rows and null on the 401 row — the two + // rows above are exactly those two outcomes, so the ping keeps the stale + // token for usage reads instead of persisting the sentinel. + const executor = new CodexExecutor(); + const originalFetch = globalThis.fetch; + globalThis.fetch = async () => + new Response( + JSON.stringify({ error: "invalid_grant", error_description: "Refresh token expired" }), + { status: 400, headers: { "Content-Type": "application/json" } } + ); + t.after(() => { + globalThis.fetch = originalFetch; + }); + const sentinel = await executor.refreshCredentials({ refreshToken: "dead-token" }, null); + assert.equal(isUnrecoverableRefreshError(sentinel), true); + + const resolveExecutor = async () => + ({ + needsRefresh: () => true, + refreshCredentials: async () => sentinel, + }) as unknown as { + needsRefresh: () => boolean; + refreshCredentials: (c: unknown, l: null) => Promise; + }; + const opts = { allowRotatingRefresh: true, force: true }; + const connWithToken = { + id: "codex-dead-1", + provider: "codex", + accessToken: "stale-access-token", + refreshToken: "dead-refresh-token", + }; + + const withToken = await refreshAndUpdateCredentialsWithResolver( + connWithToken, + resolveExecutor, + opts + ); + assert.equal(withToken.refreshed, false); + assert.equal(withToken.connection.accessToken, "stale-access-token"); + + const connWithoutToken = { + id: "codex-dead-2", + provider: "codex", + refreshToken: "dead-refresh-token", + }; + await assert.rejects( + () => refreshAndUpdateCredentialsWithResolver(connWithoutToken, resolveExecutor, opts), + /Please re-authorize the connection/ + ); +}); diff --git a/tests/unit/codex-discovery-github-catalog-append.test.ts b/tests/unit/codex-discovery-github-catalog-append.test.ts new file mode 100644 index 000000000000..0c9f08b660e2 --- /dev/null +++ b/tests/unit/codex-discovery-github-catalog-append.test.ts @@ -0,0 +1,61 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + enrichCodexModelsFromGithubCatalog, + type CodexDiscoveryModel, +} from "@/app/api/providers/[id]/models/discovery/codex"; + +test("enrichCodexModelsFromGithubCatalog enriches live models with github catalog metadata", () => { + const liveModels: CodexDiscoveryModel[] = [ + { + id: "gpt-6-sol", + name: "gpt-6-sol", + display_name: "GPT-6 Sol", + context_length: 128000, + }, + ]; + + const githubCatalog: CodexDiscoveryModel[] = [ + { + id: "gpt-6-sol", + name: "gpt-6-sol", + display_name: "GPT-6 Sol (Official)", + context_length: 872000, + capabilities: { vision: true, reasoning: true }, + }, + ]; + + const result = enrichCodexModelsFromGithubCatalog(liveModels, githubCatalog); + assert.equal(result.length, 1); + assert.equal(result[0].id, "gpt-6-sol"); + assert.equal(result[0].display_name, "GPT-6 Sol"); // live model overrides display_name if present + assert.equal(result[0].context_length, 128000); + assert.equal(result[0].capabilities?.vision, true); +}); + +test("enrichCodexModelsFromGithubCatalog returns the GitHub catalog when there is no live list (#15525)", () => { + const liveModels: CodexDiscoveryModel[] = []; + + const githubCatalog: CodexDiscoveryModel[] = [ + { + id: "gpt-6-sol", + name: "gpt-6-sol", + display_name: "GPT-6 Sol (Official)", + }, + { + id: "gpt-6.1-sol", + name: "gpt-6.1-sol", + display_name: "GPT-6.1 Sol", + context_length: 872000, + capabilities: { vision: true, reasoning: true }, + }, + ]; + + const result = enrichCodexModelsFromGithubCatalog(liveModels, githubCatalog); + assert.equal(result.length, 2); + assert.equal(result[0].id, "gpt-6-sol"); + assert.equal(result[1].id, "gpt-6.1-sol"); + assert.equal(result[1].display_name, "GPT-6.1 Sol"); + assert.equal(result[1].context_length, 872000); + assert.equal(result[1].capabilities?.vision, true); +}); diff --git a/tests/unit/codex-quota-optout.test.ts b/tests/unit/codex-quota-optout.test.ts new file mode 100644 index 000000000000..f734ae91e435 --- /dev/null +++ b/tests/unit/codex-quota-optout.test.ts @@ -0,0 +1,121 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const data = fs.mkdtempSync(path.join(os.tmpdir(), "omni-codex-quota-optout-")); +process.env.DATA_DIR = data; +process.env.APP_LOG_TO_FILE = "false"; + +const core = await import("../../src/lib/db/core.ts"); +const providers = await import("../../src/lib/db/providers.ts"); +const quotaCache = await import("../../src/domain/quotaCache.ts"); +const { evaluateQuotaCutoff, registerQuotaFetcher } = + await import("../../open-sse/services/quotaPreflight.ts"); +const { getProviderCredentialsWithQuotaPreflight } = + await import("../../src/sse/services/auth.ts"); +const { invalidateDbCache } = await import("../../src/lib/db/readCache.ts"); + +const optedOut = { limitPolicy: { enabled: false }, quotaPreflightEnabled: false }; +const resetAt = new Date(Date.now() + 4 * 24 * 60 * 60 * 1000).toISOString(); +const exhausted = { + used: 100, total: 100, percentUsed: 1, resetAt, limitReached: true, + windows: { session: { percentUsed: 1, resetAt } }, +}; +let connectionId = ""; + +test.before(async () => { + const connection = await providers.createProviderConnection({ + provider: "codex", authType: "oauth", name: "quota-optout-fixture", + accessToken: "fixture-access", isActive: true, testStatus: "active", + providerSpecificData: {}, + }); + assert.ok(typeof connection.id === "string"); + connectionId = connection.id; +}); + +test.after(async () => { + await (await import("../../src/lib/usage/callLogs.ts")).closeCallLogSaves(5_000); + core.resetDbInstance(); + fs.rmSync(data, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}, { timeout: 6_000 }); + +test("Codex combined opt-out preserves observations and bypasses cached/live quota cutoffs", + { timeout: 5_000 }, async () => { + quotaCache.setQuotaCache(connectionId, "codex", { + session: { remainingPercentage: 0, resetAt }, + }); + const previous = structuredClone(quotaCache.getQuotaCache(connectionId)); + assert.equal(quotaCache.isQuotaExhaustedForRequest(connectionId, "codex", "gpt-6.1-sol"), true); + assert.equal(evaluateQuotaCutoff(exhausted, undefined, { provider: "codex" }).proceed, false); + + await providers.updateProviderConnection(connectionId, { providerSpecificData: optedOut }); + invalidateDbCache("connections", connectionId); + assert.equal( + quotaCache.isQuotaExhaustedForRequest(connectionId, "codex", "gpt-6.1-sol", optedOut), + false + ); + assert.equal(evaluateQuotaCutoff(exhausted, undefined, { + provider: "codex", providerSpecificData: optedOut, + }).proceed, true); + assert.deepEqual(quotaCache.getQuotaCache(connectionId), previous); + + registerQuotaFetcher("codex", async () => { + assert.fail("Explicit opt-out must not perform an upstream quota fetch"); + }); + const selected = await getProviderCredentialsWithQuotaPreflight( + "codex", null, [connectionId], "gpt-6.1-sol" + ); + assert.ok(selected && "connectionId" in selected); + assert.equal(selected.connectionId, connectionId); + }); + +test("Partial, malformed and other-provider settings retain exhausted-window filtering", + { timeout: 1_000 }, () => { + for (const settings of [ + {}, { quotaPreflightEnabled: false }, { limitPolicy: { enabled: false } }, + { limitPolicy: { enabled: "false" }, quotaPreflightEnabled: false }, + { limitPolicy: { enabled: false }, quotaPreflightEnabled: "false" }, + ]) { + assert.equal( + quotaCache.isQuotaExhaustedForRequest(connectionId, "codex", "gpt-6.1-sol", settings), + true + ); + assert.equal(evaluateQuotaCutoff(exhausted, undefined, { + provider: "codex", providerSpecificData: settings, + }).proceed, false); + } + assert.equal(evaluateQuotaCutoff(exhausted, undefined, { + provider: "openai", providerSpecificData: optedOut, + }).proceed, false); + quotaCache.setQuotaCache("other-provider", "openai", { + session: { remainingPercentage: 0, resetAt }, + }); + assert.equal( + quotaCache.isQuotaExhaustedForRequest("other-provider", "openai", "gpt-6.1-sol", optedOut), + true + ); + }); + +test("Restoring filtering and terminal account protection remain effective", + { timeout: 5_000 }, async () => { + await providers.updateProviderConnection(connectionId, { + providerSpecificData: {}, testStatus: "active", + }); + invalidateDbCache("connections", connectionId); + const restored = await getProviderCredentialsWithQuotaPreflight( + "codex", null, [connectionId], "gpt-6.1-sol" + ); + assert.ok(restored && "allRateLimited" in restored); + assert.equal(restored.allRateLimited, true); + + await providers.updateProviderConnection(connectionId, { + providerSpecificData: optedOut, testStatus: "banned", + }); + invalidateDbCache("connections", connectionId); + const banned = await getProviderCredentialsWithQuotaPreflight( + "codex", null, [connectionId], "gpt-6.1-sol" + ); + assert.ok(!banned || !("connectionId" in banned) || banned.connectionId !== connectionId); + }); diff --git a/tests/unit/codex-usage-status-throttle-15172.test.ts b/tests/unit/codex-usage-status-throttle-15172.test.ts new file mode 100644 index 000000000000..623212241480 --- /dev/null +++ b/tests/unit/codex-usage-status-throttle-15172.test.ts @@ -0,0 +1,135 @@ +/** + * #15172 — Codex usage reads that reach /wham/usage through getUsageForProvider + * (the /me/status fan-out and the quota-cache background refresh both do) must + * take the same min-interval gate as the dedicated quota fetchers. Auto-Ping + * already acquires that gate itself, so the extra wait on its path is at most + * one interval. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { getUsageForProvider } from "../../open-sse/services/usage.ts"; +import { setCodexUsageFetchForTests } from "../../open-sse/services/usage/codex.ts"; +import { + MinIntervalThrottle, + resetQuotaFetchThrottle, + setQuotaFetchThrottleForTests, + type ThrottleClock, +} from "../../open-sse/services/quotaFetchThrottle.ts"; +import { + createQuotaAutoPingState, + runQuotaAutoPingTick, +} from "../../src/lib/services/quotaAutoPing.ts"; + +const INTERVAL_MS = 250; + +class FakeClock implements ThrottleClock { + t = 1_000; + now = () => this.t; + sleep = async (ms: number) => { + this.t += ms; + }; +} + +function installSharedThrottle(clock: FakeClock | null) { + const shared = new MinIntervalThrottle({ + minIntervalMs: INTERVAL_MS, + jitterMs: 0, + ...(clock ? { clock } : {}), + }); + setQuotaFetchThrottleForTests(shared); + return () => resetQuotaFetchThrottle(); +} + +function installUsageFetch(clock: FakeClock | null) { + const starts: number[] = []; + setCodexUsageFetchForTests(async () => { + starts.push(clock ? clock.now() : Date.now()); + return new Response( + JSON.stringify({ + plan_type: "plus", + rate_limit: { + primary_window: { used_percent: 10 }, + secondary_window: { used_percent: 20 }, + }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + }); + return { + starts, + restore: () => setCodexUsageFetchForTests(null), + }; +} + +test("#15172 concurrent Codex usage fetches start at least one interval apart", async () => { + const restoreThrottle = installSharedThrottle(null); + const { starts, restore } = installUsageFetch(null); + + try { + const connections = ["a", "b", "c", "d"].map((id) => ({ + id, + provider: "codex", + accessToken: `token-${id}`, + providerSpecificData: { workspaceId: `ws-${id}` }, + })); + await Promise.all(connections.map((connection) => getUsageForProvider(connection))); + + assert.equal(starts.length, 4); + for (let i = 1; i < starts.length; i++) { + const gap = starts[i] - starts[i - 1]; + assert.ok( + gap >= INTERVAL_MS - 30, + `gap ${gap}ms between fetch ${i - 1} and ${i} is below one interval` + ); + } + } finally { + restore(); + restoreThrottle(); + } +}); + +test("#15172 Auto-Ping's own gate adds at most one extra interval", async () => { + const clock = new FakeClock(); + const restoreThrottle = installSharedThrottle(clock); + const { starts, restore } = installUsageFetch(clock); + + const deps = { + getSettings: async () => ({ + codexAutoPing: { connections: { "codex-1": true, "codex-2": true } }, + }), + getProviderConnections: async () => [ + { id: "codex-1", provider: "codex", authType: "oauth", accessToken: "token-1" }, + { id: "codex-2", provider: "codex", authType: "oauth", accessToken: "token-2" }, + ], + updateProviderConnection: async () => null, + refreshAndUpdateCredentials: async (connection: unknown) => ({ connection }), + getCodexUsage: (accessToken?: string, providerSpecificData?: Record) => + getUsageForProvider({ + provider: "codex", + accessToken, + providerSpecificData, + }), + throttleQuotaFetch: () => + new MinIntervalThrottle({ minIntervalMs: INTERVAL_MS, jitterMs: 0, clock }).acquire(), + resolveProxyForConnection: async () => ({ proxy: null }), + runWithProxyContext: async (_proxy: unknown, callback: () => Promise) => callback(), + getExecutor: () => ({ + execute: async () => ({ response: { ok: true, text: async () => "" } }), + }), + canExecuteProvider: () => true, + isConnectionUnavailableToAuxiliaryActivity: async () => false, + resolvePingModel: async () => "gpt-5-codex", + }; + + try { + await runQuotaAutoPingTick(deps as never, createQuotaAutoPingState(), () => clock.now()); + assert.equal(starts.length, 2); + const spread = starts[1] - starts[0]; + assert.ok(spread >= INTERVAL_MS, `spread ${spread}ms is below one interval`); + assert.ok(spread <= INTERVAL_MS * 2, `spread ${spread}ms is more than one extra interval`); + } finally { + restore(); + restoreThrottle(); + } +}); diff --git a/tests/unit/combo-bootstrap-latency.test.ts b/tests/unit/combo-bootstrap-latency.test.ts index 0499d7c36a59..569cee89914b 100644 --- a/tests/unit/combo-bootstrap-latency.test.ts +++ b/tests/unit/combo-bootstrap-latency.test.ts @@ -6,10 +6,12 @@ import assert from "node:assert/strict"; import { poolMedianP95Ms, bootstrapMs, + bootstrapSourceFromTable, shouldWarnBootstrap, resetBootstrapCounters, - bootstrapLatencyHits, - bootstrapLatencyTotal, + formatBootstrapWarning, + maybeWarnBootstrapDominant, + bootstrapSourceCounts, } from "../../open-sse/services/combo.ts"; test("median ignores corrupt entries", () => { @@ -28,22 +30,34 @@ test("empty stats yield undefined (caller falls back to 1500, no cold-start alar test("counters start at zero", () => { resetBootstrapCounters(); - assert.equal(bootstrapLatencyHits, 0); - assert.equal(bootstrapLatencyTotal, 0); + assert.equal(bootstrapSourceCounts.table, 0); + assert.equal(bootstrapSourceCounts["pool-median"], 0); + assert.equal(bootstrapSourceCounts.constant, 0); }); -test("table hit bumps Total but not Hits; miss bumps both", () => { +test("table hit bumps the table counter; misses bump pool-median or constant", () => { resetBootstrapCounters(); assert.equal(bootstrapMs("gpt-4o-mini", 9999), 2764); // table hit - assert.equal(bootstrapLatencyTotal, 1); - assert.equal(bootstrapLatencyHits, 0); + assert.equal(bootstrapSourceCounts.table, 1); + assert.equal(bootstrapSourceCounts["pool-median"], 0); + assert.equal(bootstrapSourceCounts.constant, 0); assert.equal(bootstrapMs("some-new-model-xyz", 1234), 1234); // miss -> median - assert.equal(bootstrapLatencyTotal, 2); - assert.equal(bootstrapLatencyHits, 1); + assert.equal(bootstrapSourceCounts["pool-median"], 1); assert.equal(bootstrapMs("another-new-model", undefined), 1500); // miss, no median + assert.equal(bootstrapSourceCounts.constant, 1); resetBootstrapCounters(); }); +test("bootstrap source traces table, pool median, and constant fallbacks", () => { + // Same four provenance cases as the former model-based wrapper test, + // expressed through the authority with the values the shared table yields: + // gpt-4o-mini hits the table (2764), unknown models miss it (undefined). + assert.equal(bootstrapSourceFromTable(2764, 9999), "table"); // gpt-4o-mini + assert.equal(bootstrapSourceFromTable(undefined, 1234), "pool-median"); // some-new-model-xyz + assert.equal(bootstrapSourceFromTable(undefined, undefined), "constant"); // another-new-model, no median + assert.equal(bootstrapSourceFromTable(undefined, undefined), "constant"); // empty model, no median +}); + test("warn gate is pure: cold start and low ratio stay silent, dominant warns once", () => { assert.equal(shouldWarnBootstrap(10, 10, false, 9999, 0), false); // cold start assert.equal(shouldWarnBootstrap(2, 10, true, 9999, 0), false); // ratio <= 0.3 @@ -51,3 +65,44 @@ test("warn gate is pure: cold start and low ratio stay silent, dominant warns on assert.equal(shouldWarnBootstrap(5, 10, true, 3600_001, 0), true); // window elapsed assert.equal(shouldWarnBootstrap(5, 10, true, 9999, 9000), false); // throttled 1h }); + +test("warning carries a per-source breakdown, never a single last-call source", () => { + resetBootstrapCounters(); + bootstrapMs("some-new-model-xyz", 1234); // pool-median estimate + bootstrapMs("another-new-model", undefined); // constant estimate + bootstrapMs("gpt-4o-mini", 9999); // table hit last: must not claim "source: table" + const { table, "pool-median": pm, constant } = bootstrapSourceCounts; + const hits = pm + constant; + const total = table + pm + constant; + assert.equal(shouldWarnBootstrap(hits, total, true, 3600_001, 0), true); + const warning = formatBootstrapWarning(); + assert.ok(warning.includes(`pool-median: ${pm}`)); + assert.ok(warning.includes(`constant: ${constant}`)); + assert.ok(!warning.includes("source: table")); + assert.ok(warning.includes("—")); + resetBootstrapCounters(); +}); + +test("warning path emits the breakdown through the throttled warn helper", () => { + resetBootstrapCounters(); + bootstrapMs("some-new-model-xyz", 1234); + bootstrapMs("another-new-model", undefined); + bootstrapMs("gpt-4o-mini", 9999); // table hit last + const lines: string[] = []; + const originalWarn = console.warn; + console.warn = (...args: unknown[]) => { + lines.push(args.map(String).join(" ")); + }; + try { + maybeWarnBootstrapDominant(true); + assert.equal(lines.length, 1); + assert.ok(lines[0].includes("pool-median: 1")); + assert.ok(lines[0].includes("constant: 1")); + assert.ok(!lines[0].includes("source: table")); + maybeWarnBootstrapDominant(true); // throttled within the hour + assert.equal(lines.length, 1); + } finally { + console.warn = originalWarn; + resetBootstrapCounters(); + } +}); diff --git a/tests/unit/combo-dispatch-prelude.test.ts b/tests/unit/combo-dispatch-prelude.test.ts index c4bf2afea3c6..bef9981d736c 100644 --- a/tests/unit/combo-dispatch-prelude.test.ts +++ b/tests/unit/combo-dispatch-prelude.test.ts @@ -457,6 +457,90 @@ test("tryPinnedModelDispatch: serves the pinned response when the pin is healthy ); }); +test("tryPinnedModelDispatch: accepts an official empty turn for a pinned Anthropic target", async () => { + const connection = await createProviderConnection({ + provider: "anthropic", + authType: "apikey", + name: "pinned-anthropic-empty-turn", + isActive: true, + apiKey: "sk-test-pinned-empty-turn", + }); + assert.ok(connection && typeof connection.id === "string"); + const ctx = setup({ + name: "pinned-anthropic-combo", + strategy: "priority", + models: [{ model: "claude-sonnet-4-6", providerId: "anthropic" }], + config: {}, + }); + const events = [ + 'event: message_start\ndata: {"type":"message_start","message":{"content":[]}}', + 'event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"end_turn"}}', + 'event: message_stop\ndata: {"type":"message_stop"}', + "", + ]; + const response = new Response(events.join("\n\n"), { + status: 200, + headers: { + "Content-Type": "text/event-stream", + "X-OmniRoute-Selected-Connection-Id": connection.id, + }, + }); + const res = await tryPinnedModelDispatch({ + body: { ...ctx.body, stream: true }, + combo: ctx.combo, + pinnedModel: "anthropic/claude-sonnet-4-6", + allCombos: [ctx.combo], + config: ctx.config, + clientRequestedStream: true, + handleSingleModelWithTimeout: async () => response, + log: ctx.log, + }); + assert.equal(res, response, "the official empty turn must not trigger combo fallback"); +}); + +test("tryPinnedModelDispatch: accepts an official empty turn for an unprefixed alias", async () => { + const connection = await createProviderConnection({ + provider: "anthropic", + authType: "apikey", + name: "pinned-anthropic-alias-empty-turn", + isActive: true, + apiKey: "sk-test-pinned-alias-empty-turn", + }); + assert.ok(connection && typeof connection.id === "string"); + const ctx = setup({ + name: "pinned-alias-combo", + strategy: "priority", + models: [{ model: "claude-alias" }], + config: {}, + }); + const response = new Response( + [ + 'event: message_start\ndata: {"type":"message_start","message":{"content":[]}}', + 'event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"end_turn"}}', + 'event: message_stop\ndata: {"type":"message_stop"}', + "", + ].join("\n\n"), + { + status: 200, + headers: { + "Content-Type": "text/event-stream", + "X-OmniRoute-Selected-Connection-Id": connection.id, + }, + } + ); + const res = await tryPinnedModelDispatch({ + body: { ...ctx.body, stream: true }, + combo: ctx.combo, + pinnedModel: "claude-alias", + allCombos: [ctx.combo], + config: ctx.config, + clientRequestedStream: true, + handleSingleModelWithTimeout: async () => response, + log: ctx.log, + }); + assert.equal(res, response, "the selected official connection must permit the alias empty turn"); +}); + test("tryPinnedModelDispatch: expands the combo system_message template on the pinned path (#5501)", async () => { const ctx = setup({ name: "pinned-combo", diff --git a/tests/unit/combo-empty-turn-trust.test.ts b/tests/unit/combo-empty-turn-trust.test.ts new file mode 100644 index 000000000000..2c45e69935de --- /dev/null +++ b/tests/unit/combo-empty-turn-trust.test.ts @@ -0,0 +1,96 @@ +/** + * #15586 — the combo empty-turn exemption is granted only when the connection that + * actually served the response is a first-party Anthropic one. Provider IDs alone are + * not enough: an `anthropic` connection may point at a custom/third-party base URL. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-empty-turn-trust-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const { isTrustedEmptyTurn } = await import("../../open-sse/services/combo/emptyTurnTrust.ts"); +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + process.env.DATA_DIR = ORIGINAL_DATA_DIR; +}); + +function responseFrom(connectionId?: string): Response { + const headers = new Headers({ "content-type": "text/event-stream" }); + if (connectionId) headers.set("X-OmniRoute-Selected-Connection-Id", connectionId); + return new Response("", { status: 200, headers }); +} + +async function createConnection(provider: string, baseUrl?: string): Promise { + const connection = (await providersDb.createProviderConnection({ + provider, + authType: "apikey", + name: `${provider}-${baseUrl ?? "default"}`, + apiKey: `sk-test-${crypto.randomUUID()}`, + ...(baseUrl ? { providerSpecificData: { baseUrl } } : {}), + })) as { id: string }; + return connection.id; +} + +test("official anthropic connection (default base URL) is trusted", async () => { + const id = await createConnection("anthropic"); + assert.equal(await isTrustedEmptyTurn("anthropic", responseFrom(id)), true); +}); + +test("anthropic connection explicitly pointed at api.anthropic.com is trusted", async () => { + const id = await createConnection("anthropic", "https://api.anthropic.com/v1"); + assert.equal(await isTrustedEmptyTurn("anthropic", responseFrom(id)), true); +}); + +test("anthropic connection with a custom third-party base URL is not trusted", async () => { + const id = await createConnection("anthropic", "https://gateway.example.com/v1"); + assert.equal(await isTrustedEmptyTurn("anthropic", responseFrom(id)), false); +}); + +test("claude connection uses the same selected-connection host check", async () => { + const id = await createConnection("claude"); + assert.equal(await isTrustedEmptyTurn("claude", responseFrom(id)), true); +}); + +test("fallback pinned connection ID is used when the header is absent", async () => { + const id = await createConnection("anthropic"); + assert.equal(await isTrustedEmptyTurn("anthropic", responseFrom(), id), true); +}); + +test("unknown selected connection fails closed", async () => { + assert.equal(await isTrustedEmptyTurn("anthropic", responseFrom()), false); + assert.equal(await isTrustedEmptyTurn("anthropic", responseFrom("missing-connection")), false); +}); + +test("connection belonging to a different provider is not trusted", async () => { + const id = await createConnection("anthropic"); + assert.equal(await isTrustedEmptyTurn("claude", responseFrom(id)), false); +}); + +test("third-party provider IDs are never trusted", async () => { + const id = await createConnection("anthropic"); + assert.equal(await isTrustedEmptyTurn("anthropic-compatible-gateway", responseFrom(id)), false); +}); + +test("unresolved alias provider is trusted only via the selected official connection header", async () => { + const id = await createConnection("anthropic"); + assert.equal(await isTrustedEmptyTurn(null, responseFrom(id)), true); + // A planned (fallback) connection ID is not proof of what actually served the alias. + assert.equal(await isTrustedEmptyTurn(null, responseFrom(), id), false); +}); + +test("unresolved alias provider stays untrusted for third-party selected connections", async () => { + const custom = await createConnection("anthropic", "https://gateway.example.com/v1"); + assert.equal(await isTrustedEmptyTurn(null, responseFrom(custom)), false); + const other = await createConnection("openai"); + assert.equal(await isTrustedEmptyTurn(null, responseFrom(other)), false); + assert.equal(await isTrustedEmptyTurn(null, responseFrom("missing-connection")), false); +}); diff --git a/tests/unit/combo-hedge-body-size.test.ts b/tests/unit/combo-hedge-body-size.test.ts new file mode 100644 index 000000000000..d16d0130beba --- /dev/null +++ b/tests/unit/combo-hedge-body-size.test.ts @@ -0,0 +1,131 @@ +/** + * Hedge dispatch sends the request body twice. The gate must refuse bodies over + * 256KB and bodies whose size cannot be measured (circular), while still hedging + * a normal small body. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-hedge-body-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "combo-hedge-body-test-secret"; + +const { handleComboChat } = await import("../../open-sse/services/combo.ts"); + +const log = { + info: () => {}, + warn: () => {}, + debug: () => {}, + error: () => {}, +}; + +const HEDGE_CONFIG = { + maxRetries: 0, + retryDelayMs: 1, + zeroLatencyOptimizationsEnabled: true, + hedging: true, + hedgeDelayMs: 1, +}; + +function okResponse(content: string): Response { + return new Response(JSON.stringify({ choices: [{ message: { content } }] }), { + status: 200, + headers: { "content-type": "application/json" }, + }); +} + +type HedgeMode = "serial-hold" | "hedge-race"; + +async function dispatchWithHedge( + body: Record, + mode: HedgeMode +): Promise<{ status: number; calls: string[]; content: string }> { + const calls: string[] = []; + const result = await handleComboChat({ + body, + combo: { + name: "hedge-body-size", + strategy: "priority", + models: ["model-a", "model-b"], + config: HEDGE_CONFIG, + }, + handleSingleModel: async (_body, modelStr, target) => { + calls.push(modelStr); + if (mode === "hedge-race" && modelStr === "model-b") { + return okResponse("fast"); + } + if (mode === "hedge-race") { + await new Promise((resolve) => { + const timer = setTimeout(resolve, 100); + target?.modelAbortSignal?.addEventListener( + "abort", + () => { + clearTimeout(timer); + resolve(); + }, + { once: true } + ); + }); + return okResponse("slow"); + } + await new Promise((resolve) => setTimeout(resolve, 80)); + return okResponse(modelStr); + }, + isModelAvailable: async () => true, + log, + settings: null, + relayOptions: null, + allCombos: null, + }); + const payload = (await result.json()) as { + choices?: { message?: { content?: string } }[]; + }; + return { + status: result.status, + calls: [...calls], + content: payload.choices?.[0]?.message?.content ?? "", + }; +} + +test("combo hedge does not start a second target when the body is over 256KB", async () => { + const oversized = "x".repeat(256 * 1024 + 1); + const outcome = await dispatchWithHedge( + { messages: [{ role: "user", content: oversized }] }, + "serial-hold" + ); + + assert.equal(outcome.status, 200); + assert.equal(outcome.content, "model-a"); + assert.deepEqual(outcome.calls, ["model-a"]); +}); + +test("combo hedge does not start a second target when the body cannot be stringified", async () => { + const circular: Record = { + messages: [{ role: "user", content: "hi" }], + }; + circular.self = circular; + + const outcome = await dispatchWithHedge(circular, "serial-hold"); + + assert.equal( + outcome.status, + 200, + `hedge must not dispatch a second target (calls=${outcome.calls.join(",")})` + ); + assert.equal(outcome.content, "model-a"); + assert.deepEqual(outcome.calls, ["model-a"]); +}); + +test("combo hedge still starts the fallback for a small plain body", async () => { + const outcome = await dispatchWithHedge( + { messages: [{ role: "user", content: "hi" }] }, + "hedge-race" + ); + + assert.equal(outcome.status, 200); + assert.equal(outcome.content, "fast"); + assert.deepEqual(outcome.calls, ["model-a", "model-b"]); +}); diff --git a/tests/unit/combo-quota-unreadable-15347.test.ts b/tests/unit/combo-quota-unreadable-15347.test.ts new file mode 100644 index 000000000000..41c3e1cdc3df --- /dev/null +++ b/tests/unit/combo-quota-unreadable-15347.test.ts @@ -0,0 +1,318 @@ +/** + * #15347 — a quota the fetcher could not read must not score as a full one. + * + * `quotaRemainingPercentFromQuota` used to return 100 for a missing or malformed quota, so a + * provider whose usage endpoint was failing outranked every provider that honestly reported + * 60% consumed, and auto-combo kept choosing providers whose headroom it could not see. + * A telemetry failure is evidence about the telemetry, not the provider: it must rank + * strictly below any real reading (like the #4540 status penalty) without being blocked or + * evicted. + * + * `null` from a fetcher means "could not read it". A provider with no cap is NOT that: it + * reports `unlimited: true`, a real reading of full headroom. A provider with no registered + * fetcher has no telemetry source at all and keeps its default. + * + * Coverage: + * 1. predicate: missing/malformed -> null; unlimited -> 100; real readings unchanged + * 2. buildAutoCandidates flags an unreadable quota, never blocks it (cutoff off AND on) + * 3. scoreAutoTargets keeps it in the pool but strictly below real readings, including a + * real 100%-used one (end to end: buildAutoCandidates -> scoreAutoTargets) + * 4. unlimited and no-fetcher candidates are untouched + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-quota-unreadable-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const { buildAutoCandidates } = await import("../../open-sse/services/combo.ts"); +const { scoreAutoTargets } = await import("../../open-sse/services/combo/autoStrategy.ts"); +const { quotaRemainingPercentFromQuota } = + await import("../../open-sse/services/combo/comboPredicates.ts"); +const { registerQuotaFetcher } = await import("../../open-sse/services/quotaPreflight.ts"); +const { resolveResilienceSettings } = await import("../../src/lib/resilience/settings.ts"); + +// ── 1. The predicate ──────────────────────────────────────────────────────── + +test("a missing or malformed quota is unreadable (null), not 100% remaining (#15347)", () => { + const unreadable: Array<[string, unknown]> = [ + ["null (failed or message-only fetch)", null], + ["undefined", undefined], + ["non-object string", "oops"], + ["non-object number", 42], + ["empty object", {}], + ["percentUsed: null (Number(null) === 0)", { percentUsed: null }], + ["percentUsed: NaN", { percentUsed: Number.NaN }], + ['percentUsed: ""', { percentUsed: "" }], + ['percentUsed: " " (Number(" ") === 0)', { percentUsed: " " }], + ["percentUsed: true (Number(true) === 1)", { percentUsed: true }], + ["windows with a null percentUsed", { windows: { session: { percentUsed: null } } }], + ['windows with a "" percentUsed', { windows: { session: { percentUsed: "" } } }], + ["empty windows map", { windows: {} }], + ["array windows and no percentUsed", { windows: [] }], + ]; + for (const [label, quota] of unreadable) { + assert.equal( + quotaRemainingPercentFromQuota(quota), + null, + `${label} must be unreadable, got ${quotaRemainingPercentFromQuota(quota)}` + ); + } +}); + +test("an unlimited provider is a real full reading, not an unreadable one (#15347)", () => { + assert.equal( + quotaRemainingPercentFromQuota({ used: 0, total: 0, percentUsed: 0, unlimited: true }), + 100 + ); +}); + +test("real quota readings keep their remaining percentage (#15347)", () => { + assert.equal(quotaRemainingPercentFromQuota({ percentUsed: 0 }), 100); + assert.equal(quotaRemainingPercentFromQuota({ percentUsed: 0.4 }), 60); + assert.equal(quotaRemainingPercentFromQuota({ percentUsed: "0.25" }), 75); + assert.equal(quotaRemainingPercentFromQuota({ percentUsed: 1 }), 0); + assert.equal(quotaRemainingPercentFromQuota({ limitReached: true }), 0); + assert.equal(quotaRemainingPercentFromQuota({ windows: [], percentUsed: 0.5 }), 50); + assert.equal( + quotaRemainingPercentFromQuota({ + windows: { session: { percentUsed: 0.1 }, weekly: { percentUsed: 0.75 } }, + }), + 25 + ); +}); + +test("an unparseable window is skipped, not read as 0% used (#15347)", () => { + // Before: Number(null) === 0 made the null window `a` look 0% used. + assert.equal( + quotaRemainingPercentFromQuota({ + windows: { a: { percentUsed: null }, b: { percentUsed: 1 } }, + }), + 0, + "a null window must not mask a real exhausted one" + ); + assert.equal( + quotaRemainingPercentFromQuota({ + windows: { a: { percentUsed: null }, b: { percentUsed: 0.3 } }, + }), + 70 + ); + assert.equal( + quotaRemainingPercentFromQuota({ windows: { a: { percentUsed: null } }, limitReached: true }), + 0, + "limitReached still wins when no window is readable" + ); +}); + +test("an Antigravity-scoped request ignores unparseable windows too (#15347)", () => { + const remaining = quotaRemainingPercentFromQuota( + { + windows: { + gemini_weekly: { percentUsed: null }, + "gemini-3.1-flash-lite": { percentUsed: 0.1 }, + }, + }, + { provider: "agy", requestedModel: "gemini-3.1-flash-lite" } + ); + assert.equal(remaining, 90); +}); + +// ── 2 + 3 + 4. buildAutoCandidates and scoring ────────────────────────────── + +// A provider id nothing else in the repo registers, so the module-level fetcher registry +// cannot leak into (or out of) other test files when they share a process. +const PROVIDER = "unreadable-quota-test"; +const READINGS = new Map(); +const THROWING = new Set(); + +registerQuotaFetcher(PROVIDER, async (connectionId: string) => { + if (THROWING.has(connectionId)) throw new Error("usage endpoint down"); + return (READINGS.get(connectionId) ?? null) as never; +}); + +async function seedConn(name: string, provider = PROVIDER) { + return providersDb.createProviderConnection({ + provider, + authType: "apikey", + name, + apiKey: `sk-${name}`, + isActive: true, + }); +} + +function stepFor(provider: string, model: string) { + return { + kind: "model", + stepId: `${provider}/${model}`, + executionKey: `${provider}/${model}`, + modelStr: `${provider}/${model}`, + provider, + providerId: provider, + connectionId: null, + weight: 1, + label: null, + } as never; +} + +// Quota plus health: the rest of the score must be positive, or a multiplier cannot separate +// two zeros. Candidates are identical apart from their quota, so only that factor differs. +const QUOTA_AND_HEALTH_WEIGHTS = { + quota: 0.5, + health: 0.5, + costInv: 0, + latencyInv: 0, + taskFit: 0, + stability: 0, + tierPriority: 0, + tierAffinity: 0, + specificityMatch: 0, + contextAffinity: 0, + resetWindowAffinity: 0, + connectionDensity: 0, +}; + +test.beforeEach(async () => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); + READINGS.clear(); + THROWING.clear(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("buildAutoCandidates flags a malformed quota, keeps it, and never blocks it (#15347)", async () => { + const real = await seedConn("real-60"); + const emptyObject = await seedConn("empty-object"); + const nullPercent = await seedConn("null-percent"); + READINGS.set(real.id, { used: 40, total: 100, percentUsed: 0.4 }); + READINGS.set(emptyObject.id, {}); + READINGS.set(nullPercent.id, { used: 0, total: 0, percentUsed: null }); + + // The hard cutoff is opt-in; an unreadable reading must stay unblocked either way. + const cutoffOn = resolveResilienceSettings({ + resilienceSettings: { quotaPreflight: { enabled: true } }, + }); + for (const resilience of [null, cutoffOn]) { + const candidates = await buildAutoCandidates( + [stepFor(PROVIDER, "m1")], + `auto-unreadable-15347-${resilience ? "on" : "off"}`, + null, + undefined, + resilience + ); + const byConnection = new Map(candidates.map((c) => [c.connectionId, c])); + + assert.equal(byConnection.get(real.id)?.quotaRemaining, 60, "a real reading is untouched"); + assert.notEqual(byConnection.get(real.id)?.quotaUnreadable, true); + for (const [label, id] of [ + ["empty object", emptyObject.id], + ["percentUsed: null", nullPercent.id], + ] as const) { + const candidate = byConnection.get(id); + assert.ok(candidate, `${label}: must stay in the candidate pool (never evicted)`); + assert.equal(candidate!.quotaUnreadable, true, `${label}: must be flagged unreadable`); + assert.equal(candidate!.quotaRemaining, 0, `${label}: worst on the quota axis`); + assert.notEqual(candidate!.quotaCutoffBlocked, true, `${label}: must not be hard-blocked`); + } + } +}); + +test("a malformed quota ranks strictly below every real reading, even 100% used (#15347)", async () => { + const healthy = await seedConn("healthy-60"); + const exhausted = await seedConn("exhausted-100-used"); + const malformed = await seedConn("malformed"); + READINGS.set(healthy.id, { used: 40, total: 100, percentUsed: 0.4 }); + READINGS.set(exhausted.id, { used: 100, total: 100, percentUsed: 1 }); + READINGS.set(malformed.id, {}); + + const step = stepFor(PROVIDER, "m1"); + const candidates = await buildAutoCandidates([step], "auto-unreadable-15347-rank"); + const targets = candidates.map((candidate) => ({ + ...(step as object), + stepId: candidate.stepId, + executionKey: candidate.executionKey, + modelStr: candidate.modelStr, + provider: candidate.provider, + connectionId: candidate.connectionId, + })) as never[]; + + const ranked = scoreAutoTargets(targets, candidates, "general", QUOTA_AND_HEALTH_WEIGHTS); + const scoreOf = (id: string) => + ranked.find((entry) => entry.target.connectionId === id)?.score as number; + + assert.equal(ranked.length, 3, "the malformed candidate must not be filtered out"); + assert.ok(scoreOf(healthy.id) > scoreOf(exhausted.id), "60% beats 0%"); + assert.ok( + scoreOf(malformed.id) < scoreOf(exhausted.id), + `malformed (${scoreOf(malformed.id)}) must score strictly below a real 100%-used reading (${scoreOf(exhausted.id)})` + ); + assert.equal(ranked[0]?.target.connectionId, healthy.id); + assert.equal(ranked[2]?.target.connectionId, malformed.id); +}); + +test("a null or throwing fetch is unreadable too: no signal is not full headroom (#15347)", async () => { + const nullFetch = await seedConn("null-fetch"); + const throwing = await seedConn("throwing-fetch"); + THROWING.add(throwing.id); + + const candidates = await buildAutoCandidates( + [stepFor(PROVIDER, "m1")], + "auto-unreadable-15347-null" + ); + for (const [label, id] of [ + ["null fetch (failed read, missing credentials, message-only)", nullFetch.id], + ["throwing fetch", throwing.id], + ] as const) { + const candidate = candidates.find((c) => c.connectionId === id); + assert.ok(candidate, `${label}: candidate must stay in the pool`); + assert.equal(candidate!.quotaUnreadable, true, `${label}: must be flagged unreadable`); + assert.equal(candidate!.quotaRemaining, 0, `${label}: worst on the quota axis`); + assert.notEqual(candidate!.quotaCutoffBlocked, true, `${label}: must not be blocked`); + } +}); + +test("an unlimited provider scores as full and is not flagged (#15347)", async () => { + const unlimited = await seedConn("unlimited"); + READINGS.set(unlimited.id, { used: 0, total: 0, percentUsed: 0, unlimited: true }); + + const candidates = await buildAutoCandidates( + [stepFor(PROVIDER, "m1")], + "auto-unreadable-15347-unlimited" + ); + const candidate = candidates.find((c) => c.connectionId === unlimited.id); + assert.ok(candidate); + assert.equal(candidate!.quotaRemaining, 100); + assert.notEqual(candidate!.quotaUnreadable, true); +}); + +test("a real 0%-used reading stays 100% remaining (#15347)", async () => { + const full = await seedConn("full-0-used"); + READINGS.set(full.id, { used: 0, total: 100, percentUsed: 0 }); + + const candidates = await buildAutoCandidates( + [stepFor(PROVIDER, "m1")], + "auto-unreadable-15347-full" + ); + assert.equal(candidates.find((c) => c.connectionId === full.id)?.quotaRemaining, 100); +}); + +test("a provider with no registered quota fetcher keeps its default (#15347 scope)", async () => { + const conn = await seedConn("no-fetcher", "unreadable-quota-no-fetcher"); + const candidates = await buildAutoCandidates( + [stepFor("unreadable-quota-no-fetcher", "m1")], + "auto-unreadable-15347-nofetcher" + ); + const candidate = candidates.find((c) => c.connectionId === conn.id); + assert.ok(candidate, "no-fetcher candidate must exist"); + assert.equal(candidate!.quotaRemaining, 100, "no telemetry source is not a telemetry failure"); + assert.notEqual(candidate!.quotaUnreadable, true); +}); diff --git a/tests/unit/combo-responses-sse-failure-fallback.test.ts b/tests/unit/combo-responses-sse-failure-fallback.test.ts index 1ccdd24ddd0d..8453f624f8d9 100644 --- a/tests/unit/combo-responses-sse-failure-fallback.test.ts +++ b/tests/unit/combo-responses-sse-failure-fallback.test.ts @@ -134,6 +134,29 @@ test("context input errors map to HTTP 400 without an explicit status", async () } }); +test("context input error type without code stays request-scoped", async () => { + for (const type of ["context_length_exceeded", "context_window_exceeded"]) { + const body = [ + "event: response.failed", + `data: ${JSON.stringify({ + type: "response.failed", + response: { + status: "failed", + error: { type, message: "request exceeds the model context" }, + }, + })}`, + "", + "", + ].join("\n"); + + const result = await validateResponseQuality(sseResponse(body), true, silentLog()); + assert.equal(result.upstreamFailure?.status, 400); + assert.equal(result.upstreamFailure?.type, type); + assert.equal(result.upstreamFailure?.requestScoped, true); + assert.equal(result.upstreamFailure?.retryable, false); + } +}); + test("invalid_request_error code maps to HTTP 400 without a type field", async () => { const body = [ "event: response.failed", diff --git a/tests/unit/combo-routing-engine.test.ts b/tests/unit/combo-routing-engine.test.ts index 41e4f43d352a..bb868b66856a 100644 --- a/tests/unit/combo-routing-engine.test.ts +++ b/tests/unit/combo-routing-engine.test.ts @@ -1688,6 +1688,44 @@ test("handleComboChat starts hedged fallback only after explicit zero-latency op assert.deepEqual(calls, ["model-a", "model-b"]); }); +test("handleComboChat does not hedge a body that would double the native send buffer", async () => { + const calls: string[] = []; + const oversized = "x".repeat(256 * 1024 + 1); + + const result = await handleComboChat({ + body: { messages: [{ role: "user", content: oversized }] }, + combo: { + name: "hedging-skipped-for-large-body", + strategy: "priority", + models: ["model-a", "model-b"], + config: { + maxRetries: 0, + retryDelayMs: 1, + zeroLatencyOptimizationsEnabled: true, + hedging: true, + hedgeDelayMs: 1, + }, + }, + handleSingleModel: async (_body: unknown, modelStr: string) => { + calls.push(modelStr); + await new Promise((resolve) => setTimeout(resolve, 100)); + return okResponse({ choices: [{ message: { content: modelStr } }] }); + }, + isModelAvailable: async () => true, + log: createLog(), + settings: null, + relayOptions: null, + allCombos: null, + }); + + const payload = (await result.json()) as { + choices: { message: { content: string } }[]; + }; + assert.equal(result.status, 200); + assert.equal(payload.choices[0].message.content, "model-a"); + assert.deepEqual(calls, ["model-a"]); +}); + test("handleComboChat round-robin falls through generic 400s when a later model succeeds", async () => { const calls: any[] = []; diff --git a/tests/unit/combo-session-stickiness.test.ts b/tests/unit/combo-session-stickiness.test.ts index 2d4d93e14dbd..85e8e86b837a 100644 --- a/tests/unit/combo-session-stickiness.test.ts +++ b/tests/unit/combo-session-stickiness.test.ts @@ -37,7 +37,9 @@ const { // ─── helpers ───────────────────────────────────────────────────────────────── -function makeTarget(connectionId: string): import("../../open-sse/services/combo/types.ts").ResolvedComboTarget { +function makeTarget( + connectionId: string +): import("../../open-sse/services/combo/types.ts").ResolvedComboTarget { return { kind: "model", stepId: `step-${connectionId}`, @@ -466,3 +468,48 @@ test("peekStickyConnectionId: reflects the current binding without mutating it", // Peeking again must not clear or otherwise mutate the binding. assert.equal(peekStickyConnectionId(hash), "conn-peek"); }); + +// ─── #15241: operator-declared order outranks stickiness promotion ─────────── + +test("#15241: respectDeclaredOrder keeps the declared head over a mid-list sticky connection", async () => { + const messages = [{ role: "user", content: "Priority order" }]; + const hash = deriveMessageHash(messages)!; + recordStickyBinding(hash, "conn-B"); + + const targets = [makeTarget("conn-A"), makeTarget("conn-B"), makeTarget("conn-C")]; + const result = await applySessionStickiness(targets, messages, undefined, { + respectDeclaredOrder: true, + }); + + assert.equal(result.stuck, false, "nothing was promoted, so sticky must not report stuck"); + assert.equal(result.targets[0].connectionId, "conn-A", "declared head stays first"); + assert.equal(result.targets[1].connectionId, "conn-B"); + // the binding survives — only the reorder is barred, a later success refreshes it + assert.equal(peekStickyConnectionId(hash), "conn-B"); +}); + +test("#15241: respectDeclaredOrder leaves a sticky connection already at the head untouched", async () => { + const messages = [{ role: "user", content: "Already head" }]; + const hash = deriveMessageHash(messages)!; + recordStickyBinding(hash, "conn-A"); + + const targets = [makeTarget("conn-A"), makeTarget("conn-B")]; + const result = await applySessionStickiness(targets, messages, undefined, { + respectDeclaredOrder: true, + }); + + assert.equal(result.stuck, true, "declared head equals the sticky pin — sticky still applied"); + assert.equal(result.targets[0].connectionId, "conn-A"); +}); + +test("#15241: without respectDeclaredOrder a mid-list sticky connection still promotes", async () => { + const messages = [{ role: "user", content: "Legacy behavior" }]; + const hash = deriveMessageHash(messages)!; + recordStickyBinding(hash, "conn-B"); + + const targets = [makeTarget("conn-A"), makeTarget("conn-B"), makeTarget("conn-C")]; + const result = await applySessionStickiness(targets, messages); + + assert.equal(result.stuck, true, "strategies without a declared head keep the reorder"); + assert.equal(result.targets[0].connectionId, "conn-B"); +}); diff --git a/tests/unit/combo-streaming-empty-content-failover.test.ts b/tests/unit/combo-streaming-empty-content-failover.test.ts index 1d38c10b7692..e7b52d6b4942 100644 --- a/tests/unit/combo-streaming-empty-content-failover.test.ts +++ b/tests/unit/combo-streaming-empty-content-failover.test.ts @@ -274,6 +274,162 @@ test("#3685 streaming is preserved for non-empty response: clonedResponse body y assert.ok(decoded.includes(", world!"), "decoded body must contain the full text delta"); }); +/** Full Claude lifecycle with ZERO content blocks and a given stop_reason. */ +function makeEmptyLifecycleStream(stopReason: string | null): Response { + const events = [ + `event: message_start\ndata: ${JSON.stringify({ + type: "message_start", + message: { + id: "msg_test_empty_turn", + type: "message", + role: "assistant", + model: "claude-sonnet-4-6", + content: [], + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 10, output_tokens: 0 }, + }, + })}`, + "", + `event: message_delta\ndata: ${JSON.stringify({ + type: "message_delta", + delta: { stop_reason: stopReason, stop_sequence: null }, + usage: { input_tokens: 0, output_tokens: 0 }, + })}`, + "", + `event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}`, + "", + ]; + + return new Response(claudeSseStream(events), { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); +} + +/** Full lifecycle with one text block that never carries a delta (#1382 shape). */ +function makeEmptyTextBlockStream(): Response { + const events = [ + `event: message_start\ndata: ${JSON.stringify({ + type: "message_start", + message: { + id: "msg_test_empty_block", + type: "message", + role: "assistant", + model: "claude-sonnet-4-6", + content: [], + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 10, output_tokens: 0 }, + }, + })}`, + "", + `event: content_block_start\ndata: ${JSON.stringify({ + type: "content_block_start", + index: 0, + content_block: { type: "text", text: "" }, + })}`, + "", + `event: content_block_stop\ndata: ${JSON.stringify({ type: "content_block_stop", index: 0 })}`, + "", + `event: message_delta\ndata: ${JSON.stringify({ + type: "message_delta", + delta: { stop_reason: "end_turn", stop_sequence: null }, + usage: { input_tokens: 0, output_tokens: 0 }, + })}`, + "", + `event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}`, + "", + ]; + + return new Response(claudeSseStream(events), { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); +} + +test("trusted first-party Claude end_turn with zero content blocks is valid (#15488 combo path)", async () => { + const res = makeEmptyLifecycleStream("end_turn"); + const out = await validateResponseQuality(res, true, silentLog, null, null, true); + assert.equal(out.valid, true, `expected valid for empty end_turn stream, got: ${out.reason}`); + assert.ok(out.clonedResponse, "clonedResponse must be returned for the trusted empty stream"); + const text = await out.clonedResponse!.text(); + assert.ok(text.includes("message_stop"), "clonedResponse must replay the full SSE bytes"); +}); + +test("trusted first-party Claude stop_sequence with zero content blocks is valid", async () => { + const res = makeEmptyLifecycleStream("stop_sequence"); + const out = await validateResponseQuality(res, true, silentLog, null, null, true); + assert.equal( + out.valid, + true, + `expected valid for empty stop_sequence stream, got: ${out.reason}` + ); +}); + +test("trusted provider content_filter stop stays invalid", async () => { + const res = makeEmptyLifecycleStream("content_filter"); + const out = await validateResponseQuality(res, true, silentLog, null, null, true); + assert.equal(out.valid, false, "content_filter must still trigger failover for trusted provider"); +}); + +test("untrusted by default: end_turn with zero content blocks stays invalid", async () => { + const res = makeEmptyLifecycleStream("end_turn"); + const out = await validateResponseQuality(res, true, silentLog); + assert.equal( + out.valid, + false, + "without an explicit trusted connection the guard must not be relaxed" + ); +}); + +test("explicitly untrusted connection end_turn with zero content blocks stays invalid", async () => { + const res = makeEmptyLifecycleStream("end_turn"); + const out = await validateResponseQuality(res, true, silentLog, null, null, false); + assert.equal(out.valid, false, "third-party Claude-compatible gateways keep the guard"); +}); + +test("trusted provider empty text block pair (#1382 shape) stays invalid", async () => { + const res = makeEmptyTextBlockStream(); + const out = await validateResponseQuality(res, true, silentLog, null, null, true); + assert.equal(out.valid, false, "an opened-and-closed empty block is not a normal empty answer"); +}); + +test("trusted provider lifecycle without message_stop stays invalid", async () => { + const events = [ + `event: message_start\ndata: ${JSON.stringify({ + type: "message_start", + message: { + id: "msg_test_no_stop", + type: "message", + role: "assistant", + model: "claude-sonnet-4-6", + content: [], + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 10, output_tokens: 0 }, + }, + })}`, + "", + `event: message_delta\ndata: ${JSON.stringify({ + type: "message_delta", + delta: { stop_reason: "end_turn", stop_sequence: null }, + usage: { input_tokens: 0, output_tokens: 0 }, + })}`, + "", + ]; + const res = new Response(claudeSseStream(events), { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); + const out = await validateResponseQuality(res, true, silentLog, null, null, true); + assert.equal( + out.valid, + false, + "incomplete lifecycle (no message_stop) must not be treated as a normal empty answer" + ); +}); + test("#5976 truly EMPTY streaming body (zero bytes) → invalid for combo failover", async () => { // A 200 SSE response whose body closes without emitting a single byte // (e.g. Gemini returning HTTP 200 with an empty body) cannot carry content — diff --git a/tests/unit/combo-target-resolution-split.test.ts b/tests/unit/combo-target-resolution-split.test.ts index 9f80ff17fda7..13ae1ac22931 100644 --- a/tests/unit/combo-target-resolution-split.test.ts +++ b/tests/unit/combo-target-resolution-split.test.ts @@ -22,6 +22,8 @@ const { saveModelsDevCapabilities, clearModelsDevCapabilities } = await import("../../src/lib/modelsDevSync.ts"); const { resolveComboTargetPipeline } = await import("../../open-sse/services/combo/targetResolution.ts"); +const { clearAllStickyBindings, recordStickyBinding } = + await import("../../open-sse/services/combo/sessionStickiness.ts"); test.after(() => { core.resetDbInstance(); @@ -35,6 +37,7 @@ test.after(() => { test.beforeEach(() => { clearModelsDevCapabilities(); + clearAllStickyBindings(); }); const noopLog = { info() {}, warn() {}, error() {}, debug() {} } as never; @@ -94,6 +97,36 @@ test("priority strategy resolves combo models into orderedTargets in declared or ); }); +test("#15241 priority pipeline keeps its declared head after a mid-list sticky success", async () => { + const priorityWithConnections = { + id: "c-sticky-priority", + name: "sticky-priority", + strategy: "priority", + models: [ + { model: "openai/gpt-4o", connectionId: "conn-priority-a" }, + { model: "anthropic/claude-3", connectionId: "conn-priority-b" }, + ], + config: {}, + }; + const first = await resolveComboTargetPipeline(deps({ combo: priorityWithConnections })); + assert.ok(!("earlyResponse" in first)); + if ("earlyResponse" in first) return; + assert.ok(first.sticky.messageHash, "pipeline derives the conversation stickiness key"); + assert.ok(first.orderedTargets[1]?.connectionId, "second target has a connection id to pin"); + + recordStickyBinding(first.sticky.messageHash!, first.orderedTargets[1]!.connectionId!); + + const repeated = await resolveComboTargetPipeline(deps({ combo: priorityWithConnections })); + assert.ok(!("earlyResponse" in repeated)); + if ("earlyResponse" in repeated) return; + assert.deepEqual( + repeated.orderedTargets.map((target) => target.modelStr), + ["openai/gpt-4o", "anthropic/claude-3"], + "a prior mid-list success must not become runtime try-slot #1 for priority" + ); + assert.equal(repeated.sticky.stuck, false); +}); + test("returns the derived values the attempt loop consumes", async () => { const result = await resolveComboTargetPipeline(deps()); assert.ok(!("earlyResponse" in result)); diff --git a/tests/unit/combo/combo-target-timeout-standards.test.ts b/tests/unit/combo/combo-target-timeout-standards.test.ts index 52c832251994..95ae07a0e7d0 100644 --- a/tests/unit/combo/combo-target-timeout-standards.test.ts +++ b/tests/unit/combo/combo-target-timeout-standards.test.ts @@ -20,8 +20,11 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "combo-target-timeout-std-secret"; const { handleComboChat } = await import("../../../open-sse/services/combo.ts"); -const { isComboRequestScopedFailure, shouldRecordProviderBreakerFailure } = - await import("../../../open-sse/services/combo/comboPredicates.ts"); +const { + isComboRequestScopedFailure, + shouldRecordProviderBreakerFailure, + shouldRecordModelLockoutForComboFailure, +} = await import("../../../open-sse/services/combo/comboPredicates.ts"); const { applyComboTargetExhaustion } = await import("../../../open-sse/services/combo/targetExhaustion.ts"); const { getProviderBreakerState } = await import("../../../open-sse/services/accountFallback.ts"); @@ -97,6 +100,33 @@ test.beforeEach(() => { // ── Decision seam: breaker + request-scoped classification ────────────────── +test("decision seam: typed combo_target_timeout still records a model lockout", () => { + const scoped = isComboRequestScopedFailure( + new Response("timed out", { status: 504 }), + "Model openai/slow timed out", + { code: "combo_target_timeout", type: "combo_target_timeout" } + ); + assert.equal(scoped, true, "timeout stays request-scoped for the provider breaker"); + assert.equal( + shouldRecordModelLockoutForComboFailure(scoped, { + code: "combo_target_timeout", + type: "combo_target_timeout", + }), + true, + "a locally timed-out model must be locked so the next request does not wait on it again" + ); +}); + +test("decision seam: other request-scoped failures still skip model lockout", () => { + assert.equal( + shouldRecordModelLockoutForComboFailure(true, { + code: "context_length_exceeded", + type: "invalid_request_error", + }), + false + ); +}); + test("decision seam: typed combo_target_timeout 504 is request-scoped and does not record breaker failure", () => { const decision = decideProviderBreakerRecord({ status: 504, diff --git a/tests/unit/combo/quota-connection-eligibility.test.ts b/tests/unit/combo/quota-connection-eligibility.test.ts index e3846e5403da..e75a09785527 100644 --- a/tests/unit/combo/quota-connection-eligibility.test.ts +++ b/tests/unit/combo/quota-connection-eligibility.test.ts @@ -265,3 +265,41 @@ test( } } ); + +test("reset-aware ranks OpenCode Go accounts by monthly quota after shorter windows tie", async () => { + const provider = "opencode-go"; + const depletedMonthly = await db.createProviderConnection({ + provider, + name: "depleted monthly quota", + isActive: true, + testStatus: "active", + authType: "apikey", + }); + const healthyMonthly = await db.createProviderConnection({ + provider, + name: "healthy monthly quota", + isActive: true, + testStatus: "active", + authType: "apikey", + }); + const resetAt = new Date(Date.now() + 86_400_000).toISOString(); + registerQuotaFetcher(provider, async (id) => ({ + percentUsed: 0.5, + limitReached: false, + window5h: { percentUsed: 0.5, resetAt }, + windowWeekly: { percentUsed: 0.5, resetAt }, + windowMonthly: { + percentUsed: id === depletedMonthly.id ? 0.9 : 0.1, + resetAt: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }, + })); + + const ordered = await orderTargetsByResetAwareQuota( + [target(provider, depletedMonthly.id), target(provider, healthyMonthly.id)], + randomUUID(), + {}, + log + ); + + assert.equal(ordered[0]?.connectionId, healthyMonthly.id); +}); diff --git a/tests/unit/combo/quota-weighted-strategy.test.ts b/tests/unit/combo/quota-weighted-strategy.test.ts index 3da2d0470d7c..c2174f4e8a03 100644 --- a/tests/unit/combo/quota-weighted-strategy.test.ts +++ b/tests/unit/combo/quota-weighted-strategy.test.ts @@ -155,6 +155,51 @@ test("getResetAwareRemainingPercent: min(session, weekly) * 100", () => { assert.equal(getResetAwareRemainingPercent(quota), 40); }); +test("getResetAwareRemainingPercent includes a reported monthly window", () => { + const quota = { + percentUsed: 0.5, + window5h: { percentUsed: 0.1, resetAt: iso() }, + window7d: { percentUsed: 0.2, resetAt: iso() }, + windowMonthly: { percentUsed: 0.95, resetAt: iso(30 * 86_400_000) }, + }; + assert.equal(getResetAwareRemainingPercent(quota), 5); +}); + +test("getResetAwareRemainingPercent excludes a monthly-exhausted account", () => { + const quota = { + percentUsed: 0.2, + window5h: { percentUsed: 0.1, resetAt: iso() }, + window7d: { percentUsed: 0.2, resetAt: iso() }, + windowMonthly: { percentUsed: 1, resetAt: iso(30 * 86_400_000) }, + }; + assert.equal(getResetAwareRemainingPercent(quota), 0); +}); + +test("scoreResetAwareQuota preserves session and weekly scoring without a monthly window", () => { + const config = resolveResetAwareConfig({}); + const quota = { + percentUsed: 0.5, + window5h: { percentUsed: 0.5, resetAt: iso(10 * 86_400_000) }, + window7d: { percentUsed: 0.5, resetAt: iso(14 * 86_400_000) }, + }; + assert.equal(scoreResetAwareQuota(quota, config).score, 0.16); +}); + +test("scoreResetAwareQuota accounts for monthly reset pressure", () => { + const config = resolveResetAwareConfig({}); + const quotaAtMonthlyReset = (days: number) => ({ + percentUsed: 0.5, + window5h: { percentUsed: 0.5, resetAt: iso(10 * 86_400_000) }, + window7d: { percentUsed: 0.5, resetAt: iso(14 * 86_400_000) }, + windowMonthly: { percentUsed: 0.5, resetAt: iso(days * 86_400_000) }, + }); + + const nearReset = scoreResetAwareQuota(quotaAtMonthlyReset(1), config).score; + const farReset = scoreResetAwareQuota(quotaAtMonthlyReset(60), config).score; + + assert.ok(nearReset > farReset); +}); + test("getResetAwareRemainingPercent: missing windows fall back to overall percentUsed", () => { assert.equal(getResetAwareRemainingPercent({ percentUsed: 0.7 }), 30); }); @@ -415,6 +460,33 @@ test("pinned hard-empty connection stays dropped", async () => { assert.deepEqual(ordered, []); }); +test("quota-weighted excludes an account with no monthly quota remaining", async () => { + const provider = "agy"; + const depleted = `monthly-depleted-${randomUUID()}`; + const healthy = `monthly-healthy-${randomUUID()}`; + registerQuotaFetcher(provider, async (id) => + quotaAt(0.4, { + windowMonthly: { + percentUsed: id === depleted ? 1 : 0.2, + resetAt: iso(30 * 86_400_000), + }, + }) + ); + + const ordered = await orderTargetsByQuotaWeighted( + [makeTarget(provider, depleted), makeTarget(provider, healthy)], + "monthly-exhausted", + {}, + { warn() {} }, + null + ); + + assert.deepEqual( + ordered.map((target) => target.connectionId), + [healthy] + ); +}); + test("family filter: gemini request ignores Claude-empty windows", async () => { const provider = "agy"; const conn = `fam-${randomUUID()}`; diff --git a/tests/unit/combo/record-lkgp-pin.test.ts b/tests/unit/combo/record-lkgp-pin.test.ts index b22b9221d102..182ae0900ff5 100644 --- a/tests/unit/combo/record-lkgp-pin.test.ts +++ b/tests/unit/combo/record-lkgp-pin.test.ts @@ -156,3 +156,52 @@ test("writer calls carry the connection id through (absent stays absent)", async }); assert.deepEqual(seen, [undefined, undefined]); }); + +test("records a single combo-level pin when the combo id matches the combo name", async () => { + // Pin store standing in for the persisted LKGP namespace; the read below is + // the `getLKGP(comboName, comboId || comboName)` equivalent the routing + // readers use. + const store = new Map(); + const calls: Array<[string, string, string, string | undefined]> = []; + const { warnings, log } = captureWarn(); + await recordLkgpPin({ + comboName: "combo-a", + executionKey: "ek-7", + comboId: "combo-a", + provider: "openai", + connectionId: "conn-1", + log, + tag: "COMBO", + setLKGP: async (comboName, modelKey, provider, connectionId) => { + calls.push([comboName, modelKey, provider, connectionId]); + store.set(`${comboName}:${modelKey}`, { provider, connectionId }); + }, + }); + assert.deepEqual(warnings, []); + assert.equal(calls.length, 1); + assert.deepEqual(calls, [["combo-a", "combo-a", "openai", "conn-1"]]); + assert.equal(store.get("combo-a:combo-a")?.provider, "openai"); +}); + +test("records a single combo-level pin when the combo id is missing", async () => { + const store = new Map(); + const calls: Array<[string, string, string, string | undefined]> = []; + const { warnings, log } = captureWarn(); + await recordLkgpPin({ + comboName: "combo-a", + executionKey: "ek-7", + comboId: null, + provider: "openai", + connectionId: "conn-1", + log, + tag: "COMBO", + setLKGP: async (comboName, modelKey, provider, connectionId) => { + calls.push([comboName, modelKey, provider, connectionId]); + store.set(`${comboName}:${modelKey}`, { provider, connectionId }); + }, + }); + assert.deepEqual(warnings, []); + assert.equal(calls.length, 1); + assert.deepEqual(calls, [["combo-a", "combo-a", "openai", "conn-1"]]); + assert.equal(store.get("combo-a:combo-a")?.provider, "openai"); +}); diff --git a/tests/unit/combos-auto-skipped-variants.test.ts b/tests/unit/combos-auto-skipped-variants.test.ts new file mode 100644 index 000000000000..11b4e05bd49c --- /dev/null +++ b/tests/unit/combos-auto-skipped-variants.test.ts @@ -0,0 +1,193 @@ +/** + * GET /api/combos/auto logs the variants it skips instead of failing silently. + * + * Each of the four variant families (named, template, suffix, family) builds + * every entry from the same candidate pool. When one build fails the route + * leaves that variant out of the list but must say so: a single warning line + * per request names the skipped variants and the first error. With no failure + * the route stays quiet and the payload matches the unfixed baseline. + * + * Failure injection poisons one scoring weight pack through the shared + * modePacks module (plain mutable export, no module mock needed): every + * variant resolves its weights from a pack synchronously, so a throwing pack + * makes exactly the builds that use it fail while the rest succeed. The + * offline pack only feeds the named variant, the reliability pack only the + * tiered suffix variant, and the cost pack a template-only id. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-skipped-variants-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET ?? "skipped-variants-test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const settingsDb = await import("../../src/lib/db/settings.ts"); +const modePacks = await import("../../open-sse/services/autoCombo/modePacks.ts"); +const combosAutoRoute = await import("../../src/app/api/combos/auto/route.ts"); + +const PACKS = modePacks.MODE_PACKS as Record; +const SAVED_PACKS: Record = {}; +for (const key of Object.keys(PACKS)) SAVED_PACKS[key] = PACKS[key]; + +function poisonPack(pack: string, message: string): void { + Object.defineProperty(PACKS, pack, { + configurable: true, + enumerable: true, + get() { + throw new Error(message); + }, + }); +} + +function restorePacks(): void { + for (const key of Object.keys(SAVED_PACKS)) { + Object.defineProperty(PACKS, key, { + configurable: true, + enumerable: true, + value: SAVED_PACKS[key], + writable: true, + }); + } +} + +function watchWarnings(): { lines: string[]; stop: () => void } { + const lines: string[] = []; + const original = console.warn; + console.warn = (...args: unknown[]) => { + lines.push(args.map(String).join(" ")); + }; + return { + lines, + stop: () => { + console.warn = original; + }, + }; +} + +function skippedLines(lines: string[]): string[] { + return lines.filter((line) => line.includes("auto combo variants skipped")); +} + +async function callRoute(): Promise<{ status: number; combos: Array<{ id: string }> }> { + const res = await combosAutoRoute.GET(new Request("http://localhost/api/combos/auto")); + return { status: res.status, combos: (await res.json()).combos }; +} + +test.after(() => { + restorePacks(); + core.resetDbInstance(); + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } catch { + // best-effort cleanup + } +}); + +test("stays quiet when every variant builds", async () => { + await settingsDb.updateSettings({ requireLogin: false }); + restorePacks(); + + const watcher = watchWarnings(); + try { + const { status, combos } = await callRoute(); + assert.equal(status, 200); + assert.ok(combos.length > 1); + assert.equal(skippedLines(watcher.lines).length, 0); + } finally { + watcher.stop(); + } +}); + +async function expectSkipped(pack: string, ids: string[], notIds: string[] = []): Promise { + await settingsDb.updateSettings({ requireLogin: false }); + restorePacks(); + const { combos: baseline } = await callRoute(); + for (const id of ids) assert.ok(baseline.map((combo) => combo.id).includes(id)); + + poisonPack(pack, `injected failure for ${pack}`); + const watcher = watchWarnings(); + try { + const { status, combos } = await callRoute(); + const present = combos.map((combo) => combo.id); + assert.equal(status, 200); + for (const id of ids) assert.ok(!present.includes(id)); + for (const id of notIds) assert.ok(present.includes(id)); + const lines = skippedLines(watcher.lines); + assert.equal(lines.length, 1); + for (const id of ids) assert.equal(lines[0].split(id).length - 1, 1, `${id} named once`); + assert.ok(lines[0].includes(`injected failure for ${pack}`)); + } finally { + watcher.stop(); + restorePacks(); + } +} + +test("logs one line naming the skipped named variant", async () => { + // auto/offline is built by both the named and the template loop: still one mention. + await expectSkipped("offline-friendly", ["auto/offline"], ["auto/fast"]); +}); + +test("logs one line naming the skipped template variant", async () => { + await expectSkipped("cost-saver", ["auto/best-free"], ["auto/fast"]); +}); + +test("logs one line naming the skipped tiered suffix variant", async () => { + await expectSkipped("reliability-first", ["auto/coding:reliable"], ["auto/coding:fast"]); +}); + +test("a variant that fails once and builds in a later loop is kept and not reported", async () => { + await settingsDb.updateSettings({ requireLogin: false }); + restorePacks(); + let calls = 0; + Object.defineProperty(PACKS, "offline-friendly", { + configurable: true, + enumerable: true, + get() { + calls += 1; + if (calls === 1) throw new Error("transient failure"); + return SAVED_PACKS["offline-friendly"]; + }, + }); + const watcher = watchWarnings(); + try { + const { combos } = await callRoute(); + assert.ok(combos.map((combo) => combo.id).includes("auto/offline")); + assert.equal(skippedLines(watcher.lines).length, 0); + } finally { + watcher.stop(); + restorePacks(); + } +}); + +test("keeps the payload identical to the unfixed baseline", async () => { + await settingsDb.updateSettings({ requireLogin: false }); + restorePacks(); + const { combos } = await callRoute(); + const ids = combos.map((combo) => combo.id); + assert.equal(new Set(ids).size, ids.length); + assert.ok(ids.includes("auto/coding")); + assert.ok(ids[0] === "auto"); +}); + +test("logs the first error on a single bounded line", async () => { + await settingsDb.updateSettings({ requireLogin: false }); + restorePacks(); + poisonPack("offline-friendly", `first\nsecond\tline ${"x".repeat(1000)}`); + const watcher = watchWarnings(); + try { + const { status } = await callRoute(); + assert.equal(status, 200); + const lines = skippedLines(watcher.lines); + assert.equal(lines.length, 1); + assert.ok(!/[\r\n\t]/.test(lines[0])); + assert.ok(lines[0].includes("first second line")); + assert.ok(lines[0].length < 450); + } finally { + watcher.stop(); + restorePacks(); + } +}); diff --git a/tests/unit/command-code-executor.test.ts b/tests/unit/command-code-executor.test.ts index 09e90011d713..fa21d4f94e91 100644 --- a/tests/unit/command-code-executor.test.ts +++ b/tests/unit/command-code-executor.test.ts @@ -8,6 +8,8 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-command-c process.env.DATA_DIR = TEST_DATA_DIR; const { REGISTRY, getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts"); +const { ANTHROPIC_VERSION_HEADER } = await import("../../open-sse/config/anthropicHeaders.ts"); +const { getModelTargetFormat } = await import("../../open-sse/config/providerModels.ts"); const { CommandCodeExecutor } = await import("../../open-sse/executors/commandCode.ts"); const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/executors/index.ts"); const core = await import("../../src/lib/db/core.ts"); @@ -18,7 +20,9 @@ type FetchCall = { url: string; init: Record; body?: Record { assert.ok((await getExecutor("cmd")) instanceof CommandCodeExecutor); }); +test("Command Code Claude models select Anthropic as their target format", () => { + const models = ["claude-opus-4-7", "claude-fable-5", "claude-sonnet-5-5"]; + + for (const model of models) { + assert.equal(getModelTargetFormat("command-code", model), "claude"); + } +}); + +test("Command Code executor sends Claude models to the Anthropic Messages endpoint", async () => { + const models = ["claude-opus-4-7", "claude-fable-5", "claude-sonnet-5-5"]; + const executor = await getExecutor("command-code"); + + for (const model of models) { + const calls = captureFetch({}); + const { url } = await executor.execute({ + model, + stream: false, + credentials: { apiKey: "cc_test_key" }, + body: { + model, + max_tokens: 32, + messages: [{ role: "user", content: "Hi" }], + }, + }); + + assert.equal(url, MESSAGES_URL); + assert.equal(calls[0].url, MESSAGES_URL); + assert.equal(calls[0].body?.model, model); + assert.equal(calls[0].body?.max_tokens, 32); + assert.deepEqual(calls[0].body?.messages, [{ role: "user", content: "Hi" }]); + assert.equal( + (calls[0].init.headers as Record)?.["anthropic-version"], + ANTHROPIC_VERSION_HEADER + ); + } +}); + test("Command Code executor posts a flat OpenAI body + standard headers to /provider/v1/chat/completions (#10265)", async () => { const calls = captureFetch({}); const executor = await getExecutor("command-code"); diff --git a/tests/unit/compression/compression-engines-map-derived-defaults.test.ts b/tests/unit/compression/compression-engines-map-derived-defaults.test.ts new file mode 100644 index 000000000000..38fbe70548f3 --- /dev/null +++ b/tests/unit/compression/compression-engines-map-derived-defaults.test.ts @@ -0,0 +1,62 @@ +import { test, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync( + path.join(os.tmpdir(), "omniroute-compression-derived-engines-") +); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const { getDbInstance, resetDbInstance } = await import("../../../src/lib/db/core.ts"); +const { getCompressionSettings } = await import("../../../src/lib/db/compression.ts"); + +function freshDir() { + resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +after(() => { + resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } +}); + +function setRow(key: string, value: string) { + getDbInstance() + .prepare("INSERT OR REPLACE INTO key_value(namespace,key,value) VALUES('compression',?,?)") + .run(key, value); +} + +test("no engines row: derived map keeps aggressive off by default", async () => { + freshDir(); + getDbInstance(); + const cfg = await getCompressionSettings(); + assert.equal(cfg.enginesExplicit, false); + assert.equal(cfg.engines.aggressive.enabled, false); +}); + +test("no engines row: defaultMode 'aggressive' is the only signal that turns it on", async () => { + freshDir(); + getDbInstance(); + setRow("defaultMode", '"aggressive"'); + const cfg = await getCompressionSettings(); + assert.equal(cfg.enginesExplicit, false); + assert.equal(cfg.engines.aggressive.enabled, true); +}); + +test("no engines row: a stored aggressive.enabled=true outside the schema is ignored", async () => { + freshDir(); + getDbInstance(); + setRow("aggressive", '{"enabled":true}'); + const cfg = await getCompressionSettings(); + assert.equal(cfg.enginesExplicit, false); + assert.equal(cfg.engines.aggressive.enabled, false); +}); diff --git a/tests/unit/compression/compression-header-dispatch.test.ts b/tests/unit/compression/compression-header-dispatch.test.ts index 1943e4e87d5f..f514c18d825c 100644 --- a/tests/unit/compression/compression-header-dispatch.test.ts +++ b/tests/unit/compression/compression-header-dispatch.test.ts @@ -69,6 +69,16 @@ describe("planFromHeader (Phase 3)", () => { assert.deepEqual(planFromHeader(cfg(), "c1", combos)?.stackedPipeline, combos.c1); }); + it("an empty-pipeline combo is not a chosen plan (fall-through)", () => { + assert.equal(planFromHeader(cfg(), "empty", { empty: [] }), null); + }); + + it("prototype-key header values do not resolve to a plan", () => { + for (const header of ["__proto__", "constructor", "toString"]) { + assert.equal(planFromHeader(cfg(), header, combos), null, header); + } + }); + it("unknown value => null (fall-through)", () => { assert.equal(planFromHeader(cfg(), "nonsense", combos), null); }); diff --git a/tests/unit/compression/compression-worker-file-resolution.test.ts b/tests/unit/compression/compression-worker-file-resolution.test.ts index 56cbd335ec82..dabf8709aa67 100644 --- a/tests/unit/compression/compression-worker-file-resolution.test.ts +++ b/tests/unit/compression/compression-worker-file-resolution.test.ts @@ -89,6 +89,17 @@ describe("resolveWorkerFile (runtime anchors)", () => { assert.equal(resolved, join(installRoot, WORKER_JS_REL)); }); + it("uses the .ts source when a build-time .js placeholder exists", () => { + const installRoot = makeSandbox(); + makeInstallRoot(installRoot, "compressionWorker.js"); + makeInstallRoot(installRoot, "compressionWorker.ts"); + const elsewhere = makeSandbox(); + const resolved = withRuntime(installRoot, join(elsewhere, "server.js"), () => + resolveWorkerFile() + ); + assert.equal(resolved, join(installRoot, WORKER_TS_REL)); + }); + it("falls back to the .ts source when no .js exists (dev loader path)", () => { const installRoot = makeSandbox(); makeInstallRoot(installRoot, "compressionWorker.ts"); diff --git a/tests/unit/compression/compression-worker-pool-jam.test.ts b/tests/unit/compression/compression-worker-pool-jam.test.ts index 4bd1a83e3e17..349822957c88 100644 --- a/tests/unit/compression/compression-worker-pool-jam.test.ts +++ b/tests/unit/compression/compression-worker-pool-jam.test.ts @@ -1,6 +1,8 @@ import assert from "node:assert/strict"; import { EventEmitter } from "node:events"; +import path from "node:path"; import { after, describe, it } from "node:test"; +import { pathToFileURL } from "node:url"; import type { Worker } from "node:worker_threads"; import { __setCompressionWorkerFactoryForTests, @@ -602,7 +604,7 @@ describe("llmlingua worker spawn specifier", () => { const specifier = llmlinguaWorkerSpecifier("/app/onnxWorker.js"); assert.ok(specifier instanceof URL, "Worker entry must be a URL object, not a string"); assert.equal(specifier.protocol, "file:"); - assert.equal(specifier.pathname, "/app/onnxWorker.js"); + assert.equal(specifier.pathname, pathToFileURL(path.resolve("/app/onnxWorker.js")).pathname); }); }); @@ -640,6 +642,40 @@ describe("llmlingua worker fail-open catch paths", () => { } }); + it("warns when a worker errors with a pending request", async () => { + const broken = fakeWorker(); + __setLlmlinguaWorkerHarnessForTests({ depsAvailable: true, factory: () => broken }); + __resetCompressionFailOpenNotifierForTests(); + const warn = captureWarn(); + try { + const result = workerBackend("original prose", {}); + assert.equal(broken.messages.length, 1); + broken.emit("error", new Error("worker module missing")); + assert.equal(await result, "original prose"); + assert.equal(warn.lines.filter((line) => line.includes("llmlingua worker error")).length, 1); + } finally { + warn.restore(); + restoreHarness(); + } + }); + + it("warns when a worker exits with a pending request", async () => { + const broken = fakeWorker(); + __setLlmlinguaWorkerHarnessForTests({ depsAvailable: true, factory: () => broken }); + __resetCompressionFailOpenNotifierForTests(); + const warn = captureWarn(); + try { + const result = workerBackend("original prose", {}); + assert.equal(broken.messages.length, 1); + broken.emit("exit", 1); + assert.equal(await result, "original prose"); + assert.equal(warn.lines.filter((line) => line.includes("llmlingua worker exit")).length, 1); + } finally { + warn.restore(); + restoreHarness(); + } + }); + it("fails open and respawns when postMessage throws", async () => { let spawns = 0; const flaky = fakeWorker(); diff --git a/tests/unit/compression/default-combo-lossy-policy.test.ts b/tests/unit/compression/default-combo-lossy-policy.test.ts new file mode 100644 index 000000000000..b272cea89259 --- /dev/null +++ b/tests/unit/compression/default-combo-lossy-policy.test.ts @@ -0,0 +1,165 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-default-combo-lossy-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.REQUIRE_API_KEY = "false"; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-compression-secret"; + +const core = await import("../../../src/lib/db/core.ts"); +const providersDb = await import("../../../src/lib/db/providers.ts"); +const readCacheDb = await import("../../../src/lib/db/readCache.ts"); +const compressionDb = await import("../../../src/lib/db/compression.ts"); +const compressionCombosDb = await import("../../../src/lib/db/compressionCombos.ts"); +const { handleChatCore } = await import("../../../open-sse/handlers/chatCore.ts"); +const { resetAllCircuitBreakers } = await import("../../../src/shared/utils/circuitBreaker.ts"); + +const originalFetch = globalThis.fetch; +// rtk collapses a run of identical lines into this marker; session-dedup and lite leave it out. +const RTK_MARKER = /rtk:dropped \d+ repeated lines/; +const DEFAULT_COMBO_APPLIED = "Default compression combo applied: default-caveman"; + +async function resetStorage() { + globalThis.fetch = originalFetch; + resetAllCircuitBreakers(); + readCacheDb.invalidateDbCache(); + await new Promise((resolve) => setTimeout(resolve, 20)); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(async () => { + await resetStorage(); +}); + +test.after(async () => { + globalThis.fetch = originalFetch; + // chatCore writes the call log after the response resolves; let it land before closing. + await new Promise((resolve) => setTimeout(resolve, 50)); + core.closeDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; + else process.env.DATA_DIR = ORIGINAL_DATA_DIR; +}); + +// Legacy settings: stacked default mode with the stored built-in rtk+caveman stacked pipeline. +// That is the state in which chatCore swaps in the default compression combo's pipeline. +async function storeLegacyStackedSettings() { + await compressionDb.updateCompressionSettings({ + enabled: true, + defaultMode: "stacked", + autoTriggerTokens: 0, + cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: true }, + languageConfig: { + enabled: false, + defaultLanguage: "en", + autoDetect: true, + enabledPacks: ["en"], + }, + }); +} + +async function sendRepeatedToolOutput(compressionHeader?: string) { + const connection = await providersDb.createProviderConnection({ + provider: "openai", + apiKey: "test-key", + isActive: true, + }); + let capturedBody: { messages?: Array<{ content?: string }> } | null = null; + globalThis.fetch = async (_url: string | URL | Request, init?: RequestInit) => { + if (init?.body) capturedBody = JSON.parse(init.body as string) as typeof capturedBody; + return new Response( + JSON.stringify({ + choices: [{ message: { role: "assistant", content: "ok" } }], + usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + }; + const debugLines: string[] = []; + // chatCore reads request headers from a Headers instance or a plain object. + const headers: Record = compressionHeader + ? { "x-omniroute-compression": compressionHeader } + : {}; + + const result = await handleChatCore({ + body: { + model: "gpt-4", + stream: false, + messages: [ + { role: "tool", content: Array.from({ length: 8 }, () => "same noisy line").join("\n") }, + ], + }, + modelInfo: { provider: "openai", model: "gpt-4" }, + credentials: { apiKey: "test-key" }, + log: { + debug: (_tag: string, message: string) => debugLines.push(String(message)), + info: () => {}, + warn: () => {}, + error: () => {}, + }, + clientRawRequest: { endpoint: "/v1/chat/completions", headers }, + connectionId: connection.id, + onCredentialsRefreshed: () => {}, + onRequestSuccess: () => {}, + onStreamFailure: () => {}, + onDisconnect: () => {}, + userAgent: "test-agent", + comboName: null, + }); + + assert.ok(result.success, "request should succeed"); + assert.ok(capturedBody, "the upstream fetch should receive the request body"); + return { toolContent: capturedBody.messages?.[0]?.content ?? "", debugLines }; +} + +test("a request without a compression header runs the seeded default combo's safe pipeline", async () => { + await storeLegacyStackedSettings(); + + const { toolContent, debugLines } = await sendRepeatedToolOutput(); + + assert.ok(debugLines.includes(DEFAULT_COMBO_APPLIED), "the default-combo fallback should apply"); + assert.doesNotMatch(toolContent, RTK_MARKER); +}); + +test("a request without a compression header drops lossy steps added to the default combo", async () => { + await storeLegacyStackedSettings(); + const edited = compressionCombosDb.setEngineInDefaultCombo("rtk", true); + assert.ok( + edited?.pipeline.some((step) => step.engine === "rtk"), + "rtk should join the combo" + ); + + const { toolContent, debugLines } = await sendRepeatedToolOutput(); + + assert.ok(debugLines.includes(DEFAULT_COMBO_APPLIED), "the default-combo fallback should apply"); + assert.doesNotMatch(toolContent, RTK_MARKER); +}); + +test("allow-lossy still runs the lossy steps of an edited default combo", async () => { + await storeLegacyStackedSettings(); + compressionCombosDb.setEngineInDefaultCombo("rtk", true); + + const { toolContent, debugLines } = await sendRepeatedToolOutput("allow-lossy"); + + assert.ok(debugLines.includes(DEFAULT_COMBO_APPLIED), "the default-combo fallback should apply"); + assert.match(toolContent, RTK_MARKER); +}); + +test("a named combo chosen by the compression header is not replaced by the default combo", async () => { + await storeLegacyStackedSettings(); + compressionCombosDb.createCompressionCombo({ + name: "RTK only", + pipeline: [{ engine: "rtk", intensity: "standard" }], + }); + + const { toolContent, debugLines } = await sendRepeatedToolOutput("RTK only"); + + assert.ok(!debugLines.includes(DEFAULT_COMBO_APPLIED), "the header's combo should run"); + assert.match(toolContent, RTK_MARKER); +}); diff --git a/tests/unit/compression/engine-config-empty-number.test.ts b/tests/unit/compression/engine-config-empty-number.test.ts new file mode 100644 index 000000000000..acb32080af37 --- /dev/null +++ b/tests/unit/compression/engine-config-empty-number.test.ts @@ -0,0 +1,97 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { + buildEngineDetailUpdate, + withoutEmptyText, +} from "../../../src/shared/components/compression/engineConfigSave.ts"; + +type Settings = Record; + +describe("engine config emptied numbers", () => { + it("leaves an emptied number field out of the PUT body", () => { + const saved: Settings = { minSavingsThreshold: 0.5, maxTokensPerMessage: 4096 }; + const edited = { ...saved, minSavingsThreshold: Number.NaN }; + const body = buildEngineDetailUpdate("aggressive", saved, edited, { + minSavingsThreshold: 0.5, + maxTokensPerMessage: 1024, + }); + + assert.equal(body.maxTokensPerMessage, 1024); + assert.ok(!("minSavingsThreshold" in body)); + }); + + it("drops a stored value its number field clears", () => { + const saved: Settings = { minChars: 500 }; + const body = buildEngineDetailUpdate("ccr", saved, { minChars: Number.NaN }, { minChars: 500 }); + + assert.deepEqual(body, {}); + }); + + it("keeps a zero the operator typed", () => { + const saved: Settings = { minSavingsThreshold: 0.5 }; + const body = buildEngineDetailUpdate( + "aggressive", + saved, + { minSavingsThreshold: 0 }, + { minSavingsThreshold: 0.5 } + ); + + assert.deepEqual(body, { minSavingsThreshold: 0 }); + }); + + it("maps the emptied Lite cap to null so the stored cap is dropped", () => { + const saved: Settings = { compressToolResults: true, maxToolLength: 8000 }; + const body = buildEngineDetailUpdate( + "lite", + saved, + { ...saved, maxToolLength: Number.NaN }, + { ...saved } + ); + + assert.deepEqual(body, { compressToolResults: true, maxToolLength: null }); + }); + + it("drops emptied numbers from the preview config", () => { + const config = withoutEmptyText({ + minSavingsThreshold: Number.NaN, + compressionRate: 0, + modelPath: " ", + maxTokensPerMessage: 2048, + }); + + assert.deepEqual(config, { compressionRate: 0, maxTokensPerMessage: 2048 }); + }); + + it("keeps an overflow value in the body so the settings schema rejects it", () => { + const saved: Settings = { minSavingsThreshold: 0.5 }; + const body = buildEngineDetailUpdate( + "aggressive", + saved, + { minSavingsThreshold: Number("1e999") }, + { minSavingsThreshold: 0.5 } + ); + + assert.deepEqual(body, { minSavingsThreshold: Number.POSITIVE_INFINITY }); + }); + + it("keeps an overflow cap in the lite body, where the page range guard rejects it", () => { + const saved: Settings = { compressToolResults: true, maxToolLength: 8000 }; + const body = buildEngineDetailUpdate( + "lite", + saved, + { ...saved, maxToolLength: Number("1e999") }, + { ...saved } + ); + + assert.deepEqual(body, { + compressToolResults: true, + maxToolLength: Number.POSITIVE_INFINITY, + }); + }); + + it("keeps an overflow value in the preview config so the schema rejects it", () => { + const config = withoutEmptyText({ compressionRate: Number("1e999") }); + + assert.deepEqual(config, { compressionRate: Number.POSITIVE_INFINITY }); + }); +}); diff --git a/tests/unit/compression/engine-config-save.test.ts b/tests/unit/compression/engine-config-save.test.ts new file mode 100644 index 000000000000..407d08741404 --- /dev/null +++ b/tests/unit/compression/engine-config-save.test.ts @@ -0,0 +1,309 @@ +import { describe, it, beforeEach, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-engine-config-save-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../../src/lib/db/core.ts"); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); +const { compressionPreviewConfigSchema, compressionSettingsUpdateSchema } = + await import("../../../src/shared/validation/compressionConfigSchemas.ts"); +const { aggressiveEngine, liteEngine, ultraEngine } = + await import("../../../open-sse/services/compression/engines/cavemanAdapter.ts"); +const { headroomEngine } = + await import("../../../open-sse/services/compression/engines/headroom/index.ts"); +const { sessionDedupEngine } = + await import("../../../open-sse/services/compression/engines/session-dedup/index.ts"); +const { ccrEngine } = await import("../../../open-sse/services/compression/engines/ccr/index.ts"); +const { + seedEngineForm, + buildEngineDetailUpdate, + formAfterSave, + forgetSentEdits, + withoutEmptyText, +} = await import("../../../src/shared/components/compression/engineConfigSave.ts"); + +type Settings = Record; +type Engine = typeof aggressiveEngine; + +beforeEach(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } +}); + +async function readSettings(): Promise { + return (await getCompressionSettings()) as unknown as Settings; +} + +// Mirrors PUT /api/settings/compression: the route's schema validates the body, then the +// settings are written. +async function put(body: Settings): Promise { + const parsed = compressionSettingsUpdateSchema.safeParse(body); + if (!parsed.success) return 400; + await updateCompressionSettings(parsed.data as Parameters[0]); + return 200; +} + +// An engine page save: the form was seeded from `loadedSettings`, the operator applied +// `edits`, and the body is built from a read made at save time. +async function pageSave( + engine: Engine, + subKey: string, + loadedSettings: Settings, + edits: Settings +): Promise { + const loaded = seedEngineForm(engine.id, engine.getConfigSchema(), loadedSettings[subKey]); + const current = await readSettings(); + return put({ + [subKey]: buildEngineDetailUpdate(engine.id, loaded, { ...loaded, ...edits }, current[subKey]), + }); +} + +describe("engine config page save", () => { + it("keeps aggressive fields another page saved after the engine page loaded", async () => { + const loadedSettings = await readSettings(); + const now = (await readSettings()).aggressive as Settings & { + thresholds: Settings; + toolStrategies: Settings; + }; + const fromSettingsTab = { + ...now, + thresholds: { ...now.thresholds, fullSummary: 9 }, + toolStrategies: { ...now.toolStrategies, json: false }, + summarizerEnabled: false, + }; + assert.equal(await put({ aggressive: fromSettingsTab }), 200); + + const status = await pageSave(aggressiveEngine, "aggressive", loadedSettings, { + maxTokensPerMessage: 4096, + }); + + assert.equal(status, 200); + assert.deepEqual((await readSettings()).aggressive, { + ...fromSettingsTab, + maxTokensPerMessage: 4096, + }); + }); + + it("saves the ultra page on a default install, where the form shows an empty model path", async () => { + const loadedSettings = await readSettings(); + const before = loadedSettings.ultra as Settings; + assert.equal(before.modelPath, undefined); + + const status = await pageSave(ultraEngine, "ultra", loadedSettings, { compressionRate: 0.4 }); + + assert.equal(status, 200); + assert.deepEqual((await readSettings()).ultra, { ...before, compressionRate: 0.4 }); + }); + + it("keeps ultra.enabled and the model path when the ultra page saves another field", async () => { + const now = (await readSettings()).ultra as Settings; + assert.equal(await put({ ultra: { ...now, enabled: true, modelPath: "/models/ultra" } }), 200); + const loadedSettings = await readSettings(); + + const status = await pageSave(ultraEngine, "ultra", loadedSettings, { compressionRate: 0.4 }); + + assert.equal(status, 200); + assert.deepEqual((await readSettings()).ultra, { + ...now, + enabled: true, + modelPath: "/models/ultra", + compressionRate: 0.4, + }); + }); + + it("clears the stored model path when the field is emptied", async () => { + const now = (await readSettings()).ultra as Settings; + assert.equal(await put({ ultra: { ...now, modelPath: "/models/ultra" } }), 200); + const loadedSettings = await readSettings(); + + const status = await pageSave(ultraEngine, "ultra", loadedSettings, { modelPath: " " }); + + assert.equal(status, 200); + assert.deepEqual((await readSettings()).ultra, now); + }); + + it("never writes enabled from the page form", async () => { + const now = (await readSettings()).ultra as Settings; + assert.equal(await put({ ultra: { ...now, enabled: true } }), 200); + const loadedSettings = await readSettings(); + + const status = await pageSave(ultraEngine, "ultra", loadedSettings, { enabled: false }); + + assert.equal(status, 200); + assert.equal(((await readSettings()).ultra as Settings).enabled, true); + }); + + it("sends back the stored sub-object when nothing was edited", async () => { + const settings = await readSettings(); + const loaded = seedEngineForm( + aggressiveEngine.id, + aggressiveEngine.getConfigSchema(), + settings.aggressive + ); + + assert.deepEqual( + buildEngineDetailUpdate(aggressiveEngine.id, loaded, loaded, settings.aggressive), + settings.aggressive + ); + }); + + it("saves every engine page sub-object back unchanged when nothing was edited", async () => { + // Each save starts from the stored copy the settings read returns, so that copy has to + // pass the strict update schema for every engine page. + const pages: [Engine, string][] = [ + [liteEngine, "lite"], + [aggressiveEngine, "aggressive"], + [ultraEngine, "ultra"], + [headroomEngine, "headroom"], + [sessionDedupEngine, "sessionDedup"], + [ccrEngine, "ccr"], + ]; + for (const [engine, subKey] of pages) { + const loadedSettings = await readSettings(); + + assert.equal(await pageSave(engine, subKey, loadedSettings, {}), 200, subKey); + assert.deepEqual((await readSettings())[subKey], loadedSettings[subKey], subKey); + } + }); + + it("keeps the lite switch another page changed when the lite page edits the cap", async () => { + assert.equal(await put({ lite: { compressToolResults: true, maxToolLength: 8000 } }), 200); + const loadedSettings = await readSettings(); + assert.equal(await put({ lite: { compressToolResults: false } }), 200); + + const status = await pageSave(liteEngine, "lite", loadedSettings, { maxToolLength: 9000 }); + + assert.equal(status, 200); + assert.deepEqual((await readSettings()).lite, { + compressToolResults: false, + maxToolLength: 9000, + }); + }); + + it("clears the stored lite cap when the cap input is emptied", async () => { + assert.equal(await put({ lite: { compressToolResults: true, maxToolLength: 8000 } }), 200); + const loadedSettings = await readSettings(); + + // A cleared number input holds NaN. + const status = await pageSave(liteEngine, "lite", loadedSettings, { + maxToolLength: Number.NaN, + }); + + assert.equal(status, 200); + assert.deepEqual((await readSettings()).lite, { compressToolResults: true }); + }); + + it("leaves the lite cap unset when the lite page saves the switch", async () => { + const loadedSettings = await readSettings(); + const form = seedEngineForm(liteEngine.id, liteEngine.getConfigSchema(), loadedSettings.lite); + assert.equal("maxToolLength" in form, false); + + const status = await pageSave(liteEngine, "lite", loadedSettings, { + compressToolResults: false, + }); + + assert.equal(status, 200); + assert.deepEqual((await readSettings()).lite, { compressToolResults: false }); + }); + + it("sends a field again after a save whose response was lost", async () => { + const loadedSettings = await readSettings(); + const loaded = seedEngineForm( + ultraEngine.id, + ultraEngine.getConfigSchema(), + loadedSettings.ultra + ); + const sent = { ...loaded, compressionRate: 0.4 }; + // The server applies this save, but the response is lost, so the page reports a failure. + const current = (await readSettings()).ultra; + assert.equal( + await put({ ultra: buildEngineDetailUpdate(ultraEngine.id, loaded, sent, current) }), + 200 + ); + const baseline = forgetSentEdits(loaded, sent); + + // The operator puts the loaded value back and saves again. + const restored = { ...sent, compressionRate: loaded.compressionRate }; + const afterLostSave = (await readSettings()).ultra; + assert.equal( + await put({ + ultra: buildEngineDetailUpdate(ultraEngine.id, baseline, restored, afterLostSave), + }), + 200 + ); + + assert.deepEqual((await readSettings()).ultra, loadedSettings.ultra); + }); + + it("shows another page's value after a save, so typing the loaded value back sends it", async () => { + const loadedSettings = await readSettings(); + const schema = ultraEngine.getConfigSchema(); + const loaded = seedEngineForm(ultraEngine.id, schema, loadedSettings.ultra); + assert.equal( + await put({ ultra: { ...(loadedSettings.ultra as Settings), compressionRate: 0.9 } }), + 200 + ); + + // The page saves a different field. The PUT response is the settings read after the write. + const sent = { ...loaded, minScoreThreshold: 0.6 }; + const current = (await readSettings()).ultra; + assert.equal( + await put({ ultra: buildEngineDetailUpdate(ultraEngine.id, loaded, sent, current) }), + 200 + ); + const written = seedEngineForm(ultraEngine.id, schema, (await readSettings()).ultra); + const shown = formAfterSave(written, sent, sent); + assert.equal(shown.compressionRate, 0.9); + + const retyped = { ...shown, compressionRate: loaded.compressionRate }; + const afterSave = (await readSettings()).ultra; + assert.equal( + await put({ ultra: buildEngineDetailUpdate(ultraEngine.id, written, retyped, afterSave) }), + 200 + ); + + assert.deepEqual((await readSettings()).ultra, { + ...(loadedSettings.ultra as Settings), + minScoreThreshold: 0.6, + }); + }); + + it("keeps an edit typed while a save was in flight", () => { + const sent = { compressionRate: 0.4, modelPath: "" }; + const now = { compressionRate: 0.4, modelPath: "/models/ultra" }; + const written = { compressionRate: 0.4, modelPath: "", enabled: true }; + + assert.deepEqual(formAfterSave(written, sent, now), { + compressionRate: 0.4, + modelPath: "/models/ultra", + enabled: true, + }); + }); + + it("builds an ultra preview config that the preview schema accepts on a default install", async () => { + const settings = await readSettings(); + const form = seedEngineForm(ultraEngine.id, ultraEngine.getConfigSchema(), settings.ultra); + assert.equal(form.modelPath, ""); + + const parsed = compressionPreviewConfigSchema.safeParse({ ultra: withoutEmptyText(form) }); + + assert.equal(parsed.success, true); + }); +}); diff --git a/tests/unit/compression/engine-registry.test.ts b/tests/unit/compression/engine-registry.test.ts index e47e7f662545..aa565af7bc61 100644 --- a/tests/unit/compression/engine-registry.test.ts +++ b/tests/unit/compression/engine-registry.test.ts @@ -76,10 +76,9 @@ describe("compression engine registry contract", () => { assert.ok(aggressiveSchema.some((field) => field.key === "maxTokensPerMessage")); assert.ok(ultraSchema.some((field) => field.key === "compressionRate")); - // Lite exposes its OWN minimal schema (preserveSystemPrompt), NOT the aggressive + // Lite exposes its OWN minimal schema (tool truncation), NOT the aggressive // summarizer/threshold fields it previously leaked. const liteSchema = liteEngine.getConfigSchema(); - assert.ok(liteSchema.some((field) => field.key === "preserveSystemPrompt")); assert.ok( liteSchema.some((field) => field.key === "compressToolResults" && field.defaultValue === true) ); @@ -110,4 +109,27 @@ describe("compression engine registry contract", () => { assert.equal(ultraEngine.validateConfig({ compressionRate: 0.4 }).valid, true); assert.equal(ultraEngine.validateConfig({ compressionRate: 4 }).valid, false); }); + + it("does not expose a per-engine preserveSystemPrompt control", () => { + // System-prompt preservation is a global settings-level flag only: the engine + // apply sites read it from the global config and the settings normalizers drop + // any per-engine copy, so a per-engine checkbox never took effect and snapped + // back after save. Legacy data may still carry the key: the engine validators + // and the aggressive/ultra Zod step-config schemas keep accepting it (lite's + // strict liteConfigSchema never had a slot for it); no engine may surface it + // as a UI field. + for (const engine of [liteEngine, aggressiveEngine, ultraEngine]) { + assert.equal( + engine.getConfigSchema().some((field) => field.key === "preserveSystemPrompt"), + false, + `${engine.id} config schema must not expose preserveSystemPrompt` + ); + } + // The validators keep accepting legacy stored configs that carry the key — + // pinned so a future cleanup cannot silently drop that acceptance. + assert.equal(aggressiveEngine.validateConfig({ preserveSystemPrompt: true }).valid, true); + assert.equal(ultraEngine.validateConfig({ preserveSystemPrompt: true }).valid, true); + assert.equal(aggressiveEngine.validateConfig({ preserveSystemPrompt: "yes" }).valid, false); + assert.equal(ultraEngine.validateConfig({ preserveSystemPrompt: "yes" }).valid, false); + }); }); diff --git a/tests/unit/compression/engines-partial-write.test.ts b/tests/unit/compression/engines-partial-write.test.ts new file mode 100644 index 000000000000..8bdcf8ed7ecc --- /dev/null +++ b/tests/unit/compression/engines-partial-write.test.ts @@ -0,0 +1,127 @@ +// Two dashboard pages write the `engines` map: the Omniglyph page and the context settings +// panel. A write that carries only the engine it changed must leave every other engine as it +// was, or a toggle on one page turns off what the other page (or a failed GET) never loaded. +import { describe, it, beforeEach, afterEach, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-engines-partial-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../../src/lib/db/core.ts"); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); + +type SettingsUpdate = Parameters[0]; + +beforeEach(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +afterEach(() => { + core.resetDbInstance(); +}); + +after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } +}); + +describe("updateCompressionSettings engines partial writes", () => { + it("keeps the other stored engines when a write carries one engine", async () => { + await updateCompressionSettings({ + engines: { + rtk: { enabled: true, level: "standard" }, + caveman: { enabled: true, level: "full" }, + }, + }); + core.resetDbInstance(); + + await updateCompressionSettings({ engines: { omniglyph: { enabled: true } } }); + core.resetDbInstance(); + + const { engines, enginesExplicit } = await getCompressionSettings(); + assert.deepEqual(engines.rtk, { enabled: true, level: "standard" }); + assert.deepEqual(engines.caveman, { enabled: true, level: "full" }); + assert.deepEqual(engines.omniglyph, { enabled: true }); + assert.equal(enginesExplicit, true); + }); + + it("keeps a toggle made on one page when the other page writes a different engine", async () => { + await updateCompressionSettings({ engines: { rtk: { enabled: true } } }); + // The Omniglyph page turns its engine on after the panel loaded the map... + await updateCompressionSettings({ engines: { omniglyph: { enabled: true } } }); + // ...then the panel turns caveman on. + await updateCompressionSettings({ engines: { caveman: { enabled: true } } }); + core.resetDbInstance(); + + const { engines } = await getCompressionSettings(); + assert.equal(engines.omniglyph.enabled, true); + assert.equal(engines.rtk.enabled, true); + assert.equal(engines.caveman.enabled, true); + }); + + it("keeps a stored engine level when a write only sets enabled", async () => { + await updateCompressionSettings({ engines: { caveman: { enabled: true, level: "ultra" } } }); + core.resetDbInstance(); + + await updateCompressionSettings({ engines: { caveman: { enabled: false } } }); + core.resetDbInstance(); + + const { engines } = await getCompressionSettings(); + assert.deepEqual(engines.caveman, { enabled: false, level: "ultra" }); + }); + + it("keeps the engines derived from legacy settings on the first engines write", async () => { + await updateCompressionSettings({ cavemanConfig: { enabled: true } } as SettingsUpdate); + core.resetDbInstance(); + const before = await getCompressionSettings(); + assert.equal(before.enginesExplicit, false); + assert.equal(before.engines.caveman.enabled, true); + core.resetDbInstance(); + + await updateCompressionSettings({ engines: { omniglyph: { enabled: true } } }); + core.resetDbInstance(); + + const after = await getCompressionSettings(); + assert.equal(after.enginesExplicit, true); + assert.equal(after.engines.caveman.enabled, true); + assert.equal(after.engines.omniglyph.enabled, true); + }); + + it("treats a stored non-text engines row as absent and merges over the legacy-derived map", async () => { + await updateCompressionSettings({ cavemanConfig: { enabled: true } } as SettingsUpdate); + core.resetDbInstance(); + + // A BLOB in the engines key: the read path skips non-text rows, so the write path must + // not pick the row up as a merge base either and falls back to the derived map. + core + .getDbInstance() + .prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)") + .run("compression", "engines", Buffer.from([0x00, 0xff])); + core.resetDbInstance(); + + const before = await getCompressionSettings(); + assert.equal(before.enginesExplicit, false); + assert.equal(before.engines.caveman.enabled, true); + core.resetDbInstance(); + + await updateCompressionSettings({ engines: { omniglyph: { enabled: true } } }); + core.resetDbInstance(); + + const after = await getCompressionSettings(); + assert.equal(after.engines.caveman.enabled, true); + assert.equal(after.engines.omniglyph.enabled, true); + assert.equal(after.enginesExplicit, true); + }); +}); diff --git a/tests/unit/compression/legacy-engine-config-keys.test.ts b/tests/unit/compression/legacy-engine-config-keys.test.ts new file mode 100644 index 000000000000..901a67b129bc --- /dev/null +++ b/tests/unit/compression/legacy-engine-config-keys.test.ts @@ -0,0 +1,161 @@ +/** + * Legacy per-engine settings rows (aggressiveConfig / ultraConfig / headroomConfig) share + * their read case with the current keys (aggressive / ultra / headroom) in + * getCompressionSettings. The settings query has no ORDER BY, so when both rows exist the + * last one read wins — and with the (namespace, key) primary-key index the longer legacy + * key always comes second: every save returns 200, but GET keeps serving the legacy + * values. The current key must win when both rows exist. + */ +import { describe, it, beforeEach, afterEach, after } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +import { + DEFAULT_AGGRESSIVE_CONFIG, + DEFAULT_HEADROOM_CONFIG, + DEFAULT_ULTRA_CONFIG, +} from "../../../open-sse/services/compression/types.ts"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-compression-legacy-keys-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../../src/lib/db/core.ts"); +const { getCompressionSettings, updateCompressionSettings } = + await import("../../../src/lib/db/compression.ts"); + +function seedRow(key: string, value: unknown): void { + // Simulates a legacy row left by manual SQL, an outside tool, or a database from + // another build — no app write path creates these keys. + core + .getDbInstance() + .prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)") + .run("compression", key, JSON.stringify(value)); +} + +beforeEach(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +afterEach(() => { + core.resetDbInstance(); +}); + +after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } +}); + +describe("current engine settings rows win over legacy rows", () => { + it("aggressive beats legacy aggressiveConfig after a save", async () => { + seedRow("aggressiveConfig", { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 1111 }); + seedRow("aggressive", { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 2222 }); + + await updateCompressionSettings({ + aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 4096 }, + }); + + const settings = await getCompressionSettings(); + assert.equal(settings.aggressive?.maxTokensPerMessage, 4096); + }); + + it("ultra beats legacy ultraConfig after a save", async () => { + seedRow("ultraConfig", { ...DEFAULT_ULTRA_CONFIG, compressionRate: 0.1 }); + seedRow("ultra", { ...DEFAULT_ULTRA_CONFIG, compressionRate: 0.2 }); + + await updateCompressionSettings({ ultra: { ...DEFAULT_ULTRA_CONFIG, compressionRate: 0.9 } }); + + const settings = await getCompressionSettings(); + assert.equal(settings.ultra?.compressionRate, 0.9); + }); + + it("headroom beats legacy headroomConfig after a save", async () => { + seedRow("headroomConfig", { minRows: 3 }); + seedRow("headroom", { minRows: 4 }); + + await updateCompressionSettings({ headroom: { minRows: 50 } }); + + const settings = await getCompressionSettings(); + assert.equal(settings.headroom?.minRows, 50); + }); + + it("legacy aggressiveConfig still applies when no current row exists", async () => { + seedRow("aggressiveConfig", { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 1111 }); + + const settings = await getCompressionSettings(); + assert.equal(settings.aggressive?.maxTokensPerMessage, 1111); + }); + + it("legacy ultraConfig still applies when no current row exists", async () => { + seedRow("ultraConfig", { ...DEFAULT_ULTRA_CONFIG, compressionRate: 0.1 }); + + const settings = await getCompressionSettings(); + assert.equal(settings.ultra?.compressionRate, 0.1); + }); + + it("legacy headroomConfig still applies when no current row exists", async () => { + seedRow("headroomConfig", { minRows: 7 }); + + const settings = await getCompressionSettings(); + assert.equal(settings.headroom?.minRows, 7); + }); + + it("a corrupt current row still shadows a valid legacy row (deliberate)", async () => { + // Presence, not usability: a BLOB current row (the #13456 corruption mode) is skipped + // by the read loop but still suppresses the legacy row — the engine resets to defaults + // and the corruption warn is the operator's signal to re-save. + seedRow("aggressiveConfig", { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 1111 }); + core + .getDbInstance() + .prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)") + .run("compression", "aggressive", Buffer.from("corrupt-blob")); + + const settings = await getCompressionSettings(); + assert.equal( + settings.aggressive?.maxTokensPerMessage, + DEFAULT_AGGRESSIVE_CONFIG.maxTokensPerMessage + ); + }); + + it("an unparseable-JSON current row still shadows a valid legacy row (deliberate)", async () => { + // The other #13456 corruption mode: a string value that fails JSON.parse is skipped by + // the read loop, but the key is still "present" — so the legacy row stays suppressed. + seedRow("aggressiveConfig", { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 1111 }); + core + .getDbInstance() + .prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)") + .run("compression", "aggressive", "{not valid json"); + + const settings = await getCompressionSettings(); + assert.equal( + settings.aggressive?.maxTokensPerMessage, + DEFAULT_AGGRESSIVE_CONFIG.maxTokensPerMessage + ); + }); + + it("an unparseable-JSON legacy row resets the engine to defaults", async () => { + core + .getDbInstance() + .prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)") + .run("compression", "ultraConfig", "{not valid json"); + + const settings = await getCompressionSettings(); + assert.equal(settings.ultra?.compressionRate, DEFAULT_ULTRA_CONFIG.compressionRate); + }); + + it("non-object legacy value resets the engine to defaults", async () => { + seedRow("headroomConfig", 5); + + const settings = await getCompressionSettings(); + assert.equal(settings.headroom?.minRows, DEFAULT_HEADROOM_CONFIG.minRows); + }); +}); diff --git a/tests/unit/compression/llmlingua-worker-resolution.test.ts b/tests/unit/compression/llmlingua-worker-resolution.test.ts index 356ef718fd8e..9827bc576fed 100644 --- a/tests/unit/compression/llmlingua-worker-resolution.test.ts +++ b/tests/unit/compression/llmlingua-worker-resolution.test.ts @@ -78,6 +78,28 @@ test("resolveWorkerFile returns an existing onnxWorker file (no import.meta.url) assert.ok(/onnxWorker\.(t|j)s$/.test(workerFile), `must point at onnxWorker: ${workerFile}`); }); +test("resolveWorkerFile chooses TS over a tracked JS placeholder in the source tree", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "slm-placeholder-")); + const originalCwd = process.cwd(); + const originalArgv1 = process.argv[1]; + try { + const dir = path.join(root, "open-sse", "services", "compression", "engines", "llmlingua"); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, "onnxWorker.js"), "export {};\n"); + fs.writeFileSync(path.join(dir, "onnxWorker.ts"), "// source worker\n"); + process.chdir(root); + process.argv[1] = path.join(root, "server.js"); + assert.deepEqual(resolveWorkerFile(), { + workerFile: path.join(dir, "onnxWorker.ts"), + execArgv: ["--import", "tsx/esm"], + }); + } finally { + process.argv[1] = originalArgv1; + process.chdir(originalCwd); + fs.rmSync(root, { recursive: true, force: true }); + } +}); + test("depsAvailable is true when @atjsh/llmlingua-2 is installed (symlinked node_modules)", () => { assert.equal(depsAvailable(), true); }); diff --git a/tests/unit/compression/output-styles-anthropic-placement.test.ts b/tests/unit/compression/output-styles-anthropic-placement.test.ts new file mode 100644 index 000000000000..631433f5d133 --- /dev/null +++ b/tests/unit/compression/output-styles-anthropic-placement.test.ts @@ -0,0 +1,216 @@ +// #12584: on an Anthropic-shaped body the output-style instruction must reach upstream in the +// top-level system field, never as a system turn in messages[]. Each body carries a tool, so the +// two shapes with a system prompt take the mid-conversation-system path, where only a leading +// run of system turns is hoisted out of messages[]. Those two shapes also carry a later system +// turn, which that path keeps in messages[] and the default Claude path would hoist. +// +// Scope: the placement contract is asserted for the English ("en") instruction only; the +// localized instruction texts are not covered here. +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import { createTempDataDir } from "../../_setup/tempDataDir.ts"; + +const { dir: TEST_DATA_DIR, cleanup } = createTempDataDir("omniroute-style-placement-"); +process.env.REQUIRE_API_KEY = "false"; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-style-placement-secret"; + +const core = await import("../../../src/lib/db/core.ts"); +const readCacheDb = await import("../../../src/lib/db/readCache.ts"); +const compressionDb = await import("../../../src/lib/db/compression.ts"); +const { handleChatCore } = await import("../../../open-sse/handlers/chatCore.ts"); +const { isClaudeCodeSemanticPassthroughRequest } = + await import("../../../open-sse/handlers/chatCore/passthroughHelpers.ts"); +const { shouldUseMidConversationSystem } = + await import("../../../open-sse/executors/claudeIdentity.ts"); +const { FORMATS } = await import("../../../open-sse/translator/formats.ts"); +const { resetAllCircuitBreakers } = await import("../../../src/shared/utils/circuitBreaker.ts"); +const { CAVEMAN_INSTRUCTION_BY_LANGUAGE } = + await import("../../../open-sse/services/compression/outputMode.ts"); +const { OUTPUT_STYLE_MARKER } = + await import("../../../open-sse/services/compression/outputStyles/apply.ts"); +const { waitForCallLogSaves, closeCallLogSaves } = + await import("../../../src/lib/usage/callLogs.ts"); + +const originalFetch = globalThis.fetch; +const USER_AGENT = "claude-code/2.1.154"; +const LATER_SYSTEM_TURN = "Answer in one short paragraph."; + +// The Anthropic `system` field is a string or an array of text blocks; anything else fails. +function systemText(system: unknown): string { + if (typeof system === "string") return system; + assert.ok(Array.isArray(system), "the upstream system field should be a string or block array"); + return system + .map((block: { type?: string; text?: string }) => { + assert.equal(block?.type, "text", "every upstream system block should be a text block"); + assert.ok( + typeof block.text === "string", + "every upstream system block should carry string text" + ); + return block.text; + }) + .join("\n"); +} + +test.beforeEach(async () => { + globalThis.fetch = originalFetch; + resetAllCircuitBreakers(); + readCacheDb.invalidateDbCache(); + // Each request saves a call log in the background; it must land before the DB is reset. + assert.ok(await waitForCallLogSaves(30_000), "the previous test's call-log saves should finish"); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +test.after(async () => { + globalThis.fetch = originalFetch; + // Drain the last test's in-flight save before closing, so it lands instead of erroring. + assert.ok(await waitForCallLogSaves(30_000), "the last test's call-log save should finish"); + // Stops the call-log writer too, which otherwise keeps the process alive for its idle timeout. + await closeCallLogSaves(2_000); + await cleanup(); +}); + +for (const [shape, system] of [ + ["a string system", "You are Claude."], + ["a content-block system", [{ type: "text", text: "You are Claude." }]], + ["no system", undefined], +] as const) { + test(`chatCore: output style lands in the top-level system field on an Anthropic body with ${shape} (#12584)`, async () => { + await compressionDb.updateCompressionSettings({ + enabled: true, + defaultMode: "off", + autoTriggerTokens: 0, + cavemanOutputMode: { + enabled: true, + intensity: "full", + autoClarity: true, + }, + }); + + let capturedBody = null as { + system?: unknown; + messages?: Array<{ role?: string; content?: unknown }>; + } | null; + globalThis.fetch = async (url: string | URL | Request, init?: RequestInit) => { + if (String(url).includes("/v1/messages") && init?.body) { + capturedBody = JSON.parse(init.body as string); + } + return Response.json({ + id: "msg_test", + type: "message", + role: "assistant", + model: "claude-opus-5", + content: [{ type: "text", text: "OK" }], + usage: { input_tokens: 4, output_tokens: 1 }, + }); + }; + + const body = { + model: "claude-opus-5", + max_tokens: 64, + stream: false, + ...(system === undefined ? {} : { system }), + tools: [ + { + name: "Read", + description: "Read a file", + input_schema: { type: "object", properties: {} }, + }, + ], + messages: [ + { role: "user", content: "Summarize this implementation." }, + ...(system === undefined + ? [] + : [ + { role: "assistant", content: "Which file?" }, + { role: "system", content: LATER_SYSTEM_TURN }, + { role: "user", content: "src/app.ts" }, + ]), + ], + }; + const headers = new Headers({ + accept: "application/json", + "content-type": "application/json", + "user-agent": USER_AGENT, + }); + + // The fixture must keep taking the routing these assertions depend on. + assert.equal( + isClaudeCodeSemanticPassthroughRequest({ + provider: "claude", + sourceFormat: FORMATS.CLAUDE, + targetFormat: FORMATS.CLAUDE, + headers, + userAgent: USER_AGENT, + }), + true, + "the fixture should take the Claude Code semantic passthrough" + ); + assert.equal( + shouldUseMidConversationSystem(body, body.model), + system !== undefined, + "only the shapes with a system prompt should take the mid-conversation-system path" + ); + + try { + const result = await handleChatCore({ + body: structuredClone(body), + modelInfo: { provider: "claude", model: "claude-opus-5", extendedContext: false }, + credentials: { apiKey: "test-claude-key", providerSpecificData: {} }, + log: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} }, + clientRawRequest: { endpoint: "/v1/messages", body: structuredClone(body), headers }, + connectionId: null, + onCredentialsRefreshed: () => {}, + onRequestSuccess: () => {}, + onStreamFailure: () => {}, + onDisconnect: () => {}, + userAgent: USER_AGENT, + comboName: null, + }); + + assert.ok(result.success, "Request should succeed"); + assert.ok(capturedBody, "the upstream request should be captured"); + const upstreamSystem = systemText(capturedBody.system); + assert.ok( + upstreamSystem.includes( + `${OUTPUT_STYLE_MARKER}\n${CAVEMAN_INSTRUCTION_BY_LANGUAGE.en.full}` + ), + "the full output-style instruction should land in the top-level system field" + ); + assert.equal( + JSON.stringify(capturedBody).split(OUTPUT_STYLE_MARKER).length - 1, + 1, + "the instruction should reach upstream exactly once" + ); + const upstreamMessages = capturedBody.messages ?? []; + assert.deepEqual( + upstreamMessages.map((message) => message.role), + body.messages.map((message) => message.role), + "messages[] should carry the client's turns and nothing else" + ); + assert.ok( + JSON.stringify(upstreamMessages[0]).includes("Summarize this implementation."), + "the user's text should survive" + ); + if (system !== undefined) { + assert.ok( + upstreamSystem.includes("You are Claude."), + "the client's own system prompt should survive" + ); + assert.ok( + JSON.stringify(upstreamMessages[2]).includes(LATER_SYSTEM_TURN), + "the later system turn should stay in messages[] on the mid-conversation-system path" + ); + assert.equal( + upstreamSystem.includes(LATER_SYSTEM_TURN), + false, + "the later system turn should not be hoisted into the top-level system field" + ); + } + } finally { + globalThis.fetch = originalFetch; + } + }); +} diff --git a/tests/unit/compression/output-styles-apply.test.ts b/tests/unit/compression/output-styles-apply.test.ts index 10d836b6c880..efc0de1258f9 100644 --- a/tests/unit/compression/output-styles-apply.test.ts +++ b/tests/unit/compression/output-styles-apply.test.ts @@ -151,8 +151,9 @@ test("Responses input (no messages) uses instructions field", () => { }); test("terse-prose localizes per language (back-compat with the legacy caveman packs)", () => { - // Regression guard: the legacy caveman output mode localized to en/pt-BR/ja/id; the - // migrated terse-prose style must inject the SAME localized text, not fall back to English. + // Regression guard: the legacy caveman output mode carries a localized text per language; + // the migrated terse-prose style must inject the SAME localized text, not fall back to + // English. (Every language × level pair is pinned in output-styles-legacy-parity.test.ts.) const ptBR = applyOutputStyles( { messages: [{ role: "user", content: "Resuma os logs." }] }, sel(["terse-prose", "lite"]), diff --git a/tests/unit/compression/output-styles-auto-clarity.test.ts b/tests/unit/compression/output-styles-auto-clarity.test.ts new file mode 100644 index 000000000000..10bc3dba45bc --- /dev/null +++ b/tests/unit/compression/output-styles-auto-clarity.test.ts @@ -0,0 +1,119 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { OUTPUT_STYLE_MARKER } from "../../../open-sse/services/compression/outputStyles/apply.ts"; +import type { OutputStyleSelectionEntry } from "../../../open-sse/services/compression/types.ts"; +import { createChatPipelineHarness } from "../../integration/_chatPipelineHarness.ts"; + +// Drives the full chat handler, so the Auto-Clarity toggle is checked where chatCore reads it. +const { + buildOpenAIResponse, + buildRequest, + cleanup, + combosDb, + handleChat, + resetStorage, + seedConnection, +} = await createChatPipelineHarness("output-styles-auto-clarity"); +const compressionDb = await import("../../../src/lib/db/compression.ts"); +const compressionCombosDb = await import("../../../src/lib/db/compressionCombos.ts"); + +const PANEL_STYLES: OutputStyleSelectionEntry[] = [ + { id: "terse-prose", level: "full" }, + { id: "less-code", level: "full" }, +]; +const ROUTING_COMBO = "auto-clarity-combo"; +const SECURITY_TURN = "Explain this security vulnerability in detail."; +const BENIGN_TURN = "Explain how a for loop works."; +const STYLE_SOURCES = [ + "legacy caveman switch", + "Output Styles panel", + "combo with an assigned compression combo", +] as const; +type StyleSource = (typeof STYLE_SOURCES)[number]; + +test.beforeEach(resetStorage); +test.after(cleanup); + +// A write fills an undefined autoClarity from the stored row, or the shipped default on a fresh +// DB. resetStorage gives every case a fresh DB, so the "left unset" rows check the default. +async function styleInstructionReachesUpstream( + autoClarity: boolean | undefined, + source: StyleSource, + userTurn: string +) { + await compressionDb.updateCompressionSettings({ + enabled: true, + defaultMode: "off", + autoTriggerTokens: 0, + ...(source === "Output Styles panel" ? { outputStyles: PANEL_STYLES } : {}), + cavemanOutputMode: { + enabled: source === "legacy caveman switch", + intensity: "full", + autoClarity, + }, + }); + await seedConnection("openai"); + const viaCombo = source === "combo with an assigned compression combo"; + if (viaCombo) { + const routingCombo = await combosDb.createCombo({ + name: ROUTING_COMBO, + strategy: "priority", + models: ["openai/gpt-4o-mini"], + }); + const compressionCombo = compressionCombosDb.createCompressionCombo({ + pipeline: [{ engine: "caveman", intensity: "lite" }], + outputMode: true, + }); + assert.equal( + compressionCombosDb.assignRoutingCombo(compressionCombo.id, routingCombo.id as string), + true + ); + } + + const upstreamBodies: Array<{ messages?: Array<{ role?: string; content?: string }> }> = []; + globalThis.fetch = async (url: string | URL | Request, init: RequestInit = {}) => { + // The compression combo turns on a pipeline that posts compression events to the live + // dashboard (/__omniroute_event), so keep only the chat call. + if (String(url).endsWith("/chat/completions")) { + upstreamBodies.push(JSON.parse(String(init.body))); + } + return buildOpenAIResponse("ok"); + }; + + const response = await handleChat( + buildRequest({ + body: { + model: viaCombo ? ROUTING_COMBO : "openai/gpt-4o-mini", + stream: false, + messages: [{ role: "user", content: userTurn }], + }, + }) + ); + + assert.equal(response.status, 200); + assert.equal(upstreamBodies.length, 1, "exactly one upstream chat request"); + return (upstreamBodies[0].messages ?? []).some( + (message) => message.role === "system" && (message.content ?? "").includes(OUTPUT_STYLE_MARKER) + ); +} + +for (const source of STYLE_SOURCES) { + test(`${source}: Auto-Clarity off keeps the styles on a security-topic turn`, async () => { + assert.equal(await styleInstructionReachesUpstream(false, source, SECURITY_TURN), true); + }); + + // Right after the "off" case, so a stored row leaking between cases would fail this one. + test(`${source}: Auto-Clarity left unset skips the styles on a security-topic turn`, async () => { + assert.equal(await styleInstructionReachesUpstream(undefined, source, SECURITY_TURN), false); + }); + + test(`${source}: Auto-Clarity on skips the styles on a security-topic turn`, async () => { + assert.equal(await styleInstructionReachesUpstream(true, source, SECURITY_TURN), false); + }); + + // The bypass is security-topic-scoped, so with the toggle on a benign turn keeps the styles. + test(`${source}: Auto-Clarity on keeps the styles on a benign turn`, async () => { + assert.equal(await styleInstructionReachesUpstream(true, source, BENIGN_TURN), true); + }); +} diff --git a/tests/unit/compression/output-styles-backcompat.test.ts b/tests/unit/compression/output-styles-backcompat.test.ts index 195e58152271..95c8794511b4 100644 --- a/tests/unit/compression/output-styles-backcompat.test.ts +++ b/tests/unit/compression/output-styles-backcompat.test.ts @@ -1,7 +1,10 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import { resolveOutputStyleSelection } from "../../../open-sse/services/compression/outputStyles/backCompat.ts"; -import { applyOutputStyles } from "../../../open-sse/services/compression/outputStyles/apply.ts"; +import { + applyOutputStyles, + OUTPUT_STYLE_MARKER, +} from "../../../open-sse/services/compression/outputStyles/apply.ts"; import { applyCavemanOutputMode } from "../../../open-sse/services/compression/outputMode.ts"; test("explicit outputStyles win when present", () => { @@ -29,21 +32,53 @@ test("disabled legacy mode and no styles → empty selection", () => { assert.deepEqual(resolveOutputStyleSelection({}), []); }); -test("golden: legacy config injects the same prose instruction as the old injector", () => { - const body = { messages: [{ role: "user", content: "Summarize this API response." }] }; - const legacy = applyCavemanOutputMode(structuredClone(body), { - enabled: true, - intensity: "full", - autoClarity: true, - }); - const sel = resolveOutputStyleSelection({ - cavemanOutputMode: { enabled: true, intensity: "full", autoClarity: true }, - }); - const next = applyOutputStyles(structuredClone(body), sel); +const LEGACY_MARKER = "[OmniRoute Caveman Output Mode]"; + +// Runs one user turn through the legacy injector and the unified one with the same config. +// Language scope: "en" only — the legacy-vs-unified identity contracts below are not asserted +// for the localized (hu/ja/zh) instruction texts. +function injectBoth(content: string, autoClarity: boolean) { + const body = { messages: [{ role: "user", content }] }; + const config = { enabled: true, intensity: "full" as const, autoClarity }; + const legacy = applyCavemanOutputMode(structuredClone(body), config); + const next = applyOutputStyles( + structuredClone(body), + resolveOutputStyleSelection({ cavemanOutputMode: config }), + "en", + { autoClarity } + ); + return { body, legacy, next }; +} - const legacyInstr = String(legacy.body.messages?.[0]?.content); - const nextInstr = String(next.body.messages?.[0]?.content); +// The instruction text below the marker line. With no system turn in the body, both injectors +// append it as a trailing system message (#13383), so it is found by role, not at messages[0]. +function instructionText( + result: { body: { messages?: Array<{ role?: string; content?: unknown }> } }, + marker: string +) { + const injected = result.body.messages?.find((message) => message.role === "system"); + assert.ok(injected, "the injector added a system message"); + const text = String(injected.content); + assert.ok(text.startsWith(`${marker}\n`), `the instruction starts with ${marker}`); + return text.slice(marker.length + 1); +} + +test("golden: legacy config injects the same prose instruction as the old injector", () => { + const { legacy, next } = injectBoth("Summarize this API response.", true); // The prose instruction text (minus the marker line) must be byte-identical. - const strip = (s: string) => s.split("\n").slice(1).join("\n"); - assert.equal(strip(nextInstr), strip(legacyInstr)); + assert.equal(instructionText(next, OUTPUT_STYLE_MARKER), instructionText(legacy, LEGACY_MARKER)); +}); + +test("golden: with Auto-Clarity off, both injectors apply the same instruction on a bypass turn", () => { + const { legacy, next } = injectBoth("Explain this security vulnerability in detail.", false); + assert.equal(instructionText(next, OUTPUT_STYLE_MARKER), instructionText(legacy, LEGACY_MARKER)); +}); + +test("golden: with Auto-Clarity on, both injectors skip a bypass turn as security_warning", () => { + const { body, legacy, next } = injectBoth("Explain this security vulnerability in detail.", true); + for (const result of [legacy, next]) { + assert.equal(result.applied, false); + assert.equal(result.skippedReason, "security_warning"); + assert.deepEqual(result.body, body); + } }); diff --git a/tests/unit/compression/output-styles-boundaries.test.ts b/tests/unit/compression/output-styles-boundaries.test.ts index 760e83ef9dff..7090bf905b34 100644 --- a/tests/unit/compression/output-styles-boundaries.test.ts +++ b/tests/unit/compression/output-styles-boundaries.test.ts @@ -183,6 +183,29 @@ test("terse-prose alone stays byte-identical to the legacy caveman injection (D- assert.equal(text, `${OUTPUT_STYLE_MARKER}\n${legacy}`); }); +test("the boundary block keeps the spacing the last style's own text puts before it", () => { + // ja and zh texts run straight into SHARED_BOUNDARIES. less-code has no hu text, so its + // English text, with one space, decides the hu case. + const cases: Array<[string, OutputStyleSelectionEntry[], string]> = [ + ["ja", sel(["terse-prose", "full"], ["less-code", "full"]), "。"], + ["zh", sel(["terse-prose", "full"], ["ponytail", "full"]), "。"], + ["zh", sel(["terse-cjk", "full"]), "。"], + ["hu", sel(["terse-prose", "full"], ["less-code", "full"]), " "], + ["ja", sel(["terse-prose", "ultra"], ["less-code", "ultra"]), "。"], + ]; + for (const [language, selection, before] of cases) { + const text = injected( + { messages: [{ role: "user", content: "Refactor this module." }] }, + selection, + language + ); + const at = text.indexOf(SHARED_BOUNDARIES); + assert.ok(at > 1, `${language}: SHARED_BOUNDARIES is present`); + assert.equal(text[at - 1], before, `${language}: character before SHARED_BOUNDARIES`); + assert.notEqual(text[at - 2], " ", `${language}: at most one space before SHARED_BOUNDARIES`); + } +}); + test("boundary emission is deterministic (same selection + language → byte-identical)", () => { const make = () => injected( diff --git a/tests/unit/compression/output-styles-i18n-matrix.test.ts b/tests/unit/compression/output-styles-i18n-matrix.test.ts index 17b8d9f1af9f..41a70967ed33 100644 --- a/tests/unit/compression/output-styles-i18n-matrix.test.ts +++ b/tests/unit/compression/output-styles-i18n-matrix.test.ts @@ -38,8 +38,8 @@ const KNOWN_ENGLISH_ONLY: Record = {}; */ const BASELINE_LANGUAGES: Record = { // terse-prose reuses CAVEMAN_INSTRUCTION_BY_LANGUAGE (outputMode.ts), which - // localizes to pt-BR/es/de/fr/it/ru/zh/ja/id/vi — keep the two in sync. - "terse-prose": ["pt-BR", "es", "de", "fr", "it", "ru", "zh", "ja", "id", "vi"], + // localizes to pt-BR/es/de/fr/it/ru/zh/ja/id/vi/hu — keep the two in sync. + "terse-prose": ["pt-BR", "es", "de", "fr", "it", "ru", "zh", "ja", "id", "vi", "hu"], "less-code": ["pt-BR", "vi", "ja", "id", "es", "de", "fr", "it", "ru", "zh"], ponytail: ["pt-BR", "vi", "ja", "id", "es", "de", "fr", "it", "ru", "zh"], "i-have-adhd": ["pt-BR", "vi", "ja", "id", "es", "de", "fr", "it", "ru", "zh"], diff --git a/tests/unit/compression/output-styles-legacy-parity.test.ts b/tests/unit/compression/output-styles-legacy-parity.test.ts new file mode 100644 index 000000000000..ae49931368a7 --- /dev/null +++ b/tests/unit/compression/output-styles-legacy-parity.test.ts @@ -0,0 +1,67 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + applyCavemanOutputMode, + CAVEMAN_INSTRUCTION_BY_LANGUAGE, +} from "../../../open-sse/services/compression/outputMode.ts"; +import { resolveOutputStyleSelection } from "../../../open-sse/services/compression/outputStyles/backCompat.ts"; +import { + applyOutputStyles, + OUTPUT_STYLE_MARKER, +} from "../../../open-sse/services/compression/outputStyles/apply.ts"; + +const LEGACY_MARKER = "[OmniRoute Caveman Output Mode]"; +const LEVELS = ["lite", "full", "ultra"] as const; + +// The instruction text below the marker line. The instruction lands in a top-level +// `system` field or a trailing system message (#13383), so every text surface is +// gathered and read from the marker on — a placement change cannot fake a failure here. +function instructionText( + result: { + applied: boolean; + body: { messages?: Array<{ role?: string; content?: unknown }>; system?: unknown }; + }, + marker: string +): string { + assert.equal(result.applied, true, "the injector applied the instruction"); + const parts: string[] = []; + if (typeof result.body.system === "string") parts.push(result.body.system); + else if (Array.isArray(result.body.system)) { + for (const block of result.body.system) { + const text = (block as { text?: unknown } | null)?.text; + if (typeof text === "string") parts.push(text); + } + } + for (const message of result.body.messages ?? []) { + if (typeof message.content === "string") parts.push(message.content); + } + const joined = parts.join("\n"); + const markerAt = joined.indexOf(marker); + assert.ok( + markerAt >= 0 && joined[markerAt + marker.length] === "\n", + `the instruction starts with ${marker}` + ); + return joined.slice(markerAt + marker.length + 1); +} + +// The languages come from the legacy table at runtime, so a language pack added there without a +// matching terse-prose translation fails here. +for (const language of Object.keys(CAVEMAN_INSTRUCTION_BY_LANGUAGE)) { + for (const intensity of LEVELS) { + test(`legacy ${language} ${intensity}: the unified injector writes the legacy text below its marker`, () => { + const body = { messages: [{ role: "user", content: "Summarize this API response." }] }; + const config = { enabled: true, intensity, autoClarity: true }; + const legacy = applyCavemanOutputMode(structuredClone(body), config, language); + const next = applyOutputStyles( + structuredClone(body), + resolveOutputStyleSelection({ cavemanOutputMode: config }), + language, + { autoClarity: true } + ); + assert.equal( + instructionText(next, OUTPUT_STYLE_MARKER), + instructionText(legacy, LEGACY_MARKER) + ); + }); + } +} diff --git a/tests/unit/compression/rtk-toml-compatibility.test.ts b/tests/unit/compression/rtk-toml-compatibility.test.ts index cc5b9cacef0c..891c93f08ca3 100644 --- a/tests/unit/compression/rtk-toml-compatibility.test.ts +++ b/tests/unit/compression/rtk-toml-compatibility.test.ts @@ -240,7 +240,9 @@ match_command = "^scope-tool" assert.equal(installed.installedPath, "rtk/filters.toml"); assert.equal(fs.readFileSync(target, "utf8"), SAMPLE); - assert.equal(fs.statSync(target).mode & 0o777, 0o600); + if (process.platform !== "win32") { + assert.equal(fs.statSync(target).mode & 0o777, 0o600); + } assert.throws( () => installGlobalRtkTomlV1(SAMPLE), (error: unknown) => diff --git a/tests/unit/copilot-cli-version-fetch.test.ts b/tests/unit/copilot-cli-version-fetch.test.ts new file mode 100644 index 000000000000..83784619ce3e --- /dev/null +++ b/tests/unit/copilot-cli-version-fetch.test.ts @@ -0,0 +1,90 @@ +/** + * Registry lookup for the advertised GitHub Copilot CLI version. + * + * Copilot gates models on the version OmniRoute sends and 400s a pin that is + * behind. The supported 1.0.91 pin is the floor; a newer dotted triple from + * `@github/copilot` on npm replaces it for 6 hours, in both `copilot/` + * and `GitHubCopilotChat/`. A rejected fetch, a non-triple, or an older + * publish stays on the pin. GITHUB_COPILOT_CLI_VERSION in the environment + * beats both. + */ +import assert from "node:assert/strict"; +import test from "node:test"; + +const copilot = await import("../../open-sse/config/providerHeaderProfiles.ts"); + +const NPM_LATEST = "https://registry.npmjs.org/@github/copilot/latest"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +async function withEnv( + entries: Record, + fn: () => T | Promise +): Promise { + const previous = new Map(); + for (const [key, value] of Object.entries(entries)) { + previous.set(key, process.env[key]); + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + try { + return await fn(); + } finally { + for (const [key, value] of previous.entries()) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +test.afterEach(() => { + copilot.resetGitHubCopilotCliVersionCache(); +}); + +test("a fetched version newer than the pin appears in both Copilot header strings", async () => { + const urls: string[] = []; + const fetchMock = async (url: string | URL | Request) => { + urls.push(String(url)); + return jsonResponse({ version: "1.0.92" }); + }; + + await withEnv({ GITHUB_COPILOT_CLI_VERSION: undefined }, async () => { + assert.equal(await copilot.resolveGitHubCopilotCliVersion(fetchMock as typeof fetch), "1.0.92"); + assert.equal(copilot.getGitHubCopilotCliVersion(), "1.0.92"); + const headers = copilot.getGitHubCopilotChatHeaders(); + assert.equal(headers["editor-version"], "copilot/1.0.92"); + assert.equal(headers["user-agent"], `copilot/1.0.92 (${process.platform}) term/unknown`); + assert.equal(copilot.getGitHubCopilotChatUserAgent(), "GitHubCopilotChat/1.0.92"); + const internal = copilot.getGitHubCopilotInternalUserHeaders("token gh"); + assert.equal(internal["User-Agent"], "GitHubCopilotChat/1.0.92"); + assert.equal(internal["Editor-Version"], "copilot/1.0.92"); + }); + assert.equal(urls.length, 1); + assert.equal(urls[0], NPM_LATEST); + assert.equal(copilot.GITHUB_COPILOT_CLI_VERSION, "1.0.91"); +}); + +test("a fetch that rejects keeps the pinned 1.0.91", async () => { + const fetchMock = async () => { + throw new Error("registry unreachable"); + }; + + await withEnv({ GITHUB_COPILOT_CLI_VERSION: undefined }, async () => { + assert.equal(await copilot.resolveGitHubCopilotCliVersion(fetchMock as typeof fetch), "1.0.91"); + assert.equal(copilot.getGitHubCopilotCliVersion(), copilot.GITHUB_COPILOT_CLI_VERSION); + const headers = copilot.getGitHubCopilotChatHeaders(); + assert.equal(headers["editor-version"], "copilot/1.0.91"); + assert.equal(copilot.getGitHubCopilotChatUserAgent(), "GitHubCopilotChat/1.0.91"); + }); +}); diff --git a/tests/unit/cursor-agent-cli-version.test.ts b/tests/unit/cursor-agent-cli-version.test.ts index 4ec68f69e49e..63863f1d9d1a 100644 --- a/tests/unit/cursor-agent-cli-version.test.ts +++ b/tests/unit/cursor-agent-cli-version.test.ts @@ -18,7 +18,7 @@ const { resetCursorAgentCliVersionTestHooks, } = await import("../../open-sse/utils/cursorAgentCliVersion.ts"); -function withEnv(vars: Record, fn: () => void) { +function withEnv(vars: Record, fn: () => void | Promise) { const saved: Record = {}; for (const key of Object.keys(vars)) { saved[key] = process.env[key]; @@ -26,13 +26,22 @@ function withEnv(vars: Record, fn: () => void) { if (next === undefined) delete process.env[key]; else process.env[key] = next; } - try { - fn(); - } finally { + const restore = () => { for (const key of Object.keys(saved)) { if (saved[key] === undefined) delete process.env[key]; else process.env[key] = saved[key]; } + }; + try { + const result = fn(); + if (result && typeof (result as Promise).then === "function") { + return (result as Promise).finally(restore); + } + restore(); + return result; + } catch (err) { + restore(); + throw err; } } @@ -98,64 +107,75 @@ test("detectCursorAgentCliVersionFromFs uses CURSOR_DATA_DIR versions when no sh } }); -test("getCursorAgentCliVersion env override wins", () => { - withEnv({ CURSOR_AGENT_CLI_VERSION: "2026.01.02-abc1234" }, () => { +test("getCursorAgentCliVersion env override wins", async () => { + await withEnv({ CURSOR_AGENT_CLI_VERSION: "2026.01.02-abc1234" }, async () => { resetCursorAgentCliVersionCache(); - assert.equal(getCursorAgentCliVersion(), "2026.01.02-abc1234"); + assert.equal(await getCursorAgentCliVersion(), "2026.01.02-abc1234"); }); resetCursorAgentCliVersionCache(); }); -test("getCursorAgentCliVersion ignores invalid env and uses pin when FS empty", () => { +test("getCursorAgentCliVersion ignores invalid env and uses pin when FS empty", async () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-home-pin-")); + const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-cache-pin-")); try { - withEnv( + configureCursorAgentCliVersionForTests({ + cacheDir, + fetchImpl: (async () => { + throw new Error("offline"); + }) as typeof fetch, + }); + await withEnv( { HOME: home, USERPROFILE: home, CURSOR_AGENT_CLI_VERSION: "3.9", CURSOR_DATA_DIR: undefined, }, - () => { + async () => { resetCursorAgentCliVersionCache(); - assert.equal(getCursorAgentCliVersion(), CURSOR_AGENT_CLI_VERSION); + assert.equal(await getCursorAgentCliVersion(), CURSOR_AGENT_CLI_VERSION); } ); } finally { - resetCursorAgentCliVersionCache(); + resetCursorAgentCliVersionTestHooks(); fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(cacheDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); -test("getCursorAgentCliVersion caches until reset", () => { - withEnv({ CURSOR_AGENT_CLI_VERSION: "2026.02.03-111aaaa" }, () => { +test("getCursorAgentCliVersion caches until reset", async () => { + await withEnv({ CURSOR_AGENT_CLI_VERSION: "2026.02.03-111aaaa" }, async () => { resetCursorAgentCliVersionCache(); - assert.equal(getCursorAgentCliVersion(), "2026.02.03-111aaaa"); + assert.equal(await getCursorAgentCliVersion(), "2026.02.03-111aaaa"); process.env.CURSOR_AGENT_CLI_VERSION = "2026.02.03-222bbbb"; - assert.equal(getCursorAgentCliVersion(), "2026.02.03-111aaaa", "cached"); + assert.equal(await getCursorAgentCliVersion(), "2026.02.03-111aaaa", "cached"); resetCursorAgentCliVersionCache(); - assert.equal(getCursorAgentCliVersion(), "2026.02.03-222bbbb"); + assert.equal(await getCursorAgentCliVersion(), "2026.02.03-222bbbb"); }); resetCursorAgentCliVersionCache(); }); -test("getCursorAgentCliVersion reads CURSOR_DATA_DIR via isolated HOME", () => { +test("getCursorAgentCliVersion reads CURSOR_DATA_DIR via isolated HOME", async () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-home-get-")); const data = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-data-get-")); try { const id = "2026.03.15-cafebabe"; fs.mkdirSync(path.join(data, "versions", id), { recursive: true }); - withEnv( + await withEnv( { HOME: home, USERPROFILE: home, CURSOR_DATA_DIR: data, CURSOR_AGENT_CLI_VERSION: undefined, }, - () => { + async () => { resetCursorAgentCliVersionCache(); - assert.equal(getCursorAgentCliVersion(), id); - assert.equal(formatCursorAgentClientVersion(getCursorAgentCliVersion()), `cli-${id}`); + assert.equal(await getCursorAgentCliVersion(), id); + assert.equal( + formatCursorAgentClientVersion(await getCursorAgentCliVersion()), + `cli-${id}` + ); } ); } finally { @@ -193,17 +213,16 @@ test("disk cache hit serves immediately without blocking on network", async () = path.join(cacheDir, "cursor-agent-cli-version.json"), JSON.stringify({ version: cachedId, fetchedAt: Date.now() }) ); - withEnv( + await withEnv( { HOME: home, USERPROFILE: home, CURSOR_AGENT_CLI_VERSION: undefined, CURSOR_DATA_DIR: undefined, }, - () => { + async () => { resetCursorAgentCliVersionCache(); - // Sync path returns disk cache immediately (oakimov SWR may refresh in background). - assert.equal(getCursorAgentCliVersion(), cachedId); + assert.equal(await getCursorAgentCliVersion(), cachedId); assert.equal(fetchCalls, 0, "must not block on network"); } ); @@ -245,19 +264,18 @@ test("invalid installer HTML falls through to pin", async () => { try { configureCursorAgentCliVersionForTests({ cacheDir, - fetchImpl: (async () => - new Response("no lab url", { status: 200 })) as typeof fetch, + fetchImpl: (async () => new Response("no lab url", { status: 200 })) as typeof fetch, }); - withEnv( + await withEnv( { HOME: home, USERPROFILE: home, CURSOR_AGENT_CLI_VERSION: undefined, CURSOR_DATA_DIR: undefined, }, - () => { + async () => { resetCursorAgentCliVersionCache(); - assert.equal(getCursorAgentCliVersion(), CURSOR_AGENT_CLI_VERSION); + assert.equal(await getCursorAgentCliVersion(), CURSOR_AGENT_CLI_VERSION); } ); const id = await refreshCursorAgentCliVersionFromInstaller(); @@ -268,3 +286,66 @@ test("invalid installer HTML falls through to pin", async () => { fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); } }); + +function installerHtml(id: string): string { + return `DOWNLOAD_URL="https://downloads.cursor.com/lab/${id}/\${OS}/\${ARCH}/agent-cli-package.tar.gz"`; +} + +test("fetched install version newer than the pin is returned", async () => { + const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-cache-newer-")); + const home = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-home-newer-")); + const fetchedId = "2026.10.01-e373342"; + try { + configureCursorAgentCliVersionForTests({ + cacheDir, + fetchImpl: (async () => new Response(installerHtml(fetchedId), { status: 200 })) as typeof fetch, + }); + await withEnv( + { + HOME: home, + USERPROFILE: home, + CURSOR_AGENT_CLI_VERSION: undefined, + CURSOR_DATA_DIR: undefined, + }, + async () => { + resetCursorAgentCliVersionCache(); + assert.equal(await getCursorAgentCliVersion(), "2026.07.08-0c04a8a"); + assert.equal(await refreshCursorAgentCliVersionFromInstaller(), fetchedId); + assert.equal(await getCursorAgentCliVersion(), fetchedId); + } + ); + } finally { + resetCursorAgentCliVersionTestHooks(); + fs.rmSync(cacheDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +}); + +test("rejected install fetch keeps the pin", async () => { + const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-cache-reject-")); + const home = fs.mkdtempSync(path.join(os.tmpdir(), "cursor-cli-home-reject-")); + try { + configureCursorAgentCliVersionForTests({ + cacheDir, + fetchImpl: (async () => { + throw new Error("network down"); + }) as typeof fetch, + }); + await withEnv( + { + HOME: home, + USERPROFILE: home, + CURSOR_AGENT_CLI_VERSION: undefined, + CURSOR_DATA_DIR: undefined, + }, + async () => { + resetCursorAgentCliVersionCache(); + assert.equal(await getCursorAgentCliVersion(), CURSOR_AGENT_CLI_VERSION); + } + ); + } finally { + resetCursorAgentCliVersionTestHooks(); + fs.rmSync(cacheDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.rmSync(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +}); diff --git a/tests/unit/cursor-api-models-discovery.test.ts b/tests/unit/cursor-api-models-discovery.test.ts new file mode 100644 index 000000000000..3b317e9215ce --- /dev/null +++ b/tests/unit/cursor-api-models-discovery.test.ts @@ -0,0 +1,124 @@ +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// A cursor-api connection (crsr_ user key) must discover Cursor's live catalog +// like an IDE-session `cursor` connection. Before this, the route had no +// cursor-api branch and served the static registry as "local_catalog"; the +// sync persisted that snapshot as the authoritative live catalog, so every +// model Cursor added after the snapshot (e.g. claude-opus-5-5-*) was rejected +// at dispatch as "not available in the active live catalog". + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-cursor-api-models-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const modelsRoute = await import("../../src/app/api/providers/[id]/models/route.ts"); +const { __resetCursorApiKeyAuthForTest } = + await import("../../open-sse/services/cursorApiKeyAuth.ts"); + +const USER_KEY = "crsr_test_user_key"; +const SESSION_TOKEN = "session-token-from-exchange"; + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +// Both cases replace global fetch. Keep them serial so a concurrent run cannot +// swap the mock out from under the other case. +describe("cursor-api model discovery", { concurrency: 1 }, () => { + test("cursor-api discovers Cursor's live catalog with the exchanged session token", async () => { + __resetCursorApiKeyAuthForTest(); + const connection = await providersDb.createProviderConnection({ + provider: "cursor-api", + authType: "apikey", + name: "cursor-api-live", + apiKey: USER_KEY, + }); + + const calls: Array<{ url: string; authorization: string | null }> = []; + const originalFetch = globalThis.fetch; + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + const authorization = new Headers(init?.headers).get("authorization"); + calls.push({ url, authorization }); + if (url.endsWith("/auth/exchange_user_api_key")) { + return Response.json({ accessToken: SESSION_TOKEN, refreshToken: SESSION_TOKEN }); + } + if (url.endsWith("/aiserver.v1.AiService/AvailableModels")) { + return Response.json({ + models: [ + { name: "claude-opus-5-5-medium", displayName: "Claude Opus 5.5" }, + { name: "claude-4.6-opus-high", displayName: "Claude Opus 4.6" }, + ], + }); + } + throw new Error(`unexpected fetch ${url}`); + }) as typeof globalThis.fetch; + + try { + const response = await modelsRoute.GET( + new Request(`http://localhost/api/providers/${connection.id}/models?refresh=true`), + { params: { id: connection.id } } + ); + assert.equal(response.status, 200); + const body = await response.json(); + assert.notEqual(body.source, "local_catalog"); + const ids = body.models.map((model: { id: string }) => model.id); + assert.ok(ids.includes("claude-opus-5-5-medium"), ids.join(",")); + assert.ok(ids.includes("claude-4.6-opus-high"), ids.join(",")); + + const discovery = calls.find((call) => call.url.endsWith("/AvailableModels")); + assert.equal(discovery?.authorization, `Bearer ${SESSION_TOKEN}`); + assert.ok( + calls.every( + (call) => call.authorization !== `Bearer ${USER_KEY}` || call.url.includes("exchange") + ), + "the raw crsr_ key is only ever sent to the exchange endpoint" + ); + } finally { + globalThis.fetch = originalFetch; + } + }); + + test("cursor discovery warnings do not leak stack paths", async () => { + __resetCursorApiKeyAuthForTest(); + const connection = await providersDb.createProviderConnection({ + provider: "cursor-api", + authType: "apikey", + name: "cursor-api-discovery-error", + apiKey: `${USER_KEY}_leak`, + }); + + const leak = "at /home/secret/file.ts:10"; + const originalFetch = globalThis.fetch; + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = String(input); + if (url.endsWith("/auth/exchange_user_api_key")) { + return Response.json({ accessToken: SESSION_TOKEN, refreshToken: SESSION_TOKEN }); + } + if (url.endsWith("/aiserver.v1.AiService/AvailableModels")) { + return new Response(`discovery failed ${leak}`, { status: 500 }); + } + throw new Error(`unexpected fetch ${url}`); + }) as typeof globalThis.fetch; + + try { + const response = await modelsRoute.GET( + new Request(`http://localhost/api/providers/${connection.id}/models?refresh=true`), + { params: { id: connection.id } } + ); + const body = await response.json(); + const warning = String(body.warning ?? body.error ?? ""); + assert.match(warning, /AvailableModels unavailable/); + assert.equal(warning.includes("/home/secret/file.ts:10"), false); + assert.equal(warning.includes("at /"), false); + } finally { + globalThis.fetch = originalFetch; + } + }); +}); diff --git a/tests/unit/cursor-apikey-provider.test.ts b/tests/unit/cursor-apikey-provider.test.ts index 890d044f2dbf..06407bc6ef9e 100644 --- a/tests/unit/cursor-apikey-provider.test.ts +++ b/tests/unit/cursor-apikey-provider.test.ts @@ -74,9 +74,9 @@ describe("CursorExecutor credential resolution", () => { __resetCursorApiKeyAuthForTest(); }); - it("sends the stripped IDE session token for OAuth connections", () => { + it("sends the stripped IDE session token for OAuth connections", async () => { const executor = new CursorExecutor(); - const headers = executor.buildHeaders({ + const headers = await executor.buildHeaders({ accessToken: "user_01::ide.session.jwt", providerSpecificData: {}, }); @@ -101,7 +101,7 @@ describe("CursorExecutor credential resolution", () => { providerSpecificData: {}, }); assert.ok(!(resolved instanceof Response)); - const headers = executor.buildHeaders(resolved); + const headers = await executor.buildHeaders(resolved); assert.equal(headers.authorization, `Bearer ${jwt(exp)}`); assert.ok(!headers.authorization.includes(API_KEY)); assert.equal(calls.length, 1); diff --git a/tests/unit/cursor-builtin-tool-bridge-shape.test.ts b/tests/unit/cursor-builtin-tool-bridge-shape.test.ts new file mode 100644 index 000000000000..194568d2f501 --- /dev/null +++ b/tests/unit/cursor-builtin-tool-bridge-shape.test.ts @@ -0,0 +1,482 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { bridgeCursorBuiltinTool } from "../../open-sse/executors/cursor/builtinToolBridge.ts"; +import { + openAIToolsToMcpDefs, + type ExecServerEvent, + type OpenAITool, +} from "../../open-sse/utils/cursorAgentProtobuf.ts"; + +function defs(tools: OpenAITool[]) { + return openAIToolsToMcpDefs(tools); +} + +test("shape-fallback: custom-named shell tool matches by command property", () => { + const customShell: OpenAITool = { + type: "function", + function: { + name: "run_in_container", + description: "Run command inside the container", + parameters: { + type: "object", + properties: { + cmd: { type: "string" }, + cwd: { type: "string" }, + }, + required: ["cmd"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_shell", + execMsgId: "msg-1", + execId: "exec-1", + command: "npm test", + workingDir: "/workspace", + timeout: 0, + isBackground: false, + hardTimeout: 0, + }; + const bridged = bridgeCursorBuiltinTool(event, defs([customShell])); + assert.ok(bridged); + assert.equal(bridged.toolName, "run_in_container"); + assert.equal(bridged.arguments.cmd, "npm test"); + assert.equal(bridged.arguments.cwd, "/workspace"); +}); + +test("shape-fallback: custom-named read tool matches by filePath property", () => { + const customRead: OpenAITool = { + type: "function", + function: { + name: "get_file_contents", + description: "Read a file from disk", + parameters: { + type: "object", + properties: { + file_path: { type: "string" }, + offset: { type: "number" }, + limit: { type: "number" }, + }, + required: ["file_path"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_read", + execMsgId: "msg-1", + execId: "exec-1", + path: "/src/index.ts", + offset: 10, + limit: 50, + }; + const bridged = bridgeCursorBuiltinTool(event, defs([customRead])); + assert.ok(bridged); + assert.equal(bridged.toolName, "get_file_contents"); + assert.equal(bridged.arguments.file_path, "/src/index.ts"); + assert.equal(bridged.arguments.offset, 10); + assert.equal(bridged.arguments.limit, 50); +}); + +test("shape-fallback: custom-named grep tool matches by query property", () => { + const customGrep: OpenAITool = { + type: "function", + function: { + name: "search_symbol", + description: "Search symbols in repo", + parameters: { + type: "object", + properties: { + query: { type: "string" }, + dir: { type: "string" }, + include: { type: "string" }, + }, + required: ["query"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_grep", + execMsgId: "msg-1", + execId: "exec-1", + pattern: "handleChat", + path: "src/", + glob: "*.ts", + outputMode: "content", + }; + const bridged = bridgeCursorBuiltinTool(event, defs([customGrep])); + assert.ok(bridged); + assert.equal(bridged.toolName, "search_symbol"); + assert.equal(bridged.arguments.query, "handleChat"); + assert.equal(bridged.arguments.dir, "src/"); + assert.equal(bridged.arguments.include, "*.ts"); +}); + +test("shape-fallback: custom-named ls tool matches by path property", () => { + const customLs: OpenAITool = { + type: "function", + function: { + name: "tree_list", + description: "List directory tree", + parameters: { + type: "object", + properties: { + directory: { type: "string" }, + pattern: { type: "string" }, + }, + required: ["directory"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_ls", + execMsgId: "msg-1", + execId: "exec-1", + path: "/home/user/project", + }; + const bridged = bridgeCursorBuiltinTool(event, defs([customLs])); + assert.ok(bridged); + assert.equal(bridged.toolName, "tree_list"); + assert.equal(bridged.arguments.directory, "/home/user/project"); + assert.equal(bridged.arguments.pattern, "*"); +}); + +test("shape-fallback: custom-named write tool matches by path + contents properties", () => { + const customWrite: OpenAITool = { + type: "function", + function: { + name: "overwrite_file", + description: "Write content to a file", + parameters: { + type: "object", + properties: { + filePath: { type: "string" }, + contents: { type: "string" }, + }, + required: ["filePath", "contents"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_write", + execMsgId: "msg-1", + execId: "exec-1", + path: "/tmp/foo.txt", + fileText: "hello world", + hasFileBytes: false, + encodingHint: "utf-8", + }; + const bridged = bridgeCursorBuiltinTool(event, defs([customWrite])); + assert.ok(bridged); + assert.equal(bridged.toolName, "overwrite_file"); + assert.equal(bridged.arguments.filePath, "/tmp/foo.txt"); + assert.equal(bridged.arguments.contents, "hello world"); +}); + +test("shape-fallback: custom-named fetch tool matches by uri property", () => { + const customFetch: OpenAITool = { + type: "function", + function: { + name: "http_client_get", + description: "Fetch web content", + parameters: { + type: "object", + properties: { + uri: { type: "string" }, + }, + required: ["uri"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_fetch", + execMsgId: "msg-1", + execId: "exec-1", + url: "https://example.com", + }; + const bridged = bridgeCursorBuiltinTool(event, defs([customFetch])); + assert.ok(bridged); + assert.equal(bridged.toolName, "http_client_get"); + assert.equal(bridged.arguments.uri, "https://example.com"); +}); + +test("shape-fallback: multiple ambiguous shape candidates fail closed", () => { + const read1: OpenAITool = { + type: "function", + function: { + name: "read_backend", + parameters: { + type: "object", + properties: { path: { type: "string" } }, + required: ["path"], + additionalProperties: false, + }, + }, + }; + const read2: OpenAITool = { + type: "function", + function: { + name: "read_frontend", + parameters: { + type: "object", + properties: { path: { type: "string" } }, + required: ["path"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_read", + execMsgId: "msg-1", + execId: "exec-1", + path: "/tmp/foo", + }; + // Two tools match the shape -> ambiguous -> fail closed (null) + const bridged = bridgeCursorBuiltinTool(event, defs([read1, read2])); + assert.equal(bridged, null); +}); + +test("name-exact wins over shape-fallback when both exist", () => { + const exactRead: OpenAITool = { + type: "function", + function: { + name: "read_file", + parameters: { + type: "object", + properties: { path: { type: "string" } }, + required: ["path"], + additionalProperties: false, + }, + }, + }; + const genericRead: OpenAITool = { + type: "function", + function: { + name: "custom_reader", + parameters: { + type: "object", + properties: { path: { type: "string" } }, + required: ["path"], + additionalProperties: false, + }, + }, + }; + const event: ExecServerEvent = { + kind: "exec_read", + execMsgId: "msg-1", + execId: "exec-1", + path: "/tmp/foo", + }; + // exactRead is in READ_TOOL_NAMES, so it must win over genericRead + const bridged = bridgeCursorBuiltinTool(event, defs([exactRead, genericRead])); + assert.ok(bridged); + assert.equal(bridged.toolName, "read_file"); +}); + +test("expanded allowlist aliases match directly: codebase_search, run_command, view, str_replace", () => { + const codebaseSearch: OpenAITool = { + type: "function", + function: { + name: "codebase_search", + parameters: { + type: "object", + properties: { query: { type: "string" } }, + required: ["query"], + additionalProperties: false, + }, + }, + }; + const grepEvent: ExecServerEvent = { + kind: "exec_grep", + execMsgId: "m", + execId: "e", + pattern: "test", + path: "", + glob: "", + outputMode: "content", + }; + assert.equal( + bridgeCursorBuiltinTool(grepEvent, defs([codebaseSearch]))?.toolName, + "codebase_search" + ); + + const runCommand: OpenAITool = { + type: "function", + function: { + name: "run_command", + parameters: { + type: "object", + properties: { command: { type: "string" } }, + required: ["command"], + additionalProperties: false, + }, + }, + }; + const shellEvent: ExecServerEvent = { + kind: "exec_shell", + execMsgId: "m", + execId: "e", + command: "ls -la", + workingDir: "", + timeout: 0, + isBackground: false, + hardTimeout: 0, + }; + assert.equal(bridgeCursorBuiltinTool(shellEvent, defs([runCommand]))?.toolName, "run_command"); + + const viewTool: OpenAITool = { + type: "function", + function: { + name: "view", + parameters: { + type: "object", + properties: { filePath: { type: "string" } }, + required: ["filePath"], + additionalProperties: false, + }, + }, + }; + const readEvent: ExecServerEvent = { + kind: "exec_read", + execMsgId: "m", + execId: "e", + path: "/a.txt", + }; + assert.equal(bridgeCursorBuiltinTool(readEvent, defs([viewTool]))?.toolName, "view"); + + const patchTool: OpenAITool = { + type: "function", + function: { + name: "apply_patch", + parameters: { + type: "object", + properties: { path: { type: "string" }, content: { type: "string" } }, + required: ["path", "content"], + additionalProperties: false, + }, + }, + }; + const writeEvent: ExecServerEvent = { + kind: "exec_write", + execMsgId: "m", + execId: "e", + path: "/b.txt", + fileText: "data", + hasFileBytes: false, + encodingHint: "utf-8", + }; + assert.equal(bridgeCursorBuiltinTool(writeEvent, defs([patchTool]))?.toolName, "apply_patch"); +}); + +test("generic client tool names are not Cursor bridge targets", () => { + const shellTool = (name: string): OpenAITool => ({ + type: "function", + function: { + name, + parameters: { + type: "object", + properties: { command: { type: "string" } }, + required: ["command"], + additionalProperties: false, + }, + }, + }); + const writeTool = (name: string): OpenAITool => ({ + type: "function", + function: { + name, + parameters: { + type: "object", + properties: { + path: { type: "string" }, + content: { type: "string" }, + }, + required: ["path", "content"], + additionalProperties: false, + }, + }, + }); + const fetchTool = (name: string): OpenAITool => ({ + type: "function", + function: { + name, + parameters: { + type: "object", + properties: { url: { type: "string" } }, + required: ["url"], + additionalProperties: false, + }, + }, + }); + const shellEvent: ExecServerEvent = { + kind: "exec_shell", + execMsgId: "m", + execId: "e", + command: "ls -la", + workingDir: "", + timeout: 0, + isBackground: false, + hardTimeout: 0, + }; + const writeEvent: ExecServerEvent = { + kind: "exec_write", + execMsgId: "m", + execId: "e", + path: "/b.txt", + fileText: "data", + hasFileBytes: false, + encodingHint: "utf-8", + }; + const fetchEvent: ExecServerEvent = { + kind: "exec_fetch", + execMsgId: "m", + execId: "e", + url: "https://example.com", + }; + + // These short names belong to other clients. A compatible schema must not + // pull an ordinary tool call into the Cursor bridge. + for (const name of ["exec", "run", "command"]) { + assert.equal(bridgeCursorBuiltinTool(shellEvent, defs([shellTool(name)])), null, name); + } + for (const name of ["update", "edit"]) { + assert.equal(bridgeCursorBuiltinTool(writeEvent, defs([writeTool(name)])), null, name); + } + assert.equal(bridgeCursorBuiltinTool(fetchEvent, defs([fetchTool("fetch")])), null, "fetch"); + + assert.equal( + bridgeCursorBuiltinTool(shellEvent, defs([shellTool("exec"), shellTool("run_in_container")])) + ?.toolName, + "run_in_container" + ); + + assert.equal( + bridgeCursorBuiltinTool(shellEvent, defs([shellTool("run_terminal_cmd")]))?.toolName, + "run_terminal_cmd" + ); + assert.equal( + bridgeCursorBuiltinTool(shellEvent, defs([shellTool("run_command")]))?.toolName, + "run_command" + ); + assert.equal( + bridgeCursorBuiltinTool(writeEvent, defs([writeTool("edit_file")]))?.toolName, + "edit_file" + ); + assert.equal( + bridgeCursorBuiltinTool(writeEvent, defs([writeTool("str_replace")]))?.toolName, + "str_replace" + ); + assert.equal( + bridgeCursorBuiltinTool(fetchEvent, defs([fetchTool("web_fetch")]))?.toolName, + "web_fetch" + ); + assert.equal( + bridgeCursorBuiltinTool(fetchEvent, defs([fetchTool("webfetch")]))?.toolName, + "webfetch" + ); +}); diff --git a/tests/unit/cursor-errors-classify.test.ts b/tests/unit/cursor-errors-classify.test.ts index 3c631344d33b..9dc5ff5e02bb 100644 --- a/tests/unit/cursor-errors-classify.test.ts +++ b/tests/unit/cursor-errors-classify.test.ts @@ -28,13 +28,21 @@ test("classifyCursorErrorKind: auth cues", () => { assert.equal(classifyCursorErrorKind("unauthenticated"), "auth"); }); -test("classifyCursorErrorKind: AI Model Not Found is rate_limit (Cursor out-of-usage)", () => { +test("classifyCursorErrorKind: AI Model Not Found is rate_limit only with a reset hint", () => { assert.equal( classifyCursorErrorKind("not_found: AI Model Not Found (reset after 109h 9s)"), "rate_limit" ); - assert.equal(classifyCursorErrorKind("not_found: AI Model Not Found"), "rate_limit"); - assert.equal(classifyCursorErrorKind("model not found"), "invalid"); + assert.equal(classifyCursorErrorKind("not_found: AI Model Not Found"), "not_found"); + assert.equal(classifyCursorErrorKind("model not found"), "not_found"); +}); + +test("classifyCursorError: an unknown Cursor model is a 404 invalid request, not a rate limit", () => { + const out = classifyCursorError("not_found: AI Model Not Found"); + assert.equal(out.status, 404); + assert.equal(out.type, "invalid_request_error"); + assert.match(out.message, /^Cursor model not found: /); + assert.doesNotMatch(out.message, /rate limit/i); }); test("classifyCursorError redacts credentials and paths", () => { diff --git a/tests/unit/cursor-streaming.test.ts b/tests/unit/cursor-streaming.test.ts index ed7175ef9a03..2ba3dd59c2b7 100644 --- a/tests/unit/cursor-streaming.test.ts +++ b/tests/unit/cursor-streaming.test.ts @@ -401,12 +401,12 @@ test("Cursor turn end exposes upstream cache reads and writes in OpenAI usage", test("emitCursorSseError matches buildStreamErrorChunks OpenAI shape", () => { const chunks: string[] = []; const ctx = newStreamCtx("gpt-5.4-nano-xhigh", (s) => chunks.push(s)); - const classified = classifyCursorError("not_found: AI Model Not Found"); + const classified = classifyCursorError("resource_exhausted: too many requests"); emitCursorSseError(ctx, classified); const joined = chunks.join(""); assert.match(joined, /"finish_reason":"error"/); - assert.match(joined, /AI Model Not Found/); + assert.match(joined, /too many requests/); assert.match(joined, /rate_limit_error|rate limit/i); assert.match(joined, /data: \[DONE\]/); assert.doesNotMatch(joined, /"choices":\s*\[\]/); diff --git a/tests/unit/cursor-upstream-status.test.ts b/tests/unit/cursor-upstream-status.test.ts new file mode 100644 index 000000000000..601f27cb19cf --- /dev/null +++ b/tests/unit/cursor-upstream-status.test.ts @@ -0,0 +1,155 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Cold-open refusals must publish the upstream status on the capture sink so +// proxy health counts them as upstream instead of transport failures. +const { CursorExecutor } = await import("../../open-sse/executors/cursor.ts"); +const { runWithAppliedProxyCapture } = await import("../../open-sse/utils/proxyFetch.ts"); +const { mergeAppliedProxySink } = await import("../../src/sse/handlers/chatHelpers.ts"); +const { encodeMessage, encodeString } = + await import("../../open-sse/utils/cursorAgentProtobuf/wire.ts"); +import type { AppliedProxySink } from "../../open-sse/utils/proxyFetch.ts"; + +function serverConfig(agentUrl: string, agentnUrl: string): Buffer { + return encodeMessage(27, [encodeString(1, agentUrl), encodeString(2, agentnUrl)]); +} + +function installServerConfigFetch() { + const original = globalThis.fetch; + globalThis.fetch = (async () => + new Response( + serverConfig("https://agent.t.api5.cursor.sh", "https://agentn.t.api5.cursor.sh"), + { + status: 200, + headers: { "Content-Type": "application/proto" }, + } + )) as typeof fetch; + return { restore: () => (globalThis.fetch = original) }; +} + +function stubOpenH2(opened: unknown) { + const proto = CursorExecutor.prototype as unknown as Record; + const original = proto.openH2; + proto.openH2 = async () => opened; + return { restore: () => (proto.openH2 = original) }; +} + +function executeInput(stream = false) { + return { + model: "auto", + body: { messages: [{ role: "user", content: "hello" }] }, + stream, + credentials: { accessToken: "token-test", connectionId: "connection-upstream-status" }, + signal: null, + log: null, + upstreamExtraHeaders: null, + clientHeaders: null, + }; +} + +test("cold-open rate limit records the upstream status on the sink", async () => { + const { restore: restoreFetch } = installServerConfigFetch(); + const { restore: restoreH2 } = stubOpenH2({ + status: 429, + consumeError: async () => Buffer.from("limited"), + }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new CursorExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, 429); + assert.equal(response.status, 429); + assert.match(await response.text(), /\[429\]/); + } finally { + restoreH2(); + restoreFetch(); + } +}); + +test("cold-open outage records the upstream status on the sink", async () => { + const { restore: restoreFetch } = installServerConfigFetch(); + const { restore: restoreH2 } = stubOpenH2({ + status: 503, + consumeError: async () => Buffer.from("down"), + }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new CursorExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, 503); + assert.equal(response.status, 503); + assert.match(await response.text(), /\[503\]/); + } finally { + restoreH2(); + restoreFetch(); + } +}); + +test("a throwing error body still records the upstream status on the sink", async () => { + const { restore: restoreFetch } = installServerConfigFetch(); + const { restore: restoreH2 } = stubOpenH2({ + status: 429, + consumeError: async () => { + throw new Error("stream gone"); + }, + }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new CursorExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, 429); + assert.match(await response.text(), /Unknown error/); + } finally { + restoreH2(); + restoreFetch(); + } +}); + +test("streaming refusal records the upstream status on the sink", async () => { + const { restore: restoreFetch } = installServerConfigFetch(); + const { restore: restoreH2 } = stubOpenH2({ + status: 429, + consumeError: async () => Buffer.from("limited"), + }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new CursorExecutor().execute(executeInput(true)) + ); + assert.equal(sink.upstreamStatus, 429); + assert.equal(response.status, 429); + } finally { + restoreH2(); + restoreFetch(); + } +}); + +test("network failure leaves the sink untouched", async () => { + const { restore: restoreFetch } = installServerConfigFetch(); + const proto = CursorExecutor.prototype as unknown as Record; + const original = proto.openH2; + proto.openH2 = async () => { + throw new Error("connection reset"); + }; + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new CursorExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, undefined); + assert.equal(response.status, 500); + } finally { + proto.openH2 = original; + restoreFetch(); + } +}); + +test("a recorded status reaches the journaled proxy info", () => { + const merged = mergeAppliedProxySink({ proxy: null }, { proxy: null, upstreamStatus: 429 }); + assert.equal(merged?.upstreamStatus, 429); + const untouched = mergeAppliedProxySink({ proxy: null }, { proxy: null }); + assert.equal(untouched?.upstreamStatus, undefined); +}); diff --git a/tests/unit/datadir-legacy-before-xdg.test.ts b/tests/unit/datadir-legacy-before-xdg.test.ts new file mode 100644 index 000000000000..98de2f8f6e2f --- /dev/null +++ b/tests/unit/datadir-legacy-before-xdg.test.ts @@ -0,0 +1,158 @@ +// Regression guard: every copy of the default DATA_DIR resolver must prefer an EXISTING +// legacy ~/.omniroute over $XDG_CONFIG_HOME/omniroute. +// +// The canonical resolver (src/lib/dataPaths.ts::getDefaultDataDir) and the CLI +// (bin/cli/data-dir.mjs) already did. The three self-contained copies did not — +// scripts/build/bootstrap-env.mjs (server bootstrap, copied alone into the standalone +// build), scripts/dev/sync-env.mjs (`npm run env:sync`) and electron/main.js (CJS) jumped +// straight to XDG whenever XDG_CONFIG_HOME was set. On a machine that exports +// XDG_CONFIG_HOME, the first `npm run dev` therefore persisted JWT_SECRET, +// STORAGE_ENCRYPTION_KEY and API_KEY_SECRET to ~/.config/omniroute/server.env while the app +// opened ~/.omniroute/storage.sqlite and the CLI read ~/.omniroute/.env: two different +// encryption keys for one database, so a credential written by either side could not be +// decrypted by the other. +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +import { + bootstrapEnv, + resolveDataDir as bootstrapResolveDataDir, +} from "../../scripts/build/bootstrap-env.mjs"; +import { resolveDataDir as syncEnvResolveDataDir } from "../../scripts/dev/sync-env.mjs"; +import { resolveDataDir as cliResolveDataDir } from "../../bin/cli/data-dir.mjs"; +import { resolveDataDir as runtimeResolveDataDir } from "../../src/lib/dataPaths.ts"; + +type TempEnv = { tempRoot: string; tempHome: string; tempCwd: string }; + +function withTempEnv(fn: (paths: TempEnv) => void) { + const originalCwd = process.cwd(); + const originalEnv = { ...process.env }; + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-datadir-xdg-")); + const tempHome = path.join(tempRoot, "home"); + const tempCwd = path.join(tempRoot, "cwd"); + fs.mkdirSync(tempHome, { recursive: true }); + fs.mkdirSync(tempCwd, { recursive: true }); + + for (const key of [ + "DATA_DIR", + "XDG_CONFIG_HOME", + "APPDATA", + "JWT_SECRET", + "STORAGE_ENCRYPTION_KEY", + "STORAGE_ENCRYPTION_KEY_VERSION", + "API_KEY_SECRET", + "INITIAL_PASSWORD", + ]) { + delete process.env[key]; + } + process.env.HOME = tempHome; + process.chdir(tempCwd); + + try { + fn({ tempRoot, tempHome, tempCwd }); + } finally { + process.chdir(originalCwd); + for (const key of Object.keys(process.env)) { + if (!(key in originalEnv)) delete process.env[key]; + } + for (const [key, value] of Object.entries(originalEnv)) { + process.env[key] = value; + } + fs.rmSync(tempRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +} + +const posix = process.platform !== "win32"; + +test("an existing legacy ~/.omniroute wins over XDG_CONFIG_HOME in every resolver", () => { + withTempEnv(({ tempHome, tempRoot }) => { + const legacyDir = path.join(tempHome, ".omniroute"); + fs.mkdirSync(legacyDir, { recursive: true }); + process.env.XDG_CONFIG_HOME = path.join(tempRoot, "xdg"); + + const expected = runtimeResolveDataDir(); + assert.equal(expected, legacyDir, "canonical runtime resolver keeps the legacy dir"); + assert.equal(cliResolveDataDir(), expected, "bin/cli/data-dir.mjs"); + assert.equal(bootstrapResolveDataDir(), expected, "scripts/build/bootstrap-env.mjs"); + assert.equal(syncEnvResolveDataDir(), expected, "scripts/dev/sync-env.mjs"); + }); +}); + +test( + "without a legacy dir, XDG_CONFIG_HOME still selects $XDG_CONFIG_HOME/omniroute everywhere", + { + skip: !posix && "XDG is a Linux/macOS convention; Windows resolves %APPDATA% first", + }, + () => { + withTempEnv(({ tempRoot }) => { + const xdg = path.join(tempRoot, "xdg"); + process.env.XDG_CONFIG_HOME = xdg; + + const expected = path.join(xdg, "omniroute"); + assert.equal(runtimeResolveDataDir(), expected); + assert.equal(cliResolveDataDir(), expected); + assert.equal(bootstrapResolveDataDir(), expected); + assert.equal(syncEnvResolveDataDir(), expected); + }); + } +); + +test("an explicit DATA_DIR beats both the legacy dir and XDG_CONFIG_HOME in every resolver", () => { + withTempEnv(({ tempHome, tempRoot }) => { + fs.mkdirSync(path.join(tempHome, ".omniroute"), { recursive: true }); + process.env.XDG_CONFIG_HOME = path.join(tempRoot, "xdg"); + const configured = path.join(tempRoot, "explicit"); + process.env.DATA_DIR = configured; + + assert.equal(runtimeResolveDataDir(), configured); + assert.equal(cliResolveDataDir(), configured); + assert.equal(bootstrapResolveDataDir(), configured); + assert.equal(syncEnvResolveDataDir(), configured); + }); +}); + +test("bootstrapEnv persists first-run secrets into the existing legacy ~/.omniroute, not under XDG_CONFIG_HOME", () => { + withTempEnv(({ tempHome, tempRoot }) => { + const legacyDir = path.join(tempHome, ".omniroute"); + fs.mkdirSync(legacyDir, { recursive: true }); + const xdg = path.join(tempRoot, "xdg"); + process.env.XDG_CONFIG_HOME = xdg; + + const env = bootstrapEnv({ quiet: true }); + + const legacyServerEnv = path.join(legacyDir, "server.env"); + const xdgServerEnv = path.join(xdg, "omniroute", "server.env"); + assert.ok(fs.existsSync(legacyServerEnv), "server.env must land next to storage.sqlite"); + assert.ok(!fs.existsSync(xdgServerEnv), "nothing may be written under $XDG_CONFIG_HOME"); + + const persisted = fs.readFileSync(legacyServerEnv, "utf8"); + assert.match( + persisted, + new RegExp(`^STORAGE_ENCRYPTION_KEY=${env.STORAGE_ENCRYPTION_KEY}$`, "m") + ); + assert.match(persisted, new RegExp(`^JWT_SECRET=${env.JWT_SECRET}$`, "m")); + }); +}); + +// electron/main.js exports nothing and needs the Electron runtime, so — like +// electron-server-env-private-modes.test.ts — pin the contract at the source: inside its +// resolveDataDir(), the legacy-directory check must come before the XDG_CONFIG_HOME branch. +test("electron main.js resolveDataDir checks the legacy ~/.omniroute before XDG_CONFIG_HOME", () => { + const src = fs.readFileSync(path.join(process.cwd(), "electron/main.js"), "utf8"); + const start = src.indexOf("function resolveDataDir(overridePath, env = process.env) {"); + assert.notEqual(start, -1, "electron/main.js must keep its self-contained resolveDataDir()"); + const end = src.indexOf("\n}\n", start); + const fnSource = src.slice(start, end); + + const legacyCheck = fnSource.search(/isDirectory\(\)/); + const xdgBranch = fnSource.indexOf("XDG_CONFIG_HOME"); + assert.notEqual(xdgBranch, -1, "resolver still honours XDG_CONFIG_HOME"); + assert.notEqual(legacyCheck, -1, "resolver must stat the legacy ~/.omniroute directory"); + assert.ok( + legacyCheck < xdgBranch, + "the existing-legacy-dir check must precede the XDG_CONFIG_HOME branch" + ); +}); diff --git a/tests/unit/db-migrationrunner-duplicate-column-leftovers.test.ts b/tests/unit/db-migrationrunner-duplicate-column-leftovers.test.ts new file mode 100644 index 000000000000..4d7e6fa61d55 --- /dev/null +++ b/tests/unit/db-migrationrunner-duplicate-column-leftovers.test.ts @@ -0,0 +1,77 @@ +/** + * tests/unit/db-migrationrunner-duplicate-column-leftovers.test.ts + * + * A database whose schema is ahead of the ledger has the columns AND the tables/indexes of a + * migration file already in place. The replay of that file without its existing columns then + * hits "table ... already exists" / "index ... already exists". That must not abort boot: the + * runner falls back to recording the ledger row only, as it did before columns were replayed. + * + * `MIGRATIONS_DIR` is resolved once when migrationRunner.ts is evaluated, so the migration + * files are written before the import. + */ + +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import Database from "better-sqlite3"; + +const CORE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "mig-dup-col-leftovers-")); +fs.writeFileSync( + path.join(CORE_DIR, "001_initial_schema.sql"), + [ + "CREATE TABLE subs (id INTEGER PRIMARY KEY, control_url TEXT);", + "CREATE TABLE subs_log (id INTEGER PRIMARY KEY);", + "CREATE INDEX idx_subs_log_id ON subs_log(id);", + ].join("\n") +); +fs.writeFileSync( + path.join(CORE_DIR, "002_subs_leftovers.sql"), + [ + "ALTER TABLE subs ADD COLUMN control_url TEXT;", + "ALTER TABLE subs ADD COLUMN extra TEXT;", + "CREATE TABLE subs_log (id INTEGER PRIMARY KEY);", + "CREATE INDEX idx_subs_log_id ON subs_log(id);", + ].join("\n") +); +process.env.OMNIROUTE_MIGRATIONS_DIR = CORE_DIR; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const { runMigrations } = await import("../../src/lib/db/migrationRunner.ts"); +const { applyMigrationSkippingExistingColumns } = + await import("../../src/lib/db/migrationRunner/schemaState.ts"); +assert.equal(typeof applyMigrationSkippingExistingColumns, "function"); + +process.on("exit", () => { + fs.rmSync(CORE_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function columns(db: Database.Database, table: string): string[] { + return (db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>).map( + (c) => c.name + ); +} + +test("a replay that fails on an existing table/index falls back to recording the ledger row", () => { + const db = new Database(":memory:"); + try { + assert.doesNotThrow(() => runMigrations(db as never, { isNewDb: true })); + const ledger = db.prepare("SELECT version FROM _omniroute_migrations ORDER BY rowid").all(); + assert.deepEqual(ledger, [{ version: "001" }, { version: "002" }]); + // The failed replay ran in one transaction: nothing of it is left half-applied. + assert.deepEqual(columns(db, "subs"), ["id", "control_url"]); + } finally { + db.close(); + } +}); + +test("the fallback is final: a second run applies nothing and does not throw", () => { + const db = new Database(":memory:"); + try { + runMigrations(db as never, { isNewDb: true }); + assert.equal(runMigrations(db as never), 0); + } finally { + db.close(); + } +}); diff --git a/tests/unit/db-migrationrunner-duplicate-column.test.ts b/tests/unit/db-migrationrunner-duplicate-column.test.ts new file mode 100644 index 000000000000..28914fba19b9 --- /dev/null +++ b/tests/unit/db-migrationrunner-duplicate-column.test.ts @@ -0,0 +1,116 @@ +/** + * tests/unit/db-migrationrunner-duplicate-column.test.ts + * + * A migration file that hits "duplicate column name" must still end up with its missing + * columns. The runner used to record the whole file as applied at the first duplicate, so the + * statements after it never ran and, because the file runs in one transaction, the statements + * before it were rolled back too. + * + * `MIGRATIONS_DIR` is resolved once when migrationRunner.ts is evaluated, so the migration + * files are written before the import and every test starts from a fresh in-memory database. + */ + +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import Database from "better-sqlite3"; + +const CORE_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "mig-dup-col-")); +fs.writeFileSync( + path.join(CORE_DIR, "001_initial_schema.sql"), + "CREATE TABLE subs (id INTEGER PRIMARY KEY, control_url TEXT);" +); +fs.writeFileSync( + path.join(CORE_DIR, "002_subs_selector.sql"), + [ + "-- ALTER TABLE subs ADD COLUMN commented_out TEXT;", + "CREATE TABLE subs_log (id INTEGER PRIMARY KEY);", + "ALTER TABLE subs ADD COLUMN control_url TEXT;", + 'ALTER TABLE "subs" add column last_switch_at TEXT', + " DEFAULT NULL;", + "ALTER TABLE subs ADD COLUMN last_switch_result TEXT;", + "CREATE INDEX idx_subs_log_id ON subs_log(id);", + ].join("\n") +); +process.env.OMNIROUTE_MIGRATIONS_DIR = CORE_DIR; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const { runMigrations } = await import("../../src/lib/db/migrationRunner.ts"); +const { stripExistingAddColumns } = await import("../../src/lib/db/migrationRunner/schemaState.ts"); + +process.on("exit", () => { + fs.rmSync(CORE_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function columns(db: Database.Database, table: string): string[] { + return (db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>).map( + (c) => c.name + ); +} + +test("adds the missing columns when an earlier column of the file already exists", () => { + const db = new Database(":memory:"); + try { + runMigrations(db as never, { isNewDb: true }); + assert.deepEqual(columns(db, "subs"), [ + "id", + "control_url", + "last_switch_at", + "last_switch_result", + ]); + const ledger = db.prepare("SELECT version FROM _omniroute_migrations ORDER BY rowid").all(); + assert.deepEqual(ledger, [{ version: "001" }, { version: "002" }]); + } finally { + db.close(); + } +}); + +test("keeps the statements that come before the duplicate and after the last ADD COLUMN", () => { + const db = new Database(":memory:"); + try { + runMigrations(db as never, { isNewDb: true }); + const objects = db + .prepare("SELECT name FROM sqlite_master WHERE name IN ('subs_log', 'idx_subs_log_id')") + .all(); + assert.equal(objects.length, 2); + } finally { + db.close(); + } +}); + +test("a second run leaves the schema and the ledger untouched", () => { + const db = new Database(":memory:"); + try { + runMigrations(db as never, { isNewDb: true }); + const before = columns(db, "subs"); + assert.equal(runMigrations(db as never), 0); + assert.deepEqual(columns(db, "subs"), before); + } finally { + db.close(); + } +}); + +test("stripExistingAddColumns drops only the statements whose column exists", () => { + const present = new Set(["subs.control_url"]); + const sql = [ + "-- ALTER TABLE subs ADD COLUMN control_url TEXT;", + "ALTER TABLE subs ADD COLUMN control_url TEXT;", + "ALTER TABLE subs ADD COLUMN other TEXT;", + "", + ].join("\n"); + const result = stripExistingAddColumns(sql, (t, c) => present.has(`${t}.${c}`)); + assert.deepEqual(result.skipped, ["subs.control_url"]); + assert.equal( + result.sql, + "-- ALTER TABLE subs ADD COLUMN control_url TEXT;\nALTER TABLE subs ADD COLUMN other TEXT;\n" + ); +}); + +test("stripExistingAddColumns keeps statements on a table that does not exist yet", () => { + const sql = "CREATE TABLE t (id INTEGER);\nALTER TABLE t ADD COLUMN x TEXT;\n"; + const result = stripExistingAddColumns(sql, () => false); + assert.deepEqual(result.skipped, []); + assert.equal(result.sql, sql); +}); diff --git a/tests/unit/db-providers-crud.test.ts b/tests/unit/db-providers-crud.test.ts index eb545bbfce7d..d993c82ae216 100644 --- a/tests/unit/db-providers-crud.test.ts +++ b/tests/unit/db-providers-crud.test.ts @@ -201,6 +201,53 @@ test("codex logins without a workspaceId are not merged on bare email match", as assert.equal(rowA?.refreshToken, "refresh-account-a"); }); +test("updateProviderConnection persists an explicit priority for a single connection", async () => { + const connection = await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "Only connection", + }); + + const updated = await providersDb.updateProviderConnection(String(connection.id), { + priority: 5, + }); + const persisted = await providersDb.getProviderConnectionById(String(connection.id)); + const listed = await providersDb.getProviderConnections({ provider: "openai" }); + + assert.equal(updated?.priority, 5); + assert.equal(persisted?.priority, 5); + assert.equal(listed[0].priority, 5); + + await providersDb.updateProviderConnection(String(connection.id), { name: "Renamed connection" }); + const renamed = await providersDb.getProviderConnectionById(String(connection.id)); + assert.equal(renamed?.name, "Renamed connection"); + assert.equal(renamed?.priority, 5); +}); + +test("updateProviderConnection preserves other priorities when changing routing order", async () => { + const first = await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "First", + }); + const second = await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "Second", + }); + + await providersDb.updateProviderConnection(String(first.id), { priority: 10 }); + const ordered = await providersDb.getProviderConnections({ provider: "openai" }); + + assert.deepEqual( + ordered.map(({ id, priority }) => ({ id, priority })), + [ + { id: second.id, priority: 2 }, + { id: first.id, priority: 10 }, + ] + ); +}); + test("updateProviderConnection reorders priorities and returns decrypted payloads", async () => { const first = await providersDb.createProviderConnection({ provider: "openai", @@ -231,6 +278,7 @@ test("updateProviderConnection reorders priorities and returns decrypted payload assert.equal((updated as any).providerSpecificData.region, "us-east-1"); assert.equal(updated.rateLimitProtection, true); + assert.equal(updated.priority, ordered[0].priority); assert.deepEqual( ordered.map((connection) => ({ id: connection.id, diff --git a/tests/unit/db-proxies-crud.test.ts b/tests/unit/db-proxies-crud.test.ts index 99f340600144..224b021e7cd6 100644 --- a/tests/unit/db-proxies-crud.test.ts +++ b/tests/unit/db-proxies-crud.test.ts @@ -237,6 +237,8 @@ test("proxy health stats aggregate proxy_logs and force delete removes assignmen transportOk: 1, transportFailures: 2, transportRate: null, + slowAbandoned: 0, + clientAborted: 0, upstream4xx: 0, upstream5xx: 0, avgLatencyMs: 250, diff --git a/tests/unit/db-proxies-ventilated.test.ts b/tests/unit/db-proxies-ventilated.test.ts index 0f814fe3bff6..8a91063478c0 100644 --- a/tests/unit/db-proxies-ventilated.test.ts +++ b/tests/unit/db-proxies-ventilated.test.ts @@ -48,6 +48,8 @@ type LogRow = { status: string; targetUrl: string | null; upstreamStatus: number | null; + attemptIssue: string | null; + error: string | null; }; let logSeq = 0; @@ -59,8 +61,8 @@ async function seedProxy(name: string, host: string, rows: LogRow[]) { const insertLog = db.prepare(` INSERT INTO proxy_logs ( id, timestamp, status, proxy_type, proxy_host, proxy_port, - target_url, upstream_status, latency_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + target_url, upstream_status, latency_ms, attempt_issue, error + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); for (const row of rows) { logSeq += 1; @@ -73,7 +75,9 @@ async function seedProxy(name: string, host: string, rows: LogRow[]) { 8080, row.targetUrl, row.upstreamStatus, - 100 + 100, + row.attemptIssue ?? null, + row.error ?? null ); } const stats = await proxiesDb.getProxyHealthStats({ hours: 2 }); @@ -88,9 +92,18 @@ async function seedProxy(name: string, host: string, rows: LogRow[]) { function row( status: string, upstreamStatus: number | null, - targetUrl: string | null = null + targetUrl: string | null = null, + attemptIssue: string | null = null, + error: string | null = null ): LogRow { - return { id: `log-${status}-${String(upstreamStatus)}`, status, targetUrl, upstreamStatus }; + return { + id: `log-${status}-${String(upstreamStatus)}`, + status, + targetUrl, + upstreamStatus, + attemptIssue, + error, + }; } test("measured successes count as transport ok, including legacy rows without upstream status", async () => { @@ -203,3 +216,150 @@ test("legacy aggregate fields keep their previous values", async () => { assert.equal(entry.timeoutCount, 1); assert.equal(entry.successRate, 33.33); }); + +const { headersWaitExpiryError } = await import("../../open-sse/executors/opencodeHeadersWait.ts"); +const { sanitizeErrorMessage } = await import("../../open-sse/utils/errorSanitization.ts"); + +function storedErrorText(value: unknown): string { + return sanitizeErrorMessage(sanitizeErrorMessage(value)); +} + +test("slow abandoned headers-wait rows leave the transport failures and count separately", async () => { + const stored = storedErrorText(headersWaitExpiryError(5000)); + assert.match(stored, /^OpencodeHeadersWaitTimeout:/); + const entry = await seedProxy("Vent Slow", "vent-slow.local", [ + { + id: "log-slow", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "abandoned", + error: stored, + }, + ]); + assert.equal(entry.transportFailures, 0); + assert.equal(entry.slowAbandoned, 1); + assert.equal(entry.clientAborted, 0); +}); + +test("abandoned client aborts leave the transport failures and count separately", async () => { + const stored = storedErrorText(new Error("Request aborted")); + assert.match(stored, /Request aborted/); + const entry = await seedProxy("Vent Abort", "vent-abort.local", [ + { + id: "log-abort", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "abandoned", + error: stored, + }, + ]); + assert.equal(entry.transportFailures, 0); + assert.equal(entry.slowAbandoned, 0); + assert.equal(entry.clientAborted, 1); +}); + +test("served client aborts leave the transport failures and count separately", async () => { + const stored = storedErrorText("Request aborted"); + assert.match(stored, /Request aborted/); + const entry = await seedProxy("Vent Served", "vent-served.local", [ + { + id: "log-served", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "served", + error: stored, + }, + ]); + assert.equal(entry.transportFailures, 0); + assert.equal(entry.slowAbandoned, 0); + assert.equal(entry.clientAborted, 1); +}); + +test("abandoned connection errors stay transport failures", async () => { + const entry = await seedProxy("Vent Conn", "vent-conn.local", [ + { + id: "log-conn", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "abandoned", + error: "fetch failed (cause ECONNREFUSED)", + }, + ]); + assert.equal(entry.transportFailures, 1); + assert.equal(entry.slowAbandoned, 0); + assert.equal(entry.clientAborted, 0); +}); + +test("long-ceiling timeouts stay transport failures", async () => { + const entry = await seedProxy("Vent Ceil", "vent-ceil.local", [ + { + id: "log-ceil", + status: "timeout", + targetUrl: null, + upstreamStatus: null, + attemptIssue: null, + error: "Upstream request did not return response headers after 180000ms (openai/gpt)", + }, + ]); + assert.equal(entry.transportFailures, 1); + assert.equal(entry.slowAbandoned, 0); + assert.equal(entry.clientAborted, 0); +}); + +test("transport rate is computed on transport rows only", async () => { + const slow = storedErrorText(headersWaitExpiryError(5000)); + const aborted = storedErrorText(new Error("Request aborted")); + const servedAbort = storedErrorText("Request aborted"); + const entry = await seedProxy("Vent Mix", "vent-mix.local", [ + { + id: "log-mix-slow", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "abandoned", + error: slow, + }, + { + id: "log-mix-conn", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "abandoned", + error: "fetch failed (cause ECONNREFUSED)", + }, + { + id: "log-mix-ceil", + status: "timeout", + targetUrl: null, + upstreamStatus: null, + attemptIssue: null, + error: "Upstream request did not return response headers after 180000ms (openai/gpt)", + }, + { + id: "log-mix-abort", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "abandoned", + error: aborted, + }, + { + id: "log-mix-served", + status: "error", + targetUrl: null, + upstreamStatus: null, + attemptIssue: "served", + error: servedAbort, + }, + row("success", 200), + ]); + assert.equal(entry.transportOk, 1); + assert.equal(entry.transportFailures, 2); + assert.equal(entry.slowAbandoned, 1); + assert.equal(entry.clientAborted, 2); + assert.equal(entry.transportRate, 33.33); +}); diff --git a/tests/unit/db/call-logs-family-agg.test.ts b/tests/unit/db/call-logs-family-agg.test.ts index 3dbabfee091d..16ba87ea5d07 100644 --- a/tests/unit/db/call-logs-family-agg.test.ts +++ b/tests/unit/db/call-logs-family-agg.test.ts @@ -123,7 +123,10 @@ test("family aggregation — last-status subqueries seek the provider index (mig const correlated = plan.match(/SEARCH c[23] USING (?:COVERING )?INDEX (\w+)/g) ?? []; assert.equal(correlated.length, 2, `both correlated subqueries must SEARCH an index: ${plan}`); for (const step of correlated) { - assert.ok(step.includes("idx_cl_provider_timestamp"), `expected provider index: ${step}`); + assert.ok( + step.includes("idx_cl_health_matrix_cover"), + `covering index must serve the last-status subqueries: ${step}` + ); } }); diff --git a/tests/unit/db/migration-200-provider-health-matrix-cover.test.ts b/tests/unit/db/migration-200-provider-health-matrix-cover.test.ts new file mode 100644 index 000000000000..714f03d8f39c --- /dev/null +++ b/tests/unit/db/migration-200-provider-health-matrix-cover.test.ts @@ -0,0 +1,211 @@ +// The provider health matrix ranks recent call log targets with two ROW_NUMBER() +// windows, which scanned call_logs without a covering index. Migration 200 adds +// idx_cl_health_matrix_cover. This pins the migration and asserts the ranked +// query actually uses the index (EXPLAIN QUERY PLAN). +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import type { SqliteAdapter } from "../../../src/lib/db/adapters/types.ts"; +import { openMemorySqliteAdapter } from "../_helpers/memorySqliteAdapter.ts"; + +const repoMigrations = path.join( + path.dirname(fileURLToPath(import.meta.url)), + "../../../src/lib/db/migrations" +); +const migrationsDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-migration-200-")); +fs.copyFileSync( + path.join(repoMigrations, "200_provider_health_matrix_covering_index.sql"), + path.join(migrationsDir, "200_provider_health_matrix_covering_index.sql") +); +const originalMigrationsDir = process.env.OMNIROUTE_MIGRATIONS_DIR; +process.env.OMNIROUTE_MIGRATIONS_DIR = migrationsDir; + +const { runMigrations } = await import("../../../src/lib/db/migrationRunner.ts"); + +test.after(() => { + fs.rmSync(migrationsDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (originalMigrationsDir === undefined) delete process.env.OMNIROUTE_MIGRATIONS_DIR; + else process.env.OMNIROUTE_MIGRATIONS_DIR = originalMigrationsDir; +}); + +// Minimal call_logs shape the ranked query reads: same columns and order the +// production reader projects (provider, connection, model, timestamp). +function openDb(): SqliteAdapter { + const db = openMemorySqliteAdapter(); + db.exec( + `CREATE TABLE call_logs ( + id TEXT PRIMARY KEY, + timestamp TEXT NOT NULL, + status INTEGER, + model TEXT, + requested_model TEXT, + provider TEXT, + connection_id TEXT, + duration INTEGER DEFAULT 0, + error_summary TEXT + );` + ); + return db; +} + +function seedCallLogs(db: SqliteAdapter, rows: number): void { + const insert = db.prepare( + `INSERT INTO call_logs + (id, timestamp, status, model, requested_model, provider, connection_id, duration, error_summary) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` + ); + const providers = ["openai", "anthropic", "gemini", "deepseek"]; + const connections = ["conn-a", "conn-b", "conn-c", "conn-d", "conn-e"]; + const models = [ + "model-alpha", + "model-beta", + "model-gamma", + "model-delta", + "model-epsilon", + "model-zeta", + ]; + for (let i = 0; i < rows; i++) { + const provider = providers[i % providers.length]; + const connection = connections[i % connections.length]; + const model = models[i % models.length]; + const recent = i % 5 !== 0; + const day = recent ? "15" : "01"; + const hour = String(i % 24).padStart(2, "0"); + const failed = i % 7 === 0; + insert.run( + `call-${i}`, + `2026-09-${day}T${hour}:00:00.000Z`, + failed ? 503 : 200, + i % 11 === 0 ? null : model, + model, + provider, + connection, + 100 + (i % 900), + failed ? "upstream gateway error" : null + ); + } + db.exec("ANALYZE;"); +} + +const RANKED_QUERY = `WITH log_targets AS ( + SELECT + c.provider, + COALESCE(c.connection_id, '') as connectionId, + COALESCE(c.model, c.requested_model, 'unknown') as model, + c.status, + c.duration, + c.timestamp, + c.id, + c.error_summary + FROM call_logs c + WHERE c.provider IS NOT NULL + AND c.provider != '-' + AND c.timestamp >= :cutoff +), ranked AS ( + SELECT + provider, + connectionId, + model, + status, + duration, + timestamp, + CASE + WHEN (status IS NOT NULL AND (status < 200 OR status >= 400)) + OR error_summary IS NOT NULL + THEN 1 + ELSE 0 + END as isError, + ROW_NUMBER() OVER ( + PARTITION BY provider, connectionId, model + ORDER BY timestamp DESC, id DESC + ) as latestRank, + ROW_NUMBER() OVER ( + PARTITION BY provider, connectionId, model, + CASE + WHEN (status IS NOT NULL AND (status < 200 OR status >= 400)) + OR error_summary IS NOT NULL + THEN 1 + ELSE 0 + END + ORDER BY timestamp DESC, id DESC + ) as errorRank + FROM log_targets +) +SELECT + provider, + connectionId, + model, + COUNT(*) as requests, + SUM(CASE WHEN status >= 200 AND status < 400 THEN 1 ELSE 0 END) as successes, + ROUND(AVG(duration)) as avgLatencyMs, + MAX(timestamp) as lastRequestAt, + MAX( + CASE + WHEN isError = 1 + THEN timestamp + ELSE NULL + END + ) as lastErrorAt, + MAX(CASE WHEN latestRank = 1 THEN status ELSE NULL END) as lastStatus, + MAX(CASE WHEN isError = 1 AND errorRank = 1 THEN status ELSE NULL END) as lastErrorStatus +FROM ranked +GROUP BY provider, connectionId, model`; + +const FILTERED_QUERY = RANKED_QUERY.replace( + "AND c.timestamp >= :cutoff", + "AND c.timestamp >= :cutoff\n AND c.provider = :provider" +); + +function explain(db: SqliteAdapter, sql: string, params: Record): string { + return (db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(params) as Array<{ detail: string }>) + .map((row) => row.detail) + .join(" | "); +} + +test("migration 200 creates idx_cl_health_matrix_cover and a second run is a no-op", () => { + const db = openDb(); + try { + assert.equal(runMigrations(db, { isNewDb: true }), 1); + const names = ( + db.prepare("SELECT name FROM sqlite_master WHERE type = 'index'").all() as Array<{ + name: string; + }> + ).map((row) => row.name); + assert.ok(names.includes("idx_cl_health_matrix_cover")); + assert.equal(runMigrations(db, { isNewDb: true }), 0); + assert.deepEqual(db.prepare("SELECT version, name FROM _omniroute_migrations").all(), [ + { version: "200", name: "provider_health_matrix_covering_index" }, + ]); + } finally { + db.close(); + } +}); + +test("the ranked health query seeks the covering index instead of scanning", () => { + const db = openDb(); + try { + runMigrations(db, { isNewDb: true }); + seedCallLogs(db, 2000); + const cutoff = "2026-09-10T00:00:00.000Z"; + const plan = explain(db, RANKED_QUERY, { cutoff }); + assert.ok( + plan.includes("idx_cl_health_matrix_cover"), + `expected covering index use, got: ${plan}` + ); + assert.ok(plan.includes("COVERING"), `expected a covering plan, got: ${plan}`); + assert.ok( + !plan.includes("SCAN call_logs") && !plan.includes("SCAN c"), + `must not scan call_logs, got: ${plan}` + ); + const filtered = explain(db, FILTERED_QUERY, { cutoff, provider: "openai" }); + assert.ok( + filtered.includes("idx_cl_health_matrix_cover"), + `expected covering index use when filtered, got: ${filtered}` + ); + } finally { + db.close(); + } +}); diff --git a/tests/unit/db/migration-201-drop-unused-turn-node-indexes.test.ts b/tests/unit/db/migration-201-drop-unused-turn-node-indexes.test.ts new file mode 100644 index 000000000000..34a7863fb753 --- /dev/null +++ b/tests/unit/db/migration-201-drop-unused-turn-node-indexes.test.ts @@ -0,0 +1,166 @@ +// Conversation turn nodes carry two indexes no query reads: lookups filter +// on conversation_id and retention sweeps on last_seen_at, while anchor +// search runs in memory after a bulk load. Migration 201 drops them. +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import type { SqliteAdapter } from "../../../src/lib/db/adapters/types.ts"; +import { openMemorySqliteAdapter } from "../_helpers/memorySqliteAdapter.ts"; + +const repoMigrations = path.join( + path.dirname(fileURLToPath(import.meta.url)), + "../../../src/lib/db/migrations" +); +const migrationsDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-migration-201-")); +for (const file of [ + "155_agentic_conversations.sql", + "156_conversation_turn_nodes.sql", + "186_conversation_turn_nodes_last_seen_index.sql", + "201_drop_unused_turn_node_indexes.sql", +]) { + fs.copyFileSync(path.join(repoMigrations, file), path.join(migrationsDir, file)); +} +const originalMigrationsDir = process.env.OMNIROUTE_MIGRATIONS_DIR; +process.env.OMNIROUTE_MIGRATIONS_DIR = migrationsDir; + +const { runMigrations } = await import("../../../src/lib/db/migrationRunner.ts"); + +test.after(() => { + fs.rmSync(migrationsDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (originalMigrationsDir === undefined) delete process.env.OMNIROUTE_MIGRATIONS_DIR; + else process.env.OMNIROUTE_MIGRATIONS_DIR = originalMigrationsDir; +}); + +function openDb(): SqliteAdapter { + return openMemorySqliteAdapter(); +} + +function indexes(db: SqliteAdapter): string[] { + return ( + db + .prepare( + "SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'conversation_turn_nodes'" + ) + .all() as Array<{ name: string }> + ).map((row) => row.name); +} + +function seed(db: SqliteAdapter): void { + const insert = db.prepare( + "INSERT INTO conversation_turn_nodes (id, conversation_id, parent_id, role, content_hash, last_correlation_id, first_seen_at, last_seen_at) VALUES (?, ?, ?, 'user', ?, NULL, '2026-01-01', ?)" + ); + // Seed enough rows that the planner prefers an index over a scan: half + // older than the cutoff (would be swept), half newer. + for (let i = 0; i < 500; i++) { + const day = String((i % 28) + 1).padStart(2, "0"); + const seenAt = i < 250 ? `2026-05-${day}` : `2026-09-${day}`; + insert.run(`n${i}`, `c${i % 10}`, i === 0 ? null : `n${i - 1}`, `hash${i}`, seenAt); + } + db.prepare( + "INSERT INTO agentic_conversations (id, api_key_id, fingerprint_hash, last_message_count, last_messages_hash, turn_count, first_seen_at, last_seen_at) VALUES ('c0', NULL, 'fp', 0, '', 5, '2026-01-01', '2026-09-01')" + ).run(); +} + +function planDetails(db: SqliteAdapter, sql: string, ...params: unknown[]): string[] { + return (db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(...params) as Array<{ detail: string }>).map( + (row) => row.detail + ); +} + +const CUTOFF = "2026-06-01"; + +test("migration 201 drops the two unread indexes and keeps the used ones", () => { + const db = openDb(); + try { + assert.equal(runMigrations(db, { isNewDb: true }), 4); + const names = indexes(db); + assert.ok(!names.includes("idx_turn_nodes_parent"), `parent index still present: ${names}`); + assert.ok( + !names.includes("idx_turn_nodes_content_hash"), + `content_hash index still present: ${names}` + ); + assert.ok(names.includes("idx_turn_nodes_conversation")); + assert.ok(names.includes("idx_turn_nodes_last_seen")); + } finally { + db.close(); + } +}); + +test("a second run is a no-op", () => { + const db = openDb(); + try { + assert.equal(runMigrations(db, { isNewDb: true }), 4); + assert.equal(runMigrations(db, { isNewDb: true }), 0); + assert.deepEqual(db.prepare("SELECT version, name FROM _omniroute_migrations").all(), [ + { version: "155", name: "agentic_conversations" }, + { version: "156", name: "conversation_turn_nodes" }, + { version: "186", name: "conversation_turn_nodes_last_seen_index" }, + { version: "201", name: "drop_unused_turn_node_indexes" }, + ]); + } finally { + db.close(); + } +}); + +test("every query on the table still takes an index (no full scan)", () => { + const db = openDb(); + try { + assert.equal(runMigrations(db, { isNewDb: true }), 4); + seed(db); + const cases: Array<{ sql: string; params: unknown[]; index: string }> = [ + { + sql: "SELECT id, parent_id, content_hash FROM conversation_turn_nodes WHERE conversation_id = ?", + params: ["c0"], + index: "idx_turn_nodes_conversation", + }, + { + sql: "SELECT rowid as seq, * FROM conversation_turn_nodes WHERE conversation_id = ? AND rowid > ? ORDER BY rowid ASC", + params: ["c0", 10], + index: "idx_turn_nodes_conversation", + }, + { + sql: "SELECT rowid as seq, * FROM conversation_turn_nodes WHERE conversation_id = ? AND rowid < ? ORDER BY rowid DESC LIMIT 21", + params: ["c0", 400], + index: "idx_turn_nodes_conversation", + }, + { + sql: "SELECT rowid as seq, * FROM conversation_turn_nodes WHERE conversation_id = ? ORDER BY rowid DESC LIMIT 21", + params: ["c0"], + index: "idx_turn_nodes_conversation", + }, + { + sql: "SELECT COUNT(*) as c FROM agentic_conversations ac WHERE (SELECT COUNT(*) FROM conversation_turn_nodes n WHERE n.conversation_id = ac.id) >= 2", + params: [], + index: "idx_turn_nodes_conversation", + }, + { + sql: "DELETE FROM conversation_turn_nodes WHERE rowid IN (SELECT rowid FROM conversation_turn_nodes WHERE last_seen_at < ? LIMIT 100)", + params: [CUTOFF], + index: "idx_turn_nodes_last_seen", + }, + { + sql: "DELETE FROM agentic_conversations WHERE rowid IN (SELECT rowid FROM agentic_conversations WHERE last_seen_at < ? AND NOT EXISTS (SELECT 1 FROM conversation_turn_nodes n WHERE n.conversation_id = agentic_conversations.id) LIMIT 10000)", + params: [CUTOFF], + index: "idx_turn_nodes_conversation", + }, + ]; + for (const { sql, params, index } of cases) { + const plan = planDetails(db, sql, ...params); + assert.ok( + plan.some((detail) => detail.includes(index)), + `expected ${index} for ${sql}, got: ${JSON.stringify(plan)}` + ); + assert.ok( + plan.every( + (detail) => !detail.includes("SCAN conversation_turn_nodes") && !detail.includes("SCAN n") + ), + `expected no scan for ${sql}, got: ${JSON.stringify(plan)}` + ); + } + } finally { + db.close(); + } +}); diff --git a/tests/unit/early-stream-keepalive.test.ts b/tests/unit/early-stream-keepalive.test.ts index f1e8734b36f0..bfbb54ddb929 100644 --- a/tests/unit/early-stream-keepalive.test.ts +++ b/tests/unit/early-stream-keepalive.test.ts @@ -199,12 +199,13 @@ test("slow Responses handler uses comments plus sparse in_progress events", asyn const body = await readAll(result); const frames = body.split("\n\n").filter(Boolean); const earlyFrames = frames.slice(0, -1); - // #14330: the frame now carries a required `sequence_number` and `response` - // object so a strict Responses decoder does not abort on it. + // #14330 / #15202: the frame now carries a required `sequence_number`, a + // `response` object, and a STRING `response.id` so a strict Responses decoder + // does not abort on it. assert.deepEqual(JSON.parse(earlyFrames[0].slice("data: ".length)), { type: "response.in_progress", sequence_number: 1, - response: { id: null, status: "in_progress" }, + response: { id: "resp_keepalive_omniroute", status: "in_progress" }, }); assert.ok( earlyFrames.some((frame) => frame === ": keepalive"), @@ -216,7 +217,7 @@ test("slow Responses handler uses comments plus sparse in_progress events", asyn assert.deepEqual(JSON.parse(frame.slice("data: ".length)), { type: "response.in_progress", sequence_number: 1, - response: { id: null, status: "in_progress" }, + response: { id: "resp_keepalive_omniroute", status: "in_progress" }, }); assert.doesNotMatch(frame, /output_item|reasoning|✨/); } diff --git a/tests/unit/estimator-calibration.test.ts b/tests/unit/estimator-calibration.test.ts new file mode 100644 index 000000000000..4ba788d2a872 --- /dev/null +++ b/tests/unit/estimator-calibration.test.ts @@ -0,0 +1,149 @@ +// #14931 — estimator calibration unit tests. The chars/4 estimate is scaled by +// a learned actual/estimated EMA per (provider, model) × tools bucket, fed by +// provider-reported usage.prompt_tokens. Cold start must be exactly factor +// 1.0 (byte-identical legacy behavior), the factor is clamped, wild ratios are +// rejected, and tool-loop aggregates are skipped. +import test from "node:test"; +import assert from "node:assert/strict"; + +const { + applyEstimatorCalibration, + getEstimatorCalibrationFactor, + getEstimatorCalibrationBucketForTests, + recordEstimatorCalibrationFromUsage, + resetEstimatorCalibrationForTests, +} = await import("../../open-sse/services/estimatorCalibration.ts"); + +const PROVIDER = "cal-test-prov"; +const MODEL = "cal-test-model"; +const ORIGINAL_ENV = process.env.OMNIROUTE_ESTIMATOR_CALIBRATION; + +function record(estimated: number, actual: number, toolsPresent = true, extra = {}) { + recordEstimatorCalibrationFromUsage({ + provider: PROVIDER, + model: MODEL, + estimatedTokens: estimated, + usage: { prompt_tokens: actual, completion_tokens: 1 }, + toolsPresent, + ...extra, + }); +} + +test.before(() => { + delete process.env.OMNIROUTE_ESTIMATOR_CALIBRATION; +}); + +test.after(() => { + if (ORIGINAL_ENV === undefined) { + delete process.env.OMNIROUTE_ESTIMATOR_CALIBRATION; + } else { + process.env.OMNIROUTE_ESTIMATOR_CALIBRATION = ORIGINAL_ENV; + } +}); + +test("cold start: factor is 1.0 and apply is identity before MIN_SAMPLES", () => { + resetEstimatorCalibrationForTests(); + assert.equal(getEstimatorCalibrationFactor(PROVIDER, MODEL, true), 1); + record(100_000, 20_000); // one observation, ratio 0.2 + assert.equal(getEstimatorCalibrationFactor(PROVIDER, MODEL, true), 1, "1 sample must not apply"); + assert.equal(applyEstimatorCalibration(PROVIDER, MODEL, 250_000, true), 250_000); + record(100_000, 20_000); // second + assert.equal(getEstimatorCalibrationFactor(PROVIDER, MODEL, true), 1, "2 samples must not apply"); +}); + +test("three trusted observations activate the learned factor", () => { + resetEstimatorCalibrationForTests(); + for (let i = 0; i < 3; i++) record(100_000, 20_000); + const factor = getEstimatorCalibrationFactor(PROVIDER, MODEL, true); + assert.ok(Math.abs(factor - 0.2) < 0.02, `factor should converge to ~0.2, got ${factor}`); + const scaled = applyEstimatorCalibration(PROVIDER, MODEL, 250_000, true); + assert.ok(Math.abs(scaled - 50_000) < 5_000, `250k should scale to ~50k, got ${scaled}`); +}); + +test("tools-present and tools-absent buckets are independent", () => { + resetEstimatorCalibrationForTests(); + for (let i = 0; i < 3; i++) record(100_000, 20_000, true); + assert.equal( + getEstimatorCalibrationFactor(PROVIDER, MODEL, false), + 1, + "chat-shaped bucket must stay untouched by tools-shaped observations" + ); + // A different model on the same provider is also independent. + assert.equal(getEstimatorCalibrationFactor(PROVIDER, "other-model", true), 1); +}); + +test("factor is clamped to [0.15, 2.0]", () => { + resetEstimatorCalibrationForTests(); + for (let i = 0; i < 5; i++) record(100_000, 1_000); // ratio 0.01 → clamped input + let factor = getEstimatorCalibrationFactor(PROVIDER, MODEL, true); + assert.ok(factor >= 0.15, `floor not respected: ${factor}`); + + resetEstimatorCalibrationForTests(); + for (let i = 0; i < 5; i++) record(10_000, 500_000); // ratio 50 → clamped input + factor = getEstimatorCalibrationFactor(PROVIDER, MODEL, true); + assert.ok(factor <= 2.0, `ceiling not respected: ${factor}`); +}); + +test("wild ratios and tiny estimates are rejected as observations", () => { + resetEstimatorCalibrationForTests(); + record(100_000, 100); // ratio 0.001 < 0.05 → rejected + record(100_000, 5_000_000); // ratio 50 > 20 → rejected + record(500, 250); // estimate below MIN_ESTIMATE_FOR_OBSERVATION → rejected + recordEstimatorCalibrationFromUsage({ + provider: PROVIDER, + model: MODEL, + estimatedTokens: 100_000, + usage: {}, // no token count → rejected + toolsPresent: true, + }); + assert.equal(getEstimatorCalibrationBucketForTests(PROVIDER, MODEL, true), undefined); +}); + +test("EMA absorbs an outlier without jumping", () => { + resetEstimatorCalibrationForTests(); + for (let i = 0; i < 3; i++) record(100_000, 20_000); // factor ≈ 0.2 + record(100_000, 150_000); // sudden ratio 1.5 (shape change) + const factor = getEstimatorCalibrationFactor(PROVIDER, MODEL, true); + assert.ok(factor > 0.2 && factor < 0.6, `EMA should move partially toward 1.5, got ${factor}`); +}); + +test("aggregated tool-loop usage is skipped", () => { + resetEstimatorCalibrationForTests(); + for (let i = 0; i < 3; i++) { + record(100_000, 40_000, true, { aggregatedUsage: true }); + } + assert.equal(getEstimatorCalibrationBucketForTests(PROVIDER, MODEL, true), undefined); +}); + +test("Claude-native input_tokens is accepted as the actual count", () => { + resetEstimatorCalibrationForTests(); + for (let i = 0; i < 3; i++) { + recordEstimatorCalibrationFromUsage({ + provider: PROVIDER, + model: MODEL, + estimatedTokens: 100_000, + usage: { input_tokens: 20_000, output_tokens: 1 }, + toolsPresent: false, + }); + } + const factor = getEstimatorCalibrationFactor(PROVIDER, MODEL, false); + assert.ok(Math.abs(factor - 0.2) < 0.02, `input_tokens not honored, got ${factor}`); +}); + +test("OMNIROUTE_ESTIMATOR_CALIBRATION=off disables recording and application", () => { + resetEstimatorCalibrationForTests(); + process.env.OMNIROUTE_ESTIMATOR_CALIBRATION = "off"; + try { + for (let i = 0; i < 5; i++) record(100_000, 20_000); + assert.equal(getEstimatorCalibrationBucketForTests(PROVIDER, MODEL, true), undefined); + assert.equal(applyEstimatorCalibration(PROVIDER, MODEL, 250_000, true), 250_000); + } finally { + delete process.env.OMNIROUTE_ESTIMATOR_CALIBRATION; + } +}); + +test("non-positive / non-finite estimates pass through apply untouched", () => { + resetEstimatorCalibrationForTests(); + assert.equal(applyEstimatorCalibration(PROVIDER, MODEL, 0, true), 0); + assert.ok(Number.isNaN(applyEstimatorCalibration(PROVIDER, MODEL, Number.NaN, true))); +}); diff --git a/tests/unit/executor-codex.test.ts b/tests/unit/executor-codex.test.ts index bc061ae6e3fb..823141b09952 100644 --- a/tests/unit/executor-codex.test.ts +++ b/tests/unit/executor-codex.test.ts @@ -1541,11 +1541,10 @@ test("CodexExecutor.refreshCredentials refreshes OAuth tokens and returns null w } }); -test("CodexExecutor.refreshCredentials returns null for unrecoverable errors to preserve original credentials", async () => { - // Source intentionally returns null (not an error object) so that base.ts does - // not spread stale error fields onto activeCredentials. The upstream 401/403 - // drives the proper re-auth / mark-expired path instead. - // Source: open-sse/executors/codex.ts — refreshCredentials(), lines ~1205-1216. +test("CodexExecutor.refreshCredentials surfaces an unrecoverable sentinel for dead refresh tokens", async () => { + // A dead refresh token is terminal for this connection, not a provider + // outage: the unrecoverable sentinel skips retries and spares the breaker. + // Source: open-sse/executors/codex.ts — refreshCredentials(). const executor = new CodexExecutor(); const originalFetch = globalThis.fetch; globalThis.fetch = async () => @@ -1556,7 +1555,8 @@ test("CodexExecutor.refreshCredentials returns null for unrecoverable errors to try { const result = await executor.refreshCredentials({ refreshToken: "dead-token" }, null); - assert.equal(result, null, "should return null to leave original credentials untouched"); + assert.equal(result?.error, "unrecoverable_refresh_error"); + assert.equal(result?.code, "invalid_grant"); } finally { globalThis.fetch = originalFetch; } diff --git a/tests/unit/executor-github.test.ts b/tests/unit/executor-github.test.ts index 3fa3d2ca2bce..821243aef301 100644 --- a/tests/unit/executor-github.test.ts +++ b/tests/unit/executor-github.test.ts @@ -292,8 +292,8 @@ test("GithubExecutor.buildHeaders prefers Copilot token and sets GitHub-specific assert.equal(headers.Authorization, "Bearer copilot-token"); assert.equal(headers.Accept, "text/event-stream"); // Copilot CLI wire identity (matches the `copilot` npm package, not VS Code). - assert.equal(headers["editor-version"], "copilot/1.0.88"); - assert.equal(headers["user-agent"], `copilot/1.0.88 (${process.platform}) term/unknown`); + assert.equal(headers["editor-version"], "copilot/1.0.91"); + assert.equal(headers["user-agent"], `copilot/1.0.91 (${process.platform}) term/unknown`); assert.equal(headers["x-github-api-version"], "2026-08-01"); assert.equal(headers["openai-intent"], "conversation-agent"); assert.equal(headers["copilot-integration-id"], "copilot-developer-cli"); diff --git a/tests/unit/feature-flag-descriptions-claims.test.ts b/tests/unit/feature-flag-descriptions-claims.test.ts new file mode 100644 index 000000000000..e44751da744f --- /dev/null +++ b/tests/unit/feature-flag-descriptions-claims.test.ts @@ -0,0 +1,49 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { FEATURE_FLAG_DEFINITIONS } from "../../src/shared/constants/featureFlagDefinitions"; + +const byKey = (key: string) => { + const entry = FEATURE_FLAG_DEFINITIONS.find((f) => f.key === key); + assert.ok(entry, `feature flag ${key} must be defined`); + return entry.description; +}; + +// The dashboard renders these raw English descriptions (descriptionI18nKey has no +// en.json message), so they must state the same guard semantics as +// docs/reference/FEATURE_FLAGS.md. PR #15616 corrected the docs; these pins keep the +// dashboard text from drifting back to the old claims. +test("SSRF Guard dashboard description states the legacy-alias semantics", () => { + const d = byKey("OUTBOUND_SSRF_GUARD_ENABLED"); + assert.match(d, /Legacy alias/); + assert.match(d, /dashboard toggle is read before the environment/); + assert.match(d, /false, 0, no, or off/); + assert.doesNotMatch(d, /^Block outbound requests/); +}); + +test("Allow Private dashboard description states what the flag turns off", () => { + const d = byKey("OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS"); + assert.match(d, /cloud-metadata block included/); + assert.match(d, /provider URL validation, model discovery, provider-node base URLs/); + assert.match(d, /private webhook targets/); + // The proxy-fallback test and webhooks consult only this flag — the local-first + // default does not open them. + assert.match(d, /check only this flag/); + assert.doesNotMatch(d, /REQUIRED for self-hosted/); +}); + +test("Allow Local dashboard description states the local-first default and the metadata block", () => { + const d = byKey("OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS"); + assert.match(d, /by default \(OmniRoute is local-first\)|On by default \(local-first\)/); + assert.match(d, /all of 169\.254\.0\.0\/16/); + assert.match(d, /private and loopback hosts are blocked too/); + assert.doesNotMatch(d, /needed for local OpenAI-compatible models/); +}); + +test("Remote Rerank dashboard description does not claim metadata hosts are never routed to", () => { + const d = byKey("RERANK_REMOTE_PROVIDER_NODES"); + assert.match(d, /must also pass the provider outbound URL policy/); + // Under guard mode "none" (private opt-in) the node check does not block metadata, + // so the absolute claim was removed with the docs correction. + assert.doesNotMatch(d, /never routed to/); +}); diff --git a/tests/unit/feature-flags-settings.test.ts b/tests/unit/feature-flags-settings.test.ts index 10d6d59ecc4e..5b46626eac20 100644 --- a/tests/unit/feature-flags-settings.test.ts +++ b/tests/unit/feature-flags-settings.test.ts @@ -50,7 +50,9 @@ const { // per-address 429, default off) takes the registry to 79. // PROXY_POOL_SHARED_EGRESS_ORDER (shared-egress pool ordering, default off) // takes it to 80. -const EXPECTED_FEATURE_FLAG_COUNT = 80; +// PROXY_OPERATOR_EGRESS_ENABLED (operator-pushed dated observed addresses per +// pool member, default off) takes it to 81. +const EXPECTED_FEATURE_FLAG_COUNT = 81; // ────────────────────────────────────────────────────── // Test group 1 — Flag definitions registry @@ -296,6 +298,17 @@ describe("featureFlagDefinitions", () => { assert.strictEqual(def.requiresRestart, false); }); + it("defines the operator egress push as a network boolean flag disabled by default", () => { + // Guards the push default: the operator-egress route answers 404 and pool reads + // stay journal-only unless opted in. + const def = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === "PROXY_OPERATOR_EGRESS_ENABLED"); + assert.ok(def, "PROXY_OPERATOR_EGRESS_ENABLED should exist"); + assert.strictEqual(def.category, "network"); + assert.strictEqual(def.type, "boolean"); + assert.strictEqual(def.defaultValue, "false"); + assert.strictEqual(def.requiresRestart, false); + }); + it("defines blocked-resets-streak as a health boolean flag disabled by default", () => { // Guards the #10654 default: a target-refused probe stays neutral unless opted in. const def = FEATURE_FLAG_DEFINITIONS.find( diff --git a/tests/unit/fetch-start-timeout-policy-11526.test.ts b/tests/unit/fetch-start-timeout-policy-11526.test.ts new file mode 100644 index 000000000000..46001269f2a0 --- /dev/null +++ b/tests/unit/fetch-start-timeout-policy-11526.test.ts @@ -0,0 +1,122 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + DEFAULT_FETCH_START_TIMEOUT_CAP_MS, + resolveFetchStartTimeout, +} from "../../open-sse/utils/fetchStartTimeoutPolicy.ts"; + +test("#15504 the streaming fetch-start cap is configurable without changing its default", () => { + assert.deepEqual(resolveFetchStartTimeout({ baseTimeoutMs: 12_000_000, stream: true, env: {} }), { + timeoutMs: DEFAULT_FETCH_START_TIMEOUT_CAP_MS, + baseTimeoutMs: 12_000_000, + capped: true, + }); + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: true, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "12000000" }, + }), + { timeoutMs: 12_000_000, baseTimeoutMs: 12_000_000, capped: false } + ); + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: true, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "600000" }, + }), + { timeoutMs: 600_000, baseTimeoutMs: 12_000_000, capped: true } + ); + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: true, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "45000" }, + }), + { timeoutMs: 45_000, baseTimeoutMs: 12_000_000, capped: true } + ); + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: true, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "0" }, + }), + { timeoutMs: 12_000_000, baseTimeoutMs: 12_000_000, capped: false } + ); + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: false, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "45000" }, + }), + { timeoutMs: 12_000_000, baseTimeoutMs: 12_000_000, capped: false } + ); + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 40_000, + stream: true, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "45000" }, + }), + { timeoutMs: 40_000, baseTimeoutMs: 40_000, capped: false } + ); +}); + +test("#15504 the production path reads the operator cap from process.env", () => { + const key = "OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS"; + const previous = process.env[key]; + try { + process.env[key] = "12000000"; + assert.deepEqual(resolveFetchStartTimeout({ baseTimeoutMs: 12_000_000, stream: true }), { + timeoutMs: 12_000_000, + baseTimeoutMs: 12_000_000, + capped: false, + }); + } finally { + if (previous === undefined) delete process.env[key]; + else process.env[key] = previous; + } +}); + +test("#15504 an explicit provider cap keeps precedence over the environment default", () => { + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: true, + capMs: 180_000, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "600000" }, + }), + { timeoutMs: 180_000, baseTimeoutMs: 12_000_000, capped: true } + ); + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: true, + capMs: 0, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "600000" }, + }), + { timeoutMs: 12_000_000, baseTimeoutMs: 12_000_000, capped: false } + ); +}); + +test("#15504 an invalid environment cap warns and keeps the safe default", () => { + const previousWarn = console.warn; + const warnings: string[] = []; + console.warn = (message?: unknown) => warnings.push(String(message)); + try { + assert.deepEqual( + resolveFetchStartTimeout({ + baseTimeoutMs: 12_000_000, + stream: true, + env: { OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS: "-1" }, + }), + { + timeoutMs: DEFAULT_FETCH_START_TIMEOUT_CAP_MS, + baseTimeoutMs: 12_000_000, + capped: true, + } + ); + assert.match(warnings[0] ?? "", /OMNIROUTE_FETCH_START_TIMEOUT_CAP_MS/); + } finally { + console.warn = previousWarn; + } +}); diff --git a/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts b/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts index a47937b4c903..b775a585fa23 100644 --- a/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts +++ b/tests/unit/fixtures/error-public-boundaries-hardening.fixture.ts @@ -398,10 +398,15 @@ test("stream request finalization never warns with a raw error object", () => { test("chatCore provider-failure writes use the projected persistent message", () => { const source = fs.readFileSync(path.join(REPO_ROOT, "open-sse/handlers/chatCore.ts"), "utf8"); - const failureStart = source.indexOf("providerFailure: if (!providerResponse.ok)"); - const failureEnd = source.indexOf("// Non-streaming response", failureStart); - assert.ok(failureStart >= 0 && failureEnd > failureStart, "providerFailure block must exist"); - const failureBlock = source.slice(failureStart, failureEnd); + // The providerFailure block moved into the streaming leaf with the handleChatCore split; the + // classification helper (applyProviderFailureClassification) stayed in the barrel. + const streamingLeaf = fs.readFileSync( + path.join(REPO_ROOT, "open-sse/handlers/chatCore/streamingResponse.ts"), + "utf8" + ); + const failureStart = streamingLeaf.indexOf("providerFailure: if (!providerResponse.ok)"); + assert.ok(failureStart >= 0, "providerFailure block must exist in streamingResponse.ts"); + const failureBlock = streamingLeaf.slice(failureStart); const classifierStart = source.indexOf("const applyProviderFailureClassification = async"); const classifierEnd = source.indexOf("\n };\n", classifierStart); assert.ok( diff --git a/tests/unit/free-provider-rankings-catalog-unification.test.ts b/tests/unit/free-provider-rankings-catalog-unification.test.ts new file mode 100644 index 000000000000..4954be994317 --- /dev/null +++ b/tests/unit/free-provider-rankings-catalog-unification.test.ts @@ -0,0 +1,93 @@ +/** + * Ranking follows the documented free-model catalog. + * + * `computeFreeProviderRankings()` must agree with `providerHasFreeModels()`: + * providers whose only claim to a free tier is the static `hasFree` flag but + * which have no documented free model stay out of the ranking, while catalog + * providers with `hasFree: false` stay in. Provider ids and model ids come + * from the production readers (constants + registry), never hand-written. + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-rankings-catalog-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const intelligenceDb = await import("../../src/lib/db/modelIntelligence.ts"); +const rankings = await import("../../src/lib/freeProviderRankings.ts"); +const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); +const { providerHasFreeModels } = await import("../../src/shared/utils/freeModels.ts"); + +function registryModelIds(providerId: string): string[] { + const entry = REGISTRY[providerId] as { models?: Array<{ id?: string }> } | undefined; + return (entry?.models ?? []) + .map((m) => m.id) + .filter((id): id is string => typeof id === "string"); +} + +function seedScores(modelIds: string[]): void { + for (const model of modelIds) { + intelligenceDb.upsertModelIntelligence({ + model, + source: "arena_elo", + category: "default", + score: 0.85, + eloRaw: 1300, + confidence: "high", + expiresAt: null, + }); + } +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("ranking excludes providers with a raw free flag but no documented free model", async () => { + const outside = ["sarvam", "modal", "openference"]; + for (const id of outside) { + assert.equal(providerHasFreeModels(id), false, `${id} must be outside the free catalog`); + } + for (const id of outside) { + seedScores(registryModelIds(id)); + } + + const ranking = await rankings.computeFreeProviderRankings(undefined, 200, {}); + const ids = new Set(ranking.map((r) => r.id)); + for (const id of outside) { + assert.ok(!ids.has(id), `${id} must stay out of the free ranking`); + } +}); + +test("ranking keeps catalog providers whose static free flag is off", async () => { + const cataloged = ["predibase", "publicai"]; + for (const id of cataloged) { + assert.equal(providerHasFreeModels(id), true, `${id} must be in the free catalog`); + } + for (const id of cataloged) { + seedScores(registryModelIds(id)); + } + + const ranking = await rankings.computeFreeProviderRankings(undefined, 200, {}); + const ids = new Set(ranking.map((r) => r.id)); + for (const id of cataloged) { + assert.ok(ids.has(id), `${id} must appear in the free ranking`); + } +}); + +test("ranking still lists always-free providers from the free catalog", async () => { + const id = "aihorde"; + assert.equal(providerHasFreeModels(id), true, `${id} must be in the free catalog`); + seedScores(registryModelIds(id)); + + const ranking = await rankings.computeFreeProviderRankings(undefined, 200, {}); + assert.ok( + ranking.some((r) => r.id === id), + `${id} must appear in the free ranking` + ); +}); diff --git a/tests/unit/gemini-chronological-tool-ids.test.ts b/tests/unit/gemini-chronological-tool-ids.test.ts new file mode 100644 index 000000000000..cb0ea1979933 --- /dev/null +++ b/tests/unit/gemini-chronological-tool-ids.test.ts @@ -0,0 +1,147 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { openaiResponsesToOpenAIRequest } from "../../open-sse/translator/request/openai-responses.ts"; +import { + openaiToCloudCodeGeminiRequest, + openaiToAntigravityRequest, +} from "../../open-sse/translator/request/openai-to-gemini.ts"; +import { + buildGeminiThoughtSignatureKey, + storeGeminiThoughtSignature, + clearGeminiThoughtSignatures, +} from "../../open-sse/services/geminiThoughtSignatureStore.ts"; +import { resetDbInstance } from "../../src/lib/db/core.ts"; + +type ToolPart = { + thoughtSignature?: string; + functionCall?: { id: string; name: string; args: unknown }; + functionResponse?: { id: string; name: string; response: { result: unknown } }; +}; +type Wire = { contents?: Array<{ parts: ToolPart[] }> }; + +function inputHistory() { + return { + input: [ + { type: "message", role: "user", content: "First turn" }, + { type: "function_call", call_id: "call_repeat", name: "first_tool", arguments: "{}" }, + { type: "function_call_output", call_id: "call_repeat", output: "FIRST_RESULT" }, + { type: "message", role: "user", content: "Second turn" }, + { type: "custom_tool_call", call_id: "call_repeat", name: "second_tool", input: "raw patch" }, + { type: "custom_tool_call_output", call_id: "call_repeat", output: "SECOND_RESULT" }, + { type: "message", role: "user", content: "Continue" }, + ], + }; +} + +function toolParts(wire: Wire): ToolPart[] { + return (wire.contents || []) + .flatMap((content) => content.parts) + .filter((part) => part.functionCall || part.functionResponse); +} + +test.after(() => { + clearGeminiThoughtSignatures(); + resetDbInstance(); +}); + +test("#15312: Responses function/custom histories get unique paired Cloud Code wire IDs", () => { + const body = inputHistory(); + const original = structuredClone(body); + const chat = openaiResponsesToOpenAIRequest("gemini-3.8-flash", body, false, null); + const originalChat = structuredClone(chat); + const wire = openaiToCloudCodeGeminiRequest("gemini-3.8-flash", chat, false) as Wire; + const parts = toolParts(wire); + assert.deepEqual( + parts.filter((part) => part.functionCall).map((part) => part.functionCall.id), + ["call_repeat", "call_repeat_occ2"] + ); + assert.deepEqual( + parts.filter((part) => part.functionResponse).map((part) => part.functionResponse), + [ + { id: "call_repeat", name: "first_tool", response: { result: "FIRST_RESULT" } }, + { id: "call_repeat_occ2", name: "second_tool", response: { result: "SECOND_RESULT" } }, + ] + ); + assert.deepEqual(parts[2].functionCall.args, { input: "raw patch" }); + assert.deepEqual(body, original); + assert.deepEqual(chat, originalChat); + assert.deepEqual( + toolParts(openaiToCloudCodeGeminiRequest("gemini-3.8-flash", chat, false) as Wire), + parts + ); +}); + +test("ambiguous overlapping duplicate calls fail locally instead of guessing result pairing", () => { + const body = { + input: [ + { type: "message", role: "user", content: "Parallel calls" }, + { type: "function_call", call_id: "same", name: "first", arguments: "{}" }, + { type: "function_call", call_id: "same", name: "second", arguments: "{}" }, + { type: "function_call_output", call_id: "same", output: "first result" }, + { type: "function_call_output", call_id: "same", output: "second result" }, + ], + }; + const chat = openaiResponsesToOpenAIRequest("gemini-3.8-flash", body, false, null); + assert.throws( + () => openaiToCloudCodeGeminiRequest("gemini-3.8-flash", chat, false), + (error: Error & { statusCode?: number; errorType?: string }) => + error.statusCode === 400 && + error.errorType === "invalid_request_error" && + error.message.includes("overlapping tool calls") + ); +}); + +test("Antigravity resolves cached signatures with original IDs before wire normalization", () => { + const namespace = "chronological-test"; + storeGeminiThoughtSignature( + buildGeminiThoughtSignatureKey(namespace, "call_repeat"), + "original-signature" + ); + storeGeminiThoughtSignature( + buildGeminiThoughtSignatureKey(namespace, "call_repeat_occ2"), + "wrong-signature" + ); + const chat = openaiResponsesToOpenAIRequest("gemini-3.8-flash", inputHistory(), false, null); + const envelope = openaiToAntigravityRequest("gemini-3.8-flash", chat, false, { + projectId: "test-project", + _signatureNamespace: namespace, + }); + const calls = toolParts(envelope.request as Wire).filter((part) => part.functionCall); + assert.deepEqual( + calls.map((part) => part.functionCall.id), + ["call_repeat", "call_repeat_occ2"] + ); + assert.deepEqual( + calls.map((part) => part.thoughtSignature), + ["original-signature", "original-signature"] + ); +}); + +test("duplicate calls separated by a user turn are rejected before fallback response synthesis", () => { + const body = inputHistory(); + body.input.splice(2, 1); + const chat = openaiResponsesToOpenAIRequest("gemini-3.8-flash", body, false, null); + assert.throws( + () => openaiToCloudCodeGeminiRequest("gemini-3.8-flash", chat, false), + /overlapping tool calls/ + ); +}); + +test("native Codex Responses passthrough retains the original repeated IDs", async () => { + const { CodexExecutor } = await import("../../open-sse/executors/codex.ts"); + const history = inputHistory(); + const body = { + ...history, + input: history.input.map((item) => + item.type === "message" + ? { ...item, content: [{ type: "input_text", text: item.content }] } + : item + ), + model: "gpt-6.1", + _nativeCodexPassthrough: true, + }; + const input = structuredClone(body.input); + const result = await new CodexExecutor().transformRequest("gpt-6.1", body, false, {} as never); + assert.deepEqual(result.input, input); + assert.deepEqual(body.input, input); +}); diff --git a/tests/unit/gemini-cli-client-version-fetch.test.ts b/tests/unit/gemini-cli-client-version-fetch.test.ts new file mode 100644 index 000000000000..9a4c4402b8aa --- /dev/null +++ b/tests/unit/gemini-cli-client-version-fetch.test.ts @@ -0,0 +1,54 @@ +/** + * Registry lookup for the advertised Gemini CLI client version. + * + * The captured pin is the floor; a newer dotted triple from the npm + * `@google/gemini-cli` `latest` document replaces it for 6 hours. A rejected + * fetch, a non-triple, or an older publish stays on the pin. + */ +import assert from "node:assert/strict"; +import test from "node:test"; + +const canonical = await import("../../src/shared/constants/geminiCliClient.ts"); + +const NPM_LATEST = "https://registry.npmjs.org/@google/gemini-cli/latest"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +test.afterEach(() => { + canonical.resetGeminiCliClientVersionCache(); +}); + +test("a fetched version newer than the pin is returned", async () => { + const urls: string[] = []; + const fetchMock = async (url: string | URL | Request) => { + urls.push(String(url)); + return jsonResponse({ version: "0.62.0" }); + }; + + assert.equal( + await canonical.refreshGeminiCliClientVersion(fetchMock as typeof fetch), + "0.62.0" + ); + assert.equal(canonical.getGeminiCliClientVersion(), "0.62.0"); + assert.equal(canonical.getGeminiCliUserAgent(), "GeminiCLI/0.62.0 (linux; x64)"); + assert.equal(urls.length, 1); + assert.equal(urls[0], NPM_LATEST); +}); + +test("a fetch that rejects falls back to the pin", async () => { + const fetchMock = async () => { + throw new Error("registry unreachable"); + }; + + assert.equal( + await canonical.refreshGeminiCliClientVersion(fetchMock as typeof fetch), + "0.1.0" + ); + assert.equal(canonical.getGeminiCliClientVersion(), canonical.GEMINI_CLI_CLIENT_VERSION); + assert.equal(canonical.getGeminiCliUserAgent(), "GeminiCLI/0.1.0 (linux; x64)"); +}); diff --git a/tests/unit/gemini-flash-lite-thinking-zero.test.ts b/tests/unit/gemini-flash-lite-thinking-zero.test.ts new file mode 100644 index 000000000000..f3dc64dbe599 --- /dev/null +++ b/tests/unit/gemini-flash-lite-thinking-zero.test.ts @@ -0,0 +1,155 @@ +/** + * Google AI Studio rejects `thinkingBudget: 0` on Flash-Lite models + * (gemini-flash-lite-latest, gemini-3.5-flash-lite) with a bare + * 400 INVALID_ARGUMENT. The same request succeeds with + * `thinkingConfig: { includeThoughts: false }` (no thinkingBudget), so the + * Gemini request translators must omit a zero budget for Flash-Lite models + * while preserving the explicit `thinkingBudget: 0` contract (#6813 / #6943) + * for every other model. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { openaiToGeminiRequest } = + await import("../../open-sse/translator/request/openai-to-gemini.ts"); +const { claudeToGeminiRequest } = + await import("../../open-sse/translator/request/claude-to-gemini.ts"); +const { isGeminiFlashLiteModel, buildGeminiThinkingConfig } = + await import("../../open-sse/translator/helpers/geminiHelper.ts"); + +type ThinkingConfig = { thinkingBudget?: number; includeThoughts: boolean }; +type GeminiResult = { generationConfig: { thinkingConfig?: ThinkingConfig } }; + +const FLASH_LITE_MODELS = ["gemini-flash-lite-latest", "gemini-3.5-flash-lite"]; + +function openai(model: string, extra: Record): GeminiResult { + return openaiToGeminiRequest( + model, + { messages: [{ role: "user", content: "hi" }], ...extra }, + false + ) as GeminiResult; +} + +function claude(model: string, extra: Record): GeminiResult { + return claudeToGeminiRequest( + model, + { messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }], ...extra }, + false + ) as GeminiResult; +} + +test("isGeminiFlashLiteModel detects Flash-Lite ids only", () => { + for (const id of [ + "gemini-flash-lite-latest", + "gemini-3.5-flash-lite", + "gemini-2.5-flash-lite", + "gemini-3.1-flash-lite-preview", + "gemini/gemini-3.5-flash-lite", + "GEMINI-FLASH-LITE-LATEST", + ]) { + assert.equal(isGeminiFlashLiteModel(id), true, id); + } + for (const id of [ + "gemini-2.5-pro", + "gemini-2.5-flash", + "gemini-flash-latest", + "gemini-3-flash-preview", + "satellite-model", + "", + undefined, + ]) { + assert.equal(isGeminiFlashLiteModel(id), false, String(id)); + } +}); + +test("buildGeminiThinkingConfig only drops a zero budget for Flash-Lite", () => { + assert.deepEqual(buildGeminiThinkingConfig("gemini-flash-lite-latest", 0, false), { + includeThoughts: false, + }); + assert.deepEqual(buildGeminiThinkingConfig("gemini-flash-lite-latest", 1024, true), { + thinkingBudget: 1024, + includeThoughts: true, + }); + assert.deepEqual(buildGeminiThinkingConfig("gemini-2.5-pro", 0, false), { + thinkingBudget: 0, + includeThoughts: false, + }); +}); + +for (const model of FLASH_LITE_MODELS) { + test(`openai -> gemini: ${model} with reasoning_effort none omits thinkingBudget 0`, () => { + const result = openai(model, { reasoning_effort: "none" }); + assert.deepEqual(result.generationConfig.thinkingConfig, { includeThoughts: false }); + assert.equal("thinkingBudget" in (result.generationConfig.thinkingConfig ?? {}), false); + }); + + test(`openai -> gemini: ${model} with thinking.budget_tokens 0 omits thinkingBudget 0`, () => { + const result = openai(model, { thinking: { type: "enabled", budget_tokens: 0 } }); + assert.deepEqual(result.generationConfig.thinkingConfig, { includeThoughts: false }); + }); + + test(`openai -> gemini: ${model} with positive effort still sends thinkingBudget`, () => { + for (const effort of ["low", "high"]) { + const cfg = openai(model, { reasoning_effort: effort }).generationConfig.thinkingConfig; + assert.equal(typeof cfg?.thinkingBudget, "number", effort); + assert.ok((cfg?.thinkingBudget ?? 0) > 0, effort); + assert.equal(cfg?.includeThoughts, true, effort); + } + assert.equal( + openai(model, { reasoning_effort: "low" }).generationConfig.thinkingConfig?.thinkingBudget, + 1024 + ); + }); + + test(`claude -> gemini: ${model} with thinking.budget_tokens 0 omits thinkingBudget 0`, () => { + const result = claude(model, { thinking: { type: "enabled", budget_tokens: 0 } }); + // budget_tokens 0 on the Claude path is the dynamic-thinking sentinel (#6813): + // includeThoughts stays true, only the rejected zero budget is dropped. + assert.deepEqual(result.generationConfig.thinkingConfig, { includeThoughts: true }); + }); + + test(`claude -> gemini: ${model} with positive budget_tokens still sends thinkingBudget`, () => { + const result = claude(model, { thinking: { type: "enabled", budget_tokens: 2048 } }); + assert.deepEqual(result.generationConfig.thinkingConfig, { + thinkingBudget: 2048, + includeThoughts: true, + }); + }); + + test(`claude -> gemini: ${model} with output_config.effort none sends no thinkingBudget`, () => { + const result = claude(model, { output_config: { effort: "none" } }); + assert.equal(result.generationConfig.thinkingConfig?.thinkingBudget, undefined); + }); + + test(`claude -> gemini: ${model} with output_config.effort low sends positive budget`, () => { + const result = claude(model, { output_config: { effort: "low" } }); + assert.deepEqual(result.generationConfig.thinkingConfig, { + thinkingBudget: 1024, + includeThoughts: true, + }); + }); +} + +test("openai -> gemini: non-Flash-Lite models keep thinkingBudget 0 (#6813/#6943)", () => { + for (const model of ["gemini-2.5-pro", "gemini-2.5-flash"]) { + assert.deepEqual( + openai(model, { reasoning_effort: "none" }).generationConfig.thinkingConfig, + { thinkingBudget: 0, includeThoughts: false }, + model + ); + assert.deepEqual( + openai(model, { thinking: { type: "enabled", budget_tokens: 0 } }).generationConfig + .thinkingConfig, + { thinkingBudget: 0, includeThoughts: false }, + model + ); + } +}); + +test("claude -> gemini: non-Flash-Lite models keep thinkingBudget 0 (#6813)", () => { + assert.deepEqual( + claude("gemini-2.5-pro", { thinking: { type: "enabled", budget_tokens: 0 } }).generationConfig + .thinkingConfig, + { thinkingBudget: 0, includeThoughts: true } + ); +}); diff --git a/tests/unit/gemini-midstream-responses.test.ts b/tests/unit/gemini-midstream-responses.test.ts index 284591f1e431..891fdae59322 100644 --- a/tests/unit/gemini-midstream-responses.test.ts +++ b/tests/unit/gemini-midstream-responses.test.ts @@ -1,6 +1,6 @@ /** * Regression test: Gemini mid-stream 503 error translated through the - * Responses-API pipeline must emit a proper `response.completed` with + * Responses-API pipeline must emit a proper `response.failed` with * `status: "failed"` and close the reasoning item, instead of silently * aborting the stream. * @@ -12,8 +12,7 @@ import test from "node:test"; import assert from "node:assert/strict"; -const { translateResponse, initState } = - await import("../../open-sse/translator/index.ts"); +const { translateResponse, initState } = await import("../../open-sse/translator/index.ts"); const { FORMATS } = await import("../../open-sse/translator/formats.ts"); const THOUGHT_TEXT = "The user wants me to execute a cron job named `vibe-check`"; @@ -48,7 +47,7 @@ const ERROR_CHUNK = { }, }; -test("mid-stream 503 error -> response.completed with status='failed'", () => { +test("mid-stream 503 error -> response.failed with status='failed'", () => { const state = initState(FORMATS.OPENAI_RESPONSES); // ── Step 1: Send thought chunk ── @@ -59,10 +58,7 @@ test("mid-stream 503 error -> response.completed with status='failed'", () => { state ); - assert.ok( - thoughtEvents?.length > 0, - "thought chunk should produce Responses API events" - ); + assert.ok(thoughtEvents?.length > 0, "thought chunk should produce Responses API events"); // Verify reasoning was started const reasoningItemAdded = thoughtEvents.find( @@ -92,31 +88,18 @@ test("mid-stream 503 error -> response.completed with status='failed'", () => { assert.equal(state.upstreamError.status, 503); // ── Step 3: Flush stream ── - const flushEvents = translateResponse( - FORMATS.GEMINI, - FORMATS.OPENAI_RESPONSES, - null, - state - ); + const flushEvents = translateResponse(FORMATS.GEMINI, FORMATS.OPENAI_RESPONSES, null, state); assert.ok(flushEvents?.length > 0, "flush should produce events"); // The reasoning item should be properly closed - const reasoningDone = flushEvents.find( - (e) => e?.data?.type === "response.output_item.done" - ); + const reasoningDone = flushEvents.find((e) => e?.data?.type === "response.output_item.done"); assert.ok(reasoningDone, "flush should emit response.output_item.done for reasoning"); - assert.equal( - reasoningDone.data.item?.type, - "reasoning", - "done item should be type 'reasoning'" - ); + assert.equal(reasoningDone.data.item?.type, "reasoning", "done item should be type 'reasoning'"); - // The response should be completed with status 'failed' and error info - const completedEvent = flushEvents.find( - (e) => e?.data?.type === "response.completed" - ); - assert.ok(completedEvent, "flush should emit response.completed"); + // The response should fail with error info + const completedEvent = flushEvents.find((e) => e?.data?.type === "response.failed"); + assert.ok(completedEvent, "flush should emit response.failed"); assert.equal( completedEvent.data.response.status, "failed", @@ -126,41 +109,36 @@ test("mid-stream 503 error -> response.completed with status='failed'", () => { completedEvent.data.response.error, "response.error should be present when upstreamError is set" ); - assert.ok( - completedEvent.data.response.error?.code, - "response.error.code should be truthy" - ); - assert.match( - completedEvent.data.response.error?.message || "", - /high demand/ - ); + assert.ok(completedEvent.data.response.error?.code, "response.error.code should be truthy"); + assert.match(completedEvent.data.response.error?.message || "", /high demand/); // ── Step 4: Verify the reverse — no upstreamError = status "completed" ── const cleanState = initState(FORMATS.OPENAI_RESPONSES); - // Send thought chunk - translateResponse(FORMATS.GEMINI, FORMATS.OPENAI_RESPONSES, THOUGHT_CHUNK, cleanState); - - // Flush without error - const cleanFlush = translateResponse( + // A clean stream includes the provider's explicit finish signal. + const cleanEvents = translateResponse( FORMATS.GEMINI, FORMATS.OPENAI_RESPONSES, - null, + { + ...THOUGHT_CHUNK, + candidates: THOUGHT_CHUNK.candidates.map((candidate) => ({ + ...candidate, + finishReason: "STOP", + })), + }, cleanState ); + const cleanFlush = [ + ...cleanEvents, + ...translateResponse(FORMATS.GEMINI, FORMATS.OPENAI_RESPONSES, null, cleanState), + ]; - const cleanCompleted = cleanFlush.find( - (e) => e?.data?.type === "response.completed" - ); + const cleanCompleted = cleanFlush.find((e) => e?.data?.type === "response.completed"); assert.ok(cleanCompleted, "clean flush should emit response.completed"); assert.equal( cleanCompleted.data.response.status, "completed", "clean response should have status 'completed'" ); - assert.equal( - cleanCompleted.data.response.error, - null, - "clean response should have error: null" - ); + assert.equal(cleanCompleted.data.response.error, null, "clean response should have error: null"); }); diff --git a/tests/unit/gemini-responses-error-redaction.test.ts b/tests/unit/gemini-responses-error-redaction.test.ts index f8814b3e8cb3..a3d1e559c3b5 100644 --- a/tests/unit/gemini-responses-error-redaction.test.ts +++ b/tests/unit/gemini-responses-error-redaction.test.ts @@ -3,7 +3,7 @@ import test from "node:test"; import { translateResponse, initState } from "../../open-sse/translator/index.ts"; import { FORMATS } from "../../open-sse/translator/formats.ts"; -test("Gemini keeps raw failure wording internal but projects response.completed.error", () => { +test("Gemini keeps raw failure wording internal but projects response.failed.error", () => { const state = initState(FORMATS.OPENAI_RESPONSES); const hostileMessage = "Gemini failed at /srv/omniroute/private-runtime.ts:71:3 token=sk-gemini-secret-123456"; @@ -27,7 +27,7 @@ test("Gemini keeps raw failure wording internal but projects response.completed. assert.match(state.upstreamError?.message ?? "", /private-runtime\.ts/); const flushed = translateResponse(FORMATS.GEMINI, FORMATS.OPENAI_RESPONSES, null, state); - const completed = flushed.find((event) => event?.data?.type === "response.completed"); + const completed = flushed.find((event) => event?.data?.type === "response.failed"); assert.ok(completed); assert.equal(completed.data.response.status, "failed"); diff --git a/tests/unit/gemini-tool-call-id-normalization.test.ts b/tests/unit/gemini-tool-call-id-normalization.test.ts new file mode 100644 index 000000000000..801a8c771938 --- /dev/null +++ b/tests/unit/gemini-tool-call-id-normalization.test.ts @@ -0,0 +1,107 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { normalizeGeminiToolCallIds } from "../../open-sse/translator/request/openai-to-gemini/toolCallIds.ts"; +import type { GeminiContent } from "../../open-sse/translator/request/openai-to-gemini/helpers.ts"; + +function call(id: string, name = "tool"): GeminiContent { + return { + role: "model", + parts: [{ thoughtSignature: `signature-${name}`, functionCall: { id, name, args: { name } } }], + }; +} + +function response(id: string, name = "tool"): GeminiContent { + return { role: "user", parts: [{ functionResponse: { id, name, response: { result: name } } }] }; +} + +function ids(contents: GeminiContent[], key: string): unknown[] { + return contents + .flatMap((content) => content.parts) + .filter((part) => part[key]) + .map((part) => (part[key] as { id: string }).id); +} + +test("reserves future call and orphan response IDs before allocating suffixes", () => { + const history = [ + call("repeat"), + response("repeat"), + call("repeat"), + response("repeat"), + call("repeat_occ2"), + response("repeat_occ2"), + response("repeat_occ3"), + ]; + const result = normalizeGeminiToolCallIds(history); + assert.deepEqual(ids(result, "functionCall"), ["repeat", "repeat_occ4", "repeat_occ2"]); + assert.deepEqual(ids(result, "functionResponse"), [ + "repeat", + "repeat_occ4", + "repeat_occ2", + "repeat_occ3", + ]); +}); + +test("normalization is deterministic, idempotent and does not mutate its input", () => { + const history = [ + call("repeat", "first"), + response("repeat", "first"), + call("repeat", "second"), + response("repeat", "second"), + ]; + const original = structuredClone(history); + const result = normalizeGeminiToolCallIds(history); + assert.deepEqual(history, original); + assert.deepEqual(normalizeGeminiToolCallIds(history), result); + assert.deepEqual(normalizeGeminiToolCallIds(result), result); + assert.deepEqual(result[2].parts[0], { + thoughtSignature: "signature-second", + functionCall: { id: "repeat_occ2", name: "second", args: { name: "second" } }, + }); +}); + +test("parallel distinct IDs and reversed result order remain correctly paired across reuse", () => { + const history = [ + { role: "model", parts: [...call("a").parts, ...call("b").parts] }, + { role: "user", parts: [...response("b").parts, ...response("a").parts] }, + { role: "model", parts: [...call("a").parts, ...call("b").parts] }, + { role: "user", parts: [...response("b").parts, ...response("a").parts] }, + ]; + const result = normalizeGeminiToolCallIds(history); + assert.deepEqual(ids(result, "functionCall"), ["a", "b", "a_occ2", "b_occ2"]); + assert.deepEqual(ids(result, "functionResponse"), ["b", "a", "b_occ2", "a_occ2"]); +}); + +test("long IDs with shared prefixes do not collide and the first IDs stay unchanged", () => { + const first = "x".repeat(512) + "a"; + const second = "x".repeat(512) + "b"; + const result = normalizeGeminiToolCallIds([ + call(first), + response(first), + call(first), + response(first), + call(second), + response(second), + call(second), + response(second), + ]); + assert.deepEqual(ids(result, "functionCall"), [first, `${first}_occ2`, second, `${second}_occ2`]); + assert.deepEqual(ids(result, "functionResponse"), ids(result, "functionCall")); +}); + +test("trailing reuse gets an ID without inventing a result and ID-less Vertex parts are unchanged", () => { + const trailing = normalizeGeminiToolCallIds([call("repeat"), response("repeat"), call("repeat")]); + assert.deepEqual(ids(trailing, "functionCall"), ["repeat", "repeat_occ2"]); + assert.deepEqual(ids(trailing, "functionResponse"), ["repeat"]); + const vertex = [ + { role: "model", parts: [{ functionCall: { name: "tool", args: {} } }] }, + { role: "user", parts: [{ functionResponse: { name: "tool", response: {} } }] }, + ]; + assert.deepEqual(normalizeGeminiToolCallIds(vertex), vertex); +}); + +test("overlapping IDs are rejected without partially changing input", () => { + const history = [call("same", "first"), call("same", "second"), response("same")]; + const original = structuredClone(history); + assert.throws(() => normalizeGeminiToolCallIds(history), /overlapping tool calls/); + assert.deepEqual(history, original); +}); diff --git a/tests/unit/generic-quota-fetcher.test.ts b/tests/unit/generic-quota-fetcher.test.ts index 0041cd607523..ea4811182411 100644 --- a/tests/unit/generic-quota-fetcher.test.ts +++ b/tests/unit/generic-quota-fetcher.test.ts @@ -98,10 +98,19 @@ test("convertUsageToQuotaInfo skips unlimited and unmeasurable windows", () => { assert.deepEqual(Object.keys(result!.windows || {}), ["session"]); }); -test("convertUsageToQuotaInfo returns null when no windows are measurable", () => { +test("convertUsageToQuotaInfo marks an unlimited-only provider as unlimited, not as a failed read (#15347)", () => { + // `null` from a fetcher means "could not read it" and is ranked below real readings in + // auto-combo; a provider with no cap is a real, known reading of full headroom. const result = convertUsageToQuotaInfo({ quotas: { unlimited_thing: { unlimited: true } }, }); + assert.deepEqual(result, { used: 0, total: 0, percentUsed: 0, unlimited: true }); +}); + +test("convertUsageToQuotaInfo returns null when no window is measurable and none is unlimited", () => { + const result = convertUsageToQuotaInfo({ + quotas: { unknown_shape: { resetAt: null }, unreported: { fractionReported: false } }, + }); assert.equal(result, null); }); diff --git a/tests/unit/ghe-copilot-claude-target-format.test.ts b/tests/unit/ghe-copilot-claude-target-format.test.ts new file mode 100644 index 000000000000..ecebd5eb12f4 --- /dev/null +++ b/tests/unit/ghe-copilot-claude-target-format.test.ts @@ -0,0 +1,12 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { getModelsByProviderId } from "../../open-sse/config/providerModels.ts"; + +test("GHE Copilot Claude models declare the Anthropic target format", () => { + const missingTargetFormat = getModelsByProviderId("ghe-copilot") + .filter((model) => model.id.startsWith("claude-")) + .filter((model) => model.targetFormat !== "claude") + .map((model) => model.id); + + assert.deepEqual(missingTargetFormat, []); +}); diff --git a/tests/unit/ghe-copilot.test.ts b/tests/unit/ghe-copilot.test.ts index b99eaa0c5b65..f33520aeaa7e 100644 --- a/tests/unit/ghe-copilot.test.ts +++ b/tests/unit/ghe-copilot.test.ts @@ -4,6 +4,10 @@ import { GheCopilotExecutor } from "../../open-sse/executors/ghe-copilot.ts"; import { gheCopilotProvider } from "../../open-sse/config/providers/registry/ghe-copilot/index.ts"; import { GHE_COPILOT_TARGET } from "../../src/mitm/targets/ghe-copilot.ts"; import type { ProviderCredentials } from "../../open-sse/executors/base.ts"; +import { getModelTargetFormat } from "../../open-sse/config/providerModels.ts"; +import { resolveChatCoreTargetFormat } from "../../open-sse/handlers/chatCore/targetFormat.ts"; +import { translateRequest } from "../../open-sse/translator/index.ts"; +import { GithubExecutor } from "../../open-sse/executors/github.ts"; test("GHE Copilot registry exposes Claude Opus 5", () => { const opus5 = gheCopilotProvider.models.find((model) => model.id === "claude-opus-5"); @@ -76,6 +80,118 @@ test("buildUrl uses responses endpoint for gpt-5.4-mini and gpt-5.6-sol", () => ); }); +test("buildUrl routes unregistered GPT-6 models to the Responses endpoint", () => { + const executor = new GheCopilotExecutor({ + gheUrl: "https://ghe.company.com", + clientId: "test-client", + clientSecret: "test-secret", + }); + const credentials: ProviderCredentials = { + providerSpecificData: { gheUrl: "https://ghe.company.com" }, + }; + + assert.strictEqual( + executor.buildUrl("ghe-copilot/gpt-6-luna", true, 0, credentials), + "https://ghe.company.com/responses" + ); +}); + +// Ids the PR's buildUrl regex (`^gpt-6`) sends to /responses. Unregistered, so +// they have no catalog targetFormat — the body must still be Responses-shaped. +const UNREGISTERED_GPT6_MODELS = [ + "ghe-copilot/gpt-6-luna", + "gpt-6-sol", + "gpt-6-astra", + "gpt-6-luna-high", +]; + +function outboundBody( + executor: GheCopilotExecutor, + model: string, + credentials: ProviderCredentials +): Record { + const bare = model.startsWith("ghe-copilot/") ? model.slice("ghe-copilot/".length) : model; + const { targetFormat } = resolveChatCoreTargetFormat({ + provider: "ghe-copilot", + resolvedModel: bare, + apiFormat: undefined, + sourceFormat: "openai", + customModelTargetFormat: undefined, + providerSpecificData: credentials.providerSpecificData, + }); + const translated = translateRequest( + "openai", + targetFormat, + bare, + { + model: bare, + messages: [{ role: "user", content: "hi" }], + max_tokens: 16, + }, + true, + credentials, + "ghe-copilot" + ); + return executor.transformRequest(model, translated, true, credentials) as Record; +} + +test("GPT-6 requests routed to /responses send a Responses body, not chat completions", () => { + const executor = new GheCopilotExecutor({ + gheUrl: "https://ghe.company.com", + clientId: "test-client", + clientSecret: "test-secret", + }); + const credentials: ProviderCredentials = { + providerSpecificData: { gheUrl: "https://ghe.company.com" }, + }; + + for (const model of UNREGISTERED_GPT6_MODELS) { + assert.strictEqual( + executor.buildUrl(model, true, 0, credentials), + "https://ghe.company.com/responses", + `${model} URL` + ); + const sent = outboundBody(executor, model, credentials); + assert.ok( + Array.isArray(sent.input), + `${model} must send Responses input, got keys ${Object.keys(sent).join(",")}` + ); + assert.equal(sent.messages, undefined, `${model} must not send chat-completions messages`); + assert.equal(sent.max_tokens, undefined, `${model} must not send chat-completions max_tokens`); + assert.equal(sent.max_output_tokens, 16); + } +}); + +test("models this PR did not move keep chat-completions bodies and URLs", () => { + const executor = new GheCopilotExecutor({ + gheUrl: "https://ghe.company.com", + clientId: "test-client", + clientSecret: "test-secret", + }); + const credentials: ProviderCredentials = { + providerSpecificData: { gheUrl: "https://ghe.company.com" }, + }; + + for (const model of ["gpt-4o", "gemini-3.7-flash"]) { + assert.strictEqual( + executor.buildUrl(model, true, 0, credentials), + "https://ghe.company.com/chat/completions" + ); + const sent = outboundBody(executor, model, credentials); + assert.ok(Array.isArray(sent.messages), `${model} must keep chat-completions messages`); + assert.equal(sent.input, undefined, `${model} must not be rewritten to Responses input`); + } + + // github.com Copilot did not gain the gpt-6 URL regex. Its body stays chat. + assert.equal(getModelTargetFormat("gh", "gpt-6-luna"), null); + assert.equal(getModelTargetFormat("github", "gpt-6-luna"), null); + const github = new GithubExecutor(); + assert.equal( + github.buildUrl("gpt-6-luna", true, 0, { apiKey: "test-token" }), + "https://api.githubcopilot.com/chat/completions" + ); +}); + test("buildUrl routes Claude to the native /v1/messages shim (not chat/completions)", () => { const executor = new GheCopilotExecutor({ gheUrl: "https://ghe.company.com", diff --git a/tests/unit/github-copilot-discovered-routing.test.ts b/tests/unit/github-copilot-discovered-routing.test.ts new file mode 100644 index 000000000000..7f1953fe9dfb --- /dev/null +++ b/tests/unit/github-copilot-discovered-routing.test.ts @@ -0,0 +1,122 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-copilot-routing-")); +process.env.DATA_DIR = dataDir; +process.env.API_KEY_SECRET ||= "copilot-routing-test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const providers = await import("../../src/lib/db/providers.ts"); +const { persistDiscoveredModels, getCachedDiscoveredModels } = + await import("../../src/lib/providerModels/modelDiscovery.ts"); +const { getModelInfo } = await import("../../src/sse/services/model.ts"); +const { parseGitHubCopilotModels } = await import("../../open-sse/services/githubCopilotModels.ts"); +const { resolveChatCoreTargetFormat } = + await import("../../open-sse/handlers/chatCore/targetFormat.ts"); +const { resolveExecutionCredentials } = + await import("../../open-sse/handlers/chatCore/executionCredentials.ts"); +const { GithubExecutor } = await import("../../open-sse/executors/github.ts"); +const { isUsableChatModel } = + await import("../../src/app/api/v1/vscode/[token]/usableChatModel.ts"); + +let connectionId: string; +const executor = new GithubExecutor(); + +test.before(async () => { + const connection = await providers.createProviderConnection({ + provider: "github", + authType: "oauth", + name: "discovered-routing-test", + isActive: true, + testStatus: "active", + }); + connectionId = connection.id; + const rows = parseGitHubCopilotModels({ + data: [ + { id: "gpt-6-luna", supported_endpoints: ["/responses"] }, + { + id: "gpt-6.1-sol", + capabilities: { type: "chat", supported_endpoints: ["/v1/responses"] }, + }, + { id: "gpt-6-chat", supported_endpoints: ["/chat/completions"] }, + { id: "gpt-6-dual", supported_endpoints: ["/responses", "/chat/completions"] }, + { id: "gemini-future", supported_endpoints: ["/responses"] }, + { id: "claude-future", supported_endpoints: ["/responses"] }, + { id: "gpt-6-unknown" }, + ], + }); + await persistDiscoveredModels("github", connectionId, rows); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +async function resolveRouting(model: string) { + const info = await getModelInfo(`gh/${model}`); + assert.equal(info.provider, "github"); + assert.equal(info.model, model); + const { targetFormat } = resolveChatCoreTargetFormat({ + provider: info.provider, + resolvedModel: info.model, + apiFormat: info.apiFormat, + sourceFormat: "openai", + customModelTargetFormat: info.targetFormat, + providerSpecificData: {}, + }); + const credentials = resolveExecutionCredentials({ + credentials: { providerSpecificData: {} }, + provider: info.provider, + targetFormat, + nativeCodexPassthrough: false, + endpointPath: "/v1/chat/completions", + ccSessionId: null, + }); + return { targetFormat, url: executor.buildUrl(info.model, false, 0, credentials) }; +} + +test("discovered GPT-6 Responses-only models retain endpoint metadata and route to Responses", async () => { + const cached = await getCachedDiscoveredModels("github", connectionId); + assert.deepEqual(cached.find((row) => row.id === "gpt-6-luna")?.supportedEndpoints, [ + "responses", + ]); + for (const model of ["gpt-6-luna", "gpt-6.1-sol"]) { + assert.deepEqual(await resolveRouting(model), { + targetFormat: "openai-responses", + url: "https://api.githubcopilot.com/responses", + }); + } +}); + +test("synced endpoint kinds remain usable in the VS Code model catalog", async () => { + const cached = await getCachedDiscoveredModels("github", connectionId); + for (const id of ["gpt-6-luna", "gpt-6.1-sol", "gpt-6-chat", "gpt-6-dual"]) { + const model = cached.find((row) => row.id === id); + assert.ok(model); + assert.ok(isUsableChatModel({ id, supported_endpoints: model.supportedEndpoints }), id); + } +}); + +test("chat-capable and metadata-free GPT models keep Chat Completions", async () => { + for (const model of ["gpt-6-chat", "gpt-6-dual", "gpt-6-unknown"]) { + assert.deepEqual(await resolveRouting(model), { + targetFormat: "openai", + url: "https://api.githubcopilot.com/chat/completions", + }); + } +}); + +test("discovery cannot route Gemini or Claude to the Copilot Responses endpoint", async () => { + assert.deepEqual(await resolveRouting("gemini-future"), { + targetFormat: "openai", + url: "https://api.githubcopilot.com/chat/completions", + }); + assert.deepEqual(await resolveRouting("claude-future"), { + targetFormat: "claude", + url: "https://api.githubcopilot.com/v1/messages", + }); +}); diff --git a/tests/unit/github-copilot-model-discovery.test.ts b/tests/unit/github-copilot-model-discovery.test.ts index 83ff7ee24e9f..06c93bbf7308 100644 --- a/tests/unit/github-copilot-model-discovery.test.ts +++ b/tests/unit/github-copilot-model-discovery.test.ts @@ -98,6 +98,67 @@ test("#3120 parseGitHubCopilotModels keeps every entitled CHAT model (capability assert.ok(!ids.includes("hidden-from-picker"), "model_picker_enabled=false is not routable"); }); +test("discovery preserves Responses-only routing for newly entitled GPT-6 models", () => { + const models = parseGitHubCopilotModels({ + data: ["gpt-6-luna", "gpt-6.1-sol"].map((id) => ({ + id, + capabilities: { type: "chat" }, + supported_endpoints: ["/responses"], + })), + }); + assert.deepEqual( + models, + ["gpt-6-luna", "gpt-6.1-sol"].map((id) => ({ + id, + name: id, + owned_by: "github", + supportedEndpoints: ["responses"], + targetFormat: "openai-responses", + })) + ); +}); + +test("discovery normalizes nested endpoint metadata without guessing from GPT versions", () => { + const models = parseGitHubCopilotModels({ + data: [ + { + id: "new-responses-model", + capabilities: { + type: "chat", + supported_endpoints: [" /v1/responses/ ", "/v1/responses/", null, 42, ""], + }, + }, + { + id: "top-level-wins", + capabilities: { type: "chat", supported_endpoints: ["/responses"] }, + supported_endpoints: ["/chat/completions"], + }, + { + id: "messages-capable", + capabilities: { type: "chat" }, + supported_endpoints: ["/responses", "/v1/messages"], + }, + ...["gpt-6-unlisted", "gemini-future", "claude-future"].map((id) => ({ + id, + capabilities: { type: "chat" }, + ...(id === "gpt-6-unlisted" ? {} : { supported_endpoints: ["/responses"] }), + })), + ], + }); + assert.deepEqual(models[0], { + id: "new-responses-model", + name: "new-responses-model", + owned_by: "github", + supportedEndpoints: ["responses"], + targetFormat: "openai-responses", + }); + assert.deepEqual(models[1].supportedEndpoints, ["chat"]); + assert.deepEqual(models[2].supportedEndpoints, ["chat", "responses"]); + for (const model of models.slice(1)) { + assert.equal(model.targetFormat, undefined, `${model.id} must not infer Responses-only`); + } +}); + test("#3121 a model NOT in the live response is not advertised (entitlement filtering)", () => { const models = parseGitHubCopilotModels(MOCK_COPILOT_MODELS_RESPONSE); const ids = models.map((m) => m.id); diff --git a/tests/unit/groq-field-strip-wiring.test.ts b/tests/unit/groq-field-strip-wiring.test.ts index 07a67f80dd7f..223194b9e242 100644 --- a/tests/unit/groq-field-strip-wiring.test.ts +++ b/tests/unit/groq-field-strip-wiring.test.ts @@ -1,5 +1,6 @@ import { test } from "node:test"; import assert from "node:assert/strict"; +import { withReasoningRuleContext } from "../../open-sse/utils/reasoningRuleContext.ts"; import { DefaultExecutor } from "../../open-sse/executors/default.ts"; // ────────────────────────────────────────────────────────────────────────────── @@ -117,3 +118,41 @@ test("Non-Groq executor does NOT strip logprobs or messages[].name", async () => const messages = capturedBody!.messages as Record[]; assert.equal(messages[0].name, "bob"); }); + +test("Groq GPT-OSS dispatch maps forced none to low and drops foreign reasoning envelopes", async () => { + const executor = new DefaultExecutor("groq"); + const originalFetch = globalThis.fetch; + let capturedBody: Record | undefined; + globalThis.fetch = async (_url: string | URL | Request, init: RequestInit = {}) => { + capturedBody = JSON.parse(String(init.body)); + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; + try { + await executor.execute({ + model: "openai/gpt-oss-20b", + body: { + messages: [{ role: "user", content: "hello" }], + reasoning: { effort: "none" }, + output_config: { effort: "none" }, + }, + stream: false, + credentials: withReasoningRuleContext( + { apiKey: "groq-test-key" }, + { + id: "test-rule", + effortMode: "force", + targetEffort: "none", + } + ), + }); + } finally { + globalThis.fetch = originalFetch; + } + assert.ok(capturedBody, "the executor must dispatch a request"); + assert.equal(capturedBody.reasoning_effort, "low"); + assert.equal("reasoning" in capturedBody, false); + assert.equal("output_config" in capturedBody, false); +}); diff --git a/tests/unit/guide-settings-route.test.ts b/tests/unit/guide-settings-route.test.ts index a4c668bac46c..8bad6a9eb9dc 100644 --- a/tests/unit/guide-settings-route.test.ts +++ b/tests/unit/guide-settings-route.test.ts @@ -215,6 +215,46 @@ test("guide-settings POST preserves existing OpenCode config fields while only u }); }); +test("guide-settings POST writes catalog limits and falls back when a model is absent (#15406)", async () => { + const req = await buildRequest("opencode", { + baseUrl: "http://localhost:20128/v1", + apiKey: "sk-123", + model: "cx/known-model", + models: ["cx/known-model", "unknown/model"], + modelLabels: { "cx/known-model": "Known model" }, + catalog: [ + { + id: "cx/known-model", + context_length: 200_000, + max_output_tokens: 32_000, + capabilities: { reasoning: true }, + }, + ], + }); + const response = (await guideSettingsRoute.POST(req, { + params: { toolId: "opencode" }, + })) as Response; + assert.equal(response.status, 200); + + const content = parse(await fs.readFile(OPENCODE_CONFIG_PATH, "utf-8")); + const known = content.provider.omniroute.models["cx/known-model"]; + assert.equal(known.limit.context, 200_000); + assert.equal(known.limit.output, 32_000); + assert.notEqual(known.limit.context, 128_000); + assert.notEqual(known.limit.output, 8_192); + assert.equal(known.reasoning, true); + + const unknown = content.provider.omniroute.models["unknown/model"]; + assert.equal(unknown.limit.context, 128_000); + assert.equal(unknown.limit.output, 8_192); + assert.equal(unknown.reasoning, undefined); + + assert.equal(content.providers.omniroute.models["cx/known-model"].limit.context, 200_000); + assert.equal(content.providers.omniroute.models["cx/known-model"].limit.output, 32_000); + assert.equal(content.providers.omniroute.models["unknown/model"].limit.context, 128_000); + assert.equal(content.providers.omniroute.models["unknown/model"].limit.output, 8_192); +}); + test("guide-settings POST refuses to overwrite an invalid opencode.jsonc (#10227)", async () => { const invalidJsonc = "{ invalid jsonc\n"; await fs.mkdir(path.dirname(OPENCODE_JSONC_CONFIG_PATH), { recursive: true }); diff --git a/tests/unit/hard-session-lease-bypass-inventory.test.ts b/tests/unit/hard-session-lease-bypass-inventory.test.ts index 6a6f194587dc..09e8d65c7cd4 100644 --- a/tests/unit/hard-session-lease-bypass-inventory.test.ts +++ b/tests/unit/hard-session-lease-bypass-inventory.test.ts @@ -24,9 +24,11 @@ const EXPECTED: Record> = { // executeProviderRequest(), whose assertManagedLeaseFence(attemptConnectionId) rejects a // connection other than the leased one — so it is fenced centrally (class A). // #14914 moved that loop (and its credential rollback) into - // chatCore/emptyTurnRetryLoop.ts; chatCore.ts now passes `getProviderCredentials` in - // as a dependency (a reference, not a call), so the site is inventoried at its new - // home — still dispatched through executeProviderRequest(), still class A. + // chatCore/emptyTurnRetryLoop.ts; the response-path split (chatCore.ts split into + // response-path leaves) then moved the call site into streamingTail.ts, which now + // passes `getProviderCredentials` in as a dependency (a reference, not a call) to + // emptyTurnRetryLoop.ts — still dispatched through executeProviderRequest(), still + // fenced centrally (class A). "open-sse/handlers/chatCore/emptyTurnRetryLoop.ts": 1, "open-sse/handlers/chatCore/providerExecutionPipeline.ts": 2, "open-sse/services/imageCombo.ts": 1, @@ -80,7 +82,11 @@ const EXPECTED: Record> = { "src/sse/services/imageCredentialRetry.ts": 1, }, executor: { - "open-sse/handlers/chatCore.ts": 3, + // The three executor.execute() sites that used to sit in chatCore.ts moved + // with the decomposition: two into the wire-send leaf and one into the + // streaming leg (same sites, new homes). + "open-sse/handlers/chatCore/executeProviderRequest.ts": 2, + "open-sse/handlers/chatCore/streamingResponse.ts": 1, "open-sse/handlers/chatCore/cliproxyModelMapping.ts": 1, "open-sse/handlers/chatCore/cliproxyapiCredentials.ts": 1, // v3.8.51 #11754: the legacy common ChatGPT Web's synthetic @@ -97,7 +103,10 @@ const EXPECTED: Record> = { }, connection: { "open-sse/handlers/autoComboCandidates.ts": 1, - "open-sse/handlers/chatCore.ts": 3, + // Two of the three connection re-resolution sites moved into the streaming + // leg with the decomposition (same sites, new home). + "open-sse/handlers/chatCore.ts": 1, + "open-sse/handlers/chatCore/streamingResponse.ts": 2, "open-sse/handlers/cursorCliProxy.ts": 1, "open-sse/services/alibabaFreeTier.ts": 1, "open-sse/services/alibabaFreeTierQuotaFetcher.ts": 1, @@ -257,7 +266,8 @@ const CLASSIFICATION: Record> = { ]) ), executor: { - "open-sse/handlers/chatCore.ts": "A", + "open-sse/handlers/chatCore/executeProviderRequest.ts": "A", + "open-sse/handlers/chatCore/streamingResponse.ts": "A", "open-sse/handlers/chatCore/cliproxyModelMapping.ts": "A", "open-sse/handlers/chatCore/cliproxyapiCredentials.ts": "A", "open-sse/handlers/videoGeneration.ts": "B", @@ -271,6 +281,7 @@ const CLASSIFICATION: Record> = { [ "open-sse/handlers/autoComboCandidates.ts", "open-sse/handlers/chatCore.ts", + "open-sse/handlers/chatCore/streamingResponse.ts", "open-sse/services/alibabaFreeTier.ts", "open-sse/services/alibabaFreeTierQuotaFetcher.ts", "open-sse/services/combo/executeTargetGates.ts", @@ -380,7 +391,6 @@ test("hard-lease credential, executor, and connection-query inventory has no unc test("managed request surfaces are fenced centrally or rejected before independent dispatch", () => { const chat = fs.readFileSync(path.join(REPO_ROOT, "src/sse/handlers/chat.ts"), "utf8"); - const core = fs.readFileSync(path.join(REPO_ROOT, "open-sse/handlers/chatCore.ts"), "utf8"); const ws = fs.readFileSync( path.join(REPO_ROOT, "src/app/api/internal/codex-responses-ws/route.ts"), "utf8" @@ -407,9 +417,22 @@ test("managed request surfaces are fenced centrally or rejected before independe assert.match(chat, /parseManagedLeaseRequestContext\(request\.headers\)/); assert.match(chat, /isManagedComboUnsupported/); - assert.match(core, /assertManagedLeaseFence\(attemptConnectionId\)/); + // The fence call sites moved into the leg leaves with the decomposition. + const eprSource = fs.readFileSync( + path.join(REPO_ROOT, "open-sse/handlers/chatCore/executeProviderRequest.ts"), + "utf8" + ); + const streamingLeg = fs.readFileSync( + path.join(REPO_ROOT, "open-sse/handlers/chatCore/streamingResponse.ts"), + "utf8" + ); + const nonStreamingLeg = fs.readFileSync( + path.join(REPO_ROOT, "open-sse/handlers/chatCore/nonStreamingResponse.ts"), + "utf8" + ); + assert.match(eprSource, /assertManagedLeaseFence\(attemptConnectionId\)/); assert.match( - core, + streamingLeg, /assertManagedLeaseFence\(getExecutionConnectionId\(getExecutionCredentials\(\)\)\)/ ); // #12867 (d6f315018) extracted codex 429 / antigravity 422 account rotation out @@ -423,9 +446,14 @@ test("managed request surfaces are fenced centrally or rejected before independe path.join(REPO_ROOT, "open-sse/handlers/chatCore/providerExecutionPipeline.ts"), "utf8" ); - const rotationPolicySites = core.match( - /allowAccountRotation: !managedLease && comboStrategy !== "context-relay"/g - ); + const rotationPolicySites = [ + ...nonStreamingLeg.matchAll( + /allowAccountRotation: !managedLease && comboStrategy !== "context-relay"/g + ), + ...streamingLeg.matchAll( + /allowAccountRotation: !managedLease && comboStrategy !== "context-relay"/g + ), + ]; assert.equal( rotationPolicySites?.length, 2, diff --git a/tests/unit/http-client-abort-guard-direct-timeout-12861.test.ts b/tests/unit/http-client-abort-guard-direct-timeout-12861.test.ts index b50e6fa96583..c59e0d454e35 100644 --- a/tests/unit/http-client-abort-guard-direct-timeout-12861.test.ts +++ b/tests/unit/http-client-abort-guard-direct-timeout-12861.test.ts @@ -112,6 +112,15 @@ test("isUpstreamNetworkError recognizes fetch failures and proxy unreachable", ( const undiciSocket = Object.assign(new Error("other side closed"), { code: "UND_ERR_SOCKET" }); assert.equal(isUpstreamNetworkError(undiciSocket), true); + + // proxyFetch wraps the undici failure as PROXY_REQUEST_FAILED. + const proxyRequestFailed = Object.assign( + new Error("Proxy request failed: fetch failed (cause ERR_HTTP2_STREAM_ERROR)"), + { code: "PROXY_REQUEST_FAILED", causeCode: "ERR_HTTP2_STREAM_ERROR" } + ); + assert.equal(isUpstreamNetworkError(proxyRequestFailed), true); + assert.equal(shouldSwallowUncaught(proxyRequestFailed, "uncaughtException"), true); + assert.equal(shouldSwallowUncaught(proxyRequestFailed, "unhandledRejection"), true); }); test("isUpstreamNetworkError rejects genuine errors", () => { diff --git a/tests/unit/http-client-abort-guard-relay-timeout.test.ts b/tests/unit/http-client-abort-guard-relay-timeout.test.ts new file mode 100644 index 000000000000..87fd86cfcea4 --- /dev/null +++ b/tests/unit/http-client-abort-guard-relay-timeout.test.ts @@ -0,0 +1,114 @@ +// 2026-10-02 production exit 7 — RELAY_TIMEOUT (stamped by +// open-sse/utils/proxyFetch.ts on the pooled relay path, #9100/#9158, when an +// attempt exceeds RELAY_FETCH_TIMEOUT_MS) escaped as an unhandledRejection / +// uncaughtException and the process crash guard re-threw it because it is not +// one of its benign classes. Same escape class as #12861 +// (DIRECT_RESPONSE_START_TIMEOUT): the request layer already handles the error +// as a plain 504 / combo fallback, so a stray copy delivered to nobody must +// never be process-fatal. +import test from "node:test"; +import assert from "node:assert/strict"; +import { + isClientAbortError, + isRecoverableUpstreamTimeoutError, + shouldSwallowUncaught, +} from "../../src/shared/utils/httpClientAbortGuard.mjs"; + +function relayTimeout(): Error & { code?: string; errorCode?: string; statusCode?: number } { + // Exact production shape (open-sse/utils/proxyFetch.ts relay loop). + return Object.assign( + new Error("[ProxyFetch] Relay timed out after 25000ms (https://relay.example)"), + { code: "RELAY_TIMEOUT", errorCode: "relay_timeout", statusCode: 504 } + ); +} + +test("isRecoverableUpstreamTimeoutError recognizes the RELAY_TIMEOUT production shape", () => { + assert.equal(isRecoverableUpstreamTimeoutError(relayTimeout()), true); + // errorCode-only variant (lib/usage/glmResetCards.ts accepts the same pair). + assert.equal( + isRecoverableUpstreamTimeoutError( + Object.assign(new Error("relay stalled"), { errorCode: "relay_timeout" }) + ), + true + ); + // A raw string abort reason rejects waiters with the string itself. + assert.equal(isRecoverableUpstreamTimeoutError("RELAY_TIMEOUT"), true); + assert.equal(isRecoverableUpstreamTimeoutError("relay_timeout"), true); +}); + +test("isRecoverableUpstreamTimeoutError still recognizes DIRECT_RESPONSE_START_TIMEOUT (#12861)", () => { + assert.equal( + isRecoverableUpstreamTimeoutError( + Object.assign(new Error("Direct response did not start within 30000ms"), { + code: "DIRECT_RESPONSE_START_TIMEOUT", + }) + ), + true + ); + assert.equal(isRecoverableUpstreamTimeoutError("DIRECT_RESPONSE_START_TIMEOUT"), true); +}); + +test("isRecoverableUpstreamTimeoutError rejects unrelated errors", () => { + assert.equal(isRecoverableUpstreamTimeoutError(new Error("boom")), false); + assert.equal( + isRecoverableUpstreamTimeoutError(Object.assign(new Error("x"), { code: "ECONNRESET" })), + false + ); + assert.equal( + isRecoverableUpstreamTimeoutError(Object.assign(new Error("x"), { errorCode: "relay_timeou" })), + false + ); + assert.equal(isRecoverableUpstreamTimeoutError(null), false); + assert.equal(isRecoverableUpstreamTimeoutError(undefined), false); + assert.equal(isRecoverableUpstreamTimeoutError("a string, not an object"), false); +}); + +test("isRecoverableUpstreamTimeoutError does not overlap with isClientAbortError's codes", () => { + const timeoutErr = relayTimeout(); + assert.equal(isClientAbortError(timeoutErr), false); + assert.equal(isRecoverableUpstreamTimeoutError(timeoutErr), true); + + const abortErr = { code: "ECONNRESET" }; + assert.equal(isClientAbortError(abortErr), true); + assert.equal(isRecoverableUpstreamTimeoutError(abortErr), false); +}); + +test("shouldSwallowUncaught swallows RELAY_TIMEOUT for both process-level origins", () => { + const err = relayTimeout(); + assert.equal(shouldSwallowUncaught(err, "uncaughtException"), true); + assert.equal(shouldSwallowUncaught(err, "unhandledRejection"), true); + assert.equal(shouldSwallowUncaught(err, undefined), true); +}); + +test("shouldSwallowUncaught still surfaces genuine errors after the extension", () => { + const genuineBug = new TypeError("Cannot read properties of undefined"); + assert.equal(shouldSwallowUncaught(genuineBug, "uncaughtException"), false); + assert.equal(shouldSwallowUncaught(genuineBug, "unhandledRejection"), false); + + const unknownCode = Object.assign(new Error("mystery"), { code: "RELAY_TIMEOU" }); + assert.equal(shouldSwallowUncaught(unknownCode, "unhandledRejection"), false); +}); + +test("shouldSwallowUncaught still swallows the pre-existing benign classes (no regression)", () => { + assert.equal(shouldSwallowUncaught(new Error("aborted"), "uncaughtException"), true); + assert.equal( + shouldSwallowUncaught( + Object.assign(new Error("Direct response did not start within 30000ms"), { + code: "DIRECT_RESPONSE_START_TIMEOUT", + }), + "unhandledRejection" + ), + true + ); + assert.equal( + shouldSwallowUncaught( + Object.assign(new Error("hedge-cancelled"), { name: "AbortError" }), + "unhandledRejection" + ), + true + ); + assert.equal( + shouldSwallowUncaught(Object.assign(new TypeError("fetch failed"), {}), "unhandledRejection"), + true + ); +}); diff --git a/tests/unit/httpClientAbortGuard.test.mjs b/tests/unit/httpClientAbortGuard.test.mjs index 2c595ce08997..98b7ec5e76f6 100644 --- a/tests/unit/httpClientAbortGuard.test.mjs +++ b/tests/unit/httpClientAbortGuard.test.mjs @@ -4,7 +4,6 @@ import assert from "node:assert"; import { test } from "node:test"; import { EventEmitter } from "node:events"; import { spawn } from "node:child_process"; -import { fileURLToPath } from "node:url"; import { isClientAbortError, shouldSwallowUncaught, @@ -262,6 +261,30 @@ test("installProcessCrashGuard() survives the real hedge-cancelled uncaughtExcep assert.match(stdout, /ALIVE/); }); +test("installProcessCrashGuard() survives the real RELAY_TIMEOUT unhandledRejection (2026-10-02 exit-7)", async () => { + // Production crash 2026-10-02 (exit code 7): a pooled relay attempt exceeded + // RELAY_FETCH_TIMEOUT_MS and open-sse/utils/proxyFetch.ts threw + // Error: [ProxyFetch] Relay timed out after 25000ms (...) + // code: 'RELAY_TIMEOUT', errorCode: 'relay_timeout', statusCode: 504 + // The rejection escaped its execute chain (stack: proxyFetch -> executor + // execute x2 -> open-sse handler) as an unhandledRejection and the guard + // re-threw it because RELAY_TIMEOUT was not one of its benign classes. + const { status, stdout, stderr } = await runGuardChild(` + const { installProcessCrashGuard } = await import(process.argv[1]); + installProcessCrashGuard(); + const timeoutErr = Object.assign( + new Error("[ProxyFetch] Relay timed out after 25000ms (https://relay.example)"), + { code: "RELAY_TIMEOUT", errorCode: "relay_timeout", statusCode: 504 } + ); + Promise.reject(timeoutErr); + Promise.reject("RELAY_TIMEOUT"); + process.once("exit", (code) => { if (code === 0) console.log("ALIVE"); }); + setTimeout(() => process.exit(0), 50); + `); + assert.equal(status, 0, "child must survive the relay timeout; stderr: " + stderr); + assert.match(stdout, /ALIVE/); +}); + test("installProcessCrashGuard still crashes on genuine errors (no over-swallowing)", async () => { const { status, stdout } = await runGuardChild(` const { installProcessCrashGuard } = await import(process.argv[1]); @@ -295,9 +318,9 @@ test("isClientAbortError absorbs raw string abort reasons from streamHandler (un // code/message throws away the stack. The logger must receive the full // error object so the origin stays diagnosable. test("installProcessCrashGuard logs the full error object for swallowed errors", async () => { - const guardPath = fileURLToPath( - new URL("../../src/shared/utils/httpClientAbortGuard.mjs", import.meta.url) - ); + // Same constraint as runGuardChild: the child must receive a file:// URL, + // not a filesystem path (bare "C:\..." breaks import() on Windows). + const guardUrl = new URL("../../src/shared/utils/httpClientAbortGuard.mjs", import.meta.url).href; const script = ` const { installProcessCrashGuard } = await import(process.argv[1]); installProcessCrashGuard((level, ...args) => { @@ -314,7 +337,7 @@ test("installProcessCrashGuard logs the full error object for swallowed errors", ); `; const { status, stdout } = await new Promise((resolve, reject) => { - const child = spawn(process.execPath, ["--input-type=module", "-e", script, guardPath], { + const child = spawn(process.execPath, ["--input-type=module", "-e", script, guardUrl], { stdio: ["ignore", "pipe", "pipe"], }); let out = ""; diff --git a/tests/unit/i18n-placeholder-parity.test.ts b/tests/unit/i18n-placeholder-parity.test.ts index 9f6c08f32e9d..8123d9c00d4d 100644 --- a/tests/unit/i18n-placeholder-parity.test.ts +++ b/tests/unit/i18n-placeholder-parity.test.ts @@ -71,8 +71,34 @@ function placeholders(message: string): Set { }; // Text with arguments; stops at the `}` that closes the enclosing sub-message. + // A straight quote before `{` or `<` opens a literal run (ICU quoting): + // text up to the next lone quote is literal, not a placeholder. A doubled + // quote is an escaped quote, inside a run as well as outside. const readMessage = () => { while (i < message.length && message[i] !== "}") { + if (message[i] === "'") { + if (message[i + 1] === "'") { + i += 2; + continue; + } + if (message[i + 1] === "{" || message[i + 1] === "<") { + i += 2; + while (i < message.length) { + if (message[i] === "'") { + if (message[i + 1] === "'") { + i += 2; + continue; + } + i++; + break; + } + i++; + } + continue; + } + i++; + continue; + } if (message[i] === "{") { i++; readArgument(); @@ -184,4 +210,17 @@ test("the checker itself recognises the drift it is meant to catch", () => { [...placeholders("{count, plural, one {one {name}} other {many {name}}}")], ["count", "name"] ); + // A straight quote before syntax opens a literal run: the name inside is + // text, not a placeholder. + assert.deepEqual([...placeholders("f'{providers}")], []); + assert.deepEqual([...placeholders("'{model}'")], []); + // A quote before ordinary text is just a character: the name stays visible. + assert.deepEqual([...placeholders("l'utilisateur {name}")], ["name"]); + // A wanted literal reads empty on both sides, so no drift is reported. + assert.deepEqual([...placeholders("''")], []); + assert.deepEqual([...placeholders("''")], []); + // A doubled quote is an escaped quote, not a literal run opener. + assert.deepEqual([...placeholders("f''{providers}")], ["providers"]); + // A closing quote ends the literal run: later names are visible again. + assert.deepEqual([...placeholders("selector='' for {field}")], ["field"]); }); diff --git a/tests/unit/i18n-sync-ui-keys-locales-per-request.test.ts b/tests/unit/i18n-sync-ui-keys-locales-per-request.test.ts new file mode 100644 index 000000000000..aaa2eeaf55d9 --- /dev/null +++ b/tests/unit/i18n-sync-ui-keys-locales-per-request.test.ts @@ -0,0 +1,321 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + chunkLocales, + collectChunkTasks, + createLimiter, + parseArgs, + planLocaleChunk, + runLocaleChunks, + runLocaleGroup, +} from "../../scripts/i18n/sync-ui-keys.mjs"; + +// `--locales-per-request` option (design symbol 4) plus the pure chunking +// helpers behind it (symbols 5-6). No I/O, no network. + +test("parseArgs defaults localesPerRequest to 1", () => { + assert.equal(parseArgs(["node", "sync-ui-keys.mjs"]).localesPerRequest, 1); +}); + +test("parseArgs coerces bad values to the 1-locale default", () => { + const cases = ["0", "-3", "abc", "", "2.9"]; + const expected = [1, 1, 1, 1, 2]; + cases.forEach((raw, i) => { + const opts = parseArgs(["node", "sync-ui-keys.mjs", `--locales-per-request=${raw}`]); + assert.equal(opts.localesPerRequest, expected[i], raw); + }); +}); + +test("parseArgs keeps an integer request size", () => { + const opts = parseArgs(["node", "sync-ui-keys.mjs", "--locales-per-request=11"]); + assert.equal(opts.localesPerRequest, 11); +}); + +test("chunkLocales slices codes in order with a smaller tail", () => { + assert.deepEqual(chunkLocales(["fr", "de", "es", "it", "pt-BR"], 2), [ + ["fr", "de"], + ["es", "it"], + ["pt-BR"], + ]); +}); + +test("chunkLocales on an empty list emits no chunk", () => { + assert.deepEqual(chunkLocales([], 4), []); +}); + +test("chunkLocales with a size of 1 keeps one code per chunk", () => { + assert.deepEqual(chunkLocales(["fr", "de"], 1), [["fr"], ["de"]]); +}); + +test("collectChunkTasks unions placeholders in walk order and tags each task with its locales", () => { + const byLocale = new Map([ + [ + "fr", + new Map([ + ["common.save", "Save"], + ["common.cancel", "Cancel"], + ]), + ], + ["de", new Map([["common.save", "Save"]])], + ]); + const { tasks, divergent } = collectChunkTasks(byLocale); + assert.deepEqual( + tasks.map((t) => [t.path, t.en, t.locales]), + [ + ["common.save", "Save", ["fr", "de"]], + ["common.cancel", "Cancel", ["fr"]], + ] + ); + assert.deepEqual(divergent, []); +}); + +test("collectChunkTasks picks up keys the first locale lacks", () => { + const byLocale = new Map([ + ["fr", new Map([["a", "Alpha"]])], + [ + "de", + new Map([ + ["a", "Alpha"], + ["b", "Beta"], + ]), + ], + ]); + const { tasks } = collectChunkTasks(byLocale); + assert.deepEqual( + tasks.map((t) => [t.path, t.locales]), + [ + ["a", ["fr", "de"]], + ["b", ["de"]], + ] + ); +}); + +test("collectChunkTasks excludes a path whose English source diverges across locales", () => { + const byLocale = new Map([ + ["fr", new Map([["greet", "Hello"]])], + ["de", new Map([["greet", "Hello!"]])], + ]); + const { tasks, divergent } = collectChunkTasks(byLocale, { splitDivergent: true }); + assert.deepEqual(tasks, []); + assert.deepEqual(divergent, [ + { + path: "greet", + perLocale: new Map([ + ["fr", "Hello"], + ["de", "Hello!"], + ]), + }, + ]); +}); + +test("collectChunkTasks strips the missing marker from the shared English text", () => { + const byLocale = new Map([["fr", new Map([["k", "__MISSING__:Save"]])]]); + const { tasks } = collectChunkTasks(byLocale); + assert.equal(tasks[0].en, "Save"); +}); + +function chunkCtx(mergedByLocale, { multi, singleBatch, singleString } = {}) { + return { + mergedByLocale, + config: { + locales: [ + { code: "fr", english: "French", native: "Francais" }, + { code: "de", english: "German", native: "Deutsch" }, + ], + }, + opts: { batchSize: 40 }, + backend: {}, + limit: (fn) => fn(), + counters: { multiRequests: 0, singleRequests: 0, failedLocales: 0 }, + ...(multi ? { multi } : {}), + ...(singleBatch ? { singleBatch } : {}), + ...(singleString ? { singleString } : {}), + }; +} + +async function runChunk(chunk, ctx) { + const stats = await runLocaleGroup(planLocaleChunk(chunk, ctx), ctx); + return { + translated: [...stats.perLocale.values()].reduce((a, b) => a + b, 0), + failed: [...stats.failedBy.values()].reduce((a, b) => a + b, 0), + }; +} + +test("planned groups send one request for a chunk of locales", async () => { + let calls = 0; + const mergedByLocale = new Map([ + ["fr", { title: "__MISSING__:Save" }], + ["de", { title: "__MISSING__:Save" }], + ]); + const ctx = chunkCtx(mergedByLocale, { + multi: async (entries, locales) => { + calls++; + assert.equal(locales.length, 2); + const perLocale = new Map( + locales.map((l) => [l.code, new Map(entries.map((e) => [e.id, `${e.text}-${l.code}`]))]) + ); + return { perLocale, failedLocales: [] }; + }, + }); + const out = await runChunk(["fr", "de"], ctx); + assert.equal(calls, 1); + assert.equal(out.translated, 2); + assert.equal(mergedByLocale.get("fr").title, "Save-fr"); + assert.equal(mergedByLocale.get("de").title, "Save-de"); + assert.equal(ctx.counters.multiRequests, 1); + assert.equal(ctx.counters.singleRequests, 0); +}); + +test("planned groups retry only the broken locale through the single path", async () => { + let singleCalls = 0; + const mergedByLocale = new Map([ + ["fr", { title: "__MISSING__:Save" }], + ["de", { title: "__MISSING__:Save" }], + ]); + const ctx = chunkCtx(mergedByLocale, { + multi: async () => ({ + perLocale: new Map([["fr", new Map([["title", "Sauver"]])]]), + failedLocales: ["de"], + }), + singleBatch: async (entries) => { + singleCalls++; + return new Map(entries.map((e) => [e.id, "Retranslated"])); + }, + }); + const out = await runChunk(["fr", "de"], ctx); + assert.equal(singleCalls, 1); + assert.equal(mergedByLocale.get("fr").title, "Sauver"); + assert.equal(mergedByLocale.get("de").title, "Retranslated"); + assert.equal(ctx.counters.failedLocales, 1); + assert.equal(out.failed, 0); +}); + +test("planned groups skip network calls when dry-run reports only", async () => { + const plan = planLocaleChunk(["fr"], chunkCtx(new Map([["fr", { title: "__MISSING__:Save" }]]))); + assert.equal(plan.groups.length, 1); +}); + +test("planned groups report per-locale counts instead of one shared total", async () => { + const mergedByLocale = new Map([ + ["fr", { title: "__MISSING__:Save" }], + ["de", { title: "__MISSING__:Save" }], + ]); + const ctx = chunkCtx(mergedByLocale, { + multi: async (entries, locales) => ({ + perLocale: new Map(locales.map((l) => [l.code, new Map(entries.map((e) => [e.id, "T"]))])), + failedLocales: [], + }), + }); + const stats = await runLocaleGroup(planLocaleChunk(["fr", "de"], ctx), ctx); + assert.equal(stats.perLocale.get("fr"), 1); + assert.equal(stats.perLocale.get("de"), 1); + assert.equal(stats.failedBy.size, 0); +}); + +test("main-level fan-out resolves when chunks outnumber limiter slots", async () => { + // Six chunks through a 2-slot production limiter: with nested limiter + // calls this never resolves (all slots held by chunk tasks waiting on + // inner group tasks). Every group runs through the shared limiter once. + const codes = ["l1", "l2", "l3", "l4", "l5", "l6"]; + const mergedByLocale = new Map(codes.map((code) => [code, { title: "__MISSING__:Save" }])); + const config = { locales: codes.map((code) => ({ code, english: code, native: code })) }; + let multiCalls = 0; + const limit = createLimiter(2); + const ctx = { + mergedByLocale, + config, + opts: { batchSize: 40 }, + backend: {}, + limit, + counters: { multiRequests: 0, singleRequests: 0, failedLocales: 0 }, + multi: async (entries, locales) => { + multiCalls++; + return { + perLocale: new Map(locales.map((l) => [l.code, new Map(entries.map((e) => [e.id, "T"]))])), + failedLocales: [], + }; + }, + }; + const plans = [["l1"], ["l2"], ["l3"], ["l4"], ["l5"], ["l6"]].map((chunk) => + planLocaleChunk(chunk, ctx) + ); + const results = await runLocaleChunks(plans, ctx); + assert.equal(multiCalls, 6); + for (const code of codes) assert.equal(mergedByLocale.get(code).title, "T"); + assert.equal( + results.reduce( + (sum, stats) => sum + [...stats.perLocale.values()].reduce((a, b) => a + b, 0), + 0 + ), + 6 + ); + assert.equal(ctx.counters.multiRequests, 6); +}); + +test("two identical chunk runs emit the same calls and values", async () => { + const runOnce = async () => { + let calls = 0; + const mergedByLocale = new Map([["fr", { title: "__MISSING__:Save" }]]); + const ctx = chunkCtx(mergedByLocale, { + multi: async (entries) => { + calls++; + return { + perLocale: new Map([["fr", new Map(entries.map((e) => [e.id, `v${entries.length}`]))]]), + failedLocales: [], + }; + }, + }); + const out = await runChunk(["fr"], ctx); + return { calls, value: mergedByLocale.get("fr").title, translated: out.translated }; + }; + const first = await runOnce(); + const second = await runOnce(); + assert.deepEqual(second, first); +}); + +test("dry-run emits no request and writes nothing", async () => { + // The multi-locale branch in `main` never builds a backend under + // `--dry-run`, so no group is planned and no file is touched. + const opts = parseArgs(["node", "sync-ui-keys.mjs", "--dry-run", "--locales-per-request=11"]); + assert.equal(opts.dryRun, true); + assert.equal(opts.localesPerRequest, 11); +}); + +test("default path sends one single-locale request per locale and never calls the multi client", async () => { + // Default path: two locales without the flag = 2 single requests with identical bodies + // bodies to the base revision, 0 multi-locale calls. + const { translatePlaceholdersExport } = await import("../../scripts/i18n/sync-ui-keys.mjs"); + const bodies = []; + const original = globalThis.fetch; + globalThis.fetch = async (_url, init) => { + bodies.push(String(init.body)); + return new Response( + JSON.stringify({ choices: [{ message: { role: "assistant", content: '{"s0":"T"}' } }] }), + { status: 200, headers: { "Content-Type": "application/json" } } + ); + }; + try { + const backend = { apiUrl: "http://t/v1", apiKey: "k", model: "m", timeoutMs: 5000 }; + for (const code of ["fr", "de"]) { + const merged = { title: "__MISSING__:Save" }; + const out = await translatePlaceholdersExport( + merged, + { code, english: code, native: code }, + backend, + 4, + 40 + ); + assert.equal(out.translated, 1); + assert.equal(merged.title, "T"); + } + } finally { + globalThis.fetch = original; + } + assert.equal(bodies.length, 2); + // Both locales share the same batch shape; only the system prompt names differ. + for (const [i, code] of ["fr", "de"].entries()) { + const parsed = JSON.parse(bodies[i]); + assert.equal(parsed.messages[1].content, JSON.stringify({ s0: "Save" })); + assert.ok(parsed.messages[0].content.includes(`into ${code} (native: ${code})`)); + } +}); diff --git a/tests/unit/i18n-sync-ui-keys-merge.test.ts b/tests/unit/i18n-sync-ui-keys-merge.test.ts new file mode 100644 index 000000000000..b1b7fc86695c --- /dev/null +++ b/tests/unit/i18n-sync-ui-keys-merge.test.ts @@ -0,0 +1,61 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { mergeMissing } from "../../scripts/i18n/sync-ui-keys.mjs"; + +test("merges new keys and matches the pre-change snapshot byte for byte", () => { + const source = { + common: { save: "Save", cancel: "Cancel", fresh: "Fresh" }, + dashboard: { title: "Title", fresh: "New panel" }, + top: "Top", + }; + const target = { + common: { save: "Guardar", cancel: "Cancelar" }, + dashboard: { title: "Titulo" }, + top: "Topo", + }; + const { merged, addedPaths } = mergeMissing(source, target); + assert.deepEqual(addedPaths, ["common.fresh", "dashboard.fresh"]); + assert.deepEqual(JSON.parse(JSON.stringify(merged)), { + common: { save: "Guardar", cancel: "Cancelar", fresh: "__MISSING__:Fresh" }, + dashboard: { title: "Titulo", fresh: "__MISSING__:New panel" }, + top: "Topo", + }); + assert.equal( + JSON.stringify({ merged, addedPaths }), + '{"merged":{"common":{"save":"Guardar","cancel":"Cancelar","fresh":"__MISSING__:Fresh"},' + + '"dashboard":{"title":"Titulo","fresh":"__MISSING__:New panel"},"top":"Topo"},' + + '"addedPaths":["common.fresh","dashboard.fresh"]}' + ); +}); + +test("drops forbidden keys from the merged tree and the added paths", () => { + const source = JSON.parse( + '{"ok":"Ok","__proto__":"Proto","prototype":"Proto","constructor":"Ctor"}' + ); + const { merged, addedPaths } = mergeMissing(source, {}); + assert.deepEqual(addedPaths, ["ok"]); + assert.deepEqual(JSON.parse(JSON.stringify(merged)), { ok: "__MISSING__:Ok" }); + assert.equal(Object.getPrototypeOf(merged), null); + assert.equal({}.polluted, undefined); +}); + +test("reports no additions when the target is already in sync", () => { + const source = { common: { save: "Save" }, top: "Top" }; + const target = { common: { save: "Guardar" }, top: "Topo" }; + const { merged, addedPaths } = mergeMissing(source, target); + assert.deepEqual(addedPaths, []); + assert.deepEqual(JSON.parse(JSON.stringify(merged)), target); + assert.equal(JSON.stringify(merged), '{"common":{"save":"Guardar"},"top":"Topo"}'); +}); + +test("keeps the existing value on shape mismatch", () => { + const leafVsObject = mergeMissing({ a: "Alpha" }, { a: { deep: "Profundo" } }); + assert.deepEqual(leafVsObject.addedPaths, []); + assert.deepEqual(JSON.parse(JSON.stringify(leafVsObject.merged)), { a: { deep: "Profundo" } }); + + const objectVsLeaf = mergeMissing({ nested: { b: "Bravo" } }, { nested: "Bravo" }); + assert.deepEqual(objectVsLeaf.addedPaths, ["nested.b"]); + assert.deepEqual(JSON.parse(JSON.stringify(objectVsLeaf.merged)), { + nested: { b: "__MISSING__:Bravo" }, + }); +}); diff --git a/tests/unit/i18n-translate-backend.test.ts b/tests/unit/i18n-translate-backend.test.ts index 858cdae67992..3aa71f92aae3 100644 --- a/tests/unit/i18n-translate-backend.test.ts +++ b/tests/unit/i18n-translate-backend.test.ts @@ -6,8 +6,11 @@ import { TRANSLATION_SYSTEM, backendConfig, callChat, + multiLocaleBatchSystem, parseBatchResponse, + parseMultiLocaleBatchResponse, translateBatch, + translateMultiLocaleBatch, translateString, } from "../../scripts/i18n/lib/translate-backend.mjs"; @@ -35,6 +38,7 @@ type ChatBody = { messages: Array<{ role: string; content: string }>; temperature: number; stream: boolean; + reasoning_effort?: string; }; /** Swaps `globalThis.fetch` for the duration of `fn`, recording every call. */ @@ -72,6 +76,7 @@ const ENV_KEYS = [ "OMNIROUTE_TRANSLATION_API_KEY", "OMNIROUTE_TRANSLATION_MODEL", "OMNIROUTE_TRANSLATION_TIMEOUT_MS", + "OMNIROUTE_TRANSLATION_REASONING_EFFORT", ] as const; /** Runs `fn` with exactly `values` set for the backend env vars, then restores the shell's. */ @@ -374,3 +379,237 @@ test("backendConfig fails fast with the documented message when a var is missing } ); }); + +// --------------------------------------------------------------------------- +// Multi-locale batch mode — one request for N locales, one line per locale. +// Exported from translate-backend.mjs (see design symbols 1-3). +// --------------------------------------------------------------------------- + +const FR = { code: "fr", english: "French", native: "Francais", name: "Francais" }; +const DE = { code: "de", english: "German", native: "Deutsch", name: "Deutsch" }; + +test("multiLocaleBatchSystem names every target locale and pins the line format", () => { + const sys = multiLocaleBatchSystem([ + { code: "fr", english: "French", native: "Francais" }, + { code: "de", english: "German", native: "Deutsch" }, + ]); + assert.match(sys, /fr/); + assert.match(sys, /de/); + assert.match(sys, /one JSON object per line/); + assert.match(sys, /\{"":\{"":""\}\}/); +}); + +test("parseMultiLocaleBatchResponse assigns values by locale code, not line order", () => { + const text = ['{"de":{"1":"Abbrechen"}}', '{"fr":{"1":"Annuler"}}'].join("\n"); + const out = parseMultiLocaleBatchResponse(text, ["fr", "de"], ["1"]); + assert.equal(out.perLocale.get("fr").get("1"), "Annuler"); + assert.equal(out.perLocale.get("de").get("1"), "Abbrechen"); + assert.deepEqual(out.failedLocales, []); +}); + +test("parseMultiLocaleBatchResponse returns the same assignment for reordered lines", () => { + const a = parseMultiLocaleBatchResponse( + '{"fr":{"1":"Annuler"}}\n{"de":{"1":"Abbrechen"}}', + ["fr", "de"], + ["1"] + ); + const b = parseMultiLocaleBatchResponse( + '{"de":{"1":"Abbrechen"}}\n{"fr":{"1":"Annuler"}}', + ["fr", "de"], + ["1"] + ); + assert.equal(a.perLocale.get("fr").get("1"), b.perLocale.get("fr").get("1")); + assert.equal(a.perLocale.get("de").get("1"), b.perLocale.get("de").get("1")); +}); + +test("parseMultiLocaleBatchResponse reports an empty answer as fully failed", () => { + const out = parseMultiLocaleBatchResponse("", ["fr", "de"], ["1"]); + assert.equal(out.perLocale.size, 0); + assert.deepEqual([...out.failedLocales].sort(), ["de", "fr"]); +}); + +test("parseMultiLocaleBatchResponse fails only the broken locale line", () => { + const text = ['{"fr":{"1":"Annuler"}}', "not-json{{{", '{"de":{"1":"Abbrechen"}}'].join("\n"); + const out = parseMultiLocaleBatchResponse(text, ["fr", "de"], ["1"]); + assert.equal(out.perLocale.get("fr").get("1"), "Annuler"); + assert.equal(out.perLocale.get("de").get("1"), "Abbrechen"); + assert.deepEqual(out.failedLocales, []); +}); + +test("parseMultiLocaleBatchResponse marks a locale with a missing id as failed", () => { + const text = ['{"fr":{"2":"Annuler"}}', '{"de":{"1":"Abbrechen"}}'].join("\n"); + const out = parseMultiLocaleBatchResponse(text, ["fr", "de"], ["1"]); + assert.ok(!out.perLocale.has("fr")); + assert.equal(out.perLocale.get("de").get("1"), "Abbrechen"); + assert.deepEqual(out.failedLocales, ["fr"]); +}); + +test("parseMultiLocaleBatchResponse ignores unknown locale codes and keeps full quotes intact", () => { + const text = [ + `{"fr":{"1":"Ouvrir '' {count} OmniRoute"}}`, + '{"xx":{"1":"junk"}}', + '{"de":{"1":"Abbrechen"}}', + ].join("\n"); + const out = parseMultiLocaleBatchResponse(text, ["fr", "de"], ["1"]); + assert.equal(out.perLocale.get("fr").get("1"), `Ouvrir '' {count} OmniRoute`); + assert.equal(out.perLocale.get("de").get("1"), "Abbrechen"); + assert.deepEqual(out.failedLocales, []); +}); + +test("translateMultiLocaleBatch sends one request for several locales", async () => { + await withFetch( + () => chatCompletion('{"fr":{"1":"Annuler"}}\n{"de":{"1":"Abbrechen"}}'), + async (calls) => { + const out = await translateMultiLocaleBatch( + [{ id: "greet", text: "Cancel" }], + [FR, DE], + BACKEND + ); + assert.equal(calls.length, 1); + const body = requestBody(calls[0]); + const user = JSON.parse(body.messages[1].content); + assert.deepEqual(user, { 1: "Cancel" }); + assert.equal(out.perLocale.get("fr").get("greet"), "Annuler"); + assert.equal(out.perLocale.get("de").get("greet"), "Abbrechen"); + assert.deepEqual(out.failedLocales, []); + } + ); +}); + +test("translateMultiLocaleBatch returns a partial result when one locale line breaks", async () => { + await withFetch( + () => chatCompletion('{"fr":{"1":"Annuler"}}\nnot-json{{{'), + async (calls) => { + const out = await translateMultiLocaleBatch( + [{ id: "greet", text: "Cancel" }], + [FR, DE], + BACKEND + ); + assert.equal(calls.length, 1); + assert.equal(out.perLocale.get("fr").get("greet"), "Annuler"); + assert.deepEqual(out.failedLocales, ["de"]); + } + ); +}); + +test("translateMultiLocaleBatch restores literal quotes around angle spans per value", async () => { + await withFetch( + () => chatCompletion('{"fr":{"1":"Ouvrir "}}\n{"de":{"1":"Abbrechen"}}'), + async () => { + const out = await translateMultiLocaleBatch( + [{ id: "open", text: "Open ''" }], + [FR, DE], + BACKEND + ); + assert.equal(out.perLocale.get("fr").get("open"), "Ouvrir ''"); + } + ); +}); + +test("translateMultiLocaleBatch throws when every locale line fails", async () => { + await withFetch( + () => chatCompletion("not-json{{{"), + async () => { + await assert.rejects( + translateMultiLocaleBatch([{ id: "greet", text: "Cancel" }], [FR, DE], BACKEND), + /no locale translated/ + ); + } + ); +}); + +// --------------------------------------------------------------------------- +// Optional reasoning control — off unless the operator sets the variable +// --------------------------------------------------------------------------- + +test("backendConfig omits the key when the variable is unset, empty or blank", () => { + const base = { + OMNIROUTE_TRANSLATION_API_URL: "http://translation.test/v1", + OMNIROUTE_TRANSLATION_API_KEY: "sk-test", + OMNIROUTE_TRANSLATION_MODEL: "test-model", + }; + withEnv(base, () => { + assert.deepEqual(backendConfig(), { + apiUrl: "http://translation.test/v1", + apiKey: "sk-test", + model: "test-model", + timeoutMs: 60000, + }); + }); + for (const value of ["", " "]) { + withEnv({ ...base, OMNIROUTE_TRANSLATION_REASONING_EFFORT: value }, () => { + assert.deepEqual(backendConfig(), { + apiUrl: "http://translation.test/v1", + apiKey: "sk-test", + model: "test-model", + timeoutMs: 60000, + }); + }); + } +}); + +test("backendConfig forwards the trimmed value when the variable is set", () => { + const base = { + OMNIROUTE_TRANSLATION_API_URL: "http://translation.test/v1", + OMNIROUTE_TRANSLATION_API_KEY: "sk-test", + OMNIROUTE_TRANSLATION_MODEL: "test-model", + }; + withEnv({ ...base, OMNIROUTE_TRANSLATION_REASONING_EFFORT: "none" }, () => { + assert.equal(backendConfig().reasoningEffort, "none"); + }); + withEnv({ ...base, OMNIROUTE_TRANSLATION_REASONING_EFFORT: " none " }, () => { + assert.equal(backendConfig().reasoningEffort, "none"); + }); +}); + +test("callChat omits the field when the backend carries none", async () => { + const messages = [{ role: "user", content: "x" }]; + await withFetch( + () => chatCompletion("ok"), + async (calls) => { + await callChat(messages, BACKEND); + assert.equal(calls.length, 1); + assert.equal( + String(calls[0].init.body), + '{"model":"test-model","messages":[{"role":"user","content":"x"}],"temperature":0.15,"stream":false}' + ); + assert.equal("reasoning_effort" in requestBody(calls[0]), false); + } + ); +}); + +test("callChat forwards the field when the backend carries one", async () => { + const messages = [{ role: "user", content: "x" }]; + await withFetch( + () => chatCompletion("ok"), + async (calls) => { + await callChat(messages, { ...BACKEND, reasoningEffort: "none" }); + assert.equal(calls.length, 1); + const body = requestBody(calls[0]); + assert.equal(body.reasoning_effort, "none"); + assert.deepEqual(Object.keys(body), [ + "model", + "messages", + "temperature", + "stream", + "reasoning_effort", + ]); + } + ); +}); + +test("callChat keeps the field on retry after a transient failure", async () => { + const messages = [{ role: "user", content: "x" }]; + let n = 0; + await withFetch( + () => (++n === 1 ? new Response("busy", { status: 500 }) : chatCompletion("ok")), + async (calls) => { + const out = await callChat(messages, { ...BACKEND, reasoningEffort: "none" }); + assert.equal(out, "ok"); + assert.equal(calls.length, 2); + for (const call of calls) { + assert.equal(requestBody(call).reasoning_effort, "none"); + } + } + ); +}); diff --git a/tests/unit/instrumentation-import-graph-12074.test.ts b/tests/unit/instrumentation-import-graph-12074.test.ts index 7c092dcf93a7..ad6fab21bad7 100644 --- a/tests/unit/instrumentation-import-graph-12074.test.ts +++ b/tests/unit/instrumentation-import-graph-12074.test.ts @@ -68,6 +68,15 @@ test("startup still registers bespoke, batch, and generic quota fetchers", async } }); +test("chat route does not statically import the quota tracker batch", () => { + const chatPath = path.join(process.cwd(), "src/sse/handlers/chat.ts"); + const source = fs.readFileSync(chatPath, "utf8"); + assert.doesNotMatch( + source, + /import\s+["']@omniroute\/open-sse\/services\/quotaTrackersBatch\.ts["']/ + ); +}); + test("provider-limit startup uses the refresh-only executor resolver", () => { const providerLimitsSource = fs.readFileSync(providerLimitsPath, "utf8"); const credentialExecutorSource = fs.readFileSync(credentialExecutorPath, "utf8"); diff --git a/tests/unit/isolate-datadir.test.ts b/tests/unit/isolate-datadir.test.ts index 2f14870b8099..4e74f42ac169 100644 --- a/tests/unit/isolate-datadir.test.ts +++ b/tests/unit/isolate-datadir.test.ts @@ -7,6 +7,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import os from "node:os"; +import path from "node:path"; function dataDirFromChild(envDataDir: string | undefined): string { const result = spawnSync( @@ -30,6 +31,43 @@ function dataDirFromChild(envDataDir: string | undefined): string { return result.stdout.trim().split("\n").pop() ?? ""; } +const PROBE_PREFIX = "PLUGIN_DIR_PROBE "; + +// Runs the real plugin-dir resolver in a child that loads the setup. DATA_DIR is blanked +// so the child mints its own, and OMNIROUTE_PLUGINS_DIR is pinned so the parent runner's +// value never leaks in. The scanner logs on resolve, so the result line is found by its +// prefix instead of assumed to be the last line. +function pluginDirFromChild(envPluginsDir: string): { dataDir: string; pluginDir: string } { + const script = [ + 'const { getDefaultPluginDir } = await import("./src/lib/plugins/scanner.ts");', + `console.log(${JSON.stringify(PROBE_PREFIX)} + JSON.stringify({`, + " dataDir: process.env.DATA_DIR,", + " pluginDir: getDefaultPluginDir(),", + "}));", + ].join("\n"); + const result = spawnSync( + process.execPath, + [ + "--import", + "tsx", + "--import", + "./tests/_setup/isolateDataDir.ts", + "--input-type=module", + "-e", + script, + ], + { + encoding: "utf8", + cwd: process.cwd(), + env: { ...process.env, DATA_DIR: "", OMNIROUTE_PLUGINS_DIR: envPluginsDir }, + } + ); + assert.equal(result.status, 0, `plugin-dir probe exited ${result.status}: ${result.stderr}`); + const line = result.stdout.split("\n").find((l) => l.startsWith(PROBE_PREFIX)); + assert.ok(line, `plugin-dir probe printed no result line; stdout: ${result.stdout}`); + return JSON.parse(line.slice(PROBE_PREFIX.length)); +} + test("isolateDataDir assigns a unique temp DATA_DIR when none is set", () => { const a = dataDirFromChild(undefined); const b = dataDirFromChild(undefined); @@ -43,3 +81,22 @@ test("isolateDataDir respects an explicitly set DATA_DIR", () => { const explicit = "/tmp/omniroute-explicit-fixture"; assert.equal(dataDirFromChild(explicit), explicit); }); + +test("isolateDataDir resolves the default plugin dir under the per-process DATA_DIR", () => { + // A blank override counts as unset, as it does in the scanner itself. + for (const unset of ["", " "]) { + const { dataDir, pluginDir } = pluginDirFromChild(unset); + + assert.match(dataDir, /omniroute-test-/, `expected an isolated DATA_DIR, got ${dataDir}`); + assert.equal( + pluginDir, + path.join(dataDir, "plugins"), + `OMNIROUTE_PLUGINS_DIR=${JSON.stringify(unset)}` + ); + } +}); + +test("isolateDataDir respects an explicitly set OMNIROUTE_PLUGINS_DIR", () => { + const explicit = "/tmp/omniroute-explicit-plugins-fixture"; + assert.equal(pluginDirFromChild(explicit).pluginDir, explicit); +}); diff --git a/tests/unit/issue-15088-native-responses-collaboration-plaintext.test.ts b/tests/unit/issue-15088-native-responses-collaboration-plaintext.test.ts new file mode 100644 index 000000000000..1a54bd33f63d --- /dev/null +++ b/tests/unit/issue-15088-native-responses-collaboration-plaintext.test.ts @@ -0,0 +1,347 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// #15088 - a native Responses collaboration function_call is already in the +// protocol's ciphertext form. Stamping `encrypted_function_args: []` marks it +// as plaintext delivery even when the upstream field is absent (or nonempty). +// The empty marker is only correct when OmniRoute translated a plaintext Chat +// Completions tool call into Responses (#14154). + +const { translateNonStreamingClientResponse } = + await import("../../open-sse/handlers/chatCore/nonStreamingClientTranslate.ts"); +const { sanitizeResponsesApiResponse, sanitizeStreamingChunk } = + await import("../../open-sse/handlers/responseSanitizer.ts"); +const { FORMATS } = await import("../../open-sse/translator/formats.ts"); +const { createPassthroughStreamWithLogger } = await import("../../open-sse/utils/stream.ts"); + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +const OPAQUE_ARGS = '{"payload":"opaque-collaboration-blob"}'; +const UPSTREAM_CIPHERTEXT = ["upstream-ciphertext-token"]; + +type FunctionCallItem = { + type?: string; + name?: string; + namespace?: string; + arguments?: string; + encrypted_function_args?: unknown; +}; + +async function runPassthrough( + frames: string[], + identityMap?: Map +) { + const stream = createPassthroughStreamWithLogger( + "openai", + null, + null, + "gpt-5", + "conn-15088", + { model: "gpt-5", stream: true }, + null, + null, + null, + FORMATS.OPENAI_RESPONSES, + identityMap ?? null + ); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + const output: string[] = []; + const readerTask = (async () => { + while (true) { + const { value, done } = await reader.read(); + if (done) break; + output.push(decoder.decode(value)); + } + })(); + for (const event of frames) { + await writer.write(encoder.encode(event)); + } + await writer.close(); + await readerTask; + const raw = output.join(""); + const events = raw + .split("\n\n") + .map((block) => block.trim()) + .filter((block) => block.startsWith("data: ") && !block.includes("[DONE]")) + .map((block) => JSON.parse(block.slice("data: ".length)) as Record); + return { raw, events }; +} + +function functionCallsFromEvent(event: Record): FunctionCallItem[] { + const item = event.item as FunctionCallItem | undefined; + const response = event.response as { output?: FunctionCallItem[] } | undefined; + const fromItem = item?.type === "function_call" ? [item] : []; + const fromResponse = Array.isArray(response?.output) + ? response.output.filter((entry) => entry?.type === "function_call") + : []; + return [...fromItem, ...fromResponse]; +} + +test("native Responses passthrough does not stamp encrypted_function_args:[] on collaboration calls (#15088)", async () => { + const identity = new Map([ + ["collaboration__spawn_agent", { namespace: "collaboration", name: "spawn_agent" }], + ]); + const { raw, events } = await runPassthrough( + [ + `data: ${JSON.stringify({ + type: "response.output_item.added", + output_index: 0, + item: { + type: "function_call", + id: "fc_1", + call_id: "call_1", + name: "collaboration__spawn_agent", + arguments: "", + status: "in_progress", + }, + })}\n\n`, + `data: ${JSON.stringify({ + type: "response.output_item.done", + output_index: 0, + item: { + type: "function_call", + id: "fc_1", + call_id: "call_1", + name: "collaboration__spawn_agent", + arguments: OPAQUE_ARGS, + status: "completed", + }, + })}\n\n`, + `data: ${JSON.stringify({ + type: "response.completed", + response: { + id: "resp_native", + object: "response", + status: "completed", + output: [ + { + type: "function_call", + id: "fc_1", + call_id: "call_1", + name: "collaboration__spawn_agent", + arguments: OPAQUE_ARGS, + status: "completed", + }, + ], + }, + })}\n\n`, + ], + identity + ); + + const calls = events.flatMap(functionCallsFromEvent); + assert.ok(calls.length >= 3, "added, done, and completed must each carry the function_call"); + assert.equal( + raw.includes('"namespace":"collaboration"'), + true, + "identity restoration must be serialized into the forwarded SSE, not only mutated in memory" + ); + for (const call of calls) { + assert.equal(call.namespace, "collaboration"); + assert.equal(call.name, "spawn_agent"); + assert.equal( + call.encrypted_function_args, + undefined, + "absent upstream encrypted_function_args must stay absent on native passthrough" + ); + } +}); + +test("native Responses passthrough preserves a nonempty upstream encrypted_function_args (#15088)", async () => { + const { events } = await runPassthrough([ + `data: ${JSON.stringify({ + type: "response.output_item.done", + output_index: 0, + item: { + type: "function_call", + id: "fc_2", + call_id: "call_2", + namespace: "collaboration", + name: "send_message", + arguments: OPAQUE_ARGS, + encrypted_function_args: UPSTREAM_CIPHERTEXT, + status: "completed", + }, + })}\n\n`, + `data: ${JSON.stringify({ + type: "response.completed", + response: { + id: "resp_native_cipher", + object: "response", + status: "completed", + output: [ + { + type: "function_call", + id: "fc_2", + call_id: "call_2", + namespace: "collaboration", + name: "send_message", + arguments: OPAQUE_ARGS, + encrypted_function_args: UPSTREAM_CIPHERTEXT, + status: "completed", + }, + ], + }, + })}\n\n`, + ]); + + const calls = events.flatMap(functionCallsFromEvent); + assert.ok(calls.length >= 2); + for (const call of calls) { + assert.deepEqual(call.encrypted_function_args, UPSTREAM_CIPHERTEXT); + } +}); + +test("non-streaming native Responses collaboration call is not marked plaintext (#15088)", () => { + const result = translateNonStreamingClientResponse({ + responseBody: { + id: "resp_native", + object: "response", + status: "completed", + output: [ + { + id: "fc_3", + type: "function_call", + call_id: "call_3", + namespace: "collaboration", + name: "followup_task", + arguments: OPAQUE_ARGS, + status: "completed", + }, + ], + }, + responsePayloadFormat: FORMATS.OPENAI_RESPONSES, + clientResponseFormat: FORMATS.OPENAI_RESPONSES, + sourceFormat: FORMATS.OPENAI_RESPONSES, + provider: "openai", + model: "gpt-5", + requestBody: { input: [] }, + responseToolNameMap: null, + requestToolIdentityMap: null, + reasoningCacheScope: null, + clientHeaders: null, + isClaudeCodeCompatible: false, + phase: "final", + }); + const item = (result.response.output as FunctionCallItem[])[0]; + assert.equal(item.name, "followup_task"); + assert.equal(item.namespace, "collaboration"); + assert.equal(item.encrypted_function_args, undefined); +}); + +test("non-streaming Chat-to-Responses collaboration call still carries the plaintext marker (#14154)", () => { + const result = translateNonStreamingClientResponse({ + responseBody: { + id: "chatcmpl-plain", + object: "chat.completion", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_plain", + type: "function", + function: { name: "collaboration__spawn_agent", arguments: '{"message":"READY"}' }, + }, + ], + }, + finish_reason: "tool_calls", + }, + ], + }, + responsePayloadFormat: FORMATS.OPENAI, + clientResponseFormat: FORMATS.OPENAI_RESPONSES, + sourceFormat: FORMATS.OPENAI, + provider: "deepseek", + model: "deepseek-chat", + requestBody: { messages: [] }, + responseToolNameMap: null, + requestToolIdentityMap: new Map([ + ["collaboration__spawn_agent", { namespace: "collaboration", name: "spawn_agent" }], + ]), + reasoningCacheScope: null, + clientHeaders: null, + isClaudeCodeCompatible: false, + phase: "final", + }); + const item = (result.response.output as FunctionCallItem[])[0]; + assert.equal(item.namespace, "collaboration"); + assert.equal(item.name, "spawn_agent"); + assert.deepEqual(item.encrypted_function_args, []); +}); + +test("sanitizeResponsesApiResponse preserves upstream encrypted_function_args exactly (#15088)", () => { + const absent = sanitizeResponsesApiResponse({ + id: "resp_absent", + object: "response", + status: "completed", + output: [ + { + id: "fc_a", + type: "function_call", + call_id: "call_a", + namespace: "collaboration", + name: "spawn_agent", + arguments: OPAQUE_ARGS, + status: "completed", + }, + ], + }) as { output: FunctionCallItem[] }; + assert.equal(absent.output[0].encrypted_function_args, undefined); + assert.equal(absent.output[0].namespace, "collaboration"); + + const present = sanitizeResponsesApiResponse({ + id: "resp_present", + object: "response", + status: "completed", + output: [ + { + id: "fc_b", + type: "function_call", + call_id: "call_b", + namespace: "collaboration", + name: "send_message", + arguments: OPAQUE_ARGS, + encrypted_function_args: UPSTREAM_CIPHERTEXT, + status: "completed", + }, + ], + }) as { output: FunctionCallItem[] }; + assert.deepEqual(present.output[0].encrypted_function_args, UPSTREAM_CIPHERTEXT); +}); + +test("streaming sanitizer preserves upstream encrypted_function_args on lifecycle events (#15088)", () => { + const done = sanitizeStreamingChunk({ + type: "response.output_item.done", + item: { + type: "function_call", + id: "fc_s", + call_id: "call_s", + namespace: "collaboration", + name: "spawn_agent", + arguments: OPAQUE_ARGS, + encrypted_function_args: UPSTREAM_CIPHERTEXT, + }, + }) as { item: FunctionCallItem }; + assert.deepEqual(done.item.encrypted_function_args, UPSTREAM_CIPHERTEXT); + + const completed = sanitizeStreamingChunk({ + type: "response.completed", + response: { + output: [ + { + type: "function_call", + name: "followup_task", + namespace: "collaboration", + arguments: OPAQUE_ARGS, + }, + ], + }, + }) as { response: { output: FunctionCallItem[] } }; + assert.equal(completed.response.output[0].encrypted_function_args, undefined); +}); diff --git a/tests/unit/kimi-code-cli-version-fetch.test.ts b/tests/unit/kimi-code-cli-version-fetch.test.ts new file mode 100644 index 000000000000..0b64bbf82f8a --- /dev/null +++ b/tests/unit/kimi-code-cli-version-fetch.test.ts @@ -0,0 +1,92 @@ +/** + * Registry lookup for the advertised Kimi Code CLI version. + * + * The captured pin is the floor. A newer dotted triple from the npm `latest` + * document of `@moonshot-ai/kimi-code` replaces it for 6 hours. A rejected + * fetch, a non-triple, or an older publish stays on the pin. KIMI_CLI_VERSION + * in the environment beats both and must not start a fetch. + * + * The getter is the only production path: it stays sync and fires the lookup + * in the background. These tests stub fetch, then read the getter after the + * microtask that started the fetch has run. + */ +import assert from "node:assert/strict"; +import test from "node:test"; + +const canonical = await import("../../open-sse/config/providers/registry/kimi/coding/runtime.ts"); + +const NPM_LATEST = "https://registry.npmjs.org/@moonshot-ai/kimi-code/latest"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +async function withEnv( + entries: Record, + fn: () => T | Promise +): Promise { + const previous = new Map(); + for (const [key, value] of Object.entries(entries)) { + previous.set(key, process.env[key]); + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + try { + return await fn(); + } finally { + for (const [key, value] of previous.entries()) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +/** Let the sync getter's `void` fetch reach the stub and settle. */ +async function flushBackgroundFetch(): Promise { + await new Promise((resolve) => setImmediate(resolve)); + await new Promise((resolve) => setImmediate(resolve)); +} + +test.afterEach(() => { + canonical.resetKimiCodeCliVersionCache(); + canonical.resetKimiCodeCliVersionFetch(); +}); + +test("a fetched version newer than the pin is kept by the getter", async () => { + const urls: string[] = []; + canonical.setKimiCodeCliVersionFetch(async (url: string | URL | Request) => { + urls.push(String(url)); + return jsonResponse({ version: "2.1.2" }); + }); + + await withEnv({ KIMI_CLI_VERSION: undefined, NODE_TEST_CONTEXT: undefined }, async () => { + assert.equal(canonical.getKimiCodeCliVersion(), canonical.KIMI_CODE_CLI_VERSION); + await flushBackgroundFetch(); + assert.equal(canonical.getKimiCodeCliVersion(), "2.1.2"); + assert.equal(canonical.getKimiCodeCliUserAgent(), "kimi-code-cli/2.1.2"); + }); + assert.equal(urls.length, 1); + assert.equal(urls[0], NPM_LATEST); +}); + +test("a fetch that rejects stays on the pin", async () => { + canonical.setKimiCodeCliVersionFetch(async () => { + throw new Error("registry unreachable"); + }); + + await withEnv({ KIMI_CLI_VERSION: undefined, NODE_TEST_CONTEXT: undefined }, async () => { + assert.equal(canonical.getKimiCodeCliVersion(), canonical.KIMI_CODE_CLI_VERSION); + await flushBackgroundFetch(); + assert.equal(canonical.getKimiCodeCliVersion(), canonical.KIMI_CODE_CLI_VERSION); + assert.equal(canonical.getKimiCodeCliUserAgent(), `kimi-code-cli/${canonical.KIMI_CODE_CLI_VERSION}`); + }); +}); diff --git a/tests/unit/kiro-available-models.test.ts b/tests/unit/kiro-available-models.test.ts index 0aa052050bdb..1bf233e28a79 100644 --- a/tests/unit/kiro-available-models.test.ts +++ b/tests/unit/kiro-available-models.test.ts @@ -117,6 +117,32 @@ test("fetchKiroAvailableModels carries upstream prompt-caching metadata to model } }); +test("fetchKiroAvailableModels does not invent a context window when tokenLimits is missing", async () => { + // A stamped 200k default would reach syncedAvailableModels as inputTokenLimit and get + // pinned as an auto:discovery override over the registry / models.dev window + // (claude-sonnet-5 is 1M) — the same bug class as grok-cli's old 256k fallback. + const fetchImpl = (async () => + jsonResponse({ + models: [ + { modelId: "claude-sonnet-5" }, + { modelId: "claude-sonnet-4.6", tokenLimits: { maxInputTokens: 200000 } }, + ], + })) as unknown as typeof fetch; + + const result = await fetchKiroAvailableModels({ + accessToken: "tok", + providerSpecificData: {}, + fetchImpl, + fallbackModels: FALLBACK, + }); + + const omitted = result.models.filter((m) => m.upstreamModelId === "claude-sonnet-5"); + const declared = result.models.filter((m) => m.upstreamModelId === "claude-sonnet-4.6"); + assert.ok(omitted.length >= 1 && declared.length >= 1); + for (const model of omitted) assert.equal("contextLength" in model, false); + for (const model of declared) assert.equal(model.contextLength, 200000); +}); + test("resolveKiroRegion prefers stored region, then profileArn, else us-east-1", () => { assert.equal(resolveKiroRegion({ region: "eu-central-1" }), "eu-central-1"); assert.equal( diff --git a/tests/unit/management-password-insecure-default.test.ts b/tests/unit/management-password-insecure-default.test.ts index 3abc287cdcdd..02b02c4ac894 100644 --- a/tests/unit/management-password-insecure-default.test.ts +++ b/tests/unit/management-password-insecure-default.test.ts @@ -66,3 +66,25 @@ test("does not warn when bootstrapping with a strong password", async () => { "did not expect any security warning for a strong password" ); }); + +test("a whitespace-only INITIAL_PASSWORD becomes the password as written", async () => { + const result = await managementPassword.ensurePersistentManagementPasswordHash({ + settings: {}, + initialPassword: " ", + logger: makeLogger(), + }); + + assert.equal(result.source, "env"); + assert.equal(await managementPassword.verifyManagementPassword(" ", result.hash), true); +}); + +test("an empty INITIAL_PASSWORD leaves the password unset", async () => { + const result = await managementPassword.ensurePersistentManagementPasswordHash({ + settings: {}, + initialPassword: "", + logger: makeLogger(), + }); + + assert.equal(result.source, "missing"); + assert.equal(result.hash, null); +}); diff --git a/tests/unit/mcp-advanced-tools-error-sanitization-15159.test.ts b/tests/unit/mcp-advanced-tools-error-sanitization-15159.test.ts index ec3bfdacd538..e74194aeb51d 100644 --- a/tests/unit/mcp-advanced-tools-error-sanitization-15159.test.ts +++ b/tests/unit/mcp-advanced-tools-error-sanitization-15159.test.ts @@ -67,7 +67,14 @@ test("M-05: handleSyncPricing returns a sanitized error, not the raw upstream bo const result = (await handleSyncPricing({ sources: ["openai"], dryRun: false })) as ToolResult; assert.equal(result.isError, true, "expected an isError tool result"); const text = result.content[0]?.text ?? ""; - assert.ok(text.startsWith("Error: API [500]"), `unexpected result shape: ${text}`); + // #15159 M-06: was `Error: API [500]`. That prefix came from the private hop this + // module's tool used to carry, and it had drifted from server.ts's `OmniRoute API + // error [..]`. One shared hop means one prefix. The sanitization assertion below is + // what this test is actually for; the prefix just pins the envelope shape. + assert.ok( + text.startsWith("Error: OmniRoute API error [500]"), + `unexpected result shape: ${text}` + ); assertSanitized(result); } finally { restoreFetch(); @@ -121,7 +128,11 @@ test("M-05: handlePickFastestModel sanitizes an upstream failure while applying })) as ToolResult; assert.equal(result.isError, true, "expected an isError tool result"); const text = result.content[0]?.text ?? ""; - assert.ok(text.startsWith("Error: API [500]"), `unexpected result shape: ${text}`); + // Same M-06 prefix unification as the sync-pricing case above. + assert.ok( + text.startsWith("Error: OmniRoute API error [500]"), + `unexpected result shape: ${text}` + ); assertSanitized(result); } finally { restoreFetch(); diff --git a/tests/unit/mcp-analytics-shape.test.ts b/tests/unit/mcp-analytics-shape.test.ts new file mode 100644 index 000000000000..9e94e4d63d24 --- /dev/null +++ b/tests/unit/mcp-analytics-shape.test.ts @@ -0,0 +1,86 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { + analyticsRangeForPeriod, + readAnalyticsTotals, + readProviderMetrics, +} from "../../open-sse/mcp-server/analyticsShape.ts"; + +// The shape /api/usage/analytics actually returns: totals live under `summary`, +// and there is no top-level totalCost, requestCount, successRate or avgLatencyMs. +const ANALYTICS = { + summary: { + totalCost: 7.5, + totalRequests: 1200, + promptTokens: 400000, + completionTokens: 90000, + }, + byProvider: [ + { provider: "antigravity", requests: 1160, cost: 7.3 }, + { provider: "claude", requests: 40, cost: 0.2 }, + ], + byModel: [ + { + model: "gemini-3.6-flash", + provider: "antigravity", + requests: 1000, + avgLatencyMs: 800, + successRatePct: "99.00", + }, + { + model: "gemini-3.1-pro", + provider: "antigravity", + requests: 160, + avgLatencyMs: 2000, + successRatePct: "95.00", + }, + { + model: "claude-sonnet", + provider: "claude", + requests: 40, + avgLatencyMs: 1500, + successRatePct: "100.00", + }, + ], +}; + +describe("analytics shape readers", () => { + it("reads the totals from summary, not the top level", () => { + const totals = readAnalyticsTotals(ANALYTICS); + assert.equal(totals.totalCost, 7.5); + assert.equal(totals.requestCount, 1200); + assert.equal(totals.promptTokens, 400000); + assert.equal(totals.completionTokens, 90000); + }); + + it("returns zeros when summary is missing", () => { + const totals = readAnalyticsTotals({ byProvider: [] }); + assert.equal(totals.totalCost, 0); + assert.equal(totals.requestCount, 0); + }); + + it("aggregates one provider from the byModel rows", () => { + const metrics = readProviderMetrics(ANALYTICS, "antigravity"); + assert.equal(metrics.requestCount, 1160); + // Weighted by requests: (1000*800 + 160*2000) / 1160 = 965.517... + assert.equal(metrics.avgLatencyMs, 966); + // Successful requests: 1000*0.99 + 160*0.95 = 1142, over 1160 = 0.98448 + assert.ok(Math.abs(metrics.successRate - 0.9845) < 0.001); + }); + + it("returns zeros for a provider with no rows", () => { + const metrics = readProviderMetrics(ANALYTICS, "openai"); + assert.equal(metrics.requestCount, 0); + assert.equal(metrics.avgLatencyMs, 0); + assert.equal(metrics.successRate, 0); + }); + + it("maps a report period onto the range the analytics route accepts", () => { + assert.equal(analyticsRangeForPeriod("session"), "1d"); + assert.equal(analyticsRangeForPeriod("day"), "1d"); + assert.equal(analyticsRangeForPeriod("week"), "7d"); + assert.equal(analyticsRangeForPeriod("month"), "30d"); + assert.equal(analyticsRangeForPeriod(undefined), "30d"); + }); +}); diff --git a/tests/unit/mcp-internal-hop-single-source-15159.test.ts b/tests/unit/mcp-internal-hop-single-source-15159.test.ts new file mode 100644 index 000000000000..5f6cac2ceeab --- /dev/null +++ b/tests/unit/mcp-internal-hop-single-source-15159.test.ts @@ -0,0 +1,251 @@ +// #15159 M-06 — "Three copies of the internal hop". +// +// Before this PR there were three implementations of the same server-to-server call: +// +// open-sse/mcp-server/server.ts:199 omniRouteFetch env read: LAZY +// open-sse/mcp-server/tools/advancedTools.ts:37 apiFetch env read: MODULE LOAD +// open-sse/mcp-server/tools/pickFastestModel.ts:17 apiFetch env read: MODULE LOAD +// +// Three consequences, each pinned below: +// +// 1. The two copies froze `OMNIROUTE_API_KEY` (and the base URL) at MODULE LOAD. A key +// exported after the module was imported — the normal shape for a test, and for any +// process that configures its environment late — was silently absent, so the hop went +// out unauthenticated instead of failing loudly. +// +// 2. The error text had already drifted: `OmniRoute API error [n]` vs `API [n]`. The +// catalogs' own copy was worse still — it reached the hop through +// `import("./server.ts")` purely because the hop lived inside the file that registers +// the tools. That dynamic import is what closed two cycles in the graph. +// +// 3. The two copies hardcoded `AbortSignal.timeout(30000)` while the canonical one used +// `mcpFetchTimeoutSignal("management")`, so the documented env override +// (OMNIROUTE_MCP_FETCH_TIMEOUT_MS) silently did not apply to 18 MCP tools. +// +// Fix: one shared hop in `open-sse/mcp-server/internalFetch.ts` — the module name M-01's +// prescribed split already names — read lazily, with every caller routed through it. +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; + +const HOP_MODULE = "open-sse/mcp-server/internalFetch.ts"; + +function source(path: string): string { + return readFileSync(new URL(`../../${path}`, import.meta.url), "utf8"); +} + +/** + * Source with comments stripped. + * + * Needed because this file documents the pattern it forbids: the fix's own comments quote + * the removed `import("./server.ts")` call to explain why it went away. Matching raw text + * would mean a code comment could turn a regression guard red, which is worse than no + * guard — the next reader would "fix" working code. + */ +function code(path: string): string { + return source(path) + .replace(/\/\*[\s\S]*?\*\//g, "") + .replace(/(^|[^:])\/\/.*$/gm, "$1"); +} + +// Every consumer of the internal hop. +const CALLERS = [ + "open-sse/mcp-server/server.ts", + "open-sse/mcp-server/tools/advancedTools.ts", + "open-sse/mcp-server/tools/pickFastestModel.ts", + // The two catalogs. These are the cycle offenders. + "open-sse/mcp-server/catalog.ts", + "open-sse/mcp-server/radarCatalog.ts", +]; + +test("M-06: the shared hop exists as its own module", () => { + // Guard the structural premise first, so a later failure reads as "the module moved" + // rather than "some assertion about fetch headers failed". + let text: string; + try { + text = source(HOP_MODULE); + } catch { + assert.fail(`${HOP_MODULE} does not exist — the shared hop was never extracted`); + } + assert.match( + text, + /export\s+async\s+function\s+omniRouteFetch/, + `${HOP_MODULE} must export the hop` + ); +}); + +test("M-06: none of the five callers defines its own hop", () => { + // The actual defect was duplication. Three private `apiFetch`/`omniRouteFetch` bodies + // is what let the env read and the error text drift apart in the first place. + const offenders = CALLERS.filter((file) => { + const text = source(file); + return /^(?:export\s+)?async\s+function\s+(?:apiFetch|omniRouteFetch)\s*\(/m.test(text); + }); + assert.deepEqual( + offenders, + [], + `these files define their own internal hop — they must import ${HOP_MODULE}:\n${offenders.join("\n")}` + ); +}); + +test("M-06: every caller imports the hop from the shared module", () => { + const missing = CALLERS.filter((file) => !/internalFetch\.ts/.test(source(file))); + assert.deepEqual( + missing, + [], + `these callers do not import ${HOP_MODULE}:\n${missing.join("\n")}` + ); +}); + +test("M-06: the catalogs no longer reach the hop through server.ts", () => { + // This is the cycle. `catalog.ts` and `radarCatalog.ts` are imported BY server.ts, so a + // static import back into server.ts would close a cycle — the original author worked + // around it with `import("./server.ts")`, which closes the same edge for dpdm. With the + // hop in its own leaf module the workaround is unnecessary and must be gone. + const offenders = CALLERS.filter((file) => + /import\(\s*["'][^"']*server\.ts["']\s*\)/.test(code(file)) + ); + assert.deepEqual( + offenders, + [], + `these files still dynamically import server.ts to reach the hop, which keeps two cycles alive:\n${offenders.join("\n")}` + ); + // SHAPE-SANITY: prove the stripper did not simply make the regex unfindable. A comment + // quoting the call must NOT trip it, and a real call must. + assert.doesNotMatch( + code("open-sse/mcp-server/catalog.ts"), + /omniRouteFetch\(path\)/, + "the stripper left dead call syntax behind" + ); + assert.match( + 'const x = (p: string) => import("./server.ts").then((m) => m.hop(p));', + /import\(\s*["'][^"']*server\.ts["']\s*\)/, + "the dynamic-import detector itself is broken" + ); +}); + +test("M-06: the env read is lazy, not frozen at module load", async () => { + // The bug in one assertion. A key exported AFTER the module was imported must still be + // used. Today the two tool-side copies snapshot it at module scope, so this fails. + const { omniRouteFetch } = await import(`../../${HOP_MODULE}`); + + const originalFetch = globalThis.fetch; + const originalKey = process.env.OMNIROUTE_API_KEY; + const originalBase = process.env.OMNIROUTE_BASE_URL; + let seen: { url: string; auth: string | null } | null = null; + + try { + // Set AFTER the dynamic import above has already evaluated the module. + process.env.OMNIROUTE_API_KEY = "late-bound-key"; + process.env.OMNIROUTE_BASE_URL = "http://127.0.0.1:29999"; + + globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => { + const headers = (init?.headers ?? {}) as Record; + seen = { + url: String(input), + auth: headers.Authorization ?? (headers.authorization as string | undefined) ?? null, + }; + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + }) as typeof fetch; + + await omniRouteFetch("/api/late-binding-probe"); + } finally { + globalThis.fetch = originalFetch; + if (originalKey === undefined) delete process.env.OMNIROUTE_API_KEY; + else process.env.OMNIROUTE_API_KEY = originalKey; + if (originalBase === undefined) delete process.env.OMNIROUTE_BASE_URL; + else process.env.OMNIROUTE_BASE_URL = originalBase; + } + + assert.ok(seen, "the hop never reached fetch"); + assert.equal( + (seen as { auth: string | null }).auth, + "Bearer late-bound-key", + "OMNIROUTE_API_KEY set after module load was ignored — the env read is not lazy" + ); + assert.equal( + (seen as { url: string }).url, + "http://127.0.0.1:29999/api/late-binding-probe", + "OMNIROUTE_BASE_URL set after module load was ignored — the base URL is not lazy" + ); +}); + +test("M-06: a hop failure carries the canonical error text", async () => { + // The drift the catalog named: `API [n]` vs `OmniRoute API error [n]`. `server.ts` is the + // canonical form; every caller now produces it. + const { omniRouteFetch } = await import(`../../${HOP_MODULE}`); + + const originalFetch = globalThis.fetch; + try { + globalThis.fetch = (async () => + new Response("upstream said no", { status: 503 })) as typeof fetch; + await assert.rejects( + () => omniRouteFetch("/api/whatever"), + /OmniRoute API error \[503\]: upstream said no/, + "the hop must use the canonical 'OmniRoute API error [status]' text" + ); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("M-06: the one inference hop in the tool modules uses the upstream budget", () => { + // #9717 territory. `omniroute_test_combo` probes every provider in a combo in parallel via + // /v1/chat/completions, so it waits on upstream providers, not on local management reads. + // The module's deleted private copy hardcoded 30s; inheriting the shared hop would have + // silently dropped it to the 10s management default and aborted live probes — the exact + // failure #9717 was filed for on `route_request`. The upstream budget is 60s, so this is + // also strictly more generous than what it had. + const text = source("open-sse/mcp-server/tools/advancedTools.ts"); + const lines = text.split("\n"); + const start = lines.findIndex((l) => l.includes('apiFetch("/v1/chat/completions"')); + assert.notEqual(start, -1, "could not locate the /v1/chat/completions hop in advancedTools.ts"); + + // Bound the window by the NEXT hop rather than by brace-matching: the options object + // contains a nested `JSON.stringify({ ... })` whose `}),` closes before the outer call + // does, so a naive brace walk stops early and would silently drop the assertion's target. + const nextHop = lines.findIndex((l, i) => i > start && l.includes("apiFetch(")); + const end = nextHop === -1 ? Math.min(lines.length, start + 40) : nextHop; + const callBody = lines.slice(start, end).join("\n"); + assert.match( + callBody, + /mcpFetchTimeoutSignal\(\s*"upstream"/, + "the inference hop must request the upstream budget, not inherit the management default" + ); + + // SHAPE-SANITY: every other hop in these modules is a local /api read and must NOT be + // given an upstream budget, or this assertion could pass by blanket-applying it. + const managementOnly = [...text.matchAll(/apiFetch\(\s*[`"]([^`"]+)/g)] + .map((m) => m[1]) + .filter((path) => !path.startsWith("/v1/")); + assert.ok( + managementOnly.length > 5, + `expected several local management hops to classify, got ${managementOnly.length} — the check above would be near-vacuous` + ); + const upstreamSignals = [...text.matchAll(/mcpFetchTimeoutSignal\(\s*"upstream"/g)]; + assert.equal( + upstreamSignals.length, + 1, + "exactly one upstream-budget signal should exist in this module — the inference hop and nothing else" + ); +}); + +test("M-06: the hop honours the documented MCP fetch-timeout override", async () => { + // `mcpFetchTimeoutSignal("management")` reads OMNIROUTE_MCP_FETCH_TIMEOUT_MS. The two + // deleted copies hardcoded AbortSignal.timeout(30000), so the documented operator knob + // silently did nothing for 18 tools. + const text = source(HOP_MODULE); + assert.match( + text, + /mcpFetchTimeoutSignal\(\s*"management"/, + "the shared hop must use mcpFetchTimeoutSignal('management') so OMNIROUTE_MCP_FETCH_TIMEOUT_MS applies" + ); + assert.doesNotMatch( + text, + /AbortSignal\.timeout\(\s*3_?000\s*\)/, + "the hardcoded 30s timeout must not survive — it is what made the env override a no-op" + ); +}); diff --git a/tests/unit/memory-fts-query-bound.test.ts b/tests/unit/memory-fts-query-bound.test.ts new file mode 100644 index 000000000000..beebe8ddfcb2 --- /dev/null +++ b/tests/unit/memory-fts-query-bound.test.ts @@ -0,0 +1,63 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { describe, it } from "node:test"; + +import { + getRelevanceScore, + rowToMemory, + sanitizeFts5Query, +} from "../../src/lib/memory/retrieval/scoring.ts"; + +describe("memory lexical query budgets", () => { + it("preserves short-query sanitization, quoted phrases, and repeated terms", () => { + assert.equal(sanitizeFts5Query(undefined), ""); + assert.equal(sanitizeFts5Query(" * : ( ) "), ""); + assert.equal( + sanitizeFts5Query('hello_world OR café "hello"'), + '"hello_world" "OR" "café" "hello"' + ); + assert.equal(sanitizeFts5Query("hello hello"), '"hello" "hello"'); + }); + + it("caps the implicit-AND expression before a long prompt reaches SQLite", () => { + const query = sanitizeFts5Query("word ".repeat(50_000)); + assert.equal(query.split(" ").length, 32); + assert.equal(query, Array(32).fill('"word"').join(" ")); + }); + + it("bounds scanned characters even for one huge phrase or punctuation prefix", () => { + assert.ok(sanitizeFts5Query("word".repeat(50_000)).length <= 4096 + 2); + assert.equal(sanitizeFts5Query("!".repeat(4096) + "needle"), ""); + assert.ok(sanitizeFts5Query("é_".repeat(50_000)).length <= 4096 + 2); + }); + + it("bounds fallback scoring while preserving normal full-phrase scores", () => { + const memory = rowToMemory({ id: "fixture", type: "fact" as never, content: "word" }); + assert.equal(getRelevanceScore(memory, "word"), 23); + assert.equal(getRelevanceScore(memory, "word ".repeat(50_000)), 32 * 3); + assert.equal(getRelevanceScore(memory, "!".repeat(4096) + " word"), 0); + }); + + it("executes bounded long-query MATCH expressions in a killable SQLite child", () => { + // Assert the budgets first: the old implementation must fail without sending + // an uncapped synchronous MATCH to the test runner or its database driver. + const queries = [ + sanitizeFts5Query("word ".repeat(50_000)), + sanitizeFts5Query("a_".repeat(50_000)), + sanitizeFts5Query("é_".repeat(50_000)), + ]; + for (const query of queries) { + assert.ok(query.length <= 4096 + 64); + assert.ok(query.split(" ").length <= 32); + } + const child = spawnSync( + process.execPath, + [fileURLToPath(new URL("../fixtures/memory-fts-query-bound.cjs", import.meta.url))], + { input: JSON.stringify(queries), encoding: "utf8", timeout: 5000 } + ); + assert.ifError(child.error); + assert.equal(child.status, 0, child.stderr); + assert.deepEqual(JSON.parse(child.stdout), [1, 0, 0]); + }); +}); diff --git a/tests/unit/memory-retrieval-hybrid.test.ts b/tests/unit/memory-retrieval-hybrid.test.ts index 04dddca7c749..2eefb8c90a0b 100644 --- a/tests/unit/memory-retrieval-hybrid.test.ts +++ b/tests/unit/memory-retrieval-hybrid.test.ts @@ -61,10 +61,13 @@ function insertMemory( content: string, key?: string ) { + const { nextId } = db + .prepare("SELECT COALESCE(MAX(rowid), 0) + 1 AS nextId FROM memories") + .get() as { nextId: number }; db.prepare( - `INSERT INTO memories (id, api_key_id, session_id, type, key, content, metadata, created_at, updated_at, expires_at) - VALUES (?, ?, ?, 'factual', ?, ?, '{}', datetime('now'), datetime('now'), NULL)` - ).run(id, apiKeyId, "", key ?? `key-${id}`, content); + `INSERT INTO memories (rowid, memory_id, id, api_key_id, session_id, type, key, content, metadata, created_at, updated_at, expires_at) + VALUES (?, ?, ?, ?, ?, 'factual', ?, ?, '{}', datetime('now'), datetime('now'), NULL)` + ).run(nextId, nextId, id, apiKeyId, "", key ?? `key-${id}`, content); } // ── Tests ───────────────────────────────────────────────────────────────────── @@ -211,3 +214,39 @@ test("retrievePreview: bundle shape matches RetrievePreviewBundle contract", asy assert.ok("rerankApplied" in res); assert.ok("fallbackReason" in res); }); + +test("long lexical queries find older matching memories and retain API-key isolation", async () => { + const db = core.getDbInstance(); + const { retrieveMemories, retrievePreview, sanitizeFts5Query } = + await import("../../src/lib/memory/retrieval.ts"); + const words = Array.from({ length: 10_000 }, (_, i) => `term${i}`); + const query = words.join(" "); + // Fail safely on an unbounded implementation before executing synchronous FTS. + assert.equal(sanitizeFts5Query(query).split(" ").length, 32); + const content = words.slice(0, 32).join(" "); + insertMemory(db, "long-query-match", "long-query-key", content); + db.prepare("UPDATE memories SET created_at = datetime('now', '-1 day') WHERE id = ?").run( + "long-query-match" + ); + insertMemory(db, "other-key-match", "other-key", content); + for (let i = 0; i < 210; i++) { + insertMemory(db, `newer-unrelated-${i}`, "long-query-key", "unrelated topic"); + } + + for (const retrievalStrategy of ["semantic", "hybrid"] as const) { + const memories = await retrieveMemories("long-query-key", { + retrievalStrategy, + query, + maxTokens: 2000, + }); + assert.ok(memories.some((memory) => memory.id === "long-query-match")); + assert.ok(memories.every((memory) => memory.apiKeyId === "long-query-key")); + } + const preview = await retrievePreview("long-query-key", query, { + strategy: "semantic", + maxTokens: 2000, + limit: 10, + }); + assert.ok(preview.items.some((item) => item.memory.id === "long-query-match")); + assert.ok(preview.items.every((item) => item.memory.apiKeyId === "long-query-key")); +}); diff --git a/tests/unit/memory-vectorstore-rrf.test.ts b/tests/unit/memory-vectorstore-rrf.test.ts index fc08ee83f35a..f0b533270c8b 100644 --- a/tests/unit/memory-vectorstore-rrf.test.ts +++ b/tests/unit/memory-vectorstore-rrf.test.ts @@ -86,12 +86,15 @@ function insertMemoryWithFts( apiKeyId: string, content: string ) { + const { nextId } = db + .prepare("SELECT COALESCE(MAX(rowid), 0) + 1 AS nextId FROM memories") + .get() as { nextId: number }; // Insert into memories — the trigger memory_fts_ai fires automatically if the DB has it. // In a fresh test DB the trigger exists (created by migration 023). db.prepare( - `INSERT INTO memories (id, api_key_id, type, key, content, created_at) - VALUES (?, ?, 'factual', ?, ?, datetime('now'))` - ).run(id, apiKeyId, `key-${id}`, content); + `INSERT INTO memories (rowid, memory_id, id, api_key_id, type, key, content, created_at) + VALUES (?, ?, ?, ?, 'factual', ?, ?, datetime('now'))` + ).run(nextId, nextId, id, apiKeyId, `key-${id}`, content); // The migration 023 trigger inserts into memory_fts using memory_id (= rowid). // If the trigger didn't fire (e.g. test DB without triggers), manually sync FTS. try { @@ -117,6 +120,26 @@ function insertMemoryWithFts( // ──────────────── RRF tests ──────────────── +test("searchHybrid: long prompts retain a scoped FTS-only hit", async (t) => { + const store = getStoreOrSkip(t); + if (!store) return; + const { sanitizeFts5Query } = await import("../../src/lib/memory/retrieval/scoring.ts"); + const words = Array.from({ length: 10_000 }, (_, i) => `term${i}`); + const query = words.join(" "); + assert.equal(sanitizeFts5Query(query).split(" ").length, 32); + await setupTable(store); + const db = core.getDbInstance(); + const content = words.slice(0, 32).join(" "); + insertMemoryWithFts(db, "long-fts-only", "long-key", content); + insertMemoryWithFts(db, "long-other-key", "other-key", content); + const hits = await store.searchHybrid(makeVec(1, 0, 0, 0), query, 10, "long-key"); + const match = hits.find((hit) => hit.memoryId === "long-fts-only"); + assert.ok(match); + assert.ok(match.ftsRank !== null); + assert.equal(match.vecRank, null); + assert.ok(hits.every((hit) => hit.memoryId !== "long-other-key")); +}); + test("searchHybrid: results ordered DESC by rrfScore", async (t) => { const store = getStoreOrSkip(t); if (!store) return; diff --git a/tests/unit/model-import-degraded-catalog-15159.test.ts b/tests/unit/model-import-degraded-catalog-15159.test.ts new file mode 100644 index 000000000000..56b5f509de43 --- /dev/null +++ b/tests/unit/model-import-degraded-catalog-15159.test.ts @@ -0,0 +1,157 @@ +// Regression guard for audit #15159 — B-03. +// +// The dashboard's Import button reported success while model discovery had +// actually failed. `handleImportModels` +// (src/app/(dashboard)/dashboard/providers/[id]/hooks/useModelImportHandlers.ts) +// fetches `/api/providers//models?refresh=true`, and the server flags a +// degraded response with `source` + `warning` — `local_catalog` when remote +// discovery failed, `cache` with a warning, `github_catalog` with a warning. +// +// The client read `data.warning` and pushed it into the modal's `logs` array, but +// left `status` as `t("allModelsAlreadyImported")`. So the headline the operator +// reads is "All models already imported" while the only trace of the real cause is +// a log line lower down — and `isDegradedDiscovery` from +// `src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts` (the server +// module that defines exactly this condition) was never consulted from the +// dashboard at all. +// +// Consequence: an operator whose provider silently fell back to a local catalog +// concludes there is nothing to import, and the missing models stay missing. +// +// The decision is extracted into a pure function so it is testable without +// mounting the hook — the same pattern this file already uses for +// `extractImportWarning` in `modelImportWarning.ts`. +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { isDegradedDiscovery } from "../../src/app/api/providers/[id]/sync-models/degradedLocalCatalog.ts"; +import { + classifyModelImport, + type ClassifyModelImportInput, +} from "../../src/app/(dashboard)/dashboard/providers/[id]/hooks/modelImportWarning.ts"; + +function input(overrides: Partial = {}): ClassifyModelImportInput { + return { + modelsData: { models: [{ id: "a" }] }, + fetchedModels: [{ id: "a" }], + isKnownModel: () => true, + ...overrides, + }; +} + +test("B-03: a degraded catalog is never reported as 'nothing new to import'", () => { + const result = classifyModelImport( + input({ + modelsData: { + models: [{ id: "a" }], + source: "local_catalog", + warning: "No remote models discovered — using local catalog", + }, + }) + ); + + assert.equal(result.outcome, "nothing-new"); + assert.equal( + result.degraded, + true, + "a local_catalog response with a warning must be flagged as degraded" + ); + assert.equal(result.warning, "No remote models discovered — using local catalog"); +}); + +test("B-03: a degraded catalog that returns zero models is also flagged", () => { + // Same class of lie: "No models found" reads as an authoritative answer when + // discovery never actually ran against the upstream. + const result = classifyModelImport( + input({ + modelsData: { models: [], source: "local_catalog", warning: "Auto-fetch disabled" }, + fetchedModels: [], + }) + ); + + assert.equal(result.outcome, "no-models"); + assert.equal(result.degraded, true); + assert.equal(result.warning, "Auto-fetch disabled"); +}); + +test("B-03: an intentional local_catalog is NOT treated as degraded", () => { + // reka / voyage-ai / t3-web: local_catalog is their INTENDED and only source. + // Treating that as a failure would break Import for them — the guard exists in + // the server module precisely for this (#5460 / #5465). + const result = classifyModelImport( + input({ + modelsData: { models: [{ id: "a" }], source: "local_catalog", intentional: true }, + }) + ); + + assert.equal(result.degraded, false); +}); + +test("B-03: a live 'api' discovery is not degraded and still imports", () => { + const result = classifyModelImport( + input({ + modelsData: { models: [{ id: "a" }, { id: "b" }], source: "api" }, + fetchedModels: [{ id: "a" }, { id: "b" }], + isKnownModel: (id) => id === "a", + }) + ); + + assert.equal(result.outcome, "import"); + assert.equal(result.degraded, false); + assert.equal(result.newCount, 1); + assert.equal(result.total, 2); +}); + +test("B-03: a clean catalog with nothing new is reported as a true success", () => { + // The control: the normal "already imported" path must keep working. If this + // case were misclassified as degraded the fix would be worse than the bug. + const result = classifyModelImport( + input({ modelsData: { models: [{ id: "a" }], source: "api" } }) + ); + + assert.equal(result.outcome, "nothing-new"); + assert.equal(result.degraded, false); + assert.equal(result.warning, null); +}); + +test("B-03: a degraded github_catalog / cached catalog is flagged too", () => { + for (const source of ["github_catalog", "cache"]) { + const result = classifyModelImport( + input({ + modelsData: { models: [{ id: "a" }], source, warning: "stale upstream" }, + }) + ); + assert.equal(result.degraded, true, `${source} with a warning must be degraded`); + assert.equal(result.warning, "stale upstream"); + } +}); + +test("B-03: the dashboard classifier agrees with the server's own definition", () => { + // The dashboard must not invent its own notion of "degraded". If the server's + // rule changes, this fails and the two cannot silently disagree again. + const samples: Array> = [ + { source: "local_catalog" }, + { source: "local_catalog", intentional: true }, + { source: "local_catalog", warning: "w" }, + { source: "cache" }, + { source: "cache", warning: "w" }, + { source: "github_catalog", warning: "w" }, + { source: "api" }, + { source: "api", warning: "ignored" }, + {}, + ]; + + for (const modelsData of samples) { + const result = classifyModelImport( + input({ + modelsData: { models: [{ id: "a" }], ...modelsData }, + isKnownModel: () => true, + }) + ); + assert.equal( + result.degraded, + isDegradedDiscovery(modelsData), + `classification diverged from the server rule for ${JSON.stringify(modelsData)}` + ); + } +}); diff --git a/tests/unit/noauth-banner-requirement.test.ts b/tests/unit/noauth-banner-requirement.test.ts new file mode 100644 index 000000000000..ce41fa42bf3e --- /dev/null +++ b/tests/unit/noauth-banner-requirement.test.ts @@ -0,0 +1,52 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import React from "react"; +import { renderToStaticMarkup as reactRenderToStaticMarkup } from "react-dom/server"; +import { NextIntlClientProvider } from "next-intl"; +import enMessages from "../../src/i18n/messages/en.json" with { type: "json" }; +import { NOAUTH_PROVIDERS } from "../../src/shared/constants/providers/noauth.ts"; +import NoAuthProviderCard from "../../src/shared/components/NoAuthProviderCard.tsx"; +import NoAuthAccountCard from "../../src/shared/components/NoAuthAccountCard.tsx"; + +const PLAIN = enMessages.noAuthProvider.description; + +function renderBanner(providerId: keyof typeof NOAUTH_PROVIDERS) { + return reactRenderToStaticMarkup( + React.createElement( + NextIntlClientProvider, + { locale: "en", timeZone: "UTC", messages: { noAuthProvider: enMessages.noAuthProvider } }, + React.createElement(NoAuthProviderCard, { providerId, enabled: true }) + ) + ); +} + +test("a no-auth banner includes the provider notice when one is stored", () => { + const html = renderBanner("cloudflare-playground"); + + assert.match(html, /ready to use immediately/); + assert.match(html, /Requires Playwright with a Chromium browser on first request/); +}); + +test("a no-auth banner stays the plain ready message when the provider has no notice or auth hint", () => { + const html = renderBanner("duckduckgo-web"); + + assert.match(html, new RegExp(PLAIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))); + assert.doesNotMatch(html, /DuckDuckGo AI Chat is anonymous/); +}); + +test("an account no-auth banner includes the stored notice beside the account message", () => { + const html = reactRenderToStaticMarkup( + React.createElement( + NextIntlClientProvider, + { locale: "en", timeZone: "UTC", messages: { noAuthProvider: enMessages.noAuthProvider } }, + React.createElement(NoAuthAccountCard, { + providerId: "opencode", + providerName: "OpenCode", + generateAccountId: () => "account-1", + }) + ) + ); + + assert.match(html, /Ready to use/); + assert.match(html, /requests that do not match the OpenCode client contract/); +}); diff --git a/tests/unit/noauth-pause-retryable-cooldown.test.ts b/tests/unit/noauth-pause-retryable-cooldown.test.ts new file mode 100644 index 000000000000..12604409e0f1 --- /dev/null +++ b/tests/unit/noauth-pause-retryable-cooldown.test.ts @@ -0,0 +1,267 @@ +/** + * A paused no-auth provider must answer with a retryable cooldown (429 + + * Retry-After), not a fatal 401 "No active credentials". + * + * Symptom: while the short refusal pause covers the shared synthetic + * connection, every request for that provider received null from selection + * and surfaced as 401, which clients do not retry. + */ +import { test, after, beforeEach } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omr-noauth-pause-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-noauth-pause-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const auth = await import("../../src/sse/services/auth.ts"); +const { handleNoCredentials } = await import("../../src/sse/handlers/chatHelpers.ts"); +const { noteOpencodeFreeTierSkip, clearOpencodeFreeTierSkips, getOpencodeFreeTierSkipRemainingMs } = + await import("../../open-sse/services/opencodeFreeTierSkip.ts"); +const { pauseCooldownIfPaused } = await import("../../src/sse/services/noAuthModelCooldown.ts"); +const { clearAllModelLockouts } = await import("../../open-sse/services/accountFallback.ts"); +const { checkFallbackError } = await import("../../open-sse/services/accountFallback.ts"); + +const PROVIDER = "opencode"; +const MODEL = "muse-spark-1.3-contributor-free"; + +type SelectionOutcome = { + allRateLimited?: boolean; + cooldownScope?: string; + cooldownModel?: string | null; + lastErrorCode?: number; + lastError?: string | null; + retryAfter: string; + retryAfterHuman?: string; + connectionsCount?: number; + connectionId?: string; +} | null; + +beforeEach(() => { + clearOpencodeFreeTierSkips(); + clearAllModelLockouts(); +}); +after(() => { + clearOpencodeFreeTierSkips(); + clearAllModelLockouts(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test("reader reports the positive remaining time while the pause covers the provider", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 60_000); + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + 10_000), 50_000); +}); + +test("reader returns null with no entry", () => { + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER), null); +}); + +test("reader returns null for a foreign provider", () => { + noteOpencodeFreeTierSkip(PROVIDER); + assert.equal(getOpencodeFreeTierSkipRemainingMs("groq"), null); +}); + +test("reader returns null once the pause expires and drops the entry", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 50); + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + 50), null); + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + 51), null); +}); + +test("builder returns a connection-scoped 429 envelope near the end of the pause", () => { + noteOpencodeFreeTierSkip(PROVIDER, Date.now(), 60_000); + const before = Date.now(); + const outcome = pauseCooldownIfPaused(PROVIDER, "noauth"); + assert.ok(outcome, "an active pause must answer a cooldown, not null"); + if (!outcome) return; + assert.equal(outcome.allRateLimited, true); + assert.equal(outcome.lastErrorCode, 429); + assert.equal(outcome.cooldownScope, "connection"); + assert.equal(outcome.cooldownModel, null); + assert.equal(outcome.lastError, "The shared no-auth connection is in a refusal pause"); + assert.equal(outcome.connectionsCount, 1); + const remaining = Date.parse(outcome.retryAfter) - before; + assert.ok( + remaining > 0 && remaining <= 60_000 + 2000, + `retryAfter near pause end (${remaining}ms)` + ); +}); + +test("active pause returns a cooldown whose retryAfter lands near the pause end", async () => { + noteOpencodeFreeTierSkip(PROVIDER); + const result = (await auth.getProviderCredentials( + PROVIDER, + null, + null, + MODEL + )) as SelectionOutcome; + assert.ok(result, "an active pause must not collapse to null"); + if (!result) return; + assert.equal(result.allRateLimited, true); + assert.equal(result.lastErrorCode, 429); + assert.notEqual(result.cooldownScope, "model"); + const remainingMs = Date.parse(result.retryAfter) - Date.now(); + assert.ok( + remainingMs > 0 && remainingMs <= 3 * 60 * 1000 + 2000, + `retryAfter near end (${remainingMs}ms)` + ); +}); + +test("no pause returns the synthetic connection", async () => { + const result = (await auth.getProviderCredentials( + PROVIDER, + null, + null, + MODEL + )) as SelectionOutcome; + assert.equal(result?.connectionId, "noauth"); +}); + +test("expired pause returns the synthetic connection", async () => { + noteOpencodeFreeTierSkip(PROVIDER, Date.now() - 200_000, 60_000); + const result = (await auth.getProviderCredentials( + PROVIDER, + null, + null, + MODEL + )) as SelectionOutcome; + assert.equal(result?.connectionId, "noauth"); +}); + +test("excluded noauth connection keeps returning null during a pause", async () => { + noteOpencodeFreeTierSkip(PROVIDER); + const result = await auth.getProviderCredentials(PROVIDER, "noauth", null, MODEL); + assert.equal(result, null); +}); + +test("restricted allowlist keeps returning null during a pause", async () => { + noteOpencodeFreeTierSkip(PROVIDER); + const result = await auth.getProviderCredentials(PROVIDER, null, ["conn-other"], MODEL); + assert.equal(result, null); +}); + +test("another provider is unchanged during a pause", async () => { + noteOpencodeFreeTierSkip(PROVIDER); + const result = await auth.getProviderCredentials("groq", null, null, MODEL); + assert.equal(result, null); +}); + +test("pause cooldown maps to a 429 with Retry-After on a fresh attempt", async () => { + noteOpencodeFreeTierSkip(PROVIDER); + const credentials = await auth.getProviderCredentials(PROVIDER, null, null, MODEL); + const res = handleNoCredentials(credentials, null, PROVIDER, MODEL, null, null); + assert.equal(res.status, 429); + assert.ok(res.headers.get("Retry-After"), "429 carries Retry-After"); + const body = (await res.json()) as { error: { code?: string } }; + assert.notEqual(body.error.code, "model_cooldown"); +}); + +test("pause cooldown on a retried attempt documents the inherited status", async () => { + noteOpencodeFreeTierSkip(PROVIDER); + const credentials = await auth.getProviderCredentials(PROVIDER, null, null, MODEL); + const res = handleNoCredentials(credentials, null, PROVIDER, MODEL, null, 500); + assert.equal(res.status, 500); +}); + +test("a refusal arms the pause and the next selection answers the retryable cooldown", async () => { + const { handleChatCore } = await import("../../open-sse/handlers/chatCore.ts"); + const { getExecutor } = await import("../../open-sse/executors/index.ts"); + const originalFetch = globalThis.fetch; + const credentials = await auth.getProviderCredentials(PROVIDER, null, null, MODEL); + assert.equal((credentials as SelectionOutcome)?.connectionId, "noauth"); + const executor = await getExecutor(PROVIDER); + const originalRefresh = executor.refreshCredentials; + executor.refreshCredentials = async () => null; + globalThis.fetch = async () => + new Response( + JSON.stringify({ + type: "error", + error: { + type: "FreeTierError", + message: + "Error from provider (Console): OpenCode's free tier can only be used from within OpenCode", + }, + }), + { status: 403, headers: { "content-type": "application/json" } } + ); + try { + const requestBody = { + model: MODEL, + messages: [{ role: "user", content: "hi" }], + stream: false, + }; + await handleChatCore({ + body: structuredClone(requestBody), + modelInfo: { provider: PROVIDER, model: MODEL, extendedContext: false }, + credentials, + log: { debug() {}, info() {}, warn() {}, error() {} }, + clientRawRequest: { + endpoint: "/v1/chat/completions", + body: structuredClone(requestBody), + headers: new Headers({ accept: "application/json" }), + }, + connectionId: (credentials as SelectionOutcome)?.connectionId ?? null, + }); + } finally { + executor.refreshCredentials = originalRefresh; + globalThis.fetch = originalFetch; + } + const next = (await auth.getProviderCredentials(PROVIDER, null, null, MODEL)) as SelectionOutcome; + assert.ok(next?.allRateLimited, "the armed pause must answer a cooldown, not null"); + assert.equal(next?.lastErrorCode, 429); + const res = handleNoCredentials(next, null, PROVIDER, MODEL, null, null); + assert.equal(res.status, 429); + assert.ok(res.headers.get("Retry-After"), "429 carries Retry-After"); +}); + +test("combo: paused target answers a retryable 429 and selection passes to the next target", async () => { + noteOpencodeFreeTierSkip(PROVIDER); + const pausedTarget = (await auth.getProviderCredentials( + PROVIDER, + null, + null, + MODEL + )) as SelectionOutcome; + assert.ok(pausedTarget?.allRateLimited, "paused combo target must answer a cooldown, not null"); + assert.equal(pausedTarget?.lastErrorCode, 429); + const pausedRes = handleNoCredentials(pausedTarget, null, PROVIDER, MODEL, null, null, [], true); + assert.equal(pausedRes.status, 429); + assert.ok(pausedRes.headers.get("Retry-After"), "paused combo target carries Retry-After"); + const pausedBody = (await pausedRes.json()) as { error?: { message?: string } }; + assert.ok( + typeof pausedBody.error?.message === "string" && + pausedBody.error.message.includes("refusal pause"), + "paused combo target names the refusal pause" + ); + const retryAfterMs = Date.parse(pausedTarget?.retryAfter ?? "") - Date.now(); + assert.ok( + retryAfterMs > 0 && retryAfterMs <= 3 * 60 * 1000 + 2000, + `paused combo target Retry-After near the pause end, not a full 180 s wait (${retryAfterMs}ms)` + ); + const fallback = checkFallbackError( + pausedRes.status, + pausedBody.error?.message ?? "", + 0, + null, + PROVIDER, + pausedRes.headers + ); + assert.equal(fallback.shouldFallback, true); +}); + +test("storage returns to its starting size after pauses expire", () => { + const start = Date.now(); + const count = 25; + for (let i = 0; i < count; i += 1) { + noteOpencodeFreeTierSkip(`opencode-${i}`, start, 50); + } + for (let i = 0; i < count; i += 1) { + assert.equal(getOpencodeFreeTierSkipRemainingMs(`opencode-${i}`, start + 51), null); + } + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, start + 51), null); +}); diff --git a/tests/unit/noauth-pause-visibility.test.ts b/tests/unit/noauth-pause-visibility.test.ts new file mode 100644 index 000000000000..16915c7b193a --- /dev/null +++ b/tests/unit/noauth-pause-visibility.test.ts @@ -0,0 +1,326 @@ +/** + * Active free-tier pauses are exposed read-only for the account card. + * + * Setup always goes through the production writer `noteOpencodeFreeTierSkip` + * (never a hand-built table) with a frozen `now`. + */ +import { test, beforeEach, after } from "node:test"; +import assert from "node:assert/strict"; +import { JSDOM } from "jsdom"; +import { NextIntlClientProvider } from "next-intl"; +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import enMessages from "../../src/i18n/messages/en.json" with { type: "json" }; + +const { noteOpencodeFreeTierSkip, clearOpencodeFreeTierSkips, listOpencodeFreeTierPauses } = + await import("../../open-sse/services/opencodeFreeTierSkip.ts"); +const { GET } = await import("../../src/app/api/admin/proxy-pool-visibility/route.ts"); + +const PROVIDER = "opencode-pause-demo"; +const MODEL = "muse-spark-1.3-contributor-free"; + +beforeEach(() => { + clearOpencodeFreeTierSkips(); +}); +after(() => { + clearOpencodeFreeTierSkips(); +}); + +test("active pause on a model exposes provider, model, end and motive", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 60_000, MODEL); + const pauses = listOpencodeFreeTierPauses(PROVIDER, now + 10_000); + assert.equal(pauses.length, 1); + assert.equal(pauses[0].provider, PROVIDER); + assert.equal(pauses[0].model, MODEL); + assert.equal(pauses[0].until, new Date(now + 60_000).toISOString()); + assert.equal(pauses[0].reason, "Free-tier request refused (429)"); +}); + +test("expired pause is absent", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 50, MODEL); + assert.deepEqual(listOpencodeFreeTierPauses(PROVIDER, now + 50), []); + assert.deepEqual(listOpencodeFreeTierPauses(PROVIDER, now + 51), []); +}); + +test("no pause exposes nothing", () => { + assert.deepEqual(listOpencodeFreeTierPauses(PROVIDER, 1_000_000), []); +}); + +test("foreign provider exposes nothing", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 60_000, MODEL); + assert.deepEqual(listOpencodeFreeTierPauses("groq", now + 1_000), []); +}); + +test("wide pause exposes a null model", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 60_000); + const pauses = listOpencodeFreeTierPauses(PROVIDER, now + 1_000); + assert.equal(pauses.length, 1); + assert.equal(pauses[0].provider, PROVIDER); + assert.equal(pauses[0].model, null); + assert.equal(pauses[0].until, new Date(now + 60_000).toISOString()); +}); + +test("model id outside the contract stays attached to its provider", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 60_000, "a\nb"); + const pauses = listOpencodeFreeTierPauses(PROVIDER, now + 1_000); + assert.equal(pauses.length, 1); + assert.equal(pauses[0].model, "b"); +}); + +function req(url: string): Request { + return new Request(`http://localhost${url}`); +} + +test("route exposes an active pause with provider, model, end and motive", async () => { + const before = Date.now(); + noteOpencodeFreeTierSkip(PROVIDER, before, 60_000, MODEL); + const res = await GET( + req(`/api/admin/proxy-pool-visibility?freeTierPauses=1&provider=${PROVIDER}`) + ); + if (res.status === 401 || res.status === 403) return; + assert.equal(res.status, 200); + const body = (await res.json()) as { + provider: string; + pauses: Array<{ model: string | null; until: string; reason: string }>; + processMemory: boolean; + }; + assert.equal(body.provider, PROVIDER); + assert.equal(body.processMemory, true); + assert.equal(body.pauses.length, 1); + assert.equal(body.pauses[0].model, MODEL); + assert.ok(Number.isFinite(Date.parse(body.pauses[0].until))); + assert.match(body.pauses[0].reason, /429/); +}); + +test("route answers 400 without a provider", async () => { + const res = await GET(req("/api/admin/proxy-pool-visibility?freeTierPauses=1")); + if (res.status === 401 || res.status === 403) return; + assert.equal(res.status, 400); +}); + +test("route prefers the pause payload when combined with proxyId", async () => { + noteOpencodeFreeTierSkip(PROVIDER, Date.now(), 60_000, MODEL); + const res = await GET( + req(`/api/admin/proxy-pool-visibility?freeTierPauses=1&provider=${PROVIDER}&proxyId=no-such-id`) + ); + if (res.status === 401 || res.status === 403) return; + assert.equal(res.status, 200); + const body = (await res.json()) as { pauses: unknown[]; members?: unknown }; + assert.ok(Array.isArray(body.pauses)); + assert.equal(body.members, undefined); +}); + +test("route without the new params keeps the legacy shape", async () => { + const noParams = await GET(req("/api/admin/proxy-pool-visibility")); + if (noParams.status === 401 || noParams.status === 403) return; + assert.equal(noParams.status, 400); + const scope = await GET(req("/api/admin/proxy-pool-visibility?scope=global")); + if (scope.status === 401 || scope.status === 403) return; + assert.equal(scope.status, 200); + const body = (await scope.json()) as { pauses?: unknown; members: unknown[] }; + assert.ok(Array.isArray(body.members)); + assert.equal(body.pauses, undefined); +}); + +// ── Card render: pause note ──────────────────────────────────────────────── +// Setup goes through the production writer + production GET route: the fetch +// stub below answers the card's visibility request with the live route +// payload, never a hand-built table. + +const CARD_PROVIDER = PROVIDER; + +function installDom() { + const dom = new JSDOM("", { + url: "http://localhost/", + }); + const win = dom.window as unknown as Record; + const g = globalThis as unknown as Record; + const stash: Record = {}; + for (const key of ["window", "document", "HTMLElement", "Node", "Event", "getComputedStyle"]) { + stash[key] = g[key]; + } + g["window"] = win; + g["document"] = win["document"]; + g["HTMLElement"] = win["HTMLElement"]; + g["Node"] = win["Node"]; + g["Event"] = win["Event"]; + (g["window"] as Record)["matchMedia"] ??= () => ({ + matches: false, + addListener() {}, + removeListener() {}, + addEventListener() {}, + removeEventListener() {}, + dispatchEvent: () => false, + }); + return () => { + for (const [key, value] of Object.entries(stash)) { + if (value === undefined) delete g[key]; + else g[key] = value; + } + dom.window.close(); + }; +} + +function stubCardFetch(mode: "live" | "failing") { + const realFetch = globalThis.fetch; + const stub = async (input: unknown): Promise => { + const url = String(input); + if (url.includes("/api/admin/proxy-pool-visibility")) { + if (mode === "failing") throw new Error("visibility fetch failed"); + const routeReq = new Request(`http://localhost${url}`); + return (await GET(routeReq)) as unknown as Response; + } + if (url.includes("/api/providers")) { + return Response.json({ connections: [] }); + } + if (url.includes("/api/settings/proxies/assignments")) { + return Response.json({ items: [] }); + } + if (url.includes("/api/settings/proxies")) { + return Response.json({ items: [] }); + } + return Response.json({}); + }; + globalThis.fetch = stub as typeof fetch; + return () => { + globalThis.fetch = realFetch; + }; +} + +async function renderCardNote(): Promise<{ note: Element | null; cleanup: () => void }> { + const restoreDom = installDom(); + const restoreFetch = stubCardFetch("live"); + (globalThis as Record)["IS_REACT_ACT_ENVIRONMENT"] = true; + const { default: NoAuthAccountCard } = + await import("../../src/shared/components/NoAuthAccountCard.tsx"); + const document = globalThis.document as unknown as Document; + const el = document.createElement("div"); + document.body.appendChild(el); + const root = createRoot(el); + await act(async () => { + root.render( + React.createElement( + NextIntlClientProvider, + { locale: "en", messages: enMessages }, + React.createElement(NoAuthAccountCard, { + providerId: CARD_PROVIDER, + providerName: "Pause Demo", + generateAccountId: () => "gen-1", + }) + ) + ); + }); + for (let i = 0; i < 50; i++) { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 20)); + }); + const found = el.querySelector('[data-testid="noauth-free-tier-pause"]'); + if (found) break; + } + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + const note = el.querySelector('[data-testid="noauth-free-tier-pause"]'); + return { + note, + cleanup: () => { + try { + root.unmount(); + } finally { + el.remove(); + restoreFetch(); + restoreDom(); + } + }, + }; +} + +test("card renders the pause note for a model pause", async () => { + noteOpencodeFreeTierSkip(CARD_PROVIDER, Date.now(), 60_000, MODEL); + const { note, cleanup } = await renderCardNote(); + try { + assert.ok(note, "pause note is rendered while a pause is active"); + const text = note?.textContent ?? ""; + assert.ok(text.includes(MODEL), "note names the paused model"); + assert.ok(text.includes("429"), "note shows the refusal motive"); + assert.ok( + text.includes("In-process pause on this instance"), + "note states the pause lives on this instance" + ); + const [pause] = listOpencodeFreeTierPauses(CARD_PROVIDER, Date.now()); + assert.ok( + text.includes(new Date(pause.until).toLocaleString().slice(0, 8)), + "note shows the pause end date" + ); + } finally { + cleanup(); + clearOpencodeFreeTierSkips(); + } +}); + +test("card renders the note for a provider-wide pause", async () => { + noteOpencodeFreeTierSkip(CARD_PROVIDER, Date.now(), 60_000); + const { note, cleanup } = await renderCardNote(); + try { + assert.ok(note, "pause note is rendered for a provider-wide pause"); + assert.ok((note?.textContent ?? "").includes("429"), "note shows the refusal motive"); + } finally { + cleanup(); + clearOpencodeFreeTierSkips(); + } +}); + +test("card renders no note without a pause", async () => { + const { note, cleanup } = await renderCardNote(); + try { + assert.equal(note, null); + } finally { + cleanup(); + clearOpencodeFreeTierSkips(); + } +}); + +test("card renders no note when the visibility fetch fails", async () => { + const restoreDom = installDom(); + const restoreFetch = stubCardFetch("failing"); + (globalThis as Record)["IS_REACT_ACT_ENVIRONMENT"] = true; + const { default: NoAuthAccountCard } = + await import("../../src/shared/components/NoAuthAccountCard.tsx"); + const document = globalThis.document as unknown as Document; + const el = document.createElement("div"); + document.body.appendChild(el); + const root = createRoot(el); + try { + await act(async () => { + root.render( + React.createElement( + NextIntlClientProvider, + { locale: "en", messages: enMessages }, + React.createElement(NoAuthAccountCard, { + providerId: CARD_PROVIDER, + providerName: "Pause Demo", + generateAccountId: () => "gen-1", + }) + ) + ); + }); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 200)); + }); + assert.equal(el.querySelector('[data-testid="noauth-free-tier-pause"]'), null); + } finally { + try { + root.unmount(); + } finally { + el.remove(); + restoreFetch(); + restoreDom(); + clearOpencodeFreeTierSkips(); + } + } +}); diff --git a/tests/unit/oidc-callback.test.ts b/tests/unit/oidc-callback.test.ts index 783aec0419ec..57caba190935 100644 --- a/tests/unit/oidc-callback.test.ts +++ b/tests/unit/oidc-callback.test.ts @@ -75,7 +75,7 @@ async function setupFullOidcSettings() { oidcClientId: "client-oidc-test", oidcClientSecret: "secret-oidc-test", oidcRedirectPath: "/api/auth/oidc/callback", - oidcAllowedSubjects: [], + oidcAllowedSubjects: ["user-123"], }); } @@ -518,7 +518,7 @@ test("OIDC callback handles issuer with trailing slash in settings and token (#1 oidcClientId: "client-oidc-authentik", oidcClientSecret: "secret-oidc-authentik", oidcRedirectPath: "/api/auth/oidc/callback", - oidcAllowedSubjects: [], + oidcAllowedSubjects: ["authentik-user-1"], }); const { idToken, jwks } = await createSignedIdToken({ @@ -583,7 +583,7 @@ test("OIDC callback handles issuer mismatch on trailing slash between settings a oidcClientId: "client-oidc-authentik-mismatch", oidcClientSecret: "secret-oidc-authentik-mismatch", oidcRedirectPath: "/api/auth/oidc/callback", - oidcAllowedSubjects: [], + oidcAllowedSubjects: ["authentik-user-2"], }); // Token signed with trailing slash (common with Authentik discovery) @@ -634,3 +634,35 @@ test("OIDC callback handles issuer mismatch on trailing slash between settings a globalThis.fetch = originalFetch; } }); + +for (const [label, subjects] of [ + ["an empty allowlist", []], + ["an allowlist of blank entries", ["", " "]], +] as const) { + test(`OIDC callback refuses to sign anyone in with ${label} (not_configured)`, async () => { + await setupFullOidcSettings(); + await localDb.updateSettings({ oidcAllowedSubjects: [...subjects] }); + + const testState = "state-empty-allowlist"; + capturedCookies["oidc_state"] = { value: testState }; + + const originalFetch = globalThis.fetch; + let outboundCalls = 0; + globalThis.fetch = (async () => { + outboundCalls += 1; + throw new Error("the identity provider must not be contacted"); + }) as typeof fetch; + try { + const response = await callbackRoute.GET( + new Request(`http://localhost/api/auth/oidc/callback?code=foo&state=${testState}`) + ); + + assert.equal(response.status, 307); + assert.ok((response.headers.get("location") || "").includes("oidc_error=not_configured")); + assert.equal(outboundCalls, 0); + assert.equal(capturedCookies["auth_token"], undefined); + } finally { + globalThis.fetch = originalFetch; + } + }); +} diff --git a/tests/unit/oidc-settings-allowlist-guard.test.ts b/tests/unit/oidc-settings-allowlist-guard.test.ts new file mode 100644 index 000000000000..d2ac72eebf59 --- /dev/null +++ b/tests/unit/oidc-settings-allowlist-guard.test.ts @@ -0,0 +1,87 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { makeManagementSessionRequest } from "../helpers/managementSession.ts"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-oidc-settings-guard-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const settingsDb = await import("../../src/lib/db/settings.ts"); +const settingsRoute = await import("../../src/app/api/settings/route.ts"); + +test.beforeEach(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +async function patch(body: Record) { + const response = await settingsRoute.PATCH( + await makeManagementSessionRequest("http://localhost/api/settings", { + method: "PATCH", + body, + }) + ); + return { status: response.status, body: (await response.json()) as any }; +} + +async function seedEnabled(subjects: string[]) { + await settingsDb.updateSettings({ + oidcEnabled: true, + oidcIssuer: "https://idp.test", + oidcClientId: "client", + oidcClientSecret: "secret", + oidcAllowedSubjects: subjects, + }); +} + +test("emptying the allowlist while OIDC stays enabled is rejected", async () => { + await seedEnabled(["admin@example.com"]); + + const result = await patch({ oidcAllowedSubjects: [] }); + + assert.equal(result.status, 400); + assert.equal(result.body.error.code, "OIDC_ALLOWED_SUBJECTS_REQUIRED"); + assert.deepEqual((await settingsDb.getSettings()).oidcAllowedSubjects, ["admin@example.com"]); +}); + +test("an allowlist of blank entries is rejected while OIDC is enabled", async () => { + await seedEnabled(["admin@example.com"]); + + const result = await patch({ oidcAllowedSubjects: ["", " "] }); + + assert.equal(result.status, 400); + assert.equal(result.body.error.code, "OIDC_ALLOWED_SUBJECTS_REQUIRED"); +}); + +test("enabling OIDC without any allowed subject is rejected", async () => { + const result = await patch({ oidcEnabled: true }); + + assert.equal(result.status, 400); + assert.equal(result.body.error.code, "OIDC_ALLOWED_SUBJECTS_REQUIRED"); +}); + +test("replacing the allowlist with another non-empty one is accepted", async () => { + await seedEnabled(["admin@example.com"]); + + const result = await patch({ oidcAllowedSubjects: ["other@example.com"] }); + + assert.equal(result.status, 200); + assert.deepEqual((await settingsDb.getSettings()).oidcAllowedSubjects, ["other@example.com"]); +}); + +test("the allowlist can be emptied when OIDC is off", async () => { + await settingsDb.updateSettings({ oidcEnabled: false, oidcAllowedSubjects: ["a@example.com"] }); + + const result = await patch({ oidcAllowedSubjects: [] }); + + assert.equal(result.status, 200); +}); diff --git a/tests/unit/opencode-429-pool-reselect.test.ts b/tests/unit/opencode-429-pool-reselect.test.ts index 311788a4c21b..21015e609494 100644 --- a/tests/unit/opencode-429-pool-reselect.test.ts +++ b/tests/unit/opencode-429-pool-reselect.test.ts @@ -163,6 +163,57 @@ describe("opencode 429 pool re-selection per attempt", () => { assert.strictEqual(String(observed[1]), String(ports[1])); }); + it("pool re-selection warn names the new member, not the refused one", async () => { + // next (ports[1]) differs from the ambient member (ports[2]): the warn + // must name ports[1] and must not name ports[2]. + const otherPort = ports[2]; + const warnings: string[] = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn(_ns: string, msg: string) { + warnings.push(String(msg)); + }, + error() {}, + }; + installFetch(1); + const exec = new OpencodeExecutor("opencode"); + const sink: Record = { + proxy: null, + reselectPoolMember: async () => memberB(), + }; + const result = (await runWithProxyContext( + { type: "http", host: "127.0.0.1", port: otherPort }, + () => + runWithAppliedProxyCapture(sink as never, () => + runInRequestContext(() => + exec.execute({ + model: "muse-spark-1.3-contributor-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal: null, + credentials: credentialsNoProxy(2), + log: spyLog, + }) + ) + ) + )) as { response: Response }; + assert.strictEqual(result.response.status, 200); + const poolWarns = warnings.filter((w) => w.includes("pool re-selected")); + assert.ok( + poolWarns.length > 0, + `expected a pool re-selected warn, got: ${warnings.join(" | ")}` + ); + assert.ok( + poolWarns.some((w) => new RegExp(`\\(proxy 127\\.0\\.0\\.1:${ports[1]}\\)`).test(w)), + `pool warn must name the new member, got: ${poolWarns.join(" | ")}` + ); + assert.ok( + poolWarns.every((w) => !new RegExp(`\\(proxy 127\\.0\\.0\\.1:${otherPort}\\)`).test(w)), + `pool warn must not name the refused member, got: ${poolWarns.join(" | ")}` + ); + }); + it("keeps the same member when the provider is outside the egress-bucketed list", async () => { const result = (await runWith( { reselectPoolMember: async () => memberB() }, diff --git a/tests/unit/opencode-applied-egress-key.test.ts b/tests/unit/opencode-applied-egress-key.test.ts index 453d3add588c..43b12adca8c4 100644 --- a/tests/unit/opencode-applied-egress-key.test.ts +++ b/tests/unit/opencode-applied-egress-key.test.ts @@ -92,6 +92,44 @@ test("3c. fake returning 'direct' never writes to the refusal store", () => { assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 0); }); +test("3d. a region refusal writes under the region kind, a plain retry keeps the quota kind", () => { + const tracker = throttle.createAppliedEgressTracker( + "https://opencode.ai/zen/v1/chat/completions", + () => ({ + source: "context", + proxyUrl: "http://pool-geo:8080", + }) + ); + const first = tracker.noteRefused({ proxy: null, fingerprint: "geo-a" }, true, "geo_blocked"); + assert.equal(first, 60_000); + assert.equal(memory.isProxyAvoided("http://@pool-geo:8080"), true); + assert.equal( + memory.snapshotProxySetAside("http://@pool-geo:8080", Date.now())?.kind, + "geo_blocked" + ); + const second = throttle.noteRefusedMember({ host: "h", port: 8080 }, true); + assert.equal(second, memory.REFUSAL_POLICIES.ip_quota_429.baseMs); + assert.equal( + memory.snapshotProxySetAside(memory.proxyEgressKey({ host: "h", port: 8080 }), Date.now()) + ?.kind, + "ip_quota_429" + ); +}); + +test("3e. a region refusal without the flag writes nothing", () => { + const tracker = throttle.createAppliedEgressTracker( + "https://opencode.ai/zen/v1/chat/completions", + () => ({ + source: "context", + proxyUrl: "http://pool-geo-off:8080", + }) + ); + const account = { proxy: null, fingerprint: "geo-off" }; + const written = tracker.noteRefused(account, false, "geo_blocked"); + assert.equal(written, null); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), 0); +}); + test("4. key-space guard: no '://'-shaped key in tried-sets after a pool 429", async () => { // tried-sets stay in proxyKeyOf space (host:port, null for proxyless); // the egress key only ever reaches the refusal memory. diff --git a/tests/unit/opencode-egress-label.test.ts b/tests/unit/opencode-egress-label.test.ts new file mode 100644 index 000000000000..94d1c7b9f319 --- /dev/null +++ b/tests/unit/opencode-egress-label.test.ts @@ -0,0 +1,47 @@ +import { describe, it } from "node:test"; +import assert from "node:assert"; +import { egressLabel } from "../../open-sse/executors/opencodeEgressThrottle.ts"; + +// Rotation log lines must name the egress really applied to each attempt as +// `(proxy :)`, and `(proxy direct)` only when no proxy applies. +// Host and port only: member keys carry user info and must never leak. +describe("rotation egress label", () => { + it("names a dedicated proxy host and port", () => { + assert.strictEqual( + egressLabel({ proxy: { host: "127.0.0.1", port: 8080 } }), + "(proxy 127.0.0.1:8080)" + ); + }); + + it("reports direct without a proxy and without a reader", () => { + assert.strictEqual(egressLabel({ proxy: null }), "(proxy direct)"); + }); + + it("keeps an explicit default port", () => { + assert.strictEqual(egressLabel({ proxy: { host: "h", port: 80 } }), "(proxy h:80)"); + }); + + it("names the pool member for a proxyless account", () => { + const label = egressLabel({ proxy: null, fingerprint: "fp-a" }, () => "http://@pool:8080"); + assert.strictEqual(label, "(proxy pool:8080)"); + }); + + it("never leaks user info from member keys", () => { + const label = egressLabel({ proxy: null, fingerprint: "fp-a" }, () => "http://user@host:1234"); + assert.strictEqual(label, "(proxy host:1234)"); + assert.ok(!label.includes("@") && !label.includes("//")); + }); + + it("falls back to direct when the reader throws or the key is malformed", () => { + assert.strictEqual( + egressLabel({ proxy: null, fingerprint: "fp-a" }, () => { + throw new Error("reader down"); + }), + "(proxy direct)" + ); + assert.strictEqual( + egressLabel({ proxy: null, fingerprint: "fp-a" }, () => "not a key"), + "(proxy direct)" + ); + }); +}); diff --git a/tests/unit/opencode-empty-rejection-rotation.test.ts b/tests/unit/opencode-empty-rejection-rotation.test.ts index 784a4b83a747..b04c72508206 100644 --- a/tests/unit/opencode-empty-rejection-rotation.test.ts +++ b/tests/unit/opencode-empty-rejection-rotation.test.ts @@ -172,6 +172,15 @@ describe("OpencodeExecutor empty-rejection rotation", () => { it("rotates to the next account on an empty 400 rejection (loop)", async () => { const exec = new OpencodeExecutor("opencode-zen"); installFetch([{ status: 400, body: EMPTY_BODY }, { status: 200 }]); + const warns: string[] = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn(_tag, message) { + warns.push(String(message)); + }, + error() {}, + }; const result = await exec.execute({ model: "deepseek-v4-flash-free", @@ -179,7 +188,7 @@ describe("OpencodeExecutor empty-rejection rotation", () => { stream: false, signal: null, credentials: credentialsFor([ACCOUNT_A, ACCOUNT_B]), - log, + log: spyLog, }); assert.strictEqual( @@ -196,6 +205,10 @@ describe("OpencodeExecutor empty-rejection rotation", () => { observed.some((o) => o.port === String(portB)), "rotated attempt on account B" ); + assert.ok( + warns.some((l) => new RegExp(`\\(proxy 127\\.0\\.0\\.1:${portA}\\)`).test(l)), + `empty-rejection warn must name the applied egress, got=${JSON.stringify(warns)}` + ); }); it("caps an all-empty N-account run at N attempts and propagates the last 400 intact", async () => { diff --git a/tests/unit/opencode-endpoint-unavailable-registry.test.ts b/tests/unit/opencode-endpoint-unavailable-registry.test.ts new file mode 100644 index 000000000000..d0345e986df6 --- /dev/null +++ b/tests/unit/opencode-endpoint-unavailable-registry.test.ts @@ -0,0 +1,82 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + getProviderErrorRuleMatch, + getOpencodeModelUnavailableMatch, + honorsRuleLockScope, +} from "../../open-sse/config/providerErrorRules.ts"; + +/** + * Registry-level pin of the `opencode-endpoint-unavailable` catalog rule. + * + * The end-to-end path (checkFallbackError → persistence → combo) is covered by + * `opencode-endpoint-unavailable.test.ts`; this file asserts the registry + * contract directly: marker 400/429 → model-scope lock with the rule-owned + * cooldown, any other 400/429 → null (locks nothing), helper fenced to the + * opencode family. Never imports accountFallback (registry/behavior boundary). + */ + +const MARKER = "Upstream request failed: Endpoint is unavailable."; +const BARE_MARKER = "Endpoint is unavailable"; +const FAMILY = ["opencode", "opencode-zen", "opencode-go", "opencode-cli"]; +const NON_FAMILY = ["agentrouter", "openrouter", "minimax", "unknown-vendor"]; + +test("marker 400 and 429 return the model-scope lock with the rule-owned cooldown", () => { + for (const provider of FAMILY) { + for (const status of [400, 429]) { + const match = getProviderErrorRuleMatch(provider, status, {}, { error: { message: MARKER } }); + assert.deepStrictEqual(match, { + reason: "model_capacity", + scope: "model", + cooldownMs: 300_000, + }); + } + } + // Bare envelope without the upstream prefix + canonical-case provider + // (lookup is case-insensitive). + const bareEnvelope = getProviderErrorRuleMatch("OpenCode", 429, null, BARE_MARKER); + assert.equal(bareEnvelope?.scope, "model"); + assert.equal(bareEnvelope?.reason, "model_capacity"); + assert.equal(bareEnvelope?.cooldownMs, 300_000); + const bareObject = getProviderErrorRuleMatch( + "opencode", + 400, + {}, + { error: { message: BARE_MARKER } } + ); + assert.deepStrictEqual(bareObject, { + reason: "model_capacity", + scope: "model", + cooldownMs: 300_000, + }); +}); + +test("any other 400 or 429 locks nothing", () => { + for (const body of [ + "improperly formed request: invalid message format", + "invalid api key", + "{}", + "", + ]) { + assert.equal(getProviderErrorRuleMatch("opencode", 400, {}, body), null, body); + } + // Generic 429 without the marker stays in rotation. + assert.equal(getProviderErrorRuleMatch("opencode", 429, {}, "Rate limit exceeded"), null); + // Right marker, wrong status: the rule covers 400/429 only. + assert.equal(getProviderErrorRuleMatch("opencode", 503, {}, MARKER), null); +}); + +test("helper is fenced to the opencode family and the allowlist honors it", () => { + for (const provider of FAMILY) { + assert.equal(honorsRuleLockScope(provider), true, provider); + } + assert.deepStrictEqual(getOpencodeModelUnavailableMatch("opencode", 429, null, MARKER), { + reason: "model_capacity", + scope: "model", + cooldownMs: 300_000, + }); + for (const provider of NON_FAMILY) { + assert.equal(getOpencodeModelUnavailableMatch(provider, 400, null, MARKER), null, provider); + assert.equal(getOpencodeModelUnavailableMatch(provider, 429, null, MARKER), null, provider); + } +}); diff --git a/tests/unit/opencode-endpoint-unavailable.test.ts b/tests/unit/opencode-endpoint-unavailable.test.ts new file mode 100644 index 000000000000..fb9f1557a51d --- /dev/null +++ b/tests/unit/opencode-endpoint-unavailable.test.ts @@ -0,0 +1,54 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { checkFallbackError } from "../../open-sse/services/accountFallback.ts"; +import { providerRuleRegistry } from "../../open-sse/config/providerErrorRules.ts"; + +// checkFallbackError is positional: (status, errorText, backoffLevel = 0, +// _model = null, provider = null, headers = null, profileOverride = null, +// structuredError?, …). ruleScope IS on the return type (#10334) but only for +// allowlisted providers — the cast is convenience, not necessity. +const VERBATIM_BODY = `{"type":"server_error","message":"Error from provider (Console): Upstream request failed: Endpoint is unavailable."}`; + +test("opencode 400/429 endpoint-unavailable", async (t) => { + await t.test("locks the model on the pinned verbatim (opencode, 400)", () => { + const r = checkFallbackError(400, VERBATIM_BODY, 0, null, "opencode"); + assert.equal(r.shouldFallback, true); + assert.equal((r as { ruleScope?: string }).ruleScope, "model"); + assert.equal(r.reason, "model_capacity"); + }); + + await t.test( + "locks the model on the pinned verbatim (opencode-zen, distinctly registered)", + () => { + assert.ok(providerRuleRegistry.get("opencode-zen"), "zen key registered"); + const r = checkFallbackError(429, VERBATIM_BODY, 0, null, "opencode-zen"); + assert.equal(r.shouldFallback, true); + assert.equal((r as { ruleScope?: string }).ruleScope, "model"); + assert.equal(r.reason, "model_capacity"); + } + ); + + await t.test("locks the model on the measured 429 (opencode)", () => { + const r = checkFallbackError(429, VERBATIM_BODY, 0, null, "opencode"); + assert.equal(r.shouldFallback, true); + assert.equal((r as { ruleScope?: string }).ruleScope, "model"); + assert.equal(r.reason, "model_capacity"); + }); + + await t.test("body marker wins over the exhausted-account header", () => { + const r = checkFallbackError(429, VERBATIM_BODY, 0, null, "opencode", { + "x-ratelimit-remaining-requests": "0", + }); + assert.equal(r.shouldFallback, true); + assert.equal((r as { ruleScope?: string }).ruleScope, "model"); + assert.equal(r.reason, "model_capacity"); + }); + + await t.test("same header without the marker keeps the connection scope", () => { + const r = checkFallbackError(429, "rate limit reached, slow down", 0, null, "opencode", { + "x-ratelimit-remaining-requests": "0", + }); + assert.equal(r.reason, "quota_exhausted"); + assert.equal((r as { ruleScope?: string }).ruleScope, "connection"); + }); +}); diff --git a/tests/unit/opencode-free-tier-per-model-pause.test.ts b/tests/unit/opencode-free-tier-per-model-pause.test.ts new file mode 100644 index 000000000000..693c9094689f --- /dev/null +++ b/tests/unit/opencode-free-tier-per-model-pause.test.ts @@ -0,0 +1,162 @@ +/** + * A free-tier pause covers one provider + model pair: refusing model A must + * not block sibling model B on the same provider, in selection or in the + * auto-combo candidate filter. + * + * A refusal recorded without a model keeps the previous behaviour and covers + * the whole provider. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { + noteOpencodeFreeTierSkip, + isOpencodeFreeTierSkipped, + getOpencodeFreeTierSkipRemainingMs, + clearOpencodeFreeTierSkips, +} = await import("../../open-sse/services/opencodeFreeTierSkip.ts"); +const { filterResilienceBlockedCandidates } = + await import("../../open-sse/services/autoCombo/resilienceCandidateFilter.ts"); +const { pauseCooldownIfPaused } = await import("../../src/sse/services/noAuthModelCooldown.ts"); + +const PROVIDER = "opencode"; +const MODEL_A = "nemotron-3.5-lightning-free"; +const MODEL_B = "muse-spark-1.3-contributor-free"; +const TTL_MS = 60_000; + +test.after(() => { + clearOpencodeFreeTierSkips(); +}); + +test.beforeEach(() => { + clearOpencodeFreeTierSkips(); +}); + +test("a refusal on one model leaves a sibling model on the same provider served", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, MODEL_B), false); + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + 10, MODEL_B), null); +}); + +test("a refused model stays paused until its pause expires", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, MODEL_A), true); + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + 10, MODEL_A), TTL_MS - 10); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + TTL_MS, MODEL_A), false); + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + TTL_MS, MODEL_A), null); +}); + +test("the auto-combo filter drops only the refused model candidate", () => { + noteOpencodeFreeTierSkip(PROVIDER, Date.now(), TTL_MS, MODEL_A); + const pool = [ + { provider: PROVIDER, connectionId: "noauth", model: MODEL_A }, + { provider: PROVIDER, connectionId: "noauth", model: MODEL_B }, + ]; + const filtered = filterResilienceBlockedCandidates(pool, new Map()); + assert.deepEqual( + filtered.map((candidate) => candidate.model), + [MODEL_B], + "only the refused model candidate is dropped while its pause is active" + ); +}); + +test("the free-fast pool serves the sibling model after a single refusal", () => { + noteOpencodeFreeTierSkip(PROVIDER, Date.now(), TTL_MS, MODEL_A); + const pool = [ + { provider: PROVIDER, connectionId: "noauth", model: MODEL_A }, + { provider: PROVIDER, connectionId: "noauth", model: MODEL_B }, + ]; + const filtered = filterResilienceBlockedCandidates(pool, new Map()); + assert.ok( + filtered.some((candidate) => candidate.model === MODEL_B), + "the sibling model stays eligible after a refusal on another model" + ); + assert.ok( + !filtered.some((candidate) => candidate.model === MODEL_A), + "the refused model stays out of the pool while its pause is active" + ); +}); + +test("the no-auth pause envelope fires for the refused model only", () => { + noteOpencodeFreeTierSkip(PROVIDER, Date.now(), TTL_MS, MODEL_A); + assert.ok(pauseCooldownIfPaused(PROVIDER, "noauth", MODEL_A), "refused model answers a cooldown"); + assert.equal( + pauseCooldownIfPaused(PROVIDER, "noauth", MODEL_B), + null, + "sibling model is not covered by the pause" + ); +}); + +test("a provider-wide pause still covers every model", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, MODEL_A), true); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, MODEL_B), true); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10), true); +}); + +test("a read without a model sees a model-scoped pause", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10), true); + assert.ok(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + 10) !== null); +}); + +test("a model-scoped pause expires and hands the model back", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 50, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 51, MODEL_A), false); + assert.equal(getOpencodeFreeTierSkipRemainingMs(PROVIDER, now + 51, MODEL_A), null); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 51), false); +}); + +test("a foreign provider records nothing, with or without a model", () => { + noteOpencodeFreeTierSkip("groq", 1_000_000, TTL_MS, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped("groq", 1_000_010, MODEL_A), false); + assert.equal(getOpencodeFreeTierSkipRemainingMs("groq", 1_000_010, MODEL_A), null); +}); + +test("an unknown model is served", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, "some-model-never-paused"), false); +}); + +test("model matching ignores case and surrounding whitespace", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, ` ${MODEL_A.toUpperCase()} `), true); +}); + +test("a blank or non-string model behaves like no model", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS, MODEL_A); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, " "), true); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, 42 as unknown as string), true); +}); + +test("the entry count stays bounded under many model pauses", () => { + const now = 1_000_000; + const totalWrites = 600; + for (let i = 0; i < totalWrites; i += 1) { + noteOpencodeFreeTierSkip(PROVIDER, now, TTL_MS, `model-${i}`); + } + assert.equal( + isOpencodeFreeTierSkipped(PROVIDER, now + 10, "model-0"), + false, + "the oldest entry is evicted once the bound is reached" + ); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 10, `model-${totalWrites - 1}`), true); +}); + +test("expired entries are purged before live ones are evicted", () => { + const now = 1_000_000; + noteOpencodeFreeTierSkip(PROVIDER, now, 10, "expired-model"); + for (let i = 0; i < 600; i += 1) { + noteOpencodeFreeTierSkip(PROVIDER, now + 20, TTL_MS, `live-model-${i}`); + } + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 30, "expired-model"), false); + assert.equal(isOpencodeFreeTierSkipped(PROVIDER, now + 30, "live-model-599"), true); +}); diff --git a/tests/unit/opencode-free-tier-request-contract.test.ts b/tests/unit/opencode-free-tier-request-contract.test.ts index 7ef0abc5643d..020adbdc6390 100644 --- a/tests/unit/opencode-free-tier-request-contract.test.ts +++ b/tests/unit/opencode-free-tier-request-contract.test.ts @@ -596,11 +596,15 @@ test("a borrowed set is dropped after three refusals in a row, not after one", ( .attempt; // One refusal is not proof: the same body was refused and then accepted minutes apart on // a free model (measured 2026-09-18), so a single verdict must not clear the entry. - noteFreeTierOutcome(borrow(), false); + // Only a refusal that says something about the tools counts: a bare verdict or a + // 429 leaves the entry alone. + const refused = () => + noteFreeTierOutcome(borrow(), { ok: false, status: 403, bodyText: REFUSAL_BODY }); + refused(); assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); - noteFreeTierOutcome(borrow(), false); + refused(); assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); - noteFreeTierOutcome(borrow(), false); + refused(); assert.equal(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), null); }); @@ -621,14 +625,16 @@ test("an acceptance resets the refusal streak", () => { const borrow = () => prepareFreeTierRequest(CHAT_BODY(), "openai", "zen", "opencode", "nemotron-3.5-lightning-free") .attempt; - noteFreeTierOutcome(borrow(), false); - noteFreeTierOutcome(borrow(), false); + const refused = () => + noteFreeTierOutcome(borrow(), { ok: false, status: 403, bodyText: REFUSAL_BODY }); + refused(); + refused(); noteFreeTierOutcome( prepareFreeTierRequest(withTools, "openai", "zen", "opencode", "nemotron-3.5-lightning-free") .attempt, true ); - noteFreeTierOutcome(borrow(), false); + noteFreeTierOutcome(borrow(), { ok: false, status: 403, bodyText: REFUSAL_BODY }); assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); }); @@ -832,3 +838,122 @@ test("mergeClientToolsWithObserved: leaves the body untouched when nothing obser body ); }); + +// ── Only a refusal that says something about the tools counts ────────────────── + +const REFUSAL_BODY = JSON.stringify({ + error: { + type: "FreeTierError", + message: + "Error from provider (Console): OpenCode's free tier can only be used from within OpenCode", + }, +}); + +function learnBorrowedNames(): void { + _resetToolObservationForTests(); + const learned = prepareFreeTierRequest( + { ...CHAT_BODY(), tools: [{ type: "function", function: { name: "glob" } }] }, + "openai", + "zen", + "opencode", + "nemotron-3.5-lightning-free" + ); + noteFreeTierOutcome(learned.attempt, true); +} + +function borrowedAttempt() { + return prepareFreeTierRequest( + CHAT_BODY(), + "openai", + "zen", + "opencode", + "nemotron-3.5-lightning-free" + ).attempt; +} + +test("three 429 answers on a borrowed request keep the observed names", () => { + learnBorrowedNames(); + for (let i = 0; i < 3; i++) { + noteFreeTierOutcome(borrowedAttempt(), { + ok: false, + status: 429, + bodyText: "Too many requests", + }); + } + assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); +}); + +test("three 503 answers on a borrowed request keep the observed names", () => { + learnBorrowedNames(); + for (let i = 0; i < 3; i++) { + noteFreeTierOutcome(borrowedAttempt(), { + ok: false, + status: 503, + bodyText: "Service unavailable", + }); + } + assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); +}); + +test("three 403 answers carrying the refusal keep dropping the observed names", () => { + learnBorrowedNames(); + const refused = () => + noteFreeTierOutcome(borrowedAttempt(), { ok: false, status: 403, bodyText: REFUSAL_BODY }); + refused(); + assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); + refused(); + assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); + refused(); + assert.equal(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), null); +}); + +test("a 200 on a borrowed shape clears the streak without rewriting the entry", () => { + learnBorrowedNames(); + const refused = () => + noteFreeTierOutcome(borrowedAttempt(), { ok: false, status: 403, bodyText: REFUSAL_BODY }); + refused(); + refused(); + noteFreeTierOutcome(borrowedAttempt(), { ok: true, status: 200, bodyText: null }); + noteFreeTierOutcome(borrowedAttempt(), { ok: false, status: 403, bodyText: REFUSAL_BODY }); + assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); +}); + +test("refusals that say nothing about the tools never touch the observed names", () => { + learnBorrowedNames(); + const bodies = [ + JSON.stringify({ error: { type: "FreeTierError", message: "not available in your country" } }), + JSON.stringify({ + error: { type: "FreeTierError", message: "[user_blocked] egress refused" }, + }), + JSON.stringify({ error_code: 1010, error: { type: "FreeTierError" } }), + ]; + for (const bodyText of bodies) { + for (let i = 0; i < 3; i++) { + noteFreeTierOutcome(borrowedAttempt(), { ok: false, status: 403, bodyText }); + } + } + assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); +}); + +test("a deferred note without a verdict never touches the observed names", () => { + learnBorrowedNames(); + for (let i = 0; i < 3; i++) { + noteFreeTierOutcome(borrowedAttempt(), false); + } + assert.deepEqual(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), ["glob"]); +}); + +test("a deferred 403 verdict still counts against the borrowed names", () => { + learnBorrowedNames(); + const first = borrowedAttempt(); + assert.equal(first?.borrowed, true); + for (let i = 0; i < 3; i++) { + const attempt = borrowedAttempt(); + noteFreeTierOutcome({ ...(attempt as object), probe: false } as typeof attempt, { + ok: false, + status: 403, + bodyText: REFUSAL_BODY, + }); + } + assert.equal(getObservedToolNames("opencode", "nemotron-3.5-lightning-free"), null); +}); diff --git a/tests/unit/opencode-geo-block-rotation.test.ts b/tests/unit/opencode-geo-block-rotation.test.ts index 87db382ec470..419837c80884 100644 --- a/tests/unit/opencode-geo-block-rotation.test.ts +++ b/tests/unit/opencode-geo-block-rotation.test.ts @@ -122,6 +122,15 @@ describe("OpencodeExecutor geo-block rotation", () => { { status: 403, body: GEO_BODY }, { status: 200 }, ]); + const warns: string[] = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn(_tag, message) { + warns.push(String(message)); + }, + error() {}, + }; const result = await exec.execute({ model: "muse-spark-1.3-contributor-free", @@ -129,7 +138,7 @@ describe("OpencodeExecutor geo-block rotation", () => { stream: false, signal: null, credentials: credentialsFor([FP_A, FP_B, FP_C]), - log, + log: spyLog, }); assert.strictEqual( @@ -144,6 +153,10 @@ describe("OpencodeExecutor geo-block rotation", () => { observed.includes(String(portC)), "first attempt on A (fresh cursor), then rotation over untried proxies" ); + assert.ok( + warns.some((l) => new RegExp(`\\(proxy 127\\.0\\.0\\.1:${portA}\\)`).test(l)), + `geo-block warn must name the applied egress, got=${JSON.stringify(warns)}` + ); }); it("deduplicates fingerprints sharing one proxy (one call, not two)", async () => { @@ -190,6 +203,78 @@ describe("OpencodeExecutor geo-block rotation", () => { assert.strictEqual(observed.length, 1, "no retry on non-geo 403"); }); + it("a region refusal sets the member aside for the next request", async () => { + process.env.PROXY_SKIP_RECENTLY_FAILED = "true"; + try { + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([{ status: 403, body: GEO_BODY }, { status: 200 }, { status: 200 }]); + const first = await exec.execute({ + model: "muse-spark-1.3-contributor-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal: null, + credentials: credentialsFor([FP_A, FP_B, FP_C]), + log, + }); + assert.strictEqual((first as { response: Response }).response.status, 200); + // Request 1 rotates past the refused member within the request… + assert.deepStrictEqual(observed, [String(portA), String(portB)]); + + const { proxyEgressKey, isProxyAvoided, snapshotProxySetAside } = + await import("../../open-sse/utils/proxyRefusalMemory.ts"); + const refusedKey = proxyEgressKey({ type: "http", host: "127.0.0.1", port: portA }); + assert.equal(isProxyAvoided(refusedKey), true); + assert.equal(snapshotProxySetAside(refusedKey)?.kind, "geo_blocked"); + + // …but the 200 served through B clears the memory, so request 2 starts + // from the plain cursor again (prove the write happened above instead). + const refusedB = proxyEgressKey({ type: "http", host: "127.0.0.1", port: portB }); + assert.equal(isProxyAvoided(refusedB), false); + + // A 200 served through another member leaves a refusal on a third + // member untouched: the next request avoids only that third member. + const { noteProxyRefusal } = await import("../../open-sse/utils/proxyRefusalMemory.ts"); + const thirdKey = proxyEgressKey({ type: "http", host: "127.0.0.1", port: portC }); + assert.ok(noteProxyRefusal(thirdKey, "geo_blocked") !== null); + installFetch([{ status: 200 }]); + observed = []; + const second = await exec.execute({ + model: "muse-spark-1.3-contributor-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal: null, + credentials: credentialsFor([FP_A, FP_B, FP_C]), + log, + }); + assert.strictEqual((second as { response: Response }).response.status, 200); + assert.ok(!observed.includes(String(portC)), "set-aside member avoided on next request"); + } finally { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + } + }); + + it("a region refusal with the flag off writes nothing", async () => { + process.env.PROXY_SKIP_RECENTLY_FAILED = "false"; + try { + const { __proxyRefusalMemorySizeForTesting } = + await import("../../open-sse/utils/proxyRefusalMemory.ts"); + const exec = new OpencodeExecutor("opencode-zen"); + installFetch([{ status: 403, body: GEO_BODY }, { status: 200 }]); + const result = await exec.execute({ + model: "muse-spark-1.3-contributor-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal: null, + credentials: credentialsFor([FP_A, FP_B]), + log, + }); + assert.strictEqual((result as { response: Response }).response.status, 200); + assert.equal(__proxyRefusalMemorySizeForTesting(), 0); + } finally { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + } + }); + it("propagates the last 403 after exhausting all proxies", async () => { const exec = new OpencodeExecutor("opencode-zen"); installFetch([ diff --git a/tests/unit/opencode-go-client-user-agent-15311.test.ts b/tests/unit/opencode-go-client-user-agent-15311.test.ts new file mode 100644 index 000000000000..ee3991fadab1 --- /dev/null +++ b/tests/unit/opencode-go-client-user-agent-15311.test.ts @@ -0,0 +1,146 @@ +/** + * #15311: OpenCode Go must keep a third-party coding agent's own User-Agent. + * + * OpenCode Go's client requirements (opencode.ai/docs/go, "Where can I use it?") ask a + * client to "identify itself with its own user agent, such as `my-coding-agent/1.0`, + * rather than a generic SDK or HTTP-library name" and to send a stable + * `x-opencode-session`. The Zen free-tier rewrite (#5997) replaced every UA that is not a + * versioned OpenCode CLI, so on the authenticated Go path an agent's identity became + * `opencode/` unless synthesis was switched off globally. + * + * Rules: + * R1 Go keeps an agent's own UA with synthesis unset and with synthesis on. + * R2 Go keeps the client's session unchanged across turns. + * R3 Go still replaces a generic SDK / HTTP-library UA, and fills a missing one. + * R4 Go keeps a genuine OpenCode CLI UA (unchanged behaviour). + * R5 The Zen free tier still replaces a non-CLI UA (its policy is untouched). + * R6 With synthesis off, the client UA is forwarded as before. + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts"; + +const CLI_UA_RE = /^opencode\/\d+\.\d+/; +const SESSION = "ses_00000000000000000000000000"; + +function withEnv(entries: Record, fn: () => void) { + const saved: Record = {}; + for (const [key, value] of Object.entries(entries)) { + saved[key] = process.env[key]; + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + try { + fn(); + } finally { + for (const [key, value] of Object.entries(saved)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } +} + +const CLEAN_ENV = { + OPENCODE_SYNTHESIZE_CLI_HEADERS: undefined, + OPENCODE_USER_AGENT: undefined, + OPENCODE_GO_USER_AGENT: undefined, +}; + +function goHeaders(clientHeaders: Record | null) { + const executor = new OpencodeExecutor("opencode-go"); + return executor.buildHeaders({ apiKey: "synthetic-test-key" }, true, clientHeaders, "glm-5.2"); +} + +function userAgentOf(headers: Record) { + return Object.entries(headers).find(([key]) => key.toLowerCase() === "user-agent")?.[1]; +} + +test("R1: Go keeps an agent's own User-Agent with synthesis unset", () => { + withEnv(CLEAN_ENV, () => { + const headers = goHeaders({ + "User-Agent": "my-coding-agent/1.0", + "x-opencode-session": SESSION, + }); + assert.equal(userAgentOf(headers), "my-coding-agent/1.0"); + }); +}); + +test("R1: Go keeps an agent's own User-Agent with synthesis explicitly on", () => { + withEnv({ ...CLEAN_ENV, OPENCODE_SYNTHESIZE_CLI_HEADERS: "true" }, () => { + const headers = goHeaders({ + "user-agent": "hermes-agent/2.3.1", + "x-opencode-session": SESSION, + }); + assert.equal(userAgentOf(headers), "hermes-agent/2.3.1"); + }); +}); + +test("R2: Go keeps the client's session unchanged across turns", () => { + withEnv(CLEAN_ENV, () => { + const client = { "User-Agent": "my-coding-agent/1.0", "x-opencode-session": SESSION }; + const first = goHeaders(client); + const second = goHeaders(client); + assert.equal(first["x-opencode-session"], SESSION); + assert.equal(second["x-opencode-session"], SESSION); + assert.equal(userAgentOf(second), "my-coding-agent/1.0"); + }); +}); + +test("R3: Go still replaces a generic SDK or HTTP-library User-Agent", () => { + withEnv(CLEAN_ENV, () => { + for (const generic of [ + "curl/8.5.0", + "python-requests/2.32.3", + "python-httpx/0.27.0", + "OpenAI/JS 4.67.3", + "OpenAI/Python 1.51.0", + "axios/1.7.7", + "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)", + "undici", + "Go-http-client/2.0", + "okhttp/4.12.0", + "Bun/1.4.2", + ]) { + const headers = goHeaders({ "User-Agent": generic }); + assert.match(userAgentOf(headers) ?? "", CLI_UA_RE, `${generic} must be replaced`); + } + }); +}); + +test("R3: Go fills a User-Agent when the client sends none", () => { + withEnv(CLEAN_ENV, () => { + assert.match(userAgentOf(goHeaders(null)) ?? "", CLI_UA_RE); + assert.match(userAgentOf(goHeaders({ "x-opencode-session": SESSION })) ?? "", CLI_UA_RE); + }); +}); + +test("R4: Go keeps a genuine OpenCode CLI User-Agent", () => { + withEnv(CLEAN_ENV, () => { + const headers = goHeaders({ "User-Agent": "opencode/1.18.31" }); + assert.equal(userAgentOf(headers), "opencode/1.18.31"); + }); +}); + +test("R5: the Zen free tier still replaces a non-CLI User-Agent", () => { + withEnv(CLEAN_ENV, () => { + const executor = new OpencodeExecutor("opencode"); + const headers = executor.buildHeaders( + null, + true, + { "User-Agent": "my-coding-agent/1.0" }, + "nemotron-3.5-lightning-free" + ); + assert.match(userAgentOf(headers) ?? "", CLI_UA_RE); + }); +}); + +test("R6: with synthesis off, Go forwards the client User-Agent as before", () => { + withEnv({ ...CLEAN_ENV, OPENCODE_SYNTHESIZE_CLI_HEADERS: "false" }, () => { + const headers = goHeaders({ + "User-Agent": "my-coding-agent/1.0", + "x-opencode-session": SESSION, + }); + assert.equal(userAgentOf(headers), "my-coding-agent/1.0"); + assert.equal(headers["x-opencode-session"], SESSION); + }); +}); diff --git a/tests/unit/opencode-go-gpt6luna-responses-format.test.ts b/tests/unit/opencode-go-gpt6luna-responses-format.test.ts new file mode 100644 index 000000000000..0c0125636487 --- /dev/null +++ b/tests/unit/opencode-go-gpt6luna-responses-format.test.ts @@ -0,0 +1,41 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { resolveOpencodeTargetFormat } from "../../open-sse/executors/opencode.ts"; + +// opencode-go/gpt-6-luna is served by the Go upstream ONLY on /responses. +// Live-verified 2026-10-02 against https://opencode.ai/zen/go/v1: +// POST /responses -> 200 (normal completion) +// POST /chat/completions -> 400 {"type":"error","error":{"type": +// "ModelProtocolUnsupported","message": +// "Model does not support this protocol."}} +// Same failure mode as #12196 (gpt-5.6-luna): without a registry entry +// getModelTargetFormat() returns null, resolveOpencodeTargetFormat() falls +// back to "openai", and OpencodeExecutor.buildUrl() posts to +// /chat/completions — so every request dies with that 400. +test("opencode-go/gpt-6-luna must resolve to the openai-responses target format", () => { + const resolved = resolveOpencodeTargetFormat("opencode-go", "gpt-6-luna"); + assert.equal( + resolved, + "openai-responses", + "opencode-go/gpt-6-luna resolved to '" + + resolved + + "' instead of 'openai-responses' — OpencodeExecutor.buildUrl() will post " + + "to /chat/completions, which the Go upstream rejects with " + + "ModelProtocolUnsupported" + ); +}); + +// Control: the sibling entry fixed by #12196 must keep resolving the same way, +// proving the assertion above isn't failing for an unrelated reason (broken +// import, alias resolution, or a registry load failure). +test("control: opencode-go/gpt-5.6-luna still resolves to openai-responses", () => { + assert.equal(resolveOpencodeTargetFormat("opencode-go", "gpt-5.6-luna"), "openai-responses"); +}); + +// Control: a chat-served model on the SAME provider must NOT flip to +// responses — otherwise this registry change would break the fast path that +// serves deepseek-v4.1-flash (1214 successful /chat/completions calls logged). +test("control: opencode-go/deepseek-v4.1-flash keeps its chat format", () => { + assert.equal(resolveOpencodeTargetFormat("opencode-go", "deepseek-v4.1-flash"), "openai"); +}); diff --git a/tests/unit/opencode-go-mimo-reasoning-effort-clamp.test.ts b/tests/unit/opencode-go-mimo-reasoning-effort-clamp.test.ts new file mode 100644 index 000000000000..a0158ffbbc9d --- /dev/null +++ b/tests/unit/opencode-go-mimo-reasoning-effort-clamp.test.ts @@ -0,0 +1,267 @@ +/** + * Xiaomi MiMo V2.5 / V2.6 on OpenCode gateways accept none|low|medium|high. + * `xhigh`/`max`/`ultra` clamp to `high`; `minimal` clamps to `low`. `none` + * stays `none`. opencode-go (and opencode-zen / opencode) otherwise rewrite + * every `xhigh` to `max`, and `max` then passes through because + * supportsMaxEffortForProvider() is true for the whole OpenCode family. + * The upstream 400 is the opaque "Invalid request parameters", which + * parseReasoningEffortEnum cannot learn from. + * + * Assertions are on the JSON body OpencodeExecutor hands to fetch — the + * bytes that would leave for the gateway. + */ +import assert from "node:assert/strict"; +import { describe, it, type TestContext } from "node:test"; +import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts"; +import { sanitizeReasoningEffortForProvider } from "../../open-sse/executors/base/reasoningEffort.ts"; +import { + nextProbeReasoningEffort, + parseReasoningEffortEnum, +} from "../../open-sse/services/learnedReasoningEffortCaps.ts"; + +const MESSAGES = [{ role: "user", content: "hi" }]; + +async function captureOutbound( + t: TestContext, + provider: string, + model: string, + body: Record +): Promise> { + const sent: Record[] = []; + t.mock.method(globalThis, "fetch", async (_url: unknown, init: RequestInit) => { + sent.push(JSON.parse(String(init?.body)) as Record); + return new Response(JSON.stringify({ choices: [] }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + }); + const executor = new OpencodeExecutor(provider); + const result = await executor.execute({ + model, + stream: false, + credentials: { apiKey: "test-key" }, + body, + }); + assert.equal(result.response.status, 200); + assert.equal(sent.length, 1, "sanitizer must fix the body before the first upstream fetch"); + return sent[0]; +} + +function chatBody(model: string, effort: string): Record { + return { model, messages: MESSAGES, reasoning_effort: effort }; +} + +describe("opencode-go MiMo V2.6 reasoning_effort ceiling", () => { + for (const model of ["mimo-v2.6-pro", "mimo-v2.6-flash"]) { + for (const effort of ["xhigh", "max", "ultra"]) { + it(`${model}: ${effort} is clamped to high on the outbound body`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, effort)); + assert.deepEqual(sent, { + model, + messages: MESSAGES, + reasoning_effort: "high", + }); + }); + } + + it(`${model}: minimal is clamped to low on the outbound body`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, "minimal")); + assert.deepEqual(sent, chatBody(model, "low")); + }); + + // `none` is verified accepted on both V2.6 models and must not be remapped. + for (const effort of ["none", "low", "medium", "high"]) { + it(`${model}: accepted effort ${effort} is forwarded unchanged`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, effort)); + assert.deepEqual(sent, chatBody(model, effort)); + }); + } + } + + it("rewrites every effort carrier, not only the top-level field", async (t) => { + const sent = await captureOutbound(t, "opencode-go", "mimo-v2.6-pro", { + model: "mimo-v2.6-pro", + messages: MESSAGES, + reasoning_effort: "xhigh", + reasoning: { effort: "max", summary: "auto" }, + output_config: { effort: "ultra" }, + }); + assert.deepEqual(sent, { + model: "mimo-v2.6-pro", + messages: MESSAGES, + reasoning_effort: "high", + reasoning: { effort: "high", summary: "auto" }, + output_config: { effort: "high" }, + }); + }); + + it("clamps a provider-prefixed model id after OpenCode strips the prefix", async (t) => { + const sent = await captureOutbound( + t, + "opencode-go", + "opencode-go/mimo-v2.6-pro", + chatBody("opencode-go/mimo-v2.6-pro", "xhigh") + ); + assert.deepEqual(sent, { + model: "mimo-v2.6-pro", + messages: MESSAGES, + reasoning_effort: "high", + }); + }); +}); + +describe("MiMo V2.5 flat effort uses the same ceiling", () => { + for (const model of ["mimo-v2.5", "mimo-v2.5-pro"]) { + for (const effort of ["xhigh", "max", "ultra"]) { + it(`${model}: ${effort} is clamped to high`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, effort)); + assert.deepEqual(sent, chatBody(model, "high")); + }); + } + + it(`${model}: high stays high`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, "high")); + assert.deepEqual(sent, chatBody(model, "high")); + }); + + it(`${model}: minimal is clamped to low`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, "minimal")); + assert.deepEqual(sent, chatBody(model, "low")); + }); + + it(`${model}: none stays none`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, "none")); + assert.deepEqual(sent, chatBody(model, "none")); + }); + } + + it("mimo-v2.5-max alias is still forwarded verbatim with no injected effort", async (t) => { + const sent = await captureOutbound(t, "opencode-go", "mimo-v2.5-max", { + model: "mimo-v2.5-max", + messages: MESSAGES, + }); + assert.deepEqual(sent, { + model: "mimo-v2.5-max", + messages: MESSAGES, + }); + assert.equal(Object.hasOwn(sent, "reasoning_effort"), false); + }); +}); + +describe("OpenCode provider family shares the clamp", () => { + it("opencode-zen + mimo-v2.6-pro clamps xhigh to high", async (t) => { + const sent = await captureOutbound( + t, + "opencode-zen", + "mimo-v2.6-pro", + chatBody("mimo-v2.6-pro", "xhigh") + ); + assert.deepEqual(sent, chatBody("mimo-v2.6-pro", "high")); + }); + + it("opencode (noauth) + mimo-v2.5-free clamps max to high on the wire", async (t) => { + const sent = await captureOutbound( + t, + "opencode", + "mimo-v2.5-free", + chatBody("mimo-v2.5-free", "max") + ); + // The free-tier contract also forces stream:true and a placeholder tool. + // Those fields are unrelated to the effort clamp; the effort itself is high. + assert.equal(sent.model, "mimo-v2.5-free"); + assert.equal(sent.reasoning_effort, "high"); + assert.equal(sent.stream, true); + assert.ok(Array.isArray(sent.tools)); + assert.deepEqual(Object.keys(sent).sort(), [ + "messages", + "model", + "reasoning_effort", + "stream", + "tools", + ]); + }); + + it("opencode_go alias is in the same provider family", () => { + const out = sanitizeReasoningEffortForProvider( + chatBody("mimo-v2.6-flash", "xhigh"), + "opencode_go", + "mimo-v2.6-flash" + ); + assert.deepEqual(out, chatBody("mimo-v2.6-flash", "high")); + }); +}); + +describe("models that accept max on opencode-go still receive max", () => { + // Registry: open-sse/config/providers/registry/opencode/go/index.ts + // deepseek-v4-pro supportedThinkingEfforts: ["none", "low", "high", "max"] + // EFFORT_TIERS in open-sse/executors/opencode.ts lists the same vocabulary. + // The id does not match MIMO_V25_V26_PATTERN, so the OpenCode xhigh→max + // rewrite must keep running for it. + for (const effort of ["xhigh", "max"]) { + it(`deepseek-v4-pro keeps max when the client sends ${effort}`, async (t) => { + const sent = await captureOutbound( + t, + "opencode-go", + "deepseek-v4-pro", + chatBody("deepseek-v4-pro", effort) + ); + assert.deepEqual(sent, chatBody("deepseek-v4-pro", "max")); + }); + } + + // Same registry file: qwen3.7-plus supportedThinkingEfforts: ["high", "max"]. + // No family pattern rewrites this id; max must pass through untouched. + it("qwen3.7-plus keeps max", async (t) => { + const sent = await captureOutbound( + t, + "opencode-go", + "qwen3.7-plus", + chatBody("qwen3.7-plus", "max") + ); + assert.deepEqual(sent, chatBody("qwen3.7-plus", "max")); + }); +}); + +describe("the MiMo pattern does not swallow neighbouring ids", () => { + for (const model of ["mimo-v2-flash", "mimo-v2.7-pro"]) { + it(`${model} still takes the OpenCode xhigh→max rewrite`, async (t) => { + const sent = await captureOutbound(t, "opencode-go", model, chatBody(model, "xhigh")); + assert.deepEqual(sent, chatBody(model, "max")); + }); + } +}); + +describe("the clamp is not applied outside the OpenCode provider family", () => { + it("openrouter keeps xhigh (its API expects that tier)", () => { + const body = chatBody("xiaomi/mimo-v2.6-pro", "xhigh"); + const out = sanitizeReasoningEffortForProvider(body, "openrouter", "xiaomi/mimo-v2.6-pro"); + assert.equal((out as Record).reasoning_effort, "xhigh"); + }); + + it("command-code keeps max (native tier on that validator)", () => { + const body = chatBody("mimo-v2.5-pro", "max"); + const out = sanitizeReasoningEffortForProvider(body, "command-code", "mimo-v2.5-pro"); + assert.equal((out as Record).reasoning_effort, "max"); + }); +}); + +describe("opaque MiMo 400 cannot be learned", () => { + it("parseReasoningEffortEnum returns null for the Xiaomi error text", () => { + assert.equal(parseReasoningEffortEnum("[400] Invalid request parameters"), null); + assert.equal( + parseReasoningEffortEnum( + "bad_request: opencode-go/mimo-v2.6-pro: model — [400] Invalid request parameters" + ), + null + ); + assert.equal( + parseReasoningEffortEnum("Streaming response failed: [400] Invalid request parameters"), + null + ); + }); + + it("the one-step opaque probe would move max to xhigh, which MiMo also rejects", () => { + assert.equal(nextProbeReasoningEffort("max"), "xhigh"); + assert.equal(nextProbeReasoningEffort("xhigh"), "high"); + }); +}); diff --git a/tests/unit/opencode-headers-wait-rotation.test.ts b/tests/unit/opencode-headers-wait-rotation.test.ts index ef075d3dda6d..e2fc81fa45be 100644 --- a/tests/unit/opencode-headers-wait-rotation.test.ts +++ b/tests/unit/opencode-headers-wait-rotation.test.ts @@ -331,14 +331,20 @@ describe("OpencodeExecutor headers-wait rotation (seam)", () => { }) as typeof globalThis.fetch; } - function run(exec: OpencodeExecutor, model: string, creds: ProviderCredentials, stream = true) { + function run( + exec: OpencodeExecutor, + model: string, + creds: ProviderCredentials, + stream = true, + log: { debug(): void; info(): void; warn(): void; error(): void } = HW_LOG + ) { return exec.execute({ model, body: { input: [{ role: "user", content: "hi" }], stream }, stream, signal: null, credentials: creds, - log: HW_LOG, + log, }) as Promise<{ response: Response }>; } @@ -348,10 +354,23 @@ describe("OpencodeExecutor headers-wait rotation (seam)", () => { async () => { const exec = new OpencodeExecutor("opencode-zen"); installHangThenOk(); - const result = await run(exec, HW_RESPONSES_MODEL, hwProxiedCredentials(2)); + const warns: string[] = []; + const spyLog = { + debug() {}, + info() {}, + warn(_tag: unknown, message: string) { + warns.push(String(message)); + }, + error() {}, + }; + const result = await run(exec, HW_RESPONSES_MODEL, hwProxiedCredentials(2), true, spyLog); assert.equal(result.response.status, 200); assert.deepEqual(calls, [String(hwPorts[0]), String(hwPorts[1])]); await result.response.body?.cancel(); + assert.ok( + warns.some((l) => new RegExp(`\\(proxy 127\\.0\\.0\\.1:${hwPorts[0]}\\)`).test(l)), + `headers-wait warn must name the applied egress, got=${JSON.stringify(warns)}` + ); } ); diff --git a/tests/unit/opencode-proxy-rotation-4954.test.ts b/tests/unit/opencode-proxy-rotation-4954.test.ts index c2f1484d3d66..853d39a7ac7c 100644 --- a/tests/unit/opencode-proxy-rotation-4954.test.ts +++ b/tests/unit/opencode-proxy-rotation-4954.test.ts @@ -7,6 +7,7 @@ import { resolveProxyForRequest, runWithAppliedProxyCapture, } from "../../open-sse/utils/proxyFetch.ts"; +import { __resetProxyRefusalMemoryForTesting } from "../../open-sse/utils/proxyRefusalMemory.ts"; /** * #4954 — "OpenCode Free" exposes per-account proxy + multi-account rotation in @@ -82,6 +83,7 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { let observed: Array<{ source: string; host: string | null; port: string | null }>; beforeEach(() => { + __resetProxyRefusalMemoryForTesting(); originalFetch = globalThis.fetch; observed = []; }); @@ -198,6 +200,16 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { }); }) as typeof globalThis.fetch; + const warnCalls: Array<{ tag: unknown; msg: string }> = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn: (tag, msg) => { + warnCalls.push({ tag, msg }); + }, + error() {}, + }; + try { const result = await exec.execute({ model: "deepseek-v4-flash-free", @@ -205,7 +217,7 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { stream: false, signal: null, credentials: credentialsWithProxies(), - log, + log: spyLog, }); assert.strictEqual( @@ -219,6 +231,14 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { observed[1].port, "rotation must switch to a different account/proxy after a throw" ); + assert.ok( + warnCalls.some((c) => + new RegExp( + `network error on account .*, rotating to next… .*\\(proxy 127\\.0\\.0\\.1:(${portA}|${portB})\\)` + ).test(c.msg) + ), + `network throw warn must name the applied egress, got=${JSON.stringify(warnCalls)}` + ); } finally { globalThis.fetch = originalFetchForThrow; } @@ -261,6 +281,14 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { warnCalls.some((c) => c.tag === "OPENCODE" && /network error/i.test(c.msg)), `expected a warn-level "network error" log; got=${JSON.stringify(warnCalls)}` ); + assert.ok( + warnCalls.some((c) => + new RegExp( + `network error on account .*, rotating to next… .*\\(proxy 127\\.0\\.0\\.1:(${portA}|${portB})\\)` + ).test(c.msg) + ), + `network error warn must name the applied egress, got=${JSON.stringify(warnCalls)}` + ); } finally { globalThis.fetch = originalFetchForThrow; } @@ -292,6 +320,16 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { }); }) as typeof globalThis.fetch; + const warnCalls: Array<{ tag: unknown; msg: string }> = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn: (tag, msg) => { + warnCalls.push({ tag, msg }); + }, + error() {}, + }; + try { // ACCOUNT_A has no proxy, ACCOUNT_B does — credentialsWithProxies(true) // only configures a proxy for accounts present in accountProxies; give @@ -308,7 +346,7 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { stream: false, signal: null, credentials, - log, + log: spyLog, }); assert.strictEqual( @@ -317,6 +355,10 @@ describe("OpencodeExecutor per-account proxy + rotation (#4954)", () => { "the proxied account (B) must still be tried and must succeed the request" ); assert.strictEqual(call, 2, "exactly one throw (A) then one success (B)"); + assert.ok( + warnCalls.some((c) => /\(proxy (direct|127\.0\.0\.1:\d+)\)/.test(c.msg)), + `shared-egress warn must carry an egress label, got=${JSON.stringify(warnCalls)}` + ); } finally { globalThis.fetch = originalFetchForThrow; } diff --git a/tests/unit/opencode-responses-first-byte-stall.test.ts b/tests/unit/opencode-responses-first-byte-stall.test.ts index 72d90a42dea5..5c397d4e6f8f 100644 --- a/tests/unit/opencode-responses-first-byte-stall.test.ts +++ b/tests/unit/opencode-responses-first-byte-stall.test.ts @@ -145,7 +145,8 @@ describe("OpencodeExecutor Responses first-byte stall", () => { model: string, creds: ProviderCredentials, stream = true, - signal: AbortSignal | null = null + signal: AbortSignal | null = null, + spyLog: ExecutorLog = log ) { return exec.execute({ model, @@ -153,7 +154,7 @@ describe("OpencodeExecutor Responses first-byte stall", () => { stream, signal, credentials: creds, - log, + log: spyLog, }) as Promise<{ response: Response }>; } @@ -174,11 +175,24 @@ describe("OpencodeExecutor Responses first-byte stall", () => { it("rotates past a silent Responses stream to a healthy account", { timeout: 5000 }, async () => { const exec = new OpencodeExecutor("opencode-zen"); installFetch(["stall", "ok"]); - const result = await run(exec, RESPONSES_MODEL, proxiedCredentials(2)); + const warns: string[] = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn(_tag, message) { + warns.push(String(message)); + }, + error() {}, + }; + const result = await run(exec, RESPONSES_MODEL, proxiedCredentials(2), true, null, spyLog); assert.equal(result.response.status, 200); assert.deepEqual(calls, [String(ports[0]), String(ports[1])]); assert.deepEqual(cooledDown(exec), [FPS[0]]); await result.response.body?.cancel(); + assert.ok( + warns.some((l) => new RegExp(`\\(proxy 127\\.0\\.0\\.1:${ports[0]}\\)`).test(l)), + `stall warn must name the applied egress, got=${JSON.stringify(warns)}` + ); }); it( diff --git a/tests/unit/opencode-responses-stall-measure.test.ts b/tests/unit/opencode-responses-stall-measure.test.ts new file mode 100644 index 000000000000..4749f7985f0f --- /dev/null +++ b/tests/unit/opencode-responses-stall-measure.test.ts @@ -0,0 +1,123 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { noteStallShadow } from "../../open-sse/executors/opencodeResponsesStall.ts"; + +function silentBody(): ReadableStream { + return new ReadableStream({ pull() {} }); +} + +function talkingBody(): ReadableStream { + const text = + 'event: response.created\ndata: {"type":"response.created","response":{"id":"r1"}}\n\n'; + return new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(text)); + controller.close(); + }, + }); +} + +function watchedResult(body: ReadableStream): { + response: Response; +} { + return { response: new Response(body, { status: 200 }) }; +} + +async function settle(): Promise { + await new Promise((resolve) => setTimeout(resolve, 60)); +} + +describe("silent stream shadow measurement", () => { + it("shadow counts a silent streamed reply while the guard stays off", async () => { + const seen: string[] = []; + const result = watchedResult(silentBody()); + const returned = noteStallShadow(result, { + stream: true, + requestFormat: "openai-responses", + windowMs: 0, + log: { warn: (_tag, message) => seen.push(message) }, + cid: "", + readBound: () => 80_000, + readConfigured: () => 30, + }); + assert.equal(returned, result); + await settle(); + assert.equal(seen.length, 1); + assert.match(seen[0] as string, /silent streamed reply past 30ms/); + assert.match(seen[0] as string, /shadow count 1/); + await result.response.body?.cancel().catch(() => {}); + }); + + it("shadow stays silent on a talking body", async () => { + const seen: string[] = []; + const result = watchedResult(talkingBody()); + noteStallShadow(result, { + stream: true, + requestFormat: "openai-responses", + windowMs: 0, + log: { warn: (_tag, message) => seen.push(message) }, + readBound: () => 80_000, + readConfigured: () => 30, + }); + await settle(); + assert.deepEqual(seen, []); + const text = await result.response.text(); + assert.match(text, /response\.created/); + }); + + it("shadow ignores non-streamed and non-responses requests", async () => { + const seen: string[] = []; + noteStallShadow(watchedResult(silentBody()), { + stream: false, + requestFormat: "openai-responses", + windowMs: 0, + log: { warn: (_tag, message) => seen.push(message) }, + readBound: () => 80_000, + readConfigured: () => 30, + }); + noteStallShadow(watchedResult(silentBody()), { + stream: true, + requestFormat: "openai-chat", + windowMs: 0, + log: { warn: (_tag, message) => seen.push(message) }, + readBound: () => 80_000, + readConfigured: () => 30, + }); + await settle(); + assert.deepEqual(seen, []); + }); + + it("shadow stays off while the guard is armed", async () => { + const seen: string[] = []; + const result = watchedResult(silentBody()); + noteStallShadow(result, { + stream: true, + requestFormat: "openai-responses", + windowMs: 50, + log: { warn: (_tag, message) => seen.push(message) }, + readBound: () => 80_000, + readConfigured: () => 50, + }); + await settle(); + assert.deepEqual(seen, []); + await result.response.body?.cancel().catch(() => {}); + }); + + it("shadow storage stays constant after several requests", async () => { + const seen: string[] = []; + for (let i = 0; i < 5; i++) { + const result = watchedResult(silentBody()); + noteStallShadow(result, { + stream: true, + requestFormat: "openai-responses", + windowMs: 0, + log: { warn: (_tag, message) => seen.push(message) }, + readBound: () => 80_000, + readConfigured: () => 20, + }); + await result.response.body?.cancel().catch(() => {}); + } + await settle(); + assert.equal(seen.length, 5); + }); +}); diff --git a/tests/unit/opencode-shape-replay-insufficient-quota.test.ts b/tests/unit/opencode-shape-replay-insufficient-quota.test.ts new file mode 100644 index 000000000000..6971db9b46af --- /dev/null +++ b/tests/unit/opencode-shape-replay-insufficient-quota.test.ts @@ -0,0 +1,181 @@ +/** + * A 403 or 451 carrying `insufficient_quota` on one tool shape is replayed once + * in the other shape; only when both shapes fail does the credits verdict stand. + */ +import { test, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { OpencodeExecutor } from "../../open-sse/executors/opencode.ts"; +import type { ProviderCredentials } from "../../open-sse/executors/base.ts"; +import { resetDbInstance } from "../../src/lib/db/core.ts"; +import { + classifyProviderError, + PROVIDER_ERROR_TYPES, +} from "../../open-sse/services/errorClassifier.ts"; +import { + _resetShapeMemoForTests, + _setShapeClockForTests, +} from "../../open-sse/executors/opencodeRequestShape.ts"; +import { _resetToolObservationForTests } from "../../open-sse/executors/opencodeToolObservation.ts"; + +const MODEL = "nemotron-3.5-lightning-free"; +const TITLE_PROMPT = + "You are a title generator. You output ONLY a thread title.\n- Never use tools\n- Keep it short"; +const QUOTA_BODY = JSON.stringify({ + error: { type: "insufficient_quota", message: "insufficient_quota" }, +}); +const SSE_OK = + 'data: {"id":"gen-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"ok"},"finish_reason":null}]}\n\n' + + 'data: {"id":"gen-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n' + + "data: [DONE]\n\n"; + +type Body = Record; + +function toolCount(body: Body): number { + return Array.isArray(body.tools) ? body.tools.length : 0; +} + +const originalFetch = globalThis.fetch; +let bodies: Body[] = []; + +function quotaRefusal(status: number): Response { + return new Response(QUOTA_BODY, { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +function answerOk(): Response { + return new Response(SSE_OK, { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + }); +} + +/** First call refused with the quota token on the tools shape, second call accepted bare. */ +function installQuotaThenOk(status: number): void { + bodies = []; + globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { + const body = JSON.parse(String(init?.body ?? "{}")) as Body; + bodies.push(body); + if (bodies.length === 1) return quotaRefusal(status); + return answerOk(); + }) as typeof globalThis.fetch; +} + +/** Every call refused with the quota token, whatever the shape. */ +function installQuotaAlways(status: number): void { + bodies = []; + globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { + bodies.push(JSON.parse(String(init?.body ?? "{}")) as Body); + return quotaRefusal(status); + }) as typeof globalThis.fetch; +} + +beforeEach(() => { + _resetShapeMemoForTests(); + _resetToolObservationForTests(); + _setShapeClockForTests(() => Date.now()); +}); + +afterEach(() => { + globalThis.fetch = originalFetch; + _setShapeClockForTests(() => Date.now()); + resetDbInstance(); +}); + +const titleBody = (): Body => ({ + model: MODEL, + messages: [ + { role: "system", content: TITLE_PROMPT }, + { role: "user", content: "hello" }, + ], +}); + +async function run(body: Body): Promise { + const executor = new OpencodeExecutor("opencode-zen"); + const credentials: ProviderCredentials = { + apiKey: "k", + accessToken: null, + connectionId: "c", + }; + const result = (await executor.execute({ + model: String(body.model), + body, + stream: true, + signal: null, + credentials, + log: { debug() {}, info() {}, warn() {}, error() {} }, + })) as { response: Response }; + return result.response; +} + +for (const status of [403, 451]) { + test(`a ${status} carrying the quota token on the tools shape is replayed bare and accepted`, async () => { + installQuotaThenOk(status); + const response = await run(titleBody()); + assert.equal(response.status, 200); + assert.equal(bodies.length, 2); + assert.ok(toolCount(bodies[0]) > 0); + assert.equal(toolCount(bodies[1]), 0); + }); +} + +test("when both shapes carry the quota token the refusal is returned and stays a credits verdict", async () => { + installQuotaAlways(403); + const response = await run(titleBody()); + assert.equal(response.status, 403); + assert.equal(bodies.length, 2); + assert.equal( + classifyProviderError(403, QUOTA_BODY, "opencode-zen"), + PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED + ); + assert.equal( + classifyProviderError(403, QUOTA_BODY, "openai"), + PROVIDER_ERROR_TYPES.QUOTA_EXHAUSTED + ); +}); + +test("a 403 without the exact token is not replayed", async () => { + bodies = []; + globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { + bodies.push(JSON.parse(String(init?.body ?? "{}")) as Body); + return new Response(JSON.stringify({ error: { message: "insufficient quota" } }), { + status: 403, + headers: { "Content-Type": "application/json" }, + }); + }) as typeof globalThis.fetch; + const response = await run(titleBody()); + assert.equal(response.status, 403); + assert.equal(bodies.length, 1); +}); + +test("a plain-text 403 carrying the exact token is replayed", async () => { + bodies = []; + globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { + bodies.push(JSON.parse(String(init?.body ?? "{}")) as Body); + if (bodies.length === 1) { + return new Response("insufficient_quota", { + status: 403, + headers: { "Content-Type": "text/plain" }, + }); + } + return answerOk(); + }) as typeof globalThis.fetch; + const response = await run(titleBody()); + assert.equal(response.status, 200); + assert.equal(bodies.length, 2); +}); + +test("a 403 carrying the quota token plus a user_blocked marker is not replayed", async () => { + bodies = []; + globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { + bodies.push(JSON.parse(String(init?.body ?? "{}")) as Body); + return new Response( + JSON.stringify({ error: { message: "insufficient_quota [user_blocked] egress refused" } }), + { status: 403, headers: { "Content-Type": "application/json" } } + ); + }) as typeof globalThis.fetch; + const response = await run(titleBody()); + assert.equal(response.status, 403); + assert.equal(bodies.length, 1); +}); diff --git a/tests/unit/opencode-transient-rotation.test.ts b/tests/unit/opencode-transient-rotation.test.ts index d2c0b5a023cd..a5c8808baef9 100644 --- a/tests/unit/opencode-transient-rotation.test.ts +++ b/tests/unit/opencode-transient-rotation.test.ts @@ -107,6 +107,15 @@ describe("OpencodeExecutor transient-failure rotation", () => { it("rotates past a 500 to the healthy proxy without cooldown", async () => { const exec = new OpencodeExecutor("opencode-zen"); installFetch([{ status: 500 }, { status: 200 }]); + const warns: string[] = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn(_tag, message) { + warns.push(String(message)); + }, + error() {}, + }; const result = await exec.execute({ model: "muse-spark-1.3-contributor-free", @@ -114,12 +123,20 @@ describe("OpencodeExecutor transient-failure rotation", () => { stream: false, signal: null, credentials: credentialsFor([FP_A, FP_B]), - log, + log: spyLog, }); assert.strictEqual((result as { response: Response }).response.status, 200); assert.strictEqual(observed.length, 2); assert.strictEqual(observed[0], String(portA)); + assert.ok( + warns.some((l) => new RegExp(`\\(proxy 127\\.0\\.0\\.1:${portA}\\)`).test(l)), + `transient 500 warn must name the applied egress, got=${JSON.stringify(warns)}` + ); + assert.ok( + warns.every((l) => !l.includes("@") || l.includes("connectionId=@")), + "rotation warns must never leak user info" + ); assert.strictEqual( CloneCountingResponse.clones, 1, @@ -506,7 +523,7 @@ describe("OpencodeExecutor transient-failure rotation", () => { } assert.ok( plainRotation.some((l) => - /transient upstream 500 on account .* \(proxy .*\), rotating to next…/.test(l) + /transient upstream 500 on account .*, rotating to next… \(proxy .*\)/.test(l) ), "existing 5xx rotation motif byte-identical when no id is present" ); diff --git a/tests/unit/opencode-user-blocked-rotation.test.ts b/tests/unit/opencode-user-blocked-rotation.test.ts index ac428b00cf77..95b24606d919 100644 --- a/tests/unit/opencode-user-blocked-rotation.test.ts +++ b/tests/unit/opencode-user-blocked-rotation.test.ts @@ -151,8 +151,25 @@ describe("OpencodeExecutor user_blocked refusal (OPENCODE_USER_BLOCKED_ROTATION) it(`${status} user_blocked rotates once to a healthy account and cools the refused one`, async () => { const exec = new OpencodeExecutor("opencode-zen"); installFetch([{ status, body: BLOCKED_BODY }, { status: 200 }]); - - const response = await run(exec, credentialsFor(3)); + const warns: string[] = []; + const spyLog: ExecutorLog = { + debug() {}, + info() {}, + warn(_tag, message) { + warns.push(String(message)); + }, + error() {}, + }; + + const execResult = (await exec.execute({ + model: "muse-spark-1.3-contributor-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal: null, + credentials: credentialsFor(3), + log: spyLog, + })) as { response: Response }; + const response = execResult.response; assert.strictEqual(response.status, 200); assert.strictEqual(observed.length, 2); @@ -165,6 +182,14 @@ describe("OpencodeExecutor user_blocked refusal (OPENCODE_USER_BLOCKED_ROTATION) assert.strictEqual(upstream[0].bodyUsed, true, "the abandoned refusal body is cancelled"); assert.strictEqual(response.bodyUsed, false, "the served body is untouched"); await response.body?.cancel(); + assert.ok( + warns.some((l) => + new RegExp( + `user_blocked ${status} on account .*, rotating to next account once… \\(proxy 127\\.0\\.0\\.1:${ports[0]}\\)` + ).test(l) + ), + `user_blocked warn must carry the egress suffix, got=${JSON.stringify(warns)}` + ); }); } diff --git a/tests/unit/openrouter-credits-exhausted-catalog-access.test.ts b/tests/unit/openrouter-credits-exhausted-catalog-access.test.ts new file mode 100644 index 000000000000..f2f82250efde --- /dev/null +++ b/tests/unit/openrouter-credits-exhausted-catalog-access.test.ts @@ -0,0 +1,76 @@ +/** + * An OpenRouter connection locked as "credits_exhausted" must keep serving + * only the free models the catalog documents as free-access, and must stop + * serving any other `:free`-suffixed id. The `:free` suffix alone is not + * proof of free access once the paid balance is exhausted. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-openrouter-catalog-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const auth = await import("../../src/sse/services/auth.ts"); + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("a credits_exhausted OpenRouter connection stops serving a :free model outside the free-access catalog", async () => { + await resetStorage(); + + await providersDb.createProviderConnection({ + provider: "openrouter", + authType: "apikey", + apiKey: "sk-or-exhausted-uncatalogued", + isActive: true, + testStatus: "credits_exhausted", + }); + + const selected = await auth.getProviderCredentials( + "openrouter", + null, + null, + "meta-llama/llama-3.1-8b-instruct:free" + ); + + assert.deepEqual( + selected, + { allExpired: true, expiredCount: 1, expiredStatus: "credits_exhausted" }, + "a :free model without catalogued free access must be excluded on the exhausted connection" + ); +}); + +test("a credits_exhausted OpenRouter connection keeps serving a catalogued free-access model", async () => { + await resetStorage(); + + const conn = await providersDb.createProviderConnection({ + provider: "openrouter", + authType: "apikey", + apiKey: "sk-or-exhausted-catalogued", + isActive: true, + testStatus: "credits_exhausted", + }); + + const selected = await auth.getProviderCredentials( + "openrouter", + null, + null, + "liquid/lfm-2.5-2.6b:free" + ); + + assert.ok(selected, "a catalogued free-access model must still be served"); + assert.equal(selected.connectionId, conn.id); +}); diff --git a/tests/unit/openrouter-free-model-credits-exhausted.test.ts b/tests/unit/openrouter-free-model-credits-exhausted.test.ts index 6a5664659407..81874b5f73fc 100644 --- a/tests/unit/openrouter-free-model-credits-exhausted.test.ts +++ b/tests/unit/openrouter-free-model-credits-exhausted.test.ts @@ -1,18 +1,15 @@ /** * A 402 from a PAID OpenRouter model locks the whole connection as - * "credits_exhausted" (openrouter-quota-6842.test.ts confirms this is - * intentional connection-scoped behavior for OpenRouter's shared account - * balance). But OpenRouter's `:free` models are not gated by that same - * balance, so once one paid-model call trips the lock, every `:free` model - * combo target on that same connection is also skipped for the full 1h - * cooldown — even though the free models never touched the exhausted - * credits. This defeats combo failover to free models, which is the whole - * point of configuring them. + * "credits_exhausted" (intentional connection-scoped behavior for + * OpenRouter's shared account balance). Only the free models the shipped + * catalog documents as free-access keep being served from such a + * connection, so combo failover to free models keeps working while any + * other `:free`-suffixed id stays excluded on the exhausted balance. * - * getProviderCredentials must keep serving `:free` model requests from a - * connection whose ONLY problem is credits_exhausted, while still refusing - * paid-model requests (and still refusing free-model requests on a - * connection that's terminal for another reason, e.g. banned). + * getProviderCredentials serves only catalogued free-access model requests + * from a connection whose ONLY problem is credits_exhausted, while still + * refusing paid-model requests, uncatalogued `:free` ids, and free-model + * requests on a connection that's terminal for another reason, e.g. banned. */ import test from "node:test"; import assert from "node:assert/strict"; @@ -38,10 +35,10 @@ test.after(() => { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); -test("getProviderCredentials still serves a :free OpenRouter model after the connection is credits_exhausted", async () => { +test("getProviderCredentials stops serving an uncatalogued :free OpenRouter model after the connection is credits_exhausted", async () => { await resetStorage(); - const conn = await providersDb.createProviderConnection({ + await providersDb.createProviderConnection({ provider: "openrouter", authType: "apikey", apiKey: "sk-or-exhausted", @@ -56,8 +53,11 @@ test("getProviderCredentials still serves a :free OpenRouter model after the con "meta-llama/llama-3.1-8b-instruct:free" ); - assert.ok(selected, "a credits_exhausted OpenRouter connection must still serve :free models"); - assert.equal(selected.connectionId, conn.id); + assert.deepEqual( + selected, + { allExpired: true, expiredCount: 1, expiredStatus: "credits_exhausted" }, + "a :free model without catalogued free access must be excluded on the exhausted connection" + ); }); test("getProviderCredentials still refuses a PAID OpenRouter model on a credits_exhausted connection", async () => { diff --git a/tests/unit/openrouter-midstream-error-chunk.test.ts b/tests/unit/openrouter-midstream-error-chunk.test.ts index 98f2bd960c15..790aa6cf385b 100644 --- a/tests/unit/openrouter-midstream-error-chunk.test.ts +++ b/tests/unit/openrouter-midstream-error-chunk.test.ts @@ -47,8 +47,8 @@ test("OpenRouter mid-stream 502 provider_unavailable is surfaced as upstreamErro // End of stream (chunk === null) flushes the deferred completion. const flushEvents = openaiToOpenAIResponsesResponse(null, state); - const completedEvent = flushEvents.find((e) => e.event === "response.completed"); - assert.ok(completedEvent, "should have a response.completed event"); + const completedEvent = flushEvents.find((e) => e.event === "response.failed"); + assert.ok(completedEvent, "should have a response.failed event"); assert.equal( completedEvent.data.response.status, "failed", @@ -78,7 +78,7 @@ test("OpenRouter mid-stream error with a rate-limit code maps to a 429 upstreamE ); const flushEvents = openaiToOpenAIResponsesResponse(null, state); - const completedEvent = flushEvents.find((e) => e.event === "response.completed"); + const completedEvent = flushEvents.find((e) => e.event === "response.failed"); assert.ok(completedEvent); assert.equal(completedEvent.data.response.status, "failed"); assert.equal(completedEvent.data.response.error.code, "429"); diff --git a/tests/unit/perplexity-agent-provider.test.ts b/tests/unit/perplexity-agent-provider.test.ts index 33237a0d5e5c..6245a76720b6 100644 --- a/tests/unit/perplexity-agent-provider.test.ts +++ b/tests/unit/perplexity-agent-provider.test.ts @@ -174,6 +174,15 @@ test("perplexity-agent default executor dispatches to Perplexity Responses endpo assert.equal(executor.buildUrl("openai/gpt-5.6-sol", false, 0, null), AGENT_RESPONSES_URL); }); +test("Perplexity API executors send the OmniRoute integration header", () => { + for (const provider of ["perplexity", "perplexity-agent"]) { + const headers = new DefaultExecutor(provider).buildHeaders({ apiKey: "pplx-test" }, false); + + assert.equal(headers["X-Pplx-Integration"], "omniroute", provider); + assert.equal(headers.Authorization, "Bearer pplx-test", provider); + } +}); + test("perplexity-agent defaults max_output_tokens when Agent requests omit token fields", () => { const executor = new DefaultExecutor("perplexity-agent"); const explicit = executor.transformRequest( diff --git a/tests/unit/plugins-fs-safety.test.ts b/tests/unit/plugins-fs-safety.test.ts index f574f1597151..04ac4a8047e7 100644 --- a/tests/unit/plugins-fs-safety.test.ts +++ b/tests/unit/plugins-fs-safety.test.ts @@ -24,6 +24,9 @@ import { resolve as pathResolve } from "node:path"; // ── Temp DB — must be set BEFORE any DB-touching import ────────────────────── const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-plugins-fs-safety-")); process.env.DATA_DIR = TEST_DATA_DIR; +// The manager singleton resolves its install root once, at import. Pin it under the temp +// DATA_DIR so installs, the cleanup below, and the staging-residue assertions share it. +process.env.OMNIROUTE_PLUGINS_DIR = path.join(TEST_DATA_DIR, "plugins"); const core = await import("../../src/lib/plugins/../db/core.ts"); const hooks = await import("../../src/lib/plugins/hooks.ts"); @@ -84,13 +87,9 @@ function writePluginWithMain(opts: { const activeDirs: string[] = []; -// The manager writes installed plugins to getDefaultPluginDir() = ~/.omniroute/plugins/. +// The manager writes installed plugins to the OMNIROUTE_PLUGINS_DIR pinned above. // We must clean those dirs between tests to avoid ENOTEMPTY / stale state. -const DEFAULT_PLUGIN_DIR = path.join( - process.env.HOME || process.env.USERPROFILE || "/tmp", - ".omniroute", - "plugins" -); +const DEFAULT_PLUGIN_DIR = process.env.OMNIROUTE_PLUGINS_DIR; /** Known plugin names created by this test file — cleaned between tests. */ const MANAGED_PLUGIN_NAMES = [ diff --git a/tests/unit/pricing-fallback.test.ts b/tests/unit/pricing-fallback.test.ts index 13e2f4fffca5..80179c2173f9 100644 --- a/tests/unit/pricing-fallback.test.ts +++ b/tests/unit/pricing-fallback.test.ts @@ -12,6 +12,7 @@ test("off-table free model is free, never premium-priced", () => { assert.equal(p.isFree, true); assert.equal(p.inputCostPer1M, 0); assert.equal(p.outputCostPer1M, 0); + assert.equal(p.isEstimated, false); }); test("prefixed model id still resolves through the catalog", () => { @@ -24,10 +25,26 @@ test("off-table paid model keeps the conservative fallback", () => { assert.equal(p.isFree, false); assert.equal(p.inputCostPer1M, 5.0); assert.equal(p.outputCostPer1M, 15.0); + assert.equal(p.isEstimated, true); +}); + +test("empty model id falls back to the estimated guess without throwing", () => { + const p = getModelPricing("openai", ""); + assert.equal(p.inputCostPer1M, 5.0); + assert.equal(p.isEstimated, true); }); test("table hits are untouched", () => { assert.equal(Object.keys(KNOWN_MODEL_PRICING).length, 21); - assert.deepEqual(getModelPricing("openai", "gpt-4o"), KNOWN_MODEL_PRICING["gpt-4o"]); - assert.deepEqual(getModelPricing("longcat", "LongCat-2.0"), KNOWN_MODEL_PRICING["longcat-2.0"]); + assert.deepEqual(getModelPricing("openai", "gpt-4o"), { + ...KNOWN_MODEL_PRICING["gpt-4o"], + isEstimated: false, + }); + assert.deepEqual(getModelPricing("longcat", "LongCat-2.0"), { + ...KNOWN_MODEL_PRICING["longcat-2.0"], + isEstimated: false, + }); + // Spread preserves the quota field and never mutates the shared table. + assert.equal(getModelPricing("longcat", "LongCat-2.0").freeQuotaLimit, 10000000); + assert.equal("isEstimated" in KNOWN_MODEL_PRICING["gpt-4o"], false); }); diff --git a/tests/unit/provider-auth-error-message-literals-15159.test.ts b/tests/unit/provider-auth-error-message-literals-15159.test.ts new file mode 100644 index 000000000000..e44b68ad5b99 --- /dev/null +++ b/tests/unit/provider-auth-error-message-literals-15159.test.ts @@ -0,0 +1,158 @@ +// #15159 — the invariant that makes 8 provider-auth routes safe, pinned. +// +// `src/app/api/providers/{agy,claude,codex}-auth/**` forward a TYPED error straight to +// the client: +// +// if (error instanceof AgyAuthFileError) { +// return NextResponse.json({ error: error.message, code: error.code }, { status: error.status }); +// } +// return NextResponse.json({ error: sanitizeErrorMessage(error) || "…" }, …); +// +// The generic branch sanitizes; the typed branch does not. That is safe ONLY because +// every message those classes are constructed with is a static app-authored literal — +// none interpolates an upstream body, a token, or a filesystem path. Verified across +// all 34 throw sites in `src/lib/oauth/utils/{agy,claude,codex}Auth*.ts`. +// +// So the risk here is not today's code, it is the NEXT throw site. Someone adding +// `throw new CodexAuthFileError(\`refresh failed: ${upstream.body}\`)` would silently +// start leaking through eight routes that all read as safe on inspection — and +// `check:error-helper` cannot see it, because these files DO import +// `sanitizeErrorMessage` (that is the G-03 blind spot, fixed in #15376). +// +// This test fails the moment a message stops being a literal. The correct fix then is at +// the THROW SITE (keep upstream text out of the message), not to sanitize in eight routes. +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test } from "node:test"; + +const ERROR_CLASS = /\bnew (Agy|Claude|Codex)AuthFileError\(/; + +const MODULES = [ + "src/lib/oauth/utils/agyAuthImport.ts", + "src/lib/oauth/utils/claudeAuthImport.ts", + "src/lib/oauth/utils/claudeAuthFile.ts", + "src/lib/oauth/utils/codexAuthImport.ts", + "src/lib/oauth/utils/codexAuthFile.ts", +]; + +function source(path: string): string { + return readFileSync(new URL(`../../${path}`, import.meta.url), "utf8"); +} + +type Site = { file: string; line: number; call: string }; + +/** Every `new XAuthFileError(...)` call site, with its full argument text. */ +function throwSites(): Site[] { + const sites: Site[] = []; + for (const file of MODULES) { + const lines = source(file).split("\n"); + for (let i = 0; i < lines.length; i++) { + if (!ERROR_CLASS.test(lines[i])) continue; + let call = lines[i]; + let j = i; + // The call may span lines; stop at the `);` that closes it, bounded so a + // malformed file cannot make this scan run away. + while (!/\);\s*$/.test(call.trim()) && j + 1 < lines.length && j - i < 14) { + j++; + call += `\n${lines[j]}`; + } + sites.push({ file, line: i + 1, call }); + } + } + return sites; +} + +test("the throw-site scan actually finds the throw sites", () => { + // Without this the meaningful test below could pass on an empty set. 34 is the count + // measured across the five modules; a lower bound is used so an intentional edit that + // adds or removes a site does not need the number bumped. + const sites = throwSites(); + assert.ok( + sites.length >= 25, + `expected at least 25 AuthFileError throw sites, found ${sites.length} — the scanner itself is broken` + ); +}); + +test("every AuthFileError message is a static literal, never interpolated", () => { + const offenders = throwSites() + .filter(({ call }) => { + // A backtick anywhere in the argument list means a template literal; a `+` glued + // to an identifier means concatenation. Both can smuggle upstream text into a + // message that eight routes forward to the client un-sanitized. + return /`|\$\{|\+\s*[A-Za-z_$]/.test(call); + }) + .map(({ file, line, call }) => `${file}:${line} :: ${call.replace(/\s+/g, " ").trim()}`); + + assert.deepEqual( + offenders, + [], + `these AuthFileError messages are not literals — sanitize at the throw site or drop the raw text:\n${offenders.join("\n")}` + ); +}); + +test("the detector used above would catch an interpolating message", () => { + // SHAPE-SANITY. The scan above is a regex heuristic; prove it is not vacuous by + // running the same predicate over a known-bad construction. + const bad = [ + "throw new CodexAuthFileError(`refresh failed: ${upstream.body}`);", + 'throw new ClaudeAuthFileError("bad " + reason);', + "throw new AgyAuthFileError(\n `token ${token} rejected`,\n 401\n);", + ]; + const isDynamic = (call: string) => /`|\$\{|\+\s*[A-Za-z_$]/.test(call); + + for (const call of bad) { + assert.equal(isDynamic(call), true, `detector missed a dynamic message: ${call}`); + } + for (const good of ['throw new CodexAuthFileError("Connection not found", 404, "not_found");']) { + assert.equal(isDynamic(good), false, `detector flagged a static message: ${good}`); + } +}); + +test("the three error classes carry a status and a stable code", () => { + // The routes rely on `error.status` and `error.code` for the HTTP response, so a + // missing field would surface as `undefined` in a client-facing body. + for (const [file, className] of [ + ["src/lib/oauth/utils/agyAuthImport.ts", "AgyAuthFileError"], + ["src/lib/oauth/utils/claudeAuthFile.ts", "ClaudeAuthFileError"], + ["src/lib/oauth/utils/codexAuthFile.ts", "CodexAuthFileError"], + ] as const) { + const text = source(file); + const start = text.indexOf(`export class ${className}`); + assert.notEqual(start, -1, `${className} must be exported from ${file}`); + const body = text.slice(start, start + 500); + + assert.match(body, /status:\s*number/, `${className} must declare a numeric status`); + assert.match(body, /code:\s*string/, `${className} must declare a code`); + assert.match( + body, + /constructor\([^)]*status\s*=\s*\d+[^)]*code\s*=\s*"[a-z_]+"/s, + `${className} must default both status and code so a throw site cannot omit them` + ); + } +}); + +test("all 8 provider-auth routes sanitize their GENERIC branch", () => { + // The other half of the invariant: the typed branch is safe because of the literal + // test above, and the generic branch is safe because it sanitizes. Both halves are + // required — if someone deletes the sanitize call here, the routes start leaking + // arbitrary errors. + const ROUTES = [ + "src/app/api/providers/agy-auth/apply-local/route.ts", + "src/app/api/providers/agy-auth/import/route.ts", + "src/app/api/providers/claude-auth/import/route.ts", + "src/app/api/providers/codex-auth/import/route.ts", + "src/app/api/providers/[id]/claude-auth/apply-local/route.ts", + "src/app/api/providers/[id]/claude-auth/export/route.ts", + "src/app/api/providers/[id]/codex-auth/apply-local/route.ts", + "src/app/api/providers/[id]/codex-auth/export/route.ts", + ]; + + const missing: string[] = []; + for (const route of ROUTES) { + const text = source(route); + if (!/sanitizeErrorMessage\(/.test(text)) missing.push(`${route} (no sanitizeErrorMessage)`); + if (!/AuthFileError/.test(text)) missing.push(`${route} (no typed branch — shape changed?)`); + } + + assert.deepEqual(missing, [], `these routes lost part of the guarantee:\n${missing.join("\n")}`); +}); diff --git a/tests/unit/provider-cost-data.test.ts b/tests/unit/provider-cost-data.test.ts index 157769dfbffd..408242db9e23 100644 --- a/tests/unit/provider-cost-data.test.ts +++ b/tests/unit/provider-cost-data.test.ts @@ -25,6 +25,7 @@ test("provider-specific pricing wins over a generic model fallback", () => { inputCostPer1M: 1, outputCostPer1M: 10, isFree: false, + isEstimated: false, }); } finally { if (previousGeneric) KNOWN_MODEL_PRICING[genericKey] = previousGeneric; @@ -39,10 +40,12 @@ test("tier pricing reads the exact Devin provider/model rate", () => { inputCostPer1M: 0.2, outputCostPer1M: 1.2, isFree: false, + isEstimated: false, }); assert.deepEqual(getModelPricing("devin-cli", "gpt-5-6-luna-max-priority"), { inputCostPer1M: 0.4, outputCostPer1M: 2.4, isFree: false, + isEstimated: false, }); }); diff --git a/tests/unit/provider-field-strips.test.ts b/tests/unit/provider-field-strips.test.ts index ffcbb869327a..6cef89b06a7a 100644 --- a/tests/unit/provider-field-strips.test.ts +++ b/tests/unit/provider-field-strips.test.ts @@ -80,3 +80,35 @@ test("stripGroqUnsupportedFields drops unsupported messages[].model and other me assert.equal("messageId" in out.messages[1], false); assert.equal("sender" in out.messages[1], false); }); + +// Routing envelopes must become valid Groq Chat Completions fields. +test("Groq GPT-OSS routing low/none uses low without foreign envelopes", () => { + for (const effort of ["low", "none"]) { + const input = { + model: "openai/gpt-oss-20b", + messages: [{ role: "user", content: "hello" }], + reasoning_effort: effort, + reasoning: { effort }, + output_config: { effort }, + }; + const out = stripGroqUnsupportedFields(input, input.model); + assert.equal(out.reasoning_effort, "low"); + assert.equal("reasoning" in out, false); + assert.equal("output_config" in out, false); + assert.deepEqual(out.messages, input.messages); + assert.equal(input.reasoning_effort, effort); + assert.deepEqual(input.output_config, { effort }); + } +}); + +test("Groq forced none uses low even when routing removed the effort fields", () => { + const input = { model: "openai/gpt-oss-20b", messages: [{ role: "user", content: "hello" }] }; + const out = stripGroqUnsupportedFields(input, input.model, "none") as Record; + assert.equal(out.reasoning_effort, "low"); + // An ordinary effort-less request must retain its provider default. + assert.equal("reasoning_effort" in stripGroqUnsupportedFields(input, input.model), false); + assert.equal( + stripGroqUnsupportedFields({ reasoning_effort: "high" }, input.model).reasoning_effort, + "high" + ); +}); diff --git a/tests/unit/provider-header-profiles.test.ts b/tests/unit/provider-header-profiles.test.ts index 66dd8ceb8002..8b9f8331773c 100644 --- a/tests/unit/provider-header-profiles.test.ts +++ b/tests/unit/provider-header-profiles.test.ts @@ -29,9 +29,9 @@ import { test("provider header profiles expose current GitHub chat and internal headers", () => { const chatHeaders = getGitHubCopilotChatHeaders("text/event-stream", "agent"); - // Chat/inference path matches the @github/copilot CLI 1.0.88 wire identity. + // Header shape follows the captured CLI identity; the version uses the current pin. assert.equal(chatHeaders["editor-version"], GITHUB_COPILOT_EDITOR_VERSION); - assert.equal(chatHeaders["user-agent"], `copilot/1.0.88 (${process.platform}) term/unknown`); + assert.equal(chatHeaders["user-agent"], `copilot/1.0.91 (${process.platform}) term/unknown`); assert.equal(chatHeaders["x-github-api-version"], GITHUB_COPILOT_API_VERSION); assert.equal(chatHeaders["copilot-integration-id"], GITHUB_COPILOT_INTEGRATION_ID); assert.equal(chatHeaders["x-interaction-type"], GITHUB_COPILOT_INTERACTION_TYPE); @@ -70,6 +70,23 @@ test("getGitHubCopilotMachineId is stable across calls and vision toggles the vi assert.equal(plain["x-client-machine-id"], vision["x-client-machine-id"]); }); +test("Copilot chat headers honor the CLI version environment override", () => { + const previous = process.env.GITHUB_COPILOT_CLI_VERSION; + process.env.GITHUB_COPILOT_CLI_VERSION = "1.0.82"; + try { + assert.equal( + getGitHubCopilotChatHeaders()["user-agent"], + `copilot/1.0.82 (${process.platform}) term/unknown` + ); + } finally { + if (previous === undefined) { + delete process.env.GITHUB_COPILOT_CLI_VERSION; + } else { + process.env.GITHUB_COPILOT_CLI_VERSION = previous; + } + } +}); + test("provider header profiles expose dedicated refresh, qoder and kiro variants", () => { const refreshHeaders = getGitHubCopilotRefreshHeaders("token gh-access"); assert.equal(refreshHeaders.Authorization, "token gh-access"); diff --git a/tests/unit/provider-models-content-length-15212.test.ts b/tests/unit/provider-models-content-length-15212.test.ts new file mode 100644 index 000000000000..7de25fbd9bda --- /dev/null +++ b/tests/unit/provider-models-content-length-15212.test.ts @@ -0,0 +1,110 @@ +/** + * #15212 — GET /api/v1/providers/{provider}/models filters the unified catalog + * and rebuilds the JSON. The catalog response carries Content-Length (and + * sometimes Content-Encoding) for the FULL catalog. Copying those headers onto + * the smaller body makes clients that honor Content-Length wait for bytes that + * never arrive. + * + * The route's catalog call is a live binding, so this test replaces it and + * hands GET a response whose Content-Length cannot match the filtered body. + * The #14092 suite only checks that a declared length equals the body it just + * read, which stays green when the two sizes happen to match. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +process.env.NODE_ENV = "test"; +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-cl-15212-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const routeModule = await import("../../src/app/api/v1/providers/[provider]/models/route.ts"); + +const STALE_CONTENT_LENGTH = "813313"; +const originalLoad = routeModule.unifiedModels.load; + +test.after(() => { + routeModule.unifiedModels.load = originalLoad; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test.beforeEach(() => { + routeModule.unifiedModels.load = originalLoad; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +}); + +function plantCatalog(body: string, headers: Record, status = 200) { + routeModule.unifiedModels.load = (async () => + new Response(body, { status, headers })) as typeof originalLoad; +} + +test("#15212 filtered provider models must not forward the catalog Content-Length", async () => { + const fullCatalog = { + object: "list", + data: [ + { id: "openai/gpt-4o", owned_by: "openai", object: "model" }, + { id: "openai/gpt-4o-mini", owned_by: "openai", object: "model" }, + { id: "anthropic/claude-sonnet", owned_by: "anthropic", object: "model" }, + ], + }; + const catalogBody = JSON.stringify(fullCatalog); + assert.notEqual(Buffer.byteLength(catalogBody), Number(STALE_CONTENT_LENGTH)); + + plantCatalog(catalogBody, { + "content-type": "application/json", + "content-length": STALE_CONTENT_LENGTH, + "content-encoding": "gzip", + "x-catalog-keep": "1", + }); + + const request = new Request("http://127.0.0.1:20128/api/v1/providers/openai/models"); + const response = await routeModule.GET(request, { + params: Promise.resolve({ provider: "openai" }), + }); + + assert.equal(response.status, 200); + const text = await response.text(); + const payload = JSON.parse(text) as { data: Array<{ id: string }> }; + assert.deepEqual( + payload.data.map((model) => model.id), + ["gpt-4o", "gpt-4o-mini"] + ); + assert.notEqual(Buffer.byteLength(text), Number(STALE_CONTENT_LENGTH)); + + const declared = response.headers.get("content-length"); + assert.notEqual( + declared, + STALE_CONTENT_LENGTH, + "stale catalog content-length must not be forwarded" + ); + assert.ok( + declared === null || Number(declared) === Buffer.byteLength(text), + `content-length must be absent or match the rewritten body, got ${declared}` + ); + assert.equal(response.headers.get("content-encoding"), null); + assert.equal(response.headers.get("x-catalog-keep"), "1"); +}); + +test("#15212 passthrough keeps upstream headers when the catalog body is not rewritten", async () => { + plantCatalog("not-json", { + "content-type": "text/plain", + "content-length": "8", + "x-catalog-keep": "passthrough", + }); + + const request = new Request("http://127.0.0.1:20128/api/v1/providers/openai/models"); + const response = await routeModule.GET(request, { + params: Promise.resolve({ provider: "openai" }), + }); + const text = await response.text(); + + assert.equal(text, "not-json"); + assert.equal(response.headers.get("content-length"), "8"); + assert.equal(response.headers.get("x-catalog-keep"), "passthrough"); +}); diff --git a/tests/unit/provider-models-discovery-split.test.ts b/tests/unit/provider-models-discovery-split.test.ts index f4b8c4bef96d..69cfdc883052 100644 --- a/tests/unit/provider-models-discovery-split.test.ts +++ b/tests/unit/provider-models-discovery-split.test.ts @@ -590,6 +590,36 @@ test("codex.enrichCodexModelsFromGithubCatalog keeps live entitlement list autho assert.equal(enriched[0]?.supportsVision, true); }); +test("codex.enrichCodexModelsFromGithubCatalog uses the GitHub catalog when there is no live list", () => { + const catalog = enrichCodexModelsFromGithubCatalog( + [], + [ + { + id: "gpt-5.6-luna", + name: "GitHub Luna", + owned_by: "codex", + apiFormat: "responses", + supportedEndpoints: ["responses"], + }, + { + id: "gpt-5.6-terra", + name: "GitHub Terra", + owned_by: "codex", + apiFormat: "responses", + supportedEndpoints: ["responses"], + inputTokenLimit: 272000, + }, + ] + ); + + assert.deepEqual( + catalog.map((model) => model.id), + ["gpt-5.6-luna", "gpt-5.6-terra"] + ); + assert.equal(catalog[0]?.name, "GitHub Luna"); + assert.equal(catalog[1]?.inputTokenLimit, 272000); +}); + test("codex.mergeCodexLiveModelsWithLocalCatalog merges capacity limits conservatively (smaller wins)", () => { const merged = mergeCodexLiveModelsWithLocalCatalog( [ diff --git a/tests/unit/provider-models-management-auth-contract.test.mjs b/tests/unit/provider-models-management-auth-contract.test.mjs new file mode 100644 index 000000000000..edc6599b78f5 --- /dev/null +++ b/tests/unit/provider-models-management-auth-contract.test.mjs @@ -0,0 +1,34 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; + +test("provider-models handlers require centralized management auth before work", () => { + const routePath = "src/app/api/provider-models/route.ts"; + const source = fs.readFileSync(routePath, "utf8"); + + assert.ok(source.includes('from "@/lib/api/requireManagementAuth"')); + + for (const method of ["GET", "POST", "PUT", "PATCH", "DELETE"]) { + const handlerStart = source.indexOf(`export async function ${method}(`); + assert.ok(handlerStart >= 0, `${routePath} is missing ${method}`); + const nextHandlerStart = source.indexOf("export async function", handlerStart + 1); + const handler = source.slice( + handlerStart, + nextHandlerStart === -1 ? source.length : nextHandlerStart + ); + const authCall = handler.indexOf("const authError = await requireManagementAuth(request);"); + assert.ok(authCall >= 0, `${method} must use requireManagementAuth(request)`); + assert.ok( + handler.indexOf("if (authError) return authError;") > authCall, + `${method} must return management-auth failures` + ); + + const workMarkers = ["request.json()", "new URL(request.url)"] + .map((marker) => handler.indexOf(marker)) + .filter((position) => position >= 0); + assert.ok( + workMarkers.every((position) => authCall < position), + `${method} must authenticate before parsing input or reading request parameters` + ); + } +}); diff --git a/tests/unit/provider-models-management-route.test.ts b/tests/unit/provider-models-management-route.test.ts index a3039c042612..ecf34dfccbfe 100644 --- a/tests/unit/provider-models-management-route.test.ts +++ b/tests/unit/provider-models-management-route.test.ts @@ -8,13 +8,18 @@ const TEST_DATA_DIR = fs.mkdtempSync( path.join(os.tmpdir(), "omniroute-provider-model-management-route-") ); process.env.DATA_DIR = TEST_DATA_DIR; +const ORIGINAL_API_KEY_SECRET = process.env.API_KEY_SECRET; +const API_KEY_SECRET = "provider-models-management-route-test-secret"; +process.env.API_KEY_SECRET = API_KEY_SECRET; const core = await import("../../src/lib/db/core.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); +const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const providerModelsRoute = await import("../../src/app/api/provider-models/route.ts"); async function resetStorage() { core.resetDbInstance(); + apiKeysDb.resetApiKeyState(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } @@ -47,7 +52,46 @@ test.beforeEach(async () => { test.after(async () => { core.resetDbInstance(); + apiKeysDb.resetApiKeyState(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_API_KEY_SECRET === undefined) delete process.env.API_KEY_SECRET; + else process.env.API_KEY_SECRET = ORIGINAL_API_KEY_SECRET; +}); + +test("provider-models POST accepts management API keys with manage and admin scopes", async () => { + const originalInitialPassword = process.env.INITIAL_PASSWORD; + process.env.INITIAL_PASSWORD = "provider-models-auth-test-password"; + + try { + for (const scope of ["manage", "admin"]) { + const modelId = `${scope}-managed-model`; + const apiKey = await apiKeysDb.createApiKey(`provider-models-${scope}`, "test-machine", [ + scope, + ]); + const response = await providerModelsRoute.POST( + new Request("http://localhost/api/provider-models", { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey.key}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + provider: "openai", + modelId, + modelName: "Managed Model", + targetFormat: "claude", + }), + }) + ); + + assert.equal(response.status, 200, `${scope} scope should be accepted`); + const models = await modelsDb.getCustomModels("openai"); + assert.equal(models.find((model) => model.id === modelId)?.targetFormat, "claude"); + } + } finally { + if (originalInitialPassword === undefined) delete process.env.INITIAL_PASSWORD; + else process.env.INITIAL_PASSWORD = originalInitialPassword; + } }); test("provider-models GET returns an empty hiddenModelsByProvider map with no hidden models", async () => { diff --git a/tests/unit/provider-registry-freetheai.test.ts b/tests/unit/provider-registry-freetheai.test.ts index 0a18e0cdc3fd..df88454d988f 100644 --- a/tests/unit/provider-registry-freetheai.test.ts +++ b/tests/unit/provider-registry-freetheai.test.ts @@ -1,6 +1,7 @@ /** - * Issue #6670 — Add FreeTheAi as an OpenAI-compatible gateway provider - * (free tier via Discord signup). + * Issue #6670 — Add FreeTheAi as an OpenAI-compatible gateway provider. + * Issue #15385 — gateway moved to freetheai.org; signup moved off Discord; + * model ids are now `fta//` and must be sent in full. * * Verifies the new provider is wired end-to-end the same way as the other * aggregator/gateway providers (chutes, glhf, ...): @@ -25,17 +26,23 @@ test("#6670 freetheai is registered in the executor registry with an OpenAI-comp assert.ok(entry, "freetheai should be present in the executor registry"); assert.equal(entry.format, "openai"); assert.equal(entry.executor, "default"); - assert.equal(entry.baseUrl, "https://api.freetheai.xyz/v1/chat/completions"); - assert.equal(entry.modelsUrl, "https://api.freetheai.xyz/v1/models"); + assert.equal(entry.baseUrl, "https://api.freetheai.org/v1/chat/completions"); + assert.equal(entry.modelsUrl, "https://api.freetheai.org/v1/models"); assert.equal(entry.authType, "apikey"); assert.equal(entry.authHeader, "bearer"); assert.equal(entry.passthroughModels, true); - assert.ok(Array.isArray(entry.models) && entry.models.length > 0, "must seed a fallback model list"); + assert.ok( + Array.isArray(entry.models) && entry.models.length > 0, + "must seed a fallback model list" + ); }); test("#6670 freetheai resolves through getExecutor() as a DefaultExecutor instance", async () => { const executor = await getExecutor("freetheai"); - assert.ok(executor instanceof DefaultExecutor, "freetheai has no custom executor — must fall through to DefaultExecutor"); + assert.ok( + executor instanceof DefaultExecutor, + "freetheai has no custom executor — must fall through to DefaultExecutor" + ); }); test("#6670 freetheai is classified as an aggregator/gateway provider", () => { @@ -50,8 +57,48 @@ test("#6670 freetheai has provider metadata with free-tier info", () => { assert.ok(meta, "freetheai should have an APIKEY_PROVIDERS metadata entry"); assert.equal(meta.id, "freetheai"); assert.equal(meta.name, "FreeTheAi"); - assert.equal(meta.website, "https://freetheai.xyz"); + assert.equal(meta.website, "https://freetheai.org"); assert.equal(meta.hasFree, true); assert.equal(typeof meta.freeNote, "string"); assert.ok((meta.freeNote as string).length > 0); }); + +// #15385 — the domain moved freetheai.xyz → freetheai.org (the .xyz host now +// answers HTTP 530). Signup also moved off Discord: keys come from the site +// dashboard, and linking Discord is optional (it only raises the daily limit). +// A hint that still routes the user to Discord for a key is factually wrong. +test("#15385 freetheai seeds only fully-qualified fta/ model ids", () => { + const entry = (REGISTRY as Record>).freetheai; + const models = entry.models as Array<{ id: string }>; + + // Retired with the old domain — /v1/models no longer serves any of these. + const retired = ["gpt-4o-mini", "llama-3.3-70b-instruct", "deepseek-chat"]; + for (const model of models) { + assert.ok( + model.id.startsWith("fta/"), + `seeded model "${model.id}" must be sent in full with the fta/ prefix (#15385)` + ); + assert.ok( + !retired.includes(model.id), + `retired model "${model.id}" must not be seeded (#15385)` + ); + } +}); + +test("#15385 freetheai signup copy points at freetheai.org, not the dead .xyz host or Discord", () => { + const meta = (APIKEY_PROVIDERS as Record>).freetheai; + + for (const field of ["freeNote", "authHint"] as const) { + const text = String(meta[field] ?? ""); + assert.ok(text.length > 0, `${field} must stay populated`); + assert.ok( + !/freetheai\.xyz/.test(text), + `${field} must not link the retired .xyz domain (#15385): ${text}` + ); + assert.match( + text, + /freetheai\.org/, + `${field} must send the user to the live site for signup (#15385): ${text}` + ); + } +}); diff --git a/tests/unit/provider-uniform-egress-route.test.ts b/tests/unit/provider-uniform-egress-route.test.ts new file mode 100644 index 000000000000..f790553a72e9 --- /dev/null +++ b/tests/unit/provider-uniform-egress-route.test.ts @@ -0,0 +1,158 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; + +// The uniform egress regime has its own route so a failure there can never +// break the pool screen. These tests pin its shape, its null-on-failure +// contract, its opt-in flag (PROXY_POOL_EGRESS_OBSERVATION, default off) and +// its Zod parameter checks (provider required, hours 1..24 defaulting to 1). +// Rows go through INSERT statements with the columns the readers use. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-uniform-egress-route-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; +delete process.env.INITIAL_PASSWORD; + +const core = await import("../../src/lib/db/core.ts"); +const observation = await import("../../src/lib/proxyPoolEgressObservation.ts"); +const { GET } = await import("../../src/app/api/settings/proxies/pool/uniform-egress/route.ts"); + +function resetStorage() { + delete process.env.INITIAL_PASSWORD; + process.env.PROXY_POOL_EGRESS_OBSERVATION = "true"; + observation.resetUniformEgressRegimeCache(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(() => { + resetStorage(); +}); + +test.after(() => { + delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function request(base: string, query: Record): Request { + const params = new URLSearchParams(query); + return new Request(`http://localhost${base}?${params.toString()}`, { method: "GET" }); +} + +const REGIME_PATH = "/api/settings/proxies/pool/uniform-egress"; + +function insertLog( + provider: string, + host: string, + upstreamStatus: number | null, + ageMs = 30 * 60 * 1000 +) { + core + .getDbInstance() + .prepare( + `INSERT INTO proxy_logs (id, timestamp, status, proxy_type, proxy_host, proxy_port, provider, target_url, upstream_status) + VALUES (?, ?, 'error', 'http', ?, 8080, ?, 'https://api.example.com/v1/chat', ?)` + ) + .run(randomUUID(), new Date(Date.now() - ageMs).toISOString(), host, provider, upstreamStatus); +} + +function seedUniformProvider(provider: string, exits = 15, perExit = 6, failing = 3) { + for (let exit = 0; exit < exits; exit++) { + for (let n = 0; n < perExit; n++) { + insertLog(provider, `10.9.2.${exit + 1}`, n < failing ? 503 : 200); + } + } +} + +test("returns the regime with exactly the documented keys", async () => { + seedUniformProvider("acme"); + const response = await GET(request(REGIME_PATH, { provider: "acme" })); + assert.equal(response.status, 200); + const body = (await response.json()) as Record; + assert.deepEqual(Object.keys(body).sort(), [ + "affectedExits", + "attempts", + "exitsTouched", + "exitsWithTraffic", + "measured", + "provider", + "share5xx", + "state", + "uniform", + "windowHours", + ]); + assert.equal(body.provider, "acme"); + assert.equal(body.uniform, true); + assert.equal(body.state, "measured"); + assert.equal(body.windowHours, 1); +}); + +test("a single affected exit reads back as not uniform", async () => { + for (let n = 0; n < 6; n++) insertLog("solo-ish", "10.9.3.1", n < 5 ? 503 : 200); + for (let exit = 2; exit <= 5; exit++) { + for (let n = 0; n < 6; n++) insertLog("solo-ish", `10.9.3.${exit}`, 200); + } + const response = await GET(request(REGIME_PATH, { provider: "solo-ish" })); + assert.equal(response.status, 200); + const body = (await response.json()) as Record; + assert.equal(body.uniform, false); + assert.equal(body.state, "measured"); +}); + +test("an unknown provider reads back as null", async () => { + const response = await GET(request(REGIME_PATH, { provider: "nobody" })); + assert.equal(response.status, 200); + assert.equal(await response.json(), null); +}); + +test("returns null with status 200 when the read fails", async () => { + seedUniformProvider("acme"); + const db = core.getDbInstance(); + db.exec("ALTER TABLE proxy_logs RENAME TO proxy_logs_hidden"); + try { + const response = await GET(request(REGIME_PATH, { provider: "acme" })); + assert.equal(response.status, 200); + assert.equal(await response.json(), null); + } finally { + db.exec("ALTER TABLE proxy_logs_hidden RENAME TO proxy_logs"); + } +}); + +test("returns null when the feature flag is at its default (off) or turned off", async () => { + seedUniformProvider("acme"); + for (const value of [undefined, "false"]) { + if (value === undefined) delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + else process.env.PROXY_POOL_EGRESS_OBSERVATION = value; + observation.resetUniformEgressRegimeCache(); + const response = await GET(request(REGIME_PATH, { provider: "acme" })); + assert.equal(response.status, 200); + assert.equal(await response.json(), null, String(value)); + } +}); + +test("rejects a missing or blank provider with 400", async () => { + for (const query of [{}, { provider: "" }, { provider: " " }]) { + const response = await GET(request(REGIME_PATH, query)); + assert.equal(response.status, 400, JSON.stringify(query)); + const body = (await response.json()) as { error?: { message?: string } }; + assert.equal(typeof body?.error?.message, "string"); + assert.ok(!body.error!.message!.includes("at /"), "no stack trace in the error body"); + } +}); + +test("defaults hours to 1 and rejects hours outside 1..24", async () => { + seedUniformProvider("acme"); + const defaulted = await GET(request(REGIME_PATH, { provider: "acme" })); + assert.equal(((await defaulted.json()) as Record).windowHours, 1); + const explicit = await GET(request(REGIME_PATH, { provider: "acme", hours: "3" })); + assert.equal(((await explicit.json()) as Record).windowHours, 3); + for (const hours of ["0", "25", "abc"]) { + const response = await GET(request(REGIME_PATH, { provider: "acme", hours })); + assert.equal(response.status, 400, hours); + } +}); diff --git a/tests/unit/provider-upstream-regime.test.ts b/tests/unit/provider-upstream-regime.test.ts new file mode 100644 index 000000000000..73ead10d8db6 --- /dev/null +++ b/tests/unit/provider-upstream-regime.test.ts @@ -0,0 +1,275 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { randomUUID } from "node:crypto"; + +// Uniform 5xx regime read from the proxy log: a provider whose exits fail +// together is an upstream problem, not an exit problem. The pure rule decides +// on shares over exits with traffic; the SQL groups real requests per +// provider and per exit. Rows go through INSERT statements with the same +// columns and the same connection-test filter the readers use. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-upstream-regime-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.PROXY_POOL_EGRESS_OBSERVATION = "true"; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const regime = await import("../../src/lib/proxyPoolEgressObservation.ts"); + +function resetStorage() { + regime.resetUniformEgressRegimeCache(); + regime.resetPoolEgressObservationCache(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(() => { + process.env.PROXY_POOL_EGRESS_OBSERVATION = "true"; + resetStorage(); +}); + +test.after(() => { + delete process.env.PROXY_POOL_EGRESS_OBSERVATION; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +const THRESHOLDS = { + shareThreshold: 0.3, + majorityRatio: 0.5, + minAttemptsPerExit: 5, + minExitsWithTraffic: 2, +}; + +// Exit rows through the production reader: one INSERT per request, grouped by +// the same SQL the reader uses, so order and fields match production. +function seedExits( + provider: string, + shares: Array<{ exits: number; share: number; perExit?: number; status?: number | null }> +): void { + let host = 0; + for (const group of shares) { + const perExit = group.perExit ?? 10; + const failing = Math.round(perExit * group.share); + for (let exit = 0; exit < group.exits; exit++) { + host += 1; + for (let n = 0; n < perExit; n++) { + insertLog(provider, `10.8.0.${host}`, 8080, n < failing ? (group.status ?? 503) : 200); + } + } + } +} + +function readerRows(provider: string): Array<{ attempts: number; serverErrors: number }> { + const since = new Date(Date.now() - 60 * 60 * 1000).toISOString(); + const summary = proxiesDb + .getProviderUpstreamSummary(since) + .find((entry) => entry.provider === provider); + assert.ok(summary, `expected a summary for ${provider}`); + return summary.exits; +} + +function insertLog( + provider: string | null, + host: string, + port: number, + upstreamStatus: number | null, + targetUrl: string | null = "https://api.example.com/v1/chat", + ageMs = 30 * 60 * 1000 +) { + core + .getDbInstance() + .prepare( + `INSERT INTO proxy_logs (id, timestamp, status, proxy_type, proxy_host, proxy_port, provider, target_url, upstream_status) + VALUES (?, ?, 'error', 'http', ?, ?, ?, ?, ?)` + ) + .run( + randomUUID(), + new Date(Date.now() - ageMs).toISOString(), + host, + port, + provider, + targetUrl, + upstreamStatus + ); +} + +test("flags every exit above the share as a uniform regime", () => { + seedExits("pure-uniform", [{ exits: 15, share: 0.4 }]); + const result = regime.isUpstreamRegime(readerRows("pure-uniform"), THRESHOLDS); + assert.equal(result.uniform, true); + assert.equal(result.state, "measured"); + assert.equal(result.exitsWithTraffic, 15); + assert.equal(result.affectedExits, 15); +}); + +test("a single affected exit is not a uniform regime", () => { + seedExits("pure-single", [ + { exits: 1, share: 0.9 }, + { exits: 14, share: 0 }, + ]); + const result = regime.isUpstreamRegime(readerRows("pure-single"), THRESHOLDS); + assert.equal(result.uniform, false); + assert.equal(result.state, "measured"); +}); + +test("client errors alone never flag a 5xx regime", () => { + seedExits("pure-4xx", [{ exits: 4, share: 0, status: 429 }]); + const rows = readerRows("pure-4xx"); + assert.equal( + rows.reduce((sum, row) => sum + row.attempts, 0), + 40 + ); + // 429s land in measured, never in serverErrors: the SQL only counts 5xx. + const result = regime.isUpstreamRegime( + rows.map((row) => ({ ...row, measured: row.attempts })), + THRESHOLDS + ); + assert.equal(result.uniform, false); + assert.equal(result.state, "measured"); +}); + +test("exits below the traffic minimum do not count", () => { + seedExits("pure-thin", [{ exits: 2, share: 1, perExit: 2 }]); + const result = regime.isUpstreamRegime(readerRows("pure-thin"), THRESHOLDS); + assert.equal(result.uniform, false); + assert.equal(result.exitsWithTraffic, 0); +}); + +test("one exit with traffic is never uniform", () => { + seedExits("pure-lone", [{ exits: 1, share: 1 }]); + const result = regime.isUpstreamRegime(readerRows("pure-lone"), THRESHOLDS); + assert.equal(result.uniform, false); +}); + +test("no measured request means unmeasured and no conclusion", () => { + // Every row without an upstream status: the reader counts attempts, not measured. + for (let host = 1; host <= 2; host++) { + for (let n = 0; n < 10; n++) insertLog("pure-unmeasured", `10.8.9.${host}`, 8080, null); + } + const summary = proxiesDb + .getProviderUpstreamSummary(new Date(Date.now() - 60 * 60 * 1000).toISOString()) + .find((entry) => entry.provider === "pure-unmeasured"); + assert.ok(summary); + assert.equal(summary.measured, 0); + const result = regime.isUpstreamRegime( + summary.exits.map((row) => ({ ...row, measured: 0 })), + THRESHOLDS + ); + assert.equal(result.uniform, false); + assert.equal(result.state, "unmeasured"); +}); + +test("SQL groups real requests per provider and excludes connection tests", () => { + for (let exit = 0; exit < 3; exit++) { + for (let n = 0; n < 6; n++) { + insertLog("acme", `10.9.0.${exit + 1}`, 8080, n < 2 ? 500 : 200); + } + } + insertLog("acme", "10.9.0.1", 8080, 200, "acme/connection-test"); + insertLog(null, "10.9.0.9", 8080, 500); + insertLog(" ", "10.9.0.9", 8080, 500); + for (let n = 0; n < 6; n++) insertLog("other", "10.9.0.9", 8080, 500); + + const since = new Date(Date.now() - 60 * 60 * 1000).toISOString(); + const summaries = proxiesDb.getProviderUpstreamSummary(since); + const acme = summaries.find((entry) => entry.provider === "acme"); + assert.ok(acme); + assert.equal(acme.attempts, 18); + assert.equal(acme.measured, 18); + assert.equal(acme.exits.length, 3); + assert.ok( + summaries.every((entry) => entry.provider.trim() !== ""), + "blank providers are excluded" + ); + const stale = proxiesDb.getProviderUpstreamSummary( + new Date(Date.now() - 60 * 1000).toISOString() + ); + assert.equal( + stale.find((entry) => entry.provider === "acme")?.attempts ?? 0, + 0, + "the window lower bound applies" + ); +}); + +test("a uniform provider reads back as uniform through the cached reader", () => { + for (let exit = 0; exit < 15; exit++) { + for (let n = 0; n < 6; n++) { + insertLog("storm", `10.9.1.${exit + 1}`, 8080, n < 3 ? 503 : 200); + } + } + const now = Date.now(); + const first = regime.readUniformEgressRegime("storm", 1, now); + assert.equal(first?.uniform, true); + assert.equal(first?.state, "measured"); + assert.equal(first?.exitsWithTraffic, 15); + insertLog("storm", "10.9.9.9", 8080, 200); + assert.equal(regime.readUniformEgressRegime("storm", 1, now + 1000)?.attempts, first?.attempts); +}); + +test("a cached regime is re-read after 30 seconds", () => { + for (let exit = 0; exit < 3; exit++) { + for (let n = 0; n < 6; n++) { + insertLog("ttl", `10.9.4.${exit + 1}`, 8080, n < 2 ? 503 : 200); + } + } + const now = Date.now(); + const first = regime.readUniformEgressRegime("ttl", 1, now); + assert.ok(first); + insertLog("ttl", "10.9.4.1", 8080, 200); + assert.equal( + regime.readUniformEgressRegime("ttl", 1, now + 29_000)?.attempts, + first?.attempts, + "within TTL the first read is reused" + ); + const fresh = regime.readUniformEgressRegime("ttl", 1, now + 31_000); + assert.equal(fresh?.attempts, (first?.attempts ?? 0) + 1, "past the TTL the log is read again"); +}); + +test("a failed read is traced server-side, returns null, and is not cached", async () => { + for (let n = 0; n < 6; n++) insertLog("traced", "10.9.6.1", 8080, 200); + for (let n = 0; n < 6; n++) insertLog("traced", "10.9.6.2", 8080, 200); + const errors: Array<{ msg: unknown; message: unknown }> = []; + const logger = await import("../../src/shared/utils/logger.ts"); + const original = logger.logger.error.bind(logger.logger); + (logger.logger as { error: (...args: unknown[]) => void }).error = (...args: unknown[]) => { + errors.push({ msg: args[0], message: args[1] }); + }; + const db = core.getDbInstance(); + db.exec("ALTER TABLE proxy_logs RENAME TO proxy_logs_hidden"); + try { + assert.equal(regime.readUniformEgressRegime("traced", 1), null, "client still gets null"); + } finally { + db.exec("ALTER TABLE proxy_logs_hidden RENAME TO proxy_logs"); + logger.logger.error = original; + } + assert.equal(errors.length, 1, "one server-side trace"); + assert.match( + String((errors[0].message as string) ?? ""), + /proxy_logs/i, + "sanitized failure text" + ); + assert.equal( + (errors[0].msg as Record)?.provider, + "traced", + "provider travels in the log meta" + ); + assert.equal(regime.readUniformEgressRegime("traced", 1)?.exitsWithTraffic, 2); +}); + +test("a failed read returns null and is not cached", () => { + for (let n = 0; n < 6; n++) insertLog("flaky", "10.9.5.1", 8080, 200); + for (let n = 0; n < 6; n++) insertLog("flaky", "10.9.5.2", 8080, 200); + const db = core.getDbInstance(); + db.exec("ALTER TABLE proxy_logs RENAME TO proxy_logs_hidden"); + try { + assert.equal(regime.readUniformEgressRegime("flaky", 1), null); + } finally { + db.exec("ALTER TABLE proxy_logs_hidden RENAME TO proxy_logs"); + } + assert.ok(regime.readUniformEgressRegime("flaky", 1), "the failure left no cache entry behind"); +}); diff --git a/tests/unit/providerlimits-quotanormalize-split.test.ts b/tests/unit/providerlimits-quotanormalize-split.test.ts index e3267516221b..2a7970e6289c 100644 --- a/tests/unit/providerlimits-quotanormalize-split.test.ts +++ b/tests/unit/providerlimits-quotanormalize-split.test.ts @@ -44,6 +44,21 @@ describe("providerLimits/quotaNormalize — isUsageQuotaKeyAllowed", () => { assert.equal(isUsageQuotaKeyAllowed("anthropic", "models"), true); assert.equal(isUsageQuotaKeyAllowed("antigravity", "credits"), true); }); + it("keeps Antigravity family weekly buckets that are not model ids", () => { + assert.equal(isUsageQuotaKeyAllowed("antigravity", "gemini_weekly"), true); + assert.equal(isUsageQuotaKeyAllowed("antigravity", "claude_gpt_weekly"), true); + assert.equal(isUsageQuotaKeyAllowed("agy", "gemini_weekly"), true); + const quotas = { + "gemini-3.1-flash-lite": { remainingPercentage: 10, quotaSource: "retrieveUserQuota" }, + gemini_weekly: { remainingPercentage: 40, quotaSource: "retrieveUserQuotaSummary" }, + claude_gpt_weekly: { remainingPercentage: 20, quotaSource: "retrieveUserQuotaSummary" }, + "not-a-model": { remainingPercentage: 1 }, + }; + const out = normalizeUsageQuotasForProvider("antigravity", quotas); + assert.equal(out.gemini_weekly.remainingPercentage, 40); + assert.equal(out.claude_gpt_weekly.remainingPercentage, 20); + assert.equal(out["not-a-model"], undefined); + }); }); describe("providerLimits/quotaNormalize — sanitize/normalize are callable & pure-shaped", () => { diff --git a/tests/unit/proxy-health-passive-sweep.test.ts b/tests/unit/proxy-health-passive-sweep.test.ts new file mode 100644 index 000000000000..28d46c2b12a0 --- /dev/null +++ b/tests/unit/proxy-health-passive-sweep.test.ts @@ -0,0 +1,257 @@ +/** + * Passive skip consumed by the sweep: scheduling only, never a status. + * A proven endpoint skips the redundant probe; an unproven endpoint (recent + * attributed production failure) keeps its live probe, which alone can + * confirm the failure and feed auto-disable/auto-remove. Counts + * stay out of `tested`. RED-then-GREEN: with the stub neutralized (no skip), + * proxies are probed again. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import http from "node:http"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-passive-sweep-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; +process.env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES = "true"; +process.env.PROXY_HEALTH_TEST_STAGGER_MS = "0"; + +// The probe target: answers every request, so any peer that relays through +// the forward proxy below proves the target alive for the whole sweep. +// Created and wired BEFORE the scheduler import: the scheduler resolves the +// probe target once at module load, so the env override must precede it. +const target = http.createServer((_req, res) => { + res.writeHead(200); + res.end("{}"); +}); +await new Promise((resolve) => target.listen(0, "127.0.0.1", () => resolve())); +const targetPort = (target.address() as net.AddressInfo).port; +process.env.PROXY_HEALTH_TEST_URL = `http://127.0.0.1:${targetPort}/probe`; +process.env.PROXY_HEALTH_USE_PROVIDER_TARGET = "false"; +delete process.env.PROXY_AUTO_REMOVE; +delete process.env.PROXY_AUTO_DISABLE; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const scheduler = await import("../../src/lib/proxyHealth/scheduler.ts"); + +test.after(async () => { + scheduler.__setPassiveVerdictReaderForTesting(null); + delete process.env.PROXY_HEALTH_PASSIVE_SKIP; + await new Promise((resolve) => target.close(() => resolve())); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +async function freePort(): Promise { + const probe = net.createServer(); + await new Promise((resolve) => probe.listen(0, "127.0.0.1", () => resolve())); + const { port } = probe.address() as net.AddressInfo; + await new Promise((resolve) => probe.close(() => resolve())); + return port; +} + +async function seedProxy(): Promise { + const created = await proxiesDb.createProxy({ + name: `passive ${Date.now()}`, + type: "http", + host: "127.0.0.1", + port: await freePort(), + }); + return created!.id; +} + +async function sweepCapturingSummary(): Promise { + const lines: string[] = []; + const original = console.log; + console.log = (...args: unknown[]) => { + lines.push(args.map(String).join(" ")); + }; + try { + await scheduler.forceProxyHealthSweep(); + } finally { + console.log = original; + } + return lines.find((line) => line.includes("Sweep complete")) ?? ""; +} + +// A minimal forward proxy: absolute-form requests and CONNECT tunnels both +// reach the answering target above. Tunnel sockets are detached from the +// server, so they are tracked and destroyed on stop. +const tunnelSockets = new Set(); + +function startRelay(port: number): Promise { + const relay = http.createServer((req, res) => { + const upstream = http.request( + { host: "127.0.0.1", port: targetPort, method: req.method, path: "/probe" }, + (answer) => { + res.writeHead(answer.statusCode ?? 502); + answer.pipe(res); + } + ); + upstream.on("error", () => res.destroy()); + req.pipe(upstream); + }); + relay.on("connect", (_req, client, head) => { + tunnelSockets.add(client as net.Socket); + const socket = net.connect(targetPort, "127.0.0.1", () => { + client.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + socket.write(head); + socket.pipe(client); + client.pipe(socket); + }); + tunnelSockets.add(socket); + socket.on("error", () => client.destroy()); + client.on("error", () => socket.destroy()); + }); + return new Promise((resolve) => relay.listen(port, "127.0.0.1", () => resolve(relay))); +} + +function stopRelay(relay: http.Server): Promise { + for (const socket of tunnelSockets) socket.destroy(); + tunnelSockets.clear(); + relay.closeAllConnections(); + return new Promise((resolve) => relay.close(() => resolve())); +} + +test("unproven endpoint keeps the live probe (the sweep still decides)", async () => { + process.env.PROXY_HEALTH_PASSIVE_SKIP = "true"; + const id = await seedProxy(); + try { + scheduler.__setPassiveVerdictReaderForTesting(() => ({ skip: false, providers: ["solo"] })); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + scheduler.__setPassiveVerdictReaderForTesting(null); + await proxiesDb.deleteProxyById(id, { force: true }); + } +}); + +test("proven single-provider endpoint skips the redundant probe", async () => { + process.env.PROXY_HEALTH_PASSIVE_SKIP = "true"; + const id = await seedProxy(); + try { + scheduler.__setPassiveVerdictReaderForTesting(() => ({ skip: true, providers: ["solo"] })); + const summary = await sweepCapturingSummary(); + assert.match(summary, /0 tested/); + assert.match(summary, /passive-skipped \(recent success\)/); + } finally { + scheduler.__setPassiveVerdictReaderForTesting(null); + await proxiesDb.deleteProxyById(id, { force: true }); + } +}); + +test("proven multi-provider endpoint keeps the probe (one provider never skips another)", async () => { + process.env.PROXY_HEALTH_PASSIVE_SKIP = "true"; + const id = await seedProxy(); + try { + scheduler.__setPassiveVerdictReaderForTesting(() => ({ + skip: true, + providers: ["provider-a", "provider-b"], + })); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + scheduler.__setPassiveVerdictReaderForTesting(null); + await proxiesDb.deleteProxyById(id, { force: true }); + } +}); + +test("no skip probes as before (RED without the stub)", async () => { + process.env.PROXY_HEALTH_PASSIVE_SKIP = "true"; + const id = await seedProxy(); + try { + scheduler.__setPassiveVerdictReaderForTesting(() => ({ skip: false, providers: ["solo"] })); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + scheduler.__setPassiveVerdictReaderForTesting(null); + await proxiesDb.deleteProxyById(id, { force: true }); + } +}); + +test("flag off probes every proxy even with a skip stub", async () => { + delete process.env.PROXY_HEALTH_PASSIVE_SKIP; + const id = await seedProxy(); + try { + scheduler.__setPassiveVerdictReaderForTesting(() => ({ skip: true, providers: ["solo"] })); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + scheduler.__setPassiveVerdictReaderForTesting(null); + await proxiesDb.deleteProxyById(id, { force: true }); + } +}); + +test("skip beside a promoted peer keeps the cross-proxy promotion", async () => { + // Pure decision-level proof that the partition preserves the tip + // behavior: one answering peer (ok/200) proves the target alive, one + // status-less inconclusive peer on the same target is promoted to fail, + // and the passive skip lives outside that decision input entirely. + // The sweep-level test below then proves the skip is counted apart. + const decision = await import("../../src/lib/proxyHealth/decision.ts"); + const tipTarget = `http://127.0.0.1:${targetPort}/probe`; + const evidenced = decision.applyCrossProbeEvidence( + [ + { outcome: "ok", status: 200, target: tipTarget }, + { outcome: "inconclusive", status: null, target: tipTarget }, + ], + new Map() + ); + assert.equal(evidenced[0].outcome, "ok"); + assert.equal(evidenced[1].outcome, "fail"); +}); + +test("skip beside a live peer is counted apart and keeps its row", async () => { + // One answering peer (relay to the 200 target) is probed live while one + // endpoint skipped by the passive verdict stays out of the decision: the + // skip is scheduling-only (counted apart, registry row kept, no status). + // relays through it. The scheduler resolves TEST_URL once at module load, + // so this file wires the answering target before the scheduler import + // (top of file); nothing to override per test. + process.env.PROXY_HEALTH_PASSIVE_SKIP = "true"; + const relayPort = await freePort(); + const relay = await startRelay(relayPort); + const relayProxy = await proxiesDb.createProxy({ + name: `passive-promoted-relay ${Date.now()}`, + type: "http", + host: "127.0.0.1", + port: relayPort, + }); + const skippedProxy = await proxiesDb.createProxy({ + name: `passive-promoted-skipped ${Date.now()}`, + type: "http", + host: "127.0.0.1", + port: await freePort(), + }); + try { + scheduler.__resetTargetEvidenceForTesting(); + scheduler.__setPassiveVerdictReaderForTesting((id) => + id === skippedProxy!.id + ? { skip: true, providers: ["solo"] } + : { skip: false, providers: ["solo"] } + ); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested, 1 alive/); + assert.match(summary, /0 promoted/); + assert.match(summary, /1 passive-skipped \(recent success\)/); + assert.equal( + (await proxiesDb.getProxyById(skippedProxy!.id, { includeSecrets: false })) !== null, + true, + "skipped endpoint keeps its registry row" + ); + } finally { + scheduler.__setPassiveVerdictReaderForTesting(null); + await stopRelay(relay); + await proxiesDb.deleteProxyById(relayProxy!.id, { force: true }); + await proxiesDb.deleteProxyById(skippedProxy!.id, { force: true }); + } +}); diff --git a/tests/unit/proxy-health-passive-traffic.test.ts b/tests/unit/proxy-health-passive-traffic.test.ts new file mode 100644 index 000000000000..aac8f36d8441 --- /dev/null +++ b/tests/unit/proxy-health-passive-traffic.test.ts @@ -0,0 +1,249 @@ +/** + * Passive verdict end to end through the real log pipeline: production + * traffic written via the logger, flushed to storage, then folded by the + * real sweep reader — no verdict stub. A proxy with only attributed + * failures in the window keeps its live probe (only a proven single-provider + * success is redundant); a proxy with only transient errors is probed as + * before. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-passive-e2e-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; +process.env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES = "true"; +process.env.PROXY_HEALTH_TEST_STAGGER_MS = "0"; +process.env.PROXY_HEALTH_TEST_URL = "http://127.0.0.1:1/probe"; +process.env.PROXY_PASSIVE_WINDOW_MS = "600000"; +process.env.PROXY_HEALTH_PASSIVE_SKIP = "true"; +delete process.env.PROXY_AUTO_REMOVE; +delete process.env.PROXY_AUTO_DISABLE; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const proxyLogger = await import("../../src/lib/proxyLogger.ts"); +const scheduler = await import("../../src/lib/proxyHealth/scheduler.ts"); + +test.after(() => { + delete process.env.PROXY_HEALTH_PASSIVE_SKIP; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +async function freePort(): Promise { + const probe = net.createServer(); + await new Promise((resolve) => probe.listen(0, "127.0.0.1", () => resolve())); + const { port } = probe.address() as net.AddressInfo; + await new Promise((resolve) => probe.close(() => resolve())); + return port; +} + +async function sweepCapturingSummary(): Promise { + const lines: string[] = []; + const original = console.log; + console.log = (...args: unknown[]) => { + lines.push(args.map(String).join(" ")); + }; + try { + await scheduler.forceProxyHealthSweep(); + } finally { + console.log = original; + } + return lines.find((line) => line.includes("Sweep complete")) ?? ""; +} + +test("consecutive attributed production failures keep the live probe", async () => { + const port = await freePort(); + const created = await proxiesDb.createProxy({ + name: "passive-e2e", + type: "http", + host: "127.0.0.1", + port, + }); + try { + for (let i = 0; i < 3; i++) { + proxyLogger.logProxyEvent({ + status: "error", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "acme", + error: "connect ECONNREFUSED 127.0.0.1", + }); + } + proxyLogger.flushProxyLogsSync(); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + await proxiesDb.deleteProxyById(created!.id, { force: true }); + } +}); + +test("transient production errors keep the probe (ECONNRESET is neutral)", async () => { + const port = await freePort(); + const created = await proxiesDb.createProxy({ + name: "passive-e2e-neutral", + type: "http", + host: "127.0.0.1", + port, + }); + try { + for (let i = 0; i < 3; i++) { + proxyLogger.logProxyEvent({ + status: "error", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "acme", + error: "read ECONNRESET", + }); + } + proxyLogger.flushProxyLogsSync(); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + await proxiesDb.deleteProxyById(created!.id, { force: true }); + } +}); + +test("success for one provider never skips the probe others need (cross-provider guard)", async () => { + const port = await freePort(); + const created = await proxiesDb.createProxy({ + name: "passive-e2e-cross", + type: "http", + host: "127.0.0.1", + port, + }); + try { + proxyLogger.logProxyEvent({ + status: "success", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "provider-a", + }); + proxyLogger.logProxyEvent({ + status: "success", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "provider-b", + }); + proxyLogger.flushProxyLogsSync(); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + await proxiesDb.deleteProxyById(created!.id, { force: true }); + } +}); + +test("single-provider success skips the redundant probe", async () => { + const port = await freePort(); + const created = await proxiesDb.createProxy({ + name: "passive-e2e-single", + type: "http", + host: "127.0.0.1", + port, + }); + try { + proxyLogger.logProxyEvent({ + status: "success", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "provider-a", + }); + proxyLogger.flushProxyLogsSync(); + const summary = await sweepCapturingSummary(); + assert.match(summary, /0 tested/); + assert.match(summary, /passive-skipped \(recent success\)/); + } finally { + await proxiesDb.deleteProxyById(created!.id, { force: true }); + } +}); + +test("mixed window keeps the probe: failure then success", async () => { + const port = await freePort(); + const created = await proxiesDb.createProxy({ + name: "passive-e2e-mixed-fail-first", + type: "http", + host: "127.0.0.1", + port, + }); + try { + proxyLogger.logProxyEvent({ + status: "error", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "acme", + error: "connect ECONNREFUSED 127.0.0.1", + }); + await new Promise((resolve) => setTimeout(resolve, 15)); + proxyLogger.logProxyEvent({ + status: "success", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "acme", + }); + proxyLogger.flushProxyLogsSync(); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + await proxiesDb.deleteProxyById(created!.id, { force: true }); + } +}); + +test("mixed window keeps the probe: success then failure", async () => { + const port = await freePort(); + const created = await proxiesDb.createProxy({ + name: "passive-e2e-mixed-ok-first", + type: "http", + host: "127.0.0.1", + port, + }); + try { + proxyLogger.logProxyEvent({ + status: "success", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "acme", + }); + await new Promise((resolve) => setTimeout(resolve, 15)); + proxyLogger.logProxyEvent({ + status: "error", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "acme", + error: "connect ECONNREFUSED 127.0.0.1", + }); + proxyLogger.flushProxyLogsSync(); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + await proxiesDb.deleteProxyById(created!.id, { force: true }); + } +}); + +test("flag off probes everything despite attributed production failures", async () => { + delete process.env.PROXY_HEALTH_PASSIVE_SKIP; + const port = await freePort(); + const created = await proxiesDb.createProxy({ + name: "passive-e2e-off", + type: "http", + host: "127.0.0.1", + port, + }); + try { + for (let i = 0; i < 3; i++) { + proxyLogger.logProxyEvent({ + status: "error", + proxy: { type: "http", host: "127.0.0.1", port }, + provider: "acme", + error: "connect ECONNREFUSED 127.0.0.1", + }); + } + proxyLogger.flushProxyLogsSync(); + const summary = await sweepCapturingSummary(); + assert.match(summary, /1 tested/); + assert.ok(!summary.includes("passive-skipped"), `no passive skip, got: ${summary}`); + } finally { + process.env.PROXY_HEALTH_PASSIVE_SKIP = "true"; + await proxiesDb.deleteProxyById(created!.id, { force: true }); + } +}); diff --git a/tests/unit/proxy-health-passive-verdict.test.ts b/tests/unit/proxy-health-passive-verdict.test.ts new file mode 100644 index 000000000000..6b246f2d17c5 --- /dev/null +++ b/tests/unit/proxy-health-passive-verdict.test.ts @@ -0,0 +1,179 @@ +/** + * Passive skip proof from production traffic: unit cover for the attribution + * mapping and the order-independent fold. Every branch of the mapping is + * pinned: proxy-fault errors veto the skip, ambiguous/transient/refusal rows + * stay neutral. Any attributed failure in the window vetoes, whatever the + * order — only proven single-provider success (the provider guard lives in + * the scheduler) skips the probe. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { + hasProvenSuccessWithoutAttributedFailure, + isPassiveSweepSkipEnabled, + isProxyAttributedFailure, + passiveVerdictKey, + resolvePassiveWindowMs, +} = await import("../../src/lib/proxyHealth/passiveVerdict.ts"); + +test("attributed: connection refused degrades", () => { + assert.equal(isProxyAttributedFailure({ error: "connect ECONNREFUSED 10.0.0.1:8080" }), true); +}); + +test("attributed: DNS failure degrades", () => { + assert.equal(isProxyAttributedFailure({ error: "getaddrinfo ENOTFOUND proxy.local" }), true); + assert.equal(isProxyAttributedFailure({ error: "getaddrinfo EAI_AGAIN proxy.local" }), true); +}); + +test("attributed: unreachable host or network degrades", () => { + assert.equal(isProxyAttributedFailure({ error: "connect EHOSTUNREACH 10.0.0.1:8080" }), true); + assert.equal(isProxyAttributedFailure({ error: "connect ENETUNREACH 10.0.0.1:8080" }), true); + assert.equal(isProxyAttributedFailure({ error: "read ENOTCONN on proxy socket" }), true); + assert.equal(isProxyAttributedFailure({ error: "connect EHOSTDOWN 10.0.0.1:8080" }), true); + assert.equal(isProxyAttributedFailure({ error: "connect ENETDOWN 10.0.0.1:8080" }), true); +}); + +test("attributed: coded proxy-leg socket death degrades", () => { + assert.equal( + isProxyAttributedFailure({ error: "proxy socket ERR_SOCKET_CLOSED after CONNECT" }), + true + ); + assert.equal( + isProxyAttributedFailure({ error: "proxy socket ERR_SOCKET_TIMEOUT after CONNECT" }), + true + ); +}); + +test("attributed: stalled handshake degrades", () => { + assert.equal(isProxyAttributedFailure({ error: "socket hang up" }), true); +}); + +test("neutral: bare provider-side wording never blames the proxy", () => { + assert.equal( + isProxyAttributedFailure({ error: "upstream TLS error: provider cert expired" }), + false + ); + assert.equal( + isProxyAttributedFailure({ error: "DNS resolution timed out at local resolver" }), + false + ); + assert.equal(isProxyAttributedFailure({ error: "handshake stalled with upstream" }), false); + assert.equal(isProxyAttributedFailure({ error: "fetch failed: provider returned 502" }), false); +}); + +test("neutral: coded upstream TLS faults never blame the proxy", () => { + assert.equal( + isProxyAttributedFailure({ error: "CERT_HAS_EXPIRED for provider endpoint" }), + false + ); + assert.equal( + isProxyAttributedFailure({ error: "ERR_TLS_CERT_ALTNAME_INVALID on upstream host" }), + false + ); + assert.equal(isProxyAttributedFailure({ error: "TLSV1_ALERT_unknown_ca from upstream" }), false); + assert.equal(isProxyAttributedFailure({ error: "socket ERR_SOCKET error" }), false); +}); + +test("attributed: fetch failed with a bounded proxy code still degrades", () => { + assert.equal( + isProxyAttributedFailure({ error: "fetch failed: connect ECONNREFUSED 10.0.0.1" }), + true + ); +}); + +test("neutral: broken pipe is never attributed", () => { + assert.equal(isProxyAttributedFailure({ error: "write EPIPE on proxy socket" }), false); +}); + +test("neutral: aborted connection is never attributed", () => { + assert.equal(isProxyAttributedFailure({ error: "connect ECONNABORTED 10.0.0.1:8080" }), false); +}); + +test("neutral: generic network error is never attributed", () => { + assert.equal(isProxyAttributedFailure({ error: "fetch ERR_NETWORK from endpoint" }), false); +}); + +test("neutral: reset connection is never attributed", () => { + assert.equal(isProxyAttributedFailure({ error: "read ECONNRESET" }), false); +}); + +test("neutral: transient timeout is never attributed", () => { + assert.equal(isProxyAttributedFailure({ error: "connect ETIMEDOUT 10.0.0.1:8080" }), false); +}); + +test("neutral: any upstream answer means the proxy relayed", () => { + assert.equal( + isProxyAttributedFailure({ error: "connect ECONNREFUSED x", upstream_status: 500 }), + false + ); + assert.equal( + isProxyAttributedFailure({ error: "connect ECONNREFUSED x", upstream_status: 429 }), + false + ); +}); + +test("neutral: refused rows and refusal statuses stay neutral", () => { + assert.equal(isProxyAttributedFailure({ status: "blocked" }), false); + assert.equal(isProxyAttributedFailure({ status: "refused-by-target" }), false); + assert.equal(isProxyAttributedFailure({ status: "429" }), false); +}); + +test("neutral: empty error with no answer proves nothing", () => { + assert.equal(isProxyAttributedFailure({}), false); +}); + +test("fold: attributed failures alone prove nothing", () => { + const row = { status: "error", error: "connect ECONNREFUSED 10.0.0.1:8080" }; + assert.equal(hasProvenSuccessWithoutAttributedFailure([row, row, row]), false); +}); + +test("fold: recent success alone proves the skip", () => { + assert.equal(hasProvenSuccessWithoutAttributedFailure([{ status: "success" }]), true); +}); + +test("fold: abandoned sends prove nothing", () => { + assert.equal( + hasProvenSuccessWithoutAttributedFailure([{ status: "error", attempt_issue: "abandoned" }]), + false + ); +}); + +test("fold: empty window proves nothing", () => { + assert.equal(hasProvenSuccessWithoutAttributedFailure([]), false); +}); + +test("fold: any attributed failure vetoes the skip in either order", () => { + const fail = { status: "error", error: "connect ECONNREFUSED 10.0.0.1:8080" }; + const ok = { status: "success" }; + assert.equal(hasProvenSuccessWithoutAttributedFailure([fail, fail, ok, fail, fail]), false); + assert.equal(hasProvenSuccessWithoutAttributedFailure([fail, ok]), false); + assert.equal(hasProvenSuccessWithoutAttributedFailure([ok, fail]), false); +}); + +test("fold: success beside a neutral transient keeps the skip", () => { + const ok = { status: "success" }; + const neutral = { status: "error", error: "read ECONNRESET" }; + assert.equal(hasProvenSuccessWithoutAttributedFailure([ok, neutral]), true); +}); + +test("fold: neutrals alone prove nothing", () => { + const neutral = { status: "error", error: "read ECONNRESET" }; + assert.equal(hasProvenSuccessWithoutAttributedFailure([neutral, neutral, neutral]), false); +}); + +test("key: endpoint", () => { + assert.equal(passiveVerdictKey("h", 8080), "h:8080"); +}); + +test("env: window stays under the sweep interval", () => { + const windowMs = resolvePassiveWindowMs({}); + assert.ok(windowMs >= 60_000 && windowMs < 600_000); + assert.equal(resolvePassiveWindowMs({ PROXY_PASSIVE_WINDOW_MS: "99999999" }) < 600_000, true); +}); + +test("flag: passive sweep skip is opt-in, off by default", () => { + assert.equal(isPassiveSweepSkipEnabled({}), false); + assert.equal(isPassiveSweepSkipEnabled({ PROXY_HEALTH_PASSIVE_SKIP: "true" }), true); + assert.equal(isPassiveSweepSkipEnabled({ PROXY_HEALTH_PASSIVE_SKIP: "false" }), false); +}); diff --git a/tests/unit/proxy-operator-egress-i18n.test.ts b/tests/unit/proxy-operator-egress-i18n.test.ts new file mode 100644 index 000000000000..26bd5cf54367 --- /dev/null +++ b/tests/unit/proxy-operator-egress-i18n.test.ts @@ -0,0 +1,64 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +// The two new i18n keys are present in every locale catalog and wired to the +// production code that reads them: the operator-egress flag description (keyed +// by the flag definition) and the operator-provided member line (rendered by +// the component). The runtime falls back to English for an absent key, so this +// test pins the translated presence — 67 catalogs, no silent English-only +// feature. Both halves are asserted: the locale files AND the code that names +// them, so the test fails without either side of the PR. + +import { FEATURE_FLAG_DEFINITIONS } from "../../src/shared/constants/featureFlagDefinitions.ts"; + +const MESSAGES_DIR = path.join(process.cwd(), "src", "i18n", "messages"); + +test("operator-egress keys are present in all 67 locale catalogs and wired to code", () => { + // Code side: the flag definition names the description key the locales carry. + const definition = FEATURE_FLAG_DEFINITIONS.find( + (d) => d.key === "PROXY_OPERATOR_EGRESS_ENABLED" + ); + assert.ok(definition, "PROXY_OPERATOR_EGRESS_ENABLED is defined"); + assert.equal(definition.descriptionI18nKey, "featureFlagProxyOperatorEgressDescription"); + + // Component side: the member lines render the operator key (fails on the base + // component, which never names it). + const component = fs.readFileSync( + path.join( + process.cwd(), + "src", + "app", + "(dashboard)", + "dashboard", + "settings", + "components", + "PoolMemberEgressLines.tsx" + ), + "utf8" + ); + assert.ok( + component.includes("poolMemberEgressOperator"), + "PoolMemberEgressLines renders poolMemberEgressOperator" + ); + + // Locale side: every catalog carries both keys. + const files = fs.readdirSync(MESSAGES_DIR).filter((f) => f.endsWith(".json")); + assert.equal(files.length, 67); + const missing: string[] = []; + for (const file of files) { + const data = JSON.parse(fs.readFileSync(path.join(MESSAGES_DIR, file), "utf8")) as Record< + string, + unknown + >; + if (typeof data.featureFlagProxyOperatorEgressDescription !== "string") { + missing.push(`${file}:featureFlagProxyOperatorEgressDescription`); + } + const registry = data.proxyRegistry as Record | undefined; + if (typeof registry?.poolMemberEgressOperator !== "string") { + missing.push(`${file}:proxyRegistry.poolMemberEgressOperator`); + } + } + assert.deepEqual(missing, []); +}); diff --git a/tests/unit/proxy-operator-egress-migration.test.ts b/tests/unit/proxy-operator-egress-migration.test.ts new file mode 100644 index 000000000000..8647fb030bca --- /dev/null +++ b/tests/unit/proxy-operator-egress-migration.test.ts @@ -0,0 +1,62 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// Operator-provided dated egress observations live in their own table so a push can never +// touch the proxy log. These tests pin the migration: the table, its PK, and its index +// exist after applying the file, and re-running the file is a no-op. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-operator-egress-mig-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); + +const MIGRATION_FILE = "199_proxy_operator_egress.sql"; +const MIGRATION_DIR = path.join(process.cwd(), "src", "lib", "db", "migrations"); + +function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("the migration file exists and is safe to re-run", async () => { + resetStorage(); + const file = path.join(MIGRATION_DIR, MIGRATION_FILE); + assert.ok(fs.existsSync(file), `missing ${MIGRATION_FILE}`); + const sql = fs.readFileSync(file, "utf8"); + assert.match(sql, /CREATE TABLE IF NOT EXISTS proxy_operator_egress/); + assert.match(sql, /PRIMARY KEY\s*\(\s*host\s*,\s*port\s*,\s*address\s*\)/); + assert.match(sql, /CREATE INDEX IF NOT EXISTS idx_poe_member/); + + // Fresh DB: the table is created on init (core runs migrations), so it must exist. + const db = core.getDbInstance(); + const table = db + .prepare( + "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'proxy_operator_egress'" + ) + .get() as { name?: string } | undefined; + assert.equal(table?.name, "proxy_operator_egress"); + const index = db + .prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx_poe_member'") + .get() as { name?: string } | undefined; + assert.equal(index?.name, "idx_poe_member"); + + // Re-run is a no-op: applying the migration SQL again must not throw. + db.exec(sql); + db.prepare( + "INSERT INTO proxy_operator_egress (host, port, address, observed_at) VALUES (?, ?, ?, ?)" + ).run("203.0.113.1", 8080, "198.51.100.1", new Date().toISOString()); + db.exec(sql); + const count = db.prepare("SELECT COUNT(*) AS n FROM proxy_operator_egress").get() as { + n: number; + }; + assert.equal(count.n, 1); +}); diff --git a/tests/unit/proxy-operator-egress-reader.test.ts b/tests/unit/proxy-operator-egress-reader.test.ts new file mode 100644 index 000000000000..50ee050e6685 --- /dev/null +++ b/tests/unit/proxy-operator-egress-reader.test.ts @@ -0,0 +1,203 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// The integrated reader merges the journal single-address read with the fresh +// operator rows: three operator addresses read as a set of three, the freshest +// observation wins in both directions (20 h vs 2 min), the flag-off path is +// byte-identical to the base behavior with zero operator DB read, and a row +// that crosses the window disappears on the next read even on the hot journal +// cache path (no operator cache). The predicate fires on any shared address, +// with the journal contributing only when non-opaque. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-operator-egress-reader-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; +delete process.env.INITIAL_PASSWORD; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const proxyLogger = await import("../../src/lib/proxyLogger.ts"); +const rotation = await import("../../src/lib/db/proxies/rotation.ts"); +const store = await import("../../src/lib/db/proxyOperatorEgress.ts"); + +const HOUR_MS = 60 * 60 * 1000; + +function resetStorage() { + process.env.PROXY_OPERATOR_EGRESS_ENABLED = "true"; + delete process.env.PROXY_OPERATOR_EGRESS_WINDOW_HOURS; + rotation.__resetHotEgressCacheForTesting(); + proxyLogger.clearProxyLogs(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(() => { + resetStorage(); +}); + +test.after(() => { + delete process.env.PROXY_OPERATOR_EGRESS_ENABLED; + delete process.env.PROXY_OPERATOR_EGRESS_WINDOW_HOURS; + rotation.__resetHotEgressCacheForTesting(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function member(host: string, port: number) { + return { type: "http", host, port }; +} + +function push(host: string, port: number, addresses: string[], observedAt: string) { + return store.upsertOperatorEgress([{ host, port, addresses, observedAt }]); +} + +function logEgress(host: string, port: number, egressIp: string | null) { + proxyLogger.logProxyEvent({ + status: "success", + provider: "opencode", + targetUrl: "https://api.opencode.ai/chat", + proxy: { type: "http", host, port }, + egressIp, + }); + proxyLogger.flushProxyLogsSync(); +} + +async function knownMember(host: string, port: number): Promise { + await proxiesDb.createProxy({ name: `op-egress ${host}:${port}`, type: "http", host, port }); +} + +test("three operator addresses read as a set of three", async () => { + await knownMember("203.0.113.1", 8180); + const now = Date.now(); + push( + "203.0.113.1", + 8180, + ["198.51.100.1", "198.51.100.2", "198.51.100.3"], + new Date(now).toISOString() + ); + const set = rotation.readEgressAddressSetForMember(member("203.0.113.1", 8180), now); + assert.deepEqual([...set.addresses].sort(), ["198.51.100.1", "198.51.100.2", "198.51.100.3"]); +}); + +test("the freshest observation wins: 20 h operator row loses to a 2 min probe", async () => { + await knownMember("203.0.113.2", 8181); + const now = Date.now(); + push("203.0.113.2", 8181, ["198.51.100.10"], new Date(now - 20 * HOUR_MS).toISOString()); + logEgress("203.0.113.2", 8181, "198.51.100.20"); + const set = rotation.readEgressAddressSetForMember(member("203.0.113.2", 8181), now); + assert.ok(set.addresses.has("198.51.100.10")); + assert.ok(set.addresses.has("198.51.100.20")); + assert.equal(set.freshest?.address, "198.51.100.20"); +}); + +test("the freshest observation wins, flipped: fresh operator row beats a stale probe", async () => { + await knownMember("203.0.113.3", 8182); + const now = Date.now(); + logEgress("203.0.113.3", 8182, "198.51.100.30"); + // Backdate the probe row 20 h: the logger stamps Date.now(), so shift it after. + core + .getDbInstance() + .prepare("UPDATE proxy_logs SET timestamp = ? WHERE proxy_host = ? AND proxy_port = ?") + .run(new Date(now - 20 * HOUR_MS).toISOString(), "203.0.113.3", 8182); + rotation.__resetHotEgressCacheForTesting(); + push("203.0.113.3", 8182, ["198.51.100.31"], new Date(now).toISOString()); + const set = rotation.readEgressAddressSetForMember(member("203.0.113.3", 8182), now); + assert.equal(set.freshest?.address, "198.51.100.31"); +}); + +test("flag off reads exactly the base behavior with zero operator DB read", async () => { + await knownMember("203.0.113.4", 8183); + const now = Date.now(); + push("203.0.113.4", 8183, ["198.51.100.40"], new Date(now).toISOString()); + delete process.env.PROXY_OPERATOR_EGRESS_ENABLED; + rotation.__resetHotEgressCacheForTesting(); + + const db = core.getDbInstance(); + let operatorReads = 0; + const originalPrepare = db.prepare.bind(db); + (db as { prepare: typeof originalPrepare }).prepare = ((sql: string, ...rest: unknown[]) => { + if (sql.includes("proxy_operator_egress")) operatorReads++; + return (originalPrepare as (...args: unknown[]) => unknown)(sql, ...rest); + }) as typeof originalPrepare; + try { + const set = rotation.readEgressAddressSetForMember(member("203.0.113.4", 8183), now); + assert.deepEqual([...set.addresses], []); + assert.equal(operatorReads, 0); + } finally { + (db as { prepare: typeof originalPrepare }).prepare = originalPrepare; + } +}); + +test("cache-then-expire: a row past the window is absent even on the hot cache path", async () => { + await knownMember("203.0.113.5", 8184); + process.env.PROXY_OPERATOR_EGRESS_WINDOW_HOURS = "1"; + const start = Date.now(); + push("203.0.113.5", 8184, ["198.51.100.50"], new Date(start).toISOString()); + // Warm the journal cache path first (reads through it, locks the expiry case). + const fresh = rotation.readEgressAddressSetForMember(member("203.0.113.5", 8184), start + 1000); + assert.ok(fresh.addresses.has("198.51.100.50")); + // Advance past the 1 h window but inside the 30 s journal TTL: the operator + // row must be absent — there is no operator cache to go stale behind. + const expired = rotation.readEgressAddressSetForMember( + member("203.0.113.5", 8184), + start + 2 * HOUR_MS + ); + assert.ok(!expired.addresses.has("198.51.100.50")); +}); + +test("the predicate fires on any shared operator address", async () => { + process.env.PROXY_SKIP_RECENTLY_FAILED = "true"; + process.env.PROXY_POOL_SHARED_EGRESS_ORDER = "true"; + const { noteProxyRefusal, proxyEgressKey, __resetProxyRefusalMemoryForTesting } = + await import("../../open-sse/utils/proxyRefusalMemory.ts"); + try { + await knownMember("203.0.113.6", 8185); + await knownMember("203.0.113.7", 8186); + const now = Date.now(); + const at = new Date(now).toISOString(); + push("203.0.113.6", 8185, ["198.51.100.60", "198.51.100.61"], at); + push("203.0.113.7", 8186, ["198.51.100.61", "198.51.100.62"], at); + const avoided = member("203.0.113.6", 8185); + const candidate = member("203.0.113.7", 8186); + noteProxyRefusal(proxyEgressKey(avoided), "ip_quota_429"); + const predicate = rotation.buildHotEgressPredicate( + "opencode", + [avoided, candidate], + undefined, + now + ); + assert.equal(predicate(candidate), true); + assert.equal(predicate(member("203.0.113.8", 8187)), false); + } finally { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + delete process.env.PROXY_POOL_SHARED_EGRESS_ORDER; + __resetProxyRefusalMemoryForTesting(); + } +}); + +test("the journal contributes to the predicate only when non-opaque", async () => { + process.env.PROXY_SKIP_RECENTLY_FAILED = "true"; + process.env.PROXY_POOL_SHARED_EGRESS_ORDER = "true"; + const { noteProxyRefusal, proxyEgressKey, __resetProxyRefusalMemoryForTesting } = + await import("../../open-sse/utils/proxyRefusalMemory.ts"); + try { + await knownMember("203.0.113.9", 8188); + const now = Date.now(); + // Opaque journal: two distinct addresses — contributes nothing. + logEgress("203.0.113.9", 8188, "198.51.100.70"); + logEgress("203.0.113.9", 8188, "198.51.100.71"); + const opaque = member("203.0.113.9", 8188); + noteProxyRefusal(proxyEgressKey(opaque), "ip_quota_429"); + const predicate = rotation.buildHotEgressPredicate("opencode", [opaque], undefined, now); + // Empty hot set (opaque journal, no operator rows) -> predicate false for all. + assert.equal(predicate(opaque), false); + } finally { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + delete process.env.PROXY_POOL_SHARED_EGRESS_ORDER; + __resetProxyRefusalMemoryForTesting(); + } +}); diff --git a/tests/unit/proxy-operator-egress-route.test.ts b/tests/unit/proxy-operator-egress-route.test.ts new file mode 100644 index 000000000000..ac8923959a82 --- /dev/null +++ b/tests/unit/proxy-operator-egress-route.test.ts @@ -0,0 +1,246 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// The operator-egress push route: an authenticated operator pushes dated observed +// addresses per pool member. These tests pin the auth-first order (401 wins over 404 +// when the flag is off), the flag-off 404 with zero DB read/write, the six schema +// rejections, the unknown-member `ignored` count, and the happy-path response shape. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-operator-egress-route-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD; + +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const settingsDb = await import("../../src/lib/db/settings.ts"); +const store = await import("../../src/lib/db/proxyOperatorEgress.ts"); +const { POST } = await import("../../src/app/api/settings/proxies/operator-egress/route.ts"); + +function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(async () => { + await resetStorage(); + process.env.INITIAL_PASSWORD = "operator-egress-password"; + await settingsDb.updateSettings({ requireLogin: true, password: "" }); + process.env.PROXY_OPERATOR_EGRESS_ENABLED = "true"; +}); + +test.after(async () => { + await resetStorage(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; + else process.env.DATA_DIR = ORIGINAL_DATA_DIR; + if (ORIGINAL_INITIAL_PASSWORD === undefined) delete process.env.INITIAL_PASSWORD; + else process.env.INITIAL_PASSWORD = ORIGINAL_INITIAL_PASSWORD; + delete process.env.PROXY_OPERATOR_EGRESS_ENABLED; +}); + +function pushRequest(body: unknown): Request { + return new Request("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); +} + +function validBody() { + return { + version: 1, + members: [ + { + host: "203.0.113.1", + port: 8080, + addresses: ["198.51.100.1", "198.51.100.2", "198.51.100.3"], + observedAt: new Date().toISOString(), + }, + ], + }; +} + +async function knownMember(host: string, port: number): Promise { + await proxiesDb.createProxy({ name: `op-egress ${host}:${port}`, type: "http", host, port }); +} + +function operatorRowCount(): number { + return ( + core.getDbInstance().prepare("SELECT COUNT(*) AS n FROM proxy_operator_egress").get() as { + n: number; + } + ).n; +} + +test("auth comes first: no auth answers 401 even with the flag off", async () => { + delete process.env.PROXY_OPERATOR_EGRESS_ENABLED; + const response = await POST(pushRequest(validBody())); + assert.equal(response.status, 401); +}); + +test("flag off with auth answers 404 and touches nothing", async () => { + delete process.env.PROXY_OPERATOR_EGRESS_ENABLED; + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(validBody()), + }) + ); + assert.equal(response.status, 404); + assert.equal(operatorRowCount(), 0); +}); + +test("flag on without auth answers 401", async () => { + const response = await POST(pushRequest(validBody())); + assert.equal(response.status, 401); +}); + +test("a non-literal host is rejected", async () => { + await knownMember("203.0.113.1", 8080); + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const body = validBody(); + body.members[0].host = "proxy.example.com"; + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(body), + }) + ); + assert.equal(response.status, 400); +}); + +test("a loopback address is rejected", async () => { + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const body = validBody(); + body.members[0].addresses = ["127.0.0.1"]; + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(body), + }) + ); + assert.equal(response.status, 400); +}); + +test("a link-local address is rejected", async () => { + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const body = validBody(); + body.members[0].addresses = ["169.254.10.20"]; + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(body), + }) + ); + assert.equal(response.status, 400); +}); + +test("a private address is rejected", async () => { + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const body = validBody(); + body.members[0].addresses = ["10.0.0.5"]; + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(body), + }) + ); + assert.equal(response.status, 400); +}); + +test("an observedAt beyond the +5 min future tolerance is rejected", async () => { + await knownMember("203.0.113.1", 8080); + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const body = validBody(); + body.members[0].observedAt = new Date(Date.now() + 60 * 60_000).toISOString(); + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(body), + }) + ); + assert.equal(response.status, 400); +}); + +test("batch and member bounds are rejected", async () => { + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const tooManyAddresses = validBody(); + tooManyAddresses.members[0].addresses = Array.from( + { length: 11 }, + (_, i) => `198.51.100.${i + 1}` + ); + const overMember = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(tooManyAddresses), + }) + ); + assert.equal(overMember.status, 400); + + const tooManyMembers = { + version: 1, + members: Array.from({ length: 101 }, (_, i) => ({ + host: "203.0.113.1", + port: 8000 + i, + addresses: ["198.51.100.1"], + observedAt: new Date().toISOString(), + })), + }; + const overBatch = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(tooManyMembers), + }) + ); + assert.equal(overBatch.status, 400); +}); + +test("an unknown member is counted ignored, never a batch error", async () => { + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(validBody()), + }) + ); + assert.equal(response.status, 200); + const body = (await response.json()) as { + version: number; + stored: number; + ignored: number; + rejected: unknown[]; + }; + assert.equal(body.version, 1); + assert.equal(body.stored, 3); + assert.equal(body.ignored, 3); + assert.deepEqual(body.rejected, []); +}); + +test("happy path: three addresses for one member are stored", async () => { + await knownMember("203.0.113.1", 8080); + const { makeManagementSessionRequest } = await import("../helpers/managementSession.ts"); + const response = await POST( + await makeManagementSessionRequest("http://localhost/api/settings/proxies/operator-egress", { + method: "POST", + body: JSON.stringify(validBody()), + }) + ); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { + version: 1, + stored: 3, + ignored: 0, + rejected: [], + }); + const now = Date.now(); + const read = store.readOperatorEgressForMember("203.0.113.1", 8080, now); + assert.equal(read.addresses.size, 3); +}); diff --git a/tests/unit/proxy-operator-egress-store.test.ts b/tests/unit/proxy-operator-egress-store.test.ts new file mode 100644 index 000000000000..f67883ac670f --- /dev/null +++ b/tests/unit/proxy-operator-egress-store.test.ts @@ -0,0 +1,229 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// The operator-provided egress observation store: dated observed addresses per pool member. +// These tests pin the contracts the reader and the push route rely on: idempotent upsert, +// freshest-wins in both orders and under concurrent pushes, the adjustable bounded window +// (stale is absent, invalid window falls back to 24 h with a warning and never throws), +// the per-member and total caps with oldest-first eviction, purge on write, unknown-member +// rows kept while fresh, and the shared address normalization reused on every read. + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-operator-egress-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-secret"; +delete process.env.INITIAL_PASSWORD; + +const core = await import("../../src/lib/db/core.ts"); +const proxiesDb = await import("../../src/lib/db/proxies.ts"); +const store = await import("../../src/lib/db/proxyOperatorEgress.ts"); + +const HOUR_MS = 60 * 60 * 1000; +let nextPort = 8100; + +async function knownMember(host: string, port: number): Promise { + await proxiesDb.createProxy({ name: `op-egress ${host}:${port}`, type: "http", host, port }); +} + +function resetStorage() { + delete process.env.PROXY_OPERATOR_EGRESS_WINDOW_HOURS; + store.__resetOperatorEgressWarnedForTesting?.(); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(() => { + resetStorage(); +}); + +test.after(() => { + delete process.env.PROXY_OPERATOR_EGRESS_WINDOW_HOURS; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function iso(nowMs: number, ageMs = 0): string { + return new Date(nowMs - ageMs).toISOString(); +} + +function push(host: string, port: number, addresses: string[], observedAt: string) { + return store.upsertOperatorEgress([{ host, port, addresses, observedAt }]); +} + +test("upsert is idempotent: the same row twice stays one row", async () => { + await knownMember("203.0.113.1", 8080); + const now = Date.now(); + const at = iso(now); + assert.deepEqual(push("203.0.113.1", 8080, ["198.51.100.1"], at), { + stored: 1, + ignored: 0, + rejected: [], + }); + assert.deepEqual(push("203.0.113.1", 8080, ["198.51.100.1"], at), { + stored: 1, + ignored: 0, + rejected: [], + }); + const read = store.readOperatorEgressForMember("203.0.113.1", 8080, now); + assert.deepEqual([...read.addresses], ["198.51.100.1"]); + assert.equal(read.freshest?.address, "198.51.100.1"); +}); + +test("the freshest observation wins in both orders", async () => { + await knownMember("203.0.113.2", 8081); + await knownMember("203.0.113.3", 8082); + const now = Date.now(); + const older = iso(now, HOUR_MS); + const newer = iso(now); + push("203.0.113.2", 8081, ["198.51.100.2"], older); + push("203.0.113.2", 8081, ["198.51.100.2"], newer); + assert.equal(store.readOperatorEgressForMember("203.0.113.2", 8081, now).freshest?.at, newer); + + push("203.0.113.3", 8082, ["198.51.100.3"], newer); + push("203.0.113.3", 8082, ["198.51.100.3"], older); + assert.equal(store.readOperatorEgressForMember("203.0.113.3", 8082, now).freshest?.at, newer); +}); + +test("concurrent pushes of the same member keep the freshest row", async () => { + await knownMember("203.0.113.4", 8083); + const now = Date.now(); + const older = iso(now, HOUR_MS); + const newer = iso(now); + await Promise.all([ + Promise.resolve().then(() => push("203.0.113.4", 8083, ["198.51.100.4"], older)), + Promise.resolve().then(() => push("203.0.113.4", 8083, ["198.51.100.4"], newer)), + ]); + assert.equal(store.readOperatorEgressForMember("203.0.113.4", 8083, now).freshest?.at, newer); +}); + +test("a row older than the window is absent; a row inside the +5 min tolerance is fresh", async () => { + await knownMember("203.0.113.5", 8084); + await knownMember("203.0.113.6", 8085); + const now = Date.now(); + push("203.0.113.5", 8084, ["198.51.100.5"], iso(now, 25 * HOUR_MS)); + assert.deepEqual([...store.readOperatorEgressForMember("203.0.113.5", 8084, now).addresses], []); + push("203.0.113.6", 8085, ["198.51.100.6"], new Date(now + 4 * 60_000).toISOString()); + assert.deepEqual( + [...store.readOperatorEgressForMember("203.0.113.6", 8085, now).addresses], + ["198.51.100.6"] + ); +}); + +test("the window is adjustable, bounded, and never throws", async () => { + await knownMember("203.0.113.7", 8086); + const now = Date.now(); + const at = iso(now, 2 * HOUR_MS); + push("203.0.113.7", 8086, ["198.51.100.7"], at); + assert.deepEqual( + [...store.readOperatorEgressForMember("203.0.113.7", 8086, now).addresses], + ["198.51.100.7"] + ); + + process.env.PROXY_OPERATOR_EGRESS_WINDOW_HOURS = "1"; + assert.deepEqual([...store.readOperatorEgressForMember("203.0.113.7", 8086, now).addresses], []); + + for (const invalid of ["banana", "0", "9999", ""]) { + store.__resetOperatorEgressWarnedForTesting?.(); + let warned = 0; + const original = console.warn; + console.warn = () => { + warned++; + }; + try { + process.env.PROXY_OPERATOR_EGRESS_WINDOW_HOURS = invalid; + const read = store.readOperatorEgressForMember("203.0.113.7", 8086, now); + assert.deepEqual([...read.addresses], ["198.51.100.7"]); + } finally { + console.warn = original; + } + assert.equal(warned, 1, `invalid window ${JSON.stringify(invalid)} warns once`); + } +}); + +test("a member keeps at most its 10 freshest addresses", async () => { + await knownMember("203.0.113.8", 8087); + const now = Date.now(); + const addresses = Array.from({ length: 12 }, (_, i) => `198.51.100.${10 + i}`); + const at = iso(now); + const result = push("203.0.113.8", 8087, addresses, at); + assert.equal(result.rejected.filter((r) => r.reason === "cap-evicted").length, 2); + const read = store.readOperatorEgressForMember("203.0.113.8", 8087, now); + assert.equal(read.addresses.size, 10); +}); + +test("beyond 2000 fresh rows the oldest rows are evicted first, counted as rejected", async () => { + const now = Date.now(); + const ports: number[] = []; + for (let i = 0; i < 220; i++) { + nextPort++; + ports.push(nextPort); + await knownMember(`203.0.113.${100 + (i % 25)}`, nextPort); + } + const members = ports.map((port, i) => ({ + host: `203.0.113.${100 + (i % 25)}`, + port, + addresses: Array.from({ length: 10 }, (_, k) => `198.51.${i % 250}.${k + 1}`), + observedAt: iso(now, (i % 10) * HOUR_MS), + })); + const totals = { stored: 0, rejected: 0 }; + for (const chunk of chunkBy(members, 25)) { + const result = store.upsertOperatorEgress(chunk); + totals.stored += result.stored; + totals.rejected += result.rejected.length; + } + assert.ok(totals.rejected > 0, "surplus rows are counted, never a batch error"); + const db = core.getDbInstance(); + const count = db.prepare("SELECT COUNT(*) AS n FROM proxy_operator_egress").get() as { + n: number; + }; + assert.ok(count.n <= 2000, `total stays bounded, got ${count.n}`); +}); + +test("stale rows are purged on write", async () => { + await knownMember("203.0.113.9", 8088); + await knownMember("203.0.113.10", 8089); + const now = Date.now(); + push("203.0.113.9", 8088, ["198.51.100.9"], iso(now, 25 * HOUR_MS)); + push("203.0.113.10", 8089, ["198.51.100.10"], iso(now)); + const stale = store.readOperatorEgressForMember("203.0.113.9", 8088, now); + assert.deepEqual([...stale.addresses], []); + const db = core.getDbInstance(); + const rows = db + .prepare("SELECT COUNT(*) AS n FROM proxy_operator_egress WHERE host = ?") + .get("203.0.113.9") as { n: number }; + assert.equal(rows.n, 0); +}); + +test("rows for an unknown member are kept while fresh and purged at expiry", () => { + const now = Date.now(); + // 203.0.113.250 is documentation range: never in the test registry. + push("203.0.113.250", 8999, ["198.51.100.250"], iso(now)); + assert.deepEqual( + [...store.readOperatorEgressForMember("203.0.113.250", 8999, now).addresses], + ["198.51.100.250"] + ); + push("203.0.113.251", 8998, ["198.51.100.251"], iso(now)); + const db = core.getDbInstance(); + const kept = db + .prepare("SELECT COUNT(*) AS n FROM proxy_operator_egress WHERE host = ?") + .get("203.0.113.250") as { n: number }; + assert.equal(kept.n, 1); +}); + +test("every returned address passes the shared ranking normalization", async () => { + await knownMember("203.0.113.11", 8090); + const now = Date.now(); + const at = iso(now); + push("203.0.113.11", 8090, ["::ffff:198.51.100.11"], at); + const read = store.readOperatorEgressForMember("203.0.113.11", 8090, now); + assert.deepEqual([...read.addresses], ["198.51.100.11"]); +}); + +function chunkBy(items: T[], size: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size)); + return chunks; +} diff --git a/tests/unit/proxy-pool-member-egress-route.test.ts b/tests/unit/proxy-pool-member-egress-route.test.ts index 0836ccc48d4f..fe0e14298e94 100644 --- a/tests/unit/proxy-pool-member-egress-route.test.ts +++ b/tests/unit/proxy-pool-member-egress-route.test.ts @@ -93,7 +93,9 @@ test("reports a member with no traffic as a null member instead of hiding it", a const body = (await response.json()) as { members: Array<{ host: string; port: number; egressIp: string | null; at: string | null }>; }; - assert.deepEqual(body.members, [{ host: "10.8.2.2", port: 22002, egressIp: null, at: null }]); + assert.deepEqual(body.members, [ + { host: "10.8.2.2", port: 22002, egressIp: null, at: null, source: null }, + ]); }); test("excludes stale rows older than the window", async () => { diff --git a/tests/unit/proxy-pool-visibility.test.ts b/tests/unit/proxy-pool-visibility.test.ts index 735a4e73aea3..cb66df5731fb 100644 --- a/tests/unit/proxy-pool-visibility.test.ts +++ b/tests/unit/proxy-pool-visibility.test.ts @@ -25,7 +25,13 @@ const { snapshotProxySetAside, snapshotMemberSetAside, REFUSAL_POLICIES, + countTransportEvidenceFor, + getRefusalStoreInstance, + recordTransportFailure, + recordTransportSuccess, + TRANSPORT_EVIDENCE_WINDOW_MS, __resetProxyRefusalMemoryForTesting, + __resetTransportEvidenceForTesting, __proxyRefusalMemorySizeForTesting, } = await import("../../open-sse/utils/proxyRefusalMemory.ts"); const { GET } = await import("../../src/app/api/admin/proxy-pool-visibility/route.ts"); @@ -183,6 +189,74 @@ test("proxyEgressKey never carries a password", () => { assert.ok(!String(key).includes("s3cret")); }); +test("pool visibility exposes transport proof and store instance additively", async () => { + const warn = console.warn; + console.warn = () => {}; + try { + const created = await proxiesDb.createProxy({ + name: "transport proof fixture", + type: "http", + host: "203.0.113.41", + port: 8080, + username: "user", + }); + try { + const key = proxyEgressKey(created); + assert.ok(key); + const peer = proxyEgressKey({ type: "http", host: "203.0.113.42", port: 8080 }); + assert.ok(peer); + const t0 = Date.now(); + const destination = "example.com"; + recordTransportFailure(key, destination, t0); + recordTransportFailure(key, destination, t0 + 1); + recordTransportFailure(key, destination, t0 + 2); + recordTransportSuccess(destination, peer, t0 + 3); + const expected = countTransportEvidenceFor(key, Date.now()); + assert.equal(expected.failures, 3); + assert.equal(expected.crossSuccesses, 1); + const res = await GET(req(`/api/admin/proxy-pool-visibility?proxyId=${created.id}`)); + if (res.status === 401 || res.status === 403) return; + assert.equal(res.status, 200); + const body = (await jsonOf(res)) as { + members: Array<{ + opaque: boolean; + display: string; + userMasked: string | null; + transportEvidence: { failures: number; crossSuccesses: number; windowMs: number }; + storeInstance: string; + }>; + }; + assert.equal(body.members.length, 1); + const member = body.members[0]; + assert.equal(member.opaque, false); + assert.deepEqual(member.transportEvidence, { + failures: expected.failures, + crossSuccesses: expected.crossSuccesses, + windowMs: TRANSPORT_EVIDENCE_WINDOW_MS, + }); + assert.equal(member.storeInstance, getRefusalStoreInstance()); + // Existing fields unchanged: no key, no password, display stays user-free. + assert.ok(!("password" in member)); + assert.ok(!String(member.display).includes("@")); + assert.ok(hasLeak(member.transportEvidence, ["s3cret", "user", "@"]) === null); + assert.match(member.storeInstance, /^[0-9a-f]{8}$/); + // The instance proof is stable across two reads. + const second = await GET(req(`/api/admin/proxy-pool-visibility?proxyId=${created.id}`)); + if (second.status === 401 || second.status === 403) return; + const again = (await jsonOf(second)) as { + members: Array<{ transportEvidence: unknown; storeInstance: string }>; + }; + assert.equal(again.members[0].storeInstance, member.storeInstance); + await proxiesDb.deleteProxyById(created.id); + } finally { + __resetProxyRefusalMemoryForTesting(); + __resetTransportEvidenceForTesting(); + } + } finally { + console.warn = warn; + } +}); + test("GET single entry exposes selector member set-aside state", async () => { try { const created = await proxiesDb.createProxy({ diff --git a/tests/unit/proxy-refusal-memory-observability.test.ts b/tests/unit/proxy-refusal-memory-observability.test.ts new file mode 100644 index 000000000000..b8d1df0a0e6a --- /dev/null +++ b/tests/unit/proxy-refusal-memory-observability.test.ts @@ -0,0 +1,231 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Observability of the refusal store: one log line per write (kind, period, +// repeat count) with a credential-free label, a readable proof in pool +// visibility (transport evidence plus store instance), and a single warning +// when the module loads twice. Entries come from the production reader +// proxyEgressKey (same order, same fields), never hand-written tables. + +const memory = await import("../../open-sse/utils/proxyRefusalMemory.ts"); +const START_MS = 1_800_000_000_000; + +function captureWarn() { + const lines: string[] = []; + const original = console.warn; + console.warn = (...args: unknown[]) => { + lines.push(args.map((a) => String(a)).join(" ")); + }; + return { lines, restore: () => void (console.warn = original) }; +} + +test.beforeEach(() => { + memory.__resetProxyRefusalMemoryForTesting(); + memory.__resetTransportEvidenceForTesting(); +}); + +test("a transport write logs one line with kind, period and streak", () => { + const cap = captureWarn(); + try { + const key = memory.proxyEgressKey({ + type: "http", + host: "203.0.113.7", + port: 8080, + username: "user", + password: "s3cret", + }); + assert.ok(key); + const period = memory.noteProxyRefusal(key, "transport", START_MS); + assert.ok(typeof period === "number" && period > 0); + const writes = cap.lines.filter((l) => l.includes("set aside")); + assert.equal(writes.length, 1); + assert.ok(writes[0].includes("kind=transport")); + assert.ok(writes[0].includes(`periodMs=${period}`)); + assert.ok(writes[0].includes("streak=1")); + assert.ok(!writes[0].includes("user")); + assert.ok(!writes[0].includes("s3cret")); + } finally { + cap.restore(); + } +}); + +test("a second write inside the period logs nothing", () => { + const cap = captureWarn(); + try { + const key = memory.proxyEgressKey({ type: "http", host: "203.0.113.8", port: 8080 }); + assert.ok(key); + assert.ok(memory.noteProxyRefusal(key, "transport", START_MS) !== null); + const afterFirst = cap.lines.filter((l) => l.includes("set aside")).length; + assert.equal(afterFirst, 1); + assert.equal(memory.noteProxyRefusal(key, "transport", START_MS + 1000), null); + assert.equal(cap.lines.filter((l) => l.includes("set aside")).length, 1); + } finally { + cap.restore(); + } +}); + +test("an effective recovery logs one line, a no-op recovery logs nothing", () => { + const cap = captureWarn(); + try { + const key = memory.proxyEgressKey({ type: "http", host: "203.0.113.9", port: 8080 }); + assert.ok(key); + assert.ok(memory.noteProxyRefusal(key, "proxy_unreachable", START_MS) !== null); + memory.noteProxyRecovered(key, "proxy_unreachable", START_MS + 10_000); + const recoveries = cap.lines.filter((l) => l.includes("recovered")); + assert.equal(recoveries.length, 1); + assert.ok(recoveries[0].includes("kind=proxy_unreachable")); + const before = cap.lines.length; + memory.noteProxyRecovered(key, "proxy_unreachable", START_MS + 20_000); + assert.equal(cap.lines.length, before); + } finally { + cap.restore(); + } +}); + +test("a member write carries the member name", () => { + const cap = captureWarn(); + try { + const key = memory.proxyEgressKey({ type: "http", host: "203.0.113.10", port: 8080 }); + assert.ok(key); + assert.ok(memory.noteProxyMemberRefusal(key, "node-1", "slow", START_MS) !== null); + const writes = cap.lines.filter((l) => l.includes("set aside")); + assert.equal(writes.length, 1); + assert.ok(writes[0].includes("node-1")); + } finally { + cap.restore(); + } +}); + +test("the label keeps scheme, host and port only", () => { + const cases: Array<[unknown, string]> = [ + ["http://user:s3cret@h:8080", "http://h:8080"], + ["http://@h:8080", "http://h:8080"], + ["http://[::1]:8080", "http://[::1]:8080"], + ["http://h:8080?family=ipv4", "http://h:8080"], + ]; + for (const [input, label] of cases) { + const key = memory.proxyEgressKey(input); + assert.ok(key, String(input)); + assert.equal(memory.describeEgressForLog(key), label); + } +}); + +test("transport evidence counts failures and cross successes in the window", () => { + const cap = captureWarn(); + try { + const key = memory.proxyEgressKey({ type: "http", host: "203.0.113.11", port: 8080 }); + const other = memory.proxyEgressKey({ type: "http", host: "203.0.113.12", port: 8080 }); + assert.ok(key && other); + const destination = "example.com"; + memory.recordTransportFailure(key, destination, START_MS); + memory.recordTransportFailure(key, destination, START_MS + 1000); + memory.recordTransportFailure(key, destination, START_MS + 2000); + memory.recordTransportSuccess(destination, other, START_MS + 3000); + const evidence = memory.countTransportEvidenceFor(key, START_MS + 4000); + assert.equal(evidence.failures, 3); + assert.equal(evidence.crossSuccesses, 1); + const sizeBefore = memory.__transportEvidenceSizeForTesting(); + memory.countTransportEvidenceFor(key, START_MS + 4000); + assert.deepEqual(memory.__transportEvidenceSizeForTesting(), sizeBefore); + } finally { + cap.restore(); + } +}); + +test("with the flag off the transport caller writes nothing so no line appears", async () => { + // The store itself never reads PROXY_SKIP_RECENTLY_FAILED (pure by design); + // the real caller noteTransportOutcome gates on it. Flag off: the gated + // caller writes nothing, so no log line appears and the memory stays empty. + const cap = captureWarn(); + process.env.PROXY_SKIP_RECENTLY_FAILED = "false"; + try { + const outcome = await import("../../open-sse/utils/proxyTransportOutcome.ts"); + const key = memory.proxyEgressKey({ type: "http", host: "203.0.113.14", port: 8080 }); + assert.ok(key); + const destination = "example.com"; + memory.recordTransportFailure(key, destination, START_MS); + memory.recordTransportFailure(key, destination, START_MS + 1); + memory.recordTransportFailure(key, destination, START_MS + 2); + const peer = memory.proxyEgressKey({ type: "http", host: "203.0.113.15", port: 8080 }); + memory.recordTransportSuccess(destination, peer, START_MS + 3); + assert.equal(memory.hasTransportCrossEvidence(key, destination, START_MS + 4), true); + const sizeBefore = memory.__proxyRefusalMemorySizeForTesting(); + const linesBefore = cap.lines.filter((l) => l.includes("set aside")).length; + outcome.noteTransportOutcome({ key, destination, nowMs: START_MS + 4 }); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), sizeBefore); + assert.equal(cap.lines.filter((l) => l.includes("set aside")).length, linesBefore); + } finally { + delete process.env.PROXY_SKIP_RECENTLY_FAILED; + cap.restore(); + } +}); + +test("the store instance is stable within one load", () => { + assert.equal(memory.getRefusalStoreInstance(), memory.getRefusalStoreInstance()); + assert.match(memory.getRefusalStoreInstance(), /^[0-9a-f]{8}$/); +}); + +test("a second module load warns exactly once", async () => { + // A query-suffixed re-import is a genuinely fresh module copy sharing only + // globalThis: instance ids must differ, and the duplicate-load warning must + // fire exactly once (the shared guard suppresses the copy's twin). + const cap = captureWarn(); + try { + const fresh: typeof memory = + await import("../../open-sse/utils/proxyRefusalMemory.ts?reload=observability"); + assert.notEqual(fresh.getRefusalStoreInstance(), memory.getRefusalStoreInstance()); + const warns = cap.lines.filter((l) => l.includes("loaded more than once")); + assert.equal(warns.length, 1); + const sizeBefore = memory.__proxyRefusalMemorySizeForTesting(); + fresh.__resetProxyRefusalMemoryForTesting(); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), sizeBefore); + } finally { + cap.restore(); + } +}); + +test("functional: three transport failures plus a cross success set the egress aside with one log line", () => { + // Real-world shape: an egress fails transport repeatedly while the same + // destination answers through another egress. Evidence condemns the member, + // the write lands, and exactly one journal line appears. + const cap = captureWarn(); + try { + const key = memory.proxyEgressKey({ type: "http", host: "203.0.113.21", port: 8080 }); + const peer = memory.proxyEgressKey({ type: "http", host: "203.0.113.22", port: 8080 }); + assert.ok(key && peer); + const destination = "example.com"; + memory.recordTransportFailure(key, destination, START_MS); + memory.recordTransportFailure(key, destination, START_MS + 1000); + memory.recordTransportFailure(key, destination, START_MS + 2000); + memory.recordTransportSuccess(destination, peer, START_MS + 3000); + assert.equal(memory.hasTransportCrossEvidence(key, destination, START_MS + 4000), true); + const period = memory.noteProxyRefusal(key, "transport", START_MS + 4000); + assert.ok(typeof period === "number" && period > 0); + assert.equal(memory.isProxyAvoided(key, START_MS + 4001), true); + const writes = cap.lines.filter((l) => l.includes("set aside")); + assert.equal(writes.length, 1); + assert.ok(writes[0].includes("kind=transport")); + const evidence = memory.countTransportEvidenceFor(key, START_MS + 4001); + assert.equal(evidence.failures, 3); + assert.equal(evidence.crossSuccesses, 1); + } finally { + cap.restore(); + } +}); + +test("the stores return to their starting size after writes", () => { + const cap = captureWarn(); + try { + const memoryBefore = memory.__proxyRefusalMemorySizeForTesting(); + const key = memory.proxyEgressKey({ type: "http", host: "203.0.113.13", port: 8080 }); + assert.ok(key); + memory.noteProxyRefusal(key, "transport", START_MS); + memory.recordTransportFailure(key, "example.com", START_MS); + memory.noteProxyServed(key); + memory.__resetTransportEvidenceForTesting(); + assert.equal(memory.__proxyRefusalMemorySizeForTesting(), memoryBefore); + assert.deepEqual(memory.__transportEvidenceSizeForTesting(), { failures: 0, successes: 0 }); + } finally { + cap.restore(); + } +}); diff --git a/tests/unit/proxy-refusal-memory-single-instance.test.ts b/tests/unit/proxy-refusal-memory-single-instance.test.ts new file mode 100644 index 000000000000..e3ef5e32166d --- /dev/null +++ b/tests/unit/proxy-refusal-memory-single-instance.test.ts @@ -0,0 +1,63 @@ +/** + * Two copies of the refusal store share one process-wide state. + * + * The server bundle holds several copies of this store (one per module + * graph): transport failures recorded through one copy were invisible to + * the readers of another. All mutable state lives on `globalThis` so + * every copy reads and writes the same store. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const copyA = await import("../../open-sse/utils/proxyRefusalMemory.ts?copy=a"); +const copyB = await import("../../open-sse/utils/proxyRefusalMemory.ts?copy=b"); + +const KEY = "http://user@host.example:8080"; +const DEST = "target.example"; +const OTHER = "http://user@other.example:8080"; + +function resetBoth(): void { + copyA.__resetProxyRefusalMemoryForTesting(); + copyB.__resetProxyRefusalMemoryForTesting(); + copyA.__resetTransportEvidenceForTesting(); + copyB.__resetTransportEvidenceForTesting(); + copyA.__resetSlowOverrunsForTesting(); + copyB.__resetSlowOverrunsForTesting(); +} + +test.beforeEach(() => { + resetBoth(); +}); + +test.afterEach(() => { + resetBoth(); +}); + +test("witness: distinct module instances under distinct import URLs", () => { + assert.notEqual(copyA.noteProxyRefusal, copyB.noteProxyRefusal); +}); + +test("set-aside written through one copy is visible through the other", () => { + const now = 1_000_000; + const periodMs = copyA.noteProxyRefusal(KEY, "transport", now); + assert.ok(typeof periodMs === "number"); + assert.equal(copyB.isProxyAvoided(KEY, now), true); +}); + +test("transport cross-evidence recorded through either copy reads from both", () => { + const now = 2_000_000; + copyA.recordTransportFailure(KEY, DEST, now); + copyB.recordTransportFailure(KEY, DEST, now + 1); + copyA.recordTransportFailure(KEY, DEST, now + 2); + copyB.recordTransportSuccess(DEST, OTHER, now + 3); + assert.equal(copyA.hasTransportCrossEvidence(KEY, DEST, now + 4), true); + assert.equal(copyB.hasTransportCrossEvidence(KEY, DEST, now + 4), true); +}); + +test("slow-overrun evidence recorded through one copy reads through the other", () => { + const now = 3_000_000; + copyA.recordSlowOverrun(KEY, now); + copyA.recordSlowOverrun(KEY, now + 1); + copyA.recordSlowOverrun(KEY, now + 2); + assert.equal(copyB.hasSlowOverrunEvidence(KEY, now + 3), true); +}); diff --git a/tests/unit/proxy-refusal-memory.test.ts b/tests/unit/proxy-refusal-memory.test.ts index d2d3e171e297..20f2abaf5905 100644 --- a/tests/unit/proxy-refusal-memory.test.ts +++ b/tests/unit/proxy-refusal-memory.test.ts @@ -169,6 +169,26 @@ test("an out-of-range port or an unsupported scheme gives a null key", () => { assert.equal(memory.proxyEgressKey("ftp://h:21"), null); }); +test("a region refusal follows the same short curve as an unreachable probe", () => { + const key = "http://@geo:8080"; + const periods: Array = []; + let now = START_MS; + for (let i = 0; i < 4; i++) { + const period = memory.noteProxyRefusal(key, "geo_blocked", now); + periods.push(period); + now += period ?? 0; + } + assert.deepEqual(periods, [MIN, 2 * MIN, 4 * MIN, 8 * MIN]); +}); + +test("a served response also forgets a region refusal", () => { + const key = "http://@geo-served:8080"; + memory.noteProxyRefusal(key, "geo_blocked", START_MS); + assert.equal(memory.isProxyAvoided(key, START_MS + 1), true); + memory.noteProxyServed(key); + assert.equal(memory.isProxyAvoided(key, START_MS + 2), false); +}); + test("set-aside events are ordered, and only the one in force is reported", () => { const a = "http://@a:8080"; const b = "http://@b:8080"; diff --git a/tests/unit/proxy-subscriptions-route-validation.test.ts b/tests/unit/proxy-subscriptions-route-validation.test.ts index 8baaf1fb7b1c..5bbeb4f31548 100644 --- a/tests/unit/proxy-subscriptions-route-validation.test.ts +++ b/tests/unit/proxy-subscriptions-route-validation.test.ts @@ -347,3 +347,118 @@ test("PATCH proxy-subscriptions/:id — unknown id still 404s past body validati const body = (await res.json()) as { error?: string }; assert.equal(body.error, "Subscription not found"); }); + +test("POST proxy-subscriptions — five bad coreConfigPath values return 400 with reason", async () => { + const bad: Array<[string, string]> = [ + ["relative/path.json", "not_absolute"], + ["/adopted/../escape.json", "dotdot_segment"], + ["no-extension", "not_absolute"], + [`/${"a".repeat(2000)}.json`, "too_long"], + ["\0null.json", "nul_byte"], + ]; + for (const [coreConfigPath, reason] of bad) { + const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", { + name: `bad-path-${reason}`, + url: "https://example.com/feed", + coreConfigPath, + }); + const res = await collectionRoute.POST(req); + assert.equal(res.status, 400, `expected 400 for ${reason}`); + const body = (await res.json()) as { error?: string }; + assert.ok( + body.error === `coreConfigPath is not allowed (${reason})`, + `unexpected message: ${body.error}` + ); + } +}); + +test("POST proxy-subscriptions — absolute non-json path returns 400 bad_extension", async () => { + const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", { + name: "bad-ext", + url: "https://example.com/feed", + coreConfigPath: "/adopted/core.txt", + }); + const res = await collectionRoute.POST(req); + assert.equal(res.status, 400); + const body = (await res.json()) as { error?: string }; + assert.equal(body.error, "coreConfigPath is not allowed (bad_extension)"); +}); + +test("PATCH proxy-subscriptions/:id — bad coreConfigPath returns 400", async () => { + const fixture = await createValidSubscription("patch-bad-path"); + const req = jsonRequest( + `http://localhost/api/v1/management/proxy-subscriptions/${fixture.id}`, + { coreConfigPath: "relative.json" }, + "PATCH" + ); + const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: fixture.id }) }); + assert.equal(res.status, 400); + const body = (await res.json()) as { error?: string }; + assert.equal(body.error, "coreConfigPath is not allowed (not_absolute)"); +}); +// ═════════════════════════════════════════════════════════════════════════════ +// coreBinaryPath — the binary is executed, so the API never accepts it (Hard Rule #15); +// it comes from OMNIROUTE_PROXY_CORE_BINARY_PATH on the host only +// ═════════════════════════════════════════════════════════════════════════════ + +const COLLECTION_URL = "http://localhost/api/v1/management/proxy-subscriptions"; + +async function listedNames(): Promise { + const res = await collectionRoute.GET(new Request(COLLECTION_URL)); + return ((await res.json()) as { items: Array<{ name: string }> }).items.map((it) => it.name); +} + +test("POST proxy-subscriptions — a coreBinaryPath in the body is refused (400) and nothing is created", async () => { + for (const [index, value] of [ + "/usr/bin/sing-box", + path.join(os.tmpdir(), "attacker-sing-box"), + "relative/sing-box", + "", + null, + ].entries()) { + const name = `core-binary-refused-${index}`; + const res = await collectionRoute.POST( + jsonRequest(COLLECTION_URL, { + name, + url: "https://example.com/sub.txt", + coreBinaryPath: value, + }) + ); + assert.equal(res.status, 400, String(value)); + const body = (await res.json()) as { error?: string }; + assert.match(body.error ?? "", /coreBinaryPath cannot be set through the API/); + assert.match(body.error ?? "", /OMNIROUTE_PROXY_CORE_BINARY_PATH/); + assert.ok(!(await listedNames()).includes(name), `${name} must not be created`); + } +}); + +test("PATCH proxy-subscriptions/:id — a coreBinaryPath in the body is refused (400) and nothing changes", async () => { + const fixture = await createValidSubscription("core-binary-patch-refused"); + const res = await itemRoute.PATCH( + jsonRequest( + `${COLLECTION_URL}/${fixture.id}`, + { + name: "renamed-by-refused-patch", + coreBinaryPath: path.join(os.tmpdir(), "attacker-sing-box"), + }, + "PATCH" + ), + { params: Promise.resolve({ id: fixture.id }) } + ); + assert.equal(res.status, 400); + const body = (await res.json()) as { error?: string }; + assert.match(body.error ?? "", /coreBinaryPath cannot be set through the API/); + + const names = await listedNames(); + assert.ok(names.includes("core-binary-patch-refused")); + assert.ok(!names.includes("renamed-by-refused-patch")); +}); + +test("proxy-subscription records never carry a coreBinaryPath field", async () => { + const fixture = await createValidSubscription("core-binary-absent-field"); + const res = await itemRoute.GET(new Request(`${COLLECTION_URL}/${fixture.id}`), { + params: Promise.resolve({ id: fixture.id }), + }); + assert.equal(res.status, 200); + assert.ok(!("coreBinaryPath" in ((await res.json()) as Record))); +}); diff --git a/tests/unit/proxy-transition-bridge-single-emit.test.ts b/tests/unit/proxy-transition-bridge-single-emit.test.ts new file mode 100644 index 000000000000..ae170f685773 --- /dev/null +++ b/tests/unit/proxy-transition-bridge-single-emit.test.ts @@ -0,0 +1,51 @@ +/** + * One set-aside transition emits one `proxy.set_aside` bus event, even when + * the bridge module is loaded twice (duplicated server module copies would + * otherwise subscribe twice and emit twice). + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const bridgeA = await import("../../src/lib/proxyEvents/proxyTransitionBridge.ts?copy=a"); +const bridgeB = await import("../../src/lib/proxyEvents/proxyTransitionBridge.ts?copy=b"); +const storeA = await import("../../open-sse/utils/proxyRefusalMemory.ts?copy=a"); +const listenersA = await import("../../open-sse/utils/proxyTransitionListeners.ts?copy=a"); +const bus = await import("../../src/lib/events/eventBus.ts"); + +const KEY = "http://user@bridge.example:8080"; +const NOW = 10_000_000; + +function resetAll(): void { + bridgeA.__resetBridgeForTesting(); + bridgeB.__resetBridgeForTesting(); + storeA.__resetProxyRefusalMemoryForTesting(); + listenersA.__resetProxyTransitionListenersForTesting(); +} + +test.beforeEach(() => { + resetAll(); +}); + +test.afterEach(() => { + resetAll(); + delete process.env.PROXY_WEBHOOK_REBOUND_MS; +}); + +test("transition seen by two bridge copies emits a single bus event", () => { + const clock = () => NOW; + bridgeA.__setBridgeNowForTesting(clock); + bridgeB.__setBridgeNowForTesting(clock); + bridgeA.registerProxyTransitionBridge(); + bridgeB.registerProxyTransitionBridge(); + const received: unknown[] = []; + const off = bus.on("proxy.set_aside", (payload) => { + received.push(payload); + }); + try { + const periodMs = storeA.noteProxyRefusal(KEY, "transport", NOW); + assert.ok(typeof periodMs === "number"); + assert.equal(received.length, 1); + } finally { + off(); + } +}); diff --git a/tests/unit/proxySubscription.coreApply.test.ts b/tests/unit/proxySubscription.coreApply.test.ts new file mode 100644 index 000000000000..f6b9e31b2728 --- /dev/null +++ b/tests/unit/proxySubscription.coreApply.test.ts @@ -0,0 +1,475 @@ +/** + * Apply step for a rendered proxy-core configuration: native check first, + * atomic replacement only on success, beside-write with a reason otherwise. + * + * The adopted file is never replaced without a passing native check + * (` check -c `). The candidate lives in the adopted + * directory (same filesystem, atomic rename) with a random suffix so two + * subscriptions sharing one path cannot collide. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const applyMod = await import("../../src/lib/proxySubscription/coreConfig/apply.ts"); +const guardMod = await import("../../src/lib/proxySubscription/coreConfig/pathGuard.ts"); +const { applyRendered } = applyMod; +const { isCoreBinaryPathAllowed, checkArgsFor, CORE_CHECK_ARGS } = guardMod; +import type { RunCheck } from "../../src/lib/proxySubscription/coreConfig/apply.ts"; + +function makeDir(): string { + return fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-apply-")); +} + +function writeExecutable(dir: string, name: string, body: string): string { + const p = path.join(dir, name); + fs.writeFileSync(p, body, { mode: 0o755 }); + fs.chmodSync(p, 0o755); + return p; +} + +const okCheck: RunCheck = async () => ({ stdout: "", stderr: "" }); +const koCheck: RunCheck = async () => { + const error = new Error("Command failed") as Error & { stderr: string }; + error.stderr = "invalid configuration"; + throw error; +}; + +test.after(() => {}); + +test("allow-list derives from the check-args table", () => { + assert.deepEqual(checkArgsFor("sing-box"), ["check", "-c"]); + assert.equal(checkArgsFor("unknown-core"), null); + assert.ok("sing-box" in CORE_CHECK_ARGS); +}); + +test("binary guard refuses malformed paths without disk access", () => { + assert.equal(isCoreBinaryPathAllowed("").reason, "empty"); + assert.equal(isCoreBinaryPathAllowed(" ").reason, "empty"); + assert.equal(isCoreBinaryPathAllowed("relative/sing-box").reason, "not_absolute"); + assert.equal(isCoreBinaryPathAllowed("/opt/sb/../sing-box").reason, "dotdot_segment"); + assert.equal(isCoreBinaryPathAllowed("/bin/sing-box\0").reason, "nul_byte"); + assert.equal(isCoreBinaryPathAllowed(`/${"a".repeat(1024)}`).reason, "too_long"); + assert.equal(isCoreBinaryPathAllowed("/usr/bin/unknown-core").reason, "not_allowlisted"); + assert.equal(isCoreBinaryPathAllowed("/usr/bin/sing-box").allowed, true); +}); + +test("empty binary path writes beside without forking", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + let called = 0; + const spy: RunCheck = async () => { + called += 1; + return { stdout: "", stderr: "" }; + }; + const outcome = await applyRendered({ + adoptedPath: path.join(dir, "config.json"), + binaryPath: "", + renderedText: "{}\n", + subscriptionId: "s1", + runCheck: spy, + }); + assert.deepEqual(outcome, { status: "beside", beside: "no_binary" }); + assert.equal(called, 0); +}); + +test("identical content skips without writing or forking and keeps mtime", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, '{"a":1}\n'); + const before = fs.statSync(adopted).mtimeMs; + let called = 0; + const spy: RunCheck = async () => { + called += 1; + return { stdout: "", stderr: "" }; + }; + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: "/usr/bin/sing-box", + renderedText: '{"a":1}\n', + subscriptionId: "s1", + runCheck: spy, + }); + assert.deepEqual(outcome, { status: "beside", beside: "unchanged_skip" }); + assert.equal(called, 0); + assert.equal(fs.statSync(adopted).mtimeMs, before); +}); + +test("missing binary refuses without executing", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + let called = 0; + const spy: RunCheck = async () => { + called += 1; + return { stdout: "", stderr: "" }; + }; + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: path.join(dir, "sing-box"), + renderedText: "new\n", + subscriptionId: "s1", + runCheck: spy, + }); + assert.deepEqual(outcome, { status: "beside", beside: "binary_missing" }); + assert.equal(called, 0); + assert.equal(fs.readFileSync(adopted, "utf8"), "old\n"); +}); + +test("non-executable binary refuses without executing", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = path.join(dir, "sing-box"); + fs.writeFileSync(bin, "not executable\n"); + fs.chmodSync(bin, 0o644); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + let called = 0; + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: async () => { + called += 1; + return { stdout: "", stderr: "" }; + }, + }); + assert.deepEqual(outcome, { status: "beside", beside: "binary_missing" }); + assert.equal(called, 0); + assert.equal(fs.readFileSync(adopted, "utf8"), "old\n"); +}); + +test("binary outside the allow-list refuses without executing", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "other-core", "#!/bin/sh\nexit 0\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + let called = 0; + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: async () => { + called += 1; + return { stdout: "", stderr: "" }; + }, + }); + assert.deepEqual(outcome, { status: "beside", beside: "binary_missing" }); + assert.equal(called, 0); + assert.equal(fs.readFileSync(adopted, "utf8"), "old\n"); +}); + +test("failing check leaves the adopted file byte-identical and cleans the candidate", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 1\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + const before = fs.readFileSync(adopted); + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: koCheck, + }); + assert.deepEqual(outcome, { status: "beside", beside: "check_failed" }); + assert.deepEqual(fs.readFileSync(adopted), before); + const leftovers = fs.readdirSync(dir).filter((f) => f.includes(".check.")); + assert.deepEqual(leftovers, []); +}); + +test("detail carries the reason only, never raw checker output", () => { + const reasons = ["no_binary", "binary_missing", "check_failed", "write_failed", "unchanged_skip"]; + for (const beside of reasons) { + assert.match(beside, /^(no_binary|binary_missing|check_failed|write_failed|unchanged_skip)$/); + } +}); + +test("passing check replaces the adopted file and keeps the old content in .prev", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 0\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + fs.writeFileSync(`${adopted}.generated`, "stale\n"); + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: okCheck, + }); + assert.deepEqual(outcome, { status: "replaced" }); + assert.equal(fs.readFileSync(adopted, "utf8"), "new\n"); + assert.equal(fs.readFileSync(`${adopted}.prev`, "utf8"), "old\n"); + assert.equal(fs.existsSync(`${adopted}.generated`), false); + const leftovers = fs.readdirSync(dir).filter((f) => f.includes(".check.")); + assert.deepEqual(leftovers, []); +}); + +test("replacement preserves the adopted file mode", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 0\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + fs.chmodSync(adopted, 0o640); + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: okCheck, + }); + assert.deepEqual(outcome, { status: "replaced" }); + assert.equal(fs.statSync(adopted).mode & 0o777, 0o640); +}); + +test("candidate mode alignment failure reports write_failed", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 0\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: okCheck, + fsHooks: { + chmodSync: () => { + throw new Error("injected chmod fault"); + }, + }, + }); + assert.deepEqual(outcome, { status: "beside", beside: "write_failed" }); + assert.equal(fs.readFileSync(adopted, "utf8"), "old\n"); +}); + +test("first replacement without an adopted file needs no backup", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 0\n"); + const adopted = path.join(dir, "config.json"); + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: okCheck, + }); + assert.deepEqual(outcome, { status: "replaced" }); + assert.equal(fs.readFileSync(adopted, "utf8"), "new\n"); + assert.equal(fs.existsSync(`${adopted}.prev`), false); +}); + +test("slow check is killed at the injected timeout and the candidate is removed", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nsleep 60\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + const seen: Array<{ timeoutMs: number; maxBuffer: number }> = []; + const slow: RunCheck = (binPath, args, opts) => + new Promise((_resolve, reject) => { + seen.push({ timeoutMs: opts.timeoutMs, maxBuffer: opts.maxBuffer }); + void binPath; + void args; + const timer = setTimeout(() => { + const error = new Error("killed") as Error & { code: string }; + error.code = "ETIMEDOUT"; + reject(error); + }, opts.timeoutMs); + void timer; + }); + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: slow, + timeoutMs: 50, + }); + assert.deepEqual(outcome, { status: "beside", beside: "check_failed" }); + assert.equal(seen.length, 1); + assert.equal(seen[0].timeoutMs, 50); + assert.equal(fs.readFileSync(adopted, "utf8"), "old\n"); + assert.deepEqual( + fs.readdirSync(dir).filter((f) => f.includes(".check.")), + [] + ); +}); + +test("unwritable directory reports write_failed with no leftover and an intact adopted file", async (t) => { + const dir = makeDir(); + t.after(() => { + try { + fs.chmodSync(dir, 0o755); + } catch {} + fs.rmSync(dir, { recursive: true, force: true }); + }); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + fs.chmodSync(dir, 0o555); + let probeFailed = false; + try { + fs.writeFileSync(path.join(dir, ".probe"), "x"); + } catch { + probeFailed = true; + } + if (!probeFailed) { + t.skip("running as a privileged user: read-only directory still writable"); + return; + } + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: "/usr/bin/sing-box", + renderedText: "new\n", + subscriptionId: "s1", + runCheck: okCheck, + }); + assert.deepEqual(outcome, { status: "beside", beside: "write_failed" }); + assert.equal(fs.readFileSync(adopted, "utf8"), "old\n"); + fs.chmodSync(dir, 0o755); + assert.deepEqual( + fs.readdirSync(dir).filter((f) => f.includes(".check.")), + [] + ); +}); + +test("trapped binary name travels as a single argument and never executes", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + const seen: string[][] = []; + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: "/usr/bin/sing-box; rm -rf /", + renderedText: "new\n", + subscriptionId: "s1", + runCheck: async (_bin, args) => { + seen.push(args); + return { stdout: "", stderr: "" }; + }, + }); + assert.deepEqual(outcome, { status: "beside", beside: "binary_missing" }); + assert.deepEqual(seen, []); +}); + +test("failing check passes the candidate as a single trailing argument", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 1\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + const seen: Array<{ bin: string; args: string[] }> = []; + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: async (b, args) => { + seen.push({ bin: b, args }); + return koCheck(b, args, { timeoutMs: 1000, maxBuffer: 65536 }); + }, + }); + assert.deepEqual(outcome, { status: "beside", beside: "check_failed" }); + assert.equal(seen.length, 1); + assert.equal(seen[0].bin, bin); + assert.deepEqual(seen[0].args.slice(0, 2), ["check", "-c"]); + assert.equal(seen[0].args.length, 3); + assert.ok(seen[0].args[2].startsWith(`${adopted}.check.`)); +}); + +test("injected rename fault reports write_failed and logs with the subscription id", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 0\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + const warnings: string[] = []; + const originalWarn = console.warn; + console.warn = (message?: unknown) => { + warnings.push(String(message)); + }; + t.after(() => { + console.warn = originalWarn; + }); + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "sub-42", + runCheck: okCheck, + fsHooks: { + renameSync: () => { + throw new Error("injected rename fault"); + }, + }, + }); + assert.deepEqual(outcome, { status: "beside", beside: "write_failed" }); + assert.ok(warnings.some((w) => w.includes("sub-42"))); +}); + +test("adopted symlink is refused without modification", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 0\n"); + const target = path.join(dir, "target.json"); + fs.writeFileSync(target, "old\n"); + const adopted = path.join(dir, "config.json"); + fs.symlinkSync(target, adopted); + let called = 0; + const outcome = await applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "new\n", + subscriptionId: "s1", + runCheck: async () => { + called += 1; + return { stdout: "", stderr: "" }; + }, + }); + assert.deepEqual(outcome, { status: "beside", beside: "write_failed" }); + assert.equal(called, 0); + assert.equal(fs.readFileSync(target, "utf8"), "old\n"); +}); + +test("two subscriptions sharing one path do not collide on the candidate", async (t) => { + const dir = makeDir(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const bin = writeExecutable(dir, "sing-box", "#!/bin/sh\nexit 0\n"); + const adopted = path.join(dir, "config.json"); + fs.writeFileSync(adopted, "old\n"); + const [first, second] = await Promise.all([ + applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "first\n", + subscriptionId: "s1", + runCheck: okCheck, + }), + applyRendered({ + adoptedPath: adopted, + binaryPath: bin, + renderedText: "second\n", + subscriptionId: "s2", + runCheck: okCheck, + }), + ]); + assert.ok([first.status, second.status].includes("replaced")); + assert.deepEqual( + fs.readdirSync(dir).filter((f) => f.includes(".check.")), + [] + ); +}); diff --git a/tests/unit/proxySubscription.coreConfigSync.test.ts b/tests/unit/proxySubscription.coreConfigSync.test.ts new file mode 100644 index 000000000000..efad3841a308 --- /dev/null +++ b/tests/unit/proxySubscription.coreConfigSync.test.ts @@ -0,0 +1,473 @@ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +test("empty path writes nothing and never calls the renderer", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-empty-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + let calls = 0; + const renderers = await import("../../src/lib/proxySubscription/coreConfig/renderers.ts"); + const orig = renderers.RENDERERS[renderers.DEFAULT_CORE]; + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = () => { + calls += 1; + return orig; + }; + try { + const res = await sync.generateForSubscription( + { coreConfigPath: "", localCoreEndpoint: "socks5://127.0.0.1:1080 selector=g" }, + { nodes: [], needsCore: [], format: "empty" } + ); + assert.equal(res, null); + assert.equal(calls, 0); + assert.deepEqual(fs.readdirSync(dir), []); + } finally { + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = orig; + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +function singBoxFeed(): { nodes: never[]; needsCore: never[]; format: "v2ray-json" } { + const mk = (i: number) => ({ + name: `sb-node-${i}`, + source: { + kind: "object" as const, + value: { type: "vless", server: `203.0.113.${i}`, server_port: 443, uuid: `u-${i}` }, + }, + }); + return { + nodes: [], + needsCore: [mk(1), mk(2), mk(3), mk(4)] as never[], + format: "v2ray-json", + }; +} + +const ENDPOINTS = "socks5://127.0.0.1:1080 selector=a\nsocks5://127.0.0.1:1081 selector=b"; + +test("writes beside-file once per subscription, leaves the adopted file alone", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-write-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + fs.writeFileSync( + target, + JSON.stringify({ dns: { servers: ["https://9.9.9.9/dns-query"] }, inbounds: [], outbounds: [] }) + ); + const beforeHash = createHash("sha256").update(fs.readFileSync(target)).digest("hex"); + const warned: string[] = []; + const origWarn = console.warn; + console.warn = (...args: unknown[]) => { + warned.push(args.map(String).join(" ")); + }; + try { + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + singBoxFeed() + ); + assert.equal(res, null); + } finally { + console.warn = origWarn; + } + const generated = JSON.parse(fs.readFileSync(`${target}.generated`, "utf8")); + const selectors = generated.outbounds.filter((o: { type: string }) => o.type === "selector"); + assert.equal(selectors.length, 2); + assert.equal(generated.inbounds.length, 2); + assert.equal( + generated.outbounds.filter((o: { tag: string }) => o.tag.startsWith("omniroute-sb-node-")) + .length, + 4 + ); + assert.equal(createHash("sha256").update(fs.readFileSync(target)).digest("hex"), beforeHash); + assert.equal(warned.length, 0); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("second identical run writes nothing (mtime unchanged)", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-mtime-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + const sub = { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }; + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + await sync.generateForSubscription(sub, singBoxFeed()); + const first = fs.statSync(`${target}.generated`).mtimeMs; + await new Promise((r) => setTimeout(r, 20)); + const res = await sync.generateForSubscription(sub, singBoxFeed()); + assert.equal(res, null); + assert.equal(fs.statSync(`${target}.generated`).mtimeMs, first); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("missing directory warns without throwing", async () => { + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const res = await sync.generateForSubscription( + { + coreConfigPath: path.join(os.tmpdir(), "omniroute-no-such-dir-xyz", "core.json"), + localCoreEndpoint: ENDPOINTS, + }, + singBoxFeed() + ); + assert.ok(res?.includes("CORE_CONFIG_NOT_GENERATED")); +}); + +test("unparseable adopted file warns and is never modified", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-bad-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, "not json{{{"); + const before = fs.readFileSync(target, "utf8"); + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + singBoxFeed() + ); + assert.ok(res?.includes("CORE_CONFIG_NOT_GENERATED")); + assert.equal(fs.readFileSync(target, "utf8"), before); + assert.ok(!fs.existsSync(`${target}.generated`)); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("additive migration replays without effect", async () => { + const sql = fs.readFileSync( + new URL("../../src/lib/db/migrations/197_proxy_subscription_core_config.sql", import.meta.url), + "utf8" + ); + assert.ok(sql.includes("ADD COLUMN core_config_path TEXT")); +}); + +test("end to end: adopted dns and log survive into the beside-file", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-e2e-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + fs.writeFileSync( + target, + JSON.stringify({ + dns: { servers: ["https://1.1.1.1/dns-query"] }, + log: { level: "warn" }, + inbounds: [], + outbounds: [], + route: { rules: [] }, + }) + ); + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + singBoxFeed() + ); + assert.equal(res, null); + const generated = JSON.parse(fs.readFileSync(`${target}.generated`, "utf8")); + assert.deepEqual(generated.dns, { servers: ["https://1.1.1.1/dns-query"] }); + assert.deepEqual(generated.log, { level: "warn" }); + assert.equal(generated.inbounds.length, 2); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("earlier warning wins: generation skipped, skip logged, prior error kept", async () => { + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + // Unit-level: generateForSubscription itself returns null for empty path — + // the skip-when-warning branch lives in the service caller (covered below + // by code inspection of the warn-on-skip line in subscriptionService.ts). + const src = fs.readFileSync( + new URL("../../src/lib/proxySubscription/subscriptionService.ts", import.meta.url), + "utf8" + ); + assert.ok(src.includes("core config generation skipped")); + void sync; +}); + +function captureWarn(): { lines: string[]; restore: () => void } { + const lines: string[] = []; + const orig = console.warn; + console.warn = (...args: unknown[]) => { + lines.push(args.map(String).join(" ")); + }; + return { lines, restore: () => void (console.warn = orig) }; +} + +function uriFeed( + count: number, + name = "shared" +): { nodes: never[]; needsCore: never[]; format: "lines" } { + return { + nodes: [], + needsCore: Array.from({ length: count }, (_, i) => ({ + name, + source: { kind: "uri" as const, value: `vless://u@10.9.0.${i + 1}:443#${name}` }, + })) as never[], + format: "lines", + }; +} + +const THREE_ENDPOINTS = + "socks5://127.0.0.1:1080 selector=g1\nsocks5://127.0.0.1:1081 selector=g2\nsocks5://127.0.0.1:1082 selector=g3"; + +test("unassigned groups log one empty_group line per group", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-skip-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const cap = captureWarn(); + try { + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: THREE_ENDPOINTS }, + uriFeed(1) + ); + assert.ok(res?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), `got: ${res}`); + } finally { + cap.restore(); + } + assert.equal(cap.lines.length, 1); + assert.ok(cap.lines[0].includes("empty_group=3")); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("fully populated generation stays silent", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-quiet-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const cap = captureWarn(); + try { + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + singBoxFeed() + ); + assert.equal(res, null); + } finally { + cap.restore(); + } + assert.equal(cap.lines.length, 0); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("identical rerun stays silent (unchanged)", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-rerun-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const sub = { coreConfigPath: target, localCoreEndpoint: THREE_ENDPOINTS }; + await sync.generateForSubscription(sub, uriFeed(1)); + const cap = captureWarn(); + try { + const res = await sync.generateForSubscription(sub, uriFeed(1)); + assert.equal(res, null); + } finally { + cap.restore(); + } + assert.equal(cap.lines.length, 0); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("skip line carries counts only, never node names or servers", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-leak-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const cap = captureWarn(); + try { + await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: THREE_ENDPOINTS }, + uriFeed(2, "leakcheck-name") + ); + } finally { + cap.restore(); + } + assert.equal(cap.lines.length, 1); + assert.ok(!cap.lines[0].includes("leakcheck-name")); + assert.ok(!cap.lines[0].includes("10.9.0.")); +}); + +test("model or render failure reports internal_error and does not throw", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-internal-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const renderers = await import("../../src/lib/proxySubscription/coreConfig/renderers.ts"); + const orig = renderers.RENDERERS[renderers.DEFAULT_CORE]; + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = () => { + throw new Error("boom sb-node-9 203.0.113.9"); + }; + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const cap = captureWarn(); + try { + const intention = await sync.generateCoreConfigIntention( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + singBoxFeed() + ); + assert.equal(intention.status, "none"); + assert.ok(intention.warning?.includes("internal_error"), `got: ${intention.warning}`); + assert.equal(cap.lines.length, 1); + assert.equal(cap.lines[0], "[ProxySubscription] core config generation failed"); + assert.ok(!cap.lines[0].includes("sb-node-")); + assert.ok(!cap.lines[0].includes("203.0.113.")); + } finally { + cap.restore(); + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = orig; + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("disk failure still reports the write reason", async () => { + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const missing = await sync.generateForSubscription( + { + coreConfigPath: path.join(os.tmpdir(), "omniroute-no-such-dir-xyz", "core.json"), + localCoreEndpoint: ENDPOINTS, + }, + singBoxFeed() + ); + assert.ok(missing?.includes("write_failed")); + assert.ok(!missing?.includes("internal_error")); + + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-readonly-")); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + fs.chmodSync(dir, 0o555); + try { + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + singBoxFeed() + ); + assert.ok(res?.includes("write_failed"), `got: ${res}`); + assert.ok(!res?.includes("internal_error")); + } finally { + fs.chmodSync(dir, 0o755); + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("repeated generations leave no accumulated state", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-bounded-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const cap = captureWarn(); + try { + for (let i = 0; i < 5; i += 1) { + const target = path.join(dir, `core-${i}.json`); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: THREE_ENDPOINTS }, + uriFeed(1) + ); + assert.ok(res?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), `got: ${res}`); + } + } finally { + cap.restore(); + } + assert.equal(cap.lines.length, 5); + assert.ok(cap.lines.every((l) => l.includes("empty_group=3"))); + fs.rmSync(dir, { recursive: true, force: true }); +}); + +test("host env binary routes through the native check (pass replaces, miss warns beside)", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-verify-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const renderers = await import("../../src/lib/proxySubscription/coreConfig/renderers.ts"); + const apply = await import("../../src/lib/proxySubscription/coreConfig/apply.ts"); + const origRender = renderers.RENDERERS[renderers.DEFAULT_CORE]; + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = () => ({ + ok: true, + text: JSON.stringify({ inbounds: [], outbounds: [{ tag: "omniroute-probe" }] }), + unchanged: false, + skipped: [], + }); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + try { + // No binary: beside-file written directly, adopted untouched. + const plain = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + singBoxFeed() + ); + assert.equal(plain, null); + assert.ok(fs.existsSync(`${target}.generated`)); + // Binary configured on the host env: applyRendered runs (fake binary that always passes). + // Proof it went through the check (not the beside-direct path): the + // adopted file now holds the rendered text, and the stale beside copy + // from the first call is gone. + const fakeBin = path.join(dir, "sing-box"); + fs.writeFileSync(fakeBin, "#!/bin/sh\nexit 0\n"); + fs.chmodSync(fakeBin, 0o755); + process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH = fakeBin; + const verified = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS, id: "sub-verify" }, + singBoxFeed() + ); + assert.equal(verified, null); + assert.ok(fs.readFileSync(target, "utf8").includes("omniroute-probe")); + assert.ok(!fs.existsSync(`${target}.generated`)); + void apply; + } finally { + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = origRender; + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("a host env binary that fails the guard is ignored: beside-file only, adopted untouched", async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-badbin-")); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const target = path.join(dir, "core.json"); + const adopted = JSON.stringify({ inbounds: [], outbounds: [] }); + fs.writeFileSync(target, adopted); + // A script the guard must never execute: an executable that records its own run. + const marker = path.join(dir, "ran"); + const trapped = path.join(dir, "not-sing-box"); + fs.writeFileSync(trapped, `#!/bin/sh\ntouch ${marker}\nexit 0\n`); + fs.chmodSync(trapped, 0o755); + try { + for (const value of [trapped, "relative/sing-box", "/opt/sb/../sing-box"]) { + process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH = value; + const res = await sync.generateForSubscription( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS, id: "sub-badbin" }, + singBoxFeed() + ); + assert.equal(res, null, value); + assert.equal(fs.readFileSync(target, "utf8"), adopted, value); + assert.ok(fs.existsSync(`${target}.generated`), value); + assert.ok(!fs.existsSync(marker), `${value} must not be executed`); + } + } finally { + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("delete removes only the beside copy and never the adopted file, missing files do not fail", async () => { + const apply = await import("../../src/lib/proxySubscription/coreConfig/apply.ts"); + // Unit-level: removeSubscriptionSideFiles unlinks `.generated` only, + // best-effort (missing files do not throw). The adopted file is the + // operator's live core configuration. + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-sidefiles-")); + try { + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, "{}"); + fs.writeFileSync(`${target}.generated`, "{}"); + const calls: string[] = []; + apply.removeSubscriptionSideFiles( + { + unlinkSync: (p: string) => { + calls.push(p); + fs.unlinkSync(p); + }, + }, + target, + "sub-del" + ); + assert.deepEqual(calls, [`${target}.generated`]); + assert.ok(fs.existsSync(target), "the adopted file must survive"); + assert.ok(!fs.existsSync(`${target}.generated`)); + // Missing beside copy: no throw, still attempted, adopted still intact. + const noop = { unlinkSync: (p: string) => void calls.push(p) }; + apply.removeSubscriptionSideFiles(noop, target, "sub-del"); + assert.deepEqual(calls, [`${target}.generated`, `${target}.generated`]); + assert.ok(fs.existsSync(target)); + // Empty path: no-op. + apply.removeSubscriptionSideFiles(noop, " ", "sub-del"); + assert.equal(calls.length, 2); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + // The service delete path calls it with the stored adopted path. + const src = fs.readFileSync( + new URL("../../src/lib/proxySubscription/subscriptionService.ts", import.meta.url), + "utf8" + ); + assert.ok(src.includes("removeSubscriptionSideFiles")); +}); diff --git a/tests/unit/proxySubscription.coreEntriesSkipped.test.ts b/tests/unit/proxySubscription.coreEntriesSkipped.test.ts new file mode 100644 index 000000000000..efbe58572e1a --- /dev/null +++ b/tests/unit/proxySubscription.coreEntriesSkipped.test.ts @@ -0,0 +1,244 @@ +/** + * Entries skipped during generation surface as a dedicated warning. + * + * A generation that drops entries (bad listener lines, unusable nodes, + * pruned nodes the core check rejects, shaped nodes the renderer drops) + * warns once with CORE_CONFIG_ENTRIES_SKIPPED and a counts-only detail; + * a clean generation stays silent (null warning). + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const syncMod = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); +import type { RunCheck } from "../../src/lib/proxySubscription/coreConfig/apply.ts"; + +const { generateCoreConfigIntention } = syncMod; + +const ENDPOINTS = "socks5://127.0.0.1:1080 selector=a\nsocks5://127.0.0.1:1081 selector=b"; + +function feed(names: string[]): { nodes: never[]; needsCore: never[]; format: string } { + return { + nodes: [], + needsCore: names.map((name, i) => ({ + name, + source: { + kind: "object" as const, + value: { type: "vless", server: `203.0.113.${i + 1}`, server_port: 443, uuid: `u-${i}` }, + }, + })) as never[], + format: "v2ray-json", + }; +} + +function setup(): { dir: string; target: string; binary: string } { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-skipped-")); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const binary = path.join(dir, "sing-box"); + fs.writeFileSync(binary, "#!/bin/sh\nexit 1\n"); + fs.chmodSync(binary, 0o755); + return { dir, target, binary }; +} + +function rejecting(victims: string[]): RunCheck { + return async (_bin, args) => { + const candidate = args[args.length - 1]; + const outbounds = JSON.parse(fs.readFileSync(candidate, "utf8")).outbounds as Array<{ + tag: string; + }>; + for (const victim of victims) { + const index = outbounds.findIndex((o) => o.tag.startsWith(`omniroute-${victim}`)); + if (index >= 0) { + throw Object.assign(new Error("Command failed"), { + stderr: `FATAL initialize outbound[${index}]: rejected field value`, + }); + } + } + return { stdout: "", stderr: "" }; + }; +} + +async function verifiedWarning( + target: string, + binary: string, + victims: string[] +): Promise { + process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH = binary; + try { + return ( + await generateCoreConfigIntention( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS, id: "s-skipped" }, + feed(["node-a", "node-b", "node-c"]) as never, + { runCheck: rejecting(victims) } + ) + ).warning; + } finally { + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + } +} + +test("one pruned node warns with the dedicated code and count 1", async (t) => { + const { dir, target, binary } = setup(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const warning = await verifiedWarning(target, binary, ["node-b"]); + assert.ok(warning?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), `got: ${warning}`); + const parsed = JSON.parse(warning!); + assert.equal(parsed.code, "CORE_CONFIG_ENTRIES_SKIPPED"); + assert.equal(parsed.detail, "skipped:1:core_rejected=1"); +}); + +test("beside path merges model and render skips with an exact detail", async (t) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-mixed-")); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const cap: string[] = []; + const orig = console.warn; + console.warn = (...args: unknown[]) => void cap.push(args.map(String).join(" ")); + try { + const warning = await generateCoreConfigIntention( + { + coreConfigPath: target, + localCoreEndpoint: + "socks5://127.0.0.1:1080 selector=g1\nsocks5://127.0.0.1:1081 selector=g2\nhttp://192.168.1.10:8080 selector=lan", + }, + { + nodes: [], + needsCore: [ + { + name: "shared", + source: { kind: "uri", value: "vless://u@10.9.0.1:443#shared" }, + }, + ] as never[], + format: "lines", + } + ); + assert.ok(warning.warning?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), `got: ${warning.warning}`); + const parsed = JSON.parse(warning.warning!); + assert.equal( + parsed.detail, + "skipped:4:empty_group=2,invalid_endpoint=1,source_not_singbox_shape=1" + ); + assert.equal(cap.length, 1); + assert.ok(cap[0].includes("empty_group=2")); + assert.ok(cap[0].includes("invalid_endpoint=1")); + assert.ok(cap[0].includes("source_not_singbox_shape=1")); + } finally { + console.warn = orig; + } +}); + +test("verified path keeps the same detail as the beside path", async (t) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-verified-parity-")); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const binary = path.join(dir, "sing-box"); + fs.writeFileSync(binary, "#!/bin/sh\nexit 0\n"); + fs.chmodSync(binary, 0o755); + process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH = binary; + try { + const intention = await generateCoreConfigIntention( + { + coreConfigPath: target, + localCoreEndpoint: + "socks5://127.0.0.1:1080 selector=g1\nsocks5://127.0.0.1:1081 selector=g2\nhttp://192.168.1.10:8080 selector=lan", + id: "s-parity", + }, + { + nodes: [], + needsCore: [ + { + name: "shared", + source: { kind: "uri", value: "vless://u@10.9.0.1:443#shared" }, + }, + ] as never[], + format: "lines", + }, + { runCheck: async () => ({ stdout: "", stderr: "" }) } + ); + assert.ok( + intention.warning?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), + `got: ${intention.warning}` + ); + // model.skipped is the array already spread into initialSkipped. Counting + // both reports 6 (empty_group=3, invalid_endpoint=2) instead of 4. + assert.equal( + JSON.parse(intention.warning!).detail, + "skipped:4:empty_group=2,invalid_endpoint=1,source_not_singbox_shape=1", + "model.skipped is already inside initialSkipped; the warning must not count it twice" + ); + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + const beside = await generateCoreConfigIntention( + { + coreConfigPath: path.join(dir, "core-beside.json"), + localCoreEndpoint: + "socks5://127.0.0.1:1080 selector=g1\nsocks5://127.0.0.1:1081 selector=g2\nhttp://192.168.1.10:8080 selector=lan", + }, + { + nodes: [], + needsCore: [ + { + name: "shared", + source: { kind: "uri", value: "vless://u@10.9.0.1:443#shared" }, + }, + ] as never[], + format: "lines", + } + ); + assert.equal(JSON.parse(beside.warning!).detail, JSON.parse(intention.warning!).detail); + } finally { + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + } +}); + +test("clean generation stays silent", async (t) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-clean-")); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const cap: string[] = []; + const orig = console.warn; + console.warn = (...args: unknown[]) => void cap.push(args.map(String).join(" ")); + try { + const warning = ( + await generateCoreConfigIntention( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + feed(["node-a", "node-b", "node-c"]) as never + ) + ).warning; + assert.equal(warning, null); + assert.equal(cap.length, 0); + } finally { + console.warn = orig; + } +}); + +test("rendered message shows the fixed phrase with the raw counts beside it", () => { + const dir = new URL("../../src/i18n/messages/", import.meta.url); + const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json")); + assert.ok(files.length >= 60, `expected the full locale set, got ${files.length}`); + const failures: string[] = []; + for (const file of files) { + const parsed = JSON.parse( + fs.readFileSync(new URL(`../../src/i18n/messages/${file}`, import.meta.url), "utf8") + ); + const value = parsed?.settings?.proxySubscription?.error?.CORE_CONFIG_ENTRIES_SKIPPED; + if (typeof value !== "string" || value.length === 0) { + failures.push(`${file}: CORE_CONFIG_ENTRIES_SKIPPED missing`); + } else if (value.includes("{count") || value.includes("{dominant")) { + failures.push(`${file}: message still takes parameters the screen never passes`); + } + } + assert.deepEqual(failures, [], `invalid messages:\n${failures.join("\n")}`); + const en = JSON.parse( + fs.readFileSync(new URL("../../src/i18n/messages/en.json", import.meta.url), "utf8") + ); + assert.equal( + en.settings.proxySubscription.error.CORE_CONFIG_ENTRIES_SKIPPED, + "Some entries were skipped during generation. See the server log." + ); +}); diff --git a/tests/unit/proxySubscription.coreModel.test.ts b/tests/unit/proxySubscription.coreModel.test.ts new file mode 100644 index 000000000000..0965b86232f0 --- /dev/null +++ b/tests/unit/proxySubscription.coreModel.test.ts @@ -0,0 +1,374 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const parse = await import("../../src/lib/proxySubscription/parse.ts"); +const model = await import("../../src/lib/proxySubscription/coreConfig/model.ts"); +const { parseSubscription, redactedNodeSummary } = parse; +const { buildCoreModel, OWNED_TAG_PREFIX } = model; + +function clashYaml(): string { + return `proxies: + - name: "Direct HTTP" + type: http + server: 1.2.3.4 + port: 8080 + username: u + password: p + - name: "Node A" + type: ss + server: 9.9.9.9 + port: 8388 + - name: "Node B" + type: vmess + server: 11.11.11.11 + port: 443 + - name: "Node C" + type: vless + server: 12.12.12.12 + port: 443 + - name: "Extra" + type: socks5 + server: 5.6.7.8 + port: 1080 +`; +} + +test("duplicate node names get identity suffixes, stable under permutation", () => { + const feed = "vless://u@1.1.1.1:443#dup\nvless://u@2.2.2.2:443#dup\nvless://u@3.3.3.3:443#dup"; + const parsed = parseSubscription(feed); + assert.equal(parsed.needsCore.length, 3); + const m = buildCoreModel(["socks5://127.0.0.1:1080 selector=g"], parsed.needsCore); + const tags = m.nodes.map((n) => n.tag); + assert.equal(new Set(tags).size, 3); + assert.ok(tags.every((t) => t.startsWith("dup-") && t !== "dup" && !/-2$/.test(t))); + const rev = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=g"], + [...parsed.needsCore].reverse() + ); + const byServer = (model: typeof m): Map => + new Map(model.nodes.map((n) => [(n.source as { kind: "uri"; value: string }).value, n.tag])); + for (const [source, tag] of byServer(m)) assert.equal(byServer(rev).get(source), tag); +}); + +test("no-suffix, valid and non-loopback entries sort into skipped vs groups", () => { + const parsed = parseSubscription("vless://u@1.1.1.1:443#n"); + const m = buildCoreModel( + [ + "socks5://127.0.0.1:1080", + "socks5://127.0.0.1:1081 selector=g", + "http://192.168.1.10:8080 selector=lan", + ], + [...parsed.nodes, ...parsed.needsCore] + ); + assert.equal(m.groups.length, 1); + assert.equal(m.groups[0].tag, `${OWNED_TAG_PREFIX}g`); + const reasons = m.skipped.map((s) => s.reason).sort(); + assert.deepEqual(reasons, ["invalid_endpoint", "no_selector_tag"]); +}); + +test("one group keeps every node, two groups split, zero groups stay empty", () => { + const parsed = parseSubscription( + "vless://u@example.com:443#alpha\nvless://u@example.net:443#beta" + ); + const all = [...parsed.nodes, ...parsed.needsCore]; + const one = buildCoreModel(["socks5://127.0.0.1:1080 selector=g"], all); + assert.equal(one.groups.length, 1); + assert.equal(one.groups[0].members.length, 2); + + const two = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=g1", "socks5://127.0.0.1:1081 selector=g2"], + all + ); + assert.equal(two.groups.length, 2); + const assigned = two.groups.flatMap((g) => g.members).sort(); + assert.deepEqual(assigned, two.nodes.map((n) => n.tag).sort()); + + const none = buildCoreModel([], all); + assert.deepEqual(none.listeners, []); + assert.deepEqual(none.groups, []); + assert.deepEqual(none.nodes, []); +}); + +test("adding a node keeps every group assignment (rendezvous)", () => { + const before = buildCoreModel( + [ + "socks5://127.0.0.1:1080 selector=g1", + "socks5://127.0.0.1:1081 selector=g2", + "socks5://127.0.0.1:1082 selector=g3", + ], + distinctServerNodes(4) + ); + const after = buildCoreModel( + [ + "socks5://127.0.0.1:1080 selector=g1", + "socks5://127.0.0.1:1081 selector=g2", + "socks5://127.0.0.1:1082 selector=g3", + ], + distinctServerNodes(5) + ); + const groupOf = (m: typeof before, tag: string): string | undefined => + m.groups.find((g) => g.members.includes(tag))?.tag; + for (const n of before.nodes) assert.equal(groupOf(after, n.tag), groupOf(before, n.tag)); + const used = new Set(before.nodes.map((n) => groupOf(before, n.tag))); + assert.ok(used.size > 1, `expected spread across groups, got ${[...used]}`); +}); + +test("lines and clash-yaml feeds each keep their source shape", () => { + const uri = parseSubscription("vless://u@example.com:443#alpha"); + assert.equal(uri.needsCore[0].source?.kind, "uri"); + const yaml = parseSubscription(clashYaml()); + assert.equal(yaml.nodes[0].source?.kind, "object"); + assert.equal(yaml.needsCore[0].source?.kind, "object"); +}); + +test("redacted summary never leaks password or source", () => { + const parsed = parseSubscription(clashYaml()); + const text = JSON.stringify(redactedNodeSummary(parsed)); + assert.ok(!text.includes('"password"')); + assert.ok(!text.includes(" p")); + assert.ok(!text.includes('"source"')); +}); + +test("end to end: clash yaml with 5 nodes builds stable groups", () => { + const parsed = parseSubscription(clashYaml()); + assert.equal(parsed.nodes.length + parsed.needsCore.length, 5); + const m = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=main", "http://127.0.0.1:8080 selector=web"], + [...parsed.nodes, ...parsed.needsCore] + ); + assert.equal(m.listeners.length, 2); + assert.equal(m.groups.length, 2); + assert.equal(m.nodes.length, 5); + assert.deepEqual(m.groups.flatMap((g) => g.members).sort(), m.nodes.map((n) => n.tag).sort()); + // Deterministic: same input gives the same output. + assert.deepEqual( + buildCoreModel( + ["socks5://127.0.0.1:1080 selector=main", "http://127.0.0.1:8080 selector=web"], + [...parsed.nodes, ...parsed.needsCore] + ), + m + ); +}); + +function exactDuplicateNodes(count: number, name = "shared"): never[] { + return Array.from({ length: count }, () => ({ + name, + source: { + kind: "object" as const, + value: { + type: "vless", + server: "10.9.0.7", + server_port: 443, + uuid: "uuid-same", + }, + }, + })) as never[]; +} + +const THREE_GROUPS = [ + "socks5://127.0.0.1:1080 selector=g1", + "socks5://127.0.0.1:1081 selector=g2", + "socks5://127.0.0.1:1082 selector=g3", +]; + +test("lines sharing a selector form one group (no member reset)", () => { + const parsed = parseSubscription("vless://u@10.7.0.1:443#only"); + const m = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=g", "socks5://127.0.0.1:1081 selector=g"], + [...parsed.nodes, ...parsed.needsCore] + ); + assert.equal(m.listeners.length, 2); + assert.equal(m.groups.length, 1); + assert.equal(m.groups[0].members.length, 1); +}); + +test("exact duplicates share one group, the other groups stay reported", () => { + const m = buildCoreModel(THREE_GROUPS, exactDuplicateNodes(60)); + const sizes = m.groups.map((g) => g.members.length).sort((a, b) => a - b); + assert.deepEqual(sizes, [0, 0, 60]); + assert.equal(m.skipped.filter((s) => s.reason === "empty_group").length, 2); +}); + +test("populated groups report no empty_group", () => { + const nodes = Array.from({ length: 60 }, (_, i) => ({ + name: `node-${i + 1}`, + source: { + kind: "object" as const, + value: { + type: "vless", + server: `10.8.0.${i + 1}`, + server_port: 443, + uuid: `uuid-${i + 1}`, + }, + }, + })) as never[]; + const m = buildCoreModel(THREE_GROUPS, nodes); + assert.ok(m.groups.every((g) => g.members.length > 0)); + assert.equal(m.skipped.filter((s) => s.reason === "empty_group").length, 0); +}); + +test("groups with only sourceless nodes report one empty_group each", () => { + const nodes = [{ name: "ghost-a" }, { name: "ghost-b" }, { name: "ghost-c" }] as never[]; + const m = buildCoreModel(THREE_GROUPS, nodes); + const empty = m.skipped.filter((s) => s.reason === "empty_group"); + assert.equal(empty.length, 3); + assert.deepEqual(empty.map((s) => s.node).sort(), m.groups.map((g) => g.tag).sort()); +}); + +function distinctServerNodes(count: number, name = "shared"): never[] { + return Array.from({ length: count }, (_, i) => ({ + name, + source: { + kind: "object" as const, + value: { + type: "vless", + server: `10.9.0.${i + 1}`, + server_port: 443, + uuid: `uuid-${i + 1}`, + }, + }, + })) as never[]; +} + +test("same display name nodes spread across every group", () => { + const m = buildCoreModel(THREE_GROUPS, distinctServerNodes(60)); + assert.ok(m.groups.every((g) => g.members.length > 0)); +}); + +test("permuted same-name nodes keep their server tag", () => { + const mk = (server: string) => + ({ + name: "shared", + source: { + kind: "object" as const, + value: { type: "vless", server, server_port: 443, uuid: "uuid-x" }, + }, + }) as never; + const forward = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=g"], + [mk("10.9.0.1"), mk("10.9.0.2")] + ); + const backward = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=g"], + [mk("10.9.0.2"), mk("10.9.0.1")] + ); + const tagOf = (tag: string): string | undefined => forward.nodes.find((n) => n.tag === tag)?.tag; + assert.ok(typeof tagOf === "function"); + assert.deepEqual(forward.nodes.map((n) => n.tag).sort(), backward.nodes.map((n) => n.tag).sort()); + const byServerFwd = new Map(forward.nodes.map((n) => [n.source.value.server, n.tag])); + const byServerBwd = new Map(backward.nodes.map((n) => [n.source.value.server, n.tag])); + assert.equal(byServerFwd.get("10.9.0.1"), byServerBwd.get("10.9.0.1")); + assert.equal(byServerFwd.get("10.9.0.2"), byServerBwd.get("10.9.0.2")); +}); + +test("removing a group moves only its members", () => { + const nodes = distinctServerNodes(60); + const three = buildCoreModel(THREE_GROUPS, nodes); + const tagIndex = new Map(three.nodes.map((n) => [n.tag, n.tag])); + assert.equal(tagIndex.size, three.nodes.length); + const groupOf = (m: typeof three, tag: string): string | undefined => + m.groups.find((g) => g.members.includes(tag))?.tag; + const removedGroup = three.groups + .map((g) => g.tag) + .sort() + .pop()!; + const keptEntries = THREE_GROUPS.filter( + (e) => !e.includes(`selector=${removedGroup.replace(OWNED_TAG_PREFIX, "")}`) + ); + const two = buildCoreModel(keptEntries, nodes); + const moved = three.nodes.filter((n) => groupOf(three, n.tag) !== groupOf(two, n.tag)); + const removedMembers = new Set(three.groups.find((g) => g.tag === removedGroup)?.members ?? []); + assert.ok(moved.length > 0); + assert.ok(moved.every((n) => removedMembers.has(n.tag))); +}); + +test("sourceless nodes keep current names and groups", () => { + const nodes = [ + { name: "dup", source: { kind: "uri" as const, value: "not a url at all" } }, + { name: "dup", source: { kind: "uri" as const, value: "also not a url" } }, + { name: "dup", source: { kind: "uri" as const, value: "still not a url" } }, + ] as never[]; + const m = buildCoreModel(["socks5://127.0.0.1:1080 selector=g"], nodes); + assert.deepEqual( + m.nodes.map((n) => n.tag), + ["dup", "dup-2", "dup-3"] + ); +}); + +test("mixed sourced and sourceless nodes stay deterministic", () => { + const sourced = distinctServerNodes(3); + const mixed = [...sourced, { name: "ghost" } as never]; + const first = buildCoreModel(THREE_GROUPS, mixed); + const second = buildCoreModel(THREE_GROUPS, mixed); + assert.deepEqual(first, second); +}); + +test("object and uri forms of one node share tag and group", () => { + const objectNode = { + name: "same", + source: { + kind: "object" as const, + value: { type: "vless", server: "EXAMPLE.com", server_port: 443, uuid: "u" }, + }, + } as never; + const uriNode = { + name: "same", + source: { kind: "uri" as const, value: "vless://u@EXAMPLE.com:443#same" }, + } as never; + const fromObject = buildCoreModel(THREE_GROUPS, [objectNode]); + const fromUri = buildCoreModel(THREE_GROUPS, [uriNode]); + assert.deepEqual( + fromObject.nodes.map((n) => n.tag), + fromUri.nodes.map((n) => n.tag) + ); + const groupOf = (m: typeof fromObject, tag: string): string | undefined => + m.groups.find((g) => g.members.includes(tag))?.tag; + assert.equal( + groupOf(fromObject, fromObject.nodes[0].tag), + groupOf(fromUri, fromUri.nodes[0].tag) + ); +}); + +test("missing port and explicit port stay distinct", () => { + const noPort = { + name: "same", + source: { kind: "object" as const, value: { type: "vless", server: "10.9.0.1", uuid: "u" } }, + } as never; + const explicit = { + name: "same", + source: { + kind: "object" as const, + value: { type: "vless", server: "10.9.0.1", server_port: 443, uuid: "u" }, + }, + } as never; + const uriNoPort = { + name: "same", + source: { kind: "uri" as const, value: "vless://u@10.9.0.1#same" }, + } as never; + const a = buildCoreModel(THREE_GROUPS, [noPort]); + const b = buildCoreModel(THREE_GROUPS, [uriNoPort]); + const c = buildCoreModel(THREE_GROUPS, [explicit]); + assert.deepEqual( + a.nodes.map((n) => n.tag), + b.nodes.map((n) => n.tag) + ); + assert.notEqual(a.nodes[0].tag, c.nodes[0].tag); +}); + +test("nfc-equivalent names share tag base and fallback group", () => { + const composed = "caf\u00e9"; + const decomposed = "café"; + assert.notEqual(composed, decomposed); + const mk = (name: string) => + ({ + name, + source: { kind: "uri" as const, value: "not a url" }, + }) as never; + const first = buildCoreModel(["socks5://127.0.0.1:1080 selector=g"], [mk(composed)]); + const second = buildCoreModel(["socks5://127.0.0.1:1080 selector=g"], [mk(decomposed)]); + assert.deepEqual( + first.nodes.map((n) => n.tag), + second.nodes.map((n) => n.tag) + ); + assert.deepEqual(first.groups, second.groups); +}); diff --git a/tests/unit/proxySubscription.corePruneRejected.test.ts b/tests/unit/proxySubscription.corePruneRejected.test.ts new file mode 100644 index 000000000000..2c0f89275aa3 --- /dev/null +++ b/tests/unit/proxySubscription.corePruneRejected.test.ts @@ -0,0 +1,209 @@ +/** + * Core verification prunes rejected nodes: when the native check names an + * offending node, the node is dropped, the candidate is re-rendered and the + * check runs again (bounded); anything else keeps the previous behaviour + * (warning, adopted file untouched). + * + * The check runner is injected (never the real binary). The only + * adapter-shaped strings below live in the small stub helper marked as such: + * the rejection token shape is owned by the sing-box adapter + * (see `resolveOffendingTag`); every assertion uses core/node language. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const syncMod = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); +const renderersMod = await import("../../src/lib/proxySubscription/coreConfig/renderers.ts"); +const adapterMod = await import("../../src/lib/proxySubscription/coreConfig/singbox.ts"); +import type { RunCheck } from "../../src/lib/proxySubscription/coreConfig/apply.ts"; + +const { generateCoreConfigIntention, MAX_PRUNE_ATTEMPTS } = syncMod; +const { OFFENDING_RESOLVERS, DEFAULT_CORE } = renderersMod; +const { resolveOffendingTag } = adapterMod; + +const ENDPOINTS = "socks5://127.0.0.1:1080 selector=a\nsocks5://127.0.0.1:1081 selector=b"; + +function feed(names: string[]): { nodes: never[]; needsCore: never[]; format: string } { + return { + nodes: [], + needsCore: names.map((name, i) => ({ + name, + source: { + kind: "object" as const, + value: { type: "vless", server: `203.0.113.${i + 1}`, server_port: 443, uuid: `u-${i}` }, + }, + })) as never[], + format: "v2ray-json", + }; +} + +function setup(): { dir: string; target: string; binary: string; before: Buffer } { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-core-prune-")); + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const binary = path.join(dir, "sing-box"); + fs.writeFileSync(binary, "#!/bin/sh\nexit 1\n"); + fs.chmodSync(binary, 0o755); + return { dir, target, binary, before: fs.readFileSync(target) }; +} + +/** + * Adapter-token stub (see module header): fails the check while any victim + * node is still rendered, naming its position with the adapter's token; + * passes once every victim is gone. A victim-free error fakes a rejection + * the core cannot attribute; an out-of-range position fakes one the table + * does not own. + */ +function rejecting( + victims: string[], + calls: string[][], + opts?: { freeError?: string; foreignPosition?: number } +): RunCheck { + return async (_bin, args) => { + calls.push(args); + const candidate = args[args.length - 1]; + const outbounds = JSON.parse(fs.readFileSync(candidate, "utf8")).outbounds as Array<{ + tag: string; + }>; + if (opts?.freeError) { + throw Object.assign(new Error("Command failed"), { stderr: opts.freeError }); + } + if (opts?.foreignPosition !== undefined) { + throw Object.assign(new Error("Command failed"), { + stderr: `FATAL initialize outbound[${opts.foreignPosition}]: rejected field value`, + }); + } + for (const victim of victims) { + const index = outbounds.findIndex((o) => o.tag.startsWith(`omniroute-${victim}`)); + if (index >= 0) { + throw Object.assign(new Error("Command failed"), { + stderr: `FATAL initialize outbound[${index}]: rejected field value`, + }); + } + } + return { stdout: "", stderr: "" }; + }; +} + +function nodeTags(target: string): string[] { + const outbounds = JSON.parse(fs.readFileSync(target, "utf8")).outbounds as Array<{ + tag: string; + }>; + return outbounds + .map((o) => o.tag) + .filter((t) => t.startsWith("omniroute-node-")) + .map((t) => t.replace(/-[0-9a-f]{6}(-\d+)?$/, "")); +} + +async function sub( + target: string, + binary: string, + runCheck: RunCheck, + maxPruneAttempts?: number +): Promise { + process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH = binary; + try { + return ( + await generateCoreConfigIntention( + { coreConfigPath: target, localCoreEndpoint: ENDPOINTS, id: "s-prune" }, + feed(["node-a", "node-b", "node-c"]) as never, + { runCheck, ...(maxPruneAttempts !== undefined ? { maxPruneAttempts } : {}) } + ) + ).warning; + } finally { + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + } +} + +test("rejected node is pruned and the check runs again (one rejection)", async (t) => { + const { dir, target, binary, before } = setup(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const calls: string[][] = []; + const warning = await sub(target, binary, rejecting(["node-b"], calls)); + assert.ok(warning?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), `got: ${warning}`); + assert.deepEqual(nodeTags(target).sort(), ["omniroute-node-a", "omniroute-node-c"]); + assert.ok(!fs.readFileSync(target, "utf8").includes("omniroute-node-b")); + assert.equal(calls.length, 2); + void before; +}); + +test("two rejections take two rounds", async (t) => { + const { dir, target, binary } = setup(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const calls: string[][] = []; + const warning = await sub(target, binary, rejecting(["node-a", "node-c"], calls)); + assert.ok(warning?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), `got: ${warning}`); + assert.deepEqual(nodeTags(target), ["omniroute-node-b"]); + assert.equal(calls.length, 3); +}); + +test("attempt bound keeps the previous behaviour", async (t) => { + const { dir, target, binary, before } = setup(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const calls: string[][] = []; + const warning = await sub(target, binary, rejecting(["node-a", "node-b"], calls), 1); + assert.ok(warning?.includes("check_failed"), `got: ${warning}`); + assert.ok(!warning?.includes("CORE_CONFIG_ENTRIES_SKIPPED")); + assert.deepEqual(fs.readFileSync(target), before); + assert.equal(calls.length, 2); +}); + +test("rejection without a named node keeps the previous behaviour", async (t) => { + const { dir, target, binary, before } = setup(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const calls: string[][] = []; + const warning = await sub( + target, + binary, + rejecting([], calls, { freeError: "FATAL: configuration missing" }) + ); + assert.ok(warning?.includes("check_failed"), `got: ${warning}`); + assert.deepEqual(fs.readFileSync(target), before); + assert.equal(calls.length, 1); +}); + +test("rejection naming a position the table does not own keeps the previous behaviour", async (t) => { + const { dir, target, binary, before } = setup(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const calls: string[][] = []; + const warning = await sub(target, binary, rejecting([], calls, { foreignPosition: 99 })); + assert.ok(warning?.includes("check_failed"), `got: ${warning}`); + assert.deepEqual(fs.readFileSync(target), before); + assert.equal(calls.length, 1); +}); + +test("pruning every node stops before an empty render (adopted file intact)", async (t) => { + const { dir, target, binary, before } = setup(); + t.after(() => fs.rmSync(dir, { recursive: true, force: true })); + const calls: string[][] = []; + const warning = await sub(target, binary, rejecting(["node-a", "node-b", "node-c"], calls)); + assert.ok(warning?.includes("check_failed"), `got: ${warning}`); + assert.deepEqual(fs.readFileSync(target), before); + assert.equal(calls.length, 3); +}); + +test("prune budget defaults to eight", () => { + assert.equal(MAX_PRUNE_ATTEMPTS, 8); +}); + +test("core registry resolves through the default core adapter", () => { + assert.equal(OFFENDING_RESOLVERS[DEFAULT_CORE], resolveOffendingTag); +}); + +test("adapter maps the first named position to the owned node tag", () => { + const table = [null, "node-b", "node-c"]; + const mark = resolveOffendingTag("FATAL initialize outbound[1]: rejected field value", table); + assert.equal(mark?.tag, "node-b"); +}); + +test("adapter ignores unowned positions, unknown indexes and free errors", () => { + const table: Array = [null, "node-b"]; + assert.equal(resolveOffendingTag("FATAL initialize outbound[0]: rejected", table), null); + assert.equal(resolveOffendingTag("FATAL initialize outbound[7]: rejected", table), null); + assert.equal(resolveOffendingTag("FATAL: configuration missing", table), null); + const first = resolveOffendingTag("outbound[1] then outbound[0]", table); + assert.equal(first?.tag, "node-b"); +}); diff --git a/tests/unit/proxySubscription.coreReload.test.ts b/tests/unit/proxySubscription.coreReload.test.ts new file mode 100644 index 000000000000..93422b7fb678 --- /dev/null +++ b/tests/unit/proxySubscription.coreReload.test.ts @@ -0,0 +1,583 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +function makeFeed(tags: string[]): { + nodes: never[]; + needsCore: unknown[]; + format: "v2ray-json"; +} { + const mk = (tag: string, port: number) => ({ + name: tag, + source: { + kind: "object" as const, + value: { type: "vless", server: "203.0.113.9", server_port: port, uuid: `u-${tag}` }, + }, + }); + return { + nodes: [], + needsCore: tags.map((t, i) => mk(t, 443 + i)) as never[], + format: "v2ray-json", + }; +} + +const ENDPOINTS = "socks5://127.0.0.1:1080 selector=a\nsocks5://127.0.0.1:1081 selector=b"; + +async function freshReload() { + const reload = await import("../../src/lib/proxySubscription/coreConfig/reload.ts"); + reload.__resetCoreReloadTestState(); + return reload; +} + +// 1. Nothing declared after a replacement reports undeclared, no fetch/exec. +test("nothing declared after a replacement reports undeclared", async () => { + const reload = await freshReload(); + let fetchCalls = 0; + let execCalls = 0; + const outcome = await reload.reloadCore({ + subscriptionId: "case-1", + controlUrl: null, + secret: null, + configPath: "core.json", + command: null, + fetchFn: async () => { + fetchCalls += 1; + return { status: 200 }; + }, + execFn: async () => { + execCalls += 1; + return { code: 0 }; + }, + }); + assert.equal(outcome.kind, "undeclared"); + assert.equal(fetchCalls, 0); + assert.equal(execCalls, 0); + assert.equal( + reload.resolveCoreReloadMode({ + controlUrl: null, + apiCapable: false, + commandDecl: { kind: "absent" }, + }), + "undeclared" + ); +}); + +// 2. External succeeds silently, exposed mode is external. +test("external declaration succeeds silently", async () => { + const reload = await freshReload(); + let calls = 0; + const outcome = await reload.reloadCore({ + subscriptionId: "case-2", + controlUrl: null, + secret: null, + configPath: "core.json", + command: "external", + execFn: async () => { + calls += 1; + return { code: 0 }; + }, + }); + assert.equal(outcome.kind, "external"); + assert.equal(calls, 0); + assert.equal( + reload.resolveCoreReloadMode({ + controlUrl: null, + apiCapable: false, + commandDecl: { kind: "external" }, + }), + "external" + ); +}); + +// 3. Deleting a subscription clears its state and cancels the deferred run. +test("clearing state cancels a deferred reload", async () => { + const reload = await freshReload(); + let now = 1_000_000; + const jobs: Array<() => void> = []; + let calls = 0; + const base = { + subscriptionId: "case-3", + controlUrl: null, + secret: null, + configPath: "core.json", + command: ["/bin/true"], + nowFn: () => now, + scheduleFn: (_ms: number, fn: () => void) => { + jobs.push(fn); + return () => {}; + }, + execFn: async () => { + calls += 1; + return { code: 0 }; + }, + }; + await reload.reloadCore({ ...base }); + assert.equal(calls, 1); + await reload.reloadCore({ ...base }); + assert.equal(jobs.length, 1); + assert.equal(reload.__coreReloadStateSize(), 1); + reload.clearCoreReloadState("case-3"); + assert.equal(reload.__coreReloadStateSize(), 0); + now += 61_000; + for (const j of jobs) j(); + await new Promise((r) => setTimeout(r, 10)); + assert.equal(calls, 1); +}); + +// 3bis. First verified replacement without a seed reports digestChanged:true, +// an identical second one reports false (via finishVerified). +test("first replaced without seed reloads, identical digest does not", async () => { + const reload = await freshReload(); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const fs = await import("node:fs"); + const os = await import("node:os"); + const path = await import("node:path"); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reload-3bis-")); + try { + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const binDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reload-bin-")); + const fakeBin = path.join(binDir, "sing-box"); + fs.writeFileSync( + fakeBin, + '#!/bin/sh\nif grep -q "BAD" "$3" 2>/dev/null; then exit 1; fi\nexit 0\n' + ); + fs.chmodSync(fakeBin, 0o755); + const run = ( + sync as unknown as { + generateCoreConfigIntention: ( + sub: unknown, + parsed: unknown + ) => Promise<{ + status: string; + digestChanged: boolean; + warning: string | null; + membersDigest?: string; + }>; + } + ).generateCoreConfigIntention; + const sub = { + id: "case-3bis", + coreConfigPath: target, + localCoreEndpoint: ENDPOINTS, + }; + process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH = fakeBin; + // Absent seed counts as a change: first verified replacement reloads. + assert.equal(reload.getLastMembersDigest("case-3bis"), undefined); + const first = await run(sub, makeFeed(["sb-1", "sb-2"])); + assert.equal(first.warning, null); + assert.equal(first.status, "replaced"); + assert.equal(first.digestChanged, true); + // Identical members: adopted file already current, no replacement at all. + const second = await run(sub, makeFeed(["sb-1", "sb-2"])); + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + assert.equal(second.status, "none"); + assert.equal(second.digestChanged, false); + fs.rmSync(binDir, { recursive: true, force: true }); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +// 4. Invalid command is ignored with a single process-wide warning. +test("invalid command is ignored with one warning across syncs", async () => { + const reload = await freshReload(); + const warnings: string[] = []; + const orig = console.warn; + console.warn = (msg: string) => { + warnings.push(String(msg)); + }; + try { + let calls = 0; + for (const bad of ['"not an array"', "[]", "not json"]) { + const outcome = await reload.reloadCore({ + subscriptionId: `case-4-${bad.length}`, + controlUrl: null, + secret: null, + configPath: "core.json", + command: bad, + execFn: async () => { + calls += 1; + return { code: 0 }; + }, + }); + assert.equal(outcome.kind, "undeclared"); + } + assert.equal(calls, 0); + assert.equal(warnings.length, 1); + } finally { + console.warn = orig; + } +}); + +// 5. Two replacements in a minute run once now, once deferred at expiry. +test("second replacement inside the window is deferred then runs at expiry", async () => { + const reload = await freshReload(); + let now = 2_000_000; + const jobs: Array<() => void> = []; + let calls = 0; + const base = { + subscriptionId: "case-5", + controlUrl: null, + secret: null, + configPath: "core.json", + command: ["/bin/true"], + nowFn: () => now, + scheduleFn: (_ms: number, fn: () => void) => { + assert.ok(_ms > 0 && _ms <= 60_000); + jobs.push(fn); + return () => {}; + }, + execFn: async () => { + calls += 1; + return { code: 0 }; + }, + }; + const first = await reload.reloadCore({ ...base }); + assert.equal(first.kind, "command"); + now += 20_000; + const second = await reload.reloadCore({ ...base }); + assert.equal(second.kind, "command"); + assert.equal(calls, 1); + assert.equal(jobs.length, 1); + now += 40_000; + jobs[0](); + await new Promise((r) => setTimeout(r, 10)); + assert.equal(calls, 2); +}); + +// 6. Same endpoint with a changed port gets a new identity tag, so the digest +// changes and the core reloads once (identity-derived tags replace the old +// positional display-name tags). +test("changed port changes identity tag and digest", async () => { + const reload = await freshReload(); + const singbox = await import("../../src/lib/proxySubscription/coreConfig/singbox.ts"); + const model = await import("../../src/lib/proxySubscription/coreConfig/model.ts"); + const core = await import("../../src/lib/proxySubscription/coreEndpoint.ts"); + const base = JSON.stringify({ inbounds: [], outbounds: [] }); + const endpoints = core.parseLocalCoreEndpoints(ENDPOINTS); + const mkFeed = (port: number) => ({ + nodes: [], + needsCore: [ + { + name: "sb-1", + source: { + kind: "object", + value: { type: "vless", server: "203.0.113.9", server_port: port, uuid: "u-same" }, + }, + }, + ], + format: "v2ray-json", + }); + const r1 = singbox.renderSingBox( + model.buildCoreModel( + endpoints, + (mkFeed(443) as unknown as { nodes: []; needsCore: never[] }).needsCore.concat([]) as never[] + ), + base + ); + const r2 = singbox.renderSingBox( + model.buildCoreModel( + endpoints, + (mkFeed(8443) as unknown as { nodes: []; needsCore: never[] }).needsCore.concat([]) as never[] + ), + base + ); + assert.equal(r1.ok, true); + assert.equal(r2.ok, true); + if (r1.ok && r2.ok) { + assert.notEqual(r1.membersDigest, r2.membersDigest); + } + assert.equal(typeof reload.getLastMembersDigest, "function"); +}); + +// 7. Failing command maps to failed with exit-code. +test("command exit 1 maps to failed", async () => { + const reload = await freshReload(); + const outcome = await reload.reloadCore({ + subscriptionId: "case-7", + controlUrl: null, + secret: null, + configPath: "core.json", + command: ["/bin/false"], + execFn: async () => ({ code: 1 }), + }); + assert.equal(outcome.kind, "failed"); + assert.equal(outcome.reason, "exit-code"); +}); + +// 8. Overrunning command maps to failed with timeout (rejected timeout shape). +test("command timeout maps to failed with timeout reason", async () => { + const reload = await freshReload(); + const outcome = await reload.reloadCore({ + subscriptionId: "case-8", + controlUrl: null, + secret: null, + configPath: "core.json", + command: ["/bin/sleep", "60"], + execFn: async () => { + const err = new Error("reload command timed out") as Error & { code: string }; + err.code = "ETIMEDOUT"; + throw err; + }, + }); + assert.equal(outcome.kind, "failed"); + assert.equal(outcome.reason, "timeout"); +}); + +// 9. Trapped arguments pass through verbatim (no shell). +test("trapped arguments are passed verbatim", async () => { + const reload = await freshReload(); + const seen: Array<{ file: string; args: string[] }> = []; + const outcome = await reload.reloadCore({ + subscriptionId: "case-9", + controlUrl: null, + secret: null, + configPath: "core.json", + command: ["/bin/echo", "$(rm -rf /)", "a b"], + execFn: async (file: string, args: string[]) => { + seen.push({ file, args }); + return { code: 0 }; + }, + }); + assert.equal(outcome.kind, "command"); + assert.deepEqual(seen[0], { file: "/bin/echo", args: ["$(rm -rf /)", "a b"] }); +}); + +// 10. Beside-write without replacement reports no-call intention. +test("beside-write without a binary reports status none", async () => { + await freshReload(); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const fs = await import("node:fs"); + const os = await import("node:os"); + const path = await import("node:path"); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reload-10-")); + try { + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const intention = await ( + sync as unknown as { + generateCoreConfigIntention: ( + sub: unknown, + parsed: unknown + ) => Promise<{ status: string; digestChanged: boolean; warning: string | null }>; + } + ).generateCoreConfigIntention( + { id: "case-10", coreConfigPath: target, localCoreEndpoint: ENDPOINTS }, + makeFeed(["sb-1"]) + ); + assert.equal(intention.status, "none"); + assert.equal(intention.digestChanged, false); + assert.ok( + intention.warning?.includes("CORE_CONFIG_ENTRIES_SKIPPED"), + `got: ${intention.warning}` + ); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +// 11. Capable renderer triggers a guarded PUT; sing-box never fetches. +test("capable renderer calls the control API after the guard", async () => { + const reload = await freshReload(); + const renderers = await import("../../src/lib/proxySubscription/coreConfig/renderers.ts"); + const orig = renderers.RENDERERS[renderers.DEFAULT_CORE]; + let guardCalls = 0; + const seen: Array<{ url: string; init: Record }> = []; + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = Object.assign( + (...a: unknown[]) => (orig as (...x: unknown[]) => unknown)(...a), + { apiReload: true } + ); + try { + // Loopback control URL passes the real fetch-time guard without DNS. + const outcome = await reload.reloadCore({ + subscriptionId: "case-11", + controlUrl: "http://127.0.0.1:9090", + secret: "s3cret", + configPath: "core.json", + command: null, + fetchFn: async (url: string, init: Record) => { + guardCalls += 1; + seen.push({ url, init }); + return { status: 204 }; + }, + }); + assert.equal(outcome.kind, "api"); + assert.equal(seen.length, 1); + assert.ok(seen[0].url.endsWith("/configs?force=true")); + assert.equal(seen[0].init.body as string, JSON.stringify({ path: "core.json" })); + assert.ok(guardCalls >= 0); + } finally { + (renderers.RENDERERS as Record)[renderers.DEFAULT_CORE] = orig; + } + // A blocked control URL never reaches fetch (falls back to undeclared here). + { + let fetchCalls = 0; + const outcome = await reload.reloadCore({ + subscriptionId: "case-11b", + controlUrl: "http://192.168.1.1:9090", + secret: "s3cret", + configPath: "core.json", + command: null, + fetchFn: async () => { + fetchCalls += 1; + return { status: 204 }; + }, + }); + assert.equal(outcome.kind, "undeclared"); + assert.equal(fetchCalls, 0); + } +}); + +// State size tracks subscriptions: two ids then delete one. +test("state size tracks two subscriptions, delete leaves one", async () => { + const reload = await freshReload(); + let now = 3_000_000; + const jobs = new Map void>>(); + let calls = 0; + const mkBase = (id: string) => ({ + subscriptionId: id, + controlUrl: null, + secret: null, + configPath: "core.json", + command: ["/bin/true"], + nowFn: () => now, + scheduleFn: (_ms: number, fn: () => void) => { + void _ms; + const list = jobs.get(id) ?? []; + list.push(fn); + jobs.set(id, list); + return () => {}; + }, + execFn: async () => { + calls += 1; + return { code: 0 }; + }, + }); + reload.setLastMembersDigest("r2-a", "d1"); + reload.setLastMembersDigest("r2-b", "d2"); + assert.equal(reload.__coreReloadStateSize(), 2); + await reload.reloadCore(mkBase("r2-a")); + await reload.reloadCore(mkBase("r2-b")); + assert.equal(reload.__coreReloadStateSize(), 2); + reload.clearCoreReloadState("r2-a"); + assert.equal(reload.__coreReloadStateSize(), 1); + assert.equal(calls, 2); + void now; +}); + +// Functional: five verified syncs with stable members reload nothing (first +// seeds, rest are current), then one added member reloads exactly once. +// reloadCore is counted by wrapping the module through the service path: +// the intention gate decides, the counting exec proves the call. +test("stable syncs reload nothing, added member reloads", async () => { + const reload = await freshReload(); + const sync = await import("../../src/lib/proxySubscription/coreConfig/sync.ts"); + const fs = await import("node:fs"); + const os = await import("node:os"); + const path = await import("node:path"); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reload-fn-")); + const binDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reload-fnbin-")); + try { + const target = path.join(dir, "core.json"); + fs.writeFileSync(target, JSON.stringify({ inbounds: [], outbounds: [] })); + const fakeBin = path.join(binDir, "sing-box"); + fs.writeFileSync( + fakeBin, + '#!/bin/sh\nif grep -q "BAD" "$3" 2>/dev/null; then exit 1; fi\nexit 0\n' + ); + fs.chmodSync(fakeBin, 0o755); + const run = ( + sync as unknown as { + generateCoreConfigIntention: ( + sub: unknown, + parsed: unknown + ) => Promise<{ + status: string; + digestChanged: boolean; + warning: string | null; + membersDigest?: string; + }>; + } + ).generateCoreConfigIntention; + const sub = { + id: "fn-1", + coreConfigPath: target, + localCoreEndpoint: ENDPOINTS, + }; + process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH = fakeBin; + // Count reloadCore executions by routing the intention through it with an + // injected command executor (no real core, no real process). A fake clock + // steps past the 60 s cadence between phases so each decision executes. + let reloadCalls = 0; + let now = 10_000_000; + const maybeReload = async (intention: { + status: string; + digestChanged: boolean; + warning: string | null; + configPath?: string; + }): Promise => { + if (intention.warning || intention.status !== "replaced" || !intention.digestChanged) return; + now += 61_000; + const outcome = await reload.reloadCore({ + subscriptionId: "fn-1", + controlUrl: null, + secret: null, + configPath: target, + command: ["/bin/true"], + nowFn: () => now, + scheduleFn: (_ms: number, fn: () => void) => { + fn(); + return () => {}; + }, + execFn: async () => { + reloadCalls += 1; + return { code: 0 }; + }, + }); + assert.notEqual(outcome.kind, "undeclared"); + }; + // First verified sync seeds the digest and reloads once. + const seed = await run(sub, makeFeed(["sb-1", "sb-2"])); + assert.equal(seed.warning, null); + assert.equal(seed.status, "replaced"); + await maybeReload(seed); + assert.equal(reloadCalls, 1); + // Four further stable syncs: adopted file current, zero reloads. + for (let i = 0; i < 4; i += 1) { + const intention = await run(sub, makeFeed(["sb-1", "sb-2"])); + assert.equal(intention.status, "none"); + await maybeReload(intention); + } + assert.equal(reloadCalls, 1); + // One added member: replaced + digest changed → exactly one more reload. + const grown = await run(sub, makeFeed(["sb-1", "sb-2", "sb-3"])); + assert.equal(grown.warning, null); + assert.equal(grown.status, "replaced"); + assert.equal(grown.digestChanged, true); + await maybeReload(grown); + assert.equal(reloadCalls, 2); + // One removed member: N → N-1 reloads as well. + const shrunk = await run(sub, makeFeed(["sb-1", "sb-2"])); + assert.equal(shrunk.warning, null); + assert.equal(shrunk.status, "replaced"); + assert.equal(shrunk.digestChanged, true); + await maybeReload(shrunk); + assert.equal(reloadCalls, 3); + } finally { + delete process.env.OMNIROUTE_PROXY_CORE_BINARY_PATH; + fs.rmSync(dir, { recursive: true, force: true }); + fs.rmSync(binDir, { recursive: true, force: true }); + } +}); + +test("reload warnings encode a terminal failure and an undeclared core", async () => { + const secret = await import("../../src/lib/proxySubscription/reloadSecret.ts"); + assert.equal( + secret.encodeReloadWarning("CORE_RELOAD_FAILED", "exit-code"), + JSON.stringify({ code: "CORE_RELOAD_FAILED", detail: "exit-code" }) + ); + assert.equal( + secret.encodeReloadWarning("CORE_RELOAD_UNDECLARED"), + JSON.stringify({ code: "CORE_RELOAD_UNDECLARED" }) + ); +}); diff --git a/tests/unit/proxySubscription.coreRenderSingbox.test.ts b/tests/unit/proxySubscription.coreRenderSingbox.test.ts new file mode 100644 index 000000000000..37fbc87046e3 --- /dev/null +++ b/tests/unit/proxySubscription.coreRenderSingbox.test.ts @@ -0,0 +1,231 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { buildCoreModel, OWNED_TAG_PREFIX } = + await import("../../src/lib/proxySubscription/coreConfig/model.ts"); +const { renderSingBox, isSingBoxShape } = + await import("../../src/lib/proxySubscription/coreConfig/singbox.ts"); +const renderers = await import("../../src/lib/proxySubscription/coreConfig/renderers.ts"); + +function objNode(name: string, extra: Record = {}): never { + throw new Error(`test helper miswired: ${name} ${JSON.stringify(extra)}`); +} + +function modelOf(count: number, opts?: { clash?: boolean; uri?: boolean }) { + const nodes = Array.from({ length: count }, (_, i) => ({ + name: `node-${i + 1}`, + source: + opts?.clash || opts?.uri + ? { kind: "uri" as const, value: `vless://u@example.com:443#node-${i + 1}` } + : { + kind: "object" as const, + value: { + type: "vless", + server: `10.0.0.${i + 1}`, + server_port: 443 + i, + uuid: `uuid-${i + 1}`, + }, + }, + })); + return buildCoreModel( + ["socks5://127.0.0.1:1080 selector=a", "http://127.0.0.1:8080 selector=b"], + nodes as never + ); +} + +function realisticExisting(): string { + return JSON.stringify( + { + log: { level: "info" }, + dns: { servers: ["https://1.1.1.1/dns-query"] }, + experimental: { clash_api: { external_controller: "127.0.0.1:9090" } }, + inbounds: [{ type: "mixed", tag: "user-in", listen: "127.0.0.1", listen_port: 7890 }], + outbounds: [ + { type: "direct", tag: "direct" }, + { type: "selector", tag: "user-pick", outbounds: ["direct"] }, + ], + route: { + rules: [{ outbound: "direct", inbound: ["user-in"] }], + final: "direct", + }, + }, + null, + 2 + ); +} + +test("preserves dns, log, clash_api and user rules verbatim (deep compare)", () => { + const before = JSON.parse(realisticExisting()); + const res = renderSingBox(modelOf(4), realisticExisting()); + assert.equal(res.ok, true); + if (!res.ok) return; + const after = JSON.parse(res.text); + assert.deepEqual(after.log, before.log); + assert.deepEqual(after.dns, before.dns); + assert.deepEqual(after.experimental, before.experimental); + assert.ok(after.inbounds.some((i: { tag: string }) => i.tag === "user-in")); + assert.ok(after.outbounds.some((o: { tag: string }) => o.tag === "direct")); + assert.ok(after.outbounds.some((o: { tag: string }) => o.tag === "user-pick")); + assert.ok(after.route.rules.some((r: { outbound: string }) => r.outbound === "direct")); + assert.equal(after.route.final, "direct"); +}); + +test("second render is unchanged", () => { + const first = renderSingBox(modelOf(4), realisticExisting()); + assert.equal(first.ok, true); + if (!first.ok) return; + assert.equal(first.unchanged, false); + const second = renderSingBox(modelOf(4), first.text); + assert.equal(second.ok, true); + if (!second.ok) return; + assert.equal(second.unchanged, true); + assert.equal(second.text, first.text); +}); + +test("removed member disappears, neighbors stay", () => { + const m = modelOf(4); + const first = renderSingBox(m, null); + assert.equal(first.ok, true); + if (!first.ok) return; + const removedTag = `${OWNED_TAG_PREFIX}${m.nodes[3].tag}`; + const small = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=a", "http://127.0.0.1:8080 selector=b"], + m.nodes.slice(0, 3) as never + ); + const second = renderSingBox(small, first.text); + assert.equal(second.ok, true); + if (!second.ok) return; + const after = JSON.parse(second.text); + const tags = after.outbounds.map((o: { tag: string }) => o.tag); + assert.ok(!tags.includes(removedTag), `removed ${removedTag} still present`); + for (const n of small.nodes) assert.ok(tags.includes(`${OWNED_TAG_PREFIX}${n.tag}`)); +}); + +test("refuses {} / not-json / array roots", () => { + assert.deepEqual(renderSingBox(modelOf(1), "{}"), { ok: false, reason: "no_ownable_section" }); + assert.deepEqual(renderSingBox(modelOf(1), "not json"), { ok: false, reason: "unparseable" }); + assert.deepEqual(renderSingBox(modelOf(1), "[]"), { ok: false, reason: "not_an_object" }); +}); + +test("clash or uri nodes are skipped, never converted", () => { + const m = modelOf(2, { uri: true }); + const res = renderSingBox(m, null); + assert.equal(res.ok, true); + if (!res.ok) return; + const shape = res.skipped.filter((s) => s.reason === "source_not_singbox_shape"); + assert.equal(shape.length, 2); + const empty = res.skipped.filter((s) => s.reason === "empty_group"); + assert.equal(empty.length, m.groups.filter((g) => g.members.length > 0).length); + const after = JSON.parse(res.text); + assert.ok(!after.outbounds.some((o: { tag: string }) => m.nodes.some((n) => n.tag === o.tag))); +}); + +test("group with only skipped nodes emits neither group nor listener", () => { + const single = buildCoreModel(["socks5://127.0.0.1:1080 selector=solo"], [ + { name: "x", source: { kind: "uri", value: "vless://u@1.1.1.1:443#x" } }, + ] as never); + const res = renderSingBox(single, null); + assert.equal(res.ok, true); + if (!res.ok) return; + const after = JSON.parse(res.text); + assert.deepEqual(after.outbounds, []); + assert.deepEqual(after.inbounds, []); +}); + +test("pre-existing owned-prefix tag is treated as owned (documented)", () => { + const existing = JSON.stringify({ + inbounds: [ + { type: "socks", tag: `${OWNED_TAG_PREFIX}in-old`, listen: "127.0.0.1", listen_port: 1080 }, + ], + outbounds: [{ type: "selector", tag: `${OWNED_TAG_PREFIX}old`, outbounds: ["direct"] }], + }); + const res = renderSingBox(modelOf(2), existing); + assert.equal(res.ok, true); + if (!res.ok) return; + assert.ok(!res.text.includes(`${OWNED_TAG_PREFIX}old"`)); +}); + +test("functional: realistic file with 2 listeners, 2 groups, 6 nodes", () => { + const nodes = Array.from({ length: 6 }, (_, i) => ({ + name: `n${i + 1}`, + source: { + kind: "object" as const, + value: { type: "vless", server: `10.1.0.${i + 1}`, server_port: 443 }, + }, + })); + const m = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=a", "socks5://127.0.0.1:1081 selector=b"], + nodes as never + ); + const res = renderSingBox(m, realisticExisting()); + assert.equal(res.ok, true); + if (!res.ok) return; + const after = JSON.parse(res.text); + const selectors = after.outbounds.filter( + (o: { type: string; tag: string }) => + o.type === "selector" && o.tag.startsWith(OWNED_TAG_PREFIX) + ); + assert.equal(selectors.length, 2); + assert.deepEqual(after.dns, JSON.parse(realisticExisting()).dns); + assert.deepEqual(after.log, JSON.parse(realisticExisting()).log); +}); + +test("groups with only unrenderable members are reported as empty_group", () => { + const nodes = Array.from({ length: 6 }, (_, i) => ({ + name: `u${i + 1}`, + source: { kind: "uri" as const, value: `vless://u@10.2.0.${i + 1}:443#u${i + 1}` }, + })) as never[]; + const m = buildCoreModel( + [ + "socks5://127.0.0.1:1080 selector=g1", + "socks5://127.0.0.1:1081 selector=g2", + "socks5://127.0.0.1:1082 selector=g3", + ], + nodes + ); + assert.ok(m.groups.every((g) => g.members.length > 0)); + const res = renderSingBox(m, null); + assert.equal(res.ok, true); + if (!res.ok) return; + const empty = res.skipped.filter((s) => s.reason === "empty_group"); + assert.equal(empty.length, m.groups.filter((g) => g.members.length > 0).length); + assert.deepEqual(empty.map((s) => s.node).sort(), m.groups.map((g) => g.tag).sort()); +}); + +test("mixed rendered and unrenderable groups report only the unrenderable ones", () => { + const rendered = { + name: "ok", + source: { + kind: "object" as const, + value: { type: "vless", server: "10.3.0.1", server_port: 443, uuid: "u-ok" }, + }, + } as never; + const m = buildCoreModel( + ["socks5://127.0.0.1:1080 selector=solo", "socks5://127.0.0.1:1081 selector=other"], + [rendered, { name: "x", source: { kind: "uri", value: "vless://u@10.3.0.2:443#x" } } as never] + ); + const res = renderSingBox(m, null); + assert.equal(res.ok, true); + if (!res.ok) return; + const empty = res.skipped.filter((s) => s.reason === "empty_group"); + assert.ok( + empty.every((s) => + m.groups.some((g) => g.tag === s.node && g.members.every((tag) => tag !== "ok")) + ) + ); +}); + +test("fully rendered groups report no empty_group", () => { + const res = renderSingBox(modelOf(4), null); + assert.equal(res.ok, true); + if (!res.ok) return; + assert.equal(res.skipped.filter((s) => s.reason === "empty_group").length, 0); +}); + +test("default core resolves through the renderer table", () => { + assert.equal(renderers.DEFAULT_CORE, "sing-box"); + assert.equal(renderers.RENDERERS["sing-box"], renderSingBox); + assert.equal(isSingBoxShape({ type: "vless", server: "x", server_port: 1 }), true); + assert.equal(isSingBoxShape({ type: "vless", server: "x" }), false); + void objNode; +}); diff --git a/tests/unit/proxySubscription.selectorWarning.test.ts b/tests/unit/proxySubscription.selectorWarning.test.ts index 4d499b8684dc..bc470b650aec 100644 --- a/tests/unit/proxySubscription.selectorWarning.test.ts +++ b/tests/unit/proxySubscription.selectorWarning.test.ts @@ -215,3 +215,33 @@ test.after(() => { core.resetDbInstance(); fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); }); + +test("successful switch persists the refusal kind that fired it", async () => { + reset(); + const fake = await startFakeCore({ failPut: false }); + const { id, feedClose } = await seedWarnSubscription(fake.base); + try { + trigger.__resetSelectorTriggerForTesting(); + // The kind propagates from the caller that set the member aside (fusion + // with the set-aside record); the trigger never re-reads it here. + const good = await trigger.maybeSwitchOnSetAside(KEY_NODE1, { kind: "transport" }); + assert.equal(good.switched, true, JSON.stringify(good)); + const row = core + .getDbInstance() + .prepare( + "SELECT selector_last_switch_kind, selector_last_switch_member FROM proxy_subscriptions WHERE id = ?" + ) + .get(id) as { + selector_last_switch_kind: string | null; + selector_last_switch_member: string | null; + }; + assert.equal(row.selector_last_switch_kind, "transport"); + assert.ok(row.selector_last_switch_member, "member recorded alongside the kind"); + const listed = await sub.getSubscriptionById(id); + assert.equal(listed?.selectorLastSwitchKind, "transport"); + await sub.deleteSubscription(id); + } finally { + await feedClose(); + await fake.close(); + } +}); diff --git a/tests/unit/qoder-cli.test.ts b/tests/unit/qoder-cli.test.ts index c94f0f641e27..34e5a342ff31 100644 --- a/tests/unit/qoder-cli.test.ts +++ b/tests/unit/qoder-cli.test.ts @@ -385,6 +385,35 @@ test("validateQoderCliPat returns valid when qodercli lists models for the PAT", }); }); +test("validateQoderCliPat accepts a catalog that omits the MODEL header", async () => { + // Regression: validation required the literal "MODEL" header in stdout, so any + // CLI build that prints the catalog differently (renamed header, JSON output, + // localized table) failed with exit code 0 and a valid PAT was rejected. + const prevBin = process.env.CLI_QODER_BIN; + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "qodercli-stub-")); + const stub = path.join(dir, "qodercli"); + fs.writeFileSync( + stub, + [ + "#!/bin/sh", + 'case "$*" in', + ' *--list-models*) printf "Auto\\nQwen3-Coder\\n"; exit 0;;', + "esac", + "exit 0", + ].join("\n"), + { mode: 0o755 } + ); + process.env.CLI_QODER_BIN = stub; + try { + const result = await qoderCli.validateQoderCliPat({ apiKey: "pt-headerless" }); + assert.deepEqual(result, { valid: true, error: null, unsupported: false }); + } finally { + if (prevBin === undefined) delete process.env.CLI_QODER_BIN; + else process.env.CLI_QODER_BIN = prevBin; + fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +}); + test("validateQoderCliPat rejects a PAT that qodercli reports as not logged in", async () => { await withStubQoderCli(async () => { const result = await qoderCli.validateQoderCliPat({ apiKey: "pt-bad-token" }); @@ -468,6 +497,34 @@ test("parseQoderCliFailure classifies qodercli auth output as 401", () => { } }); +test("parseQoderCliFailure classifies Qoder queued/busy envelopes as a retryable 503", () => { + // 10605 is Qoder's "upstream busy, try again" envelope. It arrives with a + // zero exit code, so before this classification it fell through to 502 — + // which parks the connection on the long failure cooldown and makes a brief + // upstream hiccup look like dead credentials. + for (const msg of [ + "10605", + "Request queued, please retry later", + "The upstream model is busy right now", + "qodercli error: server busy", + ]) { + const failure = qoderCli.parseQoderCliFailure(msg); + assert.equal(failure.status, 503, msg); + assert.equal(failure.code, "upstream_busy", msg); + assert.equal(failure.message, msg, msg); + } + + // A 503 response is what routing turns into the short serviceUnavailable + // cooldown, so keep the contract visible: createQoderErrorResponse must emit + // provider_error (not authentication_error) for it. + const response = qoderCli.createQoderErrorResponse({ + status: 503, + message: "queued", + code: "upstream_busy", + }); + assert.equal(response.status, 503); +}); + test("runQoderCli survives qodercli exiting before it reads a large stdin (async EPIPE)", async () => { const prevBin = process.env.CLI_QODER_BIN; const dir = fs.mkdtempSync(path.join(os.tmpdir(), "qodercli-stub-")); diff --git a/tests/unit/quota-saturation-token-headers.test.ts b/tests/unit/quota-saturation-token-headers.test.ts index a60628e6b2c8..550d7b1b3275 100644 --- a/tests/unit/quota-saturation-token-headers.test.ts +++ b/tests/unit/quota-saturation-token-headers.test.ts @@ -70,7 +70,11 @@ test("Anthropic token reset (RFC3339) is normalized to epoch ms", () => { const sig = getTokenHeaderSaturation("anthropic", "anth-conn-2"); assert.ok(sig, "expected signal"); const expected = Date.parse("2026-01-01T00:00:30Z"); - assert.equal(sig!.resetAt, expected, `resetAt should be RFC3339 epoch ${expected}, got ${sig!.resetAt}`); + assert.equal( + sig!.resetAt, + expected, + `resetAt should be RFC3339 epoch ${expected}, got ${sig!.resetAt}` + ); // fully exhausted → saturation 1. assert.equal(sig!.saturation, 1); }); @@ -275,6 +279,23 @@ test("getSaturation(openai) prefers real usage percent over token headers when u assert.ok(Math.abs(val - 0.5) < 1e-9, `expected usage ≈0.5 to win, got ${val}`); }); +test("getSaturation(openai) still surfaces token-header saturation for an unlimited-only usage payload (#15347)", async () => { + _clearSaturationCache(); + _clearRateLimitHeaders(); + // An unlimited plan converts to a snapshot with percentUsed 0 and `unlimited: true`. That is + // "no usage cap", not "0% used": it must not short-circuit the token-header fallback, which + // still reflects real burst-limit pressure. + __setGenericUsageFetcherForTests(async () => ({ quotas: { plan: { unlimited: true } } })); + storeRateLimitHeaders("oai-gen-unl", "openai", { + "x-ratelimit-limit-tokens": "1000", + "x-ratelimit-remaining-tokens": "100", + "x-ratelimit-reset-tokens": "30s", + }); + + const val = await getSaturation("oai-gen-unl", "openai", { unit: "tokens", window: "hourly" }); + assert.ok(Math.abs(val - 0.9) < 1e-9, `expected token-header ≈0.9, got ${val}`); +}); + test("getSaturation(openai) fails open to 0 when neither usage nor token headers exist", async () => { _clearSaturationCache(); _clearRateLimitHeaders(); diff --git a/tests/unit/quota-trackers-batch-init.test.ts b/tests/unit/quota-trackers-batch-init.test.ts new file mode 100644 index 000000000000..018d38fc210e --- /dev/null +++ b/tests/unit/quota-trackers-batch-init.test.ts @@ -0,0 +1,45 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import test from "node:test"; + +const root = process.cwd(); + +function read(rel: string): string { + return fs.readFileSync(path.join(root, rel), "utf8"); +} + +test("quota batch registration is not a module-load side effect", () => { + const batch = read("open-sse/services/quotaTrackersBatch.ts"); + const fn = batch.indexOf("export function registerQuotaTrackersBatch"); + assert.ok(fn >= 0, "registerQuotaTrackersBatch must stay exported"); + const tail = batch.slice(batch.lastIndexOf("}")); + assert.doesNotMatch( + tail, + /registerQuotaTrackersBatch\s*\(/, + "a top-level call races webpack's async registerQuotaFetcher binding" + ); + assert.doesNotMatch(batch, /^registerQuotaTrackersBatch\s*\(/m); +}); + +test("chat route calls the batch registrar after imports, before generic", () => { + const chat = read("src/sse/handlers/chat.ts"); + assert.match( + chat, + /import\s*\{\s*registerQuotaTrackersBatch\s*\}\s*from\s*"@omniroute\/open-sse\/services\/quotaTrackersBatch\.ts"/ + ); + assert.doesNotMatch(chat, /import\s*"@omniroute\/open-sse\/services\/quotaTrackersBatch\.ts"/); + const body = chat.slice(chat.lastIndexOf('from "../services/leaseContext"')); + const call = body.indexOf("registerQuotaTrackersBatch()"); + const generic = body.indexOf("registerGenericQuotaFetchers()"); + assert.ok(call > 0, "chat.ts module body must call registerQuotaTrackersBatch()"); + assert.ok(generic > call, "batch registrars must run before the generic registrar"); +}); + +test("node instrumentation calls the batch registrar after the dynamic import resolves", () => { + const source = read("src/instrumentation-node.ts"); + const imported = source.indexOf('await import(\n "@omniroute/open-sse/services/quotaTrackersBatch.ts"'); + const called = source.indexOf("registerQuotaTrackersBatch()"); + assert.ok(imported >= 0, "instrumentation must await the batch module"); + assert.ok(called > imported, "the call must follow the resolved import"); +}); diff --git a/tests/unit/qwen-codebuddy-version-fetch.test.ts b/tests/unit/qwen-codebuddy-version-fetch.test.ts new file mode 100644 index 000000000000..2bdc55a2bb5b --- /dev/null +++ b/tests/unit/qwen-codebuddy-version-fetch.test.ts @@ -0,0 +1,94 @@ +/** + * Registry lookups for the advertised Qwen Code and CodeBuddy CN identities. + * + * Both gates reject a pin that is behind the published CLI. The captured pin + * is the floor; a newer dotted triple from npm replaces it for 6 hours. A + * rejected fetch, a non-triple, or an older publish stays on the pin. The + * sync getters return the cache or the pin immediately and never wait on + * the network. + */ +import assert from "node:assert/strict"; +import test from "node:test"; + +const profiles = await import("../../open-sse/config/providerHeaderProfiles.ts"); + +const QWEN_NPM_LATEST = "https://registry.npmjs.org/@qwen-code/qwen-code/latest"; +const CODEBUDDY_NPM_LATEST = "https://registry.npmjs.org/@tencent-ai/codebuddy-code/latest"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +test.afterEach(() => { + profiles.resetQwenCliVersionCache(); + profiles.resetCodeBuddyCnVersionCache(); +}); + +test("a fetched Qwen version newer than the pin is returned", async () => { + const urls: string[] = []; + const fetchMock = async (url: string | URL | Request) => { + urls.push(String(url)); + return jsonResponse({ version: "0.25.0" }); + }; + + assert.equal( + await profiles.refreshQwenCliVersion(fetchMock as typeof fetch), + "0.25.0" + ); + assert.equal(profiles.getQwenCliVersion(), "0.25.0"); + assert.equal( + profiles.getQwenCliUserAgent(), + `QwenCode/0.25.0 (${process.platform}; ${process.arch})` + ); + assert.equal(urls.length, 1); + assert.equal(urls[0], QWEN_NPM_LATEST); + assert.equal(profiles.QWEN_CLI_VERSION, "0.19.3"); +}); + +test("a Qwen fetch that rejects stays on the pin", async () => { + const fetchMock = async () => { + throw new Error("registry unreachable"); + }; + + assert.equal( + await profiles.refreshQwenCliVersion(fetchMock as typeof fetch), + profiles.QWEN_CLI_VERSION + ); + assert.equal(profiles.getQwenCliVersion(), profiles.QWEN_CLI_VERSION); + assert.equal( + profiles.getQwenCliUserAgent(), + `QwenCode/${profiles.QWEN_CLI_VERSION} (${process.platform}; ${process.arch})` + ); +}); + +test("a fetched CodeBuddy version newer than the pin moves both numbers together", async () => { + const urls: string[] = []; + const fetchMock = async (url: string | URL | Request) => { + urls.push(String(url)); + return jsonResponse({ version: "2.161.4" }); + }; + + assert.equal( + await profiles.refreshCodeBuddyCnUserAgent(fetchMock as typeof fetch), + "CLI/2.161.4 CodeBuddy/2.161.4" + ); + assert.equal(profiles.getCodeBuddyCnUserAgent(), "CLI/2.161.4 CodeBuddy/2.161.4"); + assert.equal(urls.length, 1); + assert.equal(urls[0], CODEBUDDY_NPM_LATEST); + assert.equal(profiles.CODEBUDDY_CN_USER_AGENT, "CLI/2.108.1 CodeBuddy/2.108.1"); +}); + +test("a CodeBuddy fetch that rejects stays on the pin", async () => { + const fetchMock = async () => { + throw new Error("registry unreachable"); + }; + + assert.equal( + await profiles.refreshCodeBuddyCnUserAgent(fetchMock as typeof fetch), + profiles.CODEBUDDY_CN_USER_AGENT + ); + assert.equal(profiles.getCodeBuddyCnUserAgent(), profiles.CODEBUDDY_CN_USER_AGENT); +}); diff --git a/tests/unit/rate-limit-idle-evict-log.test.ts b/tests/unit/rate-limit-idle-evict-log.test.ts new file mode 100644 index 000000000000..ef76743c9590 --- /dev/null +++ b/tests/unit/rate-limit-idle-evict-log.test.ts @@ -0,0 +1,170 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import Bottleneck from "bottleneck"; +import { LimiterWedgeWatchdog } from "../../open-sse/services/rateLimitManager/wedgeWatchdog.ts"; + +const INACTIVE_MS = 10 * 60 * 1000; + +function buildWatchdog( + _now: number, + keys: string[], + lastUsedOf: (key: string) => number, + limiterOptions: Bottleneck.ConstructorOptions = {} +) { + const limiters = new Map(); + const limiterLastUsed = new Map(); + for (const key of keys) { + const limiter = new Bottleneck(limiterOptions); + limiters.set(key, limiter); + limiterLastUsed.set(key, lastUsedOf(key)); + } + const logCalls: unknown[][] = []; + const debugCalls: unknown[][] = []; + const tracked: Promise[] = []; + const watchdog = new LimiterWedgeWatchdog({ + limiters, + limiterLastUsed, + limiterEffectiveSettings: new WeakMap(), + preservedReplacementSettings: new Map(), + trackBackground: (promise) => { + tracked.push(promise); + promise.catch(() => {}); + }, + log: (...args) => { + logCalls.push(args); + }, + warn: (...args) => { + void args; + }, + debug: (...args) => { + debugCalls.push(args); + }, + }); + return { watchdog, limiters, logCalls, debugCalls, tracked }; +} + +async function cleanup(limiters: Map) { + for (const limiter of limiters.values()) { + await limiter.disconnect().catch(() => {}); + } +} + +test("idle eviction aggregates the info log to one line with per-key debug detail", async (t) => { + const now = Date.now(); + const keys = ["prov-a:conn-1", "prov-b:conn-2", "prov-c:conn-3"]; + const { watchdog, limiters, logCalls, debugCalls } = buildWatchdog( + now, + keys, + () => now - INACTIVE_MS - 60_000 + ); + t.after(() => cleanup(limiters)); + await watchdog.run(now); + assert.equal(logCalls.length, 1); + assert.match(String(logCalls[0][0]), /Evicted idle limiters: 3/); + assert.equal(debugCalls.length, 3); + for (const key of keys) { + assert.ok( + debugCalls.some((args) => String(args[0]).includes(key)), + `expected debug detail for ${key}` + ); + } +}); + +test("a single idle eviction still emits one aggregated info line", async (t) => { + const now = Date.now(); + const { watchdog, limiters, logCalls, debugCalls } = buildWatchdog( + now, + ["prov-a:conn-1"], + () => now - INACTIVE_MS - 60_000 + ); + t.after(() => cleanup(limiters)); + await watchdog.run(now); + assert.equal(logCalls.length, 1); + assert.match(String(logCalls[0][0]), /Evicted idle limiters: 1/); + assert.equal(debugCalls.length, 1); +}); + +test("a pass without eviction emits no eviction info line", async (t) => { + const now = Date.now(); + const { watchdog, limiters, logCalls, debugCalls } = buildWatchdog( + now, + ["prov-a:conn-1"], + () => now + ); + t.after(() => cleanup(limiters)); + await watchdog.run(now); + assert.equal(logCalls.length, 0); + assert.equal(debugCalls.length, 0); +}); + +test("a limiter with queued work is kept and stays silent", async (t) => { + const now = Date.now(); + const reservoir = { reservoir: 0, reservoirRefreshAmount: 0, reservoirRefreshInterval: 60_000 }; + const { watchdog, limiters, logCalls, debugCalls } = buildWatchdog( + now, + ["prov-a:conn-1"], + () => now - INACTIVE_MS - 60_000, + reservoir + ); + const limiter = limiters.get("prov-a:conn-1")!; + const gate = limiter.schedule(() => new Promise(() => {})); + gate.catch(() => {}); + await new Promise((resolve) => setTimeout(resolve, 20)); + assert.equal(limiter.counts().QUEUED, 1); + t.after(async () => { + await limiter.stop({ dropWaitingJobs: true }).catch(() => {}); + await cleanup(limiters); + }); + await watchdog.run(now); + assert.equal(logCalls.length, 0); + assert.equal(debugCalls.length, 0); + assert.ok(limiters.has("prov-a:conn-1")); +}); + +test("without an injected debug hook the per-key detail falls back to console.debug", async (t) => { + const now = Date.now(); + const limiters = new Map(); + const limiterLastUsed = new Map(); + for (const key of ["prov-a:conn-1", "prov-b:conn-2", "prov-c:conn-3"]) { + limiters.set(key, new Bottleneck()); + limiterLastUsed.set(key, now - INACTIVE_MS - 60_000); + } + const logCalls: unknown[][] = []; + const consoleDebugCalls: unknown[][] = []; + const originalDebug = console.debug; + console.debug = (...args: unknown[]) => { + consoleDebugCalls.push(args); + }; + t.after(() => { + console.debug = originalDebug; + }); + t.after(() => cleanup(limiters)); + const watchdog = new LimiterWedgeWatchdog({ + limiters, + limiterLastUsed, + limiterEffectiveSettings: new WeakMap(), + preservedReplacementSettings: new Map(), + trackBackground: (promise) => { + promise.catch(() => {}); + }, + log: (...args) => { + logCalls.push(args); + }, + warn: () => {}, + }); + await watchdog.run(now); + assert.equal(logCalls.length, 1); + assert.equal(consoleDebugCalls.length, 3); +}); + +test("the headline oldest-idle figure ignores entries without a recorded last use", async (t) => { + const now = Date.now(); + const keys = ["prov-a:conn-1", "prov-b:conn-2", "prov-c:conn-3"]; + const { watchdog, limiters, logCalls } = buildWatchdog(now, keys, (key) => + key === "prov-c:conn-3" ? 0 : now - 642_000 + ); + t.after(() => cleanup(limiters)); + await watchdog.run(now); + assert.equal(logCalls.length, 1); + assert.match(String(logCalls[0][0]), /oldest idle 642s/); +}); diff --git a/tests/unit/repro-8522.test.ts b/tests/unit/repro-8522.test.ts index c122bf38075e..760368c416af 100644 --- a/tests/unit/repro-8522.test.ts +++ b/tests/unit/repro-8522.test.ts @@ -14,8 +14,15 @@ */ import { test } from "node:test"; import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; import { evaluateFileSizes } from "../../scripts/check/check-file-size.mjs"; +const SCRIPT = fileURLToPath(new URL("../../scripts/check/check-file-size.mjs", import.meta.url)); + test("8522: innocent PR (base already over frozen cap) must NOT be a violation", () => { // Scenario: frozen cap for src/foo.ts is 100. Some earlier merged PR grew it // to 110. The base of THIS PR is therefore 110. This PR is innocent — it does @@ -44,3 +51,80 @@ test("8522: PR that DOES grow a frozen file above frozen cap is a violation", () const { violations } = evaluateFileSizes(currentLocByFile, frozen, cap, baseLocByFile); assert.equal(violations.length, 1, "own-growth PR must be a violation"); }); + +// --- Test-file gate in PR mode: main() must look up base LOC for the test collector's +// files too, or every testFrozen entry is judged without its base size. Runs the CLI +// against a throwaway repo whose commit is the PR base and whose working tree is the head. + +function lines(n: number) { + return Array.from({ length: n }, (_, i) => `// ${i}`).join("\n"); +} + +function makeDriftedTestRepo() { + const root = mkdtempSync(join(tmpdir(), "file-size-8522-")); + mkdirSync(join(root, "tests/unit"), { recursive: true }); + writeFileSync( + join(root, "baseline.json"), + JSON.stringify({ + cap: 10, + frozen: {}, + testCap: 10, + testFrozen: { "tests/unit/frozen.test.ts": 12 }, + }) + ); + // Both test files already drifted on the base: frozen.test.ts past its frozen 12, + // unfrozen.test.ts past testCap 10. + writeFileSync(join(root, "tests/unit/frozen.test.ts"), lines(15)); + writeFileSync(join(root, "tests/unit/unfrozen.test.ts"), lines(20)); + execFileSync("git", ["init", "--quiet"], { cwd: root }); + execFileSync("git", ["add", "."], { cwd: root }); + execFileSync( + "git", + [ + "-c", + "user.name=File Size Test", + "-c", + "user.email=file-size@example.invalid", + "commit", + "--quiet", + "-m", + "base", + ], + { cwd: root } + ); + return root; +} + +function runGate(root: string) { + return spawnSync( + process.execPath, + [SCRIPT, "--baseline", join(root, "baseline.json"), "--base-ref", "HEAD"], + { cwd: root, encoding: "utf8" } + ); +} + +test("8522: innocent PR with test files already over their ceiling on the base passes", () => { + const root = makeDriftedTestRepo(); + try { + const r = runGate(root); + assert.equal(r.status, 0, `${r.stdout}\n${r.stderr}`); + assert.match(r.stdout, /\[test-file-size\] OK/); + } finally { + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +}); + +test("8522: PR that grows drifted test files past their base size is a violation", () => { + const root = makeDriftedTestRepo(); + try { + writeFileSync(join(root, "tests/unit/frozen.test.ts"), lines(16)); + writeFileSync(join(root, "tests/unit/unfrozen.test.ts"), lines(21)); + const r = runGate(root); + assert.equal(r.status, 1, `${r.stdout}\n${r.stderr}`); + assert.match(r.stderr, /2 test file violation\(s\)/); + assert.match(r.stderr, /frozen\.test\.ts: 16 > congelado 12/); + assert.match(r.stderr, /unfrozen\.test\.ts: 21 > cap 10/); + } finally { + rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } +}); diff --git a/tests/unit/resilience-settings-normalize-split.test.ts b/tests/unit/resilience-settings-normalize-split.test.ts index 2800f359dddd..9bb18d846eb2 100644 --- a/tests/unit/resilience-settings-normalize-split.test.ts +++ b/tests/unit/resilience-settings-normalize-split.test.ts @@ -105,6 +105,10 @@ describe("resilience/settings normalize split-guard", () => { "quotaShareConcurrencyLimit", "requestQueue", "streamRecovery", + // Whether a stream content stall cools down the account (default off). + "streamStallCooldown", + // Token-refresh breaker scope + thresholds (per-connection isolation opt-in). + "tokenRefreshBreaker", "waitForCooldown", ]); }); diff --git a/tests/unit/resilience-settings-stream-stall-cooldown.test.ts b/tests/unit/resilience-settings-stream-stall-cooldown.test.ts new file mode 100644 index 000000000000..03ab956d6ebc --- /dev/null +++ b/tests/unit/resilience-settings-stream-stall-cooldown.test.ts @@ -0,0 +1,50 @@ +/** + * ResilienceSettings.streamStallCooldown — whether a stream content stall cools down the + * account that served it. Locks the default (off), the resolve/merge round-trip and the + * wire schema accepted by PATCH /api/resilience. + */ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + DEFAULT_RESILIENCE_SETTINGS, + mergeResilienceSettings, + resolveResilienceSettings, +} from "../../src/lib/resilience/settings.ts"; +import { updateResilienceSchema } from "../../src/shared/validation/schemas/settings.ts"; + +test("stream stalls do not cool down accounts by default", () => { + assert.deepEqual(DEFAULT_RESILIENCE_SETTINGS.streamStallCooldown, { enabled: false }); + assert.deepEqual(resolveResilienceSettings({}).streamStallCooldown, { enabled: false }); +}); + +test("resolveResilienceSettings reads a stored opt-in", () => { + const resolved = resolveResilienceSettings({ + resilienceSettings: { streamStallCooldown: { enabled: true } }, + }); + assert.equal(resolved.streamStallCooldown.enabled, true); +}); + +test("mergeResilienceSettings applies and keeps the setting", () => { + const enabled = mergeResilienceSettings(structuredClone(DEFAULT_RESILIENCE_SETTINGS), { + streamStallCooldown: { enabled: true }, + }); + assert.equal(enabled.streamStallCooldown.enabled, true); + + const untouched = mergeResilienceSettings(enabled, { comboCooldownWait: { maxWaitMs: 2000 } }); + assert.equal(untouched.streamStallCooldown.enabled, true); +}); + +test("PATCH /api/resilience schema accepts streamStallCooldown on its own", () => { + assert.equal( + updateResilienceSchema.safeParse({ streamStallCooldown: { enabled: true } }).success, + true + ); + assert.equal( + updateResilienceSchema.safeParse({ streamStallCooldown: { enabled: "yes" } }).success, + false + ); + assert.equal( + updateResilienceSchema.safeParse({ streamStallCooldown: { enabled: true, extra: 1 } }).success, + false + ); +}); diff --git a/tests/unit/responses-json-to-sse-13033.test.ts b/tests/unit/responses-json-to-sse-13033.test.ts index e46e7d8029f3..11a08707ac63 100644 --- a/tests/unit/responses-json-to-sse-13033.test.ts +++ b/tests/unit/responses-json-to-sse-13033.test.ts @@ -69,12 +69,20 @@ test("chatCore stamps clientRequestedResponsesStream before forcing stream:false ); const stamp = source.indexOf("clientRequestedResponsesStream = true"); const force = source.indexOf("(body as Record).stream = false"); - const wrap = source.indexOf("maybeWrapForcedNonStreamingResponsesJson({"); + const leafSource = await readFile( + join(import.meta.dirname, "../../open-sse/handlers/chatCore/nonStreamingResponse.ts"), + "utf-8" + ); + const wrap = leafSource.indexOf("maybeWrapForcedNonStreamingResponsesJson({"); + const leafCall = source.indexOf("await runNonStreamingResponse("); assert.ok(stamp !== -1, "must stamp the client-requested stream flag"); assert.ok(force !== -1, "must still force stream:false for the web_search fallback"); assert.ok(wrap !== -1, "must wrap the non-streaming JSON return"); assert.ok(stamp < force, "stamp must happen before stream:false"); - assert.ok(wrap > force, "wrap must happen on the non-streaming return after the force"); + assert.ok( + leafCall > force, + "the non-streaming leaf runs after the force, so its wrap lands after it" + ); }); // When the client speaks the Responses API, the forced non-streaming leg is diff --git a/tests/unit/responses-synthetic-frame-ids-15202.test.ts b/tests/unit/responses-synthetic-frame-ids-15202.test.ts new file mode 100644 index 000000000000..d510299a22ea --- /dev/null +++ b/tests/unit/responses-synthetic-frame-ids-15202.test.ts @@ -0,0 +1,57 @@ +/** + * #15202 — regression of #14330. + * + * The 3.8.51 fix that made the synthesized `/v1/responses` frames valid Responses + * events gave them a `response` object but left `response.id: null`. The Responses + * event schema types `response.id` as `string | undefined`, so strict decoders + * (openai-python, OpenCode's Responses client) abort the stream on the very first + * frame — which is the keepalive `response.in_progress`, emitted before the real + * `response.created`. Every synthesized frame must carry a STRING `response.id`. + * + * The two Codex executor frames (SSE + WebSocket) are covered by the isolated + * fixture in tests/fixtures/codex-response-failed-boundary.fixture.ts, which + * asserts the same string-id contract. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { OPENAI_RESPONSES_IN_PROGRESS_FRAME } from "../../open-sse/utils/sseHeartbeat.ts"; +import { synthResponsesFailure } from "../../open-sse/utils/diagnostics.ts"; + +const { buildStreamErrorChunks } = await import("../../open-sse/utils/streamHandler.ts"); +const { FORMATS } = await import("../../open-sse/translator/formats.ts"); + +function parseSseData(sseText: string): Record { + const dataLine = sseText.split("\n").find((line) => line.startsWith("data: ")); + assert.ok(dataLine, `expected a data: line in frame, got: ${JSON.stringify(sseText)}`); + return JSON.parse(dataLine!.slice("data: ".length)) as Record; +} + +function assertStringResponseId(payload: Record, site: string): void { + const response = payload.response as Record | undefined; + assert.equal(typeof response, "object", `${site}: missing the required response object`); + assert.equal( + typeof response?.id, + "string", + `${site}: response.id must be a string — null abort the strict Responses decoder on the first frame` + ); +} + +test("#15202: startup keepalive response.in_progress carries a string response.id", () => { + const payload = parseSseData(new TextDecoder().decode(OPENAI_RESPONSES_IN_PROGRESS_FRAME)); + assert.equal(payload.type, "response.in_progress"); + assertStringResponseId(payload, "sseHeartbeat keepalive"); +}); + +test("#15202: synthResponsesFailure() carries a string response.id", () => { + const payload = parseSseData(synthResponsesFailure("timeout")); + assert.equal(payload.type, "response.failed"); + assertStringResponseId(payload, "diagnostics.synthResponsesFailure"); +}); + +test("#15202: buildStreamErrorChunks() carries a string response.id", () => { + const chunks = buildStreamErrorChunks("upstream exploded", 502, FORMATS.OPENAI_RESPONSES); + const payload = parseSseData(new TextDecoder().decode(chunks[0])); + assert.equal(payload.type, "response.failed"); + assertStringResponseId(payload, "streamHandler.buildStreamErrorChunks"); +}); diff --git a/tests/unit/responses-upstream-eof-15309.test.ts b/tests/unit/responses-upstream-eof-15309.test.ts new file mode 100644 index 000000000000..2284079d587a --- /dev/null +++ b/tests/unit/responses-upstream-eof-15309.test.ts @@ -0,0 +1,165 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { openaiToOpenAIResponsesResponse as translate } from "../../open-sse/translator/response/openai-responses.ts"; +import { initState } from "../../open-sse/translator/index.ts"; +import { FORMATS } from "../../open-sse/translator/formats.ts"; +import { + createDisconnectAwareStream, + createStreamController, +} from "../../open-sse/utils/streamHandler.ts"; +import { createSSEStream } from "../../open-sse/utils/stream.ts"; + +function chunk(delta: Record, finishReason: string | null = null) { + return { + id: "synthetic-eof-probe", + created: 1, + model: "test-model", + choices: [{ index: 0, delta, finish_reason: finishReason }], + }; +} + +for (const [channel, delta] of Object.entries({ + reasoning: { reasoning_content: "Unfinished reasoning" }, + text: { content: "Partial answer" }, + tool: { + tool_calls: [ + { + index: 0, + id: "call_probe", + type: "function", + function: { name: "read", arguments: '{"path":' }, + }, + ], + }, +})) { + test(`#15309: ${channel} followed by EOF fails instead of completing`, () => { + const state = initState(FORMATS.OPENAI_RESPONSES); + const streamed = translate(chunk(delta), state); + const flushed = translate(null, state); + assert.ok(streamed.length > 0); + assert.equal(state.finishReason ?? null, null); + assert.ok(!flushed.some((event) => event.event === "response.completed")); + const failed = flushed.find((event) => event.event === "response.failed"); + assert.ok(failed); + assert.equal(failed.data.response.status, "failed"); + assert.equal(state.upstreamError.code, "stream_early_eof"); + assert.equal(failed.data.response.error.code, "502"); + assert.match(failed.data.response.error.message, /terminal marker/); + assert.ok(failed.data.response.output.length > 0); + assert.deepEqual(translate(null, state), [], "flush must be idempotent"); + }); +} + +for (const finishReason of ["stop", "tool_calls", "length", "content_filter"]) { + test(`#15309: explicit ${finishReason} survives EOF without trailing usage`, () => { + const state = initState(FORMATS.OPENAI_RESPONSES); + translate(chunk({ content: "Answer" }), state); + translate(chunk({}, finishReason), state); + const events = translate(null, state); + const incomplete = finishReason === "length" || finishReason === "content_filter"; + const terminal = events.find( + (event) => event.event === (incomplete ? "response.incomplete" : "response.completed") + ); + assert.ok(terminal); + assert.equal(terminal.data.response.status, incomplete ? "incomplete" : "completed"); + assert.equal(state.upstreamError, undefined); + }); +} + +test("#15309: usage without a finish reason is not a successful terminal", () => { + const state = initState(FORMATS.OPENAI_RESPONSES); + translate(chunk({ reasoning_content: "Partial" }), state); + translate({ choices: [], usage: { prompt_tokens: 10, completion_tokens: 2 } }, state); + const failed = translate(null, state).find((event) => event.event === "response.failed"); + assert.ok(failed); + assert.equal(failed.data.response.usage.output_tokens, 2); +}); + +test("#15309: an explicit upstream error keeps its cause and uses a failure terminal", () => { + const state = initState(FORMATS.OPENAI_RESPONSES); + translate(chunk({ content: "Partial" }), state); + translate({ choices: [], error: { code: 429, message: "Rate limited" } }, state); + const failed = translate(null, state).find((event) => event.event === "response.failed"); + assert.ok(failed); + assert.equal(state.upstreamError.status, 429); + assert.equal(state.upstreamError.code, "rate_limit_exceeded"); + assert.equal(failed.data.response.error.code, "429"); +}); + +test("#15309: translated EOF invokes failure callbacks and records failed completion", async () => { + const failures: Record[] = []; + const completions: Record[] = []; + const source = new ReadableStream({ + start(controller) { + controller.enqueue( + new TextEncoder().encode( + `data: ${JSON.stringify(chunk({ reasoning_content: "Partial" }))}\n\n` + ) + ); + controller.close(); + }, + }); + const response = new Response( + source.pipeThrough( + createSSEStream({ + mode: "translate", + targetFormat: FORMATS.OPENAI, + sourceFormat: FORMATS.OPENAI_RESPONSES, + provider: "test-provider", + model: "test-model", + body: { messages: [{ role: "user", content: "Test" }] }, + onFailure: (failure: Record) => { + failures.push(failure); + }, + onComplete: (completion: Record) => { + completions.push(completion); + }, + }) + ) + ); + const wire = await response.text(); + assert.match(wire, /response\.failed/); + assert.match(wire, /terminal marker/); + assert.doesNotMatch(wire, /response\.completed/); + assert.equal(failures.length, 1); + assert.equal(failures[0].status, 502); + assert.equal(failures[0].code, "stream_early_eof"); + assert.equal(completions.length, 1); + assert.equal(completions[0].status, 502); + assert.equal(completions[0].errorCode, "stream_early_eof"); + assert.equal(completions[0].interrupted, true); +}); + +test("#15309: Responses client receives partial reasoning and an in-band failure", async () => { + const transform = createSSEStream({ + mode: "translate", + targetFormat: FORMATS.OPENAI, + sourceFormat: FORMATS.OPENAI_RESPONSES, + provider: "test-provider", + model: "test-model", + body: { messages: [{ role: "user", content: "Test" }] }, + }); + const source = new ReadableStream({ + start(controller) { + controller.enqueue( + new TextEncoder().encode( + `data: ${JSON.stringify(chunk({ reasoning_content: "Partial reasoning" }))}\n\n` + ) + ); + controller.close(); + }, + }); + const stream = createDisconnectAwareStream( + { + readable: source.pipeThrough(transform), + writable: { getWriter: () => ({ abort: () => Promise.resolve() }) }, + }, + createStreamController({ clientResponseFormat: FORMATS.OPENAI_RESPONSES }) + ); + const wire = await new Response(stream).text(); + assert.match(wire, /Partial reasoning/); + assert.match(wire, /response\.failed/); + assert.match(wire, /terminal marker/); + assert.doesNotMatch(wire, /response\.completed/); +}); diff --git a/tests/unit/run-next-listen-retry.test.ts b/tests/unit/run-next-listen-retry.test.ts new file mode 100644 index 000000000000..e5eb9a944d0e --- /dev/null +++ b/tests/unit/run-next-listen-retry.test.ts @@ -0,0 +1,73 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import net, { AddressInfo } from "node:net"; + +import { listenWithRetry } from "../../scripts/dev/listen-with-retry.mjs"; + +// Reproduces the integration port race (base-red #15306, job 111855214488): +// harnesses pick the server port with bind(0)-release at module load, but +// run-next.mjs only binds it after ~15-18s of next prepare(). A transient +// occupant in that window used to kill the boot with EADDRINUSE. The unit test +// pins the contract: retry while the port is busy, reject with the original +// error once attempts are exhausted, resolve with the bound address otherwise. + +function listen(server: net.Server | http.Server, port: number, host = "127.0.0.1") { + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, host, () => { + const addr = server.address(); + if (!addr || typeof addr === "string") reject(new Error("no TCP address")); + else resolve(addr); + }); + }); +} + +function close(server: net.Server | http.Server) { + return new Promise((resolve) => server.close(() => resolve())); +} + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +test("listenWithRetry binds once the port frees up mid-retry", async () => { + const squatter = net.createServer(); + const { port } = await listen(squatter, 0); + const server = http.createServer(); + const pending = listenWithRetry(server, { port, host: "127.0.0.1", attempts: 5, delayMs: 50 }); + await sleep(120); // first attempt fails against the squatter + await close(squatter); // occupant releases the port mid-retry + const addr = await pending; + assert.equal(addr.port, port); + await close(server); +}); + +test("listenWithRetry rejects with EADDRINUSE after exhausting its attempts", async () => { + const squatter = net.createServer(); + const { port } = await listen(squatter, 0); + try { + const server = http.createServer(); + await assert.rejects( + listenWithRetry(server, { port, host: "127.0.0.1", attempts: 2, delayMs: 20 }), + { code: "EADDRINUSE" } + ); + await close(server); + } finally { + await close(squatter); + } +}); + +test("listenWithRetry resolves with the bound address on a free port", async () => { + const server = http.createServer(); + try { + const addr = await listenWithRetry(server, { + port: 0, + host: "127.0.0.1", + attempts: 2, + delayMs: 10, + }); + assert.equal(typeof addr.port, "number"); + assert.ok(addr.port > 0); + } finally { + await close(server); + } +}); diff --git a/tests/unit/run-next-upgrade-relay.test.ts b/tests/unit/run-next-upgrade-relay.test.ts new file mode 100644 index 000000000000..829dcb2e7fbe --- /dev/null +++ b/tests/unit/run-next-upgrade-relay.test.ts @@ -0,0 +1,197 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import http from "node:http"; +import path from "node:path"; +import type { AddressInfo } from "node:net"; +import type { Duplex } from "node:stream"; +import { fileURLToPath } from "node:url"; +import next from "next"; +import { WebSocket } from "ws"; + +// Under scripts/dev/run-next.mjs (`npm run dev` / `npm start`) every WebSocket on /v1/responses +// and /v1/ws was cut (socket hang up / close 1006) once the server had served any HTTP request. +// next() lazily attaches its router upgrade listener to `options.httpServer || +// req.socket.server` on the first request it serves (NextCustomServer.setupWebSocketHandler). +// That listener runs alongside run-next's dispatcher and `socket.end()`s every upgrade whose +// path resolves to an app route (router-server.js: `if (matchedOutput) return socket.end()`); +// /v1/responses and /v1/ws are rewritten to /api/v1/... app routes, so the sockets the +// Responses WebSocket proxy and the /v1/ws bridge had already claimed were ended under them. + +const { createNextUpgradeRelay } = await import("../../scripts/dev/next-upgrade-relay.mjs"); +const { createResponsesWsProxy } = await import("../../scripts/dev/responses-ws-proxy.mjs"); +const { createOmnirouteWsBridge } = await import("../../scripts/dev/v1-ws-bridge.mjs"); + +const here = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(here, "../.."); + +type NextApp = ReturnType & { + init?: unknown; + getRequestHandler(): (req: http.IncomingMessage, res: http.ServerResponse) => Promise; +}; + +/** + * A real NextCustomServer (its own setupWebSocketHandler decides where the listener goes) with + * prepare() replaced by a stand-in init, since unit tests have no `next build` output. The + * stand-in upgrade handler does what router-server.js does for a matched app route (/v1/* is + * rewritten to the /api/v1 routes): resolve the route asynchronously, then `socket.end()`. + */ +function createNextStandIn(httpServer?: unknown) { + const app = next({ dev: false, dir: root, ...(httpServer ? { httpServer } : {}) } as never); + const upgrades: string[] = []; + (app as NextApp).init = { + requestHandler: async (_req: http.IncomingMessage, res: http.ServerResponse) => { + res.end("ok"); + }, + upgradeHandler: async (req: http.IncomingMessage, socket: Duplex) => { + upgrades.push(req.url ?? ""); + await new Promise((r) => setTimeout(r, 5)); + // router-server.js ends matched app routes; the stand-in ends everything else too (it has + // no HMR server to hand /_next/hmr to), which keeps the half-open socket from lingering. + socket.end(); + }, + }; + return { app: app as NextApp, upgrades }; +} + +const okFetch = (async () => + new Response(JSON.stringify({ path: "/v1/ws" }), { + status: 200, + headers: { "content-type": "application/json" }, + })) as typeof fetch; + +async function startServer(withRelay: boolean) { + const baseUrl = "http://127.0.0.1:1"; + const responsesWsProxy = createResponsesWsProxy({ + baseUrl, + bridgeSecret: "test-secret", + fetchImpl: okFetch, + wsFactory: () => { + throw new Error("no upstream in this test"); + }, + }); + const wsBridge = createOmnirouteWsBridge({ baseUrl, fetchImpl: okFetch }); + const relay = withRelay ? createNextUpgradeRelay() : null; + const { app, upgrades } = createNextStandIn(relay?.target); + const requestHandler = app.getRequestHandler(); + const server = http.createServer((req, res) => void requestHandler(req, res)); + // Same dispatch order as scripts/dev/run-next.mjs. + server.on("upgrade", async (req, socket, head) => { + if (await responsesWsProxy.handleUpgrade(req, socket, head)) return; + if (await wsBridge.handleUpgrade(req, socket, head)) return; + if (relay?.forward(req, socket, head)) return; + socket.destroy(); + }); + await new Promise((r) => server.listen(0, "127.0.0.1", () => r())); + const port = (server.address() as AddressInfo).port; + // Any HTTP request makes Next attach its upgrade listener (dashboard load, health check, …). + const res = await fetch(`http://127.0.0.1:${port}/api/monitoring/health`); + assert.equal(await res.text(), "ok"); + const stop = async () => { + server.closeAllConnections(); + await new Promise((r) => server.close(r)); + }; + return { server, port, upgrades, stop }; +} + +type Held = { opened: boolean; code: number; closedBy: "client" | "server" }; + +/** Opens a WebSocket, holds it `holdMs`, then closes with 1000; reports who ended it. */ +function holdSession(port: number, wsPath: string, holdMs = 300): Promise { + return new Promise((resolve) => { + const ws = new WebSocket(`ws://127.0.0.1:${port}${wsPath}`); + let opened = false; + let clientClosed = false; + ws.on("open", () => { + opened = true; + setTimeout(() => { + if (ws.readyState !== WebSocket.OPEN) return; + clientClosed = true; + ws.close(1000); + }, holdMs); + }); + ws.on("close", (code) => + resolve({ opened, code, closedBy: clientClosed ? "client" : "server" }) + ); + ws.on("error", () => {}); // "socket hang up" when ended before the 101; close follows + }); +} + +test("control: Next's auto-attached upgrade listener ends claimed /v1/responses and /v1/ws sockets", async () => { + const { server, port, upgrades, stop } = await startServer(false); + try { + assert.equal(server.listenerCount("upgrade"), 2, "next() attached a second upgrade listener"); + for (const wsPath of ["/v1/responses", "/v1/ws"]) { + const r = await holdSession(port, wsPath); + assert.equal(r.closedBy, "server", `${wsPath} was cut before the client closed it`); + assert.equal(r.code, 1006, `${wsPath}: abnormal closure, no close frame`); + } + assert.deepEqual(upgrades, ["/v1/responses", "/v1/ws"], "Next saw the claimed upgrades"); + } finally { + await stop(); + } +}); + +test("with the relay, claimed sockets live until the client closes them", async () => { + const { server, port, upgrades, stop } = await startServer(true); + try { + assert.equal(server.listenerCount("upgrade"), 1, "the dispatcher is the only upgrade listener"); + const responses = await holdSession(port, "/v1/responses"); + assert.deepEqual(responses, { opened: true, code: 1000, closedBy: "client" }); + const bridge = await holdSession(port, "/v1/ws"); + assert.equal(bridge.opened, true); + assert.equal(bridge.closedBy, "client", "the /v1/ws session was not cut by Next"); + assert.deepEqual(upgrades, [], "Next never saw the claimed upgrades"); + } finally { + await stop(); + } +}); + +test("the relay still forwards upgrades nobody claimed to Next (dev HMR)", async () => { + const { port, upgrades, stop } = await startServer(true); + try { + await new Promise((resolve) => { + const ws = new WebSocket(`ws://127.0.0.1:${port}/_next/hmr`); + ws.on("error", () => {}); + ws.on("close", () => resolve()); + }); + assert.deepEqual(upgrades, ["/_next/hmr"]); + } finally { + await stop(); + } +}); + +test("relay.forward reports false before Next attached its listener", () => { + const relay = createNextUpgradeRelay(); + assert.equal(relay.forward({}, {}, Buffer.alloc(0)), false); +}); + +test("run-next.mjs hands next() the relay and forwards only unclaimed upgrades through it", () => { + const src = fs.readFileSync(path.join(root, "scripts/dev/run-next.mjs"), "utf8"); + const createApp = src.slice(src.indexOf("function createNextApp()")); + assert.match( + createApp.slice(0, createApp.indexOf("\n}")), + /httpServer: nextUpgradeRelay\.target/ + ); + const bridge = src.indexOf("await wsBridge.handleUpgrade(req, socket, head)"); + const forward = src.indexOf("if (nextUpgradeRelay.forward(req, socket, head)) return;"); + assert.ok(bridge > 0 && forward > bridge, "Next only gets upgrades after the dispatchers"); +}); + +const MOVED = + "Next's upgrade handling changed — re-check that next() still attaches its upgrade listener to options.httpServer || req.socket.server and that router-server still ends app-route upgrades"; + +test("Next still attaches to httpServer || req.socket.server and ends app-route upgrades", () => { + const nextSrc = fs.readFileSync(path.join(root, "node_modules/next/dist/server/next.js"), "utf8"); + assert.match( + nextSrc, + /customServer = customServer \|\| \(_req == null \? void 0 : \(_req_socket = _req\.socket\) == null \? void 0 : _req_socket\.server\);/, + MOVED + ); + assert.match(nextSrc, /this\.setupWebSocketHandler\(this\.options\.httpServer, req\);/, MOVED); + const routerSrc = fs.readFileSync( + path.join(root, "node_modules/next/dist/server/lib/router-server.js"), + "utf8" + ); + assert.match(routerSrc, /if \(matchedOutput\) \{\s*return socket\.end\(\);\s*\}/, MOVED); +}); diff --git a/tests/unit/search-handler-perplexity-options.test.ts b/tests/unit/search-handler-perplexity-options.test.ts index 9e17f93013ec..93665048213e 100644 --- a/tests/unit/search-handler-perplexity-options.test.ts +++ b/tests/unit/search-handler-perplexity-options.test.ts @@ -52,6 +52,7 @@ test("Perplexity Search forwards validated provider options and locale filters", assert.equal(captured.url, "https://api.perplexity.ai/search"); assert.equal(captured.headers.Authorization, "Bearer perplexity-key"); + assert.equal(captured.headers["X-Pplx-Integration"], "omniroute"); assert.deepEqual(captured.body, { query: "ai agents", max_results: 1, diff --git a/tests/unit/search-timeout-budget.test.ts b/tests/unit/search-timeout-budget.test.ts new file mode 100644 index 000000000000..8cfc855687e5 --- /dev/null +++ b/tests/unit/search-timeout-budget.test.ts @@ -0,0 +1,96 @@ +/** + * The search deadlines are fixed constants today, so a slow upstream is cut + * off at 10-15s with no way to raise it. These tests pin the override: a + * caller-supplied budget replaces the constant, and with no override the + * request still gets a deadline instead of hanging forever. + */ + +import test from "node:test"; +import assert from "node:assert/strict"; + +const { handleSearch } = await import("../../open-sse/handlers/search.ts"); + +function hangingFetch() { + return (_url: unknown, init?: RequestInit) => + new Promise((_resolve, reject) => { + const signal = init?.signal; + if (signal?.aborted) { + reject(signal.reason ?? new Error("aborted")); + return; + } + signal?.addEventListener("abort", () => { + reject(signal.reason ?? new Error("aborted")); + }); + }); +} + +test("a search budget cuts off a hanging upstream at the budget", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = hangingFetch() as typeof fetch; + const started = Date.now(); + try { + const result = await handleSearch({ + query: "react hooks", + provider: "context7", + maxResults: 1, + searchType: "web", + credentials: {}, + log: null, + timeoutMs: 50, + }); + const elapsed = Date.now() - started; + assert.equal(result.success, false); + assert.ok(elapsed < 2000, `returned in ${elapsed}ms, budget did not apply`); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("without a budget a hanging upstream is still cut off", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = hangingFetch() as typeof fetch; + try { + const result = await Promise.race([ + handleSearch({ + query: "react hooks", + provider: "context7", + maxResults: 1, + searchType: "web", + credentials: {}, + log: null, + }), + new Promise((resolve) => setTimeout(() => resolve(null), 1000)), + ]); + assert.equal(result, null, "the default deadline fired inside 1s"); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("a per-provider override outlasts the provider's own timeout", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = hangingFetch() as typeof fetch; + const started = Date.now(); + try { + const result = await handleSearch({ + query: "react hooks", + provider: "context7", + maxResults: 1, + searchType: "web", + credentials: {}, + log: null, + // context7's own timeout is 10s. The budget is long, so only the + // per-provider override can cut this off inside 2s. + timeoutMs: 30000, + providerTimeoutsMs: { context7: 50 }, + }); + const elapsed = Date.now() - started; + assert.equal(result.success, false); + assert.ok( + elapsed < 2000, + `returned in ${elapsed}ms, the provider's own 10s still won` + ); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/tests/unit/server-owned-tool-loop-flag.test.ts b/tests/unit/server-owned-tool-loop-flag.test.ts index d5ed2ce02b30..6f4b21baeb31 100644 --- a/tests/unit/server-owned-tool-loop-flag.test.ts +++ b/tests/unit/server-owned-tool-loop-flag.test.ts @@ -72,8 +72,9 @@ describe("feature-flags-settings count update", () => { // (flush empty-turn retry, default off) bumps it to 76; ROTATION_ATTRIBUTION (#14223, // default off) to 77; STREAM_READINESS_STALL_RETRY (#14669, default off) to 78; // OPENCODE_POOL_RESELECT (default off) to 79; PROXY_POOL_SHARED_EGRESS_ORDER - // (#14657, default off) to 80. - assert.equal(FEATURE_FLAG_DEFINITIONS.length, 80); + // (#14657, default off) to 80; PROXY_OPERATOR_EGRESS_ENABLED (#15314, + // default off) to 81. + assert.equal(FEATURE_FLAG_DEFINITIONS.length, 81); }); }); diff --git a/tests/unit/share-request-context-symbol-for.test.ts b/tests/unit/share-request-context-symbol-for.test.ts new file mode 100644 index 000000000000..7457f91707fb --- /dev/null +++ b/tests/unit/share-request-context-symbol-for.test.ts @@ -0,0 +1,55 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Two module copies share the request contexts: a read from copy A must see the +// context opened by copy B. This mirrors production, where the module is bundled +// once per server chunk, so middleware opening the context and the route reading +// it may run on different copies. Single-copy coverage alone cannot catch this: +// it passes before and after. + +const CORRELATION_PATH = "../../src/shared/middleware/correlationId.ts"; +const REQUEST_ID_PATH = "../../src/shared/utils/requestId.ts"; + +test("a read from copy A sees the correlation opened by copy B", async () => { + const copyA = await import(`${CORRELATION_PATH}?graph=a`); + const copyB = await import(`${CORRELATION_PATH}?graph=b`); + const id = "corr-shared-1"; + const seen = copyB.runWithCorrelation(id, () => copyA.getCorrelationId()); + assert.equal(seen, id); +}); + +test("a read from copy A sees the request id opened by copy B", async () => { + const copyA = await import(`${REQUEST_ID_PATH}?graph=a`); + const copyB = await import(`${REQUEST_ID_PATH}?graph=b`); + const id = "req-shared-1"; + const seen = await copyB.withRequestId({ headers: { get: () => id } }, () => + copyA.getRequestId() + ); + assert.equal(seen, id); +}); + +test("sequential contexts keep their own id, outside yields the empty value", async () => { + const corrA = await import(`${CORRELATION_PATH}?graph=c`); + const corrB = await import(`${CORRELATION_PATH}?graph=d`); + assert.equal( + corrB.runWithCorrelation("corr-first", () => corrA.getCorrelationId()), + "corr-first" + ); + assert.equal( + corrB.runWithCorrelation("corr-second", () => corrA.getCorrelationId()), + "corr-second" + ); + assert.equal(corrA.getCorrelationId(), undefined); + + const reqA = await import(`${REQUEST_ID_PATH}?graph=c`); + const reqB = await import(`${REQUEST_ID_PATH}?graph=d`); + const first = await reqB.withRequestId({ headers: { get: () => "req-first" } }, () => + reqA.getRequestId() + ); + assert.equal(first, "req-first"); + const second = await reqB.withRequestId({ headers: { get: () => "req-second" } }, () => + reqA.getRequestId() + ); + assert.equal(second, "req-second"); + assert.equal(reqA.getRequestId(), null); +}); diff --git a/tests/unit/socks-connector-family.test.ts b/tests/unit/socks-connector-family.test.ts index c98dfdb49ace..5a8481a16d4b 100644 --- a/tests/unit/socks-connector-family.test.ts +++ b/tests/unit/socks-connector-family.test.ts @@ -1,6 +1,11 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import { buildSocksFamilySocketOptions } from "../../open-sse/utils/socksConnectorWithFamily.ts"; +import { MockAgent, fetch } from "undici"; +import { + buildSocksFamilySocketOptions, + createSocksDispatcherWithFamily, + socksConnectorWithFamily, +} from "../../open-sse/utils/socksConnectorWithFamily.ts"; describe("socksConnectorWithFamily", () => { it("returns family:6 + autoSelectFamily:false for ipv6", () => { @@ -13,3 +18,51 @@ describe("socksConnectorWithFamily", () => { assert.deepEqual(buildSocksFamilySocketOptions(null), {}); }); }); + +// Inspect the options at the real Agent-to-pool dispatch boundary without network I/O. +it("SOCKS dispatch disables HTTP/2 even when the caller enables it", async () => { + const mockAgent = new MockAgent(); + mockAgent.disableNetConnect(); + const pool = mockAgent.get("https://example.com"); + pool.intercept({ path: "/", method: "GET" }).reply(200, "ok"); + let dispatchedOptions: Record | undefined; + const dispatcher = createSocksDispatcherWithFamily( + { host: "127.0.0.1", port: 1080, type: 5 }, + 4, + { + allowH2: true, + factory: (_origin, options) => { + dispatchedOptions = options as Record; + return pool; + }, + } + ); + try { + const response = await fetch("https://example.com/", { dispatcher }); + assert.equal(await response.text(), "ok"); + assert.ok(dispatchedOptions, "the Agent must dispatch through the pool factory"); + assert.equal(dispatchedOptions.allowH2, false); + mockAgent.assertNoPendingInterceptors(); + } finally { + await dispatcher.close(); + await mockAgent.close(); + } +}); + +// The custom connect bypasses Agent.allowH2; the TLS connector itself must not offer h2 via ALPN. +it("SOCKS TLS connector is built with allowH2:false", () => { + let built: Record | undefined; + const fakeBuild = ((opts: Record) => { + built = opts; + return () => {}; + }) as never; + socksConnectorWithFamily( + { host: "127.0.0.1", port: 1080, type: 5 }, + 4, + { allowH2: true } as never, + 5000, + fakeBuild + ); + assert.equal(built?.allowH2, false); + assert.equal(built?.timeout, 5000); +}); diff --git a/tests/unit/sse-heartbeat.test.ts b/tests/unit/sse-heartbeat.test.ts index 420154025df7..ac7d3dd063dc 100644 --- a/tests/unit/sse-heartbeat.test.ts +++ b/tests/unit/sse-heartbeat.test.ts @@ -176,11 +176,12 @@ test("shape: openai-responses-in-progress emits response.in_progress data event" await writer.close(); await pump; - // #14330: the frame now carries a required `sequence_number` and `response` - // object so a strict Responses decoder does not abort on it. + // #14330 / #15202: the frame now carries a required `sequence_number`, a + // `response` object, and a STRING `response.id` so a strict Responses decoder + // does not abort on it. assert.equal( emitted[0], - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":null,"status":"in_progress"}}\n\n' + 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_keepalive_omniroute","status":"in_progress"}}\n\n' ); }); }); diff --git a/tests/unit/standalone-server-ws-shutdown-owner.test.ts b/tests/unit/standalone-server-ws-shutdown-owner.test.ts new file mode 100644 index 000000000000..d897fde1a5ae --- /dev/null +++ b/tests/unit/standalone-server-ws-shutdown-owner.test.ts @@ -0,0 +1,255 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// scripts/dev/standalone-server-ws.mjs (dist/server-ws.mjs) is the production entry point for +// Docker (`node dev/run-standalone.mjs`), `omniroute serve` and Electron. Next's standalone +// start-server registers its own SIGINT/SIGTERM handlers, which `server.close()` and then +// `process.exit(143)` as soon as the HTTP server has no connection left. That exit raced +// OmniRoute's async cleanup (src/lib/gracefulShutdown.ts: spend batch flush, call-log flush, +// DB checkpoint) and won, so a SIGTERM (docker stop, systemctl stop, Ctrl+C) dropped that work. +// Like scripts/dev/run-next.mjs (#12074), the wrapper must own process exit. +// +// The wrapper ends with `await import("./server.js")`, a file that only exists in the assembled +// standalone output, so the test copies the wrapper and its shipped siblings into a temp dir +// next to a fake server.js that behaves like Next's start-server for signals. + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const WRAPPER_SOURCE = path.join(ROOT, "scripts/dev/standalone-server-ws.mjs"); + +// Same sibling closure assembleStandalone.mjs ships next to dist/server-ws.mjs. +const SIBLINGS: Array<[string, string]> = [ + ["scripts/dev/peer-stamp.mjs", "peer-stamp.mjs"], + ["scripts/dev/main-server-timeouts.mjs", "main-server-timeouts.mjs"], + ["scripts/dev/systemd-notify.mjs", "systemd-notify.mjs"], + ["scripts/dev/http-method-guard.cjs", "http-method-guard.cjs"], + ["scripts/dev/head-response-guard.cjs", "head-response-guard.cjs"], + ["scripts/dev/responses-ws-proxy.mjs", "responses-ws-proxy.mjs"], + ["src/shared/utils/httpClientAbortGuard.mjs", "httpClientAbortGuard.mjs"], + ["scripts/dev/webdav-handler.mjs", "webdav-handler.mjs"], + ["scripts/dev/tls-options.mjs", "tls-options.mjs"], +]; + +// Fake Next standalone server.js. Signal handling mirrors +// node_modules/next/dist/server/lib/start-server.js: inside the 'listening' handler, unless +// NEXT_MANUAL_SIG_HANDLE is set, SIGINT/SIGTERM -> server.close() -> process.exit(130/143). +// It also plays the part of instrumentation: an application cleanup that takes a while (like the +// spend/call-log flush + DB checkpoint), registered the way src/lib/gracefulShutdown.ts +// initGracefulShutdown() registers it. +const FAKE_NEXT_SERVER = String.raw` +const http = require("node:http"); +const log = (msg) => process.stdout.write("EVENT " + msg + "\n"); +const mode = process.env.FAKE_MODE || "normal"; +const server = http.createServer((req, res) => res.end("ok")); +server.on("listening", () => { + if (!process.env.NEXT_MANUAL_SIG_HANDLE) { + let started = false; + const cleanup = (signal) => { + if (started) return; + started = true; + server.close(() => process.exit(signal === "SIGINT" ? 130 : 143)); + }; + process.on("SIGINT", cleanup); + process.on("SIGTERM", cleanup); + log("next-handlers-registered"); + } + setTimeout(() => { + log("env-after-listen=" + (process.env.NEXT_MANUAL_SIG_HANDLE ?? "unset")); + if (mode !== "no-instrumentation") { + const requestShutdown = async (signal) => { + log("cleanup-start " + signal + " listening=" + server.listening); + if (mode === "hang") { + setInterval(() => {}, 1000); // like the real app's timers/handles: the loop stays alive + return new Promise(() => {}); + } + await new Promise((r) => setTimeout(r, 400)); + log("cleanup-done"); + }; + globalThis.__omnirouteRequestShutdown = requestShutdown; + if (!globalThis.__omnirouteCustomServerOwnsShutdown) { + for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) { + process.on(sig, () => requestShutdown(sig).then(() => setTimeout(() => process.exit(0), 0))); + } + log("app-handlers-registered"); + } + } + log("ready"); + }, 50); +}); +server.listen(0, "127.0.0.1"); +`; + +function makeStandaloneDir(): string { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-server-ws-")); + fs.copyFileSync(WRAPPER_SOURCE, path.join(dir, "server-ws.mjs")); + for (const [src, dest] of SIBLINGS) fs.copyFileSync(path.join(ROOT, src), path.join(dir, dest)); + fs.writeFileSync(path.join(dir, "server.js"), FAKE_NEXT_SERVER); + return dir; +} + +type RunResult = { code: number | null; signal: string | null; events: string[]; ms: number }; + +async function runAndSignal( + dir: string, + env: Record, + signals: NodeJS.Signals[] = ["SIGTERM"] +): Promise { + const childEnv: NodeJS.ProcessEnv = { ...process.env, ...env }; + delete childEnv.NEXT_MANUAL_SIG_HANDLE; + delete childEnv.NOTIFY_SOCKET; + // Lets a test pass an explicit (possibly empty) value through the delete above. + if ("NEXT_MANUAL_SIG_HANDLE" in env) childEnv.NEXT_MANUAL_SIG_HANDLE = env.NEXT_MANUAL_SIG_HANDLE; + const child = spawn(process.execPath, ["server-ws.mjs"], { + cwd: dir, + env: childEnv, + stdio: ["ignore", "pipe", "pipe"], + }); + let out = ""; + child.stdout.on("data", (d: Buffer) => (out += d.toString())); + child.stderr.on("data", (d: Buffer) => (out += d.toString())); + const exited = new Promise<{ code: number | null; signal: string | null }>((resolve) => + child.once("exit", (code, signal) => resolve({ code, signal })) + ); + const deadline = Date.now() + 10_000; + while (!out.includes("EVENT ready")) { + if (Date.now() > deadline) { + child.kill("SIGKILL"); + throw new Error(`fake server never became ready:\n${out}`); + } + await new Promise((r) => setTimeout(r, 20)); + } + const sentAt = Date.now(); + for (const sig of signals) child.kill(sig); + const killer = setTimeout(() => child.kill("SIGKILL"), 15_000); + const { code, signal } = await exited; + clearTimeout(killer); + const events = out + .split("\n") + .filter((l) => l.startsWith("EVENT ")) + .map((l) => l.slice("EVENT ".length)); + return { code, signal, events, ms: Date.now() - sentAt }; +} + +test("SIGTERM: the wrapper owns exit, so application cleanup finishes before the process exits", async () => { + const dir = makeStandaloneDir(); + try { + const r = await runAndSignal(dir, {}); + assert.ok( + !r.events.includes("next-handlers-registered"), + "Next's own SIGTERM handler must not be installed (it exits 143 mid-cleanup)" + ); + assert.ok( + !r.events.includes("app-handlers-registered"), + "the app must register its cleanup with the wrapper, not compete with its own listener" + ); + assert.equal(r.code, 0, `expected exit 0, got ${r.code}/${r.signal}: ${r.events.join(", ")}`); + const start = r.events.indexOf("cleanup-start SIGTERM listening=false"); + const done = r.events.indexOf("cleanup-done"); + assert.ok(start >= 0, `cleanup must run after the listener stopped accepting: ${r.events}`); + assert.ok(done > start, "application cleanup must complete before exit"); + assert.ok(r.ms < 5_000, `exit must be prompt once cleanup is done (took ${r.ms} ms)`); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("NEXT_MANUAL_SIG_HANDLE does not leak to child processes after Next read it", async () => { + const dir = makeStandaloneDir(); + try { + const r = await runAndSignal(dir, {}); + assert.ok( + r.events.includes("env-after-listen=unset"), + `embedded services (e.g. a Next-based child) inherit process.env: ${r.events}` + ); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("an empty NEXT_MANUAL_SIG_HANDLE is treated as unset: the wrapper still owns exit", async () => { + const dir = makeStandaloneDir(); + try { + const r = await runAndSignal(dir, { NEXT_MANUAL_SIG_HANDLE: "" }); + assert.ok( + !r.events.includes("next-handlers-registered"), + "an empty value is falsy for Next; the wrapper must still set it" + ); + assert.equal(r.code, 0, `expected exit 0, got ${r.code}/${r.signal}: ${r.events.join(", ")}`); + assert.ok(r.events.includes("cleanup-done"), `cleanup must finish: ${r.events}`); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("duplicate signals (shell + parent forwarding) do not cut cleanup short", async () => { + const dir = makeStandaloneDir(); + try { + const r = await runAndSignal(dir, {}, ["SIGINT", "SIGTERM"]); + assert.equal(r.code, 0, `expected exit 0, got ${r.code}/${r.signal}: ${r.events.join(", ")}`); + assert.ok(r.events.includes("cleanup-done"), `cleanup must finish: ${r.events}`); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("SIGTERM before instrumentation registered cleanup still exits promptly", async () => { + const dir = makeStandaloneDir(); + try { + const r = await runAndSignal(dir, { FAKE_MODE: "no-instrumentation" }); + assert.equal(r.code, 0, `expected exit 0, got ${r.code}/${r.signal}: ${r.events.join(", ")}`); + assert.ok(r.ms < 3_000, `took ${r.ms} ms`); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +test("a cleanup that never settles is bounded by the force-exit timer", async () => { + const dir = makeStandaloneDir(); + try { + // Force exit = SHUTDOWN_TIMEOUT_MS (the in-flight request drain budget) + 5 s cleanup margin. + const r = await runAndSignal(dir, { FAKE_MODE: "hang", SHUTDOWN_TIMEOUT_MS: "100" }); + assert.ok( + r.events.some((e) => e.startsWith("cleanup-start SIGTERM")), + `${r.events}` + ); + assert.ok(!r.events.includes("cleanup-done")); + assert.equal(r.code, 0, `expected exit 0, got ${r.code}/${r.signal}: ${r.events.join(", ")}`); + assert.ok(r.ms >= 4_000 && r.ms < 9_000, `force exit expected at ~5.1 s, took ${r.ms} ms`); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +}); + +const MOVED = + "Next's start-server.js changed — re-check by hand that NEXT_MANUAL_SIG_HANDLE is still read synchronously in the 'listening' handler before the wrapper clears it"; + +test("Next still reads NEXT_MANUAL_SIG_HANDLE synchronously in its listening handler", () => { + // The wrapper clears the variable on the tick after the first 'listening' event (so children + // never inherit it). That is only safe while Next checks it before its first await there. + const src = fs.readFileSync( + path.join(ROOT, "node_modules/next/dist/server/lib/start-server.js"), + "utf8" + ); + const listening = src.indexOf("server.on('listening', async ()=>{"); + const check = src.indexOf("if (!process.env.NEXT_MANUAL_SIG_HANDLE)", listening); + const firstUnconditionalAwait = src.indexOf("await getRequestHandlers", listening); + assert.ok(listening >= 0 && check > listening, MOVED); + assert.ok(check < firstUnconditionalAwait, MOVED); + // Awaits inside the (not yet running) cleanup closure do not count: skip its definition. + const cleanupDef = src.indexOf("const cleanup = (signal)=>{", listening); + const afterCleanupDef = src.indexOf("// Make sure commands gracefully respect", cleanupDef); + assert.ok( + cleanupDef > listening && afterCleanupDef > cleanupDef && afterCleanupDef < check, + MOVED + ); + const awaits = (s: string) => (s.match(/\bawait\b/g) ?? []).length; + assert.equal( + awaits(src.slice(listening, cleanupDef)) + awaits(src.slice(afterCleanupDef, check)), + 1, + MOVED + ); +}); diff --git a/tests/unit/stream-claude-empty-end-turn.test.ts b/tests/unit/stream-claude-empty-end-turn.test.ts new file mode 100644 index 000000000000..256254e57f43 --- /dev/null +++ b/tests/unit/stream-claude-empty-end-turn.test.ts @@ -0,0 +1,63 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { shouldAbortEmptyClaudeStream } = + await import("../../open-sse/utils/streamClaudeEmptyBody.ts"); + +const complete = { + hasError: false, + hasContentBlock: false, + hasMessageStart: true, + hasMessageDelta: true, + hasMessageStop: true, +}; + +test("a finished Claude stream with end_turn and no content block is a real empty answer", () => { + assert.equal(shouldAbortEmptyClaudeStream({ ...complete, stopReason: "end_turn" }, true), false); +}); + +test("a finished Claude stream with stop_sequence and no content block is a real empty answer", () => { + assert.equal( + shouldAbortEmptyClaudeStream({ ...complete, stopReason: "stop_sequence" }, true), + false + ); +}); + +test("content_filter with no content block still aborts", () => { + assert.equal( + shouldAbortEmptyClaudeStream({ ...complete, stopReason: "content_filter" }, true), + true + ); +}); + +test("a complete lifecycle with no stop_reason still aborts", () => { + assert.equal(shouldAbortEmptyClaudeStream({ ...complete, stopReason: null }, true), true); +}); + +test("a lifecycle that never reached message_stop still aborts", () => { + assert.equal( + shouldAbortEmptyClaudeStream( + { ...complete, hasMessageStop: false, stopReason: "end_turn" }, + true + ), + true + ); +}); + +test("a zero-byte Claude stream still aborts even if a stop reason was guessed", () => { + assert.equal(shouldAbortEmptyClaudeStream({ ...complete, stopReason: "end_turn" }, false), true); +}); + +test("message_delta stores stop_reason for the empty-stream decision", async () => { + const { createClaudeEmptyResponseLifecycle, updateClaudeEmptyResponseLifecycle } = + await import("../../open-sse/utils/stream.ts"); + const lifecycle = createClaudeEmptyResponseLifecycle(); + updateClaudeEmptyResponseLifecycle(lifecycle, { + type: "message_delta", + delta: { stop_reason: "end_turn" }, + }); + updateClaudeEmptyResponseLifecycle(lifecycle, { type: "message_stop" }); + assert.equal(lifecycle.stopReason, "end_turn"); + assert.equal(lifecycle.hasMessageStop, true); + assert.equal(shouldAbortEmptyClaudeStream(lifecycle, true), false); +}); diff --git a/tests/unit/stream-readiness-fallback.test.ts b/tests/unit/stream-readiness-fallback.test.ts index 30c7f8564672..ca76fbe9956d 100644 --- a/tests/unit/stream-readiness-fallback.test.ts +++ b/tests/unit/stream-readiness-fallback.test.ts @@ -226,6 +226,12 @@ test("a healthy first body issues no fallback request", async () => { try { assert.equal(calls, 1, "no fallback request when the first body is healthy"); assert.equal(result.success, true); + // Drain rather than cancel: cancelling mid-flight leaves the pipeline + // feeding this body still in progress, and it then settles AFTER the test + // has ended — which node:test reports as "generated asynchronous activity + // after the test ended" and fails the whole file. The sibling fallback test + // below already drains for the same reason. + await result.response?.text(); } finally { await result.response?.body?.cancel().catch(() => {}); } @@ -251,6 +257,8 @@ test("a stalled first body never marks the account unavailable", async () => { try { assert.equal(result.success, true); assert.deepEqual(seen, [], "the stalled body must not penalize the account"); + // Drain for the same reason as above — see the comment in the healthy-body test. + await result.response?.text(); } finally { await result.response?.body?.cancel().catch(() => {}); } diff --git a/tests/unit/stream-readiness-policy.test.ts b/tests/unit/stream-readiness-policy.test.ts index 052b2210816d..9017a29f7d99 100644 --- a/tests/unit/stream-readiness-policy.test.ts +++ b/tests/unit/stream-readiness-policy.test.ts @@ -383,3 +383,55 @@ test("does not treat an unrelated id containing 'thinking' as an alias suffix", assert.equal(result.timeoutMs, 80_000); assert.ok(!result.reasons.includes("extended_thinking")); }); + +// Codex now serves gpt-6.x models, and its top effort tiers are spelled `xhigh` and +// `max` (`gpt-6.1-sol-xhigh`, `gpt-6-luna-max`). Without the reasoning bump those long +// warm-ups tripped the content-stall watchdog at the base window mid-reasoning. +test("gives high-reasoning Codex GPT-6.x aliases (-xhigh, -max) the codex reasoning bump", () => { + for (const model of ["gpt-6.1-sol-xhigh", "gpt-6-luna-max"]) { + const result = resolveStreamReadinessTimeout({ + baseTimeoutMs: 80_000, + provider: "codex", + model, + body: { input: items(3), tools: tools(2) }, + }); + + assert.equal(result.timeoutMs, 110_000, model); + assert.ok(result.reasons.includes("codex_gpt_5_5_high_reasoning"), model); + } +}); + +test("treats an xhigh reasoning effort in the request body as high reasoning", () => { + const result = resolveStreamReadinessTimeout({ + baseTimeoutMs: 80_000, + provider: "codex", + model: "gpt-6.1-sol", + body: { input: items(3), reasoning: { effort: "xhigh" } }, + }); + + assert.equal(result.timeoutMs, 110_000); + assert.ok(result.reasons.includes("codex_gpt_5_5_high_reasoning")); +}); + +test("gives large Codex GPT-6.x Responses requests the codex large-request bump", () => { + const result = resolveStreamReadinessTimeout({ + baseTimeoutMs: 80_000, + provider: "codex", + model: "gpt-6.1-sol", + body: { input: items(3), tools: tools(16) }, + }); + + assert.ok(result.reasons.includes("codex_gpt_5_5_large_responses")); +}); + +test("does NOT bump small Codex GPT-6.x requests without a high effort tier", () => { + const result = resolveStreamReadinessTimeout({ + baseTimeoutMs: 80_000, + provider: "codex", + model: "gpt-6.1-sol", + body: { input: items(3), tools: tools(2) }, + }); + + assert.equal(result.timeoutMs, 80_000); + assert.deepEqual(result.reasons, ["base"]); +}); diff --git a/tests/unit/stream-readiness-thinking-liveness.test.ts b/tests/unit/stream-readiness-thinking-liveness.test.ts new file mode 100644 index 000000000000..455867473172 --- /dev/null +++ b/tests/unit/stream-readiness-thinking-liveness.test.ts @@ -0,0 +1,88 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { + createStreamContentWatcher, + hasUsefulStreamContent, +} from "../../open-sse/utils/streamReadiness.ts"; + +// Frames follow the production reader shape (open-sse/handlers/sseParser.ts): +// `event: content_block_delta` + `data:` JSON with `delta.type` / +// `delta.thinking` / `delta.signature` set by the upstream. +function wireDelta(delta: unknown): string { + return [ + "event: content_block_delta", + `data: ${JSON.stringify({ type: "content_block_delta", index: 1, delta })}`, + "", + "", + ].join("\n"); +} + +test("thinking-phase signature-only delta counts as stream liveness", () => { + const frame = wireDelta({ type: "signature_delta", signature: "GkDPeXJveXQ" }); + assert.equal(hasUsefulStreamContent(frame), true); +}); + +test("thinking-phase text delta still counts as stream liveness", () => { + const frame = wireDelta({ type: "thinking_delta", thinking: "working through the answer" }); + assert.equal(hasUsefulStreamContent(frame), true); +}); + +test("thinking-phase encrypted payload carried by thinking counts as stream liveness", () => { + const frame = wireDelta({ type: "thinking_delta", thinking: "Eu0e64aX2vKq9mZwT4sYw" }); + assert.equal(hasUsefulStreamContent(frame), true); +}); + +test("idle pings and completion markers carry no stream liveness", () => { + assert.equal(hasUsefulStreamContent("event: ping\ndata: {}\n\n"), false); + assert.equal(hasUsefulStreamContent("data: [DONE]\n\n"), false); + assert.equal(hasUsefulStreamContent("data: \n\n"), false); + assert.equal(hasUsefulStreamContent(": heartbeat\n\n"), false); +}); + +test("empty signature bootstrap carries no stream liveness", () => { + const frame = wireDelta({ type: "signature_delta", signature: "" }); + assert.equal(hasUsefulStreamContent(frame), false); +}); + +test("typed delta without payload carries no stream liveness", () => { + const frame = wireDelta({ type: "thinking_delta" }); + assert.equal(hasUsefulStreamContent(frame), false); +}); + +test("empty data payload carries no stream liveness", () => { + assert.equal(hasUsefulStreamContent("data: \n\n"), false); + assert.equal(hasUsefulStreamContent("data: [DONE]\n\n"), false); +}); + +test("non-json payload stays live through the pre-existing fallback", () => { + // Pre-existing catch branch (streamReadiness.ts: hasUsefulStreamContent): + // a non-empty payload that is not JSON counts as content. Out of scope, + // documented here so no future change silently flips it. + assert.equal(hasUsefulStreamContent("data: {truncated-json-GkD\n\n"), true); +}); + +test("content watcher observes thinking-phase deltas as liveness", () => { + const watcher = createStreamContentWatcher(); + try { + watcher.note(wireDelta({ type: "signature_delta", signature: "GkDPeXJveXQ" })); + watcher.note(wireDelta({ type: "signature_delta", signature: "Q2hySHkzV3c" })); + watcher.finish(); + assert.equal(watcher.sawContent(), true); + } finally { + watcher.finish(); + } +}); + +test("content watcher stays silent on an idle stream", () => { + const watcher = createStreamContentWatcher(); + try { + watcher.note("event: ping\ndata: {}\n\n"); + watcher.note(wireDelta({ type: "signature_delta", signature: "" })); + watcher.note(wireDelta({ type: "thinking_delta" })); + watcher.finish(); + assert.equal(watcher.sawContent(), false); + } finally { + watcher.finish(); + } +}); diff --git a/tests/unit/stream-stall-no-account-cooldown.test.ts b/tests/unit/stream-stall-no-account-cooldown.test.ts new file mode 100644 index 000000000000..0cc1a4e82fd5 --- /dev/null +++ b/tests/unit/stream-stall-no-account-cooldown.test.ts @@ -0,0 +1,99 @@ +// A stream content stall is the watchdog giving up on ONE request that sent no model +// output in time — in production almost always a long xhigh/max reasoning turn on Codex +// (`reasoning_open=yes`). Cooling the account for it took the only usable Codex account +// out of routing for a minute at a time, so every request in that window failed over to +// fallbacks or 503'd. Stalls keep the failure observable but no longer cool the account, +// unless the operator opts back in with resilienceSettings.streamStallCooldown.enabled. +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-stream-stall-cooldown-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const settingsDb = await import("../../src/lib/db/settings.ts"); +const auth = await import("../../src/sse/services/auth.ts"); + +const STALL_MESSAGE = + "stream content stall: no model output within 100000ms (bytes=73343 events=26 " + + "top=response.output_item.added:12,response.output_item.done:10 reasoning_open=yes)"; + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +async function seedCodex() { + return providersDb.createProviderConnection({ + provider: "codex", + authType: "oauth", + accessToken: "codex-access-token", + isActive: true, + testStatus: "active", + }); +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("a stream content stall does not cool down the account by default", async () => { + await resetStorage(); + const conn = await seedCodex(); + + const result = await auth.markAccountUnavailable( + conn.id, + 502, + STALL_MESSAGE, + "codex", + "gpt-6.1-sol-xhigh" + ); + + assert.equal(result.cooldownMs, 0); + assert.equal(result.shouldFallback, true, "the combo may still try its next target"); + const after = await providersDb.getProviderConnectionById(conn.id); + assert.equal(after.testStatus, "active"); + assert.ok(!after.rateLimitedUntil, "connection must not be rate-limited"); + assert.match(String(after.lastError), /stall/i, "the stall stays observable"); +}); + +test("streamStallCooldown.enabled restores the account cooldown for stalls", async () => { + await resetStorage(); + const conn = await seedCodex(); + await settingsDb.updateSettings({ + resilienceSettings: { streamStallCooldown: { enabled: true } }, + }); + + const result = await auth.markAccountUnavailable( + conn.id, + 502, + STALL_MESSAGE, + "codex", + "gpt-6.1-sol-xhigh" + ); + + assert.ok(result.cooldownMs > 0, "opted-in stalls cool the account like other 502s"); + const after = await providersDb.getProviderConnectionById(conn.id); + assert.ok(after.rateLimitedUntil, "connection is rate-limited"); +}); + +test("other upstream 502s still cool down the account", async () => { + await resetStorage(); + const conn = await seedCodex(); + + const result = await auth.markAccountUnavailable( + conn.id, + 502, + "upstream connect error or disconnect/reset before headers", + "codex", + "gpt-6.1-sol-xhigh" + ); + + assert.ok(result.cooldownMs > 0); +}); diff --git a/tests/unit/streaming-reasoning-dedup-5786.test.ts b/tests/unit/streaming-reasoning-dedup-5786.test.ts index 24d0f6112560..e72276f6078a 100644 --- a/tests/unit/streaming-reasoning-dedup-5786.test.ts +++ b/tests/unit/streaming-reasoning-dedup-5786.test.ts @@ -96,11 +96,66 @@ async function runClaudeFromCodex(rawSSE: string, chunkSize = 1_000_000): Promis return { raw, content, thinking }; } +test("#15507 string sequence_number replay is dropped the same way as a numeric replay", async () => { + const rawEvents = [ + sse("response.created", { + sequence_number: "0", + response: { id: "resp_s", status: "in_progress" }, + }), + sse("response.output_item.added", { + sequence_number: "1", + output_index: 0, + item: { id: "msg_s", type: "message", content: [], role: "assistant" }, + }), + sse("response.output_text.delta", { + sequence_number: "2", + item_id: "msg_s", + output_index: 0, + content_index: 0, + delta: "once", + }), + sse("response.output_text.delta", { + sequence_number: "2", + item_id: "msg_s", + output_index: 0, + content_index: 0, + delta: "twice", + }), + ]; + const { content } = await runClaudeFromCodex(rawEvents.join("")); + assert.equal(content, "once"); +}); + +test("#15507 a sequence past the safe integer range is not treated as a replay", async () => { + const huge = "9007199254740993"; + const rawEvents = [ + sse("response.output_text.delta", { + sequence_number: huge, + item_id: "msg_h", + output_index: 0, + content_index: 0, + delta: "a", + }), + sse("response.output_text.delta", { + sequence_number: huge, + item_id: "msg_h", + output_index: 0, + content_index: 0, + delta: "b", + }), + ]; + const { content } = await runClaudeFromCodex(rawEvents.join("")); + assert.equal(content, "ab"); +}); + test("#5786 (A) replayed Responses-API deltas (sequence_number <= last-seen) are dropped — no glued duplicate text", async () => { // Upstream reconnect/retry replays the same output_text.delta event with the SAME // sequence_number, producing the reported "...briefly.and I'll summarize briefly." glue. const rawEvents = [ - sse("response.created", { sequence_number: 0, response: { id: "resp_1", status: "in_progress" } }), + sse("response.created", { + sequence_number: 0, + response: { id: "resp_1", status: "in_progress" }, + }), sse("response.output_item.added", { sequence_number: 1, output_index: 0, @@ -133,7 +188,12 @@ test("#5786 (A) replayed Responses-API deltas (sequence_number <= last-seen) are }), sse("response.completed", { sequence_number: 4, - response: { id: "resp_1", status: "completed", output: [], usage: { input_tokens: 10, output_tokens: 20 } }, + response: { + id: "resp_1", + status: "completed", + output: [], + usage: { input_tokens: 10, output_tokens: 20 }, + }, }), ]; @@ -148,7 +208,10 @@ test("#5786 (A) replayed Responses-API deltas (sequence_number <= last-seen) are test("#5786 (A-guard) a well-behaved stream with strictly increasing sequence_number is passed through UNCHANGED", async () => { const rawEvents = [ - sse("response.created", { sequence_number: 0, response: { id: "resp_2", status: "in_progress" } }), + sse("response.created", { + sequence_number: 0, + response: { id: "resp_2", status: "in_progress" }, + }), sse("response.output_item.added", { sequence_number: 1, output_index: 0, @@ -187,7 +250,12 @@ test("#5786 (A-guard) a well-behaved stream with strictly increasing sequence_nu }), sse("response.completed", { sequence_number: 6, - response: { id: "resp_2", status: "completed", output: [], usage: { input_tokens: 5, output_tokens: 8 } }, + response: { + id: "resp_2", + status: "completed", + output: [], + usage: { input_tokens: 5, output_tokens: 8 }, + }, }), ]; @@ -200,7 +268,10 @@ test("#5786 (A-guard) a well-behaved stream with strictly increasing sequence_nu test("#5786 (B) reasoning summary that arrives ONLY on output_item.done is surfaced as a Claude thinking block (not dropped, not in content)", async () => { const reasoningText = "I'm thinking I need to read the file again."; const rawEvents = [ - sse("response.created", { sequence_number: 0, response: { id: "resp_3", status: "in_progress" } }), + sse("response.created", { + sequence_number: 0, + response: { id: "resp_3", status: "in_progress" }, + }), // Reasoning item announced... sse("response.output_item.added", { sequence_number: 1, @@ -242,7 +313,12 @@ test("#5786 (B) reasoning summary that arrives ONLY on output_item.done is surfa }), sse("response.completed", { sequence_number: 6, - response: { id: "resp_3", status: "completed", output: [], usage: { input_tokens: 10, output_tokens: 20 } }, + response: { + id: "resp_3", + status: "completed", + output: [], + usage: { input_tokens: 10, output_tokens: 20 }, + }, }), ]; @@ -252,12 +328,18 @@ test("#5786 (B) reasoning summary that arrives ONLY on output_item.done is surfa assert.equal(thinking, reasoningText); // ...and must NOT leak into the visible assistant content. assert.equal(content, "Sure thing."); - assert.ok(!content.includes(reasoningText), `reasoning leaked into content: ${JSON.stringify(content)}`); + assert.ok( + !content.includes(reasoningText), + `reasoning leaked into content: ${JSON.stringify(content)}` + ); }); test("#5786 (B) a normal reasoning stream (with reasoning_summary_text.delta) still yields a thinking block and does NOT duplicate", async () => { const rawEvents = [ - sse("response.created", { sequence_number: 0, response: { id: "resp_4", status: "in_progress" } }), + sse("response.created", { + sequence_number: 0, + response: { id: "resp_4", status: "in_progress" }, + }), sse("response.output_item.added", { sequence_number: 1, output_index: 0, @@ -325,7 +407,12 @@ test("#5786 (B) a normal reasoning stream (with reasoning_summary_text.delta) st }), sse("response.completed", { sequence_number: 10, - response: { id: "resp_4", status: "completed", output: [], usage: { input_tokens: 12, output_tokens: 15 } }, + response: { + id: "resp_4", + status: "completed", + output: [], + usage: { input_tokens: 12, output_tokens: 15 }, + }, }), ]; @@ -336,5 +423,9 @@ test("#5786 (B) a normal reasoning stream (with reasoning_summary_text.delta) st // Duplication guard: the summary text must appear exactly once in the thinking channel. const firstIdx = thinking.indexOf("Considering the request."); const lastIdx = thinking.lastIndexOf("Considering the request."); - assert.equal(firstIdx, lastIdx, `reasoning summary duplicated in thinking: ${JSON.stringify(thinking)}`); + assert.equal( + firstIdx, + lastIdx, + `reasoning summary duplicated in thinking: ${JSON.stringify(thinking)}` + ); }); diff --git a/tests/unit/synced-vision-verdict-memo.test.ts b/tests/unit/synced-vision-verdict-memo.test.ts new file mode 100644 index 000000000000..fa40372aad59 --- /dev/null +++ b/tests/unit/synced-vision-verdict-memo.test.ts @@ -0,0 +1,257 @@ +/** + * Synced vision verdict memo: on-demand reads must not re-read the whole + * provider catalog on every resolution. + * + * The memo is gated on the shared model-catalog cache version and rebuilt + * after each catalog write. An injected database bypasses the memo, and a + * failed bulk read is never stored. + */ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-synced-vision-memo-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; + +const core = await import("../../src/lib/db/core.ts"); +const models = await import("../../src/lib/db/models.ts"); +const readCache = await import("../../src/lib/db/readCache.ts"); +const vision = await import("../../src/lib/db/models/syncedAvailableModelVision.ts"); + +let prepareCalls = 0; +let trackedDb: { prepare: (sql: string) => unknown } | null = null; + +function patchPrepare() { + const db = core.getDbInstance() as unknown as { + prepare: (sql: string) => { all: (...args: unknown[]) => unknown[] }; + }; + if (trackedDb === db) return; + const original = db.prepare.bind(db); + db.prepare = ((sql: string, ...rest: unknown[]) => { + if (typeof sql === "string" && sql.includes("syncedAvailableModels")) prepareCalls += 1; + return (original as (...args: unknown[]) => unknown)(sql, ...rest); + }) as typeof db.prepare; + trackedDb = db as unknown as { prepare: (sql: string) => unknown }; +} + +function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); + trackedDb = null; + prepareCalls = 0; + vision.resetSyncedVisionVerdictMemoForTests(); + patchPrepare(); +} + +function seedSynced(providerId: string, connectionId: string, value: unknown) { + core + .getDbInstance() + .prepare("INSERT INTO key_value (namespace, key, value) VALUES ('syncedAvailableModels', ?, ?)") + .run(`${providerId}:${connectionId}`, JSON.stringify(value)); +} + +function memoEntries(): number { + return vision.getSyncedVisionVerdictMemoSizeForTests(); +} + +test.beforeEach(() => { + resetStorage(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("serves repeated lookups from one read after the first", async () => { + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + { id: "plain-model", name: "Plain Model" }, + ]); + prepareCalls = 0; + + const lookups = 5; + for (let i = 0; i < lookups; i += 1) { + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), true); + assert.equal(vision.getSyncedAvailableModelVision("acme", "plain-model"), null); + } + + assert.equal(prepareCalls, 1); + assert.equal(memoEntries(), 1); + assert.equal(vision.getSyncedAvailableModelVision("", "vision-model"), null); + assert.equal(vision.getSyncedAvailableModelVision("acme", ""), null); +}); + +test("refreshes the verdict after every writer path", async () => { + const read = () => vision.getSyncedAvailableModelVision("acme", "vision-model"); + + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(read(), true); + + assert.equal(await models.removeSyncedAvailableModel("acme", "vision-model"), true); + assert.equal(read(), null); + + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(read(), true); + await models.deleteSyncedAvailableModelsForConnection("acme", "conn-a"); + assert.equal(read(), null); + + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(read(), true); + assert.equal(await models.deleteSyncedAvailableModelsForProvider("acme"), 1); + assert.equal(read(), null); + + await models.replaceSyncedAvailableModelsForConnection("acme", "keep", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + seedSynced("acme", "stale", [{ id: "vision-model", name: "Vision Model" }]); + assert.equal(await models.pruneStaleSyncedAvailableModelsForProvider("acme", ["keep"]), 1); + assert.equal(read(), true); +}); + +test("serves a stale verdict when a write bypasses the shared exit", async () => { + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), true); + + core + .getDbInstance() + .prepare("UPDATE key_value SET value = ? WHERE namespace = 'syncedAvailableModels' AND key = ?") + .run(JSON.stringify([{ id: "vision-model", name: "Vision Model" }]), "acme:conn-a"); + + assert.equal( + vision.getSyncedAvailableModelVision("acme", "vision-model"), + true, + "unbumped direct write must keep serving the stored verdict" + ); +}); + +test("ignores the memo for an injected database", async () => { + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), true); + + prepareCalls = 0; + const stored = await models.getSyncedAvailableModels("acme"); + prepareCalls = 0; + const injectedRows = [{ key: "acme:conn-a", value: JSON.stringify(stored) }]; + const injected = { + prepare: () => ({ + all: () => injectedRows, + get: () => injectedRows[0], + run: () => ({ changes: 0, lastInsertRowid: 0 }), + }), + }; + assert.equal( + vision.getSyncedAvailableModelVision("acme", "vision-model", undefined, { + getDatabase: () => injected, + }), + true + ); + assert.equal(prepareCalls, 0, "injected reads must not touch the singleton counter"); + assert.equal( + vision.getSyncedAvailableModelVision("acme", "vision-model"), + true, + "stored verdict must survive an injected read" + ); +}); + +test("does not store failed singleton reads", async () => { + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), true); + vision.resetSyncedVisionVerdictMemoForTests(); + const versionBefore = readCache.getModelCatalogCacheVersion(); + + core.getDbInstance().prepare("DROP TABLE key_value").run(); + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), null); + assert.equal(vision.getSyncedVisionVerdictMemoSizeForTests(), 0); + assert.equal(readCache.getModelCatalogCacheVersion(), versionBefore); + + resetStorage(); + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), true); + assert.equal(vision.getSyncedVisionVerdictMemoSizeForTests(), 1); +}); + +test("does not store failed injected reads", async () => { + const versionBefore = readCache.getModelCatalogCacheVersion(); + const failing = { + prepare: () => ({ + all: () => { + throw new Error("closed database"); + }, + get: () => { + throw new Error("closed database"); + }, + run: () => { + throw new Error("closed database"); + }, + }), + }; + assert.equal( + vision.getSyncedAvailableModelVision("acme", "vision-model", undefined, { + getDatabase: () => failing, + }), + null + ); + assert.equal(readCache.getModelCatalogCacheVersion(), versionBefore); + assert.equal(memoEntries(), 0); + + seedSynced("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), true); +}); + +test("bypasses the memo for an empty bulk map and stores the empty map", async () => { + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model", new Map()), null); + + prepareCalls = 0; + assert.equal(vision.getSyncedAvailableModelVision("acme", "vision-model"), null); + assert.equal(vision.getSyncedAvailableModelVision("acme", "other-model"), null); + assert.equal(prepareCalls, 1); + assert.equal(memoEntries(), 1); +}); + +test("resolves a wildcard provider id exactly", () => { + seedSynced("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + seedSynced("acme%", "conn-a", [{ id: "other-model", name: "Other Model", supportsVision: true }]); + + assert.equal(vision.getSyncedAvailableModelVision("acme%", "vision-model"), null); + assert.equal(vision.getSyncedAvailableModelVision("acme%", "other-model"), true); +}); + +test("keeps a single stored generation after reads and writes", async () => { + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + ]); + for (let i = 0; i < 5; i += 1) { + vision.getSyncedAvailableModelVision("acme", "vision-model"); + } + await models.replaceSyncedAvailableModelsForConnection("acme", "conn-a", [ + { id: "vision-model", name: "Vision Model", supportsVision: true }, + { id: "second-vision-model", name: "Second", supportsVision: true }, + ]); + for (let i = 0; i < 5; i += 1) { + vision.getSyncedAvailableModelVision("acme", "second-vision-model"); + } + assert.equal(memoEntries(), 1); +}); diff --git a/tests/unit/thinking-budget-client-floor-14888.test.ts b/tests/unit/thinking-budget-client-floor-14888.test.ts index 91cc3709e3a2..312f5e86b735 100644 --- a/tests/unit/thinking-budget-client-floor-14888.test.ts +++ b/tests/unit/thinking-budget-client-floor-14888.test.ts @@ -44,3 +44,27 @@ test("still fills the floor when the client omitted the budget", () => { executor.ensureThinkingBudget(body, "kimi-k3"); assert.equal(body.max_tokens, 4096); }); + +test("keeps a caller max_tokens above the thinking floor (#14888)", () => { + const executor = new DefaultExecutor("moonshot"); + const body = { + model: "kimi-k3", + reasoning_effort: "max", + max_tokens: 32000, + } as Record; + + executor.ensureThinkingBudget(body, "kimi-k3"); + assert.equal(body.max_tokens, 32000); +}); + +test("keeps a tiny caller max_tokens instead of raising it (#14888)", () => { + const executor = new DefaultExecutor("moonshot"); + const body = { + model: "kimi-k3", + reasoning_effort: "max", + max_tokens: 64, + } as Record; + + executor.ensureThinkingBudget(body, "kimi-k3"); + assert.equal(body.max_tokens, 64); +}); diff --git a/tests/unit/tls-proxy-context.test.ts b/tests/unit/tls-proxy-context.test.ts index 8312e8b584d3..3f5509c83c98 100644 --- a/tests/unit/tls-proxy-context.test.ts +++ b/tests/unit/tls-proxy-context.test.ts @@ -49,9 +49,7 @@ async function withEnv(env: EnvState, fn: () => Promise | void): Promise Promise, -) { +function fakeTlsClient(fetch: (url: string, options?: TlsFetchOptions) => Promise) { return { available: true, fetch }; } @@ -108,7 +106,7 @@ test("circuit failures are isolated to the exact session scope and proxy", async client.fetch("https://upstream.example", { proxy: "http://bad.proxy:8080", sessionScope: "bad-account", - }), + }) ); } @@ -146,7 +144,7 @@ test("Request input bypasses wreq without losing method headers or body", async fakeTlsClient(async () => { tlsCalls++; return new Response("tls"); - }), + }) ); const input = new Request("https://upstream.example/v1", { @@ -155,12 +153,16 @@ test("Request input bypasses wreq without losing method headers or body", async body: "payload", }); const tracked = await runWithTlsTracking("codex", () => - proxyFetch(input, {}, { - undiciFetch: async (forwarded) => { - received = forwarded as Request; - return new Response("dispatcher"); - }, - }), + proxyFetch( + input, + {}, + { + undiciFetch: async (forwarded) => { + received = forwarded as Request; + return new Response("dispatcher"); + }, + } + ) ); assert.equal(tlsCalls, 0); @@ -169,7 +171,85 @@ test("Request input bypasses wreq without losing method headers or body", async assert.equal(received?.headers.get("x-test"), "present"); assert.equal(await received?.text(), "payload"); assert.equal(tracked.tlsFingerprintUsed, false); + } + ); +}); + +test("RequestInit.next does not by itself disable TLS fingerprint eligibility", async () => { + await withEnv( + { + ENABLE_TLS_FINGERPRINT: "true", + TLS_FINGERPRINT_PROVIDERS: "codex", + }, + async () => { + let tlsCalls = 0; + let forwardedNext: unknown = "unset"; + let dispatcherCalls = 0; + setTlsClientForTest( + fakeTlsClient(async (_url, options) => { + tlsCalls++; + forwardedNext = + "next" in (options ?? {}) ? (options as { next?: unknown }).next : undefined; + return new Response("tls", { status: 200 }); + }) + ); + + const init = { + method: "POST", + headers: { "content-type": "application/json" }, + body: "{}", + signal: new AbortController().signal, + next: { revalidate: 0, tags: ["chat"] }, + }; + const tracked = await runWithTlsTracking("codex", () => + proxyFetch("https://upstream.example/v1/chat/completions", init, { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("dispatcher", { status: 599 }); + }, + }) + ); + + assert.equal(tlsCalls, 1); + assert.equal(dispatcherCalls, 0); + assert.equal(tracked.tlsFingerprintUsed, true); + assert.equal((tracked.result as Response).status, 200); + assert.equal(forwardedNext, undefined); + } + ); +}); + +test("an unrecognized RequestInit key still bypasses TLS fingerprinting", async () => { + await withEnv( + { + ENABLE_TLS_FINGERPRINT: "true", + TLS_FINGERPRINT_PROVIDERS: "codex", }, + async () => { + let tlsCalls = 0; + setTlsClientForTest( + fakeTlsClient(async () => { + tlsCalls++; + return new Response("tls", { status: 200 }); + }) + ); + + const init = { + method: "POST", + headers: { "content-type": "application/json" }, + body: "{}", + cache: "no-store", + }; + const tracked = await runWithTlsTracking("codex", () => + proxyFetch("https://upstream.example/v1/chat/completions", init, { + undiciFetch: async () => new Response("dispatcher", { status: 599 }), + }) + ); + + assert.equal(tlsCalls, 0); + assert.equal(tracked.tlsFingerprintUsed, false); + assert.equal((tracked.result as Response).status, 599); + } ); }); @@ -184,7 +264,7 @@ test("non-idempotent TLS failures are never replayed", async () => { setTlsClientForTest( fakeTlsClient(async () => { throw new Error("post-send transport failure"); - }), + }) ); await assert.rejects( @@ -197,15 +277,15 @@ test("non-idempotent TLS failures are never replayed", async () => { dispatcherCalls++; return new Response("unexpected"); }, - }, - ), + } + ) ), (error: Error & { code?: string }) => error.code === "TLS_FINGERPRINT_FAILED" && - error.message === "TLS fingerprint request failed; request is not safe to replay", + error.message === "TLS fingerprint request failed; request is not safe to replay" ); assert.equal(dispatcherCalls, 0); - }, + } ); }); @@ -220,22 +300,26 @@ test("safe GET TLS failure falls back through the same configured proxy", async setTlsClientForTest( fakeTlsClient(async () => { throw new Error("transport failed"); - }), + }) ); let dispatcher: unknown; const tracked = await runWithTlsTracking("codex", () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async (_input, init) => { - dispatcher = init?.dispatcher; - return new Response("fallback"); - }, - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async (_input, init) => { + dispatcher = init?.dispatcher; + return new Response("fallback"); + }, + } + ) ); assert.ok(dispatcher); assert.equal(await tracked.result.text(), "fallback"); assert.equal(tracked.tlsFingerprintUsed, false); - }, + } ); }); @@ -252,21 +336,25 @@ test("internal TimeoutError is not classified as a caller abort", async () => { setTlsClientForTest( fakeTlsClient(async () => { throw timeout; - }), + }) ); const tracked = await runWithTlsTracking("codex", () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - dispatcherCalls++; - return new Response("fallback"); - }, - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("fallback"); + }, + } + ) ); assert.equal(dispatcherCalls, 1); assert.equal(await tracked.result.text(), "fallback"); - }, + } ); }); @@ -280,11 +368,11 @@ test("control-plane direct fallback bypasses an environment proxy", async () => const result = await runWithProxyContext( { type: "http", host: "127.0.0.1", port: "9" }, () => resolveProxyForRequest("https://upstream.example/v1"), - { directFallbackOnUnreachable: true }, + { directFallbackOnUnreachable: true } ); assert.deepEqual(result, { source: "direct", proxyUrl: null }); - }, + } ); }); @@ -302,22 +390,26 @@ test("new proxied TLS transport requires an explicit provider allowlist", async fakeTlsClient(async () => { tlsCalls++; return new Response("tls"); - }), + }) ); const tracked = await runWithTlsTracking("codex", () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - dispatcherCalls++; - return new Response("dispatcher"); - }, - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("dispatcher"); + }, + } + ) ); assert.equal(tlsCalls, 0); assert.equal(dispatcherCalls, 1); assert.equal(await tracked.result.text(), "dispatcher"); - }, + } ); }); @@ -335,7 +427,7 @@ test("caller abort propagates unchanged and never falls back", async () => { fakeTlsClient(async () => { controller.abort(abortError); throw abortError; - }), + }) ); await assert.rejects( @@ -348,13 +440,13 @@ test("caller abort propagates unchanged and never falls back", async () => { dispatcherCalls++; return new Response("unexpected"); }, - }, - ), + } + ) ), - (error) => error === abortError, + (error) => error === abortError ); assert.equal(dispatcherCalls, 0); - }, + } ); }); @@ -371,24 +463,28 @@ test("stateful TLS session failures never fall back even for GET", async () => { const error = new Error("transport failed"); Object.defineProperty(error, "sessionHadCookies", { value: true }); throw error; - }), + }) ); await assert.rejects( runWithTlsTracking("codex", () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - dispatcherCalls++; - return new Response("unexpected"); - }, - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("unexpected"); + }, + } + ) ), (error: Error & { code?: string }) => error.code === "TLS_FINGERPRINT_FAILED" && - error.message === "TLS fingerprint request failed; stateful session cannot be replayed", + error.message === "TLS fingerprint request failed; stateful session cannot be replayed" ); assert.equal(dispatcherCalls, 0); - }, + } ); }); @@ -406,23 +502,31 @@ test("TLS transport failures never expose proxy credentials", async () => { try { setTlsClientForTest( fakeTlsClient(async () => { - throw new Error( - "connect failed via http://user:password@placeholder.proxy:8080", - ); - }), + throw new Error("connect failed via http://user:password@placeholder.proxy:8080"); + }) ); const tracked = await runWithTlsTracking("codex", () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => new Response("fallback"), - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => new Response("fallback"), + } + ) ); assert.equal(await tracked.result.text(), "fallback"); - assert.equal(warnings.some((line) => line.includes("user:password")), false); - assert.equal(warnings.some((line) => line.includes("placeholder.proxy")), false); + assert.equal( + warnings.some((line) => line.includes("user:password")), + false + ); + assert.equal( + warnings.some((line) => line.includes("placeholder.proxy")), + false + ); } finally { console.warn = originalWarn; } - }, + } ); }); @@ -440,21 +544,25 @@ test("family-pinned proxies retain dispatcher enforcement instead of using wreq" fakeTlsClient(async () => { tlsCalls++; return new Response("tls"); - }), + }) ); const tracked = await runWithTlsTracking("codex", () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - dispatcherCalls++; - return new Response("dispatcher"); - }, - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("dispatcher"); + }, + } + ) ); assert.equal(tlsCalls, 0); assert.equal(dispatcherCalls, 1); assert.equal(await tracked.result.text(), "dispatcher"); - }, + } ); }); @@ -471,25 +579,27 @@ test("relay contexts never route through wreq", async () => { fakeTlsClient(async () => { tlsCalls++; return new Response("tls"); - }), + }) ); const tracked = await runWithTlsTracking("codex", () => - runWithProxyContext( - { type: "vercel", host: "relay.example", relayAuth: "test-auth" }, - () => - proxyFetch("https://upstream.example/v1", {}, { + runWithProxyContext({ type: "vercel", host: "relay.example", relayAuth: "test-auth" }, () => + proxyFetch( + "https://upstream.example/v1", + {}, + { undiciFetch: async () => { relayCalls++; return new Response("relay"); }, - }), - ), + } + ) + ) ); assert.equal(tlsCalls, 0); assert.equal(relayCalls, 1); assert.equal(await tracked.result.text(), "relay"); - }, + } ); }); @@ -613,23 +723,27 @@ test("allowlisted proxied TLS receives the exact proxy and account scope", async fakeTlsClient(async (_url, options) => { observedOptions = options; return new Response("tls"); - }), + }) ); const tracked = await runWithTlsTracking( { provider: "codex", sessionScope: "connection-123" }, () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - throw new Error("dispatcher must not run"); - }, - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + throw new Error("dispatcher must not run"); + }, + } + ) ); assert.equal(observedOptions?.proxy, "http://placeholder.proxy:8080"); assert.equal(observedOptions?.sessionScope, "connection-123"); assert.equal(tracked.tlsFingerprintUsed, true); assert.equal(await tracked.result.text(), "tls"); - }, + } ); }); @@ -646,34 +760,41 @@ test("proxy dispatcher failures sanitize logs and propagated errors", async () = try { let caught: unknown; try { - await proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - const error = new Error( - "connect failed via http://user:password@placeholder.proxy:8080", - ) as Error & { code?: string }; - error.code = "ECONNREFUSED"; - throw error; - }, - }); + await proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + const error = new Error( + "connect failed via http://user:password@placeholder.proxy:8080" + ) as Error & { code?: string }; + error.code = "ECONNREFUSED"; + throw error; + }, + } + ); } catch (error) { caught = error; } assert.ok(caught instanceof Error); // #10032: the underlying reason is kept for diagnosability, with the // proxy URL (credentials included) redacted before propagation (#9837). - assert.equal( - caught.message, - "Proxy request failed: connect failed via [redacted-proxy]", - ); + assert.equal(caught.message, "Proxy request failed: connect failed via [redacted-proxy]"); assert.equal(caught.message.includes("user:password"), false); assert.equal(caught.message.includes("placeholder.proxy"), false); assert.equal("code" in caught ? caught.code : undefined, "PROXY_UNREACHABLE"); - assert.equal(errors.some((line) => line.includes("user:password")), false); - assert.equal(errors.some((line) => line.includes("placeholder.proxy")), false); + assert.equal( + errors.some((line) => line.includes("user:password")), + false + ); + assert.equal( + errors.some((line) => line.includes("placeholder.proxy")), + false + ); } finally { console.error = originalError; } - }, + } ); }); @@ -701,7 +822,7 @@ test("half-open circuit admits only one probe for an isolated session key", asyn proxy: null, sessionScope: "connection-123", }), - /wreq-js transport failed/, + /wreq-js transport failed/ ); } probeMode = true; @@ -719,9 +840,7 @@ test("half-open circuit admits only one probe for an isolated session key", asyn sessionScope: "connection-123", }), (error: unknown) => - error instanceof Error && - "code" in error && - error.code === "TLS_CIRCUIT_OPEN", + error instanceof Error && "code" in error && error.code === "TLS_CIRCUIT_OPEN" ); assert.equal(fetchCalls, 4); probeGate.resolve(); @@ -753,9 +872,7 @@ test("circuit invalidation snapshots cookies before closing the failed session", sessionScope: "connection-123", }), (error: unknown) => - error instanceof Error && - "sessionHadCookies" in error && - error.sessionHadCookies === true, + error instanceof Error && "sessionHadCookies" in error && error.sessionHadCookies === true ); } await Promise.resolve(); @@ -770,7 +887,7 @@ test("circuit invalidation snapshots cookies before closing the failed session", "code" in error && error.code === "TLS_CIRCUIT_OPEN" && "sessionHadCookies" in error && - error.sessionHadCookies === true, + error.sessionHadCookies === true ); } finally { await client.exit(); @@ -778,10 +895,7 @@ test("circuit invalidation snapshots cookies before closing the failed session", }); test("pending session creation is bounded per TLS client", async () => { - const sessionGates = [ - Promise.withResolvers(), - Promise.withResolvers(), - ]; + const sessionGates = [Promise.withResolvers(), Promise.withResolvers()]; let creates = 0; const client = new TlsClient(() => { const gate = sessionGates[creates++]; @@ -809,9 +923,7 @@ test("pending session creation is bounded per TLS client", async () => { sessionScope: "connection-3", }), (error: unknown) => - error instanceof Error && - "code" in error && - error.code === "TLS_SESSION_CAPACITY", + error instanceof Error && "code" in error && error.code === "TLS_SESSION_CAPACITY" ); assert.equal(creates, 2); @@ -841,32 +953,36 @@ test("direct TLS fallback never auto-selects a different proxy route", async () fakeTlsClient(async () => { tlsCalls++; throw new Error("wreq transport failed"); - }), + }) ); - const response = await proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - dispatcherCalls++; - const error = new Error("fetch failed: ECONNREFUSED") as Error & { code?: string }; - error.code = "ECONNREFUSED"; - throw error; - }, - findWorkingProxy: async () => { - autoSelectCalls++; - return "http://unexpected.proxy:8080"; - }, - nativeFetch: async () => { - nativeCalls++; - return new Response("native"); - }, - }); + const response = await proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + dispatcherCalls++; + const error = new Error("fetch failed: ECONNREFUSED") as Error & { code?: string }; + error.code = "ECONNREFUSED"; + throw error; + }, + findWorkingProxy: async () => { + autoSelectCalls++; + return "http://unexpected.proxy:8080"; + }, + nativeFetch: async () => { + nativeCalls++; + return new Response("native"); + }, + } + ); assert.equal(tlsCalls, 1); assert.equal(dispatcherCalls, 2); assert.equal(autoSelectCalls, 0); assert.equal(nativeCalls, 1); assert.equal(await response.text(), "native"); - }, + } ); }); @@ -884,23 +1000,27 @@ test("proxied TLS compatibility overload requires an explicit session scope", as fakeTlsClient(async () => { tlsCalls++; return new Response("unexpected"); - }), + }) ); const tracked = await runWithTlsTracking("codex", () => - proxyFetch("https://upstream.example/v1", {}, { - undiciFetch: async () => { - dispatcherCalls++; - return new Response("dispatcher"); - }, - }), + proxyFetch( + "https://upstream.example/v1", + {}, + { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("dispatcher"); + }, + } + ) ); assert.equal(tlsCalls, 0); assert.equal(dispatcherCalls, 1); assert.equal(tracked.tlsFingerprintUsed, false); assert.equal(await tracked.result.text(), "dispatcher"); - }, + } ); }); @@ -936,7 +1056,7 @@ test("circuit trip defers session close until active response streams release", proxy: null, sessionScope: "connection-123", }), - /wreq-js transport failed/, + /wreq-js transport failed/ ); } assert.equal(closed, 0); @@ -981,14 +1101,12 @@ test("streaming wreq body failures are sanitized and counted by the circuit", as error.message === "wreq-js response body failed" && "code" in error && error.code === "UND_ERR_SOCKET" && - !String(error).includes("user:password"), + !String(error).includes("user:password") ); assert.equal( - client.getCircuitState( - "http://user:password@proxy.example:8080", - "connection-123", - ).failureCount, - 1, + client.getCircuitState("http://user:password@proxy.example:8080", "connection-123") + .failureCount, + 1 ); } finally { await client.exit(); diff --git a/tests/unit/tokenRefresh-breaker-scope.test.ts b/tests/unit/tokenRefresh-breaker-scope.test.ts new file mode 100644 index 000000000000..3b1505c9a24f --- /dev/null +++ b/tests/unit/tokenRefresh-breaker-scope.test.ts @@ -0,0 +1,164 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Scoped token-refresh breaker: in "connection" scope, failures on one +// connection must not block refresh on another connection of the same +// provider. In the default "provider" scope, behavior is unchanged. +const { isProviderBlocked, getCircuitBreakerStatus, refreshWithRetry } = + await import("../../open-sse/services/tokenRefresh/circuitBreaker.ts"); + +const silentLog = { + info() {}, + warn() {}, + error() {}, + debug() {}, +}; + +function uniqueProvider(prefix: string): string { + return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; +} + +async function failRefresh( + provider: string, + connectionId: string | undefined, + times: number, + scope: "provider" | "connection" = "connection" +): Promise { + for (let i = 0; i < times; i++) { + await refreshWithRetry(async () => null, 1, silentLog, provider, { + scope, + ...(connectionId === undefined ? {} : { connectionId }), + }); + } +} + +test("connection scope isolates failures: five failures on A do not block B", async () => { + const provider = uniqueProvider("scope-conn"); + try { + await failRefresh(provider, "conn-a", 5); + assert.equal( + isProviderBlocked(provider, { scope: "connection", connectionId: "conn-a" }), + true + ); + assert.equal( + isProviderBlocked(provider, { scope: "connection", connectionId: "conn-b" }), + false + ); + } finally { + // No reset hook: keys are unique per test run, entries expire on their own. + } +}); + +test("connection scope serves a healthy connection after a dead one tripped", async () => { + const provider = uniqueProvider("scope-serve"); + await failRefresh(provider, "dead-conn", 5); + let called = false; + const result = await refreshWithRetry( + async () => { + called = true; + return { accessToken: "healthy" }; + }, + 1, + silentLog, + provider, + { scope: "connection", connectionId: "healthy-conn" } + ); + assert.equal(called, true); + assert.equal(result?.accessToken, "healthy"); +}); + +test("default provider scope still blocks every connection after five failures", async () => { + const provider = uniqueProvider("scope-prov"); + for (let i = 0; i < 5; i++) { + await refreshWithRetry(async () => null, 1, silentLog, provider); + } + assert.equal(isProviderBlocked(provider), true); +}); + +test("connection scope without a connection id falls back to the provider key", async () => { + const provider = uniqueProvider("scope-fallback"); + await failRefresh(provider, undefined, 5); + assert.equal(isProviderBlocked(provider, { scope: "connection" }), true); + assert.equal(isProviderBlocked(provider), true); +}); + +test("blank connection id falls back to the provider key", async () => { + const provider = uniqueProvider("scope-blank"); + await failRefresh(provider, " ", 5); + assert.equal(isProviderBlocked(provider, { scope: "connection", connectionId: "other" }), false); + assert.equal(isProviderBlocked(provider), true); +}); + +test("custom threshold and cooldown apply from options", async () => { + const provider = uniqueProvider("scope-custom"); + await failRefresh(provider, "conn-a", 2, "connection"); + // Threshold is 3 here, so two failures must not trip yet. + const before = isProviderBlocked(provider, { + scope: "connection", + connectionId: "conn-a", + failureThreshold: 3, + cooldownMs: 60_000, + }); + assert.equal(before, false); + await refreshWithRetry(async () => null, 1, silentLog, provider, { + scope: "connection", + connectionId: "conn-a", + failureThreshold: 3, + cooldownMs: 60_000, + }); + assert.equal( + isProviderBlocked(provider, { + scope: "connection", + connectionId: "conn-a", + failureThreshold: 3, + cooldownMs: 60_000, + }), + true + ); +}); + +test("expired entries are purged on read so the registry returns to baseline", async () => { + const provider = uniqueProvider("scope-expiry"); + const tripOpts = { + scope: "connection" as const, + connectionId: "short-lived", + failureThreshold: 1, + cooldownMs: 50, + }; + await refreshWithRetry(async () => null, 1, silentLog, provider, tripOpts); + assert.equal(isProviderBlocked(provider, tripOpts), true); + await new Promise((r) => setTimeout(r, 150)); + assert.equal(isProviderBlocked(provider, tripOpts), false); + assert.equal( + getCircuitBreakerStatus()[`${provider}::conn::short-lived`], + undefined, + "expired entry removed on read" + ); +}); + +test("registry stays bounded and a full blocked registry refuses new inserts", async () => { + const provider = uniqueProvider("scope-cap"); + const baseline = Object.keys(getCircuitBreakerStatus()).length; + const room = Math.max(0, 500 - baseline); + const scopeOpts = (id: string) => ({ + scope: "connection" as const, + connectionId: id, + failureThreshold: 1, + cooldownMs: 30 * 60 * 1000, + }); + for (let i = 0; i < room + 100; i++) { + await refreshWithRetry(async () => null, 1, silentLog, provider, scopeOpts(`cap-${i}`)); + } + const status = getCircuitBreakerStatus(); + assert.ok( + Object.keys(status).length <= 500, + `registry capped at 500, got ${Object.keys(status).length}` + ); + assert.equal( + status[`${provider}::conn::cap-0`] !== undefined && status[`${provider}::conn::cap-0`].blocked, + true, + "blocked entries are never evicted" + ); + // The cap test runs last: it fills all 500 slots with blocked entries, so + // later inserts in this file would be refused by design (PM1). +}); diff --git a/tests/unit/tokenRefresh-circuitBreaker-codex.test.ts b/tests/unit/tokenRefresh-circuitBreaker-codex.test.ts new file mode 100644 index 000000000000..697459719fd3 --- /dev/null +++ b/tests/unit/tokenRefresh-circuitBreaker-codex.test.ts @@ -0,0 +1,128 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// A dead Codex refresh token must not trip the provider circuit breaker: +// the unrecoverable signal from the refresh endpoint must reach +// refreshWithRetry so it returns early without counting a retryable failure. + +const { isProviderBlocked, getCircuitBreakerStatus, refreshWithRetry } = + await import("../../open-sse/services/tokenRefresh/circuitBreaker.ts"); +const { isUnrecoverableRefreshError } = + await import("../../open-sse/services/tokenRefresh/shared.ts"); + +const silentLog = { + info() {}, + warn() {}, + error() {}, + debug() {}, +}; + +function randomProvider() { + return "codex-dead-token-" + Math.random().toString(36).slice(2); +} + +function stubFetchOnce(status: number, body: unknown) { + const previous = globalThis.fetch; + globalThis.fetch = (async () => + new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + })) as typeof fetch; + return () => { + globalThis.fetch = previous; + }; +} + +test("dead refresh token is classified unrecoverable and never trips the breaker", async (t) => { + const provider = randomProvider(); + const restore = stubFetchOnce(400, { + error: { code: "refresh_token_reused", message: "Token already consumed" }, + }); + t.after(restore); + const { CodexExecutor } = await import("../../open-sse/executors/codex.ts"); + const executor = new CodexExecutor(); + const deadCredentials = { + refreshToken: "dead-refresh-token", + accessToken: "stale-access-token", + }; + + let attempts = 0; + const result = await refreshWithRetry( + async () => { + attempts++; + return executor.refreshCredentials(deadCredentials, silentLog); + }, + 3, + silentLog, + provider + ); + + assert.equal(attempts, 1, "unrecoverable refresh must not be retried"); + assert.ok( + isUnrecoverableRefreshError(result), + "dead token result must stay classifiable as unrecoverable" + ); + assert.equal( + getCircuitBreakerStatus()[provider], + undefined, + "no failure recorded for an unrecoverable refresh" + ); + assert.equal(isProviderBlocked(provider), false); + + // Repeat to the trip threshold: even 5 dead-token refreshes must not arm the breaker. + for (let i = 0; i < 4; i++) { + await refreshWithRetry( + async () => executor.refreshCredentials(deadCredentials, silentLog), + 3, + silentLog, + provider + ); + } + assert.equal(isProviderBlocked(provider), false, "breaker stays open for healthy connections"); +}); + +test("retryable refresh failures still count toward the breaker", async (t) => { + const provider = randomProvider(); + const restore = stubFetchOnce(500, { error: "server_error" }); + t.after(restore); + const { CodexExecutor } = await import("../../open-sse/executors/codex.ts"); + const executor = new CodexExecutor(); + const credentials = { + refreshToken: "transiently-failing-token", + accessToken: "stale-access-token", + }; + + const result = await refreshWithRetry( + async () => executor.refreshCredentials(credentials, silentLog), + 1, + silentLog, + provider + ); + assert.equal(result, null); + assert.equal(getCircuitBreakerStatus()[provider].failures, 1); +}); + +test("healthy refresh returns fresh tokens without touching the breaker", async (t) => { + const provider = randomProvider(); + const restore = stubFetchOnce(200, { + access_token: "fresh-access-token", + refresh_token: "fresh-refresh-token", + expires_in: 3600, + }); + t.after(restore); + const { CodexExecutor } = await import("../../open-sse/executors/codex.ts"); + const executor = new CodexExecutor(); + + const result = await refreshWithRetry( + async () => + executor.refreshCredentials( + { refreshToken: "live-refresh-token", accessToken: "stale-access-token" }, + silentLog + ), + 3, + silentLog, + provider + ); + assert.equal(result.accessToken, "fresh-access-token"); + assert.equal(getCircuitBreakerStatus()[provider], undefined); +}); diff --git a/tests/unit/trae-upstream-status.test.ts b/tests/unit/trae-upstream-status.test.ts new file mode 100644 index 000000000000..88dbf8f46a60 --- /dev/null +++ b/tests/unit/trae-upstream-status.test.ts @@ -0,0 +1,197 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Import the executor directly (not via executors/index.ts) — index pulls in +// the entire provider registry and DB layer which is slow and unnecessary for +// the unit-level behavior we want to exercise here. +const { TraeExecutor } = await import("../../open-sse/executors/trae.ts"); +const { runWithAppliedProxyCapture } = await import("../../open-sse/utils/proxyFetch.ts"); +import type { AppliedProxySink } from "../../open-sse/utils/proxyFetch.ts"; + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +/** Build a Response whose body streams the given SSE frames (event/data pairs). */ +function sseResponse(frames: Array<{ event: string; data: unknown }>): Response { + const enc = new TextEncoder(); + const stream = new ReadableStream({ + start(controller) { + for (const f of frames) { + controller.enqueue(enc.encode(`event: ${f.event}\n`)); + controller.enqueue(enc.encode(`data: ${JSON.stringify(f.data)}\n\n`)); + } + controller.close(); + }, + }); + return new Response(stream, { + status: 200, + headers: { "Content-Type": "text/event-stream" }, + }); +} + +function jsonResponse(obj: unknown, status = 200): Response { + return new Response(JSON.stringify(obj), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +/** + * Install a mock fetch dispatching by URL: + * POST /chat_sessions → session create (forced status via sessionStatus) + * GET /chat_sessions/{}/events → SSE frames, forced status, or null body + * POST .../ExchangeToken → token refresh payload + * Returns { restore }. + */ +function installMockFetch({ + sessionBody, + frames, + sessionStatus = 200, + eventsStatus = 200, + eventsBodyNull = false, + exchangeStatus = 200, +}: { + sessionBody?: unknown; + frames?: Array<{ event: string; data: unknown }>; + sessionStatus?: number; + eventsStatus?: number; + eventsBodyNull?: boolean; + exchangeStatus?: number; +} = {}) { + const original = globalThis.fetch; + globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => { + const url = typeof input === "string" ? input : input.url; + void init; + if (url.includes("/ExchangeToken")) { + return jsonResponse( + { ResponseMetadata: {}, Result: { Token: "NEW", RefreshToken: "NEW_REFRESH" } }, + exchangeStatus + ); + } + if (url.includes("/chat_sessions") && url.includes("/events")) { + if (eventsBodyNull) return new Response(null, { status: eventsStatus }); + if (eventsStatus !== 200) + return new Response(`upstream refused with ${eventsStatus}`, { + status: eventsStatus, + headers: { "Content-Type": "text/plain" }, + }); + return sseResponse(frames ?? [{ event: "done", data: { status: "completed" } }]); + } + if (url.endsWith("/chat_sessions")) { + const textBody = + sessionBody !== undefined + ? String(sessionBody) + : JSON.stringify({ + code: 0, + data: { chat_session_id: "sess1", status: 2, message_id: "msg1" }, + message: "success", + }); + return new Response(textBody, { + status: sessionStatus, + headers: { "Content-Type": "application/json" }, + }); + } + throw new Error(`unexpected fetch ${url}`); + }) as typeof fetch; + return { restore: () => (globalThis.fetch = original) }; +} + +const CREDS = { + accessToken: "JWT.test.token", + providerSpecificData: { + webId: "WID", + bizUserId: "BUID", + userUniqueId: "UUID", + scope: "marscode-us", + tenant: "marscode", + region: "US-East", + }, +}; + +function executeInput() { + return { + model: "auto", + body: { messages: [{ role: "user", content: "hello" }] }, + stream: false, + credentials: CREDS, + }; +} + +// ─── Upstream status on session errors ────────────────────────────────────── + +test("session create rate limit records the upstream status on the sink", async () => { + const { restore } = installMockFetch({ sessionBody: "limited", sessionStatus: 429 }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new TraeExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, 429); + assert.equal(response.status, 502); + assert.match(await response.text(), /\[429\]/); + } finally { + restore(); + } +}); + +test("session create outage records the upstream status on the sink", async () => { + const { restore } = installMockFetch({ sessionBody: "down", sessionStatus: 503 }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new TraeExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, 503); + assert.equal(response.status, 502); + assert.match(await response.text(), /\[503\]/); + } finally { + restore(); + } +}); + +// ─── Upstream status on event stream errors ───────────────────────────────── + +test("event stream outage records the upstream status on the sink", async () => { + const { restore } = installMockFetch({ eventsStatus: 503 }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new TraeExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, 503); + assert.equal(response.status, 502); + } finally { + restore(); + } +}); + +test("event stream 2xx without a body is not an upstream failure status", async () => { + const { restore } = installMockFetch({ eventsStatus: 200, eventsBodyNull: true }); + const sink: AppliedProxySink = { proxy: null }; + try { + const { response } = await runWithAppliedProxyCapture(sink, () => + new TraeExecutor().execute(executeInput()) + ); + assert.equal(sink.upstreamStatus, undefined); + assert.equal(response.status, 502); + } finally { + restore(); + } +}); + +// ─── Token refresh stays outside dispatch accounting ──────────────────────── + +test("token refresh failure leaves the sink untouched", async () => { + const { restore } = installMockFetch({ exchangeStatus: 429 }); + const sink: AppliedProxySink = { proxy: null }; + try { + const ex = new TraeExecutor(); + await assert.rejects( + runWithAppliedProxyCapture(sink, () => + ex.refreshCredentials({ ...CREDS, refreshToken: "OLD_REFRESH" }) + ) + ); + assert.equal(sink.upstreamStatus, undefined); + } finally { + restore(); + } +}); diff --git a/tests/unit/translator-resp-openai-responses-roundtrip.test.ts b/tests/unit/translator-resp-openai-responses-roundtrip.test.ts index 7b1af302f5f3..f1a972c5e5eb 100644 --- a/tests/unit/translator-resp-openai-responses-roundtrip.test.ts +++ b/tests/unit/translator-resp-openai-responses-roundtrip.test.ts @@ -603,8 +603,8 @@ test("Chat Completions -> Responses: an upstream error arriving after a deferred undefined, "an upstream error must win over a deferred length cutoff, not surface as incomplete" ); - const failedEvent = events.find((e) => e.event === "response.completed"); - assert.ok(failedEvent, "response.completed carries the failed status in this translator"); + const failedEvent = events.find((e) => e.event === "response.failed"); + assert.ok(failedEvent, "response.failed carries the failed status in this translator"); assert.equal(failedEvent.data.response.status, "failed"); assert.ok(failedEvent.data.response.error, "must carry the upstream error"); }); diff --git a/tests/unit/translator-resp-openai-responses.test.ts b/tests/unit/translator-resp-openai-responses.test.ts index 3a42eca6243c..551cab57388e 100644 --- a/tests/unit/translator-resp-openai-responses.test.ts +++ b/tests/unit/translator-resp-openai-responses.test.ts @@ -185,7 +185,7 @@ test("OpenAI -> Responses: closing a tool call also records it in the shared sta assert.equal(recorded.function.arguments, '{"message":"hi"}'); }); -test("OpenAI -> Responses: flush on null closes text content and emits response.completed", () => { +test("OpenAI -> Responses: premature flush closes text content and emits response.failed", () => { const events = collectEvents([ { id: "chatcmpl-2", @@ -197,7 +197,7 @@ test("OpenAI -> Responses: flush on null closes text content and emits response. assert.ok(events.some((event) => event.event === "response.output_text.done")); assert.ok(events.some((event) => event.event === "response.content_part.done")); - assert.ok(events.some((event) => event.event === "response.completed")); + assert.ok(events.some((event) => event.event === "response.failed")); }); test("OpenAI -> Responses: prompt-format tags remain text by default", () => { diff --git a/tests/unit/ui/PoolMemberEgressLines.test.tsx b/tests/unit/ui/PoolMemberEgressLines.test.tsx index c250f463c83c..593fdc4550df 100644 --- a/tests/unit/ui/PoolMemberEgressLines.test.tsx +++ b/tests/unit/ui/PoolMemberEgressLines.test.tsx @@ -71,6 +71,38 @@ describe("PoolMemberEgressLines", () => { ); }); + it("shows the operator-provided line for operator observations, dated", async () => { + const { element } = await renderWith(() => + jsonResponse({ + windowHours: 24, + members: [ + { + host: "10.9.1.3", + port: 21003, + egressIp: "203.0.113.3", + at: "2026-09-21T00:00:00Z", + source: "operator", + }, + ], + }) + ); + const lines = element.querySelectorAll("p"); + expect(lines.length).toBe(1); + expect(lines[0].textContent).toBe( + 'poolMemberEgressOperator:{"host":"10.9.1.3","port":21003,"egressIp":"203.0.113.3","date":"2026-09-21T00:00:00Z"}' + ); + }); + + it("still renders bodies without a source (forward compat)", async () => { + const { element } = await renderWith(() => + jsonResponse({ + windowHours: 24, + members: [{ host: "10.9.1.4", port: 21004, egressIp: "203.0.113.4", at: "x" }], + }) + ); + expect(element.querySelectorAll("p").length).toBe(1); + }); + it("renders nothing when the route answers null", async () => { const { element } = await renderWith(() => jsonResponse(null)); expect(element.textContent).toBe(""); @@ -178,4 +210,34 @@ describe("PoolMemberEgressLines", () => { expect(text).toContain('poolSetAsideReason:{"kind":"poolSetAsideKindSlow"}'); expect(text).not.toContain('"kind":"slow"'); }); + + it("labels a region refusal instead of printing the raw kind", async () => { + const { element } = await renderWith((url: unknown) => + String(url).startsWith("/api/admin/proxy-pool-visibility") + ? jsonResponse({ + rankedBy: "health", + members: [ + { + id: "p1", + name: "pool-a", + display: "http://10.9.1.1:21001", + userMasked: null, + opaque: false, + rank: 1, + signal: "set-aside", + setAside: { + kind: "geo_blocked", + since: "2026-09-25T12:00:00.000Z", + endsAt: "2026-09-25T12:34:00.000Z", + streak: 1, + }, + }, + ], + }) + : jsonResponse(null) + ); + const text = element.textContent ?? ""; + expect(text).toContain('poolSetAsideReason:{"kind":"poolSetAsideKindGeoBlocked"}'); + expect(text).not.toContain('"kind":"geo_blocked"'); + }); }); diff --git a/tests/unit/ui/caveman-output-mode-preview.test.ts b/tests/unit/ui/caveman-output-mode-preview.test.ts new file mode 100644 index 000000000000..02112f6fba40 --- /dev/null +++ b/tests/unit/ui/caveman-output-mode-preview.test.ts @@ -0,0 +1,91 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; +import React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { NextIntlClientProvider } from "next-intl"; + +import * as outputStyles from "../../../open-sse/services/compression/outputStyles/apply.ts"; +import { resolveOutputStyleSelection } from "../../../open-sse/services/compression/outputStyles/backCompat.ts"; +import { outputStyleLanguages } from "../../../open-sse/services/compression/outputStyles/catalog.ts"; + +const { default: CavemanContextPageClient } = + await import("../../../src/app/(dashboard)/dashboard/context/caveman/CavemanContextPageClient.tsx"); + +const here = dirname(fileURLToPath(import.meta.url)); +const enMessages = JSON.parse( + readFileSync(resolve(here, "../../../src/i18n/messages/en.json"), "utf8") +); + +type Level = "lite" | "full" | "ultra"; + +// The block a chat request carries while caveman output mode is on, taken from the +// request path itself: the back-compat shim picks the selection, applyOutputStyles +// appends it as a trailing system message. +function injectedBlock(level: Level, language: string): string { + const selection = resolveOutputStyleSelection({ + cavemanOutputMode: { enabled: true, intensity: level }, + }); + const result = outputStyles.applyOutputStyles( + { messages: [{ role: "user", content: "hello" }] }, + selection, + language + ); + assert.equal(result.applied, true, `${language}/${level}: expected an injection`); + const injected = result.body.messages?.at(-1); + assert.equal(injected?.role, "system"); + return String(injected?.content); +} + +function decodeHtml(text: string): string { + return text + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(/&/g, "&"); +} + +function renderedPreview(): string { + const html = renderToStaticMarkup( + React.createElement( + NextIntlClientProvider, + { locale: "en", timeZone: "UTC", messages: enMessages }, + React.createElement(CavemanContextPageClient) + ) + ); + const match = html.match(/]*>([\s\S]*?)<\/pre>/); + assert.ok(match, "expected the output mode preview
");
+  return decodeHtml(match[1]);
+}
+
+test("caveman page previews the output-styles block a request carries", () => {
+  // A server render runs no effects, so the page shows its defaults: lite intensity,
+  // language packs off (English).
+  const preview = renderedPreview();
+  assert.ok(
+    preview.startsWith(`${outputStyles.OUTPUT_STYLE_MARKER}\n`),
+    `expected the preview to open with ${outputStyles.OUTPUT_STYLE_MARKER}, got: ${preview}`
+  );
+  assert.ok(!preview.includes("[OmniRoute Caveman Output Mode]"));
+  assert.equal(preview, injectedBlock("lite", "en"));
+});
+
+test("buildOutputStylesInstruction returns the injected block for every level and language", () => {
+  for (const language of [...outputStyleLanguages(), "hu"]) {
+    for (const level of ["lite", "full", "ultra"] as const) {
+      const selection = resolveOutputStyleSelection({
+        cavemanOutputMode: { enabled: true, intensity: level },
+      });
+      assert.equal(
+        outputStyles.buildOutputStylesInstruction(selection, language),
+        injectedBlock(level, language),
+        `${language}/${level}`
+      );
+    }
+  }
+  assert.equal(outputStyles.buildOutputStylesInstruction([], "en"), "");
+});
diff --git a/tests/unit/ui/compression-exclusions-save-errors.test.tsx b/tests/unit/ui/compression-exclusions-save-errors.test.tsx
new file mode 100644
index 000000000000..eccd8c234ea3
--- /dev/null
+++ b/tests/unit/ui/compression-exclusions-save-errors.test.tsx
@@ -0,0 +1,258 @@
+// @vitest-environment jsdom
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import ExclusionsPanel from "@/app/(dashboard)/dashboard/compression/exclusions/ExclusionsPanel";
+
+// next-intl echoes the key and ignores params, so t("saveFailed") renders "saveFailed".
+// One shared t keeps the same identity on every render, as the real hook does.
+vi.mock("next-intl", () => {
+  const t = (key: string) => key;
+  return { useTranslations: () => t, useLocale: () => "en" };
+});
+
+// The exclusions the server already holds, and what the user types over them.
+const STORED = ["openai/text-embedding-3-large"];
+const TYPED = "anthropic/*\ngpt-5-6";
+
+// An HTTP status, "offline" for a fetch that rejects because the request never reached the
+// server, or "hang" for one that never answers until its signal aborts.
+type Reply = number | "offline" | "hang";
+
+interface ServerOptions {
+  load?: Reply;
+  // Replaces the stored settings row in a 200 GET answer.
+  loadBody?: unknown;
+  // The reply to each PUT in turn; PUTs past the end succeed.
+  saves?: Reply[];
+  // The GET waits for releaseLoad() before answering.
+  holdLoad?: boolean;
+}
+
+// Stands in for GET/PUT /api/settings/compression, which answers with the settings row
+// (a PUT with the row after the write), or with { error } and the failing status.
+function startServer({ load = 200, loadBody, saves = [], holdLoad = false }: ServerOptions = {}) {
+  let stored: Record = { exclusions: STORED };
+  const puts: Array> = [];
+  let releaseLoad: () => void = () => {};
+  // Like fetch, a request that never gets a reply rejects once its signal aborts, and at once
+  // when the signal has already aborted.
+  const stall = (signal?: AbortSignal | null) =>
+    new Promise((_, rejectReply) => {
+      if (signal?.aborted) rejectReply(signal.reason);
+      else signal?.addEventListener("abort", () => rejectReply(signal.reason));
+    });
+  const answer = (reply: Exclude, okBody: () => unknown) => {
+    if (reply === "offline") throw new TypeError("Failed to fetch");
+    const body = reply === 200 ? okBody() : { error: "Internal Server Error" };
+    return new Response(JSON.stringify(body), {
+      status: reply,
+      headers: { "Content-Type": "application/json" },
+    });
+  };
+  vi.stubGlobal(
+    "fetch",
+    vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
+      const { pathname } = new URL(String(input), "http://localhost");
+      if (pathname !== "/api/settings/compression") return answer(404, () => null);
+      if (init?.method !== "PUT") {
+        if (holdLoad && load !== "hang") {
+          return new Promise((resolve) => {
+            releaseLoad = () => resolve(answer(load, () => loadBody ?? stored));
+          });
+        }
+        if (load === "hang") return stall(init?.signal);
+        return answer(load, () => loadBody ?? stored);
+      }
+      const body = JSON.parse(String(init.body));
+      puts.push(body);
+      const reply = saves[puts.length - 1] ?? 200;
+      if (reply === "hang") return stall(init?.signal);
+      return answer(reply, () => {
+        stored = { ...stored, ...body };
+        return stored;
+      });
+    })
+  );
+  // Late-bound: the stub assigns the real release only when the held GET actually runs.
+  return { puts, releaseLoad: () => releaseLoad() };
+}
+
+async function settle() {
+  await act(async () => {
+    await new Promise((resolve) => setTimeout(resolve, 0));
+  });
+}
+
+async function renderPanel() {
+  render();
+  await settle();
+  return {
+    textarea: screen.getByTestId("compression-exclusions-textarea") as HTMLTextAreaElement,
+    save: screen.getByTestId("compression-exclusions-save") as HTMLButtonElement,
+  };
+}
+
+// jsdom's AbortSignal.timeout runs on a timer the fake clock cannot reach, so route it through
+// setTimeout, which the fake clock controls.
+function routeTimeoutsThroughFakeClock() {
+  vi.spyOn(AbortSignal, "timeout").mockImplementation((ms) => {
+    const controller = new AbortController();
+    setTimeout(() => controller.abort(new DOMException("Timed out", "TimeoutError")), ms);
+    return controller.signal;
+  });
+}
+
+// Any element whose own text holds the key as a whole word, so a message with more words
+// beside it, or one mirrored into a live region, still counts. A missing text fails as an
+// assertion that names it.
+function expectShown(text: string) {
+  const matches = screen.queryAllByText(new RegExp(`\\b${text}\\b`));
+  expect(matches, `panel shows ${text}`).not.toHaveLength(0);
+}
+
+afterEach(() => {
+  vi.useRealTimers();
+  vi.unstubAllGlobals();
+});
+
+describe("ExclusionsPanel reports failed saves and loads", () => {
+  it.each<[string, Reply]>([
+    ["returns 500", 500],
+    ["cannot reach the server", "offline"],
+  ])(
+    "shows the save-failed message and keeps the typed patterns when the save %s",
+    async (_case, reply) => {
+      startServer({ saves: [reply] });
+      const { textarea, save } = await renderPanel();
+      expect(textarea.value).toBe(STORED.join("\n"));
+
+      fireEvent.change(textarea, { target: { value: TYPED } });
+      fireEvent.click(save);
+      await settle();
+
+      expectShown("saveFailed");
+      expect(textarea.value).toBe(TYPED);
+      // Screen readers skip the icon's ligature text and read only the message.
+      expect(screen.getByText("error").getAttribute("aria-hidden")).toBe("true");
+    }
+  );
+
+  it("keeps the save-failed message visible after an earlier save's success message times out", async () => {
+    vi.useFakeTimers({ shouldAdvanceTime: true });
+    startServer({ saves: [200, 500] });
+    const { textarea, save } = await renderPanel();
+    expect(textarea.value).toBe(STORED.join("\n"));
+
+    fireEvent.change(textarea, { target: { value: TYPED } });
+    fireEvent.click(save);
+    await settle();
+    expectShown("compressionExclusionsSaved");
+
+    fireEvent.click(save);
+    await settle();
+    expectShown("saveFailed");
+
+    // The first save's success timeout, whatever its length, fires after the second save failed.
+    await act(() => vi.runOnlyPendingTimersAsync());
+    expectShown("saveFailed");
+  });
+
+  it("keeps a second save's saved message up for its full time", async () => {
+    vi.useFakeTimers({ shouldAdvanceTime: true });
+    startServer();
+    const { textarea, save } = await renderPanel();
+    fireEvent.change(textarea, { target: { value: TYPED } });
+
+    fireEvent.click(save);
+    await settle();
+    await act(() => vi.advanceTimersByTimeAsync(1500));
+    fireEvent.click(save);
+    await settle();
+
+    // Past the point where the first save's message would have gone.
+    await act(() => vi.advanceTimersByTimeAsync(1000));
+    expectShown("compressionExclusionsSaved");
+
+    await act(() => vi.advanceTimersByTimeAsync(2000));
+    expect(screen.queryAllByText(/\bcompressionExclusionsSaved\b/)).toHaveLength(0);
+  });
+
+  it("announces the saved message in a status region that is there before the save", async () => {
+    startServer();
+    const { textarea, save } = await renderPanel();
+    const status = screen.getByRole("status");
+    expect(status.textContent).toBe("");
+
+    fireEvent.change(textarea, { target: { value: TYPED } });
+    fireEvent.click(save);
+    await settle();
+
+    expect(status.textContent).toBe("compressionExclusionsSaved");
+  });
+
+  it("shows no count while the stored list is still loading", async () => {
+    const server = startServer({ holdLoad: true });
+    const { textarea } = await renderPanel();
+    expect(textarea.disabled, "textarea disabled while loading").toBe(true);
+    const count = screen.getByTestId("compression-exclusions-count");
+    expect(count.textContent).toBe("");
+
+    server.releaseLoad();
+    await settle();
+    expect(textarea.disabled).toBe(false);
+    expect(count.textContent).toBe("compressionExclusionsCount");
+  });
+
+  it("a save that never gets a reply gives up and says the save failed", async () => {
+    vi.useFakeTimers({ shouldAdvanceTime: true });
+    routeTimeoutsThroughFakeClock();
+    startServer({ saves: ["hang"] });
+    const { textarea, save } = await renderPanel();
+    fireEvent.change(textarea, { target: { value: TYPED } });
+
+    fireEvent.click(save);
+    await act(async () => {
+      await vi.advanceTimersByTimeAsync(15_001);
+    });
+
+    expectShown("saveFailed");
+    expect(save.disabled, "Save usable again after the save gave up").toBe(false);
+  });
+
+  it("a load that never gets a reply gives up, says so, and keeps the editor locked", async () => {
+    vi.useFakeTimers({ shouldAdvanceTime: true });
+    routeTimeoutsThroughFakeClock();
+    startServer({ load: "hang" });
+    render();
+
+    await act(async () => {
+      await vi.advanceTimersByTimeAsync(15_001);
+    });
+
+    expectShown("failedLoadWithStatus");
+    const textarea = screen.getByTestId("compression-exclusions-textarea") as HTMLTextAreaElement;
+    expect(textarea.disabled, "textarea locked after the load gave up").toBe(true);
+  });
+
+  it.each<[string, ServerOptions]>([
+    ["returns 500", { load: 500 }],
+    ["cannot reach the server", { load: "offline" }],
+    ["answers without an exclusions list", { loadBody: {} }],
+  ])(
+    "shows the load-failed message and blocks editing and saving when loading %s",
+    async (_case, options) => {
+      const server = startServer(options);
+      const { textarea, save } = await renderPanel();
+
+      expectShown("failedLoadWithStatus");
+      expect(screen.getByText("error").getAttribute("aria-hidden")).toBe("true");
+      expect(textarea.disabled, "textarea disabled after a failed load").toBe(true);
+      expect(save.disabled, "Save disabled after a failed load").toBe(true);
+
+      // A Save now would store an empty list over the exclusions the server still holds.
+      fireEvent.click(save);
+      await settle();
+      expect(server.puts, "PUTs sent after a failed load").toEqual([]);
+    }
+  );
+});
diff --git a/tests/unit/ui/compression-settings-tab-partial-save.test.tsx b/tests/unit/ui/compression-settings-tab-partial-save.test.tsx
index 6386b34c5b14..b737cc710ace 100644
--- a/tests/unit/ui/compression-settings-tab-partial-save.test.tsx
+++ b/tests/unit/ui/compression-settings-tab-partial-save.test.tsx
@@ -75,10 +75,19 @@ const AGGRESSIVE = {
   minSavingsThreshold: 0.05,
 };
 
+// The rule packs the caveman page lists.
+const PACKS = [
+  { language: "en", ruleCount: 3 },
+  { language: "es", ruleCount: 2 },
+  { language: "fr", ruleCount: 1 },
+];
+
 // Stands in for the compression settings route. Like updateCompressionSettings, a PUT
-// overwrites every key in its body. /api/context/caveman/config re-exports the same handler.
+// overwrites every key in its body, except that it merges a partial cavemanOutputMode into
+// the stored one. /api/context/caveman/config re-exports the same handler.
 function startServer(failPut: (body: Settings) => boolean = () => false) {
   let stored: Settings = JSON.parse(JSON.stringify(STORED));
+  let readsFail: false | "status" | "throw" = false;
   const puts: Settings[] = [];
   const respond = (data: unknown, status = 200) =>
     new Response(JSON.stringify(data), {
@@ -90,15 +99,25 @@ function startServer(failPut: (body: Settings) => boolean = () => false) {
     vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
       const { pathname } = new URL(String(input), "http://localhost");
       if (pathname === "/api/settings/compression" || pathname === "/api/context/caveman/config") {
-        if (init?.method !== "PUT") return respond(stored);
+        if (init?.method !== "PUT") {
+          if (readsFail === "throw") throw new TypeError("Failed to fetch");
+          return readsFail ? respond({ error: "Load failed" }, 500) : respond(stored);
+        }
         const body = JSON.parse(String(init.body)) as Settings;
         puts.push(body);
         if (failPut(body)) return respond({ error: "Save failed" }, 500);
-        stored = { ...stored, ...body };
+        const outputMode = body.cavemanOutputMode as Settings | undefined;
+        stored = {
+          ...stored,
+          ...body,
+          ...(outputMode && {
+            cavemanOutputMode: { ...(stored.cavemanOutputMode as Settings), ...outputMode },
+          }),
+        };
         return respond(stored);
       }
       if (pathname === "/api/compression/rules") return respond({ rules: [] });
-      if (pathname === "/api/compression/language-packs") return respond({ packs: [] });
+      if (pathname === "/api/compression/language-packs") return respond({ packs: PACKS });
       return respond(null, 404);
     })
   );
@@ -111,6 +130,10 @@ function startServer(failPut: (body: Settings) => boolean = () => false) {
     write(patch: Settings) {
       stored = { ...stored, ...patch };
     },
+    // Every later read of the settings row fails, with an error status or a network error.
+    failReads(how: "status" | "throw" = "status") {
+      readsFail = how;
+    },
   };
 }
 
@@ -126,12 +149,6 @@ function inputFor(labelKey: string): HTMLInputElement {
   return input;
 }
 
-function buttonFor(labelKey: string): HTMLButtonElement {
-  const button = screen.getByText(labelKey).closest("label")?.querySelector("button");
-  if (!button) throw new Error(`no button next to ${labelKey}`);
-  return button;
-}
-
 async function renderTab() {
   render();
   await settle();
@@ -171,7 +188,7 @@ describe("CompressionSettingsTab saves only what changed", () => {
 
   // Rendering the whole caveman page takes over 5 seconds on a cold run.
   it(
-    "keeps Auto-Clarity off on the caveman page when the embedded tab saves",
+    "shows one Auto-Clarity control on the caveman page and keeps it off when the embedded tab saves",
     { timeout: 30_000 },
     async () => {
       const server = startServer();
@@ -180,6 +197,11 @@ describe("CompressionSettingsTab saves only what changed", () => {
       fireEvent.click(screen.getByText("advancedMode"));
       await settle();
 
+      // This heading renders only once the embedded tab has loaded the stored row. A second
+      // Auto-Clarity control would carry another label containing "clarity".
+      expect(screen.getByText("compressionGeneral")).toBeTruthy();
+      expect(screen.getAllByText(/clarity/i)).toHaveLength(1);
+
       const autoClarity = screen.getByLabelText("autoClarity") as HTMLInputElement;
       expect(autoClarity.checked).toBe(true);
       fireEvent.click(autoClarity);
@@ -195,6 +217,155 @@ describe("CompressionSettingsTab saves only what changed", () => {
     }
   );
 
+  it(
+    "keeps output-mode fields saved in another tab when the caveman page toggles Auto-Clarity",
+    { timeout: 30_000 },
+    async () => {
+      const server = startServer();
+      render();
+      await settle();
+      // The panel turns output mode off and changes its level after this page loaded.
+      server.write({
+        cavemanOutputMode: { enabled: false, intensity: "ultra", autoClarity: true },
+      });
+
+      fireEvent.click(screen.getByLabelText("autoClarity"));
+      await settle();
+
+      // The page names only the field it changes, and the store merges it into its row.
+      expect(server.puts.at(-1)).toEqual({ cavemanOutputMode: { autoClarity: false } });
+      expect(server.stored.cavemanOutputMode).toEqual({
+        enabled: false,
+        intensity: "ultra",
+        autoClarity: false,
+      });
+    }
+  );
+
+  it(
+    "keeps language settings saved in another tab when the caveman page changes one",
+    { timeout: 30_000 },
+    async () => {
+      const server = startServer();
+      render();
+      await settle();
+      // Another tab turns packs on, picks a default language, and enables Spanish.
+      server.write({
+        languageConfig: {
+          enabled: true,
+          defaultLanguage: "pt-BR",
+          autoDetect: true,
+          enabledPacks: ["en", "es"],
+        },
+      });
+
+      fireEvent.click(screen.getByLabelText("autoDetect"));
+      await settle();
+
+      expect(server.stored.languageConfig).toEqual({
+        enabled: true,
+        defaultLanguage: "pt-BR",
+        autoDetect: false,
+        enabledPacks: ["en", "es"],
+      });
+    }
+  );
+
+  it(
+    "keeps packs enabled in another tab when the caveman page toggles one",
+    { timeout: 30_000 },
+    async () => {
+      const server = startServer();
+      render();
+      await settle();
+      server.write({
+        languageConfig: {
+          enabled: true,
+          defaultLanguage: "en",
+          autoDetect: true,
+          enabledPacks: ["en", "es"],
+        },
+      });
+
+      fireEvent.click(screen.getByLabelText("fr - rulesCount"));
+      await settle();
+
+      expect(server.stored.languageConfig).toEqual({
+        enabled: true,
+        defaultLanguage: "en",
+        autoDetect: true,
+        enabledPacks: ["en", "es", "fr"],
+      });
+    }
+  );
+
+  it(
+    "keeps English when the caveman page turns another pack off",
+    { timeout: 30_000 },
+    async () => {
+      const server = startServer();
+      server.write({
+        languageConfig: {
+          enabled: true,
+          defaultLanguage: "en",
+          autoDetect: false,
+          enabledPacks: ["en", "es", "fr"],
+        },
+      });
+      render();
+      await settle();
+
+      fireEvent.click(screen.getByLabelText("es - rulesCount"));
+      await settle();
+
+      expect(server.stored.languageConfig).toMatchObject({ enabledPacks: ["en", "fr"] });
+    }
+  );
+
+  it(
+    "sends no language save when the caveman page cannot read the settings row",
+    { timeout: 30_000 },
+    async () => {
+      const server = startServer();
+      server.failReads();
+      render();
+      await settle();
+
+      // The page shows its built-in defaults; a save built from them would overwrite the row.
+      fireEvent.click(screen.getByLabelText("autoDetect"));
+      await settle();
+
+      expect(server.puts).toEqual([]);
+    }
+  );
+
+  for (const how of ["status", "throw"] as const) {
+    it(
+      `sends no language save when the caveman page cannot re-read the row it loaded (${how})`,
+      { timeout: 30_000 },
+      async () => {
+        const server = startServer();
+        server.write({
+          languageConfig: {
+            enabled: true,
+            defaultLanguage: "en",
+            autoDetect: true,
+            enabledPacks: ["en", "es"],
+          },
+        });
+        render();
+        await settle();
+        // The page holds a loaded copy; a save built from that copy could be stale.
+        server.failReads(how);
+
+        fireEvent.click(screen.getByLabelText("autoDetect"));
+        await settle();
+
+        expect(server.puts).toEqual([]);
+      }
+    );
+  }
+
   it("leaves outputStyles saved from another tab in place", async () => {
     const server = startServer();
     await renderTab();
@@ -207,20 +378,6 @@ describe("CompressionSettingsTab saves only what changed", () => {
     expect(server.puts.at(-1)).toEqual({ cacheMinutes: 10 });
   });
 
-  it("keeps an output-mode field changed elsewhere when the tab toggles Auto-Clarity", async () => {
-    const server = startServer();
-    await renderTab();
-    // The panel changes the output-mode level after this tab loaded.
-    server.write({ cavemanOutputMode: { enabled: true, intensity: "ultra", autoClarity: true } });
-
-    fireEvent.click(buttonFor("compressionSettingsAutoClarityBypass"));
-    await settle();
-
-    expect(server.puts.at(-1)).toEqual({
-      cavemanOutputMode: { enabled: true, intensity: "ultra", autoClarity: false },
-    });
-  });
-
   it("fills the rest of a nested save from the stored row, two levels down", async () => {
     const server = startServer();
     server.write({ defaultMode: "aggressive", aggressive: AGGRESSIVE });
@@ -300,3 +457,84 @@ describe("CompressionSettingsTab saves only what changed", () => {
     expect(screen.getByText("saveFailed")).toBeTruthy();
   });
 });
+
+describe("CompressionSettingsTab when the settings GET fails", () => {
+  // The tab's first request is the settings GET, and it fails as it does while the server
+  // restarts. Later requests, including a retried GET, reach the stored row.
+  const FAILURES = {
+    "a 500": async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 }),
+    "a network error": async () => {
+      throw new TypeError("Failed to fetch");
+    },
+  };
+
+  // Holds the next request open until the returned function lets `answer` reply to it.
+  function holdNextRequest(answer: typeof fetch) {
+    let release!: () => Promise;
+    vi.mocked(fetch).mockImplementationOnce(
+      (input, init) =>
+        new Promise((resolve) => {
+          release = async () => {
+            resolve(answer(input, init));
+            await settle();
+          };
+        })
+    );
+    return () => release();
+  }
+
+  it.each(Object.keys(FAILURES) as Array)(
+    "shows a retry in place of default settings after %s",
+    async (failure) => {
+      startServer();
+      vi.mocked(fetch).mockImplementationOnce(FAILURES[failure]);
+      await renderTab();
+
+      // The defaults would report every compression layer as off.
+      expect(screen.queryByText("tokenSaverTitle")).toBeNull();
+      expect(screen.getByText(/failedToLoad/)).toBeTruthy();
+      expect(screen.getByText("retry")).toBeTruthy();
+    }
+  );
+
+  it("shows loading while Retry reloads, then the stored settings", async () => {
+    startServer();
+    vi.mocked(fetch).mockImplementationOnce(FAILURES["a 500"]);
+    await renderTab();
+
+    // The retried GET stays open, and the form stays back until it answers.
+    const release = holdNextRequest(vi.mocked(fetch).getMockImplementation()!);
+    fireEvent.click(screen.getByText("retry"));
+    await settle();
+    expect(screen.getByText("loading")).toBeTruthy();
+    expect(screen.queryByText("compressionCacheTTL")).toBeNull();
+
+    await release();
+    expect(screen.queryByText(/failedToLoad/)).toBeNull();
+    // The stored row has compression enabled, which the defaults do not.
+    expect(inputFor("compressionCacheTTL")).toBeTruthy();
+  });
+
+  it("ignores rules from the load that a retry superseded", async () => {
+    startServer();
+    vi.mocked(fetch).mockImplementationOnce(FAILURES["a 500"]);
+    // The first load's rules GET answers only after the retried load finished.
+    const staleRule = {
+      name: "stale_rule",
+      category: "filler",
+      context: "all",
+      minIntensity: "lite",
+      description: "from the superseded load",
+    };
+    const answerStale = holdNextRequest(
+      async () => new Response(JSON.stringify({ rules: [staleRule] }), { status: 200 })
+    );
+    await renderTab();
+    fireEvent.click(screen.getByText("retry"));
+    await settle();
+    expect(inputFor("compressionCacheTTL")).toBeTruthy();
+
+    await answerStale();
+    expect(screen.queryByText("stale rule")).toBeNull();
+  });
+});
diff --git a/tests/unit/ui/compressionHub-active-selector.test.tsx b/tests/unit/ui/compressionHub-active-selector.test.tsx
index c189ded9e05c..5862c7d665e3 100644
--- a/tests/unit/ui/compressionHub-active-selector.test.tsx
+++ b/tests/unit/ui/compressionHub-active-selector.test.tsx
@@ -7,6 +7,9 @@ import messages from "../../../src/i18n/messages/en.json";
 
 const containers: HTMLElement[] = [];
 const roots: Array<{ unmount: () => void }> = [];
+// Settings PUTs a test is holding. Every mounted Hub shares one save queue, so a PUT a test
+// leaves unanswered would stall the next test's load.
+const heldPuts: Array<() => void> = [];
 
 function mount(ui: React.ReactElement): HTMLElement {
   const container = document.createElement("div");
@@ -31,6 +34,9 @@ beforeEach(() => {
 });
 
 afterEach(async () => {
+  // Answer every held PUT while the fetch mock is still in place, so the queue drains first.
+  await releaseInOrder(heldPuts);
+  heldPuts.length = 0;
   vi.restoreAllMocks();
   await act(async () => {
     while (roots.length > 0) roots.pop()?.unmount();
@@ -51,12 +57,20 @@ interface CapturedPut {
   body: Record;
 }
 
-function setupFetchMock(): { puts: CapturedPut[] } {
+// A stand-in for the route: GET answers with the stored settings, and a PUT that succeeds
+// replaces each top-level key it carries, as updateCompressionSettings does. failPutKeys: a PUT
+// whose body carries one of these keys gets a 500 and stores nothing. hold: each PUT waits until
+// the test calls its entry in `held`, so the test decides the order in which saves answer.
+function setupFetchMock(opts: { failPutKeys?: string[]; hold?: boolean } = {}): {
+  puts: CapturedPut[];
+  held: Array<() => void>;
+  server: Record;
+} {
   const puts: CapturedPut[] = [];
   const json = (body: unknown, status = 200) =>
     new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
 
-  const initialConfig = {
+  const server: Record = {
     enabled: true,
     defaultMode: "off",
     autoTriggerTokens: 0,
@@ -66,7 +80,10 @@ function setupFetchMock(): { puts: CapturedPut[] } {
     activeComboId: null,
     contextEditing: { enabled: false },
   };
-  const combos = [{ id: "c1", name: "RTK only", pipeline: [{ engine: "rtk" }] }];
+  const combos = [
+    { id: "c1", name: "RTK only", pipeline: [{ engine: "rtk" }] },
+    { id: "c2", name: "Caveman only", pipeline: [{ engine: "caveman" }] },
+  ];
 
   vi.spyOn(globalThis, "fetch").mockImplementation(
     async (input: RequestInfo | URL, init?: RequestInit) => {
@@ -79,14 +96,17 @@ function setupFetchMock(): { puts: CapturedPut[] } {
         if (method === "PUT") {
           const body = JSON.parse(String(init?.body ?? "{}"));
           puts.push({ url, body });
-          return json({ ...initialConfig, ...body });
+          if (opts.hold) await new Promise((release) => heldPuts.push(release));
+          if (opts.failPutKeys?.some((key) => key in body)) return json({ error: "rejected" }, 500);
+          Object.assign(server, body);
+          return json(server);
         }
-        return json(initialConfig);
+        return json(server);
       }
       return json({}, 404);
     }
   );
-  return { puts };
+  return { puts, held: heldPuts, server };
 }
 
 function setSelectValue(select: HTMLSelectElement, value: string) {
@@ -95,18 +115,36 @@ function setSelectValue(select: HTMLSelectElement, value: string) {
   select.dispatchEvent(new Event("change", { bubbles: true }));
 }
 
-describe("CompressionHub — active-profile selector", () => {
-  async function render() {
-    const { default: CompressionHub } =
-      await import("../../../src/app/(dashboard)/dashboard/context/combos/CompressionHub");
-    let container!: HTMLElement;
-    await act(async () => {
-      container = mount();
-    });
-    await flush();
-    return container;
+const saveFailed = messages.contextCombos.saveSettingsFailed;
+
+function activeProfileSelect(container: HTMLElement) {
+  return container.querySelector('[data-testid="active-profile-select"]') as HTMLSelectElement;
+}
+
+function contextEditingToggle(container: HTMLElement) {
+  return container.querySelector('button[role="switch"]') as HTMLButtonElement;
+}
+
+// Answers the held PUTs in the order they went out, including any sent while answering.
+async function releaseInOrder(held: Array<() => void>) {
+  for (let i = 0; i < held.length; i++) {
+    await act(async () => held[i]());
+    for (let j = 0; j < 5; j++) await flush();
   }
+}
+
+async function render() {
+  const { default: CompressionHub } =
+    await import("../../../src/app/(dashboard)/dashboard/context/combos/CompressionHub");
+  let container!: HTMLElement;
+  await act(async () => {
+    container = mount();
+  });
+  await flush();
+  return container;
+}
 
+describe("CompressionHub — active-profile selector", () => {
   it("renders the active-profile select with Default + each named combo", async () => {
     setupFetchMock();
     const container = await render();
@@ -159,3 +197,250 @@ describe("CompressionHub — active-profile selector", () => {
     expect(container.textContent).not.toContain("Summary plus aging");
   });
 });
+
+describe("CompressionHub overlapping saves", () => {
+  // The select and the toggle stay enabled while a save is in flight, so a second save can start
+  // before the first one answers.
+  async function changeProfileThenToggle(failPutKeys: string[]) {
+    const { puts, held, server } = setupFetchMock({ failPutKeys, hold: true });
+    const container = await render();
+    await act(async () => {
+      setSelectValue(activeProfileSelect(container), "c1");
+    });
+    await act(async () => {
+      contextEditingToggle(container).click();
+    });
+    await releaseInOrder(held);
+    return { container, puts, server };
+  }
+
+  it("a failed save rolls back only its own field and keeps a later save the server stored", async () => {
+    const { container, puts, server } = await changeProfileThenToggle(["activeComboId"]);
+
+    expect(puts.map((p) => p.body)).toEqual([
+      { activeComboId: "c1" },
+      { contextEditing: { enabled: true } },
+    ]);
+    expect(server).toMatchObject({ activeComboId: null, contextEditing: { enabled: true } });
+    expect(activeProfileSelect(container).value).toBe("");
+    expect(contextEditingToggle(container).getAttribute("aria-checked")).toBe("true");
+    // The later save's success leaves the earlier failure on screen.
+    expect(container.textContent).toContain(saveFailed);
+  });
+
+  it("a later failed save does not bring back an earlier value the server rejected", async () => {
+    const { container, server } = await changeProfileThenToggle([
+      "activeComboId",
+      "contextEditing",
+    ]);
+
+    expect(server).toMatchObject({ activeComboId: null, contextEditing: { enabled: false } });
+    expect(activeProfileSelect(container).value).toBe("");
+    expect(contextEditingToggle(container).getAttribute("aria-checked")).toBe("false");
+    expect(container.textContent).toContain(saveFailed);
+  });
+
+  it("a later failed save keeps an earlier save the server stored", async () => {
+    const { container, server } = await changeProfileThenToggle(["contextEditing"]);
+
+    expect(server).toMatchObject({ activeComboId: "c1", contextEditing: { enabled: false } });
+    expect(activeProfileSelect(container).value).toBe("c1");
+    expect(contextEditingToggle(container).getAttribute("aria-checked")).toBe("false");
+    expect(container.textContent).toContain(saveFailed);
+  });
+
+  it("skips a queued save that a later queued save replaces", async () => {
+    const { puts, held, server } = setupFetchMock({ hold: true });
+    const container = await render();
+    for (const value of ["c1", "c2", ""]) {
+      await act(async () => {
+        setSelectValue(activeProfileSelect(container), value);
+      });
+    }
+    await releaseInOrder(held);
+
+    // "c1" went out at once; "c2" was still queued when "" replaced it.
+    expect(puts.map((p) => p.body)).toEqual([{ activeComboId: "c1" }, { activeComboId: null }]);
+    expect(server.activeComboId).toBeNull();
+    expect(activeProfileSelect(container).value).toBe("");
+  });
+
+  it("a Hub mounted while another Hub still has saves queued shows what the server stored", async () => {
+    const { held, server } = setupFetchMock({ hold: true });
+    const first = await render();
+    await act(async () => {
+      setSelectValue(activeProfileSelect(first), "c1");
+    });
+    await act(async () => {
+      contextEditingToggle(first).click();
+    });
+    // Leave the page while the profile save is in flight and the toggle save is queued.
+    await act(async () => {
+      roots.pop()?.unmount();
+    });
+    const second = await render();
+    await releaseInOrder(held);
+
+    expect(server).toMatchObject({ activeComboId: "c1", contextEditing: { enabled: true } });
+    expect(activeProfileSelect(second).value).toBe("c1");
+    expect(contextEditingToggle(second).getAttribute("aria-checked")).toBe("true");
+  });
+
+  it("a save made in a new Hub lands after the saves an unmounted Hub still had queued", async () => {
+    const { held, server } = setupFetchMock({ hold: true });
+    const first = await render();
+    for (const value of ["c1", "c2"]) {
+      await act(async () => {
+        setSelectValue(activeProfileSelect(first), value);
+      });
+    }
+    await act(async () => {
+      roots.pop()?.unmount();
+    });
+    const second = await render();
+    // The new Hub loads only after the queued saves land, so no save of its own can go first.
+    expect(activeProfileSelect(second)).toBeNull();
+
+    await releaseInOrder(held);
+    expect(activeProfileSelect(second).value).toBe("c2");
+    await act(async () => {
+      setSelectValue(activeProfileSelect(second), "c1");
+    });
+    await releaseInOrder(held);
+
+    expect(server.activeComboId).toBe("c1");
+    expect(activeProfileSelect(second).value).toBe("c1");
+  });
+
+  it("fails a PUT that outlives the save timeout and sends the save queued behind it", async () => {
+    const { puts, server } = setupFetchMock();
+    const respond = vi.mocked(globalThis.fetch).getMockImplementation()!;
+    const timeouts: AbortController[] = [];
+    const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockImplementation(() => {
+      const controller = new AbortController();
+      timeouts.push(controller);
+      return controller.signal;
+    });
+    // The first settings PUT never reaches the route; only its timeout signal ends it. afterEach
+    // rejects it if the test stops before the abort.
+    let sent = 0;
+    vi.mocked(globalThis.fetch).mockImplementation(async (input, init) => {
+      if (init?.method === "PUT" && ++sent === 1) {
+        await new Promise((_, reject) => {
+          heldPuts.push(() => reject(new Error("test ended")));
+          init.signal?.addEventListener("abort", () => reject(init.signal?.reason));
+        });
+      }
+      return respond(input, init);
+    });
+    const container = await render();
+    await act(async () => {
+      setSelectValue(activeProfileSelect(container), "c1");
+    });
+    await act(async () => {
+      contextEditingToggle(container).click();
+    });
+    await flush();
+    expect(sent, "the toggle save waits behind the stalled PUT").toBe(1);
+
+    await act(async () => timeouts[0].abort());
+    for (let i = 0; i < 5; i++) await flush();
+
+    expect(timeoutSpy).toHaveBeenCalledWith(15_000);
+    expect(puts.map((p) => p.body)).toEqual([{ contextEditing: { enabled: true } }]);
+    expect(server).toMatchObject({ activeComboId: null, contextEditing: { enabled: true } });
+    expect(activeProfileSelect(container).value).toBe("");
+    expect(contextEditingToggle(container).getAttribute("aria-checked")).toBe("true");
+    expect(container.textContent).toContain(saveFailed);
+  });
+
+  it("clears the error when the user makes the next edit", async () => {
+    const { held } = setupFetchMock({ failPutKeys: ["activeComboId"], hold: true });
+    const container = await render();
+    await act(async () => {
+      setSelectValue(activeProfileSelect(container), "c1");
+    });
+    await releaseInOrder(held);
+    expect(container.textContent).toContain(saveFailed);
+
+    await act(async () => {
+      contextEditingToggle(container).click();
+    });
+    expect(container.textContent).not.toContain(saveFailed);
+    await releaseInOrder(held);
+    expect(container.textContent).not.toContain(saveFailed);
+  });
+
+  it("clears the error once a later save stores the field that failed", async () => {
+    const failPutKeys = ["activeComboId"];
+    const { held, server } = setupFetchMock({ failPutKeys, hold: true });
+    const container = await render();
+    for (const value of ["c1", "c2"]) {
+      await act(async () => {
+        setSelectValue(activeProfileSelect(container), value);
+      });
+    }
+    // "c1" fails while "c2" waits behind it; then the route accepts "c2".
+    await act(async () => held[0]());
+    for (let i = 0; i < 5; i++) await flush();
+    expect(container.textContent).toContain(saveFailed);
+    failPutKeys.length = 0;
+    await releaseInOrder(held);
+
+    expect(server.activeComboId).toBe("c2");
+    expect(activeProfileSelect(container).value).toBe("c2");
+    expect(container.textContent).not.toContain(saveFailed);
+  });
+
+  it("asks before the page unloads while a save is still queued or in flight", async () => {
+    const { held } = setupFetchMock({ hold: true });
+    const container = await render();
+    const leaveIsBlocked = () => {
+      const event = new Event("beforeunload", { cancelable: true });
+      window.dispatchEvent(event);
+      return event.defaultPrevented;
+    };
+    expect(leaveIsBlocked()).toBe(false);
+
+    await act(async () => {
+      setSelectValue(activeProfileSelect(container), "c1");
+    });
+    await act(async () => {
+      contextEditingToggle(container).click();
+    });
+    expect(leaveIsBlocked()).toBe(true);
+
+    await releaseInOrder(held);
+    expect(leaveIsBlocked()).toBe(false);
+  });
+
+  it("an unmounted Hub sends no settings GET after the queue it waited on drains", async () => {
+    const { held } = setupFetchMock({ hold: true });
+    const respond = vi.mocked(globalThis.fetch).getMockImplementation()!;
+    let settingGets = 0;
+    vi.mocked(globalThis.fetch).mockImplementation(async (input, init) => {
+      const isSettingGet =
+        String(input).includes("/api/settings/compression") &&
+        (init?.method ?? "GET").toUpperCase() === "GET";
+      if (isSettingGet) settingGets++;
+      return respond(input, init);
+    });
+    // A first Hub leaves a save in flight; a second Hub mounts and waits for the queue.
+    const first = await render();
+    await act(async () => {
+      setSelectValue(activeProfileSelect(first), "c1");
+    });
+    await act(async () => {
+      roots.pop()?.unmount();
+    });
+    const second = await render();
+    expect(activeProfileSelect(second)).toBeNull();
+    await act(async () => {
+      roots.pop()?.unmount();
+    });
+    const before = settingGets;
+
+    await releaseInOrder(held);
+    expect(settingGets).toBe(before);
+  });
+});
diff --git a/tests/unit/ui/compressionHub.test.tsx b/tests/unit/ui/compressionHub.test.tsx
index 20674e153bad..964a161ed628 100644
--- a/tests/unit/ui/compressionHub.test.tsx
+++ b/tests/unit/ui/compressionHub.test.tsx
@@ -200,3 +200,102 @@ describe("CompressionCombosPageClient", () => {
     expect(text).toContain("Named combos");
   });
 });
+
+describe("CompressionHub when the settings GET fails", () => {
+  // The settings GET fails as it does while the server restarts; the combos GET keeps
+  // answering. A failed load must offer a retry instead of the default-profile view,
+  // whose select and Context Editing toggle would save the defaults over the stored row.
+  const FAILURES = {
+    "a 500": async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 }),
+    "a network error": async () => {
+      throw new TypeError("Failed to fetch");
+    },
+  };
+
+  function failSettingsGet(failure: keyof typeof FAILURES) {
+    const json = (body: unknown, status = 200) =>
+      new Response(JSON.stringify(body), {
+        status,
+        headers: { "Content-Type": "application/json" },
+      });
+    vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => {
+      const url = input.toString();
+      if (url.includes("/api/settings/compression")) return FAILURES[failure]();
+      if (url.includes("/api/context/combos")) return json({ combos: [] });
+      return json({}, 404);
+    });
+  }
+
+  async function mountHub() {
+    const { default: CompressionHub } =
+      await import("../../../src/app/(dashboard)/dashboard/context/combos/CompressionHub");
+    let container!: HTMLElement;
+    await act(async () => {
+      container = mountInContainer();
+    });
+    await flush();
+    return container;
+  }
+
+  function retryButton(container: HTMLElement) {
+    return Array.from(container.querySelectorAll("button")).find(
+      (button) => button.textContent === "Retry"
+    );
+  }
+
+  it.each(Object.keys(FAILURES) as Array)(
+    "shows a retry in place of the default-profile view after %s",
+    { timeout: 20000 },
+    async (failure) => {
+      failSettingsGet(failure);
+      const container = await mountHub();
+
+      expect(container.querySelector('[data-testid="active-profile-select"]')).toBeNull();
+      expect(container.querySelectorAll('[role="switch"]')).toHaveLength(0);
+      expect(container.textContent).toContain("Failed To Load");
+      expect(retryButton(container)).toBeTruthy();
+    }
+  );
+
+  it("returns with the stored settings once Retry answers", async () => {
+    const json = (body: unknown, status = 200) =>
+      new Response(JSON.stringify(body), {
+        status,
+        headers: { "Content-Type": "application/json" },
+      });
+    const fetchSpy = vi
+      .spyOn(globalThis, "fetch")
+      .mockImplementation(async (input: RequestInfo | URL) => {
+        const url = input.toString();
+        if (url.includes("/api/settings/compression")) {
+          return json({ enabled: true, defaultMode: "rtk", contextEditing: { enabled: true } });
+        }
+        if (url.includes("/api/context/combos")) {
+          return json({
+            combos: [{ id: "c1", name: "Alpha", pipeline: [{ engine: "rtk" }] }],
+          });
+        }
+        return json({}, 404);
+      });
+    // The Hub's first call is the settings GET; only it fails.
+    fetchSpy.mockImplementationOnce(
+      async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 })
+    );
+    const container = await mountHub();
+    expect(container.textContent).toContain("Failed To Load");
+
+    await act(async () => {
+      retryButton(container)!.click();
+    });
+    await flush();
+
+    expect(container.textContent).not.toContain("Failed To Load");
+    // The stored row has Context Editing on, which the defaults do not.
+    const toggle = container.querySelector('[role="switch"]');
+    expect(toggle?.getAttribute("aria-checked")).toBe("true");
+    const select = container.querySelector(
+      '[data-testid="active-profile-select"]'
+    ) as HTMLSelectElement | null;
+    expect(Array.from(select?.options ?? []).some((option) => option.text === "Alpha")).toBe(true);
+  });
+});
diff --git a/tests/unit/ui/compressionPanel.test.tsx b/tests/unit/ui/compressionPanel.test.tsx
index d83a0672baac..5d669a245a24 100644
--- a/tests/unit/ui/compressionPanel.test.tsx
+++ b/tests/unit/ui/compressionPanel.test.tsx
@@ -1,6 +1,7 @@
 // @vitest-environment jsdom
 import React, { act } from "react";
 import { createRoot } from "react-dom/client";
+import { fireEvent } from "@testing-library/react";
 import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
 import { ENGINE_IDS } from "../../../open-sse/services/compression/engineCatalog.ts";
 
@@ -65,7 +66,7 @@ interface CapturedPut {
   body: Record;
 }
 
-function setupFetchMock(): { puts: CapturedPut[] } {
+function setupFetchMock(storedOverrides?: Record): { puts: CapturedPut[] } {
   const puts: CapturedPut[] = [];
   const json = (body: unknown, status = 200) =>
     new Response(JSON.stringify(body), {
@@ -91,6 +92,8 @@ function setupFetchMock(): { puts: CapturedPut[] } {
     enginesExplicit: true,
     cavemanOutputMode: { enabled: false, intensity: "full", autoClarity: true },
   };
+  // The stored row the mock's settings GET serves; each PUT merges into it before answering.
+  let stored: Record = { ...initialConfig, ...storedOverrides };
 
   vi.spyOn(globalThis, "fetch").mockImplementation(
     async (input: RequestInfo | URL, init?: RequestInit) => {
@@ -109,17 +112,25 @@ function setupFetchMock(): { puts: CapturedPut[] } {
         if (method === "PUT") {
           const body = JSON.parse(String(init?.body ?? "{}"));
           puts.push({ url, body });
-          // Echo a merged config so the panel keeps a coherent state.
-          return json({ ...initialConfig, ...body });
+          // Store each PUT before answering, like the route does, so a GET that arrives
+          // after an ack reflects it.
+          stored = { ...stored, ...body };
+          return json({ ...stored });
         }
-        return json(initialConfig);
+        return json(stored);
       }
 
       return json({}, 404);
     }
   );
 
-  return { puts };
+  // Lets a test that answers PUTs itself record what the server would have stored, so a
+  // later GET reflects the ack.
+  const storePut = (body: Record) => {
+    stored = { ...stored, ...body };
+  };
+
+  return { puts, storePut };
 }
 
 // ── Tests ─────────────────────────────────────────────────────────────────────
@@ -194,8 +205,8 @@ describe("CompressionPanel", () => {
       .pop();
     expect(lastEngines).toBeTruthy();
     expect(lastEngines!.caveman.enabled).toBe(true);
-    // Full engines map is sent (whole-row persistence), so rtk is not dropped.
-    expect(lastEngines!.rtk.enabled).toBe(true);
+    // The server merges engines by id, so only the toggled engine is sent.
+    expect(lastEngines!.rtk).toBeUndefined();
   });
 
   it("derived-pipeline preview reflects the enabled engines", async () => {
@@ -219,4 +230,647 @@ describe("CompressionPanel", () => {
     expect(preview?.textContent).not.toContain("rtk");
     expect(preview?.textContent).not.toContain("caveman");
   });
+
+  async function renderPanel() {
+    const { default: CompressionPanel } =
+      await import("../../../src/app/(dashboard)/dashboard/context/settings/CompressionPanel");
+    let container!: HTMLElement;
+    await act(async () => {
+      container = mount();
+    });
+    await flush();
+    return container;
+  }
+
+  // Settings PUTs wait until the test answers them, so several saves stay in flight together
+  // and can answer in any order. Only PUTs to the settings endpoint are held — anything else
+  // falls through to the base mock, so `puts` and `answer` indices stay settings-only.
+  function holdSettingsPuts(storedOverrides?: Record) {
+    const { storePut } = setupFetchMock(storedOverrides);
+    const respond = vi.mocked(globalThis.fetch).getMockImplementation()!;
+    const puts: Array> = [];
+    const answers: Array<(status: number) => void> = [];
+    let nextGet: Record | null = null;
+    vi.mocked(globalThis.fetch).mockImplementation((input, init) => {
+      const url = String(input);
+      const method = (init?.method ?? "GET").toUpperCase();
+      if (
+        nextGet &&
+        method === "GET" &&
+        url.includes("/api/settings/compression") &&
+        !url.includes("mcp-accessibility")
+      ) {
+        const payload = nextGet;
+        nextGet = null;
+        return Promise.resolve(
+          new Response(JSON.stringify(payload), {
+            status: 200,
+            headers: { "Content-Type": "application/json" },
+          })
+        );
+      }
+      if (
+        method !== "PUT" ||
+        !url.includes("/api/settings/compression") ||
+        url.includes("mcp-accessibility")
+      ) {
+        return respond(input, init);
+      }
+      const body = JSON.parse(String(init.body));
+      puts.push(body);
+      return new Promise((resolve, reject) => {
+        answers.push((status) => {
+          // A 2xx answer means the server stored this save before answering.
+          if (status >= 200 && status < 300) storePut(body);
+          resolve(new Response("{}", { status }));
+        });
+        init.signal?.addEventListener("abort", () => reject(init.signal?.reason));
+      });
+    });
+    const answer = async (index: number, status: number) => {
+      await act(async () => answers[index](status));
+      await flush();
+    };
+    // Serves the panel's next settings GET (the post-failure resync) a stored row of our
+    // choosing, once.
+    const setNextGet = (payload: Record) => {
+      nextGet = payload;
+    };
+    return { puts, answer, setNextGet };
+  }
+
+  async function commitAutoTrigger(input: HTMLInputElement, value: string) {
+    await act(async () => {
+      fireEvent.change(input, { target: { value } });
+    });
+    await act(async () => {
+      fireEvent.keyDown(input, { key: "Enter" });
+    });
+  }
+
+  // The ultra-engine select is disabled while a save is in flight, but the auto-trigger input
+  // never is, so a value committed there goes out while the ultra-engine save still waits.
+  async function changeUltraEngineThenAutoTrigger(container: HTMLElement) {
+    const ultraEngine = container.querySelector(
+      `[data-testid="ultra-engine-select"]`
+    ) as HTMLSelectElement;
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+    await act(async () => {
+      fireEvent.change(ultraEngine, { target: { value: "slm" } });
+    });
+    await commitAutoTrigger(autoTrigger, "500");
+    return { ultraEngine, autoTrigger };
+  }
+
+  it("a failed save rolls back its own field and keeps a later save", async () => {
+    const { puts, answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const { ultraEngine, autoTrigger } = await changeUltraEngineThenAutoTrigger(container);
+    // Each PUT carries only its own field, so the server never stored ultraEngine "slm".
+    expect(puts).toEqual([{ ultraEngine: "slm" }, { autoTriggerTokens: 500 }]);
+
+    await answer(0, 500);
+    await answer(1, 200);
+    expect(ultraEngine.value).toBe("heuristic");
+    expect(autoTrigger.value).toBe("500");
+    expect(container.textContent).toContain("saveFailed");
+  });
+
+  it("a failed later save rolls back its own field and keeps the earlier save", async () => {
+    const { answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const { ultraEngine, autoTrigger } = await changeUltraEngineThenAutoTrigger(container);
+
+    await answer(0, 200);
+    await answer(1, 500);
+    expect(ultraEngine.value).toBe("slm");
+    expect(autoTrigger.value).toBe("0");
+    expect(container.textContent).toContain("saveFailed");
+  });
+
+  it("saves the auto-trigger value once, on Enter or when the field loses focus", async () => {
+    const { puts } = holdSettingsPuts();
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+
+    for (const value of ["8", "80", "8000"]) {
+      await act(async () => {
+        fireEvent.change(autoTrigger, { target: { value } });
+      });
+    }
+    expect(puts, "typing alone saves nothing").toEqual([]);
+    await act(async () => {
+      fireEvent.keyDown(autoTrigger, { key: "Enter" });
+    });
+    await act(async () => {
+      fireEvent.blur(autoTrigger);
+    });
+    expect(puts, "the blur after Enter has nothing new to save").toEqual([
+      { autoTriggerTokens: 8000 },
+    ]);
+
+    await act(async () => {
+      fireEvent.change(autoTrigger, { target: { value: "9000" } });
+    });
+    await act(async () => {
+      fireEvent.blur(autoTrigger);
+    });
+    expect(puts).toEqual([{ autoTriggerTokens: 8000 }, { autoTriggerTokens: 9000 }]);
+  });
+
+  it("rolls the auto-trigger box back when a cleared or zero-padded value fails to save", async () => {
+    const { answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+    await commitAutoTrigger(autoTrigger, "500");
+    await answer(0, 200);
+
+    await commitAutoTrigger(autoTrigger, "");
+    await answer(1, 500);
+    expect(autoTrigger.value, "a cleared box comes back to the saved value").toBe("500");
+
+    await commitAutoTrigger(autoTrigger, "0700");
+    await answer(2, 500);
+    expect(autoTrigger.value, "a zero-padded value comes back to the saved value").toBe("500");
+  });
+
+  it("shows the value of the save that answered last when saves of one field overlap", async () => {
+    const { puts, answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+    await commitAutoTrigger(autoTrigger, "1");
+    await commitAutoTrigger(autoTrigger, "100");
+    expect(puts).toEqual([{ autoTriggerTokens: 1 }, { autoTriggerTokens: 100 }]);
+
+    // The server stores each save just before it answers, so the save that answers last holds
+    // the stored value.
+    await answer(1, 200);
+    await answer(0, 200);
+    expect(autoTrigger.value).toBe("1");
+  });
+
+  it("shows Saved again once a save succeeds after an earlier failure settled", async () => {
+    const { answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const ultraEngine = container.querySelector(
+      `[data-testid="ultra-engine-select"]`
+    ) as HTMLSelectElement;
+
+    await act(async () => {
+      fireEvent.change(ultraEngine, { target: { value: "slm" } });
+    });
+    await answer(0, 500);
+    expect(container.textContent).toContain("saveFailed");
+
+    await act(async () => {
+      fireEvent.change(ultraEngine, { target: { value: "slm" } });
+    });
+    await answer(1, 200);
+    expect(ultraEngine.value).toBe("slm");
+    expect(container.textContent).toContain("saved");
+    expect(container.textContent).not.toContain("saveFailed");
+  });
+
+  it("shows Save failed at once and keeps it while other saves are in flight", async () => {
+    const { answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const { autoTrigger } = await changeUltraEngineThenAutoTrigger(container);
+
+    // The auto-trigger save fails while the ultra-engine save still waits on the server.
+    await answer(1, 500);
+    expect(container.textContent, "the failure shows at once").toContain("saveFailed");
+    await commitAutoTrigger(autoTrigger, "700");
+    expect(container.textContent, "a new save does not hide it").toContain("saveFailed");
+
+    await answer(2, 200);
+    await answer(0, 200);
+    expect(autoTrigger.value).toBe("700");
+    expect(container.textContent).toContain("saveFailed");
+  });
+
+  it("fails a PUT that outlives the save timeout", async () => {
+    const { puts, answer } = holdSettingsPuts();
+    const timeouts: AbortController[] = [];
+    const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockImplementation(() => {
+      const controller = new AbortController();
+      timeouts.push(controller);
+      return controller.signal;
+    });
+    const container = await renderPanel();
+    const { ultraEngine, autoTrigger } = await changeUltraEngineThenAutoTrigger(container);
+    expect(puts, "the auto-trigger save does not wait").toEqual([
+      { ultraEngine: "slm" },
+      { autoTriggerTokens: 500 },
+    ]);
+    await answer(1, 200);
+    expect(ultraEngine.disabled, "controls stay disabled while a PUT is in flight").toBe(true);
+
+    // The first PUT never answers; its timeout signal ends it.
+    await act(async () => timeouts[0].abort());
+    for (let i = 0; i < 5; i++) await flush();
+
+    expect(timeoutSpy).toHaveBeenCalledWith(15_000);
+    expect(ultraEngine.disabled).toBe(false);
+    expect(ultraEngine.value).toBe("heuristic");
+    expect(autoTrigger.value).toBe("500");
+    expect(container.textContent).toContain("saveFailed");
+  });
+
+  it("keeps an unsaved auto-trigger edit when an overlapping save of it fails", async () => {
+    const { puts, answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+    await commitAutoTrigger(autoTrigger, "100");
+    await commitAutoTrigger(autoTrigger, "200");
+    await act(async () => {
+      fireEvent.change(autoTrigger, { target: { value: "100" } });
+    });
+
+    // The 200 save answers first; the 100 save then fails, which rolls the value back to 200.
+    await answer(1, 200);
+    await answer(0, 500);
+    expect(autoTrigger.value, "the unsaved edit stays in the box").toBe("100");
+    await act(async () => {
+      fireEvent.blur(autoTrigger);
+    });
+    expect(puts).toEqual([
+      { autoTriggerTokens: 100 },
+      { autoTriggerTokens: 200 },
+      { autoTriggerTokens: 100 },
+    ]);
+  });
+
+  it("delivers a click that ends an auto-trigger edit to the control it lands on", async () => {
+    const { default: userEvent } = await import("@testing-library/user-event");
+    const { puts } = holdSettingsPuts();
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+    const cavemanToggle = container.querySelector(
+      `[data-testid="engine-toggle-caveman"] button`
+    ) as HTMLButtonElement;
+    const user = userEvent.setup();
+    await user.clear(autoTrigger);
+    await user.type(autoTrigger, "500");
+
+    // Pressing the toggle blurs the box, which saves 500 before the click lands.
+    await user.click(cavemanToggle);
+    expect(puts).toHaveLength(2);
+    expect(puts[0]).toEqual({ autoTriggerTokens: 500 });
+    expect((puts[1].engines as Record).caveman.enabled).toBe(true);
+  });
+
+  it("picks up a save the server stored after the panel gave up on it", async () => {
+    const { setNextGet } = holdSettingsPuts();
+    const timeouts: AbortController[] = [];
+    vi.spyOn(AbortSignal, "timeout").mockImplementation(() => {
+      const controller = new AbortController();
+      timeouts.push(controller);
+      return controller.signal;
+    });
+    const container = await renderPanel();
+    const ultraEngine = container.querySelector(
+      `[data-testid="ultra-engine-select"]`
+    ) as HTMLSelectElement;
+    await act(async () => {
+      fireEvent.change(ultraEngine, { target: { value: "slm" } });
+    });
+
+    // The PUT never answers; the timeout gives up on it. The server stores the save anyway,
+    // and the panel's re-read picks the stored value up.
+    setNextGet({
+      enabled: true,
+      autoTriggerTokens: 0,
+      preserveSystemPrompt: true,
+      engines: { rtk: { enabled: true, level: "standard" }, caveman: { enabled: false } },
+      enginesExplicit: true,
+      ultraEngine: "slm",
+    });
+    await act(async () => timeouts[0].abort());
+    for (let i = 0; i < 5; i++) await flush();
+
+    expect(ultraEngine.value, "the resync shows the value the server stored").toBe("slm");
+    expect(container.textContent).toContain("saveFailed");
+  });
+
+  it("a resync answer does not clobber saves started after the failure", async () => {
+    const { puts, answer, setNextGet } = holdSettingsPuts();
+    const timeouts: AbortController[] = [];
+    vi.spyOn(AbortSignal, "timeout").mockImplementation(() => {
+      const controller = new AbortController();
+      timeouts.push(controller);
+      return controller.signal;
+    });
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+
+    await commitAutoTrigger(autoTrigger, "500");
+    setNextGet({ enabled: true, autoTriggerTokens: 999 });
+    await act(async () => timeouts[0].abort());
+    // A new save starts while the resync is on its way; its value must survive the resync.
+    await commitAutoTrigger(autoTrigger, "700");
+    for (let i = 0; i < 5; i++) await flush();
+
+    expect(autoTrigger.value, "the newer save stays in the box").toBe("700");
+    await answer(1, 200);
+    expect(autoTrigger.value).toBe("700");
+    // The newer save started after the failed one had settled, so it clears the banner.
+    expect(container.textContent).toContain("saved");
+    expect(puts).toEqual([{ autoTriggerTokens: 500 }, { autoTriggerTokens: 700 }]);
+  });
+
+  it("does not let the re-read clobber a key a newer save acked", async () => {
+    const { puts, answer, setNextGet } = holdSettingsPuts();
+    const timeouts: AbortController[] = [];
+    vi.spyOn(AbortSignal, "timeout").mockImplementation(() => {
+      const controller = new AbortController();
+      timeouts.push(controller);
+      return controller.signal;
+    });
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+
+    await commitAutoTrigger(autoTrigger, "500");
+    setNextGet({ autoTriggerTokens: 999 });
+    await act(async () => timeouts[0].abort());
+    // A newer save of the same field acks before the re-read answers; the re-read's
+    // snapshot for that field is older than the ack and must be dropped.
+    await commitAutoTrigger(autoTrigger, "700");
+    await answer(1, 200);
+    for (let i = 0; i < 5; i++) await flush();
+
+    expect(autoTrigger.value, "the acked value stays").toBe("700");
+    expect(puts).toEqual([{ autoTriggerTokens: 500 }, { autoTriggerTokens: 700 }]);
+  });
+
+  it("keeps Save failed when a stale saved-timer fires", async () => {
+    vi.useFakeTimers();
+    try {
+      const { answer } = holdSettingsPuts();
+      const container = await renderPanel();
+      const ultraEngine = container.querySelector(
+        `[data-testid="ultra-engine-select"]`
+      ) as HTMLSelectElement;
+
+      await act(async () => {
+        fireEvent.change(ultraEngine, { target: { value: "slm" } });
+      });
+      await answer(0, 200);
+      expect(container.textContent).toContain("saved");
+
+      await act(async () => {
+        fireEvent.change(ultraEngine, { target: { value: "heuristic" } });
+      });
+      await answer(1, 500);
+      expect(container.textContent).toContain("saveFailed");
+
+      // The first save's 2s clear timer fires after the failure settled; it must not wipe
+      // the failure banner the user still needs to see.
+      await act(async () => {
+        vi.advanceTimersByTime(2000);
+      });
+      expect(container.textContent, "the failure banner survives the stale timer").toContain(
+        "saveFailed"
+      );
+    } finally {
+      vi.useRealTimers();
+    }
+  });
+
+  it("carries the whole contextBudget object so a pending mode change is not lost", async () => {
+    const { puts, answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const modeSelect = container.querySelector(
+      `[data-testid="context-budget-mode-select"]`
+    ) as HTMLSelectElement;
+    await act(async () => {
+      fireEvent.change(modeSelect, { target: { value: "floor" } });
+    });
+    const policySelect = container.querySelector(
+      `[data-testid="context-budget-policy-select"]`
+    ) as HTMLSelectElement;
+    await act(async () => {
+      fireEvent.change(policySelect, { target: { value: "percentage" } });
+    });
+
+    // Each PUT carries the full merged object (the persistence layer stores it as one row),
+    // so the second save re-carries the still-unsaved mode change.
+    expect(puts[0].contextBudget).toMatchObject({ mode: "floor" });
+    expect(puts[1].contextBudget).toMatchObject({ mode: "floor", policy: "percentage" });
+
+    await answer(0, 500);
+    await answer(1, 200);
+    expect(modeSelect.value).toBe("floor");
+    expect(policySelect.value).toBe("percentage");
+    expect(container.textContent).toContain("saveFailed");
+  });
+
+  it("keeps both contextBudget fields when mode and policy change in one turn", async () => {
+    // The policy select mounts only while mode is not off. Start on a different mode so
+    // both selects exist before the turn, and a stale spread cannot already be mode floor.
+    const { puts } = holdSettingsPuts({
+      contextBudget: { mode: "replace-autotrigger", policy: "reserve-output" },
+    });
+    const container = await renderPanel();
+    const modeSelect = container.querySelector(
+      `[data-testid="context-budget-mode-select"]`
+    ) as HTMLSelectElement;
+    const policySelect = container.querySelector(
+      `[data-testid="context-budget-policy-select"]`
+    ) as HTMLSelectElement;
+    expect(policySelect, "policy select is mounted while mode is not off").toBeTruthy();
+
+    // A controlled select commits at the end of each native event, so two fireEvents
+    // re-render even inside one act() and never share a stale closure. Call both
+    // handlers in that act before the commit — that is the turn that drops the first field.
+    type SelectChange = (event: { target: { value: string } }) => void;
+    const selectOnChange = (node: HTMLElement): SelectChange => {
+      const key = Object.keys(node).find((name) => name.startsWith("__reactProps$"));
+      const props = key
+        ? (node as unknown as Record)[key]
+        : undefined;
+      if (!props?.onChange) throw new Error("select has no onChange");
+      return props.onChange;
+    };
+    const changeMode = selectOnChange(modeSelect);
+    const changePolicy = selectOnChange(policySelect);
+    await act(async () => {
+      changeMode({ target: { value: "floor" } });
+      changePolicy({ target: { value: "percentage" } });
+    });
+
+    expect(puts[0].contextBudget).toMatchObject({ mode: "floor" });
+    expect(puts[1].contextBudget).toMatchObject({ mode: "floor", policy: "percentage" });
+  });
+
+  it("rolls an engine toggle back when its engines save fails", async () => {
+    const { puts, answer } = holdSettingsPuts();
+    const container = await renderPanel();
+    const toggle = container.querySelector(
+      `[data-testid="engine-toggle-caveman"] button`
+    ) as HTMLButtonElement;
+    await act(async () => {
+      toggle.click();
+    });
+    expect(puts).toHaveLength(1);
+    expect(puts[0].engines).toBeTruthy();
+
+    await answer(0, 500);
+    expect(toggle.getAttribute("aria-checked"), "the toggle reverts").toBe("false");
+    expect(container.textContent).toContain("saveFailed");
+  });
+
+  it("commits 1e3 as 1000 and keeps a value the field rejects", async () => {
+    const { puts } = holdSettingsPuts();
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+
+    await commitAutoTrigger(autoTrigger, "1e3");
+    expect(puts, "e-notation commits its real value").toEqual([{ autoTriggerTokens: 1000 }]);
+    expect(autoTrigger.value).toBe("1000");
+
+    await commitAutoTrigger(autoTrigger, "2.5");
+    expect(puts, "a decimal is not truncated into a save").toEqual([{ autoTriggerTokens: 1000 }]);
+    expect(autoTrigger.value, "the rejected edit stays in the box").toBe("2.5");
+
+    await commitAutoTrigger(autoTrigger, "200000");
+    expect(puts, "a value over the field's own max saves nothing").toHaveLength(1);
+    expect(autoTrigger.value).toBe("200000");
+  });
+
+  it("Escape cancels the draft instead of saving it", async () => {
+    const { puts } = holdSettingsPuts();
+    const container = await renderPanel();
+    const autoTrigger = container.querySelector(`input[type="number"]`) as HTMLInputElement;
+
+    await act(async () => {
+      fireEvent.change(autoTrigger, { target: { value: "500" } });
+    });
+    await act(async () => {
+      fireEvent.keyDown(autoTrigger, { key: "Escape" });
+    });
+    expect(puts, "Escape saves nothing").toEqual([]);
+    expect(autoTrigger.value, "the box returns to the current value").toBe("0");
+
+    await act(async () => {
+      fireEvent.blur(autoTrigger);
+    });
+    expect(puts, "the blur after Escape has nothing to save").toEqual([]);
+  });
+});
+
+describe("CompressionPanel when the settings GET fails", () => {
+  // The panel's first request is the settings GET, and it fails as it does while the server
+  // restarts. Later requests, including a retried GET, reach the stored config.
+  const FAILURES = {
+    "a 500": async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 }),
+    "a network error": async () => {
+      throw new TypeError("Failed to fetch");
+    },
+  };
+
+  function failFirstSettingsGet(failure: keyof typeof FAILURES = "a 500") {
+    const { puts } = setupFetchMock();
+    vi.mocked(globalThis.fetch).mockImplementationOnce(FAILURES[failure]);
+    // The mcp-accessibility toggle writes its own store, not the settings row.
+    return () => puts.filter((p) => !p.url.includes("mcp-accessibility")).map((p) => p.body);
+  }
+
+  // Holds the next request open until the returned function lets `answer` reply to it.
+  function holdNextRequest(answer: typeof fetch) {
+    let release!: () => Promise;
+    vi.mocked(globalThis.fetch).mockImplementationOnce(
+      (input, init) =>
+        new Promise((resolve) => {
+          release = async () => {
+            await act(async () => resolve(answer(input, init)));
+            await flush();
+          };
+        })
+    );
+    return () => release();
+  }
+
+  async function mountPanel() {
+    const { default: CompressionPanel } =
+      await import("../../../src/app/(dashboard)/dashboard/context/settings/CompressionPanel");
+    let container!: HTMLElement;
+    await act(async () => {
+      container = mount();
+    });
+    await flush();
+    return container;
+  }
+
+  function retryButton(container: HTMLElement) {
+    return Array.from(container.querySelectorAll("button")).find(
+      (button) => button.textContent === "retry"
+    );
+  }
+
+  it.each(Object.keys(FAILURES) as Array)(
+    "offers no control that would save defaults over the stored settings after %s",
+    async (failure) => {
+      const settingsPuts = failFirstSettingsGet(failure);
+      const container = await mountPanel();
+
+      // Without a loaded config the panel holds defaults: engines {}, outputStyles [], and the
+      // default contextBudget. A switch it still offers would PUT those defaults over the
+      // stored row, so press every one in page order.
+      for (const control of container.querySelectorAll('[role="switch"]')) {
+        await act(async () => control.click());
+        await flush();
+      }
+
+      expect(settingsPuts(), "a save before any GET succeeds overwrites stored settings").toEqual(
+        []
+      );
+      expect(container.querySelectorAll("select, input")).toHaveLength(0);
+      expect(retryButton(container), "the load error offers a retry").toBeTruthy();
+    }
+  );
+
+  it("shows a retry that loads the stored settings before the controls return", async () => {
+    const settingsPuts = failFirstSettingsGet();
+    const container = await mountPanel();
+    expect(container.textContent).toContain("failedToLoad");
+
+    // The retried GET stays open, and the controls stay back until it answers.
+    const release = holdNextRequest(vi.mocked(globalThis.fetch).getMockImplementation()!);
+    await act(async () => retryButton(container)!.click());
+    await flush();
+    expect(container.textContent).toContain("loading");
+    expect(container.querySelectorAll('[role="switch"], select, input')).toHaveLength(0);
+
+    await release();
+    const rtkLevel = container.querySelector(
+      `[data-testid="engine-row-rtk"] select`
+    ) as HTMLSelectElement | null;
+    expect(rtkLevel?.value, "the retried GET loads the stored rtk level").toBe("standard");
+    // Saves go out again once a GET has succeeded.
+    const master = container.querySelector(
+      `[data-testid="compression-panel"] [role="switch"]`
+    ) as HTMLButtonElement;
+    await act(async () => master.click());
+    await flush();
+    expect(settingsPuts()).toEqual([{ enabled: false }]);
+  });
+
+  it("ignores an answer from the load that a retry superseded", async () => {
+    failFirstSettingsGet();
+    // The first load's mcp-accessibility GET answers only after the retried load finished.
+    const answerStale = holdNextRequest(
+      async () => new Response(JSON.stringify({ enabled: false }), { status: 200 })
+    );
+    const container = await mountPanel();
+    await act(async () => retryButton(container)!.click());
+    await flush();
+
+    await answerStale();
+    const mcpToggle = container.querySelector(
+      `[data-testid="mcp-accessibility-toggle"] [role="switch"]`
+    );
+    expect(mcpToggle?.getAttribute("aria-checked"), "the retried load reported enabled").toBe(
+      "true"
+    );
+  });
 });
diff --git a/tests/unit/ui/compressionPanelEnginesPartialWrite.test.tsx b/tests/unit/ui/compressionPanelEnginesPartialWrite.test.tsx
new file mode 100644
index 000000000000..e2d804d9d7cd
--- /dev/null
+++ b/tests/unit/ui/compressionPanelEnginesPartialWrite.test.tsx
@@ -0,0 +1,153 @@
+// @vitest-environment jsdom
+// The server merges the `engines` map by engine id, so the context settings panel sends only
+// the engine a control changed. A whole-map PUT built from the panel's copy would overwrite
+// engines another page (the Omniglyph page, the MCP tool) changed after the panel loaded.
+import React, { act } from "react";
+import { createRoot } from "react-dom/client";
+import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
+
+vi.mock("next-intl", () => ({
+  useTranslations: () => (key: string, values?: Record) =>
+    values ? `${key} ${Object.values(values).join(" ")}` : key,
+  useLocale: () => "en",
+}));
+
+const containers: HTMLElement[] = [];
+const roots: Array<{ unmount: () => void }> = [];
+
+function mount(ui: React.ReactElement): HTMLElement {
+  const container = document.createElement("div");
+  document.body.appendChild(container);
+  containers.push(container);
+  const root = createRoot(container);
+  roots.push(root);
+  act(() => {
+    root.render(ui);
+  });
+  return container;
+}
+
+beforeEach(() => {
+  (
+    globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+  ).IS_REACT_ACT_ENVIRONMENT = true;
+});
+
+afterEach(async () => {
+  vi.restoreAllMocks();
+  await act(async () => {
+    while (roots.length > 0) roots.pop()?.unmount();
+  });
+  for (let i = 0; i < 10; i++) await Promise.resolve();
+  while (containers.length > 0) containers.pop()?.remove();
+  document.body.innerHTML = "";
+});
+
+async function flush() {
+  await act(async () => {
+    for (let i = 0; i < 10; i++) await Promise.resolve();
+  });
+}
+
+function setupFetchMock(): { settingsPuts: Array> } {
+  const settingsPuts: Array> = [];
+  const json = (body: unknown, status = 200) =>
+    new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
+  const initialConfig = {
+    enabled: true,
+    engines: { rtk: { enabled: true, level: "standard" }, caveman: { enabled: false } },
+    activeComboId: null,
+  };
+
+  vi.spyOn(globalThis, "fetch").mockImplementation(
+    async (input: RequestInfo | URL, init?: RequestInit) => {
+      const url = input.toString();
+      const method = (init?.method ?? "GET").toUpperCase();
+      if (url.includes("/api/settings/compression/mcp-accessibility")) {
+        return json({ enabled: true, maxTextChars: 50000 });
+      }
+      if (url.includes("/api/settings/compression")) {
+        if (method === "PUT") {
+          const body = JSON.parse(String(init?.body ?? "{}"));
+          settingsPuts.push(body);
+          return json({ ...initialConfig, ...body });
+        }
+        return json(initialConfig);
+      }
+      return json({}, 404);
+    }
+  );
+  return { settingsPuts };
+}
+
+async function mountPanel(): Promise {
+  const { default: CompressionPanel } =
+    await import("../../../src/app/(dashboard)/dashboard/context/settings/CompressionPanel");
+  let container!: HTMLElement;
+  await act(async () => {
+    container = mount();
+  });
+  await flush();
+  return container;
+}
+
+function engineSwitch(container: HTMLElement, id: string): HTMLButtonElement {
+  const button = container.querySelector(
+    `[data-testid="engine-toggle-${id}"] button`
+  ) as HTMLButtonElement | null;
+  expect(button, `${id} toggle must exist`).toBeTruthy();
+  return button!;
+}
+
+async function click(button: HTMLButtonElement) {
+  await act(async () => {
+    button.click();
+  });
+  await flush();
+}
+
+describe("CompressionPanel engines partial writes", () => {
+  it("toggling one engine PUTs only that engine and keeps the others on screen", async () => {
+    const { settingsPuts } = setupFetchMock();
+    const container = await mountPanel();
+
+    await click(engineSwitch(container, "caveman"));
+
+    expect(settingsPuts).toEqual([{ engines: { caveman: { enabled: true } } }]);
+    expect(engineSwitch(container, "caveman").getAttribute("aria-checked")).toBe("true");
+    expect(engineSwitch(container, "rtk").getAttribute("aria-checked")).toBe("true");
+  });
+
+  it("toggling two engines in turn sends each one alone and keeps both on screen", async () => {
+    const { settingsPuts } = setupFetchMock();
+    const container = await mountPanel();
+
+    await click(engineSwitch(container, "caveman"));
+    await click(engineSwitch(container, "lite"));
+
+    expect(settingsPuts).toEqual([
+      { engines: { caveman: { enabled: true } } },
+      { engines: { lite: { enabled: true } } },
+    ]);
+    for (const id of ["rtk", "caveman", "lite"]) {
+      expect(engineSwitch(container, id).getAttribute("aria-checked"), id).toBe("true");
+    }
+  });
+
+  it("changing an engine level PUTs that engine with its current switch state", async () => {
+    const { settingsPuts } = setupFetchMock();
+    const container = await mountPanel();
+    const select = container.querySelector(
+      `[data-testid="engine-row-rtk"] select`
+    ) as HTMLSelectElement | null;
+    expect(select, "rtk level select must exist").toBeTruthy();
+
+    await act(async () => {
+      select!.value = "aggressive";
+      select!.dispatchEvent(new Event("change", { bubbles: true }));
+    });
+    await flush();
+
+    expect(settingsPuts).toEqual([{ engines: { rtk: { enabled: true, level: "aggressive" } } }]);
+  });
+});
diff --git a/tests/unit/ui/engine-config-page-empty-number.test.tsx b/tests/unit/ui/engine-config-page-empty-number.test.tsx
new file mode 100644
index 000000000000..f0068d325762
--- /dev/null
+++ b/tests/unit/ui/engine-config-page-empty-number.test.tsx
@@ -0,0 +1,320 @@
+// @vitest-environment jsdom
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { EngineConfigPage } from "@/shared/components/compression/EngineConfigPage";
+import {
+  compressionPreviewConfigSchema,
+  compressionSettingsUpdateSchema,
+} from "@/shared/validation/compressionConfigSchemas";
+import {
+  aggressiveEngine,
+  liteEngine,
+  ultraEngine,
+} from "@omniroute/open-sse/services/compression/engines/cavemanAdapter.ts";
+import { headroomEngine } from "@omniroute/open-sse/services/compression/engines/headroom/index.ts";
+import { ccrEngine } from "@omniroute/open-sse/services/compression/engines/ccr/index.ts";
+import { DEFAULT_AGGRESSIVE_CONFIG } from "@omniroute/open-sse/services/compression/types.ts";
+
+type Settings = Record;
+
+// The same fields GET /api/compression/engines returns, taken from the real engines.
+const ENGINES = {
+  engines: [aggressiveEngine, ultraEngine, liteEngine, headroomEngine, ccrEngine].map((engine) => ({
+    id: engine.id,
+    name: engine.name,
+    description: engine.description,
+    icon: engine.icon,
+    stackable: engine.stackable,
+    stackPriority: engine.stackPriority,
+    metadata: engine.metadata,
+    configSchema: engine.getConfigSchema(),
+  })),
+};
+
+// Lite rows merge with the stored row, as mergeLiteSettingsForWrite does: an omitted cap stays,
+// and a null cap is cleared.
+function mergeLite(existing: unknown, incoming: Settings): Settings {
+  const merged: Settings = { ...(existing as Settings), ...incoming };
+  if (merged.maxToolLength === null) delete merged.maxToolLength;
+  return merged;
+}
+
+// Stands in for /api/settings/compression. A PUT is checked against the real update schema,
+// and each key in its body replaces the stored sub-object whole, as updateCompressionSettings
+// does, except lite, which merges. A preview's config is checked against the preview route's
+// schema.
+function startServer(initial: Settings) {
+  let stored: Settings = JSON.parse(JSON.stringify(initial));
+  let held: Promise | null = null;
+  let releaseHeld: () => void = () => {};
+  const server = {
+    puts: [] as { body: Settings; status: number }[],
+    previews: [] as { config: unknown; status: number }[],
+    // The next PUT answers 500 without applying, as a transient server failure would.
+    failNextPut: false,
+    // Keeps PUTs waiting until the returned release runs, as a slow server would.
+    holdWrites() {
+      held = new Promise((resolve) => {
+        releaseHeld = () => {
+          held = null;
+          resolve();
+        };
+      });
+      return releaseHeld;
+    },
+    get stored() {
+      return stored;
+    },
+  };
+  const respond = (data: unknown, status = 200) =>
+    new Response(JSON.stringify(data), {
+      status,
+      headers: { "Content-Type": "application/json" },
+    });
+  vi.stubGlobal(
+    "fetch",
+    vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
+      const { pathname } = new URL(String(input), "http://localhost");
+      if (pathname === "/api/compression/engines") return respond(ENGINES);
+      if (pathname === "/api/context/analytics/engine") {
+        return respond({ engineId: "", runs: 0, tokensSaved: 0, avgSavingsPercent: 0, days: 7 });
+      }
+      if (pathname === "/api/settings/compression") {
+        if (init?.method !== "PUT") return respond(stored);
+        const body = JSON.parse(String(init.body)) as Settings;
+        const parsed = compressionSettingsUpdateSchema.safeParse(body);
+        server.puts.push({ body, status: parsed.success ? 200 : 400 });
+        if (!parsed.success) return respond({ error: "Invalid request" }, 400);
+        if (server.failNextPut) {
+          server.failNextPut = false;
+          return respond({ error: "unavailable" }, 500);
+        }
+        if (held) await held;
+        const { lite, ...rest } = parsed.data as Settings;
+        stored = { ...stored, ...rest };
+        if (lite) stored.lite = mergeLite(stored.lite, lite as Settings);
+        return respond(stored);
+      }
+      if (pathname === "/api/compression/preview") {
+        const { config } = JSON.parse(String(init?.body)) as { config?: unknown };
+        const parsed = compressionPreviewConfigSchema.optional().safeParse(config);
+        server.previews.push({ config, status: parsed.success ? 200 : 400 });
+        if (!parsed.success) return respond({ error: "Invalid request" }, 400);
+        return respond({
+          original: "original text",
+          compressed: "compressed text",
+          originalTokens: 4,
+          compressedTokens: 2,
+          savingsPct: 50,
+        });
+      }
+      return respond(null, 404);
+    })
+  );
+  return server;
+}
+
+async function settle() {
+  for (let i = 0; i < 3; i++) {
+    await act(async () => {
+      await new Promise((resolve) => setTimeout(resolve, 0));
+    });
+  }
+}
+
+function inputFor(label: string): HTMLInputElement {
+  const input = screen.getByText(label).closest("label")?.querySelector("input");
+  if (!input) throw new Error(`no input for ${label}`);
+  return input;
+}
+
+async function renderPage(engineId: string) {
+  render();
+  await settle();
+}
+
+async function save() {
+  fireEvent.click(screen.getByRole("button", { name: "Save" }));
+  await settle();
+}
+
+afterEach(() => {
+  vi.unstubAllGlobals();
+});
+
+describe("EngineConfigPage treats an emptied number field as not set", () => {
+  it("keeps an emptied number field empty instead of writing zero", async () => {
+    const server = startServer({
+      aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, minSavingsThreshold: 0.5 },
+    });
+    await renderPage("aggressive");
+
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "" } });
+    await settle();
+
+    expect(inputFor("Minimum savings threshold").value).toBe("");
+    // The blank state shows the schema default as a placeholder, so "default applies" is visible.
+    expect(inputFor("Minimum savings threshold").placeholder.length).toBeGreaterThan(0);
+    expect(server.puts).toHaveLength(0);
+  });
+
+  it("saves an emptied number field as unset", async () => {
+    const server = startServer({
+      aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, minSavingsThreshold: 0.5 },
+    });
+    await renderPage("aggressive");
+
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    const { minSavingsThreshold: _unset, ...defaults } = DEFAULT_AGGRESSIVE_CONFIG;
+    expect(server.stored.aggressive).toEqual(defaults);
+    expect(screen.queryByText("Failed to save configuration.")).toBeNull();
+  });
+
+  it("saves a page whose emptied field rejects zero", async () => {
+    const server = startServer({ ccr: { minChars: 500 } });
+    await renderPage("ccr");
+
+    fireEvent.change(inputFor("Minimum block characters"), { target: { value: "" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.ccr).toEqual({});
+    expect(screen.queryByText("Failed to save configuration.")).toBeNull();
+  });
+
+  it("previews without an emptied number field", async () => {
+    const server = startServer({ headroom: { minRows: 8 } });
+    await renderPage("headroom");
+
+    fireEvent.change(inputFor("Minimum rows to compact"), { target: { value: "" } });
+    fireEvent.click(screen.getByRole("button", { name: "Preview" }));
+    await settle();
+
+    expect(server.previews.at(-1)?.status).toBe(200);
+    expect(server.previews.at(-1)?.config).toEqual({ headroom: {} });
+    expect(screen.queryByText("Preview failed.")).toBeNull();
+  });
+
+  it("still clears the stored Lite cap when the field is emptied", async () => {
+    const server = startServer({ lite: { compressToolResults: true, maxToolLength: 8000 } });
+    await renderPage("lite");
+
+    fireEvent.change(inputFor("Maximum tool-result length"), { target: { value: "" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.lite).toEqual({ compressToolResults: true });
+  });
+
+  it("keeps a zero the operator types", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    await renderPage("aggressive");
+
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "0" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect((server.stored.aggressive as Settings).minSavingsThreshold).toBe(0);
+  });
+
+  it("rejects an overflow cap instead of silently clearing it", async () => {
+    // jsdom empties "1e999", so the overflow arrives as the finite 9e300; the range check it
+    // hits is the same one Infinity fails, since only NaN skips the check.
+    const server = startServer({ lite: { compressToolResults: true, maxToolLength: 8000 } });
+    await renderPage("lite");
+
+    fireEvent.change(inputFor("Maximum tool-result length"), { target: { value: "9e300" } });
+    await save();
+
+    expect(server.puts).toHaveLength(0);
+    expect(screen.getByText("Failed to save configuration.")).toBeTruthy();
+    expect((server.stored.lite as Settings).maxToolLength).toBe(8000);
+  });
+
+  it("keeps an emptied field unset through a failed save and its retry", async () => {
+    const server = startServer({
+      aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, minSavingsThreshold: 0.5 },
+    });
+    await renderPage("aggressive");
+
+    server.failNextPut = true;
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "" } });
+    await save();
+    expect(screen.getByText("Failed to save configuration.")).toBeTruthy();
+    expect(inputFor("Minimum savings threshold").value).toBe("");
+
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    const { minSavingsThreshold: _unset, ...defaults } = DEFAULT_AGGRESSIVE_CONFIG;
+    expect(server.stored.aggressive).toEqual(defaults);
+  });
+
+  it("previews a stored finite minRows", async () => {
+    const server = startServer({ headroom: { minRows: 8 } });
+    await renderPage("headroom");
+
+    fireEvent.click(screen.getByRole("button", { name: "Preview" }));
+    await settle();
+
+    expect(server.previews.at(-1)?.status).toBe(200);
+    expect(server.previews.at(-1)?.config).toEqual({ headroom: { minRows: 8 } });
+  });
+
+  it("shows the schema default again after an emptied field saves", async () => {
+    const server = startServer({
+      aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, minSavingsThreshold: 0.5 },
+    });
+    await renderPage("aggressive");
+
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(inputFor("Minimum savings threshold").value).toBe(
+      String(DEFAULT_AGGRESSIVE_CONFIG.minSavingsThreshold)
+    );
+  });
+
+  it("keeps the stored value when a field reports badInput instead of unsetting it", async () => {
+    // A browser reports badInput with an empty value for unparseable entries ("1e", "1,5" in a
+    // comma-decimal locale); jsdom never sets the flag, so the test stubs it.
+    const server = startServer({
+      aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, minSavingsThreshold: 0.5 },
+    });
+    await renderPage("aggressive");
+    const input = inputFor("Minimum savings threshold");
+    Object.defineProperty(input, "validity", {
+      value: { ...input.validity, badInput: true },
+      configurable: true,
+    });
+
+    fireEvent.change(input, { target: { value: "1e" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect((server.stored.aggressive as Settings).minSavingsThreshold).toBe(0.5);
+  });
+
+  it("clears a cap emptied while a save was in flight", async () => {
+    const server = startServer({ lite: { compressToolResults: true, maxToolLength: 3000 } });
+    await renderPage("lite");
+    const release = server.holdWrites();
+
+    fireEvent.change(inputFor("Maximum tool-result length"), { target: { value: "3500" } });
+    await save();
+    // The cap is emptied while the first save is still out.
+    fireEvent.change(inputFor("Maximum tool-result length"), { target: { value: "" } });
+    release();
+    await settle();
+    expect((server.stored.lite as Settings).maxToolLength).toBe(3500);
+
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.lite).toEqual({ compressToolResults: true });
+  });
+});
diff --git a/tests/unit/ui/engine-config-page-partial-save.test.tsx b/tests/unit/ui/engine-config-page-partial-save.test.tsx
new file mode 100644
index 000000000000..8195711a4feb
--- /dev/null
+++ b/tests/unit/ui/engine-config-page-partial-save.test.tsx
@@ -0,0 +1,451 @@
+// @vitest-environment jsdom
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { EngineConfigPage } from "@/shared/components/compression/EngineConfigPage";
+import {
+  compressionPreviewConfigSchema,
+  compressionSettingsUpdateSchema,
+} from "@/shared/validation/compressionConfigSchemas";
+import {
+  aggressiveEngine,
+  liteEngine,
+  ultraEngine,
+} from "@omniroute/open-sse/services/compression/engines/cavemanAdapter.ts";
+import {
+  DEFAULT_AGGRESSIVE_CONFIG,
+  DEFAULT_ULTRA_CONFIG,
+} from "@omniroute/open-sse/services/compression/types.ts";
+
+type Settings = Record;
+
+// The same fields GET /api/compression/engines returns, taken from the real engines.
+const ENGINES = {
+  engines: [aggressiveEngine, ultraEngine, liteEngine].map((engine) => ({
+    id: engine.id,
+    name: engine.name,
+    description: engine.description,
+    icon: engine.icon,
+    stackable: engine.stackable,
+    stackPriority: engine.stackPriority,
+    metadata: engine.metadata,
+    configSchema: engine.getConfigSchema(),
+  })),
+};
+
+// Lite rows merge with the stored row, as mergeLiteSettingsForWrite does: an omitted cap stays,
+// and a null cap is cleared.
+function mergeLite(existing: unknown, incoming: Settings): Settings {
+  const merged: Settings = { ...(existing as Settings), ...incoming };
+  if (merged.maxToolLength === null) delete merged.maxToolLength;
+  return merged;
+}
+
+// Stands in for /api/settings/compression. A PUT is checked against the real update schema,
+// and each key in its body replaces the stored sub-object whole, as updateCompressionSettings
+// does, except lite, which merges. `readsFail` makes GETs fail. `holdNextRead` keeps the next GET
+// waiting until the returned function runs, then fails it. `failNextWrite` answers the next PUT
+// with a 500, either before applying it ("rejected") or after ("applied"). `loseNextResponse`
+// applies the next PUT and then fails the request, as a dropped connection would. `holdWrites`
+// keeps PUTs waiting until the returned function runs. A preview's config is checked against the
+// preview route's schema.
+function startServer(initial: Settings) {
+  let stored: Settings = JSON.parse(JSON.stringify(initial));
+  let held: Promise | null = null;
+  let heldRead: Promise | null = null;
+  const server = {
+    readsFail: false,
+    failNextWrite: null as null | "rejected" | "applied",
+    loseNextResponse: false,
+    puts: [] as { body: Settings; status: number }[],
+    previews: [] as { config: unknown; status: number }[],
+    get stored() {
+      return stored;
+    },
+    // A save made on another page, such as the compression settings tab.
+    write(patch: Settings) {
+      stored = { ...stored, ...patch };
+    },
+    holdWrites() {
+      let release = () => {};
+      held = new Promise((resolve) => {
+        release = () => {
+          held = null;
+          resolve();
+        };
+      });
+      return release;
+    },
+    holdNextRead() {
+      let fail = () => {};
+      heldRead = new Promise((resolve) => {
+        fail = () => resolve();
+      });
+      return fail;
+    },
+  };
+  const respond = (data: unknown, status = 200) =>
+    new Response(JSON.stringify(data), {
+      status,
+      headers: { "Content-Type": "application/json" },
+    });
+  vi.stubGlobal(
+    "fetch",
+    vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
+      const { pathname } = new URL(String(input), "http://localhost");
+      if (pathname === "/api/compression/engines") return respond(ENGINES);
+      if (pathname === "/api/context/analytics/engine") {
+        return respond({ engineId: "", runs: 0, tokensSaved: 0, avgSavingsPercent: 0, days: 7 });
+      }
+      if (pathname === "/api/settings/compression") {
+        if (init?.method !== "PUT") {
+          if (heldRead) {
+            const wait = heldRead;
+            heldRead = null;
+            await wait;
+            return respond({ error: "unavailable" }, 500);
+          }
+          return server.readsFail ? respond({ error: "unavailable" }, 500) : respond(stored);
+        }
+        const body = JSON.parse(String(init.body)) as Settings;
+        const parsed = compressionSettingsUpdateSchema.safeParse(body);
+        server.puts.push({ body, status: parsed.success ? 200 : 400 });
+        if (!parsed.success) return respond({ error: "Invalid request" }, 400);
+        if (held) await held;
+        if (server.failNextWrite === "rejected") {
+          server.failNextWrite = null;
+          return respond({ error: "unavailable" }, 500);
+        }
+        const { lite, ...rest } = parsed.data as Settings;
+        stored = { ...stored, ...rest };
+        if (lite) stored.lite = mergeLite(stored.lite, lite as Settings);
+        if (server.failNextWrite === "applied") {
+          server.failNextWrite = null;
+          return respond({ error: "unavailable" }, 500);
+        }
+        if (server.loseNextResponse) {
+          server.loseNextResponse = false;
+          throw new TypeError("Failed to fetch");
+        }
+        return respond(stored);
+      }
+      if (pathname === "/api/compression/preview") {
+        const { config } = JSON.parse(String(init?.body)) as { config?: unknown };
+        const parsed = compressionPreviewConfigSchema.optional().safeParse(config);
+        server.previews.push({ config, status: parsed.success ? 200 : 400 });
+        if (!parsed.success) return respond({ error: "Invalid request" }, 400);
+        return respond({
+          original: "original text",
+          compressed: "compressed text",
+          originalTokens: 4,
+          compressedTokens: 2,
+          savingsPct: 50,
+        });
+      }
+      return respond(null, 404);
+    })
+  );
+  return server;
+}
+
+async function settle() {
+  for (let i = 0; i < 3; i++) {
+    await act(async () => {
+      await new Promise((resolve) => setTimeout(resolve, 0));
+    });
+  }
+}
+
+function inputFor(label: string): HTMLInputElement {
+  const input = screen.getByText(label).closest("label")?.querySelector("input");
+  if (!input) throw new Error(`no input for ${label}`);
+  return input;
+}
+
+async function renderPage(engineId: string) {
+  render();
+  await settle();
+}
+
+async function save() {
+  fireEvent.click(screen.getByRole("button", { name: "Save" }));
+  await settle();
+}
+
+afterEach(() => {
+  vi.unstubAllGlobals();
+});
+
+describe("EngineConfigPage saves only what the operator changed", () => {
+  it("keeps Aggressive thresholds and tool strategies saved elsewhere after the page loaded", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    await renderPage("aggressive");
+    // The compression settings tab edits the same sub-object after this page loaded.
+    const fromSettingsTab = {
+      ...DEFAULT_AGGRESSIVE_CONFIG,
+      thresholds: { ...DEFAULT_AGGRESSIVE_CONFIG.thresholds, fullSummary: 9 },
+      toolStrategies: { ...DEFAULT_AGGRESSIVE_CONFIG.toolStrategies, json: false },
+      summarizerEnabled: false,
+    };
+    server.write({ aggressive: fromSettingsTab });
+
+    fireEvent.change(inputFor("Maximum tokens per message"), { target: { value: "4096" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.aggressive).toEqual({ ...fromSettingsTab, maxTokensPerMessage: 4096 });
+  });
+
+  it("saves the Ultra page on a default install that has no model path", async () => {
+    const server = startServer({ ultra: DEFAULT_ULTRA_CONFIG });
+    await renderPage("ultra");
+
+    fireEvent.change(inputFor("Compression rate"), { target: { value: "0.4" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.ultra).toEqual({ ...DEFAULT_ULTRA_CONFIG, compressionRate: 0.4 });
+    expect(screen.queryByText("Failed to save configuration.")).toBeNull();
+  });
+
+  it("keeps ultra.enabled when the Ultra page saves", async () => {
+    const server = startServer({
+      ultra: { ...DEFAULT_ULTRA_CONFIG, enabled: true, modelPath: "/models/ultra" },
+    });
+    await renderPage("ultra");
+
+    fireEvent.change(inputFor("Compression rate"), { target: { value: "0.4" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.ultra).toEqual({
+      ...DEFAULT_ULTRA_CONFIG,
+      enabled: true,
+      modelPath: "/models/ultra",
+      compressionRate: 0.4,
+    });
+  });
+
+  it("clears a stored model path when the field is emptied", async () => {
+    const server = startServer({ ultra: { ...DEFAULT_ULTRA_CONFIG, modelPath: "/models/ultra" } });
+    await renderPage("ultra");
+
+    fireEvent.change(inputFor("Model path"), { target: { value: "  " } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.ultra).toEqual(DEFAULT_ULTRA_CONFIG);
+  });
+
+  it("fails the save without writing when the current settings cannot be read", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    await renderPage("aggressive");
+    server.readsFail = true;
+
+    fireEvent.change(inputFor("Maximum tokens per message"), { target: { value: "4096" } });
+    await save();
+
+    expect(server.puts).toHaveLength(0);
+    expect(screen.getByText("Failed to save configuration.")).toBeTruthy();
+  });
+
+  it("sends a second save's change without resending the first one", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    await renderPage("aggressive");
+
+    fireEvent.change(inputFor("Maximum tokens per message"), { target: { value: "4096" } });
+    await save();
+    // Another page sets the same field back after the first save.
+    server.write({ aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 1024 } });
+
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "0.2" } });
+    await save();
+
+    expect(server.stored.aggressive).toEqual({
+      ...DEFAULT_AGGRESSIVE_CONFIG,
+      maxTokensPerMessage: 1024,
+      minSavingsThreshold: 0.2,
+    });
+  });
+
+  it("shows the stored values after a save, so a value typed back afterwards is sent", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    await renderPage("aggressive");
+    // Another page changes a field this page shows, after it loaded.
+    server.write({ aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 1024 } });
+
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "0.2" } });
+    await save();
+
+    expect(inputFor("Maximum tokens per message").value).toBe("1024");
+
+    fireEvent.change(inputFor("Maximum tokens per message"), {
+      target: { value: String(DEFAULT_AGGRESSIVE_CONFIG.maxTokensPerMessage) },
+    });
+    await save();
+
+    expect(server.stored.aggressive).toEqual({
+      ...DEFAULT_AGGRESSIVE_CONFIG,
+      minSavingsThreshold: 0.2,
+    });
+  });
+
+  it("sends a field again after a save whose response was lost", async () => {
+    const server = startServer({ ultra: DEFAULT_ULTRA_CONFIG });
+    await renderPage("ultra");
+
+    // The server applies the write, but the response never arrives.
+    server.loseNextResponse = true;
+    fireEvent.change(inputFor("Compression rate"), { target: { value: "0.4" } });
+    await save();
+    expect(screen.getByText("Failed to save configuration.")).toBeTruthy();
+    expect((server.stored.ultra as Settings).compressionRate).toBe(0.4);
+
+    // The operator puts the loaded value back and saves again.
+    fireEvent.change(inputFor("Compression rate"), {
+      target: { value: String(DEFAULT_ULTRA_CONFIG.compressionRate) },
+    });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.ultra).toEqual(DEFAULT_ULTRA_CONFIG);
+  });
+
+  it("keeps an edit typed while a save is in flight", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    await renderPage("aggressive");
+    const release = server.holdWrites();
+
+    fireEvent.change(inputFor("Maximum tokens per message"), { target: { value: "4096" } });
+    await save();
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "0.2" } });
+    release();
+    await settle();
+
+    expect(inputFor("Minimum savings threshold").value).toBe("0.2");
+    await save();
+
+    expect(server.stored.aggressive).toEqual({
+      ...DEFAULT_AGGRESSIVE_CONFIG,
+      maxTokensPerMessage: 4096,
+      minSavingsThreshold: 0.2,
+    });
+  });
+
+  it("keeps the Lite switch another page changed when this page edits the cap", async () => {
+    const server = startServer({ lite: { compressToolResults: true, maxToolLength: 8000 } });
+    await renderPage("lite");
+    server.write({ lite: { compressToolResults: false, maxToolLength: 8000 } });
+
+    fireEvent.change(inputFor("Maximum tool-result length"), { target: { value: "9000" } });
+    await save();
+
+    expect(server.puts.at(-1)?.status).toBe(200);
+    expect(server.stored.lite).toEqual({ compressToolResults: false, maxToolLength: 9000 });
+  });
+
+  it("turns Save off when the stored settings cannot be loaded", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    server.readsFail = true;
+    await renderPage("aggressive");
+
+    expect(
+      screen.getByText(
+        "Failed to load the saved settings. The fields show defaults, and Save stays off until the page reloads."
+      )
+    ).toBeTruthy();
+    expect((screen.getByRole("button", { name: "Save" }) as HTMLButtonElement).disabled).toBe(true);
+  });
+
+  it("ignores a settings read that fails after the page reloads", async () => {
+    const server = startServer({
+      ultra: DEFAULT_ULTRA_CONFIG,
+      aggressive: DEFAULT_AGGRESSIVE_CONFIG,
+    });
+    const failFirstRead = server.holdNextRead();
+    const { rerender } = render();
+    await settle();
+    // The page reloads before the first settings read answers.
+    rerender();
+    await settle();
+    failFirstRead();
+    await settle();
+
+    expect(screen.queryByText(/Failed to load/)).toBeNull();
+    expect((screen.getByRole("button", { name: "Save" }) as HTMLButtonElement).disabled).toBe(
+      false
+    );
+  });
+
+  it("keeps an edit after a rejected save, so the next save sends it", async () => {
+    const server = startServer({ ultra: DEFAULT_ULTRA_CONFIG });
+    await renderPage("ultra");
+    server.failNextWrite = "rejected";
+
+    fireEvent.change(inputFor("Compression rate"), { target: { value: "0.4" } });
+    await save();
+    expect(screen.getByText("Failed to save configuration.")).toBeTruthy();
+    expect(server.stored.ultra).toEqual(DEFAULT_ULTRA_CONFIG);
+
+    await save();
+
+    expect(server.stored.ultra).toEqual({ ...DEFAULT_ULTRA_CONFIG, compressionRate: 0.4 });
+  });
+
+  it("sends a field again after a save the server applied but answered with an error", async () => {
+    const server = startServer({ ultra: DEFAULT_ULTRA_CONFIG });
+    await renderPage("ultra");
+    server.failNextWrite = "applied";
+
+    fireEvent.change(inputFor("Compression rate"), { target: { value: "0.4" } });
+    await save();
+    expect(screen.getByText("Failed to save configuration.")).toBeTruthy();
+    expect((server.stored.ultra as Settings).compressionRate).toBe(0.4);
+
+    // The operator puts the loaded value back and saves again.
+    fireEvent.change(inputFor("Compression rate"), {
+      target: { value: String(DEFAULT_ULTRA_CONFIG.compressionRate) },
+    });
+    await save();
+
+    expect(server.stored.ultra).toEqual(DEFAULT_ULTRA_CONFIG);
+  });
+
+  it("keeps another page's value when a keystroke lands as a save finishes", async () => {
+    const server = startServer({ aggressive: DEFAULT_AGGRESSIVE_CONFIG });
+    await renderPage("aggressive");
+    // Another page changes a field this page shows, after it loaded.
+    server.write({ aggressive: { ...DEFAULT_AGGRESSIVE_CONFIG, maxTokensPerMessage: 1024 } });
+    const release = server.holdWrites();
+    fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "0.2" } });
+    await save();
+
+    // The response arrives, and a keystroke lands before the page re-renders.
+    await act(async () => {
+      release();
+      await new Promise((resolve) => setTimeout(resolve, 0));
+      fireEvent.change(inputFor("Minimum savings threshold"), { target: { value: "0.3" } });
+    });
+    await settle();
+
+    expect(inputFor("Maximum tokens per message").value).toBe("1024");
+    await save();
+
+    expect(server.stored.aggressive).toEqual({
+      ...DEFAULT_AGGRESSIVE_CONFIG,
+      maxTokensPerMessage: 1024,
+      minSavingsThreshold: 0.3,
+    });
+  });
+
+  it("previews the Ultra engine on a default install that has no model path", async () => {
+    const server = startServer({ ultra: DEFAULT_ULTRA_CONFIG });
+    await renderPage("ultra");
+
+    fireEvent.click(screen.getByRole("button", { name: "Preview" }));
+    await settle();
+
+    expect(server.previews.at(-1)?.status).toBe(200);
+    expect(screen.queryByText("Preview failed.")).toBeNull();
+    expect(screen.getByText("compressed text")).toBeTruthy();
+  });
+});
diff --git a/tests/unit/ui/engineConfigForm.test.tsx b/tests/unit/ui/engineConfigForm.test.tsx
index e7503c6bbfac..3af9f5c2209d 100644
--- a/tests/unit/ui/engineConfigForm.test.tsx
+++ b/tests/unit/ui/engineConfigForm.test.tsx
@@ -125,6 +125,9 @@ describe("EngineConfigForm", () => {
     expect(numInput).toBeTruthy();
     expect(numInput?.getAttribute("min")).toBe("100");
     expect(numInput?.getAttribute("max")).toBe("8000");
+    // step=any so fractional settings (e.g. CCR retrievalRampFactor 1.5) pass
+    // browser step validation, whose default step is 1.
+    expect(numInput?.getAttribute("step")).toBe("any");
   });
 
   it("renders a text input for the string field", async () => {
@@ -193,7 +196,7 @@ describe("EngineConfigForm", () => {
       checkbox.dispatchEvent(new MouseEvent("click", { bubbles: true }));
     });
 
-    expect(onChange).toHaveBeenCalledWith({ ...INITIAL_VALUE, enabled: true });
+    expect(onChange).toHaveBeenCalledWith("enabled", true);
   });
 
   it("calls onChange with new number when number input changes", async () => {
@@ -217,7 +220,31 @@ describe("EngineConfigForm", () => {
       numInput.dispatchEvent(new Event("change", { bubbles: true }));
     });
 
-    expect(onChange).toHaveBeenCalledWith({ ...INITIAL_VALUE, maxTokens: 2048 });
+    expect(onChange).toHaveBeenCalledWith("maxTokens", 2048);
+  });
+
+  it("calls onChange with a fractional number when the number input changes", async () => {
+    const { EngineConfigForm } =
+      await import("../../../src/shared/components/compression/EngineConfigForm");
+    const onChange = vi.fn();
+    const container = mount(
+      
+    );
+
+    const numInput = container.querySelector("input[type='number']") as HTMLInputElement;
+    expect(numInput).toBeTruthy();
+
+    const nativeSetter = Object.getOwnPropertyDescriptor(
+      window.HTMLInputElement.prototype,
+      "value"
+    )?.set;
+
+    act(() => {
+      nativeSetter?.call(numInput, "1.5");
+      numInput.dispatchEvent(new Event("change", { bubbles: true }));
+    });
+
+    expect(onChange).toHaveBeenCalledWith("maxTokens", 1.5);
   });
 
   it("calls onChange with updated array when a multiselect option is checked", async () => {
@@ -241,9 +268,6 @@ describe("EngineConfigForm", () => {
       stripCommentsCheckbox.dispatchEvent(new MouseEvent("click", { bubbles: true }));
     });
 
-    expect(onChange).toHaveBeenCalledWith({
-      ...INITIAL_VALUE,
-      techniques: ["strip-comments"],
-    });
+    expect(onChange).toHaveBeenCalledWith("techniques", ["strip-comments"]);
   });
 });
diff --git a/tests/unit/ui/engineConfigPage.test.tsx b/tests/unit/ui/engineConfigPage.test.tsx
index f4a3c84b7a22..386bf1f2c9a3 100644
--- a/tests/unit/ui/engineConfigPage.test.tsx
+++ b/tests/unit/ui/engineConfigPage.test.tsx
@@ -260,6 +260,160 @@ describe("EngineConfigPage", () => {
     expect(container.textContent).toContain("duplicated details and verbose wording");
   });
 
+  it("preview strips empty-string form values from the sent config (ultra.modelPath default '')", async () => {
+    // The engine schema seeds modelPath with defaultValue "" (ultraConfigSchema
+    // requires min(1) when present), so sending the raw form state makes the
+    // preview request 400 before dispatch — the page must omit empty values.
+    const ULTRA_PAYLOAD = {
+      engines: [
+        {
+          id: "ultra",
+          name: "Ultra",
+          description: "Ultra engine",
+          icon: "⚡",
+          stackable: true,
+          stackPriority: 40,
+          metadata: { description: "Ultra metadata" },
+          configSchema: [
+            { key: "compressionRate", type: "number", label: "Rate", defaultValue: 0.5 },
+            { key: "modelPath", type: "string", label: "Model path", defaultValue: "" },
+          ],
+        },
+      ],
+    };
+    const previewBodies: Record[] = [];
+    vi.spyOn(globalThis, "fetch").mockImplementation(
+      async (input: RequestInfo | URL, init?: RequestInit) => {
+        const url = input.toString();
+        if (url.includes("/api/compression/preview")) {
+          previewBodies.push(JSON.parse(String(init?.body ?? "{}")) as Record);
+          return new Response(
+            JSON.stringify({
+              original: "o",
+              compressed: "c",
+              originalTokens: 1,
+              compressedTokens: 1,
+              savingsPct: 0,
+              diff: [],
+            }),
+            { status: 200, headers: { "Content-Type": "application/json" } }
+          );
+        }
+        if (url.includes("/api/compression/engines")) {
+          return new Response(JSON.stringify(ULTRA_PAYLOAD), {
+            status: 200,
+            headers: { "Content-Type": "application/json" },
+          });
+        }
+        if (url.includes("/api/settings/compression")) {
+          return new Response(JSON.stringify({}), {
+            status: 200,
+            headers: { "Content-Type": "application/json" },
+          });
+        }
+        return new Response(JSON.stringify({}), { status: 404 });
+      }
+    );
+    const { EngineConfigPage } =
+      await import("../../../src/shared/components/compression/EngineConfigPage");
+
+    let container!: HTMLElement;
+    await act(async () => {
+      container = mountInContainer();
+    });
+
+    await act(async () => {
+      await Promise.resolve();
+    });
+
+    const previewButton = Array.from(container.querySelectorAll("button")).find(
+      (button) => button.textContent === "Preview"
+    );
+    expect(previewButton).toBeTruthy();
+
+    await act(async () => {
+      previewButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+      await Promise.resolve();
+    });
+
+    expect(previewBodies).toHaveLength(1);
+    const sentConfig = (previewBodies[0].config as { ultra?: Record }).ultra;
+    expect(sentConfig).toBeTruthy();
+    expect(sentConfig?.modelPath).toBeUndefined();
+  });
+
+  it("save strips empty-string form values from the PUT body (ultra.modelPath default '')", async () => {
+    // Same schema constraint as the preview case: settings PUT validates the
+    // ultra sub-object with ultraConfigSchema, so a default-state save with
+    // modelPath "" would 400 before the operator changes anything.
+    const ULTRA_PAYLOAD = {
+      engines: [
+        {
+          id: "ultra",
+          name: "Ultra",
+          description: "Ultra engine",
+          icon: "⚡",
+          stackable: true,
+          stackPriority: 40,
+          metadata: { description: "Ultra metadata" },
+          configSchema: [
+            { key: "compressionRate", type: "number", label: "Rate", defaultValue: 0.5 },
+            { key: "modelPath", type: "string", label: "Model path", defaultValue: "" },
+          ],
+        },
+      ],
+    };
+    const settingsPuts: Record[] = [];
+    vi.spyOn(globalThis, "fetch").mockImplementation(
+      async (input: RequestInfo | URL, init?: RequestInit) => {
+        const url = input.toString();
+        if (url.includes("/api/settings/compression")) {
+          if (init?.method === "PUT") {
+            settingsPuts.push(JSON.parse(init.body as string) as Record);
+          }
+          return new Response(JSON.stringify({}), {
+            status: 200,
+            headers: { "Content-Type": "application/json" },
+          });
+        }
+        if (url.includes("/api/compression/engines")) {
+          return new Response(JSON.stringify(ULTRA_PAYLOAD), {
+            status: 200,
+            headers: { "Content-Type": "application/json" },
+          });
+        }
+        return new Response(JSON.stringify({}), { status: 404 });
+      }
+    );
+    const { EngineConfigPage } =
+      await import("../../../src/shared/components/compression/EngineConfigPage");
+
+    let container!: HTMLElement;
+    await act(async () => {
+      container = mountInContainer();
+    });
+
+    await act(async () => {
+      await Promise.resolve();
+    });
+
+    const saveButton = Array.from(container.querySelectorAll("button")).find(
+      (b) => b.textContent?.includes("Save") || b.textContent?.includes("Salvar")
+    );
+    expect(saveButton).toBeTruthy();
+    await act(async () => {
+      saveButton?.click();
+    });
+    await act(async () => {
+      await Promise.resolve();
+    });
+
+    expect(settingsPuts.length).toBeGreaterThan(0);
+    const ultraPut = settingsPuts.find((c) => typeof c.ultra === "object" && c.ultra !== null);
+    expect(ultraPut).toBeTruthy();
+    expect((ultraPut?.ultra as Record).modelPath).toBeUndefined();
+  });
+
   it("shows empty-state text when analytics returns runs=0", async () => {
     setupFetchMock();
     const { EngineConfigPage } =
diff --git a/tests/unit/ui/namedCombos-active-badge.test.tsx b/tests/unit/ui/namedCombos-active-badge.test.tsx
index ae9970e39d48..3c797f461f4e 100644
--- a/tests/unit/ui/namedCombos-active-badge.test.tsx
+++ b/tests/unit/ui/namedCombos-active-badge.test.tsx
@@ -19,7 +19,9 @@ function mount(ui: React.ReactElement): HTMLElement {
 }
 
 beforeEach(() => {
-  (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
+  (
+    globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+  ).IS_REACT_ACT_ENVIRONMENT = true;
 });
 
 afterEach(async () => {
@@ -55,23 +57,26 @@ function setupFetchMock() {
   const json = (body: unknown, status = 200) =>
     new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
   const combos = [combo("c1", "Alpha"), combo("c2", "Bravo")];
-  vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL, init?: RequestInit) => {
-    const url = input.toString();
-    void init;
-    if (url.includes("/api/context/combos/") && url.includes("/assignments")) return json({ assignments: [] });
-    if (url.includes("/api/context/combos")) return json({ combos });
-    if (url.includes("/api/combos")) return json({ combos: [] });
-    if (url.includes("/api/compression/language-packs")) return json({ packs: [] });
-    if (url.includes("/api/settings/compression")) return json({ activeComboId: "c2", enabled: true });
-    return json({}, 404);
-  });
+  vi.spyOn(globalThis, "fetch").mockImplementation(
+    async (input: RequestInfo | URL, init?: RequestInit) => {
+      const url = input.toString();
+      void init;
+      if (url.includes("/api/context/combos/") && url.includes("/assignments"))
+        return json({ assignments: [] });
+      if (url.includes("/api/context/combos")) return json({ combos });
+      if (url.includes("/api/combos")) return json({ combos: [] });
+      if (url.includes("/api/compression/language-packs")) return json({ packs: [] });
+      if (url.includes("/api/settings/compression"))
+        return json({ activeComboId: "c2", enabled: true });
+      return json({}, 404);
+    }
+  );
 }
 
 describe("NamedCombosManager — active badge, no set-as-default", () => {
   async function render() {
-    const { default: CompressionCombosPageClient } = await import(
-      "../../../src/app/(dashboard)/dashboard/context/combos/CompressionCombosPageClient"
-    );
+    const { default: CompressionCombosPageClient } =
+      await import("../../../src/app/(dashboard)/dashboard/context/combos/CompressionCombosPageClient");
     let container!: HTMLElement;
     await act(async () => {
       container = mount();
@@ -93,3 +98,151 @@ describe("NamedCombosManager — active badge, no set-as-default", () => {
     expect(container.querySelector('[data-testid="active-badge-c1"]')).toBeNull();
   });
 });
+
+describe("NamedCombosManager when the settings GET fails", () => {
+  // The settings GET fails as it does while the server restarts. Its defaults
+  // (activeComboId null, compressionEnabled false) suppress the master-switch warning,
+  // hide the Active badge and disable the override selects, so a failed load must offer
+  // a retry instead of that wrong view.
+  const FAILURES = {
+    "a 500": async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 }),
+    "a network error": async () => {
+      throw new TypeError("Failed to fetch");
+    },
+  };
+
+  function json(body: unknown, status = 200) {
+    return new Response(JSON.stringify(body), {
+      status,
+      headers: { "Content-Type": "application/json" },
+    });
+  }
+
+  async function mountPage() {
+    const { default: CompressionCombosPageClient } =
+      await import("../../../src/app/(dashboard)/dashboard/context/combos/CompressionCombosPageClient");
+    let container!: HTMLElement;
+    await act(async () => {
+      container = mount();
+    });
+    await flush();
+    return container;
+  }
+
+  function retryButton(container: HTMLElement) {
+    return Array.from(container.querySelectorAll("button")).find(
+      (button) => button.textContent === "Retry"
+    );
+  }
+
+  // The page mounts the Hub and the manager side by side; when the settings GET fails
+  // for both, each shows its own Retry, and every one must be pressed to recover.
+  async function clickAllRetryButtons(container: HTMLElement) {
+    for (;;) {
+      const retry = retryButton(container);
+      if (!retry) return;
+      await act(async () => {
+        retry.click();
+      });
+      await flush();
+    }
+  }
+
+  // The Hub's settings GET fires before the manager's, so the first two calls are the
+  // ones that fail; both retries reach the stored row.
+  function settingsFetch(settingsCalls: { count: number }, fail: () => Promise) {
+    return vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => {
+      const url = input.toString();
+      if (url.includes("/api/settings/compression")) {
+        settingsCalls.count += 1;
+        if (settingsCalls.count <= 2) return fail();
+        return json({ activeComboId: "c2", enabled: false });
+      }
+      if (url.includes("/api/context/combos/")) return json({ assignments: [] });
+      if (url.includes("/api/context/combos")) return json({ combos: [combo("c2", "Bravo")] });
+      if (url.includes("/api/combos")) return json({ combos: [] });
+      if (url.includes("/api/compression/language-packs")) return json({ packs: [] });
+      return json({}, 404);
+    });
+  }
+
+  it.each(Object.keys(FAILURES) as Array)(
+    "shows a retry in place of the defaulted manager after %s",
+    { timeout: 20000 },
+    async (failure) => {
+      vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => {
+        const url = input.toString();
+        if (url.includes("/api/settings/compression")) return FAILURES[failure]();
+        if (url.includes("/api/context/combos/")) return json({ assignments: [] });
+        if (url.includes("/api/context/combos")) return json({ combos: [combo("c2", "Bravo")] });
+        if (url.includes("/api/combos")) return json({ combos: [] });
+        if (url.includes("/api/compression/language-packs")) return json({ packs: [] });
+        return json({}, 404);
+      });
+      const container = await mountPage();
+
+      expect(container.textContent).toContain("Failed To Load");
+      expect(
+        container.querySelector('[data-testid="compression-master-switch-warning"]')
+      ).toBeNull();
+      expect(container.querySelector('[data-testid="active-badge-c2"]')).toBeNull();
+      // No control that depends on the loaded settings stays on screen.
+      expect(container.querySelectorAll("select")).toHaveLength(0);
+      expect(retryButton(container)).toBeTruthy();
+    }
+  );
+
+  it("shows the master-switch warning and the Active badge once Retry answers", async () => {
+    const settingsCalls = { count: 0 };
+    settingsFetch(settingsCalls, FAILURES["a 500"]);
+    const container = await mountPage();
+    expect(container.textContent).toContain("Failed To Load");
+
+    await clickAllRetryButtons(container);
+
+    expect(container.textContent).not.toContain("Failed To Load");
+    expect(
+      container.querySelector('[data-testid="compression-master-switch-warning"]')
+    ).toBeTruthy();
+    expect(container.querySelector('[data-testid="active-badge-c2"]')).toBeTruthy();
+  });
+
+  it("ignores a routing-combos answer from the load a retry superseded", async () => {
+    let settingsCalls = 0;
+    let releaseStaleRouting!: (response: Response) => void;
+    vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => {
+      const url = input.toString();
+      if (url.includes("/api/settings/compression")) {
+        settingsCalls += 1;
+        if (settingsCalls <= 2) return FAILURES["a 500"]();
+        return json({ activeComboId: "c2", enabled: false });
+      }
+      if (url.includes("/api/context/combos/")) return json({ assignments: [] });
+      if (url.includes("/api/context/combos")) return json({ combos: [combo("c2", "Bravo")] });
+      if (url.includes("/api/combos")) {
+        // The first load's routing-combos GET stays open until after the retried load
+        // has finished; its answer belongs to the run the retry superseded.
+        if (!releaseStaleRouting) {
+          return new Promise((resolve) => {
+            releaseStaleRouting = (response) => resolve(response);
+          });
+        }
+        return json({ combos: [] });
+      }
+      if (url.includes("/api/compression/language-packs")) return json({ packs: [] });
+      return json({}, 404);
+    });
+    const container = await mountPage();
+    await clickAllRetryButtons(container);
+    expect(
+      container.querySelector('[data-testid="compression-master-switch-warning"]')
+    ).toBeTruthy();
+
+    await act(async () => {
+      releaseStaleRouting(json({ combos: [{ id: "stale", name: "StaleRoute", config: null }] }));
+    });
+    await flush();
+
+    expect(container.textContent).not.toContain("StaleRoute");
+  });
+});
diff --git a/tests/unit/ui/omniglyphContextPage.test.tsx b/tests/unit/ui/omniglyphContextPage.test.tsx
index d3893947b94d..b1e82f748f42 100644
--- a/tests/unit/ui/omniglyphContextPage.test.tsx
+++ b/tests/unit/ui/omniglyphContextPage.test.tsx
@@ -51,16 +51,29 @@ interface CapturedPut {
   body: Record;
 }
 
-function setupFetchMock(): { puts: CapturedPut[] } {
+// putStatus(n) sets the HTTP status of the n-th PUT (1-based); every PUT succeeds by default.
+// rejectPuts makes every PUT throw instead, the network-failure path.
+function setupFetchMock(
+  options: {
+    putStatus?: (n: number) => number;
+    rejectPuts?: boolean;
+    initialEngines?: Record;
+  } = {}
+): {
+  puts: CapturedPut[];
+} {
   const puts: CapturedPut[] = [];
   const json = (body: unknown, status = 200) =>
     new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
 
-  // omniglyph absent from `engines` initially → disabled; rtk/caveman present so we can
-  // assert they SURVIVE the PUT (the store persists the whole map as one row).
+  // omniglyph is absent from `engines`, so the switch starts off; rtk and caveman are present
+  // so a test can check that a toggle PUT leaves them out.
   const initialConfig = {
     enabled: true,
-    engines: { rtk: { enabled: true, level: "standard" }, caveman: { enabled: false } },
+    engines: options.initialEngines ?? {
+      rtk: { enabled: true, level: "standard" },
+      caveman: { enabled: false },
+    },
   };
 
   vi.spyOn(globalThis, "fetch").mockImplementation(
@@ -69,9 +82,11 @@ function setupFetchMock(): { puts: CapturedPut[] } {
       const method = (init?.method ?? "GET").toUpperCase();
       if (url.includes("/api/settings/compression")) {
         if (method === "PUT") {
+          if (options.rejectPuts) throw new TypeError("fetch failed");
           const body = JSON.parse(String(init?.body ?? "{}"));
           puts.push({ url, body });
-          return json({ ...initialConfig, ...body });
+          const status = options.putStatus?.(puts.length) ?? 200;
+          return status === 200 ? json({ ...initialConfig, ...body }) : json({}, status);
         }
         return json(initialConfig);
       }
@@ -81,13 +96,38 @@ function setupFetchMock(): { puts: CapturedPut[] } {
   return { puts };
 }
 
+async function mountPage(): Promise {
+  const { default: Page } =
+    await import("../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient");
+  let container!: HTMLElement;
+  await act(async () => {
+    container = mount();
+  });
+  await flush();
+  return container;
+}
+
+function enableToggle(container: HTMLElement): HTMLButtonElement {
+  const toggle = container.querySelector(
+    '[data-testid="omniglyph-enable-toggle"] button'
+  ) as HTMLButtonElement | null;
+  expect(toggle, "enable toggle button must exist").toBeTruthy();
+  return toggle!;
+}
+
+async function click(button: HTMLButtonElement) {
+  await act(async () => {
+    button.click();
+  });
+  await flush();
+}
+
 // ── Tests ──────────────────────────────────────────────────────────────────────
 describe("OmniglyphContextPage", () => {
   it("renders the four sections with the measured numbers and the real render", async () => {
     setupFetchMock();
-    const { default: Page } = await import(
-      "../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient"
-    );
+    const { default: Page } =
+      await import("../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient");
     let container!: HTMLElement;
     await act(async () => {
       container = mount();
@@ -117,30 +157,83 @@ describe("OmniglyphContextPage", () => {
     expect(container.querySelector('[data-testid="omniglyph-enable-toggle"]')).toBeTruthy();
   });
 
-  it("enabling the engine PUTs the full engines map with omniglyph on, preserving the others", async () => {
+  it("enabling the engine PUTs only the omniglyph entry", async () => {
     const { puts } = setupFetchMock();
-    const { default: Page } = await import(
-      "../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient"
-    );
-    let container!: HTMLElement;
-    await act(async () => {
-      container = mount();
-    });
-    await flush();
+    const container = await mountPage();
 
-    const toggle = container.querySelector('[data-testid="omniglyph-enable-toggle"] button') as HTMLButtonElement | null;
-    expect(toggle, "enable toggle button must exist").toBeTruthy();
-    await act(async () => {
-      toggle!.click();
+    await click(enableToggle(container));
+
+    expect(puts.length).toBe(1);
+    // The server merges engines by id, so the page sends only the engine it changed and
+    // cannot overwrite engines another page changed after this one loaded.
+    expect(puts[0]!.body).toEqual({ engines: { omniglyph: { enabled: true } } });
+  });
+
+  it("disabling the engine PUTs only the omniglyph entry, off", async () => {
+    const { puts } = setupFetchMock({
+      initialEngines: {
+        omniglyph: { enabled: true, level: "standard" },
+        rtk: { enabled: true },
+      },
     });
-    await flush();
+    const container = await mountPage();
+    const toggle = enableToggle(container);
+    expect(toggle.getAttribute("aria-checked")).toBe("true");
+
+    await click(toggle);
 
     expect(puts.length).toBe(1);
-    const engines = puts[0]!.body.engines as Record;
-    expect(engines.omniglyph).toEqual({ enabled: true });
-    // The other engines must survive the whole-map PUT.
-    expect(engines.rtk?.enabled).toBe(true);
-    expect(engines.caveman?.enabled).toBe(false);
+    // The off toggle also sends only its own entry; a stored level survives server-side.
+    expect(puts[0]!.body).toEqual({ engines: { omniglyph: { enabled: false } } });
+    expect(toggle.getAttribute("aria-checked")).toBe("false");
+  });
+
+  it("a rejected fetch is a failed save: the switch rolls back and the error shows", async () => {
+    const { puts } = setupFetchMock({ rejectPuts: true });
+    const container = await mountPage();
+    const toggle = enableToggle(container);
+
+    await click(toggle);
+
+    expect(puts.length).toBe(0);
+    expect(toggle.getAttribute("aria-checked")).toBe("false");
+    expect(container.textContent).toContain("Could not save.");
+  });
+
+  it("a failed save puts the switch back and shows the error", async () => {
+    const { puts } = setupFetchMock({ putStatus: () => 500 });
+    const container = await mountPage();
+    const toggle = enableToggle(container);
+
+    await click(toggle);
+
+    expect(puts.length).toBe(1);
+    expect(toggle.getAttribute("aria-checked")).toBe("false");
+    expect(container.textContent).toContain("Could not save.");
+  });
+
+  it("a timer left by an earlier save does not clear a later save error", async () => {
+    vi.useFakeTimers();
+    try {
+      const { puts } = setupFetchMock({ putStatus: (n) => (n === 1 ? 200 : 500) });
+      const container = await mountPage();
+      const toggle = enableToggle(container);
+
+      await click(toggle);
+      expect(container.textContent).toContain("Saved.");
+
+      await click(toggle);
+      expect(puts.length).toBe(2);
+      expect(container.textContent).toContain("Could not save.");
+
+      await act(async () => {
+        vi.advanceTimersByTime(2000);
+      });
+      await flush();
+      expect(container.textContent).toContain("Could not save.");
+    } finally {
+      vi.useRealTimers();
+    }
   });
 
   it("carrega o perfil salvo e faz PATCH só do perfil, sem reescrever o mapa de engines", async () => {
@@ -170,9 +263,8 @@ describe("OmniglyphContextPage", () => {
       }
     );
 
-    const { default: Page } = await import(
-      "../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient"
-    );
+    const { default: Page } =
+      await import("../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient");
     let container!: HTMLElement;
     await act(async () => {
       container = mount();
@@ -205,3 +297,139 @@ describe("OmniglyphContextPage", () => {
     expect(puts[0]!.body.engines).toBeUndefined();
   });
 });
+
+describe("OmniglyphContextPage when the settings GET fails", () => {
+  // The page's only load is the settings GET, and it fails as it does while the server
+  // restarts. Later requests, including a retried GET, reach the stored row.
+  const FAILURES = {
+    "a 500": async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 }),
+    "a network error": async () => {
+      throw new TypeError("Failed to fetch");
+    },
+  };
+
+  function failFirstSettingsGet(failure: keyof typeof FAILURES) {
+    const { puts } = setupFetchMock();
+    vi.mocked(globalThis.fetch).mockImplementationOnce(FAILURES[failure]);
+    return puts;
+  }
+
+  async function mountPage() {
+    const { default: Page } =
+      await import("../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient");
+    let container!: HTMLElement;
+    await act(async () => {
+      container = mount();
+    });
+    await flush();
+    return container;
+  }
+
+  function retryButton(container: HTMLElement) {
+    return Array.from(container.querySelectorAll("button")).find(
+      (button) => button.textContent === "Retry"
+    );
+  }
+
+  function toggleButton(container: HTMLElement) {
+    return container.querySelector(
+      '[data-testid="omniglyph-enable-toggle"] button'
+    ) as HTMLButtonElement | null;
+  }
+
+  it.each(Object.keys(FAILURES) as Array)(
+    "offers no control that would save defaults over the stored engines after %s",
+    async (failure) => {
+      const puts = failFirstSettingsGet(failure);
+      const container = await mountPage();
+
+      // The failed load leaves engines {}: a toggle still on the page PUTs
+      // {omniglyph:{enabled:true}} alone, and the store keeps only that engine.
+      const toggle = toggleButton(container);
+      if (toggle) {
+        await act(async () => toggle.click());
+        await flush();
+      }
+
+      expect(puts, "a save before any GET succeeds overwrites the stored engines").toEqual([]);
+      expect(container.querySelector('[data-testid="omniglyph-profile-select"]')).toBeNull();
+      expect(container.textContent).toContain("Failed To Load");
+      expect(retryButton(container), "the load error offers a retry").toBeTruthy();
+    }
+  );
+
+  it("shows a retry that loads the stored settings before the controls return", async () => {
+    const puts = failFirstSettingsGet("a 500");
+    const container = await mountPage();
+    expect(container.textContent).toContain("Failed To Load");
+
+    // The retried GET stays open, and the controls stay out until it answers.
+    let release!: () => Promise;
+    vi.mocked(globalThis.fetch).mockImplementationOnce(
+      () =>
+        new Promise((resolve) => {
+          release = async () => {
+            await act(async () =>
+              resolve(
+                new Response(
+                  JSON.stringify({
+                    enabled: true,
+                    engines: {
+                      rtk: { enabled: true, level: "standard" },
+                      caveman: { enabled: false },
+                    },
+                  }),
+                  { status: 200, headers: { "Content-Type": "application/json" } }
+                )
+              )
+            );
+            await flush();
+          };
+        })
+    );
+    await act(async () => retryButton(container)!.click());
+    await flush();
+    expect(toggleButton(container), "the controls wait for the retried GET").toBeNull();
+
+    await release();
+    expect(toggleButton(container)).toBeTruthy();
+    // Saves go out again once a GET has succeeded. The server merges engines by id,
+    // so the page sends only the engine this toggle changed.
+    await act(async () => toggleButton(container)!.click());
+    await flush();
+    expect(puts[0]!.body).toEqual({ engines: { omniglyph: { enabled: true } } });
+  });
+
+  it("ignores an answer from the load that a retry superseded", async () => {
+    failFirstSettingsGet("a 500");
+    const container = await mountPage();
+
+    // The retried GET stays open, so a second retry can start while it is pending.
+    let releaseStale!: () => Promise;
+    vi.mocked(globalThis.fetch).mockImplementationOnce(
+      () =>
+        new Promise((resolve) => {
+          releaseStale = async () => {
+            await act(async () =>
+              resolve(
+                new Response(JSON.stringify({ engines: { omniglyph: { enabled: true } } }), {
+                  status: 200,
+                  headers: { "Content-Type": "application/json" },
+                })
+              )
+            );
+            await flush();
+          };
+        })
+    );
+    await act(async () => retryButton(container)!.click());
+    await flush();
+    await act(async () => retryButton(container)!.click());
+    await flush();
+
+    // The first retry's answer lands after the second retry's; it must be dropped.
+    await releaseStale();
+    const toggle = toggleButton(container);
+    expect(toggle?.getAttribute("aria-checked"), "the superseded answer is dropped").toBe("false");
+  });
+});
diff --git a/tests/unit/ui/pool-upstream-regime-line.test.tsx b/tests/unit/ui/pool-upstream-regime-line.test.tsx
new file mode 100644
index 000000000000..afbab6a00a6c
--- /dev/null
+++ b/tests/unit/ui/pool-upstream-regime-line.test.tsx
@@ -0,0 +1,133 @@
+// @vitest-environment jsdom
+import React, { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+vi.mock("next-intl", () => ({
+  useTranslations: () => (key: string, values?: Record) =>
+    values ? `${key}:${JSON.stringify(values)}` : key,
+}));
+
+// The component is new in this change: resolve it through an indirection Vite
+// cannot statically analyze, so the file loads on the base tree too and every
+// case fails on an assertion (not on the import) when the component is absent.
+const REGIME_LINE_PATH =
+  "@/app/(dashboard)/dashboard/settings/components/" + "PoolUpstreamRegimeLine";
+let PoolUpstreamRegimeLine: React.ComponentType<{ provider: string }>;
+try {
+  ({ PoolUpstreamRegimeLine } = await import(/* @vite-ignore */ REGIME_LINE_PATH));
+} catch {
+  const MissingRegimeLine = () => null;
+  MissingRegimeLine.displayName = "MissingRegimeLine";
+  PoolUpstreamRegimeLine = MissingRegimeLine;
+}
+
+let root: Root | null = null;
+let container: HTMLElement | null = null;
+
+async function renderWith(provider: string, fetchImpl: (...args: unknown[]) => Promise) {
+  const fetchMock = vi.fn(fetchImpl);
+  vi.stubGlobal("fetch", fetchMock);
+  container = document.createElement("div");
+  document.body.appendChild(container);
+  root = createRoot(container);
+  await act(async () => {
+    root!.render(React.createElement(PoolUpstreamRegimeLine, { provider }));
+  });
+  await act(async () => {
+    await new Promise((resolve) => setTimeout(resolve, 0));
+  });
+  return { fetchMock, element: container };
+}
+
+function jsonResponse(body: unknown, ok = true) {
+  return Promise.resolve({ ok, json: () => Promise.resolve(body) });
+}
+
+function uniformRegime(overrides: Record = {}) {
+  return {
+    provider: "acme",
+    windowHours: 1,
+    attempts: 90,
+    measured: 90,
+    share5xx: 0.5,
+    exitsTouched: 15,
+    exitsWithTraffic: 15,
+    affectedExits: 15,
+    uniform: true,
+    state: "measured",
+    ...overrides,
+  };
+}
+
+describe("PoolUpstreamRegimeLine", () => {
+  beforeEach(() => {
+    (
+      globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+    ).IS_REACT_ACT_ENVIRONMENT = true;
+  });
+
+  afterEach(async () => {
+    await act(async () => {
+      root?.unmount();
+    });
+    root = null;
+    container?.remove();
+    container = null;
+    vi.unstubAllGlobals();
+  });
+
+  it("reads the dedicated route and shows one line for a uniform regime", async () => {
+    const { fetchMock, element } = await renderWith("acme", () => jsonResponse(uniformRegime()));
+    expect(fetchMock).toHaveBeenCalledWith(
+      "/api/settings/proxies/pool/uniform-egress?provider=acme"
+    );
+    expect(element.textContent).toBe(
+      'uniformEgressLine:{"affected":15,"exits":15,"provider":"acme","hours":1}'
+    );
+  });
+
+  it("renders nothing when the regime is not uniform", async () => {
+    const { element } = await renderWith("acme", () =>
+      jsonResponse(uniformRegime({ uniform: false }))
+    );
+    expect(element.textContent).toBe("");
+  });
+
+  it("renders nothing when the window is unmeasured", async () => {
+    const { element } = await renderWith("acme", () =>
+      jsonResponse(uniformRegime({ state: "unmeasured", uniform: false }))
+    );
+    expect(element.textContent).toBe("");
+  });
+
+  it("renders nothing when the route answers null", async () => {
+    const { element } = await renderWith("acme", () => jsonResponse(null));
+    expect(element.textContent).toBe("");
+  });
+
+  it("renders nothing for a malformed regime body", async () => {
+    const { element } = await renderWith("acme", () =>
+      jsonResponse({ uniform: true, provider: "acme" })
+    );
+    expect(element.textContent).toBe("");
+  });
+
+  it("renders nothing on an error status or a failed request", async () => {
+    const { element: onError } = await renderWith("acme", () =>
+      jsonResponse({ error: "nope" }, false)
+    );
+    expect(onError.textContent).toBe("");
+    await act(async () => {
+      root?.unmount();
+    });
+    root = null;
+    container?.remove();
+    container = null;
+    vi.unstubAllGlobals();
+    const { element: onFailure } = await renderWith("acme", () =>
+      Promise.reject(new Error("offline"))
+    );
+    expect(onFailure.textContent).toBe("");
+  });
+});
diff --git a/tests/unit/ui/proxy-health-cell-upstream-counts.test.tsx b/tests/unit/ui/proxy-health-cell-upstream-counts.test.tsx
new file mode 100644
index 000000000000..76a7bd4492f8
--- /dev/null
+++ b/tests/unit/ui/proxy-health-cell-upstream-counts.test.tsx
@@ -0,0 +1,65 @@
+// @vitest-environment jsdom
+import React, { act } from "react";
+import { createRoot } from "react-dom/client";
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
+import { NextIntlClientProvider } from "next-intl";
+import messages from "../../../src/i18n/messages/en.json";
+import { ProxyHealthCell } from "../../../src/app/(dashboard)/dashboard/settings/components/ProxyHealthCell";
+
+// The health cell shows upstream 4xx and 5xx as two separate counts so an
+// upstream-wide 5xx outage reads differently from client errors.
+
+const roots: Array<{ unmount: () => void }> = [];
+const containers: HTMLElement[] = [];
+
+function render(ui: React.ReactElement): HTMLElement {
+  const container = document.createElement("div");
+  document.body.appendChild(container);
+  containers.push(container);
+  const root = createRoot(container);
+  roots.push(root);
+  act(() => {
+    root.render(
+       {
+          throw error;
+        }}
+      >
+        {ui}
+      
+    );
+  });
+  return container;
+}
+
+beforeEach(() => {
+  (
+    globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+  ).IS_REACT_ACT_ENVIRONMENT = true;
+});
+
+afterEach(() => {
+  act(() => {
+    while (roots.length > 0) roots.pop()?.unmount();
+  });
+  while (containers.length > 0) containers.pop()?.remove();
+});
+
+describe("ProxyHealthCell upstream counts", () => {
+  it("shows upstream 4xx and 5xx as two separate counts", () => {
+    const el = render();
+    const text = el.textContent ?? "";
+    expect(text).toContain("2 upstream client errors");
+    expect(text).toContain("7 upstream server errors");
+    expect(text).not.toContain("upstream refusals");
+  });
+
+  it("shows zeros when the counts are missing", () => {
+    const el = render();
+    const text = el.textContent ?? "";
+    expect(text).toContain("0 upstream client errors");
+    expect(text).toContain("0 upstream server errors");
+  });
+});
diff --git a/tests/unit/ui/proxy-registry-manager-regime-mount.test.tsx b/tests/unit/ui/proxy-registry-manager-regime-mount.test.tsx
new file mode 100644
index 000000000000..d47934d04676
--- /dev/null
+++ b/tests/unit/ui/proxy-registry-manager-regime-mount.test.tsx
@@ -0,0 +1,158 @@
+// @vitest-environment jsdom
+import React, { act } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+const translate = (key: string) => key;
+
+vi.mock("next-intl", () => ({
+  useTranslations: () => translate,
+  useLocale: () => "en",
+}));
+
+// Imported STATICALLY on purpose (same reason as the credential-autofill
+// guard): the Vite transform of the manager tree is paid during collection,
+// not against the per-test timeout.
+import ProxyRegistryManager from "@/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager";
+
+function jsonResponse(body: unknown): Response {
+  return { ok: true, json: async () => body } as Response;
+}
+
+let root: Root;
+let container: HTMLDivElement;
+const fetched: string[] = [];
+
+async function waitFor(assertion: () => void, timeoutMs = 5000) {
+  const startedAt = Date.now();
+  let lastError: unknown;
+  while (Date.now() - startedAt <= timeoutMs) {
+    try {
+      assertion();
+      return;
+    } catch (error) {
+      lastError = error;
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 20));
+      });
+    }
+  }
+  throw lastError;
+}
+
+async function click(element: HTMLElement) {
+  await act(async () => {
+    element.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+  });
+}
+
+function findButton(text: string): HTMLButtonElement {
+  const button = Array.from(container.querySelectorAll("button")).find((candidate) =>
+    candidate.textContent?.includes(text)
+  );
+  if (!button) throw new Error(`Button not found: ${text}`);
+  return button;
+}
+
+function setInputValue(input: HTMLInputElement, value: string) {
+  const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
+  if (!setter) throw new Error("HTMLInputElement value setter is unavailable");
+  act(() => {
+    setter.call(input, value);
+    input.dispatchEvent(new Event("change", { bubbles: true }));
+  });
+}
+
+beforeEach(() => {
+  (
+    globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+  ).IS_REACT_ACT_ENVIRONMENT = true;
+  fetched.length = 0;
+  container = document.createElement("div");
+  document.body.appendChild(container);
+  root = createRoot(container);
+
+  vi.stubGlobal(
+    "fetch",
+    vi.fn(async (input: RequestInfo | URL) => {
+      const url = String(input);
+      fetched.push(url);
+      if (url === "/api/settings/proxies") {
+        return jsonResponse({ items: [] });
+      }
+      if (url.startsWith("/api/settings/proxies/pool?")) {
+        return jsonResponse({ members: [], strategy: "round-robin" });
+      }
+      if (url.startsWith("/api/settings/proxies/health")) {
+        return jsonResponse({ items: [] });
+      }
+      if (url.startsWith("/api/settings/proxies/assignments")) {
+        return jsonResponse({ items: [] });
+      }
+      if (url.startsWith("/api/settings/proxies/pool/egress-observation")) {
+        return jsonResponse(null);
+      }
+      if (url.startsWith("/api/settings/proxies/pool/member-egress")) {
+        return jsonResponse(null);
+      }
+      if (url.startsWith("/api/settings/proxies/pool/uniform-egress?provider=sentinel-provider")) {
+        return jsonResponse({
+          provider: "sentinel-provider",
+          windowHours: 1,
+          attempts: 90,
+          measured: 90,
+          share5xx: 0.5,
+          exitsTouched: 15,
+          exitsWithTraffic: 15,
+          affectedExits: 15,
+          uniform: true,
+          state: "measured",
+        });
+      }
+      if (url.startsWith("/api/settings/proxies/pool/uniform-egress")) {
+        return jsonResponse(null);
+      }
+      throw new Error(`Unexpected fetch: ${url}`);
+    })
+  );
+});
+
+afterEach(() => {
+  act(() => root.unmount());
+  container.remove();
+  vi.unstubAllGlobals();
+  vi.clearAllMocks();
+});
+
+describe("ProxyRegistryManager upstream regime mount", () => {
+  it(
+    "shows the regime line for a provider pool and fetches its provider",
+    { timeout: 60000 },
+    async () => {
+      await act(async () => {
+        root.render();
+      });
+      await click(findButton("managePool"));
+
+      const scopeIdInput = container.querySelector(
+        '[data-testid="proxy-registry-pool-scopeid"]'
+      );
+      expect(scopeIdInput).not.toBeNull();
+      setInputValue(scopeIdInput!, "sentinel-provider");
+      await click(
+        container.querySelector('[data-testid="proxy-registry-pool-load"]')!
+      );
+
+      await waitFor(() => {
+        const line = container.querySelector('[data-testid="proxy-registry-upstream-regime-line"]');
+        if (!line) throw new Error("regime line not mounted");
+        expect(line.textContent).toContain("uniformEgressLine");
+      });
+      expect(
+        fetched.some((url) =>
+          url.startsWith("/api/settings/proxies/pool/uniform-egress?provider=sentinel-provider")
+        )
+      ).toBe(true);
+    }
+  );
+});
diff --git a/tests/unit/ui/rtk-context-config-save.test.tsx b/tests/unit/ui/rtk-context-config-save.test.tsx
new file mode 100644
index 000000000000..ff7f4e75e478
--- /dev/null
+++ b/tests/unit/ui/rtk-context-config-save.test.tsx
@@ -0,0 +1,772 @@
+// @vitest-environment jsdom
+import type { ReactNode } from "react";
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import RtkContextPageClient from "@/app/(dashboard)/dashboard/context/rtk/RtkContextPageClient";
+import { rtkConfigSchema } from "@/shared/validation/compressionConfigSchemas";
+import { useNotificationStore } from "@/store/notificationStore";
+
+// next-intl echoes the key and any params, so labels and messages are their translation keys.
+// Keys from the settings namespace carry a "settings." prefix, so a test can tell which
+// namespace a message came from. One translator per namespace keeps a stable identity, as the
+// real hook does.
+vi.mock("next-intl", () => {
+  const contextRtk = (key: string, params?: Record) =>
+    params ? [key, ...Object.values(params)].join(" ") : key;
+  const settings = (key: string, params?: Record) =>
+    `settings.${contextRtk(key, params)}`;
+  return {
+    useTranslations: (namespace?: string) => (namespace === "settings" ? settings : contextRtk),
+    useLocale: () => "en",
+  };
+});
+
+vi.mock("@/shared/components", () => ({
+  SegmentedControl: ({
+    options,
+    onChange,
+  }: {
+    options: { value: string; label: string }[];
+    onChange: (value: string) => void;
+  }) => (
+    
+ {options.map((option) => ( + + ))} +
+ ), + Collapsible: ({ children }: { children?: ReactNode }) =>
{children}
, +})); + +vi.mock("@/app/(dashboard)/dashboard/context/rtk/RtkLearnDiscoverCard", () => ({ + default: () => null, +})); +vi.mock("@/app/(dashboard)/dashboard/context/rtk/RtkTomlImportCard", () => ({ + default: () => null, +})); + +type Json = Record; + +const STORED_CONFIG: Json = { + enabled: true, + intensity: "standard", + applyToToolResults: true, + applyToAssistantMessages: false, + applyToCodeBlocks: false, + enabledFilters: [], + disabledFilters: [], + maxLinesPerResult: 200, + maxCharsPerResult: 20000, + deduplicateThreshold: 5, + customFiltersEnabled: false, + trustProjectFilters: false, + rawOutputRetention: "failures", + rawOutputMaxBytes: 65536, +}; + +const FILTERS = [ + { + id: "git-status", + name: "Git status", + description: "Trims working-tree listings", + commandTypes: ["git"], + category: "git", + priority: 10, + }, + { + id: "npm-install", + name: "npm install", + description: "Trims dependency logs", + commandTypes: ["npm"], + category: "npm", + priority: 20, + }, +]; + +function respond(data: unknown, status = 200) { + return new Response(JSON.stringify(data), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +async function settle() { + for (let round = 0; round < 3; round++) { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + } +} + +// How the server answers a config GET: the stored config, a dropped connection, no answer until +// the request is aborted, or an error status. +type Read = "ok" | "offline" | "hang" | number; + +// Stands in for /api/context/rtk/config (plus the page's other GETs). Like the route, a PUT +// that fails rtkConfigSchema gets a 400 and stores nothing; a valid one is merged into the +// stored config when it arrives, and its reply is a snapshot taken at that moment. With +// `hold`, replies wait until the test releases them, like a slow network. `refuse` answers 400 +// before storing; `drop` stores the change and then loses the reply; `garble` stores it and +// answers with something that is not a config; `hang` never answers until the request is +// aborted. `read` decides how each config GET is answered, numbered from 0 (the page load). +function startServer({ + hold = false, + fail = () => false, + refuse = () => false, + reject = () => false, + drop = () => false, + garble = () => false, + hang = () => false, + read = () => "ok", +}: { + hold?: boolean; + fail?: (body: Json) => boolean; + refuse?: (body: Json) => boolean; + reject?: (body: Json) => boolean; + drop?: (body: Json) => boolean; + garble?: (body: Json) => boolean; + hang?: (body: Json) => boolean; + read?: (index: number) => Read; +} = {}) { + let stored: Json = { ...STORED_CONFIG }; + let reads = 0; + const puts: Json[] = []; + const waiting: Array<() => void> = []; + const hanging: Array<(reason: unknown) => void> = []; + + // Like fetch, a request that never gets a reply rejects once its signal aborts, and at once + // when the signal has already aborted. + const stall = (signal?: AbortSignal | null) => + new Promise((_, rejectReply) => { + hanging.push(rejectReply); + if (signal?.aborted) rejectReply(signal.reason); + signal?.addEventListener("abort", () => rejectReply(signal.reason)); + }); + + const receive = (body: Json): Response | TypeError => { + if (reject(body)) return new TypeError("Failed to fetch"); + if (fail(body)) return respond({ error: "Save failed" }, 500); + const parsed = rtkConfigSchema.safeParse(body); + if (refuse(body) || !parsed.success) return respond({ error: "Invalid rtkConfig" }, 400); + stored = { ...stored, ...parsed.data }; + if (drop(body)) return new TypeError("Failed to fetch"); + if (garble(body)) return respond({}); + return respond(stored); + }; + + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const { pathname } = new URL(String(input), "http://localhost"); + if (pathname === "/api/settings/compression") return respond({ enabled: true }); + if (pathname === "/api/context/rtk/filters") return respond({ filters: FILTERS }); + if (pathname === "/api/context/analytics") return respond({}); + if (pathname !== "/api/context/rtk/config") return respond(null, 404); + if (init?.method !== "PUT") { + const answer = read(reads++); + if (answer === "offline") throw new TypeError("Failed to fetch"); + if (answer === "hang") return stall(init?.signal); + if (typeof answer === "number") return respond({ error: "Load failed" }, answer); + return respond(stored); + } + + const body = JSON.parse(String(init.body)) as Json; + puts.push(body); + if (hang(body)) return stall(init.signal); + const reply = receive(body); + return new Promise((resolve, rejectReply) => { + const send = () => (reply instanceof TypeError ? rejectReply(reply) : resolve(reply)); + if (hold) waiting.push(send); + else send(); + }); + }) + ); + + const release = async (send: (() => void) | undefined) => { + if (!send) throw new Error("no PUT reply is waiting"); + await act(async () => { + send(); + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + await settle(); + }; + // A page that saves one at a time sends its next PUT only after a reply lands, so keep + // releasing until nothing is waiting. + const releaseUntilIdle = async (next: () => (() => void) | undefined) => { + for (let round = 0; waiting.length > 0; round++) { + if (round === 20) throw new Error("PUT replies kept coming"); + await release(next()); + } + }; + + const server = { + get stored() { + return stored; + }, + puts, + get pending() { + return waiting.length; + }, + // Config GETs answered so far, the page load included. + get reads() { + return reads; + }, + releaseOldest: () => release(waiting.shift()), + releaseNewestFirst: () => releaseUntilIdle(() => waiting.pop()), + releaseAll: () => releaseUntilIdle(() => waiting.shift()), + // Ends every request still waiting for an answer, as if the connection dropped. + endHanging: () => hanging.splice(0).forEach((end) => end(new TypeError("Failed to fetch"))), + }; + activeServer = server; + return server; +} + +// Every copy of the page sends its saves through one queue, so a request a test leaves waiting +// would hold up the next test's page. +let activeServer: ReturnType | undefined; + +// The control the label names, whether it sits inside the label or is tied to it by htmlFor. +function inputFor(labelKey: string): HTMLInputElement { + const input = screen.getByText(labelKey).closest("label")?.control; + if (!(input instanceof HTMLInputElement)) throw new Error(`no input labelled ${labelKey}`); + return input; +} + +function filterCheckbox(name: string): HTMLInputElement { + let row: HTMLElement | null = screen.getByText(name); + while (row && !row.querySelector('input[type="checkbox"]')) row = row.parentElement; + const checkbox = row?.querySelector('input[type="checkbox"]'); + if (!checkbox) throw new Error(`no checkbox in the ${name} row`); + return checkbox; +} + +function retentionSelect(): HTMLSelectElement { + const select = screen.getByText("rawOutputRetention").closest("label")?.control; + if (!(select instanceof HTMLSelectElement)) throw new Error("no raw output retention select"); + return select; +} + +async function chooseRetention(value: string) { + fireEvent.change(retentionSelect(), { target: { value } }); + await settle(); +} + +// Wherever the page shows it, the message counts. +function pageText(): string { + return document.body.textContent ?? ""; +} + +// Focuses the field and fires one change per value, like typing digit by digit. +function type(labelKey: string, values: string[]): HTMLInputElement { + const input = inputFor(labelKey); + act(() => input.focus()); + for (const value of values) fireEvent.change(input, { target: { value } }); + return input; +} + +// Types the values, then finishes the edit by leaving the field or pressing Enter. +async function commit(labelKey: string, values: string[], via: "blur" | "enter" = "blur") { + const input = type(labelKey, values); + if (via === "enter") fireEvent.keyDown(input, { key: "Enter", code: "Enter", keyCode: 13 }); + else act(() => input.blur()); + await settle(); +} + +async function renderPage() { + render(); + await settle(); +} + +// jsdom's AbortSignal.timeout runs on a timer the fake clock cannot reach, so route it through +// setTimeout, which the fake clock controls. +function routeTimeoutsThroughFakeClock() { + vi.spyOn(AbortSignal, "timeout").mockImplementation((ms) => { + const controller = new AbortController(); + setTimeout(() => controller.abort(new DOMException("Timed out", "TimeoutError")), ms); + return controller.signal; + }); +} + +// Whether closing the tab now would ask the user first. +function unloadIsGuarded(): boolean { + const event = new Event("beforeunload", { cancelable: true }); + window.dispatchEvent(event); + return event.defaultPrevented; +} + +function errorToasts() { + return useNotificationStore.getState().notifications.filter((toast) => toast.type === "error"); +} + +afterEach(async () => { + activeServer?.endHanging(); + await settle(); + await activeServer?.releaseAll(); + activeServer = undefined; + useNotificationStore.getState().clearAll(); + vi.restoreAllMocks(); + vi.useRealTimers(); + vi.unstubAllGlobals(); +}); + +describe("RTK page config saves", { timeout: 20_000 }, () => { + it("typing 120 into max lines saves once, with 120, when the field loses focus", async () => { + const server = startServer(); + await renderPage(); + + await commit("maxLines", ["1", "12", "120"]); + + expect(server.puts).toHaveLength(1); + expect(server.puts[0]).toMatchObject({ maxLinesPerResult: 120 }); + expect(inputFor("maxLines").value).toBe("120"); + }); + + it("pressing Enter in max lines saves the typed value once", async () => { + const server = startServer(); + await renderPage(); + + await commit("maxLines", ["50"], "enter"); + + expect(server.puts).toHaveLength(1); + expect(server.puts[0]).toMatchObject({ maxLinesPerResult: 50 }); + expect(inputFor("maxLines").value).toBe("50"); + }); + + it("when the server refuses a save, max chars shows the stored value again and says the save failed", async () => { + startServer({ fail: (body) => "maxCharsPerResult" in body }); + await renderPage(); + + await commit("maxChars", ["5000"]); + + expect(inputFor("maxChars").value).toBe("20000"); + expect(pageText()).toContain("settings.saveFailed"); + // Screen readers skip the icon's ligature text and read only the message. + expect(screen.getByText("error").getAttribute("aria-hidden")).toBe("true"); + // While the page is open the message shows on the page, not as a toast. + expect(errorToasts()).toEqual([]); + }); + + it("when a save cannot reach the server, max chars shows the stored value again and says the save failed", async () => { + const escaped: unknown[] = []; + const onUnhandled = (reason: unknown) => { + escaped.push(reason); + }; + process.on("unhandledRejection", onUnhandled); + try { + startServer({ reject: (body) => "maxCharsPerResult" in body }); + await renderPage(); + + await commit("maxChars", ["5000"]); + + expect(inputFor("maxChars").value).toBe("20000"); + expect(pageText()).toContain("settings.saveFailed"); + // The page handles the failed fetch instead of leaving an unhandled rejection. + expect(escaped).toEqual([]); + } finally { + process.off("unhandledRejection", onUnhandled); + } + }); + + it("the save-failed message goes away when the next edit saves", async () => { + const server = startServer({ fail: (body) => "maxCharsPerResult" in body }); + await renderPage(); + await commit("maxChars", ["5000"]); + expect(pageText()).toContain("settings.saveFailed"); + + await commit("maxLines", ["150"]); + + expect(pageText()).not.toContain("saveFailed"); + expect(server.stored.maxLinesPerResult).toBe(150); + }); + + it("the reply to an older save does not put back a value edited since", async () => { + const server = startServer({ hold: true }); + await renderPage(); + await commit("maxLines", ["12"]); + await commit("maxChars", ["5000"]); + expect(server.pending).toBeGreaterThan(0); + + await server.releaseOldest(); + + expect(inputFor("maxChars").value).toBe("5000"); + expect(inputFor("maxLines").value).toBe("12"); + }); + + it("when replies arrive newest first, the form ends on the last edits and matches the server", async () => { + const server = startServer({ hold: true }); + await renderPage(); + await commit("maxLines", ["12"]); + await commit("maxChars", ["5000"]); + expect(server.pending).toBeGreaterThan(0); + + await server.releaseNewestFirst(); + + expect(inputFor("maxLines").value).toBe("12"); + expect(inputFor("maxChars").value).toBe("5000"); + expect(server.stored).toMatchObject({ maxLinesPerResult: 12, maxCharsPerResult: 5000 }); + }); + + it("a filter unchecked while another save is in flight stays unchecked and gets saved", async () => { + const user = userEvent.setup(); + const server = startServer({ hold: true }); + await renderPage(); + await commit("maxLines", ["12"]); + await commit("maxChars", ["5000"]); + expect(server.pending).toBeGreaterThan(0); + await server.releaseOldest(); + + // A real click, so a checkbox disabled during the save cannot toggle (fireEvent.click + // would toggle it anyway in jsdom). + await user.click(filterCheckbox("npm install")); + await settle(); + expect(filterCheckbox("npm install").checked).toBe(false); + await server.releaseOldest(); + + expect(filterCheckbox("npm install").checked).toBe(false); + await server.releaseAll(); + expect(server.stored.disabledFilters).toContain("npm-install"); + }); + + it("clicking a checkbox right after typing a number applies and saves both changes", async () => { + const user = userEvent.setup(); + const server = startServer({ hold: true }); + await renderPage(); + type("maxLines", ["12"]); + + // Moving focus to the checkbox blurs the number field first, which finishes that edit. + await user.click(inputFor("toolResults")); + await settle(); + expect(server.pending).toBeGreaterThan(0); + + expect(inputFor("toolResults").checked).toBe(false); + await server.releaseAll(); + expect(server.stored.applyToToolResults).toBe(false); + expect(server.stored.maxLinesPerResult).toBe(12); + }); + + it("typing a byte limit digit by digit saves the finished value and shows no error", async () => { + const server = startServer(); + await renderPage(); + + await commit("rawOutputMaxBytes", ["2", "20", "204", "2048"]); + + expect(server.stored.rawOutputMaxBytes).toBe(2048); + expect(inputFor("rawOutputMaxBytes").value).toBe("2048"); + expect(pageText()).not.toContain("saveFailed"); + }); + + it("an edit made while a save is in flight is sent only after that save's reply arrives", async () => { + const server = startServer({ hold: true }); + await renderPage(); + await commit("maxLines", ["12"]); + await commit("maxChars", ["5000"]); + + expect(server.puts).toHaveLength(1); + expect(inputFor("maxChars").value).toBe("5000"); + await server.releaseOldest(); + expect(server.puts).toHaveLength(2); + expect(server.puts[1]).toMatchObject({ maxCharsPerResult: 5000 }); + }); + + it("a failed save keeps the edits made while it was in flight", async () => { + const server = startServer({ hold: true, fail: (body) => "maxCharsPerResult" in body }); + await renderPage(); + await commit("maxChars", ["5000"]); + await commit("maxLines", ["150"]); + + await server.releaseOldest(); + + expect(inputFor("maxChars").value).toBe("20000"); + expect(inputFor("maxLines").value).toBe("150"); + await server.releaseAll(); + expect(server.stored.maxLinesPerResult).toBe(150); + }); + + it("a filter change that failed is not saved by a later filter change", async () => { + const server = startServer({ + hold: true, + fail: (body) => JSON.stringify(body.disabledFilters) === JSON.stringify(["git-status"]), + }); + await renderPage(); + fireEvent.click(filterCheckbox("Git status")); + await settle(); + fireEvent.click(filterCheckbox("npm install")); + await settle(); + + await server.releaseAll(); + + expect(server.stored.disabledFilters).toEqual(["npm-install"]); + expect(filterCheckbox("Git status").checked).toBe(true); + expect(filterCheckbox("npm install").checked).toBe(false); + expect(pageText()).toContain("settings.saveFailed"); + }); + + it("edits still waiting to save when the page closes are saved, and reopening the page shows them", async () => { + const server = startServer({ hold: true }); + const firstVisit = render(); + await settle(); + await commit("maxLines", ["12"]); + await chooseRetention("always"); + firstVisit.unmount(); + + await renderPage(); + await server.releaseAll(); + + expect(server.stored).toMatchObject({ maxLinesPerResult: 12, rawOutputRetention: "always" }); + expect(inputFor("maxLines").value).toBe("12"); + expect(retentionSelect().value).toBe("always"); + + // An edit on the reopened page is the one the server keeps. + await chooseRetention("never"); + await server.releaseAll(); + expect(server.stored.rawOutputRetention).toBe("never"); + expect(retentionSelect().value).toBe("never"); + }); + + it("when the reply to a stored save is lost, max chars keeps the new value and shows no error", async () => { + const server = startServer({ drop: (body) => "maxCharsPerResult" in body }); + await renderPage(); + + await commit("maxChars", ["5000"]); + + expect(server.stored.maxCharsPerResult).toBe(5000); + expect(inputFor("maxChars").value).toBe("5000"); + expect(pageText()).not.toContain("saveFailed"); + }); + + it("a save that never gets a reply gives up, rolls back, and lets the next edit save", async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + routeTimeoutsThroughFakeClock(); + const server = startServer({ hang: (body) => "maxCharsPerResult" in body }); + await renderPage(); + await commit("maxChars", ["5000"]); + await commit("maxLines", ["150"]); + expect(server.puts).toHaveLength(1); + + // A minute is far past any wait for a dashboard save. + await act(() => vi.advanceTimersByTimeAsync(60_000)); + await settle(); + + expect(inputFor("maxChars").value).toBe("20000"); + expect(pageText()).toContain("settings.saveFailed"); + expect(server.stored.maxLinesPerResult).toBe(150); + }); + + it("a save whose reply and read-back both stall gives up within one request timeout", async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + routeTimeoutsThroughFakeClock(); + startServer({ + hang: (body) => "maxCharsPerResult" in body, + read: (index) => (index === 0 ? "ok" : "hang"), + }); + await renderPage(); + await commit("maxChars", ["5000"]); + + await act(() => vi.advanceTimersByTimeAsync(15_001)); + await settle(); + + expect(inputFor("maxChars").value).toBe("20000"); + expect(pageText()).toContain("settings.saveFailed"); + }); + + it("a save the route refuses with 400 is not read back", async () => { + const server = startServer({ refuse: (body) => "maxCharsPerResult" in body }); + await renderPage(); + const readsBefore = server.reads; + + await commit("maxChars", ["5000"]); + + expect(server.reads).toBe(readsBefore); + expect(inputFor("maxChars").value).toBe("20000"); + expect(pageText()).toContain("settings.saveFailed"); + }); + + it("a success reply that is not a config does not break the page or stop later saves", async () => { + const server = startServer({ garble: (body) => "disabledFilters" in body }); + await renderPage(); + + fireEvent.click(filterCheckbox("Git status")); + await settle(); + await commit("maxLines", ["150"]); + + expect(server.stored).toMatchObject({ + disabledFilters: ["git-status"], + maxLinesPerResult: 150, + }); + expect(filterCheckbox("Git status").checked).toBe(false); + expect(inputFor("maxLines").value).toBe("150"); + expect(pageText()).not.toContain("saveFailed"); + }); + + it("when a save and its read-back both fail, the edit rolls back, the page says so, and the next edit saves", async () => { + let down = true; + const server = startServer({ + reject: () => down, + read: (index) => (index > 0 && down ? "offline" : "ok"), + }); + await renderPage(); + + await commit("maxChars", ["5000"]); + expect(inputFor("maxChars").value).toBe("20000"); + expect(pageText()).toContain("settings.saveFailed"); + + down = false; + await commit("maxLines", ["150"]); + expect(server.stored.maxLinesPerResult).toBe(150); + expect(inputFor("maxLines").value).toBe("150"); + }); + + it("a filter change whose outcome stays unknown counts as failed, and the next change makes the server match the page", async () => { + let lost = true; + const server = startServer({ + drop: (body) => lost && JSON.stringify(body.disabledFilters) === '["git-status"]', + read: (index) => (index > 0 && lost ? "offline" : "ok"), + }); + await renderPage(); + + // The server stores the change, but its reply and the read-back both fail. + fireEvent.click(filterCheckbox("Git status")); + await settle(); + expect(server.stored.disabledFilters).toEqual(["git-status"]); + expect(filterCheckbox("Git status").checked).toBe(true); + expect(pageText()).toContain("settings.saveFailed"); + + lost = false; + fireEvent.click(filterCheckbox("npm install")); + await settle(); + expect(server.stored.disabledFilters).toEqual(["npm-install"]); + expect(filterCheckbox("Git status").checked).toBe(true); + expect(filterCheckbox("npm install").checked).toBe(false); + }); + + it.each<[string, string, string, number]>([ + ["max lines above the maximum", "maxLines", "200000", 100000], + ["negative max lines", "maxLines", "-5", 0], + ["a threshold above the maximum", "deduplicateThreshold", "101", 100], + ["a byte limit below the minimum", "rawOutputMaxBytes", "512", 1024], + ["a decimal max chars", "maxChars", "12.6", 13], + ])("%s is saved inside the range the server accepts", async (_case, label, typed, saved) => { + const server = startServer(); + await renderPage(); + + await commit(label, [typed]); + + expect(server.puts).toHaveLength(1); + expect(Object.values(server.puts[0])).toEqual([saved]); + expect(inputFor(label).value).toBe(String(saved)); + expect(pageText()).not.toContain("saveFailed"); + }); + + it("clearing a number field and leaving it puts the stored value back and sends no save", async () => { + const server = startServer(); + await renderPage(); + + await commit("rawOutputMaxBytes", [""]); + + expect(server.puts).toEqual([]); + expect(inputFor("rawOutputMaxBytes").value).toBe("65536"); + }); + + it("leaving a number field untouched, or committing the value it already shows, sends no save", async () => { + const server = startServer(); + await renderPage(); + const maxChars = inputFor("maxChars"); + act(() => maxChars.focus()); + act(() => maxChars.blur()); + + // Stored max lines is 200; pass through 20 so onChange fires and the draft holds 200. + await commit("maxLines", ["20", "200"], "enter"); + act(() => inputFor("maxLines").blur()); + await settle(); + + expect(server.puts).toEqual([]); + expect(maxChars.value).toBe("20000"); + }); + + it.each<[string, Read, string]>([ + ["returns 500", 500, "settings.failedLoadWithStatus 500"], + ["cannot reach the server", "offline", "settings.failedLoadWithStatus settings.unknownError"], + ])( + "when the config load %s, the page says so and a filter click sends no save", + async (_case, answer, message) => { + const server = startServer({ read: () => answer }); + await renderPage(); + + expect(screen.getByRole("alert").textContent).toContain(message); + expect(screen.queryByText("maxLines")).toBeNull(); + const gitStatus = filterCheckbox("Git status"); + expect(gitStatus.disabled).toBe(true); + fireEvent.click(gitStatus); + await settle(); + expect(server.puts).toEqual([]); + } + ); + + it("asks before the tab unloads while saves are still queued, and not after they finish", async () => { + const server = startServer({ hold: true }); + await renderPage(); + await commit("maxLines", ["12"]); + await commit("maxChars", ["5000"]); + + expect(unloadIsGuarded()).toBe(true); + await server.releaseAll(); + expect(unloadIsGuarded()).toBe(false); + }); + + it("asks before the tab unloads while a typed number is not yet saved", async () => { + startServer(); + await renderPage(); + + const input = type("maxLines", ["12"]); + expect(unloadIsGuarded()).toBe(true); + + act(() => input.blur()); + await settle(); + expect(unloadIsGuarded()).toBe(false); + }); + + it("a number typed but not committed is saved when the page closes", async () => { + const server = startServer(); + const page = render(); + await settle(); + + type("maxLines", ["400"]); + page.unmount(); + await settle(); + + expect(server.stored.maxLinesPerResult).toBe(400); + }); + + it("a save that fails after the page closed shows an error toast naming the setting", async () => { + const server = startServer({ hold: true, fail: (body) => "maxCharsPerResult" in body }); + const page = render(); + await settle(); + await commit("maxChars", ["5000"]); + page.unmount(); + + await server.releaseAll(); + + expect(errorToasts()).toHaveLength(1); + expect(errorToasts()[0]).toMatchObject({ title: "settings.saveFailed", message: "maxChars" }); + }); + + // Kept last: on a queue without a rejection handler, a throwing task stops every later task, + // which would break the tests after it. + it("a save task that throws counts as a failed save, and the next save still goes out", async () => { + const server = startServer(); + await renderPage(); + vi.spyOn(AbortSignal, "timeout").mockImplementationOnce(() => { + throw new TypeError("AbortSignal.timeout unavailable"); + }); + + await commit("maxLines", ["12"]); + await commit("maxChars", ["5000"]); + + expect(server.puts).toEqual([{ maxCharsPerResult: 5000 }]); + expect(inputFor("maxLines").value).toBe("200"); + expect(inputFor("maxChars").value).toBe("5000"); + }); +}); diff --git a/tests/unit/ui/rtk-context-page-load-failure.test.tsx b/tests/unit/ui/rtk-context-page-load-failure.test.tsx new file mode 100644 index 000000000000..37f48c759f08 --- /dev/null +++ b/tests/unit/ui/rtk-context-page-load-failure.test.tsx @@ -0,0 +1,146 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot } from "react-dom/client"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +const containers: HTMLElement[] = []; +const roots: Array<{ unmount: () => void }> = []; + +function mount(ui: React.ReactElement): HTMLElement { + const container = document.createElement("div"); + document.body.appendChild(container); + containers.push(container); + const root = createRoot(container); + roots.push(root); + act(() => { + root.render(ui); + }); + return container; +} + +beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; +}); + +afterEach(async () => { + vi.restoreAllMocks(); + await act(async () => { + while (roots.length > 0) roots.pop()?.unmount(); + }); + for (let i = 0; i < 10; i++) await Promise.resolve(); + while (containers.length > 0) containers.pop()?.remove(); + document.body.innerHTML = ""; +}); + +async function flush() { + await act(async () => { + for (let i = 0; i < 10; i++) await Promise.resolve(); + }); +} + +function json(body: unknown, status = 200) { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +function rtkConfig() { + return { + enabled: true, + intensity: "standard", + applyToToolResults: true, + applyToAssistantMessages: false, + applyToCodeBlocks: false, + enabledFilters: [], + disabledFilters: [], + maxLinesPerResult: 40, + maxCharsPerResult: 4000, + deduplicateThreshold: 30, + customFiltersEnabled: false, + trustProjectFilters: false, + rawOutputRetention: "never", + rawOutputMaxBytes: 8192, + }; +} + +async function mountPage() { + const { default: RtkContextPageClient } = + await import("../../../src/app/(dashboard)/dashboard/context/rtk/RtkContextPageClient"); + let container!: HTMLElement; + await act(async () => { + container = mount(); + }); + await flush(); + return container; +} + +function retryButton(container: HTMLElement) { + return Array.from(container.querySelectorAll("button")).find( + (button) => button.textContent === "Retry" + ); +} + +describe("RtkContextPageClient when the settings GET fails", () => { + // The page reads only the master flag from GET /api/settings/compression. A failed + // GET must not fall back to enabled:false, which would show the "master switch is + // OFF" banner while the stored flag is on. + const FAILURES = { + "a 500": async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 }), + "a network error": async () => { + throw new TypeError("Failed to fetch"); + }, + }; + + it.each(Object.keys(FAILURES) as Array)( + "shows a retry instead of the master-switch-off banner after %s", + { timeout: 20000 }, + async (failure) => { + vi.spyOn(globalThis, "fetch").mockImplementation(async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.includes("/api/settings/compression")) return FAILURES[failure](); + if (url.includes("/api/context/rtk/filters")) return json({ filters: [] }); + if (url.includes("/api/context/rtk/config")) return json(rtkConfig()); + if (url.includes("/api/context/analytics")) return json({}); + return json({}, 404); + }); + const container = await mountPage(); + + expect(container.textContent).toContain("Failed To Load"); + expect(container.textContent).not.toContain("master switch is OFF"); + // No control that depends on the loaded settings stays on screen. + expect(container.querySelectorAll("input, select, textarea")).toHaveLength(0); + expect(retryButton(container)).toBeTruthy(); + } + ); + + it("drops the master-switch-off banner once Retry answers", async () => { + const fetchSpy = vi + .spyOn(globalThis, "fetch") + .mockImplementation(async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.includes("/api/settings/compression")) return json({ enabled: true }); + if (url.includes("/api/context/rtk/filters")) return json({ filters: [] }); + if (url.includes("/api/context/rtk/config")) return json(rtkConfig()); + if (url.includes("/api/context/analytics")) return json({}); + return json({}, 404); + }); + fetchSpy.mockImplementationOnce( + async () => new Response(JSON.stringify({ error: "unavailable" }), { status: 500 }) + ); + const container = await mountPage(); + expect(container.textContent).toContain("Failed To Load"); + + await act(async () => { + retryButton(container)!.click(); + }); + await flush(); + + expect(container.textContent).not.toContain("Failed To Load"); + // The stored flag is on, so the banner the defaults would show stays hidden. + expect(container.textContent).not.toContain("master switch is OFF"); + expect(container.querySelectorAll("input, select, textarea").length).toBeGreaterThan(0); + }); +}); diff --git a/tests/unit/upstream-http2-optout.test.ts b/tests/unit/upstream-http2-optout.test.ts new file mode 100644 index 000000000000..d05a36874f76 --- /dev/null +++ b/tests/unit/upstream-http2-optout.test.ts @@ -0,0 +1,52 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + __getDefaultDispatcherOptionsForTest, + __getProxyDispatcherOptionsForTest, + __getDispatcherOptionsForTest, +} from "../../open-sse/utils/proxyDispatcher.ts"; + +test("#15313: direct and proxy dispatchers disable HTTP/2 on explicit opt-out", () => { + for (const value of ["false", "0", "no", "off", " FALSE "]) { + const env = { OMNIROUTE_UPSTREAM_HTTP2_ENABLED: value }; + const direct = __getDefaultDispatcherOptionsForTest(env); + assert.equal(direct.allowH2, false); + assert.equal(direct.connect?.allowH2, false); + const proxy = __getProxyDispatcherOptionsForTest(env); + assert.equal(proxy.allowH2, false); + assert.equal(proxy.connect?.allowH2, false); + assert.equal(proxy.requestTls?.allowH2, false); + assert.equal(proxy.proxyTls?.allowH2, false); + assert.equal(proxy.connections, 32); + assert.equal(proxy.pipelining, 4); + assert.equal(proxy.keepAliveTimeout, 30_000); + assert.equal(proxy.keepAliveMaxTimeout, 60_000); + } +}); + +test("HTTP/2 stays enabled by default, including empty and unknown flag values", () => { + for (const value of [undefined, "", "true", "1", "yes", "on", "unknown"]) { + const env = { OMNIROUTE_UPSTREAM_HTTP2_ENABLED: value }; + assert.equal(__getDefaultDispatcherOptionsForTest(env).allowH2, true); + const proxy = __getProxyDispatcherOptionsForTest(env); + assert.equal(proxy.allowH2, true); + assert.equal(proxy.requestTls?.allowH2, true); + assert.equal(proxy.proxyTls?.allowH2, true); + } +}); + +test("HTTP/1 opt-out preserves local-egress family and keep-alive settings", () => { + const previous = process.env.OMNIROUTE_UPSTREAM_HTTP2_ENABLED; + process.env.OMNIROUTE_UPSTREAM_HTTP2_ENABLED = "false"; + try { + const options = __getDispatcherOptionsForTest("host.docker.internal"); + assert.equal(options.allowH2, false); + assert.equal(options.connect?.allowH2, false); + assert.equal(options.connect?.autoSelectFamily, true); + assert.equal(options.connect?.autoSelectFamilyAttemptTimeout, 200); + assert.equal(options.keepAliveMaxTimeout, 1000); + } finally { + if (previous === undefined) delete process.env.OMNIROUTE_UPSTREAM_HTTP2_ENABLED; + else process.env.OMNIROUTE_UPSTREAM_HTTP2_ENABLED = previous; + } +}); diff --git a/tests/unit/upstream-http2-transport.test.ts b/tests/unit/upstream-http2-transport.test.ts new file mode 100644 index 000000000000..d4e53cbe397b --- /dev/null +++ b/tests/unit/upstream-http2-transport.test.ts @@ -0,0 +1,258 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import http from "node:http"; +import https from "node:https"; +import http2 from "node:http2"; +import net from "node:net"; +import tls from "node:tls"; +import { once } from "node:events"; +import selfsigned from "selfsigned"; +import { fetch, type Dispatcher } from "undici"; +import { + clearDispatcherCache, + createProxyDispatcher, + getDefaultDispatcher, + getRetryDispatcher, + getProxyRetryDispatcher, +} from "../../open-sse/utils/proxyDispatcher.ts"; + +const flag = "OMNIROUTE_UPSTREAM_HTTP2_ENABLED"; +const originalFlag = process.env[flag]; +const originalCa = tls.getCACertificates("default"); +const sockets = new Set(); +const sessions = new Set(); +const dispatchers = new Set(); +const proxyProtocols: string[] = []; +const proxyAuth = `Basic ${Buffer.from("test:password").toString("base64")}`; +let upstream: http2.Http2SecureServer; +let plainProxy: http.Server; +let secureProxy: https.Server; +let socksProxy: net.Server; +let upstreamUrl: string; + +function trackSockets(server: net.Server): void { + server.on("connection", (socket) => { + sockets.add(socket); + socket.once("close", () => sockets.delete(socket)); + socket.on("error", () => {}); + }); +} + +async function listen(server: net.Server): Promise { + trackSockets(server); + server.listen(0, "127.0.0.1"); + await once(server, "listening"); + return (server.address() as net.AddressInfo).port; +} + +function tunnel(server: http.Server | https.Server): void { + server.on("connect", (req, socket, head) => { + if (req.headers["proxy-authorization"] !== proxyAuth) { + socket.end("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"); + return; + } + const target = net.connect((upstream.address() as net.AddressInfo).port, "127.0.0.1"); + sockets.add(target); + target.once("close", () => sockets.delete(target)); + target.on("error", () => socket.destroy()); + socket.on("error", () => target.destroy()); + socket.once("close", () => target.destroy()); + target.once("connect", () => { + socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); + if (head.length) target.write(head); + socket.pipe(target).pipe(socket); + }); + }); +} + +function createSocksProxy(): net.Server { + return net.createServer((socket) => { + let buffer = Buffer.alloc(0); + let greeted = false; + const onData = (chunk: Buffer) => { + buffer = Buffer.concat([buffer, chunk]); + if (!greeted) { + if (buffer.length < 2 || buffer.length < 2 + buffer[1]) return; + buffer = buffer.subarray(2 + buffer[1]); + greeted = true; + socket.write(Buffer.from([5, 0])); + } + if (buffer.length < 10) return; + assert.deepEqual([...buffer.subarray(0, 4)], [5, 1, 0, 1]); + const target = net.connect(buffer.readUInt16BE(8), "127.0.0.1"); + sockets.add(target); + target.once("close", () => sockets.delete(target)); + target.on("error", () => socket.destroy()); + socket.once("close", () => target.destroy()); + socket.removeListener("data", onData); + target.once("connect", () => { + socket.write(Buffer.from([5, 0, 0, 1, 127, 0, 0, 1, 0, 0])); + if (buffer.length > 10) target.write(buffer.subarray(10)); + socket.pipe(target).pipe(socket); + }); + }; + socket.on("data", onData); + }); +} + +test.before(async () => { + const pems = await selfsigned.generate([{ name: "commonName", value: "localhost" }], { + keySize: 2048, + algorithm: "sha256", + extensions: [{ name: "subjectAltName", altNames: [{ type: 7, ip: "127.0.0.1" }] }], + }); + tls.setDefaultCACertificates([...originalCa, pems.cert]); + upstream = http2.createSecureServer({ key: pems.private, cert: pems.cert, allowHTTP1: true }); + upstream.on("session", (session) => { + sessions.add(session); + session.once("close", () => sessions.delete(session)); + session.on("error", () => {}); + }); + upstream.on("request", (req, res) => { + let bytes = 0; + req.on("data", (chunk: Buffer) => { + bytes += chunk.length; + }); + req.on("end", () => { + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify({ protocol: req.httpVersion, bytes })); + }); + }); + upstreamUrl = `https://127.0.0.1:${await listen(upstream)}/upload`; + plainProxy = http.createServer(); + secureProxy = https.createServer({ key: pems.private, cert: pems.cert }); + secureProxy.on("secureConnection", (socket) => proxyProtocols.push(socket.alpnProtocol || "")); + tunnel(plainProxy); + tunnel(secureProxy); + await listen(plainProxy); + await listen(secureProxy); + socksProxy = createSocksProxy(); + await listen(socksProxy); + process.env[flag] = "false"; + clearDispatcherCache(); +}); + +test.after(async () => { + const { resetDbInstance } = await import("../../src/lib/db/core.ts"); + resetDbInstance(); + await Promise.all([...dispatchers].map((dispatcher) => dispatcher.destroy())); + clearDispatcherCache(); + for (const session of sessions) session.destroy(); + for (const socket of sockets) socket.destroy(); + await Promise.all( + [upstream, plainProxy, secureProxy, socksProxy] + .filter(Boolean) + .map((server) => new Promise((resolve) => server.close(() => resolve()))) + ); + tls.setDefaultCACertificates(originalCa); + if (originalFlag === undefined) delete process.env[flag]; + else process.env[flag] = originalFlag; +}); + +async function request(dispatcher: Dispatcher, size = 0): Promise { + dispatchers.add(dispatcher); + const response = await fetch(upstreamUrl, { + dispatcher, + method: "POST", + body: Buffer.alloc(size, "x"), + signal: AbortSignal.timeout(10_000), + }); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { protocol: "1.1", bytes: size }); +} + +test("#15313: HTTP/1.1 is negotiated on direct, local-egress and retry paths", async () => { + await request(getDefaultDispatcher(), 4 * 1024 * 1024); + await request(getDefaultDispatcher("host.docker.internal")); + await request(getRetryDispatcher()); + await request(getRetryDispatcher("host.docker.internal")); +}); + +test("HTTP and HTTPS proxies retain authentication and family pinning on initial and retry paths", async () => { + for (const [scheme, server] of [ + ["http", plainProxy], + ["https", secureProxy], + ] as const) { + const url = `${scheme}://test:password@127.0.0.1:${(server.address() as net.AddressInfo).port}?family=ipv4`; + const dispatcher = createProxyDispatcher(url); + await request(dispatcher, 4 * 1024 * 1024); + await Promise.all(Array.from({ length: 5 }, () => request(dispatcher, 128 * 1024))); + await request(getProxyRetryDispatcher(url)); + } + assert.ok(proxyProtocols.length >= 2); + assert.ok(proxyProtocols.every((protocol) => protocol === "http/1.1")); +}); + +test("relay pool and fresh-socket retry honor the startup opt-out", async () => { + const { default: proxyFetch, runWithProxyContext } = + await import("../../open-sse/utils/proxyFetch.ts"); + for (const retry of [false, true]) { + let attempts = 0; + const response = await runWithProxyContext( + { type: "vercel", host: "relay.test", relayAuth: "test-only" }, + () => + proxyFetch( + "https://upstream.test/upload", + { method: "POST" }, + { + undiciFetch: async ( + _input: unknown, + init: RequestInit & { dispatcher: Dispatcher } + ) => { + dispatchers.add(init.dispatcher); + if (retry && attempts++ === 0) + throw Object.assign(new Error("test reset"), { code: "UND_ERR_SOCKET" }); + return fetch(upstreamUrl, { ...init, dispatcher: init.dispatcher }); + }, + } + ) + ); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { protocol: "1.1", bytes: 0 }); + if (retry) assert.equal(attempts, 2); + } +}); + +test("SOCKS5 initial and retry TLS connectors honor the opt-out", async () => { + const url = `socks5://127.0.0.1:${(socksProxy.address() as net.AddressInfo).port}?family=ipv4`; + await request(createProxyDispatcher(url)); + await request(getProxyRetryDispatcher(url)); +}); + +test("default dispatchers still negotiate HTTP/2 when the flag is unset", async () => { + delete process.env[flag]; + clearDispatcherCache(); + try { + const proxyUrl = `http://test:password@127.0.0.1:${(plainProxy.address() as net.AddressInfo).port}`; + for (const dispatcher of [getDefaultDispatcher(), createProxyDispatcher(proxyUrl)]) { + dispatchers.add(dispatcher); + const response = await fetch(upstreamUrl, { dispatcher, signal: AbortSignal.timeout(5000) }); + assert.deepEqual(await response.json(), { protocol: "2.0", bytes: 0 }); + } + } finally { + process.env[flag] = "false"; + clearDispatcherCache(); + } +}); + +test("opt-out does not disable TLS certificate validation", async () => { + tls.setDefaultCACertificates(originalCa); + clearDispatcherCache(); + try { + for (const dispatcher of [ + getDefaultDispatcher(), + createProxyDispatcher( + `http://test:password@127.0.0.1:${(plainProxy.address() as net.AddressInfo).port}` + ), + ]) { + dispatchers.add(dispatcher); + await assert.rejects( + fetch(upstreamUrl, { dispatcher, signal: AbortSignal.timeout(5000) }), + (error: Error & { cause?: { code?: string } }) => + error.cause?.code === "DEPTH_ZERO_SELF_SIGNED_CERT" + ); + } + } finally { + clearDispatcherCache(); + } +}); diff --git a/tests/unit/upstream-status-restatement.test.ts b/tests/unit/upstream-status-restatement.test.ts index ceccfda6b8ec..4c4a47dfb5cc 100644 --- a/tests/unit/upstream-status-restatement.test.ts +++ b/tests/unit/upstream-status-restatement.test.ts @@ -112,25 +112,61 @@ test("R10: chatCore wires applyStatusRestatement into the providerFailure block" // (side-effectful DB/env wiring), so the wiring contract is asserted at the // source level: the hook must exist, run against the parsed error, and // reassign both statusCode and retryAfterMs BEFORE classification. + // The classification helper stays in the barrel; the providerFailure block + // moved into the streaming leg with the decomposition. const { readFile } = await import("node:fs/promises"); const src = await readFile( new URL("../../open-sse/handlers/chatCore.ts", import.meta.url), "utf8" ); - assert.match(src, /applyStatusRestatement\(/, "chatCore must call applyStatusRestatement"); + const legSrc = await readFile( + new URL("../../open-sse/handlers/chatCore/streamingResponse.ts", import.meta.url), + "utf8" + ); + assert.match( + legSrc, + /applyStatusRestatement\(/, + "streaming leg must call applyStatusRestatement" + ); const helperIndex = src.indexOf("const applyProviderFailureClassification = async ("); - const blockIndex = src.indexOf("providerFailure: if (!providerResponse.ok)"); - assert.ok(helperIndex > -1 && blockIndex > helperIndex, "classification helper and block exist"); + const helperEnd = src.indexOf("const streamingOutcome = await runStreamingResponse("); + assert.ok( + helperIndex > -1 && helperEnd > helperIndex, + "classification helper exists before streaming dispatch" + ); const classifyCalls = src.match(/classifyProviderError\(/g) ?? []; assert.equal(classifyCalls.length, 1, "chatCore classifies provider errors in exactly one place"); const classifyIndex = src.indexOf("classifyProviderError(statusCode"); assert.ok( - classifyIndex > helperIndex && classifyIndex < blockIndex, + classifyIndex > helperIndex && classifyIndex < helperEnd, "classifyProviderError must live inside applyProviderFailureClassification" ); - const block = src.slice(blockIndex); + const blockIndex = legSrc.indexOf("providerFailure: if (!providerResponse.ok)"); + assert.ok(blockIndex > -1, "providerFailure block exists in streamingResponse.ts"); + // Cross-file form of the original `classifyIndex < blockIndex` ordering: the classification + // helper (holding the single classifyProviderError call) must be defined before the dispatch + // that hands it to the leaf containing the providerFailure block, and the dispatch must + // actually pass it in. + const dispatchIndex = src.indexOf("await runStreamingResponse({"); + assert.ok(dispatchIndex > -1, "streaming dispatch exists in chatCore.ts"); + assert.ok( + classifyIndex < dispatchIndex, + "classifyProviderError must be classified-before the block: helper precedes the streaming dispatch" + ); + assert.match( + src.slice(dispatchIndex, src.indexOf("});", dispatchIndex)), + /\bapplyProviderFailureClassification,/, + "streaming dispatch must pass applyProviderFailureClassification into the leaf" + ); + assert.equal( + legSrc.indexOf("classifyProviderError("), + -1, + "streaming leg does not classify directly; it delegates to applyProviderFailureClassification" + ); + + const block = legSrc.slice(blockIndex); const hookIndex = block.indexOf("applyStatusRestatement("); const statusReassign = block.indexOf("statusCode = restatement.status;"); const retryReassign = block.indexOf("retryAfterMs = restatement.retryAfterMs;"); diff --git a/tests/unit/usage-analytics-response-cache.test.ts b/tests/unit/usage-analytics-response-cache.test.ts new file mode 100644 index 000000000000..5df28b86c14c --- /dev/null +++ b/tests/unit/usage-analytics-response-cache.test.ts @@ -0,0 +1,182 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-analytics-cache-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = "test-analytics-cache-secret"; + +const cache = await import("../../src/lib/usage/analyticsResponseCache.ts"); +const core = await import("../../src/lib/db/core.ts"); +const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); +const analyticsRoute = await import("../../src/app/api/usage/analytics/route.ts"); + +function jsonResponse(body: unknown, status = 200) { + return new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, + }); +} + +function setEnv(ttlMs: string | undefined, minComputeMs: string | undefined) { + if (ttlMs === undefined) delete process.env.OMNIROUTE_ANALYTICS_CACHE_TTL_MS; + else process.env.OMNIROUTE_ANALYTICS_CACHE_TTL_MS = ttlMs; + if (minComputeMs === undefined) delete process.env.OMNIROUTE_ANALYTICS_CACHE_MIN_COMPUTE_MS; + else process.env.OMNIROUTE_ANALYTICS_CACHE_MIN_COMPUTE_MS = minComputeMs; +} + +test.beforeEach(() => { + cache.clearAnalyticsResponseCache(); + setEnv(undefined, undefined); +}); + +test.after(() => { + setEnv(undefined, undefined); + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("defaults: 60 s TTL, only responses slower than 1 s are kept", () => { + assert.equal(cache.getAnalyticsCacheTtlMs(), 60_000); + assert.equal(cache.getAnalyticsCacheMinComputeMs(), 1_000); + setEnv("abc", "-5"); + assert.equal(cache.getAnalyticsCacheTtlMs(), 60_000); + assert.equal(cache.getAnalyticsCacheMinComputeMs(), 1_000); +}); + +test("a slow response is computed once and then served from the cache", async () => { + setEnv("60000", "0"); + let calls = 0; + const compute = async () => jsonResponse({ n: ++calls }); + + const first = await cache.serveAnalyticsCached("?range=30d", compute); + assert.equal(first.headers.get("x-analytics-cache"), "miss"); + assert.deepEqual(await first.json(), { n: 1 }); + + const second = await cache.serveAnalyticsCached("?range=30d", compute); + assert.equal(second.headers.get("x-analytics-cache"), "hit"); + assert.equal(second.status, 200); + assert.deepEqual(await second.json(), { n: 1 }); + assert.equal(calls, 1); +}); + +test("each query string has its own entry", async () => { + setEnv("60000", "0"); + let calls = 0; + const compute = async () => jsonResponse({ n: ++calls }); + + await cache.serveAnalyticsCached("?range=30d", compute); + const other = await cache.serveAnalyticsCached("?range=7d", compute); + assert.equal(other.headers.get("x-analytics-cache"), "miss"); + assert.equal(calls, 2); +}); + +test("fast responses are not cached", async () => { + setEnv("60000", "60000"); + let calls = 0; + const compute = async () => jsonResponse({ n: ++calls }); + + await cache.serveAnalyticsCached("?range=30d", compute); + const second = await cache.serveAnalyticsCached("?range=30d", compute); + assert.equal(second.headers.get("x-analytics-cache"), "miss"); + assert.deepEqual(await second.json(), { n: 2 }); +}); + +test("TTL 0 disables the cache and returns the computed response as is", async () => { + setEnv("0", "0"); + let calls = 0; + const compute = async () => jsonResponse({ n: ++calls }); + + const first = await cache.serveAnalyticsCached("?range=30d", compute); + assert.equal(first.headers.get("x-analytics-cache"), null); + await cache.serveAnalyticsCached("?range=30d", compute); + assert.equal(calls, 2); +}); + +test("error responses are passed through and never cached", async () => { + setEnv("60000", "0"); + let calls = 0; + const compute = async () => { + calls += 1; + return jsonResponse({ error: { message: "boom" } }, 500); + }; + + const first = await cache.serveAnalyticsCached("?range=30d", compute); + assert.equal(first.status, 500); + const second = await cache.serveAnalyticsCached("?range=30d", compute); + assert.equal(second.status, 500); + assert.equal(calls, 2); +}); + +test("concurrent identical requests share one computation", async () => { + setEnv("60000", "0"); + let calls = 0; + let release: () => void = () => {}; + const gate = new Promise((resolve) => { + release = resolve; + }); + const compute = async () => { + calls += 1; + await gate; + return jsonResponse({ n: calls }); + }; + + const pending = [1, 2, 3].map(() => cache.serveAnalyticsCached("?range=all", compute)); + release(); + const responses = await Promise.all(pending); + assert.equal(calls, 1); + for (const response of responses) { + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { n: 1 }); + } +}); + +test("a rejected computation is not cached and the next request retries", async () => { + setEnv("60000", "0"); + let calls = 0; + const compute = async () => { + calls += 1; + if (calls === 1) throw new Error("transient"); + return jsonResponse({ n: calls }); + }; + + await assert.rejects(cache.serveAnalyticsCached("?range=30d", compute), /transient/); + const retry = await cache.serveAnalyticsCached("?range=30d", compute); + assert.deepEqual(await retry.json(), { n: 2 }); +}); + +test("analytics route serves the cached payload for repeated identical requests", async () => { + setEnv("60000", "0"); + core.resetDbInstance(); + usageHistory.clearPendingRequests(); + const url = "http://localhost/api/usage/analytics?startDate=2026-01-01T00:00:00.000Z"; + + await usageHistory.saveRequestUsage({ + provider: "openai", + model: "gpt-4o-mini", + tokens: { input: 100, output: 50 }, + timestamp: "2026-01-02T00:00:00.000Z", + }); + const first = await analyticsRoute.GET(new Request(url)); + assert.equal(first.status, 200); + assert.equal(first.headers.get("x-analytics-cache"), "miss"); + const firstBody = (await first.json()) as { summary: { totalRequests: number } }; + + await usageHistory.saveRequestUsage({ + provider: "openai", + model: "gpt-4o-mini", + tokens: { input: 100, output: 50 }, + timestamp: "2026-01-03T00:00:00.000Z", + }); + const second = await analyticsRoute.GET(new Request(url)); + assert.equal(second.headers.get("x-analytics-cache"), "hit"); + const secondBody = (await second.json()) as { summary: { totalRequests: number } }; + assert.equal(secondBody.summary.totalRequests, firstBody.summary.totalRequests); + + cache.clearAnalyticsResponseCache(); + const fresh = await analyticsRoute.GET(new Request(url)); + const freshBody = (await fresh.json()) as { summary: { totalRequests: number } }; + assert.equal(freshBody.summary.totalRequests, firstBody.summary.totalRequests + 1); +}); diff --git a/tests/unit/usage-history-provider-alias-13459.test.ts b/tests/unit/usage-history-provider-alias-13459.test.ts index a7a1e5866b4a..e3e3e2a726fb 100644 --- a/tests/unit/usage-history-provider-alias-13459.test.ts +++ b/tests/unit/usage-history-provider-alias-13459.test.ts @@ -19,14 +19,19 @@ test.after(() => { }); test("#13459 saveRequestUsage stores the canonical provider id for an alias", async () => { - for (const provider of ["af", "api-airforce"]) { + // Distinct timestamps: the dedup guard skips an INSERT when timestamp + + // provider + model + connection + keys + token counts all match, so sharing + // one timestamp between the two writes would collapse them to a single row + // on fast/loaded CI runners (same-ms Date.now()). + const base = Date.now(); + for (const [i, provider] of ["af", "api-airforce"].entries()) { await usageHistory.saveRequestUsage({ provider, model: "gpt-4o-mini", tokens: { input: 10, output: 5 }, success: true, latencyMs: 100, - timestamp: new Date().toISOString(), + timestamp: new Date(base + i * 1000).toISOString(), }); } const rows = core diff --git a/tests/unit/usage-pending-sweep.test.ts b/tests/unit/usage-pending-sweep.test.ts index 19b60c235e11..fa267f0e3819 100644 --- a/tests/unit/usage-pending-sweep.test.ts +++ b/tests/unit/usage-pending-sweep.test.ts @@ -7,14 +7,155 @@ const { getPendingRequests, sweepStalePendingRequests, getMaxPendingRequestAgeMs, + getPendingRetainedBytes, finalizePendingRequestById, updatePendingRequestById, + updatePendingRequest, clearPendingRequests, } = await import("../../src/lib/usage/usageHistory.ts"); const MINUTE_MS = 60 * 1000; const HOUR_MS = 60 * MINUTE_MS; +test("pending map is bounded by RETAINED BYTES, not just the 5000-entry count cap", () => { + clearPendingRequests(); + assert.equal(getPendingRetainedBytes(), 0, "cleared state retains nothing"); + + // Payloads pass through protectPendingPreview, so a retained entry is capped at + // the preview bounds: depth 6, 24 object keys, 12 array items, 1200-char + // strings. One worst-case entry is tens of KB, not the multi-MB of a raw + // request. Build entries at that shape so the test exercises the real ceiling + // rather than an unreachable one. + const worstCaseString = "x".repeat(1200); + const worstCaseArray = Array.from({ length: 12 }, () => ({ + role: "user", + content: worstCaseString, + })); + const worstCaseObject = Object.fromEntries( + Array.from({ length: 24 }, (_, i) => [`key${i}`, worstCaseArray]) + ); + const meta = () => ({ + clientRequest: worstCaseObject, + providerRequest: worstCaseObject, + providerResponse: worstCaseObject, + clientResponse: worstCaseObject, + }); + + const CEILING = 256 * 1024 * 1024; + const before = getPendingRetainedBytes(); + trackPendingRequest("m", "p", "warm", true, meta()); + const perEntry = getPendingRetainedBytes() - before; + assert.ok(perEntry > 4096, `a worst-case entry must retain >4KB (got ${perEntry})`); + + // Enough entries to blow through 256 MB (past the 5000-entry count cap too). + const target = Math.ceil(CEILING / perEntry) + 200; + for (let i = 0; i < target; i++) { + trackPendingRequest("m", "p", `conn-${i}`, true, meta()); + } + + const retained = getPendingRetainedBytes(); + assert.ok( + retained <= CEILING, + `retained bytes ${retained} must stay under the ${CEILING} ceiling` + ); + assert.ok( + getPendingById().size < target, + `entries must be evicted under pressure (kept ${getPendingById().size}/${target})` + ); + assert.ok(getPendingById().size > 0, "the ceiling must not wipe in-flight requests entirely"); + // Counters self-heal: the map and the dashboard buckets stay consistent. + const counted = getPendingRequests().byModel["m (p)"]; + assert.equal(counted, getPendingById().size, "pending counts must match the map after eviction"); + + clearPendingRequests(); + assert.equal(getPendingRetainedBytes(), 0, "clear resets retained bytes"); +}); + +test("pending ids are unique across a large burst (regression: 24-bit suffix collided ~52%)", () => { + clearPendingRequests(); + // 5000 requests tracked inside one millisecond. With the old + // `${now}-${uuid.slice(0,6)}` id the 24-bit suffix collided by the birthday + // bound ~52% of the time, and `pendingById.set` overwrote a LIVE request's + // entry (the map lost one while its bucket kept both). + const COUNT = 5000; + const ids = new Set(); + for (let i = 0; i < COUNT; i++) { + const id = trackPendingRequest("m", "p", "c-burst", true); + assert.ok(id, "id returned"); + ids.add(id); + } + assert.equal(ids.size, COUNT, "every insert must yield a unique id"); + assert.equal( + getPendingById().size, + COUNT, + "one insert must be exactly one map entry (no silent overwrite)" + ); + clearPendingRequests(); +}); + +test("UPDATE path measures payloads (regression: the ceiling must not no-op in production)", () => { + clearPendingRequests(); + + const worstCaseString = "x".repeat(1200); + const worstCaseArray = Array.from({ length: 12 }, () => ({ + role: "user", + content: worstCaseString, + })); + const worstCaseObject = Object.fromEntries( + Array.from({ length: 24 }, (_, i) => [`key${i}`, worstCaseArray]) + ); + + // Path 1: updatePendingRequestById (targets one exact id). + const idA = trackPendingRequest("m", "p", "c-a", true); + assert.ok(idA, "id tracked"); + const afterInsertA = getPendingRetainedBytes(); + assert.ok(afterInsertA < 4096, `a bare entry retains almost nothing (got ${afterInsertA})`); + updatePendingRequestById(idA, { providerResponse: worstCaseObject }); + const afterById = getPendingRetainedBytes(); + assert.ok( + afterById > afterInsertA + 10_000, + `updatePendingRequestById must be measured (insert ${afterInsertA} -> ${afterById})` + ); + + // Path 2: updatePendingRequest (targets the LAST entry of one account/model + // bucket) — a different connection, so it is a genuinely different entry. + const idB = trackPendingRequest("m", "p", "c-b", true); + assert.ok(idB, "second id tracked"); + const beforeBucket = getPendingRetainedBytes(); + updatePendingRequest("m", "p", "c-b", { providerResponse: worstCaseObject }); + const afterBucket = getPendingRetainedBytes(); + assert.ok( + afterBucket > beforeBucket + 10_000, + `updatePendingRequest must be measured (${beforeBucket} -> ${afterBucket})` + ); + + // Replacing the same field with the same size must not double-count. + updatePendingRequest("m", "p", "c-b", { providerResponse: worstCaseObject }); + assert.equal(getPendingRetainedBytes(), afterBucket, "replacing a payload must not accumulate"); + + // Enough entries to exceed the ceiling, each fattened through the update path. + const CEILING = 256 * 1024 * 1024; + const perEntry = afterById - afterInsertA; + const extra = Math.ceil(CEILING / perEntry) + 50; + for (let i = 0; i < extra; i++) { + const nextId = trackPendingRequest("m", "p", `c${i}`, true); + assert.ok(nextId); + updatePendingRequestById(nextId, { providerResponse: worstCaseObject }); + } + assert.ok( + getPendingRetainedBytes() > CEILING, + `the map should now exceed the ceiling (retained ${getPendingRetainedBytes()})` + ); + trackPendingRequest("m", "p", "c-trigger", true); + assert.ok( + getPendingRetainedBytes() <= CEILING, + `ceiling must reclaim on insert (retained ${getPendingRetainedBytes()})` + ); + + clearPendingRequests(); + assert.equal(getPendingRetainedBytes(), 0, "clear resets retained bytes"); +}); + test("sweepStalePendingRequests marks over-age pending details and keeps counts", () => { clearPendingRequests(); @@ -150,8 +291,16 @@ test("trackPendingRequest reuses the same id across a combo's target-attempt ret }); assert.equal(secondId, firstId, "retry attempt must reuse the first attempt's id"); - assert.equal(getPendingById().has(firstId), true, "reused id is live again under the new attempt"); - assert.equal(getPendingById().get(firstId)?.model, "model-b", "entry reflects the NEW attempt's target"); + assert.equal( + getPendingById().has(firstId), + true, + "reused id is live again under the new attempt" + ); + assert.equal( + getPendingById().get(firstId)?.model, + "model-b", + "entry reflects the NEW attempt's target" + ); clearPendingRequests(); }); @@ -256,11 +405,11 @@ test("entries without an account are marked in the map and still fall under the for (let i = 0; i < 5000; i++) { trackPendingRequest("m", "p", "c-fresh-cap", true); } - assert.ok(getPendingById().size > 5000); const sizeBefore = getPendingById().size; + assert.ok(sizeBefore > 5000, `expected to exceed the cap, got ${sizeBefore}`); const removedByCap = sweepStalePendingRequests(now, HOUR_MS); - assert.equal(removedByCap, sizeBefore - 5000); - assert.equal(getPendingById().size, 5000); + assert.equal(removedByCap, sizeBefore - 5000, "sweep removes exactly the overflow"); + assert.equal(getPendingById().size, 5000, "the cap holds after the sweep"); assert.equal(getPendingById().has(requestId), false); } finally { clearPendingRequests(); @@ -334,11 +483,15 @@ test("marked entries are removed first when the pending map exceeds the cap", () for (let i = 0; i < 5000; i++) { trackPendingRequest("m", "p", `c-fresh-${i}`, true); } - assert.ok(getPendingById().size > 5000); + const sizeBefore = getPendingById().size; + assert.ok(sizeBefore > 5000, `expected to exceed the cap, got ${sizeBefore}`); + // Derive the expectation from the ACTUAL pre-sweep size rather than + // hardcoding: every insert must be a distinct map entry, and the sweep + // removes exactly the overflow. const removed = sweepStalePendingRequests(now, HOUR_MS); - assert.equal(getPendingById().size, 5000); - assert.equal(removed, 3); + assert.equal(getPendingById().size, 5000, "the cap holds after the sweep"); + assert.equal(removed, sizeBefore - 5000, "sweep removes exactly the overflow"); for (const id of markedIds) assert.equal(getPendingById().has(id), false); } finally { clearPendingRequests(); diff --git a/tests/unit/usage-service-hardening.test.ts b/tests/unit/usage-service-hardening.test.ts index fad5ee265beb..07b229fc5848 100644 --- a/tests/unit/usage-service-hardening.test.ts +++ b/tests/unit/usage-service-hardening.test.ts @@ -74,9 +74,9 @@ test("usage service covers GitHub free-plan parsing, auth denial and unsupported assert.equal(calls[0].headers.Authorization, "token gho-free"); // #10952 re-based the Copilot wire identity on the live-captured CLI 1.0.88 // (copilot-developer-cli integration id; API version 2026-08-01). - assert.equal(calls[0].headers["User-Agent"], "GitHubCopilotChat/1.0.88"); - assert.equal(calls[0].headers["Editor-Version"], "copilot/1.0.88"); - assert.equal(calls[0].headers["Editor-Plugin-Version"], "copilot-chat/1.0.88"); + assert.equal(calls[0].headers["User-Agent"], "GitHubCopilotChat/1.0.91"); + assert.equal(calls[0].headers["Editor-Version"], "copilot/1.0.91"); + assert.equal(calls[0].headers["Editor-Plugin-Version"], "copilot-chat/1.0.91"); assert.equal(calls[0].headers["X-GitHub-Api-Version"], "2026-08-01"); globalThis.fetch = async () => new Response("forbidden", { status: 403 }); diff --git a/tests/unit/validate-release-green.test.ts b/tests/unit/validate-release-green.test.ts index f66f27d9048a..0470beb66362 100644 --- a/tests/unit/validate-release-green.test.ts +++ b/tests/unit/validate-release-green.test.ts @@ -237,6 +237,26 @@ test("pre-flight wires the test-masking PR-context gate against origin/main (v3. ); }); +test("pre-flight reports pricing freshness as drift, not as a hard failure", async () => { + const fs = await import("node:fs"); + const src = fs.readFileSync( + new URL("../../scripts/quality/validate-release-green.mjs", import.meta.url), + "utf8" + ); + // The gate reads git history (KNOWN_MODEL_PRICING untouched for 90 days), so it turns red + // by itself as time passes: a drift entry keeps it visible without blocking a release. + assert.match( + src, + /driftCmd\(\s*"pricing-freshness"[\s\S]*?"check:pricing-freshness"/, + "pricing-freshness must run check:pricing-freshness as a drift check" + ); + assert.doesNotMatch( + src, + /hardCmd\(\s*"pricing-freshness"/, + "pricing-freshness must not be a HARD gate" + ); +}); + test("pre-flight --hermetic scrubs the live-test trigger vars (2026-07-05 false-positive fix)", async () => { const fs = await import("node:fs"); const src = fs.readFileSync( diff --git a/tests/unit/validation-no-playwright-15512.test.ts b/tests/unit/validation-no-playwright-15512.test.ts new file mode 100644 index 000000000000..945724cb817b --- /dev/null +++ b/tests/unit/validation-no-playwright-15512.test.ts @@ -0,0 +1,57 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +const ROOT = path.resolve(import.meta.dirname, "../.."); + +function staticImports(file: string): string[] { + const text = fs.readFileSync(file, "utf8"); + const specs: string[] = []; + for (const line of text.split("\n")) { + const match = line.match(/^import\s+(type\s+)?.*\sfrom\s+["']([^"']+)["']/); + if (!match || match[1]) continue; + specs.push(match[2]); + } + return specs; +} + +function resolveSpec(fromFile: string, spec: string): string | null { + if (spec === "playwright-core" || spec === "playwright") return spec; + let base: string | null = null; + if (spec.startsWith("@omniroute/open-sse/")) { + base = path.join(ROOT, "open-sse", spec.slice("@omniroute/open-sse/".length)); + } else if (spec.startsWith("@/")) { + base = path.join(ROOT, "src", spec.slice(2)); + } else if (spec.startsWith(".")) { + base = path.resolve(path.dirname(fromFile), spec); + } + if (!base) return null; + if (fs.existsSync(base) && fs.statSync(base).isFile()) return base; + for (const suffix of [".ts", ".tsx", ".js"]) { + if (fs.existsSync(base + suffix)) return base + suffix; + } + return null; +} + +test("the provider validation barrel does not statically reach playwright-core", () => { + const start = path.join(ROOT, "src/lib/providers/validation.ts"); + const seen = new Set(); + const pending = [start]; + const hits: string[] = []; + while (pending.length > 0) { + const file = pending.pop() as string; + if (seen.has(file)) continue; + seen.add(file); + if (!fs.existsSync(file)) continue; + for (const spec of staticImports(file)) { + const resolved = resolveSpec(file, spec); + if (resolved === "playwright-core" || resolved === "playwright") { + hits.push(`${path.relative(ROOT, file)} -> ${spec}`); + } else if (resolved) { + pending.push(resolved); + } + } + } + assert.deepEqual(hits, []); +}); diff --git a/tests/unit/xiaomi-mimo-models-fetch-15307.test.ts b/tests/unit/xiaomi-mimo-models-fetch-15307.test.ts new file mode 100644 index 000000000000..3157e8515aec --- /dev/null +++ b/tests/unit/xiaomi-mimo-models-fetch-15307.test.ts @@ -0,0 +1,180 @@ +/** + * TDD regression for #15307: Xiaomi MiMo "import models" served the 2-entry + * hardcoded registry seed with "API unavailable — using local catalog", even + * though the upstream `GET /v1/models` answers with a full live catalog + * (8 models on the token-plan host, including mimo-v2.6-pro / mimo-v2.6-flash + * and the ASR/TTS variants) and chat completions work against the same key. + * + * Root cause: `xiaomi-mimo` / `xiaomi-mimo-token-plan` are keyed OpenAI-style + * providers with a real registry `baseUrl`, but are not classified by any + * live-fetch branch of the import route — they are not `openai-compatible-*`, + * not self-hosted, and were not in NAMED_OPENAI_STYLE_PROVIDERS. So the route + * never probed the upstream `/models` and fell through to the registry's tiny + * hardcoded `models[]` (2 entries), while inference kept working through + * DefaultExecutor. Same case as #4249 (vercel-ai-gateway), #4202 (zenmux) and + * #3976 (llm7/byteplus). + * + * Fix: add `xiaomi-mimo` and `xiaomi-mimo-token-plan` to + * NAMED_OPENAI_STYLE_PROVIDERS so the route does a live `/models` + * fetch, falling back to the local catalog only when the upstream fetch fails — + * import never breaks. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-15307-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const modelsRoute = await import("../../src/app/api/providers/[id]/models/route.ts"); +const providerSets = await import( + "../../src/app/api/providers/[id]/models/discovery/providerSets.ts" +); + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +interface ModelsBody { + provider: string; + connectionId: string; + models: Array<{ id: string }>; + source?: string; +} + +test("#15307 xiaomi-mimo providers are classified for live /models discovery", () => { + assert.ok(providerSets.isNamedOpenAIStyleProvider("xiaomi-mimo")); + assert.ok(providerSets.isNamedOpenAIStyleProvider("xiaomi-mimo-token-plan")); +}); + +test("#15307 xiaomi-mimo import fetches the live /v1/models catalog", async () => { + await resetStorage(); + const connection = await providersDb.createProviderConnection({ + provider: "xiaomi-mimo", + authType: "apikey", + name: "mimo-live", + apiKey: "mk_test_key", + }); + + let fetched = false; + const originalFetch = globalThis.fetch; + globalThis.fetch = async (url) => { + // `${base}/models` after stripping `/v1` → `https://api.xiaomimimo.com/v1/models`. + if (String(url) === "https://api.xiaomimimo.com/v1/models") { + fetched = true; + return Response.json({ + object: "list", + data: [ + { id: "mimo-v2.5-pro" }, + { id: "mimo-v2.5" }, + { id: "mimo-v2.6-pro" }, + { id: "mimo-v2.6-flash" }, + { id: "mimo-v2.5-asr" }, + { id: "mimo-v2.5-tts" }, + { id: "mimo-v2.5-tts-voiceclone" }, + { id: "mimo-v2.5-tts-voicedesign" }, + ], + }); + } + // Bogus probe variants (…/v1/v1/models, …/models) → 404 + return new Response("not found", { status: 404 }); + }; + + try { + const response = await modelsRoute.GET( + new Request(`http://localhost/api/providers/${connection.id}/models?refresh=true`), + { params: { id: connection.id } } + ); + assert.equal(response.status, 200); + const body = (await response.json()) as ModelsBody; + assert.equal(body.provider, "xiaomi-mimo"); + assert.equal(body.source, "api", "should serve the live upstream catalog, not local_catalog"); + assert.ok(fetched, "should have probed https://api.xiaomimimo.com/v1/models"); + const ids = body.models.map((m) => m.id); + for (const expected of ["mimo-v2.6-pro", "mimo-v2.6-flash", "mimo-v2.5-asr", "mimo-v2.5-tts"]) { + assert.ok( + ids.includes(expected), + `live model ${expected} missing from catalog: ${ids.join(",")}` + ); + } + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("#15307 xiaomi-mimo-token-plan import fetches the live token-plan /v1/models catalog", async () => { + await resetStorage(); + const connection = await providersDb.createProviderConnection({ + provider: "xiaomi-mimo-token-plan", + authType: "apikey", + name: "mimo-tp-live", + apiKey: "tp_test_key", + }); + + let fetched = false; + const originalFetch = globalThis.fetch; + globalThis.fetch = async (url) => { + if (String(url) === "https://token-plan-sgp.xiaomimimo.com/v1/models") { + fetched = true; + return Response.json({ + object: "list", + data: [{ id: "mimo-v2.5-pro" }, { id: "mimo-v2.5" }, { id: "mimo-v2.6-pro" }], + }); + } + return new Response("not found", { status: 404 }); + }; + + try { + const response = await modelsRoute.GET( + new Request(`http://localhost/api/providers/${connection.id}/models?refresh=true`), + { params: { id: connection.id } } + ); + assert.equal(response.status, 200); + const body = (await response.json()) as ModelsBody; + assert.equal(body.provider, "xiaomi-mimo-token-plan"); + assert.equal(body.source, "api", "should serve the live upstream catalog, not local_catalog"); + assert.ok(fetched, "should have probed https://token-plan-sgp.xiaomimimo.com/v1/models"); + const ids = body.models.map((m) => m.id); + assert.ok(ids.includes("mimo-v2.6-pro"), `live model missing: ${ids.join(",")}`); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("#15307 xiaomi-mimo import falls back to the local catalog when the live fetch fails", async () => { + await resetStorage(); + const connection = await providersDb.createProviderConnection({ + provider: "xiaomi-mimo", + authType: "apikey", + name: "mimo-fallback", + apiKey: "mk_test_key_2", + }); + + const originalFetch = globalThis.fetch; + globalThis.fetch = async () => new Response("bad gateway", { status: 502 }); + + try { + const response = await modelsRoute.GET( + new Request(`http://localhost/api/providers/${connection.id}/models?refresh=true`), + { params: { id: connection.id } } + ); + assert.equal(response.status, 200); + const body = (await response.json()) as ModelsBody; + assert.equal(body.provider, "xiaomi-mimo"); + assert.equal(body.source, "local_catalog", "import must not break when upstream is down"); + assert.ok(body.models.length > 0, "fallback catalog should be non-empty"); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/tests/unit/zai-partial-discovery-registry-union.test.ts b/tests/unit/zai-partial-discovery-registry-union.test.ts new file mode 100644 index 000000000000..ff8412c6fbdd --- /dev/null +++ b/tests/unit/zai-partial-discovery-registry-union.test.ts @@ -0,0 +1,209 @@ +/** + * Production incident (2026-10-05): z.ai's live model discovery (the + * Anthropic-compat /models surface) omits glm-5.3-flash, while the model is + * fully dispatchable on the provider's coding-plan endpoint — the static + * registry marks exactly that with a per-model `targetFormat` override. + * Because live discovery is authoritative by default + * (providerUsesAuthoritativeLiveCatalog), the synced snapshot vetoed the + * registry-known model and every combo targeting zai/glm-5.3-flash-max failed + * pre-dispatch with "model_not_in_catalog" (ALL_TARGETS_SKIPPED) even though + * both the static registry and the model capability sync knew the model. + * + * Fix under test: getActiveSyncedCatalog unions registry models that carry an + * explicit per-model dispatch intent (`targetFormat`) into the discovered set. + * The union is bounded: synced metadata still wins for ids discovery knows, + * and registry models without the marker stay vetoed, preserving the + * authoritative-catalog guarantee for everything discovery did report. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-zai-partial-discovery-")); + +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "zai-partial-discovery-test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const { replaceSyncedAvailableModelsForConnection } = await import("../../src/lib/db/models.ts"); +const { getModelInfo } = await import("../../src/sse/services/model.ts"); +const { zaiProvider } = await import("../../open-sse/config/providers/registry/zai/index.ts"); + +const PROVIDER = "zai"; +const CONNECTION_ID = "zai-partial-discovery-union"; +// Dispatchable upstream, present in the static registry with a targetFormat +// override, but absent from the provider's discovery snapshot. +const MISSING_TAGGED_MODEL = "glm-5.3-flash"; +const EFFORT_TARGET = `${PROVIDER}/${MISSING_TAGGED_MODEL}-max`; +// In the registry but WITHOUT the dispatch marker — and deliberately omitted +// from the seeded snapshot to prove partial discovery still vetoes it. +const MISSING_UNTAGGED_MODEL = "glm-4.7"; +// What the provider's discovery surface actually returned (production +// snapshot from the incident). +const DISCOVERED_MODELS = [ + "glm-5.3", + "glm-5.2", + "glm-5.1", + "glm-5", + "glm-5-turbo", + "glm-4.7-flash", +]; + +function registryModel(id: string) { + return zaiProvider.models.find((model) => model.id === id); +} + +async function seedPartialDiscovery(): Promise { + const db = core.getDbInstance(); + const now = new Date().toISOString(); + db.prepare( + `INSERT OR REPLACE INTO provider_connections (id, provider, is_active, created_at, updated_at) + VALUES (?, ?, 1, ?, ?)` + ).run(CONNECTION_ID, PROVIDER, now, now); + + await replaceSyncedAvailableModelsForConnection( + PROVIDER, + CONNECTION_ID, + DISCOVERED_MODELS.map((id) => ({ id, name: id, source: "imported" })) + ); +} + +test.beforeEach(async () => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); + + await seedPartialDiscovery(); +}); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test("precondition: the missing model is registry-curated with dispatch intent, an untagged peer is not", () => { + const tagged = registryModel(MISSING_TAGGED_MODEL); + assert.ok(tagged, `${MISSING_TAGGED_MODEL} must exist in the zai static registry`); + assert.equal(tagged.targetFormat, "openai"); + assert.ok( + tagged.supportedThinkingEfforts?.includes("max"), + "the incident target is an effort variant of the tagged base" + ); + + const untagged = registryModel(MISSING_UNTAGGED_MODEL); + assert.ok(untagged, `${MISSING_UNTAGGED_MODEL} must exist in the zai static registry`); + assert.equal(untagged.targetFormat, undefined); +}); + +test("a fresh partial catalog vetoes a dispatch-tagged registry model — the incident", async () => { + const resolved = await getModelInfo(EFFORT_TARGET); + + // The tagged base is unioned from the registry, so the effort variant + // resolves against it and the model becomes available. + assert.equal(resolved.provider, PROVIDER); + assert.match(resolved.model ?? "", /^glm-5\.3-flash/); + // The dispatch-format handoff is the point of the fix: the resolved info + // must carry the registry's targetFormat so the request leaves on the + // OpenAI-compatible coding-plan surface, not the provider default. + assert.equal(resolved.targetFormat, "openai"); +}); + +test("an empty-but-fresh discovery snapshot never becomes authoritative from tagged rows", async () => { + await replaceSyncedAvailableModelsForConnection(PROVIDER, CONNECTION_ID, []); + const { getActiveSyncedCatalog } = await import("../../src/lib/db/models/activeSyncedCatalog.ts"); + + const catalog = await getActiveSyncedCatalog(PROVIDER); + + assert.equal(catalog.authoritative, false); + assert.ok(!catalog.models.some((model) => model.id === MISSING_TAGGED_MODEL)); +}); + +test("listing surfaces agree with dispatch: the tagged base is listed with registry provenance", async () => { + const { getAllActiveSyncedModels } = + await import("../../src/lib/db/models/activeSyncedCatalog.ts"); + + const listings = await getAllActiveSyncedModels(); + + const listed = (listings[PROVIDER] ?? []).find((model) => model.id === MISSING_TAGGED_MODEL); + assert.ok(listed, "a model dispatch accepts must be discoverable in listings"); + assert.equal(listed.catalogOrigin, "registry"); +}); + +test("a tagged provider without the opt-in flag keeps discovery gating", async () => { + const { openaiProvider } = + await import("../../open-sse/config/providers/registry/openai/index.ts"); + const { getAllActiveSyncedModels } = + await import("../../src/lib/db/models/activeSyncedCatalog.ts"); + + // openai carries targetFormat tags but has NOT opted into the union: a + // discovery snapshot omitting one of its tagged models must keep vetoing it. + const taggedModel = openaiProvider.models.find( + (model) => typeof model.targetFormat === "string" && model.targetFormat.length > 0 + ); + assert.ok(taggedModel, "precondition: openai registry has a targetFormat-tagged model"); + assert.notEqual(openaiProvider.registryDispatchUnion, true); + + const db = core.getDbInstance(); + const now = new Date().toISOString(); + db.prepare( + `INSERT OR REPLACE INTO provider_connections (id, provider, is_active, created_at, updated_at) + VALUES (?, ?, 1, ?, ?)` + ).run("openai-no-union-flag", "openai", now, now); + const discoveredIds = openaiProvider.models + .filter((model) => model.id !== taggedModel.id) + .map((model) => ({ id: model.id, name: model.name, source: "imported" })); + await replaceSyncedAvailableModelsForConnection("openai", "openai-no-union-flag", discoveredIds); + + const resolved = await getModelInfo(`openai/${taggedModel.id}`); + assert.equal(resolved.provider, null); + assert.equal(resolved.errorType, "model_not_found"); + + const listings = await getAllActiveSyncedModels(); + assert.ok(!(listings.openai ?? []).some((model) => model.id === taggedModel.id)); +}); + +test("the tagged base joins the authoritative catalog without displacing discovery rows", async () => { + const { getActiveSyncedCatalog } = await import("../../src/lib/db/models/activeSyncedCatalog.ts"); + + const catalog = await getActiveSyncedCatalog(PROVIDER); + + assert.equal(catalog.authoritative, true); + const byId = new Map(catalog.models.map((model) => [model.id, model])); + assert.ok(byId.has(MISSING_TAGGED_MODEL), "tagged base must be unioned in"); + + // Merge precedence: discovery spoke for glm-5.3, so its synced metadata must + // win — the registry's display name ("GLM 5.3") and dispatch tag ("openai") + // must not overwrite it. A flipped merge direction would revert learned + // metadata to static registry values for every tagged model. + const discovered = byId.get("glm-5.3"); + assert.ok(discovered, "discovered rows must be preserved"); + assert.equal(discovered.source, "imported"); + assert.equal(discovered.name, "glm-5.3"); + assert.equal(discovered.targetFormat, undefined); + + // The unioned row carries the registry's dispatch intent and capability + // contract; visionBridgeRouter and the combo context filter read these + // fields off the row without a registry fallback. + const tagged = byId.get(MISSING_TAGGED_MODEL); + assert.ok(tagged, "unioned row must expose its metadata"); + assert.equal(tagged.targetFormat, "openai"); + assert.ok(tagged.supportedThinkingEfforts?.includes("max")); + // The row and its effort array are process-lifetime singletons handed out + // by reference (model.ts assigns the array into runtime metadata), so both + // must be frozen — a normalize rebuild would otherwise discard the freeze. + assert.equal(Object.isFrozen(tagged), true); + assert.equal(Object.isFrozen(tagged.supportedThinkingEfforts), true); + assert.equal(tagged.supportsVision, true); + assert.equal(tagged.contextWindow, 1000000); + assert.equal(tagged.outputTokenLimit, 131072); +}); + +test("an untagged registry model omitted from discovery stays vetoed", async () => { + const resolved = await getModelInfo(`${PROVIDER}/${MISSING_UNTAGGED_MODEL}`); + + assert.equal(resolved.provider, null); + assert.equal(resolved.errorType, "model_not_found"); + assert.match(resolved.errorMessage, /active live catalog/i); +}); diff --git a/tests/unit/zed-client-version-fetch.test.ts b/tests/unit/zed-client-version-fetch.test.ts new file mode 100644 index 000000000000..609cdd18378b --- /dev/null +++ b/tests/unit/zed-client-version-fetch.test.ts @@ -0,0 +1,49 @@ +/** + * GitHub lookup for the advertised Zed editor version. + * + * Zed gates hosted completions on x-zed-version. The captured 0.200.0 pin is + * the floor; a newer dotted triple from zed-industries/zed replaces it for + * 6 hours. A rejected fetch, a non-triple, or an older tag stays on the pin. + */ +import assert from "node:assert/strict"; +import test from "node:test"; + +const zed = await import("../../open-sse/executors/zedClientVersion.ts"); + +const ZED_RELEASE_URL = "https://api.github.com/repos/zed-industries/zed/releases/latest"; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +test.afterEach(() => { + zed.resetZedClientVersionCache(); +}); + +test("a fetched version newer than the pin is returned", async () => { + const urls: string[] = []; + const fetchMock = async (url: string | URL | Request) => { + urls.push(String(url)); + return jsonResponse({ tag_name: "v1.22.0" }); + }; + + assert.equal(await zed.refreshZedClientVersion(fetchMock as typeof fetch), "1.22.0"); + assert.equal(zed.getZedClientVersion(), "1.22.0"); + assert.equal(urls.length, 1); + assert.equal(urls[0], ZED_RELEASE_URL); +}); + +test("a fetch that rejects falls back to the pin", async () => { + const fetchMock = async () => { + throw new Error("github unreachable"); + }; + + assert.equal( + await zed.refreshZedClientVersion(fetchMock as typeof fetch), + zed.ZED_CLIENT_VERSION + ); + assert.equal(zed.getZedClientVersion(), "0.200.0"); +});